From c492efaf307b228cc34a76e3da60786bc3580a03 Mon Sep 17 00:00:00 2001 From: seungyeonnnnnni Date: Tue, 12 Jul 2022 03:11:00 +0900 Subject: [PATCH] =?UTF-8?q?[#4]=F0=9F=94=84=20h2=20database=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 + h2/build.bat | 7 + h2/build.sh | 18 + h2/docs/h2.pdf | Bin 0 -> 2018998 bytes h2/docs/html/advanced.html | 2023 +++ h2/docs/html/architecture.html | 269 + h2/docs/html/build.html | 412 + h2/docs/html/changelog.html | 1075 ++ h2/docs/html/cheatSheet.html | 221 + h2/docs/html/commands.html | 3366 +++++ h2/docs/html/datatypes.html | 1095 ++ h2/docs/html/download-archive.html | 274 + h2/docs/html/download.html | 181 + h2/docs/html/faq.html | 398 + h2/docs/html/features.html | 1955 +++ h2/docs/html/fragments.html | 133 + h2/docs/html/frame.html | 40 + h2/docs/html/functions-aggregate.html | 1292 ++ h2/docs/html/functions-window.html | 512 + h2/docs/html/functions.html | 4124 ++++++ h2/docs/html/grammar.html | 3615 ++++++ h2/docs/html/history.html | 284 + .../images/connection-mode-embedded-2.png | Bin 0 -> 48466 bytes .../html/images/connection-mode-embedded.png | Bin 0 -> 20407 bytes .../html/images/connection-mode-mixed-2.png | Bin 0 -> 70685 bytes h2/docs/html/images/connection-mode-mixed.png | Bin 0 -> 31588 bytes .../html/images/connection-mode-remote-2.png | Bin 0 -> 58248 bytes .../html/images/connection-mode-remote.png | Bin 0 -> 25020 bytes h2/docs/html/images/console-2.png | Bin 0 -> 68604 bytes h2/docs/html/images/console.png | Bin 0 -> 29569 bytes h2/docs/html/images/db-16.png | Bin 0 -> 547 bytes h2/docs/html/images/db-64-t.png | Bin 0 -> 11867 bytes h2/docs/html/images/div-d.png | Bin 0 -> 8289 bytes h2/docs/html/images/div-ke.png | Bin 0 -> 763 bytes h2/docs/html/images/div-ks.png | Bin 0 -> 759 bytes h2/docs/html/images/div-le.png | Bin 0 -> 387 bytes h2/docs/html/images/div-ls.png | Bin 0 -> 396 bytes h2/docs/html/images/div-te.png | Bin 0 -> 739 bytes h2/docs/html/images/div-ts.png | Bin 0 -> 727 bytes h2/docs/html/images/download-2.png | Bin 0 -> 2673 bytes h2/docs/html/images/download.png | Bin 0 -> 745 bytes h2/docs/html/images/h2-logo-2.png | Bin 0 -> 4810 bytes h2/docs/html/images/h2-logo.png | Bin 0 -> 3361 bytes h2/docs/html/images/h2-logo_square.png | Bin 0 -> 2330 bytes h2/docs/html/images/icon_disconnect.gif | Bin 0 -> 114 bytes h2/docs/html/images/language_de.png | Bin 0 -> 240 bytes h2/docs/html/images/language_en.gif | Bin 0 -> 268 bytes h2/docs/html/images/language_ja.gif | Bin 0 -> 685 bytes h2/docs/html/images/mail-support.png | Bin 0 -> 756 bytes h2/docs/html/images/paypal-donate.png | Bin 0 -> 6172 bytes h2/docs/html/images/performance.png | Bin 0 -> 2343 bytes h2/docs/html/images/quickstart-1.png | Bin 0 -> 13911 bytes h2/docs/html/images/quickstart-2.png | Bin 0 -> 3765 bytes h2/docs/html/images/quickstart-3.png | Bin 0 -> 11783 bytes h2/docs/html/images/quickstart-4.png | Bin 0 -> 30387 bytes h2/docs/html/images/quickstart-5.png | Bin 0 -> 36043 bytes h2/docs/html/images/quickstart-6.png | Bin 0 -> 31108 bytes h2/docs/html/images/screenshot.png | Bin 0 -> 16894 bytes h2/docs/html/index.js | 199 + h2/docs/html/installation.html | 233 + h2/docs/html/license.html | 518 + h2/docs/html/links.html | 822 ++ h2/docs/html/main.html | 160 + h2/docs/html/mainWeb.html | 139 + h2/docs/html/migration-to-v2.html | 301 + h2/docs/html/mvstore.html | 863 ++ h2/docs/html/navigation.js | 195 + h2/docs/html/performance.html | 1009 ++ h2/docs/html/quickstart.html | 219 + h2/docs/html/search.js | 273 + h2/docs/html/security.html | 187 + h2/docs/html/source.html | 55 + h2/docs/html/sourceError.html | 255 + h2/docs/html/stylesheet.css | 396 + h2/docs/html/stylesheetPdf.css | 162 + h2/docs/html/systemtables.html | 2034 +++ h2/docs/html/tutorial.html | 1589 +++ h2/docs/index.html | 44 + h2/docs/javadoc/allclasses-frame.html | 130 + h2/docs/javadoc/allclasses-noframe.html | 130 + h2/docs/javadoc/constant-values.html | 2678 ++++ h2/docs/javadoc/deprecated-list.html | 213 + h2/docs/javadoc/help-doc.html | 223 + h2/docs/javadoc/index-all.html | 10585 ++++++++++++++++ h2/docs/javadoc/index.html | 75 + h2/docs/javadoc/org/h2/api/Aggregate.html | 311 + .../javadoc/org/h2/api/AggregateFunction.html | 315 + .../org/h2/api/CredentialsValidator.html | 249 + .../org/h2/api/DatabaseEventListener.html | 518 + h2/docs/javadoc/org/h2/api/ErrorCode.html | 5792 +++++++++ h2/docs/javadoc/org/h2/api/H2Type.html | 993 ++ h2/docs/javadoc/org/h2/api/Interval.html | 1068 ++ .../javadoc/org/h2/api/IntervalQualifier.html | 786 ++ .../org/h2/api/JavaObjectSerializer.html | 258 + h2/docs/javadoc/org/h2/api/TableEngine.html | 230 + h2/docs/javadoc/org/h2/api/Trigger.html | 447 + .../javadoc/org/h2/api/UserToRolesMapper.html | 248 + h2/docs/javadoc/org/h2/api/package-frame.html | 37 + .../javadoc/org/h2/api/package-summary.html | 250 + h2/docs/javadoc/org/h2/api/package-tree.html | 168 + .../org/h2/engine/CastDataProvider.html | 312 + h2/docs/javadoc/org/h2/engine/Comment.html | 446 + .../javadoc/org/h2/engine/ConnectionInfo.html | 807 ++ h2/docs/javadoc/org/h2/engine/Constants.html | 2002 +++ h2/docs/javadoc/org/h2/engine/Database.html | 2899 +++++ h2/docs/javadoc/org/h2/engine/DbObject.html | 1160 ++ h2/docs/javadoc/org/h2/engine/DbSettings.html | 888 ++ h2/docs/javadoc/org/h2/engine/Engine.html | 247 + .../org/h2/engine/GeneratedKeysMode.html | 352 + .../javadoc/org/h2/engine/IsolationLevel.html | 547 + h2/docs/javadoc/org/h2/engine/MetaRecord.html | 366 + .../org/h2/engine/Mode.CharPadding.html | 369 + .../org/h2/engine/Mode.ExpressionNames.html | 410 + .../javadoc/org/h2/engine/Mode.ModeEnum.html | 451 + .../engine/Mode.UniqueIndexNullsHandling.html | 372 + .../h2/engine/Mode.ViewExpressionNames.html | 367 + h2/docs/javadoc/org/h2/engine/Mode.html | 1332 ++ h2/docs/javadoc/org/h2/engine/Procedure.html | 286 + .../QueryStatisticsData.QueryEntry.html | 488 + .../org/h2/engine/QueryStatisticsData.html | 331 + h2/docs/javadoc/org/h2/engine/Right.html | 685 + h2/docs/javadoc/org/h2/engine/RightOwner.html | 487 + h2/docs/javadoc/org/h2/engine/Role.html | 466 + .../h2/engine/Session.DynamicSettings.html | 305 + .../org/h2/engine/Session.StaticSettings.html | 324 + h2/docs/javadoc/org/h2/engine/Session.html | 751 ++ .../org/h2/engine/SessionLocal.Savepoint.html | 241 + .../org/h2/engine/SessionLocal.State.html | 403 + .../h2/engine/SessionLocal.TimeoutValue.html | 200 + .../javadoc/org/h2/engine/SessionLocal.html | 2913 +++++ .../javadoc/org/h2/engine/SessionRemote.html | 1861 +++ h2/docs/javadoc/org/h2/engine/Setting.html | 536 + .../javadoc/org/h2/engine/SettingsBase.html | 400 + .../javadoc/org/h2/engine/SysProperties.html | 1139 ++ h2/docs/javadoc/org/h2/engine/User.html | 652 + .../javadoc/org/h2/engine/UserBuilder.html | 284 + .../javadoc/org/h2/engine/package-frame.html | 61 + .../org/h2/engine/package-summary.html | 385 + .../javadoc/org/h2/engine/package-tree.html | 196 + .../h2/fulltext/FullText.FullTextTrigger.html | 401 + h2/docs/javadoc/org/h2/fulltext/FullText.html | 901 ++ .../FullTextLucene.FullTextTrigger.html | 391 + .../org/h2/fulltext/FullTextLucene.html | 699 + .../javadoc/org/h2/fulltext/IndexInfo.html | 355 + .../org/h2/fulltext/package-frame.html | 24 + .../org/h2/fulltext/package-summary.html | 181 + .../javadoc/org/h2/fulltext/package-tree.html | 142 + h2/docs/javadoc/org/h2/jdbc/JdbcArray.html | 639 + .../org/h2/jdbc/JdbcBatchUpdateException.html | 339 + h2/docs/javadoc/org/h2/jdbc/JdbcBlob.html | 634 + .../org/h2/jdbc/JdbcCallableStatement.html | 3890 ++++++ h2/docs/javadoc/org/h2/jdbc/JdbcClob.html | 672 + .../javadoc/org/h2/jdbc/JdbcConnection.html | 2047 +++ .../jdbc/JdbcConnectionBackwardsCompat.html | 171 + .../org/h2/jdbc/JdbcDatabaseMetaData.html | 4948 ++++++++ .../JdbcDatabaseMetaDataBackwardsCompat.html | 171 + .../javadoc/org/h2/jdbc/JdbcException.html | 318 + .../javadoc/org/h2/jdbc/JdbcLob.State.html | 380 + h2/docs/javadoc/org/h2/jdbc/JdbcLob.html | 311 + .../org/h2/jdbc/JdbcParameterMetaData.html | 585 + .../org/h2/jdbc/JdbcPreparedStatement.html | 2364 ++++ .../javadoc/org/h2/jdbc/JdbcResultSet.html | 5990 +++++++++ .../org/h2/jdbc/JdbcResultSetMetaData.html | 915 ++ .../org/h2/jdbc/JdbcSQLDataException.html | 491 + .../javadoc/org/h2/jdbc/JdbcSQLException.html | 481 + .../JdbcSQLFeatureNotSupportedException.html | 491 + ...IntegrityConstraintViolationException.html | 491 + ...cSQLInvalidAuthorizationSpecException.html | 491 + ...dbcSQLNonTransientConnectionException.html | 491 + .../h2/jdbc/JdbcSQLNonTransientException.html | 486 + .../h2/jdbc/JdbcSQLSyntaxErrorException.html | 491 + .../org/h2/jdbc/JdbcSQLTimeoutException.html | 491 + .../JdbcSQLTransactionRollbackException.html | 491 + .../h2/jdbc/JdbcSQLTransientException.html | 486 + h2/docs/javadoc/org/h2/jdbc/JdbcSQLXML.html | 511 + .../javadoc/org/h2/jdbc/JdbcSavepoint.html | 327 + .../javadoc/org/h2/jdbc/JdbcStatement.html | 2054 +++ .../h2/jdbc/JdbcStatementBackwardsCompat.html | 265 + .../javadoc/org/h2/jdbc/package-frame.html | 59 + .../javadoc/org/h2/jdbc/package-summary.html | 374 + h2/docs/javadoc/org/h2/jdbc/package-tree.html | 248 + .../org/h2/jdbcx/JdbcConnectionPool.html | 633 + .../JdbcConnectionPoolBackwardsCompat.html | 171 + .../javadoc/org/h2/jdbcx/JdbcDataSource.html | 930 ++ .../jdbcx/JdbcDataSourceBackwardsCompat.html | 171 + .../org/h2/jdbcx/JdbcDataSourceFactory.html | 323 + .../org/h2/jdbcx/JdbcXAConnection.html | 703 + h2/docs/javadoc/org/h2/jdbcx/JdbcXid.html | 328 + .../javadoc/org/h2/jdbcx/package-frame.html | 29 + .../javadoc/org/h2/jdbcx/package-summary.html | 206 + .../javadoc/org/h2/jdbcx/package-tree.html | 148 + h2/docs/javadoc/org/h2/tools/Backup.html | 386 + .../org/h2/tools/ChangeFileEncryption.html | 390 + .../javadoc/org/h2/tools/CompressTool.html | 452 + h2/docs/javadoc/org/h2/tools/Console.html | 394 + .../org/h2/tools/ConvertTraceFile.html | 346 + .../javadoc/org/h2/tools/CreateCluster.html | 385 + h2/docs/javadoc/org/h2/tools/Csv.html | 956 ++ .../javadoc/org/h2/tools/DeleteDbFiles.html | 372 + h2/docs/javadoc/org/h2/tools/GUIConsole.html | 585 + .../javadoc/org/h2/tools/MultiDimension.html | 513 + h2/docs/javadoc/org/h2/tools/Recover.html | 710 ++ h2/docs/javadoc/org/h2/tools/Restore.html | 374 + h2/docs/javadoc/org/h2/tools/RunScript.html | 435 + h2/docs/javadoc/org/h2/tools/Script.html | 421 + h2/docs/javadoc/org/h2/tools/Server.html | 860 ++ h2/docs/javadoc/org/h2/tools/Shell.html | 490 + .../h2/tools/SimpleResultSet.SimpleArray.html | 492 + .../javadoc/org/h2/tools/SimpleResultSet.html | 5813 +++++++++ .../javadoc/org/h2/tools/SimpleRowSource.html | 272 + .../javadoc/org/h2/tools/TriggerAdapter.html | 514 + h2/docs/javadoc/org/h2/tools/Upgrade.html | 305 + .../javadoc/org/h2/tools/package-frame.html | 43 + .../javadoc/org/h2/tools/package-summary.html | 290 + .../javadoc/org/h2/tools/package-tree.html | 165 + h2/docs/javadoc/overview-frame.html | 26 + h2/docs/javadoc/overview-summary.html | 177 + h2/docs/javadoc/overview-tree.html | 364 + h2/docs/javadoc/package-list | 6 + h2/docs/javadoc/script.js | 30 + h2/docs/javadoc/serialized-form.html | 549 + h2/docs/javadoc/stylesheet.css | 574 + h2/service/0_run_server_debug.bat | 68 + h2/service/1_install_service.bat | 61 + h2/service/2_start_service.bat | 3 + h2/service/3_start_browser.bat | 1 + h2/service/4_stop_service.bat | 1 + h2/service/5_uninstall_service.bat | 53 + h2/service/serviceWrapperLicense.txt | 58 + h2/service/wrapper.conf | 101 + h2/service/wrapper.dll | Bin 0 -> 81920 bytes h2/service/wrapper.exe | Bin 0 -> 204800 bytes h2/service/wrapper.jar | Bin 0 -> 83820 bytes h2/src/docsrc/help/information_schema.csv | 1022 ++ h2/src/docsrc/html/advanced.html | 1909 +++ h2/src/docsrc/html/architecture.html | 155 + h2/src/docsrc/html/build.html | 298 + h2/src/docsrc/html/changelog.html | 961 ++ h2/src/docsrc/html/cheatSheet.html | 221 + h2/src/docsrc/html/commands.html | 181 + h2/src/docsrc/html/datatypes.html | 101 + h2/src/docsrc/html/download-archive.html | 160 + h2/src/docsrc/html/download.html | 67 + h2/src/docsrc/html/faq.html | 284 + h2/src/docsrc/html/features.html | 1841 +++ h2/src/docsrc/html/fragments.html | 133 + h2/src/docsrc/html/frame.html | 40 + h2/src/docsrc/html/functions-aggregate.html | 326 + h2/src/docsrc/html/functions-window.html | 277 + h2/src/docsrc/html/functions.html | 326 + h2/src/docsrc/html/grammar.html | 180 + h2/src/docsrc/html/history.html | 170 + .../images/connection-mode-embedded-2.png | Bin 0 -> 48466 bytes .../html/images/connection-mode-embedded.png | Bin 0 -> 20407 bytes .../html/images/connection-mode-mixed-2.png | Bin 0 -> 70685 bytes .../html/images/connection-mode-mixed.png | Bin 0 -> 31588 bytes .../html/images/connection-mode-remote-2.png | Bin 0 -> 58248 bytes .../html/images/connection-mode-remote.png | Bin 0 -> 25020 bytes h2/src/docsrc/html/images/console-2.png | Bin 0 -> 68604 bytes h2/src/docsrc/html/images/console.png | Bin 0 -> 29569 bytes h2/src/docsrc/html/images/db-16.png | Bin 0 -> 547 bytes h2/src/docsrc/html/images/db-64-t.png | Bin 0 -> 11867 bytes h2/src/docsrc/html/images/download-2.png | Bin 0 -> 2673 bytes h2/src/docsrc/html/images/download.png | Bin 0 -> 745 bytes h2/src/docsrc/html/images/h2-logo-2.png | Bin 0 -> 4810 bytes h2/src/docsrc/html/images/h2-logo.png | Bin 0 -> 3361 bytes h2/src/docsrc/html/images/h2-logo_square.png | Bin 0 -> 2330 bytes h2/src/docsrc/html/images/icon_disconnect.gif | Bin 0 -> 114 bytes h2/src/docsrc/html/images/language_de.png | Bin 0 -> 240 bytes h2/src/docsrc/html/images/language_en.gif | Bin 0 -> 268 bytes h2/src/docsrc/html/images/language_ja.gif | Bin 0 -> 685 bytes h2/src/docsrc/html/images/mail-support.png | Bin 0 -> 756 bytes h2/src/docsrc/html/images/paypal-donate.png | Bin 0 -> 6172 bytes h2/src/docsrc/html/images/performance.png | Bin 0 -> 2343 bytes h2/src/docsrc/html/images/quickstart-1.png | Bin 0 -> 13911 bytes h2/src/docsrc/html/images/quickstart-2.png | Bin 0 -> 3765 bytes h2/src/docsrc/html/images/quickstart-3.png | Bin 0 -> 11783 bytes h2/src/docsrc/html/images/quickstart-4.png | Bin 0 -> 30387 bytes h2/src/docsrc/html/images/quickstart-5.png | Bin 0 -> 36043 bytes h2/src/docsrc/html/images/quickstart-6.png | Bin 0 -> 31108 bytes h2/src/docsrc/html/images/screenshot.png | Bin 0 -> 16894 bytes h2/src/docsrc/html/installation.html | 119 + h2/src/docsrc/html/license.html | 404 + h2/src/docsrc/html/links.html | 708 ++ h2/src/docsrc/html/main.html | 46 + h2/src/docsrc/html/mainWeb.html | 139 + h2/src/docsrc/html/migration-to-v2.html | 187 + h2/src/docsrc/html/mvstore.html | 749 ++ h2/src/docsrc/html/navigation.js | 195 + h2/src/docsrc/html/performance.html | 895 ++ h2/src/docsrc/html/quickstart.html | 105 + h2/src/docsrc/html/search.js | 273 + h2/src/docsrc/html/security.html | 73 + h2/src/docsrc/html/source.html | 55 + h2/src/docsrc/html/sourceError.html | 255 + h2/src/docsrc/html/stylesheet.css | 396 + h2/src/docsrc/html/stylesheetPdf.css | 162 + h2/src/docsrc/html/systemtables.html | 104 + h2/src/docsrc/html/tutorial.html | 1475 +++ .../images/connection-mode-embedded-2.png | Bin 0 -> 48466 bytes .../docsrc/images/connection-mode-mixed-2.png | Bin 0 -> 70685 bytes .../images/connection-mode-remote-2.png | Bin 0 -> 58248 bytes h2/src/docsrc/images/connection-modes.svg | 1118 ++ h2/src/docsrc/images/console-2.png | Bin 0 -> 68604 bytes h2/src/docsrc/images/console.png | Bin 0 -> 29569 bytes h2/src/docsrc/images/console.svg | 515 + h2/src/docsrc/images/db-16.png | Bin 0 -> 547 bytes h2/src/docsrc/images/db-22.png | Bin 0 -> 1239 bytes h2/src/docsrc/images/db-24.png | Bin 0 -> 574 bytes h2/src/docsrc/images/db-32.png | Bin 0 -> 1399 bytes h2/src/docsrc/images/db-64-t.png | Bin 0 -> 11867 bytes h2/src/docsrc/images/db.svg | 308 + h2/src/docsrc/images/download-2.png | Bin 0 -> 2673 bytes h2/src/docsrc/images/download.png | Bin 0 -> 745 bytes h2/src/docsrc/images/download.svg | 125 + h2/src/docsrc/images/favicon.ico | Bin 0 -> 4286 bytes h2/src/docsrc/images/h2-16.png | Bin 0 -> 675 bytes h2/src/docsrc/images/h2-24.png | Bin 0 -> 995 bytes h2/src/docsrc/images/h2-32.png | Bin 0 -> 1362 bytes h2/src/docsrc/images/h2-64.png | Bin 0 -> 2718 bytes h2/src/docsrc/images/h2-logo-2.png | Bin 0 -> 6617 bytes h2/src/docsrc/images/h2-logo.png | Bin 0 -> 12780 bytes h2/src/docsrc/images/h2-logo.svg | 61 + h2/src/docsrc/images/h2_v2_3_7.svg | 61 + h2/src/docsrc/images/paypal-donate.png | Bin 0 -> 6172 bytes h2/src/docsrc/images/screenshot.png | Bin 0 -> 113875 bytes h2/src/docsrc/index.html | 44 + h2/src/installer/buildRelease.bat | 23 + h2/src/installer/buildRelease.sh | 18 + h2/src/installer/checkstyle.xml | 93 + .../eclipse.settings/eclipseCodeStyle.xml | 269 + .../org.eclipse.core.resources.prefs | 2 + .../org.eclipse.jdt.core.prefs | 74 + .../org.eclipse.jdt.launching.prefs | 2 + .../eclipse.settings/org.eclipse.jdt.ui.prefs | 53 + h2/src/installer/favicon.ico | Bin 0 -> 4286 bytes h2/src/installer/h2.bat | 2 + h2/src/installer/h2.nsi | 179 + h2/src/installer/h2.sh | 3 + h2/src/installer/h2w.bat | 2 + h2/src/installer/mvstore/MANIFEST.MF | 23 + h2/src/installer/openoffice.txt | 133 + h2/src/installer/pom-mvstore-template.xml | 35 + h2/src/installer/pom-template.xml | 35 + h2/src/installer/release.txt | 138 + h2/src/installer/source-manifest.mf | 7 + h2/src/installer/source-mvstore-manifest.mf | 7 + .../precompiled/org/h2/util/Utils10.class | Bin 0 -> 1133 bytes h2/src/java10/src/org/h2/util/Utils10.java | 72 + h2/src/java10/src/org/h2/util/package.html | 14 + .../java9/precompiled/org/h2/util/Bits.class | Bin 0 -> 2361 bytes h2/src/java9/src/org/h2/util/Bits.java | 320 + h2/src/java9/src/org/h2/util/package.html | 14 + h2/src/main/META-INF/MANIFEST.MF | 75 + h2/src/main/META-INF/services/java.sql.Driver | 1 + h2/src/main/org/h2/Driver.java | 204 + .../org/h2/JdbcDriverBackwardsCompat.java | 16 + h2/src/main/org/h2/api/Aggregate.java | 60 + h2/src/main/org/h2/api/AggregateFunction.java | 64 + .../main/org/h2/api/CredentialsValidator.java | 32 + .../org/h2/api/DatabaseEventListener.java | 112 + h2/src/main/org/h2/api/ErrorCode.java | 2331 ++++ h2/src/main/org/h2/api/H2Type.java | 321 + h2/src/main/org/h2/api/Interval.java | 635 + h2/src/main/org/h2/api/IntervalQualifier.java | 352 + .../main/org/h2/api/JavaObjectSerializer.java | 34 + h2/src/main/org/h2/api/TableEngine.java | 27 + h2/src/main/org/h2/api/Trigger.java | 104 + h2/src/main/org/h2/api/UserToRolesMapper.java | 33 + h2/src/main/org/h2/api/package.html | 14 + h2/src/main/org/h2/bnf/Bnf.java | 415 + h2/src/main/org/h2/bnf/BnfVisitor.java | 69 + h2/src/main/org/h2/bnf/Rule.java | 38 + h2/src/main/org/h2/bnf/RuleElement.java | 85 + h2/src/main/org/h2/bnf/RuleExtension.java | 49 + h2/src/main/org/h2/bnf/RuleFixed.java | 218 + h2/src/main/org/h2/bnf/RuleHead.java | 38 + h2/src/main/org/h2/bnf/RuleList.java | 90 + h2/src/main/org/h2/bnf/RuleOptional.java | 52 + h2/src/main/org/h2/bnf/RuleRepeat.java | 52 + h2/src/main/org/h2/bnf/Sentence.java | 221 + h2/src/main/org/h2/bnf/context/DbColumn.java | 116 + .../main/org/h2/bnf/context/DbContents.java | 292 + .../org/h2/bnf/context/DbContextRule.java | 361 + .../main/org/h2/bnf/context/DbProcedure.java | 98 + h2/src/main/org/h2/bnf/context/DbSchema.java | 177 + .../org/h2/bnf/context/DbTableOrView.java | 114 + h2/src/main/org/h2/bnf/context/package.html | 14 + h2/src/main/org/h2/bnf/package.html | 14 + h2/src/main/org/h2/command/Command.java | 381 + .../main/org/h2/command/CommandContainer.java | 317 + .../main/org/h2/command/CommandInterface.java | 611 + h2/src/main/org/h2/command/CommandList.java | 126 + h2/src/main/org/h2/command/CommandRemote.java | 321 + h2/src/main/org/h2/command/Parser.java | 9717 ++++++++++++++ h2/src/main/org/h2/command/Prepared.java | 473 + h2/src/main/org/h2/command/Token.java | 757 ++ h2/src/main/org/h2/command/Tokenizer.java | 1400 ++ .../main/org/h2/command/ddl/AlterDomain.java | 111 + .../command/ddl/AlterDomainAddConstraint.java | 105 + .../ddl/AlterDomainDropConstraint.java | 54 + .../command/ddl/AlterDomainExpressions.java | 92 + .../org/h2/command/ddl/AlterDomainRename.java | 52 + .../ddl/AlterDomainRenameConstraint.java | 59 + .../org/h2/command/ddl/AlterIndexRename.java | 74 + .../org/h2/command/ddl/AlterSchemaRename.java | 65 + .../org/h2/command/ddl/AlterSequence.java | 106 + .../main/org/h2/command/ddl/AlterTable.java | 51 + .../command/ddl/AlterTableAddConstraint.java | 496 + .../h2/command/ddl/AlterTableAlterColumn.java | 730 ++ .../command/ddl/AlterTableDropConstraint.java | 82 + .../org/h2/command/ddl/AlterTableRename.java | 64 + .../command/ddl/AlterTableRenameColumn.java | 76 + .../ddl/AlterTableRenameConstraint.java | 66 + h2/src/main/org/h2/command/ddl/AlterUser.java | 101 + h2/src/main/org/h2/command/ddl/AlterView.java | 52 + h2/src/main/org/h2/command/ddl/Analyze.java | 233 + .../h2/command/ddl/CommandWithColumns.java | 165 + .../org/h2/command/ddl/CreateAggregate.java | 69 + .../org/h2/command/ddl/CreateConstant.java | 67 + .../main/org/h2/command/ddl/CreateDomain.java | 131 + .../h2/command/ddl/CreateFunctionAlias.java | 91 + .../main/org/h2/command/ddl/CreateIndex.java | 132 + .../org/h2/command/ddl/CreateLinkedTable.java | 147 + .../main/org/h2/command/ddl/CreateRole.java | 62 + .../main/org/h2/command/ddl/CreateSchema.java | 74 + .../org/h2/command/ddl/CreateSequence.java | 70 + .../org/h2/command/ddl/CreateSynonym.java | 110 + .../org/h2/command/ddl/CreateSynonymData.java | 44 + .../main/org/h2/command/ddl/CreateTable.java | 265 + .../org/h2/command/ddl/CreateTableData.java | 78 + .../org/h2/command/ddl/CreateTrigger.java | 145 + .../main/org/h2/command/ddl/CreateUser.java | 143 + .../main/org/h2/command/ddl/CreateView.java | 152 + .../h2/command/ddl/DeallocateProcedure.java | 38 + .../org/h2/command/ddl/DefineCommand.java | 52 + .../org/h2/command/ddl/DropAggregate.java | 56 + .../main/org/h2/command/ddl/DropConstant.java | 56 + .../main/org/h2/command/ddl/DropDatabase.java | 165 + .../main/org/h2/command/ddl/DropDomain.java | 108 + .../org/h2/command/ddl/DropFunctionAlias.java | 56 + h2/src/main/org/h2/command/ddl/DropIndex.java | 87 + h2/src/main/org/h2/command/ddl/DropRole.java | 60 + .../main/org/h2/command/ddl/DropSchema.java | 83 + .../main/org/h2/command/ddl/DropSequence.java | 57 + .../main/org/h2/command/ddl/DropSynonym.java | 54 + h2/src/main/org/h2/command/ddl/DropTable.java | 149 + .../main/org/h2/command/ddl/DropTrigger.java | 60 + h2/src/main/org/h2/command/ddl/DropUser.java | 74 + h2/src/main/org/h2/command/ddl/DropView.java | 99 + .../main/org/h2/command/ddl/GrantRevoke.java | 225 + .../org/h2/command/ddl/PrepareProcedure.java | 62 + .../org/h2/command/ddl/SchemaCommand.java | 38 + .../h2/command/ddl/SchemaOwnerCommand.java | 38 + .../org/h2/command/ddl/SequenceOptions.java | 362 + .../main/org/h2/command/ddl/SetComment.java | 186 + .../org/h2/command/ddl/TruncateTable.java | 64 + h2/src/main/org/h2/command/ddl/package.html | 14 + .../org/h2/command/dml/AlterTableSet.java | 80 + .../org/h2/command/dml/BackupCommand.java | 143 + h2/src/main/org/h2/command/dml/Call.java | 131 + .../org/h2/command/dml/CommandWithValues.java | 44 + .../h2/command/dml/DataChangeStatement.java | 88 + h2/src/main/org/h2/command/dml/Delete.java | 141 + .../org/h2/command/dml/ExecuteImmediate.java | 57 + .../org/h2/command/dml/ExecuteProcedure.java | 93 + h2/src/main/org/h2/command/dml/Explain.java | 156 + .../dml/FilteredDataChangeStatement.java | 97 + h2/src/main/org/h2/command/dml/Help.java | 163 + h2/src/main/org/h2/command/dml/Insert.java | 456 + h2/src/main/org/h2/command/dml/Merge.java | 349 + .../main/org/h2/command/dml/MergeUsing.java | 570 + .../main/org/h2/command/dml/NoOperation.java | 52 + .../org/h2/command/dml/RunScriptCommand.java | 157 + .../main/org/h2/command/dml/ScriptBase.java | 203 + .../org/h2/command/dml/ScriptCommand.java | 858 ++ h2/src/main/org/h2/command/dml/Set.java | 652 + .../org/h2/command/dml/SetClauseList.java | 404 + .../dml/SetSessionCharacteristics.java | 52 + h2/src/main/org/h2/command/dml/SetTypes.java | 329 + .../h2/command/dml/TransactionCommand.java | 130 + h2/src/main/org/h2/command/dml/Update.java | 187 + h2/src/main/org/h2/command/dml/package.html | 14 + h2/src/main/org/h2/command/package.html | 14 + .../h2/command/query/AllColumnsForPlan.java | 58 + .../main/org/h2/command/query/Optimizer.java | 266 + h2/src/main/org/h2/command/query/Query.java | 1036 ++ .../org/h2/command/query/QueryOrderBy.java | 44 + h2/src/main/org/h2/command/query/Select.java | 1924 +++ .../org/h2/command/query/SelectGroups.java | 433 + .../query/SelectListColumnResolver.java | 80 + .../org/h2/command/query/SelectUnion.java | 459 + .../command/query/TableValueConstructor.java | 397 + h2/src/main/org/h2/command/query/package.html | 14 + .../main/org/h2/compress/CompressDeflate.java | 95 + h2/src/main/org/h2/compress/CompressLZF.java | 473 + h2/src/main/org/h2/compress/CompressNo.java | 37 + h2/src/main/org/h2/compress/Compressor.java | 68 + .../main/org/h2/compress/LZFInputStream.java | 133 + .../main/org/h2/compress/LZFOutputStream.java | 102 + h2/src/main/org/h2/compress/package.html | 14 + h2/src/main/org/h2/constraint/Constraint.java | 212 + .../h2/constraint/ConstraintActionType.java | 44 + .../org/h2/constraint/ConstraintCheck.java | 167 + .../org/h2/constraint/ConstraintDomain.java | 240 + .../h2/constraint/ConstraintReferential.java | 630 + .../org/h2/constraint/ConstraintUnique.java | 162 + .../h2/constraint/DomainColumnResolver.java | 72 + h2/src/main/org/h2/constraint/package.html | 14 + .../main/org/h2/engine/CastDataProvider.java | 53 + h2/src/main/org/h2/engine/Comment.java | 114 + h2/src/main/org/h2/engine/ConnectionInfo.java | 761 ++ h2/src/main/org/h2/engine/Constants.java | 508 + h2/src/main/org/h2/engine/Database.java | 2479 ++++ h2/src/main/org/h2/engine/DbObject.java | 331 + h2/src/main/org/h2/engine/DbSettings.java | 329 + .../org/h2/engine/DelayedDatabaseCloser.java | 76 + h2/src/main/org/h2/engine/Engine.java | 408 + .../main/org/h2/engine/GeneratedKeysMode.java | 65 + h2/src/main/org/h2/engine/IsolationLevel.java | 162 + h2/src/main/org/h2/engine/MetaRecord.java | 208 + h2/src/main/org/h2/engine/Mode.java | 732 ++ .../org/h2/engine/OnExitDatabaseCloser.java | 117 + h2/src/main/org/h2/engine/Procedure.java | 32 + .../org/h2/engine/QueryStatisticsData.java | 192 + h2/src/main/org/h2/engine/Right.java | 191 + h2/src/main/org/h2/engine/RightOwner.java | 259 + h2/src/main/org/h2/engine/Role.java | 87 + h2/src/main/org/h2/engine/Session.java | 310 + h2/src/main/org/h2/engine/SessionLocal.java | 2086 +++ h2/src/main/org/h2/engine/SessionRemote.java | 987 ++ h2/src/main/org/h2/engine/Setting.java | 83 + h2/src/main/org/h2/engine/SettingsBase.java | 134 + h2/src/main/org/h2/engine/SysProperties.java | 449 + h2/src/main/org/h2/engine/User.java | 269 + h2/src/main/org/h2/engine/UserBuilder.java | 36 + h2/src/main/org/h2/engine/package.html | 14 + h2/src/main/org/h2/expression/Alias.java | 126 + .../expression/ArrayConstructorByQuery.java | 102 + .../h2/expression/ArrayElementReference.java | 68 + .../org/h2/expression/BinaryOperation.java | 447 + .../CompatibilityDatePlusTimeOperation.java | 117 + .../h2/expression/ConcatenationOperation.java | 281 + .../h2/expression/DomainValueExpression.java | 78 + h2/src/main/org/h2/expression/Expression.java | 536 + .../org/h2/expression/ExpressionColumn.java | 490 + .../org/h2/expression/ExpressionList.java | 140 + .../org/h2/expression/ExpressionVisitor.java | 416 + .../h2/expression/ExpressionWithFlags.java | 28 + .../ExpressionWithVariableParameters.java | 33 + .../org/h2/expression/FieldReference.java | 72 + h2/src/main/org/h2/expression/Format.java | 99 + .../org/h2/expression/IntervalOperation.java | 380 + h2/src/main/org/h2/expression/Operation0.java | 40 + h2/src/main/org/h2/expression/Operation1.java | 75 + .../main/org/h2/expression/Operation1_2.java | 97 + h2/src/main/org/h2/expression/Operation2.java | 88 + h2/src/main/org/h2/expression/OperationN.java | 132 + h2/src/main/org/h2/expression/Parameter.java | 122 + .../org/h2/expression/ParameterInterface.java | 61 + .../org/h2/expression/ParameterRemote.java | 89 + h2/src/main/org/h2/expression/Rownum.java | 75 + .../main/org/h2/expression/SearchedCase.java | 95 + .../main/org/h2/expression/SequenceValue.java | 92 + h2/src/main/org/h2/expression/SimpleCase.java | 273 + h2/src/main/org/h2/expression/Subquery.java | 169 + .../org/h2/expression/TimeZoneOperation.java | 146 + .../h2/expression/TypedValueExpression.java | 103 + .../org/h2/expression/UnaryOperation.java | 55 + .../org/h2/expression/ValueExpression.java | 152 + h2/src/main/org/h2/expression/Variable.java | 61 + h2/src/main/org/h2/expression/Wildcard.java | 135 + .../aggregate/AbstractAggregate.java | 324 + .../h2/expression/aggregate/Aggregate.java | 1347 ++ .../expression/aggregate/AggregateData.java | 32 + .../aggregate/AggregateDataAvg.java | 90 + .../aggregate/AggregateDataBinarySet.java | 24 + .../aggregate/AggregateDataCollecting.java | 168 + .../aggregate/AggregateDataCorr.java | 96 + .../aggregate/AggregateDataCount.java | 38 + .../aggregate/AggregateDataCovar.java | 70 + .../aggregate/AggregateDataDefault.java | 119 + .../AggregateDataDistinctWithCounts.java | 72 + .../aggregate/AggregateDataEnvelope.java | 72 + .../aggregate/AggregateDataStdVar.java | 90 + .../expression/aggregate/AggregateType.java | 233 + .../expression/aggregate/JavaAggregate.java | 225 + .../aggregate/ListaggArguments.java | 126 + .../expression/aggregate/LongDataCounter.java | 18 + .../h2/expression/aggregate/Percentile.java | 384 + .../org/h2/expression/aggregate/package.html | 14 + .../analysis/DataAnalysisOperation.java | 536 + .../h2/expression/analysis/PartitionData.java | 91 + .../org/h2/expression/analysis/Window.java | 338 + .../h2/expression/analysis/WindowFrame.java | 877 ++ .../expression/analysis/WindowFrameBound.java | 164 + .../analysis/WindowFrameBoundType.java | 54 + .../analysis/WindowFrameExclusion.java | 62 + .../expression/analysis/WindowFrameUnits.java | 40 + .../expression/analysis/WindowFunction.java | 544 + .../analysis/WindowFunctionType.java | 142 + .../org/h2/expression/analysis/package.html | 14 + .../condition/BetweenPredicate.java | 207 + .../h2/expression/condition/BooleanTest.java | 91 + .../h2/expression/condition/CompareLike.java | 634 + .../h2/expression/condition/Comparison.java | 599 + .../h2/expression/condition/Condition.java | 38 + .../expression/condition/ConditionAndOr.java | 367 + .../expression/condition/ConditionAndOrN.java | 341 + .../h2/expression/condition/ConditionIn.java | 270 + .../condition/ConditionInConstantSet.java | 219 + .../condition/ConditionInParameter.java | 224 + .../condition/ConditionInQuery.java | 256 + .../condition/ConditionLocalAndGlobal.java | 152 + .../h2/expression/condition/ConditionNot.java | 109 + .../expression/condition/ExistsPredicate.java | 33 + .../expression/condition/IsJsonPredicate.java | 217 + .../expression/condition/NullPredicate.java | 153 + .../condition/PredicateWithSubquery.java | 66 + .../expression/condition/SimplePredicate.java | 98 + .../expression/condition/TypePredicate.java | 90 + .../expression/condition/UniquePredicate.java | 102 + .../org/h2/expression/condition/package.html | 14 + .../h2/expression/function/ArrayFunction.java | 177 + .../h2/expression/function/BitFunction.java | 725 ++ .../expression/function/BuiltinFunctions.java | 136 + .../expression/function/CSVWriteFunction.java | 126 + .../function/CardinalityExpression.java | 78 + .../function/CastSpecification.java | 115 + .../expression/function/CoalesceFunction.java | 111 + .../CompatibilitySequenceValueFunction.java | 100 + .../expression/function/CompressFunction.java | 77 + .../expression/function/ConcatFunction.java | 118 + .../h2/expression/function/CryptFunction.java | 87 + .../CurrentDateTimeValueFunction.java | 112 + .../CurrentGeneralValueSpecification.java | 147 + .../expression/function/DBObjectFunction.java | 144 + .../function/DataTypeSQLFunction.java | 157 + .../function/DateTimeFormatFunction.java | 313 + .../expression/function/DateTimeFunction.java | 1038 ++ .../function/DayMonthNameFunction.java | 107 + .../h2/expression/function/FileFunction.java | 145 + .../h2/expression/function/Function0_1.java | 96 + .../org/h2/expression/function/Function1.java | 25 + .../h2/expression/function/Function1_2.java | 66 + .../org/h2/expression/function/Function2.java | 58 + .../org/h2/expression/function/FunctionN.java | 77 + .../h2/expression/function/HashFunction.java | 193 + .../h2/expression/function/JavaFunction.java | 140 + .../function/JsonConstructorFunction.java | 171 + .../expression/function/LengthFunction.java | 86 + .../h2/expression/function/MathFunction.java | 395 + .../h2/expression/function/MathFunction1.java | 212 + .../h2/expression/function/MathFunction2.java | 100 + .../expression/function/NamedExpression.java | 20 + .../expression/function/NullIfFunction.java | 50 + .../h2/expression/function/RandFunction.java | 124 + .../expression/function/RegexpFunction.java | 270 + .../function/SessionControlFunction.java | 99 + .../h2/expression/function/SetFunction.java | 64 + .../expression/function/SignalFunction.java | 49 + .../expression/function/SoundexFunction.java | 128 + .../expression/function/StringFunction.java | 244 + .../expression/function/StringFunction1.java | 283 + .../expression/function/StringFunction2.java | 108 + .../function/SubstringFunction.java | 126 + .../expression/function/SysInfoFunction.java | 176 + .../function/TableInfoFunction.java | 111 + .../expression/function/ToCharFunction.java | 1127 ++ .../h2/expression/function/TrimFunction.java | 86 + .../function/TruncateValueFunction.java | 105 + .../h2/expression/function/XMLFunction.java | 161 + .../org/h2/expression/function/package.html | 14 + .../function/table/ArrayTableFunction.java | 178 + .../function/table/CSVReadFunction.java | 119 + .../function/table/JavaTableFunction.java | 63 + .../function/table/LinkSchemaFunction.java | 125 + .../function/table/TableFunction.java | 90 + .../h2/expression/function/table/package.html | 14 + h2/src/main/org/h2/expression/package.html | 14 + h2/src/main/org/h2/fulltext/FullText.java | 1167 ++ .../main/org/h2/fulltext/FullTextLucene.java | 764 ++ .../org/h2/fulltext/FullTextSettings.java | 278 + h2/src/main/org/h2/fulltext/IndexInfo.java | 42 + h2/src/main/org/h2/fulltext/package.html | 14 + h2/src/main/org/h2/index/Cursor.java | 53 + h2/src/main/org/h2/index/DualCursor.java | 48 + h2/src/main/org/h2/index/DualIndex.java | 58 + h2/src/main/org/h2/index/Index.java | 743 ++ h2/src/main/org/h2/index/IndexCondition.java | 434 + h2/src/main/org/h2/index/IndexCursor.java | 326 + h2/src/main/org/h2/index/IndexType.java | 187 + h2/src/main/org/h2/index/LinkedCursor.java | 75 + h2/src/main/org/h2/index/LinkedIndex.java | 266 + h2/src/main/org/h2/index/MetaCursor.java | 48 + h2/src/main/org/h2/index/MetaIndex.java | 129 + .../org/h2/index/QueryExpressionCursor.java | 86 + .../org/h2/index/QueryExpressionIndex.java | 414 + h2/src/main/org/h2/index/RangeCursor.java | 58 + h2/src/main/org/h2/index/RangeIndex.java | 107 + h2/src/main/org/h2/index/SingleRowCursor.java | 53 + h2/src/main/org/h2/index/SpatialIndex.java | 30 + .../index/VirtualConstructedTableIndex.java | 66 + .../main/org/h2/index/VirtualTableCursor.java | 112 + .../main/org/h2/index/VirtualTableIndex.java | 68 + h2/src/main/org/h2/index/package.html | 14 + h2/src/main/org/h2/jdbc/JdbcArray.java | 313 + .../org/h2/jdbc/JdbcBatchUpdateException.java | 78 + h2/src/main/org/h2/jdbc/JdbcBlob.java | 316 + .../org/h2/jdbc/JdbcCallableStatement.java | 1862 +++ h2/src/main/org/h2/jdbc/JdbcClob.java | 264 + h2/src/main/org/h2/jdbc/JdbcConnection.java | 1880 +++ .../jdbc/JdbcConnectionBackwardsCompat.java | 16 + .../org/h2/jdbc/JdbcDatabaseMetaData.java | 2756 ++++ .../JdbcDatabaseMetaDataBackwardsCompat.java | 16 + h2/src/main/org/h2/jdbc/JdbcException.java | 51 + h2/src/main/org/h2/jdbc/JdbcLob.java | 230 + .../org/h2/jdbc/JdbcParameterMetaData.java | 257 + .../org/h2/jdbc/JdbcPreparedStatement.java | 1627 +++ h2/src/main/org/h2/jdbc/JdbcResultSet.java | 4290 +++++++ .../org/h2/jdbc/JdbcResultSetMetaData.java | 465 + .../org/h2/jdbc/JdbcSQLDataException.java | 87 + h2/src/main/org/h2/jdbc/JdbcSQLException.java | 87 + .../JdbcSQLFeatureNotSupportedException.java | 88 + ...IntegrityConstraintViolationException.java | 88 + ...cSQLInvalidAuthorizationSpecException.java | 88 + ...dbcSQLNonTransientConnectionException.java | 88 + .../h2/jdbc/JdbcSQLNonTransientException.java | 87 + .../h2/jdbc/JdbcSQLSyntaxErrorException.java | 87 + .../org/h2/jdbc/JdbcSQLTimeoutException.java | 87 + .../JdbcSQLTransactionRollbackException.java | 88 + .../h2/jdbc/JdbcSQLTransientException.java | 87 + h2/src/main/org/h2/jdbc/JdbcSQLXML.java | 270 + h2/src/main/org/h2/jdbc/JdbcSavepoint.java | 123 + h2/src/main/org/h2/jdbc/JdbcStatement.java | 1472 +++ .../h2/jdbc/JdbcStatementBackwardsCompat.java | 41 + .../main/org/h2/jdbc/meta/DatabaseMeta.java | 395 + .../org/h2/jdbc/meta/DatabaseMetaLegacy.java | 691 + .../org/h2/jdbc/meta/DatabaseMetaLocal.java | 1523 +++ .../h2/jdbc/meta/DatabaseMetaLocalBase.java | 173 + .../org/h2/jdbc/meta/DatabaseMetaRemote.java | 383 + .../org/h2/jdbc/meta/DatabaseMetaServer.java | 198 + h2/src/main/org/h2/jdbc/meta/package.html | 14 + h2/src/main/org/h2/jdbc/package.html | 14 + .../main/org/h2/jdbcx/JdbcConnectionPool.java | 358 + .../JdbcConnectionPoolBackwardsCompat.java | 16 + h2/src/main/org/h2/jdbcx/JdbcDataSource.java | 418 + .../jdbcx/JdbcDataSourceBackwardsCompat.java | 16 + .../org/h2/jdbcx/JdbcDataSourceFactory.java | 90 + .../main/org/h2/jdbcx/JdbcXAConnection.java | 478 + h2/src/main/org/h2/jdbcx/JdbcXid.java | 89 + h2/src/main/org/h2/jdbcx/package.html | 14 + h2/src/main/org/h2/jmx/DatabaseInfo.java | 228 + h2/src/main/org/h2/jmx/DatabaseInfoMBean.java | 116 + h2/src/main/org/h2/jmx/DocumentedMBean.java | 76 + h2/src/main/org/h2/jmx/package.html | 14 + h2/src/main/org/h2/message/DbException.java | 767 ++ h2/src/main/org/h2/message/Trace.java | 356 + h2/src/main/org/h2/message/TraceObject.java | 403 + h2/src/main/org/h2/message/TraceSystem.java | 353 + h2/src/main/org/h2/message/TraceWriter.java | 52 + .../org/h2/message/TraceWriterAdapter.java | 76 + h2/src/main/org/h2/message/package.html | 14 + .../CompatibilityDateTimeValueFunction.java | 99 + .../main/org/h2/mode/DefaultNullOrdering.java | 102 + h2/src/main/org/h2/mode/FunctionInfo.java | 89 + .../main/org/h2/mode/FunctionsDB2Derby.java | 73 + h2/src/main/org/h2/mode/FunctionsLegacy.java | 69 + .../org/h2/mode/FunctionsMSSQLServer.java | 143 + h2/src/main/org/h2/mode/FunctionsMySQL.java | 258 + h2/src/main/org/h2/mode/FunctionsOracle.java | 135 + .../main/org/h2/mode/FunctionsPostgreSQL.java | 377 + h2/src/main/org/h2/mode/ModeFunction.java | 222 + .../org/h2/mode/OnDuplicateKeyValues.java | 64 + h2/src/main/org/h2/mode/PgCatalogSchema.java | 59 + h2/src/main/org/h2/mode/PgCatalogTable.java | 720 ++ h2/src/main/org/h2/mode/Regclass.java | 82 + h2/src/main/org/h2/mode/ToDateParser.java | 376 + h2/src/main/org/h2/mode/ToDateTokenizer.java | 717 ++ h2/src/main/org/h2/mode/package.html | 14 + h2/src/main/org/h2/mvstore/Chunk.java | 548 + h2/src/main/org/h2/mvstore/Cursor.java | 185 + h2/src/main/org/h2/mvstore/CursorPos.java | 89 + h2/src/main/org/h2/mvstore/DataUtils.java | 1190 ++ h2/src/main/org/h2/mvstore/FileStore.java | 427 + .../main/org/h2/mvstore/FreeSpaceBitSet.java | 351 + h2/src/main/org/h2/mvstore/MVMap.java | 2125 ++++ h2/src/main/org/h2/mvstore/MVStore.java | 4159 ++++++ .../main/org/h2/mvstore/MVStoreException.java | 25 + h2/src/main/org/h2/mvstore/MVStoreTool.java | 742 ++ h2/src/main/org/h2/mvstore/OffHeapStore.java | 148 + h2/src/main/org/h2/mvstore/Page.java | 1662 +++ h2/src/main/org/h2/mvstore/RootReference.java | 256 + h2/src/main/org/h2/mvstore/StreamStore.java | 583 + h2/src/main/org/h2/mvstore/WriteBuffer.java | 331 + .../h2/mvstore/cache/CacheLongKeyLIRS.java | 1210 ++ .../org/h2/mvstore/cache/FilePathCache.java | 181 + h2/src/main/org/h2/mvstore/cache/package.html | 14 + .../main/org/h2/mvstore/db/LobStorageMap.java | 602 + .../org/h2/mvstore/db/MVDelegateIndex.java | 152 + .../h2/mvstore/db/MVInDoubtTransaction.java | 47 + h2/src/main/org/h2/mvstore/db/MVIndex.java | 47 + .../org/h2/mvstore/db/MVPlainTempResult.java | 124 + .../org/h2/mvstore/db/MVPrimaryIndex.java | 447 + .../org/h2/mvstore/db/MVSecondaryIndex.java | 445 + .../org/h2/mvstore/db/MVSortedTempResult.java | 389 + .../org/h2/mvstore/db/MVSpatialIndex.java | 550 + h2/src/main/org/h2/mvstore/db/MVTable.java | 946 ++ .../main/org/h2/mvstore/db/MVTempResult.java | 230 + .../org/h2/mvstore/db/NullValueDataType.java | 73 + .../main/org/h2/mvstore/db/RowDataType.java | 262 + h2/src/main/org/h2/mvstore/db/SpatialKey.java | 143 + h2/src/main/org/h2/mvstore/db/Store.java | 416 + .../main/org/h2/mvstore/db/ValueDataType.java | 897 ++ h2/src/main/org/h2/mvstore/db/package.html | 14 + h2/src/main/org/h2/mvstore/package.html | 14 + .../org/h2/mvstore/rtree/DefaultSpatial.java | 75 + .../main/org/h2/mvstore/rtree/MVRTreeMap.java | 632 + h2/src/main/org/h2/mvstore/rtree/Spatial.java | 76 + .../org/h2/mvstore/rtree/SpatialDataType.java | 385 + h2/src/main/org/h2/mvstore/rtree/package.html | 14 + .../h2/mvstore/tx/CommitDecisionMaker.java | 65 + h2/src/main/org/h2/mvstore/tx/Record.java | 118 + .../h2/mvstore/tx/RollbackDecisionMaker.java | 69 + h2/src/main/org/h2/mvstore/tx/Snapshot.java | 54 + .../main/org/h2/mvstore/tx/Transaction.java | 807 ++ .../org/h2/mvstore/tx/TransactionMap.java | 1127 ++ .../org/h2/mvstore/tx/TransactionStore.java | 961 ++ .../org/h2/mvstore/tx/TxDecisionMaker.java | 383 + .../org/h2/mvstore/tx/VersionedBitSet.java | 33 + .../mvstore/tx/VersionedValueCommitted.java | 53 + .../org/h2/mvstore/tx/VersionedValueType.java | 164 + .../mvstore/tx/VersionedValueUncommitted.java | 61 + h2/src/main/org/h2/mvstore/tx/package.html | 14 + .../org/h2/mvstore/type/BasicDataType.java | 98 + .../h2/mvstore/type/ByteArrayDataType.java | 46 + h2/src/main/org/h2/mvstore/type/DataType.java | 95 + .../org/h2/mvstore/type/LongDataType.java | 83 + h2/src/main/org/h2/mvstore/type/MetaType.java | 108 + .../org/h2/mvstore/type/ObjectDataType.java | 1617 +++ .../org/h2/mvstore/type/StatefulDataType.java | 47 + .../org/h2/mvstore/type/StringDataType.java | 72 + h2/src/main/org/h2/mvstore/type/package.html | 14 + h2/src/main/org/h2/package.html | 14 + h2/src/main/org/h2/res/_messages_cs.prop | 201 + h2/src/main/org/h2/res/_messages_de.prop | 201 + h2/src/main/org/h2/res/_messages_en.prop | 201 + h2/src/main/org/h2/res/_messages_es.prop | 201 + h2/src/main/org/h2/res/_messages_fr.prop | 201 + h2/src/main/org/h2/res/_messages_ja.prop | 201 + h2/src/main/org/h2/res/_messages_pl.prop | 201 + h2/src/main/org/h2/res/_messages_pt_br.prop | 201 + h2/src/main/org/h2/res/_messages_ru.prop | 202 + h2/src/main/org/h2/res/_messages_sk.prop | 201 + h2/src/main/org/h2/res/_messages_zh_cn.prop | 201 + h2/src/main/org/h2/res/h2-22-t.png | Bin 0 -> 3870 bytes h2/src/main/org/h2/res/h2-22.png | Bin 0 -> 1239 bytes h2/src/main/org/h2/res/h2-24.png | Bin 0 -> 574 bytes h2/src/main/org/h2/res/h2-64-t.png | Bin 0 -> 11867 bytes h2/src/main/org/h2/res/h2.png | Bin 0 -> 547 bytes h2/src/main/org/h2/res/help.csv | 7495 +++++++++++ h2/src/main/org/h2/res/javadoc.properties | 37 + h2/src/main/org/h2/result/DefaultRow.java | 116 + h2/src/main/org/h2/result/FetchedResult.java | 69 + h2/src/main/org/h2/result/LazyResult.java | 160 + h2/src/main/org/h2/result/LocalResult.java | 696 + h2/src/main/org/h2/result/MergedResult.java | 88 + h2/src/main/org/h2/result/ResultColumn.java | 95 + h2/src/main/org/h2/result/ResultExternal.java | 74 + .../main/org/h2/result/ResultInterface.java | 182 + h2/src/main/org/h2/result/ResultRemote.java | 274 + h2/src/main/org/h2/result/ResultTarget.java | 36 + .../h2/result/ResultWithGeneratedKeys.java | 72 + .../h2/result/ResultWithPaddedStrings.java | 193 + h2/src/main/org/h2/result/Row.java | 77 + h2/src/main/org/h2/result/RowFactory.java | 207 + h2/src/main/org/h2/result/SearchRow.java | 146 + h2/src/main/org/h2/result/SimpleResult.java | 302 + h2/src/main/org/h2/result/SimpleRowValue.java | 74 + h2/src/main/org/h2/result/SortOrder.java | 296 + h2/src/main/org/h2/result/Sparse.java | 64 + h2/src/main/org/h2/result/UpdatableRow.java | 340 + h2/src/main/org/h2/result/package.html | 14 + h2/src/main/org/h2/schema/Constant.java | 61 + h2/src/main/org/h2/schema/Domain.java | 224 + h2/src/main/org/h2/schema/FunctionAlias.java | 561 + .../main/org/h2/schema/InformationSchema.java | 77 + h2/src/main/org/h2/schema/MetaSchema.java | 97 + h2/src/main/org/h2/schema/Schema.java | 831 ++ h2/src/main/org/h2/schema/SchemaObject.java | 60 + h2/src/main/org/h2/schema/Sequence.java | 585 + h2/src/main/org/h2/schema/TriggerObject.java | 542 + h2/src/main/org/h2/schema/UserAggregate.java | 119 + .../org/h2/schema/UserDefinedFunction.java | 36 + h2/src/main/org/h2/schema/package.html | 14 + h2/src/main/org/h2/security/AES.java | 327 + h2/src/main/org/h2/security/BlockCipher.java | 53 + .../main/org/h2/security/CipherFactory.java | 410 + h2/src/main/org/h2/security/Fog.java | 75 + h2/src/main/org/h2/security/SHA256.java | 156 + h2/src/main/org/h2/security/SHA3.java | 289 + .../main/org/h2/security/SecureFileStore.java | 114 + h2/src/main/org/h2/security/XTEA.java | 150 + .../h2/security/auth/AuthConfigException.java | 30 + .../auth/AuthenticationException.java | 30 + .../h2/security/auth/AuthenticationInfo.java | 89 + .../org/h2/security/auth/Authenticator.java | 34 + .../security/auth/AuthenticatorFactory.java | 20 + .../h2/security/auth/ConfigProperties.java | 115 + .../org/h2/security/auth/Configurable.java | 17 + .../security/auth/DefaultAuthenticator.java | 364 + .../org/h2/security/auth/H2AuthConfig.java | 98 + .../org/h2/security/auth/H2AuthConfigXml.java | 144 + .../h2/security/auth/HasConfigProperties.java | 15 + .../org/h2/security/auth/PropertyConfig.java | 40 + .../org/h2/security/auth/RealmConfig.java | 64 + .../auth/UserToRolesMapperConfig.java | 46 + .../auth/impl/AssignRealmNameRole.java | 48 + .../auth/impl/JaasCredentialsValidator.java | 85 + .../auth/impl/LdapCredentialsValidator.java | 73 + .../security/auth/impl/StaticRolesMapper.java | 51 + .../impl/StaticUserCredentialsValidator.java | 74 + .../org/h2/security/auth/impl/package.html | 14 + h2/src/main/org/h2/security/auth/package.html | 14 + h2/src/main/org/h2/security/package.html | 14 + h2/src/main/org/h2/server/Service.java | 94 + .../main/org/h2/server/ShutdownHandler.java | 17 + h2/src/main/org/h2/server/TcpServer.java | 516 + .../main/org/h2/server/TcpServerThread.java | 675 + h2/src/main/org/h2/server/package.html | 14 + h2/src/main/org/h2/server/pg/PgServer.java | 485 + .../main/org/h2/server/pg/PgServerThread.java | 1386 ++ h2/src/main/org/h2/server/pg/package.html | 14 + .../org/h2/server/web/ConnectionInfo.java | 66 + h2/src/main/org/h2/server/web/DbStarter.java | 93 + .../org/h2/server/web/JakartaDbStarter.java | 93 + .../org/h2/server/web/JakartaWebServlet.java | 169 + h2/src/main/org/h2/server/web/PageParser.java | 349 + h2/src/main/org/h2/server/web/WebApp.java | 1849 +++ h2/src/main/org/h2/server/web/WebServer.java | 958 ++ h2/src/main/org/h2/server/web/WebServlet.java | 169 + h2/src/main/org/h2/server/web/WebSession.java | 275 + h2/src/main/org/h2/server/web/WebThread.java | 429 + h2/src/main/org/h2/server/web/package.html | 14 + .../main/org/h2/server/web/res/_text_cs.prop | 164 + .../main/org/h2/server/web/res/_text_de.prop | 164 + .../main/org/h2/server/web/res/_text_en.prop | 164 + .../main/org/h2/server/web/res/_text_es.prop | 164 + .../main/org/h2/server/web/res/_text_fr.prop | 164 + .../main/org/h2/server/web/res/_text_hi.prop | 164 + .../main/org/h2/server/web/res/_text_hu.prop | 164 + .../main/org/h2/server/web/res/_text_in.prop | 164 + .../main/org/h2/server/web/res/_text_it.prop | 164 + .../main/org/h2/server/web/res/_text_ja.prop | 164 + .../main/org/h2/server/web/res/_text_ko.prop | 164 + .../main/org/h2/server/web/res/_text_nl.prop | 164 + .../main/org/h2/server/web/res/_text_pl.prop | 164 + .../org/h2/server/web/res/_text_pt_br.prop | 164 + .../org/h2/server/web/res/_text_pt_pt.prop | 164 + .../main/org/h2/server/web/res/_text_ru.prop | 164 + .../main/org/h2/server/web/res/_text_sk.prop | 164 + .../main/org/h2/server/web/res/_text_tr.prop | 164 + .../main/org/h2/server/web/res/_text_uk.prop | 164 + .../org/h2/server/web/res/_text_zh_cn.prop | 164 + .../org/h2/server/web/res/_text_zh_tw.prop | 164 + h2/src/main/org/h2/server/web/res/admin.jsp | 129 + .../main/org/h2/server/web/res/adminLogin.jsp | 44 + .../h2/server/web/res/autoCompleteList.jsp | 1 + .../main/org/h2/server/web/res/background.gif | Bin 0 -> 169 bytes h2/src/main/org/h2/server/web/res/error.jsp | 16 + h2/src/main/org/h2/server/web/res/favicon.ico | Bin 0 -> 4286 bytes h2/src/main/org/h2/server/web/res/frame.jsp | 28 + h2/src/main/org/h2/server/web/res/header.jsp | 163 + h2/src/main/org/h2/server/web/res/help.jsp | 99 + .../org/h2/server/web/res/helpTranslate.jsp | 43 + h2/src/main/org/h2/server/web/res/ico_add.gif | Bin 0 -> 318 bytes h2/src/main/org/h2/server/web/res/ico_ok.gif | Bin 0 -> 846 bytes .../main/org/h2/server/web/res/ico_remove.gif | Bin 0 -> 350 bytes .../org/h2/server/web/res/ico_remove_ok.gif | Bin 0 -> 107 bytes .../main/org/h2/server/web/res/ico_search.gif | Bin 0 -> 545 bytes .../main/org/h2/server/web/res/ico_undo.gif | Bin 0 -> 851 bytes .../main/org/h2/server/web/res/ico_write.gif | Bin 0 -> 210 bytes .../org/h2/server/web/res/icon_commit.gif | Bin 0 -> 323 bytes .../org/h2/server/web/res/icon_disconnect.gif | Bin 0 -> 114 bytes .../main/org/h2/server/web/res/icon_help.gif | Bin 0 -> 373 bytes .../org/h2/server/web/res/icon_history.gif | Bin 0 -> 216 bytes .../main/org/h2/server/web/res/icon_line.gif | Bin 0 -> 818 bytes .../org/h2/server/web/res/icon_refresh.gif | Bin 0 -> 327 bytes .../org/h2/server/web/res/icon_rollback.gif | Bin 0 -> 331 bytes .../main/org/h2/server/web/res/icon_run.gif | Bin 0 -> 379 bytes .../h2/server/web/res/icon_run_selected.gif | Bin 0 -> 312 bytes .../main/org/h2/server/web/res/icon_stop.gif | Bin 0 -> 215 bytes h2/src/main/org/h2/server/web/res/index.jsp | 24 + h2/src/main/org/h2/server/web/res/login.jsp | 131 + .../main/org/h2/server/web/res/notAllowed.jsp | 16 + h2/src/main/org/h2/server/web/res/query.jsp | 530 + h2/src/main/org/h2/server/web/res/result.jsp | 22 + .../main/org/h2/server/web/res/stylesheet.css | 338 + h2/src/main/org/h2/server/web/res/table.js | 265 + h2/src/main/org/h2/server/web/res/tables.jsp | 35 + h2/src/main/org/h2/server/web/res/tools.jsp | 241 + h2/src/main/org/h2/server/web/res/tree.js | 124 + .../org/h2/server/web/res/tree_column.gif | Bin 0 -> 317 bytes .../org/h2/server/web/res/tree_database.gif | Bin 0 -> 545 bytes .../main/org/h2/server/web/res/tree_empty.gif | Bin 0 -> 62 bytes .../org/h2/server/web/res/tree_folder.gif | Bin 0 -> 372 bytes .../main/org/h2/server/web/res/tree_index.gif | Bin 0 -> 152 bytes .../org/h2/server/web/res/tree_index_az.gif | Bin 0 -> 157 bytes .../main/org/h2/server/web/res/tree_info.gif | Bin 0 -> 267 bytes .../main/org/h2/server/web/res/tree_line.gif | Bin 0 -> 66 bytes .../main/org/h2/server/web/res/tree_minus.gif | Bin 0 -> 861 bytes .../main/org/h2/server/web/res/tree_page.gif | Bin 0 -> 582 bytes .../main/org/h2/server/web/res/tree_plus.gif | Bin 0 -> 870 bytes .../org/h2/server/web/res/tree_sequence.gif | Bin 0 -> 91 bytes .../org/h2/server/web/res/tree_sequences.gif | Bin 0 -> 114 bytes .../main/org/h2/server/web/res/tree_table.gif | Bin 0 -> 343 bytes .../main/org/h2/server/web/res/tree_type.gif | Bin 0 -> 197 bytes .../main/org/h2/server/web/res/tree_types.gif | Bin 0 -> 364 bytes .../main/org/h2/server/web/res/tree_user.gif | Bin 0 -> 500 bytes .../main/org/h2/server/web/res/tree_users.gif | Bin 0 -> 601 bytes .../main/org/h2/server/web/res/tree_view.gif | Bin 0 -> 157 bytes .../h2/store/CountingReaderInputStream.java | 112 + h2/src/main/org/h2/store/Data.java | 218 + h2/src/main/org/h2/store/DataHandler.java | 105 + h2/src/main/org/h2/store/DataReader.java | 132 + h2/src/main/org/h2/store/FileLister.java | 113 + h2/src/main/org/h2/store/FileLock.java | 513 + h2/src/main/org/h2/store/FileLockMethod.java | 29 + h2/src/main/org/h2/store/FileStore.java | 512 + .../org/h2/store/FileStoreInputStream.java | 159 + .../org/h2/store/FileStoreOutputStream.java | 84 + .../main/org/h2/store/InDoubtTransaction.java | 71 + .../main/org/h2/store/LobStorageFrontend.java | 99 + .../org/h2/store/LobStorageInterface.java | 89 + .../h2/store/LobStorageRemoteInputStream.java | 70 + .../main/org/h2/store/RangeInputStream.java | 102 + h2/src/main/org/h2/store/RangeReader.java | 103 + h2/src/main/org/h2/store/RecoverTester.java | 176 + .../main/org/h2/store/fs/FakeFileChannel.java | 112 + h2/src/main/org/h2/store/fs/FileBase.java | 93 + .../main/org/h2/store/fs/FileBaseDefault.java | 68 + .../h2/store/fs/FileChannelInputStream.java | 71 + h2/src/main/org/h2/store/fs/FilePath.java | 357 + .../main/org/h2/store/fs/FilePathWrapper.java | 170 + h2/src/main/org/h2/store/fs/FileUtils.java | 476 + h2/src/main/org/h2/store/fs/Recorder.java | 59 + .../main/org/h2/store/fs/async/FileAsync.java | 89 + .../org/h2/store/fs/async/FilePathAsync.java | 28 + .../main/org/h2/store/fs/async/package.html | 14 + .../org/h2/store/fs/disk/FilePathDisk.java | 450 + h2/src/main/org/h2/store/fs/disk/package.html | 16 + .../org/h2/store/fs/encrypt/FileEncrypt.java | 261 + .../h2/store/fs/encrypt/FilePathEncrypt.java | 118 + h2/src/main/org/h2/store/fs/encrypt/XTS.java | 129 + .../main/org/h2/store/fs/encrypt/package.html | 14 + h2/src/main/org/h2/store/fs/mem/FileMem.java | 137 + .../main/org/h2/store/fs/mem/FileMemData.java | 385 + .../main/org/h2/store/fs/mem/FilePathMem.java | 224 + .../org/h2/store/fs/mem/FilePathMemLZF.java | 30 + h2/src/main/org/h2/store/fs/mem/package.html | 15 + .../h2/store/fs/niomapped/FileNioMapped.java | 209 + .../store/fs/niomapped/FilePathNioMapped.java | 28 + .../org/h2/store/fs/niomapped/package.html | 15 + .../org/h2/store/fs/niomem/FileNioMem.java | 131 + .../h2/store/fs/niomem/FileNioMemData.java | 394 + .../h2/store/fs/niomem/FilePathNioMem.java | 220 + .../h2/store/fs/niomem/FilePathNioMemLZF.java | 44 + .../main/org/h2/store/fs/niomem/package.html | 15 + h2/src/main/org/h2/store/fs/package.html | 14 + .../main/org/h2/store/fs/rec/FilePathRec.java | 119 + h2/src/main/org/h2/store/fs/rec/FileRec.java | 111 + h2/src/main/org/h2/store/fs/rec/package.html | 14 + .../fs/retry/FilePathRetryOnInterrupt.java | 35 + .../store/fs/retry/FileRetryOnInterrupt.java | 234 + .../main/org/h2/store/fs/retry/package.html | 16 + .../org/h2/store/fs/split/FilePathSplit.java | 242 + .../main/org/h2/store/fs/split/FileSplit.java | 156 + .../main/org/h2/store/fs/split/package.html | 15 + .../main/org/h2/store/fs/zip/FilePathZip.java | 251 + h2/src/main/org/h2/store/fs/zip/FileZip.java | 147 + h2/src/main/org/h2/store/fs/zip/package.html | 14 + h2/src/main/org/h2/store/package.html | 14 + h2/src/main/org/h2/table/Column.java | 819 ++ h2/src/main/org/h2/table/ColumnResolver.java | 129 + h2/src/main/org/h2/table/ColumnTemplate.java | 61 + .../org/h2/table/DataChangeDeltaTable.java | 134 + h2/src/main/org/h2/table/DerivedTable.java | 94 + h2/src/main/org/h2/table/DualTable.java | 74 + h2/src/main/org/h2/table/FunctionTable.java | 69 + .../org/h2/table/GeneratedColumnResolver.java | 101 + h2/src/main/org/h2/table/IndexColumn.java | 192 + h2/src/main/org/h2/table/IndexHints.java | 57 + .../org/h2/table/InformationSchemaTable.java | 3481 +++++ .../table/InformationSchemaTableLegacy.java | 2519 ++++ h2/src/main/org/h2/table/MetaTable.java | 277 + h2/src/main/org/h2/table/Plan.java | 149 + h2/src/main/org/h2/table/PlanItem.java | 58 + .../org/h2/table/QueryExpressionTable.java | 319 + h2/src/main/org/h2/table/RangeTable.java | 175 + h2/src/main/org/h2/table/Table.java | 1441 +++ h2/src/main/org/h2/table/TableBase.java | 159 + h2/src/main/org/h2/table/TableFilter.java | 1250 ++ h2/src/main/org/h2/table/TableLink.java | 740 ++ .../org/h2/table/TableLinkConnection.java | 163 + h2/src/main/org/h2/table/TableSynonym.java | 120 + h2/src/main/org/h2/table/TableType.java | 51 + .../h2/table/TableValueConstructorTable.java | 70 + h2/src/main/org/h2/table/TableView.java | 517 + .../org/h2/table/VirtualConstructedTable.java | 44 + h2/src/main/org/h2/table/VirtualTable.java | 93 + h2/src/main/org/h2/table/package.html | 14 + h2/src/main/org/h2/tools/Backup.java | 178 + .../org/h2/tools/ChangeFileEncryption.java | 244 + h2/src/main/org/h2/tools/CompressTool.java | 349 + h2/src/main/org/h2/tools/Console.java | 315 + .../main/org/h2/tools/ConvertTraceFile.java | 226 + h2/src/main/org/h2/tools/CreateCluster.java | 181 + h2/src/main/org/h2/tools/Csv.java | 856 ++ h2/src/main/org/h2/tools/DeleteDbFiles.java | 110 + h2/src/main/org/h2/tools/GUIConsole.java | 611 + h2/src/main/org/h2/tools/MultiDimension.java | 336 + h2/src/main/org/h2/tools/Recover.java | 754 ++ h2/src/main/org/h2/tools/Restore.java | 194 + h2/src/main/org/h2/tools/RunScript.java | 319 + h2/src/main/org/h2/tools/Script.java | 139 + h2/src/main/org/h2/tools/Server.java | 792 ++ h2/src/main/org/h2/tools/Shell.java | 633 + h2/src/main/org/h2/tools/SimpleResultSet.java | 2479 ++++ h2/src/main/org/h2/tools/SimpleRowSource.java | 35 + h2/src/main/org/h2/tools/TriggerAdapter.java | 97 + h2/src/main/org/h2/tools/Upgrade.java | 384 + h2/src/main/org/h2/tools/package.html | 14 + h2/src/main/org/h2/util/AbbaDetector.java | 124 + .../main/org/h2/util/AbbaLockingDetector.java | 255 + h2/src/main/org/h2/util/Bits.java | 325 + h2/src/main/org/h2/util/ByteStack.java | 122 + h2/src/main/org/h2/util/Cache.java | 92 + h2/src/main/org/h2/util/CacheHead.java | 23 + h2/src/main/org/h2/util/CacheLRU.java | 381 + h2/src/main/org/h2/util/CacheObject.java | 84 + h2/src/main/org/h2/util/CacheSecondLevel.java | 89 + h2/src/main/org/h2/util/CacheTQ.java | 133 + h2/src/main/org/h2/util/CacheWriter.java | 38 + h2/src/main/org/h2/util/CloseWatcher.java | 117 + h2/src/main/org/h2/util/DateTimeUtils.java | 1151 ++ .../main/org/h2/util/DbDriverActivator.java | 52 + .../org/h2/util/DebuggingThreadLocal.java | 45 + h2/src/main/org/h2/util/HasSQL.java | 76 + h2/src/main/org/h2/util/IOUtils.java | 423 + h2/src/main/org/h2/util/IntArray.java | 170 + h2/src/main/org/h2/util/IntervalUtils.java | 856 ++ h2/src/main/org/h2/util/JSR310Utils.java | 424 + h2/src/main/org/h2/util/JdbcUtils.java | 777 ++ .../main/org/h2/util/LegacyDateTimeUtils.java | 328 + h2/src/main/org/h2/util/MathUtils.java | 320 + h2/src/main/org/h2/util/MemoryEstimator.java | 195 + h2/src/main/org/h2/util/MemoryUnmapper.java | 92 + h2/src/main/org/h2/util/NetUtils.java | 399 + .../org/h2/util/NetworkConnectionInfo.java | 104 + .../org/h2/util/OsgiDataSourceFactory.java | 306 + h2/src/main/org/h2/util/ParserUtil.java | 695 + h2/src/main/org/h2/util/Permutations.java | 173 + h2/src/main/org/h2/util/Profiler.java | 516 + h2/src/main/org/h2/util/ScriptReader.java | 336 + h2/src/main/org/h2/util/SimpleColumnInfo.java | 80 + h2/src/main/org/h2/util/SmallLRUCache.java | 48 + h2/src/main/org/h2/util/SmallMap.java | 94 + .../main/org/h2/util/SoftValuesHashMap.java | 108 + h2/src/main/org/h2/util/SortedProperties.java | 172 + h2/src/main/org/h2/util/SourceCompiler.java | 602 + h2/src/main/org/h2/util/StringUtils.java | 1307 ++ h2/src/main/org/h2/util/Task.java | 126 + h2/src/main/org/h2/util/TempFileDeleter.java | 138 + .../org/h2/util/ThreadDeadlockDetector.java | 197 + h2/src/main/org/h2/util/TimeZoneProvider.java | 441 + h2/src/main/org/h2/util/Tool.java | 140 + h2/src/main/org/h2/util/Utils.java | 775 ++ h2/src/main/org/h2/util/Utils10.java | 78 + .../main/org/h2/util/geometry/EWKBUtils.java | 563 + .../main/org/h2/util/geometry/EWKTUtils.java | 888 ++ .../org/h2/util/geometry/GeoJsonUtils.java | 455 + .../org/h2/util/geometry/GeometryUtils.java | 480 + .../main/org/h2/util/geometry/JTSUtils.java | 488 + h2/src/main/org/h2/util/geometry/package.html | 14 + h2/src/main/org/h2/util/json/JSONArray.java | 47 + h2/src/main/org/h2/util/json/JSONBoolean.java | 47 + .../org/h2/util/json/JSONByteArrayTarget.java | 246 + .../org/h2/util/json/JSONBytesSource.java | 258 + .../main/org/h2/util/json/JSONItemType.java | 48 + h2/src/main/org/h2/util/json/JSONNull.java | 26 + h2/src/main/org/h2/util/json/JSONNumber.java | 35 + h2/src/main/org/h2/util/json/JSONObject.java | 69 + h2/src/main/org/h2/util/json/JSONString.java | 33 + .../org/h2/util/json/JSONStringSource.java | 161 + .../org/h2/util/json/JSONStringTarget.java | 247 + h2/src/main/org/h2/util/json/JSONTarget.java | 105 + .../main/org/h2/util/json/JSONTextSource.java | 216 + .../h2/util/json/JSONValidationTarget.java | 20 + .../JSONValidationTargetWithUniqueKeys.java | 157 + ...JSONValidationTargetWithoutUniqueKeys.java | 143 + h2/src/main/org/h2/util/json/JSONValue.java | 31 + .../org/h2/util/json/JSONValueTarget.java | 155 + .../h2/util/json/JsonConstructorUtils.java | 105 + h2/src/main/org/h2/util/json/package.html | 14 + h2/src/main/org/h2/util/package.html | 14 + .../value/CaseInsensitiveConcurrentMap.java | 46 + .../main/org/h2/value/CaseInsensitiveMap.java | 61 + h2/src/main/org/h2/value/CharsetCollator.java | 85 + h2/src/main/org/h2/value/CompareMode.java | 283 + .../main/org/h2/value/CompareModeDefault.java | 78 + .../main/org/h2/value/CompareModeIcu4J.java | 95 + h2/src/main/org/h2/value/DataType.java | 826 ++ h2/src/main/org/h2/value/ExtTypeInfo.java | 20 + h2/src/main/org/h2/value/ExtTypeInfoEnum.java | 214 + .../org/h2/value/ExtTypeInfoGeometry.java | 92 + .../main/org/h2/value/ExtTypeInfoNumeric.java | 26 + h2/src/main/org/h2/value/ExtTypeInfoRow.java | 130 + h2/src/main/org/h2/value/Transfer.java | 1316 ++ h2/src/main/org/h2/value/TypeInfo.java | 1536 +++ h2/src/main/org/h2/value/Typed.java | 20 + h2/src/main/org/h2/value/Value.java | 2782 ++++ h2/src/main/org/h2/value/ValueArray.java | 149 + .../org/h2/value/ValueBigDecimalBase.java | 37 + h2/src/main/org/h2/value/ValueBigint.java | 232 + h2/src/main/org/h2/value/ValueBinary.java | 90 + h2/src/main/org/h2/value/ValueBlob.java | 329 + h2/src/main/org/h2/value/ValueBoolean.java | 141 + h2/src/main/org/h2/value/ValueBytesBase.java | 77 + h2/src/main/org/h2/value/ValueChar.java | 55 + h2/src/main/org/h2/value/ValueClob.java | 369 + .../org/h2/value/ValueCollectionBase.java | 114 + h2/src/main/org/h2/value/ValueDate.java | 97 + h2/src/main/org/h2/value/ValueDecfloat.java | 361 + h2/src/main/org/h2/value/ValueDouble.java | 196 + h2/src/main/org/h2/value/ValueEnum.java | 40 + h2/src/main/org/h2/value/ValueEnumBase.java | 144 + h2/src/main/org/h2/value/ValueGeometry.java | 260 + h2/src/main/org/h2/value/ValueInteger.java | 196 + h2/src/main/org/h2/value/ValueInterval.java | 394 + h2/src/main/org/h2/value/ValueJavaObject.java | 73 + h2/src/main/org/h2/value/ValueJson.java | 243 + h2/src/main/org/h2/value/ValueLob.java | 297 + h2/src/main/org/h2/value/ValueNull.java | 150 + h2/src/main/org/h2/value/ValueNumeric.java | 218 + h2/src/main/org/h2/value/ValueReal.java | 196 + h2/src/main/org/h2/value/ValueRow.java | 166 + h2/src/main/org/h2/value/ValueSmallint.java | 179 + h2/src/main/org/h2/value/ValueStringBase.java | 188 + h2/src/main/org/h2/value/ValueTime.java | 145 + .../main/org/h2/value/ValueTimeTimeZone.java | 158 + h2/src/main/org/h2/value/ValueTimestamp.java | 202 + .../org/h2/value/ValueTimestampTimeZone.java | 219 + h2/src/main/org/h2/value/ValueTinyint.java | 183 + .../org/h2/value/ValueToObjectConverter.java | 637 + .../org/h2/value/ValueToObjectConverter2.java | 432 + h2/src/main/org/h2/value/ValueUuid.java | 224 + h2/src/main/org/h2/value/ValueVarbinary.java | 92 + h2/src/main/org/h2/value/ValueVarchar.java | 67 + .../org/h2/value/ValueVarcharIgnoreCase.java | 87 + h2/src/main/org/h2/value/VersionedValue.java | 36 + h2/src/main/org/h2/value/lob/LobData.java | 53 + .../org/h2/value/lob/LobDataDatabase.java | 85 + .../h2/value/lob/LobDataFetchOnDemand.java | 84 + h2/src/main/org/h2/value/lob/LobDataFile.java | 72 + .../org/h2/value/lob/LobDataInMemory.java | 51 + h2/src/main/org/h2/value/lob/package.html | 14 + h2/src/main/org/h2/value/package.html | 14 + .../javax.annotation.processing.Processor | 1 + .../h2/samples/CachedPreparedStatements.java | 60 + h2/src/test/org/h2/samples/Compact.java | 65 + .../test/org/h2/samples/CreateScriptFile.java | 167 + h2/src/test/org/h2/samples/CsvSample.java | 68 + h2/src/test/org/h2/samples/DirectInsert.java | 81 + h2/src/test/org/h2/samples/FileFunctions.java | 91 + h2/src/test/org/h2/samples/Function.java | 157 + .../org/h2/samples/FunctionMultiReturn.java | 160 + h2/src/test/org/h2/samples/HelloWorld.java | 49 + .../org/h2/samples/InitDatabaseFromJar.java | 71 + h2/src/test/org/h2/samples/MixedMode.java | 65 + h2/src/test/org/h2/samples/Newsfeed.java | 79 + .../org/h2/samples/ReadOnlyDatabaseInZip.java | 70 + .../test/org/h2/samples/RowAccessRights.java | 124 + h2/src/test/org/h2/samples/SQLInjection.java | 434 + .../test/org/h2/samples/SecurePassword.java | 90 + h2/src/test/org/h2/samples/ShowProgress.java | 174 + .../test/org/h2/samples/ShutdownServer.java | 26 + .../test/org/h2/samples/TriggerPassData.java | 87 + h2/src/test/org/h2/samples/TriggerSample.java | 125 + h2/src/test/org/h2/samples/UpdatableView.java | 130 + h2/src/test/org/h2/samples/fullTextSearch.sql | 59 + h2/src/test/org/h2/samples/newsfeed.sql | 128 + h2/src/test/org/h2/samples/optimizations.sql | 293 + h2/src/test/org/h2/samples/package.html | 14 + h2/src/test/org/h2/test/TestAll.java | 1141 ++ h2/src/test/org/h2/test/TestAllJunit.java | 23 + h2/src/test/org/h2/test/TestBase.java | 1722 +++ h2/src/test/org/h2/test/TestDb.java | 223 + .../h2/test/ap/TestAnnotationProcessor.java | 83 + h2/src/test/org/h2/test/ap/package.html | 14 + .../test/org/h2/test/auth/MyLoginModule.java | 61 + .../org/h2/test/auth/TestAuthentication.java | 289 + h2/src/test/org/h2/test/auth/package.html | 14 + h2/src/test/org/h2/test/bench/Bench.java | 36 + h2/src/test/org/h2/test/bench/BenchA.java | 190 + h2/src/test/org/h2/test/bench/BenchB.java | 242 + h2/src/test/org/h2/test/bench/BenchC.java | 564 + .../test/org/h2/test/bench/BenchCRandom.java | 179 + .../test/org/h2/test/bench/BenchCThread.java | 726 ++ .../test/org/h2/test/bench/BenchSimple.java | 104 + h2/src/test/org/h2/test/bench/Database.java | 553 + .../org/h2/test/bench/TestPerformance.java | 224 + .../org/h2/test/bench/TestScalability.java | 263 + h2/src/test/org/h2/test/bench/package.html | 14 + h2/src/test/org/h2/test/bench/test.properties | 45 + .../test/org/h2/test/coverage/Coverage.java | 531 + h2/src/test/org/h2/test/coverage/Profile.java | 228 + .../test/org/h2/test/coverage/Tokenizer.java | 277 + h2/src/test/org/h2/test/coverage/package.html | 14 + ...AbstractBaseForCommonTableExpressions.java | 103 + h2/src/test/org/h2/test/db/Db.java | 252 + h2/src/test/org/h2/test/db/TaskDef.java | 76 + h2/src/test/org/h2/test/db/TaskProcess.java | 132 + h2/src/test/org/h2/test/db/TestAlter.java | 244 + .../org/h2/test/db/TestAlterSchemaRename.java | 127 + .../h2/test/db/TestAlterTableNotFound.java | 174 + .../org/h2/test/db/TestAnalyzeTableTx.java | 61 + .../org/h2/test/db/TestAutoRecompile.java | 54 + h2/src/test/org/h2/test/db/TestBackup.java | 195 + h2/src/test/org/h2/test/db/TestBigDb.java | 170 + h2/src/test/org/h2/test/db/TestBigResult.java | 499 + h2/src/test/org/h2/test/db/TestCases.java | 1765 +++ .../test/org/h2/test/db/TestCheckpoint.java | 58 + h2/src/test/org/h2/test/db/TestCluster.java | 518 + .../org/h2/test/db/TestCompatibility.java | 788 ++ .../h2/test/db/TestCompatibilityOracle.java | 417 + .../test/db/TestCompatibilitySQLServer.java | 85 + h2/src/test/org/h2/test/db/TestCsv.java | 585 + .../test/org/h2/test/db/TestDateStorage.java | 228 + h2/src/test/org/h2/test/db/TestDeadlock.java | 246 + .../h2/test/db/TestDuplicateKeyUpdate.java | 314 + .../test/org/h2/test/db/TestEncryptedDb.java | 67 + h2/src/test/org/h2/test/db/TestExclusive.java | 126 + h2/src/test/org/h2/test/db/TestFullText.java | 639 + .../org/h2/test/db/TestFunctionOverload.java | 200 + h2/src/test/org/h2/test/db/TestFunctions.java | 2398 ++++ .../db/TestGeneralCommonTableQueries.java | 581 + .../org/h2/test/db/TestIgnoreCatalogs.java | 240 + h2/src/test/org/h2/test/db/TestIndex.java | 783 ++ .../test/org/h2/test/db/TestIndexHints.java | 136 + .../h2/test/db/TestLIRSMemoryConsumption.java | 103 + h2/src/test/org/h2/test/db/TestLargeBlob.java | 91 + .../test/org/h2/test/db/TestLinkedTable.java | 777 ++ h2/src/test/org/h2/test/db/TestListener.java | 151 + h2/src/test/org/h2/test/db/TestLob.java | 1628 +++ h2/src/test/org/h2/test/db/TestLobObject.java | 26 + .../test/org/h2/test/db/TestMemoryUsage.java | 305 + .../test/org/h2/test/db/TestMergeUsing.java | 326 + h2/src/test/org/h2/test/db/TestMultiConn.java | 217 + .../org/h2/test/db/TestMultiDimension.java | 268 + .../test/org/h2/test/db/TestMultiThread.java | 504 + .../h2/test/db/TestMultiThreadedKernel.java | 183 + h2/src/test/org/h2/test/db/TestOpenClose.java | 272 + .../org/h2/test/db/TestOptimizations.java | 1204 ++ .../test/org/h2/test/db/TestOutOfMemory.java | 254 + .../TestPersistentCommonTableExpressions.java | 253 + h2/src/test/org/h2/test/db/TestPowerOff.java | 356 + .../test/org/h2/test/db/TestQueryCache.java | 110 + h2/src/test/org/h2/test/db/TestReadOnly.java | 207 + .../org/h2/test/db/TestRecursiveQueries.java | 183 + h2/src/test/org/h2/test/db/TestRights.java | 860 ++ h2/src/test/org/h2/test/db/TestRunscript.java | 625 + .../test/org/h2/test/db/TestSQLInjection.java | 123 + .../h2/test/db/TestSelectTableNotFound.java | 177 + h2/src/test/org/h2/test/db/TestSequence.java | 500 + .../org/h2/test/db/TestSessionsLocks.java | 188 + .../test/org/h2/test/db/TestSetCollation.java | 191 + .../test/org/h2/test/db/TestSpaceReuse.java | 66 + h2/src/test/org/h2/test/db/TestSpatial.java | 1209 ++ h2/src/test/org/h2/test/db/TestSpeed.java | 164 + ...ubqueryPerformanceOnLazyExecutionMode.java | 167 + .../org/h2/test/db/TestSynonymForTable.java | 333 + .../test/org/h2/test/db/TestTableEngines.java | 1152 ++ .../test/org/h2/test/db/TestTempTables.java | 332 + .../test/org/h2/test/db/TestTransaction.java | 1271 ++ .../h2/test/db/TestTriggersConstraints.java | 830 ++ .../org/h2/test/db/TestTwoPhaseCommit.java | 157 + h2/src/test/org/h2/test/db/TestView.java | 350 + .../org/h2/test/db/TestViewAlterTable.java | 215 + .../test/org/h2/test/db/TestViewDropView.java | 172 + h2/src/test/org/h2/test/db/package.html | 14 + .../org/h2/test/jdbc/TestBatchUpdates.java | 545 + .../h2/test/jdbc/TestCallableStatement.java | 538 + h2/src/test/org/h2/test/jdbc/TestCancel.java | 212 + .../jdbc/TestConcurrentConnectionUsage.java | 59 + .../test/org/h2/test/jdbc/TestConnection.java | 402 + .../test/jdbc/TestDatabaseEventListener.java | 273 + h2/src/test/org/h2/test/jdbc/TestDriver.java | 72 + .../h2/test/jdbc/TestGetGeneratedKeys.java | 1751 +++ .../test/jdbc/TestJavaObjectSerializer.java | 154 + h2/src/test/org/h2/test/jdbc/TestLobApi.java | 384 + .../org/h2/test/jdbc/TestManyJdbcObjects.java | 117 + .../test/org/h2/test/jdbc/TestMetaData.java | 1409 ++ .../test/org/h2/test/jdbc/TestNativeSQL.java | 264 + .../h2/test/jdbc/TestPreparedStatement.java | 1759 +++ .../test/org/h2/test/jdbc/TestResultSet.java | 2100 +++ h2/src/test/org/h2/test/jdbc/TestSQLXML.java | 217 + .../test/org/h2/test/jdbc/TestStatement.java | 530 + .../test/jdbc/TestTransactionIsolation.java | 111 + .../h2/test/jdbc/TestUpdatableResultSet.java | 828 ++ .../jdbc/TestUrlJavaObjectSerializer.java | 95 + h2/src/test/org/h2/test/jdbc/TestZloty.java | 121 + h2/src/test/org/h2/test/jdbc/package.html | 14 + h2/src/test/org/h2/test/jdbcx/SimpleXid.java | 85 + .../org/h2/test/jdbcx/TestConnectionPool.java | 269 + .../org/h2/test/jdbcx/TestDataSource.java | 210 + h2/src/test/org/h2/test/jdbcx/TestXA.java | 424 + .../test/org/h2/test/jdbcx/TestXASimple.java | 161 + h2/src/test/org/h2/test/jdbcx/package.html | 14 + h2/src/test/org/h2/test/mvcc/TestMvcc1.java | 381 + h2/src/test/org/h2/test/mvcc/TestMvcc2.java | 168 + h2/src/test/org/h2/test/mvcc/TestMvcc3.java | 221 + h2/src/test/org/h2/test/mvcc/TestMvcc4.java | 138 + .../h2/test/mvcc/TestMvccMultiThreaded.java | 179 + .../h2/test/mvcc/TestMvccMultiThreaded2.java | 186 + h2/src/test/org/h2/test/mvcc/package.html | 14 + h2/src/test/org/h2/test/otherDatabases.txt | 75 + h2/src/test/org/h2/test/package.html | 14 + .../test/org/h2/test/poweroff/Listener.java | 83 + h2/src/test/org/h2/test/poweroff/Test.java | 169 + .../org/h2/test/poweroff/TestRecover.java | 374 + .../h2/test/poweroff/TestRecoverKillLoop.java | 67 + .../h2/test/poweroff/TestReorderWrites.java | 213 + .../test/org/h2/test/poweroff/TestWrite.java | 134 + h2/src/test/org/h2/test/poweroff/package.html | 14 + .../org/h2/test/recover/RecoverLobTest.java | 81 + h2/src/test/org/h2/test/recover/package.html | 14 + .../org/h2/test/rowlock/TestRowLocks.java | 103 + h2/src/test/org/h2/test/rowlock/package.html | 14 + .../test/org/h2/test/scripts/Aggregate1.java | 32 + .../test/org/h2/test/scripts/TestScript.java | 770 ++ h2/src/test/org/h2/test/scripts/Trigger1.java | 25 + h2/src/test/org/h2/test/scripts/Trigger2.java | 59 + .../org/h2/test/scripts/altertable-fk.sql | 26 + .../test/scripts/altertable-index-reuse.sql | 33 + .../test/scripts/compatibility/add_months.sql | 23 + .../scripts/compatibility/compatibility.sql | 757 ++ .../test/scripts/compatibility/group_by.sql | 57 + .../compatibility/strict_and_legacy.sql | 101 + .../org/h2/test/scripts/datatypes/array.sql | 270 + .../org/h2/test/scripts/datatypes/bigint.sql | 68 + .../org/h2/test/scripts/datatypes/binary.sql | 58 + .../org/h2/test/scripts/datatypes/blob.sql | 61 + .../org/h2/test/scripts/datatypes/boolean.sql | 51 + .../org/h2/test/scripts/datatypes/char.sql | 198 + .../org/h2/test/scripts/datatypes/clob.sql | 70 + .../org/h2/test/scripts/datatypes/date.sql | 60 + .../h2/test/scripts/datatypes/decfloat.sql | 283 + .../scripts/datatypes/double_precision.sql | 233 + .../org/h2/test/scripts/datatypes/enum.sql | 391 + .../h2/test/scripts/datatypes/geometry.sql | 322 + .../h2/test/scripts/datatypes/identity.sql | 4 + .../org/h2/test/scripts/datatypes/int.sql | 18 + .../h2/test/scripts/datatypes/interval.sql | 1105 ++ .../h2/test/scripts/datatypes/java_object.sql | 53 + .../org/h2/test/scripts/datatypes/json.sql | 360 + .../org/h2/test/scripts/datatypes/numeric.sql | 188 + .../org/h2/test/scripts/datatypes/real.sql | 247 + .../org/h2/test/scripts/datatypes/row.sql | 220 + .../h2/test/scripts/datatypes/smallint.sql | 30 + .../scripts/datatypes/time-with-time-zone.sql | 98 + .../org/h2/test/scripts/datatypes/time.sql | 128 + .../datatypes/timestamp-with-time-zone.sql | 138 + .../h2/test/scripts/datatypes/timestamp.sql | 173 + .../org/h2/test/scripts/datatypes/tinyint.sql | 18 + .../org/h2/test/scripts/datatypes/uuid.sql | 42 + .../h2/test/scripts/datatypes/varbinary.sql | 143 + .../scripts/datatypes/varchar-ignorecase.sql | 191 + .../org/h2/test/scripts/datatypes/varchar.sql | 126 + .../org/h2/test/scripts/ddl/alterDomain.sql | 346 + .../org/h2/test/scripts/ddl/alterTableAdd.sql | 395 + .../scripts/ddl/alterTableAlterColumn.sql | 816 ++ .../test/scripts/ddl/alterTableDropColumn.sql | 98 + .../scripts/ddl/alterTableDropConstraint.sql | 19 + .../h2/test/scripts/ddl/alterTableRename.sql | 61 + .../ddl/alterTableRenameConstraint.sql | 19 + .../test/org/h2/test/scripts/ddl/analyze.sql | 67 + .../org/h2/test/scripts/ddl/commentOn.sql | 66 + .../org/h2/test/scripts/ddl/createAlias.sql | 156 + .../h2/test/scripts/ddl/createConstant.sql | 82 + .../org/h2/test/scripts/ddl/createDomain.sql | 259 + .../org/h2/test/scripts/ddl/createIndex.sql | 34 + .../org/h2/test/scripts/ddl/createSchema.sql | 64 + .../h2/test/scripts/ddl/createSequence.sql | 196 + .../org/h2/test/scripts/ddl/createSynonym.sql | 52 + .../org/h2/test/scripts/ddl/createTable.sql | 279 + .../org/h2/test/scripts/ddl/createTrigger.sql | 230 + .../org/h2/test/scripts/ddl/createView.sql | 54 + .../h2/test/scripts/ddl/dropAllObjects.sql | 61 + .../org/h2/test/scripts/ddl/dropDomain.sql | 76 + .../org/h2/test/scripts/ddl/dropIndex.sql | 75 + .../org/h2/test/scripts/ddl/dropSchema.sql | 149 + .../org/h2/test/scripts/ddl/dropTable.sql | 64 + h2/src/test/org/h2/test/scripts/ddl/grant.sql | 57 + .../org/h2/test/scripts/ddl/truncateTable.sql | 189 + .../h2/test/scripts/default-and-on_update.sql | 111 + .../test/org/h2/test/scripts/dml/delete.sql | 101 + .../h2/test/scripts/dml/error_reporting.sql | 43 + .../h2/test/scripts/dml/execute_immediate.sql | 33 + .../test/org/h2/test/scripts/dml/insert.sql | 150 + .../org/h2/test/scripts/dml/insertIgnore.sql | 127 + h2/src/test/org/h2/test/scripts/dml/merge.sql | 161 + .../org/h2/test/scripts/dml/mergeUsing.sql | 541 + .../test/org/h2/test/scripts/dml/replace.sql | 53 + .../test/org/h2/test/scripts/dml/script.sql | 142 + h2/src/test/org/h2/test/scripts/dml/show.sql | 113 + .../test/org/h2/test/scripts/dml/update.sql | 345 + h2/src/test/org/h2/test/scripts/dml/with.sql | 245 + h2/src/test/org/h2/test/scripts/dual.sql | 58 + .../test/scripts/functions/aggregate/any.sql | 33 + .../scripts/functions/aggregate/array_agg.sql | 678 + .../test/scripts/functions/aggregate/avg.sql | 136 + .../functions/aggregate/bit_and_agg.sql | 48 + .../functions/aggregate/bit_or_agg.sql | 45 + .../functions/aggregate/bit_xor_agg.sql | 25 + .../test/scripts/functions/aggregate/corr.sql | 28 + .../scripts/functions/aggregate/count.sql | 235 + .../scripts/functions/aggregate/covar_pop.sql | 28 + .../functions/aggregate/covar_samp.sql | 28 + .../scripts/functions/aggregate/envelope.sql | 132 + .../scripts/functions/aggregate/every.sql | 21 + .../scripts/functions/aggregate/histogram.sql | 19 + .../functions/aggregate/json_arrayagg.sql | 71 + .../functions/aggregate/json_objectagg.sql | 73 + .../scripts/functions/aggregate/listagg.sql | 218 + .../test/scripts/functions/aggregate/max.sql | 69 + .../test/scripts/functions/aggregate/min.sql | 75 + .../test/scripts/functions/aggregate/mode.sql | 94 + .../functions/aggregate/percentile.sql | 916 ++ .../test/scripts/functions/aggregate/rank.sql | 150 + .../scripts/functions/aggregate/regr_avgx.sql | 28 + .../scripts/functions/aggregate/regr_avgy.sql | 28 + .../functions/aggregate/regr_count.sql | 28 + .../functions/aggregate/regr_intercept.sql | 28 + .../scripts/functions/aggregate/regr_r2.sql | 28 + .../functions/aggregate/regr_slope.sql | 28 + .../scripts/functions/aggregate/regr_sxx.sql | 28 + .../scripts/functions/aggregate/regr_sxy.sql | 28 + .../scripts/functions/aggregate/regr_syy.sql | 28 + .../functions/aggregate/stddev_pop.sql | 4 + .../functions/aggregate/stddev_samp.sql | 4 + .../test/scripts/functions/aggregate/sum.sql | 232 + .../scripts/functions/aggregate/var_pop.sql | 4 + .../scripts/functions/aggregate/var_samp.sql | 4 + .../scripts/functions/json/json_array.sql | 58 + .../scripts/functions/json/json_object.sql | 58 + .../h2/test/scripts/functions/numeric/abs.sql | 23 + .../test/scripts/functions/numeric/acos.sql | 10 + .../test/scripts/functions/numeric/asin.sql | 10 + .../test/scripts/functions/numeric/atan.sql | 10 + .../test/scripts/functions/numeric/atan2.sql | 10 + .../test/scripts/functions/numeric/bitand.sql | 79 + .../scripts/functions/numeric/bitcount.sql | 27 + .../test/scripts/functions/numeric/bitget.sql | 30 + .../test/scripts/functions/numeric/bitnot.sql | 31 + .../test/scripts/functions/numeric/bitor.sql | 79 + .../test/scripts/functions/numeric/bitxor.sql | 79 + .../test/scripts/functions/numeric/ceil.sql | 46 + .../scripts/functions/numeric/compress.sql | 25 + .../h2/test/scripts/functions/numeric/cos.sql | 10 + .../test/scripts/functions/numeric/cosh.sql | 10 + .../h2/test/scripts/functions/numeric/cot.sql | 10 + .../scripts/functions/numeric/decrypt.sql | 10 + .../scripts/functions/numeric/degrees.sql | 14 + .../scripts/functions/numeric/encrypt.sql | 13 + .../h2/test/scripts/functions/numeric/exp.sql | 10 + .../test/scripts/functions/numeric/expand.sql | 19 + .../test/scripts/functions/numeric/floor.sql | 43 + .../test/scripts/functions/numeric/hash.sql | 85 + .../test/scripts/functions/numeric/length.sql | 34 + .../h2/test/scripts/functions/numeric/log.sql | 100 + .../test/scripts/functions/numeric/lshift.sql | 109 + .../h2/test/scripts/functions/numeric/mod.sql | 10 + .../scripts/functions/numeric/ora-hash.sql | 64 + .../h2/test/scripts/functions/numeric/pi.sql | 7 + .../test/scripts/functions/numeric/power.sql | 13 + .../scripts/functions/numeric/radians.sql | 17 + .../test/scripts/functions/numeric/rand.sql | 15 + .../scripts/functions/numeric/random-uuid.sql | 34 + .../test/scripts/functions/numeric/rotate.sql | 103 + .../test/scripts/functions/numeric/round.sql | 111 + .../scripts/functions/numeric/roundmagic.sql | 10 + .../test/scripts/functions/numeric/rshift.sql | 115 + .../scripts/functions/numeric/secure-rand.sql | 13 + .../test/scripts/functions/numeric/sign.sql | 16 + .../h2/test/scripts/functions/numeric/sin.sql | 10 + .../test/scripts/functions/numeric/sinh.sql | 10 + .../test/scripts/functions/numeric/sqrt.sql | 10 + .../h2/test/scripts/functions/numeric/tan.sql | 10 + .../test/scripts/functions/numeric/tanh.sql | 10 + .../scripts/functions/numeric/truncate.sql | 131 + .../test/scripts/functions/numeric/zero.sql | 4 + .../functions/string/array-to-string.sql | 34 + .../test/scripts/functions/string/ascii.sql | 10 + .../scripts/functions/string/bit-length.sql | 4 + .../h2/test/scripts/functions/string/char.sql | 10 + .../scripts/functions/string/concat-ws.sql | 16 + .../test/scripts/functions/string/concat.sql | 13 + .../scripts/functions/string/difference.sql | 16 + .../scripts/functions/string/hextoraw.sql | 25 + .../test/scripts/functions/string/insert.sql | 19 + .../h2/test/scripts/functions/string/left.sql | 10 + .../test/scripts/functions/string/length.sql | 31 + .../test/scripts/functions/string/locate.sql | 49 + .../test/scripts/functions/string/lower.sql | 16 + .../h2/test/scripts/functions/string/lpad.sql | 7 + .../test/scripts/functions/string/ltrim.sql | 10 + .../scripts/functions/string/octet-length.sql | 4 + .../scripts/functions/string/quote_ident.sql | 16 + .../scripts/functions/string/rawtohex.sql | 28 + .../functions/string/regex-replace.sql | 76 + .../scripts/functions/string/regexp-like.sql | 13 + .../functions/string/regexp-substr.sql | 83 + .../test/scripts/functions/string/repeat.sql | 10 + .../test/scripts/functions/string/replace.sql | 25 + .../test/scripts/functions/string/right.sql | 10 + .../h2/test/scripts/functions/string/rpad.sql | 7 + .../test/scripts/functions/string/rtrim.sql | 13 + .../test/scripts/functions/string/soundex.sql | 20 + .../test/scripts/functions/string/space.sql | 10 + .../scripts/functions/string/stringdecode.sql | 22 + .../scripts/functions/string/stringencode.sql | 13 + .../scripts/functions/string/stringtoutf8.sql | 4 + .../scripts/functions/string/substring.sql | 82 + .../test/scripts/functions/string/to-char.sql | 4 + .../scripts/functions/string/translate.sql | 37 + .../h2/test/scripts/functions/string/trim.sql | 31 + .../test/scripts/functions/string/upper.sql | 16 + .../scripts/functions/string/utf8tostring.sql | 7 + .../test/scripts/functions/string/xmlattr.sql | 4 + .../scripts/functions/string/xmlcdata.sql | 10 + .../scripts/functions/string/xmlcomment.sql | 10 + .../test/scripts/functions/string/xmlnode.sql | 19 + .../scripts/functions/string/xmlstartdoc.sql | 7 + .../test/scripts/functions/string/xmltext.sql | 16 + .../scripts/functions/system/array-cat.sql | 22 + .../functions/system/array-contains.sql | 56 + .../scripts/functions/system/array-get.sql | 17 + .../scripts/functions/system/array-slice.sql | 46 + .../scripts/functions/system/autocommit.sql | 7 + .../functions/system/cancel-session.sql | 4 + .../scripts/functions/system/cardinality.sql | 41 + .../scripts/functions/system/casewhen.sql | 10 + .../h2/test/scripts/functions/system/cast.sql | 203 + .../scripts/functions/system/coalesce.sql | 10 + .../test/scripts/functions/system/convert.sql | 10 + .../test/scripts/functions/system/csvread.sql | 4 + .../scripts/functions/system/csvwrite.sql | 4 + .../functions/system/current_catalog.sql | 37 + .../functions/system/current_schema.sql | 40 + .../scripts/functions/system/current_user.sql | 25 + .../test/scripts/functions/system/currval.sql | 4 + .../functions/system/data_type_sql.sql | 121 + .../functions/system/database-path.sql | 4 + .../scripts/functions/system/db_object.sql | 284 + .../test/scripts/functions/system/decode.sql | 31 + .../functions/system/disk-space-used.sql | 4 + .../scripts/functions/system/file-read.sql | 4 + .../scripts/functions/system/file-write.sql | 4 + .../scripts/functions/system/greatest.sql | 4 + .../scripts/functions/system/h2version.sql | 7 + .../scripts/functions/system/identity.sql | 34 + .../test/scripts/functions/system/ifnull.sql | 37 + .../functions/system/last-insert-id.sql | 43 + .../test/scripts/functions/system/least.sql | 4 + .../scripts/functions/system/link-schema.sql | 4 + .../scripts/functions/system/lock-mode.sql | 4 + .../scripts/functions/system/lock-timeout.sql | 4 + .../scripts/functions/system/memory-free.sql | 4 + .../scripts/functions/system/memory-used.sql | 4 + .../test/scripts/functions/system/nextval.sql | 4 + .../test/scripts/functions/system/nullif.sql | 27 + .../h2/test/scripts/functions/system/nvl2.sql | 4 + .../scripts/functions/system/readonly.sql | 7 + .../test/scripts/functions/system/rownum.sql | 33 + .../scripts/functions/system/session-id.sql | 4 + .../test/scripts/functions/system/table.sql | 65 + .../functions/system/transaction-id.sql | 4 + .../scripts/functions/system/trim_array.sql | 28 + .../functions/system/truncate-value.sql | 19 + .../test/scripts/functions/system/unnest.sql | 67 + .../functions/timeanddate/current-time.sql | 43 + .../functions/timeanddate/current_date.sql | 13 + .../timeanddate/current_timestamp.sql | 137 + .../functions/timeanddate/date_trunc.sql | 925 ++ .../scripts/functions/timeanddate/dateadd.sql | 142 + .../functions/timeanddate/datediff.sql | 229 + .../functions/timeanddate/day-of-month.sql | 23 + .../functions/timeanddate/day-of-week.sql | 7 + .../functions/timeanddate/day-of-year.sql | 7 + .../scripts/functions/timeanddate/dayname.sql | 7 + .../scripts/functions/timeanddate/extract.sql | 275 + .../functions/timeanddate/formatdatetime.sql | 25 + .../scripts/functions/timeanddate/hour.sql | 26 + .../scripts/functions/timeanddate/minute.sql | 7 + .../scripts/functions/timeanddate/month.sql | 7 + .../functions/timeanddate/monthname.sql | 7 + .../functions/timeanddate/parsedatetime.sql | 22 + .../scripts/functions/timeanddate/quarter.sql | 7 + .../scripts/functions/timeanddate/second.sql | 7 + .../functions/timeanddate/truncate.sql | 16 + .../scripts/functions/timeanddate/week.sql | 12 + .../scripts/functions/timeanddate/year.sql | 7 + .../h2/test/scripts/functions/window/lead.sql | 181 + .../scripts/functions/window/nth_value.sql | 263 + .../test/scripts/functions/window/ntile.sql | 129 + .../functions/window/ratio_to_report.sql | 38 + .../scripts/functions/window/row_number.sql | 245 + h2/src/test/org/h2/test/scripts/indexes.sql | 412 + .../h2/test/scripts/information_schema.sql | 193 + .../h2/test/scripts/other/at-time-zone.sql | 134 + .../h2/test/scripts/other/boolean-test.sql | 135 + .../test/org/h2/test/scripts/other/case.sql | 133 + .../h2/test/scripts/other/concatenation.sql | 50 + .../org/h2/test/scripts/other/conditions.sql | 168 + .../scripts/other/data-change-delta-table.sql | 417 + .../h2/test/scripts/other/field-reference.sql | 31 + .../test/org/h2/test/scripts/other/help.sql | 26 + .../org/h2/test/scripts/other/sequence.sql | 481 + h2/src/test/org/h2/test/scripts/other/set.sql | 244 + .../test/scripts/other/two_phase_commit.sql | 28 + .../h2/test/scripts/other/unique_include.sql | 76 + h2/src/test/org/h2/test/scripts/package.html | 14 + .../org/h2/test/scripts/parser/comments.sql | 50 + .../h2/test/scripts/parser/identifiers.sql | 52 + .../h2/test/scripts/predicates/between.sql | 107 + .../h2/test/scripts/predicates/distinct.sql | 66 + .../org/h2/test/scripts/predicates/in.sql | 428 + .../org/h2/test/scripts/predicates/like.sql | 214 + .../org/h2/test/scripts/predicates/null.sql | 200 + .../org/h2/test/scripts/predicates/type.sql | 49 + .../org/h2/test/scripts/predicates/unique.sql | 54 + .../scripts/queries/derived-column-names.sql | 88 + .../org/h2/test/scripts/queries/distinct.sql | 190 + .../org/h2/test/scripts/queries/joins.sql | 1046 ++ .../scripts/queries/query-optimisations.sql | 210 + .../org/h2/test/scripts/queries/select.sql | 1213 ++ .../org/h2/test/scripts/queries/table.sql | 64 + .../org/h2/test/scripts/queries/values.sql | 115 + .../org/h2/test/scripts/queries/window.sql | 232 + .../test/org/h2/test/scripts/range_table.sql | 235 + .../test/org/h2/test/scripts/testScript.sql | 7076 +++++++++++ .../test/org/h2/test/scripts/testSimple.sql | 1259 ++ .../org/h2/test/server/TestAutoServer.java | 241 + h2/src/test/org/h2/test/server/TestInit.java | 80 + .../org/h2/test/server/TestJakartaWeb.java | 698 + .../org/h2/test/server/TestNestedLoop.java | 68 + h2/src/test/org/h2/test/server/TestWeb.java | 1187 ++ h2/src/test/org/h2/test/server/WebClient.java | 153 + h2/src/test/org/h2/test/server/package.html | 14 + .../h2/test/store/CalculateHashConstant.java | 554 + .../test/store/CalculateHashConstantLong.java | 435 + .../test/org/h2/test/store/FreeSpaceList.java | 217 + .../test/org/h2/test/store/FreeSpaceTree.java | 206 + .../test/org/h2/test/store/RowDataType.java | 81 + .../test/org/h2/test/store/SequenceMap.java | 74 + .../test/org/h2/test/store/TestBenchmark.java | 273 + .../test/store/TestCacheConcurrentLIRS.java | 94 + .../test/org/h2/test/store/TestCacheLIRS.java | 559 + .../h2/test/store/TestCacheLongKeyLIRS.java | 505 + .../test/org/h2/test/store/TestDataUtils.java | 360 + h2/src/test/org/h2/test/store/TestDefrag.java | 79 + .../test/org/h2/test/store/TestFreeSpace.java | 145 + .../org/h2/test/store/TestImmutableArray.java | 162 + .../store/TestKillProcessWhileWriting.java | 155 + .../test/org/h2/test/store/TestMVRTree.java | 433 + .../test/org/h2/test/store/TestMVStore.java | 2085 +++ .../h2/test/store/TestMVStoreBenchmark.java | 194 + .../store/TestMVStoreCachePerformance.java | 97 + .../h2/test/store/TestMVStoreConcurrent.java | 804 ++ .../h2/test/store/TestMVStoreStopCompact.java | 80 + .../org/h2/test/store/TestMVStoreTool.java | 151 + .../org/h2/test/store/TestMVTableEngine.java | 1406 ++ .../org/h2/test/store/TestObjectDataType.java | 185 + .../org/h2/test/store/TestRandomMapOps.java | 285 + .../org/h2/test/store/TestShardedMap.java | 99 + .../test/org/h2/test/store/TestSpinLock.java | 155 + .../org/h2/test/store/TestStreamStore.java | 466 + .../h2/test/store/TestTransactionStore.java | 1009 ++ h2/src/test/org/h2/test/store/package.html | 14 + h2/src/test/org/h2/test/synth/BnfRandom.java | 223 + .../test/org/h2/test/synth/OutputCatcher.java | 88 + .../org/h2/test/synth/TestBtreeIndex.java | 201 + .../h2/test/synth/TestConcurrentUpdate.java | 174 + .../test/org/h2/test/synth/TestCrashAPI.java | 535 + .../test/org/h2/test/synth/TestDiskFull.java | 135 + .../h2/test/synth/TestFuzzOptimizations.java | 274 + h2/src/test/org/h2/test/synth/TestHalt.java | 353 + .../test/org/h2/test/synth/TestHaltApp.java | 183 + h2/src/test/org/h2/test/synth/TestJoin.java | 309 + h2/src/test/org/h2/test/synth/TestKill.java | 166 + .../org/h2/test/synth/TestKillProcess.java | 92 + .../org/h2/test/synth/TestKillRestart.java | 233 + .../h2/test/synth/TestKillRestartMulti.java | 331 + h2/src/test/org/h2/test/synth/TestLimit.java | 95 + .../org/h2/test/synth/TestMultiThreaded.java | 189 + .../org/h2/test/synth/TestNestedJoins.java | 658 + .../org/h2/test/synth/TestOuterJoins.java | 594 + .../org/h2/test/synth/TestPowerOffFs.java | 102 + .../org/h2/test/synth/TestPowerOffFs2.java | 232 + .../org/h2/test/synth/TestRandomCompare.java | 308 + .../test/org/h2/test/synth/TestRandomSQL.java | 122 + .../h2/test/synth/TestReleaseSelectLock.java | 79 + .../org/h2/test/synth/TestSimpleIndex.java | 177 + .../test/org/h2/test/synth/TestThreads.java | 175 + h2/src/test/org/h2/test/synth/TestTimer.java | 145 + h2/src/test/org/h2/test/synth/package.html | 14 + h2/src/test/org/h2/test/synth/sql/Column.java | 224 + .../test/org/h2/test/synth/sql/Command.java | 423 + .../org/h2/test/synth/sql/DbConnection.java | 209 + .../org/h2/test/synth/sql/DbInterface.java | 118 + .../test/org/h2/test/synth/sql/DbState.java | 120 + .../org/h2/test/synth/sql/Expression.java | 379 + h2/src/test/org/h2/test/synth/sql/Index.java | 52 + .../test/org/h2/test/synth/sql/RandomGen.java | 345 + h2/src/test/org/h2/test/synth/sql/Result.java | 154 + h2/src/test/org/h2/test/synth/sql/Row.java | 51 + h2/src/test/org/h2/test/synth/sql/Table.java | 265 + .../test/org/h2/test/synth/sql/TestSynth.java | 346 + h2/src/test/org/h2/test/synth/sql/Value.java | 338 + .../test/org/h2/test/synth/sql/package.html | 14 + .../org/h2/test/synth/thread/TestMulti.java | 64 + .../h2/test/synth/thread/TestMultiNews.java | 134 + .../synth/thread/TestMultiNewsSimple.java | 95 + .../h2/test/synth/thread/TestMultiOrder.java | 167 + .../h2/test/synth/thread/TestMultiThread.java | 72 + .../org/h2/test/synth/thread/package.html | 14 + .../org/h2/test/todo/TestDiskSpaceLeak.java | 80 + .../org/h2/test/todo/TestDropTableLarge.java | 58 + .../todo/TestLinkedTableFullCondition.java | 44 + .../org/h2/test/todo/TestTempTableCrash.java | 81 + .../org/h2/test/todo/TestUndoLogLarge.java | 55 + h2/src/test/org/h2/test/todo/columnAlias.txt | 31 + .../test/org/h2/test/todo/dateFunctions.txt | 10 + h2/src/test/org/h2/test/todo/package.html | 14 + .../org/h2/test/todo/recursiveQueries.txt | 130 + .../org/h2/test/todo/supportTemplates.txt | 251 + h2/src/test/org/h2/test/todo/todo.txt | 94 + h2/src/test/org/h2/test/todo/tools.sql | 83 + h2/src/test/org/h2/test/todo/versionlist.txt | 65 + h2/src/test/org/h2/test/trace/Arg.java | 88 + h2/src/test/org/h2/test/trace/Parser.java | 279 + h2/src/test/org/h2/test/trace/Player.java | 179 + h2/src/test/org/h2/test/trace/Statement.java | 165 + h2/src/test/org/h2/test/trace/package.html | 14 + .../org/h2/test/unit/TestAnsCompression.java | 110 + .../org/h2/test/unit/TestAutoReconnect.java | 189 + .../test/unit/TestBinaryArithmeticStream.java | 172 + .../org/h2/test/unit/TestBinaryOperation.java | 109 + .../test/org/h2/test/unit/TestBitStream.java | 160 + h2/src/test/org/h2/test/unit/TestBnf.java | 177 + h2/src/test/org/h2/test/unit/TestCache.java | 202 + .../org/h2/test/unit/TestCharsetCollator.java | 73 + .../org/h2/test/unit/TestClassLoaderLeak.java | 128 + .../test/org/h2/test/unit/TestCollation.java | 53 + .../test/org/h2/test/unit/TestCompress.java | 329 + .../org/h2/test/unit/TestConcurrentJdbc.java | 99 + .../org/h2/test/unit/TestConnectionInfo.java | 84 + h2/src/test/org/h2/test/unit/TestDate.java | 460 + .../org/h2/test/unit/TestDateIso8601.java | 282 + .../org/h2/test/unit/TestDateTimeUtils.java | 327 + .../org/h2/test/unit/TestDbException.java | 55 + h2/src/test/org/h2/test/unit/TestExit.java | 149 + h2/src/test/org/h2/test/unit/TestFile.java | 193 + .../test/org/h2/test/unit/TestFileLock.java | 157 + .../org/h2/test/unit/TestFileLockProcess.java | 127 + .../test/org/h2/test/unit/TestFileSystem.java | 802 ++ h2/src/test/org/h2/test/unit/TestFtp.java | 82 + .../org/h2/test/unit/TestGeometryUtils.java | 531 + .../test/org/h2/test/unit/TestIntArray.java | 112 + .../org/h2/test/unit/TestIntPerfectHash.java | 109 + .../test/org/h2/test/unit/TestInterval.java | 547 + .../org/h2/test/unit/TestJakartaServlet.java | 437 + h2/src/test/org/h2/test/unit/TestJmx.java | 144 + .../test/org/h2/test/unit/TestJsonUtils.java | 340 + .../test/org/h2/test/unit/TestKeywords.java | 754 ++ h2/src/test/org/h2/test/unit/TestLocale.java | 88 + .../org/h2/test/unit/TestMVTempResult.java | 81 + .../test/org/h2/test/unit/TestMathUtils.java | 69 + .../org/h2/test/unit/TestMemoryEstimator.java | 120 + .../org/h2/test/unit/TestMemoryUnmapper.java | 51 + h2/src/test/org/h2/test/unit/TestMode.java | 90 + .../h2/test/unit/TestMultiThreadedKernel.java | 90 + .../test/org/h2/test/unit/TestNetUtils.java | 334 + .../test/unit/TestObjectDeserialization.java | 72 + .../test/org/h2/test/unit/TestOverflow.java | 130 + .../h2/test/unit/TestPageStoreCoverage.java | 243 + h2/src/test/org/h2/test/unit/TestPattern.java | 124 + .../org/h2/test/unit/TestPerfectHash.java | 317 + .../test/org/h2/test/unit/TestPgServer.java | 913 ++ h2/src/test/org/h2/test/unit/TestReader.java | 43 + .../test/org/h2/test/unit/TestRecovery.java | 217 + h2/src/test/org/h2/test/unit/TestReopen.java | 184 + .../test/org/h2/test/unit/TestSampleApps.java | 154 + .../org/h2/test/unit/TestScriptReader.java | 244 + .../test/org/h2/test/unit/TestSecurity.java | 333 + h2/src/test/org/h2/test/unit/TestServlet.java | 437 + h2/src/test/org/h2/test/unit/TestShell.java | 241 + h2/src/test/org/h2/test/unit/TestSort.java | 163 + h2/src/test/org/h2/test/unit/TestStreams.java | 121 + .../org/h2/test/unit/TestStringCache.java | 177 + .../org/h2/test/unit/TestStringUtils.java | 300 + .../test/unit/TestTimeStampWithTimeZone.java | 231 + h2/src/test/org/h2/test/unit/TestTools.java | 1303 ++ .../org/h2/test/unit/TestTraceSystem.java | 78 + h2/src/test/org/h2/test/unit/TestUpgrade.java | 112 + h2/src/test/org/h2/test/unit/TestUtils.java | 277 + h2/src/test/org/h2/test/unit/TestValue.java | 552 + .../org/h2/test/unit/TestValueMemory.java | 425 + h2/src/test/org/h2/test/unit/package.html | 14 + .../test/org/h2/test/utils/FilePathDebug.java | 342 + .../h2/test/utils/FilePathReorderWrites.java | 379 + .../org/h2/test/utils/FilePathUnstable.java | 214 + .../org/h2/test/utils/MemoryFootprint.java | 84 + .../test/org/h2/test/utils/OutputCatcher.java | 218 + .../org/h2/test/utils/RandomDataUtils.java | 62 + .../org/h2/test/utils/ResultVerifier.java | 26 + .../org/h2/test/utils/SelfDestructor.java | 108 + h2/src/test/org/h2/test/utils/package.html | 14 + h2/src/tools/WEB-INF/console.html | 15 + h2/src/tools/WEB-INF/web.xml | 67 + h2/src/tools/org/h2/dev/cache/CacheLIRS.java | 1081 ++ h2/src/tools/org/h2/dev/cache/package.html | 14 + .../tools/org/h2/dev/cluster/ShardedMap.java | 330 + h2/src/tools/org/h2/dev/cluster/package.html | 14 + h2/src/tools/org/h2/dev/fs/ArchiveTool.java | 1195 ++ .../tools/org/h2/dev/fs/ArchiveToolStore.java | 518 + h2/src/tools/org/h2/dev/fs/FilePathZip2.java | 456 + h2/src/tools/org/h2/dev/fs/FileShell.java | 512 + h2/src/tools/org/h2/dev/fs/package.html | 14 + h2/src/tools/org/h2/dev/ftp/FtpClient.java | 475 + h2/src/tools/org/h2/dev/ftp/package.html | 14 + .../org/h2/dev/ftp/server/FtpControl.java | 420 + .../tools/org/h2/dev/ftp/server/FtpData.java | 145 + .../tools/org/h2/dev/ftp/server/FtpEvent.java | 48 + .../h2/dev/ftp/server/FtpEventListener.java | 34 + .../org/h2/dev/ftp/server/FtpServer.java | 572 + .../tools/org/h2/dev/ftp/server/package.html | 14 + .../tools/org/h2/dev/hash/IntPerfectHash.java | 409 + .../org/h2/dev/hash/MinimalPerfectHash.java | 783 ++ h2/src/tools/org/h2/dev/hash/PerfectHash.java | 258 + h2/src/tools/org/h2/dev/hash/package.html | 14 + .../tools/org/h2/dev/mail/SendMail.java.txt | 45 + .../tools/org/h2/dev/net/PgTcpRedirect.java | 559 + h2/src/tools/org/h2/dev/net/package.html | 14 + .../dev/security/SecureKeyStoreBuilder.java | 106 + h2/src/tools/org/h2/dev/security/package.html | 14 + .../h2/dev/sort/InPlaceStableMergeSort.java | 322 + .../h2/dev/sort/InPlaceStableQuicksort.java | 310 + h2/src/tools/org/h2/dev/sort/package.html | 14 + .../tools/org/h2/dev/util/AnsCompression.java | 188 + h2/src/tools/org/h2/dev/util/ArrayUtils.java | 65 + h2/src/tools/org/h2/dev/util/Base64.java | 234 + .../h2/dev/util/BinaryArithmeticStream.java | 269 + h2/src/tools/org/h2/dev/util/BitStream.java | 298 + .../org/h2/dev/util/ConcurrentLinkedList.java | 145 + .../util/ConcurrentLinkedListWithTail.java | 148 + .../tools/org/h2/dev/util/ConcurrentRing.java | 148 + .../org/h2/dev/util/FileContentHash.java | 171 + h2/src/tools/org/h2/dev/util/FileViewer.java | 214 + .../tools/org/h2/dev/util/ImmutableArray.java | 172 + .../org/h2/dev/util/ImmutableArray2.java | 210 + .../org/h2/dev/util/ImmutableArray3.java | 451 + .../org/h2/dev/util/JavaProcessKiller.java | 126 + h2/src/tools/org/h2/dev/util/Migrate.java | 249 + .../org/h2/dev/util/ReaderInputStream.java | 67 + .../org/h2/dev/util/RemovePasswords.java | 85 + .../org/h2/dev/util/ThreadDumpCleaner.java | 144 + .../org/h2/dev/util/ThreadDumpFilter.java | 42 + .../org/h2/dev/util/ThreadDumpInliner.java | 55 + h2/src/tools/org/h2/dev/util/package.html | 14 + h2/src/tools/org/h2/jcr/Railroads.java | 126 + h2/src/tools/org/h2/jcr/help.csv | 99 + h2/src/tools/org/h2/jcr/jcr-sql2.html | 72 + h2/src/tools/org/h2/jcr/package.html | 14 + h2/src/tools/org/h2/jcr/stylesheet.css | 340 + .../basic/{myData.java => MyData.java} | 3 +- .../basic/{myUser.java => MyUser.java} | 2 +- .../basic/userRepository/UserRepository.java | 15 + src/main/resources/application.properties | 4 +- test.mv.db | Bin 0 -> 24576 bytes test.trace.db | 447 + 1991 files changed, 548063 insertions(+), 3 deletions(-) create mode 100644 h2/build.bat create mode 100644 h2/build.sh create mode 100644 h2/docs/h2.pdf create mode 100644 h2/docs/html/advanced.html create mode 100644 h2/docs/html/architecture.html create mode 100644 h2/docs/html/build.html create mode 100644 h2/docs/html/changelog.html create mode 100644 h2/docs/html/cheatSheet.html create mode 100644 h2/docs/html/commands.html create mode 100644 h2/docs/html/datatypes.html create mode 100644 h2/docs/html/download-archive.html create mode 100644 h2/docs/html/download.html create mode 100644 h2/docs/html/faq.html create mode 100644 h2/docs/html/features.html create mode 100644 h2/docs/html/fragments.html create mode 100644 h2/docs/html/frame.html create mode 100644 h2/docs/html/functions-aggregate.html create mode 100644 h2/docs/html/functions-window.html create mode 100644 h2/docs/html/functions.html create mode 100644 h2/docs/html/grammar.html create mode 100644 h2/docs/html/history.html create mode 100644 h2/docs/html/images/connection-mode-embedded-2.png create mode 100644 h2/docs/html/images/connection-mode-embedded.png create mode 100644 h2/docs/html/images/connection-mode-mixed-2.png create mode 100644 h2/docs/html/images/connection-mode-mixed.png create mode 100644 h2/docs/html/images/connection-mode-remote-2.png create mode 100644 h2/docs/html/images/connection-mode-remote.png create mode 100644 h2/docs/html/images/console-2.png create mode 100644 h2/docs/html/images/console.png create mode 100644 h2/docs/html/images/db-16.png create mode 100644 h2/docs/html/images/db-64-t.png create mode 100644 h2/docs/html/images/div-d.png create mode 100644 h2/docs/html/images/div-ke.png create mode 100644 h2/docs/html/images/div-ks.png create mode 100644 h2/docs/html/images/div-le.png create mode 100644 h2/docs/html/images/div-ls.png create mode 100644 h2/docs/html/images/div-te.png create mode 100644 h2/docs/html/images/div-ts.png create mode 100644 h2/docs/html/images/download-2.png create mode 100644 h2/docs/html/images/download.png create mode 100644 h2/docs/html/images/h2-logo-2.png create mode 100644 h2/docs/html/images/h2-logo.png create mode 100644 h2/docs/html/images/h2-logo_square.png create mode 100644 h2/docs/html/images/icon_disconnect.gif create mode 100644 h2/docs/html/images/language_de.png create mode 100644 h2/docs/html/images/language_en.gif create mode 100644 h2/docs/html/images/language_ja.gif create mode 100644 h2/docs/html/images/mail-support.png create mode 100644 h2/docs/html/images/paypal-donate.png create mode 100644 h2/docs/html/images/performance.png create mode 100644 h2/docs/html/images/quickstart-1.png create mode 100644 h2/docs/html/images/quickstart-2.png create mode 100644 h2/docs/html/images/quickstart-3.png create mode 100644 h2/docs/html/images/quickstart-4.png create mode 100644 h2/docs/html/images/quickstart-5.png create mode 100644 h2/docs/html/images/quickstart-6.png create mode 100644 h2/docs/html/images/screenshot.png create mode 100644 h2/docs/html/index.js create mode 100644 h2/docs/html/installation.html create mode 100644 h2/docs/html/license.html create mode 100644 h2/docs/html/links.html create mode 100644 h2/docs/html/main.html create mode 100644 h2/docs/html/mainWeb.html create mode 100644 h2/docs/html/migration-to-v2.html create mode 100644 h2/docs/html/mvstore.html create mode 100644 h2/docs/html/navigation.js create mode 100644 h2/docs/html/performance.html create mode 100644 h2/docs/html/quickstart.html create mode 100644 h2/docs/html/search.js create mode 100644 h2/docs/html/security.html create mode 100644 h2/docs/html/source.html create mode 100644 h2/docs/html/sourceError.html create mode 100644 h2/docs/html/stylesheet.css create mode 100644 h2/docs/html/stylesheetPdf.css create mode 100644 h2/docs/html/systemtables.html create mode 100644 h2/docs/html/tutorial.html create mode 100644 h2/docs/index.html create mode 100644 h2/docs/javadoc/allclasses-frame.html create mode 100644 h2/docs/javadoc/allclasses-noframe.html create mode 100644 h2/docs/javadoc/constant-values.html create mode 100644 h2/docs/javadoc/deprecated-list.html create mode 100644 h2/docs/javadoc/help-doc.html create mode 100644 h2/docs/javadoc/index-all.html create mode 100644 h2/docs/javadoc/index.html create mode 100644 h2/docs/javadoc/org/h2/api/Aggregate.html create mode 100644 h2/docs/javadoc/org/h2/api/AggregateFunction.html create mode 100644 h2/docs/javadoc/org/h2/api/CredentialsValidator.html create mode 100644 h2/docs/javadoc/org/h2/api/DatabaseEventListener.html create mode 100644 h2/docs/javadoc/org/h2/api/ErrorCode.html create mode 100644 h2/docs/javadoc/org/h2/api/H2Type.html create mode 100644 h2/docs/javadoc/org/h2/api/Interval.html create mode 100644 h2/docs/javadoc/org/h2/api/IntervalQualifier.html create mode 100644 h2/docs/javadoc/org/h2/api/JavaObjectSerializer.html create mode 100644 h2/docs/javadoc/org/h2/api/TableEngine.html create mode 100644 h2/docs/javadoc/org/h2/api/Trigger.html create mode 100644 h2/docs/javadoc/org/h2/api/UserToRolesMapper.html create mode 100644 h2/docs/javadoc/org/h2/api/package-frame.html create mode 100644 h2/docs/javadoc/org/h2/api/package-summary.html create mode 100644 h2/docs/javadoc/org/h2/api/package-tree.html create mode 100644 h2/docs/javadoc/org/h2/engine/CastDataProvider.html create mode 100644 h2/docs/javadoc/org/h2/engine/Comment.html create mode 100644 h2/docs/javadoc/org/h2/engine/ConnectionInfo.html create mode 100644 h2/docs/javadoc/org/h2/engine/Constants.html create mode 100644 h2/docs/javadoc/org/h2/engine/Database.html create mode 100644 h2/docs/javadoc/org/h2/engine/DbObject.html create mode 100644 h2/docs/javadoc/org/h2/engine/DbSettings.html create mode 100644 h2/docs/javadoc/org/h2/engine/Engine.html create mode 100644 h2/docs/javadoc/org/h2/engine/GeneratedKeysMode.html create mode 100644 h2/docs/javadoc/org/h2/engine/IsolationLevel.html create mode 100644 h2/docs/javadoc/org/h2/engine/MetaRecord.html create mode 100644 h2/docs/javadoc/org/h2/engine/Mode.CharPadding.html create mode 100644 h2/docs/javadoc/org/h2/engine/Mode.ExpressionNames.html create mode 100644 h2/docs/javadoc/org/h2/engine/Mode.ModeEnum.html create mode 100644 h2/docs/javadoc/org/h2/engine/Mode.UniqueIndexNullsHandling.html create mode 100644 h2/docs/javadoc/org/h2/engine/Mode.ViewExpressionNames.html create mode 100644 h2/docs/javadoc/org/h2/engine/Mode.html create mode 100644 h2/docs/javadoc/org/h2/engine/Procedure.html create mode 100644 h2/docs/javadoc/org/h2/engine/QueryStatisticsData.QueryEntry.html create mode 100644 h2/docs/javadoc/org/h2/engine/QueryStatisticsData.html create mode 100644 h2/docs/javadoc/org/h2/engine/Right.html create mode 100644 h2/docs/javadoc/org/h2/engine/RightOwner.html create mode 100644 h2/docs/javadoc/org/h2/engine/Role.html create mode 100644 h2/docs/javadoc/org/h2/engine/Session.DynamicSettings.html create mode 100644 h2/docs/javadoc/org/h2/engine/Session.StaticSettings.html create mode 100644 h2/docs/javadoc/org/h2/engine/Session.html create mode 100644 h2/docs/javadoc/org/h2/engine/SessionLocal.Savepoint.html create mode 100644 h2/docs/javadoc/org/h2/engine/SessionLocal.State.html create mode 100644 h2/docs/javadoc/org/h2/engine/SessionLocal.TimeoutValue.html create mode 100644 h2/docs/javadoc/org/h2/engine/SessionLocal.html create mode 100644 h2/docs/javadoc/org/h2/engine/SessionRemote.html create mode 100644 h2/docs/javadoc/org/h2/engine/Setting.html create mode 100644 h2/docs/javadoc/org/h2/engine/SettingsBase.html create mode 100644 h2/docs/javadoc/org/h2/engine/SysProperties.html create mode 100644 h2/docs/javadoc/org/h2/engine/User.html create mode 100644 h2/docs/javadoc/org/h2/engine/UserBuilder.html create mode 100644 h2/docs/javadoc/org/h2/engine/package-frame.html create mode 100644 h2/docs/javadoc/org/h2/engine/package-summary.html create mode 100644 h2/docs/javadoc/org/h2/engine/package-tree.html create mode 100644 h2/docs/javadoc/org/h2/fulltext/FullText.FullTextTrigger.html create mode 100644 h2/docs/javadoc/org/h2/fulltext/FullText.html create mode 100644 h2/docs/javadoc/org/h2/fulltext/FullTextLucene.FullTextTrigger.html create mode 100644 h2/docs/javadoc/org/h2/fulltext/FullTextLucene.html create mode 100644 h2/docs/javadoc/org/h2/fulltext/IndexInfo.html create mode 100644 h2/docs/javadoc/org/h2/fulltext/package-frame.html create mode 100644 h2/docs/javadoc/org/h2/fulltext/package-summary.html create mode 100644 h2/docs/javadoc/org/h2/fulltext/package-tree.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcArray.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcBatchUpdateException.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcBlob.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcCallableStatement.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcClob.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcConnection.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcConnectionBackwardsCompat.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcDatabaseMetaData.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcDatabaseMetaDataBackwardsCompat.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcException.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcLob.State.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcLob.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcParameterMetaData.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcPreparedStatement.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcResultSet.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcResultSetMetaData.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcSQLDataException.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcSQLException.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcSQLFeatureNotSupportedException.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcSQLIntegrityConstraintViolationException.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcSQLInvalidAuthorizationSpecException.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcSQLNonTransientConnectionException.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcSQLNonTransientException.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcSQLSyntaxErrorException.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcSQLTimeoutException.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcSQLTransactionRollbackException.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcSQLTransientException.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcSQLXML.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcSavepoint.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcStatement.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/JdbcStatementBackwardsCompat.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/package-frame.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/package-summary.html create mode 100644 h2/docs/javadoc/org/h2/jdbc/package-tree.html create mode 100644 h2/docs/javadoc/org/h2/jdbcx/JdbcConnectionPool.html create mode 100644 h2/docs/javadoc/org/h2/jdbcx/JdbcConnectionPoolBackwardsCompat.html create mode 100644 h2/docs/javadoc/org/h2/jdbcx/JdbcDataSource.html create mode 100644 h2/docs/javadoc/org/h2/jdbcx/JdbcDataSourceBackwardsCompat.html create mode 100644 h2/docs/javadoc/org/h2/jdbcx/JdbcDataSourceFactory.html create mode 100644 h2/docs/javadoc/org/h2/jdbcx/JdbcXAConnection.html create mode 100644 h2/docs/javadoc/org/h2/jdbcx/JdbcXid.html create mode 100644 h2/docs/javadoc/org/h2/jdbcx/package-frame.html create mode 100644 h2/docs/javadoc/org/h2/jdbcx/package-summary.html create mode 100644 h2/docs/javadoc/org/h2/jdbcx/package-tree.html create mode 100644 h2/docs/javadoc/org/h2/tools/Backup.html create mode 100644 h2/docs/javadoc/org/h2/tools/ChangeFileEncryption.html create mode 100644 h2/docs/javadoc/org/h2/tools/CompressTool.html create mode 100644 h2/docs/javadoc/org/h2/tools/Console.html create mode 100644 h2/docs/javadoc/org/h2/tools/ConvertTraceFile.html create mode 100644 h2/docs/javadoc/org/h2/tools/CreateCluster.html create mode 100644 h2/docs/javadoc/org/h2/tools/Csv.html create mode 100644 h2/docs/javadoc/org/h2/tools/DeleteDbFiles.html create mode 100644 h2/docs/javadoc/org/h2/tools/GUIConsole.html create mode 100644 h2/docs/javadoc/org/h2/tools/MultiDimension.html create mode 100644 h2/docs/javadoc/org/h2/tools/Recover.html create mode 100644 h2/docs/javadoc/org/h2/tools/Restore.html create mode 100644 h2/docs/javadoc/org/h2/tools/RunScript.html create mode 100644 h2/docs/javadoc/org/h2/tools/Script.html create mode 100644 h2/docs/javadoc/org/h2/tools/Server.html create mode 100644 h2/docs/javadoc/org/h2/tools/Shell.html create mode 100644 h2/docs/javadoc/org/h2/tools/SimpleResultSet.SimpleArray.html create mode 100644 h2/docs/javadoc/org/h2/tools/SimpleResultSet.html create mode 100644 h2/docs/javadoc/org/h2/tools/SimpleRowSource.html create mode 100644 h2/docs/javadoc/org/h2/tools/TriggerAdapter.html create mode 100644 h2/docs/javadoc/org/h2/tools/Upgrade.html create mode 100644 h2/docs/javadoc/org/h2/tools/package-frame.html create mode 100644 h2/docs/javadoc/org/h2/tools/package-summary.html create mode 100644 h2/docs/javadoc/org/h2/tools/package-tree.html create mode 100644 h2/docs/javadoc/overview-frame.html create mode 100644 h2/docs/javadoc/overview-summary.html create mode 100644 h2/docs/javadoc/overview-tree.html create mode 100644 h2/docs/javadoc/package-list create mode 100644 h2/docs/javadoc/script.js create mode 100644 h2/docs/javadoc/serialized-form.html create mode 100644 h2/docs/javadoc/stylesheet.css create mode 100644 h2/service/0_run_server_debug.bat create mode 100644 h2/service/1_install_service.bat create mode 100644 h2/service/2_start_service.bat create mode 100644 h2/service/3_start_browser.bat create mode 100644 h2/service/4_stop_service.bat create mode 100644 h2/service/5_uninstall_service.bat create mode 100644 h2/service/serviceWrapperLicense.txt create mode 100644 h2/service/wrapper.conf create mode 100644 h2/service/wrapper.dll create mode 100644 h2/service/wrapper.exe create mode 100644 h2/service/wrapper.jar create mode 100644 h2/src/docsrc/help/information_schema.csv create mode 100644 h2/src/docsrc/html/advanced.html create mode 100644 h2/src/docsrc/html/architecture.html create mode 100644 h2/src/docsrc/html/build.html create mode 100644 h2/src/docsrc/html/changelog.html create mode 100644 h2/src/docsrc/html/cheatSheet.html create mode 100644 h2/src/docsrc/html/commands.html create mode 100644 h2/src/docsrc/html/datatypes.html create mode 100644 h2/src/docsrc/html/download-archive.html create mode 100644 h2/src/docsrc/html/download.html create mode 100644 h2/src/docsrc/html/faq.html create mode 100644 h2/src/docsrc/html/features.html create mode 100644 h2/src/docsrc/html/fragments.html create mode 100644 h2/src/docsrc/html/frame.html create mode 100644 h2/src/docsrc/html/functions-aggregate.html create mode 100644 h2/src/docsrc/html/functions-window.html create mode 100644 h2/src/docsrc/html/functions.html create mode 100644 h2/src/docsrc/html/grammar.html create mode 100644 h2/src/docsrc/html/history.html create mode 100644 h2/src/docsrc/html/images/connection-mode-embedded-2.png create mode 100644 h2/src/docsrc/html/images/connection-mode-embedded.png create mode 100644 h2/src/docsrc/html/images/connection-mode-mixed-2.png create mode 100644 h2/src/docsrc/html/images/connection-mode-mixed.png create mode 100644 h2/src/docsrc/html/images/connection-mode-remote-2.png create mode 100644 h2/src/docsrc/html/images/connection-mode-remote.png create mode 100644 h2/src/docsrc/html/images/console-2.png create mode 100644 h2/src/docsrc/html/images/console.png create mode 100644 h2/src/docsrc/html/images/db-16.png create mode 100644 h2/src/docsrc/html/images/db-64-t.png create mode 100644 h2/src/docsrc/html/images/download-2.png create mode 100644 h2/src/docsrc/html/images/download.png create mode 100644 h2/src/docsrc/html/images/h2-logo-2.png create mode 100644 h2/src/docsrc/html/images/h2-logo.png create mode 100644 h2/src/docsrc/html/images/h2-logo_square.png create mode 100644 h2/src/docsrc/html/images/icon_disconnect.gif create mode 100644 h2/src/docsrc/html/images/language_de.png create mode 100644 h2/src/docsrc/html/images/language_en.gif create mode 100644 h2/src/docsrc/html/images/language_ja.gif create mode 100644 h2/src/docsrc/html/images/mail-support.png create mode 100644 h2/src/docsrc/html/images/paypal-donate.png create mode 100644 h2/src/docsrc/html/images/performance.png create mode 100644 h2/src/docsrc/html/images/quickstart-1.png create mode 100644 h2/src/docsrc/html/images/quickstart-2.png create mode 100644 h2/src/docsrc/html/images/quickstart-3.png create mode 100644 h2/src/docsrc/html/images/quickstart-4.png create mode 100644 h2/src/docsrc/html/images/quickstart-5.png create mode 100644 h2/src/docsrc/html/images/quickstart-6.png create mode 100644 h2/src/docsrc/html/images/screenshot.png create mode 100644 h2/src/docsrc/html/installation.html create mode 100644 h2/src/docsrc/html/license.html create mode 100644 h2/src/docsrc/html/links.html create mode 100644 h2/src/docsrc/html/main.html create mode 100644 h2/src/docsrc/html/mainWeb.html create mode 100644 h2/src/docsrc/html/migration-to-v2.html create mode 100644 h2/src/docsrc/html/mvstore.html create mode 100644 h2/src/docsrc/html/navigation.js create mode 100644 h2/src/docsrc/html/performance.html create mode 100644 h2/src/docsrc/html/quickstart.html create mode 100644 h2/src/docsrc/html/search.js create mode 100644 h2/src/docsrc/html/security.html create mode 100644 h2/src/docsrc/html/source.html create mode 100644 h2/src/docsrc/html/sourceError.html create mode 100644 h2/src/docsrc/html/stylesheet.css create mode 100644 h2/src/docsrc/html/stylesheetPdf.css create mode 100644 h2/src/docsrc/html/systemtables.html create mode 100644 h2/src/docsrc/html/tutorial.html create mode 100644 h2/src/docsrc/images/connection-mode-embedded-2.png create mode 100644 h2/src/docsrc/images/connection-mode-mixed-2.png create mode 100644 h2/src/docsrc/images/connection-mode-remote-2.png create mode 100644 h2/src/docsrc/images/connection-modes.svg create mode 100644 h2/src/docsrc/images/console-2.png create mode 100644 h2/src/docsrc/images/console.png create mode 100644 h2/src/docsrc/images/console.svg create mode 100644 h2/src/docsrc/images/db-16.png create mode 100644 h2/src/docsrc/images/db-22.png create mode 100644 h2/src/docsrc/images/db-24.png create mode 100644 h2/src/docsrc/images/db-32.png create mode 100644 h2/src/docsrc/images/db-64-t.png create mode 100644 h2/src/docsrc/images/db.svg create mode 100644 h2/src/docsrc/images/download-2.png create mode 100644 h2/src/docsrc/images/download.png create mode 100644 h2/src/docsrc/images/download.svg create mode 100644 h2/src/docsrc/images/favicon.ico create mode 100644 h2/src/docsrc/images/h2-16.png create mode 100644 h2/src/docsrc/images/h2-24.png create mode 100644 h2/src/docsrc/images/h2-32.png create mode 100644 h2/src/docsrc/images/h2-64.png create mode 100644 h2/src/docsrc/images/h2-logo-2.png create mode 100644 h2/src/docsrc/images/h2-logo.png create mode 100644 h2/src/docsrc/images/h2-logo.svg create mode 100644 h2/src/docsrc/images/h2_v2_3_7.svg create mode 100644 h2/src/docsrc/images/paypal-donate.png create mode 100644 h2/src/docsrc/images/screenshot.png create mode 100644 h2/src/docsrc/index.html create mode 100644 h2/src/installer/buildRelease.bat create mode 100644 h2/src/installer/buildRelease.sh create mode 100644 h2/src/installer/checkstyle.xml create mode 100644 h2/src/installer/eclipse.settings/eclipseCodeStyle.xml create mode 100644 h2/src/installer/eclipse.settings/org.eclipse.core.resources.prefs create mode 100644 h2/src/installer/eclipse.settings/org.eclipse.jdt.core.prefs create mode 100644 h2/src/installer/eclipse.settings/org.eclipse.jdt.launching.prefs create mode 100644 h2/src/installer/eclipse.settings/org.eclipse.jdt.ui.prefs create mode 100644 h2/src/installer/favicon.ico create mode 100644 h2/src/installer/h2.bat create mode 100644 h2/src/installer/h2.nsi create mode 100644 h2/src/installer/h2.sh create mode 100644 h2/src/installer/h2w.bat create mode 100644 h2/src/installer/mvstore/MANIFEST.MF create mode 100644 h2/src/installer/openoffice.txt create mode 100644 h2/src/installer/pom-mvstore-template.xml create mode 100644 h2/src/installer/pom-template.xml create mode 100644 h2/src/installer/release.txt create mode 100644 h2/src/installer/source-manifest.mf create mode 100644 h2/src/installer/source-mvstore-manifest.mf create mode 100644 h2/src/java10/precompiled/org/h2/util/Utils10.class create mode 100644 h2/src/java10/src/org/h2/util/Utils10.java create mode 100644 h2/src/java10/src/org/h2/util/package.html create mode 100644 h2/src/java9/precompiled/org/h2/util/Bits.class create mode 100644 h2/src/java9/src/org/h2/util/Bits.java create mode 100644 h2/src/java9/src/org/h2/util/package.html create mode 100644 h2/src/main/META-INF/MANIFEST.MF create mode 100644 h2/src/main/META-INF/services/java.sql.Driver create mode 100644 h2/src/main/org/h2/Driver.java create mode 100644 h2/src/main/org/h2/JdbcDriverBackwardsCompat.java create mode 100644 h2/src/main/org/h2/api/Aggregate.java create mode 100644 h2/src/main/org/h2/api/AggregateFunction.java create mode 100644 h2/src/main/org/h2/api/CredentialsValidator.java create mode 100644 h2/src/main/org/h2/api/DatabaseEventListener.java create mode 100644 h2/src/main/org/h2/api/ErrorCode.java create mode 100644 h2/src/main/org/h2/api/H2Type.java create mode 100644 h2/src/main/org/h2/api/Interval.java create mode 100644 h2/src/main/org/h2/api/IntervalQualifier.java create mode 100644 h2/src/main/org/h2/api/JavaObjectSerializer.java create mode 100644 h2/src/main/org/h2/api/TableEngine.java create mode 100644 h2/src/main/org/h2/api/Trigger.java create mode 100644 h2/src/main/org/h2/api/UserToRolesMapper.java create mode 100644 h2/src/main/org/h2/api/package.html create mode 100644 h2/src/main/org/h2/bnf/Bnf.java create mode 100644 h2/src/main/org/h2/bnf/BnfVisitor.java create mode 100644 h2/src/main/org/h2/bnf/Rule.java create mode 100644 h2/src/main/org/h2/bnf/RuleElement.java create mode 100644 h2/src/main/org/h2/bnf/RuleExtension.java create mode 100644 h2/src/main/org/h2/bnf/RuleFixed.java create mode 100644 h2/src/main/org/h2/bnf/RuleHead.java create mode 100644 h2/src/main/org/h2/bnf/RuleList.java create mode 100644 h2/src/main/org/h2/bnf/RuleOptional.java create mode 100644 h2/src/main/org/h2/bnf/RuleRepeat.java create mode 100644 h2/src/main/org/h2/bnf/Sentence.java create mode 100644 h2/src/main/org/h2/bnf/context/DbColumn.java create mode 100644 h2/src/main/org/h2/bnf/context/DbContents.java create mode 100644 h2/src/main/org/h2/bnf/context/DbContextRule.java create mode 100644 h2/src/main/org/h2/bnf/context/DbProcedure.java create mode 100644 h2/src/main/org/h2/bnf/context/DbSchema.java create mode 100644 h2/src/main/org/h2/bnf/context/DbTableOrView.java create mode 100644 h2/src/main/org/h2/bnf/context/package.html create mode 100644 h2/src/main/org/h2/bnf/package.html create mode 100644 h2/src/main/org/h2/command/Command.java create mode 100644 h2/src/main/org/h2/command/CommandContainer.java create mode 100644 h2/src/main/org/h2/command/CommandInterface.java create mode 100644 h2/src/main/org/h2/command/CommandList.java create mode 100644 h2/src/main/org/h2/command/CommandRemote.java create mode 100644 h2/src/main/org/h2/command/Parser.java create mode 100644 h2/src/main/org/h2/command/Prepared.java create mode 100644 h2/src/main/org/h2/command/Token.java create mode 100644 h2/src/main/org/h2/command/Tokenizer.java create mode 100644 h2/src/main/org/h2/command/ddl/AlterDomain.java create mode 100644 h2/src/main/org/h2/command/ddl/AlterDomainAddConstraint.java create mode 100644 h2/src/main/org/h2/command/ddl/AlterDomainDropConstraint.java create mode 100644 h2/src/main/org/h2/command/ddl/AlterDomainExpressions.java create mode 100644 h2/src/main/org/h2/command/ddl/AlterDomainRename.java create mode 100644 h2/src/main/org/h2/command/ddl/AlterDomainRenameConstraint.java create mode 100644 h2/src/main/org/h2/command/ddl/AlterIndexRename.java create mode 100644 h2/src/main/org/h2/command/ddl/AlterSchemaRename.java create mode 100644 h2/src/main/org/h2/command/ddl/AlterSequence.java create mode 100644 h2/src/main/org/h2/command/ddl/AlterTable.java create mode 100644 h2/src/main/org/h2/command/ddl/AlterTableAddConstraint.java create mode 100644 h2/src/main/org/h2/command/ddl/AlterTableAlterColumn.java create mode 100644 h2/src/main/org/h2/command/ddl/AlterTableDropConstraint.java create mode 100644 h2/src/main/org/h2/command/ddl/AlterTableRename.java create mode 100644 h2/src/main/org/h2/command/ddl/AlterTableRenameColumn.java create mode 100644 h2/src/main/org/h2/command/ddl/AlterTableRenameConstraint.java create mode 100644 h2/src/main/org/h2/command/ddl/AlterUser.java create mode 100644 h2/src/main/org/h2/command/ddl/AlterView.java create mode 100644 h2/src/main/org/h2/command/ddl/Analyze.java create mode 100644 h2/src/main/org/h2/command/ddl/CommandWithColumns.java create mode 100644 h2/src/main/org/h2/command/ddl/CreateAggregate.java create mode 100644 h2/src/main/org/h2/command/ddl/CreateConstant.java create mode 100644 h2/src/main/org/h2/command/ddl/CreateDomain.java create mode 100644 h2/src/main/org/h2/command/ddl/CreateFunctionAlias.java create mode 100644 h2/src/main/org/h2/command/ddl/CreateIndex.java create mode 100644 h2/src/main/org/h2/command/ddl/CreateLinkedTable.java create mode 100644 h2/src/main/org/h2/command/ddl/CreateRole.java create mode 100644 h2/src/main/org/h2/command/ddl/CreateSchema.java create mode 100644 h2/src/main/org/h2/command/ddl/CreateSequence.java create mode 100644 h2/src/main/org/h2/command/ddl/CreateSynonym.java create mode 100644 h2/src/main/org/h2/command/ddl/CreateSynonymData.java create mode 100644 h2/src/main/org/h2/command/ddl/CreateTable.java create mode 100644 h2/src/main/org/h2/command/ddl/CreateTableData.java create mode 100644 h2/src/main/org/h2/command/ddl/CreateTrigger.java create mode 100644 h2/src/main/org/h2/command/ddl/CreateUser.java create mode 100644 h2/src/main/org/h2/command/ddl/CreateView.java create mode 100644 h2/src/main/org/h2/command/ddl/DeallocateProcedure.java create mode 100644 h2/src/main/org/h2/command/ddl/DefineCommand.java create mode 100644 h2/src/main/org/h2/command/ddl/DropAggregate.java create mode 100644 h2/src/main/org/h2/command/ddl/DropConstant.java create mode 100644 h2/src/main/org/h2/command/ddl/DropDatabase.java create mode 100644 h2/src/main/org/h2/command/ddl/DropDomain.java create mode 100644 h2/src/main/org/h2/command/ddl/DropFunctionAlias.java create mode 100644 h2/src/main/org/h2/command/ddl/DropIndex.java create mode 100644 h2/src/main/org/h2/command/ddl/DropRole.java create mode 100644 h2/src/main/org/h2/command/ddl/DropSchema.java create mode 100644 h2/src/main/org/h2/command/ddl/DropSequence.java create mode 100644 h2/src/main/org/h2/command/ddl/DropSynonym.java create mode 100644 h2/src/main/org/h2/command/ddl/DropTable.java create mode 100644 h2/src/main/org/h2/command/ddl/DropTrigger.java create mode 100644 h2/src/main/org/h2/command/ddl/DropUser.java create mode 100644 h2/src/main/org/h2/command/ddl/DropView.java create mode 100644 h2/src/main/org/h2/command/ddl/GrantRevoke.java create mode 100644 h2/src/main/org/h2/command/ddl/PrepareProcedure.java create mode 100644 h2/src/main/org/h2/command/ddl/SchemaCommand.java create mode 100644 h2/src/main/org/h2/command/ddl/SchemaOwnerCommand.java create mode 100644 h2/src/main/org/h2/command/ddl/SequenceOptions.java create mode 100644 h2/src/main/org/h2/command/ddl/SetComment.java create mode 100644 h2/src/main/org/h2/command/ddl/TruncateTable.java create mode 100644 h2/src/main/org/h2/command/ddl/package.html create mode 100644 h2/src/main/org/h2/command/dml/AlterTableSet.java create mode 100644 h2/src/main/org/h2/command/dml/BackupCommand.java create mode 100644 h2/src/main/org/h2/command/dml/Call.java create mode 100644 h2/src/main/org/h2/command/dml/CommandWithValues.java create mode 100644 h2/src/main/org/h2/command/dml/DataChangeStatement.java create mode 100644 h2/src/main/org/h2/command/dml/Delete.java create mode 100644 h2/src/main/org/h2/command/dml/ExecuteImmediate.java create mode 100644 h2/src/main/org/h2/command/dml/ExecuteProcedure.java create mode 100644 h2/src/main/org/h2/command/dml/Explain.java create mode 100644 h2/src/main/org/h2/command/dml/FilteredDataChangeStatement.java create mode 100644 h2/src/main/org/h2/command/dml/Help.java create mode 100644 h2/src/main/org/h2/command/dml/Insert.java create mode 100644 h2/src/main/org/h2/command/dml/Merge.java create mode 100644 h2/src/main/org/h2/command/dml/MergeUsing.java create mode 100644 h2/src/main/org/h2/command/dml/NoOperation.java create mode 100644 h2/src/main/org/h2/command/dml/RunScriptCommand.java create mode 100644 h2/src/main/org/h2/command/dml/ScriptBase.java create mode 100644 h2/src/main/org/h2/command/dml/ScriptCommand.java create mode 100644 h2/src/main/org/h2/command/dml/Set.java create mode 100644 h2/src/main/org/h2/command/dml/SetClauseList.java create mode 100644 h2/src/main/org/h2/command/dml/SetSessionCharacteristics.java create mode 100644 h2/src/main/org/h2/command/dml/SetTypes.java create mode 100644 h2/src/main/org/h2/command/dml/TransactionCommand.java create mode 100644 h2/src/main/org/h2/command/dml/Update.java create mode 100644 h2/src/main/org/h2/command/dml/package.html create mode 100644 h2/src/main/org/h2/command/package.html create mode 100644 h2/src/main/org/h2/command/query/AllColumnsForPlan.java create mode 100644 h2/src/main/org/h2/command/query/Optimizer.java create mode 100644 h2/src/main/org/h2/command/query/Query.java create mode 100644 h2/src/main/org/h2/command/query/QueryOrderBy.java create mode 100644 h2/src/main/org/h2/command/query/Select.java create mode 100644 h2/src/main/org/h2/command/query/SelectGroups.java create mode 100644 h2/src/main/org/h2/command/query/SelectListColumnResolver.java create mode 100644 h2/src/main/org/h2/command/query/SelectUnion.java create mode 100644 h2/src/main/org/h2/command/query/TableValueConstructor.java create mode 100644 h2/src/main/org/h2/command/query/package.html create mode 100644 h2/src/main/org/h2/compress/CompressDeflate.java create mode 100644 h2/src/main/org/h2/compress/CompressLZF.java create mode 100644 h2/src/main/org/h2/compress/CompressNo.java create mode 100644 h2/src/main/org/h2/compress/Compressor.java create mode 100644 h2/src/main/org/h2/compress/LZFInputStream.java create mode 100644 h2/src/main/org/h2/compress/LZFOutputStream.java create mode 100644 h2/src/main/org/h2/compress/package.html create mode 100644 h2/src/main/org/h2/constraint/Constraint.java create mode 100644 h2/src/main/org/h2/constraint/ConstraintActionType.java create mode 100644 h2/src/main/org/h2/constraint/ConstraintCheck.java create mode 100644 h2/src/main/org/h2/constraint/ConstraintDomain.java create mode 100644 h2/src/main/org/h2/constraint/ConstraintReferential.java create mode 100644 h2/src/main/org/h2/constraint/ConstraintUnique.java create mode 100644 h2/src/main/org/h2/constraint/DomainColumnResolver.java create mode 100644 h2/src/main/org/h2/constraint/package.html create mode 100644 h2/src/main/org/h2/engine/CastDataProvider.java create mode 100644 h2/src/main/org/h2/engine/Comment.java create mode 100644 h2/src/main/org/h2/engine/ConnectionInfo.java create mode 100644 h2/src/main/org/h2/engine/Constants.java create mode 100644 h2/src/main/org/h2/engine/Database.java create mode 100644 h2/src/main/org/h2/engine/DbObject.java create mode 100644 h2/src/main/org/h2/engine/DbSettings.java create mode 100644 h2/src/main/org/h2/engine/DelayedDatabaseCloser.java create mode 100644 h2/src/main/org/h2/engine/Engine.java create mode 100644 h2/src/main/org/h2/engine/GeneratedKeysMode.java create mode 100644 h2/src/main/org/h2/engine/IsolationLevel.java create mode 100644 h2/src/main/org/h2/engine/MetaRecord.java create mode 100644 h2/src/main/org/h2/engine/Mode.java create mode 100644 h2/src/main/org/h2/engine/OnExitDatabaseCloser.java create mode 100644 h2/src/main/org/h2/engine/Procedure.java create mode 100644 h2/src/main/org/h2/engine/QueryStatisticsData.java create mode 100644 h2/src/main/org/h2/engine/Right.java create mode 100644 h2/src/main/org/h2/engine/RightOwner.java create mode 100644 h2/src/main/org/h2/engine/Role.java create mode 100644 h2/src/main/org/h2/engine/Session.java create mode 100644 h2/src/main/org/h2/engine/SessionLocal.java create mode 100644 h2/src/main/org/h2/engine/SessionRemote.java create mode 100644 h2/src/main/org/h2/engine/Setting.java create mode 100644 h2/src/main/org/h2/engine/SettingsBase.java create mode 100644 h2/src/main/org/h2/engine/SysProperties.java create mode 100644 h2/src/main/org/h2/engine/User.java create mode 100644 h2/src/main/org/h2/engine/UserBuilder.java create mode 100644 h2/src/main/org/h2/engine/package.html create mode 100644 h2/src/main/org/h2/expression/Alias.java create mode 100644 h2/src/main/org/h2/expression/ArrayConstructorByQuery.java create mode 100644 h2/src/main/org/h2/expression/ArrayElementReference.java create mode 100644 h2/src/main/org/h2/expression/BinaryOperation.java create mode 100644 h2/src/main/org/h2/expression/CompatibilityDatePlusTimeOperation.java create mode 100644 h2/src/main/org/h2/expression/ConcatenationOperation.java create mode 100644 h2/src/main/org/h2/expression/DomainValueExpression.java create mode 100644 h2/src/main/org/h2/expression/Expression.java create mode 100644 h2/src/main/org/h2/expression/ExpressionColumn.java create mode 100644 h2/src/main/org/h2/expression/ExpressionList.java create mode 100644 h2/src/main/org/h2/expression/ExpressionVisitor.java create mode 100644 h2/src/main/org/h2/expression/ExpressionWithFlags.java create mode 100644 h2/src/main/org/h2/expression/ExpressionWithVariableParameters.java create mode 100644 h2/src/main/org/h2/expression/FieldReference.java create mode 100644 h2/src/main/org/h2/expression/Format.java create mode 100644 h2/src/main/org/h2/expression/IntervalOperation.java create mode 100644 h2/src/main/org/h2/expression/Operation0.java create mode 100644 h2/src/main/org/h2/expression/Operation1.java create mode 100644 h2/src/main/org/h2/expression/Operation1_2.java create mode 100644 h2/src/main/org/h2/expression/Operation2.java create mode 100644 h2/src/main/org/h2/expression/OperationN.java create mode 100644 h2/src/main/org/h2/expression/Parameter.java create mode 100644 h2/src/main/org/h2/expression/ParameterInterface.java create mode 100644 h2/src/main/org/h2/expression/ParameterRemote.java create mode 100644 h2/src/main/org/h2/expression/Rownum.java create mode 100644 h2/src/main/org/h2/expression/SearchedCase.java create mode 100644 h2/src/main/org/h2/expression/SequenceValue.java create mode 100644 h2/src/main/org/h2/expression/SimpleCase.java create mode 100644 h2/src/main/org/h2/expression/Subquery.java create mode 100644 h2/src/main/org/h2/expression/TimeZoneOperation.java create mode 100644 h2/src/main/org/h2/expression/TypedValueExpression.java create mode 100644 h2/src/main/org/h2/expression/UnaryOperation.java create mode 100644 h2/src/main/org/h2/expression/ValueExpression.java create mode 100644 h2/src/main/org/h2/expression/Variable.java create mode 100644 h2/src/main/org/h2/expression/Wildcard.java create mode 100644 h2/src/main/org/h2/expression/aggregate/AbstractAggregate.java create mode 100644 h2/src/main/org/h2/expression/aggregate/Aggregate.java create mode 100644 h2/src/main/org/h2/expression/aggregate/AggregateData.java create mode 100644 h2/src/main/org/h2/expression/aggregate/AggregateDataAvg.java create mode 100644 h2/src/main/org/h2/expression/aggregate/AggregateDataBinarySet.java create mode 100644 h2/src/main/org/h2/expression/aggregate/AggregateDataCollecting.java create mode 100644 h2/src/main/org/h2/expression/aggregate/AggregateDataCorr.java create mode 100644 h2/src/main/org/h2/expression/aggregate/AggregateDataCount.java create mode 100644 h2/src/main/org/h2/expression/aggregate/AggregateDataCovar.java create mode 100644 h2/src/main/org/h2/expression/aggregate/AggregateDataDefault.java create mode 100644 h2/src/main/org/h2/expression/aggregate/AggregateDataDistinctWithCounts.java create mode 100644 h2/src/main/org/h2/expression/aggregate/AggregateDataEnvelope.java create mode 100644 h2/src/main/org/h2/expression/aggregate/AggregateDataStdVar.java create mode 100644 h2/src/main/org/h2/expression/aggregate/AggregateType.java create mode 100644 h2/src/main/org/h2/expression/aggregate/JavaAggregate.java create mode 100644 h2/src/main/org/h2/expression/aggregate/ListaggArguments.java create mode 100644 h2/src/main/org/h2/expression/aggregate/LongDataCounter.java create mode 100644 h2/src/main/org/h2/expression/aggregate/Percentile.java create mode 100644 h2/src/main/org/h2/expression/aggregate/package.html create mode 100644 h2/src/main/org/h2/expression/analysis/DataAnalysisOperation.java create mode 100644 h2/src/main/org/h2/expression/analysis/PartitionData.java create mode 100644 h2/src/main/org/h2/expression/analysis/Window.java create mode 100644 h2/src/main/org/h2/expression/analysis/WindowFrame.java create mode 100644 h2/src/main/org/h2/expression/analysis/WindowFrameBound.java create mode 100644 h2/src/main/org/h2/expression/analysis/WindowFrameBoundType.java create mode 100644 h2/src/main/org/h2/expression/analysis/WindowFrameExclusion.java create mode 100644 h2/src/main/org/h2/expression/analysis/WindowFrameUnits.java create mode 100644 h2/src/main/org/h2/expression/analysis/WindowFunction.java create mode 100644 h2/src/main/org/h2/expression/analysis/WindowFunctionType.java create mode 100644 h2/src/main/org/h2/expression/analysis/package.html create mode 100644 h2/src/main/org/h2/expression/condition/BetweenPredicate.java create mode 100644 h2/src/main/org/h2/expression/condition/BooleanTest.java create mode 100644 h2/src/main/org/h2/expression/condition/CompareLike.java create mode 100644 h2/src/main/org/h2/expression/condition/Comparison.java create mode 100644 h2/src/main/org/h2/expression/condition/Condition.java create mode 100644 h2/src/main/org/h2/expression/condition/ConditionAndOr.java create mode 100644 h2/src/main/org/h2/expression/condition/ConditionAndOrN.java create mode 100644 h2/src/main/org/h2/expression/condition/ConditionIn.java create mode 100644 h2/src/main/org/h2/expression/condition/ConditionInConstantSet.java create mode 100644 h2/src/main/org/h2/expression/condition/ConditionInParameter.java create mode 100644 h2/src/main/org/h2/expression/condition/ConditionInQuery.java create mode 100644 h2/src/main/org/h2/expression/condition/ConditionLocalAndGlobal.java create mode 100644 h2/src/main/org/h2/expression/condition/ConditionNot.java create mode 100644 h2/src/main/org/h2/expression/condition/ExistsPredicate.java create mode 100644 h2/src/main/org/h2/expression/condition/IsJsonPredicate.java create mode 100644 h2/src/main/org/h2/expression/condition/NullPredicate.java create mode 100644 h2/src/main/org/h2/expression/condition/PredicateWithSubquery.java create mode 100644 h2/src/main/org/h2/expression/condition/SimplePredicate.java create mode 100644 h2/src/main/org/h2/expression/condition/TypePredicate.java create mode 100644 h2/src/main/org/h2/expression/condition/UniquePredicate.java create mode 100644 h2/src/main/org/h2/expression/condition/package.html create mode 100644 h2/src/main/org/h2/expression/function/ArrayFunction.java create mode 100644 h2/src/main/org/h2/expression/function/BitFunction.java create mode 100644 h2/src/main/org/h2/expression/function/BuiltinFunctions.java create mode 100644 h2/src/main/org/h2/expression/function/CSVWriteFunction.java create mode 100644 h2/src/main/org/h2/expression/function/CardinalityExpression.java create mode 100644 h2/src/main/org/h2/expression/function/CastSpecification.java create mode 100644 h2/src/main/org/h2/expression/function/CoalesceFunction.java create mode 100644 h2/src/main/org/h2/expression/function/CompatibilitySequenceValueFunction.java create mode 100644 h2/src/main/org/h2/expression/function/CompressFunction.java create mode 100644 h2/src/main/org/h2/expression/function/ConcatFunction.java create mode 100644 h2/src/main/org/h2/expression/function/CryptFunction.java create mode 100644 h2/src/main/org/h2/expression/function/CurrentDateTimeValueFunction.java create mode 100644 h2/src/main/org/h2/expression/function/CurrentGeneralValueSpecification.java create mode 100644 h2/src/main/org/h2/expression/function/DBObjectFunction.java create mode 100644 h2/src/main/org/h2/expression/function/DataTypeSQLFunction.java create mode 100644 h2/src/main/org/h2/expression/function/DateTimeFormatFunction.java create mode 100644 h2/src/main/org/h2/expression/function/DateTimeFunction.java create mode 100644 h2/src/main/org/h2/expression/function/DayMonthNameFunction.java create mode 100644 h2/src/main/org/h2/expression/function/FileFunction.java create mode 100644 h2/src/main/org/h2/expression/function/Function0_1.java create mode 100644 h2/src/main/org/h2/expression/function/Function1.java create mode 100644 h2/src/main/org/h2/expression/function/Function1_2.java create mode 100644 h2/src/main/org/h2/expression/function/Function2.java create mode 100644 h2/src/main/org/h2/expression/function/FunctionN.java create mode 100644 h2/src/main/org/h2/expression/function/HashFunction.java create mode 100644 h2/src/main/org/h2/expression/function/JavaFunction.java create mode 100644 h2/src/main/org/h2/expression/function/JsonConstructorFunction.java create mode 100644 h2/src/main/org/h2/expression/function/LengthFunction.java create mode 100644 h2/src/main/org/h2/expression/function/MathFunction.java create mode 100644 h2/src/main/org/h2/expression/function/MathFunction1.java create mode 100644 h2/src/main/org/h2/expression/function/MathFunction2.java create mode 100644 h2/src/main/org/h2/expression/function/NamedExpression.java create mode 100644 h2/src/main/org/h2/expression/function/NullIfFunction.java create mode 100644 h2/src/main/org/h2/expression/function/RandFunction.java create mode 100644 h2/src/main/org/h2/expression/function/RegexpFunction.java create mode 100644 h2/src/main/org/h2/expression/function/SessionControlFunction.java create mode 100644 h2/src/main/org/h2/expression/function/SetFunction.java create mode 100644 h2/src/main/org/h2/expression/function/SignalFunction.java create mode 100644 h2/src/main/org/h2/expression/function/SoundexFunction.java create mode 100644 h2/src/main/org/h2/expression/function/StringFunction.java create mode 100644 h2/src/main/org/h2/expression/function/StringFunction1.java create mode 100644 h2/src/main/org/h2/expression/function/StringFunction2.java create mode 100644 h2/src/main/org/h2/expression/function/SubstringFunction.java create mode 100644 h2/src/main/org/h2/expression/function/SysInfoFunction.java create mode 100644 h2/src/main/org/h2/expression/function/TableInfoFunction.java create mode 100644 h2/src/main/org/h2/expression/function/ToCharFunction.java create mode 100644 h2/src/main/org/h2/expression/function/TrimFunction.java create mode 100644 h2/src/main/org/h2/expression/function/TruncateValueFunction.java create mode 100644 h2/src/main/org/h2/expression/function/XMLFunction.java create mode 100644 h2/src/main/org/h2/expression/function/package.html create mode 100644 h2/src/main/org/h2/expression/function/table/ArrayTableFunction.java create mode 100644 h2/src/main/org/h2/expression/function/table/CSVReadFunction.java create mode 100644 h2/src/main/org/h2/expression/function/table/JavaTableFunction.java create mode 100644 h2/src/main/org/h2/expression/function/table/LinkSchemaFunction.java create mode 100644 h2/src/main/org/h2/expression/function/table/TableFunction.java create mode 100644 h2/src/main/org/h2/expression/function/table/package.html create mode 100644 h2/src/main/org/h2/expression/package.html create mode 100644 h2/src/main/org/h2/fulltext/FullText.java create mode 100644 h2/src/main/org/h2/fulltext/FullTextLucene.java create mode 100644 h2/src/main/org/h2/fulltext/FullTextSettings.java create mode 100644 h2/src/main/org/h2/fulltext/IndexInfo.java create mode 100644 h2/src/main/org/h2/fulltext/package.html create mode 100644 h2/src/main/org/h2/index/Cursor.java create mode 100644 h2/src/main/org/h2/index/DualCursor.java create mode 100644 h2/src/main/org/h2/index/DualIndex.java create mode 100644 h2/src/main/org/h2/index/Index.java create mode 100644 h2/src/main/org/h2/index/IndexCondition.java create mode 100644 h2/src/main/org/h2/index/IndexCursor.java create mode 100644 h2/src/main/org/h2/index/IndexType.java create mode 100644 h2/src/main/org/h2/index/LinkedCursor.java create mode 100644 h2/src/main/org/h2/index/LinkedIndex.java create mode 100644 h2/src/main/org/h2/index/MetaCursor.java create mode 100644 h2/src/main/org/h2/index/MetaIndex.java create mode 100644 h2/src/main/org/h2/index/QueryExpressionCursor.java create mode 100644 h2/src/main/org/h2/index/QueryExpressionIndex.java create mode 100644 h2/src/main/org/h2/index/RangeCursor.java create mode 100644 h2/src/main/org/h2/index/RangeIndex.java create mode 100644 h2/src/main/org/h2/index/SingleRowCursor.java create mode 100644 h2/src/main/org/h2/index/SpatialIndex.java create mode 100644 h2/src/main/org/h2/index/VirtualConstructedTableIndex.java create mode 100644 h2/src/main/org/h2/index/VirtualTableCursor.java create mode 100644 h2/src/main/org/h2/index/VirtualTableIndex.java create mode 100644 h2/src/main/org/h2/index/package.html create mode 100644 h2/src/main/org/h2/jdbc/JdbcArray.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcBatchUpdateException.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcBlob.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcCallableStatement.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcClob.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcConnection.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcConnectionBackwardsCompat.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcDatabaseMetaData.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcDatabaseMetaDataBackwardsCompat.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcException.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcLob.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcParameterMetaData.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcPreparedStatement.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcResultSet.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcResultSetMetaData.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcSQLDataException.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcSQLException.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcSQLFeatureNotSupportedException.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcSQLIntegrityConstraintViolationException.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcSQLInvalidAuthorizationSpecException.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcSQLNonTransientConnectionException.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcSQLNonTransientException.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcSQLSyntaxErrorException.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcSQLTimeoutException.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcSQLTransactionRollbackException.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcSQLTransientException.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcSQLXML.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcSavepoint.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcStatement.java create mode 100644 h2/src/main/org/h2/jdbc/JdbcStatementBackwardsCompat.java create mode 100644 h2/src/main/org/h2/jdbc/meta/DatabaseMeta.java create mode 100644 h2/src/main/org/h2/jdbc/meta/DatabaseMetaLegacy.java create mode 100644 h2/src/main/org/h2/jdbc/meta/DatabaseMetaLocal.java create mode 100644 h2/src/main/org/h2/jdbc/meta/DatabaseMetaLocalBase.java create mode 100644 h2/src/main/org/h2/jdbc/meta/DatabaseMetaRemote.java create mode 100644 h2/src/main/org/h2/jdbc/meta/DatabaseMetaServer.java create mode 100644 h2/src/main/org/h2/jdbc/meta/package.html create mode 100644 h2/src/main/org/h2/jdbc/package.html create mode 100644 h2/src/main/org/h2/jdbcx/JdbcConnectionPool.java create mode 100644 h2/src/main/org/h2/jdbcx/JdbcConnectionPoolBackwardsCompat.java create mode 100644 h2/src/main/org/h2/jdbcx/JdbcDataSource.java create mode 100644 h2/src/main/org/h2/jdbcx/JdbcDataSourceBackwardsCompat.java create mode 100644 h2/src/main/org/h2/jdbcx/JdbcDataSourceFactory.java create mode 100644 h2/src/main/org/h2/jdbcx/JdbcXAConnection.java create mode 100644 h2/src/main/org/h2/jdbcx/JdbcXid.java create mode 100644 h2/src/main/org/h2/jdbcx/package.html create mode 100644 h2/src/main/org/h2/jmx/DatabaseInfo.java create mode 100644 h2/src/main/org/h2/jmx/DatabaseInfoMBean.java create mode 100644 h2/src/main/org/h2/jmx/DocumentedMBean.java create mode 100644 h2/src/main/org/h2/jmx/package.html create mode 100644 h2/src/main/org/h2/message/DbException.java create mode 100644 h2/src/main/org/h2/message/Trace.java create mode 100644 h2/src/main/org/h2/message/TraceObject.java create mode 100644 h2/src/main/org/h2/message/TraceSystem.java create mode 100644 h2/src/main/org/h2/message/TraceWriter.java create mode 100644 h2/src/main/org/h2/message/TraceWriterAdapter.java create mode 100644 h2/src/main/org/h2/message/package.html create mode 100644 h2/src/main/org/h2/mode/CompatibilityDateTimeValueFunction.java create mode 100644 h2/src/main/org/h2/mode/DefaultNullOrdering.java create mode 100644 h2/src/main/org/h2/mode/FunctionInfo.java create mode 100644 h2/src/main/org/h2/mode/FunctionsDB2Derby.java create mode 100644 h2/src/main/org/h2/mode/FunctionsLegacy.java create mode 100644 h2/src/main/org/h2/mode/FunctionsMSSQLServer.java create mode 100644 h2/src/main/org/h2/mode/FunctionsMySQL.java create mode 100644 h2/src/main/org/h2/mode/FunctionsOracle.java create mode 100644 h2/src/main/org/h2/mode/FunctionsPostgreSQL.java create mode 100644 h2/src/main/org/h2/mode/ModeFunction.java create mode 100644 h2/src/main/org/h2/mode/OnDuplicateKeyValues.java create mode 100644 h2/src/main/org/h2/mode/PgCatalogSchema.java create mode 100644 h2/src/main/org/h2/mode/PgCatalogTable.java create mode 100644 h2/src/main/org/h2/mode/Regclass.java create mode 100644 h2/src/main/org/h2/mode/ToDateParser.java create mode 100644 h2/src/main/org/h2/mode/ToDateTokenizer.java create mode 100644 h2/src/main/org/h2/mode/package.html create mode 100644 h2/src/main/org/h2/mvstore/Chunk.java create mode 100644 h2/src/main/org/h2/mvstore/Cursor.java create mode 100644 h2/src/main/org/h2/mvstore/CursorPos.java create mode 100644 h2/src/main/org/h2/mvstore/DataUtils.java create mode 100644 h2/src/main/org/h2/mvstore/FileStore.java create mode 100644 h2/src/main/org/h2/mvstore/FreeSpaceBitSet.java create mode 100644 h2/src/main/org/h2/mvstore/MVMap.java create mode 100644 h2/src/main/org/h2/mvstore/MVStore.java create mode 100644 h2/src/main/org/h2/mvstore/MVStoreException.java create mode 100644 h2/src/main/org/h2/mvstore/MVStoreTool.java create mode 100644 h2/src/main/org/h2/mvstore/OffHeapStore.java create mode 100644 h2/src/main/org/h2/mvstore/Page.java create mode 100644 h2/src/main/org/h2/mvstore/RootReference.java create mode 100644 h2/src/main/org/h2/mvstore/StreamStore.java create mode 100644 h2/src/main/org/h2/mvstore/WriteBuffer.java create mode 100644 h2/src/main/org/h2/mvstore/cache/CacheLongKeyLIRS.java create mode 100644 h2/src/main/org/h2/mvstore/cache/FilePathCache.java create mode 100644 h2/src/main/org/h2/mvstore/cache/package.html create mode 100644 h2/src/main/org/h2/mvstore/db/LobStorageMap.java create mode 100644 h2/src/main/org/h2/mvstore/db/MVDelegateIndex.java create mode 100644 h2/src/main/org/h2/mvstore/db/MVInDoubtTransaction.java create mode 100644 h2/src/main/org/h2/mvstore/db/MVIndex.java create mode 100644 h2/src/main/org/h2/mvstore/db/MVPlainTempResult.java create mode 100644 h2/src/main/org/h2/mvstore/db/MVPrimaryIndex.java create mode 100644 h2/src/main/org/h2/mvstore/db/MVSecondaryIndex.java create mode 100644 h2/src/main/org/h2/mvstore/db/MVSortedTempResult.java create mode 100644 h2/src/main/org/h2/mvstore/db/MVSpatialIndex.java create mode 100644 h2/src/main/org/h2/mvstore/db/MVTable.java create mode 100644 h2/src/main/org/h2/mvstore/db/MVTempResult.java create mode 100644 h2/src/main/org/h2/mvstore/db/NullValueDataType.java create mode 100644 h2/src/main/org/h2/mvstore/db/RowDataType.java create mode 100644 h2/src/main/org/h2/mvstore/db/SpatialKey.java create mode 100644 h2/src/main/org/h2/mvstore/db/Store.java create mode 100644 h2/src/main/org/h2/mvstore/db/ValueDataType.java create mode 100644 h2/src/main/org/h2/mvstore/db/package.html create mode 100644 h2/src/main/org/h2/mvstore/package.html create mode 100644 h2/src/main/org/h2/mvstore/rtree/DefaultSpatial.java create mode 100644 h2/src/main/org/h2/mvstore/rtree/MVRTreeMap.java create mode 100644 h2/src/main/org/h2/mvstore/rtree/Spatial.java create mode 100644 h2/src/main/org/h2/mvstore/rtree/SpatialDataType.java create mode 100644 h2/src/main/org/h2/mvstore/rtree/package.html create mode 100644 h2/src/main/org/h2/mvstore/tx/CommitDecisionMaker.java create mode 100644 h2/src/main/org/h2/mvstore/tx/Record.java create mode 100644 h2/src/main/org/h2/mvstore/tx/RollbackDecisionMaker.java create mode 100644 h2/src/main/org/h2/mvstore/tx/Snapshot.java create mode 100644 h2/src/main/org/h2/mvstore/tx/Transaction.java create mode 100644 h2/src/main/org/h2/mvstore/tx/TransactionMap.java create mode 100644 h2/src/main/org/h2/mvstore/tx/TransactionStore.java create mode 100644 h2/src/main/org/h2/mvstore/tx/TxDecisionMaker.java create mode 100644 h2/src/main/org/h2/mvstore/tx/VersionedBitSet.java create mode 100644 h2/src/main/org/h2/mvstore/tx/VersionedValueCommitted.java create mode 100644 h2/src/main/org/h2/mvstore/tx/VersionedValueType.java create mode 100644 h2/src/main/org/h2/mvstore/tx/VersionedValueUncommitted.java create mode 100644 h2/src/main/org/h2/mvstore/tx/package.html create mode 100644 h2/src/main/org/h2/mvstore/type/BasicDataType.java create mode 100644 h2/src/main/org/h2/mvstore/type/ByteArrayDataType.java create mode 100644 h2/src/main/org/h2/mvstore/type/DataType.java create mode 100644 h2/src/main/org/h2/mvstore/type/LongDataType.java create mode 100644 h2/src/main/org/h2/mvstore/type/MetaType.java create mode 100644 h2/src/main/org/h2/mvstore/type/ObjectDataType.java create mode 100644 h2/src/main/org/h2/mvstore/type/StatefulDataType.java create mode 100644 h2/src/main/org/h2/mvstore/type/StringDataType.java create mode 100644 h2/src/main/org/h2/mvstore/type/package.html create mode 100644 h2/src/main/org/h2/package.html create mode 100644 h2/src/main/org/h2/res/_messages_cs.prop create mode 100644 h2/src/main/org/h2/res/_messages_de.prop create mode 100644 h2/src/main/org/h2/res/_messages_en.prop create mode 100644 h2/src/main/org/h2/res/_messages_es.prop create mode 100644 h2/src/main/org/h2/res/_messages_fr.prop create mode 100644 h2/src/main/org/h2/res/_messages_ja.prop create mode 100644 h2/src/main/org/h2/res/_messages_pl.prop create mode 100644 h2/src/main/org/h2/res/_messages_pt_br.prop create mode 100644 h2/src/main/org/h2/res/_messages_ru.prop create mode 100644 h2/src/main/org/h2/res/_messages_sk.prop create mode 100644 h2/src/main/org/h2/res/_messages_zh_cn.prop create mode 100644 h2/src/main/org/h2/res/h2-22-t.png create mode 100644 h2/src/main/org/h2/res/h2-22.png create mode 100644 h2/src/main/org/h2/res/h2-24.png create mode 100644 h2/src/main/org/h2/res/h2-64-t.png create mode 100644 h2/src/main/org/h2/res/h2.png create mode 100644 h2/src/main/org/h2/res/help.csv create mode 100644 h2/src/main/org/h2/res/javadoc.properties create mode 100644 h2/src/main/org/h2/result/DefaultRow.java create mode 100644 h2/src/main/org/h2/result/FetchedResult.java create mode 100644 h2/src/main/org/h2/result/LazyResult.java create mode 100644 h2/src/main/org/h2/result/LocalResult.java create mode 100644 h2/src/main/org/h2/result/MergedResult.java create mode 100644 h2/src/main/org/h2/result/ResultColumn.java create mode 100644 h2/src/main/org/h2/result/ResultExternal.java create mode 100644 h2/src/main/org/h2/result/ResultInterface.java create mode 100644 h2/src/main/org/h2/result/ResultRemote.java create mode 100644 h2/src/main/org/h2/result/ResultTarget.java create mode 100644 h2/src/main/org/h2/result/ResultWithGeneratedKeys.java create mode 100644 h2/src/main/org/h2/result/ResultWithPaddedStrings.java create mode 100644 h2/src/main/org/h2/result/Row.java create mode 100644 h2/src/main/org/h2/result/RowFactory.java create mode 100644 h2/src/main/org/h2/result/SearchRow.java create mode 100644 h2/src/main/org/h2/result/SimpleResult.java create mode 100644 h2/src/main/org/h2/result/SimpleRowValue.java create mode 100644 h2/src/main/org/h2/result/SortOrder.java create mode 100644 h2/src/main/org/h2/result/Sparse.java create mode 100644 h2/src/main/org/h2/result/UpdatableRow.java create mode 100644 h2/src/main/org/h2/result/package.html create mode 100644 h2/src/main/org/h2/schema/Constant.java create mode 100644 h2/src/main/org/h2/schema/Domain.java create mode 100644 h2/src/main/org/h2/schema/FunctionAlias.java create mode 100644 h2/src/main/org/h2/schema/InformationSchema.java create mode 100644 h2/src/main/org/h2/schema/MetaSchema.java create mode 100644 h2/src/main/org/h2/schema/Schema.java create mode 100644 h2/src/main/org/h2/schema/SchemaObject.java create mode 100644 h2/src/main/org/h2/schema/Sequence.java create mode 100644 h2/src/main/org/h2/schema/TriggerObject.java create mode 100644 h2/src/main/org/h2/schema/UserAggregate.java create mode 100644 h2/src/main/org/h2/schema/UserDefinedFunction.java create mode 100644 h2/src/main/org/h2/schema/package.html create mode 100644 h2/src/main/org/h2/security/AES.java create mode 100644 h2/src/main/org/h2/security/BlockCipher.java create mode 100644 h2/src/main/org/h2/security/CipherFactory.java create mode 100644 h2/src/main/org/h2/security/Fog.java create mode 100644 h2/src/main/org/h2/security/SHA256.java create mode 100644 h2/src/main/org/h2/security/SHA3.java create mode 100644 h2/src/main/org/h2/security/SecureFileStore.java create mode 100644 h2/src/main/org/h2/security/XTEA.java create mode 100644 h2/src/main/org/h2/security/auth/AuthConfigException.java create mode 100644 h2/src/main/org/h2/security/auth/AuthenticationException.java create mode 100644 h2/src/main/org/h2/security/auth/AuthenticationInfo.java create mode 100644 h2/src/main/org/h2/security/auth/Authenticator.java create mode 100644 h2/src/main/org/h2/security/auth/AuthenticatorFactory.java create mode 100644 h2/src/main/org/h2/security/auth/ConfigProperties.java create mode 100644 h2/src/main/org/h2/security/auth/Configurable.java create mode 100644 h2/src/main/org/h2/security/auth/DefaultAuthenticator.java create mode 100644 h2/src/main/org/h2/security/auth/H2AuthConfig.java create mode 100644 h2/src/main/org/h2/security/auth/H2AuthConfigXml.java create mode 100644 h2/src/main/org/h2/security/auth/HasConfigProperties.java create mode 100644 h2/src/main/org/h2/security/auth/PropertyConfig.java create mode 100644 h2/src/main/org/h2/security/auth/RealmConfig.java create mode 100644 h2/src/main/org/h2/security/auth/UserToRolesMapperConfig.java create mode 100644 h2/src/main/org/h2/security/auth/impl/AssignRealmNameRole.java create mode 100644 h2/src/main/org/h2/security/auth/impl/JaasCredentialsValidator.java create mode 100644 h2/src/main/org/h2/security/auth/impl/LdapCredentialsValidator.java create mode 100644 h2/src/main/org/h2/security/auth/impl/StaticRolesMapper.java create mode 100644 h2/src/main/org/h2/security/auth/impl/StaticUserCredentialsValidator.java create mode 100644 h2/src/main/org/h2/security/auth/impl/package.html create mode 100644 h2/src/main/org/h2/security/auth/package.html create mode 100644 h2/src/main/org/h2/security/package.html create mode 100644 h2/src/main/org/h2/server/Service.java create mode 100644 h2/src/main/org/h2/server/ShutdownHandler.java create mode 100644 h2/src/main/org/h2/server/TcpServer.java create mode 100644 h2/src/main/org/h2/server/TcpServerThread.java create mode 100644 h2/src/main/org/h2/server/package.html create mode 100644 h2/src/main/org/h2/server/pg/PgServer.java create mode 100644 h2/src/main/org/h2/server/pg/PgServerThread.java create mode 100644 h2/src/main/org/h2/server/pg/package.html create mode 100644 h2/src/main/org/h2/server/web/ConnectionInfo.java create mode 100644 h2/src/main/org/h2/server/web/DbStarter.java create mode 100644 h2/src/main/org/h2/server/web/JakartaDbStarter.java create mode 100644 h2/src/main/org/h2/server/web/JakartaWebServlet.java create mode 100644 h2/src/main/org/h2/server/web/PageParser.java create mode 100644 h2/src/main/org/h2/server/web/WebApp.java create mode 100644 h2/src/main/org/h2/server/web/WebServer.java create mode 100644 h2/src/main/org/h2/server/web/WebServlet.java create mode 100644 h2/src/main/org/h2/server/web/WebSession.java create mode 100644 h2/src/main/org/h2/server/web/WebThread.java create mode 100644 h2/src/main/org/h2/server/web/package.html create mode 100644 h2/src/main/org/h2/server/web/res/_text_cs.prop create mode 100644 h2/src/main/org/h2/server/web/res/_text_de.prop create mode 100644 h2/src/main/org/h2/server/web/res/_text_en.prop create mode 100644 h2/src/main/org/h2/server/web/res/_text_es.prop create mode 100644 h2/src/main/org/h2/server/web/res/_text_fr.prop create mode 100644 h2/src/main/org/h2/server/web/res/_text_hi.prop create mode 100644 h2/src/main/org/h2/server/web/res/_text_hu.prop create mode 100644 h2/src/main/org/h2/server/web/res/_text_in.prop create mode 100644 h2/src/main/org/h2/server/web/res/_text_it.prop create mode 100644 h2/src/main/org/h2/server/web/res/_text_ja.prop create mode 100644 h2/src/main/org/h2/server/web/res/_text_ko.prop create mode 100644 h2/src/main/org/h2/server/web/res/_text_nl.prop create mode 100644 h2/src/main/org/h2/server/web/res/_text_pl.prop create mode 100644 h2/src/main/org/h2/server/web/res/_text_pt_br.prop create mode 100644 h2/src/main/org/h2/server/web/res/_text_pt_pt.prop create mode 100644 h2/src/main/org/h2/server/web/res/_text_ru.prop create mode 100644 h2/src/main/org/h2/server/web/res/_text_sk.prop create mode 100644 h2/src/main/org/h2/server/web/res/_text_tr.prop create mode 100644 h2/src/main/org/h2/server/web/res/_text_uk.prop create mode 100644 h2/src/main/org/h2/server/web/res/_text_zh_cn.prop create mode 100644 h2/src/main/org/h2/server/web/res/_text_zh_tw.prop create mode 100644 h2/src/main/org/h2/server/web/res/admin.jsp create mode 100644 h2/src/main/org/h2/server/web/res/adminLogin.jsp create mode 100644 h2/src/main/org/h2/server/web/res/autoCompleteList.jsp create mode 100644 h2/src/main/org/h2/server/web/res/background.gif create mode 100644 h2/src/main/org/h2/server/web/res/error.jsp create mode 100644 h2/src/main/org/h2/server/web/res/favicon.ico create mode 100644 h2/src/main/org/h2/server/web/res/frame.jsp create mode 100644 h2/src/main/org/h2/server/web/res/header.jsp create mode 100644 h2/src/main/org/h2/server/web/res/help.jsp create mode 100644 h2/src/main/org/h2/server/web/res/helpTranslate.jsp create mode 100644 h2/src/main/org/h2/server/web/res/ico_add.gif create mode 100644 h2/src/main/org/h2/server/web/res/ico_ok.gif create mode 100644 h2/src/main/org/h2/server/web/res/ico_remove.gif create mode 100644 h2/src/main/org/h2/server/web/res/ico_remove_ok.gif create mode 100644 h2/src/main/org/h2/server/web/res/ico_search.gif create mode 100644 h2/src/main/org/h2/server/web/res/ico_undo.gif create mode 100644 h2/src/main/org/h2/server/web/res/ico_write.gif create mode 100644 h2/src/main/org/h2/server/web/res/icon_commit.gif create mode 100644 h2/src/main/org/h2/server/web/res/icon_disconnect.gif create mode 100644 h2/src/main/org/h2/server/web/res/icon_help.gif create mode 100644 h2/src/main/org/h2/server/web/res/icon_history.gif create mode 100644 h2/src/main/org/h2/server/web/res/icon_line.gif create mode 100644 h2/src/main/org/h2/server/web/res/icon_refresh.gif create mode 100644 h2/src/main/org/h2/server/web/res/icon_rollback.gif create mode 100644 h2/src/main/org/h2/server/web/res/icon_run.gif create mode 100644 h2/src/main/org/h2/server/web/res/icon_run_selected.gif create mode 100644 h2/src/main/org/h2/server/web/res/icon_stop.gif create mode 100644 h2/src/main/org/h2/server/web/res/index.jsp create mode 100644 h2/src/main/org/h2/server/web/res/login.jsp create mode 100644 h2/src/main/org/h2/server/web/res/notAllowed.jsp create mode 100644 h2/src/main/org/h2/server/web/res/query.jsp create mode 100644 h2/src/main/org/h2/server/web/res/result.jsp create mode 100644 h2/src/main/org/h2/server/web/res/stylesheet.css create mode 100644 h2/src/main/org/h2/server/web/res/table.js create mode 100644 h2/src/main/org/h2/server/web/res/tables.jsp create mode 100644 h2/src/main/org/h2/server/web/res/tools.jsp create mode 100644 h2/src/main/org/h2/server/web/res/tree.js create mode 100644 h2/src/main/org/h2/server/web/res/tree_column.gif create mode 100644 h2/src/main/org/h2/server/web/res/tree_database.gif create mode 100644 h2/src/main/org/h2/server/web/res/tree_empty.gif create mode 100644 h2/src/main/org/h2/server/web/res/tree_folder.gif create mode 100644 h2/src/main/org/h2/server/web/res/tree_index.gif create mode 100644 h2/src/main/org/h2/server/web/res/tree_index_az.gif create mode 100644 h2/src/main/org/h2/server/web/res/tree_info.gif create mode 100644 h2/src/main/org/h2/server/web/res/tree_line.gif create mode 100644 h2/src/main/org/h2/server/web/res/tree_minus.gif create mode 100644 h2/src/main/org/h2/server/web/res/tree_page.gif create mode 100644 h2/src/main/org/h2/server/web/res/tree_plus.gif create mode 100644 h2/src/main/org/h2/server/web/res/tree_sequence.gif create mode 100644 h2/src/main/org/h2/server/web/res/tree_sequences.gif create mode 100644 h2/src/main/org/h2/server/web/res/tree_table.gif create mode 100644 h2/src/main/org/h2/server/web/res/tree_type.gif create mode 100644 h2/src/main/org/h2/server/web/res/tree_types.gif create mode 100644 h2/src/main/org/h2/server/web/res/tree_user.gif create mode 100644 h2/src/main/org/h2/server/web/res/tree_users.gif create mode 100644 h2/src/main/org/h2/server/web/res/tree_view.gif create mode 100644 h2/src/main/org/h2/store/CountingReaderInputStream.java create mode 100644 h2/src/main/org/h2/store/Data.java create mode 100644 h2/src/main/org/h2/store/DataHandler.java create mode 100644 h2/src/main/org/h2/store/DataReader.java create mode 100644 h2/src/main/org/h2/store/FileLister.java create mode 100644 h2/src/main/org/h2/store/FileLock.java create mode 100644 h2/src/main/org/h2/store/FileLockMethod.java create mode 100644 h2/src/main/org/h2/store/FileStore.java create mode 100644 h2/src/main/org/h2/store/FileStoreInputStream.java create mode 100644 h2/src/main/org/h2/store/FileStoreOutputStream.java create mode 100644 h2/src/main/org/h2/store/InDoubtTransaction.java create mode 100644 h2/src/main/org/h2/store/LobStorageFrontend.java create mode 100644 h2/src/main/org/h2/store/LobStorageInterface.java create mode 100644 h2/src/main/org/h2/store/LobStorageRemoteInputStream.java create mode 100644 h2/src/main/org/h2/store/RangeInputStream.java create mode 100644 h2/src/main/org/h2/store/RangeReader.java create mode 100644 h2/src/main/org/h2/store/RecoverTester.java create mode 100644 h2/src/main/org/h2/store/fs/FakeFileChannel.java create mode 100644 h2/src/main/org/h2/store/fs/FileBase.java create mode 100644 h2/src/main/org/h2/store/fs/FileBaseDefault.java create mode 100644 h2/src/main/org/h2/store/fs/FileChannelInputStream.java create mode 100644 h2/src/main/org/h2/store/fs/FilePath.java create mode 100644 h2/src/main/org/h2/store/fs/FilePathWrapper.java create mode 100644 h2/src/main/org/h2/store/fs/FileUtils.java create mode 100644 h2/src/main/org/h2/store/fs/Recorder.java create mode 100644 h2/src/main/org/h2/store/fs/async/FileAsync.java create mode 100644 h2/src/main/org/h2/store/fs/async/FilePathAsync.java create mode 100644 h2/src/main/org/h2/store/fs/async/package.html create mode 100644 h2/src/main/org/h2/store/fs/disk/FilePathDisk.java create mode 100644 h2/src/main/org/h2/store/fs/disk/package.html create mode 100644 h2/src/main/org/h2/store/fs/encrypt/FileEncrypt.java create mode 100644 h2/src/main/org/h2/store/fs/encrypt/FilePathEncrypt.java create mode 100644 h2/src/main/org/h2/store/fs/encrypt/XTS.java create mode 100644 h2/src/main/org/h2/store/fs/encrypt/package.html create mode 100644 h2/src/main/org/h2/store/fs/mem/FileMem.java create mode 100644 h2/src/main/org/h2/store/fs/mem/FileMemData.java create mode 100644 h2/src/main/org/h2/store/fs/mem/FilePathMem.java create mode 100644 h2/src/main/org/h2/store/fs/mem/FilePathMemLZF.java create mode 100644 h2/src/main/org/h2/store/fs/mem/package.html create mode 100644 h2/src/main/org/h2/store/fs/niomapped/FileNioMapped.java create mode 100644 h2/src/main/org/h2/store/fs/niomapped/FilePathNioMapped.java create mode 100644 h2/src/main/org/h2/store/fs/niomapped/package.html create mode 100644 h2/src/main/org/h2/store/fs/niomem/FileNioMem.java create mode 100644 h2/src/main/org/h2/store/fs/niomem/FileNioMemData.java create mode 100644 h2/src/main/org/h2/store/fs/niomem/FilePathNioMem.java create mode 100644 h2/src/main/org/h2/store/fs/niomem/FilePathNioMemLZF.java create mode 100644 h2/src/main/org/h2/store/fs/niomem/package.html create mode 100644 h2/src/main/org/h2/store/fs/package.html create mode 100644 h2/src/main/org/h2/store/fs/rec/FilePathRec.java create mode 100644 h2/src/main/org/h2/store/fs/rec/FileRec.java create mode 100644 h2/src/main/org/h2/store/fs/rec/package.html create mode 100644 h2/src/main/org/h2/store/fs/retry/FilePathRetryOnInterrupt.java create mode 100644 h2/src/main/org/h2/store/fs/retry/FileRetryOnInterrupt.java create mode 100644 h2/src/main/org/h2/store/fs/retry/package.html create mode 100644 h2/src/main/org/h2/store/fs/split/FilePathSplit.java create mode 100644 h2/src/main/org/h2/store/fs/split/FileSplit.java create mode 100644 h2/src/main/org/h2/store/fs/split/package.html create mode 100644 h2/src/main/org/h2/store/fs/zip/FilePathZip.java create mode 100644 h2/src/main/org/h2/store/fs/zip/FileZip.java create mode 100644 h2/src/main/org/h2/store/fs/zip/package.html create mode 100644 h2/src/main/org/h2/store/package.html create mode 100644 h2/src/main/org/h2/table/Column.java create mode 100644 h2/src/main/org/h2/table/ColumnResolver.java create mode 100644 h2/src/main/org/h2/table/ColumnTemplate.java create mode 100644 h2/src/main/org/h2/table/DataChangeDeltaTable.java create mode 100644 h2/src/main/org/h2/table/DerivedTable.java create mode 100644 h2/src/main/org/h2/table/DualTable.java create mode 100644 h2/src/main/org/h2/table/FunctionTable.java create mode 100644 h2/src/main/org/h2/table/GeneratedColumnResolver.java create mode 100644 h2/src/main/org/h2/table/IndexColumn.java create mode 100644 h2/src/main/org/h2/table/IndexHints.java create mode 100644 h2/src/main/org/h2/table/InformationSchemaTable.java create mode 100644 h2/src/main/org/h2/table/InformationSchemaTableLegacy.java create mode 100644 h2/src/main/org/h2/table/MetaTable.java create mode 100644 h2/src/main/org/h2/table/Plan.java create mode 100644 h2/src/main/org/h2/table/PlanItem.java create mode 100644 h2/src/main/org/h2/table/QueryExpressionTable.java create mode 100644 h2/src/main/org/h2/table/RangeTable.java create mode 100644 h2/src/main/org/h2/table/Table.java create mode 100644 h2/src/main/org/h2/table/TableBase.java create mode 100644 h2/src/main/org/h2/table/TableFilter.java create mode 100644 h2/src/main/org/h2/table/TableLink.java create mode 100644 h2/src/main/org/h2/table/TableLinkConnection.java create mode 100644 h2/src/main/org/h2/table/TableSynonym.java create mode 100644 h2/src/main/org/h2/table/TableType.java create mode 100644 h2/src/main/org/h2/table/TableValueConstructorTable.java create mode 100644 h2/src/main/org/h2/table/TableView.java create mode 100644 h2/src/main/org/h2/table/VirtualConstructedTable.java create mode 100644 h2/src/main/org/h2/table/VirtualTable.java create mode 100644 h2/src/main/org/h2/table/package.html create mode 100644 h2/src/main/org/h2/tools/Backup.java create mode 100644 h2/src/main/org/h2/tools/ChangeFileEncryption.java create mode 100644 h2/src/main/org/h2/tools/CompressTool.java create mode 100644 h2/src/main/org/h2/tools/Console.java create mode 100644 h2/src/main/org/h2/tools/ConvertTraceFile.java create mode 100644 h2/src/main/org/h2/tools/CreateCluster.java create mode 100644 h2/src/main/org/h2/tools/Csv.java create mode 100644 h2/src/main/org/h2/tools/DeleteDbFiles.java create mode 100644 h2/src/main/org/h2/tools/GUIConsole.java create mode 100644 h2/src/main/org/h2/tools/MultiDimension.java create mode 100644 h2/src/main/org/h2/tools/Recover.java create mode 100644 h2/src/main/org/h2/tools/Restore.java create mode 100644 h2/src/main/org/h2/tools/RunScript.java create mode 100644 h2/src/main/org/h2/tools/Script.java create mode 100644 h2/src/main/org/h2/tools/Server.java create mode 100644 h2/src/main/org/h2/tools/Shell.java create mode 100644 h2/src/main/org/h2/tools/SimpleResultSet.java create mode 100644 h2/src/main/org/h2/tools/SimpleRowSource.java create mode 100644 h2/src/main/org/h2/tools/TriggerAdapter.java create mode 100644 h2/src/main/org/h2/tools/Upgrade.java create mode 100644 h2/src/main/org/h2/tools/package.html create mode 100644 h2/src/main/org/h2/util/AbbaDetector.java create mode 100644 h2/src/main/org/h2/util/AbbaLockingDetector.java create mode 100644 h2/src/main/org/h2/util/Bits.java create mode 100644 h2/src/main/org/h2/util/ByteStack.java create mode 100644 h2/src/main/org/h2/util/Cache.java create mode 100644 h2/src/main/org/h2/util/CacheHead.java create mode 100644 h2/src/main/org/h2/util/CacheLRU.java create mode 100644 h2/src/main/org/h2/util/CacheObject.java create mode 100644 h2/src/main/org/h2/util/CacheSecondLevel.java create mode 100644 h2/src/main/org/h2/util/CacheTQ.java create mode 100644 h2/src/main/org/h2/util/CacheWriter.java create mode 100644 h2/src/main/org/h2/util/CloseWatcher.java create mode 100644 h2/src/main/org/h2/util/DateTimeUtils.java create mode 100644 h2/src/main/org/h2/util/DbDriverActivator.java create mode 100644 h2/src/main/org/h2/util/DebuggingThreadLocal.java create mode 100644 h2/src/main/org/h2/util/HasSQL.java create mode 100644 h2/src/main/org/h2/util/IOUtils.java create mode 100644 h2/src/main/org/h2/util/IntArray.java create mode 100644 h2/src/main/org/h2/util/IntervalUtils.java create mode 100644 h2/src/main/org/h2/util/JSR310Utils.java create mode 100644 h2/src/main/org/h2/util/JdbcUtils.java create mode 100644 h2/src/main/org/h2/util/LegacyDateTimeUtils.java create mode 100644 h2/src/main/org/h2/util/MathUtils.java create mode 100644 h2/src/main/org/h2/util/MemoryEstimator.java create mode 100644 h2/src/main/org/h2/util/MemoryUnmapper.java create mode 100644 h2/src/main/org/h2/util/NetUtils.java create mode 100644 h2/src/main/org/h2/util/NetworkConnectionInfo.java create mode 100644 h2/src/main/org/h2/util/OsgiDataSourceFactory.java create mode 100644 h2/src/main/org/h2/util/ParserUtil.java create mode 100644 h2/src/main/org/h2/util/Permutations.java create mode 100644 h2/src/main/org/h2/util/Profiler.java create mode 100644 h2/src/main/org/h2/util/ScriptReader.java create mode 100644 h2/src/main/org/h2/util/SimpleColumnInfo.java create mode 100644 h2/src/main/org/h2/util/SmallLRUCache.java create mode 100644 h2/src/main/org/h2/util/SmallMap.java create mode 100644 h2/src/main/org/h2/util/SoftValuesHashMap.java create mode 100644 h2/src/main/org/h2/util/SortedProperties.java create mode 100644 h2/src/main/org/h2/util/SourceCompiler.java create mode 100644 h2/src/main/org/h2/util/StringUtils.java create mode 100644 h2/src/main/org/h2/util/Task.java create mode 100644 h2/src/main/org/h2/util/TempFileDeleter.java create mode 100644 h2/src/main/org/h2/util/ThreadDeadlockDetector.java create mode 100644 h2/src/main/org/h2/util/TimeZoneProvider.java create mode 100644 h2/src/main/org/h2/util/Tool.java create mode 100644 h2/src/main/org/h2/util/Utils.java create mode 100644 h2/src/main/org/h2/util/Utils10.java create mode 100644 h2/src/main/org/h2/util/geometry/EWKBUtils.java create mode 100644 h2/src/main/org/h2/util/geometry/EWKTUtils.java create mode 100644 h2/src/main/org/h2/util/geometry/GeoJsonUtils.java create mode 100644 h2/src/main/org/h2/util/geometry/GeometryUtils.java create mode 100644 h2/src/main/org/h2/util/geometry/JTSUtils.java create mode 100644 h2/src/main/org/h2/util/geometry/package.html create mode 100644 h2/src/main/org/h2/util/json/JSONArray.java create mode 100644 h2/src/main/org/h2/util/json/JSONBoolean.java create mode 100644 h2/src/main/org/h2/util/json/JSONByteArrayTarget.java create mode 100644 h2/src/main/org/h2/util/json/JSONBytesSource.java create mode 100644 h2/src/main/org/h2/util/json/JSONItemType.java create mode 100644 h2/src/main/org/h2/util/json/JSONNull.java create mode 100644 h2/src/main/org/h2/util/json/JSONNumber.java create mode 100644 h2/src/main/org/h2/util/json/JSONObject.java create mode 100644 h2/src/main/org/h2/util/json/JSONString.java create mode 100644 h2/src/main/org/h2/util/json/JSONStringSource.java create mode 100644 h2/src/main/org/h2/util/json/JSONStringTarget.java create mode 100644 h2/src/main/org/h2/util/json/JSONTarget.java create mode 100644 h2/src/main/org/h2/util/json/JSONTextSource.java create mode 100644 h2/src/main/org/h2/util/json/JSONValidationTarget.java create mode 100644 h2/src/main/org/h2/util/json/JSONValidationTargetWithUniqueKeys.java create mode 100644 h2/src/main/org/h2/util/json/JSONValidationTargetWithoutUniqueKeys.java create mode 100644 h2/src/main/org/h2/util/json/JSONValue.java create mode 100644 h2/src/main/org/h2/util/json/JSONValueTarget.java create mode 100644 h2/src/main/org/h2/util/json/JsonConstructorUtils.java create mode 100644 h2/src/main/org/h2/util/json/package.html create mode 100644 h2/src/main/org/h2/util/package.html create mode 100644 h2/src/main/org/h2/value/CaseInsensitiveConcurrentMap.java create mode 100644 h2/src/main/org/h2/value/CaseInsensitiveMap.java create mode 100644 h2/src/main/org/h2/value/CharsetCollator.java create mode 100644 h2/src/main/org/h2/value/CompareMode.java create mode 100644 h2/src/main/org/h2/value/CompareModeDefault.java create mode 100644 h2/src/main/org/h2/value/CompareModeIcu4J.java create mode 100644 h2/src/main/org/h2/value/DataType.java create mode 100644 h2/src/main/org/h2/value/ExtTypeInfo.java create mode 100644 h2/src/main/org/h2/value/ExtTypeInfoEnum.java create mode 100644 h2/src/main/org/h2/value/ExtTypeInfoGeometry.java create mode 100644 h2/src/main/org/h2/value/ExtTypeInfoNumeric.java create mode 100644 h2/src/main/org/h2/value/ExtTypeInfoRow.java create mode 100644 h2/src/main/org/h2/value/Transfer.java create mode 100644 h2/src/main/org/h2/value/TypeInfo.java create mode 100644 h2/src/main/org/h2/value/Typed.java create mode 100644 h2/src/main/org/h2/value/Value.java create mode 100644 h2/src/main/org/h2/value/ValueArray.java create mode 100644 h2/src/main/org/h2/value/ValueBigDecimalBase.java create mode 100644 h2/src/main/org/h2/value/ValueBigint.java create mode 100644 h2/src/main/org/h2/value/ValueBinary.java create mode 100644 h2/src/main/org/h2/value/ValueBlob.java create mode 100644 h2/src/main/org/h2/value/ValueBoolean.java create mode 100644 h2/src/main/org/h2/value/ValueBytesBase.java create mode 100644 h2/src/main/org/h2/value/ValueChar.java create mode 100644 h2/src/main/org/h2/value/ValueClob.java create mode 100644 h2/src/main/org/h2/value/ValueCollectionBase.java create mode 100644 h2/src/main/org/h2/value/ValueDate.java create mode 100644 h2/src/main/org/h2/value/ValueDecfloat.java create mode 100644 h2/src/main/org/h2/value/ValueDouble.java create mode 100644 h2/src/main/org/h2/value/ValueEnum.java create mode 100644 h2/src/main/org/h2/value/ValueEnumBase.java create mode 100644 h2/src/main/org/h2/value/ValueGeometry.java create mode 100644 h2/src/main/org/h2/value/ValueInteger.java create mode 100644 h2/src/main/org/h2/value/ValueInterval.java create mode 100644 h2/src/main/org/h2/value/ValueJavaObject.java create mode 100644 h2/src/main/org/h2/value/ValueJson.java create mode 100644 h2/src/main/org/h2/value/ValueLob.java create mode 100644 h2/src/main/org/h2/value/ValueNull.java create mode 100644 h2/src/main/org/h2/value/ValueNumeric.java create mode 100644 h2/src/main/org/h2/value/ValueReal.java create mode 100644 h2/src/main/org/h2/value/ValueRow.java create mode 100644 h2/src/main/org/h2/value/ValueSmallint.java create mode 100644 h2/src/main/org/h2/value/ValueStringBase.java create mode 100644 h2/src/main/org/h2/value/ValueTime.java create mode 100644 h2/src/main/org/h2/value/ValueTimeTimeZone.java create mode 100644 h2/src/main/org/h2/value/ValueTimestamp.java create mode 100644 h2/src/main/org/h2/value/ValueTimestampTimeZone.java create mode 100644 h2/src/main/org/h2/value/ValueTinyint.java create mode 100644 h2/src/main/org/h2/value/ValueToObjectConverter.java create mode 100644 h2/src/main/org/h2/value/ValueToObjectConverter2.java create mode 100644 h2/src/main/org/h2/value/ValueUuid.java create mode 100644 h2/src/main/org/h2/value/ValueVarbinary.java create mode 100644 h2/src/main/org/h2/value/ValueVarchar.java create mode 100644 h2/src/main/org/h2/value/ValueVarcharIgnoreCase.java create mode 100644 h2/src/main/org/h2/value/VersionedValue.java create mode 100644 h2/src/main/org/h2/value/lob/LobData.java create mode 100644 h2/src/main/org/h2/value/lob/LobDataDatabase.java create mode 100644 h2/src/main/org/h2/value/lob/LobDataFetchOnDemand.java create mode 100644 h2/src/main/org/h2/value/lob/LobDataFile.java create mode 100644 h2/src/main/org/h2/value/lob/LobDataInMemory.java create mode 100644 h2/src/main/org/h2/value/lob/package.html create mode 100644 h2/src/main/org/h2/value/package.html create mode 100644 h2/src/test/META-INF/services/javax.annotation.processing.Processor create mode 100644 h2/src/test/org/h2/samples/CachedPreparedStatements.java create mode 100644 h2/src/test/org/h2/samples/Compact.java create mode 100644 h2/src/test/org/h2/samples/CreateScriptFile.java create mode 100644 h2/src/test/org/h2/samples/CsvSample.java create mode 100644 h2/src/test/org/h2/samples/DirectInsert.java create mode 100644 h2/src/test/org/h2/samples/FileFunctions.java create mode 100644 h2/src/test/org/h2/samples/Function.java create mode 100644 h2/src/test/org/h2/samples/FunctionMultiReturn.java create mode 100644 h2/src/test/org/h2/samples/HelloWorld.java create mode 100644 h2/src/test/org/h2/samples/InitDatabaseFromJar.java create mode 100644 h2/src/test/org/h2/samples/MixedMode.java create mode 100644 h2/src/test/org/h2/samples/Newsfeed.java create mode 100644 h2/src/test/org/h2/samples/ReadOnlyDatabaseInZip.java create mode 100644 h2/src/test/org/h2/samples/RowAccessRights.java create mode 100644 h2/src/test/org/h2/samples/SQLInjection.java create mode 100644 h2/src/test/org/h2/samples/SecurePassword.java create mode 100644 h2/src/test/org/h2/samples/ShowProgress.java create mode 100644 h2/src/test/org/h2/samples/ShutdownServer.java create mode 100644 h2/src/test/org/h2/samples/TriggerPassData.java create mode 100644 h2/src/test/org/h2/samples/TriggerSample.java create mode 100644 h2/src/test/org/h2/samples/UpdatableView.java create mode 100644 h2/src/test/org/h2/samples/fullTextSearch.sql create mode 100644 h2/src/test/org/h2/samples/newsfeed.sql create mode 100644 h2/src/test/org/h2/samples/optimizations.sql create mode 100644 h2/src/test/org/h2/samples/package.html create mode 100644 h2/src/test/org/h2/test/TestAll.java create mode 100644 h2/src/test/org/h2/test/TestAllJunit.java create mode 100644 h2/src/test/org/h2/test/TestBase.java create mode 100644 h2/src/test/org/h2/test/TestDb.java create mode 100644 h2/src/test/org/h2/test/ap/TestAnnotationProcessor.java create mode 100644 h2/src/test/org/h2/test/ap/package.html create mode 100644 h2/src/test/org/h2/test/auth/MyLoginModule.java create mode 100644 h2/src/test/org/h2/test/auth/TestAuthentication.java create mode 100644 h2/src/test/org/h2/test/auth/package.html create mode 100644 h2/src/test/org/h2/test/bench/Bench.java create mode 100644 h2/src/test/org/h2/test/bench/BenchA.java create mode 100644 h2/src/test/org/h2/test/bench/BenchB.java create mode 100644 h2/src/test/org/h2/test/bench/BenchC.java create mode 100644 h2/src/test/org/h2/test/bench/BenchCRandom.java create mode 100644 h2/src/test/org/h2/test/bench/BenchCThread.java create mode 100644 h2/src/test/org/h2/test/bench/BenchSimple.java create mode 100644 h2/src/test/org/h2/test/bench/Database.java create mode 100644 h2/src/test/org/h2/test/bench/TestPerformance.java create mode 100644 h2/src/test/org/h2/test/bench/TestScalability.java create mode 100644 h2/src/test/org/h2/test/bench/package.html create mode 100644 h2/src/test/org/h2/test/bench/test.properties create mode 100644 h2/src/test/org/h2/test/coverage/Coverage.java create mode 100644 h2/src/test/org/h2/test/coverage/Profile.java create mode 100644 h2/src/test/org/h2/test/coverage/Tokenizer.java create mode 100644 h2/src/test/org/h2/test/coverage/package.html create mode 100644 h2/src/test/org/h2/test/db/AbstractBaseForCommonTableExpressions.java create mode 100644 h2/src/test/org/h2/test/db/Db.java create mode 100644 h2/src/test/org/h2/test/db/TaskDef.java create mode 100644 h2/src/test/org/h2/test/db/TaskProcess.java create mode 100644 h2/src/test/org/h2/test/db/TestAlter.java create mode 100644 h2/src/test/org/h2/test/db/TestAlterSchemaRename.java create mode 100644 h2/src/test/org/h2/test/db/TestAlterTableNotFound.java create mode 100644 h2/src/test/org/h2/test/db/TestAnalyzeTableTx.java create mode 100644 h2/src/test/org/h2/test/db/TestAutoRecompile.java create mode 100644 h2/src/test/org/h2/test/db/TestBackup.java create mode 100644 h2/src/test/org/h2/test/db/TestBigDb.java create mode 100644 h2/src/test/org/h2/test/db/TestBigResult.java create mode 100644 h2/src/test/org/h2/test/db/TestCases.java create mode 100644 h2/src/test/org/h2/test/db/TestCheckpoint.java create mode 100644 h2/src/test/org/h2/test/db/TestCluster.java create mode 100644 h2/src/test/org/h2/test/db/TestCompatibility.java create mode 100644 h2/src/test/org/h2/test/db/TestCompatibilityOracle.java create mode 100644 h2/src/test/org/h2/test/db/TestCompatibilitySQLServer.java create mode 100644 h2/src/test/org/h2/test/db/TestCsv.java create mode 100644 h2/src/test/org/h2/test/db/TestDateStorage.java create mode 100644 h2/src/test/org/h2/test/db/TestDeadlock.java create mode 100644 h2/src/test/org/h2/test/db/TestDuplicateKeyUpdate.java create mode 100644 h2/src/test/org/h2/test/db/TestEncryptedDb.java create mode 100644 h2/src/test/org/h2/test/db/TestExclusive.java create mode 100644 h2/src/test/org/h2/test/db/TestFullText.java create mode 100644 h2/src/test/org/h2/test/db/TestFunctionOverload.java create mode 100644 h2/src/test/org/h2/test/db/TestFunctions.java create mode 100644 h2/src/test/org/h2/test/db/TestGeneralCommonTableQueries.java create mode 100644 h2/src/test/org/h2/test/db/TestIgnoreCatalogs.java create mode 100644 h2/src/test/org/h2/test/db/TestIndex.java create mode 100644 h2/src/test/org/h2/test/db/TestIndexHints.java create mode 100644 h2/src/test/org/h2/test/db/TestLIRSMemoryConsumption.java create mode 100644 h2/src/test/org/h2/test/db/TestLargeBlob.java create mode 100644 h2/src/test/org/h2/test/db/TestLinkedTable.java create mode 100644 h2/src/test/org/h2/test/db/TestListener.java create mode 100644 h2/src/test/org/h2/test/db/TestLob.java create mode 100644 h2/src/test/org/h2/test/db/TestLobObject.java create mode 100644 h2/src/test/org/h2/test/db/TestMemoryUsage.java create mode 100644 h2/src/test/org/h2/test/db/TestMergeUsing.java create mode 100644 h2/src/test/org/h2/test/db/TestMultiConn.java create mode 100644 h2/src/test/org/h2/test/db/TestMultiDimension.java create mode 100644 h2/src/test/org/h2/test/db/TestMultiThread.java create mode 100644 h2/src/test/org/h2/test/db/TestMultiThreadedKernel.java create mode 100644 h2/src/test/org/h2/test/db/TestOpenClose.java create mode 100644 h2/src/test/org/h2/test/db/TestOptimizations.java create mode 100644 h2/src/test/org/h2/test/db/TestOutOfMemory.java create mode 100644 h2/src/test/org/h2/test/db/TestPersistentCommonTableExpressions.java create mode 100644 h2/src/test/org/h2/test/db/TestPowerOff.java create mode 100644 h2/src/test/org/h2/test/db/TestQueryCache.java create mode 100644 h2/src/test/org/h2/test/db/TestReadOnly.java create mode 100644 h2/src/test/org/h2/test/db/TestRecursiveQueries.java create mode 100644 h2/src/test/org/h2/test/db/TestRights.java create mode 100644 h2/src/test/org/h2/test/db/TestRunscript.java create mode 100644 h2/src/test/org/h2/test/db/TestSQLInjection.java create mode 100644 h2/src/test/org/h2/test/db/TestSelectTableNotFound.java create mode 100644 h2/src/test/org/h2/test/db/TestSequence.java create mode 100644 h2/src/test/org/h2/test/db/TestSessionsLocks.java create mode 100644 h2/src/test/org/h2/test/db/TestSetCollation.java create mode 100644 h2/src/test/org/h2/test/db/TestSpaceReuse.java create mode 100644 h2/src/test/org/h2/test/db/TestSpatial.java create mode 100644 h2/src/test/org/h2/test/db/TestSpeed.java create mode 100644 h2/src/test/org/h2/test/db/TestSubqueryPerformanceOnLazyExecutionMode.java create mode 100644 h2/src/test/org/h2/test/db/TestSynonymForTable.java create mode 100644 h2/src/test/org/h2/test/db/TestTableEngines.java create mode 100644 h2/src/test/org/h2/test/db/TestTempTables.java create mode 100644 h2/src/test/org/h2/test/db/TestTransaction.java create mode 100644 h2/src/test/org/h2/test/db/TestTriggersConstraints.java create mode 100644 h2/src/test/org/h2/test/db/TestTwoPhaseCommit.java create mode 100644 h2/src/test/org/h2/test/db/TestView.java create mode 100644 h2/src/test/org/h2/test/db/TestViewAlterTable.java create mode 100644 h2/src/test/org/h2/test/db/TestViewDropView.java create mode 100644 h2/src/test/org/h2/test/db/package.html create mode 100644 h2/src/test/org/h2/test/jdbc/TestBatchUpdates.java create mode 100644 h2/src/test/org/h2/test/jdbc/TestCallableStatement.java create mode 100644 h2/src/test/org/h2/test/jdbc/TestCancel.java create mode 100644 h2/src/test/org/h2/test/jdbc/TestConcurrentConnectionUsage.java create mode 100644 h2/src/test/org/h2/test/jdbc/TestConnection.java create mode 100644 h2/src/test/org/h2/test/jdbc/TestDatabaseEventListener.java create mode 100644 h2/src/test/org/h2/test/jdbc/TestDriver.java create mode 100644 h2/src/test/org/h2/test/jdbc/TestGetGeneratedKeys.java create mode 100644 h2/src/test/org/h2/test/jdbc/TestJavaObjectSerializer.java create mode 100644 h2/src/test/org/h2/test/jdbc/TestLobApi.java create mode 100644 h2/src/test/org/h2/test/jdbc/TestManyJdbcObjects.java create mode 100644 h2/src/test/org/h2/test/jdbc/TestMetaData.java create mode 100644 h2/src/test/org/h2/test/jdbc/TestNativeSQL.java create mode 100644 h2/src/test/org/h2/test/jdbc/TestPreparedStatement.java create mode 100644 h2/src/test/org/h2/test/jdbc/TestResultSet.java create mode 100644 h2/src/test/org/h2/test/jdbc/TestSQLXML.java create mode 100644 h2/src/test/org/h2/test/jdbc/TestStatement.java create mode 100644 h2/src/test/org/h2/test/jdbc/TestTransactionIsolation.java create mode 100644 h2/src/test/org/h2/test/jdbc/TestUpdatableResultSet.java create mode 100644 h2/src/test/org/h2/test/jdbc/TestUrlJavaObjectSerializer.java create mode 100644 h2/src/test/org/h2/test/jdbc/TestZloty.java create mode 100644 h2/src/test/org/h2/test/jdbc/package.html create mode 100644 h2/src/test/org/h2/test/jdbcx/SimpleXid.java create mode 100644 h2/src/test/org/h2/test/jdbcx/TestConnectionPool.java create mode 100644 h2/src/test/org/h2/test/jdbcx/TestDataSource.java create mode 100644 h2/src/test/org/h2/test/jdbcx/TestXA.java create mode 100644 h2/src/test/org/h2/test/jdbcx/TestXASimple.java create mode 100644 h2/src/test/org/h2/test/jdbcx/package.html create mode 100644 h2/src/test/org/h2/test/mvcc/TestMvcc1.java create mode 100644 h2/src/test/org/h2/test/mvcc/TestMvcc2.java create mode 100644 h2/src/test/org/h2/test/mvcc/TestMvcc3.java create mode 100644 h2/src/test/org/h2/test/mvcc/TestMvcc4.java create mode 100644 h2/src/test/org/h2/test/mvcc/TestMvccMultiThreaded.java create mode 100644 h2/src/test/org/h2/test/mvcc/TestMvccMultiThreaded2.java create mode 100644 h2/src/test/org/h2/test/mvcc/package.html create mode 100644 h2/src/test/org/h2/test/otherDatabases.txt create mode 100644 h2/src/test/org/h2/test/package.html create mode 100644 h2/src/test/org/h2/test/poweroff/Listener.java create mode 100644 h2/src/test/org/h2/test/poweroff/Test.java create mode 100644 h2/src/test/org/h2/test/poweroff/TestRecover.java create mode 100644 h2/src/test/org/h2/test/poweroff/TestRecoverKillLoop.java create mode 100644 h2/src/test/org/h2/test/poweroff/TestReorderWrites.java create mode 100644 h2/src/test/org/h2/test/poweroff/TestWrite.java create mode 100644 h2/src/test/org/h2/test/poweroff/package.html create mode 100644 h2/src/test/org/h2/test/recover/RecoverLobTest.java create mode 100644 h2/src/test/org/h2/test/recover/package.html create mode 100644 h2/src/test/org/h2/test/rowlock/TestRowLocks.java create mode 100644 h2/src/test/org/h2/test/rowlock/package.html create mode 100644 h2/src/test/org/h2/test/scripts/Aggregate1.java create mode 100644 h2/src/test/org/h2/test/scripts/TestScript.java create mode 100644 h2/src/test/org/h2/test/scripts/Trigger1.java create mode 100644 h2/src/test/org/h2/test/scripts/Trigger2.java create mode 100644 h2/src/test/org/h2/test/scripts/altertable-fk.sql create mode 100644 h2/src/test/org/h2/test/scripts/altertable-index-reuse.sql create mode 100644 h2/src/test/org/h2/test/scripts/compatibility/add_months.sql create mode 100644 h2/src/test/org/h2/test/scripts/compatibility/compatibility.sql create mode 100644 h2/src/test/org/h2/test/scripts/compatibility/group_by.sql create mode 100644 h2/src/test/org/h2/test/scripts/compatibility/strict_and_legacy.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/array.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/bigint.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/binary.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/blob.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/boolean.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/char.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/clob.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/date.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/decfloat.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/double_precision.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/enum.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/geometry.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/identity.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/int.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/interval.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/java_object.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/json.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/numeric.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/real.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/row.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/smallint.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/time-with-time-zone.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/time.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/timestamp-with-time-zone.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/timestamp.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/tinyint.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/uuid.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/varbinary.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/varchar-ignorecase.sql create mode 100644 h2/src/test/org/h2/test/scripts/datatypes/varchar.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/alterDomain.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/alterTableAdd.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/alterTableAlterColumn.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/alterTableDropColumn.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/alterTableDropConstraint.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/alterTableRename.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/alterTableRenameConstraint.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/analyze.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/commentOn.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/createAlias.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/createConstant.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/createDomain.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/createIndex.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/createSchema.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/createSequence.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/createSynonym.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/createTable.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/createTrigger.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/createView.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/dropAllObjects.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/dropDomain.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/dropIndex.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/dropSchema.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/dropTable.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/grant.sql create mode 100644 h2/src/test/org/h2/test/scripts/ddl/truncateTable.sql create mode 100644 h2/src/test/org/h2/test/scripts/default-and-on_update.sql create mode 100644 h2/src/test/org/h2/test/scripts/dml/delete.sql create mode 100644 h2/src/test/org/h2/test/scripts/dml/error_reporting.sql create mode 100644 h2/src/test/org/h2/test/scripts/dml/execute_immediate.sql create mode 100644 h2/src/test/org/h2/test/scripts/dml/insert.sql create mode 100644 h2/src/test/org/h2/test/scripts/dml/insertIgnore.sql create mode 100644 h2/src/test/org/h2/test/scripts/dml/merge.sql create mode 100644 h2/src/test/org/h2/test/scripts/dml/mergeUsing.sql create mode 100644 h2/src/test/org/h2/test/scripts/dml/replace.sql create mode 100644 h2/src/test/org/h2/test/scripts/dml/script.sql create mode 100644 h2/src/test/org/h2/test/scripts/dml/show.sql create mode 100644 h2/src/test/org/h2/test/scripts/dml/update.sql create mode 100644 h2/src/test/org/h2/test/scripts/dml/with.sql create mode 100644 h2/src/test/org/h2/test/scripts/dual.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/any.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/array_agg.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/avg.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/bit_and_agg.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/bit_or_agg.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/bit_xor_agg.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/corr.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/count.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/covar_pop.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/covar_samp.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/envelope.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/every.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/histogram.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/json_arrayagg.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/json_objectagg.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/listagg.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/max.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/min.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/mode.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/percentile.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/rank.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/regr_avgx.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/regr_avgy.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/regr_count.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/regr_intercept.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/regr_r2.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/regr_slope.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/regr_sxx.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/regr_sxy.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/regr_syy.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/stddev_pop.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/stddev_samp.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/sum.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/var_pop.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/aggregate/var_samp.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/json/json_array.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/json/json_object.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/abs.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/acos.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/asin.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/atan.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/atan2.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/bitand.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/bitcount.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/bitget.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/bitnot.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/bitor.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/bitxor.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/ceil.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/compress.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/cos.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/cosh.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/cot.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/decrypt.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/degrees.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/encrypt.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/exp.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/expand.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/floor.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/hash.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/length.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/log.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/lshift.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/mod.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/ora-hash.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/pi.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/power.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/radians.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/rand.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/random-uuid.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/rotate.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/round.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/roundmagic.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/rshift.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/secure-rand.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/sign.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/sin.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/sinh.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/sqrt.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/tan.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/tanh.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/truncate.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/numeric/zero.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/array-to-string.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/ascii.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/bit-length.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/char.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/concat-ws.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/concat.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/difference.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/hextoraw.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/insert.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/left.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/length.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/locate.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/lower.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/lpad.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/ltrim.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/octet-length.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/quote_ident.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/rawtohex.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/regex-replace.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/regexp-like.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/regexp-substr.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/repeat.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/replace.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/right.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/rpad.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/rtrim.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/soundex.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/space.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/stringdecode.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/stringencode.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/stringtoutf8.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/substring.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/to-char.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/translate.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/trim.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/upper.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/utf8tostring.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/xmlattr.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/xmlcdata.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/xmlcomment.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/xmlnode.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/xmlstartdoc.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/string/xmltext.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/array-cat.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/array-contains.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/array-get.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/array-slice.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/autocommit.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/cancel-session.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/cardinality.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/casewhen.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/cast.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/coalesce.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/convert.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/csvread.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/csvwrite.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/current_catalog.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/current_schema.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/current_user.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/currval.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/data_type_sql.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/database-path.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/db_object.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/decode.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/disk-space-used.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/file-read.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/file-write.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/greatest.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/h2version.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/identity.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/ifnull.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/last-insert-id.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/least.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/link-schema.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/lock-mode.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/lock-timeout.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/memory-free.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/memory-used.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/nextval.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/nullif.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/nvl2.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/readonly.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/rownum.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/session-id.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/table.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/transaction-id.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/trim_array.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/truncate-value.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/system/unnest.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/timeanddate/current-time.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/timeanddate/current_date.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/timeanddate/current_timestamp.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/timeanddate/date_trunc.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/timeanddate/dateadd.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/timeanddate/datediff.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/timeanddate/day-of-month.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/timeanddate/day-of-week.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/timeanddate/day-of-year.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/timeanddate/dayname.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/timeanddate/extract.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/timeanddate/formatdatetime.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/timeanddate/hour.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/timeanddate/minute.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/timeanddate/month.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/timeanddate/monthname.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/timeanddate/parsedatetime.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/timeanddate/quarter.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/timeanddate/second.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/timeanddate/truncate.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/timeanddate/week.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/timeanddate/year.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/window/lead.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/window/nth_value.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/window/ntile.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/window/ratio_to_report.sql create mode 100644 h2/src/test/org/h2/test/scripts/functions/window/row_number.sql create mode 100644 h2/src/test/org/h2/test/scripts/indexes.sql create mode 100644 h2/src/test/org/h2/test/scripts/information_schema.sql create mode 100644 h2/src/test/org/h2/test/scripts/other/at-time-zone.sql create mode 100644 h2/src/test/org/h2/test/scripts/other/boolean-test.sql create mode 100644 h2/src/test/org/h2/test/scripts/other/case.sql create mode 100644 h2/src/test/org/h2/test/scripts/other/concatenation.sql create mode 100644 h2/src/test/org/h2/test/scripts/other/conditions.sql create mode 100644 h2/src/test/org/h2/test/scripts/other/data-change-delta-table.sql create mode 100644 h2/src/test/org/h2/test/scripts/other/field-reference.sql create mode 100644 h2/src/test/org/h2/test/scripts/other/help.sql create mode 100644 h2/src/test/org/h2/test/scripts/other/sequence.sql create mode 100644 h2/src/test/org/h2/test/scripts/other/set.sql create mode 100644 h2/src/test/org/h2/test/scripts/other/two_phase_commit.sql create mode 100644 h2/src/test/org/h2/test/scripts/other/unique_include.sql create mode 100644 h2/src/test/org/h2/test/scripts/package.html create mode 100644 h2/src/test/org/h2/test/scripts/parser/comments.sql create mode 100644 h2/src/test/org/h2/test/scripts/parser/identifiers.sql create mode 100644 h2/src/test/org/h2/test/scripts/predicates/between.sql create mode 100644 h2/src/test/org/h2/test/scripts/predicates/distinct.sql create mode 100644 h2/src/test/org/h2/test/scripts/predicates/in.sql create mode 100644 h2/src/test/org/h2/test/scripts/predicates/like.sql create mode 100644 h2/src/test/org/h2/test/scripts/predicates/null.sql create mode 100644 h2/src/test/org/h2/test/scripts/predicates/type.sql create mode 100644 h2/src/test/org/h2/test/scripts/predicates/unique.sql create mode 100644 h2/src/test/org/h2/test/scripts/queries/derived-column-names.sql create mode 100644 h2/src/test/org/h2/test/scripts/queries/distinct.sql create mode 100644 h2/src/test/org/h2/test/scripts/queries/joins.sql create mode 100644 h2/src/test/org/h2/test/scripts/queries/query-optimisations.sql create mode 100644 h2/src/test/org/h2/test/scripts/queries/select.sql create mode 100644 h2/src/test/org/h2/test/scripts/queries/table.sql create mode 100644 h2/src/test/org/h2/test/scripts/queries/values.sql create mode 100644 h2/src/test/org/h2/test/scripts/queries/window.sql create mode 100644 h2/src/test/org/h2/test/scripts/range_table.sql create mode 100644 h2/src/test/org/h2/test/scripts/testScript.sql create mode 100644 h2/src/test/org/h2/test/scripts/testSimple.sql create mode 100644 h2/src/test/org/h2/test/server/TestAutoServer.java create mode 100644 h2/src/test/org/h2/test/server/TestInit.java create mode 100644 h2/src/test/org/h2/test/server/TestJakartaWeb.java create mode 100644 h2/src/test/org/h2/test/server/TestNestedLoop.java create mode 100644 h2/src/test/org/h2/test/server/TestWeb.java create mode 100644 h2/src/test/org/h2/test/server/WebClient.java create mode 100644 h2/src/test/org/h2/test/server/package.html create mode 100644 h2/src/test/org/h2/test/store/CalculateHashConstant.java create mode 100644 h2/src/test/org/h2/test/store/CalculateHashConstantLong.java create mode 100644 h2/src/test/org/h2/test/store/FreeSpaceList.java create mode 100644 h2/src/test/org/h2/test/store/FreeSpaceTree.java create mode 100644 h2/src/test/org/h2/test/store/RowDataType.java create mode 100644 h2/src/test/org/h2/test/store/SequenceMap.java create mode 100644 h2/src/test/org/h2/test/store/TestBenchmark.java create mode 100644 h2/src/test/org/h2/test/store/TestCacheConcurrentLIRS.java create mode 100644 h2/src/test/org/h2/test/store/TestCacheLIRS.java create mode 100644 h2/src/test/org/h2/test/store/TestCacheLongKeyLIRS.java create mode 100644 h2/src/test/org/h2/test/store/TestDataUtils.java create mode 100644 h2/src/test/org/h2/test/store/TestDefrag.java create mode 100644 h2/src/test/org/h2/test/store/TestFreeSpace.java create mode 100644 h2/src/test/org/h2/test/store/TestImmutableArray.java create mode 100644 h2/src/test/org/h2/test/store/TestKillProcessWhileWriting.java create mode 100644 h2/src/test/org/h2/test/store/TestMVRTree.java create mode 100644 h2/src/test/org/h2/test/store/TestMVStore.java create mode 100644 h2/src/test/org/h2/test/store/TestMVStoreBenchmark.java create mode 100644 h2/src/test/org/h2/test/store/TestMVStoreCachePerformance.java create mode 100644 h2/src/test/org/h2/test/store/TestMVStoreConcurrent.java create mode 100644 h2/src/test/org/h2/test/store/TestMVStoreStopCompact.java create mode 100644 h2/src/test/org/h2/test/store/TestMVStoreTool.java create mode 100644 h2/src/test/org/h2/test/store/TestMVTableEngine.java create mode 100644 h2/src/test/org/h2/test/store/TestObjectDataType.java create mode 100644 h2/src/test/org/h2/test/store/TestRandomMapOps.java create mode 100644 h2/src/test/org/h2/test/store/TestShardedMap.java create mode 100644 h2/src/test/org/h2/test/store/TestSpinLock.java create mode 100644 h2/src/test/org/h2/test/store/TestStreamStore.java create mode 100644 h2/src/test/org/h2/test/store/TestTransactionStore.java create mode 100644 h2/src/test/org/h2/test/store/package.html create mode 100644 h2/src/test/org/h2/test/synth/BnfRandom.java create mode 100644 h2/src/test/org/h2/test/synth/OutputCatcher.java create mode 100644 h2/src/test/org/h2/test/synth/TestBtreeIndex.java create mode 100644 h2/src/test/org/h2/test/synth/TestConcurrentUpdate.java create mode 100644 h2/src/test/org/h2/test/synth/TestCrashAPI.java create mode 100644 h2/src/test/org/h2/test/synth/TestDiskFull.java create mode 100644 h2/src/test/org/h2/test/synth/TestFuzzOptimizations.java create mode 100644 h2/src/test/org/h2/test/synth/TestHalt.java create mode 100644 h2/src/test/org/h2/test/synth/TestHaltApp.java create mode 100644 h2/src/test/org/h2/test/synth/TestJoin.java create mode 100644 h2/src/test/org/h2/test/synth/TestKill.java create mode 100644 h2/src/test/org/h2/test/synth/TestKillProcess.java create mode 100644 h2/src/test/org/h2/test/synth/TestKillRestart.java create mode 100644 h2/src/test/org/h2/test/synth/TestKillRestartMulti.java create mode 100644 h2/src/test/org/h2/test/synth/TestLimit.java create mode 100644 h2/src/test/org/h2/test/synth/TestMultiThreaded.java create mode 100644 h2/src/test/org/h2/test/synth/TestNestedJoins.java create mode 100644 h2/src/test/org/h2/test/synth/TestOuterJoins.java create mode 100644 h2/src/test/org/h2/test/synth/TestPowerOffFs.java create mode 100644 h2/src/test/org/h2/test/synth/TestPowerOffFs2.java create mode 100644 h2/src/test/org/h2/test/synth/TestRandomCompare.java create mode 100644 h2/src/test/org/h2/test/synth/TestRandomSQL.java create mode 100644 h2/src/test/org/h2/test/synth/TestReleaseSelectLock.java create mode 100644 h2/src/test/org/h2/test/synth/TestSimpleIndex.java create mode 100644 h2/src/test/org/h2/test/synth/TestThreads.java create mode 100644 h2/src/test/org/h2/test/synth/TestTimer.java create mode 100644 h2/src/test/org/h2/test/synth/package.html create mode 100644 h2/src/test/org/h2/test/synth/sql/Column.java create mode 100644 h2/src/test/org/h2/test/synth/sql/Command.java create mode 100644 h2/src/test/org/h2/test/synth/sql/DbConnection.java create mode 100644 h2/src/test/org/h2/test/synth/sql/DbInterface.java create mode 100644 h2/src/test/org/h2/test/synth/sql/DbState.java create mode 100644 h2/src/test/org/h2/test/synth/sql/Expression.java create mode 100644 h2/src/test/org/h2/test/synth/sql/Index.java create mode 100644 h2/src/test/org/h2/test/synth/sql/RandomGen.java create mode 100644 h2/src/test/org/h2/test/synth/sql/Result.java create mode 100644 h2/src/test/org/h2/test/synth/sql/Row.java create mode 100644 h2/src/test/org/h2/test/synth/sql/Table.java create mode 100644 h2/src/test/org/h2/test/synth/sql/TestSynth.java create mode 100644 h2/src/test/org/h2/test/synth/sql/Value.java create mode 100644 h2/src/test/org/h2/test/synth/sql/package.html create mode 100644 h2/src/test/org/h2/test/synth/thread/TestMulti.java create mode 100644 h2/src/test/org/h2/test/synth/thread/TestMultiNews.java create mode 100644 h2/src/test/org/h2/test/synth/thread/TestMultiNewsSimple.java create mode 100644 h2/src/test/org/h2/test/synth/thread/TestMultiOrder.java create mode 100644 h2/src/test/org/h2/test/synth/thread/TestMultiThread.java create mode 100644 h2/src/test/org/h2/test/synth/thread/package.html create mode 100644 h2/src/test/org/h2/test/todo/TestDiskSpaceLeak.java create mode 100644 h2/src/test/org/h2/test/todo/TestDropTableLarge.java create mode 100644 h2/src/test/org/h2/test/todo/TestLinkedTableFullCondition.java create mode 100644 h2/src/test/org/h2/test/todo/TestTempTableCrash.java create mode 100644 h2/src/test/org/h2/test/todo/TestUndoLogLarge.java create mode 100644 h2/src/test/org/h2/test/todo/columnAlias.txt create mode 100644 h2/src/test/org/h2/test/todo/dateFunctions.txt create mode 100644 h2/src/test/org/h2/test/todo/package.html create mode 100644 h2/src/test/org/h2/test/todo/recursiveQueries.txt create mode 100644 h2/src/test/org/h2/test/todo/supportTemplates.txt create mode 100644 h2/src/test/org/h2/test/todo/todo.txt create mode 100644 h2/src/test/org/h2/test/todo/tools.sql create mode 100644 h2/src/test/org/h2/test/todo/versionlist.txt create mode 100644 h2/src/test/org/h2/test/trace/Arg.java create mode 100644 h2/src/test/org/h2/test/trace/Parser.java create mode 100644 h2/src/test/org/h2/test/trace/Player.java create mode 100644 h2/src/test/org/h2/test/trace/Statement.java create mode 100644 h2/src/test/org/h2/test/trace/package.html create mode 100644 h2/src/test/org/h2/test/unit/TestAnsCompression.java create mode 100644 h2/src/test/org/h2/test/unit/TestAutoReconnect.java create mode 100644 h2/src/test/org/h2/test/unit/TestBinaryArithmeticStream.java create mode 100644 h2/src/test/org/h2/test/unit/TestBinaryOperation.java create mode 100644 h2/src/test/org/h2/test/unit/TestBitStream.java create mode 100644 h2/src/test/org/h2/test/unit/TestBnf.java create mode 100644 h2/src/test/org/h2/test/unit/TestCache.java create mode 100644 h2/src/test/org/h2/test/unit/TestCharsetCollator.java create mode 100644 h2/src/test/org/h2/test/unit/TestClassLoaderLeak.java create mode 100644 h2/src/test/org/h2/test/unit/TestCollation.java create mode 100644 h2/src/test/org/h2/test/unit/TestCompress.java create mode 100644 h2/src/test/org/h2/test/unit/TestConcurrentJdbc.java create mode 100644 h2/src/test/org/h2/test/unit/TestConnectionInfo.java create mode 100644 h2/src/test/org/h2/test/unit/TestDate.java create mode 100644 h2/src/test/org/h2/test/unit/TestDateIso8601.java create mode 100644 h2/src/test/org/h2/test/unit/TestDateTimeUtils.java create mode 100644 h2/src/test/org/h2/test/unit/TestDbException.java create mode 100644 h2/src/test/org/h2/test/unit/TestExit.java create mode 100644 h2/src/test/org/h2/test/unit/TestFile.java create mode 100644 h2/src/test/org/h2/test/unit/TestFileLock.java create mode 100644 h2/src/test/org/h2/test/unit/TestFileLockProcess.java create mode 100644 h2/src/test/org/h2/test/unit/TestFileSystem.java create mode 100644 h2/src/test/org/h2/test/unit/TestFtp.java create mode 100644 h2/src/test/org/h2/test/unit/TestGeometryUtils.java create mode 100644 h2/src/test/org/h2/test/unit/TestIntArray.java create mode 100644 h2/src/test/org/h2/test/unit/TestIntPerfectHash.java create mode 100644 h2/src/test/org/h2/test/unit/TestInterval.java create mode 100644 h2/src/test/org/h2/test/unit/TestJakartaServlet.java create mode 100644 h2/src/test/org/h2/test/unit/TestJmx.java create mode 100644 h2/src/test/org/h2/test/unit/TestJsonUtils.java create mode 100644 h2/src/test/org/h2/test/unit/TestKeywords.java create mode 100644 h2/src/test/org/h2/test/unit/TestLocale.java create mode 100644 h2/src/test/org/h2/test/unit/TestMVTempResult.java create mode 100644 h2/src/test/org/h2/test/unit/TestMathUtils.java create mode 100644 h2/src/test/org/h2/test/unit/TestMemoryEstimator.java create mode 100644 h2/src/test/org/h2/test/unit/TestMemoryUnmapper.java create mode 100644 h2/src/test/org/h2/test/unit/TestMode.java create mode 100644 h2/src/test/org/h2/test/unit/TestMultiThreadedKernel.java create mode 100644 h2/src/test/org/h2/test/unit/TestNetUtils.java create mode 100644 h2/src/test/org/h2/test/unit/TestObjectDeserialization.java create mode 100644 h2/src/test/org/h2/test/unit/TestOverflow.java create mode 100644 h2/src/test/org/h2/test/unit/TestPageStoreCoverage.java create mode 100644 h2/src/test/org/h2/test/unit/TestPattern.java create mode 100644 h2/src/test/org/h2/test/unit/TestPerfectHash.java create mode 100644 h2/src/test/org/h2/test/unit/TestPgServer.java create mode 100644 h2/src/test/org/h2/test/unit/TestReader.java create mode 100644 h2/src/test/org/h2/test/unit/TestRecovery.java create mode 100644 h2/src/test/org/h2/test/unit/TestReopen.java create mode 100644 h2/src/test/org/h2/test/unit/TestSampleApps.java create mode 100644 h2/src/test/org/h2/test/unit/TestScriptReader.java create mode 100644 h2/src/test/org/h2/test/unit/TestSecurity.java create mode 100644 h2/src/test/org/h2/test/unit/TestServlet.java create mode 100644 h2/src/test/org/h2/test/unit/TestShell.java create mode 100644 h2/src/test/org/h2/test/unit/TestSort.java create mode 100644 h2/src/test/org/h2/test/unit/TestStreams.java create mode 100644 h2/src/test/org/h2/test/unit/TestStringCache.java create mode 100644 h2/src/test/org/h2/test/unit/TestStringUtils.java create mode 100644 h2/src/test/org/h2/test/unit/TestTimeStampWithTimeZone.java create mode 100644 h2/src/test/org/h2/test/unit/TestTools.java create mode 100644 h2/src/test/org/h2/test/unit/TestTraceSystem.java create mode 100644 h2/src/test/org/h2/test/unit/TestUpgrade.java create mode 100644 h2/src/test/org/h2/test/unit/TestUtils.java create mode 100644 h2/src/test/org/h2/test/unit/TestValue.java create mode 100644 h2/src/test/org/h2/test/unit/TestValueMemory.java create mode 100644 h2/src/test/org/h2/test/unit/package.html create mode 100644 h2/src/test/org/h2/test/utils/FilePathDebug.java create mode 100644 h2/src/test/org/h2/test/utils/FilePathReorderWrites.java create mode 100644 h2/src/test/org/h2/test/utils/FilePathUnstable.java create mode 100644 h2/src/test/org/h2/test/utils/MemoryFootprint.java create mode 100644 h2/src/test/org/h2/test/utils/OutputCatcher.java create mode 100644 h2/src/test/org/h2/test/utils/RandomDataUtils.java create mode 100644 h2/src/test/org/h2/test/utils/ResultVerifier.java create mode 100644 h2/src/test/org/h2/test/utils/SelfDestructor.java create mode 100644 h2/src/test/org/h2/test/utils/package.html create mode 100644 h2/src/tools/WEB-INF/console.html create mode 100644 h2/src/tools/WEB-INF/web.xml create mode 100644 h2/src/tools/org/h2/dev/cache/CacheLIRS.java create mode 100644 h2/src/tools/org/h2/dev/cache/package.html create mode 100644 h2/src/tools/org/h2/dev/cluster/ShardedMap.java create mode 100644 h2/src/tools/org/h2/dev/cluster/package.html create mode 100644 h2/src/tools/org/h2/dev/fs/ArchiveTool.java create mode 100644 h2/src/tools/org/h2/dev/fs/ArchiveToolStore.java create mode 100644 h2/src/tools/org/h2/dev/fs/FilePathZip2.java create mode 100644 h2/src/tools/org/h2/dev/fs/FileShell.java create mode 100644 h2/src/tools/org/h2/dev/fs/package.html create mode 100644 h2/src/tools/org/h2/dev/ftp/FtpClient.java create mode 100644 h2/src/tools/org/h2/dev/ftp/package.html create mode 100644 h2/src/tools/org/h2/dev/ftp/server/FtpControl.java create mode 100644 h2/src/tools/org/h2/dev/ftp/server/FtpData.java create mode 100644 h2/src/tools/org/h2/dev/ftp/server/FtpEvent.java create mode 100644 h2/src/tools/org/h2/dev/ftp/server/FtpEventListener.java create mode 100644 h2/src/tools/org/h2/dev/ftp/server/FtpServer.java create mode 100644 h2/src/tools/org/h2/dev/ftp/server/package.html create mode 100644 h2/src/tools/org/h2/dev/hash/IntPerfectHash.java create mode 100644 h2/src/tools/org/h2/dev/hash/MinimalPerfectHash.java create mode 100644 h2/src/tools/org/h2/dev/hash/PerfectHash.java create mode 100644 h2/src/tools/org/h2/dev/hash/package.html create mode 100644 h2/src/tools/org/h2/dev/mail/SendMail.java.txt create mode 100644 h2/src/tools/org/h2/dev/net/PgTcpRedirect.java create mode 100644 h2/src/tools/org/h2/dev/net/package.html create mode 100644 h2/src/tools/org/h2/dev/security/SecureKeyStoreBuilder.java create mode 100644 h2/src/tools/org/h2/dev/security/package.html create mode 100644 h2/src/tools/org/h2/dev/sort/InPlaceStableMergeSort.java create mode 100644 h2/src/tools/org/h2/dev/sort/InPlaceStableQuicksort.java create mode 100644 h2/src/tools/org/h2/dev/sort/package.html create mode 100644 h2/src/tools/org/h2/dev/util/AnsCompression.java create mode 100644 h2/src/tools/org/h2/dev/util/ArrayUtils.java create mode 100644 h2/src/tools/org/h2/dev/util/Base64.java create mode 100644 h2/src/tools/org/h2/dev/util/BinaryArithmeticStream.java create mode 100644 h2/src/tools/org/h2/dev/util/BitStream.java create mode 100644 h2/src/tools/org/h2/dev/util/ConcurrentLinkedList.java create mode 100644 h2/src/tools/org/h2/dev/util/ConcurrentLinkedListWithTail.java create mode 100644 h2/src/tools/org/h2/dev/util/ConcurrentRing.java create mode 100644 h2/src/tools/org/h2/dev/util/FileContentHash.java create mode 100644 h2/src/tools/org/h2/dev/util/FileViewer.java create mode 100644 h2/src/tools/org/h2/dev/util/ImmutableArray.java create mode 100644 h2/src/tools/org/h2/dev/util/ImmutableArray2.java create mode 100644 h2/src/tools/org/h2/dev/util/ImmutableArray3.java create mode 100644 h2/src/tools/org/h2/dev/util/JavaProcessKiller.java create mode 100644 h2/src/tools/org/h2/dev/util/Migrate.java create mode 100644 h2/src/tools/org/h2/dev/util/ReaderInputStream.java create mode 100644 h2/src/tools/org/h2/dev/util/RemovePasswords.java create mode 100644 h2/src/tools/org/h2/dev/util/ThreadDumpCleaner.java create mode 100644 h2/src/tools/org/h2/dev/util/ThreadDumpFilter.java create mode 100644 h2/src/tools/org/h2/dev/util/ThreadDumpInliner.java create mode 100644 h2/src/tools/org/h2/dev/util/package.html create mode 100644 h2/src/tools/org/h2/jcr/Railroads.java create mode 100644 h2/src/tools/org/h2/jcr/help.csv create mode 100644 h2/src/tools/org/h2/jcr/jcr-sql2.html create mode 100644 h2/src/tools/org/h2/jcr/package.html create mode 100644 h2/src/tools/org/h2/jcr/stylesheet.css rename src/main/java/portfopol/refactoring/basic/{myData.java => MyData.java} (77%) rename src/main/java/portfopol/refactoring/basic/{myUser.java => MyUser.java} (87%) create mode 100644 src/main/java/portfopol/refactoring/basic/userRepository/UserRepository.java create mode 100644 test.mv.db create mode 100644 test.trace.db diff --git a/build.gradle b/build.gradle index 474e045..9300908 100644 --- a/build.gradle +++ b/build.gradle @@ -24,6 +24,9 @@ dependencies { compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' + + implementation 'org.springframework.boot:spring-boot-starter-jdbc' + runtimeOnly 'com.h2database:h2' } tasks.named('test') { diff --git a/h2/build.bat b/h2/build.bat new file mode 100644 index 0000000..e7fb42a --- /dev/null +++ b/h2/build.bat @@ -0,0 +1,7 @@ +@echo off +if "%JAVA_HOME%"=="" echo Error: JAVA_HOME is not defined. +if "%1"=="clean" rmdir /s /q temp bin 2>nul +if not exist temp mkdir temp +if not exist bin mkdir bin +"%JAVA_HOME%/bin/javac" -sourcepath src/tools -d bin src/tools/org/h2/build/*.java +"%JAVA_HOME%/bin/java" -Djava.net.useSystemProxies=true -Xmx256m -cp "bin;%JAVA_HOME%/lib/tools.jar;temp" org.h2.build.Build %* \ No newline at end of file diff --git a/h2/build.sh b/h2/build.sh new file mode 100644 index 0000000..769262d --- /dev/null +++ b/h2/build.sh @@ -0,0 +1,18 @@ +#!/bin/sh +if [ -z "$JAVA_HOME" ] ; then + if [[ "$OSTYPE" == "darwin"* ]]; then + if [ -d "/System/Library/Frameworks/JavaVM.framework/Home" ] ; then + export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Home + else + export JAVA_HOME=`/usr/libexec/java_home` + fi + fi +fi +if [ -z "$JAVA_HOME" ] ; then + echo "Error: JAVA_HOME is not defined." +fi +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" -Xmx1g -cp "bin:$JAVA_HOME/lib/tools.jar:temp" org.h2.build.Build $@ diff --git a/h2/docs/h2.pdf b/h2/docs/h2.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1310369ed8381fe8b9a11f40768bb4d89362b70e GIT binary patch literal 2018998 zcmb5VV{~WTw(T9;72CFL+qP}nsW=roso1t{+qU_SD#=^Ve($-b-L`jY_uOx@&Gl`p zHOKs|-uoa^6cwjqqGyL88!Q^^8f+QNgJC9QBy=#ghT-L9kTtWnaJ3|4`MaacAZ}&r zYUa!!ZfoRfCTeElU~0zC598wMY-VH!LvSmP{-0IoGraZ1)5fX|7;0;is(;2z@kvK>8|LHk>ttkCx*X zE0Y!kVBR_OERhZwcN3S40D-1lqJzxk%*7c3!IbaG!!r{~H_(4gqo}1M3ZeUAPE=GD zGply&B09ZZa!YM%FTchj2|0d1Q#f`;A@H3~p_XgW?wzP~zLBHN!<*h}u|rvarcsAt1@cphQ1f7d1wdB$EZ znHjLp-_<`=g!!j<|16l3{3pv2cXQVy5wNP)L+Xxtce6FI1cP-6$U-adkX`1szJAWw?nq@NWM|wFNTgbAl(tGj9P-kA3~w-W{6y-q?r2RCUMrP-ug$ z-actu@J{5Vw@BSFsfSKBqLW2COB|H5g6u2M^CyQ}=;u|PP#G=STFGp`Uwej(L5D^I%yW*NeI$z8_ZDdv^uhssRx_!XD;a__R zgu+Ty`ek;_7zkH5E!**DoGMT9NZUcVboI8RmP7}rL5L4XHT9{_e1pr3knUwfNXQQ| zvrEA*hQHG`Xw;p9Ob(b6f1Jg8;*^z3osdV;4zTdbiK-Ku9;FhB$ie+R8e^5!pJFCbf?Zc1$*hw9=jcEYVUDHg=S-R1sJ)-c04`8{ z7*`5os~3og$ZxwxTj$73x+m3c6#2V#V!!aOp%x)=-FP77>9Y8J`dyXZGMJ#a(wLh- zL?lq81kfxmt(e;W@IP6H_=B%!+UGE>BKY05-)CL^zTbG}My(a>3|(si*`m8-Q3f5hz5#|vxms7R~>@*XWe1pA=g=-*&`O2G>df@R8t zd{Wh`eHij}+}5kFf~z;~>`5%9HF~`v_6jX~$Q+mPB2cmNycll6#_8v#L>z;{{4RFZ z<(Q?E&^Z-98lPz;$iHq&k%HYk!B@Mcp97 z?qA$3`Gr8Z*t&l%zqq)Lx}?+AgWQ-fF%_&YZ{wu~%Co@c2F=@%6nhAh>p5w>p!@aZQd#w{oZdtK_9K8HnQF{?b{ z>z4sa1x-6iV8_FfTNKz>uM!&tm|pm-PfKMvt0x)-RA>hpzvmIk)Zwt=AS!ryNLHP$ zUH|X2#=Z&EvZ73b-J9!Fmk{MSo2=|aV6qZ6Y@f#L^)p-5mZwYrr&swqU(Y1Ya!D}0 zAWG}AAag=|r#)jhtT;NNXgs>gOVw_<0ZPf=voQrMu$k@xofK|9T;j4q!y|_#<#KJ+ zaPlOm=VvX+7Y?+2gw>b`N<39HqbhRA(((8XKnjW52~Rgl0Pduv^7yI237X5!6X1tz zeL!0dSUP=VH*H)C=3iYi*HCj@LqT|Oi3aJUluelSdWAu}v}!|}B^MRBdogMmpGUi>E1OO_Z-A6;9%&GZ^Y>rynZM*ho$8T_Hm$Xm?;AIDV#IQNn0ts9LC zgO#CC*;C&|@Av`^jaBDNb(j8F-?CjS))aCcY#t0(xWf zT;~eE=l)mP25|LR3Jlj~BaYV8D5EsVgtQJ`-6r`X2GGoQ&R%Akr&4Ui-bIhM{RUAs z!=s<<ebzI}aIVv^CNiQ|EygKZ0YE^4-20O&6?MH*x zB^#{wtUZZhbk*wJ{>)?zYld4wnXkh|Tgi^5!cu#ow_l~lCt@6mEr8q~W)orie0dm` zhyj;hkdYwyXa50;{=uDpK@l^{e^U|Xzflp_|A>lY;z=m}Qqd!gZ=7j_$6}(H6+VEd zHAQ^FC`AWf8R1t$+MiEFLC|gRCqRvb;mV5in$2RkJ%3aIJ{;HFb^zk{%~%{R)FO}A zxe@M}J?8dhngG84r`1b*rO}t)+vQ(y0H&67R_t8e^$;*vi;LR6%*%_{&0hNjMXccC zO*&{b8~0?qd-+3v*_rwF%HwI+e_{_5VOiG9si^Q%K5I6>=ks{~&-z29fJ z^e3}-uv;^58X`zSSWTz?B8*xc0woZO%eoPppdU&Tagt((ZQ|6+S)9cRCps0lwl#5w z%eue?-jQ}n((!P0BGs(~Q$BCGNhKo>AP6GNovM=LPmH!n?W_lv&oFMv5+pQ2n{`nU z;X57Ps#w(`nYV99s6T41ISMFs-+($KODwMQz9W#_2(F67S%MA2EQ*hXd_x{=1=aQG z_S}~OwJP|Q$5U!%R07v2XX0<<3d|CiHxuzjY#BCHHbmAz<28sd5bv?utyy_SY?5rm zm|C-h{rTSth`q->6V_{*Bo6g^j+e@R$2bB#w}m$#J8z%y&oj(71#aS%0d?uU zS>}O~S{9keDl_}Wi{a8fG7b%c4(|;yTL!of_ofav&o-Hhu9%t4&5Bap9yn+|YjFT6pH`u*K?vd#^ z!IBDvjBnq^g1X^7&AEWrk5w8g(YNAnO|)62J;e;gx*+}fcyqR{Z>rRcqD>>jJ{I14 ztE`?(772?y>AssdCG%8?ZdtmliqX9Uo#u_$46hc?81hNrHqj*sfWrZ1yp*sQ=Z2xx zY*pA^N;2eN0@INAm2Rya7vCi~J+E~TdUuu6grVC?f^VZ*@LTINk?HEbT{OGvi7L9s zA@FirG*1>jUu<3iHvTuCJ$!!6d|2*0x1Oj`t*lQ6%KW&R=FxT>sowNC6?QKR`5jn$ z7~uk& zwOQAVW@|q}?Qq7{VYu&?&mgwGFJZ!A^r+_HnL6?iKLkxBe(6sl?$GYi7&d@dr#9)< z#x3E&qRd`{!H8^{@8~AzyvTZ`>=222QT$Oc^^fDgPm+{w(*xxeO>@CG@l>`K0<|#w zCV~V13H+6PaZVEE6(@8s9#NEeuLYLL9E|J*t=irlxEpkHMGzIUm08ekxH?4h`=~ax z^_tew+V2MVOv}2Jc~cM`Lu#8x@#gvVS=G2v=O)G{%GFvx;fBI`NP@3O!u(lnh%Bo- zJ2zyj<62@_M&84tUuP`53gmbEB}Y2aDu14L}RB(OSDsNK2Y zkP~bpTu~YJDJoDUU)r*fjmQIk;T{5tZtDEIrR^v<$;tV zw8vGX#*|~BQo1vqjX=xGgZ5(~1QWJ*LND9TjE(f-P+kkGElbsGzZsWzzn>!(2Gz-o zLQ~#}m!=>6ZD9{NVvN|QY~ED;2waTfo|BFRh#!nf0B32qPNp_javwkH045>PpoD4G zZT~L&-&g%`9%>iJQsM935I{KK&P{eRK#Uimx}rTr>niHXGG&@~_Ewdfj}B?y24Poy zu&wWhhW9&VzayYOYEZ1r=Pjbp7xR4av*tx-(Mua~wvKokMMdOjdqe4+Wx9ZY%N#qK zSs|*NEfLvX5-$yX)?Fu$vshxpaWQL1rJ0DF3RJNxle}YLrBYX#E*YD>KvH7NIoIh{ zO+S9Mr}0h2U~fR-%z;Vo9|W>*mVmd3tJ6>u!k2+@rWCRkN{l60pz1f6x3lfY4t zJS)12ua69Zx_H+@ov>QqRhsE2Xv-@>?-SmzY+DPY<~ZGfb}uD}Q!eGR6KBW4@Elid zPCr=8!jYSOVE%&4_>2X>ZBP17q^f`{wzHG5uAf{j-iHuH7WRSx^HW{_9&jyz!Cgd~ zLXC_RX%p-!<_P26Oc4rWF#|7bGIvf|&$5~oS*7jf`(-I>Eh_DptTI6`VC3at!~&||VXPv^RTa+bcF)$kE%}#~wd}m`xscW|9i=Pfs`STx zPa5Bxui77Ei@*S?rWt!Vs?s&NbV~9wQHL(iDGLd={W|EecJj@}Ig52r$IpW}LG95T zn4{E2gyD6f*84gbj4V=f7;kLe?iTeWb5IjUf3+Hk>-G;yGsIARG%L|T>V-n_Tu}l> z>Met*w;PkIvp(4$8SWeL-`!Wv=BiKZ+i>h4YPEE8{jxno+QSh3p( zYE_u}{3M9BEDE^zbK0@{2)cSWdRD%x@(oPt*tG6 z`zLWvggWfb$#C|lIp725BQzC$vsstbHyo7@05Or4OK_IU?f< zrFFZo$;0BR+rU{CRCz*Hg>UtYU9cQ6R7d*l4g%f*GY)SAupP}=LdRhOzf-}Fr_))$ zlt$t+61ShpZ_!TPWM$Fjb)1r_#*T40ydkT$3iZVPocFy_6Ai#!1IowUAg@w~a9~Qi zN`lU^It@m^)5qKUx)t@jcWGfpzZKx1b}8BI*i@h%_Idy5sus&>Qy@A z#xK=SECsf(Z#CeyiZh(6EegHh`{k%+URb4`?Veq^6+j<8l>NPm2jC*}KPM1zN3Pon z7Tp-IEF&XEc% zQP;X&wkyNs@xGtIB`gTC>HC3hWj$ybvqKV|<4*8plupxXBmsTHG8QygsJ7B+?Rnf* zaG4bcPJQXfN~P_GT{C~QqddmtFzVx0D4vuynoVG1Z0gC{rP7Rtn$qJ>lME2yAZMzS z^WFgVOQ!xYBnlsM=#+|oSP(R(D)vWAb-01A~_R+19I>6BiiBUtMhTHC` zCQwOefE8u-+slq>ew6PQ`5@Frm*NpWFJBxs&j6GM_E@Ho%?G&Gl@&+Zg)~q1(#dPC z+fAmYG*~wZ-yznTs&j3Hi4m^Ce8&apPJaNU$v$EcLbPLI@8 zri6&)n$^St20s~I{?vqm4N29)7_TbpS!kQ_%g8uLZ*mCR3~V`j(e@tywh9Ju#=}rS zu7l6aTCuu^vHu4Ok5bkClELWh$64QdKrzuxg%qB($s9Exw zD$(-c6wU*45R*oI$$(nxVWcOI+|cBCp%U)mx&zs~{7PEqXt<@=obaQzWhYz{U4$5p zP7EA!xdWXL4wWe~1v|0KNRAYv5rNVb-k(;y)uTruePVv_s>OLzo70_JZKXG4B1f>P%%;~5dr8bR6e z&%458my%bKviOba{YfKGd_iQKo#~Ja55<9m7Ay>_d@kI?6Tlw{;>LHF9!x0Y%6iYU{imYjoJI1DKNKjtd;QpEn zMwcK{D2r7!BflzvK!W+rsyi}BmO39Si}n76iFyabvEWE#6`}0d6B?SUxG@z+!H;%G zY#{uFL~)&w$*VySLO*ui1LBfP3GU}9uy-+bV;UzYTO*3WjCODgT!yqQ<8mqE{rAz*V!4dtNWqtwd3&lb?Bj_1DvGj;u;gQmB*f`FPb_?((-!oC zJY8RfT^U8{LwDE3(lu)@buNRR@8XA znDNta?MAhYw!p8P{Ve%meL0F#gjT;3%~F`#M9w)QOEu&aO4=}&>*>9GblI3bhrRU( znwpz03xA|nk5OEsHZ=YeNUA9?mF|oIztr-}3djWjJcXk-a2+nPN#aX?k!WH>&*VPe z4ZHjnQ!154?lfp|R9&zB(zLkTym#89#)gTtB%02J6-J8~ooZ)f-LXGXeP)(>4xQ*K zp(j<{Je04?U1@#f#?s33iCCd2q|(+TX4uYOwb%vP`^*_&NkiEAuh|~FvMe0(n(u-^*3;PIO&%c`19eYZg+h-3s zkz^6+$y*MTLd=l*MOl*}`^A`wL3*DgMYiME)cZXDHf5I~ZOANR1|SWSAJmg5{mm#) z^^wFtqo^vI@(On8 z^djiI12m;iG<;%fM-F`A)^)4?z#TPqv_GU-@oeSxDEP-B6Zowz|{2+*wtM3_eT5PKxt_hQZbCZ{&1jssF%W|6;3uK^O}w^M6Aa>%T+TKhER- z5W@cP8UI4qdF3|)@N_~E~X1(<(bmx?fK*C;n)7bT$$}`XFYO3 z@sdWeJGo3&{9%4w0#c}o8D%lC{&{@Na0R!?(Z+f2wTpz|PCoDgWb6$Z?Kk9(Sdr1x zX&k`>Vmbgoxk12Xc#utbd!YQh|6?6zGWE=gccF@bOif+@;EaiHaUgnG&0D!n)7g8{ z!Ga)mV5&Nf#Dl1?h)I^z1}qirJCV}GhJZIl6$yQ_5@K93+;m@U`e}2{I%#`73TeEitxu0xQn65&vEt+o3NmNd4_n#apEyd=Qjr$S&o{B?=y=?Bx zb(Ooiu;ArYEq-ppr=Gr7YTCo0R+_Vp-l9^8YNyJlb&`+rmBa++qG=>ybefj4oZKpu zpx8kCGMN!IlU!>HJ>+w ztv#NNYLqN`Tw$j;tntfH>v^tSrZe;88*(fX#OW}!23}QiL6Bm`Q8_qHrC?3$#+uZI zvs_;fy4kni|Dqt&Qiab%01n1{TX-Kb8U1PVT}13shE6_@1_ zaeM$j0$7^`ALpm*^ai28CgdrQtp<~PaOcd{lx(nfvK2wKs)IZLeTp@ME52U{6QV~G zTb&7$OgkYQL{X{qCz&vMt8`0CFM8!}RSF^UZ3JL3zX}3YV^ZY<}8@v!agb; zC;3t*rKM3%Jzp+g`2B8syo@iWYj`8`sgMp{H-uYhYNvgkkdxWkLxDX=^$rB4JWctv z_?oJHmdPr1Qa0X?#FA!4Od2CIJtE^>V}CAXW;t@x9$>NC618?yBE*9Fl!5ZFl<$?l zoS-zPMXYqi)9iv}{==S;k^6AsC0y8~Ecl^p`VV9FAYVKEw3Ym@;~#F?H}VjC)%)}{ zoJN*QztH%NGN3B5_rO};3s_r0??3~%erZ7G$J~?a|jZ+Sq z>RHnrA`bbgcS-VtpmdN2?tbN?QsqH?#0In<;vd052GB9gnBk)hE10hGb;dLDCf_Iw zZ`a@fmknBv=HxSVzkm>>t2Msvyxh&n)@v;~|DZj(UXvt)0>eGCoLCK)WY21ftycG& zWM6iw`LSojQi)HGx4_@>=RWaaD?uu5KSU&7t+Ijyodwt6BsKK2Xjj+m#f;u{OH zYS~;KbaUANQE+Q_-t_jQ7XVmced3LJ&gxnh?3L2{kGkfE>pjrvUbsA5!_Y&vJF`Ux zy;l)q%^?@^6Qf_IekG5Q>0TU%QzP*k8`$+~??YCidB^BvzFAr5X@ZQCx-cN1_|u}< zyvR&0yQXnj+}2_(rhK;};!AZnm$l07L%YcagpX$a8G*>z0Om+p3U)l9x2Xt)UwZ{{ zCSw(HM2BcnP_}hmW^1u8rPm?MFMuNutBrAm*sUOZI0;7e!$g!vPbXQ6WKKime%`u+ zHu?~L_m|58ezy`U8h~)NTOhAiK#_pux-iOEKcN&3&CDw&$3+7;s8N>(rTHYYmrpsS z*#_oB-NDUuOBxRKt}M-Lf8&a1B`6#UL|Q{Ls+PIDCjVd0TfL2CHZoZ#kNz}eT`d)) zyZ8%a)4U!0$Nq;QY^Z&|ooRs!S1?clD2FplT5!tbin6o{u(Mt@B6}=)r*!}fzeqc{ ze=p5lAgN6StMW?w9wBbBkGfmxZD{d6o`|M^x#r2I36xaY&YqH1TO~6KY5Yyh`2!VD ziOiu@Dm~_*MgKwRwIc{%>{F_XYD5Ptc*PExa#@OiQ-!c6sZveN(~2f~-ZfXAILZuu z%4>e417XDBckdCPf#8L0=s2*?nH=$`JoCnQnU;)jD*2S*k}5lstP`yGg7tKw3_S&= zTQk7v>KMIK5Y{Ddp7_KTm681=0c$M$2+Z7OMbzVS0u>c2EN`Iblv` z%(0a>+6~zSp+Y!dCL0C@%y^0?DPvk?Fi@LWOE1Z8Nmc8y2y^m~ldje|a58MvI!6#P zt7ZSwOi(P#?DWJCt_X5^xd@iR0`}$;cB)}Y!UAb25Y1hQrb{}Y|N1!AH3IzsuAZp^ zOHAEM1p>sLZBH!eC0##`&Leq^nGN}I2Isa=`3xSAcHurjxAtQfh6c$FMIEWr&Y9>#Cc@nDM9B%`aHkC3M6hi2NK5;t14M)qmb-Izt=K&6hbJ*H;Z-TEIN9P9f2oP3+a z_>67go`n&DH4S9^p){eNuYEOL-d5C*i}Q8f@6i{BDLR<_yID4YE7&#L57JN9>_$&7 zPA3-&T+~}Yv8sy%!QEZ;^2-G{NG-uPN{0dfG#2tQg#K76P2ID{_xN6;;jiI{Bv(_FCbEqp#jD-Z4 zqRhAWrVLo_v;($lr3Q^P(?Ej$pJp00(`K{Y0W`Q9?-NK4`2az_=5#GDz zc37V|jBsx!?^x6R-U z6XDks0e8$r--jptpFAlSQh`i8BgAZ)t7_3TrdPbP#FM)uPg}Mt=2ngUbWaDTCLf;8 zH2ANYQT6s{>Bdp~arr|BhF;!mef%DJ{qW?0uA!w(OM>fS{at4FW4V*a6yG1hG6iuP6Cr+(j)v6~qccFvG+n zLM1%6f4QvRe3)AJs}jEe_^9air9yQ^ez2nBq&l{k^eIEV2m+B^fnZ(}_1R{T&14(h zQ>@l9W$A7jtVEl>40kXhT&^Z3wTLnA$OrI(~2c zK1@z{VU(Aj)@mIJF-^hyV=nuCW+n+sdBzW_pUbk6zPIVb%Hus{=_GU8e~s%JatvW68t z(7XpWW0DP)J2`(DIMjLy#bb0DOXQC7)?nPhaQ)UKz#4rtxy zz*;br;My>fR_0j5c-=HD8(AnZ)W;0XVyM$dQi*)>Hdtz;pn7Eu{Yju5P+6H0wHKze z?oj=qM(VeQ2uUYbH-Q!OxaBIsR(Czvc{Mi-;1#nb*^Bw^>=5L`!=4s&r-ut>>AU0o(H;`++_L{0VB%G;F!#CDC_AlbFxX+Ph1+w3Gz zSkKR%d9CnVN%J5KkzGvms`c zbFy(+$Acu3T_TThTy90pACF{8#Y^y>&h8B}ouDRgTUGeSi$*O=W|W_u#SjlXzL)aiY1e>(gjqeP3Pyo0F7(;`5X%ja%OPzz5tlI_l--og9+(pa;*~)*M%l z5kAz)Tv!5+OY=zi$5Ps7_x8$*NUMl&I1S^}Jmm@eB7PaW>K_0`db0+IVf|lIas%9A8Ls7unrx-I zu{j%LwR+#p0lDt5;r-9748@cCGkMW%G;)b@HE;w|P-*9*ZyH?fzNFONUnD|os;@9w zyE8MtrQGWHrC!hjl*lL}UsnXrSMVj(z&XdVH&4_#?!4X@&`G%%-Ni7?96FLOH2sKt zsli|EH3JKJpN_=3aanUreSA zjaY;03jZ|MAMN;=j{E3RF@p{+{X5a&6ul=Bh3x3pisFDa_v1JZ3G>vou7(j}#VnT$ z(Jle3LNNChw5u036{Uj1Vb1hrafhyftB2VOjBzAsKNU5~WOLDCrerR=5_yM-sZ#+5 z-H&TqO1kAv9?#Z#ss!obj_{?P((;N*r^vM30jISx7b9f)(&FhwV(HN3*>>y&cg7Zp z3#AL2<@gt+6eLn&%@D)UGZ^REJBg#JE>y1LuQAR>dN4?<6WsFBXY zB%sqTPh^=o7hEcQo{L>HI&RykfEDg2L2s{MR-ij{38M9}YrL^XwrSVFLOb6AEX70Q zn2u%4U0$jb_E$FO=2{}q?p!7;2}BiY6r-&kI{Pbyp!g3^FydT zI|0)@G|*~ybfHUl%s;KONU<12LyV(cM(?vU>0stv!hPZ`m%dLmY0FXgpfzHY4{wzc zUrzUF^)n6RYvBJErR`$;|!eDgKI6tI67` zX_35L=WoSs4>8F426o$E|BtZvUx_+qHYUdZ4U5c-{|1Z9|Jb$v53u-;H_JiQ$B5*R z{eOD1Uw^&XUkh+nTsZ&oX66B{7)wZL{UYNsb2(ZT)u*_ zd=sF44+29jP&^wUTsPNV-JcAf2Q`LYX%}r4kOO-0C$F2642Y*`5w(p@j3eAZ2w}V( z@~r@My5d_GN+^Bl*q@(*@>sZOFCHnH+CQu^y^7K;1d=SCBvz z+<{FeT8t6aBPAJh5mREEQ|A}z2>B%F0l(-ZWSipYs3`)1zL#E*;LaUE;$818*;L$d zEn$CBZ3>)zIiWwZvG8%<01kRt((U6&lNV4()f}dmxDx#p&|f3}6yvSCnG4QEw(tAm zKjZRg;jgQdXpuDRiv}p2@!YNp;Y6gKNMTw^VQBv#_%6Xw76`=g%yrwUk}D^@JaOLz zUz>>|iHB0ijIF2$D|cNje6g-IYjKBHWw5{%Y!WSYHj}F@pyKkb)1}#l3gB*^Ys$P|&o5@y-^y>#0ZX-Hss6geupuk|&U)w@{U!iYJg)S9_ zSr*qUAnqqe5~#l|zaEwE#|J-*-bWOiQ^vAM)&(_{9`tOm2|^8ddbLw*(GQKeTP$gK zI~E{xT9Au>VyLP#eFwK|>D+U9cLECjUeef4r&$dMk}yP}FqaFKm@g>u+YL>si1NRW z6hUHq%w)=RzK>tei}|?OMrPz}p|020pme8y3-g*%Abl4Lca<=jl4mzYj$VHl4x%7i zX&lDsO?Tz-PuajDpnXDTjJmZEazaR#K)5OK+Dcxp>$-G8&^iRM0R4_@Y)voDcCc=9 zavxh7MRVk(V9R-{7LBw&mRHSikWu35s5j0U{(;IeI)7PguD8MTx)vlo7*#ofaXJI9 zq1;>3wi$j&MQ48wcS-rMofX~s+^=gaYwW|UPZooNY3ls(be5;FB&~Uz>WGhH7&6J< z@ey55P~jsZ;NA#;u~2cf;ay^c!~~y{8fLyb*I=3IriYPc^~NaS57>$(vrLEEc|92D`pkVnjq}*}Hw97|ANGHvPIsvNSoyWnR{ahWensHrna)$D0sLcS{bLA%_W# z{WD^(dtEv=KzVHmROIHLD37FPvvVd^D&B%y=6n2ik}M^seyweD@m*W!YbT=?RLb z2hWjY@YS;+-3L|L` zE%P+d?qS(9L={>Hy49e+=V+SrX q~E3Hhh?<@5!=rf&tAnR<99OJA(o6qc4ji4 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 z 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 z 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
SctRau( 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≪b+MmTV}1nAX}Oz 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 z zCNU)Ss)ZaTNo)^#_iK)v-Ka`H9C_-G;hRhFFd)e$%mfi};%_K9G30NkoNuUJ2G8-I zc|G4yh4P4hL81Q>YUzKWfDGS7LVouC1(j#?7ZmURLhYb;VGzpeeM8NWhy4YG@K30e z|AVUiLJh|k*OjzhY*r*z-A&S*B!Yiu{=ma%SiAby?)7g13?u75jjGxHRv7)Wd;M>W zs{gWA{9mJLuFB>g{XG*`R`_gWP#l3924GUGFdiB^d289?F&-?ea;c`*eo?C`&^AYb^8{i`aThT4;tTy z=7|uylxDbrlyX6iYE8l2$Le8>S(sC>s<=}hD%|7~2#6sp>k&iFcqXC!1`2*1kbKmRKwpfVy>~;g$q2l?&Nj_BCL-+srYW-q zx5yuihiG39b^h$pO8IE9-@AlFiG3}`^sSbZlkd}l_LUY7x4_yI&Hf zE|Vjvk7NcB*(HU0ITH!&+v}6yJ{gTJTq99xTKvk%nICpELL68#_Ao4)zV4$7)aym9 ze(GzaCzmE#q9|Yp-ZyHq3U0tQdunmYLuU6121+>fr9>iwl^t~`9X#W02!$?k(~+t- zxd1E+ZhYZ5108xeHoQ>xOU%!l;cl?B^TtPf!rZRCuYB3=rHOD~I7in2?vUd|59Fp^ zckjvo+8Flq=G`9NfJ4Yj!%ou)j~dnD*06ly^9d=|(83X?H1cxT;9w`5=l(b|HSC*$ z2!$tp+6?5rP26~oC|I!N!6V6zOJ<>@DVCvXOjcf;raIQ6%%jLD-yH^Uk;GIsMHUo> zxuw^*@{|29AC0`0%JE4V&@e(TQYRQV7c04ZKg&$T=e%o!j<&Q&SM94A41J@UD%^AvXpnmHt@x8-X_ZBcBLHG6X27*5hT*^*tU2DnN-3(t{H|^f?Do!v zI?AB-MX}JAQhupY48MrLQ91D_OzAnI9AVZr_{vLuuXWye!*UC)WcvlO{4TNlz=jwx zwv!tQie&721t0Qa-4>QWFe8%i_;UkwPwrEcsSGooIpG!Y&tQ?D1*`l`WvZ|p(kEpx z=PWJr#Vhs%_R7c@O_5cTCCP-#m^BA^YDM}CDV`CXAXzSVMHy%xd>GeO5qR0n%p3_z zi%6FV1>M~#Z>%(qMlU6KJRaseP$LC;1MA@dh2=q#z%o<+r@4?Y_q(-|R^#P+(PT&ZcH{$M5@4nv!oq3RfuU8e}zX7lj z0tg{IIWBB$jE1%-lonkRQ+ut?98O?MS?p0(mv$qH3_SO_gcx3VO90f0q~PN2r^KCg z0r8F#Ey-u}_8)M|ly1sWeRDiW)z=hh!^Eyg2HlQ@mr>aA?$fHH+^mLUED$wYlTYg1 zi6@VD7t{vzQFW}Eq(X&~he-!es%v(j*B=6v2`%dt2PavqqB?&^7zl>7vM8_qSrmuf z*e$BbD@IRUJ4fk`3t0h}{slQ-MN9P+4cTA+5j?D|A_rX)x4}=HZnG?za GeWz3 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

&G5H zZe{GQ>hi7uGxm(pt@OOFaCZBya2W*2jlI<*vwAKk!kz>BX9=UfbJBp(OE{VG}sl}H*~uYMe)2H=;_bJ4_3RKI4eSGR!oR5J$Kd?TIg+Z ze3o^t8V%kw?tixUR6~}RR+Bk$1Wr;xO0 zlvLi0THbAOAu~|N zl!-s!GAt+;WX5%yVN=d>c@tX5_3iR zG*KDbMsUl^1;y&cXTA;#$-Hs3!)!3tByBziW8t`pp&r(S^bfXxN(>u`aaZC_+_AC1 zl$+NT2DViW`?h+OsWoK!bNehDqKiiS6k!$+PYf+381#f?1+q04vnk%L4 z*)N?Kd;p%aQsJXPIp~1GiD;pg`UflogNGFJL_k?8O_+Nrg`n2?7yz^34 zg5&PHrO8ttN00bF`btaxHKzU3@L*(O``4PB)6<@4$GgdTR0 z-JEYqGFo4L5b5-1wwL?!b@_I5kQwaC3ZN`+s$kF8Hgy~}*VohTp$!J^+k%aqC$=A! zG77Bci}CXv)t8l>j(4)wBwegI)=CS*`;EIct{QXuTeW-4xw^MPg4Xg$IS}r6r9fW$ z7Cy+=Bf8EwjcmT$t_bjY#a3$u@wI#GUq)FE?|N&j@lSZ;BhOVoKV_o3~+l>FcJ(Wy7 z8klaw`W}(BUNVxA67OfY0b$-VKdUY5Kp8^m*kjcK`ZUDn69*PEksn4(?_Sl)n7A6! zIn@=*++W-!0L#mA3;Qe8vWv^{{d;*ZJ=d*_7y=q}%^5@hc&rBm;psdp0&l@x6{>$G z>F*%xkND*rzTQ2Ic=URp>ED-PJLg~r`6oOs15hUQ&6h&}^oo4xtsEo!0%LA{R>w>i zp=xp6d+o$lx(r85YcG+FngkX23?;x2M$+I2V`*>%aSXWpa)|+RUJ$_hJ-lIv0M7^^ zHdjMp<{%i5)*12f2!`!WOdbAue zuz3@iPSB!8S`bJn$*2=5Ol>4DVqE3^2B{8{ z1;>x9;jPwrPWy8goP13^n^7DN9_oN*;~#)kO^z$ObmuR!P_k(~!M^v)@!gpEl;`GY zrlJtI*;?b&P$w=W22#=9^-l}6*a(TnY-*dicU6MBBsv!PCDPaAc;j6Vl2a;7RwY!U zJl*@IMmNJ>hYAq)k^MuvG4oVfqKqQbjJxFwAUXQL62BN79Ff{@j{!-`ao>}TFh)1| zQE_jr#y@JFMlHsg5H`PnB%Dci7ZU2Ot`SH!xm2VqD8M6}B_lnlIWV)$R2}+?g4z%D zJBz%!lR@K}I~B^KYSog?6iy>-i9C~F#)a8+N{m_>2y;0iAt4Hqc~?hFi_@7Q3_owK z0fXclcR(Mv=&1020rNF9mdLIL(bWtwusId099c}f`5dbx}37JW+ zsEO?)LUi{)tyHdEtmL<1&M!z#6}dddG5L4PRij*LB0t~M8pnQi6I7#FDcUfs z7ej)hNU8$N-uQzi08X1IGL0RYZ)1#0%;}WnFykDcDQkFA&dy8R^LoPVjw|Nr8$jmQ zel)lgH$0SZ5EOSPh*63H{U)Hv>9$JtTo7oe$ynMYMVy}y3|SQ=an7P=hRrjsedfF1kX^fD$Oa-VA`itJfBT zi@z*{!}iRmlS&-*JQo-}STb@L_@HC~+`Z9a`ms+bs2 zs98en0l_4X)6VobdtFt=Y@StwNA-0{LY_^}x%v&_9ozL2(@X1~DW3BH3wS#WRv(0R z##EBN)wfp@P7VdJT>4nh!G~aOY5)7&IJvHVP;Je+b7|OqAxgw$8dZwRZl_&qe#N?? z_D9p+F>giAnZ`{!-zu8i_+1&+Uy-iz%ShiEslJoSqnk{67fJ{fvAfI?d9i$%Cb$8e zSV~i8ySeAdEGJbXQhBnVqR2>dtCWRUIaO|r=rx7riLEIAaN&8YB0?O+vC4ij#=n7@r$5gqgIB^%nE(w?$wU$o9=or|0({Ub5+bOW@5hh>x z@wl6>T_$nJW^lMdx_Q5bRJHs5b@{p`eew`#U?{7ZLb#D=i@5RCEqa%zu_K0XS8Ln7 z7RIuWWG_Zjg;E5atjGRpi3#qgmBlT_Sp4llg`#I^vC#u^hx_v{jTcltQT;9`(NrGF6-dVEnBR6HSw+wi`RKSL!Cw(U8Z2Ki# zDOt1UhjHrK0Goxg262r1;*RkgiY|_IT!Lps!$`NWW$aBFs~2D2?yr}f- zm%FDGZm%C164YE@zA(FY1cm_8d-6UtZ`^-koC!p$1auWB-)V5gqv_GPn%)^5AtCoSeJF)J>Zf#Gx|%x!ni_Z_%-b79FU4S^7hZ5i0gfT`v{d zf6Jo8n-(*8EuLCvE9AJERI-)taG? zNIq|xH>d7DdUd0;xA%v7JQ^{~SGTEe^ZsngPR{PmpU6~~c(qjS>sGr~;`lbxcA4b8 zsW#tK;u5Rq1hWCpO;}Y<>LzPhKPjIn!@o>yGsPs2dlu5~;fEc6F;zW9F5>a}ep|jx zh63?Vp3L&~*nB(@=@xfXsOAsIBI;W0SDX!-)S|z>+6NnhQztUgM(!r{em^{Xq#isR zfiL8&57|I(cyFVVp_Z`~BuD-w0)yo3D_g7HQSJa6*|XFth)Lh9M*3`X=Pl?G1MZbB z)Jb-SYCFJpGJpD~)-l`qO^*yOFNoYSexlgRg5bI?M7j>y8Nu8U>$4NdYw!+iqX}y| zf;*7~tjP-N4agc%>?`~|fkEFzG9g)J*f_XJF$2&|Y=a>B?I)5!>zs+Ek^vzICgB6A z_98A-1mQcnx`_)pwjCAMC2}2$=x)JOd40niFOOb*8P*G&eorNDmP07#4*b0!B9zco ztKT_zVdA=k6>$-RM-Wg>8HU?+Oi17cd)xJdVeA4VU)w3~Mi46=HV{f4-*nei z%+7*9$~pQB`enpl{OB{314s$J5+MHzqz+@>FO%GgWNo`K%g>VE4ob!YI0 zmX~gb80?t2Obp4*Xy%eK|@#S)i9J0wiIq;tSSGq87g=DC30A92vGf^G6@Z;_NF{-c; z`%sVz!vIi+EJ-Id-a^9EP`zy((~(Q!j-`VRG``G8I-531D@?&Ouw{lLt8$nXYHkq# zJqtSXe4GqyZ?8;#=3l{8bcbmSBfzg9aQI6B;$i(cV*Pv*mWkEXj-(zl#8+{g{qIkC zd1@Jg0fXr#Fh)V+Nz0b}*KcxweX^t^qAQp-MNy&Gb9=Um(!&ci6^~8uxodWbV^#1- zgYN=f#?<+?i2j@`GYgoR;(-**P2B0dkHSwGClD-4%c2H$Q~v9P?6$D9Ipc!SWPl`| zO7RH?h*9;uX0m|Nwf%?0I*@#pNcAA}pniCWNd6_fVBcuv#VySQq*0Ih{q%5dvjiO^~j>A1f&w3iM-aA3BYk*rG0?!Q-uvnodkc<<_!^6MH zv(ME}9mg%Ga%n)V4C`jcx`d;+x?0TBLod52giCfrmRXUbQ5PDMsuT zXJh&Vu^LLX7@R>F_w}0?&qx$-Lx7uU9vDevQ5CFY(k+X$Ru`@73TAafM)LCc%(%Fj z?`vfW1*wx%L}Mw!h#ONyh@wgb}8=CdM&_qk;z4nS< zM$6j$#uXpTfiZq>oenxgRB)oUrd5n-4g|Qf691icDQL4{exY4dG~oa zh9V!L4dGW?4Mv%r%Md2Wf+^5d47u=RaOarCseKPc$t&hqQXr_Lh@;4GE^H!ufy9B2 zrA%%s8yx3l>Qbf+`2$WI8rL)KYU1S#fBu&1V|9f zgDuK^0_%I82OSZMn7|cCy9WYtwDWs!bTGLzL`&g=*PNlu_e>Sey}{tfk7+_YqJnwT zC?t}kdgS`=wEA>3doKd-j5@Wt5XbaUJgdY>xW33_DOn|8VL6Etw{f;ruCzG7j5GiD z>H?GLJziYS&h*AA`;219VZJ5iwrH3FHh{lIxdKqR!WfBt2+B9}_&^BrVgcNv3hy>7 z=tMRc7X>5iYm;Z3tSLU#U^&=(p2CRBJNLdDg3xyychOb==8GCJY6HjGn4_{TyDVe5 z6w?-1K_d9N*cWy{&K2^@tD2HTeM`xN0@7q&n#}|z6dc?B*{prVG>~G?%x0oN=Ha@Jn$L%dODYo!4gwbnsqQPG<3zLG+)1paa9!9OK55DU{X|Z zf`z8U-hxpRGpiMQ-{v%1$+aWnx#_g%z-vh~~(F0RNF|EBZ9f2a^eW@Dj$=ahhQZ z1^0wQ9A{yXXLLscA{vqoZhz4@-c_QfuKjuak)efBEX5OGTuyR|gGKIfnouzJN4wac zdEsoCJ#j$^f+w?+s+hxEF9;zDMAGn1xD})(vfOs|2!fLbF|)hb(cwZkCnw5!P>L6V zc;*-_5&-f=47Ugd)R#p_g^^Cp+flrolg=?fC=TI%lN%L7BS?fa#E>&KH1%Zzciw zT=Msn*%sUJ;4LiZi(Z^`zEfc(Q1Q=OgQwtA)`qK$YFDOr0xIiuI3ig?x!EHs<>WXa z;q-sW>_Mdu5|-U+9u>))Bl5eNWlZ>$aIjoZ`eG%c>s}43%cVZ7IPcoRj^}L4!!B5N zo$n}TJuNIJ`PFug+$*11%;Wg#<*fNhVPY;rCmWJq-Dg-E$hrCrggm#3yE)hw3Uy6S zN~9pvzFaKRTK7ITFsjB8SKV=a(i!sxZy&Zj00r7@p?MoIH_o2un=8M=nea8+TI@EG z7}1aQC}~6s;&``4c@DnTT=E%rtebyhpE_6H@ovetOzwVT@dr!I09<;WKKSNCk6Y*l ztJ~0xRNlW{+`A5G+xn5N46!7*1c`OsajzK+b{l9oNW~)d;fU=B%(~fnM1Z7jS!6j< zyF>FXeqCUqX*g8I|E^D>dBnN4mu)%B+-W`Ec}c2(xK~+Z->EybJ1H~E#w*n_t4BvW zZFq|!(MT|Z+AO`D^zaygzEDJIJ5({uG91@D#iZ+zt^f0X9oe?|rd|Ae!vRt@EDIbd zU7)!KBA?~j^U7bE&i1~dsDEwT%=;nuDL;U1jUE~QC2juS2>GA;PnLfzs#xj&|CDT2 z`u~NJy{#c-i^B%f^PxJ`?$=s)V&*ptwBx2l(Aos%0r7vtjAl^(N6e^DgtufllGz3! zhf&wfghU#TKOVa@L&oss`0~Om`(U18(;%3&A|gF0>D%ko_VE;YR@?d3Bw476(G1lr z{(AAqn0Ypd?>dr`B4LJM`-`~xhxkaMzw_^)8(*CDvv2I85&yDec#4dR=ZE*}l|=ve z@$>FWeHz{VZ>@`4%Dx@lw7ncQr;N&{&BFWb>g{U#+3TeyfjkLFpC`Vtv+SWu`&#*R z)lI!I1pYfdr&xDE`%-;YAK`NZ94Tai2@axA*@IxmO?|}|jTY|iVd(rQQf#Y!p!r^t zFC0ufv{wAmVO|}r`bTYgs9HR{p*0dxVPN5QDAq?jG-=}p``Pvn`B~T7BW0Mboi-$A zDa{;V@KM6H_>GYSs7dV-9twgiWaeE-w(8npP1$;m5iC?7mVlBO-FSf+oh;uNC|}ig zU@!x*m)U$WZc~Hydq^)EAafCkm$azSGof5t6!?-ch;&nx0($ZkzYv7`0Ap6ZH8equ zdf-$RG&poPD@YhU7xMjL)fanYVq zmFP!2jkhXNm(a_aF8_jb4-_0arPrVom+l@sbi!GIG9jWLb?UIBDVOa?kSr20pHSju z^n4b4iwJ}9!1)TvI{K`OCyc_XS~#p?EEiLL4mi=Hp-Ao5i1&&9b^cV;flRn{RjPEI z?Uk5TFn3QKkXS4j(z!(!Ji2i?N^KY$HshBg(uzxH&ay)jg2h+|{>lObBe$omc|3hB zX!D#|M)a0~Yq)aeRN~p@BYn@Vt+4ibO#WanM+4nvd?U>!k}(GEK5+yy8bFrTh3oOq zha23*N`$yKB+>%F4aFtOf$x%$=x)sIiloy5iCsWx^gMwq{!hWN*xgjQr zgTUOIsCrymM$w#p0Z~zO7z8H0>S7NNuBqob$&uwHP#KfXCPodoLXm?=Q^b9<2e-*8 zQXzAa11;;^3D<9gS=}aIkFQ-0;MuE|T^&WrEfdqFu5aK^kit1<97qR0x{Uac;71pj zayyN^FvvQo=KY*g5S0(izAY%(XewoO+EzY@BFrl&#db5CLeKHVg*0xogH`!XZ`skTjNxc6tx;&$NNgsO~kMLv53iy zrSVhY=;8pATYnR6;za3rYXtiU!%4F>Vbqcj*4&fx1Z5Vjd`=+o%6eN0;=QhJMIq@$ zh6p{W6G`8)@fV5Ha6 zE*yp+Mzv^e8y$a}@6}N)Ce?7GwW2w!0sX_7j@pR)bQ6v`G71M|9+Y zmZPlS_9P3L=m)o_Gk$r~XR0E8V&gR85x44^AkyjIh{-Yat+xniLTEy43*}W_u-U2_VEU59Ga{v9Ek)&e^)K{7_I9BgrFID zcUCM8YXA(Q0(0NNPqS~ZHfoAkIPik(ujHBwHYrfm0+MH@rAdoVQ?b~$XqWgoxQWEV zcf^Z`wD-x{RU0_c)$^*NYd1s!aKt_UnY%%szoNukxKDwEn9%%1K)wlym}C~eS`IDs*Dp!GH_j)c7Fi~>B33jujbXbiHt-00yHY}f z)JxapdH93J5i)+*yPrk+!Pa@mC#A&e%-a1L@zo1)>QHXsN1V4y@9*a`2mJcCLeGM5eKjQuFBB6g{1TAZJ7 zX5YRvK4s9{erF7-*4bL%BdyI26D!@tTg-Eq zYyl>4H_pm+h7z))U7$Irf^5MD{kMDzm z-<}8a_x2-Q1as%`e@#~ZRLB?^IsRX>jFtYM`QN|K@%|S8cK6?{I!Qa?UjX*lgJAO0 zZ&GGZZEfX{?&|G7gZr7Tu#ua8x?gX&iXbHNmkJ$BOsD#sj7v}=UiWd+_o-8VdOl4N zBiW&k(8-S6HIEu>USD2xb-tem?{CoUcam7Jt2T;sQ2**RQC;4?a_LsGSBKx8sILr? zxolnHH`NClbyIYG^F~*)tbMAM*Og_Pc`7ZKd@C+pSPd5@!nVgBnV~>`{Dv>Y)Gkt& zq1fLJ@F#QeEaFYxUY|}>k7&u>DuzXRCN4y}DKTr3+q0N=5O_bMpdWF+6;<238TWc@ zUbYXgyra=o4QYLUUhj^+@9q^-@y%zk?+ek-i1-XTv-~o^_7y^N8Fvuw4NgPs;#*zY z{3DMt&@20(E-|t@`rU1iO&@STHTlICDp(>=S1YY@K=FNWNGZNYN&)Q<)o^aa zN!t82`3`fn{_MpFQ|Tu@u*rA5zQyUx-kO-CjhvE;3bXL&9Qs+14Y?Y65;}A?sKtr0 z7~_WO2DN-K`w6k4gg&zbCu|INjdq$qW4tk5)YJ(QuxO))ih$XIw!(RA18QS&!hQ-k z^fMoHr(q-vJkk(@Phd^lyWhp6>Sw2Co*UN>?m&gMj~5TRFoL9zT_7}G)G#hax)8eF z!7LsWLc|6`*x1jO%T93an11WmEt+t*E?|`1>$`+uha3-*f(|#9ZH3`4MG=ZXDWS#8 z0AgEr2F$J(dE}I*yr@<)p0?hx3d9Rn4=<9bzM^g~pXLc)5UW79vt;2{+JYmQ*^uhV z%$Z_{Ueh3K{S()+U?cyk#?w=FqGa!og=m8{)fiz?4BgS+$v{b8s>?L02rB?u{ro*D z6SS!o{A&b$Q8XH%@4mpTekML)=o2{=tzaBh4Cq{mEnH@m9xJ~ZS%@8q33yQw`faA6 z9c+^x&32apf~=6}BJ7Z%YACI;ax8PeJCgv6u;@m#rELes`@mRn-Anc{5Ith+RVmKt z^N)IzB)Q0(0n+07QJarV5=P<)f^Ar>1P%ZrS2fy%HUgdrqS?J zE{-3Do7bUQYDdp)Xa3@nIbx=y9=*)aS2|`>?fO-YiSz@`0-Wqm0od)hzL9~o@vKFD z`}sU)&6uT2@5uX>l;15W(G-bto2wX8^PDu}4;4tEgeWcM`IDo?k_dyUTP3UBvC`TP zv|q22J1rj7&a-Xjl&#FA;|8C|z4Mjk9yA&FbvpE9M?#cmG*v;kX#Q0oHWBF85|49n zm-YL&Axqu^XBE8m(ic#t4B8tDkNb&ad04wn8K+vU+W;V-CiWwz9LB{N$7f~Wp&bU? z^`xEpn|4B7thS+GJ-aprsbo_QP@#YM50tuWtfkm&C~VXtdv81#GooxJiH1i@@o}6u z`JI|(`&rj4}Lm_89FpG)Tlg!)Kp=YJ*;xbf-OAC7> zv6o;E;?*Ku}6%9O7f@eoEPW-w%q-nm~=?VLNx$4Z&5*N8<6GaCe zHf@WjaG$(@<6-%P5p_t;?3bi)uVnq%8e=WT7;I6 z2Ez+K2PR}f-JtwZBU%F4k>TpgBK7X4UrwE4M8imMWUEPY4@ZNgUCuleY31nS@P`7mm%AzDz}e)Zj8(9C-JbXy)3uS zro|K(_)T2e`I7xui?S!WFO3m?%cF<^moCJDwgXbW|4O-fE#Hsv`(kQ;A;gUzZ{mYHukDW{%6tQg=8wWuT-Zx8FuMUE6v45^Zh? zr^#wW4+n_4Y5l$4yyCP>8p9c-nu}PMh1u>A7guYT)M*i-&J9Dg%msh8naT!(1s$k> zdb~!9(vW??($zWGN5shpPK@eGFLX zE?$FoPV?E5y?recRZ%83lUt`+g1B##7Hh4Sofw^hV9u4?Th~?dp?LgrWTohj#p=)J z-xr}J4ktuYr^P!pb&Gcz~}z_^K`$F#yTAOW zvl*E7Ea~?E8aol)Td@=|Jx|ePzSU>FyC($gTlHLaUSiw4%PYS}BZ=SOr-siw_Yq~{ zXSD~8o%-_m&|ooqXSO8b>MJ3Lj~I_pWGZoOeE(;3cTnxn+PYHlsn^xq`Nb85Qo^Cx z4596-#oMp%pmFUN2K)F^mv>(4tIQ{c{Xj--ZpOT`^7Oh=R3&D8H1*4_ zkH*@73fYw*yY6E&&1USU@8|J3?j}`l@1IR^82{T8_tCf4tkEW7W4YqFFj(ECTdWJq z{bBL;)^)4CrBXb$UESzj+5X}AsO)Z8RA#MrbX&1~G&w5A)?xd0mYeJ8)Bj7{?ZesV zs?nk~^XNfuN{@GVaOM8(A*MATo7)7=dbU6aX2PK$yRx%+A&%?^j)(%{@Tcy; z&1|hMm>ZD`f|v+XEFuLcMm1I3!S)n>yV{zK_|STQEcu^88d=V8k!XNC(sI4Y!=hoL zqhl7yQhP%!5eTq=B*XB+c9P*cBPo7h{Vp*!2bcbP{h$kAoOE0uYgjid;zkhIBlFs% zHZ>SUvFAVag=e)?c=rww0Ns(;>DR@7fCkU#v6tO>Je6(Fkjmg|s7o-!x$jx$gJ;29 zBkjX4F<%d9s>{G)L8O55D4~I4Lq&Pv^h}p-H-hW9hI8Cp52i(kqIoWX15^9R0;n`~ zQ4?|hiXNmr0ukn{_MAn*YJ^EPYtzyd!z|SI6aq81c@_u67`?=^^@Ey*(@{3&%|;l@ zu_ZUw<^@sc-ZWx}^58!w&A(`d1*WaR@XkSM1{Ysj3ah&qK=G^)fRYs#nZ3ChqvfkA zd*neEP>%W&Sc%jAi1Eoc11BJeYqkTXtU!}$s*L^4LP@_UEEWJysurfsNSt8FP*4xE zWOK}>tTpe|$5TSerBb;;i|n5S^_lN9v-fJr?;lQ-oTad9WKE3=k|}&3ebmc`Vi0|& zYmX#AYvMw)Amx_3Yt1dF??i?tmn)^VZ28_x4;ByJb3$)zyF_ikTX9y=s~AN(83yJR zwulOVEDVH(pN-)GaBgJ*`kob0?`Pgm|XuPFVLY0BZ?* zR7uh9T}t}Mww=Ev=OtegKY-Kv&<5>58HmO_NOfav2=J_BG@eZNp#5=g5hsazH+G-MkW zGOKH`E&dxS3BCm|7z~v*3!&dt6bfe22nOR@DaJgv%&>rHcXqV9~8ex?ptvav8XUsG;0CthS0Ggu_fA0i6oMx6)8)og}OfZkR%YpeJ}=1$2g(B3quy z6}Cf8(chBd1tC*fxU{|bM6{X5cnb^ViCAYLPTXepC*OWrLl+o zPXQX(&|t<^&>?ZLEb)^e-$ZVCRg>na`Nby4tdTS$#ovYXxMob>!=Z-n~BBE45FKCA~zK)l{p1xt_8{*NP>t4ia)94HJw`O{sO zYnJY;ZvjD|%6n~54NR7hK9lleH>YXnID_?@6mI0%6|0}T2Ab2<4Y0RDP|58AgH_cK=u@3L;nU5`5+$9 zsk4!5n1C~JQ2%ZKxSO5@S_V3!h7hig%I0nJh*-skGMdTL}Oc z3j}dajNHtY=+I|pFQq>dr|Jx&xH#O-kelVUT@kCUFk}_rZA2|l&ftqyQg{m=tboMt zV~aVqGYP&Ai9VNS6_CxV5UtSP_d-hWVKM%*g-t11mKr2*KS5?3;mTd8Fk}>MeN``T zlA<9oPbbU&WDR#+oauiAB2J3>-;{XgPdz>X>GbH z=37@Xodk1JnauIPES_nZ$YjPEfb#4~N01648LA1W zJPD4DATlK5}FgljU7MM)tOsZ_*Dxi!Zi%BZM{1v*dDP3RMaWy9VD z%HrX8PZB-YxuPPVr`NmZ5za-3Q+Y*3@-?nrcI89Vf+}8M!RfsHt|GH&rN|pEd#*=C zd>eN0C|-<*k|S}Dq3jo~ql|#PGfdt1bxjsTBrg3`)@db7p`Gf??g;%`OB2c%;143! z0e*VN^{*8(wU`cKf?kyXP9V(w;$0|s%D&^6lptm2%R%AL^8bjy=W05u3)`~BF8cw)5)egPV z7Cv>2Pv>mt(CeQU+vtx%muQ%F;8At#bT##JH3Cvg{5pw)-jE;CT8pLd%azdrXr9%j#KsbT3+?lf|kvN`>X|-lbAAx?FTdq%lOy9QjzPfO{-+;q? z`fiWVy`lH=;LeZUN^7Ruel))Ebic_-`w?EW05FIKVcKE&crm0Iu5o|p4>4^M`kiCA ziM9{qFiN9-$D4H=d#5my{2d8OtNt;b{?ADH{~wwDcLco|lH)e53MQCyTsWijkZgD3 zL(Pzu;3M@wf3r%kGW@^k+^h`$O*;2K zAT2Q)(m!~|rUJ|V%RBC@K=*pI8Ni#`S<$821o_WxUpj8VfBwAW<$gcyJYOWS|C-DM7R6P(f%<+iMR)!9>XTc| zd6~g*V|8ts%z^KkIH=y=k=y#KA+(~IZR~b6)9FchzbvzRYRJ3#tVuIt@w+f~pB^u4 zyBofRKkf4kZt&fzVeh&+;M>~UT_K%$zI%w9yZ2O>8`PD(*YE`W8Deuum=hd}`@X-4 zyUDZtCCA4MEm@#Sb#A4u(5L&@#Hr8m30G%21sGu3o{d-!Ht!JFcWro$d<-k@-JIwP zK1j$i8GMwSUQ>{qT`dsY4MeawMf8uCoC@cs$m$!v!)G(N=8=~-szJrF!a5Ol}geI_)w8j`^ z0s|XJ*!u9wqfOWW157RH-@B+BCc3mA;THLXbb6pvC8+81h!oOW@u2?1(B~ph?dT*z z*&@05iz2T6s~e+&@rhZ6MCL~k_jO2 z4-nKhWyn+)f{XAS;`JE^504B{l9L!ye$jxnGtfrllFzS=jvp}B9*SjTv)EDi#s}Hi z+zvo2h0y1~l0opD|C~S4Gf@Q>EFE-@T_P-{jQ3Vkw=Siu4ois{cWGi@>X^`Daes(o za&F>O_-#XgM*cA%>qMm*?+*gDX2ljSm!M0Pjseb?s5o&PaA+m|Ncs!-P`hKTjXLon zNHsnX__(cax771foLS8dgGw%Feh0OJh?K#+_JfVXLqxQTh>>I`J~K7#1StJ&05;5{ z*uKEG0GGckEU|0dN$WTUN=6_u4Pd$j-EgV`_0BwSrXV#KxMovLy9hB*57t3TKu5Yr zM|Is7ARqy*=CRJ^fS;37(3YsM+%rSF!#lL_bnh3iOh}>k=sJ9~qs9o1OX`V+5SWP1 z*yDFn18UZthAFmqmN^fL7`>S0i2*ZxP%o&Csanr?%_Z9FT#tqsskjzcp3(emopw8?te}#(!!#b z%W(yk7&6yNow~1YjZ=fc42`c$6jl#kyr3Vfl~VD6bjVbZh77aWMtN<6*br zqE#SAOJOQpCl#$zLmMNC(8KLaVhK1@I;_5z6)3^4xuOd8BnmKoxm3|ik+SIm4KSV!1G7>`Hk=H)Yv1# zu0;SpW~rJ02uwM>HFApp8-3azVRt2*K`IGe zv1(r!v6?QzH7zIaJKMNx!80MwvbAsGUMh@r3p6;oAbcz`j@^^iM*{KzM2IM@>E=*L zQg=THg=0PhsaHm9(nVA&pAa~89K)sJ0%uCsensGg_;3Z2SHIN28@k;k9TBT= zlMxL;3D_hOF|0lm5lS)2o5KS`L`Rl3NK}k_!I{%L!jjfm+`som@)UB|97QFpq8KhF z2-{UjrG9=GEhL+ZVmTp+EEP;-dCPt^d`wRcNze(yy9wI34)~L76|F*0bL=yJJz(j+ zj->Rv;`p4_h>}$sa`{W{qEpeZ)tmcRdm{xcU?(VJNu02=Gi-d3P~2gXPhSnnMH95F zK7qo-AtM|(4>eyOE-@15qY!_h{DQZAyjSxnA%I7`lI3A`l^5My%P!h}5s-c8j-;L3069eWChL8~w`Y`-*?mr?sl2^Smbc(KfE9s!NL zlxB0AdLvaHLf7AjP8dwk!u@Zt8R2JGrUj`cbjhC~AXSu*KNU=v7i(sjAG-*UmNuk! z<~pDC6cvU$f!YAe-FZS!+LJ?*4x$9`$I2G$_c@d;Bu-|at#Asb0I8_N)x4O5v1z^5 zN`ERUIXf_vNp6)N;w`jPj|^xQ6g$)#bzLX^5Q)UPz*WLANW6DdJ`fF3wA5E;lxcTO z`sit)9(UE!vF0Y2Y?EnHMo4GDcDOQ4XG95%KYupUMG|sa{)7v3bKc5I)8cT^a`EJ6 zwiBM~V3~QEyKZMg(sWi5C@`#_NA5~2eC}L8hKK1qvp}vvGPxZ%tNb!ZgHbHQcGyF) zil=f_thP8`V#qb$8cra6ae0lDtaEcE-g-@xFln`md-5rYRqIHx^k}+YMte^aY^dz3 zo^P8ax>%*DN-m1I_HPGD<)&7ZH0*L&WS6Q5QD|@&&Z``aWh}oMD+kA-(!H#dpq%HW zb@c6?{j4+~C`JT)?V&ZqoahwKPiWC#(?E-6h)Y3ExlOWt*RMmdNvB~*CU;j~$- z-LLRPvf0fP<=b6}WN6gseFdDVce%Q{5Gl!`4aHO}ZHsiZHEu#_cg-=Z4wCuH>}O^k zE_|g9r+Cjc%;ZPFfTcru4d^k8S+!&4m6~FbV`J<^*rEtATl$hMj3vn>!f2zBXj&Z% z-SvuE?`rLVoN${3rzPNH42wf~5+b`&Uc*kQ=<4EYKh4{Hg7TWq4|<|uNq(E_7W=E_f4gU zWS{k4ztn3OHviKj;Gf9;pR!I4Hje-0wHf|pqy9Huo8i9+${*2`h@$#etc{qKlqEgz zbM#64R!{(rpsfae82>JbAK#`*V&(GA>?p98u2xOSYF#f<*X>dsonQ8Pgid-r+02^> z%ct?`>(|ps+D#wF7oJ$VpPrvjhhH!CyP=$^0lLro=h@>`7`ubZ`{z*@)l!|A!;vcW zcUFX&mkHyih2Gr#WD9nF?R2nMq}EGO=l#>ube%P19;s}9vX_wV0k?xcvw^?+(b_Gx zU)>MM6sauhXOz;{yN9l{v6CruWs*e;SQQDB$09xQ?0y>& zw#B5za9$gxD5v%KdV|SZ?#hOQxHVyKyd!|(1lEHw;*~1m`I2;qb^B$cdPMdK8(P#- zy27ML<7=C@Te9}3u5L)Y*0QfM#%1U23AMrplcp$EAey_L6+Wc}+C&r5x4~ZLm8cu;!Dc4f#Ac?n9^0^AuIgLm0&17v@hwb0&_jtx=+lhNSkb&|cj#DTfPpieB za)VjRL=hSxPTLYS4ri*vt0BG(f$j-MEKI;QGbU$qcM#aXg z$$?uxweItpG!0HI?o0UpI7EbGL%ULAh%W><;Y zyp9nm9JuiNG6HupPY1uDZl0LXU8jY?L36b8o$z8JTZ)yLT}ZV6`&zi9+f9;nt5!zI zR2&rDx_6;eV(OCTB)k;L$@3mLXmRuhj=2C*TTyNjThCGBpl~{g(7GP<&t%eoy_YvV zpsnz`F9!tb_58uXu-AO3rvuOg{h|1m>Vr2XxeY8qQ>7jHi!RyG0EFnFKu0%Pf32{0 zz>*2wuY)l<-HwH9!>W*)~MDZLx7M9Y^+@c=NRJv-Hrca=KNbla1> zhoyDUO+0E2+?S^>S$_$`EX@D$wPUs{Bb#z?6nKYuc85|kyRX-HCko+XPJ-~JH0Rx8u%9XKq+Ye;93Qtdwt+w_ronHXAb{j z`5m3NpujxSHbP`NUPWkm4dK+y@qmMj@j^Hhl9p8HM4pvPxp|Z8@l8E#4`_woR7Wqs zkZKyS{}@qog=?B;UJamMR1pn~PsxTiq$OK!lM%$cvJ)`h-D~UCi%s5-cg~hy)P!Zg zV}6WPH-9~fIh~{G@P>uiG*P@PTzG`AN}GC6l?&sL%T$?_nn2P1m8e_IH$NXe9ur(D zV3$qowZ&FgzE_5T+$sJSzE36%o84s6Yt^p7sAN`vU4Ws6fPqp;8n6Rx?lB-_GE4ke z9Rd?+set1TyG1MXbTcIEDV#LO1SCo=Gb%oQECH&Xf)a8xD~?)?P(mL?FQ$!RqC9I( zWI2l@H4=y{O;6{w%6P5SQK4~4wO2NgFk-N!nkEA?LSAVhVc3>Hl0;>G8Om%ki9P{% zE-*4ZL)eoxA8S}M4QH~z7c*KCZ-fEzNK=KOd#fh0I2MOBpb85Mk_NZTqxcgW`&h{{ z40Gz{1MF}|oPbe>WnMkK4o>K3F3H%FXY+A5n)()}8~$01`pok9;3W<0oXzE?Qrx}C z6OgbfPwreukQ5f*0Fwyzu0N3zf<8n@yCr%xl%A0Lh513y;0*slQ1``@YM8D6EE^ zn$3`TuNMecAzmo3k@#4!z4L;ZFt~5Q&x-g6g`N~e)@0mT7Gf2N8lBq(RESv$*1v&U zJqZ+)0!*$i36z=5*KYKCMoZ#&*0JW6Nuru4ocq9EZ(+NRrqprvGXLQ8i_?XM*8?tE zq$szQ#;Qosw3cSg&@9TF6Rs#<5@E6dL~_~3Y{@#&0Xg`WaxmRQ12Iefna`*Vj#>wX ztxxK)tnVLo1?`lC*x~p=sf?NAJMn=OCzqgh8}2p(S?sfuMFr?*H>Gq4;V;rrki_7s zM`ZeTO2?TX>Q5>l7L7DJb(t{=S08qd6ZF{ey2N(sxnEf;1~I_O0h59kMvg0&zTcoe zL0*u9pnO0RkSQKW`KN@FtPd93$W^VvJ3I;k98wNEv8liU%eG8? zuwNVYNiHn*G?Bol&a(Hd!*&dvkSyx~iaV%NS+GIWNzu9#)>f^Wb9r5sBiYq!Hc@Az3HK(U42v;#c_P{p|8^F3n$TIDeV`gC+h-UWNS!FYSCCYhp2)!?oO z26!vn?4D-P^itdehdj`BP@tK2#5Tj?H|Ju&ohU~f7yYgz+hhN$%BvTucv$1} zFU8XjayJu20Ii{;hhTaUk%)Zw@i2MGfE@fn9 z`QLI0R)&9h%Kshr{Ws+j|G<4m>`1*Qb+;V@cGZ93erQ?>9r(E_6Qq;*@hUWP4y;bv z_Xm+kaPhq1#tI4y{RnM+8HU;u#MJ8v+e88#hl#Fo5A~m zMTgDe=}g^bux826&0}Z!*+re)=&ov?+-`D4baUH`v>~)!?>G0?J+N$+%ucFd<{V|y z_T!yt$)n7ng_aH4`>|)S0Qt{}hPMa2)lTz!(Byt_&}q44OiZr|Zw+Iw52q)D_?Z4Zi}!m{HdUw|&$Q^g1JBrT+k~1>c2&TXt`y8OD^%g?f>Wu*-;E( zLYn+qAj}R1(x^YhQuRk6wBL727TcL7WX*!#?uKq1WELn5@=Urz@*&J`5#F|j(0+HJ z+qYmu01N^U17pw6C!szzH%3^7ca%%{28kfbxsqJMz`}Uxr34;)Fm7M?T<$oYMk{pl zNOjmyxaSI%L#>aeYw(r_;Y1L}I6++CA{AO3+D;6{I~FP(Sv|=Ze2zr_wIQ;qmlzPM zY&#r4m}mw;>4Jhg`Z+NZhvRU{0i5EH6B=N&+ik>tJmZaRWvOH5S=8LGr5~hpJXs$bMB{5j0N-*qt?!5|1O8fF>n5b}E6-yODam z%Vx4r-Vp_6_^37?^l!C9kLZs!NBW{qKZm>UtnBN6K{ARz6fnVrEO9x7^Z{LwABJo= zCNRm;E;d4pwNNeNZ#qH#{5HZ1ljC`J3S8v?M(n_RNo}e7H%bg@`^3AX`i$Uw{og|; z-AD#P(ZIiALPAsHXZI#H>Hf$@ZXpK0{z8ta=H!GC@ia!z3n|V( zpf4=bu@QI9BWKaA5Z#j5iSl$f@Ja*I2D2_4>Y)M_u4dQn-elCnh(jA1h!qR_ly@9V zAj?7$>kiL*XXoyh^3b52$IMdrpkEpu4YlqRmWehPt>3sSoH8jPzG1kH;Usyk?>D9! zr$}?{E7#V{JNUI6Q<}>sP_mh+jkIK-n%5Mb&TUvOHJa?B@XeO>D@=lIC5v8<1*bkW z`=WU{+qi^D#fO+2%I0fcoVmreVefY4DN6B>i5Rwu6P6Cy-bDEzv^JUcfLa5j>P5Wi zlGc){f;T6O(wBflt5KJFaF6Jc7X^GcK@jWiwPW z0Z9wx^D5Cb4OFw3h9{Q`9ra*ZQdG@(t1@$8ndBg%SEywng<7>bH5mSZR$ zMG8UU63)u-J-=Fa@dW6Y z#EW$2Q$F7VuH^tPZR8^`ZTq&@LK+EoJ-(Vz{HN&2cv28_0k5c&YK8%8QssC*s96BX zd96VW(t^p|N$Ds1{Ar1?q~_YNvoZx0mwOQI5@pA)rdZ0~Z#Y6S)j2axDw#)$bf=l% zcB%RLb;yy(u`bmd83y!#`JTZiy~XU!WMfjdLq2q;>^KPKRxaqC89fB;uuLBXCew`X zkj0e1)@@MY7S$$b+%%x+Dt#O-zfMXOU45(atFS(rSPJ1zZ7u+NvT5gKI3*loxf`2w z?u(%u->{hiXWzLVAY}@?1DAuU3Rk`3EYU0OZD`(EzGfpL`LgaZ^G_lAaSw1CbTjyx zzZPA=IsDy)Xu4vQvm;`qG$7k@(&KSRHvs5t<#5l`gr>^xsC+$(BwZ^1kgCplpiOgS zIgEP}E6`HzV<5g`AQL4)uDQa3zTiv~+>THDhJv{}hT{-lEeoGv3axBB5x63^OhW4+ zHT+GFQ*pSAyzKmKK*PAr3g(!^f$Eva^&y$k>aP?7{w<`7%c&`i;&#bWqKkhwuik1y z?fS(IuN~PoA$-W6njhHCt?+wC$btfxBP~mDANg@G6h?p|87Bf>O1FVipj- z?Kd5i!NxOgQgm6`YuCY| zc1(MyFy1!7EmP~W831GMvc?%t#48xrv{E{%OWUv|b$PHd<_rqA57Zz^J2Zu4hg?nLX^fSCjz%Y3S?-N;h?}$Q)Ft z`P3QW`#0z4+$}<6cWPFrbp$9T({m|q#sycH^6a-!oXaK$ZQ3E8s;(vsbytTDc6X&l*s^7s44qaAnG_V2XUiF(r#+KHA;dSOVxEVR)4b#bxkg(L}7 z#PqyGSAW?&r1M(@?R$k>4qg(Qf}4Gl)dH4Nm$kS*M{5LX{P#tNh-}^&-{9FIQC*D%6PxN+|Kobu`=v~4ZSgI@vdil z`+YgTUhUkz1jsbaTY^<){7+TK{zV?R4JntpEom7z%{23cK)A$Uo7~We+oJ|_6j9Bb z;^yw9hIHZk$`q_0{>U4u#g2YT*7WWB*zHmj4m-Ecw>yd<=7@s$XNq@#SWMh>KT+OS zTN=r?J08InLMD)7SpJ=PQDADQ2Dwnttx_02Cza6Hs^n71D3QjlsD4Kt96+ zvEx()JWxGR4EI35cL73Qu+r}oUg(fwdQQjKheHJ(uZUZP>GncVsGn;xs{OE#nWeLg z_Dbk-pg1cPFuY`hS?R(`P<6pyZ_u^EebZVI5hQR3ArD0NIHI@cD*+3zQz-}0Sw#m> zU4YObv4VCGb4m$lxu{J8NJYd9!%bxiQGkL7!XIjKsKU>xgfCQlA?(_pndowVo)GoN z9XVrk8!_$Fg~V1Cu!`WK9aj2tf*Ezik(FVpWhe3u*=z;--&+<^n_I~WG8F$9r7}YZ zrQTtkQ&ZG{Wv}qUtnxP0@4YD0tELu@-zEoU<*6X^D|tl&p7!>FLz}23vzO0=^V1_o|7qGiH#K2Neo; zhGGHEQ<_f$JVH7#%(t54R}1zfQtT2vdMIw&55kPh);(DU%*q;hCn#<64aKj%#1i9NPKn*Zy8$UqbAKT(2lCbB|bGd18NHqVrG2Luc*HVhqS7Cbh%#}LijhRgv3Vd*1Xi# z0$MWfk-X#&@rWS(2rLHBiwAO(G>IgpR)NYb~wQNvbv|0st<$tqJN*16o6C%|tmFsn3 zfZL!qHm({qt74zd=T9-K`tO@TvQ#;#@&le4paWv3PG}KRJ;CXEonsW%INSo@)K-`K zrQ1hSZ#X>~S_h7mX&RS0qM88mQ#?jw#cfKYxBy@7fX(P8{|J;EW+{G;&t^xI9X(00 z+@}MkxF!+yFOgoU5o-Q+t+@3uU}9EHplilZwWzOHnBCvW!u(s%oHjVcrGCkiej(K! zmh>?EWY1$x_Hdy$R5$F!11KK-%uG1luhucWqe<#M|gqk}sba_Hj1v#}JA8qJB&7YnyozX-qIQU$z-;(O~>`1gCwnTt!< z=KuFSTWBKH1)@#|H-6L@_x4X~70}M5?v&MTE|6~zIo}=OJ^YYg`c=f!e_|Z|xt{)U zX<}eyrvKjs$M`Sz>%TD$jQ_YkDbk5rSUa0I(urCdIGYHY7}*({@bW_a&w_N%KGKx1 z+xfqnO#64mwg@0Re0fqDP)7;N|I}IPhZgFp0GbLUW{w}YUs9$X?mXZ7_Wae_bPwhH{uYZd6~z0SJWc5z+Hk_p|Nh|yqE zszF+F25eEvOChAiP2ul;LizGG@ql4j(gg+C^Q7?^{IjkNPWY;Xm_85{wHK*|n* z_RosBK<<~YuQ%dyrK80J%-)Kh(X#^97N7@bt*k=Fv7zSRvcQ$c_eh#m#o#%*>Q1*x zP}2xv><+=Je4YX+Y5E9L5NH&-F&G&1lV=UvI8&H1=^E?vco)yqHEl9;9Q<5U} zm;T|>gyjc;P#$M<@4b=KBuLFcYQodXt{TWu^`?iWQMO_&m&o3L>gc4F=4`Gan(2nc zBZC~DGmT!T5Z!0&$0p8HPorfBn5p%cq%6PWiD)`lTv!q=2icR_>1 z?Gyz{4G(0i3A_TeC$z-SD_lRU3r2Xd@Umux;+7mW-1d~NVS4KifX3>$@gk!rgBMK` z9f)m~Ppvtmvn}+{W<5MlK#qL{oc|s<2|a9FY=;Hl^JcLQw-d|TC=j()%-!H&L-WZk zk{n(0jUUclIB7b2S(OOfwDA!Eb7sRZdVp>19i%($ zpVIyCIe;vU{ucnWm5*L<{nUa`}^`zYn!LIGt zcLn;l7Glqsm+#EYKzej-xXL|71maz-oGQF>1mamcJ0dP&1Mk&?W5Lp{w25aKuz*-{ z0WBIzp(iUa}&A>S*Cc%m@ zn*bPjTz(GhO@p91`lwFy#f@CjpI%rSJym6QQOfBs=Jd_UsW!VV7U+v+FLZRiZ;H+d zt~FWR=GUstB-f8eZ#kb}oH;aRINx+)1L<<+N&^e7`$tH6nQ_M)-RZ&8MoXP8J2=?2 zP%(%;>VpT#jy#o*A8vW+D#y58apJmV(&?Xs`2(a7)rC6MD6Pc7ne1TZRgKqQA4{|p zT6P8k(vpzO=x9`SUB0{RO)odm=ci5vb`APF7U3vr( z25yjMjqeVCMT)GWV;vqVfJM4+X$9oN#@s~UqxPufWy0Lxr%r+;jyvU({sAz6@|ORP zEA?M04F)zgwtrix|MDvS`%3+9!tPfzccQjg5&ZW02Im4?Sl%09rUq?omBzI;gLs2` zStx>a3p~#H_Aq0NC0dK0bZ7${e>c%s^gd-RxQ{>#^@aI?F@!6Kv#I+#$NxYK~quzBd<*O1)JA%Lg*=jR>BS`+s~*aM<0{l~>t z6Q_hko8p4XI8$~r=g;@+x4jp)ce!G!XSQ1Mkqo4Eg{`xI=Q%M>qAcAJYQ`^?9^TLw zYFPJUHeHV=__YV;#ZIcAoDp&jFcyCk%=o%T&+#OnLYq@n^==W7Ggr|J{X(tVCE^JG zQuBsP1qCaX%-*V6D9ee7DZ4Kh1qF_uOVkk$lDY+m^s7NcYR~pbMLu`f@k&S1R#J7$ zy4;x5Z=ZO9ydrjPkv&aG$s%%FHfoFggEVhYYAPJ8KGNcO{W(^wT*C=53~+2OHfZkb z`FIJ;oj?3w9 zdhDBu&8x(m8`kY1@=C&sq^->3;X@D9$o&!#knP3U8rCZ<9ODbs12c?ztTIqstzrnb zjr_=A!lsI7zVL{@+$NP6AvuBlJt-3nLULy;soVL%=%Z_a?TA#=BYlw5K>8XZ*j6n? zmjVGgs0KNixfmz5^(;FaVI!?u0aQ6hr=jXfj9l#&zzpI`CE3Ky7us0|p`^R8T_U)y<#d%FAFO}@@0s5$+Y0F1Pnsj!yaqxJ;omP$jQ37A0cy?TNjq9N_ zuPU|j6$&-_n+Kz%P(T4OW4xDBLfK8b^l=w_7)Z#I>q*JL5+uxX8|mk*1Ru~u$m%02 z6=2*EwzXmDS{xw}ic=R<0C9SBThN_ak%KVR@OhtlqSbm%*;J8-PKUz{=!EZ6Z+ z+QEkC@qN)+KRxAeK9*Gx#38Ba^EXm2SwnnQSL7mOR&F^%>JTrom&m=`R{p-$kk}y# z`$c^^gbK(=@Wki~^R%#>c&PC(N>r-j>`;uI(&wuZTUN3CuT>{>3FeuL&t@;&WHjl< zLVSBJVR9-dryE40%VHXJZIh?Qs&%hZr`$2B@1jD^EHRDQmD(1oR;H6|9NLvxA`2-_ zhDs?j)5H+N1w-dxq*K9Pq{jCnz^$GJ9XTYE^3$P~1hZAntlu&3J4~7&Z8sHIFP2m_ z<04WD-(HO>O9#|u2$ahJPa^m-SgkUO6*bb!61WwvYHEsx z+2z(lNMW&%*lkV2K4>kp{5n^_&&mnNUXWbuOvl*0e zZjSKISXcCk9!^VG)zYeoR^%8$EoW)sZr0)zmVq}JVJD?E|icC`T1Yo8*XVT5UI&YgA{KCzWlcW|spo7&moNw`70D#PoVJ)GS=C zE#yPKmlbyS7KXtFbH0qD}>qqYcXA_#~r zqh~|H@-h}39%&Mw1Nwjp60XW`L{irp#fz46Rj;KrDH(=Ved|MJN_|J9$FM1ZO}$g? z5Zhb7wV#$3f#OprW_Z)k{)yDv$|bNjl?OLk@%XxSOZ7mD{u{;W69-*&p2$b>rdqxC zln-_91sj^W!7NekZsNge?pN~&q(s>5`bxm@v;_E1QD80Hz8SdONF0JEC;db@@WaI^ z{)q+3|NO$gNbq@IK&F(A6k-VEgEtj<>%F$oXy4j>W39p7HUDSm_Wv+;4pEl2X|qn- zwr$%sDs8*cwr$(CZQHh4X**AK|Et&e20iHcJJ_?>_Y)CU;2-xZ=>Avz`;S}A$j154 z$I<^3oBpAH%>N4keOf~@@`w$kXSMd$JrCz9C7JR1$7+>ZjL->+{{_^$)>_h6b~6 zt;U69zppKsL1?WnP8D{kL4wVrOWj0tBW@BFYm+w?U+;x{8|ufq0n;2`*EiqiZ2>r>Z7zRK5*Plmq=lEIdx%4Jo5b~+Hw8w1bq~5HDm}OYbUp3x|0niW&1E{ zqQS9AkD@5z>_Bs6ajbJ843smHaad-{!rZ&@n@3D87L-M~<`UnEOcc~W96W31qQ`&( zIvUDbhn80X`uv=&XTOM~q?uVp%19Fbb{H~3vUDOyG7u}Ud_$;t9!s=px~0(^T&~$h zpDk*Nu=w3VKJHNDUP>R{3ol zoL!=F^{xo%0kJaJ;pV2C!cwmB=9o`lNVu?q@zZ6^=axCI*(ref67sZvV0m7E8#08b z($N5F0pj)729$0B4XguD((P9p^pFDZAcelXA$ZX+q}-HbJ3&G>1aGZ6q+I;ME0=hp z7**HLPQNe)Yc*PK2t=md>qrJDGW1TxVscL9s0$CUg8PpT zX^P@*CM9+^tI#c^uWNR)IG>)E76Br$Rx33q&9AO2fD{g4O7-)q@TNLl)>T&AIN1!O zs`TqN?~Y4%+s|YsSpjobbE8rV$u=4u%MPP5x8s}fwuiciaovQsA^dRx96+k7-s(g~ zYC0)PXb_|yNV{o$mp;)Bz{VQ z#w!QI%)?iNN?|(D*w*TZLNWoZEewz}Nh#OW~&+TDfpt?_Cb8qX2~gp*!3qiuMX0=HUR=@5f@>6hWn4`)PJ~CMxRMxU6$s z9tk*`0F9R1&`Yez{IZ;HA?S%FaKW9#ku*C|J8Y9I=X4iM6|pMx4y%<)&tYp5bg-i2 z-YQw?%1A+=r|SIli>yE;uLh8|QW_bMJz^w~-QjW?ynZx=?kxZYU6zFtah(TqJdQdf z+S+3s?uUZWVg^k}Xnj+0P@Q`0gOq|GG#sP~;!vC;E>@=Pv8?=I(vc>;?p>iQd(b!a|mgB=>Jl2kwV6&?`MA0?7Wo>AcbsIrQTSZXKU+ z=um~<Ru6bZfPX4 z#X+u{Ys2cqA+MySY#Nha;u-7K7s9u^58O;iWGQ7Ai+7%|@cXt^-LJ_WvNQ-3bK2@dVsz%%QNUxUP-6+ zjJeU<1moz@OKw~h=kQk*5@}3Sr|7b96NHKE#PiDADUq3J?=cFLxI}$)RJ>7wW&(|*%sX0|k-ZP`|LL-0c^bjf=toh|h5LhHS+1 zndg?aerp&%Jo^c)Ptp60FK}pv@09c}#14cUs+zg#wu$(NP9pawv%+Zlh6ACjakO` z`aA9KgDc^eG2qFbzJvd_??=PO`QYP++>{^|uAM5tG0FGK1|+lhhqKw`C7Janj49K? zOux*p8K}`7nOS^ubr1C1EiAad=-a|^hEHME(o&99`I)eFIr(0hU7ceP4-b!TFNFgL zaKbyFSaD~B=eAVvr*^gKzaAG@u5lT!KAAe z;L`CE^Q84$kShhEDVs@AGV77RXyEDxXF~4M;eOJUFq7bARNB=MM|w$aHY&{&SsBY2 z-VzJa!CC3}XxeJnY23}v1XxcQPjRGsOq-uECYffIEWGKJ6?%J$$uzn>o2MH1rEC+fI0;Dx zQ`(-giSR0O6Ea2?qNg4fNh18J)JF@1@`m=Re)K-*X|t*D2y<3L!s3TDb%F*_2!?bdIO83s{E|_P_JL+Lf4~9tKkYs5G1m?dKSrM zC+|KWaJAmoLY6M8DC-XR61HmoKq~7ubFvNr(NZ2E)wEn0GQns?kaq116_gNcP3l(}#vT!)YUm7==LloE zr7B0xt`LSuNrULPwuV?3B(!K5xdxhl_~B-HF6ZAJG^7hMyjX!+MNOyeMH_Ubb(s)Sr z#$Z2+O_&WS#mWD`&VTCBRg~!Bd!WDI853c~-^`U`k`!<}0nVW>AC&JX)F0;(I6?HQ6?CMfzV zyA6;DW$R*(S+IyFSsG{(l~N;s0HPa+umMl@`Of+j@4gwQ4r=Gz(TWyTW&UFx1LQs{ z+{*rCQ18A71}Yo<9hUG z!m%nz(dfy}oF^(^>tF%8@NrZB_7v95;R`VL*R3}SD!)KpT`aPU-a{)0y=m5a1G8@P z=57hT<*?q(75YxTC2pYb`ZXb9Y(I`dvWUDa#0nWNt*sgO3-CSrc`ZucikwAjl zCAkjE*Wx3(oNt}Le-)Si*mjJp%>Uq_G5;r4{LjG}9J*gOU{8nFE9_DcNM?KS0% z!{;r&II=N+Y!OX=+_X@rUb=k7)+iLqH#=dmbX=8SsvMdyPxT6nTU#5?0kZ?O_w~Mc z`xS{*gsIE#%`y3>YUaY7Z@e+<_HD3gR%$p`YP3|Lkq4Xi=l%WK!rj}$cT6MQe#2gs zu4V2tc zcqr`e=bUike#0|b2~WQ!grV>$$pYuh4?&26#0z1?*ZPaHb(}PCMkEuIivWVUv59Dv z!c@&(v*-!>{WkQYU?T6}%&O{xGN$ojk>AZWPxHr1>7=_0o z0t_se+7pku^vUwGtxysY3IvKvz9xov>Uz>ZA-74?or=z%l~!j$fLh*b0m31$Cvxb*JE5BH zbQVz8z!*+WpmM8}=FPkdE^-Yu%tDvjh=RxWc37IO5R@`>pD}x~FagHskhl#(>(Dts z@P!c>K0`Q(K2+LvinJGlz8L@$ZE1*)+GFNp5R|-;30Bku;)uFm=y_lE`w~p^8Bzkf zw~;Ol`;TX-w3_Z?tm`aB-qHiX_m%k>_C5Q^msB*L?rO34{WfC4(cEK8-0qgLeu1O$ zhNGSx2B$8)efgp{i#`Vd^LmuSJ-um7lC2uTr~p=&#!%MYsq9=Q>Q*lT9(a;M0O=Vm zJ_uaY9NTZeC8AGOgx<*6&UL>Wk(u<;5wYarp4E$Oa13jgc2x9Hxz(dfFe6kISOfD> zD~OUJz!)t6^)dDLrJG?Fegi6K0DnqmcEI4A3omh@#{jAQ-3B(760Y7R7QSwXmdb=@ zKB;2;e#DGQ4N2tEGS+)<45}T6&3ssfTkjOs&y5wViSt6=9u?J#96_#-PlJc20nMyw zHNaL`dzu#aqZQ6`zs+O$h1m|rtNp)QBy1|CTF& zQuBYX1^!S$Sf(AqDAjAcs_OpwhwVwh=5CWd#6-J=exl^3ZIuyH;5eF`FF!pB;c^~DsbW!Y_~;!!loxTMR$37Z?^#DkX*mQ z#Q8CMh?!+)`p$J(Y{*fq`Aqoy!|834Ndgo=4BSC)2t&- zr!gFx)OU&NG&TF#%_v06y_G}rIP>{8Jjgu0Ll<_nOeDL9ddKQhe_1uQ%VL{JpA`*C z7eSuV`WSM*@fetH{OOSXdvCP{HeRfj?bd8LM@}Xoi3rRa)BSI`dUU-8fvDZu?p_t!1FfJDFke^t%@ICG5bEFAy(;QuJ1%>M~_{!=yo7m8>%=48Z{*xz;P{6>Q$ zAAWs=DK|6V9!`yaU1TnO#z1$Zy}1=NK2jGs+s(LWO~lU5-FiCHrhH$XuV#kGndx?k z`(Do}@+*r!wYhM;-I?Efb?ea8JOb`?)qX#68YQK*zIaXPSH@Wx?Kw>hjjM?WjX}-w zi>IzlRb3gfz8X~gf4-So4OQJWEJal4rTIG>XiOKYpj7uyd{XRLA^xU22aX{PwvAZ> zr{(|Jro0j1mFMRV50X84N0JS}xgdHmpCt6JD3+WP$d0c;H}52LkKuY~f_uwESBe(! zUd@2vkn$iU)^uIqO!lop2dh!CIq4cp;rukcf0b7|7Y^Tbc+caRBqXa1(lAV%fZ)gh zh5R}`kow!0hetLK6^&F6ryQK(AW!89;0zU7CMWe)f*-YOg_pScAPV1V9nAM86?~Ul6gZTAEg8uR=TG4w@qnqMvSc?AI@DubQPmlZYw3U@k~q z_pS@I7{7Hpb8cNNQ{d!)OrUCJxi- zHg@acp`cbdjpB<)=-ZSF;-mr%B1m-ehBDcML~*E)?X89!ex zug}`8SH}k)B|Ak%P-9^G0Rqrgv*d;H9pk>=WVK^X&HX188K0tA>!F8DCgqIS1bx=9I(8h`FKFlI% zleN|um!!NYLr7vg91A7GK4$(s#=qYO{iJEbte6?twtGfg8E}`SuRKv#6yb2TSi`k& zW4ecfzu!kHsquHurnBr#Ll|Fa6)DOgx>{b*f=45z@Vwc85YV*b>ud~q+Fuh?(q04a3M0j)m7iT0YW0LPWsm@x;7b&5P z&ATOTpNix_ID^>wI{+&T`o76`3ba9MhEPcE;b%7rXTCBO)Pkq{*4eSVi9%7=GoQsI z8T01I$L}Z^ztXg??p-e8L?rG-I4xH*V_h4qafXDMeUa|4rsa6*O?_6)i7zEg+TfYW zbnjz-rOVNQNivCxE0x4YzSPnm+cQ)4FCCiU>>};?5+gxpIrBDXhi0SpY{eq&=E`Gl z))eMWrS`3w%!vE#dF^G2-VSbg9Um;8Pz$4AQ@1GRP1EOL@__C1ee4j2X{<{baNKU@ z*v*)Uia|-{NUh3!jOFqUW)3di@=j(B6W*r&gxXIOc{cdDO}5|ytZMAA&*_p=P2%&d zrN%TnH{#afk1?L6jc~fmXca8Wq`!eu@4WQCfl|f&U?PGK0F8U-?LG~CC;?mWD`WRB ztM_E{R&0QwGk`>wKC3nzoXfG7*wz!ahDJaFqzNrAVh`_E$p<^{G~=*ZVN8}*(EvL@ zt|S88_?-;s#dwe)CP}{~uutj9yBL-`e7(?5;wXBgAnuKPkUO-Om96s_5`rX2paS+d zK?e3lB4IDw2|tFeF$DSqaURjQArwN41q$v1ZXI#>aD=hyV$PC;)*;uf6}${$U9a5? zt6M@fz~k!Ucs8%BZ(zf#wZZ=?wf`{z895mKaiFsN7rG(K|3WwXXK|vBD%2J*1ix+CL)-n|?HvF6m zV8-0|Pc6QSZx_ZpcXIn&v9U)#ePVW-Hh#9}E8O zSi@8NjxFtTOBi&b7+JfDr|IL`lT@>9KWjStU3~qsC&AozUYxwc z+0q@Hrjpu?xzYt*oT~GmuCDED`cBu%tY+&@>>06rKGRyi*EHrkNE)NFm*+ggUU9`Y z*b2rm{@DTAYNF`)Y*T%cGmVrWwyg!O_lX7*Eu2=3VaHV-26TfI%YLv?4pc0f~g?ReGc z`$pAuSt6zmlO`oSkqYFMGjeW#rby(z;$L=j6NLb&6JA3m86kpa?p7*>SvC(H$`1A5 zPzh=MXwtZ+7-r*3W@F$L$sjXwSm_6$gX7BKTG~0Mfe=a9~g!R~T`W!yjQ(L!?_rRxPM>k$Vb7_U~b1+_7Od&qVbcb!{B`p3bK;yM3(&G%-yBj`sgo=7-f8fTKwj}#+CX%26?B5i#ae=+cf_$Bnc0VPp&WdHSjHvSD z260y zV|Q?$D>x~^({P<7TDbTL8jb{r8`D57`NBNRt>jcP2F|}`zm69z^--oHBV}(Uh0R>H zP7u)Q>1)QkRdLf4Yb?F%;G~B;G0uMtviE*TRh_AVq<0Uqd?nFw6j%mY)o`OvKfboV z=@`$l6NJ0UATPps6j3Kk#M~hrb9Me&U)z)Hs_Q!Wd%MZ*uEEJWCBmN9TLRCgjf3@! zmT$z_c+@m{=4QYSFl`dtQ?X%yn&2!LErS(M9`vQks$efTFFCt#sx%6!n_`RT4udfe zzJ(1Jk5<6yLqWgtRapvlFm}#d&sw5EO#TLxM80sH_MPkdfckG@@^6Lsx0S}q$ol{9 zHkSY7FaL43vHUNTtZq%&*ey1g?!T{{?R{;y-1?;5<&V*NB6O}~DPs-T|JYDOx03bClVNufvrl7*AHsCXm;hWwQyf)UYz zuBjany7I^Y{GhmQW2Ce$b)^Gs0LW)}T^t*TiSHIRUo14=iqjKdCbO7%1j{ugn2;cU zaospXOCwFQ<{DPF^R~nxuKPx{0+`1=n+t!P@!+;PVW%82kH27@{rL5Y3^GwL?UTta zHOB8&0aK{ONnR15LkWZC{wePK{S$T|=usV&7_pwFNLWlyT9*a8-0 zmAu3*rM~D@u9zS>4~j%~fn_Y#%p0HzRWPdh{+L2903HWa+Sdv?Gv5U^ZP2bRCI(PN zV$Z39cZqUHh{1V(RxuU_Q|hA&OJf#-Mq11!!h)BCJMO+#u$iSqgO#ky7BWeU@KiF0 z*819d-Ef54980HWcgS=!U(M9{G3~pH83z<&#a;3!J3@v!Z`(ckE1a#^!PoK@F>_>| zYK&zOb{a_@;)GY^*5>ffcp5=8N7_!xdm=ftOx|a8FAUvZ1H0VzVJSHctV}0EFb_)* zXydIVf%Gt=RBME$W^wi_^_XY`QE?32TsaiFj0jE}b<_9XrZ6m{2(~=r3p3}8Z)BOK z8wvJN1`VM8*1Mx+#9~?YN`9ci^(*QArv-B^OxDeNK2aGue+Ff_ouV=>_yoDQHqZ2f z>r-(HA`Tl=ou0bD@t_hTsDbl4Rjvd>35)JA{G@g}7_*92fhj6;#) z(Cg2~@-nHo(s0oW>p;CXXSbBnp47twy(0{eJAfA@$Gc&965_~vCy@D;LK3{P;VP^= zV--(dOmg{0iC-W+0>`)O!>hmZPgBMk!j=vv^$qAP=7MOgD9Og7n@hys2aBzbP z4X;MJ zLn|6*n}?qr!=fIi3*`pSj%TXK0L7&^uiYA~K4dsxAB-NZ75PMb;qJ6h>0SP!jV{OV z@L@2tv0IR(kUsroJ=<|J-dSL#FtED?RgafHauDR0u9HDEM-rl8$W)135A{dEX`1wR z{jIbiCmu7hg~;ZPGu2j1&@yoKwp3eTs2j-c1^6jz<1xVvb!|f_^Pz4{vFC62CxXW7 zJasl3yj*Wb7W`^vBnZLD03`QW<1CYNUU)EUy`tz=T6~L}8(yMhtFic);MTLboIVrK zflcHvmzTzCvAVx`R-Fqbcv)~G_VhM)oUjdb@Wn<;4AuM=+t+V=U47dTl^W>Vo0{-+ zi<=My%stWW_Ckbv6&3ezid`;K${0%FpTww9CwIEe2ejHQ=&-LA;%#r?el%r>L7%cD zzmrCC{+^hy57^D(wbmkC3TIZ9iX~t#WRtQ*&XGx%+X)$s-9>zy`}bn@{@1$U=HCm% zU0B}no{3d1#90B5ABPX<;I_*z;M~aiAW&Y? zy@fSj^NDwsEyF%sxEuH*ZHB&|M+R*`?-U2s?;?gxKq%=ee(zvb3cB|gbLV!j1~8HS zOn>Y^AGb%eo=`{4K?U_2@SXS1;1_$9O;jEJtMdQH&}8If`A3L`Oe=7g~!b!UN zJA3}OV)5_(y}n9<@Gp`&ggUEQ=%bVs0eC@MStt-^|C5DYy@bKhTf(ai=RiCl;0DzL zBM#=zlT5_*snXt??~Jt}Q?jWd&fKek(&2-@Pq)`T?(ARgqo&kJO*P5Kbr3JRW5MO; ze{Ni*W+#i#c&%6|^UW&B2u|8G+X4;5pkAKw__Z4Nd z<>Yhu|5oPVKga0zJ$eV?DSanXo(N*5UcnJ;293VYYK5xr$6yn$zW`&lYN(o#sQC{T zKl^Y1PZj?D`NArNV{RvOPG9Lof!{YVFSQ3)Et46TuIT14*&|7Gp|@+Qqv~2~V98Q# zAfVC^yTnhmQmC1Wja1nq9T#TZTL#j_2%CI+I{(@}KEEEEL)gp8V+2{Xzl&6|QTELN zUrpKhsC@jxjd~vJ6yTEK$8(v3=A!)f=dA{EB3UbdLwW~|l@Q%%&+1z$+o14XB+|*G z$d!5P@a~b(%N|a+EKeGs)vDEYN!DNMo!L70E}a>|{@u^^Nyj2d9RZw*PU)ue z)6%k{)@4Jv^GK?0)PIb*(%HD|{+i<=y9UKXycr| z%O#;I=G4ct05dcU<~mer5HFPagjLGvdPhaMAyiEjMO|OGs)I2$Z6C+crCDGLysY~o zrHD<+If-FEtL+5?CwmuRdsRbsIOH^Rf5kSW|V(cuZ|kPJb4bOyLHEE_6= z_;U~G?-)D`Z1ko$F#!XNT75~3@oi0u$1n{_U9yA7lSb?_2|=UchawsjuAXBSL~Tm8#?_f z`btvRtg4FfZ*cLl&w2`(8O87|P= zyMoZq$0n7m4#P9hj!TAf!$K#~q!u^)I?;SDBl2M`bP<@O@_p;!^88R@QFl1bw&(Y9P_1{o1tK} zi3O&&gjG=k&vw~P34mewt0ozd!z+(t6O45w28wMN;aa_@C#kU{dC1TuI)3Z83IvV> zL2PoC`((oN46WZC=32;hzRcUf#HRj6JF+o=EdB7K3k>68QKOe{q(>b-SPSE)=ag`sxeb zH_pD)oA2UP8`o>=Y)`X@Fn5ujebeO+B+El6apLFQY0YO(Gz~QXL+AZyW7_QU_!RHk zm!G6Z7POXc+Be{m`RjirDX{#kE!k*r5!(+XV?DPHo9#?4`Fu2YHjlT*$8RTZF7LJBswouJwB@Wh(oa*&Cym!vWt)04Qe>ah9793| z$52n@FcjRXXtL`Cv+`k4D8vq#HUhF>8FK}g2-n^ysFu;vxbt&AB>jbWhA#00ssT(O z0sUq)w+10FHA*mqHBMkQH;v!fqTS|=6hQs8kP70c09#UAl1(ISV|6572ZdC44bDSstw7mlcOz(iOluR1qRC$Wn=p&bQW*QkPP(M zv4*mWNsGnFm}QiAa3BeUVry;{ibVp#hKoo;a(yz$Ek6u2O6YO|n}s|3!usw9JT zX#(j|!`Lsb5`i}wB*czONBmw+mt87jF0d-bPXs53@~@4SajlaXYb#_dRP$dTKMExR z7>>0O4H>0Xi5bwG1z|IHt7#|}CXA5I#NZwqHy^Ln(9h>W335}dY)YnWDdnP&?hpax z<));hDj`|Gy3XTn{A0+7Ls>>4B{40^*s*8p7-s7o+1k@2Ig9GdI`dj0i=oib=KmrTa9N;szo% zCyVvWoBz~!bfJYteV&1HWm665p2bQ(qUEy~+Dd#MHM~?zKlJtF!x8YTFbyjqS&q5e zg&AG;12Q@8w(%$mr$I*suPd(T7P(j?phkf8q+S(r@XH|x79`&39Ah&5rgDPTM9A_( ziMRkhjm?o7ol6pfM3sv3{8pjEgiDgI%iZz8G^T#f#*KcOyA)0>R{&$X3LPBSgY0}} zxi!FzO97&*?_d8?lC?IxdU61-nKG*AjHk@4W8|=ZO4)eoBml^(!Ym`nA=*$45snBBP@} zU`%q1O+pLvqoO%r~v3S<-W-cGg213mZFHN#Q70rNrr2CL0>{* zg=d?She^-(Blc3e`5gy&WppS>ccOYk954wS`nm`}dZwR6we!Fxf|((;j{D++j zPgRhjV-jtE>G7mmT!aLf?K8ktSDUifz1ej~9Xv%01u2HTyOZ@TvPI3iTvA~?TKKFI z>*(&`VlsXC)W_A~Uu&%=e7$UMCJntd3utXwX5lf3)!8ieJ=l*WK#N>fuxmCc%^f3( z2F#JiHIq$5%(A}@wKl&>n3{RLQ#;mT2+y!=FPq$AR;*6Pp?g|Fn|BSASTeE7C)-Wd zwmOz7LTW;00+}W{e>a}d)?`lF%im4dhDOUBSE4#@dUBcmSc9v=QDj6{Rfz&jW`!E; z1Z~nzkiKrH^c1^LNvvl=?u9PN6+g+E=z9s{*40u2UlEEV{&*sdA?Vt}g@dX{aE*)= zbEv2~doiI?mt;MWu{y&OB!a=HR1~EbfoE&Ct9BIzN~HB})6KKkuRHSPQ9VI98m@3< zevEo}IS}MIeB9SpY+6$$sdY{j1j*~YfGF=qNXI0%8W-F~iNnOLRZ93*~B%+et{RnFmzy-`YvlQBkbg)IGTy1bZNqkII zEjtt|QLr8eZr9%H?u&8QjsN8*cnbH&3rvjunV{VkN6fo6=`eI?tdE1we&X8+>DX`8 zBd&1Eh8o)uru&v+FVvN3#Lbak`Sbq%a?8D7!wwME2syGC^NMw&%FR7-^~cc>-LtM= z^gisKvzOFk^fnNVIn`gclSu@FOt=H!`Y(oTXm&c-ULH&&-9*3x7qbXFwt{KSN-YH9 zuma{hrGpxH+lZgNb%PfZ!6XInobWSr-`6ptIq$!n_5aqz|LexE{lgt*{ZFd-A7?%5 z{{$n#8jm0rf7aEDHw!#kV@47YDlf*moz4m1#Mm9dGuIBeGx71&9f~|2u0YwoilkJK zOh>fcC0v}%sGp_`}T@vf4g?lb~kucGt1q_dZxjRdUzcz^IWTj)ty=d>}ouO zpWxvA8g0{L`SN$li!c3dZ+LL!@mFguz2Ua}=Rj>|8$@&NQLx4fAc zxYo_%zb}*%BRY!|v5ah4{!F4`Y*^!g<;J9e%Vz$PqJx)8?d3b0Di-CTJ3QLCmlSIi6WVc+BaD0)*TFzQ5A>p2pILWgwL{?l^08rFf%!%#k;1 zyUe{moG5X%H0bAZo7f~L(w8{bQO&@GKOEenMVpd9%f&TMoVpl8wH+F337L2qj!1i? zP8q1>80C%(t)z9INcM7|q@kPX%TX!iSoyC+E^)I9;T$^b=S6}&fXz=~CEx^3G;_WZ z-89s$8u4!)aqdj+0!YZ?nCpQXivX!9T`-&nyiqb{oNOfTw6+_W0iWnd6=znha}A+L zC1xg)i#Dt_yONDS-4t#w?dvE4+YQP#@zz*vlbW7l^&BSc!n9h`3Ga4J(x!qTY1+S|8lfpU zEA24C%WnP31=2IAI+DOpW!JVNl=L`U@@ka?smhQqz^G7jiw>~*>i%dv8yH8%P1rw> zO|R*ou4%zbDLuGgcXXtjpA>eOQdrGtCvC(KCu(}Dw%Q3p22M{@BPFR;of$=A@|H#} zWhzj0!wA;YN4eKjYOaLB48cSN$nu{n@umnk*XL_VZ|!_i)UHG>P0>+sp2Dch2JY!-V0cOynH-~tMYnhQ;rEU@Ia1l&8e$sbN~aF z$AJZuqEP%+J-t)5FN-F7b>|X1dYYublGh?VXj+OX?{4g1GGI$tF)q@inrbt>ln1La zor@^TxXdIXOkjgYnvBdnClG06iHa;=-vk)XFuP#9Z?QeP%`gCa6r_C1s~J3T1cV$* zQxzeTn&@s!I30(;ePR|hFu+OXqIJq>6bfc$lTx>|@7COT4BsN6qo=$f9*& zDtpqUl=W7tk)3-#KR#Mpg&TK5HbMTe$)ccJ6Kk7KV*F23VC$41KZX3@Bu|K>$)NQ~qx$NJ#iQe`oT5as`OJ2TQJ>q783`P+Q=ihf`=T^~XnTL#R`K zmGEjzP4nUj`%}M^Bu^;>&FmZLG<29J)%I;y;0czKgQm%Nw78z)-Bt%ZM)01 z?JnE4ZQHiHY}+=wY*(F{IcFl?vtuImd|UtE&h=#GmDBY_w0o@`n!nZ(==6M1)apx> z-Q(~{qYq%A^{mM#KeHV6mcu54dH{Kii8c|xDQu!<()i=zAqPXxeYL;ftyMt^{;O#F zPuzlund2Y0E7t#n>i;R){ukWUj~Dar&e`)1?js+!i&p6O=?chG%NReLl+79}O*;S6 z5A7qN!YG+mW8oLG6p}jFW(!U7-{Oi|3mT8L&#w1M#B2bbyE+wrt!zZ%cQ@oo$rg@KB2YAn4T)$xUD}T z&B+5M(VJa;uH)*q^(qv6Nn+ub&zD==ia9;JLzEhP&l3T?yEsNZnOj^UXV^j03i#fD zN;)K+%X(#C9n0K+e8zQg=`G=8Jp3*1WLylz2s#G}dlYoMhmlY@Lxh@FXi?2Qz?LYI zE=K|S^7|O+6~>Y!0-Y3=kCXy2r!|@LED+w(*s1w-|4nV|wd=~rR%nF_S{fsQ6zHoQ&Tehz@vjrJeIQOA^sa zn*M=SXxm_f)q#E5x5f=wSJ=5q>Ap?OUTM(-sePJj-6r*}X)Be-5c*9`Ra|c73geR_ zsFjvqVg@htRIDIogQS;MZN|;S75!q$_(3`&Y|+LTIhx^4&gj2U2yG%3M3B4B=V^;U zixhlDIC=nwG7JlUGh*cn6Aq2bW_`eOxTm6y=b%p*O@G>_y3)1>$%-*e>M(Gxx>O?J zJy8V3thr`_zjSGmARW#1C!StLPYt!ZwQ)KT6HT=8eQ!K>d7kE%6P(zj&N2eQC7bDS zNio1^+9M%L8%w4i%i*ay-H<*O(9l{-cs%*mu{0#8U->YW$V(g#u`0nx=WtvRKEt7{ z!lRJRW-?Wwg2{;nPIo4JsBTBcL`53>dobI^)598dB4zG#L`i)!z(sq)SEr~e;oEFq zI1TZ$Upz!C=#Hk8R<8z+i_5{Hajo%{9;b41nEJ)k5<(;MJZXebOrS{6WS?WO@rm~* zF3kqO%D`xS&Sy$VoaKYaZpl7_!HM~Gcs+9LF-``0`z#G5YJJs}6YII570aNU_9!*D zXc79CK66(Zk4kJ!r))7(~yP4h? zXVm0vs6uxBab}xJCxskCYk}LntmN@5*^^<29BxzsOlr^y1UI~!{QmK>p<{~mQ%{<% z=;?A&gwd?vKl0(M7`uk0{-Mvo+=_d>)dw0|{t8sh@S-`_J^VwlVf9g zF|gl*xST6i%FWk9;K-S0av)0d1)XeojIU>6)d~2oVl`LoSv-yiAW*IdC~suI&npbSgdZgd)~Y%eJJ#rTk| z!D7@$Ygz^R)(&7NdD7H_)mKNA2*fle?e#s zVNY0)u-{zOp56y=SDRM$!MI)6>)U2xI6(X{X0IFczxb)7Q6+EJUfp^yhTJx_D_68u zNM;zDEcmj2|7<`_LbE5DSZ%AaRbzjCKfNBe!Y}Z#PjuRoviq&4sdIj?JLoJvT-IV2 zpEzr#=R{|qtlXE7O`8H<^UCS*&RUwR(qz_N&fkECP0v7Bm8fKB$j$i5^8E@A?`x&+ z*{Kg*pzZk$#D{Le_ZYro?}KEMgbiKa2cUP^i{wwrea7umKk!xTgpdFkQU(F-dM?i1 zn{$IZ<;C@R`*C}G=XF0OAx&$w5dUJmU6KrqP)?d7C#|_}FX1vW9VU@uGxXE%ACJUE zgc9QrLLfvul;*$tYT6EW*-ggK%0p8Av6VZwgfcD3JaZ+0xyBx=fUm;3rkDv>n4Q3 zhz~Gh4_Sdq1T-==;b^(R-pGEj0EhHF1!>urU7`kvk14dfECTsXHq(AUMWA4y%3%>& z+IvJp%$9e>Z7N?Xz@Ut(DeDKW3;G|zlSGBos;ixzVf34LM(dl;Zy2V^6Q-u?iI`nh z;4UUe)phfFOBZIJ4VH_8Vn}4^i2n)V7idW;cOOwj0bhUNs*JNB-CH@~-$ujS$H2a%zLU?CZ*7Kc0q zLLn9>`iU?AJ==~Lu-|3btWd8`s_~+Au(NcaS%&^@^)HuMlTYJsJs-9?c~||K4$u0F zOx7_drS552bD0oD3PKL1JlcI<>(SevKTFaI`&^eoK#NL8dxnWIX=xt-&Fg!aNs@S@ zLBqaHJH~?N5yUVY9}TvhBhKjS*#xFTVwYMoMVm&^x=gUYU*k>*!tL&NA#7*7XGNwo^CW#%d^YTZ7* zEPGG)mqy+KZ+W-94-JG#r0 z4Onq+k=7tCOriEYo-KGFMKWB0k^2g(^a88RPA|K%Q?$BdQp6f$r4e0(zf&!|quN?C zjEN3^xsK(r%2r@;(~JR5s}34Fpk}T8;*tfu(S3Ee{>ZVh#oa62$~c1)WlIhrb=46T z5$7qmNia+2$mILGz-Y}UmU;TuI&MIfnTCK}H4uTsq>T1q83!Fiq2cP4${Q0@*+U?S zH=aEKY$Z2r)^q~@ECVTi?+ z%qG6z8ZF~9y9JIWxe;R*)&{w*7szOdYDBq;=o9bU%z;O_&56@X2yQ}&sgi+8C^y=+ zHrP<1(JhI)>_A!3Ez-Xt3ZCcqt1F;Wx8OolqpFZx5JEw}HFdalszWtyG?=2Fqc?$(Dus~Y4vX3_<_y_$x)h$h< zQvV${)lFMc+V=W&O*a0SjbK(IzK$lhGLySMNaW0NF;4n=WmW{&)pWQ-_yH9#{7{PH zWcyFlKvLCT-c(10G>^iCAd-}_Q^4-KmN6$qSX)NDQ8r5@u+9V)y^`W|1sYchPkKpt zE`>rUvW=krok$aA$=)_0qg$`>3~=~2##2A)DBI$_lY?(*lHQIR_&vx*LGod7|;>fN1Z%R+P~-IIaLxHQkJJ_*@xy@vCnyFv5Urh zwv3l6mE$NZjbXMwRu<^_h^ao#00`bW>dz|QQTpz}4*!~N$bN*EyI^a`Wq{sTe*GfEwS19x&eC1l8)`_=XSZ(Km#4=z9#otM_~J<8;W(s z8(6-YL2T!FA9K-U$((xl7$6RZFZJXCx*9Z@kp^N#CFg+3K|!j^q{LJ= zz$Uck1vMB|nY`m zsJB{1HLeHX%&QGEsj&%bY+SS(`6Ma-D3VGmxe9y?B7fcoBGF&u3$8@53Bl$o&*L>; z(A_KT*96#@9J%+G}yA^9VzmK5m4B z7V?m?{omL~n_cOJyoJC|y}8XUH8*gX8CfhjmDT1_1RobG?2?Vcdh!RxChddrz-9*- z13|j6fOz-^3DiFmlMM2N9aa+>^;WgAq7&)&o%C(^{cykb3ZKV&d-kMw*oM*|xS(zU z(jcW~m!Rz9-QI=w)6YbH{UEQU5fby-%ld5wP4kQ%i_j76_pR_~-OR*+h2pr4b)*ITY%H$tO6j*drgHH!k1Yt=WrnGpyJlF`l${ zNhX4Ta`)wf2}peQI^RLc=7m@RsV$CU*pr!~nS?MaLYElh$~YRXrz@aKPadPt0vcWf z+w)x($PHn2N*RAy-aNz=u8WpeuSjgyKp2BU_i5&oB_|oin*qKX-g0CKD+ZV_^~75w zMd~>h(GFoHj{9pb!P0OY8_xmT2^<&zHopTLwk)K{RRl1Y^bJBX4pzN#Eb{5jux)!% zqBz(Rd1Hbr9pYGwpi((sD(H*0A%-;;M(sO$Kxv3jAvhJtS_7|GqA-@McaSG+a>ZTY znv)arW^7zuln(9_`KkqhT-A(8%6qOYa1%Vf-TavG<1Hf5#le`%7UldGlY>Op zPq)fN95#DY1}}fT83cv+>f#EP7`+*~FEX!g`M1#~NT9Fur37d4b4V|g|UK|9B)jL)n_<`-(y{>*bs zMF^YUM3oURLTY^{H&lStetajl<3Bs1rbob2^re`DAp!&>0m@Hx=r@TFcBq387Tche zvM4I*PI8Q5@fWOJlnT>co8XX|3RwrVy2?ntgs>X>_ z-&JU-YgrYod=0#*yAlObX=Uk4UEFQs_V$&G4o>1oHc42 zl8@uO^W<(=j^G6m+H$M4aB&&o#U2@;pQ0o#OSBKUs}2W-#J1sW##sz@4cDXctnCLe z6x@Rh@uFx8woyB#CClKZ+x6O*yn%(e9)@aLXy=@?H-t-<(lVvT;GseY-aL>1JJma) zjA();^nr3V^p`KB#;W(bp|HZM;kgN)nJJ_&I`?AZk@Q7MlGu*RVlJqc7GNiN1A9_4 zY2JDe30M90*&^Gr4p|pvSP9;gcqiQrE9zP7d)G)NrnsBvq5{dq+-W^SEvk4%xHtJ_ zD`V&m$!SRe-wUXCnTVG?7E(cEf_mE3veUeVfa$me$mc zqE$pATh3BT+xEFDedU1ZSY^|c{Se(n!!O^n@#NQMzNv!P+H#Pf>vPv9Z$s)-b3L|9 zk+ZSSh2=N7mdS^SnVh8W;|WAhzS}cspKj9VZ<6yPS*>*2?+hPU&s6A5ltP1;e$=n! z`Z_*7Z-OU9FS?e--YEzWy?xtNOW!9FRkp}UzHPYIXGf!B{BC*d9Dp7JPs(mH>}-O# zC{u{{;8*;TZ-A-8usiQaK-qX0r82G)T48F*A|sy4P&zb#$;I zT2=LqhT-_V$yh4ZvrLz1QmKLWt?lEE0IfE0kQ&?i;e^t<@4$1T)jja&mDDQuk}LQ| zjV$ISWO?aHt>c4>*|P1bIMyvvCNZ&i4sKColWnGfW{rhER%Km014v_v)S zIOzS2!tU_RU)Q;5>-s<`RjHbBnN+_8XdLBG(E+!j(zK}KkA5+*Q(A}8bSA#RFQ>H_ zz@^F$7PUp)75rVyTOk}}7h3tm`H&!X+8CsZMt4)xcNJaj(D@D%y_XkkP!*9oKKBNWJn-K?14$iALgJ0b( zS)9cPwY4xKzMW&=qNEKG3k^8al*K$ovzRHq@ucF|a0%5!(ZnP$i-NY!OM-(mUph#C zB4Korym#!XK?~&51Dcii=?9PHxKt&%7)|! z)j0ak&$<@gY=RZo1d_K1E*ccqX*jok$OGM0bHqE@DwrA09*~K25>pqvnMxQ3ofW=b zt19^y)C)oeMbk9a<2cKefI3m@g?O9Tn12l2U^tQTj4KDqguKRB%+JdD>v#{&Yg~It zm`q1OJ`{a$qzBLE>zQBPmg+fjY_wKfIl-cLLm5&(a7J3hn`%-UP$r&4X2ZB6pfn(4 z6ItV)-x6i}zKWZ4y{nqa@15vo7AvvD`9bZHzGyOkesHc88=`<3XKj&lh!xL@=)MrS4W%E4xR+^G2_x=Mkg%Y0Oo0DVfD4(Y zjDmw@L?IIt8W~i*v%cJnI0hq_7KCTXVGG>(LQ_NHBEd~I?N)90Q?z7;8GZ_TeN7V$U%V0eT*@0ve}O*C>5W|sra6zMK!vi zvE2D}ibhtMg>%i!RDFhu`O+*&#LPO&%b3-C_jsoEa$7q=WJiDgeNB~Xs-g}1H(pP* zM8^Ez3n(t3Y!G;(Zbf$!k|AnSFE-;Q;`P;#lbBWK=c)s>u$0|Q*Ie&)EoRtmb>Zx5Ou*TgGZ4|S>z>t}HDe$Rit z$uL5rraVpj;Yw)=u$Umz@Q&1+IH{%l$U{~@7#Wtb)NG=R|4w&;oFFn4T$n2o&GB(bO39<}ou3g?@L z1T9t0_?DTwNM^C@`8mKm;)0ZSmXMG7h};sD%tCx(PSu!SQAiBvhe76<6r5bRh!xeZ z>$?ul ztM$0UUM*#_utbSKoUnjg_qBL)?<}riO4uU>HWugUl7ro?-oI{3LknoBRp6KAn|cNm z6!@N8NvH%d(~ISaf1TuICt{m>&Bb0wDf>#Os!1EJ6H4-(uf!Y_3Kr`~HqTxtbEY(- z&Y#Pl!z&-BMC>n|j{jLX?S(%pA}rbV^IfP}kZss0N{DArEi`IajWYO@Qvgxd5sg*0 za}pB=1kw^|Ew_NN)%vVNL=wP{+9q}?IsoIfyq01|ID-QJ$~7Q))pZG5(};e zru4`kO=|ue1Iau5GcS0T-Cx5lhh_Ba3Gh&;}k>CB(-_J~#n*QI(P%=n!;(`iu{QLMZ`)kag zw_7bj@%WhaI^UHtww=TFg>lgDZ|AqO29V&TyjwMVKJ4(F{R?DVbKMAdxrBVE#~kPYF(#%u&MtCp$1o zcDOV|9Kx}?IIRBix%``e>8jm$Ml931QsHo-s&%~yempXLtKE_zV_u3?SpAqQlYZ2y zx0i|4?d3YS&5hH&FD73zxbtx5RA`V8)apy$b0Ees1lBrervBSLwC54uK*ptLrYR>P zwgn73ijOzWE5g-o+wHR;NchbTXIs|z#?mPTup1?F+;1o07ph*$lo13;AN|7)yNG{f9ez6TA zb9jAP5vWk1?_XCL7h!7F{KL@V;P&MHdIyNFWTVFDbs;f;kK^Ry9641$s ztyQM3+wGjhCXA;47D@wreQsSD`0UR*3J9R(;_ytLWdmry8xSb=;oS!@`1H_6HgPQv2%lJt zm4`wiJS4raL&V|09A&_tG<4MGkrzX)MU!2m$N1(fnr>`OTCut~PyErtf(GC@%O@=q z{*L5|{pEJnJ^E>0= zB~Y%RTYW;zZY2+j+~f`#Akwg1Vj3ReFoz($#m9jt7{x#+1L0C3rhsgU?152#Rho7~ zL-n8qC2x8&P;s28%|Jv2qKhsjDZ=QJZWIV}%IrojU-C~zl|7EHAdbHok0@S(3G=ka z_tl>F6x6B24tY?81osNHd~Zure^@1C1XOaOfUGidss{yNa_GUS*@WlJ@TGj(kYuPx zj=G#WoDLJ{74pzC+mMLP<`4pTf}3jmzNU&N9O$ti`opxFE)jRwmJyXCixtu0BP*Wz=E;soSZm0kgUC+q`eW)krWr4c+21`Nq*=^Zv z!`lYr4UV&s0mNFbul1*vGCS}iV5%y#E@zY#gebR)q$3N?)M;ENT>FkwZNxyT6G|fG z68HB6{KZZ`iP#(juyVvj4h@@37@)!|)L`=1U%G66+%-#Ig@S(19A9o(7O^z<`-hTx8HnTt51^ z6N3m)B;NF@%&6eH$SfT*Kx58T2_EP`=hw?tVZyIu(qD%hr|pkqPUo`QMI0&NkAT_{ zztZTA?NlJSRBCN3*TIvhuN2SAKB^ndE@N@)j)E49GPy53GX1$vs2Z-MjvbS!L`Ob1 z78w*>y$yc(^%-EM7)A7DJS2*TK`=k*l7(`IQSfX7P#Xcc5&MoWT zq0eGX7Imd1^IE*UvVD7gkqC1;F^7Cw@ht(2AZAV>+_yL9GP(kQaPT&@MTD|6?`URV zLe{)@L7F^~A}Q74U=TO?d5mXSUD}?Hix>y`$Gy5(gmS%G&`u*KRtI-(@qzoJorzlU zj%@&`qz6l@Rlw9Q*l#1TH`rdS$4I|y*g7J-SG1bA?Jp00t-s46nEgk}V6DSOm>)9; zB}_s1RGlh(T4`f`VxpLFcs1WmAMgEpJZEh_@m~h{`LjIa=D-d9EP+ z!T5W>SRw>s1ZVtU{4qLlymohH@ZozpF~4}i7%41)s-S)-{ypRe!MHiTeCq6PePG-c z-0DZe`rySkN3RDrXRvaXg`z!t)&tWtj9`3rldp&?vVpCJP6G$F1Mu(`aVBmb4|}rd zuVvRs*;Cnd_X^vP#AH8%plz>ZSBIZf2gI|TrE}1qzxEiuB_e|}h(U4DMTwBqPhdiVXttzpl}DP1cifMh5oFK`w` zv!`X8xL+pBZ*-gB(@EJn6zh0^C8@A*9fUB@s;fn+%uAp%Cg1x5;gFkdqFN}I-?kvQ zxnUID?bSe(NWpuO*%Iuz2mQ&6F<~eY*oig2=&~%di4+9Hh zj*>t*!bLx0>|EWH1m6iw1}j=9RD7B`C8KO)gc9iphaI7{V)wi=oE(U2Q7{QZhP8cT z%X5onS7G^NPxzW>>VxE?MJgFJenjV=W+pXTvuNs)y37=gj3i)1!}N@b__2g#>_)rt zx*;0wfO3E|Bdvx?4>07jscLF;X%*7SVU^_i8aXb@@)~_jHPTij_j1)O1Un-$JaYn3 zL1yi#0<8*86u0?Dpklx|YrqiJQ_WkoR5Mo7q16D_goGyWbCK^^V+B%+a~px2#j^oQrm#3Gn{UcG2IeT63 zy8OdSDLMQkqZOyi{>!J)LY>EUo^n*#t4Q1rRe0U2*sH)+5^Eb=2+=Ocy=|=FnaJ># z3ZNE}5he#%vMr`Wl@vs&6PmY9W9>i;V4SaDd?TtQt~D&NxT4B77KT(;w8qhPJL*$s zX8to+=nF@|K8qAAGETZ=EOe&SicC5m`E^~OVx9QBGIq>sk1n9P1V-)jwi(6dPE?ez zt%SZ5HaQ9uuPtL0wxkN$3AE&eelL$y-+enhbPUuw@KMpuUe2J?nr0~1sDa=SBCT3Q zFD@x*L-qLdI0Ri=@C4f+y~R4wf{?e7aRIB4`#D(sAOttjb0jO4^t!B<<7)O>{BCqZQW_k)Oxi?ojnR?|^3)^Wg<&?;FWGS$Hc)w+h zLy^$jn;u+HKVQgN9xSf&mcnSb6_$MHom)?tsHSuzn7XO$AJ*}lb7ar&ame zM+(&J!c*$;Qj_ZJ^Sh%s!R3H@R?WP;$f4Gzygge$R4d=pCr)n`?-rp{&f>aEs=)PP zUJ<<55xf``p=YoCOU3o^F%_=HxCs9*gE=xyA39!%Ei5nCnl7;MW=85pJ9+0|{VXr- zKF}nf%=y5_i~9l*(88j{LTEMi=C8sE$R76E8Sgw*&TMMYlW5ROZf=T0era~lZp*+zpKN>Q7vsmIX)Crqr1U-(zH2YFH_r9NXzomIM(i0kHjXzn z`Ol_k^vs6dj~5`O@BLCYClxDhd$HgR5iR`u2fz%JL zina^({UQ&t3b5Qn?i%F59je@c{|30NL9PC8C-C2Q?cZHcHqL)|?d<<)8~o#!l>J}t z=Kr@h@}F8k?8g6xc%?rJMQ!iBMIqR2WtRM0BBQ+ zFx<&d`gJ17&l&7^H~rTcybDZp$Rg*IX8!zsGk@Ob*t^cgFg2cnDKsf32mS6!55n#K z`qOBW1xEd7cB!9?ZOTo-YSH#={_+uHLyd5bZhP<(gA9bR%(aBhI7Ug6P)WQ6gU)wK5vX6pd_Cm z+Q%`u2jD#DsE%@#?xu3i&^vhkbXp+a&L0tnpo7BNR5Z_^{l>Af`->+yJO_pt@Qg@e zA$kb;OO-0tiiNX~tdB=?k1BAsgIm5YhW&{%G?22Cc|Oy@42ly7E)iyAcTGCZeu6ETmTm@Ee7pyqN4;) z*dxfB>2*jF>j<0evwH&3%E_qKEY@8S9PM})8rG^$1!Qzv82KCbl}=Q%=C~WKVHy5i znGJZ}%0rOH)X;hvWjUUJt)Srqfncp%&P5x`ua}S4i3;DWT+xjb z6peoT)TPB zO?eUP(5R+Ic)1>b%iR@arcE4@$WPj5H@;rhRdCf~oWZtIBM-97ANy~b8JFs_7z-x{XuQ zSmlLW6>I#BfNo6k^x`;Qm=!>sOPlyvwp=?hxYzF73AIy2>lKlNRq1pr#p|Fdk0)jD z1};_aLnD=O*#h@CY)m6Jb>8$G-ps1Q&)R0Ni^%WPYXkXCopTp8VDrC7NM;Hzp$LxJ zLPQ3dbj8Et7(KV}=RJheiN5+!_ot;9nx^PR-LwjiMobb9YcyQKP1nvE6j3oU#^=8@ z`I}BLKx>(HSo|wajoXAjzR?C3v0@baowRODXsoMlUNcmR_-o=!vNjnCt5^r+Zh4H4uRU5GoD(mXZX@~T1|bh+Bd%Q$bP<2 zp9O{fVvN8aKToCSsyfQeTn&6QodS8GZ)(Z7uC~!>_XAfp2H4%Noc6u zjX(-=sx^mV(9aHFrCA=fA&kp_?{1)G)?>kiD=3i9Q>0;P&y(FUJUJq&WnR~{k!QJ# zZ^pbh&G0Oq9V#wa^g|q2epFO_uwdqxcKIk0;*1A)F_WVcAOhN)F>gy=jJhu=JU9R}_4Q>*EEN2@^7uUZXB44r#+AVt$Mw{dL43 zaH`Ahr*DqGx4T5}IKzSwsO?kdNF8~}Zg&gEIiL+@;$)q`JrC~*d2DHKy z4OhYto=hEejKij*T5{<={sOB}^fp8yep+{H7KJ7Ipu|+ zWRKd$yKpnK)=E^kY$;Wkoe_JhcSz8R$zJ0(ThuBF!jOcBlSgRD7HUlbqO~MV$-W@l zPLLUT#`+c@tlt~=)>$hvmjxTPj1Mc*v(;I(RsL%iU@@;cI@*Ma=M)w5l)PNC zRWG3#ovgznZ5ISRUb_T-sxC~GyOO)UF7&zXAWR%@^}kn6Z?-h92STN7UXD%(sJtLS z`?rB5zmWJW=GnZO9~v5UQx|$d^vo~+1zfXx-Lo*c!O?NZKFsFwCalsJ^) z8hBhDftMWkqGcOgkiqW9P_gQ@ZDq3#({t@o-tn+r<0ndlZNiKGX7~UvfMcBfulM&q zW;7$mKRs&p|1=Z+d4K;G#A3DPR^$(2(YL3kf5zYW(Hb))cq^+muC)o=8^YaE5u!`* ze%`l-cuE{gqQ!3-c#RmuqcHFH8DHyB>+lK!d62!nZC`w?ujIe?Y880{vk zkVB%{fCp)7KZ}|{-0K!iY(RKm88LPMw?kzT%C(C8OdkE3p4>TwOFKA94c!GlHDON{ zQDDlwMX;vkMo$2J8k1q>9BWP+qIpOFrPl)oE+D_Dk7<1nH$OuEf&{gpzIStvwkA+2 zPcRTE89Krgs{vYZ2g-H6GcseFaO**(>Y`LXuNp&EPbQukc@Q`6lx%aGD6{_%&KQd? zQ#AcljKX}VlY;X~Lj23Cp;@@hYtwnC1S#JQLk)ql<`|s^(|RoWrklyA89{@#PJINA z+SL$--LO!rCJ&*0aHABbt)Ia0jGnITp3*uk{$OUF7(eZJMv!?NmV%Pg?QI4LtO@}g zYF|pMQxZu6IhYoCZ@c&+fX@>ZRscF=ZP1KZLSITPMfhTf#N4S(23y9kH2N+%3UQ5d zp#;@kxX^73)s_&nQfDYO$XR4LK~l>g5tyw%WD4)+8B$32!4vAu{mbR_*KTg3bufQx zY{yeP2JvuegR!r@RH0jo zYJYS2Fptko0~+owTsry*z=MX?7{FmT)Vxe*QMPBnu)UL%gDhTe(L=G>2&vxg0oh0I zJ`#`QvnIrbz58GB7U4}rlQrqw&e_4f*S3Y?`h3tPFoYHL-a@GxR2WJ}HBd?%?MET$ z5^HslKa=@3jPQr{Xc&~Tmd!2p5$!B0SwQEiDh+N-&mbx_Jp3AxTM?-@H~P*(-4*s_ z2G)e7e)Nw+cuA?q`S>)0?0!Y+T(L;S(92s|3VaR1fzQ)s35Kj#Dz-*YcB2}qPYhG- zaSBL&W;X-!u*2_gIA1sfHd#KvF!b|TFn`wR)UoNyKdP>U=e62IC~$3~gW4dE#RuzD z+o+1>iu}`{bVI{;DRo4D^=`J#bI5BMAmyV3fj=tQwE%s#^iB{1-?!!JA=$~#BD`+F z8ib)i2dac7P@0ik@Rl|pmO*kEM_G2WYK4f}aW~|O=ORwk)FRhq!7_U>9g2oJb!o&*Lmz)KKu_(B6PssaS=vDSFSDgH@D3w7)nX}EQ0 zHwHx?k#MDDRN2!^+%XeT^LNB@yLwH)ul8Z|a#RwMz>;u4C1;iV_7o4#xb>*nS&s`! zu!I(wb$b{>M{^&V3^?l|w6a@IPlHNhl#@Tq-3bWAS020x5=S|{`1d{ezLK2|J%^)> zFqQ-wx9A6wRUZz%v94-mB0Za{%XjfW=WmMhm_V+}NkvArj)a}Srq4QZRfRNVjJU&; zoz;8lNF4OGiRNWLJsGg8vdi|Y6e^&Pu56{J;$4JL_f?gMC_WWVI%<5JscrCkSNQ0( zk;W<=!WV7HSh(%Mk{z0PpTlZ;PNel7P>cv`!>=&|8nl{V{uBgk6Ib z@XsVL{u*yf`$eElMd%wg96cYni;S9&j&wn=E{kM-r#9(9trU)28+ z>1OzhuirZ7zbnTF>7h0Dw?GXo#hGl*UU<@h2=)@8<|N9KZ38?+o8cv>pJ5gG_XKg_uMdo#dZ7KUG#iuEBVDeDQ zr=+<(M)pW3cG-PBVpv~l2hRaxr{!)6I;+_7C?qZcb@gt2NY!oj@EFrNkn6y-=5cbAGvARIuRwPirv~fc^8yQXiF<3cv(E zZ{tmL2i%R3-o?YcPB(R=r*y~uM>WK8V4L$yLB#Gs2lhpM!1hZ$iuCt^pr?qX(4SG( zxJvT@zGQgE`{RW&fn+p~BJc%bjPv75b}Hw}L>cj?NPubE5(GAdyo?;3+EWPS*^7j{ zR1eF8y=5E}zqR}(0dA0H+Vv3swtvHyHPWW}ue#zto-z}|KcBWa{tG6N<4r%QE z;NzeiaZeav`Pbr}OHUW}*LJwQ>hk2QWr~O5%}%Do>fU)S_xZ4ST_SciZk5A<6?m~I ztaWb4%ypT@yLr;a)cojN4O^knkEqD9&2Rkoul?p1hEF>c2InTPFAs(L=0srU?)_OA ze1C`+2 zNu^Ev43TjCW~wL88}D03E$?-IAz(d=9DD#reO zJZ6Q*MElxv%NsdeY>7*=MwJ~3Q~Kf?lUa6}V8diH`!qJoG>rz{%yARyNGNau@3 z+f>7E@|UR=32SWxbp7M`zA|Ofliv@Lx`yPO1oULT zUJK`?Itk>DKRA280s)uB0*%jb2acJL9cuFI)FX{=2>wcB(t^>I@K?`4rExlEkKZy2 zEHQbP#KoFH{hk8qARku8Lg&F^4!$B8&T`>b&Qy{{Ub^trSt+ zyVBI276Lysj)y+6Ch8vga1%4k@g6e)BI`RyAF=xy#8RJRT3~>Wr+hFM7~YE5S}I^J z(hG!h^yE}^P7QOq!6^WwgLKwR<|qixD7pQ`xb44Th|CIL&I+0z1Vl7jq4+%Ktp2qF zvCN_)%0PR1yn)llLvBL$#DVopDL;PQ1WxV-Gil~JSlId8|>@HqR|3kYoo>5#BD*pp9Fr!DTJ zVP8bFNYbDG;9ak)m2C2DGd*;2ZaG$ldcNa(bd)+W_8bsypxF+~>v5iKa6;3>05M&3 z5BPg^tfDHPmn6NiMk$1onO5D8hcaGx1X#bsPCu9?E10+}Z~;%)Gc2NV3OeI1vT z^N;)5Jyi`)4uFoJ$zJ#4uRYN=go_7R)sP>N&_mIHbHp;QNWT3R8{Oc<0KknOAC1($ zzYQC~+)US({cKru(Ok25V9h4V0IOH$1vq)$s(}AooFTlS==4P{*T8u_zBROkU7qqQ zir|{@oc+;%ftSy*8T+^F_n(yZKV9vAD8JeN7KHz!s%HOh2#0^ca&XxGGHCy6usl?P<`XRi$P%a;psUqo4Dnlmw@MWQ>l8A^nE8c z5J_EfNz_@;VzIf(@;u=wf?Xdl#Dbv^V#cuo^J0E-(YRhY_mIUNg{H)lyR z?F@yA5?n^P0JspP^WMPXZ{6JOD>~Uu*T}0n1j=YGw*+qPYFND@bV!$$*rVU!+feJ8 z4Z3}m@|r;syF@F*Oqey)enOM2W9#PPA@I0a+qAUt$#4oSck)KK_RC9_c;pZLFAFzmQ9*BHw|A(DW*NEqdl70g^uoyf3&r#Qh~%cF;{ z`PLAWcXi9zRzo^Chf!SfnJK!63!z=)m##=*jVzg0{B!_y!>@^}&*pUb=!-9*ngU#K#yMv1w|>2)c}z zWKa@7I!c5#1JmlHO7Ut6U%SawZVtEzj`Gq$zBWDL!sQUsgH!Bug=O8eVPV`-)rxNr zppe=~Z`*ev6*Rw`Am<=gOTwKoC_zsw@S!-;lFJpODvVO^nU7i^f22zKXQGz6*Wgsd zO&-))DfI}06K-c+mbpPRGVa)A5dxTKP6?!y20!Ca(TNHYLD2O4B$Pk|R=gdq!-MW` zI3=z0+xV7}8d#!^Ew+eVT741x#Ia7qvI9wpR!-NN&kAPGLC}aeP=n0^T4!pJ^rs2S zRqHdcEyS)Li7^Jtp>3G2jolL!hbXa6K4rYA*`QlwDu~0h&v)41+@`yu3)$Y5+&v+2 zi>H$%2nF&MJY@>V9MYJ}x2m=#9#^(%ha2PILN@j0j2nk^x)_hV@A-A?vO(|Z_qH+OFN`8Nof~!(XfY&^QEPB4CN8tINv8&UcS#S- z>Sa2(;-ZuW@l0=;clD?oq$j5l8m?Lsi>7k#;VcK<3U_L0!R1Mra^#BKqSSmk_cX1u z2stD9!j&de6HCbvv~{VJW$4{h@hCqh&K8wn$3@MF88}-E1*-v^TdO?Hw)1i2T^7(n z(7Iv?4hJOb$jF;xP*8%N{XUFy%w%Qm3wnMTEg+#QF!E>1Ck$I6Rqlan4aRaxWASRFI1PjOXFGH!X^Bt>$xT|Ta-NY$Ek>b3s-yi`Ob%raD$b0%qfMDgSGSmkPx z&O}IOvx_(U9-daUqdOG%h}TYGB-$VMrNf|qD8tFI9v~4hYO7Ql{(OcL{{ij7HuYZE9&8cz>Gh|KKY1d~AzHT& z=IOH~o4nW$I*dbJ^6ke?$cv+wuN|r^5zd5*@H2ubP`o1#m)uT#pJ^o$_&fRyt#M?$ z^Qm_1ntn>pUH9`Tt9%`~-%V$LFn-enOgE6O{nVFe#KqRPle5Xs2|vHwOZy2^pTftL zBQCBq9aQWW=fx0`!bsQ`M}KMT7jV$oa*tPc`qH*ap_`iJf=FZMk9~Ct`)z!>RIy$$Nb(2G~U!HdMcA~8> zhJrR=PD;1#rmyzyH%WxK`zdnEgoP{p@^$v|vd2#C%x^4$#;PK$|1@0itdc?7+`fNVp(F|Bi@J zqI*$XHlCEln->DU2^wHX4+(C9N(Kj8Vq{-#Uv_RDd|tR|vVLQY#`d!`eyA5{qXyV@ zE)u+lLb@>&!l@l}*vnT-C4azn;(MVoyQJ>l5|;Ai^Sj%acnu6d@p`i8xx6c1h%gyE z`F3PeQIOvXOnpXHr1Yh%HE$>OBBz|c74?Y?jC-aYZz|p8-aWmf+|3U?s5+#ov55Y?flSkFn0M3K{ zldlv;ntfcPw3K3$uT{b7!y)mcRAH#?V|*0sJgl&klweX&NW-?N?vOC#G|0qKLrj_Y zB6J6)J*sawv_JeM067rn*$EP5R4JwAZWO>m(s1q-v8~Q5cXF#aKL$$DIPygXWgA&& z!bu4lbV3>HifW27d)$JCMm6c-M{=8Age4JtKrbD{h1dMP5oV23al@&oZnMh$RD5z=q^-4N>@2OhMQ!X!vUGthhGi`QHUr=z$;}_-vIIKaeDNtNXb?U zq31%>bqT_b04`Kl@$?vR@t7N1lC=Fa>lJQ1xhM(~(5@HfK=Fm8rB1uOT)(L4u$6y3 zqFWQ=AMHBa4HGyX%u@yD%GtL&Q{$!uruS* zrdCh>#ACeQX+f}I7(7XxN5WB?hl#2i8Kj&j#Y5>b9YwD6L_kZo80a)4KE5xQu`?LX@aGjXRijgtWWFNXmi(F%YLciAk+??0v5?bG z;pFBeCn2|7z);vrGm^5(bgrcgHQ4(ltsNjuNo^($bIF}P$x#fZ&NNBl1yAkz^wct$ zWMuK;LxMxGCHj)ll{Z`^PWY{3N4L|FQMb=k$OZ+5))q_C9RRX!12rB|C8Ho!6BUbq zB(PHgc*Ih^^^gz4h-)GE0<)1_qYqTZYVHin)j#x{nFcyBDP*BsqH>~04Jf=mCm2+t zuRMlj^VejR28l>oHUJ~YC9AM$0R_rC4?e42lzG#ja|8MEFHriN(X)N1*S=()*dG^K zJC{|v_gEZw#GH7h1V2u(a@KmJUl?o_u9MzqSR6tdX;Vto5%*?8bR~(H{7hWyP)SS| z4;QnwIWmuwObGtCWb)4YdjW|sfkJ`L+3-K}Y~&}4IjxVY6$8d$U1s@0j#~A8?Z@8P z-uCI^13>sj|2Xs`QL+8;?P1^k;rUKGSHsqaRA?U^$@Nj}TWnfS-1~5^&<(`AuE+gq zcPQEM)8qbe&QE|F9drLk0jyU94fCeEuo&icx+wLGr#KmAZ;V7R4}HaV^9%f#Z_kqPNkXsr>C+~*{Xlq{1urjRiAgJhwH<%1zAi&-euLw3)++HuS74LBeLlTj z#ky|sxeD@`o08{cBw@0AfN{aqyg#jG-+!(}PdxWK%e?zIWUqAlx6f{+x-wgfZk%&h zmmD=gcF!)3JnGC?Fg>^=vYo;D<_*76B8A#C^;LMjHNGFsJ3(FbcyU*}zvjhG44voQ zoSEW=UfQSG+0dZ8afcQT=vN*pU@(bFYwgq0Hb``GOK$4#FD|}!PA+bNhpfO_+zHQ@ z=XS_r_u-)~XE&6UfyJDa&E#dZ+ylM@Qy_^pkDk*r`9r=`?JwsXO5iZY%T?8r7ZU%- zA~|M(eQH?hf&#%WL4g3(9Uy+4#09^5H^dRq1e0`7PWxooaI-rM_Z~Dyty;n1rWX=O zM9U*@_7fG$8^$|*oEoBnkgrM(Wr+}pZX+6o5G#*^@Xs{9e~G~I%(E-YKB3H{9}Y&q zJXV_owMmVFuNVa*Q1-${8MpQ@44Sfc#9$yODkH@Wn?pJcE@zxiV*slmB-DB#3nLmJ zK=1Sj`f|5Kg5tz+qIuP|226dRWTTN-BZedj;8efQMB-Q+nvn z={fI^T=XVmx15Iad|H8z2QGX@J&K#sH!u;se25rs~0@m4(Oy{eUy#JPCLprXeHfvKNN2L!VUV zGpn-CM6H!nqd*B79U_PqC9bZU$LSSSY;i=Qw@4vPrFaeDJ`&-uT7Q|$5<=4~1?XKBv^0;{-gdc(osXRECWM%0xT+@( zW3>g56T1_JZ0YC7wKv^bRz;z;pMGK^X1;#+_7ZMgr6-e@+D|F3x%l=lQ5x6ObagR? zE*SG4juv|fH`<`u<4Ez3l8khOENSRYr<;B|k>`deB}B}rNup5~lftbs)vrnzm1#Y0 zSN1YA?sRZBHM>5mL2LaR_FAcaBH*%xAk^CEd5s%LhxvCMNLTzbhwxc~U8BKl=`K^@ zk?LW9Bxn~5YH9KbC#=LI^r1wUrG3xQc?G1VN?vlH369t%wJNCF^03Stbm|wRmLmOb zbb0Z37(F;cWu4RP2)WQ4{n4u+7ml#wAJ}XAk;I^L6G^{_RKPQJ8j=|U%#j5wRi|4a z#1RFDQg!ss*_Hsz74N%4sOl!FYqwmwn|$N3fvG!D^ST77%eSLmQ$%%prJ;@C ziLl{b>Mf~dNPUY&8n#o6K^oYvxDq;OMbpJ=B~bGu-T|{W&{xL7W*SBKAI{eOur6u; z;MDM2r((}`vra%Io3Ty$H+6?gbLwN*ut|qDXf{!b9FZZP$XHu_RzSLauITmd$W~2M zCJQdt_$OYLPzp49)fz!}-P?qNu~+>%HMW!FeWLPXwVBv15M-LDjtzFQm4R%M^&JuKudQ=hOL0A zF*2xsOdZu%j<#|a1?~JL9QHsLtJ>fL5+F6H%#viRsAPaE!GI`Ap0AQqsY?aC|Dcf_ z0?sQShB~HWt6;<9;#KY$0*+OL?!zYXX@8GVdWv|xVk(V(>gRR5Z*;T=Cuq;iD!iuu zIh&jNz1-dMXKqojcr`QO`-iQaM_I^Y$$qB{{E2<52VTWp`6)euqoQP^efbJJ=Z)rb zx#t#1$h5L0dlH$LZ70~eeL#Z#QbBxlrQX)IFD)2+MGcxax6Q@r6U_=>S0~}KeOjKg zghS|d?25qsp*F;=BH+&VNY@Cpmj%G&I=}mMA{RETT(!ot$FVx<0h}3$T#Cg%e$yWU)|=yn+Fi?g5@Igz9|Oh8ja zr+@YqJCaQ919imlQCdWi(&$yIa*bbt3l|bu;bX0dXd%b|#0P4DIe-t%y@nUp98-WM z5s@VJH=olV;GAG5nSaZ?|4FC+>2R|%u>N)6uV*M$j(=gKar`%ov}UZy2yD@B8$1Tn zK(bFCKZL5TU;pfH_Z&VeYA?rTq+i7!Rw0UIoSvO|^qLIOl=|X-r4Nyq9?BacbCt0mEeySiH)QQ-WB=d=)Z6 z)V-p6T~yh-GC|Nj(t`)mE#8w2aKUG5zuczNsPl>J3;A)7qM0TGo{yT}iRrw?TB+Rn zvR4$`jOal?LqMQ;;TK^qEIyGB}4?%4+FsTLlAr){+~OXesq&#i+4A-8h+8hL+TG1aZJh*Qr880%S(!vr51SVi#Nw*xEjc9U19SztH@S?vz@N&ZMX587lY&*n_db$ zVy_*gpI>SP-&%)*?Ak?UMoExG16mFF8jO1wHB%&9DBgmw;s_E#Z*ny5dT%Tqbskh6 zvr>GXwggrbL4KPww)`$X2FA2egH1;)8h=OckkmPqRl$JbI1xTbCGqV0bmIWlM(ZU+`26!@mqr)j;Mm@KUf;sOm-l z!9#h_!hlWL+AmGu=-Ool&+FrgCF9Fo7g&&rpNX+7o6{i1j`sWV!Hg)+O__)!Tj`Bg z#m$V`<^^w?Wv!^KL(j+#tMc%iU zx3@5iK1%3{=?yfq#=~8d4UHC>Yc_A3B&1q?iGk@by^6TO8WZMMz?>c0bb+O~WCwA% z2ZFW;=M_m9D30Z$I3j5znG0r@Q1y^AJ&ap%*tBg)m{EIfM8dK8af)Gm^hUZ%VA=FJdt2q)`Ns4J}! zS9mn_Gr6+!Mbe@85ubWc<(%AJmUvWyK6|F9b)`QcKMUGfcz{#4Qs)%Wc@zZLV41j_4}E z+}>Ly`rhYIo6E;F=DTXqgs&|b#!L*|hx(4?Atn<(3|UxiDtQCDXbjZ|ptR+~Au=s= zx%FM!Z06CHH`Dpm&};>vK05tkK&CZ6INA68&M*Pp7iS_QU$fG%qtMdQ>$b$WQz}}b zWhds2D~79%QBSz0!%%rM2l)0pJDPK~&7^|Y(v7z*%%_s1w5N^kLOi8{qRRO$Ls+dR=hUwV>GB$;!K6}`+!6^)Q=uDCuaZfFn{eBH) zc!tP!q8%9be#{nDaF~|Rl;C5`1e~jt z!|1Rj(Q*25lCsc>(Z?_hCGR=gH**S_)(DR{nj>Zj^KP3e+$RlWP{DCR9C{%|0ZKNY zEv|H}VLGW0FM3)pQcc=)6lzCPv|4-E{4Q=uv~9#7sr@zbxZCbz{ZWrR`D8OM+j?v0 z3L(1~u356Ouw1Q*M<3U}F-@N%0ucg#AnJB5mK{>COjq+;u*y)ToJNhT<2`-oZXNQ+ z$IeCiP)tMbHRT{D0JF@M&ww2*DlLw!EEkf)K!>Z2o4M&lpoSeL`HmraC;xwh0_@b8ltu*Zf@^(Mc9H5aL_3 z#u-!c5;zfrZSaM{ntCQw#%<|)2afaLptrB#M)wBx2_%%nYMBn8YEblZROR%u8HufQ zY?H7YW~{!q)O4fPI!PEB@5!<};?N1L&GtaBWu)HXTnzU>- zw52^Oh|^*cw$D4qpUK-6Kt}ldP`dZjB3M5|vUTvS<}UAQPgOZR+X4BrgfWs`izXi; z7H4AKke1H(lgs+S{3s}NCsA|QLUNl#+Xe)CHjfDVgGPXRLYpTAeH71~Z?{+Q;DZ`& z_)jix5Lit8Yg{-05Jvbt6s>f@6XIpjJF()T+_)q1_+el zZQ}7|{kBI&R#S5Fy_IdiarNpSPRwUFiPAXQ9b_x&i15~i-h{Nxi@P`I?q)AQbIEtg z0(7t%0FwBECf`OW9$pCX`zINgSu|yAxTS=rN1IcCX;2*a9(Ut@o9e^VWyUh^Yj+7t z5YZR0B*h2nUI$cInZHU3r&n_X;Q^0{$bbk?+j;Hmw>izpR%p#0$%~RP`vPse1{4dQ zFlM085DF87G!(Bpf*0UN8cdt${h65UC+9+e@LE4!5sbl3qgjfAN3rRQJ=bo{?sJmT z`dWHg-e>&hi0o0ILQLB97vk&F8Ul83a^W8KYlupa7v5_M2gs*iM&mw<+ z0P$}E52GgZKEmA$Gt23bnSG(!;>X&f(>Avi5g_c&sg4;oc@X6Vayl_5J(YJDei=9F z7n)Z@qK)XP8VG67qi=X&MQkBsjq(Rr>`%&Y6!mAY9`O!3M&gmsDkAP~zqDQTSAVa) ztF1V#cboxCyl2`4vAv5}jw}W=Ug}ULLZ_S+IQbE;^ck&FVCq_Un-ILvTNL(IhB@t+ z+T9JpFY%k=7pXF{0S8?8K*brRUA~6p0Mk*DrFbjEwh*a?YM5@6*CD^j6dm-{5HvsJ z)K#>)Q#C2BGB=SYC4}sqDxF;cMR3H4VpivK!pO(yp$4dTWu*kVWhyFvr)sJkp7sZj zc)m#p+J;pTw&;S4nv5KdguQLM}e{1+Z_IDMEGSjkd2;}2Sjd@ryREZCE{9RLwh+#W@+ z9rJ@#Tf<=n*hiyc_E;JyW&JV`!Q%s{2j+q*+Hq74V$*LF zbvU*{A94qUu^k>1{6~cx;6Nl0jn;S_VU@8TDfckwJ;TAiJ53j*N+n1o5Uv(1N=n3$ zI@6Vu$)z_@2`2N*T?WMw(2xCkTH2tGeueTXYsOQH zQ6oeE^`Aqp%xEw^bT1{!mmwmGD?)|F>#EKNU{3=8wUJv6U8rAH{$Z!{WtQ)2%P8aU zjLkA9rU26%^x1u%CRTxjgsjRx$!79V3#CSjt=zPzB>bec9B{Sw62LDQz?{PKl_FH` zxE`{a=I16%%&}BQ9C-<1I=li}JAU`*Z=5q!$-AXVmEQ2qs#>uhmG64c&Zm%8g)rqC zZzOtuHPDt?ymz&Z_eW%wJY$3%!Iv%ug5ls(FWjvGZ%$SysF)}0#1Q6sybco@OH5>@ zf&jZKnn>B!fiILSeRjrinzzIpH$-MH^fRp-`(~gNBUtY|+9_NZ^fFRDnSIoX*N@8W z7`ULUL~iAwwspKMFs`Pzi;1{cYXc;f8!t#M(>P0*572eWHPdA)o->-kEjL)LwZpxx zn(|iDl4Mw;R==m^qcg3$)Mjt6>^ukiXrkrzV9L4z9KiyAC|V~f>v9c~J^NpYJYOLE zNSEqYSpPy(=jN2U`P@IF)MkH!rf|p|F-?%La^Ferh<)tF(4%|Y@Y?FI>4FX*nOM{M z8mrXQ=8j75bd)9`z+Hu1a@9iiRu!!1`277PBS`h987&c|NNq90TQO8U^tm<5Xi1XS z3oj0!O9sqVaoO(tmqP1~W>4_%%+lvL(P{%kw^jfo;jKU(lL}`c5sHrS&@&u5E$p-{ z3DNBY!w#dvwR>$y&W9jz!sOrFDS$G&P8C}nQNQXpxV%j@STL5knJXm%gWr&N2}XiC zB?#Jd)4YmK<5NegZKN`_dOM7{uTm{a@_zve?~>yDFs8F|)N$1db&TyBuvQjeQQaAA z4>r$CDD{~}Qgo&`j<_e8S;gy^x!mDOVdP_mUAuR|p#7BPF8ScHgY6h+u-{|F8o3as7}c^eTRr`- z7@L!X?*&}{jBuel0HbNa7wofcPJMsy?P=fij0MOaC4zxY|52D508hV=^-Tx zsedqx2KXySk3#TexPS8O8T@~a5Wjp|y@(zo6Z~t6{d?xlz{bJ!k6Qn4iT6KK?0~gIxj=sZfLjfLkMi<>4aC)i)ur+L4E%f`N}s!UydNz@DFwGj zNpZC&P$kXRDCPwap@ zintHr7Vk20rvL&(Lk*G_&)(79!KbSYg%=d0sJk0M7$7@R1z_vfeG=HZySG~w=pCi% zULr6H4WgR7%C7@f?-$6$+lz$KZ9Z%B*a4osh4g0Mm2*%L=ybVe$$&BPu+U8b>U zfv%N{{BUu5l7BhHoS^ zG<%9>5kFEmIFg?5E) zK?!?wGtGo9V2%hn#s#=;>ym&XSoSNL6dFN<2PQ$(5M@qc-qsb6IWT}}n^4`@?TJB* zQCdvYB))?XGWunZ&HMKWVl@a=)(byHty~gW--I;Vn)l!oQGT7o=(DyL7;o9FLPQAq zH$omjhwFtNvLam6gDfpsu%SXw9yCKP7m=7K=vXrKI-eAA6AGFFJu;sJ+a7B z$|xyczaxC1MNs$}QrVO83#ICMD~B3U09no?a?mQU{U{UYt1zn9;(In*I+MqAintrE0uydusa+Qj-TKU}1F*I*ln8scdMhB`x zi)=MOezl766p(_=YDnVGK?_Q4O$N6rwr0JKqrny%U8^#9xPncmO%>GsUI$c0hum-kgw47?ViB1{ZfMCOvrd&mqS~ZLGT6zqd5I<-T3Eh=<&Yjz z%T;c=FjS^*Irn%AW)?67)OZhbHol5&Httplud_|u#Lb{*8_N0^d?bR4fptnoJgdS^ zHOXZCDU!Xv(BczNNn@}V8b>epcHOD>d5-;^daO6E8D$;U3|p@_qS@hxxPNisM`o=E zX)|BVQevW=;I47J)Jt~T0$&QDkeU`u$!wmT>LNRZ=q-ZiGA5IeBomT2UB7`KS$qua zcZvip?KmH~qT=_Y;I?Lo`Z+&z;mqSe#m7tCbK6_`+mpf{8-!+rvaYTDJR&OrBjH3o z0?@Esm}_X#m@;%&5jVa_4ohsf-X1jt71q-9#|-OG?Rpewzm{0AHSU~^KPH!AvfYFF zDB5vZ(>H)Gg?L>^EQ+ZV9fcG~YgS@k5?1lRna?4eZ5!m+jR7=jPjMa8z$-LwC}BMZ zh}COZ{oz5X`8v6NXviGZjsx^ie=E(rI=A_Rn2Mwyu>wv*^x}NUBhUWEvZ2ZF8#l0@X*W3hZswK8yP zN~mNO(U3W~x27TR)t7Hu%T8$RvUORGXw3(=o+g>+sL1RZ?30}Uu!ug_$1iwhfd!tq zm?9hG@-b0SC@^KOM{6Rr4KNzn^sbzg>BIcYRfcDe+>ZkKlr}G7a8f%em=T~k;dTcK zk8)PQWNCoblu~0DIR%fbS4x>Clx&h$_NU$JJ3#~E&4X&hkIpYi? zrl4DtGjJN@OO1d6{&Z$^xl^n1EKH6>b4S2}Wl9XZRU&PN)0v9sF=LG_KM(G%K+e6l zm*+HGQph-SckRkWy$M6IGtydWYG}g^9*VU6Cxxk-{!^6BFQsN+rE14<*Jm51_thd; zxV)rs7Wmli^WM3>} z3T-XTISQO>p({`XfAkL;&JieWKg7cfJH&I2W^kRhpKJ{o=dme?zsAR-;*PE<*gBde z6)-YYlKFh;mX z^HR+ct*^C)`Z?`9tA7AfPT8RS%P0MRztg|wE3AzFOKoMN|63sWzt=A|`u_%zQ>@Zi zvq2BD^@*ARJycxAriT%xX!_d)5Ez%P;}Ot_Vvg?fQ=vdN9*Lt>8)XeHmf|jp_$T#n z?r|yrAZ;GH$^7G(F2miw-+*HR>Yz?N`K43kL6VJ2^qV zPqUG7_jc?8h)1$RmXWCLi>2Z}+}>U|l**Ct#LD2nUS&XQd76oUcI5TCn(kiQjNw&^8$O_REsTNr<)t%_0#vM;< z92;es4DyOXphAkiO}- z(wCrrAS%H8*|NUQ_c5hlYzkO?*V#xu0ADEskjUh7G&g|Bt9p?wx zuu&T$*`h{4Yel+U^IhEM)Ap4{8`KwxJGfkcECilfGX_31bTCZ^w=<@;->ioRb2nnB zC&ZHjk9gx(5?9dHrmBmDLxUR?_t&QN>&M3Mzl91L{og8~e@4oG z1G6Yr9k0mdN7#Bt?S?a}s3ZqPtm$tZyYi0{9;0*jrvVx#`<4v%*LPLlv2I&h=3;fs zlp|45exdq;K$HPk>K9jGkq2AuU+>@#r^4^MdI`>b&KBT98&+zt11F;%xZd7^1}_rAL=V4^*%QL4V^v^_X6z=&KGq!3fZ zSyaxc4vfeLL_0;6BF712NtTd$C1S|JonN)TW=UtLO);g>n^3Nc6XZ#DJOgSzG$LYC zq_9_Qg{rYcR(UvCxH`iWF~zcmh4hBB^*cxt0UehY?b0}X(C|r!zhM-snhKSPAbQm~ z&Z5^)s0vd8b`E!J&}J;9n-$x{BG-w#$G@nK7T3X8ciCHoNsC~2`8Vo=(IZqm=l~FB zTl7A4gF<~Nq}_&p5Qg+Zc)PLGEbCiCthNE}JHkWRsE2Y3$CG%@<}2m<7wbo8WGMFz zNxS3Sxvya)6s=onby-lPXkdBHyvH@cUdLV61!E(^3?Su}dzmH|9aS}6i5$`b+YE_m z%z&?)uhEn!=)gpTSg&}6Ue@u+iVTjgNkh$)P1T3|?hu!zdZ*OmNXFcCny5#3@Sq!u zVT*B|4XP&A>OK5i$CUY%r!6=ty2GO>(=^8)7;E$4OhNGtWV1qsl#mt#hYVZ=d;sd> zg)@Puq6`D>{4pnYl6%n;@dv>eq32)I&foPt8}t9dN!b|w-wkdyhW}=RyIQq9W`iE# z@)NZRSPV0V2vES!dQ7+j6sAb6XcDjpB98g>Gm5=H%+g9?Te`5h?IH8N8&i;acxTWS z5J*E#hn;d`PJABp?fqn9?;iX%(gwook3G9K{7SQq6PgBjjqdPhB+^3I`SYqyv;2bjdE0*`k|cqNsEl3Nul z9{lXfv^^PyO`)~H0)IpT>C#qs5#ew}jP3+h7mqk2XEnX*8*y33 zXz^Y}Y-G%oRxt*(7V6yycTTx!T5p>yRNhyr7E!E)E5Qg`kq8Ahs&UChfm!sJfN5=1 z_WX?ZXt%7oEgp694v(~6c-$Lhm?SJ%es6M@H$?@%$+IIkqy21WlNP_=;$B|VzZyPg0$G+rlrO@WHaA**~iC z!3)!4T2U8OW0Gr|^A&(fXOBXgV>YcIgeP#@X-x~N=JT2U-yaXZ!I;~ zqWMtJe!U;#`K`hH@Wrh@{P4j~3%xt>A}y%0Do-g52phj%y5}?9nV_6BJ~=)W^74Qe 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

CG_wyC(r9|7A>+!mv?&u^`evO~l;aFk@X50 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

Byk*#he5}j}tWtb|SSO<@xw3<7JVGY=TVP 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%0DV!Y_H`6Z`Zfw1P3{%<$YgIL8T!J7i*9{ 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⪚B>oL6jkMG;#?{%&EA6Ks=^?bWGL;KhZ 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 + + + + + + +Advanced + + + + + + + + + + + + + + + diff --git a/h2/docs/html/architecture.html b/h2/docs/html/architecture.html new file mode 100644 index 0000000..45e7fa4 --- /dev/null +++ b/h2/docs/html/architecture.html @@ -0,0 +1,269 @@ + + + + + + + +Architecture + + + + + + + + + + + + + + + diff --git a/h2/docs/html/build.html b/h2/docs/html/build.html new file mode 100644 index 0000000..7f3f3b0 --- /dev/null +++ b/h2/docs/html/build.html @@ -0,0 +1,412 @@ + + + + + + + +Build + + + + + + + + + + + + + + + + diff --git a/h2/docs/html/changelog.html b/h2/docs/html/changelog.html new file mode 100644 index 0000000..4ba8cf0 --- /dev/null +++ b/h2/docs/html/changelog.html @@ -0,0 +1,1075 @@ + + + + + + +Change Log + + + + + + + + + + + + + + + diff --git a/h2/docs/html/cheatSheet.html b/h2/docs/html/cheatSheet.html new file mode 100644 index 0000000..dfbd7a6 --- /dev/null +++ b/h2/docs/html/cheatSheet.html @@ -0,0 +1,221 @@ + + + + + + + +H2 Database Engine + + + + + + + \ No newline at end of file diff --git a/h2/docs/html/commands.html b/h2/docs/html/commands.html new file mode 100644 index 0000000..3c65f3b --- /dev/null +++ b/h2/docs/html/commands.html @@ -0,0 +1,3366 @@ + + + + + + +Commands + + + + + + + + + + + + + + + diff --git a/h2/docs/html/datatypes.html b/h2/docs/html/datatypes.html new file mode 100644 index 0000000..9cd0fcf --- /dev/null +++ b/h2/docs/html/datatypes.html @@ -0,0 +1,1095 @@ + + + + + + +Data Types + + + + + + + + + + + + + + + + diff --git a/h2/docs/html/download-archive.html b/h2/docs/html/download-archive.html new file mode 100644 index 0000000..cab8a4a --- /dev/null +++ b/h2/docs/html/download-archive.html @@ -0,0 +1,274 @@ + + + + + + +Archive Downloads + + + + + + + + + + + + + + + + diff --git a/h2/docs/html/download.html b/h2/docs/html/download.html new file mode 100644 index 0000000..a98e8bc --- /dev/null +++ b/h2/docs/html/download.html @@ -0,0 +1,181 @@ + + + + + + +Downloads + + + + + + + + + + + + + + + + diff --git a/h2/docs/html/faq.html b/h2/docs/html/faq.html new file mode 100644 index 0000000..8b5ccd3 --- /dev/null +++ b/h2/docs/html/faq.html @@ -0,0 +1,398 @@ + + + + + + +Frequently Asked Questions + + + + + + + + + + + + + + + diff --git a/h2/docs/html/features.html b/h2/docs/html/features.html new file mode 100644 index 0000000..5f7e5a8 --- /dev/null +++ b/h2/docs/html/features.html @@ -0,0 +1,1955 @@ + + + + + + +Features + + + + + + + + + + + + + + + + diff --git a/h2/docs/html/fragments.html b/h2/docs/html/fragments.html new file mode 100644 index 0000000..0a75e12 --- /dev/null +++ b/h2/docs/html/fragments.html @@ -0,0 +1,133 @@ + + + + + Fragments + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/h2/docs/html/frame.html b/h2/docs/html/frame.html new file mode 100644 index 0000000..42c7d49 --- /dev/null +++ b/h2/docs/html/frame.html @@ -0,0 +1,40 @@ + + + + + H2 Database Engine + + + + + +

H2 Database Engine

+

+Welcome to H2, the free SQL database. The main feature of H2 are: +

+
    +
  • It is free to use for everybody, source code is included +
  • Written in Java, but also available as native executable +
  • JDBC and (partial) ODBC API +
  • Embedded and client/server modes +
  • Clustering is supported +
  • A web client is included +
+ +

No Javascript

+

+If you are not automatically redirected to the main page, then +Javascript is currently disabled or your browser does not support Javascript. +Some features (for example the integrated search) require Javascript. +

+Please enable Javascript, or go ahead without it: +H2 Database Engine +

+ + \ No newline at end of file diff --git a/h2/docs/html/functions-aggregate.html b/h2/docs/html/functions-aggregate.html new file mode 100644 index 0000000..4c1e1fb --- /dev/null +++ b/h2/docs/html/functions-aggregate.html @@ -0,0 +1,1292 @@ + + + + + + +Aggregate Functions + + + + + + + + + + + + + + + + diff --git a/h2/docs/html/functions-window.html b/h2/docs/html/functions-window.html new file mode 100644 index 0000000..995961d --- /dev/null +++ b/h2/docs/html/functions-window.html @@ -0,0 +1,512 @@ + + + + + + +Window Functions + + + + + + + + + + + + + + + + diff --git a/h2/docs/html/functions.html b/h2/docs/html/functions.html new file mode 100644 index 0000000..49e6fd5 --- /dev/null +++ b/h2/docs/html/functions.html @@ -0,0 +1,4124 @@ + + + + + + +Functions + + + + + + + + + + + + + + + + diff --git a/h2/docs/html/grammar.html b/h2/docs/html/grammar.html new file mode 100644 index 0000000..5161e08 --- /dev/null +++ b/h2/docs/html/grammar.html @@ -0,0 +1,3615 @@ + + + + + + +SQL Grammar + + + + + + + + + + + + + + + diff --git a/h2/docs/html/history.html b/h2/docs/html/history.html new file mode 100644 index 0000000..14b275a --- /dev/null +++ b/h2/docs/html/history.html @@ -0,0 +1,284 @@ + + + + + + +History + + + + + + + + + + + + + + + + diff --git a/h2/docs/html/images/connection-mode-embedded-2.png b/h2/docs/html/images/connection-mode-embedded-2.png new file mode 100644 index 0000000000000000000000000000000000000000..7da33a3ad9345a54629cbc2129a2034a70dfc636 GIT binary patch literal 48466 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 Sa-bs(^@B3DM6?;+O!_~}tLW1J literal 0 HcmV?d00001 diff --git a/h2/docs/html/images/connection-mode-embedded.png b/h2/docs/html/images/connection-mode-embedded.png new file mode 100644 index 0000000000000000000000000000000000000000..ffbccfcb5564e0721fc3a01dfd167bc3829eacb3 GIT binary patch literal 20407 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 literal 0 HcmV?d00001 diff --git a/h2/docs/html/images/quickstart-5.png b/h2/docs/html/images/quickstart-5.png new file mode 100644 index 0000000000000000000000000000000000000000..b2f007da5290f985f5bd23500eda21cbd73d7547 GIT binary patch literal 36043 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 K&t;ucLK6VCX0dPp literal 0 HcmV?d00001 diff --git a/h2/docs/html/images/quickstart-6.png b/h2/docs/html/images/quickstart-6.png new file mode 100644 index 0000000000000000000000000000000000000000..e981477f05dd82403ee0349cc8a7d2a56ccdd9a6 GIT binary patch literal 31108 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 E09LURGXMYp literal 0 HcmV?d00001 diff --git a/h2/docs/html/index.js b/h2/docs/html/index.js new file mode 100644 index 0000000..3347bbf --- /dev/null +++ b/h2/docs/html/index.js @@ -0,0 +1,199 @@ +var pages=new Array(); +var ref=new Array(); +var ignored=''; +function Page(title, file) { + this.title=title; this.file=file; +} +function load() { +pages[0]=new Page('Index', 'javadoc/index-all.html'); +pages[1]=new Page('Commands', 'html/commands.html'); +pages[2]=new Page('ErrorCode', 'javadoc/org/h2/api/ErrorCode.html'); +pages[3]=new Page('JdbcResultSet', 'javadoc/org/h2/jdbc/JdbcResultSet.html'); +pages[4]=new Page('JdbcDatabaseMetaData', 'javadoc/org/h2/jdbc/JdbcDatabaseMetaData.html'); +pages[5]=new Page('Advanced', 'html/advanced.html'); +pages[6]=new Page('Functions', 'html/functions.html'); +pages[7]=new Page('Features', 'html/features.html'); +pages[8]=new Page('JdbcCallableStatement', 'javadoc/org/h2/jdbc/JdbcCallableStatement.html'); +pages[9]=new Page('SimpleResultSet', 'javadoc/org/h2/tools/SimpleResultSet.html'); +pages[10]=new Page('SQL Grammar', 'html/grammar.html'); +pages[11]=new Page('Tutorial', 'html/tutorial.html'); +pages[12]=new Page('System Tables', 'html/systemtables.html'); +pages[13]=new Page('JdbcPreparedStatement', 'javadoc/org/h2/jdbc/JdbcPreparedStatement.html'); +pages[14]=new Page('SessionLocal', 'javadoc/org/h2/engine/SessionLocal.html'); +pages[15]=new Page('Performance', 'html/performance.html'); +pages[16]=new Page('JdbcStatement', 'javadoc/org/h2/jdbc/JdbcStatement.html'); +pages[17]=new Page('MVStore', 'html/mvstore.html'); +pages[18]=new Page('Database', 'javadoc/org/h2/engine/Database.html'); +pages[19]=new Page('JdbcConnection', 'javadoc/org/h2/jdbc/JdbcConnection.html'); +pages[20]=new Page('License', 'html/license.html'); +pages[21]=new Page('Data Types', 'html/datatypes.html'); +pages[22]=new Page('Change Log', 'html/changelog.html'); +pages[23]=new Page('Aggregate Functions', 'html/functions-aggregate.html'); +pages[24]=new Page('Constants', 'javadoc/org/h2/engine/Constants.html'); +pages[25]=new Page('SessionRemote', 'javadoc/org/h2/engine/SessionRemote.html'); +pages[26]=new Page('Mode', 'javadoc/org/h2/engine/Mode.html'); +pages[27]=new Page('FullText', 'javadoc/org/h2/fulltext/FullText.html'); +pages[28]=new Page('SysProperties', 'javadoc/org/h2/engine/SysProperties.html'); +pages[29]=new Page('H2 In Use and Links', 'html/links.html'); +pages[30]=new Page('Csv', 'javadoc/org/h2/tools/Csv.html'); +pages[31]=new Page('JdbcResultSetMetaData', 'javadoc/org/h2/jdbc/JdbcResultSetMetaData.html'); +pages[32]=new Page('DbSettings', 'javadoc/org/h2/engine/DbSettings.html'); +pages[33]=new Page('JdbcDataSource', 'javadoc/org/h2/jdbcx/JdbcDataSource.html'); +pages[34]=new Page('Server', 'javadoc/org/h2/tools/Server.html'); +pages[35]=new Page('DbObject', 'javadoc/org/h2/engine/DbObject.html'); +pages[36]=new Page('Interval', 'javadoc/org/h2/api/Interval.html'); +pages[37]=new Page('Window Functions', 'html/functions-window.html'); +pages[38]=new Page('Frequently Asked Questions', 'html/faq.html'); +pages[39]=new Page('FullTextLucene', 'javadoc/org/h2/fulltext/FullTextLucene.html'); +pages[40]=new Page('Build', 'html/build.html'); +pages[41]=new Page('H2Type', 'javadoc/org/h2/api/H2Type.html'); +pages[42]=new Page('JdbcArray', 'javadoc/org/h2/jdbc/JdbcArray.html'); +pages[43]=new Page('ConnectionInfo', 'javadoc/org/h2/engine/ConnectionInfo.html'); +pages[44]=new Page('JdbcXAConnection', 'javadoc/org/h2/jdbcx/JdbcXAConnection.html'); +pages[45]=new Page('IntervalQualifier', 'javadoc/org/h2/api/IntervalQualifier.html'); +pages[46]=new Page('JdbcClob', 'javadoc/org/h2/jdbc/JdbcClob.html'); +pages[47]=new Page('Session', 'javadoc/org/h2/engine/Session.html'); +pages[48]=new Page('User', 'javadoc/org/h2/engine/User.html'); +pages[49]=new Page('JdbcConnectionPool', 'javadoc/org/h2/jdbcx/JdbcConnectionPool.html'); +pages[50]=new Page('Recover', 'javadoc/org/h2/tools/Recover.html'); +pages[51]=new Page('JdbcBlob', 'javadoc/org/h2/jdbc/JdbcBlob.html'); +pages[52]=new Page('Right', 'javadoc/org/h2/engine/Right.html'); +pages[53]=new Page('JdbcParameterMetaData', 'javadoc/org/h2/jdbc/JdbcParameterMetaData.html'); +pages[54]=new Page('TriggerAdapter', 'javadoc/org/h2/tools/TriggerAdapter.html'); +pages[55]=new Page('IsolationLevel', 'javadoc/org/h2/engine/IsolationLevel.html'); +pages[56]=new Page('Class Hierarchy', 'javadoc/overview-tree.html'); +pages[57]=new Page('MultiDimension', 'javadoc/org/h2/tools/MultiDimension.html'); +pages[58]=new Page('DatabaseEventListener', 'javadoc/org/h2/api/DatabaseEventListener.html'); +pages[59]=new Page('Migration to 2.0', 'html/migration-to-v2.html'); +pages[60]=new Page('Setting', 'javadoc/org/h2/engine/Setting.html'); +pages[61]=new Page('Trigger', 'javadoc/org/h2/api/Trigger.html'); +pages[62]=new Page('Shell', 'javadoc/org/h2/tools/Shell.html'); +pages[63]=new Page('RunScript', 'javadoc/org/h2/tools/RunScript.html'); +pages[64]=new Page('GUIConsole', 'javadoc/org/h2/tools/GUIConsole.html'); +pages[65]=new Page('SimpleResultSet.SimpleArray', 'javadoc/org/h2/tools/SimpleResultSet.SimpleArray.html'); +pages[66]=new Page('RightOwner', 'javadoc/org/h2/engine/RightOwner.html'); +pages[67]=new Page('Role', 'javadoc/org/h2/engine/Role.html'); +pages[68]=new Page('History', 'html/history.html'); +pages[69]=new Page('API Help', 'javadoc/help-doc.html'); +pages[70]=new Page('JdbcSQLXML', 'javadoc/org/h2/jdbc/JdbcSQLXML.html'); +pages[71]=new Page('Script', 'javadoc/org/h2/tools/Script.html'); +pages[72]=new Page('CompressTool', 'javadoc/org/h2/tools/CompressTool.html'); +pages[73]=new Page('JdbcSQLTimeoutException', 'javadoc/org/h2/jdbc/JdbcSQLTimeoutException.html'); +pages[74]=new Page('JdbcSQLInvalidAuthorizationSpecException', 'javadoc/org/h2/jdbc/JdbcSQLInvalidAuthorizationSpecException.html'); +pages[75]=new Page('JdbcSQLFeatureNotSupportedException', 'javadoc/org/h2/jdbc/JdbcSQLFeatureNotSupportedException.html'); +pages[76]=new Page('JdbcSQLNonTransientConnectionException', 'javadoc/org/h2/jdbc/JdbcSQLNonTransientConnectionException.html'); +pages[77]=new Page('JdbcSQLTransactionRollbackException', 'javadoc/org/h2/jdbc/JdbcSQLTransactionRollbackException.html'); +pages[78]=new Page('JdbcSQLSyntaxErrorException', 'javadoc/org/h2/jdbc/JdbcSQLSyntaxErrorException.html'); +pages[79]=new Page('JdbcSQLDataException', 'javadoc/org/h2/jdbc/JdbcSQLDataException.html'); +pages[80]=new Page('JdbcSQLIntegrityConstraintViolationException', 'javadoc/org/h2/jdbc/JdbcSQLIntegrityConstraintViolationException.html'); +pages[81]=new Page('JdbcSQLTransientException', 'javadoc/org/h2/jdbc/JdbcSQLTransientException.html'); +pages[82]=new Page('JdbcSQLNonTransientException', 'javadoc/org/h2/jdbc/JdbcSQLNonTransientException.html'); +pages[83]=new Page('JdbcSQLException', 'javadoc/org/h2/jdbc/JdbcSQLException.html'); +pages[84]=new Page('Comment', 'javadoc/org/h2/engine/Comment.html'); +pages[85]=new Page('Mode.UniqueIndexNullsHandling', 'javadoc/org/h2/engine/Mode.UniqueIndexNullsHandling.html'); +pages[86]=new Page('Mode.ExpressionNames', 'javadoc/org/h2/engine/Mode.ExpressionNames.html'); +pages[87]=new Page('ChangeFileEncryption', 'javadoc/org/h2/tools/ChangeFileEncryption.html'); +pages[88]=new Page('Backup', 'javadoc/org/h2/tools/Backup.html'); +pages[89]=new Page('Mode.CharPadding', 'javadoc/org/h2/engine/Mode.CharPadding.html'); +pages[90]=new Page('Console', 'javadoc/org/h2/tools/Console.html'); +pages[91]=new Page('Mode.ModeEnum', 'javadoc/org/h2/engine/Mode.ModeEnum.html'); +pages[92]=new Page('Mode.ViewExpressionNames', 'javadoc/org/h2/engine/Mode.ViewExpressionNames.html'); +pages[93]=new Page('Architecture', 'html/architecture.html'); +pages[94]=new Page('CreateCluster', 'javadoc/org/h2/tools/CreateCluster.html'); +pages[95]=new Page('JdbcLob.State', 'javadoc/org/h2/jdbc/JdbcLob.State.html'); +pages[96]=new Page('SettingsBase', 'javadoc/org/h2/engine/SettingsBase.html'); +pages[97]=new Page('FullTextLucene.FullTextTrigger', 'javadoc/org/h2/fulltext/FullTextLucene.FullTextTrigger.html'); +pages[98]=new Page('FullText.FullTextTrigger', 'javadoc/org/h2/fulltext/FullText.FullTextTrigger.html'); +pages[99]=new Page('SessionLocal.State', 'javadoc/org/h2/engine/SessionLocal.State.html'); +pages[100]=new Page('H2 Database Engine', 'html/cheatSheet.html'); +pages[101]=new Page('AggregateFunction', 'javadoc/org/h2/api/AggregateFunction.html'); +pages[102]=new Page('Restore', 'javadoc/org/h2/tools/Restore.html'); +pages[103]=new Page('Aggregate', 'javadoc/org/h2/api/Aggregate.html'); +pages[104]=new Page('QueryStatisticsData.QueryEntry', 'javadoc/org/h2/engine/QueryStatisticsData.QueryEntry.html'); +pages[105]=new Page('DeleteDbFiles', 'javadoc/org/h2/tools/DeleteDbFiles.html'); +pages[106]=new Page('ConvertTraceFile', 'javadoc/org/h2/tools/ConvertTraceFile.html'); +pages[107]=new Page('GeneratedKeysMode', 'javadoc/org/h2/engine/GeneratedKeysMode.html'); +pages[108]=new Page('org.h2.engine', 'javadoc/org/h2/engine/package-summary.html'); +pages[109]=new Page('org.h2.jdbc Class Hierarchy', 'javadoc/org/h2/jdbc/package-tree.html'); +pages[110]=new Page('JdbcSavepoint', 'javadoc/org/h2/jdbc/JdbcSavepoint.html'); +pages[111]=new Page('Upgrade', 'javadoc/org/h2/tools/Upgrade.html'); +pages[112]=new Page('JdbcDataSourceFactory', 'javadoc/org/h2/jdbcx/JdbcDataSourceFactory.html'); +pages[113]=new Page('MetaRecord', 'javadoc/org/h2/engine/MetaRecord.html'); +pages[114]=new Page('JdbcXid', 'javadoc/org/h2/jdbcx/JdbcXid.html'); +pages[115]=new Page('JdbcBatchUpdateException', 'javadoc/org/h2/jdbc/JdbcBatchUpdateException.html'); +pages[116]=new Page('org.h2.tools', 'javadoc/org/h2/tools/package-summary.html'); +pages[117]=new Page('JdbcException', 'javadoc/org/h2/jdbc/JdbcException.html'); +pages[118]=new Page('org.h2.jdbc', 'javadoc/org/h2/jdbc/package-summary.html'); +pages[119]=new Page('JdbcLob', 'javadoc/org/h2/jdbc/JdbcLob.html'); +pages[120]=new Page('CastDataProvider', 'javadoc/org/h2/engine/CastDataProvider.html'); +pages[121]=new Page('QueryStatisticsData', 'javadoc/org/h2/engine/QueryStatisticsData.html'); +pages[122]=new Page('Session.StaticSettings', 'javadoc/org/h2/engine/Session.StaticSettings.html'); +pages[123]=new Page('JdbcStatementBackwardsCompat', 'javadoc/org/h2/jdbc/JdbcStatementBackwardsCompat.html'); +pages[124]=new Page('Quickstart', 'html/quickstart.html'); +pages[125]=new Page('IndexInfo', 'javadoc/org/h2/fulltext/IndexInfo.html'); +pages[126]=new Page('org.h2.engine Class Hierarchy', 'javadoc/org/h2/engine/package-tree.html'); +pages[127]=new Page('org.h2.api', 'javadoc/org/h2/api/package-summary.html'); +pages[128]=new Page('UserToRolesMapper', 'javadoc/org/h2/api/UserToRolesMapper.html'); +pages[129]=new Page('SimpleRowSource', 'javadoc/org/h2/tools/SimpleRowSource.html'); +pages[130]=new Page('UserBuilder', 'javadoc/org/h2/engine/UserBuilder.html'); +pages[131]=new Page('JavaObjectSerializer', 'javadoc/org/h2/api/JavaObjectSerializer.html'); +pages[132]=new Page('Session.DynamicSettings', 'javadoc/org/h2/engine/Session.DynamicSettings.html'); +pages[133]=new Page('CredentialsValidator', 'javadoc/org/h2/api/CredentialsValidator.html'); +pages[134]=new Page('Procedure', 'javadoc/org/h2/engine/Procedure.html'); +pages[135]=new Page('Engine', 'javadoc/org/h2/engine/Engine.html'); +pages[136]=new Page('Features', 'html/security.html'); +pages[137]=new Page('org.h2.tools Class Hierarchy', 'javadoc/org/h2/tools/package-tree.html'); +pages[138]=new Page('Installation', 'html/installation.html'); +pages[139]=new Page('Archive Downloads', 'html/download-archive.html'); +pages[140]=new Page('TableEngine', 'javadoc/org/h2/api/TableEngine.html'); +pages[141]=new Page('Deprecated List', 'javadoc/deprecated-list.html'); +pages[142]=new Page('org.h2.jdbcx', 'javadoc/org/h2/jdbcx/package-summary.html'); +pages[143]=new Page('org.h2.fulltext', 'javadoc/org/h2/fulltext/package-summary.html'); +pages[144]=new Page('SessionLocal.Savepoint', 'javadoc/org/h2/engine/SessionLocal.Savepoint.html'); +pages[145]=new Page('org.h2.jdbcx Class Hierarchy', 'javadoc/org/h2/jdbcx/package-tree.html'); +pages[146]=new Page('org.h2.api Class Hierarchy', 'javadoc/org/h2/api/package-tree.html'); +pages[147]=new Page('SessionLocal.TimeoutValue', 'javadoc/org/h2/engine/SessionLocal.TimeoutValue.html'); +pages[148]=new Page('JdbcConnectionPoolBackwardsCompat', 'javadoc/org/h2/jdbcx/JdbcConnectionPoolBackwardsCompat.html'); +pages[149]=new Page('JdbcDataSourceBackwardsCompat', 'javadoc/org/h2/jdbcx/JdbcDataSourceBackwardsCompat.html'); +pages[150]=new Page('JdbcConnectionBackwardsCompat', 'javadoc/org/h2/jdbc/JdbcConnectionBackwardsCompat.html'); +pages[151]=new Page('JdbcDatabaseMetaDataBackwardsCompat', 'javadoc/org/h2/jdbc/JdbcDatabaseMetaDataBackwardsCompat.html'); +pages[152]=new Page('org.h2.fulltext Class Hierarchy', 'javadoc/org/h2/fulltext/package-tree.html'); +pages[153]=new Page('Downloads', 'html/download.html'); +pages[154]=new Page('org.h2.engine', 'javadoc/org/h2/engine/package-frame.html'); +pages[155]=new Page('org.h2.jdbc', 'javadoc/org/h2/jdbc/package-frame.html'); +pages[156]=new Page('H2 Database Engine', 'html/main.html'); +pages[157]=new Page('org.h2.tools', 'javadoc/org/h2/tools/package-frame.html'); +pages[158]=new Page('org.h2.api', 'javadoc/org/h2/api/package-frame.html'); +pages[159]=new Page('org.h2.jdbcx', 'javadoc/org/h2/jdbcx/package-frame.html'); +pages[160]=new Page('org.h2.fulltext', 'javadoc/org/h2/fulltext/package-frame.html'); +// words: 7266 +ref['_']='__not__=136;_currently_=24,0;_int2=22;_int4=22;_messages_=40;_ROWID_=15,10,22,5;_text_=40'; +ref['a']='abandoned=22;ability=29,20;able=16,5,13,7,20,22,15,101,103;abnormal=5;abort=h19,r0;abort_session=h6,r22;aborted=6,5;above=20,5,15,17,40,10,7;ABS=h6,r22,2;absence=20,1,10,7;absent=23,6,10;absolute=h3,9,r6,0,2,11,21,22,28,17,100,5;absolutely=20,136;abstract=47,35,54,129,123,61,101,103,120,117,140,128,131,133,66,7,93,119;abstraction=h17,93,r22;aca899=100;accept=22,26,21,20,0,11,7,5;acceptable=22,5;ACCEPTANCE=20;accepted=22,32;access=h5,7,r11,0,2,17,4,29,18,39,19,15,124,16,6,93,8,1,10,13,116,52,34,62,108;ACCESS_DENIED_TO_CLASS_1=h2,r0;ACCESS_MODE_DATA=7,2;AccessControlException=11;accessed=0,8,15,3,9,5,17,30,34,11,7;accessible=5,1,32,10;accessing=2,22,0,7,5;accidental=7;ACCOMPANYING=20;accordance=20;according=2,19,17,1,10,38,0,15;accordingly=6;account=15,6,40,11;accu=15;accumulated=15,17;AccuProcess=29;accurate=20,6;Achieve=h5;achieved=7,5;ACID=h5;acme=5,1,7,15,6,58;ACME_SVN1651_BUILD3=24;acos=h6;acquire=20;acquired=24,0;acquisition=1;across=4,0,15,6;acting=20;action=h10,r4,20,54,0,32,61,29,1,11;ACTION_ORIENTATION=12;ACTION_TIMING=12;ActionEvent=64,0;ActionListener=64,137,56;actionPerformed=h64,r0;active=49,5,32,0;actively=5;activemq=h11;activities=29;acts=20,7;actual=21,6,1,24,5,32,20,0,41,17;actually=12,37,15,1,7,48,17,5;Adam=68;Adamo=68;adapter=54,116,0,11,5,138;aDateAndTime=6;add=h1,101,103,r22,0,14,11,2,17,27,9,18,20,7,16,4,47,39,40,13,25,44,46,29,6,15,124,5,45;ADD_PLAN_INFORMATION=60,84,66,48,52,35,67;addBatch=h13,16,r8,0;addColumn=h9,r7,0,11;addConnectionEventListener=h44,r0;addDatabaseObject=h18,r0;added=17,11,15,10,20,22,101,9,103,46,5,30,32,2,26,51,1,58,124;addiction=4,0;adding=h11,r9,7,17,10,5,38;addIntLong=6;addition=20,17,7,5,4,0,11,38;additional=h20,7,5,r12,23,15,21,22,0,6,26,1,30,28,17,4,11,90,38,117,34,50,118;additionally=15,20,24,7;addLocalTempTable=h14,r0;addLocalTempTableConstraint=h14,r0;addLocalTempTableIndex=h14,r0;addProcedure=h14,r0;address=h5,r15,7,6,2,11,29,12,28,1;ADDRESS_VIEW=1;addresses=5,29,34,94;addRow=h9,r7,0,11;addSavepoint=h14,r0;addSchemaObject=h18,r0;addStatementEventListener=h44,r0;addString=6;addSuppressed=74,83,80,81,73,77,79,76,82,75,78,115;addTemporaryLob=h25,14,47,r0;addWords=h27,r0,39;Adeptia=29;adjust=11;adjusted=6,17,1,11;admin=h1,r6,48,2,0,12,5,11;ADMIN_RIGHTS_REQUIRED=h2,r0;administration=29,7;administrator=5,11,2,34,0,7;ADO=29;advanced=t5,r29,11,34,7;advantage=5,15,11;adversarial=136;ADVISED=20;aed9a4f6=17;Aejaks=29;aes=7,10,5,6,2,87,17;affect=1,7,20,16,10,4,32,0,11,26;affected=16,13,1,8;Affero=20;afterLast=h3,9,r0;afterwards=5,15,11,40,4,7;again=5,11,29,34,116,0,17,1,72,10,38;against=h5,r20,63,0,17,2,29,32,116,11,136,34,15;aggregate=t23,103,h1,35,r0,2,6,10,101,22,21,127,37,12,29,60,84,66,48,146,5,52,56,67,158;AGGREGATE_NOT_FOUND_1=h2,r0;aggregated=101,103;AggregateFunction=t101,r21,0,1,146,158,56,127;aggregateName=1;agile=29;ago=21;agree=20;agreement=20;aimed=22;AIOBE=22;Ajax=29;Alan=68;Alessio=68;algorithm=h5,r7,17,2,6,72,0,15,10,29,22,116,11,87,57,68,1;algorithmString=6;alias=h1,10,136,r2,7,0,4,11,22,35,26,38,5,21,27,39;aliasColumnName=h26,r0;aliased=7,26,22,0;aliases=24,22,0,1,7;aliasing=4,0;aliasName=1,7;align=100;alive=7;alleged=20;alleging=20;allNumericTypesHavePrecision=h26,r0;Allocate=18,93,0;allocated=17,6,10,11;allocateDirect=17;allocateObjectId=h18,r0;allocation=h93,r17;allow=0,11,2,29,17,26,22,7,34,20,5,6,15,1,118,142,93,14,116,21,149,54,151,148,150,123,90,136,87;ALLOW_DUPLICATES_WITH_ALL_NULLS=h85;ALLOW_DUPLICATES_WITH_ANY_NULL=h85;ALLOW_LITERALS=h1,r5,2,14;ALLOW_LITERALS_ALL=h24,r0;ALLOW_LITERALS_NONE=h24,r0;ALLOW_LITERALS_NUMBERS=h24,r0;allowBigDecimalExtensions=2,0;allowDB2TimestampFormat=h26,r0;allowed=23,0,1,7,55,26,52,24,21,16,2,37,5,85,6,10,4,8,13,60,84,35,11,25,18,20,17,34,59,3;ALLOWED_CLASSES=h28,r0;allowedClasses=28,5,0,2;allowEmptyInPredicate=h26,r0;allowEmptySchemaValuesAsDefaultSchema=h26,r0;allowing=11;allowNonRepeatableRead=h55,r0;allowPlusForStringConcat=h26,r0;allowUnrelatedOrderByExpressionsInDistinctQueries=h26,r0;allowUserRegistration=7;allowUsingFromClauseInUpdateStatement=h26,r0;allProceduresAreCallable=h4,r0;allTablesAreSelectable=h4,r0;alone=20,116,0,72,94;alphabetic=69;alphabetical=96,0,69;already=h7,r2,0,11,14,18,17,6,34,27,1,5,32,16,25,49,50,68,40,33;alter=h1,10,r2,0,22,20,4,26,7,16,52,8,13,12;ALTER_ANY_SCHEMA=h52,r0;alterIdentityColumnOption=1;alternative=29,7;Alternatively=1;alterSequenceOption=1;alterTableExtensionsMySQL=h26,r0;alterTableModifyColumn=h26,r0;although=59,20;always=h89,r4,10,1,16,5,17,15,31,2,7,42,11,12,59,0,6,27,38,37,19,53,60,21,30,32,20,22,40,14;alwaysQuote=123,16;ambiguities=22;ambiguous=4,6,2;AMBIGUOUS_COLUMN_NAME_1=h2,r0;America=20;among=93;amount=15,17,6,2,7,10,11,5;analysis=29,40;analyze=h1,15,r32,38,24,0,6,40,63;ANALYZE_AUTO=32,0;ANALYZE_SAMPLE=32,0;analyzeAuto=h32,r0;analyzed=14,0,1;Analyzer=40;analyzeSample=h32,r0;analyzing=29,32;andCondition=10;Android=17,28,0;angle=6;annotation=h69;Announced=24,0;annual=5;ano=2;anonymous=5,28;another=h11,r2,0,5,7,10,16,6,1,13,21,4,8,15,14,18,108,29,20,94,38,34,62;ANSI=93,7,5;Ansorg=68;answers=40;Ant=29;Anthony=68;Antonio=68;anyone=20;anything=10,4,1,136,0,20,3;anyway=2,0,10;anywhere=10,5;Anzo=29;apache=h11,40,r29,22,7,143,20,0,39,38;ApacheMQ=11;apos=22;app=29;apparatus=20;appear=2,15,1,11,124,5,69,0,6,38;append=7,5,60,45,35,32,0,15,17,1,6,11;appended=1,17,32,23;appending=5;AppFuse=29;applicable=20,6,12,2,31;application=h11,124,15,20,r7,29,5,2,17,1,38,0,28,68,32,49,88,50,27,69,39,40,116,44,9,59,62,112,57;ApplicationName=19;applied=1,7;applies=32,69,7,5,38;apply=20,24,5,0,11,7;applying=58,0;appName=7;appropriate=61;appropriateness=20;approximate=h10,r4,6,12;approximately=15;approximateNumeric=10;arbitrary=13,21,0;arc=6;Architectural=29;architecture=t93,r29,5;Archive=t139,h153,r11;area=38,15,17,7,46,124,5;areEqual=h14,r0;aren=5,55,59,1,10,2,0,6;arg=34,62,11,106,102,94,90,88,87,50,63,71,105,49,5;argument=6,23,36,34,22,7,102,88,106,94,87,71,105,0,26,50,63,2,90,21,92,17,95,45,101,89,103,86,91,37;Arial=100;arising=20;arithmetic=22;army=29;arose=20;around=5,10,29,32,22,15,28,23,6,7;ARRAY_AGG=h23;ARRAY_APPEND=h6;ARRAY_CAT=h6;ARRAY_CONTAINS=h6;ARRAY_ELEMENT_ERROR_2=h2,r0;array_element_reference=6;ARRAY_GET=h6;ARRAY_LENGTH=6;ARRAY_MAX_CARDINALITY=h6,r22;ARRAY_SLICE=h6,r7;array_to_string=22;arrayElementReference=10;arrayExpression=6;ArrayIndexOutOfBoundsException=22;ArrayList=18,27,0,25,48,35,47,67,14;arrayType=10;Articles=h29;artifactId=40,100;Arun=68;AS_IS=h92;AS_LOCATOR=12;ASC=10,4,0,12;ASC_OR_DESC=4;ascending=1,32,23,10,4;ASCII=h6,r0,8,13;Ashwin=68;asin=h6;asked=t38,r11;Aspect=29;asserting=20;AssertionError=22;Asset=29;assign=2,7,0,10,20;assignable=19,16,49,3,4,9,33,31,53;assigned=11,128,59,10,7;assignment=7,21,22,1,8,13,10;AssignRealmNameRole=7;associated=20,11,5;Assorted=22;asString=h27,r0,39;assume=20,136,14;assumed=21,1;assumption=17;assurances=20;ASYMMETRIC=10,5;async=5,7;AsynchronousFileChannel=5;atan=h6;atan2=h6;Atom=153;atomic=5;atomically=5;atomicity=h5,r11;attached=20,34,0;attachment=40;attack=5,24,28,6,0,17;attacker=5,17;attempt=2,0,20,22,7;attempted=16;attention=21;attnum=22;attribute=35,0,6,11,19,2,22;attributeNamePattern=4;attributeNoNulls=4;attributeNullable=4;attributeNullableUnknown=4;attributesString=6;Australia=68;Austria=68;auth=h128,133,r7,18,11,130,146,56;AUTH_CONFIG_FILE=h28,r0;authConfigFile=28,0;authenticate=7;authenticated=7;authentication=h7,r128,0,130,5,43,50,133,25,127,18;AuthenticationException=128;authenticationInfo=128,133,130,0;authenticator=18,0,2,7;AUTHENTICATOR_NOT_AVAILABLE=h2,r0;authIdentity=7;author=68;AUTHORIZATION=1,5;authorized=20;AUTHREALM=2,0,7;auto=h7,107,r0,19,9,25,47,14,32,16,4,13,8,1,58,29,28,17,6,11,59,100;AUTO_COMPACT_FILL_RATE=32,22,0;AUTO_INCREMENT=7,26,0,16,10,4,13;AUTO_RECONNECT=7,5,38;AUTO_SERVER=7,38;AUTO_SERVER_PORT=7;autoClose=9;AutoCloseable=47,19,16,25,3,13,9,14,8,56,126;autocommit=h1,6,5,r2,25,47,7,19,15,22,0,17,11;autocommit_false=11;autocommit_true=11;autoCommitBufferSize=17;autoCommitDisabled=17;autoCommitFailureClosesAllResultSets=h4,r0;autoCommitIfCluster=h25,r0;autoCompactFillRate=h32,r0;autocomplete=7;autoGeneratedKeys=16,19;autoincrement=31,0;autoIncrementClause=h26,r0;automate=29;automated=h40,r38,29;automatic=h7,r17;automatically=7,11,15,1,5,107,38,32,20,17,6,10,0,16,2,27,39,100,40,13,124;automation=29;availability=h5,r29;available=h38,r6,20,16,1,7,28,11,3,17,54,5,40,59,0,98,49,15,61,2,19,68,97,32,22,129,69,13,12,90;aValue=6;average=23,12,21,32,17,1;AVERAGE_EXECUTION_TIME=12;AVERAGE_ROW_COUNT=12;averageSize=22;AVG=h23;avoid=22,24,7,1,5,20,0,6,11,59,15,40,10;avoided=15,40,38;aware=20,15;awt=64,137,56,116,0;Axiom=29'; +ref['b']='backed=17,65,116,0,15,9;backend=22,15,11;Backgammon=29;background=100,17,28,5;backgroundExceptionHandler=17;backslash=10,7,30;backslashes=7;backspace=62;backup=t88,h11,1,7,17,r0,2,29,116,102,58,157,56,38,137,63,71;Backus=24,0;backward=37,32,22,28;bad=5,21,15,6,11;Bahrain=15;balancing=5;Banking=29;bar=69,1;Barcelona=15;base=h11,r6,0,42,7,65,12,43,10,28,26,38,29,21,2,4,34,96,1,5,108;BASE_TYPE=4;BASE_VALUE=12;based=h7,93,r29,12,18,1,0,22,17,11,120,15,14,25,19,24,6,10,32,20,49,23,143,39,5,45,37,46,59;baseDataType=21;baseDir=5,34,28,22,2;baseName=18;baseNumeric=6;basic=h10,r12,29,9,5;basically=15;basicSequenceOption=10;basis=20;bat=5,100,40,11;batch=0,16,13,5,4,15,118,115,138;batched=16,13;BatchUpdateException=h115,r109,56;bDateAndTime=6;bean=11,130;become=2,20,6,0,49,11,1,5,16,7,19,15,68,13;becoming=20;been=0,2,14,66,59,38,25,15,1,20,24,49,9,19,58,28,17,11,47,51,69,3,5,18;beforeFirst=h3,9,r0;begin=h14,r0,22,1;beginning=4,0,30,6,11,7;behalf=20;behave=1,7;behavior=9,0,17,1,2,5,21,22,6,7,38;behaviour=59;being=1,5,121,59,22,131,0,28,7,127;Belgium=68;believes=20;Bellotti=68;belong=2,0,24,37,12,46,51,42,17;belonging=116,0,11,105;below=34,5,20,11,124,21,12,32,15,17,7;BenchA=15;BenchB=15;BenchC=15;benchmark=h15,r29;BenchSimple=15;beneficial=20;benefit=15;Berger=68;Berkeley=17;Berlin=22,11;Berlini=68;Berne=15;best=4,12,0,1,5,38,136;best_row_identifier=11;bestRowNotPseudo=4;bestRowPseudo=4;bestRowSession=4;bestRowTemporary=4;bestRowTransaction=4;bestRowUnknown=4;better=7,22,17,5,59,15,38;between=h10,r6,1,7,22,23,11,57,5,21,0,2,37,32,24,28,17,15,25,136,93,59;betweenPredicateRightHandSide=10;BGBlitz=29;big=17,7,38;BigDecimal=3,9,0,8,2,21,13,22,17;bigger=4,0,15;BIGINT=h21,41,r12,6,15,10,0,23,1,26,7,5;BigInteger=7,17;bigintType=10;BIGSERIAL=7,26,0;Bilingual=68;billion=5;bin=11,17,38,138;binary=h21,23,41,153,r6,12,0,22,10,2,14,59,7,1,20,5,24,11,26,17,4,15,40;BINARY_COLLATION=22;binary_double=22;binary_float=22;BINARY_VARYING=1;binaryLargeObjectType=10;BinaryOperation=22;binaryType=10;binaryVaryingType=10;bind=h5,r22,28,33;BIND_ADDRESS=h28,r0;bindAddress=28,0,5;bindDnPattern=7;biodiversity=29;biological=29;birthday=5;bit=6,52,5,0,57,17,21,14,22,7,54,61,26,98,28,97;BIT_AND=23;BIT_AND_AGG=h23,r6;BIT_LENGTH=h6;BIT_NAND_AGG=h23,r22,6;BIT_NOR_AGG=h23,r22,6;BIT_OR=23;BIT_OR_AGG=h23,r6;BIT_XNOR_AGG=h23,r22,6;BIT_XOR_AGG=h23,r22,6;BITAND=h6,r23;BITCOUNT=h6,r22;BITGET=h6;BITNAND=h6,r22,23;BITNOR=h6,r22,23;BITNOT=h6;BITOR=h6,r23;BitSet=14,0,18;bitwise=6,23;BITXNOR=h6,r22,23;BITXOR=h6,r23;Bjorn=68;black=6,100;Bleyl=68;Blind=15;blob=h51,5,41,17,r0,8,9,3,13,21,19,4,15,22,24,2,7,16,109,6,1,33,31,119,44,110,56,114,46,42;BLOB_SEARCH=h24,r0;blobPattern=51;blobValue=6;block=5,17,24,0,1,6,16,8,13,11,124;BLOCKED=h99;BLOCKER_ID=12;blocking=12;blockSize=1,17;blockSizeInt=1;Blog=h29;blue=100;bnf=40,37,21,23,1,6,10;bob=1,11;body=100;bogus=5;bold=100;BOM=30,6;Bonita=29;bookkeeping=27,39;bookmarks=29;Books=h29;bool=21,22;BOOL_AND=23;BOOL_OR=23;booleanTestRightHandSide=10;booleanType=10;boost=15;boot=7;border=100;Borg=68;both=5,1,17,6,7,36,92,15,14,2,10,0,24,11,29,12,30,32,20,38;bottom=68,100;bound=h10,r6,7,14,29,11;boundaries=6;bounding=23,6,10;box=100,29,23,6;boxes=10;BPM=29;bracketed=h10,r22;bracketedComment=10;brackets=26,0,7;branch=114,0;branches=44,0;Branda=68;Brasil=68;break=1,40,7;bring=20,29;broader=136;broken=22,2,5,38;brought=20;browse=29,28;brute=28,5,17,6;Btrfs=17;bucket=6;bucketLong=6;buff=72,50,25,18;buffer=5,1,72,50,15,25,68,18,28,17;BUFFER_LENGTH=4;buffered=1,15,17,5;BufferedInputStream=22;BufferedReader=62,22,0;buffering=15;bug=h38,r40,22,28,50,68,136;build=t40,r24,0,22,17,7,29,60,84,48,2,11,52,35,130,38,67,15,1,5,138;BUILD_DATE=h24,r0;BUILD_ID=h24,r0;BUILD_SNAPSHOT=h24,r0;BUILD_VENDOR_AND_VERSION=h24,r0;builder=h17,r60,45,35,0;building=h40,r29,58,0,138;buildRelease=40;buildUser=h130,r0;built=h15,r11,7,5,68,2,29,0;builtin=1;BUILTIN_ALIAS_OVERRIDE=h1;Bukkit=29;bulk=11;bundle=11;bundled=5;bungisoft=20;burden=29;business=29,20,15;busy=5;bValue=6;bypassing=32;byte=h10,r0,6,51,8,9,3,72,1,21,17,131,13,43,18,12,48,4,50,25,5,16,114,24,26,2,7,59,30;bytea=21,22;ByteBuffer=17;bytecode=29;BZIP=2'; +ref['c']='C20000=2;C_NUMBER=h86;Cabinet=17;cache=h7,15,r10,14,24,0,28,17,12,32,25,18,11,1,21,6,2,38,50;CACHE_MAX_SIZE=7;CACHE_MIN_RECORDS=h24,r0;CACHE_SIZE=h1,r7;CACHE_TYPE=7,38;CACHE_TYPE_DEFAULT=h24,r0;cached=1,32,15,12,22,28;cacheSize=17,11;CacheSizeMax=11;caching=h17,r7;cafe=10;cal=8,9;calculate=6,5,1,15,7;calculated=24,5,37,0,17,23,6,15,7;calculating=5;calculation=23;calendar=3,8,0,13,9,21;call=h1,r6,2,11,0,7,16,4,5,8,15,19,3,25,22,17,18,32,28,47,27,39,40,10,14,38,34;callable=19,0,8,4,118;CALLABLE_STATEMENT=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;CallableStatement=h8,r19,4,0,109,56;called=0,61,2,54,1,58,14,7,3,17,25,101,103,47,27,18,11,5,44,22,39,31,21,20,66,98,16,24,28,49;caller=3,0;calling=0,2,13,24,7,14,16,6,4,11,19,15,5,22,17,49,34,87,27,39,1,3,105;CAN_ONLY_ASSIGN_TO_VARIABLE_1=h2,r0;cancel=h14,25,16,47,r0,2,8,6,13,3,11;CANCEL_SESSION=h6;canceled=14,0,2,6,5;cancelled=16,0;cancelRowUpdates=h3,9,r0;cancelStatement=h25,r0;candidates=22,17,5;cannot=7,5,1,21,59,10,22,6,15,16,25,2,47,38,3,36,14;CANNOT_CHANGE_SETTING_WHEN_OPEN_1=h2,r0;CANNOT_DROP_2=h2,r0;CANNOT_DROP_CURRENT_USER=h2,r0;CANNOT_DROP_LAST_COLUMN=h2,r0;CANNOT_DROP_TABLE_1=h2,r0;CANNOT_MIX_INDEXED_AND_UNINDEXED_PARAMS=h2,r0;CANNOT_TRUNCATE_1=h2,r0;capability=29;capacity=5,18;car=10;cardinality=h6,r12,21,22,24,4,0,5;care=5;cascade=1,10,12,2,0,7;CASE_INSENSITIVE=6;CASE_INSENSITIVE_IDENTIFIERS=32,7,0,10;CASE_SENSITIVE=4;caseExpression=10;caseInsensitiveIdentifiers=h122,32,r0;caseSensitiveColumnNames=30,6,10;CASEWHEN=h6;Casqueiro=68;cast=h10,r21,22,2,6,5,0,120,108;CastDataProvider=t120,h47,r19,18,25,14,0,56,126,109,154,108;castSpecification=10;CAT=10;catalina=38;catalog=h4,1,r12,0,19,18,31,2,26,32,11,5;CATALOG_NAME=12;catalogName=1;catalogPattern=4;catalogString=1;cataloguing=29;Catch=22;categories=69;CATEGORY=37,10;cause=2,22,6,20,77,76,75,78,74,83,80,81,73,79,82,11,19,0,7,38,15,1,40,10;caused=20;caution=1,6;Caveat=1;Cayenne=29;CBC=5;CDATA=6;CDO=29;cease=20;CEIL=h6,r22;CEILING=6;cell=2,0,6;Center=29;Central=h139,40;centric=29;Century=h10;centuryField=10;certain=5,7,15,20,26;certificate=5,136;certified=5;cfg=100;CFML=29;Chafik=68;chain=17,5;chaining=5;challenge=5;challenger=5;chance=5;change=t22,h10,40,68,r1,0,5,7,17,14,2,11,27,19,59,18,87,3,32,20,6,54,15,61,35,153,29,21,133,12;CHANGE_ID=h25,r0;changed=17,7,27,1,2,5,32,16,25,47,15,14,22,0,6,11,88,59,54,40,3;ChangeFileEncryption=t87,r0,7,137,116,157,11,56;changelog=22,40;changing=h7,r15,87,32,116,0,16,17,1,11;channel=5;char=h6,5,41,r21,30,0,89,87,22,26,7,33,2,59,43,17,4,108;CHAR_LENGTH=h6;CHAR_OCTET_LENGTH=4;character=h12,21,5,r10,6,0,30,8,13,3,2,4,41,46,59,22,27,7,32,24,11,26,1,92,20,17,95,63,45,89;CHARACTER_LENGTH=6;CHARACTER_MAXIMUM_LENGTH=12;CHARACTER_OCTET_LENGTH=12;CHARACTER_SET_CATALOG=12;CHARACTER_SET_NAME=12;CHARACTER_SET_SCHEMA=12;characteristics=h1,r5,15,17;characterLargeObjectType=10;characterType=10;characterVaryingType=10;charAndByteLengthUnits=h26,r0;charge=20;charPadding=t89,h26,r0,154,56,126,108;charset=30,1,63,6,10,0;CHARSET_=1;CHARSET_CP500=1;charsetString=1,10;Chartrand=68;Cheat=h100,r22;CHECK_CLAUSE=12;CHECK_COLUMN_USAGE=22;CHECK_CONSTRAINT_INVALID=h2,r0;CHECK_CONSTRAINT_VIOLATED_1=h2,r0;CHECK_CONSTRAINTS=h12;CHECK_OPTION=12;checkAdmin=h48,r0;checkCanceled=h14,r0;checkClosed=h19,25,r0;checked=6,1,10,14,24,23,0,17;checking=1,24,0,10;checklist=38,29,40;checkOpen=22;checkOwnsNoSchemas=h66,r0,48,67;checkpoint=h1,18,r0,15,5;checkPowerOff=h50,25,18,r0;checkRename=h60,84,52,35,r0,66,48,67;checkResults=63;checkSchemaAdmin=h48,r0;checkSchemaOwner=h48,r0;checkstyle=40;checksum=17,2,153,0,11,7;checkTableRight=h48,r0;checkWritingAllowed=h50,25,18,r0;chemical=29;Chen=68;CHF=2;child=2,17,10,0;childCounts=17;children=48,35,67,60,84,0,17,52;Chile=68;China=68;Chittanoor=68;choice=20,138,136;choose=20,11,12,16,1,13;choosing=15,136;chosen=23,16,15,13;CHR=6;Christian=49,11;Christos=68;chunk=h17,r32,22,1;CID=2;cipher=h10,r5,7,87,2,18,0,28,136;circumstances=20;city=15;claim=20,5;Claros=29;clashes=5;CLASS_NAME=4;CLASS_NOT_FOUND_1=h2,r0;classAndMethodString=1;ClassCastException=22;classification=h20,r29;classloaders=11;classname=1,11,18;classNameString=1;classpath=1,7,6,2,11,5,0,21,124;clause=h10,r1,7,0,26,37,2,22,4,23,12,15,59,6;Clean=22;cleanAuthenticationInfo=h43,r0;cleaned=87;cleanup=22,1;clear=0,16,13,19,43,3,14,20,17,38;clearBatch=h13,16,r0,8;cleared=17,5,14;clearing=17;clearly=15;clearParameters=h13,r8,0;clearViewIndexCache=h14,r0;clearWarnings=h19,16,3,9,r0,8,13;Clemens=68;click=11,124,5,38,15,21,23,6,37,1,100,40,10;clicking=69,5,11;client=h7,15,r19,0,5,8,28,11,2,13,12,1,29,22,25,3,21,6,127,133,14,108;CLIENT_ADDR=12;client_encoding=22;CLIENT_INFO=12;CLIENT_TRACE_DIRECTORY=h28,r0;ClientAccountingInformation=19;ClientCorrelationToken=19;clientTraceDirectory=28,0;ClientUser=19;Clinton=68;clipboard=40;clob=h46,5,41,r0,8,3,9,13,21,19,15,1,2,22,16,6,4,33,31,119,44,110,7,114,51,42,118,53,70;clobPattern=46;Cloneable=43,56,126;CloneNotSupportedException=43;closable=19,0;close=h13,9,19,14,16,25,47,44,7,3,129,30,98,97,61,r0,8,11,49,2,28,17,1,15,100,4,27,39,124,38;CLOSE_ALL_RESULTS=16,8,13;CLOSE_CURRENT_RESULT=16,8,13;CLOSE_CURSORS_AT_COMMIT=4,19,3,9;closeAll=h27,r0,39;closed=h3,8,95,99,r13,16,19,31,7,4,0,1,2,25,11,5,9,17,15,14,6,47,58,30,32,61,38,34,87,100;closeImpl=22;closely=17,4;closeOldResultSet=h16,r0,8,13;closeOnCompletion=h16,r8,13,0;closeOthers=18;closer=6;CloseWatcher=19,0;closing=h7,r1,18,0,25,47,15,14,32,22,61,49,100,10;closingDatabase=h58,r0;cloud=29;clubs=21;cluster=h5,1,r25,0,24,2,94,47,14,18,116,11,38;CLUSTER_ERROR_DATABASE_RUNS_ALONE=h2,r0;CLUSTER_ERROR_DATABASE_RUNS_CLUSTERED_1=h2,r0;clustered=2,0,15,11;clustering=h5,r24,0,2,29,7,38;CLUSTERING_DISABLED=h24,r0;CLUSTERING_ENABLED=h24,r0;CMS=29;cnf=15;CNT=10;COALESCE=h6,r23,2,11,5;codebase=22,68;codebook=5;coded=5;codegen=11;coding=72,0,40;Codist=29;coefficient=23;Cognitect=68;COL=1;COL1=1,6,10;COL2=1;Cold=29;Coldrick=29;Collaborative=29;collapse=100;COLLATE=22;collation=h1,12,r2,0,28,7;COLLATION_CATALOG=12;COLLATION_CHANGE_WITH_DATA_TABLE_1=h2,r0;COLLATION_NAME=12;COLLATION_SCHEMA=12;collationName=1;collator=1,28,38;COLLATOR_CACHE_SIZE=h28,r0;collatorCacheSize=28,0;collect=10,7;collected=5,121,104,0,28,17,2,7,108;collection=18,23,128,11,29,28,17,68,5;COLLECTION_TYPE_IDENTIFIER=12;collectively=20;colNames=30;colon=11;color=100;COLUMN_ALIAS_IS_NOT_SPECIFIED_1=h2,r0;COLUMN_CONTAINS_NULL_VALUES_1=h2,r0;COLUMN_COUNT_DOES_NOT_MATCH=h2,r0;COLUMN_DEF=4;COLUMN_DEFAULT=12;COLUMN_IS_PART_OF_INDEX_1=h2,r0;COLUMN_IS_REFERENCED_1=h2,r0;COLUMN_MUST_NOT_BE_NULLABLE_1=h2,r0;COLUMN_NAME=h107,r4,12,0;COLUMN_NOT_FOUND_1=h2,r0;COLUMN_NUMBERS=h107,r0;COLUMN_ON_UPDATE=12;column_privileges=h12,r11;COLUMN_SIZE=4;COLUMN_TYPE=4;COLUMN_USAGE=4;columnAlias=10;columnConstraintDefinition=10;columnDefinition=1;columnIndex=h3,9,r8;columnIndexes=16,19;columnLabel=h3,9;columnlist=14,27,39,7;columnListString=27,39;columnName=1,10,16,19,3,9,5;columnNamePattern=4;ColumnNamer=22;columnNameString=6;columnNoNulls=4,31,9;columnNullable=4,31,9;columnNullableUnknown=9,31,53,0,4;columnsString=6;com=40,5,1,20,6,7,11,29,28,93,15,68,100,58;combination=20,2,5,22,0,54,11,98,61,7,38,15,1,97;combine=6,2,5,20,0,15,1,11;combined=36,6,5,20,0,10;comma=h11,r6,27,0,4,39,5,30,116,28,94,7,34,10;COMMAND_CLOSE=h25,r0;COMMAND_COMMIT=h25,r0;COMMAND_EXECUTE_QUERY=h25,r0;COMMAND_EXECUTE_UPDATE=h25,r0;COMMAND_GET_META_DATA=h25,r0;CommandInterface=25,3,13,47,14,0;commandString=16;commaSeparatedList=27;comment=t84,h35,10,1,15,r0,30,18,22,4,60,66,48,52,67,108,6,154,56,40,126;commentString=6;commercial=h20,38,r29,68;commit=h1,5,14,19,15,44,r0,25,2,17,4,47,22,16,7,8,13,28,12,32,6,38;COMMIT_ACTION=12;commit_delay=15;COMMIT_ROLLBACK_NOT_ALLOWED=h2,r0;committed=5,16,13,8,1,7,12,54,15,61,17;committing=1;common=17,2,5,1,29,20,27,15,11,7,38;CommonDataSource=49,33;commonly=28,5;communicates=25,1,14;communication=29,25;community=15;comp=1;compact=1,7,32,22,17;compacted=17,22,1,7;compacting=h7,r24,0,32,1;compaction=7,32,22;compactMode=18;companies=68;company=29,20;comparable=113,21,2,56,126,92,45,0,109,146,89,86,91,95,59,85,99,55;Comparator=h57,r137,56;compare=h10,14,57,r0,18,2,50,25,26,22,15;compared=17,21,15,1,5;compareMode=18,50,25,0;compareTo=h113,r95,92,45,0,85,99,89,86,55,91;compareTypeSafe=h14,r0;compareWithNull=h14,r0;comparing=h15,r57,10,29,21,32,1;comparingDouble=57;comparingInt=57;comparingLong=57;comparison=h10,15,r7,14,0,22,32,4,5,29,23,120,108;comparisonRightHandSide=10;compatibility=h7,5,r22,1,59,6,11,38,10,21,0,23,2,37,19,134,4,26,100,108;compatible=11,22,6,5,32,28,10,7;competitive=15;compilable=40;compilation=1,7;compile=0,118,7,142,150,29,149,123,28,151,148,5;compiled=h7,r24,0,15,1;compiler=28,1,7;CompileStatic=1;complete=7,5,20,22,0,24,43,11,40,38;completed=0,4,14;completely=29,17,5;complex=15,7,29,22,11,5;complexity=15,5;compliance=h5,r20,29;compliant=22,59,20,29,93,21,10,5;complicated=5;complies=20;comply=h20,r5;component=29,41,6,0;componentType=41;composed=29;compounds=29;compress=h6,72,r0,17,32,2,24,116;compressData=h32,r0;compressed=72,17,0,2,5,116,6,11,7;Compresses=6;compressHigh=17;compressing=5;compression=h10,r6,2,11,22,0,17,1,7;COMPRESSION_ERROR=h2,r0;compressionAlgorithm=72;CompressTool=t72,r0,137,116,157,56;computed=h7,r0,101,103,57;computer=5,11,7,34,29,20,15,124;Computes=6;Computing=68;CONCAT=h6,r22;CONCAT_WS=h6,r22;concatenated=6,26,7,0;Concatenates=23;concatenating=7;concatenation=10,22,6;concerning=20;CONCUR_=19;CONCUR_READ_ONLY=9,3,0;CONCUR_UPDATABLE=3,9;concurrency=h5,r19,0,2,4,16,3,24,7,29,1;concurrent=h17,11,r5,19,22,18,1,55;CONCURRENT_UPDATE_1=h2,r0;ConcurrentHashMap=18;concurrently=17,7,72,3,5;ConcurrentModificationException=22,17;condition=h10,20,r1,22,5,27,15,6,32,0,4;conditional=10;conditionally=5;ConditionNot=22;conditionRightHandSide=10;conf=15,7;config=7;configurable=h128,133,r17,146,56;configuration=h5,r11,18,7,15,28,17,40;configurationFile=7;configure=128,15,133,7;configured=107,21,0,15,11;configuring=15;confirm=11;confirmed=38;conflict=2,20,0,26,7;conflicting=10,5;conforms=33;confusing=5;confusion=22,2,7;conjunction=20;conn=h16,r27,39,54,11,2,7,5,98,50,61,34,97,62,100,30,49,63,101,103,46,51,42,71,70,0,3,8,15,13;CONN_URL_COLUMNLIST=h24,r0;CONN_URL_INTERNAL=h24,r0;connect=h5,38,r11,2,7,0,34,58,29,32,28,90,15,124;connected=18,29,0,6,11,5;connectEmbeddedOrServer=h25,r0;connecting=h11,7,r58,5,34,0,90;CONNECTION_BROKEN_1=h2,r0;connectionClosed=h49,r0;connectionErrorOccurred=h49,r0;ConnectionEvent=49,0;ConnectionEventListener=49,44,0,145,56;ConnectionInfo=t43,r0,25,135,18,154,56,126,108;ConnectionPoolDataSource=33,49,145,0,56;connecturl=15;consequential=20;consider=15,40,5,11,7;considerable=59;Considerations=h5;considered=10,1,37,21,30,6,22,38;consistency=h5,r1,17,11,7;consistent=5,1,11;consisting=24,0,69,6;consists=15,17;console=t90,h11,124,5,64,138,r0,24,22,116,7,34,15,29,28,38,40,157,56,137,100;CONSOLE_MAX_PROCEDURES_LIST_COLUMNS=h28,r0;CONSOLE_MAX_TABLES_LIST_COLUMNS=h28,r0;CONSOLE_MAX_TABLES_LIST_INDEXES=h28,r0;CONSOLE_STREAM=h28,r0;CONSOLE_TIMEOUT=h28,r0;consoleProcedureColumns=28,0;consoleStream=28,0;consoleTableColumns=28,0;consoleTableIndexes=28,0;consoleTimeout=28,0;Consortium=29;CONST=5;CONST_ID=10;constant=t24,h1,55,92,95,45,89,86,91,85,99,2,35,0,5,12,69,r25,6,58,52,107,61,28,108,22,15,60,84,66;CONSTANT_ALREADY_EXISTS_1=h2,r0;CONSTANT_CATALOG=12;CONSTANT_NAME=12;CONSTANT_NOT_FOUND_1=h2,r0;CONSTANT_SCHEMA=12;constantName=1;constitutes=20;constraint=h1,10,35,93,r2,12,14,0,22,21,7,6,11,26,15,32,24,5,60,84,66,16,48,13,52,67;CONSTRAINT_ALREADY_EXISTS_1=h2,r0;CONSTRAINT_CATALOG=12;CONSTRAINT_COLUMN_USAGE=h12,r22;CONSTRAINT_IS_USED_BY_CONSTRAINT_2=h2,r0;CONSTRAINT_NAME=12,1,2;CONSTRAINT_NOT_FOUND_1=h2,r0;CONSTRAINT_SCHEMA=12,2;CONSTRAINT_TYPE=12,1;constraintName=1;constraintNameDefinition=10,1;construct=0,35,49,60,84,48,52,26,67,140;constructed=6,23,1;constructing=29,5;construction=29,22,11;constructorParam=1;construe=20;construed=20;consult=5;Consulting=68;contained=20,40;container=11,29;containing=0,18,92,45,89,86,91,95,85,99,55,29,5,21,20,22,6,27,1;CONTAINS_UNCOMMITTED=12;containsKey=h96,r32,0;containsUncommitted=h14,r0;content=h141,59,r7,50,0,29,17,6,5,22,39,63,14,32,20,11,138;contentString=6;context=11,33,112,7,5,0,28,1,10;continue=1,16,44,13,5,20,22,0,61,11,63,7;continued=68,63;continueOnError=63;continuous=17;contract=20;contribute=h38,r20,40;contributing=22,40;Contribution=20,40;Contributor=20;control=h20,5,r2,29,22,0,7;controlled=20,5;convention=33;conversion=22,2,21,0,16,43,1,6,10,7;convert=h6,r0,2,4,57,22,1,26,7,27,39,10,32,21,106,116,11,15;converted=8,13,122,6,10,0,26,1,32,2,7,21,12,30,20,11,38,39,5;converter=22;convertException=h39,r0;converting=10,2,0,7,26,6,11;convertOnlyToSmallerScale=h26,r0;ConvertTraceFile=t106,r0,15,7,137,116,157,11,56;conveyed=20;cooperate=20;coordinate=6,23;copies=88,20,94,5;Copilot=68;copy=20,0,11,5,29,60,84,48,17,4,52,35,113,67,94,40;copying=1,11;copyright=20,22;copyrighted=20;Core=4,0;corporate=29;CORR=h23;correct=2,22,29,19,20,17,40,101,103,5;corrected=7;correction=20;correctly=22,11,5,28;correlated=22,4,0;correlation=4,0,23;corresponding=11,69,101,4;corrupt=17,5,88,22,38;corrupted=h7,r50,0,2,116,11,5;corruption=7,22;cos=h6;cosh=h6;cosine=6;cost=20,15,24,1,0,5,17,7;COST_ROW_OFFSET=h24,r0;COT=h6;cotangent=6;count=h15,23,104,r16,0,42,65,13,1,8,22,25,2,18,9,5,6,57,12,121,11;countdown=25,18;counted=17,37,10;counter=2,20,0;counterclaim=20;counting=17;couple=29,59;court=20;COVAR_POP=h23;COVAR_SAMP=h23;covariance=23;cover=40;coverage=40,38,7;covered=20;COW=17;CP850=62;cpu=15,63,5;cracking=17;crash=59,22,15,5;crashes=10;CrashPlan=29;CREATE_PARAMS=4;createArrayOf=h19,r0;createBlob=h19,r51,0;createClob=h19,r46,0;CreateCluster=t94,h5,r0,137,116,157,11,56;createConnection=h14,r0;created=6,5,7,1,0,15,4,2,9,16,17,11,46,3,51,30,24,38,54,13,124,61,101,90,103,136,140,19,34,100;CREATEDATE=1;createIndex=h27,39,r0;createMissingRoles=7;createNClob=h19,r46,0;createNewFile=5;createPgServer=h34,r0,5;createResultSet=h27,r0,39;createSession=h135,r0;createSQLXML=h19,r0;createStatement=h19,r0,24,49,2,11,7,5;createStruct=h19,r0;createTable=h140,r1,0,7;CreateTableData=140,7,0,1;createTcpServer=h34,r11,0,2;createTempFile=7;createUniqueConstraintForReferencedColumns=h26,r0;createVersion=17;createWebServer=h34,r0;creating=h11,7,r0,15,1,58,5,2,26,22,17,135,38,88;creation=11,7,20,22,2,29,19,32,0,15,1;creatively=5;credential=133,7,0,127;CredentialsValidator=t133,r0,146,158,7,56,127;Cristan=68;criteria=1,7;cross=10,20,29,5;cross_references=11;crossed=6;cryptographic=5;cryptographically=5,17,6;cryptoloop=5;css=6;csv=t30,h11,10,r0,6,116,29,40,22,129,157,7,56,137,15,100,5;csvOptions=6;CSVREAD=h6,r11,32,10,5;CSVWRITE=h6,r11,10,5;cte=1,5;cte1=1;cte2=1;ctid=26,0,7;Ctrl=11,15;ctx=33;CUME_DIST=h37,23;cumulative=6;CUMULATIVE_EXECUTION_TIME=12;CUMULATIVE_ROW_COUNT=12;curation=29;cure=20;currency=21,31,0;current=h3,0,17,r14,10,33,19,6,1,30,25,2,37,16,18,47,5,120,9,7,12,26,13,22,132,28,11,8,35,38;CURRENT_CATALOG=h6,r5;CURRENT_DATE=h6,r21,1,5;CURRENT_PATH=h6,r22,5;CURRENT_ROLE=h6,r22,5;CURRENT_SCHEMA=h6,r5;CURRENT_SEQUENCE_VALUE_IS_NOT_DEFINED_IN_SESSION_1=h2,r0;CURRENT_TIME=h6,r1,5;CURRENT_TIMESTAMP=h6,r1,19,25,7,5,120,14,18;CURRENT_USER=h6,r5;currently=h15,r5,18,17,11,0,6,16,2,7,1,10,12,4,38,93,40;currentTimeMillis=15;currentTimestamp=h19,25,14,18,120,r0,22,47;currentTimeZone=h19,25,14,18,120,r0,47;CURRVAL=h6,r26,0,7,59;cursor=3,0,16,4,9,22,5;curve=29,7;CurveFit=29;custom=h7,r0,25,120,19,14,18,26,127,1,21,140,131,5;customarily=20;CUSTOMER=15,20;CUSTOMER_ID=15;CustomerId=5;customizable=5;Customizing=5;cut=5;CVE=22;cycle=10,22;CYCLE_OPTION=12;cycled=12;cycling=2,0;Cyr=68;Czech=68'; +ref['d']='D_POSITIVE=1;daemon=34,7;damage=20;Dan=68;dangerous=38,1,2,11;Daniel=68;Darri=68;dash=7;dashes=6;DATA_CONVERSION_ERROR_1=h2,r0;DATA_SOURCE=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;DATA_TYPE=12,4,22;DATA_TYPE_SQL=h6,r22;DATABASE_ALREADY_OPEN_1=h2,r0;DATABASE_CALLED_AT_SHUTDOWN=h2,r0;DATABASE_EVENT_LISTENER=h1,r2,58;DATABASE_IS_CLOSED=h2,r0;DATABASE_IS_IN_EXCLUSIVE_MODE=h2,r0;DATABASE_IS_NOT_PERSISTENT=h2,r0;DATABASE_IS_READ_ONLY=h2,r0;DATABASE_META_DATA=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;DATABASE_NOT_FOUND_1=h2,r0;DATABASE_NOT_FOUND_WITH_IF_EXISTS_1=h2,r0;DATABASE_PATH=h6;database_that_does_not_exist=2;DATABASE_TO_LOWER=7,32,22,0,100,10;DATABASE_TO_UPPER=32,22,0,10;DatabaseEventListener=t58,r0,18,146,158,56,127;databaseLocker=11;DatabaseMeta=25,47,14;DatabaseMetaData=h4,r22,19,11,0,109,3,56,38;databaseName=7,5;DatabasePlatform=11;databaseToLower=h122,32,r0;databaseToUpper=h122,32,r0;databaseType=7;dataBytes=6;dataChangeDeltaTable=10;dataDefinitionCausesTransactionCommit=h4,r0;dataDefinitionIgnoredInTransactions=h4,r0;Dataflyer=29;DataHandler=25,18,50,56,47,126,14,137;DataNucleus=29;datasource=49,33,11,145,56,29,112,0,100,142,5;DATASOURCE_TRACE_LEVEL=h28,r0;dataSourceTraceLevel=28,0;datatype=10,21,1,22,4,26,59,0;dataTypeOrDomain=1,6,10;date=h6,10,21,41,11,20,r0,8,3,9,2,13,24,7,23,4,22,17,27,15,39,1,5,38,68;DATE_TRUNC=h6,r22,10;DATEADD=h6,r22,10;dateAndTime=6,10;DATEDIFF=h6,r22,10;datetime=h10,r7,12,26,21,22,0,23,6,11;DATETIME_PRECISION=12;datetimeField=6;DateTimeFormatter=6,22;dateTimeValueWithinTransaction=h26,r0;dateType=10;David=29;day=h10,21,45,r36,0,6,41,20,40,5;DAY_OF_MONTH=h6;DAY_OF_WEEK=h6,r10;DAY_OF_YEAR=h6,r10;DAY_TO_HOUR=h45;DAY_TO_MINUTE=h45;DAY_TO_SECOND=h45;dayInt=10;daylight=38,21;DAYNAME=h6;dayOfMonthField=10;DAYOFWEEK=10;dayOfWeekField=10;DAYOFYEAR=10;dayOfYearField=10;db1=7;DB2=h7,91,15,r1,5,26,19,0,2;db4o=15;DB_CLOSE_DELAY=h1,r7,22,15,100;DB_CLOSE_ON_EXIT=32,7,0;DB_NAME=1;DB_OBJECT_ID=h6;DB_OBJECT_SQL=h6;dBase=29;dbbench=15;dbbench2=15;dbCloseOnExit=h32,r0;DBCP=11;DbException=14,25,48,18,19,66,0,102;DbListener=58;dbMeta3=7;DBMS=29,26;dbName=7;DbObject=t35,h52,60,84,48,67,66,r0,18,113,154,56,126,108;DBs=5;dbserv=7;DbSettings=t32,r0,43,18,96,154,7,56,126,108;DbStarter=11;DbUnit=29;DbUpgrade=22;DbVisualizer=29;DCL=22;ddl=14,140,22,16,8,13,5,93,7;dead=22;deadlock=h7,r2,0,22,24,28,5;DEADLOCK_1=h2,r0;DEADLOCK_CHECK=h24,r0;deal=21;dealing=20;death=20;debug=7,25,0,1;debugCode=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;debugCodeAssign=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;debugCodeCall=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;debugging=34,25;DEC=21,59;Decade=h10;decadeField=10;December=68;decent=136;DECFLOAT=h21,41,r23,10,22,0,59,1,5;decfloatType=10;decimal=21,23,26,0,59,15,17,2,4,7;DECIMAL_DIGITS=4;decimalSequences=h26,r0;declaration=69,59,6,1;declarative=29;declaratory=20;declare=95,92,45,85,11,99,89,86,55,91;declared=12,0,89,7,92,45,86,91,95,85,99,55,22,6,59,10;DECLARED_DATA_TYPE=12;DECLARED_NUMERIC_PRECISION=12;DECLARED_NUMERIC_SCALE=12;declaring=h7;DECODE=h6,r7;decompression=22;decoration=100;decrement=25,18;decremented=17;decrementing=10;decrypt=h6,r87,7;decrypted=5,50;decrypting=h7,r5;decryption=87;decryptPassword=87;deductive=29;deep=15;DEF=1;DEFAULT_=1;DEFAULT_CHARACTER_SET_CATALOG=12;DEFAULT_CHARACTER_SET_NAME=12;DEFAULT_CHARACTER_SET_SCHEMA=12;DEFAULT_COLLATION_NAME=12;DEFAULT_CONNECTION=32,0;DEFAULT_ESCAPE=32,0;DEFAULT_HTTP_PORT=h24,r0;DEFAULT_LOCK_MODE=h24,r0;DEFAULT_LOCK_TIMEOUT=h1,r7;DEFAULT_MAX_LENGTH_INPLACE_LOB=h24,r0;DEFAULT_MAX_OPERATION_MEMORY=h24,r0;DEFAULT_NULL_ORDERING=h1,r7,22;DEFAULT_ON_NULL=12;DEFAULT_PAGE_SIZE=h24,r0;DEFAULT_RESULT_SET_CONCURRENCY=h24,r0;DEFAULT_SQL_FLAGS=60,84,66,48,52,35,67;DEFAULT_TABLE_ENGINE=32,0,1;default_table_type=h1,r15;DEFAULT_TCP_PORT=h24,r0;DEFAULT_WRITE_DELAY=h24,r0;DefaultAuthenticator=28;defaultConnection=h32,r0;defaultEscape=h32,r0;defaultNullOrdering=18,0;defaultTableEngine=h32,r0;defaultTableType=18;defaultValue=96,43,37;defective=20;defects=20;defend=20;defendant=20;defense=20;DEFERRABILITY=4;deferrable=4;deferred=10;define=10,22,2,0,1,28,127;defined=h7,11,r0,2,1,5,127,14,18,22,24,28,4,8,13,12,35,3,20,17,6,54,10,61,101,103,38,68,108;defining=1,7,5;definitely=31,0;definition=h10,1,20,r14,6,5,7,22,0;Deflate=10,17,2,6,72;defrag=1,32,22,0,7;DEFRAG_ALWAYS=32,0;defragAlways=h32,r0;defragment=17;defragmented=32,0;degree=h6,r5;deinterleave=h57,r0;delay=h5,r28,1,24,15,0,17;DELAY_WRONG_PASSWORD_MAX=h28,r0;DELAY_WRONG_PASSWORD_MIN=h28,r0;delayed=h15,7;delayWrongPasswordMax=28,0,5;delayWrongPasswordMin=28,0,5;delegated=22;delete=h1,61,52,r0,4,2,10,15,12,54,8,16,13,5,32,98,7,3,105,97,60,84,22,48,14,35,67,28,6,11;DELETE_RULE=4,12;deleted=2,5,1,0,3,7,28,10;DeleteDbFiles=t105,r0,137,116,157,11,7,56;deleter=25,18,50;deleteRow=h3,9,r0,2;deletesAreDetected=h4,r0;deleting=0,2,143,52,98,27,39,97;deletion=20;delicious=29;delimited=30;delimiter=30,0,10,7;delivered=29;delta=h10,r22,59;demand=30,129,116,0,1;denial=5;denied=11;Denmark=68;dense=37,23;DENSE_RANK=h37,23;depend=6,18,21,1,10,7,5,0,15,68,4,11;dependencies=22,40,124,138;dependency=40,100,29;dependent=1,16,0,48,35,67,23,60,84,52,32,2,7,18;dependentExpression=23;depending=6,18,17,1,30,59,22,0,10,11,7,5;deployment=7;depth=15,63;derby=h15,7,91,r5,1;derivation=5;derivative=20;derived=22,14;derivedTable=14;DES=2;DESC=10,4,0,12;descending=1,22,23,10,4;descent=93;describe=12,20,15,5;described=20,21,69,6,7,5;describing=26,0;descriptor=1,5;deserialization=21,5;DESERIALIZATION_FAILED_1=h2,r0;deserialize=h131,r0,1;deserialized=21;deserializing=28;design=93;designed=136;desirable=20;desired=7;desktop=29;destroy=11;destroyed=5;destruction=2,0;detailed=69,20,124;detect=h5,r3,0,59,28,1;detected=2,0,4,17;detection=22;determination=23;determine=0,26,107,85,1,6,10,108;determined=23,15,1,136;determining=20;deterministic=1,12,22,23;develop=38;developed=15;developer=29,69,15,68,44;developing=68,29;development=29,24,68,11;deviation=23,12;device=5,1;Dfile=62;Dh2=5,28;Dh2auth=7;DHIS=29;diabetes=29;diagram=h40,r37,21,22,69,23,1,6,10;dialect=11,100;diamonds=21;Dick=68;dictionary=24,0,6,40,5;did=3,20,0,40,124,5;died=5;Dieguez=68;dies=5;diff=29;differ=20,38;difference=h6,17,r7,93,1,10;different=5,2,7,0,4,6,15,60,84,22,48,11,52,35,67,17,26,21,93,1,10,14,38,19,68,20,138,59;DiffKit=29;Digest=5,22;digit=h10,r23,6,7,30,22,2;Digital=29;digitsInt=6;dim=57;dimension=57,21,17,0,7,22,10;dimensional=h7,r57,0,22,116;Dimitrijs=68;Dinamica=29;dir=87,50,105,7,43,28,88,34,102,100,62;direct=20,1,15,66,16,90,35,47,96,27,69,13,5,119;direction=3,16,0,9,20;directly=17,1,7,5,93,32,20,22,112,15;directories=2,5;directory=h138,r5,38,11,2,88,102,28,0,7,43,87,100,40,20,24,6,50,105,106,34,62,15,124;Dirty=55,5;disable=0,1,30,5,6,32,16,25,7,14,28,17,11,34,10;disabling=h5,r38,15;disadvantage=7;disallow=22,10;disallowed=26,7,0;disallowedTypes=h26,r0;disappear=5;Discard=26,0;discarded=7;discardWithTableHints=h26,r0;disclaimer=h20;disclaims=20;Disconnect=h124,r1,11;disconnected=34,5,18;Disconnecting=h11;discontinue=68;discouraged=8,3,13;discovered=22;disk=17,1,5,15,7,18,93,32,0,136;DISK_SPACE_USED=h6;display=11,31,20,69,0,15,1,5;displayed=10,11;Displaying=h15;dispose=h49,r0,100,11;distinct=h10,r23,1,2,7,15,0,22,24,26,5,32;distinctPredicateRightHandSide=10;distinguish=24,4,21,0,17,10;distinguished=10;distinguishing=20;distribute=20,37,100,38;distributed=20,29,37,21,0,6,44,142,5;distributing=h20;distribution=h20,23,139,r40;distributors=20;District=29;divide=2,0;dividend=6;dividendNumeric=6;divisible=37;division=22;DIVISION_BY_ZERO_1=h2,r0;divisor=6;divisorNumeric=6;Djava=7;dml=16,22,13,26,93,0,7,5;DOAP=153;doc=138,22,40;doclet=69;docsrc=40,138;doctrines=20;document=h69,r21,20,22,40,11,5,38;DOCUMENT_ID=10;documentation=h100,r22,5,24,2,11,38,59,138,21,12,20,69,15,1,40,10;documented=12,32,22;doesMaxRowSizeIncludeBlobs=h4,r0;doesn=2,0,22,15,7,1,14,48,12,6,26,38,5,21,25,17,11,47,50,62,40,10;doing=68,10;dollar=h10,r1,7;dollarQuotedString=10;domain=h1,12,35,10,r6,22,2,0,32,29,60,84,66,48,52,67;DOMAIN_ALREADY_EXISTS_1=h2,r0;DOMAIN_CATALOG=12;DOMAIN_CONSTRAINTS=h12;DOMAIN_DEFAULT=12;DOMAIN_NAME=12,6;DOMAIN_NOT_FOUND_1=h2,r0;DOMAIN_ON_UPDATE=12;DOMAIN_SCHEMA=12,6;domainName=1,10;don=h7,r1,22,11,5,38,21,0,17,23,15,124,2,108,20,6,26,88,27,40,10,58,45,101,37,34,87,59,62,3;Donald=68;donation=29;Donators=68;done=h25,r5,11,21,17,57,30,20,22,0,61,28,110,54,1,40,58,14;donor=68;dose=29;dot=10;DOTALL=6;double=h21,38,r6,0,23,10,9,3,8,5,104,12,30,57,11,22,41,1,13,32,28,17,4,15,40,7,100;DOUBLE_PRECISION=h41,r0;doublePrecisionType=10;doubt=1,5,18,0,11;DOW=10,7;down=h93,r21,0,24,28,7,1,5,14,22,6,11,34,15;download=t153,139,r40,100,5;downloaded=138;DOY=10;DpropertyName=5;drafter=20;drastic=7;drastically=15;drive=5,15,22,40,2,38;driven=29,116;driver=h5,11,93,r111,4,0,1,16,15,18,29,22,7,3,32,6,90,38,62,63,124,28;DRIVER_VERSION_ERROR_2=h2,r0;DriverManager=2,7,11,5,4,32,0,15,100,38;driverString=1,6;drop=h1,r2,0,32,16,4,8,22,13,27,39,14,11,52,35,15,12,7;DROP_RESTRICT=32,0;dropAll=h27,39,r0;dropIndex=h27,39,r0;dropped=1,2,0,61,6,11,7,14;dropping=5;dropRestrict=h32,r0;dsl=11;DSLContext=11;DSN=5;dsName=33;DST=6,21,22;DTD_IDENTIFIER=12,6;dual=2,20,1,22;due=h20,r7,19,21,22,0,17,6,2,11,38,69,5,14;dummy=7,2,22;dump=50,0,15,17,40,7;dump_from_1_4_200=1;dumpDomains=22;dumping=1;duplicate=7,22,21,6,1,10,2,0,26,5;DUPLICATE_COLUMN_NAME_1=h2,r0;DUPLICATE_KEY_1=h2,r0;DUPLICATE_PROPERTY_1=h2,r0;durability=h5,15,r7;durable=15;Duration=21;during=22,58,0,1,21,25,47,5,14,17,6,44,11,7,127,128,15,111;Duske=68;Dutch=29;dynamic=47,0,25,132,14,29,12,129,7,108;dynamically=1,5;DynamicSettings=t132,r0,25,47,14,154,56,126,108'; +ref['e']='each=15,20,17,1,5,7,69,10,0,11,32,6,54,101,103,57,22,24,72,4,61,2,38,21,30,16,26,27,39,13;earlier=20,17,1;early=1,2;ease=29,68;easier=17,68,5;easiest=29,15,7;easily=29,17,9,7;easy=29,5,38,15,7;Ebean=29;ECB=5;ECCN=h20;ece9d8=100;Eckenfelder=68;EclEmma=40;eclipse=h40,20,r29,19,0,11;eclipseCodeStyle=40;EclipseLink=h11;ECM=29;edit=5,29,11;editing=11;Edition=17;editor=29,7;edugility=40;EEE=6;effect=h20,r1,21,19,7,32,15,10,44,55;effective=h20,r1;effectively=20,11;efficient=29,93,15,17,7;efficiently=17;either=23,20,10,5,17,1,6,11,7,38;elapsed=49;elect=20;electronic=5;element=h10,r6,21,42,11,19,69,23,2,59,12,22,0,28,41,1;ELEMENT_TYPES=h12;elementString=6;eliminate=5;Elisabetta=68;else=10,3,0,5;Elton=11;email=1,40,2,29,11;embedded=h7,15,11,r5,0,25,2,93,14,47,100,63,124,29,17,38,136,1,71,108;Embedding=h124;embeds=5;emergency=88;EMF=29;emit=1;empty=h86,r0,9,19,22,4,6,2,7,28,26,27,1,10,42,16,13,46,32,11,12,51,71,21,88,15,31,5;emulate=15,7;emulated=7;emulation=26;enable=5,7,1,6,11,0,16,30,29,19,14,32,28,15;ENABLE_ANONYMOUS_TLS=h28,r0;ENABLE_CLEAR_REFERENCES=38;enableAnonymousTLS=28,0,5;enabled=0,11,7,1,5,14,2,29,12,24,28,25,26,58,30,17,6,9,15,10;enabling=1,2,19,0,15;Encapsulates=0,43,108;enclosed=1,10,5;encloses=23,6;Enclosing=92,104,65,147,98,132,89,86,144,91,95,85,122,99,97;encoded=6,17,2,5;Encodes=6;encoding=h5,r6,28,30,22,62,24,10,11,7;encodingString=6;encrypt=h6,r7,87,5;encrypted=h17,7,136,r5,2,34,0,87,50,11;encrypting=h7,r17,5,136;encryption=h7,5,10,r2,87,0,17,116,24,100,11;ENCRYPTION_KEY_HASH_ITERATIONS=h24,r0;encryptionKey=17;encryptPassword=87;end=h6,10,1,44,124,r23,21,37,12,0,17,4,11,20,22,15,58,5,7,3,29,30,32,40,59;ending=15,5;EndlessTable=7;endStatement=h14,r0;enforce=15,23,5;enforce_size=15;enforceability=20;enforceable=20;enforced=12,1,5;England=68;English=1,6,29,43,2;Enhance=22;enough=2,10,0,15,17,6,40,5;enquoteIdentifier=h16,123,r0,8,2,13;Enquotes=123,16,0;ensure=5,15,17,40,7,38;ensured=1;enter=11;enterprise=29,11;entire=20,10,11,5;entities=20,5;entity=20,6;entries=17,24,0,1,22,69,15,11;entry=17,11,104,0,4,96,72,7,69,40,124,5,14;entryName=72;enum=h45,92,95,89,86,91,85,99,55,21,69,41,109,146,56,126,155,154,158,r0,12,2,22,120,14,25,19,18,5,127;ENUM_DUPLICATE=h2,r0;ENUM_EMPTY=h2,r0;ENUM_IDENTIFIER=12;ENUM_VALUE_NOT_PERMITTED=h2,r0;ENUM_VALUES=h12;enumerated=2,21,0;enumeration=22;enumerators=2,0;enumType=10;env=1;envelope=h23,r5;environment=h40,r11,29,112,5,21,116,0,7,38,136,64,1;EPL=20,40;Epoch=h10,r6;epochField=10;equality=15,10,14;equally=38;equalsIdentifiers=h18,r0;equipment=20;equivalence=5;equivalent=55,6,0,10,1,20,38;Erik=68;err=62;error=h2,0,11,r22,16,17,1,40,23,5,7,117,62,28,63,38,77,20,76,6,74,81,73,69,79,82,10,75,78,127;ERROR_ACCESSING_LINKED_TABLE_2=h2,r0;ERROR_CREATING_TRIGGER_OBJECT_3=h2,r0;ERROR_EXECUTING_TRIGGER_3=h2,r0;ERROR_OPENING_DATABASE_1=h2,r0;ERROR_SETTING_DATABASE_EVENT_LISTENER_2=h2,r0;errorCode=t2,h0,r77,76,75,78,74,83,80,81,73,79,82,141,146,56,127,158;escape=10,30,2,0,4,32,16,19,11;escapeCharacter=30;escaped=10,7,30;escapeNewlineBoolean=6;escaping=30,1,10,7;escString=10;essential=11,20,100;established=69,49;estimate=15,17,22,6,5;estimated=32,1,6,5;ESTIMATED_ENVELOPE=h6;ESTIMATED_FUNCTION_TABLE_ROWS=32,0;estimatedFunctionTableRows=h32,r0;estimateMemory=22;estimating=29;estimation=6;estoppel=20;etc=12,6,2,29,22,0,23,40,111;eternal=22;ETL=29;Europe=10,22,1,11,38;Eva=10;evaluate=22,10,1,11;evaluated=10,1,15,7,16,13;evaluating=6;evaluation=10,1,2,0;event=64,44,0,49,58,56,137,2,18,29,20,7,127,1,5;EVENT_MANIPULATION=12;EVENT_OBJECT_CATALOG=12;EVENT_OBJECT_SCHEMA=12;EVENT_OBJECT_TABLE=12;eventListener=58,18,64,49,146,56;ever=29;every=h23,r5,1,20,0,24,48,17,7,38,6,15,10;Everyone=20;evolving=29;EWKB=21,10,22;EWKT=21,10;exact=h10,r17,23,16,13,7;exactly=10,95,92,45,85,23,99,89,86,55,91;exactNumeric=10;example=h6,10,2,1,15,17,r21,23,5,7,11,37,0,27,34,20,39,40,30,26,35,38,32,22,24,93,88,58,33,12;exceed=1,7;except=1,20,7,10,5,17,18,6,11,26,15,0,4,69;EXCEPTION_IN_FUNCTION_1=h2,r0;EXCEPTION_OPENING_PORT_2=h2,r0;exceptionThrown=h58,18,r0;excessive=1;exchange=20;exclude=10,11,59,20,15,23,18;excluded=10,1,18;excluding=20,0,24,43,57,18,32,17,6,10,7;exclusion=20,21,10;exclusive=h1,r7,2,18,20,0,14,11,22,17,5;exclusively=18,0;exe=5;executable=h20,r29,40;execute=h16,63,1,13,7,102,88,50,94,87,105,124,r0,2,8,6,5,29,11,40,57,15,58,32,121,49,10,116,19,62;EXECUTE_FAILED=16,8,13;executeBatch=h13,16,r0,8;executed=16,5,1,0,13,104,24,11,15,10,21,12,121,22,28,7,19,63,18;executeLargeBatch=h13,16,r0,8;executeLargeUpdate=h16,8,13,r0;executeQuery=h13,16,r2,8,0,5,15,7;executeUpdate=h16,8,13,r0,2;executing=8,13,7,5,16,6,11,12,15,40,18;EXECUTING_STATEMENT=12;EXECUTING_STATEMENT_START=12;execution=h93,15,r104,12,0,25,1,14,29,16,2,47,19,10,13,22,11,7,120,8,63,5,55,18;EXECUTION_COUNT=12;executionTimeCumulativeNanos=h104,r0;executionTimeMaxNanos=h104,r0;executionTimeMeanNanos=h104,r0;executionTimeMinNanos=h104,r0;executionTimeNanos=121;executor=19,0;EXEMPLARY=20;exercise=20;exercising=20;EXHAUSTED=10;exhausting=10;Exhibit=h20;exist=h1,7,r2,0,18,10,14,5,6,11,16,27,15,13,24,4,26,8,39,38,21,20,50,25,44,67,59,3;existence=5,22;existing=1,0,7,2,34,10,11,27,39,40,5,18,32,22,19,15;existingValue=14;existingWindowName=10;exit=h7,r62,1,2,11,32,0,90;exp=h6,r5;expand=h72,6,r0,116,2;expandable=22;expanded=11,59;expect=38;expected=2,5,0,1,62,15,63,124;expedites=29;expense=20;expensive=5,29,6;experimental=h7,r5,32,128,133,17,2,38;experts=5;expired=14,0;expires=5;explain=h1,r15,5,38,7;explaining=22;explicit=h1,r10,22,17,2,7;explicitly=24,0,10,14,17,1,7,20,123,16,11,5;explicitTable=10;exploit=20;Explorer=28;expNumber=10;exponential=17;export=h20,r29,59,22,38;exported=1,22;exported_keys=11;expose=136,7;exposed=h136;express=20;expressed=20;expression=h10,23,1,15,r6,0,2,5,26,22,86,12,7,4,37,59,60,32,14,35,108,21,92,16,11,13;expressionNames=t86,h26,r0,154,56,126,108;expressly=20;ext=138;extended=21,0,142,4,22,9;extension=h11,29,r5,127,0,10,2,7;extent=20;external=h7,5,r12,1,128,34,0,28,40,4,127,138;EXTERNAL_LANGUAGE=12;EXTERNAL_NAME=12;externalizable=69;externally=7,15,136;extract=h6,r22,5,29,10,7,136;extracting=116,0,102,71;Extraneous=95,92,45,85,99,89,86,55,91;extras=22'; +ref['f']='F262=22;Fabien=68;Fabric3=29;facade=7;facilitate=20;facility=7,30,116,0;fact=20,15,5;factor=h10,r15,7;factory=112,11,22,0,33;factual=20;fail=22,16,13,1,5,20,8,2,93,28,17;failed=2,22,0,15,17,5;Failing=7;failure=27,39,34,5,30,50,63,71,88,94,101,62,20,22,102,106,123,25,90,103,87,46,131,105,70,18,129,16,111,7;fair=h20;falls=5;false=4,1,10,0,32,28,9,3,16,14,31,5,26,7,2,6,11,34,18,15,38,19,21,30,22,133,13,111,12,107;familiar=40,38;FAQ=h20,r40;far=29,59,20;fast=h15,r17,29,11,7,93,156,68,6,4,5;faster=15,5,22,17,68,1,6,10,11,7;fastest=29,7;FAT=5;FAT32=5,138;fault=29;feature=t7,136,h38,5,17,r22,40,68,2,11,13,29,32,156,3,21,0,28,128,59,133,1,100;FEATURE_NOT_SUPPORTED_1=h2,r0;Feb=6;federated=29;federation=29;Fedora=15;Fedotovs=68;fee=20;feed=153,30;feedback=68;fetch=1,3,0,16,2,5,7,22,28,25,6,11,47,14;FETCH_FORWARD=9,3,0,16;FETCH_REVERSE=3,9;FETCH_SIZE=1,22;FETCH_UNKNOWN=3,9;fetched=1;fetchSize=h16,r25,47,14,22,0,8,13;few=15,40,7,29,22,17,68,10,11,5,38;ff795bf6ccefd5950d5080b596d835d13206b325=153;fff=100;ffffff=100;FIELD_NAME=12;fieldDelimiter=30,10;fieldDelimString=10;fieldName=21,10;fieldReference=10;fieldSeparator=6,10;fieldSeparatorRead=30;fieldSeparatorWrite=30;fieldSepString=10;fieldTypes=41;fifty=20;FILE_BLOCK_SIZE=h24,r0;FILE_CORRUPTED_1=h2,r0;FILE_CREATION_FAILED_1=h2,r0;FILE_DELETE_FAILED_1=h2,r0;FILE_ENCRYPTION_ERROR_1=h2,r0;FILE_LOCK=7,5,2,38;FILE_NOT_FOUND_1=h2,r0;FILE_READ=h6;FILE_RENAME_FAILED_2=h2,r0;FILE_VERSION_ERROR_1=h2,r0;FILE_WRITE=h6;fileBase=22,5;FileChannel=5,22,15,7;FileChannelInputStream=17;filed=20;FileDescriptor=5,15,38;filename=17,71,63,5,1,88,22,102;fileNameString=6,1;filePassword=2,5;filePasswordUserPassword=2;FilePath=5;FilePathNio=22;FilePermission=11;filepwd=7;FileReadCount=11;FileSize=11;fileStore=50,25,17,18,93,22;filesystem=h93,r22,136;FileWriteCount=11;FileWriteCountTotal=11;fill=32,46,0,51,2,40,5;filled=48,5;filling=7;fillInStackTrace=74,83,80,81,73,77,79,76,82,75,78,115;filter=h23,r1,11,22,4,7,5;FILTER_CONDITION=4;filtered=5;filterString=23;finalizer=2;finally=20,5;find=5,29,2,11,7,15,22,17,6,38,1,40,10,18;findColumn=h3,9,r0;findComment=h18,r0;Finder=29;findIntersectingKeys=17;findLocalTempTable=h14,r0;findLocalTempTableConstraint=h14,r0;findLocalTempTableIndex=h14,r0;findRole=h18,r0;findSchema=h18,r0;findSetting=h18,r0;findUser=h18,r0;findUserOrRole=h18,r0;Finish=40,5;finished=12,49;Finland=68;fire=h54,98,97,61,r0,7,2;Firebird=h15;fired=7,1;Firefox=40,138;firewall=h11,r124,2;FIRST_COLUMN=1;FIRST_NAME=15,11;FIRST_VALUE=h37,r10,2;FirstName=15,10;fit=17,10,5,108,29,20,0,23,36;FIT4Data=29;fitness=20;fitting=29;five=4;fix=22,40;fixed=21,22,38,59,0,24,6,4,26,5,108;FIXED_PREC_SCALE=4;FixedPasswordCredentialsValidator=7;fixes=40,38;FK_NAME=4;FKCOLUMN_NAME=4;FKTABLE_CAT=4;FKTABLE_NAME=4;FKTABLE_SCHEM=4;flag=44,6,1,2,22,60,0,35,59,61,7;flagsString=6,7;Flash=29;fletcher=17;flexibility=29;flexible=29,17;fleXive=29;flipped=5;float=h38,r0,3,9,21,8,27,39,13,17;FLOAT4=21;FLOAT8=21;floating=21,7;FLOOR=h6,r22;Florent=68;fluent=29;fluid=17;flush=h18,r0,15,1,5,62,25;flushes=1,5;flushing=15,1;Flux=29;Flyway=29;FOG=7;folder=5,43,7,29,0,6,40;follow=11,5,17,38,15,40,10,12,92,45,22,49,2,89,7,86,91,95,85,69,99,55;followed=3;following=5,11,10,17,15,7,40,2,20,27,39,30,69,33,138,38,37,32,0,16,6,1,13,9,19;font=100;FOO=1;footer=17;footprint=7;FOR_UPDATE_IS_NOT_ALLOWED_IN_DISTINCT_OR_GROUPED_SELECT=h2,r0;FORBID_ANY_DUPLICATES=h85;forbidCreation=19;forbidden=19;forBitData=h26,r0;force=1,34,5,15,28,6,59,17,2,7;FORCE_AUTOCOMMIT_OFF_ON_COMMIT=h28,r0,2;FORCE_JOIN_ORDER=22;forceAutoCommitOffOnCommit=28,0;forceBinary=22;forceBoolean=6;forEach=74,83,80,81,73,77,79,76,82,75,78,115;foreign=4,2,0,10,15,1,7,5;foreignCatalog=4;foreignSchema=4;foreignTable=4;forEquality=14;forget=h44,r0,11,7;forgot=28;forgotten=24,2;form=h20,69,r15,2,17,11,33,6,32,0,24,40,5,21,77,76,74,81,73,79,82,1,115,75,78,19,83,80;format=h17,59,r6,2,0,21,7,30,114,10,5,22,24,11,26,4,50,15,18;FORMATDATETIME=h6,r22;formatString=6;formatted=21,6,40,2;formatting=40,60,35,15;formed=68;formula=7;forth=20,29;forward=22,3,7;found=3,8,2,6,22,0,38,5,51,20,17,9,19,15;Foundation=20;four=6,40,10;fractional=21,12,6,10,7,45,0,2,36;fractionalPrecisionInt=21,10;framework=29,11;France=68;Frank=68;free=h65,42,119,r17,29,20,0,49,46,51,70,156,6,11,7,38,15,100,40;freeing=27,0;freely=5;freeze=22;frequency=23;frequently=t38,r7;fresh=59;Freshmeat=29;Friend=29;friendly=29,20;FROM_1X=1,59;fromJdbc=h55,r0;fromLockMode=h55,r0;fromSql=h55,r0;fromType=4;front=29,69,17,1;Frontend=h29;fsync=5,15;FT_CREATE_INDEX=27,11;FT_DROP_ALL=27;FT_DROP_INDEX=11;FT_INIT=27,11,22;FT_REINDEX=27;FT_SEARCH=27,11;FT_SEARCH_DATA=11;FTL=39;FTL_CREATE_INDEX=11,39;FTL_DROP_ALL=39,11;FTL_DROP_INDEX=11;FTL_INIT=11,39;FTL_REINDEX=39;FTL_SEARCH=32,39,11;FTL_SEARCH_DATA=11;ftp=11,5;full=0,27,39,143,45,4,11,15,29,7,1,22,17,125,5;FULL_VERSION=h24,r0;fulltext=t27,143,152,98,160,h11,39,r0,56,125,97,61,38,100;FullTextLucene=t39,97,r0,11,143,152,160,56,27,61;FullTextSettings=27,0;FullTextTrigger=t98,97,r0,143,152,61,160,56,27,39;fully=1,32,5,21,0,15,14,28,17,6,38;Fun=29;func=1;function=t6,23,37,h7,5,r0,22,1,2,10,4,26,103,24,11,101,35,86,127,32,27,15,39,12,57,100,29,28,14,9;FUNCTION_ALIAS=h35,r60,84,66,0,48,52,67;FUNCTION_ALIAS_ALREADY_EXISTS_1=h2,r0;FUNCTION_ALIAS_NOT_FOUND_1=h2,r0;FUNCTION_MUST_RETURN_RESULT_SET_1=h2,r0;FUNCTION_NOT_FOUND_1=h2,r0;Functional=29;functionAliasName=1;functionality=0,27,39,6,7,5;functionColumnIn=4;functionColumnInOut=4;functionColumnOut=4;functionColumnResult=4;functionColumnUnknown=4;FunctionMultiReturn=22;functionNamePattern=4;functionNoNulls=4;functionNoTable=4;functionNullable=4;functionNullableUnknown=4;functionResultUnknown=4;functionReturn=4;functionReturnsTable=4;FUNCTIONS_IN_SCHEMA=22;further=20,17,1,7;fusion=29;future=22,4,69,68,1,58,7,5;Fyodor=68;F\u00e1bio=68'; +ref['g']='games=29;gaps=37,23;garbage=5,28,17,6,68,2,7;Garringer=68;gathering=107,0,12,1,108;gave=68;GBIF=29;general=h23,20,59,r22,29,12;GENERAL_ERROR_1=h2,r0;generally=69,15,5;generate=7,93,15,57,40,86,0,1,6,10,5;generated=h7,r10,0,16,107,2,1,13,92,11,26,5,22,59,15,12,19,6,4,40,14,110,57,21,48,17,69,52,108;GENERATED_COLUMN_CANNOT_BE_ASSIGNED_1=h2,r0;GENERATED_COLUMN_CANNOT_BE_UPDATABLE_BY_CONSTRAINT_2=h2,r0;generatedColumnExpression=10;generatedKeyAlwaysReturned=h4,r0;generatedKeys=h16,r0,8,13;GeneratedKeysMode=t107,r0,154,56,126,108;generatedKeysRequest=107;generatePreparedQuery=h57,r0;generating=h40,r5,29,15;generation=h7,r26,0,22,10,108,29,12,92,6,11,86,93,59;GENERATION_EXPRESSION=12;GenerationTool=11;generator=11,7,22,17,6,5;Generic=11,124;generification=22;genetic=15,7;Genomics=29;GEO_TABLE=5;GEO_TABLE_SPATIAL_INDEX=5;geocoder=29;Geocoding=29;Geographic=29;geom=21;GEOM1=10;GEOM2=10;geometries=21,22,10;geometry=h21,10,41,r12,5,22,0,59,23,1,6,101;GEOMETRY_COLUMN=6;GEOMETRY_SRID=12;GEOMETRY_TYPE=12;GEOMETRYCOLLECTION=21,10;geometryType=10;GeoServer=29;geospatial=29;German=22;Germany=68;GET_JDBC_META=h25,r0;GET_LONG=2;GET_PROPERTY=5;GET_SYSTEM_PROPERTY=1;getActiveConnections=h49,r0;getAllComments=h18,r0;getAllowLiterals=h14,18,r0;getAllRights=h18,r0;getAllSchemas=h18,r0;getAllSchemasNoMeta=h18,r0;getAllSettings=h18,r0;getAllSynonyms=h18,r0;getAllTablesAndViews=h18,r0;getAllUsersAndRoles=h18,r0;getArray=h65,42,8,3,9,r0,21;getAsciiStream=h3,9,46,r0;getAttribute=h4,r0,11;getAuthenticator=h18,r0;getAutoClose=h9,r0;getAutoCommit=h19,25,14,47,r0;getBackgroundException=h18,r0;getBaseDir=h28,r0;getBaseType=h65,42,r0;getBaseTypeName=h65,42,r0;getBestRowIdentifier=h4,r22,0;getBigDecimal=h3,9,8,r0,141;getBinaryStream=h51,3,9,70,r0,5;getBlob=h8,3,9,r0;getBlockingSessionId=h14,r0;getBoolean=h8,3,9,r0;getBranchQualifier=h114,r0;getBufferedReader=22;getByName=7;getByte=h8,3,9,51,r0,22;getCacheType=h18,r0;getCancel=h14,r0;getCaseSensitiveColumnNames=h30,r0;getCatalog=h19,4,r0;getCatalogName=h9,31,r0;getCatalogSeparator=h4,r0;getCatalogTerm=h4,r0;getCause=74,83,80,81,73,77,79,76,82,75,78,115;getCharacterStream=h3,46,8,9,70,r0,141,5;getChildren=h48,67,35,r0,60,84,66,52;getClientInfo=h19,r5,0;getClientInfoProperties=h4,r0;getClientVersion=h25,r0;getClob=h8,3,9,r0;getCluster=h18,r0;getClusterServers=h25,14,47,r0;getColumnClassName=h9,31,r0;getColumnCount=h9,31,r0,11;getColumnDisplaySize=h9,31,r0;getColumnLabel=h9,31,r0,11,38;getColumnName=h9,31,r7,0,26,38;getColumnPrivileges=h4,r0;getColumns=h4,r22,0;getColumnType=h9,31,r0;getColumnTypeName=h9,31,r0,22;getCommandStartOrEnd=h14,r0;getComment=h35,r60,84,66,0,48,52,67;getCompareMode=h50,25,18,r0;getCompiler=h18,r0;getConcurrency=h3,9,r0;getConnection=h49,33,4,16,44,r0,2,11,7,5,8,100,13,32,22,38,15;getCreateSQL=h48,67,60,84,52,35,r0,66;getCreateSQLForCopy=h60,84,48,52,67,35,r0,66;getCreateSQLForMeta=h35,r60,84,66,0,48,52,67;getCrossReference=h4,r22,0;getCurrentCommand=h14,r0;getCurrentId=h25,r0;getCurrentSchemaName=h25,14,47,r0;getCurrentValueFor=h14,r0;getCurrentVersion=17;getCursorName=h3,9,r0;getDatabase=h35,14,r0,60,84,66,48,52,67;getDatabaseMajorVersion=h4,r0;getDatabaseMeta=h25,14,47,r0;getDatabaseMinorVersion=h4,r0;getDatabasePath=h50,25,18,r0;getDatabaseProductName=h4,r0;getDatabaseProductVersion=h4,r0;getDataHandler=h25,14,47,r0;getDate=h8,3,9,r0;getDays=h36,r0;getDbSettings=h43,r0;getDeclaringClass=95,92,45,85,99,89,86,55,91;getDefaultNullOrdering=h18,r0;getDefaultTableType=h18,r0;getDefaultTransactionIsolation=h4,r0;getDependentTable=h18,r0;getDescription=h33,r0;getDouble=h8,3,9,r0;getDriverMajorVersion=h4,r0;getDriverMinorVersion=h4,r0;getDriverName=h4,r0;getDriverVersion=h4,r0;getDropSQL=h35,r60,84,66,0,48,52,67;getDynamicSettings=h25,14,47,r0;getEnum=h26,r0;getErrorCode=h117,r77,76,75,78,74,83,80,81,73,79,82,0,115;getEscapeCharacter=h30,r0;getExclusiveSession=h18,r0;getExecutionTimeStandardDeviation=h104,r0;getExportedKeys=h4,r22,0;getExtraNameCharacters=h4,r0;getFetchDirection=h16,3,9,r0,8,13;getFetchSize=h16,3,9,r0,8,13;getFieldDelimiter=h30,r0;getFieldSeparatorRead=h30,r0;getFieldSeparatorWrite=h30,r0;getFileEncryptionKey=h18,r0;getFilePasswordHash=h43,r0;getFirstUserTable=h18,r0;getFloat=h8,3,9,r0;getFormatId=h114,r0;getFunctionColumns=h4,r0;getFunctions=h4,r0;getGeneratedKeys=h13,16,r0,8,4,59,11;getGlobalTransactionId=h114,r0;getGrantedObject=h52,r0;getGrantedRole=h52,r0;getGrantee=h52,r0;getHigherType=22;getHoldability=h19,3,9,r0;getHostAddress=7;getHours=h36,r0;getId=h35,113,14,r0,60,84,66,48,52,67;getIdentifierQuoteString=h4,r0;getIgnoreCase=h18,r0;getIgnoreCatalogs=h18,r0;getImportedKeys=h4,r22,0;getIndexAccess=h39,r0;getIndexInfo=h4,r22,0;getIndexPath=h39,r0;getInDoubtTransactions=h18,r0;getInstance=h57,72,26,r0;getInt=h8,3,9,r0;getInternal=h3,r0;getInternalType=h103,r0;getIntValue=h60,r0;getIsolationLevel=h25,14,47,r0;getJavaObjectSerializer=h19,25,14,18,120,r0,47;getJdbc=h55,r0;getJDBCMajorVersion=h4,r0;getJDBCMinorVersion=h4,r0;getLargeMaxRows=h16,r8,13,0;getLargeUpdateCount=h16,r8,13,0,115;getLastIdentity=h14,r0;getLastReconnect=h25,r0;getLeading=h36,r0;getLineCommentCharacter=h30,r0;getLineSeparator=h30,r0;getLinkConnection=h18,r0;getLobFileListCache=h50,25,18,r0;getLobSession=h18,r0;getLobStorage=h50,25,18,r0;getLobSyncObject=h50,25,18,r0;getLocalizedMessage=74,83,80,81,73,77,79,76,82,75,78,115;getLocalTempTableConstraints=h14,r0;getLocalTempTableIndexes=h14,r0;getLocalTempTables=h14,r0;getLockMode=h55,18,r0;getLocks=h14,r0;getLockTimeout=h14,18,r0;getLoginTimeout=h49,33,r0;getLogWriter=h49,33,r0;getLong=h8,3,9,r0,2;getMainSchema=h18,r0;getMatrix=7;getMaxBinaryLiteralLength=h4,r0;getMaxCatalogNameLength=h4,r0;getMaxCharLiteralLength=h4,r0;getMaxColumnNameLength=h4,r0;getMaxColumnsInGroupBy=h4,r0;getMaxColumnsInIndex=h4,r0;getMaxColumnsInOrderBy=h4,r0;getMaxColumnsInSelect=h4,r0;getMaxColumnsInTable=h4,r0;getMaxConnections=h4,49,r0;getMaxCursorNameLength=h4,r0;getMaxFieldSize=h16,r8,13,0;getMaxIndexLength=h4,r0;getMaxLengthInplaceLob=h50,25,18,r0;getMaxLogicalLobSize=4;getMaxMemoryRows=h18,r0;getMaxOperationMemory=h18,r0;getMaxProcedureNameLength=h4,r0;getMaxRows=h16,r8,13,0;getMaxRowSize=h4,r0;getMaxSchemaNameLength=h4,r0;getMaxStatementLength=h4,r0;getMaxStatements=h4,r0;getMaxTableNameLength=h4,r0;getMaxTablesInSelect=h4,r0;getMaxUserNameLength=h4,r0;getMaxValue=h57,r0;getMessage=h77,76,75,78,74,83,80,81,73,79,82,r0,115;getMetaData=h19,3,13,9,r0,8,11,7;getMinutes=h36,r0;getMode=h19,25,14,18,120,r0,47;getModificationDataId=h18,r0;getModificationId=h35,14,r0,60,84,66,48,52,67;getModificationMetaId=h18,r0;getMonths=h36,r0;getMoreResults=h16,r8,13,0;getName=h41,134,43,35,26,18,r0,60,84,66,48,52,67;getNanosOfSecond=h36,r0;getNCharacterStream=h8,3,9,r0;getNClob=h8,3,9,r0;getNetworkConnectionInfo=h43,14,r0;getNetworkTimeout=h19,r0;getNextException=74,83,80,81,73,77,79,76,82,75,78,115;getNextId=h25,r0,16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;getNextModificationDataId=h18,r0;getNextModificationMetaId=h18,r0;getNextRemoteSettingsId=h18,r0;getNextSystemIdentifier=h14,r0;getNextValueFor=h14,r0;getNonKeywords=h14,r0;getNotIfPossible=22;getNString=h8,3,9,r0;getNullString=h30,r0;getNumericFunctions=h4,r0;getObject=h8,3,9,r0,21,31,38;getObjectId=22;getObjectInstance=h112,r0;getObjectType=h113,r0;getOptimizeReuseResults=h18,r0;getOriginalMessage=h77,76,75,78,74,83,80,81,73,79,82,117,r0;getOriginalURL=h43,r0;getPageSize=h18,r0;getParameterClassName=h53,r0;getParameterCount=h53,r0;getParameterMetaData=h13,r8,0,11;getParameterMode=h53,r0;getParameterType=h53,r0;getParameterTypeName=h53,r0;getParentLogger=h49,33,r0;getPassword=h33,r0;getPooledConnection=h33,r0;getPort=h34,r0;getPowerOffCount=h18,r0;getPrecision=h9,31,53,r0;getPrepared=h134,r0;getPreserveWhitespace=h30,r0;getPrimaryKeys=h4,r22,0;getProcedure=h4,14,r0;getProcedureColumns=h4,r0;getProcedureTerm=h4,r0;getProperty=h43,r0,1,2,5;getPseudoColumns=h4,r22,0;getPublicRole=h18,r0;getQualifier=h36,r0;getQueries=h121,r0;getQueryStatistics=h18,r0;getQueryStatisticsData=h18,r0;getQueryTimeout=h16,14,r0,8,13;getRandom=h14,r0;getRef=h8,3,9,r0;getReference=h33,r0;getReferentialIntegrity=h18,r0;getRegular=h26,r0;getRemaining=h36,r0;getRemoteSettingsId=h18,r0;getResult=h57,101,3,103,r0;getResultSet=h65,42,16,r0,8,13;getResultSetConcurrency=h16,r8,13,0;getResultSetHoldability=h16,4,r0,8,13;getResultSetType=h16,r8,13,0;getRetentionTime=h18,r0;getRightForObject=h66,r0,48,67;getRightForRole=h66,r0,48,67;getRightMask=h52,r0;getRights=h52,r0;getRow=h3,9,r0;getRowCount=22;getRowCountStandardDeviation=h104,r0;getRowFactory=h18,r0;getRowId=h8,3,9,r0;getRowIdLifetime=h4,r0;getRuntime=7;getSavepointId=h110,r2,0;getSavepointName=h110,r2,0;getScale=h9,31,53,r0;getSchema=h4,19,18,r0;getSchemaName=h9,31,r0,22;getSchemaSearchPath=h14,r0;getSchemaTerm=h4,r0;getScriptDirectory=h28,r0;getSearchStringEscape=h4,r0;getSeconds=h36,r0;getSecondsAndNanos=h36,r0;getService=h34,r0;getServletContext=11;getSession=h19,18,r0;getSessionCount=h18,r0;getSessionStart=h14,r0;getSettings=h96,18,r0,32;getShort=h8,3,9,r0;getShortName=h18,r0;getSnapshotDataModificationId=h14,r0;getSortedSettings=h96,r32,0;getSource=h70,r0;getSQL=h60,35,77,76,75,78,74,83,80,81,73,79,82,113,117,55,r0,84,66,48,52,67,22;getSQLKeywords=h4,r0;getSQLState=74,83,80,81,73,77,79,76,82,75,78,115;getSQLStateType=h4,r0;getSQLXML=h8,3,9,r0;getStackTrace=74,83,80,81,73,77,79,76,82,75,78,115;getState=h2,14,r0;getStatement=h3,9,r0;getStaticSettings=h25,14,19,47,r0;getStatus=h34,r0;getStore=h18,r0;getString=h8,3,9,70,r0,2,21,22,11;getStringFunctions=h4,r0;getStringValue=h60,r0;getSubString=h46,r0;getSuperTables=h4,r0;getSuperTypes=h4,r0;getSuppressed=74,83,80,81,73,77,79,76,82,75,78,115;getSystemFunctions=h4,r0;getSystemJavaCompiler=28,7;getSystemSession=h18,r0;getSystemUser=h18,r0;getTableEngine=h18,r0;getTableName=h9,31,r7,0,26;getTablePrivileges=h4,r0;getTables=h4,r0,22,7;getTableTypes=h4,r22,0;getTempFileDeleter=h50,25,18,r0;getTempTableName=h18,r0;getters=22;getTime=h8,3,9,r0,22;getTimeDateFunctions=h4,r0;getTimestamp=h8,3,9,r0;getTimeZone=h43,r0;getting=17;getTop=15;getTrace=h25,14,47,18,r0;getTraceId=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;getTraceObjectName=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;getTraceSQL=60,84,66,48,52,35,67;getTraceSystem=h112,18,r0;getTransaction=h14,r0;getTransactionId=h14,r0;getTransactionIsolation=h19,r0;getTransactionTimeout=h44,r0;getType=h60,84,48,3,9,52,67,101,35,r0,66;getTypeInfo=h4,r22,0;getTypeMap=h19,r0;getTypeName=h45,r0;getUDTs=h4,r0;getUnicodeStream=h3,9,r0,141;getUpdateCount=h16,r8,13,0,115;getUpdateRow=h3,r0;getUrl=h8,3,9,33,34,4,43,r0,7;getUser=h33,14,18,r0;getUserName=h4,43,r0;getValue=22;getVariable=h14,r0;getVariableIntLength=h72,r0;getVariableNames=h14,r0;getVendor=h41,r0;getVendorTypeNumber=h41,r0;getVersionColumns=h4,r0;getViewIndexCache=h14,r0;getWaitForLock=h14,r0;getWaitForLockThread=h14,r0;getWarnings=h19,16,3,9,r0,8,13;getWriteColumnHeader=h30,r0;getXAConnection=h33,r0;getXAResource=h44,r0;getYears=h36,r0;GiB=5;GID=5;Gilbert=68;GIS=29,10;git=h153,r40;github=40,22,59,153;gives=29,22;glassfish=h11,100,r29,38;Glenn=68;global=1,12,114,93,22,0;Glossary=h5;Gluco=29;GmbH=68;GMT=6;GNU=20,29;going=69;Golay=68;Golden=29;Gomes=68;goodwill=20;Google=40,38;got=40,2;Goubard=68;governed=20;Grails=29;grammar=t10,r21,4,0,40,19,93,59,22,6,100,7;grant=h20,1,136,r2,0,66,4,12,22,52;granted=20,66,0,128,12,2,67;grantedObject=52;grantedRight=52;grantedRole=66,52,12;grantee=12,4,52;GRANTEETYPE=12;granting=22;grantor=4,12;grantRight=h66,r0,48,67;grantRole=h66,r0,48,67;graph=22;Graphical=29;Gray=68;great=29;greater=12,21,6;greatest=h6,r23;greatly=15;Greece=68;greedy=15;green=37,21,23,1,6,10;Gregorian=21;GregorianCalendar=21;grid=29;GridGain=29;groovy=1;group=h10,r23,1,2,37,0,4,22,40,6,7,29,26,15,68,5,32,38;GROUP_BY_NOT_IN_THE_RESULT=h2,r0;GROUP_ID=10;groupByColumnIndex=h26,r0;grouped=10,1;groupId=40,100;grouping=h10,r6,69,1;groupingElement=1;groupInt=6;groupware=29;Grover=68;grow=11,22,1;guarantee=5,17,10,7;guaranteed=2,11,7;guard=11;guesses=15;GUEST=1;GUI=29,11,7,5,38;GUIConsole=t64,r0,137,116,157,90,56;guidelines=40;Gustav=68;GZIP=10'; +ref['h']='H2_BROWSER=h28,r0;H2_SCRIPT_DIRECTORY=h28,r0;h2auth=7;H2AuthConfigXml=22;H2Console=11;h2database=40,6,20,93,100,7;H2Dialect=11,100;H2DRIVERS=11,5;H2MigrationTool=59;h2mvstore=17;h2osgi=29;H2Platform=11,100;H2Sharp=29;H2Type=t41,r0,21,146,158,56,127;H2VERSION=h6;hack=28;had=21,29,15,68;Haidinyak=68;half=21,23,15,7;HALF_DONE=1;hand=h10;handing=29;handle=29,44,22,0,17,7;handled=26,0,85,108;handler=25,47,14,0,17;handling=h17,r22,6,40;Hang=22;happen=17,2,5;happened=20;happier=22;hard=5,15,1,17;harder=5;hardware=20,5,50,15,1;harmless=15;Harpal=68;hasChanged=h27,r0,39;hasDays=h45,r0;hash=h5,1,6,15,r43,0,48,18,27,10,22,24,17,2,11,7;hashed=5,24,0,17;hashing=5;HashMap=96,14,18,26,0;hasHours=h45,r0;Hashtable=112,0;hasMinutes=h45,r0;hasMonths=h45,r0;hasMultipleFields=h45,r0;hasn=59;hasNext=17;hasPendingTransaction=h25,14,47,r0;hasPreparedTransaction=h14,r0;hasSeconds=h45,r0;HasSQL=h35,60,84,66,48,52,67,r56,126;hasTableRight=h48,r0;hasYears=h45,r0;having=1,5,20,17,2,7,29,22,0;HBCI=29;head=17;header=h17,r30,0,2,37,21,23,1,6,10;health=29;heap=h17,r5,2,0,40;HeapDumpOnOutOfMemoryError=40;hearts=21;height=100;held=20,5,14;hello=6,11,2,30,17,1,7,10,9;HelloWorld=40;HenPlus=29;here=7,5,11,93,15,68,10,101,103,38;hereby=20;herein=20;hereof=20;hereto=20;hereunder=20;Heureuse=49,11;hex=h10,r6,1,4,0,17,2;HEX_STRING_ODD_1=h2,r0;HEX_STRING_WRONG_1=h2,r0;hexadecimal=10,2,0,26,7,22,17;hexNumber=10;HEXTORAW=h6,r7;hibernate=h11,100,r29,5;Hibicius=29;Hickey=68;hidden=11,26,0,15,1;hide=77,76,75,78,11,117,74,83,80,81,73,62,69,79,82;hiding=20,7;Hierarchies=137,145,152,109,146,56,126;hierarchy=t109,146,56,126,137,145,152,h69;high=h5,r1,7,4,0,10,108,29,22;higher=6,21,17,1,7,38,5,32,16,23,93,15,40,3;highest=24,0,23,58,18;highlights=29;highly=29;Hilbert=7;hint=h15,r19,0,1,26,7;hispanic=29;history=t68,r11;hit=5;hmac=50,25,18;HOLD=19,21;HOLD_CURSORS_OVER_COMMIT=19,3,4,9;holdability=19,0,4,16,3,9;holding=5;hole=11;home=11,0,43,28,100,7,5,40,2,124;homed=5;Homepage=11;hook=2,32,0,7;hopefully=5;hops=17;host=7,94,5;hostnames=5;hot=15,11;hour=h10,21,45,6,r36,0,41,1,40,5,38;HOUR_TO_MINUTE=h45;HOUR_TO_SECOND=h45;hourField=10;hourInt=10;hoursInt=10;hover=100;however=7,5,1,11,20,15,17,16,40,30,0,72,136,88,59,68,10,58;href=6;hsql=1,40;hsqldb=h15,7,91,r11,5,1,20,68,100;HSQLDialect=100;html=40,93,20,6,138,29,21,69;http=h5,r6,11,20,40,24,28,93,29,22,0,34,124;Huang=68;human=50,11,0,17;hundred=10;Hyde=68;hyperbolic=6;hyperlinks=22;Hypersonic=68;hypothetical=h23,r37'; +ref['i']='i10=60,30,16,43,48,49,4,47,50,27,8,39,13,33,31,14,45,65,25,44,9,52,35,19,34,46,64,42,3,36;i100=8,3,4,9,14,18;i101=8,3,4,9,14,18;i102=8,3,4,9,14,18;i103=8,3,4,9,14,18;i104=8,3,4,9,14,18;i105=8,3,4,9,14,18;i106=8,3,4,9,14,18;i107=8,3,4,9,14,18;i108=8,3,4,9,14,18;i109=8,3,4,9,14,18;i11=30,16,43,48,49,4,47,50,27,8,39,13,33,31,14,45,25,44,9,35,19,34,46,64,42,3,36,53,18;i110=8,3,4,9,14,18;i111=8,3,4,9,14,18;i112=8,3,4,9,14,18;i113=8,3,4,9,14,18;i114=8,3,4,9,14,18;i115=8,3,4,9,14,18;i116=8,3,4,9,14,18;i117=3,4,9,14,18;i118=3,4,9,14,18;i119=3,4,9,14,18;i12=30,16,43,48,49,4,47,50,27,8,13,33,31,14,45,25,44,9,35,19,34,64,3,36,18;i120=3,4,9,14,18;i121=3,4,9,14,18;i122=3,4,9,14,18;i123=3,4,9,14,18;i124=3,4,9,14,18;i125=3,4,9,14,18;i126=3,4,9,14,18;i127=3,4,9,14,18;i128=3,4,9,14,18;i129=3,4,9,14,18;i13=30,16,43,48,49,4,47,50,27,8,13,33,31,14,45,25,44,9,35,19,34,64,3,36,18;i130=3,4,9,14,18;i131=3,4,9,18;i132=3,4,9,18;i133=3,4,9,18;i134=3,4,9,18;i135=3,4,9,18;i136=3,4,9,18;i137=3,4,9,18;i138=3,4,9,18;i139=3,4,9,18;i14=30,45,16,43,25,48,49,44,4,9,35,47,19,34,50,27,8,3,13,33,31,36,14,18;i140=3,4,9,18;i141=3,4,9,18;i142=3,4,9,18;i143=3,4,9,18;i144=3,4,9,18;i145=3,4,9,18;i146=3,4,9,18;i147=3,4,9,18;i148=3,4,9,18;i149=3,4,9;i15=30,16,43,25,49,44,4,9,35,47,19,34,50,27,8,3,13,33,31,36,14,18;i150=3,4,9;i151=3,4,9;i152=3,4,9;i153=3,4,9;i154=3,4,9;i155=3,4,9;i156=3,4,9;i157=3,4,9;i158=3,4,9;i159=3,4,9;i16=30,16,43,25,49,44,4,9,35,47,19,34,27,8,3,13,33,31,36,14,18;i160=3,4,9;i161=3,4,9;i162=3,4,9;i163=3,4,9;i164=3,4,9;i165=3,4,9;i166=3,4,9;i167=3,4,9;i168=3,4,9;i169=3,4,9;i17=30,16,43,25,44,4,9,35,47,19,34,27,8,3,13,33,31,36,14,18;i170=3,4,9;i171=3,4,9;i172=3,4,9;i173=3,4,9;i174=3,4,9;i175=3,4,9;i176=3,4,9;i177=3,9;i178=3,9;i179=3,9;i18=30,16,43,25,4,9,35,47,19,34,27,8,3,13,33,31,36,14,18;i180=3,9;i181=3,9;i182=3,9;i183=3,9;i184=3,9;i185=3,9;i186=3,9;i187=3,9;i188=3,9;i189=3,9;i19=30,16,43,25,4,9,35,47,19,27,8,3,13,33,31,36,14,18;i190=3,9;i191=3,9;i192=3,9;i193=3,9;i194=3,9;i195=3,9;i196=3,9;i197=3,9;i198=3,9;i199=9;i20=30,16,43,25,4,9,35,47,19,27,8,3,13,33,31,36,14,18;i200=9;i201=9;i202=9;i203=9;i204=9;i205=9;i206=9;i207=9;i208=9;i209=9;i21=30,16,43,25,4,9,35,47,19,8,3,13,33,31,36,14,18;i210=9;i211=9;i212=9;i213=9;i214=9;i215=9;i216=9;i22=30,16,43,25,4,9,35,47,19,8,3,13,33,31,36,14,18;i23=30,16,43,25,4,9,35,47,19,8,3,13,33,31,36,14,18;i24=30,16,43,25,4,9,19,8,3,13,33,36,14,18;i25=30,16,25,4,9,19,8,3,13,33,36,14,18;i26=19,30,16,8,25,3,4,13,9,36,14,18;i27=19,30,16,8,25,3,4,13,9,36,14,18;i28=19,30,16,8,25,3,4,13,9,36,14,18;i29=19,16,8,25,3,4,13,9,36,14,18;i30=19,16,8,25,3,4,13,9,36,14,18;i31=19,16,8,25,3,4,13,9,36,14,18;i32=19,16,8,25,3,4,13,9,36,14,18;i33=19,16,8,25,3,4,13,9,14,18;i34=19,16,8,25,3,4,13,9,14,18;i35=19,16,8,25,3,4,13,9,14,18;i36=19,16,8,25,3,4,13,9,14,18;i37=19,16,8,25,3,4,13,9,14,18;i38=19,16,8,25,3,4,13,9,14,18;i39=19,16,8,25,3,4,13,9,14,18;i40=19,16,8,25,3,4,13,9,14,18;i41=19,16,8,25,3,4,13,9,14,18;i42=19,16,8,25,3,4,13,9,14,18;i43=19,16,8,25,3,4,13,9,14,18;i44=19,16,8,25,3,4,13,9,14,18;i45=19,16,8,25,3,4,13,9,14,18;i46=19,16,8,25,3,4,13,9,14,18;i47=19,16,8,25,3,4,13,9,14,18;i48=19,16,8,25,3,4,13,9,14,18;i49=19,16,8,25,3,4,13,9,14,18;i50=19,16,8,25,3,4,13,9,14,18;i51=19,16,8,3,4,13,9,14,18;i52=19,16,8,3,4,13,9,14,18;i53=19,16,8,3,4,13,9,14,18;i54=19,16,8,3,4,13,9,14,18;i55=19,16,8,3,4,13,9,14,18;i56=19,16,8,3,4,13,9,14,18;i57=19,8,3,4,13,9,14,18;i58=19,8,3,4,13,9,14,18;i59=19,8,3,4,13,9,14,18;i60=19,8,3,4,13,9,14,18;i61=19,8,3,4,13,9,14,18;i62=19,8,3,4,13,9,14,18;i63=8,3,4,13,9,14,18;i64=8,3,4,13,9,14,18;i65=8,3,4,13,9,14,18;i66=8,3,4,9,14,18;i67=8,3,4,9,14,18;i68=8,3,4,9,14,18;i69=8,3,4,9,14,18;i70=8,3,4,9,14,18;i71=8,3,4,9,14,18;i72=8,3,4,9,14,18;i73=8,3,4,9,14,18;i74=8,3,4,9,14,18;i75=8,3,4,9,14,18;i76=8,3,4,9,14,18;i77=8,3,4,9,14,18;i78=8,3,4,9,14,18;i79=8,3,4,9,14,18;i80=8,3,4,9,14,18;i81=8,3,4,9,14,18;i82=8,3,4,9,14,18;i83=8,3,4,9,14,18;i84=8,3,4,9,14,18;i85=8,3,4,9,14,18;i86=8,3,4,9,14,18;i87=8,3,4,9,14,18;i88=8,3,4,9,14,18;i89=8,3,4,9,14,18;i90=8,3,4,9,14,18;i91=8,3,4,9,14,18;i92=8,3,4,9,14,18;i93=8,3,4,9,14,18;i94=8,3,4,9,14,18;i95=8,3,4,9,14,18;i96=8,3,4,9,14,18;i97=8,3,4,9,14,18;i98=8,3,4,9,14,18;i99=8,3,4,9,14,18;IBM=7;Iceland=68;icon=11,90,124;ICU4J=1,7,38;ICU4J_=1;idea=11,5;identical=85,1,2,0,5;IDENTICAL_EXPRESSIONS_SHOULD_BE_USED=h2,r0;identified=128,15,0;identifier=h5,r0,16,12,123,4,10,6,122,18,114,21,32,14,2,7,22,24,26,1,92,11,95,45,89,86,91,59,85,99;identify=20;identity=h10,59,r7,1,26,0,12,22,24,2,15,5,16,6,13;IDENTITY_BASE=12;IDENTITY_CACHE=12;IDENTITY_CYCLE=12;IDENTITY_GENERATION=12;IDENTITY_INCREMENT=12;IDENTITY_MAXIMUM=12;IDENTITY_MINIMUM=12;IDENTITY_START=12;identityClause=h26,r0;identityColumnsHaveDefaultOnNull=h26,r0;identityDataType=h26,r0;idiomatic=29;ids=18,0;idsToRelease=18;IDX_ID=2;idx_name=7,26;IDX_PHONE=15;IDX_TEST_ID=2;IDX_TEST_NAME=1;IDX_U_NAME=7;IDXNAME=1;IEEEremainder=2;iface=19,16,49,3,4,9,33,31,53;ifexists=34,7,2,0,5;ifNotExists=34,67,11;IFNULL=6;Ignite=22;ignore=h7,r37,27,10,0,15,58,26;IGNORE_CATALOGS=h1,r32,0;IGNORE_UNKNOWN_SETTINGS=7,22;IGNORECASE=h1,r7,22;ignoreCatalogs=h32,r0;ignored=23,8,16,6,4,19,1,10,13,42,3,43,7,33,30,32,22,0,2,44,34;IGNOREDRIVERPRIVILEGES=7;ignoreProperties=34;iii=20;ILIKE=10,22,5;illegal=22,17;IllegalArgumentException=95,92,45,85,17,99,89,86,55,91;illegally=22;IllegalStateException=22,17;image=21,29,40;ImageMapper=29;IMMEDIATE=h1;immediately=1,22,0,24,5,18,54,61,17,10;immutable=17,5;Imola=15;impact=15;impl=7;implementation=h17,r0,143,7,29,93,5,9,142,11,12,116,118,22,28,54,69,15,1,58,65,127,19,140,68;implementing=0,118,150,149,123,142,151,148,29,129,61,7,120,117,69;implementors=69;implication=20;implicit=10,2,0,7;implicitly=0,1,2,14,10;implicitRelativePath=2;implied=20;import=h15,r11,7,33,20,1,22,49,5,17,6,100,40;important=15,5,6,59,38;imported=7;imported_keys=11;importedKey=4;importedKeyCascade=4;importedKeyInitiallyDeferred=4;importedKeyInitiallyImmediate=4;importedKeyNoAction=4;importedKeyNotDeferrable=4;importedKeyRestrict=4;importedKeySetDefault=4;importedKeySetNull=4;Importing=h11;impossible=20;improve=22,15,7,17,5,38,29,32,28,11;improved=15,7;improvement=7,22,69;improving=59,68;IN_DOUBT=h12,r5;IN_RESULT_SETS=h89;Inability=h20;inaccuracies=20;Inactive=5;inactivity=1;Inc=68,5;incidental=20;include=20,15,1,11,40,10,5,7,32,0,48,4,63,2,35,67,30,22,16,6,27,8,39,13,136;included=7,17,23,1,5,12,20,24,6,11,15,40,10,0,16,2,38,13,18;including=20,7,11,1,10,5,29,12,43,17,23,6,57,36,14,30,0,16,88,40,13,108;includingSystemSession=18;inclusive=6,23,10,1;incoming=5;incomparable=2,22,0;incompatible=h20,r2,22,0,5;incomplete=22;inconsistency=20;inconsistent=22,1;incorporated=68;incorrect=h38,r22,15,2,0,17,103;incorrectly=22;increase=17,15,22,68,2,5;increased=24;increment=10,12,6,2,4,7;incremented=17,10;incrementing=10,5;incurred=20;incurring=5;Indemnified=20;indemnify=20;indemnity=20;indentBoolean=6;indented=6;independent=139,23,29,2,153,0,57,1,10,4,11,7;independentExpression=23;INDEX_ALREADY_EXISTS_1=h2,r0;INDEX_BELONGS_TO_CONSTRAINT_2=h2,r0;INDEX_CATALOG=12;INDEX_CLASS=12;INDEX_COLUMNS=h12;index_info=11;INDEX_NAME=12,4,2;index_name_1=15;index_name_2=15;INDEX_NOT_FOUND_1=h2,r0;INDEX_PLACE=15;INDEX_QUALIFIER=4;INDEX_SCHEMA=12,2;INDEX_TYPE_NAME=12;IndexAccess=39;indexColumn=h125,r1,27,0;indexDefinitionInCreateTable=h26,r0;indexed=85,27,0,15,26,2,11,22,6,10,7,108;indexes=h15,7,12,r0,4,1,16,26,125,27,22,13,5,24,48,11,39,35,38,67,28,17,6,93,10,19,85,108;indexExpression=6;IndexInfo=t125,r0,143,152,160,56;indexInt=10;indexName=1,10;indexPath=39;indicate=6,15,10;indicating=16,19;indices=107,27,0,93,11;indirect=20;indirectly=20,17;individual=20,15;individually=5;InDoubtTransaction=18;industries=68;InetAddress=7;INF=11;inferred=1;infinite=22;infinity=10,21,22;info=43,7,19,0,111,128,24,22,133,1,11,130;inform=20,18,0;information=h12,153,r0,14,11,43,47,25,7,29,5,135,15,22,6,88,69,2,130,87,112,105,21,20,102,120,50,27,39,124;INFORMATION_SCHEMA=h59,r12,22,6,14,2,1,0,25,47,7,5,24;INFORMATION_SCHEMA_CATALOG_NAME=h12;INFORMATION_SCHEMA_ID=h24,r0;informed=20;infrastructure=29;infringe=20;infringed=20;infringement=20;infringing=20;inherent=17;inherit=69;inheritance=69;ini=15,5;init=h98,97,54,27,39,61,101,103,58,99,r7,0,11,15,22;initCause=74,83,80,81,73,77,79,76,82,75,78,115;initial=10,20,12,22,1,7,18;INITIAL_LOCK_TIMEOUT=h24,r0;initialCapacity=18;InitialContext=33,1;initialization=40,5,111;initialize=0,27,11,5,39,2,35,7;initialized=54,0,28,108;initializing=0,54,61,2,14;INITIALLY_DEFERRED=12;initialValueExpression=1;initiate=20;inject=5;injection=h5,r1,29,7;injury=20;inline=21,22,1,10,7;inner=10,5,1,22,15,7;InnoDB=15;innodb_flush_log_at_trx_commit=15;inPredicateRightHandSide=10;input=0,3,5,51,62,8,13,2,46,70,101,4,103,100;inputFileName=30;inputSchema=11;InputStream=0,9,3,13,8,72,51,50,46,62,70,21,28,141;inputString=6;inputTypes=101,103;insecure=7;insensitive=7,6,1,122,18,0,4,10,3,30,32,22,43,2,9;insert=h3,1,10,0,15,61,6,52,r2,4,7,11,5,22,16,54,8,13,26,12,98,97,32,21,59;insertable=12;INSERTABLE_INTO=12;inserted=16,1,7,5,19,3,12,22,0,13;inserting=h11,r0,143,52,5,98,27,15,39,7,97;insertion=26,0,15;insertOnConflict=h26,r0;insertRow=h3,9,r0,2;insertsAreDetected=h4,r0;insertValues=1;inside=10,2,0,1,11,21,17,6,118,110,7;inspect=34;install=h5,r40,29,11,124,138;installation=t138,h5,124,r15,40,11,38;installed=11,5;installer=139,40,138,153,100,124;installing=h138,r5;Instant=21;instead=h5,r6,8,3,7,11,1,21,26,13,2,0,10,12,17,15,40,36,29,22,28,54,14,116,38,34,59,68;institutes=29,20;INSTR=6;INT2=21;INT4=21;INT8=21;integer=h21,41,r12,6,7,0,72,10,2,17,1,4,36,23,59,22,9,15,14;integerType=10;integrate=20,62,68,40;integrated=29;integration=29;integrity=1,4,7,12,0,2,5,38;intellectual=20;intelliBO=29;intended=21,20,17,5;interaction=21,15;interactive=0,24,29,62,116,11;intercept=23;interest=93,69;interesting=5;interleave=h57,r0;interleaving=57,7;intermediate=93,4,0,1;Intermittent=22;intern=2;internally=h15,r17,7,21,5,22,6,44,38;internet=5,136;interpolated=23;interpolation=23;interpreted=6,10,11;interprets=15;interrupt=7;interrupted=16;interruption=20,7;intersect=10,1,5;intersecting=17;intersection=5;interval=t36,h10,21,r0,45,6,41,12,23,22,127,2,146,56,1,158,5;INTERVAL_DAY=h41,r0;INTERVAL_DAY_TO_HOUR=h41,r0;INTERVAL_DAY_TO_MINUTE=h41,r0;INTERVAL_DAY_TO_SECOND=h41,r0;INTERVAL_HOUR=h41,r0;INTERVAL_HOUR_TO_MINUTE=h41,r0;INTERVAL_HOUR_TO_SECOND=h41,r0;INTERVAL_MINUTE=h41,r0;INTERVAL_MINUTE_TO_SECOND=h41,r0;INTERVAL_MONTH=h41,r0;INTERVAL_PRECISION=12;INTERVAL_SECOND=h41,r0;INTERVAL_TYPE=12;INTERVAL_YEAR=h41,r0;INTERVAL_YEAR_TO_MONTH=h41,r0;intervalDay=10;intervalDayToHour=10;intervalDayToHourType=21;intervalDayToMinute=10;intervalDayToMinuteType=21;intervalDayToSecond=10;intervalDayToSecondType=21;intervalDayType=21;intervalHour=10;intervalHourToMinute=10,1;intervalHourToMinuteType=21;intervalHourToSecond=10,1;intervalHourToSecondType=21;intervalHourType=21;intervalMinute=10;intervalMinuteToSecond=10;intervalMinuteToSecondType=21;intervalMinuteType=21;intervalMonth=10;intervalMonthType=21;intervalQualifier=t45,r0,36,10,146,158,56,127;intervalSecond=10;intervalSecondType=21;intervalType=10;intervalYear=10;intervalYearToMonth=10;intervalYearToMonthType=21;intervalYearType=21;inTouch=29;Introduction=h93,59,136;intValue=7;INV_INS=7;invalid=2,31,0,13,22,16,8,1,12,20,19,133,10;INVALID_CLASS_2=h2,r0;INVALID_DATABASE_NAME_1=h2,r0;INVALID_DATETIME_CONSTANT_2=h2,r0;INVALID_NAME_1=h2,r0;INVALID_PARAMETER_COUNT_2=h2,r0;INVALID_PRECEDING_OR_FOLLOWING_1=h2,r0;INVALID_TO_CHAR_FORMAT=h2,r0;INVALID_TO_DATE_FORMAT=h2,r0;INVALID_TRIGGER_FLAGS_1=h2,r0;INVALID_USE_OF_AGGREGATE_FUNCTION_1=h2,r0;INVALID_VALUE_2=h2,r0;INVALID_VALUE_PRECISION=h2,r0;INVALID_VALUE_SCALE=h2,r0;invalidate=h35,r60,84,66,0,48,52,67;Inverse=h23;invisible=1,10,4,0;invocation=101,103,21;INVOICE=15,7;invoked=95,26,0,7;involve=2,5;involved=32;IO_BUFFER_SIZE=h24,r0;IO_BUFFER_SIZE_COMPRESS=h24,r0;IO_EXCEPTION_1=h2,r0;IO_EXCEPTION_2=h2,r0;IOException=25,30,111,0,102;IOUtils=22;IP_ADDRESS=7;ipAddress=7;IPT=29;IPv4=5;IPv6=5;IS_ADMIN=12;IS_AUTOINCREMENT=4;IS_DEFERRABLE=12;IS_DETERMINISTIC=12;IS_GENERATED=12;IS_GENERATEDCOLUMN=4;IS_GRANTABLE=12,4;IS_IDENTITY=12;IS_INSERTABLE_INTO=12;IS_NULLABLE=4,12;IS_PRIME=7;IS_RESULT=12;IS_ROLLBACK=12;IS_TRIGGER_DELETABLE=12;IS_TRIGGER_INSERTABLE_INTO=12;IS_TRIGGER_UPDATABLE=12,22;IS_UNIQUE=12;IS_UPDATABLE=12;IS_VISIBLE=12;isAdmin=h48,r0;isAfterLast=h3,9,r0;isAllowBuiltinAliasOverride=h18,r0;isAutoIncrement=h9,31,r0;isBeforeFirst=h3,9,r0;isCancelled=h16,r0,8,13;isCaseSensitive=h9,31,r0;isCatalogAtStart=h4,r0;isClosed=h19,16,25,3,9,14,47,r0,8,13;isCloseOnCompletion=h16,r8,13,0;isClosing=h18,r0;isClustered=h25,r0;isCommon=h2,r0;isCurrency=h9,31,r0;isDayTime=h45,r0;isDebugEnabled=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;isDefinitelyWritable=h9,31,r0;isFirst=h3,9,r0;isIgnoredByParser=h43,r0;isInfoEnabled=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;isLast=h3,9,r0;isLazyQueryExecution=h14,r0;isn=6,22,59,10,52;isNegative=h36,r0;isNullable=h9,31,53,r0;ISO=h10,r6,11,5;ISO_DAY_OF_WEEK=h6,r10;ISO_WEEK=h6,r10;ISO_WEEK_YEAR=10;ISO_YEAR=h6,r10;isoDayOfWeekField=10;ISODOW=10;isolated=5;isolation=h5,r55,0,14,19,1,25,47,4,22,24,26,38,12,17,11,108;ISOLATION_LEVEL=12;isolationLevel=t55,r0,25,47,14,154,56,126,108;isolationLevelInSelectOrInsertStatement=h26,r0;isOldInformationSchema=h25,14,47,r0;isOpen=h14,r0;isOption=106,102,94,90,88,34,87,50,62,64,63,71,105;isoWeekField=10;isoWeekYearField=10;ISOYEAR=10;isParsingCreateView=h14,r0;isPersistent=h43,18,r0;isPoolable=h16,r8,13,0;isPrime=7;isProbablePrime=7;isQuirksMode=h14,r0;Israels=68;isReadOnly=h4,19,9,31,18,r0,7;isRemote=h25,14,43,47,r0;isRoleGranted=h66,r0,48,67;isRunning=h34,r0;isSameRM=h44,r0;isSearchable=h9,31,r0;isSigned=h9,31,53,r0,22;isSimpleIdentifier=h16,123,r0,8,13;isStarting=h18,r0;issue=h22,r40,38,5,7,29,133,11;issued=49,7;isSysTableLocked=h18,r0;isSysTableLockedBy=h18,r0;isTemporary=h35,r60,84,66,0,48,52,67;isTruncateLargeLength=h14,r0;isValid=h19,35,r0,60,84,66,48,52,67;isVariableBinary=h14,r0;isWrapperFor=h19,16,49,3,4,9,33,31,53,r0,8,13;isWritable=h9,31,r0;isYearMonth=h45,r0;italic=69;Italy=68;item=69,10,2,11,5;Iterable=h77,76,75,78,74,83,80,81,73,79,82,115,r109,56;iterate=17,95,92,45,85,15,99,89,86,55,91;iteration=5,6,29,22,17;iterationInt=6;iterator=17,77,76,75,78,74,83,80,81,73,79,82,115;itself=20,1,5,0,24,17,2,9,7,61,44,38,54,15;iTunes=29;Iyama=68'; +ref['j']='J2EE=29;jaas=7;JaasCredentialsValidator=7;Jackrabbit=29;Jacopo=68;Jakarta=7;JakartaDbStarter=11;JakartaWebServlet=11;Jake=68;Jakob=68;Jalpesh=68;JAMWiki=29;Jan=17;January=2;Japan=68;jar=h7,153,r11,100,40,5,17,15,22,1,20,6,38,124,138;jarClient=22;jarMVStore=17;jarSmall=22;Jaspa=29;JAVA_CLASS=12;JAVA_HOME=22,40;JAVA_OBJECT=h21,41,r22,0,59,5;JAVA_OBJECT_SERIALIZER=h1,28,r5,0;JAVA_OBJECT_SERIALIZER_CHANGE_WITH_DATA_TABLE=h2,r0;JAVA_SYSTEM_COMPILER=h28,r0;JavaBean=33;javac=28,1,7;javaClass=106,7;javadoc=h153,r24,40,11,138,22,7;JavaEE=29;javaObjectSerializer=t131,r0,5,28,25,120,19,14,18,146,56,127,1,158;javaObjectType=10;javaSystemCompiler=28,0;javax=h44,114,r33,49,56,145,112,1,70,5,142,0,7;Jayaprakash=68;JBoss=29,11;jBPM=29;JCL=7;jconsole=11;jcr=40,29;JDBC_DATASOURCE_NAME=11;JDBC_DESCRIPTION=11;JDBC_NETWORK_PROTOCOL=11;JDBC_PASSWORD=11;JDBC_PORT_NUMBER=11;JDBC_SERVER_NAME=11;JDBC_URL=11;JDBC_USER=11;JdbcArray=t42,r0,155,109,118,56;JdbcBatchUpdateException=t115,r0,155,109,118,56;JdbcBlob=t51,r0,155,109,118,56,119;JdbcCallableStatement=t8,h0,r141,123,155,109,118,13,56;JdbcClob=t46,r0,155,109,118,56,119;JdbcConnection=t19,r0,16,46,51,42,3,70,14,150,109,56,120,155,118;JdbcConnectionBackwardsCompat=t150,r19,109,56,0,155,118;JdbcConnectionPool=t49,r0,100,11,145,142,148,159,56;JdbcConnectionPoolBackwardsCompat=t148,r145,49,56,0,142,159;JdbcDatabaseMetaData=t4,h0,r22,155,109,151,118,56;JdbcDatabaseMetaDataBackwardsCompat=t151,r109,4,56,0,155,118;JdbcDataSource=t33,r0,112,11,149,145,100,142,159,56;JdbcDataSourceBackwardsCompat=t149,r145,33,56,0,142,159;JdbcDataSourceFactory=t112,r0,145,142,159,56;JdbcException=t117,h77,76,75,78,74,83,80,81,73,79,82,r109,56,0,155,118;JdbcLob=t95,119,h70,46,51,r0,155,109,118,56;JdbcParameterMetaData=t53,r0,155,109,118,56;jdbcPersistenceAdapter=11;JdbcPreparedStatement=t13,h8,r0,123,16,155,109,141,118,56;JdbcResultSet=t3,h0,r141,16,155,109,118,56;JdbcResultSetMetaData=t31,r0,155,109,118,56;JdbcSavepoint=t110,r0,155,109,118,56;JdbcSQLDataException=t79,r0,155,109,118,56,117;JdbcSQLException=t83,r0,155,109,118,56,117;JdbcSQLFeatureNotSupportedException=t75,r0,22,155,109,118,56,117;JdbcSQLIntegrityConstraintViolationException=t80,r0,155,109,118,56,117;JdbcSQLInvalidAuthorizationSpecException=t74,r0,155,109,118,56,117;JdbcSQLNonTransientConnectionException=t76,r0,155,109,118,56,117;JdbcSQLNonTransientException=t82,r0,155,109,118,56,117;JdbcSQLSyntaxErrorException=t78,r0,22,155,109,118,56,117;JdbcSQLTimeoutException=t73,r0,155,109,118,56,117;JdbcSQLTransactionRollbackException=t77,r0,155,109,118,56,117;JdbcSQLTransientException=t81,r0,155,109,118,56,117;JdbcSQLXML=t70,r0,155,109,118,56,119;JdbcStatement=t16,h13,8,r0,3,123,155,109,118,56;JdbcStatementBackwardsCompat=t123,r16,0,109,56,155,8,118,13;jdbcx=t142,145,159,r0,56,49,11,33,44,114,112,100,149,93,148;JdbcXAConnection=t44,r0,145,142,159,56;JdbcXid=t114,r0,145,142,159,56;JDBM=17;JDK=28,40,22,0;JDO=29;Jech=68;Jena=29;Jenkov=68;JGeocoder=29;JGrass=29;JiaDong=68;JIRA=22;JMatter=29;JMX=h11;jmxremote=11;JNDI=33,29,1;jnlp=h11;Joachim=68;Joaquim=68;job=29;Joe=6,2,11;Joel=68;john=15,11,10;Johnny=11;join=h10,r15,4,11,0,1,5,38,24,7,22,6;join_collapse_limit=22;joined=22,15,68;joinSpecification=10;jones=15,11;Joonas=68;jooq=h11,r29;Jopr=29;JPA=29;JPasswordField=5;jps=15;JRE=28,0,1,6,40;JRuby=1;json=h10,6,23,21,41,r22,0,59,5;JSON_ARRAY=h6,r22;JSON_ARRAYAGG=h23,r22;JSON_DATA_A=6;JSON_DATA_B=6;JSON_OBJECT=h6,r22;JSON_OBJECTAGG=h23;jsonPredicateRightHandSide=10;jsonType=10;JSR=29;JSSE=5;jstack=15;jts=21;judgment=20;judicial=20;Julian=21;Jun=68;junctions=40;JUnit=29;jurisdiction=20;jury=20;JVM=h15,r6,17,7,136,11,34,5;J\u00fcnger=68'; +ref['k']='Kaspersky=11;Kawashima=68;Keegan=68;keep=h15,r24,7,0,17,29,59,28,40,11,38;KEEP_CURRENT_RESULT=16,8,13;keeping=15;kept=1,21,30,15,17,5,32,27,28,39,7;kernel=29;Kervin=68;key=h125,r2,0,17,10,15,4,96,16,1,43,7,27,107,5,23,6,11,13,26,12,19,14,22,34,3,18,24,39,136;KEY_COLUMN_USAGE=h12,r2;KEY_SEQ=4;keyBytes=6;keystore=5;keyStorePassword=5;keyword=h5,r22,14,4,0,1,59;khtml=100;KiB=7;Kidd=68;kill=34,15;killed=34;killing=5;kind=93,20,1,59,136;Kingdom=68;knife=29;knowledge=29,20;known=h38,r53,21,15,69,10,5,7,149,20,22,129,66,16,17,6,11,47,120,96,27,151,13,148,138,119,150,12,123,61;Knut=68;Kotek=17;Krenger=5;Kritchai=68;Kupolov=68;Kyoto=17'; +ref['l']='label=3,9,31,0,38;lack=29;Ladislav=68;Lag=h37;Laird=40;Landry=68;language=29,5,20,12,7,38;LANGUAGE_TAG=12;laptop=5;large=h5,21,r37,17,16,0,15,14,7,41,1,22,10,25,59,3,18,29,6,11,40,13,119,118,36;larger=h20,r6,1,5,4,7,59,15,10,22,57,14;largest=6,23;last=h3,9,r0,1,10,37,16,7,17,6,12,26,2,8,32,104,15,5,14,30,28,11,63,44,38,34,100;LAST_MOD=7;LAST_MODIFICATION=12;LAST_NAME=11;LAST_VALUE=h37,r10;lastUpdateTime=h104,r0;later=6,0,24,17,9,7,20,58;latest=17,16,13,0,15,6,10;latitude=29;Latvia=68;Launch=11;Laurent=68;law=20;lawsuit=20;layer=h93,r5,29,22,11;Layout=h7;lazy=22,1;LAZY_QUERY_EXECUTION=h1;lazyQueryExecution=14;LCASE=6;ldap=7,2;LdapCredentialsValidator=7;LDAPEXAMPLE=7;LdapLoginModule=7;Leach=5;lead=h37,r2,1,6,7;leading=10,21,12,36,6,0,23,7,5;leaf=17;leak=19,22,0,100,7;leaking=22;learning=29;least=h6,r10,3,5,0,7,85,24,17,15,40,2;leaves=22;leaving=15;left=h6,r10,1,100,11,15,17,124,5,38;leftmost=6;leftover=22;legacy=h7,91,r1,5,12,59,22,29;legal=20,13,0,40;len=51,46,17,6;length=h46,51,r3,9,13,8,21,4,0,6,12,17,5,22,24,14,25,59,18,50,10,72,26,1,89,11,7;lengthInt=21,6;lengthLong=21;less=37,15,17,23,12,29,22,24,6,5;Lesser=20;let=29,11,124;letter=30,22,6,7,5;level=0,5,55,7,4,19,1,24,14,25,47,15,108,22,11,26,40,12,32,28,17,38,68;LevelDB=17;liability=h20;liable=20;lib=11,38,15,40;liberal=17;libraries=68,29,20,40,7;library=29,7,21,20,11;licensable=20;license=t20,r29,22,15,17,40,38;licensed=20,40;licensees=20;licensing=20;Lies=5;life=29;lifecycle=25,47,14;lifetime=4,0;Lift=22;Liftweb=29;light=29;lightweight=29;LIKE_ESCAPE_ERROR_1=h2,r0;likely=20,15,138;likePredicateRightHandSide=10;limit=h5,26,38,r4,7,27,39,1,16,17,6,11,20,22,0,32,24,15;limitation=h5,20,r22,7,38;limited=1,6,4,7,29,59,20,0,15,101,11,5;Limiting=h5;limitInt=27,39;line=h11,r30,62,0,5,6,15,40,10,7,23,50,63,116,90,34,29,102,88,1,124,106,94,87,100,71,105;lineComment=10;lineCommentCharacter=30;linefeed=6;lineSeparator=30,10;lineSepString=6;LINESTRING=21,5;LINK_SCHEMA=h6;linked=h1,5,r22,2,32,0,14,21,12,6,7,38;lint=29;Linux=29,138,15,40,2,38;LiquiBase=29;LIRS=17;Lisboa=68;listagg=h23,r22;listed=h15,r28,11,7,106,77,22,76,75,2,4,78,117,74,83,80,81,73,79,82,5;listen=5,29,11;listener=h11,r44,5,0,58,1,2,18,7;listening=34,136,0,5;listSessions=11;listSettings=11;literal=h10,5,r21,2,7,0,1,4,22,6,24,26,14,11,59;LITERAL_PREFIX=4;LITERAL_SUFFIX=4;LITERALS_ARE_NOT_ALLOWED=h2,r0;literalsChecked=14;litigation=h20;little=17,5,29,20,22,6,7;live=17,93;LLC=68;LMT=21,10;load=11,111,0,2,29,59,22,62,6,5;loaded=21,2,32,0,28,1,6;loader=2,0,28,7,111,38;loadH2=h111,r0;loading=h5,r28,18,0,15;lob=25,18,22,50,14,0,24,28,1,5,32,2,47,12,147,6,11,38,46,51,70,108;LOB_BLOCKS=18,0;LOB_CLIENT_MAX_SIZE_MEMORY=h28,r0;LOB_CLOSED_ON_TIMEOUT_1=h2,r0;LOB_READ=h25,r0;LOB_TIMEOUT=32,0,2;lobClientMaxSizeMemory=28,0;lobCloseBetweenReads=h28,r0;lobId=50,25,18;LobStorageFrontend=25;LobStorageInterface=50,18;LobStorageMap=22;lobTimeout=h32,r0;local=h7,r14,0,1,47,10,21,11,8,13,12,4,32,93,40,63,124,5,108;LOCAL_TYPE_NAME=4;LocalDate=8,3,21,13;LocalDateTime=8,3,21,13;localdb=29;locale=6,10;localeString=6;localhost=5,11,7,34,100,15,6,2,124,136;locality=17;localized=4;locally=11,7;LocalResult=22;LocalTime=h6,r8,3,21,13,1,5;LOCALTIMESTAMP=h6,r1,5;locate=h6,r2,29,11;located=15,17,5;location=112,25,6,11,7,5,18,29,20,0,94;locationtech=21;locatorsUpdateCopy=h4,r0;lock=h5,7,12,r24,0,2,1,22,6,18,17,14;LOCK_MODE=h1,6,r55,0,24,38;LOCK_MODE_OFF=h24,r0,14;LOCK_MODE_READ_COMMITTED=h24,r0;LOCK_MODE_TABLE=h24,r0;LOCK_MODE_TABLE_GC=h24,r0;LOCK_SLEEP=h24,r0;LOCK_TIMEOUT=h1,6,r2,7;LOCK_TIMEOUT_1=h2,r0;LOCK_TYPE=12;locked=18,2,0,1,5,24,14,17,12,11;locking=h5,7,17,r24,0,2,1,11,14;lockMeta=h18,r0;lockMode=55,18;lockSharedSessions=22;lockTimeout=14;log=t22,h93,17,6,68,r0,7,1,33,5,11,26,59,14,18,38,24,58,49,50,15,100,40,153;LOG10=h6;Log4j=7;logAndConvert=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;logarithm=6;Logback=7;logFileId=6;logged=61,124,7;logger=49,33,7;logging=h7,r49,33,29;logic=29,22;logical=5,69,15,1;logically=10;login=h11,124,r33,0,28,7,2,38,133,5;logIsLogBase10=h26,r0;LogMode=11;logos=20;logWriter=49;London=10,1;long=h10,0,r36,9,3,6,8,46,51,18,16,13,104,57,50,2,17,58,65,42,21,15,5,121,28,14,25,37,7,92;LONGBLOB=21;longer=0,22,24,17,2,35,15,14,21,32,93,59,68,1,5;longest=6;longitude=29;LONGTEXT=21;LONGVARBINARY=21;LONGVARCHAR=21;lookup=17,15,33;loop=11,22,30,15,28,5;lose=5;losing=15,5;loss=20;losses=20;lossless=7;losslessly=116,0,72;lost=20,5,1,2,7,22,0,15,10;lot=37,15,38,59,17,68,1,10,11,7,5;low=1,4,15,10,7,32,0,17,68;lower=h6,r21,32,122,18,0,10,5,57,23,1;lowerBoundInt=6;lowercase=6,10,7;lowest=15,17,23;LPAD=h6;LSHIFT=h6;LTRIM=h6,r22;Lucene=h11,40,r39,0,143;Lumber=68;Luntbuild=29;Lutin=68;Lyderic=68;LZF=10,17,72,6;LZFOutputStream=24,0'; +ref['m']='Mac=138,22,62,40,5,38;machine=7,5,2,0,28,6,11,15,32,20,136,1,124;Macromedia=29;made=2,20,0,1,5,59,3,9,7;Magnolia=29;mail=29;main=h34,7,102,88,50,63,106,94,90,87,62,71,105,r0,5,11,40,1,17,18,35,68,29,22,49,15,138,64;MAIN_SCHEMA_ID=h24,r0;mainly=5,29,15;maintains=121,20,0,108;major=4,0,24,138;majority=17;MajorVersion=4;making=20,5;malfunction=20;manage=29,1;management=h11,93,r29,68,20;manager=29,6,44,1,2,11,5;manifest=22;manipulate=5;manipulating=6;manipulation=h1,r4,0,15,5;Manley=68;manner=20;manual=29,68,1;manually=7,2,5,34,0,15,1,20,40;many=15,5,7,28,11,68,0,24,2,32,22,16,17,38,136,59,51,1,40,10,138;map=h17,r0,42,65,14,19,18,9,8,3,128,28,96,5,29,135,4,127,34,108;MapDB=17;mapId=17;mapped=21,7,28,1,5,38;mapping=h7,r29,0,8,3,11,26,22,15,28,5,38;mapUserToRoles=h128,r0;margin=100;MariaDB=h7,91,r5,22;mark=14,0,10,22,18,30,20,68,6,2,7;marked=37,21,17,23,1,6,10,59,5;marker=30;Marketing=68;markets=29;markTableForAnalyze=h14,r0;Markup=29;Marschall=68;Martin=68;mask=52,0,22;Master=7;MAT=40;match=2,6,0,1,32,92,45,17,4,89,7,86,38,91,95,85,27,10,63,99,55,18;MATCH_OPTION=12;matched=h10,r1,22,6,26,7,0;Matcher=6,10;matches=6,17,1;matching=6,2,0,1,10,7;material=20;materialized=7;math=6,9,3,8,2,5,7,21,0,1,13,22;mathematicians=5;MATRIX=7;matter=4,0,1,20,3,7,5;maven=h40,139,100,153,r22;mavenInstallLocal=22,40;max=h23,r2,57,1,17,49,16,58,18,22,0,7,15,5;MAX_ARRAY_CARDINALITY=h24,r0;MAX_COLUMNS=h24,r0;MAX_COMPACT_TIME=32,0;MAX_EXECUTION_TIME=12;MAX_FILE_RETRY=h28,r0;MAX_IDENTIFIER_LENGTH=h24,r0;MAX_LENGTH_INPLACE_LOB=h1,r22,5;MAX_LOG_SIZE=h1;MAX_MEMORY_ROWS=h1,28,r0,5;MAX_MEMORY_UNDO=h1;MAX_NUMERIC_PRECISION=h24,r0;MAX_OPERATION_MEMORY=h1,r24,0;MAX_PARAMETER_INDEX=h24,r0;MAX_QUERY_TIMEOUT=32,0;MAX_RECONNECT=h28,r0;MAX_ROW_COUNT=12;MAX_STRING_LENGTH=h24,r0;MAX_TRACE_DATA_LENGTH=h28,r0;MAXBQUALSIZE=114;maxCompactTime=h32,r0,1;maxConnections=49;maxFileRetry=28,0;MAXGTRIDSIZE=114;maximum=h7,38,r0,4,5,12,24,16,49,8,13,28,1,57,21,17,6,25,18,104,10,42,59,32,20,23,11,120,27,39;MAXIMUM_CARDINALITY=12;MAXIMUM_SCALE=4;MAXIMUM_VALUE=12;maximumCardinalityInt=21;maxMemory=7;maxMemoryRows=28,0;maxOperationMemory=18;maxQueryEntries=121;maxQueryTimeout=h32,r0;maxReconnect=28,0;maxrows=h16,r0,8,13,11;maxTraceDataLength=28,0;MAXVALUE=10;maybe=11,7;MBeans=11;McLeod=68;McMahon=68;MCS=10;MD5=6,22;mean=20,0,5,1,3,52,7,24,10,15,6,21,16,17,30,32,104,23,11,28,4,9,59,2,37;meaning=24,0,42,33,7,46,10,51,5,32,17,49,6,2,11,1;meant=5;measure=15,136;measured=15,36,0,7;mechanism=5,25,18,17,0,2,11,93,50,131,7,127;Mederp=68;media=29,17;median=h23,r17,1;medium=20,15,5;MEDIUMBLOB=21;MEDIUMINT=21;MEDIUMTEXT=21;megabytes=7,1;meier=11;Meijer=68;Melbourne=15;mem=7,100,2,11,5;member=21,10,69;memFS=5;memLZF=5;memo=21;memory=h15,7,17,r1,5,37,24,0,21,2,22,40,11,12,28,6,93,27,38,68,100,10,111,136,18;MEMORY_ARRAY=h24,r0;MEMORY_FREE=h6,r5;MEMORY_OBJECT=h24,r0;MEMORY_POINTER=h24,r0;MEMORY_ROW=h24,r0;MEMORY_USED=h6,r5;MemoryEstimator=22;Mendonca=68;Mention=22;mentioned=22;menu=11,38;merchantability=20;merchantable=20;merge=h10,1,r22,16,26,7,13,32,0,2,38,15,5;merged=5;mergeInto=1,10;mergeUsing=1,10;mergeWhenClause=1;mergeWhenMatchedClause=10;mergeWhenNotMatchedClause=10;mergeWhere=h26,r0;merging=40;messageString=6;meta=0,18,25,11,47,14,13,22,19,3,113,1,118,12,116,4,9,35,31,5;metadata=h17,r0,18,22,13,5,113,29,10,11;MetaRecord=t113,r0,154,56,126,108;meteorite=5;METHOD_DISABLED_ON_AUTOCOMMIT_TRUE=h2,r0;METHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT=h2,r0;METHOD_NOT_ALLOWED_FOR_QUERY=h2,r0;METHOD_NOT_FOUND_1=h2,r0;METHOD_ONLY_ALLOWED_FOR_QUERY=h2,r0;METHODS_MUST_HAVE_DIFFERENT_PARAMETER_COUNTS_2=h2,r0;MiB=5;Micro=68;Microarray=29;Microsecond=h10,r15,6;microsecondField=10;Microsoft=h5;middle=23;middleware=29;midnight=21,10;might=5,17,7,20,22,1,6,58,38;Migrating=59;migration=t59,r22,29,11;mill=68;Millennium=h10;millenniumField=10;Miller=15,7;million=29,21,6,5;Millisecond=h10,r24,6,1,0,12,28,32,7,19,104,17,5,15;millisecondField=10;min=h23,r57,17,2,22,0,15;MIN_EXECUTION_TIME=12;MIN_ROW_COUNT=12;MIN_VALUE=21,14;mind=59,38;mine=40;Minecraft=29;Mini=11;MiniConnectionPoolManager=29,49;minimal=22,27;minimum=0,12,104,24,4,57,6,29,20,28,17,23,10,5;MINIMUM_SCALE=4;MINIMUM_VALUE=12;Minneapolis=68;minor=22,4,0,24;MinorVersion=4;minus=7,5,26,0;minusIsExcept=h26,r0;minute=h10,21,45,6,r36,0,41,29,32,5,28,1;MINUTE_TO_SECOND=h45;minuteField=10;minuteInt=10;MINVALUE=10;Miscellaneous=h20;mismatch=2,0;missing=6,11,38;misuse=7;mix=2,0;mixed=h7,r21,59,42,2;mixing=2;MMM=6;mock=7;MOD=h6,r22;ModeEnum=t91,r0,26,154,56,126,108;model=29,7;modeling=29;modern=h15,r17,14;modification=20,14,12,0,17,7;modified=h20,r17,5,14,1,10,35,7,32,0,27;modify=2,0,20,7,93,17,26,11;modifying=5,17,7;modular=17;module=35,18,0,20,15,7;moduleId=18;modulus=6;MON=6;Monday=6,10;MONEY=7;monitor=40;Monitoring=29;month=h10,21,6,45,r36,0,41,5;monthField=10;monthInt=10;MONTHNAME=h6;more=15,5,17,11,1,2,20,7,59,22,10,38,37,21,0,23,6,129,66,93,58,124,101,44,9,103,68,3;Moreover=20;Morocco=68;Morton=7;mostly=15,59;mouseClicked=h64,r0;mouseEntered=h64,r0;MouseEvent=64,0;mouseExited=h64,r0;MouseListener=64,137,56;mousePressed=h64,r0;mouseReleased=h64,r0;Move=3,0,16,9,22;moved=3,7,32,17;moveToCurrentRow=h3,9,r0;moveToInsertRow=h3,9,r0;moving=h7,r17;moz=100;mozilla=h20,r40,138;MPL=20,40;msi=5;MSSQLServer=h91,r1,5,7,22,6;much=11,7,22,62,5,138;Mueller=68;multi=h7,5,r57,15,0,22,17,1,2,11,29,116;MultiDimension=t57,r0,137,116,157,56;multiline=6;MULTILINESTRING=21;multiple=h7,15,11,r4,5,1,0,85,2,22,6,10,17,45,38,16,23,26,13,100,3,72,8,40,101,103;multiplied=12;multiply=17;MULTIPOINT=21;MULTIPOLYGON=21;MultiThreaded=11;multithreading=h7,r72;multiuser=29;music=29;MUST_GROUP_BY_COLUMN_1=h2,r0;mustExist=50,25,18;MV_STORE=32,0,17;MVC=29;Mvcc=h5,r11,17;MVMap=17,14,22,0;mvn=40;MVPrimaryIndex=22;MVRTreeMap=17;mvstore=t17,h32,59,r22,14,24,0,7,93,18,56,139,126;MVStoreException=22;MVStoreTool=17;MY_DATA=1;MY_DOMAIN=10;MY_ROUND=1;MY_SQRT=1;my_table=6;MY_TYPE=1;MY_VIEW=1;MyDecimal=2;MyListener=1;Myna=29;MyPackage=1;mysql=h15,7,91,r26,22,1,5,0,11,19,100;MYSQL_STYLE=h92;Mystic=40;MyTable=7;MyTableEngine=7;MyTrigger=1,2;MyTunesRss=29'; +ref['n']='Naked=29;NAME1=1;NAME2=1;Name_exp_=92;NAME_TOO_LONG_2=h2,r0;NAME_UNIQUE=1;nameCtx=112;named=0,24,17,7,19,2,4,5,1;namespace=19;nameString=6;naming=33,112,1,145,56;NaN=10,21,22,6;NAND=23,6;nanos=36;nanosecond=h10,r36,104,0,121,6;nanosecondField=10;national=21,10,29,22;native=h11,r143,27,5,0,7,68,38;nativeSQL=h19,r0,16;natural=10,6,5;naturalOrder=57;Naur=24,0;navigate=11;NCGC=29;NCHAR=21;NClob=3,9,0,8,19,21,46,13,109,56;near=21;necessarily=20,17,7;necessary=20,11,47,0,27,39,1,7;need=11,5,7,15,40,59,1,10,17,0,2,21,20,24,22,23,6,72,14,101,38,127,30,50,124,103,37,62;needed=24,0,17,11,107,30,7,18,21,20,72,19,93,27,15,39,5;negates=10;negative=6,36,22,10,37,12,0,72,2,59,1,7;negligence=20;negotiations=20;neither=20,1,10;Nelson=40;NeoOffice=11;net=8,3,9,29,28,13,5,40,7;Netbeans=h11;Netherlands=68;netstat=2;NetSuxxess=68;network=h136,r5,43,14,0,25,11,47,29,2,7,68,124;NETWORK_TIMEOUT=22;networkConnectionInfo=43,14,25,47,0;never=h89,r2,10,5,7,1,12,32,22,0,17,6,59,27,15;newConcurrentStringMap=h18,r0;newConstraintName=1,10;newer=0,17,118,5,142,15,150,149,123,11,7,38,151,148,138;newest=17;newFile=7;newIndexName=1;newline=6,10,62,0;newly=1;newName=1,18,66,35;newRoleName=1;newRow=54,61,98,27,97,7,0;newSchemaName=1;newsfeed=5;newStringMap=h18,r0;newTableAlias=1,10;newTransactionName=1;newUserName=1;newValueExpression=1;NEXT_PRIME=7;nextObjectId=h14,r0;nextPrime=7;nextProbablePrime=7;NEXTVAL=h6,r26,0,7,59;nextvalAndCurrvalPseudoColumns=h26,r0;nextValueReturnsDifferentValues=h26,r0;nice=59;night=38;NIH=29;nInt=37;nio=63;NIO2=22,5;NIO_CLEANER_HACK=h28,r0;NIO_LOAD_MAPPED=h28,r0;nioCleanerHack=28,0;nioLoadMapped=28,0;nioMapped=5;nioMemFS=5;nioMemLZF=5;nLine=6;nlsParamString=6;nnnnnnnnn=10,21;NO_CASTS=60,84,66,48,52,35,67;NO_DATA_AVAILABLE=h2,r0;NO_DEFAULT_SET_1=h2,r0;NO_GENERATED_KEYS=16,19,8,13;NO_WAIT=12;nobody=20;NOCACHE=10;NOCHECK=1;NoClassDefFoundError=22;NOCYCLE=10;NODATA=1;node=17,5,2,0,25,6,22;NOLOCK=26,7;NOMAXVALUE=10;NOMINVALUE=10;non=23,55,0,2,20,5,22,1,37,14,4,7,36,21,6,26,15,10,12,59,29,30,17,11,69,101,103,38,18;NON_KEYWORDS=h1,r59,5;NON_UNIQUE=4;noncompliance=20;none=h107,r1,5,14,12,0,100,2;nonKeywords=14;nonRecursiveSelect=5;NOPASSWORDS=1;nor=20,23,1,6;normal=22,21,15,1,6,100,10,2,5;normalize=h57,r0,22;normalized=57,43,22,0;normally=1,32,5,0,17,58,7,21,59,22,27;Norway=68;NOSETTINGS=1;NOT_ENOUGH_RIGHTS_FOR_1=h2,r0;NOT_ON_UPDATABLE_ROW=h2,r0;notation=10;note=5,1,10,6,11,7,37,15,21,32,29,20,23,101,93,62,40;nothing=16,13,60,84,20,22,6,52,35,26,7;notice=h20,r5;notification=58,0;notified=14,0,58,127;notifies=20,5;Notwithstanding=20;novelist=29;NOWAIT=1;NPE=22;npl=2;NSIS=40;NTEXT=21;NTFS=138;Nth=h37;NTH_VALUE=h37,r10;NTILE=h37;NULL_NOT_ALLOWED=h2,r0;NULL_ORDERING=12;nullable=4,31,0,2,53,7;NULLIF=h6,r22;nullPlusNonNullIsNull=h4,r0;NullPointerException=22,16,95,92,45,85,99,89,86,38,55,91;nullPredicateRightHandSide=10;nullsAreSortedAtEnd=h4,r0;nullsAreSortedAtStart=h4,r0;nullsAreSortedHigh=h4,r0;nullsAreSortedLow=h4,r0;nullsFirst=57;nullsLast=57;Nullsoft=40;nullString=30,10,6;NUM_INPUT_PARAMS=4;NUM_PREC_RADIX=4;numeric=h10,6,21,41,r12,23,7,0,26,22,15,1,24,4,59,31,5;NUMERIC_PRECISION=12;NUMERIC_PRECISION_RADIX=12;NUMERIC_SCALE=12;NUMERIC_VALUE_OUT_OF_RANGE_1=h2,r0;NUMERIC_VALUE_OUT_OF_RANGE_2=h2,r0;numerical=1;numericType=10;numericWithBooleanComparison=h26,r0;numServers=5;Nuxeo=29;NVARCHAR=21;NVARCHAR2=21;NVL=6;NVL2=h6;nWire=29'; +ref['o']='obey=5;obj=18,131,112,113,84,36;OBJECT_CACHE=h28,r0;OBJECT_CACHE_MAX_PER_ELEMENT_SIZE=h28,r0;OBJECT_CACHE_SIZE=h28,r0;OBJECT_CATALOG=12;OBJECT_CLOSED=h2,r0;OBJECT_NAME=12;OBJECT_SCHEMA=12;OBJECT_TYPE=12;objectCache=28,0;objectCacheMaxPerElementSize=28,0;objectCacheSize=28,0;ObjectDataType=22;ObjectFactory=112,145,56;objectId=35;objectName=1;objectNameString=6;objectSchemaString=6;objectType=18;objectTypeString=6;obligation=20;obtain=20;obtained=15;obtaining=22;Occasional=22;occupancy=22;occur=17,2,5,1,22,0,68,23,11,63,7,14;occurred=2,0,16,18,54,61,25,58,133,17,49,5;occurrence=6,29;occurrenceInt=6;octal=6;OCTET_LENGTH=h6;octets=21,59,22;October=93;odbc=h5,r4,0,7,22;odbcad32=5;odd=24,2;ofDays=h36,r0;ofDaysHours=h36,r0;ofDaysHoursMinutes=h36,r0;ofDaysHoursMinutesNanos=h36,r0;ofDaysHoursMinutesSeconds=h36,r0;off=h17,r1,19,50,15,25,18,7,5,22,0;offer=20,7;offered=20;offering=20;offHeap=17;OffHeapStore=17;official=24,59,22,0;officially=59;offset=1,27,39,7,11,46,6,37,51,25,50,18,0,21,10,24,17,9,26,72,5;OffsetDateTime=21;offsetInt=37,27,39;OffsetTime=21;ofHours=h36,r0;ofHoursMinutes=h36,r0;ofHoursMinutesNanos=h36,r0;ofHoursMinutesSeconds=h36,r0;ofMinutes=h36,r0;ofMinutesNanos=h36,r0;ofMinutesSeconds=h36,r0;ofMonths=h36,r0;ofNanos=h36,r0;ofSeconds=h36,r0;often=24,0,15;ofYears=h36,r0;ofYearsMonths=h36,r0;OGC=10,29;Ohloh=29;oid=26,22,0,7;old=1,17,14,47,10,55,21,0,54,22,25,98,11,5,61,7,38,97,20,16,23,6,27,111,12,37,19,59;oldConstraintName=1;older=h139,r0,1,118,5,142,111,29,150,149,22,123,17,11,38,15,151,148;oldInformationSchema=14;oldMap=17;oldRow=54,98,27,61,97,7,0;oldSession=47,14;oldVersion=17;Oliver=68;Olivier=68;omissions=20;once=0,17,1,22,61,24,101,103,54,15,3,21,32,6,2,38,10,5;onDuplicateKeyUpdate=h26,r0;onePhase=44;ongoing=20;online=h17,11,r29;onRollback=h14,r0;OntoBroker=29;ontology=29,68;Ontoprise=29;OOM=22;OOME=22;open=h15,38,r1,0,7,2,29,11,4,33,17,5,18,25,34,28,135,30,32,20,24,49,124,19,68,108,16,6,50,90;openBrowser=h34,r0;opened=h58,r7,5,2,11,0,17,25,38,15,1,61,28,34,54;openFile=h50,25,18,r0;OpenGroove=29;opening=h7,38,r17,11,1,15,18,32,22,0,28,135,58;openjdk=28,38;OpenJPA=29;openMap=17;openMode=18;openNew=25;OpenOffice=h11,r29,7,40;OpenSocial=29;openVersion=17;operand=h10,r22;operates=17;operating=5,6,7,11,1,138;operation=h17,15,r5,6,0,54,25,2,22,1,10,61,11,21,18,98,16,7,97,29,24,28,93,14,38,30,20,129,23,120;operator=7,22,10,1,6;opponent=136;optimal=15;optimisation=93;optimization=h15,r32,22,19,60,0,1,4,35;optimize=32,22,15,7;OPTIMIZE_DISTINCT=32,0;OPTIMIZE_EVALUATABLE_SUBQUERIES=32,0;OPTIMIZE_IN_LIST=32,0;OPTIMIZE_IN_SELECT=32,22,0;OPTIMIZE_INSERT_FROM_SELECT=32,0;OPTIMIZE_OR=32,0;OPTIMIZE_REUSE_RESULTS=h1;OPTIMIZE_SIMPLE_SINGLE_ROW_SUBQUERIES=32,0;OPTIMIZE_TWO_EQUALS=32,0;optimized=15,57,86,22,0;OPTIMIZED_SQL=h86;optimizeDistinct=h32,r0;optimizeEvaluatableSubqueries=h32,r0;optimizeInList=h32,r0;optimizeInSelect=h32,r0;optimizeInsertFromSelect=h32,r0;optimizeOr=h32,r0;optimizer=h15,r1,32,7,38;optimizeSimpleSingleRowSubqueries=h32,r0;optimizeTwoEquals=h32,r0;option=h10,7,15,r11,0,1,71,5,34,30,63,90,102,62,88,50,106,94,87,105,40,2,17,20,22,6,59;optional=12,10,6,1,4,11,26,21,0,28,23,7,69,68,5;optionally=17,11,1,7;options1=71;options2=71;optionString=10;ORA_HASH=h6;oracle=h7,91,15,r22,59,6,1,5,11,40,29,32,26,38,19,100,138;OraclePlatform=11;orchestration=29;order=h10,r23,37,1,0,2,15,7,4,6,26,92,20,95,5,45,89,86,91,85,99,55,22,17,113,96,21,30,49,11;ORDER_BY_NOT_IN_RESULT=h2,r0;orderBy=11;ordered=h23,r22,10,4;ordering=1,37,6;ORDERING_SPECIFICATION=12;ordinal=45,12,86,32,92,0,89,91,95,59,85,99,55;ORDINAL_POSITION=12,4;ORDINALITY=6;ordinary=20;organization=29;organized=h69,r15,17;oriented=29;original=6,43,20,0,22,1,5,86,57,68,60,77,76,72,11,74,81,73,39,79,82,94,75,78,35,7,117,83,80;ORIGINAL_SQL=h86;originally=29,20,5;originalSchemaString=1;originalString=6;originalTableString=1;originate=20;originator=20;Orion=29;ORM=29;orphan=2,0;OSDE=29;OSGi=h11,r29;OSGI_JDBC_DRIVER_CLASS=11;OSGI_JDBC_DRIVER_NAME=11;OSGI_JDBC_DRIVER_VERSION=11;Osmond=68;osoa=29;othersDeletesAreVisible=h4,r0;othersInsertsAreVisible=h4,r0;othersUpdatesAreVisible=h4,r0;otherwise=6,20,10,4,16,11,14,1,3,2,7,15,5,18,21,23,90,133,40,31;OUT_OF_MEMORY=h2,r0;outer=4,0,10,38,32,22,6,7;OutOfMemory=38;outperforms=29;outPos=72;output=0,2,106,102,25,94,88,34,87,46,71,105,72,51,1;outputFileName=30;OutputStream=72,51,46,70,0;outside=5,54,61,17,1,100,11;outstanding=20;overall=69;overflow=23,22,68,10,2,0,5;overhead=17,5,21,15;overlapping=1,10;overload=2,4,0;overloaded=12;overloading=h7;overrideClause=1,10;overriding=10,59,1;overtakes=5;overtaking=38;overwrite=43,0,5,1;overwritten=17,5,32,15,1,6;OWL=29;own=20,4,7,66,0,22,69,1,5,11,3,29,12,32,21,23,93,59;ownDeletesAreVisible=h4,r0;owner=1,66,0,48,52,12,22,2,5,108;ownerName=1;ownership=20;ownInsertsAreVisible=h4,r0;ownUpdatesAreVisible=h4,r0'; +ref['p']='P5H2=29;P_ID=2;packageName=11;pad=6,26,12,0;PAD_ATTRIBUTE=12;padded=89,21,0,26,7,108;padding=100,6,22;paddingString=6;page=h17,7,93,29,r69,22,15,11,40,5,20,24,4,0,6,68,1,124,138;PAGE_SIZE=7;PageParser=40;pageSplitSize=17;PageStore=55,22,0,59;pair=17,10;panel=11;Paradox=5;paragraphs=5;parallel=5,19,17;param=11,53,1,7,22;param1=7;param2=7;PARAMETER_DEFAULT=12;parameter_meta=11;PARAMETER_META_DATA=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;PARAMETER_MODE=12;PARAMETER_NAME=12;PARAMETER_NOT_SET_1=h2,r0;PARAMETER_STYLE=12;parameterIndex=h13,r8;Parameterized=17;ParameterMetaData=h53,r13,109,56;parameterModeIn=53;parameterModeInOut=53;parameterModeOut=53;parameterModeUnknown=53;parameterName=h8;parameterNoNulls=53;parameterNullable=53;parameterNullableUnknown=53;parent=2,12,17,5;PARENT_DOMAIN_CATALOG=12;PARENT_DOMAIN_NAME=12;PARENT_DOMAIN_SCHEMA=12;PARENT_ID=10;parentheses=10,23,22;parenthesis=1;parenthesized=22;parse=14,0,22,27,25,2,47,30,6,40;PARSE_ERROR_1=h2,r0;parseColumnWithType=22;parsed=26,0,7,1,2,14,15,23,6,10,11;PARSEDATETIME=h6,r22,2;parseInt=1;parseKey=h27,r0,39;parser=h93,r22,43,40,0;parsing=14,0,22,15,7;parsingView=14;part=2,0,1,5,36,20,11,15,29,12,22,25,17,38,10,40,108;partial=22;partially=7,19,0,17,14,5;participate=20;particular=20,69;particularly=7;parties=20;partition=37,10,5;partners=20;partnership=29;party=20,22,7;pass=7,22,11,5;passed=7,1,103,6,101,4,19,0,11,5;passes=7;passing=h5;password=h5,1,r33,7,0,2,11,43,48,87,34,17,49,63,94,71,24,18,10,19,62,28,6,4,90,111,116,108;password_hash=5;passwordString=1,6;past=21,10;Paste=40;Pastebin=40;Patadia=68;patch=40;patches=38,40;patent=20;path=2,11,7,39,22,25,18,0,1,40,5,29,38,88,50;Pathogen=29;pattern=6,51,46,0,4,11,26,10,5,29;paused=1;pauses=17;pay=20;PayPal=68;PBKDF2=17;Pearson=23;pending=25,47,14,0,18,7;Pengxiang=68;people=7,40;per=h15,r5,7,17,1,28,4,0,11,6,12,32,20,66,2,93,18,108;percent=1,20,7;PERCENT_RANK=h37,23;percentage=32;percentile=23;PERCENTILE_CONT=h23;PERCENTILE_DISC=h23;perform=21,29,20,0,15,2,10,7,111,59,5,14;performance=t15,h17,r7,5,20,38,32,22,6,11,29,21,16,28,59,1,63;performed=6,54,7,5,0,98,15,61,25,23,97;period=21,6,20;periodic=58,0;permissible=1;permission=h136,r11;permits=20;permitted=20,95,21,92,45,85,99,89,86,55,91;persist=7,17;persisted=21,17,1,12,7,22,6,11,130,5;persistence=29,11,17;persistent=1,7,0,43,2,130,15,29,60,22,17,5,108;Persister=29;persisting=17;person=20,1,5;personal=20;Pete=68;Peter=68;PFGRC=29;pg_catalog=24,0,22;PG_CATALOG_SCHEMA_ID=h24,r0;PG_DEFAULT_CLIENT_ENCODING=h28,r0;pg_type=22;PG_TYPE_TEXTARRAY=22;PG_VERSION=h24,r0;pgAllowOthers=34,5,11;PgCatalogTable=22;pgClientEncoding=28,0;pgDaemon=34;pgPort=34;PgServer=22,24,0;phantom=55,5,22;phase=h5,r1,17,29,44,7,38;phenomena=55,0;Philip=15;Philippe=68;phone=15;Phromros=68;physical=44,5,0,7;Pickle=29;pid=2,15;pieces=5;Pierre=68;Piman=29;Pipelining=22;PK_NAME=4;PKCOLUMN_NAME=4;PKTABLE_CAT=4;PKTABLE_NAME=4;PKTABLE_SCHEM=4;place=17,5,24,25,18,20,0,1;placed=20,23,11;placeholder=23;plain=5,1,10,7;plaintext=5;plan=h15,r1,17,7,38,6,5;plane=23,6;planning=h93;platform=h138,15,r139,11,29,0,118,142,68,153,150,149,123,90,7,38,151,100,40,148;play=29;please=40,5,11,7,38,15,1,32,6,2,29,21,101,59,10,124;plug=29,40,7;pluggable=h17,7,5,r1;plugin=h40,r29,11;plus=17,37,12,15,23,10,69,6,11;point=21,5,17,7,11,10,58,0,110,15,68,1,118,40;PointBase=68;pointer=17,24,0;pointing=17,5;POINTZ=21;Poker=68;Poland=68;polar=6;polar2CartesianArray=22;poleposition=h15,r29;policies=29;polling=5;POLYGON=5,21,22;pom=22;pool=h11,15,100,r49,0,1,44,29,142;poolable=16,0;pooled=0,33,16,49;PooledConnection=44,33;pooling=4,0;poor=21,6;Poormans=29;pop=5;populate=1,7;populated=9,0;populateRowFromDBObject=h113,r0;population=23;port=h11,r34,5,7,24,0,2,12,25,47,14,136;portability=h40,r68;portable=22,1;ported=20,7;porting=29,20,68;portions=20;portlet=29;Portugal=68;pos=46,51,72,4;position=h51,46,r3,0,6,17,12,14,7,22,129,72,9,1,2,144,100,10,58,18,108;POSITION_IN_UNIQUE_CONSTRAINT=12;positioned=4,0,2,30;positionInt=6;positive=6,37,12,10;possibility=20,22,7;possible=5,7,0,15,11,1,21,22,2,4,31,20,38,34,16,24,49,23,47,33,14,25,9,37,19,3,53,28,17,88;possibly=93,16,6,2,13,11,7;post=10,29,22,40,38;PostGIS=5;postgres=22;postgresql=h15,7,91,r5,22,26,11,1,134,0,6,38,19;POSTGRESQL_STYLE=h86;posting=40;potential=29,20,5;potentially=4,29,34;pow=5,6;power=h6,r5,25,18,20,28,1,7,38;poweroff=5;practicable=20;practice=2;pre=100,22,17,10,5;precede=37,23;precedence=1;preceding=h10,r2,37,0;precision=h21,r12,10,6,9,45,23,22,4,0,50,2,53,31,24,41,26,1,28,59,15,5;precisionInt=21,10,6;predefined=h10,r21;predefinedType=10;predicate=h10,r22,7,23,26,0;predicted=17;prediction=17;PREFERDOSLIKELINEENDS=7;preferences=11,40;preferred=20;prefix=24,0,27,5,6,7,86,10,45,43,28,2,4,1,18;PREFIX_INDEX=h24,r0;PREFIX_JOIN=h24,r0;PREFIX_PRIMARY_KEY=h24,r0;PREFIX_QUERY_ALIAS=h24,r0;PREFIX_TEMP_FILE=h28,r0;prefixed=26,0;prefixes=28;prefixTempFile=28,0;prep=13,2,15,57,5,22;prepare=h14,1,44,r0,5,25,47,20,15,2;prepareCall=h19,r0;prepareCommand=h25,14,47,r0;prepareCommit=h14,r0;prepared=h15,r19,14,0,16,134,13,5,12,57,32,22,44,118,3,25,47,7,53;PREPARED_STATEMENT=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;PreparedStatement=h8,r13,19,21,2,0,5,15,57,11,109,56;prepareLocal=h14,r0;prepareQueryExpression=h14,r0;prepareStatement=h19,r0,24,15,2,5;preparing=24,0;presence=22,14,0,10;present=10;presentation=29;preserve=12,22,69,6,101,11,103;preserved=10,30,1,34,6,21,0,11;preserveWhitespace=10;preserving=30,0;press=15,11;prevent=22,20,15,133,6,2,11;previous=h3,9,r59,0,37,69,15,17,1,10,101,103;previously=17,101,11,103;PRICE=10;primary=2,4,10,15,1,0,27,93,12,11,7,17,5,22,24,3,29,16,26,62,39,13;PRIMARY_KEY=4,11;PRIMARY_KEY_2=15;PRIMARY_KEY_5=15;PRIMARY_KEY_9=15;primaryCatalog=4;primarySchema=4;primaryTable=4;primitive=21,17,4,5;principal=20,66,0,108;print=h62,r17,88,87,50,105,5,0,102,34,71,106,94,90,15,63;printed=11,2;printing=62;println=17,11,15,92,45,89,86,91,95,85,99,5,55;printNoDatabaseFilesFound=106,102,94,90,88,34,87,50,62,64,63,71,105;printStackTrace=h115,77,76,75,78,74,83,80,81,73,79,82,r0;PrintStream=0,77,76,75,78,74,83,80,81,73,62,79,82,115;PrintWriter=0,49,33,77,76,75,78,74,83,80,81,73,79,82,115;prior=20;priority=10,5;private=7,100;PRIVILEGE=4,12,1,0,2,22;PRIVILEGE_TYPE=12;prize=136;PRO=29;probability=5;probably=27,0,38;problem=h5,40,38,r2,7,15,11,21,1,32,22,0,28,25,6,50,33,63;problematic=11,5;procedure=t134,h7,136,r4,0,14,11,108,12,28,154,56,126;PROCEDURE_CAT=4;procedure_columns=11;PROCEDURE_NAME=4;PROCEDURE_SCHEM=4;PROCEDURE_TYPE=4;procedureColumnIn=4;procedureColumnInOut=4;procedureColumnOut=4;procedureColumnResult=4;procedureColumnReturn=4;procedureColumnUnknown=4;procedureNamePattern=4;procedureNoNulls=4;procedureNoResult=4;procedureNullable=4;procedureNullableUnknown=4;procedureResultUnknown=4;procedureReturnsResult=4;process=h71,11,15,r5,7,29,0,2,22,24,1,100,40,18,20,16,28,17,6,136;processed=10,1,6,22,15;processes=7,5,29,15;processing=29,16,0,7;produce=22,16,11,0,1,10,38;product=h29,r20,4,0,24,5,23,38;production=5,15,1,7,38;prof=15;prof_start=11;prof_stop=11;profile=15,11;profiler=h15,r63;profiling=h15,r11;Profit=20,29;program=20,11,29,5,15,90,124;programmed=29;programmer=69;programming=29,69;progress=88,87,105,18,0,102,71;prohibited=19,20,0;prohibits=20;project=h29,38,17,153,r40,59,68;proleptic=21;prompt=5,11;promptly=20;proof=68;prop=5,40,6;propagated=5;proper=22;properly=22;properties=h5,r19,0,11,43,28,2,34,24,6,111,62,32,23,7,15,108;property=28,0,43,5,7,2,19,20,11,38,100,30,16,6,15,3;protect=5,7,11;protected=27,16,35,125,39,54,96,5,66,19,62,57,13,11;protecting=5;protection=h5,r34,17,38;protocol=h5,r24,0,25,1,29,44,11,7;prototyping=7;prove=20,5;proven=5;provide=5,29,40,20,0,11,38,17,44,142,7,120,136,59,69,15,108;provided=20,0,26,7,10,6,4,1,5,18,12,2,127,59,133;providers=29;providing=29,40,5;provision=20;provisionally=20;proxy=29;pseudo=26,0,4,7,6,15,5;PSEUDO_COLUMN=4,11;PseudoColumnUsage=4;psqlodbc=5;psycopg2=22;PUBLIC_ROLE_NAME=h24,r0;PUBLIC_STATIC_JAVA_METHOD_NOT_FOUND_1=h2,r0;publicly=20;publish=20,15;published=20,68;publishing=29;Pulakka=68;pull=40,22;pure=17;purely=59,17;purpose=20,29,59,1;Push=22;pushed=6;put=17,5,20,22,11,38;PVS=22;pwd=87,2,7,5,34,62,94,63,71'; +ref['q']='Qian=68;qualified=1,5,38;qualifier=h10,r45,0,12,36,23,22,127;qualifierOnly=45;qualify=1,20,5;quality=29,20;quantified=h10,r4,22,0,23;quantifiedComparisonRightHandSide=10;quantifiedDistinctPredicateRightHandSide=10;QUANTITY=10;quarter=h6,10;quarterField=10;queries=h5,r15,37,1,7,0,26,29,24,2,11,10,58,12,32,22,116,17,59,57,63;query=h10,38,r1,15,0,11,2,22,16,32,5,14,7,29,121,6,27,39,13,57,24,58,30,4,12,108,104,28,93,63;QUERY_CACHE_SIZE=32,0;query_indexed_int=15;query_indexed_string=15;QUERY_STATISTICS=h1,12;QUERY_STATISTICS_MAX_ENTRIES=h1,24,r0;query_string=15;QUERY_TIMEOUT=h1;queryCacheSize=h32,r0;QueryEntry=t104,r0,121,154,56,126,108;QueryExpressionIndex=14;querying=22,29,7,5;QueryStatisticsData=t121,104,r0,154,56,126,18,108;queryString=6,27,16,39;queryTimeout=14;question=t38,r40;queue=1,12,7;QUEUE_SIZE=12;quick=5;quicker=29;quickly=29,15,17,7;Quickstart=t124,r156;Quickstarter=11;quiet=88,87,105,102,71;quirks=14,22,0,1;QUIRKS_MODE=1;quirksMode=14;quit=15,11;quite=15,38,5;Quoss=68;quotation=7;quote=h29,r10,30,4,1,16,123,6,5,21,0,3,8,13,33,31,119,44,110,7,114,19,46,51,42,53,70;QUOTE_IDENT=h6,r1;QUOTE_ONLY_WHEN_REQUIRED=60,84,66,48,52,35,67;quoteArray=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;quoteBigDecimal=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;quoteBytes=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;quoted=h10,r16,22,123,6,11,26,7,27,39,1,5,60,21,30,84,0,48,2,52,35,67;quoteDate=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;quotedName=60,84,48,10,52,35,67;quoteIntArray=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;quoteMap=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;quoteSQL=h27,r0,39;quoteTime=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;quoteTimestamp=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119'; +ref['r']='R_1=6;race=5;RAD=29;RADIANS=h6;radius=100;radix=12,4;Rafel=68;rail=40;Railo=29;railroad=h40,r37,21,23,1,6,10;rainbow=17,6;raised=6;RAM=28,1,0;Ramiere=68;ran=2,0,15,11;RAND=h6,r5;random=5,6,17,7,34,15,24,11,14,93,0,48;RANDOM_UUID=h6,r5,16,13;RandomAccessFile=22,5;randomized=68,7,38;randomly=5,21,6;range=h12,r57,10,0,2,7,17,37,15,5,116,6,1;ranging=29;rank=h37,23;rapid=7;rate=32,5;rather=7,136;ratio=37,15;RATIO_TO_REPORT=h37;raw=21,11,27,39,17,5;RAWTOHEX=h6,r7;RazorSQL=29;Razuna=29;RDF=29;reached=2,0,32;read=h7,30,5,r55,17,0,1,15,12,22,11,6,25,21,24,3,18,2,16,50,40,19,28,72,47,63,31,51,4,116;READ_COMMITTED=h55;read_hot=15;READ_UNCOMMITTED=h55,r38;readable=50,17,11,0,6,5;readBlobMap=h50,r0;readClobMap=h50,r0;reader=0,8,3,9,13,46,30,63,27,70,62,50,29,21;readException=h25,r0;reading=h11,5,r30,17,0,10,51,15,6,9,7,136;readLob=h50,25,18,r0;README=22;readOnly=h6,r1,19,18,17,11,7;ReadOnlyDatabaseInZip=7;readPassword=5;readRow=h30,129,r0;readSessionState=h47,r0,25,14;readStoreHeader=22;readVariableInt=h72,r0;ready=11,5;real=h21,41,15,r10,0,23,19,1,26,38,136;really=15,11;realm=7;realtime=11,29;realType=10;reason=6,5,21,77,76,75,78,2,11,7,38,74,83,80,81,73,79,82,32,20,16,68,40;reasonable=20,15;reasonably=20;reasoning=5;receipt=20;received=20;receives=20;receiving=25,0;recent=17,1,22,11,5;recently=11,5;recipient=20;recoding=29;recognising=22;Recognize=22;recognized=22,11;recommendations=29;recommended=21,11,7,69,17,6,5,138;recompilation=22;recompile=h1,r32;RECOMPILE_ALWAYS=32,0;recompileAlways=h32,r0;reconnect=h7,r58,0,28,100;reconnection=22;record=5,113,0,1,24,7,15,17,4,108;recover=t50,h5,44,r0,38,18,116,157,2,11,56,137,15;RECOVER_TEST=5;recovering=0,58,50,116,11;recovery=h7,r2,44,11,5;recreate=5;recreated=1;rectangular=6;recursion=5;recursive=h5,r1,93,22;recursively=17,14,0;recursiveQueryName=5;recursiveSelect=5;red=6,37,21,59,23,1,10,11;RedBase=20;redeployment=11;redirection=28;Redirects=62,0;redistributions=20;redo=h93;reduce=17,1,7,5,22,15,6,2;reduced=15;redundancy=15,6;redundant=22,5;Reenable=22;reevaluate=6,101,103;Reeve=68;Ref=3,9,0,8,13;REF_GENERATION=4;Refactor=22;refactoring=22,29;refColumnName=10;refer=17,1,5;reference=h10,r0,2,4,12,7,112,33,8,3,1,21,32,20,22,9,11,26,15,13,6,38,100;Referenceable=33,145,56;referenced=12,2,0,10,43,4,26,1,6,7;referencesSpecification=10;referencing=h7,r2,60,84,0,48,10,52,35,67,12,22;referential=h10,r2,12,0,1,22,7,4,35,26,5,38;REFERENTIAL_CONSTRAINTS=h12;REFERENTIAL_INTEGRITY=h1,r38;REFERENTIAL_INTEGRITY_VIOLATED_CHILD_EXISTS_1=h2,r0;REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1=h2,r0;referentialAction=10;referentialConstraint=10;referred=1;reflected=1;reflection=28,68;ReflectiveOperationException=111;reflects=11;reformed=20;refreshRow=h3,9,r0;refTableName=10;regarding=1;regardless=10,5;regex=26,6;Regexp=h10,r2,7,0,5;REGEXP_LIKE=h6;REGEXP_REPLACE=h6,r7,26,22,0;REGEXP_SUBSTR=h6,r22;regexpPredicateRightHandSide=10;regexpReplaceBackslashReferences=h26,r0;regexString=6;regions=38;register=8,0,14,44,5,29,11,33;registered=2,33,7,5,0,58,11,18;registerOutParameter=h8,r0;registerTableAsLocked=h14,r0;registerTableAsUpdated=h14,r0;REGR_AVGX=h23;REGR_AVGY=h23;REGR_COUNT=h23;REGR_INTERCEPT=h23;REGR_R2=h23;REGR_SLOPE=h23;REGR_SXX=h23;REGR_SXY=h23;REGR_SYY=h23;regression=22,23;regular=h7,91,r17,1,5,6,12,0,24,2,59,10,11,52,15,14;regularly=40,5,38;regulation=h20;regulatory=29;Reimplement=22;reimplemented=22;reindex=h27,39,r0;reinstated=20;reject=22;rejected=21,1,11;related=2,20,22,0,59,69,6,7;relates=29;relating=20;Relation=29;relational=29;relative=h3,9,r37,0,23,2,10,38,28,6,7;relatively=21,15,5;release=h38,139,r0,24,19,22,9,7,42,5,119,6,2,11,1,40,138,18;released=24,7,29,0,5;releaseDatabaseObjectIds=h18,r0;releaseSavepoint=h19,r0;relevance=27,39;relevant=20,17,11;reliability=38;reliable=h38,r15,6,5;relies=2,9;relying=2;remain=5,11;remainder=20,2;remaining=36,15,6,22,0,5;remarks=12,4,22,11,5;remedy=20;remember=5,14,22,0;remembered=3,5;remote=h5,r7,11,0,25,2,47,12,14,43,93,1,34,63,111,108;REMOTE_CONNECTION_NOT_ALLOWED=h2,r0;REMOTE_DATABASE_NOT_FOUND_1=h2,r0;remotely=11,2,0,7,5;REMOTEUSER=7;remoting=29;removal=22;remove=h98,61,r22,0,1,14,18,6,44,20,25,66,43,7,27,39,17,54,97;removeAllTriggers=h27,r0,39;removeAtCommit=h14,r0;removeAtCommitStop=h14,r0;removeChildrenAndResources=h60,84,48,52,67,35,r0,66;removeConnectionEventListener=h44,r0;removed=17,22,6,59,100,14,20,0,69,1,18;removeDatabaseObject=h18,r0;removeIndexAccess=h39,r0;removeLocalTempTable=h14,r0;removeLocalTempTableIndex=h14,r0;removeMeta=h18,r0;removeProcedure=h14,r0;removeProperty=h43,r0;removeSchemaObject=h18,r0;removeServer=h25,r0;removeSession=h18,r0;removeStatementEventListener=h44,r0;removing=17,5;rename=h1,66,35,r0,18,22,2,20,60,84,28,48,11,52,67;renamed=7,2,20,0,1;renameDatabaseObject=h18,r0;renameSchemaObject=h18,r0;renaming=h7,r60,84,52,35,0,1;Rene=68;repair=20;repeat=h6,r7;repeatable=55,5,1,22,12,0,7;REPEATABLE_READ=h55,r22;repeated=6,17,136;repeatedly=62;replace=h6,r22,1,7,92,26,5,19,93,0,15,23;REPLACE_LOBS_FOR_TRACE=60,84,66,48,52,35,67;replaceAll=6;replaced=1,9,59,23,11;replaceInto=h26,r0;replacement=69,28,6;replacementSet=1;replacementString=6;replication=29;repo=40;report=40,22,38;reported=0,19,16,3,38,68,11;Reporting=h40;repositories=29;repository=h40,153,r29,22;representation=h20,r6,10,86,55,0,77,22,76,74,93,81,73,79,82,1,75,78,7,127,117,83,80,3,36;represented=21,7;representing=6;reproduce=20;reproduced=40;reproducing=7;REPRODUCTION=20;Republic=68;request=h40,38,r16,107,22,0,15,11;requested=16,10,13,46,0,123,6,5;require=h7,r37,10,2,5,29,15,1,20,22,0,17,19,40;required=1,6,5,7,11,15,40,13,2,8,16,48,17,10,21,20,123,0,23,26,88,69,63,124,94,9,38,62;requirement=h20,17,138,r29,57,1,7,5;res=40;research=29;resellers=20;reserved=h5,r4,20,0;reserves=20;reset=h129,30,r0,28,3,9,47,14,1;resetThreadLocalSession=h14,47,r0,25;reside=11,7,5,14;resistant=17,7;resolution=19,25,1,10,120,14,18,0;resolve=32,40;resort=32,28;resource=11,0,29,60,84,48,9,52,35,67,42,119,6,136,1,5;respect=37,20;respective=17,7;respectively=1;respond=19;responding=88,1;response=29,5;responsibilities=h20;responsibility=20;responsible=20,135;rest=29,17;restart=10,1,7,26,5,22,0,15,6,11;restarted=1,10;restarting=11;restore=t102,h11,r0,22,116,12,137,1,157,2,56,5;restored=14;restoredValue=14;restrict=1,10,22,12,32,2,20,0,7,5;restricted=59,4,0,6,136;Restricting=h5;restriction=21,22,11,5;restrictive=7;RESULT_CLOSE=h25,r0;RESULT_FETCH_ROWS=h25,r0;RESULT_RESET=h25,r0;RESULT_SET=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;RESULT_SET_META_DATA=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;RESULT_SET_NOT_SCROLLABLE=h2,r0;RESULT_SET_NOT_UPDATABLE=h2,r0;RESULT_SET_READONLY=h2,r0;resultClass=70;resulted=2,0;resulting=5,20,15,1,2,11;ResultInterface=3,0;resultSet=h9,3,16,r4,19,0,30,13,65,42,2,21,54,27,39,7,5,31,11,8,63,56,57,22,109,15,1,116,38,137;resultSetConcurrency=h16,r19,0,8,13;resultSetHoldability=19;ResultSetMetaData=h9,31,r7,22,38,0,11,26,56,3,13,53,109,137;resultSetType=h16,r19,0,8,13;retain=1;RETENTION_TIME=h1;retrieval=16,29,19,93,1;retrieve=0,11,19,21,34,15,49,9;retrieved=15,1;retry=28,22,25;RETURN_GENERATED_KEYS=16,13,19,0,8;returned=16,6,3,0,4,39,1,53,24,23,13,9,42,27,5,8,31,37,19,32,92,41,48,26,95,15,10,14,45,89;returning=h7,r1,6,22,16,13,5;reuse=17,1,11,5;REUSE_SPACE=32,0;reuseSpace=h32,r0;revealed=5;reverse=1,21,10,5;reversed=57;reverseOrder=57;revert=18;revisions=20;revoke=h1,r2,0,52,136;revokeTemporaryRightsOnRoles=h66,r0,48,67;revolutions=5;rewritten=29;RFC=5,21,22,10;Rice=72,0;Richard=68;rid=22;RIFE=29;right=t52,h10,1,20,6,12,35,r0,66,48,89,2,7,108,11,14,21,22,17,18,60,84,23,26,5,154,56,38,136,67;rightMask=48,52;rightmost=6;RightOwner=t66,h48,67,r0,52,18,35,154,56,126,108;rightsChecked=14;Rijndael=7;rioyxlgt=1;risk=20,5,59,17;risky=34;rnd=11;Rojas=68;role=t67,h1,12,35,136,r0,66,2,18,6,11,7,52,128,24,108,60,84,22,48,154,56,127,126;ROLE_ALREADY_EXISTS_1=h2,r0;ROLE_ALREADY_GRANTED_1=h2,r0;ROLE_CAN_NOT_BE_DROPPED_1=h2,r0;ROLE_NAME=12;ROLE_NOT_FOUND_1=h2,r0;roleName=1,18,67;ROLES_AND_RIGHT_CANNOT_BE_MIXED=h2,r0;roll=14,0,19,25,44,47,17,1,144,108;rollback=h1,19,14,44,r0,2,4,61,54,12,7,8,22,16,17,93,5;RollbackListener=14,56,126;rollbackTo=h14,r0;rollbackToSavepoint=h14,r0;rolled=1,5,14,110,7,19,0,17,6,118,10,2;ROLLED_BACK=22;rolling=0,1,58,14,16,11;ROLLING_BACK=22;room=5;root=17,2,93,40;ROTATELEFT=h6,r22;ROTATERIGHT=h6,r22;Rotates=6;rotation=6;rough=15;roughly=5;round=h6,r21,22,17,1,7;rounded=7,21,16,1,6;rounding=21,10;ROUNDMAGIC=h6;routine=h12,r6,22;ROUTINE_BODY=12;ROUTINE_CATALOG=12;ROUTINE_DEFINITION=12;ROUTINE_NAME=12;ROUTINE_SCHEMA=12;ROUTINE_TYPE=12;ROW_COUNT_ESTIMATE=12;ROW_IDENTIFIER=12;ROW_NOT_FOUND_IN_PRIMARY_INDEX=h2,r0;ROW_NOT_FOUND_WHEN_DELETING_1=h2,r0;ROW_NUMBER=h37,r6;rowcount=121,3,24,0;rowCountCumulative=h104,r0;rowCountInt=1;rowCountMax=h104,r0;rowCountMean=h104,r0;rowCountMin=h104,r0;rowDeleted=h3,9,r0;RowDescription=22;rowFactory=18,0;rowid=3,9,0,8,4,13;ROWID_=10;ROWID_UNSUPPORTED=4;RowIdLifetime=4;rowInserted=h3,9,r0;rownum=h6,r22,5;rowNumber=3;rowSeparator=6,10;RowSource=9;rowType=10;rowUpdated=h3,9,r0;rowValueExpression=1,6,10;Roy=68;royalty=20;RPAD=h6;RPM=5;RSHIFT=h6;rss=29,153;RTRIM=h6,r22;ruby=1;rule=12,10,38,30,6,11,5;run=h15,5,62,34,r11,2,0,7,38,40,1,138,32,88,102,17,6,63,106,94,136,87,71,105,22,93,124,116,59,68;Runnable=34,62,137,56;running=h5,99,r0,11,15,34,7,58,2,136,24,29,32,16,90,18,28,17,6,88,1,40,63,12,38;RUNNING_TOTAL=6;runscript=t63,h1,r7,0,11,5,59,137,116,157,56;runtime=7,15,40,58,11,38;runTool=h62,106,102,94,90,88,34,87,50,63,71,105,r0,64;rwd=5,7;rws=7,5'; +ref['s']='S_UNKNOWN=2;safe=10,58,19,16,8,72,3,13,11,7,5;safely=5,36;safety=19,59,16,8,17,3,13,7;said=30;sale=20;salt=h1,r5,48,0,24,6,17,7;SALT_LEN=h24,r0;Salz=5;same=h7,r6,5,2,23,0,11,1,12,15,10,37,36,17,32,4,26,14,44,38,19,21,22,54,33,61,30,20,16,28;sample=h124,r7,23,1,5,17,2,15,32,156,11,38,63;SAMPLE_SIZE=1,22;sans=100;Sat=6;satisfy=1;Sava=29;save=22,11,5;saved=11,5;savepoint=t144,h1,r19,0,14,2,110,4,7,56,108,16,109,17,8,13,33,31,119,44,154,38,114,46,51,42,118,3,126,53;SAVEPOINT_IS_INVALID_1=h2,r0;SAVEPOINT_IS_NAMED=h2,r0;SAVEPOINT_IS_UNNAMED=h2,r0;savepointName=1;saving=38,21,5;saying=7;Scala=29;scalability=22;scalable=29,17;scalar=57,10,0,7;SCALAR_SUBQUERY_CONTAINS_MORE_THAN_ONE_ROW=h2,r0;scalarColumn=57;scale=9,12,3,8,22,4,0,26,45,6,53,21,23,59,13,31,2,29,1,10;scaleInt=10,21;scaleOrLength=3,8,13;scan=h15,r17,1,7,5;scanCount=15;scanned=15;scanners=h15;scanning=58,0;scenarios=15;scheduler=29;schema=h1,12,125,35,r4,0,18,2,48,6,14,27,39,19,66,11,47,25,24,7,52,5,22,54,31,32,26,60,84,98;SCHEMA2=10;SCHEMA_ALREADY_EXISTS_1=h2,r0;SCHEMA_CAN_NOT_BE_DROPPED_1=h2,r0;SCHEMA_MAIN=h24,r0;SCHEMA_NAME=12;SCHEMA_NAME_MUST_MATCH=h2,r0;SCHEMA_NOT_FOUND_1=h2,r0;SCHEMA_OWNER=h52,r12,0;SCHEMA_PG_CATALOG=h24,r0;SCHEMA_SEARCH_PATH=h1;schemaName=h1,54,r10,18,98,61,97,14,0,7;schemaNameString=6,27,39;SchemaObject=18,0;schemaPattern=4;schemaString=1;SCHEMATA=h12;Schwietzke=68;sciences=29;scope=h20,r4,22;SCOPE_CATALOG=4;SCOPE_GENERATED_KEYS=22;SCOPE_IDENTITY=22,7,59;SCOPE_SCHEMA=4;SCOPE_TABLE=4;scoped=1,11;score=27,39;scratch=68;script=t71,h11,1,10,r0,7,63,5,116,106,22,15,29,59,50,124,28,157,56,38,137,40,138;Scriptable=40;ScriptCommand=22;scriptCompressionEncryption=1;scriptDirectory=28,0;Scriptella=29;ScriptEngineManager=1;scripting=29;ScriptReader=22;scrollable=2,3,0,7;sE2rtPiUKtT=5;Seam=29;search=h11,27,39,r0,143,6,113,15,2,4,51,22,9,38,125,1,100,40,10,3,5;searchable=31,0,4;searchData=h27,39,r0,11;searched=h10,r6;searchedCase=10;searcher=39,0;searches=0,46,51,27,39,3,9,15;searching=24,29,0,2,11;SearchRow=113,0;searchString=6;second=h10,21,6,45,r36,0,5,7,16,49,41,15,12,33,14,17,1,2,42,44,24,11,3,136,19,57,18;SECOND_PRIMARY_KEY=h2,r0;Secondary=h20,r1;secondField=10;secondInt=10;secret=136,5;section=20,69,21,11,1,7;sector=17;secure=h5,r68,6,7,29,21,20,22,17,136;SECURE_RAND=h6,r5;secured=136;securing=h136,r5;security=h5,7,128,133,r11,18,130,136,21,22,43,146,56,33,124;seed=6,5;seedLong=6;seek=22;seemed=15;seemingly=22;seems=20,22,15,5;select=h10,1,61,52,r2,23,15,11,5,0,6,4,37,7,16,32,22,12,26,54,13,38,98,24,40,100,97,27,39,124;selected=23,37,1,10,11,5;selectExpression=1;selectFrom=11;selecting=52,0;selection=22;selectivity=h15,r1,24,10,12,0,32;SELECTIVITY_DEFAULT=h24,r0;SELECTIVITY_DISTINCT_COUNT=h24,r0;selectivityInt=10;selectOrder=1;self=2,0,15,40,5;SELF_REFERENCING_COL_NAME=4;sell=20;selling=20;semantic=29,5;SemanticWeb=29;semicolon=11,7;SemmleCode=29;send=22,7,29,28,40;sending=8,13,40;sense=15,62;sensitive=0,1,30,4,7,6,27,39,5,2,10,18,102,11,88,50,63,31,106,94,87,62,71,105,21,90,19,34;sensitivity=7,18;sent=25,0,7,5;sentence=5,69;sentinel=15;Sepang=15;separate=h15,r5,20,69,22,0,24,17,7,1,111,72,46,51,27,10;separated=h11,r6,27,10,5,0,4,7,39,12,30,116,28,94,34;separately=7,10,5;separating=2,0,23;separation=22;separator=30,0,6,23,10,4;separatorString=23,6;SEQ=11;SEQ1=10;SEQ2=1,10;SEQ_ID=1;SeQuaLite=29;sequence=h10,1,12,35,59,r2,0,14,26,6,7,22,4,60,84,66,16,48,11,52,67,19,13,5;SEQUENCE_ALREADY_EXISTS_1=h2,r0;SEQUENCE_ATTRIBUTES_INVALID_7=h2,r0;SEQUENCE_BELONGS_TO_A_TABLE_1=h2,r0;SEQUENCE_CATALOG=12;SEQUENCE_EXHAUSTED=h2,r0;SEQUENCE_NAME=12;SEQUENCE_NOT_FOUND_1=h2,r0;SEQUENCE_SCHEMA=12;sequenceName=1,10,6,2,0;sequenceOption=1,10;sequenceString=6;sequenceValueExpression=10;sequential=15,24,0,17;sequentially=19,15,17;serial=7,26,0,55;serialDataTypes=h26,r0;serializable=h55,r5,1,56,109,33,92,77,76,146,95,74,81,73,145,69,79,82,115,12,45,75,78,89,86,91,83,80,85,99;serialization=h5,r17,21,131,0,69,127;SERIALIZATION_FAILED_1=h2,r0;serialize=h131,r0,1,5;serialized=h69,r8,13,2,21,17,0,131,3,33,77,76,75,78,7,74,83,80,81,73,79,82,115;serializeJavaObject=5;serializer=19,25,2,120,14,18,0,21,1;SerializerClassName=5,1;serializerName=18;serif=100;serve=29,20;server0=5;server1=5;server2=5;SERVER_CACHED_OBJECTS=h28,r0;SERVER_PROPERTIES_DIR=h24,r0;SERVER_PROPERTIES_NAME=h24,r0;SERVER_RESULT_SET_FETCH_SIZE=h28,r0;serverCachedObjects=28,0,5;serverKey=43;serverList=94,5;serverListString=1;Servername=5;serverResultSetFetchSize=28,0,16,3;serverX=5;service=h5,r34,29,11,0,33,138,20,15,90;servicing=20;servlet=h11,r22,29;Sesar=29;session=t47,122,132,h14,25,1,12,11,93,16,r0,18,6,2,19,5,108,60,84,48,26,52,35,67,22,10,154,56,126,28;SESSION_CANCEL_STATEMENT=h25,r0;SESSION_CHECK_KEY=h25,r0;SESSION_CLOSE=h25,r0;SESSION_HAS_PENDING_TRANSACTION=h25,r0;SESSION_ID=h6,r12,5;SESSION_PREPARE=h25,r0;SESSION_PREPARE_READ_PARAMS2=h25,r0;SESSION_SET_AUTOCOMMIT=h25,r0;SESSION_SET_ID=h25,r0;SESSION_START=12;SESSION_STATE=h12,r7;SESSION_USER=6,22,5;sessionId=6;sessionInt=6;SessionInterface=93;SessionLocal=t14,99,147,144,h0,r18,154,56,126,108,60,84,48,135,52,35,67,47,120;SessionRemote=t25,r0,93,47,154,56,120,126,14,108;SET_CALLED=h95;set_log=93;SET_PROPERTY=5;setAdmin=h48,r0;setAllowBuiltinAliasOverride=h18,r0;setAllowLiterals=h14,18,r0;setArray=h13,r8,21,0;setAsciiStream=h8,13,46,r0;setAuthenticator=h18,r0;setAutoClose=h9,r0;setAutoCommit=h19,25,14,47,r0,1,5;setAutoCommitFromServer=h25,r0;setBackgroundException=h18,r0;setBaseDir=h43,28,r0;setBigDecimal=h8,13,r2,0;setBinaryStream=h8,13,51,70,r0,21,5;setBlob=h8,13,r0;setBoolean=h8,13,r0;setByte=h8,51,13,r0,21;setCacheSize=h18,r0;setCaseSensitiveColumnNames=h30,r0;setCatalog=h19,r0;setCharacterStream=h8,13,46,70,r0,141,3,21,5;setClauseList=1,10;setClientInfo=h19,r0,26;setClob=h8,13,r0;setCloseDelay=h18,r0;setCluster=h18,r0;setColumns=h27,r0,39;setComment=h35,r60,84,66,0,48,52,67;setCommentText=h84,r0;setCommitOrRollbackDisabled=h14,r0;setCompactMode=h18,r0;setCompareMode=h18,r0;setCurrentSchema=h14,r0;setCurrentSchemaName=h25,14,47,r0;setCursorName=h16,r8,13,0;setDate=h8,13,r0;setDefaultNullOrdering=h18,r0;setDefaultTableType=h18,r0;setDeleteFilesOnDisconnect=h18,r0;setDescription=h33,r0;setDouble=h8,13,r0;setErr=h62,r0;setEscapeCharacter=h30,r0;setEscapeProcessing=h16,r8,13,0;setEventListener=h18,r0;setEventListenerClass=h18,r0;setExclusiveSession=h18,r0;setFetchDirection=h16,3,9,r0,8,13;setFetchSize=h16,3,9,r0,8,13,1;setFieldDelimiter=h30,r0;setFieldSeparatorRead=h30,r0;setFieldSeparatorWrite=h30,r0;setFileEncryptionKey=h43,r0;setFilePasswordHash=h43,r0;setFloat=h8,13,r0;setGregorianChange=21;setHoldability=h19,r0;setIgnoreCase=h18,r0;setIgnoreCatalogs=h18,r0;setIgnoreList=h27,r0,39;setIn=h62,r0;setInitialPowerOffCount=h18,r0;setInReader=h62,r0;setInt=h8,13,r0;setIntValue=h60,r0;setIsolationLevel=h25,14,47,r0;setJavaObjectSerializerName=h18,r0;setLargeMaxRows=h16,r8,13,0;setLastIdentity=h14,r0;setLazyQueryExecution=h14,r0;setLineCommentCharacter=h30,r0;setLineSeparator=h30,r0;setLockMode=h18,r0;setLockTimeout=h14,r0;setLoginTimeout=h49,33,r0;setLogWriter=h49,33,r0;setLong=h8,13,r0;setMasterUser=h18,r0;setMaxConnections=h49,r0;setMaxFieldSize=h16,r8,13,0;setMaxLengthInplaceLob=h18,r0;setMaxMemoryRows=h18,r0;setMaxOperationMemory=h18,r0;setMaxQueryEntries=h121,r0;setMaxRows=h16,r8,13,0,3,5;setMode=h18,r0;setModified=h35,r60,84,66,0,48,52,67;setNCharacterStream=h8,13,r0;setNClob=h8,13,r0;setNetworkConnectionInfo=h25,14,43,47,r0;setNetworkTimeout=h19,r0;setNextException=74,83,80,81,73,77,79,76,82,75,78,115;setNonKeywords=h14,r0;setNString=h8,13,r0;setNull=h8,13,r0;setNullString=h30,r0;setObject=h8,13,r0,21,15;setObjectName=h35,r60,84,66,0,48,52,67;setOldInformationSchema=h14,r0;setOptimizeReuseResults=h18,r0;setOptions=h30,r0;setOriginalURL=h43,r0;setOut=106,102,94,90,88,34,87,50,62,64,63,71,105;setParsingCreateView=h14,r0;setPassword=h33,r0;setPasswordChars=h33,r0;setPoolable=h16,r8,13,0;setPowerOffCount=h18,r0;setPreparedTransaction=h14,r0;setPreserveWhitespace=h30,r0;setProgress=h58,18,r0;setProperty=h43,r5,19,0,28;setQueryStatistics=h18,r0;setQueryStatisticsMaxEntries=h18,r0;setQueryTimeout=h16,14,r0,8,13,2;setQuirksMode=h14,r0;setReadOnly=h19,18,r0;setRef=h13,r8,0;setReferentialIntegrity=h18,r0;setResult=h70,r0;setRetentionTime=h18,r0;setRightMask=h52,r0;setRowFactory=h18,r0;setRowId=h8,13,r0;setSaltAndHash=h48,r0;setSavepoint=h19,14,r0,2;setSchema=h19,r0;setSchemaSearchPath=h14,r0;setServerKey=h43,r0;setShort=h8,13,r0;setShutdownHandler=h34,r0;setSQL=h77,76,75,78,74,83,80,81,73,79,82,117,r0;setSQLXML=h8,13,r0;setStackTrace=74,83,80,81,73,77,79,76,82,75,78,115;setString=h46,8,13,70,r0,21,5;setStringValue=h60,r0;setTemporary=h35,r60,84,66,0,48,52,67;setter=95;setThreadLocalSession=h14,47,r0,25;setThrottle=h14,r0;setTime=h8,13,r0;setTimestamp=h8,13,r0;setTimeZone=h14,r0;SETTING_NAME=12,7,5;SETTING_VALUE=12,5;settingName=60;SettingsBase=t96,h32,r0,154,56,126,108;settlement=20;setTrace=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;setTransactionIsolation=h19,r0;setTransactionTimeout=h44,r0;setTruncateLargeLength=h14,r0;setTypeMap=h19,r0;setUnicodeStream=h13,r8,0,141;Setup=5;setUrl=h33,8,13,r0;setUser=h33,r0;setUserName=h43,r0;setUserPasswordHash=h43,48,r0;setVariable=h14,r0;setVariableBinary=h14,r0;setWaitForLock=h14,r0;setWhitespaceChars=h27,r0,39;setWriteColumnHeader=h30,r0;setWriteDelay=h18,r0;several=29,5;SHA=6,5,22,7,17;SHA1=153;SHA256=11;SHA3=6;shadow=100;shall=20;Shao=68;ShapeLogic=29;sharding=17;share=20,68,1,5;SHARE_LINKED_CONNECTIONS=32,0;shared=5,17,29,93,32,28,11,38;shareLinkedConnections=h32,r0,5;sharing=29;Sheet=h100,r22;shell=t62,h11,r0,29,22,137,116,157,40,56,138;Shellbook=29;shift=6;short=4,0,9,3,8,21,17,32,40,13,28,11,15,5;shorter=6,21;shouldn=22;show=h1,r11,15,5,69,17,22,63;shown=11,124;showResults=63;showUsage=106,102,94,90,88,34,87,50,62,64,63,71,105;showUsageAndThrowUnsupportedOption=106,102,94,90,88,34,87,50,62,64,63,71,105;shrinking=11;shut=22;shutdown=h1,34,64,90,r0,7,32,22,2,11;shutdownHandler=34,90,137,64,56,0;shutdownImmediately=h18,r0;shutdownTcpServer=h34,r0,11;shutting=34,11;side=h10,r8,7,13,5,11,89,21,25,1,3,14,29,22,0,28,23,124,108;sign=h6,r36,10;signal=h6,r7,5;signature=33;signed=6,21,0,31,53,11,5;significance=15;significant=6,40;significantly=15;Signsoft=29;Sigurdsson=68;silently=32,27,39,58,7;similar=h17,r15,6,0,2,26,29,30,22,11,7,1,5;similarity=29;Simon=29,5;simple=h10,r15,29,1,7,5,0,11,116,17,9,40,32,20,22,65,49,142,59,68;SIMPLE_MEDIAN=1;SimpleArray=t65,r0,137,116,157,9,56;simpleCase=10;SimpleORM=29;simpler=17,7;simpleResultSet=t9,65,h0,7,r141,11,137,116,27,157,56,129;SimpleRowSource=t129,r30,0,137,9,56,116,157;simplest=11,7;simplicity=29,93,5;simplified=24,0,15;simplifies=7;simplify=40;simply=93,59,40,7;simulate=5,7;simulated=25,18,22,1;simulation=29;sin=h6,r2;since=0,24,10,3,141,11,104,2,13,32,22,17,6,101,103,136,59,62,18;sine=6;single=10,15,1,7,2,14,21,32,0,6,26,5,12,22,16,17,8,13;singleton=57,0,135;sinh=h6;site=40;situation=7,23,5,38,15;six=69;size=h7,38,15,r17,5,1,0,21,24,28,72,10,2,4,32,6,11,31,12,22,16,100,93,50,25,3,18;sizeInt=1;skeleton=22;skill=20;skipped=37;skipping=17;SkyCash=68;slashes=7;sleep=h99,r11,5,12;SLEEP_SINCE=12,22;sleeping=12;SLF4J=7,1;slight=15;slightly=15,17;slope=23;slow=h38,r15,5,24,28,6,0,68,1,11,7;SLOW_QUERY_LIMIT_MS=h24,r0;slowed=14,0;slower=15,17,7,5;slowest=15;slowing=1;slowly=15;small=15,17,29,22,7,11,38,10,3,5,6,90,136,1;SMALLDATETIME=21;smaller=1,0,14,24,6,4,26,7,22,16,17,23,15,5;smallest=6,23;SMALLINT=h21,41,r6,15,0,23,7,1;smallintType=10;SmallLRUCache=50,25,18;SMALLMONEY=7;SmartFoxServer=29;smells=22;snake=2;snapshot=h40,55,r14,0,24,17,5,19,12,22,11,7;snippet=11;SO_TIMEOUT=22;Social=29;socket=h5,r7,28,2;SOCKET_CONNECT_RETRY=h28,r0;SOCKET_CONNECT_TIMEOUT=h28,r0;socketConnectRetry=28,0;socketConnectTimeout=28,0;Soerensen=68;soft=22,15,7,38;SOFT_=7;SOFT_LRU=7,38;software=h40,138,r20,29,5,68,50,124,38;sole=20;solely=20;solid=100;Solo=29;solution=29,11,2,5,32,20,15,28,7;solve=2,1,38;somebody=3,0,15;someone=136;something=2,0,16,8,13,124,7;sometimes=7,15,28,5,66,0,11,38,108;somewhat=17;Sonatype=22;soon=34,20,11,14;sophisticated=7;sormula=29;sort=h10,r1,5,113,22,0,15;sorted=h15,r4,0,1,17,5;sorting=h5,r1,15,29,22,7;sortNullsHigh=22;sortSpecification=23,10;sortSpecificationList=23,10,1;sound=6;Soundex=h6;source=h20,153,7,40,38,r29,1,5,9,22,138,0,11,88,15,33,129,68,70,102,28,49,142,4,69,12,106,116,94,100;SOURCE_DATA_TYPE=4;SOURCE_TABLE=1;sourceClass=70;sourceCode=7;sourceCodeString=1;SourceCompiler=18;sourceforge=40;sourceSchemaString=6;sourceSet=1;space=h6,r89,7,17,1,10,2,15,21,32,11,22,0,23,26,40,5,108;spades=21;SPARQL=29;spatial=h5,r12,1,29,21,22,17,7,6;SpatialKey=17;spawn=40;special=h11,15,r6,7,21,20,17,5,10,22,93,62,63;SPECIAL_SIN=2;specialized=7;specially=15,34,60,1,35,5;specific=0,4,3,2,7,41,117,22,6,11,9,10,20,59,15,5;SPECIFIC_CATALOG=12;SPECIFIC_NAME=12,4,6;SPECIFIC_SCHEMA=12;specification=h10,r38,29,21,22,40,2,11;specifies=10,1,21,0,16,6,4,7;specify=7,10,107,59,23,6;specifying=21,7,5;specs=19,2,0;speed=15,17,10,7;spellcheck=40;spelling=22,40;spends=5;spent=68;spi=112,145,56;split=h5,r22,17,7,15,28,1,40;SPLIT_FILE_SIZE_SHIFT=h28,r0;spliterator=74,83,80,81,73,77,79,76,82,75,78,115;splitFileSizeShift=28,0;splitting=17;sponsored=15;Sporadic=22;spots=15;spread=15,68;Spring=h11,r29;Springfuse=29;SQL89=93;SQL_DATA_TYPE=4;SQL_DATETIME_SUB=4;SQL_PATH=12;SQL_STATEMENT=12;SQL_TSI_DAY=10;SQL_TSI_HOUR=10;SQL_TSI_MINUTE=10;SQL_TSI_MONTH=10;SQL_TSI_SECOND=10;SQL_TSI_WEEK=10;SQL_TSI_YEAR=10;SQLClientInfoException=19;SQLDataException=79,0,109,56;SQLFeatureNotSupportedException=75,0,109,56;sqlFlags=60,35;SQLIntegrityConstraintViolationException=80,0,109,56;SQLInvalidAuthorizationSpecException=74,0,109,56;SQLite=h15,r17,22;SQLNonTransientConnectionException=76,0,109,56;SQLNonTransientException=82,74,80,0,109,79,76,75,78,56;SQLOrm=29;SQLServer=26,0;SQLState=6;sqlStatement=h104,r121,0;sqlStateSQL=4;sqlStateSQL99=4;sqlStateString=6;sqlStateXOpen=4;sqlString=1;SQLSyntaxErrorException=78,0,109,56;SQLTimeoutException=73,0,109,56;SQLTransactionRollbackException=77,0,109,56;SQLTransientException=81,73,77,0,109,56;sqlType=8,13,0,3,9,41,22,146,56;sqlTypeName=9;SQLWarning=19,16,3,9;SQLXML=h70,r0,8,3,9,19,13,16,109,44,4,110,56,114,46,51,42,118,33,31,53,119;sqrt=h6,r2,1;square=23,26,0,7;squareBracketQuotedNames=h26,r0;SQuirreL=29;src=40,138,5,11,7,15;SRID=12,21,10,22;sridInt=21;SSD=17,15;ssl=7,5,34,136;SSLServerSocket=5;SSLSocket=5;stack=2,77,76,75,78,74,83,80,81,73,79,82,40,29,0,11,38,117,15,5,138;stackable=5;StackOverflowError=22;stackTrace=74,83,80,81,73,77,79,76,82,75,78;stage=17;stand=17,116,0,68,94,5;standalone=11,0,2,29,93,49,142,9;standard=h5,r22,7,62,23,0,11,6,26,59,10,29,21,12,37,19,1,17,9,69,15;standardized=5,17;standby=5;staring=10,68;start=h11,34,5,44,40,124,r10,0,12,90,51,6,2,7,46,17,1,38,42,15,22,27,58,14,116,100,30,43;START_URL=h24,r0;START_VALUE=12;startCollecting=15;started=11,5,7,90,0,34,12,2,6,38,15,64,68,40,10,14;starting=h11,5,r42,0,130,34,46,51,32,28,6,2,4,7,37,69,57,14,18;startInt=6;startStatementWithinTransaction=h14,r0;startup=h15,r11,7;startWebServer=h34,r0;stat=2,16,3;STATE_BACKUP_FILE=h58,r0;STATE_COMMAND=12;STATE_CREATE_INDEX=h58,r0;STATE_KEY=12;STATE_RECONNECTED=h58,r0;STATE_RECOVER=h58,r0;STATE_SCAN_FILE=h58,r0;STATE_STATEMENT_END=h58,r0;STATE_STATEMENT_PROGRESS=h58,r0;STATE_STATEMENT_START=h58,r0;stated=20;STATEMENT_TIMEOUT=22;STATEMENT_WAS_CANCELED=h2,r0;StatementEventListener=44,0;statically=6;StaticRolesMapper=7;StaticSettings=t122,r0,19,25,47,14,154,56,126,108;statistical=29;statistics=h15,r0,121,1,104,12,24,108,106;status=34,12,0,25,1;STATUS_CLOSED=h25,r0;STATUS_ERROR=h25,r0;STATUS_OK=h25,r0;STATUS_OK_STATE_CHANGED=h25,r0;statute=h20;statutory=20;stay=20,17,1,6;STD_DEV_EXECUTION_TIME=12;STD_DEV_ROW_COUNT=12;STDDEV=23;STDDEV_POP=h23;STDDEV_SAMP=h23;STDOUT=22;StelsCSV=29;StelsXML=29;step=h124,r12,0,16,3,2,93,5,22,25,11,47,38,15,1,10,40,14;STEP_SIZE_MUST_NOT_BE_ZERO=h2,r0;Steve=68;Steven=68;steward=20;still=5,0,11,7,1,118,24,2,142,38,19,150,149,123,16,17,93,59,15,151,40,148,18;StockMarketEye=68;stolen=17,5;stop=h11,34,5,40,r0,18,32,64,90,7;stopCollecting=15;stoppage=20;stopped=11,5,34,2,7;stopping=h11,r5;storage=h17,93,136,r5,32,15,25,18,1,50,22,40;STORAGE_TYPE=12;store=h17,r18,25,50,5,21,24,11,15,22,0,56,93,4,47,126,14,7,137;STORE_DOCUMENT_TEXT_IN_INDEX=h39,r0;stored=h15,7,38,136,r17,5,11,0,1,21,28,2,39,24,4,102,43,6,127,93,131,27,10,18;storesLowerCaseIdentifiers=h4,r0;storesLowerCaseQuotedIdentifiers=h4,r0;storesMixedCaseIdentifiers=h4,r0;storesMixedCaseQuotedIdentifiers=h4,r0;storesUpperCaseIdentifiers=h4,r0;storesUpperCaseQuotedIdentifiers=h4,r0;storeVersion=17;storing=h5,r17,21,32,24,7;story=29;StorYBook=29;str=46,1;straight=40;straightforward=93;strategy=15;stream=8,13,0,3,51,71,46,62,72,5,70,102,6,95,88,50,1,106,94,34,87,105,29,28,49;StreamCruncher=29;streamed=21;streaming=17,7;strength=1;stress=7,38;Strict=h7,91,r1,5,20,22;STRING_FORMAT_ERROR_1=h2,r0;StringBuilder=60,45,35,0,22,1;STRINGDECODE=h6,r2,10,22,0;STRINGENCODE=h6;StringIndexOutOfBoundsException=22;STRINGTOUTF8=h6,r2;strong=5,7;Strongly=22;Struct=19,0;structural=15;structure=h138,r29,93,69,6,136;structured=h17;Stuck=29;Studio=29,22;STUFF=22;style=26,0,14,25,47,7,59,100,40,2,11;stylesheet=6;St\u00e9phane=68;sub=108,22,0;subclass=21;subclasses=22,66,96,27,16,69,13,90,35,47,119;subclause=23;subcondition=10;Subinterfaces=69;subject=20,128,133,7;sublicense=20;Submit=40;Submitting=h40;subqueries=4,0,24,1,7,32,22,5;subquery=10,2,22,0,6,23;SUBQUERY_IS_NOT_SINGLE_COLUMN=h2,r0;subsequent=h20,r22,17,5;subsequently=20;subset=42,21,7,5;substance=20;substituted=1;SUBSTR=6;substring=h6,r46,0,7,5;substructure=29;subsystem=17;subtle=59;subtract=6;subtraction=10,22;succeed=31,0,7;success=18,111;SUCCESS_NO_INFO=16,13,8;successful=24,28,5,34,0,15,3,9,38;successfully=4,5,22,0,11;such=20,5,7,10,2,0,17,11,21,38,68,1,40,127,58,32,16,72,35,26,59,13,63,108;sufficient=20,7;sufficiently=20;suffix=24,0,15,4,11;SUFFIX_LOCK_FILE=h24,r0;SUFFIX_MV_FILE=h24,r0;SUFFIX_MV_STORE_NEW_FILE=h24,r0;SUFFIX_MV_STORE_TEMP_FILE=h24,r0;SUFFIX_OLD_DATABASE_FILE=h24,r0;SUFFIX_TEMP_FILE=h24,r0;SUFFIX_TRACE_FILE=h24,r0;suggest=15,38;suggested=0,16,3;suitable=20;suite=29,28,40;sum=h23,r37,17,1,6,10,2;SUM_OR_AVG_ON_WRONG_DATATYPE_1=h2,r0;summand=h10;sun=11,28,1,40,7;Sunday=7;super=4,0;super_tables=11;super_types=11;Superinterfaces=128,133,58;superseded=3;SUPERTABLE_NAME=4;supplied=6,87,72,5;supply=5;support=h7,5,11,17,38,r22,4,0,26,15,10,29,20,68,16,1,6,142,138,116,101,44,90,59,112,64,42,57;supportedClientInfoPropertiesRegEx=h26,r0;Supporters=h68;supporting=29,17;supportPoundSymbolForColumnNames=h26,r0;supportsAlterTableWithAddColumn=h4,r0;supportsAlterTableWithDropColumn=h4,r0;supportsANSI92EntryLevelSQL=h4,r0;supportsANSI92FullSQL=h4,r0;supportsANSI92IntermediateSQL=h4,r0;supportsBatchUpdates=h4,r0;supportsCatalogsInDataManipulation=h4,r0;supportsCatalogsInIndexDefinitions=h4,r0;supportsCatalogsInPrivilegeDefinitions=h4,r0;supportsCatalogsInProcedureCalls=h4,r0;supportsCatalogsInTableDefinitions=h4,r0;supportsColumnAliasing=h4,r0;supportsConvert=h4,r0;supportsCoreSQLGrammar=h4,r0;supportsCorrelatedSubqueries=h4,r0;supportsDataDefinitionAndDataManipulationTransactions=h4,r0;supportsDataManipulationTransactionsOnly=h4,r0;supportsDifferentTableCorrelationNames=h4,r0;supportsExpressionsInOrderBy=h4,r0;supportsExtendedSQLGrammar=h4,r0;supportsFullOuterJoins=h4,r0;supportsGetGeneratedKeys=h4,r0;supportsGroupBy=h4,r0;supportsGroupByBeyondSelect=h4,r0;supportsGroupByUnrelated=h4,r0;supportsIntegrityEnhancementFacility=h4,r0;supportsLikeEscapeClause=h4,r0;supportsLimitedOuterJoins=h4,r0;supportsMinimumSQLGrammar=h4,r0;supportsMixedCaseIdentifiers=h4,r0;supportsMixedCaseQuotedIdentifiers=h4,r0;supportsMultipleOpenResults=h4,r0;supportsMultipleResultSets=h4,r0;supportsMultipleTransactions=h4,r0;supportsNamedParameters=h4,r0;supportsNonNullableColumns=h4,r0;supportsOpenCursorsAcrossCommit=h4,r0;supportsOpenCursorsAcrossRollback=h4,r0;supportsOpenStatementsAcrossCommit=h4,r0;supportsOpenStatementsAcrossRollback=h4,r0;supportsOrderByUnrelated=h4,r0;supportsOuterJoins=h4,r0;supportsPositionedDelete=h4,r0;supportsPositionedUpdate=h4,r0;supportsRefCursors=4;supportsResultSetConcurrency=h4,r0;supportsResultSetHoldability=h4,r0;supportsResultSetType=h4,r0;supportsSavepoints=h4,r0;supportsSchemasInDataManipulation=h4,r0;supportsSchemasInIndexDefinitions=h4,r0;supportsSchemasInPrivilegeDefinitions=h4,r0;supportsSchemasInProcedureCalls=h4,r0;supportsSchemasInTableDefinitions=h4,r0;supportsSelectForUpdate=h4,r0;supportsStatementPooling=h4,r0;supportsStoredFunctionsUsingCallSyntax=h4,r0;supportsStoredProcedures=h4,r0;supportsSubqueriesInComparisons=h4,r0;supportsSubqueriesInExists=h4,r0;supportsSubqueriesInIns=h4,r0;supportsSubqueriesInQuantifieds=h4,r0;supportsTableCorrelationNames=h4,r0;supportsTransactionIsolationLevel=h4,r0;supportsTransactions=h4,r0;supportsUnion=h4,r0;supportsUnionAll=h4,r0;supposed=5;sure=5,0,24,40,35,11;surrounded=6,5;survive=20,5;SUSE=29;SUSPENDED=h99;Suspicious=22;swap=26,0,5;swapConvertFunctionParameters=h26,r0;swapLogFunctionParameters=h26,r0;swapped=7,5;Sweden=68;swing=5,68;Swiss=29;switch=0,43,1,2,18,37,21,22,23,6,10,5;switchBnf=37,21,23,1,6,10;switched=19,5,15,1,6,4;switches=1,19,0,15,5;Switching=15,1;Switzerland=68;SWT=29;symbols=6;Symmetric=10,22,5;SymmetricDS=29;sync=h1,18,r5,17,22,0,15,38;sync_binlog=15;synchronization=25,18,29,50;synchronize=18,29,22,0,7,5;synchronized=16,8,13,19,17,40,5;synchronizing=7;synchronous=15,5;synchronously=5;synonym=h12,35,r6,0,18,60,84,66,48,52,67;SYNONYM_CATALOG=12;SYNONYM_FOR=12;SYNONYM_FOR_SCHEMA=12;SYNONYM_NAME=12;SYNONYM_SCHEMA=12;syntax=h6,10,1,11,r23,21,37,22,0,16,2,4,59,63,12,5;SYNTAX_ERROR_1=h2,r0;SYNTAX_ERROR_2=h2,r0;synthetic=24,0;SYS_PROP=2;SYSDATE=7,22;sysDummy1=h26,r0,7;SYSIBM=26,7,0;sysIdentifier=h18,r0;SysProperties=t28,r0,2,154,56,5,126,108;SYSTEM_RANGE=12,15,22,6,10,11;SYSTEM_USER=6,22,5;systemColumns=h26,r0;systemUser=48;SYSTIMESTAMP=7;syswow64=5'; +ref['t']='T461=22;TABLE1=2;TABLE_CAT=4;TABLE_CATALOG=12,4;TABLE_CLASS=12;TABLE_CONSTRAINTS=h12,r1;TABLE_DISTINCT=6;table_name=12,4,26,0,1,6,2;TABLE_OR_VIEW=h35,r60,84,66,0,48,52,67;TABLE_OR_VIEW_ALREADY_EXISTS_1=h2,r0;TABLE_OR_VIEW_NOT_FOUND_1=h2,r0;TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1=h2,r0;TABLE_OR_VIEW_NOT_FOUND_WITH_CANDIDATES_2=h2,r0;table_privileges=h12,r11;TABLE_SCHEM=4;TABLE_SCHEMA=12,1,6;TABLE_TYPE=4,12,11;tableAlias=10;TableBase=7;tableConstraintDefinition=1;tableEngine=t140,r18,7,0,1,146,158,56,127;tableEngineName=1;tableEngineParamName=1;tableEngineParams=1,7;tableExpression=1,10;TableFilter=22;tableIndexClustered=4;tableIndexHash=4;tableIndexHashed=4;tableIndexOther=4;tableIndexStatistic=4;TableLink=22;TableLinkConnection=18;tableName=h54,r1,98,61,10,97,0,7;tableNamePattern=4;tableNameString=6,27,39;tableScan=15;TableSynonym=18;tableValue=10;TableView=22;tag=11,7,12,43,1;tagtraum=68;tailored=29;Takanori=68;takeGeneratedSequenceValue=h26,r0;takeInsertedIdentity=h26,r0;taken=15,1;tan=h6;tangent=6;tanh=h6;Tanuki=5;target=1,11,71,32,22,102,25,2,88,50,40,5,18,0,6,94,4,130,38,10;TARGET_TABLE=1;targetAlias=1;targeted=29;targetSchemaString=6;targetSqlType=8,3,13;targetTableName=1;Task=2,110,5;taxon=29;tcp=h11,40,r0,7,34,24,5,2,90,25,100,116,12,22,28,136,19,15;TCP_PROTOCOL_VERSION_17=h24,r0;TCP_PROTOCOL_VERSION_18=h24,r0;TCP_PROTOCOL_VERSION_19=h24,r0;TCP_PROTOCOL_VERSION_20=h24,r0;TCP_PROTOCOL_VERSION_MAX_SUPPORTED=h24,r0;TCP_PROTOCOL_VERSION_MIN_SUPPORTED=h24,r0;tcpAllowOthers=34,2,11,5;tcpDaemon=34;tcpPassword=34,11;tcpPort=34,5,11;tcpServer=11;tcpShutdown=34,11;tcpShutdownForce=34;tcpSSL=34;team=59;technical=5;technologies=29;tell=35,0,11;telling=20;temp=2,28,25,1,18,7,50,0,6,5;tempFile=7;TempFileDeleter=50,25,18;template=40,15,7;temporarily=17;temporary=14,0,1,35,7,12,24,32,66,28,25,47,5,18,6,11,128,59;ten=10;term=h20,10,r4,0,1,17,5;terminate=20,90,7,5;termination=h20,r5;Terrence=68;TERTIARY=1;test=h15,5,2,10,r1,7,11,4,37,6,0,38,40,100,26,23,124,106,22,17,49,27,39,138,21,32,58,33,59;TEST1=10;test2=6,1,10;TEST_A=2;TEST_B=2;TEST_DATA=15;TEST_ID=2;TEST_ID_PARENT=2;test_mem=7;TEST_ROLE=2;TEST_ROLE_2=2;TEST_SCHEMA=2,1,7;TEST_SEQ=6,2;TEST_USER=2;TEST_VIEW=1,2;TestAll=40;testCI=40;TestClearReferences=22;TestCrashAPI=22;TestCsv=11;TestDateTimeUtils=22;tested=h15,r38,1,5,138,32,59,28,10,7;TestFileSystem=22;testing=h11,r1,15,24,5,29,0,68,40,2,7;TestMultiDimension=7;TestOutOfMemory=22;TestScript=22;TestTableEngines=7;TestTriggersConstraints=22;testValue=6;TestWrite=5;text=27,0,39,21,5,7,6,11,143,2,1,10,100,30,24,84,50,29,20,26,46,22,17,125,15;textarea=100;textual=21,5;Thailand=68;thanks=68,29;THE_GEOM=5;Theis=68;themselves=12;thenComparing=57;thenComparingDouble=57;thenComparingInt=57;thenComparingLong=57;thenValue=6;Theoretically=93;theory=20,5;therefore=5,7,11,20,15,42,1,6,40,58;thereof=20;thin=11;thing=93,5,136;third=20,7,22,21,11,5;ThirdHalf=68;Thomas=93,68,1;Thompson=68;those=5,20,17,11,7,38,15,1,32,6,101,103,93,68;though=17;thousand=5,29,10;thread=5,14,7,47,0,16,28,34,8,3,13,19,15,22,17,11,46,51;THREAD_DEADLOCK_DETECTOR=h28,r0;threadDeadlockDetector=28,0;threaded=15,11,7;three=7,11,29,21,69,15;threshold=5;throttle=h1,14,r0,24;THROTTLE_DELAY=h24,r0;throttled=h99,r14,0;throttling=24,0,1;through=7,5,20,156;throughput=17;Throwable=h77,76,75,78,74,83,80,81,73,79,82,115,r0,18,109,56;throwException=h27,r0,39;throwing=h7,r26,0;thrown=h2,0,r7,48,1,28,66,17,49,19,5,14,18;throwUnsupportedOption=106,102,94,90,88,34,87,50,62,64,63,71,105;thus=29;TIES=1,10,2,0,7;tilde=2,33;TIME_WITH_TIME_ZONE=h41,r0;timed=2,0;timely=20;timeout=h7,5,r0,33,16,49,1,2,44,19,32,14,24,28,147,6,108;TimeoutValue=t147,r0,154,56,126,14,108;timestamp=h21,10,41,r6,0,8,3,9,2,11,13,12,25,7,120,19,14,18,26,1,22,15;TIMESTAMP_WITH_TIME_ZONE=h41,r0;TIMESTAMPADD=6;TIMESTAMPDIFF=6;timestampString=6;timestampType=10;timestampWithTimeZone=6,10;timestampWithTimeZoneType=10;timeType=10;timeWithTimeZone=10;timeWithTimeZoneType=10;TimeWriter=29,68;timezone=h10,132,r38,14,22,0;TIMEZONE_HOUR=10,6;TIMEZONE_MINUTE=10,6;TIMEZONE_SECOND=10,6;timezoneHourField=10;timezoneMinuteField=10;timeZoneNameString=10;timeZoneOffsetString=10;TimeZoneProvider=132,14,19,0,43,25,120,18;timezoneSecondField=10;timeZoneString=6;timing=11,38;TINYBLOB=21;TINYINT=h21,41,r6,15,0,23,59,1,2;tinyintType=10;TINYTEXT=21;title=20;TLS=h5,r7,11,28;TMENDRSCAN=44;TMFAIL=44;TMJOIN=44;TMNOFLAGS=44;TMONEPHASE=44;tmp=59,6;TMRESUME=44;TMSTARTRSCAN=44;TMSUCCESS=44;TMSUSPEND=44;TO_BINARY=2;TO_CHAR=h6,r2,22,0;TO_DATE=2,22,0;TO_TIMESTAMP=2,22,0;toBinaryString=2;toCharArray=17;TODAY=7;toDegrees=6;Todescato=68;TODO=136;together=2,1,90,22,0,5,34,129,7;Tokenize=22;tokens=22,1,5;tolerant=29;TOM=1;Tomcat=11,38,22;too=2,21,0,16,3,14,92,7,12,23,6,15,68,1,13,5;TOO_MANY_COLUMNS_1=h2,r0;took=15,121;toolbar=11;Toolkit=29;ToolProvider=28,7;Toolset=29;top=h93,r7,26,11,0,100,5,17,1,38;Topic=34,7;topInDML=h26,r0;topInSelect=h26,r0;toplink=h11,100;toRadians=6;tort=20;total=15,104,12,11,0,17,23,1,6,10,7;toType=4;toward=29;tpc=15;TRACE_CONNECTION_NOT_CLOSED=h2,r0;TRACE_FILE_ERROR_2=h2,r0;TRACE_IO=h28,r0;TRACE_LEVEL=h1;TRACE_LEVEL_FILE=7,15,1,22,100;TRACE_LEVEL_SYSTEM_OUT=1,7;TRACE_MAX_FILE_SIZE=h1,r7;TRACE_SQL_FLAGS=60,84,66,48,52,35,67;traceError=34;traceFile=106,15,7;traceIO=28,0;TraceLevel=11;traceModuleId=35,66;TraceObject=h16,44,4,110,114,19,42,3,33,31,53,119,46,51,8,13,70,r109,56,145;traceOperation=h25,r0;TraceSystem=112,18;tracing=7,29;tracker=22,40;tracking=40,29;trademark=20;traditional=17;trailing=6,5;TransactDatabaseLocker=11;TRANSACTION_=4;TRANSACTION_ID=h6;transaction_isolation=11;Transaction_log=93;TRANSACTION_NAME=12;TRANSACTION_NONE=19;TRANSACTION_NOT_FOUND_1=h2,r0;TRANSACTION_READ_COMMITTED=19,4;TRANSACTION_READ_UNCOMMITTED=19;TRANSACTION_REPEATABLE_READ=19;TRANSACTION_SERIALIZABLE=19;TRANSACTION_SNAPSHOT=h24,r0;TRANSACTION_STATE=12;transactional=1,32,22;transactionally=1,11;transactionLog=50;TransactionMap=22;transactionName=1,14,5;TransactionStore=14,22,17,56,126;transfer=25,20,0,29,22;transferred=7,5;transform=70,29,1;transition=6,21,1;transitioned=22;translate=h6,38,r19,40,0,7;translated=40,19,68,2,7;Translating=h40,r38;translation=22,40;transmission=5;transmitted=5,7;transparent=29;Transport=5;Travis=22;tray=11,90;treat=93;treated=7,10,26,0,6,5;treatEmptyStringsAsNull=h26,r0;treatment=29;Trede=68;TreeMap=17;trial=20;trick=5;tricky=136;tried=20;tries=5,2,34,0,15,7,28,1;TRIG_INS=1;TRIG_JS=1;TRIG_RUBY=1;TRIG_SRC=1;trigger=t61,h1,54,97,7,98,12,35,r0,2,6,21,5,22,56,127,27,143,152,10,60,32,84,66,48,146,39,14,116,52;TRIGGER_A=2;TRIGGER_ALREADY_EXISTS_1=h2,r0;TRIGGER_CATALOG=12;TRIGGER_NAME=12,5;TRIGGER_NOT_FOUND_1=h2,r0;TRIGGER_SCHEMA=12;TRIGGER_XYZ=2;TriggerAdapter=t54,r0,7,22,137,116,61,157,56;TriggerClassName=5;triggered=54,0,61;triggeredClassNameString=1;triggerName=h54,r1,98,61,97,0,7;TriggerSample=7,2;triggerUpdatable=3;trigonometric=6;trigraphs=22;trim=h6,r26,0;TRIM_ARRAY=h6,r22;trimmed=89,7;TRUNC=h6,r22;truncate=h1,46,51,r6,0,23,2,7,26,14;TRUNCATE_LARGE_LENGTH=h1;TRUNCATE_VALUE=h6;truncated=6,2,23,1,7,21,14,0;truncateLargeLength=14;truncateTableRestartIdentity=h26,r0;Trust=5;trusted=11,5;try=5,11,19,32,16,8,15,1,40,13,7,38;trying=h2,0,r5,15,17,1,11,7;tsv=6;Tune=29;Tuning=h15,r38;turned=17;turns=1,40;Tutorial=t11,r124,156;tweak=5;twice=2,0,15,17,7,6,11;txt=20,6,40,11;typdelim=22;TYPE_=19;TYPE_CAT=4;TYPE_FORWARD_ONLY=9,3;type_info=22,11;TYPE_NAME=4,12;TYPE_SCHEM=4;TYPE_SCROLL_INSENSITIVE=9,3;TYPE_SCROLL_SENSITIVE=4,3,9;typeByNameMap=h26,r0;typed=2,0,59,22;typeIdentifierString=6;TypeInfo=22;typelem=22;typeName=8,19,13;typeNamePattern=4;typeNoNulls=4;typeNullable=4;typeNullableUnknown=4;typePredBasic=4;typePredChar=4;typePredicateRightHandSide=10;typePredNone=4;TYPES_ARE_NOT_COMPARABLE_2=h2,r0;typesafe=29;typeSearchable=4;typical=17;typically=17,29,93,7;typing=11;typos=5'; +ref['u']='UCASE=6;uDig=29;udts=11;UESCAPE=10,5;uid=2,7;UID_INDEX_0=2;ULSHIFT=h6,r22;umlaut=1;unable=22,11;unaligned=17;unavailability=20;unavailable=6;UNBOUNDED=10,37;unchanged=7;unchecked=17;uncomment=11;uncommitted=5,1,12,19,0,6,58,14,11,7;uncommon=17;UNCOMPARABLE_REFERENCED_COLUMN_2=h2,r0;uncompressed=72,5;unconditionally=37,123,16,6,5;undefined=1,2,0;under=20,11,29,22,40,5,38;underline=100;underlying=1,5,29;underscore=11;understand=5,29,20,50,15,58;undetected=5;undo=h93,r17,14,59,0,1;UNDO_LOG=22;undocumented=59,6;undone=54,98,61,97;unencrypted=5;unenforceable=20;unexpected=22,2,0,40,5;unexpectedly=22;Unfortunately=15,5,59;unicode=h5,r10,12,21,22,68,6;uninstall=h5;uninterruptible=5;union=1,5,4,15,22,0,32,2,7;unique=h5,r10,0,2,1,12,4,7,85,22,6,26,15,23,35,18,24,43,17,3,21,108;UNIQUE_CONSTRAINT_CATALOG=12;UNIQUE_CONSTRAINT_NAME=12;UNIQUE_CONSTRAINT_SCHEMA=12;UNIQUE_NAME=1;uniqueIndexNullsHandling=t85,h26,r0,154,56,126,108;uniqueness=22,15,23,6;unit=6,21,15,10,26,59,0,1,23,7;United=20,68;Universal=29;Universally=h5,r21;Unix=29;unknown=h7,r4,10,2,8,13,0,5,21,12,22,59,1,58,18;UNKNOWN_DATA_TYPE_1=h2,r0;UNKNOWN_MODE_1=h2,r0;unless=1,43,11,21,20,0,17,23,6,7,37,59,15,10,5;unlike=17,21,15,93,1,6;unlimited=21;unload=111,0;unloadH2=h111,r0;unloading=38;Unlock=18,22,0;unlocked=5,14;unlockMeta=h18,r0;unmodified=20;unnamed=19,0,2,22,100;unnecessary=22,40;unneeded=1;unnest=h6,r10;unquoted=4,10,122,0,1,30,32,6,59,16,5;unrelated=26,7,22,0;unreleased=h22;unsafe=7,22;unsetExclusiveSession=h18,r0;unsigned=6,26,0,4,5;UNSIGNED_ATTRIBUTE=4;unspecified=92;unsuccessful=5,28;unsupported=2,0,13,19,3,16,44,4,110,114,46,51,8,42,33,31,53,70,119;UNSUPPORTED_CIPHER=h2,r0;UNSUPPORTED_COMPRESSION_ALGORITHM_1=h2,r0;UNSUPPORTED_COMPRESSION_OPTIONS_1=h2,r0;UNSUPPORTED_LOCK_METHOD_1=h2,r0;UNSUPPORTED_SETTING_1=h2,r0;UNSUPPORTED_SETTING_COMBINATION=h2,r0;UnsupportedOperationException=17;until=17,5,1,30,28,7,15,32,20,49,11,34,10,18;untrusted=136;untyped=22;unused=4,22,112,0,49,18,17;unwrap=h19,16,49,3,4,9,33,31,53,r0,8,13;unzip=138;updatable=h5,r3,2,0,1,11,7;UpdatableView=22,5;update=h1,3,0,61,121,52,r16,2,4,10,22,13,5,7,8,15,12,17,32,54,18,98,11,26,97,6,93,143,14,46;UPDATE_RULE=4,12;updateArray=h3,9,r0;updateAsciiStream=h3,9,r0;updateBigDecimal=h3,9,r0;updateBinaryStream=h3,9,r0;updateBlob=h3,9,r0;updateBoolean=h3,9,r0;updateByte=h3,9,r0;updateCharacterStream=h3,9,r0;updateClob=h3,9,r0;updateCount=h16,r8,13,0;updated=0,26,17,7,1,10,104,24,6,4,11,3,14,16,46,51,13;updateDate=h3,9,r0;updateDouble=h3,9,r0;updateFloat=h3,9,r0;updateInt=h3,9,r0;updateLong=h3,9,r0;updateMeta=h18,r0;updateNCharacterStream=h3,9,r0;updateNClob=h3,9,r0;updateNString=h3,9,r0;updateNull=h3,9,r0;updateObject=h3,9,r0;updateRef=h3,9,r0;updateRow=h3,9,r0,2;updateRowId=h3,9,r0;updatesAreDetected=h4,r0;updateSequenceOnManualIdentityInsertion=h26,r0;updateShort=h3,9,r0;updateSQLXML=h3,9,r0;updateString=h3,9,r0;updateTime=h3,9,r0;updateTimestamp=h3,9,r0;updateX=54,3;updating=h15,r0,143,2,3,4,52,22,98,27,39,97;upgrade=t111,h11,r0,59,22,116,137,157,56;Upgrading=h59,r15;upload=40;uploaded=40;upper=h6,r10,7,18,122,0,32,57,5;UPPER_NAME=7;upperBoundInt=6;uppercase=4,1,6,11,30,43;upperCaseUserName=5;upperName=18;UPS=5;urgently=40;url=h7,5,100,r1,33,11,0,2,43,34,24,63,71,8,58,111,94,38,19,4,90,22,49,9,62,3,18,28,32,6;URL_FORMAT=h24,r0;URL_FORMAT_ERROR_2=h2,r0;URL_MAP=h28,r0;URL_RELATIVE_TO_CWD=h2,r0;urlMap=28,0;urlOfH2Auth=7;urlSource=94,5;urlString=1,6;urlTarget=94,5;URSHIFT=h6,r22;USA=68,29,38;usable=29;usage=h15,17,5,r8,3,2,13,59,4;USD=29;USE_THREAD_CONTEXT_CLASS_LOADER=h28,r0;useDatabaseLock=11;useful=34,7,5,21,11,26;useless=22;USER_ALREADY_EXISTS_1=h2,r0;USER_DATA_TYPE_ALREADY_EXISTS_1=h2,r0,141;USER_DATA_TYPE_NOT_FOUND_1=h2,r0,141;USER_HOME=h28,r0;user_id=6;USER_NAME=12;USER_NOT_FOUND_1=h2,r0;USER_OR_ROLE_NOT_FOUND_1=h2,r0;USER_PACKAGE=h24,r0;UserBuilder=t130,r0,154,56,126,108;userDefinedFunctionName=10;username=1,48,11,7,38,5;userNamePattern=7;userPassword=2;userPasswordHash=48;userProvider=7;userpwd=7;UserRecord=11;userString=1,6;UserToRoleMapper=7;userToRolesMapper=t128,r7,0,146,158,56,127;usesLocalFilePerTable=h4,r0;usesLocalFiles=h4,r0;useSSL=7;useThreadContextClassLoader=28,0;usual=5;usually=7,11,1,17,44,10,5,18,21,38,15,30,0,28,6,26,93,27,69,39,63,33;UTC=6,21,10,11,1;utf=10,6,28,22,62,0,11,63,7;UTF8=6;UTF8TOSTRING=h6;utility=17,29,22,116,0,111;utilizes=29;uuid=h21,5,10,41,r6,22,0,59,17;UUID_COLLATION=22;uuidType=10;UUIs=5'; +ref['v']='val=8,23;valid=19,0,2,10,3,12,17,4,20,16,6,15,133,1,5,14;validate=133,0,7,127;validateCredentials=h133,r0;validation=7,29;validator=7;validatorClass=7;validity=20;validly=20;valuable=68;VALUE_DEFINITION=12;VALUE_NAME=12;VALUE_ORDINAL=12;VALUE_TOO_LONG_2=h2,r0;ValueLob=14,0,25,47,22;ValueNull=22,14;valueOf=h45,92,89,86,91,95,85,99,55,107,r0,7,5;valueString=6;ValueTimestampTimeZone=14,19,25,120,18,22;van=68;VAR_POP=h23;VAR_SAMP=h23;VARBINARY=h41,r21,1,14,22,0;varchar=h41,r2,11,21,7,15,27,39,4,6,1,5,53,0,9,26,59,10,18;VARCHAR2=21;VARCHAR_CASESENSITIVE=21;VARCHAR_IGNORECASE=h21,41,r0,22,1,5;variable=h0,11,r14,72,17,21,22,2,7,1,40,5,6,59,15;variable_binary=h1,r11;variableBinary=14;variableName=1,6;variance=23;variant=6,5;various=116,0,11,59,1,29,12,32,7,38,15,5,108;vary=15;VARYING=h12,21,r6,41,59,0,5,1;Vasilakis=68;vast=17;vector=5;vendor=41,0,24,59,49;verified=22,17;verify=40,18,0,5,17,11;verifyMetaLocked=h18,r0;versa=2,7;version=h20,22,153,17,24,5,40,r0,4,11,111,15,1,59,12,7,55,2,6,38,29,25,100,21,41,23,10,58,37;version_columns=11;VERSION_MAJOR=h24,r0;VERSION_MINOR=h24,r0;versionColumnNotPseudo=4;versionColumnPseudo=4;versionColumnUnknown=4;VersionedValue=14,0;versioning=17;versus=40;vertical=100;very=h38,r17,5,21,15,68,29,6,7,22,11,1,36;VERYSMALLINT=2;veto=7;via=7,29;vice=2,7;Videos=h29;view=h1,12,5,r2,0,22,14,29,24,7,26,32,35,18,92,4,15,108;VIEW_ALREADY_EXISTS_1=h2,r0;VIEW_COST_CACHE_MAX_AGE=h24,r0;VIEW_DEFINITION=12,22;VIEW_INDEX_CACHE_SIZE=h24,r0;VIEW_IS_INVALID_2=h2,r0;VIEW_NAME=5;VIEW_NOT_FOUND_1=h2,r0;viewed=5;viewExpressionNames=t92,h26,r0,154,56,126,108;viewing=69;viewName=1;violate=2,0;violated=2,0,10;virtual=7,2,5,32,0,15,28,1;virus=h15;viruses=15;visible=4,1,0,10,12,3,11,5,136;vision=29;visit=20,22;Vista=138;Visual=29;volunteer=29,59;VPDA=29;vulnerability=5,22'; +ref['w']='W3C=29;waitFor=22;waitForLock=14;waitForLockThread=14;waitIfExclusiveModeEnabled=h14,r0;waiting=14,0,28;waives=20;wakes=5;WAL=15;warConsole=11;warned=11;warning=0,16,19,3,22,1,11,124;warns=19,0;warranties=20;warranty=h20;wasn=1;wasNull=h8,3,9,r0;watchdog=5,7;Water=29;watermark=5;Watson=68;Wayback=20;Wayne=11;weak=5;web=h11,r29,34,0,90,40,5,116,7,20,22,38,68;webAdminPassword=34,22,11;webAllowOthers=34,11,5;webapp=29;WebappClassLoader=38;webDaemon=34;webExternalNames=34,22,5;webkit=100;weblica=29;Weblog=29;webPort=34,11;WebServer=22;WebServlet=11;webSSL=34,11;week=h10,6,r38;WEEK_YEAR=10;weekField=10;weekYearField=10;weight=29,100;welcome=15,156,40,11;were=16,0,2,13,46,104,17,4,51,1,22,61,101,11,90,103,38,59,54,15,64,10;Werkzeugkasten=29;whenever=5,7,10;whenValue=6;where=h23,38,r15,5,1,2,11,7,10,0,17,16,6,20,26,46,51,36,22,28,27,39,110,37,21,144,93,13,14,19;wherever=11,29,15;whether=h4,0,r45,26,12,14,16,122,10,25,30,20,47,31,9,3,24,32,43,54,39,55,18,8,7,19,98,28,49,11;while=2,0,7,11,17,10,5,118,6,142,4,88,69,15,1,38,30,149,20,16,49,8,151,13,148,150,123;white=22;whitespace=30,0,27,10,21,92,45,22,89,86,91,95,85,99,55;whitespaceChars=27;whole=21,46,1,51,0,15,24,10,5,108;whose=7;why=h15,68,38,r7,17;wide=20,7;wiki=29,93,6;wikipedia=5,93,29,6,2;Wildam=68;wildcard=h10;wildcardExpression=10;William=68;window=t37,h10,5,r139,11,1,2,0,23,124,138,28,7,38,40,90,34,153,22,62,15,100;WINDOW_NOT_FOUND_1=h2,r0;windowActivated=h64,r0;windowClosed=h64,r0;windowClosing=h64,r0;windowDeactivated=h64,r0;windowDeiconified=h64,r0;WindowEvent=64,0;windowFrame=10;windowFrameBound=10;windowFramePreceding=10;windowIconified=h64,r0;WindowListener=64,137,56;windowName=1,10;windowNameOrSpecification=h23,r37;windowOpened=h64,r0;windowSpecification=1,10;WITH_HIERARCHY=12;WITH_TIES_WITHOUT_ORDER_BY=h2,r0;WITH_VALUE=h95;within=h11,10,r7,23,6,14,5,0,17,26,1,25,18,2,38,20,120,50,54,15,61,19,22,16,28,49;withinGroupSpecification=23;without=10,1,6,20,23,21,0,2,22,5,17,11,7,123,16,32,90,95,34,59,62,43,19,69;wizard=15,11;WKB=10;WKT=21,10;word=h5,r27,0,4,68,40;workaround=5,38,59,22,1,11,7;Workbench=29;worked=59;workflow=29,22,40;workgroup=29;working=2,5,29,93,38,59,0,6,100,7;world=h15,r6,2,11,17,1,20,9,7,5;worldwide=20;worry=7;worse=15;wrapInputStream=h72,r0;wrapOutputStream=h72,r0;wrapper=h8,13,r5,16,49,4,9,19,39,3,33,31,53,0,143,17,15;write=h30,r17,0,5,11,15,7,21,24,6,31,29,1,25,72,19,59,51,12,22,116,50;WRITE_DELAY=h1,r15,5;writeColumnHeader=10;writer=0,30,46,33,39,51,70,5,29,11;writeRow=30;writeVariableInt=h72,r0;writing=h11,r30,0,17,46,25,2,51,1,5,18,20,6,136,68,10;written=17,7,5,30,10,24,6,0,29,28,49,1,32,72,2,38,34,51,40,138;wrong=h5,r2,22,0,24,28,38;WRONG_PASSWORD_FORMAT=h2,r0;WRONG_USER_OR_PASSWORD=h2,r0;WRONG_XID_FORMAT_1=h2,r0;wrote=68,40;www=29,20,15,6,11,5;WWWWorld=6'; +ref['x']='X11=11;x4juli=7;x64=5;XA_DATA_SOURCE=16,44,4,110,114,19,46,51,8,42,3,13,33,31,53,70,119;XA_OK=44;XA_RDONLY=44;XAConnection=33,44,145,2,56;XADataSource=33,145,56;XAException=44;xAllowOthers=34;xares=44;XAResource=h44,r0,145,56;xAuth=29;xid=h114,r44,0,16,4,110,56,19,46,51,145,8,42,3,13,33,31,53,70,119;XID_TEST=1;xml=6,11,7,70,29,21,100,40,5;XMLATTR=h6;XMLCDATA=h6;XMLCOMMENT=h6;XMLNODE=h6;xmlns=11;xmlObject=3;XMLSTARTDOC=h6;XMLTEXT=h6;Xmx128m=2;XNOR=23,6;xor=17,5,23,6;Xrunhprof=15,63;xsd=11;XTEA=7;XTS=17;XXX=1;XYM=21;XYZ=2,21,22;XYZM=21'; +ref['y']='year=h10,21,6,45,r36,0,22,41,7,20,2,5;YEAR_TO_MONTH=h45;yearField=10;yearInt=10;yes=12,4,16,11,38,14;yet=34,0,38,3,7,95,32,22,28,49,6,11,87,1,124,5;yield=5;York=20;your_username=11;yourself=15,40,38;Youtube=29;yyyy=10,6,2'; +ref['z']='Zen=29;zero=h6,5,r36,2,0,21,15,1,12,48,44,7,10;ZERO_BASED_ENUMS=32,0;zeroBasedEnums=h19,25,14,18,32,120,r0,47;zeroExLiteralsAreBinaryStrings=h26,r0;zip=h7,r139,11,5,10,102,17,72,1,100,153,116,0,6,2,93,88;zipFileName=88,102,7,5;zone=h10,21,1,r0,6,8,11,3,13,14,22,41,12,43,25,132,120,19,18,2,7;ZonedDateTime=21'; +// totalRelations: 26439 +ignored=';result;after;the;good;notifyall;have;state;two;think;tabletab;override;static;final;back;use;null;make;look;work;var;parameter;exception;engine;getclass;default;next;classes;was;way;finalize;who;extend;which;for;that;than;they;them;then;this;know;type;rowcolor;get;help;create;concrete;wait;void;lang;want;javascript;must;has;time;before;his;you;how;notify;could;first;from;value;int;its;clone;number;copied;modifier;using;constr;description;contain;only;should;would;object;throw;implement;check;well;activetabletab;nested;prev;does;equal;browser;navigation;field;server;skip;may;about;into;internal;specified;command;because;detail;new;tool;inherited;java;disabled;supported;string;not;now;statement;what;tostring;when;used;user;frame;class;index;transaction;code;altcolor;one;org;connection;method;come;sqlexception;our;out;data;like;link;will;boolean;your;these;util;list;interface;with;there;even;given;jdbc;overview;tree;their;represent;take;some;just;file;return;instance;true;constructor;summary;trace;public;array;setting;message;system;other;mode;implemented;all;and;row;any;api;are;name;deprecated;case;package;most;database;also;say;see;set;column;over;table;sql;but;hashcode;tab;can;'; +} diff --git a/h2/docs/html/installation.html b/h2/docs/html/installation.html new file mode 100644 index 0000000..a58b631 --- /dev/null +++ b/h2/docs/html/installation.html @@ -0,0 +1,233 @@ + + + + + + +Installation + + + + + + + + + + + + + + + + diff --git a/h2/docs/html/license.html b/h2/docs/html/license.html new file mode 100644 index 0000000..6c8ced7 --- /dev/null +++ b/h2/docs/html/license.html @@ -0,0 +1,518 @@ + + + + + + +License + + + + + + + + + + + + + + + + diff --git a/h2/docs/html/links.html b/h2/docs/html/links.html new file mode 100644 index 0000000..bc1a3fc --- /dev/null +++ b/h2/docs/html/links.html @@ -0,0 +1,822 @@ + + + + + + +H2 In Use and Links + + + + + + + + + + + + + + + + diff --git a/h2/docs/html/main.html b/h2/docs/html/main.html new file mode 100644 index 0000000..0fb93ce --- /dev/null +++ b/h2/docs/html/main.html @@ -0,0 +1,160 @@ + + + + + + + + +H2 Database Engine + + + + + + + + + + + + + + + + diff --git a/h2/docs/html/mainWeb.html b/h2/docs/html/mainWeb.html new file mode 100644 index 0000000..9fe36ac --- /dev/null +++ b/h2/docs/html/mainWeb.html @@ -0,0 +1,139 @@ + + + + + + + + +H2 Database Engine + + + + + + + +
+ + +

H2 Database Engine

+

+Welcome to H2, the Java SQL database. The main features of H2 are: +

+
    +
  • Very fast, open source, JDBC API +
  • Embedded and server modes; in-memory databases +
  • Browser based Console application +
  • Small footprint: around 2.5 MB jar file size +
+ + + + + + + + + + + + + + +
+ + + + + +
+

Download

+ Version 2.1.212 (2022-04-09) +
+ Download this database + + Windows Installer (6.7 MB) +
+ Download this database + + All Platforms (zip, 9.5 MB) +
+ All Downloads +
+
    + + +
+

Support

+

+ Stack Overflow (tag H2)

+ Google Group

+ For non-technical issues, use:
+ +

+
+
+

Features

+
    +
  • Very fast, open source, JDBC API
  • +
  • Embedded and server modes; disk-based or in-memory databases
  • +
  • Transaction support, multi-version concurrency
  • +
  • Browser based Console application
  • +
  • Encrypted databases
  • +
  • Fulltext search
  • +
  • Pure Java with small footprint: around 2.5 MB jar file size
  • +
  • ODBC driver
  • +
+
+ + +
+

News

+

+ Newsfeeds: + Full text (Atom) + or Header only (RSS). +

+ Email Newsletter: Subscribe to + + H2 Database News (Google account required) + to get informed about new releases. + Your email address is only used in this context. +

+
+
 
+ + +
+

Contribute

+

+ You can contribute to the development of H2 by sending feedback and bug + reports, or translate the H2 Console application (for details, start the H2 Console + and select Options / Translate). + To donate money, click on the PayPal button below. You will be listed as a supporter: +

+
+

+ + + +

+
+
+
+ +
diff --git a/h2/docs/html/migration-to-v2.html b/h2/docs/html/migration-to-v2.html new file mode 100644 index 0000000..7b55898 --- /dev/null +++ b/h2/docs/html/migration-to-v2.html @@ -0,0 +1,301 @@ + + + + + + + +Migration to 2.0 + + + + + + + + + + + + + + + diff --git a/h2/docs/html/mvstore.html b/h2/docs/html/mvstore.html new file mode 100644 index 0000000..b6dc862 --- /dev/null +++ b/h2/docs/html/mvstore.html @@ -0,0 +1,863 @@ + + + + + + + +MVStore + + + + + + + + + + + + + + + diff --git a/h2/docs/html/navigation.js b/h2/docs/html/navigation.js new file mode 100644 index 0000000..1262d1b --- /dev/null +++ b/h2/docs/html/navigation.js @@ -0,0 +1,195 @@ +/* + * 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 + */ + +function scroll() { + var scroll = document.documentElement.scrollTop; + if (!scroll) { + scroll = document.body.scrollTop; + } + var c = 255 - Math.min(scroll / 4, 64); + var goTop = document.getElementById('goTop'); + goTop.style.color = 'rgb(' + c + ',' + c + ',' + c + ')'; +} + +function loadFrameset() { + var a = location.search.split('&'); + var page = decodeURIComponent(a[0].substr(1)); + var frame = a[1]; + if(page && frame){ + var s = "top." + frame + ".location.replace('" + page + "')"; + eval(s); + } + return; +} + +function frameMe(frame) { + if(location.host.indexOf('h2database') < 0) { + // allow translation + return; + } + var frameset = "frame.html"; // name of the frameset page + if(frame == null) { + frame = 'main'; + } + page = new String(self.document.location); + var pos = page.lastIndexOf("/") + 1; + var file = page.substr(pos); + file = encodeURIComponent(file); + if(window.name != frame) { + var s = frameset + "?" + file + "&" + frame; + top.location.replace(s); + } else { + highlightFrame(); + } + return; +} + +function addHighlight(page, word, count) { + if(count > 0) { + if(top.main.document.location.href.indexOf(page) > 0 && top.main.document.body && top.main.document.body.innerHTML) { + highlight(); + } else { + window.setTimeout('addHighlight("'+page+'","'+word+'",'+(count-1)+')', 10); + } + } +} + +function highlightFrame() { + var url = new String(top.main.location.href); + if(url.indexOf('?highlight=') < 0) { + return; + } else { + var page = url.split('?highlight='); + var word = decodeURIComponent(page[1]); + top.main.document.body.innerHTML = highlightSearchTerms(top.main.document.body, word); + top.main.location = '#firstFound'; + // window.setTimeout('goFirstFound()', 1); + } +} + +function highlight() { + var url = new String(document.location.href); + if(url.indexOf('?highlight=') < 0) { + return; + } else { + var page = url.split('highlight=')[1].split('&')[0]; + var search = decodeURIComponent(url.split('search=')[1].split('#')[0]); + var word = decodeURIComponent(page); + document.body.innerHTML = highlightSearchTerms(document.body, word); + document.location = '#firstFound'; + document.getElementById('search').value = search; + listWords(search, ''); + } +} + +function goFirstFound() { + top.main.location = '#firstFound'; +/* + var page = new String(parent.main.location); + alert('first: ' + page); + page = page.split('#')[0]; + paramSplit = page.split('?'); + page = paramSplit[0]; + page += '#firstFound'; + if(paramSplit.length > 0) { + page += '?' + paramSplit[1]; + } + top.main.location = page; +*/ +} + +function highlightSearchTerms(body, searchText) { + matchColor = "ffff00,00ffff,00ff00,ff8080,ff0080".split(','); + highlightEndTag = ""; + searchArray = searchText.split(","); + if (!body || typeof(body.innerHTML) == "undefined") { + return false; + } + var bodyText = body.innerHTML; + for (var i = 0; i < searchArray.length; i++) { + var color = matchColor[i % matchColor.length]; + highlightStartTag = ""; + bodyText = doHighlight(bodyText, searchArray[i], highlightStartTag, highlightEndTag); + } + return bodyText; +} + +function doHighlight(bodyText, searchTerm, highlightStartTag, highlightEndTag) { + if(searchTerm == undefined || searchTerm=="" || searchTerm.length < 3) { + return bodyText; + } + var newText = ""; + var i = -1; + var lcSearchTerm = searchTerm.toLowerCase(); + var lcBodyText = bodyText.toLowerCase(); + while (bodyText.length > 0) { + i = lcBodyText.indexOf(lcSearchTerm, i+1); + if (i < 0) { + newText += bodyText; + bodyText = ""; + } else { + // skip anything inside an HTML tag + if (bodyText.lastIndexOf(">", i) >= bodyText.lastIndexOf("<", i)) { + // skip anything inside a + + + + + + + + + + + diff --git a/h2/docs/html/quickstart.html b/h2/docs/html/quickstart.html new file mode 100644 index 0000000..b2d905f --- /dev/null +++ b/h2/docs/html/quickstart.html @@ -0,0 +1,219 @@ + + + + + + + +Quickstart + + + + + + + + + + + + + + + + diff --git a/h2/docs/html/search.js b/h2/docs/html/search.js new file mode 100644 index 0000000..6d32a65 --- /dev/null +++ b/h2/docs/html/search.js @@ -0,0 +1,273 @@ +/* + * 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 + */ + +var pages = new Array(); +var ref = new Array(); +var ignored = ''; +var firstLink = null; +var firstLinkWord = null; + +String.prototype.endsWith = function(suffix) { + var startPos = this.length - suffix.length; + if (startPos < 0) { + return false; + } + return this.lastIndexOf(suffix, startPos) == startPos; +}; + +function listWords(value, open) { + value = replaceOtherChars(value); + value = trim(value); + if (pages.length == 0) { + load(); + } + var table = document.getElementById('result'); + while (table.rows.length > 0) { + table.deleteRow(0); + } + firstLink = null; + var clear = document.getElementById('clear'); + if (value.length == 0) { + clear.style.display = 'none'; + return; + } + clear.style.display = ''; + var keywords = value.split(' '); + if (keywords.length > 1) { + listMultipleWords(keywords); + return; + } + if (value.length < 3) { + max = 100; + } else { + max = 1000; + } + value = value.toLowerCase(); + var r = ref[value.substring(0, 1)]; + if (r == undefined) { + return; + } + var x = 0; + var words = r.split(';'); + var count = 0; + for (var i = 0; i < words.length; i++) { + var wordRef = words[i]; + if (wordRef.toLowerCase().indexOf(value) == 0) { + count++; + } + } + for (var i = 0; i < words.length && (x <= max); i++) { + var wordRef = words[i]; + if (wordRef.toLowerCase().indexOf(value) == 0) { + word = wordRef.split("=")[0]; + var tr = table.insertRow(x++); + var td = document.createElement('td'); + var tdClass = document.createAttribute('class'); + tdClass.nodeValue = 'searchKeyword'; + td.setAttributeNode(tdClass); + + var ah = document.createElement('a'); + var href = document.createAttribute('href'); + href.nodeValue = 'javascript:set("' + word + '");'; + var link = document.createTextNode(word); + ah.setAttributeNode(href); + ah.appendChild(link); + td.appendChild(ah); + tr.appendChild(td); + piList = wordRef.split("=")[1].split(","); + if (count < 20 || open == word) { + x = addReferences(x, piList, word); + } + } + } + if (x == 0) { + if (ignored.indexOf(';' + value + ';') >= 0) { + noResults(table, 'Common word (not indexed)'); + } else { + noResults(table, 'No results found!'); + } + } +} + +function set(v) { + if (pages.length == 0) { + load(); + } + var search = document.getElementById('search').value; + listWords(search, v); + document.getElementById('search').focus(); + window.scrollBy(-20, 0); +} + +function goFirst() { + var table = document.getElementById('result'); + if (firstLink != null) { + go(firstLink, firstLinkWord); + } + return false; +} + +function go(pageId, word) { + var page = pages[pageId]; + var load = '../' + page.file + '?highlight=' + encodeURIComponent(word); + if (top.main) { + if (!top.main.location.href.endsWith(page.file)) { + top.main.location = load; + } + } else { + if (!document.location.href.endsWith(page.file)) { + var search = document.getElementById('search').value; + document.location = load + '&search=' + encodeURIComponent(search); + } + } +} + +function listMultipleWords(keywords) { + var count = new Array(); + var weight = new Array(); + for (var i = 0; i < pages.length; i++) { + count[i] = 0; + weight[i] = 0.0; + } + for (var i = 0; i < keywords.length; i++) { + var value = keywords[i].toLowerCase(); + if (value.length <= 1) { + continue; + } + var r = ref[value.substring(0, 1)]; + if (r == undefined) { + continue; + } + var words = r.split(';'); + for (var j = 0; j < words.length; j++) { + var wordRef = words[j]; + if (wordRef.toLowerCase().indexOf(value) == 0) { + var word = wordRef.split("=")[0].toLowerCase(); + piList = wordRef.split("=")[1].split(","); + var w = 1; + for (var k = 0; k < piList.length; k++) { + var pi = piList[k]; + if (pi.charAt(0) == 't') { + pi = pi.substring(1); + w = 10000; + } else if (pi.charAt(0) == 'h') { + pi = pi.substring(1); + w = 100; + } else if (pi.charAt(0) == 'r') { + pi = pi.substring(1); + w = 1; + } + if (w > 0) { + if (word != value) { + // if it's only the start of the word, + // reduce the weight + w /= 10.0; + } + // higher weight for longer words + w += w * word.length / 10.0; + count[pi]++; + weight[pi] += w; + } + } + } + } + } + var x = 0; + var table = document.getElementById('result'); + var piList = new Array(); + var piWeight = new Array(); + for (var i = 0; i < pages.length; i++) { + var w = weight[i]; + if (w > 0) { + piList[x] = '' + i; + piWeight[x] = w * count[i]; + x++; + } + } + // sort + for (var i = 1, j; i < x; i++) { + var tw = piWeight[i]; + var ti = piList[i]; + for (j = i - 1; j >= 0 && (piWeight[j] < tw); j--) { + piWeight[j + 1] = piWeight[j]; + piList[j + 1] = piList[j]; + } + piWeight[j + 1] = tw; + piList[j + 1] = ti; + } + addReferences(0, piList, keywords); + if (piList.length == 0) { + noResults(table, 'No results found'); + } +} + +function addReferences(x, piList, word) { + var table = document.getElementById('result'); + for (var j = 0; j < piList.length; j++) { + var pi = piList[j]; + if (pi.charAt(0) == 't') { + pi = pi.substring(1); + } else if (pi.charAt(0) == 'h') { + pi = pi.substring(1); + } else if (pi.charAt(0) == 'r') { + pi = pi.substring(1); + } + var tr = table.insertRow(x++); + var td = document.createElement('td'); + var tdClass = document.createAttribute('class'); + tdClass.nodeValue = 'searchLink'; + td.setAttributeNode(tdClass); + var ah = document.createElement('a'); + var href = document.createAttribute('href'); + var thisLink = 'javascript:go(' + pi + ', "' + word + '")'; + if (firstLink == null) { + firstLink = pi; + firstLinkWord = word; + } + href.nodeValue = thisLink; + ah.setAttributeNode(href); + var page = pages[pi]; + var link = document.createTextNode(page.title); + ah.appendChild(link); + td.appendChild(ah); + tr.appendChild(td); + } + return x; +} + +function trim(s) { + while (s.charAt(0) == ' ' && s.length > 0) { + s = s.substring(1); + } + while (s.charAt(s.length - 1) == ' ' && s.length > 0) { + s = s.substring(0, s.length - 1); + } + return s; +} + +function replaceOtherChars(s) { + var x = ""; + for (var i = 0; i < s.length; i++) { + var c = s.charAt(i); + if ("\t\r\n\"'.,:;!&/\\?%@`[]{}()+-=<>|*^~#$".indexOf(c) >= 0) { + c = " "; + } + x += c; + } + return x; +} + +function noResults(table, message) { + var tr = table.insertRow(0); + var td = document.createElement('td'); + var tdClass = document.createAttribute('class'); + tdClass.nodeValue = 'searchKeyword'; + td.setAttributeNode(tdClass); + var text = document.createTextNode(message); + td.appendChild(text); + tr.appendChild(td); +} + diff --git a/h2/docs/html/security.html b/h2/docs/html/security.html new file mode 100644 index 0000000..dcbae44 --- /dev/null +++ b/h2/docs/html/security.html @@ -0,0 +1,187 @@ + + + + + + +Features + + + + + + + + + + + + + + + + diff --git a/h2/docs/html/source.html b/h2/docs/html/source.html new file mode 100644 index 0000000..5b8f130 --- /dev/null +++ b/h2/docs/html/source.html @@ -0,0 +1,55 @@ + + + + + + + diff --git a/h2/docs/html/sourceError.html b/h2/docs/html/sourceError.html new file mode 100644 index 0000000..84538c4 --- /dev/null +++ b/h2/docs/html/sourceError.html @@ -0,0 +1,255 @@ + + + + + +Error Analyzer + + + + + + + + +

Error Analyzer

+
Home
+

+ Input  + Details  + Source Code +

+
+
+

Paste the error message and stack trace below and click on 'Details' or 'Source Code':

+ +
+
+

Error Code:

+

Product Version:

+

Message:

+

+

More Information:

+ +
+
+ + +
+

Stack Trace:

+ +
+

Source File:
+ Inline

+ +
+
+ + + diff --git a/h2/docs/html/stylesheet.css b/h2/docs/html/stylesheet.css new file mode 100644 index 0000000..a30f4d5 --- /dev/null +++ b/h2/docs/html/stylesheet.css @@ -0,0 +1,396 @@ +/* + * 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 + */ + +td, input, select, textarea, body, code, pre, td, th { + font: 13px/1.4 Arial, sans-serif; + font-weight: normal; +} + +p { + margin: 0.4em 0 0.6em 0; +} + +h1 { + font-weight: bold; +} + +h2, h3, h4, h5 { + margin: 0.8em 0 0.5em 0; + border-bottom-color: #999; + border-bottom-style: solid; + border-bottom-width: 1px; +} + +td, input, select, textarea, body, code, pre { + font-size: 13px; +} + +pre { + background-color: #ece9d8; + border: 1px solid #aca899; + padding: 6px; + + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + + -moz-box-shadow: 2px 2px 2px #aca899; + -webkit-box-shadow: 2px 2px 2px #aca899; + -khtml-box-shadow: 2px 2px 2px #aca899; + -o-box-shadow: 2px 2px 2px #aca899; + box-shadow: 2px 2px 2px #aca899; +} + +code { + background-color: #ece9d8; + padding: 0px 4px; + + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; +} + +img { + border: 0px; +} + +body { + max-width: 800px; + clear: both; + width: 800px; + margin: 0 auto; +} + +h1 { + background-color: #0000bb; + padding: 2px 4px 2px 4px; + margin-top: 11px; + color: #fff; + font-size: 22px; + line-height: normal; +} + +h2 { + font-size: 19px; +} + +h3 { + font-size: 16px; +} + +h4 { + font-size: 13px; +} + +hr { + color: #CCC; + background-color: #CCC; + height: 1px; + border: 0px solid blue; +} + +table { + background-color: #ffffff; + border-collapse: collapse; + border: 1px solid #aca899; +} + +.main { + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + + -moz-box-shadow: 2px 2px 2px #aca899; + -webkit-box-shadow: 2px 2px 2px #aca899; + -khtml-box-shadow: 2px 2px 2px #aca899; + -o-box-shadow: 2px 2px 2px #aca899; + box-shadow: 2px 2px 2px #aca899; +} + +th { + text-align: left; + background-color: #ece9d8; + border: 1px solid #aca899; + padding: 3px; +} + +td { + background-color: #ffffff; + text-align: left; + vertical-align: top; + border: 1px solid #aca899; + padding: 3px; +} + +form { +} + +ul, ol { + list-style-position: outside; + padding-left: 20px; +} + +li { + margin-top: 2px; +} + +a { + text-decoration: none; + color: #0000ff; +} + +a:visited { + color: #0000bb; +} + +a:hover { + text-decoration: underline; +} + +em.u { + text-decoration: underline; + font-style: normal; +} + +.menu { + margin: 10px 10px 10px 10px; +} + +table.search { + width: 100%; + border: 0px; +} + +tr.search { + border: 0px; +} + +td.search { + border: 0px; + padding: 2px 0px 2px 10px; +} + +td.searchKeyword { + border: 0px; + padding: 0px 0px 0px 2px; +} + +td.searchKeyword a { + text-decoration: none; + color: #000000; +} + +td.searchKeyword a:hover { + text-decoration: underline; +} + +td.searchLink { + border: 0px; + padding: 0px 0px 0px 32px; + text-indent: -16px; +} + +td.searchLink a { + text-decoration: none; + color: #0000ff; +} + +td.searchLink a:hover { + text-decoration: underline; +} + +table.nav { + border: 0px; +} + +tr.nav { + border: 0px; +} + +td.nav { + border: 0px; +} + +table.content { + width: 100%; + height: 100%; + border: 0px; +} + +tr.content { + border:0px; +} + +td.content { + border:0px; +} + +.contentDiv { + margin:10px; +} + +.content { + margin: 10px 10px 10px 0px; +} + +.screenshot { + border: 1px outset #800; + padding: 10px; + margin: 10px 0px; +} + +.compareFeature { +} + +.compareY { + color: #050; +} + +.compareN { + color: #800; +} + +table.index { + width: 100%; + border: 0px; + padding: 0px; + margin: 0px; + border: 0px none; + border-collapse: collapse; +} + +td.index { + width: 33%; + border: 0px; + padding: 0px; + margin: 0px; + border: 0px none; + border-collapse: collapse; + vertical-align: top; +} + +.railroad { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; +} + +div.ruleCompat code { + border-color: coral; + background-color: mistyrose; +} + +div.ruleH2 code { + border-color: lightseagreen; +} + +span.ruleCompat { + color: darkred; +} + +span.ruleH2 { + color: green; +} + +.c { + padding: 1px 3px; + margin: 0px 0px; + border: 2px solid; + -moz-border-radius: 0.4em; + -webkit-border-radius: 0.4em; + -khtml-border-radius: 0.4em; + border-radius: 0.4em; + background-color: #fff; +} + +.ts { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; + width: 16px; + height: 24px; + background-image: url(images/div-ts.png); + background-size: 16px 512px; +} + +.ls { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; + width: 16px; + height: 24px; + background-image: url(images/div-ls.png); + background-size: 16px 512px; +} + +.ks { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; + width: 16px; + height: 24px; + background-image: url(images/div-ks.png); + background-size: 16px 512px; +} + +.te { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; + width: 16px; + height: 24px; + background-image: url(images/div-te.png); + background-size: 16px 512px; +} + +.le { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; + width: 16px; + height: 24px; + background-image: url(images/div-le.png); + background-size: 16px 512px; +} + +.ke { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; + width: 16px; + height: 24px; + background-image: url(images/div-ke.png); + background-size: 16px 512px; +} + +.d { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; + min-width: 16px; + height: 24px; + background-image: url(images/div-d.png); + background-size: 1024px 512px; +} diff --git a/h2/docs/html/stylesheetPdf.css b/h2/docs/html/stylesheetPdf.css new file mode 100644 index 0000000..dacc282 --- /dev/null +++ b/h2/docs/html/stylesheetPdf.css @@ -0,0 +1,162 @@ +/* + * 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 + */ + +td, input, select, textarea, body, code, pre, td, th { + font: 14pt Tahoma, Arial, Helvetica, sans-serif; + font-weight: normal; +} + +h1, h2, h3, h4, h5 { + font: 14pt Arial, Helvetica, sans-serif; + font-weight: bold; +} + +td, input, select, textarea, body, code, pre { + font-size: 14pt; +} + +pre { + background-color: #ece9d8; + border: 1px solid rgb(172, 168, 153); + padding: 4px; +} + +img { + border: 0px; +} + +body { + margin: 0px; +} + +h1, p.title { + background-color: #0000bb; + padding: 2px 4px 2px 4px; + color: #fff; + font-size: 24pt; + font-weight: bold; + line-height: normal; +} + +h2 { + font-size: 18pt; + margin-top: 1.5em; +} + +h3 { + font-size: 16pt; + margin-top: 1.5em; +} + +h4 { + font-size: 14pt; + margin-top: 1.5em; +} + +hr { + color: #CCC; + background-color: #CCC; + height: 1px; + border: 0px solid blue; +} + +table { + background-color: #ffffff; + border-collapse: collapse; + border: 1px solid #aca899; +} + +th { + font-size: 14pt; + font-weight: bold; + text-align: left; + border: 1px solid #aca899; + padding: 2px; +} + +td { + background-color: #ffffff; + font-size: 14pt; + text-align: left; + vertical-align: top; + border: 1px solid #aca899; + padding: 2px; + margin: 0px; +} + +form { +} + +ul, ol { + list-style-position: outside; + padding-left: 20px; +} + +li { + margin-top: 2px; +} + +a { + text-decoration: none; + color: #0000ff; +} + +a:hover { + text-decoration: underline; +} + +em.u { + text-decoration: underline; + font-style: normal; +} + +.screenshot { + border: 1px outset #888; + padding: 10px; + margin: 10px 0px; +} + +.compareFeature { +} + +.compareY { + color: #050; +} + +.compareN { + color: #800; +} + +table.index { + width: 100%; + border: 0px; + padding: 0px; + margin: 0px; + border: 0px none; + border-collapse: collapse; +} + +/* width: 570px; + width: 190px; + */ + +td.index { + width: 33%; + border: 0px; + padding: 0px; + margin: 0px; + border: 0px none; + border-collapse: collapse; + vertical-align: top; +} + +span.ruleCompat { + color: darkred; +} + +span.ruleH2 { + color: green; +} diff --git a/h2/docs/html/systemtables.html b/h2/docs/html/systemtables.html new file mode 100644 index 0000000..a248786 --- /dev/null +++ b/h2/docs/html/systemtables.html @@ -0,0 +1,2034 @@ + + + + + + +System Tables + + + + + + + + + + + + + + + diff --git a/h2/docs/html/tutorial.html b/h2/docs/html/tutorial.html new file mode 100644 index 0000000..841a2be --- /dev/null +++ b/h2/docs/html/tutorial.html @@ -0,0 +1,1589 @@ + + + + + + + +Tutorial + + + + + + + + + + + + + + + + diff --git a/h2/docs/index.html b/h2/docs/index.html new file mode 100644 index 0000000..2e09c2f --- /dev/null +++ b/h2/docs/index.html @@ -0,0 +1,44 @@ + + + + + + + + +H2 Database Engine (redirect) + + + + + +

H2 Database Engine

+

+Welcome to H2, the free SQL database. The main feature of H2 are: +

+
    +
  • It is free to use for everybody, source code is included +
  • Written in Java, but also available as native executable +
  • JDBC and (partial) ODBC API +
  • Embedded and client/server modes +
  • Clustering is supported +
  • A web client is included +
+ +

No Javascript

+

+If you are not automatically redirected to the main page, then +Javascript is currently disabled or your browser does not support Javascript. +Some features (for example the integrated search) require Javascript. +Please enable Javascript, or go ahead without it: +

+H2 Database Engine +

+ + diff --git a/h2/docs/javadoc/allclasses-frame.html b/h2/docs/javadoc/allclasses-frame.html new file mode 100644 index 0000000..e072cdc --- /dev/null +++ b/h2/docs/javadoc/allclasses-frame.html @@ -0,0 +1,130 @@ + + + + + +All Classes + + + + + +

All Classes

+
+ +
+ + diff --git a/h2/docs/javadoc/allclasses-noframe.html b/h2/docs/javadoc/allclasses-noframe.html new file mode 100644 index 0000000..f1fdd06 --- /dev/null +++ b/h2/docs/javadoc/allclasses-noframe.html @@ -0,0 +1,130 @@ + + + + + +All Classes + + + + + +

All Classes

+
+ +
+ + diff --git a/h2/docs/javadoc/constant-values.html b/h2/docs/javadoc/constant-values.html new file mode 100644 index 0000000..5680a7a --- /dev/null +++ b/h2/docs/javadoc/constant-values.html @@ -0,0 +1,2678 @@ + + + + + +Constant Field Values + + + + + + + + + + + +
+

Constant Field Values

+

Contents

+ +
+
+ + +

org.h2.*

+ + +
+ + + + + + diff --git a/h2/docs/javadoc/deprecated-list.html b/h2/docs/javadoc/deprecated-list.html new file mode 100644 index 0000000..10996e5 --- /dev/null +++ b/h2/docs/javadoc/deprecated-list.html @@ -0,0 +1,213 @@ + + + + + +Deprecated List + + + + + + + + +
+ + + + + + + +
+ + +
+

Deprecated API

+

Contents

+ +
+ + +
+ + + + + + + +
+ + + + diff --git a/h2/docs/javadoc/help-doc.html b/h2/docs/javadoc/help-doc.html new file mode 100644 index 0000000..8f8d7c7 --- /dev/null +++ b/h2/docs/javadoc/help-doc.html @@ -0,0 +1,223 @@ + + + + + +API Help + + + + + + + + +
+ + + + + + + +
+ + +
+

How This API Document Is Organized

+
This API (Application Programming Interface) document has pages corresponding to the items in the navigation bar, described as follows.
+
+
+
    +
  • +

    Overview

    +

    The Overview page is the front page of this API document and provides a list of all packages with a summary for each. This page can also contain an overall description of the set of packages.

    +
  • +
  • +

    Package

    +

    Each package has a page that contains a list of its classes and interfaces, with a summary for each. This page can contain six categories:

    +
      +
    • Interfaces (italic)
    • +
    • Classes
    • +
    • Enums
    • +
    • Exceptions
    • +
    • Errors
    • +
    • Annotation Types
    • +
    +
  • +
  • +

    Class/Interface

    +

    Each class, interface, nested class and nested interface has its own separate page. Each of these pages has three sections consisting of a class/interface description, summary tables, and detailed member descriptions:

    +
      +
    • Class inheritance diagram
    • +
    • Direct Subclasses
    • +
    • All Known Subinterfaces
    • +
    • All Known Implementing Classes
    • +
    • Class/interface declaration
    • +
    • Class/interface description
    • +
    +
      +
    • Nested Class Summary
    • +
    • Field Summary
    • +
    • Constructor Summary
    • +
    • Method Summary
    • +
    +
      +
    • Field Detail
    • +
    • Constructor Detail
    • +
    • Method Detail
    • +
    +

    Each summary entry contains the first sentence from the detailed description for that item. The summary entries are alphabetical, while the detailed descriptions are in the order they appear in the source code. This preserves the logical groupings established by the programmer.

    +
  • +
  • +

    Annotation Type

    +

    Each annotation type has its own separate page with the following sections:

    +
      +
    • Annotation Type declaration
    • +
    • Annotation Type description
    • +
    • Required Element Summary
    • +
    • Optional Element Summary
    • +
    • Element Detail
    • +
    +
  • +
  • +

    Enum

    +

    Each enum has its own separate page with the following sections:

    +
      +
    • Enum declaration
    • +
    • Enum description
    • +
    • Enum Constant Summary
    • +
    • Enum Constant Detail
    • +
    +
  • +
  • +

    Tree (Class Hierarchy)

    +

    There is a Class Hierarchy page for all packages, plus a hierarchy for each package. Each hierarchy page contains a list of classes and a list of interfaces. The classes are organized by inheritance structure starting with java.lang.Object. The interfaces do not inherit from java.lang.Object.

    +
      +
    • When viewing the Overview page, clicking on "Tree" displays the hierarchy for all packages.
    • +
    • When viewing a particular package, class or interface page, clicking "Tree" displays the hierarchy for only that package.
    • +
    +
  • +
  • +

    Deprecated API

    +

    The Deprecated API page lists all of the API that have been deprecated. A deprecated API is not recommended for use, generally due to improvements, and a replacement API is usually given. Deprecated APIs may be removed in future implementations.

    +
  • +
  • +

    Index

    +

    The Index contains an alphabetic list of all classes, interfaces, constructors, methods, and fields.

    +
  • +
  • +

    Prev/Next

    +

    These links take you to the next or previous class, interface, package, or related page.

    +
  • +
  • +

    Frames/No Frames

    +

    These links show and hide the HTML frames. All pages are available with or without frames.

    +
  • +
  • +

    All Classes

    +

    The All Classes link shows all classes and interfaces except non-static nested types.

    +
  • +
  • +

    Serialized Form

    +

    Each serializable or externalizable class has a description of its serialization fields and methods. This information is of interest to re-implementors, not to developers using the API. While there is no link in the navigation bar, you can get to this information by going to any serialized class and clicking "Serialized Form" in the "See also" section of the class description.

    +
  • +
  • +

    Constant Field Values

    +

    The Constant Field Values page lists the static final fields and their values.

    +
  • +
+This help file applies to API documentation generated using the standard doclet.
+ +
+ + + + + + + +
+ + + + diff --git a/h2/docs/javadoc/index-all.html b/h2/docs/javadoc/index-all.html new file mode 100644 index 0000000..e4f01cb --- /dev/null +++ b/h2/docs/javadoc/index-all.html @@ -0,0 +1,10585 @@ + + + + + +Index + + + + + + + + +
+ + + + + + + +
+ + +
A B C D E F G H I J K L M N O P Q R S T U V W Z  + + +

A

+
+
abort(Executor) - Method in class org.h2.jdbc.JdbcConnection
+
+
[Not supported]
+
+
absolute(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Moves the current position to a specific row.
+
+
absolute(int) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
ACCESS_DENIED_TO_CLASS_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90134 is thrown when + trying to load a Java class that is not part of the allowed classes.
+
+
actionPerformed(ActionEvent) - Method in class org.h2.tools.GUIConsole
+
+
INTERNAL
+
+
add(Object) - Method in interface org.h2.api.Aggregate
+
+
This method is called once for each row.
+
+
add(Object) - Method in interface org.h2.api.AggregateFunction
+
+
This method is called once for each row.
+
+
addBatch(String) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Calling this method is not legal on a PreparedStatement.
+
+
addBatch() - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Adds the current settings to the batch.
+
+
addBatch(String) - Method in class org.h2.jdbc.JdbcStatement
+
+
Adds a statement to the batch.
+
+
addColumn(String, int, int, int) - Method in class org.h2.tools.SimpleResultSet
+
+
Adds a column to the result set.
+
+
addColumn(String, int, String, int, int) - Method in class org.h2.tools.SimpleResultSet
+
+
Adds a column to the result set.
+
+
addConnectionEventListener(ConnectionEventListener) - Method in class org.h2.jdbcx.JdbcXAConnection
+
+
Register a new listener for the connection.
+
+
addDatabaseObject(SessionLocal, DbObject) - Method in class org.h2.engine.Database
+
+
Add an object to the database.
+
+
addLocalTempTable(Table) - Method in class org.h2.engine.SessionLocal
+
+
Add a local temporary table to this session.
+
+
addLocalTempTableConstraint(Constraint) - Method in class org.h2.engine.SessionLocal
+
+
Add a local temporary constraint to this session.
+
+
addLocalTempTableIndex(Index) - Method in class org.h2.engine.SessionLocal
+
+
Add a local temporary index to this session.
+
+
addProcedure(Procedure) - Method in class org.h2.engine.SessionLocal
+
+
Add a procedure to this session.
+
+
addRow(Object...) - Method in class org.h2.tools.SimpleResultSet
+
+
Add a new row to the result set.
+
+
addSavepoint(String) - Method in class org.h2.engine.SessionLocal
+
+
Create a savepoint that is linked to the current log position.
+
+
addSchemaObject(SessionLocal, SchemaObject) - Method in class org.h2.engine.Database
+
+
Add a schema object to the database.
+
+
addStatementEventListener(StatementEventListener) - Method in class org.h2.jdbcx.JdbcXAConnection
+
+
[Not supported] Add a statement event listener.
+
+
addTemporaryLob(ValueLob) - Method in class org.h2.engine.Session
+
+
Add a temporary LOB, which is closed when the session commits.
+
+
addTemporaryLob(ValueLob) - Method in class org.h2.engine.SessionLocal
+
 
+
addTemporaryLob(ValueLob) - Method in class org.h2.engine.SessionRemote
+
 
+
addWords(FullTextSettings, Set<String>, Reader) - Static method in class org.h2.fulltext.FullText
+
+
Add all words in the given text to the hash set.
+
+
addWords(FullTextSettings, Set<String>, String) - Static method in class org.h2.fulltext.FullText
+
+
Add all words in the given text to the hash set.
+
+
ADMIN_RIGHTS_REQUIRED - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90040 is thrown when + a user that is not administrator tries to execute a statement + that requires admin privileges.
+
+
afterLast() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Moves the current position to after the last row, that means after the + end.
+
+
afterLast() - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
Aggregate - Interface in org.h2.api
+
+
A user-defined aggregate function needs to implement this interface.
+
+
AGGREGATE - Static variable in class org.h2.engine.DbObject
+
+
This object is a user-defined aggregate function.
+
+
AGGREGATE_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90132 is thrown when + trying to drop a user-defined aggregate function that doesn't exist.
+
+
AggregateFunction - Interface in org.h2.api
+
+
A user-defined aggregate function needs to implement this interface.
+
+
aliasColumnName - Variable in class org.h2.engine.Mode
+
+
When enabled, aliased columns (as in SELECT ID AS I FROM TEST) return the + alias (I in this case) in ResultSetMetaData.getColumnName() and 'null' in + getTableName().
+
+
ALL - Static variable in class org.h2.engine.Right
+
+
The right bit mask that means: select, insert, update, delete, and update + for this object is allowed.
+
+
allNumericTypesHavePrecision - Variable in class org.h2.engine.Mode
+
+
If true all numeric data types may have precision and 'UNSIGNED' + clause.
+
+
allocateObjectId() - Method in class org.h2.engine.Database
+
+
Allocate a new object id.
+
+
ALLOW_LITERALS_ALL - Static variable in class org.h2.engine.Constants
+
+
Constant meaning both numbers and text is allowed in SQL statements.
+
+
ALLOW_LITERALS_NONE - Static variable in class org.h2.engine.Constants
+
+
Constant meaning no literals are allowed in SQL statements.
+
+
ALLOW_LITERALS_NUMBERS - Static variable in class org.h2.engine.Constants
+
+
Constant meaning only numbers are allowed in SQL statements (but no + texts).
+
+
allowDB2TimestampFormat - Variable in class org.h2.engine.Mode
+
+
Whether DB2 TIMESTAMP formats are allowed.
+
+
ALLOWED_CLASSES - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.allowedClasses (default: *).
+
+
allowEmptyInPredicate - Variable in class org.h2.engine.Mode
+
+
Whether IN predicate may have an empty value list.
+
+
allowEmptySchemaValuesAsDefaultSchema - Variable in class org.h2.engine.Mode
+
+
If true constructs like 'CREATE TABLE CATALOG..TABLE_NAME' are allowed, + the default schema is used.
+
+
allowNonRepeatableRead() - Method in enum org.h2.engine.IsolationLevel
+
+
Returns whether a non-repeatable read phenomena is allowed.
+
+
allowPlusForStringConcat - Variable in class org.h2.engine.Mode
+
+
Text can be concatenated using '+'.
+
+
allowUnrelatedOrderByExpressionsInDistinctQueries - Variable in class org.h2.engine.Mode
+
+
If true unrelated ORDER BY expression are allowed in DISTINCT + queries, if false they are disallowed.
+
+
allowUsingFromClauseInUpdateStatement - Variable in class org.h2.engine.Mode
+
+
If true, allow using from clause in update statement.
+
+
allProceduresAreCallable() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Checks if all procedures callable.
+
+
allTablesAreSelectable() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Checks if it possible to query all tables returned by getTables.
+
+
ALTER_ANY_SCHEMA - Static variable in class org.h2.engine.Right
+
+
The right bit mask that means: create/alter/drop schema is allowed.
+
+
alterTableExtensionsMySQL - Variable in class org.h2.engine.Mode
+
+
If true some additional non-standard ALTER TABLE commands are allowed.
+
+
alterTableModifyColumn - Variable in class org.h2.engine.Mode
+
+
If true non-standard ALTER TABLE MODIFY COLUMN is allowed.
+
+
AMBIGUOUS_COLUMN_NAME_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90059 is thrown when + a query contains a column that could belong to multiple tables.
+
+
analyzeAuto - Variable in class org.h2.engine.DbSettings
+
+
Database setting ANALYZE_AUTO (default: 2000).
+
+
analyzeSample - Variable in class org.h2.engine.DbSettings
+
+
Database setting ANALYZE_SAMPLE (default: 10000).
+
+
areEqual(Value, Value) - Method in class org.h2.engine.SessionLocal
+
+
Check if two values are equal with the current comparison mode.
+
+
array(H2Type) - Static method in class org.h2.api.H2Type
+
+
Returns ARRAY data type with the specified component type.
+
+
ARRAY_ELEMENT_ERROR_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 22034 is thrown when an + attempt is made to read non-existing element of an array.
+
+
asString(Object, int) - Static method in class org.h2.fulltext.FullText
+
+
INTERNAL.
+
+
AUTH_CONFIG_FILE - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.authConfigFile + (default: null).
+
+
AUTHENTICATOR_NOT_AVAILABLE - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90144 is thrown when + user trying to login into a database with AUTHREALM set and + the target database doesn't have an authenticator defined
+
+
AUTO - Static variable in class org.h2.engine.GeneratedKeysMode
+
+
Generated keys should be configured automatically.
+
+
autoCommitFailureClosesAllResultSets() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether an exception while auto commit is on closes all result + sets.
+
+
autoCommitIfCluster() - Method in class org.h2.engine.SessionRemote
+
+
Calls COMMIT if the session is in cluster mode.
+
+
autoCompactFillRate - Variable in class org.h2.engine.DbSettings
+
+
Database setting AUTO_COMPACT_FILL_RATE + (default: 90, which means 90%, 0 disables auto-compacting).
+
+
autoIncrementClause - Variable in class org.h2.engine.Mode
+
+
Whether MySQL-style AUTO_INCREMENT clause is supported.
+
+
+ + + +

B

+
+
Backup - Class in org.h2.tools
+
+
Creates a backup of a database.
+
+
Backup() - Constructor for class org.h2.tools.Backup
+
 
+
before - Variable in class org.h2.tools.TriggerAdapter
+
+
Whether the fire method is called before or after the operation is + performed.
+
+
beforeFirst() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Moves the current position to before the first row, that means resets the + result set.
+
+
beforeFirst() - Method in class org.h2.tools.SimpleResultSet
+
+
Moves the current position to before the first row, that means the result + set is reset.
+
+
begin() - Method in class org.h2.engine.SessionLocal
+
+
Begin a transaction.
+
+
BIGINT - Static variable in class org.h2.api.H2Type
+
+
The BIGINT data type.
+
+
BINARY - Static variable in class org.h2.api.H2Type
+
+
The BINARY data type.
+
+
BIND_ADDRESS - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.bindAddress (default: null).
+
+
BLOB - Static variable in class org.h2.api.H2Type
+
+
The BINARY LARGE OBJECT data type.
+
+
BLOB_SEARCH - Static variable in class org.h2.engine.Constants
+
+
Whether searching in Blob values should be supported.
+
+
BOOLEAN - Static variable in class org.h2.api.H2Type
+
+
The BOOLEAN data type
+
+
BUILD_DATE - Static variable in class org.h2.engine.Constants
+
+
The build date is updated for each public release.
+
+
BUILD_ID - Static variable in class org.h2.engine.Constants
+
+
Sequential version number.
+
+
BUILD_SNAPSHOT - Static variable in class org.h2.engine.Constants
+
+
Whether this is a snapshot version.
+
+
BUILD_VENDOR_AND_VERSION - Static variable in class org.h2.engine.Constants
+
+
If H2 is compiled to be included in a product, this should be set to + a unique vendor id (to distinguish from official releases).
+
+
buildUser(AuthenticationInfo, Database, boolean) - Static method in class org.h2.engine.UserBuilder
+
+
Build the database user starting from authentication informations.
+
+
+ + + +

C

+
+
CACHE_MIN_RECORDS - Static variable in class org.h2.engine.Constants
+
+
The minimum number of entries to keep in the cache.
+
+
CACHE_TYPE_DEFAULT - Static variable in class org.h2.engine.Constants
+
+
The default cache type.
+
+
CAN_ONLY_ASSIGN_TO_VARIABLE_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90137 is thrown when + trying to assign a value to something that is not a variable.
+
+
cancel() - Method in class org.h2.engine.Session
+
+
Cancel the current or next command (called when closing a connection).
+
+
cancel() - Method in class org.h2.engine.SessionLocal
+
 
+
cancel() - Method in class org.h2.engine.SessionRemote
+
 
+
cancel() - Method in class org.h2.jdbc.JdbcStatement
+
+
Cancels a currently running statement.
+
+
cancelRowUpdates() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Cancels updating a row.
+
+
cancelRowUpdates() - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
cancelStatement(int) - Method in class org.h2.engine.SessionRemote
+
+
Cancel the statement with the given id.
+
+
CANNOT_CHANGE_SETTING_WHEN_OPEN_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90133 is thrown when + trying to change a specific database property while the database is + already open.
+
+
CANNOT_DROP_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90107 is thrown when + trying to drop an object because another object would become invalid.
+
+
CANNOT_DROP_CURRENT_USER - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90019 is thrown when + trying to drop the current user, if there are no other admin users.
+
+
CANNOT_DROP_LAST_COLUMN - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90084 is thrown when + trying to drop the last column of a table.
+
+
CANNOT_DROP_TABLE_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90118 is thrown when + trying to drop a table can not be dropped.
+
+
CANNOT_MIX_INDEXED_AND_UNINDEXED_PARAMS - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90123 is thrown when + trying mix regular parameters and indexed parameters in the same + statement.
+
+
CANNOT_TRUNCATE_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90106 is thrown when + trying to truncate a table that can not be truncated.
+
+
caseInsensitiveIdentifiers - Variable in class org.h2.engine.DbSettings
+
+
Database setting CASE_INSENSITIVE_IDENTIFIERS (default: + false).
+
+
caseInsensitiveIdentifiers - Variable in class org.h2.engine.Session.StaticSettings
+
+
Whether all identifiers are case insensitive.
+
+
CastDataProvider - Interface in org.h2.engine
+
+
Provides information for type casts and comparison operations.
+
+
CHANGE_ID - Static variable in class org.h2.engine.SessionRemote
+
 
+
ChangeFileEncryption - Class in org.h2.tools
+
+
Allows changing the database file encryption password or algorithm.
+
+
ChangeFileEncryption() - Constructor for class org.h2.tools.ChangeFileEncryption
+
 
+
CHAR - Static variable in class org.h2.api.H2Type
+
+
The CHARACTER data type.
+
+
charAndByteLengthUnits - Variable in class org.h2.engine.Mode
+
+
If true 'CHAR' and 'BYTE' length units are allowed.
+
+
charPadding - Variable in class org.h2.engine.Mode
+
+
How to pad or trim CHAR values.
+
+
CHECK - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.check + (default: true for JDK/JRE, false for Android).
+
+
CHECK_CONSTRAINT_INVALID - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 23514 is thrown when + evaluation of a check constraint resulted in an error.
+
+
CHECK_CONSTRAINT_VIOLATED_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 23513 is thrown when + a check constraint is violated.
+
+
checkAdmin() - Method in class org.h2.engine.User
+
+
Checks if this user has admin rights.
+
+
checkCanceled() - Method in class org.h2.engine.SessionLocal
+
+
Check if the current transaction is canceled by calling + Statement.cancel() or because a session timeout was set and expired.
+
+
checkClosed() - Method in class org.h2.engine.SessionRemote
+
+
Check if this session is closed and throws an exception if so.
+
+
checkClosed() - Method in class org.h2.jdbc.JdbcConnection
+
+
INTERNAL.
+
+
checkOwnsNoSchemas() - Method in class org.h2.engine.RightOwner
+
+
Check that this right owner does not own any schema.
+
+
checkpoint() - Method in class org.h2.engine.Database
+
+
Flush all changes and open a new transaction log.
+
+
checkPowerOff() - Method in class org.h2.engine.Database
+
 
+
checkPowerOff() - Method in class org.h2.engine.SessionRemote
+
 
+
checkPowerOff() - Method in class org.h2.tools.Recover
+
+
INTERNAL
+
+
checkRename() - Method in class org.h2.engine.Comment
+
 
+
checkRename() - Method in class org.h2.engine.DbObject
+
+
Check if renaming is allowed.
+
+
checkRename() - Method in class org.h2.engine.Right
+
 
+
checkRename() - Method in class org.h2.engine.Setting
+
 
+
checkSchemaAdmin() - Method in class org.h2.engine.User
+
+
Checks if this user has schema admin rights for every schema.
+
+
checkSchemaOwner(Schema) - Method in class org.h2.engine.User
+
+
Checks if this user has schema owner rights for the specified schema.
+
+
checkTableRight(Table, int) - Method in class org.h2.engine.User
+
+
Checks that this user has the given rights for the specified table.
+
+
checkWritingAllowed() - Method in class org.h2.engine.Database
+
 
+
checkWritingAllowed() - Method in class org.h2.engine.SessionRemote
+
 
+
checkWritingAllowed() - Method in class org.h2.tools.Recover
+
+
INTERNAL
+
+
CLASS_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90086 is thrown when + a class can not be loaded because it is not in the classpath + or because a related class is not in the classpath.
+
+
cleanAuthenticationInfo() - Method in class org.h2.engine.ConnectionInfo
+
+
Clear authentication properties.
+
+
clearBatch() - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Clears the batch.
+
+
clearBatch() - Method in class org.h2.jdbc.JdbcStatement
+
+
Clears the batch.
+
+
clearParameters() - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Clears all parameters.
+
+
clearViewIndexCache() - Method in class org.h2.engine.SessionLocal
+
+
Clear the view cache for this session.
+
+
clearWarnings() - Method in class org.h2.jdbc.JdbcConnection
+
+
Clears all warnings.
+
+
clearWarnings() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Clears all warnings.
+
+
clearWarnings() - Method in class org.h2.jdbc.JdbcStatement
+
+
Clears all warnings.
+
+
clearWarnings() - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
CLIENT_TRACE_DIRECTORY - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.clientTraceDirectory (default: + trace.db/).
+
+
CLOB - Static variable in class org.h2.api.H2Type
+
+
The CHARACTER LARGE OBJECT data type.
+
+
clone() - Method in class org.h2.engine.ConnectionInfo
+
 
+
close() - Method in interface org.h2.api.Trigger
+
+
This method is called when the database is closed.
+
+
close() - Method in class org.h2.engine.Session
+
+
Roll back pending transactions and close the session.
+
+
close() - Method in class org.h2.engine.SessionLocal
+
 
+
close() - Method in class org.h2.engine.SessionRemote
+
 
+
close() - Method in class org.h2.fulltext.FullText.FullTextTrigger
+
+
INTERNAL
+
+
close() - Method in class org.h2.fulltext.FullTextLucene.FullTextTrigger
+
+
INTERNAL
+
+
close() - Method in class org.h2.jdbc.JdbcConnection
+
+
Closes this connection.
+
+
close() - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Closes this statement.
+
+
close() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Closes the result set.
+
+
close() - Method in class org.h2.jdbc.JdbcStatement
+
+
Closes this statement.
+
+
close() - Method in class org.h2.jdbcx.JdbcXAConnection
+
+
Close the physical connection.
+
+
close() - Method in class org.h2.tools.Csv
+
+
INTERNAL
+
+
close() - Method in class org.h2.tools.SimpleResultSet
+
+
Closes the result set and releases the resources.
+
+
close() - Method in interface org.h2.tools.SimpleRowSource
+
+
Close the row source.
+
+
closeAll() - Static method in class org.h2.fulltext.FullText
+
+
INTERNAL + Close all fulltext settings, freeing up memory.
+
+
closeOldResultSet() - Method in class org.h2.jdbc.JdbcStatement
+
+
INTERNAL.
+
+
closeOnCompletion() - Method in class org.h2.jdbc.JdbcStatement
+
+
Specifies that this statement will be closed when its dependent result + set is closed.
+
+
closingDatabase() - Method in interface org.h2.api.DatabaseEventListener
+
+
This method is called before the database is closed normally.
+
+
CLUSTER_ERROR_DATABASE_RUNS_ALONE - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90093 is thrown when + trying to connect to a clustered database that runs in standalone + mode.
+
+
CLUSTER_ERROR_DATABASE_RUNS_CLUSTERED_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90094 is thrown when + trying to connect to a clustered database that runs together with a + different cluster node setting than what is used when trying to connect.
+
+
CLUSTERING_DISABLED - Static variable in class org.h2.engine.Constants
+
+
The value of the cluster setting if clustering is disabled.
+
+
CLUSTERING_ENABLED - Static variable in class org.h2.engine.Constants
+
+
The value of the cluster setting if clustering is enabled (the actual + value is checked later).
+
+
COLLATION_CHANGE_WITH_DATA_TABLE_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90089 is thrown when + trying to change the collation while there was already data in + the database.
+
+
COLLATOR_CACHE_SIZE - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.collatorCacheSize (default: 3 + 2000).
+
+
COLUMN_ALIAS_IS_NOT_SPECIFIED_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90156 is thrown when trying to create a + view or a table from a select and some expression doesn't have a column + name or alias when it is required by a compatibility mode.
+
+
COLUMN_CONTAINS_NULL_VALUES_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90081 is thrown when + trying to alter a column to not allow NULL, if there + is already data in the table where this column is NULL.
+
+
COLUMN_COUNT_DOES_NOT_MATCH - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 21002 is thrown when the number of + columns does not match.
+
+
COLUMN_IS_PART_OF_INDEX_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90075 is thrown when + trying to alter a table and allow null for a column that is part of a + primary key or hash index.
+
+
COLUMN_IS_REFERENCED_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90083 is thrown when + trying to drop a column that is part of a constraint.
+
+
COLUMN_MUST_NOT_BE_NULLABLE_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90023 is thrown when trying to set a + primary key on a nullable column or when trying to drop NOT NULL + constraint on primary key or identity column.
+
+
COLUMN_NAMES - Static variable in class org.h2.engine.GeneratedKeysMode
+
+
Use specified column names to return generated keys from.
+
+
COLUMN_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 42122 is thrown when + referencing an non-existing column.
+
+
COLUMN_NUMBERS - Static variable in class org.h2.engine.GeneratedKeysMode
+
+
Use specified column indices to return generated keys from.
+
+
columns - Variable in class org.h2.fulltext.IndexInfo
+
+
The column names.
+
+
command - Variable in class org.h2.jdbc.JdbcPreparedStatement
+
 
+
COMMAND_CLOSE - Static variable in class org.h2.engine.SessionRemote
+
 
+
COMMAND_COMMIT - Static variable in class org.h2.engine.SessionRemote
+
 
+
COMMAND_EXECUTE_QUERY - Static variable in class org.h2.engine.SessionRemote
+
 
+
COMMAND_EXECUTE_UPDATE - Static variable in class org.h2.engine.SessionRemote
+
 
+
COMMAND_GET_META_DATA - Static variable in class org.h2.engine.SessionRemote
+
 
+
Comment - Class in org.h2.engine
+
+
Represents a database object comment.
+
+
Comment(Database, int, DbObject) - Constructor for class org.h2.engine.Comment
+
 
+
COMMENT - Static variable in class org.h2.engine.DbObject
+
+
This object is a comment.
+
+
comment - Variable in class org.h2.engine.DbObject
+
+
The comment (if set).
+
+
commit(boolean) - Method in class org.h2.engine.SessionLocal
+
+
Commit the current transaction.
+
+
commit() - Method in class org.h2.jdbc.JdbcConnection
+
+
Commits the current transaction.
+
+
commit(Xid, boolean) - Method in class org.h2.jdbcx.JdbcXAConnection
+
+
Commit a transaction.
+
+
COMMIT_ROLLBACK_NOT_ALLOWED - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90058 is thrown when trying to call + commit or rollback inside a trigger, or when trying to call a method + inside a trigger that implicitly commits the current transaction, if an + object is locked.
+
+
compare(Value, Value) - Method in class org.h2.engine.SessionLocal
+
+
Compare two values with the current comparison mode.
+
+
compare(long[], long[]) - Method in class org.h2.tools.MultiDimension
+
 
+
compareTo(MetaRecord) - Method in class org.h2.engine.MetaRecord
+
+
Sort the list of meta records by 'create order'.
+
+
compareTypeSafe(Value, Value) - Method in class org.h2.engine.SessionLocal
+
+
Compare two values with the current comparison mode.
+
+
compareWithNull(Value, Value, boolean) - Method in class org.h2.engine.SessionLocal
+
+
Compare two values with the current comparison mode.
+
+
compress(byte[], String) - Method in class org.h2.tools.CompressTool
+
+
Compressed the data using the specified algorithm.
+
+
compressData - Variable in class org.h2.engine.DbSettings
+
+
Database setting COMPRESS + (default: false).
+
+
COMPRESSION_ERROR - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90104 is thrown when + the data can not be de-compressed.
+
+
CompressTool - Class in org.h2.tools
+
+
A tool to losslessly compress data, and expand the compressed data again.
+
+
CONCURRENT_UPDATE_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90131 is thrown when using multi version + concurrency control, and trying to update the same row from within two + connections at the same time, or trying to insert two rows with the same + key from two connections.
+
+
conn - Variable in class org.h2.jdbc.JdbcStatement
+
 
+
CONN_URL_COLUMNLIST - Static variable in class org.h2.engine.Constants
+
+
The database URL used when calling a function if only the column list + should be returned.
+
+
CONN_URL_INTERNAL - Static variable in class org.h2.engine.Constants
+
+
The database URL used when calling a function if the data should be + returned.
+
+
connectEmbeddedOrServer(boolean) - Method in class org.h2.engine.SessionRemote
+
+
Open a new (remote or embedded) session.
+
+
CONNECTION_BROKEN_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90067 is thrown when the client could + not connect to the database, or if the connection was lost.
+
+
connectionClosed(ConnectionEvent) - Method in class org.h2.jdbcx.JdbcConnectionPool
+
+
INTERNAL
+
+
connectionErrorOccurred(ConnectionEvent) - Method in class org.h2.jdbcx.JdbcConnectionPool
+
+
INTERNAL
+
+
ConnectionInfo - Class in org.h2.engine
+
+
Encapsulates the connection settings, including user name and password.
+
+
ConnectionInfo(String) - Constructor for class org.h2.engine.ConnectionInfo
+
+
Create a connection info object.
+
+
ConnectionInfo(String, Properties, String, Object) - Constructor for class org.h2.engine.ConnectionInfo
+
+
Create a connection info object.
+
+
Console - Class in org.h2.tools
+
+
Starts the H2 Console (web-) server, as well as the TCP and PG server.
+
+
Console() - Constructor for class org.h2.tools.Console
+
 
+
CONSOLE_MAX_PROCEDURES_LIST_COLUMNS - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.consoleProcedureColumns + (default: 500).
+
+
CONSOLE_MAX_TABLES_LIST_COLUMNS - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.consoleTableColumns + (default: 500).
+
+
CONSOLE_MAX_TABLES_LIST_INDEXES - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.consoleTableIndexes + (default: 100).
+
+
CONSOLE_STREAM - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.consoleStream (default: true).
+
+
CONSOLE_TIMEOUT - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.consoleTimeout (default: 1800000).
+
+
CONSTANT - Static variable in class org.h2.engine.DbObject
+
+
This object is a constant.
+
+
CONSTANT_ALREADY_EXISTS_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90114 is thrown when + trying to create a constant if a constant with this name already exists.
+
+
CONSTANT_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90115 is thrown when + trying to drop a constant that does not exists.
+
+
Constants - Class in org.h2.engine
+
+
Constants are fixed values that are used in the whole database code.
+
+
CONSTRAINT - Static variable in class org.h2.engine.DbObject
+
+
This object is a constraint (check constraint, unique constraint, or + referential constraint).
+
+
CONSTRAINT_ALREADY_EXISTS_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90045 is thrown when trying to create a + constraint if an object with this name already exists.
+
+
CONSTRAINT_IS_USED_BY_CONSTRAINT_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90152 is thrown when trying to manually + drop a unique or primary key constraint that is referenced by a foreign + key constraint without a CASCADE clause.
+
+
CONSTRAINT_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90057 is thrown when + trying to drop a constraint that does not exist.
+
+
containsKey(String) - Method in class org.h2.engine.SettingsBase
+
+
Check if the settings contains the given key.
+
+
containsUncommitted() - Method in class org.h2.engine.SessionLocal
+
+
Whether the session contains any uncommitted changes.
+
+
convertException(Exception) - Static method in class org.h2.fulltext.FullTextLucene
+
+
Convert an exception to a fulltext exception.
+
+
convertOnlyToSmallerScale - Variable in class org.h2.engine.Mode
+
+
When converting the scale of decimal data, the number is only converted + if the new scale is smaller than the current scale.
+
+
ConvertTraceFile - Class in org.h2.tools
+
+
Converts a .trace.db file to a SQL script and Java source code.
+
+
ConvertTraceFile() - Constructor for class org.h2.tools.ConvertTraceFile
+
 
+
COST_ROW_OFFSET - Static variable in class org.h2.engine.Constants
+
+
The cost is calculated on rowcount + this offset, + to avoid using the wrong or no index if the table + contains no rows _currently_ (when preparing the statement)
+
+
count - Variable in class org.h2.engine.QueryStatisticsData.QueryEntry
+
+
The number of times the statement was executed.
+
+
create(ConnectionPoolDataSource) - Static method in class org.h2.jdbcx.JdbcConnectionPool
+
+
Constructs a new connection pool.
+
+
create(String, String, String) - Static method in class org.h2.jdbcx.JdbcConnectionPool
+
+
Constructs a new connection pool for H2 databases.
+
+
createArrayOf(String, Object[]) - Method in class org.h2.jdbc.JdbcConnection
+
+
Create a new Array object.
+
+
createBlob() - Method in class org.h2.jdbc.JdbcConnection
+
+
Create a new empty Blob object.
+
+
createClob() - Method in class org.h2.jdbc.JdbcConnection
+
+
Create a new empty Clob object.
+
+
CreateCluster - Class in org.h2.tools
+
+
Creates a cluster from a stand-alone database.
+
+
CreateCluster() - Constructor for class org.h2.tools.CreateCluster
+
 
+
createConnection(boolean) - Method in class org.h2.engine.SessionLocal
+
+
Create an internal connection.
+
+
createIndex(Connection, String, String, String) - Static method in class org.h2.fulltext.FullText
+
+
Create a new full text index for a table and column list.
+
+
createIndex(Connection, String, String, String) - Static method in class org.h2.fulltext.FullTextLucene
+
+
Create a new full text index for a table and column list.
+
+
createNClob() - Method in class org.h2.jdbc.JdbcConnection
+
+
Create a new empty NClob object.
+
+
createPgServer(String...) - Static method in class org.h2.tools.Server
+
+
Create a new PG server, but does not start it yet.
+
+
createResultSet(boolean) - Static method in class org.h2.fulltext.FullText
+
+
Create an empty search result and initialize the columns.
+
+
createSession(ConnectionInfo) - Static method in class org.h2.engine.Engine
+
+
Open a database connection with the given connection information.
+
+
createSQLXML() - Method in class org.h2.jdbc.JdbcConnection
+
+
Create a new SQLXML object with no data.
+
+
createStatement() - Method in class org.h2.jdbc.JdbcConnection
+
+
Creates a new statement.
+
+
createStatement(int, int) - Method in class org.h2.jdbc.JdbcConnection
+
+
Creates a statement with the specified result set type and concurrency.
+
+
createStatement(int, int, int) - Method in class org.h2.jdbc.JdbcConnection
+
+
Creates a statement with the specified result set type, concurrency, and + holdability.
+
+
createStruct(String, Object[]) - Method in class org.h2.jdbc.JdbcConnection
+
+
[Not supported] Create a new empty Struct object.
+
+
createTable(CreateTableData) - Method in interface org.h2.api.TableEngine
+
+
Create new table.
+
+
createTcpServer(String...) - Static method in class org.h2.tools.Server
+
+
Create a new TCP server, but does not start it yet.
+
+
createUniqueConstraintForReferencedColumns - Variable in class org.h2.engine.Mode
+
+
If true, referential constraints will create a unique constraint + on referenced columns if it doesn't exist instead of throwing an + exception.
+
+
createWebServer(String...) - Static method in class org.h2.tools.Server
+
+
Create a new web server, but does not start it yet.
+
+
CredentialsValidator - Interface in org.h2.api
+
+
A class that implement this interface can be used to validate credentials + provided by client.
+
+
Csv - Class in org.h2.tools
+
+
A facility to read from and write to CSV (comma separated values) files.
+
+
Csv() - Constructor for class org.h2.tools.Csv
+
 
+
CURRENT_SEQUENCE_VALUE_IS_NOT_DEFINED_IN_SESSION_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90148 is thrown when trying to access + the current value of a sequence before execution of NEXT VALUE FOR + sequenceName in the current session.
+
+
currentTimestamp() - Method in interface org.h2.engine.CastDataProvider
+
+
Returns the current timestamp with maximum resolution.
+
+
currentTimestamp() - Method in class org.h2.engine.Database
+
 
+
currentTimestamp() - Method in class org.h2.engine.SessionLocal
+
 
+
currentTimestamp() - Method in class org.h2.engine.SessionRemote
+
 
+
currentTimestamp() - Method in class org.h2.jdbc.JdbcConnection
+
 
+
currentTimeZone() - Method in interface org.h2.engine.CastDataProvider
+
+
Returns the current time zone.
+
+
currentTimeZone() - Method in class org.h2.engine.Database
+
 
+
currentTimeZone() - Method in class org.h2.engine.SessionLocal
+
 
+
currentTimeZone() - Method in class org.h2.engine.SessionRemote
+
 
+
currentTimeZone() - Method in class org.h2.jdbc.JdbcConnection
+
 
+
+ + + +

D

+
+
DATA_CONVERSION_ERROR_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 22018 is thrown when + trying to convert a value to a data type where the conversion is + undefined, or when an error occurred trying to convert.
+
+
Database - Class in org.h2.engine
+
+
There is one database object per open database.
+
+
Database(ConnectionInfo, String) - Constructor for class org.h2.engine.Database
+
 
+
database - Variable in class org.h2.engine.DbObject
+
+
The database.
+
+
DATABASE_ALREADY_OPEN_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90020 is thrown when trying to open a + database in embedded mode if this database is already in use in another + process (or in a different class loader).
+
+
DATABASE_CALLED_AT_SHUTDOWN - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90121 is thrown when + a database operation is started while the virtual machine exits + (for example in a shutdown hook), or when the session is closed.
+
+
DATABASE_IS_CLOSED - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90098 is thrown when the database has + been closed, for example because the system ran out of memory or because + the self-destruction counter has reached zero.
+
+
DATABASE_IS_IN_EXCLUSIVE_MODE - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90135 is thrown when + trying to open a connection to a database that is currently open + in exclusive mode.
+
+
DATABASE_IS_NOT_PERSISTENT - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90126 is thrown when + trying to call the BACKUP statement for an in-memory database.
+
+
DATABASE_IS_READ_ONLY - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90097 is thrown when + trying to delete or update a database if it is open in read-only mode.
+
+
DATABASE_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90013 is thrown when when trying to access + a database object with a catalog name that does not match the database + name.
+
+
DATABASE_NOT_FOUND_WITH_IF_EXISTS_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90146 is thrown when trying to open a + database that does not exist using the flag IFEXISTS=TRUE
+
+
DatabaseEventListener - Interface in org.h2.api
+
+
A class that implements this interface can get notified about exceptions + and other events.
+
+
databaseToLower - Variable in class org.h2.engine.DbSettings
+
+
Database setting DATABASE_TO_LOWER (default: false).
+
+
databaseToLower - Variable in class org.h2.engine.Session.StaticSettings
+
+
Whether unquoted identifiers are converted to lower case.
+
+
databaseToUpper - Variable in class org.h2.engine.DbSettings
+
+
Database setting DATABASE_TO_UPPER (default: true).
+
+
databaseToUpper - Variable in class org.h2.engine.Session.StaticSettings
+
+
Whether unquoted identifiers are converted to upper case.
+
+
dataDefinitionCausesTransactionCommit() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether CREATE/DROP commit an open transaction.
+
+
dataDefinitionIgnoredInTransactions() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether CREATE/DROP do not affect transactions.
+
+
DATASOURCE_TRACE_LEVEL - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.dataSourceTraceLevel (default: 1).
+
+
DATE - Static variable in class org.h2.api.H2Type
+
+
The DATE data type.
+
+
dateTimeValueWithinTransaction - Variable in class org.h2.engine.Mode
+
+
If true, datetime value function return the same value within a + transaction, if false datetime value functions return the same + value within a command.
+
+
dbCloseOnExit - Variable in class org.h2.engine.DbSettings
+
+
Database setting DB_CLOSE_ON_EXIT (default: true).
+
+
DbObject - Class in org.h2.engine
+
+
A database object such as a table, an index, or a user.
+
+
DbObject(Database, int, String, int) - Constructor for class org.h2.engine.DbObject
+
+
Initialize some attributes of this object.
+
+
DbSettings - Class in org.h2.engine
+
+
This class contains various database-level settings.
+
+
DEADLOCK_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 40001 is thrown when + the database engine has detected a deadlock.
+
+
DEADLOCK_CHECK - Static variable in class org.h2.engine.Constants
+
+
The number of milliseconds after which to check for a deadlock if locking + is not successful.
+
+
DECFLOAT - Static variable in class org.h2.api.H2Type
+
+
The DECFLOAT data type.
+
+
decimalSequences - Variable in class org.h2.engine.Mode
+
+
If true NEXT VALUE FOR SEQUENCE, CURRENT VALUE FOR SEQUENCE, + SEQUENCE.NEXTVAL, and SEQUENCE.CURRVAL return values with DECIMAL/NUMERIC + data type instead of BIGINT.
+
+
DEFAULT - Static variable in class org.h2.engine.DbSettings
+
+
INTERNAL.
+
+
DEFAULT_HTTP_PORT - Static variable in class org.h2.engine.Constants
+
+
The default port number of the HTTP server (for the H2 Console).
+
+
DEFAULT_LOCK_MODE - Static variable in class org.h2.engine.Constants
+
+
The default value for the LOCK_MODE setting.
+
+
DEFAULT_MAX_LENGTH_INPLACE_LOB - Static variable in class org.h2.engine.Constants
+
+
The default maximum length of an LOB that is stored with the record + itself, and not in a separate place.
+
+
DEFAULT_MAX_OPERATION_MEMORY - Static variable in class org.h2.engine.Constants
+
+
The default for the setting MAX_OPERATION_MEMORY.
+
+
DEFAULT_PAGE_SIZE - Static variable in class org.h2.engine.Constants
+
+
The default page size to use for new databases.
+
+
DEFAULT_RESULT_SET_CONCURRENCY - Static variable in class org.h2.engine.Constants
+
+
The default result set concurrency for statements created with + Connection.createStatement() or prepareStatement(String sql).
+
+
DEFAULT_TCP_PORT - Static variable in class org.h2.engine.Constants
+
+
The default port of the TCP server.
+
+
DEFAULT_WRITE_DELAY - Static variable in class org.h2.engine.Constants
+
+
The default delay in milliseconds before the transaction log is written.
+
+
defaultConnection - Variable in class org.h2.engine.DbSettings
+
+
Database setting DEFAULT_CONNECTION (default: false).
+
+
defaultEscape - Variable in class org.h2.engine.DbSettings
+
+
Database setting DEFAULT_ESCAPE (default: \).
+
+
defaultTableEngine - Variable in class org.h2.engine.DbSettings
+
+
Database setting DEFAULT_TABLE_ENGINE + (default: null).
+
+
defragAlways - Variable in class org.h2.engine.DbSettings
+
+
Database setting DEFRAG_ALWAYS (default: false) + Each time the database is closed normally, it is fully defragmented (the + same as SHUTDOWN DEFRAG).
+
+
deinterleave(int, long, int) - Method in class org.h2.tools.MultiDimension
+
+
Gets one of the original multi-dimensional values from a scalar value.
+
+
DELAY_WRONG_PASSWORD_MAX - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.delayWrongPasswordMax + (default: 4000).
+
+
DELAY_WRONG_PASSWORD_MIN - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.delayWrongPasswordMin + (default: 250).
+
+
DELETE - Static variable in interface org.h2.api.Trigger
+
+
The trigger is called for DELETE statements.
+
+
DELETE - Static variable in class org.h2.engine.Right
+
+
The right bit mask that means: deleting rows from a table is allowed.
+
+
DeleteDbFiles - Class in org.h2.tools
+
+
Deletes all files belonging to a database.
+
+
DeleteDbFiles() - Constructor for class org.h2.tools.DeleteDbFiles
+
 
+
deleteRow() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Deletes the current row.
+
+
deleteRow() - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
deletesAreDetected(int) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether deletes are detected.
+
+
DESERIALIZATION_FAILED_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90027 is thrown when + an object could not be de-serialized.
+
+
deserialize(byte[]) - Method in interface org.h2.api.JavaObjectSerializer
+
+
Deserialize object from byte array.
+
+
disallowedTypes - Variable in class org.h2.engine.Mode
+
+
An optional Set of hidden/disallowed column types.
+
+
discardWithTableHints - Variable in class org.h2.engine.Mode
+
+
Discard SQLServer table hints (e.g.
+
+
dispose() - Method in class org.h2.jdbcx.JdbcConnectionPool
+
+
Closes all unused pooled connections.
+
+
DIVISION_BY_ZERO_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 22012 is thrown when trying to divide + a value by zero.
+
+
doesMaxRowSizeIncludeBlobs() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether the maximum row size includes blobs.
+
+
DOMAIN - Static variable in class org.h2.engine.DbObject
+
+
This object is a domain.
+
+
DOMAIN_ALREADY_EXISTS_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90119 is thrown when + trying to create a domain if an object with this name already exists, + or when trying to overload a built-in data type.
+
+
DOMAIN_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90120 is thrown when + trying to drop a domain that doesn't exist.
+
+
done(Transfer) - Method in class org.h2.engine.SessionRemote
+
+
Called to flush the output after data has been sent to the server and + just before receiving data.
+
+
DOUBLE_PRECISION - Static variable in class org.h2.api.H2Type
+
+
The DOUBLE PRECISION data type.
+
+
DRIVER_VERSION_ERROR_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90047 is thrown when + trying to connect to a TCP server with an incompatible client.
+
+
dropAll(Connection) - Static method in class org.h2.fulltext.FullText
+
+
Drops all full text indexes from the database.
+
+
dropAll(Connection) - Static method in class org.h2.fulltext.FullTextLucene
+
+
Drops all full text indexes from the database.
+
+
dropIndex(Connection, String, String) - Static method in class org.h2.fulltext.FullText
+
+
Drop an existing full text index for a table.
+
+
dropIndex(Connection, String, String) - Static method in class org.h2.fulltext.FullTextLucene
+
+
Drop an existing full text index for a table.
+
+
dropRestrict - Variable in class org.h2.engine.DbSettings
+
+
Database setting DROP_RESTRICT (default: true) + Whether the default action for DROP TABLE, DROP VIEW, DROP SCHEMA, DROP + DOMAIN, and DROP CONSTRAINT is RESTRICT.
+
+
DUPLICATE_COLUMN_NAME_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 42121 is thrown when trying to create + a table or insert into a table and use the same column name twice.
+
+
DUPLICATE_KEY_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 23505 is thrown when trying to insert + a row that would violate a unique index or primary key.
+
+
DUPLICATE_PROPERTY_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90066 is thrown when + the same property appears twice in the database URL or in + the connection properties.
+
+
DynamicSettings(Mode, TimeZoneProvider) - Constructor for class org.h2.engine.Session.DynamicSettings
+
+
Creates new instance of dynamic settings.
+
+
+ + + +

E

+
+
ENABLE_ANONYMOUS_TLS - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.enableAnonymousTLS (default: true).
+
+
ENCRYPTION_KEY_HASH_ITERATIONS - Static variable in class org.h2.engine.Constants
+
+
The password is hashed this many times + to slow down dictionary attacks.
+
+
end(Xid, int) - Method in class org.h2.jdbcx.JdbcXAConnection
+
+
End a transaction.
+
+
endStatement() - Method in class org.h2.engine.SessionLocal
+
+
Mark the statement as completed.
+
+
Engine - Class in org.h2.engine
+
+
The engine contains a map of all open databases.
+
+
enquoteIdentifier(String, boolean) - Method in class org.h2.jdbc.JdbcStatement
+
 
+
enquoteIdentifier(String, boolean) - Method in interface org.h2.jdbc.JdbcStatementBackwardsCompat
+
+
Enquotes the specified identifier.
+
+
ENUM - Static variable in class org.h2.api.H2Type
+
+
The ENUM data type.
+
+
ENUM_DUPLICATE - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 22033 is thrown when an + attempt is made to add or modify an ENUM-typed column so + that it would have duplicate values.
+
+
ENUM_EMPTY - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 22032 is thrown when an + attempt is made to add or modify an ENUM-typed column so + that one or more of its enumerators would be empty.
+
+
ENUM_VALUE_NOT_PERMITTED - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 22030 is thrown when + an attempt is made to INSERT or UPDATE an ENUM-typed cell, + but the value is not one of the values enumerated by the + type.
+
+
equals(Object) - Method in class org.h2.api.Interval
+
 
+
equalsIdentifiers(String, String) - Method in class org.h2.engine.Database
+
+
Compare two identifiers (table names, column names,...) and verify they + are equal.
+
+
ERROR_ACCESSING_LINKED_TABLE_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90111 is thrown when + an exception occurred while accessing a linked table.
+
+
ERROR_CREATING_TRIGGER_OBJECT_3 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90043 is thrown when + there is an error initializing the trigger, for example because the + class does not implement the Trigger interface.
+
+
ERROR_EXECUTING_TRIGGER_3 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90044 is thrown when + an exception or error occurred while calling the triggers fire method.
+
+
ERROR_OPENING_DATABASE_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 8000 is thrown when + there was a problem trying to create a database lock.
+
+
ERROR_SETTING_DATABASE_EVENT_LISTENER_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90099 is thrown when an error occurred + trying to initialize the database event listener.
+
+
ErrorCode - Class in org.h2.api
+
+
This class defines the error codes used for SQL exceptions.
+
+
estimatedFunctionTableRows - Variable in class org.h2.engine.DbSettings
+
+
Database setting ESTIMATED_FUNCTION_TABLE_ROWS (default: + 1000).
+
+
EXCEPTION_IN_FUNCTION_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90105 is thrown when + an exception occurred in a user-defined method.
+
+
EXCEPTION_OPENING_PORT_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90061 is thrown when + trying to start a server if a server is already running at the same port.
+
+
exceptionThrown(SQLException, String) - Method in interface org.h2.api.DatabaseEventListener
+
+
This method is called if an exception occurred.
+
+
exceptionThrown(SQLException, String) - Method in class org.h2.engine.Database
+
+
This method is called after an exception occurred, to inform the database + event listener (if one is set).
+
+
execute() - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Executes an arbitrary statement.
+
+
execute(String) - Method in class org.h2.jdbc.JdbcStatement
+
+
Executes a statement and returns type of its result.
+
+
execute(String, int) - Method in class org.h2.jdbc.JdbcStatement
+
+
Executes a statement and returns type of its result.
+
+
execute(String, int[]) - Method in class org.h2.jdbc.JdbcStatement
+
+
Executes a statement and returns type of its result.
+
+
execute(String, String[]) - Method in class org.h2.jdbc.JdbcStatement
+
+
Executes a statement and returns type of its result.
+
+
execute(String, String, String, boolean) - Static method in class org.h2.tools.Backup
+
+
Backs up database files.
+
+
execute(String, String, String, char[], char[], boolean) - Static method in class org.h2.tools.ChangeFileEncryption
+
+
Changes the password for a database.
+
+
execute(String, String, String, String, String) - Method in class org.h2.tools.CreateCluster
+
+
Creates a cluster.
+
+
execute(String, String, boolean) - Static method in class org.h2.tools.DeleteDbFiles
+
+
Deletes the database files.
+
+
execute(String, String) - Static method in class org.h2.tools.Recover
+
+
Dumps the contents of a database to a SQL script file.
+
+
execute(String, String, String) - Static method in class org.h2.tools.Restore
+
+
Restores database files.
+
+
execute(Connection, Reader) - Static method in class org.h2.tools.RunScript
+
+
Executes the SQL commands read from the reader against a database.
+
+
execute(String, String, String, String, Charset, boolean) - Static method in class org.h2.tools.RunScript
+
+
Executes the SQL commands in a script file against a database.
+
+
executeBatch() - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Executes the batch.
+
+
executeBatch() - Method in class org.h2.jdbc.JdbcStatement
+
+
Executes the batch.
+
+
executeLargeBatch() - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Executes the batch.
+
+
executeLargeBatch() - Method in class org.h2.jdbc.JdbcStatement
+
+
Executes the batch.
+
+
executeLargeUpdate() - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Executes a statement (insert, update, delete, create, drop) + and returns the update count.
+
+
executeLargeUpdate() - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Executes a statement (insert, update, delete, create, drop) + and returns the update count.
+
+
executeLargeUpdate(String) - Method in class org.h2.jdbc.JdbcStatement
+
+
Executes a statement (insert, update, delete, create, drop) + and returns the update count.
+
+
executeLargeUpdate(String, int) - Method in class org.h2.jdbc.JdbcStatement
+
+
Executes a statement and returns the update count.
+
+
executeLargeUpdate(String, int[]) - Method in class org.h2.jdbc.JdbcStatement
+
+
Executes a statement and returns the update count.
+
+
executeLargeUpdate(String, String[]) - Method in class org.h2.jdbc.JdbcStatement
+
+
Executes a statement and returns the update count.
+
+
executeQuery() - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Executes a query (select statement) and returns the result set.
+
+
executeQuery(String) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Calling this method is not legal on a PreparedStatement.
+
+
executeQuery(String) - Method in class org.h2.jdbc.JdbcStatement
+
+
Executes a query (select statement) and returns the result set.
+
+
executeUpdate() - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Executes a statement (insert, update, delete, create, drop) + and returns the update count.
+
+
executeUpdate() - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Executes a statement (insert, update, delete, create, drop) + and returns the update count.
+
+
executeUpdate(String) - Method in class org.h2.jdbc.JdbcStatement
+
+
Executes a statement (insert, update, delete, create, drop) + and returns the update count.
+
+
executeUpdate(String, int) - Method in class org.h2.jdbc.JdbcStatement
+
+
Executes a statement and returns the update count.
+
+
executeUpdate(String, int[]) - Method in class org.h2.jdbc.JdbcStatement
+
+
Executes a statement and returns the update count.
+
+
executeUpdate(String, String[]) - Method in class org.h2.jdbc.JdbcStatement
+
+
Executes a statement and returns the update count.
+
+
executionTimeCumulativeNanos - Variable in class org.h2.engine.QueryStatisticsData.QueryEntry
+
+
The total execution time.
+
+
executionTimeMaxNanos - Variable in class org.h2.engine.QueryStatisticsData.QueryEntry
+
+
The maximum execution time, in nanoseconds.
+
+
executionTimeMeanNanos - Variable in class org.h2.engine.QueryStatisticsData.QueryEntry
+
+
The mean execution time.
+
+
executionTimeMinNanos - Variable in class org.h2.engine.QueryStatisticsData.QueryEntry
+
+
The minimum execution time, in nanoseconds.
+
+
expand(byte[]) - Method in class org.h2.tools.CompressTool
+
+
Expands the compressed data.
+
+
expand(byte[], byte[], int) - Static method in class org.h2.tools.CompressTool
+
+
INTERNAL
+
+
expressionNames - Variable in class org.h2.engine.Mode
+
+
How column names are generated for expressions.
+
+
+ + + +

F

+
+
FEATURE_NOT_SUPPORTED_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 50100 is thrown when calling an + unsupported JDBC method or database feature.
+
+
fetchSize - Variable in class org.h2.jdbc.JdbcStatement
+
 
+
FILE_BLOCK_SIZE - Static variable in class org.h2.engine.Constants
+
+
The block of a file.
+
+
FILE_CORRUPTED_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90030 is thrown when + the database engine has detected a checksum mismatch in the data + or index.
+
+
FILE_CREATION_FAILED_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90062 is thrown when + a directory or file could not be created.
+
+
FILE_DELETE_FAILED_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90025 is thrown when + a file could not be deleted, because it is still in use + (only in Windows), or because an error occurred when deleting.
+
+
FILE_ENCRYPTION_ERROR_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90049 is thrown when + trying to open an encrypted database with the wrong file encryption + password or algorithm.
+
+
FILE_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90124 is thrown when + trying to access a file that doesn't exist.
+
+
FILE_RENAME_FAILED_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90024 is thrown when + a file could not be renamed.
+
+
FILE_VERSION_ERROR_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90048 is thrown when + the file header of a database files (*.db) does not match the + expected version, or if it is corrupted.
+
+
findColumn(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Searches for a specific column in the result set.
+
+
findColumn(String) - Method in class org.h2.tools.SimpleResultSet
+
+
Searches for a specific column in the result set.
+
+
findComment(DbObject) - Method in class org.h2.engine.Database
+
+
Get the comment for the given database object if one exists, or null if + not.
+
+
findLocalTempTable(String) - Method in class org.h2.engine.SessionLocal
+
+
Get the local temporary table if one exists with that name, or null if + not.
+
+
findLocalTempTableConstraint(String) - Method in class org.h2.engine.SessionLocal
+
+
Get the local temporary constraint if one exists with that name, or + null if not.
+
+
findLocalTempTableIndex(String) - Method in class org.h2.engine.SessionLocal
+
+
Get the local temporary index if one exists with that name, or null if + not.
+
+
findRole(String) - Method in class org.h2.engine.Database
+
+
Get the role if it exists, or null if not.
+
+
findSchema(String) - Method in class org.h2.engine.Database
+
+
Get the schema if it exists, or null if not.
+
+
findSetting(String) - Method in class org.h2.engine.Database
+
+
Get the setting if it exists, or null if not.
+
+
findUser(String) - Method in class org.h2.engine.Database
+
+
Get the user if it exists, or null if not.
+
+
findUserOrRole(String) - Method in class org.h2.engine.Database
+
+
Get the user or role if it exists, or null if not.
+
+
fire(Connection, Object[], Object[]) - Method in interface org.h2.api.Trigger
+
+
This method is called for each triggered action.
+
+
fire(Connection, Object[], Object[]) - Method in class org.h2.fulltext.FullText.FullTextTrigger
+
+
INTERNAL
+
+
fire(Connection, Object[], Object[]) - Method in class org.h2.fulltext.FullTextLucene.FullTextTrigger
+
+
INTERNAL
+
+
fire(Connection, Object[], Object[]) - Method in class org.h2.tools.TriggerAdapter
+
 
+
fire(Connection, ResultSet, ResultSet) - Method in class org.h2.tools.TriggerAdapter
+
+
This method is called for each triggered action by the default + fire(Connection conn, Object[] oldRow, Object[] newRow) method.
+
+
first() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Moves the current position to the first row.
+
+
first() - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
flush() - Method in class org.h2.engine.Database
+
+
Flush all pending changes to the transaction log.
+
+
FOR_UPDATE_IS_NOT_ALLOWED_IN_DISTINCT_OR_GROUPED_SELECT - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90145 is thrown when trying to execute a + SELECT statement with non-window aggregates, DISTINCT, GROUP BY, or + HAVING clauses together with FOR UPDATE clause.
+
+
forBitData - Variable in class org.h2.engine.Mode
+
+
If true 'FOR BIT DATA' clauses are allowed for character string + data types.
+
+
FORCE_AUTOCOMMIT_OFF_ON_COMMIT - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.forceAutoCommitOffOnCommit (default: false).
+
+
forget(Xid) - Method in class org.h2.jdbcx.JdbcXAConnection
+
+
Forget a transaction.
+
+
free() - Method in class org.h2.jdbc.JdbcArray
+
+
Release all resources of this object.
+
+
free() - Method in class org.h2.jdbc.JdbcLob
+
+
Release all resources of this object.
+
+
free() - Method in class org.h2.tools.SimpleResultSet.SimpleArray
+
+
INTERNAL
+
+
fromJdbc(int) - Static method in enum org.h2.engine.IsolationLevel
+
+
Returns the isolation level from LOCK_MODE equivalent for PageStore and + old versions of H2.
+
+
fromLockMode(int) - Static method in enum org.h2.engine.IsolationLevel
+
+
Returns the isolation level from LOCK_MODE equivalent for PageStore and + old versions of H2.
+
+
fromSql(String) - Static method in enum org.h2.engine.IsolationLevel
+
+
Returns the isolation level from its SQL name.
+
+
FULL_VERSION - Static variable in class org.h2.engine.Constants
+
+
The complete version number of this database, consisting of + the major version, the minor version, the build id, and the build date.
+
+
FullText - Class in org.h2.fulltext
+
+
This class implements the native full text search.
+
+
FullText() - Constructor for class org.h2.fulltext.FullText
+
 
+
FullText.FullTextTrigger - Class in org.h2.fulltext
+
+
Trigger updates the index when a inserting, updating, or deleting a row.
+
+
FullTextLucene - Class in org.h2.fulltext
+
+
This class implements the full text search based on Apache Lucene.
+
+
FullTextLucene() - Constructor for class org.h2.fulltext.FullTextLucene
+
 
+
FullTextLucene.FullTextTrigger - Class in org.h2.fulltext
+
+
Trigger updates the index when a inserting, updating, or deleting a row.
+
+
FullTextTrigger() - Constructor for class org.h2.fulltext.FullText.FullTextTrigger
+
 
+
FullTextTrigger() - Constructor for class org.h2.fulltext.FullTextLucene.FullTextTrigger
+
 
+
FUNCTION_ALIAS - Static variable in class org.h2.engine.DbObject
+
+
This object is an alias for a Java function.
+
+
FUNCTION_ALIAS_ALREADY_EXISTS_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90076 is thrown when + trying to create a function alias for a system function or for a function + that is already defined.
+
+
FUNCTION_ALIAS_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90077 is thrown when + trying to drop a system function or a function alias that does not exist.
+
+
FUNCTION_MUST_RETURN_RESULT_SET_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90000 is thrown when + a function that does not return a result set was used in the FROM clause.
+
+
FUNCTION_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90022 is thrown when + trying to call a unknown function.
+
+
+ + + +

G

+
+
GENERAL_ERROR_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 50000 is thrown when + something unexpected occurs, for example an internal stack + overflow.
+
+
GENERATED_COLUMN_CANNOT_BE_ASSIGNED_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90154 is thrown when trying to assign a + value to a generated column.
+
+
GENERATED_COLUMN_CANNOT_BE_UPDATABLE_BY_CONSTRAINT_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90155 is thrown when trying to create a + referential constraint that can update a referenced generated column.
+
+
generatedKeyAlwaysReturned() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether database always returns generated keys if valid names or + indexes of columns were specified and command was completed successfully.
+
+
generatedKeys - Variable in class org.h2.jdbc.JdbcStatement
+
 
+
GeneratedKeysMode - Class in org.h2.engine
+
+
Modes of generated keys' gathering.
+
+
generatePreparedQuery(String, String, String[]) - Method in class org.h2.tools.MultiDimension
+
+
Generates an optimized multi-dimensional range query.
+
+
GEOMETRY - Static variable in class org.h2.api.H2Type
+
+
The GEOMETRY data type.
+
+
get(String, boolean) - Method in class org.h2.engine.SettingsBase
+
+
Get the setting for the given key.
+
+
get(String, int) - Method in class org.h2.engine.SettingsBase
+
+
Get the setting for the given key.
+
+
get(String, String) - Method in class org.h2.engine.SettingsBase
+
+
Get the setting for the given key.
+
+
GET_JDBC_META - Static variable in class org.h2.engine.SessionRemote
+
 
+
getActiveConnections() - Method in class org.h2.jdbcx.JdbcConnectionPool
+
+
Returns the number of active (open) connections of this pool.
+
+
getAllComments() - Method in class org.h2.engine.Database
+
 
+
getAllowLiterals() - Method in class org.h2.engine.Database
+
 
+
getAllowLiterals() - Method in class org.h2.engine.SessionLocal
+
 
+
getAllRights() - Method in class org.h2.engine.Database
+
 
+
getAllSchemas() - Method in class org.h2.engine.Database
+
 
+
getAllSchemasNoMeta() - Method in class org.h2.engine.Database
+
 
+
getAllSettings() - Method in class org.h2.engine.Database
+
 
+
getAllSynonyms() - Method in class org.h2.engine.Database
+
+
Get all synonyms.
+
+
getAllTablesAndViews() - Method in class org.h2.engine.Database
+
+
Get all tables and views.
+
+
getAllUsersAndRoles() - Method in class org.h2.engine.Database
+
 
+
getArray() - Method in class org.h2.jdbc.JdbcArray
+
+
Returns the value as a Java array.
+
+
getArray(Map<String, Class<?>>) - Method in class org.h2.jdbc.JdbcArray
+
+
Returns the value as a Java array.
+
+
getArray(long, int) - Method in class org.h2.jdbc.JdbcArray
+
+
Returns the value as a Java array.
+
+
getArray(long, int, Map<String, Class<?>>) - Method in class org.h2.jdbc.JdbcArray
+
+
Returns the value as a Java array.
+
+
getArray(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as an Array.
+
+
getArray(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as an Array.
+
+
getArray(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as an Array.
+
+
getArray(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as an Array.
+
+
getArray(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a java.sql.Array.
+
+
getArray(String) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a java.sql.Array.
+
+
getArray() - Method in class org.h2.tools.SimpleResultSet.SimpleArray
+
+
Get the object array.
+
+
getArray(Map<String, Class<?>>) - Method in class org.h2.tools.SimpleResultSet.SimpleArray
+
+
INTERNAL
+
+
getArray(long, int) - Method in class org.h2.tools.SimpleResultSet.SimpleArray
+
+
INTERNAL
+
+
getArray(long, int, Map<String, Class<?>>) - Method in class org.h2.tools.SimpleResultSet.SimpleArray
+
+
INTERNAL
+
+
getAsciiStream() - Method in class org.h2.jdbc.JdbcClob
+
+
Returns the input stream.
+
+
getAsciiStream(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as an input stream.
+
+
getAsciiStream(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as an input stream.
+
+
getAsciiStream(int) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getAsciiStream(String) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getAttributes(String, String, String, String) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
[Not supported]
+
+
getAuthenticator() - Method in class org.h2.engine.Database
+
+
get authenticator for database users
+
+
getAutoClose() - Method in class org.h2.tools.SimpleResultSet
+
+
Get the current auto-close behavior.
+
+
getAutoCommit() - Method in class org.h2.engine.Session
+
+
Check if this session is in auto-commit mode.
+
+
getAutoCommit() - Method in class org.h2.engine.SessionLocal
+
 
+
getAutoCommit() - Method in class org.h2.engine.SessionRemote
+
 
+
getAutoCommit() - Method in class org.h2.jdbc.JdbcConnection
+
+
Gets the current setting for auto commit.
+
+
getBackgroundException() - Method in class org.h2.engine.Database
+
 
+
getBaseDir() - Static method in class org.h2.engine.SysProperties
+
+
INTERNAL
+
+
getBaseType() - Method in class org.h2.jdbc.JdbcArray
+
+
Returns the base type of the array.
+
+
getBaseType() - Method in class org.h2.tools.SimpleResultSet.SimpleArray
+
+
Get the base type of this array.
+
+
getBaseTypeName() - Method in class org.h2.jdbc.JdbcArray
+
+
Returns the base type name of the array.
+
+
getBaseTypeName() - Method in class org.h2.tools.SimpleResultSet.SimpleArray
+
+
Get the base type name of this array.
+
+
getBestRowIdentifier(String, String, String, int, boolean) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the list of columns that best identifier a row in a table.
+
+
getBigDecimal(int, int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+ +
+
getBigDecimal(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a BigDecimal.
+
+
getBigDecimal(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a BigDecimal.
+
+
getBigDecimal(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a BigDecimal.
+
+
getBigDecimal(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a BigDecimal.
+
+
getBigDecimal(String, int) - Method in class org.h2.jdbc.JdbcResultSet
+
+ +
+
getBigDecimal(int, int) - Method in class org.h2.jdbc.JdbcResultSet
+
+ +
+
getBigDecimal(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a java.math.BigDecimal.
+
+
getBigDecimal(String) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a java.math.BigDecimal.
+
+
getBigDecimal(int, int) - Method in class org.h2.tools.SimpleResultSet
+
+
Deprecated. +
INTERNAL
+
+
+
getBigDecimal(String, int) - Method in class org.h2.tools.SimpleResultSet
+
+
Deprecated. +
INTERNAL
+
+
+
getBinaryStream() - Method in class org.h2.jdbc.JdbcBlob
+
 
+
getBinaryStream(long, long) - Method in class org.h2.jdbc.JdbcBlob
+
+
Returns the input stream, starting from an offset.
+
+
getBinaryStream(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as an input stream.
+
+
getBinaryStream(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as an input stream.
+
+
getBinaryStream() - Method in class org.h2.jdbc.JdbcSQLXML
+
 
+
getBinaryStream(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a java.io.InputStream.
+
+
getBinaryStream(String) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a java.io.InputStream.
+
+
getBlob(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a Blob.
+
+
getBlob(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a Blob.
+
+
getBlob(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a Blob.
+
+
getBlob(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a Blob.
+
+
getBlob(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a java.sql.Blob.
+
+
getBlob(String) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a java.sql.Blob.
+
+
getBlockingSessionId() - Method in class org.h2.engine.SessionLocal
+
 
+
getBoolean(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a boolean.
+
+
getBoolean(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a boolean.
+
+
getBoolean(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a boolean.
+
+
getBoolean(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a boolean.
+
+
getBoolean(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a boolean.
+
+
getBoolean(String) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a boolean.
+
+
getBranchQualifier() - Method in class org.h2.jdbcx.JdbcXid
+
+
The transaction branch identifier.
+
+
getByte(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a byte.
+
+
getByte(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a byte.
+
+
getByte(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a byte.
+
+
getByte(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a byte.
+
+
getByte(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a byte.
+
+
getByte(String) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a byte.
+
+
getBytes(long, int) - Method in class org.h2.jdbc.JdbcBlob
+
+
Returns some bytes of the object.
+
+
getBytes(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a byte array.
+
+
getBytes(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a byte array.
+
+
getBytes(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a byte array.
+
+
getBytes(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a byte array.
+
+
getBytes(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a byte array.
+
+
getBytes(String) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a byte array.
+
+
getCacheType() - Method in class org.h2.engine.Database
+
 
+
getCancel() - Method in class org.h2.engine.SessionLocal
+
+
Get the cancel time.
+
+
getCaseSensitiveColumnNames() - Method in class org.h2.tools.Csv
+
+
Get the current case sensitive column names setting.
+
+
getCatalog() - Method in class org.h2.jdbc.JdbcConnection
+
+
Gets the current catalog name.
+
+
getCatalogName(int) - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
Returns the catalog name.
+
+
getCatalogName(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns empty string.
+
+
getCatalogs() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the list of catalogs.
+
+
getCatalogSeparator() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the catalog separator.
+
+
getCatalogTerm() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the term for "catalog".
+
+
getCharacterStream(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a reader.
+
+
getCharacterStream(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a reader.
+
+
getCharacterStream() - Method in class org.h2.jdbc.JdbcClob
+
 
+
getCharacterStream(long, long) - Method in class org.h2.jdbc.JdbcClob
+
+
Returns the reader, starting from an offset.
+
+
getCharacterStream(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a reader.
+
+
getCharacterStream(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a reader.
+
+
getCharacterStream() - Method in class org.h2.jdbc.JdbcSQLXML
+
 
+
getCharacterStream(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a java.io.Reader.
+
+
getCharacterStream(String) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a java.io.Reader.
+
+
getChildren() - Method in class org.h2.engine.DbObject
+
+
Get the list of dependent children (for tables, this includes indexes and + so on).
+
+
getChildren() - Method in class org.h2.engine.Role
+
 
+
getChildren() - Method in class org.h2.engine.User
+
 
+
getClientInfo() - Method in class org.h2.jdbc.JdbcConnection
+
+
Get the client properties.
+
+
getClientInfo(String) - Method in class org.h2.jdbc.JdbcConnection
+
+
Get a client property.
+
+
getClientInfoProperties() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
 
+
getClientVersion() - Method in class org.h2.engine.SessionRemote
+
+
Returns the TCP protocol version of remote connection.
+
+
getClob(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a Clob.
+
+
getClob(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a Clob.
+
+
getClob(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a Clob.
+
+
getClob(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a Clob.
+
+
getClob(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a java.sql.Clob.
+
+
getClob(String) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a java.sql.Clob.
+
+
getCluster() - Method in class org.h2.engine.Database
+
 
+
getClusterServers() - Method in class org.h2.engine.Session
+
+
Get the list of the cluster servers for this session.
+
+
getClusterServers() - Method in class org.h2.engine.SessionLocal
+
 
+
getClusterServers() - Method in class org.h2.engine.SessionRemote
+
 
+
getColumnClassName(int) - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
Gets the Java class name of the object that will be returned + if ResultSet.getObject is called.
+
+
getColumnClassName(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the Java class name if this column.
+
+
getColumnCount() - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
Returns the number of columns.
+
+
getColumnCount() - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the column count.
+
+
getColumnDisplaySize(int) - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
Gets the maximum display size for this column.
+
+
getColumnDisplaySize(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns 15.
+
+
getColumnLabel(int) - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
Returns the column label.
+
+
getColumnLabel(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the column label.
+
+
getColumnName(int) - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
Returns the column name.
+
+
getColumnName(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the column name.
+
+
getColumnPrivileges(String, String, String, String) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the list of column privileges.
+
+
getColumns(String, String, String, String) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the list of columns.
+
+
getColumnType(int) - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
Returns the data type of a column.
+
+
getColumnType(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the SQL type.
+
+
getColumnTypeName(int) - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
Returns the data type name of a column.
+
+
getColumnTypeName(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the data type name of a column.
+
+
getCommandStartOrEnd() - Method in class org.h2.engine.SessionLocal
+
 
+
getComment() - Method in class org.h2.engine.DbObject
+
+
Get the current comment of this object.
+
+
getCompareMode() - Method in class org.h2.engine.Database
+
 
+
getCompareMode() - Method in class org.h2.engine.SessionRemote
+
 
+
getCompareMode() - Method in class org.h2.tools.Recover
+
 
+
getCompiler() - Method in class org.h2.engine.Database
+
 
+
getConcurrency() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Gets the result set concurrency.
+
+
getConcurrency() - Method in class org.h2.tools.SimpleResultSet
+
+
Returns ResultSet.CONCUR_READ_ONLY.
+
+
getConnection() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the connection that created this object.
+
+
getConnection() - Method in class org.h2.jdbc.JdbcStatement
+
+
Returns the connection that created this object.
+
+
getConnection() - Method in class org.h2.jdbcx.JdbcConnectionPool
+
+
Retrieves a connection from the connection pool.
+
+
getConnection(String, String) - Method in class org.h2.jdbcx.JdbcConnectionPool
+
+
INTERNAL
+
+
getConnection() - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Open a new connection using the current URL, user name and password.
+
+
getConnection(String, String) - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Open a new connection using the current URL and the specified user name + and password.
+
+
getConnection() - Method in class org.h2.jdbcx.JdbcXAConnection
+
+
Get a connection that is a handle to the physical connection.
+
+
getCreateSQL() - Method in class org.h2.engine.Comment
+
 
+
getCreateSQL() - Method in class org.h2.engine.DbObject
+
+
Construct the CREATE ...
+
+
getCreateSQL() - Method in class org.h2.engine.Right
+
 
+
getCreateSQL(boolean) - Method in class org.h2.engine.Role
+
+
Get the CREATE SQL statement for this object.
+
+
getCreateSQL() - Method in class org.h2.engine.Role
+
 
+
getCreateSQL() - Method in class org.h2.engine.Setting
+
 
+
getCreateSQL() - Method in class org.h2.engine.User
+
 
+
getCreateSQL(boolean) - Method in class org.h2.engine.User
+
+
Get the CREATE SQL statement for this object.
+
+
getCreateSQLForCopy(Table, String) - Method in class org.h2.engine.Comment
+
 
+
getCreateSQLForCopy(Table, String) - Method in class org.h2.engine.DbObject
+
+
Build a SQL statement to re-create the object, or to create a copy of the + object with a different name or referencing a different table
+
+
getCreateSQLForCopy(Table, String) - Method in class org.h2.engine.Right
+
 
+
getCreateSQLForCopy(Table, String) - Method in class org.h2.engine.Role
+
 
+
getCreateSQLForCopy(Table, String) - Method in class org.h2.engine.Setting
+
 
+
getCreateSQLForCopy(Table, String) - Method in class org.h2.engine.User
+
 
+
getCreateSQLForMeta() - Method in class org.h2.engine.DbObject
+
+
Construct the CREATE ...
+
+
getCrossReference(String, String, String, String, String, String) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the list of foreign key columns that references a table, as well as + the list of primary key columns that are references by a table.
+
+
getCurrentCommand() - Method in class org.h2.engine.SessionLocal
+
 
+
getCurrentId() - Method in class org.h2.engine.SessionRemote
+
 
+
getCurrentSchemaName() - Method in class org.h2.engine.Session
+
+
Get current schema.
+
+
getCurrentSchemaName() - Method in class org.h2.engine.SessionLocal
+
 
+
getCurrentSchemaName() - Method in class org.h2.engine.SessionRemote
+
 
+
getCurrentValueFor(Sequence) - Method in class org.h2.engine.SessionLocal
+
+
Returns the current value of the sequence in this session.
+
+
getCursorName() - Method in class org.h2.jdbc.JdbcResultSet
+
+
[Not supported] Gets the cursor name if it was defined.
+
+
getCursorName() - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getDatabase() - Method in class org.h2.engine.DbObject
+
+
Get the database.
+
+
getDatabase() - Method in class org.h2.engine.SessionLocal
+
 
+
getDatabaseMajorVersion() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the major version of the database.
+
+
getDatabaseMeta() - Method in class org.h2.engine.Session
+
+
Returns database meta information.
+
+
getDatabaseMeta() - Method in class org.h2.engine.SessionLocal
+
 
+
getDatabaseMeta() - Method in class org.h2.engine.SessionRemote
+
 
+
getDatabaseMinorVersion() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the minor version of the database.
+
+
getDatabasePath() - Method in class org.h2.engine.Database
+
 
+
getDatabasePath() - Method in class org.h2.engine.SessionRemote
+
 
+
getDatabasePath() - Method in class org.h2.tools.Recover
+
+
INTERNAL
+
+
getDatabaseProductName() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the database product name.
+
+
getDatabaseProductVersion() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the product version of the database.
+
+
getDataHandler() - Method in class org.h2.engine.Session
+
+
Get the data handler object.
+
+
getDataHandler() - Method in class org.h2.engine.SessionLocal
+
 
+
getDataHandler() - Method in class org.h2.engine.SessionRemote
+
 
+
getDate(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a java.sql.Date.
+
+
getDate(int, Calendar) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a java.sql.Date using a + specified time zone.
+
+
getDate(String, Calendar) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a java.sql.Date using a + specified time zone.
+
+
getDate(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a java.sql.Date.
+
+
getDate(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a java.sql.Date.
+
+
getDate(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a java.sql.Date.
+
+
getDate(int, Calendar) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a java.sql.Date using a + specified time zone.
+
+
getDate(String, Calendar) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a java.sql.Date using a + specified time zone.
+
+
getDate(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as an java.sql.Date.
+
+
getDate(String) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a java.sql.Date.
+
+
getDate(int, Calendar) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getDate(String, Calendar) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getDays() - Method in class org.h2.api.Interval
+
+
Returns days value, if any.
+
+
getDbSettings() - Method in class org.h2.engine.ConnectionInfo
+
 
+
getDefaultNullOrdering() - Method in class org.h2.engine.Database
+
 
+
getDefaultTableType() - Method in class org.h2.engine.Database
+
 
+
getDefaultTransactionIsolation() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the default transaction isolation level.
+
+
getDependentTable(SchemaObject, Table) - Method in class org.h2.engine.Database
+
+
Get the first table that depends on this object.
+
+
getDescription() - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Get the current description.
+
+
getDouble(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a double.
+
+
getDouble(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a double.
+
+
getDouble(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a double.
+
+
getDouble(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a double.
+
+
getDouble(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as an double.
+
+
getDouble(String) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a double.
+
+
getDriverMajorVersion() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the major version of this driver.
+
+
getDriverMinorVersion() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the minor version of this driver.
+
+
getDriverName() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the name of the JDBC driver.
+
+
getDriverVersion() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the version number of the driver.
+
+
getDropSQL() - Method in class org.h2.engine.DbObject
+
+
Construct a DROP ...
+
+
getDynamicSettings() - Method in class org.h2.engine.Session
+
+
Returns dynamic settings.
+
+
getDynamicSettings() - Method in class org.h2.engine.SessionLocal
+
 
+
getDynamicSettings() - Method in class org.h2.engine.SessionRemote
+
 
+
getEnum() - Method in class org.h2.engine.Mode
+
 
+
getErrorCode() - Method in interface org.h2.jdbc.JdbcException
+
+
Returns the H2-specific error code.
+
+
getEscapeCharacter() - Method in class org.h2.tools.Csv
+
+
Get the current escape character.
+
+
getExclusiveSession() - Method in class org.h2.engine.Database
+
 
+
getExecutionTimeStandardDeviation() - Method in class org.h2.engine.QueryStatisticsData.QueryEntry
+
 
+
getExportedKeys(String, String, String) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the list of foreign key columns that reference a table.
+
+
getExtraNameCharacters() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the characters that are allowed for identifiers in addiction to + A-Z, a-z, 0-9 and '_'.
+
+
getFetchDirection() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Gets the fetch direction.
+
+
getFetchDirection() - Method in class org.h2.jdbc.JdbcStatement
+
+
Gets the fetch direction.
+
+
getFetchDirection() - Method in class org.h2.tools.SimpleResultSet
+
+
Returns ResultSet.FETCH_FORWARD.
+
+
getFetchSize() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Gets the number of rows suggested to read in one step.
+
+
getFetchSize() - Method in class org.h2.jdbc.JdbcStatement
+
+
Gets the number of rows suggested to read in one step.
+
+
getFetchSize() - Method in class org.h2.tools.SimpleResultSet
+
+
Returns 0.
+
+
getFieldDelimiter() - Method in class org.h2.tools.Csv
+
+
Get the current field delimiter.
+
+
getFieldSeparatorRead() - Method in class org.h2.tools.Csv
+
+
Get the current field separator for reading.
+
+
getFieldSeparatorWrite() - Method in class org.h2.tools.Csv
+
+
Get the current field separator for writing.
+
+
getFileEncryptionKey() - Method in class org.h2.engine.Database
+
 
+
getFilePasswordHash() - Method in class org.h2.engine.ConnectionInfo
+
+
Get the file password hash if it is set.
+
+
getFirstUserTable() - Method in class org.h2.engine.Database
+
+
Get the first user defined table, excluding the LOB_BLOCKS table that the + Recover tool creates.
+
+
getFloat(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a float.
+
+
getFloat(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a float.
+
+
getFloat(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a float.
+
+
getFloat(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a float.
+
+
getFloat(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a float.
+
+
getFloat(String) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a float.
+
+
getFormatId() - Method in class org.h2.jdbcx.JdbcXid
+
+
Get the format id.
+
+
getFunctionColumns(String, String, String, String) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
[Not supported] Gets the list of function columns.
+
+
getFunctions(String, String, String) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
[Not supported] Gets the list of functions.
+
+
getGeneratedKeys() - Method in class org.h2.jdbc.JdbcPreparedStatement
+
 
+
getGeneratedKeys() - Method in class org.h2.jdbc.JdbcStatement
+
+
Return a result set with generated keys from the latest executed command + or an empty result set if keys were not generated or were not requested + with Statement.RETURN_GENERATED_KEYS, column indexes, or column + names.
+
+
getGlobalTransactionId() - Method in class org.h2.jdbcx.JdbcXid
+
+
The global transaction identifier.
+
+
getGrantedObject() - Method in class org.h2.engine.Right
+
 
+
getGrantedRole() - Method in class org.h2.engine.Right
+
 
+
getGrantee() - Method in class org.h2.engine.Right
+
 
+
getHoldability() - Method in class org.h2.jdbc.JdbcConnection
+
+
Returns the current result set holdability.
+
+
getHoldability() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the current result set holdability.
+
+
getHoldability() - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the current result set holdability.
+
+
getHours() - Method in class org.h2.api.Interval
+
+
Returns hours value, if any.
+
+
getId() - Method in class org.h2.engine.DbObject
+
+
Get the unique object id.
+
+
getId() - Method in class org.h2.engine.MetaRecord
+
 
+
getId() - Method in class org.h2.engine.SessionLocal
+
 
+
getIdentifierQuoteString() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the string used to quote identifiers.
+
+
getIgnoreCase() - Method in class org.h2.engine.Database
+
 
+
getIgnoreCatalogs() - Method in class org.h2.engine.Database
+
 
+
getImportedKeys(String, String, String) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the list of primary key columns that are referenced by a table.
+
+
getIndexAccess(Connection) - Static method in class org.h2.fulltext.FullTextLucene
+
+
Get the index writer/searcher wrapper for the given connection.
+
+
getIndexInfo(String, String, String, boolean, boolean) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the list of indexes for this database.
+
+
getIndexPath(Connection) - Static method in class org.h2.fulltext.FullTextLucene
+
+
Get the path of the Lucene index for this database.
+
+
getInDoubtTransactions() - Method in class org.h2.engine.Database
+
+
Get the list of in-doubt transactions.
+
+
getInstance(String) - Static method in class org.h2.engine.Mode
+
+
Get the mode with the given name.
+
+
getInstance() - Static method in class org.h2.tools.CompressTool
+
+
Get a new instance.
+
+
getInstance() - Static method in class org.h2.tools.MultiDimension
+
+
Get the singleton.
+
+
getInt(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as an int.
+
+
getInt(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as an int.
+
+
getInt(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as an int.
+
+
getInt(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as an int.
+
+
getInt(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as an int.
+
+
getInt(String) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as an int.
+
+
getInternal(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
INTERNAL
+
+
getInternalType(int[]) - Method in interface org.h2.api.Aggregate
+
+
This method must return the H2 data type, Value, + of the aggregate function, given the H2 data type of the input data.
+
+
getIntValue() - Method in class org.h2.engine.Setting
+
 
+
getIsolationLevel() - Method in class org.h2.engine.Session
+
+
Returns the isolation level.
+
+
getIsolationLevel() - Method in class org.h2.engine.SessionLocal
+
 
+
getIsolationLevel() - Method in class org.h2.engine.SessionRemote
+
 
+
getJavaObjectSerializer() - Method in interface org.h2.engine.CastDataProvider
+
+
Returns the custom Java object serializer, or null.
+
+
getJavaObjectSerializer() - Method in class org.h2.engine.Database
+
 
+
getJavaObjectSerializer() - Method in class org.h2.engine.SessionLocal
+
 
+
getJavaObjectSerializer() - Method in class org.h2.engine.SessionRemote
+
 
+
getJavaObjectSerializer() - Method in class org.h2.jdbc.JdbcConnection
+
 
+
getJdbc() - Method in enum org.h2.engine.IsolationLevel
+
+
Returns the JDBC constant for this isolation level.
+
+
getJDBCMajorVersion() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the major version of the supported JDBC API.
+
+
getJDBCMinorVersion() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the minor version of the supported JDBC API.
+
+
getLargeMaxRows() - Method in class org.h2.jdbc.JdbcStatement
+
+
Gets the maximum number of rows for a ResultSet.
+
+
getLargeUpdateCount() - Method in class org.h2.jdbc.JdbcStatement
+
+
Returns the last update count of this statement.
+
+
getLastIdentity() - Method in class org.h2.engine.SessionLocal
+
 
+
getLastReconnect() - Method in class org.h2.engine.SessionRemote
+
 
+
getLeading() - Method in class org.h2.api.Interval
+
+
Returns value of leading field of this interval.
+
+
getLineCommentCharacter() - Method in class org.h2.tools.Csv
+
+
Get the line comment character.
+
+
getLineSeparator() - Method in class org.h2.tools.Csv
+
+
Get the line separator used for writing.
+
+
getLinkConnection(String, String, String, String) - Method in class org.h2.engine.Database
+
+
Open a new connection or get an existing connection to another database.
+
+
getLobFileListCache() - Method in class org.h2.engine.Database
+
 
+
getLobFileListCache() - Method in class org.h2.engine.SessionRemote
+
 
+
getLobFileListCache() - Method in class org.h2.tools.Recover
+
+
INTERNAL
+
+
getLobSession() - Method in class org.h2.engine.Database
+
 
+
getLobStorage() - Method in class org.h2.engine.Database
+
 
+
getLobStorage() - Method in class org.h2.engine.SessionRemote
+
 
+
getLobStorage() - Method in class org.h2.tools.Recover
+
+
INTERNAL
+
+
getLobSyncObject() - Method in class org.h2.engine.Database
+
 
+
getLobSyncObject() - Method in class org.h2.engine.SessionRemote
+
 
+
getLobSyncObject() - Method in class org.h2.tools.Recover
+
+
INTERNAL
+
+
getLocalTempTableConstraints() - Method in class org.h2.engine.SessionLocal
+
+
Get the map of constraints for all constraints on local, temporary + tables, if any.
+
+
getLocalTempTableIndexes() - Method in class org.h2.engine.SessionLocal
+
 
+
getLocalTempTables() - Method in class org.h2.engine.SessionLocal
+
 
+
getLockMode() - Method in class org.h2.engine.Database
+
 
+
getLockMode() - Method in enum org.h2.engine.IsolationLevel
+
+
Returns the LOCK_MODE equivalent for PageStore and old versions of H2.
+
+
getLocks() - Method in class org.h2.engine.SessionLocal
+
 
+
getLockTimeout() - Method in class org.h2.engine.Database
+
 
+
getLockTimeout() - Method in class org.h2.engine.SessionLocal
+
 
+
getLoginTimeout() - Method in class org.h2.jdbcx.JdbcConnectionPool
+
+
Gets the maximum time in seconds to wait for a free connection.
+
+
getLoginTimeout() - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Get the login timeout in seconds, 0 meaning no timeout.
+
+
getLogWriter() - Method in class org.h2.jdbcx.JdbcConnectionPool
+
+
INTERNAL
+
+
getLogWriter() - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Get the current log writer for this object.
+
+
getLong(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a long.
+
+
getLong(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a long.
+
+
getLong(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a long.
+
+
getLong(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a long.
+
+
getLong(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a long.
+
+
getLong(String) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a long.
+
+
getMainSchema() - Method in class org.h2.engine.Database
+
+
Returns main schema (usually PUBLIC).
+
+
getMaxBinaryLiteralLength() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the maximum length for hex values (characters).
+
+
getMaxCatalogNameLength() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the maximum length for a catalog name.
+
+
getMaxCharLiteralLength() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the maximum length for literals.
+
+
getMaxColumnNameLength() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the maximum length for column names.
+
+
getMaxColumnsInGroupBy() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the maximum number of columns in GROUP BY.
+
+
getMaxColumnsInIndex() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the maximum number of columns in CREATE INDEX.
+
+
getMaxColumnsInOrderBy() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the maximum number of columns in ORDER BY.
+
+
getMaxColumnsInSelect() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the maximum number of columns in SELECT.
+
+
getMaxColumnsInTable() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the maximum number of columns in CREATE TABLE.
+
+
getMaxConnections() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the maximum number of open connection.
+
+
getMaxConnections() - Method in class org.h2.jdbcx.JdbcConnectionPool
+
+
Gets the maximum number of connections to use.
+
+
getMaxCursorNameLength() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the maximum length for a cursor name.
+
+
getMaxFieldSize() - Method in class org.h2.jdbc.JdbcStatement
+
+
Gets the maximum number of bytes for a result set column.
+
+
getMaxIndexLength() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the maximum length for an index (in bytes).
+
+
getMaxLengthInplaceLob() - Method in class org.h2.engine.Database
+
 
+
getMaxLengthInplaceLob() - Method in class org.h2.engine.SessionRemote
+
 
+
getMaxLengthInplaceLob() - Method in class org.h2.tools.Recover
+
+
INTERNAL
+
+
getMaxMemoryRows() - Method in class org.h2.engine.Database
+
 
+
getMaxOperationMemory() - Method in class org.h2.engine.Database
+
 
+
getMaxProcedureNameLength() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the maximum length for a procedure name.
+
+
getMaxRows() - Method in class org.h2.jdbc.JdbcStatement
+
+
Gets the maximum number of rows for a ResultSet.
+
+
getMaxRowSize() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the maximum size of a row (in bytes).
+
+
getMaxSchemaNameLength() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the maximum length for a schema name.
+
+
getMaxStatementLength() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the maximum length of a statement.
+
+
getMaxStatements() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the maximum number of open statements.
+
+
getMaxTableNameLength() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the maximum length for a table name.
+
+
getMaxTablesInSelect() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the maximum number of tables in a SELECT.
+
+
getMaxUserNameLength() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the maximum length for a user name.
+
+
getMaxValue(int) - Method in class org.h2.tools.MultiDimension
+
+
Get the maximum value for the given dimension count.
+
+
getMessage() - Method in exception org.h2.jdbc.JdbcSQLDataException
+
 
+
getMessage() - Method in exception org.h2.jdbc.JdbcSQLException
+
 
+
getMessage() - Method in exception org.h2.jdbc.JdbcSQLFeatureNotSupportedException
+
 
+
getMessage() - Method in exception org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException
+
 
+
getMessage() - Method in exception org.h2.jdbc.JdbcSQLInvalidAuthorizationSpecException
+
 
+
getMessage() - Method in exception org.h2.jdbc.JdbcSQLNonTransientConnectionException
+
 
+
getMessage() - Method in exception org.h2.jdbc.JdbcSQLNonTransientException
+
 
+
getMessage() - Method in exception org.h2.jdbc.JdbcSQLSyntaxErrorException
+
 
+
getMessage() - Method in exception org.h2.jdbc.JdbcSQLTimeoutException
+
 
+
getMessage() - Method in exception org.h2.jdbc.JdbcSQLTransactionRollbackException
+
 
+
getMessage() - Method in exception org.h2.jdbc.JdbcSQLTransientException
+
 
+
getMetaData() - Method in class org.h2.jdbc.JdbcConnection
+
+
Gets the database meta data for this database.
+
+
getMetaData() - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Gets the result set metadata of the query returned when the statement is + executed.
+
+
getMetaData() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Gets the meta data of this result set.
+
+
getMetaData() - Method in class org.h2.tools.SimpleResultSet
+
+
Returns a reference to itself.
+
+
getMinutes() - Method in class org.h2.api.Interval
+
+
Returns minutes value, if any.
+
+
getMode() - Method in interface org.h2.engine.CastDataProvider
+
+
Returns the database mode.
+
+
getMode() - Method in class org.h2.engine.Database
+
 
+
getMode() - Method in class org.h2.engine.SessionLocal
+
 
+
getMode() - Method in class org.h2.engine.SessionRemote
+
 
+
getMode() - Method in class org.h2.jdbc.JdbcConnection
+
 
+
getModificationDataId() - Method in class org.h2.engine.Database
+
 
+
getModificationId() - Method in class org.h2.engine.DbObject
+
 
+
getModificationId() - Method in class org.h2.engine.SessionLocal
+
 
+
getModificationMetaId() - Method in class org.h2.engine.Database
+
 
+
getMonths() - Method in class org.h2.api.Interval
+
+
Returns months value, if any.
+
+
getMoreResults() - Method in class org.h2.jdbc.JdbcStatement
+
+
Moves to the next result set - however there is always only one result + set.
+
+
getMoreResults(int) - Method in class org.h2.jdbc.JdbcStatement
+
+
Move to the next result set.
+
+
getName() - Method in class org.h2.api.H2Type
+
 
+
getName() - Method in class org.h2.engine.ConnectionInfo
+
+
Get the unique and normalized database name (excluding settings).
+
+
getName() - Method in class org.h2.engine.Database
+
 
+
getName() - Method in class org.h2.engine.DbObject
+
+
Get the name.
+
+
getName() - Method in class org.h2.engine.Mode
+
 
+
getName() - Method in class org.h2.engine.Procedure
+
 
+
getNanosOfSecond() - Method in class org.h2.api.Interval
+
+
Returns value of fractional part of seconds (in nanoseconds), if any.
+
+
getNCharacterStream(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a reader.
+
+
getNCharacterStream(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a reader.
+
+
getNCharacterStream(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a reader.
+
+
getNCharacterStream(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a reader.
+
+
getNCharacterStream(int) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getNCharacterStream(String) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getNClob(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a Clob.
+
+
getNClob(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a Clob.
+
+
getNClob(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a Clob.
+
+
getNClob(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a Clob.
+
+
getNClob(int) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getNClob(String) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getNetworkConnectionInfo() - Method in class org.h2.engine.ConnectionInfo
+
+
Returns the network connection information, or null.
+
+
getNetworkConnectionInfo() - Method in class org.h2.engine.SessionLocal
+
+
Returns the network connection information, or null.
+
+
getNetworkTimeout() - Method in class org.h2.jdbc.JdbcConnection
+
+
[Not supported]
+
+
getNextId() - Method in class org.h2.engine.SessionRemote
+
 
+
getNextModificationDataId() - Method in class org.h2.engine.Database
+
 
+
getNextModificationMetaId() - Method in class org.h2.engine.Database
+
 
+
getNextRemoteSettingsId() - Method in class org.h2.engine.Database
+
 
+
getNextSystemIdentifier(String) - Method in class org.h2.engine.SessionLocal
+
+
Get the next system generated identifiers.
+
+
getNextValueFor(Sequence, Prepared) - Method in class org.h2.engine.SessionLocal
+
+
Returns the next value of the sequence in this session.
+
+
getNonKeywords() - Method in class org.h2.engine.SessionLocal
+
+
Gets bit set of non-keywords.
+
+
getNString(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a String.
+
+
getNString(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a String.
+
+
getNString(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a String.
+
+
getNString(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a String.
+
+
getNString(int) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getNString(String) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getNullString() - Method in class org.h2.tools.Csv
+
+
Get the current null string.
+
+
getNumericFunctions() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the list of numeric functions supported by this database.
+
+
getObject(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns a column value as a Java object.
+
+
getObject(int, Map<String, Class<?>>) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
[Not supported] Gets a column as a object using the specified type + mapping.
+
+
getObject(String, Map<String, Class<?>>) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
[Not supported] Gets a column as a object using the specified type + mapping.
+
+
getObject(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns a column value as a Java object.
+
+
getObject(int, Class<T>) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a Java object of the + specified type.
+
+
getObject(String, Class<T>) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a Java object of the + specified type.
+
+
getObject(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns a column value as a Java object.
+
+
getObject(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns a column value as a Java object.
+
+
getObject(int, Map<String, Class<?>>) - Method in class org.h2.jdbc.JdbcResultSet
+
+
[Not supported] Gets a column as a object using the specified type + mapping.
+
+
getObject(String, Map<String, Class<?>>) - Method in class org.h2.jdbc.JdbcResultSet
+
+
[Not supported] Gets a column as a object using the specified type + mapping.
+
+
getObject(int, Class<T>) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns a column value as a Java object of the specified type.
+
+
getObject(String, Class<T>) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns a column value as a Java object of the specified type.
+
+
getObject(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as an Object.
+
+
getObject(String) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as an Object.
+
+
getObject(int, Class<T>) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as an Object of the specified type.
+
+
getObject(String, Class<T>) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as an Object of the specified type.
+
+
getObject(int, Map<String, Class<?>>) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getObject(String, Map<String, Class<?>>) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getObjectInstance(Object, Name, Context, Hashtable<?, ?>) - Method in class org.h2.jdbcx.JdbcDataSourceFactory
+
+
Creates a new object using the specified location or reference + information.
+
+
getObjectType() - Method in class org.h2.engine.MetaRecord
+
 
+
getOptimizeReuseResults() - Method in class org.h2.engine.Database
+
 
+
getOriginalMessage() - Method in interface org.h2.jdbc.JdbcException
+
+
INTERNAL
+
+
getOriginalMessage() - Method in exception org.h2.jdbc.JdbcSQLDataException
+
 
+
getOriginalMessage() - Method in exception org.h2.jdbc.JdbcSQLException
+
 
+
getOriginalMessage() - Method in exception org.h2.jdbc.JdbcSQLFeatureNotSupportedException
+
 
+
getOriginalMessage() - Method in exception org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException
+
 
+
getOriginalMessage() - Method in exception org.h2.jdbc.JdbcSQLInvalidAuthorizationSpecException
+
 
+
getOriginalMessage() - Method in exception org.h2.jdbc.JdbcSQLNonTransientConnectionException
+
 
+
getOriginalMessage() - Method in exception org.h2.jdbc.JdbcSQLNonTransientException
+
 
+
getOriginalMessage() - Method in exception org.h2.jdbc.JdbcSQLSyntaxErrorException
+
 
+
getOriginalMessage() - Method in exception org.h2.jdbc.JdbcSQLTimeoutException
+
 
+
getOriginalMessage() - Method in exception org.h2.jdbc.JdbcSQLTransactionRollbackException
+
 
+
getOriginalMessage() - Method in exception org.h2.jdbc.JdbcSQLTransientException
+
 
+
getOriginalURL() - Method in class org.h2.engine.ConnectionInfo
+
+
Get the complete original database URL.
+
+
getPageSize() - Method in class org.h2.engine.Database
+
 
+
getParameterClassName(int) - Method in class org.h2.jdbc.JdbcParameterMetaData
+
+
Returns the Java class name of the parameter.
+
+
getParameterCount() - Method in class org.h2.jdbc.JdbcParameterMetaData
+
+
Returns the number of parameters.
+
+
getParameterMetaData() - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Get the parameter meta data of this prepared statement.
+
+
getParameterMode(int) - Method in class org.h2.jdbc.JdbcParameterMetaData
+
+
Returns the parameter mode.
+
+
getParameterType(int) - Method in class org.h2.jdbc.JdbcParameterMetaData
+
+
Returns the parameter type.
+
+
getParameterTypeName(int) - Method in class org.h2.jdbc.JdbcParameterMetaData
+
+
Returns the parameter type name.
+
+
getParentLogger() - Method in class org.h2.jdbcx.JdbcConnectionPool
+
+
[Not supported]
+
+
getParentLogger() - Method in class org.h2.jdbcx.JdbcDataSource
+
+
[Not supported]
+
+
getPassword() - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Get the current password.
+
+
getPooledConnection() - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Open a new pooled connection using the current URL, user name and + password.
+
+
getPooledConnection(String, String) - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Open a new pooled connection using the current URL and the specified user + name and password.
+
+
getPort() - Method in class org.h2.tools.Server
+
+
Gets the port this server is listening on.
+
+
getPowerOffCount() - Method in class org.h2.engine.Database
+
 
+
getPrecision(int) - Method in class org.h2.jdbc.JdbcParameterMetaData
+
+
Returns the parameter precision.
+
+
getPrecision(int) - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
Gets the precision for this column.
+
+
getPrecision(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the precision.
+
+
getPrepared() - Method in class org.h2.engine.Procedure
+
 
+
getPreserveWhitespace() - Method in class org.h2.tools.Csv
+
+
Whether whitespace in unquoted text is preserved.
+
+
getPrimaryKeys(String, String, String) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the primary key columns for a table.
+
+
getProcedure(String) - Method in class org.h2.engine.SessionLocal
+
+
Get the procedure with the given name, or null + if none exists.
+
+
getProcedureColumns(String, String, String, String) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the list of procedure columns.
+
+
getProcedures(String, String, String) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the list of procedures.
+
+
getProcedureTerm() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the term for "procedure".
+
+
getProperty(String, boolean) - Method in class org.h2.engine.ConnectionInfo
+
+
Get a boolean property if it is set and return the value.
+
+
getProperty(String, String) - Method in class org.h2.engine.ConnectionInfo
+
+
Get the value of the given property.
+
+
getPseudoColumns(String, String, String, String) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the list of pseudo and invisible columns.
+
+
getPublicRole() - Method in class org.h2.engine.Database
+
 
+
getQualifier() - Method in class org.h2.api.Interval
+
+
Returns qualifier of this interval.
+
+
getQueries() - Method in class org.h2.engine.QueryStatisticsData
+
 
+
getQueryStatistics() - Method in class org.h2.engine.Database
+
 
+
getQueryStatisticsData() - Method in class org.h2.engine.Database
+
 
+
getQueryTimeout() - Method in class org.h2.engine.SessionLocal
+
 
+
getQueryTimeout() - Method in class org.h2.jdbc.JdbcStatement
+
+
Gets the current query timeout in seconds.
+
+
getRandom() - Method in class org.h2.engine.SessionLocal
+
 
+
getRef(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
[Not supported] Gets a column as a reference.
+
+
getRef(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
[Not supported] Gets a column as a reference.
+
+
getRef(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
[Not supported] Gets a column as a reference.
+
+
getRef(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
[Not supported] Gets a column as a reference.
+
+
getRef(int) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getRef(String) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getReference() - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Get a new reference for this object, using the current settings.
+
+
getReferentialIntegrity() - Method in class org.h2.engine.Database
+
 
+
getRegular() - Static method in class org.h2.engine.Mode
+
 
+
getRemaining() - Method in class org.h2.api.Interval
+
+
Returns combined value of remaining fields of this interval.
+
+
getRemoteSettingsId() - Method in class org.h2.engine.Database
+
 
+
getResult() - Method in interface org.h2.api.Aggregate
+
+
This method returns the computed aggregate value.
+
+
getResult() - Method in interface org.h2.api.AggregateFunction
+
+
This method returns the computed aggregate value.
+
+
getResult() - Method in class org.h2.jdbc.JdbcResultSet
+
+
INTERNAL
+
+
getResult(PreparedStatement, int[], int[]) - Method in class org.h2.tools.MultiDimension
+
+
Executes a prepared query that was generated using generatePreparedQuery.
+
+
getResultSet() - Method in class org.h2.jdbc.JdbcArray
+
+
Returns the value as a result set.
+
+
getResultSet(Map<String, Class<?>>) - Method in class org.h2.jdbc.JdbcArray
+
+
Returns the value as a result set.
+
+
getResultSet(long, int) - Method in class org.h2.jdbc.JdbcArray
+
+
Returns the value as a result set.
+
+
getResultSet(long, int, Map<String, Class<?>>) - Method in class org.h2.jdbc.JdbcArray
+
+
Returns the value as a result set.
+
+
getResultSet() - Method in class org.h2.jdbc.JdbcStatement
+
+
Returns the last result set produces by this statement.
+
+
getResultSet() - Method in class org.h2.tools.SimpleResultSet.SimpleArray
+
+
INTERNAL
+
+
getResultSet(Map<String, Class<?>>) - Method in class org.h2.tools.SimpleResultSet.SimpleArray
+
+
INTERNAL
+
+
getResultSet(long, int) - Method in class org.h2.tools.SimpleResultSet.SimpleArray
+
+
INTERNAL
+
+
getResultSet(long, int, Map<String, Class<?>>) - Method in class org.h2.tools.SimpleResultSet.SimpleArray
+
+
INTERNAL
+
+
getResultSetConcurrency() - Method in class org.h2.jdbc.JdbcStatement
+
+
Gets the result set concurrency created by this object.
+
+
getResultSetHoldability() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the result set holdability.
+
+
getResultSetHoldability() - Method in class org.h2.jdbc.JdbcStatement
+
+
Gets the result set holdability.
+
+
getResultSetType() - Method in class org.h2.jdbc.JdbcStatement
+
+
Gets the result set type.
+
+
getRetentionTime() - Method in class org.h2.engine.Database
+
 
+
getRightForObject(DbObject) - Method in class org.h2.engine.RightOwner
+
+
Get the 'grant schema' right of this object.
+
+
getRightForRole(Role) - Method in class org.h2.engine.RightOwner
+
+
Get the 'grant role' right of this object.
+
+
getRightMask() - Method in class org.h2.engine.Right
+
 
+
getRights() - Method in class org.h2.engine.Right
+
 
+
getRow() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Gets the current row number.
+
+
getRow() - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the row number (1, 2,...) or 0 for no row.
+
+
getRowCountStandardDeviation() - Method in class org.h2.engine.QueryStatisticsData.QueryEntry
+
 
+
getRowFactory() - Method in class org.h2.engine.Database
+
 
+
getRowId(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
[Not supported] Returns the value of the specified column as a row id.
+
+
getRowId(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
[Not supported] Returns the value of the specified column as a row id.
+
+
getRowId(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
[Not supported] Returns the value of the specified column as a row id.
+
+
getRowId(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
[Not supported] Returns the value of the specified column as a row id.
+
+
getRowId(int) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getRowId(String) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getRowIdLifetime() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Get the lifetime of a rowid.
+
+
getSavepointId() - Method in class org.h2.jdbc.JdbcSavepoint
+
+
Get the generated id of this savepoint.
+
+
getSavepointName() - Method in class org.h2.jdbc.JdbcSavepoint
+
+
Get the name of this savepoint.
+
+
getScale(int) - Method in class org.h2.jdbc.JdbcParameterMetaData
+
+
Returns the parameter scale.
+
+
getScale(int) - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
Gets the scale for this column.
+
+
getScale(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the scale.
+
+
getSchema(String) - Method in class org.h2.engine.Database
+
+
Get the schema.
+
+
getSchema() - Method in class org.h2.jdbc.JdbcConnection
+
+
Retrieves this current schema name for this connection.
+
+
getSchemaName(int) - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
Returns the schema name.
+
+
getSchemaName(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns empty string.
+
+
getSchemas() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the list of schemas.
+
+
getSchemas(String, String) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the list of schemas in the database.
+
+
getSchemaSearchPath() - Method in class org.h2.engine.SessionLocal
+
 
+
getSchemaTerm() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the term for "schema".
+
+
getScriptDirectory() - Static method in class org.h2.engine.SysProperties
+
+
System property h2.scriptDirectory (default: empty + string).
+
+
getSearchStringEscape() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the default escape character for DatabaseMetaData search + patterns.
+
+
getSeconds() - Method in class org.h2.api.Interval
+
+
Returns value of integer part of seconds, if any.
+
+
getSecondsAndNanos() - Method in class org.h2.api.Interval
+
+
Returns seconds value measured in nanoseconds, if any.
+
+
getService() - Method in class org.h2.tools.Server
+
+
Get the service attached to this server.
+
+
getSession() - Method in class org.h2.jdbc.JdbcConnection
+
+
INTERNAL
+
+
getSessionCount() - Method in class org.h2.engine.Database
+
 
+
getSessions(boolean) - Method in class org.h2.engine.Database
+
+
Get all sessions that are currently connected to the database.
+
+
getSessionStart() - Method in class org.h2.engine.SessionLocal
+
 
+
getSettings() - Method in class org.h2.engine.Database
+
 
+
getSettings() - Method in class org.h2.engine.SettingsBase
+
+
Get all settings.
+
+
getShort(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a short.
+
+
getShort(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a short.
+
+
getShort(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a short.
+
+
getShort(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a short.
+
+
getShort(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a short.
+
+
getShort(String) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a short.
+
+
getShortName() - Method in class org.h2.engine.Database
+
 
+
getSnapshotDataModificationId() - Method in class org.h2.engine.SessionLocal
+
+
Returns the data modification id of transaction's snapshot, or 0 if + isolation level doesn't use snapshots.
+
+
getSortedSettings() - Method in class org.h2.engine.SettingsBase
+
+
Get all settings in alphabetical order.
+
+
getSource(Class<T>) - Method in class org.h2.jdbc.JdbcSQLXML
+
 
+
getSQL(int) - Method in class org.h2.engine.DbObject
+
 
+
getSQL(StringBuilder, int) - Method in class org.h2.engine.DbObject
+
 
+
getSQL() - Method in enum org.h2.engine.IsolationLevel
+
+
Returns the SQL representation of this isolation level.
+
+
getSQL() - Method in class org.h2.engine.MetaRecord
+
 
+
getSQL(int) - Method in class org.h2.engine.Setting
+
 
+
getSQL(StringBuilder, int) - Method in class org.h2.engine.Setting
+
 
+
getSQL() - Method in interface org.h2.jdbc.JdbcException
+
+
Returns the SQL statement.
+
+
getSQL() - Method in exception org.h2.jdbc.JdbcSQLDataException
+
 
+
getSQL() - Method in exception org.h2.jdbc.JdbcSQLException
+
 
+
getSQL() - Method in exception org.h2.jdbc.JdbcSQLFeatureNotSupportedException
+
 
+
getSQL() - Method in exception org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException
+
 
+
getSQL() - Method in exception org.h2.jdbc.JdbcSQLInvalidAuthorizationSpecException
+
 
+
getSQL() - Method in exception org.h2.jdbc.JdbcSQLNonTransientConnectionException
+
 
+
getSQL() - Method in exception org.h2.jdbc.JdbcSQLNonTransientException
+
 
+
getSQL() - Method in exception org.h2.jdbc.JdbcSQLSyntaxErrorException
+
 
+
getSQL() - Method in exception org.h2.jdbc.JdbcSQLTimeoutException
+
 
+
getSQL() - Method in exception org.h2.jdbc.JdbcSQLTransactionRollbackException
+
 
+
getSQL() - Method in exception org.h2.jdbc.JdbcSQLTransientException
+
 
+
getSQLKeywords() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the comma-separated list of all SQL keywords that are not supported + as unquoted identifiers, in addition to the SQL:2003 reserved words.
+
+
getSQLStateType() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the SQL State type.
+
+
getSQLXML(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a SQLXML object.
+
+
getSQLXML(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a SQLXML object.
+
+
getSQLXML(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a SQLXML.
+
+
getSQLXML(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a SQLXML.
+
+
getSQLXML(int) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getSQLXML(String) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getState(int) - Static method in class org.h2.api.ErrorCode
+
+
INTERNAL
+
+
getState() - Method in class org.h2.engine.SessionLocal
+
 
+
getStatement() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the statement that created this object.
+
+
getStatement() - Method in class org.h2.tools.SimpleResultSet
+
+
Returns null.
+
+
getStaticSettings() - Method in class org.h2.engine.Session
+
+
Returns static settings.
+
+
getStaticSettings() - Method in class org.h2.engine.SessionLocal
+
 
+
getStaticSettings() - Method in class org.h2.engine.SessionRemote
+
 
+
getStaticSettings() - Method in class org.h2.jdbc.JdbcConnection
+
+
INTERNAL
+
+
getStatus() - Method in class org.h2.tools.Server
+
+
Get the status of this server.
+
+
getStore() - Method in class org.h2.engine.Database
+
 
+
getString(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a String.
+
+
getString(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a String.
+
+
getString(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a String.
+
+
getString(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a String.
+
+
getString() - Method in class org.h2.jdbc.JdbcSQLXML
+
 
+
getString(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a String.
+
+
getString(String) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a String.
+
+
getStringFunctions() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the list of string functions supported by this database.
+
+
getStringValue() - Method in class org.h2.engine.Setting
+
 
+
getSubString(long, int) - Method in class org.h2.jdbc.JdbcClob
+
+
Returns a substring.
+
+
getSuperTables(String, String, String) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Get the list of super tables of a table.
+
+
getSuperTypes(String, String, String) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
[Not supported]
+
+
getSystemFunctions() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the list of system functions supported by this database.
+
+
getSystemSession() - Method in class org.h2.engine.Database
+
 
+
getSystemUser() - Method in class org.h2.engine.Database
+
+
Returns system user.
+
+
getTableEngine(String) - Method in class org.h2.engine.Database
+
+
Get the table engine class, loading it if needed.
+
+
getTableName(int) - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
Returns the table name.
+
+
getTableName(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns empty string.
+
+
getTablePrivileges(String, String, String) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the list of table privileges.
+
+
getTables(String, String, String, String[]) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the list of tables in the database.
+
+
getTableTypes() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the list of table types.
+
+
getTempFileDeleter() - Method in class org.h2.engine.Database
+
 
+
getTempFileDeleter() - Method in class org.h2.engine.SessionRemote
+
 
+
getTempFileDeleter() - Method in class org.h2.tools.Recover
+
+
INTERNAL
+
+
getTempTableName(String, SessionLocal) - Method in class org.h2.engine.Database
+
+
Get a unique temporary table name.
+
+
getTime(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a java.sql.Time.
+
+
getTime(int, Calendar) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a java.sql.Time using a + specified time zone.
+
+
getTime(String, Calendar) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a java.sql.Time using a + specified time zone.
+
+
getTime(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a java.sql.Time.
+
+
getTime(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a java.sql.Time.
+
+
getTime(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a java.sql.Time.
+
+
getTime(int, Calendar) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a java.sql.Time using a + specified time zone.
+
+
getTime(String, Calendar) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a java.sql.Time using a + specified time zone.
+
+
getTime(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as an java.sql.Time.
+
+
getTime(String) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a java.sql.Time.
+
+
getTime(int, Calendar) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getTime(String, Calendar) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getTimeDateFunctions() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the list of date and time functions supported by this database.
+
+
getTimestamp(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a java.sql.Timestamp.
+
+
getTimestamp(int, Calendar) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a java.sql.Timestamp using a + specified time zone.
+
+
getTimestamp(String, Calendar) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a java.sql.Timestamp using a + specified time zone.
+
+
getTimestamp(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns the value of the specified column as a java.sql.Timestamp.
+
+
getTimestamp(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a java.sql.Timestamp.
+
+
getTimestamp(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a java.sql.Timestamp.
+
+
getTimestamp(int, Calendar) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a java.sql.Timestamp using a + specified time zone.
+
+
getTimestamp(String, Calendar) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns the value of the specified column as a java.sql.Timestamp.
+
+
getTimestamp(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as an java.sql.Timestamp.
+
+
getTimestamp(String) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the value as a java.sql.Timestamp.
+
+
getTimestamp(int, Calendar) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getTimestamp(String, Calendar) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getTimeZone() - Method in class org.h2.engine.ConnectionInfo
+
+
Returns the time zone.
+
+
getTrace(int) - Method in class org.h2.engine.Database
+
+
Get the trace object for the given module id.
+
+
getTrace() - Method in class org.h2.engine.Session
+
+
Get the trace object
+
+
getTrace() - Method in class org.h2.engine.SessionLocal
+
 
+
getTrace() - Method in class org.h2.engine.SessionRemote
+
 
+
getTraceSystem() - Method in class org.h2.engine.Database
+
 
+
getTraceSystem() - Static method in class org.h2.jdbcx.JdbcDataSourceFactory
+
+
INTERNAL
+
+
getTransaction() - Method in class org.h2.engine.SessionLocal
+
+
Get the transaction to use for this session.
+
+
getTransactionId() - Method in class org.h2.engine.SessionLocal
+
 
+
getTransactionIsolation() - Method in class org.h2.jdbc.JdbcConnection
+
+
Returns the current transaction isolation level.
+
+
getTransactionTimeout() - Method in class org.h2.jdbcx.JdbcXAConnection
+
+
Get the transaction timeout.
+
+
getType(int[]) - Method in interface org.h2.api.AggregateFunction
+
+
This method must return the SQL type of the method, given the SQL type of + the input data.
+
+
getType() - Method in class org.h2.engine.Comment
+
 
+
getType() - Method in class org.h2.engine.DbObject
+
+
Get the object type.
+
+
getType() - Method in class org.h2.engine.Right
+
 
+
getType() - Method in class org.h2.engine.Role
+
 
+
getType() - Method in class org.h2.engine.Setting
+
 
+
getType() - Method in class org.h2.engine.User
+
 
+
getType() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Get the result set type.
+
+
getType() - Method in class org.h2.tools.SimpleResultSet
+
+
Returns the result set type.
+
+
getTypeInfo() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the list of data types.
+
+
getTypeMap() - Method in class org.h2.jdbc.JdbcConnection
+
+
Gets the type map.
+
+
getTypeName(int, int) - Method in enum org.h2.api.IntervalQualifier
+
+
Returns full type name.
+
+
getTypeName(StringBuilder, int, int, boolean) - Method in enum org.h2.api.IntervalQualifier
+
+
Appends full type name to the specified string builder.
+
+
getUDTs(String, String, String, int[]) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Gets the list of user-defined data types.
+
+
getUnicodeStream(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Deprecated. +
since JDBC 2.0, use getCharacterStream
+
+
+
getUnicodeStream(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Deprecated. +
since JDBC 2.0, use setCharacterStream
+
+
+
getUnicodeStream(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Deprecated. +
INTERNAL
+
+
+
getUnicodeStream(String) - Method in class org.h2.tools.SimpleResultSet
+
+
Deprecated. +
INTERNAL
+
+
+
getUpdateCount() - Method in class org.h2.jdbc.JdbcStatement
+
+
Returns the last update count of this statement.
+
+
getUpdateRow() - Method in class org.h2.jdbc.JdbcResultSet
+
+
INTERNAL
+
+
getURL() - Method in class org.h2.engine.ConnectionInfo
+
+
Get the database URL.
+
+
getURL(int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
[Not supported]
+
+
getURL(String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
[Not supported]
+
+
getURL() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the database URL for this connection.
+
+
getURL(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
[Not supported]
+
+
getURL(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
[Not supported]
+
+
getURL() - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Get the current URL.
+
+
getUrl() - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Get the current URL.
+
+
getURL() - Method in class org.h2.tools.Server
+
+
Gets the URL of this server.
+
+
getURL(int) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getURL(String) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
getUser(String) - Method in class org.h2.engine.Database
+
+
Get user with the given name.
+
+
getUser() - Method in class org.h2.engine.SessionLocal
+
 
+
getUser() - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Get the current user name.
+
+
getUserName() - Method in class org.h2.engine.ConnectionInfo
+
+
Get the name of the user.
+
+
getUserName() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the user name as passed to DriverManager.getConnection(url, user, + password).
+
+
getVariable(String) - Method in class org.h2.engine.SessionLocal
+
+
Get the value of the specified user defined variable.
+
+
getVariableIntLength(int) - Static method in class org.h2.tools.CompressTool
+
+
Get a variable size integer length using Rice coding.
+
+
getVariableNames() - Method in class org.h2.engine.SessionLocal
+
+
Get the list of variable names that are set for this session.
+
+
getVendor() - Method in class org.h2.api.H2Type
+
 
+
getVendorTypeNumber() - Method in class org.h2.api.H2Type
+
+
Returns the vendor specific type number for the data type.
+
+
getVersionColumns(String, String, String) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Get the list of columns that are update when any value is updated.
+
+
getViewIndexCache(boolean) - Method in class org.h2.engine.SessionLocal
+
+
Get the view cache for this session.
+
+
getWaitForLock() - Method in class org.h2.engine.SessionLocal
+
 
+
getWaitForLockThread() - Method in class org.h2.engine.SessionLocal
+
 
+
getWarnings() - Method in class org.h2.jdbc.JdbcConnection
+
+
Gets the first warning reported by calls on this object.
+
+
getWarnings() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Gets the first warning reported by calls on this object.
+
+
getWarnings() - Method in class org.h2.jdbc.JdbcStatement
+
+
Gets the first warning reported by calls on this object.
+
+
getWarnings() - Method in class org.h2.tools.SimpleResultSet
+
+
Returns null.
+
+
getWriteColumnHeader() - Method in class org.h2.tools.Csv
+
+
Whether the column header is written.
+
+
getXAConnection() - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Open a new XA connection using the current URL, user name and password.
+
+
getXAConnection(String, String) - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Open a new XA connection using the current URL and the specified user + name and password.
+
+
getXAResource() - Method in class org.h2.jdbcx.JdbcXAConnection
+
+
Get the XAResource object.
+
+
getYears() - Method in class org.h2.api.Interval
+
+
Returns years value, if any.
+
+
grantRight(DbObject, Right) - Method in class org.h2.engine.RightOwner
+
+
Grant a right for the given table.
+
+
grantRole(Role, Right) - Method in class org.h2.engine.RightOwner
+
+
Grant a role to this object.
+
+
GROUP_BY_NOT_IN_THE_RESULT - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90157 is thrown when the integer + index that is used in the GROUP BY is not in the SELECT list
+
+
groupByColumnIndex - Variable in class org.h2.engine.Mode
+
+
Allow to use GROUP BY n, where n is column index in the SELECT list, similar to ORDER BY
+
+
GUIConsole - Class in org.h2.tools
+
+
Console for environments with AWT support.
+
+
GUIConsole() - Constructor for class org.h2.tools.GUIConsole
+
 
+
+ + + +

H

+
+
H2_BROWSER - Static variable in class org.h2.engine.SysProperties
+
+
INTERNAL
+
+
H2_SCRIPT_DIRECTORY - Static variable in class org.h2.engine.SysProperties
+
+
INTERNAL
+
+
H2Type - Class in org.h2.api
+
+
Data types of H2.
+
+
hasChanged(Object[], Object[], int[]) - Static method in class org.h2.fulltext.FullText
+
+
Check if a the indexed columns of a row probably have changed.
+
+
hasDays() - Method in enum org.h2.api.IntervalQualifier
+
+
Returns whether interval with this qualifier has days.
+
+
hashCode() - Method in class org.h2.api.Interval
+
 
+
hashCode() - Method in class org.h2.engine.SessionLocal
+
 
+
hasHours() - Method in enum org.h2.api.IntervalQualifier
+
+
Returns whether interval with this qualifier has hours.
+
+
hasMinutes() - Method in enum org.h2.api.IntervalQualifier
+
+
Returns whether interval with this qualifier has minutes.
+
+
hasMonths() - Method in enum org.h2.api.IntervalQualifier
+
+
Returns whether interval with this qualifier has months.
+
+
hasMultipleFields() - Method in enum org.h2.api.IntervalQualifier
+
+
Returns whether interval with this qualifier has multiple fields.
+
+
hasPendingTransaction() - Method in class org.h2.engine.Session
+
+
Check whether this session has a pending transaction.
+
+
hasPendingTransaction() - Method in class org.h2.engine.SessionLocal
+
 
+
hasPendingTransaction() - Method in class org.h2.engine.SessionRemote
+
 
+
hasPreparedTransaction() - Method in class org.h2.engine.SessionLocal
+
+
Checks presence of prepared transaction in this session.
+
+
hasSeconds() - Method in enum org.h2.api.IntervalQualifier
+
+
Returns whether interval with this qualifier has seconds.
+
+
hasTableRight(Table, int) - Method in class org.h2.engine.User
+
+
See if this user has the given rights for this database object.
+
+
hasYears() - Method in enum org.h2.api.IntervalQualifier
+
+
Returns whether interval with this qualifier has years.
+
+
HEX_STRING_ODD_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90003 is thrown when + trying to convert a String to a binary value.
+
+
HEX_STRING_WRONG_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90004 is thrown when + trying to convert a text to binary, but the expression contains + a non-hexadecimal character.
+
+
+ + + +

I

+
+
id - Variable in class org.h2.fulltext.IndexInfo
+
+
The index id.
+
+
IDENTICAL_EXPRESSIONS_SHOULD_BE_USED - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 42131 is thrown when + identical expressions should be used, but different + expressions were found.
+
+
identityClause - Variable in class org.h2.engine.Mode
+
+
Whether SQL Server-style IDENTITY clause is supported.
+
+
identityColumnsHaveDefaultOnNull - Variable in class org.h2.engine.Mode
+
+
If true, identity columns have DEFAULT ON NULL clause.
+
+
identityDataType - Variable in class org.h2.engine.Mode
+
+
Whether IDENTITY pseudo data type is supported.
+
+
ignoreCatalogs - Variable in class org.h2.engine.DbSettings
+
+
Database setting IGNORE_CATALOGS + (default: false).
+
+
INDEX - Static variable in class org.h2.engine.DbObject
+
+
This object is an index.
+
+
INDEX_ALREADY_EXISTS_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 42111 is thrown when + trying to create an index if an index with the same name already exists.
+
+
INDEX_BELONGS_TO_CONSTRAINT_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90085 is thrown when + trying to manually drop an index that was generated by the system + because of a unique or referential constraint.
+
+
INDEX_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 42112 is thrown when + trying to drop or reference an index that does not exist.
+
+
indexColumns - Variable in class org.h2.fulltext.IndexInfo
+
+
The column indexes of the index columns.
+
+
indexDefinitionInCreateTable - Variable in class org.h2.engine.Mode
+
+
Creating indexes in the CREATE TABLE statement is allowed using + INDEX(..) or KEY(..).
+
+
IndexInfo - Class in org.h2.fulltext
+
+
The settings of one full text search index.
+
+
IndexInfo() - Constructor for class org.h2.fulltext.IndexInfo
+
 
+
INFORMATION_SCHEMA_ID - Static variable in class org.h2.engine.Constants
+
+
The identity of INFORMATION_SCHEMA.
+
+
init(Connection) - Method in interface org.h2.api.Aggregate
+
+
This method is called when the aggregate function is used.
+
+
init(Connection) - Method in interface org.h2.api.AggregateFunction
+
+
This method is called when the aggregate function is used.
+
+
init(String) - Method in interface org.h2.api.DatabaseEventListener
+
+
This method is called just after creating the object.
+
+
init(Connection, String, String, String, boolean, int) - Method in interface org.h2.api.Trigger
+
+
This method is called by the database engine once when initializing the + trigger.
+
+
init(Connection, String, String, String, boolean, int) - Method in class org.h2.fulltext.FullText.FullTextTrigger
+
+
INTERNAL
+
+
init(Connection) - Static method in class org.h2.fulltext.FullText
+
+
Initializes full text search functionality for this database.
+
+
init(Connection, String, String, String, boolean, int) - Method in class org.h2.fulltext.FullTextLucene.FullTextTrigger
+
+
INTERNAL
+
+
init(Connection) - Static method in class org.h2.fulltext.FullTextLucene
+
+
Initializes full text search functionality for this database.
+
+
init(Connection, String, String, String, boolean, int) - Method in class org.h2.tools.TriggerAdapter
+
+
This method is called by the database engine once when initializing the + trigger.
+
+
INITIAL_LOCK_TIMEOUT - Static variable in class org.h2.engine.Constants
+
+
For testing, the lock timeout is smaller than for interactive use cases.
+
+
INSERT - Static variable in interface org.h2.api.Trigger
+
+
The trigger is called for INSERT statements.
+
+
INSERT - Static variable in class org.h2.engine.Right
+
+
The right bit mask that means: inserting rows into a table is allowed.
+
+
insertOnConflict - Variable in class org.h2.engine.Mode
+
+
PostgreSQL style INSERT ...
+
+
insertRow() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Inserts the current row.
+
+
insertRow() - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
insertsAreDetected(int) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether inserts are detected.
+
+
INTEGER - Static variable in class org.h2.api.H2Type
+
+
The INTEGER data type.
+
+
interleave(int...) - Method in class org.h2.tools.MultiDimension
+
+
Convert the multi-dimensional value into a one-dimensional (scalar) + value.
+
+
interleave(int, int) - Method in class org.h2.tools.MultiDimension
+
+
Convert the two-dimensional value into a one-dimensional (scalar) value.
+
+
Interval - Class in org.h2.api
+
+
INTERVAL representation for result sets.
+
+
Interval(IntervalQualifier, boolean, long, long) - Constructor for class org.h2.api.Interval
+
+
Creates a new interval.
+
+
INTERVAL_DAY - Static variable in class org.h2.api.H2Type
+
+
The INTERVAL DAY data type.
+
+
INTERVAL_DAY_TO_HOUR - Static variable in class org.h2.api.H2Type
+
+
The INTERVAL DAY TO HOUR data type.
+
+
INTERVAL_DAY_TO_MINUTE - Static variable in class org.h2.api.H2Type
+
+
The INTERVAL DAY TO MINUTE data type.
+
+
INTERVAL_DAY_TO_SECOND - Static variable in class org.h2.api.H2Type
+
+
The INTERVAL DAY TO SECOND data type.
+
+
INTERVAL_HOUR - Static variable in class org.h2.api.H2Type
+
+
The INTERVAL HOUR data type.
+
+
INTERVAL_HOUR_TO_MINUTE - Static variable in class org.h2.api.H2Type
+
+
The INTERVAL HOUR TO MINUTE data type.
+
+
INTERVAL_HOUR_TO_SECOND - Static variable in class org.h2.api.H2Type
+
+
The INTERVAL HOUR TO SECOND data type.
+
+
INTERVAL_MINUTE - Static variable in class org.h2.api.H2Type
+
+
The INTERVAL MINUTE data type.
+
+
INTERVAL_MINUTE_TO_SECOND - Static variable in class org.h2.api.H2Type
+
+
The INTERVAL MINUTE TO SECOND data type.
+
+
INTERVAL_MONTH - Static variable in class org.h2.api.H2Type
+
+
The INTERVAL MONTH data type.
+
+
INTERVAL_SECOND - Static variable in class org.h2.api.H2Type
+
+
The INTERVAL SECOND data type.
+
+
INTERVAL_YEAR - Static variable in class org.h2.api.H2Type
+
+
The INTERVAL YEAR data type.
+
+
INTERVAL_YEAR_TO_MONTH - Static variable in class org.h2.api.H2Type
+
+
The INTERVAL YEAR TO MONTH data type.
+
+
IntervalQualifier - Enum in org.h2.api
+
+
Interval qualifier.
+
+
INVALID_CLASS_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90125 is thrown when + PreparedStatement.setBigDecimal is called with object that extends the + class BigDecimal, and the system property h2.allowBigDecimalExtensions is + not set.
+
+
INVALID_DATABASE_NAME_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90138 is thrown when + + trying to open a persistent database using an incorrect database name.
+
+
INVALID_DATETIME_CONSTANT_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 22007 is thrown when + a text can not be converted to a date, time, or timestamp constant.
+
+
INVALID_NAME_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 42602 is thrown when + invalid name of identifier is used.
+
+
INVALID_PARAMETER_COUNT_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 7001 is thrown when + trying to call a function with the wrong number of parameters.
+
+
INVALID_PRECEDING_OR_FOLLOWING_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 22013 is thrown when preceding or + following size in a window function is null or negative.
+
+
INVALID_TO_CHAR_FORMAT - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90010 is thrown when + trying to format a timestamp or number using TO_CHAR + with an invalid format.
+
+
INVALID_TO_DATE_FORMAT - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90056 is thrown when trying to format a + timestamp using TO_DATE and TO_TIMESTAMP with an invalid format.
+
+
INVALID_TRIGGER_FLAGS_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90005 is thrown when + trying to create a trigger with invalid combination of flags.
+
+
INVALID_USE_OF_AGGREGATE_FUNCTION_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90054 is thrown when + an aggregate function is used where it is not allowed.
+
+
INVALID_VALUE_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90008 is thrown when + trying to use a value that is not valid for the given operation.
+
+
INVALID_VALUE_PRECISION - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90150 is thrown when + trying to use an invalid precision.
+
+
INVALID_VALUE_SCALE - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90151 is thrown when + trying to use an invalid scale or fractional seconds precision.
+
+
invalidate() - Method in class org.h2.engine.DbObject
+
+
Set the main attributes to null to make sure the object is no longer + used.
+
+
IO_BUFFER_SIZE - Static variable in class org.h2.engine.Constants
+
+
The block size for I/O operations.
+
+
IO_BUFFER_SIZE_COMPRESS - Static variable in class org.h2.engine.Constants
+
+
The block size used to compress data in the LZFOutputStream.
+
+
IO_EXCEPTION_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90028 is thrown when + an input / output error occurred.
+
+
IO_EXCEPTION_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90031 is thrown when + an input / output error occurred.
+
+
isAdmin() - Method in class org.h2.engine.User
+
 
+
isAfterLast() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Checks if the current position is after the last row, that means next() + was called and returned false, and there was at least one row.
+
+
isAfterLast() - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
isAllowBuiltinAliasOverride() - Method in class org.h2.engine.Database
+
 
+
isAutoIncrement(int) - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
Checks if this an autoincrement column.
+
+
isAutoIncrement(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns false.
+
+
isBeforeFirst() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Checks if the current position is before the first row, that means next() + was not called yet, and there is at least one row.
+
+
isBeforeFirst() - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
isCancelled() - Method in class org.h2.jdbc.JdbcStatement
+
+
Check whether the statement was cancelled.
+
+
isCaseSensitive(int) - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
Checks if this column is case sensitive.
+
+
isCaseSensitive(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns true.
+
+
isCatalogAtStart() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether the catalog is at the beginning.
+
+
isClosed() - Method in class org.h2.engine.Session
+
+
Check if close was called.
+
+
isClosed() - Method in class org.h2.engine.SessionLocal
+
 
+
isClosed() - Method in class org.h2.engine.SessionRemote
+
 
+
isClosed() - Method in class org.h2.jdbc.JdbcConnection
+
+
Returns true if this connection has been closed.
+
+
isClosed() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns whether this result set is closed.
+
+
isClosed() - Method in class org.h2.jdbc.JdbcStatement
+
+
Returns whether this statement is closed.
+
+
isClosed() - Method in class org.h2.tools.SimpleResultSet
+
+
Returns whether this result set has been closed.
+
+
isCloseOnCompletion() - Method in class org.h2.jdbc.JdbcStatement
+
+
Returns whether this statement will be closed when its dependent result + set is closed.
+
+
isClosing() - Method in class org.h2.engine.Database
+
+
Check if the database is in the process of closing.
+
+
isClustered() - Method in class org.h2.engine.SessionRemote
+
+
Returns true if the connection was opened in cluster mode.
+
+
isCommon(int) - Static method in class org.h2.api.ErrorCode
+
+
INTERNAL
+
+
isCurrency(int) - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
Checks if this is a currency column.
+
+
isCurrency(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns false.
+
+
isDayTime() - Method in enum org.h2.api.IntervalQualifier
+
+
Returns whether interval with this qualifier is a day-time interval.
+
+
isDefinitelyWritable(int) - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
Checks whether a write on this column will definitely succeed.
+
+
isDefinitelyWritable(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns false.
+
+
isFirst() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Checks if the current position is row 1, that means next() was called + once and returned true.
+
+
isFirst() - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
isIgnoredByParser(String) - Static method in class org.h2.engine.ConnectionInfo
+
+
Returns whether setting with the specified name should be ignored by + parser.
+
+
isLast() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Checks if the current position is the last row, that means next() was + called and did not yet returned false, but will in the next call.
+
+
isLast() - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
isLazyQueryExecution() - Method in class org.h2.engine.SessionLocal
+
 
+
isNegative() - Method in class org.h2.api.Interval
+
+
Returns where the interval is negative.
+
+
isNullable(int) - Method in class org.h2.jdbc.JdbcParameterMetaData
+
+
Checks if this is nullable parameter.
+
+
isNullable(int) - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
Checks if this is nullable column.
+
+
isNullable(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns ResultSetMetaData.columnNullableUnknown.
+
+
IsolationLevel - Enum in org.h2.engine
+
+
Level of isolation.
+
+
isolationLevelInSelectOrInsertStatement - Variable in class org.h2.engine.Mode
+
+
can set the isolation level using WITH {RR|RS|CS|UR}
+
+
isOldInformationSchema() - Method in class org.h2.engine.Session
+
+
Returns whether INFORMATION_SCHEMA contains old-style tables.
+
+
isOldInformationSchema() - Method in class org.h2.engine.SessionLocal
+
 
+
isOldInformationSchema() - Method in class org.h2.engine.SessionRemote
+
 
+
isOpen() - Method in class org.h2.engine.SessionLocal
+
 
+
isParsingCreateView() - Method in class org.h2.engine.SessionLocal
+
 
+
isPersistent() - Method in class org.h2.engine.ConnectionInfo
+
+
Check if the referenced database is persistent.
+
+
isPersistent() - Method in class org.h2.engine.Database
+
+
Check if this database is disk-based.
+
+
isPoolable() - Method in class org.h2.jdbc.JdbcStatement
+
+
Returns whether this object is poolable.
+
+
isQuirksMode() - Method in class org.h2.engine.SessionLocal
+
+
Returns whether quirks mode is enabled explicitly or implicitly.
+
+
isReadOnly() - Method in class org.h2.engine.Database
+
 
+
isReadOnly() - Method in class org.h2.jdbc.JdbcConnection
+
+
Returns true if the database is read-only.
+
+
isReadOnly() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns the same as Connection.isReadOnly().
+
+
isReadOnly(int) - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
Checks if this column is read only.
+
+
isReadOnly(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns true.
+
+
isRemote() - Method in class org.h2.engine.ConnectionInfo
+
+
Check if this is a remote connection.
+
+
isRemote() - Method in class org.h2.engine.Session
+
+
Check if this session is remote or embedded.
+
+
isRemote() - Method in class org.h2.engine.SessionLocal
+
 
+
isRemote() - Method in class org.h2.engine.SessionRemote
+
 
+
isRoleGranted(Role) - Method in class org.h2.engine.RightOwner
+
+
Check if a role has been granted for this right owner.
+
+
isRunning(boolean) - Method in class org.h2.tools.Server
+
+
Checks if the server is running.
+
+
isSameRM(XAResource) - Method in class org.h2.jdbcx.JdbcXAConnection
+
+
Checks if this is the same XAResource.
+
+
isSearchable(int) - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
Checks if this column is searchable.
+
+
isSearchable(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns true.
+
+
isSigned(int) - Method in class org.h2.jdbc.JdbcParameterMetaData
+
+
Checks if this parameter is signed.
+
+
isSigned(int) - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
Checks if this column is signed.
+
+
isSigned(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns true.
+
+
isSimpleIdentifier(String) - Method in class org.h2.jdbc.JdbcStatement
+
 
+
isSimpleIdentifier(String) - Method in interface org.h2.jdbc.JdbcStatementBackwardsCompat
+
+
Checks if specified identifier may be used without quotes.
+
+
isStarting() - Method in class org.h2.engine.Database
+
+
Check if the database is currently opening.
+
+
isSysTableLocked() - Method in class org.h2.engine.Database
+
+
Checks if the system table (containing the catalog) is locked.
+
+
isSysTableLockedBy(SessionLocal) - Method in class org.h2.engine.Database
+
+
Checks if the system table (containing the catalog) is locked by the + given session.
+
+
isTemporary() - Method in class org.h2.engine.DbObject
+
+
Check if this object is temporary (for example, a temporary table).
+
+
isTruncateLargeLength() - Method in class org.h2.engine.SessionLocal
+
+
Returns parsing mode of data types with too large length.
+
+
isValid() - Method in class org.h2.engine.DbObject
+
 
+
isValid(int) - Method in class org.h2.jdbc.JdbcConnection
+
+
Returns true if this connection is still valid.
+
+
isVariableBinary() - Method in class org.h2.engine.SessionLocal
+
+
Returns BINARY data type parsing mode.
+
+
isWrapperFor(Class<?>) - Method in class org.h2.jdbc.JdbcConnection
+
+
Checks if unwrap can return an object of this class.
+
+
isWrapperFor(Class<?>) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Checks if unwrap can return an object of this class.
+
+
isWrapperFor(Class<?>) - Method in class org.h2.jdbc.JdbcParameterMetaData
+
+
Checks if unwrap can return an object of this class.
+
+
isWrapperFor(Class<?>) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Checks if unwrap can return an object of this class.
+
+
isWrapperFor(Class<?>) - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
Checks if unwrap can return an object of this class.
+
+
isWrapperFor(Class<?>) - Method in class org.h2.jdbc.JdbcStatement
+
+
Checks if unwrap can return an object of this class.
+
+
isWrapperFor(Class<?>) - Method in class org.h2.jdbcx.JdbcConnectionPool
+
+
Checks if unwrap can return an object of this class.
+
+
isWrapperFor(Class<?>) - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Checks if unwrap can return an object of this class.
+
+
isWrapperFor(Class<?>) - Method in class org.h2.tools.SimpleResultSet
+
+
Checks if unwrap can return an object of this class.
+
+
isWritable(int) - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
Checks whether it is possible for a write on this column to succeed.
+
+
isWritable(int) - Method in class org.h2.tools.SimpleResultSet
+
+
Returns false.
+
+
isYearMonth() - Method in enum org.h2.api.IntervalQualifier
+
+
Returns whether interval with this qualifier is a year-month interval.
+
+
+ + + +

J

+
+
JAVA_OBJECT - Static variable in class org.h2.api.H2Type
+
+
The JAVA_OBJECT data type.
+
+
JAVA_OBJECT_SERIALIZER - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.javaObjectSerializer + (default: null).
+
+
JAVA_OBJECT_SERIALIZER_CHANGE_WITH_DATA_TABLE - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90141 is thrown when + trying to change the java object serializer while there was already data + in the database.
+
+
JAVA_SYSTEM_COMPILER - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.javaSystemCompiler (default: true).
+
+
JavaObjectSerializer - Interface in org.h2.api
+
+
Custom serialization mechanism for java objects being stored in column of + type OTHER.
+
+
JdbcArray - Class in org.h2.jdbc
+
+
Represents an ARRAY value.
+
+
JdbcArray(JdbcConnection, Value, int) - Constructor for class org.h2.jdbc.JdbcArray
+
+
INTERNAL
+
+
JdbcBatchUpdateException - Exception in org.h2.jdbc
+
+
Represents a batch update database exception.
+
+
JdbcBlob - Class in org.h2.jdbc
+
+
Represents a BLOB value.
+
+
JdbcBlob(JdbcConnection, Value, JdbcLob.State, int) - Constructor for class org.h2.jdbc.JdbcBlob
+
+
INTERNAL
+
+
JdbcCallableStatement - Class in org.h2.jdbc
+
+
Represents a callable statement.
+
+
JdbcClob - Class in org.h2.jdbc
+
+
Represents a CLOB value.
+
+
JdbcClob(JdbcConnection, Value, JdbcLob.State, int) - Constructor for class org.h2.jdbc.JdbcClob
+
+
INTERNAL
+
+
JdbcConnection - Class in org.h2.jdbc
+
+
Represents a connection (session) to a database.
+
+
JdbcConnection(String, Properties, String, Object, boolean) - Constructor for class org.h2.jdbc.JdbcConnection
+
+
INTERNAL + the session closable object does not leak as Eclipse warns - due to the + CloseWatcher.
+
+
JdbcConnection(JdbcConnection) - Constructor for class org.h2.jdbc.JdbcConnection
+
+
INTERNAL
+
+
JdbcConnection(Session, String, String) - Constructor for class org.h2.jdbc.JdbcConnection
+
+
INTERNAL
+
+
JdbcConnectionBackwardsCompat - Interface in org.h2.jdbc
+
+
Allows us to compile on older platforms, while still implementing the methods + from the newer JDBC API.
+
+
JdbcConnectionPool - Class in org.h2.jdbcx
+
+
A simple standalone JDBC connection pool.
+
+
JdbcConnectionPoolBackwardsCompat - Interface in org.h2.jdbcx
+
+
Allows us to compile on older platforms, while still implementing the methods + from the newer JDBC API.
+
+
JdbcDatabaseMetaData - Class in org.h2.jdbc
+
+
Represents the meta data for a database.
+
+
JdbcDatabaseMetaDataBackwardsCompat - Interface in org.h2.jdbc
+
+
Allows us to compile on older platforms, while still implementing the methods + from the newer JDBC API.
+
+
JdbcDataSource - Class in org.h2.jdbcx
+
+
A data source for H2 database connections.
+
+
JdbcDataSource() - Constructor for class org.h2.jdbcx.JdbcDataSource
+
+
The public constructor.
+
+
JdbcDataSourceBackwardsCompat - Interface in org.h2.jdbcx
+
+
Allows us to compile on older platforms, while still implementing the methods + from the newer JDBC API.
+
+
JdbcDataSourceFactory - Class in org.h2.jdbcx
+
+
This class is used to create new DataSource objects.
+
+
JdbcDataSourceFactory() - Constructor for class org.h2.jdbcx.JdbcDataSourceFactory
+
+
The public constructor to create new factory objects.
+
+
JdbcException - Interface in org.h2.jdbc
+
+
This interface contains additional methods for database exceptions.
+
+
JdbcLob - Class in org.h2.jdbc
+
+
Represents a large object value.
+
+
JdbcLob.State - Enum in org.h2.jdbc
+
+
State of the object.
+
+
JdbcParameterMetaData - Class in org.h2.jdbc
+
+
Information about the parameters of a prepared statement.
+
+
JdbcPreparedStatement - Class in org.h2.jdbc
+
+
Represents a prepared statement.
+
+
JdbcResultSet - Class in org.h2.jdbc
+
+
Represents a result set.
+
+
JdbcResultSet(JdbcConnection, JdbcStatement, CommandInterface, ResultInterface, int, boolean, boolean, boolean) - Constructor for class org.h2.jdbc.JdbcResultSet
+
 
+
JdbcResultSetMetaData - Class in org.h2.jdbc
+
+
Represents the meta data for a ResultSet.
+
+
JdbcSavepoint - Class in org.h2.jdbc
+
+
A savepoint is a point inside a transaction to where a transaction can be + rolled back.
+
+
JdbcSQLDataException - Exception in org.h2.jdbc
+
+
Represents a database exception.
+
+
JdbcSQLDataException(String, String, String, int, Throwable, String) - Constructor for exception org.h2.jdbc.JdbcSQLDataException
+
+
Creates a SQLDataException.
+
+
JdbcSQLException - Exception in org.h2.jdbc
+
+
Represents a database exception.
+
+
JdbcSQLException(String, String, String, int, Throwable, String) - Constructor for exception org.h2.jdbc.JdbcSQLException
+
+
Creates a SQLException.
+
+
JdbcSQLFeatureNotSupportedException - Exception in org.h2.jdbc
+
+
Represents a database exception.
+
+
JdbcSQLFeatureNotSupportedException(String, String, String, int, Throwable, String) - Constructor for exception org.h2.jdbc.JdbcSQLFeatureNotSupportedException
+
+
Creates a SQLFeatureNotSupportedException.
+
+
JdbcSQLIntegrityConstraintViolationException - Exception in org.h2.jdbc
+
+
Represents a database exception.
+
+
JdbcSQLIntegrityConstraintViolationException(String, String, String, int, Throwable, String) - Constructor for exception org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException
+
+
Creates a SQLIntegrityConstraintViolationException.
+
+
JdbcSQLInvalidAuthorizationSpecException - Exception in org.h2.jdbc
+
+
Represents a database exception.
+
+
JdbcSQLInvalidAuthorizationSpecException(String, String, String, int, Throwable, String) - Constructor for exception org.h2.jdbc.JdbcSQLInvalidAuthorizationSpecException
+
+
Creates a SQLInvalidAuthorizationSpecException.
+
+
JdbcSQLNonTransientConnectionException - Exception in org.h2.jdbc
+
+
Represents a database exception.
+
+
JdbcSQLNonTransientConnectionException(String, String, String, int, Throwable, String) - Constructor for exception org.h2.jdbc.JdbcSQLNonTransientConnectionException
+
+
Creates a SQLNonTransientConnectionException.
+
+
JdbcSQLNonTransientException - Exception in org.h2.jdbc
+
+
Represents a database exception.
+
+
JdbcSQLNonTransientException(String, String, String, int, Throwable, String) - Constructor for exception org.h2.jdbc.JdbcSQLNonTransientException
+
+
Creates a SQLNonTransientException.
+
+
JdbcSQLSyntaxErrorException - Exception in org.h2.jdbc
+
+
Represents a database exception.
+
+
JdbcSQLSyntaxErrorException(String, String, String, int, Throwable, String) - Constructor for exception org.h2.jdbc.JdbcSQLSyntaxErrorException
+
+
Creates a SQLSyntaxErrorException.
+
+
JdbcSQLTimeoutException - Exception in org.h2.jdbc
+
+
Represents a database exception.
+
+
JdbcSQLTimeoutException(String, String, String, int, Throwable, String) - Constructor for exception org.h2.jdbc.JdbcSQLTimeoutException
+
+
Creates a SQLTimeoutException.
+
+
JdbcSQLTransactionRollbackException - Exception in org.h2.jdbc
+
+
Represents a database exception.
+
+
JdbcSQLTransactionRollbackException(String, String, String, int, Throwable, String) - Constructor for exception org.h2.jdbc.JdbcSQLTransactionRollbackException
+
+
Creates a SQLTransactionRollbackException.
+
+
JdbcSQLTransientException - Exception in org.h2.jdbc
+
+
Represents a database exception.
+
+
JdbcSQLTransientException(String, String, String, int, Throwable, String) - Constructor for exception org.h2.jdbc.JdbcSQLTransientException
+
+
Creates a SQLTransientException.
+
+
JdbcSQLXML - Class in org.h2.jdbc
+
+
Represents a SQLXML value.
+
+
JdbcSQLXML(JdbcConnection, Value, JdbcLob.State, int) - Constructor for class org.h2.jdbc.JdbcSQLXML
+
+
INTERNAL
+
+
JdbcStatement - Class in org.h2.jdbc
+
+
Represents a statement.
+
+
JdbcStatementBackwardsCompat - Interface in org.h2.jdbc
+
+
Allows us to compile on older platforms, while still implementing the methods + from the newer JDBC API.
+
+
JdbcXAConnection - Class in org.h2.jdbcx
+
+
This class provides support for distributed transactions.
+
+
JdbcXid - Class in org.h2.jdbcx
+
+
An object of this class represents a transaction id.
+
+
JSON - Static variable in class org.h2.api.H2Type
+
+
The JSON data type.
+
+
+ + + +

K

+
+
keys - Variable in class org.h2.fulltext.IndexInfo
+
+
The column indexes of the key columns.
+
+
+ + + +

L

+
+
last() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Moves the current position to the last row.
+
+
last() - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
lastUpdateTime - Variable in class org.h2.engine.QueryStatisticsData.QueryEntry
+
+
The last time the statistics for this entry were updated, + in milliseconds since 1970.
+
+
length() - Method in class org.h2.jdbc.JdbcBlob
+
+
Returns the length.
+
+
length() - Method in class org.h2.jdbc.JdbcClob
+
+
Returns the length.
+
+
LIKE_ESCAPE_ERROR_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 22025 is thrown when using an invalid + escape character sequence for LIKE or REGEXP.
+
+
limit - Variable in class org.h2.engine.Mode
+
+
Whether LIMIT / OFFSET clauses are supported.
+
+
LITERALS_ARE_NOT_ALLOWED - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90116 is thrown when + trying use a literal in a SQL statement if literals are disabled.
+
+
loadH2(int) - Static method in class org.h2.tools.Upgrade
+
+
Loads the specified version of H2 in a separate class loader.
+
+
LOB_CLIENT_MAX_SIZE_MEMORY - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.lobClientMaxSizeMemory (default: + 1048576).
+
+
LOB_CLOSED_ON_TIMEOUT_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90039 is thrown when + trying to access a CLOB or BLOB object that timed out.
+
+
LOB_READ - Static variable in class org.h2.engine.SessionRemote
+
 
+
lobCloseBetweenReads - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.lobCloseBetweenReads + (default: false).
+
+
lobTimeout - Variable in class org.h2.engine.DbSettings
+
+
Database setting LOB_TIMEOUT (default: 300000, + which means 5 minutes).
+
+
locatorsUpdateCopy() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Does the database make a copy before updating.
+
+
LOCK_MODE_OFF - Static variable in class org.h2.engine.Constants
+
+
The lock mode that means no locking is used at all.
+
+
LOCK_MODE_READ_COMMITTED - Static variable in class org.h2.engine.Constants
+
+
The lock mode that means read locks are acquired, but they are released + immediately after the statement is executed.
+
+
LOCK_MODE_TABLE - Static variable in class org.h2.engine.Constants
+
+
The lock mode that means table level locking is used for reads and + writes.
+
+
LOCK_MODE_TABLE_GC - Static variable in class org.h2.engine.Constants
+
+
The lock mode that means table level locking is used for reads and + writes.
+
+
LOCK_SLEEP - Static variable in class org.h2.engine.Constants
+
+
The number of milliseconds to wait between checking the .lock.db file + still exists once a database is locked.
+
+
LOCK_TIMEOUT_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 50200 is thrown when + another connection locked an object longer than the lock timeout + set for this connection, or when a deadlock occurred.
+
+
lockMeta(SessionLocal) - Method in class org.h2.engine.Database
+
+
Lock the metadata table for updates.
+
+
logIsLogBase10 - Variable in class org.h2.engine.Mode
+
+
The single-argument function LOG() uses base 10 instead of E.
+
+
+ + + +

M

+
+
main(String...) - Static method in class org.h2.tools.Backup
+
+
Options are case sensitive.
+
+
main(String...) - Static method in class org.h2.tools.ChangeFileEncryption
+
+
Options are case sensitive.
+
+
main(String...) - Static method in class org.h2.tools.Console
+
+
When running without options, -tcp, -web, -browser and -pg are started.
+
+
main(String...) - Static method in class org.h2.tools.ConvertTraceFile
+
+
Options are case sensitive.
+
+
main(String...) - Static method in class org.h2.tools.CreateCluster
+
+
Options are case sensitive.
+
+
main(String...) - Static method in class org.h2.tools.DeleteDbFiles
+
+
Options are case sensitive.
+
+
main(String...) - Static method in class org.h2.tools.Recover
+
+
Options are case sensitive.
+
+
main(String...) - Static method in class org.h2.tools.Restore
+
+
Options are case sensitive.
+
+
main(String...) - Static method in class org.h2.tools.RunScript
+
+
Options are case sensitive.
+
+
main(String...) - Static method in class org.h2.tools.Script
+
+
Options are case sensitive.
+
+
main(String...) - Static method in class org.h2.tools.Server
+
+
When running without options, -tcp, -web, -browser and -pg are started.
+
+
main(String...) - Static method in class org.h2.tools.Shell
+
+
Options are case sensitive.
+
+
MAIN_SCHEMA_ID - Static variable in class org.h2.engine.Constants
+
+
The identity of PUBLIC schema.
+
+
mapUserToRoles(AuthenticationInfo) - Method in interface org.h2.api.UserToRolesMapper
+
+
Map user identified by authentication info to a set of granted roles.
+
+
markTableForAnalyze(Table) - Method in class org.h2.engine.SessionLocal
+
+
Mark that the given table needs to be analyzed on commit.
+
+
MAX_ARRAY_CARDINALITY - Static variable in class org.h2.engine.Constants
+
+
The maximum allowed cardinality of array.
+
+
MAX_COLUMNS - Static variable in class org.h2.engine.Constants
+
+
The maximum number of columns in a table, select statement or row value.
+
+
MAX_FILE_RETRY - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.maxFileRetry (default: 16).
+
+
MAX_IDENTIFIER_LENGTH - Static variable in class org.h2.engine.Constants
+
+
The maximum allowed length of identifiers.
+
+
MAX_MEMORY_ROWS - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.maxMemoryRows + (default: 40000 per GB of available RAM).
+
+
MAX_NUMERIC_PRECISION - Static variable in class org.h2.engine.Constants
+
+
The maximum allowed precision of numeric data types.
+
+
MAX_PARAMETER_INDEX - Static variable in class org.h2.engine.Constants
+
+
The highest possible parameter index.
+
+
MAX_RECONNECT - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.maxReconnect (default: 3).
+
+
MAX_STRING_LENGTH - Static variable in class org.h2.engine.Constants
+
+
The maximum allowed length for character string, binary string, and other + data types based on them; excluding LOB data types.
+
+
MAX_TRACE_DATA_LENGTH - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.maxTraceDataLength + (default: 65535).
+
+
maxCompactTime - Variable in class org.h2.engine.DbSettings
+
+
Database setting MAX_COMPACT_TIME (default: 200).
+
+
maxQueryTimeout - Variable in class org.h2.engine.DbSettings
+
+
Database setting MAX_QUERY_TIMEOUT (default: 0).
+
+
maxRows - Variable in class org.h2.jdbc.JdbcStatement
+
 
+
MEMORY_ARRAY - Static variable in class org.h2.engine.Constants
+
+
The memory needed by an array.
+
+
MEMORY_OBJECT - Static variable in class org.h2.engine.Constants
+
+
The memory needed by a regular object with at least one field.
+
+
MEMORY_POINTER - Static variable in class org.h2.engine.Constants
+
+
The memory needed by a pointer.
+
+
MEMORY_ROW - Static variable in class org.h2.engine.Constants
+
+
The memory needed by a Row.
+
+
mergeWhere - Variable in class org.h2.engine.Mode
+
+
If true, merge when matched clause may have WHERE clause.
+
+
MetaRecord - Class in org.h2.engine
+
+
A record in the system table of the database.
+
+
MetaRecord(SearchRow) - Constructor for class org.h2.engine.MetaRecord
+
 
+
METHOD_DISABLED_ON_AUTOCOMMIT_TRUE - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90147 is thrown when trying to execute a + statement which closes the transaction (such as commit and rollback) and + autocommit mode is on.
+
+
METHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90130 is thrown when + an execute method of PreparedStatement was called with a SQL statement.
+
+
METHOD_NOT_ALLOWED_FOR_QUERY - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90001 is thrown when + Statement.executeUpdate() was called for a SELECT statement.
+
+
METHOD_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90087 is thrown when + a method with matching number of arguments was not found in the class.
+
+
METHOD_ONLY_ALLOWED_FOR_QUERY - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90002 is thrown when + Statement.executeQuery() was called for a statement that does + not return a result set (for example, an UPDATE statement).
+
+
METHODS_MUST_HAVE_DIFFERENT_PARAMETER_COUNTS_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90073 is thrown when trying to create + an alias for a Java method, if two methods exists in this class that have + this name and the same number of parameters.
+
+
minusIsExcept - Variable in class org.h2.engine.Mode
+
+
Whether MINUS can be used as EXCEPT.
+
+
Mode - Class in org.h2.engine
+
+
The compatibility modes.
+
+
mode - Variable in class org.h2.engine.Session.DynamicSettings
+
+
The database mode.
+
+
Mode.CharPadding - Enum in org.h2.engine
+
+
When CHAR values are right-padded with spaces.
+
+
Mode.ExpressionNames - Enum in org.h2.engine
+
+
Generation of column names for expressions.
+
+
Mode.ModeEnum - Enum in org.h2.engine
+
 
+
Mode.UniqueIndexNullsHandling - Enum in org.h2.engine
+
+
Determines how rows with NULL values in indexed columns are handled + in unique indexes.
+
+
Mode.ViewExpressionNames - Enum in org.h2.engine
+
+
Generation of column names for expressions to be used in a view.
+
+
mouseClicked(MouseEvent) - Method in class org.h2.tools.GUIConsole
+
+
INTERNAL
+
+
mouseEntered(MouseEvent) - Method in class org.h2.tools.GUIConsole
+
+
INTERNAL
+
+
mouseExited(MouseEvent) - Method in class org.h2.tools.GUIConsole
+
+
INTERNAL
+
+
mousePressed(MouseEvent) - Method in class org.h2.tools.GUIConsole
+
+
INTERNAL
+
+
mouseReleased(MouseEvent) - Method in class org.h2.tools.GUIConsole
+
+
INTERNAL
+
+
moveToCurrentRow() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Moves the current position to the current row.
+
+
moveToCurrentRow() - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
moveToInsertRow() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Moves the current position to the insert row.
+
+
moveToInsertRow() - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
MultiDimension - Class in org.h2.tools
+
+
A tool to help an application execute multi-dimensional range queries.
+
+
MultiDimension() - Constructor for class org.h2.tools.MultiDimension
+
 
+
MUST_GROUP_BY_COLUMN_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90016 is thrown when + a column was used in the expression list or the order by clause of a + group or aggregate query, and that column is not in the GROUP BY clause.
+
+
mvStore - Variable in class org.h2.engine.DbSettings
+
+
Database setting MV_STORE + (default: true).
+
+
+ + + +

N

+
+
NAME_TOO_LONG_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 42622 is thrown when + name of identifier is too long.
+
+
nativeSQL(String) - Method in class org.h2.jdbc.JdbcConnection
+
+
Translates a SQL statement into the database grammar.
+
+
newConcurrentStringMap() - Method in class org.h2.engine.Database
+
+
Create a new hash map.
+
+
newStringMap() - Method in class org.h2.engine.Database
+
+
Create a new hash map.
+
+
newStringMap(int) - Method in class org.h2.engine.Database
+
+
Create a new hash map.
+
+
next() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Moves the cursor to the next row of the result set.
+
+
next() - Method in class org.h2.tools.SimpleResultSet
+
+
Moves the cursor to the next row of the result set.
+
+
nextObjectId() - Method in class org.h2.engine.SessionLocal
+
+
Get the next object id.
+
+
nextvalAndCurrvalPseudoColumns - Variable in class org.h2.engine.Mode
+
+
If true, sequence.NEXTVAL and sequence.CURRVAL pseudo columns are + supported.
+
+
nextValueReturnsDifferentValues - Variable in class org.h2.engine.Mode
+
+
If true, the next value expression returns different values when + invoked multiple times within a row.
+
+
NIO_CLEANER_HACK - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.nioCleanerHack (default: false).
+
+
NIO_LOAD_MAPPED - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.nioLoadMapped (default: false).
+
+
NO_DATA_AVAILABLE - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 2000 is thrown when + the result set is positioned before the first or after the last row, or + not on a valid row for the given operation.
+
+
NO_DEFAULT_SET_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 23507 is thrown when + updating or deleting from a table with a foreign key constraint + that should set the default value, but there is no default value defined.
+
+
NONE - Static variable in class org.h2.engine.GeneratedKeysMode
+
+
Generated keys are not needed.
+
+
normalize(int, double, double, double) - Method in class org.h2.tools.MultiDimension
+
+
Normalize a value so that it is between the minimum and maximum for the + given number of dimensions.
+
+
NOT_ENOUGH_RIGHTS_FOR_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90096 is thrown when + trying to perform an operation with a non-admin user if the + user does not have enough rights.
+
+
NOT_ON_UPDATABLE_ROW - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90029 is thrown when + calling ResultSet.deleteRow(), insertRow(), or updateRow() + when the current row is not updatable.
+
+
NULL_NOT_ALLOWED - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 23502 is thrown when + trying to insert NULL into a column that does not allow NULL.
+
+
nullPlusNonNullIsNull() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether NULL+1 is NULL or not.
+
+
nullsAreSortedAtEnd() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Checks if NULL values are sorted at the end (no matter if ASC or DESC is + used).
+
+
nullsAreSortedAtStart() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Checks if NULL values are sorted at the beginning (no matter if ASC or + DESC is used).
+
+
nullsAreSortedHigh() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Checks if NULL values are sorted high (bigger than anything that is not + null).
+
+
nullsAreSortedLow() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Checks if NULL values are sorted low (smaller than anything that is not + null).
+
+
NUMERIC - Static variable in class org.h2.api.H2Type
+
+
The NUMERIC data type.
+
+
NUMERIC_VALUE_OUT_OF_RANGE_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 22003 is thrown when a value is out of + range when converting to another data type.
+
+
NUMERIC_VALUE_OUT_OF_RANGE_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 22004 is thrown when a value is out of + range when converting to another column's data type.
+
+
numericWithBooleanComparison - Variable in class org.h2.engine.Mode
+
+
Allow to compare numeric with BOOLEAN.
+
+
+ + + +

O

+
+
OBJECT_CACHE - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.objectCache (default: true).
+
+
OBJECT_CACHE_MAX_PER_ELEMENT_SIZE - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.objectCacheMaxPerElementSize (default: + 4096).
+
+
OBJECT_CACHE_SIZE - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.objectCacheSize (default: 1024).
+
+
OBJECT_CLOSED - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90007 is thrown when + trying to call a JDBC method on an object that has been closed.
+
+
ofDays(long) - Static method in class org.h2.api.Interval
+
+
Creates a new INTERVAL DAY.
+
+
ofDaysHours(long, int) - Static method in class org.h2.api.Interval
+
+
Creates a new INTERVAL DAY TO HOUR.
+
+
ofDaysHoursMinutes(long, int, int) - Static method in class org.h2.api.Interval
+
+
Creates a new INTERVAL DAY TO MINUTE.
+
+
ofDaysHoursMinutesNanos(long, int, int, long) - Static method in class org.h2.api.Interval
+
+
Creates a new INTERVAL DAY TO SECOND.
+
+
ofDaysHoursMinutesSeconds(long, int, int, int) - Static method in class org.h2.api.Interval
+
+
Creates a new INTERVAL DAY TO SECOND.
+
+
ofHours(long) - Static method in class org.h2.api.Interval
+
+
Creates a new INTERVAL HOUR.
+
+
ofHoursMinutes(long, int) - Static method in class org.h2.api.Interval
+
+
Creates a new INTERVAL HOUR TO MINUTE.
+
+
ofHoursMinutesNanos(long, int, long) - Static method in class org.h2.api.Interval
+
+
Creates a new INTERVAL HOUR TO SECOND.
+
+
ofHoursMinutesSeconds(long, int, int) - Static method in class org.h2.api.Interval
+
+
Creates a new INTERVAL HOUR TO SECOND.
+
+
ofMinutes(long) - Static method in class org.h2.api.Interval
+
+
Creates a new INTERVAL MINUTE.
+
+
ofMinutesNanos(long, long) - Static method in class org.h2.api.Interval
+
+
Creates a new INTERVAL MINUTE TO SECOND.
+
+
ofMinutesSeconds(long, int) - Static method in class org.h2.api.Interval
+
+
Creates a new INTERVAL MINUTE TO SECOND.
+
+
ofMonths(long) - Static method in class org.h2.api.Interval
+
+
Creates a new INTERVAL MONTH.
+
+
ofNanos(long) - Static method in class org.h2.api.Interval
+
+
Creates a new INTERVAL SECOND.
+
+
ofSeconds(long) - Static method in class org.h2.api.Interval
+
+
Creates a new INTERVAL SECOND.
+
+
ofSeconds(long, int) - Static method in class org.h2.api.Interval
+
+
Creates a new INTERVAL SECOND.
+
+
ofYears(long) - Static method in class org.h2.api.Interval
+
+
Creates a new INTERVAL YEAR.
+
+
ofYearsMonths(long, int) - Static method in class org.h2.api.Interval
+
+
Creates a new INTERVAL YEAR TO MONTH.
+
+
onDuplicateKeyUpdate - Variable in class org.h2.engine.Mode
+
+
MySQL style INSERT ...
+
+
onRollback(MVMap<Object, VersionedValue<Object>>, Object, VersionedValue<Object>, VersionedValue<Object>) - Method in class org.h2.engine.SessionLocal
+
 
+
openBrowser(String) - Static method in class org.h2.tools.Server
+
+
Open a new browser tab or window with the given URL.
+
+
opened() - Method in interface org.h2.api.DatabaseEventListener
+
+
This method is called after the database has been opened.
+
+
openFile(String, String, boolean) - Method in class org.h2.engine.Database
+
 
+
openFile(String, String, boolean) - Method in class org.h2.engine.SessionRemote
+
 
+
openFile(String, String, boolean) - Method in class org.h2.tools.Recover
+
+
INTERNAL
+
+
optimizeDistinct - Variable in class org.h2.engine.DbSettings
+
+
Database setting OPTIMIZE_DISTINCT (default: true).
+
+
optimizeEvaluatableSubqueries - Variable in class org.h2.engine.DbSettings
+
+
Database setting OPTIMIZE_EVALUATABLE_SUBQUERIES (default: + true).
+
+
optimizeInList - Variable in class org.h2.engine.DbSettings
+
+
Database setting OPTIMIZE_IN_LIST (default: true).
+
+
optimizeInSelect - Variable in class org.h2.engine.DbSettings
+
+
Database setting OPTIMIZE_IN_SELECT (default: true).
+
+
optimizeInsertFromSelect - Variable in class org.h2.engine.DbSettings
+
+
Database setting OPTIMIZE_INSERT_FROM_SELECT + (default: true).
+
+
optimizeOr - Variable in class org.h2.engine.DbSettings
+
+
Database setting OPTIMIZE_OR (default: true).
+
+
optimizeSimpleSingleRowSubqueries - Variable in class org.h2.engine.DbSettings
+
+
Database setting OPTIMIZE_SIMPLE_SINGLE_ROW_SUBQUERIES (default: true).
+
+
optimizeTwoEquals - Variable in class org.h2.engine.DbSettings
+
+
Database setting OPTIMIZE_TWO_EQUALS (default: true).
+
+
ORDER_BY_NOT_IN_RESULT - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90068 is thrown when the given + expression that is used in the ORDER BY is not in the result list.
+
+
org.h2.api - package org.h2.api
+
+
+ +Contains interfaces for user-defined extensions, such as triggers and user-defined aggregate functions.
+
+
org.h2.engine - package org.h2.engine
+
+
+ +Contains high level classes of the database and classes that don't fit in another sub-package.
+
+
org.h2.fulltext - package org.h2.fulltext
+
+
+ +The native full text search implementation, and the wrapper for the Lucene full text search implementation.
+
+
org.h2.jdbc - package org.h2.jdbc
+
+
+ +Implementation of the JDBC API (package java.sql).
+
+
org.h2.jdbcx - package org.h2.jdbcx
+
+
+ +Implementation of the extended JDBC API (package javax.sql).
+
+
org.h2.tools - package org.h2.tools
+
+
+ +Various tools.
+
+
othersDeletesAreVisible(int) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether other deletes are visible.
+
+
othersInsertsAreVisible(int) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether other inserts are visible.
+
+
othersUpdatesAreVisible(int) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether other updates are visible.
+
+
OUT_OF_MEMORY - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90108 is thrown when not enough heap + memory was available.
+
+
ownDeletesAreVisible(int) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether own deletes are visible.
+
+
ownInsertsAreVisible(int) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether own inserts are visible.
+
+
ownUpdatesAreVisible(int) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether own updates are visible.
+
+
+ + + +

P

+
+
PARAMETER_NOT_SET_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90012 is thrown when + trying to execute a statement with an parameter.
+
+
PARSE_ERROR_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90014 is thrown when + trying to parse a date with an unsupported format string, or + when the date can not be parsed.
+
+
parseKey(Connection, String) - Static method in class org.h2.fulltext.FullText
+
+
Parse a primary key condition into the primary key columns.
+
+
PG_CATALOG_SCHEMA_ID - Static variable in class org.h2.engine.Constants
+
+
The identity of pg_catalog schema.
+
+
PG_DEFAULT_CLIENT_ENCODING - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.pgClientEncoding (default: UTF-8).
+
+
PG_VERSION - Static variable in class org.h2.engine.Constants
+
+
Announced version for PgServer.
+
+
populateRowFromDBObject(DbObject, SearchRow) - Static method in class org.h2.engine.MetaRecord
+
+
Copy metadata from the specified object into specified search row.
+
+
position(byte[], long) - Method in class org.h2.jdbc.JdbcBlob
+
+
[Not supported] Searches a pattern and return the position.
+
+
position(Blob, long) - Method in class org.h2.jdbc.JdbcBlob
+
+
[Not supported] Searches a pattern and return the position.
+
+
position(String, long) - Method in class org.h2.jdbc.JdbcClob
+
+
[Not supported] Searches a pattern and return the position.
+
+
position(Clob, long) - Method in class org.h2.jdbc.JdbcClob
+
+
[Not supported] Searches a pattern and return the position.
+
+
PREFIX_INDEX - Static variable in class org.h2.engine.Constants
+
+
The name prefix used for indexes that are not explicitly named.
+
+
PREFIX_JOIN - Static variable in class org.h2.engine.Constants
+
+
The name prefix used for synthetic nested join tables.
+
+
PREFIX_PRIMARY_KEY - Static variable in class org.h2.engine.Constants
+
+
The name prefix used for primary key constraints that are not explicitly + named.
+
+
PREFIX_QUERY_ALIAS - Static variable in class org.h2.engine.Constants
+
+
The name prefix used for query aliases that are not explicitly named.
+
+
PREFIX_TEMP_FILE - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.prefixTempFile (default: h2.temp).
+
+
prepare(String) - Method in class org.h2.engine.SessionLocal
+
+
Parse and prepare the given SQL statement.
+
+
prepare(String, boolean, boolean) - Method in class org.h2.engine.SessionLocal
+
+
Parse and prepare the given SQL statement.
+
+
prepare(Xid) - Method in class org.h2.jdbcx.JdbcXAConnection
+
+
Prepare a transaction.
+
+
prepareCall(String) - Method in class org.h2.jdbc.JdbcConnection
+
+
Creates a new callable statement.
+
+
prepareCall(String, int, int) - Method in class org.h2.jdbc.JdbcConnection
+
+
Creates a callable statement with the specified result set type and + concurrency.
+
+
prepareCall(String, int, int, int) - Method in class org.h2.jdbc.JdbcConnection
+
+
Creates a callable statement with the specified result set type, + concurrency, and holdability.
+
+
prepareCommand(String, int) - Method in class org.h2.engine.Session
+
+
Parse a command and prepare it for execution.
+
+
prepareCommand(String, int) - Method in class org.h2.engine.SessionLocal
+
 
+
prepareCommand(String, int) - Method in class org.h2.engine.SessionRemote
+
 
+
prepareCommit(String) - Method in class org.h2.engine.SessionLocal
+
+
Prepare the given transaction.
+
+
prepareLocal(String) - Method in class org.h2.engine.SessionLocal
+
+
Parse and prepare the given SQL statement.
+
+
prepareQueryExpression(String) - Method in class org.h2.engine.SessionLocal
+
+
Parse a query and prepare its expressions.
+
+
prepareStatement(String) - Method in class org.h2.jdbc.JdbcConnection
+
+
Creates a new prepared statement.
+
+
prepareStatement(String, int, int) - Method in class org.h2.jdbc.JdbcConnection
+
+
Creates a prepared statement with the specified result set type and + concurrency.
+
+
prepareStatement(String, int, int, int) - Method in class org.h2.jdbc.JdbcConnection
+
+
Creates a prepared statement with the specified result set type, + concurrency, and holdability.
+
+
prepareStatement(String, int) - Method in class org.h2.jdbc.JdbcConnection
+
+
Creates a new prepared statement.
+
+
prepareStatement(String, int[]) - Method in class org.h2.jdbc.JdbcConnection
+
+
Creates a new prepared statement.
+
+
prepareStatement(String, String[]) - Method in class org.h2.jdbc.JdbcConnection
+
+
Creates a new prepared statement.
+
+
previous() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Moves the cursor to the last row, or row before first row if the current + position is the first row.
+
+
previous() - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
print(String) - Method in class org.h2.tools.Shell
+
+
Print the string without newline, and flush.
+
+
printStackTrace() - Method in exception org.h2.jdbc.JdbcBatchUpdateException
+
+
INTERNAL
+
+
printStackTrace(PrintWriter) - Method in exception org.h2.jdbc.JdbcBatchUpdateException
+
+
INTERNAL
+
+
printStackTrace(PrintStream) - Method in exception org.h2.jdbc.JdbcBatchUpdateException
+
+
INTERNAL
+
+
printStackTrace(PrintWriter) - Method in exception org.h2.jdbc.JdbcSQLDataException
+
 
+
printStackTrace(PrintStream) - Method in exception org.h2.jdbc.JdbcSQLDataException
+
 
+
printStackTrace(PrintWriter) - Method in exception org.h2.jdbc.JdbcSQLException
+
 
+
printStackTrace(PrintStream) - Method in exception org.h2.jdbc.JdbcSQLException
+
 
+
printStackTrace(PrintWriter) - Method in exception org.h2.jdbc.JdbcSQLFeatureNotSupportedException
+
 
+
printStackTrace(PrintStream) - Method in exception org.h2.jdbc.JdbcSQLFeatureNotSupportedException
+
 
+
printStackTrace(PrintWriter) - Method in exception org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException
+
 
+
printStackTrace(PrintStream) - Method in exception org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException
+
 
+
printStackTrace(PrintWriter) - Method in exception org.h2.jdbc.JdbcSQLInvalidAuthorizationSpecException
+
 
+
printStackTrace(PrintStream) - Method in exception org.h2.jdbc.JdbcSQLInvalidAuthorizationSpecException
+
 
+
printStackTrace(PrintWriter) - Method in exception org.h2.jdbc.JdbcSQLNonTransientConnectionException
+
 
+
printStackTrace(PrintStream) - Method in exception org.h2.jdbc.JdbcSQLNonTransientConnectionException
+
 
+
printStackTrace(PrintWriter) - Method in exception org.h2.jdbc.JdbcSQLNonTransientException
+
 
+
printStackTrace(PrintStream) - Method in exception org.h2.jdbc.JdbcSQLNonTransientException
+
 
+
printStackTrace(PrintWriter) - Method in exception org.h2.jdbc.JdbcSQLSyntaxErrorException
+
 
+
printStackTrace(PrintStream) - Method in exception org.h2.jdbc.JdbcSQLSyntaxErrorException
+
 
+
printStackTrace(PrintWriter) - Method in exception org.h2.jdbc.JdbcSQLTimeoutException
+
 
+
printStackTrace(PrintStream) - Method in exception org.h2.jdbc.JdbcSQLTimeoutException
+
 
+
printStackTrace(PrintWriter) - Method in exception org.h2.jdbc.JdbcSQLTransactionRollbackException
+
 
+
printStackTrace(PrintStream) - Method in exception org.h2.jdbc.JdbcSQLTransactionRollbackException
+
 
+
printStackTrace(PrintWriter) - Method in exception org.h2.jdbc.JdbcSQLTransientException
+
 
+
printStackTrace(PrintStream) - Method in exception org.h2.jdbc.JdbcSQLTransientException
+
 
+
Procedure - Class in org.h2.engine
+
+
Represents a procedure.
+
+
Procedure(String, Prepared) - Constructor for class org.h2.engine.Procedure
+
 
+
process(String, String, String, String, String, String) - Static method in class org.h2.tools.Script
+
+
Backs up a database to a stream.
+
+
process(Connection, String, String, String) - Static method in class org.h2.tools.Script
+
+
Backs up a database to a stream.
+
+
PUBLIC_ROLE_NAME - Static variable in class org.h2.engine.Constants
+
+
Every user belongs to this role.
+
+
PUBLIC_STATIC_JAVA_METHOD_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90139 is thrown when + the specified public static Java method was not found in the class.
+
+
+ + + +

Q

+
+
QUERY_STATISTICS_MAX_ENTRIES - Static variable in class org.h2.engine.Constants
+
+
The maximum number of entries in query statistics.
+
+
queryCacheSize - Variable in class org.h2.engine.DbSettings
+
+
Database setting QUERY_CACHE_SIZE (default: 8).
+
+
QueryEntry(String) - Constructor for class org.h2.engine.QueryStatisticsData.QueryEntry
+
 
+
QueryStatisticsData - Class in org.h2.engine
+
+
Maintains query statistics.
+
+
QueryStatisticsData(int) - Constructor for class org.h2.engine.QueryStatisticsData
+
 
+
QueryStatisticsData.QueryEntry - Class in org.h2.engine
+
+
The collected statistics for one query.
+
+
quoteSQL(Object, int) - Static method in class org.h2.fulltext.FullText
+
+
INTERNAL.
+
+
+ + + +

R

+
+
read(String, String[], String) - Method in class org.h2.tools.Csv
+
+
Reads from the CSV file and returns a result set.
+
+
read(Reader, String[]) - Method in class org.h2.tools.Csv
+
+
Reads CSV data from a reader and returns a result set.
+
+
readBlobMap(Connection, long, long) - Static method in class org.h2.tools.Recover
+
+
INTERNAL
+
+
readClobMap(Connection, long, long) - Static method in class org.h2.tools.Recover
+
+
INTERNAL
+
+
readException(Transfer) - Static method in class org.h2.engine.SessionRemote
+
+
Reads an exception.
+
+
readLob(long, byte[], long, byte[], int, int) - Method in class org.h2.engine.Database
+
 
+
readLob(long, byte[], long, byte[], int, int) - Method in class org.h2.engine.SessionRemote
+
 
+
readLob(long, byte[], long, byte[], int, int) - Method in class org.h2.tools.Recover
+
+
INTERNAL
+
+
readRow() - Method in class org.h2.tools.Csv
+
+
INTERNAL
+
+
readRow() - Method in interface org.h2.tools.SimpleRowSource
+
+
Get the next row.
+
+
readSessionState() - Method in class org.h2.engine.Session
+
+
Read the session state if necessary.
+
+
readVariableInt(byte[], int) - Static method in class org.h2.tools.CompressTool
+
+
Read a variable size integer using Rice coding.
+
+
REAL - Static variable in class org.h2.api.H2Type
+
+
The REAL data type.
+
+
recompileAlways - Variable in class org.h2.engine.DbSettings
+
+
Database setting RECOMPILE_ALWAYS (default: false).
+
+
recover(int) - Method in class org.h2.jdbcx.JdbcXAConnection
+
+
Get the list of prepared transaction branches.
+
+
Recover - Class in org.h2.tools
+
+
Helps recovering a corrupted database.
+
+
Recover() - Constructor for class org.h2.tools.Recover
+
 
+
REFERENTIAL_INTEGRITY_VIOLATED_CHILD_EXISTS_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 23503 is thrown when trying to delete + or update a row when this would violate a referential constraint, because + there is a child row that would become an orphan.
+
+
REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 23506 is thrown when trying to insert + or update a row that would violate a referential constraint, because the + referenced row does not exist.
+
+
refreshRow() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Re-reads the current row from the database.
+
+
refreshRow() - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
regexpReplaceBackslashReferences - Variable in class org.h2.engine.Mode
+
+
The function REGEXP_REPLACE() uses \ for back-references.
+
+
registerOutParameter(int, int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Registers the given OUT parameter.
+
+
registerOutParameter(int, int, String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Registers the given OUT parameter.
+
+
registerOutParameter(int, int, int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Registers the given OUT parameter.
+
+
registerOutParameter(String, int, String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Registers the given OUT parameter.
+
+
registerOutParameter(String, int, int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Registers the given OUT parameter.
+
+
registerOutParameter(String, int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Registers the given OUT parameter.
+
+
registerTableAsLocked(Table) - Method in class org.h2.engine.SessionLocal
+
+
Register table as locked within current transaction.
+
+
registerTableAsUpdated(Table) - Method in class org.h2.engine.SessionLocal
+
+
Register table as updated within current transaction.
+
+
reindex(Connection) - Static method in class org.h2.fulltext.FullText
+
+
Re-creates the full text index for this database.
+
+
reindex(Connection) - Static method in class org.h2.fulltext.FullTextLucene
+
+
Re-creates the full text index for this database.
+
+
relative(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Moves the current position to a specific row relative to the current row.
+
+
relative(int) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
releaseDatabaseObjectIds(BitSet) - Method in class org.h2.engine.Database
+
+
Mark some database ids as unused.
+
+
releaseSavepoint(Savepoint) - Method in class org.h2.jdbc.JdbcConnection
+
+
Releases a savepoint.
+
+
REMOTE_CONNECTION_NOT_ALLOWED - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90117 is thrown when + trying to connect to a TCP server from another machine, if remote + connections are not allowed.
+
+
REMOTE_DATABASE_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90149 is thrown when trying to open a + database that does not exist remotely without enabling remote database + creation first.
+
+
remove() - Method in interface org.h2.api.Trigger
+
+
This method is called when the trigger is dropped.
+
+
remove() - Method in class org.h2.fulltext.FullText.FullTextTrigger
+
+
INTERNAL
+
+
removeAllTriggers(Connection, String) - Static method in class org.h2.fulltext.FullText
+
+
Remove all triggers that start with the given prefix.
+
+
removeAtCommit(ValueLob) - Method in class org.h2.engine.SessionLocal
+
+
Remember that the given LOB value must be removed at commit.
+
+
removeAtCommitStop(ValueLob) - Method in class org.h2.engine.SessionLocal
+
+
Do not remove this LOB value at commit any longer.
+
+
removeChildrenAndResources(SessionLocal) - Method in class org.h2.engine.Comment
+
 
+
removeChildrenAndResources(SessionLocal) - Method in class org.h2.engine.DbObject
+
+
Delete all dependent children objects and resources of this object.
+
+
removeChildrenAndResources(SessionLocal) - Method in class org.h2.engine.Right
+
 
+
removeChildrenAndResources(SessionLocal) - Method in class org.h2.engine.Role
+
 
+
removeChildrenAndResources(SessionLocal) - Method in class org.h2.engine.Setting
+
 
+
removeChildrenAndResources(SessionLocal) - Method in class org.h2.engine.User
+
 
+
removeConnectionEventListener(ConnectionEventListener) - Method in class org.h2.jdbcx.JdbcXAConnection
+
+
Remove the event listener.
+
+
removeDatabaseObject(SessionLocal, DbObject) - Method in class org.h2.engine.Database
+
+
Remove the object from the database.
+
+
removeIndexAccess(String) - Static method in class org.h2.fulltext.FullTextLucene
+
+
Close the index writer and searcher and remove them from the index access + set.
+
+
removeLocalTempTable(Table) - Method in class org.h2.engine.SessionLocal
+
+
Drop and remove the given local temporary table from this session.
+
+
removeLocalTempTableIndex(Index) - Method in class org.h2.engine.SessionLocal
+
+
Drop and remove the given local temporary index from this session.
+
+
removeMeta(SessionLocal, int) - Method in class org.h2.engine.Database
+
+
Remove the given object from the meta data.
+
+
removeProcedure(String) - Method in class org.h2.engine.SessionLocal
+
+
Remove a procedure from this session.
+
+
removeProperty(String, boolean) - Method in class org.h2.engine.ConnectionInfo
+
+
Remove a boolean property if it is set and return the value.
+
+
removeSchemaObject(SessionLocal, SchemaObject) - Method in class org.h2.engine.Database
+
+
Remove an object from the system table.
+
+
removeServer(IOException, int, int) - Method in class org.h2.engine.SessionRemote
+
+
Remove a server from the list of cluster nodes and disables the cluster + mode.
+
+
removeSession(SessionLocal) - Method in class org.h2.engine.Database
+
+
Remove a session.
+
+
removeStatementEventListener(StatementEventListener) - Method in class org.h2.jdbcx.JdbcXAConnection
+
+
[Not supported] Remove a statement event listener.
+
+
rename(String) - Method in class org.h2.engine.DbObject
+
+
Rename the object.
+
+
rename(String) - Method in class org.h2.engine.RightOwner
+
 
+
renameDatabaseObject(SessionLocal, DbObject, String) - Method in class org.h2.engine.Database
+
+
Rename a database object.
+
+
renameSchemaObject(SessionLocal, SchemaObject, String) - Method in class org.h2.engine.Database
+
+
Rename a schema object.
+
+
replaceInto - Variable in class org.h2.engine.Mode
+
+
MySQL style REPLACE INTO.
+
+
reset() - Method in class org.h2.tools.Csv
+
+
INTERNAL
+
+
reset() - Method in interface org.h2.tools.SimpleRowSource
+
+
Reset the position (before the first row).
+
+
resetThreadLocalSession(Session) - Method in class org.h2.engine.Session
+
+
Resets old thread local session.
+
+
resetThreadLocalSession(Session) - Method in class org.h2.engine.SessionLocal
+
 
+
Restore - Class in org.h2.tools
+
+
Restores a H2 database by extracting the database files from a .zip file.
+
+
Restore() - Constructor for class org.h2.tools.Restore
+
 
+
RESULT_CLOSE - Static variable in class org.h2.engine.SessionRemote
+
 
+
RESULT_FETCH_ROWS - Static variable in class org.h2.engine.SessionRemote
+
 
+
RESULT_RESET - Static variable in class org.h2.engine.SessionRemote
+
 
+
RESULT_SET_NOT_SCROLLABLE - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90128 is thrown when + trying to call a method of the ResultSet that is only supported + for scrollable result sets, and the result set is not scrollable.
+
+
RESULT_SET_NOT_UPDATABLE - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90127 is thrown when + trying to update or delete a row in a result set if the result set is + not updatable.
+
+
RESULT_SET_READONLY - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90140 is thrown when trying to update or + delete a row in a result set if the statement was not created with + updatable concurrency.
+
+
resultSet - Variable in class org.h2.jdbc.JdbcStatement
+
 
+
resultSetConcurrency - Variable in class org.h2.jdbc.JdbcStatement
+
 
+
resultSetType - Variable in class org.h2.jdbc.JdbcStatement
+
 
+
reuseSpace - Variable in class org.h2.engine.DbSettings
+
+
Database setting REUSE_SPACE (default: true).
+
+
revokeTemporaryRightsOnRoles() - Method in class org.h2.engine.RightOwner
+
+
Remove all the temporary rights granted on roles
+
+
RIGHT - Static variable in class org.h2.engine.DbObject
+
+
This object is a right.
+
+
Right - Class in org.h2.engine
+
+
An access right.
+
+
Right(Database, int, RightOwner, Role) - Constructor for class org.h2.engine.Right
+
 
+
Right(Database, int, RightOwner, int, DbObject) - Constructor for class org.h2.engine.Right
+
 
+
RightOwner - Class in org.h2.engine
+
+
A right owner (sometimes called principal).
+
+
RightOwner(Database, int, String, int) - Constructor for class org.h2.engine.RightOwner
+
 
+
ROLE - Static variable in class org.h2.engine.DbObject
+
+
This object is a role.
+
+
Role - Class in org.h2.engine
+
+
Represents a role.
+
+
Role(Database, int, String, boolean) - Constructor for class org.h2.engine.Role
+
 
+
ROLE_ALREADY_EXISTS_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90069 is thrown when + trying to create a role if an object with this name already exists.
+
+
ROLE_ALREADY_GRANTED_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90074 is thrown when + trying to grant a role that has already been granted.
+
+
ROLE_CAN_NOT_BE_DROPPED_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90091 is thrown when + trying to drop the role PUBLIC.
+
+
ROLE_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90070 is thrown when + trying to drop or grant a role that does not exists.
+
+
ROLES_AND_RIGHT_CANNOT_BE_MIXED - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90072 is thrown when + trying to grant or revoke both roles and rights at the same time.
+
+
rollback() - Method in class org.h2.engine.SessionLocal
+
+
Fully roll back the current transaction.
+
+
rollback() - Method in class org.h2.jdbc.JdbcConnection
+
+
Rolls back the current transaction.
+
+
rollback(Savepoint) - Method in class org.h2.jdbc.JdbcConnection
+
+
Rolls back to a savepoint.
+
+
rollback(Xid) - Method in class org.h2.jdbcx.JdbcXAConnection
+
+
Roll back a transaction.
+
+
rollbackTo(SessionLocal.Savepoint) - Method in class org.h2.engine.SessionLocal
+
+
Partially roll back the current transaction.
+
+
rollbackToSavepoint(String) - Method in class org.h2.engine.SessionLocal
+
+
Undo all operations back to the log position of the given savepoint.
+
+
row(H2Type...) - Static method in class org.h2.api.H2Type
+
+
Returns ROW data type with specified types of fields and default names.
+
+
ROW_NOT_FOUND_IN_PRIMARY_INDEX - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90143 is thrown when + trying to fetch a row from the primary index and the row is not there.
+
+
ROW_NOT_FOUND_WHEN_DELETING_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90112 is thrown when a row was deleted + twice while locking was disabled.
+
+
rowCountCumulative - Variable in class org.h2.engine.QueryStatisticsData.QueryEntry
+
+
The total number of rows.
+
+
rowCountMax - Variable in class org.h2.engine.QueryStatisticsData.QueryEntry
+
+
The maximum number of rows.
+
+
rowCountMean - Variable in class org.h2.engine.QueryStatisticsData.QueryEntry
+
+
The mean number of rows.
+
+
rowCountMin - Variable in class org.h2.engine.QueryStatisticsData.QueryEntry
+
+
The minimum number of rows.
+
+
rowDeleted() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Detects if the row was deleted (by somebody else or the caller).
+
+
rowDeleted() - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
rowInserted() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Detects if the row was inserted.
+
+
rowInserted() - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
rowUpdated() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Detects if the row was updated (by somebody else or the caller).
+
+
rowUpdated() - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
run() - Method in class org.h2.tools.Server
+
+
INTERNAL
+
+
run() - Method in class org.h2.tools.Shell
+
+
INTERNAL.
+
+
RunScript - Class in org.h2.tools
+
+
Runs a SQL script against a database.
+
+
RunScript() - Constructor for class org.h2.tools.RunScript
+
 
+
runTool(String...) - Method in class org.h2.tools.Backup
+
 
+
runTool(String...) - Method in class org.h2.tools.ChangeFileEncryption
+
 
+
runTool(String...) - Method in class org.h2.tools.Console
+
+
This tool starts the H2 Console (web-) server, as well as the TCP and PG + server.
+
+
runTool(String...) - Method in class org.h2.tools.ConvertTraceFile
+
 
+
runTool(String...) - Method in class org.h2.tools.CreateCluster
+
 
+
runTool(String...) - Method in class org.h2.tools.DeleteDbFiles
+
 
+
runTool(String...) - Method in class org.h2.tools.Recover
+
+
Dumps the contents of a database file to a human readable text file.
+
+
runTool(String...) - Method in class org.h2.tools.Restore
+
 
+
runTool(String...) - Method in class org.h2.tools.RunScript
+
+
Executes the contents of a SQL script file against a database.
+
+
runTool(String...) - Method in class org.h2.tools.Script
+
 
+
runTool(String...) - Method in class org.h2.tools.Server
+
 
+
runTool(String...) - Method in class org.h2.tools.Shell
+
+
Run the shell tool with the given command line settings.
+
+
runTool(Connection, String...) - Method in class org.h2.tools.Shell
+
+
Run the shell tool with the given connection and command line settings.
+
+
+ + + +

S

+
+
SALT_LEN - Static variable in class org.h2.engine.Constants
+
+
The number of bytes in random salt that is used to hash passwords.
+
+
Savepoint() - Constructor for class org.h2.engine.SessionLocal.Savepoint
+
 
+
SAVEPOINT_IS_INVALID_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90063 is thrown when + trying to rollback to a savepoint that is not defined.
+
+
SAVEPOINT_IS_NAMED - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90065 is thrown when + Savepoint.getSavepointId() is called on a named savepoint.
+
+
SAVEPOINT_IS_UNNAMED - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90064 is thrown when + Savepoint.getSavepointName() is called on an unnamed savepoint.
+
+
SCALAR_SUBQUERY_CONTAINS_MORE_THAN_ONE_ROW - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90053 is thrown when + a subquery that is used as a value contains more than one row.
+
+
SCHEMA - Static variable in class org.h2.engine.DbObject
+
+
This object is a schema.
+
+
schema - Variable in class org.h2.fulltext.IndexInfo
+
+
The schema name.
+
+
SCHEMA_ALREADY_EXISTS_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90078 is thrown when + trying to create a schema if an object with this name already exists.
+
+
SCHEMA_CAN_NOT_BE_DROPPED_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90090 is thrown when + trying to drop a schema that may not be dropped (the schema PUBLIC + and the schema INFORMATION_SCHEMA).
+
+
SCHEMA_MAIN - Static variable in class org.h2.engine.Constants
+
+
The name of the default schema.
+
+
SCHEMA_NAME_MUST_MATCH - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90080 is thrown when + trying to rename a object to a different schema, or when trying to + create a related object in another schema.
+
+
SCHEMA_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90079 is thrown when + trying to drop a schema that does not exist.
+
+
SCHEMA_OWNER - Static variable in class org.h2.engine.Right
+
+
The right bit mask that means: user is a schema owner.
+
+
SCHEMA_PG_CATALOG - Static variable in class org.h2.engine.Constants
+
+
The name of the pg_catalog schema.
+
+
schemaName - Variable in class org.h2.tools.TriggerAdapter
+
+
The schema name.
+
+
Script - Class in org.h2.tools
+
+
Creates a SQL script file by extracting the schema and data of a database.
+
+
Script() - Constructor for class org.h2.tools.Script
+
 
+
search(Connection, String, int, int) - Static method in class org.h2.fulltext.FullText
+
+
Searches from the full text index for this database.
+
+
search(Connection, String, int, int, boolean) - Static method in class org.h2.fulltext.FullText
+
+
Do the search.
+
+
search(Connection, String, int, int) - Static method in class org.h2.fulltext.FullTextLucene
+
+
Searches from the full text index for this database.
+
+
search(Connection, String, int, int, boolean) - Static method in class org.h2.fulltext.FullTextLucene
+
+
Do the search.
+
+
searchData(Connection, String, int, int) - Static method in class org.h2.fulltext.FullText
+
+
Searches from the full text index for this database.
+
+
searchData(Connection, String, int, int) - Static method in class org.h2.fulltext.FullTextLucene
+
+
Searches from the full text index for this database.
+
+
SECOND_PRIMARY_KEY - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90017 is thrown when + trying to define a second primary key constraint for this table.
+
+
SELECT - Static variable in interface org.h2.api.Trigger
+
+
The trigger is called for SELECT statements.
+
+
SELECT - Static variable in class org.h2.engine.Right
+
+
The right bit mask that means: selecting from a table is allowed.
+
+
SELECTIVITY_DEFAULT - Static variable in class org.h2.engine.Constants
+
+
The default selectivity (used if the selectivity is not calculated).
+
+
SELECTIVITY_DISTINCT_COUNT - Static variable in class org.h2.engine.Constants
+
+
The number of distinct values to keep in memory when running ANALYZE.
+
+
SEQUENCE - Static variable in class org.h2.engine.DbObject
+
+
This object is a sequence.
+
+
SEQUENCE_ALREADY_EXISTS_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90035 is thrown when + trying to create a sequence if a sequence with this name already + exists.
+
+
SEQUENCE_ATTRIBUTES_INVALID_7 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90009 is thrown when + trying to create a sequence with an invalid combination + of attributes (min value, max value, start value, etc).
+
+
SEQUENCE_BELONGS_TO_A_TABLE_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90082 is thrown when + trying to drop a system generated sequence.
+
+
SEQUENCE_EXHAUSTED - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90006 is thrown when + trying to get a value from a sequence that has run out of numbers + and does not have cycling enabled.
+
+
SEQUENCE_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90036 is thrown when + trying to access a sequence that does not exist.
+
+
serialDataTypes - Variable in class org.h2.engine.Mode
+
+
Whether SERIAL and BIGSERIAL pseudo data types are supported.
+
+
SERIALIZATION_FAILED_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90026 is thrown when + an object could not be serialized.
+
+
serialize(Object) - Method in interface org.h2.api.JavaObjectSerializer
+
+
Serialize object to byte array.
+
+
Server - Class in org.h2.tools
+
+
Starts the H2 Console (web-) server, TCP, and PG server.
+
+
Server() - Constructor for class org.h2.tools.Server
+
 
+
Server(Service, String...) - Constructor for class org.h2.tools.Server
+
+
Create a new server for the given service.
+
+
SERVER_CACHED_OBJECTS - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.serverCachedObjects (default: 64).
+
+
SERVER_PROPERTIES_DIR - Static variable in class org.h2.engine.Constants
+
+
The default directory name of the server properties file for the H2 + Console.
+
+
SERVER_PROPERTIES_NAME - Static variable in class org.h2.engine.Constants
+
+
The name of the server properties file for the H2 Console.
+
+
SERVER_RESULT_SET_FETCH_SIZE - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.serverResultSetFetchSize + (default: 100).
+
+
Session - Class in org.h2.engine
+
+
A local or remote session.
+
+
session - Variable in class org.h2.jdbc.JdbcStatement
+
 
+
Session.DynamicSettings - Class in org.h2.engine
+
+
Dynamic settings.
+
+
Session.StaticSettings - Class in org.h2.engine
+
+
Static settings.
+
+
SESSION_CANCEL_STATEMENT - Static variable in class org.h2.engine.SessionRemote
+
 
+
SESSION_CHECK_KEY - Static variable in class org.h2.engine.SessionRemote
+
 
+
SESSION_CLOSE - Static variable in class org.h2.engine.SessionRemote
+
 
+
SESSION_HAS_PENDING_TRANSACTION - Static variable in class org.h2.engine.SessionRemote
+
 
+
SESSION_PREPARE - Static variable in class org.h2.engine.SessionRemote
+
 
+
SESSION_PREPARE_READ_PARAMS2 - Static variable in class org.h2.engine.SessionRemote
+
 
+
SESSION_SET_AUTOCOMMIT - Static variable in class org.h2.engine.SessionRemote
+
 
+
SESSION_SET_ID - Static variable in class org.h2.engine.SessionRemote
+
 
+
SessionLocal - Class in org.h2.engine
+
+
A session represents an embedded database connection.
+
+
SessionLocal(Database, User, int) - Constructor for class org.h2.engine.SessionLocal
+
 
+
SessionLocal.Savepoint - Class in org.h2.engine
+
+
Represents a savepoint (a position in a transaction to where one can roll + back to).
+
+
SessionLocal.State - Enum in org.h2.engine
+
 
+
SessionLocal.TimeoutValue - Class in org.h2.engine
+
+
An LOB object with a timeout.
+
+
SessionRemote - Class in org.h2.engine
+
+
The client side part of a session when using the server mode.
+
+
SessionRemote(ConnectionInfo) - Constructor for class org.h2.engine.SessionRemote
+
 
+
setAdmin(boolean) - Method in class org.h2.engine.User
+
 
+
setAllowBuiltinAliasOverride(boolean) - Method in class org.h2.engine.Database
+
 
+
setAllowLiterals(int) - Method in class org.h2.engine.Database
+
 
+
setAllowLiterals(boolean) - Method in class org.h2.engine.SessionLocal
+
 
+
setArray(int, Array) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter as an Array.
+
+
setAsciiStream(String, InputStream, long) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter as an ASCII stream.
+
+
setAsciiStream(String, InputStream) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter as an ASCII stream.
+
+
setAsciiStream(String, InputStream, int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter as an ASCII stream.
+
+
setAsciiStream(long) - Method in class org.h2.jdbc.JdbcClob
+
+
[Not supported] Returns an output stream.
+
+
setAsciiStream(int, InputStream, int) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter as an ASCII stream.
+
+
setAsciiStream(int, InputStream, long) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter as an ASCII stream.
+
+
setAsciiStream(int, InputStream) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter as an ASCII stream.
+
+
setAuthenticator(Authenticator) - Method in class org.h2.engine.Database
+
+
Set current database authenticator
+
+
setAutoClose(boolean) - Method in class org.h2.tools.SimpleResultSet
+
+
Set the auto-close behavior.
+
+
setAutoCommit(boolean) - Method in class org.h2.engine.Session
+
+
Set the auto-commit mode.
+
+
setAutoCommit(boolean) - Method in class org.h2.engine.SessionLocal
+
 
+
setAutoCommit(boolean) - Method in class org.h2.engine.SessionRemote
+
 
+
setAutoCommit(boolean) - Method in class org.h2.jdbc.JdbcConnection
+
+
Switches auto commit on or off.
+
+
setAutoCommitFromServer(boolean) - Method in class org.h2.engine.SessionRemote
+
 
+
setBackgroundException(DbException) - Method in class org.h2.engine.Database
+
 
+
setBaseDir(String) - Method in class org.h2.engine.ConnectionInfo
+
+
Set the base directory of persistent databases, unless the database is in + the user home folder (~).
+
+
setBaseDir(String) - Static method in class org.h2.engine.SysProperties
+
+
INTERNAL
+
+
setBigDecimal(String, BigDecimal) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter.
+
+
setBigDecimal(int, BigDecimal) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter.
+
+
setBinaryStream(long) - Method in class org.h2.jdbc.JdbcBlob
+
+
Get a writer to update the Blob.
+
+
setBinaryStream(String, InputStream, int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter as an input stream.
+
+
setBinaryStream(String, InputStream) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter as an input stream.
+
+
setBinaryStream(String, InputStream, long) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter as an input stream.
+
+
setBinaryStream(int, InputStream, long) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter as an input stream.
+
+
setBinaryStream(int, InputStream, int) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter as an input stream.
+
+
setBinaryStream(int, InputStream) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter as an input stream.
+
+
setBinaryStream() - Method in class org.h2.jdbc.JdbcSQLXML
+
 
+
setBlob(String, InputStream, long) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter as a Blob.
+
+
setBlob(String, Blob) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter as a Blob.
+
+
setBlob(String, InputStream) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter as a Blob.
+
+
setBlob(int, Blob) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter as a Blob.
+
+
setBlob(int, InputStream) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter as a Blob.
+
+
setBlob(int, InputStream, long) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter as a Blob.
+
+
setBoolean(String, boolean) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter.
+
+
setBoolean(int, boolean) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter.
+
+
setByte(String, byte) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter.
+
+
setByte(int, byte) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter.
+
+
setBytes(long, byte[]) - Method in class org.h2.jdbc.JdbcBlob
+
+
Fills the Blob.
+
+
setBytes(long, byte[], int, int) - Method in class org.h2.jdbc.JdbcBlob
+
+
Sets some bytes of the object.
+
+
setBytes(String, byte[]) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter as a byte array.
+
+
setBytes(int, byte[]) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter as a byte array.
+
+
setCacheSize(int) - Method in class org.h2.engine.Database
+
 
+
setCaseSensitiveColumnNames(boolean) - Method in class org.h2.tools.Csv
+
+
Override the case sensitive column names setting.
+
+
setCatalog(String) - Method in class org.h2.jdbc.JdbcConnection
+
+
Set the default catalog name.
+
+
setCharacterStream(String, Reader, int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter as a character stream.
+
+
setCharacterStream(String, Reader) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter as a character stream.
+
+
setCharacterStream(String, Reader, long) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter as a character stream.
+
+
setCharacterStream(long) - Method in class org.h2.jdbc.JdbcClob
+
+
Get a writer to update the Clob.
+
+
setCharacterStream(int, Reader, int) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter as a character stream.
+
+
setCharacterStream(int, Reader) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter as a character stream.
+
+
setCharacterStream(int, Reader, long) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter as a character stream.
+
+
setCharacterStream() - Method in class org.h2.jdbc.JdbcSQLXML
+
 
+
setClientInfo(String, String) - Method in class org.h2.jdbc.JdbcConnection
+
+
Set a client property.
+
+
setClientInfo(Properties) - Method in class org.h2.jdbc.JdbcConnection
+
+
Set the client properties.
+
+
setClob(String, Reader, long) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter as a Clob.
+
+
setClob(String, Clob) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter as a Clob.
+
+
setClob(String, Reader) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter as a character stream.
+
+
setClob(int, Clob) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter as a Clob.
+
+
setClob(int, Reader) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter as a Clob.
+
+
setClob(int, Reader, long) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter as a Clob.
+
+
setCloseDelay(int) - Method in class org.h2.engine.Database
+
 
+
setCluster(String) - Method in class org.h2.engine.Database
+
 
+
setColumns(int[], ArrayList<String>, ArrayList<String>) - Static method in class org.h2.fulltext.FullText
+
+
Set the column indices of a set of keys.
+
+
setComment(String) - Method in class org.h2.engine.DbObject
+
+
Change the comment of this object.
+
+
setCommentText(String) - Method in class org.h2.engine.Comment
+
+
Set the comment text.
+
+
setCommitOrRollbackDisabled(boolean) - Method in class org.h2.engine.SessionLocal
+
 
+
setCompactMode(int) - Method in class org.h2.engine.Database
+
 
+
setCompareMode(CompareMode) - Method in class org.h2.engine.Database
+
 
+
setCurrentSchema(Schema) - Method in class org.h2.engine.SessionLocal
+
 
+
setCurrentSchemaName(String) - Method in class org.h2.engine.Session
+
+
Set current schema.
+
+
setCurrentSchemaName(String) - Method in class org.h2.engine.SessionLocal
+
 
+
setCurrentSchemaName(String) - Method in class org.h2.engine.SessionRemote
+
 
+
setCursorName(String) - Method in class org.h2.jdbc.JdbcStatement
+
+
Sets the name of the cursor.
+
+
setDate(String, Date, Calendar) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the date using a specified time zone.
+
+
setDate(String, Date) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter.
+
+
setDate(int, Date) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter.
+
+
setDate(int, Date, Calendar) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the date using a specified time zone.
+
+
setDefaultNullOrdering(DefaultNullOrdering) - Method in class org.h2.engine.Database
+
 
+
setDefaultTableType(int) - Method in class org.h2.engine.Database
+
 
+
setDeleteFilesOnDisconnect(boolean) - Method in class org.h2.engine.Database
+
 
+
setDescription(String) - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Set the description.
+
+
setDouble(String, double) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter.
+
+
setDouble(int, double) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter.
+
+
setErr(PrintStream) - Method in class org.h2.tools.Shell
+
+
Sets the standard error stream.
+
+
setEscapeCharacter(char) - Method in class org.h2.tools.Csv
+
+
Set the escape character.
+
+
setEscapeProcessing(boolean) - Method in class org.h2.jdbc.JdbcStatement
+
+
Enables or disables processing or JDBC escape syntax.
+
+
setEventListener(DatabaseEventListener) - Method in class org.h2.engine.Database
+
 
+
setEventListenerClass(String) - Method in class org.h2.engine.Database
+
 
+
setExclusiveSession(SessionLocal, boolean) - Method in class org.h2.engine.Database
+
+
Set the session that can exclusively access the database.
+
+
setFetchDirection(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
[Not supported] + Sets (changes) the fetch direction for this result set.
+
+
setFetchDirection(int) - Method in class org.h2.jdbc.JdbcStatement
+
+
Sets the fetch direction.
+
+
setFetchDirection(int) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
setFetchSize(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Sets the number of rows suggested to read in one step.
+
+
setFetchSize(int) - Method in class org.h2.jdbc.JdbcStatement
+
+
Sets the number of rows suggested to read in one step.
+
+
setFetchSize(int) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
setFieldDelimiter(char) - Method in class org.h2.tools.Csv
+
+
Set the field delimiter.
+
+
setFieldSeparatorRead(char) - Method in class org.h2.tools.Csv
+
+
Override the field separator for reading.
+
+
setFieldSeparatorWrite(String) - Method in class org.h2.tools.Csv
+
+
Override the field separator for writing.
+
+
setFileEncryptionKey(byte[]) - Method in class org.h2.engine.ConnectionInfo
+
 
+
setFilePasswordHash(byte[]) - Method in class org.h2.engine.ConnectionInfo
+
+
Set the file password hash.
+
+
setFloat(String, float) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter.
+
+
setFloat(int, float) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter.
+
+
setHoldability(int) - Method in class org.h2.jdbc.JdbcConnection
+
+
Changes the current result set holdability.
+
+
setIgnoreCase(boolean) - Method in class org.h2.engine.Database
+
 
+
setIgnoreCatalogs(boolean) - Method in class org.h2.engine.Database
+
 
+
setIgnoreList(Connection, String) - Static method in class org.h2.fulltext.FullText
+
+
Change the ignore list.
+
+
setIn(InputStream) - Method in class org.h2.tools.Shell
+
+
Redirects the standard input.
+
+
setInitialPowerOffCount(int) - Static method in class org.h2.engine.Database
+
 
+
setInReader(BufferedReader) - Method in class org.h2.tools.Shell
+
+
Redirects the standard input.
+
+
setInt(String, int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter.
+
+
setInt(int, int) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter.
+
+
setIntValue(int) - Method in class org.h2.engine.Setting
+
 
+
setIsolationLevel(IsolationLevel) - Method in class org.h2.engine.Session
+
+
Sets the isolation level.
+
+
setIsolationLevel(IsolationLevel) - Method in class org.h2.engine.SessionLocal
+
 
+
setIsolationLevel(IsolationLevel) - Method in class org.h2.engine.SessionRemote
+
 
+
setJavaObjectSerializerName(String) - Method in class org.h2.engine.Database
+
 
+
setLargeMaxRows(long) - Method in class org.h2.jdbc.JdbcStatement
+
+
Gets the maximum number of rows for a ResultSet.
+
+
setLastIdentity(Value) - Method in class org.h2.engine.SessionLocal
+
 
+
setLazyQueryExecution(boolean) - Method in class org.h2.engine.SessionLocal
+
 
+
setLineCommentCharacter(char) - Method in class org.h2.tools.Csv
+
+
Set the line comment character.
+
+
setLineSeparator(String) - Method in class org.h2.tools.Csv
+
+
Set the line separator used for writing.
+
+
setLockMode(int) - Method in class org.h2.engine.Database
+
 
+
setLockTimeout(int) - Method in class org.h2.engine.SessionLocal
+
 
+
setLoginTimeout(int) - Method in class org.h2.jdbcx.JdbcConnectionPool
+
+
Sets the maximum time in seconds to wait for a free connection.
+
+
setLoginTimeout(int) - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Set the login timeout in seconds, 0 meaning no timeout.
+
+
setLogWriter(PrintWriter) - Method in class org.h2.jdbcx.JdbcConnectionPool
+
+
INTERNAL
+
+
setLogWriter(PrintWriter) - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Set the current log writer for this object.
+
+
setLong(String, long) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter.
+
+
setLong(int, long) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter.
+
+
setMasterUser(User) - Method in class org.h2.engine.Database
+
 
+
setMaxConnections(int) - Method in class org.h2.jdbcx.JdbcConnectionPool
+
+
Sets the maximum number of connections to use from now on.
+
+
setMaxFieldSize(int) - Method in class org.h2.jdbc.JdbcStatement
+
+
Sets the maximum number of bytes for a result set column.
+
+
setMaxLengthInplaceLob(int) - Method in class org.h2.engine.Database
+
 
+
setMaxMemoryRows(int) - Method in class org.h2.engine.Database
+
 
+
setMaxOperationMemory(int) - Method in class org.h2.engine.Database
+
 
+
setMaxQueryEntries(int) - Method in class org.h2.engine.QueryStatisticsData
+
 
+
setMaxRows(int) - Method in class org.h2.jdbc.JdbcStatement
+
+
Gets the maximum number of rows for a ResultSet.
+
+
setMode(Mode) - Method in class org.h2.engine.Database
+
 
+
setModified() - Method in class org.h2.engine.DbObject
+
+
Tell the object that is was modified.
+
+
setNCharacterStream(String, Reader, long) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter as a character stream.
+
+
setNCharacterStream(String, Reader) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter as a character stream.
+
+
setNCharacterStream(int, Reader, long) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter as a character stream.
+
+
setNCharacterStream(int, Reader) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter as a character stream.
+
+
setNClob(String, NClob) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter as a Clob.
+
+
setNClob(String, Reader, long) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter as a Clob.
+
+
setNClob(String, Reader) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter as a Clob.
+
+
setNClob(int, NClob) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter as a Clob.
+
+
setNClob(int, Reader) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter as a Clob.
+
+
setNClob(int, Reader, long) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter as a Clob.
+
+
setNetworkConnectionInfo(NetworkConnectionInfo) - Method in class org.h2.engine.ConnectionInfo
+
+
Sets the network connection information.
+
+
setNetworkConnectionInfo(NetworkConnectionInfo) - Method in class org.h2.engine.Session
+
+
Sets the network connection information if possible.
+
+
setNetworkConnectionInfo(NetworkConnectionInfo) - Method in class org.h2.engine.SessionLocal
+
 
+
setNetworkConnectionInfo(NetworkConnectionInfo) - Method in class org.h2.engine.SessionRemote
+
 
+
setNetworkTimeout(Executor, int) - Method in class org.h2.jdbc.JdbcConnection
+
+
[Not supported]
+
+
setNonKeywords(BitSet) - Method in class org.h2.engine.SessionLocal
+
+
Sets bit set of non-keywords.
+
+
setNString(String, String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter.
+
+
setNString(int, String) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter.
+
+
setNull(String, int, String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets a parameter to null.
+
+
setNull(String, int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets a parameter to null.
+
+
setNull(int, int) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets a parameter to null.
+
+
setNull(int, int, String) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets a parameter to null.
+
+
setNullString(String) - Method in class org.h2.tools.Csv
+
+
Set the value that represents NULL.
+
+
setObject(String, Object) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter.
+
+
setObject(String, Object, int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter.
+
+
setObject(String, Object, int, int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter.
+
+
setObject(String, Object, SQLType) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter.
+
+
setObject(String, Object, SQLType, int) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter.
+
+
setObject(int, Object) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter.
+
+
setObject(int, Object, int) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter.
+
+
setObject(int, Object, int, int) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter.
+
+
setObject(int, Object, SQLType) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter.
+
+
setObject(int, Object, SQLType, int) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter.
+
+
setObjectName(String) - Method in class org.h2.engine.DbObject
+
 
+
setOldInformationSchema(boolean) - Method in class org.h2.engine.SessionLocal
+
+
Changes INFORMATION_SCHEMA content.
+
+
setOptimizeReuseResults(boolean) - Method in class org.h2.engine.Database
+
 
+
setOptions(String) - Method in class org.h2.tools.Csv
+
+
INTERNAL.
+
+
setOriginalURL(String) - Method in class org.h2.engine.ConnectionInfo
+
+
Set the original database URL.
+
+
setParsingCreateView(boolean) - Method in class org.h2.engine.SessionLocal
+
+
This method is called before and after parsing of view definition and may + be called recursively.
+
+
setPassword(String) - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Set the current password.
+
+
setPasswordChars(char[]) - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Set the current password in the form of a char array.
+
+
setPoolable(boolean) - Method in class org.h2.jdbc.JdbcStatement
+
+
Requests that this object should be pooled or not.
+
+
setPowerOffCount(int) - Method in class org.h2.engine.Database
+
 
+
setPreparedTransaction(String, boolean) - Method in class org.h2.engine.SessionLocal
+
+
Commit or roll back the given transaction.
+
+
setPreserveWhitespace(boolean) - Method in class org.h2.tools.Csv
+
+
Enable or disable preserving whitespace in unquoted text.
+
+
setProgress(int, String, long, long) - Method in interface org.h2.api.DatabaseEventListener
+
+
This method is called for long running events, such as recovering, + scanning a file or building an index.
+
+
setProgress(int, String, long, long) - Method in class org.h2.engine.Database
+
+
Set the progress of a long running operation.
+
+
setProperty(String, String) - Method in class org.h2.engine.ConnectionInfo
+
+
Overwrite a property.
+
+
setQueryStatistics(boolean) - Method in class org.h2.engine.Database
+
 
+
setQueryStatisticsMaxEntries(int) - Method in class org.h2.engine.Database
+
 
+
setQueryTimeout(int) - Method in class org.h2.engine.SessionLocal
+
 
+
setQueryTimeout(int) - Method in class org.h2.jdbc.JdbcStatement
+
+
Sets the current query timeout in seconds.
+
+
setQuirksMode(boolean) - Method in class org.h2.engine.SessionLocal
+
+
Enables or disables the quirks mode.
+
+
setReadOnly(boolean) - Method in class org.h2.engine.Database
+
+
Switch the database to read-only mode.
+
+
setReadOnly(boolean) - Method in class org.h2.jdbc.JdbcConnection
+
+
According to the JDBC specs, this setting is only a hint to the database + to enable optimizations - it does not cause writes to be prohibited.
+
+
setRef(int, Ref) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
[Not supported] Sets the value of a column as a reference.
+
+
setReferentialIntegrity(boolean) - Method in class org.h2.engine.Database
+
 
+
setResult(Class<T>) - Method in class org.h2.jdbc.JdbcSQLXML
+
 
+
setRetentionTime(int) - Method in class org.h2.engine.Database
+
 
+
setRightMask(int) - Method in class org.h2.engine.Right
+
 
+
setRowFactory(RowFactory) - Method in class org.h2.engine.Database
+
 
+
setRowId(String, RowId) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
[Not supported] Sets the value of a parameter as a row id.
+
+
setRowId(int, RowId) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
[Not supported] Sets the value of a parameter as a row id.
+
+
setSaltAndHash(byte[], byte[]) - Method in class org.h2.engine.User
+
+
Set the salt and hash of the password for this user.
+
+
setSavepoint() - Method in class org.h2.engine.SessionLocal
+
+
Create a savepoint to allow rolling back to this state.
+
+
setSavepoint() - Method in class org.h2.jdbc.JdbcConnection
+
+
Creates a new unnamed savepoint.
+
+
setSavepoint(String) - Method in class org.h2.jdbc.JdbcConnection
+
+
Creates a new named savepoint.
+
+
setSchema(String) - Method in class org.h2.jdbc.JdbcConnection
+
+
Sets the given schema name to access.
+
+
setSchemaSearchPath(String[]) - Method in class org.h2.engine.SessionLocal
+
 
+
setServerKey(String) - Method in class org.h2.engine.ConnectionInfo
+
+
Switch to server mode, and set the server name and database key.
+
+
setShort(String, short) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter.
+
+
setShort(int, short) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter.
+
+
setShutdownHandler(ShutdownHandler) - Method in class org.h2.tools.Server
+
+
INTERNAL
+
+
setSQL(String) - Method in interface org.h2.jdbc.JdbcException
+
+
INTERNAL
+
+
setSQL(String) - Method in exception org.h2.jdbc.JdbcSQLDataException
+
 
+
setSQL(String) - Method in exception org.h2.jdbc.JdbcSQLException
+
 
+
setSQL(String) - Method in exception org.h2.jdbc.JdbcSQLFeatureNotSupportedException
+
 
+
setSQL(String) - Method in exception org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException
+
 
+
setSQL(String) - Method in exception org.h2.jdbc.JdbcSQLInvalidAuthorizationSpecException
+
 
+
setSQL(String) - Method in exception org.h2.jdbc.JdbcSQLNonTransientConnectionException
+
 
+
setSQL(String) - Method in exception org.h2.jdbc.JdbcSQLNonTransientException
+
 
+
setSQL(String) - Method in exception org.h2.jdbc.JdbcSQLSyntaxErrorException
+
 
+
setSQL(String) - Method in exception org.h2.jdbc.JdbcSQLTimeoutException
+
 
+
setSQL(String) - Method in exception org.h2.jdbc.JdbcSQLTransactionRollbackException
+
 
+
setSQL(String) - Method in exception org.h2.jdbc.JdbcSQLTransientException
+
 
+
setSQLXML(String, SQLXML) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter as a SQLXML object.
+
+
setSQLXML(int, SQLXML) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter as a SQLXML.
+
+
setString(String, String) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter.
+
+
setString(long, String) - Method in class org.h2.jdbc.JdbcClob
+
+
Fills the Clob.
+
+
setString(long, String, int, int) - Method in class org.h2.jdbc.JdbcClob
+
+
Fills the Clob.
+
+
setString(int, String) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter.
+
+
setString(String) - Method in class org.h2.jdbc.JdbcSQLXML
+
 
+
setStringValue(String) - Method in class org.h2.engine.Setting
+
 
+
setTemporary(boolean) - Method in class org.h2.engine.DbObject
+
+
Tell this object that it is temporary or not.
+
+
setThreadLocalSession() - Method in class org.h2.engine.Session
+
+
Sets this session as thread local session, if this session is a local + session.
+
+
setThreadLocalSession() - Method in class org.h2.engine.SessionLocal
+
 
+
setThrottle(int) - Method in class org.h2.engine.SessionLocal
+
 
+
setTime(String, Time, Calendar) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the time using a specified time zone.
+
+
setTime(String, Time) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the time using a specified time zone.
+
+
setTime(int, Time) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter.
+
+
setTime(int, Time, Calendar) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the time using a specified time zone.
+
+
setTimestamp(String, Timestamp, Calendar) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the timestamp using a specified time zone.
+
+
setTimestamp(String, Timestamp) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Sets the value of a parameter.
+
+
setTimestamp(int, Timestamp) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the value of a parameter.
+
+
setTimestamp(int, Timestamp, Calendar) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Sets the timestamp using a specified time zone.
+
+
setTimeZone(TimeZoneProvider) - Method in class org.h2.engine.SessionLocal
+
+
Sets current time zone.
+
+
SETTING - Static variable in class org.h2.engine.DbObject
+
+
This object is a setting.
+
+
Setting - Class in org.h2.engine
+
+
A persistent database setting.
+
+
Setting(Database, int, String) - Constructor for class org.h2.engine.Setting
+
 
+
SettingsBase - Class in org.h2.engine
+
+
The base class for settings.
+
+
SettingsBase(HashMap<String, String>) - Constructor for class org.h2.engine.SettingsBase
+
 
+
setTransactionIsolation(int) - Method in class org.h2.jdbc.JdbcConnection
+
+
Changes the current transaction isolation level.
+
+
setTransactionTimeout(int) - Method in class org.h2.jdbcx.JdbcXAConnection
+
+
Set the transaction timeout.
+
+
setTruncateLargeLength(boolean) - Method in class org.h2.engine.SessionLocal
+
+
Changes parsing mode of data types with too large length.
+
+
setTypeMap(Map<String, Class<?>>) - Method in class org.h2.jdbc.JdbcConnection
+
+
[Partially supported] Sets the type map.
+
+
setUnicodeStream(int, InputStream, int) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
Deprecated. +
since JDBC 2.0, use setCharacterStream
+
+
+
setURL(String, URL) - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
[Not supported]
+
+
setURL(int, URL) - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
[Not supported]
+
+
setURL(String) - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Set the current URL.
+
+
setUrl(String) - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Set the current URL.
+
+
setUser(String) - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Set the current user name.
+
+
setUserName(String) - Method in class org.h2.engine.ConnectionInfo
+
+
Overwrite the user name.
+
+
setUserPasswordHash(byte[]) - Method in class org.h2.engine.ConnectionInfo
+
+
Set the user password hash.
+
+
setUserPasswordHash(byte[]) - Method in class org.h2.engine.User
+
+
Set the user name password hash.
+
+
setVariable(String, Value) - Method in class org.h2.engine.SessionLocal
+
+
Set the value of the given variable for this session.
+
+
setVariableBinary(boolean) - Method in class org.h2.engine.SessionLocal
+
+
Changes parsing of a BINARY data type.
+
+
setWaitForLock(Table, Thread) - Method in class org.h2.engine.SessionLocal
+
+
Set the table this session is waiting for, and the thread that is + waiting.
+
+
setWhitespaceChars(Connection, String) - Static method in class org.h2.fulltext.FullText
+
+
Change the whitespace characters.
+
+
setWriteColumnHeader(boolean) - Method in class org.h2.tools.Csv
+
+
Enable or disable writing the column header.
+
+
setWriteDelay(int) - Method in class org.h2.engine.Database
+
 
+
shareLinkedConnections - Variable in class org.h2.engine.DbSettings
+
+
Database setting SHARE_LINKED_CONNECTIONS + (default: true).
+
+
Shell - Class in org.h2.tools
+
+
Interactive command line tool to access a database using JDBC.
+
+
Shell() - Constructor for class org.h2.tools.Shell
+
 
+
shutdown() - Method in class org.h2.tools.Console
+
+
INTERNAL.
+
+
shutdown() - Method in class org.h2.tools.GUIConsole
+
 
+
shutdown() - Method in class org.h2.tools.Server
+
+
INTERNAL
+
+
shutdownImmediately() - Method in class org.h2.engine.Database
+
+
Immediately close the database.
+
+
shutdownTcpServer(String, String, boolean, boolean) - Static method in class org.h2.tools.Server
+
+
Shutdown one or all TCP server.
+
+
SimpleResultSet - Class in org.h2.tools
+
+
This class is a simple result set and meta data implementation.
+
+
SimpleResultSet() - Constructor for class org.h2.tools.SimpleResultSet
+
+
This constructor is used if the result set is later populated with + addRow.
+
+
SimpleResultSet(SimpleRowSource) - Constructor for class org.h2.tools.SimpleResultSet
+
+
This constructor is used if the result set should retrieve the rows using + the specified row source object.
+
+
SimpleResultSet.SimpleArray - Class in org.h2.tools
+
+
A simple array implementation, + backed by an object array
+
+
SimpleRowSource - Interface in org.h2.tools
+
+
This interface is for classes that create rows on demand.
+
+
SLOW_QUERY_LIMIT_MS - Static variable in class org.h2.engine.Constants
+
+
Queries that take longer than this number of milliseconds are written to + the trace file with the level info.
+
+
SMALLINT - Static variable in class org.h2.api.H2Type
+
+
The SMALLINT data type.
+
+
SOCKET_CONNECT_RETRY - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.socketConnectRetry (default: 16).
+
+
SOCKET_CONNECT_TIMEOUT - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.socketConnectTimeout + (default: 2000).
+
+
SPLIT_FILE_SIZE_SHIFT - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.splitFileSizeShift (default: 30).
+
+
sqlStatement - Variable in class org.h2.engine.QueryStatisticsData.QueryEntry
+
+
The SQL statement.
+
+
squareBracketQuotedNames - Variable in class org.h2.engine.Mode
+
+
Identifiers may be quoted using square brackets as in [Test].
+
+
start(Xid, int) - Method in class org.h2.jdbcx.JdbcXAConnection
+
+
Start or continue to work on a transaction.
+
+
start() - Method in class org.h2.tools.Server
+
+
Tries to start the server.
+
+
START_URL - Static variable in class org.h2.engine.Constants
+
+
The database URL prefix of this database.
+
+
startStatementWithinTransaction(Command) - Method in class org.h2.engine.SessionLocal
+
+
Start a new statement within a transaction.
+
+
startWebServer(Connection) - Static method in class org.h2.tools.Server
+
+
Start a web server and a browser that uses the given connection.
+
+
startWebServer(Connection, boolean) - Static method in class org.h2.tools.Server
+
+
Start a web server and a browser that uses the given connection.
+
+
STATE_BACKUP_FILE - Static variable in interface org.h2.api.DatabaseEventListener
+
+
This state is used during the BACKUP command.
+
+
STATE_CREATE_INDEX - Static variable in interface org.h2.api.DatabaseEventListener
+
+
This state is used when re-creating an index.
+
+
STATE_RECONNECTED - Static variable in interface org.h2.api.DatabaseEventListener
+
+
This state is used after re-connecting to a database (if auto-reconnect + is enabled).
+
+
STATE_RECOVER - Static variable in interface org.h2.api.DatabaseEventListener
+
+
This state is used when re-applying the transaction log or rolling back + uncommitted transactions.
+
+
STATE_SCAN_FILE - Static variable in interface org.h2.api.DatabaseEventListener
+
+
This state is used when scanning the database file.
+
+
STATE_STATEMENT_END - Static variable in interface org.h2.api.DatabaseEventListener
+
+
This state is used when a query ends.
+
+
STATE_STATEMENT_PROGRESS - Static variable in interface org.h2.api.DatabaseEventListener
+
+
This state is used for periodic notification during long-running queries.
+
+
STATE_STATEMENT_START - Static variable in interface org.h2.api.DatabaseEventListener
+
+
This state is used when a query starts.
+
+
STATEMENT_WAS_CANCELED - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 57014 is thrown when + a statement was canceled using Statement.cancel() or + when the query timeout has been reached.
+
+
StaticSettings(boolean, boolean, boolean) - Constructor for class org.h2.engine.Session.StaticSettings
+
+
Creates new instance of static settings.
+
+
STATUS_CLOSED - Static variable in class org.h2.engine.SessionRemote
+
 
+
STATUS_ERROR - Static variable in class org.h2.engine.SessionRemote
+
 
+
STATUS_OK - Static variable in class org.h2.engine.SessionRemote
+
 
+
STATUS_OK_STATE_CHANGED - Static variable in class org.h2.engine.SessionRemote
+
 
+
STEP_SIZE_MUST_NOT_BE_ZERO - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90142 is thrown when + trying to set zero for step size.
+
+
stop() - Method in class org.h2.tools.Server
+
+
Stops the server.
+
+
STORE_DOCUMENT_TEXT_IN_INDEX - Static variable in class org.h2.fulltext.FullTextLucene
+
+
Whether the text content should be stored in the Lucene index.
+
+
storesLowerCaseIdentifiers() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Checks if for CREATE TABLE Test(ID INT), getTables returns test as the + table name.
+
+
storesLowerCaseQuotedIdentifiers() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Checks if for CREATE TABLE "Test"(ID INT), getTables returns test as the + table name.
+
+
storesMixedCaseIdentifiers() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Checks if for CREATE TABLE Test(ID INT), getTables returns Test as the + table name and identifiers are not case sensitive.
+
+
storesMixedCaseQuotedIdentifiers() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Checks if for CREATE TABLE "Test"(ID INT), getTables returns Test as the + table name and identifiers are case insensitive.
+
+
storesUpperCaseIdentifiers() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Checks if for CREATE TABLE Test(ID INT), getTables returns TEST as the + table name.
+
+
storesUpperCaseQuotedIdentifiers() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Checks if for CREATE TABLE "Test"(ID INT), getTables returns TEST as the + table name.
+
+
STRING_FORMAT_ERROR_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90095 is thrown when + calling the method STRINGDECODE with an invalid escape sequence.
+
+
SUBQUERY_IS_NOT_SINGLE_COLUMN - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90052 is thrown when a single-column + subquery is expected but a subquery with other number of columns was + specified.
+
+
SUFFIX_LOCK_FILE - Static variable in class org.h2.engine.Constants
+
+
The file name suffix of file lock files that are used to make sure a + database is open by only one process at any time.
+
+
SUFFIX_MV_FILE - Static variable in class org.h2.engine.Constants
+
+
The file name suffix of a MVStore file.
+
+
SUFFIX_MV_STORE_NEW_FILE - Static variable in class org.h2.engine.Constants
+
+
The file name suffix of a new MVStore file, used when compacting a store.
+
+
SUFFIX_MV_STORE_TEMP_FILE - Static variable in class org.h2.engine.Constants
+
+
The file name suffix of a temporary MVStore file, used when compacting a + store.
+
+
SUFFIX_OLD_DATABASE_FILE - Static variable in class org.h2.engine.Constants
+
+
The file name suffix of a H2 version 1.1 database file.
+
+
SUFFIX_TEMP_FILE - Static variable in class org.h2.engine.Constants
+
+
The file name suffix of temporary files.
+
+
SUFFIX_TRACE_FILE - Static variable in class org.h2.engine.Constants
+
+
The file name suffix of trace files.
+
+
SUM_OR_AVG_ON_WRONG_DATATYPE_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90015 is thrown when + using an aggregate function with a data type that is not supported.
+
+
supportedClientInfoPropertiesRegEx - Variable in class org.h2.engine.Mode
+
+
Pattern describing the keys the java.sql.Connection.setClientInfo() + method accepts.
+
+
supportPoundSymbolForColumnNames - Variable in class org.h2.engine.Mode
+
+
Support the # for column names
+
+
supportsAlterTableWithAddColumn() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether alter table with add column is supported.
+
+
supportsAlterTableWithDropColumn() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether alter table with drop column is supported.
+
+
supportsANSI92EntryLevelSQL() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether SQL-92 entry level grammar is supported.
+
+
supportsANSI92FullSQL() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether SQL-92 full level grammar is supported.
+
+
supportsANSI92IntermediateSQL() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether SQL-92 intermediate level grammar is supported.
+
+
supportsBatchUpdates() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether batch updates are supported.
+
+
supportsCatalogsInDataManipulation() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether the catalog name in INSERT, UPDATE, DELETE is supported.
+
+
supportsCatalogsInIndexDefinitions() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether the catalog name in CREATE INDEX is supported.
+
+
supportsCatalogsInPrivilegeDefinitions() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether the catalog name in GRANT is supported.
+
+
supportsCatalogsInProcedureCalls() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether the catalog name in procedure calls is supported.
+
+
supportsCatalogsInTableDefinitions() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether the catalog name in CREATE TABLE is supported.
+
+
supportsColumnAliasing() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether column aliasing is supported.
+
+
supportsConvert() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether CONVERT is supported.
+
+
supportsConvert(int, int) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether CONVERT is supported for one datatype to another.
+
+
supportsCoreSQLGrammar() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether ODBC Core SQL grammar is supported.
+
+
supportsCorrelatedSubqueries() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether correlated subqueries are supported.
+
+
supportsDataDefinitionAndDataManipulationTransactions() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether data manipulation and CREATE/DROP is supported in + transactions.
+
+
supportsDataManipulationTransactionsOnly() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether only data manipulations are supported in transactions.
+
+
supportsDifferentTableCorrelationNames() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether table correlation names (table alias) are restricted to + be different than table names.
+
+
supportsExpressionsInOrderBy() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether expression in ORDER BY are supported.
+
+
supportsExtendedSQLGrammar() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether ODBC Extended SQL grammar is supported.
+
+
supportsFullOuterJoins() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether full outer joins are supported.
+
+
supportsGetGeneratedKeys() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Does the database support getGeneratedKeys.
+
+
supportsGroupBy() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether GROUP BY is supported.
+
+
supportsGroupByBeyondSelect() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Checks whether a GROUP BY clause can use columns that are not in the + SELECT clause, provided that it specifies all the columns in the SELECT + clause.
+
+
supportsGroupByUnrelated() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether GROUP BY is supported if the column is not in the SELECT + list.
+
+
supportsIntegrityEnhancementFacility() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether referential integrity is supported.
+
+
supportsLikeEscapeClause() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether LIKE...
+
+
supportsLimitedOuterJoins() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether limited outer joins are supported.
+
+
supportsMinimumSQLGrammar() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether ODBC Minimum SQL grammar is supported.
+
+
supportsMixedCaseIdentifiers() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Checks if for CREATE TABLE Test(ID INT), getTables returns Test as the + table name and identifiers are case sensitive.
+
+
supportsMixedCaseQuotedIdentifiers() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Checks if a table created with CREATE TABLE "Test"(ID INT) is a different + table than a table created with CREATE TABLE "TEST"(ID INT).
+
+
supportsMultipleOpenResults() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Does the database support multiple open result sets returned from a + CallableStatement.
+
+
supportsMultipleResultSets() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether multiple result sets are supported.
+
+
supportsMultipleTransactions() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether multiple transactions (on different connections) are + supported.
+
+
supportsNamedParameters() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Does the database support named parameters.
+
+
supportsNonNullableColumns() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether columns with NOT NULL are supported.
+
+
supportsOpenCursorsAcrossCommit() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether open result sets across commits are supported.
+
+
supportsOpenCursorsAcrossRollback() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether open result sets across rollback are supported.
+
+
supportsOpenStatementsAcrossCommit() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether open statements across commit are supported.
+
+
supportsOpenStatementsAcrossRollback() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether open statements across rollback are supported.
+
+
supportsOrderByUnrelated() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether ORDER BY is supported if the column is not in the SELECT + list.
+
+
supportsOuterJoins() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether outer joins are supported.
+
+
supportsPositionedDelete() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether positioned deletes are supported.
+
+
supportsPositionedUpdate() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether positioned updates are supported.
+
+
supportsResultSetConcurrency(int, int) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether a specific result set concurrency is supported.
+
+
supportsResultSetHoldability(int) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Does this database supports a result set holdability.
+
+
supportsResultSetType(int) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether a specific result set type is supported.
+
+
supportsSavepoints() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Does the database support savepoints.
+
+
supportsSchemasInDataManipulation() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether the schema name in INSERT, UPDATE, DELETE is supported.
+
+
supportsSchemasInIndexDefinitions() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether the schema name in CREATE INDEX is supported.
+
+
supportsSchemasInPrivilegeDefinitions() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether the schema name in GRANT is supported.
+
+
supportsSchemasInProcedureCalls() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether the schema name in procedure calls is supported.
+
+
supportsSchemasInTableDefinitions() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether the schema name in CREATE TABLE is supported.
+
+
supportsSelectForUpdate() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether SELECT ...
+
+
supportsStatementPooling() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Does the database support statement pooling.
+
+
supportsStoredFunctionsUsingCallSyntax() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether the database supports calling functions using the call + syntax.
+
+
supportsStoredProcedures() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether stored procedures are supported.
+
+
supportsSubqueriesInComparisons() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether subqueries (SELECT) in comparisons are supported.
+
+
supportsSubqueriesInExists() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether SELECT in EXISTS is supported.
+
+
supportsSubqueriesInIns() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether IN(SELECT...) is supported.
+
+
supportsSubqueriesInQuantifieds() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether subqueries in quantified expression are supported.
+
+
supportsTableCorrelationNames() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether table correlation names (table alias) are supported.
+
+
supportsTransactionIsolationLevel(int) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether a specific transaction isolation level is supported.
+
+
supportsTransactions() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether transactions are supported.
+
+
supportsUnion() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether UNION SELECT is supported.
+
+
supportsUnionAll() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether UNION ALL SELECT is supported.
+
+
swapConvertFunctionParameters - Variable in class org.h2.engine.Mode
+
+
Swap the parameters of the CONVERT function.
+
+
swapLogFunctionParameters - Variable in class org.h2.engine.Mode
+
+
Swap the parameters of LOG() function.
+
+
sync() - Method in class org.h2.engine.Database
+
+
Synchronize the files with the file system.
+
+
SYNONYM - Static variable in class org.h2.engine.DbObject
+
+
This object is a synonym.
+
+
SYNTAX_ERROR_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 42000 is thrown when + trying to execute an invalid SQL statement.
+
+
SYNTAX_ERROR_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 42001 is thrown when + trying to execute an invalid SQL statement.
+
+
sysDummy1 - Variable in class org.h2.engine.Mode
+
+
Support the pseudo-table SYSIBM.SYSDUMMY1.
+
+
sysIdentifier(String) - Method in class org.h2.engine.Database
+
+
Returns identifier in upper or lower case depending on database settings.
+
+
SysProperties - Class in org.h2.engine
+
+
The constants defined in this class are initialized from system properties.
+
+
systemColumns - Variable in class org.h2.engine.Mode
+
+
The system columns 'ctid' and 'oid' are supported.
+
+
+ + + +

T

+
+
table - Variable in class org.h2.fulltext.IndexInfo
+
+
The table name.
+
+
TABLE_OR_VIEW - Static variable in class org.h2.engine.DbObject
+
+
The object is of the type table or view.
+
+
TABLE_OR_VIEW_ALREADY_EXISTS_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 42101 is thrown when + trying to create a table or view if an object with this name already + exists.
+
+
TABLE_OR_VIEW_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 42102 is thrown when + trying to query, modify or drop a table or view that does not exists + in this schema and database.
+
+
TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 42104 is thrown when + trying to query, modify or drop a table or view that does not exists + in this schema and database but it is empty anyway.
+
+
TABLE_OR_VIEW_NOT_FOUND_WITH_CANDIDATES_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 42103 is thrown when + trying to query, modify or drop a table or view that does not exists + in this schema and database but similar names were found.
+
+
TableEngine - Interface in org.h2.api
+
+
A class that implements this interface can create custom table + implementations.
+
+
tableName - Variable in class org.h2.tools.TriggerAdapter
+
+
The name of the table.
+
+
takeGeneratedSequenceValue - Variable in class org.h2.engine.Mode
+
+
If true, last identity of the session is updated on generation of + a new sequence value.
+
+
takeInsertedIdentity - Variable in class org.h2.engine.Mode
+
+
If true, last identity of the session is updated on insertion of + a new value into identity column.
+
+
TCP_PROTOCOL_VERSION_17 - Static variable in class org.h2.engine.Constants
+
+
The TCP protocol version number 17.
+
+
TCP_PROTOCOL_VERSION_18 - Static variable in class org.h2.engine.Constants
+
+
The TCP protocol version number 18.
+
+
TCP_PROTOCOL_VERSION_19 - Static variable in class org.h2.engine.Constants
+
+
The TCP protocol version number 19.
+
+
TCP_PROTOCOL_VERSION_20 - Static variable in class org.h2.engine.Constants
+
+
The TCP protocol version number 20.
+
+
TCP_PROTOCOL_VERSION_MAX_SUPPORTED - Static variable in class org.h2.engine.Constants
+
+
Maximum supported version of TCP protocol.
+
+
TCP_PROTOCOL_VERSION_MIN_SUPPORTED - Static variable in class org.h2.engine.Constants
+
+
Minimum supported version of TCP protocol.
+
+
THREAD_DEADLOCK_DETECTOR - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.threadDeadlockDetector + (default: false).
+
+
throttle() - Method in class org.h2.engine.SessionLocal
+
+
Wait for some time if this session is throttled (slowed down).
+
+
THROTTLE_DELAY - Static variable in class org.h2.engine.Constants
+
+
How often we check to see if we need to apply a throttling delay if SET + THROTTLE has been used.
+
+
throwException(String) - Static method in class org.h2.fulltext.FullText
+
+
Throw a SQLException with the given message.
+
+
TIME - Static variable in class org.h2.api.H2Type
+
+
The TIME data type.
+
+
TIME_WITH_TIME_ZONE - Static variable in class org.h2.api.H2Type
+
+
The TIME WITH TIME ZONE data type.
+
+
TIMESTAMP - Static variable in class org.h2.api.H2Type
+
+
The TIMESTAMP data type.
+
+
TIMESTAMP_WITH_TIME_ZONE - Static variable in class org.h2.api.H2Type
+
+
The TIMESTAMP WITH TIME ZONE data type.
+
+
timeZone - Variable in class org.h2.engine.Session.DynamicSettings
+
+
The current time zone.
+
+
TINYINT - Static variable in class org.h2.api.H2Type
+
+
The TINYINT data type.
+
+
TOO_MANY_COLUMNS_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 54011 is thrown when + too many columns were specified in a table, select statement, + or row value.
+
+
topInDML - Variable in class org.h2.engine.Mode
+
+
Whether TOP clause in DML commands is supported.
+
+
topInSelect - Variable in class org.h2.engine.Mode
+
+
Whether TOP clause in SELECT queries is supported.
+
+
toString() - Method in class org.h2.api.H2Type
+
 
+
toString() - Method in class org.h2.api.Interval
+
 
+
toString() - Method in enum org.h2.api.IntervalQualifier
+
 
+
toString() - Method in class org.h2.engine.Database
+
 
+
toString() - Method in class org.h2.engine.DbObject
+
 
+
toString() - Method in class org.h2.engine.MetaRecord
+
 
+
toString() - Method in class org.h2.engine.Mode
+
 
+
toString() - Method in class org.h2.engine.SessionLocal
+
 
+
toString() - Method in class org.h2.jdbc.JdbcArray
+
+
INTERNAL
+
+
toString() - Method in class org.h2.jdbc.JdbcConnection
+
+
INTERNAL
+
+
toString() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
INTERNAL
+
+
toString() - Method in interface org.h2.jdbc.JdbcException
+
+
Returns the class name, the message, and in the server mode, the stack + trace of the server
+
+
toString() - Method in class org.h2.jdbc.JdbcLob
+
+
INTERNAL
+
+
toString() - Method in class org.h2.jdbc.JdbcParameterMetaData
+
+
INTERNAL
+
+
toString() - Method in class org.h2.jdbc.JdbcPreparedStatement
+
+
INTERNAL
+
+
toString() - Method in class org.h2.jdbc.JdbcResultSet
+
+
INTERNAL
+
+
toString() - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
INTERNAL
+
+
toString() - Method in class org.h2.jdbc.JdbcSavepoint
+
+
INTERNAL
+
+
toString() - Method in exception org.h2.jdbc.JdbcSQLDataException
+
 
+
toString() - Method in exception org.h2.jdbc.JdbcSQLException
+
 
+
toString() - Method in exception org.h2.jdbc.JdbcSQLFeatureNotSupportedException
+
 
+
toString() - Method in exception org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException
+
 
+
toString() - Method in exception org.h2.jdbc.JdbcSQLInvalidAuthorizationSpecException
+
 
+
toString() - Method in exception org.h2.jdbc.JdbcSQLNonTransientConnectionException
+
 
+
toString() - Method in exception org.h2.jdbc.JdbcSQLNonTransientException
+
 
+
toString() - Method in exception org.h2.jdbc.JdbcSQLSyntaxErrorException
+
 
+
toString() - Method in exception org.h2.jdbc.JdbcSQLTimeoutException
+
 
+
toString() - Method in exception org.h2.jdbc.JdbcSQLTransactionRollbackException
+
 
+
toString() - Method in exception org.h2.jdbc.JdbcSQLTransientException
+
 
+
toString() - Method in class org.h2.jdbc.JdbcStatement
+
+
INTERNAL
+
+
toString() - Method in class org.h2.jdbcx.JdbcDataSource
+
+
INTERNAL
+
+
toString() - Method in class org.h2.jdbcx.JdbcXAConnection
+
+
INTERNAL
+
+
trace - Variable in class org.h2.engine.DbObject
+
+
The trace module.
+
+
TRACE_CONNECTION_NOT_CLOSED - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90018 is thrown when + the connection was opened, but never closed.
+
+
TRACE_FILE_ERROR_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90034 is thrown when + writing to the trace file failed, for example because the there + is an I/O exception.
+
+
TRACE_IO - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.traceIO (default: false).
+
+
traceOperation(String, int) - Method in class org.h2.engine.SessionRemote
+
+
Write the operation to the trace system if debug trace is enabled.
+
+
TRANSACTION_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90129 is thrown when + trying to commit a transaction that doesn't exist.
+
+
TRANSACTION_SNAPSHOT - Static variable in class org.h2.engine.Constants
+
+
SNAPSHOT isolation level of transaction.
+
+
treatEmptyStringsAsNull - Variable in class org.h2.engine.Mode
+
+
Empty strings are treated like NULL values.
+
+
Trigger - Interface in org.h2.api
+
+
A class that implements this interface can be used as a trigger.
+
+
TRIGGER - Static variable in class org.h2.engine.DbObject
+
+
This object is a trigger.
+
+
TRIGGER_ALREADY_EXISTS_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90041 is thrown when + trying to create a trigger and there is already a trigger with that name.
+
+
TRIGGER_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90042 is thrown when + trying to drop a trigger that does not exist.
+
+
TriggerAdapter - Class in org.h2.tools
+
+
An adapter for the trigger interface that allows to use the ResultSet + interface instead of a row array.
+
+
TriggerAdapter() - Constructor for class org.h2.tools.TriggerAdapter
+
 
+
triggerName - Variable in class org.h2.tools.TriggerAdapter
+
+
The name of the trigger.
+
+
truncate(long) - Method in class org.h2.jdbc.JdbcBlob
+
+
[Not supported] Truncates the object.
+
+
truncate(long) - Method in class org.h2.jdbc.JdbcClob
+
+
[Not supported] Truncates the object.
+
+
truncateTableRestartIdentity - Variable in class org.h2.engine.Mode
+
+
If true TRUNCATE TABLE uses RESTART IDENTITY by default.
+
+
type - Variable in class org.h2.tools.TriggerAdapter
+
+
The trigger type: INSERT, UPDATE, DELETE, SELECT, or a combination (a bit + field).
+
+
typeByNameMap - Variable in class org.h2.engine.Mode
+
+
Custom mappings from type names to data types.
+
+
TYPES_ARE_NOT_COMPARABLE_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90110 is thrown when + trying to compare or combine values of incomparable data types.
+
+
+ + + +

U

+
+
UNCOMPARABLE_REFERENCED_COLUMN_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90153 is thrown when trying to reference + a column of another data type when data types aren't comparable or don't + have a session-independent compare order between each other.
+
+
uniqueIndexNullsHandling - Variable in class org.h2.engine.Mode
+
+
Determines how rows with NULL values in indexed columns are handled + in unique indexes.
+
+
UNKNOWN_DATA_TYPE_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 50004 is thrown when + creating a table with an unsupported data type, or + when the data type is unknown because parameters are used.
+
+
UNKNOWN_MODE_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90088 is thrown when + trying to switch to an unknown mode.
+
+
unloadH2(Driver) - Static method in class org.h2.tools.Upgrade
+
+
Unloads the specified driver of H2.
+
+
unlockMeta(SessionLocal) - Method in class org.h2.engine.Database
+
+
Unlock the metadata table.
+
+
unsetExclusiveSession(SessionLocal) - Method in class org.h2.engine.Database
+
+
Stop exclusive access the database by provided session.
+
+
UNSUPPORTED_CIPHER - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90055 is thrown when + trying to open a database with an unsupported cipher algorithm.
+
+
UNSUPPORTED_COMPRESSION_ALGORITHM_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90103 is thrown when + trying to use an unsupported compression algorithm.
+
+
UNSUPPORTED_COMPRESSION_OPTIONS_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90102 is thrown when + trying to use unsupported options for the given compression algorithm.
+
+
UNSUPPORTED_LOCK_METHOD_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90060 is thrown when + trying to use a file locking mechanism that is not supported.
+
+
UNSUPPORTED_SETTING_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90113 is thrown when + the database URL contains unsupported settings.
+
+
UNSUPPORTED_SETTING_COMBINATION - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90021 is thrown when + trying to change a specific database property that conflicts with other + database properties.
+
+
unwrap(Class<T>) - Method in class org.h2.jdbc.JdbcConnection
+
+
Return an object of this class if possible.
+
+
unwrap(Class<T>) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Return an object of this class if possible.
+
+
unwrap(Class<T>) - Method in class org.h2.jdbc.JdbcParameterMetaData
+
+
Return an object of this class if possible.
+
+
unwrap(Class<T>) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Return an object of this class if possible.
+
+
unwrap(Class<T>) - Method in class org.h2.jdbc.JdbcResultSetMetaData
+
+
Return an object of this class if possible.
+
+
unwrap(Class<T>) - Method in class org.h2.jdbc.JdbcStatement
+
+
Return an object of this class if possible.
+
+
unwrap(Class<T>) - Method in class org.h2.jdbcx.JdbcConnectionPool
+
+
Return an object of this class if possible.
+
+
unwrap(Class<T>) - Method in class org.h2.jdbcx.JdbcDataSource
+
+
Return an object of this class if possible.
+
+
unwrap(Class<T>) - Method in class org.h2.tools.SimpleResultSet
+
+
Return an object of this class if possible.
+
+
UPDATE - Static variable in interface org.h2.api.Trigger
+
+
The trigger is called for UPDATE statements.
+
+
update(String, long, long) - Method in class org.h2.engine.QueryStatisticsData
+
+
Update query statistics.
+
+
UPDATE - Static variable in class org.h2.engine.Right
+
+
The right bit mask that means: updating data is allowed.
+
+
updateArray(int, Array) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateArray(String, Array) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateArray(int, Array) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateArray(String, Array) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateAsciiStream(int, InputStream, int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateAsciiStream(int, InputStream) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateAsciiStream(int, InputStream, long) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateAsciiStream(String, InputStream, int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateAsciiStream(String, InputStream) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateAsciiStream(String, InputStream, long) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateAsciiStream(int, InputStream) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateAsciiStream(String, InputStream) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateAsciiStream(int, InputStream, int) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateAsciiStream(String, InputStream, int) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateAsciiStream(int, InputStream, long) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateAsciiStream(String, InputStream, long) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateBigDecimal(int, BigDecimal) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateBigDecimal(String, BigDecimal) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateBigDecimal(int, BigDecimal) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateBigDecimal(String, BigDecimal) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateBinaryStream(int, InputStream, int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateBinaryStream(int, InputStream) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateBinaryStream(int, InputStream, long) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateBinaryStream(String, InputStream) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateBinaryStream(String, InputStream, int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateBinaryStream(String, InputStream, long) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateBinaryStream(int, InputStream) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateBinaryStream(String, InputStream) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateBinaryStream(int, InputStream, int) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateBinaryStream(String, InputStream, int) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateBinaryStream(int, InputStream, long) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateBinaryStream(String, InputStream, long) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateBlob(int, InputStream) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateBlob(int, InputStream, long) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateBlob(int, Blob) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateBlob(String, Blob) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateBlob(String, InputStream) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateBlob(String, InputStream, long) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateBlob(int, Blob) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateBlob(String, Blob) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateBlob(int, InputStream) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateBlob(String, InputStream) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateBlob(int, InputStream, long) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateBlob(String, InputStream, long) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateBoolean(int, boolean) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateBoolean(String, boolean) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateBoolean(int, boolean) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateBoolean(String, boolean) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateByte(int, byte) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateByte(String, byte) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateByte(int, byte) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateByte(String, byte) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateBytes(int, byte[]) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateBytes(String, byte[]) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateBytes(int, byte[]) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateBytes(String, byte[]) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateCharacterStream(int, Reader, long) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateCharacterStream(int, Reader, int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateCharacterStream(int, Reader) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateCharacterStream(String, Reader, int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateCharacterStream(String, Reader) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateCharacterStream(String, Reader, long) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateCharacterStream(int, Reader) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateCharacterStream(String, Reader) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateCharacterStream(int, Reader, int) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateCharacterStream(String, Reader, int) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateCharacterStream(int, Reader, long) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateCharacterStream(String, Reader, long) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateClob(int, Clob) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateClob(int, Reader) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateClob(int, Reader, long) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateClob(String, Clob) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateClob(String, Reader) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateClob(String, Reader, long) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateClob(int, Clob) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateClob(String, Clob) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateClob(int, Reader) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateClob(String, Reader) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateClob(int, Reader, long) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateClob(String, Reader, long) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateCount - Variable in class org.h2.jdbc.JdbcStatement
+
 
+
updateDate(int, Date) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateDate(String, Date) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateDate(int, Date) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateDate(String, Date) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateDouble(int, double) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateDouble(String, double) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateDouble(int, double) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateDouble(String, double) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateFloat(int, float) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateFloat(String, float) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateFloat(int, float) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateFloat(String, float) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateInt(int, int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateInt(String, int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateInt(int, int) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateInt(String, int) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateLong(int, long) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateLong(String, long) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateLong(int, long) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateLong(String, long) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateMeta(SessionLocal, DbObject) - Method in class org.h2.engine.Database
+
+
Update an object in the system table.
+
+
updateNCharacterStream(int, Reader) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateNCharacterStream(int, Reader, long) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateNCharacterStream(String, Reader) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateNCharacterStream(String, Reader, long) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateNCharacterStream(int, Reader) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateNCharacterStream(String, Reader) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateNCharacterStream(int, Reader, long) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateNCharacterStream(String, Reader, long) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateNClob(int, NClob) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateNClob(int, Reader) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateNClob(int, Reader, long) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateNClob(String, Reader) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateNClob(String, Reader, long) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateNClob(String, NClob) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateNClob(int, NClob) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateNClob(String, NClob) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateNClob(int, Reader) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateNClob(String, Reader) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateNClob(int, Reader, long) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateNClob(String, Reader, long) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateNString(int, String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateNString(String, String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateNString(int, String) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateNString(String, String) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateNull(int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateNull(String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateNull(int) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateNull(String) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateObject(int, Object, int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateObject(String, Object, int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateObject(int, Object) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateObject(String, Object) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateObject(int, Object, SQLType) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateObject(int, Object, SQLType, int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateObject(String, Object, SQLType) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateObject(String, Object, SQLType, int) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateObject(int, Object) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateObject(String, Object) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateObject(int, Object, int) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateObject(String, Object, int) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateRef(int, Ref) - Method in class org.h2.jdbc.JdbcResultSet
+
+
[Not supported]
+
+
updateRef(String, Ref) - Method in class org.h2.jdbc.JdbcResultSet
+
+
[Not supported]
+
+
updateRef(int, Ref) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateRef(String, Ref) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateRow() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates the current row.
+
+
updateRow() - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateRowId(int, RowId) - Method in class org.h2.jdbc.JdbcResultSet
+
+
[Not supported] Updates a column in the current or insert row.
+
+
updateRowId(String, RowId) - Method in class org.h2.jdbc.JdbcResultSet
+
+
[Not supported] Updates a column in the current or insert row.
+
+
updateRowId(int, RowId) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateRowId(String, RowId) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updatesAreDetected(int) - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Returns whether updates are detected.
+
+
updateSequenceOnManualIdentityInsertion - Variable in class org.h2.engine.Mode
+
+
If true, sequences of generated by default identity columns are + updated when value is provided by user.
+
+
updateShort(int, short) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateShort(String, short) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateShort(int, short) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateShort(String, short) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateSQLXML(int, SQLXML) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateSQLXML(String, SQLXML) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateSQLXML(int, SQLXML) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateSQLXML(String, SQLXML) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateString(int, String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateString(String, String) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateString(int, String) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateString(String, String) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateTime(int, Time) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateTime(String, Time) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateTime(int, Time) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateTime(String, Time) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateTimestamp(int, Timestamp) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateTimestamp(String, Timestamp) - Method in class org.h2.jdbc.JdbcResultSet
+
+
Updates a column in the current or insert row.
+
+
updateTimestamp(int, Timestamp) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
updateTimestamp(String, Timestamp) - Method in class org.h2.tools.SimpleResultSet
+
+
INTERNAL
+
+
Upgrade - Class in org.h2.tools
+
+
Upgrade utility.
+
+
upgrade(String, Properties, int) - Static method in class org.h2.tools.Upgrade
+
+
Performs database upgrade from an older version of H2.
+
+
URL_FORMAT - Static variable in class org.h2.engine.Constants
+
+
The database URL format in simplified Backus-Naur form.
+
+
URL_FORMAT_ERROR_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90046 is thrown when + trying to open a connection to a database using an unsupported URL + format.
+
+
URL_MAP - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.urlMap (default: null).
+
+
URL_RELATIVE_TO_CWD - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90011 is thrown when + trying to open a connection to a database using an implicit relative + path, such as "jdbc:h2:test" (in which case the database file would be + stored in the current working directory of the application).
+
+
USE_THREAD_CONTEXT_CLASS_LOADER - Static variable in class org.h2.engine.SysProperties
+
+
System property h2.useThreadContextClassLoader + (default: false).
+
+
USER - Static variable in class org.h2.engine.DbObject
+
+
This object is a user.
+
+
User - Class in org.h2.engine
+
+
Represents a user object.
+
+
User(Database, int, String, boolean) - Constructor for class org.h2.engine.User
+
 
+
USER_ALREADY_EXISTS_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90033 is thrown when + trying to create a user or role if a user with this name already exists.
+
+
USER_DATA_TYPE_ALREADY_EXISTS_1 - Static variable in class org.h2.api.ErrorCode
+
+
Deprecated.
+
+
USER_DATA_TYPE_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
Deprecated.
+
+
USER_HOME - Static variable in class org.h2.engine.SysProperties
+
+
System property user.home (empty string if not set).
+
+
USER_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90032 is thrown when + trying to drop or alter a user that does not exist.
+
+
USER_OR_ROLE_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90071 is thrown when + trying to grant or revoke if no role or user with that name exists.
+
+
USER_PACKAGE - Static variable in class org.h2.engine.Constants
+
+
The package name of user defined classes.
+
+
UserBuilder - Class in org.h2.engine
+
 
+
UserBuilder() - Constructor for class org.h2.engine.UserBuilder
+
 
+
UserToRolesMapper - Interface in org.h2.api
+
+
A class that implement this interface can be used during authentication to + map external users to database roles.
+
+
usesLocalFilePerTable() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Checks if this database use one file per table.
+
+
usesLocalFiles() - Method in class org.h2.jdbc.JdbcDatabaseMetaData
+
+
Checks if this database store data in local files.
+
+
UUID - Static variable in class org.h2.api.H2Type
+
+
The UUID data type.
+
+
+ + + +

V

+
+
validateCredentials(AuthenticationInfo) - Method in interface org.h2.api.CredentialsValidator
+
+
Validate user credential.
+
+
VALUE_TOO_LONG_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 22001 is thrown when + trying to insert a value that is too long for the column.
+
+
valueOf(String) - Static method in enum org.h2.api.IntervalQualifier
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(int) - Static method in enum org.h2.api.IntervalQualifier
+
+
Returns the interval qualifier with the specified ordinal value.
+
+
valueOf(Object) - Static method in class org.h2.engine.GeneratedKeysMode
+
+
Determines mode of generated keys' gathering.
+
+
valueOf(String) - Static method in enum org.h2.engine.IsolationLevel
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum org.h2.engine.Mode.CharPadding
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum org.h2.engine.Mode.ExpressionNames
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum org.h2.engine.Mode.ModeEnum
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum org.h2.engine.Mode.UniqueIndexNullsHandling
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum org.h2.engine.Mode.ViewExpressionNames
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum org.h2.engine.SessionLocal.State
+
+
Returns the enum constant of this type with the specified name.
+
+
valueOf(String) - Static method in enum org.h2.jdbc.JdbcLob.State
+
+
Returns the enum constant of this type with the specified name.
+
+
values() - Static method in enum org.h2.api.IntervalQualifier
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum org.h2.engine.IsolationLevel
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum org.h2.engine.Mode.CharPadding
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum org.h2.engine.Mode.ExpressionNames
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum org.h2.engine.Mode.ModeEnum
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum org.h2.engine.Mode.UniqueIndexNullsHandling
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum org.h2.engine.Mode.ViewExpressionNames
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum org.h2.engine.SessionLocal.State
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
values() - Static method in enum org.h2.jdbc.JdbcLob.State
+
+
Returns an array containing the constants of this enum type, in +the order they are declared.
+
+
VARBINARY - Static variable in class org.h2.api.H2Type
+
+
The BINARY VARYING data type.
+
+
VARCHAR - Static variable in class org.h2.api.H2Type
+
+
The CHARACTER VARYING data type.
+
+
VARCHAR_IGNORECASE - Static variable in class org.h2.api.H2Type
+
+
The VARCHAR_IGNORECASE data type.
+
+
verifyMetaLocked(SessionLocal) - Method in class org.h2.engine.Database
+
+
Verify the meta table is locked.
+
+
VERSION - Static variable in class org.h2.engine.Constants
+
+
The version of this product, consisting of major version, minor + version, and build id.
+
+
VERSION_MAJOR - Static variable in class org.h2.engine.Constants
+
+
The major version of this database.
+
+
VERSION_MINOR - Static variable in class org.h2.engine.Constants
+
+
The minor version of this database.
+
+
VIEW_ALREADY_EXISTS_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90038 is thrown when + trying to create a view if a view with this name already + exists.
+
+
VIEW_COST_CACHE_MAX_AGE - Static variable in class org.h2.engine.Constants
+
+
The maximum time in milliseconds to keep the cost of a view.
+
+
VIEW_INDEX_CACHE_SIZE - Static variable in class org.h2.engine.Constants
+
+
The name of the index cache that is used for temporary view (subqueries + used as tables).
+
+
VIEW_IS_INVALID_2 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90109 is thrown when + trying to run a query against an invalid view.
+
+
VIEW_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90037 is thrown when + trying to drop or alter a view that does not exist.
+
+
viewExpressionNames - Variable in class org.h2.engine.Mode
+
+
How column names are generated for views.
+
+
+ + + +

W

+
+
waitIfExclusiveModeEnabled() - Method in class org.h2.engine.SessionLocal
+
+
Wait if the exclusive mode has been enabled for another session.
+
+
wasNull() - Method in class org.h2.jdbc.JdbcCallableStatement
+
+
Returns whether the last column accessed was null.
+
+
wasNull() - Method in class org.h2.jdbc.JdbcResultSet
+
+
Returns whether the last column accessed was null.
+
+
wasNull() - Method in class org.h2.tools.SimpleResultSet
+
+
Returns whether the last column accessed was null.
+
+
WINDOW_NOT_FOUND_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90136 is thrown when + trying to reference a window that does not exist.
+
+
windowActivated(WindowEvent) - Method in class org.h2.tools.GUIConsole
+
+
INTERNAL
+
+
windowClosed(WindowEvent) - Method in class org.h2.tools.GUIConsole
+
+
INTERNAL
+
+
windowClosing(WindowEvent) - Method in class org.h2.tools.GUIConsole
+
+
INTERNAL
+
+
windowDeactivated(WindowEvent) - Method in class org.h2.tools.GUIConsole
+
+
INTERNAL
+
+
windowDeiconified(WindowEvent) - Method in class org.h2.tools.GUIConsole
+
+
INTERNAL
+
+
windowIconified(WindowEvent) - Method in class org.h2.tools.GUIConsole
+
+
INTERNAL
+
+
windowOpened(WindowEvent) - Method in class org.h2.tools.GUIConsole
+
+
INTERNAL
+
+
WITH_TIES_WITHOUT_ORDER_BY - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90122 is thrown when + WITH TIES clause is used without ORDER BY clause.
+
+
wrapInputStream(InputStream, String, String) - Static method in class org.h2.tools.CompressTool
+
+
INTERNAL
+
+
wrapOutputStream(OutputStream, String, String) - Static method in class org.h2.tools.CompressTool
+
+
INTERNAL
+
+
write(Writer, ResultSet) - Method in class org.h2.tools.Csv
+
+
Writes the result set to a file in the CSV format.
+
+
write(String, ResultSet, String) - Method in class org.h2.tools.Csv
+
+
Writes the result set to a file in the CSV format.
+
+
write(Connection, String, String, String) - Method in class org.h2.tools.Csv
+
+
Writes the result set of a query to a file in the CSV format.
+
+
writeVariableInt(byte[], int, int) - Static method in class org.h2.tools.CompressTool
+
+
Write a variable size integer using Rice coding.
+
+
WRONG_PASSWORD_FORMAT - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90050 is thrown when trying to open an + encrypted database, but not separating the file password from the user + password.
+
+
WRONG_USER_OR_PASSWORD - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 28000 is thrown when + there is no such user registered in the database, when the user password + does not match, or when the database encryption password does not match + (if database encryption is used).
+
+
WRONG_XID_FORMAT_1 - Static variable in class org.h2.api.ErrorCode
+
+
The error with code 90101 is thrown when + the XA API detected unsupported transaction names.
+
+
+ + + +

Z

+
+
zeroBasedEnums() - Method in interface org.h2.engine.CastDataProvider
+
+
Returns are ENUM values 0-based.
+
+
zeroBasedEnums() - Method in class org.h2.engine.Database
+
 
+
zeroBasedEnums - Variable in class org.h2.engine.DbSettings
+
+
Database setting ZERO_BASED_ENUMS + (default: false).
+
+
zeroBasedEnums() - Method in class org.h2.engine.SessionLocal
+
 
+
zeroBasedEnums() - Method in class org.h2.engine.SessionRemote
+
 
+
zeroBasedEnums() - Method in class org.h2.jdbc.JdbcConnection
+
 
+
zeroExLiteralsAreBinaryStrings - Variable in class org.h2.engine.Mode
+
+
If true 0x-prefixed numbers are parsed as binary string + literals, if false they are parsed as hexadecimal numeric values.
+
+
+A B C D E F G H I J K L M N O P Q R S T U V W Z 
+ +
+ + + + + + + +
+ + + + diff --git a/h2/docs/javadoc/index.html b/h2/docs/javadoc/index.html new file mode 100644 index 0000000..5f61a11 --- /dev/null +++ b/h2/docs/javadoc/index.html @@ -0,0 +1,75 @@ + + + + + +Generated Documentation (Untitled) + + + + + + + + + +<noscript> +<div>JavaScript is disabled on your browser.</div> +</noscript> +<h2>Frame Alert</h2> +<p>This document is designed to be viewed using the frames feature. If you see this message, you are using a non-frame-capable web client. Link to <a href="overview-summary.html">Non-frame version</a>.</p> + + + diff --git a/h2/docs/javadoc/org/h2/api/Aggregate.html b/h2/docs/javadoc/org/h2/api/Aggregate.html new file mode 100644 index 0000000..09b23d7 --- /dev/null +++ b/h2/docs/javadoc/org/h2/api/Aggregate.html @@ -0,0 +1,311 @@ + + + + + +Aggregate + + + + + + + + + + + + +
+
org.h2.api
+

Interface Aggregate

+
+
+
+
    +
  • +
    +
    +
    public interface Aggregate
    +
    A user-defined aggregate function needs to implement this interface. + The class must be public and must have a public non-argument constructor.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Abstract Methods Default Methods 
      Modifier and TypeMethod and Description
      voidadd(java.lang.Object value) +
      This method is called once for each row.
      +
      intgetInternalType(int[] inputTypes) +
      This method must return the H2 data type, Value, + of the aggregate function, given the H2 data type of the input data.
      +
      java.lang.ObjectgetResult() +
      This method returns the computed aggregate value.
      +
      default voidinit(java.sql.Connection conn) +
      This method is called when the aggregate function is used.
      +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        init

        +
        default void init(java.sql.Connection conn)
        +           throws java.sql.SQLException
        +
        This method is called when the aggregate function is used. + A new object is created for each invocation.
        +
        +
        Parameters:
        +
        conn - a connection to the database
        +
        Throws:
        +
        java.sql.SQLException - on SQL exception
        +
        +
      • +
      + + + +
        +
      • +

        getInternalType

        +
        int getInternalType(int[] inputTypes)
        +             throws java.sql.SQLException
        +
        This method must return the H2 data type, Value, + of the aggregate function, given the H2 data type of the input data. + The method should check here if the number of parameters + passed is correct, and if not it should throw an exception.
        +
        +
        Parameters:
        +
        inputTypes - the H2 data type of the parameters,
        +
        Returns:
        +
        the H2 data type of the result
        +
        Throws:
        +
        java.sql.SQLException - if the number/type of parameters passed is incorrect
        +
        +
      • +
      + + + +
        +
      • +

        add

        +
        void add(java.lang.Object value)
        +  throws java.sql.SQLException
        +
        This method is called once for each row. + If the aggregate function is called with multiple parameters, + those are passed as array.
        +
        +
        Parameters:
        +
        value - the value(s) for this row
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        getResult

        +
        java.lang.Object getResult()
        +                    throws java.sql.SQLException
        +
        This method returns the computed aggregate value. This method must + preserve previously added values and must be able to reevaluate result if + more values were added since its previous invocation.
        +
        +
        Returns:
        +
        the aggregated value
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/api/AggregateFunction.html b/h2/docs/javadoc/org/h2/api/AggregateFunction.html new file mode 100644 index 0000000..8b9c52f --- /dev/null +++ b/h2/docs/javadoc/org/h2/api/AggregateFunction.html @@ -0,0 +1,315 @@ + + + + + +AggregateFunction + + + + + + + + + + + + +
+
org.h2.api
+

Interface AggregateFunction

+
+
+
+
    +
  • +
    +
    +
    public interface AggregateFunction
    +
    A user-defined aggregate function needs to implement this interface. + The class must be public and must have a public non-argument constructor. +

    + Please note this interface only has limited support for data types. + If you need data types that don't have a corresponding SQL type + (for example GEOMETRY), then use the Aggregate interface. +

    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Abstract Methods Default Methods 
      Modifier and TypeMethod and Description
      voidadd(java.lang.Object value) +
      This method is called once for each row.
      +
      java.lang.ObjectgetResult() +
      This method returns the computed aggregate value.
      +
      intgetType(int[] inputTypes) +
      This method must return the SQL type of the method, given the SQL type of + the input data.
      +
      default voidinit(java.sql.Connection conn) +
      This method is called when the aggregate function is used.
      +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        init

        +
        default void init(java.sql.Connection conn)
        +           throws java.sql.SQLException
        +
        This method is called when the aggregate function is used. + A new object is created for each invocation.
        +
        +
        Parameters:
        +
        conn - a connection to the database
        +
        Throws:
        +
        java.sql.SQLException - on SQL exception
        +
        +
      • +
      + + + +
        +
      • +

        getType

        +
        int getType(int[] inputTypes)
        +     throws java.sql.SQLException
        +
        This method must return the SQL type of the method, given the SQL type of + the input data. The method should check here if the number of parameters + passed is correct, and if not it should throw an exception.
        +
        +
        Parameters:
        +
        inputTypes - the SQL type of the parameters, Types
        +
        Returns:
        +
        the SQL type of the result
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        add

        +
        void add(java.lang.Object value)
        +  throws java.sql.SQLException
        +
        This method is called once for each row. + If the aggregate function is called with multiple parameters, + those are passed as array.
        +
        +
        Parameters:
        +
        value - the value(s) for this row
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        getResult

        +
        java.lang.Object getResult()
        +                    throws java.sql.SQLException
        +
        This method returns the computed aggregate value. This method must + preserve previously added values and must be able to reevaluate result if + more values were added since its previous invocation.
        +
        +
        Returns:
        +
        the aggregated value
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/api/CredentialsValidator.html b/h2/docs/javadoc/org/h2/api/CredentialsValidator.html new file mode 100644 index 0000000..2b20f1e --- /dev/null +++ b/h2/docs/javadoc/org/h2/api/CredentialsValidator.html @@ -0,0 +1,249 @@ + + + + + +CredentialsValidator + + + + + + + + + + + + +
+
org.h2.api
+

Interface CredentialsValidator

+
+
+
+
    +
  • +
    +
    All Superinterfaces:
    +
    org.h2.security.auth.Configurable
    +
    +
    +
    +
    public interface CredentialsValidator
    +extends org.h2.security.auth.Configurable
    +
    A class that implement this interface can be used to validate credentials + provided by client. +

    + This feature is experimental and subject to change +

    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + +
      All Methods Instance Methods Abstract Methods 
      Modifier and TypeMethod and Description
      booleanvalidateCredentials(org.h2.security.auth.AuthenticationInfo authenticationInfo) +
      Validate user credential.
      +
      +
        +
      • + + +

        Methods inherited from interface org.h2.security.auth.Configurable

        +configure
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        validateCredentials

        +
        boolean validateCredentials(org.h2.security.auth.AuthenticationInfo authenticationInfo)
        +                     throws java.lang.Exception
        +
        Validate user credential.
        +
        +
        Parameters:
        +
        authenticationInfo - = authentication info
        +
        Returns:
        +
        true if credentials are valid, otherwise false
        +
        Throws:
        +
        java.lang.Exception - any exception occurred (invalid credentials or internal + issue) prevent user login
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/api/DatabaseEventListener.html b/h2/docs/javadoc/org/h2/api/DatabaseEventListener.html new file mode 100644 index 0000000..5610356 --- /dev/null +++ b/h2/docs/javadoc/org/h2/api/DatabaseEventListener.html @@ -0,0 +1,518 @@ + + + + + +DatabaseEventListener + + + + + + + + + + + + +
+
org.h2.api
+

Interface DatabaseEventListener

+
+
+
+
    +
  • +
    +
    All Superinterfaces:
    +
    java.util.EventListener
    +
    +
    +
    +
    public interface DatabaseEventListener
    +extends java.util.EventListener
    +
    A class that implements this interface can get notified about exceptions + and other events. A database event listener can be registered when + connecting to a database. Example database URL: + jdbc:h2:./test;DATABASE_EVENT_LISTENER='com.acme.DbListener'
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Fields 
      Modifier and TypeField and Description
      static intSTATE_BACKUP_FILE +
      This state is used during the BACKUP command.
      +
      static intSTATE_CREATE_INDEX +
      This state is used when re-creating an index.
      +
      static intSTATE_RECONNECTED +
      This state is used after re-connecting to a database (if auto-reconnect + is enabled).
      +
      static intSTATE_RECOVER +
      This state is used when re-applying the transaction log or rolling back + uncommitted transactions.
      +
      static intSTATE_SCAN_FILE +
      This state is used when scanning the database file.
      +
      static intSTATE_STATEMENT_END +
      This state is used when a query ends.
      +
      static intSTATE_STATEMENT_PROGRESS +
      This state is used for periodic notification during long-running queries.
      +
      static intSTATE_STATEMENT_START +
      This state is used when a query starts.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Default Methods 
      Modifier and TypeMethod and Description
      default voidclosingDatabase() +
      This method is called before the database is closed normally.
      +
      default voidexceptionThrown(java.sql.SQLException e, + java.lang.String sql) +
      This method is called if an exception occurred.
      +
      default voidinit(java.lang.String url) +
      This method is called just after creating the object.
      +
      default voidopened() +
      This method is called after the database has been opened.
      +
      default voidsetProgress(int state, + java.lang.String name, + long x, + long max) +
      This method is called for long running events, such as recovering, + scanning a file or building an index.
      +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Detail

      + + + +
        +
      • +

        STATE_SCAN_FILE

        +
        static final int STATE_SCAN_FILE
        +
        This state is used when scanning the database file.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        STATE_CREATE_INDEX

        +
        static final int STATE_CREATE_INDEX
        +
        This state is used when re-creating an index.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        STATE_RECOVER

        +
        static final int STATE_RECOVER
        +
        This state is used when re-applying the transaction log or rolling back + uncommitted transactions.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        STATE_BACKUP_FILE

        +
        static final int STATE_BACKUP_FILE
        +
        This state is used during the BACKUP command.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        STATE_RECONNECTED

        +
        static final int STATE_RECONNECTED
        +
        This state is used after re-connecting to a database (if auto-reconnect + is enabled).
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        STATE_STATEMENT_START

        +
        static final int STATE_STATEMENT_START
        +
        This state is used when a query starts.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        STATE_STATEMENT_END

        +
        static final int STATE_STATEMENT_END
        +
        This state is used when a query ends.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        STATE_STATEMENT_PROGRESS

        +
        static final int STATE_STATEMENT_PROGRESS
        +
        This state is used for periodic notification during long-running queries.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        init

        +
        default void init(java.lang.String url)
        +
        This method is called just after creating the object. + This is done when opening the database if the listener is specified + in the database URL, but may be later if the listener is set at + runtime with the SET SQL statement.
        +
        +
        Parameters:
        +
        url - - the database URL
        +
        +
      • +
      + + + +
        +
      • +

        opened

        +
        default void opened()
        +
        This method is called after the database has been opened. It is safe to + connect to the database and execute statements at this point.
        +
      • +
      + + + +
        +
      • +

        exceptionThrown

        +
        default void exceptionThrown(java.sql.SQLException e,
        +                             java.lang.String sql)
        +
        This method is called if an exception occurred.
        +
        +
        Parameters:
        +
        e - the exception
        +
        sql - the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        setProgress

        +
        default void setProgress(int state,
        +                         java.lang.String name,
        +                         long x,
        +                         long max)
        +
        This method is called for long running events, such as recovering, + scanning a file or building an index. +

        + More states might be added in future versions, therefore implementations + should silently ignore states that they don't understand. +

        +
        +
        Parameters:
        +
        state - the state
        +
        name - the object name
        +
        x - the current position
        +
        max - the highest possible value or 0 if unknown
        +
        +
      • +
      + + + +
        +
      • +

        closingDatabase

        +
        default void closingDatabase()
        +
        This method is called before the database is closed normally. It is safe + to connect to the database and execute statements at this point, however + the connection must be closed before the method returns.
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/api/ErrorCode.html b/h2/docs/javadoc/org/h2/api/ErrorCode.html new file mode 100644 index 0000000..32f48b1 --- /dev/null +++ b/h2/docs/javadoc/org/h2/api/ErrorCode.html @@ -0,0 +1,5792 @@ + + + + + +ErrorCode + + + + + + + + + + + + +
+
org.h2.api
+

Class ErrorCode

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.api.ErrorCode
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public class ErrorCode
    +extends java.lang.Object
    +
    This class defines the error codes used for SQL exceptions. + Error messages are formatted as follows: +
    + { error message (possibly translated; may include quoted data) }
    + { error message in English if different }
    + { SQL statement if applicable }
    + { [ error code - build number ] }
    + 
    + Example: +
    + Syntax error in SQL statement "SELECT * FORM[*] TEST ";
    + SQL statement: select * form test [42000-125]
    + 
    + The [*] marks the position of the syntax error + (FORM instead of FROM in this case). + The error code is 42000, and the build number is 125, + meaning version 1.2.125.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Fields 
      Modifier and TypeField and Description
      static intACCESS_DENIED_TO_CLASS_1 +
      The error with code 90134 is thrown when + trying to load a Java class that is not part of the allowed classes.
      +
      static intADMIN_RIGHTS_REQUIRED +
      The error with code 90040 is thrown when + a user that is not administrator tries to execute a statement + that requires admin privileges.
      +
      static intAGGREGATE_NOT_FOUND_1 +
      The error with code 90132 is thrown when + trying to drop a user-defined aggregate function that doesn't exist.
      +
      static intAMBIGUOUS_COLUMN_NAME_1 +
      The error with code 90059 is thrown when + a query contains a column that could belong to multiple tables.
      +
      static intARRAY_ELEMENT_ERROR_2 +
      The error with code 22034 is thrown when an + attempt is made to read non-existing element of an array.
      +
      static intAUTHENTICATOR_NOT_AVAILABLE +
      The error with code 90144 is thrown when + user trying to login into a database with AUTHREALM set and + the target database doesn't have an authenticator defined
      +
      static intCAN_ONLY_ASSIGN_TO_VARIABLE_1 +
      The error with code 90137 is thrown when + trying to assign a value to something that is not a variable.
      +
      static intCANNOT_CHANGE_SETTING_WHEN_OPEN_1 +
      The error with code 90133 is thrown when + trying to change a specific database property while the database is + already open.
      +
      static intCANNOT_DROP_2 +
      The error with code 90107 is thrown when + trying to drop an object because another object would become invalid.
      +
      static intCANNOT_DROP_CURRENT_USER +
      The error with code 90019 is thrown when + trying to drop the current user, if there are no other admin users.
      +
      static intCANNOT_DROP_LAST_COLUMN +
      The error with code 90084 is thrown when + trying to drop the last column of a table.
      +
      static intCANNOT_DROP_TABLE_1 +
      The error with code 90118 is thrown when + trying to drop a table can not be dropped.
      +
      static intCANNOT_MIX_INDEXED_AND_UNINDEXED_PARAMS +
      The error with code 90123 is thrown when + trying mix regular parameters and indexed parameters in the same + statement.
      +
      static intCANNOT_TRUNCATE_1 +
      The error with code 90106 is thrown when + trying to truncate a table that can not be truncated.
      +
      static intCHECK_CONSTRAINT_INVALID +
      The error with code 23514 is thrown when + evaluation of a check constraint resulted in an error.
      +
      static intCHECK_CONSTRAINT_VIOLATED_1 +
      The error with code 23513 is thrown when + a check constraint is violated.
      +
      static intCLASS_NOT_FOUND_1 +
      The error with code 90086 is thrown when + a class can not be loaded because it is not in the classpath + or because a related class is not in the classpath.
      +
      static intCLUSTER_ERROR_DATABASE_RUNS_ALONE +
      The error with code 90093 is thrown when + trying to connect to a clustered database that runs in standalone + mode.
      +
      static intCLUSTER_ERROR_DATABASE_RUNS_CLUSTERED_1 +
      The error with code 90094 is thrown when + trying to connect to a clustered database that runs together with a + different cluster node setting than what is used when trying to connect.
      +
      static intCOLLATION_CHANGE_WITH_DATA_TABLE_1 +
      The error with code 90089 is thrown when + trying to change the collation while there was already data in + the database.
      +
      static intCOLUMN_ALIAS_IS_NOT_SPECIFIED_1 +
      The error with code 90156 is thrown when trying to create a + view or a table from a select and some expression doesn't have a column + name or alias when it is required by a compatibility mode.
      +
      static intCOLUMN_CONTAINS_NULL_VALUES_1 +
      The error with code 90081 is thrown when + trying to alter a column to not allow NULL, if there + is already data in the table where this column is NULL.
      +
      static intCOLUMN_COUNT_DOES_NOT_MATCH +
      The error with code 21002 is thrown when the number of + columns does not match.
      +
      static intCOLUMN_IS_PART_OF_INDEX_1 +
      The error with code 90075 is thrown when + trying to alter a table and allow null for a column that is part of a + primary key or hash index.
      +
      static intCOLUMN_IS_REFERENCED_1 +
      The error with code 90083 is thrown when + trying to drop a column that is part of a constraint.
      +
      static intCOLUMN_MUST_NOT_BE_NULLABLE_1 +
      The error with code 90023 is thrown when trying to set a + primary key on a nullable column or when trying to drop NOT NULL + constraint on primary key or identity column.
      +
      static intCOLUMN_NOT_FOUND_1 +
      The error with code 42122 is thrown when + referencing an non-existing column.
      +
      static intCOMMIT_ROLLBACK_NOT_ALLOWED +
      The error with code 90058 is thrown when trying to call + commit or rollback inside a trigger, or when trying to call a method + inside a trigger that implicitly commits the current transaction, if an + object is locked.
      +
      static intCOMPRESSION_ERROR +
      The error with code 90104 is thrown when + the data can not be de-compressed.
      +
      static intCONCURRENT_UPDATE_1 +
      The error with code 90131 is thrown when using multi version + concurrency control, and trying to update the same row from within two + connections at the same time, or trying to insert two rows with the same + key from two connections.
      +
      static intCONNECTION_BROKEN_1 +
      The error with code 90067 is thrown when the client could + not connect to the database, or if the connection was lost.
      +
      static intCONSTANT_ALREADY_EXISTS_1 +
      The error with code 90114 is thrown when + trying to create a constant if a constant with this name already exists.
      +
      static intCONSTANT_NOT_FOUND_1 +
      The error with code 90115 is thrown when + trying to drop a constant that does not exists.
      +
      static intCONSTRAINT_ALREADY_EXISTS_1 +
      The error with code 90045 is thrown when trying to create a + constraint if an object with this name already exists.
      +
      static intCONSTRAINT_IS_USED_BY_CONSTRAINT_2 +
      The error with code 90152 is thrown when trying to manually + drop a unique or primary key constraint that is referenced by a foreign + key constraint without a CASCADE clause.
      +
      static intCONSTRAINT_NOT_FOUND_1 +
      The error with code 90057 is thrown when + trying to drop a constraint that does not exist.
      +
      static intCURRENT_SEQUENCE_VALUE_IS_NOT_DEFINED_IN_SESSION_1 +
      The error with code 90148 is thrown when trying to access + the current value of a sequence before execution of NEXT VALUE FOR + sequenceName in the current session.
      +
      static intDATA_CONVERSION_ERROR_1 +
      The error with code 22018 is thrown when + trying to convert a value to a data type where the conversion is + undefined, or when an error occurred trying to convert.
      +
      static intDATABASE_ALREADY_OPEN_1 +
      The error with code 90020 is thrown when trying to open a + database in embedded mode if this database is already in use in another + process (or in a different class loader).
      +
      static intDATABASE_CALLED_AT_SHUTDOWN +
      The error with code 90121 is thrown when + a database operation is started while the virtual machine exits + (for example in a shutdown hook), or when the session is closed.
      +
      static intDATABASE_IS_CLOSED +
      The error with code 90098 is thrown when the database has + been closed, for example because the system ran out of memory or because + the self-destruction counter has reached zero.
      +
      static intDATABASE_IS_IN_EXCLUSIVE_MODE +
      The error with code 90135 is thrown when + trying to open a connection to a database that is currently open + in exclusive mode.
      +
      static intDATABASE_IS_NOT_PERSISTENT +
      The error with code 90126 is thrown when + trying to call the BACKUP statement for an in-memory database.
      +
      static intDATABASE_IS_READ_ONLY +
      The error with code 90097 is thrown when + trying to delete or update a database if it is open in read-only mode.
      +
      static intDATABASE_NOT_FOUND_1 +
      The error with code 90013 is thrown when when trying to access + a database object with a catalog name that does not match the database + name.
      +
      static intDATABASE_NOT_FOUND_WITH_IF_EXISTS_1 +
      The error with code 90146 is thrown when trying to open a + database that does not exist using the flag IFEXISTS=TRUE
      +
      static intDEADLOCK_1 +
      The error with code 40001 is thrown when + the database engine has detected a deadlock.
      +
      static intDESERIALIZATION_FAILED_1 +
      The error with code 90027 is thrown when + an object could not be de-serialized.
      +
      static intDIVISION_BY_ZERO_1 +
      The error with code 22012 is thrown when trying to divide + a value by zero.
      +
      static intDOMAIN_ALREADY_EXISTS_1 +
      The error with code 90119 is thrown when + trying to create a domain if an object with this name already exists, + or when trying to overload a built-in data type.
      +
      static intDOMAIN_NOT_FOUND_1 +
      The error with code 90120 is thrown when + trying to drop a domain that doesn't exist.
      +
      static intDRIVER_VERSION_ERROR_2 +
      The error with code 90047 is thrown when + trying to connect to a TCP server with an incompatible client.
      +
      static intDUPLICATE_COLUMN_NAME_1 +
      The error with code 42121 is thrown when trying to create + a table or insert into a table and use the same column name twice.
      +
      static intDUPLICATE_KEY_1 +
      The error with code 23505 is thrown when trying to insert + a row that would violate a unique index or primary key.
      +
      static intDUPLICATE_PROPERTY_1 +
      The error with code 90066 is thrown when + the same property appears twice in the database URL or in + the connection properties.
      +
      static intENUM_DUPLICATE +
      The error with code 22033 is thrown when an + attempt is made to add or modify an ENUM-typed column so + that it would have duplicate values.
      +
      static intENUM_EMPTY +
      The error with code 22032 is thrown when an + attempt is made to add or modify an ENUM-typed column so + that one or more of its enumerators would be empty.
      +
      static intENUM_VALUE_NOT_PERMITTED +
      The error with code 22030 is thrown when + an attempt is made to INSERT or UPDATE an ENUM-typed cell, + but the value is not one of the values enumerated by the + type.
      +
      static intERROR_ACCESSING_LINKED_TABLE_2 +
      The error with code 90111 is thrown when + an exception occurred while accessing a linked table.
      +
      static intERROR_CREATING_TRIGGER_OBJECT_3 +
      The error with code 90043 is thrown when + there is an error initializing the trigger, for example because the + class does not implement the Trigger interface.
      +
      static intERROR_EXECUTING_TRIGGER_3 +
      The error with code 90044 is thrown when + an exception or error occurred while calling the triggers fire method.
      +
      static intERROR_OPENING_DATABASE_1 +
      The error with code 8000 is thrown when + there was a problem trying to create a database lock.
      +
      static intERROR_SETTING_DATABASE_EVENT_LISTENER_2 +
      The error with code 90099 is thrown when an error occurred + trying to initialize the database event listener.
      +
      static intEXCEPTION_IN_FUNCTION_1 +
      The error with code 90105 is thrown when + an exception occurred in a user-defined method.
      +
      static intEXCEPTION_OPENING_PORT_2 +
      The error with code 90061 is thrown when + trying to start a server if a server is already running at the same port.
      +
      static intFEATURE_NOT_SUPPORTED_1 +
      The error with code 50100 is thrown when calling an + unsupported JDBC method or database feature.
      +
      static intFILE_CORRUPTED_1 +
      The error with code 90030 is thrown when + the database engine has detected a checksum mismatch in the data + or index.
      +
      static intFILE_CREATION_FAILED_1 +
      The error with code 90062 is thrown when + a directory or file could not be created.
      +
      static intFILE_DELETE_FAILED_1 +
      The error with code 90025 is thrown when + a file could not be deleted, because it is still in use + (only in Windows), or because an error occurred when deleting.
      +
      static intFILE_ENCRYPTION_ERROR_1 +
      The error with code 90049 is thrown when + trying to open an encrypted database with the wrong file encryption + password or algorithm.
      +
      static intFILE_NOT_FOUND_1 +
      The error with code 90124 is thrown when + trying to access a file that doesn't exist.
      +
      static intFILE_RENAME_FAILED_2 +
      The error with code 90024 is thrown when + a file could not be renamed.
      +
      static intFILE_VERSION_ERROR_1 +
      The error with code 90048 is thrown when + the file header of a database files (*.db) does not match the + expected version, or if it is corrupted.
      +
      static intFOR_UPDATE_IS_NOT_ALLOWED_IN_DISTINCT_OR_GROUPED_SELECT +
      The error with code 90145 is thrown when trying to execute a + SELECT statement with non-window aggregates, DISTINCT, GROUP BY, or + HAVING clauses together with FOR UPDATE clause.
      +
      static intFUNCTION_ALIAS_ALREADY_EXISTS_1 +
      The error with code 90076 is thrown when + trying to create a function alias for a system function or for a function + that is already defined.
      +
      static intFUNCTION_ALIAS_NOT_FOUND_1 +
      The error with code 90077 is thrown when + trying to drop a system function or a function alias that does not exist.
      +
      static intFUNCTION_MUST_RETURN_RESULT_SET_1 +
      The error with code 90000 is thrown when + a function that does not return a result set was used in the FROM clause.
      +
      static intFUNCTION_NOT_FOUND_1 +
      The error with code 90022 is thrown when + trying to call a unknown function.
      +
      static intGENERAL_ERROR_1 +
      The error with code 50000 is thrown when + something unexpected occurs, for example an internal stack + overflow.
      +
      static intGENERATED_COLUMN_CANNOT_BE_ASSIGNED_1 +
      The error with code 90154 is thrown when trying to assign a + value to a generated column.
      +
      static intGENERATED_COLUMN_CANNOT_BE_UPDATABLE_BY_CONSTRAINT_2 +
      The error with code 90155 is thrown when trying to create a + referential constraint that can update a referenced generated column.
      +
      static intGROUP_BY_NOT_IN_THE_RESULT +
      The error with code 90157 is thrown when the integer + index that is used in the GROUP BY is not in the SELECT list
      +
      static intHEX_STRING_ODD_1 +
      The error with code 90003 is thrown when + trying to convert a String to a binary value.
      +
      static intHEX_STRING_WRONG_1 +
      The error with code 90004 is thrown when + trying to convert a text to binary, but the expression contains + a non-hexadecimal character.
      +
      static intIDENTICAL_EXPRESSIONS_SHOULD_BE_USED +
      The error with code 42131 is thrown when + identical expressions should be used, but different + expressions were found.
      +
      static intINDEX_ALREADY_EXISTS_1 +
      The error with code 42111 is thrown when + trying to create an index if an index with the same name already exists.
      +
      static intINDEX_BELONGS_TO_CONSTRAINT_2 +
      The error with code 90085 is thrown when + trying to manually drop an index that was generated by the system + because of a unique or referential constraint.
      +
      static intINDEX_NOT_FOUND_1 +
      The error with code 42112 is thrown when + trying to drop or reference an index that does not exist.
      +
      static intINVALID_CLASS_2 +
      The error with code 90125 is thrown when + PreparedStatement.setBigDecimal is called with object that extends the + class BigDecimal, and the system property h2.allowBigDecimalExtensions is + not set.
      +
      static intINVALID_DATABASE_NAME_1 +
      The error with code 90138 is thrown when + + trying to open a persistent database using an incorrect database name.
      +
      static intINVALID_DATETIME_CONSTANT_2 +
      The error with code 22007 is thrown when + a text can not be converted to a date, time, or timestamp constant.
      +
      static intINVALID_NAME_1 +
      The error with code 42602 is thrown when + invalid name of identifier is used.
      +
      static intINVALID_PARAMETER_COUNT_2 +
      The error with code 7001 is thrown when + trying to call a function with the wrong number of parameters.
      +
      static intINVALID_PRECEDING_OR_FOLLOWING_1 +
      The error with code 22013 is thrown when preceding or + following size in a window function is null or negative.
      +
      static intINVALID_TO_CHAR_FORMAT +
      The error with code 90010 is thrown when + trying to format a timestamp or number using TO_CHAR + with an invalid format.
      +
      static intINVALID_TO_DATE_FORMAT +
      The error with code 90056 is thrown when trying to format a + timestamp using TO_DATE and TO_TIMESTAMP with an invalid format.
      +
      static intINVALID_TRIGGER_FLAGS_1 +
      The error with code 90005 is thrown when + trying to create a trigger with invalid combination of flags.
      +
      static intINVALID_USE_OF_AGGREGATE_FUNCTION_1 +
      The error with code 90054 is thrown when + an aggregate function is used where it is not allowed.
      +
      static intINVALID_VALUE_2 +
      The error with code 90008 is thrown when + trying to use a value that is not valid for the given operation.
      +
      static intINVALID_VALUE_PRECISION +
      The error with code 90150 is thrown when + trying to use an invalid precision.
      +
      static intINVALID_VALUE_SCALE +
      The error with code 90151 is thrown when + trying to use an invalid scale or fractional seconds precision.
      +
      static intIO_EXCEPTION_1 +
      The error with code 90028 is thrown when + an input / output error occurred.
      +
      static intIO_EXCEPTION_2 +
      The error with code 90031 is thrown when + an input / output error occurred.
      +
      static intJAVA_OBJECT_SERIALIZER_CHANGE_WITH_DATA_TABLE +
      The error with code 90141 is thrown when + trying to change the java object serializer while there was already data + in the database.
      +
      static intLIKE_ESCAPE_ERROR_1 +
      The error with code 22025 is thrown when using an invalid + escape character sequence for LIKE or REGEXP.
      +
      static intLITERALS_ARE_NOT_ALLOWED +
      The error with code 90116 is thrown when + trying use a literal in a SQL statement if literals are disabled.
      +
      static intLOB_CLOSED_ON_TIMEOUT_1 +
      The error with code 90039 is thrown when + trying to access a CLOB or BLOB object that timed out.
      +
      static intLOCK_TIMEOUT_1 +
      The error with code 50200 is thrown when + another connection locked an object longer than the lock timeout + set for this connection, or when a deadlock occurred.
      +
      static intMETHOD_DISABLED_ON_AUTOCOMMIT_TRUE +
      The error with code 90147 is thrown when trying to execute a + statement which closes the transaction (such as commit and rollback) and + autocommit mode is on.
      +
      static intMETHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT +
      The error with code 90130 is thrown when + an execute method of PreparedStatement was called with a SQL statement.
      +
      static intMETHOD_NOT_ALLOWED_FOR_QUERY +
      The error with code 90001 is thrown when + Statement.executeUpdate() was called for a SELECT statement.
      +
      static intMETHOD_NOT_FOUND_1 +
      The error with code 90087 is thrown when + a method with matching number of arguments was not found in the class.
      +
      static intMETHOD_ONLY_ALLOWED_FOR_QUERY +
      The error with code 90002 is thrown when + Statement.executeQuery() was called for a statement that does + not return a result set (for example, an UPDATE statement).
      +
      static intMETHODS_MUST_HAVE_DIFFERENT_PARAMETER_COUNTS_2 +
      The error with code 90073 is thrown when trying to create + an alias for a Java method, if two methods exists in this class that have + this name and the same number of parameters.
      +
      static intMUST_GROUP_BY_COLUMN_1 +
      The error with code 90016 is thrown when + a column was used in the expression list or the order by clause of a + group or aggregate query, and that column is not in the GROUP BY clause.
      +
      static intNAME_TOO_LONG_2 +
      The error with code 42622 is thrown when + name of identifier is too long.
      +
      static intNO_DATA_AVAILABLE +
      The error with code 2000 is thrown when + the result set is positioned before the first or after the last row, or + not on a valid row for the given operation.
      +
      static intNO_DEFAULT_SET_1 +
      The error with code 23507 is thrown when + updating or deleting from a table with a foreign key constraint + that should set the default value, but there is no default value defined.
      +
      static intNOT_ENOUGH_RIGHTS_FOR_1 +
      The error with code 90096 is thrown when + trying to perform an operation with a non-admin user if the + user does not have enough rights.
      +
      static intNOT_ON_UPDATABLE_ROW +
      The error with code 90029 is thrown when + calling ResultSet.deleteRow(), insertRow(), or updateRow() + when the current row is not updatable.
      +
      static intNULL_NOT_ALLOWED +
      The error with code 23502 is thrown when + trying to insert NULL into a column that does not allow NULL.
      +
      static intNUMERIC_VALUE_OUT_OF_RANGE_1 +
      The error with code 22003 is thrown when a value is out of + range when converting to another data type.
      +
      static intNUMERIC_VALUE_OUT_OF_RANGE_2 +
      The error with code 22004 is thrown when a value is out of + range when converting to another column's data type.
      +
      static intOBJECT_CLOSED +
      The error with code 90007 is thrown when + trying to call a JDBC method on an object that has been closed.
      +
      static intORDER_BY_NOT_IN_RESULT +
      The error with code 90068 is thrown when the given + expression that is used in the ORDER BY is not in the result list.
      +
      static intOUT_OF_MEMORY +
      The error with code 90108 is thrown when not enough heap + memory was available.
      +
      static intPARAMETER_NOT_SET_1 +
      The error with code 90012 is thrown when + trying to execute a statement with an parameter.
      +
      static intPARSE_ERROR_1 +
      The error with code 90014 is thrown when + trying to parse a date with an unsupported format string, or + when the date can not be parsed.
      +
      static intPUBLIC_STATIC_JAVA_METHOD_NOT_FOUND_1 +
      The error with code 90139 is thrown when + the specified public static Java method was not found in the class.
      +
      static intREFERENTIAL_INTEGRITY_VIOLATED_CHILD_EXISTS_1 +
      The error with code 23503 is thrown when trying to delete + or update a row when this would violate a referential constraint, because + there is a child row that would become an orphan.
      +
      static intREFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1 +
      The error with code 23506 is thrown when trying to insert + or update a row that would violate a referential constraint, because the + referenced row does not exist.
      +
      static intREMOTE_CONNECTION_NOT_ALLOWED +
      The error with code 90117 is thrown when + trying to connect to a TCP server from another machine, if remote + connections are not allowed.
      +
      static intREMOTE_DATABASE_NOT_FOUND_1 +
      The error with code 90149 is thrown when trying to open a + database that does not exist remotely without enabling remote database + creation first.
      +
      static intRESULT_SET_NOT_SCROLLABLE +
      The error with code 90128 is thrown when + trying to call a method of the ResultSet that is only supported + for scrollable result sets, and the result set is not scrollable.
      +
      static intRESULT_SET_NOT_UPDATABLE +
      The error with code 90127 is thrown when + trying to update or delete a row in a result set if the result set is + not updatable.
      +
      static intRESULT_SET_READONLY +
      The error with code 90140 is thrown when trying to update or + delete a row in a result set if the statement was not created with + updatable concurrency.
      +
      static intROLE_ALREADY_EXISTS_1 +
      The error with code 90069 is thrown when + trying to create a role if an object with this name already exists.
      +
      static intROLE_ALREADY_GRANTED_1 +
      The error with code 90074 is thrown when + trying to grant a role that has already been granted.
      +
      static intROLE_CAN_NOT_BE_DROPPED_1 +
      The error with code 90091 is thrown when + trying to drop the role PUBLIC.
      +
      static intROLE_NOT_FOUND_1 +
      The error with code 90070 is thrown when + trying to drop or grant a role that does not exists.
      +
      static intROLES_AND_RIGHT_CANNOT_BE_MIXED +
      The error with code 90072 is thrown when + trying to grant or revoke both roles and rights at the same time.
      +
      static intROW_NOT_FOUND_IN_PRIMARY_INDEX +
      The error with code 90143 is thrown when + trying to fetch a row from the primary index and the row is not there.
      +
      static intROW_NOT_FOUND_WHEN_DELETING_1 +
      The error with code 90112 is thrown when a row was deleted + twice while locking was disabled.
      +
      static intSAVEPOINT_IS_INVALID_1 +
      The error with code 90063 is thrown when + trying to rollback to a savepoint that is not defined.
      +
      static intSAVEPOINT_IS_NAMED +
      The error with code 90065 is thrown when + Savepoint.getSavepointId() is called on a named savepoint.
      +
      static intSAVEPOINT_IS_UNNAMED +
      The error with code 90064 is thrown when + Savepoint.getSavepointName() is called on an unnamed savepoint.
      +
      static intSCALAR_SUBQUERY_CONTAINS_MORE_THAN_ONE_ROW +
      The error with code 90053 is thrown when + a subquery that is used as a value contains more than one row.
      +
      static intSCHEMA_ALREADY_EXISTS_1 +
      The error with code 90078 is thrown when + trying to create a schema if an object with this name already exists.
      +
      static intSCHEMA_CAN_NOT_BE_DROPPED_1 +
      The error with code 90090 is thrown when + trying to drop a schema that may not be dropped (the schema PUBLIC + and the schema INFORMATION_SCHEMA).
      +
      static intSCHEMA_NAME_MUST_MATCH +
      The error with code 90080 is thrown when + trying to rename a object to a different schema, or when trying to + create a related object in another schema.
      +
      static intSCHEMA_NOT_FOUND_1 +
      The error with code 90079 is thrown when + trying to drop a schema that does not exist.
      +
      static intSECOND_PRIMARY_KEY +
      The error with code 90017 is thrown when + trying to define a second primary key constraint for this table.
      +
      static intSEQUENCE_ALREADY_EXISTS_1 +
      The error with code 90035 is thrown when + trying to create a sequence if a sequence with this name already + exists.
      +
      static intSEQUENCE_ATTRIBUTES_INVALID_7 +
      The error with code 90009 is thrown when + trying to create a sequence with an invalid combination + of attributes (min value, max value, start value, etc).
      +
      static intSEQUENCE_BELONGS_TO_A_TABLE_1 +
      The error with code 90082 is thrown when + trying to drop a system generated sequence.
      +
      static intSEQUENCE_EXHAUSTED +
      The error with code 90006 is thrown when + trying to get a value from a sequence that has run out of numbers + and does not have cycling enabled.
      +
      static intSEQUENCE_NOT_FOUND_1 +
      The error with code 90036 is thrown when + trying to access a sequence that does not exist.
      +
      static intSERIALIZATION_FAILED_1 +
      The error with code 90026 is thrown when + an object could not be serialized.
      +
      static intSTATEMENT_WAS_CANCELED +
      The error with code 57014 is thrown when + a statement was canceled using Statement.cancel() or + when the query timeout has been reached.
      +
      static intSTEP_SIZE_MUST_NOT_BE_ZERO +
      The error with code 90142 is thrown when + trying to set zero for step size.
      +
      static intSTRING_FORMAT_ERROR_1 +
      The error with code 90095 is thrown when + calling the method STRINGDECODE with an invalid escape sequence.
      +
      static intSUBQUERY_IS_NOT_SINGLE_COLUMN +
      The error with code 90052 is thrown when a single-column + subquery is expected but a subquery with other number of columns was + specified.
      +
      static intSUM_OR_AVG_ON_WRONG_DATATYPE_1 +
      The error with code 90015 is thrown when + using an aggregate function with a data type that is not supported.
      +
      static intSYNTAX_ERROR_1 +
      The error with code 42000 is thrown when + trying to execute an invalid SQL statement.
      +
      static intSYNTAX_ERROR_2 +
      The error with code 42001 is thrown when + trying to execute an invalid SQL statement.
      +
      static intTABLE_OR_VIEW_ALREADY_EXISTS_1 +
      The error with code 42101 is thrown when + trying to create a table or view if an object with this name already + exists.
      +
      static intTABLE_OR_VIEW_NOT_FOUND_1 +
      The error with code 42102 is thrown when + trying to query, modify or drop a table or view that does not exists + in this schema and database.
      +
      static intTABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1 +
      The error with code 42104 is thrown when + trying to query, modify or drop a table or view that does not exists + in this schema and database but it is empty anyway.
      +
      static intTABLE_OR_VIEW_NOT_FOUND_WITH_CANDIDATES_2 +
      The error with code 42103 is thrown when + trying to query, modify or drop a table or view that does not exists + in this schema and database but similar names were found.
      +
      static intTOO_MANY_COLUMNS_1 +
      The error with code 54011 is thrown when + too many columns were specified in a table, select statement, + or row value.
      +
      static intTRACE_CONNECTION_NOT_CLOSED +
      The error with code 90018 is thrown when + the connection was opened, but never closed.
      +
      static intTRACE_FILE_ERROR_2 +
      The error with code 90034 is thrown when + writing to the trace file failed, for example because the there + is an I/O exception.
      +
      static intTRANSACTION_NOT_FOUND_1 +
      The error with code 90129 is thrown when + trying to commit a transaction that doesn't exist.
      +
      static intTRIGGER_ALREADY_EXISTS_1 +
      The error with code 90041 is thrown when + trying to create a trigger and there is already a trigger with that name.
      +
      static intTRIGGER_NOT_FOUND_1 +
      The error with code 90042 is thrown when + trying to drop a trigger that does not exist.
      +
      static intTYPES_ARE_NOT_COMPARABLE_2 +
      The error with code 90110 is thrown when + trying to compare or combine values of incomparable data types.
      +
      static intUNCOMPARABLE_REFERENCED_COLUMN_2 +
      The error with code 90153 is thrown when trying to reference + a column of another data type when data types aren't comparable or don't + have a session-independent compare order between each other.
      +
      static intUNKNOWN_DATA_TYPE_1 +
      The error with code 50004 is thrown when + creating a table with an unsupported data type, or + when the data type is unknown because parameters are used.
      +
      static intUNKNOWN_MODE_1 +
      The error with code 90088 is thrown when + trying to switch to an unknown mode.
      +
      static intUNSUPPORTED_CIPHER +
      The error with code 90055 is thrown when + trying to open a database with an unsupported cipher algorithm.
      +
      static intUNSUPPORTED_COMPRESSION_ALGORITHM_1 +
      The error with code 90103 is thrown when + trying to use an unsupported compression algorithm.
      +
      static intUNSUPPORTED_COMPRESSION_OPTIONS_1 +
      The error with code 90102 is thrown when + trying to use unsupported options for the given compression algorithm.
      +
      static intUNSUPPORTED_LOCK_METHOD_1 +
      The error with code 90060 is thrown when + trying to use a file locking mechanism that is not supported.
      +
      static intUNSUPPORTED_SETTING_1 +
      The error with code 90113 is thrown when + the database URL contains unsupported settings.
      +
      static intUNSUPPORTED_SETTING_COMBINATION +
      The error with code 90021 is thrown when + trying to change a specific database property that conflicts with other + database properties.
      +
      static intURL_FORMAT_ERROR_2 +
      The error with code 90046 is thrown when + trying to open a connection to a database using an unsupported URL + format.
      +
      static intURL_RELATIVE_TO_CWD +
      The error with code 90011 is thrown when + trying to open a connection to a database using an implicit relative + path, such as "jdbc:h2:test" (in which case the database file would be + stored in the current working directory of the application).
      +
      static intUSER_ALREADY_EXISTS_1 +
      The error with code 90033 is thrown when + trying to create a user or role if a user with this name already exists.
      +
      static intUSER_DATA_TYPE_ALREADY_EXISTS_1 +
      Deprecated. 
      +
      static intUSER_DATA_TYPE_NOT_FOUND_1 +
      Deprecated. 
      +
      static intUSER_NOT_FOUND_1 +
      The error with code 90032 is thrown when + trying to drop or alter a user that does not exist.
      +
      static intUSER_OR_ROLE_NOT_FOUND_1 +
      The error with code 90071 is thrown when + trying to grant or revoke if no role or user with that name exists.
      +
      static intVALUE_TOO_LONG_2 +
      The error with code 22001 is thrown when + trying to insert a value that is too long for the column.
      +
      static intVIEW_ALREADY_EXISTS_1 +
      The error with code 90038 is thrown when + trying to create a view if a view with this name already + exists.
      +
      static intVIEW_IS_INVALID_2 +
      The error with code 90109 is thrown when + trying to run a query against an invalid view.
      +
      static intVIEW_NOT_FOUND_1 +
      The error with code 90037 is thrown when + trying to drop or alter a view that does not exist.
      +
      static intWINDOW_NOT_FOUND_1 +
      The error with code 90136 is thrown when + trying to reference a window that does not exist.
      +
      static intWITH_TIES_WITHOUT_ORDER_BY +
      The error with code 90122 is thrown when + WITH TIES clause is used without ORDER BY clause.
      +
      static intWRONG_PASSWORD_FORMAT +
      The error with code 90050 is thrown when trying to open an + encrypted database, but not separating the file password from the user + password.
      +
      static intWRONG_USER_OR_PASSWORD +
      The error with code 28000 is thrown when + there is no such user registered in the database, when the user password + does not match, or when the database encryption password does not match + (if database encryption is used).
      +
      static intWRONG_XID_FORMAT_1 +
      The error with code 90101 is thrown when + the XA API detected unsupported transaction names.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethod and Description
      static java.lang.StringgetState(int errorCode) +
      INTERNAL
      +
      static booleanisCommon(int errorCode) +
      INTERNAL
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Detail

      + + + +
        +
      • +

        NO_DATA_AVAILABLE

        +
        public static final int NO_DATA_AVAILABLE
        +
        The error with code 2000 is thrown when + the result set is positioned before the first or after the last row, or + not on a valid row for the given operation. + Example of wrong usage: +
        + ResultSet rs = stat.executeQuery("SELECT * FROM DUAL");
        + rs.getString(1);
        + 
        + Correct: +
        + ResultSet rs = stat.executeQuery("SELECT * FROM DUAL");
        + rs.next();
        + rs.getString(1);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        INVALID_PARAMETER_COUNT_2

        +
        public static final int INVALID_PARAMETER_COUNT_2
        +
        The error with code 7001 is thrown when + trying to call a function with the wrong number of parameters. + Example: +
        + CALL ABS(1, 2)
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ERROR_OPENING_DATABASE_1

        +
        public static final int ERROR_OPENING_DATABASE_1
        +
        The error with code 8000 is thrown when + there was a problem trying to create a database lock. + See the message and cause for details.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        COLUMN_COUNT_DOES_NOT_MATCH

        +
        public static final int COLUMN_COUNT_DOES_NOT_MATCH
        +
        The error with code 21002 is thrown when the number of + columns does not match. Possible reasons are: for an INSERT or MERGE + statement, the column count does not match the table or the column list + specified. For a SELECT UNION statement, both queries return a different + number of columns. For a constraint, the number of referenced and + referencing columns does not match. Example: +
        + CREATE TABLE TEST(ID INT, NAME VARCHAR);
        + INSERT INTO TEST VALUES('Hello');
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        VALUE_TOO_LONG_2

        +
        public static final int VALUE_TOO_LONG_2
        +
        The error with code 22001 is thrown when + trying to insert a value that is too long for the column. + Example: +
        + CREATE TABLE TEST(ID INT, NAME VARCHAR(2));
        + INSERT INTO TEST VALUES(1, 'Hello');
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        NUMERIC_VALUE_OUT_OF_RANGE_1

        +
        public static final int NUMERIC_VALUE_OUT_OF_RANGE_1
        +
        The error with code 22003 is thrown when a value is out of + range when converting to another data type. Example: +
        + CALL CAST(1000000 AS TINYINT);
        + SELECT CAST(124.34 AS DECIMAL(2, 2));
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        NUMERIC_VALUE_OUT_OF_RANGE_2

        +
        public static final int NUMERIC_VALUE_OUT_OF_RANGE_2
        +
        The error with code 22004 is thrown when a value is out of + range when converting to another column's data type.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        INVALID_DATETIME_CONSTANT_2

        +
        public static final int INVALID_DATETIME_CONSTANT_2
        +
        The error with code 22007 is thrown when + a text can not be converted to a date, time, or timestamp constant. + Examples: +
        + CALL DATE '2007-January-01';
        + CALL TIME '14:61:00';
        + CALL TIMESTAMP '2001-02-30 12:00:00';
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DIVISION_BY_ZERO_1

        +
        public static final int DIVISION_BY_ZERO_1
        +
        The error with code 22012 is thrown when trying to divide + a value by zero. Example: +
        + CALL 1/0;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        INVALID_PRECEDING_OR_FOLLOWING_1

        +
        public static final int INVALID_PRECEDING_OR_FOLLOWING_1
        +
        The error with code 22013 is thrown when preceding or + following size in a window function is null or negative. Example: +
        + FIRST_VALUE(N) OVER(ORDER BY N ROWS -1 PRECEDING)
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DATA_CONVERSION_ERROR_1

        +
        public static final int DATA_CONVERSION_ERROR_1
        +
        The error with code 22018 is thrown when + trying to convert a value to a data type where the conversion is + undefined, or when an error occurred trying to convert. Example: +
        + CALL CAST(DATE '2001-01-01' AS BOOLEAN);
        + CALL CAST('CHF 99.95' AS INT);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        LIKE_ESCAPE_ERROR_1

        +
        public static final int LIKE_ESCAPE_ERROR_1
        +
        The error with code 22025 is thrown when using an invalid + escape character sequence for LIKE or REGEXP. The default escape + character is '\'. The escape character is required when searching for + the characters '%', '_' and the escape character itself. That means if + you want to search for the text '10%', you need to use LIKE '10\%'. If + you want to search for 'C:\temp' you need to use 'C:\\temp'. The escape + character can be changed using the ESCAPE clause as in LIKE '10+%' ESCAPE + '+'. Example of wrong usage: +
        + CALL 'C:\temp' LIKE 'C:\temp';
        + CALL '1+1' LIKE '1+1' ESCAPE '+';
        + 
        + Correct: +
        + CALL 'C:\temp' LIKE 'C:\\temp';
        + CALL '1+1' LIKE '1++1' ESCAPE '+';
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ENUM_VALUE_NOT_PERMITTED

        +
        public static final int ENUM_VALUE_NOT_PERMITTED
        +
        The error with code 22030 is thrown when + an attempt is made to INSERT or UPDATE an ENUM-typed cell, + but the value is not one of the values enumerated by the + type. + + Example: +
        + CREATE TABLE TEST(CASE ENUM('sensitive','insensitive'));
        + INSERT INTO TEST VALUES('snake');
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ENUM_EMPTY

        +
        public static final int ENUM_EMPTY
        +
        The error with code 22032 is thrown when an + attempt is made to add or modify an ENUM-typed column so + that one or more of its enumerators would be empty. + + Example: +
        + CREATE TABLE TEST(CASE ENUM(' '));
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ENUM_DUPLICATE

        +
        public static final int ENUM_DUPLICATE
        +
        The error with code 22033 is thrown when an + attempt is made to add or modify an ENUM-typed column so + that it would have duplicate values. + + Example: +
        + CREATE TABLE TEST(CASE ENUM('sensitive', 'sensitive'));
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ARRAY_ELEMENT_ERROR_2

        +
        public static final int ARRAY_ELEMENT_ERROR_2
        +
        The error with code 22034 is thrown when an + attempt is made to read non-existing element of an array. + + Example: +
        + VALUES ARRAY[1, 2][3]
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        NULL_NOT_ALLOWED

        +
        public static final int NULL_NOT_ALLOWED
        +
        The error with code 23502 is thrown when + trying to insert NULL into a column that does not allow NULL. + Example: +
        + CREATE TABLE TEST(ID INT, NAME VARCHAR NOT NULL);
        + INSERT INTO TEST(ID) VALUES(1);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        REFERENTIAL_INTEGRITY_VIOLATED_CHILD_EXISTS_1

        +
        public static final int REFERENTIAL_INTEGRITY_VIOLATED_CHILD_EXISTS_1
        +
        The error with code 23503 is thrown when trying to delete + or update a row when this would violate a referential constraint, because + there is a child row that would become an orphan. Example: +
        + CREATE TABLE TEST(ID INT PRIMARY KEY, PARENT INT);
        + INSERT INTO TEST VALUES(1, 1), (2, 1);
        + ALTER TABLE TEST ADD CONSTRAINT TEST_ID_PARENT
        +       FOREIGN KEY(PARENT) REFERENCES TEST(ID) ON DELETE RESTRICT;
        + DELETE FROM TEST WHERE ID = 1;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DUPLICATE_KEY_1

        +
        public static final int DUPLICATE_KEY_1
        +
        The error with code 23505 is thrown when trying to insert + a row that would violate a unique index or primary key. Example: +
        + CREATE TABLE TEST(ID INT PRIMARY KEY);
        + INSERT INTO TEST VALUES(1);
        + INSERT INTO TEST VALUES(1);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1

        +
        public static final int REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1
        +
        The error with code 23506 is thrown when trying to insert + or update a row that would violate a referential constraint, because the + referenced row does not exist. Example: +
        + CREATE TABLE PARENT(ID INT PRIMARY KEY);
        + CREATE TABLE CHILD(P_ID INT REFERENCES PARENT(ID));
        + INSERT INTO CHILD VALUES(1);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        NO_DEFAULT_SET_1

        +
        public static final int NO_DEFAULT_SET_1
        +
        The error with code 23507 is thrown when + updating or deleting from a table with a foreign key constraint + that should set the default value, but there is no default value defined. + Example: +
        + CREATE TABLE TEST(ID INT PRIMARY KEY, PARENT INT);
        + INSERT INTO TEST VALUES(1, 1), (2, 1);
        + ALTER TABLE TEST ADD CONSTRAINT TEST_ID_PARENT
        +   FOREIGN KEY(PARENT) REFERENCES TEST(ID) ON DELETE SET DEFAULT;
        + DELETE FROM TEST WHERE ID = 1;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CHECK_CONSTRAINT_VIOLATED_1

        +
        public static final int CHECK_CONSTRAINT_VIOLATED_1
        +
        The error with code 23513 is thrown when + a check constraint is violated. Example: +
        + CREATE TABLE TEST(ID INT CHECK (ID>0));
        + INSERT INTO TEST VALUES(0);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CHECK_CONSTRAINT_INVALID

        +
        public static final int CHECK_CONSTRAINT_INVALID
        +
        The error with code 23514 is thrown when + evaluation of a check constraint resulted in an error.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        WRONG_USER_OR_PASSWORD

        +
        public static final int WRONG_USER_OR_PASSWORD
        +
        The error with code 28000 is thrown when + there is no such user registered in the database, when the user password + does not match, or when the database encryption password does not match + (if database encryption is used).
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DEADLOCK_1

        +
        public static final int DEADLOCK_1
        +
        The error with code 40001 is thrown when + the database engine has detected a deadlock. The transaction of this + session has been rolled back to solve the problem. A deadlock occurs when + a session tries to lock a table another session has locked, while the + other session wants to lock a table the first session has locked. As an + example, session 1 has locked table A, while session 2 has locked table + B. If session 1 now tries to lock table B and session 2 tries to lock + table A, a deadlock has occurred. Deadlocks that involve more than two + sessions are also possible. To solve deadlock problems, an application + should lock tables always in the same order, such as always lock table A + before locking table B. For details, see Wikipedia Deadlock.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SYNTAX_ERROR_1

        +
        public static final int SYNTAX_ERROR_1
        +
        The error with code 42000 is thrown when + trying to execute an invalid SQL statement. + Example: +
        + CREATE ALIAS REMAINDER FOR "IEEEremainder";
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SYNTAX_ERROR_2

        +
        public static final int SYNTAX_ERROR_2
        +
        The error with code 42001 is thrown when + trying to execute an invalid SQL statement. + Example: +
        + CREATE TABLE TEST(ID INT);
        + INSERT INTO TEST(1);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        TABLE_OR_VIEW_ALREADY_EXISTS_1

        +
        public static final int TABLE_OR_VIEW_ALREADY_EXISTS_1
        +
        The error with code 42101 is thrown when + trying to create a table or view if an object with this name already + exists. Example: +
        + CREATE TABLE TEST(ID INT);
        + CREATE TABLE TEST(ID INT PRIMARY KEY);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        TABLE_OR_VIEW_NOT_FOUND_1

        +
        public static final int TABLE_OR_VIEW_NOT_FOUND_1
        +
        The error with code 42102 is thrown when + trying to query, modify or drop a table or view that does not exists + in this schema and database. A common cause is that the wrong + database was opened. + Example: +
        + SELECT * FROM ABC;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        TABLE_OR_VIEW_NOT_FOUND_WITH_CANDIDATES_2

        +
        public static final int TABLE_OR_VIEW_NOT_FOUND_WITH_CANDIDATES_2
        +
        The error with code 42103 is thrown when + trying to query, modify or drop a table or view that does not exists + in this schema and database but similar names were found. A common cause + is that the names are written in different case. + Example: +
        + SELECT * FROM ABC;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1

        +
        public static final int TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1
        +
        The error with code 42104 is thrown when + trying to query, modify or drop a table or view that does not exists + in this schema and database but it is empty anyway. A common cause is + that the wrong database was opened. + Example: +
        + SELECT * FROM ABC;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        INDEX_ALREADY_EXISTS_1

        +
        public static final int INDEX_ALREADY_EXISTS_1
        +
        The error with code 42111 is thrown when + trying to create an index if an index with the same name already exists. + Example: +
        + CREATE TABLE TEST(ID INT, NAME VARCHAR);
        + CREATE INDEX IDX_ID ON TEST(ID);
        + CREATE TABLE ADDRESS(ID INT);
        + CREATE INDEX IDX_ID ON ADDRESS(ID);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        INDEX_NOT_FOUND_1

        +
        public static final int INDEX_NOT_FOUND_1
        +
        The error with code 42112 is thrown when + trying to drop or reference an index that does not exist. + Example: +
        + DROP INDEX ABC;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DUPLICATE_COLUMN_NAME_1

        +
        public static final int DUPLICATE_COLUMN_NAME_1
        +
        The error with code 42121 is thrown when trying to create + a table or insert into a table and use the same column name twice. + Example: +
        + CREATE TABLE TEST(ID INT, ID INT);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        COLUMN_NOT_FOUND_1

        +
        public static final int COLUMN_NOT_FOUND_1
        +
        The error with code 42122 is thrown when + referencing an non-existing column. + Example: +
        + CREATE TABLE TEST(ID INT);
        + SELECT NAME FROM TEST;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        IDENTICAL_EXPRESSIONS_SHOULD_BE_USED

        +
        public static final int IDENTICAL_EXPRESSIONS_SHOULD_BE_USED
        +
        The error with code 42131 is thrown when + identical expressions should be used, but different + expressions were found. + Example: +
        + SELECT MODE(A ORDER BY B) FROM TEST;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        INVALID_NAME_1

        +
        public static final int INVALID_NAME_1
        +
        The error with code 42602 is thrown when + invalid name of identifier is used. + Example: +
        + statement.enquoteIdentifier("\"", true);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        NAME_TOO_LONG_2

        +
        public static final int NAME_TOO_LONG_2
        +
        The error with code 42622 is thrown when + name of identifier is too long. + Example: +
        + char[] c = new char[1000];
        + Arrays.fill(c, 'A');
        + statement.executeQuery("SELECT 1 " + new String(c));
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        TOO_MANY_COLUMNS_1

        +
        public static final int TOO_MANY_COLUMNS_1
        +
        The error with code 54011 is thrown when + too many columns were specified in a table, select statement, + or row value. + Example: +
        + CREATE TABLE TEST(C1 INTEGER, C2 INTEGER, ..., C20000 INTEGER);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        GENERAL_ERROR_1

        +
        public static final int GENERAL_ERROR_1
        +
        The error with code 50000 is thrown when + something unexpected occurs, for example an internal stack + overflow. For details about the problem, see the cause of the + exception in the stack trace.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        UNKNOWN_DATA_TYPE_1

        +
        public static final int UNKNOWN_DATA_TYPE_1
        +
        The error with code 50004 is thrown when + creating a table with an unsupported data type, or + when the data type is unknown because parameters are used. + Example: +
        + CREATE TABLE TEST(ID VERYSMALLINT);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        FEATURE_NOT_SUPPORTED_1

        +
        public static final int FEATURE_NOT_SUPPORTED_1
        +
        The error with code 50100 is thrown when calling an + unsupported JDBC method or database feature. See the stack trace for + details.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        LOCK_TIMEOUT_1

        +
        public static final int LOCK_TIMEOUT_1
        +
        The error with code 50200 is thrown when + another connection locked an object longer than the lock timeout + set for this connection, or when a deadlock occurred. + Example: +
        + CREATE TABLE TEST(ID INT);
        + -- connection 1:
        + SET AUTOCOMMIT FALSE;
        + INSERT INTO TEST VALUES(1);
        + -- connection 2:
        + SET AUTOCOMMIT FALSE;
        + INSERT INTO TEST VALUES(1);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        STATEMENT_WAS_CANCELED

        +
        public static final int STATEMENT_WAS_CANCELED
        +
        The error with code 57014 is thrown when + a statement was canceled using Statement.cancel() or + when the query timeout has been reached. + Examples: +
        + stat.setQueryTimeout(1);
        + stat.cancel();
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        FUNCTION_MUST_RETURN_RESULT_SET_1

        +
        public static final int FUNCTION_MUST_RETURN_RESULT_SET_1
        +
        The error with code 90000 is thrown when + a function that does not return a result set was used in the FROM clause. + Example: +
        + SELECT * FROM SIN(1);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        METHOD_NOT_ALLOWED_FOR_QUERY

        +
        public static final int METHOD_NOT_ALLOWED_FOR_QUERY
        +
        The error with code 90001 is thrown when + Statement.executeUpdate() was called for a SELECT statement. + This is not allowed according to the JDBC specs.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        METHOD_ONLY_ALLOWED_FOR_QUERY

        +
        public static final int METHOD_ONLY_ALLOWED_FOR_QUERY
        +
        The error with code 90002 is thrown when + Statement.executeQuery() was called for a statement that does + not return a result set (for example, an UPDATE statement). + This is not allowed according to the JDBC specs.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        HEX_STRING_ODD_1

        +
        public static final int HEX_STRING_ODD_1
        +
        The error with code 90003 is thrown when + trying to convert a String to a binary value. Two hex digits + per byte are required. Example of wrong usage: +
        + CALL X'00023';
        + Hexadecimal string with odd number of characters: 00023
        + 
        + Correct: +
        + CALL X'000023';
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        HEX_STRING_WRONG_1

        +
        public static final int HEX_STRING_WRONG_1
        +
        The error with code 90004 is thrown when + trying to convert a text to binary, but the expression contains + a non-hexadecimal character. + Example: +
        + CALL X'ABCDEFGH';
        + CALL CAST('ABCDEFGH' AS BINARY);
        + 
        + Conversion from text to binary is supported, but the text must + represent the hexadecimal encoded bytes.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        INVALID_TRIGGER_FLAGS_1

        +
        public static final int INVALID_TRIGGER_FLAGS_1
        +
        The error with code 90005 is thrown when + trying to create a trigger with invalid combination of flags.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SEQUENCE_EXHAUSTED

        +
        public static final int SEQUENCE_EXHAUSTED
        +
        The error with code 90006 is thrown when + trying to get a value from a sequence that has run out of numbers + and does not have cycling enabled.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        OBJECT_CLOSED

        +
        public static final int OBJECT_CLOSED
        +
        The error with code 90007 is thrown when + trying to call a JDBC method on an object that has been closed.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        INVALID_VALUE_2

        +
        public static final int INVALID_VALUE_2
        +
        The error with code 90008 is thrown when + trying to use a value that is not valid for the given operation. + Example: +
        + CREATE SEQUENCE TEST INCREMENT 0;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SEQUENCE_ATTRIBUTES_INVALID_7

        +
        public static final int SEQUENCE_ATTRIBUTES_INVALID_7
        +
        The error with code 90009 is thrown when + trying to create a sequence with an invalid combination + of attributes (min value, max value, start value, etc).
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        INVALID_TO_CHAR_FORMAT

        +
        public static final int INVALID_TO_CHAR_FORMAT
        +
        The error with code 90010 is thrown when + trying to format a timestamp or number using TO_CHAR + with an invalid format.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        URL_RELATIVE_TO_CWD

        +
        public static final int URL_RELATIVE_TO_CWD
        +
        The error with code 90011 is thrown when + trying to open a connection to a database using an implicit relative + path, such as "jdbc:h2:test" (in which case the database file would be + stored in the current working directory of the application). This is not + allowed because it can lead to confusion where the database file is, and + can result in multiple databases because different working directories + are used. Instead, use "jdbc:h2:~/name" (relative to the current user + home directory), use an absolute path, set the base directory (baseDir), + use "jdbc:h2:./name" (explicit relative path), or set the system property + "h2.implicitRelativePath" to "true" (to prevent this check). For Windows, + an absolute path also needs to include the drive ("C:/..."). Please see + the documentation on the supported URL format. Example: +
        + jdbc:h2:test
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        PARAMETER_NOT_SET_1

        +
        public static final int PARAMETER_NOT_SET_1
        +
        The error with code 90012 is thrown when + trying to execute a statement with an parameter. + Example: +
        + CALL SIN(?);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DATABASE_NOT_FOUND_1

        +
        public static final int DATABASE_NOT_FOUND_1
        +
        The error with code 90013 is thrown when when trying to access + a database object with a catalog name that does not match the database + name. +
        + SELECT * FROM database_that_does_not_exist.table_name
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        PARSE_ERROR_1

        +
        public static final int PARSE_ERROR_1
        +
        The error with code 90014 is thrown when + trying to parse a date with an unsupported format string, or + when the date can not be parsed. + Example: +
        + CALL PARSEDATETIME('2001 January', 'yyyy mm');
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SUM_OR_AVG_ON_WRONG_DATATYPE_1

        +
        public static final int SUM_OR_AVG_ON_WRONG_DATATYPE_1
        +
        The error with code 90015 is thrown when + using an aggregate function with a data type that is not supported. + Example: +
        + SELECT SUM('Hello') FROM DUAL;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        MUST_GROUP_BY_COLUMN_1

        +
        public static final int MUST_GROUP_BY_COLUMN_1
        +
        The error with code 90016 is thrown when + a column was used in the expression list or the order by clause of a + group or aggregate query, and that column is not in the GROUP BY clause. + Example of wrong usage: +
        + CREATE TABLE TEST(ID INT, NAME VARCHAR);
        + INSERT INTO TEST VALUES(1, 'Hello'), (2, 'World');
        + SELECT ID, MAX(NAME) FROM TEST;
        + Column ID must be in the GROUP BY list.
        + 
        + Correct: +
        + SELECT ID, MAX(NAME) FROM TEST GROUP BY ID;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SECOND_PRIMARY_KEY

        +
        public static final int SECOND_PRIMARY_KEY
        +
        The error with code 90017 is thrown when + trying to define a second primary key constraint for this table. + Example: +
        + CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR);
        + ALTER TABLE TEST ADD CONSTRAINT PK PRIMARY KEY(NAME);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        TRACE_CONNECTION_NOT_CLOSED

        +
        public static final int TRACE_CONNECTION_NOT_CLOSED
        +
        The error with code 90018 is thrown when + the connection was opened, but never closed. In the finalizer of the + connection, this forgotten close was detected and the connection was + closed automatically, but relying on the finalizer is not good practice + as it is not guaranteed and behavior is virtual machine dependent. The + application should close the connection. This exception only appears in + the .trace.db file. Example of wrong usage: +
        + Connection conn;
        + conn = DriverManager.getConnection("jdbc:h2:˜/test");
        + conn = null;
        + The connection was not closed by the application and is
        + garbage collected
        + 
        + Correct: +
        + conn.close();
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CANNOT_DROP_CURRENT_USER

        +
        public static final int CANNOT_DROP_CURRENT_USER
        +
        The error with code 90019 is thrown when + trying to drop the current user, if there are no other admin users. + Example: +
        + DROP USER SA;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DATABASE_ALREADY_OPEN_1

        +
        public static final int DATABASE_ALREADY_OPEN_1
        +
        The error with code 90020 is thrown when trying to open a + database in embedded mode if this database is already in use in another + process (or in a different class loader). Multiple connections to the + same database are supported in the following cases: +
        • In embedded mode (URL of the form jdbc:h2:~/test) if all + connections are opened within the same process and class loader. +
        • In server and cluster mode (URL of the form + jdbc:h2:tcp://localhost/test) using remote connections. +
        + The mixed mode is also supported. This mode requires to start a server + in the same process where the database is open in embedded mode.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        UNSUPPORTED_SETTING_COMBINATION

        +
        public static final int UNSUPPORTED_SETTING_COMBINATION
        +
        The error with code 90021 is thrown when + trying to change a specific database property that conflicts with other + database properties.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        FUNCTION_NOT_FOUND_1

        +
        public static final int FUNCTION_NOT_FOUND_1
        +
        The error with code 90022 is thrown when + trying to call a unknown function. + Example: +
        + CALL SPECIAL_SIN(10);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        COLUMN_MUST_NOT_BE_NULLABLE_1

        +
        public static final int COLUMN_MUST_NOT_BE_NULLABLE_1
        +
        The error with code 90023 is thrown when trying to set a + primary key on a nullable column or when trying to drop NOT NULL + constraint on primary key or identity column. + Examples: +
        + CREATE TABLE TEST(ID INT, NAME VARCHAR);
        + ALTER TABLE TEST ADD CONSTRAINT PK PRIMARY KEY(ID);
        + 
        +
        + CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR);
        + ALTER TABLE TEST ALTER COLUMN ID DROP NOT NULL;
        + 
        +
        + CREATE TABLE TEST(ID INT GENERATED ALWAYS AS IDENTITY, NAME VARCHAR);
        + ALTER TABLE TEST ALTER COLUMN ID DROP NOT NULL;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        FILE_RENAME_FAILED_2

        +
        public static final int FILE_RENAME_FAILED_2
        +
        The error with code 90024 is thrown when + a file could not be renamed.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        FILE_DELETE_FAILED_1

        +
        public static final int FILE_DELETE_FAILED_1
        +
        The error with code 90025 is thrown when + a file could not be deleted, because it is still in use + (only in Windows), or because an error occurred when deleting.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SERIALIZATION_FAILED_1

        +
        public static final int SERIALIZATION_FAILED_1
        +
        The error with code 90026 is thrown when + an object could not be serialized.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DESERIALIZATION_FAILED_1

        +
        public static final int DESERIALIZATION_FAILED_1
        +
        The error with code 90027 is thrown when + an object could not be de-serialized.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        IO_EXCEPTION_1

        +
        public static final int IO_EXCEPTION_1
        +
        The error with code 90028 is thrown when + an input / output error occurred. For more information, see the root + cause of the exception.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        NOT_ON_UPDATABLE_ROW

        +
        public static final int NOT_ON_UPDATABLE_ROW
        +
        The error with code 90029 is thrown when + calling ResultSet.deleteRow(), insertRow(), or updateRow() + when the current row is not updatable. + Example: +
        + ResultSet rs = stat.executeQuery("SELECT * FROM TEST");
        + rs.next();
        + rs.insertRow();
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        FILE_CORRUPTED_1

        +
        public static final int FILE_CORRUPTED_1
        +
        The error with code 90030 is thrown when + the database engine has detected a checksum mismatch in the data + or index. To solve this problem, restore a backup or use the + Recovery tool (org.h2.tools.Recover).
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        IO_EXCEPTION_2

        +
        public static final int IO_EXCEPTION_2
        +
        The error with code 90031 is thrown when + an input / output error occurred. For more information, see the root + cause of the exception.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        USER_NOT_FOUND_1

        +
        public static final int USER_NOT_FOUND_1
        +
        The error with code 90032 is thrown when + trying to drop or alter a user that does not exist. + Example: +
        + DROP USER TEST_USER;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        USER_ALREADY_EXISTS_1

        +
        public static final int USER_ALREADY_EXISTS_1
        +
        The error with code 90033 is thrown when + trying to create a user or role if a user with this name already exists. + Example: +
        + CREATE USER TEST_USER;
        + CREATE USER TEST_USER;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        TRACE_FILE_ERROR_2

        +
        public static final int TRACE_FILE_ERROR_2
        +
        The error with code 90034 is thrown when + writing to the trace file failed, for example because the there + is an I/O exception. This message is printed to System.out, + but only once.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SEQUENCE_ALREADY_EXISTS_1

        +
        public static final int SEQUENCE_ALREADY_EXISTS_1
        +
        The error with code 90035 is thrown when + trying to create a sequence if a sequence with this name already + exists. + Example: +
        + CREATE SEQUENCE TEST_SEQ;
        + CREATE SEQUENCE TEST_SEQ;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SEQUENCE_NOT_FOUND_1

        +
        public static final int SEQUENCE_NOT_FOUND_1
        +
        The error with code 90036 is thrown when + trying to access a sequence that does not exist. + Example: +
        + SELECT NEXT VALUE FOR SEQUENCE XYZ;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        VIEW_NOT_FOUND_1

        +
        public static final int VIEW_NOT_FOUND_1
        +
        The error with code 90037 is thrown when + trying to drop or alter a view that does not exist. + Example: +
        + DROP VIEW XYZ;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        VIEW_ALREADY_EXISTS_1

        +
        public static final int VIEW_ALREADY_EXISTS_1
        +
        The error with code 90038 is thrown when + trying to create a view if a view with this name already + exists. + Example: +
        + CREATE VIEW DUMMY AS SELECT * FROM DUAL;
        + CREATE VIEW DUMMY AS SELECT * FROM DUAL;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        LOB_CLOSED_ON_TIMEOUT_1

        +
        public static final int LOB_CLOSED_ON_TIMEOUT_1
        +
        The error with code 90039 is thrown when + trying to access a CLOB or BLOB object that timed out. + See the database setting LOB_TIMEOUT.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ADMIN_RIGHTS_REQUIRED

        +
        public static final int ADMIN_RIGHTS_REQUIRED
        +
        The error with code 90040 is thrown when + a user that is not administrator tries to execute a statement + that requires admin privileges.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        TRIGGER_ALREADY_EXISTS_1

        +
        public static final int TRIGGER_ALREADY_EXISTS_1
        +
        The error with code 90041 is thrown when + trying to create a trigger and there is already a trigger with that name. +
        + CREATE TABLE TEST(ID INT);
        + CREATE TRIGGER TRIGGER_A AFTER INSERT ON TEST
        +      CALL "org.h2.samples.TriggerSample$MyTrigger";
        + CREATE TRIGGER TRIGGER_A AFTER INSERT ON TEST
        +      CALL "org.h2.samples.TriggerSample$MyTrigger";
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        TRIGGER_NOT_FOUND_1

        +
        public static final int TRIGGER_NOT_FOUND_1
        +
        The error with code 90042 is thrown when + trying to drop a trigger that does not exist. + Example: +
        + DROP TRIGGER TRIGGER_XYZ;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ERROR_CREATING_TRIGGER_OBJECT_3

        +
        public static final int ERROR_CREATING_TRIGGER_OBJECT_3
        +
        The error with code 90043 is thrown when + there is an error initializing the trigger, for example because the + class does not implement the Trigger interface. + See the root cause for details. + Example: +
        + CREATE TABLE TEST(ID INT);
        + CREATE TRIGGER TRIGGER_A AFTER INSERT ON TEST
        +      CALL "java.lang.String";
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ERROR_EXECUTING_TRIGGER_3

        +
        public static final int ERROR_EXECUTING_TRIGGER_3
        +
        The error with code 90044 is thrown when + an exception or error occurred while calling the triggers fire method. + See the root cause for details.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CONSTRAINT_ALREADY_EXISTS_1

        +
        public static final int CONSTRAINT_ALREADY_EXISTS_1
        +
        The error with code 90045 is thrown when trying to create a + constraint if an object with this name already exists. Example: +
        + CREATE TABLE TEST(ID INT NOT NULL);
        + ALTER TABLE TEST ADD CONSTRAINT PK PRIMARY KEY(ID);
        + ALTER TABLE TEST ADD CONSTRAINT PK PRIMARY KEY(ID);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        URL_FORMAT_ERROR_2

        +
        public static final int URL_FORMAT_ERROR_2
        +
        The error with code 90046 is thrown when + trying to open a connection to a database using an unsupported URL + format. Please see the documentation on the supported URL format and + examples. Example: +
        + jdbc:h2:;;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DRIVER_VERSION_ERROR_2

        +
        public static final int DRIVER_VERSION_ERROR_2
        +
        The error with code 90047 is thrown when + trying to connect to a TCP server with an incompatible client.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        FILE_VERSION_ERROR_1

        +
        public static final int FILE_VERSION_ERROR_1
        +
        The error with code 90048 is thrown when + the file header of a database files (*.db) does not match the + expected version, or if it is corrupted.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        FILE_ENCRYPTION_ERROR_1

        +
        public static final int FILE_ENCRYPTION_ERROR_1
        +
        The error with code 90049 is thrown when + trying to open an encrypted database with the wrong file encryption + password or algorithm.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        WRONG_PASSWORD_FORMAT

        +
        public static final int WRONG_PASSWORD_FORMAT
        +
        The error with code 90050 is thrown when trying to open an + encrypted database, but not separating the file password from the user + password. The file password is specified in the password field, before + the user password. A single space needs to be added between the file + password and the user password; the file password itself may not contain + spaces. File passwords (as well as user passwords) are case sensitive. + Example of wrong usage: +
        + String url = "jdbc:h2:˜/test;CIPHER=AES";
        + String passwords = "filePasswordUserPassword";
        + DriverManager.getConnection(url, "sa", pwds);
        + 
        + Correct: +
        + String url = "jdbc:h2:˜/test;CIPHER=AES";
        + String passwords = "filePassword userPassword";
        + DriverManager.getConnection(url, "sa", pwds);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SUBQUERY_IS_NOT_SINGLE_COLUMN

        +
        public static final int SUBQUERY_IS_NOT_SINGLE_COLUMN
        +
        The error with code 90052 is thrown when a single-column + subquery is expected but a subquery with other number of columns was + specified. + Example: +
        + VALUES ARRAY(SELECT A, B FROM TEST)
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SCALAR_SUBQUERY_CONTAINS_MORE_THAN_ONE_ROW

        +
        public static final int SCALAR_SUBQUERY_CONTAINS_MORE_THAN_ONE_ROW
        +
        The error with code 90053 is thrown when + a subquery that is used as a value contains more than one row. + Example: +
        + CREATE TABLE TEST(ID INT, NAME VARCHAR);
        + INSERT INTO TEST VALUES(1, 'Hello'), (1, 'World');
        + SELECT X, (SELECT NAME FROM TEST WHERE ID=X) FROM DUAL;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        INVALID_USE_OF_AGGREGATE_FUNCTION_1

        +
        public static final int INVALID_USE_OF_AGGREGATE_FUNCTION_1
        +
        The error with code 90054 is thrown when + an aggregate function is used where it is not allowed. + Example: +
        + CREATE TABLE TEST(ID INT);
        + INSERT INTO TEST VALUES(1), (2);
        + SELECT MAX(ID) FROM TEST WHERE ID = MAX(ID) GROUP BY ID;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        UNSUPPORTED_CIPHER

        +
        public static final int UNSUPPORTED_CIPHER
        +
        The error with code 90055 is thrown when + trying to open a database with an unsupported cipher algorithm. + Supported is AES. + Example: +
        + jdbc:h2:~/test;CIPHER=DES
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        INVALID_TO_DATE_FORMAT

        +
        public static final int INVALID_TO_DATE_FORMAT
        +
        The error with code 90056 is thrown when trying to format a + timestamp using TO_DATE and TO_TIMESTAMP with an invalid format.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CONSTRAINT_NOT_FOUND_1

        +
        public static final int CONSTRAINT_NOT_FOUND_1
        +
        The error with code 90057 is thrown when + trying to drop a constraint that does not exist. + Example: +
        + CREATE TABLE TEST(ID INT);
        + ALTER TABLE TEST DROP CONSTRAINT CID;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        COMMIT_ROLLBACK_NOT_ALLOWED

        +
        public static final int COMMIT_ROLLBACK_NOT_ALLOWED
        +
        The error with code 90058 is thrown when trying to call + commit or rollback inside a trigger, or when trying to call a method + inside a trigger that implicitly commits the current transaction, if an + object is locked. This is not because it would release the lock too + early.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        AMBIGUOUS_COLUMN_NAME_1

        +
        public static final int AMBIGUOUS_COLUMN_NAME_1
        +
        The error with code 90059 is thrown when + a query contains a column that could belong to multiple tables. + Example: +
        + CREATE TABLE PARENT(ID INT, NAME VARCHAR);
        + CREATE TABLE CHILD(PID INT, NAME VARCHAR);
        + SELECT ID, NAME FROM PARENT P, CHILD C WHERE P.ID = C.PID;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        UNSUPPORTED_LOCK_METHOD_1

        +
        public static final int UNSUPPORTED_LOCK_METHOD_1
        +
        The error with code 90060 is thrown when + trying to use a file locking mechanism that is not supported. + Currently only FILE (the default) and SOCKET are supported + Example: +
        + jdbc:h2:~/test;FILE_LOCK=LDAP
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        EXCEPTION_OPENING_PORT_2

        +
        public static final int EXCEPTION_OPENING_PORT_2
        +
        The error with code 90061 is thrown when + trying to start a server if a server is already running at the same port. + It could also be a firewall problem. To find out if another server is + already running, run the following command on Windows: +
        + netstat -ano
        + 
        + The column PID is the process id as listed in the Task Manager. + For Linux, use: +
        + netstat -npl
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        FILE_CREATION_FAILED_1

        +
        public static final int FILE_CREATION_FAILED_1
        +
        The error with code 90062 is thrown when + a directory or file could not be created. This can occur when + trying to create a directory if a file with the same name already + exists, or vice versa.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SAVEPOINT_IS_INVALID_1

        +
        public static final int SAVEPOINT_IS_INVALID_1
        +
        The error with code 90063 is thrown when + trying to rollback to a savepoint that is not defined. + Example: +
        + ROLLBACK TO SAVEPOINT S_UNKNOWN;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SAVEPOINT_IS_UNNAMED

        +
        public static final int SAVEPOINT_IS_UNNAMED
        +
        The error with code 90064 is thrown when + Savepoint.getSavepointName() is called on an unnamed savepoint. + Example: +
        + Savepoint sp = conn.setSavepoint();
        + sp.getSavepointName();
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SAVEPOINT_IS_NAMED

        +
        public static final int SAVEPOINT_IS_NAMED
        +
        The error with code 90065 is thrown when + Savepoint.getSavepointId() is called on a named savepoint. + Example: +
        + Savepoint sp = conn.setSavepoint("Joe");
        + sp.getSavepointId();
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DUPLICATE_PROPERTY_1

        +
        public static final int DUPLICATE_PROPERTY_1
        +
        The error with code 90066 is thrown when + the same property appears twice in the database URL or in + the connection properties. + Example: +
        + jdbc:h2:~/test;LOCK_TIMEOUT=0;LOCK_TIMEOUT=1
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CONNECTION_BROKEN_1

        +
        public static final int CONNECTION_BROKEN_1
        +
        The error with code 90067 is thrown when the client could + not connect to the database, or if the connection was lost. Possible + reasons are: the database server is not running at the given port, the + connection was closed due to a shutdown, or the server was stopped. Other + possible causes are: the server is not an H2 server, or the network + connection is broken.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ORDER_BY_NOT_IN_RESULT

        +
        public static final int ORDER_BY_NOT_IN_RESULT
        +
        The error with code 90068 is thrown when the given + expression that is used in the ORDER BY is not in the result list. This + is required for distinct queries, otherwise the result would be + ambiguous. + Example of wrong usage: +
        + CREATE TABLE TEST(ID INT, NAME VARCHAR);
        + INSERT INTO TEST VALUES(2, 'Hello'), (1, 'Hello');
        + SELECT DISTINCT NAME FROM TEST ORDER BY ID;
        + Order by expression ID must be in the result list in this case
        + 
        + Correct: +
        + SELECT DISTINCT ID, NAME FROM TEST ORDER BY ID;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ROLE_ALREADY_EXISTS_1

        +
        public static final int ROLE_ALREADY_EXISTS_1
        +
        The error with code 90069 is thrown when + trying to create a role if an object with this name already exists. + Example: +
        + CREATE ROLE TEST_ROLE;
        + CREATE ROLE TEST_ROLE;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ROLE_NOT_FOUND_1

        +
        public static final int ROLE_NOT_FOUND_1
        +
        The error with code 90070 is thrown when + trying to drop or grant a role that does not exists. + Example: +
        + DROP ROLE TEST_ROLE_2;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        USER_OR_ROLE_NOT_FOUND_1

        +
        public static final int USER_OR_ROLE_NOT_FOUND_1
        +
        The error with code 90071 is thrown when + trying to grant or revoke if no role or user with that name exists. + Example: +
        + GRANT SELECT ON TEST TO UNKNOWN;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ROLES_AND_RIGHT_CANNOT_BE_MIXED

        +
        public static final int ROLES_AND_RIGHT_CANNOT_BE_MIXED
        +
        The error with code 90072 is thrown when + trying to grant or revoke both roles and rights at the same time. + Example: +
        + GRANT SELECT, TEST_ROLE ON TEST TO SA;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        METHODS_MUST_HAVE_DIFFERENT_PARAMETER_COUNTS_2

        +
        public static final int METHODS_MUST_HAVE_DIFFERENT_PARAMETER_COUNTS_2
        +
        The error with code 90073 is thrown when trying to create + an alias for a Java method, if two methods exists in this class that have + this name and the same number of parameters. + Example of wrong usage: +
        + CREATE ALIAS GET_LONG FOR
        +      "java.lang.Long.getLong";
        + 
        + Correct: +
        + CREATE ALIAS GET_LONG FOR
        +      "java.lang.Long.getLong(java.lang.String, java.lang.Long)";
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ROLE_ALREADY_GRANTED_1

        +
        public static final int ROLE_ALREADY_GRANTED_1
        +
        The error with code 90074 is thrown when + trying to grant a role that has already been granted. + Example: +
        + CREATE ROLE TEST_A;
        + CREATE ROLE TEST_B;
        + GRANT TEST_A TO TEST_B;
        + GRANT TEST_B TO TEST_A;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        COLUMN_IS_PART_OF_INDEX_1

        +
        public static final int COLUMN_IS_PART_OF_INDEX_1
        +
        The error with code 90075 is thrown when + trying to alter a table and allow null for a column that is part of a + primary key or hash index. + Example: +
        + CREATE TABLE TEST(ID INT PRIMARY KEY);
        + ALTER TABLE TEST ALTER COLUMN ID NULL;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        FUNCTION_ALIAS_ALREADY_EXISTS_1

        +
        public static final int FUNCTION_ALIAS_ALREADY_EXISTS_1
        +
        The error with code 90076 is thrown when + trying to create a function alias for a system function or for a function + that is already defined. + Example: +
        + CREATE ALIAS SQRT FOR "java.lang.Math.sqrt"
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        FUNCTION_ALIAS_NOT_FOUND_1

        +
        public static final int FUNCTION_ALIAS_NOT_FOUND_1
        +
        The error with code 90077 is thrown when + trying to drop a system function or a function alias that does not exist. + Example: +
        + DROP ALIAS SQRT;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SCHEMA_ALREADY_EXISTS_1

        +
        public static final int SCHEMA_ALREADY_EXISTS_1
        +
        The error with code 90078 is thrown when + trying to create a schema if an object with this name already exists. + Example: +
        + CREATE SCHEMA TEST_SCHEMA;
        + CREATE SCHEMA TEST_SCHEMA;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SCHEMA_NOT_FOUND_1

        +
        public static final int SCHEMA_NOT_FOUND_1
        +
        The error with code 90079 is thrown when + trying to drop a schema that does not exist. + Example: +
        + DROP SCHEMA UNKNOWN;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SCHEMA_NAME_MUST_MATCH

        +
        public static final int SCHEMA_NAME_MUST_MATCH
        +
        The error with code 90080 is thrown when + trying to rename a object to a different schema, or when trying to + create a related object in another schema. + For CREATE LINKED TABLE, it is thrown when multiple tables with that + name exist in different schemas. + Example: +
        + CREATE SCHEMA TEST_SCHEMA;
        + CREATE TABLE TEST(ID INT);
        + CREATE INDEX TEST_ID ON TEST(ID);
        + ALTER INDEX TEST_ID RENAME TO TEST_SCHEMA.IDX_TEST_ID;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        COLUMN_CONTAINS_NULL_VALUES_1

        +
        public static final int COLUMN_CONTAINS_NULL_VALUES_1
        +
        The error with code 90081 is thrown when + trying to alter a column to not allow NULL, if there + is already data in the table where this column is NULL. + Example: +
        + CREATE TABLE TEST(ID INT);
        + INSERT INTO TEST VALUES(NULL);
        + ALTER TABLE TEST ALTER COLUMN ID VARCHAR NOT NULL;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SEQUENCE_BELONGS_TO_A_TABLE_1

        +
        public static final int SEQUENCE_BELONGS_TO_A_TABLE_1
        +
        The error with code 90082 is thrown when + trying to drop a system generated sequence.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        COLUMN_IS_REFERENCED_1

        +
        public static final int COLUMN_IS_REFERENCED_1
        +
        The error with code 90083 is thrown when + trying to drop a column that is part of a constraint. + Example: +
        + CREATE TABLE TEST(ID INT, PID INT REFERENCES(ID));
        + ALTER TABLE TEST DROP COLUMN PID;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CANNOT_DROP_LAST_COLUMN

        +
        public static final int CANNOT_DROP_LAST_COLUMN
        +
        The error with code 90084 is thrown when + trying to drop the last column of a table. + Example: +
        + CREATE TABLE TEST(ID INT);
        + ALTER TABLE TEST DROP COLUMN ID;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        INDEX_BELONGS_TO_CONSTRAINT_2

        +
        public static final int INDEX_BELONGS_TO_CONSTRAINT_2
        +
        The error with code 90085 is thrown when + trying to manually drop an index that was generated by the system + because of a unique or referential constraint. To find + the owner of the index without attempt to drop it run +
        + SELECT CONSTRAINT_SCHEMA, CONSTRAINT_NAME
        + FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
        + WHERE INDEX_SCHEMA = '<index schema>'
        + AND INDEX_NAME = '<index name>'
        + FETCH FIRST ROW ONLY
        + 
        + Example of wrong usage: +
        + CREATE TABLE TEST(ID INT, CONSTRAINT UID UNIQUE(ID));
        + DROP INDEX UID_INDEX_0;
        + Index UID_INDEX_0 belongs to constraint UID
        + 
        + Correct: +
        + ALTER TABLE TEST DROP CONSTRAINT UID;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CLASS_NOT_FOUND_1

        +
        public static final int CLASS_NOT_FOUND_1
        +
        The error with code 90086 is thrown when + a class can not be loaded because it is not in the classpath + or because a related class is not in the classpath. + Example: +
        + CREATE ALIAS TEST FOR "java.lang.invalid.Math.sqrt";
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        METHOD_NOT_FOUND_1

        +
        public static final int METHOD_NOT_FOUND_1
        +
        The error with code 90087 is thrown when + a method with matching number of arguments was not found in the class. + Example: +
        + CREATE ALIAS TO_BINARY FOR "java.lang.Long.toBinaryString(long)";
        + CALL TO_BINARY(10, 2);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        UNKNOWN_MODE_1

        +
        public static final int UNKNOWN_MODE_1
        +
        The error with code 90088 is thrown when + trying to switch to an unknown mode. + Example: +
        + SET MODE UNKNOWN;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        COLLATION_CHANGE_WITH_DATA_TABLE_1

        +
        public static final int COLLATION_CHANGE_WITH_DATA_TABLE_1
        +
        The error with code 90089 is thrown when + trying to change the collation while there was already data in + the database. The collation of the database must be set when the + database is empty. + Example of wrong usage: +
        + CREATE TABLE TEST(NAME VARCHAR PRIMARY KEY);
        + INSERT INTO TEST VALUES('Hello', 'World');
        + SET COLLATION DE;
        + Collation cannot be changed because there is a data table: PUBLIC.TEST
        + 
        + Correct: +
        + SET COLLATION DE;
        + CREATE TABLE TEST(NAME VARCHAR PRIMARY KEY);
        + INSERT INTO TEST VALUES('Hello', 'World');
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SCHEMA_CAN_NOT_BE_DROPPED_1

        +
        public static final int SCHEMA_CAN_NOT_BE_DROPPED_1
        +
        The error with code 90090 is thrown when + trying to drop a schema that may not be dropped (the schema PUBLIC + and the schema INFORMATION_SCHEMA). + Example: +
        + DROP SCHEMA PUBLIC;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ROLE_CAN_NOT_BE_DROPPED_1

        +
        public static final int ROLE_CAN_NOT_BE_DROPPED_1
        +
        The error with code 90091 is thrown when + trying to drop the role PUBLIC. + Example: +
        + DROP ROLE PUBLIC;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CLUSTER_ERROR_DATABASE_RUNS_ALONE

        +
        public static final int CLUSTER_ERROR_DATABASE_RUNS_ALONE
        +
        The error with code 90093 is thrown when + trying to connect to a clustered database that runs in standalone + mode. This can happen if clustering is not enabled on the database, + or if one of the clients disabled clustering because it can not see + the other cluster node.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CLUSTER_ERROR_DATABASE_RUNS_CLUSTERED_1

        +
        public static final int CLUSTER_ERROR_DATABASE_RUNS_CLUSTERED_1
        +
        The error with code 90094 is thrown when + trying to connect to a clustered database that runs together with a + different cluster node setting than what is used when trying to connect.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        STRING_FORMAT_ERROR_1

        +
        public static final int STRING_FORMAT_ERROR_1
        +
        The error with code 90095 is thrown when + calling the method STRINGDECODE with an invalid escape sequence. + Only Java style escape sequences and Java properties file escape + sequences are supported. + Example: +
        + CALL STRINGDECODE('\i');
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        NOT_ENOUGH_RIGHTS_FOR_1

        +
        public static final int NOT_ENOUGH_RIGHTS_FOR_1
        +
        The error with code 90096 is thrown when + trying to perform an operation with a non-admin user if the + user does not have enough rights.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DATABASE_IS_READ_ONLY

        +
        public static final int DATABASE_IS_READ_ONLY
        +
        The error with code 90097 is thrown when + trying to delete or update a database if it is open in read-only mode. + Example: +
        + jdbc:h2:~/test;ACCESS_MODE_DATA=R
        + CREATE TABLE TEST(ID INT);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DATABASE_IS_CLOSED

        +
        public static final int DATABASE_IS_CLOSED
        +
        The error with code 90098 is thrown when the database has + been closed, for example because the system ran out of memory or because + the self-destruction counter has reached zero. This counter is only used + for recovery testing, and not set in normal operation.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ERROR_SETTING_DATABASE_EVENT_LISTENER_2

        +
        public static final int ERROR_SETTING_DATABASE_EVENT_LISTENER_2
        +
        The error with code 90099 is thrown when an error occurred + trying to initialize the database event listener. Example: +
        + jdbc:h2:˜/test;DATABASE_EVENT_LISTENER='java.lang.String'
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        WRONG_XID_FORMAT_1

        +
        public static final int WRONG_XID_FORMAT_1
        +
        The error with code 90101 is thrown when + the XA API detected unsupported transaction names. This can happen + when mixing application generated transaction names and transaction names + generated by this databases XAConnection API.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        UNSUPPORTED_COMPRESSION_OPTIONS_1

        +
        public static final int UNSUPPORTED_COMPRESSION_OPTIONS_1
        +
        The error with code 90102 is thrown when + trying to use unsupported options for the given compression algorithm. + Example of wrong usage: +
        + CALL COMPRESS(STRINGTOUTF8(SPACE(100)), 'DEFLATE l 10');
        + 
        + Correct: +
        + CALL COMPRESS(STRINGTOUTF8(SPACE(100)), 'DEFLATE l 9');
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        UNSUPPORTED_COMPRESSION_ALGORITHM_1

        +
        public static final int UNSUPPORTED_COMPRESSION_ALGORITHM_1
        +
        The error with code 90103 is thrown when + trying to use an unsupported compression algorithm. + Example: +
        + CALL COMPRESS(STRINGTOUTF8(SPACE(100)), 'BZIP');
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        COMPRESSION_ERROR

        +
        public static final int COMPRESSION_ERROR
        +
        The error with code 90104 is thrown when + the data can not be de-compressed. + Example: +
        + CALL EXPAND(X'00FF');
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        EXCEPTION_IN_FUNCTION_1

        +
        public static final int EXCEPTION_IN_FUNCTION_1
        +
        The error with code 90105 is thrown when + an exception occurred in a user-defined method. + Example: +
        + CREATE ALIAS SYS_PROP FOR "java.lang.System.getProperty";
        + CALL SYS_PROP(NULL);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CANNOT_TRUNCATE_1

        +
        public static final int CANNOT_TRUNCATE_1
        +
        The error with code 90106 is thrown when + trying to truncate a table that can not be truncated. + Tables with referential integrity constraints can not be truncated. + Also, system tables and view can not be truncated. + Example: +
        + TRUNCATE TABLE INFORMATION_SCHEMA.SETTINGS;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CANNOT_DROP_2

        +
        public static final int CANNOT_DROP_2
        +
        The error with code 90107 is thrown when + trying to drop an object because another object would become invalid. + Example: +
        + CREATE TABLE COUNT(X INT);
        + CREATE TABLE ITEMS(ID INT DEFAULT SELECT MAX(X)+1 FROM COUNT);
        + DROP TABLE COUNT;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        OUT_OF_MEMORY

        +
        public static final int OUT_OF_MEMORY
        +
        The error with code 90108 is thrown when not enough heap + memory was available. A possible solutions is to increase the memory size + using java -Xmx128m .... Another solution is to reduce + the cache size.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        VIEW_IS_INVALID_2

        +
        public static final int VIEW_IS_INVALID_2
        +
        The error with code 90109 is thrown when + trying to run a query against an invalid view. + Example: +
        + CREATE FORCE VIEW TEST_VIEW AS SELECT * FROM TEST;
        + SELECT * FROM TEST_VIEW;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        TYPES_ARE_NOT_COMPARABLE_2

        +
        public static final int TYPES_ARE_NOT_COMPARABLE_2
        +
        The error with code 90110 is thrown when + trying to compare or combine values of incomparable data types. + Example: +
        + CREATE TABLE test (id INT NOT NULL, name VARCHAR);
        + select * from test where id = (1, 2);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ERROR_ACCESSING_LINKED_TABLE_2

        +
        public static final int ERROR_ACCESSING_LINKED_TABLE_2
        +
        The error with code 90111 is thrown when + an exception occurred while accessing a linked table.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ROW_NOT_FOUND_WHEN_DELETING_1

        +
        public static final int ROW_NOT_FOUND_WHEN_DELETING_1
        +
        The error with code 90112 is thrown when a row was deleted + twice while locking was disabled. This is an intern exception that should + never be thrown to the application, because such deleted should be + detected and the resulting exception ignored inside the database engine. +
        + Row not found when trying to delete from index UID_INDEX_0
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        UNSUPPORTED_SETTING_1

        +
        public static final int UNSUPPORTED_SETTING_1
        +
        The error with code 90113 is thrown when + the database URL contains unsupported settings. + Example: +
        + jdbc:h2:~/test;UNKNOWN=TRUE
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CONSTANT_ALREADY_EXISTS_1

        +
        public static final int CONSTANT_ALREADY_EXISTS_1
        +
        The error with code 90114 is thrown when + trying to create a constant if a constant with this name already exists. + Example: +
        + CREATE CONSTANT TEST VALUE 1;
        + CREATE CONSTANT TEST VALUE 1;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CONSTANT_NOT_FOUND_1

        +
        public static final int CONSTANT_NOT_FOUND_1
        +
        The error with code 90115 is thrown when + trying to drop a constant that does not exists. + Example: +
        + DROP CONSTANT UNKNOWN;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        LITERALS_ARE_NOT_ALLOWED

        +
        public static final int LITERALS_ARE_NOT_ALLOWED
        +
        The error with code 90116 is thrown when + trying use a literal in a SQL statement if literals are disabled. + If literals are disabled, use PreparedStatement and parameters instead + of literals in the SQL statement. + Example: +
        + SET ALLOW_LITERALS NONE;
        + CALL 1+1;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        REMOTE_CONNECTION_NOT_ALLOWED

        +
        public static final int REMOTE_CONNECTION_NOT_ALLOWED
        +
        The error with code 90117 is thrown when + trying to connect to a TCP server from another machine, if remote + connections are not allowed. To allow remote connections, + start the TCP server using the option -tcpAllowOthers as in: +
        + java org.h2.tools.Server -tcp -tcpAllowOthers
        + 
        + Or, when starting the server from an application, use: +
        + Server server = Server.createTcpServer("-tcpAllowOthers");
        + server.start();
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CANNOT_DROP_TABLE_1

        +
        public static final int CANNOT_DROP_TABLE_1
        +
        The error with code 90118 is thrown when + trying to drop a table can not be dropped. + Example: +
        + DROP TABLE INFORMATION_SCHEMA.SETTINGS;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DOMAIN_ALREADY_EXISTS_1

        +
        public static final int DOMAIN_ALREADY_EXISTS_1
        +
        The error with code 90119 is thrown when + trying to create a domain if an object with this name already exists, + or when trying to overload a built-in data type. + Example: +
        + CREATE DOMAIN INTEGER AS VARCHAR;
        + CREATE DOMAIN EMAIL AS VARCHAR CHECK LOCATE('@', VALUE) > 0;
        + CREATE DOMAIN EMAIL AS VARCHAR CHECK LOCATE('@', VALUE) > 0;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        USER_DATA_TYPE_ALREADY_EXISTS_1

        +
        @Deprecated
        +public static final int USER_DATA_TYPE_ALREADY_EXISTS_1
        +
        Deprecated. 
        +
        Deprecated since 1.4.198. Use DOMAIN_ALREADY_EXISTS_1 instead.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DOMAIN_NOT_FOUND_1

        +
        public static final int DOMAIN_NOT_FOUND_1
        +
        The error with code 90120 is thrown when + trying to drop a domain that doesn't exist. + Example: +
        + DROP DOMAIN UNKNOWN;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        USER_DATA_TYPE_NOT_FOUND_1

        +
        @Deprecated
        +public static final int USER_DATA_TYPE_NOT_FOUND_1
        +
        Deprecated. 
        +
        Deprecated since 1.4.198. Use DOMAIN_NOT_FOUND_1 instead.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DATABASE_CALLED_AT_SHUTDOWN

        +
        public static final int DATABASE_CALLED_AT_SHUTDOWN
        +
        The error with code 90121 is thrown when + a database operation is started while the virtual machine exits + (for example in a shutdown hook), or when the session is closed.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        WITH_TIES_WITHOUT_ORDER_BY

        +
        public static final int WITH_TIES_WITHOUT_ORDER_BY
        +
        The error with code 90122 is thrown when + WITH TIES clause is used without ORDER BY clause.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CANNOT_MIX_INDEXED_AND_UNINDEXED_PARAMS

        +
        public static final int CANNOT_MIX_INDEXED_AND_UNINDEXED_PARAMS
        +
        The error with code 90123 is thrown when + trying mix regular parameters and indexed parameters in the same + statement. Example: +
        + SELECT ?, ?1 FROM DUAL;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        FILE_NOT_FOUND_1

        +
        public static final int FILE_NOT_FOUND_1
        +
        The error with code 90124 is thrown when + trying to access a file that doesn't exist. This can occur when trying to + read a lob if the lob file has been deleted by another application.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        INVALID_CLASS_2

        +
        public static final int INVALID_CLASS_2
        +
        The error with code 90125 is thrown when + PreparedStatement.setBigDecimal is called with object that extends the + class BigDecimal, and the system property h2.allowBigDecimalExtensions is + not set. Using extensions of BigDecimal is dangerous because the database + relies on the behavior of BigDecimal. Example of wrong usage: +
        + BigDecimal bd = new MyDecimal("$10.3");
        + prep.setBigDecimal(1, bd);
        + Invalid class, expected java.math.BigDecimal but got MyDecimal
        + 
        + Correct: +
        + BigDecimal bd = new BigDecimal("10.3");
        + prep.setBigDecimal(1, bd);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DATABASE_IS_NOT_PERSISTENT

        +
        public static final int DATABASE_IS_NOT_PERSISTENT
        +
        The error with code 90126 is thrown when + trying to call the BACKUP statement for an in-memory database. + Example: +
        + jdbc:h2:mem:
        + BACKUP TO 'test.zip';
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        RESULT_SET_NOT_UPDATABLE

        +
        public static final int RESULT_SET_NOT_UPDATABLE
        +
        The error with code 90127 is thrown when + trying to update or delete a row in a result set if the result set is + not updatable. Result sets are only updatable if: + the statement was created with updatable concurrency; + all columns of the result set are from the same table; + the table is a data table (not a system table or view); + all columns of the primary key or any unique index are included; + all columns of the result set are columns of that table.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        RESULT_SET_NOT_SCROLLABLE

        +
        public static final int RESULT_SET_NOT_SCROLLABLE
        +
        The error with code 90128 is thrown when + trying to call a method of the ResultSet that is only supported + for scrollable result sets, and the result set is not scrollable. + Example: +
        + rs.first();
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        TRANSACTION_NOT_FOUND_1

        +
        public static final int TRANSACTION_NOT_FOUND_1
        +
        The error with code 90129 is thrown when + trying to commit a transaction that doesn't exist. + Example: +
        + PREPARE COMMIT ABC;
        + COMMIT TRANSACTION TEST;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        METHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT

        +
        public static final int METHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT
        +
        The error with code 90130 is thrown when + an execute method of PreparedStatement was called with a SQL statement. + This is not allowed according to the JDBC specification. Instead, use + an execute method of Statement. + Example of wrong usage: +
        + PreparedStatement prep = conn.prepareStatement("SELECT * FROM TEST");
        + prep.execute("DELETE FROM TEST");
        + 
        + Correct: +
        + Statement stat = conn.createStatement();
        + stat.execute("DELETE FROM TEST");
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CONCURRENT_UPDATE_1

        +
        public static final int CONCURRENT_UPDATE_1
        +
        The error with code 90131 is thrown when using multi version + concurrency control, and trying to update the same row from within two + connections at the same time, or trying to insert two rows with the same + key from two connections. Example: +
        + jdbc:h2:~/test
        + Session 1:
        + CREATE TABLE TEST(ID INT);
        + INSERT INTO TEST VALUES(1);
        + SET AUTOCOMMIT FALSE;
        + UPDATE TEST SET ID = 2;
        + Session 2:
        + SET AUTOCOMMIT FALSE;
        + UPDATE TEST SET ID = 3;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        AGGREGATE_NOT_FOUND_1

        +
        public static final int AGGREGATE_NOT_FOUND_1
        +
        The error with code 90132 is thrown when + trying to drop a user-defined aggregate function that doesn't exist. + Example: +
        + DROP AGGREGATE UNKNOWN;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CANNOT_CHANGE_SETTING_WHEN_OPEN_1

        +
        public static final int CANNOT_CHANGE_SETTING_WHEN_OPEN_1
        +
        The error with code 90133 is thrown when + trying to change a specific database property while the database is + already open.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ACCESS_DENIED_TO_CLASS_1

        +
        public static final int ACCESS_DENIED_TO_CLASS_1
        +
        The error with code 90134 is thrown when + trying to load a Java class that is not part of the allowed classes. By + default, all classes are allowed, but this can be changed using the + system property h2.allowedClasses.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DATABASE_IS_IN_EXCLUSIVE_MODE

        +
        public static final int DATABASE_IS_IN_EXCLUSIVE_MODE
        +
        The error with code 90135 is thrown when + trying to open a connection to a database that is currently open + in exclusive mode. The exclusive mode is set using: +
        + SET EXCLUSIVE TRUE;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        WINDOW_NOT_FOUND_1

        +
        public static final int WINDOW_NOT_FOUND_1
        +
        The error with code 90136 is thrown when + trying to reference a window that does not exist. + Example: +
        + SELECT LEAD(X) OVER W FROM TEST;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CAN_ONLY_ASSIGN_TO_VARIABLE_1

        +
        public static final int CAN_ONLY_ASSIGN_TO_VARIABLE_1
        +
        The error with code 90137 is thrown when + trying to assign a value to something that is not a variable. +
        + SELECT AMOUNT, SET(@V, COALESCE(@V, 0)+AMOUNT) FROM TEST;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        INVALID_DATABASE_NAME_1

        +
        public static final int INVALID_DATABASE_NAME_1
        +
        The error with code 90138 is thrown when + + trying to open a persistent database using an incorrect database name. + The name of a persistent database contains the path and file name prefix + where the data is stored. The file name part of a database name must be + at least two characters. + + Example of wrong usage: +
        + DriverManager.getConnection("jdbc:h2:~/t");
        + DriverManager.getConnection("jdbc:h2:~/test/");
        + 
        + Correct: +
        + DriverManager.getConnection("jdbc:h2:~/te");
        + DriverManager.getConnection("jdbc:h2:~/test/te");
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        PUBLIC_STATIC_JAVA_METHOD_NOT_FOUND_1

        +
        public static final int PUBLIC_STATIC_JAVA_METHOD_NOT_FOUND_1
        +
        The error with code 90139 is thrown when + the specified public static Java method was not found in the class. + Example: +
        + CREATE ALIAS TEST FOR "java.lang.Math.test";
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        RESULT_SET_READONLY

        +
        public static final int RESULT_SET_READONLY
        +
        The error with code 90140 is thrown when trying to update or + delete a row in a result set if the statement was not created with + updatable concurrency. Result sets are only updatable if the statement + was created with updatable concurrency, and if the result set contains + all columns of the primary key or of a unique index of a table.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        JAVA_OBJECT_SERIALIZER_CHANGE_WITH_DATA_TABLE

        +
        public static final int JAVA_OBJECT_SERIALIZER_CHANGE_WITH_DATA_TABLE
        +
        The error with code 90141 is thrown when + trying to change the java object serializer while there was already data + in the database. The serializer of the database must be set when the + database is empty.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        STEP_SIZE_MUST_NOT_BE_ZERO

        +
        public static final int STEP_SIZE_MUST_NOT_BE_ZERO
        +
        The error with code 90142 is thrown when + trying to set zero for step size.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ROW_NOT_FOUND_IN_PRIMARY_INDEX

        +
        public static final int ROW_NOT_FOUND_IN_PRIMARY_INDEX
        +
        The error with code 90143 is thrown when + trying to fetch a row from the primary index and the row is not there.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        AUTHENTICATOR_NOT_AVAILABLE

        +
        public static final int AUTHENTICATOR_NOT_AVAILABLE
        +
        The error with code 90144 is thrown when + user trying to login into a database with AUTHREALM set and + the target database doesn't have an authenticator defined +

        Authenticator experimental feature can be enabled by +

        +
        + SET AUTHENTICATOR TRUE
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        FOR_UPDATE_IS_NOT_ALLOWED_IN_DISTINCT_OR_GROUPED_SELECT

        +
        public static final int FOR_UPDATE_IS_NOT_ALLOWED_IN_DISTINCT_OR_GROUPED_SELECT
        +
        The error with code 90145 is thrown when trying to execute a + SELECT statement with non-window aggregates, DISTINCT, GROUP BY, or + HAVING clauses together with FOR UPDATE clause. + +
        + SELECT DISTINCT NAME FOR UPDATE;
        + SELECT MAX(VALUE) FOR UPDATE;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DATABASE_NOT_FOUND_WITH_IF_EXISTS_1

        +
        public static final int DATABASE_NOT_FOUND_WITH_IF_EXISTS_1
        +
        The error with code 90146 is thrown when trying to open a + database that does not exist using the flag IFEXISTS=TRUE +
        + jdbc:h2:./database_that_does_not_exist
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        METHOD_DISABLED_ON_AUTOCOMMIT_TRUE

        +
        public static final int METHOD_DISABLED_ON_AUTOCOMMIT_TRUE
        +
        The error with code 90147 is thrown when trying to execute a + statement which closes the transaction (such as commit and rollback) and + autocommit mode is on.
        +
        +
        See Also:
        +
        SysProperties.FORCE_AUTOCOMMIT_OFF_ON_COMMIT, +Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CURRENT_SEQUENCE_VALUE_IS_NOT_DEFINED_IN_SESSION_1

        +
        public static final int CURRENT_SEQUENCE_VALUE_IS_NOT_DEFINED_IN_SESSION_1
        +
        The error with code 90148 is thrown when trying to access + the current value of a sequence before execution of NEXT VALUE FOR + sequenceName in the current session. Example: + +
        + SELECT CURRENT VALUE FOR SEQUENCE XYZ;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        REMOTE_DATABASE_NOT_FOUND_1

        +
        public static final int REMOTE_DATABASE_NOT_FOUND_1
        +
        The error with code 90149 is thrown when trying to open a + database that does not exist remotely without enabling remote database + creation first. +
        + jdbc:h2:./database_that_does_not_exist
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        INVALID_VALUE_PRECISION

        +
        public static final int INVALID_VALUE_PRECISION
        +
        The error with code 90150 is thrown when + trying to use an invalid precision. + Example: +
        + CREATE TABLE TABLE1 ( FAIL INTERVAL YEAR(20) );
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        INVALID_VALUE_SCALE

        +
        public static final int INVALID_VALUE_SCALE
        +
        The error with code 90151 is thrown when + trying to use an invalid scale or fractional seconds precision. + Example: +
        + CREATE TABLE TABLE1 ( FAIL TIME(10) );
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CONSTRAINT_IS_USED_BY_CONSTRAINT_2

        +
        public static final int CONSTRAINT_IS_USED_BY_CONSTRAINT_2
        +
        The error with code 90152 is thrown when trying to manually + drop a unique or primary key constraint that is referenced by a foreign + key constraint without a CASCADE clause. + +
        + CREATE TABLE PARENT(ID INT CONSTRAINT P1 PRIMARY KEY);
        + CREATE TABLE CHILD(ID INT CONSTRAINT P2 PRIMARY KEY, CHILD INT CONSTRAINT C REFERENCES PARENT);
        + ALTER TABLE PARENT DROP CONSTRAINT P1 RESTRICT;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        UNCOMPARABLE_REFERENCED_COLUMN_2

        +
        public static final int UNCOMPARABLE_REFERENCED_COLUMN_2
        +
        The error with code 90153 is thrown when trying to reference + a column of another data type when data types aren't comparable or don't + have a session-independent compare order between each other. + +
        + CREATE TABLE PARENT(T TIMESTAMP UNIQUE);
        + CREATE TABLE CHILD(T TIMESTAMP WITH TIME ZONE REFERENCES PARENT(T));
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        GENERATED_COLUMN_CANNOT_BE_ASSIGNED_1

        +
        public static final int GENERATED_COLUMN_CANNOT_BE_ASSIGNED_1
        +
        The error with code 90154 is thrown when trying to assign a + value to a generated column. + +
        + CREATE TABLE TEST(A INT, B INT GENERATED ALWAYS AS (A + 1));
        + INSERT INTO TEST(A, B) VALUES (1, 1);
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        GENERATED_COLUMN_CANNOT_BE_UPDATABLE_BY_CONSTRAINT_2

        +
        public static final int GENERATED_COLUMN_CANNOT_BE_UPDATABLE_BY_CONSTRAINT_2
        +
        The error with code 90155 is thrown when trying to create a + referential constraint that can update a referenced generated column. + +
        + CREATE TABLE PARENT(ID INT PRIMARY KEY, K INT GENERATED ALWAYS AS (ID) UNIQUE);
        + CREATE TABLE CHILD(ID INT PRIMARY KEY, P INT);
        + ALTER TABLE CHILD ADD FOREIGN KEY(P) REFERENCES PARENT(K) ON DELETE SET NULL;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        COLUMN_ALIAS_IS_NOT_SPECIFIED_1

        +
        public static final int COLUMN_ALIAS_IS_NOT_SPECIFIED_1
        +
        The error with code 90156 is thrown when trying to create a + view or a table from a select and some expression doesn't have a column + name or alias when it is required by a compatibility mode. + +
        + SET MODE DB2;
        + CREATE TABLE T1(A INT, B INT);
        + CREATE TABLE T2 AS (SELECT A + B FROM T1) WITH DATA;
        + 
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        GROUP_BY_NOT_IN_THE_RESULT

        +
        public static final int GROUP_BY_NOT_IN_THE_RESULT
        +
        The error with code 90157 is thrown when the integer + index that is used in the GROUP BY is not in the SELECT list
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        isCommon

        +
        public static boolean isCommon(int errorCode)
        +
        INTERNAL
        +
        +
        Parameters:
        +
        errorCode - to check
        +
        Returns:
        +
        true if provided code is common, false otherwise
        +
        +
      • +
      + + + +
        +
      • +

        getState

        +
        public static java.lang.String getState(int errorCode)
        +
        INTERNAL
        +
        +
        Parameters:
        +
        errorCode - to get state for
        +
        Returns:
        +
        error state
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/api/H2Type.html b/h2/docs/javadoc/org/h2/api/H2Type.html new file mode 100644 index 0000000..c09ae9c --- /dev/null +++ b/h2/docs/javadoc/org/h2/api/H2Type.html @@ -0,0 +1,993 @@ + + + + + +H2Type + + + + + + + + + + + + +
+
org.h2.api
+

Class H2Type

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.api.H2Type
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.sql.SQLType
    +
    +
    +
    +
    public final class H2Type
    +extends java.lang.Object
    +implements java.sql.SQLType
    +
    Data types of H2.
    +
  • +
+
+
+ +
+
+
    +
  • + +
      +
    • + + +

      Field Detail

      + + + +
        +
      • +

        CHAR

        +
        public static final H2Type CHAR
        +
        The CHARACTER data type.
        +
      • +
      + + + +
        +
      • +

        VARCHAR

        +
        public static final H2Type VARCHAR
        +
        The CHARACTER VARYING data type.
        +
      • +
      + + + +
        +
      • +

        CLOB

        +
        public static final H2Type CLOB
        +
        The CHARACTER LARGE OBJECT data type.
        +
      • +
      + + + +
        +
      • +

        VARCHAR_IGNORECASE

        +
        public static final H2Type VARCHAR_IGNORECASE
        +
        The VARCHAR_IGNORECASE data type.
        +
      • +
      + + + +
        +
      • +

        BINARY

        +
        public static final H2Type BINARY
        +
        The BINARY data type.
        +
      • +
      + + + +
        +
      • +

        VARBINARY

        +
        public static final H2Type VARBINARY
        +
        The BINARY VARYING data type.
        +
      • +
      + + + +
        +
      • +

        BLOB

        +
        public static final H2Type BLOB
        +
        The BINARY LARGE OBJECT data type.
        +
      • +
      + + + +
        +
      • +

        BOOLEAN

        +
        public static final H2Type BOOLEAN
        +
        The BOOLEAN data type
        +
      • +
      + + + +
        +
      • +

        TINYINT

        +
        public static final H2Type TINYINT
        +
        The TINYINT data type.
        +
      • +
      + + + +
        +
      • +

        SMALLINT

        +
        public static final H2Type SMALLINT
        +
        The SMALLINT data type.
        +
      • +
      + + + +
        +
      • +

        INTEGER

        +
        public static final H2Type INTEGER
        +
        The INTEGER data type.
        +
      • +
      + + + +
        +
      • +

        BIGINT

        +
        public static final H2Type BIGINT
        +
        The BIGINT data type.
        +
      • +
      + + + +
        +
      • +

        NUMERIC

        +
        public static final H2Type NUMERIC
        +
        The NUMERIC data type.
        +
      • +
      + + + +
        +
      • +

        REAL

        +
        public static final H2Type REAL
        +
        The REAL data type.
        +
      • +
      + + + +
        +
      • +

        DOUBLE_PRECISION

        +
        public static final H2Type DOUBLE_PRECISION
        +
        The DOUBLE PRECISION data type.
        +
      • +
      + + + +
        +
      • +

        DECFLOAT

        +
        public static final H2Type DECFLOAT
        +
        The DECFLOAT data type.
        +
      • +
      + + + +
        +
      • +

        DATE

        +
        public static final H2Type DATE
        +
        The DATE data type.
        +
      • +
      + + + +
        +
      • +

        TIME

        +
        public static final H2Type TIME
        +
        The TIME data type.
        +
      • +
      + + + +
        +
      • +

        TIME_WITH_TIME_ZONE

        +
        public static final H2Type TIME_WITH_TIME_ZONE
        +
        The TIME WITH TIME ZONE data type.
        +
      • +
      + + + +
        +
      • +

        TIMESTAMP

        +
        public static final H2Type TIMESTAMP
        +
        The TIMESTAMP data type.
        +
      • +
      + + + +
        +
      • +

        TIMESTAMP_WITH_TIME_ZONE

        +
        public static final H2Type TIMESTAMP_WITH_TIME_ZONE
        +
        The TIMESTAMP WITH TIME ZONE data type.
        +
      • +
      + + + +
        +
      • +

        INTERVAL_YEAR

        +
        public static final H2Type INTERVAL_YEAR
        +
        The INTERVAL YEAR data type.
        +
      • +
      + + + +
        +
      • +

        INTERVAL_MONTH

        +
        public static final H2Type INTERVAL_MONTH
        +
        The INTERVAL MONTH data type.
        +
      • +
      + + + +
        +
      • +

        INTERVAL_DAY

        +
        public static final H2Type INTERVAL_DAY
        +
        The INTERVAL DAY data type.
        +
      • +
      + + + +
        +
      • +

        INTERVAL_HOUR

        +
        public static final H2Type INTERVAL_HOUR
        +
        The INTERVAL HOUR data type.
        +
      • +
      + + + +
        +
      • +

        INTERVAL_MINUTE

        +
        public static final H2Type INTERVAL_MINUTE
        +
        The INTERVAL MINUTE data type.
        +
      • +
      + + + +
        +
      • +

        INTERVAL_SECOND

        +
        public static final H2Type INTERVAL_SECOND
        +
        The INTERVAL SECOND data type.
        +
      • +
      + + + +
        +
      • +

        INTERVAL_YEAR_TO_MONTH

        +
        public static final H2Type INTERVAL_YEAR_TO_MONTH
        +
        The INTERVAL YEAR TO MONTH data type.
        +
      • +
      + + + +
        +
      • +

        INTERVAL_DAY_TO_HOUR

        +
        public static final H2Type INTERVAL_DAY_TO_HOUR
        +
        The INTERVAL DAY TO HOUR data type.
        +
      • +
      + + + +
        +
      • +

        INTERVAL_DAY_TO_MINUTE

        +
        public static final H2Type INTERVAL_DAY_TO_MINUTE
        +
        The INTERVAL DAY TO MINUTE data type.
        +
      • +
      + + + +
        +
      • +

        INTERVAL_DAY_TO_SECOND

        +
        public static final H2Type INTERVAL_DAY_TO_SECOND
        +
        The INTERVAL DAY TO SECOND data type.
        +
      • +
      + + + +
        +
      • +

        INTERVAL_HOUR_TO_MINUTE

        +
        public static final H2Type INTERVAL_HOUR_TO_MINUTE
        +
        The INTERVAL HOUR TO MINUTE data type.
        +
      • +
      + + + +
        +
      • +

        INTERVAL_HOUR_TO_SECOND

        +
        public static final H2Type INTERVAL_HOUR_TO_SECOND
        +
        The INTERVAL HOUR TO SECOND data type.
        +
      • +
      + + + +
        +
      • +

        INTERVAL_MINUTE_TO_SECOND

        +
        public static final H2Type INTERVAL_MINUTE_TO_SECOND
        +
        The INTERVAL MINUTE TO SECOND data type.
        +
      • +
      + + + +
        +
      • +

        JAVA_OBJECT

        +
        public static final H2Type JAVA_OBJECT
        +
        The JAVA_OBJECT data type.
        +
      • +
      + + + +
        +
      • +

        ENUM

        +
        public static final H2Type ENUM
        +
        The ENUM data type.
        +
      • +
      + + + +
        +
      • +

        GEOMETRY

        +
        public static final H2Type GEOMETRY
        +
        The GEOMETRY data type.
        +
      • +
      + + + +
        +
      • +

        JSON

        +
        public static final H2Type JSON
        +
        The JSON data type.
        +
      • +
      + + + +
        +
      • +

        UUID

        +
        public static final H2Type UUID
        +
        The UUID data type.
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        array

        +
        public static H2Type array(H2Type componentType)
        +
        Returns ARRAY data type with the specified component type.
        +
        +
        Parameters:
        +
        componentType - the type of elements
        +
        Returns:
        +
        ARRAY data type
        +
        +
      • +
      + + + +
        +
      • +

        row

        +
        public static H2Type row(H2Type... fieldTypes)
        +
        Returns ROW data type with specified types of fields and default names.
        +
        +
        Parameters:
        +
        fieldTypes - the type of fields
        +
        Returns:
        +
        ROW data type
        +
        +
      • +
      + + + +
        +
      • +

        getName

        +
        public java.lang.String getName()
        +
        +
        Specified by:
        +
        getName in interface java.sql.SQLType
        +
        +
      • +
      + + + +
        +
      • +

        getVendor

        +
        public java.lang.String getVendor()
        +
        +
        Specified by:
        +
        getVendor in interface java.sql.SQLType
        +
        +
      • +
      + + + +
        +
      • +

        getVendorTypeNumber

        +
        public java.lang.Integer getVendorTypeNumber()
        +
        Returns the vendor specific type number for the data type. The returned + value is actual only for the current version of H2.
        +
        +
        Specified by:
        +
        getVendorTypeNumber in interface java.sql.SQLType
        +
        Returns:
        +
        the vendor specific data type
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        +
        Overrides:
        +
        toString in class java.lang.Object
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/api/Interval.html b/h2/docs/javadoc/org/h2/api/Interval.html new file mode 100644 index 0000000..a909694 --- /dev/null +++ b/h2/docs/javadoc/org/h2/api/Interval.html @@ -0,0 +1,1068 @@ + + + + + +Interval + + + + + + + + + + + + +
+
org.h2.api
+

Class Interval

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.api.Interval
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public final class Interval
    +extends java.lang.Object
    +
    INTERVAL representation for result sets.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      Interval(IntervalQualifier qualifier, + boolean negative, + long leading, + long remaining) +
      Creates a new interval.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      booleanequals(java.lang.Object obj) 
      longgetDays() +
      Returns days value, if any.
      +
      longgetHours() +
      Returns hours value, if any.
      +
      longgetLeading() +
      Returns value of leading field of this interval.
      +
      longgetMinutes() +
      Returns minutes value, if any.
      +
      longgetMonths() +
      Returns months value, if any.
      +
      longgetNanosOfSecond() +
      Returns value of fractional part of seconds (in nanoseconds), if any.
      +
      IntervalQualifiergetQualifier() +
      Returns qualifier of this interval.
      +
      longgetRemaining() +
      Returns combined value of remaining fields of this interval.
      +
      longgetSeconds() +
      Returns value of integer part of seconds, if any.
      +
      longgetSecondsAndNanos() +
      Returns seconds value measured in nanoseconds, if any.
      +
      longgetYears() +
      Returns years value, if any.
      +
      inthashCode() 
      booleanisNegative() +
      Returns where the interval is negative.
      +
      static IntervalofDays(long days) +
      Creates a new INTERVAL DAY.
      +
      static IntervalofDaysHours(long days, + int hours) +
      Creates a new INTERVAL DAY TO HOUR.
      +
      static IntervalofDaysHoursMinutes(long days, + int hours, + int minutes) +
      Creates a new INTERVAL DAY TO MINUTE.
      +
      static IntervalofDaysHoursMinutesNanos(long days, + int hours, + int minutes, + long nanos) +
      Creates a new INTERVAL DAY TO SECOND.
      +
      static IntervalofDaysHoursMinutesSeconds(long days, + int hours, + int minutes, + int seconds) +
      Creates a new INTERVAL DAY TO SECOND.
      +
      static IntervalofHours(long hours) +
      Creates a new INTERVAL HOUR.
      +
      static IntervalofHoursMinutes(long hours, + int minutes) +
      Creates a new INTERVAL HOUR TO MINUTE.
      +
      static IntervalofHoursMinutesNanos(long hours, + int minutes, + long nanos) +
      Creates a new INTERVAL HOUR TO SECOND.
      +
      static IntervalofHoursMinutesSeconds(long hours, + int minutes, + int seconds) +
      Creates a new INTERVAL HOUR TO SECOND.
      +
      static IntervalofMinutes(long minutes) +
      Creates a new INTERVAL MINUTE.
      +
      static IntervalofMinutesNanos(long minutes, + long nanos) +
      Creates a new INTERVAL MINUTE TO SECOND.
      +
      static IntervalofMinutesSeconds(long minutes, + int seconds) +
      Creates a new INTERVAL MINUTE TO SECOND.
      +
      static IntervalofMonths(long months) +
      Creates a new INTERVAL MONTH.
      +
      static IntervalofNanos(long nanos) +
      Creates a new INTERVAL SECOND.
      +
      static IntervalofSeconds(long seconds) +
      Creates a new INTERVAL SECOND.
      +
      static IntervalofSeconds(long seconds, + int nanos) +
      Creates a new INTERVAL SECOND.
      +
      static IntervalofYears(long years) +
      Creates a new INTERVAL YEAR.
      +
      static IntervalofYearsMonths(long years, + int months) +
      Creates a new INTERVAL YEAR TO MONTH.
      +
      java.lang.StringtoString() 
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, finalize, getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        Interval

        +
        public Interval(IntervalQualifier qualifier,
        +                boolean negative,
        +                long leading,
        +                long remaining)
        +
        Creates a new interval. Do not use this constructor, use static methods + instead.
        +
        +
        Parameters:
        +
        qualifier - qualifier
        +
        negative - whether interval is negative
        +
        leading - value of leading field
        +
        remaining - combined value of all remaining fields
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        ofYears

        +
        public static Interval ofYears(long years)
        +
        Creates a new INTERVAL YEAR.
        +
        +
        Parameters:
        +
        years - years, |years|<1018
        +
        Returns:
        +
        INTERVAL YEAR
        +
        +
      • +
      + + + +
        +
      • +

        ofMonths

        +
        public static Interval ofMonths(long months)
        +
        Creates a new INTERVAL MONTH.
        +
        +
        Parameters:
        +
        months - months, |months|<1018
        +
        Returns:
        +
        INTERVAL MONTH
        +
        +
      • +
      + + + +
        +
      • +

        ofDays

        +
        public static Interval ofDays(long days)
        +
        Creates a new INTERVAL DAY.
        +
        +
        Parameters:
        +
        days - days, |days|<1018
        +
        Returns:
        +
        INTERVAL DAY
        +
        +
      • +
      + + + +
        +
      • +

        ofHours

        +
        public static Interval ofHours(long hours)
        +
        Creates a new INTERVAL HOUR.
        +
        +
        Parameters:
        +
        hours - hours, |hours|<1018
        +
        Returns:
        +
        INTERVAL HOUR
        +
        +
      • +
      + + + +
        +
      • +

        ofMinutes

        +
        public static Interval ofMinutes(long minutes)
        +
        Creates a new INTERVAL MINUTE.
        +
        +
        Parameters:
        +
        minutes - minutes, |minutes|<1018
        +
        Returns:
        +
        interval
        +
        +
      • +
      + + + +
        +
      • +

        ofSeconds

        +
        public static Interval ofSeconds(long seconds)
        +
        Creates a new INTERVAL SECOND.
        +
        +
        Parameters:
        +
        seconds - seconds, |seconds|<1018
        +
        Returns:
        +
        INTERVAL SECOND
        +
        +
      • +
      + + + +
        +
      • +

        ofSeconds

        +
        public static Interval ofSeconds(long seconds,
        +                                 int nanos)
        +
        Creates a new INTERVAL SECOND. + +

        + If both arguments are not equal to zero they should have the same sign. +

        +
        +
        Parameters:
        +
        seconds - seconds, |seconds|<1018
        +
        nanos - nanoseconds, |nanos|<1,000,000,000
        +
        Returns:
        +
        INTERVAL SECOND
        +
        +
      • +
      + + + +
        +
      • +

        ofNanos

        +
        public static Interval ofNanos(long nanos)
        +
        Creates a new INTERVAL SECOND.
        +
        +
        Parameters:
        +
        nanos - nanoseconds (including seconds)
        +
        Returns:
        +
        INTERVAL SECOND
        +
        +
      • +
      + + + +
        +
      • +

        ofYearsMonths

        +
        public static Interval ofYearsMonths(long years,
        +                                     int months)
        +
        Creates a new INTERVAL YEAR TO MONTH. + +

        + If both arguments are not equal to zero they should have the same sign. +

        +
        +
        Parameters:
        +
        years - years, |years|<1018
        +
        months - months, |months|<12
        +
        Returns:
        +
        INTERVAL YEAR TO MONTH
        +
        +
      • +
      + + + +
        +
      • +

        ofDaysHours

        +
        public static Interval ofDaysHours(long days,
        +                                   int hours)
        +
        Creates a new INTERVAL DAY TO HOUR. + +

        + If both arguments are not equal to zero they should have the same sign. +

        +
        +
        Parameters:
        +
        days - days, |days|<1018
        +
        hours - hours, |hours|<24
        +
        Returns:
        +
        INTERVAL DAY TO HOUR
        +
        +
      • +
      + + + +
        +
      • +

        ofDaysHoursMinutes

        +
        public static Interval ofDaysHoursMinutes(long days,
        +                                          int hours,
        +                                          int minutes)
        +
        Creates a new INTERVAL DAY TO MINUTE. + +

        + Non-zero arguments should have the same sign. +

        +
        +
        Parameters:
        +
        days - days, |days|<1018
        +
        hours - hours, |hours|<24
        +
        minutes - minutes, |minutes|<60
        +
        Returns:
        +
        INTERVAL DAY TO MINUTE
        +
        +
      • +
      + + + +
        +
      • +

        ofDaysHoursMinutesSeconds

        +
        public static Interval ofDaysHoursMinutesSeconds(long days,
        +                                                 int hours,
        +                                                 int minutes,
        +                                                 int seconds)
        +
        Creates a new INTERVAL DAY TO SECOND. + +

        + Non-zero arguments should have the same sign. +

        +
        +
        Parameters:
        +
        days - days, |days|<1018
        +
        hours - hours, |hours|<24
        +
        minutes - minutes, |minutes|<60
        +
        seconds - seconds, |seconds|<60
        +
        Returns:
        +
        INTERVAL DAY TO SECOND
        +
        +
      • +
      + + + +
        +
      • +

        ofDaysHoursMinutesNanos

        +
        public static Interval ofDaysHoursMinutesNanos(long days,
        +                                               int hours,
        +                                               int minutes,
        +                                               long nanos)
        +
        Creates a new INTERVAL DAY TO SECOND. + +

        + Non-zero arguments should have the same sign. +

        +
        +
        Parameters:
        +
        days - days, |days|<1018
        +
        hours - hours, |hours|<24
        +
        minutes - minutes, |minutes|<60
        +
        nanos - nanoseconds, |nanos|<60,000,000,000
        +
        Returns:
        +
        INTERVAL DAY TO SECOND
        +
        +
      • +
      + + + +
        +
      • +

        ofHoursMinutes

        +
        public static Interval ofHoursMinutes(long hours,
        +                                      int minutes)
        +
        Creates a new INTERVAL HOUR TO MINUTE. + +

        + If both arguments are not equal to zero they should have the same sign. +

        +
        +
        Parameters:
        +
        hours - hours, |hours|<1018
        +
        minutes - minutes, |minutes|<60
        +
        Returns:
        +
        INTERVAL HOUR TO MINUTE
        +
        +
      • +
      + + + +
        +
      • +

        ofHoursMinutesSeconds

        +
        public static Interval ofHoursMinutesSeconds(long hours,
        +                                             int minutes,
        +                                             int seconds)
        +
        Creates a new INTERVAL HOUR TO SECOND. + +

        + Non-zero arguments should have the same sign. +

        +
        +
        Parameters:
        +
        hours - hours, |hours|<1018
        +
        minutes - minutes, |minutes|<60
        +
        seconds - seconds, |seconds|<60
        +
        Returns:
        +
        INTERVAL HOUR TO SECOND
        +
        +
      • +
      + + + +
        +
      • +

        ofHoursMinutesNanos

        +
        public static Interval ofHoursMinutesNanos(long hours,
        +                                           int minutes,
        +                                           long nanos)
        +
        Creates a new INTERVAL HOUR TO SECOND. + +

        + Non-zero arguments should have the same sign. +

        +
        +
        Parameters:
        +
        hours - hours, |hours|<1018
        +
        minutes - minutes, |minutes|<60
        +
        nanos - nanoseconds, |seconds|<60,000,000,000
        +
        Returns:
        +
        INTERVAL HOUR TO SECOND
        +
        +
      • +
      + + + +
        +
      • +

        ofMinutesSeconds

        +
        public static Interval ofMinutesSeconds(long minutes,
        +                                        int seconds)
        +
        Creates a new INTERVAL MINUTE TO SECOND. + +

        + If both arguments are not equal to zero they should have the same sign. +

        +
        +
        Parameters:
        +
        minutes - minutes, |minutes|<1018
        +
        seconds - seconds, |seconds|<60
        +
        Returns:
        +
        INTERVAL MINUTE TO SECOND
        +
        +
      • +
      + + + +
        +
      • +

        ofMinutesNanos

        +
        public static Interval ofMinutesNanos(long minutes,
        +                                      long nanos)
        +
        Creates a new INTERVAL MINUTE TO SECOND. + +

        + If both arguments are not equal to zero they should have the same sign. +

        +
        +
        Parameters:
        +
        minutes - minutes, |minutes|<1018
        +
        nanos - nanoseconds, |nanos|<60,000,000,000
        +
        Returns:
        +
        INTERVAL MINUTE TO SECOND
        +
        +
      • +
      + + + +
        +
      • +

        getQualifier

        +
        public IntervalQualifier getQualifier()
        +
        Returns qualifier of this interval.
        +
        +
        Returns:
        +
        qualifier
        +
        +
      • +
      + + + +
        +
      • +

        isNegative

        +
        public boolean isNegative()
        +
        Returns where the interval is negative.
        +
        +
        Returns:
        +
        where the interval is negative
        +
        +
      • +
      + + + +
        +
      • +

        getLeading

        +
        public long getLeading()
        +
        Returns value of leading field of this interval. For SECOND + intervals returns integer part of seconds.
        +
        +
        Returns:
        +
        value of leading field
        +
        +
      • +
      + + + +
        +
      • +

        getRemaining

        +
        public long getRemaining()
        +
        Returns combined value of remaining fields of this interval. For + SECOND intervals returns nanoseconds.
        +
        +
        Returns:
        +
        combined value of remaining fields
        +
        +
      • +
      + + + +
        +
      • +

        getYears

        +
        public long getYears()
        +
        Returns years value, if any.
        +
        +
        Returns:
        +
        years, or 0
        +
        +
      • +
      + + + +
        +
      • +

        getMonths

        +
        public long getMonths()
        +
        Returns months value, if any.
        +
        +
        Returns:
        +
        months, or 0
        +
        +
      • +
      + + + +
        +
      • +

        getDays

        +
        public long getDays()
        +
        Returns days value, if any.
        +
        +
        Returns:
        +
        days, or 0
        +
        +
      • +
      + + + +
        +
      • +

        getHours

        +
        public long getHours()
        +
        Returns hours value, if any.
        +
        +
        Returns:
        +
        hours, or 0
        +
        +
      • +
      + + + +
        +
      • +

        getMinutes

        +
        public long getMinutes()
        +
        Returns minutes value, if any.
        +
        +
        Returns:
        +
        minutes, or 0
        +
        +
      • +
      + + + +
        +
      • +

        getSeconds

        +
        public long getSeconds()
        +
        Returns value of integer part of seconds, if any.
        +
        +
        Returns:
        +
        seconds, or 0
        +
        +
      • +
      + + + +
        +
      • +

        getNanosOfSecond

        +
        public long getNanosOfSecond()
        +
        Returns value of fractional part of seconds (in nanoseconds), if any.
        +
        +
        Returns:
        +
        nanoseconds, or 0
        +
        +
      • +
      + + + +
        +
      • +

        getSecondsAndNanos

        +
        public long getSecondsAndNanos()
        +
        Returns seconds value measured in nanoseconds, if any. + +

        + This method returns a long value that cannot fit all possible values of + INTERVAL SECOND. For a very large intervals of this type use + getSeconds() and getNanosOfSecond() instead. This + method can be safely used for intervals of other day-time types. +

        +
        +
        Returns:
        +
        nanoseconds (including seconds), or 0
        +
        +
      • +
      + + + +
        +
      • +

        hashCode

        +
        public int hashCode()
        +
        +
        Overrides:
        +
        hashCode in class java.lang.Object
        +
        +
      • +
      + + + +
        +
      • +

        equals

        +
        public boolean equals(java.lang.Object obj)
        +
        +
        Overrides:
        +
        equals in class java.lang.Object
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        +
        Overrides:
        +
        toString in class java.lang.Object
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/api/IntervalQualifier.html b/h2/docs/javadoc/org/h2/api/IntervalQualifier.html new file mode 100644 index 0000000..8dd793f --- /dev/null +++ b/h2/docs/javadoc/org/h2/api/IntervalQualifier.html @@ -0,0 +1,786 @@ + + + + + +IntervalQualifier + + + + + + + + + + + + +
+
org.h2.api
+

Enum IntervalQualifier

+
+
+
    +
  • java.lang.Object
  • +
  • + +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.lang.Comparable<IntervalQualifier>
    +
    +
    +
    +
    public enum IntervalQualifier
    +extends java.lang.Enum<IntervalQualifier>
    +
    Interval qualifier.
    +
  • +
+
+
+
    +
  • + + + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      java.lang.StringgetTypeName(int precision, + int scale) +
      Returns full type name.
      +
      java.lang.StringBuildergetTypeName(java.lang.StringBuilder builder, + int precision, + int scale, + boolean qualifierOnly) +
      Appends full type name to the specified string builder.
      +
      booleanhasDays() +
      Returns whether interval with this qualifier has days.
      +
      booleanhasHours() +
      Returns whether interval with this qualifier has hours.
      +
      booleanhasMinutes() +
      Returns whether interval with this qualifier has minutes.
      +
      booleanhasMonths() +
      Returns whether interval with this qualifier has months.
      +
      booleanhasMultipleFields() +
      Returns whether interval with this qualifier has multiple fields.
      +
      booleanhasSeconds() +
      Returns whether interval with this qualifier has seconds.
      +
      booleanhasYears() +
      Returns whether interval with this qualifier has years.
      +
      booleanisDayTime() +
      Returns whether interval with this qualifier is a day-time interval.
      +
      booleanisYearMonth() +
      Returns whether interval with this qualifier is a year-month interval.
      +
      java.lang.StringtoString() 
      static IntervalQualifiervalueOf(int ordinal) +
      Returns the interval qualifier with the specified ordinal value.
      +
      static IntervalQualifiervalueOf(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static IntervalQualifier[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + + + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static IntervalQualifier[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (IntervalQualifier c : IntervalQualifier.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static IntervalQualifier valueOf(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static IntervalQualifier valueOf(int ordinal)
        +
        Returns the interval qualifier with the specified ordinal value.
        +
        +
        Parameters:
        +
        ordinal - Java ordinal value (0-based)
        +
        Returns:
        +
        interval qualifier with the specified ordinal value
        +
        +
      • +
      + + + +
        +
      • +

        isYearMonth

        +
        public boolean isYearMonth()
        +
        Returns whether interval with this qualifier is a year-month interval.
        +
        +
        Returns:
        +
        whether interval with this qualifier is a year-month interval
        +
        +
      • +
      + + + +
        +
      • +

        isDayTime

        +
        public boolean isDayTime()
        +
        Returns whether interval with this qualifier is a day-time interval.
        +
        +
        Returns:
        +
        whether interval with this qualifier is a day-time interval
        +
        +
      • +
      + + + +
        +
      • +

        hasYears

        +
        public boolean hasYears()
        +
        Returns whether interval with this qualifier has years.
        +
        +
        Returns:
        +
        whether interval with this qualifier has years
        +
        +
      • +
      + + + +
        +
      • +

        hasMonths

        +
        public boolean hasMonths()
        +
        Returns whether interval with this qualifier has months.
        +
        +
        Returns:
        +
        whether interval with this qualifier has months
        +
        +
      • +
      + + + +
        +
      • +

        hasDays

        +
        public boolean hasDays()
        +
        Returns whether interval with this qualifier has days.
        +
        +
        Returns:
        +
        whether interval with this qualifier has days
        +
        +
      • +
      + + + +
        +
      • +

        hasHours

        +
        public boolean hasHours()
        +
        Returns whether interval with this qualifier has hours.
        +
        +
        Returns:
        +
        whether interval with this qualifier has hours
        +
        +
      • +
      + + + +
        +
      • +

        hasMinutes

        +
        public boolean hasMinutes()
        +
        Returns whether interval with this qualifier has minutes.
        +
        +
        Returns:
        +
        whether interval with this qualifier has minutes
        +
        +
      • +
      + + + +
        +
      • +

        hasSeconds

        +
        public boolean hasSeconds()
        +
        Returns whether interval with this qualifier has seconds.
        +
        +
        Returns:
        +
        whether interval with this qualifier has seconds
        +
        +
      • +
      + + + +
        +
      • +

        hasMultipleFields

        +
        public boolean hasMultipleFields()
        +
        Returns whether interval with this qualifier has multiple fields.
        +
        +
        Returns:
        +
        whether interval with this qualifier has multiple fields
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        +
        Overrides:
        +
        toString in class java.lang.Enum<IntervalQualifier>
        +
        +
      • +
      + + + +
        +
      • +

        getTypeName

        +
        public java.lang.String getTypeName(int precision,
        +                                    int scale)
        +
        Returns full type name.
        +
        +
        Parameters:
        +
        precision - precision, or -1
        +
        scale - fractional seconds precision, or -1
        +
        Returns:
        +
        full type name
        +
        +
      • +
      + + + +
        +
      • +

        getTypeName

        +
        public java.lang.StringBuilder getTypeName(java.lang.StringBuilder builder,
        +                                           int precision,
        +                                           int scale,
        +                                           boolean qualifierOnly)
        +
        Appends full type name to the specified string builder.
        +
        +
        Parameters:
        +
        builder - string builder
        +
        precision - precision, or -1
        +
        scale - fractional seconds precision, or -1
        +
        qualifierOnly - if true, don't add the INTERVAL prefix
        +
        Returns:
        +
        the specified string builder
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/api/JavaObjectSerializer.html b/h2/docs/javadoc/org/h2/api/JavaObjectSerializer.html new file mode 100644 index 0000000..6ad0e7f --- /dev/null +++ b/h2/docs/javadoc/org/h2/api/JavaObjectSerializer.html @@ -0,0 +1,258 @@ + + + + + +JavaObjectSerializer + + + + + + + + + + + + +
+
org.h2.api
+

Interface JavaObjectSerializer

+
+
+
+
    +
  • +
    +
    +
    public interface JavaObjectSerializer
    +
    Custom serialization mechanism for java objects being stored in column of + type OTHER.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + +
      All Methods Instance Methods Abstract Methods 
      Modifier and TypeMethod and Description
      java.lang.Objectdeserialize(byte[] bytes) +
      Deserialize object from byte array.
      +
      byte[]serialize(java.lang.Object obj) +
      Serialize object to byte array.
      +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        serialize

        +
        byte[] serialize(java.lang.Object obj)
        +          throws java.lang.Exception
        +
        Serialize object to byte array.
        +
        +
        Parameters:
        +
        obj - the object to serialize
        +
        Returns:
        +
        the byte array of the serialized object
        +
        Throws:
        +
        java.lang.Exception - on failure
        +
        +
      • +
      + + + +
        +
      • +

        deserialize

        +
        java.lang.Object deserialize(byte[] bytes)
        +                      throws java.lang.Exception
        +
        Deserialize object from byte array.
        +
        +
        Parameters:
        +
        bytes - the byte array of the serialized object
        +
        Returns:
        +
        the object
        +
        Throws:
        +
        java.lang.Exception - on failure
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/api/TableEngine.html b/h2/docs/javadoc/org/h2/api/TableEngine.html new file mode 100644 index 0000000..4cc9f22 --- /dev/null +++ b/h2/docs/javadoc/org/h2/api/TableEngine.html @@ -0,0 +1,230 @@ + + + + + +TableEngine + + + + + + + + + + + + +
+
org.h2.api
+

Interface TableEngine

+
+
+
+
    +
  • +
    +
    +
    public interface TableEngine
    +
    A class that implements this interface can create custom table + implementations.
    +
  • +
+
+
+
    +
  • + + +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        createTable

        +
        org.h2.table.Table createTable(org.h2.command.ddl.CreateTableData data)
        +
        Create new table.
        +
        +
        Parameters:
        +
        data - the data to construct the table
        +
        Returns:
        +
        the created table
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/api/Trigger.html b/h2/docs/javadoc/org/h2/api/Trigger.html new file mode 100644 index 0000000..4de3a1a --- /dev/null +++ b/h2/docs/javadoc/org/h2/api/Trigger.html @@ -0,0 +1,447 @@ + + + + + +Trigger + + + + + + + + + + + + +
+
org.h2.api
+

Interface Trigger

+
+
+
+ +
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      + + + + + + + + + + + + + + + + + + + + + + +
      Fields 
      Modifier and TypeField and Description
      static intDELETE +
      The trigger is called for DELETE statements.
      +
      static intINSERT +
      The trigger is called for INSERT statements.
      +
      static intSELECT +
      The trigger is called for SELECT statements.
      +
      static intUPDATE +
      The trigger is called for UPDATE statements.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Abstract Methods Default Methods 
      Modifier and TypeMethod and Description
      default voidclose() +
      This method is called when the database is closed.
      +
      voidfire(java.sql.Connection conn, + java.lang.Object[] oldRow, + java.lang.Object[] newRow) +
      This method is called for each triggered action.
      +
      default voidinit(java.sql.Connection conn, + java.lang.String schemaName, + java.lang.String triggerName, + java.lang.String tableName, + boolean before, + int type) +
      This method is called by the database engine once when initializing the + trigger.
      +
      default voidremove() +
      This method is called when the trigger is dropped.
      +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Detail

      + + + +
        +
      • +

        INSERT

        +
        static final int INSERT
        +
        The trigger is called for INSERT statements.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        UPDATE

        +
        static final int UPDATE
        +
        The trigger is called for UPDATE statements.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DELETE

        +
        static final int DELETE
        +
        The trigger is called for DELETE statements.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SELECT

        +
        static final int SELECT
        +
        The trigger is called for SELECT statements.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        init

        +
        default void init(java.sql.Connection conn,
        +                  java.lang.String schemaName,
        +                  java.lang.String triggerName,
        +                  java.lang.String tableName,
        +                  boolean before,
        +                  int type)
        +           throws java.sql.SQLException
        +
        This method is called by the database engine once when initializing the + trigger. It is called when the trigger is created, as well as when the + database is opened. The type of operation is a bit field with the + appropriate flags set. As an example, if the trigger is of type INSERT + and UPDATE, then the parameter type is set to (INSERT | UPDATE).
        +
        +
        Parameters:
        +
        conn - a connection to the database (a system connection)
        +
        schemaName - the name of the schema
        +
        triggerName - the name of the trigger used in the CREATE TRIGGER + statement
        +
        tableName - the name of the table
        +
        before - whether the fire method is called before or after the + operation is performed
        +
        type - the operation type: INSERT, UPDATE, DELETE, SELECT, or a + combination (this parameter is a bit field)
        +
        Throws:
        +
        java.sql.SQLException - on SQL exception
        +
        +
      • +
      + + + +
        +
      • +

        fire

        +
        void fire(java.sql.Connection conn,
        +          java.lang.Object[] oldRow,
        +          java.lang.Object[] newRow)
        +   throws java.sql.SQLException
        +
        This method is called for each triggered action. The method is called + immediately when the operation occurred (before it is committed). A + transaction rollback will also rollback the operations that were done + within the trigger, if the operations occurred within the same database. + If the trigger changes state outside the database, a rollback trigger + should be used. +

        + The row arrays contain all columns of the table, in the same order + as defined in the table. +

        +

        + The trigger itself may change the data in the newRow array. +

        +
        +
        Parameters:
        +
        conn - a connection to the database
        +
        oldRow - the old row, or null if no old row is available (for + INSERT)
        +
        newRow - the new row, or null if no new row is available (for + DELETE)
        +
        Throws:
        +
        java.sql.SQLException - if the operation must be undone
        +
        +
      • +
      + + + +
        +
      • +

        close

        +
        default void close()
        +            throws java.sql.SQLException
        +
        This method is called when the database is closed. + If the method throws an exception, it will be logged, but + closing the database will continue.
        +
        +
        Throws:
        +
        java.sql.SQLException - on SQL exception
        +
        +
      • +
      + + + +
        +
      • +

        remove

        +
        default void remove()
        +             throws java.sql.SQLException
        +
        This method is called when the trigger is dropped.
        +
        +
        Throws:
        +
        java.sql.SQLException - on SQL exception
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/api/UserToRolesMapper.html b/h2/docs/javadoc/org/h2/api/UserToRolesMapper.html new file mode 100644 index 0000000..6e66a49 --- /dev/null +++ b/h2/docs/javadoc/org/h2/api/UserToRolesMapper.html @@ -0,0 +1,248 @@ + + + + + +UserToRolesMapper + + + + + + + + + + + + +
+
org.h2.api
+

Interface UserToRolesMapper

+
+
+
+
    +
  • +
    +
    All Superinterfaces:
    +
    org.h2.security.auth.Configurable
    +
    +
    +
    +
    public interface UserToRolesMapper
    +extends org.h2.security.auth.Configurable
    +
    A class that implement this interface can be used during authentication to + map external users to database roles. +

    + This feature is experimental and subject to change +

    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + +
      All Methods Instance Methods Abstract Methods 
      Modifier and TypeMethod and Description
      java.util.Collection<java.lang.String>mapUserToRoles(org.h2.security.auth.AuthenticationInfo authenticationInfo) +
      Map user identified by authentication info to a set of granted roles.
      +
      +
        +
      • + + +

        Methods inherited from interface org.h2.security.auth.Configurable

        +configure
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        mapUserToRoles

        +
        java.util.Collection<java.lang.String> mapUserToRoles(org.h2.security.auth.AuthenticationInfo authenticationInfo)
        +                                               throws org.h2.security.auth.AuthenticationException
        +
        Map user identified by authentication info to a set of granted roles.
        +
        +
        Parameters:
        +
        authenticationInfo - authentication information
        +
        Returns:
        +
        list of roles to be assigned to the user temporary
        +
        Throws:
        +
        org.h2.security.auth.AuthenticationException - on authentication exception
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/api/package-frame.html b/h2/docs/javadoc/org/h2/api/package-frame.html new file mode 100644 index 0000000..2c35395 --- /dev/null +++ b/h2/docs/javadoc/org/h2/api/package-frame.html @@ -0,0 +1,37 @@ + + + + + +org.h2.api + + + + + +

org.h2.api

+ + + diff --git a/h2/docs/javadoc/org/h2/api/package-summary.html b/h2/docs/javadoc/org/h2/api/package-summary.html new file mode 100644 index 0000000..21bfd5b --- /dev/null +++ b/h2/docs/javadoc/org/h2/api/package-summary.html @@ -0,0 +1,250 @@ + + + + + +org.h2.api + + + + + + + + + + + +
+

Package org.h2.api

+
+
+ +Contains interfaces for user-defined extensions, such as triggers and user-defined aggregate functions.
+
+

See: Description

+
+
+
    +
  • + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Interface Summary 
    InterfaceDescription
    Aggregate +
    A user-defined aggregate function needs to implement this interface.
    +
    AggregateFunction +
    A user-defined aggregate function needs to implement this interface.
    +
    CredentialsValidator +
    A class that implement this interface can be used to validate credentials + provided by client.
    +
    DatabaseEventListener +
    A class that implements this interface can get notified about exceptions + and other events.
    +
    JavaObjectSerializer +
    Custom serialization mechanism for java objects being stored in column of + type OTHER.
    +
    TableEngine +
    A class that implements this interface can create custom table + implementations.
    +
    Trigger +
    A class that implements this interface can be used as a trigger.
    +
    UserToRolesMapper +
    A class that implement this interface can be used during authentication to + map external users to database roles.
    +
    +
  • +
  • + + + + + + + + + + + + + + + + + + + + +
    Class Summary 
    ClassDescription
    ErrorCode +
    This class defines the error codes used for SQL exceptions.
    +
    H2Type +
    Data types of H2.
    +
    Interval +
    INTERVAL representation for result sets.
    +
    +
  • +
  • + + + + + + + + + + + + +
    Enum Summary 
    EnumDescription
    IntervalQualifier +
    Interval qualifier.
    +
    +
  • +
+ + + +

Package org.h2.api Description

+

+ +Contains interfaces for user-defined extensions, such as triggers and user-defined aggregate functions. + +

+
+ + + + + + diff --git a/h2/docs/javadoc/org/h2/api/package-tree.html b/h2/docs/javadoc/org/h2/api/package-tree.html new file mode 100644 index 0000000..33a40a5 --- /dev/null +++ b/h2/docs/javadoc/org/h2/api/package-tree.html @@ -0,0 +1,168 @@ + + + + + +org.h2.api Class Hierarchy + + + + + + + + + + + +
+

Hierarchy For Package org.h2.api

+Package Hierarchies: + +
+
+

Class Hierarchy

+
    +
  • java.lang.Object + +
  • +
+

Interface Hierarchy

+ +

Enum Hierarchy

+
    +
  • java.lang.Object +
      +
    • java.lang.Enum<E> (implements java.lang.Comparable<T>, java.io.Serializable) + +
    • +
    +
  • +
+
+ + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/CastDataProvider.html b/h2/docs/javadoc/org/h2/engine/CastDataProvider.html new file mode 100644 index 0000000..2e5910e --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/CastDataProvider.html @@ -0,0 +1,312 @@ + + + + + +CastDataProvider + + + + + + + + + + + + +
+
org.h2.engine
+

Interface CastDataProvider

+
+
+
+ +
+
+ +
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        currentTimestamp

        +
        org.h2.value.ValueTimestampTimeZone currentTimestamp()
        +
        Returns the current timestamp with maximum resolution. The value must be + the same within a transaction or within execution of a command.
        +
        +
        Returns:
        +
        the current timestamp for CURRENT_TIMESTAMP(9)
        +
        +
      • +
      + + + +
        +
      • +

        currentTimeZone

        +
        org.h2.util.TimeZoneProvider currentTimeZone()
        +
        Returns the current time zone.
        +
        +
        Returns:
        +
        the current time zone
        +
        +
      • +
      + + + +
        +
      • +

        getMode

        +
        Mode getMode()
        +
        Returns the database mode.
        +
        +
        Returns:
        +
        the database mode
        +
        +
      • +
      + + + +
        +
      • +

        getJavaObjectSerializer

        +
        JavaObjectSerializer getJavaObjectSerializer()
        +
        Returns the custom Java object serializer, or null.
        +
        +
        Returns:
        +
        the custom Java object serializer, or null
        +
        +
      • +
      + + + +
        +
      • +

        zeroBasedEnums

        +
        boolean zeroBasedEnums()
        +
        Returns are ENUM values 0-based.
        +
        +
        Returns:
        +
        are ENUM values 0-based
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/Comment.html b/h2/docs/javadoc/org/h2/engine/Comment.html new file mode 100644 index 0000000..b36cfe5 --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/Comment.html @@ -0,0 +1,446 @@ + + + + + +Comment + + + + + + + + + + + + +
+
org.h2.engine
+

Class Comment

+
+
+ +
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    org.h2.util.HasSQL
    +
    +
    +
    +
    public final class Comment
    +extends DbObject
    +
    Represents a database object comment.
    +
  • +
+
+
+ +
+
+
    +
  • + + + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getCreateSQLForCopy

        +
        public java.lang.String getCreateSQLForCopy(org.h2.table.Table table,
        +                                            java.lang.String quotedName)
        +
        Description copied from class: DbObject
        +
        Build a SQL statement to re-create the object, or to create a copy of the + object with a different name or referencing a different table
        +
        +
        Specified by:
        +
        getCreateSQLForCopy in class DbObject
        +
        Parameters:
        +
        table - the new table
        +
        quotedName - the quoted name
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        getCreateSQL

        +
        public java.lang.String getCreateSQL()
        +
        Description copied from class: DbObject
        +
        Construct the CREATE ... SQL statement for this object.
        +
        +
        Specified by:
        +
        getCreateSQL in class DbObject
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        getType

        +
        public int getType()
        +
        Description copied from class: DbObject
        +
        Get the object type.
        +
        +
        Specified by:
        +
        getType in class DbObject
        +
        Returns:
        +
        the object type
        +
        +
      • +
      + + + +
        +
      • +

        removeChildrenAndResources

        +
        public void removeChildrenAndResources(SessionLocal session)
        +
        Description copied from class: DbObject
        +
        Delete all dependent children objects and resources of this object.
        +
        +
        Specified by:
        +
        removeChildrenAndResources in class DbObject
        +
        Parameters:
        +
        session - the session
        +
        +
      • +
      + + + +
        +
      • +

        checkRename

        +
        public void checkRename()
        +
        Description copied from class: DbObject
        +
        Check if renaming is allowed. Does nothing when allowed.
        +
        +
        Overrides:
        +
        checkRename in class DbObject
        +
        +
      • +
      + + + +
        +
      • +

        setCommentText

        +
        public void setCommentText(java.lang.String comment)
        +
        Set the comment text.
        +
        +
        Parameters:
        +
        comment - the text
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/ConnectionInfo.html b/h2/docs/javadoc/org/h2/engine/ConnectionInfo.html new file mode 100644 index 0000000..375a8e0 --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/ConnectionInfo.html @@ -0,0 +1,807 @@ + + + + + +ConnectionInfo + + + + + + + + + + + + +
+
org.h2.engine
+

Class ConnectionInfo

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.engine.ConnectionInfo
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.lang.Cloneable
    +
    +
    +
    +
    public class ConnectionInfo
    +extends java.lang.Object
    +implements java.lang.Cloneable
    +
    Encapsulates the connection settings, including user name and password.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + + + + +
      Constructors 
      Constructor and Description
      ConnectionInfo(java.lang.String name) +
      Create a connection info object.
      +
      ConnectionInfo(java.lang.String u, + java.util.Properties info, + java.lang.String user, + java.lang.Object password) +
      Create a connection info object.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      voidcleanAuthenticationInfo() +
      Clear authentication properties.
      +
      ConnectionInfoclone() 
      DbSettingsgetDbSettings() 
      byte[]getFilePasswordHash() +
      Get the file password hash if it is set.
      +
      java.lang.StringgetName() +
      Get the unique and normalized database name (excluding settings).
      +
      org.h2.util.NetworkConnectionInfogetNetworkConnectionInfo() +
      Returns the network connection information, or null.
      +
      java.lang.StringgetOriginalURL() +
      Get the complete original database URL.
      +
      booleangetProperty(java.lang.String key, + boolean defaultValue) +
      Get a boolean property if it is set and return the value.
      +
      java.lang.StringgetProperty(java.lang.String key, + java.lang.String defaultValue) +
      Get the value of the given property.
      +
      org.h2.util.TimeZoneProvidergetTimeZone() +
      Returns the time zone.
      +
      java.lang.StringgetURL() +
      Get the database URL.
      +
      java.lang.StringgetUserName() +
      Get the name of the user.
      +
      static booleanisIgnoredByParser(java.lang.String name) +
      Returns whether setting with the specified name should be ignored by + parser.
      +
      booleanisPersistent() +
      Check if the referenced database is persistent.
      +
      booleanisRemote() +
      Check if this is a remote connection.
      +
      booleanremoveProperty(java.lang.String key, + boolean defaultValue) +
      Remove a boolean property if it is set and return the value.
      +
      voidsetBaseDir(java.lang.String dir) +
      Set the base directory of persistent databases, unless the database is in + the user home folder (~).
      +
      voidsetFileEncryptionKey(byte[] key) 
      voidsetFilePasswordHash(byte[] hash) +
      Set the file password hash.
      +
      voidsetNetworkConnectionInfo(org.h2.util.NetworkConnectionInfo networkConnectionInfo) +
      Sets the network connection information.
      +
      voidsetOriginalURL(java.lang.String url) +
      Set the original database URL.
      +
      voidsetProperty(java.lang.String key, + java.lang.String value) +
      Overwrite a property.
      +
      voidsetServerKey(java.lang.String serverKey) +
      Switch to server mode, and set the server name and database key.
      +
      voidsetUserName(java.lang.String name) +
      Overwrite the user name.
      +
      voidsetUserPasswordHash(byte[] hash) +
      Set the user password hash.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        ConnectionInfo

        +
        public ConnectionInfo(java.lang.String name)
        +
        Create a connection info object.
        +
        +
        Parameters:
        +
        name - the database name (including tags), but without the + "jdbc:h2:" prefix
        +
        +
      • +
      + + + +
        +
      • +

        ConnectionInfo

        +
        public ConnectionInfo(java.lang.String u,
        +                      java.util.Properties info,
        +                      java.lang.String user,
        +                      java.lang.Object password)
        +
        Create a connection info object.
        +
        +
        Parameters:
        +
        u - the database URL (must start with jdbc:h2:)
        +
        info - the connection properties or null
        +
        user - the user name or null
        +
        password - the password as String or char[], or + null
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        isIgnoredByParser

        +
        public static boolean isIgnoredByParser(java.lang.String name)
        +
        Returns whether setting with the specified name should be ignored by + parser.
        +
        +
        Parameters:
        +
        name - the name of the setting
        +
        Returns:
        +
        whether setting with the specified name should be ignored by + parser
        +
        +
      • +
      + + + +
        +
      • +

        clone

        +
        public ConnectionInfo clone()
        +                     throws java.lang.CloneNotSupportedException
        +
        +
        Overrides:
        +
        clone in class java.lang.Object
        +
        Throws:
        +
        java.lang.CloneNotSupportedException
        +
        +
      • +
      + + + +
        +
      • +

        setBaseDir

        +
        public void setBaseDir(java.lang.String dir)
        +
        Set the base directory of persistent databases, unless the database is in + the user home folder (~).
        +
        +
        Parameters:
        +
        dir - the new base directory
        +
        +
      • +
      + + + +
        +
      • +

        isRemote

        +
        public boolean isRemote()
        +
        Check if this is a remote connection.
        +
        +
        Returns:
        +
        true if it is
        +
        +
      • +
      + + + +
        +
      • +

        isPersistent

        +
        public boolean isPersistent()
        +
        Check if the referenced database is persistent.
        +
        +
        Returns:
        +
        true if it is
        +
        +
      • +
      + + + +
        +
      • +

        getProperty

        +
        public boolean getProperty(java.lang.String key,
        +                           boolean defaultValue)
        +
        Get a boolean property if it is set and return the value.
        +
        +
        Parameters:
        +
        key - the property name
        +
        defaultValue - the default value
        +
        Returns:
        +
        the value
        +
        +
      • +
      + + + +
        +
      • +

        removeProperty

        +
        public boolean removeProperty(java.lang.String key,
        +                              boolean defaultValue)
        +
        Remove a boolean property if it is set and return the value.
        +
        +
        Parameters:
        +
        key - the property name
        +
        defaultValue - the default value
        +
        Returns:
        +
        the value
        +
        +
      • +
      + + + +
        +
      • +

        getName

        +
        public java.lang.String getName()
        +
        Get the unique and normalized database name (excluding settings).
        +
        +
        Returns:
        +
        the database name
        +
        +
      • +
      + + + +
        +
      • +

        getFilePasswordHash

        +
        public byte[] getFilePasswordHash()
        +
        Get the file password hash if it is set.
        +
        +
        Returns:
        +
        the password hash or null
        +
        +
      • +
      + + + +
        +
      • +

        getUserName

        +
        public java.lang.String getUserName()
        +
        Get the name of the user.
        +
        +
        Returns:
        +
        the user name
        +
        +
      • +
      + + + +
        +
      • +

        getProperty

        +
        public java.lang.String getProperty(java.lang.String key,
        +                                    java.lang.String defaultValue)
        +
        Get the value of the given property.
        +
        +
        Parameters:
        +
        key - the property key
        +
        defaultValue - the default value
        +
        Returns:
        +
        the value as a String
        +
        +
      • +
      + + + +
        +
      • +

        setUserName

        +
        public void setUserName(java.lang.String name)
        +
        Overwrite the user name. The user name is case-insensitive and stored in + uppercase. English conversion is used.
        +
        +
        Parameters:
        +
        name - the user name
        +
        +
      • +
      + + + +
        +
      • +

        setUserPasswordHash

        +
        public void setUserPasswordHash(byte[] hash)
        +
        Set the user password hash.
        +
        +
        Parameters:
        +
        hash - the new hash value
        +
        +
      • +
      + + + +
        +
      • +

        setFilePasswordHash

        +
        public void setFilePasswordHash(byte[] hash)
        +
        Set the file password hash.
        +
        +
        Parameters:
        +
        hash - the new hash value
        +
        +
      • +
      + + + +
        +
      • +

        setFileEncryptionKey

        +
        public void setFileEncryptionKey(byte[] key)
        +
      • +
      + + + +
        +
      • +

        setProperty

        +
        public void setProperty(java.lang.String key,
        +                        java.lang.String value)
        +
        Overwrite a property.
        +
        +
        Parameters:
        +
        key - the property name
        +
        value - the value
        +
        +
      • +
      + + + +
        +
      • +

        getURL

        +
        public java.lang.String getURL()
        +
        Get the database URL.
        +
        +
        Returns:
        +
        the URL
        +
        +
      • +
      + + + +
        +
      • +

        getOriginalURL

        +
        public java.lang.String getOriginalURL()
        +
        Get the complete original database URL.
        +
        +
        Returns:
        +
        the database URL
        +
        +
      • +
      + + + +
        +
      • +

        setOriginalURL

        +
        public void setOriginalURL(java.lang.String url)
        +
        Set the original database URL.
        +
        +
        Parameters:
        +
        url - the database url
        +
        +
      • +
      + + + +
        +
      • +

        getTimeZone

        +
        public org.h2.util.TimeZoneProvider getTimeZone()
        +
        Returns the time zone.
        +
        +
        Returns:
        +
        the time zone
        +
        +
      • +
      + + + +
        +
      • +

        setServerKey

        +
        public void setServerKey(java.lang.String serverKey)
        +
        Switch to server mode, and set the server name and database key.
        +
        +
        Parameters:
        +
        serverKey - the server name, '/', and the security key
        +
        +
      • +
      + + + +
        +
      • +

        getNetworkConnectionInfo

        +
        public org.h2.util.NetworkConnectionInfo getNetworkConnectionInfo()
        +
        Returns the network connection information, or null.
        +
        +
        Returns:
        +
        the network connection information, or null
        +
        +
      • +
      + + + +
        +
      • +

        setNetworkConnectionInfo

        +
        public void setNetworkConnectionInfo(org.h2.util.NetworkConnectionInfo networkConnectionInfo)
        +
        Sets the network connection information.
        +
        +
        Parameters:
        +
        networkConnectionInfo - the network connection information
        +
        +
      • +
      + + + +
        +
      • +

        getDbSettings

        +
        public DbSettings getDbSettings()
        +
      • +
      + + + +
        +
      • +

        cleanAuthenticationInfo

        +
        public void cleanAuthenticationInfo()
        +
        Clear authentication properties.
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/Constants.html b/h2/docs/javadoc/org/h2/engine/Constants.html new file mode 100644 index 0000000..9ef567d --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/Constants.html @@ -0,0 +1,2002 @@ + + + + + +Constants + + + + + + + + + + + + +
+
org.h2.engine
+

Class Constants

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.engine.Constants
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public class Constants
    +extends java.lang.Object
    +
    Constants are fixed values that are used in the whole database code.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Fields 
      Modifier and TypeField and Description
      static intALLOW_LITERALS_ALL +
      Constant meaning both numbers and text is allowed in SQL statements.
      +
      static intALLOW_LITERALS_NONE +
      Constant meaning no literals are allowed in SQL statements.
      +
      static intALLOW_LITERALS_NUMBERS +
      Constant meaning only numbers are allowed in SQL statements (but no + texts).
      +
      static booleanBLOB_SEARCH +
      Whether searching in Blob values should be supported.
      +
      static java.lang.StringBUILD_DATE +
      The build date is updated for each public release.
      +
      static intBUILD_ID +
      Sequential version number.
      +
      static booleanBUILD_SNAPSHOT +
      Whether this is a snapshot version.
      +
      static java.lang.StringBUILD_VENDOR_AND_VERSION +
      If H2 is compiled to be included in a product, this should be set to + a unique vendor id (to distinguish from official releases).
      +
      static intCACHE_MIN_RECORDS +
      The minimum number of entries to keep in the cache.
      +
      static java.lang.StringCACHE_TYPE_DEFAULT +
      The default cache type.
      +
      static java.lang.StringCLUSTERING_DISABLED +
      The value of the cluster setting if clustering is disabled.
      +
      static java.lang.StringCLUSTERING_ENABLED +
      The value of the cluster setting if clustering is enabled (the actual + value is checked later).
      +
      static java.lang.StringCONN_URL_COLUMNLIST +
      The database URL used when calling a function if only the column list + should be returned.
      +
      static java.lang.StringCONN_URL_INTERNAL +
      The database URL used when calling a function if the data should be + returned.
      +
      static intCOST_ROW_OFFSET +
      The cost is calculated on rowcount + this offset, + to avoid using the wrong or no index if the table + contains no rows _currently_ (when preparing the statement)
      +
      static intDEADLOCK_CHECK +
      The number of milliseconds after which to check for a deadlock if locking + is not successful.
      +
      static intDEFAULT_HTTP_PORT +
      The default port number of the HTTP server (for the H2 Console).
      +
      static intDEFAULT_LOCK_MODE +
      The default value for the LOCK_MODE setting.
      +
      static intDEFAULT_MAX_LENGTH_INPLACE_LOB +
      The default maximum length of an LOB that is stored with the record + itself, and not in a separate place.
      +
      static intDEFAULT_MAX_OPERATION_MEMORY +
      The default for the setting MAX_OPERATION_MEMORY.
      +
      static intDEFAULT_PAGE_SIZE +
      The default page size to use for new databases.
      +
      static intDEFAULT_RESULT_SET_CONCURRENCY +
      The default result set concurrency for statements created with + Connection.createStatement() or prepareStatement(String sql).
      +
      static intDEFAULT_TCP_PORT +
      The default port of the TCP server.
      +
      static intDEFAULT_WRITE_DELAY +
      The default delay in milliseconds before the transaction log is written.
      +
      static intENCRYPTION_KEY_HASH_ITERATIONS +
      The password is hashed this many times + to slow down dictionary attacks.
      +
      static intFILE_BLOCK_SIZE +
      The block of a file.
      +
      static java.lang.StringFULL_VERSION +
      The complete version number of this database, consisting of + the major version, the minor version, the build id, and the build date.
      +
      static intINFORMATION_SCHEMA_ID +
      The identity of INFORMATION_SCHEMA.
      +
      static intINITIAL_LOCK_TIMEOUT +
      For testing, the lock timeout is smaller than for interactive use cases.
      +
      static intIO_BUFFER_SIZE +
      The block size for I/O operations.
      +
      static intIO_BUFFER_SIZE_COMPRESS +
      The block size used to compress data in the LZFOutputStream.
      +
      static intLOCK_MODE_OFF +
      The lock mode that means no locking is used at all.
      +
      static intLOCK_MODE_READ_COMMITTED +
      The lock mode that means read locks are acquired, but they are released + immediately after the statement is executed.
      +
      static intLOCK_MODE_TABLE +
      The lock mode that means table level locking is used for reads and + writes.
      +
      static intLOCK_MODE_TABLE_GC +
      The lock mode that means table level locking is used for reads and + writes.
      +
      static intLOCK_SLEEP +
      The number of milliseconds to wait between checking the .lock.db file + still exists once a database is locked.
      +
      static intMAIN_SCHEMA_ID +
      The identity of PUBLIC schema.
      +
      static intMAX_ARRAY_CARDINALITY +
      The maximum allowed cardinality of array.
      +
      static intMAX_COLUMNS +
      The maximum number of columns in a table, select statement or row value.
      +
      static intMAX_IDENTIFIER_LENGTH +
      The maximum allowed length of identifiers.
      +
      static intMAX_NUMERIC_PRECISION +
      The maximum allowed precision of numeric data types.
      +
      static intMAX_PARAMETER_INDEX +
      The highest possible parameter index.
      +
      static intMAX_STRING_LENGTH +
      The maximum allowed length for character string, binary string, and other + data types based on them; excluding LOB data types.
      +
      static intMEMORY_ARRAY +
      The memory needed by an array.
      +
      static intMEMORY_OBJECT +
      The memory needed by a regular object with at least one field.
      +
      static intMEMORY_POINTER +
      The memory needed by a pointer.
      +
      static intMEMORY_ROW +
      The memory needed by a Row.
      +
      static intPG_CATALOG_SCHEMA_ID +
      The identity of pg_catalog schema.
      +
      static java.lang.StringPG_VERSION +
      Announced version for PgServer.
      +
      static java.lang.StringPREFIX_INDEX +
      The name prefix used for indexes that are not explicitly named.
      +
      static java.lang.StringPREFIX_JOIN +
      The name prefix used for synthetic nested join tables.
      +
      static java.lang.StringPREFIX_PRIMARY_KEY +
      The name prefix used for primary key constraints that are not explicitly + named.
      +
      static java.lang.StringPREFIX_QUERY_ALIAS +
      The name prefix used for query aliases that are not explicitly named.
      +
      static java.lang.StringPUBLIC_ROLE_NAME +
      Every user belongs to this role.
      +
      static intQUERY_STATISTICS_MAX_ENTRIES +
      The maximum number of entries in query statistics.
      +
      static intSALT_LEN +
      The number of bytes in random salt that is used to hash passwords.
      +
      static java.lang.StringSCHEMA_MAIN +
      The name of the default schema.
      +
      static java.lang.StringSCHEMA_PG_CATALOG +
      The name of the pg_catalog schema.
      +
      static intSELECTIVITY_DEFAULT +
      The default selectivity (used if the selectivity is not calculated).
      +
      static intSELECTIVITY_DISTINCT_COUNT +
      The number of distinct values to keep in memory when running ANALYZE.
      +
      static java.lang.StringSERVER_PROPERTIES_DIR +
      The default directory name of the server properties file for the H2 + Console.
      +
      static java.lang.StringSERVER_PROPERTIES_NAME +
      The name of the server properties file for the H2 Console.
      +
      static longSLOW_QUERY_LIMIT_MS +
      Queries that take longer than this number of milliseconds are written to + the trace file with the level info.
      +
      static java.lang.StringSTART_URL +
      The database URL prefix of this database.
      +
      static java.lang.StringSUFFIX_LOCK_FILE +
      The file name suffix of file lock files that are used to make sure a + database is open by only one process at any time.
      +
      static java.lang.StringSUFFIX_MV_FILE +
      The file name suffix of a MVStore file.
      +
      static java.lang.StringSUFFIX_MV_STORE_NEW_FILE +
      The file name suffix of a new MVStore file, used when compacting a store.
      +
      static java.lang.StringSUFFIX_MV_STORE_TEMP_FILE +
      The file name suffix of a temporary MVStore file, used when compacting a + store.
      +
      static java.lang.StringSUFFIX_OLD_DATABASE_FILE +
      The file name suffix of a H2 version 1.1 database file.
      +
      static java.lang.StringSUFFIX_TEMP_FILE +
      The file name suffix of temporary files.
      +
      static java.lang.StringSUFFIX_TRACE_FILE +
      The file name suffix of trace files.
      +
      static intTCP_PROTOCOL_VERSION_17 +
      The TCP protocol version number 17.
      +
      static intTCP_PROTOCOL_VERSION_18 +
      The TCP protocol version number 18.
      +
      static intTCP_PROTOCOL_VERSION_19 +
      The TCP protocol version number 19.
      +
      static intTCP_PROTOCOL_VERSION_20 +
      The TCP protocol version number 20.
      +
      static intTCP_PROTOCOL_VERSION_MAX_SUPPORTED +
      Maximum supported version of TCP protocol.
      +
      static intTCP_PROTOCOL_VERSION_MIN_SUPPORTED +
      Minimum supported version of TCP protocol.
      +
      static intTHROTTLE_DELAY +
      How often we check to see if we need to apply a throttling delay if SET + THROTTLE has been used.
      +
      static intTRANSACTION_SNAPSHOT +
      SNAPSHOT isolation level of transaction.
      +
      static java.lang.StringURL_FORMAT +
      The database URL format in simplified Backus-Naur form.
      +
      static java.lang.StringUSER_PACKAGE +
      The package name of user defined classes.
      +
      static java.lang.StringVERSION +
      The version of this product, consisting of major version, minor + version, and build id.
      +
      static intVERSION_MAJOR +
      The major version of this database.
      +
      static intVERSION_MINOR +
      The minor version of this database.
      +
      static intVIEW_COST_CACHE_MAX_AGE +
      The maximum time in milliseconds to keep the cost of a view.
      +
      static intVIEW_INDEX_CACHE_SIZE +
      The name of the index cache that is used for temporary view (subqueries + used as tables).
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Detail

      + + + +
        +
      • +

        BUILD_DATE

        +
        public static final java.lang.String BUILD_DATE
        +
        The build date is updated for each public release.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        BUILD_ID

        +
        public static final int BUILD_ID
        +
        Sequential version number. Even numbers are used for official releases, + odd numbers are used for development builds.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        BUILD_SNAPSHOT

        +
        public static final boolean BUILD_SNAPSHOT
        +
        Whether this is a snapshot version.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        BUILD_VENDOR_AND_VERSION

        +
        public static final java.lang.String BUILD_VENDOR_AND_VERSION
        +
        If H2 is compiled to be included in a product, this should be set to + a unique vendor id (to distinguish from official releases). + Additionally, a version number should be set to distinguish releases. + Example: ACME_SVN1651_BUILD3
        +
      • +
      + + + +
        +
      • +

        TCP_PROTOCOL_VERSION_17

        +
        public static final int TCP_PROTOCOL_VERSION_17
        +
        The TCP protocol version number 17.
        +
        +
        Since:
        +
        1.4.197 (2018-03-18)
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        TCP_PROTOCOL_VERSION_18

        +
        public static final int TCP_PROTOCOL_VERSION_18
        +
        The TCP protocol version number 18.
        +
        +
        Since:
        +
        1.4.198 (2019-02-22)
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        TCP_PROTOCOL_VERSION_19

        +
        public static final int TCP_PROTOCOL_VERSION_19
        +
        The TCP protocol version number 19.
        +
        +
        Since:
        +
        1.4.200 (2019-10-14)
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        TCP_PROTOCOL_VERSION_20

        +
        public static final int TCP_PROTOCOL_VERSION_20
        +
        The TCP protocol version number 20.
        +
        +
        Since:
        +
        2.0.202 (2021-11-25)
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        TCP_PROTOCOL_VERSION_MIN_SUPPORTED

        +
        public static final int TCP_PROTOCOL_VERSION_MIN_SUPPORTED
        +
        Minimum supported version of TCP protocol.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        TCP_PROTOCOL_VERSION_MAX_SUPPORTED

        +
        public static final int TCP_PROTOCOL_VERSION_MAX_SUPPORTED
        +
        Maximum supported version of TCP protocol.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        VERSION_MAJOR

        +
        public static final int VERSION_MAJOR
        +
        The major version of this database.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        VERSION_MINOR

        +
        public static final int VERSION_MINOR
        +
        The minor version of this database.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        LOCK_MODE_OFF

        +
        public static final int LOCK_MODE_OFF
        +
        The lock mode that means no locking is used at all.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        LOCK_MODE_READ_COMMITTED

        +
        public static final int LOCK_MODE_READ_COMMITTED
        +
        The lock mode that means read locks are acquired, but they are released + immediately after the statement is executed.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        LOCK_MODE_TABLE

        +
        public static final int LOCK_MODE_TABLE
        +
        The lock mode that means table level locking is used for reads and + writes.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        LOCK_MODE_TABLE_GC

        +
        public static final int LOCK_MODE_TABLE_GC
        +
        The lock mode that means table level locking is used for reads and + writes. If a table is locked, System.gc is called to close forgotten + connections.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ALLOW_LITERALS_ALL

        +
        public static final int ALLOW_LITERALS_ALL
        +
        Constant meaning both numbers and text is allowed in SQL statements.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ALLOW_LITERALS_NONE

        +
        public static final int ALLOW_LITERALS_NONE
        +
        Constant meaning no literals are allowed in SQL statements.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ALLOW_LITERALS_NUMBERS

        +
        public static final int ALLOW_LITERALS_NUMBERS
        +
        Constant meaning only numbers are allowed in SQL statements (but no + texts).
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        TRANSACTION_SNAPSHOT

        +
        public static final int TRANSACTION_SNAPSHOT
        +
        SNAPSHOT isolation level of transaction.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        BLOB_SEARCH

        +
        public static final boolean BLOB_SEARCH
        +
        Whether searching in Blob values should be supported.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CACHE_MIN_RECORDS

        +
        public static final int CACHE_MIN_RECORDS
        +
        The minimum number of entries to keep in the cache.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CACHE_TYPE_DEFAULT

        +
        public static final java.lang.String CACHE_TYPE_DEFAULT
        +
        The default cache type.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CLUSTERING_DISABLED

        +
        public static final java.lang.String CLUSTERING_DISABLED
        +
        The value of the cluster setting if clustering is disabled.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CLUSTERING_ENABLED

        +
        public static final java.lang.String CLUSTERING_ENABLED
        +
        The value of the cluster setting if clustering is enabled (the actual + value is checked later).
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CONN_URL_COLUMNLIST

        +
        public static final java.lang.String CONN_URL_COLUMNLIST
        +
        The database URL used when calling a function if only the column list + should be returned.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CONN_URL_INTERNAL

        +
        public static final java.lang.String CONN_URL_INTERNAL
        +
        The database URL used when calling a function if the data should be + returned.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        COST_ROW_OFFSET

        +
        public static final int COST_ROW_OFFSET
        +
        The cost is calculated on rowcount + this offset, + to avoid using the wrong or no index if the table + contains no rows _currently_ (when preparing the statement)
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DEADLOCK_CHECK

        +
        public static final int DEADLOCK_CHECK
        +
        The number of milliseconds after which to check for a deadlock if locking + is not successful.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DEFAULT_HTTP_PORT

        +
        public static final int DEFAULT_HTTP_PORT
        +
        The default port number of the HTTP server (for the H2 Console). + This value is also in the documentation and in the Server javadoc.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DEFAULT_LOCK_MODE

        +
        public static final int DEFAULT_LOCK_MODE
        +
        The default value for the LOCK_MODE setting.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DEFAULT_MAX_LENGTH_INPLACE_LOB

        +
        public static final int DEFAULT_MAX_LENGTH_INPLACE_LOB
        +
        The default maximum length of an LOB that is stored with the record + itself, and not in a separate place.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DEFAULT_MAX_OPERATION_MEMORY

        +
        public static final int DEFAULT_MAX_OPERATION_MEMORY
        +
        The default for the setting MAX_OPERATION_MEMORY.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DEFAULT_PAGE_SIZE

        +
        public static final int DEFAULT_PAGE_SIZE
        +
        The default page size to use for new databases.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DEFAULT_RESULT_SET_CONCURRENCY

        +
        public static final int DEFAULT_RESULT_SET_CONCURRENCY
        +
        The default result set concurrency for statements created with + Connection.createStatement() or prepareStatement(String sql).
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DEFAULT_TCP_PORT

        +
        public static final int DEFAULT_TCP_PORT
        +
        The default port of the TCP server. + This port is also used in the documentation and in the Server javadoc.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DEFAULT_WRITE_DELAY

        +
        public static final int DEFAULT_WRITE_DELAY
        +
        The default delay in milliseconds before the transaction log is written.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ENCRYPTION_KEY_HASH_ITERATIONS

        +
        public static final int ENCRYPTION_KEY_HASH_ITERATIONS
        +
        The password is hashed this many times + to slow down dictionary attacks.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        FILE_BLOCK_SIZE

        +
        public static final int FILE_BLOCK_SIZE
        +
        The block of a file. It is also the encryption block size.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        INITIAL_LOCK_TIMEOUT

        +
        public static final int INITIAL_LOCK_TIMEOUT
        +
        For testing, the lock timeout is smaller than for interactive use cases. + This value could be increased to about 5 or 10 seconds.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        IO_BUFFER_SIZE

        +
        public static final int IO_BUFFER_SIZE
        +
        The block size for I/O operations.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        IO_BUFFER_SIZE_COMPRESS

        +
        public static final int IO_BUFFER_SIZE_COMPRESS
        +
        The block size used to compress data in the LZFOutputStream.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        LOCK_SLEEP

        +
        public static final int LOCK_SLEEP
        +
        The number of milliseconds to wait between checking the .lock.db file + still exists once a database is locked.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        MAX_IDENTIFIER_LENGTH

        +
        public static final int MAX_IDENTIFIER_LENGTH
        +
        The maximum allowed length of identifiers.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        MAX_COLUMNS

        +
        public static final int MAX_COLUMNS
        +
        The maximum number of columns in a table, select statement or row value.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        MAX_STRING_LENGTH

        +
        public static final int MAX_STRING_LENGTH
        +
        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.

        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        MAX_NUMERIC_PRECISION

        +
        public static final int MAX_NUMERIC_PRECISION
        +
        The maximum allowed precision of numeric data types.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        MAX_ARRAY_CARDINALITY

        +
        public static final int MAX_ARRAY_CARDINALITY
        +
        The maximum allowed cardinality of array.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        MAX_PARAMETER_INDEX

        +
        public static final int MAX_PARAMETER_INDEX
        +
        The highest possible parameter index.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        MEMORY_OBJECT

        +
        public static final int MEMORY_OBJECT
        +
        The memory needed by a regular object with at least one field.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        MEMORY_ARRAY

        +
        public static final int MEMORY_ARRAY
        +
        The memory needed by an array.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        MEMORY_POINTER

        +
        public static final int MEMORY_POINTER
        +
        The memory needed by a pointer.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        MEMORY_ROW

        +
        public static final int MEMORY_ROW
        +
        The memory needed by a Row.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        PREFIX_INDEX

        +
        public static final java.lang.String PREFIX_INDEX
        +
        The name prefix used for indexes that are not explicitly named.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        PREFIX_JOIN

        +
        public static final java.lang.String PREFIX_JOIN
        +
        The name prefix used for synthetic nested join tables.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        PREFIX_PRIMARY_KEY

        +
        public static final java.lang.String PREFIX_PRIMARY_KEY
        +
        The name prefix used for primary key constraints that are not explicitly + named.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        PREFIX_QUERY_ALIAS

        +
        public static final java.lang.String PREFIX_QUERY_ALIAS
        +
        The name prefix used for query aliases that are not explicitly named.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        PUBLIC_ROLE_NAME

        +
        public static final java.lang.String PUBLIC_ROLE_NAME
        +
        Every user belongs to this role.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SALT_LEN

        +
        public static final int SALT_LEN
        +
        The number of bytes in random salt that is used to hash passwords.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        INFORMATION_SCHEMA_ID

        +
        public static final int INFORMATION_SCHEMA_ID
        +
        The identity of INFORMATION_SCHEMA.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        MAIN_SCHEMA_ID

        +
        public static final int MAIN_SCHEMA_ID
        +
        The identity of PUBLIC schema.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SCHEMA_MAIN

        +
        public static final java.lang.String SCHEMA_MAIN
        +
        The name of the default schema.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        PG_CATALOG_SCHEMA_ID

        +
        public static final int PG_CATALOG_SCHEMA_ID
        +
        The identity of pg_catalog schema.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SCHEMA_PG_CATALOG

        +
        public static final java.lang.String SCHEMA_PG_CATALOG
        +
        The name of the pg_catalog schema.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SELECTIVITY_DEFAULT

        +
        public static final int SELECTIVITY_DEFAULT
        +
        The default selectivity (used if the selectivity is not calculated).
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SELECTIVITY_DISTINCT_COUNT

        +
        public static final int SELECTIVITY_DISTINCT_COUNT
        +
        The number of distinct values to keep in memory when running ANALYZE.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SERVER_PROPERTIES_DIR

        +
        public static final java.lang.String SERVER_PROPERTIES_DIR
        +
        The default directory name of the server properties file for the H2 + Console.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SERVER_PROPERTIES_NAME

        +
        public static final java.lang.String SERVER_PROPERTIES_NAME
        +
        The name of the server properties file for the H2 Console.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SLOW_QUERY_LIMIT_MS

        +
        public static final long SLOW_QUERY_LIMIT_MS
        +
        Queries that take longer than this number of milliseconds are written to + the trace file with the level info.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        START_URL

        +
        public static final java.lang.String START_URL
        +
        The database URL prefix of this database.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SUFFIX_LOCK_FILE

        +
        public static final java.lang.String SUFFIX_LOCK_FILE
        +
        The file name suffix of file lock files that are used to make sure a + database is open by only one process at any time.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SUFFIX_OLD_DATABASE_FILE

        +
        public static final java.lang.String SUFFIX_OLD_DATABASE_FILE
        +
        The file name suffix of a H2 version 1.1 database file.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SUFFIX_MV_FILE

        +
        public static final java.lang.String SUFFIX_MV_FILE
        +
        The file name suffix of a MVStore file.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SUFFIX_MV_STORE_NEW_FILE

        +
        public static final java.lang.String SUFFIX_MV_STORE_NEW_FILE
        +
        The file name suffix of a new MVStore file, used when compacting a store.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SUFFIX_MV_STORE_TEMP_FILE

        +
        public static final java.lang.String SUFFIX_MV_STORE_TEMP_FILE
        +
        The file name suffix of a temporary MVStore file, used when compacting a + store.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SUFFIX_TEMP_FILE

        +
        public static final java.lang.String SUFFIX_TEMP_FILE
        +
        The file name suffix of temporary files.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SUFFIX_TRACE_FILE

        +
        public static final java.lang.String SUFFIX_TRACE_FILE
        +
        The file name suffix of trace files.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        THROTTLE_DELAY

        +
        public static final int THROTTLE_DELAY
        +
        How often we check to see if we need to apply a throttling delay if SET + THROTTLE has been used.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        URL_FORMAT

        +
        public static final java.lang.String URL_FORMAT
        +
        The database URL format in simplified Backus-Naur form.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        USER_PACKAGE

        +
        public static final java.lang.String USER_PACKAGE
        +
        The package name of user defined classes.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        VIEW_COST_CACHE_MAX_AGE

        +
        public static final int VIEW_COST_CACHE_MAX_AGE
        +
        The maximum time in milliseconds to keep the cost of a view. + 10000 means 10 seconds.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        VIEW_INDEX_CACHE_SIZE

        +
        public static final int VIEW_INDEX_CACHE_SIZE
        +
        The name of the index cache that is used for temporary view (subqueries + used as tables).
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        QUERY_STATISTICS_MAX_ENTRIES

        +
        public static final int QUERY_STATISTICS_MAX_ENTRIES
        +
        The maximum number of entries in query statistics.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        PG_VERSION

        +
        public static final java.lang.String PG_VERSION
        +
        Announced version for PgServer.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        VERSION

        +
        public static final java.lang.String VERSION
        +
        The version of this product, consisting of major version, minor + version, and build id.
        +
      • +
      + + + +
        +
      • +

        FULL_VERSION

        +
        public static final java.lang.String FULL_VERSION
        +
        The complete version number of this database, consisting of + the major version, the minor version, the build id, and the build date.
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/Database.html b/h2/docs/javadoc/org/h2/engine/Database.html new file mode 100644 index 0000000..1a006a2 --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/Database.html @@ -0,0 +1,2899 @@ + + + + + +Database + + + + + + + + + + + + +
+
org.h2.engine
+

Class Database

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.engine.Database
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    CastDataProvider, org.h2.store.DataHandler
    +
    +
    +
    +
    public final class Database
    +extends java.lang.Object
    +implements org.h2.store.DataHandler, CastDataProvider
    +
    There is one database object per open database. + + The format of the meta data table is: + id int, 0, objectType int, sql varchar
    +
    +
    Since:
    +
    2004-04-15 22:49
    +
    +
  • +
+
+
+ +
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        Database

        +
        public Database(ConnectionInfo ci,
        +                java.lang.String cipher)
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getLockTimeout

        +
        public int getLockTimeout()
        +
      • +
      + + + +
        +
      • +

        getRowFactory

        +
        public org.h2.result.RowFactory getRowFactory()
        +
      • +
      + + + +
        +
      • +

        setRowFactory

        +
        public void setRowFactory(org.h2.result.RowFactory rowFactory)
        +
      • +
      + + + +
        +
      • +

        setInitialPowerOffCount

        +
        public static void setInitialPowerOffCount(int count)
        +
      • +
      + + + +
        +
      • +

        setPowerOffCount

        +
        public void setPowerOffCount(int count)
        +
      • +
      + + + +
        +
      • +

        getStore

        +
        public org.h2.mvstore.db.Store getStore()
        +
      • +
      + + + +
        +
      • +

        getModificationDataId

        +
        public long getModificationDataId()
        +
      • +
      + + + +
        +
      • +

        getNextModificationDataId

        +
        public long getNextModificationDataId()
        +
      • +
      + + + +
        +
      • +

        getModificationMetaId

        +
        public long getModificationMetaId()
        +
      • +
      + + + +
        +
      • +

        getNextModificationMetaId

        +
        public long getNextModificationMetaId()
        +
      • +
      + + + +
        +
      • +

        getRemoteSettingsId

        +
        public long getRemoteSettingsId()
        +
      • +
      + + + +
        +
      • +

        getNextRemoteSettingsId

        +
        public long getNextRemoteSettingsId()
        +
      • +
      + + + +
        +
      • +

        getPowerOffCount

        +
        public int getPowerOffCount()
        +
      • +
      + + + +
        +
      • +

        checkPowerOff

        +
        public void checkPowerOff()
        +
        Description copied from interface: org.h2.store.DataHandler
        +
        Check if the simulated power failure occurred. + This call will decrement the countdown.
        +
        +
        Specified by:
        +
        checkPowerOff in interface org.h2.store.DataHandler
        +
        +
      • +
      + + + +
        +
      • +

        getTrace

        +
        public org.h2.message.Trace getTrace(int moduleId)
        +
        Get the trace object for the given module id.
        +
        +
        Parameters:
        +
        moduleId - the module id
        +
        Returns:
        +
        the trace object
        +
        +
      • +
      + + + +
        +
      • +

        openFile

        +
        public org.h2.store.FileStore openFile(java.lang.String name,
        +                                       java.lang.String openMode,
        +                                       boolean mustExist)
        +
        Description copied from interface: org.h2.store.DataHandler
        +
        Open a file at the given location.
        +
        +
        Specified by:
        +
        openFile in interface org.h2.store.DataHandler
        +
        Parameters:
        +
        name - the file name
        +
        openMode - the mode
        +
        mustExist - whether the file must already exist
        +
        Returns:
        +
        the file
        +
        +
      • +
      + + + +
        +
      • +

        verifyMetaLocked

        +
        public void verifyMetaLocked(SessionLocal session)
        +
        Verify the meta table is locked.
        +
        +
        Parameters:
        +
        session - the session
        +
        +
      • +
      + + + +
        +
      • +

        lockMeta

        +
        public boolean lockMeta(SessionLocal session)
        +
        Lock the metadata table for updates.
        +
        +
        Parameters:
        +
        session - the session
        +
        Returns:
        +
        whether it was already locked before by this session
        +
        +
      • +
      + + + +
        +
      • +

        unlockMeta

        +
        public void unlockMeta(SessionLocal session)
        +
        Unlock the metadata table.
        +
        +
        Parameters:
        +
        session - the session
        +
        +
      • +
      + + + +
        +
      • +

        removeMeta

        +
        public void removeMeta(SessionLocal session,
        +                       int id)
        +
        Remove the given object from the meta data.
        +
        +
        Parameters:
        +
        session - the session
        +
        id - the id of the object to remove
        +
        +
      • +
      + + + +
        +
      • +

        releaseDatabaseObjectIds

        +
        public void releaseDatabaseObjectIds(java.util.BitSet idsToRelease)
        +
        Mark some database ids as unused.
        +
        +
        Parameters:
        +
        idsToRelease - the ids to release
        +
        +
      • +
      + + + +
        +
      • +

        addSchemaObject

        +
        public void addSchemaObject(SessionLocal session,
        +                            org.h2.schema.SchemaObject obj)
        +
        Add a schema object to the database.
        +
        +
        Parameters:
        +
        session - the session
        +
        obj - the object to add
        +
        +
      • +
      + + + +
        +
      • +

        addDatabaseObject

        +
        public void addDatabaseObject(SessionLocal session,
        +                              DbObject obj)
        +
        Add an object to the database.
        +
        +
        Parameters:
        +
        session - the session
        +
        obj - the object to add
        +
        +
      • +
      + + + +
        +
      • +

        findComment

        +
        public Comment findComment(DbObject object)
        +
        Get the comment for the given database object if one exists, or null if + not.
        +
        +
        Parameters:
        +
        object - the database object
        +
        Returns:
        +
        the comment or null
        +
        +
      • +
      + + + +
        +
      • +

        findRole

        +
        public Role findRole(java.lang.String roleName)
        +
        Get the role if it exists, or null if not.
        +
        +
        Parameters:
        +
        roleName - the name of the role
        +
        Returns:
        +
        the role or null
        +
        +
      • +
      + + + +
        +
      • +

        findSchema

        +
        public org.h2.schema.Schema findSchema(java.lang.String schemaName)
        +
        Get the schema if it exists, or null if not.
        +
        +
        Parameters:
        +
        schemaName - the name of the schema
        +
        Returns:
        +
        the schema or null
        +
        +
      • +
      + + + +
        +
      • +

        findSetting

        +
        public Setting findSetting(java.lang.String name)
        +
        Get the setting if it exists, or null if not.
        +
        +
        Parameters:
        +
        name - the name of the setting
        +
        Returns:
        +
        the setting or null
        +
        +
      • +
      + + + +
        +
      • +

        findUser

        +
        public User findUser(java.lang.String name)
        +
        Get the user if it exists, or null if not.
        +
        +
        Parameters:
        +
        name - the name of the user
        +
        Returns:
        +
        the user or null
        +
        +
      • +
      + + + +
        +
      • +

        getUser

        +
        public User getUser(java.lang.String name)
        +
        Get user with the given name. This method throws an exception if the user + does not exist.
        +
        +
        Parameters:
        +
        name - the user name
        +
        Returns:
        +
        the user
        +
        Throws:
        +
        org.h2.message.DbException - if the user does not exist
        +
        +
      • +
      + + + +
        +
      • +

        findUserOrRole

        +
        public RightOwner findUserOrRole(java.lang.String name)
        +
        Get the user or role if it exists, or null if not.
        +
        +
        Parameters:
        +
        name - the name of the user or role
        +
        Returns:
        +
        the user, the role, or null
        +
        +
      • +
      + + + +
        +
      • +

        removeSession

        +
        public void removeSession(SessionLocal session)
        +
        Remove a session. This method is called after the user has disconnected.
        +
        +
        Parameters:
        +
        session - the session
        +
        +
      • +
      + + + +
        +
      • +

        allocateObjectId

        +
        public int allocateObjectId()
        +
        Allocate a new object id.
        +
        +
        Returns:
        +
        the id
        +
        +
      • +
      + + + +
        +
      • +

        getSystemUser

        +
        public User getSystemUser()
        +
        Returns system user.
        +
        +
        Returns:
        +
        system user
        +
        +
      • +
      + + + +
        +
      • +

        getMainSchema

        +
        public org.h2.schema.Schema getMainSchema()
        +
        Returns main schema (usually PUBLIC).
        +
        +
        Returns:
        +
        main schema (usually PUBLIC)
        +
        +
      • +
      + + + +
        +
      • +

        getAllComments

        +
        public java.util.ArrayList<Comment> getAllComments()
        +
      • +
      + + + +
        +
      • +

        getAllowLiterals

        +
        public int getAllowLiterals()
        +
      • +
      + + + +
        +
      • +

        getAllRights

        +
        public java.util.ArrayList<Right> getAllRights()
        +
      • +
      + + + +
        +
      • +

        getAllTablesAndViews

        +
        public java.util.ArrayList<org.h2.table.Table> getAllTablesAndViews()
        +
        Get all tables and views. Meta data tables may be excluded.
        +
        +
        Returns:
        +
        all objects of that type
        +
        +
      • +
      + + + +
        +
      • +

        getAllSynonyms

        +
        public java.util.ArrayList<org.h2.table.TableSynonym> getAllSynonyms()
        +
        Get all synonyms.
        +
        +
        Returns:
        +
        all objects of that type
        +
        +
      • +
      + + + +
        +
      • +

        getAllSchemas

        +
        public java.util.Collection<org.h2.schema.Schema> getAllSchemas()
        +
      • +
      + + + +
        +
      • +

        getAllSchemasNoMeta

        +
        public java.util.Collection<org.h2.schema.Schema> getAllSchemasNoMeta()
        +
      • +
      + + + +
        +
      • +

        getAllSettings

        +
        public java.util.Collection<Setting> getAllSettings()
        +
      • +
      + + + +
        +
      • +

        getAllUsersAndRoles

        +
        public java.util.Collection<RightOwner> getAllUsersAndRoles()
        +
      • +
      + + + +
        +
      • +

        getCacheType

        +
        public java.lang.String getCacheType()
        +
      • +
      + + + +
        +
      • +

        getCluster

        +
        public java.lang.String getCluster()
        +
      • +
      + + + +
        +
      • +

        getCompareMode

        +
        public org.h2.value.CompareMode getCompareMode()
        +
        Description copied from interface: org.h2.store.DataHandler
        +
        Return compare mode.
        +
        +
        Specified by:
        +
        getCompareMode in interface org.h2.store.DataHandler
        +
        Returns:
        +
        Compare mode.
        +
        +
      • +
      + + + +
        +
      • +

        getDatabasePath

        +
        public java.lang.String getDatabasePath()
        +
        Description copied from interface: org.h2.store.DataHandler
        +
        Get the database path.
        +
        +
        Specified by:
        +
        getDatabasePath in interface org.h2.store.DataHandler
        +
        Returns:
        +
        the database path
        +
        +
      • +
      + + + +
        +
      • +

        getShortName

        +
        public java.lang.String getShortName()
        +
      • +
      + + + +
        +
      • +

        getName

        +
        public java.lang.String getName()
        +
      • +
      + + + +
        +
      • +

        getSessions

        +
        public SessionLocal[] getSessions(boolean includingSystemSession)
        +
        Get all sessions that are currently connected to the database.
        +
        +
        Parameters:
        +
        includingSystemSession - if the system session should also be + included
        +
        Returns:
        +
        the list of sessions
        +
        +
      • +
      + + + +
        +
      • +

        updateMeta

        +
        public void updateMeta(SessionLocal session,
        +                       DbObject obj)
        +
        Update an object in the system table.
        +
        +
        Parameters:
        +
        session - the session
        +
        obj - the database object
        +
        +
      • +
      + + + +
        +
      • +

        renameSchemaObject

        +
        public void renameSchemaObject(SessionLocal session,
        +                               org.h2.schema.SchemaObject obj,
        +                               java.lang.String newName)
        +
        Rename a schema object.
        +
        +
        Parameters:
        +
        session - the session
        +
        obj - the object
        +
        newName - the new name
        +
        +
      • +
      + + + +
        +
      • +

        renameDatabaseObject

        +
        public void renameDatabaseObject(SessionLocal session,
        +                                 DbObject obj,
        +                                 java.lang.String newName)
        +
        Rename a database object.
        +
        +
        Parameters:
        +
        session - the session
        +
        obj - the object
        +
        newName - the new name
        +
        +
      • +
      + + + +
        +
      • +

        getSchema

        +
        public org.h2.schema.Schema getSchema(java.lang.String schemaName)
        +
        Get the schema. If the schema does not exist, an exception is thrown.
        +
        +
        Parameters:
        +
        schemaName - the name of the schema
        +
        Returns:
        +
        the schema
        +
        Throws:
        +
        org.h2.message.DbException - no schema with that name exists
        +
        +
      • +
      + + + +
        +
      • +

        removeDatabaseObject

        +
        public void removeDatabaseObject(SessionLocal session,
        +                                 DbObject obj)
        +
        Remove the object from the database.
        +
        +
        Parameters:
        +
        session - the session
        +
        obj - the object to remove
        +
        +
      • +
      + + + +
        +
      • +

        getDependentTable

        +
        public org.h2.table.Table getDependentTable(org.h2.schema.SchemaObject obj,
        +                                            org.h2.table.Table except)
        +
        Get the first table that depends on this object.
        +
        +
        Parameters:
        +
        obj - the object to find
        +
        except - the table to exclude (or null)
        +
        Returns:
        +
        the first dependent table, or null
        +
        +
      • +
      + + + +
        +
      • +

        removeSchemaObject

        +
        public void removeSchemaObject(SessionLocal session,
        +                               org.h2.schema.SchemaObject obj)
        +
        Remove an object from the system table.
        +
        +
        Parameters:
        +
        session - the session
        +
        obj - the object to be removed
        +
        +
      • +
      + + + +
        +
      • +

        isPersistent

        +
        public boolean isPersistent()
        +
        Check if this database is disk-based.
        +
        +
        Returns:
        +
        true if it is disk-based, false if it is in-memory only.
        +
        +
      • +
      + + + +
        +
      • +

        getTraceSystem

        +
        public org.h2.message.TraceSystem getTraceSystem()
        +
      • +
      + + + +
        +
      • +

        setCacheSize

        +
        public void setCacheSize(int kb)
        +
      • +
      + + + +
        +
      • +

        setMasterUser

        +
        public void setMasterUser(User user)
        +
      • +
      + + + +
        +
      • +

        getPublicRole

        +
        public Role getPublicRole()
        +
      • +
      + + + +
        +
      • +

        getTempTableName

        +
        public java.lang.String getTempTableName(java.lang.String baseName,
        +                                         SessionLocal session)
        +
        Get a unique temporary table name.
        +
        +
        Parameters:
        +
        baseName - the prefix of the returned name
        +
        session - the session
        +
        Returns:
        +
        a unique name
        +
        +
      • +
      + + + +
        +
      • +

        setCompareMode

        +
        public void setCompareMode(org.h2.value.CompareMode compareMode)
        +
      • +
      + + + +
        +
      • +

        setCluster

        +
        public void setCluster(java.lang.String cluster)
        +
      • +
      + + + +
        +
      • +

        checkWritingAllowed

        +
        public void checkWritingAllowed()
        +
        Description copied from interface: org.h2.store.DataHandler
        +
        Check if writing is allowed.
        +
        +
        Specified by:
        +
        checkWritingAllowed in interface org.h2.store.DataHandler
        +
        +
      • +
      + + + +
        +
      • +

        isReadOnly

        +
        public boolean isReadOnly()
        +
      • +
      + + + +
        +
      • +

        setWriteDelay

        +
        public void setWriteDelay(int value)
        +
      • +
      + + + +
        +
      • +

        getRetentionTime

        +
        public int getRetentionTime()
        +
      • +
      + + + +
        +
      • +

        setRetentionTime

        +
        public void setRetentionTime(int value)
        +
      • +
      + + + +
        +
      • +

        setAllowBuiltinAliasOverride

        +
        public void setAllowBuiltinAliasOverride(boolean b)
        +
      • +
      + + + +
        +
      • +

        isAllowBuiltinAliasOverride

        +
        public boolean isAllowBuiltinAliasOverride()
        +
      • +
      + + + +
        +
      • +

        getInDoubtTransactions

        +
        public java.util.ArrayList<org.h2.store.InDoubtTransaction> getInDoubtTransactions()
        +
        Get the list of in-doubt transactions.
        +
        +
        Returns:
        +
        the list
        +
        +
      • +
      + + + +
        +
      • +

        setBackgroundException

        +
        public void setBackgroundException(org.h2.message.DbException e)
        +
      • +
      + + + +
        +
      • +

        getBackgroundException

        +
        public java.lang.Throwable getBackgroundException()
        +
      • +
      + + + +
        +
      • +

        flush

        +
        public void flush()
        +
        Flush all pending changes to the transaction log.
        +
      • +
      + + + + + + + +
        +
      • +

        setEventListenerClass

        +
        public void setEventListenerClass(java.lang.String className)
        +
      • +
      + + + +
        +
      • +

        setProgress

        +
        public void setProgress(int state,
        +                        java.lang.String name,
        +                        long x,
        +                        long max)
        +
        Set the progress of a long running operation. + This method calls the DatabaseEventListener if one is registered.
        +
        +
        Parameters:
        +
        state - the DatabaseEventListener state
        +
        name - the object name
        +
        x - the current position
        +
        max - the highest value or 0 if unknown
        +
        +
      • +
      + + + +
        +
      • +

        exceptionThrown

        +
        public void exceptionThrown(java.sql.SQLException e,
        +                            java.lang.String sql)
        +
        This method is called after an exception occurred, to inform the database + event listener (if one is set).
        +
        +
        Parameters:
        +
        e - the exception
        +
        sql - the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        sync

        +
        public void sync()
        +
        Synchronize the files with the file system. This method is called when + executing the SQL statement CHECKPOINT SYNC.
        +
      • +
      + + + +
        +
      • +

        getMaxMemoryRows

        +
        public int getMaxMemoryRows()
        +
      • +
      + + + +
        +
      • +

        setMaxMemoryRows

        +
        public void setMaxMemoryRows(int value)
        +
      • +
      + + + +
        +
      • +

        setLockMode

        +
        public void setLockMode(int lockMode)
        +
      • +
      + + + +
        +
      • +

        getLockMode

        +
        public int getLockMode()
        +
      • +
      + + + +
        +
      • +

        setCloseDelay

        +
        public void setCloseDelay(int value)
        +
      • +
      + + + +
        +
      • +

        getSystemSession

        +
        public SessionLocal getSystemSession()
        +
      • +
      + + + +
        +
      • +

        isClosing

        +
        public boolean isClosing()
        +
        Check if the database is in the process of closing.
        +
        +
        Returns:
        +
        true if the database is closing
        +
        +
      • +
      + + + +
        +
      • +

        setMaxLengthInplaceLob

        +
        public void setMaxLengthInplaceLob(int value)
        +
      • +
      + + + +
        +
      • +

        getMaxLengthInplaceLob

        +
        public int getMaxLengthInplaceLob()
        +
        Description copied from interface: org.h2.store.DataHandler
        +
        Get the maximum length of a in-place large object
        +
        +
        Specified by:
        +
        getMaxLengthInplaceLob in interface org.h2.store.DataHandler
        +
        Returns:
        +
        the maximum size
        +
        +
      • +
      + + + +
        +
      • +

        setIgnoreCase

        +
        public void setIgnoreCase(boolean b)
        +
      • +
      + + + +
        +
      • +

        getIgnoreCase

        +
        public boolean getIgnoreCase()
        +
      • +
      + + + +
        +
      • +

        setIgnoreCatalogs

        +
        public void setIgnoreCatalogs(boolean b)
        +
      • +
      + + + +
        +
      • +

        getIgnoreCatalogs

        +
        public boolean getIgnoreCatalogs()
        +
      • +
      + + + +
        +
      • +

        setDeleteFilesOnDisconnect

        +
        public void setDeleteFilesOnDisconnect(boolean b)
        +
      • +
      + + + +
        +
      • +

        setAllowLiterals

        +
        public void setAllowLiterals(int value)
        +
      • +
      + + + +
        +
      • +

        getOptimizeReuseResults

        +
        public boolean getOptimizeReuseResults()
        +
      • +
      + + + +
        +
      • +

        setOptimizeReuseResults

        +
        public void setOptimizeReuseResults(boolean b)
        +
      • +
      + + + +
        +
      • +

        getLobSyncObject

        +
        public java.lang.Object getLobSyncObject()
        +
        Description copied from interface: org.h2.store.DataHandler
        +
        Get the synchronization object for lob operations.
        +
        +
        Specified by:
        +
        getLobSyncObject in interface org.h2.store.DataHandler
        +
        Returns:
        +
        the synchronization object
        +
        +
      • +
      + + + +
        +
      • +

        getSessionCount

        +
        public int getSessionCount()
        +
      • +
      + + + +
        +
      • +

        setReferentialIntegrity

        +
        public void setReferentialIntegrity(boolean b)
        +
      • +
      + + + +
        +
      • +

        getReferentialIntegrity

        +
        public boolean getReferentialIntegrity()
        +
      • +
      + + + +
        +
      • +

        setQueryStatistics

        +
        public void setQueryStatistics(boolean b)
        +
      • +
      + + + +
        +
      • +

        getQueryStatistics

        +
        public boolean getQueryStatistics()
        +
      • +
      + + + +
        +
      • +

        setQueryStatisticsMaxEntries

        +
        public void setQueryStatisticsMaxEntries(int n)
        +
      • +
      + + + + + + + +
        +
      • +

        isStarting

        +
        public boolean isStarting()
        +
        Check if the database is currently opening. This is true until all stored + SQL statements have been executed.
        +
        +
        Returns:
        +
        true if the database is still starting
        +
        +
      • +
      + + + +
        +
      • +

        setMode

        +
        public void setMode(Mode mode)
        +
      • +
      + + + + + + + +
        +
      • +

        setDefaultNullOrdering

        +
        public void setDefaultNullOrdering(org.h2.mode.DefaultNullOrdering defaultNullOrdering)
        +
      • +
      + + + +
        +
      • +

        getDefaultNullOrdering

        +
        public org.h2.mode.DefaultNullOrdering getDefaultNullOrdering()
        +
      • +
      + + + +
        +
      • +

        setMaxOperationMemory

        +
        public void setMaxOperationMemory(int maxOperationMemory)
        +
      • +
      + + + +
        +
      • +

        getMaxOperationMemory

        +
        public int getMaxOperationMemory()
        +
      • +
      + + + +
        +
      • +

        getExclusiveSession

        +
        public SessionLocal getExclusiveSession()
        +
      • +
      + + + +
        +
      • +

        setExclusiveSession

        +
        public boolean setExclusiveSession(SessionLocal session,
        +                                   boolean closeOthers)
        +
        Set the session that can exclusively access the database.
        +
        +
        Parameters:
        +
        session - the session
        +
        closeOthers - whether other sessions are closed
        +
        Returns:
        +
        true if success or if database is in exclusive mode + set by this session already, false otherwise
        +
        +
      • +
      + + + +
        +
      • +

        unsetExclusiveSession

        +
        public boolean unsetExclusiveSession(SessionLocal session)
        +
        Stop exclusive access the database by provided session.
        +
        +
        Parameters:
        +
        session - the session
        +
        Returns:
        +
        true if success or if database is in non-exclusive mode already, + false otherwise
        +
        +
      • +
      + + + +
        +
      • +

        getLobFileListCache

        +
        public org.h2.util.SmallLRUCache<java.lang.String,java.lang.String[]> getLobFileListCache()
        +
        Description copied from interface: org.h2.store.DataHandler
        +
        Get the lob file list cache if it is used.
        +
        +
        Specified by:
        +
        getLobFileListCache in interface org.h2.store.DataHandler
        +
        Returns:
        +
        the cache or null
        +
        +
      • +
      + + + +
        +
      • +

        isSysTableLocked

        +
        public boolean isSysTableLocked()
        +
        Checks if the system table (containing the catalog) is locked.
        +
        +
        Returns:
        +
        true if it is currently locked
        +
        +
      • +
      + + + +
        +
      • +

        isSysTableLockedBy

        +
        public boolean isSysTableLockedBy(SessionLocal session)
        +
        Checks if the system table (containing the catalog) is locked by the + given session.
        +
        +
        Parameters:
        +
        session - the session
        +
        Returns:
        +
        true if it is currently locked
        +
        +
      • +
      + + + +
        +
      • +

        getLinkConnection

        +
        public org.h2.table.TableLinkConnection getLinkConnection(java.lang.String driver,
        +                                                          java.lang.String url,
        +                                                          java.lang.String user,
        +                                                          java.lang.String password)
        +
        Open a new connection or get an existing connection to another database.
        +
        +
        Parameters:
        +
        driver - the database driver or null
        +
        url - the database URL
        +
        user - the user name
        +
        password - the password
        +
        Returns:
        +
        the connection
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        +
        Overrides:
        +
        toString in class java.lang.Object
        +
        +
      • +
      + + + +
        +
      • +

        shutdownImmediately

        +
        public void shutdownImmediately()
        +
        Immediately close the database.
        +
      • +
      + + + +
        +
      • +

        getTempFileDeleter

        +
        public org.h2.util.TempFileDeleter getTempFileDeleter()
        +
        Description copied from interface: org.h2.store.DataHandler
        +
        Get the temp file deleter mechanism.
        +
        +
        Specified by:
        +
        getTempFileDeleter in interface org.h2.store.DataHandler
        +
        Returns:
        +
        the temp file deleter
        +
        +
      • +
      + + + +
        +
      • +

        getFirstUserTable

        +
        public org.h2.table.Table getFirstUserTable()
        +
        Get the first user defined table, excluding the LOB_BLOCKS table that the + Recover tool creates.
        +
        +
        Returns:
        +
        the table or null if no table is defined
        +
        +
      • +
      + + + +
        +
      • +

        checkpoint

        +
        public void checkpoint()
        +
        Flush all changes and open a new transaction log.
        +
      • +
      + + + +
        +
      • +

        setReadOnly

        +
        public void setReadOnly(boolean readOnly)
        +
        Switch the database to read-only mode.
        +
        +
        Parameters:
        +
        readOnly - the new value
        +
        +
      • +
      + + + +
        +
      • +

        setCompactMode

        +
        public void setCompactMode(int compactMode)
        +
      • +
      + + + +
        +
      • +

        getCompiler

        +
        public org.h2.util.SourceCompiler getCompiler()
        +
      • +
      + + + +
        +
      • +

        getLobStorage

        +
        public org.h2.store.LobStorageInterface getLobStorage()
        +
        Description copied from interface: org.h2.store.DataHandler
        +
        Get the lob storage mechanism to use.
        +
        +
        Specified by:
        +
        getLobStorage in interface org.h2.store.DataHandler
        +
        Returns:
        +
        the lob storage mechanism
        +
        +
      • +
      + + + +
        +
      • +

        getLobSession

        +
        public SessionLocal getLobSession()
        +
      • +
      + + + +
        +
      • +

        getDefaultTableType

        +
        public int getDefaultTableType()
        +
      • +
      + + + +
        +
      • +

        setDefaultTableType

        +
        public void setDefaultTableType(int defaultTableType)
        +
      • +
      + + + +
        +
      • +

        getSettings

        +
        public DbSettings getSettings()
        +
      • +
      + + + +
        +
      • +

        newStringMap

        +
        public <V> java.util.HashMap<java.lang.String,V> newStringMap()
        +
        Create a new hash map. Depending on the configuration, the key is case + sensitive or case insensitive.
        +
        +
        Type Parameters:
        +
        V - the value type
        +
        Returns:
        +
        the hash map
        +
        +
      • +
      + + + +
        +
      • +

        newStringMap

        +
        public <V> java.util.HashMap<java.lang.String,V> newStringMap(int initialCapacity)
        +
        Create a new hash map. Depending on the configuration, the key is case + sensitive or case insensitive.
        +
        +
        Type Parameters:
        +
        V - the value type
        +
        Parameters:
        +
        initialCapacity - the initial capacity
        +
        Returns:
        +
        the hash map
        +
        +
      • +
      + + + +
        +
      • +

        newConcurrentStringMap

        +
        public <V> java.util.concurrent.ConcurrentHashMap<java.lang.String,V> newConcurrentStringMap()
        +
        Create a new hash map. Depending on the configuration, the key is case + sensitive or case insensitive.
        +
        +
        Type Parameters:
        +
        V - the value type
        +
        Returns:
        +
        the hash map
        +
        +
      • +
      + + + +
        +
      • +

        equalsIdentifiers

        +
        public boolean equalsIdentifiers(java.lang.String a,
        +                                 java.lang.String b)
        +
        Compare two identifiers (table names, column names,...) and verify they + are equal. Case sensitivity depends on the configuration.
        +
        +
        Parameters:
        +
        a - the first identifier
        +
        b - the second identifier
        +
        Returns:
        +
        true if they match
        +
        +
      • +
      + + + +
        +
      • +

        sysIdentifier

        +
        public java.lang.String sysIdentifier(java.lang.String upperName)
        +
        Returns identifier in upper or lower case depending on database settings.
        +
        +
        Parameters:
        +
        upperName - identifier in the upper case
        +
        Returns:
        +
        identifier in upper or lower case
        +
        +
      • +
      + + + +
        +
      • +

        readLob

        +
        public int readLob(long lobId,
        +                   byte[] hmac,
        +                   long offset,
        +                   byte[] buff,
        +                   int off,
        +                   int length)
        +
        Description copied from interface: org.h2.store.DataHandler
        +
        Read from a lob.
        +
        +
        Specified by:
        +
        readLob in interface org.h2.store.DataHandler
        +
        Parameters:
        +
        lobId - the lob id
        +
        hmac - the message authentication code
        +
        offset - the offset within the lob
        +
        buff - the target buffer
        +
        off - the offset within the target buffer
        +
        length - the number of bytes to read
        +
        Returns:
        +
        the number of bytes read
        +
        +
      • +
      + + + +
        +
      • +

        getFileEncryptionKey

        +
        public byte[] getFileEncryptionKey()
        +
      • +
      + + + +
        +
      • +

        getPageSize

        +
        public int getPageSize()
        +
      • +
      + + + + + + + +
        +
      • +

        setJavaObjectSerializerName

        +
        public void setJavaObjectSerializerName(java.lang.String serializerName)
        +
      • +
      + + + +
        +
      • +

        getTableEngine

        +
        public TableEngine getTableEngine(java.lang.String tableEngine)
        +
        Get the table engine class, loading it if needed.
        +
        +
        Parameters:
        +
        tableEngine - the table engine name
        +
        Returns:
        +
        the class
        +
        +
      • +
      + + + +
        +
      • +

        getAuthenticator

        +
        public org.h2.security.auth.Authenticator getAuthenticator()
        +
        get authenticator for database users
        +
        +
        Returns:
        +
        authenticator set for database
        +
        +
      • +
      + + + +
        +
      • +

        setAuthenticator

        +
        public void setAuthenticator(org.h2.security.auth.Authenticator authenticator)
        +
        Set current database authenticator
        +
        +
        Parameters:
        +
        authenticator - = authenticator to set, null to revert to the Internal authenticator
        +
        +
      • +
      + + + +
        +
      • +

        currentTimestamp

        +
        public org.h2.value.ValueTimestampTimeZone currentTimestamp()
        +
        Description copied from interface: CastDataProvider
        +
        Returns the current timestamp with maximum resolution. The value must be + the same within a transaction or within execution of a command.
        +
        +
        Specified by:
        +
        currentTimestamp in interface CastDataProvider
        +
        Returns:
        +
        the current timestamp for CURRENT_TIMESTAMP(9)
        +
        +
      • +
      + + + +
        +
      • +

        currentTimeZone

        +
        public org.h2.util.TimeZoneProvider currentTimeZone()
        +
        Description copied from interface: CastDataProvider
        +
        Returns the current time zone.
        +
        +
        Specified by:
        +
        currentTimeZone in interface CastDataProvider
        +
        Returns:
        +
        the current time zone
        +
        +
      • +
      + + + +
        +
      • +

        zeroBasedEnums

        +
        public boolean zeroBasedEnums()
        +
        Description copied from interface: CastDataProvider
        +
        Returns are ENUM values 0-based.
        +
        +
        Specified by:
        +
        zeroBasedEnums in interface CastDataProvider
        +
        Returns:
        +
        are ENUM values 0-based
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/DbObject.html b/h2/docs/javadoc/org/h2/engine/DbObject.html new file mode 100644 index 0000000..381384e --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/DbObject.html @@ -0,0 +1,1160 @@ + + + + + +DbObject + + + + + + + + + + + + +
+
org.h2.engine
+

Class DbObject

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.engine.DbObject
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    org.h2.util.HasSQL
    +
    +
    +
    Direct Known Subclasses:
    +
    Comment, Right, RightOwner, Setting
    +
    +
    +
    +
    public abstract class DbObject
    +extends java.lang.Object
    +implements org.h2.util.HasSQL
    +
    A database object such as a table, an index, or a user.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Fields 
      Modifier and TypeField and Description
      static intAGGREGATE +
      This object is a user-defined aggregate function.
      +
      protected java.lang.Stringcomment +
      The comment (if set).
      +
      static intCOMMENT +
      This object is a comment.
      +
      static intCONSTANT +
      This object is a constant.
      +
      static intCONSTRAINT +
      This object is a constraint (check constraint, unique constraint, or + referential constraint).
      +
      protected Databasedatabase +
      The database.
      +
      static intDOMAIN +
      This object is a domain.
      +
      static intFUNCTION_ALIAS +
      This object is an alias for a Java function.
      +
      static intINDEX +
      This object is an index.
      +
      static intRIGHT +
      This object is a right.
      +
      static intROLE +
      This object is a role.
      +
      static intSCHEMA +
      This object is a schema.
      +
      static intSEQUENCE +
      This object is a sequence.
      +
      static intSETTING +
      This object is a setting.
      +
      static intSYNONYM +
      This object is a synonym.
      +
      static intTABLE_OR_VIEW +
      The object is of the type table or view.
      +
      protected org.h2.message.Tracetrace +
      The trace module.
      +
      static intTRIGGER +
      This object is a trigger.
      +
      static intUSER +
      This object is a user.
      +
      +
        +
      • + + +

        Fields inherited from interface org.h2.util.HasSQL

        +ADD_PLAN_INFORMATION, DEFAULT_SQL_FLAGS, NO_CASTS, QUOTE_ONLY_WHEN_REQUIRED, REPLACE_LOBS_FOR_TRACE, TRACE_SQL_FLAGS
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + + + +
      Constructors 
      ModifierConstructor and Description
      protected DbObject(Database db, + int objectId, + java.lang.String name, + int traceModuleId) +
      Initialize some attributes of this object.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Abstract Methods Concrete Methods 
      Modifier and TypeMethod and Description
      voidcheckRename() +
      Check if renaming is allowed.
      +
      java.util.ArrayList<DbObject>getChildren() +
      Get the list of dependent children (for tables, this includes indexes and + so on).
      +
      java.lang.StringgetComment() +
      Get the current comment of this object.
      +
      abstract java.lang.StringgetCreateSQL() +
      Construct the CREATE ...
      +
      abstract java.lang.StringgetCreateSQLForCopy(org.h2.table.Table table, + java.lang.String quotedName) +
      Build a SQL statement to re-create the object, or to create a copy of the + object with a different name or referencing a different table
      +
      java.lang.StringgetCreateSQLForMeta() +
      Construct the CREATE ...
      +
      DatabasegetDatabase() +
      Get the database.
      +
      java.lang.StringgetDropSQL() +
      Construct a DROP ...
      +
      intgetId() +
      Get the unique object id.
      +
      longgetModificationId() 
      java.lang.StringgetName() +
      Get the name.
      +
      java.lang.StringgetSQL(int sqlFlags) +
      Get the SQL statement of this expression.
      +
      java.lang.StringBuildergetSQL(java.lang.StringBuilder builder, + int sqlFlags) +
      Appends the SQL statement of this object to the specified builder.
      +
      abstract intgetType() +
      Get the object type.
      +
      protected voidinvalidate() +
      Set the main attributes to null to make sure the object is no longer + used.
      +
      booleanisTemporary() +
      Check if this object is temporary (for example, a temporary table).
      +
      booleanisValid() 
      abstract voidremoveChildrenAndResources(SessionLocal session) +
      Delete all dependent children objects and resources of this object.
      +
      voidrename(java.lang.String newName) +
      Rename the object.
      +
      voidsetComment(java.lang.String comment) +
      Change the comment of this object.
      +
      voidsetModified() +
      Tell the object that is was modified.
      +
      protected voidsetObjectName(java.lang.String name) 
      voidsetTemporary(boolean temporary) +
      Tell this object that it is temporary or not.
      +
      java.lang.StringtoString() 
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
        +
      • + + +

        Methods inherited from interface org.h2.util.HasSQL

        +getTraceSQL
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Detail

      + + + +
        +
      • +

        TABLE_OR_VIEW

        +
        public static final int TABLE_OR_VIEW
        +
        The object is of the type table or view.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        INDEX

        +
        public static final int INDEX
        +
        This object is an index.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        USER

        +
        public static final int USER
        +
        This object is a user.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SEQUENCE

        +
        public static final int SEQUENCE
        +
        This object is a sequence.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        TRIGGER

        +
        public static final int TRIGGER
        +
        This object is a trigger.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CONSTRAINT

        +
        public static final int CONSTRAINT
        +
        This object is a constraint (check constraint, unique constraint, or + referential constraint).
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SETTING

        +
        public static final int SETTING
        +
        This object is a setting.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ROLE

        +
        public static final int ROLE
        +
        This object is a role.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        RIGHT

        +
        public static final int RIGHT
        +
        This object is a right.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        FUNCTION_ALIAS

        +
        public static final int FUNCTION_ALIAS
        +
        This object is an alias for a Java function.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SCHEMA

        +
        public static final int SCHEMA
        +
        This object is a schema.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        CONSTANT

        +
        public static final int CONSTANT
        +
        This object is a constant.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DOMAIN

        +
        public static final int DOMAIN
        +
        This object is a domain.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        COMMENT

        +
        public static final int COMMENT
        +
        This object is a comment.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        AGGREGATE

        +
        public static final int AGGREGATE
        +
        This object is a user-defined aggregate function.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SYNONYM

        +
        public static final int SYNONYM
        +
        This object is a synonym.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        database

        +
        protected Database database
        +
        The database.
        +
      • +
      + + + +
        +
      • +

        trace

        +
        protected org.h2.message.Trace trace
        +
        The trace module.
        +
      • +
      + + + +
        +
      • +

        comment

        +
        protected java.lang.String comment
        +
        The comment (if set).
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        DbObject

        +
        protected DbObject(Database db,
        +                   int objectId,
        +                   java.lang.String name,
        +                   int traceModuleId)
        +
        Initialize some attributes of this object.
        +
        +
        Parameters:
        +
        db - the database
        +
        objectId - the object id
        +
        name - the name
        +
        traceModuleId - the trace module id
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        setModified

        +
        public final void setModified()
        +
        Tell the object that is was modified.
        +
      • +
      + + + +
        +
      • +

        getModificationId

        +
        public final long getModificationId()
        +
      • +
      + + + +
        +
      • +

        setObjectName

        +
        protected final void setObjectName(java.lang.String name)
        +
      • +
      + + + +
        +
      • +

        getSQL

        +
        public java.lang.String getSQL(int sqlFlags)
        +
        Description copied from interface: org.h2.util.HasSQL
        +
        Get the SQL statement of this expression. This may not always be the + original SQL statement, specially after optimization.
        +
        +
        Specified by:
        +
        getSQL in interface org.h2.util.HasSQL
        +
        Parameters:
        +
        sqlFlags - formatting flags
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        getSQL

        +
        public java.lang.StringBuilder getSQL(java.lang.StringBuilder builder,
        +                                      int sqlFlags)
        +
        Description copied from interface: org.h2.util.HasSQL
        +
        Appends the SQL statement of this object to the specified builder.
        +
        +
        Specified by:
        +
        getSQL in interface org.h2.util.HasSQL
        +
        Parameters:
        +
        builder - string builder
        +
        sqlFlags - formatting flags
        +
        Returns:
        +
        the specified string builder
        +
        +
      • +
      + + + +
        +
      • +

        getChildren

        +
        public java.util.ArrayList<DbObject> getChildren()
        +
        Get the list of dependent children (for tables, this includes indexes and + so on).
        +
        +
        Returns:
        +
        the list of children, or null
        +
        +
      • +
      + + + +
        +
      • +

        getDatabase

        +
        public final Database getDatabase()
        +
        Get the database.
        +
        +
        Returns:
        +
        the database
        +
        +
      • +
      + + + +
        +
      • +

        getId

        +
        public final int getId()
        +
        Get the unique object id.
        +
        +
        Returns:
        +
        the object id
        +
        +
      • +
      + + + +
        +
      • +

        getName

        +
        public final java.lang.String getName()
        +
        Get the name.
        +
        +
        Returns:
        +
        the name
        +
        +
      • +
      + + + +
        +
      • +

        invalidate

        +
        protected void invalidate()
        +
        Set the main attributes to null to make sure the object is no longer + used.
        +
      • +
      + + + +
        +
      • +

        isValid

        +
        public final boolean isValid()
        +
      • +
      + + + +
        +
      • +

        getCreateSQLForCopy

        +
        public abstract java.lang.String getCreateSQLForCopy(org.h2.table.Table table,
        +                                                     java.lang.String quotedName)
        +
        Build a SQL statement to re-create the object, or to create a copy of the + object with a different name or referencing a different table
        +
        +
        Parameters:
        +
        table - the new table
        +
        quotedName - the quoted name
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        getCreateSQLForMeta

        +
        public java.lang.String getCreateSQLForMeta()
        +
        Construct the CREATE ... SQL statement for this object for meta table.
        +
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        getCreateSQL

        +
        public abstract java.lang.String getCreateSQL()
        +
        Construct the CREATE ... SQL statement for this object.
        +
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        getDropSQL

        +
        public java.lang.String getDropSQL()
        +
        Construct a DROP ... SQL statement for this object.
        +
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        getType

        +
        public abstract int getType()
        +
        Get the object type.
        +
        +
        Returns:
        +
        the object type
        +
        +
      • +
      + + + +
        +
      • +

        removeChildrenAndResources

        +
        public abstract void removeChildrenAndResources(SessionLocal session)
        +
        Delete all dependent children objects and resources of this object.
        +
        +
        Parameters:
        +
        session - the session
        +
        +
      • +
      + + + +
        +
      • +

        checkRename

        +
        public void checkRename()
        +
        Check if renaming is allowed. Does nothing when allowed.
        +
      • +
      + + + +
        +
      • +

        rename

        +
        public void rename(java.lang.String newName)
        +
        Rename the object.
        +
        +
        Parameters:
        +
        newName - the new name
        +
        +
      • +
      + + + +
        +
      • +

        isTemporary

        +
        public boolean isTemporary()
        +
        Check if this object is temporary (for example, a temporary table).
        +
        +
        Returns:
        +
        true if is temporary
        +
        +
      • +
      + + + +
        +
      • +

        setTemporary

        +
        public void setTemporary(boolean temporary)
        +
        Tell this object that it is temporary or not.
        +
        +
        Parameters:
        +
        temporary - the new value
        +
        +
      • +
      + + + +
        +
      • +

        setComment

        +
        public void setComment(java.lang.String comment)
        +
        Change the comment of this object.
        +
        +
        Parameters:
        +
        comment - the new comment, or null for no comment
        +
        +
      • +
      + + + +
        +
      • +

        getComment

        +
        public java.lang.String getComment()
        +
        Get the current comment of this object.
        +
        +
        Returns:
        +
        the comment, or null if not set
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        +
        Overrides:
        +
        toString in class java.lang.Object
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/DbSettings.html b/h2/docs/javadoc/org/h2/engine/DbSettings.html new file mode 100644 index 0000000..44b3e7c --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/DbSettings.html @@ -0,0 +1,888 @@ + + + + + +DbSettings + + + + + + + + + + + + +
+
org.h2.engine
+

Class DbSettings

+
+
+ +
+
    +
  • +
    +
    +
    public class DbSettings
    +extends SettingsBase
    +
    This class contains various database-level settings. To override the + documented default value for a database, append the setting in the database + URL: "jdbc:h2:./test;ANALYZE_SAMPLE=1000" when opening the first connection + to the database. The settings can not be changed once the database is open. +

    + Some settings are a last resort and temporary solution to work around a + problem in the application or database engine. Also, there are system + properties to enable features that are not yet fully tested or that are not + backward compatible. +

    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Fields 
      Modifier and TypeField and Description
      intanalyzeAuto +
      Database setting ANALYZE_AUTO (default: 2000).
      +
      intanalyzeSample +
      Database setting ANALYZE_SAMPLE (default: 10000).
      +
      intautoCompactFillRate +
      Database setting AUTO_COMPACT_FILL_RATE + (default: 90, which means 90%, 0 disables auto-compacting).
      +
      booleancaseInsensitiveIdentifiers +
      Database setting CASE_INSENSITIVE_IDENTIFIERS (default: + false).
      +
      booleancompressData +
      Database setting COMPRESS + (default: false).
      +
      booleandatabaseToLower +
      Database setting DATABASE_TO_LOWER (default: false).
      +
      booleandatabaseToUpper +
      Database setting DATABASE_TO_UPPER (default: true).
      +
      booleandbCloseOnExit +
      Database setting DB_CLOSE_ON_EXIT (default: true).
      +
      static DbSettingsDEFAULT +
      INTERNAL.
      +
      booleandefaultConnection +
      Database setting DEFAULT_CONNECTION (default: false).
      +
      java.lang.StringdefaultEscape +
      Database setting DEFAULT_ESCAPE (default: \).
      +
      java.lang.StringdefaultTableEngine +
      Database setting DEFAULT_TABLE_ENGINE + (default: null).
      +
      booleandefragAlways +
      Database setting DEFRAG_ALWAYS (default: false) + Each time the database is closed normally, it is fully defragmented (the + same as SHUTDOWN DEFRAG).
      +
      booleandropRestrict +
      Database setting DROP_RESTRICT (default: true) + Whether the default action for DROP TABLE, DROP VIEW, DROP SCHEMA, DROP + DOMAIN, and DROP CONSTRAINT is RESTRICT.
      +
      intestimatedFunctionTableRows +
      Database setting ESTIMATED_FUNCTION_TABLE_ROWS (default: + 1000).
      +
      booleanignoreCatalogs +
      Database setting IGNORE_CATALOGS + (default: false).
      +
      intlobTimeout +
      Database setting LOB_TIMEOUT (default: 300000, + which means 5 minutes).
      +
      intmaxCompactTime +
      Database setting MAX_COMPACT_TIME (default: 200).
      +
      intmaxQueryTimeout +
      Database setting MAX_QUERY_TIMEOUT (default: 0).
      +
      booleanmvStore +
      Database setting MV_STORE + (default: true).
      +
      booleanoptimizeDistinct +
      Database setting OPTIMIZE_DISTINCT (default: true).
      +
      booleanoptimizeEvaluatableSubqueries +
      Database setting OPTIMIZE_EVALUATABLE_SUBQUERIES (default: + true).
      +
      booleanoptimizeInList +
      Database setting OPTIMIZE_IN_LIST (default: true).
      +
      booleanoptimizeInSelect +
      Database setting OPTIMIZE_IN_SELECT (default: true).
      +
      booleanoptimizeInsertFromSelect +
      Database setting OPTIMIZE_INSERT_FROM_SELECT + (default: true).
      +
      booleanoptimizeOr +
      Database setting OPTIMIZE_OR (default: true).
      +
      booleanoptimizeSimpleSingleRowSubqueries +
      Database setting OPTIMIZE_SIMPLE_SINGLE_ROW_SUBQUERIES (default: true).
      +
      booleanoptimizeTwoEquals +
      Database setting OPTIMIZE_TWO_EQUALS (default: true).
      +
      intqueryCacheSize +
      Database setting QUERY_CACHE_SIZE (default: 8).
      +
      booleanrecompileAlways +
      Database setting RECOMPILE_ALWAYS (default: false).
      +
      booleanreuseSpace +
      Database setting REUSE_SPACE (default: true).
      +
      booleanshareLinkedConnections +
      Database setting SHARE_LINKED_CONNECTIONS + (default: true).
      +
      booleanzeroBasedEnums +
      Database setting ZERO_BASED_ENUMS + (default: false).
      +
      +
    • +
    + + +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Detail

      + + + +
        +
      • +

        DEFAULT

        +
        public static final DbSettings DEFAULT
        +
        INTERNAL. + The default settings. Those must not be modified.
        +
      • +
      + + + +
        +
      • +

        analyzeAuto

        +
        public final int analyzeAuto
        +
        Database setting ANALYZE_AUTO (default: 2000). + After changing this many rows, ANALYZE is automatically run for a table. + Automatically running ANALYZE is disabled if set to 0. If set to 1000, + then ANALYZE will run against each user table after about 1000 changes to + that table. The time between running ANALYZE doubles each time since + starting the database. It is not run on local temporary tables, and + tables that have a trigger on SELECT.
        +
      • +
      + + + +
        +
      • +

        analyzeSample

        +
        public final int analyzeSample
        +
        Database setting ANALYZE_SAMPLE (default: 10000). + The default sample size when analyzing a table.
        +
      • +
      + + + +
        +
      • +

        autoCompactFillRate

        +
        public final int autoCompactFillRate
        +
        Database setting AUTO_COMPACT_FILL_RATE + (default: 90, which means 90%, 0 disables auto-compacting). + Set the auto-compact target fill rate. If the average fill rate (the + percentage of the storage space that contains active data) of the + chunks is lower, then the chunks with a low fill rate are re-written. + Also, if the percentage of empty space between chunks is higher than + this value, then chunks at the end of the file are moved. Compaction + stops if the target fill rate is reached. + This setting only affects MVStore engine.
        +
      • +
      + + + +
        +
      • +

        databaseToLower

        +
        public final boolean databaseToLower
        +
        Database setting DATABASE_TO_LOWER (default: false). + When set to true unquoted identifiers and short name of database are + converted to lower case. Value of this setting should not be changed + after creation of database. Setting this to "true" is experimental.
        +
      • +
      + + + +
        +
      • +

        databaseToUpper

        +
        public final boolean databaseToUpper
        +
        Database setting DATABASE_TO_UPPER (default: true). + When set to true unquoted identifiers and short name of database are + converted to upper case.
        +
      • +
      + + + +
        +
      • +

        caseInsensitiveIdentifiers

        +
        public final boolean caseInsensitiveIdentifiers
        +
        Database setting CASE_INSENSITIVE_IDENTIFIERS (default: + false). + When set to true, all identifier names (table names, column names) are + case insensitive. Setting this to "true" is experimental.
        +
      • +
      + + + +
        +
      • +

        dbCloseOnExit

        +
        public final boolean dbCloseOnExit
        +
        Database setting DB_CLOSE_ON_EXIT (default: true). + Close the database when the virtual machine exits normally, using a + shutdown hook.
        +
      • +
      + + + +
        +
      • +

        defaultConnection

        +
        public final boolean defaultConnection
        +
        Database setting DEFAULT_CONNECTION (default: false). + Whether Java functions can use + DriverManager.getConnection("jdbc:default:connection") to + get a database connection. This feature is disabled by default for + performance reasons. Please note the Oracle JDBC driver will try to + resolve this database URL if it is loaded before the H2 driver.
        +
      • +
      + + + +
        +
      • +

        defaultEscape

        +
        public final java.lang.String defaultEscape
        +
        Database setting DEFAULT_ESCAPE (default: \). + The default escape character for LIKE comparisons. To select no escape + character, use an empty string.
        +
      • +
      + + + +
        +
      • +

        defragAlways

        +
        public final boolean defragAlways
        +
        Database setting DEFRAG_ALWAYS (default: false) + Each time the database is closed normally, it is fully defragmented (the + same as SHUTDOWN DEFRAG). If you execute SHUTDOWN COMPACT, then this + setting is ignored.
        +
      • +
      + + + +
        +
      • +

        dropRestrict

        +
        public final boolean dropRestrict
        +
        Database setting DROP_RESTRICT (default: true) + Whether the default action for DROP TABLE, DROP VIEW, DROP SCHEMA, DROP + DOMAIN, and DROP CONSTRAINT is RESTRICT.
        +
      • +
      + + + +
        +
      • +

        estimatedFunctionTableRows

        +
        public final int estimatedFunctionTableRows
        +
        Database setting ESTIMATED_FUNCTION_TABLE_ROWS (default: + 1000). + The estimated number of rows in a function table (for example, CSVREAD or + FTL_SEARCH). This value is used by the optimizer.
        +
      • +
      + + + +
        +
      • +

        lobTimeout

        +
        public final int lobTimeout
        +
        Database setting LOB_TIMEOUT (default: 300000, + which means 5 minutes). + The number of milliseconds a temporary LOB reference is kept until it + times out. After the timeout, the LOB is no longer accessible using this + reference.
        +
      • +
      + + + +
        +
      • +

        maxCompactTime

        +
        public final int maxCompactTime
        +
        Database setting MAX_COMPACT_TIME (default: 200). + The maximum time in milliseconds used to compact a database when closing.
        +
      • +
      + + + +
        +
      • +

        maxQueryTimeout

        +
        public final int maxQueryTimeout
        +
        Database setting MAX_QUERY_TIMEOUT (default: 0). + The maximum timeout of a query in milliseconds. The default is 0, meaning + no limit. Please note the actual query timeout may be set to a lower + value.
        +
      • +
      + + + +
        +
      • +

        optimizeDistinct

        +
        public final boolean optimizeDistinct
        +
        Database setting OPTIMIZE_DISTINCT (default: true). + Improve the performance of simple DISTINCT queries if an index is + available for the given column. The optimization is used if: +
          +
        • The select is a single column query without condition
        • +
        • The query contains only one table, and no group by
        • +
        • There is only one table involved
        • +
        • There is an ascending index on the column
        • +
        • The selectivity of the column is below 20
        • +
        +
      • +
      + + + +
        +
      • +

        optimizeEvaluatableSubqueries

        +
        public final boolean optimizeEvaluatableSubqueries
        +
        Database setting OPTIMIZE_EVALUATABLE_SUBQUERIES (default: + true). + Optimize subqueries that are not dependent on the outer query.
        +
      • +
      + + + +
        +
      • +

        optimizeInsertFromSelect

        +
        public final boolean optimizeInsertFromSelect
        +
        Database setting OPTIMIZE_INSERT_FROM_SELECT + (default: true). + Insert into table from query directly bypassing temporary disk storage. + This also applies to create table as select.
        +
      • +
      + + + +
        +
      • +

        optimizeInList

        +
        public final boolean optimizeInList
        +
        Database setting OPTIMIZE_IN_LIST (default: true). + Optimize IN(...) and IN(SELECT ...) comparisons. This includes + optimization for SELECT, DELETE, and UPDATE.
        +
      • +
      + + + +
        +
      • +

        optimizeInSelect

        +
        public final boolean optimizeInSelect
        +
        Database setting OPTIMIZE_IN_SELECT (default: true). + Optimize IN(SELECT ...) comparisons. This includes + optimization for SELECT, DELETE, and UPDATE.
        +
      • +
      + + + +
        +
      • +

        optimizeOr

        +
        public final boolean optimizeOr
        +
        Database setting OPTIMIZE_OR (default: true). + Convert (C=? OR C=?) to (C IN(?, ?)).
        +
      • +
      + + + +
        +
      • +

        optimizeTwoEquals

        +
        public final boolean optimizeTwoEquals
        +
        Database setting OPTIMIZE_TWO_EQUALS (default: true). + Optimize expressions of the form A=B AND B=1. In this case, AND A=1 is + added so an index on A can be used.
        +
      • +
      + + + +
        +
      • +

        optimizeSimpleSingleRowSubqueries

        +
        public final boolean optimizeSimpleSingleRowSubqueries
        +
        Database setting OPTIMIZE_SIMPLE_SINGLE_ROW_SUBQUERIES (default: true). + Optimize expressions of the form (SELECT A) to A.
        +
      • +
      + + + +
        +
      • +

        queryCacheSize

        +
        public final int queryCacheSize
        +
        Database setting QUERY_CACHE_SIZE (default: 8). + The size of the query cache, in number of cached statements. Each session + has it's own cache with the given size. The cache is only used if the SQL + statement and all parameters match. Only the last returned result per + query is cached. The following statement types are cached: SELECT + statements are cached (excluding UNION and FOR UPDATE statements), CALL + if it returns a single value, DELETE, INSERT, MERGE, UPDATE, and + transactional statements such as COMMIT. This works for both statements + and prepared statement.
        +
      • +
      + + + +
        +
      • +

        recompileAlways

        +
        public final boolean recompileAlways
        +
        Database setting RECOMPILE_ALWAYS (default: false). + Always recompile prepared statements.
        +
      • +
      + + + +
        +
      • +

        reuseSpace

        +
        public final boolean reuseSpace
        +
        Database setting REUSE_SPACE (default: true). + If disabled, all changes are appended to the database file, and existing + content is never overwritten. This setting has no effect if the database + is already open.
        +
      • +
      + + + +
        +
      • +

        shareLinkedConnections

        +
        public final boolean shareLinkedConnections
        +
        Database setting SHARE_LINKED_CONNECTIONS + (default: true). + Linked connections should be shared, that means connections to the same + database should be used for all linked tables that connect to the same + database.
        +
      • +
      + + + +
        +
      • +

        defaultTableEngine

        +
        public final java.lang.String defaultTableEngine
        +
        Database setting DEFAULT_TABLE_ENGINE + (default: null). + The default table engine to use for new tables.
        +
      • +
      + + + +
        +
      • +

        mvStore

        +
        public final boolean mvStore
        +
        Database setting MV_STORE + (default: true). + Use the MVStore storage engine.
        +
      • +
      + + + +
        +
      • +

        compressData

        +
        public final boolean compressData
        +
        Database setting COMPRESS + (default: false). + Compress data when storing.
        +
      • +
      + + + +
        +
      • +

        ignoreCatalogs

        +
        public final boolean ignoreCatalogs
        +
        Database setting IGNORE_CATALOGS + (default: false). + If set, all catalog names in identifiers are silently accepted + without comparing them with the short name of the database.
        +
      • +
      + + + +
        +
      • +

        zeroBasedEnums

        +
        public final boolean zeroBasedEnums
        +
        Database setting ZERO_BASED_ENUMS + (default: false). + If set, ENUM ordinal values are 0-based.
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/Engine.html b/h2/docs/javadoc/org/h2/engine/Engine.html new file mode 100644 index 0000000..e1f1932 --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/Engine.html @@ -0,0 +1,247 @@ + + + + + +Engine + + + + + + + + + + + + +
+
org.h2.engine
+

Class Engine

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.engine.Engine
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public final class Engine
    +extends java.lang.Object
    +
    The engine contains a map of all open databases. + It is also responsible for opening and creating new databases. + This is a singleton class.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethod and Description
      static SessionLocalcreateSession(ConnectionInfo ci) +
      Open a database connection with the given connection information.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        createSession

        +
        public static SessionLocal createSession(ConnectionInfo ci)
        +
        Open a database connection with the given connection information.
        +
        +
        Parameters:
        +
        ci - the connection information
        +
        Returns:
        +
        the session
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/GeneratedKeysMode.html b/h2/docs/javadoc/org/h2/engine/GeneratedKeysMode.html new file mode 100644 index 0000000..37d44fe --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/GeneratedKeysMode.html @@ -0,0 +1,352 @@ + + + + + +GeneratedKeysMode + + + + + + + + + + + + +
+
org.h2.engine
+

Class GeneratedKeysMode

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.engine.GeneratedKeysMode
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public final class GeneratedKeysMode
    +extends java.lang.Object
    +
    Modes of generated keys' gathering.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      + + + + + + + + + + + + + + + + + + + + + + +
      Fields 
      Modifier and TypeField and Description
      static intAUTO +
      Generated keys should be configured automatically.
      +
      static intCOLUMN_NAMES +
      Use specified column names to return generated keys from.
      +
      static intCOLUMN_NUMBERS +
      Use specified column indices to return generated keys from.
      +
      static intNONE +
      Generated keys are not needed.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethod and Description
      static intvalueOf(java.lang.Object generatedKeysRequest) +
      Determines mode of generated keys' gathering.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Detail

      + + + +
        +
      • +

        NONE

        +
        public static final int NONE
        +
        Generated keys are not needed.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        AUTO

        +
        public static final int AUTO
        +
        Generated keys should be configured automatically.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        COLUMN_NUMBERS

        +
        public static final int COLUMN_NUMBERS
        +
        Use specified column indices to return generated keys from.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        COLUMN_NAMES

        +
        public static final int COLUMN_NAMES
        +
        Use specified column names to return generated keys from.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        valueOf

        +
        public static int valueOf(java.lang.Object generatedKeysRequest)
        +
        Determines mode of generated keys' gathering.
        +
        +
        Parameters:
        +
        generatedKeysRequest - null or false if generated keys are not + needed, true if generated keys should be configured + automatically, int[] to specify column indices to + return generated keys from, or String[] to specify + column names to return generated keys from
        +
        Returns:
        +
        mode for the specified generated keys request
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/IsolationLevel.html b/h2/docs/javadoc/org/h2/engine/IsolationLevel.html new file mode 100644 index 0000000..3c14638 --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/IsolationLevel.html @@ -0,0 +1,547 @@ + + + + + +IsolationLevel + + + + + + + + + + + + +
+
org.h2.engine
+

Enum IsolationLevel

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • java.lang.Enum<IsolationLevel>
    • +
    • +
        +
      • org.h2.engine.IsolationLevel
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.lang.Comparable<IsolationLevel>
    +
    +
    +
    +
    public enum IsolationLevel
    +extends java.lang.Enum<IsolationLevel>
    +
    Level of isolation.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + + + + + + + +
      Enum Constants 
      Enum Constant and Description
      READ_COMMITTED +
      Dirty reads aren't allowed; non-repeatable reads and phantom reads are + allowed.
      +
      READ_UNCOMMITTED +
      Dirty reads, non-repeatable reads and phantom reads are allowed.
      +
      REPEATABLE_READ +
      Dirty reads and non-repeatable reads aren't allowed; phantom reads are + allowed.
      +
      SERIALIZABLE +
      Dirty reads, non-repeatable reads and phantom reads are'n allowed.
      +
      SNAPSHOT +
      Dirty reads, non-repeatable reads and phantom reads are'n allowed.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      booleanallowNonRepeatableRead() +
      Returns whether a non-repeatable read phenomena is allowed.
      +
      static IsolationLevelfromJdbc(int level) +
      Returns the isolation level from LOCK_MODE equivalent for PageStore and + old versions of H2.
      +
      static IsolationLevelfromLockMode(int lockMode) +
      Returns the isolation level from LOCK_MODE equivalent for PageStore and + old versions of H2.
      +
      static IsolationLevelfromSql(java.lang.String sql) +
      Returns the isolation level from its SQL name.
      +
      intgetJdbc() +
      Returns the JDBC constant for this isolation level.
      +
      intgetLockMode() +
      Returns the LOCK_MODE equivalent for PageStore and old versions of H2.
      +
      java.lang.StringgetSQL() +
      Returns the SQL representation of this isolation level.
      +
      static IsolationLevelvalueOf(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static IsolationLevel[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Enum Constant Detail

      + + + +
        +
      • +

        READ_UNCOMMITTED

        +
        public static final IsolationLevel READ_UNCOMMITTED
        +
        Dirty reads, non-repeatable reads and phantom reads are allowed.
        +
      • +
      + + + +
        +
      • +

        READ_COMMITTED

        +
        public static final IsolationLevel READ_COMMITTED
        +
        Dirty reads aren't allowed; non-repeatable reads and phantom reads are + allowed.
        +
      • +
      + + + +
        +
      • +

        REPEATABLE_READ

        +
        public static final IsolationLevel REPEATABLE_READ
        +
        Dirty reads and non-repeatable reads aren't allowed; phantom reads are + allowed.
        +
      • +
      + + + +
        +
      • +

        SNAPSHOT

        +
        public static final IsolationLevel SNAPSHOT
        +
        Dirty reads, non-repeatable reads and phantom reads are'n allowed.
        +
      • +
      + + + +
        +
      • +

        SERIALIZABLE

        +
        public static final IsolationLevel SERIALIZABLE
        +
        Dirty reads, non-repeatable reads and phantom reads are'n allowed. + Concurrent and serial execution of transactions with this isolation level + should have the same effect.
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static IsolationLevel[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (IsolationLevel c : IsolationLevel.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static IsolationLevel valueOf(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      + + + +
        +
      • +

        fromJdbc

        +
        public static IsolationLevel fromJdbc(int level)
        +
        Returns the isolation level from LOCK_MODE equivalent for PageStore and + old versions of H2.
        +
        +
        Parameters:
        +
        level - the LOCK_MODE value
        +
        Returns:
        +
        the isolation level
        +
        +
      • +
      + + + +
        +
      • +

        fromLockMode

        +
        public static IsolationLevel fromLockMode(int lockMode)
        +
        Returns the isolation level from LOCK_MODE equivalent for PageStore and + old versions of H2.
        +
        +
        Parameters:
        +
        lockMode - the LOCK_MODE value
        +
        Returns:
        +
        the isolation level
        +
        +
      • +
      + + + +
        +
      • +

        fromSql

        +
        public static IsolationLevel fromSql(java.lang.String sql)
        +
        Returns the isolation level from its SQL name.
        +
        +
        Parameters:
        +
        sql - the SQL name
        +
        Returns:
        +
        the isolation level from its SQL name
        +
        +
      • +
      + + + +
        +
      • +

        getSQL

        +
        public java.lang.String getSQL()
        +
        Returns the SQL representation of this isolation level.
        +
        +
        Returns:
        +
        SQL representation of this isolation level
        +
        +
      • +
      + + + +
        +
      • +

        getJdbc

        +
        public int getJdbc()
        +
        Returns the JDBC constant for this isolation level.
        +
        +
        Returns:
        +
        the JDBC constant for this isolation level
        +
        +
      • +
      + + + +
        +
      • +

        getLockMode

        +
        public int getLockMode()
        +
        Returns the LOCK_MODE equivalent for PageStore and old versions of H2.
        +
        +
        Returns:
        +
        the LOCK_MODE equivalent
        +
        +
      • +
      + + + +
        +
      • +

        allowNonRepeatableRead

        +
        public boolean allowNonRepeatableRead()
        +
        Returns whether a non-repeatable read phenomena is allowed.
        +
        +
        Returns:
        +
        whether a non-repeatable read phenomena is allowed
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/MetaRecord.html b/h2/docs/javadoc/org/h2/engine/MetaRecord.html new file mode 100644 index 0000000..61614a9 --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/MetaRecord.html @@ -0,0 +1,366 @@ + + + + + +MetaRecord + + + + + + + + + + + + +
+
org.h2.engine
+

Class MetaRecord

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.engine.MetaRecord
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.lang.Comparable<MetaRecord>
    +
    +
    +
    +
    public class MetaRecord
    +extends java.lang.Object
    +implements java.lang.Comparable<MetaRecord>
    +
    A record in the system table of the database. + It contains the SQL statement to create the database object.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      MetaRecord(org.h2.result.SearchRow r) 
      +
    • +
    + + +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        MetaRecord

        +
        public MetaRecord(org.h2.result.SearchRow r)
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        populateRowFromDBObject

        +
        public static void populateRowFromDBObject(DbObject obj,
        +                                           org.h2.result.SearchRow r)
        +
        Copy metadata from the specified object into specified search row.
        +
        +
        Parameters:
        +
        obj - database object
        +
        r - search row
        +
        +
      • +
      + + + +
        +
      • +

        getId

        +
        public int getId()
        +
      • +
      + + + +
        +
      • +

        getObjectType

        +
        public int getObjectType()
        +
      • +
      + + + +
        +
      • +

        getSQL

        +
        public java.lang.String getSQL()
        +
      • +
      + + + +
        +
      • +

        compareTo

        +
        public int compareTo(MetaRecord other)
        +
        Sort the list of meta records by 'create order'.
        +
        +
        Specified by:
        +
        compareTo in interface java.lang.Comparable<MetaRecord>
        +
        Parameters:
        +
        other - the other record
        +
        Returns:
        +
        -1, 0, or 1
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        +
        Overrides:
        +
        toString in class java.lang.Object
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/Mode.CharPadding.html b/h2/docs/javadoc/org/h2/engine/Mode.CharPadding.html new file mode 100644 index 0000000..0019b3e --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/Mode.CharPadding.html @@ -0,0 +1,369 @@ + + + + + +Mode.CharPadding + + + + + + + + + + + + +
+
org.h2.engine
+

Enum Mode.CharPadding

+
+
+
    +
  • java.lang.Object
  • +
  • + +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.lang.Comparable<Mode.CharPadding>
    +
    +
    +
    Enclosing class:
    +
    Mode
    +
    +
    +
    +
    public static enum Mode.CharPadding
    +extends java.lang.Enum<Mode.CharPadding>
    +
    When CHAR values are right-padded with spaces.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + +
      Enum Constants 
      Enum Constant and Description
      ALWAYS +
      CHAR values are always right-padded with spaces.
      +
      IN_RESULT_SETS +
      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
      +
      NEVER +
      Spaces are trimmed from the right side of CHAR values.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethod and Description
      static Mode.CharPaddingvalueOf(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static Mode.CharPadding[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Enum Constant Detail

      + + + +
        +
      • +

        ALWAYS

        +
        public static final Mode.CharPadding ALWAYS
        +
        CHAR values are always right-padded with spaces.
        +
      • +
      + + + +
        +
      • +

        IN_RESULT_SETS

        +
        public static final Mode.CharPadding IN_RESULT_SETS
        +
        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
        +
      • +
      + + + +
        +
      • +

        NEVER

        +
        public static final Mode.CharPadding NEVER
        +
        Spaces are trimmed from the right side of CHAR values.
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static Mode.CharPadding[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (Mode.CharPadding c : Mode.CharPadding.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static Mode.CharPadding valueOf(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/Mode.ExpressionNames.html b/h2/docs/javadoc/org/h2/engine/Mode.ExpressionNames.html new file mode 100644 index 0000000..3ad50b2 --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/Mode.ExpressionNames.html @@ -0,0 +1,410 @@ + + + + + +Mode.ExpressionNames + + + + + + + + + + + + +
+
org.h2.engine
+

Enum Mode.ExpressionNames

+
+
+
    +
  • java.lang.Object
  • +
  • + +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.lang.Comparable<Mode.ExpressionNames>
    +
    +
    +
    Enclosing class:
    +
    Mode
    +
    +
    +
    +
    public static enum Mode.ExpressionNames
    +extends java.lang.Enum<Mode.ExpressionNames>
    +
    Generation of column names for expressions.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + + + + + + + + + + +
      Enum Constants 
      Enum Constant and Description
      C_NUMBER +
      Use ordinal number of a column with C prefix.
      +
      EMPTY +
      Generate empty name.
      +
      NUMBER +
      Use ordinal number of a column.
      +
      OPTIMIZED_SQL +
      Use optimized SQL representation of expression.
      +
      ORIGINAL_SQL +
      Use original SQL representation of expression.
      +
      POSTGRESQL_STYLE +
      Use function name for functions and ?column? for other expressions
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethod and Description
      static Mode.ExpressionNamesvalueOf(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static Mode.ExpressionNames[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Enum Constant Detail

      + + + +
        +
      • +

        OPTIMIZED_SQL

        +
        public static final Mode.ExpressionNames OPTIMIZED_SQL
        +
        Use optimized SQL representation of expression.
        +
      • +
      + + + +
        +
      • +

        ORIGINAL_SQL

        +
        public static final Mode.ExpressionNames ORIGINAL_SQL
        +
        Use original SQL representation of expression.
        +
      • +
      + + + + + + + + + + + +
        +
      • +

        C_NUMBER

        +
        public static final Mode.ExpressionNames C_NUMBER
        +
        Use ordinal number of a column with C prefix.
        +
      • +
      + + + +
        +
      • +

        POSTGRESQL_STYLE

        +
        public static final Mode.ExpressionNames POSTGRESQL_STYLE
        +
        Use function name for functions and ?column? for other expressions
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static Mode.ExpressionNames[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (Mode.ExpressionNames c : Mode.ExpressionNames.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static Mode.ExpressionNames valueOf(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/Mode.ModeEnum.html b/h2/docs/javadoc/org/h2/engine/Mode.ModeEnum.html new file mode 100644 index 0000000..15c4113 --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/Mode.ModeEnum.html @@ -0,0 +1,451 @@ + + + + + +Mode.ModeEnum + + + + + + + + + + + + +
+
org.h2.engine
+

Enum Mode.ModeEnum

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • java.lang.Enum<Mode.ModeEnum>
    • +
    • +
        +
      • org.h2.engine.Mode.ModeEnum
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.lang.Comparable<Mode.ModeEnum>
    +
    +
    +
    Enclosing class:
    +
    Mode
    +
    +
    +
    +
    public static enum Mode.ModeEnum
    +extends java.lang.Enum<Mode.ModeEnum>
    +
  • +
+
+
+
    +
  • + + + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethod and Description
      static Mode.ModeEnumvalueOf(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static Mode.ModeEnum[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + + + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static Mode.ModeEnum[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (Mode.ModeEnum c : Mode.ModeEnum.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static Mode.ModeEnum valueOf(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/Mode.UniqueIndexNullsHandling.html b/h2/docs/javadoc/org/h2/engine/Mode.UniqueIndexNullsHandling.html new file mode 100644 index 0000000..a65d8bd --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/Mode.UniqueIndexNullsHandling.html @@ -0,0 +1,372 @@ + + + + + +Mode.UniqueIndexNullsHandling + + + + + + + + + + + + +
+
org.h2.engine
+

Enum Mode.UniqueIndexNullsHandling

+
+
+ +
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.lang.Comparable<Mode.UniqueIndexNullsHandling>
    +
    +
    +
    Enclosing class:
    +
    Mode
    +
    +
    +
    +
    public static enum Mode.UniqueIndexNullsHandling
    +extends java.lang.Enum<Mode.UniqueIndexNullsHandling>
    +
    Determines how rows with NULL values in indexed columns are handled + in unique indexes.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + +
      Enum Constants 
      Enum Constant and Description
      ALLOW_DUPLICATES_WITH_ALL_NULLS +
      Multiple rows with identical values in indexed columns with all indexed + NULL values are allowed in unique index.
      +
      ALLOW_DUPLICATES_WITH_ANY_NULL +
      Multiple rows with identical values in indexed columns with at least one + indexed NULL value are allowed in unique index.
      +
      FORBID_ANY_DUPLICATES +
      Multiple rows with identical values in indexed columns are not allowed in + unique index.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethod and Description
      static Mode.UniqueIndexNullsHandlingvalueOf(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static Mode.UniqueIndexNullsHandling[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Enum Constant Detail

      + + + +
        +
      • +

        ALLOW_DUPLICATES_WITH_ANY_NULL

        +
        public static final Mode.UniqueIndexNullsHandling ALLOW_DUPLICATES_WITH_ANY_NULL
        +
        Multiple rows with identical values in indexed columns with at least one + indexed NULL value are allowed in unique index.
        +
      • +
      + + + +
        +
      • +

        ALLOW_DUPLICATES_WITH_ALL_NULLS

        +
        public static final Mode.UniqueIndexNullsHandling ALLOW_DUPLICATES_WITH_ALL_NULLS
        +
        Multiple rows with identical values in indexed columns with all indexed + NULL values are allowed in unique index.
        +
      • +
      + + + +
        +
      • +

        FORBID_ANY_DUPLICATES

        +
        public static final Mode.UniqueIndexNullsHandling FORBID_ANY_DUPLICATES
        +
        Multiple rows with identical values in indexed columns are not allowed in + unique index.
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static Mode.UniqueIndexNullsHandling[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (Mode.UniqueIndexNullsHandling c : Mode.UniqueIndexNullsHandling.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static Mode.UniqueIndexNullsHandling valueOf(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/Mode.ViewExpressionNames.html b/h2/docs/javadoc/org/h2/engine/Mode.ViewExpressionNames.html new file mode 100644 index 0000000..18254fa --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/Mode.ViewExpressionNames.html @@ -0,0 +1,367 @@ + + + + + +Mode.ViewExpressionNames + + + + + + + + + + + + +
+
org.h2.engine
+

Enum Mode.ViewExpressionNames

+
+
+ +
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.lang.Comparable<Mode.ViewExpressionNames>
    +
    +
    +
    Enclosing class:
    +
    Mode
    +
    +
    +
    +
    public static enum Mode.ViewExpressionNames
    +extends java.lang.Enum<Mode.ViewExpressionNames>
    +
    Generation of column names for expressions to be used in a view.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + +
      Enum Constants 
      Enum Constant and Description
      AS_IS +
      Use both specified and generated names as is.
      +
      EXCEPTION +
      Throw exception for unspecified names.
      +
      MYSQL_STYLE +
      Use both specified and generated names as is, but replace too long + generated names with Name_exp_###.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethod and Description
      static Mode.ViewExpressionNamesvalueOf(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static Mode.ViewExpressionNames[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Enum Constant Detail

      + + + + + + + + + + + +
        +
      • +

        MYSQL_STYLE

        +
        public static final Mode.ViewExpressionNames MYSQL_STYLE
        +
        Use both specified and generated names as is, but replace too long + generated names with Name_exp_###.
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static Mode.ViewExpressionNames[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (Mode.ViewExpressionNames c : Mode.ViewExpressionNames.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static Mode.ViewExpressionNames valueOf(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/Mode.html b/h2/docs/javadoc/org/h2/engine/Mode.html new file mode 100644 index 0000000..7df821e --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/Mode.html @@ -0,0 +1,1332 @@ + + + + + +Mode + + + + + + + + + + + + +
+
org.h2.engine
+

Class Mode

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.engine.Mode
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public class Mode
    +extends java.lang.Object
    +
    The compatibility modes. There is a fixed set of modes (for example + PostgreSQL, MySQL). Each mode has different settings.
    +
  • +
+
+
+ +
+
+
    +
  • + +
      +
    • + + +

      Field Detail

      + + + +
        +
      • +

        aliasColumnName

        +
        public boolean aliasColumnName
        +
        When enabled, aliased columns (as in SELECT ID AS I FROM TEST) return the + alias (I in this case) in ResultSetMetaData.getColumnName() and 'null' in + getTableName(). If disabled, the real column name (ID in this case) and + table name is returned.
        +
      • +
      + + + +
        +
      • +

        convertOnlyToSmallerScale

        +
        public boolean convertOnlyToSmallerScale
        +
        When converting the scale of decimal data, the number is only converted + if the new scale is smaller than the current scale. Usually, the scale is + converted and 0s are added if required.
        +
      • +
      + + + +
        +
      • +

        indexDefinitionInCreateTable

        +
        public boolean indexDefinitionInCreateTable
        +
        Creating indexes in the CREATE TABLE statement is allowed using + INDEX(..) or KEY(..). + Example: create table test(id int primary key, name varchar(255), + key idx_name(name));
        +
      • +
      + + + +
        +
      • +

        squareBracketQuotedNames

        +
        public boolean squareBracketQuotedNames
        +
        Identifiers may be quoted using square brackets as in [Test].
        +
      • +
      + + + +
        +
      • +

        systemColumns

        +
        public boolean systemColumns
        +
        The system columns 'ctid' and 'oid' are supported.
        +
      • +
      + + + +
        +
      • +

        uniqueIndexNullsHandling

        +
        public Mode.UniqueIndexNullsHandling uniqueIndexNullsHandling
        +
        Determines how rows with NULL values in indexed columns are handled + in unique indexes.
        +
      • +
      + + + +
        +
      • +

        treatEmptyStringsAsNull

        +
        public boolean treatEmptyStringsAsNull
        +
        Empty strings are treated like NULL values. Useful for Oracle emulation.
        +
      • +
      + + + +
        +
      • +

        sysDummy1

        +
        public boolean sysDummy1
        +
        Support the pseudo-table SYSIBM.SYSDUMMY1.
        +
      • +
      + + + +
        +
      • +

        allowPlusForStringConcat

        +
        public boolean allowPlusForStringConcat
        +
        Text can be concatenated using '+'.
        +
      • +
      + + + +
        +
      • +

        logIsLogBase10

        +
        public boolean logIsLogBase10
        +
        The single-argument function LOG() uses base 10 instead of E.
        +
      • +
      + + + +
        +
      • +

        swapLogFunctionParameters

        +
        public boolean swapLogFunctionParameters
        +
        Swap the parameters of LOG() function.
        +
      • +
      + + + +
        +
      • +

        regexpReplaceBackslashReferences

        +
        public boolean regexpReplaceBackslashReferences
        +
        The function REGEXP_REPLACE() uses \ for back-references.
        +
      • +
      + + + +
        +
      • +

        swapConvertFunctionParameters

        +
        public boolean swapConvertFunctionParameters
        +
        Swap the parameters of the CONVERT function.
        +
      • +
      + + + +
        +
      • +

        isolationLevelInSelectOrInsertStatement

        +
        public boolean isolationLevelInSelectOrInsertStatement
        +
        can set the isolation level using WITH {RR|RS|CS|UR}
        +
      • +
      + + + +
        +
      • +

        onDuplicateKeyUpdate

        +
        public boolean onDuplicateKeyUpdate
        +
        MySQL style INSERT ... ON DUPLICATE KEY UPDATE ... and INSERT IGNORE.
        +
      • +
      + + + +
        +
      • +

        replaceInto

        +
        public boolean replaceInto
        +
        MySQL style REPLACE INTO.
        +
      • +
      + + + +
        +
      • +

        insertOnConflict

        +
        public boolean insertOnConflict
        +
        PostgreSQL style INSERT ... ON CONFLICT DO NOTHING.
        +
      • +
      + + + +
        +
      • +

        supportedClientInfoPropertiesRegEx

        +
        public java.util.regex.Pattern supportedClientInfoPropertiesRegEx
        +
        Pattern describing the keys the java.sql.Connection.setClientInfo() + method accepts.
        +
      • +
      + + + +
        +
      • +

        supportPoundSymbolForColumnNames

        +
        public boolean supportPoundSymbolForColumnNames
        +
        Support the # for column names
        +
      • +
      + + + +
        +
      • +

        allowEmptyInPredicate

        +
        public boolean allowEmptyInPredicate
        +
        Whether IN predicate may have an empty value list.
        +
      • +
      + + + +
        +
      • +

        charPadding

        +
        public Mode.CharPadding charPadding
        +
        How to pad or trim CHAR values.
        +
      • +
      + + + +
        +
      • +

        allowDB2TimestampFormat

        +
        public boolean allowDB2TimestampFormat
        +
        Whether DB2 TIMESTAMP formats are allowed.
        +
      • +
      + + + +
        +
      • +

        discardWithTableHints

        +
        public boolean discardWithTableHints
        +
        Discard SQLServer table hints (e.g. "SELECT * FROM table WITH (NOLOCK)")
        +
      • +
      + + + +
        +
      • +

        dateTimeValueWithinTransaction

        +
        public boolean dateTimeValueWithinTransaction
        +
        If true, datetime value function return the same value within a + transaction, if false datetime value functions return the same + value within a command.
        +
      • +
      + + + +
        +
      • +

        zeroExLiteralsAreBinaryStrings

        +
        public boolean zeroExLiteralsAreBinaryStrings
        +
        If true 0x-prefixed numbers are parsed as binary string + literals, if false they are parsed as hexadecimal numeric values.
        +
      • +
      + + + +
        +
      • +

        allowUnrelatedOrderByExpressionsInDistinctQueries

        +
        public boolean allowUnrelatedOrderByExpressionsInDistinctQueries
        +
        If true unrelated ORDER BY expression are allowed in DISTINCT + queries, if false they are disallowed.
        +
      • +
      + + + +
        +
      • +

        alterTableExtensionsMySQL

        +
        public boolean alterTableExtensionsMySQL
        +
        If true some additional non-standard ALTER TABLE commands are allowed.
        +
      • +
      + + + +
        +
      • +

        alterTableModifyColumn

        +
        public boolean alterTableModifyColumn
        +
        If true non-standard ALTER TABLE MODIFY COLUMN is allowed.
        +
      • +
      + + + +
        +
      • +

        truncateTableRestartIdentity

        +
        public boolean truncateTableRestartIdentity
        +
        If true TRUNCATE TABLE uses RESTART IDENTITY by default.
        +
      • +
      + + + +
        +
      • +

        decimalSequences

        +
        public boolean decimalSequences
        +
        If true NEXT VALUE FOR SEQUENCE, CURRENT VALUE FOR SEQUENCE, + SEQUENCE.NEXTVAL, and SEQUENCE.CURRVAL return values with DECIMAL/NUMERIC + data type instead of BIGINT.
        +
      • +
      + + + +
        +
      • +

        allowEmptySchemaValuesAsDefaultSchema

        +
        public boolean allowEmptySchemaValuesAsDefaultSchema
        +
        If true constructs like 'CREATE TABLE CATALOG..TABLE_NAME' are allowed, + the default schema is used.
        +
      • +
      + + + +
        +
      • +

        allNumericTypesHavePrecision

        +
        public boolean allNumericTypesHavePrecision
        +
        If true all numeric data types may have precision and 'UNSIGNED' + clause.
        +
      • +
      + + + +
        +
      • +

        forBitData

        +
        public boolean forBitData
        +
        If true 'FOR BIT DATA' clauses are allowed for character string + data types.
        +
      • +
      + + + +
        +
      • +

        charAndByteLengthUnits

        +
        public boolean charAndByteLengthUnits
        +
        If true 'CHAR' and 'BYTE' length units are allowed.
        +
      • +
      + + + +
        +
      • +

        nextvalAndCurrvalPseudoColumns

        +
        public boolean nextvalAndCurrvalPseudoColumns
        +
        If true, sequence.NEXTVAL and sequence.CURRVAL pseudo columns are + supported.
        +
      • +
      + + + +
        +
      • +

        nextValueReturnsDifferentValues

        +
        public boolean nextValueReturnsDifferentValues
        +
        If true, the next value expression returns different values when + invoked multiple times within a row. This setting does not affect + NEXTVAL() function.
        +
      • +
      + + + +
        +
      • +

        updateSequenceOnManualIdentityInsertion

        +
        public boolean updateSequenceOnManualIdentityInsertion
        +
        If true, sequences of generated by default identity columns are + updated when value is provided by user.
        +
      • +
      + + + +
        +
      • +

        takeInsertedIdentity

        +
        public boolean takeInsertedIdentity
        +
        If true, last identity of the session is updated on insertion of + a new value into identity column.
        +
      • +
      + + + +
        +
      • +

        takeGeneratedSequenceValue

        +
        public boolean takeGeneratedSequenceValue
        +
        If true, last identity of the session is updated on generation of + a new sequence value.
        +
      • +
      + + + +
        +
      • +

        identityColumnsHaveDefaultOnNull

        +
        public boolean identityColumnsHaveDefaultOnNull
        +
        If true, identity columns have DEFAULT ON NULL clause.
        +
      • +
      + + + +
        +
      • +

        mergeWhere

        +
        public boolean mergeWhere
        +
        If true, merge when matched clause may have WHERE clause.
        +
      • +
      + + + +
        +
      • +

        allowUsingFromClauseInUpdateStatement

        +
        public boolean allowUsingFromClauseInUpdateStatement
        +
        If true, allow using from clause in update statement.
        +
      • +
      + + + +
        +
      • +

        createUniqueConstraintForReferencedColumns

        +
        public boolean createUniqueConstraintForReferencedColumns
        +
        If true, referential constraints will create a unique constraint + on referenced columns if it doesn't exist instead of throwing an + exception.
        +
      • +
      + + + +
        +
      • +

        expressionNames

        +
        public Mode.ExpressionNames expressionNames
        +
        How column names are generated for expressions.
        +
      • +
      + + + +
        +
      • +

        viewExpressionNames

        +
        public Mode.ViewExpressionNames viewExpressionNames
        +
        How column names are generated for views.
        +
      • +
      + + + +
        +
      • +

        topInSelect

        +
        public boolean topInSelect
        +
        Whether TOP clause in SELECT queries is supported.
        +
      • +
      + + + +
        +
      • +

        topInDML

        +
        public boolean topInDML
        +
        Whether TOP clause in DML commands is supported.
        +
      • +
      + + + +
        +
      • +

        limit

        +
        public boolean limit
        +
        Whether LIMIT / OFFSET clauses are supported.
        +
      • +
      + + + +
        +
      • +

        minusIsExcept

        +
        public boolean minusIsExcept
        +
        Whether MINUS can be used as EXCEPT.
        +
      • +
      + + + +
        +
      • +

        identityDataType

        +
        public boolean identityDataType
        +
        Whether IDENTITY pseudo data type is supported.
        +
      • +
      + + + +
        +
      • +

        serialDataTypes

        +
        public boolean serialDataTypes
        +
        Whether SERIAL and BIGSERIAL pseudo data types are supported.
        +
      • +
      + + + +
        +
      • +

        identityClause

        +
        public boolean identityClause
        +
        Whether SQL Server-style IDENTITY clause is supported.
        +
      • +
      + + + +
        +
      • +

        autoIncrementClause

        +
        public boolean autoIncrementClause
        +
        Whether MySQL-style AUTO_INCREMENT clause is supported.
        +
      • +
      + + + +
        +
      • +

        disallowedTypes

        +
        public java.util.Set<java.lang.String> disallowedTypes
        +
        An optional Set of hidden/disallowed column types. + Certain DBMSs don't support all column types provided by H2, such as + "NUMBER" when using PostgreSQL mode.
        +
      • +
      + + + +
        +
      • +

        typeByNameMap

        +
        public java.util.HashMap<java.lang.String,org.h2.value.DataType> typeByNameMap
        +
        Custom mappings from type names to data types.
        +
      • +
      + + + +
        +
      • +

        groupByColumnIndex

        +
        public boolean groupByColumnIndex
        +
        Allow to use GROUP BY n, where n is column index in the SELECT list, similar to ORDER BY
        +
      • +
      + + + +
        +
      • +

        numericWithBooleanComparison

        +
        public boolean numericWithBooleanComparison
        +
        Allow to compare numeric with BOOLEAN.
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getInstance

        +
        public static Mode getInstance(java.lang.String name)
        +
        Get the mode with the given name.
        +
        +
        Parameters:
        +
        name - the name of the mode
        +
        Returns:
        +
        the mode object
        +
        +
      • +
      + + + +
        +
      • +

        getRegular

        +
        public static Mode getRegular()
        +
      • +
      + + + +
        +
      • +

        getName

        +
        public java.lang.String getName()
        +
      • +
      + + + + + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        +
        Overrides:
        +
        toString in class java.lang.Object
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/Procedure.html b/h2/docs/javadoc/org/h2/engine/Procedure.html new file mode 100644 index 0000000..e921430 --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/Procedure.html @@ -0,0 +1,286 @@ + + + + + +Procedure + + + + + + + + + + + + +
+
org.h2.engine
+

Class Procedure

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.engine.Procedure
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public class Procedure
    +extends java.lang.Object
    +
    Represents a procedure. Procedures are implemented for PostgreSQL + compatibility.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      Procedure(java.lang.String name, + org.h2.command.Prepared prepared) 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      java.lang.StringgetName() 
      org.h2.command.PreparedgetPrepared() 
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        Procedure

        +
        public Procedure(java.lang.String name,
        +                 org.h2.command.Prepared prepared)
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getName

        +
        public java.lang.String getName()
        +
      • +
      + + + +
        +
      • +

        getPrepared

        +
        public org.h2.command.Prepared getPrepared()
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/QueryStatisticsData.QueryEntry.html b/h2/docs/javadoc/org/h2/engine/QueryStatisticsData.QueryEntry.html new file mode 100644 index 0000000..00ae31c --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/QueryStatisticsData.QueryEntry.html @@ -0,0 +1,488 @@ + + + + + +QueryStatisticsData.QueryEntry + + + + + + + + + + + + +
+
org.h2.engine
+

Class QueryStatisticsData.QueryEntry

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.engine.QueryStatisticsData.QueryEntry
    • +
    +
  • +
+
+
    +
  • +
    +
    Enclosing class:
    +
    QueryStatisticsData
    +
    +
    +
    +
    public static final class QueryStatisticsData.QueryEntry
    +extends java.lang.Object
    +
    The collected statistics for one query.
    +
  • +
+
+
+ +
+
+
    +
  • + +
      +
    • + + +

      Field Detail

      + + + +
        +
      • +

        sqlStatement

        +
        public final java.lang.String sqlStatement
        +
        The SQL statement.
        +
      • +
      + + + +
        +
      • +

        count

        +
        public int count
        +
        The number of times the statement was executed.
        +
      • +
      + + + +
        +
      • +

        lastUpdateTime

        +
        public long lastUpdateTime
        +
        The last time the statistics for this entry were updated, + in milliseconds since 1970.
        +
      • +
      + + + +
        +
      • +

        executionTimeMinNanos

        +
        public long executionTimeMinNanos
        +
        The minimum execution time, in nanoseconds.
        +
      • +
      + + + +
        +
      • +

        executionTimeMaxNanos

        +
        public long executionTimeMaxNanos
        +
        The maximum execution time, in nanoseconds.
        +
      • +
      + + + +
        +
      • +

        executionTimeCumulativeNanos

        +
        public long executionTimeCumulativeNanos
        +
        The total execution time.
        +
      • +
      + + + +
        +
      • +

        rowCountMin

        +
        public long rowCountMin
        +
        The minimum number of rows.
        +
      • +
      + + + +
        +
      • +

        rowCountMax

        +
        public long rowCountMax
        +
        The maximum number of rows.
        +
      • +
      + + + +
        +
      • +

        rowCountCumulative

        +
        public long rowCountCumulative
        +
        The total number of rows.
        +
      • +
      + + + +
        +
      • +

        executionTimeMeanNanos

        +
        public double executionTimeMeanNanos
        +
        The mean execution time.
        +
      • +
      + + + +
        +
      • +

        rowCountMean

        +
        public double rowCountMean
        +
        The mean number of rows.
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        QueryEntry

        +
        public QueryEntry(java.lang.String sql)
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getExecutionTimeStandardDeviation

        +
        public double getExecutionTimeStandardDeviation()
        +
      • +
      + + + +
        +
      • +

        getRowCountStandardDeviation

        +
        public double getRowCountStandardDeviation()
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/QueryStatisticsData.html b/h2/docs/javadoc/org/h2/engine/QueryStatisticsData.html new file mode 100644 index 0000000..cd174e0 --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/QueryStatisticsData.html @@ -0,0 +1,331 @@ + + + + + +QueryStatisticsData + + + + + + + + + + + + +
+
org.h2.engine
+

Class QueryStatisticsData

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.engine.QueryStatisticsData
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public class QueryStatisticsData
    +extends java.lang.Object
    +
    Maintains query statistics.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Nested Class Summary

      + + + + + + + + + + +
      Nested Classes 
      Modifier and TypeClass and Description
      static class QueryStatisticsData.QueryEntry +
      The collected statistics for one query.
      +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      QueryStatisticsData(int maxQueryEntries) 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      java.util.List<QueryStatisticsData.QueryEntry>getQueries() 
      voidsetMaxQueryEntries(int maxQueryEntries) 
      voidupdate(java.lang.String sqlStatement, + long executionTimeNanos, + long rowCount) +
      Update query statistics.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        QueryStatisticsData

        +
        public QueryStatisticsData(int maxQueryEntries)
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        setMaxQueryEntries

        +
        public void setMaxQueryEntries(int maxQueryEntries)
        +
      • +
      + + + + + + + +
        +
      • +

        update

        +
        public void update(java.lang.String sqlStatement,
        +                   long executionTimeNanos,
        +                   long rowCount)
        +
        Update query statistics.
        +
        +
        Parameters:
        +
        sqlStatement - the statement being executed
        +
        executionTimeNanos - the time in nanoseconds the query/update took + to execute
        +
        rowCount - the query or update row count
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/Right.html b/h2/docs/javadoc/org/h2/engine/Right.html new file mode 100644 index 0000000..6851d4f --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/Right.html @@ -0,0 +1,685 @@ + + + + + +Right + + + + + + + + + + + + +
+
org.h2.engine
+

Class Right

+
+
+ +
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    org.h2.util.HasSQL
    +
    +
    +
    +
    public final class Right
    +extends DbObject
    +
    An access right. Rights are regular database objects, but have generated + names.
    +
  • +
+
+
+ +
+
+
    +
  • + +
      +
    • + + +

      Field Detail

      + + + +
        +
      • +

        SELECT

        +
        public static final int SELECT
        +
        The right bit mask that means: selecting from a table is allowed.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        DELETE

        +
        public static final int DELETE
        +
        The right bit mask that means: deleting rows from a table is allowed.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        INSERT

        +
        public static final int INSERT
        +
        The right bit mask that means: inserting rows into a table is allowed.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        UPDATE

        +
        public static final int UPDATE
        +
        The right bit mask that means: updating data is allowed.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ALTER_ANY_SCHEMA

        +
        public static final int ALTER_ANY_SCHEMA
        +
        The right bit mask that means: create/alter/drop schema is allowed.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        SCHEMA_OWNER

        +
        public static final int SCHEMA_OWNER
        +
        The right bit mask that means: user is a schema owner. This mask isn't + used in GRANT / REVOKE statements.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        ALL

        +
        public static final int ALL
        +
        The right bit mask that means: select, insert, update, delete, and update + for this object is allowed.
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      +
    • +
    + + + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getRights

        +
        public java.lang.String getRights()
        +
      • +
      + + + +
        +
      • +

        getGrantedRole

        +
        public Role getGrantedRole()
        +
      • +
      + + + +
        +
      • +

        getGrantedObject

        +
        public DbObject getGrantedObject()
        +
      • +
      + + + +
        +
      • +

        getGrantee

        +
        public DbObject getGrantee()
        +
      • +
      + + + +
        +
      • +

        getCreateSQLForCopy

        +
        public java.lang.String getCreateSQLForCopy(org.h2.table.Table table,
        +                                            java.lang.String quotedName)
        +
        Description copied from class: DbObject
        +
        Build a SQL statement to re-create the object, or to create a copy of the + object with a different name or referencing a different table
        +
        +
        Specified by:
        +
        getCreateSQLForCopy in class DbObject
        +
        Parameters:
        +
        table - the new table
        +
        quotedName - the quoted name
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        getCreateSQL

        +
        public java.lang.String getCreateSQL()
        +
        Description copied from class: DbObject
        +
        Construct the CREATE ... SQL statement for this object.
        +
        +
        Specified by:
        +
        getCreateSQL in class DbObject
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        getType

        +
        public int getType()
        +
        Description copied from class: DbObject
        +
        Get the object type.
        +
        +
        Specified by:
        +
        getType in class DbObject
        +
        Returns:
        +
        the object type
        +
        +
      • +
      + + + +
        +
      • +

        removeChildrenAndResources

        +
        public void removeChildrenAndResources(SessionLocal session)
        +
        Description copied from class: DbObject
        +
        Delete all dependent children objects and resources of this object.
        +
        +
        Specified by:
        +
        removeChildrenAndResources in class DbObject
        +
        Parameters:
        +
        session - the session
        +
        +
      • +
      + + + +
        +
      • +

        checkRename

        +
        public void checkRename()
        +
        Description copied from class: DbObject
        +
        Check if renaming is allowed. Does nothing when allowed.
        +
        +
        Overrides:
        +
        checkRename in class DbObject
        +
        +
      • +
      + + + +
        +
      • +

        setRightMask

        +
        public void setRightMask(int rightMask)
        +
      • +
      + + + +
        +
      • +

        getRightMask

        +
        public int getRightMask()
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/RightOwner.html b/h2/docs/javadoc/org/h2/engine/RightOwner.html new file mode 100644 index 0000000..a5f07a8 --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/RightOwner.html @@ -0,0 +1,487 @@ + + + + + +RightOwner + + + + + + + + + + + + +
+
org.h2.engine
+

Class RightOwner

+
+
+ +
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    org.h2.util.HasSQL
    +
    +
    +
    Direct Known Subclasses:
    +
    Role, User
    +
    +
    +
    +
    public abstract class RightOwner
    +extends DbObject
    +
    A right owner (sometimes called principal).
    +
  • +
+
+
+ +
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        RightOwner

        +
        protected RightOwner(Database database,
        +                     int id,
        +                     java.lang.String name,
        +                     int traceModuleId)
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        rename

        +
        public void rename(java.lang.String newName)
        +
        Description copied from class: DbObject
        +
        Rename the object.
        +
        +
        Overrides:
        +
        rename in class DbObject
        +
        Parameters:
        +
        newName - the new name
        +
        +
      • +
      + + + +
        +
      • +

        isRoleGranted

        +
        public boolean isRoleGranted(Role grantedRole)
        +
        Check if a role has been granted for this right owner.
        +
        +
        Parameters:
        +
        grantedRole - the role
        +
        Returns:
        +
        true if the role has been granted
        +
        +
      • +
      + + + +
        +
      • +

        grantRight

        +
        public void grantRight(DbObject object,
        +                       Right right)
        +
        Grant a right for the given table. Only one right object per table is + supported.
        +
        +
        Parameters:
        +
        object - the object (table or schema)
        +
        right - the right
        +
        +
      • +
      + + + +
        +
      • +

        grantRole

        +
        public void grantRole(Role role,
        +                      Right right)
        +
        Grant a role to this object.
        +
        +
        Parameters:
        +
        role - the role
        +
        right - the right to grant
        +
        +
      • +
      + + + +
        +
      • +

        revokeTemporaryRightsOnRoles

        +
        public void revokeTemporaryRightsOnRoles()
        +
        Remove all the temporary rights granted on roles
        +
      • +
      + + + +
        +
      • +

        getRightForObject

        +
        public Right getRightForObject(DbObject object)
        +
        Get the 'grant schema' right of this object.
        +
        +
        Parameters:
        +
        object - the granted object (table or schema)
        +
        Returns:
        +
        the right or null if the right has not been granted
        +
        +
      • +
      + + + +
        +
      • +

        getRightForRole

        +
        public Right getRightForRole(Role role)
        +
        Get the 'grant role' right of this object.
        +
        +
        Parameters:
        +
        role - the granted role
        +
        Returns:
        +
        the right or null if the right has not been granted
        +
        +
      • +
      + + + +
        +
      • +

        checkOwnsNoSchemas

        +
        public final void checkOwnsNoSchemas()
        +
        Check that this right owner does not own any schema. An exception is + thrown if it owns one or more schemas.
        +
        +
        Throws:
        +
        org.h2.message.DbException - if this right owner owns a schema
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/Role.html b/h2/docs/javadoc/org/h2/engine/Role.html new file mode 100644 index 0000000..7f6a94c --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/Role.html @@ -0,0 +1,466 @@ + + + + + +Role + + + + + + + + + + + + +
+
org.h2.engine
+

Class Role

+
+
+ +
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    org.h2.util.HasSQL
    +
    +
    +
    +
    public final class Role
    +extends RightOwner
    +
    Represents a role. Roles can be granted to users, and to other roles.
    +
  • +
+
+
+ +
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        Role

        +
        public Role(Database database,
        +            int id,
        +            java.lang.String roleName,
        +            boolean system)
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getCreateSQLForCopy

        +
        public java.lang.String getCreateSQLForCopy(org.h2.table.Table table,
        +                                            java.lang.String quotedName)
        +
        Description copied from class: DbObject
        +
        Build a SQL statement to re-create the object, or to create a copy of the + object with a different name or referencing a different table
        +
        +
        Specified by:
        +
        getCreateSQLForCopy in class DbObject
        +
        Parameters:
        +
        table - the new table
        +
        quotedName - the quoted name
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        getCreateSQL

        +
        public java.lang.String getCreateSQL(boolean ifNotExists)
        +
        Get the CREATE SQL statement for this object.
        +
        +
        Parameters:
        +
        ifNotExists - true if IF NOT EXISTS should be used
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        getCreateSQL

        +
        public java.lang.String getCreateSQL()
        +
        Description copied from class: DbObject
        +
        Construct the CREATE ... SQL statement for this object.
        +
        +
        Specified by:
        +
        getCreateSQL in class DbObject
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        getType

        +
        public int getType()
        +
        Description copied from class: DbObject
        +
        Get the object type.
        +
        +
        Specified by:
        +
        getType in class DbObject
        +
        Returns:
        +
        the object type
        +
        +
      • +
      + + + +
        +
      • +

        getChildren

        +
        public java.util.ArrayList<DbObject> getChildren()
        +
        Description copied from class: DbObject
        +
        Get the list of dependent children (for tables, this includes indexes and + so on).
        +
        +
        Overrides:
        +
        getChildren in class DbObject
        +
        Returns:
        +
        the list of children, or null
        +
        +
      • +
      + + + +
        +
      • +

        removeChildrenAndResources

        +
        public void removeChildrenAndResources(SessionLocal session)
        +
        Description copied from class: DbObject
        +
        Delete all dependent children objects and resources of this object.
        +
        +
        Specified by:
        +
        removeChildrenAndResources in class DbObject
        +
        Parameters:
        +
        session - the session
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/Session.DynamicSettings.html b/h2/docs/javadoc/org/h2/engine/Session.DynamicSettings.html new file mode 100644 index 0000000..16ea4eb --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/Session.DynamicSettings.html @@ -0,0 +1,305 @@ + + + + + +Session.DynamicSettings + + + + + + + + + + + + +
+
org.h2.engine
+

Class Session.DynamicSettings

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.engine.Session.DynamicSettings
    • +
    +
  • +
+
+
    +
  • +
    +
    Enclosing class:
    +
    Session
    +
    +
    +
    +
    public static final class Session.DynamicSettings
    +extends java.lang.Object
    +
    Dynamic settings.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      + + + + + + + + + + + + + + +
      Fields 
      Modifier and TypeField and Description
      Modemode +
      The database mode.
      +
      org.h2.util.TimeZoneProvidertimeZone +
      The current time zone.
      +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      DynamicSettings(Mode mode, + org.h2.util.TimeZoneProvider timeZone) +
      Creates new instance of dynamic settings.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Detail

      + + + +
        +
      • +

        mode

        +
        public final Mode mode
        +
        The database mode.
        +
      • +
      + + + +
        +
      • +

        timeZone

        +
        public final org.h2.util.TimeZoneProvider timeZone
        +
        The current time zone.
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        DynamicSettings

        +
        public DynamicSettings(Mode mode,
        +                       org.h2.util.TimeZoneProvider timeZone)
        +
        Creates new instance of dynamic settings.
        +
        +
        Parameters:
        +
        mode - the database mode
        +
        timeZone - the current time zone
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/Session.StaticSettings.html b/h2/docs/javadoc/org/h2/engine/Session.StaticSettings.html new file mode 100644 index 0000000..76f6a1f --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/Session.StaticSettings.html @@ -0,0 +1,324 @@ + + + + + +Session.StaticSettings + + + + + + + + + + + + +
+
org.h2.engine
+

Class Session.StaticSettings

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.engine.Session.StaticSettings
    • +
    +
  • +
+
+
    +
  • +
    +
    Enclosing class:
    +
    Session
    +
    +
    +
    +
    public static final class Session.StaticSettings
    +extends java.lang.Object
    +
    Static settings.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      + + + + + + + + + + + + + + + + + + +
      Fields 
      Modifier and TypeField and Description
      booleancaseInsensitiveIdentifiers +
      Whether all identifiers are case insensitive.
      +
      booleandatabaseToLower +
      Whether unquoted identifiers are converted to lower case.
      +
      booleandatabaseToUpper +
      Whether unquoted identifiers are converted to upper case.
      +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      StaticSettings(boolean databaseToUpper, + boolean databaseToLower, + boolean caseInsensitiveIdentifiers) +
      Creates new instance of static settings.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Detail

      + + + +
        +
      • +

        databaseToUpper

        +
        public final boolean databaseToUpper
        +
        Whether unquoted identifiers are converted to upper case.
        +
      • +
      + + + +
        +
      • +

        databaseToLower

        +
        public final boolean databaseToLower
        +
        Whether unquoted identifiers are converted to lower case.
        +
      • +
      + + + +
        +
      • +

        caseInsensitiveIdentifiers

        +
        public final boolean caseInsensitiveIdentifiers
        +
        Whether all identifiers are case insensitive.
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        StaticSettings

        +
        public StaticSettings(boolean databaseToUpper,
        +                      boolean databaseToLower,
        +                      boolean caseInsensitiveIdentifiers)
        +
        Creates new instance of static settings.
        +
        +
        Parameters:
        +
        databaseToUpper - whether unquoted identifiers are converted to upper case
        +
        databaseToLower - whether unquoted identifiers are converted to lower case
        +
        caseInsensitiveIdentifiers - whether all identifiers are case insensitive
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/Session.html b/h2/docs/javadoc/org/h2/engine/Session.html new file mode 100644 index 0000000..2aef10e --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/Session.html @@ -0,0 +1,751 @@ + + + + + +Session + + + + + + + + + + + + +
+
org.h2.engine
+

Class Session

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.engine.Session
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.lang.AutoCloseable, CastDataProvider
    +
    +
    +
    Direct Known Subclasses:
    +
    SessionLocal, SessionRemote
    +
    +
    +
    +
    public abstract class Session
    +extends java.lang.Object
    +implements CastDataProvider, java.lang.AutoCloseable
    +
    A local or remote session. A session represents a database connection.
    +
  • +
+
+
+ +
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getClusterServers

        +
        public abstract java.util.ArrayList<java.lang.String> getClusterServers()
        +
        Get the list of the cluster servers for this session.
        +
        +
        Returns:
        +
        A list of "ip:port" strings for the cluster servers in this + session.
        +
        +
      • +
      + + + +
        +
      • +

        prepareCommand

        +
        public abstract org.h2.command.CommandInterface prepareCommand(java.lang.String sql,
        +                                                               int fetchSize)
        +
        Parse a command and prepare it for execution.
        +
        +
        Parameters:
        +
        sql - the SQL statement
        +
        fetchSize - the number of rows to fetch in one step
        +
        Returns:
        +
        the prepared command
        +
        +
      • +
      + + + +
        +
      • +

        close

        +
        public abstract void close()
        +
        Roll back pending transactions and close the session.
        +
        +
        Specified by:
        +
        close in interface java.lang.AutoCloseable
        +
        +
      • +
      + + + +
        +
      • +

        getTrace

        +
        public abstract org.h2.message.Trace getTrace()
        +
        Get the trace object
        +
        +
        Returns:
        +
        the trace object
        +
        +
      • +
      + + + +
        +
      • +

        isClosed

        +
        public abstract boolean isClosed()
        +
        Check if close was called.
        +
        +
        Returns:
        +
        if the session has been closed
        +
        +
      • +
      + + + +
        +
      • +

        getDataHandler

        +
        public abstract org.h2.store.DataHandler getDataHandler()
        +
        Get the data handler object.
        +
        +
        Returns:
        +
        the data handler
        +
        +
      • +
      + + + +
        +
      • +

        hasPendingTransaction

        +
        public abstract boolean hasPendingTransaction()
        +
        Check whether this session has a pending transaction.
        +
        +
        Returns:
        +
        true if it has
        +
        +
      • +
      + + + +
        +
      • +

        cancel

        +
        public abstract void cancel()
        +
        Cancel the current or next command (called when closing a connection).
        +
      • +
      + + + +
        +
      • +

        getAutoCommit

        +
        public abstract boolean getAutoCommit()
        +
        Check if this session is in auto-commit mode.
        +
        +
        Returns:
        +
        true if the session is in auto-commit mode
        +
        +
      • +
      + + + +
        +
      • +

        setAutoCommit

        +
        public abstract void setAutoCommit(boolean autoCommit)
        +
        Set the auto-commit mode. This call doesn't commit the current + transaction.
        +
        +
        Parameters:
        +
        autoCommit - the new value
        +
        +
      • +
      + + + +
        +
      • +

        addTemporaryLob

        +
        public abstract org.h2.value.ValueLob addTemporaryLob(org.h2.value.ValueLob v)
        +
        Add a temporary LOB, which is closed when the session commits.
        +
        +
        Parameters:
        +
        v - the value
        +
        Returns:
        +
        the specified value
        +
        +
      • +
      + + + +
        +
      • +

        isRemote

        +
        public abstract boolean isRemote()
        +
        Check if this session is remote or embedded.
        +
        +
        Returns:
        +
        true if this session is remote
        +
        +
      • +
      + + + +
        +
      • +

        setCurrentSchemaName

        +
        public abstract void setCurrentSchemaName(java.lang.String schema)
        +
        Set current schema.
        +
        +
        Parameters:
        +
        schema - the schema name
        +
        +
      • +
      + + + +
        +
      • +

        getCurrentSchemaName

        +
        public abstract java.lang.String getCurrentSchemaName()
        +
        Get current schema.
        +
        +
        Returns:
        +
        the current schema name
        +
        +
      • +
      + + + +
        +
      • +

        setNetworkConnectionInfo

        +
        public abstract void setNetworkConnectionInfo(org.h2.util.NetworkConnectionInfo networkConnectionInfo)
        +
        Sets the network connection information if possible.
        +
        +
        Parameters:
        +
        networkConnectionInfo - the network connection information
        +
        +
      • +
      + + + +
        +
      • +

        getIsolationLevel

        +
        public abstract IsolationLevel getIsolationLevel()
        +
        Returns the isolation level.
        +
        +
        Returns:
        +
        the isolation level
        +
        +
      • +
      + + + +
        +
      • +

        setIsolationLevel

        +
        public abstract void setIsolationLevel(IsolationLevel isolationLevel)
        +
        Sets the isolation level.
        +
        +
        Parameters:
        +
        isolationLevel - the isolation level to set
        +
        +
      • +
      + + + +
        +
      • +

        getStaticSettings

        +
        public abstract Session.StaticSettings getStaticSettings()
        +
        Returns static settings. These settings cannot be changed during + lifecycle of session.
        +
        +
        Returns:
        +
        static settings
        +
        +
      • +
      + + + +
        +
      • +

        getDynamicSettings

        +
        public abstract Session.DynamicSettings getDynamicSettings()
        +
        Returns dynamic settings. These settings can be changed during lifecycle + of session.
        +
        +
        Returns:
        +
        dynamic settings
        +
        +
      • +
      + + + +
        +
      • +

        getDatabaseMeta

        +
        public abstract org.h2.jdbc.meta.DatabaseMeta getDatabaseMeta()
        +
        Returns database meta information.
        +
        +
        Returns:
        +
        database meta information
        +
        +
      • +
      + + + +
        +
      • +

        isOldInformationSchema

        +
        public abstract boolean isOldInformationSchema()
        +
        Returns whether INFORMATION_SCHEMA contains old-style tables.
        +
        +
        Returns:
        +
        whether INFORMATION_SCHEMA contains old-style tables
        +
        +
      • +
      + + + +
        +
      • +

        readSessionState

        +
        public void readSessionState()
        +
        Read the session state if necessary.
        +
      • +
      + + + +
        +
      • +

        setThreadLocalSession

        +
        public Session setThreadLocalSession()
        +
        Sets this session as thread local session, if this session is a local + session.
        +
        +
        Returns:
        +
        old thread local session, or null
        +
        +
      • +
      + + + +
        +
      • +

        resetThreadLocalSession

        +
        public void resetThreadLocalSession(Session oldSession)
        +
        Resets old thread local session.
        +
        +
        Parameters:
        +
        oldSession - the old thread local session, or null
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/SessionLocal.Savepoint.html b/h2/docs/javadoc/org/h2/engine/SessionLocal.Savepoint.html new file mode 100644 index 0000000..b5130e1 --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/SessionLocal.Savepoint.html @@ -0,0 +1,241 @@ + + + + + +SessionLocal.Savepoint + + + + + + + + + + + + +
+
org.h2.engine
+

Class SessionLocal.Savepoint

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.engine.SessionLocal.Savepoint
    • +
    +
  • +
+
+
    +
  • +
    +
    Enclosing class:
    +
    SessionLocal
    +
    +
    +
    +
    public static class SessionLocal.Savepoint
    +extends java.lang.Object
    +
    Represents a savepoint (a position in a transaction to where one can roll + back to).
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      Savepoint() 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        Savepoint

        +
        public Savepoint()
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/SessionLocal.State.html b/h2/docs/javadoc/org/h2/engine/SessionLocal.State.html new file mode 100644 index 0000000..30a6b46 --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/SessionLocal.State.html @@ -0,0 +1,403 @@ + + + + + +SessionLocal.State + + + + + + + + + + + + +
+
org.h2.engine
+

Enum SessionLocal.State

+
+
+
    +
  • java.lang.Object
  • +
  • + +
  • +
+
+ +
+
+
    +
  • + + + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethod and Description
      static SessionLocal.StatevalueOf(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static SessionLocal.State[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + + + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static SessionLocal.State[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (SessionLocal.State c : SessionLocal.State.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static SessionLocal.State valueOf(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/SessionLocal.TimeoutValue.html b/h2/docs/javadoc/org/h2/engine/SessionLocal.TimeoutValue.html new file mode 100644 index 0000000..5609104 --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/SessionLocal.TimeoutValue.html @@ -0,0 +1,200 @@ + + + + + +SessionLocal.TimeoutValue + + + + + + + + + + + + +
+
org.h2.engine
+

Class SessionLocal.TimeoutValue

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.engine.SessionLocal.TimeoutValue
    • +
    +
  • +
+
+
    +
  • +
    +
    Enclosing class:
    +
    SessionLocal
    +
    +
    +
    +
    public static class SessionLocal.TimeoutValue
    +extends java.lang.Object
    +
    An LOB object with a timeout.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Summary

      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/SessionLocal.html b/h2/docs/javadoc/org/h2/engine/SessionLocal.html new file mode 100644 index 0000000..fc9b856 --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/SessionLocal.html @@ -0,0 +1,2913 @@ + + + + + +SessionLocal + + + + + + + + + + + + +
+
org.h2.engine
+

Class SessionLocal

+
+
+ +
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.lang.AutoCloseable, CastDataProvider, org.h2.mvstore.tx.TransactionStore.RollbackListener
    +
    +
    +
    +
    public final class SessionLocal
    +extends Session
    +implements org.h2.mvstore.tx.TransactionStore.RollbackListener
    +
    A session represents an embedded database connection. When using the server + mode, this object resides on the server side and communicates with a + SessionRemote object on the client side.
    +
  • +
+
+
+
    +
  • + + + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      SessionLocal(Database database, + User user, + int id) 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      voidaddLocalTempTable(org.h2.table.Table table) +
      Add a local temporary table to this session.
      +
      voidaddLocalTempTableConstraint(org.h2.constraint.Constraint constraint) +
      Add a local temporary constraint to this session.
      +
      voidaddLocalTempTableIndex(org.h2.index.Index index) +
      Add a local temporary index to this session.
      +
      voidaddProcedure(Procedure procedure) +
      Add a procedure to this session.
      +
      voidaddSavepoint(java.lang.String name) +
      Create a savepoint that is linked to the current log position.
      +
      org.h2.value.ValueLobaddTemporaryLob(org.h2.value.ValueLob v) +
      Add a temporary LOB, which is closed when the session commits.
      +
      booleanareEqual(org.h2.value.Value a, + org.h2.value.Value b) +
      Check if two values are equal with the current comparison mode.
      +
      voidbegin() +
      Begin a transaction.
      +
      voidcancel() +
      Cancel the current or next command (called when closing a connection).
      +
      voidcheckCanceled() +
      Check if the current transaction is canceled by calling + Statement.cancel() or because a session timeout was set and expired.
      +
      voidclearViewIndexCache() +
      Clear the view cache for this session.
      +
      voidclose() +
      Roll back pending transactions and close the session.
      +
      voidcommit(boolean ddl) +
      Commit the current transaction.
      +
      intcompare(org.h2.value.Value a, + org.h2.value.Value b) +
      Compare two values with the current comparison mode.
      +
      intcompareTypeSafe(org.h2.value.Value a, + org.h2.value.Value b) +
      Compare two values with the current comparison mode.
      +
      intcompareWithNull(org.h2.value.Value a, + org.h2.value.Value b, + boolean forEquality) +
      Compare two values with the current comparison mode.
      +
      booleancontainsUncommitted() +
      Whether the session contains any uncommitted changes.
      +
      JdbcConnectioncreateConnection(boolean columnList) +
      Create an internal connection.
      +
      org.h2.value.ValueTimestampTimeZonecurrentTimestamp() +
      Returns the current timestamp with maximum resolution.
      +
      org.h2.util.TimeZoneProvidercurrentTimeZone() +
      Returns the current time zone.
      +
      voidendStatement() +
      Mark the statement as completed.
      +
      org.h2.table.TablefindLocalTempTable(java.lang.String name) +
      Get the local temporary table if one exists with that name, or null if + not.
      +
      org.h2.constraint.ConstraintfindLocalTempTableConstraint(java.lang.String name) +
      Get the local temporary constraint if one exists with that name, or + null if not.
      +
      org.h2.index.IndexfindLocalTempTableIndex(java.lang.String name) +
      Get the local temporary index if one exists with that name, or null if + not.
      +
      booleangetAllowLiterals() 
      booleangetAutoCommit() +
      Check if this session is in auto-commit mode.
      +
      intgetBlockingSessionId() 
      longgetCancel() +
      Get the cancel time.
      +
      java.util.ArrayList<java.lang.String>getClusterServers() +
      Get the list of the cluster servers for this session.
      +
      org.h2.value.ValueTimestampTimeZonegetCommandStartOrEnd() 
      org.h2.command.CommandgetCurrentCommand() 
      java.lang.StringgetCurrentSchemaName() +
      Get current schema.
      +
      org.h2.value.ValuegetCurrentValueFor(org.h2.schema.Sequence sequence) +
      Returns the current value of the sequence in this session.
      +
      DatabasegetDatabase() 
      org.h2.jdbc.meta.DatabaseMetagetDatabaseMeta() +
      Returns database meta information.
      +
      org.h2.store.DataHandlergetDataHandler() +
      Get the data handler object.
      +
      Session.DynamicSettingsgetDynamicSettings() +
      Returns dynamic settings.
      +
      intgetId() 
      IsolationLevelgetIsolationLevel() +
      Returns the isolation level.
      +
      JavaObjectSerializergetJavaObjectSerializer() +
      Returns the custom Java object serializer, or null.
      +
      org.h2.value.ValuegetLastIdentity() 
      java.util.HashMap<java.lang.String,org.h2.constraint.Constraint>getLocalTempTableConstraints() +
      Get the map of constraints for all constraints on local, temporary + tables, if any.
      +
      java.util.HashMap<java.lang.String,org.h2.index.Index>getLocalTempTableIndexes() 
      java.util.List<org.h2.table.Table>getLocalTempTables() 
      java.util.Set<org.h2.table.Table>getLocks() 
      intgetLockTimeout() 
      ModegetMode() +
      Returns the database mode.
      +
      intgetModificationId() 
      org.h2.util.NetworkConnectionInfogetNetworkConnectionInfo() +
      Returns the network connection information, or null.
      +
      java.lang.StringgetNextSystemIdentifier(java.lang.String sql) +
      Get the next system generated identifiers.
      +
      org.h2.value.ValuegetNextValueFor(org.h2.schema.Sequence sequence, + org.h2.command.Prepared prepared) +
      Returns the next value of the sequence in this session.
      +
      java.util.BitSetgetNonKeywords() +
      Gets bit set of non-keywords.
      +
      ProceduregetProcedure(java.lang.String name) +
      Get the procedure with the given name, or null + if none exists.
      +
      intgetQueryTimeout() 
      java.util.RandomgetRandom() 
      java.lang.String[]getSchemaSearchPath() 
      org.h2.value.ValueTimestampTimeZonegetSessionStart() 
      longgetSnapshotDataModificationId() +
      Returns the data modification id of transaction's snapshot, or 0 if + isolation level doesn't use snapshots.
      +
      SessionLocal.StategetState() 
      Session.StaticSettingsgetStaticSettings() +
      Returns static settings.
      +
      org.h2.message.TracegetTrace() +
      Get the trace object
      +
      org.h2.mvstore.tx.TransactiongetTransaction() +
      Get the transaction to use for this session.
      +
      org.h2.value.ValuegetTransactionId() 
      UsergetUser() 
      org.h2.value.ValuegetVariable(java.lang.String name) +
      Get the value of the specified user defined variable.
      +
      java.lang.String[]getVariableNames() +
      Get the list of variable names that are set for this session.
      +
      java.util.Map<java.lang.Object,org.h2.index.QueryExpressionIndex>getViewIndexCache(boolean derivedTable) +
      Get the view cache for this session.
      +
      org.h2.table.TablegetWaitForLock() 
      java.lang.ThreadgetWaitForLockThread() 
      inthashCode() 
      booleanhasPendingTransaction() +
      Check whether this session has a pending transaction.
      +
      booleanhasPreparedTransaction() +
      Checks presence of prepared transaction in this session.
      +
      booleanisClosed() +
      Check if close was called.
      +
      booleanisLazyQueryExecution() 
      booleanisOldInformationSchema() +
      Returns whether INFORMATION_SCHEMA contains old-style tables.
      +
      booleanisOpen() 
      booleanisParsingCreateView() 
      booleanisQuirksMode() +
      Returns whether quirks mode is enabled explicitly or implicitly.
      +
      booleanisRemote() +
      Check if this session is remote or embedded.
      +
      booleanisTruncateLargeLength() +
      Returns parsing mode of data types with too large length.
      +
      booleanisVariableBinary() +
      Returns BINARY data type parsing mode.
      +
      voidmarkTableForAnalyze(org.h2.table.Table table) +
      Mark that the given table needs to be analyzed on commit.
      +
      intnextObjectId() +
      Get the next object id.
      +
      voidonRollback(org.h2.mvstore.MVMap<java.lang.Object,org.h2.value.VersionedValue<java.lang.Object>> map, + java.lang.Object key, + org.h2.value.VersionedValue<java.lang.Object> existingValue, + org.h2.value.VersionedValue<java.lang.Object> restoredValue) +
      Notified of a single map change (add/update/remove)
      +
      org.h2.command.Preparedprepare(java.lang.String sql) +
      Parse and prepare the given SQL statement.
      +
      org.h2.command.Preparedprepare(java.lang.String sql, + boolean rightsChecked, + boolean literalsChecked) +
      Parse and prepare the given SQL statement.
      +
      org.h2.command.CommandInterfaceprepareCommand(java.lang.String sql, + int fetchSize) +
      Parse a command and prepare it for execution.
      +
      voidprepareCommit(java.lang.String transactionName) +
      Prepare the given transaction.
      +
      org.h2.command.CommandprepareLocal(java.lang.String sql) +
      Parse and prepare the given SQL statement.
      +
      org.h2.command.query.QueryprepareQueryExpression(java.lang.String sql) +
      Parse a query and prepare its expressions.
      +
      voidregisterTableAsLocked(org.h2.table.Table table) +
      Register table as locked within current transaction.
      +
      voidregisterTableAsUpdated(org.h2.table.Table table) +
      Register table as updated within current transaction.
      +
      voidremoveAtCommit(org.h2.value.ValueLob v) +
      Remember that the given LOB value must be removed at commit.
      +
      voidremoveAtCommitStop(org.h2.value.ValueLob v) +
      Do not remove this LOB value at commit any longer.
      +
      voidremoveLocalTempTable(org.h2.table.Table table) +
      Drop and remove the given local temporary table from this session.
      +
      voidremoveLocalTempTableIndex(org.h2.index.Index index) +
      Drop and remove the given local temporary index from this session.
      +
      voidremoveProcedure(java.lang.String name) +
      Remove a procedure from this session.
      +
      voidresetThreadLocalSession(Session oldSession) +
      Resets old thread local session.
      +
      voidrollback() +
      Fully roll back the current transaction.
      +
      voidrollbackTo(SessionLocal.Savepoint savepoint) +
      Partially roll back the current transaction.
      +
      voidrollbackToSavepoint(java.lang.String name) +
      Undo all operations back to the log position of the given savepoint.
      +
      voidsetAllowLiterals(boolean b) 
      voidsetAutoCommit(boolean b) +
      Set the auto-commit mode.
      +
      booleansetCommitOrRollbackDisabled(boolean x) 
      voidsetCurrentSchema(org.h2.schema.Schema schema) 
      voidsetCurrentSchemaName(java.lang.String schemaName) +
      Set current schema.
      +
      voidsetIsolationLevel(IsolationLevel isolationLevel) +
      Sets the isolation level.
      +
      voidsetLastIdentity(org.h2.value.Value last) 
      voidsetLazyQueryExecution(boolean lazyQueryExecution) 
      voidsetLockTimeout(int lockTimeout) 
      voidsetNetworkConnectionInfo(org.h2.util.NetworkConnectionInfo networkConnectionInfo) +
      Sets the network connection information if possible.
      +
      voidsetNonKeywords(java.util.BitSet nonKeywords) +
      Sets bit set of non-keywords.
      +
      voidsetOldInformationSchema(boolean oldInformationSchema) +
      Changes INFORMATION_SCHEMA content.
      +
      voidsetParsingCreateView(boolean parsingView) +
      This method is called before and after parsing of view definition and may + be called recursively.
      +
      voidsetPreparedTransaction(java.lang.String transactionName, + boolean commit) +
      Commit or roll back the given transaction.
      +
      voidsetQueryTimeout(int queryTimeout) 
      voidsetQuirksMode(boolean quirksMode) +
      Enables or disables the quirks mode.
      +
      SessionLocal.SavepointsetSavepoint() +
      Create a savepoint to allow rolling back to this state.
      +
      voidsetSchemaSearchPath(java.lang.String[] schemas) 
      SessionsetThreadLocalSession() +
      Sets this session as thread local session, if this session is a local + session.
      +
      voidsetThrottle(int throttle) 
      voidsetTimeZone(org.h2.util.TimeZoneProvider timeZone) +
      Sets current time zone.
      +
      voidsetTruncateLargeLength(boolean truncateLargeLength) +
      Changes parsing mode of data types with too large length.
      +
      voidsetVariable(java.lang.String name, + org.h2.value.Value value) +
      Set the value of the given variable for this session.
      +
      voidsetVariableBinary(boolean variableBinary) +
      Changes parsing of a BINARY data type.
      +
      voidsetWaitForLock(org.h2.table.Table waitForLock, + java.lang.Thread waitForLockThread) +
      Set the table this session is waiting for, and the thread that is + waiting.
      +
      voidstartStatementWithinTransaction(org.h2.command.Command command) +
      Start a new statement within a transaction.
      +
      voidthrottle() +
      Wait for some time if this session is throttled (slowed down).
      +
      java.lang.StringtoString() 
      voidwaitIfExclusiveModeEnabled() +
      Wait if the exclusive mode has been enabled for another session.
      +
      booleanzeroBasedEnums() +
      Returns are ENUM values 0-based.
      +
      + +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        SessionLocal

        +
        public SessionLocal(Database database,
        +                    User user,
        +                    int id)
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        setLazyQueryExecution

        +
        public void setLazyQueryExecution(boolean lazyQueryExecution)
        +
      • +
      + + + +
        +
      • +

        isLazyQueryExecution

        +
        public boolean isLazyQueryExecution()
        +
      • +
      + + + +
        +
      • +

        setParsingCreateView

        +
        public void setParsingCreateView(boolean parsingView)
        +
        This method is called before and after parsing of view definition and may + be called recursively.
        +
        +
        Parameters:
        +
        parsingView - true if this method is called before parsing of view + definition, false if it is called after it.
        +
        +
      • +
      + + + +
        +
      • +

        isParsingCreateView

        +
        public boolean isParsingCreateView()
        +
      • +
      + + + +
        +
      • +

        getClusterServers

        +
        public java.util.ArrayList<java.lang.String> getClusterServers()
        +
        Description copied from class: Session
        +
        Get the list of the cluster servers for this session.
        +
        +
        Specified by:
        +
        getClusterServers in class Session
        +
        Returns:
        +
        A list of "ip:port" strings for the cluster servers in this + session.
        +
        +
      • +
      + + + +
        +
      • +

        setCommitOrRollbackDisabled

        +
        public boolean setCommitOrRollbackDisabled(boolean x)
        +
      • +
      + + + +
        +
      • +

        setVariable

        +
        public void setVariable(java.lang.String name,
        +                        org.h2.value.Value value)
        +
        Set the value of the given variable for this session.
        +
        +
        Parameters:
        +
        name - the name of the variable (may not be null)
        +
        value - the new value (may not be null)
        +
        +
      • +
      + + + +
        +
      • +

        getVariable

        +
        public org.h2.value.Value getVariable(java.lang.String name)
        +
        Get the value of the specified user defined variable. This method always + returns a value; it returns ValueNull.INSTANCE if the variable doesn't + exist.
        +
        +
        Parameters:
        +
        name - the variable name
        +
        Returns:
        +
        the value, or NULL
        +
        +
      • +
      + + + +
        +
      • +

        getVariableNames

        +
        public java.lang.String[] getVariableNames()
        +
        Get the list of variable names that are set for this session.
        +
        +
        Returns:
        +
        the list of names
        +
        +
      • +
      + + + +
        +
      • +

        findLocalTempTable

        +
        public org.h2.table.Table findLocalTempTable(java.lang.String name)
        +
        Get the local temporary table if one exists with that name, or null if + not.
        +
        +
        Parameters:
        +
        name - the table name
        +
        Returns:
        +
        the table, or null
        +
        +
      • +
      + + + +
        +
      • +

        getLocalTempTables

        +
        public java.util.List<org.h2.table.Table> getLocalTempTables()
        +
      • +
      + + + +
        +
      • +

        addLocalTempTable

        +
        public void addLocalTempTable(org.h2.table.Table table)
        +
        Add a local temporary table to this session.
        +
        +
        Parameters:
        +
        table - the table to add
        +
        Throws:
        +
        org.h2.message.DbException - if a table with this name already exists
        +
        +
      • +
      + + + +
        +
      • +

        removeLocalTempTable

        +
        public void removeLocalTempTable(org.h2.table.Table table)
        +
        Drop and remove the given local temporary table from this session.
        +
        +
        Parameters:
        +
        table - the table
        +
        +
      • +
      + + + +
        +
      • +

        findLocalTempTableIndex

        +
        public org.h2.index.Index findLocalTempTableIndex(java.lang.String name)
        +
        Get the local temporary index if one exists with that name, or null if + not.
        +
        +
        Parameters:
        +
        name - the table name
        +
        Returns:
        +
        the table, or null
        +
        +
      • +
      + + + +
        +
      • +

        getLocalTempTableIndexes

        +
        public java.util.HashMap<java.lang.String,org.h2.index.Index> getLocalTempTableIndexes()
        +
      • +
      + + + +
        +
      • +

        addLocalTempTableIndex

        +
        public void addLocalTempTableIndex(org.h2.index.Index index)
        +
        Add a local temporary index to this session.
        +
        +
        Parameters:
        +
        index - the index to add
        +
        Throws:
        +
        org.h2.message.DbException - if a index with this name already exists
        +
        +
      • +
      + + + +
        +
      • +

        removeLocalTempTableIndex

        +
        public void removeLocalTempTableIndex(org.h2.index.Index index)
        +
        Drop and remove the given local temporary index from this session.
        +
        +
        Parameters:
        +
        index - the index
        +
        +
      • +
      + + + +
        +
      • +

        findLocalTempTableConstraint

        +
        public org.h2.constraint.Constraint findLocalTempTableConstraint(java.lang.String name)
        +
        Get the local temporary constraint if one exists with that name, or + null if not.
        +
        +
        Parameters:
        +
        name - the constraint name
        +
        Returns:
        +
        the constraint, or null
        +
        +
      • +
      + + + +
        +
      • +

        getLocalTempTableConstraints

        +
        public java.util.HashMap<java.lang.String,org.h2.constraint.Constraint> getLocalTempTableConstraints()
        +
        Get the map of constraints for all constraints on local, temporary + tables, if any. The map's keys are the constraints' names.
        +
        +
        Returns:
        +
        the map of constraints, or null
        +
        +
      • +
      + + + +
        +
      • +

        addLocalTempTableConstraint

        +
        public void addLocalTempTableConstraint(org.h2.constraint.Constraint constraint)
        +
        Add a local temporary constraint to this session.
        +
        +
        Parameters:
        +
        constraint - the constraint to add
        +
        Throws:
        +
        org.h2.message.DbException - if a constraint with the same name already exists
        +
        +
      • +
      + + + +
        +
      • +

        getAutoCommit

        +
        public boolean getAutoCommit()
        +
        Description copied from class: Session
        +
        Check if this session is in auto-commit mode.
        +
        +
        Specified by:
        +
        getAutoCommit in class Session
        +
        Returns:
        +
        true if the session is in auto-commit mode
        +
        +
      • +
      + + + +
        +
      • +

        getUser

        +
        public User getUser()
        +
      • +
      + + + +
        +
      • +

        setAutoCommit

        +
        public void setAutoCommit(boolean b)
        +
        Description copied from class: Session
        +
        Set the auto-commit mode. This call doesn't commit the current + transaction.
        +
        +
        Specified by:
        +
        setAutoCommit in class Session
        +
        Parameters:
        +
        b - the new value
        +
        +
      • +
      + + + +
        +
      • +

        getLockTimeout

        +
        public int getLockTimeout()
        +
      • +
      + + + +
        +
      • +

        setLockTimeout

        +
        public void setLockTimeout(int lockTimeout)
        +
      • +
      + + + +
        +
      • +

        prepareCommand

        +
        public org.h2.command.CommandInterface prepareCommand(java.lang.String sql,
        +                                                      int fetchSize)
        +
        Description copied from class: Session
        +
        Parse a command and prepare it for execution.
        +
        +
        Specified by:
        +
        prepareCommand in class Session
        +
        Parameters:
        +
        sql - the SQL statement
        +
        fetchSize - the number of rows to fetch in one step
        +
        Returns:
        +
        the prepared command
        +
        +
      • +
      + + + +
        +
      • +

        prepare

        +
        public org.h2.command.Prepared prepare(java.lang.String sql)
        +
        Parse and prepare the given SQL statement. This method also checks the + rights.
        +
        +
        Parameters:
        +
        sql - the SQL statement
        +
        Returns:
        +
        the prepared statement
        +
        +
      • +
      + + + +
        +
      • +

        prepare

        +
        public org.h2.command.Prepared prepare(java.lang.String sql,
        +                                       boolean rightsChecked,
        +                                       boolean literalsChecked)
        +
        Parse and prepare the given SQL statement.
        +
        +
        Parameters:
        +
        sql - the SQL statement
        +
        rightsChecked - true if the rights have already been checked
        +
        literalsChecked - true if the sql string has already been checked + for literals (only used if ALLOW_LITERALS NONE is set).
        +
        Returns:
        +
        the prepared statement
        +
        +
      • +
      + + + +
        +
      • +

        prepareQueryExpression

        +
        public org.h2.command.query.Query prepareQueryExpression(java.lang.String sql)
        +
        Parse a query and prepare its expressions. Rights and literals must be + already checked.
        +
        +
        Parameters:
        +
        sql - the SQL statement
        +
        Returns:
        +
        the prepared statement
        +
        +
      • +
      + + + +
        +
      • +

        prepareLocal

        +
        public org.h2.command.Command prepareLocal(java.lang.String sql)
        +
        Parse and prepare the given SQL statement. + This method also checks if the connection has been closed.
        +
        +
        Parameters:
        +
        sql - the SQL statement
        +
        Returns:
        +
        the prepared statement
        +
        +
      • +
      + + + +
        +
      • +

        getDatabase

        +
        public Database getDatabase()
        +
      • +
      + + + +
        +
      • +

        commit

        +
        public void commit(boolean ddl)
        +
        Commit the current transaction. If the statement was not a data + definition statement, and if there are temporary tables that should be + dropped or truncated at commit, this is done as well.
        +
        +
        Parameters:
        +
        ddl - if the statement was a data definition statement
        +
        +
      • +
      + + + +
        +
      • +

        getSnapshotDataModificationId

        +
        public long getSnapshotDataModificationId()
        +
        Returns the data modification id of transaction's snapshot, or 0 if + isolation level doesn't use snapshots.
        +
        +
        Returns:
        +
        the data modification id of transaction's snapshot, or 0
        +
        +
      • +
      + + + +
        +
      • +

        rollback

        +
        public void rollback()
        +
        Fully roll back the current transaction.
        +
      • +
      + + + +
        +
      • +

        rollbackTo

        +
        public void rollbackTo(SessionLocal.Savepoint savepoint)
        +
        Partially roll back the current transaction.
        +
        +
        Parameters:
        +
        savepoint - the savepoint to which should be rolled back
        +
        +
      • +
      + + + +
        +
      • +

        hasPendingTransaction

        +
        public boolean hasPendingTransaction()
        +
        Description copied from class: Session
        +
        Check whether this session has a pending transaction.
        +
        +
        Specified by:
        +
        hasPendingTransaction in class Session
        +
        Returns:
        +
        true if it has
        +
        +
      • +
      + + + +
        +
      • +

        setSavepoint

        +
        public SessionLocal.Savepoint setSavepoint()
        +
        Create a savepoint to allow rolling back to this state.
        +
        +
        Returns:
        +
        the savepoint
        +
        +
      • +
      + + + +
        +
      • +

        getId

        +
        public int getId()
        +
      • +
      + + + +
        +
      • +

        cancel

        +
        public void cancel()
        +
        Description copied from class: Session
        +
        Cancel the current or next command (called when closing a connection).
        +
        +
        Specified by:
        +
        cancel in class Session
        +
        +
      • +
      + + + +
        +
      • +

        close

        +
        public void close()
        +
        Description copied from class: Session
        +
        Roll back pending transactions and close the session.
        +
        +
        Specified by:
        +
        close in interface java.lang.AutoCloseable
        +
        Specified by:
        +
        close in class Session
        +
        +
      • +
      + + + +
        +
      • +

        registerTableAsLocked

        +
        public void registerTableAsLocked(org.h2.table.Table table)
        +
        Register table as locked within current transaction. + Table is unlocked on commit or rollback. + It also assumes that table will be modified by transaction.
        +
        +
        Parameters:
        +
        table - the table that is locked
        +
        +
      • +
      + + + +
        +
      • +

        registerTableAsUpdated

        +
        public void registerTableAsUpdated(org.h2.table.Table table)
        +
        Register table as updated within current transaction. + This is used instead of table locking when lock mode is LOCK_MODE_OFF.
        +
        +
        Parameters:
        +
        table - to register
        +
        +
      • +
      + + + +
        +
      • +

        getRandom

        +
        public java.util.Random getRandom()
        +
      • +
      + + + +
        +
      • +

        getTrace

        +
        public org.h2.message.Trace getTrace()
        +
        Description copied from class: Session
        +
        Get the trace object
        +
        +
        Specified by:
        +
        getTrace in class Session
        +
        Returns:
        +
        the trace object
        +
        +
      • +
      + + + +
        +
      • +

        getNextValueFor

        +
        public org.h2.value.Value getNextValueFor(org.h2.schema.Sequence sequence,
        +                                          org.h2.command.Prepared prepared)
        +
        Returns the next value of the sequence in this session.
        +
        +
        Parameters:
        +
        sequence - the sequence
        +
        prepared - current prepared command, select, or null
        +
        Returns:
        +
        the next value of the sequence in this session
        +
        +
      • +
      + + + +
        +
      • +

        getCurrentValueFor

        +
        public org.h2.value.Value getCurrentValueFor(org.h2.schema.Sequence sequence)
        +
        Returns the current value of the sequence in this session.
        +
        +
        Parameters:
        +
        sequence - the sequence
        +
        Returns:
        +
        the current value of the sequence in this session
        +
        Throws:
        +
        org.h2.message.DbException - if current value is not defined
        +
        +
      • +
      + + + +
        +
      • +

        setLastIdentity

        +
        public void setLastIdentity(org.h2.value.Value last)
        +
      • +
      + + + +
        +
      • +

        getLastIdentity

        +
        public org.h2.value.Value getLastIdentity()
        +
      • +
      + + + +
        +
      • +

        containsUncommitted

        +
        public boolean containsUncommitted()
        +
        Whether the session contains any uncommitted changes.
        +
        +
        Returns:
        +
        true if yes
        +
        +
      • +
      + + + +
        +
      • +

        addSavepoint

        +
        public void addSavepoint(java.lang.String name)
        +
        Create a savepoint that is linked to the current log position.
        +
        +
        Parameters:
        +
        name - the savepoint name
        +
        +
      • +
      + + + +
        +
      • +

        rollbackToSavepoint

        +
        public void rollbackToSavepoint(java.lang.String name)
        +
        Undo all operations back to the log position of the given savepoint.
        +
        +
        Parameters:
        +
        name - the savepoint name
        +
        +
      • +
      + + + +
        +
      • +

        prepareCommit

        +
        public void prepareCommit(java.lang.String transactionName)
        +
        Prepare the given transaction.
        +
        +
        Parameters:
        +
        transactionName - the name of the transaction
        +
        +
      • +
      + + + +
        +
      • +

        hasPreparedTransaction

        +
        public boolean hasPreparedTransaction()
        +
        Checks presence of prepared transaction in this session.
        +
        +
        Returns:
        +
        true if there is a prepared transaction, + false otherwise
        +
        +
      • +
      + + + +
        +
      • +

        setPreparedTransaction

        +
        public void setPreparedTransaction(java.lang.String transactionName,
        +                                   boolean commit)
        +
        Commit or roll back the given transaction.
        +
        +
        Parameters:
        +
        transactionName - the name of the transaction
        +
        commit - true for commit, false for rollback
        +
        +
      • +
      + + + +
        +
      • +

        isClosed

        +
        public boolean isClosed()
        +
        Description copied from class: Session
        +
        Check if close was called.
        +
        +
        Specified by:
        +
        isClosed in class Session
        +
        Returns:
        +
        if the session has been closed
        +
        +
      • +
      + + + +
        +
      • +

        isOpen

        +
        public boolean isOpen()
        +
      • +
      + + + +
        +
      • +

        setThrottle

        +
        public void setThrottle(int throttle)
        +
      • +
      + + + +
        +
      • +

        throttle

        +
        public void throttle()
        +
        Wait for some time if this session is throttled (slowed down).
        +
      • +
      + + + +
        +
      • +

        checkCanceled

        +
        public void checkCanceled()
        +
        Check if the current transaction is canceled by calling + Statement.cancel() or because a session timeout was set and expired.
        +
        +
        Throws:
        +
        org.h2.message.DbException - if the transaction is canceled
        +
        +
      • +
      + + + +
        +
      • +

        getCancel

        +
        public long getCancel()
        +
        Get the cancel time.
        +
        +
        Returns:
        +
        the time or 0 if not set
        +
        +
      • +
      + + + +
        +
      • +

        getCurrentCommand

        +
        public org.h2.command.Command getCurrentCommand()
        +
      • +
      + + + +
        +
      • +

        getCommandStartOrEnd

        +
        public org.h2.value.ValueTimestampTimeZone getCommandStartOrEnd()
        +
      • +
      + + + +
        +
      • +

        getAllowLiterals

        +
        public boolean getAllowLiterals()
        +
      • +
      + + + +
        +
      • +

        setAllowLiterals

        +
        public void setAllowLiterals(boolean b)
        +
      • +
      + + + +
        +
      • +

        setCurrentSchema

        +
        public void setCurrentSchema(org.h2.schema.Schema schema)
        +
      • +
      + + + +
        +
      • +

        getCurrentSchemaName

        +
        public java.lang.String getCurrentSchemaName()
        +
        Description copied from class: Session
        +
        Get current schema.
        +
        +
        Specified by:
        +
        getCurrentSchemaName in class Session
        +
        Returns:
        +
        the current schema name
        +
        +
      • +
      + + + +
        +
      • +

        setCurrentSchemaName

        +
        public void setCurrentSchemaName(java.lang.String schemaName)
        +
        Description copied from class: Session
        +
        Set current schema.
        +
        +
        Specified by:
        +
        setCurrentSchemaName in class Session
        +
        Parameters:
        +
        schemaName - the schema name
        +
        +
      • +
      + + + +
        +
      • +

        createConnection

        +
        public JdbcConnection createConnection(boolean columnList)
        +
        Create an internal connection. This connection is used when initializing + triggers, and when calling user defined functions.
        +
        +
        Parameters:
        +
        columnList - if the url should be 'jdbc:columnlist:connection'
        +
        Returns:
        +
        the internal connection
        +
        +
      • +
      + + + +
        +
      • +

        getDataHandler

        +
        public org.h2.store.DataHandler getDataHandler()
        +
        Description copied from class: Session
        +
        Get the data handler object.
        +
        +
        Specified by:
        +
        getDataHandler in class Session
        +
        Returns:
        +
        the data handler
        +
        +
      • +
      + + + +
        +
      • +

        removeAtCommit

        +
        public void removeAtCommit(org.h2.value.ValueLob v)
        +
        Remember that the given LOB value must be removed at commit.
        +
        +
        Parameters:
        +
        v - the value
        +
        +
      • +
      + + + +
        +
      • +

        removeAtCommitStop

        +
        public void removeAtCommitStop(org.h2.value.ValueLob v)
        +
        Do not remove this LOB value at commit any longer.
        +
        +
        Parameters:
        +
        v - the value
        +
        +
      • +
      + + + +
        +
      • +

        getNextSystemIdentifier

        +
        public java.lang.String getNextSystemIdentifier(java.lang.String sql)
        +
        Get the next system generated identifiers. The identifier returned does + not occur within the given SQL statement.
        +
        +
        Parameters:
        +
        sql - the SQL statement
        +
        Returns:
        +
        the new identifier
        +
        +
      • +
      + + + +
        +
      • +

        addProcedure

        +
        public void addProcedure(Procedure procedure)
        +
        Add a procedure to this session.
        +
        +
        Parameters:
        +
        procedure - the procedure to add
        +
        +
      • +
      + + + +
        +
      • +

        removeProcedure

        +
        public void removeProcedure(java.lang.String name)
        +
        Remove a procedure from this session.
        +
        +
        Parameters:
        +
        name - the name of the procedure to remove
        +
        +
      • +
      + + + +
        +
      • +

        getProcedure

        +
        public Procedure getProcedure(java.lang.String name)
        +
        Get the procedure with the given name, or null + if none exists.
        +
        +
        Parameters:
        +
        name - the procedure name
        +
        Returns:
        +
        the procedure or null
        +
        +
      • +
      + + + +
        +
      • +

        setSchemaSearchPath

        +
        public void setSchemaSearchPath(java.lang.String[] schemas)
        +
      • +
      + + + +
        +
      • +

        getSchemaSearchPath

        +
        public java.lang.String[] getSchemaSearchPath()
        +
      • +
      + + + +
        +
      • +

        hashCode

        +
        public int hashCode()
        +
        +
        Overrides:
        +
        hashCode in class java.lang.Object
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        +
        Overrides:
        +
        toString in class java.lang.Object
        +
        +
      • +
      + + + +
        +
      • +

        begin

        +
        public void begin()
        +
        Begin a transaction.
        +
      • +
      + + + +
        +
      • +

        getSessionStart

        +
        public org.h2.value.ValueTimestampTimeZone getSessionStart()
        +
      • +
      + + + +
        +
      • +

        getLocks

        +
        public java.util.Set<org.h2.table.Table> getLocks()
        +
      • +
      + + + +
        +
      • +

        waitIfExclusiveModeEnabled

        +
        public void waitIfExclusiveModeEnabled()
        +
        Wait if the exclusive mode has been enabled for another session. This + method returns as soon as the exclusive mode has been disabled.
        +
      • +
      + + + +
        +
      • +

        getViewIndexCache

        +
        public java.util.Map<java.lang.Object,org.h2.index.QueryExpressionIndex> getViewIndexCache(boolean derivedTable)
        +
        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.
        +
        +
        Parameters:
        +
        derivedTable - true to get the cache of derived tables
        +
        Returns:
        +
        the view cache or derived table cache
        +
        +
      • +
      + + + +
        +
      • +

        setQueryTimeout

        +
        public void setQueryTimeout(int queryTimeout)
        +
      • +
      + + + +
        +
      • +

        getQueryTimeout

        +
        public int getQueryTimeout()
        +
      • +
      + + + +
        +
      • +

        setWaitForLock

        +
        public void setWaitForLock(org.h2.table.Table waitForLock,
        +                           java.lang.Thread waitForLockThread)
        +
        Set the table this session is waiting for, and the thread that is + waiting.
        +
        +
        Parameters:
        +
        waitForLock - the table
        +
        waitForLockThread - the current thread (the one that is waiting)
        +
        +
      • +
      + + + +
        +
      • +

        getWaitForLock

        +
        public org.h2.table.Table getWaitForLock()
        +
      • +
      + + + +
        +
      • +

        getWaitForLockThread

        +
        public java.lang.Thread getWaitForLockThread()
        +
      • +
      + + + +
        +
      • +

        getModificationId

        +
        public int getModificationId()
        +
      • +
      + + + +
        +
      • +

        getTransactionId

        +
        public org.h2.value.Value getTransactionId()
        +
      • +
      + + + +
        +
      • +

        nextObjectId

        +
        public int nextObjectId()
        +
        Get the next object id.
        +
        +
        Returns:
        +
        the next object id
        +
        +
      • +
      + + + +
        +
      • +

        getTransaction

        +
        public org.h2.mvstore.tx.Transaction getTransaction()
        +
        Get the transaction to use for this session.
        +
        +
        Returns:
        +
        the transaction
        +
        +
      • +
      + + + +
        +
      • +

        startStatementWithinTransaction

        +
        public void startStatementWithinTransaction(org.h2.command.Command command)
        +
        Start a new statement within a transaction.
        +
        +
        Parameters:
        +
        command - about to be started
        +
        +
      • +
      + + + +
        +
      • +

        endStatement

        +
        public void endStatement()
        +
        Mark the statement as completed. This also close all temporary result + set, and deletes all temporary files held by the result sets.
        +
      • +
      + + + +
        +
      • +

        clearViewIndexCache

        +
        public void clearViewIndexCache()
        +
        Clear the view cache for this session.
        +
      • +
      + + + +
        +
      • +

        addTemporaryLob

        +
        public org.h2.value.ValueLob addTemporaryLob(org.h2.value.ValueLob v)
        +
        Description copied from class: Session
        +
        Add a temporary LOB, which is closed when the session commits.
        +
        +
        Specified by:
        +
        addTemporaryLob in class Session
        +
        Parameters:
        +
        v - the value
        +
        Returns:
        +
        the specified value
        +
        +
      • +
      + + + +
        +
      • +

        isRemote

        +
        public boolean isRemote()
        +
        Description copied from class: Session
        +
        Check if this session is remote or embedded.
        +
        +
        Specified by:
        +
        isRemote in class Session
        +
        Returns:
        +
        true if this session is remote
        +
        +
      • +
      + + + +
        +
      • +

        markTableForAnalyze

        +
        public void markTableForAnalyze(org.h2.table.Table table)
        +
        Mark that the given table needs to be analyzed on commit.
        +
        +
        Parameters:
        +
        table - the table
        +
        +
      • +
      + + + + + + + +
        +
      • +

        getBlockingSessionId

        +
        public int getBlockingSessionId()
        +
      • +
      + + + +
        +
      • +

        onRollback

        +
        public void onRollback(org.h2.mvstore.MVMap<java.lang.Object,org.h2.value.VersionedValue<java.lang.Object>> map,
        +                       java.lang.Object key,
        +                       org.h2.value.VersionedValue<java.lang.Object> existingValue,
        +                       org.h2.value.VersionedValue<java.lang.Object> restoredValue)
        +
        Description copied from interface: org.h2.mvstore.tx.TransactionStore.RollbackListener
        +
        Notified of a single map change (add/update/remove)
        +
        +
        Specified by:
        +
        onRollback in interface org.h2.mvstore.tx.TransactionStore.RollbackListener
        +
        Parameters:
        +
        map - modified
        +
        key - of the modified entry
        +
        existingValue - value in the map (null if delete is rolled back)
        +
        restoredValue - value to be restored (null if add is rolled back)
        +
        +
      • +
      + + + +
        +
      • +

        getNetworkConnectionInfo

        +
        public org.h2.util.NetworkConnectionInfo getNetworkConnectionInfo()
        +
        Returns the network connection information, or null.
        +
        +
        Returns:
        +
        the network connection information, or null
        +
        +
      • +
      + + + +
        +
      • +

        setNetworkConnectionInfo

        +
        public void setNetworkConnectionInfo(org.h2.util.NetworkConnectionInfo networkConnectionInfo)
        +
        Description copied from class: Session
        +
        Sets the network connection information if possible.
        +
        +
        Specified by:
        +
        setNetworkConnectionInfo in class Session
        +
        Parameters:
        +
        networkConnectionInfo - the network connection information
        +
        +
      • +
      + + + +
        +
      • +

        currentTimestamp

        +
        public org.h2.value.ValueTimestampTimeZone currentTimestamp()
        +
        Description copied from interface: CastDataProvider
        +
        Returns the current timestamp with maximum resolution. The value must be + the same within a transaction or within execution of a command.
        +
        +
        Specified by:
        +
        currentTimestamp in interface CastDataProvider
        +
        Returns:
        +
        the current timestamp for CURRENT_TIMESTAMP(9)
        +
        +
      • +
      + + + + + + + + + + + +
        +
      • +

        getIsolationLevel

        +
        public IsolationLevel getIsolationLevel()
        +
        Description copied from class: Session
        +
        Returns the isolation level.
        +
        +
        Specified by:
        +
        getIsolationLevel in class Session
        +
        Returns:
        +
        the isolation level
        +
        +
      • +
      + + + +
        +
      • +

        setIsolationLevel

        +
        public void setIsolationLevel(IsolationLevel isolationLevel)
        +
        Description copied from class: Session
        +
        Sets the isolation level.
        +
        +
        Specified by:
        +
        setIsolationLevel in class Session
        +
        Parameters:
        +
        isolationLevel - the isolation level to set
        +
        +
      • +
      + + + +
        +
      • +

        getNonKeywords

        +
        public java.util.BitSet getNonKeywords()
        +
        Gets bit set of non-keywords.
        +
        +
        Returns:
        +
        set of non-keywords, or null
        +
        +
      • +
      + + + +
        +
      • +

        setNonKeywords

        +
        public void setNonKeywords(java.util.BitSet nonKeywords)
        +
        Sets bit set of non-keywords.
        +
        +
        Parameters:
        +
        nonKeywords - set of non-keywords, or null
        +
        +
      • +
      + + + +
        +
      • +

        getStaticSettings

        +
        public Session.StaticSettings getStaticSettings()
        +
        Description copied from class: Session
        +
        Returns static settings. These settings cannot be changed during + lifecycle of session.
        +
        +
        Specified by:
        +
        getStaticSettings in class Session
        +
        Returns:
        +
        static settings
        +
        +
      • +
      + + + +
        +
      • +

        getDynamicSettings

        +
        public Session.DynamicSettings getDynamicSettings()
        +
        Description copied from class: Session
        +
        Returns dynamic settings. These settings can be changed during lifecycle + of session.
        +
        +
        Specified by:
        +
        getDynamicSettings in class Session
        +
        Returns:
        +
        dynamic settings
        +
        +
      • +
      + + + +
        +
      • +

        currentTimeZone

        +
        public org.h2.util.TimeZoneProvider currentTimeZone()
        +
        Description copied from interface: CastDataProvider
        +
        Returns the current time zone.
        +
        +
        Specified by:
        +
        currentTimeZone in interface CastDataProvider
        +
        Returns:
        +
        the current time zone
        +
        +
      • +
      + + + +
        +
      • +

        setTimeZone

        +
        public void setTimeZone(org.h2.util.TimeZoneProvider timeZone)
        +
        Sets current time zone.
        +
        +
        Parameters:
        +
        timeZone - time zone
        +
        +
      • +
      + + + +
        +
      • +

        areEqual

        +
        public boolean areEqual(org.h2.value.Value a,
        +                        org.h2.value.Value b)
        +
        Check if two values are equal with the current comparison mode.
        +
        +
        Parameters:
        +
        a - the first value
        +
        b - the second value
        +
        Returns:
        +
        true if both objects are equal
        +
        +
      • +
      + + + +
        +
      • +

        compare

        +
        public int compare(org.h2.value.Value a,
        +                   org.h2.value.Value b)
        +
        Compare two values with the current comparison mode. The values may have + different data types including NULL.
        +
        +
        Parameters:
        +
        a - the first value
        +
        b - the second value
        +
        Returns:
        +
        0 if both values are equal, -1 if the first value is smaller, and + 1 otherwise
        +
        +
      • +
      + + + +
        +
      • +

        compareWithNull

        +
        public int compareWithNull(org.h2.value.Value a,
        +                           org.h2.value.Value b,
        +                           boolean forEquality)
        +
        Compare two values with the current comparison mode. The values may have + different data types including NULL.
        +
        +
        Parameters:
        +
        a - the first value
        +
        b - the second value
        +
        forEquality - perform only check for equality (= or <>)
        +
        Returns:
        +
        0 if both values are equal, -1 if the first value is smaller, 1 + if the second value is larger, Integer.MIN_VALUE if order + is not defined due to NULL comparison
        +
        +
      • +
      + + + +
        +
      • +

        compareTypeSafe

        +
        public int compareTypeSafe(org.h2.value.Value a,
        +                           org.h2.value.Value b)
        +
        Compare two values with the current comparison mode. The values must be + of the same type.
        +
        +
        Parameters:
        +
        a - the first value
        +
        b - the second value
        +
        Returns:
        +
        0 if both values are equal, -1 if the first value is smaller, and + 1 otherwise
        +
        +
      • +
      + + + +
        +
      • +

        setTruncateLargeLength

        +
        public void setTruncateLargeLength(boolean truncateLargeLength)
        +
        Changes parsing mode of data types with too large length.
        +
        +
        Parameters:
        +
        truncateLargeLength - true to truncate to valid bound, false to + throw an exception
        +
        +
      • +
      + + + +
        +
      • +

        isTruncateLargeLength

        +
        public boolean isTruncateLargeLength()
        +
        Returns parsing mode of data types with too large length.
        +
        +
        Returns:
        +
        true if large length is truncated, false if an + exception is thrown
        +
        +
      • +
      + + + +
        +
      • +

        setVariableBinary

        +
        public void setVariableBinary(boolean variableBinary)
        +
        Changes parsing of a BINARY data type.
        +
        +
        Parameters:
        +
        variableBinary - true to parse BINARY as VARBINARY, false to + parse it as is
        +
        +
      • +
      + + + +
        +
      • +

        isVariableBinary

        +
        public boolean isVariableBinary()
        +
        Returns BINARY data type parsing mode.
        +
        +
        Returns:
        +
        true if BINARY should be parsed as VARBINARY, + false if it should be parsed as is
        +
        +
      • +
      + + + +
        +
      • +

        setOldInformationSchema

        +
        public void setOldInformationSchema(boolean oldInformationSchema)
        +
        Changes INFORMATION_SCHEMA content.
        +
        +
        Parameters:
        +
        oldInformationSchema - true to have old-style tables in INFORMATION_SCHEMA, + false to have modern tables
        +
        +
      • +
      + + + +
        +
      • +

        isOldInformationSchema

        +
        public boolean isOldInformationSchema()
        +
        Description copied from class: Session
        +
        Returns whether INFORMATION_SCHEMA contains old-style tables.
        +
        +
        Specified by:
        +
        isOldInformationSchema in class Session
        +
        Returns:
        +
        whether INFORMATION_SCHEMA contains old-style tables
        +
        +
      • +
      + + + +
        +
      • +

        getDatabaseMeta

        +
        public org.h2.jdbc.meta.DatabaseMeta getDatabaseMeta()
        +
        Description copied from class: Session
        +
        Returns database meta information.
        +
        +
        Specified by:
        +
        getDatabaseMeta in class Session
        +
        Returns:
        +
        database meta information
        +
        +
      • +
      + + + +
        +
      • +

        zeroBasedEnums

        +
        public boolean zeroBasedEnums()
        +
        Description copied from interface: CastDataProvider
        +
        Returns are ENUM values 0-based.
        +
        +
        Specified by:
        +
        zeroBasedEnums in interface CastDataProvider
        +
        Returns:
        +
        are ENUM values 0-based
        +
        +
      • +
      + + + +
        +
      • +

        setQuirksMode

        +
        public void setQuirksMode(boolean quirksMode)
        +
        Enables or disables the quirks mode.
        +
        +
        Parameters:
        +
        quirksMode - whether quirks mode should be enabled
        +
        +
      • +
      + + + +
        +
      • +

        isQuirksMode

        +
        public boolean isQuirksMode()
        +
        Returns whether quirks mode is enabled explicitly or implicitly.
        +
        +
        Returns:
        +
        true if database is starting or quirks mode was enabled + explicitly, false otherwise
        +
        +
      • +
      + + + +
        +
      • +

        setThreadLocalSession

        +
        public Session setThreadLocalSession()
        +
        Description copied from class: Session
        +
        Sets this session as thread local session, if this session is a local + session.
        +
        +
        Overrides:
        +
        setThreadLocalSession in class Session
        +
        Returns:
        +
        old thread local session, or null
        +
        +
      • +
      + + + +
        +
      • +

        resetThreadLocalSession

        +
        public void resetThreadLocalSession(Session oldSession)
        +
        Description copied from class: Session
        +
        Resets old thread local session.
        +
        +
        Overrides:
        +
        resetThreadLocalSession in class Session
        +
        Parameters:
        +
        oldSession - the old thread local session, or null
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/SessionRemote.html b/h2/docs/javadoc/org/h2/engine/SessionRemote.html new file mode 100644 index 0000000..b6f51e1 --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/SessionRemote.html @@ -0,0 +1,1861 @@ + + + + + +SessionRemote + + + + + + + + + + + + +
+
org.h2.engine
+

Class SessionRemote

+
+
+ +
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.lang.AutoCloseable, CastDataProvider, org.h2.store.DataHandler
    +
    +
    +
    +
    public final class SessionRemote
    +extends Session
    +implements org.h2.store.DataHandler
    +
    The client side part of a session when using the server mode. This object + communicates with a Session on the server side.
    +
  • +
+
+
+ +
+
+
    +
  • + + + + + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getClusterServers

        +
        public java.util.ArrayList<java.lang.String> getClusterServers()
        +
        Description copied from class: Session
        +
        Get the list of the cluster servers for this session.
        +
        +
        Specified by:
        +
        getClusterServers in class Session
        +
        Returns:
        +
        A list of "ip:port" strings for the cluster servers in this + session.
        +
        +
      • +
      + + + +
        +
      • +

        hasPendingTransaction

        +
        public boolean hasPendingTransaction()
        +
        Description copied from class: Session
        +
        Check whether this session has a pending transaction.
        +
        +
        Specified by:
        +
        hasPendingTransaction in class Session
        +
        Returns:
        +
        true if it has
        +
        +
      • +
      + + + +
        +
      • +

        cancel

        +
        public void cancel()
        +
        Description copied from class: Session
        +
        Cancel the current or next command (called when closing a connection).
        +
        +
        Specified by:
        +
        cancel in class Session
        +
        +
      • +
      + + + +
        +
      • +

        cancelStatement

        +
        public void cancelStatement(int id)
        +
        Cancel the statement with the given id.
        +
        +
        Parameters:
        +
        id - the statement id
        +
        +
      • +
      + + + +
        +
      • +

        getClientVersion

        +
        public int getClientVersion()
        +
        Returns the TCP protocol version of remote connection.
        +
        +
        Returns:
        +
        the TCP protocol version
        +
        +
      • +
      + + + +
        +
      • +

        getAutoCommit

        +
        public boolean getAutoCommit()
        +
        Description copied from class: Session
        +
        Check if this session is in auto-commit mode.
        +
        +
        Specified by:
        +
        getAutoCommit in class Session
        +
        Returns:
        +
        true if the session is in auto-commit mode
        +
        +
      • +
      + + + +
        +
      • +

        setAutoCommit

        +
        public void setAutoCommit(boolean autoCommit)
        +
        Description copied from class: Session
        +
        Set the auto-commit mode. This call doesn't commit the current + transaction.
        +
        +
        Specified by:
        +
        setAutoCommit in class Session
        +
        Parameters:
        +
        autoCommit - the new value
        +
        +
      • +
      + + + +
        +
      • +

        setAutoCommitFromServer

        +
        public void setAutoCommitFromServer(boolean autoCommit)
        +
      • +
      + + + +
        +
      • +

        autoCommitIfCluster

        +
        public void autoCommitIfCluster()
        +
        Calls COMMIT if the session is in cluster mode.
        +
      • +
      + + + +
        +
      • +

        connectEmbeddedOrServer

        +
        public Session connectEmbeddedOrServer(boolean openNew)
        +
        Open a new (remote or embedded) session.
        +
        +
        Parameters:
        +
        openNew - whether to open a new session in any case
        +
        Returns:
        +
        the session
        +
        +
      • +
      + + + +
        +
      • +

        removeServer

        +
        public void removeServer(java.io.IOException e,
        +                         int i,
        +                         int count)
        +
        Remove a server from the list of cluster nodes and disables the cluster + mode.
        +
        +
        Parameters:
        +
        e - the exception (used for debugging)
        +
        i - the index of the server to remove
        +
        count - the retry count index
        +
        +
      • +
      + + + +
        +
      • +

        prepareCommand

        +
        public org.h2.command.CommandInterface prepareCommand(java.lang.String sql,
        +                                                      int fetchSize)
        +
        Description copied from class: Session
        +
        Parse a command and prepare it for execution.
        +
        +
        Specified by:
        +
        prepareCommand in class Session
        +
        Parameters:
        +
        sql - the SQL statement
        +
        fetchSize - the number of rows to fetch in one step
        +
        Returns:
        +
        the prepared command
        +
        +
      • +
      + + + +
        +
      • +

        checkClosed

        +
        public void checkClosed()
        +
        Check if this session is closed and throws an exception if so.
        +
        +
        Throws:
        +
        org.h2.message.DbException - if the session is closed
        +
        +
      • +
      + + + +
        +
      • +

        close

        +
        public void close()
        +
        Description copied from class: Session
        +
        Roll back pending transactions and close the session.
        +
        +
        Specified by:
        +
        close in interface java.lang.AutoCloseable
        +
        Specified by:
        +
        close in class Session
        +
        +
      • +
      + + + +
        +
      • +

        getTrace

        +
        public org.h2.message.Trace getTrace()
        +
        Description copied from class: Session
        +
        Get the trace object
        +
        +
        Specified by:
        +
        getTrace in class Session
        +
        Returns:
        +
        the trace object
        +
        +
      • +
      + + + +
        +
      • +

        getNextId

        +
        public int getNextId()
        +
      • +
      + + + +
        +
      • +

        getCurrentId

        +
        public int getCurrentId()
        +
      • +
      + + + +
        +
      • +

        done

        +
        public void done(org.h2.value.Transfer transfer)
        +          throws java.io.IOException
        +
        Called to flush the output after data has been sent to the server and + just before receiving data. This method also reads the status code from + the server and throws any exception the server sent.
        +
        +
        Parameters:
        +
        transfer - the transfer object
        +
        Throws:
        +
        org.h2.message.DbException - if the server sent an exception
        +
        java.io.IOException - if there is a communication problem between client + and server
        +
        +
      • +
      + + + +
        +
      • +

        readException

        +
        public static org.h2.message.DbException readException(org.h2.value.Transfer transfer)
        +                                                throws java.io.IOException
        +
        Reads an exception.
        +
        +
        Parameters:
        +
        transfer - the transfer object
        +
        Returns:
        +
        the exception
        +
        Throws:
        +
        java.io.IOException - on I/O exception
        +
        +
      • +
      + + + +
        +
      • +

        isClustered

        +
        public boolean isClustered()
        +
        Returns true if the connection was opened in cluster mode.
        +
        +
        Returns:
        +
        true if it is
        +
        +
      • +
      + + + +
        +
      • +

        isClosed

        +
        public boolean isClosed()
        +
        Description copied from class: Session
        +
        Check if close was called.
        +
        +
        Specified by:
        +
        isClosed in class Session
        +
        Returns:
        +
        if the session has been closed
        +
        +
      • +
      + + + +
        +
      • +

        traceOperation

        +
        public void traceOperation(java.lang.String operation,
        +                           int id)
        +
        Write the operation to the trace system if debug trace is enabled.
        +
        +
        Parameters:
        +
        operation - the operation performed
        +
        id - the id of the operation
        +
        +
      • +
      + + + +
        +
      • +

        checkPowerOff

        +
        public void checkPowerOff()
        +
        Description copied from interface: org.h2.store.DataHandler
        +
        Check if the simulated power failure occurred. + This call will decrement the countdown.
        +
        +
        Specified by:
        +
        checkPowerOff in interface org.h2.store.DataHandler
        +
        +
      • +
      + + + +
        +
      • +

        checkWritingAllowed

        +
        public void checkWritingAllowed()
        +
        Description copied from interface: org.h2.store.DataHandler
        +
        Check if writing is allowed.
        +
        +
        Specified by:
        +
        checkWritingAllowed in interface org.h2.store.DataHandler
        +
        +
      • +
      + + + +
        +
      • +

        getDatabasePath

        +
        public java.lang.String getDatabasePath()
        +
        Description copied from interface: org.h2.store.DataHandler
        +
        Get the database path.
        +
        +
        Specified by:
        +
        getDatabasePath in interface org.h2.store.DataHandler
        +
        Returns:
        +
        the database path
        +
        +
      • +
      + + + +
        +
      • +

        getMaxLengthInplaceLob

        +
        public int getMaxLengthInplaceLob()
        +
        Description copied from interface: org.h2.store.DataHandler
        +
        Get the maximum length of a in-place large object
        +
        +
        Specified by:
        +
        getMaxLengthInplaceLob in interface org.h2.store.DataHandler
        +
        Returns:
        +
        the maximum size
        +
        +
      • +
      + + + +
        +
      • +

        openFile

        +
        public org.h2.store.FileStore openFile(java.lang.String name,
        +                                       java.lang.String mode,
        +                                       boolean mustExist)
        +
        Description copied from interface: org.h2.store.DataHandler
        +
        Open a file at the given location.
        +
        +
        Specified by:
        +
        openFile in interface org.h2.store.DataHandler
        +
        Parameters:
        +
        name - the file name
        +
        mode - the mode
        +
        mustExist - whether the file must already exist
        +
        Returns:
        +
        the file
        +
        +
      • +
      + + + +
        +
      • +

        getDataHandler

        +
        public org.h2.store.DataHandler getDataHandler()
        +
        Description copied from class: Session
        +
        Get the data handler object.
        +
        +
        Specified by:
        +
        getDataHandler in class Session
        +
        Returns:
        +
        the data handler
        +
        +
      • +
      + + + +
        +
      • +

        getLobSyncObject

        +
        public java.lang.Object getLobSyncObject()
        +
        Description copied from interface: org.h2.store.DataHandler
        +
        Get the synchronization object for lob operations.
        +
        +
        Specified by:
        +
        getLobSyncObject in interface org.h2.store.DataHandler
        +
        Returns:
        +
        the synchronization object
        +
        +
      • +
      + + + +
        +
      • +

        getLobFileListCache

        +
        public org.h2.util.SmallLRUCache<java.lang.String,java.lang.String[]> getLobFileListCache()
        +
        Description copied from interface: org.h2.store.DataHandler
        +
        Get the lob file list cache if it is used.
        +
        +
        Specified by:
        +
        getLobFileListCache in interface org.h2.store.DataHandler
        +
        Returns:
        +
        the cache or null
        +
        +
      • +
      + + + +
        +
      • +

        getLastReconnect

        +
        public int getLastReconnect()
        +
      • +
      + + + +
        +
      • +

        getTempFileDeleter

        +
        public org.h2.util.TempFileDeleter getTempFileDeleter()
        +
        Description copied from interface: org.h2.store.DataHandler
        +
        Get the temp file deleter mechanism.
        +
        +
        Specified by:
        +
        getTempFileDeleter in interface org.h2.store.DataHandler
        +
        Returns:
        +
        the temp file deleter
        +
        +
      • +
      + + + +
        +
      • +

        getLobStorage

        +
        public org.h2.store.LobStorageFrontend getLobStorage()
        +
        Description copied from interface: org.h2.store.DataHandler
        +
        Get the lob storage mechanism to use.
        +
        +
        Specified by:
        +
        getLobStorage in interface org.h2.store.DataHandler
        +
        Returns:
        +
        the lob storage mechanism
        +
        +
      • +
      + + + +
        +
      • +

        readLob

        +
        public int readLob(long lobId,
        +                   byte[] hmac,
        +                   long offset,
        +                   byte[] buff,
        +                   int off,
        +                   int length)
        +
        Description copied from interface: org.h2.store.DataHandler
        +
        Read from a lob.
        +
        +
        Specified by:
        +
        readLob in interface org.h2.store.DataHandler
        +
        Parameters:
        +
        lobId - the lob id
        +
        hmac - the message authentication code
        +
        offset - the offset within the lob
        +
        buff - the target buffer
        +
        off - the offset within the target buffer
        +
        length - the number of bytes to read
        +
        Returns:
        +
        the number of bytes read
        +
        +
      • +
      + + + + + + + +
        +
      • +

        addTemporaryLob

        +
        public org.h2.value.ValueLob addTemporaryLob(org.h2.value.ValueLob v)
        +
        Description copied from class: Session
        +
        Add a temporary LOB, which is closed when the session commits.
        +
        +
        Specified by:
        +
        addTemporaryLob in class Session
        +
        Parameters:
        +
        v - the value
        +
        Returns:
        +
        the specified value
        +
        +
      • +
      + + + +
        +
      • +

        getCompareMode

        +
        public org.h2.value.CompareMode getCompareMode()
        +
        Description copied from interface: org.h2.store.DataHandler
        +
        Return compare mode.
        +
        +
        Specified by:
        +
        getCompareMode in interface org.h2.store.DataHandler
        +
        Returns:
        +
        Compare mode.
        +
        +
      • +
      + + + +
        +
      • +

        isRemote

        +
        public boolean isRemote()
        +
        Description copied from class: Session
        +
        Check if this session is remote or embedded.
        +
        +
        Specified by:
        +
        isRemote in class Session
        +
        Returns:
        +
        true if this session is remote
        +
        +
      • +
      + + + +
        +
      • +

        getCurrentSchemaName

        +
        public java.lang.String getCurrentSchemaName()
        +
        Description copied from class: Session
        +
        Get current schema.
        +
        +
        Specified by:
        +
        getCurrentSchemaName in class Session
        +
        Returns:
        +
        the current schema name
        +
        +
      • +
      + + + +
        +
      • +

        setCurrentSchemaName

        +
        public void setCurrentSchemaName(java.lang.String schema)
        +
        Description copied from class: Session
        +
        Set current schema.
        +
        +
        Specified by:
        +
        setCurrentSchemaName in class Session
        +
        Parameters:
        +
        schema - the schema name
        +
        +
      • +
      + + + +
        +
      • +

        setNetworkConnectionInfo

        +
        public void setNetworkConnectionInfo(org.h2.util.NetworkConnectionInfo networkConnectionInfo)
        +
        Description copied from class: Session
        +
        Sets the network connection information if possible.
        +
        +
        Specified by:
        +
        setNetworkConnectionInfo in class Session
        +
        Parameters:
        +
        networkConnectionInfo - the network connection information
        +
        +
      • +
      + + + +
        +
      • +

        getIsolationLevel

        +
        public IsolationLevel getIsolationLevel()
        +
        Description copied from class: Session
        +
        Returns the isolation level.
        +
        +
        Specified by:
        +
        getIsolationLevel in class Session
        +
        Returns:
        +
        the isolation level
        +
        +
      • +
      + + + +
        +
      • +

        setIsolationLevel

        +
        public void setIsolationLevel(IsolationLevel isolationLevel)
        +
        Description copied from class: Session
        +
        Sets the isolation level.
        +
        +
        Specified by:
        +
        setIsolationLevel in class Session
        +
        Parameters:
        +
        isolationLevel - the isolation level to set
        +
        +
      • +
      + + + +
        +
      • +

        getStaticSettings

        +
        public Session.StaticSettings getStaticSettings()
        +
        Description copied from class: Session
        +
        Returns static settings. These settings cannot be changed during + lifecycle of session.
        +
        +
        Specified by:
        +
        getStaticSettings in class Session
        +
        Returns:
        +
        static settings
        +
        +
      • +
      + + + +
        +
      • +

        getDynamicSettings

        +
        public Session.DynamicSettings getDynamicSettings()
        +
        Description copied from class: Session
        +
        Returns dynamic settings. These settings can be changed during lifecycle + of session.
        +
        +
        Specified by:
        +
        getDynamicSettings in class Session
        +
        Returns:
        +
        dynamic settings
        +
        +
      • +
      + + + +
        +
      • +

        currentTimestamp

        +
        public org.h2.value.ValueTimestampTimeZone currentTimestamp()
        +
        Description copied from interface: CastDataProvider
        +
        Returns the current timestamp with maximum resolution. The value must be + the same within a transaction or within execution of a command.
        +
        +
        Specified by:
        +
        currentTimestamp in interface CastDataProvider
        +
        Returns:
        +
        the current timestamp for CURRENT_TIMESTAMP(9)
        +
        +
      • +
      + + + +
        +
      • +

        currentTimeZone

        +
        public org.h2.util.TimeZoneProvider currentTimeZone()
        +
        Description copied from interface: CastDataProvider
        +
        Returns the current time zone.
        +
        +
        Specified by:
        +
        currentTimeZone in interface CastDataProvider
        +
        Returns:
        +
        the current time zone
        +
        +
      • +
      + + + + + + + +
        +
      • +

        getDatabaseMeta

        +
        public org.h2.jdbc.meta.DatabaseMeta getDatabaseMeta()
        +
        Description copied from class: Session
        +
        Returns database meta information.
        +
        +
        Specified by:
        +
        getDatabaseMeta in class Session
        +
        Returns:
        +
        database meta information
        +
        +
      • +
      + + + +
        +
      • +

        isOldInformationSchema

        +
        public boolean isOldInformationSchema()
        +
        Description copied from class: Session
        +
        Returns whether INFORMATION_SCHEMA contains old-style tables.
        +
        +
        Specified by:
        +
        isOldInformationSchema in class Session
        +
        Returns:
        +
        whether INFORMATION_SCHEMA contains old-style tables
        +
        +
      • +
      + + + +
        +
      • +

        zeroBasedEnums

        +
        public boolean zeroBasedEnums()
        +
        Description copied from interface: CastDataProvider
        +
        Returns are ENUM values 0-based.
        +
        +
        Specified by:
        +
        zeroBasedEnums in interface CastDataProvider
        +
        Returns:
        +
        are ENUM values 0-based
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/Setting.html b/h2/docs/javadoc/org/h2/engine/Setting.html new file mode 100644 index 0000000..58bc4f6 --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/Setting.html @@ -0,0 +1,536 @@ + + + + + +Setting + + + + + + + + + + + + +
+
org.h2.engine
+

Class Setting

+
+
+ +
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    org.h2.util.HasSQL
    +
    +
    +
    +
    public final class Setting
    +extends DbObject
    +
    A persistent database setting.
    +
  • +
+
+
+ +
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        Setting

        +
        public Setting(Database database,
        +               int id,
        +               java.lang.String settingName)
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getSQL

        +
        public java.lang.String getSQL(int sqlFlags)
        +
        Description copied from interface: org.h2.util.HasSQL
        +
        Get the SQL statement of this expression. This may not always be the + original SQL statement, specially after optimization.
        +
        +
        Specified by:
        +
        getSQL in interface org.h2.util.HasSQL
        +
        Overrides:
        +
        getSQL in class DbObject
        +
        Parameters:
        +
        sqlFlags - formatting flags
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        getSQL

        +
        public java.lang.StringBuilder getSQL(java.lang.StringBuilder builder,
        +                                      int sqlFlags)
        +
        Description copied from interface: org.h2.util.HasSQL
        +
        Appends the SQL statement of this object to the specified builder.
        +
        +
        Specified by:
        +
        getSQL in interface org.h2.util.HasSQL
        +
        Overrides:
        +
        getSQL in class DbObject
        +
        Parameters:
        +
        builder - string builder
        +
        sqlFlags - formatting flags
        +
        Returns:
        +
        the specified string builder
        +
        +
      • +
      + + + +
        +
      • +

        setIntValue

        +
        public void setIntValue(int value)
        +
      • +
      + + + +
        +
      • +

        getIntValue

        +
        public int getIntValue()
        +
      • +
      + + + +
        +
      • +

        setStringValue

        +
        public void setStringValue(java.lang.String value)
        +
      • +
      + + + +
        +
      • +

        getStringValue

        +
        public java.lang.String getStringValue()
        +
      • +
      + + + +
        +
      • +

        getCreateSQLForCopy

        +
        public java.lang.String getCreateSQLForCopy(org.h2.table.Table table,
        +                                            java.lang.String quotedName)
        +
        Description copied from class: DbObject
        +
        Build a SQL statement to re-create the object, or to create a copy of the + object with a different name or referencing a different table
        +
        +
        Specified by:
        +
        getCreateSQLForCopy in class DbObject
        +
        Parameters:
        +
        table - the new table
        +
        quotedName - the quoted name
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        getCreateSQL

        +
        public java.lang.String getCreateSQL()
        +
        Description copied from class: DbObject
        +
        Construct the CREATE ... SQL statement for this object.
        +
        +
        Specified by:
        +
        getCreateSQL in class DbObject
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        getType

        +
        public int getType()
        +
        Description copied from class: DbObject
        +
        Get the object type.
        +
        +
        Specified by:
        +
        getType in class DbObject
        +
        Returns:
        +
        the object type
        +
        +
      • +
      + + + +
        +
      • +

        removeChildrenAndResources

        +
        public void removeChildrenAndResources(SessionLocal session)
        +
        Description copied from class: DbObject
        +
        Delete all dependent children objects and resources of this object.
        +
        +
        Specified by:
        +
        removeChildrenAndResources in class DbObject
        +
        Parameters:
        +
        session - the session
        +
        +
      • +
      + + + +
        +
      • +

        checkRename

        +
        public void checkRename()
        +
        Description copied from class: DbObject
        +
        Check if renaming is allowed. Does nothing when allowed.
        +
        +
        Overrides:
        +
        checkRename in class DbObject
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/SettingsBase.html b/h2/docs/javadoc/org/h2/engine/SettingsBase.html new file mode 100644 index 0000000..2912818 --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/SettingsBase.html @@ -0,0 +1,400 @@ + + + + + +SettingsBase + + + + + + + + + + + + +
+
org.h2.engine
+

Class SettingsBase

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.engine.SettingsBase
    • +
    +
  • +
+
+
    +
  • +
    +
    Direct Known Subclasses:
    +
    DbSettings
    +
    +
    +
    +
    public class SettingsBase
    +extends java.lang.Object
    +
    The base class for settings.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + + + +
      Constructors 
      ModifierConstructor and Description
      protected SettingsBase(java.util.HashMap<java.lang.String,java.lang.String> s) 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      protected booleancontainsKey(java.lang.String k) +
      Check if the settings contains the given key.
      +
      protected booleanget(java.lang.String key, + boolean defaultValue) +
      Get the setting for the given key.
      +
      protected intget(java.lang.String key, + int defaultValue) +
      Get the setting for the given key.
      +
      protected java.lang.Stringget(java.lang.String key, + java.lang.String defaultValue) +
      Get the setting for the given key.
      +
      java.util.HashMap<java.lang.String,java.lang.String>getSettings() +
      Get all settings.
      +
      java.util.Map.Entry<java.lang.String,java.lang.String>[]getSortedSettings() +
      Get all settings in alphabetical order.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        SettingsBase

        +
        protected SettingsBase(java.util.HashMap<java.lang.String,java.lang.String> s)
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        get

        +
        protected boolean get(java.lang.String key,
        +                      boolean defaultValue)
        +
        Get the setting for the given key.
        +
        +
        Parameters:
        +
        key - the key
        +
        defaultValue - the default value
        +
        Returns:
        +
        the setting
        +
        +
      • +
      + + + +
        +
      • +

        get

        +
        protected int get(java.lang.String key,
        +                  int defaultValue)
        +
        Get the setting for the given key.
        +
        +
        Parameters:
        +
        key - the key
        +
        defaultValue - the default value
        +
        Returns:
        +
        the setting
        +
        +
      • +
      + + + +
        +
      • +

        get

        +
        protected java.lang.String get(java.lang.String key,
        +                               java.lang.String defaultValue)
        +
        Get the setting for the given key.
        +
        +
        Parameters:
        +
        key - the key
        +
        defaultValue - the default value
        +
        Returns:
        +
        the setting
        +
        +
      • +
      + + + +
        +
      • +

        containsKey

        +
        protected boolean containsKey(java.lang.String k)
        +
        Check if the settings contains the given key.
        +
        +
        Parameters:
        +
        k - the key
        +
        Returns:
        +
        true if they do
        +
        +
      • +
      + + + +
        +
      • +

        getSettings

        +
        public java.util.HashMap<java.lang.String,java.lang.String> getSettings()
        +
        Get all settings.
        +
        +
        Returns:
        +
        the settings
        +
        +
      • +
      + + + +
        +
      • +

        getSortedSettings

        +
        public java.util.Map.Entry<java.lang.String,java.lang.String>[] getSortedSettings()
        +
        Get all settings in alphabetical order.
        +
        +
        Returns:
        +
        the settings
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/SysProperties.html b/h2/docs/javadoc/org/h2/engine/SysProperties.html new file mode 100644 index 0000000..3a4cebe --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/SysProperties.html @@ -0,0 +1,1139 @@ + + + + + +SysProperties + + + + + + + + + + + + +
+
org.h2.engine
+

Class SysProperties

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.engine.SysProperties
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public class SysProperties
    +extends java.lang.Object
    +
    The constants defined in this class are initialized from system properties. + Some system properties are per machine settings, and others are as a last + resort and temporary solution to work around a problem in the application or + database engine. Also, there are system properties to enable features that + are not yet fully tested or that are not backward compatible. +

    + System properties can be set when starting the virtual machine: +

    + +
    + java -Dh2.baseDir=/temp
    + 
    + + They can be set within the application, but this must be done before loading + any classes of this database (before loading the JDBC driver): + +
    + System.setProperty("h2.baseDir", "/temp");
    + 
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Fields 
      Modifier and TypeField and Description
      static java.lang.StringALLOWED_CLASSES +
      System property h2.allowedClasses (default: *).
      +
      static java.lang.StringAUTH_CONFIG_FILE +
      System property h2.authConfigFile + (default: null).
      +
      static java.lang.StringBIND_ADDRESS +
      System property h2.bindAddress (default: null).
      +
      static booleanCHECK +
      System property h2.check + (default: true for JDK/JRE, false for Android).
      +
      static java.lang.StringCLIENT_TRACE_DIRECTORY +
      System property h2.clientTraceDirectory (default: + trace.db/).
      +
      static intCOLLATOR_CACHE_SIZE +
      System property h2.collatorCacheSize (default: 3 + 2000).
      +
      static intCONSOLE_MAX_PROCEDURES_LIST_COLUMNS +
      System property h2.consoleProcedureColumns + (default: 500).
      +
      static intCONSOLE_MAX_TABLES_LIST_COLUMNS +
      System property h2.consoleTableColumns + (default: 500).
      +
      static intCONSOLE_MAX_TABLES_LIST_INDEXES +
      System property h2.consoleTableIndexes + (default: 100).
      +
      static booleanCONSOLE_STREAM +
      System property h2.consoleStream (default: true).
      +
      static intCONSOLE_TIMEOUT +
      System property h2.consoleTimeout (default: 1800000).
      +
      static intDATASOURCE_TRACE_LEVEL +
      System property h2.dataSourceTraceLevel (default: 1).
      +
      static intDELAY_WRONG_PASSWORD_MAX +
      System property h2.delayWrongPasswordMax + (default: 4000).
      +
      static intDELAY_WRONG_PASSWORD_MIN +
      System property h2.delayWrongPasswordMin + (default: 250).
      +
      static booleanENABLE_ANONYMOUS_TLS +
      System property h2.enableAnonymousTLS (default: true).
      +
      static booleanFORCE_AUTOCOMMIT_OFF_ON_COMMIT +
      System property h2.forceAutoCommitOffOnCommit (default: false).
      +
      static java.lang.StringH2_BROWSER +
      INTERNAL
      +
      static java.lang.StringH2_SCRIPT_DIRECTORY +
      INTERNAL
      +
      static java.lang.StringJAVA_OBJECT_SERIALIZER +
      System property h2.javaObjectSerializer + (default: null).
      +
      static booleanJAVA_SYSTEM_COMPILER +
      System property h2.javaSystemCompiler (default: true).
      +
      static intLOB_CLIENT_MAX_SIZE_MEMORY +
      System property h2.lobClientMaxSizeMemory (default: + 1048576).
      +
      static booleanlobCloseBetweenReads +
      System property h2.lobCloseBetweenReads + (default: false).
      +
      static intMAX_FILE_RETRY +
      System property h2.maxFileRetry (default: 16).
      +
      static intMAX_MEMORY_ROWS +
      System property h2.maxMemoryRows + (default: 40000 per GB of available RAM).
      +
      static intMAX_RECONNECT +
      System property h2.maxReconnect (default: 3).
      +
      static longMAX_TRACE_DATA_LENGTH +
      System property h2.maxTraceDataLength + (default: 65535).
      +
      static booleanNIO_CLEANER_HACK +
      System property h2.nioCleanerHack (default: false).
      +
      static booleanNIO_LOAD_MAPPED +
      System property h2.nioLoadMapped (default: false).
      +
      static booleanOBJECT_CACHE +
      System property h2.objectCache (default: true).
      +
      static intOBJECT_CACHE_MAX_PER_ELEMENT_SIZE +
      System property h2.objectCacheMaxPerElementSize (default: + 4096).
      +
      static intOBJECT_CACHE_SIZE +
      System property h2.objectCacheSize (default: 1024).
      +
      static java.lang.StringPG_DEFAULT_CLIENT_ENCODING +
      System property h2.pgClientEncoding (default: UTF-8).
      +
      static java.lang.StringPREFIX_TEMP_FILE +
      System property h2.prefixTempFile (default: h2.temp).
      +
      static intSERVER_CACHED_OBJECTS +
      System property h2.serverCachedObjects (default: 64).
      +
      static intSERVER_RESULT_SET_FETCH_SIZE +
      System property h2.serverResultSetFetchSize + (default: 100).
      +
      static intSOCKET_CONNECT_RETRY +
      System property h2.socketConnectRetry (default: 16).
      +
      static intSOCKET_CONNECT_TIMEOUT +
      System property h2.socketConnectTimeout + (default: 2000).
      +
      static longSPLIT_FILE_SIZE_SHIFT +
      System property h2.splitFileSizeShift (default: 30).
      +
      static booleanTHREAD_DEADLOCK_DETECTOR +
      System property h2.threadDeadlockDetector + (default: false).
      +
      static booleanTRACE_IO +
      System property h2.traceIO (default: false).
      +
      static java.lang.StringURL_MAP +
      System property h2.urlMap (default: null).
      +
      static booleanUSE_THREAD_CONTEXT_CLASS_LOADER +
      System property h2.useThreadContextClassLoader + (default: false).
      +
      static java.lang.StringUSER_HOME +
      System property user.home (empty string if not set).
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethod and Description
      static java.lang.StringgetBaseDir() +
      INTERNAL
      +
      static java.lang.StringgetScriptDirectory() +
      System property h2.scriptDirectory (default: empty + string).
      +
      static voidsetBaseDir(java.lang.String dir) +
      INTERNAL
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Detail

      + + + +
        +
      • +

        H2_SCRIPT_DIRECTORY

        +
        public static final java.lang.String H2_SCRIPT_DIRECTORY
        +
        INTERNAL
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        H2_BROWSER

        +
        public static final java.lang.String H2_BROWSER
        +
        INTERNAL
        +
        +
        See Also:
        +
        Constant Field Values
        +
        +
      • +
      + + + +
        +
      • +

        USER_HOME

        +
        public static final java.lang.String USER_HOME
        +
        System property user.home (empty string if not set). + It is usually set by the system, and used as a replacement for ~ in file + names.
        +
      • +
      + + + +
        +
      • +

        ALLOWED_CLASSES

        +
        public static final java.lang.String ALLOWED_CLASSES
        +
        System property h2.allowedClasses (default: *). + Comma separated list of class names or prefixes.
        +
      • +
      + + + +
        +
      • +

        ENABLE_ANONYMOUS_TLS

        +
        public static final boolean ENABLE_ANONYMOUS_TLS
        +
        System property h2.enableAnonymousTLS (default: true). + When using TLS connection, the anonymous cipher suites should be enabled.
        +
      • +
      + + + +
        +
      • +

        BIND_ADDRESS

        +
        public static final java.lang.String BIND_ADDRESS
        +
        System property h2.bindAddress (default: null). + The bind address to use.
        +
      • +
      + + + +
        +
      • +

        CHECK

        +
        public static final boolean CHECK
        +
        System property h2.check + (default: true for JDK/JRE, false for Android). + Optional additional checks in the database engine.
        +
      • +
      + + + +
        +
      • +

        CLIENT_TRACE_DIRECTORY

        +
        public static final java.lang.String CLIENT_TRACE_DIRECTORY
        +
        System property h2.clientTraceDirectory (default: + trace.db/). + Directory where the trace files of the JDBC client are stored (only for + client / server).
        +
      • +
      + + + +
        +
      • +

        COLLATOR_CACHE_SIZE

        +
        public static final int COLLATOR_CACHE_SIZE
        +
        System property h2.collatorCacheSize (default: 3 + 2000). + The cache size for collation keys (in elements). Used when a collator has + been set for the database.
        +
      • +
      + + + +
        +
      • +

        CONSOLE_MAX_TABLES_LIST_INDEXES

        +
        public static final int CONSOLE_MAX_TABLES_LIST_INDEXES
        +
        System property h2.consoleTableIndexes + (default: 100). + Up to this many tables, the column type and indexes are listed.
        +
      • +
      + + + +
        +
      • +

        CONSOLE_MAX_TABLES_LIST_COLUMNS

        +
        public static final int CONSOLE_MAX_TABLES_LIST_COLUMNS
        +
        System property h2.consoleTableColumns + (default: 500). + Up to this many tables, the column names are listed.
        +
      • +
      + + + +
        +
      • +

        CONSOLE_MAX_PROCEDURES_LIST_COLUMNS

        +
        public static final int CONSOLE_MAX_PROCEDURES_LIST_COLUMNS
        +
        System property h2.consoleProcedureColumns + (default: 500). + Up to this many procedures, the column names are listed.
        +
      • +
      + + + +
        +
      • +

        CONSOLE_STREAM

        +
        public static final boolean CONSOLE_STREAM
        +
        System property h2.consoleStream (default: true). + H2 Console: stream query results.
        +
      • +
      + + + +
        +
      • +

        CONSOLE_TIMEOUT

        +
        public static final int CONSOLE_TIMEOUT
        +
        System property h2.consoleTimeout (default: 1800000). + H2 Console: session timeout in milliseconds. The default is 30 minutes.
        +
      • +
      + + + +
        +
      • +

        DATASOURCE_TRACE_LEVEL

        +
        public static final int DATASOURCE_TRACE_LEVEL
        +
        System property h2.dataSourceTraceLevel (default: 1). + The trace level of the data source implementation. Default is 1 for + error.
        +
      • +
      + + + +
        +
      • +

        DELAY_WRONG_PASSWORD_MIN

        +
        public static final int DELAY_WRONG_PASSWORD_MIN
        +
        System property h2.delayWrongPasswordMin + (default: 250). + The minimum delay in milliseconds before an exception is thrown for using + the wrong user name or password. This slows down brute force attacks. The + delay is reset to this value after a successful login. Unsuccessful + logins will double the time until DELAY_WRONG_PASSWORD_MAX. + To disable the delay, set this system property to 0.
        +
      • +
      + + + +
        +
      • +

        DELAY_WRONG_PASSWORD_MAX

        +
        public static final int DELAY_WRONG_PASSWORD_MAX
        +
        System property h2.delayWrongPasswordMax + (default: 4000). + The maximum delay in milliseconds before an exception is thrown for using + the wrong user name or password. This slows down brute force attacks. The + delay is reset after a successful login. The value 0 means there is no + maximum delay.
        +
      • +
      + + + +
        +
      • +

        JAVA_SYSTEM_COMPILER

        +
        public static final boolean JAVA_SYSTEM_COMPILER
        +
        System property h2.javaSystemCompiler (default: true). + Whether to use the Java system compiler + (ToolProvider.getSystemJavaCompiler()) if it is available to compile user + defined functions. If disabled or if the system compiler is not + available, the com.sun.tools.javac compiler is used if available, and + "javac" (as an external process) is used if not.
        +
      • +
      + + + +
        +
      • +

        lobCloseBetweenReads

        +
        public static boolean lobCloseBetweenReads
        +
        System property h2.lobCloseBetweenReads + (default: false). + Close LOB files between read operations.
        +
      • +
      + + + +
        +
      • +

        LOB_CLIENT_MAX_SIZE_MEMORY

        +
        public static final int LOB_CLIENT_MAX_SIZE_MEMORY
        +
        System property h2.lobClientMaxSizeMemory (default: + 1048576). + The maximum size of a LOB object to keep in memory on the client side + when using the server mode.
        +
      • +
      + + + +
        +
      • +

        MAX_FILE_RETRY

        +
        public static final int MAX_FILE_RETRY
        +
        System property h2.maxFileRetry (default: 16). + Number of times to retry file delete and rename. in Windows, files can't + be deleted if they are open. Waiting a bit can help (sometimes the + Windows Explorer opens the files for a short time) may help. Sometimes, + running garbage collection may close files if the user forgot to call + Connection.close() or InputStream.close().
        +
      • +
      + + + +
        +
      • +

        MAX_RECONNECT

        +
        public static final int MAX_RECONNECT
        +
        System property h2.maxReconnect (default: 3). + The maximum number of tries to reconnect in a row.
        +
      • +
      + + + +
        +
      • +

        MAX_MEMORY_ROWS

        +
        public static final int MAX_MEMORY_ROWS
        +
        System property h2.maxMemoryRows + (default: 40000 per GB of available RAM). + The default maximum number of rows to be kept in memory in a result set.
        +
      • +
      + + + +
        +
      • +

        MAX_TRACE_DATA_LENGTH

        +
        public static final long MAX_TRACE_DATA_LENGTH
        +
        System property h2.maxTraceDataLength + (default: 65535). + The maximum size of a LOB value that is written as data to the trace + system.
        +
      • +
      + + + +
        +
      • +

        NIO_LOAD_MAPPED

        +
        public static final boolean NIO_LOAD_MAPPED
        +
        System property h2.nioLoadMapped (default: false). + If the mapped buffer should be loaded when the file is opened. + This can improve performance.
        +
      • +
      + + + +
        +
      • +

        NIO_CLEANER_HACK

        +
        public static final boolean NIO_CLEANER_HACK
        +
        System property h2.nioCleanerHack (default: false). + If enabled, use the reflection hack to un-map the mapped file if + possible. If disabled, System.gc() is called in a loop until the object + is garbage collected. See also + https://bugs.openjdk.java.net/browse/JDK-4724038
        +
      • +
      + + + +
        +
      • +

        OBJECT_CACHE

        +
        public static final boolean OBJECT_CACHE
        +
        System property h2.objectCache (default: true). + Cache commonly used values (numbers, strings). There is a shared cache + for all values.
        +
      • +
      + + + +
        +
      • +

        OBJECT_CACHE_MAX_PER_ELEMENT_SIZE

        +
        public static final int OBJECT_CACHE_MAX_PER_ELEMENT_SIZE
        +
        System property h2.objectCacheMaxPerElementSize (default: + 4096). + The maximum size (precision) of an object in the cache.
        +
      • +
      + + + +
        +
      • +

        OBJECT_CACHE_SIZE

        +
        public static final int OBJECT_CACHE_SIZE
        +
        System property h2.objectCacheSize (default: 1024). + The maximum number of objects in the cache. + This value must be a power of 2.
        +
      • +
      + + + +
        +
      • +

        PG_DEFAULT_CLIENT_ENCODING

        +
        public static final java.lang.String PG_DEFAULT_CLIENT_ENCODING
        +
        System property h2.pgClientEncoding (default: UTF-8). + Default client encoding for PG server. It is used if the client does not + sends his encoding.
        +
      • +
      + + + +
        +
      • +

        PREFIX_TEMP_FILE

        +
        public static final java.lang.String PREFIX_TEMP_FILE
        +
        System property h2.prefixTempFile (default: h2.temp). + The prefix for temporary files in the temp directory.
        +
      • +
      + + + +
        +
      • +

        FORCE_AUTOCOMMIT_OFF_ON_COMMIT

        +
        public static boolean FORCE_AUTOCOMMIT_OFF_ON_COMMIT
        +
        System property h2.forceAutoCommitOffOnCommit (default: false). + Throw error if transaction's auto-commit property is true when a commit is executed.
        +
      • +
      + + + +
        +
      • +

        SERVER_CACHED_OBJECTS

        +
        public static final int SERVER_CACHED_OBJECTS
        +
        System property h2.serverCachedObjects (default: 64). + TCP Server: number of cached objects per session.
        +
      • +
      + + + +
        +
      • +

        SERVER_RESULT_SET_FETCH_SIZE

        +
        public static final int SERVER_RESULT_SET_FETCH_SIZE
        +
        System property h2.serverResultSetFetchSize + (default: 100). + The default result set fetch size when using the server mode.
        +
      • +
      + + + +
        +
      • +

        SOCKET_CONNECT_RETRY

        +
        public static final int SOCKET_CONNECT_RETRY
        +
        System property h2.socketConnectRetry (default: 16). + The number of times to retry opening a socket. Windows sometimes fails + to open a socket, see bug + https://bugs.openjdk.java.net/browse/JDK-6213296
        +
      • +
      + + + +
        +
      • +

        SOCKET_CONNECT_TIMEOUT

        +
        public static final int SOCKET_CONNECT_TIMEOUT
        +
        System property h2.socketConnectTimeout + (default: 2000). + The timeout in milliseconds to connect to a server.
        +
      • +
      + + + +
        +
      • +

        SPLIT_FILE_SIZE_SHIFT

        +
        public static final long SPLIT_FILE_SIZE_SHIFT
        +
        System property h2.splitFileSizeShift (default: 30). + The maximum file size of a split file is 1L << x.
        +
      • +
      + + + +
        +
      • +

        TRACE_IO

        +
        public static final boolean TRACE_IO
        +
        System property h2.traceIO (default: false). + Trace all I/O operations.
        +
      • +
      + + + +
        +
      • +

        THREAD_DEADLOCK_DETECTOR

        +
        public static final boolean THREAD_DEADLOCK_DETECTOR
        +
        System property h2.threadDeadlockDetector + (default: false). + Detect thread deadlocks in a background thread.
        +
      • +
      + + + +
        +
      • +

        URL_MAP

        +
        public static final java.lang.String URL_MAP
        +
        System property h2.urlMap (default: null). + A properties file that contains a mapping between database URLs. New + connections are written into the file. An empty value in the map means no + redirection is used for the given URL.
        +
      • +
      + + + +
        +
      • +

        USE_THREAD_CONTEXT_CLASS_LOADER

        +
        public static final boolean USE_THREAD_CONTEXT_CLASS_LOADER
        +
        System property h2.useThreadContextClassLoader + (default: false). + Instead of using the default class loader when deserializing objects, the + current thread-context class loader will be used.
        +
      • +
      + + + +
        +
      • +

        JAVA_OBJECT_SERIALIZER

        +
        public static final java.lang.String JAVA_OBJECT_SERIALIZER
        +
        System property h2.javaObjectSerializer + (default: null). + The JavaObjectSerializer class name for java objects being stored in + column of type OTHER. It must be the same on client and server to work + correctly.
        +
      • +
      + + + +
        +
      • +

        AUTH_CONFIG_FILE

        +
        public static final java.lang.String AUTH_CONFIG_FILE
        +
        System property h2.authConfigFile + (default: null). + authConfigFile define the URL of configuration file + of DefaultAuthenticator
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        setBaseDir

        +
        public static void setBaseDir(java.lang.String dir)
        +
        INTERNAL
        +
        +
        Parameters:
        +
        dir - base directory
        +
        +
      • +
      + + + +
        +
      • +

        getBaseDir

        +
        public static java.lang.String getBaseDir()
        +
        INTERNAL
        +
        +
        Returns:
        +
        base directory
        +
        +
      • +
      + + + +
        +
      • +

        getScriptDirectory

        +
        public static java.lang.String getScriptDirectory()
        +
        System property h2.scriptDirectory (default: empty + string). + Relative or absolute directory where the script files are stored to or + read from.
        +
        +
        Returns:
        +
        the current value
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/User.html b/h2/docs/javadoc/org/h2/engine/User.html new file mode 100644 index 0000000..402cfaa --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/User.html @@ -0,0 +1,652 @@ + + + + + +User + + + + + + + + + + + + +
+
org.h2.engine
+

Class User

+
+
+ +
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    org.h2.util.HasSQL
    +
    +
    +
    +
    public final class User
    +extends RightOwner
    +
    Represents a user object.
    +
  • +
+
+
+ +
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        User

        +
        public User(Database database,
        +            int id,
        +            java.lang.String userName,
        +            boolean systemUser)
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        setAdmin

        +
        public void setAdmin(boolean admin)
        +
      • +
      + + + +
        +
      • +

        isAdmin

        +
        public boolean isAdmin()
        +
      • +
      + + + +
        +
      • +

        setSaltAndHash

        +
        public void setSaltAndHash(byte[] salt,
        +                           byte[] hash)
        +
        Set the salt and hash of the password for this user.
        +
        +
        Parameters:
        +
        salt - the salt
        +
        hash - the password hash
        +
        +
      • +
      + + + +
        +
      • +

        setUserPasswordHash

        +
        public void setUserPasswordHash(byte[] userPasswordHash)
        +
        Set the user name password hash. A random salt is generated as well. + The parameter is filled with zeros after use.
        +
        +
        Parameters:
        +
        userPasswordHash - the user name password hash
        +
        +
      • +
      + + + +
        +
      • +

        getCreateSQLForCopy

        +
        public java.lang.String getCreateSQLForCopy(org.h2.table.Table table,
        +                                            java.lang.String quotedName)
        +
        Description copied from class: DbObject
        +
        Build a SQL statement to re-create the object, or to create a copy of the + object with a different name or referencing a different table
        +
        +
        Specified by:
        +
        getCreateSQLForCopy in class DbObject
        +
        Parameters:
        +
        table - the new table
        +
        quotedName - the quoted name
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        getCreateSQL

        +
        public java.lang.String getCreateSQL()
        +
        Description copied from class: DbObject
        +
        Construct the CREATE ... SQL statement for this object.
        +
        +
        Specified by:
        +
        getCreateSQL in class DbObject
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        getCreateSQL

        +
        public java.lang.String getCreateSQL(boolean password)
        +
        Get the CREATE SQL statement for this object.
        +
        +
        Parameters:
        +
        password - true if the password (actually the salt and hash) should + be returned
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        checkAdmin

        +
        public void checkAdmin()
        +
        Checks if this user has admin rights. An exception is thrown if user + doesn't have them.
        +
        +
        Throws:
        +
        org.h2.message.DbException - if this user is not an admin
        +
        +
      • +
      + + + +
        +
      • +

        checkSchemaAdmin

        +
        public void checkSchemaAdmin()
        +
        Checks if this user has schema admin rights for every schema. An + exception is thrown if user doesn't have them.
        +
        +
        Throws:
        +
        org.h2.message.DbException - if this user is not a schema admin
        +
        +
      • +
      + + + +
        +
      • +

        checkSchemaOwner

        +
        public void checkSchemaOwner(org.h2.schema.Schema schema)
        +
        Checks if this user has schema owner rights for the specified schema. An + exception is thrown if user doesn't have them.
        +
        +
        Parameters:
        +
        schema - the schema
        +
        Throws:
        +
        org.h2.message.DbException - if this user is not a schema owner
        +
        +
      • +
      + + + +
        +
      • +

        checkTableRight

        +
        public void checkTableRight(org.h2.table.Table table,
        +                            int rightMask)
        +
        Checks that this user has the given rights for the specified table.
        +
        +
        Parameters:
        +
        table - the table
        +
        rightMask - the rights required
        +
        Throws:
        +
        org.h2.message.DbException - if this user does not have the required rights
        +
        +
      • +
      + + + +
        +
      • +

        hasTableRight

        +
        public boolean hasTableRight(org.h2.table.Table table,
        +                             int rightMask)
        +
        See if this user has the given rights for this database object.
        +
        +
        Parameters:
        +
        table - the database object, or null for schema-only check
        +
        rightMask - the rights required
        +
        Returns:
        +
        true if the user has the rights
        +
        +
      • +
      + + + +
        +
      • +

        getType

        +
        public int getType()
        +
        Description copied from class: DbObject
        +
        Get the object type.
        +
        +
        Specified by:
        +
        getType in class DbObject
        +
        Returns:
        +
        the object type
        +
        +
      • +
      + + + +
        +
      • +

        getChildren

        +
        public java.util.ArrayList<DbObject> getChildren()
        +
        Description copied from class: DbObject
        +
        Get the list of dependent children (for tables, this includes indexes and + so on).
        +
        +
        Overrides:
        +
        getChildren in class DbObject
        +
        Returns:
        +
        the list of children, or null
        +
        +
      • +
      + + + +
        +
      • +

        removeChildrenAndResources

        +
        public void removeChildrenAndResources(SessionLocal session)
        +
        Description copied from class: DbObject
        +
        Delete all dependent children objects and resources of this object.
        +
        +
        Specified by:
        +
        removeChildrenAndResources in class DbObject
        +
        Parameters:
        +
        session - the session
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/UserBuilder.html b/h2/docs/javadoc/org/h2/engine/UserBuilder.html new file mode 100644 index 0000000..781abd1 --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/UserBuilder.html @@ -0,0 +1,284 @@ + + + + + +UserBuilder + + + + + + + + + + + + +
+
org.h2.engine
+

Class UserBuilder

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.engine.UserBuilder
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public class UserBuilder
    +extends java.lang.Object
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      UserBuilder() 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethod and Description
      static UserbuildUser(org.h2.security.auth.AuthenticationInfo authenticationInfo, + Database database, + boolean persistent) +
      Build the database user starting from authentication informations.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        UserBuilder

        +
        public UserBuilder()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        buildUser

        +
        public static User buildUser(org.h2.security.auth.AuthenticationInfo authenticationInfo,
        +                             Database database,
        +                             boolean persistent)
        +
        Build the database user starting from authentication informations.
        +
        +
        Parameters:
        +
        authenticationInfo - authentication info
        +
        database - target database
        +
        persistent - true if the user will be persisted in the database
        +
        Returns:
        +
        user bean
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/package-frame.html b/h2/docs/javadoc/org/h2/engine/package-frame.html new file mode 100644 index 0000000..06b1862 --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/package-frame.html @@ -0,0 +1,61 @@ + + + + + +org.h2.engine + + + + + +

org.h2.engine

+ + + diff --git a/h2/docs/javadoc/org/h2/engine/package-summary.html b/h2/docs/javadoc/org/h2/engine/package-summary.html new file mode 100644 index 0000000..b97fe4b --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/package-summary.html @@ -0,0 +1,385 @@ + + + + + +org.h2.engine + + + + + + + + + + + +
+

Package org.h2.engine

+
+
+ +Contains high level classes of the database and classes that don't fit in another sub-package.
+
+

See: Description

+
+
+
    +
  • + + + + + + + + + + + + +
    Interface Summary 
    InterfaceDescription
    CastDataProvider +
    Provides information for type casts and comparison operations.
    +
    +
  • +
  • + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Class Summary 
    ClassDescription
    Comment +
    Represents a database object comment.
    +
    ConnectionInfo +
    Encapsulates the connection settings, including user name and password.
    +
    Constants +
    Constants are fixed values that are used in the whole database code.
    +
    Database +
    There is one database object per open database.
    +
    DbObject +
    A database object such as a table, an index, or a user.
    +
    DbSettings +
    This class contains various database-level settings.
    +
    Engine +
    The engine contains a map of all open databases.
    +
    GeneratedKeysMode +
    Modes of generated keys' gathering.
    +
    MetaRecord +
    A record in the system table of the database.
    +
    Mode +
    The compatibility modes.
    +
    Procedure +
    Represents a procedure.
    +
    QueryStatisticsData +
    Maintains query statistics.
    +
    QueryStatisticsData.QueryEntry +
    The collected statistics for one query.
    +
    Right +
    An access right.
    +
    RightOwner +
    A right owner (sometimes called principal).
    +
    Role +
    Represents a role.
    +
    Session +
    A local or remote session.
    +
    Session.DynamicSettings +
    Dynamic settings.
    +
    Session.StaticSettings +
    Static settings.
    +
    SessionLocal +
    A session represents an embedded database connection.
    +
    SessionLocal.Savepoint +
    Represents a savepoint (a position in a transaction to where one can roll + back to).
    +
    SessionLocal.TimeoutValue +
    An LOB object with a timeout.
    +
    SessionRemote +
    The client side part of a session when using the server mode.
    +
    Setting +
    A persistent database setting.
    +
    SettingsBase +
    The base class for settings.
    +
    SysProperties +
    The constants defined in this class are initialized from system properties.
    +
    User +
    Represents a user object.
    +
    UserBuilder 
    +
  • +
  • + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Enum Summary 
    EnumDescription
    IsolationLevel +
    Level of isolation.
    +
    Mode.CharPadding +
    When CHAR values are right-padded with spaces.
    +
    Mode.ExpressionNames +
    Generation of column names for expressions.
    +
    Mode.ModeEnum 
    Mode.UniqueIndexNullsHandling +
    Determines how rows with NULL values in indexed columns are handled + in unique indexes.
    +
    Mode.ViewExpressionNames +
    Generation of column names for expressions to be used in a view.
    +
    SessionLocal.State 
    +
  • +
+ + + +

Package org.h2.engine Description

+

+ +Contains high level classes of the database and classes that don't fit in another sub-package. + +

+
+ + + + + + diff --git a/h2/docs/javadoc/org/h2/engine/package-tree.html b/h2/docs/javadoc/org/h2/engine/package-tree.html new file mode 100644 index 0000000..fda6607 --- /dev/null +++ b/h2/docs/javadoc/org/h2/engine/package-tree.html @@ -0,0 +1,196 @@ + + + + + +org.h2.engine Class Hierarchy + + + + + + + + + + + +
+

Hierarchy For Package org.h2.engine

+Package Hierarchies: + +
+
+

Class Hierarchy

+ +

Interface Hierarchy

+ +

Enum Hierarchy

+ +
+ + + + + + diff --git a/h2/docs/javadoc/org/h2/fulltext/FullText.FullTextTrigger.html b/h2/docs/javadoc/org/h2/fulltext/FullText.FullTextTrigger.html new file mode 100644 index 0000000..eafca91 --- /dev/null +++ b/h2/docs/javadoc/org/h2/fulltext/FullText.FullTextTrigger.html @@ -0,0 +1,401 @@ + + + + + +FullText.FullTextTrigger + + + + + + + + + + + + +
+
org.h2.fulltext
+

Class FullText.FullTextTrigger

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.fulltext.FullText.FullTextTrigger
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    Trigger
    +
    +
    +
    Enclosing class:
    +
    FullText
    +
    +
    +
    +
    public static final class FullText.FullTextTrigger
    +extends java.lang.Object
    +implements Trigger
    +
    Trigger updates the index when a inserting, updating, or deleting a row.
    +
  • +
+
+
+
    +
  • + + + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      FullTextTrigger() 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      voidclose() +
      INTERNAL
      +
      voidfire(java.sql.Connection conn, + java.lang.Object[] oldRow, + java.lang.Object[] newRow) +
      INTERNAL
      +
      voidinit(java.sql.Connection conn, + java.lang.String schemaName, + java.lang.String triggerName, + java.lang.String tableName, + boolean before, + int type) +
      INTERNAL
      +
      voidremove() +
      INTERNAL
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        FullTextTrigger

        +
        public FullTextTrigger()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        init

        +
        public void init(java.sql.Connection conn,
        +                 java.lang.String schemaName,
        +                 java.lang.String triggerName,
        +                 java.lang.String tableName,
        +                 boolean before,
        +                 int type)
        +          throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        init in interface Trigger
        +
        Parameters:
        +
        conn - a connection to the database (a system connection)
        +
        schemaName - the name of the schema
        +
        triggerName - the name of the trigger used in the CREATE TRIGGER + statement
        +
        tableName - the name of the table
        +
        before - whether the fire method is called before or after the + operation is performed
        +
        type - the operation type: INSERT, UPDATE, DELETE, SELECT, or a + combination (this parameter is a bit field)
        +
        Throws:
        +
        java.sql.SQLException - on SQL exception
        +
        See Also:
        +
        Trigger.init(Connection, String, String, String, boolean, int)
        +
        +
      • +
      + + + +
        +
      • +

        fire

        +
        public void fire(java.sql.Connection conn,
        +                 java.lang.Object[] oldRow,
        +                 java.lang.Object[] newRow)
        +          throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        fire in interface Trigger
        +
        Parameters:
        +
        conn - a connection to the database
        +
        oldRow - the old row, or null if no old row is available (for + INSERT)
        +
        newRow - the new row, or null if no new row is available (for + DELETE)
        +
        Throws:
        +
        java.sql.SQLException - if the operation must be undone
        +
        See Also:
        +
        Trigger.fire(Connection, Object[], Object[])
        +
        +
      • +
      + + + +
        +
      • +

        close

        +
        public void close()
        +
        INTERNAL
        +
        +
        Specified by:
        +
        close in interface Trigger
        +
        +
      • +
      + + + +
        +
      • +

        remove

        +
        public void remove()
        +
        INTERNAL
        +
        +
        Specified by:
        +
        remove in interface Trigger
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/fulltext/FullText.html b/h2/docs/javadoc/org/h2/fulltext/FullText.html new file mode 100644 index 0000000..0832705 --- /dev/null +++ b/h2/docs/javadoc/org/h2/fulltext/FullText.html @@ -0,0 +1,901 @@ + + + + + +FullText + + + + + + + + + + + + +
+
org.h2.fulltext
+

Class FullText

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.fulltext.FullText
    • +
    +
  • +
+
+
    +
  • +
    +
    Direct Known Subclasses:
    +
    FullTextLucene
    +
    +
    +
    +
    public class FullText
    +extends java.lang.Object
    +
    This class implements the native full text search. + Most methods can be called using SQL statements as well.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Nested Class Summary

      + + + + + + + + + + +
      Nested Classes 
      Modifier and TypeClass and Description
      static class FullText.FullTextTrigger +
      Trigger updates the index when a inserting, updating, or deleting a row.
      +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      FullText() 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethod and Description
      protected static voidaddWords(org.h2.fulltext.FullTextSettings setting, + java.util.Set<java.lang.String> set, + java.io.Reader reader) +
      Add all words in the given text to the hash set.
      +
      protected static voidaddWords(org.h2.fulltext.FullTextSettings setting, + java.util.Set<java.lang.String> set, + java.lang.String text) +
      Add all words in the given text to the hash set.
      +
      protected static java.lang.StringasString(java.lang.Object data, + int type) +
      INTERNAL.
      +
      static voidcloseAll() +
      INTERNAL + Close all fulltext settings, freeing up memory.
      +
      static voidcreateIndex(java.sql.Connection conn, + java.lang.String schema, + java.lang.String table, + java.lang.String columnList) +
      Create a new full text index for a table and column list.
      +
      protected static SimpleResultSetcreateResultSet(boolean data) +
      Create an empty search result and initialize the columns.
      +
      static voiddropAll(java.sql.Connection conn) +
      Drops all full text indexes from the database.
      +
      static voiddropIndex(java.sql.Connection conn, + java.lang.String schema, + java.lang.String table) +
      Drop an existing full text index for a table.
      +
      protected static booleanhasChanged(java.lang.Object[] oldRow, + java.lang.Object[] newRow, + int[] indexColumns) +
      Check if a the indexed columns of a row probably have changed.
      +
      static voidinit(java.sql.Connection conn) +
      Initializes full text search functionality for this database.
      +
      protected static java.lang.String[][]parseKey(java.sql.Connection conn, + java.lang.String key) +
      Parse a primary key condition into the primary key columns.
      +
      protected static java.lang.StringquoteSQL(java.lang.Object data, + int type) +
      INTERNAL.
      +
      static voidreindex(java.sql.Connection conn) +
      Re-creates the full text index for this database.
      +
      protected static voidremoveAllTriggers(java.sql.Connection conn, + java.lang.String prefix) +
      Remove all triggers that start with the given prefix.
      +
      static java.sql.ResultSetsearch(java.sql.Connection conn, + java.lang.String text, + int limit, + int offset) +
      Searches from the full text index for this database.
      +
      protected static java.sql.ResultSetsearch(java.sql.Connection conn, + java.lang.String text, + int limit, + int offset, + boolean data) +
      Do the search.
      +
      static java.sql.ResultSetsearchData(java.sql.Connection conn, + java.lang.String text, + int limit, + int offset) +
      Searches from the full text index for this database.
      +
      protected static voidsetColumns(int[] index, + java.util.ArrayList<java.lang.String> keys, + java.util.ArrayList<java.lang.String> columns) +
      Set the column indices of a set of keys.
      +
      static voidsetIgnoreList(java.sql.Connection conn, + java.lang.String commaSeparatedList) +
      Change the ignore list.
      +
      static voidsetWhitespaceChars(java.sql.Connection conn, + java.lang.String whitespaceChars) +
      Change the whitespace characters.
      +
      protected static java.sql.SQLExceptionthrowException(java.lang.String message) +
      Throw a SQLException with the given message.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        FullText

        +
        public FullText()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        init

        +
        public static void init(java.sql.Connection conn)
        +                 throws java.sql.SQLException
        +
        Initializes full text search functionality for this database. This adds + the following Java functions to the database: +
          +
        • FT_CREATE_INDEX(schemaNameString, tableNameString, + columnListString)
        • +
        • FT_SEARCH(queryString, limitInt, offsetInt): result set
        • +
        • FT_REINDEX()
        • +
        • FT_DROP_ALL()
        • +
        + It also adds a schema FT to the database where bookkeeping information + is stored. This function may be called from a Java application, or by + using the SQL statements: + +
        + CREATE ALIAS IF NOT EXISTS FT_INIT FOR
        +      "org.h2.fulltext.FullText.init";
        + CALL FT_INIT();
        + 
        +
        +
        Parameters:
        +
        conn - the connection
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        createIndex

        +
        public static void createIndex(java.sql.Connection conn,
        +                               java.lang.String schema,
        +                               java.lang.String table,
        +                               java.lang.String columnList)
        +                        throws java.sql.SQLException
        +
        Create a new full text index for a table and column list. Each table may + only have one index at any time.
        +
        +
        Parameters:
        +
        conn - the connection
        +
        schema - the schema name of the table (case sensitive)
        +
        table - the table name (case sensitive)
        +
        columnList - the column list (null for all columns)
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        reindex

        +
        public static void reindex(java.sql.Connection conn)
        +                    throws java.sql.SQLException
        +
        Re-creates the full text index for this database. Calling this method is + usually not needed, as the index is kept up-to-date automatically.
        +
        +
        Parameters:
        +
        conn - the connection
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        dropIndex

        +
        public static void dropIndex(java.sql.Connection conn,
        +                             java.lang.String schema,
        +                             java.lang.String table)
        +                      throws java.sql.SQLException
        +
        Drop an existing full text index for a table. This method returns + silently if no index for this table exists.
        +
        +
        Parameters:
        +
        conn - the connection
        +
        schema - the schema name of the table (case sensitive)
        +
        table - the table name (case sensitive)
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        dropAll

        +
        public static void dropAll(java.sql.Connection conn)
        +                    throws java.sql.SQLException
        +
        Drops all full text indexes from the database.
        +
        +
        Parameters:
        +
        conn - the connection
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        search

        +
        public static java.sql.ResultSet search(java.sql.Connection conn,
        +                                        java.lang.String text,
        +                                        int limit,
        +                                        int offset)
        +                                 throws java.sql.SQLException
        +
        Searches from the full text index for this database. + The returned result set has the following column: +
        • QUERY (varchar): the query to use to get the data. + The query does not include 'SELECT * FROM '. Example: + PUBLIC.TEST WHERE ID = 1 +
        • SCORE (float) the relevance score. This value is always 1.0 + for the native fulltext search. +
        +
        +
        Parameters:
        +
        conn - the connection
        +
        text - the search query
        +
        limit - the maximum number of rows or 0 for no limit
        +
        offset - the offset or 0 for no offset
        +
        Returns:
        +
        the result set
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        searchData

        +
        public static java.sql.ResultSet searchData(java.sql.Connection conn,
        +                                            java.lang.String text,
        +                                            int limit,
        +                                            int offset)
        +                                     throws java.sql.SQLException
        +
        Searches from the full text index for this database. The result contains + the primary key data as an array. The returned result set has the + following columns: +
          +
        • SCHEMA (varchar): the schema name. Example: PUBLIC
        • +
        • TABLE (varchar): the table name. Example: TEST
        • +
        • COLUMNS (array of varchar): comma separated list of quoted column + names. The column names are quoted if necessary. Example: (ID)
        • +
        • KEYS (array of values): comma separated list of values. Example: (1) +
        • +
        • SCORE (float) the relevance score. This value is always 1.0 + for the native fulltext search. +
        • +
        +
        +
        Parameters:
        +
        conn - the connection
        +
        text - the search query
        +
        limit - the maximum number of rows or 0 for no limit
        +
        offset - the offset or 0 for no offset
        +
        Returns:
        +
        the result set
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        setIgnoreList

        +
        public static void setIgnoreList(java.sql.Connection conn,
        +                                 java.lang.String commaSeparatedList)
        +                          throws java.sql.SQLException
        +
        Change the ignore list. The ignore list is a comma separated list of + common words that must not be indexed. The default ignore list is empty. + If indexes already exist at the time this list is changed, reindex must + be called.
        +
        +
        Parameters:
        +
        conn - the connection
        +
        commaSeparatedList - the list
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        setWhitespaceChars

        +
        public static void setWhitespaceChars(java.sql.Connection conn,
        +                                      java.lang.String whitespaceChars)
        +                               throws java.sql.SQLException
        +
        Change the whitespace characters. The whitespace characters are used to + separate words. If indexes already exist at the time this list is + changed, reindex must be called.
        +
        +
        Parameters:
        +
        conn - the connection
        +
        whitespaceChars - the list of characters
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        asString

        +
        protected static java.lang.String asString(java.lang.Object data,
        +                                           int type)
        +                                    throws java.sql.SQLException
        +
        INTERNAL. + Convert the object to a string.
        +
        +
        Parameters:
        +
        data - the object
        +
        type - the SQL type
        +
        Returns:
        +
        the string
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        createResultSet

        +
        protected static SimpleResultSet createResultSet(boolean data)
        +
        Create an empty search result and initialize the columns.
        +
        +
        Parameters:
        +
        data - true if the result set should contain the primary key data as + an array.
        +
        Returns:
        +
        the empty result set
        +
        +
      • +
      + + + +
        +
      • +

        parseKey

        +
        protected static java.lang.String[][] parseKey(java.sql.Connection conn,
        +                                               java.lang.String key)
        +
        Parse a primary key condition into the primary key columns.
        +
        +
        Parameters:
        +
        conn - the database connection
        +
        key - the primary key condition as a string
        +
        Returns:
        +
        an array containing the column name list and the data list
        +
        +
      • +
      + + + +
        +
      • +

        quoteSQL

        +
        protected static java.lang.String quoteSQL(java.lang.Object data,
        +                                           int type)
        +                                    throws java.sql.SQLException
        +
        INTERNAL. + Convert an object to a String as used in a SQL statement.
        +
        +
        Parameters:
        +
        data - the object
        +
        type - the SQL type
        +
        Returns:
        +
        the SQL String
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        removeAllTriggers

        +
        protected static void removeAllTriggers(java.sql.Connection conn,
        +                                        java.lang.String prefix)
        +                                 throws java.sql.SQLException
        +
        Remove all triggers that start with the given prefix.
        +
        +
        Parameters:
        +
        conn - the database connection
        +
        prefix - the prefix
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        setColumns

        +
        protected static void setColumns(int[] index,
        +                                 java.util.ArrayList<java.lang.String> keys,
        +                                 java.util.ArrayList<java.lang.String> columns)
        +                          throws java.sql.SQLException
        +
        Set the column indices of a set of keys.
        +
        +
        Parameters:
        +
        index - the column indices (will be modified)
        +
        keys - the key list
        +
        columns - the column list
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        search

        +
        protected static java.sql.ResultSet search(java.sql.Connection conn,
        +                                           java.lang.String text,
        +                                           int limit,
        +                                           int offset,
        +                                           boolean data)
        +                                    throws java.sql.SQLException
        +
        Do the search.
        +
        +
        Parameters:
        +
        conn - the database connection
        +
        text - the query
        +
        limit - the limit
        +
        offset - the offset
        +
        data - whether the raw data should be returned
        +
        Returns:
        +
        the result set
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        addWords

        +
        protected static void addWords(org.h2.fulltext.FullTextSettings setting,
        +                               java.util.Set<java.lang.String> set,
        +                               java.io.Reader reader)
        +
        Add all words in the given text to the hash set.
        +
        +
        Parameters:
        +
        setting - the fulltext settings
        +
        set - the hash set
        +
        reader - the reader
        +
        +
      • +
      + + + +
        +
      • +

        addWords

        +
        protected static void addWords(org.h2.fulltext.FullTextSettings setting,
        +                               java.util.Set<java.lang.String> set,
        +                               java.lang.String text)
        +
        Add all words in the given text to the hash set.
        +
        +
        Parameters:
        +
        setting - the fulltext settings
        +
        set - the hash set
        +
        text - the text
        +
        +
      • +
      + + + +
        +
      • +

        hasChanged

        +
        protected static boolean hasChanged(java.lang.Object[] oldRow,
        +                                    java.lang.Object[] newRow,
        +                                    int[] indexColumns)
        +
        Check if a the indexed columns of a row probably have changed. It may + return true even if the change was minimal (for example from 0.0 to + 0.00).
        +
        +
        Parameters:
        +
        oldRow - the old row
        +
        newRow - the new row
        +
        indexColumns - the indexed columns
        +
        Returns:
        +
        true if the indexed columns don't match
        +
        +
      • +
      + + + +
        +
      • +

        closeAll

        +
        public static void closeAll()
        +
        INTERNAL + Close all fulltext settings, freeing up memory.
        +
      • +
      + + + +
        +
      • +

        throwException

        +
        protected static java.sql.SQLException throwException(java.lang.String message)
        +                                               throws java.sql.SQLException
        +
        Throw a SQLException with the given message.
        +
        +
        Parameters:
        +
        message - the message
        +
        Returns:
        +
        never returns normally
        +
        Throws:
        +
        java.sql.SQLException - the exception
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/fulltext/FullTextLucene.FullTextTrigger.html b/h2/docs/javadoc/org/h2/fulltext/FullTextLucene.FullTextTrigger.html new file mode 100644 index 0000000..0a77afa --- /dev/null +++ b/h2/docs/javadoc/org/h2/fulltext/FullTextLucene.FullTextTrigger.html @@ -0,0 +1,391 @@ + + + + + +FullTextLucene.FullTextTrigger + + + + + + + + + + + + +
+
org.h2.fulltext
+

Class FullTextLucene.FullTextTrigger

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.fulltext.FullTextLucene.FullTextTrigger
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    Trigger
    +
    +
    +
    Enclosing class:
    +
    FullTextLucene
    +
    +
    +
    +
    public static final class FullTextLucene.FullTextTrigger
    +extends java.lang.Object
    +implements Trigger
    +
    Trigger updates the index when a inserting, updating, or deleting a row.
    +
  • +
+
+
+
    +
  • + + + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      FullTextTrigger() 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      voidclose() +
      INTERNAL
      +
      voidfire(java.sql.Connection conn, + java.lang.Object[] oldRow, + java.lang.Object[] newRow) +
      INTERNAL
      +
      voidinit(java.sql.Connection conn, + java.lang.String schemaName, + java.lang.String triggerName, + java.lang.String tableName, + boolean before, + int type) +
      INTERNAL
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      + +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        FullTextTrigger

        +
        public FullTextTrigger()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        init

        +
        public void init(java.sql.Connection conn,
        +                 java.lang.String schemaName,
        +                 java.lang.String triggerName,
        +                 java.lang.String tableName,
        +                 boolean before,
        +                 int type)
        +          throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        init in interface Trigger
        +
        Parameters:
        +
        conn - a connection to the database (a system connection)
        +
        schemaName - the name of the schema
        +
        triggerName - the name of the trigger used in the CREATE TRIGGER + statement
        +
        tableName - the name of the table
        +
        before - whether the fire method is called before or after the + operation is performed
        +
        type - the operation type: INSERT, UPDATE, DELETE, SELECT, or a + combination (this parameter is a bit field)
        +
        Throws:
        +
        java.sql.SQLException - on SQL exception
        +
        See Also:
        +
        Trigger.init(Connection, String, String, String, boolean, int)
        +
        +
      • +
      + + + +
        +
      • +

        fire

        +
        public void fire(java.sql.Connection conn,
        +                 java.lang.Object[] oldRow,
        +                 java.lang.Object[] newRow)
        +          throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        fire in interface Trigger
        +
        Parameters:
        +
        conn - a connection to the database
        +
        oldRow - the old row, or null if no old row is available (for + INSERT)
        +
        newRow - the new row, or null if no new row is available (for + DELETE)
        +
        Throws:
        +
        java.sql.SQLException - if the operation must be undone
        +
        See Also:
        +
        Trigger.fire(Connection, Object[], Object[])
        +
        +
      • +
      + + + +
        +
      • +

        close

        +
        public void close()
        +           throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        close in interface Trigger
        +
        Throws:
        +
        java.sql.SQLException - on SQL exception
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/fulltext/FullTextLucene.html b/h2/docs/javadoc/org/h2/fulltext/FullTextLucene.html new file mode 100644 index 0000000..3b60601 --- /dev/null +++ b/h2/docs/javadoc/org/h2/fulltext/FullTextLucene.html @@ -0,0 +1,699 @@ + + + + + +FullTextLucene + + + + + + + + + + + + +
+
org.h2.fulltext
+

Class FullTextLucene

+
+
+ +
+
    +
  • +
    +
    +
    public class FullTextLucene
    +extends FullText
    +
    This class implements the full text search based on Apache Lucene. + Most methods can be called using SQL statements as well.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Nested Class Summary

      + + + + + + + + + + +
      Nested Classes 
      Modifier and TypeClass and Description
      static class FullTextLucene.FullTextTrigger +
      Trigger updates the index when a inserting, updating, or deleting a row.
      +
      +
    • +
    + +
      +
    • + + +

      Field Summary

      + + + + + + + + + + +
      Fields 
      Modifier and TypeField and Description
      protected static booleanSTORE_DOCUMENT_TEXT_IN_INDEX +
      Whether the text content should be stored in the Lucene index.
      +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      FullTextLucene() 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethod and Description
      protected static java.sql.SQLExceptionconvertException(java.lang.Exception e) +
      Convert an exception to a fulltext exception.
      +
      static voidcreateIndex(java.sql.Connection conn, + java.lang.String schema, + java.lang.String table, + java.lang.String columnList) +
      Create a new full text index for a table and column list.
      +
      static voiddropAll(java.sql.Connection conn) +
      Drops all full text indexes from the database.
      +
      static voiddropIndex(java.sql.Connection conn, + java.lang.String schema, + java.lang.String table) +
      Drop an existing full text index for a table.
      +
      protected static org.h2.fulltext.FullTextLucene.IndexAccessgetIndexAccess(java.sql.Connection conn) +
      Get the index writer/searcher wrapper for the given connection.
      +
      protected static java.lang.StringgetIndexPath(java.sql.Connection conn) +
      Get the path of the Lucene index for this database.
      +
      static voidinit(java.sql.Connection conn) +
      Initializes full text search functionality for this database.
      +
      static voidreindex(java.sql.Connection conn) +
      Re-creates the full text index for this database.
      +
      protected static voidremoveIndexAccess(java.lang.String indexPath) +
      Close the index writer and searcher and remove them from the index access + set.
      +
      static java.sql.ResultSetsearch(java.sql.Connection conn, + java.lang.String text, + int limit, + int offset) +
      Searches from the full text index for this database.
      +
      protected static java.sql.ResultSetsearch(java.sql.Connection conn, + java.lang.String text, + int limit, + int offset, + boolean data) +
      Do the search.
      +
      static java.sql.ResultSetsearchData(java.sql.Connection conn, + java.lang.String text, + int limit, + int offset) +
      Searches from the full text index for this database.
      +
      + +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Detail

      + + + +
        +
      • +

        STORE_DOCUMENT_TEXT_IN_INDEX

        +
        protected static final boolean STORE_DOCUMENT_TEXT_IN_INDEX
        +
        Whether the text content should be stored in the Lucene index.
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        FullTextLucene

        +
        public FullTextLucene()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        init

        +
        public static void init(java.sql.Connection conn)
        +                 throws java.sql.SQLException
        +
        Initializes full text search functionality for this database. This adds + the following Java functions to the database: +
          +
        • FTL_CREATE_INDEX(schemaNameString, tableNameString, + columnListString)
        • +
        • FTL_SEARCH(queryString, limitInt, offsetInt): result set
        • +
        • FTL_REINDEX()
        • +
        • FTL_DROP_ALL()
        • +
        + It also adds a schema FTL to the database where bookkeeping information + is stored. This function may be called from a Java application, or by + using the SQL statements: + +
        + CREATE ALIAS IF NOT EXISTS FTL_INIT FOR
        +      "org.h2.fulltext.FullTextLucene.init";
        + CALL FTL_INIT();
        + 
        +
        +
        Parameters:
        +
        conn - the connection
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        createIndex

        +
        public static void createIndex(java.sql.Connection conn,
        +                               java.lang.String schema,
        +                               java.lang.String table,
        +                               java.lang.String columnList)
        +                        throws java.sql.SQLException
        +
        Create a new full text index for a table and column list. Each table may + only have one index at any time.
        +
        +
        Parameters:
        +
        conn - the connection
        +
        schema - the schema name of the table (case sensitive)
        +
        table - the table name (case sensitive)
        +
        columnList - the column list (null for all columns)
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        dropIndex

        +
        public static void dropIndex(java.sql.Connection conn,
        +                             java.lang.String schema,
        +                             java.lang.String table)
        +                      throws java.sql.SQLException
        +
        Drop an existing full text index for a table. This method returns + silently if no index for this table exists.
        +
        +
        Parameters:
        +
        conn - the connection
        +
        schema - the schema name of the table (case sensitive)
        +
        table - the table name (case sensitive)
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        reindex

        +
        public static void reindex(java.sql.Connection conn)
        +                    throws java.sql.SQLException
        +
        Re-creates the full text index for this database. Calling this method is + usually not needed, as the index is kept up-to-date automatically.
        +
        +
        Parameters:
        +
        conn - the connection
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        dropAll

        +
        public static void dropAll(java.sql.Connection conn)
        +                    throws java.sql.SQLException
        +
        Drops all full text indexes from the database.
        +
        +
        Parameters:
        +
        conn - the connection
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        search

        +
        public static java.sql.ResultSet search(java.sql.Connection conn,
        +                                        java.lang.String text,
        +                                        int limit,
        +                                        int offset)
        +                                 throws java.sql.SQLException
        +
        Searches from the full text index for this database. + The returned result set has the following column: +
        • QUERY (varchar): the query to use to get the data. + The query does not include 'SELECT * FROM '. Example: + PUBLIC.TEST WHERE ID = 1 +
        • SCORE (float) the relevance score as returned by Lucene. +
        +
        +
        Parameters:
        +
        conn - the connection
        +
        text - the search query
        +
        limit - the maximum number of rows or 0 for no limit
        +
        offset - the offset or 0 for no offset
        +
        Returns:
        +
        the result set
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        searchData

        +
        public static java.sql.ResultSet searchData(java.sql.Connection conn,
        +                                            java.lang.String text,
        +                                            int limit,
        +                                            int offset)
        +                                     throws java.sql.SQLException
        +
        Searches from the full text index for this database. The result contains + the primary key data as an array. The returned result set has the + following columns: +
          +
        • SCHEMA (varchar): the schema name. Example: PUBLIC
        • +
        • TABLE (varchar): the table name. Example: TEST
        • +
        • COLUMNS (array of varchar): comma separated list of quoted column + names. The column names are quoted if necessary. Example: (ID)
        • +
        • KEYS (array of values): comma separated list of values. + Example: (1)
        • +
        • SCORE (float) the relevance score as returned by Lucene.
        • +
        +
        +
        Parameters:
        +
        conn - the connection
        +
        text - the search query
        +
        limit - the maximum number of rows or 0 for no limit
        +
        offset - the offset or 0 for no offset
        +
        Returns:
        +
        the result set
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        convertException

        +
        protected static java.sql.SQLException convertException(java.lang.Exception e)
        +
        Convert an exception to a fulltext exception.
        +
        +
        Parameters:
        +
        e - the original exception
        +
        Returns:
        +
        the converted SQL exception
        +
        +
      • +
      + + + +
        +
      • +

        getIndexAccess

        +
        protected static org.h2.fulltext.FullTextLucene.IndexAccess getIndexAccess(java.sql.Connection conn)
        +                                                                    throws java.sql.SQLException
        +
        Get the index writer/searcher wrapper for the given connection.
        +
        +
        Parameters:
        +
        conn - the connection
        +
        Returns:
        +
        the index access wrapper
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        getIndexPath

        +
        protected static java.lang.String getIndexPath(java.sql.Connection conn)
        +                                        throws java.sql.SQLException
        +
        Get the path of the Lucene index for this database.
        +
        +
        Parameters:
        +
        conn - the database connection
        +
        Returns:
        +
        the path
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        removeIndexAccess

        +
        protected static void removeIndexAccess(java.lang.String indexPath)
        +                                 throws java.sql.SQLException
        +
        Close the index writer and searcher and remove them from the index access + set.
        +
        +
        Parameters:
        +
        indexPath - the index path
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        search

        +
        protected static java.sql.ResultSet search(java.sql.Connection conn,
        +                                           java.lang.String text,
        +                                           int limit,
        +                                           int offset,
        +                                           boolean data)
        +                                    throws java.sql.SQLException
        +
        Do the search.
        +
        +
        Parameters:
        +
        conn - the database connection
        +
        text - the query
        +
        limit - the limit
        +
        offset - the offset
        +
        data - whether the raw data should be returned
        +
        Returns:
        +
        the result set
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/fulltext/IndexInfo.html b/h2/docs/javadoc/org/h2/fulltext/IndexInfo.html new file mode 100644 index 0000000..558670b --- /dev/null +++ b/h2/docs/javadoc/org/h2/fulltext/IndexInfo.html @@ -0,0 +1,355 @@ + + + + + +IndexInfo + + + + + + + + + + + + +
+
org.h2.fulltext
+

Class IndexInfo

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.fulltext.IndexInfo
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public class IndexInfo
    +extends java.lang.Object
    +
    The settings of one full text search index.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Fields 
      Modifier and TypeField and Description
      protected java.lang.String[]columns +
      The column names.
      +
      protected intid +
      The index id.
      +
      protected int[]indexColumns +
      The column indexes of the index columns.
      +
      protected int[]keys +
      The column indexes of the key columns.
      +
      protected java.lang.Stringschema +
      The schema name.
      +
      protected java.lang.Stringtable +
      The table name.
      +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      IndexInfo() 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Detail

      + + + +
        +
      • +

        id

        +
        protected int id
        +
        The index id.
        +
      • +
      + + + +
        +
      • +

        schema

        +
        protected java.lang.String schema
        +
        The schema name.
        +
      • +
      + + + +
        +
      • +

        table

        +
        protected java.lang.String table
        +
        The table name.
        +
      • +
      + + + +
        +
      • +

        keys

        +
        protected int[] keys
        +
        The column indexes of the key columns.
        +
      • +
      + + + +
        +
      • +

        indexColumns

        +
        protected int[] indexColumns
        +
        The column indexes of the index columns.
        +
      • +
      + + + +
        +
      • +

        columns

        +
        protected java.lang.String[] columns
        +
        The column names.
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        IndexInfo

        +
        public IndexInfo()
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/fulltext/package-frame.html b/h2/docs/javadoc/org/h2/fulltext/package-frame.html new file mode 100644 index 0000000..d39c93e --- /dev/null +++ b/h2/docs/javadoc/org/h2/fulltext/package-frame.html @@ -0,0 +1,24 @@ + + + + + +org.h2.fulltext + + + + + +

org.h2.fulltext

+ + + diff --git a/h2/docs/javadoc/org/h2/fulltext/package-summary.html b/h2/docs/javadoc/org/h2/fulltext/package-summary.html new file mode 100644 index 0000000..3dc190a --- /dev/null +++ b/h2/docs/javadoc/org/h2/fulltext/package-summary.html @@ -0,0 +1,181 @@ + + + + + +org.h2.fulltext + + + + + + + + + + + +
+

Package org.h2.fulltext

+
+
+ +The native full text search implementation, and the wrapper for the Lucene full text search implementation.
+
+

See: Description

+
+
+
    +
  • + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Class Summary 
    ClassDescription
    FullText +
    This class implements the native full text search.
    +
    FullText.FullTextTrigger +
    Trigger updates the index when a inserting, updating, or deleting a row.
    +
    FullTextLucene +
    This class implements the full text search based on Apache Lucene.
    +
    FullTextLucene.FullTextTrigger +
    Trigger updates the index when a inserting, updating, or deleting a row.
    +
    IndexInfo +
    The settings of one full text search index.
    +
    +
  • +
+ + + +

Package org.h2.fulltext Description

+

+ +The native full text search implementation, and the wrapper for the Lucene full text search implementation. + +

+
+ + + + + + diff --git a/h2/docs/javadoc/org/h2/fulltext/package-tree.html b/h2/docs/javadoc/org/h2/fulltext/package-tree.html new file mode 100644 index 0000000..588f0cd --- /dev/null +++ b/h2/docs/javadoc/org/h2/fulltext/package-tree.html @@ -0,0 +1,142 @@ + + + + + +org.h2.fulltext Class Hierarchy + + + + + + + + + + + +
+

Hierarchy For Package org.h2.fulltext

+Package Hierarchies: + +
+
+

Class Hierarchy

+ +
+ + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcArray.html b/h2/docs/javadoc/org/h2/jdbc/JdbcArray.html new file mode 100644 index 0000000..112263b --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcArray.html @@ -0,0 +1,639 @@ + + + + + +JdbcArray + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcArray

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.message.TraceObject
    • +
    • +
        +
      • org.h2.jdbc.JdbcArray
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.sql.Array
    +
    +
    +
    +
    public final class JdbcArray
    +extends org.h2.message.TraceObject
    +implements java.sql.Array
    +
    Represents an ARRAY value.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.message.TraceObject

        +ARRAY, BLOB, CALLABLE_STATEMENT, CLOB, CONNECTION, DATA_SOURCE, DATABASE_META_DATA, PARAMETER_META_DATA, PREPARED_STATEMENT, RESULT_SET, RESULT_SET_META_DATA, SAVEPOINT, SQLXML, STATEMENT, trace, XA_DATA_SOURCE, XID
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      JdbcArray(JdbcConnection conn, + org.h2.value.Value value, + int id) +
      INTERNAL
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      voidfree() +
      Release all resources of this object.
      +
      java.lang.ObjectgetArray() +
      Returns the value as a Java array.
      +
      java.lang.ObjectgetArray(long index, + int count) +
      Returns the value as a Java array.
      +
      java.lang.ObjectgetArray(long index, + int count, + java.util.Map<java.lang.String,java.lang.Class<?>> map) +
      Returns the value as a Java array.
      +
      java.lang.ObjectgetArray(java.util.Map<java.lang.String,java.lang.Class<?>> map) +
      Returns the value as a Java array.
      +
      intgetBaseType() +
      Returns the base type of the array.
      +
      java.lang.StringgetBaseTypeName() +
      Returns the base type name of the array.
      +
      java.sql.ResultSetgetResultSet() +
      Returns the value as a result set.
      +
      java.sql.ResultSetgetResultSet(long index, + int count) +
      Returns the value as a result set.
      +
      java.sql.ResultSetgetResultSet(long index, + int count, + java.util.Map<java.lang.String,java.lang.Class<?>> map) +
      Returns the value as a result set.
      +
      java.sql.ResultSetgetResultSet(java.util.Map<java.lang.String,java.lang.Class<?>> map) +
      Returns the value as a result set.
      +
      java.lang.StringtoString() +
      INTERNAL
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.message.TraceObject

        +debugCode, debugCodeAssign, debugCodeCall, debugCodeCall, debugCodeCall, getNextId, getTraceId, getTraceObjectName, isDebugEnabled, isInfoEnabled, logAndConvert, quote, quoteArray, quoteBigDecimal, quoteBytes, quoteDate, quoteIntArray, quoteMap, quoteTime, quoteTimestamp, setTrace, unsupported
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        JdbcArray

        +
        public JdbcArray(JdbcConnection conn,
        +                 org.h2.value.Value value,
        +                 int id)
        +
        INTERNAL
        +
        +
        Parameters:
        +
        conn - it belongs to
        +
        value - of
        +
        id - of the trace object
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getArray

        +
        public java.lang.Object getArray()
        +                          throws java.sql.SQLException
        +
        Returns the value as a Java array. + This method always returns an Object[].
        +
        +
        Specified by:
        +
        getArray in interface java.sql.Array
        +
        Returns:
        +
        the Object array
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getArray

        +
        public java.lang.Object getArray(java.util.Map<java.lang.String,java.lang.Class<?>> map)
        +                          throws java.sql.SQLException
        +
        Returns the value as a Java array. + This method always returns an Object[].
        +
        +
        Specified by:
        +
        getArray in interface java.sql.Array
        +
        Parameters:
        +
        map - is ignored. Only empty or null maps are supported
        +
        Returns:
        +
        the Object array
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getArray

        +
        public java.lang.Object getArray(long index,
        +                                 int count)
        +                          throws java.sql.SQLException
        +
        Returns the value as a Java array. A subset of the array is returned, + starting from the index (1 meaning the first element) and up to the given + object count. This method always returns an Object[].
        +
        +
        Specified by:
        +
        getArray in interface java.sql.Array
        +
        Parameters:
        +
        index - the start index of the subset (starting with 1)
        +
        count - the maximum number of values
        +
        Returns:
        +
        the Object array
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getArray

        +
        public java.lang.Object getArray(long index,
        +                                 int count,
        +                                 java.util.Map<java.lang.String,java.lang.Class<?>> map)
        +                          throws java.sql.SQLException
        +
        Returns the value as a Java array. A subset of the array is returned, + starting from the index (1 meaning the first element) and up to the given + object count. This method always returns an Object[].
        +
        +
        Specified by:
        +
        getArray in interface java.sql.Array
        +
        Parameters:
        +
        index - the start index of the subset (starting with 1)
        +
        count - the maximum number of values
        +
        map - is ignored. Only empty or null maps are supported
        +
        Returns:
        +
        the Object array
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getBaseType

        +
        public int getBaseType()
        +                throws java.sql.SQLException
        +
        Returns the base type of the array.
        +
        +
        Specified by:
        +
        getBaseType in interface java.sql.Array
        +
        Returns:
        +
        the base type or Types.NULL
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getBaseTypeName

        +
        public java.lang.String getBaseTypeName()
        +                                 throws java.sql.SQLException
        +
        Returns the base type name of the array. This database does support mixed + type arrays and therefore there is no base type.
        +
        +
        Specified by:
        +
        getBaseTypeName in interface java.sql.Array
        +
        Returns:
        +
        the base type name or "NULL"
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getResultSet

        +
        public java.sql.ResultSet getResultSet()
        +                                throws java.sql.SQLException
        +
        Returns the value as a result set. + The first column contains the index + (starting with 1) and the second column the value.
        +
        +
        Specified by:
        +
        getResultSet in interface java.sql.Array
        +
        Returns:
        +
        the result set
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getResultSet

        +
        public java.sql.ResultSet getResultSet(java.util.Map<java.lang.String,java.lang.Class<?>> map)
        +                                throws java.sql.SQLException
        +
        Returns the value as a result set. The first column contains the index + (starting with 1) and the second column the value.
        +
        +
        Specified by:
        +
        getResultSet in interface java.sql.Array
        +
        Parameters:
        +
        map - is ignored. Only empty or null maps are supported
        +
        Returns:
        +
        the result set
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getResultSet

        +
        public java.sql.ResultSet getResultSet(long index,
        +                                       int count)
        +                                throws java.sql.SQLException
        +
        Returns the value as a result set. The first column contains the index + (starting with 1) and the second column the value. A subset of the array + is returned, starting from the index (1 meaning the first element) and + up to the given object count.
        +
        +
        Specified by:
        +
        getResultSet in interface java.sql.Array
        +
        Parameters:
        +
        index - the start index of the subset (starting with 1)
        +
        count - the maximum number of values
        +
        Returns:
        +
        the result set
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getResultSet

        +
        public java.sql.ResultSet getResultSet(long index,
        +                                       int count,
        +                                       java.util.Map<java.lang.String,java.lang.Class<?>> map)
        +                                throws java.sql.SQLException
        +
        Returns the value as a result set. + The first column contains the index + (starting with 1) and the second column the value. + A subset of the array is returned, starting from the index + (1 meaning the first element) and up to the given object count.
        +
        +
        Specified by:
        +
        getResultSet in interface java.sql.Array
        +
        Parameters:
        +
        index - the start index of the subset (starting with 1)
        +
        count - the maximum number of values
        +
        map - is ignored. Only empty or null maps are supported
        +
        Returns:
        +
        the result set
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        free

        +
        public void free()
        +
        Release all resources of this object.
        +
        +
        Specified by:
        +
        free in interface java.sql.Array
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        INTERNAL
        +
        +
        Overrides:
        +
        toString in class java.lang.Object
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcBatchUpdateException.html b/h2/docs/javadoc/org/h2/jdbc/JdbcBatchUpdateException.html new file mode 100644 index 0000000..16f05d7 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcBatchUpdateException.html @@ -0,0 +1,339 @@ + + + + + +JdbcBatchUpdateException + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcBatchUpdateException

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • java.lang.Throwable
    • +
    • +
        +
      • java.lang.Exception
      • +
      • +
          +
        • java.sql.SQLException
        • +
        • +
            +
          • java.sql.BatchUpdateException
          • +
          • +
              +
            • org.h2.jdbc.JdbcBatchUpdateException
            • +
            +
          • +
          +
        • +
        +
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.lang.Iterable<java.lang.Throwable>
    +
    +
    +
    +
    public final class JdbcBatchUpdateException
    +extends java.sql.BatchUpdateException
    +
    Represents a batch update database exception.
    +
    +
    See Also:
    +
    Serialized Form
    +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      voidprintStackTrace() +
      INTERNAL
      +
      voidprintStackTrace(java.io.PrintStream s) +
      INTERNAL
      +
      voidprintStackTrace(java.io.PrintWriter s) +
      INTERNAL
      +
      +
        +
      • + + +

        Methods inherited from class java.sql.BatchUpdateException

        +getLargeUpdateCounts, getUpdateCounts
      • +
      +
        +
      • + + +

        Methods inherited from class java.sql.SQLException

        +getErrorCode, getNextException, getSQLState, iterator, setNextException
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, getSuppressed, initCause, setStackTrace, toString
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
        +
      • + + +

        Methods inherited from interface java.lang.Iterable

        +forEach, spliterator
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace()
        +
        INTERNAL
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintWriter s)
        +
        INTERNAL
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintStream s)
        +
        INTERNAL
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcBlob.html b/h2/docs/javadoc/org/h2/jdbc/JdbcBlob.html new file mode 100644 index 0000000..092511f --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcBlob.html @@ -0,0 +1,634 @@ + + + + + +JdbcBlob + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcBlob

+
+
+
    +
  • java.lang.Object
  • +
  • + +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.sql.Blob
    +
    +
    +
    +
    public final class JdbcBlob
    +extends JdbcLob
    +implements java.sql.Blob
    +
    Represents a BLOB value.
    +
  • +
+
+
+
    +
  • + + + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.message.TraceObject

        +ARRAY, BLOB, CALLABLE_STATEMENT, CLOB, CONNECTION, DATA_SOURCE, DATABASE_META_DATA, PARAMETER_META_DATA, PREPARED_STATEMENT, RESULT_SET, RESULT_SET_META_DATA, SAVEPOINT, SQLXML, STATEMENT, trace, XA_DATA_SOURCE, XID
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      JdbcBlob(JdbcConnection conn, + org.h2.value.Value value, + JdbcLob.State state, + int id) +
      INTERNAL
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      java.io.InputStreamgetBinaryStream() +
      Returns the input stream.
      +
      java.io.InputStreamgetBinaryStream(long pos, + long length) +
      Returns the input stream, starting from an offset.
      +
      byte[]getBytes(long pos, + int length) +
      Returns some bytes of the object.
      +
      longlength() +
      Returns the length.
      +
      longposition(java.sql.Blob blobPattern, + long start) +
      [Not supported] Searches a pattern and return the position.
      +
      longposition(byte[] pattern, + long start) +
      [Not supported] Searches a pattern and return the position.
      +
      java.io.OutputStreamsetBinaryStream(long pos) +
      Get a writer to update the Blob.
      +
      intsetBytes(long pos, + byte[] bytes) +
      Fills the Blob.
      +
      intsetBytes(long pos, + byte[] bytes, + int offset, + int len) +
      Sets some bytes of the object.
      +
      voidtruncate(long len) +
      [Not supported] Truncates the object.
      +
      + +
        +
      • + + +

        Methods inherited from class org.h2.message.TraceObject

        +debugCode, debugCodeAssign, debugCodeCall, debugCodeCall, debugCodeCall, getNextId, getTraceId, getTraceObjectName, isDebugEnabled, isInfoEnabled, logAndConvert, quote, quoteArray, quoteBigDecimal, quoteBytes, quoteDate, quoteIntArray, quoteMap, quoteTime, quoteTimestamp, setTrace, unsupported
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
        +
      • + + +

        Methods inherited from interface java.sql.Blob

        +free
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        JdbcBlob

        +
        public JdbcBlob(JdbcConnection conn,
        +                org.h2.value.Value value,
        +                JdbcLob.State state,
        +                int id)
        +
        INTERNAL
        +
        +
        Parameters:
        +
        conn - it belongs to
        +
        value - of
        +
        state - of the LOB
        +
        id - of the trace object
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        length

        +
        public long length()
        +            throws java.sql.SQLException
        +
        Returns the length.
        +
        +
        Specified by:
        +
        length in interface java.sql.Blob
        +
        Returns:
        +
        the length
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        truncate

        +
        public void truncate(long len)
        +              throws java.sql.SQLException
        +
        [Not supported] Truncates the object.
        +
        +
        Specified by:
        +
        truncate in interface java.sql.Blob
        +
        Parameters:
        +
        len - the new length
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getBytes

        +
        public byte[] getBytes(long pos,
        +                       int length)
        +                throws java.sql.SQLException
        +
        Returns some bytes of the object.
        +
        +
        Specified by:
        +
        getBytes in interface java.sql.Blob
        +
        Parameters:
        +
        pos - the index, the first byte is at position 1
        +
        length - the number of bytes
        +
        Returns:
        +
        the bytes, at most length bytes
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        setBytes

        +
        public int setBytes(long pos,
        +                    byte[] bytes)
        +             throws java.sql.SQLException
        +
        Fills the Blob. This is only supported for new, empty Blob objects that + were created with Connection.createBlob(). The position + must be 1, meaning the whole Blob data is set.
        +
        +
        Specified by:
        +
        setBytes in interface java.sql.Blob
        +
        Parameters:
        +
        pos - where to start writing (the first byte is at position 1)
        +
        bytes - the bytes to set
        +
        Returns:
        +
        the length of the added data
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        setBytes

        +
        public int setBytes(long pos,
        +                    byte[] bytes,
        +                    int offset,
        +                    int len)
        +             throws java.sql.SQLException
        +
        Sets some bytes of the object.
        +
        +
        Specified by:
        +
        setBytes in interface java.sql.Blob
        +
        Parameters:
        +
        pos - the write position
        +
        bytes - the bytes to set
        +
        offset - the bytes offset
        +
        len - the number of bytes to write
        +
        Returns:
        +
        how many bytes have been written
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getBinaryStream

        +
        public java.io.InputStream getBinaryStream()
        +                                    throws java.sql.SQLException
        +
        Description copied from class: JdbcLob
        +
        Returns the input stream.
        +
        +
        Specified by:
        +
        getBinaryStream in interface java.sql.Blob
        +
        Returns:
        +
        the input stream
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        setBinaryStream

        +
        public java.io.OutputStream setBinaryStream(long pos)
        +                                     throws java.sql.SQLException
        +
        Get a writer to update the Blob. This is only supported for new, empty + Blob objects that were created with Connection.createBlob(). The Blob is + created in a separate thread, and the object is only updated when + OutputStream.close() is called. The position must be 1, meaning the whole + Blob data is set.
        +
        +
        Specified by:
        +
        setBinaryStream in interface java.sql.Blob
        +
        Parameters:
        +
        pos - where to start writing (the first byte is at position 1)
        +
        Returns:
        +
        an output stream
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        position

        +
        public long position(byte[] pattern,
        +                     long start)
        +              throws java.sql.SQLException
        +
        [Not supported] Searches a pattern and return the position.
        +
        +
        Specified by:
        +
        position in interface java.sql.Blob
        +
        Parameters:
        +
        pattern - the pattern to search
        +
        start - the index, the first byte is at position 1
        +
        Returns:
        +
        the position (first byte is at position 1), or -1 for not found
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        position

        +
        public long position(java.sql.Blob blobPattern,
        +                     long start)
        +              throws java.sql.SQLException
        +
        [Not supported] Searches a pattern and return the position.
        +
        +
        Specified by:
        +
        position in interface java.sql.Blob
        +
        Parameters:
        +
        blobPattern - the pattern to search
        +
        start - the index, the first byte is at position 1
        +
        Returns:
        +
        the position (first byte is at position 1), or -1 for not found
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getBinaryStream

        +
        public java.io.InputStream getBinaryStream(long pos,
        +                                           long length)
        +                                    throws java.sql.SQLException
        +
        Returns the input stream, starting from an offset.
        +
        +
        Specified by:
        +
        getBinaryStream in interface java.sql.Blob
        +
        Parameters:
        +
        pos - where to start reading
        +
        length - the number of bytes that will be read
        +
        Returns:
        +
        the input stream to read
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcCallableStatement.html b/h2/docs/javadoc/org/h2/jdbc/JdbcCallableStatement.html new file mode 100644 index 0000000..ba0316a --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcCallableStatement.html @@ -0,0 +1,3890 @@ + + + + + +JdbcCallableStatement + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcCallableStatement

+
+
+ +
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.lang.AutoCloseable, java.sql.CallableStatement, java.sql.PreparedStatement, java.sql.Statement, java.sql.Wrapper, JdbcStatementBackwardsCompat
    +
    +
    +
    +
    public final class JdbcCallableStatement
    +extends JdbcPreparedStatement
    +implements java.sql.CallableStatement
    +
    Represents a callable statement. +

    + Thread safety: the callable statement is not thread-safe. If the same + callable statement is used by multiple threads access to it must be + synchronized. The single synchronized block must include assignment of + parameters, execution of the command and all operations with its result. +

    +
    + synchronized (call) {
    +     call.setInt(1, 10);
    +     try (ResultSet rs = call.executeQuery()) {
    +         while (rs.next) {
    +             // Do something
    +         }
    +     }
    + }
    + synchronized (call) {
    +     call.setInt(1, 15);
    +     updateCount = call.executeUpdate();
    + }
    + 
    +
  • +
+
+
+
    +
  • + + + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods Deprecated Methods 
      Modifier and TypeMethod and Description
      longexecuteLargeUpdate() +
      Executes a statement (insert, update, delete, create, drop) + and returns the update count.
      +
      intexecuteUpdate() +
      Executes a statement (insert, update, delete, create, drop) + and returns the update count.
      +
      java.sql.ArraygetArray(int parameterIndex) +
      Returns the value of the specified column as an Array.
      +
      java.sql.ArraygetArray(java.lang.String parameterName) +
      Returns the value of the specified column as an Array.
      +
      java.math.BigDecimalgetBigDecimal(int parameterIndex) +
      Returns the value of the specified column as a BigDecimal.
      +
      java.math.BigDecimalgetBigDecimal(int parameterIndex, + int scale) +
      Deprecated.  + +
      +
      java.math.BigDecimalgetBigDecimal(java.lang.String parameterName) +
      Returns the value of the specified column as a BigDecimal.
      +
      java.sql.BlobgetBlob(int parameterIndex) +
      Returns the value of the specified column as a Blob.
      +
      java.sql.BlobgetBlob(java.lang.String parameterName) +
      Returns the value of the specified column as a Blob.
      +
      booleangetBoolean(int parameterIndex) +
      Returns the value of the specified column as a boolean.
      +
      booleangetBoolean(java.lang.String parameterName) +
      Returns the value of the specified column as a boolean.
      +
      bytegetByte(int parameterIndex) +
      Returns the value of the specified column as a byte.
      +
      bytegetByte(java.lang.String parameterName) +
      Returns the value of the specified column as a byte.
      +
      byte[]getBytes(int parameterIndex) +
      Returns the value of the specified column as a byte array.
      +
      byte[]getBytes(java.lang.String parameterName) +
      Returns the value of the specified column as a byte array.
      +
      java.io.ReadergetCharacterStream(int parameterIndex) +
      Returns the value of the specified column as a reader.
      +
      java.io.ReadergetCharacterStream(java.lang.String parameterName) +
      Returns the value of the specified column as a reader.
      +
      java.sql.ClobgetClob(int parameterIndex) +
      Returns the value of the specified column as a Clob.
      +
      java.sql.ClobgetClob(java.lang.String parameterName) +
      Returns the value of the specified column as a Clob.
      +
      java.sql.DategetDate(int parameterIndex) +
      Returns the value of the specified column as a java.sql.Date.
      +
      java.sql.DategetDate(int parameterIndex, + java.util.Calendar cal) +
      Returns the value of the specified column as a java.sql.Date using a + specified time zone.
      +
      java.sql.DategetDate(java.lang.String parameterName) +
      Returns the value of the specified column as a java.sql.Date.
      +
      java.sql.DategetDate(java.lang.String parameterName, + java.util.Calendar cal) +
      Returns the value of the specified column as a java.sql.Date using a + specified time zone.
      +
      doublegetDouble(int parameterIndex) +
      Returns the value of the specified column as a double.
      +
      doublegetDouble(java.lang.String parameterName) +
      Returns the value of the specified column as a double.
      +
      floatgetFloat(int parameterIndex) +
      Returns the value of the specified column as a float.
      +
      floatgetFloat(java.lang.String parameterName) +
      Returns the value of the specified column as a float.
      +
      intgetInt(int parameterIndex) +
      Returns the value of the specified column as an int.
      +
      intgetInt(java.lang.String parameterName) +
      Returns the value of the specified column as an int.
      +
      longgetLong(int parameterIndex) +
      Returns the value of the specified column as a long.
      +
      longgetLong(java.lang.String parameterName) +
      Returns the value of the specified column as a long.
      +
      java.io.ReadergetNCharacterStream(int parameterIndex) +
      Returns the value of the specified column as a reader.
      +
      java.io.ReadergetNCharacterStream(java.lang.String parameterName) +
      Returns the value of the specified column as a reader.
      +
      java.sql.NClobgetNClob(int parameterIndex) +
      Returns the value of the specified column as a Clob.
      +
      java.sql.NClobgetNClob(java.lang.String parameterName) +
      Returns the value of the specified column as a Clob.
      +
      java.lang.StringgetNString(int parameterIndex) +
      Returns the value of the specified column as a String.
      +
      java.lang.StringgetNString(java.lang.String parameterName) +
      Returns the value of the specified column as a String.
      +
      java.lang.ObjectgetObject(int parameterIndex) +
      Returns a column value as a Java object.
      +
      <T> TgetObject(int parameterIndex, + java.lang.Class<T> type) +
      Returns the value of the specified column as a Java object of the + specified type.
      +
      java.lang.ObjectgetObject(int parameterIndex, + java.util.Map<java.lang.String,java.lang.Class<?>> map) +
      [Not supported] Gets a column as a object using the specified type + mapping.
      +
      java.lang.ObjectgetObject(java.lang.String parameterName) +
      Returns a column value as a Java object.
      +
      <T> TgetObject(java.lang.String parameterName, + java.lang.Class<T> type) +
      Returns the value of the specified column as a Java object of the + specified type.
      +
      java.lang.ObjectgetObject(java.lang.String parameterName, + java.util.Map<java.lang.String,java.lang.Class<?>> map) +
      [Not supported] Gets a column as a object using the specified type + mapping.
      +
      java.sql.RefgetRef(int parameterIndex) +
      [Not supported] Gets a column as a reference.
      +
      java.sql.RefgetRef(java.lang.String parameterName) +
      [Not supported] Gets a column as a reference.
      +
      java.sql.RowIdgetRowId(int parameterIndex) +
      [Not supported] Returns the value of the specified column as a row id.
      +
      java.sql.RowIdgetRowId(java.lang.String parameterName) +
      [Not supported] Returns the value of the specified column as a row id.
      +
      shortgetShort(int parameterIndex) +
      Returns the value of the specified column as a short.
      +
      shortgetShort(java.lang.String parameterName) +
      Returns the value of the specified column as a short.
      +
      java.sql.SQLXMLgetSQLXML(int parameterIndex) +
      Returns the value of the specified column as a SQLXML object.
      +
      java.sql.SQLXMLgetSQLXML(java.lang.String parameterName) +
      Returns the value of the specified column as a SQLXML object.
      +
      java.lang.StringgetString(int parameterIndex) +
      Returns the value of the specified column as a String.
      +
      java.lang.StringgetString(java.lang.String parameterName) +
      Returns the value of the specified column as a String.
      +
      java.sql.TimegetTime(int parameterIndex) +
      Returns the value of the specified column as a java.sql.Time.
      +
      java.sql.TimegetTime(int parameterIndex, + java.util.Calendar cal) +
      Returns the value of the specified column as a java.sql.Time using a + specified time zone.
      +
      java.sql.TimegetTime(java.lang.String parameterName) +
      Returns the value of the specified column as a java.sql.Time.
      +
      java.sql.TimegetTime(java.lang.String parameterName, + java.util.Calendar cal) +
      Returns the value of the specified column as a java.sql.Time using a + specified time zone.
      +
      java.sql.TimestampgetTimestamp(int parameterIndex) +
      Returns the value of the specified column as a java.sql.Timestamp.
      +
      java.sql.TimestampgetTimestamp(int parameterIndex, + java.util.Calendar cal) +
      Returns the value of the specified column as a java.sql.Timestamp using a + specified time zone.
      +
      java.sql.TimestampgetTimestamp(java.lang.String parameterName) +
      Returns the value of the specified column as a java.sql.Timestamp.
      +
      java.sql.TimestampgetTimestamp(java.lang.String parameterName, + java.util.Calendar cal) +
      Returns the value of the specified column as a java.sql.Timestamp using a + specified time zone.
      +
      java.net.URLgetURL(int parameterIndex) +
      [Not supported]
      +
      java.net.URLgetURL(java.lang.String parameterName) +
      [Not supported]
      +
      voidregisterOutParameter(int parameterIndex, + int sqlType) +
      Registers the given OUT parameter.
      +
      voidregisterOutParameter(int parameterIndex, + int sqlType, + int scale) +
      Registers the given OUT parameter.
      +
      voidregisterOutParameter(int parameterIndex, + int sqlType, + java.lang.String typeName) +
      Registers the given OUT parameter.
      +
      voidregisterOutParameter(java.lang.String parameterName, + int sqlType) +
      Registers the given OUT parameter.
      +
      voidregisterOutParameter(java.lang.String parameterName, + int sqlType, + int scale) +
      Registers the given OUT parameter.
      +
      voidregisterOutParameter(java.lang.String parameterName, + int sqlType, + java.lang.String typeName) +
      Registers the given OUT parameter.
      +
      voidsetAsciiStream(java.lang.String parameterName, + java.io.InputStream x) +
      Sets the value of a parameter as an ASCII stream.
      +
      voidsetAsciiStream(java.lang.String parameterName, + java.io.InputStream x, + int length) +
      Sets the value of a parameter as an ASCII stream.
      +
      voidsetAsciiStream(java.lang.String parameterName, + java.io.InputStream x, + long length) +
      Sets the value of a parameter as an ASCII stream.
      +
      voidsetBigDecimal(java.lang.String parameterName, + java.math.BigDecimal x) +
      Sets the value of a parameter.
      +
      voidsetBinaryStream(java.lang.String parameterName, + java.io.InputStream x) +
      Sets the value of a parameter as an input stream.
      +
      voidsetBinaryStream(java.lang.String parameterName, + java.io.InputStream x, + int length) +
      Sets the value of a parameter as an input stream.
      +
      voidsetBinaryStream(java.lang.String parameterName, + java.io.InputStream x, + long length) +
      Sets the value of a parameter as an input stream.
      +
      voidsetBlob(java.lang.String parameterName, + java.sql.Blob x) +
      Sets the value of a parameter as a Blob.
      +
      voidsetBlob(java.lang.String parameterName, + java.io.InputStream x) +
      Sets the value of a parameter as a Blob.
      +
      voidsetBlob(java.lang.String parameterName, + java.io.InputStream x, + long length) +
      Sets the value of a parameter as a Blob.
      +
      voidsetBoolean(java.lang.String parameterName, + boolean x) +
      Sets the value of a parameter.
      +
      voidsetByte(java.lang.String parameterName, + byte x) +
      Sets the value of a parameter.
      +
      voidsetBytes(java.lang.String parameterName, + byte[] x) +
      Sets the value of a parameter as a byte array.
      +
      voidsetCharacterStream(java.lang.String parameterName, + java.io.Reader x) +
      Sets the value of a parameter as a character stream.
      +
      voidsetCharacterStream(java.lang.String parameterName, + java.io.Reader x, + int length) +
      Sets the value of a parameter as a character stream.
      +
      voidsetCharacterStream(java.lang.String parameterName, + java.io.Reader x, + long length) +
      Sets the value of a parameter as a character stream.
      +
      voidsetClob(java.lang.String parameterName, + java.sql.Clob x) +
      Sets the value of a parameter as a Clob.
      +
      voidsetClob(java.lang.String parameterName, + java.io.Reader x) +
      Sets the value of a parameter as a character stream.
      +
      voidsetClob(java.lang.String parameterName, + java.io.Reader x, + long length) +
      Sets the value of a parameter as a Clob.
      +
      voidsetDate(java.lang.String parameterName, + java.sql.Date x) +
      Sets the value of a parameter.
      +
      voidsetDate(java.lang.String parameterName, + java.sql.Date x, + java.util.Calendar cal) +
      Sets the date using a specified time zone.
      +
      voidsetDouble(java.lang.String parameterName, + double x) +
      Sets the value of a parameter.
      +
      voidsetFloat(java.lang.String parameterName, + float x) +
      Sets the value of a parameter.
      +
      voidsetInt(java.lang.String parameterName, + int x) +
      Sets the value of a parameter.
      +
      voidsetLong(java.lang.String parameterName, + long x) +
      Sets the value of a parameter.
      +
      voidsetNCharacterStream(java.lang.String parameterName, + java.io.Reader x) +
      Sets the value of a parameter as a character stream.
      +
      voidsetNCharacterStream(java.lang.String parameterName, + java.io.Reader x, + long length) +
      Sets the value of a parameter as a character stream.
      +
      voidsetNClob(java.lang.String parameterName, + java.sql.NClob x) +
      Sets the value of a parameter as a Clob.
      +
      voidsetNClob(java.lang.String parameterName, + java.io.Reader x) +
      Sets the value of a parameter as a Clob.
      +
      voidsetNClob(java.lang.String parameterName, + java.io.Reader x, + long length) +
      Sets the value of a parameter as a Clob.
      +
      voidsetNString(java.lang.String parameterName, + java.lang.String x) +
      Sets the value of a parameter.
      +
      voidsetNull(java.lang.String parameterName, + int sqlType) +
      Sets a parameter to null.
      +
      voidsetNull(java.lang.String parameterName, + int sqlType, + java.lang.String typeName) +
      Sets a parameter to null.
      +
      voidsetObject(java.lang.String parameterName, + java.lang.Object x) +
      Sets the value of a parameter.
      +
      voidsetObject(java.lang.String parameterName, + java.lang.Object x, + int targetSqlType) +
      Sets the value of a parameter.
      +
      voidsetObject(java.lang.String parameterName, + java.lang.Object x, + int targetSqlType, + int scale) +
      Sets the value of a parameter.
      +
      voidsetObject(java.lang.String parameterName, + java.lang.Object x, + java.sql.SQLType targetSqlType) +
      Sets the value of a parameter.
      +
      voidsetObject(java.lang.String parameterName, + java.lang.Object x, + java.sql.SQLType targetSqlType, + int scaleOrLength) +
      Sets the value of a parameter.
      +
      voidsetRowId(java.lang.String parameterName, + java.sql.RowId x) +
      [Not supported] Sets the value of a parameter as a row id.
      +
      voidsetShort(java.lang.String parameterName, + short x) +
      Sets the value of a parameter.
      +
      voidsetSQLXML(java.lang.String parameterName, + java.sql.SQLXML x) +
      Sets the value of a parameter as a SQLXML object.
      +
      voidsetString(java.lang.String parameterName, + java.lang.String x) +
      Sets the value of a parameter.
      +
      voidsetTime(java.lang.String parameterName, + java.sql.Time x) +
      Sets the time using a specified time zone.
      +
      voidsetTime(java.lang.String parameterName, + java.sql.Time x, + java.util.Calendar cal) +
      Sets the time using a specified time zone.
      +
      voidsetTimestamp(java.lang.String parameterName, + java.sql.Timestamp x) +
      Sets the value of a parameter.
      +
      voidsetTimestamp(java.lang.String parameterName, + java.sql.Timestamp x, + java.util.Calendar cal) +
      Sets the timestamp using a specified time zone.
      +
      voidsetURL(java.lang.String parameterName, + java.net.URL val) +
      [Not supported]
      +
      booleanwasNull() +
      Returns whether the last column accessed was null.
      +
      + + +
        +
      • + + +

        Methods inherited from class org.h2.message.TraceObject

        +debugCode, debugCodeAssign, debugCodeCall, debugCodeCall, debugCodeCall, getNextId, getTraceId, getTraceObjectName, isDebugEnabled, isInfoEnabled, logAndConvert, quote, quoteArray, quoteBigDecimal, quoteBytes, quoteDate, quoteIntArray, quoteMap, quoteTime, quoteTimestamp, setTrace, unsupported
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
        +
      • + + +

        Methods inherited from interface java.sql.CallableStatement

        +registerOutParameter, registerOutParameter, registerOutParameter, registerOutParameter, registerOutParameter, registerOutParameter
      • +
      +
        +
      • + + +

        Methods inherited from interface java.sql.PreparedStatement

        +addBatch, clearParameters, execute, executeQuery, getMetaData, getParameterMetaData, setArray, setAsciiStream, setAsciiStream, setAsciiStream, setBigDecimal, setBinaryStream, setBinaryStream, setBinaryStream, setBlob, setBlob, setBlob, setBoolean, setByte, setBytes, setCharacterStream, setCharacterStream, setCharacterStream, setClob, setClob, setClob, setDate, setDate, setDouble, setFloat, setInt, setLong, setNCharacterStream, setNCharacterStream, setNClob, setNClob, setNClob, setNString, setNull, setNull, setObject, setObject, setObject, setObject, setObject, setRef, setRowId, setShort, setSQLXML, setString, setTime, setTime, setTimestamp, setTimestamp, setUnicodeStream, setURL
      • +
      +
        +
      • + + +

        Methods inherited from interface java.sql.Statement

        +addBatch, cancel, clearBatch, clearWarnings, close, closeOnCompletion, execute, execute, execute, execute, executeBatch, executeLargeBatch, executeLargeUpdate, executeLargeUpdate, executeLargeUpdate, executeLargeUpdate, executeQuery, executeUpdate, executeUpdate, executeUpdate, executeUpdate, getConnection, getFetchDirection, getFetchSize, getGeneratedKeys, getLargeMaxRows, getLargeUpdateCount, getMaxFieldSize, getMaxRows, getMoreResults, getMoreResults, getQueryTimeout, getResultSet, getResultSetConcurrency, getResultSetHoldability, getResultSetType, getUpdateCount, getWarnings, isClosed, isCloseOnCompletion, isPoolable, setCursorName, setEscapeProcessing, setFetchDirection, setFetchSize, setLargeMaxRows, setMaxFieldSize, setMaxRows, setPoolable, setQueryTimeout
      • +
      +
        +
      • + + +

        Methods inherited from interface java.sql.Wrapper

        +isWrapperFor, unwrap
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        executeUpdate

        +
        public int executeUpdate()
        +                  throws java.sql.SQLException
        +
        Executes a statement (insert, update, delete, create, drop) + and returns the update count. + If another result set exists for this statement, this will be closed + (even if this statement fails). + + If auto commit is on, this statement will be committed. + If the statement is a DDL statement (create, drop, alter) and does not + throw an exception, the current transaction (if any) is committed after + executing the statement.
        +
        +
        Specified by:
        +
        executeUpdate in interface java.sql.PreparedStatement
        +
        Overrides:
        +
        executeUpdate in class JdbcPreparedStatement
        +
        Returns:
        +
        the update count (number of row affected by an insert, update or + delete, or 0 if no rows or the statement was a create, drop, + commit or rollback)
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed or invalid
        +
        See Also:
        +
        JdbcPreparedStatement.executeLargeUpdate()
        +
        +
      • +
      + + + +
        +
      • +

        executeLargeUpdate

        +
        public long executeLargeUpdate()
        +                        throws java.sql.SQLException
        +
        Executes a statement (insert, update, delete, create, drop) + and returns the update count. + If another result set exists for this statement, this will be closed + (even if this statement fails). + + If auto commit is on, this statement will be committed. + If the statement is a DDL statement (create, drop, alter) and does not + throw an exception, the current transaction (if any) is committed after + executing the statement.
        +
        +
        Specified by:
        +
        executeLargeUpdate in interface java.sql.PreparedStatement
        +
        Overrides:
        +
        executeLargeUpdate in class JdbcPreparedStatement
        +
        Returns:
        +
        the update count (number of row affected by an insert, update or + delete, or 0 if no rows or the statement was a create, drop, + commit or rollback)
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        registerOutParameter

        +
        public void registerOutParameter(int parameterIndex,
        +                                 int sqlType)
        +                          throws java.sql.SQLException
        +
        Registers the given OUT parameter.
        +
        +
        Specified by:
        +
        registerOutParameter in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        sqlType - the data type (Types.x) - ignored
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        registerOutParameter

        +
        public void registerOutParameter(int parameterIndex,
        +                                 int sqlType,
        +                                 java.lang.String typeName)
        +                          throws java.sql.SQLException
        +
        Registers the given OUT parameter.
        +
        +
        Specified by:
        +
        registerOutParameter in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        sqlType - the data type (Types.x) - ignored
        +
        typeName - the SQL type name - ignored
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        registerOutParameter

        +
        public void registerOutParameter(int parameterIndex,
        +                                 int sqlType,
        +                                 int scale)
        +                          throws java.sql.SQLException
        +
        Registers the given OUT parameter.
        +
        +
        Specified by:
        +
        registerOutParameter in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        sqlType - the data type (Types.x)
        +
        scale - is ignored
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        registerOutParameter

        +
        public void registerOutParameter(java.lang.String parameterName,
        +                                 int sqlType,
        +                                 java.lang.String typeName)
        +                          throws java.sql.SQLException
        +
        Registers the given OUT parameter.
        +
        +
        Specified by:
        +
        registerOutParameter in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        sqlType - the data type (Types.x) - ignored
        +
        typeName - the SQL type name - ignored
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        registerOutParameter

        +
        public void registerOutParameter(java.lang.String parameterName,
        +                                 int sqlType,
        +                                 int scale)
        +                          throws java.sql.SQLException
        +
        Registers the given OUT parameter.
        +
        +
        Specified by:
        +
        registerOutParameter in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        sqlType - the data type (Types.x) - ignored
        +
        scale - is ignored
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        registerOutParameter

        +
        public void registerOutParameter(java.lang.String parameterName,
        +                                 int sqlType)
        +                          throws java.sql.SQLException
        +
        Registers the given OUT parameter.
        +
        +
        Specified by:
        +
        registerOutParameter in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        sqlType - the data type (Types.x) - ignored
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        wasNull

        +
        public boolean wasNull()
        +                throws java.sql.SQLException
        +
        Returns whether the last column accessed was null.
        +
        +
        Specified by:
        +
        wasNull in interface java.sql.CallableStatement
        +
        Returns:
        +
        true if the last column accessed was null
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getURL

        +
        public java.net.URL getURL(int parameterIndex)
        +                    throws java.sql.SQLException
        +
        [Not supported]
        +
        +
        Specified by:
        +
        getURL in interface java.sql.CallableStatement
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getString

        +
        public java.lang.String getString(int parameterIndex)
        +                           throws java.sql.SQLException
        +
        Returns the value of the specified column as a String.
        +
        +
        Specified by:
        +
        getString in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getBoolean

        +
        public boolean getBoolean(int parameterIndex)
        +                   throws java.sql.SQLException
        +
        Returns the value of the specified column as a boolean.
        +
        +
        Specified by:
        +
        getBoolean in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getByte

        +
        public byte getByte(int parameterIndex)
        +             throws java.sql.SQLException
        +
        Returns the value of the specified column as a byte.
        +
        +
        Specified by:
        +
        getByte in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getShort

        +
        public short getShort(int parameterIndex)
        +               throws java.sql.SQLException
        +
        Returns the value of the specified column as a short.
        +
        +
        Specified by:
        +
        getShort in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getInt

        +
        public int getInt(int parameterIndex)
        +           throws java.sql.SQLException
        +
        Returns the value of the specified column as an int.
        +
        +
        Specified by:
        +
        getInt in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getLong

        +
        public long getLong(int parameterIndex)
        +             throws java.sql.SQLException
        +
        Returns the value of the specified column as a long.
        +
        +
        Specified by:
        +
        getLong in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getFloat

        +
        public float getFloat(int parameterIndex)
        +               throws java.sql.SQLException
        +
        Returns the value of the specified column as a float.
        +
        +
        Specified by:
        +
        getFloat in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getDouble

        +
        public double getDouble(int parameterIndex)
        +                 throws java.sql.SQLException
        +
        Returns the value of the specified column as a double.
        +
        +
        Specified by:
        +
        getDouble in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getBigDecimal

        +
        @Deprecated
        +public java.math.BigDecimal getBigDecimal(int parameterIndex,
        +                                                      int scale)
        +                                               throws java.sql.SQLException
        +
        Deprecated. use getBigDecimal(int)
        +
        Returns the value of the specified column as a BigDecimal.
        +
        +
        Specified by:
        +
        getBigDecimal in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        scale - is ignored
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getBytes

        +
        public byte[] getBytes(int parameterIndex)
        +                throws java.sql.SQLException
        +
        Returns the value of the specified column as a byte array.
        +
        +
        Specified by:
        +
        getBytes in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getDate

        +
        public java.sql.Date getDate(int parameterIndex)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Date. +

        + Usage of this method is discouraged. Use + getObject(columnIndex, LocalDate.class) instead. +

        +
        +
        Specified by:
        +
        getDate in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        See Also:
        +
        getObject(int, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getTime

        +
        public java.sql.Time getTime(int parameterIndex)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Time. +

        + Usage of this method is discouraged. Use + getObject(columnIndex, LocalTime.class) instead. +

        +
        +
        Specified by:
        +
        getTime in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        See Also:
        +
        getObject(int, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getTimestamp

        +
        public java.sql.Timestamp getTimestamp(int parameterIndex)
        +                                throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Timestamp. +

        + Usage of this method is discouraged. Use + getObject(columnIndex, LocalDateTime.class) instead. +

        +
        +
        Specified by:
        +
        getTimestamp in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        See Also:
        +
        getObject(int, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getObject

        +
        public java.lang.Object getObject(int parameterIndex)
        +                           throws java.sql.SQLException
        +
        Returns a column value as a Java object. The data is + de-serialized into a Java object (on the client side).
        +
        +
        Specified by:
        +
        getObject in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        Returns:
        +
        the value or null
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getBigDecimal

        +
        public java.math.BigDecimal getBigDecimal(int parameterIndex)
        +                                   throws java.sql.SQLException
        +
        Returns the value of the specified column as a BigDecimal.
        +
        +
        Specified by:
        +
        getBigDecimal in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getObject

        +
        public java.lang.Object getObject(int parameterIndex,
        +                                  java.util.Map<java.lang.String,java.lang.Class<?>> map)
        +                           throws java.sql.SQLException
        +
        [Not supported] Gets a column as a object using the specified type + mapping.
        +
        +
        Specified by:
        +
        getObject in interface java.sql.CallableStatement
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getRef

        +
        public java.sql.Ref getRef(int parameterIndex)
        +                    throws java.sql.SQLException
        +
        [Not supported] Gets a column as a reference.
        +
        +
        Specified by:
        +
        getRef in interface java.sql.CallableStatement
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getBlob

        +
        public java.sql.Blob getBlob(int parameterIndex)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a Blob.
        +
        +
        Specified by:
        +
        getBlob in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getClob

        +
        public java.sql.Clob getClob(int parameterIndex)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a Clob.
        +
        +
        Specified by:
        +
        getClob in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getArray

        +
        public java.sql.Array getArray(int parameterIndex)
        +                        throws java.sql.SQLException
        +
        Returns the value of the specified column as an Array.
        +
        +
        Specified by:
        +
        getArray in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getDate

        +
        public java.sql.Date getDate(int parameterIndex,
        +                             java.util.Calendar cal)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Date using a + specified time zone. +

        + Usage of this method is discouraged. Use + getObject(columnIndex, LocalDate.class) instead. +

        +
        +
        Specified by:
        +
        getDate in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        cal - the calendar
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        See Also:
        +
        getObject(int, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getTime

        +
        public java.sql.Time getTime(int parameterIndex,
        +                             java.util.Calendar cal)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Time using a + specified time zone. +

        + Usage of this method is discouraged. Use + getObject(columnIndex, LocalTime.class) instead. +

        +
        +
        Specified by:
        +
        getTime in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        cal - the calendar
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        See Also:
        +
        getObject(int, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getTimestamp

        +
        public java.sql.Timestamp getTimestamp(int parameterIndex,
        +                                       java.util.Calendar cal)
        +                                throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Timestamp using a + specified time zone. +

        + Usage of this method is discouraged. Use + getObject(columnIndex, LocalDateTime.class) instead. +

        +
        +
        Specified by:
        +
        getTimestamp in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        cal - the calendar
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        See Also:
        +
        getObject(int, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getURL

        +
        public java.net.URL getURL(java.lang.String parameterName)
        +                    throws java.sql.SQLException
        +
        [Not supported]
        +
        +
        Specified by:
        +
        getURL in interface java.sql.CallableStatement
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getTimestamp

        +
        public java.sql.Timestamp getTimestamp(java.lang.String parameterName,
        +                                       java.util.Calendar cal)
        +                                throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Timestamp using a + specified time zone. +

        + Usage of this method is discouraged. Use + getObject(parameterName, LocalDateTime.class) instead. +

        +
        +
        Specified by:
        +
        getTimestamp in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        cal - the calendar
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        See Also:
        +
        getObject(String, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getTime

        +
        public java.sql.Time getTime(java.lang.String parameterName,
        +                             java.util.Calendar cal)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Time using a + specified time zone. +

        + Usage of this method is discouraged. Use + getObject(parameterName, LocalTime.class) instead. +

        +
        +
        Specified by:
        +
        getTime in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        cal - the calendar
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        See Also:
        +
        getObject(String, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getDate

        +
        public java.sql.Date getDate(java.lang.String parameterName,
        +                             java.util.Calendar cal)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Date using a + specified time zone. +

        + Usage of this method is discouraged. Use + getObject(parameterName, LocalDate.class) instead. +

        +
        +
        Specified by:
        +
        getDate in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        cal - the calendar
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        See Also:
        +
        getObject(String, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getArray

        +
        public java.sql.Array getArray(java.lang.String parameterName)
        +                        throws java.sql.SQLException
        +
        Returns the value of the specified column as an Array.
        +
        +
        Specified by:
        +
        getArray in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getClob

        +
        public java.sql.Clob getClob(java.lang.String parameterName)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a Clob.
        +
        +
        Specified by:
        +
        getClob in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getBlob

        +
        public java.sql.Blob getBlob(java.lang.String parameterName)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a Blob.
        +
        +
        Specified by:
        +
        getBlob in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getRef

        +
        public java.sql.Ref getRef(java.lang.String parameterName)
        +                    throws java.sql.SQLException
        +
        [Not supported] Gets a column as a reference.
        +
        +
        Specified by:
        +
        getRef in interface java.sql.CallableStatement
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getObject

        +
        public java.lang.Object getObject(java.lang.String parameterName,
        +                                  java.util.Map<java.lang.String,java.lang.Class<?>> map)
        +                           throws java.sql.SQLException
        +
        [Not supported] Gets a column as a object using the specified type + mapping.
        +
        +
        Specified by:
        +
        getObject in interface java.sql.CallableStatement
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getBigDecimal

        +
        public java.math.BigDecimal getBigDecimal(java.lang.String parameterName)
        +                                   throws java.sql.SQLException
        +
        Returns the value of the specified column as a BigDecimal.
        +
        +
        Specified by:
        +
        getBigDecimal in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getObject

        +
        public java.lang.Object getObject(java.lang.String parameterName)
        +                           throws java.sql.SQLException
        +
        Returns a column value as a Java object. The data is + de-serialized into a Java object (on the client side).
        +
        +
        Specified by:
        +
        getObject in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        Returns:
        +
        the value or null
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getTimestamp

        +
        public java.sql.Timestamp getTimestamp(java.lang.String parameterName)
        +                                throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Timestamp. +

        + Usage of this method is discouraged. Use + getObject(parameterName, LocalDateTime.class) instead. +

        +
        +
        Specified by:
        +
        getTimestamp in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        See Also:
        +
        getObject(String, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getTime

        +
        public java.sql.Time getTime(java.lang.String parameterName)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Time. +

        + Usage of this method is discouraged. Use + getObject(parameterName, LocalTime.class) instead. +

        +
        +
        Specified by:
        +
        getTime in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        See Also:
        +
        getObject(String, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getDate

        +
        public java.sql.Date getDate(java.lang.String parameterName)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Date. +

        + Usage of this method is discouraged. Use + getObject(parameterName, LocalDate.class) instead. +

        +
        +
        Specified by:
        +
        getDate in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        See Also:
        +
        getObject(String, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getBytes

        +
        public byte[] getBytes(java.lang.String parameterName)
        +                throws java.sql.SQLException
        +
        Returns the value of the specified column as a byte array.
        +
        +
        Specified by:
        +
        getBytes in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getDouble

        +
        public double getDouble(java.lang.String parameterName)
        +                 throws java.sql.SQLException
        +
        Returns the value of the specified column as a double.
        +
        +
        Specified by:
        +
        getDouble in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getFloat

        +
        public float getFloat(java.lang.String parameterName)
        +               throws java.sql.SQLException
        +
        Returns the value of the specified column as a float.
        +
        +
        Specified by:
        +
        getFloat in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getLong

        +
        public long getLong(java.lang.String parameterName)
        +             throws java.sql.SQLException
        +
        Returns the value of the specified column as a long.
        +
        +
        Specified by:
        +
        getLong in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getInt

        +
        public int getInt(java.lang.String parameterName)
        +           throws java.sql.SQLException
        +
        Returns the value of the specified column as an int.
        +
        +
        Specified by:
        +
        getInt in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getShort

        +
        public short getShort(java.lang.String parameterName)
        +               throws java.sql.SQLException
        +
        Returns the value of the specified column as a short.
        +
        +
        Specified by:
        +
        getShort in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getByte

        +
        public byte getByte(java.lang.String parameterName)
        +             throws java.sql.SQLException
        +
        Returns the value of the specified column as a byte.
        +
        +
        Specified by:
        +
        getByte in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getBoolean

        +
        public boolean getBoolean(java.lang.String parameterName)
        +                   throws java.sql.SQLException
        +
        Returns the value of the specified column as a boolean.
        +
        +
        Specified by:
        +
        getBoolean in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getString

        +
        public java.lang.String getString(java.lang.String parameterName)
        +                           throws java.sql.SQLException
        +
        Returns the value of the specified column as a String.
        +
        +
        Specified by:
        +
        getString in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getRowId

        +
        public java.sql.RowId getRowId(int parameterIndex)
        +                        throws java.sql.SQLException
        +
        [Not supported] Returns the value of the specified column as a row id.
        +
        +
        Specified by:
        +
        getRowId in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getRowId

        +
        public java.sql.RowId getRowId(java.lang.String parameterName)
        +                        throws java.sql.SQLException
        +
        [Not supported] Returns the value of the specified column as a row id.
        +
        +
        Specified by:
        +
        getRowId in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getNClob

        +
        public java.sql.NClob getNClob(int parameterIndex)
        +                        throws java.sql.SQLException
        +
        Returns the value of the specified column as a Clob.
        +
        +
        Specified by:
        +
        getNClob in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getNClob

        +
        public java.sql.NClob getNClob(java.lang.String parameterName)
        +                        throws java.sql.SQLException
        +
        Returns the value of the specified column as a Clob.
        +
        +
        Specified by:
        +
        getNClob in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getSQLXML

        +
        public java.sql.SQLXML getSQLXML(int parameterIndex)
        +                          throws java.sql.SQLException
        +
        Returns the value of the specified column as a SQLXML object.
        +
        +
        Specified by:
        +
        getSQLXML in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getSQLXML

        +
        public java.sql.SQLXML getSQLXML(java.lang.String parameterName)
        +                          throws java.sql.SQLException
        +
        Returns the value of the specified column as a SQLXML object.
        +
        +
        Specified by:
        +
        getSQLXML in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getNString

        +
        public java.lang.String getNString(int parameterIndex)
        +                            throws java.sql.SQLException
        +
        Returns the value of the specified column as a String.
        +
        +
        Specified by:
        +
        getNString in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getNString

        +
        public java.lang.String getNString(java.lang.String parameterName)
        +                            throws java.sql.SQLException
        +
        Returns the value of the specified column as a String.
        +
        +
        Specified by:
        +
        getNString in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getNCharacterStream

        +
        public java.io.Reader getNCharacterStream(int parameterIndex)
        +                                   throws java.sql.SQLException
        +
        Returns the value of the specified column as a reader.
        +
        +
        Specified by:
        +
        getNCharacterStream in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getNCharacterStream

        +
        public java.io.Reader getNCharacterStream(java.lang.String parameterName)
        +                                   throws java.sql.SQLException
        +
        Returns the value of the specified column as a reader.
        +
        +
        Specified by:
        +
        getNCharacterStream in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getCharacterStream

        +
        public java.io.Reader getCharacterStream(int parameterIndex)
        +                                  throws java.sql.SQLException
        +
        Returns the value of the specified column as a reader.
        +
        +
        Specified by:
        +
        getCharacterStream in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getCharacterStream

        +
        public java.io.Reader getCharacterStream(java.lang.String parameterName)
        +                                  throws java.sql.SQLException
        +
        Returns the value of the specified column as a reader.
        +
        +
        Specified by:
        +
        getCharacterStream in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        setNull

        +
        public void setNull(java.lang.String parameterName,
        +                    int sqlType,
        +                    java.lang.String typeName)
        +             throws java.sql.SQLException
        +
        Sets a parameter to null.
        +
        +
        Specified by:
        +
        setNull in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        sqlType - the data type (Types.x)
        +
        typeName - this parameter is ignored
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setNull

        +
        public void setNull(java.lang.String parameterName,
        +                    int sqlType)
        +             throws java.sql.SQLException
        +
        Sets a parameter to null.
        +
        +
        Specified by:
        +
        setNull in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        sqlType - the data type (Types.x)
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setTimestamp

        +
        public void setTimestamp(java.lang.String parameterName,
        +                         java.sql.Timestamp x,
        +                         java.util.Calendar cal)
        +                  throws java.sql.SQLException
        +
        Sets the timestamp using a specified time zone. The value will be + converted to the local time zone. +

        + Usage of this method is discouraged. Use + setObject(parameterName, value) with + LocalDateTime parameter instead. +

        +
        +
        Specified by:
        +
        setTimestamp in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        cal - the calendar
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        See Also:
        +
        setObject(String, Object)
        +
        +
      • +
      + + + +
        +
      • +

        setTime

        +
        public void setTime(java.lang.String parameterName,
        +                    java.sql.Time x,
        +                    java.util.Calendar cal)
        +             throws java.sql.SQLException
        +
        Sets the time using a specified time zone. The value will be converted to + the local time zone. +

        + Usage of this method is discouraged. Use + setObject(parameterName, value) with LocalTime + parameter instead. +

        +
        +
        Specified by:
        +
        setTime in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        cal - the calendar
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        See Also:
        +
        setObject(String, Object)
        +
        +
      • +
      + + + +
        +
      • +

        setDate

        +
        public void setDate(java.lang.String parameterName,
        +                    java.sql.Date x,
        +                    java.util.Calendar cal)
        +             throws java.sql.SQLException
        +
        Sets the date using a specified time zone. The value will be converted to + the local time zone. +

        + Usage of this method is discouraged. Use + setObject(parameterName, value) with LocalDate + parameter instead. +

        +
        +
        Specified by:
        +
        setDate in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        cal - the calendar
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        See Also:
        +
        setObject(String, Object)
        +
        +
      • +
      + + + +
        +
      • +

        setCharacterStream

        +
        public void setCharacterStream(java.lang.String parameterName,
        +                               java.io.Reader x,
        +                               int length)
        +                        throws java.sql.SQLException
        +
        Sets the value of a parameter as a character stream. + This method does not close the reader. + The reader may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setCharacterStream in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        length - the maximum number of characters
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setObject

        +
        public void setObject(java.lang.String parameterName,
        +                      java.lang.Object x)
        +               throws java.sql.SQLException
        +
        Sets the value of a parameter. + Objects of unknown classes are serialized (on the client side).
        +
        +
        Specified by:
        +
        setObject in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setObject

        +
        public void setObject(java.lang.String parameterName,
        +                      java.lang.Object x,
        +                      int targetSqlType)
        +               throws java.sql.SQLException
        +
        Sets the value of a parameter. The object is converted, if required, to + the specified data type before sending to the database. + Objects of unknown classes are serialized (on the client side).
        +
        +
        Specified by:
        +
        setObject in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value, null is allowed
        +
        targetSqlType - the type as defined in java.sql.Types
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setObject

        +
        public void setObject(java.lang.String parameterName,
        +                      java.lang.Object x,
        +                      int targetSqlType,
        +                      int scale)
        +               throws java.sql.SQLException
        +
        Sets the value of a parameter. The object is converted, if required, to + the specified data type before sending to the database. + Objects of unknown classes are serialized (on the client side).
        +
        +
        Specified by:
        +
        setObject in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value, null is allowed
        +
        targetSqlType - the type as defined in java.sql.Types
        +
        scale - is ignored
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setObject

        +
        public void setObject(java.lang.String parameterName,
        +                      java.lang.Object x,
        +                      java.sql.SQLType targetSqlType)
        +               throws java.sql.SQLException
        +
        Sets the value of a parameter. The object is converted, if required, to + the specified data type before sending to the database. + Objects of unknown classes are serialized (on the client side).
        +
        +
        Specified by:
        +
        setObject in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value, null is allowed
        +
        targetSqlType - the type
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setObject

        +
        public void setObject(java.lang.String parameterName,
        +                      java.lang.Object x,
        +                      java.sql.SQLType targetSqlType,
        +                      int scaleOrLength)
        +               throws java.sql.SQLException
        +
        Sets the value of a parameter. The object is converted, if required, to + the specified data type before sending to the database. + Objects of unknown classes are serialized (on the client side).
        +
        +
        Specified by:
        +
        setObject in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value, null is allowed
        +
        targetSqlType - the type
        +
        scaleOrLength - is ignored
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setBinaryStream

        +
        public void setBinaryStream(java.lang.String parameterName,
        +                            java.io.InputStream x,
        +                            int length)
        +                     throws java.sql.SQLException
        +
        Sets the value of a parameter as an input stream. + This method does not close the stream. + The stream may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setBinaryStream in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        length - the maximum number of bytes
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setAsciiStream

        +
        public void setAsciiStream(java.lang.String parameterName,
        +                           java.io.InputStream x,
        +                           long length)
        +                    throws java.sql.SQLException
        +
        Sets the value of a parameter as an ASCII stream. + This method does not close the stream. + The stream may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setAsciiStream in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        length - the maximum number of bytes
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setTimestamp

        +
        public void setTimestamp(java.lang.String parameterName,
        +                         java.sql.Timestamp x)
        +                  throws java.sql.SQLException
        +
        Sets the value of a parameter. +

        + Usage of this method is discouraged. Use + setObject(parameterName, value) with + LocalDateTime parameter instead. +

        +
        +
        Specified by:
        +
        setTimestamp in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        See Also:
        +
        setObject(String, Object)
        +
        +
      • +
      + + + +
        +
      • +

        setTime

        +
        public void setTime(java.lang.String parameterName,
        +                    java.sql.Time x)
        +             throws java.sql.SQLException
        +
        Sets the time using a specified time zone. +

        + Usage of this method is discouraged. Use + setObject(parameterName, value) with LocalTime + parameter instead. +

        +
        +
        Specified by:
        +
        setTime in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        See Also:
        +
        setObject(String, Object)
        +
        +
      • +
      + + + +
        +
      • +

        setDate

        +
        public void setDate(java.lang.String parameterName,
        +                    java.sql.Date x)
        +             throws java.sql.SQLException
        +
        Sets the value of a parameter. +

        + Usage of this method is discouraged. Use + setObject(parameterName, value) with LocalDate + parameter instead. +

        +
        +
        Specified by:
        +
        setDate in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        See Also:
        +
        setObject(String, Object)
        +
        +
      • +
      + + + +
        +
      • +

        setBytes

        +
        public void setBytes(java.lang.String parameterName,
        +                     byte[] x)
        +              throws java.sql.SQLException
        +
        Sets the value of a parameter as a byte array.
        +
        +
        Specified by:
        +
        setBytes in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setString

        +
        public void setString(java.lang.String parameterName,
        +                      java.lang.String x)
        +               throws java.sql.SQLException
        +
        Sets the value of a parameter.
        +
        +
        Specified by:
        +
        setString in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setBigDecimal

        +
        public void setBigDecimal(java.lang.String parameterName,
        +                          java.math.BigDecimal x)
        +                   throws java.sql.SQLException
        +
        Sets the value of a parameter.
        +
        +
        Specified by:
        +
        setBigDecimal in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setDouble

        +
        public void setDouble(java.lang.String parameterName,
        +                      double x)
        +               throws java.sql.SQLException
        +
        Sets the value of a parameter.
        +
        +
        Specified by:
        +
        setDouble in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setFloat

        +
        public void setFloat(java.lang.String parameterName,
        +                     float x)
        +              throws java.sql.SQLException
        +
        Sets the value of a parameter.
        +
        +
        Specified by:
        +
        setFloat in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setLong

        +
        public void setLong(java.lang.String parameterName,
        +                    long x)
        +             throws java.sql.SQLException
        +
        Sets the value of a parameter.
        +
        +
        Specified by:
        +
        setLong in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setInt

        +
        public void setInt(java.lang.String parameterName,
        +                   int x)
        +            throws java.sql.SQLException
        +
        Sets the value of a parameter.
        +
        +
        Specified by:
        +
        setInt in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setShort

        +
        public void setShort(java.lang.String parameterName,
        +                     short x)
        +              throws java.sql.SQLException
        +
        Sets the value of a parameter.
        +
        +
        Specified by:
        +
        setShort in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setByte

        +
        public void setByte(java.lang.String parameterName,
        +                    byte x)
        +             throws java.sql.SQLException
        +
        Sets the value of a parameter.
        +
        +
        Specified by:
        +
        setByte in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setBoolean

        +
        public void setBoolean(java.lang.String parameterName,
        +                       boolean x)
        +                throws java.sql.SQLException
        +
        Sets the value of a parameter.
        +
        +
        Specified by:
        +
        setBoolean in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setURL

        +
        public void setURL(java.lang.String parameterName,
        +                   java.net.URL val)
        +            throws java.sql.SQLException
        +
        [Not supported]
        +
        +
        Specified by:
        +
        setURL in interface java.sql.CallableStatement
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        setRowId

        +
        public void setRowId(java.lang.String parameterName,
        +                     java.sql.RowId x)
        +              throws java.sql.SQLException
        +
        [Not supported] Sets the value of a parameter as a row id.
        +
        +
        Specified by:
        +
        setRowId in interface java.sql.CallableStatement
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        setNString

        +
        public void setNString(java.lang.String parameterName,
        +                       java.lang.String x)
        +                throws java.sql.SQLException
        +
        Sets the value of a parameter.
        +
        +
        Specified by:
        +
        setNString in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setNCharacterStream

        +
        public void setNCharacterStream(java.lang.String parameterName,
        +                                java.io.Reader x,
        +                                long length)
        +                         throws java.sql.SQLException
        +
        Sets the value of a parameter as a character stream. + This method does not close the reader. + The reader may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setNCharacterStream in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        length - the maximum number of characters
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setNClob

        +
        public void setNClob(java.lang.String parameterName,
        +                     java.sql.NClob x)
        +              throws java.sql.SQLException
        +
        Sets the value of a parameter as a Clob.
        +
        +
        Specified by:
        +
        setNClob in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setClob

        +
        public void setClob(java.lang.String parameterName,
        +                    java.io.Reader x,
        +                    long length)
        +             throws java.sql.SQLException
        +
        Sets the value of a parameter as a Clob. + This method does not close the reader. + The reader may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setClob in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        length - the maximum number of characters
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setBlob

        +
        public void setBlob(java.lang.String parameterName,
        +                    java.io.InputStream x,
        +                    long length)
        +             throws java.sql.SQLException
        +
        Sets the value of a parameter as a Blob. + This method does not close the stream. + The stream may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setBlob in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        length - the maximum number of bytes
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setNClob

        +
        public void setNClob(java.lang.String parameterName,
        +                     java.io.Reader x,
        +                     long length)
        +              throws java.sql.SQLException
        +
        Sets the value of a parameter as a Clob. + This method does not close the reader. + The reader may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setNClob in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        length - the maximum number of characters
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setBlob

        +
        public void setBlob(java.lang.String parameterName,
        +                    java.sql.Blob x)
        +             throws java.sql.SQLException
        +
        Sets the value of a parameter as a Blob.
        +
        +
        Specified by:
        +
        setBlob in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setClob

        +
        public void setClob(java.lang.String parameterName,
        +                    java.sql.Clob x)
        +             throws java.sql.SQLException
        +
        Sets the value of a parameter as a Clob.
        +
        +
        Specified by:
        +
        setClob in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setAsciiStream

        +
        public void setAsciiStream(java.lang.String parameterName,
        +                           java.io.InputStream x)
        +                    throws java.sql.SQLException
        +
        Sets the value of a parameter as an ASCII stream. + This method does not close the stream. + The stream may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setAsciiStream in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setAsciiStream

        +
        public void setAsciiStream(java.lang.String parameterName,
        +                           java.io.InputStream x,
        +                           int length)
        +                    throws java.sql.SQLException
        +
        Sets the value of a parameter as an ASCII stream. + This method does not close the stream. + The stream may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setAsciiStream in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        length - the maximum number of bytes
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setBinaryStream

        +
        public void setBinaryStream(java.lang.String parameterName,
        +                            java.io.InputStream x)
        +                     throws java.sql.SQLException
        +
        Sets the value of a parameter as an input stream. + This method does not close the stream. + The stream may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setBinaryStream in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setBinaryStream

        +
        public void setBinaryStream(java.lang.String parameterName,
        +                            java.io.InputStream x,
        +                            long length)
        +                     throws java.sql.SQLException
        +
        Sets the value of a parameter as an input stream. + This method does not close the stream. + The stream may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setBinaryStream in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        length - the maximum number of bytes
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setBlob

        +
        public void setBlob(java.lang.String parameterName,
        +                    java.io.InputStream x)
        +             throws java.sql.SQLException
        +
        Sets the value of a parameter as a Blob. + This method does not close the stream. + The stream may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setBlob in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setCharacterStream

        +
        public void setCharacterStream(java.lang.String parameterName,
        +                               java.io.Reader x)
        +                        throws java.sql.SQLException
        +
        Sets the value of a parameter as a character stream. + This method does not close the reader. + The reader may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setCharacterStream in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setCharacterStream

        +
        public void setCharacterStream(java.lang.String parameterName,
        +                               java.io.Reader x,
        +                               long length)
        +                        throws java.sql.SQLException
        +
        Sets the value of a parameter as a character stream. + This method does not close the reader. + The reader may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setCharacterStream in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        length - the maximum number of characters
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setClob

        +
        public void setClob(java.lang.String parameterName,
        +                    java.io.Reader x)
        +             throws java.sql.SQLException
        +
        Sets the value of a parameter as a character stream. + This method does not close the reader. + The reader may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setClob in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setNCharacterStream

        +
        public void setNCharacterStream(java.lang.String parameterName,
        +                                java.io.Reader x)
        +                         throws java.sql.SQLException
        +
        Sets the value of a parameter as a character stream. + This method does not close the reader. + The reader may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setNCharacterStream in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setNClob

        +
        public void setNClob(java.lang.String parameterName,
        +                     java.io.Reader x)
        +              throws java.sql.SQLException
        +
        Sets the value of a parameter as a Clob. + This method does not close the reader. + The reader may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setNClob in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setSQLXML

        +
        public void setSQLXML(java.lang.String parameterName,
        +                      java.sql.SQLXML x)
        +               throws java.sql.SQLException
        +
        Sets the value of a parameter as a SQLXML object.
        +
        +
        Specified by:
        +
        setSQLXML in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        getObject

        +
        public <T> T getObject(int parameterIndex,
        +                       java.lang.Class<T> type)
        +                throws java.sql.SQLException
        +
        Returns the value of the specified column as a Java object of the + specified type.
        +
        +
        Specified by:
        +
        getObject in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        type - the class of the returned value
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getObject

        +
        public <T> T getObject(java.lang.String parameterName,
        +                       java.lang.Class<T> type)
        +                throws java.sql.SQLException
        +
        Returns the value of the specified column as a Java object of the + specified type.
        +
        +
        Specified by:
        +
        getObject in interface java.sql.CallableStatement
        +
        Parameters:
        +
        parameterName - the parameter name
        +
        type - the class of the returned value
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if this object is + closed
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcClob.html b/h2/docs/javadoc/org/h2/jdbc/JdbcClob.html new file mode 100644 index 0000000..3c547a5 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcClob.html @@ -0,0 +1,672 @@ + + + + + +JdbcClob + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcClob

+
+
+
    +
  • java.lang.Object
  • +
  • + +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.sql.Clob, java.sql.NClob
    +
    +
    +
    +
    public final class JdbcClob
    +extends JdbcLob
    +implements java.sql.NClob
    +
    Represents a CLOB value.
    +
  • +
+
+
+
    +
  • + + + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.message.TraceObject

        +ARRAY, BLOB, CALLABLE_STATEMENT, CLOB, CONNECTION, DATA_SOURCE, DATABASE_META_DATA, PARAMETER_META_DATA, PREPARED_STATEMENT, RESULT_SET, RESULT_SET_META_DATA, SAVEPOINT, SQLXML, STATEMENT, trace, XA_DATA_SOURCE, XID
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      JdbcClob(JdbcConnection conn, + org.h2.value.Value value, + JdbcLob.State state, + int id) +
      INTERNAL
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      java.io.InputStreamgetAsciiStream() +
      Returns the input stream.
      +
      java.io.ReadergetCharacterStream() +
      Returns the reader.
      +
      java.io.ReadergetCharacterStream(long pos, + long length) +
      Returns the reader, starting from an offset.
      +
      java.lang.StringgetSubString(long pos, + int length) +
      Returns a substring.
      +
      longlength() +
      Returns the length.
      +
      longposition(java.sql.Clob clobPattern, + long start) +
      [Not supported] Searches a pattern and return the position.
      +
      longposition(java.lang.String pattern, + long start) +
      [Not supported] Searches a pattern and return the position.
      +
      java.io.OutputStreamsetAsciiStream(long pos) +
      [Not supported] Returns an output stream.
      +
      java.io.WritersetCharacterStream(long pos) +
      Get a writer to update the Clob.
      +
      intsetString(long pos, + java.lang.String str) +
      Fills the Clob.
      +
      intsetString(long pos, + java.lang.String str, + int offset, + int len) +
      Fills the Clob.
      +
      voidtruncate(long len) +
      [Not supported] Truncates the object.
      +
      + +
        +
      • + + +

        Methods inherited from class org.h2.message.TraceObject

        +debugCode, debugCodeAssign, debugCodeCall, debugCodeCall, debugCodeCall, getNextId, getTraceId, getTraceObjectName, isDebugEnabled, isInfoEnabled, logAndConvert, quote, quoteArray, quoteBigDecimal, quoteBytes, quoteDate, quoteIntArray, quoteMap, quoteTime, quoteTimestamp, setTrace, unsupported
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
        +
      • + + +

        Methods inherited from interface java.sql.Clob

        +free
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        JdbcClob

        +
        public JdbcClob(JdbcConnection conn,
        +                org.h2.value.Value value,
        +                JdbcLob.State state,
        +                int id)
        +
        INTERNAL
        +
        +
        Parameters:
        +
        conn - it belongs to
        +
        value - of
        +
        state - of the LOB
        +
        id - of the trace object
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        length

        +
        public long length()
        +            throws java.sql.SQLException
        +
        Returns the length.
        +
        +
        Specified by:
        +
        length in interface java.sql.Clob
        +
        Returns:
        +
        the length
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        truncate

        +
        public void truncate(long len)
        +              throws java.sql.SQLException
        +
        [Not supported] Truncates the object.
        +
        +
        Specified by:
        +
        truncate in interface java.sql.Clob
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getAsciiStream

        +
        public java.io.InputStream getAsciiStream()
        +                                   throws java.sql.SQLException
        +
        Returns the input stream.
        +
        +
        Specified by:
        +
        getAsciiStream in interface java.sql.Clob
        +
        Returns:
        +
        the input stream
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        setAsciiStream

        +
        public java.io.OutputStream setAsciiStream(long pos)
        +                                    throws java.sql.SQLException
        +
        [Not supported] Returns an output stream.
        +
        +
        Specified by:
        +
        setAsciiStream in interface java.sql.Clob
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getCharacterStream

        +
        public java.io.Reader getCharacterStream()
        +                                  throws java.sql.SQLException
        +
        Description copied from class: JdbcLob
        +
        Returns the reader.
        +
        +
        Specified by:
        +
        getCharacterStream in interface java.sql.Clob
        +
        Returns:
        +
        the reader
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        setCharacterStream

        +
        public java.io.Writer setCharacterStream(long pos)
        +                                  throws java.sql.SQLException
        +
        Get a writer to update the Clob. This is only supported for new, empty + Clob objects that were created with Connection.createClob() or + createNClob(). The Clob is created in a separate thread, and the object + is only updated when Writer.close() is called. The position must be 1, + meaning the whole Clob data is set.
        +
        +
        Specified by:
        +
        setCharacterStream in interface java.sql.Clob
        +
        Parameters:
        +
        pos - where to start writing (the first character is at position 1)
        +
        Returns:
        +
        a writer
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getSubString

        +
        public java.lang.String getSubString(long pos,
        +                                     int length)
        +                              throws java.sql.SQLException
        +
        Returns a substring.
        +
        +
        Specified by:
        +
        getSubString in interface java.sql.Clob
        +
        Parameters:
        +
        pos - the position (the first character is at position 1)
        +
        length - the number of characters
        +
        Returns:
        +
        the string
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        setString

        +
        public int setString(long pos,
        +                     java.lang.String str)
        +              throws java.sql.SQLException
        +
        Fills the Clob. This is only supported for new, empty Clob objects that + were created with Connection.createClob() or createNClob(). The position + must be 1, meaning the whole Clob data is set.
        +
        +
        Specified by:
        +
        setString in interface java.sql.Clob
        +
        Parameters:
        +
        pos - where to start writing (the first character is at position 1)
        +
        str - the string to add
        +
        Returns:
        +
        the length of the added text
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        setString

        +
        public int setString(long pos,
        +                     java.lang.String str,
        +                     int offset,
        +                     int len)
        +              throws java.sql.SQLException
        +
        Fills the Clob. This is only supported for new, empty Clob objects that + were created with Connection.createClob() or createNClob(). The position + must be 1, meaning the whole Clob data is set.
        +
        +
        Specified by:
        +
        setString in interface java.sql.Clob
        +
        Parameters:
        +
        pos - where to start writing (the first character is at position 1)
        +
        str - the string to add
        +
        offset - the string offset
        +
        len - the number of characters to read
        +
        Returns:
        +
        the length of the added text
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        position

        +
        public long position(java.lang.String pattern,
        +                     long start)
        +              throws java.sql.SQLException
        +
        [Not supported] Searches a pattern and return the position.
        +
        +
        Specified by:
        +
        position in interface java.sql.Clob
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        position

        +
        public long position(java.sql.Clob clobPattern,
        +                     long start)
        +              throws java.sql.SQLException
        +
        [Not supported] Searches a pattern and return the position.
        +
        +
        Specified by:
        +
        position in interface java.sql.Clob
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getCharacterStream

        +
        public java.io.Reader getCharacterStream(long pos,
        +                                         long length)
        +                                  throws java.sql.SQLException
        +
        Returns the reader, starting from an offset.
        +
        +
        Specified by:
        +
        getCharacterStream in interface java.sql.Clob
        +
        Parameters:
        +
        pos - 1-based offset
        +
        length - length of requested area
        +
        Returns:
        +
        the reader
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcConnection.html b/h2/docs/javadoc/org/h2/jdbc/JdbcConnection.html new file mode 100644 index 0000000..24c4d14 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcConnection.html @@ -0,0 +1,2047 @@ + + + + + +JdbcConnection + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcConnection

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.message.TraceObject
    • +
    • +
        +
      • org.h2.jdbc.JdbcConnection
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.lang.AutoCloseable, java.sql.Connection, java.sql.Wrapper, CastDataProvider, JdbcConnectionBackwardsCompat
    +
    +
    +
    +
    public class JdbcConnection
    +extends org.h2.message.TraceObject
    +implements java.sql.Connection, JdbcConnectionBackwardsCompat, CastDataProvider
    +
    Represents a connection (session) to a database. +

    + Thread safety: the connection is thread-safe, because access is synchronized. + Different statements from the same connection may try to execute their + commands in parallel, but they will be executed sequentially. If real + concurrent execution of these commands is needed, different connections + should be used. +

    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.message.TraceObject

        +ARRAY, BLOB, CALLABLE_STATEMENT, CLOB, CONNECTION, DATA_SOURCE, DATABASE_META_DATA, PARAMETER_META_DATA, PREPARED_STATEMENT, RESULT_SET, RESULT_SET_META_DATA, SAVEPOINT, SQLXML, STATEMENT, trace, XA_DATA_SOURCE, XID
      • +
      +
        +
      • + + +

        Fields inherited from interface java.sql.Connection

        +TRANSACTION_NONE, TRANSACTION_READ_COMMITTED, TRANSACTION_READ_UNCOMMITTED, TRANSACTION_REPEATABLE_READ, TRANSACTION_SERIALIZABLE
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + + + + + + + +
      Constructors 
      Constructor and Description
      JdbcConnection(JdbcConnection clone) +
      INTERNAL
      +
      JdbcConnection(Session session, + java.lang.String user, + java.lang.String url) +
      INTERNAL
      +
      JdbcConnection(java.lang.String url, + java.util.Properties info, + java.lang.String user, + java.lang.Object password, + boolean forbidCreation) +
      INTERNAL + the session closable object does not leak as Eclipse warns - due to the + CloseWatcher.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      voidabort(java.util.concurrent.Executor executor) +
      [Not supported]
      +
      protected voidcheckClosed() +
      INTERNAL.
      +
      voidclearWarnings() +
      Clears all warnings.
      +
      voidclose() +
      Closes this connection.
      +
      voidcommit() +
      Commits the current transaction.
      +
      java.sql.ArraycreateArrayOf(java.lang.String typeName, + java.lang.Object[] elements) +
      Create a new Array object.
      +
      java.sql.BlobcreateBlob() +
      Create a new empty Blob object.
      +
      java.sql.ClobcreateClob() +
      Create a new empty Clob object.
      +
      java.sql.NClobcreateNClob() +
      Create a new empty NClob object.
      +
      java.sql.SQLXMLcreateSQLXML() +
      Create a new SQLXML object with no data.
      +
      java.sql.StatementcreateStatement() +
      Creates a new statement.
      +
      java.sql.StatementcreateStatement(int resultSetType, + int resultSetConcurrency) +
      Creates a statement with the specified result set type and concurrency.
      +
      java.sql.StatementcreateStatement(int resultSetType, + int resultSetConcurrency, + int resultSetHoldability) +
      Creates a statement with the specified result set type, concurrency, and + holdability.
      +
      java.sql.StructcreateStruct(java.lang.String typeName, + java.lang.Object[] attributes) +
      [Not supported] Create a new empty Struct object.
      +
      org.h2.value.ValueTimestampTimeZonecurrentTimestamp() +
      Returns the current timestamp with maximum resolution.
      +
      org.h2.util.TimeZoneProvidercurrentTimeZone() +
      Returns the current time zone.
      +
      booleangetAutoCommit() +
      Gets the current setting for auto commit.
      +
      java.lang.StringgetCatalog() +
      Gets the current catalog name.
      +
      java.util.PropertiesgetClientInfo() +
      Get the client properties.
      +
      java.lang.StringgetClientInfo(java.lang.String name) +
      Get a client property.
      +
      intgetHoldability() +
      Returns the current result set holdability.
      +
      JavaObjectSerializergetJavaObjectSerializer() +
      Returns the custom Java object serializer, or null.
      +
      java.sql.DatabaseMetaDatagetMetaData() +
      Gets the database meta data for this database.
      +
      ModegetMode() +
      Returns the database mode.
      +
      intgetNetworkTimeout() +
      [Not supported]
      +
      java.lang.StringgetSchema() +
      Retrieves this current schema name for this connection.
      +
      SessiongetSession() +
      INTERNAL
      +
      Session.StaticSettingsgetStaticSettings() +
      INTERNAL
      +
      intgetTransactionIsolation() +
      Returns the current transaction isolation level.
      +
      java.util.Map<java.lang.String,java.lang.Class<?>>getTypeMap() +
      Gets the type map.
      +
      java.sql.SQLWarninggetWarnings() +
      Gets the first warning reported by calls on this object.
      +
      booleanisClosed() +
      Returns true if this connection has been closed.
      +
      booleanisReadOnly() +
      Returns true if the database is read-only.
      +
      booleanisValid(int timeout) +
      Returns true if this connection is still valid.
      +
      booleanisWrapperFor(java.lang.Class<?> iface) +
      Checks if unwrap can return an object of this class.
      +
      java.lang.StringnativeSQL(java.lang.String sql) +
      Translates a SQL statement into the database grammar.
      +
      java.sql.CallableStatementprepareCall(java.lang.String sql) +
      Creates a new callable statement.
      +
      java.sql.CallableStatementprepareCall(java.lang.String sql, + int resultSetType, + int resultSetConcurrency) +
      Creates a callable statement with the specified result set type and + concurrency.
      +
      java.sql.CallableStatementprepareCall(java.lang.String sql, + int resultSetType, + int resultSetConcurrency, + int resultSetHoldability) +
      Creates a callable statement with the specified result set type, + concurrency, and holdability.
      +
      java.sql.PreparedStatementprepareStatement(java.lang.String sql) +
      Creates a new prepared statement.
      +
      java.sql.PreparedStatementprepareStatement(java.lang.String sql, + int autoGeneratedKeys) +
      Creates a new prepared statement.
      +
      java.sql.PreparedStatementprepareStatement(java.lang.String sql, + int[] columnIndexes) +
      Creates a new prepared statement.
      +
      java.sql.PreparedStatementprepareStatement(java.lang.String sql, + int resultSetType, + int resultSetConcurrency) +
      Creates a prepared statement with the specified result set type and + concurrency.
      +
      java.sql.PreparedStatementprepareStatement(java.lang.String sql, + int resultSetType, + int resultSetConcurrency, + int resultSetHoldability) +
      Creates a prepared statement with the specified result set type, + concurrency, and holdability.
      +
      java.sql.PreparedStatementprepareStatement(java.lang.String sql, + java.lang.String[] columnNames) +
      Creates a new prepared statement.
      +
      voidreleaseSavepoint(java.sql.Savepoint savepoint) +
      Releases a savepoint.
      +
      voidrollback() +
      Rolls back the current transaction.
      +
      voidrollback(java.sql.Savepoint savepoint) +
      Rolls back to a savepoint.
      +
      voidsetAutoCommit(boolean autoCommit) +
      Switches auto commit on or off.
      +
      voidsetCatalog(java.lang.String catalog) +
      Set the default catalog name.
      +
      voidsetClientInfo(java.util.Properties properties) +
      Set the client properties.
      +
      voidsetClientInfo(java.lang.String name, + java.lang.String value) +
      Set a client property.
      +
      voidsetHoldability(int holdability) +
      Changes the current result set holdability.
      +
      voidsetNetworkTimeout(java.util.concurrent.Executor executor, + int milliseconds) +
      [Not supported]
      +
      voidsetReadOnly(boolean readOnly) +
      According to the JDBC specs, this setting is only a hint to the database + to enable optimizations - it does not cause writes to be prohibited.
      +
      java.sql.SavepointsetSavepoint() +
      Creates a new unnamed savepoint.
      +
      java.sql.SavepointsetSavepoint(java.lang.String name) +
      Creates a new named savepoint.
      +
      voidsetSchema(java.lang.String schema) +
      Sets the given schema name to access.
      +
      voidsetTransactionIsolation(int level) +
      Changes the current transaction isolation level.
      +
      voidsetTypeMap(java.util.Map<java.lang.String,java.lang.Class<?>> map) +
      [Partially supported] Sets the type map.
      +
      java.lang.StringtoString() +
      INTERNAL
      +
      <T> Tunwrap(java.lang.Class<T> iface) +
      Return an object of this class if possible.
      +
      booleanzeroBasedEnums() +
      Returns are ENUM values 0-based.
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.message.TraceObject

        +debugCode, debugCodeAssign, debugCodeCall, debugCodeCall, debugCodeCall, getNextId, getTraceId, getTraceObjectName, isDebugEnabled, isInfoEnabled, logAndConvert, quote, quoteArray, quoteBigDecimal, quoteBytes, quoteDate, quoteIntArray, quoteMap, quoteTime, quoteTimestamp, setTrace, unsupported
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        JdbcConnection

        +
        public JdbcConnection(java.lang.String url,
        +                      java.util.Properties info,
        +                      java.lang.String user,
        +                      java.lang.Object password,
        +                      boolean forbidCreation)
        +               throws java.sql.SQLException
        +
        INTERNAL + the session closable object does not leak as Eclipse warns - due to the + CloseWatcher.
        +
        +
        Parameters:
        +
        url - of this connection
        +
        info - of this connection
        +
        user - of this connection
        +
        password - for the user
        +
        forbidCreation - whether database creation is forbidden
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        JdbcConnection

        +
        public JdbcConnection(JdbcConnection clone)
        +
        INTERNAL
        +
        +
        Parameters:
        +
        clone - connection to clone
        +
        +
      • +
      + + + +
        +
      • +

        JdbcConnection

        +
        public JdbcConnection(Session session,
        +                      java.lang.String user,
        +                      java.lang.String url)
        +
        INTERNAL
        +
        +
        Parameters:
        +
        session - of this connection
        +
        user - of this connection
        +
        url - of this connection
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        createStatement

        +
        public java.sql.Statement createStatement()
        +                                   throws java.sql.SQLException
        +
        Creates a new statement.
        +
        +
        Specified by:
        +
        createStatement in interface java.sql.Connection
        +
        Returns:
        +
        the new statement
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        createStatement

        +
        public java.sql.Statement createStatement(int resultSetType,
        +                                          int resultSetConcurrency)
        +                                   throws java.sql.SQLException
        +
        Creates a statement with the specified result set type and concurrency.
        +
        +
        Specified by:
        +
        createStatement in interface java.sql.Connection
        +
        Parameters:
        +
        resultSetType - the result set type (ResultSet.TYPE_*)
        +
        resultSetConcurrency - the concurrency (ResultSet.CONCUR_*)
        +
        Returns:
        +
        the statement
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed or the result set type + or concurrency are not supported
        +
        +
      • +
      + + + +
        +
      • +

        createStatement

        +
        public java.sql.Statement createStatement(int resultSetType,
        +                                          int resultSetConcurrency,
        +                                          int resultSetHoldability)
        +                                   throws java.sql.SQLException
        +
        Creates a statement with the specified result set type, concurrency, and + holdability.
        +
        +
        Specified by:
        +
        createStatement in interface java.sql.Connection
        +
        Parameters:
        +
        resultSetType - the result set type (ResultSet.TYPE_*)
        +
        resultSetConcurrency - the concurrency (ResultSet.CONCUR_*)
        +
        resultSetHoldability - the holdability (ResultSet.HOLD* / CLOSE*)
        +
        Returns:
        +
        the statement
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed or the result set type, + concurrency, or holdability are not supported
        +
        +
      • +
      + + + +
        +
      • +

        prepareStatement

        +
        public java.sql.PreparedStatement prepareStatement(java.lang.String sql)
        +                                            throws java.sql.SQLException
        +
        Creates a new prepared statement.
        +
        +
        Specified by:
        +
        prepareStatement in interface java.sql.Connection
        +
        Parameters:
        +
        sql - the SQL statement
        +
        Returns:
        +
        the prepared statement
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        getMetaData

        +
        public java.sql.DatabaseMetaData getMetaData()
        +                                      throws java.sql.SQLException
        +
        Gets the database meta data for this database.
        +
        +
        Specified by:
        +
        getMetaData in interface java.sql.Connection
        +
        Returns:
        +
        the database meta data
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        getSession

        +
        public Session getSession()
        +
        INTERNAL
        +
        +
        Returns:
        +
        session
        +
        +
      • +
      + + + +
        +
      • +

        close

        +
        public void close()
        +           throws java.sql.SQLException
        +
        Closes this connection. All open statements, prepared statements and + result sets that where created by this connection become invalid after + calling this method. If there is an uncommitted transaction, it will be + rolled back.
        +
        +
        Specified by:
        +
        close in interface java.lang.AutoCloseable
        +
        Specified by:
        +
        close in interface java.sql.Connection
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        setAutoCommit

        +
        public void setAutoCommit(boolean autoCommit)
        +                   throws java.sql.SQLException
        +
        Switches auto commit on or off. Enabling it commits an uncommitted + transaction, if there is one.
        +
        +
        Specified by:
        +
        setAutoCommit in interface java.sql.Connection
        +
        Parameters:
        +
        autoCommit - true for auto commit on, false for off
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        getAutoCommit

        +
        public boolean getAutoCommit()
        +                      throws java.sql.SQLException
        +
        Gets the current setting for auto commit.
        +
        +
        Specified by:
        +
        getAutoCommit in interface java.sql.Connection
        +
        Returns:
        +
        true for on, false for off
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        commit

        +
        public void commit()
        +            throws java.sql.SQLException
        +
        Commits the current transaction. This call has only an effect if auto + commit is switched off.
        +
        +
        Specified by:
        +
        commit in interface java.sql.Connection
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        rollback

        +
        public void rollback()
        +              throws java.sql.SQLException
        +
        Rolls back the current transaction. This call has only an effect if auto + commit is switched off.
        +
        +
        Specified by:
        +
        rollback in interface java.sql.Connection
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        isClosed

        +
        public boolean isClosed()
        +                 throws java.sql.SQLException
        +
        Returns true if this connection has been closed.
        +
        +
        Specified by:
        +
        isClosed in interface java.sql.Connection
        +
        Returns:
        +
        true if close was called
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        nativeSQL

        +
        public java.lang.String nativeSQL(java.lang.String sql)
        +                           throws java.sql.SQLException
        +
        Translates a SQL statement into the database grammar.
        +
        +
        Specified by:
        +
        nativeSQL in interface java.sql.Connection
        +
        Parameters:
        +
        sql - the SQL statement with or without JDBC escape sequences
        +
        Returns:
        +
        the translated statement
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        setReadOnly

        +
        public void setReadOnly(boolean readOnly)
        +                 throws java.sql.SQLException
        +
        According to the JDBC specs, this setting is only a hint to the database + to enable optimizations - it does not cause writes to be prohibited.
        +
        +
        Specified by:
        +
        setReadOnly in interface java.sql.Connection
        +
        Parameters:
        +
        readOnly - ignored
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        isReadOnly

        +
        public boolean isReadOnly()
        +                   throws java.sql.SQLException
        +
        Returns true if the database is read-only.
        +
        +
        Specified by:
        +
        isReadOnly in interface java.sql.Connection
        +
        Returns:
        +
        if the database is read-only
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        setCatalog

        +
        public void setCatalog(java.lang.String catalog)
        +                throws java.sql.SQLException
        +
        Set the default catalog name. This call is ignored.
        +
        +
        Specified by:
        +
        setCatalog in interface java.sql.Connection
        +
        Parameters:
        +
        catalog - ignored
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        getCatalog

        +
        public java.lang.String getCatalog()
        +                            throws java.sql.SQLException
        +
        Gets the current catalog name.
        +
        +
        Specified by:
        +
        getCatalog in interface java.sql.Connection
        +
        Returns:
        +
        the catalog name
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        getWarnings

        +
        public java.sql.SQLWarning getWarnings()
        +                                throws java.sql.SQLException
        +
        Gets the first warning reported by calls on this object.
        +
        +
        Specified by:
        +
        getWarnings in interface java.sql.Connection
        +
        Returns:
        +
        null
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        clearWarnings

        +
        public void clearWarnings()
        +                   throws java.sql.SQLException
        +
        Clears all warnings.
        +
        +
        Specified by:
        +
        clearWarnings in interface java.sql.Connection
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        prepareStatement

        +
        public java.sql.PreparedStatement prepareStatement(java.lang.String sql,
        +                                                   int resultSetType,
        +                                                   int resultSetConcurrency)
        +                                            throws java.sql.SQLException
        +
        Creates a prepared statement with the specified result set type and + concurrency.
        +
        +
        Specified by:
        +
        prepareStatement in interface java.sql.Connection
        +
        Parameters:
        +
        sql - the SQL statement
        +
        resultSetType - the result set type (ResultSet.TYPE_*)
        +
        resultSetConcurrency - the concurrency (ResultSet.CONCUR_*)
        +
        Returns:
        +
        the prepared statement
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed or the result set type + or concurrency are not supported
        +
        +
      • +
      + + + +
        +
      • +

        setTransactionIsolation

        +
        public void setTransactionIsolation(int level)
        +                             throws java.sql.SQLException
        +
        Changes the current transaction isolation level. Calling this method will + commit an open transaction, even if the new level is the same as the old + one.
        +
        +
        Specified by:
        +
        setTransactionIsolation in interface java.sql.Connection
        +
        Parameters:
        +
        level - the new transaction isolation level: + Connection.TRANSACTION_READ_UNCOMMITTED, + Connection.TRANSACTION_READ_COMMITTED, + Connection.TRANSACTION_REPEATABLE_READ, + 6 (SNAPSHOT), or + Connection.TRANSACTION_SERIALIZABLE
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed or the isolation level + is not valid
        +
        +
      • +
      + + + +
        +
      • +

        getTransactionIsolation

        +
        public int getTransactionIsolation()
        +                            throws java.sql.SQLException
        +
        Returns the current transaction isolation level.
        +
        +
        Specified by:
        +
        getTransactionIsolation in interface java.sql.Connection
        +
        Returns:
        +
        the isolation level
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        setHoldability

        +
        public void setHoldability(int holdability)
        +                    throws java.sql.SQLException
        +
        Changes the current result set holdability.
        +
        +
        Specified by:
        +
        setHoldability in interface java.sql.Connection
        +
        Parameters:
        +
        holdability - ResultSet.HOLD_CURSORS_OVER_COMMIT or + ResultSet.CLOSE_CURSORS_AT_COMMIT;
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed or the holdability is + not supported
        +
        +
      • +
      + + + +
        +
      • +

        getHoldability

        +
        public int getHoldability()
        +                   throws java.sql.SQLException
        +
        Returns the current result set holdability.
        +
        +
        Specified by:
        +
        getHoldability in interface java.sql.Connection
        +
        Returns:
        +
        the holdability
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        getTypeMap

        +
        public java.util.Map<java.lang.String,java.lang.Class<?>> getTypeMap()
        +                                                              throws java.sql.SQLException
        +
        Gets the type map.
        +
        +
        Specified by:
        +
        getTypeMap in interface java.sql.Connection
        +
        Returns:
        +
        null
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        setTypeMap

        +
        public void setTypeMap(java.util.Map<java.lang.String,java.lang.Class<?>> map)
        +                throws java.sql.SQLException
        +
        [Partially supported] Sets the type map. This is only supported if the + map is empty or null.
        +
        +
        Specified by:
        +
        setTypeMap in interface java.sql.Connection
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        prepareCall

        +
        public java.sql.CallableStatement prepareCall(java.lang.String sql)
        +                                       throws java.sql.SQLException
        +
        Creates a new callable statement.
        +
        +
        Specified by:
        +
        prepareCall in interface java.sql.Connection
        +
        Parameters:
        +
        sql - the SQL statement
        +
        Returns:
        +
        the callable statement
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed or the statement is not + valid
        +
        +
      • +
      + + + +
        +
      • +

        prepareCall

        +
        public java.sql.CallableStatement prepareCall(java.lang.String sql,
        +                                              int resultSetType,
        +                                              int resultSetConcurrency)
        +                                       throws java.sql.SQLException
        +
        Creates a callable statement with the specified result set type and + concurrency.
        +
        +
        Specified by:
        +
        prepareCall in interface java.sql.Connection
        +
        Parameters:
        +
        sql - the SQL statement
        +
        resultSetType - the result set type (ResultSet.TYPE_*)
        +
        resultSetConcurrency - the concurrency (ResultSet.CONCUR_*)
        +
        Returns:
        +
        the callable statement
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed or the result set type + or concurrency are not supported
        +
        +
      • +
      + + + +
        +
      • +

        prepareCall

        +
        public java.sql.CallableStatement prepareCall(java.lang.String sql,
        +                                              int resultSetType,
        +                                              int resultSetConcurrency,
        +                                              int resultSetHoldability)
        +                                       throws java.sql.SQLException
        +
        Creates a callable statement with the specified result set type, + concurrency, and holdability.
        +
        +
        Specified by:
        +
        prepareCall in interface java.sql.Connection
        +
        Parameters:
        +
        sql - the SQL statement
        +
        resultSetType - the result set type (ResultSet.TYPE_*)
        +
        resultSetConcurrency - the concurrency (ResultSet.CONCUR_*)
        +
        resultSetHoldability - the holdability (ResultSet.HOLD* / CLOSE*)
        +
        Returns:
        +
        the callable statement
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed or the result set type, + concurrency, or holdability are not supported
        +
        +
      • +
      + + + +
        +
      • +

        setSavepoint

        +
        public java.sql.Savepoint setSavepoint()
        +                                throws java.sql.SQLException
        +
        Creates a new unnamed savepoint.
        +
        +
        Specified by:
        +
        setSavepoint in interface java.sql.Connection
        +
        Returns:
        +
        the new savepoint
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        setSavepoint

        +
        public java.sql.Savepoint setSavepoint(java.lang.String name)
        +                                throws java.sql.SQLException
        +
        Creates a new named savepoint.
        +
        +
        Specified by:
        +
        setSavepoint in interface java.sql.Connection
        +
        Parameters:
        +
        name - the savepoint name
        +
        Returns:
        +
        the new savepoint
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        rollback

        +
        public void rollback(java.sql.Savepoint savepoint)
        +              throws java.sql.SQLException
        +
        Rolls back to a savepoint.
        +
        +
        Specified by:
        +
        rollback in interface java.sql.Connection
        +
        Parameters:
        +
        savepoint - the savepoint
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        releaseSavepoint

        +
        public void releaseSavepoint(java.sql.Savepoint savepoint)
        +                      throws java.sql.SQLException
        +
        Releases a savepoint.
        +
        +
        Specified by:
        +
        releaseSavepoint in interface java.sql.Connection
        +
        Parameters:
        +
        savepoint - the savepoint to release
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        prepareStatement

        +
        public java.sql.PreparedStatement prepareStatement(java.lang.String sql,
        +                                                   int resultSetType,
        +                                                   int resultSetConcurrency,
        +                                                   int resultSetHoldability)
        +                                            throws java.sql.SQLException
        +
        Creates a prepared statement with the specified result set type, + concurrency, and holdability.
        +
        +
        Specified by:
        +
        prepareStatement in interface java.sql.Connection
        +
        Parameters:
        +
        sql - the SQL statement
        +
        resultSetType - the result set type (ResultSet.TYPE_*)
        +
        resultSetConcurrency - the concurrency (ResultSet.CONCUR_*)
        +
        resultSetHoldability - the holdability (ResultSet.HOLD* / CLOSE*)
        +
        Returns:
        +
        the prepared statement
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed or the result set type, + concurrency, or holdability are not supported
        +
        +
      • +
      + + + +
        +
      • +

        prepareStatement

        +
        public java.sql.PreparedStatement prepareStatement(java.lang.String sql,
        +                                                   int autoGeneratedKeys)
        +                                            throws java.sql.SQLException
        +
        Creates a new prepared statement.
        +
        +
        Specified by:
        +
        prepareStatement in interface java.sql.Connection
        +
        Parameters:
        +
        sql - the SQL statement
        +
        autoGeneratedKeys - Statement.RETURN_GENERATED_KEYS if generated keys should + be available for retrieval, Statement.NO_GENERATED_KEYS if + generated keys should not be available
        +
        Returns:
        +
        the prepared statement
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        prepareStatement

        +
        public java.sql.PreparedStatement prepareStatement(java.lang.String sql,
        +                                                   int[] columnIndexes)
        +                                            throws java.sql.SQLException
        +
        Creates a new prepared statement.
        +
        +
        Specified by:
        +
        prepareStatement in interface java.sql.Connection
        +
        Parameters:
        +
        sql - the SQL statement
        +
        columnIndexes - an array of column indexes indicating the columns with generated + keys that should be returned from the inserted row
        +
        Returns:
        +
        the prepared statement
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        prepareStatement

        +
        public java.sql.PreparedStatement prepareStatement(java.lang.String sql,
        +                                                   java.lang.String[] columnNames)
        +                                            throws java.sql.SQLException
        +
        Creates a new prepared statement.
        +
        +
        Specified by:
        +
        prepareStatement in interface java.sql.Connection
        +
        Parameters:
        +
        sql - the SQL statement
        +
        columnNames - an array of column names indicating the columns with generated + keys that should be returned from the inserted row
        +
        Returns:
        +
        the prepared statement
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        checkClosed

        +
        protected void checkClosed()
        +
        INTERNAL. Check if this connection is closed.
        +
        +
        Throws:
        +
        org.h2.message.DbException - if the connection or session is closed
        +
        +
      • +
      + + + +
        +
      • +

        createClob

        +
        public java.sql.Clob createClob()
        +                         throws java.sql.SQLException
        +
        Create a new empty Clob object.
        +
        +
        Specified by:
        +
        createClob in interface java.sql.Connection
        +
        Returns:
        +
        the object
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        createBlob

        +
        public java.sql.Blob createBlob()
        +                         throws java.sql.SQLException
        +
        Create a new empty Blob object.
        +
        +
        Specified by:
        +
        createBlob in interface java.sql.Connection
        +
        Returns:
        +
        the object
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        createNClob

        +
        public java.sql.NClob createNClob()
        +                           throws java.sql.SQLException
        +
        Create a new empty NClob object.
        +
        +
        Specified by:
        +
        createNClob in interface java.sql.Connection
        +
        Returns:
        +
        the object
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        createSQLXML

        +
        public java.sql.SQLXML createSQLXML()
        +                             throws java.sql.SQLException
        +
        Create a new SQLXML object with no data.
        +
        +
        Specified by:
        +
        createSQLXML in interface java.sql.Connection
        +
        Returns:
        +
        the object
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        createArrayOf

        +
        public java.sql.Array createArrayOf(java.lang.String typeName,
        +                                    java.lang.Object[] elements)
        +                             throws java.sql.SQLException
        +
        Create a new Array object.
        +
        +
        Specified by:
        +
        createArrayOf in interface java.sql.Connection
        +
        Parameters:
        +
        typeName - the type name
        +
        elements - the values
        +
        Returns:
        +
        the array
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        createStruct

        +
        public java.sql.Struct createStruct(java.lang.String typeName,
        +                                    java.lang.Object[] attributes)
        +                             throws java.sql.SQLException
        +
        [Not supported] Create a new empty Struct object.
        +
        +
        Specified by:
        +
        createStruct in interface java.sql.Connection
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        isValid

        +
        public boolean isValid(int timeout)
        +
        Returns true if this connection is still valid.
        +
        +
        Specified by:
        +
        isValid in interface java.sql.Connection
        +
        Parameters:
        +
        timeout - the number of seconds to wait for the database to respond + (ignored)
        +
        Returns:
        +
        true if the connection is valid.
        +
        +
      • +
      + + + +
        +
      • +

        setClientInfo

        +
        public void setClientInfo(java.lang.String name,
        +                          java.lang.String value)
        +                   throws java.sql.SQLClientInfoException
        +
        Set a client property. This method always throws a SQLClientInfoException + in standard mode. In compatibility mode the following properties are + supported: +
          +
        • DB2: The properties: ApplicationName, ClientAccountingInformation, + ClientUser and ClientCorrelationToken are supported.
        • +
        • MySQL: All property names are supported.
        • +
        • Oracle: All properties in the form <namespace>.<key name> + are supported.
        • +
        • PostgreSQL: The ApplicationName property is supported.
        • +
        + For unsupported properties a SQLClientInfoException is thrown.
        +
        +
        Specified by:
        +
        setClientInfo in interface java.sql.Connection
        +
        Parameters:
        +
        name - the name of the property
        +
        value - the value
        +
        Throws:
        +
        java.sql.SQLClientInfoException
        +
        +
      • +
      + + + +
        +
      • +

        setClientInfo

        +
        public void setClientInfo(java.util.Properties properties)
        +                   throws java.sql.SQLClientInfoException
        +
        Set the client properties. This replaces all existing properties. This + method always throws a SQLClientInfoException in standard mode. In + compatibility mode some properties may be supported (see + setProperty(String, String) for details).
        +
        +
        Specified by:
        +
        setClientInfo in interface java.sql.Connection
        +
        Parameters:
        +
        properties - the properties (ignored)
        +
        Throws:
        +
        java.sql.SQLClientInfoException
        +
        +
      • +
      + + + +
        +
      • +

        getClientInfo

        +
        public java.util.Properties getClientInfo()
        +                                   throws java.sql.SQLException
        +
        Get the client properties.
        +
        +
        Specified by:
        +
        getClientInfo in interface java.sql.Connection
        +
        Returns:
        +
        the property list
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getClientInfo

        +
        public java.lang.String getClientInfo(java.lang.String name)
        +                               throws java.sql.SQLException
        +
        Get a client property.
        +
        +
        Specified by:
        +
        getClientInfo in interface java.sql.Connection
        +
        Parameters:
        +
        name - the client info name
        +
        Returns:
        +
        the property value or null if the property is not found or not + supported.
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        unwrap

        +
        public <T> T unwrap(java.lang.Class<T> iface)
        +             throws java.sql.SQLException
        +
        Return an object of this class if possible.
        +
        +
        Specified by:
        +
        unwrap in interface java.sql.Wrapper
        +
        Parameters:
        +
        iface - the class
        +
        Returns:
        +
        this
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        isWrapperFor

        +
        public boolean isWrapperFor(java.lang.Class<?> iface)
        +                     throws java.sql.SQLException
        +
        Checks if unwrap can return an object of this class.
        +
        +
        Specified by:
        +
        isWrapperFor in interface java.sql.Wrapper
        +
        Parameters:
        +
        iface - the class
        +
        Returns:
        +
        whether or not the interface is assignable from this class
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        setSchema

        +
        public void setSchema(java.lang.String schema)
        +               throws java.sql.SQLException
        +
        Sets the given schema name to access. Current implementation is case + sensitive, i.e. requires schema name to be passed in correct case.
        +
        +
        Specified by:
        +
        setSchema in interface java.sql.Connection
        +
        Parameters:
        +
        schema - the schema name
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getSchema

        +
        public java.lang.String getSchema()
        +                           throws java.sql.SQLException
        +
        Retrieves this current schema name for this connection.
        +
        +
        Specified by:
        +
        getSchema in interface java.sql.Connection
        +
        Returns:
        +
        current schema name
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        abort

        +
        public void abort(java.util.concurrent.Executor executor)
        +
        [Not supported]
        +
        +
        Specified by:
        +
        abort in interface java.sql.Connection
        +
        Parameters:
        +
        executor - the executor used by this method
        +
        +
      • +
      + + + +
        +
      • +

        setNetworkTimeout

        +
        public void setNetworkTimeout(java.util.concurrent.Executor executor,
        +                              int milliseconds)
        +
        [Not supported]
        +
        +
        Specified by:
        +
        setNetworkTimeout in interface java.sql.Connection
        +
        Parameters:
        +
        executor - the executor used by this method
        +
        milliseconds - the TCP connection timeout
        +
        +
      • +
      + + + +
        +
      • +

        getNetworkTimeout

        +
        public int getNetworkTimeout()
        +
        [Not supported]
        +
        +
        Specified by:
        +
        getNetworkTimeout in interface java.sql.Connection
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        INTERNAL
        +
        +
        Overrides:
        +
        toString in class java.lang.Object
        +
        +
      • +
      + + + + + + + +
        +
      • +

        getStaticSettings

        +
        public Session.StaticSettings getStaticSettings()
        +
        INTERNAL
        +
        +
        Returns:
        +
        StaticSettings
        +
        +
      • +
      + + + +
        +
      • +

        currentTimestamp

        +
        public org.h2.value.ValueTimestampTimeZone currentTimestamp()
        +
        Description copied from interface: CastDataProvider
        +
        Returns the current timestamp with maximum resolution. The value must be + the same within a transaction or within execution of a command.
        +
        +
        Specified by:
        +
        currentTimestamp in interface CastDataProvider
        +
        Returns:
        +
        the current timestamp for CURRENT_TIMESTAMP(9)
        +
        +
      • +
      + + + +
        +
      • +

        currentTimeZone

        +
        public org.h2.util.TimeZoneProvider currentTimeZone()
        +
        Description copied from interface: CastDataProvider
        +
        Returns the current time zone.
        +
        +
        Specified by:
        +
        currentTimeZone in interface CastDataProvider
        +
        Returns:
        +
        the current time zone
        +
        +
      • +
      + + + + + + + +
        +
      • +

        zeroBasedEnums

        +
        public boolean zeroBasedEnums()
        +
        Description copied from interface: CastDataProvider
        +
        Returns are ENUM values 0-based.
        +
        +
        Specified by:
        +
        zeroBasedEnums in interface CastDataProvider
        +
        Returns:
        +
        are ENUM values 0-based
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcConnectionBackwardsCompat.html b/h2/docs/javadoc/org/h2/jdbc/JdbcConnectionBackwardsCompat.html new file mode 100644 index 0000000..33196e2 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcConnectionBackwardsCompat.html @@ -0,0 +1,171 @@ + + + + + +JdbcConnectionBackwardsCompat + + + + + + + + + + + + +
+
org.h2.jdbc
+

Interface JdbcConnectionBackwardsCompat

+
+
+
+
    +
  • +
    +
    All Known Implementing Classes:
    +
    JdbcConnection
    +
    +
    +
    +
    public interface JdbcConnectionBackwardsCompat
    +
    Allows us to compile on older platforms, while still implementing the methods + from the newer JDBC API.
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcDatabaseMetaData.html b/h2/docs/javadoc/org/h2/jdbc/JdbcDatabaseMetaData.html new file mode 100644 index 0000000..8e9efcd --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcDatabaseMetaData.html @@ -0,0 +1,4948 @@ + + + + + +JdbcDatabaseMetaData + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcDatabaseMetaData

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.message.TraceObject
    • +
    • +
        +
      • org.h2.jdbc.JdbcDatabaseMetaData
      • +
      +
    • +
    +
  • +
+
+ +
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.message.TraceObject

        +ARRAY, BLOB, CALLABLE_STATEMENT, CLOB, CONNECTION, DATA_SOURCE, DATABASE_META_DATA, PARAMETER_META_DATA, PREPARED_STATEMENT, RESULT_SET, RESULT_SET_META_DATA, SAVEPOINT, SQLXML, STATEMENT, trace, XA_DATA_SOURCE, XID
      • +
      +
        +
      • + + +

        Fields inherited from interface java.sql.DatabaseMetaData

        +attributeNoNulls, attributeNullable, attributeNullableUnknown, bestRowNotPseudo, bestRowPseudo, bestRowSession, bestRowTemporary, bestRowTransaction, bestRowUnknown, columnNoNulls, columnNullable, columnNullableUnknown, functionColumnIn, functionColumnInOut, functionColumnOut, functionColumnResult, functionColumnUnknown, functionNoNulls, functionNoTable, functionNullable, functionNullableUnknown, functionResultUnknown, functionReturn, functionReturnsTable, importedKeyCascade, importedKeyInitiallyDeferred, importedKeyInitiallyImmediate, importedKeyNoAction, importedKeyNotDeferrable, importedKeyRestrict, importedKeySetDefault, importedKeySetNull, procedureColumnIn, procedureColumnInOut, procedureColumnOut, procedureColumnResult, procedureColumnReturn, procedureColumnUnknown, procedureNoNulls, procedureNoResult, procedureNullable, procedureNullableUnknown, procedureResultUnknown, procedureReturnsResult, sqlStateSQL, sqlStateSQL99, sqlStateXOpen, tableIndexClustered, tableIndexHashed, tableIndexOther, tableIndexStatistic, typeNoNulls, typeNullable, typeNullableUnknown, typePredBasic, typePredChar, typePredNone, typeSearchable, versionColumnNotPseudo, versionColumnPseudo, versionColumnUnknown
      • +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      booleanallProceduresAreCallable() +
      Checks if all procedures callable.
      +
      booleanallTablesAreSelectable() +
      Checks if it possible to query all tables returned by getTables.
      +
      booleanautoCommitFailureClosesAllResultSets() +
      Returns whether an exception while auto commit is on closes all result + sets.
      +
      booleandataDefinitionCausesTransactionCommit() +
      Returns whether CREATE/DROP commit an open transaction.
      +
      booleandataDefinitionIgnoredInTransactions() +
      Returns whether CREATE/DROP do not affect transactions.
      +
      booleandeletesAreDetected(int type) +
      Returns whether deletes are detected.
      +
      booleandoesMaxRowSizeIncludeBlobs() +
      Returns whether the maximum row size includes blobs.
      +
      booleangeneratedKeyAlwaysReturned() +
      Returns whether database always returns generated keys if valid names or + indexes of columns were specified and command was completed successfully.
      +
      java.sql.ResultSetgetAttributes(java.lang.String catalog, + java.lang.String schemaPattern, + java.lang.String typeNamePattern, + java.lang.String attributeNamePattern) +
      [Not supported]
      +
      java.sql.ResultSetgetBestRowIdentifier(java.lang.String catalog, + java.lang.String schema, + java.lang.String table, + int scope, + boolean nullable) +
      Gets the list of columns that best identifier a row in a table.
      +
      java.sql.ResultSetgetCatalogs() +
      Gets the list of catalogs.
      +
      java.lang.StringgetCatalogSeparator() +
      Returns the catalog separator.
      +
      java.lang.StringgetCatalogTerm() +
      Returns the term for "catalog".
      +
      java.sql.ResultSetgetClientInfoProperties() 
      java.sql.ResultSetgetColumnPrivileges(java.lang.String catalog, + java.lang.String schema, + java.lang.String table, + java.lang.String columnNamePattern) +
      Gets the list of column privileges.
      +
      java.sql.ResultSetgetColumns(java.lang.String catalog, + java.lang.String schemaPattern, + java.lang.String tableNamePattern, + java.lang.String columnNamePattern) +
      Gets the list of columns.
      +
      java.sql.ConnectiongetConnection() +
      Returns the connection that created this object.
      +
      java.sql.ResultSetgetCrossReference(java.lang.String primaryCatalog, + java.lang.String primarySchema, + java.lang.String primaryTable, + java.lang.String foreignCatalog, + java.lang.String foreignSchema, + java.lang.String foreignTable) +
      Gets the list of foreign key columns that references a table, as well as + the list of primary key columns that are references by a table.
      +
      intgetDatabaseMajorVersion() +
      Gets the major version of the database.
      +
      intgetDatabaseMinorVersion() +
      Gets the minor version of the database.
      +
      java.lang.StringgetDatabaseProductName() +
      Gets the database product name.
      +
      java.lang.StringgetDatabaseProductVersion() +
      Gets the product version of the database.
      +
      intgetDefaultTransactionIsolation() +
      Returns the default transaction isolation level.
      +
      intgetDriverMajorVersion() +
      Returns the major version of this driver.
      +
      intgetDriverMinorVersion() +
      Returns the minor version of this driver.
      +
      java.lang.StringgetDriverName() +
      Gets the name of the JDBC driver.
      +
      java.lang.StringgetDriverVersion() +
      Gets the version number of the driver.
      +
      java.sql.ResultSetgetExportedKeys(java.lang.String catalog, + java.lang.String schema, + java.lang.String table) +
      Gets the list of foreign key columns that reference a table.
      +
      java.lang.StringgetExtraNameCharacters() +
      Returns the characters that are allowed for identifiers in addiction to + A-Z, a-z, 0-9 and '_'.
      +
      java.sql.ResultSetgetFunctionColumns(java.lang.String catalog, + java.lang.String schemaPattern, + java.lang.String functionNamePattern, + java.lang.String columnNamePattern) +
      [Not supported] Gets the list of function columns.
      +
      java.sql.ResultSetgetFunctions(java.lang.String catalog, + java.lang.String schemaPattern, + java.lang.String functionNamePattern) +
      [Not supported] Gets the list of functions.
      +
      java.lang.StringgetIdentifierQuoteString() +
      Returns the string used to quote identifiers.
      +
      java.sql.ResultSetgetImportedKeys(java.lang.String catalog, + java.lang.String schema, + java.lang.String table) +
      Gets the list of primary key columns that are referenced by a table.
      +
      java.sql.ResultSetgetIndexInfo(java.lang.String catalog, + java.lang.String schema, + java.lang.String table, + boolean unique, + boolean approximate) +
      Gets the list of indexes for this database.
      +
      intgetJDBCMajorVersion() +
      Gets the major version of the supported JDBC API.
      +
      intgetJDBCMinorVersion() +
      Gets the minor version of the supported JDBC API.
      +
      intgetMaxBinaryLiteralLength() +
      Returns the maximum length for hex values (characters).
      +
      intgetMaxCatalogNameLength() +
      Returns the maximum length for a catalog name.
      +
      intgetMaxCharLiteralLength() +
      Returns the maximum length for literals.
      +
      intgetMaxColumnNameLength() +
      Returns the maximum length for column names.
      +
      intgetMaxColumnsInGroupBy() +
      Returns the maximum number of columns in GROUP BY.
      +
      intgetMaxColumnsInIndex() +
      Returns the maximum number of columns in CREATE INDEX.
      +
      intgetMaxColumnsInOrderBy() +
      Returns the maximum number of columns in ORDER BY.
      +
      intgetMaxColumnsInSelect() +
      Returns the maximum number of columns in SELECT.
      +
      intgetMaxColumnsInTable() +
      Returns the maximum number of columns in CREATE TABLE.
      +
      intgetMaxConnections() +
      Returns the maximum number of open connection.
      +
      intgetMaxCursorNameLength() +
      Returns the maximum length for a cursor name.
      +
      intgetMaxIndexLength() +
      Returns the maximum length for an index (in bytes).
      +
      intgetMaxProcedureNameLength() +
      Returns the maximum length for a procedure name.
      +
      intgetMaxRowSize() +
      Returns the maximum size of a row (in bytes).
      +
      intgetMaxSchemaNameLength() +
      Returns the maximum length for a schema name.
      +
      intgetMaxStatementLength() +
      Returns the maximum length of a statement.
      +
      intgetMaxStatements() +
      Returns the maximum number of open statements.
      +
      intgetMaxTableNameLength() +
      Returns the maximum length for a table name.
      +
      intgetMaxTablesInSelect() +
      Returns the maximum number of tables in a SELECT.
      +
      intgetMaxUserNameLength() +
      Returns the maximum length for a user name.
      +
      java.lang.StringgetNumericFunctions() +
      Returns the list of numeric functions supported by this database.
      +
      java.sql.ResultSetgetPrimaryKeys(java.lang.String catalog, + java.lang.String schema, + java.lang.String table) +
      Gets the primary key columns for a table.
      +
      java.sql.ResultSetgetProcedureColumns(java.lang.String catalog, + java.lang.String schemaPattern, + java.lang.String procedureNamePattern, + java.lang.String columnNamePattern) +
      Gets the list of procedure columns.
      +
      java.sql.ResultSetgetProcedures(java.lang.String catalog, + java.lang.String schemaPattern, + java.lang.String procedureNamePattern) +
      Gets the list of procedures.
      +
      java.lang.StringgetProcedureTerm() +
      Returns the term for "procedure".
      +
      java.sql.ResultSetgetPseudoColumns(java.lang.String catalog, + java.lang.String schemaPattern, + java.lang.String tableNamePattern, + java.lang.String columnNamePattern) +
      Gets the list of pseudo and invisible columns.
      +
      intgetResultSetHoldability() +
      Gets the result set holdability.
      +
      java.sql.RowIdLifetimegetRowIdLifetime() +
      Get the lifetime of a rowid.
      +
      java.sql.ResultSetgetSchemas() +
      Gets the list of schemas.
      +
      java.sql.ResultSetgetSchemas(java.lang.String catalogPattern, + java.lang.String schemaPattern) +
      Gets the list of schemas in the database.
      +
      java.lang.StringgetSchemaTerm() +
      Returns the term for "schema".
      +
      java.lang.StringgetSearchStringEscape() +
      Returns the default escape character for DatabaseMetaData search + patterns.
      +
      java.lang.StringgetSQLKeywords() +
      Gets the comma-separated list of all SQL keywords that are not supported + as unquoted identifiers, in addition to the SQL:2003 reserved words.
      +
      intgetSQLStateType() +
      Gets the SQL State type.
      +
      java.lang.StringgetStringFunctions() +
      Returns the list of string functions supported by this database.
      +
      java.sql.ResultSetgetSuperTables(java.lang.String catalog, + java.lang.String schemaPattern, + java.lang.String tableNamePattern) +
      Get the list of super tables of a table.
      +
      java.sql.ResultSetgetSuperTypes(java.lang.String catalog, + java.lang.String schemaPattern, + java.lang.String typeNamePattern) +
      [Not supported]
      +
      java.lang.StringgetSystemFunctions() +
      Returns the list of system functions supported by this database.
      +
      java.sql.ResultSetgetTablePrivileges(java.lang.String catalog, + java.lang.String schemaPattern, + java.lang.String tableNamePattern) +
      Gets the list of table privileges.
      +
      java.sql.ResultSetgetTables(java.lang.String catalog, + java.lang.String schemaPattern, + java.lang.String tableNamePattern, + java.lang.String[] types) +
      Gets the list of tables in the database.
      +
      java.sql.ResultSetgetTableTypes() +
      Gets the list of table types.
      +
      java.lang.StringgetTimeDateFunctions() +
      Returns the list of date and time functions supported by this database.
      +
      java.sql.ResultSetgetTypeInfo() +
      Gets the list of data types.
      +
      java.sql.ResultSetgetUDTs(java.lang.String catalog, + java.lang.String schemaPattern, + java.lang.String typeNamePattern, + int[] types) +
      Gets the list of user-defined data types.
      +
      java.lang.StringgetURL() +
      Returns the database URL for this connection.
      +
      java.lang.StringgetUserName() +
      Returns the user name as passed to DriverManager.getConnection(url, user, + password).
      +
      java.sql.ResultSetgetVersionColumns(java.lang.String catalog, + java.lang.String schema, + java.lang.String table) +
      Get the list of columns that are update when any value is updated.
      +
      booleaninsertsAreDetected(int type) +
      Returns whether inserts are detected.
      +
      booleanisCatalogAtStart() +
      Returns whether the catalog is at the beginning.
      +
      booleanisReadOnly() +
      Returns the same as Connection.isReadOnly().
      +
      booleanisWrapperFor(java.lang.Class<?> iface) +
      Checks if unwrap can return an object of this class.
      +
      booleanlocatorsUpdateCopy() +
      Does the database make a copy before updating.
      +
      booleannullPlusNonNullIsNull() +
      Returns whether NULL+1 is NULL or not.
      +
      booleannullsAreSortedAtEnd() +
      Checks if NULL values are sorted at the end (no matter if ASC or DESC is + used).
      +
      booleannullsAreSortedAtStart() +
      Checks if NULL values are sorted at the beginning (no matter if ASC or + DESC is used).
      +
      booleannullsAreSortedHigh() +
      Checks if NULL values are sorted high (bigger than anything that is not + null).
      +
      booleannullsAreSortedLow() +
      Checks if NULL values are sorted low (smaller than anything that is not + null).
      +
      booleanothersDeletesAreVisible(int type) +
      Returns whether other deletes are visible.
      +
      booleanothersInsertsAreVisible(int type) +
      Returns whether other inserts are visible.
      +
      booleanothersUpdatesAreVisible(int type) +
      Returns whether other updates are visible.
      +
      booleanownDeletesAreVisible(int type) +
      Returns whether own deletes are visible.
      +
      booleanownInsertsAreVisible(int type) +
      Returns whether own inserts are visible.
      +
      booleanownUpdatesAreVisible(int type) +
      Returns whether own updates are visible.
      +
      booleanstoresLowerCaseIdentifiers() +
      Checks if for CREATE TABLE Test(ID INT), getTables returns test as the + table name.
      +
      booleanstoresLowerCaseQuotedIdentifiers() +
      Checks if for CREATE TABLE "Test"(ID INT), getTables returns test as the + table name.
      +
      booleanstoresMixedCaseIdentifiers() +
      Checks if for CREATE TABLE Test(ID INT), getTables returns Test as the + table name and identifiers are not case sensitive.
      +
      booleanstoresMixedCaseQuotedIdentifiers() +
      Checks if for CREATE TABLE "Test"(ID INT), getTables returns Test as the + table name and identifiers are case insensitive.
      +
      booleanstoresUpperCaseIdentifiers() +
      Checks if for CREATE TABLE Test(ID INT), getTables returns TEST as the + table name.
      +
      booleanstoresUpperCaseQuotedIdentifiers() +
      Checks if for CREATE TABLE "Test"(ID INT), getTables returns TEST as the + table name.
      +
      booleansupportsAlterTableWithAddColumn() +
      Returns whether alter table with add column is supported.
      +
      booleansupportsAlterTableWithDropColumn() +
      Returns whether alter table with drop column is supported.
      +
      booleansupportsANSI92EntryLevelSQL() +
      Returns whether SQL-92 entry level grammar is supported.
      +
      booleansupportsANSI92FullSQL() +
      Returns whether SQL-92 full level grammar is supported.
      +
      booleansupportsANSI92IntermediateSQL() +
      Returns whether SQL-92 intermediate level grammar is supported.
      +
      booleansupportsBatchUpdates() +
      Returns whether batch updates are supported.
      +
      booleansupportsCatalogsInDataManipulation() +
      Returns whether the catalog name in INSERT, UPDATE, DELETE is supported.
      +
      booleansupportsCatalogsInIndexDefinitions() +
      Returns whether the catalog name in CREATE INDEX is supported.
      +
      booleansupportsCatalogsInPrivilegeDefinitions() +
      Returns whether the catalog name in GRANT is supported.
      +
      booleansupportsCatalogsInProcedureCalls() +
      Returns whether the catalog name in procedure calls is supported.
      +
      booleansupportsCatalogsInTableDefinitions() +
      Returns whether the catalog name in CREATE TABLE is supported.
      +
      booleansupportsColumnAliasing() +
      Returns whether column aliasing is supported.
      +
      booleansupportsConvert() +
      Returns whether CONVERT is supported.
      +
      booleansupportsConvert(int fromType, + int toType) +
      Returns whether CONVERT is supported for one datatype to another.
      +
      booleansupportsCoreSQLGrammar() +
      Returns whether ODBC Core SQL grammar is supported.
      +
      booleansupportsCorrelatedSubqueries() +
      Returns whether correlated subqueries are supported.
      +
      booleansupportsDataDefinitionAndDataManipulationTransactions() +
      Returns whether data manipulation and CREATE/DROP is supported in + transactions.
      +
      booleansupportsDataManipulationTransactionsOnly() +
      Returns whether only data manipulations are supported in transactions.
      +
      booleansupportsDifferentTableCorrelationNames() +
      Returns whether table correlation names (table alias) are restricted to + be different than table names.
      +
      booleansupportsExpressionsInOrderBy() +
      Returns whether expression in ORDER BY are supported.
      +
      booleansupportsExtendedSQLGrammar() +
      Returns whether ODBC Extended SQL grammar is supported.
      +
      booleansupportsFullOuterJoins() +
      Returns whether full outer joins are supported.
      +
      booleansupportsGetGeneratedKeys() +
      Does the database support getGeneratedKeys.
      +
      booleansupportsGroupBy() +
      Returns whether GROUP BY is supported.
      +
      booleansupportsGroupByBeyondSelect() +
      Checks whether a GROUP BY clause can use columns that are not in the + SELECT clause, provided that it specifies all the columns in the SELECT + clause.
      +
      booleansupportsGroupByUnrelated() +
      Returns whether GROUP BY is supported if the column is not in the SELECT + list.
      +
      booleansupportsIntegrityEnhancementFacility() +
      Returns whether referential integrity is supported.
      +
      booleansupportsLikeEscapeClause() +
      Returns whether LIKE...
      +
      booleansupportsLimitedOuterJoins() +
      Returns whether limited outer joins are supported.
      +
      booleansupportsMinimumSQLGrammar() +
      Returns whether ODBC Minimum SQL grammar is supported.
      +
      booleansupportsMixedCaseIdentifiers() +
      Checks if for CREATE TABLE Test(ID INT), getTables returns Test as the + table name and identifiers are case sensitive.
      +
      booleansupportsMixedCaseQuotedIdentifiers() +
      Checks if a table created with CREATE TABLE "Test"(ID INT) is a different + table than a table created with CREATE TABLE "TEST"(ID INT).
      +
      booleansupportsMultipleOpenResults() +
      Does the database support multiple open result sets returned from a + CallableStatement.
      +
      booleansupportsMultipleResultSets() +
      Returns whether multiple result sets are supported.
      +
      booleansupportsMultipleTransactions() +
      Returns whether multiple transactions (on different connections) are + supported.
      +
      booleansupportsNamedParameters() +
      Does the database support named parameters.
      +
      booleansupportsNonNullableColumns() +
      Returns whether columns with NOT NULL are supported.
      +
      booleansupportsOpenCursorsAcrossCommit() +
      Returns whether open result sets across commits are supported.
      +
      booleansupportsOpenCursorsAcrossRollback() +
      Returns whether open result sets across rollback are supported.
      +
      booleansupportsOpenStatementsAcrossCommit() +
      Returns whether open statements across commit are supported.
      +
      booleansupportsOpenStatementsAcrossRollback() +
      Returns whether open statements across rollback are supported.
      +
      booleansupportsOrderByUnrelated() +
      Returns whether ORDER BY is supported if the column is not in the SELECT + list.
      +
      booleansupportsOuterJoins() +
      Returns whether outer joins are supported.
      +
      booleansupportsPositionedDelete() +
      Returns whether positioned deletes are supported.
      +
      booleansupportsPositionedUpdate() +
      Returns whether positioned updates are supported.
      +
      booleansupportsResultSetConcurrency(int type, + int concurrency) +
      Returns whether a specific result set concurrency is supported.
      +
      booleansupportsResultSetHoldability(int holdability) +
      Does this database supports a result set holdability.
      +
      booleansupportsResultSetType(int type) +
      Returns whether a specific result set type is supported.
      +
      booleansupportsSavepoints() +
      Does the database support savepoints.
      +
      booleansupportsSchemasInDataManipulation() +
      Returns whether the schema name in INSERT, UPDATE, DELETE is supported.
      +
      booleansupportsSchemasInIndexDefinitions() +
      Returns whether the schema name in CREATE INDEX is supported.
      +
      booleansupportsSchemasInPrivilegeDefinitions() +
      Returns whether the schema name in GRANT is supported.
      +
      booleansupportsSchemasInProcedureCalls() +
      Returns whether the schema name in procedure calls is supported.
      +
      booleansupportsSchemasInTableDefinitions() +
      Returns whether the schema name in CREATE TABLE is supported.
      +
      booleansupportsSelectForUpdate() +
      Returns whether SELECT ...
      +
      booleansupportsStatementPooling() +
      Does the database support statement pooling.
      +
      booleansupportsStoredFunctionsUsingCallSyntax() +
      Returns whether the database supports calling functions using the call + syntax.
      +
      booleansupportsStoredProcedures() +
      Returns whether stored procedures are supported.
      +
      booleansupportsSubqueriesInComparisons() +
      Returns whether subqueries (SELECT) in comparisons are supported.
      +
      booleansupportsSubqueriesInExists() +
      Returns whether SELECT in EXISTS is supported.
      +
      booleansupportsSubqueriesInIns() +
      Returns whether IN(SELECT...) is supported.
      +
      booleansupportsSubqueriesInQuantifieds() +
      Returns whether subqueries in quantified expression are supported.
      +
      booleansupportsTableCorrelationNames() +
      Returns whether table correlation names (table alias) are supported.
      +
      booleansupportsTransactionIsolationLevel(int level) +
      Returns whether a specific transaction isolation level is supported.
      +
      booleansupportsTransactions() +
      Returns whether transactions are supported.
      +
      booleansupportsUnion() +
      Returns whether UNION SELECT is supported.
      +
      booleansupportsUnionAll() +
      Returns whether UNION ALL SELECT is supported.
      +
      java.lang.StringtoString() +
      INTERNAL
      +
      <T> Tunwrap(java.lang.Class<T> iface) +
      Return an object of this class if possible.
      +
      booleanupdatesAreDetected(int type) +
      Returns whether updates are detected.
      +
      booleanusesLocalFilePerTable() +
      Checks if this database use one file per table.
      +
      booleanusesLocalFiles() +
      Checks if this database store data in local files.
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.message.TraceObject

        +debugCode, debugCodeAssign, debugCodeCall, debugCodeCall, debugCodeCall, getNextId, getTraceId, getTraceObjectName, isDebugEnabled, isInfoEnabled, logAndConvert, quote, quoteArray, quoteBigDecimal, quoteBytes, quoteDate, quoteIntArray, quoteMap, quoteTime, quoteTimestamp, setTrace, unsupported
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
        +
      • + + +

        Methods inherited from interface java.sql.DatabaseMetaData

        +getMaxLogicalLobSize, supportsRefCursors
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getDriverMajorVersion

        +
        public int getDriverMajorVersion()
        +
        Returns the major version of this driver.
        +
        +
        Specified by:
        +
        getDriverMajorVersion in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        the major version number
        +
        +
      • +
      + + + +
        +
      • +

        getDriverMinorVersion

        +
        public int getDriverMinorVersion()
        +
        Returns the minor version of this driver.
        +
        +
        Specified by:
        +
        getDriverMinorVersion in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        the minor version number
        +
        +
      • +
      + + + +
        +
      • +

        getDatabaseProductName

        +
        public java.lang.String getDatabaseProductName()
        +
        Gets the database product name.
        +
        +
        Specified by:
        +
        getDatabaseProductName in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        the product name ("H2")
        +
        +
      • +
      + + + +
        +
      • +

        getDatabaseProductVersion

        +
        public java.lang.String getDatabaseProductVersion()
        +                                           throws java.sql.SQLException
        +
        Gets the product version of the database.
        +
        +
        Specified by:
        +
        getDatabaseProductVersion in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        the product version
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getDriverName

        +
        public java.lang.String getDriverName()
        +
        Gets the name of the JDBC driver.
        +
        +
        Specified by:
        +
        getDriverName in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        the driver name ("H2 JDBC Driver")
        +
        +
      • +
      + + + +
        +
      • +

        getDriverVersion

        +
        public java.lang.String getDriverVersion()
        +
        Gets the version number of the driver. The format is + [MajorVersion].[MinorVersion].
        +
        +
        Specified by:
        +
        getDriverVersion in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        the version number
        +
        +
      • +
      + + + +
        +
      • +

        getTables

        +
        public java.sql.ResultSet getTables(java.lang.String catalog,
        +                                    java.lang.String schemaPattern,
        +                                    java.lang.String tableNamePattern,
        +                                    java.lang.String[] types)
        +                             throws java.sql.SQLException
        +
        Gets the list of tables in the database. The result set is sorted by + TABLE_TYPE, TABLE_SCHEM, and TABLE_NAME. + +
          +
        1. TABLE_CAT (String) table catalog
        2. +
        3. TABLE_SCHEM (String) table schema
        4. +
        5. TABLE_NAME (String) table name
        6. +
        7. TABLE_TYPE (String) table type
        8. +
        9. REMARKS (String) comment
        10. +
        11. TYPE_CAT (String) always null
        12. +
        13. TYPE_SCHEM (String) always null
        14. +
        15. TYPE_NAME (String) always null
        16. +
        17. SELF_REFERENCING_COL_NAME (String) always null
        18. +
        19. REF_GENERATION (String) always null
        20. +
        21. SQL (String) the create table statement or NULL for systems tables.
        22. +
        +
        +
        Specified by:
        +
        getTables in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        catalog - null (to get all objects) or the catalog name
        +
        schemaPattern - null (to get all objects) or a schema name + (uppercase for unquoted names)
        +
        tableNamePattern - null (to get all objects) or a table name + (uppercase for unquoted names)
        +
        types - null or a list of table types
        +
        Returns:
        +
        the list of columns
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        getColumns

        +
        public java.sql.ResultSet getColumns(java.lang.String catalog,
        +                                     java.lang.String schemaPattern,
        +                                     java.lang.String tableNamePattern,
        +                                     java.lang.String columnNamePattern)
        +                              throws java.sql.SQLException
        +
        Gets the list of columns. The result set is sorted by TABLE_SCHEM, + TABLE_NAME, and ORDINAL_POSITION. + +
          +
        1. TABLE_CAT (String) table catalog
        2. +
        3. TABLE_SCHEM (String) table schema
        4. +
        5. TABLE_NAME (String) table name
        6. +
        7. COLUMN_NAME (String) column name
        8. +
        9. DATA_TYPE (int) data type (see java.sql.Types)
        10. +
        11. TYPE_NAME (String) data type name ("INTEGER", "VARCHAR",...)
        12. +
        13. COLUMN_SIZE (int) precision + (values larger than 2 GB are returned as 2 GB)
        14. +
        15. BUFFER_LENGTH (int) unused
        16. +
        17. DECIMAL_DIGITS (int) scale (0 for INTEGER and VARCHAR)
        18. +
        19. NUM_PREC_RADIX (int) radix
        20. +
        21. NULLABLE (int) columnNoNulls or columnNullable
        22. +
        23. REMARKS (String) comment
        24. +
        25. COLUMN_DEF (String) default value
        26. +
        27. SQL_DATA_TYPE (int) unused
        28. +
        29. SQL_DATETIME_SUB (int) unused
        30. +
        31. CHAR_OCTET_LENGTH (int) unused
        32. +
        33. ORDINAL_POSITION (int) the column index (1,2,...)
        34. +
        35. IS_NULLABLE (String) "NO" or "YES"
        36. +
        37. SCOPE_CATALOG (String) always null
        38. +
        39. SCOPE_SCHEMA (String) always null
        40. +
        41. SCOPE_TABLE (String) always null
        42. +
        43. SOURCE_DATA_TYPE (short) null
        44. +
        45. IS_AUTOINCREMENT (String) "NO" or "YES"
        46. +
        47. IS_GENERATEDCOLUMN (String) "NO" or "YES"
        48. +
        +
        +
        Specified by:
        +
        getColumns in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        catalog - null (to get all objects) or the catalog name
        +
        schemaPattern - null (to get all objects) or a schema name + (uppercase for unquoted names)
        +
        tableNamePattern - null (to get all objects) or a table name + (uppercase for unquoted names)
        +
        columnNamePattern - null (to get all objects) or a column name + (uppercase for unquoted names)
        +
        Returns:
        +
        the list of columns
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        getIndexInfo

        +
        public java.sql.ResultSet getIndexInfo(java.lang.String catalog,
        +                                       java.lang.String schema,
        +                                       java.lang.String table,
        +                                       boolean unique,
        +                                       boolean approximate)
        +                                throws java.sql.SQLException
        +
        Gets the list of indexes for this database. The primary key index (if + there is one) is also listed, with the name PRIMARY_KEY. The result set + is sorted by NON_UNIQUE ('false' first), TYPE, TABLE_SCHEM, INDEX_NAME, + and ORDINAL_POSITION. + +
          +
        1. TABLE_CAT (String) table catalog
        2. +
        3. TABLE_SCHEM (String) table schema
        4. +
        5. TABLE_NAME (String) table name
        6. +
        7. NON_UNIQUE (boolean) 'true' if non-unique
        8. +
        9. INDEX_QUALIFIER (String) index catalog
        10. +
        11. INDEX_NAME (String) index name
        12. +
        13. TYPE (short) the index type (tableIndexOther or tableIndexHash for + unique indexes on non-nullable columns, tableIndexStatistics for other + indexes)
        14. +
        15. ORDINAL_POSITION (short) column index (1, 2, ...)
        16. +
        17. COLUMN_NAME (String) column name
        18. +
        19. ASC_OR_DESC (String) ascending or descending (always 'A')
        20. +
        21. CARDINALITY (long) number of rows or numbers of unique values for + unique indexes on non-nullable columns
        22. +
        23. PAGES (long) number of pages use
        24. +
        25. FILTER_CONDITION (String) filter condition (always empty)
        26. +
        +
        +
        Specified by:
        +
        getIndexInfo in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        catalog - null or the catalog name
        +
        schema - null (to get all objects) or a schema name + (uppercase for unquoted names)
        +
        table - table name (must be specified)
        +
        unique - only unique indexes
        +
        approximate - if true, return fast, but approximate CARDINALITY
        +
        Returns:
        +
        the list of indexes and columns
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        getPrimaryKeys

        +
        public java.sql.ResultSet getPrimaryKeys(java.lang.String catalog,
        +                                         java.lang.String schema,
        +                                         java.lang.String table)
        +                                  throws java.sql.SQLException
        +
        Gets the primary key columns for a table. The result set is sorted by + TABLE_SCHEM, and COLUMN_NAME (and not by KEY_SEQ). + +
          +
        1. TABLE_CAT (String) table catalog
        2. +
        3. TABLE_SCHEM (String) table schema
        4. +
        5. TABLE_NAME (String) table name
        6. +
        7. COLUMN_NAME (String) column name
        8. +
        9. KEY_SEQ (short) the column index of this column (1,2,...)
        10. +
        11. PK_NAME (String) the name of the primary key index
        12. +
        +
        +
        Specified by:
        +
        getPrimaryKeys in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        catalog - null or the catalog name
        +
        schema - null (to get all objects) or a schema name + (uppercase for unquoted names)
        +
        table - table name (must be specified)
        +
        Returns:
        +
        the list of primary key columns
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        allProceduresAreCallable

        +
        public boolean allProceduresAreCallable()
        +
        Checks if all procedures callable.
        +
        +
        Specified by:
        +
        allProceduresAreCallable in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        allTablesAreSelectable

        +
        public boolean allTablesAreSelectable()
        +
        Checks if it possible to query all tables returned by getTables.
        +
        +
        Specified by:
        +
        allTablesAreSelectable in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        getURL

        +
        public java.lang.String getURL()
        +                        throws java.sql.SQLException
        +
        Returns the database URL for this connection.
        +
        +
        Specified by:
        +
        getURL in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        the url
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getUserName

        +
        public java.lang.String getUserName()
        +                             throws java.sql.SQLException
        +
        Returns the user name as passed to DriverManager.getConnection(url, user, + password).
        +
        +
        Specified by:
        +
        getUserName in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        the user name
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        isReadOnly

        +
        public boolean isReadOnly()
        +                   throws java.sql.SQLException
        +
        Returns the same as Connection.isReadOnly().
        +
        +
        Specified by:
        +
        isReadOnly in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        if read only optimization is switched on
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        nullsAreSortedHigh

        +
        public boolean nullsAreSortedHigh()
        +                           throws java.sql.SQLException
        +
        Checks if NULL values are sorted high (bigger than anything that is not + null).
        +
        +
        Specified by:
        +
        nullsAreSortedHigh in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        if NULL values are sorted high
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        nullsAreSortedLow

        +
        public boolean nullsAreSortedLow()
        +                          throws java.sql.SQLException
        +
        Checks if NULL values are sorted low (smaller than anything that is not + null).
        +
        +
        Specified by:
        +
        nullsAreSortedLow in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        if NULL values are sorted low
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        nullsAreSortedAtStart

        +
        public boolean nullsAreSortedAtStart()
        +                              throws java.sql.SQLException
        +
        Checks if NULL values are sorted at the beginning (no matter if ASC or + DESC is used).
        +
        +
        Specified by:
        +
        nullsAreSortedAtStart in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        if NULL values are sorted at the beginning
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        nullsAreSortedAtEnd

        +
        public boolean nullsAreSortedAtEnd()
        +                            throws java.sql.SQLException
        +
        Checks if NULL values are sorted at the end (no matter if ASC or DESC is + used).
        +
        +
        Specified by:
        +
        nullsAreSortedAtEnd in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        if NULL values are sorted at the end
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getConnection

        +
        public java.sql.Connection getConnection()
        +
        Returns the connection that created this object.
        +
        +
        Specified by:
        +
        getConnection in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        the connection
        +
        +
      • +
      + + + +
        +
      • +

        getProcedures

        +
        public java.sql.ResultSet getProcedures(java.lang.String catalog,
        +                                        java.lang.String schemaPattern,
        +                                        java.lang.String procedureNamePattern)
        +                                 throws java.sql.SQLException
        +
        Gets the list of procedures. The result set is sorted by PROCEDURE_SCHEM, + PROCEDURE_NAME, and NUM_INPUT_PARAMS. There are potentially multiple + procedures with the same name, each with a different number of input + parameters. + +
          +
        1. PROCEDURE_CAT (String) catalog
        2. +
        3. PROCEDURE_SCHEM (String) schema
        4. +
        5. PROCEDURE_NAME (String) name
        6. +
        7. reserved
        8. +
        9. reserved
        10. +
        11. reserved
        12. +
        13. REMARKS (String) description
        14. +
        15. PROCEDURE_TYPE (short) if this procedure returns a result + (procedureNoResult or procedureReturnsResult)
        16. +
        17. SPECIFIC_NAME (String) non-ambiguous name to distinguish + overloads
        18. +
        +
        +
        Specified by:
        +
        getProcedures in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        catalog - null or the catalog name
        +
        schemaPattern - null (to get all objects) or a schema name + (uppercase for unquoted names)
        +
        procedureNamePattern - the procedure name pattern
        +
        Returns:
        +
        the procedures
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        getProcedureColumns

        +
        public java.sql.ResultSet getProcedureColumns(java.lang.String catalog,
        +                                              java.lang.String schemaPattern,
        +                                              java.lang.String procedureNamePattern,
        +                                              java.lang.String columnNamePattern)
        +                                       throws java.sql.SQLException
        +
        Gets the list of procedure columns. The result set is sorted by + PROCEDURE_SCHEM, PROCEDURE_NAME, NUM_INPUT_PARAMS, and POS. + There are potentially multiple procedures with the same name, each with a + different number of input parameters. + +
          +
        1. PROCEDURE_CAT (String) catalog
        2. +
        3. PROCEDURE_SCHEM (String) schema
        4. +
        5. PROCEDURE_NAME (String) name
        6. +
        7. COLUMN_NAME (String) column name
        8. +
        9. COLUMN_TYPE (short) column type + (always DatabaseMetaData.procedureColumnIn)
        10. +
        11. DATA_TYPE (short) sql type
        12. +
        13. TYPE_NAME (String) type name
        14. +
        15. PRECISION (int) precision
        16. +
        17. LENGTH (int) length
        18. +
        19. SCALE (short) scale
        20. +
        21. RADIX (int)
        22. +
        23. NULLABLE (short) nullable + (DatabaseMetaData.columnNoNulls for primitive data types, + DatabaseMetaData.columnNullable otherwise)
        24. +
        25. REMARKS (String) description
        26. +
        27. COLUMN_DEF (String) always null
        28. +
        29. SQL_DATA_TYPE (int) for future use
        30. +
        31. SQL_DATETIME_SUB (int) for future use
        32. +
        33. CHAR_OCTET_LENGTH (int)
        34. +
        35. ORDINAL_POSITION (int) the parameter index + starting from 1 (0 is the return value)
        36. +
        37. IS_NULLABLE (String) always "YES"
        38. +
        39. SPECIFIC_NAME (String) non-ambiguous procedure name to distinguish + overloads
        40. +
        +
        +
        Specified by:
        +
        getProcedureColumns in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        catalog - null or the catalog name
        +
        schemaPattern - null (to get all objects) or a schema name + (uppercase for unquoted names)
        +
        procedureNamePattern - the procedure name pattern
        +
        columnNamePattern - the procedure name pattern
        +
        Returns:
        +
        the procedure columns
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        getSchemas

        +
        public java.sql.ResultSet getSchemas()
        +                              throws java.sql.SQLException
        +
        Gets the list of schemas. + The result set is sorted by TABLE_SCHEM. + +
          +
        1. TABLE_SCHEM (String) schema name
        2. +
        3. TABLE_CATALOG (String) catalog name
        4. +
        +
        +
        Specified by:
        +
        getSchemas in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        the schema list
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        getCatalogs

        +
        public java.sql.ResultSet getCatalogs()
        +                               throws java.sql.SQLException
        +
        Gets the list of catalogs. + The result set is sorted by TABLE_CAT. + +
          +
        1. TABLE_CAT (String) catalog name
        2. +
        +
        +
        Specified by:
        +
        getCatalogs in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        the catalog list
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        getTableTypes

        +
        public java.sql.ResultSet getTableTypes()
        +                                 throws java.sql.SQLException
        +
        Gets the list of table types. This call returns a result set with five + records: "SYSTEM TABLE", "TABLE", "VIEW", "TABLE LINK" and "EXTERNAL". +
          +
        1. TABLE_TYPE (String) table type
        2. +
        +
        +
        Specified by:
        +
        getTableTypes in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        the table types
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        getColumnPrivileges

        +
        public java.sql.ResultSet getColumnPrivileges(java.lang.String catalog,
        +                                              java.lang.String schema,
        +                                              java.lang.String table,
        +                                              java.lang.String columnNamePattern)
        +                                       throws java.sql.SQLException
        +
        Gets the list of column privileges. The result set is sorted by + COLUMN_NAME and PRIVILEGE + +
          +
        1. TABLE_CAT (String) table catalog
        2. +
        3. TABLE_SCHEM (String) table schema
        4. +
        5. TABLE_NAME (String) table name
        6. +
        7. COLUMN_NAME (String) column name
        8. +
        9. GRANTOR (String) grantor of access
        10. +
        11. GRANTEE (String) grantee of access
        12. +
        13. PRIVILEGE (String) SELECT, INSERT, UPDATE, DELETE or REFERENCES + (only one per row)
        14. +
        15. IS_GRANTABLE (String) YES means the grantee can grant access to + others
        16. +
        +
        +
        Specified by:
        +
        getColumnPrivileges in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        catalog - null (to get all objects) or the catalog name
        +
        schema - null (to get all objects) or a schema name + (uppercase for unquoted names)
        +
        table - a table name (uppercase for unquoted names)
        +
        columnNamePattern - null (to get all objects) or a column name + (uppercase for unquoted names)
        +
        Returns:
        +
        the list of privileges
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        getTablePrivileges

        +
        public java.sql.ResultSet getTablePrivileges(java.lang.String catalog,
        +                                             java.lang.String schemaPattern,
        +                                             java.lang.String tableNamePattern)
        +                                      throws java.sql.SQLException
        +
        Gets the list of table privileges. The result set is sorted by + TABLE_SCHEM, TABLE_NAME, and PRIVILEGE. + +
          +
        1. TABLE_CAT (String) table catalog
        2. +
        3. TABLE_SCHEM (String) table schema
        4. +
        5. TABLE_NAME (String) table name
        6. +
        7. GRANTOR (String) grantor of access
        8. +
        9. GRANTEE (String) grantee of access
        10. +
        11. PRIVILEGE (String) SELECT, INSERT, UPDATE, DELETE or REFERENCES + (only one per row)
        12. +
        13. IS_GRANTABLE (String) YES means the grantee can grant access to + others
        14. +
        +
        +
        Specified by:
        +
        getTablePrivileges in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        catalog - null (to get all objects) or the catalog name
        +
        schemaPattern - null (to get all objects) or a schema name + (uppercase for unquoted names)
        +
        tableNamePattern - null (to get all objects) or a table name + (uppercase for unquoted names)
        +
        Returns:
        +
        the list of privileges
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        getBestRowIdentifier

        +
        public java.sql.ResultSet getBestRowIdentifier(java.lang.String catalog,
        +                                               java.lang.String schema,
        +                                               java.lang.String table,
        +                                               int scope,
        +                                               boolean nullable)
        +                                        throws java.sql.SQLException
        +
        Gets the list of columns that best identifier a row in a table. + The list is ordered by SCOPE. + +
          +
        1. SCOPE (short) scope of result (always bestRowSession)
        2. +
        3. COLUMN_NAME (String) column name
        4. +
        5. DATA_TYPE (short) SQL data type, see also java.sql.Types
        6. +
        7. TYPE_NAME (String) type name
        8. +
        9. COLUMN_SIZE (int) precision + (values larger than 2 GB are returned as 2 GB)
        10. +
        11. BUFFER_LENGTH (int) unused
        12. +
        13. DECIMAL_DIGITS (short) scale
        14. +
        15. PSEUDO_COLUMN (short) (always bestRowNotPseudo)
        16. +
        +
        +
        Specified by:
        +
        getBestRowIdentifier in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        catalog - null (to get all objects) or the catalog name
        +
        schema - null (to get all objects) or a schema name + (uppercase for unquoted names)
        +
        table - table name (must be specified)
        +
        scope - ignored
        +
        nullable - ignored
        +
        Returns:
        +
        the primary key index
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        getVersionColumns

        +
        public java.sql.ResultSet getVersionColumns(java.lang.String catalog,
        +                                            java.lang.String schema,
        +                                            java.lang.String table)
        +                                     throws java.sql.SQLException
        +
        Get the list of columns that are update when any value is updated. + The result set is always empty. + +
          +
        1. 1 SCOPE (int) not used
        2. +
        3. 2 COLUMN_NAME (String) column name
        4. +
        5. 3 DATA_TYPE (int) SQL data type - see also java.sql.Types
        6. +
        7. 4 TYPE_NAME (String) data type name
        8. +
        9. 5 COLUMN_SIZE (int) precision + (values larger than 2 GB are returned as 2 GB)
        10. +
        11. 6 BUFFER_LENGTH (int) length (bytes)
        12. +
        13. 7 DECIMAL_DIGITS (int) scale
        14. +
        15. 8 PSEUDO_COLUMN (int) is this column a pseudo column
        16. +
        +
        +
        Specified by:
        +
        getVersionColumns in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        catalog - null (to get all objects) or the catalog name
        +
        schema - null (to get all objects) or a schema name
        +
        table - table name (must be specified)
        +
        Returns:
        +
        an empty result set
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        getImportedKeys

        +
        public java.sql.ResultSet getImportedKeys(java.lang.String catalog,
        +                                          java.lang.String schema,
        +                                          java.lang.String table)
        +                                   throws java.sql.SQLException
        +
        Gets the list of primary key columns that are referenced by a table. The + result set is sorted by PKTABLE_CAT, PKTABLE_SCHEM, PKTABLE_NAME, + FK_NAME, KEY_SEQ. + +
          +
        1. PKTABLE_CAT (String) primary catalog
        2. +
        3. PKTABLE_SCHEM (String) primary schema
        4. +
        5. PKTABLE_NAME (String) primary table
        6. +
        7. PKCOLUMN_NAME (String) primary column
        8. +
        9. FKTABLE_CAT (String) foreign catalog
        10. +
        11. FKTABLE_SCHEM (String) foreign schema
        12. +
        13. FKTABLE_NAME (String) foreign table
        14. +
        15. FKCOLUMN_NAME (String) foreign column
        16. +
        17. KEY_SEQ (short) sequence number (1, 2, ...)
        18. +
        19. UPDATE_RULE (short) action on update (see + DatabaseMetaData.importedKey...)
        20. +
        21. DELETE_RULE (short) action on delete (see + DatabaseMetaData.importedKey...)
        22. +
        23. FK_NAME (String) foreign key name
        24. +
        25. PK_NAME (String) primary key name
        26. +
        27. DEFERRABILITY (short) deferrable or not (always + importedKeyNotDeferrable)
        28. +
        +
        +
        Specified by:
        +
        getImportedKeys in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        catalog - null (to get all objects) or the catalog name
        +
        schema - the schema name of the foreign table
        +
        table - the name of the foreign table
        +
        Returns:
        +
        the result set
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        getExportedKeys

        +
        public java.sql.ResultSet getExportedKeys(java.lang.String catalog,
        +                                          java.lang.String schema,
        +                                          java.lang.String table)
        +                                   throws java.sql.SQLException
        +
        Gets the list of foreign key columns that reference a table. The result + set is sorted by FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, FK_NAME, + KEY_SEQ. + +
          +
        1. PKTABLE_CAT (String) primary catalog
        2. +
        3. PKTABLE_SCHEM (String) primary schema
        4. +
        5. PKTABLE_NAME (String) primary table
        6. +
        7. PKCOLUMN_NAME (String) primary column
        8. +
        9. FKTABLE_CAT (String) foreign catalog
        10. +
        11. FKTABLE_SCHEM (String) foreign schema
        12. +
        13. FKTABLE_NAME (String) foreign table
        14. +
        15. FKCOLUMN_NAME (String) foreign column
        16. +
        17. KEY_SEQ (short) sequence number (1,2,...)
        18. +
        19. UPDATE_RULE (short) action on update (see + DatabaseMetaData.importedKey...)
        20. +
        21. DELETE_RULE (short) action on delete (see + DatabaseMetaData.importedKey...)
        22. +
        23. FK_NAME (String) foreign key name
        24. +
        25. PK_NAME (String) primary key name
        26. +
        27. DEFERRABILITY (short) deferrable or not (always + importedKeyNotDeferrable)
        28. +
        +
        +
        Specified by:
        +
        getExportedKeys in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        catalog - null or the catalog name
        +
        schema - the schema name of the primary table
        +
        table - the name of the primary table
        +
        Returns:
        +
        the result set
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        getCrossReference

        +
        public java.sql.ResultSet getCrossReference(java.lang.String primaryCatalog,
        +                                            java.lang.String primarySchema,
        +                                            java.lang.String primaryTable,
        +                                            java.lang.String foreignCatalog,
        +                                            java.lang.String foreignSchema,
        +                                            java.lang.String foreignTable)
        +                                     throws java.sql.SQLException
        +
        Gets the list of foreign key columns that references a table, as well as + the list of primary key columns that are references by a table. The + result set is sorted by FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, + FK_NAME, KEY_SEQ. + +
          +
        1. PKTABLE_CAT (String) primary catalog
        2. +
        3. PKTABLE_SCHEM (String) primary schema
        4. +
        5. PKTABLE_NAME (String) primary table
        6. +
        7. PKCOLUMN_NAME (String) primary column
        8. +
        9. FKTABLE_CAT (String) foreign catalog
        10. +
        11. FKTABLE_SCHEM (String) foreign schema
        12. +
        13. FKTABLE_NAME (String) foreign table
        14. +
        15. FKCOLUMN_NAME (String) foreign column
        16. +
        17. KEY_SEQ (short) sequence number (1,2,...)
        18. +
        19. UPDATE_RULE (short) action on update (see + DatabaseMetaData.importedKey...)
        20. +
        21. DELETE_RULE (short) action on delete (see + DatabaseMetaData.importedKey...)
        22. +
        23. FK_NAME (String) foreign key name
        24. +
        25. PK_NAME (String) primary key name
        26. +
        27. DEFERRABILITY (short) deferrable or not (always + importedKeyNotDeferrable)
        28. +
        +
        +
        Specified by:
        +
        getCrossReference in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        primaryCatalog - null or the catalog name
        +
        primarySchema - the schema name of the primary table + (optional)
        +
        primaryTable - the name of the primary table (must be specified)
        +
        foreignCatalog - null or the catalog name
        +
        foreignSchema - the schema name of the foreign table + (optional)
        +
        foreignTable - the name of the foreign table (must be specified)
        +
        Returns:
        +
        the result set
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        getUDTs

        +
        public java.sql.ResultSet getUDTs(java.lang.String catalog,
        +                                  java.lang.String schemaPattern,
        +                                  java.lang.String typeNamePattern,
        +                                  int[] types)
        +                           throws java.sql.SQLException
        +
        Gets the list of user-defined data types. + This call returns an empty result set. + +
          +
        1. TYPE_CAT (String) catalog
        2. +
        3. TYPE_SCHEM (String) schema
        4. +
        5. TYPE_NAME (String) type name
        6. +
        7. CLASS_NAME (String) Java class
        8. +
        9. DATA_TYPE (short) SQL Type - see also java.sql.Types
        10. +
        11. REMARKS (String) description
        12. +
        13. BASE_TYPE (short) base type - see also java.sql.Types
        14. +
        +
        +
        Specified by:
        +
        getUDTs in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        catalog - ignored
        +
        schemaPattern - ignored
        +
        typeNamePattern - ignored
        +
        types - ignored
        +
        Returns:
        +
        an empty result set
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        getTypeInfo

        +
        public java.sql.ResultSet getTypeInfo()
        +                               throws java.sql.SQLException
        +
        Gets the list of data types. The result set is sorted by DATA_TYPE and + afterwards by how closely the data type maps to the corresponding JDBC + SQL type (best match first). + +
          +
        1. TYPE_NAME (String) type name
        2. +
        3. DATA_TYPE (short) SQL data type - see also java.sql.Types
        4. +
        5. PRECISION (int) maximum precision
        6. +
        7. LITERAL_PREFIX (String) prefix used to quote a literal
        8. +
        9. LITERAL_SUFFIX (String) suffix used to quote a literal
        10. +
        11. CREATE_PARAMS (String) parameters used (may be null)
        12. +
        13. NULLABLE (short) typeNoNulls (NULL not allowed) or typeNullable
        14. +
        15. CASE_SENSITIVE (boolean) case sensitive
        16. +
        17. SEARCHABLE (short) typeSearchable
        18. +
        19. UNSIGNED_ATTRIBUTE (boolean) unsigned
        20. +
        21. FIXED_PREC_SCALE (boolean) fixed precision
        22. +
        23. AUTO_INCREMENT (boolean) auto increment
        24. +
        25. LOCAL_TYPE_NAME (String) localized version of the data type
        26. +
        27. MINIMUM_SCALE (short) minimum scale
        28. +
        29. MAXIMUM_SCALE (short) maximum scale
        30. +
        31. SQL_DATA_TYPE (int) unused
        32. +
        33. SQL_DATETIME_SUB (int) unused
        34. +
        35. NUM_PREC_RADIX (int) 2 for binary, 10 for decimal
        36. +
        +
        +
        Specified by:
        +
        getTypeInfo in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        the list of data types
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        usesLocalFiles

        +
        public boolean usesLocalFiles()
        +
        Checks if this database store data in local files.
        +
        +
        Specified by:
        +
        usesLocalFiles in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        usesLocalFilePerTable

        +
        public boolean usesLocalFilePerTable()
        +
        Checks if this database use one file per table.
        +
        +
        Specified by:
        +
        usesLocalFilePerTable in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        getIdentifierQuoteString

        +
        public java.lang.String getIdentifierQuoteString()
        +
        Returns the string used to quote identifiers.
        +
        +
        Specified by:
        +
        getIdentifierQuoteString in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        a double quote
        +
        +
      • +
      + + + +
        +
      • +

        getSQLKeywords

        +
        public java.lang.String getSQLKeywords()
        +                                throws java.sql.SQLException
        +
        Gets the comma-separated list of all SQL keywords that are not supported + as unquoted identifiers, in addition to the SQL:2003 reserved words. +

        + List of keywords in H2 may depend on compatibility mode and other + settings. +

        +
        +
        Specified by:
        +
        getSQLKeywords in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        a list of additional keywords
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getNumericFunctions

        +
        public java.lang.String getNumericFunctions()
        +                                     throws java.sql.SQLException
        +
        Returns the list of numeric functions supported by this database.
        +
        +
        Specified by:
        +
        getNumericFunctions in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        the list
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getStringFunctions

        +
        public java.lang.String getStringFunctions()
        +                                    throws java.sql.SQLException
        +
        Returns the list of string functions supported by this database.
        +
        +
        Specified by:
        +
        getStringFunctions in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        the list
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getSystemFunctions

        +
        public java.lang.String getSystemFunctions()
        +                                    throws java.sql.SQLException
        +
        Returns the list of system functions supported by this database.
        +
        +
        Specified by:
        +
        getSystemFunctions in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        the list
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getTimeDateFunctions

        +
        public java.lang.String getTimeDateFunctions()
        +                                      throws java.sql.SQLException
        +
        Returns the list of date and time functions supported by this database.
        +
        +
        Specified by:
        +
        getTimeDateFunctions in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        the list
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getSearchStringEscape

        +
        public java.lang.String getSearchStringEscape()
        +                                       throws java.sql.SQLException
        +
        Returns the default escape character for DatabaseMetaData search + patterns.
        +
        +
        Specified by:
        +
        getSearchStringEscape in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        the default escape character (always '\', independent on the + mode)
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getExtraNameCharacters

        +
        public java.lang.String getExtraNameCharacters()
        +
        Returns the characters that are allowed for identifiers in addiction to + A-Z, a-z, 0-9 and '_'.
        +
        +
        Specified by:
        +
        getExtraNameCharacters in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        an empty String ("")
        +
        +
      • +
      + + + +
        +
      • +

        supportsAlterTableWithAddColumn

        +
        public boolean supportsAlterTableWithAddColumn()
        +
        Returns whether alter table with add column is supported.
        +
        +
        Specified by:
        +
        supportsAlterTableWithAddColumn in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsAlterTableWithDropColumn

        +
        public boolean supportsAlterTableWithDropColumn()
        +
        Returns whether alter table with drop column is supported.
        +
        +
        Specified by:
        +
        supportsAlterTableWithDropColumn in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsColumnAliasing

        +
        public boolean supportsColumnAliasing()
        +
        Returns whether column aliasing is supported.
        +
        +
        Specified by:
        +
        supportsColumnAliasing in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        nullPlusNonNullIsNull

        +
        public boolean nullPlusNonNullIsNull()
        +
        Returns whether NULL+1 is NULL or not.
        +
        +
        Specified by:
        +
        nullPlusNonNullIsNull in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsConvert

        +
        public boolean supportsConvert()
        +
        Returns whether CONVERT is supported.
        +
        +
        Specified by:
        +
        supportsConvert in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsConvert

        +
        public boolean supportsConvert(int fromType,
        +                               int toType)
        +
        Returns whether CONVERT is supported for one datatype to another.
        +
        +
        Specified by:
        +
        supportsConvert in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        fromType - the source SQL type
        +
        toType - the target SQL type
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsTableCorrelationNames

        +
        public boolean supportsTableCorrelationNames()
        +
        Returns whether table correlation names (table alias) are supported.
        +
        +
        Specified by:
        +
        supportsTableCorrelationNames in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsDifferentTableCorrelationNames

        +
        public boolean supportsDifferentTableCorrelationNames()
        +
        Returns whether table correlation names (table alias) are restricted to + be different than table names.
        +
        +
        Specified by:
        +
        supportsDifferentTableCorrelationNames in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        supportsExpressionsInOrderBy

        +
        public boolean supportsExpressionsInOrderBy()
        +
        Returns whether expression in ORDER BY are supported.
        +
        +
        Specified by:
        +
        supportsExpressionsInOrderBy in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsOrderByUnrelated

        +
        public boolean supportsOrderByUnrelated()
        +
        Returns whether ORDER BY is supported if the column is not in the SELECT + list.
        +
        +
        Specified by:
        +
        supportsOrderByUnrelated in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsGroupBy

        +
        public boolean supportsGroupBy()
        +
        Returns whether GROUP BY is supported.
        +
        +
        Specified by:
        +
        supportsGroupBy in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsGroupByUnrelated

        +
        public boolean supportsGroupByUnrelated()
        +
        Returns whether GROUP BY is supported if the column is not in the SELECT + list.
        +
        +
        Specified by:
        +
        supportsGroupByUnrelated in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsGroupByBeyondSelect

        +
        public boolean supportsGroupByBeyondSelect()
        +
        Checks whether a GROUP BY clause can use columns that are not in the + SELECT clause, provided that it specifies all the columns in the SELECT + clause.
        +
        +
        Specified by:
        +
        supportsGroupByBeyondSelect in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsLikeEscapeClause

        +
        public boolean supportsLikeEscapeClause()
        +
        Returns whether LIKE... ESCAPE is supported.
        +
        +
        Specified by:
        +
        supportsLikeEscapeClause in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsMultipleResultSets

        +
        public boolean supportsMultipleResultSets()
        +
        Returns whether multiple result sets are supported.
        +
        +
        Specified by:
        +
        supportsMultipleResultSets in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        supportsMultipleTransactions

        +
        public boolean supportsMultipleTransactions()
        +
        Returns whether multiple transactions (on different connections) are + supported.
        +
        +
        Specified by:
        +
        supportsMultipleTransactions in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsNonNullableColumns

        +
        public boolean supportsNonNullableColumns()
        +
        Returns whether columns with NOT NULL are supported.
        +
        +
        Specified by:
        +
        supportsNonNullableColumns in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsMinimumSQLGrammar

        +
        public boolean supportsMinimumSQLGrammar()
        +
        Returns whether ODBC Minimum SQL grammar is supported.
        +
        +
        Specified by:
        +
        supportsMinimumSQLGrammar in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsCoreSQLGrammar

        +
        public boolean supportsCoreSQLGrammar()
        +
        Returns whether ODBC Core SQL grammar is supported.
        +
        +
        Specified by:
        +
        supportsCoreSQLGrammar in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsExtendedSQLGrammar

        +
        public boolean supportsExtendedSQLGrammar()
        +
        Returns whether ODBC Extended SQL grammar is supported.
        +
        +
        Specified by:
        +
        supportsExtendedSQLGrammar in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        supportsANSI92EntryLevelSQL

        +
        public boolean supportsANSI92EntryLevelSQL()
        +
        Returns whether SQL-92 entry level grammar is supported.
        +
        +
        Specified by:
        +
        supportsANSI92EntryLevelSQL in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsANSI92IntermediateSQL

        +
        public boolean supportsANSI92IntermediateSQL()
        +
        Returns whether SQL-92 intermediate level grammar is supported.
        +
        +
        Specified by:
        +
        supportsANSI92IntermediateSQL in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        supportsANSI92FullSQL

        +
        public boolean supportsANSI92FullSQL()
        +
        Returns whether SQL-92 full level grammar is supported.
        +
        +
        Specified by:
        +
        supportsANSI92FullSQL in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        supportsIntegrityEnhancementFacility

        +
        public boolean supportsIntegrityEnhancementFacility()
        +
        Returns whether referential integrity is supported.
        +
        +
        Specified by:
        +
        supportsIntegrityEnhancementFacility in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsOuterJoins

        +
        public boolean supportsOuterJoins()
        +
        Returns whether outer joins are supported.
        +
        +
        Specified by:
        +
        supportsOuterJoins in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsFullOuterJoins

        +
        public boolean supportsFullOuterJoins()
        +
        Returns whether full outer joins are supported.
        +
        +
        Specified by:
        +
        supportsFullOuterJoins in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        supportsLimitedOuterJoins

        +
        public boolean supportsLimitedOuterJoins()
        +
        Returns whether limited outer joins are supported.
        +
        +
        Specified by:
        +
        supportsLimitedOuterJoins in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        getSchemaTerm

        +
        public java.lang.String getSchemaTerm()
        +
        Returns the term for "schema".
        +
        +
        Specified by:
        +
        getSchemaTerm in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        "schema"
        +
        +
      • +
      + + + +
        +
      • +

        getProcedureTerm

        +
        public java.lang.String getProcedureTerm()
        +
        Returns the term for "procedure".
        +
        +
        Specified by:
        +
        getProcedureTerm in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        "procedure"
        +
        +
      • +
      + + + +
        +
      • +

        getCatalogTerm

        +
        public java.lang.String getCatalogTerm()
        +
        Returns the term for "catalog".
        +
        +
        Specified by:
        +
        getCatalogTerm in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        "catalog"
        +
        +
      • +
      + + + +
        +
      • +

        isCatalogAtStart

        +
        public boolean isCatalogAtStart()
        +
        Returns whether the catalog is at the beginning.
        +
        +
        Specified by:
        +
        isCatalogAtStart in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        getCatalogSeparator

        +
        public java.lang.String getCatalogSeparator()
        +
        Returns the catalog separator.
        +
        +
        Specified by:
        +
        getCatalogSeparator in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        "."
        +
        +
      • +
      + + + +
        +
      • +

        supportsSchemasInDataManipulation

        +
        public boolean supportsSchemasInDataManipulation()
        +
        Returns whether the schema name in INSERT, UPDATE, DELETE is supported.
        +
        +
        Specified by:
        +
        supportsSchemasInDataManipulation in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsSchemasInProcedureCalls

        +
        public boolean supportsSchemasInProcedureCalls()
        +
        Returns whether the schema name in procedure calls is supported.
        +
        +
        Specified by:
        +
        supportsSchemasInProcedureCalls in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsSchemasInTableDefinitions

        +
        public boolean supportsSchemasInTableDefinitions()
        +
        Returns whether the schema name in CREATE TABLE is supported.
        +
        +
        Specified by:
        +
        supportsSchemasInTableDefinitions in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsSchemasInIndexDefinitions

        +
        public boolean supportsSchemasInIndexDefinitions()
        +
        Returns whether the schema name in CREATE INDEX is supported.
        +
        +
        Specified by:
        +
        supportsSchemasInIndexDefinitions in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsSchemasInPrivilegeDefinitions

        +
        public boolean supportsSchemasInPrivilegeDefinitions()
        +
        Returns whether the schema name in GRANT is supported.
        +
        +
        Specified by:
        +
        supportsSchemasInPrivilegeDefinitions in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsCatalogsInDataManipulation

        +
        public boolean supportsCatalogsInDataManipulation()
        +
        Returns whether the catalog name in INSERT, UPDATE, DELETE is supported.
        +
        +
        Specified by:
        +
        supportsCatalogsInDataManipulation in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsCatalogsInProcedureCalls

        +
        public boolean supportsCatalogsInProcedureCalls()
        +
        Returns whether the catalog name in procedure calls is supported.
        +
        +
        Specified by:
        +
        supportsCatalogsInProcedureCalls in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        supportsCatalogsInTableDefinitions

        +
        public boolean supportsCatalogsInTableDefinitions()
        +
        Returns whether the catalog name in CREATE TABLE is supported.
        +
        +
        Specified by:
        +
        supportsCatalogsInTableDefinitions in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsCatalogsInIndexDefinitions

        +
        public boolean supportsCatalogsInIndexDefinitions()
        +
        Returns whether the catalog name in CREATE INDEX is supported.
        +
        +
        Specified by:
        +
        supportsCatalogsInIndexDefinitions in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsCatalogsInPrivilegeDefinitions

        +
        public boolean supportsCatalogsInPrivilegeDefinitions()
        +
        Returns whether the catalog name in GRANT is supported.
        +
        +
        Specified by:
        +
        supportsCatalogsInPrivilegeDefinitions in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsPositionedDelete

        +
        public boolean supportsPositionedDelete()
        +
        Returns whether positioned deletes are supported.
        +
        +
        Specified by:
        +
        supportsPositionedDelete in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        supportsPositionedUpdate

        +
        public boolean supportsPositionedUpdate()
        +
        Returns whether positioned updates are supported.
        +
        +
        Specified by:
        +
        supportsPositionedUpdate in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        supportsSelectForUpdate

        +
        public boolean supportsSelectForUpdate()
        +
        Returns whether SELECT ... FOR UPDATE is supported.
        +
        +
        Specified by:
        +
        supportsSelectForUpdate in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsStoredProcedures

        +
        public boolean supportsStoredProcedures()
        +
        Returns whether stored procedures are supported.
        +
        +
        Specified by:
        +
        supportsStoredProcedures in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        supportsSubqueriesInComparisons

        +
        public boolean supportsSubqueriesInComparisons()
        +
        Returns whether subqueries (SELECT) in comparisons are supported.
        +
        +
        Specified by:
        +
        supportsSubqueriesInComparisons in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsSubqueriesInExists

        +
        public boolean supportsSubqueriesInExists()
        +
        Returns whether SELECT in EXISTS is supported.
        +
        +
        Specified by:
        +
        supportsSubqueriesInExists in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsSubqueriesInIns

        +
        public boolean supportsSubqueriesInIns()
        +
        Returns whether IN(SELECT...) is supported.
        +
        +
        Specified by:
        +
        supportsSubqueriesInIns in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsSubqueriesInQuantifieds

        +
        public boolean supportsSubqueriesInQuantifieds()
        +
        Returns whether subqueries in quantified expression are supported.
        +
        +
        Specified by:
        +
        supportsSubqueriesInQuantifieds in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsCorrelatedSubqueries

        +
        public boolean supportsCorrelatedSubqueries()
        +
        Returns whether correlated subqueries are supported.
        +
        +
        Specified by:
        +
        supportsCorrelatedSubqueries in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsUnion

        +
        public boolean supportsUnion()
        +
        Returns whether UNION SELECT is supported.
        +
        +
        Specified by:
        +
        supportsUnion in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsUnionAll

        +
        public boolean supportsUnionAll()
        +
        Returns whether UNION ALL SELECT is supported.
        +
        +
        Specified by:
        +
        supportsUnionAll in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsOpenCursorsAcrossCommit

        +
        public boolean supportsOpenCursorsAcrossCommit()
        +
        Returns whether open result sets across commits are supported.
        +
        +
        Specified by:
        +
        supportsOpenCursorsAcrossCommit in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        supportsOpenCursorsAcrossRollback

        +
        public boolean supportsOpenCursorsAcrossRollback()
        +
        Returns whether open result sets across rollback are supported.
        +
        +
        Specified by:
        +
        supportsOpenCursorsAcrossRollback in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        supportsOpenStatementsAcrossCommit

        +
        public boolean supportsOpenStatementsAcrossCommit()
        +
        Returns whether open statements across commit are supported.
        +
        +
        Specified by:
        +
        supportsOpenStatementsAcrossCommit in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsOpenStatementsAcrossRollback

        +
        public boolean supportsOpenStatementsAcrossRollback()
        +
        Returns whether open statements across rollback are supported.
        +
        +
        Specified by:
        +
        supportsOpenStatementsAcrossRollback in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsTransactions

        +
        public boolean supportsTransactions()
        +
        Returns whether transactions are supported.
        +
        +
        Specified by:
        +
        supportsTransactions in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsTransactionIsolationLevel

        +
        public boolean supportsTransactionIsolationLevel(int level)
        +                                          throws java.sql.SQLException
        +
        Returns whether a specific transaction isolation level is supported.
        +
        +
        Specified by:
        +
        supportsTransactionIsolationLevel in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        level - the transaction isolation level (Connection.TRANSACTION_*)
        +
        Returns:
        +
        true
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        supportsDataDefinitionAndDataManipulationTransactions

        +
        public boolean supportsDataDefinitionAndDataManipulationTransactions()
        +
        Returns whether data manipulation and CREATE/DROP is supported in + transactions.
        +
        +
        Specified by:
        +
        supportsDataDefinitionAndDataManipulationTransactions in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        supportsDataManipulationTransactionsOnly

        +
        public boolean supportsDataManipulationTransactionsOnly()
        +
        Returns whether only data manipulations are supported in transactions.
        +
        +
        Specified by:
        +
        supportsDataManipulationTransactionsOnly in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        dataDefinitionCausesTransactionCommit

        +
        public boolean dataDefinitionCausesTransactionCommit()
        +
        Returns whether CREATE/DROP commit an open transaction.
        +
        +
        Specified by:
        +
        dataDefinitionCausesTransactionCommit in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        dataDefinitionIgnoredInTransactions

        +
        public boolean dataDefinitionIgnoredInTransactions()
        +
        Returns whether CREATE/DROP do not affect transactions.
        +
        +
        Specified by:
        +
        dataDefinitionIgnoredInTransactions in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        supportsResultSetType

        +
        public boolean supportsResultSetType(int type)
        +
        Returns whether a specific result set type is supported. + ResultSet.TYPE_SCROLL_SENSITIVE is not supported.
        +
        +
        Specified by:
        +
        supportsResultSetType in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        type - the result set type
        +
        Returns:
        +
        true for all types except ResultSet.TYPE_SCROLL_SENSITIVE
        +
        +
      • +
      + + + +
        +
      • +

        supportsResultSetConcurrency

        +
        public boolean supportsResultSetConcurrency(int type,
        +                                            int concurrency)
        +
        Returns whether a specific result set concurrency is supported. + ResultSet.TYPE_SCROLL_SENSITIVE is not supported.
        +
        +
        Specified by:
        +
        supportsResultSetConcurrency in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        type - the result set type
        +
        concurrency - the result set concurrency
        +
        Returns:
        +
        true if the type is not ResultSet.TYPE_SCROLL_SENSITIVE
        +
        +
      • +
      + + + +
        +
      • +

        ownUpdatesAreVisible

        +
        public boolean ownUpdatesAreVisible(int type)
        +
        Returns whether own updates are visible.
        +
        +
        Specified by:
        +
        ownUpdatesAreVisible in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        type - the result set type
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        ownDeletesAreVisible

        +
        public boolean ownDeletesAreVisible(int type)
        +
        Returns whether own deletes are visible.
        +
        +
        Specified by:
        +
        ownDeletesAreVisible in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        type - the result set type
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        ownInsertsAreVisible

        +
        public boolean ownInsertsAreVisible(int type)
        +
        Returns whether own inserts are visible.
        +
        +
        Specified by:
        +
        ownInsertsAreVisible in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        type - the result set type
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        othersUpdatesAreVisible

        +
        public boolean othersUpdatesAreVisible(int type)
        +
        Returns whether other updates are visible.
        +
        +
        Specified by:
        +
        othersUpdatesAreVisible in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        type - the result set type
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        othersDeletesAreVisible

        +
        public boolean othersDeletesAreVisible(int type)
        +
        Returns whether other deletes are visible.
        +
        +
        Specified by:
        +
        othersDeletesAreVisible in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        type - the result set type
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        othersInsertsAreVisible

        +
        public boolean othersInsertsAreVisible(int type)
        +
        Returns whether other inserts are visible.
        +
        +
        Specified by:
        +
        othersInsertsAreVisible in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        type - the result set type
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        updatesAreDetected

        +
        public boolean updatesAreDetected(int type)
        +
        Returns whether updates are detected.
        +
        +
        Specified by:
        +
        updatesAreDetected in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        type - the result set type
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        deletesAreDetected

        +
        public boolean deletesAreDetected(int type)
        +
        Returns whether deletes are detected.
        +
        +
        Specified by:
        +
        deletesAreDetected in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        type - the result set type
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        insertsAreDetected

        +
        public boolean insertsAreDetected(int type)
        +
        Returns whether inserts are detected.
        +
        +
        Specified by:
        +
        insertsAreDetected in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        type - the result set type
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        supportsBatchUpdates

        +
        public boolean supportsBatchUpdates()
        +
        Returns whether batch updates are supported.
        +
        +
        Specified by:
        +
        supportsBatchUpdates in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        doesMaxRowSizeIncludeBlobs

        +
        public boolean doesMaxRowSizeIncludeBlobs()
        +
        Returns whether the maximum row size includes blobs.
        +
        +
        Specified by:
        +
        doesMaxRowSizeIncludeBlobs in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        getDefaultTransactionIsolation

        +
        public int getDefaultTransactionIsolation()
        +
        Returns the default transaction isolation level.
        +
        +
        Specified by:
        +
        getDefaultTransactionIsolation in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        Connection.TRANSACTION_READ_COMMITTED
        +
        +
      • +
      + + + +
        +
      • +

        supportsMixedCaseIdentifiers

        +
        public boolean supportsMixedCaseIdentifiers()
        +                                     throws java.sql.SQLException
        +
        Checks if for CREATE TABLE Test(ID INT), getTables returns Test as the + table name and identifiers are case sensitive.
        +
        +
        Specified by:
        +
        supportsMixedCaseIdentifiers in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true is so, false otherwise
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        storesUpperCaseIdentifiers

        +
        public boolean storesUpperCaseIdentifiers()
        +                                   throws java.sql.SQLException
        +
        Checks if for CREATE TABLE Test(ID INT), getTables returns TEST as the + table name.
        +
        +
        Specified by:
        +
        storesUpperCaseIdentifiers in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true is so, false otherwise
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        storesLowerCaseIdentifiers

        +
        public boolean storesLowerCaseIdentifiers()
        +                                   throws java.sql.SQLException
        +
        Checks if for CREATE TABLE Test(ID INT), getTables returns test as the + table name.
        +
        +
        Specified by:
        +
        storesLowerCaseIdentifiers in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true is so, false otherwise
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        storesMixedCaseIdentifiers

        +
        public boolean storesMixedCaseIdentifiers()
        +                                   throws java.sql.SQLException
        +
        Checks if for CREATE TABLE Test(ID INT), getTables returns Test as the + table name and identifiers are not case sensitive.
        +
        +
        Specified by:
        +
        storesMixedCaseIdentifiers in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true is so, false otherwise
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        supportsMixedCaseQuotedIdentifiers

        +
        public boolean supportsMixedCaseQuotedIdentifiers()
        +                                           throws java.sql.SQLException
        +
        Checks if a table created with CREATE TABLE "Test"(ID INT) is a different + table than a table created with CREATE TABLE "TEST"(ID INT).
        +
        +
        Specified by:
        +
        supportsMixedCaseQuotedIdentifiers in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true is so, false otherwise
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        storesUpperCaseQuotedIdentifiers

        +
        public boolean storesUpperCaseQuotedIdentifiers()
        +                                         throws java.sql.SQLException
        +
        Checks if for CREATE TABLE "Test"(ID INT), getTables returns TEST as the + table name.
        +
        +
        Specified by:
        +
        storesUpperCaseQuotedIdentifiers in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        false
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        storesLowerCaseQuotedIdentifiers

        +
        public boolean storesLowerCaseQuotedIdentifiers()
        +                                         throws java.sql.SQLException
        +
        Checks if for CREATE TABLE "Test"(ID INT), getTables returns test as the + table name.
        +
        +
        Specified by:
        +
        storesLowerCaseQuotedIdentifiers in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        false
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        storesMixedCaseQuotedIdentifiers

        +
        public boolean storesMixedCaseQuotedIdentifiers()
        +                                         throws java.sql.SQLException
        +
        Checks if for CREATE TABLE "Test"(ID INT), getTables returns Test as the + table name and identifiers are case insensitive.
        +
        +
        Specified by:
        +
        storesMixedCaseQuotedIdentifiers in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true is so, false otherwise
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getMaxBinaryLiteralLength

        +
        public int getMaxBinaryLiteralLength()
        +
        Returns the maximum length for hex values (characters).
        +
        +
        Specified by:
        +
        getMaxBinaryLiteralLength in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        0 for limit is unknown
        +
        +
      • +
      + + + +
        +
      • +

        getMaxCharLiteralLength

        +
        public int getMaxCharLiteralLength()
        +
        Returns the maximum length for literals.
        +
        +
        Specified by:
        +
        getMaxCharLiteralLength in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        0 for limit is unknown
        +
        +
      • +
      + + + +
        +
      • +

        getMaxColumnNameLength

        +
        public int getMaxColumnNameLength()
        +
        Returns the maximum length for column names.
        +
        +
        Specified by:
        +
        getMaxColumnNameLength in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        0 for limit is unknown
        +
        +
      • +
      + + + +
        +
      • +

        getMaxColumnsInGroupBy

        +
        public int getMaxColumnsInGroupBy()
        +
        Returns the maximum number of columns in GROUP BY.
        +
        +
        Specified by:
        +
        getMaxColumnsInGroupBy in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        0 for limit is unknown
        +
        +
      • +
      + + + +
        +
      • +

        getMaxColumnsInIndex

        +
        public int getMaxColumnsInIndex()
        +
        Returns the maximum number of columns in CREATE INDEX.
        +
        +
        Specified by:
        +
        getMaxColumnsInIndex in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        0 for limit is unknown
        +
        +
      • +
      + + + +
        +
      • +

        getMaxColumnsInOrderBy

        +
        public int getMaxColumnsInOrderBy()
        +
        Returns the maximum number of columns in ORDER BY.
        +
        +
        Specified by:
        +
        getMaxColumnsInOrderBy in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        0 for limit is unknown
        +
        +
      • +
      + + + +
        +
      • +

        getMaxColumnsInSelect

        +
        public int getMaxColumnsInSelect()
        +
        Returns the maximum number of columns in SELECT.
        +
        +
        Specified by:
        +
        getMaxColumnsInSelect in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        0 for limit is unknown
        +
        +
      • +
      + + + +
        +
      • +

        getMaxColumnsInTable

        +
        public int getMaxColumnsInTable()
        +
        Returns the maximum number of columns in CREATE TABLE.
        +
        +
        Specified by:
        +
        getMaxColumnsInTable in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        0 for limit is unknown
        +
        +
      • +
      + + + +
        +
      • +

        getMaxConnections

        +
        public int getMaxConnections()
        +
        Returns the maximum number of open connection.
        +
        +
        Specified by:
        +
        getMaxConnections in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        0 for limit is unknown
        +
        +
      • +
      + + + +
        +
      • +

        getMaxCursorNameLength

        +
        public int getMaxCursorNameLength()
        +
        Returns the maximum length for a cursor name.
        +
        +
        Specified by:
        +
        getMaxCursorNameLength in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        0 for limit is unknown
        +
        +
      • +
      + + + +
        +
      • +

        getMaxIndexLength

        +
        public int getMaxIndexLength()
        +
        Returns the maximum length for an index (in bytes).
        +
        +
        Specified by:
        +
        getMaxIndexLength in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        0 for limit is unknown
        +
        +
      • +
      + + + +
        +
      • +

        getMaxSchemaNameLength

        +
        public int getMaxSchemaNameLength()
        +
        Returns the maximum length for a schema name.
        +
        +
        Specified by:
        +
        getMaxSchemaNameLength in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        0 for limit is unknown
        +
        +
      • +
      + + + +
        +
      • +

        getMaxProcedureNameLength

        +
        public int getMaxProcedureNameLength()
        +
        Returns the maximum length for a procedure name.
        +
        +
        Specified by:
        +
        getMaxProcedureNameLength in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        0 for limit is unknown
        +
        +
      • +
      + + + +
        +
      • +

        getMaxCatalogNameLength

        +
        public int getMaxCatalogNameLength()
        +
        Returns the maximum length for a catalog name.
        +
        +
        Specified by:
        +
        getMaxCatalogNameLength in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        0 for limit is unknown
        +
        +
      • +
      + + + +
        +
      • +

        getMaxRowSize

        +
        public int getMaxRowSize()
        +
        Returns the maximum size of a row (in bytes).
        +
        +
        Specified by:
        +
        getMaxRowSize in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        0 for limit is unknown
        +
        +
      • +
      + + + +
        +
      • +

        getMaxStatementLength

        +
        public int getMaxStatementLength()
        +
        Returns the maximum length of a statement.
        +
        +
        Specified by:
        +
        getMaxStatementLength in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        0 for limit is unknown
        +
        +
      • +
      + + + +
        +
      • +

        getMaxStatements

        +
        public int getMaxStatements()
        +
        Returns the maximum number of open statements.
        +
        +
        Specified by:
        +
        getMaxStatements in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        0 for limit is unknown
        +
        +
      • +
      + + + +
        +
      • +

        getMaxTableNameLength

        +
        public int getMaxTableNameLength()
        +
        Returns the maximum length for a table name.
        +
        +
        Specified by:
        +
        getMaxTableNameLength in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        0 for limit is unknown
        +
        +
      • +
      + + + +
        +
      • +

        getMaxTablesInSelect

        +
        public int getMaxTablesInSelect()
        +
        Returns the maximum number of tables in a SELECT.
        +
        +
        Specified by:
        +
        getMaxTablesInSelect in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        0 for limit is unknown
        +
        +
      • +
      + + + +
        +
      • +

        getMaxUserNameLength

        +
        public int getMaxUserNameLength()
        +
        Returns the maximum length for a user name.
        +
        +
        Specified by:
        +
        getMaxUserNameLength in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        0 for limit is unknown
        +
        +
      • +
      + + + +
        +
      • +

        supportsSavepoints

        +
        public boolean supportsSavepoints()
        +
        Does the database support savepoints.
        +
        +
        Specified by:
        +
        supportsSavepoints in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        supportsNamedParameters

        +
        public boolean supportsNamedParameters()
        +
        Does the database support named parameters.
        +
        +
        Specified by:
        +
        supportsNamedParameters in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        supportsMultipleOpenResults

        +
        public boolean supportsMultipleOpenResults()
        +
        Does the database support multiple open result sets returned from a + CallableStatement.
        +
        +
        Specified by:
        +
        supportsMultipleOpenResults in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        supportsGetGeneratedKeys

        +
        public boolean supportsGetGeneratedKeys()
        +
        Does the database support getGeneratedKeys.
        +
        +
        Specified by:
        +
        supportsGetGeneratedKeys in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        getSuperTypes

        +
        public java.sql.ResultSet getSuperTypes(java.lang.String catalog,
        +                                        java.lang.String schemaPattern,
        +                                        java.lang.String typeNamePattern)
        +                                 throws java.sql.SQLException
        +
        [Not supported]
        +
        +
        Specified by:
        +
        getSuperTypes in interface java.sql.DatabaseMetaData
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getSuperTables

        +
        public java.sql.ResultSet getSuperTables(java.lang.String catalog,
        +                                         java.lang.String schemaPattern,
        +                                         java.lang.String tableNamePattern)
        +                                  throws java.sql.SQLException
        +
        Get the list of super tables of a table. This method currently returns an + empty result set. +
          +
        1. TABLE_CAT (String) table catalog
        2. +
        3. TABLE_SCHEM (String) table schema
        4. +
        5. TABLE_NAME (String) table name
        6. +
        7. SUPERTABLE_NAME (String) the name of the super table
        8. +
        +
        +
        Specified by:
        +
        getSuperTables in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        catalog - null (to get all objects) or the catalog name
        +
        schemaPattern - null (to get all objects) or a schema name + (uppercase for unquoted names)
        +
        tableNamePattern - null (to get all objects) or a table name pattern + (uppercase for unquoted names)
        +
        Returns:
        +
        an empty result set
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getAttributes

        +
        public java.sql.ResultSet getAttributes(java.lang.String catalog,
        +                                        java.lang.String schemaPattern,
        +                                        java.lang.String typeNamePattern,
        +                                        java.lang.String attributeNamePattern)
        +                                 throws java.sql.SQLException
        +
        [Not supported]
        +
        +
        Specified by:
        +
        getAttributes in interface java.sql.DatabaseMetaData
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        supportsResultSetHoldability

        +
        public boolean supportsResultSetHoldability(int holdability)
        +
        Does this database supports a result set holdability.
        +
        +
        Specified by:
        +
        supportsResultSetHoldability in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        holdability - ResultSet.HOLD_CURSORS_OVER_COMMIT or + CLOSE_CURSORS_AT_COMMIT
        +
        Returns:
        +
        true if the holdability is ResultSet.CLOSE_CURSORS_AT_COMMIT
        +
        +
      • +
      + + + +
        +
      • +

        getResultSetHoldability

        +
        public int getResultSetHoldability()
        +
        Gets the result set holdability.
        +
        +
        Specified by:
        +
        getResultSetHoldability in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        ResultSet.CLOSE_CURSORS_AT_COMMIT
        +
        +
      • +
      + + + +
        +
      • +

        getDatabaseMajorVersion

        +
        public int getDatabaseMajorVersion()
        +                            throws java.sql.SQLException
        +
        Gets the major version of the database.
        +
        +
        Specified by:
        +
        getDatabaseMajorVersion in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        the major version
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getDatabaseMinorVersion

        +
        public int getDatabaseMinorVersion()
        +                            throws java.sql.SQLException
        +
        Gets the minor version of the database.
        +
        +
        Specified by:
        +
        getDatabaseMinorVersion in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        the minor version
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getJDBCMajorVersion

        +
        public int getJDBCMajorVersion()
        +
        Gets the major version of the supported JDBC API.
        +
        +
        Specified by:
        +
        getJDBCMajorVersion in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        the major version (4)
        +
        +
      • +
      + + + +
        +
      • +

        getJDBCMinorVersion

        +
        public int getJDBCMinorVersion()
        +
        Gets the minor version of the supported JDBC API.
        +
        +
        Specified by:
        +
        getJDBCMinorVersion in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        the minor version (2)
        +
        +
      • +
      + + + +
        +
      • +

        getSQLStateType

        +
        public int getSQLStateType()
        +
        Gets the SQL State type.
        +
        +
        Specified by:
        +
        getSQLStateType in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        DatabaseMetaData.sqlStateSQL
        +
        +
      • +
      + + + +
        +
      • +

        locatorsUpdateCopy

        +
        public boolean locatorsUpdateCopy()
        +
        Does the database make a copy before updating.
        +
        +
        Specified by:
        +
        locatorsUpdateCopy in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        supportsStatementPooling

        +
        public boolean supportsStatementPooling()
        +
        Does the database support statement pooling.
        +
        +
        Specified by:
        +
        supportsStatementPooling in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        getRowIdLifetime

        +
        public java.sql.RowIdLifetime getRowIdLifetime()
        +
        Get the lifetime of a rowid.
        +
        +
        Specified by:
        +
        getRowIdLifetime in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        ROWID_UNSUPPORTED
        +
        +
      • +
      + + + +
        +
      • +

        getSchemas

        +
        public java.sql.ResultSet getSchemas(java.lang.String catalogPattern,
        +                                     java.lang.String schemaPattern)
        +                              throws java.sql.SQLException
        +
        Gets the list of schemas in the database. + The result set is sorted by TABLE_SCHEM. + +
          +
        1. TABLE_SCHEM (String) schema name
        2. +
        3. TABLE_CATALOG (String) catalog name
        4. +
        +
        +
        Specified by:
        +
        getSchemas in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        catalogPattern - null (to get all objects) or the catalog name
        +
        schemaPattern - null (to get all objects) or a schema name + (uppercase for unquoted names)
        +
        Returns:
        +
        the schema list
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        supportsStoredFunctionsUsingCallSyntax

        +
        public boolean supportsStoredFunctionsUsingCallSyntax()
        +
        Returns whether the database supports calling functions using the call + syntax.
        +
        +
        Specified by:
        +
        supportsStoredFunctionsUsingCallSyntax in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        autoCommitFailureClosesAllResultSets

        +
        public boolean autoCommitFailureClosesAllResultSets()
        +
        Returns whether an exception while auto commit is on closes all result + sets.
        +
        +
        Specified by:
        +
        autoCommitFailureClosesAllResultSets in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        getClientInfoProperties

        +
        public java.sql.ResultSet getClientInfoProperties()
        +                                           throws java.sql.SQLException
        +
        +
        Specified by:
        +
        getClientInfoProperties in interface java.sql.DatabaseMetaData
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        unwrap

        +
        public <T> T unwrap(java.lang.Class<T> iface)
        +             throws java.sql.SQLException
        +
        Return an object of this class if possible.
        +
        +
        Specified by:
        +
        unwrap in interface java.sql.Wrapper
        +
        Parameters:
        +
        iface - the class
        +
        Returns:
        +
        this
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        isWrapperFor

        +
        public boolean isWrapperFor(java.lang.Class<?> iface)
        +                     throws java.sql.SQLException
        +
        Checks if unwrap can return an object of this class.
        +
        +
        Specified by:
        +
        isWrapperFor in interface java.sql.Wrapper
        +
        Parameters:
        +
        iface - the class
        +
        Returns:
        +
        whether or not the interface is assignable from this class
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getFunctionColumns

        +
        public java.sql.ResultSet getFunctionColumns(java.lang.String catalog,
        +                                             java.lang.String schemaPattern,
        +                                             java.lang.String functionNamePattern,
        +                                             java.lang.String columnNamePattern)
        +                                      throws java.sql.SQLException
        +
        [Not supported] Gets the list of function columns.
        +
        +
        Specified by:
        +
        getFunctionColumns in interface java.sql.DatabaseMetaData
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getFunctions

        +
        public java.sql.ResultSet getFunctions(java.lang.String catalog,
        +                                       java.lang.String schemaPattern,
        +                                       java.lang.String functionNamePattern)
        +                                throws java.sql.SQLException
        +
        [Not supported] Gets the list of functions.
        +
        +
        Specified by:
        +
        getFunctions in interface java.sql.DatabaseMetaData
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        generatedKeyAlwaysReturned

        +
        public boolean generatedKeyAlwaysReturned()
        +
        Returns whether database always returns generated keys if valid names or + indexes of columns were specified and command was completed successfully.
        +
        +
        Specified by:
        +
        generatedKeyAlwaysReturned in interface java.sql.DatabaseMetaData
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        getPseudoColumns

        +
        public java.sql.ResultSet getPseudoColumns(java.lang.String catalog,
        +                                           java.lang.String schemaPattern,
        +                                           java.lang.String tableNamePattern,
        +                                           java.lang.String columnNamePattern)
        +                                    throws java.sql.SQLException
        +
        Gets the list of pseudo and invisible columns. The result set is sorted + by TABLE_SCHEM, TABLE_NAME, and COLUMN_NAME. + +
          +
        1. TABLE_CAT (String) table catalog
        2. +
        3. TABLE_SCHEM (String) table schema
        4. +
        5. TABLE_NAME (String) table name
        6. +
        7. COLUMN_NAME (String) column name
        8. +
        9. DATA_TYPE (int) data type (see java.sql.Types)
        10. +
        11. COLUMN_SIZE (int) precision + (values larger than 2 GB are returned as 2 GB)
        12. +
        13. DECIMAL_DIGITS (int) scale (0 for INTEGER and VARCHAR)
        14. +
        15. NUM_PREC_RADIX (int) radix
        16. +
        17. COLUMN_USAGE (String) he allowed usage for the column, + see PseudoColumnUsage
        18. +
        19. REMARKS (String) comment
        20. +
        21. CHAR_OCTET_LENGTH (int) for char types the + maximum number of bytes in the column
        22. +
        23. IS_NULLABLE (String) "NO" or "YES"
        24. +
        +
        +
        Specified by:
        +
        getPseudoColumns in interface java.sql.DatabaseMetaData
        +
        Parameters:
        +
        catalog - null (to get all objects) or the catalog name
        +
        schemaPattern - null (to get all objects) or a schema name + (uppercase for unquoted names)
        +
        tableNamePattern - null (to get all objects) or a table name + (uppercase for unquoted names)
        +
        columnNamePattern - null (to get all objects) or a column name + (uppercase for unquoted names)
        +
        Returns:
        +
        the list of pseudo and invisible columns
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        INTERNAL
        +
        +
        Overrides:
        +
        toString in class java.lang.Object
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcDatabaseMetaDataBackwardsCompat.html b/h2/docs/javadoc/org/h2/jdbc/JdbcDatabaseMetaDataBackwardsCompat.html new file mode 100644 index 0000000..1f4c417 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcDatabaseMetaDataBackwardsCompat.html @@ -0,0 +1,171 @@ + + + + + +JdbcDatabaseMetaDataBackwardsCompat + + + + + + + + + + + + +
+
org.h2.jdbc
+

Interface JdbcDatabaseMetaDataBackwardsCompat

+
+
+
+
    +
  • +
    +
    All Known Implementing Classes:
    +
    JdbcDatabaseMetaData
    +
    +
    +
    +
    public interface JdbcDatabaseMetaDataBackwardsCompat
    +
    Allows us to compile on older platforms, while still implementing the methods + from the newer JDBC API.
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcException.html b/h2/docs/javadoc/org/h2/jdbc/JdbcException.html new file mode 100644 index 0000000..13f8130 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcException.html @@ -0,0 +1,318 @@ + + + + + +JdbcException + + + + + + + + + + + + +
+
org.h2.jdbc
+

Interface JdbcException

+
+
+ +
+
    +
  • + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Abstract Methods 
      Modifier and TypeMethod and Description
      intgetErrorCode() +
      Returns the H2-specific error code.
      +
      java.lang.StringgetOriginalMessage() +
      INTERNAL
      +
      java.lang.StringgetSQL() +
      Returns the SQL statement.
      +
      voidsetSQL(java.lang.String sql) +
      INTERNAL
      +
      java.lang.StringtoString() +
      Returns the class name, the message, and in the server mode, the stack + trace of the server
      +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getErrorCode

        +
        int getErrorCode()
        +
        Returns the H2-specific error code.
        +
        +
        Returns:
        +
        the H2-specific error code
        +
        +
      • +
      + + + +
        +
      • +

        getOriginalMessage

        +
        java.lang.String getOriginalMessage()
        +
        INTERNAL
        +
        +
        Returns:
        +
        original message
        +
        +
      • +
      + + + +
        +
      • +

        getSQL

        +
        java.lang.String getSQL()
        +
        Returns the SQL statement. +

        + SQL statements that contain '--hide--' are not listed. +

        +
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        setSQL

        +
        void setSQL(java.lang.String sql)
        +
        INTERNAL
        +
        +
        Parameters:
        +
        sql - to set
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        java.lang.String toString()
        +
        Returns the class name, the message, and in the server mode, the stack + trace of the server
        +
        +
        Overrides:
        +
        toString in class java.lang.Object
        +
        Returns:
        +
        the string representation
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcLob.State.html b/h2/docs/javadoc/org/h2/jdbc/JdbcLob.State.html new file mode 100644 index 0000000..80ade8f --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcLob.State.html @@ -0,0 +1,380 @@ + + + + + +JdbcLob.State + + + + + + + + + + + + +
+
org.h2.jdbc
+

Enum JdbcLob.State

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • java.lang.Enum<JdbcLob.State>
    • +
    • +
        +
      • org.h2.jdbc.JdbcLob.State
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.lang.Comparable<JdbcLob.State>
    +
    +
    +
    Enclosing class:
    +
    JdbcLob
    +
    +
    +
    +
    public static enum JdbcLob.State
    +extends java.lang.Enum<JdbcLob.State>
    +
    State of the object.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Enum Constant Summary

      + + + + + + + + + + + + + + + + + +
      Enum Constants 
      Enum Constant and Description
      CLOSED +
      Object is closed.
      +
      NEW +
      New object without a value.
      +
      SET_CALLED +
      One of setter methods is invoked, but stream is not closed yet.
      +
      WITH_VALUE +
      A value is set.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethod and Description
      static JdbcLob.StatevalueOf(java.lang.String name) +
      Returns the enum constant of this type with the specified name.
      +
      static JdbcLob.State[]values() +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Enum

        +clone, compareTo, equals, finalize, getDeclaringClass, hashCode, name, ordinal, toString, valueOf
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +getClass, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Enum Constant Detail

      + + + +
        +
      • +

        NEW

        +
        public static final JdbcLob.State NEW
        +
        New object without a value.
        +
      • +
      + + + +
        +
      • +

        SET_CALLED

        +
        public static final JdbcLob.State SET_CALLED
        +
        One of setter methods is invoked, but stream is not closed yet.
        +
      • +
      + + + +
        +
      • +

        WITH_VALUE

        +
        public static final JdbcLob.State WITH_VALUE
        +
        A value is set.
        +
      • +
      + + + +
        +
      • +

        CLOSED

        +
        public static final JdbcLob.State CLOSED
        +
        Object is closed.
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        values

        +
        public static JdbcLob.State[] values()
        +
        Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
        +for (JdbcLob.State c : JdbcLob.State.values())
        +    System.out.println(c);
        +
        +
        +
        Returns:
        +
        an array containing the constants of this enum type, in the order they are declared
        +
        +
      • +
      + + + +
        +
      • +

        valueOf

        +
        public static JdbcLob.State valueOf(java.lang.String name)
        +
        Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
        +
        +
        Parameters:
        +
        name - the name of the enum constant to be returned.
        +
        Returns:
        +
        the enum constant with the specified name
        +
        Throws:
        +
        java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
        +
        java.lang.NullPointerException - if the argument is null
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcLob.html b/h2/docs/javadoc/org/h2/jdbc/JdbcLob.html new file mode 100644 index 0000000..b1e297a --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcLob.html @@ -0,0 +1,311 @@ + + + + + +JdbcLob + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcLob

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.message.TraceObject
    • +
    • +
        +
      • org.h2.jdbc.JdbcLob
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    Direct Known Subclasses:
    +
    JdbcBlob, JdbcClob, JdbcSQLXML
    +
    +
    +
    +
    public abstract class JdbcLob
    +extends org.h2.message.TraceObject
    +
    Represents a large object value.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Nested Class Summary

      + + + + + + + + + + +
      Nested Classes 
      Modifier and TypeClass and Description
      static class JdbcLob.State +
      State of the object.
      +
      +
    • +
    + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.message.TraceObject

        +ARRAY, BLOB, CALLABLE_STATEMENT, CLOB, CONNECTION, DATA_SOURCE, DATABASE_META_DATA, PARAMETER_META_DATA, PREPARED_STATEMENT, RESULT_SET, RESULT_SET_META_DATA, SAVEPOINT, SQLXML, STATEMENT, trace, XA_DATA_SOURCE, XID
      • +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      voidfree() +
      Release all resources of this object.
      +
      java.lang.StringtoString() +
      INTERNAL
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.message.TraceObject

        +debugCode, debugCodeAssign, debugCodeCall, debugCodeCall, debugCodeCall, getNextId, getTraceId, getTraceObjectName, isDebugEnabled, isInfoEnabled, logAndConvert, quote, quoteArray, quoteBigDecimal, quoteBytes, quoteDate, quoteIntArray, quoteMap, quoteTime, quoteTimestamp, setTrace, unsupported
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        free

        +
        public void free()
        +
        Release all resources of this object.
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        INTERNAL
        +
        +
        Overrides:
        +
        toString in class java.lang.Object
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcParameterMetaData.html b/h2/docs/javadoc/org/h2/jdbc/JdbcParameterMetaData.html new file mode 100644 index 0000000..3134713 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcParameterMetaData.html @@ -0,0 +1,585 @@ + + + + + +JdbcParameterMetaData + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcParameterMetaData

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.message.TraceObject
    • +
    • +
        +
      • org.h2.jdbc.JdbcParameterMetaData
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.sql.ParameterMetaData, java.sql.Wrapper
    +
    +
    +
    +
    public final class JdbcParameterMetaData
    +extends org.h2.message.TraceObject
    +implements java.sql.ParameterMetaData
    +
    Information about the parameters of a prepared statement.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.message.TraceObject

        +ARRAY, BLOB, CALLABLE_STATEMENT, CLOB, CONNECTION, DATA_SOURCE, DATABASE_META_DATA, PARAMETER_META_DATA, PREPARED_STATEMENT, RESULT_SET, RESULT_SET_META_DATA, SAVEPOINT, SQLXML, STATEMENT, trace, XA_DATA_SOURCE, XID
      • +
      +
        +
      • + + +

        Fields inherited from interface java.sql.ParameterMetaData

        +parameterModeIn, parameterModeInOut, parameterModeOut, parameterModeUnknown, parameterNoNulls, parameterNullable, parameterNullableUnknown
      • +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      java.lang.StringgetParameterClassName(int param) +
      Returns the Java class name of the parameter.
      +
      intgetParameterCount() +
      Returns the number of parameters.
      +
      intgetParameterMode(int param) +
      Returns the parameter mode.
      +
      intgetParameterType(int param) +
      Returns the parameter type.
      +
      java.lang.StringgetParameterTypeName(int param) +
      Returns the parameter type name.
      +
      intgetPrecision(int param) +
      Returns the parameter precision.
      +
      intgetScale(int param) +
      Returns the parameter scale.
      +
      intisNullable(int param) +
      Checks if this is nullable parameter.
      +
      booleanisSigned(int param) +
      Checks if this parameter is signed.
      +
      booleanisWrapperFor(java.lang.Class<?> iface) +
      Checks if unwrap can return an object of this class.
      +
      java.lang.StringtoString() +
      INTERNAL
      +
      <T> Tunwrap(java.lang.Class<T> iface) +
      Return an object of this class if possible.
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.message.TraceObject

        +debugCode, debugCodeAssign, debugCodeCall, debugCodeCall, debugCodeCall, getNextId, getTraceId, getTraceObjectName, isDebugEnabled, isInfoEnabled, logAndConvert, quote, quoteArray, quoteBigDecimal, quoteBytes, quoteDate, quoteIntArray, quoteMap, quoteTime, quoteTimestamp, setTrace, unsupported
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getParameterCount

        +
        public int getParameterCount()
        +                      throws java.sql.SQLException
        +
        Returns the number of parameters.
        +
        +
        Specified by:
        +
        getParameterCount in interface java.sql.ParameterMetaData
        +
        Returns:
        +
        the number
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getParameterMode

        +
        public int getParameterMode(int param)
        +                     throws java.sql.SQLException
        +
        Returns the parameter mode. + Always returns parameterModeIn.
        +
        +
        Specified by:
        +
        getParameterMode in interface java.sql.ParameterMetaData
        +
        Parameters:
        +
        param - the column index (1,2,...)
        +
        Returns:
        +
        parameterModeIn
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getParameterType

        +
        public int getParameterType(int param)
        +                     throws java.sql.SQLException
        +
        Returns the parameter type. + java.sql.Types.VARCHAR is returned if the data type is not known.
        +
        +
        Specified by:
        +
        getParameterType in interface java.sql.ParameterMetaData
        +
        Parameters:
        +
        param - the column index (1,2,...)
        +
        Returns:
        +
        the data type
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getPrecision

        +
        public int getPrecision(int param)
        +                 throws java.sql.SQLException
        +
        Returns the parameter precision. + The value 0 is returned if the precision is not known.
        +
        +
        Specified by:
        +
        getPrecision in interface java.sql.ParameterMetaData
        +
        Parameters:
        +
        param - the column index (1,2,...)
        +
        Returns:
        +
        the precision
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getScale

        +
        public int getScale(int param)
        +             throws java.sql.SQLException
        +
        Returns the parameter scale. + The value 0 is returned if the scale is not known.
        +
        +
        Specified by:
        +
        getScale in interface java.sql.ParameterMetaData
        +
        Parameters:
        +
        param - the column index (1,2,...)
        +
        Returns:
        +
        the scale
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        isNullable

        +
        public int isNullable(int param)
        +               throws java.sql.SQLException
        +
        Checks if this is nullable parameter. + Returns ResultSetMetaData.columnNullableUnknown..
        +
        +
        Specified by:
        +
        isNullable in interface java.sql.ParameterMetaData
        +
        Parameters:
        +
        param - the column index (1,2,...)
        +
        Returns:
        +
        ResultSetMetaData.columnNullableUnknown
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        isSigned

        +
        public boolean isSigned(int param)
        +                 throws java.sql.SQLException
        +
        Checks if this parameter is signed. + It always returns true.
        +
        +
        Specified by:
        +
        isSigned in interface java.sql.ParameterMetaData
        +
        Parameters:
        +
        param - the column index (1,2,...)
        +
        Returns:
        +
        true
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getParameterClassName

        +
        public java.lang.String getParameterClassName(int param)
        +                                       throws java.sql.SQLException
        +
        Returns the Java class name of the parameter. + "java.lang.String" is returned if the type is not known.
        +
        +
        Specified by:
        +
        getParameterClassName in interface java.sql.ParameterMetaData
        +
        Parameters:
        +
        param - the column index (1,2,...)
        +
        Returns:
        +
        the Java class name
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getParameterTypeName

        +
        public java.lang.String getParameterTypeName(int param)
        +                                      throws java.sql.SQLException
        +
        Returns the parameter type name. + "VARCHAR" is returned if the type is not known.
        +
        +
        Specified by:
        +
        getParameterTypeName in interface java.sql.ParameterMetaData
        +
        Parameters:
        +
        param - the column index (1,2,...)
        +
        Returns:
        +
        the type name
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        unwrap

        +
        public <T> T unwrap(java.lang.Class<T> iface)
        +             throws java.sql.SQLException
        +
        Return an object of this class if possible.
        +
        +
        Specified by:
        +
        unwrap in interface java.sql.Wrapper
        +
        Parameters:
        +
        iface - the class
        +
        Returns:
        +
        this
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        isWrapperFor

        +
        public boolean isWrapperFor(java.lang.Class<?> iface)
        +                     throws java.sql.SQLException
        +
        Checks if unwrap can return an object of this class.
        +
        +
        Specified by:
        +
        isWrapperFor in interface java.sql.Wrapper
        +
        Parameters:
        +
        iface - the class
        +
        Returns:
        +
        whether or not the interface is assignable from this class
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        INTERNAL
        +
        +
        Overrides:
        +
        toString in class java.lang.Object
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcPreparedStatement.html b/h2/docs/javadoc/org/h2/jdbc/JdbcPreparedStatement.html new file mode 100644 index 0000000..e1c3595 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcPreparedStatement.html @@ -0,0 +1,2364 @@ + + + + + +JdbcPreparedStatement + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcPreparedStatement

+
+
+
    +
  • java.lang.Object
  • +
  • + +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.lang.AutoCloseable, java.sql.PreparedStatement, java.sql.Statement, java.sql.Wrapper, JdbcStatementBackwardsCompat
    +
    +
    +
    Direct Known Subclasses:
    +
    JdbcCallableStatement
    +
    +
    +
    +
    public class JdbcPreparedStatement
    +extends JdbcStatement
    +implements java.sql.PreparedStatement
    +
    Represents a prepared statement. +

    + Thread safety: the prepared statement is not thread-safe. If the same + prepared statement is used by multiple threads access to it must be + synchronized. The single synchronized block must include assignment of + parameters, execution of the command and all operations with its result. +

    +
    + synchronized (prep) {
    +     prep.setInt(1, 10);
    +     try (ResultSet rs = prep.executeQuery()) {
    +         while (rs.next) {
    +             // Do something
    +         }
    +     }
    + }
    + synchronized (prep) {
    +     prep.setInt(1, 15);
    +     updateCount = prep.executeUpdate();
    + }
    + 
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      + + + + + + + + + + +
      Fields 
      Modifier and TypeField and Description
      protected org.h2.command.CommandInterfacecommand 
      + +
        +
      • + + +

        Fields inherited from class org.h2.message.TraceObject

        +ARRAY, BLOB, CALLABLE_STATEMENT, CLOB, CONNECTION, DATA_SOURCE, DATABASE_META_DATA, PARAMETER_META_DATA, PREPARED_STATEMENT, RESULT_SET, RESULT_SET_META_DATA, SAVEPOINT, SQLXML, STATEMENT, trace, XA_DATA_SOURCE, XID
      • +
      +
        +
      • + + +

        Fields inherited from interface java.sql.Statement

        +CLOSE_ALL_RESULTS, CLOSE_CURRENT_RESULT, EXECUTE_FAILED, KEEP_CURRENT_RESULT, NO_GENERATED_KEYS, RETURN_GENERATED_KEYS, SUCCESS_NO_INFO
      • +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods Deprecated Methods 
      Modifier and TypeMethod and Description
      voidaddBatch() +
      Adds the current settings to the batch.
      +
      voidaddBatch(java.lang.String sql) +
      Calling this method is not legal on a PreparedStatement.
      +
      voidclearBatch() +
      Clears the batch.
      +
      voidclearParameters() +
      Clears all parameters.
      +
      voidclose() +
      Closes this statement.
      +
      booleanexecute() +
      Executes an arbitrary statement.
      +
      int[]executeBatch() +
      Executes the batch.
      +
      long[]executeLargeBatch() +
      Executes the batch.
      +
      longexecuteLargeUpdate() +
      Executes a statement (insert, update, delete, create, drop) + and returns the update count.
      +
      java.sql.ResultSetexecuteQuery() +
      Executes a query (select statement) and returns the result set.
      +
      java.sql.ResultSetexecuteQuery(java.lang.String sql) +
      Calling this method is not legal on a PreparedStatement.
      +
      intexecuteUpdate() +
      Executes a statement (insert, update, delete, create, drop) + and returns the update count.
      +
      java.sql.ResultSetgetGeneratedKeys() +
      Return a result set with generated keys from the latest executed command + or an empty result set if keys were not generated or were not requested + with Statement.RETURN_GENERATED_KEYS, column indexes, or column + names.
      +
      java.sql.ResultSetMetaDatagetMetaData() +
      Gets the result set metadata of the query returned when the statement is + executed.
      +
      java.sql.ParameterMetaDatagetParameterMetaData() +
      Get the parameter meta data of this prepared statement.
      +
      voidsetArray(int parameterIndex, + java.sql.Array x) +
      Sets the value of a parameter as an Array.
      +
      voidsetAsciiStream(int parameterIndex, + java.io.InputStream x) +
      Sets the value of a parameter as an ASCII stream.
      +
      voidsetAsciiStream(int parameterIndex, + java.io.InputStream x, + int length) +
      Sets the value of a parameter as an ASCII stream.
      +
      voidsetAsciiStream(int parameterIndex, + java.io.InputStream x, + long length) +
      Sets the value of a parameter as an ASCII stream.
      +
      voidsetBigDecimal(int parameterIndex, + java.math.BigDecimal x) +
      Sets the value of a parameter.
      +
      voidsetBinaryStream(int parameterIndex, + java.io.InputStream x) +
      Sets the value of a parameter as an input stream.
      +
      voidsetBinaryStream(int parameterIndex, + java.io.InputStream x, + int length) +
      Sets the value of a parameter as an input stream.
      +
      voidsetBinaryStream(int parameterIndex, + java.io.InputStream x, + long length) +
      Sets the value of a parameter as an input stream.
      +
      voidsetBlob(int parameterIndex, + java.sql.Blob x) +
      Sets the value of a parameter as a Blob.
      +
      voidsetBlob(int parameterIndex, + java.io.InputStream x) +
      Sets the value of a parameter as a Blob.
      +
      voidsetBlob(int parameterIndex, + java.io.InputStream x, + long length) +
      Sets the value of a parameter as a Blob.
      +
      voidsetBoolean(int parameterIndex, + boolean x) +
      Sets the value of a parameter.
      +
      voidsetByte(int parameterIndex, + byte x) +
      Sets the value of a parameter.
      +
      voidsetBytes(int parameterIndex, + byte[] x) +
      Sets the value of a parameter as a byte array.
      +
      voidsetCharacterStream(int parameterIndex, + java.io.Reader x) +
      Sets the value of a parameter as a character stream.
      +
      voidsetCharacterStream(int parameterIndex, + java.io.Reader x, + int length) +
      Sets the value of a parameter as a character stream.
      +
      voidsetCharacterStream(int parameterIndex, + java.io.Reader x, + long length) +
      Sets the value of a parameter as a character stream.
      +
      voidsetClob(int parameterIndex, + java.sql.Clob x) +
      Sets the value of a parameter as a Clob.
      +
      voidsetClob(int parameterIndex, + java.io.Reader x) +
      Sets the value of a parameter as a Clob.
      +
      voidsetClob(int parameterIndex, + java.io.Reader x, + long length) +
      Sets the value of a parameter as a Clob.
      +
      voidsetDate(int parameterIndex, + java.sql.Date x) +
      Sets the value of a parameter.
      +
      voidsetDate(int parameterIndex, + java.sql.Date x, + java.util.Calendar calendar) +
      Sets the date using a specified time zone.
      +
      voidsetDouble(int parameterIndex, + double x) +
      Sets the value of a parameter.
      +
      voidsetFloat(int parameterIndex, + float x) +
      Sets the value of a parameter.
      +
      voidsetInt(int parameterIndex, + int x) +
      Sets the value of a parameter.
      +
      voidsetLong(int parameterIndex, + long x) +
      Sets the value of a parameter.
      +
      voidsetNCharacterStream(int parameterIndex, + java.io.Reader x) +
      Sets the value of a parameter as a character stream.
      +
      voidsetNCharacterStream(int parameterIndex, + java.io.Reader x, + long length) +
      Sets the value of a parameter as a character stream.
      +
      voidsetNClob(int parameterIndex, + java.sql.NClob x) +
      Sets the value of a parameter as a Clob.
      +
      voidsetNClob(int parameterIndex, + java.io.Reader x) +
      Sets the value of a parameter as a Clob.
      +
      voidsetNClob(int parameterIndex, + java.io.Reader x, + long length) +
      Sets the value of a parameter as a Clob.
      +
      voidsetNString(int parameterIndex, + java.lang.String x) +
      Sets the value of a parameter.
      +
      voidsetNull(int parameterIndex, + int sqlType) +
      Sets a parameter to null.
      +
      voidsetNull(int parameterIndex, + int sqlType, + java.lang.String typeName) +
      Sets a parameter to null.
      +
      voidsetObject(int parameterIndex, + java.lang.Object x) +
      Sets the value of a parameter.
      +
      voidsetObject(int parameterIndex, + java.lang.Object x, + int targetSqlType) +
      Sets the value of a parameter.
      +
      voidsetObject(int parameterIndex, + java.lang.Object x, + int targetSqlType, + int scale) +
      Sets the value of a parameter.
      +
      voidsetObject(int parameterIndex, + java.lang.Object x, + java.sql.SQLType targetSqlType) +
      Sets the value of a parameter.
      +
      voidsetObject(int parameterIndex, + java.lang.Object x, + java.sql.SQLType targetSqlType, + int scaleOrLength) +
      Sets the value of a parameter.
      +
      voidsetRef(int parameterIndex, + java.sql.Ref x) +
      [Not supported] Sets the value of a column as a reference.
      +
      voidsetRowId(int parameterIndex, + java.sql.RowId x) +
      [Not supported] Sets the value of a parameter as a row id.
      +
      voidsetShort(int parameterIndex, + short x) +
      Sets the value of a parameter.
      +
      voidsetSQLXML(int parameterIndex, + java.sql.SQLXML x) +
      Sets the value of a parameter as a SQLXML.
      +
      voidsetString(int parameterIndex, + java.lang.String x) +
      Sets the value of a parameter.
      +
      voidsetTime(int parameterIndex, + java.sql.Time x) +
      Sets the value of a parameter.
      +
      voidsetTime(int parameterIndex, + java.sql.Time x, + java.util.Calendar calendar) +
      Sets the time using a specified time zone.
      +
      voidsetTimestamp(int parameterIndex, + java.sql.Timestamp x) +
      Sets the value of a parameter.
      +
      voidsetTimestamp(int parameterIndex, + java.sql.Timestamp x, + java.util.Calendar calendar) +
      Sets the timestamp using a specified time zone.
      +
      voidsetUnicodeStream(int parameterIndex, + java.io.InputStream x, + int length) +
      Deprecated.  +
      since JDBC 2.0, use setCharacterStream
      +
      +
      voidsetURL(int parameterIndex, + java.net.URL x) +
      [Not supported]
      +
      java.lang.StringtoString() +
      INTERNAL
      +
      + +
        +
      • + + +

        Methods inherited from class org.h2.message.TraceObject

        +debugCode, debugCodeAssign, debugCodeCall, debugCodeCall, debugCodeCall, getNextId, getTraceId, getTraceObjectName, isDebugEnabled, isInfoEnabled, logAndConvert, quote, quoteArray, quoteBigDecimal, quoteBytes, quoteDate, quoteIntArray, quoteMap, quoteTime, quoteTimestamp, setTrace, unsupported
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
        +
      • + + +

        Methods inherited from interface java.sql.Statement

        +cancel, clearWarnings, closeOnCompletion, execute, execute, execute, execute, executeLargeUpdate, executeLargeUpdate, executeLargeUpdate, executeLargeUpdate, executeUpdate, executeUpdate, executeUpdate, executeUpdate, getConnection, getFetchDirection, getFetchSize, getLargeMaxRows, getLargeUpdateCount, getMaxFieldSize, getMaxRows, getMoreResults, getMoreResults, getQueryTimeout, getResultSet, getResultSetConcurrency, getResultSetHoldability, getResultSetType, getUpdateCount, getWarnings, isClosed, isCloseOnCompletion, isPoolable, setCursorName, setEscapeProcessing, setFetchDirection, setFetchSize, setLargeMaxRows, setMaxFieldSize, setMaxRows, setPoolable, setQueryTimeout
      • +
      +
        +
      • + + +

        Methods inherited from interface java.sql.Wrapper

        +isWrapperFor, unwrap
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Detail

      + + + +
        +
      • +

        command

        +
        protected org.h2.command.CommandInterface command
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        executeQuery

        +
        public java.sql.ResultSet executeQuery()
        +                                throws java.sql.SQLException
        +
        Executes a query (select statement) and returns the result set. If + another result set exists for this statement, this will be closed (even + if this statement fails).
        +
        +
        Specified by:
        +
        executeQuery in interface java.sql.PreparedStatement
        +
        Returns:
        +
        the result set
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        executeUpdate

        +
        public int executeUpdate()
        +                  throws java.sql.SQLException
        +
        Executes a statement (insert, update, delete, create, drop) + and returns the update count. + If another result set exists for this statement, this will be closed + (even if this statement fails). + + If auto commit is on, this statement will be committed. + If the statement is a DDL statement (create, drop, alter) and does not + throw an exception, the current transaction (if any) is committed after + executing the statement.
        +
        +
        Specified by:
        +
        executeUpdate in interface java.sql.PreparedStatement
        +
        Returns:
        +
        the update count (number of affected rows by a DML statement or + other statement able to return number of rows, or 0 if no rows + were affected or the statement returns nothing, or + Statement.SUCCESS_NO_INFO if number of rows is too large for + int data type)
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed or invalid
        +
        See Also:
        +
        executeLargeUpdate()
        +
        +
      • +
      + + + +
        +
      • +

        executeLargeUpdate

        +
        public long executeLargeUpdate()
        +                        throws java.sql.SQLException
        +
        Executes a statement (insert, update, delete, create, drop) + and returns the update count. + If another result set exists for this statement, this will be closed + (even if this statement fails). + + If auto commit is on, this statement will be committed. + If the statement is a DDL statement (create, drop, alter) and does not + throw an exception, the current transaction (if any) is committed after + executing the statement.
        +
        +
        Specified by:
        +
        executeLargeUpdate in interface java.sql.PreparedStatement
        +
        Returns:
        +
        the update count (number of affected rows by a DML statement or + other statement able to return number of rows, or 0 if no rows + were affected or the statement returns nothing)
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        execute

        +
        public boolean execute()
        +                throws java.sql.SQLException
        +
        Executes an arbitrary statement. If another result set exists for this + statement, this will be closed (even if this statement fails). If auto + commit is on, and the statement is not a select, this statement will be + committed.
        +
        +
        Specified by:
        +
        execute in interface java.sql.PreparedStatement
        +
        Returns:
        +
        true if a result set is available, false if not
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        clearParameters

        +
        public void clearParameters()
        +                     throws java.sql.SQLException
        +
        Clears all parameters.
        +
        +
        Specified by:
        +
        clearParameters in interface java.sql.PreparedStatement
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        executeQuery

        +
        public java.sql.ResultSet executeQuery(java.lang.String sql)
        +                                throws java.sql.SQLException
        +
        Calling this method is not legal on a PreparedStatement.
        +
        +
        Specified by:
        +
        executeQuery in interface java.sql.Statement
        +
        Overrides:
        +
        executeQuery in class JdbcStatement
        +
        Parameters:
        +
        sql - ignored
        +
        Returns:
        +
        the result set
        +
        Throws:
        +
        java.sql.SQLException - Unsupported Feature
        +
        +
      • +
      + + + +
        +
      • +

        addBatch

        +
        public void addBatch(java.lang.String sql)
        +              throws java.sql.SQLException
        +
        Calling this method is not legal on a PreparedStatement.
        +
        +
        Specified by:
        +
        addBatch in interface java.sql.Statement
        +
        Overrides:
        +
        addBatch in class JdbcStatement
        +
        Parameters:
        +
        sql - ignored
        +
        Throws:
        +
        java.sql.SQLException - Unsupported Feature
        +
        +
      • +
      + + + +
        +
      • +

        setNull

        +
        public void setNull(int parameterIndex,
        +                    int sqlType)
        +             throws java.sql.SQLException
        +
        Sets a parameter to null.
        +
        +
        Specified by:
        +
        setNull in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        sqlType - the data type (Types.x)
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setInt

        +
        public void setInt(int parameterIndex,
        +                   int x)
        +            throws java.sql.SQLException
        +
        Sets the value of a parameter.
        +
        +
        Specified by:
        +
        setInt in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setString

        +
        public void setString(int parameterIndex,
        +                      java.lang.String x)
        +               throws java.sql.SQLException
        +
        Sets the value of a parameter.
        +
        +
        Specified by:
        +
        setString in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setBigDecimal

        +
        public void setBigDecimal(int parameterIndex,
        +                          java.math.BigDecimal x)
        +                   throws java.sql.SQLException
        +
        Sets the value of a parameter.
        +
        +
        Specified by:
        +
        setBigDecimal in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setDate

        +
        public void setDate(int parameterIndex,
        +                    java.sql.Date x)
        +             throws java.sql.SQLException
        +
        Sets the value of a parameter. +

        + Usage of this method is discouraged. Use + setObject(parameterIndex, value) with LocalDate + parameter instead. +

        +
        +
        Specified by:
        +
        setDate in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        See Also:
        +
        setObject(int, Object)
        +
        +
      • +
      + + + +
        +
      • +

        setTime

        +
        public void setTime(int parameterIndex,
        +                    java.sql.Time x)
        +             throws java.sql.SQLException
        +
        Sets the value of a parameter. +

        + Usage of this method is discouraged. Use + setObject(parameterIndex, value) with LocalTime + parameter instead. +

        +
        +
        Specified by:
        +
        setTime in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        See Also:
        +
        setObject(int, Object)
        +
        +
      • +
      + + + +
        +
      • +

        setTimestamp

        +
        public void setTimestamp(int parameterIndex,
        +                         java.sql.Timestamp x)
        +                  throws java.sql.SQLException
        +
        Sets the value of a parameter. +

        + Usage of this method is discouraged. Use + setObject(parameterIndex, value) with + LocalDateTime parameter instead. +

        +
        +
        Specified by:
        +
        setTimestamp in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        See Also:
        +
        setObject(int, Object)
        +
        +
      • +
      + + + +
        +
      • +

        setObject

        +
        public void setObject(int parameterIndex,
        +                      java.lang.Object x)
        +               throws java.sql.SQLException
        +
        Sets the value of a parameter. + Objects of unknown classes are serialized (on the client side).
        +
        +
        Specified by:
        +
        setObject in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setObject

        +
        public void setObject(int parameterIndex,
        +                      java.lang.Object x,
        +                      int targetSqlType)
        +               throws java.sql.SQLException
        +
        Sets the value of a parameter. The object is converted, if required, to + the specified data type before sending to the database. + Objects of unknown classes are serialized (on the client side).
        +
        +
        Specified by:
        +
        setObject in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value, null is allowed
        +
        targetSqlType - the type as defined in java.sql.Types
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setObject

        +
        public void setObject(int parameterIndex,
        +                      java.lang.Object x,
        +                      int targetSqlType,
        +                      int scale)
        +               throws java.sql.SQLException
        +
        Sets the value of a parameter. The object is converted, if required, to + the specified data type before sending to the database. + Objects of unknown classes are serialized (on the client side).
        +
        +
        Specified by:
        +
        setObject in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value, null is allowed
        +
        targetSqlType - the type as defined in java.sql.Types
        +
        scale - is ignored
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setObject

        +
        public void setObject(int parameterIndex,
        +                      java.lang.Object x,
        +                      java.sql.SQLType targetSqlType)
        +               throws java.sql.SQLException
        +
        Sets the value of a parameter. The object is converted, if required, to + the specified data type before sending to the database. + Objects of unknown classes are serialized (on the client side).
        +
        +
        Specified by:
        +
        setObject in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value, null is allowed
        +
        targetSqlType - the SQL type
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setObject

        +
        public void setObject(int parameterIndex,
        +                      java.lang.Object x,
        +                      java.sql.SQLType targetSqlType,
        +                      int scaleOrLength)
        +               throws java.sql.SQLException
        +
        Sets the value of a parameter. The object is converted, if required, to + the specified data type before sending to the database. + Objects of unknown classes are serialized (on the client side).
        +
        +
        Specified by:
        +
        setObject in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value, null is allowed
        +
        targetSqlType - the SQL type
        +
        scaleOrLength - is ignored
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setBoolean

        +
        public void setBoolean(int parameterIndex,
        +                       boolean x)
        +                throws java.sql.SQLException
        +
        Sets the value of a parameter.
        +
        +
        Specified by:
        +
        setBoolean in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setByte

        +
        public void setByte(int parameterIndex,
        +                    byte x)
        +             throws java.sql.SQLException
        +
        Sets the value of a parameter.
        +
        +
        Specified by:
        +
        setByte in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setShort

        +
        public void setShort(int parameterIndex,
        +                     short x)
        +              throws java.sql.SQLException
        +
        Sets the value of a parameter.
        +
        +
        Specified by:
        +
        setShort in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setLong

        +
        public void setLong(int parameterIndex,
        +                    long x)
        +             throws java.sql.SQLException
        +
        Sets the value of a parameter.
        +
        +
        Specified by:
        +
        setLong in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setFloat

        +
        public void setFloat(int parameterIndex,
        +                     float x)
        +              throws java.sql.SQLException
        +
        Sets the value of a parameter.
        +
        +
        Specified by:
        +
        setFloat in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setDouble

        +
        public void setDouble(int parameterIndex,
        +                      double x)
        +               throws java.sql.SQLException
        +
        Sets the value of a parameter.
        +
        +
        Specified by:
        +
        setDouble in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setRef

        +
        public void setRef(int parameterIndex,
        +                   java.sql.Ref x)
        +            throws java.sql.SQLException
        +
        [Not supported] Sets the value of a column as a reference.
        +
        +
        Specified by:
        +
        setRef in interface java.sql.PreparedStatement
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        setDate

        +
        public void setDate(int parameterIndex,
        +                    java.sql.Date x,
        +                    java.util.Calendar calendar)
        +             throws java.sql.SQLException
        +
        Sets the date using a specified time zone. The value will be converted to + the local time zone. +

        + Usage of this method is discouraged. Use + setObject(parameterIndex, value) with LocalDate + parameter instead. +

        +
        +
        Specified by:
        +
        setDate in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        calendar - the calendar
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        See Also:
        +
        setObject(int, Object)
        +
        +
      • +
      + + + +
        +
      • +

        setTime

        +
        public void setTime(int parameterIndex,
        +                    java.sql.Time x,
        +                    java.util.Calendar calendar)
        +             throws java.sql.SQLException
        +
        Sets the time using a specified time zone. The value will be converted to + the local time zone. +

        + Usage of this method is discouraged. Use + setObject(parameterIndex, value) with LocalTime + parameter instead. +

        +
        +
        Specified by:
        +
        setTime in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        calendar - the calendar
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        See Also:
        +
        setObject(int, Object)
        +
        +
      • +
      + + + +
        +
      • +

        setTimestamp

        +
        public void setTimestamp(int parameterIndex,
        +                         java.sql.Timestamp x,
        +                         java.util.Calendar calendar)
        +                  throws java.sql.SQLException
        +
        Sets the timestamp using a specified time zone. The value will be + converted to the local time zone. +

        + Usage of this method is discouraged. Use + setObject(parameterIndex, value) with + LocalDateTime parameter instead. +

        +
        +
        Specified by:
        +
        setTimestamp in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        calendar - the calendar
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        See Also:
        +
        setObject(int, Object)
        +
        +
      • +
      + + + +
        +
      • +

        setUnicodeStream

        +
        @Deprecated
        +public void setUnicodeStream(int parameterIndex,
        +                                         java.io.InputStream x,
        +                                         int length)
        +                                  throws java.sql.SQLException
        +
        Deprecated. since JDBC 2.0, use setCharacterStream
        +
        [Not supported] This feature is deprecated and not supported.
        +
        +
        Specified by:
        +
        setUnicodeStream in interface java.sql.PreparedStatement
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        setNull

        +
        public void setNull(int parameterIndex,
        +                    int sqlType,
        +                    java.lang.String typeName)
        +             throws java.sql.SQLException
        +
        Sets a parameter to null.
        +
        +
        Specified by:
        +
        setNull in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        sqlType - the data type (Types.x)
        +
        typeName - this parameter is ignored
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setBlob

        +
        public void setBlob(int parameterIndex,
        +                    java.sql.Blob x)
        +             throws java.sql.SQLException
        +
        Sets the value of a parameter as a Blob.
        +
        +
        Specified by:
        +
        setBlob in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setBlob

        +
        public void setBlob(int parameterIndex,
        +                    java.io.InputStream x)
        +             throws java.sql.SQLException
        +
        Sets the value of a parameter as a Blob. + This method does not close the stream. + The stream may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setBlob in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setClob

        +
        public void setClob(int parameterIndex,
        +                    java.sql.Clob x)
        +             throws java.sql.SQLException
        +
        Sets the value of a parameter as a Clob.
        +
        +
        Specified by:
        +
        setClob in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setClob

        +
        public void setClob(int parameterIndex,
        +                    java.io.Reader x)
        +             throws java.sql.SQLException
        +
        Sets the value of a parameter as a Clob. + This method does not close the reader. + The reader may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setClob in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setArray

        +
        public void setArray(int parameterIndex,
        +                     java.sql.Array x)
        +              throws java.sql.SQLException
        +
        Sets the value of a parameter as an Array.
        +
        +
        Specified by:
        +
        setArray in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setBytes

        +
        public void setBytes(int parameterIndex,
        +                     byte[] x)
        +              throws java.sql.SQLException
        +
        Sets the value of a parameter as a byte array.
        +
        +
        Specified by:
        +
        setBytes in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setBinaryStream

        +
        public void setBinaryStream(int parameterIndex,
        +                            java.io.InputStream x,
        +                            long length)
        +                     throws java.sql.SQLException
        +
        Sets the value of a parameter as an input stream. + This method does not close the stream. + The stream may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setBinaryStream in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        length - the maximum number of bytes
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setBinaryStream

        +
        public void setBinaryStream(int parameterIndex,
        +                            java.io.InputStream x,
        +                            int length)
        +                     throws java.sql.SQLException
        +
        Sets the value of a parameter as an input stream. + This method does not close the stream. + The stream may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setBinaryStream in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        length - the maximum number of bytes
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setBinaryStream

        +
        public void setBinaryStream(int parameterIndex,
        +                            java.io.InputStream x)
        +                     throws java.sql.SQLException
        +
        Sets the value of a parameter as an input stream. + This method does not close the stream. + The stream may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setBinaryStream in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setAsciiStream

        +
        public void setAsciiStream(int parameterIndex,
        +                           java.io.InputStream x,
        +                           int length)
        +                    throws java.sql.SQLException
        +
        Sets the value of a parameter as an ASCII stream. + This method does not close the stream. + The stream may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setAsciiStream in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        length - the maximum number of bytes
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setAsciiStream

        +
        public void setAsciiStream(int parameterIndex,
        +                           java.io.InputStream x,
        +                           long length)
        +                    throws java.sql.SQLException
        +
        Sets the value of a parameter as an ASCII stream. + This method does not close the stream. + The stream may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setAsciiStream in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        length - the maximum number of bytes
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setAsciiStream

        +
        public void setAsciiStream(int parameterIndex,
        +                           java.io.InputStream x)
        +                    throws java.sql.SQLException
        +
        Sets the value of a parameter as an ASCII stream. + This method does not close the stream. + The stream may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setAsciiStream in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setCharacterStream

        +
        public void setCharacterStream(int parameterIndex,
        +                               java.io.Reader x,
        +                               int length)
        +                        throws java.sql.SQLException
        +
        Sets the value of a parameter as a character stream. + This method does not close the reader. + The reader may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setCharacterStream in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        length - the maximum number of characters
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setCharacterStream

        +
        public void setCharacterStream(int parameterIndex,
        +                               java.io.Reader x)
        +                        throws java.sql.SQLException
        +
        Sets the value of a parameter as a character stream. + This method does not close the reader. + The reader may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setCharacterStream in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setCharacterStream

        +
        public void setCharacterStream(int parameterIndex,
        +                               java.io.Reader x,
        +                               long length)
        +                        throws java.sql.SQLException
        +
        Sets the value of a parameter as a character stream. + This method does not close the reader. + The reader may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setCharacterStream in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        length - the maximum number of characters
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setURL

        +
        public void setURL(int parameterIndex,
        +                   java.net.URL x)
        +            throws java.sql.SQLException
        +
        [Not supported]
        +
        +
        Specified by:
        +
        setURL in interface java.sql.PreparedStatement
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getMetaData

        +
        public java.sql.ResultSetMetaData getMetaData()
        +                                       throws java.sql.SQLException
        +
        Gets the result set metadata of the query returned when the statement is + executed. If this is not a query, this method returns null.
        +
        +
        Specified by:
        +
        getMetaData in interface java.sql.PreparedStatement
        +
        Returns:
        +
        the meta data or null if this is not a query
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        clearBatch

        +
        public void clearBatch()
        +                throws java.sql.SQLException
        +
        Clears the batch.
        +
        +
        Specified by:
        +
        clearBatch in interface java.sql.Statement
        +
        Overrides:
        +
        clearBatch in class JdbcStatement
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        close

        +
        public void close()
        +           throws java.sql.SQLException
        +
        Closes this statement. + All result sets that where created by this statement + become invalid after calling this method.
        +
        +
        Specified by:
        +
        close in interface java.lang.AutoCloseable
        +
        Specified by:
        +
        close in interface java.sql.Statement
        +
        Overrides:
        +
        close in class JdbcStatement
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        executeBatch

        +
        public int[] executeBatch()
        +                   throws java.sql.SQLException
        +
        Executes the batch. + If one of the batched statements fails, this database will continue.
        +
        +
        Specified by:
        +
        executeBatch in interface java.sql.Statement
        +
        Overrides:
        +
        executeBatch in class JdbcStatement
        +
        Returns:
        +
        the array of update counts
        +
        Throws:
        +
        java.sql.SQLException
        +
        See Also:
        +
        executeLargeBatch()
        +
        +
      • +
      + + + +
        +
      • +

        executeLargeBatch

        +
        public long[] executeLargeBatch()
        +                         throws java.sql.SQLException
        +
        Executes the batch. + If one of the batched statements fails, this database will continue.
        +
        +
        Specified by:
        +
        executeLargeBatch in interface java.sql.Statement
        +
        Overrides:
        +
        executeLargeBatch in class JdbcStatement
        +
        Returns:
        +
        the array of update counts
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getGeneratedKeys

        +
        public java.sql.ResultSet getGeneratedKeys()
        +                                    throws java.sql.SQLException
        +
        Description copied from class: JdbcStatement
        +
        Return a result set with generated keys from the latest executed command + or an empty result set if keys were not generated or were not requested + with Statement.RETURN_GENERATED_KEYS, column indexes, or column + names. +

        + Generated keys are only returned from from INSERT, + UPDATE, MERGE INTO, and MERGE INTO ... USING + commands. +

        +

        + If SQL command inserts or updates multiple rows with generated keys each + such inserted or updated row is returned. Batch methods are also + supported. +

        +

        + When Statement.RETURN_GENERATED_KEYS is used H2 chooses columns + to return automatically. The following columns are chosen: +

        +
          +
        • Columns with sequences including IDENTITY columns and columns + with AUTO_INCREMENT.
        • +
        • Columns with other default values that are not evaluated into + constant expressions (like DEFAULT RANDOM_UUID()).
        • +
        • Columns that are included into the PRIMARY KEY constraint.
        • +
        +

        + Exact required columns for the returning result set may be specified on + execution of command with names or indexes of columns. +

        +
        +
        Specified by:
        +
        getGeneratedKeys in interface java.sql.Statement
        +
        Overrides:
        +
        getGeneratedKeys in class JdbcStatement
        +
        Returns:
        +
        the possibly empty result set with generated keys
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        addBatch

        +
        public void addBatch()
        +              throws java.sql.SQLException
        +
        Adds the current settings to the batch.
        +
        +
        Specified by:
        +
        addBatch in interface java.sql.PreparedStatement
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getParameterMetaData

        +
        public java.sql.ParameterMetaData getParameterMetaData()
        +                                                throws java.sql.SQLException
        +
        Get the parameter meta data of this prepared statement.
        +
        +
        Specified by:
        +
        getParameterMetaData in interface java.sql.PreparedStatement
        +
        Returns:
        +
        the meta data
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        setRowId

        +
        public void setRowId(int parameterIndex,
        +                     java.sql.RowId x)
        +              throws java.sql.SQLException
        +
        [Not supported] Sets the value of a parameter as a row id.
        +
        +
        Specified by:
        +
        setRowId in interface java.sql.PreparedStatement
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        setNString

        +
        public void setNString(int parameterIndex,
        +                       java.lang.String x)
        +                throws java.sql.SQLException
        +
        Sets the value of a parameter.
        +
        +
        Specified by:
        +
        setNString in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setNCharacterStream

        +
        public void setNCharacterStream(int parameterIndex,
        +                                java.io.Reader x,
        +                                long length)
        +                         throws java.sql.SQLException
        +
        Sets the value of a parameter as a character stream. + This method does not close the reader. + The reader may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setNCharacterStream in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        length - the maximum number of characters
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setNCharacterStream

        +
        public void setNCharacterStream(int parameterIndex,
        +                                java.io.Reader x)
        +                         throws java.sql.SQLException
        +
        Sets the value of a parameter as a character stream. + This method does not close the reader. + The reader may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setNCharacterStream in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setNClob

        +
        public void setNClob(int parameterIndex,
        +                     java.sql.NClob x)
        +              throws java.sql.SQLException
        +
        Sets the value of a parameter as a Clob.
        +
        +
        Specified by:
        +
        setNClob in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setNClob

        +
        public void setNClob(int parameterIndex,
        +                     java.io.Reader x)
        +              throws java.sql.SQLException
        +
        Sets the value of a parameter as a Clob. + This method does not close the reader. + The reader may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setNClob in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setClob

        +
        public void setClob(int parameterIndex,
        +                    java.io.Reader x,
        +                    long length)
        +             throws java.sql.SQLException
        +
        Sets the value of a parameter as a Clob. This method does not close the + reader. The reader may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setClob in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        length - the maximum number of characters
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setBlob

        +
        public void setBlob(int parameterIndex,
        +                    java.io.InputStream x,
        +                    long length)
        +             throws java.sql.SQLException
        +
        Sets the value of a parameter as a Blob. + This method does not close the stream. + The stream may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setBlob in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        length - the maximum number of bytes
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setNClob

        +
        public void setNClob(int parameterIndex,
        +                     java.io.Reader x,
        +                     long length)
        +              throws java.sql.SQLException
        +
        Sets the value of a parameter as a Clob. + This method does not close the reader. + The reader may be closed after executing the statement.
        +
        +
        Specified by:
        +
        setNClob in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        length - the maximum number of characters
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setSQLXML

        +
        public void setSQLXML(int parameterIndex,
        +                      java.sql.SQLXML x)
        +               throws java.sql.SQLException
        +
        Sets the value of a parameter as a SQLXML.
        +
        +
        Specified by:
        +
        setSQLXML in interface java.sql.PreparedStatement
        +
        Parameters:
        +
        parameterIndex - the parameter index (1, 2, ...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        INTERNAL
        +
        +
        Overrides:
        +
        toString in class JdbcStatement
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcResultSet.html b/h2/docs/javadoc/org/h2/jdbc/JdbcResultSet.html new file mode 100644 index 0000000..9c8e4ae --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcResultSet.html @@ -0,0 +1,5990 @@ + + + + + +JdbcResultSet + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcResultSet

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.message.TraceObject
    • +
    • +
        +
      • org.h2.jdbc.JdbcResultSet
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.lang.AutoCloseable, java.sql.ResultSet, java.sql.Wrapper
    +
    +
    +
    +
    public final class JdbcResultSet
    +extends org.h2.message.TraceObject
    +implements java.sql.ResultSet
    +
    Represents a result set. +

    + Column labels are case-insensitive, quotes are not supported. The first + column has the column index 1. +

    +

    + Thread safety: the result set is not thread-safe and must not be used by + multiple threads concurrently. +

    +

    + Updatable result sets: Result sets are updatable when the result only + contains columns from one table, and if it contains all columns of a unique + index (primary key or other) of this table. Key columns may not contain NULL + (because multiple rows with NULL could exist). In updatable result sets, own + changes are visible, but not own inserts and deletes. +

    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.message.TraceObject

        +ARRAY, BLOB, CALLABLE_STATEMENT, CLOB, CONNECTION, DATA_SOURCE, DATABASE_META_DATA, PARAMETER_META_DATA, PREPARED_STATEMENT, RESULT_SET, RESULT_SET_META_DATA, SAVEPOINT, SQLXML, STATEMENT, trace, XA_DATA_SOURCE, XID
      • +
      +
        +
      • + + +

        Fields inherited from interface java.sql.ResultSet

        +CLOSE_CURSORS_AT_COMMIT, CONCUR_READ_ONLY, CONCUR_UPDATABLE, FETCH_FORWARD, FETCH_REVERSE, FETCH_UNKNOWN, HOLD_CURSORS_OVER_COMMIT, TYPE_FORWARD_ONLY, TYPE_SCROLL_INSENSITIVE, TYPE_SCROLL_SENSITIVE
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      JdbcResultSet(JdbcConnection conn, + JdbcStatement stat, + org.h2.command.CommandInterface command, + org.h2.result.ResultInterface result, + int id, + boolean scrollable, + boolean updatable, + boolean triggerUpdatable) 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods Deprecated Methods 
      Modifier and TypeMethod and Description
      booleanabsolute(int rowNumber) +
      Moves the current position to a specific row.
      +
      voidafterLast() +
      Moves the current position to after the last row, that means after the + end.
      +
      voidbeforeFirst() +
      Moves the current position to before the first row, that means resets the + result set.
      +
      voidcancelRowUpdates() +
      Cancels updating a row.
      +
      voidclearWarnings() +
      Clears all warnings.
      +
      voidclose() +
      Closes the result set.
      +
      voiddeleteRow() +
      Deletes the current row.
      +
      intfindColumn(java.lang.String columnLabel) +
      Searches for a specific column in the result set.
      +
      booleanfirst() +
      Moves the current position to the first row.
      +
      java.sql.ArraygetArray(int columnIndex) +
      Returns the value of the specified column as an Array.
      +
      java.sql.ArraygetArray(java.lang.String columnLabel) +
      Returns the value of the specified column as an Array.
      +
      java.io.InputStreamgetAsciiStream(int columnIndex) +
      Returns the value of the specified column as an input stream.
      +
      java.io.InputStreamgetAsciiStream(java.lang.String columnLabel) +
      Returns the value of the specified column as an input stream.
      +
      java.math.BigDecimalgetBigDecimal(int columnIndex) +
      Returns the value of the specified column as a BigDecimal.
      +
      java.math.BigDecimalgetBigDecimal(int columnIndex, + int scale) +
      Deprecated.  + +
      +
      java.math.BigDecimalgetBigDecimal(java.lang.String columnLabel) +
      Returns the value of the specified column as a BigDecimal.
      +
      java.math.BigDecimalgetBigDecimal(java.lang.String columnLabel, + int scale) +
      Deprecated.  + +
      +
      java.io.InputStreamgetBinaryStream(int columnIndex) +
      Returns the value of the specified column as an input stream.
      +
      java.io.InputStreamgetBinaryStream(java.lang.String columnLabel) +
      Returns the value of the specified column as an input stream.
      +
      java.sql.BlobgetBlob(int columnIndex) +
      Returns the value of the specified column as a Blob.
      +
      java.sql.BlobgetBlob(java.lang.String columnLabel) +
      Returns the value of the specified column as a Blob.
      +
      booleangetBoolean(int columnIndex) +
      Returns the value of the specified column as a boolean.
      +
      booleangetBoolean(java.lang.String columnLabel) +
      Returns the value of the specified column as a boolean.
      +
      bytegetByte(int columnIndex) +
      Returns the value of the specified column as a byte.
      +
      bytegetByte(java.lang.String columnLabel) +
      Returns the value of the specified column as a byte.
      +
      byte[]getBytes(int columnIndex) +
      Returns the value of the specified column as a byte array.
      +
      byte[]getBytes(java.lang.String columnLabel) +
      Returns the value of the specified column as a byte array.
      +
      java.io.ReadergetCharacterStream(int columnIndex) +
      Returns the value of the specified column as a reader.
      +
      java.io.ReadergetCharacterStream(java.lang.String columnLabel) +
      Returns the value of the specified column as a reader.
      +
      java.sql.ClobgetClob(int columnIndex) +
      Returns the value of the specified column as a Clob.
      +
      java.sql.ClobgetClob(java.lang.String columnLabel) +
      Returns the value of the specified column as a Clob.
      +
      intgetConcurrency() +
      Gets the result set concurrency.
      +
      java.lang.StringgetCursorName() +
      [Not supported] Gets the cursor name if it was defined.
      +
      java.sql.DategetDate(int columnIndex) +
      Returns the value of the specified column as a java.sql.Date.
      +
      java.sql.DategetDate(int columnIndex, + java.util.Calendar calendar) +
      Returns the value of the specified column as a java.sql.Date using a + specified time zone.
      +
      java.sql.DategetDate(java.lang.String columnLabel) +
      Returns the value of the specified column as a java.sql.Date.
      +
      java.sql.DategetDate(java.lang.String columnLabel, + java.util.Calendar calendar) +
      Returns the value of the specified column as a java.sql.Date using a + specified time zone.
      +
      doublegetDouble(int columnIndex) +
      Returns the value of the specified column as a double.
      +
      doublegetDouble(java.lang.String columnLabel) +
      Returns the value of the specified column as a double.
      +
      intgetFetchDirection() +
      Gets the fetch direction.
      +
      intgetFetchSize() +
      Gets the number of rows suggested to read in one step.
      +
      floatgetFloat(int columnIndex) +
      Returns the value of the specified column as a float.
      +
      floatgetFloat(java.lang.String columnLabel) +
      Returns the value of the specified column as a float.
      +
      intgetHoldability() +
      Returns the current result set holdability.
      +
      intgetInt(int columnIndex) +
      Returns the value of the specified column as an int.
      +
      intgetInt(java.lang.String columnLabel) +
      Returns the value of the specified column as an int.
      +
      org.h2.value.ValuegetInternal(int columnIndex) +
      INTERNAL
      +
      longgetLong(int columnIndex) +
      Returns the value of the specified column as a long.
      +
      longgetLong(java.lang.String columnLabel) +
      Returns the value of the specified column as a long.
      +
      java.sql.ResultSetMetaDatagetMetaData() +
      Gets the meta data of this result set.
      +
      java.io.ReadergetNCharacterStream(int columnIndex) +
      Returns the value of the specified column as a reader.
      +
      java.io.ReadergetNCharacterStream(java.lang.String columnLabel) +
      Returns the value of the specified column as a reader.
      +
      java.sql.NClobgetNClob(int columnIndex) +
      Returns the value of the specified column as a Clob.
      +
      java.sql.NClobgetNClob(java.lang.String columnLabel) +
      Returns the value of the specified column as a Clob.
      +
      java.lang.StringgetNString(int columnIndex) +
      Returns the value of the specified column as a String.
      +
      java.lang.StringgetNString(java.lang.String columnLabel) +
      Returns the value of the specified column as a String.
      +
      java.lang.ObjectgetObject(int columnIndex) +
      Returns a column value as a Java object.
      +
      <T> TgetObject(int columnIndex, + java.lang.Class<T> type) +
      Returns a column value as a Java object of the specified type.
      +
      java.lang.ObjectgetObject(int columnIndex, + java.util.Map<java.lang.String,java.lang.Class<?>> map) +
      [Not supported] Gets a column as a object using the specified type + mapping.
      +
      java.lang.ObjectgetObject(java.lang.String columnLabel) +
      Returns a column value as a Java object.
      +
      <T> TgetObject(java.lang.String columnName, + java.lang.Class<T> type) +
      Returns a column value as a Java object of the specified type.
      +
      java.lang.ObjectgetObject(java.lang.String columnLabel, + java.util.Map<java.lang.String,java.lang.Class<?>> map) +
      [Not supported] Gets a column as a object using the specified type + mapping.
      +
      java.sql.RefgetRef(int columnIndex) +
      [Not supported] Gets a column as a reference.
      +
      java.sql.RefgetRef(java.lang.String columnLabel) +
      [Not supported] Gets a column as a reference.
      +
      org.h2.result.ResultInterfacegetResult() +
      INTERNAL
      +
      intgetRow() +
      Gets the current row number.
      +
      java.sql.RowIdgetRowId(int columnIndex) +
      [Not supported] Returns the value of the specified column as a row id.
      +
      java.sql.RowIdgetRowId(java.lang.String columnLabel) +
      [Not supported] Returns the value of the specified column as a row id.
      +
      shortgetShort(int columnIndex) +
      Returns the value of the specified column as a short.
      +
      shortgetShort(java.lang.String columnLabel) +
      Returns the value of the specified column as a short.
      +
      java.sql.SQLXMLgetSQLXML(int columnIndex) +
      Returns the value of the specified column as a SQLXML.
      +
      java.sql.SQLXMLgetSQLXML(java.lang.String columnLabel) +
      Returns the value of the specified column as a SQLXML.
      +
      java.sql.StatementgetStatement() +
      Returns the statement that created this object.
      +
      java.lang.StringgetString(int columnIndex) +
      Returns the value of the specified column as a String.
      +
      java.lang.StringgetString(java.lang.String columnLabel) +
      Returns the value of the specified column as a String.
      +
      java.sql.TimegetTime(int columnIndex) +
      Returns the value of the specified column as a java.sql.Time.
      +
      java.sql.TimegetTime(int columnIndex, + java.util.Calendar calendar) +
      Returns the value of the specified column as a java.sql.Time using a + specified time zone.
      +
      java.sql.TimegetTime(java.lang.String columnLabel) +
      Returns the value of the specified column as a java.sql.Time.
      +
      java.sql.TimegetTime(java.lang.String columnLabel, + java.util.Calendar calendar) +
      Returns the value of the specified column as a java.sql.Time using a + specified time zone.
      +
      java.sql.TimestampgetTimestamp(int columnIndex) +
      Returns the value of the specified column as a java.sql.Timestamp.
      +
      java.sql.TimestampgetTimestamp(int columnIndex, + java.util.Calendar calendar) +
      Returns the value of the specified column as a java.sql.Timestamp using a + specified time zone.
      +
      java.sql.TimestampgetTimestamp(java.lang.String columnLabel) +
      Returns the value of the specified column as a java.sql.Timestamp.
      +
      java.sql.TimestampgetTimestamp(java.lang.String columnLabel, + java.util.Calendar calendar) +
      Returns the value of the specified column as a java.sql.Timestamp.
      +
      intgetType() +
      Get the result set type.
      +
      java.io.InputStreamgetUnicodeStream(int columnIndex) +
      Deprecated.  +
      since JDBC 2.0, use getCharacterStream
      +
      +
      java.io.InputStreamgetUnicodeStream(java.lang.String columnLabel) +
      Deprecated.  +
      since JDBC 2.0, use setCharacterStream
      +
      +
      org.h2.value.Value[]getUpdateRow() +
      INTERNAL
      +
      java.net.URLgetURL(int columnIndex) +
      [Not supported]
      +
      java.net.URLgetURL(java.lang.String columnLabel) +
      [Not supported]
      +
      java.sql.SQLWarninggetWarnings() +
      Gets the first warning reported by calls on this object.
      +
      voidinsertRow() +
      Inserts the current row.
      +
      booleanisAfterLast() +
      Checks if the current position is after the last row, that means next() + was called and returned false, and there was at least one row.
      +
      booleanisBeforeFirst() +
      Checks if the current position is before the first row, that means next() + was not called yet, and there is at least one row.
      +
      booleanisClosed() +
      Returns whether this result set is closed.
      +
      booleanisFirst() +
      Checks if the current position is row 1, that means next() was called + once and returned true.
      +
      booleanisLast() +
      Checks if the current position is the last row, that means next() was + called and did not yet returned false, but will in the next call.
      +
      booleanisWrapperFor(java.lang.Class<?> iface) +
      Checks if unwrap can return an object of this class.
      +
      booleanlast() +
      Moves the current position to the last row.
      +
      voidmoveToCurrentRow() +
      Moves the current position to the current row.
      +
      voidmoveToInsertRow() +
      Moves the current position to the insert row.
      +
      booleannext() +
      Moves the cursor to the next row of the result set.
      +
      booleanprevious() +
      Moves the cursor to the last row, or row before first row if the current + position is the first row.
      +
      voidrefreshRow() +
      Re-reads the current row from the database.
      +
      booleanrelative(int rowCount) +
      Moves the current position to a specific row relative to the current row.
      +
      booleanrowDeleted() +
      Detects if the row was deleted (by somebody else or the caller).
      +
      booleanrowInserted() +
      Detects if the row was inserted.
      +
      booleanrowUpdated() +
      Detects if the row was updated (by somebody else or the caller).
      +
      voidsetFetchDirection(int direction) +
      [Not supported] + Sets (changes) the fetch direction for this result set.
      +
      voidsetFetchSize(int rows) +
      Sets the number of rows suggested to read in one step.
      +
      java.lang.StringtoString() +
      INTERNAL
      +
      <T> Tunwrap(java.lang.Class<T> iface) +
      Return an object of this class if possible.
      +
      voidupdateArray(int columnIndex, + java.sql.Array x) +
      Updates a column in the current or insert row.
      +
      voidupdateArray(java.lang.String columnLabel, + java.sql.Array x) +
      Updates a column in the current or insert row.
      +
      voidupdateAsciiStream(int columnIndex, + java.io.InputStream x) +
      Updates a column in the current or insert row.
      +
      voidupdateAsciiStream(int columnIndex, + java.io.InputStream x, + int length) +
      Updates a column in the current or insert row.
      +
      voidupdateAsciiStream(int columnIndex, + java.io.InputStream x, + long length) +
      Updates a column in the current or insert row.
      +
      voidupdateAsciiStream(java.lang.String columnLabel, + java.io.InputStream x) +
      Updates a column in the current or insert row.
      +
      voidupdateAsciiStream(java.lang.String columnLabel, + java.io.InputStream x, + int length) +
      Updates a column in the current or insert row.
      +
      voidupdateAsciiStream(java.lang.String columnLabel, + java.io.InputStream x, + long length) +
      Updates a column in the current or insert row.
      +
      voidupdateBigDecimal(int columnIndex, + java.math.BigDecimal x) +
      Updates a column in the current or insert row.
      +
      voidupdateBigDecimal(java.lang.String columnLabel, + java.math.BigDecimal x) +
      Updates a column in the current or insert row.
      +
      voidupdateBinaryStream(int columnIndex, + java.io.InputStream x) +
      Updates a column in the current or insert row.
      +
      voidupdateBinaryStream(int columnIndex, + java.io.InputStream x, + int length) +
      Updates a column in the current or insert row.
      +
      voidupdateBinaryStream(int columnIndex, + java.io.InputStream x, + long length) +
      Updates a column in the current or insert row.
      +
      voidupdateBinaryStream(java.lang.String columnLabel, + java.io.InputStream x) +
      Updates a column in the current or insert row.
      +
      voidupdateBinaryStream(java.lang.String columnLabel, + java.io.InputStream x, + int length) +
      Updates a column in the current or insert row.
      +
      voidupdateBinaryStream(java.lang.String columnLabel, + java.io.InputStream x, + long length) +
      Updates a column in the current or insert row.
      +
      voidupdateBlob(int columnIndex, + java.sql.Blob x) +
      Updates a column in the current or insert row.
      +
      voidupdateBlob(int columnIndex, + java.io.InputStream x) +
      Updates a column in the current or insert row.
      +
      voidupdateBlob(int columnIndex, + java.io.InputStream x, + long length) +
      Updates a column in the current or insert row.
      +
      voidupdateBlob(java.lang.String columnLabel, + java.sql.Blob x) +
      Updates a column in the current or insert row.
      +
      voidupdateBlob(java.lang.String columnLabel, + java.io.InputStream x) +
      Updates a column in the current or insert row.
      +
      voidupdateBlob(java.lang.String columnLabel, + java.io.InputStream x, + long length) +
      Updates a column in the current or insert row.
      +
      voidupdateBoolean(int columnIndex, + boolean x) +
      Updates a column in the current or insert row.
      +
      voidupdateBoolean(java.lang.String columnLabel, + boolean x) +
      Updates a column in the current or insert row.
      +
      voidupdateByte(int columnIndex, + byte x) +
      Updates a column in the current or insert row.
      +
      voidupdateByte(java.lang.String columnLabel, + byte x) +
      Updates a column in the current or insert row.
      +
      voidupdateBytes(int columnIndex, + byte[] x) +
      Updates a column in the current or insert row.
      +
      voidupdateBytes(java.lang.String columnLabel, + byte[] x) +
      Updates a column in the current or insert row.
      +
      voidupdateCharacterStream(int columnIndex, + java.io.Reader x) +
      Updates a column in the current or insert row.
      +
      voidupdateCharacterStream(int columnIndex, + java.io.Reader x, + int length) +
      Updates a column in the current or insert row.
      +
      voidupdateCharacterStream(int columnIndex, + java.io.Reader x, + long length) +
      Updates a column in the current or insert row.
      +
      voidupdateCharacterStream(java.lang.String columnLabel, + java.io.Reader x) +
      Updates a column in the current or insert row.
      +
      voidupdateCharacterStream(java.lang.String columnLabel, + java.io.Reader x, + int length) +
      Updates a column in the current or insert row.
      +
      voidupdateCharacterStream(java.lang.String columnLabel, + java.io.Reader x, + long length) +
      Updates a column in the current or insert row.
      +
      voidupdateClob(int columnIndex, + java.sql.Clob x) +
      Updates a column in the current or insert row.
      +
      voidupdateClob(int columnIndex, + java.io.Reader x) +
      Updates a column in the current or insert row.
      +
      voidupdateClob(int columnIndex, + java.io.Reader x, + long length) +
      Updates a column in the current or insert row.
      +
      voidupdateClob(java.lang.String columnLabel, + java.sql.Clob x) +
      Updates a column in the current or insert row.
      +
      voidupdateClob(java.lang.String columnLabel, + java.io.Reader x) +
      Updates a column in the current or insert row.
      +
      voidupdateClob(java.lang.String columnLabel, + java.io.Reader x, + long length) +
      Updates a column in the current or insert row.
      +
      voidupdateDate(int columnIndex, + java.sql.Date x) +
      Updates a column in the current or insert row.
      +
      voidupdateDate(java.lang.String columnLabel, + java.sql.Date x) +
      Updates a column in the current or insert row.
      +
      voidupdateDouble(int columnIndex, + double x) +
      Updates a column in the current or insert row.
      +
      voidupdateDouble(java.lang.String columnLabel, + double x) +
      Updates a column in the current or insert row.
      +
      voidupdateFloat(int columnIndex, + float x) +
      Updates a column in the current or insert row.
      +
      voidupdateFloat(java.lang.String columnLabel, + float x) +
      Updates a column in the current or insert row.
      +
      voidupdateInt(int columnIndex, + int x) +
      Updates a column in the current or insert row.
      +
      voidupdateInt(java.lang.String columnLabel, + int x) +
      Updates a column in the current or insert row.
      +
      voidupdateLong(int columnIndex, + long x) +
      Updates a column in the current or insert row.
      +
      voidupdateLong(java.lang.String columnLabel, + long x) +
      Updates a column in the current or insert row.
      +
      voidupdateNCharacterStream(int columnIndex, + java.io.Reader x) +
      Updates a column in the current or insert row.
      +
      voidupdateNCharacterStream(int columnIndex, + java.io.Reader x, + long length) +
      Updates a column in the current or insert row.
      +
      voidupdateNCharacterStream(java.lang.String columnLabel, + java.io.Reader x) +
      Updates a column in the current or insert row.
      +
      voidupdateNCharacterStream(java.lang.String columnLabel, + java.io.Reader x, + long length) +
      Updates a column in the current or insert row.
      +
      voidupdateNClob(int columnIndex, + java.sql.NClob x) +
      Updates a column in the current or insert row.
      +
      voidupdateNClob(int columnIndex, + java.io.Reader x) +
      Updates a column in the current or insert row.
      +
      voidupdateNClob(int columnIndex, + java.io.Reader x, + long length) +
      Updates a column in the current or insert row.
      +
      voidupdateNClob(java.lang.String columnLabel, + java.sql.NClob x) +
      Updates a column in the current or insert row.
      +
      voidupdateNClob(java.lang.String columnLabel, + java.io.Reader x) +
      Updates a column in the current or insert row.
      +
      voidupdateNClob(java.lang.String columnLabel, + java.io.Reader x, + long length) +
      Updates a column in the current or insert row.
      +
      voidupdateNString(int columnIndex, + java.lang.String x) +
      Updates a column in the current or insert row.
      +
      voidupdateNString(java.lang.String columnLabel, + java.lang.String x) +
      Updates a column in the current or insert row.
      +
      voidupdateNull(int columnIndex) +
      Updates a column in the current or insert row.
      +
      voidupdateNull(java.lang.String columnLabel) +
      Updates a column in the current or insert row.
      +
      voidupdateObject(int columnIndex, + java.lang.Object x) +
      Updates a column in the current or insert row.
      +
      voidupdateObject(int columnIndex, + java.lang.Object x, + int scale) +
      Updates a column in the current or insert row.
      +
      voidupdateObject(int columnIndex, + java.lang.Object x, + java.sql.SQLType targetSqlType) +
      Updates a column in the current or insert row.
      +
      voidupdateObject(int columnIndex, + java.lang.Object x, + java.sql.SQLType targetSqlType, + int scaleOrLength) +
      Updates a column in the current or insert row.
      +
      voidupdateObject(java.lang.String columnLabel, + java.lang.Object x) +
      Updates a column in the current or insert row.
      +
      voidupdateObject(java.lang.String columnLabel, + java.lang.Object x, + int scale) +
      Updates a column in the current or insert row.
      +
      voidupdateObject(java.lang.String columnLabel, + java.lang.Object x, + java.sql.SQLType targetSqlType) +
      Updates a column in the current or insert row.
      +
      voidupdateObject(java.lang.String columnLabel, + java.lang.Object x, + java.sql.SQLType targetSqlType, + int scaleOrLength) +
      Updates a column in the current or insert row.
      +
      voidupdateRef(int columnIndex, + java.sql.Ref x) +
      [Not supported]
      +
      voidupdateRef(java.lang.String columnLabel, + java.sql.Ref x) +
      [Not supported]
      +
      voidupdateRow() +
      Updates the current row.
      +
      voidupdateRowId(int columnIndex, + java.sql.RowId x) +
      [Not supported] Updates a column in the current or insert row.
      +
      voidupdateRowId(java.lang.String columnLabel, + java.sql.RowId x) +
      [Not supported] Updates a column in the current or insert row.
      +
      voidupdateShort(int columnIndex, + short x) +
      Updates a column in the current or insert row.
      +
      voidupdateShort(java.lang.String columnLabel, + short x) +
      Updates a column in the current or insert row.
      +
      voidupdateSQLXML(int columnIndex, + java.sql.SQLXML xmlObject) +
      Updates a column in the current or insert row.
      +
      voidupdateSQLXML(java.lang.String columnLabel, + java.sql.SQLXML xmlObject) +
      Updates a column in the current or insert row.
      +
      voidupdateString(int columnIndex, + java.lang.String x) +
      Updates a column in the current or insert row.
      +
      voidupdateString(java.lang.String columnLabel, + java.lang.String x) +
      Updates a column in the current or insert row.
      +
      voidupdateTime(int columnIndex, + java.sql.Time x) +
      Updates a column in the current or insert row.
      +
      voidupdateTime(java.lang.String columnLabel, + java.sql.Time x) +
      Updates a column in the current or insert row.
      +
      voidupdateTimestamp(int columnIndex, + java.sql.Timestamp x) +
      Updates a column in the current or insert row.
      +
      voidupdateTimestamp(java.lang.String columnLabel, + java.sql.Timestamp x) +
      Updates a column in the current or insert row.
      +
      booleanwasNull() +
      Returns whether the last column accessed was null.
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.message.TraceObject

        +debugCode, debugCodeAssign, debugCodeCall, debugCodeCall, debugCodeCall, getNextId, getTraceId, getTraceObjectName, isDebugEnabled, isInfoEnabled, logAndConvert, quote, quoteArray, quoteBigDecimal, quoteBytes, quoteDate, quoteIntArray, quoteMap, quoteTime, quoteTimestamp, setTrace, unsupported
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        JdbcResultSet

        +
        public JdbcResultSet(JdbcConnection conn,
        +                     JdbcStatement stat,
        +                     org.h2.command.CommandInterface command,
        +                     org.h2.result.ResultInterface result,
        +                     int id,
        +                     boolean scrollable,
        +                     boolean updatable,
        +                     boolean triggerUpdatable)
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        next

        +
        public boolean next()
        +             throws java.sql.SQLException
        +
        Moves the cursor to the next row of the result set.
        +
        +
        Specified by:
        +
        next in interface java.sql.ResultSet
        +
        Returns:
        +
        true if successful, false if there are no more rows
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getMetaData

        +
        public java.sql.ResultSetMetaData getMetaData()
        +                                       throws java.sql.SQLException
        +
        Gets the meta data of this result set.
        +
        +
        Specified by:
        +
        getMetaData in interface java.sql.ResultSet
        +
        Returns:
        +
        the meta data
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        wasNull

        +
        public boolean wasNull()
        +                throws java.sql.SQLException
        +
        Returns whether the last column accessed was null.
        +
        +
        Specified by:
        +
        wasNull in interface java.sql.ResultSet
        +
        Returns:
        +
        true if the last column accessed was null
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        findColumn

        +
        public int findColumn(java.lang.String columnLabel)
        +               throws java.sql.SQLException
        +
        Searches for a specific column in the result set. A case-insensitive + search is made.
        +
        +
        Specified by:
        +
        findColumn in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the column index (1,2,...)
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        close

        +
        public void close()
        +           throws java.sql.SQLException
        +
        Closes the result set.
        +
        +
        Specified by:
        +
        close in interface java.lang.AutoCloseable
        +
        Specified by:
        +
        close in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getStatement

        +
        public java.sql.Statement getStatement()
        +                                throws java.sql.SQLException
        +
        Returns the statement that created this object.
        +
        +
        Specified by:
        +
        getStatement in interface java.sql.ResultSet
        +
        Returns:
        +
        the statement or prepared statement, or null if created by a + DatabaseMetaData call.
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getWarnings

        +
        public java.sql.SQLWarning getWarnings()
        +                                throws java.sql.SQLException
        +
        Gets the first warning reported by calls on this object.
        +
        +
        Specified by:
        +
        getWarnings in interface java.sql.ResultSet
        +
        Returns:
        +
        null
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        clearWarnings

        +
        public void clearWarnings()
        +                   throws java.sql.SQLException
        +
        Clears all warnings.
        +
        +
        Specified by:
        +
        clearWarnings in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getString

        +
        public java.lang.String getString(int columnIndex)
        +                           throws java.sql.SQLException
        +
        Returns the value of the specified column as a String.
        +
        +
        Specified by:
        +
        getString in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getString

        +
        public java.lang.String getString(java.lang.String columnLabel)
        +                           throws java.sql.SQLException
        +
        Returns the value of the specified column as a String.
        +
        +
        Specified by:
        +
        getString in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getInt

        +
        public int getInt(int columnIndex)
        +           throws java.sql.SQLException
        +
        Returns the value of the specified column as an int.
        +
        +
        Specified by:
        +
        getInt in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getInt

        +
        public int getInt(java.lang.String columnLabel)
        +           throws java.sql.SQLException
        +
        Returns the value of the specified column as an int.
        +
        +
        Specified by:
        +
        getInt in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getBigDecimal

        +
        public java.math.BigDecimal getBigDecimal(int columnIndex)
        +                                   throws java.sql.SQLException
        +
        Returns the value of the specified column as a BigDecimal.
        +
        +
        Specified by:
        +
        getBigDecimal in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getDate

        +
        public java.sql.Date getDate(int columnIndex)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Date. +

        + Usage of this method is discouraged. Use + getObject(columnIndex, LocalDate.class) instead. +

        +
        +
        Specified by:
        +
        getDate in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        See Also:
        +
        getObject(int, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getTime

        +
        public java.sql.Time getTime(int columnIndex)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Time. +

        + Usage of this method is discouraged. Use + getObject(columnIndex, LocalTime.class) instead. +

        +
        +
        Specified by:
        +
        getTime in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        See Also:
        +
        getObject(int, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getTimestamp

        +
        public java.sql.Timestamp getTimestamp(int columnIndex)
        +                                throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Timestamp. +

        + Usage of this method is discouraged. Use + getObject(columnIndex, LocalDateTime.class) instead. +

        +
        +
        Specified by:
        +
        getTimestamp in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        See Also:
        +
        getObject(int, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getBigDecimal

        +
        public java.math.BigDecimal getBigDecimal(java.lang.String columnLabel)
        +                                   throws java.sql.SQLException
        +
        Returns the value of the specified column as a BigDecimal.
        +
        +
        Specified by:
        +
        getBigDecimal in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getDate

        +
        public java.sql.Date getDate(java.lang.String columnLabel)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Date. +

        + Usage of this method is discouraged. Use + getObject(columnLabel, LocalDate.class) instead. +

        +
        +
        Specified by:
        +
        getDate in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        See Also:
        +
        getObject(String, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getTime

        +
        public java.sql.Time getTime(java.lang.String columnLabel)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Time. +

        + Usage of this method is discouraged. Use + getObject(columnLabel, LocalTime.class) instead. +

        +
        +
        Specified by:
        +
        getTime in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        See Also:
        +
        getObject(String, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getTimestamp

        +
        public java.sql.Timestamp getTimestamp(java.lang.String columnLabel)
        +                                throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Timestamp. +

        + Usage of this method is discouraged. Use + getObject(columnLabel, LocalDateTime.class) instead. +

        +
        +
        Specified by:
        +
        getTimestamp in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        See Also:
        +
        getObject(String, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getObject

        +
        public java.lang.Object getObject(int columnIndex)
        +                           throws java.sql.SQLException
        +
        Returns a column value as a Java object. The data is + de-serialized into a Java object (on the client side).
        +
        +
        Specified by:
        +
        getObject in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value or null
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getObject

        +
        public java.lang.Object getObject(java.lang.String columnLabel)
        +                           throws java.sql.SQLException
        +
        Returns a column value as a Java object. The data is + de-serialized into a Java object (on the client side).
        +
        +
        Specified by:
        +
        getObject in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value or null
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getBoolean

        +
        public boolean getBoolean(int columnIndex)
        +                   throws java.sql.SQLException
        +
        Returns the value of the specified column as a boolean.
        +
        +
        Specified by:
        +
        getBoolean in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getBoolean

        +
        public boolean getBoolean(java.lang.String columnLabel)
        +                   throws java.sql.SQLException
        +
        Returns the value of the specified column as a boolean.
        +
        +
        Specified by:
        +
        getBoolean in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getByte

        +
        public byte getByte(int columnIndex)
        +             throws java.sql.SQLException
        +
        Returns the value of the specified column as a byte.
        +
        +
        Specified by:
        +
        getByte in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getByte

        +
        public byte getByte(java.lang.String columnLabel)
        +             throws java.sql.SQLException
        +
        Returns the value of the specified column as a byte.
        +
        +
        Specified by:
        +
        getByte in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getShort

        +
        public short getShort(int columnIndex)
        +               throws java.sql.SQLException
        +
        Returns the value of the specified column as a short.
        +
        +
        Specified by:
        +
        getShort in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getShort

        +
        public short getShort(java.lang.String columnLabel)
        +               throws java.sql.SQLException
        +
        Returns the value of the specified column as a short.
        +
        +
        Specified by:
        +
        getShort in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getLong

        +
        public long getLong(int columnIndex)
        +             throws java.sql.SQLException
        +
        Returns the value of the specified column as a long.
        +
        +
        Specified by:
        +
        getLong in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getLong

        +
        public long getLong(java.lang.String columnLabel)
        +             throws java.sql.SQLException
        +
        Returns the value of the specified column as a long.
        +
        +
        Specified by:
        +
        getLong in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getFloat

        +
        public float getFloat(int columnIndex)
        +               throws java.sql.SQLException
        +
        Returns the value of the specified column as a float.
        +
        +
        Specified by:
        +
        getFloat in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getFloat

        +
        public float getFloat(java.lang.String columnLabel)
        +               throws java.sql.SQLException
        +
        Returns the value of the specified column as a float.
        +
        +
        Specified by:
        +
        getFloat in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getDouble

        +
        public double getDouble(int columnIndex)
        +                 throws java.sql.SQLException
        +
        Returns the value of the specified column as a double.
        +
        +
        Specified by:
        +
        getDouble in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getDouble

        +
        public double getDouble(java.lang.String columnLabel)
        +                 throws java.sql.SQLException
        +
        Returns the value of the specified column as a double.
        +
        +
        Specified by:
        +
        getDouble in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getBigDecimal

        +
        @Deprecated
        +public java.math.BigDecimal getBigDecimal(java.lang.String columnLabel,
        +                                                      int scale)
        +                                               throws java.sql.SQLException
        +
        Deprecated. use getBigDecimal(String)
        +
        Returns the value of the specified column as a BigDecimal.
        +
        +
        Specified by:
        +
        getBigDecimal in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        scale - the scale of the returned value
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getBigDecimal

        +
        @Deprecated
        +public java.math.BigDecimal getBigDecimal(int columnIndex,
        +                                                      int scale)
        +                                               throws java.sql.SQLException
        +
        Deprecated. use getBigDecimal(int)
        +
        Returns the value of the specified column as a BigDecimal.
        +
        +
        Specified by:
        +
        getBigDecimal in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        scale - the scale of the returned value
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getUnicodeStream

        +
        @Deprecated
        +public java.io.InputStream getUnicodeStream(int columnIndex)
        +                                                 throws java.sql.SQLException
        +
        Deprecated. since JDBC 2.0, use getCharacterStream
        +
        [Not supported]
        +
        +
        Specified by:
        +
        getUnicodeStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getUnicodeStream

        +
        @Deprecated
        +public java.io.InputStream getUnicodeStream(java.lang.String columnLabel)
        +                                                 throws java.sql.SQLException
        +
        Deprecated. since JDBC 2.0, use setCharacterStream
        +
        [Not supported]
        +
        +
        Specified by:
        +
        getUnicodeStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getObject

        +
        public java.lang.Object getObject(int columnIndex,
        +                                  java.util.Map<java.lang.String,java.lang.Class<?>> map)
        +                           throws java.sql.SQLException
        +
        [Not supported] Gets a column as a object using the specified type + mapping.
        +
        +
        Specified by:
        +
        getObject in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getObject

        +
        public java.lang.Object getObject(java.lang.String columnLabel,
        +                                  java.util.Map<java.lang.String,java.lang.Class<?>> map)
        +                           throws java.sql.SQLException
        +
        [Not supported] Gets a column as a object using the specified type + mapping.
        +
        +
        Specified by:
        +
        getObject in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getRef

        +
        public java.sql.Ref getRef(int columnIndex)
        +                    throws java.sql.SQLException
        +
        [Not supported] Gets a column as a reference.
        +
        +
        Specified by:
        +
        getRef in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getRef

        +
        public java.sql.Ref getRef(java.lang.String columnLabel)
        +                    throws java.sql.SQLException
        +
        [Not supported] Gets a column as a reference.
        +
        +
        Specified by:
        +
        getRef in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getDate

        +
        public java.sql.Date getDate(int columnIndex,
        +                             java.util.Calendar calendar)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Date using a + specified time zone. +

        + Usage of this method is discouraged. Use + getObject(columnIndex, LocalDate.class) instead. +

        +
        +
        Specified by:
        +
        getDate in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        calendar - the calendar
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        See Also:
        +
        getObject(int, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getDate

        +
        public java.sql.Date getDate(java.lang.String columnLabel,
        +                             java.util.Calendar calendar)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Date using a + specified time zone. +

        + Usage of this method is discouraged. Use + getObject(columnLabel, LocalDate.class) instead. +

        +
        +
        Specified by:
        +
        getDate in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        calendar - the calendar
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        See Also:
        +
        getObject(String, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getTime

        +
        public java.sql.Time getTime(int columnIndex,
        +                             java.util.Calendar calendar)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Time using a + specified time zone. +

        + Usage of this method is discouraged. Use + getObject(columnIndex, LocalTime.class) instead. +

        +
        +
        Specified by:
        +
        getTime in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        calendar - the calendar
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        See Also:
        +
        getObject(int, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getTime

        +
        public java.sql.Time getTime(java.lang.String columnLabel,
        +                             java.util.Calendar calendar)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Time using a + specified time zone. +

        + Usage of this method is discouraged. Use + getObject(columnLabel, LocalTime.class) instead. +

        +
        +
        Specified by:
        +
        getTime in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        calendar - the calendar
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        See Also:
        +
        getObject(String, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getTimestamp

        +
        public java.sql.Timestamp getTimestamp(int columnIndex,
        +                                       java.util.Calendar calendar)
        +                                throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Timestamp using a + specified time zone. +

        + Usage of this method is discouraged. Use + getObject(columnIndex, LocalDateTime.class) instead. +

        +
        +
        Specified by:
        +
        getTimestamp in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        calendar - the calendar
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        See Also:
        +
        getObject(int, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getTimestamp

        +
        public java.sql.Timestamp getTimestamp(java.lang.String columnLabel,
        +                                       java.util.Calendar calendar)
        +                                throws java.sql.SQLException
        +
        Returns the value of the specified column as a java.sql.Timestamp. +

        + Usage of this method is discouraged. Use + getObject(columnLabel, LocalDateTime.class) instead. +

        +
        +
        Specified by:
        +
        getTimestamp in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        calendar - the calendar
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        See Also:
        +
        getObject(String, Class)
        +
        +
      • +
      + + + +
        +
      • +

        getBlob

        +
        public java.sql.Blob getBlob(int columnIndex)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a Blob.
        +
        +
        Specified by:
        +
        getBlob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getBlob

        +
        public java.sql.Blob getBlob(java.lang.String columnLabel)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a Blob.
        +
        +
        Specified by:
        +
        getBlob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getBytes

        +
        public byte[] getBytes(int columnIndex)
        +                throws java.sql.SQLException
        +
        Returns the value of the specified column as a byte array.
        +
        +
        Specified by:
        +
        getBytes in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getBytes

        +
        public byte[] getBytes(java.lang.String columnLabel)
        +                throws java.sql.SQLException
        +
        Returns the value of the specified column as a byte array.
        +
        +
        Specified by:
        +
        getBytes in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getBinaryStream

        +
        public java.io.InputStream getBinaryStream(int columnIndex)
        +                                    throws java.sql.SQLException
        +
        Returns the value of the specified column as an input stream.
        +
        +
        Specified by:
        +
        getBinaryStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getBinaryStream

        +
        public java.io.InputStream getBinaryStream(java.lang.String columnLabel)
        +                                    throws java.sql.SQLException
        +
        Returns the value of the specified column as an input stream.
        +
        +
        Specified by:
        +
        getBinaryStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getClob

        +
        public java.sql.Clob getClob(int columnIndex)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a Clob.
        +
        +
        Specified by:
        +
        getClob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getClob

        +
        public java.sql.Clob getClob(java.lang.String columnLabel)
        +                      throws java.sql.SQLException
        +
        Returns the value of the specified column as a Clob.
        +
        +
        Specified by:
        +
        getClob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getArray

        +
        public java.sql.Array getArray(int columnIndex)
        +                        throws java.sql.SQLException
        +
        Returns the value of the specified column as an Array.
        +
        +
        Specified by:
        +
        getArray in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getArray

        +
        public java.sql.Array getArray(java.lang.String columnLabel)
        +                        throws java.sql.SQLException
        +
        Returns the value of the specified column as an Array.
        +
        +
        Specified by:
        +
        getArray in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getAsciiStream

        +
        public java.io.InputStream getAsciiStream(int columnIndex)
        +                                   throws java.sql.SQLException
        +
        Returns the value of the specified column as an input stream.
        +
        +
        Specified by:
        +
        getAsciiStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getAsciiStream

        +
        public java.io.InputStream getAsciiStream(java.lang.String columnLabel)
        +                                   throws java.sql.SQLException
        +
        Returns the value of the specified column as an input stream.
        +
        +
        Specified by:
        +
        getAsciiStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getCharacterStream

        +
        public java.io.Reader getCharacterStream(int columnIndex)
        +                                  throws java.sql.SQLException
        +
        Returns the value of the specified column as a reader.
        +
        +
        Specified by:
        +
        getCharacterStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getCharacterStream

        +
        public java.io.Reader getCharacterStream(java.lang.String columnLabel)
        +                                  throws java.sql.SQLException
        +
        Returns the value of the specified column as a reader.
        +
        +
        Specified by:
        +
        getCharacterStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getURL

        +
        public java.net.URL getURL(int columnIndex)
        +                    throws java.sql.SQLException
        +
        [Not supported]
        +
        +
        Specified by:
        +
        getURL in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getURL

        +
        public java.net.URL getURL(java.lang.String columnLabel)
        +                    throws java.sql.SQLException
        +
        [Not supported]
        +
        +
        Specified by:
        +
        getURL in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateNull

        +
        public void updateNull(int columnIndex)
        +                throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateNull in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateNull

        +
        public void updateNull(java.lang.String columnLabel)
        +                throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateNull in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateBoolean

        +
        public void updateBoolean(int columnIndex,
        +                          boolean x)
        +                   throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateBoolean in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateBoolean

        +
        public void updateBoolean(java.lang.String columnLabel,
        +                          boolean x)
        +                   throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateBoolean in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateByte

        +
        public void updateByte(int columnIndex,
        +                       byte x)
        +                throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateByte in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateByte

        +
        public void updateByte(java.lang.String columnLabel,
        +                       byte x)
        +                throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateByte in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateBytes

        +
        public void updateBytes(int columnIndex,
        +                        byte[] x)
        +                 throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateBytes in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateBytes

        +
        public void updateBytes(java.lang.String columnLabel,
        +                        byte[] x)
        +                 throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateBytes in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateShort

        +
        public void updateShort(int columnIndex,
        +                        short x)
        +                 throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateShort in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateShort

        +
        public void updateShort(java.lang.String columnLabel,
        +                        short x)
        +                 throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateShort in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateInt

        +
        public void updateInt(int columnIndex,
        +                      int x)
        +               throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateInt in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateInt

        +
        public void updateInt(java.lang.String columnLabel,
        +                      int x)
        +               throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateInt in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateLong

        +
        public void updateLong(int columnIndex,
        +                       long x)
        +                throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateLong in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateLong

        +
        public void updateLong(java.lang.String columnLabel,
        +                       long x)
        +                throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateLong in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateFloat

        +
        public void updateFloat(int columnIndex,
        +                        float x)
        +                 throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateFloat in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateFloat

        +
        public void updateFloat(java.lang.String columnLabel,
        +                        float x)
        +                 throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateFloat in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateDouble

        +
        public void updateDouble(int columnIndex,
        +                         double x)
        +                  throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateDouble in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateDouble

        +
        public void updateDouble(java.lang.String columnLabel,
        +                         double x)
        +                  throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateDouble in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateBigDecimal

        +
        public void updateBigDecimal(int columnIndex,
        +                             java.math.BigDecimal x)
        +                      throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateBigDecimal in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateBigDecimal

        +
        public void updateBigDecimal(java.lang.String columnLabel,
        +                             java.math.BigDecimal x)
        +                      throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateBigDecimal in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateString

        +
        public void updateString(int columnIndex,
        +                         java.lang.String x)
        +                  throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateString in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateString

        +
        public void updateString(java.lang.String columnLabel,
        +                         java.lang.String x)
        +                  throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateString in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateDate

        +
        public void updateDate(int columnIndex,
        +                       java.sql.Date x)
        +                throws java.sql.SQLException
        +
        Updates a column in the current or insert row. +

        + Usage of this method is discouraged. Use + updateObject(columnIndex, value) with LocalDate + parameter instead. +

        +
        +
        Specified by:
        +
        updateDate in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        See Also:
        +
        updateObject(int, Object)
        +
        +
      • +
      + + + +
        +
      • +

        updateDate

        +
        public void updateDate(java.lang.String columnLabel,
        +                       java.sql.Date x)
        +                throws java.sql.SQLException
        +
        Updates a column in the current or insert row. +

        + Usage of this method is discouraged. Use + updateObject(columnLabel, value) with LocalDate + parameter instead. +

        +
        +
        Specified by:
        +
        updateDate in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        See Also:
        +
        updateObject(String, Object)
        +
        +
      • +
      + + + +
        +
      • +

        updateTime

        +
        public void updateTime(int columnIndex,
        +                       java.sql.Time x)
        +                throws java.sql.SQLException
        +
        Updates a column in the current or insert row. +

        + Usage of this method is discouraged. Use + updateObject(columnIndex, value) with LocalTime + parameter instead. +

        +
        +
        Specified by:
        +
        updateTime in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        See Also:
        +
        updateObject(int, Object)
        +
        +
      • +
      + + + +
        +
      • +

        updateTime

        +
        public void updateTime(java.lang.String columnLabel,
        +                       java.sql.Time x)
        +                throws java.sql.SQLException
        +
        Updates a column in the current or insert row. +

        + Usage of this method is discouraged. Use + updateObject(columnLabel, value) with LocalTime + parameter instead. +

        +
        +
        Specified by:
        +
        updateTime in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        See Also:
        +
        updateObject(String, Object)
        +
        +
      • +
      + + + +
        +
      • +

        updateTimestamp

        +
        public void updateTimestamp(int columnIndex,
        +                            java.sql.Timestamp x)
        +                     throws java.sql.SQLException
        +
        Updates a column in the current or insert row. +

        + Usage of this method is discouraged. Use + updateObject(columnIndex, value) with + LocalDateTime parameter instead. +

        +
        +
        Specified by:
        +
        updateTimestamp in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        See Also:
        +
        updateObject(int, Object)
        +
        +
      • +
      + + + +
        +
      • +

        updateTimestamp

        +
        public void updateTimestamp(java.lang.String columnLabel,
        +                            java.sql.Timestamp x)
        +                     throws java.sql.SQLException
        +
        Updates a column in the current or insert row. +

        + Usage of this method is discouraged. Use + updateObject(columnLabel, value) with + LocalDateTime parameter instead. +

        +
        +
        Specified by:
        +
        updateTimestamp in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        See Also:
        +
        updateObject(String, Object)
        +
        +
      • +
      + + + +
        +
      • +

        updateAsciiStream

        +
        public void updateAsciiStream(int columnIndex,
        +                              java.io.InputStream x,
        +                              int length)
        +                       throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateAsciiStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        length - the number of characters
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateAsciiStream

        +
        public void updateAsciiStream(int columnIndex,
        +                              java.io.InputStream x)
        +                       throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateAsciiStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateAsciiStream

        +
        public void updateAsciiStream(int columnIndex,
        +                              java.io.InputStream x,
        +                              long length)
        +                       throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateAsciiStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        length - the number of characters
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateAsciiStream

        +
        public void updateAsciiStream(java.lang.String columnLabel,
        +                              java.io.InputStream x,
        +                              int length)
        +                       throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateAsciiStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        length - the number of characters
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateAsciiStream

        +
        public void updateAsciiStream(java.lang.String columnLabel,
        +                              java.io.InputStream x)
        +                       throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateAsciiStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed
        +
        +
      • +
      + + + +
        +
      • +

        updateAsciiStream

        +
        public void updateAsciiStream(java.lang.String columnLabel,
        +                              java.io.InputStream x,
        +                              long length)
        +                       throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateAsciiStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        length - the number of characters
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateBinaryStream

        +
        public void updateBinaryStream(int columnIndex,
        +                               java.io.InputStream x,
        +                               int length)
        +                        throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateBinaryStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        length - the number of characters
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateBinaryStream

        +
        public void updateBinaryStream(int columnIndex,
        +                               java.io.InputStream x)
        +                        throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateBinaryStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateBinaryStream

        +
        public void updateBinaryStream(int columnIndex,
        +                               java.io.InputStream x,
        +                               long length)
        +                        throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateBinaryStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        length - the number of characters
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateBinaryStream

        +
        public void updateBinaryStream(java.lang.String columnLabel,
        +                               java.io.InputStream x)
        +                        throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateBinaryStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateBinaryStream

        +
        public void updateBinaryStream(java.lang.String columnLabel,
        +                               java.io.InputStream x,
        +                               int length)
        +                        throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateBinaryStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        length - the number of characters
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateBinaryStream

        +
        public void updateBinaryStream(java.lang.String columnLabel,
        +                               java.io.InputStream x,
        +                               long length)
        +                        throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateBinaryStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        length - the number of characters
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateCharacterStream

        +
        public void updateCharacterStream(int columnIndex,
        +                                  java.io.Reader x,
        +                                  long length)
        +                           throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateCharacterStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        length - the number of characters
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateCharacterStream

        +
        public void updateCharacterStream(int columnIndex,
        +                                  java.io.Reader x,
        +                                  int length)
        +                           throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateCharacterStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        length - the number of characters
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateCharacterStream

        +
        public void updateCharacterStream(int columnIndex,
        +                                  java.io.Reader x)
        +                           throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateCharacterStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateCharacterStream

        +
        public void updateCharacterStream(java.lang.String columnLabel,
        +                                  java.io.Reader x,
        +                                  int length)
        +                           throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateCharacterStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        length - the number of characters
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateCharacterStream

        +
        public void updateCharacterStream(java.lang.String columnLabel,
        +                                  java.io.Reader x)
        +                           throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateCharacterStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateCharacterStream

        +
        public void updateCharacterStream(java.lang.String columnLabel,
        +                                  java.io.Reader x,
        +                                  long length)
        +                           throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateCharacterStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        length - the number of characters
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateObject

        +
        public void updateObject(int columnIndex,
        +                         java.lang.Object x,
        +                         int scale)
        +                  throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateObject in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        scale - is ignored
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateObject

        +
        public void updateObject(java.lang.String columnLabel,
        +                         java.lang.Object x,
        +                         int scale)
        +                  throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateObject in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        scale - is ignored
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateObject

        +
        public void updateObject(int columnIndex,
        +                         java.lang.Object x)
        +                  throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateObject in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateObject

        +
        public void updateObject(java.lang.String columnLabel,
        +                         java.lang.Object x)
        +                  throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateObject in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateObject

        +
        public void updateObject(int columnIndex,
        +                         java.lang.Object x,
        +                         java.sql.SQLType targetSqlType)
        +                  throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateObject in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        targetSqlType - the SQL type
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateObject

        +
        public void updateObject(int columnIndex,
        +                         java.lang.Object x,
        +                         java.sql.SQLType targetSqlType,
        +                         int scaleOrLength)
        +                  throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateObject in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        targetSqlType - the SQL type
        +
        scaleOrLength - is ignored
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateObject

        +
        public void updateObject(java.lang.String columnLabel,
        +                         java.lang.Object x,
        +                         java.sql.SQLType targetSqlType)
        +                  throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateObject in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        targetSqlType - the SQL type
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateObject

        +
        public void updateObject(java.lang.String columnLabel,
        +                         java.lang.Object x,
        +                         java.sql.SQLType targetSqlType,
        +                         int scaleOrLength)
        +                  throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateObject in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        targetSqlType - the SQL type
        +
        scaleOrLength - is ignored
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateRef

        +
        public void updateRef(int columnIndex,
        +                      java.sql.Ref x)
        +               throws java.sql.SQLException
        +
        [Not supported]
        +
        +
        Specified by:
        +
        updateRef in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateRef

        +
        public void updateRef(java.lang.String columnLabel,
        +                      java.sql.Ref x)
        +               throws java.sql.SQLException
        +
        [Not supported]
        +
        +
        Specified by:
        +
        updateRef in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateBlob

        +
        public void updateBlob(int columnIndex,
        +                       java.io.InputStream x)
        +                throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateBlob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateBlob

        +
        public void updateBlob(int columnIndex,
        +                       java.io.InputStream x,
        +                       long length)
        +                throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateBlob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        length - the length
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateBlob

        +
        public void updateBlob(int columnIndex,
        +                       java.sql.Blob x)
        +                throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateBlob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateBlob

        +
        public void updateBlob(java.lang.String columnLabel,
        +                       java.sql.Blob x)
        +                throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateBlob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateBlob

        +
        public void updateBlob(java.lang.String columnLabel,
        +                       java.io.InputStream x)
        +                throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateBlob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateBlob

        +
        public void updateBlob(java.lang.String columnLabel,
        +                       java.io.InputStream x,
        +                       long length)
        +                throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateBlob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        length - the length
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateClob

        +
        public void updateClob(int columnIndex,
        +                       java.sql.Clob x)
        +                throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateClob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateClob

        +
        public void updateClob(int columnIndex,
        +                       java.io.Reader x)
        +                throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateClob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateClob

        +
        public void updateClob(int columnIndex,
        +                       java.io.Reader x,
        +                       long length)
        +                throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateClob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        length - the length
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateClob

        +
        public void updateClob(java.lang.String columnLabel,
        +                       java.sql.Clob x)
        +                throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateClob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateClob

        +
        public void updateClob(java.lang.String columnLabel,
        +                       java.io.Reader x)
        +                throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateClob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateClob

        +
        public void updateClob(java.lang.String columnLabel,
        +                       java.io.Reader x,
        +                       long length)
        +                throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateClob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        length - the length
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateArray

        +
        public void updateArray(int columnIndex,
        +                        java.sql.Array x)
        +                 throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateArray in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateArray

        +
        public void updateArray(java.lang.String columnLabel,
        +                        java.sql.Array x)
        +                 throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateArray in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        getCursorName

        +
        public java.lang.String getCursorName()
        +                               throws java.sql.SQLException
        +
        [Not supported] Gets the cursor name if it was defined. This feature is + superseded by updateX methods. This method throws a SQLException because + cursor names are not supported.
        +
        +
        Specified by:
        +
        getCursorName in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getRow

        +
        public int getRow()
        +           throws java.sql.SQLException
        +
        Gets the current row number. The first row is row 1, the second 2 and so + on. This method returns 0 before the first and after the last row.
        +
        +
        Specified by:
        +
        getRow in interface java.sql.ResultSet
        +
        Returns:
        +
        the row number
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getConcurrency

        +
        public int getConcurrency()
        +                   throws java.sql.SQLException
        +
        Gets the result set concurrency. Result sets are only updatable if the + statement was created with updatable concurrency, and if the result set + contains all columns of the primary key or of a unique index of a table.
        +
        +
        Specified by:
        +
        getConcurrency in interface java.sql.ResultSet
        +
        Returns:
        +
        ResultSet.CONCUR_UPDATABLE if the result set is updatable, or + ResultSet.CONCUR_READ_ONLY otherwise
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getFetchDirection

        +
        public int getFetchDirection()
        +                      throws java.sql.SQLException
        +
        Gets the fetch direction.
        +
        +
        Specified by:
        +
        getFetchDirection in interface java.sql.ResultSet
        +
        Returns:
        +
        the direction: FETCH_FORWARD
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getFetchSize

        +
        public int getFetchSize()
        +                 throws java.sql.SQLException
        +
        Gets the number of rows suggested to read in one step.
        +
        +
        Specified by:
        +
        getFetchSize in interface java.sql.ResultSet
        +
        Returns:
        +
        the current fetch size
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        setFetchSize

        +
        public void setFetchSize(int rows)
        +                  throws java.sql.SQLException
        +
        Sets the number of rows suggested to read in one step. This value cannot + be higher than the maximum rows (setMaxRows) set by the statement or + prepared statement, otherwise an exception is throws. Setting the value + to 0 will set the default value. The default value can be changed using + the system property h2.serverResultSetFetchSize.
        +
        +
        Specified by:
        +
        setFetchSize in interface java.sql.ResultSet
        +
        Parameters:
        +
        rows - the number of rows
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        setFetchDirection

        +
        public void setFetchDirection(int direction)
        +                       throws java.sql.SQLException
        +
        [Not supported] + Sets (changes) the fetch direction for this result set. This method + should only be called for scrollable result sets, otherwise it will throw + an exception (no matter what direction is used).
        +
        +
        Specified by:
        +
        setFetchDirection in interface java.sql.ResultSet
        +
        Parameters:
        +
        direction - the new fetch direction
        +
        Throws:
        +
        java.sql.SQLException - Unsupported Feature if the method is called for a + forward-only result set
        +
        +
      • +
      + + + +
        +
      • +

        getType

        +
        public int getType()
        +            throws java.sql.SQLException
        +
        Get the result set type.
        +
        +
        Specified by:
        +
        getType in interface java.sql.ResultSet
        +
        Returns:
        +
        the result set type (TYPE_FORWARD_ONLY, TYPE_SCROLL_INSENSITIVE + or TYPE_SCROLL_SENSITIVE)
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        isBeforeFirst

        +
        public boolean isBeforeFirst()
        +                      throws java.sql.SQLException
        +
        Checks if the current position is before the first row, that means next() + was not called yet, and there is at least one row.
        +
        +
        Specified by:
        +
        isBeforeFirst in interface java.sql.ResultSet
        +
        Returns:
        +
        if there are results and the current position is before the first + row
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed
        +
        +
      • +
      + + + +
        +
      • +

        isAfterLast

        +
        public boolean isAfterLast()
        +                    throws java.sql.SQLException
        +
        Checks if the current position is after the last row, that means next() + was called and returned false, and there was at least one row.
        +
        +
        Specified by:
        +
        isAfterLast in interface java.sql.ResultSet
        +
        Returns:
        +
        if there are results and the current position is after the last + row
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed
        +
        +
      • +
      + + + +
        +
      • +

        isFirst

        +
        public boolean isFirst()
        +                throws java.sql.SQLException
        +
        Checks if the current position is row 1, that means next() was called + once and returned true.
        +
        +
        Specified by:
        +
        isFirst in interface java.sql.ResultSet
        +
        Returns:
        +
        if the current position is the first row
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed
        +
        +
      • +
      + + + +
        +
      • +

        isLast

        +
        public boolean isLast()
        +               throws java.sql.SQLException
        +
        Checks if the current position is the last row, that means next() was + called and did not yet returned false, but will in the next call.
        +
        +
        Specified by:
        +
        isLast in interface java.sql.ResultSet
        +
        Returns:
        +
        if the current position is the last row
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed
        +
        +
      • +
      + + + +
        +
      • +

        beforeFirst

        +
        public void beforeFirst()
        +                 throws java.sql.SQLException
        +
        Moves the current position to before the first row, that means resets the + result set.
        +
        +
        Specified by:
        +
        beforeFirst in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed
        +
        +
      • +
      + + + +
        +
      • +

        afterLast

        +
        public void afterLast()
        +               throws java.sql.SQLException
        +
        Moves the current position to after the last row, that means after the + end.
        +
        +
        Specified by:
        +
        afterLast in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed
        +
        +
      • +
      + + + +
        +
      • +

        first

        +
        public boolean first()
        +              throws java.sql.SQLException
        +
        Moves the current position to the first row. This is the same as calling + beforeFirst() followed by next().
        +
        +
        Specified by:
        +
        first in interface java.sql.ResultSet
        +
        Returns:
        +
        true if there is a row available, false if not
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed
        +
        +
      • +
      + + + +
        +
      • +

        last

        +
        public boolean last()
        +             throws java.sql.SQLException
        +
        Moves the current position to the last row.
        +
        +
        Specified by:
        +
        last in interface java.sql.ResultSet
        +
        Returns:
        +
        true if there is a row available, false if not
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed
        +
        +
      • +
      + + + +
        +
      • +

        absolute

        +
        public boolean absolute(int rowNumber)
        +                 throws java.sql.SQLException
        +
        Moves the current position to a specific row.
        +
        +
        Specified by:
        +
        absolute in interface java.sql.ResultSet
        +
        Parameters:
        +
        rowNumber - the row number. 0 is not allowed, 1 means the first row, + 2 the second. -1 means the last row, -2 the row before the + last row. If the value is too large, the position is moved + after the last row, if the value is too small it is moved + before the first row.
        +
        Returns:
        +
        true if there is a row available, false if not
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed
        +
        +
      • +
      + + + +
        +
      • +

        relative

        +
        public boolean relative(int rowCount)
        +                 throws java.sql.SQLException
        +
        Moves the current position to a specific row relative to the current row.
        +
        +
        Specified by:
        +
        relative in interface java.sql.ResultSet
        +
        Parameters:
        +
        rowCount - 0 means don't do anything, 1 is the next row, -1 the + previous. If the value is too large, the position is moved + after the last row, if the value is too small it is moved + before the first row.
        +
        Returns:
        +
        true if there is a row available, false if not
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed
        +
        +
      • +
      + + + +
        +
      • +

        previous

        +
        public boolean previous()
        +                 throws java.sql.SQLException
        +
        Moves the cursor to the last row, or row before first row if the current + position is the first row.
        +
        +
        Specified by:
        +
        previous in interface java.sql.ResultSet
        +
        Returns:
        +
        true if there is a row available, false if not
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed
        +
        +
      • +
      + + + +
        +
      • +

        moveToInsertRow

        +
        public void moveToInsertRow()
        +                     throws java.sql.SQLException
        +
        Moves the current position to the insert row. The current row is + remembered.
        +
        +
        Specified by:
        +
        moveToInsertRow in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or is not updatable
        +
        +
      • +
      + + + +
        +
      • +

        moveToCurrentRow

        +
        public void moveToCurrentRow()
        +                      throws java.sql.SQLException
        +
        Moves the current position to the current row.
        +
        +
        Specified by:
        +
        moveToCurrentRow in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or is not updatable
        +
        +
      • +
      + + + +
        +
      • +

        rowUpdated

        +
        public boolean rowUpdated()
        +                   throws java.sql.SQLException
        +
        Detects if the row was updated (by somebody else or the caller).
        +
        +
        Specified by:
        +
        rowUpdated in interface java.sql.ResultSet
        +
        Returns:
        +
        false because this driver does not detect this
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        rowInserted

        +
        public boolean rowInserted()
        +                    throws java.sql.SQLException
        +
        Detects if the row was inserted.
        +
        +
        Specified by:
        +
        rowInserted in interface java.sql.ResultSet
        +
        Returns:
        +
        false because this driver does not detect this
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        rowDeleted

        +
        public boolean rowDeleted()
        +                   throws java.sql.SQLException
        +
        Detects if the row was deleted (by somebody else or the caller).
        +
        +
        Specified by:
        +
        rowDeleted in interface java.sql.ResultSet
        +
        Returns:
        +
        false because this driver does not detect this
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        insertRow

        +
        public void insertRow()
        +               throws java.sql.SQLException
        +
        Inserts the current row. The current position must be the insert row.
        +
        +
        Specified by:
        +
        insertRow in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or if not on the insert + row, or if the result set it not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateRow

        +
        public void updateRow()
        +               throws java.sql.SQLException
        +
        Updates the current row.
        +
        +
        Specified by:
        +
        updateRow in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed, if the current row is + the insert row or if not on a valid row, or if the result set + it not updatable
        +
        +
      • +
      + + + +
        +
      • +

        deleteRow

        +
        public void deleteRow()
        +               throws java.sql.SQLException
        +
        Deletes the current row.
        +
        +
        Specified by:
        +
        deleteRow in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed, if the current row is + the insert row or if not on a valid row, or if the result set + it not updatable
        +
        +
      • +
      + + + +
        +
      • +

        refreshRow

        +
        public void refreshRow()
        +                throws java.sql.SQLException
        +
        Re-reads the current row from the database.
        +
        +
        Specified by:
        +
        refreshRow in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or if the current row is + the insert row or if the row has been deleted or if not on a + valid row
        +
        +
      • +
      + + + +
        +
      • +

        cancelRowUpdates

        +
        public void cancelRowUpdates()
        +                      throws java.sql.SQLException
        +
        Cancels updating a row.
        +
        +
        Specified by:
        +
        cancelRowUpdates in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or if the current row is + the insert row
        +
        +
      • +
      + + + +
        +
      • +

        getInternal

        +
        public org.h2.value.Value getInternal(int columnIndex)
        +
        INTERNAL
        +
        +
        Parameters:
        +
        columnIndex - index of a column
        +
        Returns:
        +
        internal representation of the value in the specified column
        +
        +
      • +
      + + + +
        +
      • +

        getRowId

        +
        public java.sql.RowId getRowId(int columnIndex)
        +                        throws java.sql.SQLException
        +
        [Not supported] Returns the value of the specified column as a row id.
        +
        +
        Specified by:
        +
        getRowId in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getRowId

        +
        public java.sql.RowId getRowId(java.lang.String columnLabel)
        +                        throws java.sql.SQLException
        +
        [Not supported] Returns the value of the specified column as a row id.
        +
        +
        Specified by:
        +
        getRowId in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateRowId

        +
        public void updateRowId(int columnIndex,
        +                        java.sql.RowId x)
        +                 throws java.sql.SQLException
        +
        [Not supported] Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateRowId in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateRowId

        +
        public void updateRowId(java.lang.String columnLabel,
        +                        java.sql.RowId x)
        +                 throws java.sql.SQLException
        +
        [Not supported] Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateRowId in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getHoldability

        +
        public int getHoldability()
        +                   throws java.sql.SQLException
        +
        Returns the current result set holdability.
        +
        +
        Specified by:
        +
        getHoldability in interface java.sql.ResultSet
        +
        Returns:
        +
        the holdability
        +
        Throws:
        +
        java.sql.SQLException - if the connection is closed
        +
        +
      • +
      + + + +
        +
      • +

        isClosed

        +
        public boolean isClosed()
        +                 throws java.sql.SQLException
        +
        Returns whether this result set is closed.
        +
        +
        Specified by:
        +
        isClosed in interface java.sql.ResultSet
        +
        Returns:
        +
        true if the result set is closed
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateNString

        +
        public void updateNString(int columnIndex,
        +                          java.lang.String x)
        +                   throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateNString in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateNString

        +
        public void updateNString(java.lang.String columnLabel,
        +                          java.lang.String x)
        +                   throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateNString in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateNClob

        +
        public void updateNClob(int columnIndex,
        +                        java.sql.NClob x)
        +                 throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateNClob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateNClob

        +
        public void updateNClob(int columnIndex,
        +                        java.io.Reader x)
        +                 throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateNClob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateNClob

        +
        public void updateNClob(int columnIndex,
        +                        java.io.Reader x,
        +                        long length)
        +                 throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateNClob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        length - the length
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateNClob

        +
        public void updateNClob(java.lang.String columnLabel,
        +                        java.io.Reader x)
        +                 throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateNClob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateNClob

        +
        public void updateNClob(java.lang.String columnLabel,
        +                        java.io.Reader x,
        +                        long length)
        +                 throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateNClob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        length - the length
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateNClob

        +
        public void updateNClob(java.lang.String columnLabel,
        +                        java.sql.NClob x)
        +                 throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateNClob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        getNClob

        +
        public java.sql.NClob getNClob(int columnIndex)
        +                        throws java.sql.SQLException
        +
        Returns the value of the specified column as a Clob.
        +
        +
        Specified by:
        +
        getNClob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getNClob

        +
        public java.sql.NClob getNClob(java.lang.String columnLabel)
        +                        throws java.sql.SQLException
        +
        Returns the value of the specified column as a Clob.
        +
        +
        Specified by:
        +
        getNClob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getSQLXML

        +
        public java.sql.SQLXML getSQLXML(int columnIndex)
        +                          throws java.sql.SQLException
        +
        Returns the value of the specified column as a SQLXML.
        +
        +
        Specified by:
        +
        getSQLXML in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getSQLXML

        +
        public java.sql.SQLXML getSQLXML(java.lang.String columnLabel)
        +                          throws java.sql.SQLException
        +
        Returns the value of the specified column as a SQLXML.
        +
        +
        Specified by:
        +
        getSQLXML in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        updateSQLXML

        +
        public void updateSQLXML(int columnIndex,
        +                         java.sql.SQLXML xmlObject)
        +                  throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateSQLXML in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        xmlObject - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateSQLXML

        +
        public void updateSQLXML(java.lang.String columnLabel,
        +                         java.sql.SQLXML xmlObject)
        +                  throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateSQLXML in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        xmlObject - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        getNString

        +
        public java.lang.String getNString(int columnIndex)
        +                            throws java.sql.SQLException
        +
        Returns the value of the specified column as a String.
        +
        +
        Specified by:
        +
        getNString in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getNString

        +
        public java.lang.String getNString(java.lang.String columnLabel)
        +                            throws java.sql.SQLException
        +
        Returns the value of the specified column as a String.
        +
        +
        Specified by:
        +
        getNString in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getNCharacterStream

        +
        public java.io.Reader getNCharacterStream(int columnIndex)
        +                                   throws java.sql.SQLException
        +
        Returns the value of the specified column as a reader.
        +
        +
        Specified by:
        +
        getNCharacterStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getNCharacterStream

        +
        public java.io.Reader getNCharacterStream(java.lang.String columnLabel)
        +                                   throws java.sql.SQLException
        +
        Returns the value of the specified column as a reader.
        +
        +
        Specified by:
        +
        getNCharacterStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        updateNCharacterStream

        +
        public void updateNCharacterStream(int columnIndex,
        +                                   java.io.Reader x)
        +                            throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateNCharacterStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateNCharacterStream

        +
        public void updateNCharacterStream(int columnIndex,
        +                                   java.io.Reader x,
        +                                   long length)
        +                            throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateNCharacterStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        x - the value
        +
        length - the number of characters
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateNCharacterStream

        +
        public void updateNCharacterStream(java.lang.String columnLabel,
        +                                   java.io.Reader x)
        +                            throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateNCharacterStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        updateNCharacterStream

        +
        public void updateNCharacterStream(java.lang.String columnLabel,
        +                                   java.io.Reader x,
        +                                   long length)
        +                            throws java.sql.SQLException
        +
        Updates a column in the current or insert row.
        +
        +
        Specified by:
        +
        updateNCharacterStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        x - the value
        +
        length - the number of characters
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or not updatable
        +
        +
      • +
      + + + +
        +
      • +

        unwrap

        +
        public <T> T unwrap(java.lang.Class<T> iface)
        +             throws java.sql.SQLException
        +
        Return an object of this class if possible.
        +
        +
        Specified by:
        +
        unwrap in interface java.sql.Wrapper
        +
        Parameters:
        +
        iface - the class
        +
        Returns:
        +
        this
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        isWrapperFor

        +
        public boolean isWrapperFor(java.lang.Class<?> iface)
        +                     throws java.sql.SQLException
        +
        Checks if unwrap can return an object of this class.
        +
        +
        Specified by:
        +
        isWrapperFor in interface java.sql.Wrapper
        +
        Parameters:
        +
        iface - the class
        +
        Returns:
        +
        whether or not the interface is assignable from this class
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getObject

        +
        public <T> T getObject(int columnIndex,
        +                       java.lang.Class<T> type)
        +                throws java.sql.SQLException
        +
        Returns a column value as a Java object of the specified type.
        +
        +
        Specified by:
        +
        getObject in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - the column index (1, 2, ...)
        +
        type - the class of the returned value
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getObject

        +
        public <T> T getObject(java.lang.String columnName,
        +                       java.lang.Class<T> type)
        +                throws java.sql.SQLException
        +
        Returns a column value as a Java object of the specified type.
        +
        +
        Specified by:
        +
        getObject in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnName - the column name
        +
        type - the class of the returned value
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        INTERNAL
        +
        +
        Overrides:
        +
        toString in class java.lang.Object
        +
        +
      • +
      + + + +
        +
      • +

        getUpdateRow

        +
        public org.h2.value.Value[] getUpdateRow()
        +
        INTERNAL
        +
        +
        Returns:
        +
        array of column values for the current row
        +
        +
      • +
      + + + +
        +
      • +

        getResult

        +
        public org.h2.result.ResultInterface getResult()
        +
        INTERNAL
        +
        +
        Returns:
        +
        result
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcResultSetMetaData.html b/h2/docs/javadoc/org/h2/jdbc/JdbcResultSetMetaData.html new file mode 100644 index 0000000..5173b0e --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcResultSetMetaData.html @@ -0,0 +1,915 @@ + + + + + +JdbcResultSetMetaData + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcResultSetMetaData

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.message.TraceObject
    • +
    • +
        +
      • org.h2.jdbc.JdbcResultSetMetaData
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.sql.ResultSetMetaData, java.sql.Wrapper
    +
    +
    +
    +
    public final class JdbcResultSetMetaData
    +extends org.h2.message.TraceObject
    +implements java.sql.ResultSetMetaData
    +
    Represents the meta data for a ResultSet.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.message.TraceObject

        +ARRAY, BLOB, CALLABLE_STATEMENT, CLOB, CONNECTION, DATA_SOURCE, DATABASE_META_DATA, PARAMETER_META_DATA, PREPARED_STATEMENT, RESULT_SET, RESULT_SET_META_DATA, SAVEPOINT, SQLXML, STATEMENT, trace, XA_DATA_SOURCE, XID
      • +
      +
        +
      • + + +

        Fields inherited from interface java.sql.ResultSetMetaData

        +columnNoNulls, columnNullable, columnNullableUnknown
      • +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      java.lang.StringgetCatalogName(int column) +
      Returns the catalog name.
      +
      java.lang.StringgetColumnClassName(int column) +
      Gets the Java class name of the object that will be returned + if ResultSet.getObject is called.
      +
      intgetColumnCount() +
      Returns the number of columns.
      +
      intgetColumnDisplaySize(int column) +
      Gets the maximum display size for this column.
      +
      java.lang.StringgetColumnLabel(int column) +
      Returns the column label.
      +
      java.lang.StringgetColumnName(int column) +
      Returns the column name.
      +
      intgetColumnType(int column) +
      Returns the data type of a column.
      +
      java.lang.StringgetColumnTypeName(int column) +
      Returns the data type name of a column.
      +
      intgetPrecision(int column) +
      Gets the precision for this column.
      +
      intgetScale(int column) +
      Gets the scale for this column.
      +
      java.lang.StringgetSchemaName(int column) +
      Returns the schema name.
      +
      java.lang.StringgetTableName(int column) +
      Returns the table name.
      +
      booleanisAutoIncrement(int column) +
      Checks if this an autoincrement column.
      +
      booleanisCaseSensitive(int column) +
      Checks if this column is case sensitive.
      +
      booleanisCurrency(int column) +
      Checks if this is a currency column.
      +
      booleanisDefinitelyWritable(int column) +
      Checks whether a write on this column will definitely succeed.
      +
      intisNullable(int column) +
      Checks if this is nullable column.
      +
      booleanisReadOnly(int column) +
      Checks if this column is read only.
      +
      booleanisSearchable(int column) +
      Checks if this column is searchable.
      +
      booleanisSigned(int column) +
      Checks if this column is signed.
      +
      booleanisWrapperFor(java.lang.Class<?> iface) +
      Checks if unwrap can return an object of this class.
      +
      booleanisWritable(int column) +
      Checks whether it is possible for a write on this column to succeed.
      +
      java.lang.StringtoString() +
      INTERNAL
      +
      <T> Tunwrap(java.lang.Class<T> iface) +
      Return an object of this class if possible.
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.message.TraceObject

        +debugCode, debugCodeAssign, debugCodeCall, debugCodeCall, debugCodeCall, getNextId, getTraceId, getTraceObjectName, isDebugEnabled, isInfoEnabled, logAndConvert, quote, quoteArray, quoteBigDecimal, quoteBytes, quoteDate, quoteIntArray, quoteMap, quoteTime, quoteTimestamp, setTrace, unsupported
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getColumnCount

        +
        public int getColumnCount()
        +                   throws java.sql.SQLException
        +
        Returns the number of columns.
        +
        +
        Specified by:
        +
        getColumnCount in interface java.sql.ResultSetMetaData
        +
        Returns:
        +
        the number of columns
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        getColumnLabel

        +
        public java.lang.String getColumnLabel(int column)
        +                                throws java.sql.SQLException
        +
        Returns the column label.
        +
        +
        Specified by:
        +
        getColumnLabel in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        column - the column index (1,2,...)
        +
        Returns:
        +
        the column label
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        getColumnName

        +
        public java.lang.String getColumnName(int column)
        +                               throws java.sql.SQLException
        +
        Returns the column name.
        +
        +
        Specified by:
        +
        getColumnName in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        column - the column index (1,2,...)
        +
        Returns:
        +
        the column name
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        getColumnType

        +
        public int getColumnType(int column)
        +                  throws java.sql.SQLException
        +
        Returns the data type of a column. + See also java.sql.Type.
        +
        +
        Specified by:
        +
        getColumnType in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        column - the column index (1,2,...)
        +
        Returns:
        +
        the data type
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        getColumnTypeName

        +
        public java.lang.String getColumnTypeName(int column)
        +                                   throws java.sql.SQLException
        +
        Returns the data type name of a column.
        +
        +
        Specified by:
        +
        getColumnTypeName in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        column - the column index (1,2,...)
        +
        Returns:
        +
        the data type name
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        getSchemaName

        +
        public java.lang.String getSchemaName(int column)
        +                               throws java.sql.SQLException
        +
        Returns the schema name.
        +
        +
        Specified by:
        +
        getSchemaName in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        column - the column index (1,2,...)
        +
        Returns:
        +
        the schema name, or "" (an empty string) if not applicable
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        getTableName

        +
        public java.lang.String getTableName(int column)
        +                              throws java.sql.SQLException
        +
        Returns the table name.
        +
        +
        Specified by:
        +
        getTableName in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        column - the column index (1,2,...)
        +
        Returns:
        +
        the table name
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        getCatalogName

        +
        public java.lang.String getCatalogName(int column)
        +                                throws java.sql.SQLException
        +
        Returns the catalog name.
        +
        +
        Specified by:
        +
        getCatalogName in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        column - the column index (1,2,...)
        +
        Returns:
        +
        the catalog name
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        isAutoIncrement

        +
        public boolean isAutoIncrement(int column)
        +                        throws java.sql.SQLException
        +
        Checks if this an autoincrement column.
        +
        +
        Specified by:
        +
        isAutoIncrement in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        column - the column index (1,2,...)
        +
        Returns:
        +
        false
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        isCaseSensitive

        +
        public boolean isCaseSensitive(int column)
        +                        throws java.sql.SQLException
        +
        Checks if this column is case sensitive. + It always returns true.
        +
        +
        Specified by:
        +
        isCaseSensitive in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        column - the column index (1,2,...)
        +
        Returns:
        +
        true
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        isSearchable

        +
        public boolean isSearchable(int column)
        +                     throws java.sql.SQLException
        +
        Checks if this column is searchable. + It always returns true.
        +
        +
        Specified by:
        +
        isSearchable in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        column - the column index (1,2,...)
        +
        Returns:
        +
        true
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        isCurrency

        +
        public boolean isCurrency(int column)
        +                   throws java.sql.SQLException
        +
        Checks if this is a currency column. + It always returns false.
        +
        +
        Specified by:
        +
        isCurrency in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        column - the column index (1,2,...)
        +
        Returns:
        +
        false
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        isNullable

        +
        public int isNullable(int column)
        +               throws java.sql.SQLException
        +
        Checks if this is nullable column. Returns + ResultSetMetaData.columnNullableUnknown if this is not a column of a + table. Otherwise, it returns ResultSetMetaData.columnNoNulls if the + column is not nullable, and ResultSetMetaData.columnNullable if it is + nullable.
        +
        +
        Specified by:
        +
        isNullable in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        column - the column index (1,2,...)
        +
        Returns:
        +
        ResultSetMetaData.column*
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        isSigned

        +
        public boolean isSigned(int column)
        +                 throws java.sql.SQLException
        +
        Checks if this column is signed. + Returns true for numeric columns.
        +
        +
        Specified by:
        +
        isSigned in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        column - the column index (1,2,...)
        +
        Returns:
        +
        true for numeric columns
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        isReadOnly

        +
        public boolean isReadOnly(int column)
        +                   throws java.sql.SQLException
        +
        Checks if this column is read only. + It always returns false.
        +
        +
        Specified by:
        +
        isReadOnly in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        column - the column index (1,2,...)
        +
        Returns:
        +
        false
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        isWritable

        +
        public boolean isWritable(int column)
        +                   throws java.sql.SQLException
        +
        Checks whether it is possible for a write on this column to succeed. + It always returns true.
        +
        +
        Specified by:
        +
        isWritable in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        column - the column index (1,2,...)
        +
        Returns:
        +
        true
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        isDefinitelyWritable

        +
        public boolean isDefinitelyWritable(int column)
        +                             throws java.sql.SQLException
        +
        Checks whether a write on this column will definitely succeed. + It always returns false.
        +
        +
        Specified by:
        +
        isDefinitelyWritable in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        column - the column index (1,2,...)
        +
        Returns:
        +
        false
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        getColumnClassName

        +
        public java.lang.String getColumnClassName(int column)
        +                                    throws java.sql.SQLException
        +
        Gets the Java class name of the object that will be returned + if ResultSet.getObject is called.
        +
        +
        Specified by:
        +
        getColumnClassName in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        column - the column index (1,2,...)
        +
        Returns:
        +
        the Java class name
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        getPrecision

        +
        public int getPrecision(int column)
        +                 throws java.sql.SQLException
        +
        Gets the precision for this column.
        +
        +
        Specified by:
        +
        getPrecision in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        column - the column index (1,2,...)
        +
        Returns:
        +
        the precision
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        getScale

        +
        public int getScale(int column)
        +             throws java.sql.SQLException
        +
        Gets the scale for this column.
        +
        +
        Specified by:
        +
        getScale in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        column - the column index (1,2,...)
        +
        Returns:
        +
        the scale
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        getColumnDisplaySize

        +
        public int getColumnDisplaySize(int column)
        +                         throws java.sql.SQLException
        +
        Gets the maximum display size for this column.
        +
        +
        Specified by:
        +
        getColumnDisplaySize in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        column - the column index (1,2,...)
        +
        Returns:
        +
        the display size
        +
        Throws:
        +
        java.sql.SQLException - if the result set is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        unwrap

        +
        public <T> T unwrap(java.lang.Class<T> iface)
        +             throws java.sql.SQLException
        +
        Return an object of this class if possible.
        +
        +
        Specified by:
        +
        unwrap in interface java.sql.Wrapper
        +
        Parameters:
        +
        iface - the class
        +
        Returns:
        +
        this
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        isWrapperFor

        +
        public boolean isWrapperFor(java.lang.Class<?> iface)
        +                     throws java.sql.SQLException
        +
        Checks if unwrap can return an object of this class.
        +
        +
        Specified by:
        +
        isWrapperFor in interface java.sql.Wrapper
        +
        Parameters:
        +
        iface - the class
        +
        Returns:
        +
        whether or not the interface is assignable from this class
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        INTERNAL
        +
        +
        Overrides:
        +
        toString in class java.lang.Object
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcSQLDataException.html b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLDataException.html new file mode 100644 index 0000000..405e871 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLDataException.html @@ -0,0 +1,491 @@ + + + + + +JdbcSQLDataException + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcSQLDataException

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • java.lang.Throwable
    • +
    • +
        +
      • java.lang.Exception
      • +
      • +
          +
        • java.sql.SQLException
        • +
        • +
            +
          • java.sql.SQLNonTransientException
          • +
          • +
              +
            • java.sql.SQLDataException
            • +
            • +
                +
              • org.h2.jdbc.JdbcSQLDataException
              • +
              +
            • +
            +
          • +
          +
        • +
        +
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.lang.Iterable<java.lang.Throwable>, JdbcException
    +
    +
    +
    +
    public final class JdbcSQLDataException
    +extends java.sql.SQLDataException
    +implements JdbcException
    +
    Represents a database exception.
    +
    +
    See Also:
    +
    Serialized Form
    +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      JdbcSQLDataException(java.lang.String message, + java.lang.String sql, + java.lang.String state, + int errorCode, + java.lang.Throwable cause, + java.lang.String stackTrace) +
      Creates a SQLDataException.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      java.lang.StringgetMessage() 
      java.lang.StringgetOriginalMessage() +
      INTERNAL
      +
      java.lang.StringgetSQL() +
      Returns the SQL statement.
      +
      voidprintStackTrace(java.io.PrintStream s) 
      voidprintStackTrace(java.io.PrintWriter s) 
      voidsetSQL(java.lang.String sql) +
      INTERNAL
      +
      java.lang.StringtoString() +
      Returns the class name, the message, and in the server mode, the stack + trace of the server
      +
      +
        +
      • + + +

        Methods inherited from class java.sql.SQLException

        +getErrorCode, getNextException, getSQLState, iterator, setNextException
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getStackTrace, getSuppressed, initCause, printStackTrace, setStackTrace
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      + +
        +
      • + + +

        Methods inherited from interface java.lang.Iterable

        +forEach, spliterator
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        JdbcSQLDataException

        +
        public JdbcSQLDataException(java.lang.String message,
        +                            java.lang.String sql,
        +                            java.lang.String state,
        +                            int errorCode,
        +                            java.lang.Throwable cause,
        +                            java.lang.String stackTrace)
        +
        Creates a SQLDataException.
        +
        +
        Parameters:
        +
        message - the reason
        +
        sql - the SQL statement
        +
        state - the SQL state
        +
        errorCode - the error code
        +
        cause - the exception that was the reason for this exception
        +
        stackTrace - the stack trace
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getMessage

        +
        public java.lang.String getMessage()
        +
        +
        Overrides:
        +
        getMessage in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        getOriginalMessage

        +
        public java.lang.String getOriginalMessage()
        +
        Description copied from interface: JdbcException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getOriginalMessage in interface JdbcException
        +
        Returns:
        +
        original message
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintWriter s)
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintStream s)
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        getSQL

        +
        public java.lang.String getSQL()
        +
        Description copied from interface: JdbcException
        +
        Returns the SQL statement. +

        + SQL statements that contain '--hide--' are not listed. +

        +
        +
        Specified by:
        +
        getSQL in interface JdbcException
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        setSQL

        +
        public void setSQL(java.lang.String sql)
        +
        Description copied from interface: JdbcException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        setSQL in interface JdbcException
        +
        Parameters:
        +
        sql - to set
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        Description copied from interface: JdbcException
        +
        Returns the class name, the message, and in the server mode, the stack + trace of the server
        +
        +
        Specified by:
        +
        toString in interface JdbcException
        +
        Overrides:
        +
        toString in class java.lang.Throwable
        +
        Returns:
        +
        the string representation
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcSQLException.html b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLException.html new file mode 100644 index 0000000..a3daef6 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLException.html @@ -0,0 +1,481 @@ + + + + + +JdbcSQLException + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcSQLException

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • java.lang.Throwable
    • +
    • +
        +
      • java.lang.Exception
      • +
      • +
          +
        • java.sql.SQLException
        • +
        • +
            +
          • org.h2.jdbc.JdbcSQLException
          • +
          +
        • +
        +
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.lang.Iterable<java.lang.Throwable>, JdbcException
    +
    +
    +
    +
    public final class JdbcSQLException
    +extends java.sql.SQLException
    +implements JdbcException
    +
    Represents a database exception.
    +
    +
    See Also:
    +
    Serialized Form
    +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      JdbcSQLException(java.lang.String message, + java.lang.String sql, + java.lang.String state, + int errorCode, + java.lang.Throwable cause, + java.lang.String stackTrace) +
      Creates a SQLException.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      java.lang.StringgetMessage() 
      java.lang.StringgetOriginalMessage() +
      INTERNAL
      +
      java.lang.StringgetSQL() +
      Returns the SQL statement.
      +
      voidprintStackTrace(java.io.PrintStream s) 
      voidprintStackTrace(java.io.PrintWriter s) 
      voidsetSQL(java.lang.String sql) +
      INTERNAL
      +
      java.lang.StringtoString() +
      Returns the class name, the message, and in the server mode, the stack + trace of the server
      +
      +
        +
      • + + +

        Methods inherited from class java.sql.SQLException

        +getErrorCode, getNextException, getSQLState, iterator, setNextException
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getStackTrace, getSuppressed, initCause, printStackTrace, setStackTrace
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      + +
        +
      • + + +

        Methods inherited from interface java.lang.Iterable

        +forEach, spliterator
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        JdbcSQLException

        +
        public JdbcSQLException(java.lang.String message,
        +                        java.lang.String sql,
        +                        java.lang.String state,
        +                        int errorCode,
        +                        java.lang.Throwable cause,
        +                        java.lang.String stackTrace)
        +
        Creates a SQLException.
        +
        +
        Parameters:
        +
        message - the reason
        +
        sql - the SQL statement
        +
        state - the SQL state
        +
        errorCode - the error code
        +
        cause - the exception that was the reason for this exception
        +
        stackTrace - the stack trace
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getMessage

        +
        public java.lang.String getMessage()
        +
        +
        Overrides:
        +
        getMessage in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        getOriginalMessage

        +
        public java.lang.String getOriginalMessage()
        +
        Description copied from interface: JdbcException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getOriginalMessage in interface JdbcException
        +
        Returns:
        +
        original message
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintWriter s)
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintStream s)
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        getSQL

        +
        public java.lang.String getSQL()
        +
        Description copied from interface: JdbcException
        +
        Returns the SQL statement. +

        + SQL statements that contain '--hide--' are not listed. +

        +
        +
        Specified by:
        +
        getSQL in interface JdbcException
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        setSQL

        +
        public void setSQL(java.lang.String sql)
        +
        Description copied from interface: JdbcException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        setSQL in interface JdbcException
        +
        Parameters:
        +
        sql - to set
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        Description copied from interface: JdbcException
        +
        Returns the class name, the message, and in the server mode, the stack + trace of the server
        +
        +
        Specified by:
        +
        toString in interface JdbcException
        +
        Overrides:
        +
        toString in class java.lang.Throwable
        +
        Returns:
        +
        the string representation
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcSQLFeatureNotSupportedException.html b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLFeatureNotSupportedException.html new file mode 100644 index 0000000..e1f1dd5 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLFeatureNotSupportedException.html @@ -0,0 +1,491 @@ + + + + + +JdbcSQLFeatureNotSupportedException + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcSQLFeatureNotSupportedException

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • java.lang.Throwable
    • +
    • +
        +
      • java.lang.Exception
      • +
      • +
          +
        • java.sql.SQLException
        • +
        • +
            +
          • java.sql.SQLNonTransientException
          • +
          • +
              +
            • java.sql.SQLFeatureNotSupportedException
            • +
            • +
                +
              • org.h2.jdbc.JdbcSQLFeatureNotSupportedException
              • +
              +
            • +
            +
          • +
          +
        • +
        +
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.lang.Iterable<java.lang.Throwable>, JdbcException
    +
    +
    +
    +
    public final class JdbcSQLFeatureNotSupportedException
    +extends java.sql.SQLFeatureNotSupportedException
    +implements JdbcException
    +
    Represents a database exception.
    +
    +
    See Also:
    +
    Serialized Form
    +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      JdbcSQLFeatureNotSupportedException(java.lang.String message, + java.lang.String sql, + java.lang.String state, + int errorCode, + java.lang.Throwable cause, + java.lang.String stackTrace) +
      Creates a SQLFeatureNotSupportedException.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      java.lang.StringgetMessage() 
      java.lang.StringgetOriginalMessage() +
      INTERNAL
      +
      java.lang.StringgetSQL() +
      Returns the SQL statement.
      +
      voidprintStackTrace(java.io.PrintStream s) 
      voidprintStackTrace(java.io.PrintWriter s) 
      voidsetSQL(java.lang.String sql) +
      INTERNAL
      +
      java.lang.StringtoString() +
      Returns the class name, the message, and in the server mode, the stack + trace of the server
      +
      +
        +
      • + + +

        Methods inherited from class java.sql.SQLException

        +getErrorCode, getNextException, getSQLState, iterator, setNextException
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getStackTrace, getSuppressed, initCause, printStackTrace, setStackTrace
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      + +
        +
      • + + +

        Methods inherited from interface java.lang.Iterable

        +forEach, spliterator
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        JdbcSQLFeatureNotSupportedException

        +
        public JdbcSQLFeatureNotSupportedException(java.lang.String message,
        +                                           java.lang.String sql,
        +                                           java.lang.String state,
        +                                           int errorCode,
        +                                           java.lang.Throwable cause,
        +                                           java.lang.String stackTrace)
        +
        Creates a SQLFeatureNotSupportedException.
        +
        +
        Parameters:
        +
        message - the reason
        +
        sql - the SQL statement
        +
        state - the SQL state
        +
        errorCode - the error code
        +
        cause - the exception that was the reason for this exception
        +
        stackTrace - the stack trace
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getMessage

        +
        public java.lang.String getMessage()
        +
        +
        Overrides:
        +
        getMessage in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        getOriginalMessage

        +
        public java.lang.String getOriginalMessage()
        +
        Description copied from interface: JdbcException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getOriginalMessage in interface JdbcException
        +
        Returns:
        +
        original message
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintWriter s)
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintStream s)
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        getSQL

        +
        public java.lang.String getSQL()
        +
        Description copied from interface: JdbcException
        +
        Returns the SQL statement. +

        + SQL statements that contain '--hide--' are not listed. +

        +
        +
        Specified by:
        +
        getSQL in interface JdbcException
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        setSQL

        +
        public void setSQL(java.lang.String sql)
        +
        Description copied from interface: JdbcException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        setSQL in interface JdbcException
        +
        Parameters:
        +
        sql - to set
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        Description copied from interface: JdbcException
        +
        Returns the class name, the message, and in the server mode, the stack + trace of the server
        +
        +
        Specified by:
        +
        toString in interface JdbcException
        +
        Overrides:
        +
        toString in class java.lang.Throwable
        +
        Returns:
        +
        the string representation
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcSQLIntegrityConstraintViolationException.html b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLIntegrityConstraintViolationException.html new file mode 100644 index 0000000..3eb2746 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLIntegrityConstraintViolationException.html @@ -0,0 +1,491 @@ + + + + + +JdbcSQLIntegrityConstraintViolationException + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcSQLIntegrityConstraintViolationException

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • java.lang.Throwable
    • +
    • +
        +
      • java.lang.Exception
      • +
      • +
          +
        • java.sql.SQLException
        • +
        • +
            +
          • java.sql.SQLNonTransientException
          • +
          • +
              +
            • java.sql.SQLIntegrityConstraintViolationException
            • +
            • +
                +
              • org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException
              • +
              +
            • +
            +
          • +
          +
        • +
        +
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.lang.Iterable<java.lang.Throwable>, JdbcException
    +
    +
    +
    +
    public final class JdbcSQLIntegrityConstraintViolationException
    +extends java.sql.SQLIntegrityConstraintViolationException
    +implements JdbcException
    +
    Represents a database exception.
    +
    +
    See Also:
    +
    Serialized Form
    +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      JdbcSQLIntegrityConstraintViolationException(java.lang.String message, + java.lang.String sql, + java.lang.String state, + int errorCode, + java.lang.Throwable cause, + java.lang.String stackTrace) +
      Creates a SQLIntegrityConstraintViolationException.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      java.lang.StringgetMessage() 
      java.lang.StringgetOriginalMessage() +
      INTERNAL
      +
      java.lang.StringgetSQL() +
      Returns the SQL statement.
      +
      voidprintStackTrace(java.io.PrintStream s) 
      voidprintStackTrace(java.io.PrintWriter s) 
      voidsetSQL(java.lang.String sql) +
      INTERNAL
      +
      java.lang.StringtoString() +
      Returns the class name, the message, and in the server mode, the stack + trace of the server
      +
      +
        +
      • + + +

        Methods inherited from class java.sql.SQLException

        +getErrorCode, getNextException, getSQLState, iterator, setNextException
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getStackTrace, getSuppressed, initCause, printStackTrace, setStackTrace
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      + +
        +
      • + + +

        Methods inherited from interface java.lang.Iterable

        +forEach, spliterator
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        JdbcSQLIntegrityConstraintViolationException

        +
        public JdbcSQLIntegrityConstraintViolationException(java.lang.String message,
        +                                                    java.lang.String sql,
        +                                                    java.lang.String state,
        +                                                    int errorCode,
        +                                                    java.lang.Throwable cause,
        +                                                    java.lang.String stackTrace)
        +
        Creates a SQLIntegrityConstraintViolationException.
        +
        +
        Parameters:
        +
        message - the reason
        +
        sql - the SQL statement
        +
        state - the SQL state
        +
        errorCode - the error code
        +
        cause - the exception that was the reason for this exception
        +
        stackTrace - the stack trace
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getMessage

        +
        public java.lang.String getMessage()
        +
        +
        Overrides:
        +
        getMessage in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        getOriginalMessage

        +
        public java.lang.String getOriginalMessage()
        +
        Description copied from interface: JdbcException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getOriginalMessage in interface JdbcException
        +
        Returns:
        +
        original message
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintWriter s)
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintStream s)
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        getSQL

        +
        public java.lang.String getSQL()
        +
        Description copied from interface: JdbcException
        +
        Returns the SQL statement. +

        + SQL statements that contain '--hide--' are not listed. +

        +
        +
        Specified by:
        +
        getSQL in interface JdbcException
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        setSQL

        +
        public void setSQL(java.lang.String sql)
        +
        Description copied from interface: JdbcException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        setSQL in interface JdbcException
        +
        Parameters:
        +
        sql - to set
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        Description copied from interface: JdbcException
        +
        Returns the class name, the message, and in the server mode, the stack + trace of the server
        +
        +
        Specified by:
        +
        toString in interface JdbcException
        +
        Overrides:
        +
        toString in class java.lang.Throwable
        +
        Returns:
        +
        the string representation
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcSQLInvalidAuthorizationSpecException.html b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLInvalidAuthorizationSpecException.html new file mode 100644 index 0000000..9d0afdc --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLInvalidAuthorizationSpecException.html @@ -0,0 +1,491 @@ + + + + + +JdbcSQLInvalidAuthorizationSpecException + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcSQLInvalidAuthorizationSpecException

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • java.lang.Throwable
    • +
    • +
        +
      • java.lang.Exception
      • +
      • +
          +
        • java.sql.SQLException
        • +
        • +
            +
          • java.sql.SQLNonTransientException
          • +
          • +
              +
            • java.sql.SQLInvalidAuthorizationSpecException
            • +
            • +
                +
              • org.h2.jdbc.JdbcSQLInvalidAuthorizationSpecException
              • +
              +
            • +
            +
          • +
          +
        • +
        +
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.lang.Iterable<java.lang.Throwable>, JdbcException
    +
    +
    +
    +
    public final class JdbcSQLInvalidAuthorizationSpecException
    +extends java.sql.SQLInvalidAuthorizationSpecException
    +implements JdbcException
    +
    Represents a database exception.
    +
    +
    See Also:
    +
    Serialized Form
    +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      JdbcSQLInvalidAuthorizationSpecException(java.lang.String message, + java.lang.String sql, + java.lang.String state, + int errorCode, + java.lang.Throwable cause, + java.lang.String stackTrace) +
      Creates a SQLInvalidAuthorizationSpecException.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      java.lang.StringgetMessage() 
      java.lang.StringgetOriginalMessage() +
      INTERNAL
      +
      java.lang.StringgetSQL() +
      Returns the SQL statement.
      +
      voidprintStackTrace(java.io.PrintStream s) 
      voidprintStackTrace(java.io.PrintWriter s) 
      voidsetSQL(java.lang.String sql) +
      INTERNAL
      +
      java.lang.StringtoString() +
      Returns the class name, the message, and in the server mode, the stack + trace of the server
      +
      +
        +
      • + + +

        Methods inherited from class java.sql.SQLException

        +getErrorCode, getNextException, getSQLState, iterator, setNextException
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getStackTrace, getSuppressed, initCause, printStackTrace, setStackTrace
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      + +
        +
      • + + +

        Methods inherited from interface java.lang.Iterable

        +forEach, spliterator
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        JdbcSQLInvalidAuthorizationSpecException

        +
        public JdbcSQLInvalidAuthorizationSpecException(java.lang.String message,
        +                                                java.lang.String sql,
        +                                                java.lang.String state,
        +                                                int errorCode,
        +                                                java.lang.Throwable cause,
        +                                                java.lang.String stackTrace)
        +
        Creates a SQLInvalidAuthorizationSpecException.
        +
        +
        Parameters:
        +
        message - the reason
        +
        sql - the SQL statement
        +
        state - the SQL state
        +
        errorCode - the error code
        +
        cause - the exception that was the reason for this exception
        +
        stackTrace - the stack trace
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getMessage

        +
        public java.lang.String getMessage()
        +
        +
        Overrides:
        +
        getMessage in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        getOriginalMessage

        +
        public java.lang.String getOriginalMessage()
        +
        Description copied from interface: JdbcException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getOriginalMessage in interface JdbcException
        +
        Returns:
        +
        original message
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintWriter s)
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintStream s)
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        getSQL

        +
        public java.lang.String getSQL()
        +
        Description copied from interface: JdbcException
        +
        Returns the SQL statement. +

        + SQL statements that contain '--hide--' are not listed. +

        +
        +
        Specified by:
        +
        getSQL in interface JdbcException
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        setSQL

        +
        public void setSQL(java.lang.String sql)
        +
        Description copied from interface: JdbcException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        setSQL in interface JdbcException
        +
        Parameters:
        +
        sql - to set
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        Description copied from interface: JdbcException
        +
        Returns the class name, the message, and in the server mode, the stack + trace of the server
        +
        +
        Specified by:
        +
        toString in interface JdbcException
        +
        Overrides:
        +
        toString in class java.lang.Throwable
        +
        Returns:
        +
        the string representation
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcSQLNonTransientConnectionException.html b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLNonTransientConnectionException.html new file mode 100644 index 0000000..7d93d0a --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLNonTransientConnectionException.html @@ -0,0 +1,491 @@ + + + + + +JdbcSQLNonTransientConnectionException + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcSQLNonTransientConnectionException

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • java.lang.Throwable
    • +
    • +
        +
      • java.lang.Exception
      • +
      • +
          +
        • java.sql.SQLException
        • +
        • +
            +
          • java.sql.SQLNonTransientException
          • +
          • +
              +
            • java.sql.SQLNonTransientConnectionException
            • +
            • +
                +
              • org.h2.jdbc.JdbcSQLNonTransientConnectionException
              • +
              +
            • +
            +
          • +
          +
        • +
        +
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.lang.Iterable<java.lang.Throwable>, JdbcException
    +
    +
    +
    +
    public final class JdbcSQLNonTransientConnectionException
    +extends java.sql.SQLNonTransientConnectionException
    +implements JdbcException
    +
    Represents a database exception.
    +
    +
    See Also:
    +
    Serialized Form
    +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      JdbcSQLNonTransientConnectionException(java.lang.String message, + java.lang.String sql, + java.lang.String state, + int errorCode, + java.lang.Throwable cause, + java.lang.String stackTrace) +
      Creates a SQLNonTransientConnectionException.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      java.lang.StringgetMessage() 
      java.lang.StringgetOriginalMessage() +
      INTERNAL
      +
      java.lang.StringgetSQL() +
      Returns the SQL statement.
      +
      voidprintStackTrace(java.io.PrintStream s) 
      voidprintStackTrace(java.io.PrintWriter s) 
      voidsetSQL(java.lang.String sql) +
      INTERNAL
      +
      java.lang.StringtoString() +
      Returns the class name, the message, and in the server mode, the stack + trace of the server
      +
      +
        +
      • + + +

        Methods inherited from class java.sql.SQLException

        +getErrorCode, getNextException, getSQLState, iterator, setNextException
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getStackTrace, getSuppressed, initCause, printStackTrace, setStackTrace
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      + +
        +
      • + + +

        Methods inherited from interface java.lang.Iterable

        +forEach, spliterator
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        JdbcSQLNonTransientConnectionException

        +
        public JdbcSQLNonTransientConnectionException(java.lang.String message,
        +                                              java.lang.String sql,
        +                                              java.lang.String state,
        +                                              int errorCode,
        +                                              java.lang.Throwable cause,
        +                                              java.lang.String stackTrace)
        +
        Creates a SQLNonTransientConnectionException.
        +
        +
        Parameters:
        +
        message - the reason
        +
        sql - the SQL statement
        +
        state - the SQL state
        +
        errorCode - the error code
        +
        cause - the exception that was the reason for this exception
        +
        stackTrace - the stack trace
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getMessage

        +
        public java.lang.String getMessage()
        +
        +
        Overrides:
        +
        getMessage in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        getOriginalMessage

        +
        public java.lang.String getOriginalMessage()
        +
        Description copied from interface: JdbcException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getOriginalMessage in interface JdbcException
        +
        Returns:
        +
        original message
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintWriter s)
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintStream s)
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        getSQL

        +
        public java.lang.String getSQL()
        +
        Description copied from interface: JdbcException
        +
        Returns the SQL statement. +

        + SQL statements that contain '--hide--' are not listed. +

        +
        +
        Specified by:
        +
        getSQL in interface JdbcException
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        setSQL

        +
        public void setSQL(java.lang.String sql)
        +
        Description copied from interface: JdbcException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        setSQL in interface JdbcException
        +
        Parameters:
        +
        sql - to set
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        Description copied from interface: JdbcException
        +
        Returns the class name, the message, and in the server mode, the stack + trace of the server
        +
        +
        Specified by:
        +
        toString in interface JdbcException
        +
        Overrides:
        +
        toString in class java.lang.Throwable
        +
        Returns:
        +
        the string representation
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcSQLNonTransientException.html b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLNonTransientException.html new file mode 100644 index 0000000..d2c24e1 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLNonTransientException.html @@ -0,0 +1,486 @@ + + + + + +JdbcSQLNonTransientException + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcSQLNonTransientException

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • java.lang.Throwable
    • +
    • +
        +
      • java.lang.Exception
      • +
      • +
          +
        • java.sql.SQLException
        • +
        • +
            +
          • java.sql.SQLNonTransientException
          • +
          • +
              +
            • org.h2.jdbc.JdbcSQLNonTransientException
            • +
            +
          • +
          +
        • +
        +
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.lang.Iterable<java.lang.Throwable>, JdbcException
    +
    +
    +
    +
    public final class JdbcSQLNonTransientException
    +extends java.sql.SQLNonTransientException
    +implements JdbcException
    +
    Represents a database exception.
    +
    +
    See Also:
    +
    Serialized Form
    +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      JdbcSQLNonTransientException(java.lang.String message, + java.lang.String sql, + java.lang.String state, + int errorCode, + java.lang.Throwable cause, + java.lang.String stackTrace) +
      Creates a SQLNonTransientException.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      java.lang.StringgetMessage() 
      java.lang.StringgetOriginalMessage() +
      INTERNAL
      +
      java.lang.StringgetSQL() +
      Returns the SQL statement.
      +
      voidprintStackTrace(java.io.PrintStream s) 
      voidprintStackTrace(java.io.PrintWriter s) 
      voidsetSQL(java.lang.String sql) +
      INTERNAL
      +
      java.lang.StringtoString() +
      Returns the class name, the message, and in the server mode, the stack + trace of the server
      +
      +
        +
      • + + +

        Methods inherited from class java.sql.SQLException

        +getErrorCode, getNextException, getSQLState, iterator, setNextException
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getStackTrace, getSuppressed, initCause, printStackTrace, setStackTrace
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      + +
        +
      • + + +

        Methods inherited from interface java.lang.Iterable

        +forEach, spliterator
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        JdbcSQLNonTransientException

        +
        public JdbcSQLNonTransientException(java.lang.String message,
        +                                    java.lang.String sql,
        +                                    java.lang.String state,
        +                                    int errorCode,
        +                                    java.lang.Throwable cause,
        +                                    java.lang.String stackTrace)
        +
        Creates a SQLNonTransientException.
        +
        +
        Parameters:
        +
        message - the reason
        +
        sql - the SQL statement
        +
        state - the SQL state
        +
        errorCode - the error code
        +
        cause - the exception that was the reason for this exception
        +
        stackTrace - the stack trace
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getMessage

        +
        public java.lang.String getMessage()
        +
        +
        Overrides:
        +
        getMessage in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        getOriginalMessage

        +
        public java.lang.String getOriginalMessage()
        +
        Description copied from interface: JdbcException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getOriginalMessage in interface JdbcException
        +
        Returns:
        +
        original message
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintWriter s)
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintStream s)
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        getSQL

        +
        public java.lang.String getSQL()
        +
        Description copied from interface: JdbcException
        +
        Returns the SQL statement. +

        + SQL statements that contain '--hide--' are not listed. +

        +
        +
        Specified by:
        +
        getSQL in interface JdbcException
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        setSQL

        +
        public void setSQL(java.lang.String sql)
        +
        Description copied from interface: JdbcException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        setSQL in interface JdbcException
        +
        Parameters:
        +
        sql - to set
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        Description copied from interface: JdbcException
        +
        Returns the class name, the message, and in the server mode, the stack + trace of the server
        +
        +
        Specified by:
        +
        toString in interface JdbcException
        +
        Overrides:
        +
        toString in class java.lang.Throwable
        +
        Returns:
        +
        the string representation
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcSQLSyntaxErrorException.html b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLSyntaxErrorException.html new file mode 100644 index 0000000..7b69158 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLSyntaxErrorException.html @@ -0,0 +1,491 @@ + + + + + +JdbcSQLSyntaxErrorException + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcSQLSyntaxErrorException

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • java.lang.Throwable
    • +
    • +
        +
      • java.lang.Exception
      • +
      • +
          +
        • java.sql.SQLException
        • +
        • +
            +
          • java.sql.SQLNonTransientException
          • +
          • +
              +
            • java.sql.SQLSyntaxErrorException
            • +
            • +
                +
              • org.h2.jdbc.JdbcSQLSyntaxErrorException
              • +
              +
            • +
            +
          • +
          +
        • +
        +
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.lang.Iterable<java.lang.Throwable>, JdbcException
    +
    +
    +
    +
    public final class JdbcSQLSyntaxErrorException
    +extends java.sql.SQLSyntaxErrorException
    +implements JdbcException
    +
    Represents a database exception.
    +
    +
    See Also:
    +
    Serialized Form
    +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      JdbcSQLSyntaxErrorException(java.lang.String message, + java.lang.String sql, + java.lang.String state, + int errorCode, + java.lang.Throwable cause, + java.lang.String stackTrace) +
      Creates a SQLSyntaxErrorException.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      java.lang.StringgetMessage() 
      java.lang.StringgetOriginalMessage() +
      INTERNAL
      +
      java.lang.StringgetSQL() +
      Returns the SQL statement.
      +
      voidprintStackTrace(java.io.PrintStream s) 
      voidprintStackTrace(java.io.PrintWriter s) 
      voidsetSQL(java.lang.String sql) +
      INTERNAL
      +
      java.lang.StringtoString() +
      Returns the class name, the message, and in the server mode, the stack + trace of the server
      +
      +
        +
      • + + +

        Methods inherited from class java.sql.SQLException

        +getErrorCode, getNextException, getSQLState, iterator, setNextException
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getStackTrace, getSuppressed, initCause, printStackTrace, setStackTrace
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      + +
        +
      • + + +

        Methods inherited from interface java.lang.Iterable

        +forEach, spliterator
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        JdbcSQLSyntaxErrorException

        +
        public JdbcSQLSyntaxErrorException(java.lang.String message,
        +                                   java.lang.String sql,
        +                                   java.lang.String state,
        +                                   int errorCode,
        +                                   java.lang.Throwable cause,
        +                                   java.lang.String stackTrace)
        +
        Creates a SQLSyntaxErrorException.
        +
        +
        Parameters:
        +
        message - the reason
        +
        sql - the SQL statement
        +
        state - the SQL state
        +
        errorCode - the error code
        +
        cause - the exception that was the reason for this exception
        +
        stackTrace - the stack trace
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getMessage

        +
        public java.lang.String getMessage()
        +
        +
        Overrides:
        +
        getMessage in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        getOriginalMessage

        +
        public java.lang.String getOriginalMessage()
        +
        Description copied from interface: JdbcException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getOriginalMessage in interface JdbcException
        +
        Returns:
        +
        original message
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintWriter s)
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintStream s)
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        getSQL

        +
        public java.lang.String getSQL()
        +
        Description copied from interface: JdbcException
        +
        Returns the SQL statement. +

        + SQL statements that contain '--hide--' are not listed. +

        +
        +
        Specified by:
        +
        getSQL in interface JdbcException
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        setSQL

        +
        public void setSQL(java.lang.String sql)
        +
        Description copied from interface: JdbcException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        setSQL in interface JdbcException
        +
        Parameters:
        +
        sql - to set
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        Description copied from interface: JdbcException
        +
        Returns the class name, the message, and in the server mode, the stack + trace of the server
        +
        +
        Specified by:
        +
        toString in interface JdbcException
        +
        Overrides:
        +
        toString in class java.lang.Throwable
        +
        Returns:
        +
        the string representation
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcSQLTimeoutException.html b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLTimeoutException.html new file mode 100644 index 0000000..ed70941 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLTimeoutException.html @@ -0,0 +1,491 @@ + + + + + +JdbcSQLTimeoutException + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcSQLTimeoutException

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • java.lang.Throwable
    • +
    • +
        +
      • java.lang.Exception
      • +
      • +
          +
        • java.sql.SQLException
        • +
        • +
            +
          • java.sql.SQLTransientException
          • +
          • +
              +
            • java.sql.SQLTimeoutException
            • +
            • +
                +
              • org.h2.jdbc.JdbcSQLTimeoutException
              • +
              +
            • +
            +
          • +
          +
        • +
        +
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.lang.Iterable<java.lang.Throwable>, JdbcException
    +
    +
    +
    +
    public final class JdbcSQLTimeoutException
    +extends java.sql.SQLTimeoutException
    +implements JdbcException
    +
    Represents a database exception.
    +
    +
    See Also:
    +
    Serialized Form
    +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      JdbcSQLTimeoutException(java.lang.String message, + java.lang.String sql, + java.lang.String state, + int errorCode, + java.lang.Throwable cause, + java.lang.String stackTrace) +
      Creates a SQLTimeoutException.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      java.lang.StringgetMessage() 
      java.lang.StringgetOriginalMessage() +
      INTERNAL
      +
      java.lang.StringgetSQL() +
      Returns the SQL statement.
      +
      voidprintStackTrace(java.io.PrintStream s) 
      voidprintStackTrace(java.io.PrintWriter s) 
      voidsetSQL(java.lang.String sql) +
      INTERNAL
      +
      java.lang.StringtoString() +
      Returns the class name, the message, and in the server mode, the stack + trace of the server
      +
      +
        +
      • + + +

        Methods inherited from class java.sql.SQLException

        +getErrorCode, getNextException, getSQLState, iterator, setNextException
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getStackTrace, getSuppressed, initCause, printStackTrace, setStackTrace
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      + +
        +
      • + + +

        Methods inherited from interface java.lang.Iterable

        +forEach, spliterator
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        JdbcSQLTimeoutException

        +
        public JdbcSQLTimeoutException(java.lang.String message,
        +                               java.lang.String sql,
        +                               java.lang.String state,
        +                               int errorCode,
        +                               java.lang.Throwable cause,
        +                               java.lang.String stackTrace)
        +
        Creates a SQLTimeoutException.
        +
        +
        Parameters:
        +
        message - the reason
        +
        sql - the SQL statement
        +
        state - the SQL state
        +
        errorCode - the error code
        +
        cause - the exception that was the reason for this exception
        +
        stackTrace - the stack trace
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getMessage

        +
        public java.lang.String getMessage()
        +
        +
        Overrides:
        +
        getMessage in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        getOriginalMessage

        +
        public java.lang.String getOriginalMessage()
        +
        Description copied from interface: JdbcException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getOriginalMessage in interface JdbcException
        +
        Returns:
        +
        original message
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintWriter s)
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintStream s)
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        getSQL

        +
        public java.lang.String getSQL()
        +
        Description copied from interface: JdbcException
        +
        Returns the SQL statement. +

        + SQL statements that contain '--hide--' are not listed. +

        +
        +
        Specified by:
        +
        getSQL in interface JdbcException
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        setSQL

        +
        public void setSQL(java.lang.String sql)
        +
        Description copied from interface: JdbcException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        setSQL in interface JdbcException
        +
        Parameters:
        +
        sql - to set
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        Description copied from interface: JdbcException
        +
        Returns the class name, the message, and in the server mode, the stack + trace of the server
        +
        +
        Specified by:
        +
        toString in interface JdbcException
        +
        Overrides:
        +
        toString in class java.lang.Throwable
        +
        Returns:
        +
        the string representation
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcSQLTransactionRollbackException.html b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLTransactionRollbackException.html new file mode 100644 index 0000000..d77045e --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLTransactionRollbackException.html @@ -0,0 +1,491 @@ + + + + + +JdbcSQLTransactionRollbackException + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcSQLTransactionRollbackException

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • java.lang.Throwable
    • +
    • +
        +
      • java.lang.Exception
      • +
      • +
          +
        • java.sql.SQLException
        • +
        • +
            +
          • java.sql.SQLTransientException
          • +
          • +
              +
            • java.sql.SQLTransactionRollbackException
            • +
            • +
                +
              • org.h2.jdbc.JdbcSQLTransactionRollbackException
              • +
              +
            • +
            +
          • +
          +
        • +
        +
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.lang.Iterable<java.lang.Throwable>, JdbcException
    +
    +
    +
    +
    public final class JdbcSQLTransactionRollbackException
    +extends java.sql.SQLTransactionRollbackException
    +implements JdbcException
    +
    Represents a database exception.
    +
    +
    See Also:
    +
    Serialized Form
    +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      JdbcSQLTransactionRollbackException(java.lang.String message, + java.lang.String sql, + java.lang.String state, + int errorCode, + java.lang.Throwable cause, + java.lang.String stackTrace) +
      Creates a SQLTransactionRollbackException.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      java.lang.StringgetMessage() 
      java.lang.StringgetOriginalMessage() +
      INTERNAL
      +
      java.lang.StringgetSQL() +
      Returns the SQL statement.
      +
      voidprintStackTrace(java.io.PrintStream s) 
      voidprintStackTrace(java.io.PrintWriter s) 
      voidsetSQL(java.lang.String sql) +
      INTERNAL
      +
      java.lang.StringtoString() +
      Returns the class name, the message, and in the server mode, the stack + trace of the server
      +
      +
        +
      • + + +

        Methods inherited from class java.sql.SQLException

        +getErrorCode, getNextException, getSQLState, iterator, setNextException
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getStackTrace, getSuppressed, initCause, printStackTrace, setStackTrace
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      + +
        +
      • + + +

        Methods inherited from interface java.lang.Iterable

        +forEach, spliterator
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        JdbcSQLTransactionRollbackException

        +
        public JdbcSQLTransactionRollbackException(java.lang.String message,
        +                                           java.lang.String sql,
        +                                           java.lang.String state,
        +                                           int errorCode,
        +                                           java.lang.Throwable cause,
        +                                           java.lang.String stackTrace)
        +
        Creates a SQLTransactionRollbackException.
        +
        +
        Parameters:
        +
        message - the reason
        +
        sql - the SQL statement
        +
        state - the SQL state
        +
        errorCode - the error code
        +
        cause - the exception that was the reason for this exception
        +
        stackTrace - the stack trace
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getMessage

        +
        public java.lang.String getMessage()
        +
        +
        Overrides:
        +
        getMessage in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        getOriginalMessage

        +
        public java.lang.String getOriginalMessage()
        +
        Description copied from interface: JdbcException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getOriginalMessage in interface JdbcException
        +
        Returns:
        +
        original message
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintWriter s)
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintStream s)
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        getSQL

        +
        public java.lang.String getSQL()
        +
        Description copied from interface: JdbcException
        +
        Returns the SQL statement. +

        + SQL statements that contain '--hide--' are not listed. +

        +
        +
        Specified by:
        +
        getSQL in interface JdbcException
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        setSQL

        +
        public void setSQL(java.lang.String sql)
        +
        Description copied from interface: JdbcException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        setSQL in interface JdbcException
        +
        Parameters:
        +
        sql - to set
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        Description copied from interface: JdbcException
        +
        Returns the class name, the message, and in the server mode, the stack + trace of the server
        +
        +
        Specified by:
        +
        toString in interface JdbcException
        +
        Overrides:
        +
        toString in class java.lang.Throwable
        +
        Returns:
        +
        the string representation
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcSQLTransientException.html b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLTransientException.html new file mode 100644 index 0000000..ff40a0a --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLTransientException.html @@ -0,0 +1,486 @@ + + + + + +JdbcSQLTransientException + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcSQLTransientException

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • java.lang.Throwable
    • +
    • +
        +
      • java.lang.Exception
      • +
      • +
          +
        • java.sql.SQLException
        • +
        • +
            +
          • java.sql.SQLTransientException
          • +
          • +
              +
            • org.h2.jdbc.JdbcSQLTransientException
            • +
            +
          • +
          +
        • +
        +
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.lang.Iterable<java.lang.Throwable>, JdbcException
    +
    +
    +
    +
    public final class JdbcSQLTransientException
    +extends java.sql.SQLTransientException
    +implements JdbcException
    +
    Represents a database exception.
    +
    +
    See Also:
    +
    Serialized Form
    +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      JdbcSQLTransientException(java.lang.String message, + java.lang.String sql, + java.lang.String state, + int errorCode, + java.lang.Throwable cause, + java.lang.String stackTrace) +
      Creates a SQLTransientException.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      java.lang.StringgetMessage() 
      java.lang.StringgetOriginalMessage() +
      INTERNAL
      +
      java.lang.StringgetSQL() +
      Returns the SQL statement.
      +
      voidprintStackTrace(java.io.PrintStream s) 
      voidprintStackTrace(java.io.PrintWriter s) 
      voidsetSQL(java.lang.String sql) +
      INTERNAL
      +
      java.lang.StringtoString() +
      Returns the class name, the message, and in the server mode, the stack + trace of the server
      +
      +
        +
      • + + +

        Methods inherited from class java.sql.SQLException

        +getErrorCode, getNextException, getSQLState, iterator, setNextException
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Throwable

        +addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getStackTrace, getSuppressed, initCause, printStackTrace, setStackTrace
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      + +
        +
      • + + +

        Methods inherited from interface java.lang.Iterable

        +forEach, spliterator
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        JdbcSQLTransientException

        +
        public JdbcSQLTransientException(java.lang.String message,
        +                                 java.lang.String sql,
        +                                 java.lang.String state,
        +                                 int errorCode,
        +                                 java.lang.Throwable cause,
        +                                 java.lang.String stackTrace)
        +
        Creates a SQLTransientException.
        +
        +
        Parameters:
        +
        message - the reason
        +
        sql - the SQL statement
        +
        state - the SQL state
        +
        errorCode - the error code
        +
        cause - the exception that was the reason for this exception
        +
        stackTrace - the stack trace
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getMessage

        +
        public java.lang.String getMessage()
        +
        +
        Overrides:
        +
        getMessage in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        getOriginalMessage

        +
        public java.lang.String getOriginalMessage()
        +
        Description copied from interface: JdbcException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getOriginalMessage in interface JdbcException
        +
        Returns:
        +
        original message
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintWriter s)
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        printStackTrace

        +
        public void printStackTrace(java.io.PrintStream s)
        +
        +
        Overrides:
        +
        printStackTrace in class java.lang.Throwable
        +
        +
      • +
      + + + +
        +
      • +

        getSQL

        +
        public java.lang.String getSQL()
        +
        Description copied from interface: JdbcException
        +
        Returns the SQL statement. +

        + SQL statements that contain '--hide--' are not listed. +

        +
        +
        Specified by:
        +
        getSQL in interface JdbcException
        +
        Returns:
        +
        the SQL statement
        +
        +
      • +
      + + + +
        +
      • +

        setSQL

        +
        public void setSQL(java.lang.String sql)
        +
        Description copied from interface: JdbcException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        setSQL in interface JdbcException
        +
        Parameters:
        +
        sql - to set
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        Description copied from interface: JdbcException
        +
        Returns the class name, the message, and in the server mode, the stack + trace of the server
        +
        +
        Specified by:
        +
        toString in interface JdbcException
        +
        Overrides:
        +
        toString in class java.lang.Throwable
        +
        Returns:
        +
        the string representation
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcSQLXML.html b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLXML.html new file mode 100644 index 0000000..bedb47a --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcSQLXML.html @@ -0,0 +1,511 @@ + + + + + +JdbcSQLXML + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcSQLXML

+
+
+
    +
  • java.lang.Object
  • +
  • + +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.sql.SQLXML
    +
    +
    +
    +
    public final class JdbcSQLXML
    +extends JdbcLob
    +implements java.sql.SQLXML
    +
    Represents a SQLXML value.
    +
  • +
+
+
+
    +
  • + + + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.message.TraceObject

        +ARRAY, BLOB, CALLABLE_STATEMENT, CLOB, CONNECTION, DATA_SOURCE, DATABASE_META_DATA, PARAMETER_META_DATA, PREPARED_STATEMENT, RESULT_SET, RESULT_SET_META_DATA, SAVEPOINT, SQLXML, STATEMENT, trace, XA_DATA_SOURCE, XID
      • +
      +
    • +
    + + + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      java.io.InputStreamgetBinaryStream() +
      Returns the input stream.
      +
      java.io.ReadergetCharacterStream() +
      Returns the reader.
      +
      <T extends javax.xml.transform.Source>
      T
      getSource(java.lang.Class<T> sourceClass) 
      java.lang.StringgetString() 
      java.io.OutputStreamsetBinaryStream() 
      java.io.WritersetCharacterStream() 
      <T extends javax.xml.transform.Result>
      T
      setResult(java.lang.Class<T> resultClass) 
      voidsetString(java.lang.String value) 
      + +
        +
      • + + +

        Methods inherited from class org.h2.message.TraceObject

        +debugCode, debugCodeAssign, debugCodeCall, debugCodeCall, debugCodeCall, getNextId, getTraceId, getTraceObjectName, isDebugEnabled, isInfoEnabled, logAndConvert, quote, quoteArray, quoteBigDecimal, quoteBytes, quoteDate, quoteIntArray, quoteMap, quoteTime, quoteTimestamp, setTrace, unsupported
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
        +
      • + + +

        Methods inherited from interface java.sql.SQLXML

        +free
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        JdbcSQLXML

        +
        public JdbcSQLXML(JdbcConnection conn,
        +                  org.h2.value.Value value,
        +                  JdbcLob.State state,
        +                  int id)
        +
        INTERNAL
        +
        +
        Parameters:
        +
        conn - to use
        +
        value - for this JdbcSQLXML
        +
        state - of the LOB
        +
        id - of the trace object
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getBinaryStream

        +
        public java.io.InputStream getBinaryStream()
        +                                    throws java.sql.SQLException
        +
        Description copied from class: JdbcLob
        +
        Returns the input stream.
        +
        +
        Specified by:
        +
        getBinaryStream in interface java.sql.SQLXML
        +
        Returns:
        +
        the input stream
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        getCharacterStream

        +
        public java.io.Reader getCharacterStream()
        +                                  throws java.sql.SQLException
        +
        Description copied from class: JdbcLob
        +
        Returns the reader.
        +
        +
        Specified by:
        +
        getCharacterStream in interface java.sql.SQLXML
        +
        Returns:
        +
        the reader
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        getSource

        +
        public <T extends javax.xml.transform.Source> T getSource(java.lang.Class<T> sourceClass)
        +                                                   throws java.sql.SQLException
        +
        +
        Specified by:
        +
        getSource in interface java.sql.SQLXML
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getString

        +
        public java.lang.String getString()
        +                           throws java.sql.SQLException
        +
        +
        Specified by:
        +
        getString in interface java.sql.SQLXML
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        setBinaryStream

        +
        public java.io.OutputStream setBinaryStream()
        +                                     throws java.sql.SQLException
        +
        +
        Specified by:
        +
        setBinaryStream in interface java.sql.SQLXML
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        setCharacterStream

        +
        public java.io.Writer setCharacterStream()
        +                                  throws java.sql.SQLException
        +
        +
        Specified by:
        +
        setCharacterStream in interface java.sql.SQLXML
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        setResult

        +
        public <T extends javax.xml.transform.Result> T setResult(java.lang.Class<T> resultClass)
        +                                                   throws java.sql.SQLException
        +
        +
        Specified by:
        +
        setResult in interface java.sql.SQLXML
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        setString

        +
        public void setString(java.lang.String value)
        +               throws java.sql.SQLException
        +
        +
        Specified by:
        +
        setString in interface java.sql.SQLXML
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcSavepoint.html b/h2/docs/javadoc/org/h2/jdbc/JdbcSavepoint.html new file mode 100644 index 0000000..61a55c5 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcSavepoint.html @@ -0,0 +1,327 @@ + + + + + +JdbcSavepoint + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcSavepoint

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.message.TraceObject
    • +
    • +
        +
      • org.h2.jdbc.JdbcSavepoint
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.sql.Savepoint
    +
    +
    +
    +
    public final class JdbcSavepoint
    +extends org.h2.message.TraceObject
    +implements java.sql.Savepoint
    +
    A savepoint is a point inside a transaction to where a transaction can be + rolled back. The tasks that where done before the savepoint are not rolled + back in this case.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.message.TraceObject

        +ARRAY, BLOB, CALLABLE_STATEMENT, CLOB, CONNECTION, DATA_SOURCE, DATABASE_META_DATA, PARAMETER_META_DATA, PREPARED_STATEMENT, RESULT_SET, RESULT_SET_META_DATA, SAVEPOINT, SQLXML, STATEMENT, trace, XA_DATA_SOURCE, XID
      • +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      intgetSavepointId() +
      Get the generated id of this savepoint.
      +
      java.lang.StringgetSavepointName() +
      Get the name of this savepoint.
      +
      java.lang.StringtoString() +
      INTERNAL
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.message.TraceObject

        +debugCode, debugCodeAssign, debugCodeCall, debugCodeCall, debugCodeCall, getNextId, getTraceId, getTraceObjectName, isDebugEnabled, isInfoEnabled, logAndConvert, quote, quoteArray, quoteBigDecimal, quoteBytes, quoteDate, quoteIntArray, quoteMap, quoteTime, quoteTimestamp, setTrace, unsupported
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getSavepointId

        +
        public int getSavepointId()
        +                   throws java.sql.SQLException
        +
        Get the generated id of this savepoint.
        +
        +
        Specified by:
        +
        getSavepointId in interface java.sql.Savepoint
        +
        Returns:
        +
        the id
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getSavepointName

        +
        public java.lang.String getSavepointName()
        +                                  throws java.sql.SQLException
        +
        Get the name of this savepoint.
        +
        +
        Specified by:
        +
        getSavepointName in interface java.sql.Savepoint
        +
        Returns:
        +
        the name
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        INTERNAL
        +
        +
        Overrides:
        +
        toString in class java.lang.Object
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcStatement.html b/h2/docs/javadoc/org/h2/jdbc/JdbcStatement.html new file mode 100644 index 0000000..b0ecb0e --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcStatement.html @@ -0,0 +1,2054 @@ + + + + + +JdbcStatement + + + + + + + + + + + + +
+
org.h2.jdbc
+

Class JdbcStatement

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.message.TraceObject
    • +
    • +
        +
      • org.h2.jdbc.JdbcStatement
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.lang.AutoCloseable, java.sql.Statement, java.sql.Wrapper, JdbcStatementBackwardsCompat
    +
    +
    +
    Direct Known Subclasses:
    +
    JdbcPreparedStatement
    +
    +
    +
    +
    public class JdbcStatement
    +extends org.h2.message.TraceObject
    +implements java.sql.Statement, JdbcStatementBackwardsCompat
    +
    Represents a statement. +

    + Thread safety: the statement is not thread-safe. If the same statement is + used by multiple threads access to it must be synchronized. The single + synchronized block must include execution of the command and all operations + with its result. +

    +
    + synchronized (stat) {
    +     try (ResultSet rs = stat.executeQuery(queryString)) {
    +         while (rs.next) {
    +             // Do something
    +         }
    +     }
    + }
    + synchronized (stat) {
    +     updateCount = stat.executeUpdate(commandString);
    + }
    + 
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Fields 
      Modifier and TypeField and Description
      protected JdbcConnectionconn 
      protected intfetchSize 
      protected JdbcResultSetgeneratedKeys 
      protected longmaxRows 
      protected JdbcResultSetresultSet 
      protected intresultSetConcurrency 
      protected intresultSetType 
      protected Sessionsession 
      protected longupdateCount 
      +
        +
      • + + +

        Fields inherited from class org.h2.message.TraceObject

        +ARRAY, BLOB, CALLABLE_STATEMENT, CLOB, CONNECTION, DATA_SOURCE, DATABASE_META_DATA, PARAMETER_META_DATA, PREPARED_STATEMENT, RESULT_SET, RESULT_SET_META_DATA, SAVEPOINT, SQLXML, STATEMENT, trace, XA_DATA_SOURCE, XID
      • +
      +
        +
      • + + +

        Fields inherited from interface java.sql.Statement

        +CLOSE_ALL_RESULTS, CLOSE_CURRENT_RESULT, EXECUTE_FAILED, KEEP_CURRENT_RESULT, NO_GENERATED_KEYS, RETURN_GENERATED_KEYS, SUCCESS_NO_INFO
      • +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      voidaddBatch(java.lang.String sql) +
      Adds a statement to the batch.
      +
      voidcancel() +
      Cancels a currently running statement.
      +
      voidclearBatch() +
      Clears the batch.
      +
      voidclearWarnings() +
      Clears all warnings.
      +
      voidclose() +
      Closes this statement.
      +
      protected voidcloseOldResultSet() +
      INTERNAL.
      +
      voidcloseOnCompletion() +
      Specifies that this statement will be closed when its dependent result + set is closed.
      +
      java.lang.StringenquoteIdentifier(java.lang.String identifier, + boolean alwaysQuote) +
      Enquotes the specified identifier.
      +
      booleanexecute(java.lang.String sql) +
      Executes a statement and returns type of its result.
      +
      booleanexecute(java.lang.String sql, + int autoGeneratedKeys) +
      Executes a statement and returns type of its result.
      +
      booleanexecute(java.lang.String sql, + int[] columnIndexes) +
      Executes a statement and returns type of its result.
      +
      booleanexecute(java.lang.String sql, + java.lang.String[] columnNames) +
      Executes a statement and returns type of its result.
      +
      int[]executeBatch() +
      Executes the batch.
      +
      long[]executeLargeBatch() +
      Executes the batch.
      +
      longexecuteLargeUpdate(java.lang.String sql) +
      Executes a statement (insert, update, delete, create, drop) + and returns the update count.
      +
      longexecuteLargeUpdate(java.lang.String sql, + int autoGeneratedKeys) +
      Executes a statement and returns the update count.
      +
      longexecuteLargeUpdate(java.lang.String sql, + int[] columnIndexes) +
      Executes a statement and returns the update count.
      +
      longexecuteLargeUpdate(java.lang.String sql, + java.lang.String[] columnNames) +
      Executes a statement and returns the update count.
      +
      java.sql.ResultSetexecuteQuery(java.lang.String sql) +
      Executes a query (select statement) and returns the result set.
      +
      intexecuteUpdate(java.lang.String sql) +
      Executes a statement (insert, update, delete, create, drop) + and returns the update count.
      +
      intexecuteUpdate(java.lang.String sql, + int autoGeneratedKeys) +
      Executes a statement and returns the update count.
      +
      intexecuteUpdate(java.lang.String sql, + int[] columnIndexes) +
      Executes a statement and returns the update count.
      +
      intexecuteUpdate(java.lang.String sql, + java.lang.String[] columnNames) +
      Executes a statement and returns the update count.
      +
      java.sql.ConnectiongetConnection() +
      Returns the connection that created this object.
      +
      intgetFetchDirection() +
      Gets the fetch direction.
      +
      intgetFetchSize() +
      Gets the number of rows suggested to read in one step.
      +
      java.sql.ResultSetgetGeneratedKeys() +
      Return a result set with generated keys from the latest executed command + or an empty result set if keys were not generated or were not requested + with Statement.RETURN_GENERATED_KEYS, column indexes, or column + names.
      +
      longgetLargeMaxRows() +
      Gets the maximum number of rows for a ResultSet.
      +
      longgetLargeUpdateCount() +
      Returns the last update count of this statement.
      +
      intgetMaxFieldSize() +
      Gets the maximum number of bytes for a result set column.
      +
      intgetMaxRows() +
      Gets the maximum number of rows for a ResultSet.
      +
      booleangetMoreResults() +
      Moves to the next result set - however there is always only one result + set.
      +
      booleangetMoreResults(int current) +
      Move to the next result set.
      +
      intgetQueryTimeout() +
      Gets the current query timeout in seconds.
      +
      java.sql.ResultSetgetResultSet() +
      Returns the last result set produces by this statement.
      +
      intgetResultSetConcurrency() +
      Gets the result set concurrency created by this object.
      +
      intgetResultSetHoldability() +
      Gets the result set holdability.
      +
      intgetResultSetType() +
      Gets the result set type.
      +
      intgetUpdateCount() +
      Returns the last update count of this statement.
      +
      java.sql.SQLWarninggetWarnings() +
      Gets the first warning reported by calls on this object.
      +
      booleanisCancelled() +
      Check whether the statement was cancelled.
      +
      booleanisClosed() +
      Returns whether this statement is closed.
      +
      booleanisCloseOnCompletion() +
      Returns whether this statement will be closed when its dependent result + set is closed.
      +
      booleanisPoolable() +
      Returns whether this object is poolable.
      +
      booleanisSimpleIdentifier(java.lang.String identifier) +
      Checks if specified identifier may be used without quotes.
      +
      booleanisWrapperFor(java.lang.Class<?> iface) +
      Checks if unwrap can return an object of this class.
      +
      voidsetCursorName(java.lang.String name) +
      Sets the name of the cursor.
      +
      voidsetEscapeProcessing(boolean enable) +
      Enables or disables processing or JDBC escape syntax.
      +
      voidsetFetchDirection(int direction) +
      Sets the fetch direction.
      +
      voidsetFetchSize(int rows) +
      Sets the number of rows suggested to read in one step.
      +
      voidsetLargeMaxRows(long maxRows) +
      Gets the maximum number of rows for a ResultSet.
      +
      voidsetMaxFieldSize(int max) +
      Sets the maximum number of bytes for a result set column.
      +
      voidsetMaxRows(int maxRows) +
      Gets the maximum number of rows for a ResultSet.
      +
      voidsetPoolable(boolean poolable) +
      Requests that this object should be pooled or not.
      +
      voidsetQueryTimeout(int seconds) +
      Sets the current query timeout in seconds.
      +
      java.lang.StringtoString() +
      INTERNAL
      +
      <T> Tunwrap(java.lang.Class<T> iface) +
      Return an object of this class if possible.
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.message.TraceObject

        +debugCode, debugCodeAssign, debugCodeCall, debugCodeCall, debugCodeCall, getNextId, getTraceId, getTraceObjectName, isDebugEnabled, isInfoEnabled, logAndConvert, quote, quoteArray, quoteBigDecimal, quoteBytes, quoteDate, quoteIntArray, quoteMap, quoteTime, quoteTimestamp, setTrace, unsupported
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Detail

      + + + + + + + +
        +
      • +

        session

        +
        protected Session session
        +
      • +
      + + + + + + + +
        +
      • +

        maxRows

        +
        protected long maxRows
        +
      • +
      + + + +
        +
      • +

        fetchSize

        +
        protected int fetchSize
        +
      • +
      + + + +
        +
      • +

        updateCount

        +
        protected long updateCount
        +
      • +
      + + + + + + + +
        +
      • +

        resultSetType

        +
        protected final int resultSetType
        +
      • +
      + + + +
        +
      • +

        resultSetConcurrency

        +
        protected final int resultSetConcurrency
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        executeQuery

        +
        public java.sql.ResultSet executeQuery(java.lang.String sql)
        +                                throws java.sql.SQLException
        +
        Executes a query (select statement) and returns the result set. + If another result set exists for this statement, this will be closed + (even if this statement fails).
        +
        +
        Specified by:
        +
        executeQuery in interface java.sql.Statement
        +
        Parameters:
        +
        sql - the SQL statement to execute
        +
        Returns:
        +
        the result set
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        executeUpdate

        +
        public final int executeUpdate(java.lang.String sql)
        +                        throws java.sql.SQLException
        +
        Executes a statement (insert, update, delete, create, drop) + and returns the update count. This method is not + allowed for prepared statements. + If another result set exists for this statement, this will be closed + (even if this statement fails). + + If auto commit is on, this statement will be committed. + If the statement is a DDL statement (create, drop, alter) and does not + throw an exception, the current transaction (if any) is committed after + executing the statement.
        +
        +
        Specified by:
        +
        executeUpdate in interface java.sql.Statement
        +
        Parameters:
        +
        sql - the SQL statement
        +
        Returns:
        +
        the update count (number of affected rows by a DML statement or + other statement able to return number of rows, or 0 if no rows + were affected or the statement returned nothing, or + Statement.SUCCESS_NO_INFO if number of rows is too large for the + int data type)
        +
        Throws:
        +
        java.sql.SQLException - if a database error occurred or a + select statement was executed
        +
        See Also:
        +
        executeLargeUpdate(String)
        +
        +
      • +
      + + + +
        +
      • +

        executeLargeUpdate

        +
        public final long executeLargeUpdate(java.lang.String sql)
        +                              throws java.sql.SQLException
        +
        Executes a statement (insert, update, delete, create, drop) + and returns the update count. This method is not + allowed for prepared statements. + If another result set exists for this statement, this will be closed + (even if this statement fails). + + If auto commit is on, this statement will be committed. + If the statement is a DDL statement (create, drop, alter) and does not + throw an exception, the current transaction (if any) is committed after + executing the statement.
        +
        +
        Specified by:
        +
        executeLargeUpdate in interface java.sql.Statement
        +
        Parameters:
        +
        sql - the SQL statement
        +
        Returns:
        +
        the update count (number of affected rows by a DML statement or + other statement able to return number of rows, or 0 if no rows + were affected or the statement returned nothing)
        +
        Throws:
        +
        java.sql.SQLException - if a database error occurred or a + select statement was executed
        +
        +
      • +
      + + + +
        +
      • +

        execute

        +
        public final boolean execute(java.lang.String sql)
        +                      throws java.sql.SQLException
        +
        Executes a statement and returns type of its result. This method is not + allowed for prepared statements. + If another result set exists for this + statement, this will be closed (even if this statement fails). + + If the statement is a create or drop and does not throw an exception, the + current transaction (if any) is committed after executing the statement. + If auto commit is on, and the statement is not a select, this statement + will be committed.
        +
        +
        Specified by:
        +
        execute in interface java.sql.Statement
        +
        Parameters:
        +
        sql - the SQL statement to execute
        +
        Returns:
        +
        true if result is a result set, false otherwise
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getResultSet

        +
        public java.sql.ResultSet getResultSet()
        +                                throws java.sql.SQLException
        +
        Returns the last result set produces by this statement.
        +
        +
        Specified by:
        +
        getResultSet in interface java.sql.Statement
        +
        Returns:
        +
        the result set
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getUpdateCount

        +
        public final int getUpdateCount()
        +                         throws java.sql.SQLException
        +
        Returns the last update count of this statement.
        +
        +
        Specified by:
        +
        getUpdateCount in interface java.sql.Statement
        +
        Returns:
        +
        the update count (number of affected rows by a DML statement or + other statement able to return number of rows, or 0 if no rows + were affected or the statement returned nothing, or -1 if + statement was a query, or Statement.SUCCESS_NO_INFO if number of + rows is too large for the int data type)
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed or invalid
        +
        See Also:
        +
        getLargeUpdateCount()
        +
        +
      • +
      + + + +
        +
      • +

        getLargeUpdateCount

        +
        public final long getLargeUpdateCount()
        +                               throws java.sql.SQLException
        +
        Returns the last update count of this statement.
        +
        +
        Specified by:
        +
        getLargeUpdateCount in interface java.sql.Statement
        +
        Returns:
        +
        the update count (number of affected rows by a DML statement or + other statement able to return number of rows, or 0 if no rows + were affected or the statement returned nothing, or -1 if + statement was a query)
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed or invalid
        +
        +
      • +
      + + + +
        +
      • +

        close

        +
        public void close()
        +           throws java.sql.SQLException
        +
        Closes this statement. + All result sets that where created by this statement + become invalid after calling this method.
        +
        +
        Specified by:
        +
        close in interface java.lang.AutoCloseable
        +
        Specified by:
        +
        close in interface java.sql.Statement
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getConnection

        +
        public java.sql.Connection getConnection()
        +
        Returns the connection that created this object.
        +
        +
        Specified by:
        +
        getConnection in interface java.sql.Statement
        +
        Returns:
        +
        the connection
        +
        +
      • +
      + + + +
        +
      • +

        getWarnings

        +
        public java.sql.SQLWarning getWarnings()
        +                                throws java.sql.SQLException
        +
        Gets the first warning reported by calls on this object. + This driver does not support warnings, and will always return null.
        +
        +
        Specified by:
        +
        getWarnings in interface java.sql.Statement
        +
        Returns:
        +
        null
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        clearWarnings

        +
        public void clearWarnings()
        +                   throws java.sql.SQLException
        +
        Clears all warnings. As this driver does not support warnings, + this call is ignored.
        +
        +
        Specified by:
        +
        clearWarnings in interface java.sql.Statement
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        setCursorName

        +
        public void setCursorName(java.lang.String name)
        +                   throws java.sql.SQLException
        +
        Sets the name of the cursor. This call is ignored.
        +
        +
        Specified by:
        +
        setCursorName in interface java.sql.Statement
        +
        Parameters:
        +
        name - ignored
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setFetchDirection

        +
        public void setFetchDirection(int direction)
        +                       throws java.sql.SQLException
        +
        Sets the fetch direction. + This call is ignored by this driver.
        +
        +
        Specified by:
        +
        setFetchDirection in interface java.sql.Statement
        +
        Parameters:
        +
        direction - ignored
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        getFetchDirection

        +
        public int getFetchDirection()
        +                      throws java.sql.SQLException
        +
        Gets the fetch direction.
        +
        +
        Specified by:
        +
        getFetchDirection in interface java.sql.Statement
        +
        Returns:
        +
        FETCH_FORWARD
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        getMaxRows

        +
        public int getMaxRows()
        +               throws java.sql.SQLException
        +
        Gets the maximum number of rows for a ResultSet.
        +
        +
        Specified by:
        +
        getMaxRows in interface java.sql.Statement
        +
        Returns:
        +
        the number of rows where 0 means no limit
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        getLargeMaxRows

        +
        public long getLargeMaxRows()
        +                     throws java.sql.SQLException
        +
        Gets the maximum number of rows for a ResultSet.
        +
        +
        Specified by:
        +
        getLargeMaxRows in interface java.sql.Statement
        +
        Returns:
        +
        the number of rows where 0 means no limit
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setMaxRows

        +
        public void setMaxRows(int maxRows)
        +                throws java.sql.SQLException
        +
        Gets the maximum number of rows for a ResultSet.
        +
        +
        Specified by:
        +
        setMaxRows in interface java.sql.Statement
        +
        Parameters:
        +
        maxRows - the number of rows where 0 means no limit
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setLargeMaxRows

        +
        public void setLargeMaxRows(long maxRows)
        +                     throws java.sql.SQLException
        +
        Gets the maximum number of rows for a ResultSet.
        +
        +
        Specified by:
        +
        setLargeMaxRows in interface java.sql.Statement
        +
        Parameters:
        +
        maxRows - the number of rows where 0 means no limit
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setFetchSize

        +
        public void setFetchSize(int rows)
        +                  throws java.sql.SQLException
        +
        Sets the number of rows suggested to read in one step. + This value cannot be higher than the maximum rows (setMaxRows) + set by the statement or prepared statement, otherwise an exception + is throws. Setting the value to 0 will set the default value. + The default value can be changed using the system property + h2.serverResultSetFetchSize.
        +
        +
        Specified by:
        +
        setFetchSize in interface java.sql.Statement
        +
        Parameters:
        +
        rows - the number of rows
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        getFetchSize

        +
        public int getFetchSize()
        +                 throws java.sql.SQLException
        +
        Gets the number of rows suggested to read in one step.
        +
        +
        Specified by:
        +
        getFetchSize in interface java.sql.Statement
        +
        Returns:
        +
        the current fetch size
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        getResultSetConcurrency

        +
        public int getResultSetConcurrency()
        +                            throws java.sql.SQLException
        +
        Gets the result set concurrency created by this object.
        +
        +
        Specified by:
        +
        getResultSetConcurrency in interface java.sql.Statement
        +
        Returns:
        +
        the concurrency
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getResultSetType

        +
        public int getResultSetType()
        +                     throws java.sql.SQLException
        +
        Gets the result set type.
        +
        +
        Specified by:
        +
        getResultSetType in interface java.sql.Statement
        +
        Returns:
        +
        the type
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        getMaxFieldSize

        +
        public int getMaxFieldSize()
        +                    throws java.sql.SQLException
        +
        Gets the maximum number of bytes for a result set column.
        +
        +
        Specified by:
        +
        getMaxFieldSize in interface java.sql.Statement
        +
        Returns:
        +
        always 0 for no limit
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setMaxFieldSize

        +
        public void setMaxFieldSize(int max)
        +                     throws java.sql.SQLException
        +
        Sets the maximum number of bytes for a result set column. + This method does currently do nothing for this driver.
        +
        +
        Specified by:
        +
        setMaxFieldSize in interface java.sql.Statement
        +
        Parameters:
        +
        max - the maximum size - ignored
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setEscapeProcessing

        +
        public void setEscapeProcessing(boolean enable)
        +                         throws java.sql.SQLException
        +
        Enables or disables processing or JDBC escape syntax. + See also Connection.nativeSQL.
        +
        +
        Specified by:
        +
        setEscapeProcessing in interface java.sql.Statement
        +
        Parameters:
        +
        enable - - true (default) or false (no conversion is attempted)
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        cancel

        +
        public void cancel()
        +            throws java.sql.SQLException
        +
        Cancels a currently running statement. + This method must be called from within another + thread than the execute method. + Operations on large objects are not interrupted, + only operations that process many rows.
        +
        +
        Specified by:
        +
        cancel in interface java.sql.Statement
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        isCancelled

        +
        public boolean isCancelled()
        +
        Check whether the statement was cancelled.
        +
        +
        Returns:
        +
        true if yes
        +
        +
      • +
      + + + +
        +
      • +

        getQueryTimeout

        +
        public int getQueryTimeout()
        +                    throws java.sql.SQLException
        +
        Gets the current query timeout in seconds. + This method will return 0 if no query timeout is set. + The result is rounded to the next second. + For performance reasons, only the first call to this method + will query the database. If the query timeout was changed in another + way than calling setQueryTimeout, this method will always return + the last value.
        +
        +
        Specified by:
        +
        getQueryTimeout in interface java.sql.Statement
        +
        Returns:
        +
        the timeout in seconds
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        setQueryTimeout

        +
        public void setQueryTimeout(int seconds)
        +                     throws java.sql.SQLException
        +
        Sets the current query timeout in seconds. + Changing the value will affect all statements of this connection. + This method does not commit a transaction, + and rolling back a transaction does not affect this setting.
        +
        +
        Specified by:
        +
        setQueryTimeout in interface java.sql.Statement
        +
        Parameters:
        +
        seconds - the timeout in seconds - 0 means no timeout, values + smaller 0 will throw an exception
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        addBatch

        +
        public void addBatch(java.lang.String sql)
        +              throws java.sql.SQLException
        +
        Adds a statement to the batch.
        +
        +
        Specified by:
        +
        addBatch in interface java.sql.Statement
        +
        Parameters:
        +
        sql - the SQL statement
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        clearBatch

        +
        public void clearBatch()
        +                throws java.sql.SQLException
        +
        Clears the batch.
        +
        +
        Specified by:
        +
        clearBatch in interface java.sql.Statement
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        executeBatch

        +
        public int[] executeBatch()
        +                   throws java.sql.SQLException
        +
        Executes the batch. + If one of the batched statements fails, this database will continue.
        +
        +
        Specified by:
        +
        executeBatch in interface java.sql.Statement
        +
        Returns:
        +
        the array of update counts
        +
        Throws:
        +
        java.sql.SQLException
        +
        See Also:
        +
        executeLargeBatch()
        +
        +
      • +
      + + + +
        +
      • +

        executeLargeBatch

        +
        public long[] executeLargeBatch()
        +                         throws java.sql.SQLException
        +
        Executes the batch. + If one of the batched statements fails, this database will continue.
        +
        +
        Specified by:
        +
        executeLargeBatch in interface java.sql.Statement
        +
        Returns:
        +
        the array of update counts
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getGeneratedKeys

        +
        public java.sql.ResultSet getGeneratedKeys()
        +                                    throws java.sql.SQLException
        +
        Return a result set with generated keys from the latest executed command + or an empty result set if keys were not generated or were not requested + with Statement.RETURN_GENERATED_KEYS, column indexes, or column + names. +

        + Generated keys are only returned from from INSERT, + UPDATE, MERGE INTO, and MERGE INTO ... USING + commands. +

        +

        + If SQL command inserts or updates multiple rows with generated keys each + such inserted or updated row is returned. Batch methods are also + supported. +

        +

        + When Statement.RETURN_GENERATED_KEYS is used H2 chooses columns + to return automatically. The following columns are chosen: +

        +
          +
        • Columns with sequences including IDENTITY columns and columns + with AUTO_INCREMENT.
        • +
        • Columns with other default values that are not evaluated into + constant expressions (like DEFAULT RANDOM_UUID()).
        • +
        • Columns that are included into the PRIMARY KEY constraint.
        • +
        +

        + Exact required columns for the returning result set may be specified on + execution of command with names or indexes of columns. +

        +
        +
        Specified by:
        +
        getGeneratedKeys in interface java.sql.Statement
        +
        Returns:
        +
        the possibly empty result set with generated keys
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed
        +
        +
      • +
      + + + +
        +
      • +

        getMoreResults

        +
        public boolean getMoreResults()
        +                       throws java.sql.SQLException
        +
        Moves to the next result set - however there is always only one result + set. This call also closes the current result set (if there is one). + Returns true if there is a next result set (that means - it always + returns false).
        +
        +
        Specified by:
        +
        getMoreResults in interface java.sql.Statement
        +
        Returns:
        +
        false
        +
        Throws:
        +
        java.sql.SQLException - if this object is closed.
        +
        +
      • +
      + + + +
        +
      • +

        getMoreResults

        +
        public boolean getMoreResults(int current)
        +                       throws java.sql.SQLException
        +
        Move to the next result set. + This method always returns false.
        +
        +
        Specified by:
        +
        getMoreResults in interface java.sql.Statement
        +
        Parameters:
        +
        current - Statement.CLOSE_CURRENT_RESULT, + Statement.KEEP_CURRENT_RESULT, + or Statement.CLOSE_ALL_RESULTS
        +
        Returns:
        +
        false
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        executeUpdate

        +
        public final int executeUpdate(java.lang.String sql,
        +                               int autoGeneratedKeys)
        +                        throws java.sql.SQLException
        +
        Executes a statement and returns the update count. This method is not + allowed for prepared statements.
        +
        +
        Specified by:
        +
        executeUpdate in interface java.sql.Statement
        +
        Parameters:
        +
        sql - the SQL statement
        +
        autoGeneratedKeys - Statement.RETURN_GENERATED_KEYS if generated keys should + be available for retrieval, Statement.NO_GENERATED_KEYS if + generated keys should not be available
        +
        Returns:
        +
        the update count (number of affected rows by a DML statement or + other statement able to return number of rows, or 0 if no rows + were affected or the statement returned nothing, or + Statement.SUCCESS_NO_INFO if number of rows is too large for the + int data type)
        +
        Throws:
        +
        java.sql.SQLException - if a database error occurred or a + select statement was executed
        +
        See Also:
        +
        executeLargeUpdate(String, int)
        +
        +
      • +
      + + + +
        +
      • +

        executeLargeUpdate

        +
        public final long executeLargeUpdate(java.lang.String sql,
        +                                     int autoGeneratedKeys)
        +                              throws java.sql.SQLException
        +
        Executes a statement and returns the update count. This method is not + allowed for prepared statements.
        +
        +
        Specified by:
        +
        executeLargeUpdate in interface java.sql.Statement
        +
        Parameters:
        +
        sql - the SQL statement
        +
        autoGeneratedKeys - Statement.RETURN_GENERATED_KEYS if generated keys should + be available for retrieval, Statement.NO_GENERATED_KEYS if + generated keys should not be available
        +
        Returns:
        +
        the update count (number of affected rows by a DML statement or + other statement able to return number of rows, or 0 if no rows + were affected or the statement returned nothing)
        +
        Throws:
        +
        java.sql.SQLException - if a database error occurred or a + select statement was executed
        +
        +
      • +
      + + + +
        +
      • +

        executeUpdate

        +
        public final int executeUpdate(java.lang.String sql,
        +                               int[] columnIndexes)
        +                        throws java.sql.SQLException
        +
        Executes a statement and returns the update count. This method is not + allowed for prepared statements.
        +
        +
        Specified by:
        +
        executeUpdate in interface java.sql.Statement
        +
        Parameters:
        +
        sql - the SQL statement
        +
        columnIndexes - an array of column indexes indicating the columns with generated + keys that should be returned from the inserted row
        +
        Returns:
        +
        the update count (number of affected rows by a DML statement or + other statement able to return number of rows, or 0 if no rows + were affected or the statement returned nothing, or + Statement.SUCCESS_NO_INFO if number of rows is too large for the + int data type)
        +
        Throws:
        +
        java.sql.SQLException - if a database error occurred or a + select statement was executed
        +
        See Also:
        +
        executeLargeUpdate(String, int[])
        +
        +
      • +
      + + + +
        +
      • +

        executeLargeUpdate

        +
        public final long executeLargeUpdate(java.lang.String sql,
        +                                     int[] columnIndexes)
        +                              throws java.sql.SQLException
        +
        Executes a statement and returns the update count. This method is not + allowed for prepared statements.
        +
        +
        Specified by:
        +
        executeLargeUpdate in interface java.sql.Statement
        +
        Parameters:
        +
        sql - the SQL statement
        +
        columnIndexes - an array of column indexes indicating the columns with generated + keys that should be returned from the inserted row
        +
        Returns:
        +
        the update count (number of affected rows by a DML statement or + other statement able to return number of rows, or 0 if no rows + were affected or the statement returned nothing)
        +
        Throws:
        +
        java.sql.SQLException - if a database error occurred or a + select statement was executed
        +
        +
      • +
      + + + +
        +
      • +

        executeUpdate

        +
        public final int executeUpdate(java.lang.String sql,
        +                               java.lang.String[] columnNames)
        +                        throws java.sql.SQLException
        +
        Executes a statement and returns the update count. This method is not + allowed for prepared statements.
        +
        +
        Specified by:
        +
        executeUpdate in interface java.sql.Statement
        +
        Parameters:
        +
        sql - the SQL statement
        +
        columnNames - an array of column names indicating the columns with generated + keys that should be returned from the inserted row
        +
        Returns:
        +
        the update count (number of affected rows by a DML statement or + other statement able to return number of rows, or 0 if no rows + were affected or the statement returned nothing, or + Statement.SUCCESS_NO_INFO if number of rows is too large for the + int data type)
        +
        Throws:
        +
        java.sql.SQLException - if a database error occurred or a + select statement was executed
        +
        See Also:
        +
        executeLargeUpdate(String, String[])
        +
        +
      • +
      + + + +
        +
      • +

        executeLargeUpdate

        +
        public final long executeLargeUpdate(java.lang.String sql,
        +                                     java.lang.String[] columnNames)
        +                              throws java.sql.SQLException
        +
        Executes a statement and returns the update count. This method is not + allowed for prepared statements.
        +
        +
        Specified by:
        +
        executeLargeUpdate in interface java.sql.Statement
        +
        Parameters:
        +
        sql - the SQL statement
        +
        columnNames - an array of column names indicating the columns with generated + keys that should be returned from the inserted row
        +
        Returns:
        +
        the update count (number of row affected by an insert, + update or delete, or 0 if no rows or the statement was a + create, drop, commit or rollback)
        +
        Throws:
        +
        java.sql.SQLException - if a database error occurred or a + select statement was executed
        +
        +
      • +
      + + + +
        +
      • +

        execute

        +
        public final boolean execute(java.lang.String sql,
        +                             int autoGeneratedKeys)
        +                      throws java.sql.SQLException
        +
        Executes a statement and returns type of its result. This method is not + allowed for prepared statements.
        +
        +
        Specified by:
        +
        execute in interface java.sql.Statement
        +
        Parameters:
        +
        sql - the SQL statement
        +
        autoGeneratedKeys - Statement.RETURN_GENERATED_KEYS if generated keys should + be available for retrieval, Statement.NO_GENERATED_KEYS if + generated keys should not be available
        +
        Returns:
        +
        true if result is a result set, false otherwise
        +
        Throws:
        +
        java.sql.SQLException - if a database error occurred or a + select statement was executed
        +
        +
      • +
      + + + +
        +
      • +

        execute

        +
        public final boolean execute(java.lang.String sql,
        +                             int[] columnIndexes)
        +                      throws java.sql.SQLException
        +
        Executes a statement and returns type of its result. This method is not + allowed for prepared statements.
        +
        +
        Specified by:
        +
        execute in interface java.sql.Statement
        +
        Parameters:
        +
        sql - the SQL statement
        +
        columnIndexes - an array of column indexes indicating the columns with generated + keys that should be returned from the inserted row
        +
        Returns:
        +
        true if result is a result set, false otherwise
        +
        Throws:
        +
        java.sql.SQLException - if a database error occurred or a + select statement was executed
        +
        +
      • +
      + + + +
        +
      • +

        execute

        +
        public final boolean execute(java.lang.String sql,
        +                             java.lang.String[] columnNames)
        +                      throws java.sql.SQLException
        +
        Executes a statement and returns type of its result. This method is not + allowed for prepared statements.
        +
        +
        Specified by:
        +
        execute in interface java.sql.Statement
        +
        Parameters:
        +
        sql - the SQL statement
        +
        columnNames - an array of column names indicating the columns with generated + keys that should be returned from the inserted row
        +
        Returns:
        +
        true if result is a result set, false otherwise
        +
        Throws:
        +
        java.sql.SQLException - if a database error occurred or a + select statement was executed
        +
        +
      • +
      + + + +
        +
      • +

        getResultSetHoldability

        +
        public int getResultSetHoldability()
        +                            throws java.sql.SQLException
        +
        Gets the result set holdability.
        +
        +
        Specified by:
        +
        getResultSetHoldability in interface java.sql.Statement
        +
        Returns:
        +
        the holdability
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        closeOnCompletion

        +
        public void closeOnCompletion()
        +                       throws java.sql.SQLException
        +
        Specifies that this statement will be closed when its dependent result + set is closed.
        +
        +
        Specified by:
        +
        closeOnCompletion in interface java.sql.Statement
        +
        Throws:
        +
        java.sql.SQLException - if this statement is closed
        +
        +
      • +
      + + + +
        +
      • +

        isCloseOnCompletion

        +
        public boolean isCloseOnCompletion()
        +                            throws java.sql.SQLException
        +
        Returns whether this statement will be closed when its dependent result + set is closed.
        +
        +
        Specified by:
        +
        isCloseOnCompletion in interface java.sql.Statement
        +
        Returns:
        +
        true if this statement will be closed when its dependent + result set is closed
        +
        Throws:
        +
        java.sql.SQLException - if this statement is closed
        +
        +
      • +
      + + + +
        +
      • +

        closeOldResultSet

        +
        protected void closeOldResultSet()
        +
        INTERNAL. + Close and old result set if there is still one open.
        +
      • +
      + + + +
        +
      • +

        isClosed

        +
        public boolean isClosed()
        +                 throws java.sql.SQLException
        +
        Returns whether this statement is closed.
        +
        +
        Specified by:
        +
        isClosed in interface java.sql.Statement
        +
        Returns:
        +
        true if the statement is closed
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        unwrap

        +
        public <T> T unwrap(java.lang.Class<T> iface)
        +             throws java.sql.SQLException
        +
        Return an object of this class if possible.
        +
        +
        Specified by:
        +
        unwrap in interface java.sql.Wrapper
        +
        Parameters:
        +
        iface - the class
        +
        Returns:
        +
        this
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        isWrapperFor

        +
        public boolean isWrapperFor(java.lang.Class<?> iface)
        +                     throws java.sql.SQLException
        +
        Checks if unwrap can return an object of this class.
        +
        +
        Specified by:
        +
        isWrapperFor in interface java.sql.Wrapper
        +
        Parameters:
        +
        iface - the class
        +
        Returns:
        +
        whether or not the interface is assignable from this class
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        isPoolable

        +
        public boolean isPoolable()
        +
        Returns whether this object is poolable.
        +
        +
        Specified by:
        +
        isPoolable in interface java.sql.Statement
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        setPoolable

        +
        public void setPoolable(boolean poolable)
        +
        Requests that this object should be pooled or not. + This call is ignored.
        +
        +
        Specified by:
        +
        setPoolable in interface java.sql.Statement
        +
        Parameters:
        +
        poolable - the requested value
        +
        +
      • +
      + + + +
        +
      • +

        enquoteIdentifier

        +
        public java.lang.String enquoteIdentifier(java.lang.String identifier,
        +                                          boolean alwaysQuote)
        +                                   throws java.sql.SQLException
        +
        Description copied from interface: JdbcStatementBackwardsCompat
        +
        Enquotes the specified identifier.
        +
        +
        Specified by:
        +
        enquoteIdentifier in interface JdbcStatementBackwardsCompat
        +
        Parameters:
        +
        identifier - identifier to quote if required, may be quoted or unquoted
        +
        alwaysQuote - if true identifier will be quoted unconditionally
        +
        Returns:
        +
        specified identifier quoted if required, explicitly requested, or + if it was already quoted
        +
        Throws:
        +
        java.lang.NullPointerException - if identifier is null
        +
        java.sql.SQLException - if identifier is not a valid identifier
        +
        +
      • +
      + + + +
        +
      • +

        isSimpleIdentifier

        +
        public boolean isSimpleIdentifier(java.lang.String identifier)
        +                           throws java.sql.SQLException
        +
        Description copied from interface: JdbcStatementBackwardsCompat
        +
        Checks if specified identifier may be used without quotes.
        +
        +
        Specified by:
        +
        isSimpleIdentifier in interface JdbcStatementBackwardsCompat
        +
        Parameters:
        +
        identifier - identifier to check
        +
        Returns:
        +
        is specified identifier may be used without quotes
        +
        Throws:
        +
        java.lang.NullPointerException - if identifier is null
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        INTERNAL
        +
        +
        Overrides:
        +
        toString in class java.lang.Object
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/JdbcStatementBackwardsCompat.html b/h2/docs/javadoc/org/h2/jdbc/JdbcStatementBackwardsCompat.html new file mode 100644 index 0000000..c7b1d0f --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/JdbcStatementBackwardsCompat.html @@ -0,0 +1,265 @@ + + + + + +JdbcStatementBackwardsCompat + + + + + + + + + + + + +
+
org.h2.jdbc
+

Interface JdbcStatementBackwardsCompat

+
+
+
+ +
+
+
    +
  • + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + +
      All Methods Instance Methods Abstract Methods 
      Modifier and TypeMethod and Description
      java.lang.StringenquoteIdentifier(java.lang.String identifier, + boolean alwaysQuote) +
      Enquotes the specified identifier.
      +
      booleanisSimpleIdentifier(java.lang.String identifier) +
      Checks if specified identifier may be used without quotes.
      +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        enquoteIdentifier

        +
        java.lang.String enquoteIdentifier(java.lang.String identifier,
        +                                   boolean alwaysQuote)
        +                            throws java.sql.SQLException
        +
        Enquotes the specified identifier.
        +
        +
        Parameters:
        +
        identifier - identifier to quote if required
        +
        alwaysQuote - if true identifier will be quoted unconditionally
        +
        Returns:
        +
        specified identifier quoted if required or explicitly requested
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        isSimpleIdentifier

        +
        boolean isSimpleIdentifier(java.lang.String identifier)
        +                    throws java.sql.SQLException
        +
        Checks if specified identifier may be used without quotes.
        +
        +
        Parameters:
        +
        identifier - identifier to check
        +
        Returns:
        +
        is specified identifier may be used without quotes
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/package-frame.html b/h2/docs/javadoc/org/h2/jdbc/package-frame.html new file mode 100644 index 0000000..214d35d --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/package-frame.html @@ -0,0 +1,59 @@ + + + + + +org.h2.jdbc + + + + + +

org.h2.jdbc

+ + + diff --git a/h2/docs/javadoc/org/h2/jdbc/package-summary.html b/h2/docs/javadoc/org/h2/jdbc/package-summary.html new file mode 100644 index 0000000..826af4a --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/package-summary.html @@ -0,0 +1,374 @@ + + + + + +org.h2.jdbc + + + + + + + + + + + +
+

Package org.h2.jdbc

+
+
+ +Implementation of the JDBC API (package java.sql).
+
+

See: Description

+
+
+ + + + +

Package org.h2.jdbc Description

+

+ +Implementation of the JDBC API (package java.sql). + +

+
+ + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbc/package-tree.html b/h2/docs/javadoc/org/h2/jdbc/package-tree.html new file mode 100644 index 0000000..40febcf --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbc/package-tree.html @@ -0,0 +1,248 @@ + + + + + +org.h2.jdbc Class Hierarchy + + + + + + + + + + + +
+

Hierarchy For Package org.h2.jdbc

+Package Hierarchies: + +
+
+

Class Hierarchy

+ +

Interface Hierarchy

+ +

Enum Hierarchy

+
    +
  • java.lang.Object +
      +
    • java.lang.Enum<E> (implements java.lang.Comparable<T>, java.io.Serializable) + +
    • +
    +
  • +
+
+ + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbcx/JdbcConnectionPool.html b/h2/docs/javadoc/org/h2/jdbcx/JdbcConnectionPool.html new file mode 100644 index 0000000..00cad41 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbcx/JdbcConnectionPool.html @@ -0,0 +1,633 @@ + + + + + +JdbcConnectionPool + + + + + + + + + + + + +
+
org.h2.jdbcx
+

Class JdbcConnectionPool

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.jdbcx.JdbcConnectionPool
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.sql.Wrapper, java.util.EventListener, javax.sql.CommonDataSource, javax.sql.ConnectionEventListener, javax.sql.DataSource, JdbcConnectionPoolBackwardsCompat
    +
    +
    +
    +
    public final class JdbcConnectionPool
    +extends java.lang.Object
    +implements javax.sql.DataSource, javax.sql.ConnectionEventListener, JdbcConnectionPoolBackwardsCompat
    +
    A simple standalone JDBC connection pool. + It is based on the + + MiniConnectionPoolManager written by Christian d'Heureuse (Java 1.5) + . It is used as follows: +
    + import java.sql.*;
    + import org.h2.jdbcx.JdbcConnectionPool;
    + public class Test {
    +     public static void main(String... args) throws Exception {
    +         JdbcConnectionPool cp = JdbcConnectionPool.create(
    +             "jdbc:h2:~/test", "sa", "sa");
    +         for (String sql : args) {
    +             Connection conn = cp.getConnection();
    +             conn.createStatement().execute(sql);
    +             conn.close();
    +         }
    +         cp.dispose();
    +     }
    + }
    + 
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      voidconnectionClosed(javax.sql.ConnectionEvent event) +
      INTERNAL
      +
      voidconnectionErrorOccurred(javax.sql.ConnectionEvent event) +
      INTERNAL
      +
      static JdbcConnectionPoolcreate(javax.sql.ConnectionPoolDataSource dataSource) +
      Constructs a new connection pool.
      +
      static JdbcConnectionPoolcreate(java.lang.String url, + java.lang.String user, + java.lang.String password) +
      Constructs a new connection pool for H2 databases.
      +
      voiddispose() +
      Closes all unused pooled connections.
      +
      intgetActiveConnections() +
      Returns the number of active (open) connections of this pool.
      +
      java.sql.ConnectiongetConnection() +
      Retrieves a connection from the connection pool.
      +
      java.sql.ConnectiongetConnection(java.lang.String user, + java.lang.String password) +
      INTERNAL
      +
      intgetLoginTimeout() +
      Gets the maximum time in seconds to wait for a free connection.
      +
      java.io.PrintWritergetLogWriter() +
      INTERNAL
      +
      intgetMaxConnections() +
      Gets the maximum number of connections to use.
      +
      java.util.logging.LoggergetParentLogger() +
      [Not supported]
      +
      booleanisWrapperFor(java.lang.Class<?> iface) +
      Checks if unwrap can return an object of this class.
      +
      voidsetLoginTimeout(int seconds) +
      Sets the maximum time in seconds to wait for a free connection.
      +
      voidsetLogWriter(java.io.PrintWriter logWriter) +
      INTERNAL
      +
      voidsetMaxConnections(int max) +
      Sets the maximum number of connections to use from now on.
      +
      <T> Tunwrap(java.lang.Class<T> iface) +
      Return an object of this class if possible.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        create

        +
        public static JdbcConnectionPool create(javax.sql.ConnectionPoolDataSource dataSource)
        +
        Constructs a new connection pool.
        +
        +
        Parameters:
        +
        dataSource - the data source to create connections
        +
        Returns:
        +
        the connection pool
        +
        +
      • +
      + + + +
        +
      • +

        create

        +
        public static JdbcConnectionPool create(java.lang.String url,
        +                                        java.lang.String user,
        +                                        java.lang.String password)
        +
        Constructs a new connection pool for H2 databases.
        +
        +
        Parameters:
        +
        url - the database URL of the H2 connection
        +
        user - the user name
        +
        password - the password
        +
        Returns:
        +
        the connection pool
        +
        +
      • +
      + + + +
        +
      • +

        setMaxConnections

        +
        public void setMaxConnections(int max)
        +
        Sets the maximum number of connections to use from now on. + The default value is 10 connections.
        +
        +
        Parameters:
        +
        max - the maximum number of connections
        +
        +
      • +
      + + + +
        +
      • +

        getMaxConnections

        +
        public int getMaxConnections()
        +
        Gets the maximum number of connections to use.
        +
        +
        Returns:
        +
        the max the maximum number of connections
        +
        +
      • +
      + + + +
        +
      • +

        getLoginTimeout

        +
        public int getLoginTimeout()
        +
        Gets the maximum time in seconds to wait for a free connection.
        +
        +
        Specified by:
        +
        getLoginTimeout in interface javax.sql.CommonDataSource
        +
        Returns:
        +
        the timeout in seconds
        +
        +
      • +
      + + + +
        +
      • +

        setLoginTimeout

        +
        public void setLoginTimeout(int seconds)
        +
        Sets the maximum time in seconds to wait for a free connection. + The default timeout is 30 seconds. Calling this method with the + value 0 will set the timeout to the default value.
        +
        +
        Specified by:
        +
        setLoginTimeout in interface javax.sql.CommonDataSource
        +
        Parameters:
        +
        seconds - the timeout, 0 meaning the default
        +
        +
      • +
      + + + +
        +
      • +

        dispose

        +
        public void dispose()
        +
        Closes all unused pooled connections. + Exceptions while closing are written to the log stream (if set).
        +
      • +
      + + + +
        +
      • +

        getConnection

        +
        public java.sql.Connection getConnection()
        +                                  throws java.sql.SQLException
        +
        Retrieves a connection from the connection pool. If + maxConnections connections are already in use, the method + waits until a connection becomes available or timeout + seconds elapsed. When the application is finished using the connection, + it must close it in order to return it to the pool. + If no connection becomes available within the given timeout, an exception + with SQL state 08001 and vendor code 8001 is thrown.
        +
        +
        Specified by:
        +
        getConnection in interface javax.sql.DataSource
        +
        Returns:
        +
        a new Connection object.
        +
        Throws:
        +
        java.sql.SQLException - when a new connection could not be established, + or a timeout occurred
        +
        +
      • +
      + + + +
        +
      • +

        getConnection

        +
        public java.sql.Connection getConnection(java.lang.String user,
        +                                         java.lang.String password)
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getConnection in interface javax.sql.DataSource
        +
        +
      • +
      + + + +
        +
      • +

        connectionClosed

        +
        public void connectionClosed(javax.sql.ConnectionEvent event)
        +
        INTERNAL
        +
        +
        Specified by:
        +
        connectionClosed in interface javax.sql.ConnectionEventListener
        +
        +
      • +
      + + + +
        +
      • +

        connectionErrorOccurred

        +
        public void connectionErrorOccurred(javax.sql.ConnectionEvent event)
        +
        INTERNAL
        +
        +
        Specified by:
        +
        connectionErrorOccurred in interface javax.sql.ConnectionEventListener
        +
        +
      • +
      + + + +
        +
      • +

        getActiveConnections

        +
        public int getActiveConnections()
        +
        Returns the number of active (open) connections of this pool. This is the + number of Connection objects that have been issued by + getConnection() for which Connection.close() has + not yet been called.
        +
        +
        Returns:
        +
        the number of active connections.
        +
        +
      • +
      + + + +
        +
      • +

        getLogWriter

        +
        public java.io.PrintWriter getLogWriter()
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getLogWriter in interface javax.sql.CommonDataSource
        +
        +
      • +
      + + + +
        +
      • +

        setLogWriter

        +
        public void setLogWriter(java.io.PrintWriter logWriter)
        +
        INTERNAL
        +
        +
        Specified by:
        +
        setLogWriter in interface javax.sql.CommonDataSource
        +
        +
      • +
      + + + +
        +
      • +

        unwrap

        +
        public <T> T unwrap(java.lang.Class<T> iface)
        +             throws java.sql.SQLException
        +
        Return an object of this class if possible.
        +
        +
        Specified by:
        +
        unwrap in interface java.sql.Wrapper
        +
        Parameters:
        +
        iface - the class
        +
        Returns:
        +
        this
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        isWrapperFor

        +
        public boolean isWrapperFor(java.lang.Class<?> iface)
        +                     throws java.sql.SQLException
        +
        Checks if unwrap can return an object of this class.
        +
        +
        Specified by:
        +
        isWrapperFor in interface java.sql.Wrapper
        +
        Parameters:
        +
        iface - the class
        +
        Returns:
        +
        whether or not the interface is assignable from this class
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getParentLogger

        +
        public java.util.logging.Logger getParentLogger()
        +
        [Not supported]
        +
        +
        Specified by:
        +
        getParentLogger in interface javax.sql.CommonDataSource
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbcx/JdbcConnectionPoolBackwardsCompat.html b/h2/docs/javadoc/org/h2/jdbcx/JdbcConnectionPoolBackwardsCompat.html new file mode 100644 index 0000000..6867488 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbcx/JdbcConnectionPoolBackwardsCompat.html @@ -0,0 +1,171 @@ + + + + + +JdbcConnectionPoolBackwardsCompat + + + + + + + + + + + + +
+
org.h2.jdbcx
+

Interface JdbcConnectionPoolBackwardsCompat

+
+
+
+
    +
  • +
    +
    All Known Implementing Classes:
    +
    JdbcConnectionPool
    +
    +
    +
    +
    public interface JdbcConnectionPoolBackwardsCompat
    +
    Allows us to compile on older platforms, while still implementing the methods + from the newer JDBC API.
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbcx/JdbcDataSource.html b/h2/docs/javadoc/org/h2/jdbcx/JdbcDataSource.html new file mode 100644 index 0000000..376afe8 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbcx/JdbcDataSource.html @@ -0,0 +1,930 @@ + + + + + +JdbcDataSource + + + + + + + + + + + + +
+
org.h2.jdbcx
+

Class JdbcDataSource

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.message.TraceObject
    • +
    • +
        +
      • org.h2.jdbcx.JdbcDataSource
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.io.Serializable, java.sql.Wrapper, javax.naming.Referenceable, javax.sql.CommonDataSource, javax.sql.ConnectionPoolDataSource, javax.sql.DataSource, javax.sql.XADataSource, JdbcDataSourceBackwardsCompat
    +
    +
    +
    +
    public final class JdbcDataSource
    +extends org.h2.message.TraceObject
    +implements javax.sql.XADataSource, javax.sql.DataSource, javax.sql.ConnectionPoolDataSource, java.io.Serializable, javax.naming.Referenceable, JdbcDataSourceBackwardsCompat
    +
    A data source for H2 database connections. It is a factory for XAConnection + and Connection objects. This class is usually registered in a JNDI naming + service. To create a data source object and register it with a JNDI service, + use the following code: + +
    + import org.h2.jdbcx.JdbcDataSource;
    + import javax.naming.Context;
    + import javax.naming.InitialContext;
    + JdbcDataSource ds = new JdbcDataSource();
    + ds.setURL("jdbc:h2:˜/test");
    + ds.setUser("sa");
    + ds.setPassword("sa");
    + Context ctx = new InitialContext();
    + ctx.bind("jdbc/dsName", ds);
    + 
    + + To use a data source that is already registered, use the following code: + +
    + import java.sql.Connection;
    + import javax.sql.DataSource;
    + import javax.naming.Context;
    + import javax.naming.InitialContext;
    + Context ctx = new InitialContext();
    + DataSource ds = (DataSource) ctx.lookup("jdbc/dsName");
    + Connection conn = ds.getConnection();
    + 
    + + In this example the user name and password are serialized as + well; this may be a security problem in some cases.
    +
    +
    See Also:
    +
    Serialized Form
    +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.message.TraceObject

        +ARRAY, BLOB, CALLABLE_STATEMENT, CLOB, CONNECTION, DATA_SOURCE, DATABASE_META_DATA, PARAMETER_META_DATA, PREPARED_STATEMENT, RESULT_SET, RESULT_SET_META_DATA, SAVEPOINT, SQLXML, STATEMENT, trace, XA_DATA_SOURCE, XID
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      JdbcDataSource() +
      The public constructor.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      java.sql.ConnectiongetConnection() +
      Open a new connection using the current URL, user name and password.
      +
      java.sql.ConnectiongetConnection(java.lang.String user, + java.lang.String password) +
      Open a new connection using the current URL and the specified user name + and password.
      +
      java.lang.StringgetDescription() +
      Get the current description.
      +
      intgetLoginTimeout() +
      Get the login timeout in seconds, 0 meaning no timeout.
      +
      java.io.PrintWritergetLogWriter() +
      Get the current log writer for this object.
      +
      java.util.logging.LoggergetParentLogger() +
      [Not supported]
      +
      java.lang.StringgetPassword() +
      Get the current password.
      +
      javax.sql.PooledConnectiongetPooledConnection() +
      Open a new pooled connection using the current URL, user name and + password.
      +
      javax.sql.PooledConnectiongetPooledConnection(java.lang.String user, + java.lang.String password) +
      Open a new pooled connection using the current URL and the specified user + name and password.
      +
      javax.naming.ReferencegetReference() +
      Get a new reference for this object, using the current settings.
      +
      java.lang.StringgetUrl() +
      Get the current URL.
      +
      java.lang.StringgetURL() +
      Get the current URL.
      +
      java.lang.StringgetUser() +
      Get the current user name.
      +
      javax.sql.XAConnectiongetXAConnection() +
      Open a new XA connection using the current URL, user name and password.
      +
      javax.sql.XAConnectiongetXAConnection(java.lang.String user, + java.lang.String password) +
      Open a new XA connection using the current URL and the specified user + name and password.
      +
      booleanisWrapperFor(java.lang.Class<?> iface) +
      Checks if unwrap can return an object of this class.
      +
      voidsetDescription(java.lang.String description) +
      Set the description.
      +
      voidsetLoginTimeout(int timeout) +
      Set the login timeout in seconds, 0 meaning no timeout.
      +
      voidsetLogWriter(java.io.PrintWriter out) +
      Set the current log writer for this object.
      +
      voidsetPassword(java.lang.String password) +
      Set the current password.
      +
      voidsetPasswordChars(char[] password) +
      Set the current password in the form of a char array.
      +
      voidsetUrl(java.lang.String url) +
      Set the current URL.
      +
      voidsetURL(java.lang.String url) +
      Set the current URL.
      +
      voidsetUser(java.lang.String user) +
      Set the current user name.
      +
      java.lang.StringtoString() +
      INTERNAL
      +
      <T> Tunwrap(java.lang.Class<T> iface) +
      Return an object of this class if possible.
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.message.TraceObject

        +debugCode, debugCodeAssign, debugCodeCall, debugCodeCall, debugCodeCall, getNextId, getTraceId, getTraceObjectName, isDebugEnabled, isInfoEnabled, logAndConvert, quote, quoteArray, quoteBigDecimal, quoteBytes, quoteDate, quoteIntArray, quoteMap, quoteTime, quoteTimestamp, setTrace, unsupported
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        JdbcDataSource

        +
        public JdbcDataSource()
        +
        The public constructor.
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getLoginTimeout

        +
        public int getLoginTimeout()
        +
        Get the login timeout in seconds, 0 meaning no timeout.
        +
        +
        Specified by:
        +
        getLoginTimeout in interface javax.sql.CommonDataSource
        +
        Returns:
        +
        the timeout in seconds
        +
        +
      • +
      + + + +
        +
      • +

        setLoginTimeout

        +
        public void setLoginTimeout(int timeout)
        +
        Set the login timeout in seconds, 0 meaning no timeout. + The default value is 0. + This value is ignored by this database.
        +
        +
        Specified by:
        +
        setLoginTimeout in interface javax.sql.CommonDataSource
        +
        Parameters:
        +
        timeout - the timeout in seconds
        +
        +
      • +
      + + + +
        +
      • +

        getLogWriter

        +
        public java.io.PrintWriter getLogWriter()
        +
        Get the current log writer for this object.
        +
        +
        Specified by:
        +
        getLogWriter in interface javax.sql.CommonDataSource
        +
        Returns:
        +
        the log writer
        +
        +
      • +
      + + + +
        +
      • +

        setLogWriter

        +
        public void setLogWriter(java.io.PrintWriter out)
        +
        Set the current log writer for this object. + This value is ignored by this database.
        +
        +
        Specified by:
        +
        setLogWriter in interface javax.sql.CommonDataSource
        +
        Parameters:
        +
        out - the log writer
        +
        +
      • +
      + + + +
        +
      • +

        getConnection

        +
        public java.sql.Connection getConnection()
        +                                  throws java.sql.SQLException
        +
        Open a new connection using the current URL, user name and password.
        +
        +
        Specified by:
        +
        getConnection in interface javax.sql.DataSource
        +
        Returns:
        +
        the connection
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getConnection

        +
        public java.sql.Connection getConnection(java.lang.String user,
        +                                         java.lang.String password)
        +                                  throws java.sql.SQLException
        +
        Open a new connection using the current URL and the specified user name + and password.
        +
        +
        Specified by:
        +
        getConnection in interface javax.sql.DataSource
        +
        Parameters:
        +
        user - the user name
        +
        password - the password
        +
        Returns:
        +
        the connection
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getURL

        +
        public java.lang.String getURL()
        +
        Get the current URL.
        +
        +
        Returns:
        +
        the URL
        +
        +
      • +
      + + + +
        +
      • +

        setURL

        +
        public void setURL(java.lang.String url)
        +
        Set the current URL.
        +
        +
        Parameters:
        +
        url - the new URL
        +
        +
      • +
      + + + +
        +
      • +

        getUrl

        +
        public java.lang.String getUrl()
        +
        Get the current URL. + This method does the same as getURL, but this methods signature conforms + the JavaBean naming convention.
        +
        +
        Returns:
        +
        the URL
        +
        +
      • +
      + + + +
        +
      • +

        setUrl

        +
        public void setUrl(java.lang.String url)
        +
        Set the current URL. + This method does the same as setURL, but this methods signature conforms + the JavaBean naming convention.
        +
        +
        Parameters:
        +
        url - the new URL
        +
        +
      • +
      + + + +
        +
      • +

        setPassword

        +
        public void setPassword(java.lang.String password)
        +
        Set the current password.
        +
        +
        Parameters:
        +
        password - the new password.
        +
        +
      • +
      + + + +
        +
      • +

        setPasswordChars

        +
        public void setPasswordChars(char[] password)
        +
        Set the current password in the form of a char array.
        +
        +
        Parameters:
        +
        password - the new password in the form of a char array.
        +
        +
      • +
      + + + +
        +
      • +

        getPassword

        +
        public java.lang.String getPassword()
        +
        Get the current password.
        +
        +
        Returns:
        +
        the password
        +
        +
      • +
      + + + +
        +
      • +

        getUser

        +
        public java.lang.String getUser()
        +
        Get the current user name.
        +
        +
        Returns:
        +
        the user name
        +
        +
      • +
      + + + +
        +
      • +

        setUser

        +
        public void setUser(java.lang.String user)
        +
        Set the current user name.
        +
        +
        Parameters:
        +
        user - the new user name
        +
        +
      • +
      + + + +
        +
      • +

        getDescription

        +
        public java.lang.String getDescription()
        +
        Get the current description.
        +
        +
        Returns:
        +
        the description
        +
        +
      • +
      + + + +
        +
      • +

        setDescription

        +
        public void setDescription(java.lang.String description)
        +
        Set the description.
        +
        +
        Parameters:
        +
        description - the new description
        +
        +
      • +
      + + + +
        +
      • +

        getReference

        +
        public javax.naming.Reference getReference()
        +
        Get a new reference for this object, using the current settings.
        +
        +
        Specified by:
        +
        getReference in interface javax.naming.Referenceable
        +
        Returns:
        +
        the new reference
        +
        +
      • +
      + + + +
        +
      • +

        getXAConnection

        +
        public javax.sql.XAConnection getXAConnection()
        +                                       throws java.sql.SQLException
        +
        Open a new XA connection using the current URL, user name and password.
        +
        +
        Specified by:
        +
        getXAConnection in interface javax.sql.XADataSource
        +
        Returns:
        +
        the connection
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getXAConnection

        +
        public javax.sql.XAConnection getXAConnection(java.lang.String user,
        +                                              java.lang.String password)
        +                                       throws java.sql.SQLException
        +
        Open a new XA connection using the current URL and the specified user + name and password.
        +
        +
        Specified by:
        +
        getXAConnection in interface javax.sql.XADataSource
        +
        Parameters:
        +
        user - the user name
        +
        password - the password
        +
        Returns:
        +
        the connection
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getPooledConnection

        +
        public javax.sql.PooledConnection getPooledConnection()
        +                                               throws java.sql.SQLException
        +
        Open a new pooled connection using the current URL, user name and + password.
        +
        +
        Specified by:
        +
        getPooledConnection in interface javax.sql.ConnectionPoolDataSource
        +
        Returns:
        +
        the connection
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getPooledConnection

        +
        public javax.sql.PooledConnection getPooledConnection(java.lang.String user,
        +                                                      java.lang.String password)
        +                                               throws java.sql.SQLException
        +
        Open a new pooled connection using the current URL and the specified user + name and password.
        +
        +
        Specified by:
        +
        getPooledConnection in interface javax.sql.ConnectionPoolDataSource
        +
        Parameters:
        +
        user - the user name
        +
        password - the password
        +
        Returns:
        +
        the connection
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        unwrap

        +
        public <T> T unwrap(java.lang.Class<T> iface)
        +             throws java.sql.SQLException
        +
        Return an object of this class if possible.
        +
        +
        Specified by:
        +
        unwrap in interface java.sql.Wrapper
        +
        Parameters:
        +
        iface - the class
        +
        Returns:
        +
        this
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        isWrapperFor

        +
        public boolean isWrapperFor(java.lang.Class<?> iface)
        +                     throws java.sql.SQLException
        +
        Checks if unwrap can return an object of this class.
        +
        +
        Specified by:
        +
        isWrapperFor in interface java.sql.Wrapper
        +
        Parameters:
        +
        iface - the class
        +
        Returns:
        +
        whether or not the interface is assignable from this class
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getParentLogger

        +
        public java.util.logging.Logger getParentLogger()
        +
        [Not supported]
        +
        +
        Specified by:
        +
        getParentLogger in interface javax.sql.CommonDataSource
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        INTERNAL
        +
        +
        Overrides:
        +
        toString in class java.lang.Object
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbcx/JdbcDataSourceBackwardsCompat.html b/h2/docs/javadoc/org/h2/jdbcx/JdbcDataSourceBackwardsCompat.html new file mode 100644 index 0000000..238aa80 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbcx/JdbcDataSourceBackwardsCompat.html @@ -0,0 +1,171 @@ + + + + + +JdbcDataSourceBackwardsCompat + + + + + + + + + + + + +
+
org.h2.jdbcx
+

Interface JdbcDataSourceBackwardsCompat

+
+
+
+
    +
  • +
    +
    All Known Implementing Classes:
    +
    JdbcDataSource
    +
    +
    +
    +
    public interface JdbcDataSourceBackwardsCompat
    +
    Allows us to compile on older platforms, while still implementing the methods + from the newer JDBC API.
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbcx/JdbcDataSourceFactory.html b/h2/docs/javadoc/org/h2/jdbcx/JdbcDataSourceFactory.html new file mode 100644 index 0000000..da70437 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbcx/JdbcDataSourceFactory.html @@ -0,0 +1,323 @@ + + + + + +JdbcDataSourceFactory + + + + + + + + + + + + +
+
org.h2.jdbcx
+

Class JdbcDataSourceFactory

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.jdbcx.JdbcDataSourceFactory
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    javax.naming.spi.ObjectFactory
    +
    +
    +
    +
    public final class JdbcDataSourceFactory
    +extends java.lang.Object
    +implements javax.naming.spi.ObjectFactory
    +
    This class is used to create new DataSource objects. + An application should not use this class directly.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      JdbcDataSourceFactory() +
      The public constructor to create new factory objects.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      java.lang.ObjectgetObjectInstance(java.lang.Object obj, + javax.naming.Name name, + javax.naming.Context nameCtx, + java.util.Hashtable<?,?> environment) +
      Creates a new object using the specified location or reference + information.
      +
      static org.h2.message.TraceSystemgetTraceSystem() +
      INTERNAL
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        JdbcDataSourceFactory

        +
        public JdbcDataSourceFactory()
        +
        The public constructor to create new factory objects.
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getObjectInstance

        +
        public java.lang.Object getObjectInstance(java.lang.Object obj,
        +                                          javax.naming.Name name,
        +                                          javax.naming.Context nameCtx,
        +                                          java.util.Hashtable<?,?> environment)
        +
        Creates a new object using the specified location or reference + information.
        +
        +
        Specified by:
        +
        getObjectInstance in interface javax.naming.spi.ObjectFactory
        +
        Parameters:
        +
        obj - the reference (this factory only supports objects of type + javax.naming.Reference)
        +
        name - unused
        +
        nameCtx - unused
        +
        environment - unused
        +
        Returns:
        +
        the new JdbcDataSource, or null if the reference class name is + not JdbcDataSource.
        +
        +
      • +
      + + + +
        +
      • +

        getTraceSystem

        +
        public static org.h2.message.TraceSystem getTraceSystem()
        +
        INTERNAL
        +
        +
        Returns:
        +
        TraceSystem
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbcx/JdbcXAConnection.html b/h2/docs/javadoc/org/h2/jdbcx/JdbcXAConnection.html new file mode 100644 index 0000000..ac7b682 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbcx/JdbcXAConnection.html @@ -0,0 +1,703 @@ + + + + + +JdbcXAConnection + + + + + + + + + + + + +
+
org.h2.jdbcx
+

Class JdbcXAConnection

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.message.TraceObject
    • +
    • +
        +
      • org.h2.jdbcx.JdbcXAConnection
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    javax.sql.PooledConnection, javax.sql.XAConnection, javax.transaction.xa.XAResource
    +
    +
    +
    +
    public final class JdbcXAConnection
    +extends org.h2.message.TraceObject
    +implements javax.sql.XAConnection, javax.transaction.xa.XAResource
    +
    This class provides support for distributed transactions. + An application developer usually does not use this interface. + It is used by the transaction manager internally.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.message.TraceObject

        +ARRAY, BLOB, CALLABLE_STATEMENT, CLOB, CONNECTION, DATA_SOURCE, DATABASE_META_DATA, PARAMETER_META_DATA, PREPARED_STATEMENT, RESULT_SET, RESULT_SET_META_DATA, SAVEPOINT, SQLXML, STATEMENT, trace, XA_DATA_SOURCE, XID
      • +
      +
        +
      • + + +

        Fields inherited from interface javax.transaction.xa.XAResource

        +TMENDRSCAN, TMFAIL, TMJOIN, TMNOFLAGS, TMONEPHASE, TMRESUME, TMSTARTRSCAN, TMSUCCESS, TMSUSPEND, XA_OK, XA_RDONLY
      • +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      voidaddConnectionEventListener(javax.sql.ConnectionEventListener listener) +
      Register a new listener for the connection.
      +
      voidaddStatementEventListener(javax.sql.StatementEventListener listener) +
      [Not supported] Add a statement event listener.
      +
      voidclose() +
      Close the physical connection.
      +
      voidcommit(javax.transaction.xa.Xid xid, + boolean onePhase) +
      Commit a transaction.
      +
      voidend(javax.transaction.xa.Xid xid, + int flags) +
      End a transaction.
      +
      voidforget(javax.transaction.xa.Xid xid) +
      Forget a transaction.
      +
      java.sql.ConnectiongetConnection() +
      Get a connection that is a handle to the physical connection.
      +
      intgetTransactionTimeout() +
      Get the transaction timeout.
      +
      javax.transaction.xa.XAResourcegetXAResource() +
      Get the XAResource object.
      +
      booleanisSameRM(javax.transaction.xa.XAResource xares) +
      Checks if this is the same XAResource.
      +
      intprepare(javax.transaction.xa.Xid xid) +
      Prepare a transaction.
      +
      javax.transaction.xa.Xid[]recover(int flag) +
      Get the list of prepared transaction branches.
      +
      voidremoveConnectionEventListener(javax.sql.ConnectionEventListener listener) +
      Remove the event listener.
      +
      voidremoveStatementEventListener(javax.sql.StatementEventListener listener) +
      [Not supported] Remove a statement event listener.
      +
      voidrollback(javax.transaction.xa.Xid xid) +
      Roll back a transaction.
      +
      booleansetTransactionTimeout(int seconds) +
      Set the transaction timeout.
      +
      voidstart(javax.transaction.xa.Xid xid, + int flags) +
      Start or continue to work on a transaction.
      +
      java.lang.StringtoString() +
      INTERNAL
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.message.TraceObject

        +debugCode, debugCodeAssign, debugCodeCall, debugCodeCall, debugCodeCall, getNextId, getTraceId, getTraceObjectName, isDebugEnabled, isInfoEnabled, logAndConvert, quote, quoteArray, quoteBigDecimal, quoteBytes, quoteDate, quoteIntArray, quoteMap, quoteTime, quoteTimestamp, setTrace, unsupported
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getXAResource

        +
        public javax.transaction.xa.XAResource getXAResource()
        +
        Get the XAResource object.
        +
        +
        Specified by:
        +
        getXAResource in interface javax.sql.XAConnection
        +
        Returns:
        +
        itself
        +
        +
      • +
      + + + +
        +
      • +

        close

        +
        public void close()
        +           throws java.sql.SQLException
        +
        Close the physical connection. + This method is usually called by the connection pool.
        +
        +
        Specified by:
        +
        close in interface javax.sql.PooledConnection
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getConnection

        +
        public java.sql.Connection getConnection()
        +                                  throws java.sql.SQLException
        +
        Get a connection that is a handle to the physical connection. This method + is usually called by the connection pool. This method closes the last + connection handle if one exists.
        +
        +
        Specified by:
        +
        getConnection in interface javax.sql.PooledConnection
        +
        Returns:
        +
        the connection
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        addConnectionEventListener

        +
        public void addConnectionEventListener(javax.sql.ConnectionEventListener listener)
        +
        Register a new listener for the connection.
        +
        +
        Specified by:
        +
        addConnectionEventListener in interface javax.sql.PooledConnection
        +
        Parameters:
        +
        listener - the event listener
        +
        +
      • +
      + + + +
        +
      • +

        removeConnectionEventListener

        +
        public void removeConnectionEventListener(javax.sql.ConnectionEventListener listener)
        +
        Remove the event listener.
        +
        +
        Specified by:
        +
        removeConnectionEventListener in interface javax.sql.PooledConnection
        +
        Parameters:
        +
        listener - the event listener
        +
        +
      • +
      + + + +
        +
      • +

        getTransactionTimeout

        +
        public int getTransactionTimeout()
        +
        Get the transaction timeout.
        +
        +
        Specified by:
        +
        getTransactionTimeout in interface javax.transaction.xa.XAResource
        +
        Returns:
        +
        0
        +
        +
      • +
      + + + +
        +
      • +

        setTransactionTimeout

        +
        public boolean setTransactionTimeout(int seconds)
        +
        Set the transaction timeout.
        +
        +
        Specified by:
        +
        setTransactionTimeout in interface javax.transaction.xa.XAResource
        +
        Parameters:
        +
        seconds - ignored
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        isSameRM

        +
        public boolean isSameRM(javax.transaction.xa.XAResource xares)
        +
        Checks if this is the same XAResource.
        +
        +
        Specified by:
        +
        isSameRM in interface javax.transaction.xa.XAResource
        +
        Parameters:
        +
        xares - the other object
        +
        Returns:
        +
        true if this is the same object
        +
        +
      • +
      + + + +
        +
      • +

        recover

        +
        public javax.transaction.xa.Xid[] recover(int flag)
        +                                   throws javax.transaction.xa.XAException
        +
        Get the list of prepared transaction branches. This method is called by + the transaction manager during recovery.
        +
        +
        Specified by:
        +
        recover in interface javax.transaction.xa.XAResource
        +
        Parameters:
        +
        flag - TMSTARTRSCAN, TMENDRSCAN, or TMNOFLAGS. If no other flags are + set, TMNOFLAGS must be used.
        +
        Returns:
        +
        zero or more Xid objects
        +
        Throws:
        +
        javax.transaction.xa.XAException
        +
        +
      • +
      + + + +
        +
      • +

        prepare

        +
        public int prepare(javax.transaction.xa.Xid xid)
        +            throws javax.transaction.xa.XAException
        +
        Prepare a transaction.
        +
        +
        Specified by:
        +
        prepare in interface javax.transaction.xa.XAResource
        +
        Parameters:
        +
        xid - the transaction id
        +
        Returns:
        +
        XA_OK
        +
        Throws:
        +
        javax.transaction.xa.XAException
        +
        +
      • +
      + + + +
        +
      • +

        forget

        +
        public void forget(javax.transaction.xa.Xid xid)
        +
        Forget a transaction. + This method does not have an effect for this database.
        +
        +
        Specified by:
        +
        forget in interface javax.transaction.xa.XAResource
        +
        Parameters:
        +
        xid - the transaction id
        +
        +
      • +
      + + + +
        +
      • +

        rollback

        +
        public void rollback(javax.transaction.xa.Xid xid)
        +              throws javax.transaction.xa.XAException
        +
        Roll back a transaction.
        +
        +
        Specified by:
        +
        rollback in interface javax.transaction.xa.XAResource
        +
        Parameters:
        +
        xid - the transaction id
        +
        Throws:
        +
        javax.transaction.xa.XAException
        +
        +
      • +
      + + + +
        +
      • +

        end

        +
        public void end(javax.transaction.xa.Xid xid,
        +                int flags)
        +         throws javax.transaction.xa.XAException
        +
        End a transaction.
        +
        +
        Specified by:
        +
        end in interface javax.transaction.xa.XAResource
        +
        Parameters:
        +
        xid - the transaction id
        +
        flags - TMSUCCESS, TMFAIL, or TMSUSPEND
        +
        Throws:
        +
        javax.transaction.xa.XAException
        +
        +
      • +
      + + + +
        +
      • +

        start

        +
        public void start(javax.transaction.xa.Xid xid,
        +                  int flags)
        +           throws javax.transaction.xa.XAException
        +
        Start or continue to work on a transaction.
        +
        +
        Specified by:
        +
        start in interface javax.transaction.xa.XAResource
        +
        Parameters:
        +
        xid - the transaction id
        +
        flags - TMNOFLAGS, TMJOIN, or TMRESUME
        +
        Throws:
        +
        javax.transaction.xa.XAException
        +
        +
      • +
      + + + +
        +
      • +

        commit

        +
        public void commit(javax.transaction.xa.Xid xid,
        +                   boolean onePhase)
        +            throws javax.transaction.xa.XAException
        +
        Commit a transaction.
        +
        +
        Specified by:
        +
        commit in interface javax.transaction.xa.XAResource
        +
        Parameters:
        +
        xid - the transaction id
        +
        onePhase - use a one-phase protocol if true
        +
        Throws:
        +
        javax.transaction.xa.XAException
        +
        +
      • +
      + + + +
        +
      • +

        addStatementEventListener

        +
        public void addStatementEventListener(javax.sql.StatementEventListener listener)
        +
        [Not supported] Add a statement event listener.
        +
        +
        Specified by:
        +
        addStatementEventListener in interface javax.sql.PooledConnection
        +
        Parameters:
        +
        listener - the new statement event listener
        +
        +
      • +
      + + + +
        +
      • +

        removeStatementEventListener

        +
        public void removeStatementEventListener(javax.sql.StatementEventListener listener)
        +
        [Not supported] Remove a statement event listener.
        +
        +
        Specified by:
        +
        removeStatementEventListener in interface javax.sql.PooledConnection
        +
        Parameters:
        +
        listener - the statement event listener
        +
        +
      • +
      + + + +
        +
      • +

        toString

        +
        public java.lang.String toString()
        +
        INTERNAL
        +
        +
        Overrides:
        +
        toString in class java.lang.Object
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbcx/JdbcXid.html b/h2/docs/javadoc/org/h2/jdbcx/JdbcXid.html new file mode 100644 index 0000000..e4b1865 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbcx/JdbcXid.html @@ -0,0 +1,328 @@ + + + + + +JdbcXid + + + + + + + + + + + + +
+
org.h2.jdbcx
+

Class JdbcXid

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.message.TraceObject
    • +
    • +
        +
      • org.h2.jdbcx.JdbcXid
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    javax.transaction.xa.Xid
    +
    +
    +
    +
    public final class JdbcXid
    +extends org.h2.message.TraceObject
    +implements javax.transaction.xa.Xid
    +
    An object of this class represents a transaction id.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.message.TraceObject

        +ARRAY, BLOB, CALLABLE_STATEMENT, CLOB, CONNECTION, DATA_SOURCE, DATABASE_META_DATA, PARAMETER_META_DATA, PREPARED_STATEMENT, RESULT_SET, RESULT_SET_META_DATA, SAVEPOINT, SQLXML, STATEMENT, trace, XA_DATA_SOURCE, XID
      • +
      +
        +
      • + + +

        Fields inherited from interface javax.transaction.xa.Xid

        +MAXBQUALSIZE, MAXGTRIDSIZE
      • +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      byte[]getBranchQualifier() +
      The transaction branch identifier.
      +
      intgetFormatId() +
      Get the format id.
      +
      byte[]getGlobalTransactionId() +
      The global transaction identifier.
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.message.TraceObject

        +debugCode, debugCodeAssign, debugCodeCall, debugCodeCall, debugCodeCall, getNextId, getTraceId, getTraceObjectName, isDebugEnabled, isInfoEnabled, logAndConvert, quote, quoteArray, quoteBigDecimal, quoteBytes, quoteDate, quoteIntArray, quoteMap, quoteTime, quoteTimestamp, setTrace, unsupported
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getFormatId

        +
        public int getFormatId()
        +
        Get the format id.
        +
        +
        Specified by:
        +
        getFormatId in interface javax.transaction.xa.Xid
        +
        Returns:
        +
        the format id
        +
        +
      • +
      + + + +
        +
      • +

        getBranchQualifier

        +
        public byte[] getBranchQualifier()
        +
        The transaction branch identifier.
        +
        +
        Specified by:
        +
        getBranchQualifier in interface javax.transaction.xa.Xid
        +
        Returns:
        +
        the identifier
        +
        +
      • +
      + + + +
        +
      • +

        getGlobalTransactionId

        +
        public byte[] getGlobalTransactionId()
        +
        The global transaction identifier.
        +
        +
        Specified by:
        +
        getGlobalTransactionId in interface javax.transaction.xa.Xid
        +
        Returns:
        +
        the transaction id
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbcx/package-frame.html b/h2/docs/javadoc/org/h2/jdbcx/package-frame.html new file mode 100644 index 0000000..88170b4 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbcx/package-frame.html @@ -0,0 +1,29 @@ + + + + + +org.h2.jdbcx + + + + + +

org.h2.jdbcx

+ + + diff --git a/h2/docs/javadoc/org/h2/jdbcx/package-summary.html b/h2/docs/javadoc/org/h2/jdbcx/package-summary.html new file mode 100644 index 0000000..edf7905 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbcx/package-summary.html @@ -0,0 +1,206 @@ + + + + + +org.h2.jdbcx + + + + + + + + + + + +
+

Package org.h2.jdbcx

+
+
+ +Implementation of the extended JDBC API (package javax.sql).
+
+

See: Description

+
+
+
    +
  • + + + + + + + + + + + + + + + + +
    Interface Summary 
    InterfaceDescription
    JdbcConnectionPoolBackwardsCompat +
    Allows us to compile on older platforms, while still implementing the methods + from the newer JDBC API.
    +
    JdbcDataSourceBackwardsCompat +
    Allows us to compile on older platforms, while still implementing the methods + from the newer JDBC API.
    +
    +
  • +
  • + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Class Summary 
    ClassDescription
    JdbcConnectionPool +
    A simple standalone JDBC connection pool.
    +
    JdbcDataSource +
    A data source for H2 database connections.
    +
    JdbcDataSourceFactory +
    This class is used to create new DataSource objects.
    +
    JdbcXAConnection +
    This class provides support for distributed transactions.
    +
    JdbcXid +
    An object of this class represents a transaction id.
    +
    +
  • +
+ + + +

Package org.h2.jdbcx Description

+

+ +Implementation of the extended JDBC API (package javax.sql). + +

+
+ + + + + + diff --git a/h2/docs/javadoc/org/h2/jdbcx/package-tree.html b/h2/docs/javadoc/org/h2/jdbcx/package-tree.html new file mode 100644 index 0000000..a520b64 --- /dev/null +++ b/h2/docs/javadoc/org/h2/jdbcx/package-tree.html @@ -0,0 +1,148 @@ + + + + + +org.h2.jdbcx Class Hierarchy + + + + + + + + + + + +
+

Hierarchy For Package org.h2.jdbcx

+Package Hierarchies: + +
+
+

Class Hierarchy

+ +

Interface Hierarchy

+ +
+ + + + + + diff --git a/h2/docs/javadoc/org/h2/tools/Backup.html b/h2/docs/javadoc/org/h2/tools/Backup.html new file mode 100644 index 0000000..c432844 --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/Backup.html @@ -0,0 +1,386 @@ + + + + + +Backup + + + + + + + + + + + + +
+
org.h2.tools
+

Class Backup

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.util.Tool
    • +
    • +
        +
      • org.h2.tools.Backup
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public class Backup
    +extends org.h2.util.Tool
    +
    Creates a backup of a database. + + This tool copies all database files. The database must be closed before using + this tool. To create a backup while the database is in use, run the BACKUP + SQL statement. In an emergency, for example if the application is not + responding, creating a backup using the Backup tool is possible by using the + quiet mode. However, if the database is changed while the backup is running + in quiet mode, the backup could be corrupt.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.util.Tool

        +out
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      Backup() 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      static voidexecute(java.lang.String zipFileName, + java.lang.String directory, + java.lang.String db, + boolean quiet) +
      Backs up database files.
      +
      static voidmain(java.lang.String... args) +
      Options are case sensitive.
      +
      voidrunTool(java.lang.String... args) +
      Run the tool with the given output stream and arguments.
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.util.Tool

        +isOption, printNoDatabaseFilesFound, setOut, showUsage, showUsageAndThrowUnsupportedOption, throwUnsupportedOption
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        Backup

        +
        public Backup()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        main

        +
        public static void main(java.lang.String... args)
        +                 throws java.sql.SQLException
        +
        Options are case sensitive. + + + + + + + + + + + + +
        Supported options are:
        [-help] or [-?]Print the list of options
        [-file <filename>]The target file name (default: backup.zip)
        [-dir <dir>]The source directory (default: .)
        [-db <database>]Source database; not required if there is only one
        [-quiet]Do not print progress information
        +
        +
        Parameters:
        +
        args - the command line arguments
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        runTool

        +
        public void runTool(java.lang.String... args)
        +             throws java.sql.SQLException
        +
        Description copied from class: org.h2.util.Tool
        +
        Run the tool with the given output stream and arguments.
        +
        +
        Specified by:
        +
        runTool in class org.h2.util.Tool
        +
        Parameters:
        +
        args - the argument list
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        execute

        +
        public static void execute(java.lang.String zipFileName,
        +                           java.lang.String directory,
        +                           java.lang.String db,
        +                           boolean quiet)
        +                    throws java.sql.SQLException
        +
        Backs up database files.
        +
        +
        Parameters:
        +
        zipFileName - the name of the target backup file (including path)
        +
        directory - the source directory name
        +
        db - the source database name (null if there is only one database, + and empty string to backup all files in this directory)
        +
        quiet - don't print progress information
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/tools/ChangeFileEncryption.html b/h2/docs/javadoc/org/h2/tools/ChangeFileEncryption.html new file mode 100644 index 0000000..d9679fd --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/ChangeFileEncryption.html @@ -0,0 +1,390 @@ + + + + + +ChangeFileEncryption + + + + + + + + + + + + +
+
org.h2.tools
+

Class ChangeFileEncryption

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.util.Tool
    • +
    • +
        +
      • org.h2.tools.ChangeFileEncryption
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public class ChangeFileEncryption
    +extends org.h2.util.Tool
    +
    Allows changing the database file encryption password or algorithm. + + This tool can not be used to change a password of a user. + The database must be closed before using this tool.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.util.Tool

        +out
      • +
      +
    • +
    + + + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      static voidexecute(java.lang.String dir, + java.lang.String db, + java.lang.String cipher, + char[] decryptPassword, + char[] encryptPassword, + boolean quiet) +
      Changes the password for a database.
      +
      static voidmain(java.lang.String... args) +
      Options are case sensitive.
      +
      voidrunTool(java.lang.String... args) +
      Run the tool with the given output stream and arguments.
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.util.Tool

        +isOption, printNoDatabaseFilesFound, setOut, showUsage, showUsageAndThrowUnsupportedOption, throwUnsupportedOption
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        ChangeFileEncryption

        +
        public ChangeFileEncryption()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        main

        +
        public static void main(java.lang.String... args)
        +
        Options are case sensitive. + + + + + + + + + + + + + + + + +
        Supported options
        [-help] or [-?]Print the list of options
        [-cipher type]The encryption type (AES)
        [-dir <dir>]The database directory (default: .)
        [-db <database>]Database name (all databases if not set)
        [-decrypt <pwd>]The decryption password (if not set: not yet encrypted)
        [-encrypt <pwd>]The encryption password (if not set: do not encrypt)
        [-quiet]Do not print progress information
        +
        +
        Parameters:
        +
        args - the command line arguments
        +
        +
      • +
      + + + +
        +
      • +

        runTool

        +
        public void runTool(java.lang.String... args)
        +             throws java.sql.SQLException
        +
        Description copied from class: org.h2.util.Tool
        +
        Run the tool with the given output stream and arguments.
        +
        +
        Specified by:
        +
        runTool in class org.h2.util.Tool
        +
        Parameters:
        +
        args - the argument list
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        execute

        +
        public static void execute(java.lang.String dir,
        +                           java.lang.String db,
        +                           java.lang.String cipher,
        +                           char[] decryptPassword,
        +                           char[] encryptPassword,
        +                           boolean quiet)
        +                    throws java.sql.SQLException
        +
        Changes the password for a database. The passwords must be supplied as + char arrays and are cleaned in this method. The database must be closed + before calling this method.
        +
        +
        Parameters:
        +
        dir - the directory (. for the current directory)
        +
        db - the database name (null for all databases)
        +
        cipher - the cipher (AES)
        +
        decryptPassword - the decryption password as a char array
        +
        encryptPassword - the encryption password as a char array
        +
        quiet - don't print progress information
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/tools/CompressTool.html b/h2/docs/javadoc/org/h2/tools/CompressTool.html new file mode 100644 index 0000000..d21136b --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/CompressTool.html @@ -0,0 +1,452 @@ + + + + + +CompressTool + + + + + + + + + + + + +
+
org.h2.tools
+

Class CompressTool

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.tools.CompressTool
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public class CompressTool
    +extends java.lang.Object
    +
    A tool to losslessly compress data, and expand the compressed data again.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      byte[]compress(byte[] in, + java.lang.String algorithm) +
      Compressed the data using the specified algorithm.
      +
      byte[]expand(byte[] in) +
      Expands the compressed data.
      +
      static voidexpand(byte[] in, + byte[] out, + int outPos) +
      INTERNAL
      +
      static CompressToolgetInstance() +
      Get a new instance.
      +
      static intgetVariableIntLength(int x) +
      Get a variable size integer length using Rice coding.
      +
      static intreadVariableInt(byte[] buff, + int pos) +
      Read a variable size integer using Rice coding.
      +
      static java.io.InputStreamwrapInputStream(java.io.InputStream in, + java.lang.String compressionAlgorithm, + java.lang.String entryName) +
      INTERNAL
      +
      static java.io.OutputStreamwrapOutputStream(java.io.OutputStream out, + java.lang.String compressionAlgorithm, + java.lang.String entryName) +
      INTERNAL
      +
      static intwriteVariableInt(byte[] buff, + int pos, + int x) +
      Write a variable size integer using Rice coding.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getInstance

        +
        public static CompressTool getInstance()
        +
        Get a new instance. Each instance uses a separate buffer, so multiple + instances can be used concurrently. However each instance alone is not + multithreading safe.
        +
        +
        Returns:
        +
        a new instance
        +
        +
      • +
      + + + +
        +
      • +

        compress

        +
        public byte[] compress(byte[] in,
        +                       java.lang.String algorithm)
        +
        Compressed the data using the specified algorithm. If no algorithm is + supplied, LZF is used
        +
        +
        Parameters:
        +
        in - the byte array with the original data
        +
        algorithm - the algorithm (LZF, DEFLATE)
        +
        Returns:
        +
        the compressed data
        +
        +
      • +
      + + + +
        +
      • +

        expand

        +
        public byte[] expand(byte[] in)
        +
        Expands the compressed data.
        +
        +
        Parameters:
        +
        in - the byte array with the compressed data
        +
        Returns:
        +
        the uncompressed data
        +
        +
      • +
      + + + +
        +
      • +

        expand

        +
        public static void expand(byte[] in,
        +                          byte[] out,
        +                          int outPos)
        +
        INTERNAL
        +
        +
        Parameters:
        +
        in - compressed data
        +
        out - uncompressed result
        +
        outPos - the offset at the output array
        +
        +
      • +
      + + + +
        +
      • +

        readVariableInt

        +
        public static int readVariableInt(byte[] buff,
        +                                  int pos)
        +
        Read a variable size integer using Rice coding.
        +
        +
        Parameters:
        +
        buff - the buffer
        +
        pos - the position
        +
        Returns:
        +
        the integer
        +
        +
      • +
      + + + +
        +
      • +

        writeVariableInt

        +
        public static int writeVariableInt(byte[] buff,
        +                                   int pos,
        +                                   int x)
        +
        Write a variable size integer using Rice coding. + Negative values need 5 bytes.
        +
        +
        Parameters:
        +
        buff - the buffer
        +
        pos - the position
        +
        x - the value
        +
        Returns:
        +
        the number of bytes written (0-5)
        +
        +
      • +
      + + + +
        +
      • +

        getVariableIntLength

        +
        public static int getVariableIntLength(int x)
        +
        Get a variable size integer length using Rice coding. + Negative values need 5 bytes.
        +
        +
        Parameters:
        +
        x - the value
        +
        Returns:
        +
        the number of bytes needed (0-5)
        +
        +
      • +
      + + + +
        +
      • +

        wrapOutputStream

        +
        public static java.io.OutputStream wrapOutputStream(java.io.OutputStream out,
        +                                                    java.lang.String compressionAlgorithm,
        +                                                    java.lang.String entryName)
        +
        INTERNAL
        +
        +
        Parameters:
        +
        out - stream
        +
        compressionAlgorithm - to be used
        +
        entryName - in a zip file
        +
        Returns:
        +
        compressed stream
        +
        +
      • +
      + + + +
        +
      • +

        wrapInputStream

        +
        public static java.io.InputStream wrapInputStream(java.io.InputStream in,
        +                                                  java.lang.String compressionAlgorithm,
        +                                                  java.lang.String entryName)
        +
        INTERNAL
        +
        +
        Parameters:
        +
        in - stream
        +
        compressionAlgorithm - to be used
        +
        entryName - in a zip file
        +
        Returns:
        +
        in stream or null if there is no such entry
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/tools/Console.html b/h2/docs/javadoc/org/h2/tools/Console.html new file mode 100644 index 0000000..fcb7e06 --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/Console.html @@ -0,0 +1,394 @@ + + + + + +Console + + + + + + + + + + + + +
+
org.h2.tools
+

Class Console

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.util.Tool
    • +
    • +
        +
      • org.h2.tools.Console
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    org.h2.server.ShutdownHandler
    +
    +
    +
    Direct Known Subclasses:
    +
    GUIConsole
    +
    +
    +
    +
    public class Console
    +extends org.h2.util.Tool
    +implements org.h2.server.ShutdownHandler
    +
    Starts the H2 Console (web-) server, as well as the TCP and PG server.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.util.Tool

        +out
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      Console() 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      static voidmain(java.lang.String... args) +
      When running without options, -tcp, -web, -browser and -pg are started.
      +
      voidrunTool(java.lang.String... args) +
      This tool starts the H2 Console (web-) server, as well as the TCP and PG + server.
      +
      voidshutdown() +
      INTERNAL.
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.util.Tool

        +isOption, printNoDatabaseFilesFound, setOut, showUsage, showUsageAndThrowUnsupportedOption, throwUnsupportedOption
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        Console

        +
        public Console()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        main

        +
        public static void main(java.lang.String... args)
        +                 throws java.sql.SQLException
        +
        When running without options, -tcp, -web, -browser and -pg are started. + + Options are case sensitive. + + + + + + + + + + + + + + + + + + + + + + +
        Supported options
        [-help] or [-?]Print the list of options
        [-url]Start a browser and connect to this URL
        [-driver]Used together with -url: the driver
        [-user]Used together with -url: the user name
        [-password]Used together with -url: the password
        [-web]Start the web server with the H2 Console
        [-tool]Start the icon or window that allows to start a browser
        [-browser]Start a browser connecting to the web server
        [-tcp]Start the TCP server
        [-pg]Start the PG server
        + For each Server, additional options are available; + for details, see the Server tool. + If a service can not be started, the program + terminates with an exit code of 1.
        +
        +
        Parameters:
        +
        args - the command line arguments
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        runTool

        +
        public void runTool(java.lang.String... args)
        +             throws java.sql.SQLException
        +
        This tool starts the H2 Console (web-) server, as well as the TCP and PG + server. A system tray icon is created, for platforms that + support it. Otherwise, a small window opens.
        +
        +
        Specified by:
        +
        runTool in class org.h2.util.Tool
        +
        Parameters:
        +
        args - the command line arguments
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        shutdown

        +
        public void shutdown()
        +
        INTERNAL. + Stop all servers that were started using the console.
        +
        +
        Specified by:
        +
        shutdown in interface org.h2.server.ShutdownHandler
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/tools/ConvertTraceFile.html b/h2/docs/javadoc/org/h2/tools/ConvertTraceFile.html new file mode 100644 index 0000000..a2663cd --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/ConvertTraceFile.html @@ -0,0 +1,346 @@ + + + + + +ConvertTraceFile + + + + + + + + + + + + +
+
org.h2.tools
+

Class ConvertTraceFile

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.util.Tool
    • +
    • +
        +
      • org.h2.tools.ConvertTraceFile
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public class ConvertTraceFile
    +extends org.h2.util.Tool
    +
    Converts a .trace.db file to a SQL script and Java source code. + + SQL statement statistics are listed as well.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.util.Tool

        +out
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      ConvertTraceFile() 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      static voidmain(java.lang.String... args) +
      Options are case sensitive.
      +
      voidrunTool(java.lang.String... args) +
      Run the tool with the given output stream and arguments.
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.util.Tool

        +isOption, printNoDatabaseFilesFound, setOut, showUsage, showUsageAndThrowUnsupportedOption, throwUnsupportedOption
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        ConvertTraceFile

        +
        public ConvertTraceFile()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        main

        +
        public static void main(java.lang.String... args)
        +                 throws java.sql.SQLException
        +
        Options are case sensitive. + + + + + + + + + + +
        Supported options
        [-help] or [-?]Print the list of options
        [-traceFile <file>]The trace file name (default: test.trace.db)
        [-script <file>]The script file name (default: test.sql)
        [-javaClass <file>]The Java directory and class file name (default: Test)
        +
        +
        Parameters:
        +
        args - the command line arguments
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        runTool

        +
        public void runTool(java.lang.String... args)
        +             throws java.sql.SQLException
        +
        Description copied from class: org.h2.util.Tool
        +
        Run the tool with the given output stream and arguments.
        +
        +
        Specified by:
        +
        runTool in class org.h2.util.Tool
        +
        Parameters:
        +
        args - the argument list
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/tools/CreateCluster.html b/h2/docs/javadoc/org/h2/tools/CreateCluster.html new file mode 100644 index 0000000..58219ca --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/CreateCluster.html @@ -0,0 +1,385 @@ + + + + + +CreateCluster + + + + + + + + + + + + +
+
org.h2.tools
+

Class CreateCluster

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.util.Tool
    • +
    • +
        +
      • org.h2.tools.CreateCluster
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public class CreateCluster
    +extends org.h2.util.Tool
    +
    Creates a cluster from a stand-alone database. + + Copies a database to another location if required.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.util.Tool

        +out
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      CreateCluster() 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      voidexecute(java.lang.String urlSource, + java.lang.String urlTarget, + java.lang.String user, + java.lang.String password, + java.lang.String serverList) +
      Creates a cluster.
      +
      static voidmain(java.lang.String... args) +
      Options are case sensitive.
      +
      voidrunTool(java.lang.String... args) +
      Run the tool with the given output stream and arguments.
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.util.Tool

        +isOption, printNoDatabaseFilesFound, setOut, showUsage, showUsageAndThrowUnsupportedOption, throwUnsupportedOption
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        CreateCluster

        +
        public CreateCluster()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        main

        +
        public static void main(java.lang.String... args)
        +                 throws java.sql.SQLException
        +
        Options are case sensitive. + + + + + + + + + + + + + + +
        Supported options
        [-help] or [-?]Print the list of options
        [-urlSource "<url>"]The database URL of the source database (jdbc:h2:...)
        [-urlTarget "<url>"]The database URL of the target database (jdbc:h2:...)
        [-user <user>]The user name (default: sa)
        [-password <pwd>]The password
        [-serverList <list>]The comma separated list of host names or IP addresses
        +
        +
        Parameters:
        +
        args - the command line arguments
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        runTool

        +
        public void runTool(java.lang.String... args)
        +             throws java.sql.SQLException
        +
        Description copied from class: org.h2.util.Tool
        +
        Run the tool with the given output stream and arguments.
        +
        +
        Specified by:
        +
        runTool in class org.h2.util.Tool
        +
        Parameters:
        +
        args - the argument list
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        execute

        +
        public void execute(java.lang.String urlSource,
        +                    java.lang.String urlTarget,
        +                    java.lang.String user,
        +                    java.lang.String password,
        +                    java.lang.String serverList)
        +             throws java.sql.SQLException
        +
        Creates a cluster.
        +
        +
        Parameters:
        +
        urlSource - the database URL of the original database
        +
        urlTarget - the database URL of the copy
        +
        user - the user name
        +
        password - the password
        +
        serverList - the server list
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/tools/Csv.html b/h2/docs/javadoc/org/h2/tools/Csv.html new file mode 100644 index 0000000..3da8608 --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/Csv.html @@ -0,0 +1,956 @@ + + + + + +Csv + + + + + + + + + + + + +
+
org.h2.tools
+

Class Csv

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.tools.Csv
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    SimpleRowSource
    +
    +
    +
    +
    public class Csv
    +extends java.lang.Object
    +implements SimpleRowSource
    +
    A facility to read from and write to CSV (comma separated values) files. When + reading, the BOM (the byte-order-mark) character 0xfeff at the beginning of + the file is ignored.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      Csv() 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      voidclose() +
      INTERNAL
      +
      booleangetCaseSensitiveColumnNames() +
      Get the current case sensitive column names setting.
      +
      chargetEscapeCharacter() +
      Get the current escape character.
      +
      chargetFieldDelimiter() +
      Get the current field delimiter.
      +
      chargetFieldSeparatorRead() +
      Get the current field separator for reading.
      +
      java.lang.StringgetFieldSeparatorWrite() +
      Get the current field separator for writing.
      +
      chargetLineCommentCharacter() +
      Get the line comment character.
      +
      java.lang.StringgetLineSeparator() +
      Get the line separator used for writing.
      +
      java.lang.StringgetNullString() +
      Get the current null string.
      +
      booleangetPreserveWhitespace() +
      Whether whitespace in unquoted text is preserved.
      +
      booleangetWriteColumnHeader() +
      Whether the column header is written.
      +
      java.sql.ResultSetread(java.io.Reader reader, + java.lang.String[] colNames) +
      Reads CSV data from a reader and returns a result set.
      +
      java.sql.ResultSetread(java.lang.String inputFileName, + java.lang.String[] colNames, + java.lang.String charset) +
      Reads from the CSV file and returns a result set.
      +
      java.lang.Object[]readRow() +
      INTERNAL
      +
      voidreset() +
      INTERNAL
      +
      voidsetCaseSensitiveColumnNames(boolean caseSensitiveColumnNames) +
      Override the case sensitive column names setting.
      +
      voidsetEscapeCharacter(char escapeCharacter) +
      Set the escape character.
      +
      voidsetFieldDelimiter(char fieldDelimiter) +
      Set the field delimiter.
      +
      voidsetFieldSeparatorRead(char fieldSeparatorRead) +
      Override the field separator for reading.
      +
      voidsetFieldSeparatorWrite(java.lang.String fieldSeparatorWrite) +
      Override the field separator for writing.
      +
      voidsetLineCommentCharacter(char lineCommentCharacter) +
      Set the line comment character.
      +
      voidsetLineSeparator(java.lang.String lineSeparator) +
      Set the line separator used for writing.
      +
      voidsetNullString(java.lang.String nullString) +
      Set the value that represents NULL.
      +
      java.lang.StringsetOptions(java.lang.String options) +
      INTERNAL.
      +
      voidsetPreserveWhitespace(boolean value) +
      Enable or disable preserving whitespace in unquoted text.
      +
      voidsetWriteColumnHeader(boolean value) +
      Enable or disable writing the column header.
      +
      intwrite(java.sql.Connection conn, + java.lang.String outputFileName, + java.lang.String sql, + java.lang.String charset) +
      Writes the result set of a query to a file in the CSV format.
      +
      intwrite(java.lang.String outputFileName, + java.sql.ResultSet rs, + java.lang.String charset) +
      Writes the result set to a file in the CSV format.
      +
      intwrite(java.io.Writer writer, + java.sql.ResultSet rs) +
      Writes the result set to a file in the CSV format.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        Csv

        +
        public Csv()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        write

        +
        public int write(java.io.Writer writer,
        +                 java.sql.ResultSet rs)
        +          throws java.sql.SQLException
        +
        Writes the result set to a file in the CSV format.
        +
        +
        Parameters:
        +
        writer - the writer
        +
        rs - the result set
        +
        Returns:
        +
        the number of rows written
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        write

        +
        public int write(java.lang.String outputFileName,
        +                 java.sql.ResultSet rs,
        +                 java.lang.String charset)
        +          throws java.sql.SQLException
        +
        Writes the result set to a file in the CSV format. The result set is read + using the following loop: + +
        + while (rs.next()) {
        +     writeRow(row);
        + }
        + 
        +
        +
        Parameters:
        +
        outputFileName - the name of the csv file
        +
        rs - the result set - the result set must be positioned before the + first row.
        +
        charset - the charset or null to use the system default charset
        +
        Returns:
        +
        the number of rows written
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        write

        +
        public int write(java.sql.Connection conn,
        +                 java.lang.String outputFileName,
        +                 java.lang.String sql,
        +                 java.lang.String charset)
        +          throws java.sql.SQLException
        +
        Writes the result set of a query to a file in the CSV format.
        +
        +
        Parameters:
        +
        conn - the connection
        +
        outputFileName - the file name
        +
        sql - the query
        +
        charset - the charset or null to use the system default charset + (see system property file.encoding)
        +
        Returns:
        +
        the number of rows written
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        read

        +
        public java.sql.ResultSet read(java.lang.String inputFileName,
        +                               java.lang.String[] colNames,
        +                               java.lang.String charset)
        +                        throws java.sql.SQLException
        +
        Reads from the CSV file and returns a result set. The rows in the result + set are created on demand, that means the file is kept open until all + rows are read or the result set is closed. + + If the columns are read from the CSV file, then the following rules are + used: columns names that start with a letter or '_', and only + contain letters, '_', and digits, are considered case insensitive + and are converted to uppercase. Other column names are considered + case sensitive (that means they need to be quoted when accessed).
        +
        +
        Parameters:
        +
        inputFileName - the file name
        +
        colNames - or null if the column names should be read from the CSV + file
        +
        charset - the charset or null to use the system default charset
        +
        Returns:
        +
        the result set
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        read

        +
        public java.sql.ResultSet read(java.io.Reader reader,
        +                               java.lang.String[] colNames)
        +                        throws java.io.IOException
        +
        Reads CSV data from a reader and returns a result set. The rows in the + result set are created on demand, that means the reader is kept open + until all rows are read or the result set is closed.
        +
        +
        Parameters:
        +
        reader - the reader
        +
        colNames - or null if the column names should be read from the CSV + file
        +
        Returns:
        +
        the result set
        +
        Throws:
        +
        java.io.IOException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        readRow

        +
        public java.lang.Object[] readRow()
        +                           throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        readRow in interface SimpleRowSource
        +
        Returns:
        +
        the row or null
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        close

        +
        public void close()
        +
        INTERNAL
        +
        +
        Specified by:
        +
        close in interface SimpleRowSource
        +
        +
      • +
      + + + +
        +
      • +

        reset

        +
        public void reset()
        +           throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        reset in interface SimpleRowSource
        +
        Throws:
        +
        java.sql.SQLException - if this operation is not supported
        +
        +
      • +
      + + + +
        +
      • +

        setFieldSeparatorWrite

        +
        public void setFieldSeparatorWrite(java.lang.String fieldSeparatorWrite)
        +
        Override the field separator for writing. The default is ",".
        +
        +
        Parameters:
        +
        fieldSeparatorWrite - the field separator
        +
        +
      • +
      + + + +
        +
      • +

        getFieldSeparatorWrite

        +
        public java.lang.String getFieldSeparatorWrite()
        +
        Get the current field separator for writing.
        +
        +
        Returns:
        +
        the field separator
        +
        +
      • +
      + + + +
        +
      • +

        setCaseSensitiveColumnNames

        +
        public void setCaseSensitiveColumnNames(boolean caseSensitiveColumnNames)
        +
        Override the case sensitive column names setting. The default is false. + If enabled, the case of all column names is always preserved.
        +
        +
        Parameters:
        +
        caseSensitiveColumnNames - whether column names are case sensitive
        +
        +
      • +
      + + + +
        +
      • +

        getCaseSensitiveColumnNames

        +
        public boolean getCaseSensitiveColumnNames()
        +
        Get the current case sensitive column names setting.
        +
        +
        Returns:
        +
        whether column names are case sensitive
        +
        +
      • +
      + + + +
        +
      • +

        setFieldSeparatorRead

        +
        public void setFieldSeparatorRead(char fieldSeparatorRead)
        +
        Override the field separator for reading. The default is ','.
        +
        +
        Parameters:
        +
        fieldSeparatorRead - the field separator
        +
        +
      • +
      + + + +
        +
      • +

        getFieldSeparatorRead

        +
        public char getFieldSeparatorRead()
        +
        Get the current field separator for reading.
        +
        +
        Returns:
        +
        the field separator
        +
        +
      • +
      + + + +
        +
      • +

        setLineCommentCharacter

        +
        public void setLineCommentCharacter(char lineCommentCharacter)
        +
        Set the line comment character. The default is character code 0 (line + comments are disabled).
        +
        +
        Parameters:
        +
        lineCommentCharacter - the line comment character
        +
        +
      • +
      + + + +
        +
      • +

        getLineCommentCharacter

        +
        public char getLineCommentCharacter()
        +
        Get the line comment character.
        +
        +
        Returns:
        +
        the line comment character, or 0 if disabled
        +
        +
      • +
      + + + +
        +
      • +

        setFieldDelimiter

        +
        public void setFieldDelimiter(char fieldDelimiter)
        +
        Set the field delimiter. The default is " (a double quote). + The value 0 means no field delimiter is used.
        +
        +
        Parameters:
        +
        fieldDelimiter - the field delimiter
        +
        +
      • +
      + + + +
        +
      • +

        getFieldDelimiter

        +
        public char getFieldDelimiter()
        +
        Get the current field delimiter.
        +
        +
        Returns:
        +
        the field delimiter
        +
        +
      • +
      + + + +
        +
      • +

        setEscapeCharacter

        +
        public void setEscapeCharacter(char escapeCharacter)
        +
        Set the escape character. The escape character is used to escape the + field delimiter. This is needed if the data contains the field delimiter. + The default escape character is " (a double quote), which is the same as + the field delimiter. If the field delimiter and the escape character are + both " (double quote), and the data contains a double quote, then an + additional double quote is added. Example: +
        + Data: He said "Hello".
        + Escape character: "
        + Field delimiter: "
        + CSV file: "He said ""Hello""."
        + 
        + If the field delimiter is a double quote and the escape character is a + backslash, then escaping is done similar to Java (however, only the field + delimiter is escaped). Example: +
        + Data: He said "Hello".
        + Escape character: \
        + Field delimiter: "
        + CSV file: "He said \"Hello\"."
        + 
        + The value 0 means no escape character is used.
        +
        +
        Parameters:
        +
        escapeCharacter - the escape character
        +
        +
      • +
      + + + +
        +
      • +

        getEscapeCharacter

        +
        public char getEscapeCharacter()
        +
        Get the current escape character.
        +
        +
        Returns:
        +
        the escape character
        +
        +
      • +
      + + + +
        +
      • +

        setLineSeparator

        +
        public void setLineSeparator(java.lang.String lineSeparator)
        +
        Set the line separator used for writing. This is usually a line feed (\n + or \r\n depending on the system settings). The line separator is written + after each row (including the last row), so this option can include an + end-of-row marker if needed.
        +
        +
        Parameters:
        +
        lineSeparator - the line separator
        +
        +
      • +
      + + + +
        +
      • +

        getLineSeparator

        +
        public java.lang.String getLineSeparator()
        +
        Get the line separator used for writing.
        +
        +
        Returns:
        +
        the line separator
        +
        +
      • +
      + + + +
        +
      • +

        setNullString

        +
        public void setNullString(java.lang.String nullString)
        +
        Set the value that represents NULL. It is only used for non-delimited + values.
        +
        +
        Parameters:
        +
        nullString - the null
        +
        +
      • +
      + + + +
        +
      • +

        getNullString

        +
        public java.lang.String getNullString()
        +
        Get the current null string.
        +
        +
        Returns:
        +
        the null string.
        +
        +
      • +
      + + + +
        +
      • +

        setPreserveWhitespace

        +
        public void setPreserveWhitespace(boolean value)
        +
        Enable or disable preserving whitespace in unquoted text.
        +
        +
        Parameters:
        +
        value - the new value for the setting
        +
        +
      • +
      + + + +
        +
      • +

        getPreserveWhitespace

        +
        public boolean getPreserveWhitespace()
        +
        Whether whitespace in unquoted text is preserved.
        +
        +
        Returns:
        +
        the current value for the setting
        +
        +
      • +
      + + + +
        +
      • +

        setWriteColumnHeader

        +
        public void setWriteColumnHeader(boolean value)
        +
        Enable or disable writing the column header.
        +
        +
        Parameters:
        +
        value - the new value for the setting
        +
        +
      • +
      + + + +
        +
      • +

        getWriteColumnHeader

        +
        public boolean getWriteColumnHeader()
        +
        Whether the column header is written.
        +
        +
        Returns:
        +
        the current value for the setting
        +
        +
      • +
      + + + +
        +
      • +

        setOptions

        +
        public java.lang.String setOptions(java.lang.String options)
        +
        INTERNAL. + Parse and set the CSV options.
        +
        +
        Parameters:
        +
        options - the options
        +
        Returns:
        +
        the character set
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/tools/DeleteDbFiles.html b/h2/docs/javadoc/org/h2/tools/DeleteDbFiles.html new file mode 100644 index 0000000..db81e46 --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/DeleteDbFiles.html @@ -0,0 +1,372 @@ + + + + + +DeleteDbFiles + + + + + + + + + + + + +
+
org.h2.tools
+

Class DeleteDbFiles

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.util.Tool
    • +
    • +
        +
      • org.h2.tools.DeleteDbFiles
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public class DeleteDbFiles
    +extends org.h2.util.Tool
    +
    Deletes all files belonging to a database. + + The database must be closed before calling this tool.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.util.Tool

        +out
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      DeleteDbFiles() 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      static voidexecute(java.lang.String dir, + java.lang.String db, + boolean quiet) +
      Deletes the database files.
      +
      static voidmain(java.lang.String... args) +
      Options are case sensitive.
      +
      voidrunTool(java.lang.String... args) +
      Run the tool with the given output stream and arguments.
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.util.Tool

        +isOption, printNoDatabaseFilesFound, setOut, showUsage, showUsageAndThrowUnsupportedOption, throwUnsupportedOption
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        DeleteDbFiles

        +
        public DeleteDbFiles()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        main

        +
        public static void main(java.lang.String... args)
        +                 throws java.sql.SQLException
        +
        Options are case sensitive. + + + + + + + + + + +
        Supported options
        [-help] or [-?]Print the list of options
        [-dir <dir>]The directory (default: .)
        [-db <database>]The database name
        [-quiet]Do not print progress information
        +
        +
        Parameters:
        +
        args - the command line arguments
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        runTool

        +
        public void runTool(java.lang.String... args)
        +             throws java.sql.SQLException
        +
        Description copied from class: org.h2.util.Tool
        +
        Run the tool with the given output stream and arguments.
        +
        +
        Specified by:
        +
        runTool in class org.h2.util.Tool
        +
        Parameters:
        +
        args - the argument list
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        execute

        +
        public static void execute(java.lang.String dir,
        +                           java.lang.String db,
        +                           boolean quiet)
        +
        Deletes the database files.
        +
        +
        Parameters:
        +
        dir - the directory
        +
        db - the database name (null for all databases)
        +
        quiet - don't print progress information
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/tools/GUIConsole.html b/h2/docs/javadoc/org/h2/tools/GUIConsole.html new file mode 100644 index 0000000..544de6b --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/GUIConsole.html @@ -0,0 +1,585 @@ + + + + + +GUIConsole + + + + + + + + + + + + +
+
org.h2.tools
+

Class GUIConsole

+
+
+
    +
  • java.lang.Object
  • +
  • + +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.awt.event.ActionListener, java.awt.event.MouseListener, java.awt.event.WindowListener, java.util.EventListener, org.h2.server.ShutdownHandler
    +
    +
    +
    +
    public class GUIConsole
    +extends Console
    +implements java.awt.event.ActionListener, java.awt.event.MouseListener, java.awt.event.WindowListener
    +
    Console for environments with AWT support.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.util.Tool

        +out
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      GUIConsole() 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      voidactionPerformed(java.awt.event.ActionEvent e) +
      INTERNAL
      +
      voidmouseClicked(java.awt.event.MouseEvent e) +
      INTERNAL
      +
      voidmouseEntered(java.awt.event.MouseEvent e) +
      INTERNAL
      +
      voidmouseExited(java.awt.event.MouseEvent e) +
      INTERNAL
      +
      voidmousePressed(java.awt.event.MouseEvent e) +
      INTERNAL
      +
      voidmouseReleased(java.awt.event.MouseEvent e) +
      INTERNAL
      +
      voidshutdown() +
      INTERNAL.
      +
      voidwindowActivated(java.awt.event.WindowEvent e) +
      INTERNAL
      +
      voidwindowClosed(java.awt.event.WindowEvent e) +
      INTERNAL
      +
      voidwindowClosing(java.awt.event.WindowEvent e) +
      INTERNAL
      +
      voidwindowDeactivated(java.awt.event.WindowEvent e) +
      INTERNAL
      +
      voidwindowDeiconified(java.awt.event.WindowEvent e) +
      INTERNAL
      +
      voidwindowIconified(java.awt.event.WindowEvent e) +
      INTERNAL
      +
      voidwindowOpened(java.awt.event.WindowEvent e) +
      INTERNAL
      +
      + +
        +
      • + + +

        Methods inherited from class org.h2.util.Tool

        +isOption, printNoDatabaseFilesFound, setOut, showUsage, showUsageAndThrowUnsupportedOption, throwUnsupportedOption
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        GUIConsole

        +
        public GUIConsole()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        shutdown

        +
        public void shutdown()
        +
        Description copied from class: Console
        +
        INTERNAL. + Stop all servers that were started using the console.
        +
        +
        Specified by:
        +
        shutdown in interface org.h2.server.ShutdownHandler
        +
        Overrides:
        +
        shutdown in class Console
        +
        +
      • +
      + + + +
        +
      • +

        actionPerformed

        +
        public void actionPerformed(java.awt.event.ActionEvent e)
        +
        INTERNAL
        +
        +
        Specified by:
        +
        actionPerformed in interface java.awt.event.ActionListener
        +
        +
      • +
      + + + +
        +
      • +

        mouseClicked

        +
        public void mouseClicked(java.awt.event.MouseEvent e)
        +
        INTERNAL
        +
        +
        Specified by:
        +
        mouseClicked in interface java.awt.event.MouseListener
        +
        +
      • +
      + + + +
        +
      • +

        mouseEntered

        +
        public void mouseEntered(java.awt.event.MouseEvent e)
        +
        INTERNAL
        +
        +
        Specified by:
        +
        mouseEntered in interface java.awt.event.MouseListener
        +
        +
      • +
      + + + +
        +
      • +

        mouseExited

        +
        public void mouseExited(java.awt.event.MouseEvent e)
        +
        INTERNAL
        +
        +
        Specified by:
        +
        mouseExited in interface java.awt.event.MouseListener
        +
        +
      • +
      + + + +
        +
      • +

        mousePressed

        +
        public void mousePressed(java.awt.event.MouseEvent e)
        +
        INTERNAL
        +
        +
        Specified by:
        +
        mousePressed in interface java.awt.event.MouseListener
        +
        +
      • +
      + + + +
        +
      • +

        mouseReleased

        +
        public void mouseReleased(java.awt.event.MouseEvent e)
        +
        INTERNAL
        +
        +
        Specified by:
        +
        mouseReleased in interface java.awt.event.MouseListener
        +
        +
      • +
      + + + +
        +
      • +

        windowClosing

        +
        public void windowClosing(java.awt.event.WindowEvent e)
        +
        INTERNAL
        +
        +
        Specified by:
        +
        windowClosing in interface java.awt.event.WindowListener
        +
        +
      • +
      + + + +
        +
      • +

        windowActivated

        +
        public void windowActivated(java.awt.event.WindowEvent e)
        +
        INTERNAL
        +
        +
        Specified by:
        +
        windowActivated in interface java.awt.event.WindowListener
        +
        +
      • +
      + + + +
        +
      • +

        windowClosed

        +
        public void windowClosed(java.awt.event.WindowEvent e)
        +
        INTERNAL
        +
        +
        Specified by:
        +
        windowClosed in interface java.awt.event.WindowListener
        +
        +
      • +
      + + + +
        +
      • +

        windowDeactivated

        +
        public void windowDeactivated(java.awt.event.WindowEvent e)
        +
        INTERNAL
        +
        +
        Specified by:
        +
        windowDeactivated in interface java.awt.event.WindowListener
        +
        +
      • +
      + + + +
        +
      • +

        windowDeiconified

        +
        public void windowDeiconified(java.awt.event.WindowEvent e)
        +
        INTERNAL
        +
        +
        Specified by:
        +
        windowDeiconified in interface java.awt.event.WindowListener
        +
        +
      • +
      + + + +
        +
      • +

        windowIconified

        +
        public void windowIconified(java.awt.event.WindowEvent e)
        +
        INTERNAL
        +
        +
        Specified by:
        +
        windowIconified in interface java.awt.event.WindowListener
        +
        +
      • +
      + + + +
        +
      • +

        windowOpened

        +
        public void windowOpened(java.awt.event.WindowEvent e)
        +
        INTERNAL
        +
        +
        Specified by:
        +
        windowOpened in interface java.awt.event.WindowListener
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/tools/MultiDimension.html b/h2/docs/javadoc/org/h2/tools/MultiDimension.html new file mode 100644 index 0000000..940441c --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/MultiDimension.html @@ -0,0 +1,513 @@ + + + + + +MultiDimension + + + + + + + + + + + + +
+
org.h2.tools
+

Class MultiDimension

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.tools.MultiDimension
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.util.Comparator<long[]>
    +
    +
    +
    +
    public class MultiDimension
    +extends java.lang.Object
    +implements java.util.Comparator<long[]>
    +
    A tool to help an application execute multi-dimensional range queries. + The algorithm used is database independent, the only requirement + is that the engine supports a range index (for example b-tree).
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + + + +
      Constructors 
      ModifierConstructor and Description
      protected MultiDimension() 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      intcompare(long[] a, + long[] b) 
      intdeinterleave(int dimensions, + long scalar, + int dim) +
      Gets one of the original multi-dimensional values from a scalar value.
      +
      java.lang.StringgeneratePreparedQuery(java.lang.String table, + java.lang.String scalarColumn, + java.lang.String[] columns) +
      Generates an optimized multi-dimensional range query.
      +
      static MultiDimensiongetInstance() +
      Get the singleton.
      +
      intgetMaxValue(int dimensions) +
      Get the maximum value for the given dimension count.
      +
      java.sql.ResultSetgetResult(java.sql.PreparedStatement prep, + int[] min, + int[] max) +
      Executes a prepared query that was generated using generatePreparedQuery.
      +
      longinterleave(int... values) +
      Convert the multi-dimensional value into a one-dimensional (scalar) + value.
      +
      longinterleave(int x, + int y) +
      Convert the two-dimensional value into a one-dimensional (scalar) value.
      +
      intnormalize(int dimensions, + double value, + double min, + double max) +
      Normalize a value so that it is between the minimum and maximum for the + given number of dimensions.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
        +
      • + + +

        Methods inherited from interface java.util.Comparator

        +comparing, comparing, comparingDouble, comparingInt, comparingLong, equals, naturalOrder, nullsFirst, nullsLast, reversed, reverseOrder, thenComparing, thenComparing, thenComparing, thenComparingDouble, thenComparingInt, thenComparingLong
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        MultiDimension

        +
        protected MultiDimension()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getInstance

        +
        public static MultiDimension getInstance()
        +
        Get the singleton.
        +
        +
        Returns:
        +
        the singleton
        +
        +
      • +
      + + + +
        +
      • +

        normalize

        +
        public int normalize(int dimensions,
        +                     double value,
        +                     double min,
        +                     double max)
        +
        Normalize a value so that it is between the minimum and maximum for the + given number of dimensions.
        +
        +
        Parameters:
        +
        dimensions - the number of dimensions
        +
        value - the value (must be in the range min..max)
        +
        min - the minimum value
        +
        max - the maximum value (must be larger than min)
        +
        Returns:
        +
        the normalized value in the range 0..getMaxValue(dimensions)
        +
        +
      • +
      + + + +
        +
      • +

        getMaxValue

        +
        public int getMaxValue(int dimensions)
        +
        Get the maximum value for the given dimension count. For two dimensions, + each value must contain at most 32 bit, for 3: 21 bit, 4: 16 bit, 5: 12 + bit, 6: 10 bit, 7: 9 bit, 8: 8 bit.
        +
        +
        Parameters:
        +
        dimensions - the number of dimensions
        +
        Returns:
        +
        the maximum value
        +
        +
      • +
      + + + +
        +
      • +

        interleave

        +
        public long interleave(int... values)
        +
        Convert the multi-dimensional value into a one-dimensional (scalar) + value. This is done by interleaving the bits of the values. Each values + must be between 0 (including) and the maximum value for the given number + of dimensions (getMaxValue, excluding). To normalize values to this + range, use the normalize function.
        +
        +
        Parameters:
        +
        values - the multi-dimensional value
        +
        Returns:
        +
        the scalar value
        +
        +
      • +
      + + + +
        +
      • +

        interleave

        +
        public long interleave(int x,
        +                       int y)
        +
        Convert the two-dimensional value into a one-dimensional (scalar) value. + This is done by interleaving the bits of the values. + Each values must be between 0 (including) and the maximum value + for the given number of dimensions (getMaxValue, excluding). + To normalize values to this range, use the normalize function.
        +
        +
        Parameters:
        +
        x - the value of the first dimension, normalized
        +
        y - the value of the second dimension, normalized
        +
        Returns:
        +
        the scalar value
        +
        +
      • +
      + + + +
        +
      • +

        deinterleave

        +
        public int deinterleave(int dimensions,
        +                        long scalar,
        +                        int dim)
        +
        Gets one of the original multi-dimensional values from a scalar value.
        +
        +
        Parameters:
        +
        dimensions - the number of dimensions
        +
        scalar - the scalar value
        +
        dim - the dimension of the returned value (starting from 0)
        +
        Returns:
        +
        the value
        +
        +
      • +
      + + + +
        +
      • +

        generatePreparedQuery

        +
        public java.lang.String generatePreparedQuery(java.lang.String table,
        +                                              java.lang.String scalarColumn,
        +                                              java.lang.String[] columns)
        +
        Generates an optimized multi-dimensional range query. The query contains + parameters. It can only be used with the H2 database.
        +
        +
        Parameters:
        +
        table - the table name
        +
        columns - the list of columns
        +
        scalarColumn - the column name of the computed scalar column
        +
        Returns:
        +
        the query
        +
        +
      • +
      + + + +
        +
      • +

        getResult

        +
        public java.sql.ResultSet getResult(java.sql.PreparedStatement prep,
        +                                    int[] min,
        +                                    int[] max)
        +                             throws java.sql.SQLException
        +
        Executes a prepared query that was generated using generatePreparedQuery.
        +
        +
        Parameters:
        +
        prep - the prepared statement
        +
        min - the lower values
        +
        max - the upper values
        +
        Returns:
        +
        the result set
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        compare

        +
        public int compare(long[] a,
        +                   long[] b)
        +
        +
        Specified by:
        +
        compare in interface java.util.Comparator<long[]>
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/tools/Recover.html b/h2/docs/javadoc/org/h2/tools/Recover.html new file mode 100644 index 0000000..d5181ca --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/Recover.html @@ -0,0 +1,710 @@ + + + + + +Recover + + + + + + + + + + + + +
+
org.h2.tools
+

Class Recover

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.util.Tool
    • +
    • +
        +
      • org.h2.tools.Recover
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    org.h2.store.DataHandler
    +
    +
    +
    +
    public class Recover
    +extends org.h2.util.Tool
    +implements org.h2.store.DataHandler
    +
    Helps recovering a corrupted database.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.util.Tool

        +out
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      Recover() 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      voidcheckPowerOff() +
      INTERNAL
      +
      voidcheckWritingAllowed() +
      INTERNAL
      +
      static voidexecute(java.lang.String dir, + java.lang.String db) +
      Dumps the contents of a database to a SQL script file.
      +
      org.h2.value.CompareModegetCompareMode() +
      Return compare mode.
      +
      java.lang.StringgetDatabasePath() +
      INTERNAL
      +
      org.h2.util.SmallLRUCache<java.lang.String,java.lang.String[]>getLobFileListCache() +
      INTERNAL
      +
      org.h2.store.LobStorageInterfacegetLobStorage() +
      INTERNAL
      +
      java.lang.ObjectgetLobSyncObject() +
      INTERNAL
      +
      intgetMaxLengthInplaceLob() +
      INTERNAL
      +
      org.h2.util.TempFileDeletergetTempFileDeleter() +
      INTERNAL
      +
      static voidmain(java.lang.String... args) +
      Options are case sensitive.
      +
      org.h2.store.FileStoreopenFile(java.lang.String name, + java.lang.String mode, + boolean mustExist) +
      INTERNAL
      +
      static java.io.InputStreamreadBlobMap(java.sql.Connection conn, + long lobId, + long precision) +
      INTERNAL
      +
      static java.io.ReaderreadClobMap(java.sql.Connection conn, + long lobId, + long precision) +
      INTERNAL
      +
      intreadLob(long lobId, + byte[] hmac, + long offset, + byte[] buff, + int off, + int length) +
      INTERNAL
      +
      voidrunTool(java.lang.String... args) +
      Dumps the contents of a database file to a human readable text file.
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.util.Tool

        +isOption, printNoDatabaseFilesFound, setOut, showUsage, showUsageAndThrowUnsupportedOption, throwUnsupportedOption
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        Recover

        +
        public Recover()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        main

        +
        public static void main(java.lang.String... args)
        +                 throws java.sql.SQLException
        +
        Options are case sensitive. + + + + + + + + + + + + +
        Supported options
        [-help] or [-?]Print the list of options
        [-dir <dir>]The directory (default: .)
        [-db <database>]The database name (all databases if not set)
        [-trace]Print additional trace information
        [-transactionLog]Print the transaction log
        + Encrypted databases need to be decrypted first.
        +
        +
        Parameters:
        +
        args - the command line arguments
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        runTool

        +
        public void runTool(java.lang.String... args)
        +             throws java.sql.SQLException
        +
        Dumps the contents of a database file to a human readable text file. This + text file can be used to recover most of the data. This tool does not + open the database and can be used even if the database files are + corrupted. A database can get corrupted if there is a bug in the database + engine or file system software, or if an application writes into the + database file that doesn't understand the file format, or if there is + a hardware problem.
        +
        +
        Specified by:
        +
        runTool in class org.h2.util.Tool
        +
        Parameters:
        +
        args - the command line arguments
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        readBlobMap

        +
        public static java.io.InputStream readBlobMap(java.sql.Connection conn,
        +                                              long lobId,
        +                                              long precision)
        +                                       throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Parameters:
        +
        conn - to use
        +
        lobId - id of the LOB stream
        +
        precision - not used
        +
        Returns:
        +
        InputStream to read LOB content from
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        readClobMap

        +
        public static java.io.Reader readClobMap(java.sql.Connection conn,
        +                                         long lobId,
        +                                         long precision)
        +                                  throws java.lang.Exception
        +
        INTERNAL
        +
        +
        Parameters:
        +
        conn - to use
        +
        lobId - id of the LOB stream
        +
        precision - not used
        +
        Returns:
        +
        Reader to read LOB content from
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        java.lang.Exception
        +
        +
      • +
      + + + +
        +
      • +

        execute

        +
        public static void execute(java.lang.String dir,
        +                           java.lang.String db)
        +                    throws java.sql.SQLException
        +
        Dumps the contents of a database to a SQL script file.
        +
        +
        Parameters:
        +
        dir - the directory
        +
        db - the database name (null for all databases)
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        getDatabasePath

        +
        public java.lang.String getDatabasePath()
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getDatabasePath in interface org.h2.store.DataHandler
        +
        Returns:
        +
        the database path
        +
        +
      • +
      + + + +
        +
      • +

        openFile

        +
        public org.h2.store.FileStore openFile(java.lang.String name,
        +                                       java.lang.String mode,
        +                                       boolean mustExist)
        +
        INTERNAL
        +
        +
        Specified by:
        +
        openFile in interface org.h2.store.DataHandler
        +
        Parameters:
        +
        name - the file name
        +
        mode - the mode
        +
        mustExist - whether the file must already exist
        +
        Returns:
        +
        the file
        +
        +
      • +
      + + + +
        +
      • +

        checkPowerOff

        +
        public void checkPowerOff()
        +
        INTERNAL
        +
        +
        Specified by:
        +
        checkPowerOff in interface org.h2.store.DataHandler
        +
        +
      • +
      + + + +
        +
      • +

        checkWritingAllowed

        +
        public void checkWritingAllowed()
        +
        INTERNAL
        +
        +
        Specified by:
        +
        checkWritingAllowed in interface org.h2.store.DataHandler
        +
        +
      • +
      + + + +
        +
      • +

        getMaxLengthInplaceLob

        +
        public int getMaxLengthInplaceLob()
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getMaxLengthInplaceLob in interface org.h2.store.DataHandler
        +
        Returns:
        +
        the maximum size
        +
        +
      • +
      + + + +
        +
      • +

        getLobSyncObject

        +
        public java.lang.Object getLobSyncObject()
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getLobSyncObject in interface org.h2.store.DataHandler
        +
        Returns:
        +
        the synchronization object
        +
        +
      • +
      + + + +
        +
      • +

        getLobFileListCache

        +
        public org.h2.util.SmallLRUCache<java.lang.String,java.lang.String[]> getLobFileListCache()
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getLobFileListCache in interface org.h2.store.DataHandler
        +
        Returns:
        +
        the cache or null
        +
        +
      • +
      + + + +
        +
      • +

        getTempFileDeleter

        +
        public org.h2.util.TempFileDeleter getTempFileDeleter()
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getTempFileDeleter in interface org.h2.store.DataHandler
        +
        Returns:
        +
        the temp file deleter
        +
        +
      • +
      + + + +
        +
      • +

        getLobStorage

        +
        public org.h2.store.LobStorageInterface getLobStorage()
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getLobStorage in interface org.h2.store.DataHandler
        +
        Returns:
        +
        the lob storage mechanism
        +
        +
      • +
      + + + +
        +
      • +

        readLob

        +
        public int readLob(long lobId,
        +                   byte[] hmac,
        +                   long offset,
        +                   byte[] buff,
        +                   int off,
        +                   int length)
        +
        INTERNAL
        +
        +
        Specified by:
        +
        readLob in interface org.h2.store.DataHandler
        +
        Parameters:
        +
        lobId - the lob id
        +
        hmac - the message authentication code
        +
        offset - the offset within the lob
        +
        buff - the target buffer
        +
        off - the offset within the target buffer
        +
        length - the number of bytes to read
        +
        Returns:
        +
        the number of bytes read
        +
        +
      • +
      + + + +
        +
      • +

        getCompareMode

        +
        public org.h2.value.CompareMode getCompareMode()
        +
        Description copied from interface: org.h2.store.DataHandler
        +
        Return compare mode.
        +
        +
        Specified by:
        +
        getCompareMode in interface org.h2.store.DataHandler
        +
        Returns:
        +
        Compare mode.
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/tools/Restore.html b/h2/docs/javadoc/org/h2/tools/Restore.html new file mode 100644 index 0000000..faaebf5 --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/Restore.html @@ -0,0 +1,374 @@ + + + + + +Restore + + + + + + + + + + + + +
+
org.h2.tools
+

Class Restore

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.util.Tool
    • +
    • +
        +
      • org.h2.tools.Restore
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public class Restore
    +extends org.h2.util.Tool
    +
    Restores a H2 database by extracting the database files from a .zip file.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.util.Tool

        +out
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      Restore() 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      static voidexecute(java.lang.String zipFileName, + java.lang.String directory, + java.lang.String db) +
      Restores database files.
      +
      static voidmain(java.lang.String... args) +
      Options are case sensitive.
      +
      voidrunTool(java.lang.String... args) +
      Run the tool with the given output stream and arguments.
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.util.Tool

        +isOption, printNoDatabaseFilesFound, setOut, showUsage, showUsageAndThrowUnsupportedOption, throwUnsupportedOption
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        Restore

        +
        public Restore()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        main

        +
        public static void main(java.lang.String... args)
        +                 throws java.sql.SQLException
        +
        Options are case sensitive. Supported options + + + + + + + + + + + + +
        Supported options
        [-help] or [-?]Print the list of options
        [-file <filename>]The source file name (default: backup.zip)
        [-dir <dir>]The target directory (default: .)
        [-db <database>]The target database name (as stored if not set)
        [-quiet]Do not print progress information
        +
        +
        Parameters:
        +
        args - the command line arguments
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        runTool

        +
        public void runTool(java.lang.String... args)
        +             throws java.sql.SQLException
        +
        Description copied from class: org.h2.util.Tool
        +
        Run the tool with the given output stream and arguments.
        +
        +
        Specified by:
        +
        runTool in class org.h2.util.Tool
        +
        Parameters:
        +
        args - the argument list
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        execute

        +
        public static void execute(java.lang.String zipFileName,
        +                           java.lang.String directory,
        +                           java.lang.String db)
        +
        Restores database files.
        +
        +
        Parameters:
        +
        zipFileName - the name of the backup file
        +
        directory - the directory name
        +
        db - the database name (null for all databases)
        +
        Throws:
        +
        org.h2.message.DbException - if there is an IOException
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/tools/RunScript.html b/h2/docs/javadoc/org/h2/tools/RunScript.html new file mode 100644 index 0000000..166667f --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/RunScript.html @@ -0,0 +1,435 @@ + + + + + +RunScript + + + + + + + + + + + + +
+
org.h2.tools
+

Class RunScript

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.util.Tool
    • +
    • +
        +
      • org.h2.tools.RunScript
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public class RunScript
    +extends org.h2.util.Tool
    +
    Runs a SQL script against a database.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.util.Tool

        +out
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      RunScript() 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      static java.sql.ResultSetexecute(java.sql.Connection conn, + java.io.Reader reader) +
      Executes the SQL commands read from the reader against a database.
      +
      static voidexecute(java.lang.String url, + java.lang.String user, + java.lang.String password, + java.lang.String fileName, + java.nio.charset.Charset charset, + boolean continueOnError) +
      Executes the SQL commands in a script file against a database.
      +
      static voidmain(java.lang.String... args) +
      Options are case sensitive.
      +
      voidrunTool(java.lang.String... args) +
      Executes the contents of a SQL script file against a database.
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.util.Tool

        +isOption, printNoDatabaseFilesFound, setOut, showUsage, showUsageAndThrowUnsupportedOption, throwUnsupportedOption
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        RunScript

        +
        public RunScript()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        main

        +
        public static void main(java.lang.String... args)
        +                 throws java.sql.SQLException
        +
        Options are case sensitive. + + + + + + + + + + + + + + + + + + + + + + +
        Supported options
        [-help] or [-?]Print the list of options
        [-url "<url>"]The database URL (jdbc:...)
        [-user <user>]The user name (default: sa)
        [-password <pwd>]The password
        [-script <file>]The script file to run (default: backup.sql)
        [-driver <class>]The JDBC driver class to use (not required in most cases)
        [-showResults]Show the statements and the results of queries
        [-checkResults]Check if the query results match the expected results
        [-continueOnError]Continue even if the script contains errors
        [-options ...]RUNSCRIPT options (embedded H2; -*Results not supported)
        +
        +
        Parameters:
        +
        args - the command line arguments
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        runTool

        +
        public void runTool(java.lang.String... args)
        +             throws java.sql.SQLException
        +
        Executes the contents of a SQL script file against a database. + This tool is usually used to create a database from script. + It can also be used to analyze performance problems by running + the tool using Java profiler settings such as: +
        + java -Xrunhprof:cpu=samples,depth=16 ...
        + 
        + To include local files when using remote databases, use the special + syntax: +
        + @INCLUDE fileName
        + 
        + This syntax is only supported by this tool. Embedded RUNSCRIPT SQL + statements will be executed by the database.
        +
        +
        Specified by:
        +
        runTool in class org.h2.util.Tool
        +
        Parameters:
        +
        args - the command line arguments
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        execute

        +
        public static java.sql.ResultSet execute(java.sql.Connection conn,
        +                                         java.io.Reader reader)
        +                                  throws java.sql.SQLException
        +
        Executes the SQL commands read from the reader against a database.
        +
        +
        Parameters:
        +
        conn - the connection to a database
        +
        reader - the reader
        +
        Returns:
        +
        the last result set
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        execute

        +
        public static void execute(java.lang.String url,
        +                           java.lang.String user,
        +                           java.lang.String password,
        +                           java.lang.String fileName,
        +                           java.nio.charset.Charset charset,
        +                           boolean continueOnError)
        +                    throws java.sql.SQLException
        +
        Executes the SQL commands in a script file against a database.
        +
        +
        Parameters:
        +
        url - the database URL
        +
        user - the user name
        +
        password - the password
        +
        fileName - the script file
        +
        charset - the character set or null for UTF-8
        +
        continueOnError - if execution should be continued if an error + occurs
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/tools/Script.html b/h2/docs/javadoc/org/h2/tools/Script.html new file mode 100644 index 0000000..cc051c1 --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/Script.html @@ -0,0 +1,421 @@ + + + + + +Script + + + + + + + + + + + + +
+
org.h2.tools
+

Class Script

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.util.Tool
    • +
    • +
        +
      • org.h2.tools.Script
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public class Script
    +extends org.h2.util.Tool
    +
    Creates a SQL script file by extracting the schema and data of a database.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.util.Tool

        +out
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      Script() 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      static voidmain(java.lang.String... args) +
      Options are case sensitive.
      +
      static voidprocess(java.sql.Connection conn, + java.lang.String fileName, + java.lang.String options1, + java.lang.String options2) +
      Backs up a database to a stream.
      +
      static voidprocess(java.lang.String url, + java.lang.String user, + java.lang.String password, + java.lang.String fileName, + java.lang.String options1, + java.lang.String options2) +
      Backs up a database to a stream.
      +
      voidrunTool(java.lang.String... args) +
      Run the tool with the given output stream and arguments.
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.util.Tool

        +isOption, printNoDatabaseFilesFound, setOut, showUsage, showUsageAndThrowUnsupportedOption, throwUnsupportedOption
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        Script

        +
        public Script()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        main

        +
        public static void main(java.lang.String... args)
        +                 throws java.sql.SQLException
        +
        Options are case sensitive. + + + + + + + + + + + + + + + + +
        Supported options
        [-help] or [-?]Print the list of options
        [-url "<url>"]The database URL (jdbc:...)
        [-user <user>]The user name (default: sa)
        [-password <pwd>]The password
        [-script <file>]The target script file name (default: backup.sql)
        [-options ...]A list of options (only for embedded H2, see SCRIPT)
        [-quiet]Do not print progress information
        +
        +
        Parameters:
        +
        args - the command line arguments
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        runTool

        +
        public void runTool(java.lang.String... args)
        +             throws java.sql.SQLException
        +
        Description copied from class: org.h2.util.Tool
        +
        Run the tool with the given output stream and arguments.
        +
        +
        Specified by:
        +
        runTool in class org.h2.util.Tool
        +
        Parameters:
        +
        args - the argument list
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        process

        +
        public static void process(java.lang.String url,
        +                           java.lang.String user,
        +                           java.lang.String password,
        +                           java.lang.String fileName,
        +                           java.lang.String options1,
        +                           java.lang.String options2)
        +                    throws java.sql.SQLException
        +
        Backs up a database to a stream.
        +
        +
        Parameters:
        +
        url - the database URL
        +
        user - the user name
        +
        password - the password
        +
        fileName - the target file name
        +
        options1 - the options before the file name (may be an empty string)
        +
        options2 - the options after the file name (may be an empty string)
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        process

        +
        public static void process(java.sql.Connection conn,
        +                           java.lang.String fileName,
        +                           java.lang.String options1,
        +                           java.lang.String options2)
        +                    throws java.sql.SQLException
        +
        Backs up a database to a stream. The stream is not closed. + The connection is not closed.
        +
        +
        Parameters:
        +
        conn - the connection
        +
        fileName - the target file name
        +
        options1 - the options before the file name
        +
        options2 - the options after the file name
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/tools/Server.html b/h2/docs/javadoc/org/h2/tools/Server.html new file mode 100644 index 0000000..fd23234 --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/Server.html @@ -0,0 +1,860 @@ + + + + + +Server + + + + + + + + + + + + +
+
org.h2.tools
+

Class Server

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.util.Tool
    • +
    • +
        +
      • org.h2.tools.Server
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.lang.Runnable, org.h2.server.ShutdownHandler
    +
    +
    +
    +
    public class Server
    +extends org.h2.util.Tool
    +implements java.lang.Runnable, org.h2.server.ShutdownHandler
    +
    Starts the H2 Console (web-) server, TCP, and PG server.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.util.Tool

        +out
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + + + + +
      Constructors 
      Constructor and Description
      Server() 
      Server(org.h2.server.Service service, + java.lang.String... args) +
      Create a new server for the given service.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      static ServercreatePgServer(java.lang.String... args) +
      Create a new PG server, but does not start it yet.
      +
      static ServercreateTcpServer(java.lang.String... args) +
      Create a new TCP server, but does not start it yet.
      +
      static ServercreateWebServer(java.lang.String... args) +
      Create a new web server, but does not start it yet.
      +
      intgetPort() +
      Gets the port this server is listening on.
      +
      org.h2.server.ServicegetService() +
      Get the service attached to this server.
      +
      java.lang.StringgetStatus() +
      Get the status of this server.
      +
      java.lang.StringgetURL() +
      Gets the URL of this server.
      +
      booleanisRunning(boolean traceError) +
      Checks if the server is running.
      +
      static voidmain(java.lang.String... args) +
      When running without options, -tcp, -web, -browser and -pg are started.
      +
      static voidopenBrowser(java.lang.String url) +
      Open a new browser tab or window with the given URL.
      +
      voidrun() +
      INTERNAL
      +
      voidrunTool(java.lang.String... args) +
      Run the tool with the given output stream and arguments.
      +
      voidsetShutdownHandler(org.h2.server.ShutdownHandler shutdownHandler) +
      INTERNAL
      +
      voidshutdown() +
      INTERNAL
      +
      static voidshutdownTcpServer(java.lang.String url, + java.lang.String password, + boolean force, + boolean all) +
      Shutdown one or all TCP server.
      +
      Serverstart() +
      Tries to start the server.
      +
      static voidstartWebServer(java.sql.Connection conn) +
      Start a web server and a browser that uses the given connection.
      +
      static voidstartWebServer(java.sql.Connection conn, + boolean ignoreProperties) +
      Start a web server and a browser that uses the given connection.
      +
      voidstop() +
      Stops the server.
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.util.Tool

        +isOption, printNoDatabaseFilesFound, setOut, showUsage, showUsageAndThrowUnsupportedOption, throwUnsupportedOption
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        Server

        +
        public Server()
        +
      • +
      + + + +
        +
      • +

        Server

        +
        public Server(org.h2.server.Service service,
        +              java.lang.String... args)
        +       throws java.sql.SQLException
        +
        Create a new server for the given service.
        +
        +
        Parameters:
        +
        service - the service
        +
        args - the command line arguments
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        main

        +
        public static void main(java.lang.String... args)
        +                 throws java.sql.SQLException
        +
        When running without options, -tcp, -web, -browser and -pg are started. + + Options are case sensitive. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        Supported options
        [-help] or [-?]Print the list of options
        [-web]Start the web server with the H2 Console
        [-webAllowOthers]Allow other computers to connect - see below
        [-webExternalNames <names>]The comma-separated list of external names and IP addresses of this server, + used together with -webAllowOthers
        [-webDaemon]Use a daemon thread
        [-webPort <port>]The port (default: 8082)
        [-webSSL]Use encrypted (HTTPS) connections
        [-webAdminPassword]Password of DB Console administrator
        [-browser]Start a browser connecting to the web server
        [-tcp]Start the TCP server
        [-tcpAllowOthers]Allow other computers to connect - see below
        [-tcpDaemon]Use a daemon thread
        [-tcpPort <port>]The port (default: 9092)
        [-tcpSSL]Use encrypted (SSL) connections
        [-tcpPassword <pwd>]The password for shutting down a TCP server
        [-tcpShutdown "<url>"]Stop the TCP server; example: tcp://localhost
        [-tcpShutdownForce]Do not wait until all connections are closed
        [-pg]Start the PG server
        [-pgAllowOthers]Allow other computers to connect - see below
        [-pgDaemon]Use a daemon thread
        [-pgPort <port>]The port (default: 5435)
        [-properties "<dir>"]Server properties (default: ~, disable: null)
        [-baseDir <dir>]The base directory for H2 databases (all servers)
        [-ifExists]Only existing databases may be opened (all servers)
        [-ifNotExists]Databases are created when accessed
        [-trace]Print additional trace information (all servers)
        [-key <from> <to>]Allows to map a database name to another (all servers)
        + The options -xAllowOthers are potentially risky. + + For details, see Advanced Topics / Protection against Remote Access.
        +
        +
        Parameters:
        +
        args - the command line arguments
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        runTool

        +
        public void runTool(java.lang.String... args)
        +             throws java.sql.SQLException
        +
        Description copied from class: org.h2.util.Tool
        +
        Run the tool with the given output stream and arguments.
        +
        +
        Specified by:
        +
        runTool in class org.h2.util.Tool
        +
        Parameters:
        +
        args - the argument list
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        shutdownTcpServer

        +
        public static void shutdownTcpServer(java.lang.String url,
        +                                     java.lang.String password,
        +                                     boolean force,
        +                                     boolean all)
        +                              throws java.sql.SQLException
        +
        Shutdown one or all TCP server. If force is set to false, the server will + not allow new connections, but not kill existing connections, instead it + will stop if the last connection is closed. If force is set to true, + existing connections are killed. After calling the method with + force=false, it is not possible to call it again with force=true because + new connections are not allowed. Example: + +
        + Server.shutdownTcpServer("tcp://localhost:9094",
        +         password, true, false);
        + 
        +
        +
        Parameters:
        +
        url - example: tcp://localhost:9094
        +
        password - the password to use ("" for no password)
        +
        force - the shutdown (don't wait)
        +
        all - whether all TCP servers that are running in the JVM should be + stopped
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        getStatus

        +
        public java.lang.String getStatus()
        +
        Get the status of this server.
        +
        +
        Returns:
        +
        the status
        +
        +
      • +
      + + + +
        +
      • +

        createWebServer

        +
        public static Server createWebServer(java.lang.String... args)
        +                              throws java.sql.SQLException
        +
        Create a new web server, but does not start it yet. Example: + +
        + Server server = Server.createWebServer("-trace").start();
        + 
        + Supported options are: + -webPort, -webSSL, -webAllowOthers, -webDaemon, + -trace, -ifExists, -ifNotExists, -baseDir, -properties. + See the main method for details.
        +
        +
        Parameters:
        +
        args - the argument list
        +
        Returns:
        +
        the server
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        createTcpServer

        +
        public static Server createTcpServer(java.lang.String... args)
        +                              throws java.sql.SQLException
        +
        Create a new TCP server, but does not start it yet. Example: + +
        + Server server = Server.createTcpServer(
        +     "-tcpPort", "9123", "-tcpAllowOthers").start();
        + 
        + Supported options are: + -tcpPort, -tcpSSL, -tcpPassword, -tcpAllowOthers, -tcpDaemon, + -trace, -ifExists, -ifNotExists, -baseDir, -key. + See the main method for details. +

        + If no port is specified, the default port is used if possible, + and if this port is already used, a random port is used. + Use getPort() or getURL() after starting to retrieve the port. +

        +
        +
        Parameters:
        +
        args - the argument list
        +
        Returns:
        +
        the server
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        createPgServer

        +
        public static Server createPgServer(java.lang.String... args)
        +                             throws java.sql.SQLException
        +
        Create a new PG server, but does not start it yet. + Example: +
        + Server server =
        +     Server.createPgServer("-pgAllowOthers").start();
        + 
        + Supported options are: + -pgPort, -pgAllowOthers, -pgDaemon, + -trace, -ifExists, -ifNotExists, -baseDir, -key. + See the main method for details. +

        + If no port is specified, the default port is used if possible, + and if this port is already used, a random port is used. + Use getPort() or getURL() after starting to retrieve the port. +

        +
        +
        Parameters:
        +
        args - the argument list
        +
        Returns:
        +
        the server
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        start

        +
        public Server start()
        +             throws java.sql.SQLException
        +
        Tries to start the server.
        +
        +
        Returns:
        +
        the server if successful
        +
        Throws:
        +
        java.sql.SQLException - if the server could not be started
        +
        +
      • +
      + + + +
        +
      • +

        isRunning

        +
        public boolean isRunning(boolean traceError)
        +
        Checks if the server is running.
        +
        +
        Parameters:
        +
        traceError - if errors should be written
        +
        Returns:
        +
        if the server is running
        +
        +
      • +
      + + + +
        +
      • +

        stop

        +
        public void stop()
        +
        Stops the server.
        +
      • +
      + + + +
        +
      • +

        getURL

        +
        public java.lang.String getURL()
        +
        Gets the URL of this server.
        +
        +
        Returns:
        +
        the url
        +
        +
      • +
      + + + +
        +
      • +

        getPort

        +
        public int getPort()
        +
        Gets the port this server is listening on.
        +
        +
        Returns:
        +
        the port
        +
        +
      • +
      + + + +
        +
      • +

        run

        +
        public void run()
        +
        INTERNAL
        +
        +
        Specified by:
        +
        run in interface java.lang.Runnable
        +
        +
      • +
      + + + +
        +
      • +

        setShutdownHandler

        +
        public void setShutdownHandler(org.h2.server.ShutdownHandler shutdownHandler)
        +
        INTERNAL
        +
        +
        Parameters:
        +
        shutdownHandler - to set
        +
        +
      • +
      + + + +
        +
      • +

        shutdown

        +
        public void shutdown()
        +
        INTERNAL
        +
        +
        Specified by:
        +
        shutdown in interface org.h2.server.ShutdownHandler
        +
        +
      • +
      + + + +
        +
      • +

        getService

        +
        public org.h2.server.Service getService()
        +
        Get the service attached to this server.
        +
        +
        Returns:
        +
        the service
        +
        +
      • +
      + + + +
        +
      • +

        openBrowser

        +
        public static void openBrowser(java.lang.String url)
        +                        throws java.lang.Exception
        +
        Open a new browser tab or window with the given URL.
        +
        +
        Parameters:
        +
        url - the URL to open
        +
        Throws:
        +
        java.lang.Exception - on failure
        +
        +
      • +
      + + + +
        +
      • +

        startWebServer

        +
        public static void startWebServer(java.sql.Connection conn)
        +                           throws java.sql.SQLException
        +
        Start a web server and a browser that uses the given connection. The + current transaction is preserved. This is specially useful to manually + inspect the database when debugging. This method return as soon as the + user has disconnected.
        +
        +
        Parameters:
        +
        conn - the database connection (the database must be open)
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        startWebServer

        +
        public static void startWebServer(java.sql.Connection conn,
        +                                  boolean ignoreProperties)
        +                           throws java.sql.SQLException
        +
        Start a web server and a browser that uses the given connection. The + current transaction is preserved. This is specially useful to manually + inspect the database when debugging. This method return as soon as the + user has disconnected.
        +
        +
        Parameters:
        +
        conn - the database connection (the database must be open)
        +
        ignoreProperties - if true properties from + .h2.server.properties will be ignored
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/tools/Shell.html b/h2/docs/javadoc/org/h2/tools/Shell.html new file mode 100644 index 0000000..92dc5c9 --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/Shell.html @@ -0,0 +1,490 @@ + + + + + +Shell + + + + + + + + + + + + +
+
org.h2.tools
+

Class Shell

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.util.Tool
    • +
    • +
        +
      • org.h2.tools.Shell
      • +
      +
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.lang.Runnable
    +
    +
    +
    +
    public class Shell
    +extends org.h2.util.Tool
    +implements java.lang.Runnable
    +
    Interactive command line tool to access a database using JDBC.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from class org.h2.util.Tool

        +out
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      Shell() 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      static voidmain(java.lang.String... args) +
      Options are case sensitive.
      +
      protected voidprint(java.lang.String s) +
      Print the string without newline, and flush.
      +
      voidrun() +
      INTERNAL.
      +
      voidrunTool(java.sql.Connection conn, + java.lang.String... args) +
      Run the shell tool with the given connection and command line settings.
      +
      voidrunTool(java.lang.String... args) +
      Run the shell tool with the given command line settings.
      +
      voidsetErr(java.io.PrintStream err) +
      Sets the standard error stream.
      +
      voidsetIn(java.io.InputStream in) +
      Redirects the standard input.
      +
      voidsetInReader(java.io.BufferedReader reader) +
      Redirects the standard input.
      +
      +
        +
      • + + +

        Methods inherited from class org.h2.util.Tool

        +isOption, printNoDatabaseFilesFound, setOut, showUsage, showUsageAndThrowUnsupportedOption, throwUnsupportedOption
      • +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        Shell

        +
        public Shell()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        main

        +
        public static void main(java.lang.String... args)
        +                 throws java.sql.SQLException
        +
        Options are case sensitive. + + + + + + + + + + + + + + + + +
        Supported options
        [-help] or [-?]Print the list of options
        [-url "<url>"]The database URL (jdbc:h2:...)
        [-user <user>]The user name
        [-password <pwd>]The password
        [-driver <class>]The JDBC driver class to use (not required in most cases)
        [-sql "<statements>"]Execute the SQL statements and exit
        [-properties "<dir>"]Load the server properties from this directory
        + If special characters don't work as expected, you may need to use + -Dfile.encoding=UTF-8 (Mac OS X) or CP850 (Windows).
        +
        +
        Parameters:
        +
        args - the command line arguments
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        setErr

        +
        public void setErr(java.io.PrintStream err)
        +
        Sets the standard error stream.
        +
        +
        Parameters:
        +
        err - the new standard error stream
        +
        +
      • +
      + + + +
        +
      • +

        setIn

        +
        public void setIn(java.io.InputStream in)
        +
        Redirects the standard input. By default, System.in is used.
        +
        +
        Parameters:
        +
        in - the input stream to use
        +
        +
      • +
      + + + +
        +
      • +

        setInReader

        +
        public void setInReader(java.io.BufferedReader reader)
        +
        Redirects the standard input. By default, System.in is used.
        +
        +
        Parameters:
        +
        reader - the input stream reader to use
        +
        +
      • +
      + + + +
        +
      • +

        runTool

        +
        public void runTool(java.lang.String... args)
        +             throws java.sql.SQLException
        +
        Run the shell tool with the given command line settings.
        +
        +
        Specified by:
        +
        runTool in class org.h2.util.Tool
        +
        Parameters:
        +
        args - the command line settings
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        runTool

        +
        public void runTool(java.sql.Connection conn,
        +                    java.lang.String... args)
        +             throws java.sql.SQLException
        +
        Run the shell tool with the given connection and command line settings. + The connection will be closed when the shell exits. + This is primary used to integrate the Shell into another application. +

        + Note: using the "-url" option in args doesn't make much sense + since it will override the conn parameter. +

        +
        +
        Parameters:
        +
        conn - the connection
        +
        args - the command line settings
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        print

        +
        protected void print(java.lang.String s)
        +
        Print the string without newline, and flush.
        +
        +
        Parameters:
        +
        s - the string to print
        +
        +
      • +
      + + + +
        +
      • +

        run

        +
        public void run()
        +
        INTERNAL. + Hides the password by repeatedly printing + backspace, backspace, >, <.
        +
        +
        Specified by:
        +
        run in interface java.lang.Runnable
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/tools/SimpleResultSet.SimpleArray.html b/h2/docs/javadoc/org/h2/tools/SimpleResultSet.SimpleArray.html new file mode 100644 index 0000000..b5f8b7c --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/SimpleResultSet.SimpleArray.html @@ -0,0 +1,492 @@ + + + + + +SimpleResultSet.SimpleArray + + + + + + + + + + + + +
+
org.h2.tools
+

Class SimpleResultSet.SimpleArray

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.tools.SimpleResultSet.SimpleArray
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.sql.Array
    +
    +
    +
    Enclosing class:
    +
    SimpleResultSet
    +
    +
    +
    +
    public static class SimpleResultSet.SimpleArray
    +extends java.lang.Object
    +implements java.sql.Array
    +
    A simple array implementation, + backed by an object array
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods 
      Modifier and TypeMethod and Description
      voidfree() +
      INTERNAL
      +
      java.lang.ObjectgetArray() +
      Get the object array.
      +
      java.lang.ObjectgetArray(long index, + int count) +
      INTERNAL
      +
      java.lang.ObjectgetArray(long index, + int count, + java.util.Map<java.lang.String,java.lang.Class<?>> map) +
      INTERNAL
      +
      java.lang.ObjectgetArray(java.util.Map<java.lang.String,java.lang.Class<?>> map) +
      INTERNAL
      +
      intgetBaseType() +
      Get the base type of this array.
      +
      java.lang.StringgetBaseTypeName() +
      Get the base type name of this array.
      +
      java.sql.ResultSetgetResultSet() +
      INTERNAL
      +
      java.sql.ResultSetgetResultSet(long index, + int count) +
      INTERNAL
      +
      java.sql.ResultSetgetResultSet(long index, + int count, + java.util.Map<java.lang.String,java.lang.Class<?>> map) +
      INTERNAL
      +
      java.sql.ResultSetgetResultSet(java.util.Map<java.lang.String,java.lang.Class<?>> map) +
      INTERNAL
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        getArray

        +
        public java.lang.Object getArray()
        +
        Get the object array.
        +
        +
        Specified by:
        +
        getArray in interface java.sql.Array
        +
        Returns:
        +
        the object array
        +
        +
      • +
      + + + +
        +
      • +

        getArray

        +
        public java.lang.Object getArray(java.util.Map<java.lang.String,java.lang.Class<?>> map)
        +                          throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getArray in interface java.sql.Array
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getArray

        +
        public java.lang.Object getArray(long index,
        +                                 int count)
        +                          throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getArray in interface java.sql.Array
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getArray

        +
        public java.lang.Object getArray(long index,
        +                                 int count,
        +                                 java.util.Map<java.lang.String,java.lang.Class<?>> map)
        +                          throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getArray in interface java.sql.Array
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getBaseType

        +
        public int getBaseType()
        +
        Get the base type of this array.
        +
        +
        Specified by:
        +
        getBaseType in interface java.sql.Array
        +
        Returns:
        +
        Types.NULL
        +
        +
      • +
      + + + +
        +
      • +

        getBaseTypeName

        +
        public java.lang.String getBaseTypeName()
        +
        Get the base type name of this array.
        +
        +
        Specified by:
        +
        getBaseTypeName in interface java.sql.Array
        +
        Returns:
        +
        "NULL"
        +
        +
      • +
      + + + +
        +
      • +

        getResultSet

        +
        public java.sql.ResultSet getResultSet()
        +                                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getResultSet in interface java.sql.Array
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getResultSet

        +
        public java.sql.ResultSet getResultSet(java.util.Map<java.lang.String,java.lang.Class<?>> map)
        +                                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getResultSet in interface java.sql.Array
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getResultSet

        +
        public java.sql.ResultSet getResultSet(long index,
        +                                       int count)
        +                                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getResultSet in interface java.sql.Array
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getResultSet

        +
        public java.sql.ResultSet getResultSet(long index,
        +                                       int count,
        +                                       java.util.Map<java.lang.String,java.lang.Class<?>> map)
        +                                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getResultSet in interface java.sql.Array
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        free

        +
        public void free()
        +
        INTERNAL
        +
        +
        Specified by:
        +
        free in interface java.sql.Array
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/tools/SimpleResultSet.html b/h2/docs/javadoc/org/h2/tools/SimpleResultSet.html new file mode 100644 index 0000000..07f8523 --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/SimpleResultSet.html @@ -0,0 +1,5813 @@ + + + + + +SimpleResultSet + + + + + + + + + + + + +
+
org.h2.tools
+

Class SimpleResultSet

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.tools.SimpleResultSet
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    java.lang.AutoCloseable, java.sql.ResultSet, java.sql.ResultSetMetaData, java.sql.Wrapper
    +
    +
    +
    +
    public class SimpleResultSet
    +extends java.lang.Object
    +implements java.sql.ResultSet, java.sql.ResultSetMetaData
    +
    This class is a simple result set and meta data implementation. + It can be used in Java functions that return a result set. + Only the most basic methods are implemented, the others throw an exception. + This implementation is standalone, and only relies on standard classes. + It can be extended easily if required. + + An application can create a result set using the following code: + +
    + SimpleResultSet rs = new SimpleResultSet();
    + rs.addColumn("ID", Types.INTEGER, 10, 0);
    + rs.addColumn("NAME", Types.VARCHAR, 255, 0);
    + rs.addRow(0, "Hello" });
    + rs.addRow(1, "World" });
    + 
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Nested Class Summary

      + + + + + + + + + + +
      Nested Classes 
      Modifier and TypeClass and Description
      static class SimpleResultSet.SimpleArray +
      A simple array implementation, + backed by an object array
      +
      +
    • +
    + +
      +
    • + + +

      Field Summary

      +
        +
      • + + +

        Fields inherited from interface java.sql.ResultSet

        +CLOSE_CURSORS_AT_COMMIT, CONCUR_READ_ONLY, CONCUR_UPDATABLE, FETCH_FORWARD, FETCH_REVERSE, FETCH_UNKNOWN, HOLD_CURSORS_OVER_COMMIT, TYPE_FORWARD_ONLY, TYPE_SCROLL_INSENSITIVE, TYPE_SCROLL_SENSITIVE
      • +
      +
        +
      • + + +

        Fields inherited from interface java.sql.ResultSetMetaData

        +columnNoNulls, columnNullable, columnNullableUnknown
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + + + + +
      Constructors 
      Constructor and Description
      SimpleResultSet() +
      This constructor is used if the result set is later populated with + addRow.
      +
      SimpleResultSet(SimpleRowSource source) +
      This constructor is used if the result set should retrieve the rows using + the specified row source object.
      +
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Concrete Methods Deprecated Methods 
      Modifier and TypeMethod and Description
      booleanabsolute(int row) +
      INTERNAL
      +
      voidaddColumn(java.lang.String name, + int sqlType, + int precision, + int scale) +
      Adds a column to the result set.
      +
      voidaddColumn(java.lang.String name, + int sqlType, + java.lang.String sqlTypeName, + int precision, + int scale) +
      Adds a column to the result set.
      +
      voidaddRow(java.lang.Object... row) +
      Add a new row to the result set.
      +
      voidafterLast() +
      INTERNAL
      +
      voidbeforeFirst() +
      Moves the current position to before the first row, that means the result + set is reset.
      +
      voidcancelRowUpdates() +
      INTERNAL
      +
      voidclearWarnings() +
      INTERNAL
      +
      voidclose() +
      Closes the result set and releases the resources.
      +
      voiddeleteRow() +
      INTERNAL
      +
      intfindColumn(java.lang.String columnLabel) +
      Searches for a specific column in the result set.
      +
      booleanfirst() +
      INTERNAL
      +
      java.sql.ArraygetArray(int columnIndex) +
      Returns the value as a java.sql.Array.
      +
      java.sql.ArraygetArray(java.lang.String columnLabel) +
      Returns the value as a java.sql.Array.
      +
      java.io.InputStreamgetAsciiStream(int columnIndex) +
      INTERNAL
      +
      java.io.InputStreamgetAsciiStream(java.lang.String columnLabel) +
      INTERNAL
      +
      booleangetAutoClose() +
      Get the current auto-close behavior.
      +
      java.math.BigDecimalgetBigDecimal(int columnIndex) +
      Returns the value as a java.math.BigDecimal.
      +
      java.math.BigDecimalgetBigDecimal(int columnIndex, + int scale) +
      Deprecated.  +
      INTERNAL
      +
      +
      java.math.BigDecimalgetBigDecimal(java.lang.String columnLabel) +
      Returns the value as a java.math.BigDecimal.
      +
      java.math.BigDecimalgetBigDecimal(java.lang.String columnLabel, + int scale) +
      Deprecated.  +
      INTERNAL
      +
      +
      java.io.InputStreamgetBinaryStream(int columnIndex) +
      Returns the value as a java.io.InputStream.
      +
      java.io.InputStreamgetBinaryStream(java.lang.String columnLabel) +
      Returns the value as a java.io.InputStream.
      +
      java.sql.BlobgetBlob(int columnIndex) +
      Returns the value as a java.sql.Blob.
      +
      java.sql.BlobgetBlob(java.lang.String columnLabel) +
      Returns the value as a java.sql.Blob.
      +
      booleangetBoolean(int columnIndex) +
      Returns the value as a boolean.
      +
      booleangetBoolean(java.lang.String columnLabel) +
      Returns the value as a boolean.
      +
      bytegetByte(int columnIndex) +
      Returns the value as a byte.
      +
      bytegetByte(java.lang.String columnLabel) +
      Returns the value as a byte.
      +
      byte[]getBytes(int columnIndex) +
      Returns the value as a byte array.
      +
      byte[]getBytes(java.lang.String columnLabel) +
      Returns the value as a byte array.
      +
      java.lang.StringgetCatalogName(int columnIndex) +
      Returns empty string.
      +
      java.io.ReadergetCharacterStream(int columnIndex) +
      Returns the value as a java.io.Reader.
      +
      java.io.ReadergetCharacterStream(java.lang.String columnLabel) +
      Returns the value as a java.io.Reader.
      +
      java.sql.ClobgetClob(int columnIndex) +
      Returns the value as a java.sql.Clob.
      +
      java.sql.ClobgetClob(java.lang.String columnLabel) +
      Returns the value as a java.sql.Clob.
      +
      java.lang.StringgetColumnClassName(int columnIndex) +
      Returns the Java class name if this column.
      +
      intgetColumnCount() +
      Returns the column count.
      +
      intgetColumnDisplaySize(int columnIndex) +
      Returns 15.
      +
      java.lang.StringgetColumnLabel(int columnIndex) +
      Returns the column label.
      +
      java.lang.StringgetColumnName(int columnIndex) +
      Returns the column name.
      +
      intgetColumnType(int columnIndex) +
      Returns the SQL type.
      +
      java.lang.StringgetColumnTypeName(int columnIndex) +
      Returns the data type name of a column.
      +
      intgetConcurrency() +
      Returns ResultSet.CONCUR_READ_ONLY.
      +
      java.lang.StringgetCursorName() +
      INTERNAL
      +
      java.sql.DategetDate(int columnIndex) +
      Returns the value as an java.sql.Date.
      +
      java.sql.DategetDate(int columnIndex, + java.util.Calendar cal) +
      INTERNAL
      +
      java.sql.DategetDate(java.lang.String columnLabel) +
      Returns the value as a java.sql.Date.
      +
      java.sql.DategetDate(java.lang.String columnLabel, + java.util.Calendar cal) +
      INTERNAL
      +
      doublegetDouble(int columnIndex) +
      Returns the value as an double.
      +
      doublegetDouble(java.lang.String columnLabel) +
      Returns the value as a double.
      +
      intgetFetchDirection() +
      Returns ResultSet.FETCH_FORWARD.
      +
      intgetFetchSize() +
      Returns 0.
      +
      floatgetFloat(int columnIndex) +
      Returns the value as a float.
      +
      floatgetFloat(java.lang.String columnLabel) +
      Returns the value as a float.
      +
      intgetHoldability() +
      Returns the current result set holdability.
      +
      intgetInt(int columnIndex) +
      Returns the value as an int.
      +
      intgetInt(java.lang.String columnLabel) +
      Returns the value as an int.
      +
      longgetLong(int columnIndex) +
      Returns the value as a long.
      +
      longgetLong(java.lang.String columnLabel) +
      Returns the value as a long.
      +
      java.sql.ResultSetMetaDatagetMetaData() +
      Returns a reference to itself.
      +
      java.io.ReadergetNCharacterStream(int columnIndex) +
      INTERNAL
      +
      java.io.ReadergetNCharacterStream(java.lang.String columnLabel) +
      INTERNAL
      +
      java.sql.NClobgetNClob(int columnIndex) +
      INTERNAL
      +
      java.sql.NClobgetNClob(java.lang.String columnLabel) +
      INTERNAL
      +
      java.lang.StringgetNString(int columnIndex) +
      INTERNAL
      +
      java.lang.StringgetNString(java.lang.String columnLabel) +
      INTERNAL
      +
      java.lang.ObjectgetObject(int columnIndex) +
      Returns the value as an Object.
      +
      <T> TgetObject(int columnIndex, + java.lang.Class<T> type) +
      Returns the value as an Object of the specified type.
      +
      java.lang.ObjectgetObject(int columnIndex, + java.util.Map<java.lang.String,java.lang.Class<?>> map) +
      INTERNAL
      +
      java.lang.ObjectgetObject(java.lang.String columnLabel) +
      Returns the value as an Object.
      +
      <T> TgetObject(java.lang.String columnName, + java.lang.Class<T> type) +
      Returns the value as an Object of the specified type.
      +
      java.lang.ObjectgetObject(java.lang.String columnLabel, + java.util.Map<java.lang.String,java.lang.Class<?>> map) +
      INTERNAL
      +
      intgetPrecision(int columnIndex) +
      Returns the precision.
      +
      java.sql.RefgetRef(int columnIndex) +
      INTERNAL
      +
      java.sql.RefgetRef(java.lang.String columnLabel) +
      INTERNAL
      +
      intgetRow() +
      Returns the row number (1, 2,...) or 0 for no row.
      +
      java.sql.RowIdgetRowId(int columnIndex) +
      INTERNAL
      +
      java.sql.RowIdgetRowId(java.lang.String columnLabel) +
      INTERNAL
      +
      intgetScale(int columnIndex) +
      Returns the scale.
      +
      java.lang.StringgetSchemaName(int columnIndex) +
      Returns empty string.
      +
      shortgetShort(int columnIndex) +
      Returns the value as a short.
      +
      shortgetShort(java.lang.String columnLabel) +
      Returns the value as a short.
      +
      java.sql.SQLXMLgetSQLXML(int columnIndex) +
      INTERNAL
      +
      java.sql.SQLXMLgetSQLXML(java.lang.String columnLabel) +
      INTERNAL
      +
      java.sql.StatementgetStatement() +
      Returns null.
      +
      java.lang.StringgetString(int columnIndex) +
      Returns the value as a String.
      +
      java.lang.StringgetString(java.lang.String columnLabel) +
      Returns the value as a String.
      +
      java.lang.StringgetTableName(int columnIndex) +
      Returns empty string.
      +
      java.sql.TimegetTime(int columnIndex) +
      Returns the value as an java.sql.Time.
      +
      java.sql.TimegetTime(int columnIndex, + java.util.Calendar cal) +
      INTERNAL
      +
      java.sql.TimegetTime(java.lang.String columnLabel) +
      Returns the value as a java.sql.Time.
      +
      java.sql.TimegetTime(java.lang.String columnLabel, + java.util.Calendar cal) +
      INTERNAL
      +
      java.sql.TimestampgetTimestamp(int columnIndex) +
      Returns the value as an java.sql.Timestamp.
      +
      java.sql.TimestampgetTimestamp(int columnIndex, + java.util.Calendar cal) +
      INTERNAL
      +
      java.sql.TimestampgetTimestamp(java.lang.String columnLabel) +
      Returns the value as a java.sql.Timestamp.
      +
      java.sql.TimestampgetTimestamp(java.lang.String columnLabel, + java.util.Calendar cal) +
      INTERNAL
      +
      intgetType() +
      Returns the result set type.
      +
      java.io.InputStreamgetUnicodeStream(int columnIndex) +
      Deprecated.  +
      INTERNAL
      +
      +
      java.io.InputStreamgetUnicodeStream(java.lang.String columnLabel) +
      Deprecated.  +
      INTERNAL
      +
      +
      java.net.URLgetURL(int columnIndex) +
      INTERNAL
      +
      java.net.URLgetURL(java.lang.String columnLabel) +
      INTERNAL
      +
      java.sql.SQLWarninggetWarnings() +
      Returns null.
      +
      voidinsertRow() +
      INTERNAL
      +
      booleanisAfterLast() +
      INTERNAL
      +
      booleanisAutoIncrement(int columnIndex) +
      Returns false.
      +
      booleanisBeforeFirst() +
      INTERNAL
      +
      booleanisCaseSensitive(int columnIndex) +
      Returns true.
      +
      booleanisClosed() +
      Returns whether this result set has been closed.
      +
      booleanisCurrency(int columnIndex) +
      Returns false.
      +
      booleanisDefinitelyWritable(int columnIndex) +
      Returns false.
      +
      booleanisFirst() +
      INTERNAL
      +
      booleanisLast() +
      INTERNAL
      +
      intisNullable(int columnIndex) +
      Returns ResultSetMetaData.columnNullableUnknown.
      +
      booleanisReadOnly(int columnIndex) +
      Returns true.
      +
      booleanisSearchable(int columnIndex) +
      Returns true.
      +
      booleanisSigned(int columnIndex) +
      Returns true.
      +
      booleanisWrapperFor(java.lang.Class<?> iface) +
      Checks if unwrap can return an object of this class.
      +
      booleanisWritable(int columnIndex) +
      Returns false.
      +
      booleanlast() +
      INTERNAL
      +
      voidmoveToCurrentRow() +
      INTERNAL
      +
      voidmoveToInsertRow() +
      INTERNAL
      +
      booleannext() +
      Moves the cursor to the next row of the result set.
      +
      booleanprevious() +
      INTERNAL
      +
      voidrefreshRow() +
      INTERNAL
      +
      booleanrelative(int offset) +
      INTERNAL
      +
      booleanrowDeleted() +
      INTERNAL
      +
      booleanrowInserted() +
      INTERNAL
      +
      booleanrowUpdated() +
      INTERNAL
      +
      voidsetAutoClose(boolean autoClose) +
      Set the auto-close behavior.
      +
      voidsetFetchDirection(int direction) +
      INTERNAL
      +
      voidsetFetchSize(int rows) +
      INTERNAL
      +
      <T> Tunwrap(java.lang.Class<T> iface) +
      Return an object of this class if possible.
      +
      voidupdateArray(int columnIndex, + java.sql.Array x) +
      INTERNAL
      +
      voidupdateArray(java.lang.String columnLabel, + java.sql.Array x) +
      INTERNAL
      +
      voidupdateAsciiStream(int columnIndex, + java.io.InputStream x) +
      INTERNAL
      +
      voidupdateAsciiStream(int columnIndex, + java.io.InputStream x, + int length) +
      INTERNAL
      +
      voidupdateAsciiStream(int columnIndex, + java.io.InputStream x, + long length) +
      INTERNAL
      +
      voidupdateAsciiStream(java.lang.String columnLabel, + java.io.InputStream x) +
      INTERNAL
      +
      voidupdateAsciiStream(java.lang.String columnLabel, + java.io.InputStream x, + int length) +
      INTERNAL
      +
      voidupdateAsciiStream(java.lang.String columnLabel, + java.io.InputStream x, + long length) +
      INTERNAL
      +
      voidupdateBigDecimal(int columnIndex, + java.math.BigDecimal x) +
      INTERNAL
      +
      voidupdateBigDecimal(java.lang.String columnLabel, + java.math.BigDecimal x) +
      INTERNAL
      +
      voidupdateBinaryStream(int columnIndex, + java.io.InputStream x) +
      INTERNAL
      +
      voidupdateBinaryStream(int columnIndex, + java.io.InputStream x, + int length) +
      INTERNAL
      +
      voidupdateBinaryStream(int columnIndex, + java.io.InputStream x, + long length) +
      INTERNAL
      +
      voidupdateBinaryStream(java.lang.String columnLabel, + java.io.InputStream x) +
      INTERNAL
      +
      voidupdateBinaryStream(java.lang.String columnLabel, + java.io.InputStream x, + int length) +
      INTERNAL
      +
      voidupdateBinaryStream(java.lang.String columnLabel, + java.io.InputStream x, + long length) +
      INTERNAL
      +
      voidupdateBlob(int columnIndex, + java.sql.Blob x) +
      INTERNAL
      +
      voidupdateBlob(int columnIndex, + java.io.InputStream x) +
      INTERNAL
      +
      voidupdateBlob(int columnIndex, + java.io.InputStream x, + long length) +
      INTERNAL
      +
      voidupdateBlob(java.lang.String columnLabel, + java.sql.Blob x) +
      INTERNAL
      +
      voidupdateBlob(java.lang.String columnLabel, + java.io.InputStream x) +
      INTERNAL
      +
      voidupdateBlob(java.lang.String columnLabel, + java.io.InputStream x, + long length) +
      INTERNAL
      +
      voidupdateBoolean(int columnIndex, + boolean x) +
      INTERNAL
      +
      voidupdateBoolean(java.lang.String columnLabel, + boolean x) +
      INTERNAL
      +
      voidupdateByte(int columnIndex, + byte x) +
      INTERNAL
      +
      voidupdateByte(java.lang.String columnLabel, + byte x) +
      INTERNAL
      +
      voidupdateBytes(int columnIndex, + byte[] x) +
      INTERNAL
      +
      voidupdateBytes(java.lang.String columnLabel, + byte[] x) +
      INTERNAL
      +
      voidupdateCharacterStream(int columnIndex, + java.io.Reader x) +
      INTERNAL
      +
      voidupdateCharacterStream(int columnIndex, + java.io.Reader x, + int length) +
      INTERNAL
      +
      voidupdateCharacterStream(int columnIndex, + java.io.Reader x, + long length) +
      INTERNAL
      +
      voidupdateCharacterStream(java.lang.String columnLabel, + java.io.Reader x) +
      INTERNAL
      +
      voidupdateCharacterStream(java.lang.String columnLabel, + java.io.Reader x, + int length) +
      INTERNAL
      +
      voidupdateCharacterStream(java.lang.String columnLabel, + java.io.Reader x, + long length) +
      INTERNAL
      +
      voidupdateClob(int columnIndex, + java.sql.Clob x) +
      INTERNAL
      +
      voidupdateClob(int columnIndex, + java.io.Reader x) +
      INTERNAL
      +
      voidupdateClob(int columnIndex, + java.io.Reader x, + long length) +
      INTERNAL
      +
      voidupdateClob(java.lang.String columnLabel, + java.sql.Clob x) +
      INTERNAL
      +
      voidupdateClob(java.lang.String columnLabel, + java.io.Reader x) +
      INTERNAL
      +
      voidupdateClob(java.lang.String columnLabel, + java.io.Reader x, + long length) +
      INTERNAL
      +
      voidupdateDate(int columnIndex, + java.sql.Date x) +
      INTERNAL
      +
      voidupdateDate(java.lang.String columnLabel, + java.sql.Date x) +
      INTERNAL
      +
      voidupdateDouble(int columnIndex, + double x) +
      INTERNAL
      +
      voidupdateDouble(java.lang.String columnLabel, + double x) +
      INTERNAL
      +
      voidupdateFloat(int columnIndex, + float x) +
      INTERNAL
      +
      voidupdateFloat(java.lang.String columnLabel, + float x) +
      INTERNAL
      +
      voidupdateInt(int columnIndex, + int x) +
      INTERNAL
      +
      voidupdateInt(java.lang.String columnLabel, + int x) +
      INTERNAL
      +
      voidupdateLong(int columnIndex, + long x) +
      INTERNAL
      +
      voidupdateLong(java.lang.String columnLabel, + long x) +
      INTERNAL
      +
      voidupdateNCharacterStream(int columnIndex, + java.io.Reader x) +
      INTERNAL
      +
      voidupdateNCharacterStream(int columnIndex, + java.io.Reader x, + long length) +
      INTERNAL
      +
      voidupdateNCharacterStream(java.lang.String columnLabel, + java.io.Reader x) +
      INTERNAL
      +
      voidupdateNCharacterStream(java.lang.String columnLabel, + java.io.Reader x, + long length) +
      INTERNAL
      +
      voidupdateNClob(int columnIndex, + java.sql.NClob x) +
      INTERNAL
      +
      voidupdateNClob(int columnIndex, + java.io.Reader x) +
      INTERNAL
      +
      voidupdateNClob(int columnIndex, + java.io.Reader x, + long length) +
      INTERNAL
      +
      voidupdateNClob(java.lang.String columnLabel, + java.sql.NClob x) +
      INTERNAL
      +
      voidupdateNClob(java.lang.String columnLabel, + java.io.Reader x) +
      INTERNAL
      +
      voidupdateNClob(java.lang.String columnLabel, + java.io.Reader x, + long length) +
      INTERNAL
      +
      voidupdateNString(int columnIndex, + java.lang.String x) +
      INTERNAL
      +
      voidupdateNString(java.lang.String columnLabel, + java.lang.String x) +
      INTERNAL
      +
      voidupdateNull(int columnIndex) +
      INTERNAL
      +
      voidupdateNull(java.lang.String columnLabel) +
      INTERNAL
      +
      voidupdateObject(int columnIndex, + java.lang.Object x) +
      INTERNAL
      +
      voidupdateObject(int columnIndex, + java.lang.Object x, + int scale) +
      INTERNAL
      +
      voidupdateObject(java.lang.String columnLabel, + java.lang.Object x) +
      INTERNAL
      +
      voidupdateObject(java.lang.String columnLabel, + java.lang.Object x, + int scale) +
      INTERNAL
      +
      voidupdateRef(int columnIndex, + java.sql.Ref x) +
      INTERNAL
      +
      voidupdateRef(java.lang.String columnLabel, + java.sql.Ref x) +
      INTERNAL
      +
      voidupdateRow() +
      INTERNAL
      +
      voidupdateRowId(int columnIndex, + java.sql.RowId x) +
      INTERNAL
      +
      voidupdateRowId(java.lang.String columnLabel, + java.sql.RowId x) +
      INTERNAL
      +
      voidupdateShort(int columnIndex, + short x) +
      INTERNAL
      +
      voidupdateShort(java.lang.String columnLabel, + short x) +
      INTERNAL
      +
      voidupdateSQLXML(int columnIndex, + java.sql.SQLXML x) +
      INTERNAL
      +
      voidupdateSQLXML(java.lang.String columnLabel, + java.sql.SQLXML x) +
      INTERNAL
      +
      voidupdateString(int columnIndex, + java.lang.String x) +
      INTERNAL
      +
      voidupdateString(java.lang.String columnLabel, + java.lang.String x) +
      INTERNAL
      +
      voidupdateTime(int columnIndex, + java.sql.Time x) +
      INTERNAL
      +
      voidupdateTime(java.lang.String columnLabel, + java.sql.Time x) +
      INTERNAL
      +
      voidupdateTimestamp(int columnIndex, + java.sql.Timestamp x) +
      INTERNAL
      +
      voidupdateTimestamp(java.lang.String columnLabel, + java.sql.Timestamp x) +
      INTERNAL
      +
      booleanwasNull() +
      Returns whether the last column accessed was null.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
        +
      • + + +

        Methods inherited from interface java.sql.ResultSet

        +updateObject, updateObject, updateObject, updateObject
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        SimpleResultSet

        +
        public SimpleResultSet()
        +
        This constructor is used if the result set is later populated with + addRow.
        +
      • +
      + + + +
        +
      • +

        SimpleResultSet

        +
        public SimpleResultSet(SimpleRowSource source)
        +
        This constructor is used if the result set should retrieve the rows using + the specified row source object.
        +
        +
        Parameters:
        +
        source - the row source
        +
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        addColumn

        +
        public void addColumn(java.lang.String name,
        +                      int sqlType,
        +                      int precision,
        +                      int scale)
        +
        Adds a column to the result set. + All columns must be added before adding rows. + This method uses the default SQL type names.
        +
        +
        Parameters:
        +
        name - null is replaced with C1, C2,...
        +
        sqlType - the value returned in getColumnType(..)
        +
        precision - the precision
        +
        scale - the scale
        +
        +
      • +
      + + + +
        +
      • +

        addColumn

        +
        public void addColumn(java.lang.String name,
        +                      int sqlType,
        +                      java.lang.String sqlTypeName,
        +                      int precision,
        +                      int scale)
        +
        Adds a column to the result set. + All columns must be added before adding rows.
        +
        +
        Parameters:
        +
        name - null is replaced with C1, C2,...
        +
        sqlType - the value returned in getColumnType(..)
        +
        sqlTypeName - the type name return in getColumnTypeName(..)
        +
        precision - the precision
        +
        scale - the scale
        +
        +
      • +
      + + + +
        +
      • +

        addRow

        +
        public void addRow(java.lang.Object... row)
        +
        Add a new row to the result set. + Do not use this method when using a RowSource.
        +
        +
        Parameters:
        +
        row - the row as an array of objects
        +
        +
      • +
      + + + +
        +
      • +

        getConcurrency

        +
        public int getConcurrency()
        +
        Returns ResultSet.CONCUR_READ_ONLY.
        +
        +
        Specified by:
        +
        getConcurrency in interface java.sql.ResultSet
        +
        Returns:
        +
        CONCUR_READ_ONLY
        +
        +
      • +
      + + + +
        +
      • +

        getFetchDirection

        +
        public int getFetchDirection()
        +
        Returns ResultSet.FETCH_FORWARD.
        +
        +
        Specified by:
        +
        getFetchDirection in interface java.sql.ResultSet
        +
        Returns:
        +
        FETCH_FORWARD
        +
        +
      • +
      + + + +
        +
      • +

        getFetchSize

        +
        public int getFetchSize()
        +
        Returns 0.
        +
        +
        Specified by:
        +
        getFetchSize in interface java.sql.ResultSet
        +
        Returns:
        +
        0
        +
        +
      • +
      + + + +
        +
      • +

        getRow

        +
        public int getRow()
        +
        Returns the row number (1, 2,...) or 0 for no row.
        +
        +
        Specified by:
        +
        getRow in interface java.sql.ResultSet
        +
        Returns:
        +
        0
        +
        +
      • +
      + + + +
        +
      • +

        getType

        +
        public int getType()
        +
        Returns the result set type. This is ResultSet.TYPE_FORWARD_ONLY for + auto-close result sets, and ResultSet.TYPE_SCROLL_INSENSITIVE for others.
        +
        +
        Specified by:
        +
        getType in interface java.sql.ResultSet
        +
        Returns:
        +
        TYPE_FORWARD_ONLY or TYPE_SCROLL_INSENSITIVE
        +
        +
      • +
      + + + +
        +
      • +

        close

        +
        public void close()
        +
        Closes the result set and releases the resources.
        +
        +
        Specified by:
        +
        close in interface java.lang.AutoCloseable
        +
        Specified by:
        +
        close in interface java.sql.ResultSet
        +
        +
      • +
      + + + +
        +
      • +

        next

        +
        public boolean next()
        +             throws java.sql.SQLException
        +
        Moves the cursor to the next row of the result set.
        +
        +
        Specified by:
        +
        next in interface java.sql.ResultSet
        +
        Returns:
        +
        true if successful, false if there are no more rows
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        beforeFirst

        +
        public void beforeFirst()
        +                 throws java.sql.SQLException
        +
        Moves the current position to before the first row, that means the result + set is reset.
        +
        +
        Specified by:
        +
        beforeFirst in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        wasNull

        +
        public boolean wasNull()
        +
        Returns whether the last column accessed was null.
        +
        +
        Specified by:
        +
        wasNull in interface java.sql.ResultSet
        +
        Returns:
        +
        true if the last column accessed was null
        +
        +
      • +
      + + + +
        +
      • +

        findColumn

        +
        public int findColumn(java.lang.String columnLabel)
        +               throws java.sql.SQLException
        +
        Searches for a specific column in the result set. A case-insensitive + search is made.
        +
        +
        Specified by:
        +
        findColumn in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the column index (1,2,...)
        +
        Throws:
        +
        java.sql.SQLException - if the column is not found or if the result set is + closed
        +
        +
      • +
      + + + +
        +
      • +

        getMetaData

        +
        public java.sql.ResultSetMetaData getMetaData()
        +
        Returns a reference to itself.
        +
        +
        Specified by:
        +
        getMetaData in interface java.sql.ResultSet
        +
        Returns:
        +
        this
        +
        +
      • +
      + + + +
        +
      • +

        getWarnings

        +
        public java.sql.SQLWarning getWarnings()
        +
        Returns null.
        +
        +
        Specified by:
        +
        getWarnings in interface java.sql.ResultSet
        +
        Returns:
        +
        null
        +
        +
      • +
      + + + +
        +
      • +

        getStatement

        +
        public java.sql.Statement getStatement()
        +
        Returns null.
        +
        +
        Specified by:
        +
        getStatement in interface java.sql.ResultSet
        +
        Returns:
        +
        null
        +
        +
      • +
      + + + +
        +
      • +

        clearWarnings

        +
        public void clearWarnings()
        +
        INTERNAL
        +
        +
        Specified by:
        +
        clearWarnings in interface java.sql.ResultSet
        +
        +
      • +
      + + + +
        +
      • +

        getArray

        +
        public java.sql.Array getArray(int columnIndex)
        +                        throws java.sql.SQLException
        +
        Returns the value as a java.sql.Array.
        +
        +
        Specified by:
        +
        getArray in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getArray

        +
        public java.sql.Array getArray(java.lang.String columnLabel)
        +                        throws java.sql.SQLException
        +
        Returns the value as a java.sql.Array.
        +
        +
        Specified by:
        +
        getArray in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getAsciiStream

        +
        public java.io.InputStream getAsciiStream(int columnIndex)
        +                                   throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getAsciiStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getAsciiStream

        +
        public java.io.InputStream getAsciiStream(java.lang.String columnLabel)
        +                                   throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getAsciiStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getBigDecimal

        +
        public java.math.BigDecimal getBigDecimal(int columnIndex)
        +                                   throws java.sql.SQLException
        +
        Returns the value as a java.math.BigDecimal.
        +
        +
        Specified by:
        +
        getBigDecimal in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getBigDecimal

        +
        public java.math.BigDecimal getBigDecimal(java.lang.String columnLabel)
        +                                   throws java.sql.SQLException
        +
        Returns the value as a java.math.BigDecimal.
        +
        +
        Specified by:
        +
        getBigDecimal in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getBigDecimal

        +
        @Deprecated
        +public java.math.BigDecimal getBigDecimal(int columnIndex,
        +                                                      int scale)
        +                                               throws java.sql.SQLException
        +
        Deprecated. INTERNAL
        +
        +
        Specified by:
        +
        getBigDecimal in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getBigDecimal

        +
        @Deprecated
        +public java.math.BigDecimal getBigDecimal(java.lang.String columnLabel,
        +                                                      int scale)
        +                                               throws java.sql.SQLException
        +
        Deprecated. INTERNAL
        +
        +
        Specified by:
        +
        getBigDecimal in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getBinaryStream

        +
        public java.io.InputStream getBinaryStream(int columnIndex)
        +                                    throws java.sql.SQLException
        +
        Returns the value as a java.io.InputStream.
        +
        +
        Specified by:
        +
        getBinaryStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getBinaryStream

        +
        public java.io.InputStream getBinaryStream(java.lang.String columnLabel)
        +                                    throws java.sql.SQLException
        +
        Returns the value as a java.io.InputStream.
        +
        +
        Specified by:
        +
        getBinaryStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getBlob

        +
        public java.sql.Blob getBlob(int columnIndex)
        +                      throws java.sql.SQLException
        +
        Returns the value as a java.sql.Blob. + This is only supported if the + result set was created using a Blob object.
        +
        +
        Specified by:
        +
        getBlob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getBlob

        +
        public java.sql.Blob getBlob(java.lang.String columnLabel)
        +                      throws java.sql.SQLException
        +
        Returns the value as a java.sql.Blob. + This is only supported if the + result set was created using a Blob object.
        +
        +
        Specified by:
        +
        getBlob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getBoolean

        +
        public boolean getBoolean(int columnIndex)
        +                   throws java.sql.SQLException
        +
        Returns the value as a boolean.
        +
        +
        Specified by:
        +
        getBoolean in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getBoolean

        +
        public boolean getBoolean(java.lang.String columnLabel)
        +                   throws java.sql.SQLException
        +
        Returns the value as a boolean.
        +
        +
        Specified by:
        +
        getBoolean in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getByte

        +
        public byte getByte(int columnIndex)
        +             throws java.sql.SQLException
        +
        Returns the value as a byte.
        +
        +
        Specified by:
        +
        getByte in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getByte

        +
        public byte getByte(java.lang.String columnLabel)
        +             throws java.sql.SQLException
        +
        Returns the value as a byte.
        +
        +
        Specified by:
        +
        getByte in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getBytes

        +
        public byte[] getBytes(int columnIndex)
        +                throws java.sql.SQLException
        +
        Returns the value as a byte array.
        +
        +
        Specified by:
        +
        getBytes in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getBytes

        +
        public byte[] getBytes(java.lang.String columnLabel)
        +                throws java.sql.SQLException
        +
        Returns the value as a byte array.
        +
        +
        Specified by:
        +
        getBytes in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getCharacterStream

        +
        public java.io.Reader getCharacterStream(int columnIndex)
        +                                  throws java.sql.SQLException
        +
        Returns the value as a java.io.Reader. + This is only supported if the + result set was created using a Clob or Reader object.
        +
        +
        Specified by:
        +
        getCharacterStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getCharacterStream

        +
        public java.io.Reader getCharacterStream(java.lang.String columnLabel)
        +                                  throws java.sql.SQLException
        +
        Returns the value as a java.io.Reader. + This is only supported if the + result set was created using a Clob or Reader object.
        +
        +
        Specified by:
        +
        getCharacterStream in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getClob

        +
        public java.sql.Clob getClob(int columnIndex)
        +                      throws java.sql.SQLException
        +
        Returns the value as a java.sql.Clob. + This is only supported if the + result set was created using a Clob object.
        +
        +
        Specified by:
        +
        getClob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getClob

        +
        public java.sql.Clob getClob(java.lang.String columnLabel)
        +                      throws java.sql.SQLException
        +
        Returns the value as a java.sql.Clob. + This is only supported if the + result set was created using a Clob object.
        +
        +
        Specified by:
        +
        getClob in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getDate

        +
        public java.sql.Date getDate(int columnIndex)
        +                      throws java.sql.SQLException
        +
        Returns the value as an java.sql.Date.
        +
        +
        Specified by:
        +
        getDate in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getDate

        +
        public java.sql.Date getDate(java.lang.String columnLabel)
        +                      throws java.sql.SQLException
        +
        Returns the value as a java.sql.Date.
        +
        +
        Specified by:
        +
        getDate in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getDate

        +
        public java.sql.Date getDate(int columnIndex,
        +                             java.util.Calendar cal)
        +                      throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getDate in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getDate

        +
        public java.sql.Date getDate(java.lang.String columnLabel,
        +                             java.util.Calendar cal)
        +                      throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getDate in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getDouble

        +
        public double getDouble(int columnIndex)
        +                 throws java.sql.SQLException
        +
        Returns the value as an double.
        +
        +
        Specified by:
        +
        getDouble in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getDouble

        +
        public double getDouble(java.lang.String columnLabel)
        +                 throws java.sql.SQLException
        +
        Returns the value as a double.
        +
        +
        Specified by:
        +
        getDouble in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getFloat

        +
        public float getFloat(int columnIndex)
        +               throws java.sql.SQLException
        +
        Returns the value as a float.
        +
        +
        Specified by:
        +
        getFloat in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getFloat

        +
        public float getFloat(java.lang.String columnLabel)
        +               throws java.sql.SQLException
        +
        Returns the value as a float.
        +
        +
        Specified by:
        +
        getFloat in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getInt

        +
        public int getInt(int columnIndex)
        +           throws java.sql.SQLException
        +
        Returns the value as an int.
        +
        +
        Specified by:
        +
        getInt in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getInt

        +
        public int getInt(java.lang.String columnLabel)
        +           throws java.sql.SQLException
        +
        Returns the value as an int.
        +
        +
        Specified by:
        +
        getInt in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getLong

        +
        public long getLong(int columnIndex)
        +             throws java.sql.SQLException
        +
        Returns the value as a long.
        +
        +
        Specified by:
        +
        getLong in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getLong

        +
        public long getLong(java.lang.String columnLabel)
        +             throws java.sql.SQLException
        +
        Returns the value as a long.
        +
        +
        Specified by:
        +
        getLong in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getNCharacterStream

        +
        public java.io.Reader getNCharacterStream(int columnIndex)
        +                                   throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getNCharacterStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getNCharacterStream

        +
        public java.io.Reader getNCharacterStream(java.lang.String columnLabel)
        +                                   throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getNCharacterStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getNClob

        +
        public java.sql.NClob getNClob(int columnIndex)
        +                        throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getNClob in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getNClob

        +
        public java.sql.NClob getNClob(java.lang.String columnLabel)
        +                        throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getNClob in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getNString

        +
        public java.lang.String getNString(int columnIndex)
        +                            throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getNString in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getNString

        +
        public java.lang.String getNString(java.lang.String columnLabel)
        +                            throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getNString in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getObject

        +
        public java.lang.Object getObject(int columnIndex)
        +                           throws java.sql.SQLException
        +
        Returns the value as an Object.
        +
        +
        Specified by:
        +
        getObject in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getObject

        +
        public java.lang.Object getObject(java.lang.String columnLabel)
        +                           throws java.sql.SQLException
        +
        Returns the value as an Object.
        +
        +
        Specified by:
        +
        getObject in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getObject

        +
        public <T> T getObject(int columnIndex,
        +                       java.lang.Class<T> type)
        +                throws java.sql.SQLException
        +
        Returns the value as an Object of the specified type.
        +
        +
        Specified by:
        +
        getObject in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - the column index (1, 2, ...)
        +
        type - the class of the returned value
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getObject

        +
        public <T> T getObject(java.lang.String columnName,
        +                       java.lang.Class<T> type)
        +                throws java.sql.SQLException
        +
        Returns the value as an Object of the specified type.
        +
        +
        Specified by:
        +
        getObject in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnName - the column name
        +
        type - the class of the returned value
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getObject

        +
        public java.lang.Object getObject(int columnIndex,
        +                                  java.util.Map<java.lang.String,java.lang.Class<?>> map)
        +                           throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getObject in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getObject

        +
        public java.lang.Object getObject(java.lang.String columnLabel,
        +                                  java.util.Map<java.lang.String,java.lang.Class<?>> map)
        +                           throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getObject in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getRef

        +
        public java.sql.Ref getRef(int columnIndex)
        +                    throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getRef in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getRef

        +
        public java.sql.Ref getRef(java.lang.String columnLabel)
        +                    throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getRef in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getRowId

        +
        public java.sql.RowId getRowId(int columnIndex)
        +                        throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getRowId in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getRowId

        +
        public java.sql.RowId getRowId(java.lang.String columnLabel)
        +                        throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getRowId in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getShort

        +
        public short getShort(int columnIndex)
        +               throws java.sql.SQLException
        +
        Returns the value as a short.
        +
        +
        Specified by:
        +
        getShort in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getShort

        +
        public short getShort(java.lang.String columnLabel)
        +               throws java.sql.SQLException
        +
        Returns the value as a short.
        +
        +
        Specified by:
        +
        getShort in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getSQLXML

        +
        public java.sql.SQLXML getSQLXML(int columnIndex)
        +                          throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getSQLXML in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getSQLXML

        +
        public java.sql.SQLXML getSQLXML(java.lang.String columnLabel)
        +                          throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getSQLXML in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getString

        +
        public java.lang.String getString(int columnIndex)
        +                           throws java.sql.SQLException
        +
        Returns the value as a String.
        +
        +
        Specified by:
        +
        getString in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getString

        +
        public java.lang.String getString(java.lang.String columnLabel)
        +                           throws java.sql.SQLException
        +
        Returns the value as a String.
        +
        +
        Specified by:
        +
        getString in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getTime

        +
        public java.sql.Time getTime(int columnIndex)
        +                      throws java.sql.SQLException
        +
        Returns the value as an java.sql.Time.
        +
        +
        Specified by:
        +
        getTime in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getTime

        +
        public java.sql.Time getTime(java.lang.String columnLabel)
        +                      throws java.sql.SQLException
        +
        Returns the value as a java.sql.Time.
        +
        +
        Specified by:
        +
        getTime in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getTime

        +
        public java.sql.Time getTime(int columnIndex,
        +                             java.util.Calendar cal)
        +                      throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getTime in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getTime

        +
        public java.sql.Time getTime(java.lang.String columnLabel,
        +                             java.util.Calendar cal)
        +                      throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getTime in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getTimestamp

        +
        public java.sql.Timestamp getTimestamp(int columnIndex)
        +                                throws java.sql.SQLException
        +
        Returns the value as an java.sql.Timestamp.
        +
        +
        Specified by:
        +
        getTimestamp in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getTimestamp

        +
        public java.sql.Timestamp getTimestamp(java.lang.String columnLabel)
        +                                throws java.sql.SQLException
        +
        Returns the value as a java.sql.Timestamp.
        +
        +
        Specified by:
        +
        getTimestamp in interface java.sql.ResultSet
        +
        Parameters:
        +
        columnLabel - the column label
        +
        Returns:
        +
        the value
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getTimestamp

        +
        public java.sql.Timestamp getTimestamp(int columnIndex,
        +                                       java.util.Calendar cal)
        +                                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getTimestamp in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getTimestamp

        +
        public java.sql.Timestamp getTimestamp(java.lang.String columnLabel,
        +                                       java.util.Calendar cal)
        +                                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getTimestamp in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getUnicodeStream

        +
        @Deprecated
        +public java.io.InputStream getUnicodeStream(int columnIndex)
        +                                                 throws java.sql.SQLException
        +
        Deprecated. INTERNAL
        +
        +
        Specified by:
        +
        getUnicodeStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getUnicodeStream

        +
        @Deprecated
        +public java.io.InputStream getUnicodeStream(java.lang.String columnLabel)
        +                                                 throws java.sql.SQLException
        +
        Deprecated. INTERNAL
        +
        +
        Specified by:
        +
        getUnicodeStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getURL

        +
        public java.net.URL getURL(int columnIndex)
        +                    throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getURL in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getURL

        +
        public java.net.URL getURL(java.lang.String columnLabel)
        +                    throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getURL in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateArray

        +
        public void updateArray(int columnIndex,
        +                        java.sql.Array x)
        +                 throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateArray in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateArray

        +
        public void updateArray(java.lang.String columnLabel,
        +                        java.sql.Array x)
        +                 throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateArray in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateAsciiStream

        +
        public void updateAsciiStream(int columnIndex,
        +                              java.io.InputStream x)
        +                       throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateAsciiStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateAsciiStream

        +
        public void updateAsciiStream(java.lang.String columnLabel,
        +                              java.io.InputStream x)
        +                       throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateAsciiStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateAsciiStream

        +
        public void updateAsciiStream(int columnIndex,
        +                              java.io.InputStream x,
        +                              int length)
        +                       throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateAsciiStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateAsciiStream

        +
        public void updateAsciiStream(java.lang.String columnLabel,
        +                              java.io.InputStream x,
        +                              int length)
        +                       throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateAsciiStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateAsciiStream

        +
        public void updateAsciiStream(int columnIndex,
        +                              java.io.InputStream x,
        +                              long length)
        +                       throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateAsciiStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateAsciiStream

        +
        public void updateAsciiStream(java.lang.String columnLabel,
        +                              java.io.InputStream x,
        +                              long length)
        +                       throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateAsciiStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateBigDecimal

        +
        public void updateBigDecimal(int columnIndex,
        +                             java.math.BigDecimal x)
        +                      throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateBigDecimal in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateBigDecimal

        +
        public void updateBigDecimal(java.lang.String columnLabel,
        +                             java.math.BigDecimal x)
        +                      throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateBigDecimal in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateBinaryStream

        +
        public void updateBinaryStream(int columnIndex,
        +                               java.io.InputStream x)
        +                        throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateBinaryStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateBinaryStream

        +
        public void updateBinaryStream(java.lang.String columnLabel,
        +                               java.io.InputStream x)
        +                        throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateBinaryStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateBinaryStream

        +
        public void updateBinaryStream(int columnIndex,
        +                               java.io.InputStream x,
        +                               int length)
        +                        throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateBinaryStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateBinaryStream

        +
        public void updateBinaryStream(java.lang.String columnLabel,
        +                               java.io.InputStream x,
        +                               int length)
        +                        throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateBinaryStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateBinaryStream

        +
        public void updateBinaryStream(int columnIndex,
        +                               java.io.InputStream x,
        +                               long length)
        +                        throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateBinaryStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateBinaryStream

        +
        public void updateBinaryStream(java.lang.String columnLabel,
        +                               java.io.InputStream x,
        +                               long length)
        +                        throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateBinaryStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateBlob

        +
        public void updateBlob(int columnIndex,
        +                       java.sql.Blob x)
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateBlob in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateBlob

        +
        public void updateBlob(java.lang.String columnLabel,
        +                       java.sql.Blob x)
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateBlob in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateBlob

        +
        public void updateBlob(int columnIndex,
        +                       java.io.InputStream x)
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateBlob in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateBlob

        +
        public void updateBlob(java.lang.String columnLabel,
        +                       java.io.InputStream x)
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateBlob in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateBlob

        +
        public void updateBlob(int columnIndex,
        +                       java.io.InputStream x,
        +                       long length)
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateBlob in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateBlob

        +
        public void updateBlob(java.lang.String columnLabel,
        +                       java.io.InputStream x,
        +                       long length)
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateBlob in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateBoolean

        +
        public void updateBoolean(int columnIndex,
        +                          boolean x)
        +                   throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateBoolean in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateBoolean

        +
        public void updateBoolean(java.lang.String columnLabel,
        +                          boolean x)
        +                   throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateBoolean in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateByte

        +
        public void updateByte(int columnIndex,
        +                       byte x)
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateByte in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateByte

        +
        public void updateByte(java.lang.String columnLabel,
        +                       byte x)
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateByte in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateBytes

        +
        public void updateBytes(int columnIndex,
        +                        byte[] x)
        +                 throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateBytes in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateBytes

        +
        public void updateBytes(java.lang.String columnLabel,
        +                        byte[] x)
        +                 throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateBytes in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateCharacterStream

        +
        public void updateCharacterStream(int columnIndex,
        +                                  java.io.Reader x)
        +                           throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateCharacterStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateCharacterStream

        +
        public void updateCharacterStream(java.lang.String columnLabel,
        +                                  java.io.Reader x)
        +                           throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateCharacterStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateCharacterStream

        +
        public void updateCharacterStream(int columnIndex,
        +                                  java.io.Reader x,
        +                                  int length)
        +                           throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateCharacterStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateCharacterStream

        +
        public void updateCharacterStream(java.lang.String columnLabel,
        +                                  java.io.Reader x,
        +                                  int length)
        +                           throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateCharacterStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateCharacterStream

        +
        public void updateCharacterStream(int columnIndex,
        +                                  java.io.Reader x,
        +                                  long length)
        +                           throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateCharacterStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateCharacterStream

        +
        public void updateCharacterStream(java.lang.String columnLabel,
        +                                  java.io.Reader x,
        +                                  long length)
        +                           throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateCharacterStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateClob

        +
        public void updateClob(int columnIndex,
        +                       java.sql.Clob x)
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateClob in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateClob

        +
        public void updateClob(java.lang.String columnLabel,
        +                       java.sql.Clob x)
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateClob in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateClob

        +
        public void updateClob(int columnIndex,
        +                       java.io.Reader x)
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateClob in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateClob

        +
        public void updateClob(java.lang.String columnLabel,
        +                       java.io.Reader x)
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateClob in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateClob

        +
        public void updateClob(int columnIndex,
        +                       java.io.Reader x,
        +                       long length)
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateClob in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateClob

        +
        public void updateClob(java.lang.String columnLabel,
        +                       java.io.Reader x,
        +                       long length)
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateClob in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateDate

        +
        public void updateDate(int columnIndex,
        +                       java.sql.Date x)
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateDate in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateDate

        +
        public void updateDate(java.lang.String columnLabel,
        +                       java.sql.Date x)
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateDate in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateDouble

        +
        public void updateDouble(int columnIndex,
        +                         double x)
        +                  throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateDouble in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateDouble

        +
        public void updateDouble(java.lang.String columnLabel,
        +                         double x)
        +                  throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateDouble in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateFloat

        +
        public void updateFloat(int columnIndex,
        +                        float x)
        +                 throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateFloat in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateFloat

        +
        public void updateFloat(java.lang.String columnLabel,
        +                        float x)
        +                 throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateFloat in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateInt

        +
        public void updateInt(int columnIndex,
        +                      int x)
        +               throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateInt in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateInt

        +
        public void updateInt(java.lang.String columnLabel,
        +                      int x)
        +               throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateInt in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateLong

        +
        public void updateLong(int columnIndex,
        +                       long x)
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateLong in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateLong

        +
        public void updateLong(java.lang.String columnLabel,
        +                       long x)
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateLong in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateNCharacterStream

        +
        public void updateNCharacterStream(int columnIndex,
        +                                   java.io.Reader x)
        +                            throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateNCharacterStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateNCharacterStream

        +
        public void updateNCharacterStream(java.lang.String columnLabel,
        +                                   java.io.Reader x)
        +                            throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateNCharacterStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateNCharacterStream

        +
        public void updateNCharacterStream(int columnIndex,
        +                                   java.io.Reader x,
        +                                   long length)
        +                            throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateNCharacterStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateNCharacterStream

        +
        public void updateNCharacterStream(java.lang.String columnLabel,
        +                                   java.io.Reader x,
        +                                   long length)
        +                            throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateNCharacterStream in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateNClob

        +
        public void updateNClob(int columnIndex,
        +                        java.sql.NClob x)
        +                 throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateNClob in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateNClob

        +
        public void updateNClob(java.lang.String columnLabel,
        +                        java.sql.NClob x)
        +                 throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateNClob in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateNClob

        +
        public void updateNClob(int columnIndex,
        +                        java.io.Reader x)
        +                 throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateNClob in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateNClob

        +
        public void updateNClob(java.lang.String columnLabel,
        +                        java.io.Reader x)
        +                 throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateNClob in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateNClob

        +
        public void updateNClob(int columnIndex,
        +                        java.io.Reader x,
        +                        long length)
        +                 throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateNClob in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateNClob

        +
        public void updateNClob(java.lang.String columnLabel,
        +                        java.io.Reader x,
        +                        long length)
        +                 throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateNClob in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateNString

        +
        public void updateNString(int columnIndex,
        +                          java.lang.String x)
        +                   throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateNString in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateNString

        +
        public void updateNString(java.lang.String columnLabel,
        +                          java.lang.String x)
        +                   throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateNString in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateNull

        +
        public void updateNull(int columnIndex)
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateNull in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateNull

        +
        public void updateNull(java.lang.String columnLabel)
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateNull in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateObject

        +
        public void updateObject(int columnIndex,
        +                         java.lang.Object x)
        +                  throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateObject in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateObject

        +
        public void updateObject(java.lang.String columnLabel,
        +                         java.lang.Object x)
        +                  throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateObject in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateObject

        +
        public void updateObject(int columnIndex,
        +                         java.lang.Object x,
        +                         int scale)
        +                  throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateObject in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateObject

        +
        public void updateObject(java.lang.String columnLabel,
        +                         java.lang.Object x,
        +                         int scale)
        +                  throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateObject in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateRef

        +
        public void updateRef(int columnIndex,
        +                      java.sql.Ref x)
        +               throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateRef in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateRef

        +
        public void updateRef(java.lang.String columnLabel,
        +                      java.sql.Ref x)
        +               throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateRef in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateRowId

        +
        public void updateRowId(int columnIndex,
        +                        java.sql.RowId x)
        +                 throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateRowId in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateRowId

        +
        public void updateRowId(java.lang.String columnLabel,
        +                        java.sql.RowId x)
        +                 throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateRowId in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateShort

        +
        public void updateShort(int columnIndex,
        +                        short x)
        +                 throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateShort in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateShort

        +
        public void updateShort(java.lang.String columnLabel,
        +                        short x)
        +                 throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateShort in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateSQLXML

        +
        public void updateSQLXML(int columnIndex,
        +                         java.sql.SQLXML x)
        +                  throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateSQLXML in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateSQLXML

        +
        public void updateSQLXML(java.lang.String columnLabel,
        +                         java.sql.SQLXML x)
        +                  throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateSQLXML in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateString

        +
        public void updateString(int columnIndex,
        +                         java.lang.String x)
        +                  throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateString in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateString

        +
        public void updateString(java.lang.String columnLabel,
        +                         java.lang.String x)
        +                  throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateString in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateTime

        +
        public void updateTime(int columnIndex,
        +                       java.sql.Time x)
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateTime in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateTime

        +
        public void updateTime(java.lang.String columnLabel,
        +                       java.sql.Time x)
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateTime in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateTimestamp

        +
        public void updateTimestamp(int columnIndex,
        +                            java.sql.Timestamp x)
        +                     throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateTimestamp in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateTimestamp

        +
        public void updateTimestamp(java.lang.String columnLabel,
        +                            java.sql.Timestamp x)
        +                     throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateTimestamp in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getColumnCount

        +
        public int getColumnCount()
        +
        Returns the column count.
        +
        +
        Specified by:
        +
        getColumnCount in interface java.sql.ResultSetMetaData
        +
        Returns:
        +
        the column count
        +
        +
      • +
      + + + +
        +
      • +

        getColumnDisplaySize

        +
        public int getColumnDisplaySize(int columnIndex)
        +
        Returns 15.
        +
        +
        Specified by:
        +
        getColumnDisplaySize in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        15
        +
        +
      • +
      + + + +
        +
      • +

        getColumnType

        +
        public int getColumnType(int columnIndex)
        +                  throws java.sql.SQLException
        +
        Returns the SQL type.
        +
        +
        Specified by:
        +
        getColumnType in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the SQL type
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getPrecision

        +
        public int getPrecision(int columnIndex)
        +                 throws java.sql.SQLException
        +
        Returns the precision.
        +
        +
        Specified by:
        +
        getPrecision in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the precision
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getScale

        +
        public int getScale(int columnIndex)
        +             throws java.sql.SQLException
        +
        Returns the scale.
        +
        +
        Specified by:
        +
        getScale in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the scale
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        isNullable

        +
        public int isNullable(int columnIndex)
        +
        Returns ResultSetMetaData.columnNullableUnknown.
        +
        +
        Specified by:
        +
        isNullable in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        columnNullableUnknown
        +
        +
      • +
      + + + +
        +
      • +

        isAutoIncrement

        +
        public boolean isAutoIncrement(int columnIndex)
        +
        Returns false.
        +
        +
        Specified by:
        +
        isAutoIncrement in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        isCaseSensitive

        +
        public boolean isCaseSensitive(int columnIndex)
        +
        Returns true.
        +
        +
        Specified by:
        +
        isCaseSensitive in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        isCurrency

        +
        public boolean isCurrency(int columnIndex)
        +
        Returns false.
        +
        +
        Specified by:
        +
        isCurrency in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        isDefinitelyWritable

        +
        public boolean isDefinitelyWritable(int columnIndex)
        +
        Returns false.
        +
        +
        Specified by:
        +
        isDefinitelyWritable in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        isReadOnly

        +
        public boolean isReadOnly(int columnIndex)
        +
        Returns true.
        +
        +
        Specified by:
        +
        isReadOnly in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        isSearchable

        +
        public boolean isSearchable(int columnIndex)
        +
        Returns true.
        +
        +
        Specified by:
        +
        isSearchable in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        isSigned

        +
        public boolean isSigned(int columnIndex)
        +
        Returns true.
        +
        +
        Specified by:
        +
        isSigned in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        true
        +
        +
      • +
      + + + +
        +
      • +

        isWritable

        +
        public boolean isWritable(int columnIndex)
        +
        Returns false.
        +
        +
        Specified by:
        +
        isWritable in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        false
        +
        +
      • +
      + + + +
        +
      • +

        getCatalogName

        +
        public java.lang.String getCatalogName(int columnIndex)
        +
        Returns empty string.
        +
        +
        Specified by:
        +
        getCatalogName in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        empty string
        +
        +
      • +
      + + + +
        +
      • +

        getColumnClassName

        +
        public java.lang.String getColumnClassName(int columnIndex)
        +                                    throws java.sql.SQLException
        +
        Returns the Java class name if this column.
        +
        +
        Specified by:
        +
        getColumnClassName in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the class name
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getColumnLabel

        +
        public java.lang.String getColumnLabel(int columnIndex)
        +                                throws java.sql.SQLException
        +
        Returns the column label.
        +
        +
        Specified by:
        +
        getColumnLabel in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the column label
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getColumnName

        +
        public java.lang.String getColumnName(int columnIndex)
        +                               throws java.sql.SQLException
        +
        Returns the column name.
        +
        +
        Specified by:
        +
        getColumnName in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the column name
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getColumnTypeName

        +
        public java.lang.String getColumnTypeName(int columnIndex)
        +                                   throws java.sql.SQLException
        +
        Returns the data type name of a column.
        +
        +
        Specified by:
        +
        getColumnTypeName in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        the type name
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getSchemaName

        +
        public java.lang.String getSchemaName(int columnIndex)
        +
        Returns empty string.
        +
        +
        Specified by:
        +
        getSchemaName in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        empty string
        +
        +
      • +
      + + + +
        +
      • +

        getTableName

        +
        public java.lang.String getTableName(int columnIndex)
        +
        Returns empty string.
        +
        +
        Specified by:
        +
        getTableName in interface java.sql.ResultSetMetaData
        +
        Parameters:
        +
        columnIndex - (1,2,...)
        +
        Returns:
        +
        empty string
        +
        +
      • +
      + + + +
        +
      • +

        afterLast

        +
        public void afterLast()
        +               throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        afterLast in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        cancelRowUpdates

        +
        public void cancelRowUpdates()
        +                      throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        cancelRowUpdates in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        deleteRow

        +
        public void deleteRow()
        +               throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        deleteRow in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        insertRow

        +
        public void insertRow()
        +               throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        insertRow in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        moveToCurrentRow

        +
        public void moveToCurrentRow()
        +                      throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        moveToCurrentRow in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        moveToInsertRow

        +
        public void moveToInsertRow()
        +                     throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        moveToInsertRow in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        refreshRow

        +
        public void refreshRow()
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        refreshRow in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        updateRow

        +
        public void updateRow()
        +               throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        updateRow in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        first

        +
        public boolean first()
        +              throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        first in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        isAfterLast

        +
        public boolean isAfterLast()
        +                    throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        isAfterLast in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        isBeforeFirst

        +
        public boolean isBeforeFirst()
        +                      throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        isBeforeFirst in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        isFirst

        +
        public boolean isFirst()
        +                throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        isFirst in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        isLast

        +
        public boolean isLast()
        +               throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        isLast in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        last

        +
        public boolean last()
        +             throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        last in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        previous

        +
        public boolean previous()
        +                 throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        previous in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        rowDeleted

        +
        public boolean rowDeleted()
        +                   throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        rowDeleted in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        rowInserted

        +
        public boolean rowInserted()
        +                    throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        rowInserted in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        rowUpdated

        +
        public boolean rowUpdated()
        +                   throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        rowUpdated in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        setFetchDirection

        +
        public void setFetchDirection(int direction)
        +                       throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        setFetchDirection in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        setFetchSize

        +
        public void setFetchSize(int rows)
        +                  throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        setFetchSize in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        absolute

        +
        public boolean absolute(int row)
        +                 throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        absolute in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        relative

        +
        public boolean relative(int offset)
        +                 throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        relative in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getCursorName

        +
        public java.lang.String getCursorName()
        +                               throws java.sql.SQLException
        +
        INTERNAL
        +
        +
        Specified by:
        +
        getCursorName in interface java.sql.ResultSet
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        getHoldability

        +
        public int getHoldability()
        +
        Returns the current result set holdability.
        +
        +
        Specified by:
        +
        getHoldability in interface java.sql.ResultSet
        +
        Returns:
        +
        the holdability
        +
        +
      • +
      + + + +
        +
      • +

        isClosed

        +
        public boolean isClosed()
        +
        Returns whether this result set has been closed.
        +
        +
        Specified by:
        +
        isClosed in interface java.sql.ResultSet
        +
        Returns:
        +
        true if the result set was closed
        +
        +
      • +
      + + + +
        +
      • +

        unwrap

        +
        public <T> T unwrap(java.lang.Class<T> iface)
        +             throws java.sql.SQLException
        +
        Return an object of this class if possible.
        +
        +
        Specified by:
        +
        unwrap in interface java.sql.Wrapper
        +
        Parameters:
        +
        iface - the class
        +
        Returns:
        +
        this
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        isWrapperFor

        +
        public boolean isWrapperFor(java.lang.Class<?> iface)
        +                     throws java.sql.SQLException
        +
        Checks if unwrap can return an object of this class.
        +
        +
        Specified by:
        +
        isWrapperFor in interface java.sql.Wrapper
        +
        Parameters:
        +
        iface - the class
        +
        Returns:
        +
        whether or not the interface is assignable from this class
        +
        Throws:
        +
        java.sql.SQLException
        +
        +
      • +
      + + + +
        +
      • +

        setAutoClose

        +
        public void setAutoClose(boolean autoClose)
        +
        Set the auto-close behavior. If enabled (the default), the result set is + closed after reading the last row.
        +
        +
        Parameters:
        +
        autoClose - the new value
        +
        +
      • +
      + + + +
        +
      • +

        getAutoClose

        +
        public boolean getAutoClose()
        +
        Get the current auto-close behavior.
        +
        +
        Returns:
        +
        the auto-close value
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/tools/SimpleRowSource.html b/h2/docs/javadoc/org/h2/tools/SimpleRowSource.html new file mode 100644 index 0000000..4b4a457 --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/SimpleRowSource.html @@ -0,0 +1,272 @@ + + + + + +SimpleRowSource + + + + + + + + + + + + +
+
org.h2.tools
+

Interface SimpleRowSource

+
+
+
+
    +
  • +
    +
    All Known Implementing Classes:
    +
    Csv
    +
    +
    +
    +
    public interface SimpleRowSource
    +
    This interface is for classes that create rows on demand. + It is used together with SimpleResultSet to create a dynamic result set.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Abstract Methods 
      Modifier and TypeMethod and Description
      voidclose() +
      Close the row source.
      +
      java.lang.Object[]readRow() +
      Get the next row.
      +
      voidreset() +
      Reset the position (before the first row).
      +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        readRow

        +
        java.lang.Object[] readRow()
        +                    throws java.sql.SQLException
        +
        Get the next row. Must return null if no more rows are available.
        +
        +
        Returns:
        +
        the row or null
        +
        Throws:
        +
        java.sql.SQLException - on failure
        +
        +
      • +
      + + + +
        +
      • +

        close

        +
        void close()
        +
        Close the row source.
        +
      • +
      + + + +
        +
      • +

        reset

        +
        void reset()
        +    throws java.sql.SQLException
        +
        Reset the position (before the first row).
        +
        +
        Throws:
        +
        java.sql.SQLException - if this operation is not supported
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/tools/TriggerAdapter.html b/h2/docs/javadoc/org/h2/tools/TriggerAdapter.html new file mode 100644 index 0000000..3a524c3 --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/TriggerAdapter.html @@ -0,0 +1,514 @@ + + + + + +TriggerAdapter + + + + + + + + + + + + +
+
org.h2.tools
+

Class TriggerAdapter

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.tools.TriggerAdapter
    • +
    +
  • +
+
+
    +
  • +
    +
    All Implemented Interfaces:
    +
    Trigger
    +
    +
    +
    +
    public abstract class TriggerAdapter
    +extends java.lang.Object
    +implements Trigger
    +
    An adapter for the trigger interface that allows to use the ResultSet + interface instead of a row array.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Summary

      + + + + + + + + + + + + + + + + + + + + + + + + + + +
      Fields 
      Modifier and TypeField and Description
      protected booleanbefore +
      Whether the fire method is called before or after the operation is + performed.
      +
      protected java.lang.StringschemaName +
      The schema name.
      +
      protected java.lang.StringtableName +
      The name of the table.
      +
      protected java.lang.StringtriggerName +
      The name of the trigger.
      +
      protected inttype +
      The trigger type: INSERT, UPDATE, DELETE, SELECT, or a combination (a bit + field).
      +
      + +
    • +
    + +
      +
    • + + +

      Constructor Summary

      + + + + + + + + +
      Constructors 
      Constructor and Description
      TriggerAdapter() 
      +
    • +
    + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + +
      All Methods Instance Methods Abstract Methods Concrete Methods 
      Modifier and TypeMethod and Description
      voidfire(java.sql.Connection conn, + java.lang.Object[] oldRow, + java.lang.Object[] newRow) +
      This method is called for each triggered action.
      +
      abstract voidfire(java.sql.Connection conn, + java.sql.ResultSet oldRow, + java.sql.ResultSet newRow) +
      This method is called for each triggered action by the default + fire(Connection conn, Object[] oldRow, Object[] newRow) method.
      +
      voidinit(java.sql.Connection conn, + java.lang.String schemaName, + java.lang.String triggerName, + java.lang.String tableName, + boolean before, + int type) +
      This method is called by the database engine once when initializing the + trigger.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      + +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Field Detail

      + + + +
        +
      • +

        schemaName

        +
        protected java.lang.String schemaName
        +
        The schema name.
        +
      • +
      + + + +
        +
      • +

        triggerName

        +
        protected java.lang.String triggerName
        +
        The name of the trigger.
        +
      • +
      + + + +
        +
      • +

        tableName

        +
        protected java.lang.String tableName
        +
        The name of the table.
        +
      • +
      + + + +
        +
      • +

        before

        +
        protected boolean before
        +
        Whether the fire method is called before or after the operation is + performed.
        +
      • +
      + + + +
        +
      • +

        type

        +
        protected int type
        +
        The trigger type: INSERT, UPDATE, DELETE, SELECT, or a combination (a bit + field).
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Constructor Detail

      + + + +
        +
      • +

        TriggerAdapter

        +
        public TriggerAdapter()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        init

        +
        public void init(java.sql.Connection conn,
        +                 java.lang.String schemaName,
        +                 java.lang.String triggerName,
        +                 java.lang.String tableName,
        +                 boolean before,
        +                 int type)
        +          throws java.sql.SQLException
        +
        This method is called by the database engine once when initializing the + trigger. It is called when the trigger is created, as well as when the + database is opened. The default implementation initialized the result + sets.
        +
        +
        Specified by:
        +
        init in interface Trigger
        +
        Parameters:
        +
        conn - a connection to the database
        +
        schemaName - the name of the schema
        +
        triggerName - the name of the trigger used in the CREATE TRIGGER + statement
        +
        tableName - the name of the table
        +
        before - whether the fire method is called before or after the + operation is performed
        +
        type - the operation type: INSERT, UPDATE, DELETE, SELECT, or a + combination (this parameter is a bit field)
        +
        Throws:
        +
        java.sql.SQLException - on SQL exception
        +
        +
      • +
      + + + +
        +
      • +

        fire

        +
        public final void fire(java.sql.Connection conn,
        +                       java.lang.Object[] oldRow,
        +                       java.lang.Object[] newRow)
        +                throws java.sql.SQLException
        +
        Description copied from interface: Trigger
        +
        This method is called for each triggered action. The method is called + immediately when the operation occurred (before it is committed). A + transaction rollback will also rollback the operations that were done + within the trigger, if the operations occurred within the same database. + If the trigger changes state outside the database, a rollback trigger + should be used. +

        + The row arrays contain all columns of the table, in the same order + as defined in the table. +

        +

        + The trigger itself may change the data in the newRow array. +

        +
        +
        Specified by:
        +
        fire in interface Trigger
        +
        Parameters:
        +
        conn - a connection to the database
        +
        oldRow - the old row, or null if no old row is available (for + INSERT)
        +
        newRow - the new row, or null if no new row is available (for + DELETE)
        +
        Throws:
        +
        java.sql.SQLException - if the operation must be undone
        +
        +
      • +
      + + + +
        +
      • +

        fire

        +
        public abstract void fire(java.sql.Connection conn,
        +                          java.sql.ResultSet oldRow,
        +                          java.sql.ResultSet newRow)
        +                   throws java.sql.SQLException
        +
        This method is called for each triggered action by the default + fire(Connection conn, Object[] oldRow, Object[] newRow) method. +

        + For "before" triggers, the new values of the new row may be changed + using the ResultSet.updateX methods. +

        +
        +
        Parameters:
        +
        conn - a connection to the database
        +
        oldRow - the old row, or null if no old row is available (for + INSERT)
        +
        newRow - the new row, or null if no new row is available (for + DELETE)
        +
        Throws:
        +
        java.sql.SQLException - if the operation must be undone
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/tools/Upgrade.html b/h2/docs/javadoc/org/h2/tools/Upgrade.html new file mode 100644 index 0000000..e144138 --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/Upgrade.html @@ -0,0 +1,305 @@ + + + + + +Upgrade + + + + + + + + + + + + +
+
org.h2.tools
+

Class Upgrade

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • org.h2.tools.Upgrade
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public final class Upgrade
    +extends java.lang.Object
    +
    Upgrade utility.
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Summary

      + + + + + + + + + + + + + + + + + + +
      All Methods Static Methods Concrete Methods 
      Modifier and TypeMethod and Description
      static java.sql.DriverloadH2(int version) +
      Loads the specified version of H2 in a separate class loader.
      +
      static voidunloadH2(java.sql.Driver driver) +
      Unloads the specified driver of H2.
      +
      static booleanupgrade(java.lang.String url, + java.util.Properties info, + int version) +
      Performs database upgrade from an older version of H2.
      +
      +
        +
      • + + +

        Methods inherited from class java.lang.Object

        +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
      • +
      +
    • +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      Method Detail

      + + + +
        +
      • +

        upgrade

        +
        public static boolean upgrade(java.lang.String url,
        +                              java.util.Properties info,
        +                              int version)
        +                       throws java.lang.Exception
        +
        Performs database upgrade from an older version of H2.
        +
        +
        Parameters:
        +
        url - the JDBC connection URL
        +
        info - the connection properties ("user", "password", etc).
        +
        version - the old version of H2
        +
        Returns:
        +
        true on success, false if URL is a remote or + in-memory URL
        +
        Throws:
        +
        java.lang.Exception - on failure
        +
        +
      • +
      + + + +
        +
      • +

        loadH2

        +
        public static java.sql.Driver loadH2(int version)
        +                              throws java.io.IOException,
        +                                     java.lang.ReflectiveOperationException
        +
        Loads the specified version of H2 in a separate class loader.
        +
        +
        Parameters:
        +
        version - the version to load
        +
        Returns:
        +
        the driver of the specified version
        +
        Throws:
        +
        java.io.IOException - on I/O exception
        +
        java.lang.ReflectiveOperationException - on exception during initialization of the driver
        +
        +
      • +
      + + + +
        +
      • +

        unloadH2

        +
        public static void unloadH2(java.sql.Driver driver)
        +                     throws java.lang.ReflectiveOperationException
        +
        Unloads the specified driver of H2.
        +
        +
        Parameters:
        +
        driver - the driver to unload
        +
        Throws:
        +
        java.lang.ReflectiveOperationException - on exception
        +
        +
      • +
      +
    • +
    +
  • +
+
+
+ + + + + + + diff --git a/h2/docs/javadoc/org/h2/tools/package-frame.html b/h2/docs/javadoc/org/h2/tools/package-frame.html new file mode 100644 index 0000000..4b38c9c --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/package-frame.html @@ -0,0 +1,43 @@ + + + + + +org.h2.tools + + + + + +

org.h2.tools

+ + + diff --git a/h2/docs/javadoc/org/h2/tools/package-summary.html b/h2/docs/javadoc/org/h2/tools/package-summary.html new file mode 100644 index 0000000..7a46e95 --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/package-summary.html @@ -0,0 +1,290 @@ + + + + + +org.h2.tools + + + + + + + + + + + +
+

Package org.h2.tools

+
+
+ +Various tools.
+
+

See: Description

+
+
+
    +
  • + + + + + + + + + + + + +
    Interface Summary 
    InterfaceDescription
    SimpleRowSource +
    This interface is for classes that create rows on demand.
    +
    +
  • +
  • + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Class Summary 
    ClassDescription
    Backup +
    Creates a backup of a database.
    +
    ChangeFileEncryption +
    Allows changing the database file encryption password or algorithm.
    +
    CompressTool +
    A tool to losslessly compress data, and expand the compressed data again.
    +
    Console +
    Starts the H2 Console (web-) server, as well as the TCP and PG server.
    +
    ConvertTraceFile +
    Converts a .trace.db file to a SQL script and Java source code.
    +
    CreateCluster +
    Creates a cluster from a stand-alone database.
    +
    Csv +
    A facility to read from and write to CSV (comma separated values) files.
    +
    DeleteDbFiles +
    Deletes all files belonging to a database.
    +
    GUIConsole +
    Console for environments with AWT support.
    +
    MultiDimension +
    A tool to help an application execute multi-dimensional range queries.
    +
    Recover +
    Helps recovering a corrupted database.
    +
    Restore +
    Restores a H2 database by extracting the database files from a .zip file.
    +
    RunScript +
    Runs a SQL script against a database.
    +
    Script +
    Creates a SQL script file by extracting the schema and data of a database.
    +
    Server +
    Starts the H2 Console (web-) server, TCP, and PG server.
    +
    Shell +
    Interactive command line tool to access a database using JDBC.
    +
    SimpleResultSet +
    This class is a simple result set and meta data implementation.
    +
    SimpleResultSet.SimpleArray +
    A simple array implementation, + backed by an object array
    +
    TriggerAdapter +
    An adapter for the trigger interface that allows to use the ResultSet + interface instead of a row array.
    +
    Upgrade +
    Upgrade utility.
    +
    +
  • +
+ + + +

Package org.h2.tools Description

+

+ +Various tools. Most tools are command line driven, but not all (for example the CSV tool). + +

+
+ + + + + + diff --git a/h2/docs/javadoc/org/h2/tools/package-tree.html b/h2/docs/javadoc/org/h2/tools/package-tree.html new file mode 100644 index 0000000..1d53af2 --- /dev/null +++ b/h2/docs/javadoc/org/h2/tools/package-tree.html @@ -0,0 +1,165 @@ + + + + + +org.h2.tools Class Hierarchy + + + + + + + + + + + +
+

Hierarchy For Package org.h2.tools

+Package Hierarchies: + +
+
+

Class Hierarchy

+ +

Interface Hierarchy

+ +
+ + + + + + diff --git a/h2/docs/javadoc/overview-frame.html b/h2/docs/javadoc/overview-frame.html new file mode 100644 index 0000000..531ff36 --- /dev/null +++ b/h2/docs/javadoc/overview-frame.html @@ -0,0 +1,26 @@ + + + + + +Overview List + + + + + + + +

 

+ + diff --git a/h2/docs/javadoc/overview-summary.html b/h2/docs/javadoc/overview-summary.html new file mode 100644 index 0000000..6730506 --- /dev/null +++ b/h2/docs/javadoc/overview-summary.html @@ -0,0 +1,177 @@ + + + + + +Overview + + + + + + + + +
+ + + + + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Packages 
PackageDescription
org.h2.api +
+ +Contains interfaces for user-defined extensions, such as triggers and user-defined aggregate functions.
+
org.h2.engine +
+ +Contains high level classes of the database and classes that don't fit in another sub-package.
+
org.h2.fulltext +
+ +The native full text search implementation, and the wrapper for the Lucene full text search implementation.
+
org.h2.jdbc +
+ +Implementation of the JDBC API (package java.sql).
+
org.h2.jdbcx +
+ +Implementation of the extended JDBC API (package javax.sql).
+
org.h2.tools +
+ +Various tools.
+
+
+ +
+ + + + + + + +
+ + + + diff --git a/h2/docs/javadoc/overview-tree.html b/h2/docs/javadoc/overview-tree.html new file mode 100644 index 0000000..bf7b883 --- /dev/null +++ b/h2/docs/javadoc/overview-tree.html @@ -0,0 +1,364 @@ + + + + + +Class Hierarchy + + + + + + + + +
+ + + + + + + +
+ + +
+

Hierarchy For All Packages

+Package Hierarchies: + +
+
+

Class Hierarchy

+ +

Interface Hierarchy

+ +

Enum Hierarchy

+ +
+ +
+ + + + + + + +
+ + + + diff --git a/h2/docs/javadoc/package-list b/h2/docs/javadoc/package-list new file mode 100644 index 0000000..d75d36d --- /dev/null +++ b/h2/docs/javadoc/package-list @@ -0,0 +1,6 @@ +org.h2.api +org.h2.engine +org.h2.fulltext +org.h2.jdbc +org.h2.jdbcx +org.h2.tools diff --git a/h2/docs/javadoc/script.js b/h2/docs/javadoc/script.js new file mode 100644 index 0000000..b346356 --- /dev/null +++ b/h2/docs/javadoc/script.js @@ -0,0 +1,30 @@ +function show(type) +{ + count = 0; + for (var key in methods) { + var row = document.getElementById(key); + if ((methods[key] & type) != 0) { + row.style.display = ''; + row.className = (count++ % 2) ? rowColor : altColor; + } + else + row.style.display = 'none'; + } + updateTabs(type); +} + +function updateTabs(type) +{ + for (var value in tabs) { + var sNode = document.getElementById(tabs[value][0]); + var spanNode = sNode.firstChild; + if (value == type) { + sNode.className = activeTableTab; + spanNode.innerHTML = tabs[value][1]; + } + else { + sNode.className = tableTab; + spanNode.innerHTML = "" + tabs[value][1] + ""; + } + } +} diff --git a/h2/docs/javadoc/serialized-form.html b/h2/docs/javadoc/serialized-form.html new file mode 100644 index 0000000..8cd8fb4 --- /dev/null +++ b/h2/docs/javadoc/serialized-form.html @@ -0,0 +1,549 @@ + + + + + +Serialized Form + + + + + + + + + + + +
+

Serialized Form

+
+
+
    +
  • +

    Package org.h2.jdbc

    +
      +
    • + + +

      Class org.h2.jdbc.JdbcBatchUpdateException extends java.sql.BatchUpdateException implements Serializable

      +
      +
      serialVersionUID:
      +
      1L
      +
      +
    • +
    • + + +

      Class org.h2.jdbc.JdbcSQLDataException extends java.sql.SQLDataException implements Serializable

      +
      +
      serialVersionUID:
      +
      1L
      +
      +
        +
      • +

        Serialized Fields

        +
          +
        • +

          originalMessage

          +
          java.lang.String originalMessage
          +
        • +
        • +

          stackTrace

          +
          java.lang.String stackTrace
          +
        • +
        • +

          message

          +
          java.lang.String message
          +
        • +
        • +

          sql

          +
          java.lang.String sql
          +
        • +
        +
      • +
      +
    • +
    • + + +

      Class org.h2.jdbc.JdbcSQLException extends java.sql.SQLException implements Serializable

      +
      +
      serialVersionUID:
      +
      1L
      +
      +
        +
      • +

        Serialized Fields

        +
          +
        • +

          originalMessage

          +
          java.lang.String originalMessage
          +
        • +
        • +

          stackTrace

          +
          java.lang.String stackTrace
          +
        • +
        • +

          message

          +
          java.lang.String message
          +
        • +
        • +

          sql

          +
          java.lang.String sql
          +
        • +
        +
      • +
      +
    • +
    • + + +

      Class org.h2.jdbc.JdbcSQLFeatureNotSupportedException extends java.sql.SQLFeatureNotSupportedException implements Serializable

      +
      +
      serialVersionUID:
      +
      1L
      +
      +
        +
      • +

        Serialized Fields

        +
          +
        • +

          originalMessage

          +
          java.lang.String originalMessage
          +
        • +
        • +

          stackTrace

          +
          java.lang.String stackTrace
          +
        • +
        • +

          message

          +
          java.lang.String message
          +
        • +
        • +

          sql

          +
          java.lang.String sql
          +
        • +
        +
      • +
      +
    • +
    • + + +

      Class org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException extends java.sql.SQLIntegrityConstraintViolationException implements Serializable

      +
      +
      serialVersionUID:
      +
      1L
      +
      +
        +
      • +

        Serialized Fields

        +
          +
        • +

          originalMessage

          +
          java.lang.String originalMessage
          +
        • +
        • +

          stackTrace

          +
          java.lang.String stackTrace
          +
        • +
        • +

          message

          +
          java.lang.String message
          +
        • +
        • +

          sql

          +
          java.lang.String sql
          +
        • +
        +
      • +
      +
    • +
    • + + +

      Class org.h2.jdbc.JdbcSQLInvalidAuthorizationSpecException extends java.sql.SQLInvalidAuthorizationSpecException implements Serializable

      +
      +
      serialVersionUID:
      +
      1L
      +
      +
        +
      • +

        Serialized Fields

        +
          +
        • +

          originalMessage

          +
          java.lang.String originalMessage
          +
        • +
        • +

          stackTrace

          +
          java.lang.String stackTrace
          +
        • +
        • +

          message

          +
          java.lang.String message
          +
        • +
        • +

          sql

          +
          java.lang.String sql
          +
        • +
        +
      • +
      +
    • +
    • + + +

      Class org.h2.jdbc.JdbcSQLNonTransientConnectionException extends java.sql.SQLNonTransientConnectionException implements Serializable

      +
      +
      serialVersionUID:
      +
      1L
      +
      +
        +
      • +

        Serialized Fields

        +
          +
        • +

          originalMessage

          +
          java.lang.String originalMessage
          +
        • +
        • +

          stackTrace

          +
          java.lang.String stackTrace
          +
        • +
        • +

          message

          +
          java.lang.String message
          +
        • +
        • +

          sql

          +
          java.lang.String sql
          +
        • +
        +
      • +
      +
    • +
    • + + +

      Class org.h2.jdbc.JdbcSQLNonTransientException extends java.sql.SQLNonTransientException implements Serializable

      +
      +
      serialVersionUID:
      +
      1L
      +
      +
        +
      • +

        Serialized Fields

        +
          +
        • +

          originalMessage

          +
          java.lang.String originalMessage
          +
        • +
        • +

          stackTrace

          +
          java.lang.String stackTrace
          +
        • +
        • +

          message

          +
          java.lang.String message
          +
        • +
        • +

          sql

          +
          java.lang.String sql
          +
        • +
        +
      • +
      +
    • +
    • + + +

      Class org.h2.jdbc.JdbcSQLSyntaxErrorException extends java.sql.SQLSyntaxErrorException implements Serializable

      +
      +
      serialVersionUID:
      +
      1L
      +
      +
        +
      • +

        Serialized Fields

        +
          +
        • +

          originalMessage

          +
          java.lang.String originalMessage
          +
        • +
        • +

          stackTrace

          +
          java.lang.String stackTrace
          +
        • +
        • +

          message

          +
          java.lang.String message
          +
        • +
        • +

          sql

          +
          java.lang.String sql
          +
        • +
        +
      • +
      +
    • +
    • + + +

      Class org.h2.jdbc.JdbcSQLTimeoutException extends java.sql.SQLTimeoutException implements Serializable

      +
      +
      serialVersionUID:
      +
      1L
      +
      +
        +
      • +

        Serialized Fields

        +
          +
        • +

          originalMessage

          +
          java.lang.String originalMessage
          +
        • +
        • +

          stackTrace

          +
          java.lang.String stackTrace
          +
        • +
        • +

          message

          +
          java.lang.String message
          +
        • +
        • +

          sql

          +
          java.lang.String sql
          +
        • +
        +
      • +
      +
    • +
    • + + +

      Class org.h2.jdbc.JdbcSQLTransactionRollbackException extends java.sql.SQLTransactionRollbackException implements Serializable

      +
      +
      serialVersionUID:
      +
      1L
      +
      +
        +
      • +

        Serialized Fields

        +
          +
        • +

          originalMessage

          +
          java.lang.String originalMessage
          +
        • +
        • +

          stackTrace

          +
          java.lang.String stackTrace
          +
        • +
        • +

          message

          +
          java.lang.String message
          +
        • +
        • +

          sql

          +
          java.lang.String sql
          +
        • +
        +
      • +
      +
    • +
    • + + +

      Class org.h2.jdbc.JdbcSQLTransientException extends java.sql.SQLTransientException implements Serializable

      +
      +
      serialVersionUID:
      +
      1L
      +
      +
        +
      • +

        Serialized Fields

        +
          +
        • +

          originalMessage

          +
          java.lang.String originalMessage
          +
        • +
        • +

          stackTrace

          +
          java.lang.String stackTrace
          +
        • +
        • +

          message

          +
          java.lang.String message
          +
        • +
        • +

          sql

          +
          java.lang.String sql
          +
        • +
        +
      • +
      +
    • +
    +
  • +
  • +

    Package org.h2.jdbcx

    +
      +
    • + + +

      Class org.h2.jdbcx.JdbcDataSource extends org.h2.message.TraceObject implements Serializable

      +
      +
      serialVersionUID:
      +
      1288136338451857771L
      +
      +
        +
      • +

        Serialization Methods

        +
          +
        • +

          readObject

          +
          private void readObject(java.io.ObjectInputStream in)
          +                 throws java.io.IOException,
          +                        java.lang.ClassNotFoundException
          +
          Called when de-serializing the object.
          +
          +
          Throws:
          +
          java.io.IOException - on failure
          +
          java.lang.ClassNotFoundException - on failure
          +
          +
        • +
        +
      • +
      • +

        Serialized Fields

        +
          +
        • +

          loginTimeout

          +
          int loginTimeout
          +
        • +
        • +

          userName

          +
          java.lang.String userName
          +
        • +
        • +

          passwordChars

          +
          char[] passwordChars
          +
        • +
        • +

          url

          +
          java.lang.String url
          +
        • +
        • +

          description

          +
          java.lang.String description
          +
        • +
        +
      • +
      +
    • +
    +
  • +
+
+ + + + + + diff --git a/h2/docs/javadoc/stylesheet.css b/h2/docs/javadoc/stylesheet.css new file mode 100644 index 0000000..98055b2 --- /dev/null +++ b/h2/docs/javadoc/stylesheet.css @@ -0,0 +1,574 @@ +/* Javadoc style sheet */ +/* +Overall document style +*/ + +@import url('resources/fonts/dejavu.css'); + +body { + background-color:#ffffff; + color:#353833; + font-family:'DejaVu Sans', Arial, Helvetica, sans-serif; + font-size:14px; + margin:0; +} +a:link, a:visited { + text-decoration:none; + color:#4A6782; +} +a:hover, a:focus { + text-decoration:none; + color:#bb7a2a; +} +a:active { + text-decoration:none; + color:#4A6782; +} +a[name] { + color:#353833; +} +a[name]:hover { + text-decoration:none; + color:#353833; +} +pre { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; +} +h1 { + font-size:20px; +} +h2 { + font-size:18px; +} +h3 { + font-size:16px; + font-style:italic; +} +h4 { + font-size:13px; +} +h5 { + font-size:12px; +} +h6 { + font-size:11px; +} +ul { + list-style-type:disc; +} +code, tt { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + padding-top:4px; + margin-top:8px; + line-height:1.4em; +} +dt code { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + padding-top:4px; +} +table tr td dt code { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + vertical-align:top; + padding-top:4px; +} +sup { + font-size:8px; +} +/* +Document title and Copyright styles +*/ +.clear { + clear:both; + height:0px; + overflow:hidden; +} +.aboutLanguage { + float:right; + padding:0px 21px; + font-size:11px; + z-index:200; + margin-top:-9px; +} +.legalCopy { + margin-left:.5em; +} +.bar a, .bar a:link, .bar a:visited, .bar a:active { + color:#FFFFFF; + text-decoration:none; +} +.bar a:hover, .bar a:focus { + color:#bb7a2a; +} +.tab { + background-color:#0066FF; + color:#ffffff; + padding:8px; + width:5em; + font-weight:bold; +} +/* +Navigation bar styles +*/ +.bar { + background-color:#4D7A97; + color:#FFFFFF; + padding:.8em .5em .4em .8em; + height:auto;/*height:1.8em;*/ + font-size:11px; + margin:0; +} +.topNav { + background-color:#4D7A97; + color:#FFFFFF; + float:left; + padding:0; + width:100%; + clear:right; + height:2.8em; + padding-top:10px; + overflow:hidden; + font-size:12px; +} +.bottomNav { + margin-top:10px; + background-color:#4D7A97; + color:#FFFFFF; + float:left; + padding:0; + width:100%; + clear:right; + height:2.8em; + padding-top:10px; + overflow:hidden; + font-size:12px; +} +.subNav { + background-color:#dee3e9; + float:left; + width:100%; + overflow:hidden; + font-size:12px; +} +.subNav div { + clear:left; + float:left; + padding:0 0 5px 6px; + text-transform:uppercase; +} +ul.navList, ul.subNavList { + float:left; + margin:0 25px 0 0; + padding:0; +} +ul.navList li{ + list-style:none; + float:left; + padding: 5px 6px; + text-transform:uppercase; +} +ul.subNavList li{ + list-style:none; + float:left; +} +.topNav a:link, .topNav a:active, .topNav a:visited, .bottomNav a:link, .bottomNav a:active, .bottomNav a:visited { + color:#FFFFFF; + text-decoration:none; + text-transform:uppercase; +} +.topNav a:hover, .bottomNav a:hover { + text-decoration:none; + color:#bb7a2a; + text-transform:uppercase; +} +.navBarCell1Rev { + background-color:#F8981D; + color:#253441; + margin: auto 5px; +} +.skipNav { + position:absolute; + top:auto; + left:-9999px; + overflow:hidden; +} +/* +Page header and footer styles +*/ +.header, .footer { + clear:both; + margin:0 20px; + padding:5px 0 0 0; +} +.indexHeader { + margin:10px; + position:relative; +} +.indexHeader span{ + margin-right:15px; +} +.indexHeader h1 { + font-size:13px; +} +.title { + color:#2c4557; + margin:10px 0; +} +.subTitle { + margin:5px 0 0 0; +} +.header ul { + margin:0 0 15px 0; + padding:0; +} +.footer ul { + margin:20px 0 5px 0; +} +.header ul li, .footer ul li { + list-style:none; + font-size:13px; +} +/* +Heading styles +*/ +div.details ul.blockList ul.blockList ul.blockList li.blockList h4, div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 { + background-color:#dee3e9; + border:1px solid #d0d9e0; + margin:0 0 6px -8px; + padding:7px 5px; +} +ul.blockList ul.blockList ul.blockList li.blockList h3 { + background-color:#dee3e9; + border:1px solid #d0d9e0; + margin:0 0 6px -8px; + padding:7px 5px; +} +ul.blockList ul.blockList li.blockList h3 { + padding:0; + margin:15px 0; +} +ul.blockList li.blockList h2 { + padding:0px 0 20px 0; +} +/* +Page layout container styles +*/ +.contentContainer, .sourceContainer, .classUseContainer, .serializedFormContainer, .constantValuesContainer { + clear:both; + padding:10px 20px; + position:relative; +} +.indexContainer { + margin:10px; + position:relative; + font-size:12px; +} +.indexContainer h2 { + font-size:13px; + padding:0 0 3px 0; +} +.indexContainer ul { + margin:0; + padding:0; +} +.indexContainer ul li { + list-style:none; + padding-top:2px; +} +.contentContainer .description dl dt, .contentContainer .details dl dt, .serializedFormContainer dl dt { + font-size:12px; + font-weight:bold; + margin:10px 0 0 0; + color:#4E4E4E; +} +.contentContainer .description dl dd, .contentContainer .details dl dd, .serializedFormContainer dl dd { + margin:5px 0 10px 0px; + font-size:14px; + font-family:'DejaVu Sans Mono',monospace; +} +.serializedFormContainer dl.nameValue dt { + margin-left:1px; + font-size:1.1em; + display:inline; + font-weight:bold; +} +.serializedFormContainer dl.nameValue dd { + margin:0 0 0 1px; + font-size:1.1em; + display:inline; +} +/* +List styles +*/ +ul.horizontal li { + display:inline; + font-size:0.9em; +} +ul.inheritance { + margin:0; + padding:0; +} +ul.inheritance li { + display:inline; + list-style:none; +} +ul.inheritance li ul.inheritance { + margin-left:15px; + padding-left:15px; + padding-top:1px; +} +ul.blockList, ul.blockListLast { + margin:10px 0 10px 0; + padding:0; +} +ul.blockList li.blockList, ul.blockListLast li.blockList { + list-style:none; + margin-bottom:15px; + line-height:1.4; +} +ul.blockList ul.blockList li.blockList, ul.blockList ul.blockListLast li.blockList { + padding:0px 20px 5px 10px; + border:1px solid #ededed; + background-color:#f8f8f8; +} +ul.blockList ul.blockList ul.blockList li.blockList, ul.blockList ul.blockList ul.blockListLast li.blockList { + padding:0 0 5px 8px; + background-color:#ffffff; + border:none; +} +ul.blockList ul.blockList ul.blockList ul.blockList li.blockList { + margin-left:0; + padding-left:0; + padding-bottom:15px; + border:none; +} +ul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast { + list-style:none; + border-bottom:none; + padding-bottom:0; +} +table tr td dl, table tr td dl dt, table tr td dl dd { + margin-top:0; + margin-bottom:1px; +} +/* +Table styles +*/ +.overviewSummary, .memberSummary, .typeSummary, .useSummary, .constantsSummary, .deprecatedSummary { + width:100%; + border-left:1px solid #EEE; + border-right:1px solid #EEE; + border-bottom:1px solid #EEE; +} +.overviewSummary, .memberSummary { + padding:0px; +} +.overviewSummary caption, .memberSummary caption, .typeSummary caption, +.useSummary caption, .constantsSummary caption, .deprecatedSummary caption { + position:relative; + text-align:left; + background-repeat:no-repeat; + color:#253441; + font-weight:bold; + clear:none; + overflow:hidden; + padding:0px; + padding-top:10px; + padding-left:1px; + margin:0px; + white-space:pre; +} +.overviewSummary caption a:link, .memberSummary caption a:link, .typeSummary caption a:link, +.useSummary caption a:link, .constantsSummary caption a:link, .deprecatedSummary caption a:link, +.overviewSummary caption a:hover, .memberSummary caption a:hover, .typeSummary caption a:hover, +.useSummary caption a:hover, .constantsSummary caption a:hover, .deprecatedSummary caption a:hover, +.overviewSummary caption a:active, .memberSummary caption a:active, .typeSummary caption a:active, +.useSummary caption a:active, .constantsSummary caption a:active, .deprecatedSummary caption a:active, +.overviewSummary caption a:visited, .memberSummary caption a:visited, .typeSummary caption a:visited, +.useSummary caption a:visited, .constantsSummary caption a:visited, .deprecatedSummary caption a:visited { + color:#FFFFFF; +} +.overviewSummary caption span, .memberSummary caption span, .typeSummary caption span, +.useSummary caption span, .constantsSummary caption span, .deprecatedSummary caption span { + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + padding-bottom:7px; + display:inline-block; + float:left; + background-color:#F8981D; + border: none; + height:16px; +} +.memberSummary caption span.activeTableTab span { + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + margin-right:3px; + display:inline-block; + float:left; + background-color:#F8981D; + height:16px; +} +.memberSummary caption span.tableTab span { + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + margin-right:3px; + display:inline-block; + float:left; + background-color:#4D7A97; + height:16px; +} +.memberSummary caption span.tableTab, .memberSummary caption span.activeTableTab { + padding-top:0px; + padding-left:0px; + padding-right:0px; + background-image:none; + float:none; + display:inline; +} +.overviewSummary .tabEnd, .memberSummary .tabEnd, .typeSummary .tabEnd, +.useSummary .tabEnd, .constantsSummary .tabEnd, .deprecatedSummary .tabEnd { + display:none; + width:5px; + position:relative; + float:left; + background-color:#F8981D; +} +.memberSummary .activeTableTab .tabEnd { + display:none; + width:5px; + margin-right:3px; + position:relative; + float:left; + background-color:#F8981D; +} +.memberSummary .tableTab .tabEnd { + display:none; + width:5px; + margin-right:3px; + position:relative; + background-color:#4D7A97; + float:left; + +} +.overviewSummary td, .memberSummary td, .typeSummary td, +.useSummary td, .constantsSummary td, .deprecatedSummary td { + text-align:left; + padding:0px 0px 12px 10px; +} +th.colOne, th.colFirst, th.colLast, .useSummary th, .constantsSummary th, +td.colOne, td.colFirst, td.colLast, .useSummary td, .constantsSummary td{ + vertical-align:top; + padding-right:0px; + padding-top:8px; + padding-bottom:3px; +} +th.colFirst, th.colLast, th.colOne, .constantsSummary th { + background:#dee3e9; + text-align:left; + padding:8px 3px 3px 7px; +} +td.colFirst, th.colFirst { + white-space:nowrap; + font-size:13px; +} +td.colLast, th.colLast { + font-size:13px; +} +td.colOne, th.colOne { + font-size:13px; +} +.overviewSummary td.colFirst, .overviewSummary th.colFirst, +.useSummary td.colFirst, .useSummary th.colFirst, +.overviewSummary td.colOne, .overviewSummary th.colOne, +.memberSummary td.colFirst, .memberSummary th.colFirst, +.memberSummary td.colOne, .memberSummary th.colOne, +.typeSummary td.colFirst{ + width:25%; + vertical-align:top; +} +td.colOne a:link, td.colOne a:active, td.colOne a:visited, td.colOne a:hover, td.colFirst a:link, td.colFirst a:active, td.colFirst a:visited, td.colFirst a:hover, td.colLast a:link, td.colLast a:active, td.colLast a:visited, td.colLast a:hover, .constantValuesContainer td a:link, .constantValuesContainer td a:active, .constantValuesContainer td a:visited, .constantValuesContainer td a:hover { + font-weight:bold; +} +.tableSubHeadingColor { + background-color:#EEEEFF; +} +.altColor { + background-color:#FFFFFF; +} +.rowColor { + background-color:#EEEEEF; +} +/* +Content styles +*/ +.description pre { + margin-top:0; +} +.deprecatedContent { + margin:0; + padding:10px 0; +} +.docSummary { + padding:0; +} + +ul.blockList ul.blockList ul.blockList li.blockList h3 { + font-style:normal; +} + +div.block { + font-size:14px; + font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; +} + +td.colLast div { + padding-top:0px; +} + + +td.colLast a { + padding-bottom:3px; +} +/* +Formatting effect styles +*/ +.sourceLineNo { + color:green; + padding:0 30px 0 0; +} +h1.hidden { + visibility:hidden; + overflow:hidden; + font-size:10px; +} +.block { + display:block; + margin:3px 10px 2px 0px; + color:#474747; +} +.deprecatedLabel, .descfrmTypeLabel, .memberNameLabel, .memberNameLink, +.overrideSpecifyLabel, .packageHierarchyLabel, .paramLabel, .returnLabel, +.seeLabel, .simpleTagLabel, .throwsLabel, .typeNameLabel, .typeNameLink { + font-weight:bold; +} +.deprecationComment, .emphasizedPhrase, .interfaceName { + font-style:italic; +} + +div.block div.block span.deprecationComment, div.block div.block span.emphasizedPhrase, +div.block div.block span.interfaceName { + font-style:normal; +} + +div.contentContainer ul.blockList li.blockList h2{ + padding-bottom:0px; +} diff --git a/h2/service/0_run_server_debug.bat b/h2/service/0_run_server_debug.bat new file mode 100644 index 0000000..a5f5215 --- /dev/null +++ b/h2/service/0_run_server_debug.bat @@ -0,0 +1,68 @@ +@echo off +setlocal +pushd "%~dp0" + +copy /y /b ..\bin\h2-*.jar ..\bin\h2.jar +fc /b ..\bin\h2-*.jar ..\bin\h2.jar +if not errorlevel 1 goto :start +echo Please ensure there is only one h2-*.jar file. +echo Process stopped +pause +goto :end + +:start +rem Copyright (c) 1999, 2006 Tanuki Software Inc. +rem +rem Java Service Wrapper general startup script +rem + +rem +rem Resolve the real path of the wrapper.exe +rem For non NT systems, the _REALPATH and _WRAPPER_CONF values +rem can be hard-coded below and the following test removed. +rem +if "%OS%"=="Windows_NT" goto nt +echo This script only works with NT-based versions of Windows. +goto :end + +:nt +rem +rem Find the application home. +rem +rem %~dp0 is location of current script under NT +set _REALPATH=%~dp0 + +rem Decide on the wrapper binary. +set _WRAPPER_BASE=wrapper +set _WRAPPER_EXE=%_REALPATH%%_WRAPPER_BASE%-windows-x86-32.exe +if exist "%_WRAPPER_EXE%" goto conf +set _WRAPPER_EXE=%_REALPATH%%_WRAPPER_BASE%-windows-x86-64.exe +if exist "%_WRAPPER_EXE%" goto conf +set _WRAPPER_EXE=%_REALPATH%%_WRAPPER_BASE%.exe +if exist "%_WRAPPER_EXE%" goto conf +echo Unable to locate a Wrapper executable using any of the following names: +echo %_REALPATH%%_WRAPPER_BASE%-windows-x86-32.exe +echo %_REALPATH%%_WRAPPER_BASE%-windows-x86-64.exe +echo %_REALPATH%%_WRAPPER_BASE%.exe +pause +goto :end + +:conf +rem +rem Find the wrapper.conf +rem +set _WRAPPER_CONF="%~f1" +if not %_WRAPPER_CONF%=="" goto startup +set _WRAPPER_CONF="%_REALPATH%wrapper.conf" + +:startup +rem +rem Start the Wrapper +rem +"%_WRAPPER_EXE%" -c %_WRAPPER_CONF% +if not errorlevel 1 goto :end +pause + +:end +popd + diff --git a/h2/service/1_install_service.bat b/h2/service/1_install_service.bat new file mode 100644 index 0000000..9bc64e9 --- /dev/null +++ b/h2/service/1_install_service.bat @@ -0,0 +1,61 @@ +@echo off +setlocal +pushd "%~dp0" + +copy /y /b ..\bin\h2-*.jar ..\bin\h2.jar +fc /b ..\bin\h2-*.jar ..\bin\h2.jar +if not errorlevel 1 goto :start +echo Please ensure there is only one h2-*.jar file. +echo Process stopped +pause +goto :end + +:start +rem Copyright (c) 1999, 2006 Tanuki Software Inc. +rem +rem Java Service Wrapper general NT service install script +rem +if "%OS%"=="Windows_NT" goto nt +echo This script only works with NT-based versions of Windows. +goto :end + +:nt +rem +rem Find the application home. +rem +rem %~dp0 is location of current script under NT +set _REALPATH=%~dp0 + +rem Decide on the wrapper binary. +set _WRAPPER_BASE=wrapper +set _WRAPPER_EXE=%_REALPATH%%_WRAPPER_BASE%-windows-x86-32.exe +if exist "%_WRAPPER_EXE%" goto conf +set _WRAPPER_EXE=%_REALPATH%%_WRAPPER_BASE%-windows-x86-64.exe +if exist "%_WRAPPER_EXE%" goto conf +set _WRAPPER_EXE=%_REALPATH%%_WRAPPER_BASE%.exe +if exist "%_WRAPPER_EXE%" goto conf +echo Unable to locate a Wrapper executable using any of the following names: +echo %_REALPATH%%_WRAPPER_BASE%-windows-x86-32.exe +echo %_REALPATH%%_WRAPPER_BASE%-windows-x86-64.exe +echo %_REALPATH%%_WRAPPER_BASE%.exe +pause +goto :end + +:conf +rem +rem Find the wrapper.conf +rem +set _WRAPPER_CONF="%~f1" +if not %_WRAPPER_CONF%=="" goto startup +set _WRAPPER_CONF="%_REALPATH%wrapper.conf" + +:startup +rem +rem Install the Wrapper as an NT service. +rem +"%_WRAPPER_EXE%" -i %_WRAPPER_CONF% +if not errorlevel 1 goto :end +pause + +:end +popd \ No newline at end of file diff --git a/h2/service/2_start_service.bat b/h2/service/2_start_service.bat new file mode 100644 index 0000000..8f0586c --- /dev/null +++ b/h2/service/2_start_service.bat @@ -0,0 +1,3 @@ +sc start "H2DatabaseService" +if not errorlevel 1 goto :eof +pause \ No newline at end of file diff --git a/h2/service/3_start_browser.bat b/h2/service/3_start_browser.bat new file mode 100644 index 0000000..270a2bb --- /dev/null +++ b/h2/service/3_start_browser.bat @@ -0,0 +1 @@ +start http://localhost:8082 diff --git a/h2/service/4_stop_service.bat b/h2/service/4_stop_service.bat new file mode 100644 index 0000000..1911646 --- /dev/null +++ b/h2/service/4_stop_service.bat @@ -0,0 +1 @@ +sc stop "H2DatabaseService" \ No newline at end of file diff --git a/h2/service/5_uninstall_service.bat b/h2/service/5_uninstall_service.bat new file mode 100644 index 0000000..2d74486 --- /dev/null +++ b/h2/service/5_uninstall_service.bat @@ -0,0 +1,53 @@ +@echo off +setlocal +pushd "%~dp0" + +rem Copyright (c) 1999, 2006 Tanuki Software Inc. +rem +rem Java Service Wrapper general NT service uninstall script +rem + +if "%OS%"=="Windows_NT" goto nt +echo This script only works with NT-based versions of Windows. +goto :end + +:nt +rem +rem Find the application home. +rem +rem %~dp0 is location of current script under NT +set _REALPATH=%~dp0 + +rem Decide on the wrapper binary. +set _WRAPPER_BASE=wrapper +set _WRAPPER_EXE=%_REALPATH%%_WRAPPER_BASE%-windows-x86-32.exe +if exist "%_WRAPPER_EXE%" goto conf +set _WRAPPER_EXE=%_REALPATH%%_WRAPPER_BASE%-windows-x86-64.exe +if exist "%_WRAPPER_EXE%" goto conf +set _WRAPPER_EXE=%_REALPATH%%_WRAPPER_BASE%.exe +if exist "%_WRAPPER_EXE%" goto conf +echo Unable to locate a Wrapper executable using any of the following names: +echo %_REALPATH%%_WRAPPER_BASE%-windows-x86-32.exe +echo %_REALPATH%%_WRAPPER_BASE%-windows-x86-64.exe +echo %_REALPATH%%_WRAPPER_BASE%.exe +pause +goto :end + +:conf +rem +rem Find the wrapper.conf +rem +set _WRAPPER_CONF="%~f1" +if not %_WRAPPER_CONF%=="" goto startup +set _WRAPPER_CONF="%_REALPATH%wrapper.conf" + +:startup +rem +rem Uninstall the Wrapper as an NT service. +rem +"%_WRAPPER_EXE%" -r %_WRAPPER_CONF% +if not errorlevel 1 goto :end +pause + +:end +popd diff --git a/h2/service/serviceWrapperLicense.txt b/h2/service/serviceWrapperLicense.txt new file mode 100644 index 0000000..0ec195f --- /dev/null +++ b/h2/service/serviceWrapperLicense.txt @@ -0,0 +1,58 @@ +Java Service Wrapper +http://wrapper.tanukisoftware.org + + +Copyright (c) 1999, 2006 Tanuki Software, Inc. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of the Java Service Wrapper and associated +documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sub-license, +and/or sell copies of the Software, and to permit persons to +whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + + +Portions of the Software have been derived from source code +developed by Silver Egg Technology under the following license: + +BEGIN Silver Egg Techology License ----------------------------------- + + Copyright (c) 2001 Silver Egg Technology + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sub-license, and/or + sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + +END Silver Egg Techology License ------------------------------------- + diff --git a/h2/service/wrapper.conf b/h2/service/wrapper.conf new file mode 100644 index 0000000..449920d --- /dev/null +++ b/h2/service/wrapper.conf @@ -0,0 +1,101 @@ +#******************************************************************** +# Wrapper Properties +#******************************************************************** +# Java Application +wrapper.java.command=java + +# Java Main class. This class must implement the WrapperListener interface +# or guarantee that the WrapperManager class is initialized. Helper +# classes are provided to do this for you. See the Integration section +# of the documentation for details. +wrapper.java.mainclass=org.tanukisoftware.wrapper.WrapperSimpleApp + +# Java Classpath (include wrapper.jar) Add class path elements as +# needed starting from 1 +wrapper.java.classpath.1=wrapper.jar +wrapper.java.classpath.2=../bin/h2.jar +wrapper.java.classpath.3=%H2DRIVERS% +wrapper.java.classpath.4=%CLASSPATH% + +# Java Library Path (location of Wrapper.DLL or libwrapper.so) +wrapper.java.library.path.1= + +# Java Additional Parameters +#wrapper.java.additional.1= + +# Initial Java Heap Size (in MB) +#wrapper.java.initmemory=16 + +# Maximum Java Heap Size (in MB) +#wrapper.java.maxmemory=64 + +# Application parameters. Add parameters as needed starting from 1 +wrapper.app.parameter.1=org.h2.tools.Server +wrapper.app.parameter.2=-tcp +wrapper.app.parameter.3=-web + +#******************************************************************** +# Wrapper Logging Properties +#******************************************************************** +# Format of output for the console. (See docs for formats) +wrapper.console.format=PM + +# Log Level for console output. (See docs for log levels) +wrapper.console.loglevel=INFO + +# Log file to use for wrapper output logging. +wrapper.logfile=wrapper.log + +# Format of output for the log file. (See docs for formats) +wrapper.logfile.format=LPTM + +# Log Level for log file output. (See docs for log levels) +wrapper.logfile.loglevel=INFO + +# Maximum size that the log file will be allowed to grow to before +# the log is rolled. Size is specified in bytes. The default value +# of 0, disables log rolling. May abbreviate with the 'k' (kb) or +# 'm' (mb) suffix. For example: 10m = 10 megabytes. +wrapper.logfile.maxsize=0 + +# Maximum number of rolled log files which will be allowed before old +# files are deleted. The default value of 0 implies no limit. +wrapper.logfile.maxfiles=0 + +# Log Level for sys/event log output. (See docs for log levels) +wrapper.syslog.loglevel=NONE + +#******************************************************************** +# Wrapper Windows Properties +#******************************************************************** +# Title to use when running as a console +wrapper.console.title=H2 Console + +#******************************************************************** +# Wrapper Windows NT/2000/XP Service Properties +#******************************************************************** +# WARNING - Do not modify any of these properties when an application +# using this configuration file has been installed as a service. +# Please uninstall the service before modifying this section. The +# service can then be reinstalled. + +# Name of the service +wrapper.ntservice.name=H2DatabaseService + +# Display name of the service +wrapper.ntservice.displayname=H2 Database Engine Service + +# Description of the service +wrapper.ntservice.description=H2 Database Engine Service (TCP Server and H2 Console Web-Application) + +# Service dependencies. Add dependencies as needed starting from 1 +wrapper.ntservice.dependency.1= + +# Mode in which the service is installed. AUTO_START or DEMAND_START +wrapper.ntservice.starttype=AUTO_START + +# Allow the service to interact with the desktop. +wrapper.ntservice.interactive=false + +# wrapper.ntservice.account= +# wrapper.ntservice.password= diff --git a/h2/service/wrapper.dll b/h2/service/wrapper.dll new file mode 100644 index 0000000000000000000000000000000000000000..cb553c12340ca6924776dde4961b17f0b6484271 GIT binary patch literal 81920 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¤Xge@#o@_8+<@lbHwQ;+s&;_K*J$JxJ@9 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�>_tWuvY1$7+=yvtGbCN9(~^V^me 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⋙?0UZ7k+ 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 literal 0 HcmV?d00001 diff --git a/h2/service/wrapper.jar b/h2/service/wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..4db355bc3408cded7aa5380a7646bb6cb3e404ab GIT binary patch literal 83820 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 SatHij$GLHXm>!r*-uNF9%|jLd literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/help/information_schema.csv b/h2/src/docsrc/help/information_schema.csv new file mode 100644 index 0000000..8008bb1 --- /dev/null +++ b/h2/src/docsrc/help/information_schema.csv @@ -0,0 +1,1022 @@ +# 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 + +"TABLE_NAME","COLUMN_NAME","DESCRIPTION" + +# Tables and views + +"CHECK_CONSTRAINTS",," +Contains CHECK clauses of check and domain constraints. +" + +"COLLATIONS",," +Contains available collations. +" + +"COLUMNS",," +Contains information about columns of tables. +" + +"COLUMN_PRIVILEGES",," +Contains information about privileges of columns. +H2 doesn't have per-column privileges, so this view actually contains privileges of their tables. +" + +"CONSTANTS",," +Contains information about constants. +" + +"CONSTRAINT_COLUMN_USAGE",," +Contains information about columns used in constraints. +" + +"DOMAINS",," +Contains information about domains. +" + +"DOMAIN_CONSTRAINTS",," +Contains basic information about domain constraints. +See also INFORMATION_SCHEMA.CHECK_CONSTRAINTS. +" + +"ELEMENT_TYPES",," +Contains information about types of array elements. +" + +"ENUM_VALUES",," +Contains information about enum values. +" + +"FIELDS",," +Contains information about fields of row values. +" + +"INDEXES",," +Contains information about indexes. +" + +"INDEX_COLUMNS",," +Contains information about columns used in indexes. +" + +"INFORMATION_SCHEMA_CATALOG_NAME",," +Contains a single row with the name of catalog (database name). +" + +"IN_DOUBT",," +Contains information about prepared transactions. +" + +"KEY_COLUMN_USAGE",," +Contains information about columns used by primary key, unique, or referential constraint. +" + +"LOCKS",," +Contains information about tables locked by sessions. +" + +"PARAMETERS",," +Contains information about parameters of routines. +" + +"QUERY_STATISTICS",," +Contains statistics of queries when query statistics gathering is enabled. +" + +"REFERENTIAL_CONSTRAINTS",," +Contains additional information about referential constraints. +" + +"RIGHTS",," +Contains information about granted rights and roles. +" + +"ROLES",," +Contains information about roles. +" + +"ROUTINES",," +Contains information about user-defined routines, including aggregate functions. +" + +"SCHEMATA",," +Contains information about schemas. +" + +"SEQUENCES",," +Contains information about sequences. +" + +"SESSIONS",," +Contains information about sessions. +Only users with ADMIN privileges can see all sessions, other users can see only own session. +" + +"SESSION_STATE",," +Contains the state of the current session. +" + +"SETTINGS",," +Contains values of various settings. +" + +"SYNONYMS",," +Contains information about table synonyms. +" + +"TABLES",," +Contains information about tables. +See also INFORMATION_SCHEMA.COLUMNS. +" + +"TABLE_CONSTRAINTS",," +Contains basic information about table constraints (check, primary key, unique, and referential). +" + +"TABLE_PRIVILEGES",," +Contains information about privileges of tables. +See INFORMATION_SCHEMA.CHECK_CONSTRAINTS, INFORMATION_SCHEMA.KEY_COLUMN_USAGE, +and INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS for additional information. +" + +"TRIGGERS",," +Contains information about triggers. +" + +"USERS",," +Contains information about users. +Only users with ADMIN privileges can see all users, other users can see only themselves. +" + +"VIEWS",," +Contains additional information about views. +See INFORMATION_SCHEMA.TABLES for basic information. +" + +# Common columns with data type information + +,"DATA_TYPE"," +The SQL data type name. +" + +,"CHARACTER_MAXIMUM_LENGTH"," +The maximum length in characters for character string data types. +For binary string data types contains the same value as CHARACTER_OCTET_LENGTH. +" + +,"CHARACTER_OCTET_LENGTH"," +The maximum length in bytes for binary string data types. +For character string data types contains the same value as CHARACTER_MAXIMUM_LENGTH. +" + +,"NUMERIC_PRECISION"," +The precision for numeric data types. +" + +,"NUMERIC_PRECISION_RADIX"," +The radix of precision (2 or 10) for numeric data types. +" + +,"NUMERIC_SCALE"," +The scale for numeric data types. +" + +,"DATETIME_PRECISION"," +The fractional seconds precision for datetime data types. +" + +,"INTERVAL_TYPE"," +The data type of interval qualifier for interval data types. +" + +,"INTERVAL_PRECISION"," +The leading field precision for interval data types. +" + +,"CHARACTER_SET_CATALOG"," +The catalog (database name) for character string data types. +" + +,"CHARACTER_SET_SCHEMA"," +The name of public schema for character string data types. +" + +,"CHARACTER_SET_NAME"," +The 'Unicode' for character string data types. +" + +,"COLLATION_CATALOG"," +The catalog (database name) for character string data types. +" + +,"COLLATION_SCHEMA"," +The name of public schema for character string data types. +" + +,"COLLATION_NAME"," +The name of collation for character string data types. +" + +,"MAXIMUM_CARDINALITY"," +The maximum cardinality for array data types. +" + +,"DTD_IDENTIFIER"," +The data type identifier to read additional information from INFORMATION_SCHEMA.ELEMENT_TYPES for array data types, +INFORMATION_SCHEMA.ENUM_VALUES for ENUM data type, and INFORMATION_SCHEMA.FIELDS for row value data types. +" + +,"DECLARED_DATA_TYPE"," +The declared SQL data type name for numeric data types. +" + +,"DECLARED_NUMERIC_PRECISION"," +The declared precision, if any, for numeric data types. +" + +,"DECLARED_NUMERIC_SCALE"," +The declared scale, if any, for numeric data types. +" + +,"GEOMETRY_TYPE"," +The geometry type constraint, if any, for geometry data types. +" + +,"GEOMETRY_SRID"," +The geometry SRID (Spatial Reference Identifier) constraint, if any, for geometry data types. +" + +# Other common fields + +,"CONSTRAINT_CATALOG"," +The catalog (database name). +" + +,"CONSTRAINT_SCHEMA"," +The schema of the constraint. +" + +,"CONSTRAINT_NAME"," +The name of the constraint. +" + +,"DOMAIN_CATALOG"," +The catalog (database name). +" + +,"DOMAIN_SCHEMA"," +The schema of domain. +" + +,"DOMAIN_NAME"," +The name of domain. +" + +,"INDEX_CATALOG"," +The catalog (database name). +" + +,"INDEX_SCHEMA"," +The schema of the index. +" + +,"INDEX_NAME"," +The name of the index. +" + +,"OBJECT_CATALOG"," +The catalog (database name). +" + +,"OBJECT_SCHEMA"," +The schema of the object. +" + +,"OBJECT_NAME"," +The name of the object. +" + +,"OBJECT_TYPE"," +The TYPE of the object ('CONSTANT', 'DOMAIN', 'TABLE', or 'ROUTINE'). +" + +,"SPECIFIC_CATALOG"," +The catalog (database name). +" + +,"SPECIFIC_SCHEMA"," +The schema of the overloaded version of routine. +" + +,"SPECIFIC_NAME"," +The name of the overloaded version of routine. +" + +,"TABLE_CATALOG"," +The catalog (database name). +" + +,"TABLE_SCHEMA"," +The schema of the table. +" + +,"TABLE_NAME"," +The name of the table. +" + +,"COLUMN_NAME"," +The name of the column. +" + +,"ORDINAL_POSITION"," +The ordinal position (1-based). +" + +,"GRANTOR"," +NULL. +" + +,"GRANTEE"," +The name of grantee. +" + +,"PRIVILEGE_TYPE"," +'SELECT', 'INSERT', 'UPDATE', or 'DELETE'. +" + +,"IS_GRANTABLE"," +Whether grantee may grant rights to this object to others ('YES' or 'NO'). +" + +,"REMARKS"," +Optional remarks. +" + +,"SESSION_ID"," +The identifier of the session. +" + +# Individual fields + +"CHECK_CONSTRAINTS","CHECK_CLAUSE"," +The SQL of CHECK clause. +" + +"COLLATIONS","PAD_ATTRIBUTE"," +'NO PAD'. +" + +"COLLATIONS","LANGUAGE_TAG"," +The language tag. +" + +"COLUMNS","COLUMN_DEFAULT"," +The SQL of DEFAULT expression, if any. +" + +"COLUMNS","IS_NULLABLE"," +Whether column may contain NULL value ('YES' or 'NO'). +" + +"COLUMNS","DOMAIN_CATALOG"," +The catalog for columns with domain. +" + +"COLUMNS","DOMAIN_SCHEMA"," +The schema of domain for columns with domain. +" + +"COLUMNS","DOMAIN_NAME"," +The name of domain for columns with domain. +" + +"COLUMNS","IS_IDENTITY"," +Whether column is an identity column ('YES' or 'NO'). +" + +"COLUMNS","IDENTITY_GENERATION"," +Identity generation ('ALWAYS' or 'BY DEFAULT') for identity columns. +" + +"COLUMNS","IDENTITY_START"," +The initial start value for identity columns. +" + +"COLUMNS","IDENTITY_INCREMENT"," +The increment value for identity columns. +" + +"COLUMNS","IDENTITY_MAXIMUM"," +The maximum value for identity columns. +" + +"COLUMNS","IDENTITY_MINIMUM"," +The minimum value for identity columns. +" + +"COLUMNS","IDENTITY_CYCLE"," +Whether identity values are cycled ('YES' or 'NO') for identity columns. +" + +"COLUMNS","IS_GENERATED"," +Whether column is an generated column ('ALWAYS' or 'NEVER') +" + +"COLUMNS","GENERATION_EXPRESSION"," +The SQL of GENERATED ALWAYS AS expression for generated columns. +" + +"COLUMNS","IDENTITY_BASE"," +The current base value for identity columns. +" + +"COLUMNS","IDENTITY_CACHE"," +The cache size for identity columns. +" + +"COLUMNS","COLUMN_ON_UPDATE"," +The SQL of ON UPDATE expression, if any. +" + +"COLUMNS","IS_VISIBLE"," +Whether column is visible (included into SELECT *). +" + +"COLUMNS","DEFAULT_ON_NULL"," +Whether value of DEFAULT expression is used when NULL value is inserted. +" + +"COLUMNS","SELECTIVITY"," +The selectivity of a column (0-100), used to choose the best index. +" + +"CONSTANTS","CONSTANT_CATALOG"," +The catalog (database name). +" + +"CONSTANTS","CONSTANT_SCHEMA"," +The schema of the constant. +" + +"CONSTANTS","CONSTANT_NAME"," +The name of the constant. +" + +"CONSTANTS","VALUE_DEFINITION"," +The SQL of value. +" + +"DOMAINS","DOMAIN_DEFAULT"," +The SQL of DEFAULT expression, if any. +" + +"DOMAINS","DOMAIN_ON_UPDATE"," +The SQL of ON UPDATE expression, if any. +" + +"DOMAINS","PARENT_DOMAIN_CATALOG"," +The catalog (database name) for domains with parent domain. +" + +"DOMAINS","PARENT_DOMAIN_SCHEMA"," +The schema of parent domain for domains with parent domain. +" + +"DOMAINS","PARENT_DOMAIN_NAME"," +The name of parent domain for domains with parent domain. +" + +"DOMAIN_CONSTRAINTS","IS_DEFERRABLE"," +'NO'. +" + +"DOMAIN_CONSTRAINTS","INITIALLY_DEFERRED"," +'NO'. +" + +"ELEMENT_TYPES","COLLECTION_TYPE_IDENTIFIER"," +The DTD_IDENTIFIER value of the object. +" + +"ENUM_VALUES","ENUM_IDENTIFIER"," +The DTD_IDENTIFIER value of the object. +" + +"ENUM_VALUES","VALUE_NAME"," +The name of enum value. +" + +"ENUM_VALUES","VALUE_ORDINAL"," +The ordinal of enum value. +" + +"FIELDS","ROW_IDENTIFIER"," +The DTD_IDENTIFIER value of the object. +" + +"FIELDS","FIELD_NAME"," +The name of the field of the row value. +" + +"INDEXES","INDEX_TYPE_NAME"," +The type of the index ('PRIMARY KEY', 'UNIQUE INDEX', 'SPATIAL INDEX', etc.) +" + +"INDEXES","IS_GENERATED"," +Whether index is generated by a constraint and belongs to it. +" + +"INDEXES","INDEX_CLASS"," +The Java class name of index implementation. +" + +"INDEX_COLUMNS","ORDERING_SPECIFICATION"," +'ASC' or 'DESC'. +" + +"INDEX_COLUMNS","NULL_ORDERING"," +'FIRST', 'LAST', or NULL. +" + +"INDEX_COLUMNS","IS_UNIQUE"," +Whether this column is a part of unique column list of a unique index (TRUE or FALSE). +" + +"INFORMATION_SCHEMA_CATALOG_NAME","CATALOG_NAME"," +The catalog (database name). +" + +"IN_DOUBT","TRANSACTION_NAME"," +The name of prepared transaction. +" + +"IN_DOUBT","TRANSACTION_STATE"," +The state of prepared transaction ('IN_DOUBT', 'COMMIT', or 'ROLLBACK'). +" + +"KEY_COLUMN_USAGE","POSITION_IN_UNIQUE_CONSTRAINT"," +The ordinal position in the referenced unique constraint (1-based). +" + +"LOCKS","LOCK_TYPE"," +'READ' or 'WRITE'. +" + +"PARAMETERS","PARAMETER_MODE"," +'IN'. +" + +"PARAMETERS","IS_RESULT"," +'NO'. +" + +"PARAMETERS","AS_LOCATOR"," +'YES' for LOBs, 'NO' for others. +" + +"PARAMETERS","PARAMETER_NAME"," +The name of the parameter. +" + +"PARAMETERS","PARAMETER_DEFAULT"," +NULL. +" + +"QUERY_STATISTICS","SQL_STATEMENT"," +The SQL statement. +" + +"QUERY_STATISTICS","EXECUTION_COUNT"," +The execution count. +" + +"QUERY_STATISTICS","MIN_EXECUTION_TIME"," +The minimum execution time in milliseconds. +" + +"QUERY_STATISTICS","MAX_EXECUTION_TIME"," +The maximum execution time in milliseconds. +" + +"QUERY_STATISTICS","CUMULATIVE_EXECUTION_TIME"," +The total execution time in milliseconds. +" + +"QUERY_STATISTICS","AVERAGE_EXECUTION_TIME"," +The average execution time in milliseconds. +" + +"QUERY_STATISTICS","STD_DEV_EXECUTION_TIME"," +The standard deviation of execution time in milliseconds. +" + +"QUERY_STATISTICS","MIN_ROW_COUNT"," +The minimum number of rows. +" + +"QUERY_STATISTICS","MAX_ROW_COUNT"," +The maximum number of rows. +" + +"QUERY_STATISTICS","CUMULATIVE_ROW_COUNT"," +The total number of rows. +" + +"QUERY_STATISTICS","AVERAGE_ROW_COUNT"," +The average number of rows. +" + +"QUERY_STATISTICS","STD_DEV_ROW_COUNT"," +The standard deviation of number of rows. +" + +"REFERENTIAL_CONSTRAINTS","UNIQUE_CONSTRAINT_CATALOG"," +The catalog (database name). +" + +"REFERENTIAL_CONSTRAINTS","UNIQUE_CONSTRAINT_SCHEMA"," +The schema of referenced unique constraint. +" + +"REFERENTIAL_CONSTRAINTS","UNIQUE_CONSTRAINT_NAME"," +The name of referenced unique constraint. +" + +"REFERENTIAL_CONSTRAINTS","MATCH_OPTION"," +'NONE'. +" + +"REFERENTIAL_CONSTRAINTS","UPDATE_RULE"," +The rule for UPDATE in referenced table ('RESTRICT', 'CASCADE', 'SET DEFAULT', or 'SET NULL'). +" + +"REFERENTIAL_CONSTRAINTS","DELETE_RULE"," +The rule for DELETE in referenced table ('RESTRICT', 'CASCADE', 'SET DEFAULT', or 'SET NULL'). +" + +"RIGHTS","GRANTEETYPE"," +'USER' if grantee is a user, 'ROLE' if grantee is a role. +" + +"RIGHTS","GRANTEDROLE"," +The name of the granted role for role grants. +" + +"RIGHTS","RIGHTS"," +The set of rights ('SELECT', 'DELETE', 'INSERT', 'UPDATE', or 'ALTER ANY SCHEMA' separated with ', ') for table grants. +" + +"ROLES","ROLE_NAME"," +The name of the role. +" + +"ROUTINES","ROUTINE_CATALOG"," +The catalog (database name). +" + +"ROUTINES","ROUTINE_SCHEMA"," +The schema of the routine. +" + +"ROUTINES","ROUTINE_NAME"," +The name of the routine. +" + +"ROUTINES","ROUTINE_TYPE"," +'PROCEDURE', 'FUNCTION', or 'AGGREGATE'. +" + +"ROUTINES","ROUTINE_BODY"," +'EXTERNAL'. +" + +"ROUTINES","ROUTINE_DEFINITION"," +Source code or NULL if not applicable or user doesn't have ADMIN privileges. +" + +"ROUTINES","EXTERNAL_NAME"," +The name of the class or method. +" + +"ROUTINES","EXTERNAL_LANGUAGE"," +'JAVA'. +" + +"ROUTINES","PARAMETER_STYLE"," +'GENERAL'. +" + +"ROUTINES","IS_DETERMINISTIC"," +Whether routine is deterministic ('YES' or 'NO'). +" + +"SCHEMATA","CATALOG_NAME"," +The catalog (database name). +" + +"SCHEMATA","SCHEMA_NAME"," +The schema name. +" + +"SCHEMATA","SCHEMA_OWNER"," +The name of schema owner. +" + +"SCHEMATA","DEFAULT_CHARACTER_SET_CATALOG"," +The catalog (database name). +" + +"SCHEMATA","DEFAULT_CHARACTER_SET_SCHEMA"," +The name of public schema. +" + +"SCHEMATA","DEFAULT_CHARACTER_SET_NAME"," +'Unicode'. +" + +"SCHEMATA","SQL_PATH"," +NULL. +" + +"SCHEMATA","DEFAULT_COLLATION_NAME"," +The name of database collation. +" + +"SEQUENCES","SEQUENCE_CATALOG"," +The catalog (database name). +" + +"SEQUENCES","SEQUENCE_SCHEMA"," +The schema of the sequence. +" + +"SEQUENCES","SEQUENCE_NAME"," +The name of the sequence. +" + +"SEQUENCES","START_VALUE"," +The initial start value. +" + +"SEQUENCES","MINIMUM_VALUE"," +The maximum value. +" + +"SEQUENCES","MAXIMUM_VALUE"," +The minimum value. +" + +"SEQUENCES","INCREMENT"," +The increment value. +" + +"SEQUENCES","CYCLE_OPTION"," +Whether values are cycled ('YES' or 'NO'). +" + +"SEQUENCES","BASE_VALUE"," +The current base value. +" + +"SEQUENCES","CACHE"," +The cache size. +" + +"SESSIONS","USER_NAME"," +The name of the user. +" + +"SESSIONS","SERVER"," +The name of the server used by remote connection. +" + +"SESSIONS","CLIENT_ADDR"," +The client address and port used by remote connection. +" + +"SESSIONS","CLIENT_INFO"," +Additional client information provided by remote connection. +" + +"SESSIONS","SESSION_START"," +When this session was started. +" + +"SESSIONS","ISOLATION_LEVEL"," +The isolation level of the session ('READ UNCOMMITTED', 'READ COMMITTED', 'REPEATABLE READ', 'SNAPSHOT', +or 'SERIALIZABLE'). +" + +"SESSIONS","EXECUTING_STATEMENT"," +The currently executing statement, if any. +" + +"SESSIONS","EXECUTING_STATEMENT_START"," +When the current command was started, if any. +" + +"SESSIONS","CONTAINS_UNCOMMITTED"," +Whether the session contains any uncommitted changes. +" + +"SESSIONS","SESSION_STATE"," +The state of the session ('RUNNING', 'SLEEP', etc.) +" + +"SESSIONS","BLOCKER_ID"," +The identifier or blocking session, if any. +" + +"SESSIONS","SLEEP_SINCE"," +When the last command was finished if session is sleeping. +" + +"SESSION_STATE","STATE_KEY"," +The key. +" + +"SESSION_STATE","STATE_COMMAND"," +The SQL command that can be used to restore the state. +" + +"SETTINGS","SETTING_NAME"," +The name of the setting. +" + +"SETTINGS","SETTING_VALUE"," +The value of the setting. +" + +"SYNONYMS","SYNONYM_CATALOG"," +The catalog (database name). +" + +"SYNONYMS","SYNONYM_SCHEMA"," +The schema of the synonym. +" + +"SYNONYMS","SYNONYM_NAME"," +The name of the synonym. +" + +"SYNONYMS","SYNONYM_FOR"," +The name of the referenced table. +" + +"SYNONYMS","SYNONYM_FOR_SCHEMA"," +The name of the referenced schema. +" + +"SYNONYMS","TYPE_NAME"," +'SYNONYM'. +" + +"SYNONYMS","STATUS"," +'VALID'. +" + +"TABLES","TABLE_TYPE"," +'BASE TABLE', 'VIEW', 'GLOBAL TEMPORARY', or 'LOCAL TEMPORARY'. +" + +"TABLES","IS_INSERTABLE_INTO"," +Whether the table is insertable ('YES' or 'NO'). +" + +"TABLES","COMMIT_ACTION"," +'DELETE', 'DROP', or 'PRESERVE' for temporary tables. +" + +"TABLES","STORAGE_TYPE"," +'CACHED' for regular persisted tables, 'MEMORY' for in-memory tables or persisted tables with in-memory indexes, +'GLOBAL TEMPORARY' or 'LOCAL TEMPORARY' for temporary tables, 'EXTERNAL' for tables with external table engines, +or 'TABLE LINK' for linked tables. +" + +"TABLES","LAST_MODIFICATION"," +The sequence number of the last modification, if applicable. +" + +"TABLES","TABLE_CLASS"," +The Java class name of implementation. +" + +"TABLES","ROW_COUNT_ESTIMATE"," +The approximate number of rows if known or some default value if unknown. +For regular tables contains the total number of rows including the uncommitted rows. +" + +"TABLE_CONSTRAINTS","CONSTRAINT_TYPE"," +'CHECK', 'PRIMARY KEY', 'UNIQUE', or 'REFERENTIAL'. +" + +"TABLE_CONSTRAINTS","IS_DEFERRABLE"," +'NO'. +" + +"TABLE_CONSTRAINTS","INITIALLY_DEFERRED"," +'NO'. +" + +"TABLE_CONSTRAINTS","ENFORCED"," +'YES' for non-referential constants. +'YES' for referential constants when checks for referential integrity are enabled for the both referenced and +referencing tables and 'NO' when they are disabled. +" + +"TABLE_PRIVILEGES","WITH_HIERARCHY"," +'NO'. +" + +"TRIGGERS","TRIGGER_CATALOG"," +The catalog (database name). +" + +"TRIGGERS","TRIGGER_SCHEMA"," +The schema of the trigger. +" + +"TRIGGERS","TRIGGER_NAME"," +The name of the trigger. +" + +"TRIGGERS","EVENT_MANIPULATION"," +'INSERT', 'UPDATE', 'DELETE', or 'SELECT'. +" + +"TRIGGERS","EVENT_OBJECT_CATALOG"," +The catalog (database name). +" + +"TRIGGERS","EVENT_OBJECT_SCHEMA"," +The schema of the table. +" + +"TRIGGERS","EVENT_OBJECT_TABLE"," +The name of the table. +" + +"TRIGGERS","ACTION_ORIENTATION"," +'ROW' or 'STATEMENT'. +" + +"TRIGGERS","ACTION_TIMING"," +'BEFORE', 'AFTER', or 'INSTEAD OF'. +" + +"TRIGGERS","IS_ROLLBACK"," +Whether this trigger is executed on rollback. +" + +"TRIGGERS","JAVA_CLASS"," +The Java class name. +" + +"TRIGGERS","QUEUE_SIZE"," +The size of the queue (is not actually used). +" + +"TRIGGERS","NO_WAIT"," +Whether trigger is defined with NO WAIT clause (is not actually used). +" + +"USERS","USER_NAME"," +The name of the user. +" + +"USERS","IS_ADMIN"," +Whether user has ADMIN privileges. +" + +"VIEWS","VIEW_DEFINITION"," +The query SQL, if applicable. +" + +"VIEWS","CHECK_OPTION"," +'NONE'. +" + +"VIEWS","IS_UPDATABLE"," +'NO'. +" + +"VIEWS","INSERTABLE_INTO"," +'NO'. +" + +"VIEWS","IS_TRIGGER_UPDATABLE"," +Whether the view has INSTEAD OF trigger for UPDATE ('YES' or 'NO'). +" + +"VIEWS","IS_TRIGGER_DELETABLE"," +Whether the view has INSTEAD OF trigger for DELETE ('YES' or 'NO'). +" + +"VIEWS","IS_TRIGGER_INSERTABLE_INTO"," +Whether the view has INSTEAD OF trigger for INSERT ('YES' or 'NO'). +" + +"VIEWS","STATUS"," +'VALID' or 'INVALID'. +" diff --git a/h2/src/docsrc/html/advanced.html b/h2/src/docsrc/html/advanced.html new file mode 100644 index 0000000..b4e1d3f --- /dev/null +++ b/h2/src/docsrc/html/advanced.html @@ -0,0 +1,1909 @@ + + + + + + + +Advanced + + + + + +
+ + +

Advanced

+ + Result Sets
+ + Large Objects
+ + Linked Tables
+ + Spatial Features
+ + Recursive Queries
+ + Updatable Views
+ + Transaction Isolation
+ + Multi-Version Concurrency Control (MVCC)
+ + Clustering / High Availability
+ + Two Phase Commit
+ + Compatibility
+ + Keywords / Reserved Words
+ + Standards Compliance
+ + Run as Windows Service
+ + ODBC Driver
+ + ACID
+ + Durability Problems
+ + Using the Recover Tool
+ + File Locking Protocols
+ + Using Passwords
+ + Password Hash
+ + Protection against SQL Injection
+ + Protection against Remote Access
+ + Restricting Class Loading and Usage
+ + Security Protocols
+ + TLS Connections
+ + Universally Unique Identifiers (UUID)
+ + Settings Read from System Properties
+ + Setting the Server Bind Address
+ + Pluggable File System
+ + Split File System
+ + Java Objects Serialization
+ + Limits and Limitations
+ + Glossary and Links
+ +

Result Sets

+ +

Statements that Return a Result Set

+

+The following statements return a result set: SELECT, TABLE, VALUES, +EXPLAIN, CALL, SCRIPT, SHOW, HELP. +EXECUTE may return either a result set or an update count. +Result of a WITH statement depends on inner command. +All other statements return an update count. +

+ +

Limiting the Number of Rows

+

+Before the result is returned to the application, all rows are read by the database. +Server side cursors are not supported currently. +If only the first few rows are interesting for the application, then the +result set size should be limited to improve the performance. +This can be done using FETCH in a query +(example: SELECT * FROM TEST FETCH FIRST 100 ROWS ONLY), +or by using Statement.setMaxRows(max). +

+ +

Large Result Sets and External Sorting

+

+For large result set, the result is buffered to disk. The threshold can be defined using the statement +SET MAX_MEMORY_ROWS. +If ORDER BY is used, the sorting is done using an +external sort algorithm. +In this case, each block of rows is sorted using quick sort, then written to disk; +when reading the data, the blocks are merged together. +

+ +

Large Objects

+ +

Storing and Reading Large Objects

+

+If it is possible that the objects don't fit into memory, then the data type +CLOB (for textual data) or BLOB (for binary data) should be used. +For these data types, the objects are not fully read into memory, by using streams. +To store a BLOB, use PreparedStatement.setBinaryStream. To store a CLOB, use +PreparedStatement.setCharacterStream. To read a BLOB, use ResultSet.getBinaryStream, +and to read a CLOB, use ResultSet.getCharacterStream. +When using the client/server mode, large BLOB and CLOB data is stored in a temporary file +on the client side. +

+ +

When to use CLOB/BLOB

+

+By default, this database stores large LOB (CLOB and BLOB) objects separate from the main table data. +Small LOB objects are stored in-place, the threshold can be set using +MAX_LENGTH_INPLACE_LOB, +but there is still an overhead to use CLOB/BLOB. Because of this, BLOB and CLOB +should never be used for columns with a maximum size below about 200 bytes. +The best threshold depends on the use case; reading in-place objects is faster +than reading from separate files, but slows down the performance of operations +that don't involve this column. +

+ +

Linked Tables

+

+This database supports linked tables, which means tables that don't exist in the current database but +are just links to another database. To create such a link, use the +CREATE LINKED TABLE statement: +

+
+CREATE LINKED TABLE LINK('org.postgresql.Driver', 'jdbc:postgresql:test', 'sa', 'sa', 'TEST');
+
+

+You can then access the table in the usual way. +Whenever the linked table is accessed, the database issues specific queries over JDBC. +Using the example above, if you issue the query SELECT * FROM LINK WHERE ID=1, +then the following query is run against the PostgreSQL database: SELECT * FROM TEST WHERE ID=?. +The same happens for insert and update statements. +Only simple statements are executed against the target database, that means no joins +(queries that contain joins are converted to simple queries). +Prepared statements are used where possible. +

+

+To view the statements that are executed against the target table, set the trace level to 3. +

+

+If multiple linked tables point to the same database (using the same database URL), the connection +is shared. To disable this, set the system property h2.shareLinkedConnections=false. +

+

+The statement CREATE LINKED TABLE +supports an optional schema name parameter. +

+

+The following are not supported because they may result in a deadlock: +creating a linked table to the same database, +and creating a linked table to another database using the server mode if the other database is open in the same server +(use the embedded mode instead). +

+

+Data types that are not supported in H2 are also not supported for linked tables, +for example unsigned data types if the value is outside the range of the signed type. +In such cases, the columns needs to be cast to a supported type. +

+ +

Updatable Views

+

+By default, views are not updatable. +To make a view updatable, use an "instead of" trigger as follows: +

+
+CREATE TRIGGER TRIGGER_NAME
+INSTEAD OF INSERT, UPDATE, DELETE
+ON VIEW_NAME
+FOR EACH ROW CALL "com.acme.TriggerClassName";
+
+

+Update the base table(s) within the trigger as required. +For details, see the sample application org.h2.samples.UpdatableView. +

+ +

Transaction Isolation

+

+Please note that most data definition language (DDL) statements, +such as "create table", commit the current transaction. +See the Commands for details. +

+

+Transaction isolation is provided for all data manipulation language (DML) statements. +

+

+H2 supports read uncommitted, read committed, repeatable read, snapshot, +and serializable (partially, see below) isolation levels: +

+
    +
  • Read uncommitted
    + Dirty reads, non-repeatable reads, and phantom reads are possible. + To enable, execute the SQL statement + SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ UNCOMMITTED +
  • +
  • Read committed
    + This is the default level. + Dirty reads aren't possible; non-repeatable reads and phantom reads are possible. + To enable, execute the SQL statement + SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED +
  • +
  • Repeatable read
    + Dirty reads and non-repeatable reads aren't possible, phantom reads are possible. + To enable, execute the SQL statement + SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ +
  • +
  • Snapshot
    + Dirty reads, non-repeatable reads, and phantom reads aren't possible. + This isolation level is very expensive in databases with many tables. + To enable, execute the SQL statement + SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SNAPSHOT +
  • +
  • Serializable
    + Dirty reads, non-repeatable reads, and phantom reads aren't possible. + Note that this isolation level in H2 currently doesn't ensure equivalence of concurrent and serializable execution + of transactions that perform write operations. + This isolation level is very expensive in databases with many tables. + To enable, execute the SQL statement + SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE +
  • +
+
    +
  • Dirty reads
    + Means a connection can read uncommitted changes made by another connection.
    + Possible with: read uncommitted. +
  • Non-repeatable reads
    + A connection reads a row, another connection changes a row and commits, + and the first connection re-reads the same row and gets the new result.
    + Possible with: read uncommitted, read committed. +
  • Phantom reads
    + A connection reads a set of rows using a condition, another connection + inserts a row that falls in this condition and commits, then the first connection + re-reads using the same condition and gets the new row.
    + Possible with: read uncommitted, read committed, repeatable read. +
  • +
+ +

Multi-Version Concurrency Control (MVCC)

+

+Insert and update operations only issue a shared lock on the table. +An exclusive lock is still used when adding or removing columns or when dropping the table. +Connections only 'see' committed data, and own changes. That means, if connection A updates +a row but doesn't commit this change yet, connection B will see the old value. +Only when the change is committed, the new value is visible by other connections +(read committed). If multiple connections concurrently try to lock or update the same row, the +database waits until it can apply the change, but at most until the lock timeout expires. +

+ +

Lock Timeout

+

+If a connection cannot get a lock on an object, the connection waits for some amount +of time (the lock timeout). During this time, hopefully the connection holding the +lock commits and it is then possible to get the lock. If this is not possible because +the other connection does not release the lock for some time, the unsuccessful +connection will get a lock timeout exception. The lock timeout can be set individually +for each connection. +

+ +

Clustering / High Availability

+

+This database supports a simple clustering / high availability mechanism. The architecture is: +two database servers run on two different computers, and on both computers is a copy of the +same database. If both servers run, each database operation is executed on both computers. +If one server fails (power, hardware or network failure), the other server can still continue to work. +From this point on, the operations will be executed only on one server until the other server +is back up. +

+Clustering can only be used in the server mode (the embedded mode does not support clustering). +The cluster can be re-created using the CreateCluster tool without stopping +the remaining server. Applications that are still connected are automatically disconnected, +however when appending ;AUTO_RECONNECT=TRUE, they will recover from that. +

+To initialize the cluster, use the following steps: +

+
    +
  • Create a database +
  • Use the CreateCluster tool to copy the database to + another location and initialize the clustering. + Afterwards, you have two databases containing the same data. +
  • Start two servers (one for each copy of the database) +
  • You are now ready to connect to the databases with the client application(s) +
+ +

Using the CreateCluster Tool

+

+To understand how clustering works, please try out the following example. +In this example, the two databases reside on the same computer, but usually, the +databases will be on different servers. +

+
    +
  • Create two directories: server1, server2. + Each directory will simulate a directory on a computer. +
  • Start a TCP server pointing to the first directory. + You can do this using the command line: +
    +java org.h2.tools.Server
    +    -tcp -tcpPort 9101
    +    -baseDir server1
    +
    +
  • Start a second TCP server pointing to the second directory. + This will simulate a server running on a second (redundant) computer. + You can do this using the command line: +
    +java org.h2.tools.Server
    +    -tcp -tcpPort 9102
    +    -baseDir server2
    +
    +
  • Use the CreateCluster tool to initialize clustering. + This will automatically create a new, empty database if it does not exist. + Run the tool on the command line: +
    +java org.h2.tools.CreateCluster
    +    -urlSource jdbc:h2:tcp://localhost:9101/~/test
    +    -urlTarget jdbc:h2:tcp://localhost:9102/~/test
    +    -user sa
    +    -serverList localhost:9101,localhost:9102
    +
    +
  • You can now connect to the databases using +an application or the H2 Console using the JDBC URL +jdbc:h2:tcp://localhost:9101,localhost:9102/~/test +
  • If you stop a server (by killing the process), +you will notice that the other machine continues to work, +and therefore the database is still accessible. +
  • To restore the cluster, you first need to delete the +database that failed, then restart the server that was stopped, +and re-run the CreateCluster tool. +
+ +

Detect Which Cluster Instances are Running

+

+To find out which cluster nodes are currently running, execute the following SQL statement: +

+
+SELECT SETTING_VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME = 'CLUSTER'
+
+

+If the result is '' (two single quotes), then the cluster mode is disabled. Otherwise, the list of +servers is returned, enclosed in single quote. Example: 'server1:9191,server2:9191'. +

+ +

+It is also possible to get the list of servers by using Connection.getClientInfo(). +

+ +

+The property list returned from getClientInfo() contains a numServers property that returns the +number of servers that are in the connection list. To get the actual servers, getClientInfo() also has +properties server0..serverX, where serverX is the number of servers minus 1. +

+ +

+Example: To get the 2nd server in the connection list one uses getClientInfo('server1'). +Note: The serverX property only returns IP addresses and ports and not hostnames. +

+ +

Clustering Algorithm and Limitations

+

+Read-only queries are only executed against the first cluster node, but all other statements are +executed against all nodes. There is currently no load balancing made to avoid problems with +transactions. The following functions may yield different results on different cluster nodes and must be +executed with care: UUID(), RANDOM_UUID(), SECURE_RAND(), SESSION_ID(), +MEMORY_FREE(), MEMORY_USED(), CSVREAD(), CSVWRITE(), RAND() [when not using a seed]. +Those functions should not be used directly in modifying statements +(for example INSERT, UPDATE, MERGE). However, they can be used +in read-only statements and the result can then be used for modifying statements. +Identity columns aren't supported. +Instead, sequence values need to be manually requested and then used to insert data (using two statements). +

+

+When using the cluster modes, result sets are read fully in memory by the client, so that +there is no problem if the server dies that executed the query. Result sets must fit in memory +on the client side. +

+

+The SQL statement SET AUTOCOMMIT FALSE is not supported in the cluster mode. +To disable autocommit, the method Connection.setAutoCommit(false) needs to be called. +

+

+It is possible that a transaction from one connection overtakes a transaction from a different connection. +Depending on the operations, this might result in different results, for example when +conditionally incrementing a value in a row. +

+ +

Two Phase Commit

+

+The two phase commit protocol is supported. 2-phase-commit works as follows: +

+
    +
  • Autocommit needs to be switched off +
  • A transaction is started, for example by inserting a row +
  • The transaction is marked 'prepared' by executing the SQL statement + PREPARE COMMIT transactionName +
  • The transaction can now be committed or rolled back +
  • If a problem occurs before the transaction was successfully committed or rolled back + (for example because a network problem occurred), the transaction is in the state 'in-doubt' +
  • When re-connecting to the database, the in-doubt transactions can be listed + with SELECT * FROM INFORMATION_SCHEMA.IN_DOUBT +
  • Each transaction in this list must now be committed or rolled back by executing + COMMIT TRANSACTION transactionName or + ROLLBACK TRANSACTION transactionName +
  • The database needs to be closed and re-opened to apply the changes +
+ +

Compatibility

+

+This database is (up to a certain point) compatible to other databases such as HSQLDB, MySQL and PostgreSQL. +There are certain areas where H2 is incompatible. +

+ +

Transaction Commit when Autocommit is On

+

+At this time, this database engine commits a transaction (if autocommit is switched on) just before returning the result. +For a query, this means the transaction is committed even before the application scans through the result set, and before the result set is closed. +Other database engines may commit the transaction in this case when the result set is closed. +

+ +

Keywords / Reserved Words

+

+There is a list of keywords that can't be used as identifiers (table names, column names and so on), +unless they are quoted (surrounded with double quotes). +The following tokens are keywords in H2: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeywordH2SQL Standard
2016201120082003199992
ALL+++++++
AND+++++++
ANY+++++++
ARRAY++++++
AS+++++++
ASYMMETRIC+++++NR
AUTHORIZATION+++++++
BETWEEN+++++NR+
BOTHCS++++++
CASE+++++++
CAST+++++++
CHECK+++++++
CONSTRAINT+++++++
CROSS+++++++
CURRENT_CATALOG++++
CURRENT_DATE+++++++
CURRENT_PATH++++++
CURRENT_ROLE++++++
CURRENT_SCHEMA++++
CURRENT_TIME+++++++
CURRENT_TIMESTAMP+++++++
CURRENT_USER+++++++
DAY+++++++
DEFAULT+++++++
DISTINCT+++++++
ELSE+++++++
END+++++++
EXCEPT+++++++
EXISTS+++++NR+
FALSE+++++++
FETCH+++++++
FILTERCS++++
FOR+++++++
FOREIGN+++++++
FROM+++++++
FULL+++++++
GROUP+++++++
GROUPSCS++
HAVING+++++++
HOUR+++++++
IF+
ILIKECS
IN+++++++
INNER+++++++
INTERSECT+++++++
INTERVAL+++++++
IS+++++++
JOIN+++++++
KEY+NRNRNRNR++
LEADINGCS++++++
LEFT+++++++
LIKE+++++++
LIMITMS+
LOCALTIME++++++
LOCALTIMESTAMP++++++
MINUSMS
MINUTE+++++++
MONTH+++++++
NATURAL+++++++
NOT+++++++
NULL+++++++
OFFSET++++
ON+++++++
OR+++++++
ORDER+++++++
OVERCS++++
PARTITIONCS++++
PRIMARY+++++++
QUALIFY+
RANGECS++++
REGEXPCS
RIGHT+++++++
ROW++++++
ROWNUM+
ROWSCS++++++
SECOND+++++++
SELECT+++++++
SESSION_USER++++++
SET+++++++
SOME+++++++
SYMMETRIC+++++NR
SYSTEM_USER+++++++
TABLE+++++++
TO+++++++
TOPMS
CS
TRAILINGCS++++++
TRUE+++++++
UESCAPE+++++
UNION+++++++
UNIQUE+++++++
UNKNOWN+++++++
USER+++++++
USING+++++++
VALUE+++++++
VALUES+++++++
WHEN+++++++
WHERE+++++++
WINDOW+++++
WITH+++++++
YEAR+++++++
_ROWID_+
+

+Mode-sensitive keywords (MS) are keywords only in some compatibility modes. +

+
  • LIMIT is a keywords only in Regular, Legacy, DB2, HSQLDB, MariaDB, MySQL, and PostgreSQL compatibility modes. +It is an identifier in Strict, Derby, MSSQLServer, and Oracle compatibility modes. +
  • MINUS is a keyword only in Regular, Legacy, DB2, HSQLDB, and Oracle compatibility modes. +It is an identifier in Strict, Derby, MSSQLServer, MariaDB, MySQL, and PostgreSQL compatibility modes. +
  • TOP is a context-sensitive keyword (can be either keyword or identifier) +only in Regular, Legacy, HSQLDB, and MSSQLServer compatibility modes. +It is an identifier unconditionally in Strict, Derby, DB2, MariaDB, MySQL, Oracle, and PostgreSQL compatibility modes. +
+

+Context-sensitive keywords (CS) can be used as identifiers in some places, +but cannot be used as identifiers in others. +Normal keywords (+) are always treated as keywords. +

+

+Most keywords in H2 are also reserved (+) or non-reserved (NR) words in the SQL Standard. +Newer versions of H2 may have more keywords than older ones. +Reserved words from the SQL Standard are potential candidates for keywords in future versions. +

+ +

There is a compatibility setting +SET NON_KEYWORDS +that can be used as a temporary workaround for applications that use keywords as unquoted identifiers.

+ +

Standards Compliance

+

+This database tries to be as much standard compliant as possible. For the SQL language, ANSI/ISO is the main +standard. There are several versions that refer to the release date: SQL-92, SQL:1999, and SQL:2003. +Unfortunately, the standard documentation is not freely available. Another problem is that important features +are not standardized. Whenever this is the case, this database tries to be compatible to other databases. +

+ +

Supported Character Sets, Character Encoding, and Unicode

+

+H2 internally uses Unicode, and supports all character encoding systems and character sets supported by the virtual machine you use. +

+ +

Run as Windows Service

+

+Using a native wrapper / adapter, Java applications can be run as a Windows Service. +There are various tools available to do that. The Java Service Wrapper from +Tanuki Software, Inc. +is included in the installation. Batch files are provided to install, start, stop and uninstall the +H2 Database Engine Service. This service contains the TCP Server and the H2 Console web application. +The batch files are located in the directory h2/service. +

+

+The service wrapper bundled with H2 is a 32-bit version. +To use a 64-bit version of Windows (x64), you need to use a 64-bit version of the wrapper, +for example the one from + +Simon Krenger. +

+

+When running the database as a service, absolute path should be used. +Using ~ in the database URL is problematic in this case, +because it means to use the home directory of the current user. +The service might run without or with the wrong user, so that +the database files might end up in an unexpected place. +

+ +

Install the Service

+

+The service needs to be registered as a Windows Service first. +To do that, double click on 1_install_service.bat. +If successful, a command prompt window will pop up and disappear immediately. If not, a message will appear. +

+ +

Start the Service

+

+You can start the H2 Database Engine Service using the service manager of Windows, +or by double clicking on 2_start_service.bat. +Please note that the batch file does not print an error message if the service is not installed. +

+ +

Connect to the H2 Console

+

+After installing and starting the service, you can connect to the H2 Console application using a browser. +Double clicking on 3_start_browser.bat to do that. The +default port (8082) is hard coded in the batch file. +

+ +

Stop the Service

+

+To stop the service, double click on 4_stop_service.bat. +Please note that the batch file does not print an error message if the service is not installed or started. +

+ +

Uninstall the Service

+

+To uninstall the service, double click on 5_uninstall_service.bat. +If successful, a command prompt window will pop up and disappear immediately. If not, a message will appear. +

+ +

Additional JDBC drivers

+

+To use other databases (for example MySQL), the location of the JDBC drivers of those databases need to be +added to the environment variables H2DRIVERS or CLASSPATH before +installing the service. Multiple drivers can be set; each entry needs to be separated with a ; +(Windows) or : (other operating systems). Spaces in the path names are supported. +The settings must not be quoted. +

+ +

ODBC Driver

+

+This database does not come with its own ODBC driver at this time, +but it supports the PostgreSQL network protocol. +Therefore, the PostgreSQL ODBC driver can be used. +Support for the PostgreSQL network protocol is quite new and should be viewed +as experimental. It should not be used for production applications. +

+

+To use the PostgreSQL ODBC driver on 64 bit versions of Windows, +first run c:/windows/syswow64/odbcad32.exe. +At this point you set up your DSN just like you would on any other system. +See also: +Re: ODBC Driver on Windows 64 bit +

+ +

ODBC Installation

+

+First, the ODBC driver must be installed. +Any recent PostgreSQL ODBC driver should work, however version 8.2 (psqlodbc-08_02*) or newer is recommended. +The Windows version of the PostgreSQL ODBC driver is available at +https://www.postgresql.org/ftp/odbc/versions/msi/. +

+ +

Starting the Server

+

+After installing the ODBC driver, start the H2 Server using the command line: +

+
+java -cp h2*.jar org.h2.tools.Server
+
+

+The PG Server (PG for PostgreSQL protocol) is started as well. +By default, databases are stored in the current working directory where the server is started. +Use -baseDir to save databases in another directory, for example the user home directory: +

+
+java -cp h2*.jar org.h2.tools.Server -baseDir ~
+
+

+The PG server can be started and stopped from within a Java application as follows: +

+
+Server server = Server.createPgServer("-baseDir", "~");
+server.start();
+...
+server.stop();
+
+

+By default, only connections from localhost are allowed. To allow remote connections, use +-pgAllowOthers when starting the server. +

+

+To map an ODBC database name to a different JDBC database name, +use the option -key when starting the server. +Please note only one mapping is allowed. The following will map the ODBC database named +TEST to the database URL jdbc:h2:~/data/test;cipher=aes: +

+
+java org.h2.tools.Server -pg -key TEST "~/data/test;cipher=aes"
+
+ +

ODBC Configuration

+

+After installing the driver, a new Data Source must be added. In Windows, +run odbcad32.exe to open the Data Source Administrator. Then click on 'Add...' +and select the PostgreSQL Unicode driver. Then click 'Finish'. +You will be able to change the connection properties. +The property column represents the property key in the odbc.ini file +(which may be different from the GUI). +

+ + + + + + + + + + +
PropertyExampleRemarks
Data SourceH2 TestThe name of the ODBC Data Source
Database~/test;ifexists=true + The database name. This can include connections settings. + By default, the database is stored in the current working directory + where the Server is started except when the -baseDir setting is used. + The name must be at least 3 characters. +
ServernamelocalhostThe server name or IP address.
By default, only remote connections are allowed
UsernamesaThe database user name.
SSLfalse (disabled)At this time, SSL is not supported.
Port5435The port where the PG Server is listening.
PasswordsaThe database password.
+

+To improve performance, please enable 'server side prepare' under Options / Datasource / Page 2 / Server side prepare. +

+

+Afterwards, you may use this data source. +

+ +

PG Protocol Support Limitations

+

+At this time, only a subset of the PostgreSQL network protocol is implemented. +Also, there may be compatibility problems on the SQL level, with the catalog, or with text encoding. +Problems are fixed as they are found. +Currently, statements can not be canceled when using the PG protocol. +Also, H2 does not provide index meta over ODBC. +

+

+PostgreSQL ODBC Driver Setup requires a database password; that means it +is not possible to connect to H2 databases without password. This is a limitation +of the ODBC driver. +

+ +

Security Considerations

+

+Currently, the PG Server does not support challenge response or encrypt passwords. +This may be a problem if an attacker can listen to the data transferred between the ODBC driver +and the server, because the password is readable to the attacker. +Also, it is currently not possible to use encrypted SSL connections. +Therefore the ODBC driver should not be used where security is important. +

+

+The first connection that opens a database using the PostgreSQL server needs to be an administrator user. +Subsequent connections don't need to be opened by an administrator. +

+ +

Using Microsoft Access

+

+When using Microsoft Access to edit data in a linked H2 table, you may need to enable the following option: +Tools - Options - Edit/Find - ODBC fields. +

+ +

ACID

+

+In the database world, ACID stands for: +

+
    +
  • Atomicity: transactions must be atomic, meaning either all tasks are performed or none. +
  • Consistency: all operations must comply with the defined constraints. +
  • Isolation: transactions must be isolated from each other. +
  • Durability: committed transaction will not be lost. +
+ +

Atomicity

+

+Transactions in this database are always atomic. +

+ +

Consistency

+

+By default, this database is always in a consistent state. +Referential integrity rules are enforced except when +explicitly disabled. +

+ +

Isolation

+

+For H2, as with most other database systems, the default isolation level is 'read committed'. +This provides better performance, but also means that transactions are not completely isolated. +H2 supports the transaction isolation levels 'read uncommitted', 'read committed', 'repeatable read', +and 'serializable'. +

+ +

Durability

+

+This database does not guarantee that all committed transactions survive a power failure. +Tests show that all databases sometimes lose transactions on power failure (for details, see below). +Where losing transactions is not acceptable, a laptop or UPS (uninterruptible power supply) should be used. +If durability is required for all possible cases of hardware failure, clustering should be used, +such as the H2 clustering mode. +

+ +

Durability Problems

+

+Complete durability means all committed transaction survive a power failure. +Some databases claim they can guarantee durability, but such claims are wrong. +A durability test was run against H2, HSQLDB, PostgreSQL, and Derby. +All of those databases sometimes lose committed transactions. +The test is included in the H2 download, see org.h2.test.poweroff.Test. +

+ +

Ways to (Not) Achieve Durability

+

+Making sure that committed transactions are not lost is more complicated than it seems first. +To guarantee complete durability, a database must ensure that the log record is on the hard drive +before the commit call returns. To do that, databases use different methods. One +is to use the 'synchronous write' file access mode. In Java, RandomAccessFile +supports the modes rws and rwd: +

+
    +
  • rwd: every update to the file's content is written synchronously to the underlying storage device. +
  • rws: in addition to rwd, every update to the metadata is written synchronously.
  • +
+

+A test (org.h2.test.poweroff.TestWrite) with one of those modes achieves +around 50 thousand write operations per second. +Even when the operating system write buffer is disabled, the write rate is around 50 thousand operations per second. +This feature does not force changes to disk because it does not flush all buffers. +The test updates the same byte in the file again and again. If the hard drive was able to write at this rate, +then the disk would need to make at least 50 thousand revolutions per second, or 3 million RPM +(revolutions per minute). There are no such hard drives. The hard drive used for the test is about 7200 RPM, +or about 120 revolutions per second. There is an overhead, so the maximum write rate must be lower than that. +

+

+Calling fsync flushes the buffers. There are two ways to do that in Java: +

+
    +
  • FileDescriptor.sync(). The documentation says that this forces all system +buffers to synchronize with the underlying device. +This method is supposed to return after all in-memory modified copies of buffers associated with this file descriptor +have been written to the physical medium. +
  • FileChannel.force(). This method is supposed +to force any updates to this channel's file to be written to the storage device that contains it. +
+

+By default, MySQL calls fsync for each commit. When using one of those methods, only around 60 write operations +per second can be achieved, which is consistent with the RPM rate of the hard drive used. +Unfortunately, even when calling FileDescriptor.sync() or +FileChannel.force(), +data is not always persisted to the hard drive, because most hard drives do not obey +fsync(): see +Your Hard Drive Lies to You. +In Mac OS X, fsync does not flush hard drive buffers. See +Bad fsync?. +So the situation is confusing, and tests prove there is a problem. +

+

+Trying to flush hard drive buffers is hard, and if you do the performance is very bad. +First you need to make sure that the hard drive actually flushes all buffers. +Tests show that this can not be done in a reliable way. +Then the maximum number of transactions is around 60 per second. +Because of those reasons, the default behavior of H2 is to delay writing committed transactions. +

+

+In H2, after a power failure, a bit more than one second of committed transactions may be lost. +To change the behavior, use SET WRITE_DELAY and +CHECKPOINT SYNC. +Most other databases support commit delay as well. +In the performance comparison, commit delay was used for all databases that support it. +

+ +

Running the Durability Test

+

+To test the durability / non-durability of this and other databases, you can use the test application +in the package org.h2.test.poweroff. +Two computers with network connection are required to run this test. +One computer just listens, while the test application is run (and power is cut) on the other computer. +The computer with the listener application opens a TCP/IP port and listens for an incoming connection. +The second computer first connects to the listener, and then created the databases and starts inserting +records. The connection is set to 'autocommit', which means after each inserted record a commit is performed +automatically. Afterwards, the test computer notifies the listener that this record was inserted successfully. +The listener computer displays the last inserted record number every 10 seconds. Now, switch off the power +manually, then restart the computer, and run the application again. You will find out that in most cases, +none of the databases contains all the records that the listener computer knows about. For details, please +consult the source code of the listener and test application. +

+ +

Using the Recover Tool

+

+The Recover tool can be used to extract the contents of a database file, even if the database is corrupted. +It also extracts the content of the transaction log and large objects (CLOB or BLOB). +To run the tool, type on the command line: +

+
+java -cp h2*.jar org.h2.tools.Recover
+
+

+For each database in the current directory, a text file will be created. +This file contains raw insert statements (for the data) and data definition (DDL) statements to recreate +the schema of the database. This file can be executed using the RunScript tool or a +RUNSCRIPT SQL statement. +The script includes at least one +CREATE USER statement. If you run the script against a database that was created with the same +user, or if there are conflicting users, running the script will fail. Consider running the script +against a database that was created with a user name that is not in the script. +

+

+The Recover tool creates a SQL script from database file. It also processes the transaction log. +

+

+To verify the database can recover at any time, append ;RECOVER_TEST=64 +to the database URL in your test environment. This will simulate an application crash after each 64 writes to the database file. +A log file named databaseName.h2.db.log is created that lists the operations. +The recovery is tested using an in-memory file system, that means it may require a larger heap setting. +

+ +

File Locking Protocols

+

+Multiple concurrent connections to the same database are supported, however a database file +can only be open for reading and writing (in embedded mode) by one process at the same time. +Otherwise, the processes would overwrite each others data and corrupt the database file. +To protect against this problem, whenever a database is opened, a lock file is created +to signal other processes that the database is in use. If the database is closed, or if the process that opened +the database stops normally, this lock file is deleted. +

+In special cases (if the process did not terminate normally, for example because +there was a power failure), the lock file is not deleted by the process that created it. +That means the existence of the lock file is not a safe protocol for file locking. +However, this software uses a challenge-response protocol to protect the database +files. There are two methods (algorithms) implemented to provide both security +(that is, the same database files cannot be opened by two processes at the same time) +and simplicity (that is, the lock file does not need to be deleted manually by the user). +The two methods are 'file method' and 'socket methods'. +

+

+The file locking protocols (except the file locking method 'FS') +have the following limitation: if a shared file system is used, +and the machine with the lock owner is sent to sleep (standby or hibernate), +another machine may take over. If the machine that originally held the lock +wakes up, the database may become corrupt. If this situation can occur, +the application must ensure the database is closed when the application +is put to sleep. +

+ +

File Locking Method 'File'

+

+The default method for database file locking for version 1.3 and older is the 'File Method'. +The algorithm is: +

+
    +
  • If the lock file does not exist, it is created (using the atomic operation +File.createNewFile). +Then, the process waits a little bit (20 ms) and checks the file again. If the file was changed +during this time, the operation is aborted. This protects against a race condition +when one process deletes the lock file just after another one create it, and a third process creates +the file again. It does not occur if there are only two writers. +
  • +If the file can be created, a random number is inserted together with the locking method +('file'). Afterwards, a watchdog thread is started that +checks regularly (every second once by default) if the file was deleted or modified by +another (challenger) thread / process. Whenever that occurs, the file is overwritten with the +old data. The watchdog thread runs with high priority so that a change to the lock file does +not get through undetected even if the system is very busy. However, the watchdog thread +does use very little resources (CPU time), because it waits most of the time. Also, the watchdog only reads from the hard disk +and does not write to it. +
  • +If the lock file exists and was recently modified, the process waits for some time (up to two seconds). +If it was still changed, an exception is thrown (database is locked). This is done to eliminate race conditions with many concurrent +writers. Afterwards, the file is overwritten with a new version (challenge). +After that, the thread waits for 2 seconds. +If there is a watchdog thread protecting the file, he will overwrite the change +and this process will fail to lock the database. +However, if there is no watchdog thread, the lock file will still be as written by +this thread. In this case, the file is deleted and atomically created again. +The watchdog thread is started in this case and the file is locked. +
+

+This algorithm is tested with over 100 concurrent threads. In some cases, when there are +many concurrent threads trying to lock the database, they block each other (meaning +the file cannot be locked by any of them) for some time. However, the file never gets +locked by two threads at the same time. However using that many concurrent threads +/ processes is not the common use case. Generally, an application should throw an error +to the user if it cannot open a database, and not try again in a (fast) loop. +

+ +

File Locking Method 'Socket'

+

+There is a second locking mechanism implemented, but disabled by default. +To use it, append ;FILE_LOCK=SOCKET to the database URL. +The algorithm is: +

+
    +
  • If the lock file does not exist, it is created. +Then a server socket is opened on a defined port, and kept open. +The port and IP address of the process that opened the database is written +into the lock file. +
  • If the lock file exists, and the lock method is 'file', then the software switches +to the 'file' method. +
  • If the lock file exists, and the lock method is 'socket', then the process +checks if the port is in use. If the original process is still running, the port is in use +and this process throws an exception (database is in use). If the original process +died (for example due to a power failure, or abnormal termination of the virtual machine), +then the port was released. The new process deletes the lock file and starts again. +
+

+This method does not require a watchdog thread actively polling (reading) the same +file every second. The problem with this method is, if the file is stored on a network +share, two processes (running on different computers) could still open the same +database files, if they do not have a direct TCP/IP connection. +

+ +

File Locking Method 'FS'

+

+This is the default mode for version 1.4 and newer. +This database file locking mechanism uses native file system lock on the database file. +No *.lock.db file is created in this case, and no background thread is started. +This mechanism may not work on all systems as expected. +Some systems allow to lock the same file multiple times within the same virtual machine, +and on some system native file locking is not supported or files are not unlocked after a power failure. +

+

+To enable this feature, append ;FILE_LOCK=FS to the database URL. +

+

+This feature is relatively new. When using it for production, please ensure +your system does in fact lock files as expected. +

+ +

Using Passwords

+ +

Using Secure Passwords

+

+Remember that weak passwords can be broken regardless of the encryption and security protocols. +Don't use passwords that can be found in a dictionary. Appending numbers does not make passwords +secure. A way to create good passwords that can be remembered is: take the first +letters of a sentence, use upper and lower case characters, and creatively include special characters +(but it's more important to use a long password than to use special characters). +Example: +

+i'sE2rtPiUKtT from the sentence it's easy to remember this password if you know the trick. +

+ +

Passwords: Using Char Arrays instead of Strings

+

+Java strings are immutable objects and cannot be safely 'destroyed' by the application. +After creating a string, it will remain in the main memory of the computer at least +until it is garbage collected. The garbage collection cannot be controlled by the application, +and even if it is garbage collected the data may still remain in memory. +It might also be possible that the part of memory containing the password +is swapped to disk (if not enough main memory is available), which is +a problem if the attacker has access to the swap file of the operating system. +

+It is a good idea to use char arrays instead of strings for passwords. +Char arrays can be cleared (filled with zeros) after use, and therefore the +password will not be stored in the swap file. +

+This database supports using char arrays instead of string to pass user and file passwords. +The following code can be used to do that: +

+
+import java.sql.*;
+import java.util.*;
+public class Test {
+    public static void main(String[] args) throws Exception {
+        String url = "jdbc:h2:~/test";
+        Properties prop = new Properties();
+        prop.setProperty("user", "sa");
+        System.out.print("Password?");
+        char[] password = System.console().readPassword();
+        prop.put("password", password);
+        Connection conn = null;
+        try {
+            conn = DriverManager.getConnection(url, prop);
+        } finally {
+            Arrays.fill(password, (char) 0);
+        }
+        conn.close();
+    }
+}
+
+

+When using Swing, use javax.swing.JPasswordField. +

+ +

Passing the User Name and/or Password in the URL

+

+Instead of passing the user name as a separate parameter as in + +Connection conn = DriverManager. + getConnection("jdbc:h2:~/test", "sa", "123"); + +the user name (and/or password) can be supplied in the URL itself: + +Connection conn = DriverManager. + getConnection("jdbc:h2:~/test;USER=sa;PASSWORD=123"); + +The settings in the URL override the settings passed as a separate parameter. +

+ +

Password Hash

+

+Sometimes the database password needs to be stored in a configuration file +(for example in the web.xml file). +In addition to connecting with the plain text password, +this database supports connecting with the password hash. +This means that only the hash of the password (and not the plain text password) +needs to be stored in the configuration file. +This will only protect others from reading or re-constructing the plain text password +(even if they have access to the configuration file); +it does not protect others from accessing the database using the password hash. +

+

+To connect using the password hash instead of plain text password, append +;PASSWORD_HASH=TRUE to the database URL, and replace +the password with the password hash. To calculate the password hash from a plain text password, +run the following command within the H2 Console tool: +@password_hash <upperCaseUserName> <password>. +As an example, if the user name is sa and the password is +test, run the command +@password_hash SA test. +Then use the resulting password hash as you would use the plain text password. +When using an encrypted database, then the user password and file password +need to be hashed separately. To calculate the hash of the file password, run: +@password_hash file <filePassword>. +

+ +

Protection against SQL Injection

+

What is SQL Injection

+

+This database engine provides a solution for the security vulnerability known as 'SQL Injection'. +Here is a short description of what SQL injection means. +Some applications build SQL statements with embedded user input such as: +

+
+String sql = "SELECT * FROM USERS WHERE PASSWORD='"+pwd+"'";
+ResultSet rs = conn.createStatement().executeQuery(sql);
+
+

+If this mechanism is used anywhere in the application, and user input is not correctly filtered or encoded, +it is possible for a user to inject SQL functionality or statements by using specially built input +such as (in this example) this password: ' OR ''='. +In this case the statement becomes: +

+
+SELECT * FROM USERS WHERE PASSWORD='' OR ''='';
+
+

+Which is always true no matter what the password stored in the database is. +For more information about SQL Injection, see Glossary and Links. +

+ +

Disabling Literals

+

+SQL Injection is not possible if user input is not directly embedded in SQL statements. +A simple solution for the problem above is to use a prepared statement: +

+
+String sql = "SELECT * FROM USERS WHERE PASSWORD=?";
+PreparedStatement prep = conn.prepareStatement(sql);
+prep.setString(1, pwd);
+ResultSet rs = prep.executeQuery();
+
+

+This database provides a way to enforce usage of parameters when passing user input +to the database. This is done by disabling embedded literals in SQL statements. +To do this, execute the statement: +

+
+SET ALLOW_LITERALS NONE;
+
+

+Afterwards, SQL statements with text and number literals are not allowed any more. +That means, SQL statement of the form WHERE NAME='abc' +or WHERE CustomerId=10 will fail. +It is still possible to use prepared statements and parameters as described above. Also, it is still possible to generate +SQL statements dynamically, and use the Statement API, as long as the SQL statements +do not include literals. +There is also a second mode where number literals are allowed: +SET ALLOW_LITERALS NUMBERS. +To allow all literals, execute SET ALLOW_LITERALS ALL +(this is the default setting). Literals can only be enabled or disabled by an administrator. +

+ +

Using Constants

+

+Disabling literals also means disabling hard-coded 'constant' literals. This database supports +defining constants using the CREATE CONSTANT command. +Constants can be defined only +when literals are enabled, but used even when literals are disabled. To avoid name clashes +with column names, constants can be defined in other schemas: +

+
+CREATE SCHEMA CONST AUTHORIZATION SA;
+CREATE CONSTANT CONST.ACTIVE VALUE 'Active';
+CREATE CONSTANT CONST.INACTIVE VALUE 'Inactive';
+SELECT * FROM USERS WHERE TYPE=CONST.ACTIVE;
+
+

+Even when literals are enabled, it is better to use constants instead +of hard-coded number or text literals in queries or views. With constants, typos are found at compile +time, the source code is easier to understand and change. +

+ +

Using the ZERO() Function

+

+It is not required to create a constant for the number 0 as there is already a built-in function ZERO(): +

+
+SELECT * FROM USERS WHERE LENGTH(PASSWORD)=ZERO();
+
+ +

Protection against Remote Access

+

+By default this database does not allow connections from other machines when starting the H2 Console, +the TCP server, or the PG server. Remote access can be enabled using the command line +options -webAllowOthers, -tcpAllowOthers, -pgAllowOthers. +

+

+If you enable remote access using +-tcpAllowOthers or -pgAllowOthers, +please also consider using the options -baseDir, +so that remote users can not create new databases +or access existing databases with weak passwords. +When using the option -baseDir, only databases within that directory may be accessed. +Ensure the existing accessible databases are protected using strong passwords. +

+

+If you enable remote access using -webAllowOthers, +please ensure the web server can only be accessed from trusted networks. +If this option is specified, -webExternalNames should be also specified with +comma-separated list of external names or addresses of this server. +The options -baseDir don't protect +access to the saved connection settings, +or access to other databases accessible from the system. +

+ +

Restricting Class Loading and Usage

+

+By default there is no restriction on loading classes and executing Java code for admins. +That means an admin may call system functions such as +System.setProperty by executing: +

+
+CREATE ALIAS SET_PROPERTY FOR "java.lang.System.setProperty";
+CALL SET_PROPERTY('abc', '1');
+CREATE ALIAS GET_PROPERTY FOR "java.lang.System.getProperty";
+CALL GET_PROPERTY('abc');
+
+

+To restrict users (including admins) from loading classes and executing code, +the list of allowed classes can be set in the system property +h2.allowedClasses +in the form of a comma separated list of classes or patterns (items ending with *). +By default all classes are allowed. Example: +

+
+java -Dh2.allowedClasses=java.lang.Math,com.acme.*
+
+

+This mechanism is used for all user classes, including database event listeners, +trigger classes, user-defined functions, user-defined aggregate functions, and JDBC +driver classes (with the exception of the H2 driver) when using the H2 Console. +

+ +

Security Protocols

+

+The following paragraphs document the security protocols used in this database. +These descriptions are very technical and only intended for security experts that already know +the underlying security primitives. +

+ +

User Password Encryption

+

+When a user tries to connect to a database, the combination of +user name, @, and password are hashed using SHA-256, and this hash value +is transmitted to the database. +This step does not protect against an attacker that re-uses the value if he is able to listen to the +(unencrypted) transmission between the client and the server. +But, the passwords are never transmitted as plain text, +even when using an unencrypted connection between client and server. +That means if a user reuses the same password for different things, +this password is still protected up to some point. See also +'RFC 2617 - HTTP Authentication: Basic and Digest Access Authentication' +for more information. +

+When a new database or user is created, a new random salt value is generated. +The size of the salt is 64 bits. Using the random salt reduces the risk of an +attacker pre-calculating hash values for many different (commonly used) passwords. +

+The combination of user-password hash value (see above) and salt is hashed +using SHA-256. The resulting value is stored in the database. +When a user tries to connect to the database, the database combines +user-password hash value with the stored salt value and calculates the +hash value. Other products use multiple iterations (hash the hash value again and again), +but this is not done in this product to reduce the risk of denial of service attacks +(where the attacker tries to connect with bogus passwords, and the server +spends a lot of time calculating the hash value for each password). +The reasoning is: if the attacker has access to the hashed passwords, he also has +access to the data in plain text, and therefore does not need the password any more. +If the data is protected by storing it on another computer and only accessible remotely, +then the iteration count is not required at all. +

+ +

File Encryption

+

+The database files can be encrypted using the AES-128 algorithm. +

+When a user tries to connect to an encrypted database, the combination of +file@ and the file password is hashed using SHA-256. This hash value is +transmitted to the server. +

+When a new database file is created, a new cryptographically secure +random salt value is generated. The size of the salt is 64 bits. +The combination of the file password hash and the salt value is hashed 1024 times +using SHA-256. The reason for the iteration is to make it harder for an attacker to +calculate hash values for common passwords. +

+The resulting hash value is used as the key for the block cipher algorithm. +Then, an initialization vector (IV) key +is calculated by hashing the key again using SHA-256. +This is to make sure the IV is unknown to the attacker. +The reason for using a secret IV is to protect against watermark attacks. +

+Before saving a block of data (each block is 8 bytes long), the following operations are executed: +first, the IV is calculated by encrypting the block number with the IV key (using the same +block cipher algorithm). This IV is combined with the plain text using XOR. The resulting data is +encrypted using the AES-128 algorithm. +

+When decrypting, the operation is done in reverse. First, the block is decrypted using the key, +and then the IV is calculated combined with the decrypted text using XOR. +

+Therefore, the block cipher mode of operation is CBC (cipher-block chaining), but each chain +is only one block long. The advantage over the ECB (electronic codebook) mode is that patterns +in the data are not revealed, and the advantage over multi block CBC is that flipped cipher text bits +are not propagated to flipped plaintext bits in the next block. +

+Database encryption is meant for securing the database while it is not in use (stolen laptop and so on). +It is not meant for cases where the attacker has access to files while the database is in use. +When he has write access, he can for example replace pieces of files with pieces of older versions +and manipulate data like this. +

+File encryption slows down the performance of the database engine. Compared to unencrypted mode, +database operations take about 2.5 times longer using AES (embedded mode). +

+ +

Wrong Password / User Name Delay

+

+To protect against remote brute force password attacks, the delay after each unsuccessful +login gets double as long. Use the system properties h2.delayWrongPasswordMin +and h2.delayWrongPasswordMax to change the minimum (the default is 250 milliseconds) +or maximum delay (the default is 4000 milliseconds, or 4 seconds). The delay only +applies for those using the wrong password. Normally there is no delay for a user that knows the correct +password, with one exception: after using the wrong password, there is a delay of up to (randomly distributed) +the same delay as for a wrong password. This is to protect against parallel brute force attacks, +so that an attacker needs to wait for the whole delay. Delays are synchronized. This is also required +to protect against parallel attacks. +

+

+There is only one exception message for both wrong user and for wrong password, +to make it harder to get the list of user names. It is not possible from the stack trace to see +if the user name was wrong or the password. +

+ +

HTTPS Connections

+

+The web server supports HTTP and HTTPS connections using SSLServerSocket. +There is a default self-certified certificate to support an easy starting point, but +custom certificates are supported as well. +

+ +

TLS Connections

+

+Remote TLS connections are supported using the Java Secure Socket Extension +(SSLServerSocket, SSLSocket). By default, anonymous TLS is enabled. +

+

+To use your own keystore, set the system properties javax.net.ssl.keyStore and +javax.net.ssl.keyStorePassword before starting the H2 server and client. +See also +Customizing the Default Key and Trust Stores, Store Types, and Store Passwords +for more information. +

+

+To disable anonymous TLS, set the system property h2.enableAnonymousTLS to false. +

+ +

Universally Unique Identifiers (UUID)

+

+This database supports UUIDs. Also supported is a function to create new UUIDs using +a cryptographically strong pseudo random number generator. +With random UUIDs, the chance of two having the same value can be calculated +using the probability theory. See also 'Birthday Paradox'. +Standardized randomly generated UUIDs have 122 random bits. +4 bits are used for the version (Randomly generated UUID), and 2 bits for the variant (Leach-Salz). +This database supports generating such UUIDs using the built-in function +RANDOM_UUID() or UUID(). +Here is a small program to estimate the probability of having two identical UUIDs +after generating a number of values: +

+
+public class Test {
+    public static void main(String[] args) throws Exception {
+        double x = Math.pow(2, 122);
+        for (int i = 35; i < 62; i++) {
+            double n = Math.pow(2, i);
+            double p = 1 - Math.exp(-(n * n) / 2 / x);
+            System.out.println("2^" + i + "=" + (1L << i) +
+                    " probability: 0" +
+                    String.valueOf(1 + p).substring(1));
+        }
+    }
+}
+
+

+Some values are: +

+ + + + + +
Number of UUIsProbability of Duplicates
2^36=68'719'476'7360.000'000'000'000'000'4
2^41=2'199'023'255'5520.000'000'000'000'4
2^46=70'368'744'177'6640.000'000'000'4
+

+To help non-mathematicians understand what those numbers mean, here a comparison: +one's annual risk of being hit by a meteorite is estimated to be one chance in 17 billion, +that means the probability is about 0.000'000'000'06. +

+ +

Spatial Features

+

+H2 supports the geometry data type and spatial indexes. +Here is an example SQL script to create a table with a spatial column and index: +

+
+CREATE TABLE GEO_TABLE(
+    GID BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
+    THE_GEOM GEOMETRY);
+INSERT INTO GEO_TABLE(THE_GEOM) VALUES
+    ('POINT(500 505)'),
+    ('LINESTRING(550 551, 525 512, 565 566)'),
+    ('POLYGON ((550 521, 580 540, 570 564, 512 566, 550 521))');
+CREATE SPATIAL INDEX GEO_TABLE_SPATIAL_INDEX
+    ON GEO_TABLE(THE_GEOM);
+
+

+To query the table using geometry envelope intersection, +use the operation &&, as in PostGIS: +

+
+SELECT * FROM GEO_TABLE
+    WHERE THE_GEOM &&
+    'POLYGON ((490 490, 536 490, 536 515, 490 515, 490 490))';
+
+

+You can verify that the spatial index is used using the "explain plan" feature: +

+
+EXPLAIN SELECT * FROM GEO_TABLE
+    WHERE THE_GEOM &&
+    'POLYGON ((490 490, 536 490, 536 515, 490 515, 490 490))';
+-- Result
+SELECT
+    "PUBLIC"."GEO_TABLE"."GID",
+    "PUBLIC"."GEO_TABLE"."THE_GEOM"
+FROM "PUBLIC"."GEO_TABLE"
+    /* PUBLIC.GEO_TABLE_SPATIAL_INDEX: THE_GEOM &&
+    GEOMETRY 'POLYGON ((490 490, 536 490, 536 515, 490 515, 490 490))' */
+WHERE "THE_GEOM" &&
+    GEOMETRY 'POLYGON ((490 490, 536 490, 536 515, 490 515, 490 490))'
+
+

+For persistent databases, the spatial index is stored on disk; +for in-memory databases, the index is kept in memory. +

+ +

Recursive Queries

+

+H2 has experimental support for recursive queries using so called "common table expressions" (CTE). +Examples: +

+
+WITH RECURSIVE T(N) AS (
+    SELECT 1
+    UNION ALL
+    SELECT N+1 FROM T WHERE N<10
+)
+SELECT * FROM T;
+-- returns the values 1 .. 10
+
+WITH RECURSIVE T(N) AS (
+    SELECT 1
+    UNION ALL
+    SELECT N*2 FROM T WHERE N<10
+)
+SELECT * FROM T;
+-- returns the values 1, 2, 4, 8, 16
+
+CREATE TABLE FOLDER(ID INT PRIMARY KEY, NAME VARCHAR(255), PARENT INT);
+
+INSERT INTO FOLDER VALUES(1, null, null), (2, 'src', 1),
+(3, 'main', 2), (4, 'org', 3), (5, 'test', 2);
+
+WITH LINK(ID, NAME, LEVEL) AS (
+    SELECT ID, NAME, 0 FROM FOLDER WHERE PARENT IS NULL
+    UNION ALL
+    SELECT FOLDER.ID, COALESCE(LINK.NAME || '/', '') || FOLDER.NAME, LEVEL + 1
+    FROM LINK INNER JOIN FOLDER ON LINK.ID = FOLDER.PARENT
+)
+SELECT NAME FROM LINK WHERE NAME IS NOT NULL ORDER BY ID;
+-- src
+-- src/main
+-- src/main/org
+-- src/test
+
+

+Limitations: Recursive queries need to be of the type UNION ALL, +and the recursion needs to be on the second part of the query. +No tables or views with the name of the table expression may exist. +Different table expression names need to be used when using multiple distinct table +expressions within the same transaction and for the same session. +All columns of the table expression are of type VARCHAR, +and may need to be cast to the required data type. +Views with recursive queries are not supported. +Subqueries and INSERT INTO ... FROM with recursive queries are not supported. +Parameters are only supported within the last SELECT statement +(a workaround is to use session variables like @start +within the table expression). +The syntax is: +

+
+WITH RECURSIVE recursiveQueryName(columnName, ...) AS (
+    nonRecursiveSelect
+    UNION ALL
+    recursiveSelect
+)
+select
+
+ +

Settings Read from System Properties

+

+Some settings of the database can be set on the command line using +-DpropertyName=value. It is usually not required to change those settings manually. +The settings are case sensitive. +Example: +

+
+java -Dh2.serverCachedObjects=256 org.h2.tools.Server
+
+

+The current value of the settings can be read in the table +INFORMATION_SCHEMA.SETTINGS. +

+

+For a complete list of settings, see +SysProperties. +

+ +

Setting the Server Bind Address

+

+Usually server sockets accept connections on any/all local addresses. +This may be a problem on multi-homed hosts. +To bind only to one address, use the system property h2.bindAddress. +This setting is used for both regular server sockets and for TLS server sockets. +IPv4 and IPv6 address formats are supported. +

+ +

Pluggable File System

+

+This database supports a pluggable file system API. +The file system implementation is selected using a file name prefix. +Internally, the interfaces are very similar to the Java 7 NIO2 API. +The following file systems are included: +

+
  • file: the default file system that uses FileChannel. +
  • zip: read-only zip-file based file system. Format: zip:~/zipFileName!/fileName. +
  • split: file system that splits files in 1 GB files (stackable with other file systems). +
  • nioMapped: file system that uses memory mapped files (faster in some operating systems). + Please note that there currently is a file size limitation of 2 GB when using this file system. + To work around this limitation, combine it with the split file system: split:nioMapped:~/test. +
  • async: experimental file system that uses AsynchronousFileChannel instead of FileChannel (faster in some operating systems). +
  • memFS: in-memory file system (slower than mem; experimental; mainly used for testing the database engine itself). +
  • memLZF: compressing in-memory file system (slower than memFS but uses less memory; experimental; mainly used for testing the database engine itself). +
  • nioMemFS: stores data outside of the VM's heap - useful for large memory DBs without incurring GC costs. +
  • +
  • + nioMemLZF: stores compressed data outside of the VM's heap - + useful for large memory DBs without incurring GC costs. + Use "nioMemLZF:12:" to tweak the % of blocks that are stored uncompressed. + If you size this to your working set correctly, + compressed storage is roughly the same performance as uncompressed. + The default value is 1%. +
+

+As an example, to use the async: file system +use the following database URL: jdbc:h2:async:~/test. +

+

+To register a new file system, extend the classes org.h2.store.fs.FilePath, FileBase, +and call the method FilePath.register before using it. +

+

+For input streams (but not for random access files), URLs may be used in addition to the registered file systems. +Example: jar:file:///c:/temp/example.zip!/org/example/nested.csv. +To read a stream from the classpath, use the prefix classpath:, as in +classpath:/org/h2/samples/newsfeed.sql. +

+ +

Split File System

+

+The file system prefix split: is used to split logical files into multiple physical files, +for example so that a database can get larger than the maximum file system size of the operating system. +If the logical file is larger than the maximum file size, then the file is split as follows: +

+
  • <fileName> (first block, is always created) +
  • <fileName>.1.part (second block) +
+

+More physical files (*.2.part, *.3.part) are automatically created / deleted if needed. +The maximum physical file size of a block is 2^30 bytes, which is also called 1 GiB or 1 GB. +However this can be changed if required, by specifying the block size in the file name. +The file name format is: split:<x>:<fileName> where the file size per block is 2^x. +For 1 MiB block sizes, use x = 20 (because 2^20 is 1 MiB). +The following file name means the logical file is split into 1 MiB blocks: split:20:~/test.h2.db. +An example database URL for this case is jdbc:h2:split:20:~/test. +

+ +

Java Objects Serialization

+

+Java objects serialization is enabled by default for columns of type OTHER, using standard Java serialization/deserialization semantics. +

+

+To disable this feature set the system property h2.serializeJavaObject=false (default: true). +

+

+Serialization and deserialization of java objects is customizable both at system level and at database level providing a +JavaObjectSerializer implementation: +

+
    +
  • +At system level set the system property h2.javaObjectSerializer with the +Fully Qualified Name of the JavaObjectSerializer interface implementation. +It will be used over the entire JVM session to (de)serialize java objects being stored in column of type OTHER. +Example h2.javaObjectSerializer=com.acme.SerializerClassName. +
  • +
  • +At database level execute the SQL statement SET JAVA_OBJECT_SERIALIZER 'com.acme.SerializerClassName' +or append ;JAVA_OBJECT_SERIALIZER='com.acme.SerializerClassName' to the database URL: jdbc:h2:~/test;JAVA_OBJECT_SERIALIZER='com.acme.SerializerClassName'. +

    +Please note that this SQL statement can only be executed before any tables are defined. +

    +
  • +
+ +

Limits and Limitations

+

+This database has the following known limitations: +

+
    +
  • Database file size limit: + 4 TB (using the default page size of 2 KB) or higher (when using a larger page size). + This limit is including CLOB and BLOB data. +
  • The maximum file size for FAT or FAT32 file systems is 4 GB. + That means when using FAT or FAT32, the limit is 4 GB for the data. This is the limitation of the file system. + The database does provide a workaround for this problem, it is to use the file name prefix split:. + In that case files are split into files of 1 GB by default. + An example database URL is: jdbc:h2:split:~/test. +
  • The maximum number of rows per table is 2^64. +
  • The maximum number of open transactions is 65535. +
  • The maximum number of columns in a table or expressions in a SELECT statement is 16384. +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 1_000_000_000 characters. +
  • The maximum length of BINARY, BINARY VARYING, JAVA_OBJECT, GEOMETRY, and JSON values and columns +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. +
  • The maximum degree of a ROW value or column is 16384. +
  • The maximum index of parameter is 100000. +
  • Main memory requirements: The larger the database, the more main memory is required. +
  • Limit on the complexity of SQL statements. +Very complex expressions may result in a stack overflow exception. +
  • There is no limit for the following entities, except the memory and storage capacity: + maximum number of tables, indexes, triggers, and other database objects; + maximum statement length, tables per statement; + maximum rows per query; + maximum indexes per table, lob columns per table, and so on; + maximum row length, index row length, select row length. +
  • Querying from the metadata tables is slow if there are many tables (thousands). +
  • For other limitations on data types, see the data type documentation of this database. +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TermDescription
AES-128A block encryption algorithm. See also: Wikipedia: + Advanced Encryption Standard
Birthday ParadoxDescribes the higher than expected probability that two + persons in a room have the same birthday. Also valid for randomly + generated UUIDs. See also: Wikipedia: + Birthday problem
DigestProtocol to protect a password (but not to protect data). + See also: RFC + 2617: HTTP Digest Access Authentication
HTTPSA protocol to provide security to HTTP connections. See + also: RFC 2818: + HTTP Over TLS
Modes of OperationWikipedia: + Block cipher mode of operation
SaltRandom number to increase the security of passwords. See + also: Wikipedia: + Key derivation function
SHA-256A cryptographic one-way hash function. See also: Wikipedia: + Secure Hash Algorithms
SQL InjectionA security vulnerability where an application embeds SQL + statements or expressions in user input. See also: Wikipedia: + SQL injection
Watermark AttackSecurity problem of certain encryption programs where the + existence of certain data can be proven without decrypting. For more + information, search in the internet for 'watermark attack + cryptoloop'
SSL/TLSSecure Sockets Layer / Transport Layer Security. See also: + Java Secure Socket + Extension (JSSE)
+ +
diff --git a/h2/src/docsrc/html/architecture.html b/h2/src/docsrc/html/architecture.html new file mode 100644 index 0000000..af4ccdc --- /dev/null +++ b/h2/src/docsrc/html/architecture.html @@ -0,0 +1,155 @@ + + + + + + + +Architecture + + + + + +
+ + +

Architecture

+ + Introduction
+ + Top-down overview
+ + JDBC driver
+ + Connection/session management
+ + Command execution and planning
+ + Table/index/constraints
+ + Undo log, redo log, and transactions layer
+ + B-tree engine and page-based storage allocation
+ + Filesystem abstraction
+ +

Introduction

+

+H2 implements an embedded and standalone ANSI-SQL89 compliant SQL engine on top of a B-tree based disk store. +

+

+As of October 2013, Thomas is still working on our next-generation storage engine called MVStore. This will +in time replace the B-tree based storage engine. +

+ +

Top-down Overview

+

+Working from the top down, the layers look like this: +

+
  • JDBC driver. +
  • Connection/session management. +
  • SQL Parser. +
  • Command execution and planning. +
  • Table/Index/Constraints. +
  • Undo log, redo log, and transactions layer. +
  • B-tree engine and page-based storage allocation. +
  • Filesystem abstraction. +
+ +

JDBC Driver

+

+The JDBC driver implementation lives in org.h2.jdbc, org.h2.jdbcx +

+ +

Connection/session management

+

+The primary classes of interest are: +

+ + + + + + + + + +
PackageDescription
org.h2.engine.Databasethe root/global class
org.h2.engine.SessionInterfaceabstracts over the differences between embedded and remote sessions
org.h2.engine.Sessionlocal/embedded session
org.h2.engine.SessionRemoteremote session
+ +

Parser

+

+The parser lives in org.h2.command.Parser. It uses a straightforward recursive-descent design. +

+

+See Wikipedia Recursive descent parser page. +

+ + +

Command execution and planning

+

+Unlike other databases, we do not have an intermediate step where we generate some kind of IR (intermediate representation) of the query. +The parser class directly generates a command execution object. +Then we run some optimisation steps over the command to possibly generate a more efficient command. +

+

+The primary packages of interest are: +

+ + + + +
PackageDescription
org.h2.command.ddlCommands that modify schema data structures
org.h2.command.dmlCommands that modify data
+ +

Table/Index/Constraints

+

+One thing to note here is that indexes are simply stored as special kinds of tables. +

+

+The primary packages of interest are: +

+ + + + +
PackageDescription
org.h2.tableImplementations of different kinds of tables
org.h2.indexImplementations of different kinds of indices
+ +

Undo log, redo log, and transactions layer

+

+We have a transaction log, which is shared among all sessions. See also +https://en.wikipedia.org/wiki/Transaction_log +https://h2database.com/html/grammar.html#set_log +

+

+We also have an undo log, which is per session, to undo an operation (an update that fails for example) +and to rollback a transaction. +Theoretically, the transaction log could be used, but for simplicity, H2 currently uses it's +own "list of operations" (usually in-memory). +

+

+With the MVStore, this is no longer needed (just the transaction log). +

+ +

B-tree engine and page-based storage allocation.

+

+The primary package of interest is org.h2.store. +

+

+This implements a storage mechanism which allocates pages of storage (typically 2k in size) +and also implements a b-tree over those pages to allow fast retrieval and update. +

+ +

Filesystem abstraction.

+

+The primary class of interest is org.h2.store.FileStore. +

+

+This implements an abstraction of a random-access file. +This allows the higher layers to treat in-memory vs. on-disk vs. zip-file databases the same. +

+ +
diff --git a/h2/src/docsrc/html/build.html b/h2/src/docsrc/html/build.html new file mode 100644 index 0000000..87a588d --- /dev/null +++ b/h2/src/docsrc/html/build.html @@ -0,0 +1,298 @@ + + + + + + + +Build + + + + + +
+ + +

Build

+ + Portability
+ + Environment
+ + Building the Software
+ + Using Maven 2
+ + Using Eclipse
+ + Translating
+ + Submitting Source Code Changes
+ + Reporting Problems or Requests
+ + Automated Build
+ + Generating Railroad Diagrams
+ +

Portability

+

+This database is written in Java and therefore works on many platforms. +

+ +

Environment

+

+To run this database, a Java Runtime Environment (JRE) version 8 or higher is required. +

+

+To create the database executables, the following software stack was used. +To use this database, it is not required to install this software however. +

+ + +

Building the Software

+

+You need to install a JDK, for example the Oracle JDK version 8. +Ensure that Java binary directory is included in the PATH environment variable, and that +the environment variable JAVA_HOME points to your Java installation. +On the command line, go to the directory h2 and execute the following command: +

+
+build -?
+
+

+For Linux and OS X, use ./build.sh instead of build. +

+

+You will get a list of targets. If you want to build the jar file, execute (Windows): +

+
+build jar
+
+

+To run the build tool in shell mode, use the command line option -: +

+
+./build.sh -
+
+ +

Using Apache Lucene

+

+Apache Lucene 8.5.2 is used for testing. +

+ +

Using Maven 2

+

Using a Central Repository

+

+You can include the database in your Maven 2 project as a dependency. +Example: +

+
+<dependency>
+    <groupId>com.h2database</groupId>
+    <artifactId>h2</artifactId>
+    <version>${version}</version>
+</dependency>
+
+

+New versions of this database are first uploaded to http://hsql.sourceforge.net/m2-repo/ and then automatically +synchronized with the main Maven repository; +however after a new release it may take a few hours before they are available there. +

+

Maven Plugin to Start and Stop the TCP Server

+

+A Maven plugin to start and stop the H2 TCP server is available from +Laird Nelson at GitHub. +To start the H2 server, use: +

+
+mvn com.edugility.h2-maven-plugin:1.0-SNAPSHOT:spawn
+
+

+To stop the H2 server, use: +

+
+mvn com.edugility.h2-maven-plugin:1.0-SNAPSHOT:stop
+
+ +

Using Snapshot Version

+

+To build a h2-*-SNAPSHOT.jar file and upload it the to the local Maven 2 repository, execute the following command: +

+
+build mavenInstallLocal
+
+

+Afterwards, you can include the database in your Maven 2 project as a dependency: +

+
+<dependency>
+    <groupId>com.h2database</groupId>
+    <artifactId>h2</artifactId>
+    <version>1.0-SNAPSHOT</version>
+</dependency>
+
+ +

Using Eclipse

+

+To create an Eclipse project for H2, use the following steps: +

+
  • Install Git and Eclipse. +
  • Get the H2 source code from Github:
    + git clone https://github.com/h2database/h2database +
  • Download all dependencies:
    + build.bat download(Windows)
    + ./build.sh download(otherwise)
    +
  • In Eclipse, create a new Java project from existing source code: + File, New, Project, Java Project, Create project from existing source. +
  • Select the h2 folder, click Next and Finish. +
  • To resolve com.sun.javadoc import statements, + you may need to manually add the file <java.home>/../lib/tools.jar to the build path. +
+ +

Translating

+

+The translation of this software is split into the following parts: +

+
    +
  • H2 Console: src/main/org/h2/server/web/res/_text_*.prop +
  • Error messages: src/main/org/h2/res/_messages_*.prop +
+

+To translate the H2 Console, start it and select Preferences / Translate. +After you are done, send the translated *.prop file to the Google Group. +The web site is currently translated using Google. +

+ +

Submitting Source Code Changes

+

+If you'd like to contribute bug fixes or new features, please consider the following guidelines to simplify merging them: +

+
  • Only use Java 8 features (do not use Java 9/10/etc) (see Environment). +
  • Follow the coding style used in the project, and use Checkstyle (see above) to verify. + For example, do not use tabs (use spaces instead). + The checkstyle configuration is in src/installer/checkstyle.xml. +
  • A template of the Eclipse settings are in + src/installer/eclipse.settings/*. If you want to use them, + you need to copy them to the .settings directory. + The formatting options (eclipseCodeStyle) are also included. +
  • Please provide test cases and integrate them into the test suite. + For Java level tests, see src/test/org/h2/test/TestAll.java. + For SQL level tests, see SQL files in src/test/org/h2/test/scripts. +
  • The test cases should cover at least 90% of the changed and new code; + use a code coverage tool to verify that (see above). + or use the build target coverage. +
  • Verify that you did not break other features: run the test cases by executing + build test. +
  • Provide end user documentation if required (src/docsrc/html/*). +
  • Document grammar changes in src/main/org/h2/res/help.csv +
  • Provide a change log entry (src/docsrc/html/changelog.html). +
  • Verify the spelling using build spellcheck. If required + add the new words to src/tools/org/h2/build/doc/dictionary.txt. +
  • Run src/installer/buildRelease to find and fix formatting errors. +
  • Verify the formatting using build docs and + build javadoc. +
  • Submit changes using GitHub's "pull requests". You'll require a free GitHub + account. If you are not familiar with pull requests, please read GitHub's + Using pull requests page. +
+

+For legal reasons, patches need to be public in the form of an + issue report or attachment or in the form of an email + to the group. +Significant contributions need to include the following statement: +

+

+"I wrote the code, it's mine, and I'm contributing it to H2 for distribution +multiple-licensed under the MPL 2.0, and the EPL 1.0 +(https://h2database.com/html/license.html)." +

+ +

Reporting Problems or Requests

+

+Please consider the following checklist if you have a question, want to report a problem, +or if you have a feature request: +

+
  • For bug reports, please provide a + short, self contained, correct (compilable), example of the problem. +
  • Feature requests are always welcome, even if the feature is already on the + issue tracker + you can comment it. + If you urgently need a feature, consider providing a patch. +
  • Before posting problems, check the + FAQ and do a Google search. +
  • When got an unexpected exception, please try the + Error Analyzer tool. If this doesn't help, + please report the problem, including the complete error message and stack trace, + and the root cause stack trace(s). +
  • When sending source code, please use a public web clipboard such as + Pastebin or + Mystic Paste + to avoid formatting problems. + Please keep test cases as simple and short as possible, + but so that the problem can still be reproduced. + As a template, use: + HelloWorld.java. + Method that simply call other methods should be avoided, + as well as unnecessary exception handling. + Please use the JDBC API and no external tools or libraries. + The test should include all required initialization code, and + should be started with the main method. +
  • For large attachments, use a public storage such as + Google Drive. +
  • Google Group versus issue tracking: + Use the + Google Group + for questions or if you are not sure it's a bug. + If you are sure it's a bug, you can create an + issue, + but you don't need to (sending an email to the group is enough). + Please note that only few people monitor the issue tracking system. +
  • For out-of-memory problems, please analyze the problem yourself first, + for example using the command line option + -XX:+HeapDumpOnOutOfMemoryError + (to create a heap dump file on out of memory) + and a memory analysis tool such as the + Eclipse Memory Analyzer (MAT). +
  • It may take a few days to get an answers. Please do not double post. +
+ +

Automated Build

+

+This build process is automated and runs regularly. +The build process includes running the tests and code coverage, using the command line +./build.sh jar testCI. +The results are available on CI workflow page. +

+ +

Generating Railroad Diagrams

+

+The railroad diagrams of the SQL grammar are HTML, formatted as nested tables. +The diagrams are generated as follows: +

+
  • The BNF parser (org.h2.bnf.Bnf) reads and parses the BNF from the file help.csv. +
  • The page parser (org.h2.server.web.PageParser) reads the template HTML file and fills in the diagrams. +
  • The rail images (one straight, four junctions, two turns) are generated using a simple Java application. +
+

+To generate railroad diagrams for other grammars, see the package org.h2.jcr. +This package is used to generate the SQL-2 railroad diagrams for the JCR 2.0 specification. +

+ +
+ diff --git a/h2/src/docsrc/html/changelog.html b/h2/src/docsrc/html/changelog.html new file mode 100644 index 0000000..2552c84 --- /dev/null +++ b/h2/src/docsrc/html/changelog.html @@ -0,0 +1,961 @@ + + + + + + +Change Log + + + + + +
+ + +

Change Log

+ +

Next Version (unreleased)

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

Next Version 2.1.212 (2022-04-09)

+
    +
  • PR #3481: Add support for standard interval literals with precision +
  • +
  • Issue #3471: Possibility of corruption after SHUTDOWN DEFRAG +
  • +
  • Issue #3473: DROP TABLE/INDEX causes memory leak +
  • +
  • PR #3464 / Issue #3457: increase max length of VAR* types +
  • +
  • PR #3460: fix bug in readStoreHeader() +
  • +
  • PR #3458: Add performance tests for SQLite +
  • +
  • Issue #1808: Occasional NPE in concurrent update of LOB +
  • +
  • Issue #3439: Cannot use enum values in JSON without explicit casts +
  • +
  • Issue #3426: Regression: BIT(1) is not accepted in MySQL compatibility mode +
  • +
  • PR #3422: Allow combination of any geometry types with the same SRID +
  • +
  • Issue #3414: H2 2.1.210: Query with Parameters throws NPE +
  • +
  • Issue #3413: Parser can't parse REFERENCES … NOT NULL +
  • +
  • Issue #3410: OOME with nested derived tables +
  • +
  • Issue #3405: Enhance SCRIPT to support operations on STDOUT +
  • +
  • Issue #3406 / PR #3407: FunctionMultiReturn.polar2CartesianArray result set iteration throws ClassCastException +
  • +
  • Issue #3400: Regression: ORDER BY ROWNUM fails with General error: "Unexpected code path" +
  • +
  • Issue #3387: SYSDATE behavior changed in 2.x +
  • +
  • Issue #3394: SYSDATE Considered Identifier when used in inner select +
  • +
  • Issue #3391: Hang on merge statement with data change delta table +
  • +
  • PR #3384: Remove abandoned Java to C converter and fix some warnings from Sonatype Lift +
  • +
  • PR #3382: Use secure parser in H2AuthConfigXml to avoid future reports +
  • +
+ +

Version 2.1.210 (2022-01-17)

+
    +
  • PR #3381: Add IDENTITY() and SCOPE_IDENTITY() to LEGACY mode +
  • +
  • Issue #3376: Data cannot be read after insert of clob data > MAX_LENGTH_INPLACE_LOB with data change delta table +
  • +
  • PR #3377: Add -webExternalNames setting and fix WebServer.getConnection() +
  • +
  • PR #3367: Use faster checks of dimension systems of geometries +
  • +
  • PR #3369: Added v2 changes in migration docs +
  • +
  • Issue #3361: MemoryEstimator.estimateMemory() can return negative size +
  • +
  • PR #3362: Use BufferedReader instead of BufferedInputStream to avoid Illegal seek exception +
  • +
  • Issue #3353: Wrong rownum() scope for DML with change delta table +
  • +
  • PR #3352: make Javadoc happier +
  • +
  • Issue #3344: Changelog could link to github issue +
  • +
  • Issue #3340: JDBC index type seems wrong +
  • +
  • Issue #3336: FT_INIT error when mode=MySQL +
  • +
  • Issue #3334: Regression with CREATE ALIAS - Parameter "#2" is not set +
  • +
  • Issue #3321: Insert Primary Key after import CSV Data does not work +
  • +
  • PR #3323: Tokenize SQL before parsing and preserve tokens for recompilation +
  • +
  • PR #3320: Add Servlet 5-compatible servlet for H2 Console +
  • +
  • Issue #918: Parser fails recognising set operations in correlated subqueries +
  • +
  • Issue #2050: PostgreSQL with recursive fail with union in the final query +
  • +
  • PR #3316: Update copyright years +
  • +
  • PR #3315: Never put read locks into lockSharedSessions and other minor changes +
  • +
  • Issue #492: H2 does not correctly parse <parenthesized joined table> +
  • +
  • Issue #3311: Parser creates wrong join graph in some cases and uses wrong tables for column mapping +
  • +
  • FORCE_JOIN_ORDER setting is removed +
  • +
  • Issue #1983: Official build script is not compatible with Java 13 +
  • +
  • PR #3305: Add UNIQUE(VALUE) and remove some non-standard keywords +
  • +
  • PR #3299: Remove useless StringBuilder.toString() call +
  • +
  • PR #3298: Delete unused sqlTypes array +
  • +
+ +

Version 2.0.206 (2022-01-04)

+
    +
  • Issue #3322: Create linked table fails when the table contains a Geometry with a data type specified +
  • +
  • Issue #3297: Unexpected GROUP BY results with indexed IGNORECASE column +
  • +
+ +

Version 2.0.204 (2021-12-21)

+
    +
  • Issue #3291: Add Legacy and Strict modes +
  • +
  • Issue #3287: SELECT statement works on 1.4.200 but fails on 2.0.202 with "Column XYZ must be in the GROUP BY list" +
  • +
  • PR #3284: Remove unused UNDO_LOG setting +
  • +
  • Issue #3251: Table with GEOMETRY column can't have a TriggerAdapter-based trigger any more +
  • +
  • PR #3281: DateTimeFormatter-based FORMATDATETIME and PARSEDATETIME and other changes +
  • +
  • Issue #3246: Spatial predicates with comparison are broken in MySQL compatibility mode +
  • +
  • Issue #3270: org.h2.jdbc.JdbcSQLFeatureNotSupportedException: Feature not supported: "Unsafe comparison or cast" +
  • +
  • Issue #3268 / PR #3275: Add TO_DATE and TO_TIMESTAMP to PostgreSQL compatibility mode +
  • +
  • PR #3274: Remove some dead code and unused params +
  • +
  • Issue #3266: Oracle compatibility NUMBER without precision and scale should have variable scale +
  • +
  • Issue #3263: Unable to store BigDecimal with negative scale in NUMERIC(19,6) column +
  • +
  • PR #3261: Small optimization for MIN and MAX +
  • +
  • Issue #3258 / PR #3259: Prevent incorrect optimization of COUNT(*) and other changes +
  • +
  • PR #3255: Throw proper exception when type of argument isn't known +
  • +
  • Issue #3249: Multi-column assignment with subquery throws exception when subquery doesn't return any rows +
  • +
  • PR #3248: Remove redundant uniqueness check, correct version in pom +
  • +
  • PR #3247: Avoid AIOBE exception in TestCrashAPI and in Transaction +
  • +
  • Issue #3241: ResultSetMetaData::getColumnTypeName should produce the correct ARRAY type +
  • +
  • Issue #3204: H2 Tools Web Console: Unicode 32 +
  • +
  • Issue #3227: Regression when referencing outer joined column from correlated subquery +
  • +
  • Issue #3237: Can no longer cast CHAR(n) to BOOLEAN with n > 1 +
  • +
  • Issue #3235: Regression in IN predicate with empty in list +
  • +
  • Issue #3236: NullPointerException in DatabaseMetaData::getIndexInfo when querying the info for views +
  • +
  • Issue #3233: General error when using NULL predicate on _ROWID_ column +
  • +
  • Issue #3223: TRUNC(v, p) with negative precisions no longer works +
  • +
  • Issue #3221: NullPointerException when creating domain +
  • +
  • Issue #3186: ResultSetMetaData.getSchemaName() returns empty string for aliased columns +
  • +
+ +

Version 2.0.202 (2021-11-25)

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

+ +

H2 Database Engine Cheat Sheet

+
+ +

Using H2

+ + +

Documentation

+

+Reference: +SQL grammar, +functions, +data types, +tools, +API +
+Features: +fulltext search, +encryption, +read-only +(zip/jar), +CSV, +auto-reconnect, +triggers, +user functions +

+ +

Database URLs

+

+Embedded
+jdbc:h2:~/test 'test' in the user home directory
+jdbc:h2:/data/test 'test' in the directory /data
+jdbc:h2:./test in the current(!) working directory
+

+In-Memory
+jdbc:h2:mem:test multiple connections in one process, +database is removed when all connections are closed
+jdbc:h2:mem:test;DB_CLOSE_DELAY=-1 multiple connections in one process, +database in not removed when all connections are closed +(may create a memory leak)
+jdbc:h2:mem: unnamed private; one connection
+

+Server Mode
+jdbc:h2:tcp://localhost/~/test user home dir
+jdbc:h2:tcp://localhost//data/test or jdbc:h2:tcp://localhost/D:/data/test absolute dir
+Server start:java -cp *.jar org.h2.tools.Server +

+Settings
+jdbc:h2:..;MODE=MySQL;DATABASE_TO_LOWER=TRUE +compatibility (or HSQLDB,...)
+jdbc:h2:..;TRACE_LEVEL_FILE=3 log to *.trace.db
+

+ +
+
+ +

Using the JDBC API

+
+Connection conn = DriverManager.
+    getConnection("jdbc:h2:~/test");
+conn.close();
+
+ +

Connection Pool

+
+import org.h2.jdbcx.JdbcConnectionPool;
+JdbcConnectionPool cp = JdbcConnectionPool.
+    create("jdbc:h2:~/test", "sa", "sa");
+Connection conn = cp.getConnection();
+conn.close(); cp.dispose();
+
+ +

Maven 2

+
+<dependency>
+    <groupId>com.h2database</groupId>
+    <artifactId>h2</artifactId>
+    <version>${version}</version>
+</dependency>
+
+ +

Hibernate

+

+hibernate.cfg.xml (or use the HSQLDialect): +

+
+<property name="dialect">
+    org.hibernate.dialect.H2Dialect
+</property>
+
+ +

TopLink and Glassfish

+

+Datasource class: org.h2.jdbcx.JdbcDataSource
+oracle.toplink.essentials.platform.
+database.H2Platform +

+ +
+ +
+ + \ No newline at end of file diff --git a/h2/src/docsrc/html/commands.html b/h2/src/docsrc/html/commands.html new file mode 100644 index 0000000..cb236b6 --- /dev/null +++ b/h2/src/docsrc/html/commands.html @@ -0,0 +1,181 @@ + + + + + + +Commands + + + + + +
+ + +

Commands

+

Index

+

Commands (Data Manipulation)

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

Commands (Data Definition)

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

Commands (Other)

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

Details

+ +

Click on the header of the command to switch between railroad diagram and BNF.

+ +

Non-standard syntax is marked in green. Compatibility-only non-standard syntax is marked in red, +don't use it unless you need it for compatibility with other databases or old versions of H2.

+ +

Commands (Data Manipulation)

+ +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

+${item.example}

+
+ +

Commands (Data Definition)

+ +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

+${item.example}

+
+ +

Commands (Other)

+ +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

+${item.example}

+
+ + + +
diff --git a/h2/src/docsrc/html/datatypes.html b/h2/src/docsrc/html/datatypes.html new file mode 100644 index 0000000..367ab2d --- /dev/null +++ b/h2/src/docsrc/html/datatypes.html @@ -0,0 +1,101 @@ + + + + + + +Data Types + + + + + +
+ + +

Data Types

+

Index

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

Details

+ +

Click on the header of the data type to switch between railroad diagram and BNF.

+ +

Non-standard syntax is marked in green. Compatibility-only non-standard syntax is marked in red, +don't use it unless you need it for compatibility with other databases or old versions of H2.

+ + +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

${item.example}

+
+ +

Interval Data Types

+ + +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

${item.example}

+
+ + + +
+ diff --git a/h2/src/docsrc/html/download-archive.html b/h2/src/docsrc/html/download-archive.html new file mode 100644 index 0000000..730287d --- /dev/null +++ b/h2/src/docsrc/html/download-archive.html @@ -0,0 +1,160 @@ + + + + + + +Archive Downloads + + + + + +
+ + +

Archive Downloads

+ +

Maven Central

+ +

H2 database

+

MVStore

+ +

Distribution

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
2.1.212Windows InstallerPlatform-Independent Zip
2.1.210Windows InstallerPlatform-Independent Zip
2.0.206Windows InstallerPlatform-Independent Zip
2.0.204Windows InstallerPlatform-Independent Zip
1.4.202Windows InstallerPlatform-Independent Zip
1.4.200Windows InstallerPlatform-Independent Zip
1.4.199Windows InstallerPlatform-Independent Zip
1.4.198Windows InstallerPlatform-Independent Zip
1.4.197Windows InstallerPlatform-Independent Zip
1.4.196Windows InstallerPlatform-Independent Zip
1.4.195Windows InstallerPlatform-Independent Zip
1.4.194Windows InstallerPlatform-Independent Zip
1.4.193Windows InstallerPlatform-Independent Zip
1.4.192Windows InstallerPlatform-Independent Zip
1.4.191Windows InstallerPlatform-Independent Zip
1.4.190Windows InstallerPlatform-Independent Zip
1.4.189Windows InstallerPlatform-Independent Zip
1.4.188Windows InstallerPlatform-Independent Zip
1.4.187Windows InstallerPlatform-Independent Zip
1.4.186Windows InstallerPlatform-Independent Zip
1.4.185Windows InstallerPlatform-Independent Zip
1.4.184Windows InstallerPlatform-Independent Zip
1.4.183Windows InstallerPlatform-Independent Zip
1.4.182Windows InstallerPlatform-Independent Zip
1.4.181Windows InstallerPlatform-Independent Zip
1.4.180Windows InstallerPlatform-Independent Zip
1.4.179Windows InstallerPlatform-Independent Zip
1.4.178Windows InstallerPlatform-Independent Zip
1.4.177Windows InstallerPlatform-Independent Zip
1.4.176Windows InstallerPlatform-Independent Zip
+ +

Older releases

+

+Platform-Independent Zip
+

+ +
+ diff --git a/h2/src/docsrc/html/download.html b/h2/src/docsrc/html/download.html new file mode 100644 index 0000000..a0f1a00 --- /dev/null +++ b/h2/src/docsrc/html/download.html @@ -0,0 +1,67 @@ + + + + + + +Downloads + + + + + +
+ + +

Downloads

+ +

Version ${version} (${versionDate})

+

+Windows Installer +
+Platform-Independent Zip +
+

+ +

Version 2.1.210 (2022-01-17)

+

+Windows Installer +(SHA1 checksum: ff795bf6ccefd5950d5080b596d835d13206b325)
+Platform-Independent Zip +(SHA1 checksum: 6ede99a0a987971557e878de4eddcb796d604323)
+

+ +

Archive Downloads

+

+Archive Downloads +

+ +

Maven (Binary JAR, Javadoc, and Source)

+

+Binary JAR
+Javadoc
+Sources
+

+ +

Git Source Repository

+

+Github +

+ +

+For details about changes, see the Change Log. +

+ +

News and Project Information

+

+Atom Feed
+RSS Feed
+DOAP File (what is this) +

+ +
+ diff --git a/h2/src/docsrc/html/faq.html b/h2/src/docsrc/html/faq.html new file mode 100644 index 0000000..932ef19 --- /dev/null +++ b/h2/src/docsrc/html/faq.html @@ -0,0 +1,284 @@ + + + + + + +Frequently Asked Questions + + + + + +
+ + +

Frequently Asked Questions

+ + + I Have a Problem or Feature Request
+ + Are there Known Bugs? When is the Next Release?
+ + Is this Database Engine Open Source?
+ + Is Commercial Support Available?
+ + How to Create a New Database?
+ + How to Connect to a Database?
+ + Where are the Database Files Stored?
+ + What is the Size Limit (Maximum Size) of a Database?
+ + Is it Reliable?
+ + Why is Opening my Database Slow?
+ + My Query is Slow
+ + H2 is Very Slow
+ + Column Names are Incorrect?
+ + Float is Double?
+ + How to Translate this Project?
+ + How to Contribute to this Project?
+ +

I Have a Problem or Feature Request

+

+Please read the support checklist. +

+ +

Are there Known Bugs? When is the Next Release?

+

+Usually, bugs get fixes as they are found. There is a release every few weeks. +Here is the list of known and confirmed issues: +

+
  • When opening a database file in a timezone that has different + daylight saving rules: the time part of dates where the daylight saving doesn't match + will differ. This is not a problem within regions that use the same rules (such as, within + USA, or within Europe), even if the timezone itself is different. As a workaround, export the + database to a SQL script using the old timezone, and create a new database in the new + timezone. +
  • Old versions of Tomcat and Glassfish 3 set most static fields (final or non-final) to null when + unloading a web application. This can cause a NullPointerException. + In Tomcat >= 6.0 this behavior can be disabled by setting the + system property org.apache.catalina.loader.WebappClassLoader.ENABLE_CLEAR_REFERENCES=false. + A known workaround is to put the h2*.jar file in a shared lib directory + (common/lib). + Tomcat 8.5 and newer versions don't clear fields and don't have such property. +
  • Some problems have been found with right outer join. Internally, it is converted + to left outer join, which does not always produce the same results as other databases + when used in combination with other joins. This problem is fixed in H2 version 1.3. +
+

+For a complete list, see Open Issues. +

+ +

Is this Database Engine Open Source?

+

+Yes. It is free to use and distribute, and the source code is included. +See also under license. +

+ +

Is Commercial Support Available?

+

+No, currently commercial support is not available. +

+ +

How to Create a New Database?

+

+By default, a new database is automatically created if it does not yet exist when +embedded URL is used. +See Creating New Databases. +

+ +

How to Connect to a Database?

+

+The database driver is org.h2.Driver, +and the database URL starts with jdbc:h2:. +To connect to a database using JDBC, use the following code: +

+
+Connection conn = DriverManager.getConnection("jdbc:h2:~/test", "sa", "");
+
+ +

Where are the Database Files Stored?

+

+When using database URLs like jdbc:h2:~/test, +the database is stored in the user directory. +For Windows, this is usually +C:\Documents and Settings\<userName> or +C:\Users\<userName>. +If the base directory is not set (as in jdbc:h2:./test), +the database files are stored in the directory where the application is started +(the current working directory). +When using the H2 Console application from the start menu, +this is <Installation Directory>/bin. +The base directory can be set in the database URL. +A fixed or relative path can be used. When using the URL +jdbc:h2:file:./data/sample, the database is stored in the directory +data (relative to the current working directory). +The directory is created automatically if it does not yet exist. +It is also possible to use the fully qualified directory name (and for Windows, drive name). +Example: jdbc:h2:file:C:/data/test +

+ +

What is the Size Limit (Maximum Size) of a Database?

+

+See Limits and Limitations. +

+ +

Is it Reliable?

+

+That is not easy to say. It is still a quite new product. A lot of tests have been written, +and the code coverage of these tests is higher than 80% for each package. +Randomized stress tests are run regularly. But there are probably still +bugs that have not yet been found (as with most software). Some features are known +to be dangerous, they are only supported for situations where performance is more important +than reliability. Those dangerous features are: +

+
  • Disabling the transaction log or FileDescriptor.sync() using LOG=0 or LOG=1. +
  • Using the transaction isolation level READ_UNCOMMITTED + (LOCK_MODE 0) while at the same time using multiple + connections. +
  • Disabling database file protection using (setting FILE_LOCK to + NO in the database URL). +
  • Disabling referential integrity using SET REFERENTIAL_INTEGRITY FALSE. +
+

+In addition to that, running out of memory should be avoided. +In older versions, OutOfMemory errors while using the database could corrupt a databases. +

+

+This database is well tested using automated test cases. The tests run every night +and run for more than one hour. But not all areas of this database are equally well tested. +When using one of the following features for production, please ensure your use case +is well tested (if possible with automated test cases). The areas that are not well tested are: +

+
    +
  • Platforms other than Windows, Linux, Mac OS X, or runtime environments other than Oracle / OpenJDK 7, 8, 9. +
  • The features AUTO_SERVER and AUTO_RECONNECT. +
  • Cluster mode, 2-phase commit, savepoints. +
  • Fulltext search. +
  • Operations on LOBs over 2 GB. +
  • The optimizer may not always select the best plan. +
  • Using the ICU4J collator. +
+

+Areas considered experimental are: +

+
    +
  • The PostgreSQL server +
  • Clustering (there are cases were transaction isolation can be broken + due to timing issues, for example one session overtaking another session). +
  • Compatibility modes for other databases (only some features are implemented). +
  • The soft reference cache (CACHE_TYPE=SOFT_LRU). It might not improve performance, + and out of memory issues have been reported. +
+

+Some users have reported that after a power failure, the database cannot be opened sometimes. +In this case, use a backup of the database or the Recover tool. Please report such problems. +The plan is that the database automatically recovers in all situations. +

+ +

Why is Opening my Database Slow?

+

+To find out what the problem is, use the H2 Console and click on "Test Connection" +instead of "Login". After the "Login Successful" appears, click on it (it's a link). +This will list the top stack traces. Then either analyze this yourself, or +post those stack traces in the Google Group. +

+

+Other possible reasons are: the database is very big (many GB), or contains linked tables +that are slow to open. +

+ +

My Query is Slow

+

+Slow SELECT (or DELETE, UPDATE, MERGE) +statement can have multiple reasons. Follow this checklist: +

+
    +
  • Run ANALYZE (see documentation for details). +
  • Run the query with EXPLAIN and check if indexes are used + (see documentation for details). +
  • If required, create additional indexes and try again using + ANALYZE and EXPLAIN. +
  • If it doesn't help please report the problem. +
  • +
+ + +

H2 is Very Slow

+

+By default, H2 closes the database when the last connection is closed. +If your application closes the only connection after each operation, +the database is opened and closed a lot, which is quite slow. +There are multiple ways to solve this problem, see +Database Performance Tuning. +

+ +

Column Names are Incorrect?

+

+For the query SELECT ID AS X FROM TEST the method +ResultSetMetaData.getColumnName() returns ID, I expect it to +return X. What's wrong? +

+

+This is not a bug. According the JDBC specification, the method +ResultSetMetaData.getColumnName() should return the name of the column +and not the alias name. If you need the alias name, use +ResultSetMetaData.getColumnLabel(). +Some other database don't work like this yet (they don't follow the JDBC specification). +If you need compatibility with those databases, use the Compatibility Mode. +

+

+This also applies to DatabaseMetaData calls that return a result set. +The columns in the JDBC API are column labels, not column names. +

+ +

Float is Double?

+

+For a table defined as CREATE TABLE TEST(X FLOAT) the method +ResultSet.getObject() returns a java.lang.Double, I expect it to +return a java.lang.Float. What's wrong? +

+

+This is not a bug. According the JDBC specification, the JDBC data type FLOAT +is equivalent to DOUBLE, and both are mapped to java.lang.Double. +See also + +Mapping SQL and Java Types - 8.3.10 FLOAT.

+

Use REAL or FLOAT(24) data type for java.lang.Float values.

+ +

How to Translate this Project?

+

+For more information, see +Build/Translating. +

+ +

How to Contribute to this Project?

+

+There are various way to help develop an open source project like H2. The first step could be to +translate the error messages and the GUI to your native language. +Then, you could +provide patches. +Please start with small patches. That could be adding a test case to improve the +code coverage (the target code coverage for this project is 90%, higher is better). +You will have to develop, build and run the tests. +Once you are familiar with the code, you could implement missing features from the +feature request list. +I suggest to start with very small features that are easy to implement. +Keep in mind to provide test cases as well. +

+ +
diff --git a/h2/src/docsrc/html/features.html b/h2/src/docsrc/html/features.html new file mode 100644 index 0000000..8dee94f --- /dev/null +++ b/h2/src/docsrc/html/features.html @@ -0,0 +1,1841 @@ + + + + + + +Features + + + + + +
+ + +

Features

+ + + Feature List
+ + H2 in Use
+ + Connection Modes
+ + Database URL Overview
+ + Connecting to an Embedded (Local) Database
+ + In-Memory Databases
+ + Database Files Encryption
+ + Database File Locking
+ + Opening a Database Only if it Already Exists
+ + Closing a Database
+ + Ignore Unknown Settings
+ + Changing Other Settings when Opening a Connection
+ + Custom File Access Mode
+ + Multiple Connections
+ + Database File Layout
+ + Logging and Recovery
+ + Compatibility
+ + Auto-Reconnect
+ + Automatic Mixed Mode
+ + Page Size
+ + Using the Trace Options
+ + Using Other Logging APIs
+ + Read Only Databases
+ + Read Only Databases in Zip or Jar File
+ + Generated Columns (Computed Columns) / Function Based Index
+ + Multi-Dimensional Indexes
+ + User-Defined Functions and Stored Procedures
+ + Pluggable or User-Defined Tables
+ + Triggers
+ + Compacting a Database
+ + Cache Settings
+ + External Authentication (Experimental)
+ +

Feature List

+

Main Features

+
    +
  • Very fast database engine +
  • Open source +
  • Written in Java +
  • Supports standard SQL, JDBC API +
  • Embedded and Server mode, Clustering support +
  • Strong security features +
  • The PostgreSQL ODBC driver can be used +
  • Multi version concurrency +
+ +

Additional Features

+
    +
  • Disk based or in-memory databases and tables, read-only database support, temporary tables +
  • Transaction support (read uncommitted, read committed, repeatable read, snapshot), 2-phase-commit +
  • Multiple connections, row-level locking +
  • Cost based optimizer, using a genetic algorithm for complex queries, zero-administration +
  • Scrollable and updatable result set support, large result set, external result sorting, + functions can return a result set +
  • Encrypted database (AES), SHA-256 password encryption, encryption functions, SSL +
+ +

SQL Support

+
    +
  • Support for multiple schemas, information schema +
  • Referential integrity / foreign key constraints with cascade, check constraints +
  • Inner and outer joins, subqueries, read only views and inline views +
  • Triggers and Java functions / stored procedures +
  • Many built-in functions, including XML and lossless data compression +
  • Wide range of data types including large objects (BLOB/CLOB) and arrays +
  • Sequences and identity columns, generated columns (can be used for function based indexes) +
  • ORDER BY, GROUP BY, HAVING, UNION, OFFSET / FETCH (including PERCENT and WITH TIES), LIMIT, TOP, + DISTINCT / DISTINCT ON (...) +
  • Window functions +
  • Collation support, including support for the ICU4J library +
  • Support for users and roles +
  • Compatibility modes for IBM DB2, Apache Derby, HSQLDB, + MS SQL Server, MySQL, Oracle, and PostgreSQL. +
+ +

Security Features

+
    +
  • Includes a solution for the SQL injection problem +
  • User password authentication uses SHA-256 and salt +
  • For server mode connections, user passwords are never transmitted in plain text over the network + (even when using insecure connections; this only applies to the TCP server and not to the H2 Console however; + it also doesn't apply if you set the password in the database URL) +
  • All database files (including script files that can be used to backup data) can be +encrypted using the AES-128 encryption algorithm +
  • The remote JDBC driver supports TCP/IP connections over TLS +
  • The built-in web server supports connections over TLS +
  • Passwords can be sent to the database using char arrays instead of Strings +
+ +

Other Features and Tools

+
    +
  • Small footprint (around 2.5 MB), low memory requirements +
  • Multiple index types (b-tree, tree, hash) +
  • Support for multi-dimensional indexes +
  • CSV (comma separated values) file support +
  • Support for linked tables, and a built-in virtual 'range' table +
  • Supports the EXPLAIN PLAN statement; sophisticated trace options +
  • Database closing can be delayed or disabled to improve the performance +
  • Web-based Console application (translated to many languages) with autocomplete +
  • The database can generate SQL script files +
  • Contains a recovery tool that can dump the contents of the database +
  • Support for variables (for example to calculate running totals) +
  • Automatic re-compilation of prepared statements +
  • Uses a small number of database files +
  • Uses a checksum for each record and log entry for data integrity +
  • Well tested (high code coverage, randomized stress tests) +
+ +

H2 in Use

+

+For a list of applications that work with or use H2, see: +Links. +

+ +

Connection Modes

+

+The following connection modes are supported: +

+
    +
  • Embedded mode (local connections using JDBC) +
  • Server mode (remote connections using JDBC or ODBC over TCP/IP) +
  • Mixed mode (local and remote connections at the same time) +
+ +

Embedded Mode

+

+In embedded mode, an application opens a database from within the same JVM using JDBC. +This is the fastest and easiest connection mode. +The disadvantage is that a database may only be open in one virtual machine (and class loader) at any time. +As in all modes, both persistent and in-memory databases are supported. +There is no limit on the number of database open concurrently, +or on the number of open connections. +

+

+In embedded mode I/O operations can be performed by application's threads that execute a SQL command. +The application may not interrupt these threads, it can lead to database corruption, +because JVM closes I/O handle during thread interruption. +Consider other ways to control execution of your application. +When interrupts are possible the async: +file system can be used as a workaround, but full safety is not guaranteed. +It's recommended to use the client-server model instead, the client side may interrupt own threads. +

+The database is embedded in the application + +

Server Mode

+

+When using the server mode (sometimes called remote mode or client/server mode), +an application opens a database remotely using the JDBC or ODBC API. +A server needs to be started within the same or another virtual machine, or on another computer. +Many applications can connect to the same database at the same time, by connecting to this server. +Internally, the server process opens the database(s) in embedded mode. +

+

+The server mode is slower than the embedded mode, because all data is transferred over TCP/IP. +As in all modes, both persistent and in-memory databases are supported. +There is no limit on the number of database open concurrently per server, +or on the number of open connections. +

+The database is running in a server; the application connects to the server + +

Mixed Mode

+

+The mixed mode is a combination of the embedded and the server mode. +The first application that connects to a database does that in embedded mode, but also starts +a server so that other applications (running in different processes or virtual machines) can +concurrently access the same data. The local connections are as fast as if +the database is used in just the embedded mode, while the remote +connections are a bit slower. +

+The server can be started and stopped from within the application (using the server API), +or automatically (automatic mixed mode). When using the automatic mixed mode, +all clients that want to connect to the database (no matter if +it's an local or remote connection) can do so using the exact same database URL. +

+Database, server, and application run in one JVM; an application connects + +

Database URL Overview

+

+This database supports multiple connection modes and connection settings. +This is achieved using different database URLs. Settings in the URLs are not case sensitive. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TopicURL Format and Examples
Embedded (local) connection + jdbc:h2:[file:][<path>]<databaseName>
+ jdbc:h2:~/test
+ jdbc:h2:file:/data/sample
+ jdbc:h2:file:C:/data/sample (Windows only)
+
In-memory (private)jdbc:h2:mem:
In-memory (named) + jdbc:h2:mem:<databaseName>
+ jdbc:h2:mem:test_mem +
Server mode (remote connections)
using TCP/IP
+ jdbc:h2:tcp://<server>[:<port>]/[<path>]<databaseName>
+ jdbc:h2:tcp://localhost/~/test
+ jdbc:h2:tcp://dbserv:8084/~/sample
+ jdbc:h2:tcp://localhost/mem:test
+
Server mode (remote connections)
using TLS
+ jdbc:h2:ssl://<server>[:<port>]/[<path>]<databaseName>
+ jdbc:h2:ssl://localhost:8085/~/sample; +
Using encrypted files + jdbc:h2:<url>;CIPHER=AES
+ jdbc:h2:ssl://localhost/~/test;CIPHER=AES
+ jdbc:h2:file:~/secure;CIPHER=AES
+
File locking methods + jdbc:h2:<url>;FILE_LOCK={FILE|SOCKET|FS|NO}
+ jdbc:h2:file:~/private;CIPHER=AES;FILE_LOCK=SOCKET
+
Only open if it already exists + jdbc:h2:<url>;IFEXISTS=TRUE
+ jdbc:h2:file:~/sample;IFEXISTS=TRUE
+
Don't close the database when the VM exits + jdbc:h2:<url>;DB_CLOSE_ON_EXIT=FALSE +
Execute SQL on connection + jdbc:h2:<url>;INIT=RUNSCRIPT FROM '~/create.sql'
+ jdbc:h2:file:~/sample;INIT=RUNSCRIPT FROM '~/create.sql'\;RUNSCRIPT FROM '~/populate.sql'
+
User name and/or password + jdbc:h2:<url>[;USER=<username>][;PASSWORD=<value>]
+ jdbc:h2:file:~/sample;USER=sa;PASSWORD=123
+
Debug trace settings + jdbc:h2:<url>;TRACE_LEVEL_FILE=<level 0..3>
+ jdbc:h2:file:~/sample;TRACE_LEVEL_FILE=3
+
Ignore unknown settings + jdbc:h2:<url>;IGNORE_UNKNOWN_SETTINGS=TRUE
+
Custom file access mode + jdbc:h2:<url>;ACCESS_MODE_DATA=rws
+
Database in a zip file + jdbc:h2:zip:<zipFileName>!/<databaseName>
+ jdbc:h2:zip:~/db.zip!/test +
Compatibility mode + jdbc:h2:<url>;MODE=<databaseType>
+ jdbc:h2:~/test;MODE=MYSQL;DATABASE_TO_LOWER=TRUE +
Auto-reconnect + jdbc:h2:<url>;AUTO_RECONNECT=TRUE
+ jdbc:h2:tcp://localhost/~/test;AUTO_RECONNECT=TRUE +
Automatic mixed mode + jdbc:h2:<url>;AUTO_SERVER=TRUE
+ jdbc:h2:~/test;AUTO_SERVER=TRUE +
Page size + jdbc:h2:<url>;PAGE_SIZE=512
+
Changing other settings + jdbc:h2:<url>;<setting>=<value>[;<setting>=<value>...]
+ jdbc:h2:file:~/sample;TRACE_LEVEL_SYSTEM_OUT=3
+
+ +

Connecting to an Embedded (Local) Database

+

+The database URL for connecting to a local database is +jdbc:h2:[file:][<path>]<databaseName>. +The prefix file: is optional. If no or only a relative path is used, then the current working +directory is used as a starting point. The case sensitivity of the path and database name depend on the +operating system, however it is recommended to use lowercase letters only. +The database name must be at least three characters long +(a limitation of File.createTempFile). +The database name must not contain a semicolon. +To point to the user home directory, use ~/, as in: jdbc:h2:~/test. +

+ +

In-Memory Databases

+

+For certain use cases (for example: rapid prototyping, testing, high performance +operations, read-only databases), it may not be required to persist data, or persist changes to the data. +This database supports the in-memory mode, where the data is not persisted. +

+In some cases, only one connection to a in-memory database is required. +This means the database to be opened is private. In this case, the database URL is +jdbc:h2:mem: Opening two connections within the same virtual machine +means opening two different (private) databases. +

+Sometimes multiple connections to the same in-memory database are required. +In this case, the database URL must include a name. Example: jdbc:h2:mem:db1. +Accessing the same database using this URL only works within the same virtual machine and +class loader environment. +

+To access an in-memory database from another process or from another computer, +you need to start a TCP server in the same process as the in-memory database was created. +The other processes then need to access the database over TCP/IP or TLS, +using a database URL such as: jdbc:h2:tcp://localhost/mem:db1. +

+By default, closing the last connection to a database closes the database. +For an in-memory database, this means the content is lost. +To keep the database open, add ;DB_CLOSE_DELAY=-1 to the database URL. +To keep the content of an in-memory database as long as the virtual machine is alive, use +jdbc:h2:mem:test;DB_CLOSE_DELAY=-1. +This may create a memory leak, when you need to remove the database, use +the SHUTDOWN command. +

+ +

Database Files Encryption

+

+The database files can be encrypted. +Three encryption algorithms are supported: +

+
    +
  • "AES" - also known as Rijndael, only AES-128 is implemented.
  • +
  • "XTEA" - the 32 round version.
  • +
  • "FOG" - pseudo-encryption only useful for hiding data from a text editor.
  • +
+

+To use file encryption, you need to specify the encryption algorithm (the 'cipher') +and the file password (in addition to the user password) when connecting to the database. +

+ +

Creating a New Database with File Encryption

+

+By default, a new database is automatically created if it does not exist yet +when the embedded url is used. +To create an encrypted database, connect to it as it would already exist locally using the embedded URL. +

+ +

Connecting to an Encrypted Database

+

+The encryption algorithm is set in the database URL, and the file password is specified in the password field, +before the user password. A single space separates the file password +and the user password; the file password itself may not contain spaces. File passwords +and user passwords are case sensitive. Here is an example to connect to a +password-encrypted database: +

+
+String url = "jdbc:h2:~/test;CIPHER=AES";
+String user = "sa";
+String pwds = "filepwd userpwd";
+conn = DriverManager.
+    getConnection(url, user, pwds);
+
+ +

Encrypting or Decrypting a Database

+

+To encrypt an existing database, use the ChangeFileEncryption tool. +This tool can also decrypt an encrypted database, or change the file encryption key. +The tool is available from within the H2 Console in the tools section, or you can run it from the command line. +The following command line will encrypt the database test in the user home directory +with the file password filepwd and the encryption algorithm AES: +

+
+java -cp h2*.jar org.h2.tools.ChangeFileEncryption -dir ~ -db test -cipher AES -encrypt filepwd
+
+ +

Database File Locking

+

+Whenever a database is opened, a lock file is created to signal other processes +that the database is in use. If database is closed, or if the process that opened +the database terminates, this lock file is deleted. +

+The following file locking methods are implemented: +

+
    +
  • The default method is FILE and uses a watchdog thread to +protect the database file. The watchdog reads the lock file each second. +
  • The second method is SOCKET and opens a server socket. +The socket method does not require reading the lock file every second. +The socket method should only be used if the database files +are only accessed by one (and always the same) computer. +
  • The third method is FS. +This will use native file locking using FileChannel.lock. +
  • It is also possible to open the database without file locking; +in this case it is up to the application to protect the database files. +Failing to do so will result in a corrupted database. +Using the method NO forces the database to not create a lock file at all. +Please note that this is unsafe as another process is able to open the same database, +possibly leading to data corruption.
+

+To open the database with a different file locking method, use the parameter +FILE_LOCK. +The following code opens the database with the 'socket' locking method: +

+
+String url = "jdbc:h2:~/test;FILE_LOCK=SOCKET";
+
+

+For more information about the algorithms, see +Advanced / File Locking Protocols. +

+ +

Opening a Database Only if it Already Exists

+

+By default, when an application calls DriverManager.getConnection(url, ...) +with embedded URL and the database specified in the URL does not yet exist, +a new (empty) database is created. +In some situations, it is better to restrict creating new databases, and only allow to open +existing databases. To do this, add ;IFEXISTS=TRUE +to the database URL. In this case, if the database does not already exist, an exception is thrown when +trying to connect. The connection only succeeds when the database already exists. +The complete URL may look like this: +

+
+String url = "jdbc:h2:/data/sample;IFEXISTS=TRUE";
+
+ +

Closing a Database

+ +

Delayed Database Closing

+

+Usually, a database is closed when the last connection to it is closed. In some situations +this slows down the application, for example when it is not possible to keep at least one connection open. +The automatic closing of a database can be delayed or disabled with the SQL statement +SET DB_CLOSE_DELAY <seconds>. +The parameter <seconds> specifies the number of seconds to keep +a database open after the last connection to it was closed. The following statement +will keep a database open for 10 seconds after the last connection was closed: +

+
+SET DB_CLOSE_DELAY 10
+
+

+The value -1 means the database is not closed automatically. +The value 0 is the default and means the database is closed when the last connection is closed. +This setting is persistent and can be set by an administrator only. +It is possible to set the value in the database URL: jdbc:h2:~/test;DB_CLOSE_DELAY=10. +

+ +

Don't Close a Database when the VM Exits

+

+By default, a database is closed when the last connection is closed. However, if it is never closed, +the database is closed when the virtual machine exits normally, using a shutdown hook. +In some situations, the database should not be closed in this case, for example because the +database is still used at virtual machine shutdown (to store the shutdown process in the database for example). +For those cases, the automatic closing of the database can be disabled in the database URL. +The first connection (the one that is opening the database) needs to +set the option in the database URL (it is not possible to change the setting afterwards). +The database URL to disable database closing on exit is: +

+
+String url = "jdbc:h2:~/test;DB_CLOSE_ON_EXIT=FALSE";
+
+ +

Execute SQL on Connection

+

+Sometimes, particularly for in-memory databases, it is useful to be able to execute DDL or DML +commands automatically when a client connects to a database. This functionality is enabled via +the INIT property. Note that multiple commands may be passed to INIT, but the semicolon delimiter +must be escaped, as in the example below. +

+
+String url = "jdbc:h2:mem:test;INIT=runscript from '~/create.sql'\\;runscript from '~/init.sql'";
+
+

+Please note the double backslash is only required in a Java or properties file. +In a GUI, or in an XML file, only one backslash is required: +

+
+<property name="url" value=
+"jdbc:h2:mem:test;INIT=create schema if not exists test\;runscript from '~/sql/init.sql'"
+/>
+
+

+Backslashes within the init script (for example within a runscript statement, to specify the folder names in Windows) +need to be escaped as well (using a second backslash). It might be simpler to avoid backslashes in folder names for this reason; +use forward slashes instead. +

+ +

Ignore Unknown Settings

+

+Some applications (for example OpenOffice.org Base) pass some additional parameters +when connecting to the database. Why those parameters are passed is unknown. +The parameters PREFERDOSLIKELINEENDS and +IGNOREDRIVERPRIVILEGES are such examples; +they are simply ignored to improve the compatibility with OpenOffice.org. If an application +passes other parameters when connecting to the database, usually the database throws an exception +saying the parameter is not supported. It is possible to ignored such parameters by adding +;IGNORE_UNKNOWN_SETTINGS=TRUE to the database URL. +

+ +

Changing Other Settings when Opening a Connection

+

+In addition to the settings already described, +other database settings can be passed in the database URL. +Adding ;setting=value at the end of a database URL is the +same as executing the statement SET setting value just after +connecting. For a list of supported settings, see SQL Grammar +or the DbSettings javadoc. +

+ +

Custom File Access Mode

+

+Usually, the database opens the database file with the access mode +rw, meaning read-write (except for read only databases, +where the mode r is used). +To open a database in read-only mode if the database file is not read-only, use +ACCESS_MODE_DATA=r. +Also supported are rws and rwd. +This setting must be specified in the database URL: +

+
+String url = "jdbc:h2:~/test;ACCESS_MODE_DATA=rws";
+
+

+For more information see Durability Problems. +On many operating systems the access mode rws does not guarantee that the data is written to the disk. +

+ +

Multiple Connections

+ +

Opening Multiple Databases at the Same Time

+

+An application can open multiple databases at the same time, including multiple +connections to the same database. The number of open database is only limited by the memory available. +

+ +

Multiple Connections to the Same Database: Client/Server

+

+If you want to access the same database at the same time from different processes or computers, +you need to use the client / server mode. In this case, one process acts as the server, and the +other processes (that could reside on other computers as well) connect to the server via TCP/IP +(or TLS over TCP/IP for improved security). +

+ +

Multithreading Support

+

+This database is multithreading-safe. +If an application is multi-threaded, it does not need to worry about synchronizing access to the database. +An application should normally use one connection per thread. +This database synchronizes access to the same connection, but other databases may not do this. +To get higher concurrency, you need to use multiple connections. +

+

+An application can use multiple threads that access the same database at the same time. +Threads that use different connections can use the database concurrently. +

+ +

Locking, Lock-Timeout, Deadlocks

+

+Usually, SELECT statements will generate read locks. This includes subqueries. +Statements that modify data use write locks on the modified rows. +It is also possible to issue write locks without modifying data, +using the statement SELECT ... FOR UPDATE. +Data definition statements may issue exclusive locks on tables. +The statements COMMIT and +ROLLBACK releases all open locks. +The commands SAVEPOINT and +ROLLBACK TO SAVEPOINT don't affect locks. +The locks are also released when the autocommit mode changes, and for connections with +autocommit set to true (this is the default), locks are released after each statement. +The following statements generate locks: +

+ + + + + + + + + + + + + + + + + + + + + +
Type of LockSQL Statement
ReadSELECT * FROM TEST;
+ CALL SELECT MAX(ID) FROM TEST;
+ SCRIPT;
Write (row-level)SELECT * FROM TEST WHERE 1=0 FOR UPDATE;
Write (row-level)INSERT INTO TEST VALUES(1, 'Hello');
+ INSERT INTO TEST SELECT * FROM TEST;
+ UPDATE TEST SET NAME='Hi';
+ DELETE FROM TEST;
ExclusiveALTER TABLE TEST ...;
+ CREATE INDEX ... ON TEST ...;
+ DROP INDEX ...;
+

+The number of seconds until a lock timeout exception is thrown can be +set separately for each connection using the SQL command +SET LOCK_TIMEOUT <milliseconds>. +The initial lock timeout (that is the timeout used for new connections) can be set using the SQL command +SET DEFAULT_LOCK_TIMEOUT <milliseconds>. The default lock timeout is persistent. +

+ +

Database File Layout

+

+The following files are created for persistent databases: +

+ + + + + + + + +
File NameDescriptionNumber of Files
+ test.mv.db + + Database file.
+ Contains the transaction log, indexes, and data for all tables.
+ Format: <database>.mv.db +
+ 1 per database +
+ test.newFile + + Temporary file for database compaction.
+ Contains the new MVStore file.
+ Format: <database>.newFile +
+ 0 or 1 per database +
+ test.tempFile + + Temporary file for database compaction.
+ Contains the temporary MVStore file.
+ Format: <database>.tempFile +
+ 0 or 1 per database +
+ test.lock.db + + Database lock file.
+ Automatically (re-)created while the database is in use.
+ Format: <database>.lock.db +
+ 1 per database (only if in use) +
+ test.trace.db + + Trace file (if the trace option is enabled).
+ Contains trace information.
+ Format: <database>.trace.db
+ Renamed to <database>.trace.db.old if too big. +
+ 0 or 1 per database +
+ test.123.temp.db + + Temporary file.
+ Contains a temporary blob or a large result set.
+ Format: <database>.<id>.temp.db +
+ 1 per object +
+ +

Moving and Renaming Database Files

+

+Database name and location are not stored inside the database files. +

+While a database is closed, the files can be moved to another directory, and they can +be renamed as well (as long as all files of the same database start with the same +name and the respective extensions are unchanged). +

+As there is no platform specific data in the files, they can be moved to other operating systems +without problems. +

+ +

Backup

+

+When the database is closed, it is possible to backup the database files. +

+To backup data while the database is running, +the SQL commands SCRIPT and BACKUP can be used. +

+ +

Logging and Recovery

+

+Whenever data is modified in the database and those changes are committed, the changes are written +to the transaction log (except for in-memory objects). The changes to the main data area itself are usually written +later on, to optimize disk access. If there is a power failure, the main data area is not up-to-date, +but because the changes are in the transaction log, the next time the database is opened, the changes +are re-applied automatically. +

+ +

Compatibility

+

+All database engines behave a little bit different. Where possible, H2 supports the ANSI SQL standard, +and tries to be compatible to other databases. There are still a few differences however: +

+

+In MySQL text columns are case insensitive by default, while in H2 they are case sensitive. However +H2 supports case insensitive columns as well. To create the tables with case insensitive texts, append +IGNORECASE=TRUE to the database URL +(example: jdbc:h2:~/test;IGNORECASE=TRUE). +

+ +

Compatibility Modes

+

+For certain features, this database can emulate the behavior of specific databases. +However, only a small subset of the differences between databases are implemented in this way. +Here is the list of currently supported modes and the differences to the regular mode: +

+ +

REGULAR Compatibility mode

+

+This mode is used by default. +

+
  • Empty IN predicate is allowed. +
  • TOP clause in SELECT is allowed. +
  • OFFSET/LIMIT clauses are allowed. +
  • MINUS can be used instead of EXCEPT. +
  • IDENTITY can be used as a data type. +
  • Legacy SERIAL and BIGSERIAL data types are supported. +
  • AUTO_INCREMENT clause can be used instead of GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY. +
+ +

STRICT Compatibility Mode

+

+To use the STRICT mode, use the database URL jdbc:h2:~/test;MODE=STRICT +or the SQL statement SET MODE STRICT. +In this mode some deprecated features are disabled. +

+

+If your application or library uses only the H2 or it generates different SQL for different database systems +it is recommended to use this compatibility mode in unit tests +to reduce possibility of accidental misuse of such features. +This mode cannot be used as SQL validator, however. +

+

+It is not recommended to enable this mode in production builds of libraries, +because this mode may become more restrictive in future releases of H2 that may break your library +if it will be used together with newer version of H2. +

+
  • Empty IN predicate is disallowed. +
  • TOP and OFFSET/LIMIT clauses are disallowed, only OFFSET/FETCH can be used. +
  • MINUS cannot be used instead of EXCEPT. +
  • IDENTITY cannot be used as a data type and AUTO_INCREMENT clause cannot be specified. +Use GENERATED BY DEFAULT AS IDENTITY clause instead. +
  • SERIAL and BIGSERIAL data types are disallowed. +Use INTEGER GENERATED BY DEFAULT AS IDENTITY or BIGINT GENERATED BY DEFAULT AS IDENTITY instead. +
+ +

LEGACY Compatibility Mode

+

+To use the LEGACY mode, use the database URL jdbc:h2:~/test;MODE=LEGACY +or the SQL statement SET MODE LEGACY. +In this mode some compatibility features for applications written for H2 1.X are enabled. +This mode doesn't provide full compatibility with H2 1.X. +

+
  • Empty IN predicate is allowed. +
  • TOP clause in SELECT is allowed. +
  • OFFSET/LIMIT clauses are allowed. +
  • MINUS can be used instead of EXCEPT. +
  • IDENTITY can be used as a data type. +
  • MS SQL Server-style IDENTITY clause is supported. +
  • Legacy SERIAL and BIGSERIAL data types are supported. +
  • AUTO_INCREMENT clause can be used instead of GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY. +
  • If a value for identity column was specified in an INSERT command +the base value of sequence generator of this column is updated if current value of generator was smaller +(larger for generators with negative increment) than the inserted value. +
  • Identity columns have implicit DEFAULT ON NULL clause. +It means a NULL value may be specified for this column in INSERT command and it will be treated as DEFAULT. +
  • Oracle-style CURRVAL and NEXTVAL can be used on sequences. +
  • TOP clause can be used in DELETE and UPDATE. +
  • Non-standard Oracle-style WHERE clause can be used in standard MERGE command. +
  • Attempt to reference a non-unique set of columns from a referential constraint +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

+

+To use the IBM DB2 mode, use the database URL jdbc:h2:~/test;MODE=DB2;DEFAULT_NULL_ORDERING=HIGH +or the SQL statement SET MODE DB2. +

+
  • For aliased columns, ResultSetMetaData.getColumnName() + returns the alias name and getTableName() returns + null. +
  • Support the pseudo-table SYSIBM.SYSDUMMY1. +
  • 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. +
  • LIMIT / OFFSET clauses are supported. +
  • MINUS can be used instead of EXCEPT. +
  • Unsafe comparison operators between numeric and boolean values are allowed. +
+ +

Derby Compatibility Mode

+

+To use the Apache Derby mode, use the database URL jdbc:h2:~/test;MODE=Derby;DEFAULT_NULL_ORDERING=HIGH +or the SQL statement SET MODE Derby. +

+
  • For aliased columns, ResultSetMetaData.getColumnName() + returns the alias name and getTableName() returns + null. +
  • For unique indexes, NULL is distinct. + That means only one row with NULL in one of the columns is allowed. +
  • Support the pseudo-table SYSIBM.SYSDUMMY1. +
  • Datetime value functions return the same value within a command. +
+ +

HSQLDB Compatibility Mode

+

+To use the HSQLDB mode, use the database URL jdbc:h2:~/test;MODE=HSQLDB;DEFAULT_NULL_ORDERING=FIRST +or the SQL statement SET MODE HSQLDB. +

+
  • Text can be concatenated using '+'. +
  • NULL value works like DEFAULT value is assignments to identity columns. +
  • Datetime value functions return the same value within a command. +
  • TOP clause in SELECT is supported. +
  • 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

+

+To use the MS SQL Server mode, use the database URL jdbc:h2:~/test;MODE=MSSQLServer +or the SQL statement SET MODE MSSQLServer. +

+
  • For aliased columns, ResultSetMetaData.getColumnName() + returns the alias name and getTableName() returns + null. +
  • Identifiers may be quoted using square brackets as in [Test]. +
  • For unique indexes, NULL is distinct. + That means only one row with NULL in one of the columns is allowed. +
  • Text can be concatenated using '+'. +
  • Arguments of LOG() function are swapped. +
  • MONEY data type is treated like NUMERIC(19, 4) data type. SMALLMONEY data type is treated like NUMERIC(10, 4) + data type. +
  • IDENTITY can be used for automatic id generation on column level. +
  • Table hints are discarded. Example: SELECT * FROM table WITH (NOLOCK). +
  • Datetime value functions return the same value within a command. +
  • 0x literals are parsed as binary string literals. +
  • TRUNCATE TABLE restarts next values of generated columns. +
  • TOP clause in SELECT, UPDATE, and DELETE is supported. +
  • Unsafe comparison operators between numeric and boolean values are allowed. +
+ +

MariaDB Compatibility Mode

+

+To use the MariaDB mode, use the database URL jdbc:h2:~/test;MODE=MariaDB;DATABASE_TO_LOWER=TRUE. +When case-insensitive identifiers are needed append ;CASE_INSENSITIVE_IDENTIFIERS=TRUE to URL. +Do not change value of DATABASE_TO_LOWER after creation of database. +

+
  • Creating indexes in the CREATE TABLE statement is allowed using + INDEX(..) or KEY(..). + Example: create table test(id int primary key, name varchar(255), key idx_name(name)); +
  • When converting a floating point number to an integer, the fractional + digits are not truncated, but the value is rounded. +
  • ON DUPLICATE KEY UPDATE is supported in INSERT statements, due to this feature VALUES has special non-standard + meaning is some contexts. +
  • INSERT IGNORE is partially supported and may be used to skip rows with duplicate keys if ON DUPLICATE KEY + UPDATE is not specified. +
  • REPLACE INTO is partially supported. +
  • Spaces are trimmed from the right side of CHAR values. +
  • REGEXP_REPLACE() uses \ for back-references. +
  • Datetime value functions return the same value within a command. +
  • 0x literals are parsed as binary string literals. +
  • Unrelated expressions in ORDER BY clause of DISTINCT queries are allowed. +
  • Some MariaDB-specific ALTER TABLE commands are partially supported. +
  • TRUNCATE TABLE restarts next values of generated columns. +
  • NEXT VALUE FOR returns different values when invoked multiple times within the same row. +
  • If value of an identity column was manually specified, its sequence is updated to generate values after +inserted. +
  • NULL value works like DEFAULT value is assignments to identity columns. +
  • LIMIT / OFFSET clauses are supported. +
  • AUTO_INCREMENT clause can be used. +
  • YEAR data type is treated like SMALLINT data type. +
  • GROUP BY clause can contain 1-based positions of expressions from the SELECT list. +
  • Unsafe comparison operators between numeric and boolean values are allowed. +
+

+Text comparison in MariaDB is case insensitive by default, while in H2 it is case sensitive (as in most other databases). +H2 does support case insensitive text comparison, but it needs to be set separately, +using SET IGNORECASE TRUE. +This affects comparison using =, LIKE, REGEXP. +

+ +

MySQL Compatibility Mode

+

+To use the MySQL mode, use the database URL jdbc:h2:~/test;MODE=MySQL;DATABASE_TO_LOWER=TRUE. +When case-insensitive identifiers are needed append ;CASE_INSENSITIVE_IDENTIFIERS=TRUE to URL. +Do not change value of DATABASE_TO_LOWER after creation of database. +

+
  • Creating indexes in the CREATE TABLE statement is allowed using + INDEX(..) or KEY(..). + Example: create table test(id int primary key, name varchar(255), key idx_name(name)); +
  • When converting a floating point number to an integer, the fractional + digits are not truncated, but the value is rounded. +
  • ON DUPLICATE KEY UPDATE is supported in INSERT statements, due to this feature VALUES has special non-standard + meaning is some contexts. +
  • INSERT IGNORE is partially supported and may be used to skip rows with duplicate keys if ON DUPLICATE KEY + UPDATE is not specified. +
  • REPLACE INTO is partially supported. +
  • Spaces are trimmed from the right side of CHAR values. +
  • REGEXP_REPLACE() uses \ for back-references. +
  • Datetime value functions return the same value within a command. +
  • 0x literals are parsed as binary string literals. +
  • Unrelated expressions in ORDER BY clause of DISTINCT queries are allowed. +
  • Some MySQL-specific ALTER TABLE commands are partially supported. +
  • TRUNCATE TABLE restarts next values of generated columns. +
  • If value of an identity column was manually specified, its sequence is updated to generate values after +inserted. +
  • NULL value works like DEFAULT value is assignments to identity columns. +
  • Referential constraints don't require an existing primary key or unique constraint on referenced columns +and create a unique constraint automatically if such constraint doesn't exist. +
  • LIMIT / OFFSET clauses are supported. +
  • AUTO_INCREMENT clause can be used. +
  • YEAR data type is treated like SMALLINT data type. +
  • GROUP BY clause can contain 1-based positions of expressions from the SELECT list. +
  • Unsafe comparison operators between numeric and boolean values are allowed. +
+

+Text comparison in MySQL is case insensitive by default, while in H2 it is case sensitive (as in most other databases). +H2 does support case insensitive text comparison, but it needs to be set separately, +using SET IGNORECASE TRUE. +This affects comparison using =, LIKE, REGEXP. +

+ +

Oracle Compatibility Mode

+

+To use the Oracle mode, use the database URL jdbc:h2:~/test;MODE=Oracle;DEFAULT_NULL_ORDERING=HIGH +or the SQL statement SET MODE Oracle. +

+
  • For aliased columns, ResultSetMetaData.getColumnName() + returns the alias name and getTableName() returns + null. +
  • When using unique indexes, multiple rows with NULL + in all columns are allowed, however it is not allowed to have multiple rows with the + same values otherwise. +
  • Empty strings are treated like NULL values, concatenating NULL with another value + results in the other value. +
  • REGEXP_REPLACE() uses \ for back-references. +
  • RAWTOHEX() converts character strings to hexadecimal representation of their UTF-8 encoding. +
  • HEXTORAW() decodes a hexadecimal character string to a binary string. +
  • DATE data type is treated like TIMESTAMP(0) data type. +
  • Datetime value functions return the same value within a command. +
  • ALTER TABLE MODIFY COLUMN command is partially supported. +
  • 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

+

+To use the PostgreSQL mode, use the database URL +jdbc:h2:~/test;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE;DEFAULT_NULL_ORDERING=HIGH. +Do not change value of DATABASE_TO_LOWER after creation of database. +

+
  • For aliased columns, ResultSetMetaData.getColumnName() + returns the alias name and getTableName() returns + null. +
  • When converting a floating point number to an integer, the fractional + digits are not be truncated, but the value is rounded. +
  • The system columns ctid and + oid are supported. +
  • LOG(x) is base 10 in this mode. +
  • REGEXP_REPLACE(): +
      +
    • uses \ for back-references;
    • +
    • does not throw an exception when the flagsString parameter contains a 'g';
    • +
    • replaces only the first matched substring in the absence of the 'g' flag in the flagsString parameter.
    • +
    +
  • LIMIT / OFFSET clauses are supported. +
  • Legacy SERIAL and BIGSERIAL data types are supported. +
  • 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. +
  • 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. +
  • EXTRACT function with DOW field returns (0-6), Sunday is 0. +
  • UPDATE with FROM is supported. +
  • GROUP BY clause can contain 1-based positions of expressions from the SELECT list. +
+ +

Auto-Reconnect

+

+The auto-reconnect feature causes the JDBC driver to reconnect to +the database if the connection is lost. The automatic re-connect only +occurs when auto-commit is enabled; if auto-commit is disabled, an exception is thrown. +To enable this mode, append ;AUTO_RECONNECT=TRUE to the database URL. +

+

+Re-connecting will open a new session. After an automatic re-connect, +variables and local temporary tables definitions (excluding data) are re-created. +The contents of the system table INFORMATION_SCHEMA.SESSION_STATE +contains all client side state that is re-created. +

+

+If another connection uses the database in exclusive mode (enabled using SET EXCLUSIVE 1 +or SET EXCLUSIVE 2), then this connection will try to re-connect until the exclusive mode ends. +

+ +

Automatic Mixed Mode

+

+Multiple processes can access the same database without having to start the server manually. +To do that, append ;AUTO_SERVER=TRUE to the database URL. +You can use the same database URL independent of whether the database is already open or not. +This feature doesn't work with in-memory databases. Example database URL: +

+
+jdbc:h2:/data/test;AUTO_SERVER=TRUE
+
+

+Use the same URL for all connections to this database. Internally, when using this mode, +the first connection to the database is made in embedded mode, and additionally a server +is started internally (as a daemon thread). If the database is already open in another process, +the server mode is used automatically. The IP address and port of the server are stored in the file +.lock.db, that's why in-memory databases can't be supported. +

+

+The application that opens the first connection to the database uses the embedded mode, +which is faster than the server mode. Therefore the main application should open +the database first if possible. The first connection automatically starts a server on a random port. +This server allows remote connections, however only to this database (to ensure that, +the client reads .lock.db file and sends the random key that is stored there to the server). +When the first connection is closed, the server stops. If other (remote) connections are still +open, one of them will then start a server (auto-reconnect is enabled automatically). +

+

+All processes need to have access to the database files. +If the first connection is closed (the connection that started the server), open transactions of other connections will be rolled back +(this may not be a problem if you don't disable autocommit). +Explicit client/server connections (using jdbc:h2:tcp:// or ssl://) are not supported. +This mode is not supported for in-memory databases. +

+

+Here is an example how to use this mode. Application 1 and 2 are not necessarily started +on the same computer, but they need to have access to the database files. Application 1 +and 2 are typically two different processes (however they could run within the same process). +

+
+// Application 1:
+DriverManager.getConnection("jdbc:h2:/data/test;AUTO_SERVER=TRUE");
+
+// Application 2:
+DriverManager.getConnection("jdbc:h2:/data/test;AUTO_SERVER=TRUE");
+
+

+When using this feature, by default the server uses any free TCP port. +The port can be set manually using AUTO_SERVER_PORT=9090. +

+ +

Page Size

+

+The page size for new databases is 4 KiB (4096 bytes), unless the page size is set +explicitly in the database URL using PAGE_SIZE= when +the database is created. The page size of existing databases can not be changed, +so this property needs to be set when the database is created. +The page size of encrypted databases must be a multiple of 4096 (4096, 8192, …). +

+ +

Using the Trace Options

+

+To find problems in an application, it is sometimes good to see what database operations +where executed. This database offers the following trace features: +

+
    +
  • Trace to System.out and/or to a file +
  • Support for trace levels OFF, ERROR, INFO, DEBUG +
  • The maximum size of the trace file can be set +
  • It is possible to generate Java source code from the trace file +
  • Trace can be enabled at runtime by manually creating a file +
+ +

Trace Options

+

+The simplest way to enable the trace option is setting it in the database URL. +There are two settings, one for System.out +(TRACE_LEVEL_SYSTEM_OUT) tracing, +and one for file tracing (TRACE_LEVEL_FILE). +The trace levels are +0 for OFF, +1 for ERROR (the default), +2 for INFO, and +3 for DEBUG. +A database URL with both levels set to DEBUG is: +

+
+jdbc:h2:~/test;TRACE_LEVEL_FILE=3;TRACE_LEVEL_SYSTEM_OUT=3
+
+

+The trace level can be changed at runtime by executing the SQL command +SET TRACE_LEVEL_SYSTEM_OUT level (for System.out tracing) +or SET TRACE_LEVEL_FILE level (for file tracing). +Example: +

+
+SET TRACE_LEVEL_SYSTEM_OUT 3
+
+ +

Setting the Maximum Size of the Trace File

+

+When using a high trace level, the trace file can get very big quickly. +The default size limit is 16 MB, if the trace file exceeds this limit, it is renamed to +.old and a new file is created. +If another such file exists, it is deleted. +To limit the size to a certain number of megabytes, use +SET TRACE_MAX_FILE_SIZE mb. +Example: +

+
+SET TRACE_MAX_FILE_SIZE 1
+
+ +

Java Code Generation

+

+When setting the trace level to INFO or DEBUG, +Java source code is generated as well. This simplifies reproducing problems. The trace file looks like this: +

+
+...
+12-20 20:58:09 jdbc[0]:
+/**/dbMeta3.getURL();
+12-20 20:58:09 jdbc[0]:
+/**/dbMeta3.getTables(null, "", null, new String[]{"BASE TABLE", "VIEW"});
+...
+
+

+To filter the Java source code, use the ConvertTraceFile tool as follows: +

+
+java -cp h2*.jar org.h2.tools.ConvertTraceFile
+    -traceFile "~/test.trace.db" -javaClass "Test"
+
+

+The generated file Test.java will contain the Java source code. +The generated source code may be too large to compile (the size of a Java method is limited). +If this is the case, the source code needs to be split in multiple methods. +The password is not listed in the trace file and therefore not included in the source code. +

+ +

Using Other Logging APIs

+

+By default, this database uses its own native 'trace' facility. This facility is called 'trace' and not +'log' within this database to avoid confusion with the transaction log. Trace messages can be +written to both file and System.out. +In most cases, this is sufficient, however sometimes it is better to use the same +facility as the application, for example Log4j. To do that, this database support SLF4J. +

+

+SLF4J is a simple facade for various logging APIs +and allows to plug in the desired implementation at deployment time. +SLF4J supports implementations such as Logback, Log4j, Jakarta Commons Logging (JCL), +Java logging, x4juli, and Simple Log. +

+

+To enable SLF4J, set the file trace level to 4 in the database URL: +

+
+jdbc:h2:~/test;TRACE_LEVEL_FILE=4
+
+

+Changing the log mechanism is not possible after the database is open, that means +executing the SQL statement SET TRACE_LEVEL_FILE 4 +when the database is already open will not have the desired effect. +To use SLF4J, all required jar files need to be in the classpath. +The logger name is h2database. +If it does not work, check the file <database>.trace.db for error messages. +

+ +

Read Only Databases

+

+If the database files are read-only, then the database is read-only as well. +It is not possible to create new tables, add or modify data in this database. +Only SELECT and CALL statements are allowed. +To create a read-only database, close the database. +Then, make the database file read-only. +When you open the database now, it is read-only. +There are two ways an application can find out whether database is read-only: +by calling Connection.isReadOnly() +or by executing the SQL statement CALL READONLY(). +

+

+Using the Custom Access Mode r +the database can also be opened in read-only mode, even if the database file is not read only. +

+ +

Read Only Databases in Zip or Jar File

+

+To create a read-only database in a zip file, first create a regular persistent database, and then create a backup. +The database must not have pending changes, that means you need to close all connections to the database first. +To speed up opening the read-only database and running queries, the database should be closed using SHUTDOWN DEFRAG. +If you are using a database named test, an easy way to create a zip file is using the +Backup tool. You can start the tool from the command line, or from within the +H2 Console (Tools - Backup). Please note that the database must be closed when the backup +is created. Therefore, the SQL statement BACKUP TO can not be used. +

+

+When the zip file is created, you can open the database in the zip file using the following database URL: +

+
+jdbc:h2:zip:~/data.zip!/test
+
+

+Databases in zip files are read-only. The performance for some queries will be slower than when using +a regular database, because random access in zip files is not supported (only streaming). How much this +affects the performance depends on the queries and the data. The database +is not read in memory; therefore large databases are supported as well. The same indexes are used as when using +a regular database. +

+

+If the database is larger than a few megabytes, performance is much better if the database file is split into multiple smaller files, +because random access in compressed files is not possible. +See also the sample application ReadOnlyDatabaseInZip. +

+ +

Opening a Corrupted Database

+

+If a database cannot be opened because the boot info (the SQL script that is run at startup) +is corrupted, then the database can be opened by specifying a database event listener. +The exceptions are logged, but opening the database will continue. +

+ +

Generated Columns (Computed Columns) / Function Based Index

+

+Each column is either a base column or a generated column. +A generated column is a column whose value is calculated before storing and cannot be assigned directly. +The formula is evaluated when the row is inserted, and re-evaluated every time the row is updated. +One use case is to automatically update the last-modification time: +

+
+CREATE TABLE TEST(
+    ID INT,
+    NAME VARCHAR,
+    LAST_MOD TIMESTAMP WITH TIME ZONE
+        GENERATED ALWAYS AS CURRENT_TIMESTAMP
+);
+
+

+Function indexes are not directly supported by this database, but they can be emulated +by using generated columns. For example, if an index on the upper-case version of +a column is required, create a generated column with the upper-case version of the original column, +and create an index for this column: +

+
+CREATE TABLE ADDRESS(
+    ID INT PRIMARY KEY,
+    NAME VARCHAR,
+    UPPER_NAME VARCHAR GENERATED ALWAYS AS UPPER(NAME)
+);
+CREATE INDEX IDX_U_NAME ON ADDRESS(UPPER_NAME);
+
+

+When inserting data, it is not required (and not allowed) to specify a value for the upper-case +version of the column, because the value is generated. But you can use the +column when querying the table: +

+
+INSERT INTO ADDRESS(ID, NAME) VALUES(1, 'Miller');
+SELECT * FROM ADDRESS WHERE UPPER_NAME='MILLER';
+
+ +

Multi-Dimensional Indexes

+

+A tool is provided to execute efficient multi-dimension (spatial) range queries. +This database does not support a specialized spatial index (R-Tree or similar). +Instead, the B-Tree index is used. For each record, the multi-dimensional key +is converted (mapped) to a single dimensional (scalar) value. +This value specifies the location on a space-filling curve. +

+Currently, Z-order (also called N-order or Morton-order) is used; +Hilbert curve could also be used, but the implementation is more complex. +The algorithm to convert the multi-dimensional value is called bit-interleaving. +The scalar value is indexed using a B-Tree index (usually using a generated column). +

+The method can result in a drastic performance improvement +over just using an index on the first column. Depending on the +data and number of dimensions, the improvement is usually higher than factor 5. +The tool generates a SQL query from a specified multi-dimensional range. +The method used is not database dependent, and the tool can easily be ported to other databases. +For an example how to use the tool, please have a look at the sample code provided +in TestMultiDimension.java. +

+ +

User-Defined Functions and Stored Procedures

+

+In addition to the built-in functions, this database supports user-defined Java functions. +In this database, Java functions can be used as stored procedures as well. +A function must be declared (registered) before it can be used. +A function can be defined using source code, or as a reference to +a compiled class that is available in the classpath. By default, the +function aliases are stored in the current schema. +

+ +

Referencing a Compiled Method

+

+When referencing a method, the class must already be compiled and +included in the classpath where the database is running. +Only static Java methods are supported; both the class and the method must be public. +Example Java class: +

+
+package acme;
+import java.math.*;
+public class Function {
+    public static boolean isPrime(int value) {
+        return new BigInteger(String.valueOf(value)).isProbablePrime(100);
+    }
+}
+
+

+The Java function must be registered in the database by calling CREATE ALIAS ... FOR: +

+
+CREATE ALIAS IS_PRIME FOR "acme.Function.isPrime";
+
+

+For a complete sample application, see src/test/org/h2/samples/Function.java. +

+ +

Declaring Functions as Source Code

+

+When defining a function alias with source code, the database tries to compile +the source code using the Java compiler (the class javax.tool.ToolProvider.getSystemJavaCompiler()) +if it is in the classpath. If not, javac is run as a separate process. +Only the source code is stored in the database; the class is compiled each time the database is re-opened. +Source code can be passed as dollar quoted text ($$source code$$) to avoid escaping problems. +If you use some third-party script processing tool, use standard single quotes instead and don't forget to repeat +each single quotation mark twice within the source code. +Example: +

+
+CREATE ALIAS NEXT_PRIME AS '
+String nextPrime(String value) {
+    return new BigInteger(value).nextProbablePrime().toString();
+}
+';
+
+

+By default, the three packages java.util, java.math, java.sql are imported. +The method name (nextPrime in the example above) is ignored. +Method overloading is not supported when declaring functions as source code, that means only one method may be declared for an alias. +If different import statements are required, they must be declared at the beginning +and separated with the tag @CODE: +

+
+CREATE ALIAS IP_ADDRESS AS '
+import java.net.*;
+@CODE
+String ipAddress(String host) throws Exception {
+    return InetAddress.getByName(host).getHostAddress();
+}
+';
+
+

+The following template is used to create a complete Java class: +

+
+package org.h2.dynamic;
+< import statements before the tag @CODE; if not set:
+import java.util.*;
+import java.math.*;
+import java.sql.*;
+>
+public class <aliasName> {
+    public static <sourceCode>
+}
+
+ +

Method Overloading

+

+Multiple methods may be bound to a SQL function if the class is already compiled and included in the classpath. +Each Java method must have a different number of arguments. +Method overloading is not supported when declaring functions as source code. +

+ +

Function Data Type Mapping

+

+Functions that accept non-nullable parameters such as int +will not be called if one of those parameters is NULL. +Instead, the result of the function is NULL. +If the function should be called if a parameter is NULL, you need +to use java.lang.Integer instead. +

+

+SQL types are mapped to Java classes and vice-versa as in the JDBC API. For details, see Data Types. +There are a few special cases: java.lang.Object is mapped to +OTHER (a serialized object). Therefore, +java.lang.Object can not be used +to match all SQL types (matching all SQL types is not supported). The second special case is Object[]: +arrays of any class are mapped to ARRAY. +Objects of type org.h2.value.Value (the internal value class) are passed through without conversion. +

+ +

Functions That Require a Connection

+

+If the first parameter of a Java function is a java.sql.Connection, then the connection +to database is provided. This connection does not need to be closed before returning. +When calling the method from within the SQL statement, this connection parameter +does not need to be (can not be) specified. +

+ +

Functions Throwing an Exception

+

+If a function throws an exception, then the current statement is rolled back +and the exception is thrown to the application. +SQLException are directly re-thrown to the calling application; +all other exceptions are first converted to a SQLException. +

+ +

Functions Returning a Result Set

+

+Functions may returns a result set. Such a function can be called with the CALL statement: +

+
+public static ResultSet query(Connection conn, String sql) throws SQLException {
+    return conn.createStatement().executeQuery(sql);
+}
+
+CREATE ALIAS QUERY FOR "org.h2.samples.Function.query";
+CALL QUERY('SELECT * FROM TEST');
+
+ +

Using SimpleResultSet

+

+A function can create a result set using the SimpleResultSet tool: +

+
+import org.h2.tools.SimpleResultSet;
+...
+public static ResultSet simpleResultSet() throws SQLException {
+    SimpleResultSet rs = new SimpleResultSet();
+    rs.addColumn("ID", Types.INTEGER, 10, 0);
+    rs.addColumn("NAME", Types.VARCHAR, 255, 0);
+    rs.addRow(0, "Hello");
+    rs.addRow(1, "World");
+    return rs;
+}
+
+CREATE ALIAS SIMPLE FOR "org.h2.samples.Function.simpleResultSet";
+CALL SIMPLE();
+
+ +

Using a Function as a Table

+

+A function that returns a result set can be used like a table. +However, in this case the function is called at least twice: +first while parsing the statement to collect the column names +(with parameters set to null where not known at compile time). +And then, while executing the statement to get the data (maybe multiple times if this is a join). +If the function is called just to get the column list, the URL of the connection passed to the function is +jdbc:columnlist:connection. Otherwise, the URL of the connection is +jdbc:default:connection. +

+
+public static ResultSet getMatrix(Connection conn, Integer size)
+        throws SQLException {
+    SimpleResultSet rs = new SimpleResultSet();
+    rs.addColumn("X", Types.INTEGER, 10, 0);
+    rs.addColumn("Y", Types.INTEGER, 10, 0);
+    String url = conn.getMetaData().getURL();
+    if (url.equals("jdbc:columnlist:connection")) {
+        return rs;
+    }
+    for (int s = size.intValue(), x = 0; x < s; x++) {
+        for (int y = 0; y < s; y++) {
+            rs.addRow(x, y);
+        }
+    }
+    return rs;
+}
+
+CREATE ALIAS MATRIX FOR "org.h2.samples.Function.getMatrix";
+SELECT * FROM MATRIX(4) ORDER BY X, Y;
+
+ + +

Pluggable or User-Defined Tables

+

+For situations where you need to expose other data-sources to the SQL engine as a table, +there are "pluggable tables". +For some examples, have a look at the code in org.h2.test.db.TestTableEngines. +

+

+In order to create your own TableEngine, you need to implement the org.h2.api.TableEngine interface e.g. +something like this: +

+
+package acme;
+public static class MyTableEngine implements org.h2.api.TableEngine {
+
+    private static class MyTable extends org.h2.table.TableBase {
+        .. rather a lot of code here...
+    }
+
+    public EndlessTable createTable(CreateTableData data) {
+        return new EndlessTable(data);
+    }
+}
+
+

+and then create the table from SQL like this: +

+
+CREATE TABLE TEST(ID INT, NAME VARCHAR)
+    ENGINE "acme.MyTableEngine";
+
+

+It is also possible to pass in parameters to the table engine, like so: +

+
+CREATE TABLE TEST(ID INT, NAME VARCHAR) ENGINE "acme.MyTableEngine" WITH "param1", "param2";
+
+

+In which case the parameters are passed down in the tableEngineParams field of the CreateTableData object. +

+

+It is also possible to specify default table engine params on schema creation: +

+
+CREATE SCHEMA TEST_SCHEMA WITH "param1", "param2";
+
+

+Params from the schema are used when CREATE TABLE issued on this schema does not have its own engine params specified. +

+ +

Triggers

+

+This database supports Java triggers that are called before or after a row is updated, inserted or deleted. +Triggers can be used for complex consistency checks, or to update related data in the database. +It is also possible to use triggers to simulate materialized views. +For a complete sample application, see src/test/org/h2/samples/TriggerSample.java. +A Java trigger must implement the interface org.h2.api.Trigger. The trigger class must be available +in the classpath of the database engine (when using the server mode, it must be in the classpath +of the server). +

+
+import org.h2.api.Trigger;
+...
+public class TriggerSample implements Trigger {
+
+    public void init(Connection conn, String schemaName, String triggerName,
+            String tableName, boolean before, int type) {
+        // initialize the trigger object is necessary
+    }
+
+    public void fire(Connection conn,
+            Object[] oldRow, Object[] newRow)
+            throws SQLException {
+        // the trigger is fired
+    }
+
+    public void close() {
+        // the database is closed
+    }
+
+    public void remove() {
+        // the trigger was dropped
+    }
+
+}
+
+

+The connection can be used to query or update data in other tables. +The trigger then needs to be defined in the database: +

+
+CREATE TRIGGER INV_INS AFTER INSERT ON INVOICE
+    FOR EACH ROW CALL "org.h2.samples.TriggerSample"
+
+

+The trigger can be used to veto a change by throwing a SQLException. +

+

+As an alternative to implementing the Trigger interface, +an application can extend the abstract class org.h2.tools.TriggerAdapter. +This will allows to use the ResultSet interface within trigger implementations. +In this case, only the fire method needs to be implemented: +

+
+import org.h2.tools.TriggerAdapter;
+...
+public class TriggerSample extends TriggerAdapter {
+
+    public void fire(Connection conn, ResultSet oldRow, ResultSet newRow)
+            throws SQLException {
+        // the trigger is fired
+    }
+
+}
+
+ +

Compacting a Database

+

+Empty space in the database file re-used automatically. When closing the database, +the database is automatically compacted for up to 200 milliseconds by default. To compact more, +use the SQL statement SHUTDOWN COMPACT. However re-creating the database may further +reduce the database size because this will re-build the indexes. +Here is a sample function to do this: +

+
+public static void compact(String dir, String dbName,
+        String user, String password) throws Exception {
+    String url = "jdbc:h2:" + dir + "/" + dbName;
+    String file = "data/test.sql";
+    Script.execute(url, user, password, file);
+    DeleteDbFiles.execute(dir, dbName, true);
+    RunScript.execute(url, user, password, file, null, false);
+}
+
+

+See also the sample application org.h2.samples.Compact. +The commands SCRIPT / RUNSCRIPT can be used as well to create a backup +of a database and re-build the database from the script. +

+ +

Cache Settings

+

+The database keeps most frequently used data in the main memory. +The amount of memory used for caching can be changed using the setting +CACHE_SIZE. This setting can be set in the database connection URL +(jdbc:h2:~/test;CACHE_SIZE=131072), or it can be changed at runtime using +SET CACHE_SIZE size. +The size of the cache, as represented by CACHE_SIZE is measured in KB, with each KB being 1024 bytes. +This setting has no effect for in-memory databases. +For persistent databases, the setting is stored in the database and re-used when the database is opened +the next time. However, when opening an existing database, the cache size is set to at most +half the amount of memory available for the virtual machine (Runtime.getRuntime().maxMemory()), +even if the cache size setting stored in the database is larger; however the setting stored in the database +is kept. Setting the cache size in the database URL or explicitly using SET CACHE_SIZE +overrides this value (even if larger than the physical memory). +To get the current used maximum cache size, use the query +SELECT * FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME = 'info.CACHE_MAX_SIZE' +

+An experimental scan-resistant cache algorithm "Two Queue" (2Q) is available. +To enable it, append ;CACHE_TYPE=TQ to the database URL. +The cache might not actually improve performance. +If you plan to use it, please run your own test cases first. +

+Also included is an experimental second level soft reference cache. +Rows in this cache are only garbage collected on low memory. +By default the second level cache is disabled. +To enable it, use the prefix SOFT_. +Example: jdbc:h2:~/test;CACHE_TYPE=SOFT_LRU. +The cache might not actually improve performance. +If you plan to use it, please run your own test cases first. +

+To get information about page reads and writes, and the current caching algorithm in use, +call SELECT * FROM INFORMATION_SCHEMA.SETTINGS. The number of pages read / written +is listed. +

+

External authentication (Experimental)

+

+External authentication allows to optionally validate user credentials externally (JAAS,LDAP,custom classes). +Is also possible to temporary assign roles to externally authenticated users. This feature is experimental and subject to change +

+

Master user cannot be externally authenticated

+

+To enable external authentication on a database execute statement SET AUTHENTICATOR TRUE. This setting in persisted on the database. +

+

+To connect on a database by using external credentials client must append AUTHREALM=H2 to the database URL. H2 +is the identifier of the authentication realm (see later). +

+

External authentication requires to send password to the server. For this reason is works only on local connection or remote over ssl

+

+By default external authentication is performed through JAAS login interface (configuration name is h2). +To configure JAAS add argument -Djava.security.auth.login.config=jaas.conf +Here an example of +JAAS login configuration file content: +

+
+h2 {
+    com.sun.security.auth.module.LdapLoginModule REQUIRED \
+    userProvider="ldap://127.0.0.1:10389" authIdentity="uid={USERNAME},ou=people,dc=example,dc=com" \
+    debug=true useSSL=false ;
+};
+
+

+Is it possible to specify custom authentication settings by using +JVM argument -Dh2auth.configurationFile={urlOfH2Auth.xml}. Here an example of h2auth.xml file content: +

+
+<h2Auth allowUserRegistration="false" createMissingRoles="true">
+
+    <!-- realm: DUMMY authenticate users named DUMMY[0-9] with a static password -->
+    <realm name="DUMMY"
+    validatorClass="org.h2.security.auth.impl.FixedPasswordCredentialsValidator">
+        <property name="userNamePattern" value="DUMMY[0-9]" />
+        <property name="password" value="mock" />
+    </realm>
+
+    <!-- realm LDAPEXAMPLE:perform credentials validation on LDAP -->
+    <realm name="LDAPEXAMPLE"
+    validatorClass="org.h2.security.auth.impl.LdapCredentialsValidator">
+        <property name="bindDnPattern" value="uid=%u,ou=people,dc=example,dc=com" />
+        <property name="host" value="127.0.0.1" />
+        <property name="port" value="10389" />
+        <property name="secure" value="false" />
+    </realm>
+
+    <!-- realm JAAS: perform credentials validation by using JAAS api -->
+    <realm name="JAAS"
+    validatorClass="org.h2.security.auth.impl.JaasCredentialsValidator">
+        <property name="appName" value="H2" />
+    </realm>
+
+    <!--Assign to each user role @{REALM} -->
+    <userToRolesMapper class="org.h2.security.auth.impl.AssignRealmNameRole"/>
+
+    <!--Assign to each user role REMOTEUSER -->
+    <userToRolesMapper class="org.h2.security.auth.impl.StaticRolesMapper">
+        <property name="roles" value="REMOTEUSER"/>
+    </userToRolesMapper>
+</h2Auth>
+
+

+Custom credentials validators must implement the interface +org.h2.api.CredentialsValidator +

+

+Custom criteria for role assignments must implement the interface +org.h2.api.UserToRoleMapper +

+ +
+ diff --git a/h2/src/docsrc/html/fragments.html b/h2/src/docsrc/html/fragments.html new file mode 100644 index 0000000..b35432e --- /dev/null +++ b/h2/src/docsrc/html/fragments.html @@ -0,0 +1,133 @@ + + + + + Fragments + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/h2/src/docsrc/html/frame.html b/h2/src/docsrc/html/frame.html new file mode 100644 index 0000000..42c7d49 --- /dev/null +++ b/h2/src/docsrc/html/frame.html @@ -0,0 +1,40 @@ + + + + + H2 Database Engine + + + + + +

H2 Database Engine

+

+Welcome to H2, the free SQL database. The main feature of H2 are: +

+
    +
  • It is free to use for everybody, source code is included +
  • Written in Java, but also available as native executable +
  • JDBC and (partial) ODBC API +
  • Embedded and client/server modes +
  • Clustering is supported +
  • A web client is included +
+ +

No Javascript

+

+If you are not automatically redirected to the main page, then +Javascript is currently disabled or your browser does not support Javascript. +Some features (for example the integrated search) require Javascript. +

+Please enable Javascript, or go ahead without it: +H2 Database Engine +

+ + \ No newline at end of file diff --git a/h2/src/docsrc/html/functions-aggregate.html b/h2/src/docsrc/html/functions-aggregate.html new file mode 100644 index 0000000..dd40bca --- /dev/null +++ b/h2/src/docsrc/html/functions-aggregate.html @@ -0,0 +1,326 @@ + + + + + + +Aggregate Functions + + + + + +
+ + +

Aggregate Functions

+

Index

+

General Aggregate Functions

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

Binary Set Functions

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

Ordered Aggregate Functions

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

Hypothetical Set Functions

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

Inverse Distribution Functions

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

JSON Aggregate Functions

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

Details

+ +

Click on the header of the function to switch between railroad diagram and BNF.

+ +

Non-standard syntax is marked in green. Compatibility-only non-standard syntax is marked in red, +don't use it unless you need it for compatibility with other databases or old versions of H2.

+ +

General Aggregate Functions

+ +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

${item.example}

+
+ +

Binary Set Functions

+ +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

${item.example}

+
+ +

Ordered Aggregate Functions

+ +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

${item.example}

+
+ +

Hypothetical Set Functions

+ +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

${item.example}

+
+ +

Inverse Distribution Functions

+ +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

${item.example}

+
+ +

JSON Aggregate Functions

+ +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

${item.example}

+
+ + + +
+ diff --git a/h2/src/docsrc/html/functions-window.html b/h2/src/docsrc/html/functions-window.html new file mode 100644 index 0000000..f7ad4e5 --- /dev/null +++ b/h2/src/docsrc/html/functions-window.html @@ -0,0 +1,277 @@ + + + + + + +Window Functions + + + + + +
+ + +

Window Functions

+

Index

+

Row Number Function

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

Rank Functions

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

Lead or Lag Functions

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

Nth Value Functions

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

Other Window Functions

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

Details

+ +

Click on the header of the function to switch between railroad diagram and BNF.

+ +

Non-standard syntax is marked in green. Compatibility-only non-standard syntax is marked in red, +don't use it unless you need it for compatibility with other databases or old versions of H2.

+ +

Row Number Function

+ +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

${item.example}

+
+ +

Rank Functions

+ +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

${item.example}

+
+ +

Lead or Lag Functions

+ +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

${item.example}

+
+ +

Nth Value Functions

+ +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

${item.example}

+
+ +

Other Window Functions

+ +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

${item.example}

+
+ + + +
+ diff --git a/h2/src/docsrc/html/functions.html b/h2/src/docsrc/html/functions.html new file mode 100644 index 0000000..d62066f --- /dev/null +++ b/h2/src/docsrc/html/functions.html @@ -0,0 +1,326 @@ + + + + + + +Functions + + + + + +
+ + +

Functions

+

Index

+

Numeric Functions

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

String Functions

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

Time and Date Functions

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

System Functions

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

JSON Functions

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

Table Functions

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

Details

+ +

Click on the header of the function to switch between railroad diagram and BNF.

+ +

Non-standard syntax is marked in green. Compatibility-only non-standard syntax is marked in red, +don't use it unless you need it for compatibility with other databases or old versions of H2.

+ +

Numeric Functions

+ +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

${item.example}

+
+ +

String Functions

+ +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

${item.example}

+
+ +

Time and Date Functions

+ +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

${item.example}

+
+ +

System Functions

+ +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

${item.example}

+
+ +

JSON Functions

+ +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

${item.example}

+
+ +

Table Functions

+ +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

${item.example}

+
+ + + +
+ diff --git a/h2/src/docsrc/html/grammar.html b/h2/src/docsrc/html/grammar.html new file mode 100644 index 0000000..e4f4b98 --- /dev/null +++ b/h2/src/docsrc/html/grammar.html @@ -0,0 +1,180 @@ + + + + + + +SQL Grammar + + + + + +
+ + +

SQL Grammar

+

Index

+

Literals

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

Datetime fields

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

Other Grammar

+ + + + + + +
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + ${item.topic}
+
+
+ + +

Details

+ +

Click on the header of the grammar element to switch between railroad diagram and BNF.

+ +

Non-standard syntax is marked in green. Compatibility-only non-standard syntax is marked in red, +don't use it unless you need it for compatibility with other databases or old versions of H2.

+ +

Literals

+ +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

+${item.example}

+
+ +

Datetime fields

+ +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

+${item.example}

+
+ +

Other Grammar

+ +

${item.topic}

+ +
+${item.syntax}
+
+
+${item.railroad} +
+ + +

${item.text}

+

Example:

+

${item.example}

+
+ + + +
diff --git a/h2/src/docsrc/html/history.html b/h2/src/docsrc/html/history.html new file mode 100644 index 0000000..b5068a5 --- /dev/null +++ b/h2/src/docsrc/html/history.html @@ -0,0 +1,170 @@ + + + + + + +History + + + + + +
+ + +

History

+ + Change Log
+ + History of this Database Engine
+ + Why Java
+ + Supporters
+ +

Change Log

+

+The up-to-date change log is available +here +

+ +

History of this Database Engine

+

+The development of H2 was started in May 2004, +but it was first published on December 14th 2005. +The original author of H2, Thomas Mueller, is also the original developer of Hypersonic SQL. +In 2001, he joined PointBase Inc. where he wrote PointBase Micro, a commercial Java SQL database. +At that point, he had to discontinue Hypersonic SQL. The HSQLDB Group was formed +to continued to work on the Hypersonic SQL codebase. +The name H2 stands for Hypersonic 2, however H2 does not share code with +Hypersonic SQL or HSQLDB. H2 is built from scratch. +

+ +

Why Java

+

+The main reasons to use a Java database are: +

+
    +
  • Very simple to integrate in Java applications +
  • Support for many different platforms +
  • More secure than native applications (no buffer overflows) +
  • User defined functions (or triggers) run very fast +
  • Unicode support +
+

+Some think Java is too slow for low level operations, +but this is no longer true. Garbage collection for example is +now faster than manual memory management. +

+Developing Java code is faster than developing C or C++ code. When using Java, +most time can be spent on improving the algorithms instead of +porting the code to different platforms or doing memory management. +Features such as Unicode and network libraries are already built-in. +In Java, writing secure code is easier because buffer overflows can not occur. +Features such as reflection can be used for randomized testing. +

+Java is future proof: a lot of companies support Java. Java is now open source. +

+To increase the portability and ease of use, this software depends on +very few libraries. Features that are not available in open source +Java implementations (such as Swing) are not used, or only used for optional features. +

+ +

Supporters

+

+Many thanks for those who reported bugs, gave valuable feedback, +spread the word, and translated this project. +

+

+Also many thanks to the donors. +To become a donor, use PayPal (at the very bottom of the main web page). +Donators are: +

+
    +
  • Martin Wildam, Austria +
  • tagtraum industries incorporated, USA +
  • TimeWriter, Netherlands +
  • Cognitect, USA +
  • Code 42 Software, Inc., Minneapolis +
  • Code Lutin, France +
  • NetSuxxess GmbH, Germany +
  • Poker Copilot, Steve McLeod, Germany +
  • SkyCash, Poland +
  • Lumber-mill, Inc., Japan +
  • StockMarketEye, USA +
  • Eckenfelder GmbH & Co.KG, Germany +
  • Jun Iyama, Japan +
  • Steven Branda, USA +
  • Anthony Goubard, Netherlands +
  • Richard Hickey, USA +
  • Alessio Jacopo D'Adamo, Italy +
  • Ashwin Jayaprakash, USA +
  • Donald Bleyl, USA +
  • Frank Berger, Germany +
  • Florent Ramiere, France +
  • Antonio Casqueiro, Portugal +
  • Oliver Computing LLC, USA +
  • Harpal Grover Consulting Inc., USA +
  • Elisabetta Berlini, Italy +
  • William Gilbert, USA +
  • Antonio Dieguez Rojas, Chile +
  • Ontology Works, USA +
  • Pete Haidinyak, USA +
  • William Osmond, USA +
  • Joachim Ansorg, Germany +
  • Oliver Soerensen, Germany +
  • Christos Vasilakis, Greece +
  • Fyodor Kupolov, Denmark +
  • Jakob Jenkov, Denmark +
  • Stéphane Chartrand, Switzerland +
  • Glenn Kidd, USA +
  • Gustav Trede, Sweden +
  • Joonas Pulakka, Finland +
  • Bjorn Darri Sigurdsson, Iceland +
  • Gray Watson, USA +
  • Erik Dick, Germany +
  • Pengxiang Shao, China +
  • Bilingual Marketing Group, USA +
  • Philippe Marschall, Switzerland +
  • Knut Staring, Norway +
  • Theis Borg, Denmark +
  • Mark De Mendonca Duske, USA +
  • Joel A. Garringer, USA +
  • Olivier Chafik, France +
  • Rene Schwietzke, Germany +
  • Jalpesh Patadia, USA +
  • Takanori Kawashima, Japan +
  • Terrence JC Huang, China +
  • JiaDong Huang, Australia +
  • Laurent van Roy, Belgium +
  • Qian Chen, China +
  • Clinton Hyde, USA +
  • Kritchai Phromros, Thailand +
  • Alan Thompson, USA +
  • Ladislav Jech, Czech Republic +
  • Dimitrijs Fedotovs, Latvia +
  • Richard Manley-Reeve, United Kingdom +
  • Daniel Cyr, ThirdHalf.com, LLC, USA +
  • Peter Jünger, Germany +
  • Dan Keegan, USA +
  • Rafel Israels, Germany +
  • Fabien Todescato, France +
  • Cristan Meijer, Netherlands +
  • Adam McMahon, USA +
  • Fábio Gomes Lisboa Gomes, Brasil +
  • Lyderic Landry, England +
  • Mederp, Morocco +
  • Joaquim Golay, Switzerland +
  • Clemens Quoss, Germany +
  • Kervin Pierre, USA +
  • Jake Bellotti, Australia +
  • Arun Chittanoor, USA +
+ +
+ diff --git a/h2/src/docsrc/html/images/connection-mode-embedded-2.png b/h2/src/docsrc/html/images/connection-mode-embedded-2.png new file mode 100644 index 0000000000000000000000000000000000000000..7da33a3ad9345a54629cbc2129a2034a70dfc636 GIT binary patch literal 48466 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 Sa-bs(^@B3DM6?;+O!_~}tLW1J literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/html/images/connection-mode-embedded.png b/h2/src/docsrc/html/images/connection-mode-embedded.png new file mode 100644 index 0000000000000000000000000000000000000000..ffbccfcb5564e0721fc3a01dfd167bc3829eacb3 GIT binary patch literal 20407 zcmXtA19V(nv`&M@Xq?8jZF^!iY#Q5EV>C{qi8V1A+jcWC8{77q|Go9H*2!IyJNM}B z^KE@QQL4(as7POtprD{o<>jQ*A=ep5yF>U4`At6Pdj+|`x=P4vB0zrp5X>SWpAntp zzPUoazxZGKBte(y1^FX^o3yT*hNFd>r-_R>l&7aBi?xHTtEq{TIg6ued~j3FFqS6LluQ3{TWk_j8*=S83Qv3Ljk&@EJ1<| zkvyL&0k2;Xk@~wH8+#~b&-b3sC_`d)cDeRzPOJxln`br~qD6n>8j`fzE7n&zUtav& zl4J`1o>~Pw3r}-WW?KM3dBI2wBgj#4)nafsq2iI(0bM!>4atRTg5ND=!^FO+9{t1# z%|C=Kck4mgmf^oElj7F`1<&TWOz%hzhK|Fepy>ulMPQW9eqxLao|;i%pjlIn8S7*g zVW2TUU?8pywxeSR`tM42IPq5Wqwfs1bge%Q@@V0IPkinUz(oGP+e-v5@`Ir#Ql)DZ z*Zp{7p%st;3&_#}lCb{o{v*X*ax|h&c%u|4$KnGcKIBp0* zwRxP4jU-b3^6BKZU8wcBvw@O{!54oxUx&GI*yzXrfoPyUqY=q<2fQJ*HJwu0E>xOK z0=c13NJWGi&;%(xe6Y^jPO5jnDx#=DBG$81ENzDcy48*Lt2}5TkKdta0O>u*tQ{EX zym21gZ}+RMS3`uKytbog`R>eJgvvU^@#LRTT_e zC%M;rPO7T+COD_v;z&J~OaMTxD93=C%#PbxML>>FhEb=FbI&LE%Q+2|PnZ8f&^es9 zrF?D=dxPLfMkX>iVQL;mDC3<*$RlRzEmiK41CAa1?@Vfq*sVVgB;DVnsZt@`?&;TU*z? zin213*T<{>mRx|MRWMNCNppw%{VY=SGLO+r>t<8OP{A6qfhL+XGDC0hWqTZ{XWv;3uc8FuZHX z_I=)e8)OaGdK8Nnd8Rwa4eW9xe!e~G@9@5{va%veQDBE`??E~#NGWqDt^0MaXJ=4K zmOl5~|7*%BCT+l7g|YM1AYOaw;4eJO-HPh!{pRjBW-v&}bhbzy>U8N(NU>s?%R#p1 z^^7F?-=!slo|Q&hf5G`Y@enV_o_XC&@)|ODuG%+$sttJaVd;K#0f5c=LJ^t1)rInI zc!daFbb#q~98|Ie=ER%@Iu@TjnDq4@JXDc7y^;T&7F3+@ zz3!CAi(SugY4e&3GE{|j)mTeZe>m#SWj{LnKlH#y7*M$=}jST2!Y(dVwQW+pr5`Z3tE?fnp`l+W+B^s;hXYLv&(No7cJ7{CL}hf{W-E6dVf|EB1;Ox*EVb?R?ls ztY!vx?0n_uv|X%L23iC27tdB28DnE(xwyIiuB?nLROx@K{T}4{TAXGy#)kUIGRE?6 zfwc09Ndz&j4>Px!sQ2~mOot7zQNWYAao|u5+VU7P%^0)XRakW2V3YxcKBQmsquFAp z+rz&h=j-j9PXELX9wCS@f}>-Q=xk(f&-{<(szCae!Pt(pq-03_+fhlb%Wk5q#crYv z^&9t3WR{knj5Qsz&1ez`ZSE)g42v~r#P-VWuwANZEuNeHX^^!}HZmEsu$6EGpE@`K z*NmGTh&4a}=QyQwwol}+@I+2fZ4TTYh4CU|5b3nCvokxK$wwg(O6PaozkJ+@rwsi* zSE{Dm3IQ$n|B&+K{;axf{G`mAG~m$$mq}NQA?3&W>k~n@bXC7;yzmgD6MO+TI>;+D zg&d)dm-BW&CJ*Y*b&pl%LbR%?Di)j*n1=c(fnN||qNEg_sQeYY zXdIX={IHfhU!f!TrL(-8VR&)bzInanxV)leE1Ynt!<#dL#D}ia=k5ojdnVo5p9>4x z!{Xh$Ns28W?=N2@E)NUiuXmGGC_>3H02T%hF`QHXbh@8$AX2k^*1Tx}Ilv_#Q24^a zLQF@YOzbw$wt5R9*p}m|%rT^*IXS|agC%PyS#xQj8`(+-*hoHAn!gQ4|~}X?a1L&4aY~?Sz=u z1np3rPz!x1NEQ2XL)g025tCCx6^Fo!Is<&4nc*ov7yjk zg(Q2|`rAWO&+V@XtY6FsQ-E^B4t;xIeiz|`Uvr-X1(CRy=Wbl7HALR1FoOhEG{^Uz zaq8TNknCS-(Q@NQX`~!jvGzmI*tZG{IQ4V)cK>8|!gzS>x&+Rlp>-;D6EL81om4U$~>q3hV4=|yhEiX4`?wMDLjWKMg}_R z9;Ig!24t_s3mGMaexW~hzn1=sLuU-0n65#j)DdwnB_k?ko3As}m^`3X!KT2`j}q7o zERxY8W{q+|Wc&TYuWWA5{qq335tf#`nUN?(SfT=RyDFx;7dA@_+qFK73G66H!FWJ< zEpdc)HdRT!PI{W^OUD5HU}b1QhLhSHMGR~}fO0y8R^)F{g>{|)V!@Hq>!cCIguF)J zaC=pOY5|UP&^5UVwo7SIv;xS^JsJkbWk0+W7KtLaIo>jvpM9v=jmtxF)mYG$WBlU49+Im{Z0X_$qO?vM!3r z=<#fhTs&`_)wby*koE}}r*9@rZmLc4v@#2}`%Kg@MT$Pte91(_Go#tFX?yxIn29^c zJ2prf?NYg>TJ0Vykw<5dNfidbk}xYeYCBiQH(WGiD9Rz+MaNO7)WxPdo}92~gG-cU z5g&-QnSz@y4&Ejkxwu-rzwy*GPkc~Jl0l^#=@5TtPlz;V5=r6>>dQG+12mI`T{%=( zFC!EiF~&%V$EuTSZe8BccO11o3MNS_VIZ*S28rruF)d@h^qIQ z?wjDR*@i9Ni>`zn({`%f&ure=bOS4``CfEZf1v7#!+n2Symg%xPOAxgJ2`BNGr>;> zMVa8EMNw(~kRPXF(#_8~m^@HI4{5TP1O}6|o4}6kKR|TX%+um2j^@9Jl@&b_pF{FC z&kM+-_~BdWo&hf|`rI&`sozM@AO`+qi`BdIQ@75lW zWxNi?#oIRb1W2&qr~~s^B88x2D0&lywu;Nu;r5VJB2r=16XmGniq&(#Oqio??jFIR zf!!f^ULo7eS4fUHZwIap2LS`u)_6tFP_LC|LX5~{a z!-gzts%z6L4xlqB;3#4}RTk33FnTS1N@KutGif{KZK6%#isMD5#8;Cf*Eix!QYMvz zA5f2}SvRl^v*-Im_i=@F-L~r~jzH}yh+!mDfovoTno7P%B=2p4KZ9GMiL^ z18=Im2Iy?0>6eGpk+36{aK0=4>lpnh_LDbl32ttTjl_GiCs9oyBhivlhQUjt3 z*pz|L@q3YY#z5&HhD)*VKjyV!4i(9AouNtlKmpEAPLt|%j94uCTvN#1Y)yHXVHIRp zv!OSXQGTo=glw0-UsR-<#3VGam4bo+O{W74+OZ6Wm266!Awr{Ov*hu)=-5rABg1YQ zoA6s{e7Eo;Widf686Yo$$w>|dh7D!59{+-i{v<{=m)uzkc zod>9y&U`%DfW6bCMRFZEQaOeMoy56jjry>LxBEAvx)7@!N$RIct)*mgPDi0dgWDrn z1HbpbQ}6P|!tsi%A4)=DvOTZpn_HgUotQtz1G&Qf`s%^9S&zlb(X9S;&jwF3pam_| z827ISW<&SnnBE^eCQrG<*{`*1lr2UY%DDE)@2QZk?U|HNsg9Wn#7tv*;2Ck&nndR8XN=Wj~|8oab(&Neo)s`?0Pt!?n5DX1ybzU2R2? zVQ2X-FaP$8cgfxF?0`|X*#UL6$pPg1kaK$`8fnz|Oy9k3HXKKMw3(&=@ez*aT4ihA zU*A>0P&01Ag=nCRAA{G_l?KaFxm_^UL~V@o^W4=!HiDX3i+R>x8AbH_!tZSHTTk%Y z<~+w=P2j}1mpd%>^Y#~+CWm#{o)I-)XE3u0htmujltbqe?d#cw zD3sGVoX*v2jI3^>3q6#u@7>N&CaG!e8``I}Zpk-8W8nZUTW!`7UrE;YI~zUnkDE~g zhmW0jZ`*}#gF8aueZ@7OVbP<7PTGD-Vkg(;R_kqp1u00z$$he10E~Oym zV_^Bf6e8@pVJ=lI7-6j7+^$Xf3&FJa@ce$6Pi&A~vfF6rP0<<5b$mkP=60WYF9;)b zSy!2UnDo6^=bc?>>OayXS0KW;9nUu|&C{jJbcM7v{rNr95_B>qrd7uk!t4VQ&69ipus z3?E(r{NS8#JtT!yP`waI^g1$*7c?(fW%3tp^c-mreSeRzTd(`_(=p4L{=>_G!1I06 zR_JX_U0Glc8l~_nI8^__FQ+6|e{8Z@bUgQ+BSbbQUFBi}1}gny%T7;EkI!n2ea&Uq zb&8vY$b%wY<>U8oN#K~si;~l#Vp+dZcPGkEseJT=YOSAv-g<^8n zEeQ0X>zy*;f7apuf^NROMa$@oXZD-%vNcCN0$tP#xwa2flI;)2?QX~p=XSIbQW4-o zE9`SOj6KtSK}OQ?VNw11F8}%vRmk^V+6ytGR&01VrCo1$N&wxqzVV~?8?`3PwhK(~ z-<#JTOndUaEZm(C;`44iyVybA5;ESzxK#uUnm`hZq3=xKKel^f!Cv{z7H7ka2ev|$ z#{}9)K8P`x_nr>1))Ih<0th;{45SlYH1=Jcw|%A)0VmRAZz8XdUCqjf!ItP5PhCs-_IyL&pVr}krea4+eQ zMm}JFSl7vTIj^WbnM{BGc>z|YGoSnu1<&R-a0t*+yPnd%Moab%3pBz3K^2yVe}akl zlzMbCI(*R@0EW1(av;Dv_l`+~P}o{@A4!|n6JtnAc#t}AXa{I&y`iRw>HGA)9&fZ*T-uxaOkB{}d8a(TIAH+do z_t*dIN2Z+-_FKCX#Fr8snvRO`SwG12?^zc8C^*6AvkGOLM&=~h28X4N-b|gQEr0Y- z8uFg_;wic8sZfSS%9mVih?{1sGEbklGBI$z3 z#fl>PA+4NG-_HRLj+|TnB;!q=!JaTqZ;c06!-U<__h?PwS3d(j3o?l0 zJe=+IX*`V?ax?}}h(&-kes_W~xo%0x&9c)0r<@BEW}5-A%Fj8bXcjwphKG8a>+%&< zXJf|a0lzGBUX6_cwk|65T0<97EuPWw$6Bt2!BzRZ5z0bQwlyDu(<1)77t{VJK_N0u z$46=Pen)|iP8)Z8GQ=OiM!+|0>lC~!vJ`V(P@g#3E-2G*XmtTAZo+9G$ z`-Rb`Opl30&EGq|RetM?5%Gs-cbN+?aJph8Xgv{8-Vm3R5ip(u zPqWBUpy>V??0M$@6YrUx5ocw4@Yl_Ah6${C;7?DIWWVOZs;XE#W4}a5ceqjYsAcW zM1fMgE34podNko%RY;&-0us{X=H{MTSWws3SA;l^^)=PDHa4Z@SNSYqRY0Q)vDL=*cic8<=vdPntIr!5R(8&ljqpVNLjI?=Pi?zbGlQ!8*V zLBhs;R9|d`#1^x7CAL|(MB4coFr#eB%PU)r`sO5RYEBSoYd{3lH79sDHi3xIwPoJ( zjlAnMr*+SjDbckG@VOe5D~~&EggJ43Z?3G*+y_RtCvx75k?6;j!E~YYINo`oAEa~) z3@Ud>um2j|`i6X7r*24e3LV5hRngJ4A>wf@(jB(3qZ;YdCX1gb+5p>#n~a%$r$CSP z4k9tACdSg4JiWyY%2y`6{~E4jpc-Sm))4pZVRhNJY%wpY)N`vZ7`giO^fe?IXE%cFhIs@ji6+|w zpI}%Z3^5Iz0ZCIF!$mDi{b|-5C#>Fr;xr3s?n`fC9B_O=jn0A)KE;qN=o*y*R+!r3 z^ZRZ9CGyi>a;mE`Sxd4MW82cVp?0)7zk*xoJI3NCvefwTVK`f&(i?U2SLPZ$vP^JlvMq5#8@JMA?JOpgu=~xG#ml0zv)`P@jSkaRKEnR2uZ7Nhj_USag&> zQmG>`%r`#-_~*OLM#8uVyg3VE!@|sa!GBY3xWb5_6UD0m>gMkeJe#&|WCcLS4X(Ra zIu^Ly_e{lW^Vt!)eliKCm(T>6PVdM{?~bPei5rF})^9>j(AOiF>mxN9{)wvWTYd?g z)_w>(!uu4F2W1Dsyht6p&H}Bo%h^KJyo_NHsx}1YjHTS;Ey}dA3DA4dDA0SDwmOX4 zRhn~$Srq_*0J8ayhnLdG2>h-i0`E)VMSnK<&?IWk^)ywAbgMd8JgMQ%S!aSb0t2XoyXf7-U>(h3 zZSxc$#ZYIhV`RvIje`|;Ih84nGTnsHC;gSNTMlbj8lwiH4$|BQXxE6oI~GUf1)%gR zfw&S+8__dnUz#!&|4K!F8q_XEd#g-Q$*1Pt>eHl+;j^C2D^&n;^}P$^DX?&>$YhX6 z6rZGROq~|K+l1`XL^}dOz9VKD8m_rJ>7E22!=p)o?l=-^0UbMtbRsI0h%IXktC z*ETEB)*K}a0dgq;yj!8x$r9d&bbCH0Ut-ual=>AG3`3pHYz6X`KQtBO!D983{KJ;B zYrCi#g)Z6!Kk_RPV2cq=;S*{5ORmV{FhIYA&Kn=nUt094=>dN7z-lv#!A^0Qc-G`H zmNv#o@^BF2FTq?_A|a`3c2v%}(j7T$YNuk*i@oB=k+7PohcOK^PvA98yaeV$QxqE~ zSV<5S_DoQ3sl|jplgDppKbCR%k164qeN{tq+qLvnOl#Q*S zmZ7Y>J6;6rb(QGI*OlC z(p(31?yqrT%k&9!1&DacBzODi)|-P3N?dqqfY6M(St{oV>@qXL3hjx5`r?bLr(-y~ zjoM%(LQ&Jzg*)XZ=R8G!706fxzVV-K&)bn4KqrSRVe-k+i?I`2 zO0rl(dz9$|0*=vfw*1{?o9zV!dz83lyHIq)D&0=YnJ!_{er%ryC99RbYNzq@#Sj>-y zEk!p#ah;lp`~wnf*Ir=FtpG$@_U$R>0Jb6V2NPhWE>GH%Ywmus_w=7m?~sc-H$zDN zFWhB>JodENnAKymGj&KNR>a>!)o?x9S5Ad~(~;Q#&*bArIBlzYyH_ zr)i$^PTo-<_kyF1{c@k|g~~zULkYObH&EuBJ%jxvTguR1#mz84cby_GN1+&z8tewv zW&o>Em&qMj<&=vBC^Xz;x=>Rmn%4XvlcH* zWKVc>_~A+Te%sav2g@gaJzR0;OcNiI*q zNOpN%`I2!rW^AQ4(J(Yb@_MW@e3?>%QX(6#%{1HaX`{9R$jMoI-*yw|ICD{`to%%kpq=8?5A&;-sc<6)FFPBHCb?5`# zk9`;(#S&T^3e8xWr~e?EG;$TiWr8I=m7=Oy8mZRmBK9rAsQ_Ob_HLBgpUB)^dZwt` z^oehBb>S+$+jyu>+L7-d4*&13my3_j5hN>wO3B&HpGzrp=0})?H*WxKp1k~LTp?iP zlj$t}&z3o@;c&=$%YWNaJS`#lZl(GRr`wdd#RnR5O%r^2H_9Mu(2Rx_ez-Mbk{B;0 z0yugKk$i@0m3Q{+vKfVm&vp-YcO>`v&T9=r0y{!1j83hRCM!*7AzYa3IId~bIg^`F zbhiw?yFXQFA+U=?s3qv-LNt3cIbwBSRF?aid*_-S+#Lr`^!<92HUyH;wwV&JrB_UA zx_l~+&tN}e7$SA2_J4+i$BWovYygtq@u9)k_@58^jBa7uW-$7$J>3i?u_%ajoj|bP zP^;*>REe4lWM=3k29$(r!0Aq5WDBiMCdbL_ml;2<0*$saWV>YR*1yg{@}6%k7hN}U zRBqd9$p&-u0qQou(NIKsT;{L6(s=dU~k-nPqlZ}fABrPE$S|1WnZ7bo)*56)YIpIxr`6=%fwRV%W5Hh9K z*{F)eqCG$$m4Lca8jtdo4wOmk?0XQ~uG?jX3x#Jalmi(Pt)hPL={7zSAuMTjB}d4c zQuKAJ_x1h3@sINxrbx~UtncZA7G{$~<^tz1P5iz(WxhVS1G$(hg)4}YfIAT9n8_rR zgKKMBgUozSo%8`b%%vpabHNVvmC)B^)n3OWb`!gtt2n-GPrsYc;c!et<<5MouaU_S zI9SR~yOhsKJahKseq0c=M-XK3HjV)>sYoc6>C32^@wRLBlOo3qdPQQ9;^>D)lUB<^ zxnU;eTWYh0BV1&L{ulUu0E-zVG5{8Lr8ev`$E}VG|2s4t;DLXTb%W15d9vJb#0k;D zz3dQZPlUxh6q{8TzK-BhibEg$liz^EkInE?v5O`Uww`H>!q|AkvZMU8D@v6JPCW!3 z+C<^*iyzkmJMGOtlrgkTl#0?e*XT91Q3Jk5GaYkX9ohHah`9d zRd9mAzf8AL36fk^$Ws)O0Ow+0YHt+FUgiFVMP2fMAYm4>F&z>p0Cka}h}oFC9R~^v z^q#tElXIQ{rIT{2g;96EKEJKGqaXv-8iN_Ik4K5Aq1lm|7aFJFE3uNo!949TSYSR!6Y{8VNP`4__+os1oFRhNj zHQT>L2E1X))fYJxM+xp4GN~hZoG$fS!-E6bwHGU9VyVhBkqlK#j_D^f$9yEY%~!~6 z1)nYS7|f2fBgy?)1Y!)1Hb+-~e;#0BygmD`KrzH&Ul5 z-JZ2OL_8slm z?If4M=*f=DzOPflo|P4F0CF~s)RlAyKqo|5>+aWB1Fb;Z8mrjQlMedehW(MUSfHa< zOMPA?0#&3+&>9jw4E%Kn8Q;gxg$wIOm%W=9(hp0Be#gTX_Cb%flyEngJ6rcmyGf~8 z8y>_9j78xb=aMf@N`)?7`Flr2PZ^b?pl6X~k>OnS+nrg76AFtM2CPj!sZHLaoj>d6 z$8P+K&KP?>^w)JE$M|H0K+vAtrYso_;_5>$u;tjb_v0z|jX<-R_^L(eP&^thB|!zDay7TUUR$qutTywXHPk#cWZyab_8ZZ?fdj9C*#0U7&Fm#W zrIJ!rrd>c)W+3X>v8d8h*gWM`V$hXNfxYHf|PXs{9 zZ;Z{G0fL3H@ifBO#Yp`5b$rK~2=s69omWH9)h*kffSLLkB5&&dQ6fKZ8z_HIQK%P9 zV7J-vf;slMz_9BBscyF4=Nm-I%%|0$YTt@nz9L4W?@5#W_zK@A8m*x$Dh$t*{)6*q?{*Tui{=MJHr4<#!!n~DwMj5j*>vg%jzW2}pA1}jR zck}Tr=WQWR`@FQK_%LT}FOAuIFo^wVZRy&zgMf_G<3rpiE+yU9zf8vJR@$1Y>ZB4w zA={FgV5Us#G*$s7q>`M90xu*gJJ)`r)q0ZGJ_H67$%DFav>m}s{-lze=ST?6P4Mac zq2?ciAi3XhGqs%{Ih06~NcB6YST(p$sr6N^!K9yt#Ot3iAp4nXa&oohU-0{y`y#vB z2@gK6Gly?u4;tw2(n>`?Y<{4# zc0KYyn&XC_>nGZ*nOJ>?qsb!q_B?BM*i8qO!wH8Lw||2HhhC(?b*PPQ?FGYx^30yU zV<0i#D^xPE2{k_NdZl7X1VMo9bo=;@1NzswUllbXDUkMychg|tbQw7aRkuc5a|>|i znJBmZ@T~GzsScufQy6u?a}6=YilT*O)&w{Eo&AceowAZ4Ly599d>29?5hP!wBqj0M zmHC_UFqHrGkjG)kVKn_KqKyY?n| zc%5q<ay>?>x-Hy~%3=Q#o z4NFcv^Q)^g|745}@$Plz=nMofY5hrGx*xC& z&Hz@rN)@JMveE3O;n4caUH*&^7GYtlH;nC~gXPOTZj+;9V;U|nvx=)91BFm~`f|%X zhOoqS#Tqgw282&3Wq*cNiO$#b;|sSn>s61>2A#cmpn(+7DUOs1z_ptDTZK9(iiK+! z9y2n_KW|D*lpg617G3W5;FH$^nJp&^a$Q>mw`SO#p;+^^7U7?`l~Xm3)5xM{wQt(lEOINpG>DP^0TmRJ`N-Cow*uvt9ejscL=xw5JsT-^?0HLcx|YNahs zzCnIN^3k`Vk92iPC*4&BeFZi&O@P!9q4$>&M{`DNC`lrnQ{-Y&eyCyF|c?Z_p6yVEqYz9mPEwOQHk2*fEUEvzfC#!cl)Cz z^tjiJNqxATx^TXQL~776#-?D9YwPnddqZ<3g!8Ke_d8uM zkpr9XL9aYs=Wk(C;1Xt4K$|xAWTIYD;pT zY{TkUxfgMeVf1d8wdI2Jv#@}r(1`1x`W0E@_5dYdfpc3FB zP@(fd3Ta`_?44{Xb~H^|WIog&Dr+4mD^*`|r~9!z&~-e;MZ7BW z?F#}@vPL^>G0{UZXIhD&?DqAzvDsj>J(PjZksYo7R$s`n`(jHqW~J1uU4Mo*+2r>v zqjv1!^?e7*+8s3}tZK<_z_I69!K0W9aqVwR%PeOmcv7KgLyx1QqPN!rS_qvyqIOzD zdnMbG1j209|H)J78Q=kBk3wQ#dNCa4B=59>Kn-MGJ5tZiO8?aB93tVD$5ubdYFtK= z^q*de?DNJMiK0q*e_C2v${iXyFWS5c`dbLk;p2u*Yya`~0<%8qM@y%CE%!*5xN-%n zu$$!eazr5vvl>T*pu^Q`gzz1#9DGfY05PHr#wZs>RHh$ERR>S02MgNw8CHw=j+;u( zG#Muc312PAe;=FdaZlG}IAl+SB3HN>h$JX_OZeygSX;>cr*`rvP~+51Pfpx~xq3FKeLR`_sYP0+P?| z$i^CkasEj~^zDo2`<3qfa!DwsoFT?kh%C_2Wf~`?K1D8BC9sMM3HI+qgB`AF3D;If z%0mG*QfTca97m%Z(ewI+(e_{2gSOeBxb+l>Nx>W&D=9f`*l2?z8GhP-HB8#eIlTtF z(}Tpk!evRG(vew~grS3?OQZ+_;^hK=&dq@hzDV?QRec1Nn?`B>!7e3=AQ=M$MPI=1 z_hVn{RgZ^lZbgv3qnQq$4;g} zS#Z0jBZ=Ro8Wb+G-WV$H+93O|U(~ZttSqU2P7329o0%L;1QEw?NEC@Z=C*O6#&PfY ze zA>1r22n<}RqaHN$WHuj7q6VbbG7gh&g87HL;rvA&7}o@=BMRg`;`0D^^fhRtyaK20 zpEpaQA_I9m4r9K*IykaLLDcn_Lp$p3L$_$|)z0wcQ3+!@=YeUHgT2Mr@9sLYQI`$x z1OKl3MpwRrEM%y3{u?DI?HZ%;WR=w@dEmn9U(j;3 zt)b5mL5R#%wH=wVwDuyRatX0D}9C_LK+` z8`C$RWJ1TafpJ#edT-b0w|n0-eTQ!{4Ad>RvpP$bxu*)H&q94yA!e(^{^ZMXM@aIjPyw38OE<$c4*_HDF<6fseu!e z$g>qWEhw`VBn@bZg+V_Z%HlWWdsxfK3h>df zvEe#zuR;A8K~guhYRei@KYiY})+)*Od{_<0Zi}N<)Z;&I5di?_(xR~7|Co)+Z1~tZ zU&dqAp;ite(G{(#Hc|2fkgo;7(lTXchIQF3`&djH4H$ZMj#Yjmt=|l2=kPc^ zf8FPOsCQVGAqjZo>AH%oaedtSP|^T%Kpmf)K+=|wYO_xX3Kfmi8_KVDeZMqbVJ{*PNP z*CUkO18i>R-!_52T*TmTy0urp*jKce}MOR;=+z-K(;wO=H{ZIJ2`eXrZU+(Pe<8gq*}f6NsL<3$4yO@~4oe*l@L z`iH(Ty8Z(^h~nbXEPP1K7h%#PHDHaYE@wDhF=8N<6rJUGBo)fdG%f~YD6;TFD^-9L z(;Og`E0N+MpH1HIVEG^?fywZhgSGpWWoL-EFOIm@VO{(A{tPA^koyi*Z_zn*JkOZU z=dAJ><*;v*mUO@}x9VTI>HYWfH8?IV-K(r$tRg>I16vb&LneYhxeCM0#+Z}B;QNmuIVrjoMOEa|ji7OLZE}rd%RQqz?c-rNQo);z_ zvKwe&RnkQgC$ds|q7#N^I_v@Q!by6d$RM>35cXLe?MHW_d0TO}Th)Y2t!w)CCt(U= z7mRzo6%Syt9)55oiMwF*YTmgma9>Z+>mdXKLXJn4ic!PQuQuBqsjiPfLo7Y#=;Plz zYhJuAW<{M+cd~q$`O)w^1x$&9rYZMHde=#bMnbgxA|c7ycSM#5%jHjz3&+-L0r>`U`EWrTdTaHU1HPZaIpo+8a&$X*5{HK} z=;bG0t!=+eiN62I`tPi|S=r0w56T>+Sk0GDXYicKL%9`_tRIGl2Zeuf`GSZMUMO+I zPPVR2T&pP$o=uyro#f@1r;~lWW9~L}Zoz8xE8{8&no}p^fg97k{HFiVV7poPX{n<% zmUG%KNV9w=H+WDS)Jq=v@3cO!=lPBhsqrsjVQ=Ki)hX&@tM$XMVG*Ke2*j*>4`yG8;d1+#f)?7 zT8*208y`N{?V!HU;XE?feu$nIV0BC2OwB0zOk&|exM=*rQ=!xB@^VJ2MmP#Qv7?%` zwpF|iA$`pdJb$Ex0%}Y9*Yt~3e8xm5pDC4yg#8*WjcuY!5aAo;e<fMXH-q85<{%~i>4oIhsl4ubJJ&-|3HVZ za7*(r`66x z&~K)&Bc-&JfAnr~+M=fOeI(OS&-IC`GwyjqR%6~A`%o(t+FA>t3MojMET6ivlqh@~ z`RK?hYbA%^EUVr4H*!G&J(>oy)lmX#@)+sfi|I~I>>1NBipk_gb54Pa_@Uq#{)Zg;v(yZzNR^E z3T+o%%qqHz5PAN-lL|OZL#IIs-3RT16u2m?c(W4t9n@48;LoXJc!$8d@yN9KI3zVC zaDqP+dlQhO1wB*z%HZ!>S6@8eB>g^*{r!u%WUq#I2TqLo*7s57aOmaLzT{(s-`6hboMD?i zbsMN`$7Lt39F3^SOI=rR9(LY@oULDeO8?S^IBXk(tbyKm>+PO<36fm{k5@bRq5;ql zH!SFx;zPeR6}R_vS>9p_aP@o={H-2BTwuQu6}Ivrj#}B$T)c>~nA6|SFfwOU!8x2q zsQPM0k+1bh3xPR^yJpG>VaIM&66UM+ZDeWW8x#~e(SQFJKuIJM!-bl#<i9qDd9IGmW`T9<|SG@$~52-8;wVS#F)_ z3DFAhfLwh6nP0!jZm0FYXbaLlf$~lSB*RfGc1N~ii1>deQo`4dv%C?o7oZFKXsQ{b z-`xd2`y_`RuoS5W@9*I#)1bXFdSZKnaI!DU{VD3D&&?Pv*;{-yV=OGusp=knyv zCI%>j+1M|yiQYgnX@khcyd|~7l_|8~(TFA1vs~>VyLIZ+6dpHo>g@sv_~&Y%w2_99 z1N`gmDt<_xDahoNro>wk$@LkBRSLJVzLC~~N)ar3X2wicRGM!&=SzvD4JQ$feX^x| z#{=b>SSXg;Vl-D3Fyvo6)0y;}Ao!hnbNxR7{0#&0%i*=Gu1?UktMy(`b8{OzcKFW< z4~1r+CwgI2^+B&5V3VfGCXO|uAU!xLm$V8A%^LTCDg;XKz@Cwy*&%GrRv^GO-Q?s^ zfhN*#vSv6%1wti&cMK)iL??}= zRiPQ((2TC=RlPB)`k^QKqUnZHKZ`KBO`ZgrO{8EFCEP^P(jjbTKvk3E(a?l$lPCR# zs$Dy^rjdVB6iDKDAI5(BK@aS>m8j?n6lS0L{g7A%$YsXa+YX5=2!}+b9i~+toLZXD z0(+QUeq_UW;mkmg1QMWEq1TM0x@Nd#8j7T~Hd`>08!(d_5vi>p+7V`(MLN-Luanl5 zwFH_S`I4R8@>%N865Y^@1}kzVe}iUsXf&HF>e`N++2)&^mZPcU$XHvxkHD^njKZmY zkT7iW*ikvnRwJiQY9qSaJFmMKFxHdnelbw;C|sCq3Mj&VX%?B=L8ctCQ`mJA0?Y1C z=Mm1+HXYK{w1iC}dl4znQfQVwD6e z@(7W#w@sT+rbQl2{hpTd9Vmq6`AVH%TS*_lu8-yrM-WY7RBc&l{r^5 z0!4{-_>m>HZ)3aQqe61q8|k(OR+R*Mod9eVL9_j(6bLzPWRu1wkUb+TbFqmd5VjhL z^!4OFqzdVU!bKWf>Z2(n@_rD+JBz?k7fF2QcNn&^$uTkYm2Rti&7M4(Ca$KmRdIp{ ze&op(iIeLvi%9zDWo5$oA?g#LcypJ$<@%7sRS(51@-z9=r@k1e;7=|4tGI zl{~uQCo0OLhAjQ+83QC?{9b49d%dBM0oNb|J=hB-P?(vPE#l!9g)9Cmz1fsVQ+8An zL3UDsd+bA`*HVRKP(TI>q`4*smsC^D8E`^L_p7VdG*SqpfqOQ9W-E?NlTkExv@UJx zNu|}(CZUiW(ps#0rMs&Ts66p1kvDjd4T1g3$80Oq;)%tC3xhfil=TUrxC6yQ79|3o zdoMecG{YomBw=h~dCfCTs+MZ1c|z4*zb8rqWT;PiU&{zE=nlbzqEcyPQcXS0o=spA zQ0ST0(Y$R`Jmqmp3{V=cDtYtzl@m8&HQ}NRnugn~BIc<)VF*zo*RD!1IpFF}cL*WW z&aCEDlt>mXJjBVULx>OL>#R%F(Ujvl?)u2{(@LXB^G^D9n(Fs?uH#Z2Z`=6o#{)kT zPW6GJkjJk~zqDKhB{G46q8xI&kU=bWxjEIp0A#!QQgyr)l22uoG%6Y7K&+}vc=F)0 zt*)ci-@6V_CN&7){4U!x^D36`DKkTL)I34^@j=&hUmqw}#5o5}B5xDUi(IGd1f`e$ zvqpZ`^HU=~f_YC<$)tPot3Hn>E#sOIf(Wi{we#aK;nI2Cp=b3xZ(gNrU)Ax}F>d9_ zEK5}(b?s-y$^1cML2(8dI%FU<=m{dA1OCBMln&wPw1FMUhY-S>7GyeRF)1_Zq3(Nq zNsk(7-h6(fk%aN&?I6`r5k{CGA4qPiYl*l52;d=5E(HcfIHJrs3afm_epoMp8Ubm7 z`H@LE<_b>8G+At}%HvlD&vwQ`$!)@Ao+p!3V)@mj6si-9urRhfuC67rMSa7r7ZtIY zaJeARh7}#5Kq`Vd3L)u1Wb!L7mw-W3sH6-s?|kxWLFf6D8!5ebkjkU$I)-7m_GK_3 z*N9wPD2fRu{UL7%+wZC2MMvsS8h%8Ikgj=@CXi4nqN-k48DSm7Pn{y<&*>V)h1Y!Z zi^%!SR0x_j=2fJ8qHhwhjRQrAzGb&PO-QZJt3Jg?)OO0|%lHK-3PeWV&gQE`B}2!N zMuk^#yrl8MJMQS+4ok}wB?%&Avt1peK8IB&l%h%%bRzhfZmKGI)BXBa6zeA+#7-bT z^UZs_7(b+fofkp=PcAbKf~=!#{WTk6H{rb7Vdo7SHmo4~vx1!;nx@gSXU{;@@$--5 zd2$`q|IP=oehM&`ifNig|Ni|s>Zqfrs>=MIo)uJ3U0uz7`|U@4eLaR@_(tIT#)g6h zS5?I26Lr`dsq)92dg`ehd+f1n*|LRXGD#|xBAHBvE;5g4n$9|vN|8#XFiq20vIQ~1 zrYBX9AA%frZwHrYUJ|H8>Dsj`H8nLEd-0$irT=k z^BacYDkqgnq3gOsCOMcUi3I)ujtY_DLpB8h_uMv_?j?kmG-0ZfOMoEmdmZZwbwQ)O zYRJ$FRe4l$_z^}Wjx0}AAE`jHJU`RTRK8kL@qvnqpr^i{$>nFFsp{k14;mNpGvn0y z-Pkia1s^b6bC^*ho8UV=ig*A-GWG@ zUPq<})vFL6NQi)cGF&Lart_!>b2PmTD~aSa-GB`LN2-t{kJoqd)}=zA_~EBMa_wiX z2L%hf1XA;Q%gRQKV%+zFXJdz%L8||wns7k@1eGM}wtT916-`}x!`DG!Y`=O|2vj`e zYsRSx6Xpxa<6dHU>kx%tK}r>dk_QpIkvOmUrV>ahjXX#010)g&Ct|0P#~XR`GvCy7 z6%`^Cj z*J}OLKtu(_LzojERfWPr)+#}w_%q%>mf9Ed?F+HLP!|?F$XAK{d?!EUQGK6m!Gq31 zp3YKFFM^aPstReE<{L1`ran=WCu)78pk%Kzc*)~O8g*ahQ_Jux*Q-8WwaXTPQ_)#F zYdImPBl;~odK z45>tbLnH_eU3azsLOCL8+lBc;eng5&qA;bA_4B*7U-Y+7^?@RP*2<6BUK38;mL!mC zb==3>`Mt`eM&Q&)T+sO)g^s>{so~d2{glVMA4H<4gpv`+&>I=#JAVLI@$ta!$Y+>Ob zeHa~KO)h|~Rl|VsK8gyJGkzd?i!t3k! znQ;;ow>p9$wxqHLyGl!s=n57v@J;c_+Q z{GJj)kvX+2H7#n#$(FCPldU7vHtIF~2qgEjnPk~awIIcc6D9dC-BHvbS0~?(i1NHJ zWy=Q@b1!$~N}!kpxz`(qdlz=M1z`Ue{fqC&4;WfCT@ZP-|{DE^6mQ+Ci zKVAsNq2M*5orI<%3=BSa55M{ak-&i{xcJ^lRKrQx*3`+>z`+E@)zy{J!p6$c$iU8o z(bmB%?T8N#28IMi5-OzPmVS`o>V+x#)F&t@vGX zii+9g{&XY;!r-!SKX?7>`4~7MuJ@b#;ekl_=#+3mUzNohk$KDz{q5M!JT)4_G1;yy z&?y6rNW=q35Eyodgir$&7wuL;;bI|3h#0SIxcm_PNg#-vD@jW_u3bDkdr&A-OYZQDELc{PULFnSRNMBdr9nIDg z6hh4wJtjg$iba)F#C%>2lwiY)2c;zjG2&=(w`haEcf zpHT>PRoAcR9mJ(`1js+!wvhw7t;>T_OzLcGs!oVuAR<3C*oS`rp_~_qfA;`FgyO=- zY_p`A>~##U1BaZlXbm`s5=1i{y3}YNP)_@7KA4@@6rJegY7tm;iEM9xin8UILvB9M zu^vQLYP5Z>{Lk2hl7){@;f-obK7}IFi5(zaijyJwH%d~s&)cZUz)81;&chkaefroH zND^ATs8$LMl={vknLcLM31`5vk&YVsu@V|a z=-(6hg~iaQmlfDyaKAzuWRTiAfunWRuP*rA*1Lz259F0I`_A!S5P;ASm3M?BP@$D} z;nXY;W`8tUv^q4uqq#tQy9sy8ZJhBaF|NoT+N$-6_;iVguZ6HE<#_%xQ!50qKR?5F z#7Hc(c0Jrp4-n4B1Sp3Ld(G85(aF?8vB$ki!2c`G&CNLlVF0rf4%CXDjm=MB+>BKq zMR5J3>Wf|y1RsVWM+j^VPN)%s^Q|<#jG|#e)VK%kZmwjb#6Tu&0nYpPGo7_cAKJthRII6*m3p=XNzlI^y zEODkP-?6y07sjjYu+(l9W<6yT>rMCC~ zh-kXeGs3!5X#+W{yo)&MM5pNNXG^90X12-d_q=!n|FND>Wjq^+*HGx_sgvq-?6#FF74 zQbV(^CTi&xdnAdpXT(qgDZbW-G$OLC+*c}n*FaNc`G!sHo^;^Jmir|d7L}=Jgl=Oy4bDNlTK18BdIvDY;3mdvUL0k3m<=r;1E9t1B6ag3O^(? zx*U43J;rZDZ}I&@jpNba8F=Z^t<>TJRB_b)K-pzA^=lL{8_=B}Zk?OY(SSYqRHnN# z@W%3$K*Pq_$z=Ort>>}&A0Y@3=2ASr!GWD1wI4lu)Zu!%(8=L>%@>y}qzGN7U)juv z{jRW9H&b>iNNG8sXKwYTU^|@Eg3xTotWc*&u3itc_)p^R<1;1_+y^AW)G|}3k@h*6 zWa%+}CQx*3jT({lqeJ?{rEoXtrV#Fk1v^ z=84ZV$vHg-)dE@ag*wyq!K{|TsIhSS^Q%!`^M;hgIDXWAiu}@R*-oL~5Xu#sluMfxtkrGy0cURz#}@Cs2OgRen=-y zYowKLxRiPvgUk=Ad@-T1U<~~px`hF4G`w2GK_ffkb5EVwpE}%O%;G##XNsCS)uqw7 z=m{$fMSmEUe)Ec42JSojYhaVaY_chlp=i4mnba&iwp$fDtvf! z8B-x50=7ShB}hhEr}nYKzH<@o-OB+OLqIdHg;o$KDx!ztB9eO;=5dV=)^uf(EQad| zqGOkOO(SSJXqSlnL>H2O8}_Hq9Im%e!QMYPJM@_s8Q?L9>(B{9vaUVaXz|;2kfiNG zk>Wn~gz4d-)Qn^BOO&Dlp&*QFsb!9|2v!mJ(&#d=qD4vsy|>Tl+40CA5!Qg}ws|BR zSpuTM7rXa2%%(wNBj8$F-kAESdVqyiYA6)DjD7hEcaB&rkpe*&Uk`(!jh&yxNLBFL zM_;=A;s&40wrM_F-%zB)PYC$o$*6$=B-?=qIJM~xWXK(wXoh(T22-+3tmM_i&`A)l zUNPb2e6G){Ri8hZa7RPh)GxV9uH5U3=-HW)4NE&>0D8u=s!L%HOuc1$pn)5WzLxLbXbYPU*w=q=x4%w-Hw2YxLHV5bz=xlv=-Vag=6X| z*L>_vnBr1P9*ar=LH0eabNMh-`eJL-UnnJ?LU;%P-=s}+?=v*nJa2y!%}?lVy3L$> z)HH`;pg(MUCsc_VZtb83LN(yvcCM9-ML6JX^gnt5hlUvmC2|{S&m=)ZqZI2wtkCmA zqT9TRVL(>_!*WN4j>O(OW8h}YKC$C~PB`PTJFGN*f%?S!2*gi@oS zk;nkM)|tO)+h!K%?N zDk_*cxw(oprm}DG*bz(y6NiVD#U$c$y0*_rd3fgS$b)eBV=I)oE1*o45MgRK=|2f{ z>O0YrQ2GsK)Y#}0N>Q;?CJRwXMxDklE{F50{jp?QwDQ0L$?Waz-TtkHuzdOnvoV~O zv{0ohP%*WuYrMulmnU7h_g!Y2AvZnAs1{N8yFyRTi?5C{sDavJRypMIGggo6UjOXS zDL^m}J~;R+lgf^`IhZ6~AeZ6vDkzZkn<1P@4ZTTnT0B) zAFOl`V^GPx^=JdoRBHNof3q`Nm8+k+F)J=E&X$CRMpa6wQ|fhd8^3eOZyye`idJ^u zB>_Yq$T<+b%!eP#p81YW3eu3m@5#|12)rpUZm!k>gO!!_@1=($)ov!Od|UxOVPU|l zK{4|1VOnt~_uNZFOv?dLu!NU#9xsgR0~%RnZ$7SHvKWNLTg{ZeFJT9F5jpW_nwae9~xA7+j0HPOP zGbxcEqUA}%zQLUS`cv#U!lN5u0TJ+TbrCs4p+=Ngv}UkZ$g83vY#7xZ=;JWPjjc&) zD}7n?g@Yd%v4_U;xLzh>7-boC*tk_xNH!l>oe?oN)4+_Dn1Pt)0lUJN?o)&IuKYPJS2@DHN5g)sgI-7B4ZuMk6P410$1`K|d#DUBvD6{>k653DwBsR>>4n@v^lrPle%1 zUqd6+`q3#$+1zhl$QUr1jc&lNX%dVKJ) z2n!2yyE|+UQ6al_+#JAaJ^h7Wd{J+`kfYsTLt2Q2hD=RdspcCwXx-KGj?2N7r&=ix zA=YDuj|hd2NWPp}QHXY-HAMnHjfW%74GL-<}2-OwBBnyVYCKHg$kr^33*h$Pp@|R zzj~u`$3Q_r@yVdt?bPu8w2yeG6`MipyKak1vk8Hq55bDp`8zfy78bMscogKq=+3qI zI;&(Z`yARfyVWj$E2Pj4QNvAY9Raw%{~GsWMf$&sP$3*r>qZ_y$QtOHJ}QXG#`{vR zkQ~eBV2Q?xi)v)7PhDOWC9;HL^dLnI3}>-hpqJ}385%_`!iyS^q1>=+^M9=S%sv?0n2X9B z&F7qyCyAcrUj{A_3JkF`8eXBupeB zFE&%_3GCY1AL6HZfNj&|x>m)D#~n`sW5uF;rWxx|_?(8rsUOUzOH*i>sHj2~0tdsL z;ql8nxa&p&9mjaZp(CVhwhIStBI0nN-N-TB#Ug<;dKEZCF;wbs)V*JH6e=s>)>bn2l!kUL7vPe*O9d35)i-L@Wg6-(7F?!w2x; zeC8BxN6Lwb33~0A(e3U`e$TIoiNVizVPr%GcPUgW!;^}qMpKxBMdYbW5|NP934oEfu-w zHQMXOopECOP$E2&9ypi=W!{RIFh1F86VYVNmG|S}sS=@H9f8-pDhk`zFwc5?ysYrO z$s+__R%1HMs=GxN!X0{;CCKvCI!^#;2q zo<{j=dV1+Vi^C=orEF@T#bmz0Ou6pR8xr8ED10X{uixPX0&(ov=xb@=3EXU8(CfDo zgdh{14VLJ&@!Vb?$CRj7>?LB*Z;fWfh=d?#E9FV~?B*CM$9Ugetwu1l{{-03_z+*L zS`sWy?2Y+wcP*YR927Y4@Nm7tV%URFs^76(v6!5UyXZK|pDO#O_Tf^hn20LoeEn z+i@A}4VuU{bN-UFAk9IKmJ^vnolD@5-KE7@QSP8k2I;Zs6h@1KnX9wHQBqbO2LQTQ zThV@tL=@BKlyY}{+y}IKKM`-+8uIqNzh;doDH&NelXqVvC-AO(YE1wt&ytudkhhw?KA5Y)74+fDmrV->dbdR8Z?RhymR+rKJ=SYI8=y|MEcHe6w=Ige?Pv( z=a6xAV(VIhgXp}yWH}Wr55SBbt0FHYk1vShJL_~&EM>0Ga@ zLn6|t1fD#e3k&0 z``M2y>(jN~(`COa44zdw_Xr&X0w-AdB;30rD$WgA3pp!qhopf@d|lu7Ap z9$umRliU79=S8-RJhB~q23OGIPiD%3EO}%un?%{YrQx3S(?Y#wb6Kq%sQg!n^D}n- z@vg1|{>727z%ay2k3AaI`JM6H)l;{VDRupuJi&`G5o)bk^Rd0N`}5KD_PZnHJYoSa z+^3sS-%9U0m#u~Ui9BgXGREW9b7YWZY$Z)MJ6rw8&tL0=Cx!xUG+u2Y-FiO4`|qG; zRAulOu<{5TrYqj@_M0vIbRK6Z6MTI9h!}xe1mL*cz{e{cx8(@9>|lUFnb_D?68w%P zz;uNO23Np~3*ZEc!-fRV%&YzB zGLgYV2342c-Q9nCB^4?rD1N8RbQpkuF{m_Dc@$T6^r`RrCJ<*)mmwQvnQS`q3RvkJ<^#0L$OvGXG>^0MRB_EXwd|RDlC+vD)NKz3obQwX}r9PvBPe z+ZnF6Q_6EG`fu`nzW(C#a9h-a4+A3CYu#oi#k>lgCM7rTKu?1<1EaZ0qR_7WN)M!; zq;I!rHi$^Akgl3gqvJ%kMP@Uk!zF7TVa>zd-tfmz&~EJV2`Y?=tBhDZm`*JfjTP!p zzUQ+0zTzqcq;Pt>UvW2AYqNw0qW`DpOcRPAB&;s*$gz5BZPlkJHnWk!gAf$r=mndW zD9!0=lfmTO0{N_8z-Ck2B_$<`V~~%Q+jw0c$FfCa`dancJo3fEkzRw4*js7PeJ_^b z0f_uVY97ctQNTq}HMK;$rV_O>!+gP;jkuu;uV5T<(sriQ-x(rp^oJ>TjuWf z^RF0t|D&Z2m^pXiM2N8p;b_yY(lAfswQjL!YIImPtPYAfnB*SU>WRmB^vWr}X!UOh zG4<)l04sS3(zBW>F;4M4L$vUR zT=Au5XZt=v*H3_27q^-Yr`q#w4zf5X|DCf;Alm~EPjz~);Z3$!P) z%Bm;1<|7vzJP{kbT9_S+gFPAsMz?i+-hE;iEAdH(EiD=28KwT2dEh-Sjnexw_XcpV z1_Ow$-_cUbItce?(xXXC#6;c~irRZ5f;W^h$zOvQd)s}14J~O`J9cF^{qZ%XY=d^XQZ~q|c$rm)T#CMGsB<7cFLUA& zzr*rXY~kCH#W(8E@bCfIq?KcGmYjPMBN>@UeV%=?NY4E{o8`RE9?n+y_niJo$3M0% z&=Xs$k)0=DThi$Jt>bfPUwk}XZbKs`X1YM-sJ3Bf0sMWvZbomCUk&8hQ}@*#z%o*H z0qdP+NQ1AiK(m6

=4If^M6~0Xy|^M*=Nj6h>5sdabmN3y}o!r%CnjCE?Z$8TRT%-x-K z(Y0V0@EZo+v3$vS?;i1R)rf1qw}imjbXDvn1O^LfFIp?amt}jnD?vqEIpedDrg>?w zKa9Im4kT{n{8luZ?Wbc}Z+}T@%)XSSpuby+UwStoR;Bo!vNn)sJsu5^D7SEB&I!z} zzqMigJ&%aD!<%%z3_M?%S}B-Hn20T?7~b0usJrvMy>J^OgJe;N9hg0t3F-XYYF5;% zpZv!DrME;@74U{tOkOpA5stnFh!Eu5Ac%4WgHTXwWG}Fzfvl=AsXH`wPXm_I-R6Dg z%nJ-W77WCw2Is|ba35hBWT6M3Z? zb6zo9{g#(`hwpx@2H}BuaV7>NW+Y(*;J|`*NQvSWD2LR^t~^~IJw+So`%8N2soB=i5Ix01pw@Jz^5ps$>GNo?1^AX7faa3ZYaU%$JeHe zZRB&5p`5%D9w>R==3u?)r>ABgOM-=mX_{kGH;P$dfQ~_7)C31HI09(EOdQFd!$#s$ z+X6xY&F6ETu9hX3$^vBdxsPAgCZvlsqh(pMj>XP4BU+O?7~h%AoP;VK*T^&Y*7w21 ze#~(}OW9ay7;~^Ax-#%6jl!3oYEpCveKi{D$E)^@`Nq1&AEDJ2p^yg#DjH#0Y8QD# zIdBg7m_~o=W!2S`<@6~#pAlX?j*LbLMB{iFe)fi!H!Z5NC(2B6<_^m7rzIO*w*IvJ zK^hoo-#w^7_1(1&huu#2!irVX+Fk0^5k0Lb_n>R*H@`Pevr9`Eb*YPEKXO9D!o^4SgdErtms;wlC#WbO_vkx6!V4GHAMiOnw!GQmESM5H{Ozf9+8Zf@G7n+nK{Eq*1fk`*r$^D*EDZs;WjBs91f z5InDfbBnl1bj(<89{z0JHsmC!JI+3UGnH9iO|5WAU!uFji@WFZnwh}gO0nYD??j-Bq7mQ+qqj>z25_0$Yzz3wQ{N1{lu5KQ7fMIBt4%}B%-Re1H`U=@^5Cl)~D5^ z4Ddj#qLSc@G9`Ubm1N2gUy?`|;zZKAHXo*W1#>Mx=~PKPp`AwJFJj=!G2|RPxr}6U z8>IcSy#Ve-pk9rKQX!=U(lj+vz4Nl-mBngS>{C7efJ-GkZ(Y(KJDq>3s06=!;i%!I zUm|S1z`c*wN5BEW95yJMt4Z)PdY=~cVRPMX#VJ2B%fD+8w-gQ%q3p0BpNM#{Qz&y_=t2y-D?xmxdu_-bV*^pHx> zYF)?8SrZIAm|^GHQhpcW)n3_y2>O zp?rZa+*r4!hqRkuvqPRl;ZuQqtz=R84HI=_ZO(KD@n}_z$wUv?t6!H!^le2{^mkfc zO15T>+6ruLj;pAXCa}9LitbKjy}Bd$yK_d`8-OF`tTGNVSID>IW9=gajk7M4%hh#} z6!C17Rj5A0JtV^p!b%ubS;Y<@`T^TZ4()`;$m5LlN>QHw`YNeo@HL1CNTe90kApwwwbB=gGoC z`KL}#F5N5q>Sl7vJUmb2Jg5cz7h)@2lqR`RV5VOgoPOeRvf*gCDDpaSm#-p`a>#Sv z2hiCU!BVEM50nsC(%}b8L#il-Ke`%4C26Z9TEZnA;LXW9jVqiC=dg@1?6!PdJJ(SH zS(_&j;uJ1Iq)Y7T@NJz|@hMd&!}lk$btbke&T?@qro*RcBiFL3bUY3ZFxhaFp6JuP z?o@&`az6r5x_1AFS}s~TbECrHkPoIk&i2g{VV5sF;a9Xpw8s+4aNBQzSfVCO1oqB&+Bm{JudL;fHg5XU9 z`w^KKYdcz^+V_OJvlbMa@Sa}$C;*|qC}~?wfn*R#xBMaEGLXmX`iZgalK(_4*hA!SyNJ3c^yY{2R16j8aKNiUY$UHghKHLBJ;bms`pu6G zeV>bp9bUMrR@9L|sJD3STNhP^p@$q*{o4#xHBR^$m;I z*Jw7;uH39V4ei0l16us8Z5IOMV+Ds=u<>@$N{%g);T#Y;*ySvR!A&SebFFD?d8we5 zWAw)ku!5k*3RU=Q`wnJfXw3!7F3{asDYIj1WhmI;C_&y)+NlnY}_B=6e=+~NawP)q_MBRj{k2lcD` z`-L2TKyr=-_w$YLAXGfE#a*c_I5N=1zt9HSx5_J~U39S)JMtO~Vkgpu@BN+A28I9i zES(0KpYR!f;&?a5E&s`ZO=_8b*?~|c7$0J3UedN1&1S^O&THQ}V&r^z<9%QvT2+kXuR8k!}GkWC07oH4`K&QaeNH(XJpN3blZ#V~0_Vhe@CVXEq znByZ2ls(k71FW*I-dDc$Y1^`a#So)BfO*AJK*sVK6CMW5PYlUaS8x)+{j>o2oX}gY zM}DOP!q)yeTJ+6qf2Sm)IZ4T*n3D*>+-SF)p8k|P@=X!G_67^If zW8MK(DdwtEM*|*V_omrp`4XAz&tZ}FSSd9*1)TR+rZ`K14MrRewGMisD&-w4f&k6; zNE}NAz{Jwu@HV#t3K}t53CIx$jB_}Y*H!F5mv@o|HG~c#`Qf#Z81jDA@b?4eRDkyt z-CQCBcq63#VOP5)GrO>xvvEH2czv^QcCub{?U9138EUPicS%>%Ia2a(|=BR663GAL7wE=T~6Nn)D0 zBe9|MdD$Ipjm0+{?sp7%t5;){;P1OU2ePg8Es7uc^LNvQ8?~mNUfZ7>XB1d|M^|_g z#i~DgS1_F#NEWG(}@MaSW*(9!luYq%h(p1+xpGBlBu`DrH zFfb-Hmk&tmiYCC{c)6__X1$!tr4uLiSEF&%iP4xdY*8yFNzUngqVchym?V$6e3BT9 z=FYL#DaA|XMv5B|K%U_}8w)KrUzb;j5F$u8RQRe`vwE^)gz3Mv+>exmZ(35ZF-;jugpCX z&#;N6%=j5q$7(aQ+DhBZ#mZR|3uHvL`-*l$5BR78TszJKS+aa(2(ksWcnwYHw+cQui%^9+@5mZ4`+w___uD)VhwDJ8`PsU^@Y~MSyIj-Vp>x)L1gXh*T zAp#$jGgpS=*q+S;Zi$Z9PX#op#fLtnl?{k0`V62xxJ&E}q-|+6>I?BW>&qb)gm9}L zg6+o-m3)(s_bRmxWCefT_EoIe|Xw_LK-3NVR=N!&*de@ZxIv1Qa9apgf zgA>b-d|@=GNvIM!8cj4rqrI~xT_Xhv+};FfEwsV1z7PNA`6hI(oSO08sDRPoL}pmp zxj3E^CsT_<3!xKDmq~=lo~<`BR{-~T9NwrD;iHvjNI^)embqKl_j21;biDKZXe$+% z*K94KTAmxZEvZ5C)3SFsgJl=>6sO<9$fGeSK)Aib*m5!zp-R?ufR>~S6^pA?LY+}8=A`ieZ^8PKxy5X%Ht-t$4-ss=j) zqUi@Qe-Y6bi*V!_Pf7whWKQC$(k7MF;J~%$+4F_k;U=yx#GqC`YrX?YsBywEju-*R zLT%RRcZee~*T(T8RcZgB0wuAM1r`Z{w#}fkTvF*(FeXNsYB}fB%p5CO=?v?IM0%zmPXj;! zJ93}?XuOjTygsBmi5+X}zyv`Q6$Cg)UPUW4V-EJgGzsHJ1Wtl^P7@aTF)Yt!{3nN! z^|U(a>c`BOFSLGP)?1+KnIx}!)q6ZkTg>VudLB1d_nG%05_&O-$6G>(SSxG3i%!;^ zSAs*9q_&IAD(8;09)K>YS(2y;q|lm^40QQFln+zl?j>&k7wl+WhNi?E-7a;^+^ZZYwbtWINryAE1Rud9w03aztp zn@~^nFbRlEJ@xz?U5l@rRu-yi;-;2XT6m@-$V%eQ@CTy&YOMB`)uWU*dX&H6`QMaa zwAI8e1xes((YV;jM~DNBad|2|NUZzsXA_GQH`OoMUzl<%{Vw5EH|RY5);xnke4(skjf1^}sx%9Y zM&}(UC&WX}xt%P~`2FNBD1kA{MWYJoxcFeO{JS1<>;zSSqi4H?1@8t9g7s~BL8bf`OPl+6%$ zI-)jhv1}@GB1jE_gP(xd-FpPh$daDLfv}SEd(}}4j+i%bhBQ0d2B^aZHi+bj456jF zika8colY|*!%|ah#7{vviM)eUBFfw(nVhr28q@4{e2VxUGjQ680;@Dd-X^qW*YI`{ zn^lX&v@|g&ljE4h^eB>zbF1P!6t%CwV<0qw#g*kXemQ{puJL5kY_l+>kXW1}i}R+M z64Hj>`SK&Z%yAVM`J+giRawPe^5SQR+)s+b%_ugW1Hig8pcDML38(p?X{gHwUxNiY z1{Z#LO(gpc{$%N#8!{OG2Yg9$ohc4pozC&X~sn>N% z7?m7*Yehk#IZt;0n}drxkegQ&5M>6b7O4)UeC=8?8rI(~d??74CHBvwAI)21faRFm&a6Wy#FJoBT#}d`+h2WKi%a^^cp}I zs?~ppB+T+G9#ZJab^l5W3+UnxC1nPxazaa? z<6QAfxM`wMfi_XAfD!L>G2(+z-NFLQER$GVh=-o1w=>*!n%S_Vx82k)PaVSFIP8v8!jMQF`w+d|I z0J;Omn|H6~Z)yZGuQRr8#4o$q&Xykcc%ILZx>cHSxx&hWw}E8`P{uV!jobKf;CntW*?AxV)0=N51LCrqkK>90GTXFc<2&+Yu zD`GSZD@&dA#?@e>B4uoh)wjEgrEtWC4nwJ#nr03u4Uhjz$_(N;Uk9$QHbkj$zY{n~ zMwnCYLcX@sQff73FUA_b{dHWaL>6!O>K-7`V`r=mSk(fR7DE66M(#=+4XqPSCP%6E?k6E>y|)@wAJb}MzovOsSN;TaO{d}xv>n=+ zPh*cPj6V8FPm$LiA#Rp+U$5gonx}Voo=HvZGTOMa<2OIA^M-2WHJ~YIRT+MOV+{p) zYX%o)Fzxdcsw<^_s{;CDus?L({c+ibtJ2!_)u#n8jJKd;DwY3~zS`N=BBda~rtkfx zg-QCCXy_`J>Cl;bB7c#%nd@e**WA@;*7v=T%@`lct_K_wk9N71UQ&-9@$bA1Rw8D1R$aQEFOQUx?04BjQ zb?u#DWO9sek8wcDRpv${Lu(JJ;%h!bfoY|*)=JeNuHM>DI%MdKRy zINQ;gLPNUKtgpy3gQ97e!qiCxGoUcenL$XNg;|3hKiYct9b2uh04&k_hN?HlCg)qW zf|Z3!AMR4ft2!-hTM>S{3b*q!+;`rN&d7DAS0$RIEPSD=(Y~=dLsQ&ZC2=+v>n#S{ zV$fOcV%*{!;D8!KH)fE_qfPr792fcf^&H<1S8}8y*4P`p1&5`@zSQ|F0ikcBO^9-X zTsT$3wX#M+O(vm1K~}YfI_y=ljO(=yF<#4LZmN&K@gB@Hyq`o8Bi*IV-f#KM1NxsS zf;<>{NTk}P&tNf6F%8r>vNFmNE`jWw_0XsACmzM7iQZfvrUS@ihP%2bXi>xd7jOntaJI=~o{m74r7&D%mb4o_r&xqNehGwGG`+M{{ubhqMz)X)F6) ze!ob;=e>!z)`qciM=Xx|pG=-!0<109wa%M6jRCp{4`8D~u{0g~Uo1top6L}mBgUOWV`{NMV_quATgsDPa2*FFxZv;%nxeUT zW983&k?|v6TM+?#LKkgyaVs3NJQFH9-kq|9Lymo&EeCDxrjZ=awt%7v6>szN~q z4Zc<$EJ5B5OhrbW3D*p?jAB%(#fNl28{R;Z@R#Y zs#H0BhM)r-j-h<2iwcrv>eK}F6kuL!!+L^R!qy!+5yC)m zv(a89m_S@vj(P_=K8rqn&SkoKUm;+LZ?DSLh);`)$}ey7L5^F?tci^}8(m|sjxABv zD=#t9#f}DJs8x)UmVPxzqPdH=%HZPTl}bC^Y7OzRG<+eH*iVpK?oJnw^HDy!^>ClZfn;$75}w(i@wsC{v|0(Hf@qe})!u8o#9%FL|A>A%1>u>hQ@ygsn&wvscm+H8LHN z^kd;g$>5QQ2c5srLwrDUktb#pS*J=G1ql-%^NNMX z$9M(WR}ty&RR)hXwaqEH;_;Z^6|Kt6qR-edhdO^;cu1`?;?eAFc$5Hxk={Q&>4&2Y zg6)mizg54JUd5_%Hf&?^82R)dbF)acW)LtwJ_yT)G*- zhH2i;u3^&L7@i=k@MnBEpK3R`zGYLF3;#{v;va@@l)|4=qtSugbkvlzln|9S9NdXv zq#VvqcN|i4zho?Wo%X^cIVE_4%tl`#BVmuP5Fh(2&X}2CsYad_2iv3Su)~~OUOb?) zn2CbYg>%V}Zrlh%V@`{)+|OKWCh56I3w5KFGW^A~6e4XI<3t1xctj46c__=N;oGQ+ zM^;H+FDD;#1igRBG1;fDTi&-e14n6IaTW%|se<%+*_3ewoQbw)kotK;J<@sV1X$)k>cwlr?bTRr!1P^2$fE<9eT=1iX9_ zZ6p!bM)2hP6{Fz@x-~p7DMStr?V#@3~-~V{hy$%n{?& zHL~Hyo5{m_midZ!juC<{9=B?w*Tp>!My_**Ca5cl9w+P}x7Thj52QR+T3hhUZ8zaJ z+Any<+FM-1r{p4vO&LU_Vi?V@2S!rqByD-Uim zqG0*QxYTFKTweBZjC1~?*k6xCmp0*0Fw(Ft<~Mj^k%DWUps>3*PTt~)aaP%^Zp4UD zytbT!^z!)<2P`kUvPIE3+KRYSYFdcdO86^3HbmQ3lro{__xJ8B$HR9g!o zy^%JZAi)W%Dmj;@YDQ~rZ|G~44ENHpyH+usaAXRTd=?2i;?dX%9dYeVG%h^=9}0{j zuou%2pB<5_(c#2`{X!o`5{zW2qD-|m&5P(%1 zLI}@8xCg+x+9b*FhuHx40yuHmfdZ|isaD=AU~Tr)<+=Au=^+Do+~_>rNItapL-3~5 z$QTiwVS`qc=FtQzITQGBvQ>e!HV_5=g+tdpsBGYn3*cM?Q(!;vD4X^l@^rLzv4I15 z=H*^rBMLm?lk>htsqj*(h(hNYQfSki;c8T1l9nw{$tUcGiib!a7kv?^<0@lo)g6@P z7Wz7QK;3o^bOAeVliZ0zgCABayyi0Kv&aGIo$}PPPp|4bK7RzIpa8C(-^jn4m6n6h zDaw4Av$W*K6H)$te=%5<#|?+Eo3cMK)k~VE{*$T7;cPaE@WOcpJQM3RPnD*?&w~u_ zW<_W7CwxkJ7>wS@;9?5%?7%< zivM(AJik6|PF9e7=X8`)^j1cP)Asiw8KAs@AvB)@oX-0rLV#@cA3zIe1{jz0)@P&N z?4ev(Y1g;!%d6R)T_odQ1MNI-9|P=-ldR#B^sY&-h0t98#UESQfK6aNvyD6^^6hpt z%nntl9wm$_mqN#HvTlCrDOq{0Sl~dq6xz@gP~890EWDQOa02i>B=M+o=J~2uV-pcGqCyGD{=p22vINZh3Rj;AzRGyUlb<2`$&Q}P zWlQZT(6;Wk4!!Es2zMbaGMo)n`J{CCRvB7buY3gkz{!ga02;IDy`~H%McgP5pZMhdPvrY{ zCNLe8eEg?x^@jOBbmKHYP~75@r#CVzMqZSlRRW^<`T)&_a)By~k$_mK{`!1^fC)3v z7vay3#^kY(-jI5`JaxY7ySmu0KcPJO=@99u0|dBL4az{@eX;fy?E5sF3)} z{8za@`@h7XVklDkmH#vq{M&768nEvZP4}}Z?`$DUlu%e>{C`dVh{O`Y(T3$*>#=}x z0dQLk92FDz%thz1x)u~{o84ByCEHg#^;}uA#mUTh?s74}DuNBQkG(AXk8$GTzf)iK z4|d->j$J8Oht03-6)nhj*%>q#Z=@znEEP2O70+!sfA*9Ix>I3|v)w-Vr*UdVF<{d8 z-@S>n#Res=btU6mo;lu*b$V2MR(N(8QzE(@Z{{Tv4*ZUw^xx8o$Kc@ItIrJ2j}D!q z&XiJ1tUdT*|4oH{$>&uhceh^7{$PXUV<~UyZH2YypF4f|{7u&T n-|QFq$Df%0xk;*&zy4ri=f9g(X)N%;06tm}eYHB3$C3XBBA$|d literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/html/images/quickstart-5.png b/h2/src/docsrc/html/images/quickstart-5.png new file mode 100644 index 0000000000000000000000000000000000000000..b2f007da5290f985f5bd23500eda21cbd73d7547 GIT binary patch literal 36043 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 K&t;ucLK6VCX0dPp literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/html/images/quickstart-6.png b/h2/src/docsrc/html/images/quickstart-6.png new file mode 100644 index 0000000000000000000000000000000000000000..e981477f05dd82403ee0349cc8a7d2a56ccdd9a6 GIT binary patch literal 31108 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 E09LURGXMYp literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/html/installation.html b/h2/src/docsrc/html/installation.html new file mode 100644 index 0000000..f787f95 --- /dev/null +++ b/h2/src/docsrc/html/installation.html @@ -0,0 +1,119 @@ + + + + + + +Installation + + + + + +
+ + +

Installation

+ + + Requirements
+ + Supported Platforms
+ + Installing the Software
+ + Directory Structure
+ +

Requirements

+

+To run this database, the following software stack is known to work. +Other software most likely also works, but is not tested as much. +

+ +

Database Engine

+
  • Windows XP or Vista, Mac OS X, or Linux +
  • Oracle Java 8 or newer +
  • Recommended Windows file system: NTFS (FAT32 only supports files up to 4 GB) +
+ +

H2 Console

+
  • Mozilla Firefox +
+ +

Supported Platforms

+

+As this database is written in Java, it can run on many different platforms. +It is tested with Java 8 and 11. +All major operating systems (Windows, Mac OS X, Linux, ...) are supported. +

+ +

Installing the Software

+

+To install the software, run the installer or unzip it to a directory of your choice. +

+ +

Directory Structure

+

+After installing, you should get the following directory structure: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DirectoryContents
binJAR and batch files
docsDocumentation
docs/htmlHTML pages
docs/javadocJavadoc files
extExternal dependencies (downloaded when building)
serviceTools to run the database as a Windows Service
srcSource files
src/docsrcDocumentation sources
src/installerInstaller, shell, and release build script
src/mainDatabase engine source code
src/testTest source code
src/toolsTools and database adapters source code
+ +
+ diff --git a/h2/src/docsrc/html/license.html b/h2/src/docsrc/html/license.html new file mode 100644 index 0000000..1f228df --- /dev/null +++ b/h2/src/docsrc/html/license.html @@ -0,0 +1,404 @@ + + + + + + +License + + + + + +
+ + +

License

+ + + Summary and License FAQ
+ + Mozilla Public License Version 2.0
+ + Eclipse Public License - Version 1.0
+ + Export Control Classification Number (ECCN)
+ +

Summary and License FAQ

+

+H2 is dual licensed and available under the MPL 2.0 (Mozilla Public License Version 2.0) +or under the EPL 1.0 (Eclipse Public License). +There is a license FAQ for both the MPL and the EPL. +

+
    +
  • You can use H2 for free. +
  • You can integrate it into your applications (including in commercial applications) and distribute it. +
  • Files containing only your code are not covered by this license (it is 'commercial friendly'). +
  • Modifications to the H2 source code must be published. +
  • You don't need to provide the source code of H2 if you did not modify anything. +
  • If you distribute a binary that includes H2, you need to add a disclaimer of liability - see the example below. +
+ +

+However, nobody is allowed to rename H2, modify it a little, and sell it as a database engine without telling the customers it is in fact H2. +This happened to HSQLDB: a company called 'bungisoft' copied HSQLDB, renamed it to 'RedBase', and tried to sell it, +hiding the fact that it was in fact just HSQLDB. It seems 'bungisoft' does not exist any more, but you can use the +Wayback Machine and visit old web pages of http://www.bungisoft.com. +

+About porting the source code to another language (for example C# or C++): converted source code (even if done manually) stays under the same +copyright and license as the original code. The copyright of the ported source code does not (automatically) go to the person who ported the code. +

+ +

+If you distribute a binary that includes H2, you need to add the license and a disclaimer of liability +(as you should do for your own code). You should add a disclaimer for each open source library you use. +For example, add a file 3rdparty_license.txt in the directory where the jar files are, +and list all open source libraries, each one with its license and disclaimer. +For H2, a simple solution is to copy the following text below. You may also include a copy of the complete license. +

+
+This software contains unmodified binary redistributions for
+H2 database engine (https://h2database.com/),
+which is dual licensed and available under the MPL 2.0
+(Mozilla Public License) or under the EPL 1.0 (Eclipse Public License).
+An original copy of the license agreement can be found at:
+https://h2database.com/html/license.html
+
+ +

Mozilla Public License Version 2.0

+ +

1. Definitions

+

1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software.

+

1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution.

+

1.3. "Contribution" means Covered Software of a particular Contributor.

+

1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof.

+

1.5. "Incompatible With Secondary Licenses" means

+

a. that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or

+

b. that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License.

+

1.6. "Executable Form" means any form of the work other than Source Code Form.

+

1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software.

+

1.8. "License" means this document.

+

1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License.

+

1.10. "Modifications" means any of the following:

+

a. any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or

+

b. any new file in Source Code Form that contains any Covered Software.

+

1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version.

+

1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses.

+

1.13. "Source Code Form" means the form of the work preferred for making modifications.

+

1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity.

+ +

2. License Grants and Conditions

+

2.1. Grants

+

Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license:

+
    +
  1. under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and

  2. +
  3. under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version.

  4. +
+

2.2. Effective Date

+

The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution.

+

2.3. Limitations on Grant Scope

+

The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor:

+
    +
  1. for any code that a Contributor has removed from Covered Software; or

  2. +
  3. for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or

  4. +
  5. under Patent Claims infringed by Covered Software in the absence of its Contributions.

  6. +
+

This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4).

+

2.4. Subsequent Licenses

+

No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3).

+

2.5. Representation

+

Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License.

+

2.6. Fair Use

+

This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents.

+

2.7. Conditions

+

Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1.

+

3. Responsibilities

+

3.1. Distribution of Source Form

+

All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form.

+

3.2. Distribution of Executable Form

+

If You distribute Covered Software in Executable Form then:

+
    +
  1. such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and

  2. +
  3. You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License.

  4. +
+

3.3. Distribution of a Larger Work

+

You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s).

+

3.4. Notices

+

You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies.

+

3.5. Application of Additional Terms

+

You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction.

+

4. Inability to Comply Due to Statute or Regulation

+

If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it.

+

5. Termination

+

5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice.

+

5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate.

+

5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination.

+

6. Disclaimer of Warranty

+

Covered Software is provided under this License on an "as is" basis, without warranty of any kind, either expressed, implied, or statutory, including, without limitation, warranties that the Covered Software is free of defects, merchantable, fit for a particular purpose or non-infringing. The entire risk as to the quality and performance of the Covered Software is with You. Should any Covered Software prove defective in any respect, You (not any Contributor) assume the cost of any necessary servicing, repair, or correction. This disclaimer of warranty constitutes an essential part of this License. No use of any Covered Software is authorized under this License except under this disclaimer.

+

7. Limitation of Liability

+

Under no circumstances and under no legal theory, whether tort (including negligence), contract, or otherwise, shall any Contributor, or anyone who distributes Covered Software as permitted above, be liable to You for any direct, indirect, special, incidental, or consequential damages of any character including, without limitation, damages for lost profits, loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses, even if such party shall have been informed of the possibility of such damages. This limitation of liability shall not apply to liability for death or personal injury resulting from such party's negligence to the extent applicable law prohibits such limitation. Some jurisdictions do not allow the exclusion or limitation of incidental or consequential damages, so this exclusion and limitation may not apply to You.

+

8. Litigation

+

Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims.

+

9. Miscellaneous

+

This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor.

+

10. Versions of the License

+

10.1. New Versions

+

Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number.

+

10.2. Effect of New Versions

+

You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward.

+

10.3. Modified Versions

+

If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License).

+

10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses

+

If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached.

+

Exhibit A - Source Code Form License Notice

+
+This Source Code Form is subject to the terms of the Mozilla
+Public License, v. 2.0. If a copy of the MPL was not distributed
+with this file, you can obtain one at https://mozilla.org/MPL/2.0
+
+

If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice.

+

You may add additional accurate notices of copyright ownership.

+

Exhibit B - "Incompatible With Secondary Licenses" Notice

+
+This Source Code Form is "Incompatible With Secondary Licenses",
+as defined by the Mozilla Public License, v. 2.0.
+
+ +

Eclipse Public License - Version 1.0

+

+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE +PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF +THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. +

+ +

1. DEFINITIONS

+

+"Contribution" means: +

+a) in the case of the initial Contributor, the initial code and documentation +distributed under this Agreement, and +

+b) in the case of each subsequent Contributor: +

+i) changes to the Program, and +

+ii) additions to the Program; +

+where such changes and/or additions to the Program originate from and are +distributed by that particular Contributor. A Contribution 'originates' from a +Contributor if it was added to the Program by such Contributor itself or anyone +acting on such Contributor's behalf. Contributions do not include additions to +the Program which: (i) are separate modules of software distributed in conjunction +with the Program under their own license agreement, and (ii) are not derivative +works of the Program. +

+"Contributor" means any person or entity that distributes the Program. +

+"Licensed Patents " mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when combined +with the Program. +

+"Program" means the Contributions distributed in accordance with this Agreement. +

+"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. +

+ +

2. GRANT OF RIGHTS

+

+a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient +a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare +derivative works of, publicly display, publicly perform, distribute and sublicense the +Contribution of such Contributor, if any, and such derivative works, in source code +and object code form. +

+b) Subject to the terms of this Agreement, each Contributor hereby grants +Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed +Patents to make, use, sell, offer to sell, import and otherwise transfer the +Contribution of such Contributor, if any, in source code and object code form. +This patent license shall apply to the combination of the Contribution and the +Program if, at the time the Contribution is added by the Contributor, such addition +of the Contribution causes such combination to be covered by the Licensed +Patents. The patent license shall not apply to any other combinations which +include the Contribution. No hardware per se is licensed hereunder. +

+c) Recipient understands that although each Contributor grants the licenses to +its Contributions set forth herein, no assurances are provided by any Contributor +that the Program does not infringe the patent or other intellectual property +rights of any other entity. Each Contributor disclaims any liability to Recipient +for claims brought by any other entity based on infringement of intellectual +property rights or otherwise. As a condition to exercising the rights and licenses +granted hereunder, each Recipient hereby assumes sole responsibility to secure +any other intellectual property rights needed, if any. For example, if a third party +patent license is required to allow Recipient to distribute the Program, it is +Recipient's responsibility to acquire that license before distributing the Program. +

+d) Each Contributor represents that to its knowledge it has sufficient copyright +rights in its Contribution, if any, to grant the copyright license set forth in this +Agreement. +

+ +

3. REQUIREMENTS

+

+A Contributor may choose to distribute the Program in object code form +under its own license agreement, provided that: +

+a) it complies with the terms and conditions of this Agreement; and +

+b) its license agreement: +

+i) effectively disclaims on behalf of all Contributors all warranties and conditions, +express and implied, including warranties or conditions of title and +non-infringement, and implied warranties or conditions of merchantability and +fitness for a particular purpose; +

+ii) effectively excludes on behalf of all Contributors all liability for damages, +including direct, indirect, special, incidental and consequential damages, +such as lost profits; +

+iii) states that any provisions which differ from this Agreement are offered by +that Contributor alone and not by any other party; and +

+iv) states that source code for the Program is available from such Contributor, +and informs licensees how to obtain it in a reasonable manner on or through +a medium customarily used for software exchange. +

+When the Program is made available in source code form: +

+a) it must be made available under this Agreement; and +

+b) a copy of this Agreement must be included with each copy of the Program. +

+Contributors may not remove or alter any copyright notices contained within +the Program. +

+Each Contributor must identify itself as the originator of its Contribution, +if any, in a manner that reasonably allows subsequent Recipients to identify +the originator of the Contribution. +

+ +

4. COMMERCIAL DISTRIBUTION

+

+Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor +who includes the Program in a commercial product offering should do so +in a manner which does not create potential liability for other Contributors. +Therefore, if a Contributor includes the Program in a commercial product +offering, such Contributor ("Commercial Contributor") hereby agrees to +defend and indemnify every other Contributor ("Indemnified Contributor") +against any losses, damages and costs (collectively "Losses") arising from +claims, lawsuits and other legal actions brought by a third party against the +Indemnified Contributor to the extent caused by the acts or omissions of +such Commercial Contributor in connection with its distribution of the +Program in a commercial product offering. The obligations in this section +do not apply to any claims or Losses relating to any actual or alleged +intellectual property infringement. In order to qualify, an Indemnified +Contributor must: a) promptly notify the Commercial Contributor in writing +of such claim, and b) allow the Commercial Contributor to control, and +cooperate with the Commercial Contributor in, the defense and any related +settlement negotiations. The Indemnified Contributor may participate in +any such claim at its own expense. +

+For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those performance +claims and warranties, and if a court requires any other Contributor to pay +any damages as a result, the Commercial Contributor must pay those damages. +

+ +

5. NO WARRANTY

+

+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM +IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, +WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, +NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR +PURPOSE. Each Recipient is solely responsible for determining the +appropriateness of using and distributing the Program and assumes all +risks associated with its exercise of rights under this Agreement, including +but not limited to the risks and costs of program errors, compliance with +applicable laws, damage to or loss of data, programs or equipment, and +unavailability or interruption of operations. +

+ +

6. DISCLAIMER OF LIABILITY

+

+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER +RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY +RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGES. +

+ +

7. GENERAL

+

+If any provision of this Agreement is invalid or unenforceable under applicable +law, it shall not affect the validity or enforceability of the remainder of the +terms of this Agreement, and without further action by the parties hereto, +such provision shall be reformed to the minimum extent necessary to make +such provision valid and enforceable. +

+If Recipient institutes patent litigation against any entity (including a cross-claim +or counterclaim in a lawsuit) alleging that the Program itself (excluding +combinations of the Program with other software or hardware) infringes such +Recipient's patent(s), then such Recipient's rights granted under Section +2(b) shall terminate as of the date such litigation is filed. +

+All Recipient's rights under this Agreement shall terminate if it fails to comply +with any of the material terms or conditions of this Agreement and does +not cure such failure in a reasonable period of time after becoming aware +of such noncompliance. If all Recipient's rights under this Agreement +terminate, Recipient agrees to cease use and distribution of the Program +as soon as reasonably practicable. However, Recipient's obligations under +this Agreement and any licenses granted by Recipient relating to the +Program shall continue and survive. +

+Everyone is permitted to copy and distribute copies of this Agreement, but +in order to avoid inconsistency the Agreement is copyrighted and may only +be modified in the following manner. The Agreement Steward reserves the +right to publish new versions (including revisions) of this Agreement from +time to time. No one other than the Agreement Steward has the right to +modify this Agreement. The Eclipse Foundation is the initial Agreement +Steward. The Eclipse Foundation may assign the responsibility to serve as +the Agreement Steward to a suitable separate entity. Each new version of +the Agreement will be given a distinguishing version number. The Program +(including Contributions) may always be distributed subject to the version +of the Agreement under which it was received. In addition, after a new +version of the Agreement is published, Contributor may elect to distribute +the Program (including its Contributions) under the new version. Except as +expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights +or licenses to the intellectual property of any Contributor under this +Agreement, whether expressly, by implication, estoppel or otherwise. +All rights in the Program not expressly granted under this Agreement are +reserved. +

+This Agreement is governed by the laws of the State of New York +and the intellectual property laws of the United States of America. +No party to this Agreement will bring a legal action under this Agreement more +than one year after the cause of action arose. Each party waives its rights +to a jury trial in any resulting litigation. +

+ +

Export Control Classification Number (ECCN)

+

+As far as we know, the U.S. Export Control Classification Number (ECCN) for this software is 5D002. +However, for legal reasons, we can make no warranty that this information is correct. +For details, see also the Apache Software Foundation Export Classifications page. +

+ +
+ diff --git a/h2/src/docsrc/html/links.html b/h2/src/docsrc/html/links.html new file mode 100644 index 0000000..98cf0ca --- /dev/null +++ b/h2/src/docsrc/html/links.html @@ -0,0 +1,708 @@ + + + + + + +H2 In Use and Links + + + + + +
+ + +

Links

+

+If you want to add a link, please send it to the support email address or post it to the group. +

+ + Quotes
+ + Books
+ + Extensions
+ + Blog Articles, Videos
+ + Database Frontends / Tools
+ + Products and Projects
+ +

Quotes

+

+ +Quote: +"This is by far the easiest and fastest database that I have ever used. +Originally the web application that I am working on is using SQL server. +But, in less than 15 minutes I had H2 up and working with little recoding of the SQL. +Thanks..... " +

+ +

Books

+ +Seam In Action + +

Extensions

+ +Grails H2 Database Plugin +
+ +h2osgi: OSGi for the H2 Database +
+ +H2Sharp: ADO.NET interface for the H2 database engine +
+ +A spatial extension of the H2 database. + +

Blog Articles, Videos

+ +Youtube: Minecraft 1.7.3 / How to install Bukkit Server with xAuth and H2
+ +Analyzing CSVs with H2 in under 10 minutes (2009-12-07)
+ +Efficient sorting and iteration on large databases (2009-06-15)
+ +Porting Flexive to the H2 Database (2008-12-05)
+ +H2 Database with GlassFish (2008-11-24)
+ +H2 Database - Performance Tracing (2008-04-30)
+ +Open Source Databases Comparison (2007-09-11)
+ +The Codist: The Open Source Frameworks I Use (2007-07-23)
+ +The Codist: SQL Injections: How Not To Get Stuck (2007-05-08)
+ +David Coldrick's Weblog: New Version of H2 Database Released (2007-01-06)
+ +The Codist: Write Your Own Database, Again (2006-11-13)
+ +

Project Pages

+ +Ohloh
+ +Freshmeat Project Page
+ +Wikipedia
+ +Java Source Net
+ +Linux Package Manager
+ +

Database Frontends / Tools

+ +

+Dataflyer
+A tool to browse databases and export data. +

+ +

+DB Solo
+SQL query tool. +

+ +

+DbVisualizer
+Database tool. +

+ +

+Execute Query
+Database utility written in Java. +

+ +

+Flyway
+The agile database migration framework for Java. +

+ +

+[fleXive]
+JavaEE 5 open source framework for the development of complex and evolving (web-)applications. +

+ +

+JDBC Console
+This small webapp gives an ability to execute SQL against datasources bound in container's JNDI. +Based on H2 Console. +

+ +

+HenPlus
+HenPlus is a SQL shell written in Java. +

+ +

+JDBC lint
+Helps write correct and efficient code when using the JDBC API. +

+ +

+OpenOffice
+Base is OpenOffice.org's database application. It provides access to relational data sources. +

+ +

+RazorSQL
+An SQL query tool, database browser, SQL editor, and database administration tool. +

+ +

+SQL Developer
+Universal Database Frontend. +

+ +

+SQL Workbench/J
+Free DBMS-independent SQL tool. +

+ +

+SQuirreL SQL Client
+Graphical tool to view the structure of a database, browse the data, issue SQL commands etc. +

+ +

+SQuirreL DB Copy Plugin
+Tool to copy data from one database to another. +

+ +

Products and Projects

+ +

+AccuProcess
+Visual business process modeling and simulation software for business users. +

+ +

+Adeptia BPM
+A Business Process Management (BPM) suite to quickly and easily automate business processes and workflows. +

+ +

+Adeptia Integration
+Process-centric, services-based application integration suite. +

+ +

+Aejaks
+A server-side scripting environment to build AJAX enabled web applications. +

+ +

+Axiom Stack
+A web framework that let's you write dynamic web applications with Zen-like simplicity. +

+ +

+Apache Cayenne
+Open source persistence framework providing object-relational mapping (ORM) and remoting services. +

+ +

+Apache Jackrabbit
+Open source implementation of the Java Content Repository API (JCR). +

+ +

+Apache OpenJPA
+Open source implementation of the Java Persistence API (JPA). +

+ +

+AppFuse
+Helps building web applications. +

+ +

+BGBlitz
+The Swiss army knife of Backgammon. +

+ +

+Bonita
+Open source workflow solution for handing long-running, user-oriented processes +providing out of the box workflow and business process management features. +

+ +

+Bookmarks Portlet
+JSR 168 compliant bookmarks management portlet application. +

+ +

+Claros inTouch
+Ajax communication suite with mail, addresses, notes, IM, and rss reader. +

+ +

+CrashPlan PRO Server
+Easy and cross platform backup solution for business and service providers. +

+ +

+DataNucleus
+Java persistent objects. +

+ +

+DbUnit
+A JUnit extension (also usable with Ant) targeted for database-driven projects. +

+ +

+DiffKit
+DiffKit is a tool for comparing two tables of data, field-by-field. +DiffKit is like the Unix diff utility, but for tables instead of lines of text. +

+ +

+Dinamica Framework
+Ajax/J2EE framework for RAD development (mainly oriented toward hispanic markets). +

+ +

+District Health Information Software 2 (DHIS)
+The DHIS 2 is a tool for collection, validation, analysis, and presentation of aggregate statistical data, +tailored (but not limited) to integrated health information management activities. +

+ +

+Ebean ORM Persistence Layer
+Open source Java Object Relational Mapping tool. +

+ +

+Eclipse CDO
+The CDO (Connected Data Objects) Model Repository is a distributed shared model framework for EMF models, +and a fast server-based O/R mapping solution. +

+ +

+Fabric3
+Fabric3 is a project implementing a federated service network based on the Service Component Architecture specification (http://www.osoa.org). +

+ +

+FIT4Data
+A testing framework for data management applications built on the Java implementation of FIT. +

+ +

+Flux
+Java job scheduler, file transfer, workflow, and BPM. +

+ +

+GeoServer
+GeoServer is a Java-based software server that allows users to view and edit geospatial data. Using open standards set forth by the Open Geospatial Consortium (OGC), GeoServer allows for great flexibility in map creation and data sharing. +

+ +

+GBIF Integrated Publishing Toolkit (IPT)
+The GBIF IPT is an open source, Java based web application that connects and serves +three types of biodiversity data: taxon primary occurrence data, +taxon checklists and general resource metadata. +

+ +

+GNU Gluco Control
+Helps you to manage your diabetes. +

+ +

+Golden T Studios
+Fun-to-play games with a simple interface. +

+ +

+GridGain
+GridGain is easy to use Cloud Application Platform that enables development of +highly scalable distributed Java and Scala applications +that auto-scale on any grid or cloud infrastructure. +

+ +

+Group Session
+Open source web groupware. +

+ +

+HA-JDBC
+High-Availability JDBC: A JDBC proxy that provides light-weight, transparent, fault tolerant clustering capability to any underlying JDBC driver. +

+ +

+Hibernate
+Relational persistence for idiomatic Java (O-R mapping tool). +

+ +

+Hibicius
+Online Banking Client for the HBCI protocol. +

+ +

+ImageMapper
+ImageMapper frees users from having to use file browsers to view their images. +They get fast access to images and easy cataloguing of them via a user friendly +interface. +

+ +

+JAMWiki
+Java-based Wiki engine. +

+ +

+Jaspa
+Java Spatial. Jaspa potentially brings around 200 spatial functions. +

+ +

+Java Simon
+Simple Monitoring API. +

+ +

+JBoss jBPM
+A platform for executable process languages ranging from business process management (BPM) over workflow to service orchestration. +

+ +

+JBoss Jopr
+An enterprise management solution for JBoss middleware projects and other application technologies. +

+ +

+JGeocoder
+Free Java geocoder. Geocoding is the process of estimating a latitude and longitude for a given location. +

+ +

+JGrass
+Java Geographic Resources Analysis Support System. +Free, multi platform, open source GIS based on the GIS framework of uDig. +

+ +

+Jena
+Java framework for building Semantic Web applications. +

+ +

+JMatter
+Framework for constructing workgroup business applications based on the Naked Objects Architectural Pattern. +

+ +

+jOOQ (JOOQ Object Oriented Querying)
+jOOQ is a fluent API for typesafe SQL query construction and execution +

+ +

+Liftweb
+A Scala-based, secure, developer friendly web framework. +

+ +

+LiquiBase
+A tool to manage database changes and refactorings. +

+ +

+Luntbuild
+Build automation and management tool. +

+ +

+localdb
+A tool that locates the full file path of the folder containing the database files. +

+ +

+Magnolia
+Microarray Data Management and Export System for PFGRC +(Pathogen Functional Genomics Resource Center) Microarrays. +

+ +

+MiniConnectionPoolManager
+A lightweight standalone JDBC connection pool manager. +

+ +

+Mr. Persister
+Simple, small and fast object relational mapping. +

+ +

+Myna Application Server
+Java web app that provides dynamic web content and Java libraries access from JavaScript. +

+ +

+MyTunesRss
+MyTunesRSS lets you listen to your music wherever you are. +

+ +

+NCGC CurveFit
+From: NIH Chemical Genomics Center, National Institutes of Health, USA. +An open source application in the life sciences research field. This +application handles chemical structures and biological responses of +thousands of compounds with the potential to handle million+ compounds. +It utilizes an embedded H2 database to enable flexible query/retrieval +of all data including advanced chemical substructure and similarity +searching. The application highlights an automated curve fitting and +classification algorithm that outperforms commercial packages in the +field. Commercial alternatives are typically small desktop software +that handle a few dose response curves at a time. A couple of +commercial packages that do handle several thousand curves are very +expensive tools (>60k USD) that require manual curation of analysis +by the user; require a license to Oracle; lack advanced +query/retrieval; and the ability to handle chemical structures. +

+ +

+Nuxeo
+Standards-based, open source platform for building ECM applications. +

+ +

+nWire
+Eclipse plug-in which expedites Java development. +It's main purpose is to help developers find code quicker and easily +understand how it relates to the rest of the application, thus, +understand the application structure. +

+ +

+Ontology Works
+This company provides semantic technologies including deductive +information repositories (the Ontology Works Knowledge Servers), +semantic information fusion and semantic federation of legacy +databases, ontology-based domain modeling, and management of the +distributed enterprise. +

+ +

+Ontoprise OntoBroker
+SemanticWeb-Middleware. It supports all W3C Semantic Web recommendations: +OWL, RDF, RDFS, SPARQL, and F-Logic. +

+ +

+Open Anzo
+Semantic Application Server. +

+ +

+OpenGroove
+OpenGroove is a groupware program that allows users to synchronize data. +

+ +

+OpenSocial Development Environment (OSDE)
+Development tool for OpenSocial application. +

+ +

+Orion
+J2EE Application Server. +

+ +

+P5H2
+A library for the Processing programming language and environment. +

+ +

+Phase-6
+A computer based learning software. +

+ +

+Pickle
+Pickle is a Java library containing classes for persistence, concurrency, and logging. +

+ +

+Piman
+Water treatment projects data management. +

+ +

+PolePosition
+Open source database benchmark. +

+ +

+Poormans
+Very basic CMS running as a SWT application and generating static html pages. +

+ +

+Railo
+Railo is an alternative engine for the Cold Fusion Markup Language, that compiles code +programmed in CFML into Java bytecode and executes it on a servlet engine. +

+ +

+Razuna
+Open source Digital Asset Management System with integrated Web Content Management. +

+ +

+RIFE
+A full-stack web application framework with tools and APIs to implement most common web features. +

+ +

+Sava
+Open-source web-based content management system. +

+ +

+Scriptella
+ETL (Extract-Transform-Load) and script execution tool. +

+ +

+Sesar
+Dependency Injection Container with Aspect Oriented Programming. +

+ +

+SemmleCode
+Eclipse plugin to help you improve software quality. +

+ +

+SeQuaLite
+A free, light-weight, java data access framework. +

+ +

+ShapeLogic
+Toolkit for declarative programming, image processing and computer vision. +

+ +

+Shellbook
+Desktop publishing application. +

+ +

+Signsoft intelliBO
+Persistence middleware supporting the JDO specification. +

+ +

+SimpleORM
+Simple Java Object Relational Mapping. +

+ +

+SymmetricDS
+A web-enabled, database independent, data synchronization/replication software. +

+ +

+SmartFoxServer
+Platform for developing multiuser applications and games with Macromedia Flash. +

+ +

+Social Bookmarks Friend Finder
+A GUI application that allows you to find users with similar bookmarks to the user specified (for delicious.com). +

+ +

+sormula
+Simple object relational mapping. +

+ +

+Springfuse
+Code generation For Spring, Spring MVC & Hibernate. +

+ +

+SQLOrm
+Java Object Relation Mapping. +

+ +

+StelsCSV and StelsXML
+StelsCSV is a CSV JDBC type 4 driver that allows to perform SQL queries and other JDBC operations on text files. +StelsXML is a XML JDBC type 4 driver that allows to perform SQL queries and other JDBC operations on XML files. +Both use H2 as the SQL engine. +

+ +

+StorYBook
+A summary-based tool for novelist and script writers. It helps to keep the overview over the various traces a story has. +

+ +

+StreamCruncher
+Event (stream) processing kernel. +

+ +

+SUSE Manager, part of Linux Enterprise Server 11
+The SUSE Manager + +eases the burden of compliance with regulatory requirements and corporate policies. +

+ +

+Tune Backup
+Easy-to-use backup solution for your iTunes library. +

+ +

+TimeWriter
+TimeWriter is a very flexible program for time administration / time tracking. +The older versions used dBase tables. +The new version 5 is completely rewritten, now using the H2 database. +TimeWriter is delivered in Dutch and English. +

+ +

+weblica
+Desktop CMS. +

+ +

+Web of Web
+Collaborative and realtime interactive media platform for the web. +

+ +

+Werkzeugkasten
+Minimum Java Toolset. +

+ +

+VPDA
+View providers driven applications is a Java based application framework +for building applications composed from server components - view providers. +

+ +

+Volunteer database
+A database front end to register volunteers, partnership and donation for a Non Profit organization. +

+ +
+ diff --git a/h2/src/docsrc/html/main.html b/h2/src/docsrc/html/main.html new file mode 100644 index 0000000..ea060a9 --- /dev/null +++ b/h2/src/docsrc/html/main.html @@ -0,0 +1,46 @@ + + + + + + + + +H2 Database Engine + + + + + +
+ + +

H2 Database Engine

+

+Welcome to H2, the free Java SQL database engine. +

+ +

+Quickstart +
+Get a fast overview. +

+ +

+Tutorial +
+Go through the samples. +

+ +

+Features +
+See what this database can do and how to use these features. +

+ +
+ diff --git a/h2/src/docsrc/html/mainWeb.html b/h2/src/docsrc/html/mainWeb.html new file mode 100644 index 0000000..07f12b2 --- /dev/null +++ b/h2/src/docsrc/html/mainWeb.html @@ -0,0 +1,139 @@ + + + + + + + + +H2 Database Engine + + + + + + + +
+ + +

H2 Database Engine

+

+Welcome to H2, the Java SQL database. The main features of H2 are: +

+
    +
  • Very fast, open source, JDBC API +
  • Embedded and server modes; in-memory databases +
  • Browser based Console application +
  • Small footprint: around 2.5 MB jar file size +
+ + + + + + + + + + + + + + +
+ + + + + +
+

Download

+ Version ${version} (${versionDate}) +
+ Download this database + + Windows Installer (6.7 MB) +
+ Download this database + + All Platforms (zip, 9.5 MB) +
+ All Downloads +
+
    + + +
+

Support

+

+ Stack Overflow (tag H2)

+ Google Group

+ For non-technical issues, use:
+ +

+
+
+

Features

+
    +
  • Very fast, open source, JDBC API
  • +
  • Embedded and server modes; disk-based or in-memory databases
  • +
  • Transaction support, multi-version concurrency
  • +
  • Browser based Console application
  • +
  • Encrypted databases
  • +
  • Fulltext search
  • +
  • Pure Java with small footprint: around 2.5 MB jar file size
  • +
  • ODBC driver
  • +
+
+ + +
+

News

+

+ Newsfeeds: + Full text (Atom) + or Header only (RSS). +

+ Email Newsletter: Subscribe to + + H2 Database News (Google account required) + to get informed about new releases. + Your email address is only used in this context. +

+
+
 
+ + +
+

Contribute

+

+ You can contribute to the development of H2 by sending feedback and bug + reports, or translate the H2 Console application (for details, start the H2 Console + and select Options / Translate). + To donate money, click on the PayPal button below. You will be listed as a supporter: +

+
+

+ + + +

+
+
+
+ +
diff --git a/h2/src/docsrc/html/migration-to-v2.html b/h2/src/docsrc/html/migration-to-v2.html new file mode 100644 index 0000000..915bccb --- /dev/null +++ b/h2/src/docsrc/html/migration-to-v2.html @@ -0,0 +1,187 @@ + + + + + + + +Migration to 2.0 + + + + + +
+ + +

Contents

+ + Introduction
+ + Upgrading
+ + File Format
+ + Data types
+ + Identity columns and sequences
+ + INFORMATION_SCHEMA
+ + General
+ +

Introduction

+ +

+Between version 1.4.200 and version 2.0.202 there have been considerable changes, such that a simple update is +not possible. +

+ +

+It would have been nice to write some kind of migration tool, or auto-detect the file and upgrade. Unfortunately, this +is purely a volunteer-run project, so this is just the way it has to be. There exists a migration tool H2MigrationTool available +in GitHub, but it hasn't been tested by our team. Use at +your own risk. +

+ +

Upgrading

+ +

+The official way to upgrade is to export it into SQL script with the +SCRIPT command +USING YOUR CURRENT VERSION OF H2. +

+ +

+Then create a fresh database USING THE NEW VERSION OF H2, then perform a +RUNSCRIPT to load your data. +You may need to specify FROM_1X flag, see documentation of this command for details. +

+ +

MVStore file format

+ +

+The MVStore file format we use (i.e. the default) is still mostly the same, but some subtle changes have been made +to the undo logs, +for the purposes of improving crash safety and also read/write performance. +

+ +

Data types

+ +

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

+ +

+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_000_000_000 bytes. For larger values use +BINARY LARGE OBJECT +

+ +

+NUMERIC / DECIMAL / DEC without parameters +now have scale 0. For a variable-scale data type see +DECFLOAT. +Negative scale isn't allowed for these data types any more. +The maximum precision is now 100,000. +

+ +

+ENUM values now have 1-based ordinal numbers. +

+ +

+Arrays are now typed. +Arrays with mixed types of elements aren't supported. +In some cases they can be replaced with a new ROW +data type. +

+ +

+All non-standard data types, with exception for TINYINT, JAVA_OBJECT, ENUM, GEOMETRY, JSON, and UUID are deprecated. +

+ +

Identity columns and sequences

+ +

+Various legacy vendor-specific declarations and expressions are deprecated +and may not work at all depending on compatibility mode. +

+ +

+Identity columns should be normally declared with GENERATED BY DEFAULT AS IDENTITY or GENERATED ALWAYS AS IDENTITY +clauses, options may also be specified. +GENERATED ALWAYS AS IDENTITY columns cannot be assigned to a user-provided value +unless OVERRIDING SYSTEM VALUE is specified. +

+ +

+NULL cannot be specified as a value for IDENTITY column to force identity generation +(with exception for some compatibility modes). +Use DEFAULT or simply exclude this column from insert column list. +

+ +

+IDENTITY() and SCOPE_IDENTITY() aren't available in Regular mode. If you need to get a generated value, +you need to use data change delta tables +or Statement.getGeneratedKeys(). +

+ +

+Undocumented Oracle-style .NEXTVAL and .CURRVAL expressions are restricted to Oracle compatibility mode. +Other functions are deprecated for Regular mode. +Use sequence value expression instead. +

+ +

INFORMATION_SCHEMA

+ +

+INFORMATION_SCHEMA in H2 is now compliant with the SQL Standard and other database systems, +but it isn't compliant with previous versions of H2. +You may need to update your queries. +

+ +

General

+ +

+There are a lot more SQL keywords now. Many SQL statements feature far better support of SQL-Standard behaviour. +There is a NON_KEYWORDS setting that +can be used as a temporary workaround if your application uses them as unquoted identifiers. +

+ +

+Numeric and boolean values aren't comparable. It means you need to use TRUE, FALSE, or UNKNOWN (NULL) +as boolean literals. 1 and 0 don't work any more (with exception for some compatibility modes). +

+ +

+Some other non-standard SQL syntax has been restricted to related compatibility modes. +Since H2 2.0.204 there is a LEGACY compatibility mode that provides some limited compatibility with previous versions. +

+ +

+Various deprecated grammar elements are marked in red in documentation. Please, avoid their usage. +

+ +

+Migrating an old database to the new version works most of the times. However, there are a couple of important changes in the new version to keep in mind: +

+ +
    +
  • Oracle-style units were never supported officially without being in Oracle compatibility mode, although some worked before. For example, the length of the VARCHAR datatype cannot be more specified using CHAR but CHARACTERS or OCTETS. CHAR and BYTE need to be used in Oracle compatibility mode. +
  • IDENTITY syntax changed when type is specified: if the type for IDENTITY is specified, then the clause needs to be expanded as INTEGER GENERATED ALWAYS AS IDENTITY. Using just INTEGER IDENTITY is no more working. +
  • LOG connection setting removed: PageStore was removed from H2 so the "LOG=0" setting at the end of the URL (like +"jdbc:h2:file:/tmp/test;LOG=0") is no longer available. +
+ +
diff --git a/h2/src/docsrc/html/mvstore.html b/h2/src/docsrc/html/mvstore.html new file mode 100644 index 0000000..a5fd229 --- /dev/null +++ b/h2/src/docsrc/html/mvstore.html @@ -0,0 +1,749 @@ + + + + + + + +MVStore + + + + + +
+ + +

MVStore

+ + Overview
+ + Example Code
+ + Store Builder
+ + R-Tree
+ + + Features
+- Maps
+- Versions
+- Transactions
+- In-Memory Performance and Usage
+- Pluggable Data Types
+- BLOB Support
+- R-Tree and Pluggable Map Implementations
+- Concurrent Operations and Caching
+- Log Structured Storage
+- Off-Heap and Pluggable Storage
+- File System Abstraction, File Locking and Online Backup
+- Encrypted Files
+- Tools
+- Exception Handling
+- Storage Engine for H2
+ + + File Format
+ + + Similar Projects and Differences to Other Storage Engines
+ + Current State
+ + Requirements
+ +

Overview

+

+The MVStore is a persistent, log structured key-value store. +It is used as default storage subsystem of H2, +but it can also be used directly within an application, without using JDBC or SQL. +

+
  • MVStore stands for "multi-version store". +
  • Each store contains a number of maps that can be accessed using the java.util.Map interface. +
  • Both file-based persistence and in-memory operation are supported. +
  • It is intended to be fast, simple to use, and small. +
  • Concurrent read and write operations are supported. +
  • Transactions are supported (including concurrent transactions and 2-phase commit). +
  • The tool is very modular. + It supports pluggable data types and serialization, + pluggable storage (to a file, to off-heap memory), + pluggable map implementations (B-tree, R-tree, concurrent B-tree currently), + BLOB storage, + and a file system abstraction to support encrypted files and zip files. +
+ +

Example Code

+

+The following sample code shows how to use the tool: +

+
+import org.h2.mvstore.*;
+
+// open the store (in-memory if fileName is null)
+MVStore s = MVStore.open(fileName);
+
+// create/get the map named "data"
+MVMap<Integer, String> map = s.openMap("data");
+
+// add and read some data
+map.put(1, "Hello World");
+System.out.println(map.get(1));
+
+// close the store (this will persist changes)
+s.close();
+
+ +

Store Builder

+

+The MVStore.Builder provides a fluid interface +to build a store if configuration options are needed. +Example usage: +

+
+MVStore s = new MVStore.Builder().
+    fileName(fileName).
+    encryptionKey("007".toCharArray()).
+    compress().
+    open();
+
+

+The list of available options is: +

+
  • autoCommitBufferSize: the size of the write buffer. +
  • autoCommitDisabled: to disable auto-commit. +
  • backgroundExceptionHandler: a handler for + exceptions that could occur while writing in the background. +
  • cacheSize: the cache size in MB. +
  • compress: compress the data when storing + using a fast algorithm (LZF). +
  • compressHigh: compress the data when storing + using a slower algorithm (Deflate). +
  • encryptionKey: the key for file encryption. +
  • fileName: the name of the file, for file based stores. +
  • fileStore: the storage implementation to use. +
  • pageSplitSize: the point where pages are split. +
  • readOnly: open the file in read-only mode. +
+ +

R-Tree

+

+The MVRTreeMap is an R-tree implementation +that supports fast spatial queries. It can be used as follows: +

+
+// create an in-memory store
+MVStore s = MVStore.open(null);
+
+// open an R-tree map
+MVRTreeMap<String> r = s.openMap("data",
+        new MVRTreeMap.Builder<String>());
+
+// add two key-value pairs
+// the first value is the key id (to make the key unique)
+// then the min x, max x, min y, max y
+r.add(new SpatialKey(0, -3f, -2f, 2f, 3f), "left");
+r.add(new SpatialKey(1, 3f, 4f, 4f, 5f), "right");
+
+// iterate over the intersecting keys
+Iterator<SpatialKey> it =
+        r.findIntersectingKeys(new SpatialKey(0, 0f, 9f, 3f, 6f));
+for (SpatialKey k; it.hasNext();) {
+    k = it.next();
+    System.out.println(k + ": " + r.get(k));
+}
+s.close();
+
+

+The default number of dimensions is 2. To use a different number of dimensions, +call new MVRTreeMap.Builder<String>().dimensions(3). +The minimum number of dimensions is 1, the maximum is 32. +

+ +

Features

+ +

Maps

+

+Each store contains a set of named maps. +A map is sorted by key, and supports the common lookup operations, +including access to the first and last key, iterate over some or all keys, and so on. +

+Also supported, and very uncommon for maps, is fast index lookup: +the entries of the map can be efficiently accessed like a random-access list +(get the entry at the given index), and the index of a key can be calculated efficiently. +That also means getting the median of two keys is very fast, +and a range of keys can be counted very quickly. +The iterator supports fast skipping. +This is possible because internally, each map is organized in the form of a counted B+-tree. +

+In database terms, a map can be used like a table, where the key of the map is the primary key of the table, +and the value is the row. A map can also represent an index, where the key of the map is the key +of the index, and the value of the map is the primary key of the table (for non-unique indexes, +the key of the map must also contain the primary key). +

+ +

Versions

+

+A version is a snapshot of all the data of all maps at a given point in time. +Creating a snapshot is fast: only those pages that are changed after a snapshot are copied. +This behavior is also called COW (copy on write). +Old versions are readable. +Rollback to an old version is supported. +

+The following sample code show how to create a store, open a map, add some data, +and access the current and an old version: +

+
+// create/get the map named "data"
+MVMap<Integer, String> map = s.openMap("data");
+
+// add some data
+map.put(1, "Hello");
+map.put(2, "World");
+
+// get the current version, for later use
+long oldVersion = s.getCurrentVersion();
+
+// from now on, the old version is read-only
+s.commit();
+
+// more changes, in the new version
+// changes can be rolled back if required
+// changes always go into "head" (the newest version)
+map.put(1, "Hi");
+map.remove(2);
+
+// access the old data (before the commit)
+MVMap<Integer, String> oldMap =
+        map.openVersion(oldVersion);
+
+// print the old version (can be done
+// concurrently with further modifications)
+// this will print "Hello" and "World":
+System.out.println(oldMap.get(1));
+System.out.println(oldMap.get(2));
+
+// print the newest version ("Hi")
+System.out.println(map.get(1));
+
+ +

Transactions

+

+To support multiple concurrent open transactions, a transaction utility is included, +the TransactionStore. +The tool supports "read committed" transaction isolation +with savepoints, two-phase commit, and other features typically available in a database. +There is no limit on the size of a transaction +(the log is written to disk for large or long running transactions). +

+Internally, this utility stores the old versions of changed entries in a separate map, similar to a transaction log, +except that entries of a closed transaction are removed, and the log is usually not stored for short transactions. +For common use cases, the storage overhead of this utility is very small compared to the overhead of a regular transaction log. +

+ +

In-Memory Performance and Usage

+

+Performance of in-memory operations is about 50% slower than +java.util.TreeMap. +

+The memory overhead for large maps is slightly better than for the regular +map implementations, but there is a higher overhead per map. +For maps with less than about 25 entries, the regular map implementations need less memory. +

+If no file name is specified, the store operates purely in memory. +Except for persisting data, all features are supported in this mode +(multi-versioning, index lookup, R-tree and so on). +If a file name is specified, all operations occur in memory (with the same +performance characteristics) until data is persisted. +

+As in all map implementations, keys need to be immutable, that means +changing the key object after an entry has been added is not allowed. +If a file name is specified, the value may also not be changed after +adding an entry, because it might be serialized +(which could happen at any time when autocommit is enabled). +

+ +

Pluggable Data Types

+

+Serialization is pluggable. The default serialization currently supports many common data types, +and uses Java serialization for other objects. The following classes are currently directly supported: +Boolean, Byte, Short, Character, Integer, Long, Float, Double, BigInteger, BigDecimal, +String, UUID, Date and arrays (both primitive arrays and object arrays). +For serialized objects, the size estimate is adjusted using an exponential moving average. +

+Parameterized data types are supported +(for example one could build a string data type that limits the length). +

+The storage engine itself does not have any length limits, so that keys, values, +pages, and chunks can be very big (as big as fits in memory). +Also, there is no inherent limit to the number of maps and chunks. +Due to using a log structured storage, there is no special case handling for large keys or pages. +

+ +

BLOB Support

+

+There is a mechanism that stores large binary objects by splitting them into smaller blocks. +This allows to store objects that don't fit in memory. +Streaming as well as random access reads on such objects are supported. +This tool is written on top of the store, using only the map interface. +

+ +

R-Tree and Pluggable Map Implementations

+

+The map implementation is pluggable. +In addition to the default MVMap (multi-version map), +there is a multi-version R-tree map implementation for spatial operations. +

+ +

Concurrent Operations and Caching

+

+Concurrent reads and writes are supported. +All such read operations can occur in parallel. Concurrent reads from the page cache, +as well as concurrent reads from the file system are supported. +Write operations first read the relevant pages from disk to memory +(this can happen concurrently), and only then modify the data. +The in-memory parts of write operations are synchronized. +Writing changes to the file can occur concurrently to modifying the data, +as writing operates on a snapshot. +

+Caching is done on the page level. +The page cache is a concurrent LIRS cache, which should be resistant against scan operations. +

+For fully scalable concurrent write operations to a map (in-memory and to disk), +the map could be split into multiple maps in different stores ('sharding'). +The plan is to add such a mechanism later when needed. +

+ +

Log Structured Storage

+

+Internally, changes are buffered in memory, and once enough changes have accumulated, +they are written in one continuous disk write operation. +Compared to traditional database storage engines, +this should improve write performance for file systems and storage systems +that do not efficiently support small random writes, such as Btrfs, as well as SSDs. +(According to a test, write throughput of a common SSD increases with write block size, +until a block size of 2 MB, and then does not further increase.) +By default, changes are automatically written when more than a number of pages are modified, +and once every second in a background thread, even if only little data was changed. +Changes can also be written explicitly by calling commit(). +

+When storing, all changed pages are serialized, +optionally compressed using the LZF algorithm, +and written sequentially to a free area of the file. +Each such change set is called a chunk. +All parent pages of the changed B-trees are stored in this chunk as well, +so that each chunk also contains the root of each changed map +(which is the entry point for reading this version of the data). +There is no separate index: all data is stored as a list of pages. +Per store, there is one additional map that contains the metadata (the list of +maps, where the root page of each map is stored, and the list of chunks). +

+There are usually two write operations per chunk: +one to store the chunk data (the pages), and one to update the file header (so it points to the latest chunk). +If the chunk is appended at the end of the file, the file header is only written at the end of the chunk. +There is no transaction log, no undo log, +and there are no in-place updates (however, unused chunks are overwritten by default). +

+Old data is kept for at least 45 seconds (configurable), +so that there are no explicit sync operations required to guarantee data consistency. +An application can also sync explicitly when needed. +To reuse disk space, the chunks with the lowest amount of live data are compacted +(the live data is stored again in the next chunk). +To improve data locality and disk space usage, the plan is to automatically defragment and compact data. +

+Compared to traditional storage engines (that use a transaction log, undo log, and main storage area), +the log structured storage is simpler, more flexible, and typically needs less disk operations per change, +as data is only written once instead of twice or 3 times, and because the B-tree pages are +always full (they are stored next to each other) and can be easily compressed. +But temporarily, disk space usage might actually be a bit higher than for a regular database, +as disk space is not immediately re-used (there are no in-place updates). +

+ +

Off-Heap and Pluggable Storage

+

+Storage is pluggable. Unless pure in-memory operation is used, the default storage is to a single file. +

+

+An off-heap storage implementation is available. This storage keeps the data in the off-heap memory, +meaning outside of the regular garbage collected heap. This allows to use very large in-memory +stores without having to increase the JVM heap, which would increase Java garbage collection +pauses a lot. Memory is allocated using ByteBuffer.allocateDirect. +One chunk is allocated at a time (each chunk is usually a few MB large), so that +allocation cost is low. To use the off-heap storage, call: +

+
+OffHeapStore offHeap = new OffHeapStore();
+MVStore s = new MVStore.Builder().
+        fileStore(offHeap).open();
+
+ +

File System Abstraction, File Locking and Online Backup

+

+The file system is pluggable. The same file system abstraction is used as H2 uses. +The file can be encrypted using a encrypting file system wrapper. +Other file system implementations support reading from a compressed zip or jar file. +The file system abstraction closely matches the Java 7 file system API. +

+

+Each store may only be opened once within a JVM. +When opening a store, the file is locked in exclusive mode, so that +the file can only be changed from within one process. +Files can be opened in read-only mode, in which case a shared lock is used. +

+

+The persisted data can be backed up at any time, +even during write operations (online backup). +To do that, automatic disk space reuse needs to be first disabled, so that +new data is always appended at the end of the file. +Then, the file can be copied. The file handle is available to the application. +It is recommended to use the utility class FileChannelInputStream to do this. +For encrypted databases, both the encrypted (raw) file content, +as well as the clear text content, can be backed up. +

+ +

Encrypted Files

+

+File encryption ensures the data can only be read with the correct password. +Data can be encrypted as follows: +

+
+MVStore s = new MVStore.Builder().
+    fileName(fileName).
+    encryptionKey("007".toCharArray()).
+    open();
+
+

+

+The following algorithms and settings are used: +

+
  • The password char array is cleared after use, + to reduce the risk that the password is stolen + even if the attacker has access to the main memory. +
  • The password is hashed according to the PBKDF2 standard, + using the SHA-256 hash algorithm. +
  • The length of the salt is 64 bits, + so that an attacker can not use a pre-calculated password hash table (rainbow table). + It is generated using a cryptographically secure random number generator. +
  • To speed up opening an encrypted stores on Android, + the number of PBKDF2 iterations is 10. + The higher the value, the better the protection against brute-force password cracking attacks, + but the slower is opening a file. +
  • The file itself is encrypted using the standardized disk encryption mode XTS-AES. + Only little more than one AES-128 round per block is needed. +
+ +

Tools

+

+There is a tool, the MVStoreTool, to dump the contents of a file. +

+ +

Exception Handling

+

+This tool does not throw checked exceptions. +Instead, unchecked exceptions are thrown if needed. +The error message always contains the version of the tool. +The following exceptions can occur: +

+
  • IllegalStateException if a map was already closed or + an IO exception occurred, for example if the file was locked, is already closed, + could not be opened or closed, if reading or writing failed, + if the file is corrupt, or if there is an internal error in the tool. + For such exceptions, an error code is added + so that the application can distinguish between different error cases. +
  • IllegalArgumentException if a method was called with an illegal argument. +
  • UnsupportedOperationException if a method was called that is not supported, + for example trying to modify a read-only map. +
  • ConcurrentModificationException if a map is modified concurrently. +
+ +

Storage Engine for H2

+

+For H2 version 1.4 and newer, the MVStore is the default storage engine +(supporting SQL, JDBC, transactions, MVCC, and so on). +For older versions, append +;MV_STORE=TRUE +to the database URL. +

+ +

File Format

+

+The data is stored in one file. +The file contains two file headers (for safety), and a number of chunks. +The file headers are one block each; a block is 4096 bytes. +Each chunk is at least one block, but typically 200 blocks or more. +Data is stored in the chunks in the form of a +log structured storage. +There is one chunk for every version. +

+
+[ file header 1 ] [ file header 2 ] [ chunk ] [ chunk ] ... [ chunk ]
+
+

+Each chunk contains a number of B-tree pages. +As an example, the following code: +

+
+MVStore s = MVStore.open(fileName);
+MVMap<Integer, String> map = s.openMap("data");
+for (int i = 0; i < 400; i++) {
+    map.put(i, "Hello");
+}
+s.commit();
+for (int i = 0; i < 100; i++) {
+    map.put(i, "Hi");
+}
+s.commit();
+s.close();
+
+

+will result in the following two chunks (excluding metadata): +

+

+Chunk 1:
+- Page 1: (root) node with 2 entries pointing to page 2 and 3
+- Page 2: leaf with 140 entries (keys 0 - 139)
+- Page 3: leaf with 260 entries (keys 140 - 399)
+

+

+Chunk 2:
+- Page 4: (root) node with 2 entries pointing to page 5 and 3
+- Page 5: leaf with 140 entries (keys 0 - 139)
+

+

+That means each chunk contains the changes of one version: +the new version of the changed pages and the parent pages, recursively, up to the root page. +Pages in subsequent chunks refer to pages in earlier chunks. +

+ +

File Header

+

+There are two file headers, which normally contain the exact same data. +But once in a while, the file headers are updated, and writing could partially fail, +which could corrupt a header. That's why there is a second header. +Only the file headers are updated in this way (called "in-place update"). +The headers contain the following data: +

+
+H:2,block:2,blockSize:1000,chunk:7,created:1441235ef73,format:1,version:7,fletcher:3044e6cc
+
+

+The data is stored in the form of a key-value pair. +Each value is stored as a hexadecimal number. The entries are: +

+
  • H: The entry "H:2" stands for the H2 database. +
  • block: The block number where one of the newest chunks starts + (but not necessarily the newest). +
  • blockSize: The block size of the file; currently always hex 1000, which is decimal 4096, + to match the disk sector + length of modern hard disks. +
  • chunk: The chunk id, which is normally the same value as the version; + however, the chunk id might roll over to 0, while the version doesn't. +
  • created: The number of milliseconds since 1970 when the file was created. +
  • format: The file format number. Currently 1. +
  • version: The version number of the chunk. +
  • fletcher: The + Fletcher-32 checksum of the header. +
+

+When opening the file, both headers are read and the checksum is verified. +If both headers are valid, the one with the newer version is used. +The chunk with the latest version is then detected (details about this see below), +and the rest of the metadata is read from there. +If the chunk id, block and version are not stored in the file header, +then the latest chunk lookup starts with the last chunk in the file. +

+

+

+ +

Chunk Format

+

+There is one chunk per version. +Each chunk consists of a header, the pages that were modified in this version, and a footer. +The pages contain the actual data of the maps. +The pages inside a chunk are stored right after the header, next to each other (unaligned). +The size of a chunk is a multiple of the block size. +The footer is stored in the last 128 bytes of the chunk. +

+
+[ header ] [ page ] [ page ] ... [ page ] [ footer ]
+
+

+The footer allows to verify that the chunk is completely written (a chunk is written as one write operation), +and allows to find the start position of the very last chunk in the file. +The chunk header and footer contain the following data: +

+
+chunk:1,block:2,len:1,map:6,max:1c0,next:3,pages:2,root:4000004f8c,time:1fc,version:1
+chunk:1,block:2,version:1,fletcher:aed9a4f6
+
+

+The fields of the chunk header and footer are: +

+
  • chunk: The chunk id. +
  • block: The first block of the chunk (multiply by the block size to get the position in the file). +
  • len: The size of the chunk in number of blocks. +
  • map: The id of the newest map; incremented when a new map is created. +
  • max: The sum of all maximum page sizes (see page format). +
  • next: The predicted start block of the next chunk. +
  • pages: The number of pages in the chunk. +
  • root: The position of the metadata root page (see page format). +
  • time: The time the chunk was written, in milliseconds after the file was created. +
  • version: The version this chunk represents. +
  • fletcher: The checksum of the footer. +
+

+Chunks are never updated in-place. Each chunk contains the pages that were +changed in that version (there is one chunk per version, see above), +plus all the parent nodes of those pages, recursively, up to the root page. +If an entry in a map is changed, removed, or added, then the respective page is copied, +modified, and stored in the next chunk, and the number of live pages in the old chunk is decremented. +This mechanism is called copy-on-write, and is similar to how the +Btrfs file system works. +Chunks without live pages are marked as free, so the space can be re-used by more recent chunks. +Because not all chunks are of the same size, there can be a number of free blocks in front of a chunk +for some time (until a small chunk is written or the chunks are compacted). +There is a +delay of 45 seconds (by default) before a free chunk is overwritten, +to ensure new versions are persisted first. +

+

+How the newest chunk is located when opening a store: +The file header contains the position of a recent chunk, but not always the newest one. +This is to reduce the number of file header updates. +After opening the file, the file headers, and the chunk footer of the very last chunk +(at the end of the file) are read. +From those candidates, the header of the most recent chunk is read. +If it contains a "next" pointer (see above), those chunk's header and footer are read as well. +If it turned out to be a newer valid chunk, this is repeated, until the newest chunk was found. +Before writing a chunk, the position of the next chunk is predicted based on the assumption +that the next chunk will be of the same size as the current one. +When the next chunk is written, and the previous +prediction turned out to be incorrect, the file header is updated as well. +In any case, the file header is updated if the next chain gets longer than 20 hops. +

+ +

Page Format

+

+Each map is a B-tree, +and the map data is stored in (B-tree-) pages. +There are leaf pages that contain the key-value pairs of the map, +and internal nodes, which only contain keys and pointers to leaf pages. +The root of a tree is either a leaf or an internal node. +Unlike file header and chunk header and footer, the page data is not human readable. +Instead, it is stored as byte arrays, with long (8 bytes), int (4 bytes), short (2 bytes), +and variable size int and long +(1 to 5 / 10 bytes). The page format is: +

+
  • length (int): Length of the page in bytes. +
  • checksum (short): Checksum (chunk id xor offset within the chunk xor page length). +
  • mapId (variable size int): The id of the map this page belongs to. +
  • len (variable size int): The number of keys in the page. +
  • type (byte): The page type (0 for leaf page, 1 for internal node; + plus 2 if the keys and values are compressed with the LZF algorithm, or + plus 6 if the keys and values are compressed with the Deflate algorithm). +
  • children (array of long; internal nodes only): The position of the children. +
  • childCounts (array of variable size long; internal nodes only): + The total number of entries for the given child page. +
  • keys (byte array): All keys, stored depending on the data type. +
  • values (byte array; leaf pages only): All values, stored depending on the data type. +
+

+Even though this is not required by the file format, pages are stored in the following order: +For each map, the root page is stored first, then the internal nodes (if there are any), +and then the leaf pages. +This should speed up reads for media where sequential reads are faster than random access reads. +The metadata map is stored at the end of a chunk. +

+

+Pointers to pages are stored as a long, using a special format: +26 bits for the chunk id, 32 bits for the offset within the chunk, 5 bits for the length code, +1 bit for the page type (leaf or internal node). +The page type is encoded so that when clearing or removing a map, leaf pages don't +have to be read (internal nodes do have to be read in order to know where all the pages are; +but in a typical B-tree the vast majority of the pages are leaf pages). +The absolute file position is not included so that chunks can be +moved within the file without having to change page pointers; +only the chunk metadata needs to be changed. +The length code is a number from 0 to 31, where 0 means the maximum length +of the page is 32 bytes, 1 means 48 bytes, 2: 64, 3: 96, 4: 128, 5: 192, and so on until 31 which +means longer than 1 MB. That way, reading a page only requires one +read operation (except for very large pages). +The sum of the maximum length of all pages is stored in the chunk metadata (field "max"), +and when a page is marked as removed, the live maximum length is adjusted. +This allows to estimate the amount of free space within a block, in addition to the number of free pages. +

+

+The total number of entries in child pages are kept to allow efficient range counting, +lookup by index, and skip operations. +The pages form a counted B-tree. +

+

+Data compression: The data after the page type are optionally compressed using the LZF algorithm. +

+ +

Metadata Map

+

+In addition to the user maps, there is one metadata map that contains names and +positions of user maps, and chunk metadata. +The very last page of a chunk contains the root page of that metadata map. +The exact position of this root page is stored in the chunk header. +This page (directly or indirectly) points to the root pages of all other maps. +The metadata map of a store with a map named "data", and one chunk, +contains the following entries: +

+
  • chunk.1: The metadata of chunk 1. This is the same data as the chunk header, + plus the number of live pages, and the maximum live length. +
  • map.1: The metadata of map 1. The entries are: name, createVersion, and type. +
  • name.data: The map id of the map named "data". The value is "1". +
  • root.1: The root position of map 1. +
  • setting.storeVersion: The store version (a user defined value). +
+ +

Similar Projects and Differences to Other Storage Engines

+

+Unlike similar storage engines like LevelDB and Kyoto Cabinet, +the MVStore is written in Java +and can easily be embedded in a Java and Android application. +

+The MVStore is somewhat similar to the Berkeley DB Java Edition +because it is also written in Java, +and is also a log structured storage, but the H2 license is more liberal. +

+Like SQLite 3, the MVStore keeps all data in one file. +Unlike SQLite 3, the MVStore uses is a log structured storage. +The plan is to make the MVStore both easier to use as well as faster than SQLite 3. +In a recent (very simple) test, the MVStore was about twice as fast as SQLite 3 on Android. +

+The API of the MVStore is similar to MapDB (previously known as JDBM) from Jan Kotek, +and some code is shared between MVStore and MapDB. +However, unlike MapDB, the MVStore uses is a log structured storage. +The MVStore does not have a record size limit. +

+ +

Current State

+

+The code is still experimental at this stage. +The API as well as the behavior may partially change. +Features may be added and removed (even though the main features will stay). +

+ +

Requirements

+

+The MVStore is included in the latest H2 jar file. +

+There are no special requirements to use it. +The MVStore should run on any JVM as well as on Android. +

+To build just the MVStore (without the database engine), run: +

+
+./build.sh jarMVStore
+
+

+This will create the file bin/h2mvstore-${version}.jar (about 200 KB). +

+ +
diff --git a/h2/src/docsrc/html/navigation.js b/h2/src/docsrc/html/navigation.js new file mode 100644 index 0000000..1262d1b --- /dev/null +++ b/h2/src/docsrc/html/navigation.js @@ -0,0 +1,195 @@ +/* + * 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 + */ + +function scroll() { + var scroll = document.documentElement.scrollTop; + if (!scroll) { + scroll = document.body.scrollTop; + } + var c = 255 - Math.min(scroll / 4, 64); + var goTop = document.getElementById('goTop'); + goTop.style.color = 'rgb(' + c + ',' + c + ',' + c + ')'; +} + +function loadFrameset() { + var a = location.search.split('&'); + var page = decodeURIComponent(a[0].substr(1)); + var frame = a[1]; + if(page && frame){ + var s = "top." + frame + ".location.replace('" + page + "')"; + eval(s); + } + return; +} + +function frameMe(frame) { + if(location.host.indexOf('h2database') < 0) { + // allow translation + return; + } + var frameset = "frame.html"; // name of the frameset page + if(frame == null) { + frame = 'main'; + } + page = new String(self.document.location); + var pos = page.lastIndexOf("/") + 1; + var file = page.substr(pos); + file = encodeURIComponent(file); + if(window.name != frame) { + var s = frameset + "?" + file + "&" + frame; + top.location.replace(s); + } else { + highlightFrame(); + } + return; +} + +function addHighlight(page, word, count) { + if(count > 0) { + if(top.main.document.location.href.indexOf(page) > 0 && top.main.document.body && top.main.document.body.innerHTML) { + highlight(); + } else { + window.setTimeout('addHighlight("'+page+'","'+word+'",'+(count-1)+')', 10); + } + } +} + +function highlightFrame() { + var url = new String(top.main.location.href); + if(url.indexOf('?highlight=') < 0) { + return; + } else { + var page = url.split('?highlight='); + var word = decodeURIComponent(page[1]); + top.main.document.body.innerHTML = highlightSearchTerms(top.main.document.body, word); + top.main.location = '#firstFound'; + // window.setTimeout('goFirstFound()', 1); + } +} + +function highlight() { + var url = new String(document.location.href); + if(url.indexOf('?highlight=') < 0) { + return; + } else { + var page = url.split('highlight=')[1].split('&')[0]; + var search = decodeURIComponent(url.split('search=')[1].split('#')[0]); + var word = decodeURIComponent(page); + document.body.innerHTML = highlightSearchTerms(document.body, word); + document.location = '#firstFound'; + document.getElementById('search').value = search; + listWords(search, ''); + } +} + +function goFirstFound() { + top.main.location = '#firstFound'; +/* + var page = new String(parent.main.location); + alert('first: ' + page); + page = page.split('#')[0]; + paramSplit = page.split('?'); + page = paramSplit[0]; + page += '#firstFound'; + if(paramSplit.length > 0) { + page += '?' + paramSplit[1]; + } + top.main.location = page; +*/ +} + +function highlightSearchTerms(body, searchText) { + matchColor = "ffff00,00ffff,00ff00,ff8080,ff0080".split(','); + highlightEndTag = ""; + searchArray = searchText.split(","); + if (!body || typeof(body.innerHTML) == "undefined") { + return false; + } + var bodyText = body.innerHTML; + for (var i = 0; i < searchArray.length; i++) { + var color = matchColor[i % matchColor.length]; + highlightStartTag = ""; + bodyText = doHighlight(bodyText, searchArray[i], highlightStartTag, highlightEndTag); + } + return bodyText; +} + +function doHighlight(bodyText, searchTerm, highlightStartTag, highlightEndTag) { + if(searchTerm == undefined || searchTerm=="" || searchTerm.length < 3) { + return bodyText; + } + var newText = ""; + var i = -1; + var lcSearchTerm = searchTerm.toLowerCase(); + var lcBodyText = bodyText.toLowerCase(); + while (bodyText.length > 0) { + i = lcBodyText.indexOf(lcSearchTerm, i+1); + if (i < 0) { + newText += bodyText; + bodyText = ""; + } else { + // skip anything inside an HTML tag + if (bodyText.lastIndexOf(">", i) >= bodyText.lastIndexOf("<", i)) { + // skip anything inside a + +
+ + +

Performance

+ + Performance Comparison
+ + PolePosition Benchmark
+ + Database Performance Tuning
+ + Using the Built-In Profiler
+ + Application Profiling
+ + Database Profiling
+ + Statement Execution Plans
+ + How Data is Stored and How Indexes Work
+ + Fast Database Import
+ +

Performance Comparison

+

+In many cases H2 is faster than other +(open source and not open source) database engines. +Please note this is mostly a single connection benchmark run on one computer, +with many very simple operations running against the database. +This benchmark does not include very complex queries. +The embedded mode of H2 is faster than the client-server mode because +the per-statement overhead is greatly reduced. +

+ +

Embedded

+ + + + + + + + + + + + + + + + + + + + +
Test CaseUnitH2HSQLDBDerby
Simple: Initms102125106762
Simple: Query (random)ms5136532035
Simple: Query (sequential)ms134422107665
Simple: Update (sequential)ms164230407034
Simple: Delete (sequential)ms169723109981
Simple: Memory UsageMB181513
BenchA: Initms80128776576
BenchA: Transactionsms136926294987
BenchA: Memory UsageMB12159
BenchB: Initms96625447161
BenchB: Transactionsms3412316815
BenchB: Memory UsageMB141010
BenchC: Initms263031447420
BenchC: Transactionsms173217422735
BenchC: Memory UsageMB193411
Executed statements#222203222220322222032
Total timems140562597563171
Statements per second#/s1580848554535174
+ +

Client-Server

+ + + + + + + + + + + + + + + + + + + + +
Test CaseUnitH2HSQLDBDerbyPostgreSQLMySQL
Simple: Initms27989480554714232972109482
Simple: Query (random)ms4821598414741408915140
Simple: Query (sequential)ms33656491129599935676143536
Simple: Update (sequential)ms987823565314182611350676
Simple: Delete (sequential)ms1305628584439552098564647
Simple: Memory UsageMB18151524
BenchA: Initms20993425253833527794107723
BenchA: Transactionsms1654929255289952311365036
BenchA: Memory UsageMB12181114
BenchB: Initms26785487723975632369115398
BenchB: Transactionsms8981004619168181794
BenchB: Memory UsageMB16111225
BenchC: Initms1826626865393252454770531
BenchC: Transactionsms656977839412891619150
BenchC: Memory UsageMB17351327
Executed statements#22220322222032222203222220322222032
Total timems179460320546390994237392763113
Statements per second#/s123816932568393602911
+ +

Benchmark Results and Comments

+ +

H2

+

+Version 2.0.202 (2021-11-25) was used for the test. +For most operations, the performance of H2 is about the same as for HSQLDB. +One situation where H2 is slow is large result sets, because they are buffered to +disk if more than a certain number of records are returned. +The advantage of buffering is: there is no limit on the result set size. +

+ +

HSQLDB

+

+Version 2.5.1 was used for the test. +Cached tables are used in this test (hsqldb.default_table_type=cached), +and the write delay is 1 second (SET WRITE_DELAY 1). +

+ +

Derby

+

+Version 10.14.2.0 was used for the test. Derby is clearly the slowest embedded database in this test. +This seems to be a structural problem, because all operations are really slow. +It will be hard for the developers of Derby to improve the performance to a reasonable level. +A few problems have been identified: leaving autocommit on is a problem for Derby. +If it is switched off during the whole test, the results are about 20% better for Derby. +Derby calls FileChannel.force(false), but only twice per log file (not on each commit). +Disabling this call improves performance for Derby by about 2%. +Unlike H2, Derby does not call FileDescriptor.sync() on each checkpoint. +Derby supports a testing mode (system property derby.system.durability=test) +where durability is disabled. According to the documentation, this setting should be used for testing only, +as the database may not recover after a crash. Enabling this setting improves performance +by a factor of 2.6 (embedded mode) or 1.4 (server mode). Even if enabled, Derby is still less +than half as fast as H2 in default mode. +

+ +

PostgreSQL

+

+Version 13.4 was used for the test. +The following options where changed in postgresql.conf: +fsync = off, commit_delay = 100000 (microseconds). +PostgreSQL is run in server mode. +The memory usage number is incorrect, because only the memory usage of the JDBC driver is measured. +

+ +

MySQL

+

+Version 8.0.27 was used for the test. +MySQL was run with the InnoDB backend. + The setting innodb_flush_log_at_trx_commit and sync_binlogcode> +(found in the my.ini / community-mysql-server.cnf file) was set to 0. Otherwise +(and by default), MySQL is slow (around 140 statements per second in this test) +because it tries to flush the data to disk for each commit. +For small transactions (when autocommit is on) this is really slow. +But many use cases use small or relatively small transactions. +Too bad this setting is not listed in the configuration wizard, +and it always overwritten when using the wizard. +You need to change those settings manually in the file my.ini / community-mysql-server.cnf, +and then restart the service. +The memory usage number is incorrect, because only the memory usage of the JDBC driver is measured. +

+ +

SQLite

+

+SQLite 3.36.0.3, configured to use WAL and with +synchronous=NORMAL was tested in a +separate, less reliable run. A rough estimate is that SQLite performs approximately 2-5x worse in the simple benchmarks, +which perform simple work in the database, resulting in a low work-per-transaction 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

+

+Firebird 3.0 (default installation) was tested, but failed on multi-threaded part of the test. +It is likely possible to run the performance test with the Firebird database, +and any information on how to configure Firebird for this are welcome. +

+ +

Why Oracle / MS SQL Server / DB2 are Not Listed

+

+The license of these databases does not allow to publish benchmark results. +This doesn't mean that they are fast. They are in fact quite slow, +and need a lot of memory. But you will need to test this yourself. +

+ +

About this Benchmark

+ +

How to Run

+

+This test was as follows: +

+
+build benchmark
+
+ +

Separate Process per Database

+

+For each database, a new process is started, to ensure the previous test does not impact +the current test. +

+ +

Number of Connections

+

+This is mostly a single-connection benchmark. +BenchB uses multiple connections; the other tests use one connection. +

+ +

Real-World Tests

+

+Good benchmarks emulate real-world use cases. This benchmark includes 4 test cases: +BenchSimple uses one table and many small updates / deletes. +BenchA is similar to the TPC-A test, but single connection / single threaded (see also: www.tpc.org). +BenchB is similar to the TPC-B test, using multiple connections (one thread per connection). +BenchC is similar to the TPC-C test, but single connection / single threaded. +

+ +

Comparing Embedded with Server Databases

+

+This is mainly a benchmark for embedded databases (where the application runs in the same +virtual machine as the database engine). However MySQL and PostgreSQL are not Java +databases and cannot be embedded into a Java application. +For the Java databases, both embedded and server modes are tested. +

+ +

Test Platform

+

+This test is run on Fedora v.34 with Oracle JVM 1.8 and SSD drive. +

+ +

Multiple Runs

+

+When a Java benchmark is run first, the code is not fully compiled and +therefore runs slower than when running multiple times. A benchmark +should always run the same test multiple times and ignore the first run(s). +This benchmark runs three times, but only the last run is measured. +

+ +

Memory Usage

+

+It is not enough to measure the time taken, the memory usage is important as well. +Performance can be improved by using a bigger cache, but the amount of memory is limited. +HSQLDB tables are kept fully in memory by default; this benchmark +uses 'disk based' tables for all databases. +Unfortunately, it is not so easy to calculate the memory usage of PostgreSQL +and MySQL, because they run in a different process than the test. This benchmark currently +does not print memory usage of those databases. +

+ +

Delayed Operations

+

+Some databases delay some operations (for example flushing the buffers) +until after the benchmark is run. This benchmark waits between +each database tested, and each database runs in a different process (sequentially). +

+ +

Transaction Commit / Durability

+

+Durability means transaction committed to the database will not be lost. +Some databases (for example MySQL) try to enforce this by default by +calling fsync() to flush the buffers, but +most hard drives don't actually flush all data. Calling the method slows down transaction commit a lot, +but doesn't always make data durable. When comparing the results, it is important to +think about the effect. Many database suggest to 'batch' operations when possible. +This benchmark switches off autocommit when loading the data, and calls commit after each 1000 +inserts. However many applications need 'short' transactions at runtime (a commit after each update). +This benchmark commits after each update / delete in the simple benchmark, and after each +business transaction in the other benchmarks. For databases that support delayed commits, +a delay of one second is used. +

+ +

Using Prepared Statements

+

+Wherever possible, the test cases use prepared statements. +

+ +

Currently Not Tested: Startup Time

+

+The startup time of a database engine is important as well for embedded use. +This time is not measured currently. +Also, not tested is the time used to create a database and open an existing database. +Here, one (wrapper) connection is opened at the start, +and for each step a new connection is opened and then closed. +

+ +

PolePosition Benchmark

+

+The PolePosition is an open source benchmark. The algorithms are all quite simple. +It was developed / sponsored by db4o. +This test was not run for a longer time, so please be aware that the results below +are for older database versions (H2 version 1.1, HSQLDB 1.8, Java 1.4). +

+ + + + + + + + + + + + + + + + + + + + + + +
Test CaseUnitH2HSQLDBMySQL
Melbourne writems3692492022
Melbourne readms474993
Melbourne read_hotms244395
Melbourne deletems147133176
Sepang writems96512013213
Sepang readms7659483455
Sepang read_hotms7898593563
Sepang deletems138415966214
Bahrain writems118613876904
Bahrain query_indexed_stringms336170693
Bahrain query_stringms180643970341243
Bahrain query_indexed_intms104134678
Bahrain updatems19187159
Bahrain deletems12157296812
Imola retrievems1981944036
Barcelona writems4138323191
Barcelona readms1191601177
Barcelona queryms205169101
Barcelona deletems3883193287
Totalms267245396287112
+

+There are a few problems with the PolePosition test: +

+
  • +HSQLDB uses in-memory tables by default while H2 uses persistent tables. The HSQLDB version +included in PolePosition does not support changing this, so you need to replace +poleposition-0.20/lib/hsqldb.jar with a newer version (for example +hsqldb-1.8.0.7.jar), +and then use the setting +hsqldb.connecturl=jdbc:hsqldb:file:data/hsqldb/dbbench2;hsqldb.default_table_type=cached;sql.enforce_size=true +in the file Jdbc.properties. +
  • HSQLDB keeps the database open between tests, while H2 closes the database (losing all the cache). +To change that, use the database URL jdbc:h2:file:data/h2/dbbench;DB_CLOSE_DELAY=-1 +
  • The amount of cache memory is quite important, specially for the PolePosition test. +Unfortunately, the PolePosition test does not take this into account. +
+ +

Database Performance Tuning

+ +

Keep Connections Open or Use a Connection Pool

+

+If your application opens and closes connections a lot (for example, for each request), +you should consider using a connection pool. +Opening a connection using DriverManager.getConnection is specially slow +if the database is closed. By default the database is closed if the last connection is closed. +

+If you open and close connections a lot but don't want to use a connection pool, +consider keeping a 'sentinel' connection open for as long as the application runs, +or use delayed database closing. See also +Closing a database. +

+ +

Use a Modern JVM

+

+Newer JVMs are faster. Upgrading to the latest version of your JVM can provide a "free" boost to performance. +Switching from the default Client JVM to the Server JVM using the -server command-line +option improves performance at the cost of a slight increase in start-up time. +

+ +

Virus Scanners

+

+Some virus scanners scan files every time they are accessed. +It is very important for performance that database files are not scanned for viruses. +The database engine never interprets the data stored in the files as programs, +that means even if somebody would store a virus in a database file, this would +be harmless (when the virus does not run, it cannot spread). +Some virus scanners allow to exclude files by suffix. Ensure files ending with .db are not scanned. +

+ +

Using the Trace Options

+

+If the performance hot spots are in the database engine, in many cases the performance +can be optimized by creating additional indexes, or changing the schema. Sometimes the +application does not directly generate the SQL statements, for example if an O/R mapping tool +is used. To view the SQL statements and JDBC API calls, you can use the trace options. +For more information, see Using the Trace Options. +

+ +

Index Usage

+

+This database uses indexes to improve the performance of +SELECT, UPDATE, DELETE. +If a column is used in the WHERE clause of a query, and if an index exists on this column, +then the index can be used. Multi-column indexes are used if all or the first columns of the index are used. +Both equality lookup and range scans are supported. +Indexes are used to order result sets, but only if the condition uses the same index or no index at all. +The results are sorted in memory if required. +Indexes are created automatically for primary key and unique constraints. +Indexes are also created for foreign key constraints, if required. +For other columns, indexes need to be created manually using the CREATE INDEX statement. +

+ +

Index Hints

+

+If you have determined that H2 is not using the optimal index for your query, you can use index hints to force +H2 to use specific indexes. +

+
+SELECT * FROM TEST USE INDEX (index_name_1, index_name_2) WHERE X=1
+
+

Only indexes in the list will be used when choosing an index to use on the given table. There +is no significance to order in this list. +

+It is possible that no index in the list is chosen, in which case a full table scan will be used. +

+

An empty list of index names forces a full table scan to be performed.

+

Each index in the list must exist.

+ +

How Data is Stored Internally

+

+For persistent databases, if a table is created with a single column primary key of type BIGINT, INT, SMALLINT, TINYINT, +then the data of the table is organized in this way. This is sometimes also called a "clustered index" or +"index organized table". +

+H2 internally stores table data and indexes in the form of b-trees. +Each b-tree stores entries as a list of unique keys (one or more columns) and data (zero or more columns). +The table data is always organized in the form of a "data b-tree" with a single column key of type long. +If a single column primary key of type BIGINT, INT, SMALLINT, TINYINT is specified when creating the table +(or just after creating the table, but before inserting any rows), +then this column is used as the key of the data b-tree. +If no primary key has been specified, if the primary key column is of another data type, +or if the primary key contains more than one column, +then a hidden identity column of type BIGINT is added to the table, +which is used as the key for the data b-tree. +All other columns of the table are stored within the data area of this data b-tree +(except for large BLOB, CLOB columns, which are stored externally). +

+For each additional index, one new "index b-tree" is created. The key of this b-tree consists of the indexed columns, +plus the key of the data b-tree. If a primary key is created after the table has been created, or if the primary key +contains multiple column, or if the primary key is not of the data types listed above, then the primary key +is stored in a new index b-tree. +

+ +

Optimizer

+

+This database uses a cost based optimizer. For simple and queries and queries with medium complexity +(less than 7 tables in the join), the expected cost (running time) of all possible plans is calculated, +and the plan with the lowest cost is used. For more complex queries, the algorithm first tries +all possible combinations for the first few tables, and the remaining tables added using a greedy algorithm +(this works well for most joins). Afterwards a genetic algorithm is used to test at most 2000 distinct plans. +Only left-deep plans are evaluated. +

+ +

Expression Optimization

+

+After the statement is parsed, all expressions are simplified automatically if possible. Operations +are evaluated only once if all parameters are constant. Functions are also optimized, but only +if the function is constant (always returns the same result for the same parameter values). +If the WHERE clause is always false, then the table is not accessed at all. +

+ +

COUNT(*) Optimization

+

+If the query only counts all rows of a table, then the data is not accessed. +However, this is only possible if no WHERE clause is used, that means it only works for +queries of the form SELECT COUNT(*) FROM table. +

+ +

Updating Optimizer Statistics / Column Selectivity

+

+When executing a query, at most one index per join can be used. +If the same table is joined multiple times, for each join only one index is used +(the same index could be used for both joins, or each join could use a different index). +Example: for the query +SELECT * FROM TEST T1, TEST T2 WHERE T1.NAME='A' AND T2.ID=T1.ID, +two index can be used, in this case the index on NAME for T1 and the index on ID for T2. +

+If a table has multiple indexes, sometimes more than one index could be used. +Example: if there is a table TEST(ID, NAME, FIRSTNAME) and an index on each column, +then two indexes could be used for the query SELECT * FROM TEST WHERE NAME='A' AND FIRSTNAME='B', +the index on NAME or the index on FIRSTNAME. It is not possible to use both indexes at the same time. +Which index is used depends on the selectivity of the column. The selectivity describes the 'uniqueness' of +values in a column. A selectivity of 100 means each value appears only once, and a selectivity of 1 means +the same value appears in many or most rows. For the query above, the index on NAME should be used +if the table contains more distinct names than first names. +

+The SQL statement ANALYZE can be used to automatically estimate the selectivity of the columns in the tables. +This command should be run from time to time to improve the query plans generated by the optimizer. +

+ +

In-Memory (Hash) Indexes

+

+Using in-memory indexes, specially in-memory hash indexes, can speed up +queries and data manipulation. +

+

In-memory indexes are automatically used +for in-memory databases, but can also be created for persistent databases +using CREATE MEMORY TABLE. In many cases, +the rows itself will also be kept in-memory. Please note this may cause memory +problems for large tables. +

+

+In-memory hash indexes are backed by a hash table and are usually faster than +regular indexes. However, hash indexes only supports direct lookup (WHERE ID = ?) +but not range scan (WHERE ID < ?). To use hash indexes, use HASH as in: +CREATE UNIQUE HASH INDEX and +CREATE TABLE ...(ID INT PRIMARY KEY HASH,...). +

+ +

Use Prepared Statements

+

+If possible, use prepared statements with parameters. +

+ +

Prepared Statements and IN(...)

+

+Avoid generating SQL statements with a variable size IN(...) list. +Instead, use a prepared statement with arrays as in the following example: +

+
+PreparedStatement prep = conn.prepareStatement(
+    "SELECT * FROM TEST WHERE ID = ANY(?)");
+prep.setObject(1, new Long[] { 1L, 2L });
+ResultSet rs = prep.executeQuery();
+
+ +

Optimization Examples

+

+See src/test/org/h2/samples/optimizations.sql for a few examples of queries +that benefit from special optimizations built into the database. +

+ +

Cache Size and Type

+

+By default the cache size of H2 is quite small. Consider using a larger cache size, or enable +the second level soft reference cache. See also Cache Settings. +

+ +

Data Types

+

+Each data type has different storage and performance characteristics: +

+
  • The DECIMAL/NUMERIC type is slower + and requires more storage than the REAL and DOUBLE PRECISION types. +
  • Text types are slower to read, write, and compare than numeric types and generally require more storage. +
  • See Large Objects for information on + BINARY vs. BLOB + and VARCHAR vs. CLOB performance. +
  • Parsing and formatting takes longer for the + TIME, DATE, and + TIMESTAMP types than the numeric types. +
  • SMALLINT/TINYINT/BOOLEAN are not significantly smaller or faster + to work with than INTEGER in most modes. +
+ +

Sorted Insert Optimization

+

+To reduce disk space usage and speed up table creation, an +optimization for sorted inserts is available. When used, b-tree pages +are split at the insertion point. To use this optimization, add SORTED +before the SELECT statement: +

+
+CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR) AS
+    SORTED SELECT X, SPACE(100) FROM SYSTEM_RANGE(1, 100);
+INSERT INTO TEST
+    SORTED SELECT X, SPACE(100) FROM SYSTEM_RANGE(101, 200);
+
+ +

Using the Built-In Profiler

+

+A very simple Java profiler is built-in. To use it, use the following template: +

+
+import org.h2.util.Profiler;
+Profiler prof = new Profiler();
+prof.startCollecting();
+// .... some long running process, at least a few seconds
+prof.stopCollecting();
+System.out.println(prof.getTop(3));
+
+ +

Application Profiling

+ +

Analyze First

+

+Before trying to optimize performance, it is important to understand where the problem is (what part of the application is slow). +Blind optimization or optimization based on guesses should be avoided, because usually it is not an efficient strategy. +There are various ways to analyze an application. Sometimes two implementations can be compared using +System.currentTimeMillis(). But this does not work for complex applications with many modules, and for memory problems. +

+

+A simple way to profile an application is to use the built-in profiling tool of java. Example: +

+
+java -Xrunhprof:cpu=samples,depth=16 com.acme.Test
+
+

+Unfortunately, it is only possible to profile the application from start to end. Another solution is to create +a number of full thread dumps. To do that, first run jps -l to get the process id, and then +run jstack <pid> or kill -QUIT <pid> (Linux) or press +Ctrl+C (Windows). +

+

+A simple profiling tool is included in H2. To use it, the application needs to be changed slightly. Example: +

+
+import org.h2.util;
+...
+Profiler profiler = new Profiler();
+profiler.startCollecting();
+// application code
+System.out.println(profiler.getTop(3));
+
+

+The profiler is built into the H2 Console tool, to analyze databases that open slowly. +To use it, run the H2 Console, and then click on 'Test Connection'. +Afterwards, click on "Test successful" and you get the most common stack traces, +which helps to find out why it took so long to connect. You will only get the stack traces +if opening the database took more than a few seconds. +

+ +

Database Profiling

+

+The ConvertTraceFile tool generates SQL statement statistics at the end of the SQL script file. +The format used is similar to the profiling data generated when using java -Xrunhprof. +For this to work, the trace level needs to be 2 or higher (TRACE_LEVEL_FILE=2). +The easiest way to set the trace level is to append the setting to the database URL, for example: +jdbc:h2:~/test;TRACE_LEVEL_FILE=2 or jdbc:h2:tcp://localhost/~/test;TRACE_LEVEL_FILE=2. +As an example, execute the following script using the H2 Console: +

+
+SET TRACE_LEVEL_FILE 2;
+DROP TABLE IF EXISTS TEST;
+CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255));
+@LOOP 1000 INSERT INTO TEST VALUES(?, ?);
+SET TRACE_LEVEL_FILE 0;
+
+

+After running the test case, convert the .trace.db file using the ConvertTraceFile tool. +The trace file is located in the same directory as the database file. +

+
+java -cp h2*.jar org.h2.tools.ConvertTraceFile
+    -traceFile "~/test.trace.db" -script "~/test.sql"
+
+

+The generated file test.sql will contain the SQL statements as well as the +following profiling data (results vary): +

+
+-----------------------------------------
+-- SQL Statement Statistics
+-- time: total time in milliseconds (accumulated)
+-- count: how many times the statement ran
+-- result: total update count or row count
+-----------------------------------------
+-- self accu    time   count  result sql
+--  62%  62%     158    1000    1000 INSERT INTO TEST VALUES(?, ?);
+--  37% 100%      93       1       0 CREATE TABLE TEST(ID INT PRIMARY KEY...
+--   0% 100%       0       1       0 DROP TABLE IF EXISTS TEST;
+--   0% 100%       0       1       0 SET TRACE_LEVEL_FILE 3;
+
+ +

Statement Execution Plans

+

+The SQL statement EXPLAIN displays the indexes and optimizations the database uses for a statement. +The following statements support EXPLAIN: SELECT, UPDATE, DELETE, MERGE, INSERT. +The following query shows that the database uses the primary key index to search for rows: +

+
+EXPLAIN SELECT * FROM TEST WHERE ID=1;
+SELECT
+    TEST.ID,
+    TEST.NAME
+FROM PUBLIC.TEST
+    /* PUBLIC.PRIMARY_KEY_2: ID = 1 */
+WHERE ID = 1
+
+

+For joins, the tables in the execution plan are sorted in the order they are processed. +The following query shows the database first processes the table INVOICE (using the primary key). +For each row, it will additionally check that the value of the column AMOUNT is larger than zero, +and for those rows the database will search in the table CUSTOMER (using the primary key). +The query plan contains some redundancy so it is a valid statement. +

+
+CREATE TABLE CUSTOMER(ID IDENTITY, NAME VARCHAR);
+CREATE TABLE INVOICE(ID IDENTITY,
+    CUSTOMER_ID INT REFERENCES CUSTOMER(ID),
+    AMOUNT NUMBER);
+
+EXPLAIN SELECT I.ID, C.NAME FROM CUSTOMER C, INVOICE I
+WHERE I.ID=10 AND AMOUNT>0 AND C.ID=I.CUSTOMER_ID;
+
+SELECT
+    I.ID,
+    C.NAME
+FROM PUBLIC.INVOICE I
+    /* PUBLIC.PRIMARY_KEY_9: ID = 10 */
+    /* WHERE (I.ID = 10)
+        AND (AMOUNT > 0)
+    */
+INNER JOIN PUBLIC.CUSTOMER C
+    /* PUBLIC.PRIMARY_KEY_5: ID = I.CUSTOMER_ID */
+    ON 1=1
+WHERE (C.ID = I.CUSTOMER_ID)
+    AND ((I.ID = 10)
+    AND (AMOUNT > 0))
+
+ +

Displaying the Scan Count

+

+EXPLAIN ANALYZE additionally shows the scanned rows per table and pages read from disk per table or index. +This will actually execute the query, unlike EXPLAIN which only prepares it. +The following query scanned 1000 rows, and to do that had to read 85 pages from the data area of the table. +Running the query twice will not list the pages read from disk, because they are now in the cache. +The tableScan means this query doesn't use an index. +

+
+EXPLAIN ANALYZE SELECT * FROM TEST;
+SELECT
+    TEST.ID,
+    TEST.NAME
+FROM PUBLIC.TEST
+    /* PUBLIC.TEST.tableScan */
+    /* scanCount: 1000 */
+/*
+total: 85
+TEST.TEST_DATA read: 85 (100%)
+*/
+
+

+The cache will prevent the pages are read twice. H2 reads all columns of the row +unless only the columns in the index are read. Except for large CLOB and BLOB, which are not store in the table. +

+ +

Special Optimizations

+

+For certain queries, the database doesn't need to read all rows, or doesn't need to sort the result even if ORDER BY is used. +

+For queries of the form SELECT COUNT(*), MIN(ID), MAX(ID) FROM TEST, the query plan includes the line +/* direct lookup */ if the data can be read from an index. +

+For queries of the form SELECT DISTINCT CUSTOMER_ID FROM INVOICE, the query plan includes the line +/* distinct */ if there is an non-unique or multi-column index on this column, and if this column has a low selectivity. +

+For queries of the form SELECT * FROM TEST ORDER BY ID, the query plan includes the line +/* index sorted */ to indicate there is no separate sorting required. +

+For queries of the form SELECT * FROM TEST GROUP BY ID ORDER BY ID, the query plan includes the line +/* group sorted */ to indicate there is no separate sorting required. +

+ +

How Data is Stored and How Indexes Work

+

+Internally, each row in a table is identified by a unique number, the row id. +The rows of a table are stored with the row id as the key. +The row id is a number of type long. +If a table has a single column primary key of type INT or BIGINT, +then the value of this column is the row id, otherwise the database generates the row id automatically. +There is a (non-standard) way to access the row id: using the _ROWID_ pseudo-column: +

+
+CREATE TABLE ADDRESS(FIRST_NAME VARCHAR,
+    NAME VARCHAR, CITY VARCHAR, PHONE VARCHAR);
+INSERT INTO ADDRESS VALUES('John', 'Miller', 'Berne', '123 456 789');
+INSERT INTO ADDRESS VALUES('Philip', 'Jones', 'Berne', '123 012 345');
+SELECT _ROWID_, * FROM ADDRESS;
+
+

+The data is stored in the database as follows: +

+ + + + +
_ROWID_FIRST_NAMENAMECITYPHONE
1JohnMillerBerne123 456 789
2PhilipJonesBerne123 012 345
+

+Access by row id is fast because the data is sorted by this key. +Please note the row id is not available until after the row was added +(that means, it can not be used in generated columns or constraints). +If the query condition does not contain the row id (and if no other index can be used), then all rows of the table are scanned. +A table scan iterates over all rows in the table, in the order of the row id. +To find out what strategy the database uses to retrieve the data, use EXPLAIN SELECT: +

+
+SELECT * FROM ADDRESS WHERE NAME = 'Miller';
+
+EXPLAIN SELECT PHONE FROM ADDRESS WHERE NAME = 'Miller';
+SELECT
+    PHONE
+FROM PUBLIC.ADDRESS
+    /* PUBLIC.ADDRESS.tableScan */
+WHERE NAME = 'Miller';
+
+ +

Indexes

+

+An index internally is basically just a table that contains the indexed column(s), plus the row id: +

+
+CREATE INDEX INDEX_PLACE ON ADDRESS(CITY, NAME, FIRST_NAME);
+
+

+In the index, the data is sorted by the indexed columns. +So this index contains the following data: +

+ + + + +
CITYNAMEFIRST_NAME_ROWID_
BerneJonesPhilip2
BerneMillerJohn1
+

+When the database uses an index to query the data, it searches the index for the given data, +and (if required) reads the remaining columns in the main data table (retrieved using the row id). +An index on city, name, and first name (multi-column index) allows to quickly search for rows when the city, name, and first name are known. +If only the city and name, or only the city is known, then this index is also used (so creating an additional index on just the city is not needed). +This index is also used when reading all rows, sorted by the indexed columns. +However, if only the first name is known, then this index is not used: +

+
+EXPLAIN SELECT PHONE FROM ADDRESS
+    WHERE CITY = 'Berne' AND NAME = 'Miller'
+    AND FIRST_NAME = 'John';
+SELECT
+    PHONE
+FROM PUBLIC.ADDRESS
+    /* PUBLIC.INDEX_PLACE: FIRST_NAME = 'John'
+        AND CITY = 'Berne'
+        AND NAME = 'Miller'
+     */
+WHERE (FIRST_NAME = 'John')
+    AND ((CITY = 'Berne')
+    AND (NAME = 'Miller'));
+
+EXPLAIN SELECT PHONE FROM ADDRESS WHERE CITY = 'Berne';
+SELECT
+    PHONE
+FROM PUBLIC.ADDRESS
+    /* PUBLIC.INDEX_PLACE: CITY = 'Berne' */
+WHERE CITY = 'Berne';
+
+EXPLAIN SELECT * FROM ADDRESS ORDER BY CITY, NAME, FIRST_NAME;
+SELECT
+    ADDRESS.FIRST_NAME,
+    ADDRESS.NAME,
+    ADDRESS.CITY,
+    ADDRESS.PHONE
+FROM PUBLIC.ADDRESS
+    /* PUBLIC.INDEX_PLACE */
+ORDER BY 3, 2, 1
+/* index sorted */;
+
+EXPLAIN SELECT PHONE FROM ADDRESS WHERE FIRST_NAME = 'John';
+SELECT
+    PHONE
+FROM PUBLIC.ADDRESS
+    /* PUBLIC.ADDRESS.tableScan */
+WHERE FIRST_NAME = 'John';
+
+

+If your application often queries the table for a phone number, then it makes sense to create +an additional index on it: +

+
+CREATE INDEX IDX_PHONE ON ADDRESS(PHONE);
+
+

+This index contains the phone number, and the row id: +

+ + + + +
PHONE_ROWID_
123 012 3452
123 456 7891
+ +

Using Multiple Indexes

+

+Within a query, only one index per logical table is used. +Using the condition PHONE = '123 567 789' OR CITY = 'Berne' +would use a table scan instead of first using the index on the phone number and then the index on the city. +It makes sense to write two queries and combine then using UNION. +In this case, each individual query uses a different index: +

+
+EXPLAIN SELECT NAME FROM ADDRESS WHERE PHONE = '123 567 789'
+UNION SELECT NAME FROM ADDRESS WHERE CITY = 'Berne';
+
+(SELECT
+    NAME
+FROM PUBLIC.ADDRESS
+    /* PUBLIC.IDX_PHONE: PHONE = '123 567 789' */
+WHERE PHONE = '123 567 789')
+UNION
+(SELECT
+    NAME
+FROM PUBLIC.ADDRESS
+    /* PUBLIC.INDEX_PLACE: CITY = 'Berne' */
+WHERE CITY = 'Berne')
+
+ +

Fast Database Import

+

+If you have to import a lot of rows, use a PreparedStatement or use CSV import. +Please note that CREATE TABLE(...) ... AS SELECT ... +is faster than CREATE TABLE(...); INSERT INTO ... SELECT .... +

+ +
diff --git a/h2/src/docsrc/html/quickstart.html b/h2/src/docsrc/html/quickstart.html new file mode 100644 index 0000000..5bb4fc0 --- /dev/null +++ b/h2/src/docsrc/html/quickstart.html @@ -0,0 +1,105 @@ + + + + + + + +Quickstart + + + + + +
+ + +

Quickstart

+ + Embedding H2 in an Application
+ + The H2 Console Application
+ +

Embedding H2 in an Application

+

+This database can be used in embedded mode, or in server mode. To use it in embedded mode, you need to: +

+
    +
  • Add the h2*.jar to the classpath (H2 does not have any dependencies) +
  • Use the JDBC driver class: org.h2.Driver +
  • The database URL jdbc:h2:~/test opens the database test in your user home directory +
  • A new database is automatically created +
+ +

The H2 Console Application

+

+The Console lets you access a SQL database using a browser interface. +
+Web Browser - H2 Console Server - H2 Database +
+If you don't have Windows XP, or if something does not work as expected, +please see the detailed description in the Tutorial. +

+ +

Step-by-Step

+ +

Installation

+

+Install the software using the Windows Installer (if you did not yet do that). +

+ +

Start the Console

+

+Click [Start], [All Programs], [H2], and [H2 Console (Command Line)]:
+Screenshot: start H2 Console
+A new console window appears:
+Screenshot: H2 running
+Also, a new browser page should open with the URL http://localhost:8082. +You may get a security warning from the firewall. If you don't want other computers in the network to access the database +on your machine, you can let the firewall block these connections. Only local connections are required at this time. +

+ +

Login

+

+Select [Generic H2] and click [Connect]:
+ Screenshot: login
+ You are now logged in. +

+ +

Sample

+

+Click on the [Sample SQL Script]:
+ Screenshot: click on the sample SQL script
+ The SQL commands appear in the command area.
+

+ +

Execute

+

+Click [Run]
+Screenshot: click Run
+On the left side, a new entry TEST is added below the database icon. +The operations and results of the statements are shown below the script.
+Screenshot: see the result
+

+ +

Disconnect

+

+Click on [Disconnect]:
+Disconnect icon
+to close the connection. +

+ +

End

+

+Close the console window. +For more information, see the Tutorial. +

+ +
+ diff --git a/h2/src/docsrc/html/search.js b/h2/src/docsrc/html/search.js new file mode 100644 index 0000000..6d32a65 --- /dev/null +++ b/h2/src/docsrc/html/search.js @@ -0,0 +1,273 @@ +/* + * 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 + */ + +var pages = new Array(); +var ref = new Array(); +var ignored = ''; +var firstLink = null; +var firstLinkWord = null; + +String.prototype.endsWith = function(suffix) { + var startPos = this.length - suffix.length; + if (startPos < 0) { + return false; + } + return this.lastIndexOf(suffix, startPos) == startPos; +}; + +function listWords(value, open) { + value = replaceOtherChars(value); + value = trim(value); + if (pages.length == 0) { + load(); + } + var table = document.getElementById('result'); + while (table.rows.length > 0) { + table.deleteRow(0); + } + firstLink = null; + var clear = document.getElementById('clear'); + if (value.length == 0) { + clear.style.display = 'none'; + return; + } + clear.style.display = ''; + var keywords = value.split(' '); + if (keywords.length > 1) { + listMultipleWords(keywords); + return; + } + if (value.length < 3) { + max = 100; + } else { + max = 1000; + } + value = value.toLowerCase(); + var r = ref[value.substring(0, 1)]; + if (r == undefined) { + return; + } + var x = 0; + var words = r.split(';'); + var count = 0; + for (var i = 0; i < words.length; i++) { + var wordRef = words[i]; + if (wordRef.toLowerCase().indexOf(value) == 0) { + count++; + } + } + for (var i = 0; i < words.length && (x <= max); i++) { + var wordRef = words[i]; + if (wordRef.toLowerCase().indexOf(value) == 0) { + word = wordRef.split("=")[0]; + var tr = table.insertRow(x++); + var td = document.createElement('td'); + var tdClass = document.createAttribute('class'); + tdClass.nodeValue = 'searchKeyword'; + td.setAttributeNode(tdClass); + + var ah = document.createElement('a'); + var href = document.createAttribute('href'); + href.nodeValue = 'javascript:set("' + word + '");'; + var link = document.createTextNode(word); + ah.setAttributeNode(href); + ah.appendChild(link); + td.appendChild(ah); + tr.appendChild(td); + piList = wordRef.split("=")[1].split(","); + if (count < 20 || open == word) { + x = addReferences(x, piList, word); + } + } + } + if (x == 0) { + if (ignored.indexOf(';' + value + ';') >= 0) { + noResults(table, 'Common word (not indexed)'); + } else { + noResults(table, 'No results found!'); + } + } +} + +function set(v) { + if (pages.length == 0) { + load(); + } + var search = document.getElementById('search').value; + listWords(search, v); + document.getElementById('search').focus(); + window.scrollBy(-20, 0); +} + +function goFirst() { + var table = document.getElementById('result'); + if (firstLink != null) { + go(firstLink, firstLinkWord); + } + return false; +} + +function go(pageId, word) { + var page = pages[pageId]; + var load = '../' + page.file + '?highlight=' + encodeURIComponent(word); + if (top.main) { + if (!top.main.location.href.endsWith(page.file)) { + top.main.location = load; + } + } else { + if (!document.location.href.endsWith(page.file)) { + var search = document.getElementById('search').value; + document.location = load + '&search=' + encodeURIComponent(search); + } + } +} + +function listMultipleWords(keywords) { + var count = new Array(); + var weight = new Array(); + for (var i = 0; i < pages.length; i++) { + count[i] = 0; + weight[i] = 0.0; + } + for (var i = 0; i < keywords.length; i++) { + var value = keywords[i].toLowerCase(); + if (value.length <= 1) { + continue; + } + var r = ref[value.substring(0, 1)]; + if (r == undefined) { + continue; + } + var words = r.split(';'); + for (var j = 0; j < words.length; j++) { + var wordRef = words[j]; + if (wordRef.toLowerCase().indexOf(value) == 0) { + var word = wordRef.split("=")[0].toLowerCase(); + piList = wordRef.split("=")[1].split(","); + var w = 1; + for (var k = 0; k < piList.length; k++) { + var pi = piList[k]; + if (pi.charAt(0) == 't') { + pi = pi.substring(1); + w = 10000; + } else if (pi.charAt(0) == 'h') { + pi = pi.substring(1); + w = 100; + } else if (pi.charAt(0) == 'r') { + pi = pi.substring(1); + w = 1; + } + if (w > 0) { + if (word != value) { + // if it's only the start of the word, + // reduce the weight + w /= 10.0; + } + // higher weight for longer words + w += w * word.length / 10.0; + count[pi]++; + weight[pi] += w; + } + } + } + } + } + var x = 0; + var table = document.getElementById('result'); + var piList = new Array(); + var piWeight = new Array(); + for (var i = 0; i < pages.length; i++) { + var w = weight[i]; + if (w > 0) { + piList[x] = '' + i; + piWeight[x] = w * count[i]; + x++; + } + } + // sort + for (var i = 1, j; i < x; i++) { + var tw = piWeight[i]; + var ti = piList[i]; + for (j = i - 1; j >= 0 && (piWeight[j] < tw); j--) { + piWeight[j + 1] = piWeight[j]; + piList[j + 1] = piList[j]; + } + piWeight[j + 1] = tw; + piList[j + 1] = ti; + } + addReferences(0, piList, keywords); + if (piList.length == 0) { + noResults(table, 'No results found'); + } +} + +function addReferences(x, piList, word) { + var table = document.getElementById('result'); + for (var j = 0; j < piList.length; j++) { + var pi = piList[j]; + if (pi.charAt(0) == 't') { + pi = pi.substring(1); + } else if (pi.charAt(0) == 'h') { + pi = pi.substring(1); + } else if (pi.charAt(0) == 'r') { + pi = pi.substring(1); + } + var tr = table.insertRow(x++); + var td = document.createElement('td'); + var tdClass = document.createAttribute('class'); + tdClass.nodeValue = 'searchLink'; + td.setAttributeNode(tdClass); + var ah = document.createElement('a'); + var href = document.createAttribute('href'); + var thisLink = 'javascript:go(' + pi + ', "' + word + '")'; + if (firstLink == null) { + firstLink = pi; + firstLinkWord = word; + } + href.nodeValue = thisLink; + ah.setAttributeNode(href); + var page = pages[pi]; + var link = document.createTextNode(page.title); + ah.appendChild(link); + td.appendChild(ah); + tr.appendChild(td); + } + return x; +} + +function trim(s) { + while (s.charAt(0) == ' ' && s.length > 0) { + s = s.substring(1); + } + while (s.charAt(s.length - 1) == ' ' && s.length > 0) { + s = s.substring(0, s.length - 1); + } + return s; +} + +function replaceOtherChars(s) { + var x = ""; + for (var i = 0; i < s.length; i++) { + var c = s.charAt(i); + if ("\t\r\n\"'.,:;!&/\\?%@`[]{}()+-=<>|*^~#$".indexOf(c) >= 0) { + c = " "; + } + x += c; + } + return x; +} + +function noResults(table, message) { + var tr = table.insertRow(0); + var td = document.createElement('td'); + var tdClass = document.createAttribute('class'); + tdClass.nodeValue = 'searchKeyword'; + td.setAttributeNode(tdClass); + var text = document.createTextNode(message); + td.appendChild(text); + tr.appendChild(td); +} + diff --git a/h2/src/docsrc/html/security.html b/h2/src/docsrc/html/security.html new file mode 100644 index 0000000..fe8d29f --- /dev/null +++ b/h2/src/docsrc/html/security.html @@ -0,0 +1,73 @@ + + + + + + +Features + + + + + +
+ + +

Securing your H2

+ + + Introduction
+ + Network exposed
+ + Alias / Stored Procedures
+ + Grants / Roles / Permissions
+ + Encrypted storage
+ +

Introduction

+

+H2 is __not__ designed to be run in an adversarial environment. You should absolutely not expose your H2 server to untrusted connections. +

+

+Running H2 in embedded mode is the best choice - it is not externally exposed. +

+ +

Network exposed

+

+When running an H2 server in TCP mode, first prize is to run with it only listening to connections on localhost (i.e 127.0.0.1). +

+

+Second prize is running listening to restricted ports on a secured network. +

+

+If you expose H2 to the broader Internet, you can secure the connection with SSL, but this is a rather tricky thing to get right, between JVM bugs, certificates and choosing a decent cipher. +

+ +

Alias / Stored procedures

+

+Anything created with CREATE ALIAS can do anything the JVM can do, which includes reading/writing from the filesystem on the machine the JVM is running on. +

+ +

Grants / Roles / Permissions

+

+GRANT / REVOKE TODO +

+ +

Encrypted storage

+

+Encrypting your on-disk database will provide a small measure of security to your stored data. +You should not assume that this is any kind of real security against a determined opponent however, +since there are many repeated data structures that will allow someone with resources and time to extract the secret key. +

+

+Also the secret key is visible to anything that can read the memory of the process. +

+ +
+ diff --git a/h2/src/docsrc/html/source.html b/h2/src/docsrc/html/source.html new file mode 100644 index 0000000..5b8f130 --- /dev/null +++ b/h2/src/docsrc/html/source.html @@ -0,0 +1,55 @@ + + + + + + + diff --git a/h2/src/docsrc/html/sourceError.html b/h2/src/docsrc/html/sourceError.html new file mode 100644 index 0000000..84538c4 --- /dev/null +++ b/h2/src/docsrc/html/sourceError.html @@ -0,0 +1,255 @@ + + + + + +Error Analyzer + + + + + + + + +

Error Analyzer

+
Home
+

+ Input  + Details  + Source Code +

+
+
+

Paste the error message and stack trace below and click on 'Details' or 'Source Code':

+ +
+
+

Error Code:

+

Product Version:

+

Message:

+

+

More Information:

+ +
+
+ + +
+

Stack Trace:

+ +
+

Source File:
+ Inline

+ +
+
+ + + diff --git a/h2/src/docsrc/html/stylesheet.css b/h2/src/docsrc/html/stylesheet.css new file mode 100644 index 0000000..a30f4d5 --- /dev/null +++ b/h2/src/docsrc/html/stylesheet.css @@ -0,0 +1,396 @@ +/* + * 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 + */ + +td, input, select, textarea, body, code, pre, td, th { + font: 13px/1.4 Arial, sans-serif; + font-weight: normal; +} + +p { + margin: 0.4em 0 0.6em 0; +} + +h1 { + font-weight: bold; +} + +h2, h3, h4, h5 { + margin: 0.8em 0 0.5em 0; + border-bottom-color: #999; + border-bottom-style: solid; + border-bottom-width: 1px; +} + +td, input, select, textarea, body, code, pre { + font-size: 13px; +} + +pre { + background-color: #ece9d8; + border: 1px solid #aca899; + padding: 6px; + + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + + -moz-box-shadow: 2px 2px 2px #aca899; + -webkit-box-shadow: 2px 2px 2px #aca899; + -khtml-box-shadow: 2px 2px 2px #aca899; + -o-box-shadow: 2px 2px 2px #aca899; + box-shadow: 2px 2px 2px #aca899; +} + +code { + background-color: #ece9d8; + padding: 0px 4px; + + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; +} + +img { + border: 0px; +} + +body { + max-width: 800px; + clear: both; + width: 800px; + margin: 0 auto; +} + +h1 { + background-color: #0000bb; + padding: 2px 4px 2px 4px; + margin-top: 11px; + color: #fff; + font-size: 22px; + line-height: normal; +} + +h2 { + font-size: 19px; +} + +h3 { + font-size: 16px; +} + +h4 { + font-size: 13px; +} + +hr { + color: #CCC; + background-color: #CCC; + height: 1px; + border: 0px solid blue; +} + +table { + background-color: #ffffff; + border-collapse: collapse; + border: 1px solid #aca899; +} + +.main { + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + + -moz-box-shadow: 2px 2px 2px #aca899; + -webkit-box-shadow: 2px 2px 2px #aca899; + -khtml-box-shadow: 2px 2px 2px #aca899; + -o-box-shadow: 2px 2px 2px #aca899; + box-shadow: 2px 2px 2px #aca899; +} + +th { + text-align: left; + background-color: #ece9d8; + border: 1px solid #aca899; + padding: 3px; +} + +td { + background-color: #ffffff; + text-align: left; + vertical-align: top; + border: 1px solid #aca899; + padding: 3px; +} + +form { +} + +ul, ol { + list-style-position: outside; + padding-left: 20px; +} + +li { + margin-top: 2px; +} + +a { + text-decoration: none; + color: #0000ff; +} + +a:visited { + color: #0000bb; +} + +a:hover { + text-decoration: underline; +} + +em.u { + text-decoration: underline; + font-style: normal; +} + +.menu { + margin: 10px 10px 10px 10px; +} + +table.search { + width: 100%; + border: 0px; +} + +tr.search { + border: 0px; +} + +td.search { + border: 0px; + padding: 2px 0px 2px 10px; +} + +td.searchKeyword { + border: 0px; + padding: 0px 0px 0px 2px; +} + +td.searchKeyword a { + text-decoration: none; + color: #000000; +} + +td.searchKeyword a:hover { + text-decoration: underline; +} + +td.searchLink { + border: 0px; + padding: 0px 0px 0px 32px; + text-indent: -16px; +} + +td.searchLink a { + text-decoration: none; + color: #0000ff; +} + +td.searchLink a:hover { + text-decoration: underline; +} + +table.nav { + border: 0px; +} + +tr.nav { + border: 0px; +} + +td.nav { + border: 0px; +} + +table.content { + width: 100%; + height: 100%; + border: 0px; +} + +tr.content { + border:0px; +} + +td.content { + border:0px; +} + +.contentDiv { + margin:10px; +} + +.content { + margin: 10px 10px 10px 0px; +} + +.screenshot { + border: 1px outset #800; + padding: 10px; + margin: 10px 0px; +} + +.compareFeature { +} + +.compareY { + color: #050; +} + +.compareN { + color: #800; +} + +table.index { + width: 100%; + border: 0px; + padding: 0px; + margin: 0px; + border: 0px none; + border-collapse: collapse; +} + +td.index { + width: 33%; + border: 0px; + padding: 0px; + margin: 0px; + border: 0px none; + border-collapse: collapse; + vertical-align: top; +} + +.railroad { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; +} + +div.ruleCompat code { + border-color: coral; + background-color: mistyrose; +} + +div.ruleH2 code { + border-color: lightseagreen; +} + +span.ruleCompat { + color: darkred; +} + +span.ruleH2 { + color: green; +} + +.c { + padding: 1px 3px; + margin: 0px 0px; + border: 2px solid; + -moz-border-radius: 0.4em; + -webkit-border-radius: 0.4em; + -khtml-border-radius: 0.4em; + border-radius: 0.4em; + background-color: #fff; +} + +.ts { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; + width: 16px; + height: 24px; + background-image: url(images/div-ts.png); + background-size: 16px 512px; +} + +.ls { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; + width: 16px; + height: 24px; + background-image: url(images/div-ls.png); + background-size: 16px 512px; +} + +.ks { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; + width: 16px; + height: 24px; + background-image: url(images/div-ks.png); + background-size: 16px 512px; +} + +.te { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; + width: 16px; + height: 24px; + background-image: url(images/div-te.png); + background-size: 16px 512px; +} + +.le { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; + width: 16px; + height: 24px; + background-image: url(images/div-le.png); + background-size: 16px 512px; +} + +.ke { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; + width: 16px; + height: 24px; + background-image: url(images/div-ke.png); + background-size: 16px 512px; +} + +.d { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; + min-width: 16px; + height: 24px; + background-image: url(images/div-d.png); + background-size: 1024px 512px; +} diff --git a/h2/src/docsrc/html/stylesheetPdf.css b/h2/src/docsrc/html/stylesheetPdf.css new file mode 100644 index 0000000..dacc282 --- /dev/null +++ b/h2/src/docsrc/html/stylesheetPdf.css @@ -0,0 +1,162 @@ +/* + * 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 + */ + +td, input, select, textarea, body, code, pre, td, th { + font: 14pt Tahoma, Arial, Helvetica, sans-serif; + font-weight: normal; +} + +h1, h2, h3, h4, h5 { + font: 14pt Arial, Helvetica, sans-serif; + font-weight: bold; +} + +td, input, select, textarea, body, code, pre { + font-size: 14pt; +} + +pre { + background-color: #ece9d8; + border: 1px solid rgb(172, 168, 153); + padding: 4px; +} + +img { + border: 0px; +} + +body { + margin: 0px; +} + +h1, p.title { + background-color: #0000bb; + padding: 2px 4px 2px 4px; + color: #fff; + font-size: 24pt; + font-weight: bold; + line-height: normal; +} + +h2 { + font-size: 18pt; + margin-top: 1.5em; +} + +h3 { + font-size: 16pt; + margin-top: 1.5em; +} + +h4 { + font-size: 14pt; + margin-top: 1.5em; +} + +hr { + color: #CCC; + background-color: #CCC; + height: 1px; + border: 0px solid blue; +} + +table { + background-color: #ffffff; + border-collapse: collapse; + border: 1px solid #aca899; +} + +th { + font-size: 14pt; + font-weight: bold; + text-align: left; + border: 1px solid #aca899; + padding: 2px; +} + +td { + background-color: #ffffff; + font-size: 14pt; + text-align: left; + vertical-align: top; + border: 1px solid #aca899; + padding: 2px; + margin: 0px; +} + +form { +} + +ul, ol { + list-style-position: outside; + padding-left: 20px; +} + +li { + margin-top: 2px; +} + +a { + text-decoration: none; + color: #0000ff; +} + +a:hover { + text-decoration: underline; +} + +em.u { + text-decoration: underline; + font-style: normal; +} + +.screenshot { + border: 1px outset #888; + padding: 10px; + margin: 10px 0px; +} + +.compareFeature { +} + +.compareY { + color: #050; +} + +.compareN { + color: #800; +} + +table.index { + width: 100%; + border: 0px; + padding: 0px; + margin: 0px; + border: 0px none; + border-collapse: collapse; +} + +/* width: 570px; + width: 190px; + */ + +td.index { + width: 33%; + border: 0px; + padding: 0px; + margin: 0px; + border: 0px none; + border-collapse: collapse; + vertical-align: top; +} + +span.ruleCompat { + color: darkred; +} + +span.ruleH2 { + color: green; +} diff --git a/h2/src/docsrc/html/systemtables.html b/h2/src/docsrc/html/systemtables.html new file mode 100644 index 0000000..fa19549 --- /dev/null +++ b/h2/src/docsrc/html/systemtables.html @@ -0,0 +1,104 @@ + + + + + + +System Tables + + + + + +
+ + +

System Tables

+ +

Index

+ +

+Information Schema +

+ + + + + + + +
+ + ${item.table}
+
+
+ + ${item.table}
+
+
+ + ${item.table}
+
+
+ + +

+Range Table
+

+ +

Information Schema

+

+The system tables and views in the schema INFORMATION_SCHEMA contain the meta data +of all tables, views, domains, and other objects in the database as well as the current settings. +This documentation describes the default new version of INFORMATION_SCHEMA for H2 2.0. +Old TCP clients (1.4.200 and below) see the legacy version of INFORMATION_SCHEMA, +because they can't work with the new one. The legacy version is not documented. +

+ + +

${item.table}

+

${item.description}

+ + +${item.columns} + +
+
+ +

Range Table

+

+The range table is a dynamic system table that contains all values from a start to an end value. +Non-zero step value may be also specified, default is 1. +Start value, end value, and optional step value are converted to BIGINT data type. +The table contains one column called X. +If start value is greater than end value and step is positive the result is empty. +If start value is less than end value and step is negative the result is empty too. +If start value is equal to end value the result contains only start value. +Start value, start value plus step, start value plus step multiplied by two and so on are included in result. +If step is positive the last value is less than or equal to the specified end value. +If step in negative the last value is greater than or equal to the specified end value. +The table is used as follows: +

+

Examples:

+
+SELECT X FROM SYSTEM_RANGE(1, 10);
+-- 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
+SELECT X FROM SYSTEM_RANGE(1, 10, 2);
+-- 1, 3, 5, 7, 9
+SELECT X FROM SYSTEM_RANGE(1, 10, -1);
+-- No rows
+SELECT X FROM SYSTEM_RANGE(10, 2, -2);
+-- 10, 8, 6, 4, 2
+
+ +
diff --git a/h2/src/docsrc/html/tutorial.html b/h2/src/docsrc/html/tutorial.html new file mode 100644 index 0000000..3dadf0f --- /dev/null +++ b/h2/src/docsrc/html/tutorial.html @@ -0,0 +1,1475 @@ + + + + + + + +Tutorial + + + + + +
+ + +

Tutorial

+ + Starting and Using the H2 Console
+ + Special H2 Console Syntax
+ + Settings of the H2 Console
+ + Connecting to a Database using JDBC
+ + Creating New Databases
+ + Using the Server
+ + Using Hibernate
+ + Using TopLink and Glassfish
+ + Using EclipseLink
+ + Using Apache ActiveMQ
+ + Using H2 within NetBeans
+ + Using H2 with jOOQ
+ + Using Databases in Web Applications
+ + CSV (Comma Separated Values) Support
+ + Upgrade, Backup, and Restore
+ + Command Line Tools
+ + The Shell Tool
+ + Using OpenOffice Base
+ + Java Web Start / JNLP
+ + Using a Connection Pool
+ + Fulltext Search
+ + User-Defined Variables
+ + Date and Time
+ + Using Spring
+ + OSGi
+ + Java Management Extension (JMX)
+ +

Starting and Using the H2 Console

+

+The H2 Console application lets you access a database using a browser. +This can be a H2 database, or another database that supports the JDBC API. +

+Web Browser - H2 Console Server - H2 Database +

+This is a client/server application, so both a server and a client (a browser) are required to run it. +

+Depending on your platform and environment, there are multiple ways to start the H2 Console: +

+ + + + + + + + + + + + + + + + + + +
OSStart
Windows + Click [Start], [All Programs], [H2], and [H2 Console (Command Line)]
+ An icon will be added to the system tray: + H2 database icon
+ If you don't get the window and the system tray icon, + then maybe Java is not installed correctly (in this case, try another way to start the application). + A browser window should open and point to the login page at http://localhost:8082. +
Windows + Open a file browser, navigate to h2/bin, and + double click on h2.bat.
+ A console window appears. If there is a problem, you will see an error message + in this window. A browser window will open and point to the login page + (URL: http://localhost:8082). +
Any + Double click on the h2*.jar file. + This only works if the .jar suffix is associated with Java. +
Any + Open a console window, navigate to the directory h2/bin, and type: +
java -jar h2*.jar
+
+

+If the console startup procedure is unable to locate the default system web browser, +an error message may be displayed. It is possible to explicitly tell H2 which +program/script to use when opening a system web browser by setting either the BROWSER +environment variable, or the h2.browser java property. +

+ +

Firewall

+

+If you start the server, you may get a security warning from the firewall (if you have installed one). +If you don't want other computers in the network to access the application on your machine, you can +let the firewall block those connections. The connection from the local machine will still work. +Only if you want other computers to access the database on this computer, you need allow remote connections +in the firewall. +

+

+It has been reported that when using Kaspersky 7.0 with firewall, the H2 Console is very slow when +connecting over the IP address. A workaround is to connect using 'localhost'. +

+

+A small firewall is already built into the server: other computers may not connect to the server by default. +To change this, go to 'Preferences' and select 'Allow connections from other computers'. +

+ +

Testing Java

+

+To find out which version of Java is installed, open a command prompt and type: +

+
+java -version
+
+

+If you get an error message, you may need to add the Java binary directory to the path environment variable. +

+ +

Error Message 'Port may be in use'

+

+You can only start one instance of the H2 Console, +otherwise you will get the following error message: +"The Web server could not be started. Possible cause: another server is already running...". +It is possible to start multiple console applications on the same computer (using different ports), +but this is usually not required as the console supports multiple concurrent connections. +

+ +

Using another Port

+

+If the default port of the H2 Console is already in use by another application, +then a different port needs to be configured. The settings are stored in a properties file. +For details, see Settings of the H2 Console. +The relevant entry is webPort. +

+

+If no port is specified for the TCP and PG servers, each service will try to listen on its default port. +If the default port is already in use, a random port is used. +

+ +

Connecting to the Server using a Browser

+

+If the server started successfully, you can connect to it using a web browser. +Javascript needs to be enabled. +If you started the server on the same computer as the browser, open the URL http://localhost:8082. +If you want to connect to the application from another computer, you need to provide the IP address of the server, for example: +http://192.168.0.2:8082. +If you enabled TLS on the server side, the URL needs to start with https://. +

+ +

Multiple Concurrent Sessions

+

+Multiple concurrent browser sessions are supported. As that the database objects reside on the server, +the amount of concurrent work is limited by the memory available to the server application. +

+ +

Login

+

+At the login page, you need to provide connection information to connect to a database. +Set the JDBC driver class of your database, the JDBC URL, user name, and password. +If you are done, click [Connect]. +

+You can save and reuse previously saved settings. The settings are stored in a properties file +(see Settings of the H2 Console). +

+ +

Error Messages

+

+Error messages in are shown in red. You can show/hide the stack trace of the exception +by clicking on the message. +

+ +

Adding Database Drivers

+

+To register additional JDBC drivers (MySQL, PostgreSQL, HSQLDB,...), +add the jar file names to the environment variables H2DRIVERS or CLASSPATH. +Example (Windows): to add the HSQLDB JDBC driver +C:\Programs\hsqldb\lib\hsqldb.jar, set the environment variable +H2DRIVERS to +C:\Programs\hsqldb\lib\hsqldb.jar. +

+Multiple drivers can be set; entries need to be separated by ; (Windows) +or : (other operating systems). +Spaces in the path names are supported. The settings must not be quoted. +

+ +

Using the H2 Console

+

+The H2 Console application has three main panels: the toolbar on top, the tree on the left, and the query/result panel on the right. +The database objects (for example, tables) are listed on the left. +Type a SQL command in the query panel and click [Run]. The result appears just below the command. +

+ +

Inserting Table Names or Column Names

+

+To insert table and column names into the script, click on the item in the tree. +If you click on a table while the query is empty, then SELECT * FROM ... is added. +While typing a query, the table that was used is expanded in the tree. +For example if you type SELECT * FROM TEST T WHERE T. then the table TEST is expanded. +

+ +

Disconnecting and Stopping the Application

+

+To log out of the database, click [Disconnect] in the toolbar panel. +However, the server is still running and ready to accept new sessions. +

+To stop the server, right click on the system tray icon and select [Exit]. +If you don't have the system tray icon, +navigate to [Preferences] and click [Shutdown], +press [Ctrl]+[C] in the console where the server was started (Windows), +or close the console window. +

+ +

Special H2 Console Syntax

+

+The H2 Console supports a few built-in commands. +Those are interpreted within the H2 Console, so they work with any database. +Built-in commands need to be at the beginning of a statement (before any remarks), +otherwise they are not parsed correctly. If in doubt, add ; before the command. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Command(s)Description
+ @autocommit_true;
+ @autocommit_false; +
+ Enable or disable autocommit. +
+ @cancel; + + Cancel the currently running statement. +
+ @columns null null TEST;
+ @index_info null null TEST;
+ @tables;
+ @tables null null TEST;
+
+ Call the corresponding DatabaseMetaData.get method. + Patterns are case sensitive (usually identifiers are uppercase). + For information about the parameters, see the Javadoc documentation. + Missing parameters at the end of the line are set to null. The complete list of metadata commands is: + + @attributes, @best_row_identifier, @catalogs, @columns, + @column_privileges, @cross_references, @exported_keys, + @imported_keys, @index_info, @primary_keys, @procedures, + @procedure_columns, @pseudo_columns, @schemas, @super_tables, @super_types, + @tables, @table_privileges, @table_types, @type_info, @udts, + @version_columns + +
+ @edit select * from test; + + Use an updatable result set. +
+ @generated insert into test() values();
+ @generated(1) insert into test() values();
+ @generated(ID, "TIMESTAMP") insert into test() values(); +
+ Show the result of Statement.getGeneratedKeys(). + Names or one-based indexes of required columns can be optionally specified. +
+ @history; + + List the command history. +
+ @info; + + Display the result of various Connection and DatabaseMetaData methods. +
+ @list select * from test; + + Show the result set in list format (each column on its own line, with row numbers). +
+ @loop 1000 select ?, ?/*rnd*/;
+ @loop 1000 @statement select ?; +
+ Run the statement this many times. + Parameters (?) are set using a loop from 0 up to x - 1. + Random values are used for each ?/*rnd*/. + A Statement object is used instead of a PreparedStatement if @statement is used. + Result sets are read until ResultSet.next() returns false. + Timing information is printed. +
+ @maxrows 20; + + Set the maximum number of rows to display. +
+ @memory; + + Show the used and free memory. This will call System.gc(). +
+ @meta select 1; + + List the ResultSetMetaData after running the query. +
+ @parameter_meta select ?; + + Show the result of the PreparedStatement.getParameterMetaData() calls. + The statement is not executed. +
+ @prof_start;
+ call hash('SHA256', '', 1000000);
+ @prof_stop; +
+ Start/stop the built-in profiling tool. + The top 3 stack traces of the statement(s) between start and stop are listed + (if there are 3). +
+ @prof_start;
+ @sleep 10;
+ @prof_stop; +
+ Sleep for a number of seconds. Used to profile a long running query or + operation that is running in another session (but in the same process). +
+ @transaction_isolation;
+ @transaction_isolation 2; +
+ Display (without parameters) or change + (with parameters 1, 2, 4, 8) the transaction isolation level. +
+ +

Settings of the H2 Console

+

+The settings of the H2 Console are stored in a configuration file +called .h2.server.properties in you user home directory. +For Windows installations, the user home directory is usually +C:\Documents and Settings\[username] or +C:\Users\[username]. +The configuration file contains the settings of the application and is automatically created when the H2 Console is first started. +Supported settings are: +

+
  • webAllowOthers: allow other computers to connect. +
  • webPort: the port of the H2 Console +
  • webSSL: use encrypted TLS (HTTPS) connections. +
  • webAdminPassword: password to access preferences and tools of H2 Console. +
+

+In addition to those settings, the properties of the last recently used connection +are listed in the form +<number>=<name>|<driver>|<url>|<user> +using the escape character \. +Example: +1=Generic H2 (Embedded)|org.h2.Driver|jdbc\:h2\:~/test|sa +

+ +

Connecting to a Database using JDBC

+

+To connect to a database, a Java application first needs to load the database driver, +and then get a connection. A simple way to do that is using the following code: +

+
+import java.sql.*;
+public class Test {
+    public static void main(String[] a)
+            throws Exception {
+        Connection conn = DriverManager.
+            getConnection("jdbc:h2:~/test", "sa", "");
+        // add application code here
+        conn.close();
+    }
+}
+
+

+This code opens a connection (using DriverManager.getConnection()). +The driver name is "org.h2.Driver". +The database URL always needs to start with jdbc:h2: +to be recognized by this database. The second parameter in the getConnection() call +is the user name (sa for System Administrator in this example). The third parameter is the password. +In this database, user names are not case sensitive, but passwords are. +

+ +

Creating New Databases

+

+By default, if the database specified in the embedded URL does not yet exist, +a new (empty) database is created automatically. +The user that created the database automatically becomes the administrator of this database. +

+

+Auto-creation of databases can be disabled, see +Opening a Database Only if it Already Exists. +

+

+H2 Console does not allow creation of databases unless a browser window is opened by Console during its +startup or from its icon in the system tray and remote access is not enabled. +A context menu of the tray icon can also be used to create a new database. +

+

+You can also create a new local database from a command line with a Shell tool: +

+
+> java -cp h2-*.jar org.h2.tools.Shell
+
+Welcome to H2 Shell
+Exit with Ctrl+C
+[Enter]   jdbc:h2:mem:2
+URL       jdbc:h2:./path/to/database
+[Enter]   org.h2.Driver
+Driver
+[Enter]   sa
+User      your_username
+Password  (hidden)
+Type the same password again to confirm database creation.
+Password  (hidden)
+Connected
+
+sql> quit
+Connection closed
+
+

+By default remote creation of databases from a TCP connection or a web interface is not allowed. +It's not recommended to enable remote creation of databases due to security reasons. +User who creates a new database becomes its administrator and therefore gets the same access to your JVM as H2 has +and the same access to your operating system as Java and your system account allows. +It's recommended to create all databases locally using an embedded URL, local H2 Console, or the Shell tool. +

+

+If you really need to allow remote database creation, you can pass -ifNotExists parameter to +TCP, PG, or Web servers (but not to the Console tool). +Its combination with -tcpAllowOthers, -pgAllowOthers, or -webAllowOthers +effectively creates a remote security hole in your system, if you use it, always guard your ports with a firewall +or some other solution and use such combination of settings only in trusted networks. +

+

+H2 Servlet also supports such option. +When you use it always protect the servlet with security constraints, +see Using the H2 Console Servlet for example; +don't forget to uncomment and adjust security configuration for your needs. +

+ +

Using the Server

+

+H2 currently supports three server: a web server (for the H2 Console), +a TCP server (for client/server connections) and an PG server (for PostgreSQL clients). +Please note that only the web server supports browser connections. +The servers can be started in different ways, one is using the Server tool. +Starting the server doesn't open a database - databases are opened as soon as a client connects. +

+ +

Starting the Server Tool from Command Line

+

+To start the Server tool from the command line with the default settings, run: +

+
+java -cp h2*.jar org.h2.tools.Server
+
+

+This will start the tool with the default options. To get the list of options and default values, run: +

+
+java -cp h2*.jar org.h2.tools.Server -?
+
+

+There are options available to use other ports, and start or not start parts. +

+ +

Connecting to the TCP Server

+

+To remotely connect to a database using the TCP server, use the following driver and database URL: +

+
    +
  • JDBC driver class: org.h2.Driver +
  • Database URL: jdbc:h2:tcp://localhost/~/test +
+

+For details about the database URL, see also in Features. +Please note that you can't connection with a web browser to this URL. +You can only connect using a H2 client (over JDBC). +

+ +

Starting the TCP Server within an Application

+

+Servers can also be started and stopped from within an application. Sample code: +

+
+import org.h2.tools.Server;
+...
+// start the TCP Server
+Server server = Server.createTcpServer(args).start();
+...
+// stop the TCP Server
+server.stop();
+
+ +

Stopping a TCP Server from Another Process

+

+The TCP server can be stopped from another process. +To stop the server from the command line, run: +

+
+java org.h2.tools.Server -tcpShutdown tcp://localhost:9092 -tcpPassword password
+
+

+To stop the server from a user application, use the following code: +

+
+org.h2.tools.Server.shutdownTcpServer("tcp://localhost:9092", "password", false, false);
+
+

+This function will only stop the TCP server. +If other server were started in the same process, they will continue to run. +To avoid recovery when the databases are opened the next time, +all connections to the databases should be closed before calling this method. +To stop a remote server, remote connections must be enabled on the server. +Shutting down a TCP server is protected using the option -tcpPassword +(the same password must be used to start and stop the TCP server). +

+ +

Using Hibernate

+

+This database supports Hibernate version 3.1 and newer. You can use the HSQLDB Dialect, +or the native H2 Dialect. +

+

+When using Hibernate, try to use the H2Dialect if possible. +When using the H2Dialect, +compatibility modes such as MODE=MySQL are not supported. +When using such a compatibility mode, use the Hibernate dialect for the +corresponding database instead of the H2Dialect; +but please note H2 does not support all features of all databases. +

+ + +

+To use H2 with Glassfish (or Sun AS), set the Datasource Classname to +org.h2.jdbcx.JdbcDataSource. You can set this in the GUI +at Application Server - Resources - JDBC - Connection Pools, +or by editing the file sun-resources.xml: at element +jdbc-connection-pool, set the attribute +datasource-classname to org.h2.jdbcx.JdbcDataSource. +

+

+The H2 database is compatible with HSQLDB and PostgreSQL. +To take advantage of H2 specific features, use the H2Platform. +The source code of this platform is included in H2 at +src/tools/oracle/toplink/essentials/platform/database/DatabasePlatform.java.txt. +You will need to copy this file to your application, and rename it to .java. +To enable it, change the following setting in persistence.xml: +

+
+<property
+    name="toplink.target-database"
+    value="oracle.toplink.essentials.platform.database.H2Platform"/>
+
+

+In old versions of Glassfish, the property name is toplink.platform.class.name. +

+

+To use H2 within Glassfish, copy the h2*.jar to the directory glassfish/glassfish/lib. +

+ + +

+To use H2 in EclipseLink, use the platform class org.eclipse.persistence.platform.database.H2Platform. +If this platform is not available in your version of EclipseLink, you can use the OraclePlatform instead in many case. +See also H2Platform. +

+ +

Using Apache ActiveMQ

+

+When using H2 as the backend database for Apache ActiveMQ, please use the TransactDatabaseLocker +instead of the default locking mechanism. Otherwise the database file will grow without bounds. The problem is that the +default locking mechanism uses an uncommitted UPDATE transaction, which keeps the transaction log +from shrinking (causes the database file to grow). Instead of using an UPDATE statement, the TransactDatabaseLocker uses +SELECT ... FOR UPDATE which is not problematic. +To use it, change the ApacheMQ configuration element <jdbcPersistenceAdapter> element, property +databaseLocker="org.apache.activemq.store.jdbc.adapter.TransactDatabaseLocker". +However, using the MVCC mode will again result in the same problem. Therefore, please do not use the MVCC mode in this case. +Another (more dangerous) solution is to set useDatabaseLock to false. +

+ +

Using H2 within NetBeans

+

+There is a known issue when using the Netbeans SQL Execution Window: +before executing a query, another query in the form SELECT COUNT(*) FROM <query> is run. +This is a problem for queries that modify state, such as SELECT NEXT VALUE FOR SEQ. +In this case, two sequence values are allocated instead of just one. +

+ +

Using H2 with jOOQ

+

+jOOQ adds a thin layer on top of JDBC, allowing for type-safe SQL construction, +including advanced SQL, stored procedures and advanced data types. +jOOQ takes your database schema as a base for code generation. +If this is your example schema: +

+
+CREATE TABLE USER (ID INT, NAME VARCHAR(50));
+
+

+then run the jOOQ code generator on the command line using this command: +

+
+java -cp jooq.jar;jooq-meta.jar;jooq-codegen.jar;h2-1.4.199.jar;.
+org.jooq.util.GenerationTool /codegen.xml
+
+

+...where codegen.xml is on the classpath and contains this information +

+
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<configuration xmlns="http://www.jooq.org/xsd/jooq-codegen-3.11.0.xsd">
+    <jdbc>
+        <driver>org.h2.Driver</driver>
+        <url>jdbc:h2:~/test</url>
+        <user>sa</user>
+        <password></password>
+    </jdbc>
+    <generator>
+        <database>
+            <includes>.*</includes>
+            <excludes></excludes>
+            <inputSchema>PUBLIC</inputSchema>
+        </database>
+        <target>
+            <packageName>org.jooq.h2.generated</packageName>
+            <directory>./src</directory>
+        </target>
+    </generator>
+</configuration>
+
+

+Using the generated source, you can query the database as follows: +

+
+DSLContext dsl = DSL.using(connection);
+Result<UserRecord> result =
+dsl.selectFrom(USER)
+    .where(NAME.like("Johnny%"))
+    .orderBy(ID)
+    .fetch();
+
+

+See more details on jOOQ Homepage +and in the jOOQ Tutorial +

+ +

Using Databases in Web Applications

+

+There are multiple ways to access a database from within web +applications. Here are some examples if you use Tomcat or JBoss. +

+ +

Embedded Mode

+

+The (currently) simplest solution is to use the database in the +embedded mode, that means open a connection in your application when +it starts (a good solution is using a Servlet Listener, see below), or +when a session starts. A database can be accessed from multiple +sessions and applications at the same time, as long as they run in the +same process. Most Servlet Containers (for example Tomcat) are just +using one process, so this is not a problem (unless you run Tomcat in +clustered mode). Tomcat uses multiple threads and multiple +classloaders. If multiple applications access the same database at the +same time, you need to put the database jar in the shared/lib or +server/lib directory. It is a good idea to open the database when the +web application starts, and close it when the web application stops. +If using multiple applications, only one (any) of them needs to do +that. In the application, an idea is to use one connection per +Session, or even one connection per request (action). Those +connections should be closed after use if possible (but it's not that +bad if they don't get closed). +

+ +

Server Mode

+

+The server mode is similar, but it allows you to run the server in another process. +

+ +

Using a Servlet Listener to Start and Stop a Database

+

+Add the h2*.jar file to your web application, and +add the following snippet to your web.xml file (between the +context-param and the filter section): +

+
+<listener>
+    <listener-class>org.h2.server.web.DbStarter</listener-class>
+</listener>
+
+

+If your servlet container is already Servlet 5-compatible, use the following +snippet instead: +

+
+<listener>
+    <listener-class>org.h2.server.web.JakartaDbStarter</listener-class>
+</listener>
+
+

+For details on how to access the database, see the file DbStarter.java. +By default this tool opens an embedded connection +using the database URL jdbc:h2:~/test, +user name sa, and password sa. +If you want to use this connection within your servlet, you can access as follows: +

+
+Connection conn = getServletContext().getAttribute("connection");
+
+

+DbStarter can also start the TCP server, however this is disabled by default. +To enable it, use the parameter db.tcpServer in the file web.xml. +Here is the complete list of options. +These options need to be placed between the description tag +and the listener / filter tags: +

+
+<context-param>
+    <param-name>db.url</param-name>
+    <param-value>jdbc:h2:~/test</param-value>
+</context-param>
+<context-param>
+    <param-name>db.user</param-name>
+    <param-value>sa</param-value>
+</context-param>
+<context-param>
+    <param-name>db.password</param-name>
+    <param-value>sa</param-value>
+</context-param>
+<context-param>
+    <param-name>db.tcpServer</param-name>
+    <param-value>-tcpAllowOthers</param-value>
+</context-param>
+
+

+When the web application is stopped, the database connection will be closed automatically. +If the TCP server is started within the DbStarter, it will also be stopped automatically. +

+ +

Using the H2 Console Servlet

+

+The H2 Console is a standalone application and includes its own web server, but it can be +used as a servlet as well. To do that, include the h2*.jar file in your application, and +add the following configuration to your web.xml: +

+
+<servlet>
+    <servlet-name>H2Console</servlet-name>
+    <servlet-class>org.h2.server.web.WebServlet</servlet-class>
+    <!--
+    <init-param>
+        <param-name>webAllowOthers</param-name>
+        <param-value></param-value>
+    </init-param>
+    <init-param>
+        <param-name>trace</param-name>
+        <param-value></param-value>
+    </init-param>
+    -->
+    <load-on-startup>1</load-on-startup>
+</servlet>
+<servlet-mapping>
+    <servlet-name>H2Console</servlet-name>
+    <url-pattern>/console/*</url-pattern>
+</servlet-mapping>
+<!--
+<security-role>
+    <role-name>admin</role-name>
+</security-role>
+<security-constraint>
+    <web-resource-collection>
+        <web-resource-name>H2 Console</web-resource-name>
+        <url-pattern>/console/*</url-pattern>
+    </web-resource-collection>
+    <auth-constraint>
+        <role-name>admin</role-name>
+    </auth-constraint>
+</security-constraint>
+-->
+
+

+For details, see also src/tools/WEB-INF/web.xml. +

+

+If your application is already Servlet 5-compatible, use the servlet class +org.h2.server.web.JakartaWebServlet instead. +

+

+To create a web application with just the H2 Console, run the following command: +

+
+build warConsole
+
+ +

CSV (Comma Separated Values) Support

+

+The CSV file support can be used inside the database using the functions +CSVREAD and CSVWRITE, +or it can be used outside the database as a standalone tool. +

+ +

Reading a CSV File from Within a Database

+

+A CSV file can be read using the function CSVREAD. Example: +

+
+SELECT * FROM CSVREAD('test.csv');
+
+

+Please note for performance reason, CSVREAD should not be used inside a join. +Instead, import the data first (possibly into a temporary table), create the required indexes +if necessary, and then query this table. +

+ +

Importing Data from a CSV File

+

+A fast way to load or import data (sometimes called 'bulk load') from a CSV file is +to combine table creation with import. +Optionally, the column names and data types can be set when creating the table. +Another option is to use INSERT INTO ... SELECT. +

+
+CREATE TABLE TEST AS SELECT * FROM CSVREAD('test.csv');
+CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))
+    AS SELECT * FROM CSVREAD('test.csv');
+
+ +

Writing a CSV File from Within a Database

+

+The built-in function CSVWRITE can be used to create a CSV file from a query. +Example: +

+
+CREATE TABLE TEST(ID INT, NAME VARCHAR);
+INSERT INTO TEST VALUES(1, 'Hello'), (2, 'World');
+CALL CSVWRITE('test.csv', 'SELECT * FROM TEST');
+
+ +

Writing a CSV File from a Java Application

+

+The Csv tool can be used in a Java application even when not using a database at all. +Example: +

+
+import java.sql.*;
+import org.h2.tools.Csv;
+import org.h2.tools.SimpleResultSet;
+public class TestCsv {
+    public static void main(String[] args) throws Exception {
+        SimpleResultSet rs = new SimpleResultSet();
+        rs.addColumn("NAME", Types.VARCHAR, 255, 0);
+        rs.addColumn("EMAIL", Types.VARCHAR, 255, 0);
+        rs.addRow("Bob Meier", "bob.meier@abcde.abc");
+        rs.addRow("John Jones", "john.jones@abcde.abc");
+        new Csv().write("data/test.csv", rs, null);
+    }
+}
+
+ +

Reading a CSV File from a Java Application

+

+It is possible to read a CSV file without opening a database. +Example: +

+
+import java.sql.*;
+import org.h2.tools.Csv;
+public class TestCsv {
+    public static void main(String[] args) throws Exception {
+        ResultSet rs = new Csv().read("data/test.csv", null, null);
+        ResultSetMetaData meta = rs.getMetaData();
+        while (rs.next()) {
+            for (int i = 0; i < meta.getColumnCount(); i++) {
+                System.out.println(
+                    meta.getColumnLabel(i + 1) + ": " +
+                    rs.getString(i + 1));
+            }
+            System.out.println();
+        }
+        rs.close();
+    }
+}
+
+ +

Upgrade, Backup, and Restore

+ +

Database Upgrade

+

+The recommended way to upgrade from one version of the database engine to the next +version is to create a backup of the database (in the form of a SQL script) using the old engine, +and then execute the SQL script using the new engine. +

+ +

Backup using the Script Tool

+

+The recommended way to backup a database is to create a compressed SQL script file. +This will result in a small, human readable, and database version independent backup. +Creating the script will also verify the checksums of the database file. +The Script tool is ran as follows: +

+
+java org.h2.tools.Script -url jdbc:h2:~/test -user sa -script test.zip -options compression zip
+
+

+It is also possible to use the SQL command SCRIPT to create the backup of the database. +For more information about the options, see the SQL command SCRIPT. +The backup can be done remotely, however the file will be created on the server side. +The built in FTP server could be used to retrieve the file from the server. +

+ +

Restore from a Script

+

+To restore a database from a SQL script file, you can use the RunScript tool: +

+
+java org.h2.tools.RunScript -url jdbc:h2:~/test -user sa -script test.zip -options compression zip
+
+

+For more information about the options, see the SQL command RUNSCRIPT. +The restore can be done remotely, however the file needs to be on the server side. +The built in FTP server could be used to copy the file to the server. +It is also possible to use the SQL command RUNSCRIPT to execute a SQL script. +SQL script files may contain references to other script files, in the form of +RUNSCRIPT commands. However, when using the server mode, the references script files +need to be available on the server side. +

+ +

+If the script was generated by H2 1.4.200 or an older version, add VARIABLE_BINARY option to import it +into more recent version. +

+ +
+java org.h2.tools.RunScript -url jdbc:h2:~/test -user sa -script test.zip -options compression zip variable_binary
+
+ +

Online Backup

+

+The BACKUP SQL statement and the Backup tool both create a zip file +with the database file. However, the contents of this file are not human readable. +

+The resulting backup is transactionally consistent, meaning the consistency and atomicity rules apply. +

+
+BACKUP TO 'backup.zip'
+
+

+The Backup tool (org.h2.tools.Backup) can not be used to create a online backup; +the database must not be in use while running this program. +

+

+Creating a backup by copying the database files while the database is running is not supported, +except if the file systems support creating snapshots. +With other file systems, it can't be guaranteed that the data is copied in the right order. +

+ +

Command Line Tools

+

+This database comes with a number of command line tools. To get more information about a tool, +start it with the parameter '-?', for example: +

+
+java -cp h2*.jar org.h2.tools.Backup -?
+
+

+The command line tools are: +

+
  • Backup creates a backup of a database. +
  • ChangeFileEncryption allows changing the file encryption password or algorithm of a database. +
  • Console starts the browser based H2 Console. +
  • ConvertTraceFile converts a .trace.db file to a Java application and SQL script. +
  • CreateCluster creates a cluster from a standalone database. +
  • DeleteDbFiles deletes all files belonging to a database. +
  • Recover helps recovering a corrupted database. +
  • Restore restores a backup of a database. +
  • RunScript runs a SQL script against a database. +
  • Script allows converting a database to a SQL script for backup or migration. +
  • Server is used in the server mode to start a H2 server. +
  • Shell is a command line database tool. +
+

+The tools can also be called from an application by calling the main or another public method. +For details, see the Javadoc documentation. +

+ +

The Shell Tool

+

+The Shell tool is a simple interactive command line tool. To start it, type: +

+
+java -cp h2*.jar org.h2.tools.Shell
+
+

+You will be asked for a database URL, JDBC driver, user name, and password. +The connection setting can also be set as command line parameters. +After connecting, you will get the list of options. +The built-in commands don't need to end with a semicolon, but +SQL statements are only executed if the line ends with a semicolon ;. +This allows to enter multi-line statements: +

+
+sql> select * from test
+...> where id = 0;
+
+

+By default, results are printed as a table. For results with many column, consider using the list mode: +

+
+sql> list
+Result list mode is now on
+sql> select * from test;
+ID  : 1
+NAME: Hello
+
+ID  : 2
+NAME: World
+(2 rows, 0 ms)
+
+ +

Using OpenOffice Base

+

+OpenOffice.org Base supports database access over the JDBC API. To connect to a H2 database +using OpenOffice Base, you first need to add the JDBC driver to OpenOffice. +The steps to connect to a H2 database are: +

+
  • Start OpenOffice Writer, go to [Tools], [Options] +
  • Make sure you have selected a Java runtime environment in OpenOffice.org / Java +
  • Click [Class Path...], [Add Archive...] +
  • Select your h2 jar file (location is up to you, could be wherever you choose) +
  • Click [OK] (as much as needed), stop OpenOffice (including the Quickstarter) +
  • Start OpenOffice Base +
  • Connect to an existing database; select [JDBC]; [Next] +
  • Example datasource URL: jdbc:h2:~/test +
  • JDBC driver class: org.h2.Driver +
+

+Now you can access the database stored in the current users home directory. +

+

+To use H2 in NeoOffice (OpenOffice without X11): +

+
  • In NeoOffice, go to [NeoOffice], [Preferences] +
  • Look for the page under [NeoOffice], [Java] +
  • Click [Class Path], [Add Archive...] +
  • Select your h2 jar file (location is up to you, could be wherever you choose) +
  • Click [OK] (as much as needed), restart NeoOffice. +
+

+Now, when creating a new database using the "Database Wizard" : +

+
  • Click [File], [New], [Database]. +
  • Select [Connect to existing database] and the select [JDBC]. Click next. +
  • Example datasource URL: jdbc:h2:~/test +
  • JDBC driver class: org.h2.Driver +
+

+Another solution to use H2 in NeoOffice is: +

+
  • Package the h2 jar within an extension package +
  • Install it as a Java extension in NeoOffice +
+

+This can be done by create it using the NetBeans OpenOffice plugin. +See also Extensions Development. +

+ +

Java Web Start / JNLP

+

+When using Java Web Start / JNLP (Java Network Launch Protocol), permissions tags must be set in the .jnlp file, +and the application .jar file must be signed. Otherwise, when trying to write to the file system, the following +exception will occur: java.security.AccessControlException: +access denied (java.io.FilePermission ... read). +Example permission tags: +

+
+<security>
+    <all-permissions/>
+</security>
+
+ +

Using a Connection Pool

+

+For H2, opening a connection is fast if the database is already open. +Still, using a connection pool improves performance if you open and close connections a lot. +A simple connection pool is included in H2. It is based on the +Mini Connection Pool Manager +from Christian d'Heureuse. There are other, more complex, open source connection pools available, +for example the Apache Commons DBCP. +For H2, it is about twice as faster to get a connection from the built-in connection pool than to get +one using DriverManager.getConnection().The build-in connection pool is used as follows: +

+
+import java.sql.*;
+import org.h2.jdbcx.JdbcConnectionPool;
+public class Test {
+    public static void main(String[] args) throws Exception {
+        JdbcConnectionPool cp = JdbcConnectionPool.create(
+            "jdbc:h2:~/test", "sa", "sa");
+        for (int i = 0; i < args.length; i++) {
+            Connection conn = cp.getConnection();
+            conn.createStatement().execute(args[i]);
+            conn.close();
+        }
+        cp.dispose();
+    }
+}
+
+ +

Fulltext Search

+

+H2 includes two fulltext search implementations. One is using Apache Lucene, +and the other (the native implementation) stores the index data in special +tables in the database. +

+ +

Using the Native Fulltext Search

+

+To initialize, call: +

+
+CREATE ALIAS IF NOT EXISTS FT_INIT FOR "org.h2.fulltext.FullText.init";
+CALL FT_INIT();
+
+

+You need to initialize it in each database where you want to use it. +Afterwards, you can create a fulltext index for a table using: +

+
+CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR);
+INSERT INTO TEST VALUES(1, 'Hello World');
+CALL FT_CREATE_INDEX('PUBLIC', 'TEST', NULL);
+
+

+PUBLIC is the schema name, TEST is the table name. The list of column names (comma separated) is optional, +in this case all columns are indexed. The index is updated in realtime. +To search the index, use the following query: +

+
+SELECT * FROM FT_SEARCH('Hello', 0, 0);
+
+

+This will produce a result set that contains the query needed to retrieve the data: +

+
+QUERY: "PUBLIC"."TEST" WHERE "ID"=1
+
+

+To drop an index on a table: +

+
+CALL FT_DROP_INDEX('PUBLIC', 'TEST');
+
+

+To get the raw data, use FT_SEARCH_DATA('Hello', 0, 0);. +The result contains the columns SCHEMA (the schema name), +TABLE (the table name), +COLUMNS (an array of column names), and +KEYS (an array of objects). +To join a table, use a join as in: +SELECT T.* FROM FT_SEARCH_DATA('Hello', 0, 0) FT, TEST T +WHERE FT.TABLE='TEST' AND T.ID=FT.KEYS[0]; +

+

+You can also call the index from within a Java application: +

+
+org.h2.fulltext.FullText.search(conn, text, limit, offset);
+org.h2.fulltext.FullText.searchData(conn, text, limit, offset);
+
+ +

Using the Apache Lucene Fulltext Search

+

+To use the Apache Lucene full text search, you need the Lucene library in the classpath. +Apache Lucene 8.5.2 or binary compatible version is required. +How to do that depends on the application; if you use the H2 Console, you can add the Lucene +jar file to the environment variables H2DRIVERS or +CLASSPATH. +To initialize the Lucene fulltext search in a database, call: +

+
+CREATE ALIAS IF NOT EXISTS FTL_INIT FOR "org.h2.fulltext.FullTextLucene.init";
+CALL FTL_INIT();
+
+

+You need to initialize it in each database where you want to use it. +Afterwards, you can create a full text index for a table using: +

+
+CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR);
+INSERT INTO TEST VALUES(1, 'Hello World');
+CALL FTL_CREATE_INDEX('PUBLIC', 'TEST', NULL);
+
+

+PUBLIC is the schema name, TEST is the table name. The list of column names (comma separated) is optional, +in this case all columns are indexed. The index is updated in realtime. To search the index, +use the following query: +

+
+SELECT * FROM FTL_SEARCH('Hello', 0, 0);
+
+

+This will produce a result set that contains the query needed to retrieve the data: +

+
+QUERY: "PUBLIC"."TEST" WHERE "ID"=1
+
+

+To drop an index on a table +(be warned that this will re-index all of the full-text indices for the entire database): +

+
+CALL FTL_DROP_INDEX('PUBLIC', 'TEST');
+
+

+To get the raw data, use FTL_SEARCH_DATA('Hello', 0, 0);. +The result contains the columns SCHEMA (the schema name), +TABLE (the table name), +COLUMNS (an array of column names), +and KEYS (an array of objects). To join a table, use a join as in: +SELECT T.* FROM FTL_SEARCH_DATA('Hello', 0, 0) FT, TEST T +WHERE FT.TABLE='TEST' AND T.ID=FT.KEYS[0]; +

+

+You can also call the index from within a Java application: +

+
+org.h2.fulltext.FullTextLucene.search(conn, text, limit, offset);
+org.h2.fulltext.FullTextLucene.searchData(conn, text, limit, offset);
+
+

+The Lucene fulltext search supports searching in specific column only. +Column names must be uppercase (except if the original columns are double quoted). +For column names starting with an underscore (_), another underscore needs to be added. +Example: +

+
+CREATE ALIAS IF NOT EXISTS FTL_INIT FOR "org.h2.fulltext.FullTextLucene.init";
+CALL FTL_INIT();
+DROP TABLE IF EXISTS TEST;
+CREATE TABLE TEST(ID INT PRIMARY KEY, FIRST_NAME VARCHAR, LAST_NAME VARCHAR);
+CALL FTL_CREATE_INDEX('PUBLIC', 'TEST', NULL);
+INSERT INTO TEST VALUES(1, 'John', 'Wayne');
+INSERT INTO TEST VALUES(2, 'Elton', 'John');
+SELECT * FROM FTL_SEARCH_DATA('John', 0, 0);
+SELECT * FROM FTL_SEARCH_DATA('LAST_NAME:John', 0, 0);
+CALL FTL_DROP_ALL();
+
+ +

User-Defined Variables

+

+This database supports user-defined variables. Variables start with @ and can be used wherever +expressions or parameters are allowed. Variables are not persisted and session scoped, that means only visible +from within the session in which they are defined. A value is usually assigned using the SET command: +

+
+SET @USER = 'Joe';
+
+

+The value can also be changed using the SET() method. This is useful in queries: +

+
+SET @TOTAL = NULL;
+SELECT X, SET(@TOTAL, COALESCE(@TOTAL, 1.) * X) F FROM SYSTEM_RANGE(1, 50);
+
+

+Variables that are not set evaluate to NULL. +The data type of a user-defined variable is the data type +of the value assigned to it, that means it is not necessary (or possible) to declare variable names before using them. +There are no restrictions on the assigned values; large objects (LOBs) are supported as well. +Rolling back a transaction does not affect the value of a user-defined variable. +

+ +

Date and Time

+

+Date, time and timestamp values support standard literals: +

+
+VALUES (
+    DATE '2008-01-01',
+    TIME '12:00:00',
+    TIME WITH TIME ZONE '12:00:00+01:00',
+    TIMESTAMP '2008-01-01 12:00:00',
+    TIMESTAMP WITH TIME ZONE '2008-01-01 12:00:00+01:00'
+);
+
+

+ISO 8601-style datetime formats with T instead of space between date and time parts are also supported. +

+

+TIME and TIMESTAMP values are preserved without time zone information as local time. +That means if you store the value '2000-01-01 12:00:00' in one time zone, then change time zone of the session +you will also get '2000-01-01 12:00:00', the value will not be adjusted to the new time zone, +therefore its absolute value in UTC may be different. +

+

+TIME WITH TIME ZONE and TIMESTAMP WITH TIME ZONE values preserve the specified time zone offset +and if you store the value '2008-01-01 12:00:00+01:00' it also remains the same +even if you change time zone of the session, +and because it has a time zone offset its absolute value in UTC will be the same. +TIMESTAMP WITH TIME ZONE values may be also specified with time zone name like '2008-01-01 12:00:00 Europe/Berlin'. +It that case this name will be converted into time zone offset. +Names of time zones are not stored. +

+ +

Using Spring

+

Using the TCP Server

+

+Use the following configuration to start and stop the H2 TCP server using the Spring Framework: +

+
+<bean id = "org.h2.tools.Server"
+            class="org.h2.tools.Server"
+            factory-method="createTcpServer"
+            init-method="start"
+            destroy-method="stop">
+    <constructor-arg value="-tcp,-tcpAllowOthers,-tcpPort,8043" />
+</bean>
+
+

+The destroy-method will help prevent exceptions on hot-redeployment or when restarting the server. +

+ +

OSGi

+

+The standard H2 jar can be dropped in as a bundle in an OSGi container. +H2 implements the JDBC Service defined in OSGi Service Platform Release 4 Version 4.2 Enterprise Specification. +The H2 Data Source Factory service is registered with the following properties: +OSGI_JDBC_DRIVER_CLASS=org.h2.Driver +and OSGI_JDBC_DRIVER_NAME=H2 JDBC Driver. +The OSGI_JDBC_DRIVER_VERSION +property reflects the version of the driver as is. +

+

+The following standard configuration properties are supported: +JDBC_USER, JDBC_PASSWORD, JDBC_DESCRIPTION, JDBC_DATASOURCE_NAME, JDBC_NETWORK_PROTOCOL, JDBC_URL, JDBC_SERVER_NAME, JDBC_PORT_NUMBER. +Any other standard property will be rejected. +Non-standard properties will be passed on to H2 in the connection URL. +

+ +

Java Management Extension (JMX)

+

+Management over JMX is supported, but not enabled by default. +To enable JMX, append ;JMX=TRUE to the database URL when opening the database. +Various tools support JMX, one such tool is the jconsole. +When opening the jconsole, connect to the process where the database is open +(when using the server mode, you need to connect to the server process). Then go to the MBeans section. +Under org.h2 you will find one entry per database. The object name of the entry +is the database short name, plus the path (each colon is replaced with an underscore character). +

+

+The following attributes and operations are supported: +

+
  • CacheSize: the cache size currently in use in KB. +
  • CacheSizeMax (read/write): the maximum cache size in KB. +
  • Exclusive: whether this database is open in exclusive mode or not. +
  • FileReadCount: the number of file read operations since the database was opened. +
  • FileSize: the file size in KB. +
  • FileWriteCount: the number of file write operations since the database was opened. +
  • FileWriteCountTotal: the number of file write operations since the database was created. +
  • LogMode (read/write): the current transaction log mode. See SET LOG for details. +
  • Mode: the compatibility mode (REGULAR if no compatibility mode is used). +
  • MultiThreaded: true if multi-threaded is enabled. +
  • Mvcc: true if MVCC is enabled. +
  • ReadOnly: true if the database is read-only. +
  • TraceLevel (read/write): the file trace level. +
  • Version: the database version in use. +
  • listSettings: list the database settings. +
  • listSessions: list the open sessions, including currently executing statement (if any) and locked tables (if any). +
+

+To enable JMX, you may need to set the system properties com.sun.management.jmxremote and +com.sun.management.jmxremote.port as required by the JVM. +

+
+ diff --git a/h2/src/docsrc/images/connection-mode-embedded-2.png b/h2/src/docsrc/images/connection-mode-embedded-2.png new file mode 100644 index 0000000000000000000000000000000000000000..7da33a3ad9345a54629cbc2129a2034a70dfc636 GIT binary patch literal 48466 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 Sa-bs(^@B3DM6?;+O!_~}tLW1J literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/images/connection-mode-mixed-2.png b/h2/src/docsrc/images/connection-mode-mixed-2.png new file mode 100644 index 0000000000000000000000000000000000000000..fd4afcdd2891ee3a4765dedad9bde364a96979c7 GIT binary patch literal 70685 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

FYe?M0NcwJHjO5fI9OF2r60nZu3CI89L%;kHD#8 z*Ls~WuoN%X9b7)@#)>y_fFnZ)z6V{xtXKiamvGOx4+M)~qXJ@jMGSnqy{rwXFYgoSu9_5jP9!ALVPO8SkQNg6%_x=noUs)9N68GH6!D;-O!CR&oy}k%Q|L9?FLcW0 zMO^>^Xr8ISDuS5_~2p^+y%`JJ#ZyZGgdfEUd-c#8Fv0T7<{t~Qx8-msbDrG#( zoad?JO;|53Kzr7X^_QO8miOy`%k$mQszXRC{Vm5xsjU0UV@V1pspoyIuFor}PO>C; zq~pAKoS0{5yZKO6c#`+^G#zwH=2|&z89nr(Ex3F71jpNLyyU`kXL&?18jq{#O?OsTas5TXL8LNT9N^{=cY7TOaE*WuIa_mEOzYamoS1DUd~-U{YS?So&dVkf z!2^3+VQ({0&vZIn+fQand_7o( zYcw!@PY`?E08(|6__wiB2gB<4JY6aybzlK)KSHrXXr%z}tpuk~bE;75gwD2$*d)`_ zrC!#MNdURry@exVDPn>bJccN9&9P{HFN*}CRP^HhHE}xR`qFNy?XcR>o+CHCdZo@1 zz!tF8ZSxJyjJbQqoU=4Ue)h8M@0eG?z#8CfXR_PcWaub6n3VumTPj4w%vl@6YdBOR z(IULdZC#mWG)a`o&O1nS=+uSiU12Jr_o5`0ZFk&wBaX-^II2h*K?`xt_s0fK#UD;i zi5Ce51d#Vv26W_i@?21>4=)-PjULYactJOrnRXpO0GnDXSj^vD&vxBi^rPzJyHCLo z5e5`mW0S#Urve>Pd$+cP>kK8%ZL$$?_k63J&QpIOPSnRsC0!MXFAmBRjN7@Q7t6q+ znL_E){5f@YRX>Zoy$o{yBEY}k=4>6IM%Yl_hDxR5GRT zkJJL|clTlympD}?tc2awmR(O(?WP5duipw%Yba~sRd(YDeDe?5Fpp7$1`-z#o`*7U_&kE44gfeZWG1F4O*2erQHxg?`iLWm@VXZksk&UF^wV7eQ#?k*C*364D5?0EWMr(HZBw*VQ(?gz^ooP*9_2 z!HNRvXDh%4gF2)YJ^yO6@>hF$l2Fl@`y_rU)R%cx?Uh5Oa=l=_sQ01jiBD;n;UQ!U~ITI zt&Zu*#%sH#YXD?u2>pF}Sl%~Mb=_#%l$E~P zlK92J<0Ss*iVOf1ugxDFYVuTyF4{__Ze20`Fu%wI^+V4#T*0o>9W~St8`%7+=@b!M z$q-^lPbqU_l*ILbt;M?Fa(nx{G((UAn`=c#B^}i61fK!!=fcz4>XYHX_5l_f z(8F$q*W`R2;62lhcoh5ShD&AtY?_!bDWC(pf-W{EcFKCZ1ew5cf+>KcK@D?qGe`g)t!$=(89_w&`Uk; z4eb4<@r9M2)=L?ULBpbNGRUoZ^@b6rwWbE={t9-bYU&2~e!YyM_ ziEues^vlhg4kDLIU0Hwy*{U3vYy`fXlw{2euk)olCLy|5z`5NRybc4p+Z!6%!Dh-q?cp16V6a_;h`iMEpx5#M`l+_Ed2o%v zEVX6|J<=Y!f8g6bMf=;MgM~(JeVTqwD%97_#d)-ugY(A6qY=`XHMb$9l!8}p+I|(X zyZ%GEcv9N(MAQhX@Xydydhlavrtwh9USh$fNP2k&Xq1C* z!wpW{SyOx?zCOR(O_Tz|#S{0~d5Q2!Ke9&!Z2n$0dqTxy`W8#$Uh4TM3$X^#{T0*x%VW4j^s;ot zVqieLT6Zx#(|uCo(K|`q1~N>;+P;rzXnwCa zbmA>}67c`40YxE=F_r9ob+2c8l65xi7*Kls!` zv1F_pj>EOK$$q8gWwdA?X&a8BwK^4@-@6YDHu_3+Zmx`14#(Ss+o1K%_u}3)F(d@D zW2mb0^{aBu9;@Ns`ZNFWv6mKAY#^*oWJ2o!31gNXZ~ooP5kW|=snM3Z^_~mQ_;piv zzNav0x<9E9LF1wQ@w^sgT|KT?B~|zxW(_2x$pPKjjV2dSrUu~0+=gYIgqk<+?w#Q% z+&0TK#>&ssr4T@BNQYozK=`Rhb>qEldGSP817pmj9{V_RX)z)YAHE<>Xte9Ie?>An z2Wt&6&^pn8F61HSsw0?S@!ai7V=XPG^tJo?LXSq1qd>95YR#NNubq!UBtKYU;;iyK zttM^=9=Oan`y*QK`o}7wvM?ZS`Qm_u@z0z2z@y$i1w|vn8qyfnUA-}tl{#)z$ZPpx z6?My^ZR*yRvLa+d?=>CJ++|mu5v{A8)M(pQF(PtQ%hBf9U~6h*bkM@ZczAYJNx|Lf zyyi7V!Ksz3(QxEr-97m83@toE((-DE2;!#Q6Llz$4e24n0e2=9%0K*?Xq`d(lrLT- zLDD<+@4is>-jO|n`{to;{jM{MAE)QTvs%&Np+)q-{FUQdsJ0g&TvbUN%6%D5X{Vr_ zV_CusMi?&>6u{6Wxfl84DP01@KkoIwz{}TsA zWYVysT5nCCEelO_R_taY5R#lI%4eUYjSKs#mmz!_<6XI(vkd!pFqr#-Eu954Sn``I zTkJy$9{*|?d`52<(JjQh-%bR~ zsaeXQzutcKcZ~%3i<^U`J|r@6)ilj|v%}9cdMS1-1|N_*AHu4KaOe(Z5H|f1DzMOz zK-5A*FML=jHar8BeBH@%SjLSLMkLvdgm0`i$;L^&qh<>YShEWaXu7HAgGEq`M_(^! z0M5lOS^Ac|Xno@r=%pDIr+Gn#U{Y;`{Q)L!~%K{rV?@ZEuv( z`vgs3LqG&E%!pjy+*n(u@kGrmEHu^Cz33-$3cD@3-{UXc{2L3B*!-|SM-3W zySv+S^tf4qhliI82Wf%->1yu>_2j~6)9649WViryizKn|tet}o<&TkoUNh!*PfgSs zK}6P8us5FeqMUGcAx~i_yYtyU^VPw@v#LJd82gvqEZ44s_ETSHm8$K`&5Iwi*C7j1 zR&iT>@I<~)R*bglLgTR?&Hn^xcz!(2k|;kP&3~d-qkrkXmc@twU*P;gH_36wU(nx# z$wU8H8r(l3p3+GU;Xi>0%g_UA05ifVi@3D;PsLVU<wj7p=$Ezp|EOUyh8~6Cm#>T3Xp6EGD}R4Zu;R6EyCBM^@W{p& zUiBc2#Lk5e{g3!(T#Wvt$YFt=+#ZMxz6XW=qK`PCFnN9A8HF`O&tvxJJxx<0aipzy! z;6j-^wm@grNIa{eQxjpCQsC-y#h)rP-K;rJ7lqPX3_E()>z35~t6!@{dID=>O7GcF z4x7b_a9JkGN0%1M-#I3oUb{9_vHw#-mr=w0V9xj+%tVRAPh(6>^<51~nvtOmha=#A z^kmE8-7QOPCsEw+Mtu?K`%Bw5Pr;_!`sge~i{k z*wfX~+kK(jhp)}9tPy{F>$}o)OF&x2c*t|d0ch~IMLVQ|XoMh3O1(#DDS^rFG#cKRaAv4p9jFi=J( zeRbmNC-~w3yBB>fg2IOvjijYG8(ze>%3`#1M`%9-S5_Kf)b}m3>le58q%vY?+Mnn( zw()5fWkG4~B!jxn@2zXi!I;pDLsYC?QHO&4=aN|3-2S{x393lBzfigRd4Y zXeJ@)oW~}GnV67{EqoP1fXe7Vpg(Gb>6lCGzwEqF&x)lYgT?~I;N9+L+9KI|Nu^nL zZDYM?al`z+&F8QJ@TbO^_bz%u6lQ(y_O1Ia;tOTLgu^8pd1xmBz_^OuPiw{rD~MzRh;=1|$}6lx5s%~O`xcj%)9vgGO-%&%Ab<&7u< z`ja>TM#?}B5_Gi{L|4E+kClZl648zz{T3NGI%_aK?EwE)kx`5U%DI*wC#Pw4JV65M znp#A4@Hy4XuIIV;#Fs3nb75Ga9aT46t<_bL2*53FOTJBW`ttbot3?%`F!)w`A`9*K{m zb7#lGz|q9B~kJXt%H&ipkPYUwxQT$yd50Xgqvj zA^KQ$9mp{UR7h?S#@20PV2wxoVg?F!XT!(=K?@WRq)J;5{%;Em3hWytqX}h92`55s zrOz|u7Iy%%Kx&~;{`~PDLH9Ljv~c=vL$08rj_8OED1gdsh7qJLwHnVS;sMqu1#OQr z0z4lM&0^or`L_lg(|_aLBP`9IHM8{Xx_0ALZuS6aFb72(1Khs0R2N+S(5I{SHgDi3 zy5syo;va%VM1bZuX%#7)nQX@G4)H%I-q|Pyn&Ch4?StC2{URu_z5Vl31wnB*cW`=; z2>6Bh5);!B{)4%^EjZ}cOk|*K_q+fPNe1R2-{AlYNn7=eo$rE$qy11I?v;>@&$Ev_ zi=zz@&XND8X7b1ChIf3~Tv{&ofy2Ya>!wzhA(u0uUL#`_{3k$h{prM9U~I&R3uMIb z{#aI$HQHi~6zBaM3*o=AB?WPi@wlT!2Jl8c*L%PJZQdn`mM4d1bkTH|aTxvSY9Dm$ zf`K5Ip?$j?(n}!{&UhCYo7fa^S^6+@<6Ak|I!W@vI0R z$jvTlt~K+yqta6+T_tT=UQ4syj%CQ`f;{( zkRnSfF&2`xhJN!cbVcJpB@D>EDPKrP_b>@#22JiO!C!CG1=+>8y|E+?<#c7*eS7G@ zp_oHhKz@Bc{dvOrK4NF=0P!AsxA8+XY>1eHeiGKo@rNTm00sz>4{XzL)Y`spKu9Q- zHVnPdGju{8T%t6%2SNoTf@Hx7_?n__&Tx^}L!{<-@sVj768Jl+RUjGY$nORqznA3ButT$L zgQa^T@oDc;QoW~pj!ioCW+UJXV1>u=oMk(wRkPX43#!d+9;{?m@YW|p6DL!#JSsVu z71gy_>F0+M^}{_~wRFB|6W@PEx974wYQdXWMAL-w(?j_6c^9rKorgDwQ!oCW!3PE0 zKaUD{<*(D8^#`lc>%e6)=+2&kXB&YqT1?UoPcdbtXXTBuLj~bm zST9FMBrw~1bpkayP{c9KjR{Qantt0Y$f3)x2kUST5}j}x+Ve}sFtx>bjHQp!Bm)y2@MFQ5z52Ldc~w4b3JI;eBW>3 zl9}wV|ANqJ<)armWq0v}L0crtsXI6`*Is4JVN|))GscMfz;o5R866Ad*=W12enS|2ML^!+{M(apaV-xo=%Q&gO|5%%7pBZYyz#pXe7%Wgi>X*_n ztk}g3m4{r3iHYfKHI3@D`YwY1DIJUS_q-p*m7?r)KGE<~3RTQ+SG_P%5a^ zS{N1cvf8CyB$fAEt?9WSED+K))>zAg(3$m}{&we3h+q)$Y^qHAC0X8sUN;s~qAljt za<5K)>4-?YW=B~@^tcJ%#HAhXU>@m+N9k}?FYBXG%D2{~i#A)y!2t(J;?v%ox9OU{ z+1kg|xgy{%B@~Y_T&+WxkgVKya8SUz(sunP|A`L=2K=ANFg|J))HxI%pC3OBl~<~T z55FQm?0M|wV0f{^DB$-C3bw1gSx0GrajWE8D*+{gmW?3{prvB7%#@XpaM zaXZ00Lx1Pp%i~3E#;XCO@*wN${N^0h`55m@?!7)fz8)WgPW)0{IjE0V00%Q?_&i_3 z@fySVvAF_5QG&Px&a<|ArJq2pL3*IZsCz9R&p#Oz4>aFp3kGNL*XBDg@yI70x|B8O zLC4WuZX#qpwKn>zv@9D<{~wk5>mJ2`s*;qXsydsHfSz>o2^HuT9dVLNDGT~tk20z5 zcgLi~sAb48X{;_Qj+3RI@%sGyD{VyXx?0NxCzh9s0H0Kp90vu<4}LuD;Ji4OHi5Eg zl%bJj2ExBREDu!8V$A(Ajs^vA1Ak+h0ukDP`L3+=6L7iflKN0DgiW&p+ka5ih0D^7 z18Bg%CF+;`gaMIhQwxiW>CqPhVuq3DrJbI_9h9@EVn?fgnXjrdwc48!ob}$hVfO*CY+aS|9)8f9`gdh1fk|qD`~84vQYA>%mSkvHEUM zxNm>Z_F2NrRU1AG|D!iWL$)RsOZ3d=Otc3W{ML`NY9|VSplniy?DE{0@WU8Hl(aG_ z|G`2K-cam?gf|;r`xge^px|lE_o&|s}=URj^;vs`Tp z5y640TA6(NM&D9LOY&WaNPdOS1@dYNR%QZ4bX9oS*=4m5hd}_N!Q{}!2LP@r#B@O+ zfba*ir7OymYu=k_px|N1mcI2g;IWNwQZkBA^g9V-e-~h^+Jt*&Dl#7e)q0*whyN>(gv}KAe4Z+N z%atU$c!;{5_skVcueMrZ4F>Ze-NQe&S0LY+vPi zGl|i0g9glSnBG0MsrBXYCOkJ+oSgRW(r(3fxUlrD#u@EiYJ2Jh#4y+V2h5l)CZ>!H zY;4Ar&X72@94)59LdAV$#1|p_i4V?jGcz2lYF_0TJm zd{sSn1nDc>hbIlSuJf*A0nLzoGO8o?w!+WFq(a+iIg_!JaT|oJ@eyn#EQ8j+9nD+c z^Dr%oHTW+d{sxliqd|fJYH8u^=>+>QM*{x;?#(B^zDIIhR$Xlh=bfOm?1->G++5?$ zPOyBTxEE>GXX`=)ESx9YG$-07eO=hhyNmz)m|9DoSa|bu&P%x2S%|9D(trjC)b+f% zT05S?3Y+i&AO{!K+B7vMNBUjSc6gp#Xj>aBr<&XKqGC_tbCBsJ(oEi%-CUa+E%`P4 zy94S!USTCO(W+wwH7Be>6>qcXvlF!;FG~Rj{nxk}Fc=EZCJnvY46k33npr(~5Am=? z4kLqE^EU;_X4QnZ{`#~ikp+S!@;QyEZ7)Z+^x)w-5>>t@>tJP>!st0wnOUGu`9A=~ ze)>NrIbQih3W08UrubJd|5BM9xMRNA!(--UF3mzS4Av(10-S0~xuj6%puz~GHUGIN zn*9IIzo^>8li+7&Wf$<_TOJ+62X5~yJ9N(OaKr%vLsQSIdF@7K59l~i8DcEhMd&)?DnkAje)NSaC2$nBVkmrc{mebV{14}IQpNJfNpgO} zN9vJO>rwb8bb)}1je7J%^HiA0*bcUU36oj`geBR!9$LMF;_;1SD!H8?qTfViXtCk^ zSsG_KLk29^o+^+_?hM9NPMO%0+oabVt(km7@CCMXrnFBf&>W?2oe(9 z-GVy=4eoBi-Q5WmoCJ3X?(Po3-QC^YZHn(ZXXc#y%$+;8e|1+^m-Jip*4k^Ywf9Yh zLUsyctI>^-D*vBgLToQ4i@Ky8b{?tjNfL14Y5l4$*DbZuPSE4Y)o4LTVSoaRF56iN zU+RG5gVMDD2B{Cc4RSzH9kS@5bl@#HDU?QJwP_nud(J?;fnxR@#aFB@on%{(E&+ve z@%mut&R3=IFtR!+E!gqnRn|+^^V4Z)ex~xE#&n<^`#l*+3Rc}*pLyPj+w01HN7ph8 zlC1HL>;5m+2SLwkFh)+o2D<-B^B%+i;8mCxs%x_0L2Es}fvLiaOGnd$8LJ$-O-C0>ah~twCW_TOIb&%qD<~jmIo!O% z%J014+On?OTYS~UYO+FQW!b%BIe^by2vawb|0B1yz+#m6jY z*)|E06RIG#@QZP)Hz4!UtrNKPTMVIdU#_R}5=>_ta1-`3^dw356_NBuFRE?mpG@uY zq5qR`*mt2;@}&}rfni3WYoH&u1O>5?H$wiM-$dIadi;M-?03a>rGbKSP#z3c$fjD^ zF|Mbl|IlMoMC$;`eow=wJoY9}&26}~5oy)YnaFd$|C{%KEB`n_>hfr&?p7KCDd8(G z#ux$o_$X4wb!_tYn7)<8F8rPEDOSR@H5P!MGr-{YX>1o1oZ1v|T;8g!J8WXyD*0^0 z7lAU-w1u%R?av{P0`k0C37lKzgy9>J6F1#PquB!b&r}GRpzpQRZndBmn&gH3V_c*> zQ7i!XLjvA28Z9m~uY?3KF&jBskpy#xnc+H&pXGpYnF^oH*QkbQf;UArAwE+U-RDG= zLP@@QR6qWhp5co?c*8n|h@W?sDi&$+d|T^)&!{<{=VZCJtdM!y6xnOPhZ2=vr^rd! zGxG4Pe~Ofs8;W~_k0!R^i1a-^2VUJUC16Cyv+0Ev4rxO0}q~{aF5y3F-%lOdxmsD-(QLEug6?`_UPFnWqZvI=zvjmK@Os{mbrH zll%Iy81G;^_YVg)&3i93_LfGA$&6>z8il4+&*9KkC#UDOFeiDNpwpuxEP0q~?l`0I z{%@^1Zbg4fcScct5Z~?XS@BC#DrZChKouSb65T?aasZ?^`N`u|W!lzeJvR8LYWa|t z1^ex>79TRIyhWs*C6h=wW_%V@1&*dX5?K~wF%-}E#{jF36Td~CTl~C=SwDQJr+&*P zLu|g{w)hQD5}FLcX{@-L8+obwWJAj_6Z<@s`tE;HCmOlhX~Q^1d4+RB%;A6ht0T)} z0}4izAow$@iiS!5My#A|veh`x)3XA=_U0&SZns1EV}}Xal1;l>V;XIfHtRS6(RyTf zXJ5&*0&utB{nX5=Su<`B@!Yku1EsUIY^iOAdM`BJU6o%aui-L2l+)*X2BAKK!QK-- zj)@g+`hEKl78vz7jIT9|^ba{o^H;b(JfPGvw&l*ZFVgCFFrRk}iJV%n8y0E$Jl_u6 z_PiLQDiGfD$dZExBnJ+^xOme5?){+gYg5pi#w5wr#)T8wr*=9)uS2E;5+L7Bd0$&T zoR54}`(>Z&z+uZChjgzn@}En$COSICbI;)_WHWN#hXJ6kss1$Gw=d$fF)U>v+W!0Y zpiMT~^vK35B;=&Mk;nGkZ{sP0%WAQ;z5PgyPhaq0wl|-0kvPfyQzfDK z5Rx@H*|nB1HV#qg-Ml-~GQX1k2v1e-zH>x`wd%|=#eEMOGcT5=r9ugb5gU203t6WC z=E-ful!$QK`ff%mg*<)MNAhbQxyTUaf62O!iI(2^{BIOju%!J6EegqVdxs_S7<+3l zXZ)V-B~~vV1Dhec)TPv2kA)ep1!I4}xy-GJxDIo=FqdSJWcVK~cCSv=qD0Y*$SA!G z=geHEeej^86=j{aoOH(GfCQ+YT)C$jI{AQf;8fq%PQEh-v3nG#Ml%vpYD^>KsMTajaywhe1e%7t|7@K(> zNa`Bx676y0IQhRLR*(@uB`rt2^n#tPtB%B1n)iS=i-Gv}dAm)8k@^{3U&fIkRo7z= zc#~1ZgA0yON4Jqu6?TJ;J%-ehZ4aEr4|YTEwLHg+wSHniTe&lhR$9P2(J2JQokNiW zGA7iVaaselX&th-^FEfQiy1GbhMU74jmqd%30q)3^k1FRgR=DDWX(nNlo5vdr&KpB zE6xC!=qVzKZv*~_J#p1SL0=e}wyOGwjfFH5`1URt$=35GdKE$a+xB&t`MZaEM@^-@ zrIQth#ky<(gb#n4mgukhRH<@s5;?OgQ?UWQc^OBSX%S;Cp_oES7r^$b2a11z36ae3 zh@j1Q81R~U_+qiAWZ!X;;WASzxyV{7jbtg4jaHlpx$Q+@<0CkS#0Tc6;_Dk&~65fLc4KtLm;NnTr~#oS zC@3R!$m+AZhThbCWjh9=ajxVW?tLz~y;JT5+06e%-fWrt`u|Vfgq3}S>AXg*NWIt8 zn0>e%7|~Wrzxz*B(jSI=$K*QWSF= z=|`IqF?So$b#l8WMkGDBfx^N8Mmju|fU*Y6+SNyd3=WAEj_ZJx4?B47obd;c-!gr} z8;Uim+a@L^%+5FavMO6bpu+Pf;}bI%8fJ7ev+kaq;0j-NBmOW=6yPC=peB}- zU%sWXQ5A<549jq?m1e?dza{u0B4DonIpOo?1omp%e;FLipXdO-!a>HeAwXZ$iVEdz zxWzC%&L-^hes@~zr_RkrhZ0p>%3w(>YzS}a^PMo~NI0g0Ol*vJ+PwEb?Z&_WJgzp! zXPK$VgP0~R7e&YNlmuL0QwSVlVlh30@2{cGEviiGb4q-|HrvBsQWXk&fcR#eV7QYy z-nfMa?3rr#n$@@%wo#hspoF|vY?%T|A?8LFTzN#h+pu3VT?yp_vMVfboPCj`LdiYL z3-ofiCNlJhqB#eu%DjpS^0k?a?Q2HM z=_o(+2c%l1;rzGH`5$U5)jMQCtn<9&3-hPWMbUs=nYC7!dKzPZzKR+Sc<+}D=TiUN zS1SG?rpIDnJTyvev43g2DLeeYmYVH!D)j7Rj2h?cpyiRKCnfa(mM*jk{#%LV zufByA`~8A8K@-Js+>?yp;XB`#X0gwt{hp&)6Iu@nDQKI8`dI-jY&=*1A-tIlwscG3 zW0aUQJ99@Yxz7IFTiNS3ThFoA)^q@u>23mC=a;Td_EO0E@q>tc$^qA!;pf+TU zjdsW2IMq_Iw5Fs* zbqVp$urspQU{+;U$LURLh0ErN`2}P>s)kSPjOEZ!gw`HZBw;e@RV=cTB6PXQ{OEs7 zW*W|H=}yh(FZ%q4S&e!?a0_H(9T6)pr_+sAPa>QwM`Hy+d)Zzc?ToH&1;Ll#-36*u~*h_#!anA0@m2H`sxLV3zUn$B=uKiunX8#nPiE;M^HLW zt|Y{x8U_t7-n=&;rf}sS2I3B4D20O%eUJR)Cf;*3qRbPqJ(@Ti5-7} z$W~!>=97W(UqQE+TXYztpcnAy{Q!v;LkJDRyELfRaQ;lPiUe^+O`A9l`{p==8GHM3 z$v)y1xzY#)K<<-&;GpHk%QPtsR{VT(xvl8|?E?U%r_B6{^#v4EBk*}BF0x6>?DXXh zOf`{-vPsR*{3_HZvordcsixnj=lz%F`FTYg^q%cH8d+^3jOZj?d#^@eTrPu5D&KgK z(u{UzdS#&KwEs?e-qX?PiblA;{)mVA)1(kh?pUdh6L^-mkG zke%&UjTOPPaI@xWw~AWWre4*K$>YV3Jg(R9)=51)m}Fv>&F<4dS8a}FeLxC(q>U(p z0lp3|FqSzRIWY`_24~`yrN3FJ3Ys$0W79G=xfF&Y#Qq&`fxj{FQZ)g78Oie@u|YG* zfq_iAps}eGA0oYaT)lYn+shuP&FV~6#Y}yQbc!~liIp|rFz}hIaj? z_4mhDalDl$`&fb!QA)bFsn}EEBg8$p>+FAh7Si&KeUlE|K}$OIN(xzWPf(@R`=oPO z5@;4``C=rwyv63_N{U}1VRxdf*Rr^kM6R3m@cr(3@2~3Bfz+?8N{xE3 zW@9F`W)?1z7EiCg8y4?@4y^7OE}S0dS#|oU^Jt<|!-b4mZYWN|o470@D39QBcFu$q z)k?9_%PRI%DJ&9W1!pn~)hFDMYidB#W-b&Mtj3t7TeRA3^<0|U`En$vV%2|iO+4)# zQ0)-pn+s2T8)OCL*RemVsh%tXx6ztQ3E@rKsCcqm9Q}>?wlq=zq<&ryE&67sebn0y z))wo0qNk>?$|-7O>Y%vv)Zs1s#ca|E06;82#D}xhF%5^F=IKIf$FP%MmYE&)Q7yz^ z>&BU?hsW6>{ECvT`wiw^(fkG1??BOR12ZHjgwmcfC3^f6u3P zFOmDWi$D;5Km@Br#+Mg>DXKV&bm zu2~jaR5-HZq+u2g^S_+iSy+0zZ(Es^QmvkCjQsxXAC8u9F3Nk3635^&HD~mtaBqKMb%Ueax{S)Lmg+YwKEZe(UO5~VeDBa(eX8oO8%=Pt(D_M>~Ds332 z+2<#+#;E(25A~ z6vPta3*Dai+1dr#yCI`1ovpL$j3U@QZJ}&EqXJq#5yYbxc{GQB5kv zn9}KQVFB9gy?4UmRQEMX=N$Irf2U{AnHb|(K#q}jel85R?Ke$3vW%F;p)iA85&w0@ z);RXedzM&g>qard>E7NAb5bEQPW=-M`M~E61a^;=u*Yr<&!(iBC`~Zyom-R=2!eWt z@{m@zee=GBl$U?3+}qE@W#M)VV13alK|L!hC}=UJc((bQop!a=jNgM2KO(q9jLX^Z2-CwRXVJ=8&CnyWZlg}k8+gG#C-C~ zyhHGsn2}^=&}3a2?CpELJYLg< z7XV%3I$IAQK8tOn2>!8MbN5?tijMC;*wfEQ7I~}$+z;}P1E@({!1u10x92gE;GJ?J zrA~-O3f?2H7-m-jM5^E$n`Dpem2}Fd&&VY&o*uvM>9X;nUI8X3g3B`nRzhkn&-S@0 z*DzwXzoB)VWu?1-81#?nL~(6mO%*-80Qt=H1#)syazq!aP*HC;=JkgJ(Mmx!5lo$<;z;s=vs*;t)S%+97m1M z!sMl(+j4xJLKcO7LbeYh8=8d~E{xB#thBZksphB8}pb3a89NnZR(m9 zho&+<1@iY%Pz!ku^rSBdEs8T~ku4a&1e}Qex0XiS_$Ya^a-TTQw&bE&H+jBzPl4Pk zsPc6RlIci+T8Oqvt}jZ;*5(;AcsdE22T8A-Mp;ePl)dSEo8^(kr#LNTIuP<;E8et+ zx4&p-?pJ7%M(Y!APmMokU$T~im+|-I2BWSEDD@Fl8tkRc?s<*0x;&_&$FkA3Ml@)j zRk{4dx+g&-qbnm5>i(!C2m%S#Uq1vX%l8;2v#9$9iPGaDr71LHWuGwE;Q&mcQqhpi zJr>^@+@;SA>%7HU5jLXi=f{Wc1~y|V(Q(pv{_6Ja1`K@d<#BT6s7;$oc#22U4*GTqpmE!tbN88NH+*wUm-D zauP9A&c1h}Of&Rww<`eF;TUn>uB>bC)8o4atB;0-UKc|vFWL9P}`MOmMen$4Q% z!j}GCtdU;+hU-2t>tTDk1WLT9S$)XPt_kJ$Yy~OceYf1sekOYm#mKo?DzAd5%jkSx z@TIXfcbC_tt59zYKQ=H?;~hKJcJ`37Yxzan(SlWp+=o0cisWj%U*dfpbbF!GK5E&~ z({%r<--kn?BG))kmf<*n$1`MZc4OO9vs%znNxFs%=Qqk**aSc1!DU8!Q=~E^n;#&Z z?NoE#Rg&qspkTuM+>h%~_(Kz!nWRO{*mgL8MCLNp@HCA{5^r8!2s~( zhTT+}th=%*=*&o^170MbQ4Fr!%IOa3nqYbNE2x}%H{9M8p!A)Om8LyCR?q>&sZyzS zdaVjW&kU}y6P@!T!;e)0ywAhl_ds|2sTo}3aO?=fE(+YTl+U8WX-!|uqoxr=a^Tqf z3j5VPGj&B$jb|}u?6~okblX=p3IKUPoF*{ENZi0$?)~K3UXHr&!|pKOw5yCAZti+G z-4=k|kU0Fl*5{Tg<=iT03Ri4M)U&5s5*v>wZEP6tgB!f}#*6%7l+s-HPs#TJ2v1(i zbJh3S9Ja7Ki_D{E{2k)mD-VMg`mzPxNS1!1>d~>t_v)hR@8d;bBt)gfnj8;jEcGDw z?+lr!Ilf{5(U0fQK&I5EN{fyCAInhU$F1fit)vDox@3uh^3^5Kd2K~=c zD6A^)VnyNKP3!rNn(h_w)#&+s>^!@hJr1*Fa+HIDhFU^T_9Z&|a{n~r)!-;Esw9-S zU^DH`6>T|L(R`Px{PoG5?#ffj$_eLg=LtW{`;_^<4Z$i}tAn^{o^N!?o!pNS^s>Y+H(O#exApnJD&YRt*z4Ko# zmf=GObZ)~FD2OGT6_$yZo08>7puwPVTWmA&^<(N) zUjp+|@tLF_@_p_16_op*i%VZD-A&_Ch&b8IdVL4nX2^_E^R=P)CrxfLJ2Nw5xR3xy zYg{cw2>i?8Vsh=+?Q?wZlqB)rBtU44$52gXYf(dY>vMEST<(KHESazd_R`ja_Bw4W zHu8mX%pt6+$siQaDtk~9@H{qj6UF(Z^}Y({%iQrpceRmbD@%1v)aG&d6%E;8ve)JB zkgT$qznKQo5v?s(x{djqU#g>psa}xq7f8iFIwI_DuG!KSNZZ8?_Qdg6GEZ8xNqHer zoqYhj`@oX*R|8l-JGmBW(vCg!ghT+?W;?oqFJ^hTx2(fHuI|M4Xy(X8ZDA#j> zNrx@1hX3YLG1a%P#bxQ~s_-%TJK)kVpe2MIh3jSPpzyWIqLUcR%~-yG7sJUj0! z>2F=5sxl3-EPf=}*D)LBVOwrmdceX>{#?uUnB4i7Rb0qUBeXl}Bt6-Uz@Zcr)EFgWjil+;cM=_;jW z9cMN@W_1vQbA9DTXZ&QO$&A&K*Xq)!*P`J?X7kO%2pvE+p(vG7MUV_`l;q^bZQc;uX^5`PUst!_!lIwt;C8NDoB6ijwpJ8Z zwRJ}iE};u1jhA<)UuuOn!dyLSe)LY7c6n_^{JCsw#GX$Mb!nLs2(Woaa5>`PPeMwn z3tr74eQ0oVD{A<;GRDtCOR@uKRwkJ?a{j{iX$_6$gIoSB+7=yo;HAH#qs9;0jD;Cq z=c*Mj0fX;HPeb4*qnf4!$L#r?!0#EI%@^Q@CGbPeA@M^7-$;nVHWN(d&Foi%`JBe3 z8h{7q>U(;8RC2mLwgh97P^v-4`ebsuq(X#?_s`l-npI=%p+|utb1sZZGM&D-Qct-0 z6M?>VqAh0^wcYxs!2Oau7yL&l%`#>#ibDFvPq@-HHFj8KJSlQt9=;-lp;HJi?My$R zcEvt@JY#4Do~GN6v;0;FBdJ-%U~TYj)!#D*K+_oNi416R7RqyN zJT`N126J8D@teccd6g{Ayyb#{+g955W7LK%Je51I@gKd1wCd=OO%hQzi;ORnjJCXb zoP>|hYjOkB)d8_E!av8vtw^@E6-ze<^kwgSFm5u#Y6p0K^%Iy{)s!8W-KqUEE|<~{ zSpzN&?!(|NUo3Aq8pEzd>GV_(Jya(ybiZi~L6UnR^ooz#au z#3^%+1TJtskKAeymR78r&MpN8KCr&O%YTZ{XvF*Q0T;KTilGq3q>yOlSVU0J@8|2Q zbw`dqrRvYcpQyF94@g8E+qH!XUem+9KR;}vBZ#`Ej9lOb{QQ}wF-mZ?KjQhu$b!v< zj)eQkb_XCx6|+P@^4wx z0n1T?-dl2|+dZKtC4UV%)r*3zlLlxz;TIKTx^X=?+wQ1zlAbnalG_fV3zX$77;jW~ zH~+3JNMH-KM(XhcV9$}t8Z&`OLg6l~svIfmysUD0wazFB>CLBaxxml=rZ9g}s^oge zO)&;TYvg1;&to5tQ=yBu&kg`9l)I(z5@F|@;1SZ;Yq`3`cqi|v&TLJu$BGZHg_J;)UE#=V4Qh^A`~XvQe7D9_y%-%F2?{s z=u~o1BCGM?kwHsHF)pBXcjxLhMXqu&T#fbqh!qK`hzbf=@J>s+hi|K(RRp}54&Um~ zUYX6rexys4ZcgRA|M|sJOA$y{UA^7Bh9n4x^xq-tC1j>cB&y__YD}`(SYNfl`gtc4 zf>o5g%-i_&)Aa)M-l%F%%U!xV zHo^KY0J?k&_edp)o+?Xwf>c&Jl)HY{7uv;X3fUQ0r4fEN7N>r5yt|b_3JCj-HCUAL z<*rv^o|7WeSe;WUR<@ojxyVcb65r`D3ntADREtga=YHFhTCL{4 z2gI;q|IR)1dVc0Ej+&?~#&l(yHyxCwRCPX>m&%@AWDuGD#AJWBJG_k3k~+;h+k84i zxyV)a=WO%HIB3~FsoJ$#00NLRS=R~F38vs^1+)CCd zasOoOW{qU5|Br0YV7)qTZdyrKsJSkdr}0CCk-%l9>x!txzADY$WquXJ#Ucc5tgImg zbCr3Zh?CWagpf$zb2WyvcY*^yvvI%WW5k6npeZeNlfGE@h4!5gMu zu>MOp*0NYmQ&(_OBzpT%2zCO6{{_WiK8=7(ihVbe4k>}D_3j*AixNf69+9vwDT-rj zxND^@sIW=lI_6=*FNgKQmp{KmtL{`#60i?aU2a)|Tw~tu4Cl1H_YsE%cB{@jUxR?~ z)(Vjo)9-O4U?J6wxDf3y(~E+nXwk-zhbJ-|)fw+B%Blj3GOLlI-$$(ogS z9o+-c8s`_sFQbuN?}>9cmbOQXsVL6HsebxI2xNR~q!&A39ElR5kO~cYeMmp6M(@aj zo#OZum@M|zxQiS}9z1qVZk$Ws!{4t{C0zo1`2?4zwu#uy#x?yPQz7|ezRHk`PPxt9 zPaQ96*NF$nl7-II*j({h4fxZ*3}(H_$s!}s;=DGi{Q`02&KNLVOVNrgyvMFHl~68j zFDjjRO|v(VITX5^l_o~>=m7iI@HW*YhLZ-@wk}iCK{q@UyxIgXJg`(#xE=AoT%Pwu z%OdIn(IaSlV>aW-Rrf@sFi6!zS!HqA(bK=?IOauNlpsg^h9kylsrTU}!#;HR|WAV#^MTN|ERgh2z9@`8*KoP*o;|rS3jLsPKD@-0Cu- zi-_oxd+ka|5((u=W z_tc?5vOtSQTe1|aL6kB=(O?pl@!jJh@KCwTw)m$0 z(Rv_YsX@|xBoVns?TG0B+Ux$g0O|f-rMX}br8I81#pHV{58fdxyla)s?%%K(?aF#$ zA>L^*+;78h&aOI=;AW@bT&B*|6J;(5+1~$jA@VD;diT`HG?f%qSsqlD# zBc6+T5abB=jYR|jZv2ne?9b(TjH!5LL$0}6^I4|{t>o}Ke6sbKmEK;X4d@2zw}7A0 zHR{LecP>mBxymNF-$E&V-w+9rtWkIaj+R<6Or<>Y0w;^X=LROUNMM-NeEfIvEMLp{>7GcMGfv7yw;bXJdyc6af+tcpA!c)IOU%k z^xJbKg&~2V@a0p1#Y5ii>*0g-@#1IOIl5dhv-+`Q(ZKk`C)aeq9C0K@bdO5=VT)+d ztYmx9F}kw=>hqDBYXx9#P+AgWzQZM+Fp;-U)lV(k*`y%ZKHt~_8cDd`cqBRVB+O2i zKgKc{%SXx~?9J~aKwFuq?!iO`{AzfLlWEAB)a;!$&~KHH9+LD-jb7jVUMxERTsC&! z4-O{VdELOGFOOsV1F_i!x7lAhj(-n5)KT|C0lK_<=V_=o??j=iJKhJUy!l-)cPjK` zMh6Ley4bqs3=-Jq6i0fg%Q@Q&8@?kmQ~Bx2YoBL(J3o=C#nXjkCI#0`#ho}!qf)UU zwP!R+&4D|Jg=4x*4zN|NVAmA1+7Q7i| ztnH;D{@xvy#F)%huD=FGYGq*Ey?pd*7Y_@^um=kq#+w6)ZywH1@%ERAYST~$_8MQz z&$H}4YwjZtH|T#*%w9aG6)tC?R!hITXZ{EfZ)vKN6=Tt3#^P9N6`}xujro--YDJc( z1*;;vL9AM5z(*M2t8n>|C;MDY&Is3cCR;UGo&yih+R2hWl{la0(jJ6osE_#MVj5TO zJ@l}`XP*#&$3o@(n*s0}Rzi`Q++ zbTaF$n!r!+5F$-bq>vxG9`dw;Z0C}3qMSreNucoiHKfWoXTr9*YC_0G0_!!RfS)UP zNYG6m(*^T9t;<%_3J0}f+6PH1Q2w-c1)br;T=KOPjIN-#`jKBT3ncIq zwIa&%bJ|1MiZ{+My(>29H4Iu`6}q}&W!^p$>Yc0Rc^t#Tamh3 z(Tj_zcI~m~3mybH-?!?@Jh+)CW4hUdyQW`&Ig07@bRY z&~sEkPqE2%pQrXXjl8~6_j;l_inBKcGh1%>1B2a$f0*y0*g|z26lb|Z*~2WiOVU8# zq7ZETr#RN3_N{8oq+#C72=4ybaOY^=B|>Jd=QgcsIyq%wGmILGe*&C@kny;M$opd7 zrJ(`ddFRF=j9P4V(KuJ*qD`^vYGK9Vm+@KiCN>O$%j=Gbvt7K7q)U7Yhx7Z=DdIU< zR?m|=od=4_RxjL(pvA1$a3zk#*%Wr<0{DLNr@tpVx5nQG$ts=sO1~~z>`(n>qFoPWBiT7sEt3WdV4&j8FOg4I zB0uMp`o+M&)vMC(>etPts*};JLsAWuwYDm2`PbJ-JrD9XhwJp^c{FP2d>8`GLXcig`C(V)|uAN0>r-y1vF_AKw0;i<(78$ zI+hRc{@`JeV+r562KVB*nHJ%_WS!0dkV4bU%+vnf@LHF+J43XBS`(3Dr+ zN6pxYywG=#6v6>a@YrdKTKLrJ^Cjp7Ycrgd*ZCL$?YI?1>LL-4e52XBpMM;0ZcXe- zYiV5OznuesNS_2Gl?$$4E{)oMsvZ6b)(S}dO;<{uZ2V7B%>|Z?1lVcvs~j91quDtW zYhULF6fPnlP+QF=?4$*!^8&>5{%fEC&kx@21?k4m$mscY2QgHf z@8MdQ3(G@W78{&I=#ZEpUYo2~xIx8UD>y9nWi}#}iSg%+k%3JsvVny)F*+{7ii+?u}VfoU8F41$y{I%t#wR@ngWk8g{bUG^A@^lnoE>FAMW7WiDO~PT`v+ix$x`<15Ty%4n&IfbN zKpVVJjh4O9O6AS(Ck9tFvRo^5=1c3UsWNKy*$+zgGjOvr#>-qry0?`M$Cfs&sb*eV z!d5ROxf88sBi+lyQWItzefS^o4_1F@!f;Q%1q3w*hc6G8vz61onejHbh4gAfFPc^B zuW7msINM;nzYOPW(O=9d+h@6x|2Z)jPrWjnmsu2E2nl*dtL1*X1<43- zAXA6~Z&NIWyqk%5&QA8DaUUE^?IKoMF_=m{aPwz}9n)?eq}S_i7MKA*=8DJlYRB@5 zGDaIR(-CicesA3u1E=nkUNK1+F)`eKErOw?`u@WD=}Q`?EpcOS0N*4@G}s!|VL!Y2 zV!Ov!Z@6U6U1wv9jP_1;JBH@2A7dt8s`id?UM%==yuW?jcV~N;eFD|Y&jGYZhK~P} za|n*Y`sdN1f1E&{wY6j_`7d<;sHbrpH%=9^IjXZGUHG%fHo*KpQVta(5I+iTVxu00 za=QUOEF1(tGVyLx>k$Fic3%_~^0w7bq*Cn&F)yn#p!+=s!3?J$d?x{chC?8TlUBwjl8so5^D_2Hmq)2yj3 zdAz$%igXR`9K~wg!2H<*LQl$T#eTNoh&7efj-j-T(!%_#_y@AY(p;EXw90rgoaJ(|T?psWwNb76uCb^pO z;H|4Hi+f3TczRp{W=VaaCHBtmX1Q&id19yu#XXl0w`G^Jl>os0TJp`q)!laymC?*F z0W!;`=R{6IihBsT^R8;h{|=*nNkZUXcOa$yNV94^d-NrZIek63Bpr~sh50Ox%NuG? zbou2*pcRwpS+)>8$#v3a7M4Coa5(}k#xECuoUM0($eTD8`6(hob=Oo_9!&GW`yx+O zpeuG60@Za#DVk2)&IR~hbZF+n)-0?Ii)=7OXNCR#!hFHA;U+Amc|PpF%#9Go4nO&jU;DW)%1|&RxWR za7`XCZ?Y@%wY>6DY;()?-Z-fP*2OEBq2i)bUDYY#F7l`)gK*$MCSdHylwBX6-Aohnj!DkSXJw95(%w3{TBO*qXo?9`rUR% zJufO5iHfKTzOo(YqEo4KaNJuoS9G>`o_)j z5S66HtzAO=L+%qOOz-^W55p6M&8S5l#`pdQ*Mc+lYpUQC_5QcK!Ryl3ZTt&;Usrhr z%HYzDs@D(yO!fcoOaJdT_kXpM|I_CFORnSp_L%?s=3XCKuV?b#mx8I2|F6v{e6XH< zyNid6hUOOr{+YWtLgd-AaR2-)TdmoZIW*_R!W#IX&lZaTTe31B zfPG~zT|F!Yxve-uaK!2>DBLhy^;HT!Ke`0y6e#5T{KsE4*6j9yjCM(C zc%565O1)44(?4chEMJy#(>5Wc+QByR@Gj&pr_-^Dy84^tySK0Bf*#gq+~7b($;C3I zmN{vx07q5NpCMBaE#@y)ET_IO>pWG`*{2{XDoUr-1RScr-YYUfU_Z&{rl+~0)w+7qdZ7u&~?-ZE`vp*oAK--%gEcEsi zCaN!~O%-p-B`6fg^eR$js})bGsJl5&j~-k#y)()ceBBL{pvilZtNqEciV74VQ<7YU z1O?K#zAf`F=YtKz>ZjqL^X01cQG} zj36acdbC`C&VP&^VPyXnLT4F*z&mkON`ucqeo@v4Jz!_qJ`y>~s1K1&7pPz=S)1$= zf{(#^T;H?V&up!@oT+ocev0U6)2_yHr$0P<=hZ*Ctf#QMA>4Az&9x2$o!w6t)p4*XM~`=EDlz+PSZwqo9CjR<*`_`78~s6 zFH5(r>J783vfj$#wO%cpy;pQEaAPe}TFR9pnXyD=6*h=pex4zrqOB4y{mSjyjF*_+ zlZi%fxgtN+hbsuc@%;#n4kr8n;VmPZMOfzdWkqpju@RgIo(9a4c>MK2k^ zyxUDCwLB;zvNyI7HBg^&*Yj%3=a9NLz&2oA>7x-mu zKd^t*2X`Q>_n^$%U}@J|iE$Q`uory~bNOkqwlFTUXtQSD9{b^H(=q@HMi`As=T{oj z2@~sMz0p%51|3BDz)TXW`*fccX^XSFhtdMlV5_8u_0bM<5-pcI#NV~%cHeHq<6Uwh zD6DgH6{@|*AKc0l3-9qb4ZKILJd(T?&vONQmE;XQRXUYWye@W!c~j>&rcAao7Op2s zKU7`sOpw+(j4VEPjy&LI!CZgh)RD2H(rxgtS3b?&-3b!?LQ!StOX?ADxj#GFRpF9S zVO6?dts&P5%9qj}T*)C;_rfv@TX}DjmE>{`tKAbM=+dweIo#LBac~qHUF&NPCu>FE zojxLI`;Bsj$!Q_~@F5TWBbpu^47mj*yXx{|Ctrn4%x~1|xCo{o-L@jNb=?{yMkX5% zaukmI5O-pcN)VZh9(uiP9ya>jeX{nl^Hvi-_fWDv)y-NZ4zGCaRp#{URJ(c2=?!cN zC}Texm{*=IRX%o`Q>c7mt6R?3bb?<&P{iPEooi}z>p6gKvCbF@4BYjvkw%JHREpT@ zF;XwSOmgn7@Z0P~irMyDcx=YMMkV~yVjH((KyEtNXRt|Ub#iLXxm3Zl&UUrto4+3~ z$D*Hqe|j}Da~ZgNk-Zfj&2;m9_R_H?$rZ}omS@cNuEEYJfK@ThTnqfgS!aHgU@`E0 z=PFN2F*V|PuZEMT@q315QViIOO>R5FOAt20X<(~xjWPBlp3^}=aYqbtBuRq*Zzh=| zW>|G`Sao%^T)skK^xXa7RUwAM(L6IDA>_#4jN0E4odDlds&+D0?aRV)%pm;CTO3%e zGXlVSue(y8d+j^`cSOr;RcEuFq_Yrc8`={2gZrWrF6N7IC@K}U$V~E}LM03Qy zw^KAL%)WeIzir3i7`e*{Lj`8#qdW z%Zk6id)C`}_Ta0N0E_oT{lzy;x+J}#Un#{98spyqQw^-vk*b?0`7!884Gyz%K-t9$ z;p5y!V^16;AgNk@Hi$Zan8H&x{%8lD1U9ErZg=Dv2ExxdULCFP1 zCx3SQ)MhMP8&i|7DRvWSiJ9YnhjO(Hxx$(~^wr|}Z|A^pV&($?*+!4u$$$WqA#~FF z0X@h8Ix01%k651T8MVqME!^s?W^-=ec`uZmf4(1j1LI-*JWcj-=NHy3i?vPuQsW6Z zknY2yo@OD1A}@SfsLz6P|DFm~#bqt?rXPk(nNGdNRxXWnimMoC>#htIH^}Xev0%$r z%ZZ5R&I=NVnLp~Wq*2Wd*IZyVz1JGakOJ^4WBVs#LSnw#*cJ?4DCmY?>o~p#5O9|& z1ByE)S{FLJ#ej6T1hevtHl-uim)Vi7#rGCTcaF!43y+09nx#DHBW>41Z#b)&cn=q9 zL#&c;QGY57y);j@|DJp3ECO+i+Z`hJZvy}zp%`y^M#S=>cNAPU`YI*t?EDdsPGu=5wG8s~y|dnTUkJ0KL9w)|B)Dvy0Ythsl-#EN@aW_<%*oYY@%&i|f42 zsdy#raY0e@;cQ}C+CtG53f1cEcQfV#NC4WR-mBsp)g83T8@|JgU;4g63gbqGn87K} zH3f2-TKiJ@Oy=c|W%{FuZ_^>I`n`ey1^VqY^_iZu(&?%Z9|w`I+~21X{kNaXB8Swi z)l$(UoqU9RDY-bc7dc2yz7C&+w8XD2oj#!9O0N_7En86Q#6MER5F_+_K|ucgiJ#m+ zH2?y{D$PR9+-WqhKspu2g`A1l6E{55!StOtz^4Lb^gKNSmGVv;(mS5lDqdM`yN7xp zT5tGcfJ7!N-`(J^&RmGLlA-?e>?ksX=T!$v?X0)$e_a0-ST&T74COHR++v`|NIvHQuLK8L73Gq`_Nf04N-u$y5r%;^}QoIigC_Em0iY zs52Lx+NBy)^3NhvG#slpT;ngs7jj+H{E9Pi=%?hLw84$l% z+_y^5oP?kS0VqB^&h|Gu=b|B-kT`t?h_RE$3*}2))6#4#)}eqsf>y~Co~e?>vFSTv zA+g)T^euhaFzE1ytVl$FONF&QHYvAzdTGIGH;CF-Vw=ZMRvU0GbJTw#z;n2Cd1?3o zBRcW*$gD}J&b>K6alQZou=PCQ_B`JmMkGCop!4z|yx3(gM*)&=E%P#Sf{j*ZpjuHX zbsO(l&kw&`{492(TXBB}2p*f#bTZn2^C>sY7wS%wj&f?9A6u|gI_$A)-VD?O7yHPF8OW$b6TA0*#H zgmQ}ZjIwB8J$XXTYJz#&DRbK00z})?uLEcrraeZ3U&SaAtTm5Kg>E5D1Zyba2CAa% zBmRn0(Q=UTOkUJ2UY0!_Iv>{x_Ke`CYi#8w@z@^zMnpm2ukD`9AadowVAnTP=STh3 z1>11Q{SVH*G9Zp+Tel$)Jh%lXL4yZ(NpRQT?he5<1lI%)?ykYz-QC^Y-Ck$!eeOH= z-t+#v`NI!pdb+D>R;^FgDna=9Z&B2(i!QM%y$X{lmaLv*uK~1hsHQwN zZtKRluDgCxz6yAt`pMVwqZ(Iy+?QkCT)X3p$m{?p+T@pxAQ~YsrfM{B<}(F=N%v*R zKw?oM$)6CX>kW{6YPSVi)k4+VaiK=>Zu)Vdp4sYqdZ=0OkpQ<65r;Q-kbkvo3SK%)uqLUHec-v<_(@u7hT z)oJ7DuBlIzu%W)CrMbCfwj%Xr@FG|;e9_E2LhN0kkl}0RR><9*S@R&2>kN;K1|IRMP_H&%5yCvVc!0&}pq9iYS!yf#d?4E~r3W;73^E zKM+h09g3F^MNT}Q7*>(liS-+?eA_y&?LTOXWFivGh4}L+*5G{>%(?1&w$Gm1KX<4b(`2YIf7@C)3C z{@1FOTL%AuQV=+((#pt>Bgh_Zh@+z_OiI!!>(Boz2?<63_0@=hxN_7{_1+c-a|eF`4-&<(|Gx!+84eb4FYg{wMsjNxN)s_`_Q1W&5f;2)x6m7FlqK$fA6fV}^^CmrH9ThUca~ z<*vf10byKI7@ePg4_VeA{%k>TrvFGw%7hr57hjrH2xkfQORcm}hkDCe>m?K2J~aD` zUDY$!Kxl+3#Bh~H(Mquy7!JH5iGzJfNJm~li+i8@h<@%(jgimtt58_4CA*vsl9_(( zM+SAvR=A!gCOH6v#>l)U|dCSR$9&jRs0bnQ#lNnt)fE%37G6giUaurjqUl8s9V zn7pe&6U_L%cc8e@mk&Wi;QIKol3ZJ+xdOAqkTAf_p);8tPwF*`LwMUqmUz-gQ-k3X8C~hrW`%uZY~kd|tAGHMkOk1gQd?NazxTXC1}6&T|K^RME{@jW;36!e0V8cw zlOX9jOZ0sgiFCwRA`jI-SAqxc#WZ6TDrmNKkDJV_N@~Zil}Ipw_TP zShC)AT!(9!N4l6PRXq)ib745Y2V7kI3jQ(}i@l;MpO^D_)8xQ}V89d?rP>PYCX)Hw# zil45CiH4$$liV)*8BfU09*fHaaaG&~-0;69T7K8!5FywBz-Y|qA!uT*sO8vyU-l3^?DDwT` z3a@(A7funGwDd@WAs%I1_cGxNEpltO7CTCEtOOFAn@Z?AUzA@jl`$gY5(e0Krfu3B zOmGa_;Ve{y<{FB0-9ESQa`@i*|u_gtM6|L75RrL5w+b`*JFVBC9O|69p`_CfQp}P)Jo80>d z$pP(A8|rBGw}CiWjkY12X9fT~ znrpQ}jvR^j#3T_HmfU}&K1_8s9tt9)Og?nrpKKe!Q7ZKS&nw0}zBxzUu6 z4Aok#FBwXcQVUL&2eZ2h>mZVCFSrg!7+;XJ15pT5Y?-f(A1<7TOJ+^<7TYOXOG*>r zQ==u%46e}pC|ZjL0eskZUIK4X2yd1wp6Q)w$FF6jn`?|Z%3!Y=&ew`fUN5Ezxk<9A z4iw5PwRc|~mL78*A59KJD8^*a#y6Gj$3sJc+5^WB)E!h^J?&NlH%GsxzqyE*#xTOB zEjumF%bPFK#eNW`Rb#Uzg$BD$=TAk{cqYxl0?q1Eo}Uzxe!6b^F}<6esj9KJDL7U- z%KfM*ek}O66g#*iDB@6;3TZ;5nS4RBwfW$w3>=3w$doiPv$8C}fQbj#Op(oE;h|~@ z2=l_b=y=ZCA$m}$rj$v(Htr2lcyqryAVfZ7-TWx%apLW-Rlgf35|rO6s)P1Ou*TO-TvFfifRh z8gsq>mN@%%HIrRlpF5UM7~)dJpCBLsHazznfF$0y@St{le6+HS-mybGqG|BsRM4V* z`@Jdx%$=D5TDRk;HWxN~Z44HJBgv82?VFb1xd`J1#R84u>w>18W04g|04q%^H$UH4 zWLZj*x5j;9$J});PEJP)F@qCexUq)=VhqtP?1JWbNG|;t`EHeNh>o2$@MO3!Nj4Xb z;_QN_SMt!iEf#7yN9vQmbHm@4am(s4t}3X4upNL9j=wJW2SPB=cLQw0TMmM(uEBS;ZdLi} zWd`Cz+V);(eJ?j!ivwWbFLg%Wq<|sW>aK|zxmu`ihd)k?n>^tWVwMyYcX!nhm_dz_ z@hw%tE|M`QINoo!T~!To!ZNCUBeSAC61%p6H?&j<2N=)|u_Asw;!LxiPbT*|E>Qj5 zIy>=BaWjUi;1#Li#YjjY&pgqXQ8oqHQbYB-G;4kx3Vj5Mpsjg5(7U+|mGRp9Tv+zX zGZOW17}M%-xT-x@l*4|Uahb!W@#r)nL@|h*C{ zH78>O$gOX0*OA_?yn!;IH!^NKELvi@C9D^lK5_X@rQ^te2)f+ zXbAfyr5hyIFQ*wJR;8tO6m>WaS9&90n%C8RN#}Ni8yQ0^7=cD_tB&;d61>J|aw!*OYj=FEz&uH);gzKE7a;*aH3+C;%LNkOe@?aR#$ z4=%#pO$b~8$DZRY=@L|7ieCtnO-BrQgS=AGJzQ@(cQSnn`p^4MIP5$2F2F(n;?+Dl zY*_V^ramXEua8@kPjPeeowElLIm2GjVSlpm6O$1%Suk-!oBtm9oqV?z@H z8tIO<*0c8HR6MWGuda&|Ip)p1ThBfVL7a#0O)?+N4a~Sr#>Cmbpod5KoQdbYP62>| zO5PI;E4@m@2$Z&tKHBW$l+vS_H~wZ z?p>LYA+Wn3@~UY5@z~ZBf0be*2hpqzauf|(O&V==ZNZD?Yo5A8K3Id+ zh{8hXBrpD&_H@yYk0q>~nJu~1W>P{Z4SJRB{|XRRJ?Kg7F=jp4J9o%&nebYS!)~lo z!oL|xi(T2obZ)TbNz_x7(1pRu54YqvpV{UmSYiuglghV>}3vaVZ{&yZLH|ZIkFhtSZ2iIqKbp5!J_{cW zXw-#~dp-Cl#}&$bfZ)gT=$tq1TiTT1okj#?1xhC_k|fXO$y)S5ym7En&(iFIdS6=a z&f?yEPB75&JlQuUjRGZrlBvIOWMRqtwn{itu27LWM!s}JmGxzyM6*Ho(q1gpHnbo}mOy>u0K!P>f0e3xnn*)FdAb1e9WnFWoLKueu&@reHU>*$Ev zJu^#l@|FVlu(!zazM~CND7TNuw*LdSgiA}8JTfN-uH}4@)EpaV1UX4nLY2Cnm z`nZ3J8^Bq$JfEW)$C9=?%S z7T)JBomlT54NsVgK9FAkUx8q~h2=lridLF*cXg@x+FyC{>vtQ^F# z+sFhawd*f#x({x%OyCnfzx*eRUJX7!XscM(;-=R^dS@AF z!(f$YU9=&@s-gQNf=$Vm-cwHD6Q6Y;yl@k5TrURvp89$BBwyprY`xl%xcFllKkO65 z1X?=(Hy_dbUW7^fCy(mHxWjzqLJr65_dHF=08L1s^`(;J&-?tN@|mZ_e{~)`S)b&@KVMyASLg?@S-NlA z#%{F-en($|d5(>Gk;NkzWOqOB7`U&rx%6|D~kC$rmGH6wy=f)`& z<2*q|EU0M>^bVc2*1xH)wyJvN>3J_^c&pM{)MtWT7U9Qot_4= z?c^QrB3ZX5bFFrZE`oGCzmlC0`O(G+g%h~w98!7GM}i-1Gn+m-wQtYCkn-(KPqs}@ zPZiD`R*c*`7kW2yS6_p^R1$-|6mwzFFDj85{_AQYLK{_Z@t(d39k0&uHy4S){d@%n z5H0hC%W!btvc+AelysjLM(|z9YY%;`k4zmrxtp*+>u=yw0lYRdQ>RgH@~FPDzyvmaaE5=b_Ox|PZg;Q< zoUJjqYZhp8NE&DJv|I}saC)EhPj_))Svx0D$qOxRpVXLIgyL~s*I}1&T&rF_%Ov=H zevfG({l8IW7c11|-MTAn^@7LadqzeJrwakkV-<7+naVvh(unb4qRK+kau|6$=(@@R z!-%<;o@I|75xGVrz{|+!gsyILT$K15KP=Cs+3x2AV&cyUfoT}1W!|L3KHlEm%+OS5 zSrSnhU%z`_()prA@}m($!~U@ABXg?xc=2pY756&u{yD>?nW%Ti%E@|ud6|cei|b+c zG>of;?zz!kHUy5#T5W=e_Dw?5uH9UP@wFo_YoV3j{>}cA&Hd`{Qjscza0YSv{rsxL z?$G&}o1GO_Nt@ldTH%&d@uVi3%`5bZmFG2XZ}Fvld`>qWYNoGcHwihGPFdn*@BL-) zmJccy(vn!^PDV^;jq+_DiPqL~uW3HyIUq&QKh|6B3~M$x9?e&oUo=O3vV{gVo|e>4 zvc9a#Odg*O2LzOS^4H$qwPRGfU8s_5opWc2+@73RV7oFKW8W4L|U(9|ru+SCM4 zsM1rzkf=4&F`Dnd&5;;FP?XBMuwYZLKptKp1EA+5hz6_tj zjG>N|M-YhLLB)pagVYE*Cq?v5j(mynHqT)b0p8Dq0rhlQfQS=e{A)^0VC_ z5%3HEt81T`ar2QOT37egO6-1-Ah5pTpG_W&9gFX%XBkq@G)+zbTE{nTmd%e5fEXi| z1Lm9F_&48)QkER1?Yo!NN}OqkeV^N!$x!6Qm0(G-sr{5}V(h8DEu`3e8$+WWpJypN zn?W08J^Cncjr;JP_jqubNQ+>)U|J%G1){N{d({Ndv)q{I%j>;BbA9Y&%U>nFEm(oo z!yl}d5$fMAw>lp+X{KAsTs~~MS14mczOJM@(lFf&4svj9WJd6=MVj}p7%r@`bLCYu zg#uDsZns8y0f6A(+EQ{j<>$AsWVITn!8!(|g4n~E?W69#`9X>_XkZqSx(@;foW3Rk z;w7>NUvbR!TT=a+N}^RVh04gzS%@llJsj+@c+;^5VFQ`QbOO=a&MG-7_odi2sBVH=+BP!k%&PkDbP^LUaBPvjUDwW zv+lEC-a~W+iUVZshJ}_roW^U|PIyXwnWMi$q1_^m8p%>^m4E?$#Ad)(>?>4sEfrhB zycB^A5!_?cj>D7R&ZJ}ybK?vFW==%(gYPyrjt`UOTu$Sk0FU+_BHxY#+F(eW z>iw{EP_X-o{B_H6vYGJ<-guMrbxUsJSE{k#d^m*cRnhb z;cRm!5*TF*o~VL^ci|<3jd#bbMq5?OQ@L>$oSA#$896Wx`Aiuirhti7MWrZ0;`LeN zXn?Yec1!Tq6@TS*?l7}7Z-h|Er!Jn*ATbo;3j+g66)$gA=gUKa@*t`{I7YWP>1PgR zsvY95)@3@nE5CRku}+#!DH$2Br-=~m?@wwESe?K8LKdM9kz~&8*48>(&MDw2;6jZo z#1?=4g>aD#E$_7C75yU#rCM)ZG^6(1r`ncIiG#soWBv#m9Zm1LR8@JG@?=p?+flxc z%}EC7o>q+WUa4SxXLogDEq;l~HYohNf6TB#ru$k>`bMA(VJf%k&iAz{?$e1sjX_8O zC~tW`CUFRFxrM`Jc(Q2uZVb>#_|o5;4UQt;$bd)U&6(+R?tJv%oEJ#$PgNIfR4uOb zyUG~vtLbm`A@RJeN_vWg2QIEP+-O^oyxAVgK3&DPVQNx&r}wqifZYYn$S@&GQmE|B zqdka#z=gDpxcWI6G}-6u<&JdCFeZDRg_311N^9ZH{Qby~2XZLKi{vB=x4WQ&H`jP9 zE+-Ei^YHXerk4=l(Vj;v4%Qr{Z?*9e@#Y&ddYpm0=e7NJC#*hsI2pA}4bhenmA{OO zqM5>f@o)rZR3SAFFOI)Lj9DnfQ)m{#v<7zPI-ujf&1ufZPLLBKsT{@2ZA=Mk1JNe^ z0Ds};o)*(C&-o=q9W~8Is^Y?fu5yB=07HY=I(_Jx&ZOA7ri~d!7qiZH6$43iC;K&K zMGD>5=Iy5pVDj8Ssqc#h8Hpx{*^M*7&`n7una+>d@X(1$MOKY{FOr?R?A1Sy-V;%C zGWlTLN9p{+`j9n99`&p6jVtz%LW?sQ8(DTdCdKzP4x>?JTCAwKeFGb6N--;@Ukf{(j z^+68)P>_0vbrB9Dy^+deo;(vc#3+5)OUqt;T1cod`36HT_@dM!;}@D*qdR?z%DbGB zgixpy78q)4Z*S}1U~9|BzL%_t=zsZjBBM?L^rgCQbR+#)89B|_JNPvQ3MPt?c)G4m zPEYn&l4E9?mGgqiOij;wdkhrc2NWbS92hsnlIm(}U%<2OE+ID+6<*vIc&rB&Y6d6F z-cQYv2{jYd??z*Xp9sS~T~Lx$xA-#2dNY8)D%A9AD1#ulTZ55JR_-#cG)U-0y3??i zK7hMWr7IR3?3Paw(Q@6;3`T~n812lGx{45DxC%IJ4f`1xNA%&~El%qM;hHfQL>z%( z)9Yapw5w9*gCyf0b?WvIMPs2MX zz4J>oX*>pa=@(X#_rRLZcPJ&%;p~3D7z8CufEc>X1P+trWz<~)R(CcG#T9;ds>oYZ zoQh*dewH)Q8=>#$7(L)0!I~zMjKp_U9-gGFUx|(*uDw7_ajL%#-tnSY_iv?KPs|%d z9)7iey6#6owBRf$B}!{CweVkla_VVzSgLGl-yiZZ?bNmXby0=HVVZZkktL0py8~$K>5I z%PfVierBmn=Wwhbd7R$j1E2unnEmge0b$Duw-3>>;;V1m)f-Jrq?O7Frxu2}fVD11B?@!4FdD5FA!8%dpE>CoTP?RkG9P*Qfn2FTlnt zos#raLxlKoO=MMoMyJ}lPRwr{d=`}Jgy1}C0NV#cxSQeD$jDH1Mt34y8v{soJh zUk7Yfi?DEiUZzI}k4~5}QepTvQ_>R%9v&h?sx}lc5P%$pOtGHLBxY4N+vgJ^t~Ik~ zXT1}ZBaxTc)vwNnQ53d2Z|Kyk_JTjR?`3;%Atu&bpH_8lZtnij*;sc{3tx_=7ElY# zq3~ktX-Tir)@dR34aO7?I-*ZU#2^j@ebxGJ3EqXGKtIIFqB@eD|3>Gxa}~C8^?y1C z=k}V$Ypi;)EqyYkcR)tWclX1T8x=7zahfvd&^Mbpm*nEx-as|ynVNX`hD`YLp zVLTM+c!ABD-P?YPI35#b)0=WUFc+f@5nhtIoqqiUQt z`3?SWoSURh_T)^@pQn~IHSys{wp>S$q{_tY53BD^&;$cZ?rNbMQu8=gF4i8f-oQnu z-rXhgsNKvH{oF6m%?-DB$O@w%F_^#CuTF{Q&fm&c9L&}huDT}nt;Az`G7}WOZRWVR z@i!#nHGW=Z$nW-KbDs@IH?A4-a(Z4aJM~x@T(aAKEahulzB$+pt<1#BnRT7#)x1|P z@V$)JCqhiD^<08fM>uS{Z^iJ?IaN>mG|BbkcJ-FszD0BA$NjE*_o#IMONqw((o(Km z04lNeHn(*}&B5R5jM|$IAnI?Cbd* z=Z#+Y1!}$Ba#r@x!txOlOBTnyS>2H7V)qK)kKj#iT4-Un<*()uO^f{H<>JyP-}Vf@%YQs#}#BZFPSSX%gPFK7S)q8 zt*;u3bwl5t-aaG;S7?1<`91WbY2%G1A_5$%XT?X>E@y+2ZV_B+K-7MJZ3r*2_;|^v z<<-2=N%-^E;ab9kS{~l228V||Nezw8!QTUF*Xjw za`sCLjm#ApvSBUu^ZTGV>lYis&8NqP*+?8-OXt!FtM4UAO9!=A6TkRY1}n5rcZKrU zL4sKP8=5#3Su`Z5`U!(ap3Z?AhWWbe&!}kKY9j-T+I@}NUvqo}klrw2xFh(A?)$rA zCa)kEy0ewt_NLxg^1>$6rb)XTp#iYri^mvEUj0kH-%HsXp-u}|3`1`gduIz_c$ex{ zTZdAHJRo%Lk{t^;Xm)=5Ciw74y--zuRG8W7UfzYEuFOm@o1~Cy@`Dt=>Eu3Jt4gqF*pCciO@dvBDt_LEt9xSI*sM^~Ox3{|; z#6|)ylN-F6^u{tihhWjFRk4^PwV;!T|BCSX1p^=%;al#c-ZjmZTdmAI4#ZINtyk7n z1!PKAp9eRW%$Z`aM}AhScuBBIYVtInT0nBWJf+%WwCI~QYka2& z)Uvyc-RVjd=3&_!6DHSo$n+f&pqlCq0EBq*Vj^JMZ?6+(sO*TF;;chHYEQA1pBM-Q~Nc8#;P#jZq@`?8mjh3ku z+vi@Ng{qs*J+&68T|`(FA2y$Ts->bmTP)dn5R3|(E;FCs{!vR&BmK;5v75u>m-><+ z{oKJv$yK%#`VkdbK_0C%_3&snSM1YQs15^@J)bzhzH5vAFKO~y#+{{L; z#m2`<Z&%^o8i7Kxl>TQ0&MZVuGdR!?7?hP)R@Uy(E-lYzb*p0-A=3P6d<`?&f)$- z`maLYKJwmYNwsui3M?FMM`gxc$L~88ae(b#shcXCJ=6B8x-l((4KKMAHwQc+qj?+z zMJ=07%o#Hk@i~=;E?q*X2?DK!&p$NskyM|nH37x*>^6$&9#(5P2NVE+5_tpfY`7^> z`8k=%zOC+Im#(_PI@sg4|D(m+>8Y#r+3@8KCLrxTpfKWV(Q0gbl>X^<-xcR{GUCY0yihE%V{d+G zhKZ$8*=KQAWMXNhizB^&MIO=DO}QU`Bt89J6JPh9w{k`#Rg|;=y9|Dk7w-c>==2y9=rh|ofQ8aFM8*RgZr;4cBK@hQ=fDYkrj zeR~|Ksd}bufV;BeVt^TX2i% zQn@mqp)j#9v2CtThk>1g;}`8V^hXM(Elmhmu><;=gnxX z;3`FN&vUWUl%e$NIPQ>2wWevh1Y)%&_2yMUa5yYlFX>)49H~N>&gbTMndL=NRJqfS zXtIyi*P5obRC6}1agcW!xHinsVM-ntjAB(L)!nsJddO#MAvM83GOOF|b>_K_Pz$H@ zl2M3!HosLUe$s2Wg$9MbT|Uu&XA59SpyG(V)$OH+^eg}VIr4r8a~gS4VYo~}J#$tJ z4f<$|OmFeVceDi(h9q0j4@JH}{tL7UM;u!15fwHLSaI`gM9AAr2o|EJd!bS((eeAB z2!5%Hmbb~bYg39TEU3i$FJ}S!x*&>p)+Vt4+7D`MdH(65=yod|w(Gwuw%Pw7A;tck zgbe-L4PPYJ4NC^~9&FeZ%9x`YDkmN<_>CGATotpRPF9qmqColba-|zKyLzt?f>}|H z5*jiZjg(A38uAYr9LdKgCZC=@z7m7c4oip!k^aK{zws$qt~KbO1UKWPAH<@n+bi|0|lm_DA4W)z!m&R0#$$y*ag%8KfDh}ZR|@s9Bb(0ZOS(2wWhJ*o2k%{LriS! znp4Q6AcTlLQ=rKN*WF>uv-)W_+!ISZd5vI$Gf0?Js0#5jTHbCryUc+V0y}a8l>A$m zb%O6Viku@29FN2MMjWWmhjki z{P~dMazA6X%xyFLEwAOn$v<2OXiUpMzQ#Sb-$ZyOmk4<~S@xX1kWVk$))Gk%K91#B zB#NL7mER8?FxgF#&{d)>Fx%3oN=!n_BvbTdS*oFzY2DPOA9W{LydG)M($S??EG8di z<#HyYdF;-f>c9(gDJ)jm>Oxl9`;v3iCBf!S@O+~bl2EZKP3^Y*&3$jjL1mXvY%x z23U33O6;!SGTaS5`Z&i0hPQlfl(-#yTsJM9bX6*nue0NCDtSebun`W;`*>S;q+77J zT#eMDpeWkt;3{rytv)g_EP)IVZ;#Vws=1*E&LjQuWFdKFFut6fI_sLdZN;GvAsWGP z-BuQKFhdIp_d*d(DntPPr&BfsmE&3V3OyWp`t?kEYd`xP~T&6?m6KX{1>(c7teUJnwgquEm>wA(o^YGBC?IgK3 zww?Tn;sz1-`s&2^d+sH{TYTL?(OT}p2xJo9dgSM;{a3xz%T5x!6u!|1JU8NsyW@eJGa?rrd7$Z{&!KNpOhSEl7U z0uKaSbhru`My0cH8RXDaC$_~?`*{th3oF)NkA>aEJCA=OX=T3hL_}`koTXrX&wDi6 zo})>@!|A>;TpYr8f-U#I`W{V z>rig|95;}{Q&DqKuIlhXu|?%1U;-x2gQq~##+Q*xDj>2-*h3>W8i(h6Vx+rSSs|Cj z*Zu0+w1zE3>p6*B>Awr~1NV*P%7;&FQ>VQPByDFOAH14B3@Y2HLz_FB<*O$P8JCKD z_J4D&HBosKt_b((0H4Y2)0cb*Z2HG}$B}5liZ?d07nZS3Y`B?@50l5!px4{h7WLx* zo%x)#QgF1hH?gl)(*J{X#)XoVyXlire{m&s#g>^s2Qr`mBTx5*Xw=m$)eObEa}V-r z$l5ZqNaKdEi-4vK=sa%yC}uwgbHb|q-F#LED`3lK`pWug>6fhp(*HoYAgY$lH4pxw7wlL=URzf1tw&5NCfd4XhF2W;jt~Nh( zZKppuMs-uTsPW{fL_zwr2Ov`bHkXzh+nV~FVd}OKG|vu~XIN9sn}FBd3IT9V zA3Y>^)yZ#(B(c_9+eG&%YmLoJ(YZ{KW-AT>I;YkImp`@R=9}S3j{J)Q`&l4)j6VWE zaa=_>0LXdQvO12<#qNvWnqPd)MYS~2fNj;F-g^3+CH@YS?elg%c$KV)bjptO#j>bG z72^YFLQV=@*=8L#$VrW7pYmM9>Bg;O&S|%fh{|jh)(%kPelLWXeY{XYd;<^x3SclxTvAW>mG6)D@sP+9 z4G@1*_2KnoiHVy9*PhXVF13x?H$mj@Mb<7UHoEii9Zl22PQC}#WK|6j*xNtPY@Hm3 z54oK!2CkCvBObB3ACxPa1PcHlwy2iuFG2lrZpS^8zcO0Awa)G9`Wrs73p4niSk;KD z_f6M1--&j&p!W0F_Q^hL0OBa(iod^-lbVtIoM{xwJt z?@&!ZZMKM)$j16EtjWot&Uh|V6}Zfi8^0a;HCkonc*}IOL6(kOoYL%xIdJfeUC|oU z$NP`*WPB6H(MFEcJXga9R&@<|@tbIv#**H8xpcWD7F_2Ye-d?!lU(~2+9g}^3|OA- z&T;9XD@3B}UDG8uK7>D%={5^-dN}MYM^$!m@kyq*d{i=nQ!U%*eDb}`Lukl({S^D2 z;E7u4%>L`?ne%*%UiBi9p_#+~b_5);mBnP0efoEljY6EN`kX2h^?L@d@dHebwMN5} zo}l31Gs^%guh=UcX|m|e-S~$fDyNd;7n9y;Ch)lQLwH^>senn%8zQpz6u!mrtdW!@ z_}-`;L_Ks%Qj%ifEzK5-(f6s1n##G`bAt)iY*S`WXEDEyRGwXVsA$RP_8*fXj2FfK zq@3y4J+Rrnvz~9w@-k%Mp(x2diFZ?0B?P=aqxU>I@sYZD21_HFkaC+veb9fyEU6l{1D-u8fK-2|mo`=8^<8X&_V(|sx7Gcj;7-y%U!gFQBq1b{yd_AWdUVlq(xE>gZ7=UucY1O}{r zT3ON=#;*WON$B~0}4y!*Pl)Mr+j(1t^bVI_D|h3^|J2|z!w1mO_78njv8B; z4&;%&F`zR4>W_tK{vS}1?9V^s0IDQB323b6Rs+2RblE6qMH*X?_Ebd$pOuP}3LQaD zYo+h=jEyoL`28<<{}EdJ{q6rBV*Ni_J}S14EGZcbT%@;R-4Dp$M%fsSHU9sCrQxsu zM4Do{q&%QA$=XmO2$vnr2>q{zie?mqgAjvDhu04^#nF#O%aKhBUivR@eegSBO0rH? zId6YzcyJoz1+xr7(W6eKhWNLLy1x8f(m~Y0Po8v`(^kSJ1ACwqbPe88XE{6Lg*VU zxHXQ&3HpV0bhbdM;Lp@!TK^~1{I6Bx{~8-?{!0vqeG*W_+c8t-f1e$lWKU(0EY<3_ z`L8S7n25oW)J6-6mf6KffM~e@OR37PWL`=-XakJ7KO-+^qe&d3IVqY12Ay-((D=kTn1-t|>7Msw^73IN`=2oTqV!)K;QB_(gA_%+xGtu~mWKFh zb*_dE$S=Vw90u7Txp{dp!2#TFTgF9mf!1;aIOW@bb}}1BT|ibi_pxg|sItuI;46O1 zb=K2$D)RCC^}zQAh&(K1&>X*JWn+TzKU!A8&afN?gj0lO4{GqhZynEJdespjT`2{=? z+y3yITC>@!o6ZUWbI*yXz5A}6R|J{i!-+LV%`{UdnPa6am*wv@kk#x53nr8b)vu0! zCaX*Pv+XmPE%v@4bdd=@E8F0s*DinF7kX3Ko@Oib(5#^#k&^3dvzFVHh&}=6Ebc!= zxlU#8F-qKjy*s>tiEBA@bJbC-Fizh3*tK-KX~GROa@zM)BPRg-YJ!v1cNLgAz>l|X z9y`}dJtLoSvPp=`bZ1hQ8VT_iP8Vo*euq6&pFLVGXjMs%IF?NJbW?ckhv$q}T zMt4dUf6e?MwccuPM1?50Eh-1mWdFDQoS8aFP4UZ`7F?lm=8PNLS(g)mAS?v%C*CMx zz88g*-x7rHO^A^f4FcIGYG$>jqc;DOnYZ^UI-Q8V- zySux)%eUWmX3m;fXU_VbwSUsh?%h^(*Hw4j472WcwZzlC#1r$mK>-wjXACnorg9OU zOX$Z6CON%BM>-W?-tu68?qn!({f_TITW5jH15|s8Eri^>P zv!ezmwgxcBaW~w59;G#;|G`lHclpv*pW-x4{KLTW4#E=UPuWMyL}}%GnWmP`bMM;% zy024W>Hyc_cnBajhSe8tqJTlQbf4LjY$_TdgTC>4Bh#Z(Y9$h;+p=kWn2VHG-_eifiDDeGBPrgy9uB6g5Gg zQOC#i;Km9(b}B5>m>VoyJ3Ke@f_c{!O(0`@9jRyB0te*ftlw;GKKdQ-6Kt5i+RX&m zKRM4(#exBd9>}j~*VO0BJ{RI~Q~Q0EM6%D}cvGDXK?eKiIr(Zo2!G?ZUEC&{4=gOE zn4hojGGI3>5qUK}9Hs|{tvoCLZ3H!F8TAYPKS``-;+ez?R2Tg*d|4#!uy75x_Sd63 z%)c{XRxghF#R^!ooJ3UubBN6YfWyU7xoB>|+RJ^7DBRgHl;hz;0s||Fqep>*cFE2O z{$%?BDiF0A7j1`!H(D&&0CUlf29O2)kg3o3&{~NnSvw0d< zG&smy;JR>xW6#XDwH_ZP-XL>&4h2McZVuSvV zv#m1ZJ;`p9WmHtHqFkF8d`Lka_uQ_k-fh~2B9W-S7V^8^yv@pW5M041wiyD~j|#gv z{=2Mt+bAGw&cr%@+YOP_A6|r}`i(Nq2iza@syC3yHSkBv0X0?9vi3O1m2sP`MI?#9 zG{%4-r8-z_EH$jgprQbqkHb%?2pfd6l}{t*6VW(Kmje)kv(n;!zyQ5x@IwBQ)trGo zrq6Pz&r8_E9ZPvaV=<12Nm$B!5!*ix;u`=GjQxqHEtwKES?Q(@(@l2(knL5$OPr9~ zlGNcz34eZw4v6h-Y;ESUSVvKR?0uqZ+Q!1`dow# z#GGp`y6`RE)7lNs99zpZzyi|3hdyJGKv%m0E)2=9G>ERdU`XIQcgGGW7~xgV31=0? z?1zOYKrPUN&xMy5PU?Z}1yP!~>IZ`kJNVE)IdR-?p*QAp=fSncA)|=D@8>M)xWM2sw_hQ%I>SRc_wO=@MoU{B*49+rH$DclV`H)ekp9&8iBvk z&fb=Ox7)pEeo6WE?6a`)h3QC5+4_=3srJDIp2v}wve{LQYDkGERAkq7K|G5W$4xSHUZ%Wn zfCKOyrRG|EYs(9Guv>SpnZRI4R4h?CR@_>oDNSfZ6BVRI$=cCLqlKi}QozZHg)nlp3L1t$s36>@PsA3txwrqSNFM*dsv*?JC` zce!SJh4llM%iB7cyz2U{-}HW&BLyC`Tc*wFg9CQ9DS*u^?>+hN6+Q93Cj3Pf?>o!b6tnJbFW{M73s4|kDot&kMZ5Whj+S|KCJJsNa6zD z3o8yj>^#2Ga=w@ubBi;1z03rIV)YDRSUAC^O%frj7`tLyy**(iC_q98l;M5+R{#18J^5RNRg{73>OE+l`6u=w5?t%Yjrev*9qUjBCWUjKM$=_7i^DU)S zQ36H%pm>t>=jcB)w1RCE37kYc0u70|vXmYtDW&#**nn*Z|KF&y;(z9`)nd5hai~#o zs3pw(Nr5b4Gp|FfJr3i_S$1wmOLx|9#d5ige-=sN9Z09j=GnXO`-W2{nTe;Oy_@in{ky5BO4^ti}s49h(sB^2F zfh^?yGb@e-Nb@kel$2~qm1qL0M8AV7-M?4SpA*9n602#ht!0?61A||JFyf9-ZM6(v zlSRskZH{udoYeA$x9h#eJ{y2&@}97lmsJba6q)@4Q)rMz!=g6Z`7ZrpEd`V91%oUs zRSZv>_cOl=zQUG~(q=`G5p-Tf{~Tq^a?~!q#~&N1)k%6-Fn$qx{1x|XUPo;+H-+?S z819@wP}_)kbh&SHH%NH(nqSYCaI;@}6%bd|1sTCovt`51bB60Pu@|AaG8zM>>q4*5 zuK(-RXW2at&wSWjNy;Cz`&F4(d8RHqufuwzb2ms zQk*^RqgX-oc?DwyPXPV88|BA6xp7&9%;=LC_AfmU2Q5hyNTl~mA;miK?uvTBX2Q_Gf3fL~)?zPXu%yikJ@pZ}HFnL(DG4S!*(a4kTaxi7t*&R6 z*YQDbNmyVS$fITZb5>)?NaSbIjML}zgfrvaLw|>cgh&mAC#SMJ_ayiQ)+JmqT{p+j zE5;csZEsMPPBh++(9v#M3-W}ott~Fecw|Y_c~0`bL73k2tqG`UphIW);}E>d z{F1#~LY^|fq_eT~fkTG^va*(U6*}0zu0)vvL>md%6nxy-4h?Q0xhUMVKd&5syP+BQ z%QUsuVyX5`lW0nxN16897mZoxgI#&HR_O6}-8JG3#In?OP4l+5-hnr~c@K9^M@QV? zRQ1xihxm<7)FTR1HGTc^kSkE%&Kdme=&5kr z<1eOJD9-f$iDWk&ZHrAjZ`^P1KDq(yb$NeF^7o_<<&~O0fbJg^bNST<4`+y(-pPEe zjYD*m?fnGKyuYTdF|CSJ+U4(4{b%GSYHUA&Nv?$Hd&yU!+e z)@2X3UakrqJel2lJmv1WOGzA_@)cGWw=Rnvm)b}px1rQJ>};}BC+ia{k?++5N0$yt zUx&(X1e`MLZZsz=Nn`W;Vp<&R7s`DQ8rrlbQnFp0zrGeNpY(^A=fbp~ntIyUp9R4P zqd#~2>`$m)Z*-852<{hZt|>SG@?4(cwj@N8S=hazrovsC@>{0s28{LDx!Ku-{3o!#jr|w<5^4OTIuP^N= zg>_dl-6zp(+VDK*aCD9|B4B{sA9DJ|Z=VL6^9l?WGQM0u=W{!pP(!#uWe?fRE=;;7dz=@i`a^bIb$?B(XDAtXL(;L92R)}qsZI+FIy z_!^Dc(a(A1ini_1mI#oENJ@5|ewZwhlG!{sP;B;CRO+;5$P^G&v(hRa#KzTSp3L%BH6 z6qh3$)k4qu;W>bWQXeE;{0dq>+&T~iTYZt?SsXbB@z|SMr3suMXC}g}H0v{OKx5SD zEvDLHgyRkzye2XTe5TqcsZfdBw=>zZV)!AJhkLsa>-mBFf63eV8?QipNcw9zYMFP- z3Wo2f4R)E%&MFD`W@pb(&9z!L`An8yg=L3AA0nQVxs|AL>+@u(SY60UwaSdHS)%#fqA?}Y5ZaY z^MPQ69$7zR^Ie7gB}M770^sZg>5=a`{=1~0`!$zUbf4Bc2stChIrZG4TdMkumQ!@- zQDYdC z)a(5^?dt#`B6yFzeSUBX-~dZhY@rvg)6sNyI)eGVR;P`X%7L%1V7U+BV#Du+^?xk^ zY8*zcCW8Xz1{n10g ziT!k5Cy)by-N}5W?>B;s7`bz0+1A$Mfp{mbj6dH#;QrCYvjzW$E)Ks=h5SZ$m`?f? zw8c8CkB^>y2EQsNSaaAyi)br;+EB+X|UK&G-P@$7mHY7&Jc<}355>b}BC678? z4$)8nznTtqzT|zvG`JX?xvy#`01bTW_=Lh{xJa2Spo3jFnz&#l1LnLVsIDyz=#KJA zi6jNP(Z6|+tiuDkahC?DZeP8@x>|Jhl3j}qPACYHJq{4Mxf_ki%V&-^El z_noutu&4}>9NFg!ACc`B$mV1(>jF;3F@h4p;xzCIn3z@Mp&n;OSP1v^zTQn+q==Qm%r;gTvhr4^vXu92ayVL%< z)A(0A#)URG8v2VBK_jHeYd`pai@XY$Xv_W@avwrYpTj*Nevf{2@~$s!hF!hrYU+N9 z)-_1p*X;8{?$0somB{-EgG*SJe6N-5%A~^$8dN8Px3BXODRu@kzM@QfOqFFDSh$XC zb3%EZ=pOPGQ=*-1Qqjl&1pqPqAb+0GYML+8#_hbFV_Zt6CCd+b{Cm232(XjR%Id4N z3&>etQ#;rus*?a@j%SN|l`m~>Zl3@mJ} zr_s260R;Q=ojUn>G**@mcXCkoaGK1gW8`kIt%#3FXm!x^BB2gAZ4I95D?v2Zk;n6R z!nyVwt*LzUNXa1R4Su#@e}DY`c6udcGXf6=`hZI!ATWf#YlUG zeF^_@VB}ND@VM;z=WCy`eJC{QF}JxZWQ{E93+guvNJ6AP{wzZN>)l_(0ee~cuR-dH)%7O>H$TN?JC9J03(`=T+>{O}#Z z+v?Y~&Ltu15@*FedzvmZu6JL>`d!+6MbiNuCVx{tIl$%iLYs>?9F}*#EGCHl>iM_$ zTi!4Bk=%uh2Mdh{#VIBG*AJC?Hkpiqzi{GI-fJzc=0WFQ7f))^gQP&>$F@G594vq`E6666m%J34t=sQ z-LDk;EhOLgb+KmkmCzG(j9DG8;Bo0qtns3k7PGpGP~Z_$FmL!!40i00}ppLw(a-lexhxfs$|y-hPIF zqoXGemF5@3)ycEb>Phc~2Szgv;GOMbdEdg&AV-Vs-Vk+cR`J z^Hb&OUv^~C0w?a?UNvl;FCUi|(S^_{M!+OXBuQvQ;3YySsJkE~6)0#usGpiZx5THI z*rqtc^I_ZuB_^I>=#SO(+iP-trtP!DWOvGOiQW;fA5`(~y_#Dzq+J(Ywrbp}M(|*m zDUZCd89@SV113&2R6B^D3ABa?M4^7*5Kkch=o+(-LO;JGCjWuM9o!I9CIay%0cD0~ zqOgD=WZ&N{cS<>rQKXLEJNl5X!u`}uym;IR?TLLuSdT84jrWTilGeG_fB;fwLhXNFi71Ln zP{N&ln7}Z=P~6r(ztH*GpFaaC8)lgD%R_jRAr_o}jb&ooz&fqHS}s*T6Z2Q?c)O)?%?csdQ&3>?w>+PU5BmlT% zeiuDXojrUpq2f67J56MFiZ=}pmy-z(`)5f@jX`)96-MqT)`_DlAZ;h$s3$+2Ll|Q& zx!8^|P<|A#7>NvFd{}(lTFtgIIF->V%+&mJr?_Gl0`HM5qo-+NzGG=}o^NunXGC?T z{S{3TKPikhN;AoQGH*E#pM~bh){qz{V_GEVM36e}28_wU}ckWQr{*g=(&~AqxLqq1IE^Ed8gsp_n6k)0P9J zQw7zTB*Z7BNtbyI9OLCMu-rS zq*172$puTq@(@dTkh$b^3MDiX0}nik}toVw>$SS#)Vj)gU!kV@(aOI5*0ozWcHraF%@~ z2A1zrpGF+S zUgo|r!afOu?sH=!;PnH)ZM$w zZsCFPVyEOB=6~Z$F#D_K(MWf84ZEgp__|fC-Fpx2Z7^p3JNX6Z(pEiErHzY$&RMQZ zhnB`;YMUJxK?E#30*PSN-g_4>1;kga-g`UHlX<!VB#x4slP{z}&|GhobRfx8MdWELt**DKgX4RLn9mP`t8F;(B4om^tl0ADA z0q1)s2TdgN21H0ieozPjlIY-I;NTdrQlO$SKob6Bjqm(w{o`lUU36`u)%xjT{RdTm z?k7wsnUE;e$0Nn0Kr-mCzspPc-+j>FS_F_P*y*3tT)_nMAt}H~;Q*3}0Wo6<)d~Pf zRXy509{ndhM-17PfT@ND#CwKKAap)_s)Y2{rrF}J|HT4~^CTxh<)i0H9l!t9V2bYjCYDnK_qkO8kNZMh8z1-~fgNdB*K%@Grz_NkJ_s*-UR^X2cLIOu> zl4iJ4z4w?)8|4W;WC=h!1$k`K-b;!kf9M1mgkh#s`TpBZNAO>ZSOKG3^(&o^5ryYI^j!`CWN)SEOqR=%Vw z@WPP~-psO+IQV>pzZ>hsztU(|uS2oriKyD1x*Bm%h(6#|H<|kp2Y%zQB`Mm`_txM* zls?7ofsS?uff6b8fOXrk&PcpIkNvA>J5S=1MT=nPK&diYatTwINt@?r`Pcr*_%sG* zKm17JX?P?HejcCZq~SN17Iv!h*YDCH(IKOKeXx>mPC8P~pIC_Fv%XD+4kV}i3>x;E z(TpM@fH_@Y9$Za=fX(I($^@f!La`Ous}kU-tuY7lrjt3OVPkZQm=(xIHBh?#q7jw? zreRm#{1edv2d9Srb1RNuv)A|XsBc%U4hru`4w0FebS94cDvxH05-_qm-(lYfKH+jy z8H-|#!gvSq_AmUJ&M1C#=qIwUYkmWGHS_UzG|uvbhh`j=#%lE%n61Z zQ*+rCIzjeoi{SmPUFq45XvT22f%m%-RxsIBTV=6CDbp{SX{2-}?ug?cPHr|V_6AM3 zQ^Yj&cBh@$VCG2^+Wg#SgoaY7R}F#CQY_%>30jS#X2EJpf|hcUsC{96NerRnX)L(1|8)?AsaXJI*+!QE5it z9c(m#l=m#`LEaAUnIP)Hp&aEOr|GQ9mVd%4_|WFQ%Lch?AjYywmq$xn&OH>f-nBWb zRj?Q|P#U$lrAFI+u3H`ijkGRt8gzV*#b(MBXqhE(5sihuS!c!pekRMT6>t4!($D>O zG0~rsK!VhW*Jc1{uxw8Wi)f{9M%TwC$<5u-?juS&)1R|?@>^U~?RAak7$0ep*5fyL zOO;=K!Om6ooxJFo@yD)?TyR7~GCQ!P{qaMLFjpD8tC_O>yps_c7|pxLr~(5p;Vom8 zml2pSDl^yHY=svx-<{32=FGip%7wbc_i#WXt%fFDuB~ce*rzHBDpD257Umv4EC<>_ z^Bcm4Z)g7+`4ti*)t?6iI!(EC6B@aZiCA>E>FEMB?Npl3r&9YkFTlUl#j_F`P1z-$W zy0b?Bw1w{mmjA?#SD@sxrGxbxtZ_ZujnD%a*M> znw_J<%IhP_6&te{Tq1*Tv%yDPtsK_O<}7Eo8fEdTu0{2R@X|9YBU@9068~MTrATnz zN|LS0o!WM~ktb)w#D$+!>}(&duWfFMbVmA{)ZWBWdK_76dd%x@1LEfIZ?A7JFN<_Q zbBg+NJA#5hyD3cd-#yP%U3TuSm;-Z!A;Yj2<1F3ink!Cdly8(g=~U@hsDK+8o;$ml zBc*V(JlxsZ%5EQ)gXTsF^_z!!X<156UEw(Ua=-R>Q@x(HjKt~J+GPqDXgXC5I3Pt| zU#^VwLqWqFx43?3raqz6df;87MxhaA?sV4Jq+(q#QX)Gby}j7m-oAzn)TjnUaq>q28Bcl{f|t@K_T+75J;_Ln@CNZne8Z&`1X)RRv%GBIy(LI`B20U$e$&x?Xj zSb=ypD@Vyu`%K+sahI>NgE&HSt}$_H{8*!c!XDNv>x#6*XXp0yE0(s#xo8X+uy$Xk z8eeCv_*~ZNk^1G&$FJLf{BI{3yewy(^-+~{O`5c z5ymSyIheRu>DFsLR@mWz(k9zzPx8CB~|pD0hrO;D895nN>xu%flLw%&XS0sfTEB@ha{r`hYj7J`ZItXyzo1%nx+b8_#ms}gKDnJ zH>z+m;qT+^Lmp1oP-+tOu7a&iSc72%%AqJaXb`=i5KG-XC7`-t>>msJ711OeFINsp z0&=nrBibooXz2s%`g7xKi|>35i$b9EoP^dtSx2Pz9&A46KU-$b zg}aO>mfuo^oyJ?-ky+s2H>7ykb6wx?#`BEH_V-A%#HyZtI6ZoQb+Jx&O8RMp?!-wn za81Sm^_Sb*U1lN~;WZ#g>a}BjzB26Bj^8nDGJk#yu9kQtv?HcHh+Jl^CF$5_yWrW= zc3Qabpok&7Mv*Z0vUB@6Qh~~;7z&(3f8meTd&@*?qY2n%_YF1}(#Z$i@s}nH++}s$RHILyq%8xlSE7pb5xj`=>f|_E# z4uJdSz=ETqLJH%=f*wwp=`jO|L3l(WQxFwBmWq@))45;4mp%u31ZTtf{t>e=CtmSn zGgpBDPp>z^DcJQw>*Mb(j;xx%anSw*(i|%p4q^0mDYwnYX94D3;yopFMgJv*WC{eJ ziZ!m)C8{+MuA^bH1h>Xs42XJ!C+-OR;1PjBGSrW0HvdATHq-Uid-wz~yi!()fZeSJ z{lM`hSEju46G>uFty8Aj5Y!g^1~iUOTxmxZphde=3rD-HVvlHJg&33pI?lL$iVzMz}x%qJ}SXNL&G{Y_T`IT$T(c0RV=GV3;{{ zm3JzT6eUx?i{=(Z3n5niOfmFh?T>JOSLHaGUhuLpK~ZFjyrBAGw}LxcCk)3u!Q?+f z=oIK~T_Bc`vGVsSx3_}AX^OG`jojw{M|>Rl0S=J9I*CaKP7yo(YW9tiq>-X7-vL zvc?Y4Q!2uYWpSBNWoeWYl;@O~(QbZ!8=YyoIO=93b{u|;Nn43rt9!h3a@v8Bk*;Z9 zQ8w)Aqg=~bnKcuY!Om`YOQPeYo;ao^2ZQ+- zEL#^j>SqsufX|xj+el{Uj`R0hq2g|V8-yJk?188Bv`?97GNfoIg7Ymhl`|Bdy%w#; zxN;OI@Ip{XhHz_S@i6((GWZ>XjlwIW_L_OTkCbjneP?nY=w}ZOl#{r}Kn-K-k2SMH z2l|fJz|`qPv-R}BmA>(mKp@M7n9Q1=(;1Z(1CA8+oA#$XLt6!`n1ot2zekGk`Q#>t zDBjUc(eXYgUAnUR7Vg_?lFk@yEJuP?vO>oVpU$L;%tayifT=`I#}q|!GfL?HBW5}s z?anMxItcTc1HrD(TQKya^@eL4k_E>bijB^Qm}(<>z{>HVm^|=dKt*0;I(k1RwBP=Bz12S40+GoV z(*poNEFX1$0AnjTtN4ZStRr>n-9}W!i1N4vnZ_Y?SYR39 z$({vxgtl5IbSQ0|$SY)Wa!tliVy?uAI%$KUs+c$1Fv@sX6SXXvR=ha3E}gd+ZBD#~ zUrXS7pAm#5_3eTbM}_nOJ>E(DAwg!Q6#pd3(rqS^NnXObZ}>M_S&m%Dc)@1D(mJ_t z28&gRDR&TxCwcS47)$Xf5BO@NMk>^MqkOPo` zG`!qsSH&M;LjLl=!SXjGp!*)n&1$$RhG?cJonXebX}*VD8Wymz5&^0R`QQMXUm}2D z>$6clH@mP8_-q!jfi@xIXra3&OO+bL2TNICM*6xCPe@eNwYrz+8m-;Q&GXdIZ%dda zMw3n^+izFJW7hWE9e>5$K$iS$s z^OaEU%sk3t;OuX}w?IVoaxPn_Y6mt|Z`3vS`TzNT%Q^-!m#5|OY|w5iQnyNUIvEWm zYuJEXqZ6lF$^ww5FMXC%Cz{*Q>US_PC+!^KuTU+TUth&}Tm2&r^+&-nN8SCJhg)X8 z(?#=mb}3cB;p~&N^axI9PyC&5seQ9MWgvV zL!E+3;H?>;RKBEkt2iqTRQsajCHwUq*b#eHNfWOUWIwOQYFeK2ujGb`?} z|41MF!EvMmP3HmgC#w$LC5HR<-_+9mgyA-t8uRvtALEl^3GTUXrG!!Jg@J{4-JJyu z_K&+-Yc)!A9o_HvO)gZf`z0QwUg;JFMm}ITfs!uizl>>Phw=&(l0|4)SXh*?X2K4} zPUp)tEG+2%DbD@%VTHzZ564Kti9wWtHSVd|6V->PjF(X_7EE3;xj~7>5xI0y4f}KQ z)0B-&z&JeQwsbs0JoN7?p7>wEyh)MgU)v0ImJXa25;yzpX^M=ERczZUXQ1}xyRGRS zqF){N?uzbV@|pX7T!;ky1ikv<4(AjJQ0y8l=cAyqEl90=as~PV(GOtOEADe2ABs-Q2!N9E6=|s#m!sp_BQr* zu9fKHUOc=ich|_kk@nyiBp9(2{^kH1(Mte@{{N+mme*4L+2Q(R@TWJ+823{x{r)WC z7_(V|(JchyKVqf;R0XVlhMnj~A;+%Croa7pm?fDe5tFCFju$sij%8~2r z_3c2U!TKk&o1{;)yYTtPc9eXssIjCAV(Pn{Azs(?`N0>=M11@lCcuE+DA)*d(jK!P z%fK%jOq;F}+zf`R$77#grt2~5SYy){S0!^Y1@I7p-T14{Ut*P2uM#tcG0T=xl@G;( z2C9Y^^1YN#q$|0*+Em^UGJ^}WcfjTGNAvMS>#0iFR|*aA0vmp0O3fw=Mf3^_xQ^q+!^i9FQ^n|E^qY-Ycz@zrOs#lZNB#tZ16@cJ~B;BFnv81 zuuJZF^D_$|2wiZvn_^)Ua?AW>{o}ucTL=7A<4h6&WUQ37gu-zm#H6rf8{@`%2ZLVe?% zh2WF$CU#paZFbMb-5&4W5eAPyVq&pru~NFh@)0M7BmLc>mnrQ_r~>g;E{c?+*N$M~ z?!^!kGSGu;)548~4Gx?(3uog5Y98x)l_P+}(7^ZnHzg{5>!Jm_s9S@}{K1_rPlwNhCCK!7i3y835DCo|{O zu*JiF7eW~C;dA%sl2h>IbY>)*)S~2ga+E=PAZIk`Pi=&P+UT=aDkg1yFZ*Jl>(I|g z`|Wznn`LtuW1r9On($s|IUm+)HN-h4v@?DL&voUWP+HK(CBBdUHysw!ONLdjr}!To z(n&s|Iy;I_qxr1IZ*F@K#mkB%!bzu*9D%l#IHOqI)9I_IC?eRV z6U%cxfex^dE&eKBdrFB&Syt6AWWR8d3Nqa0^2#X>7}`AtmUHz_x>D+BZYyUR+ltoK*0&tf+eW91fjd(qArqom2AAxr!aH=vivfP32UT9Vjo9E3b(Wdal%g z(dl58keoed;1*kx=XzNm{Fqr?ZOPCP;5n#^g#&dU=fmNPf|Kl0T9-e_(_6ObE*t}` z5Yccv3)(J2s__Iot|w_8oxf^E7cM^RYzjZmjg{lu-xKTW3A51yRe%E@Ufj>I+!Qz{ zV9=r|GBWXBbu}@}j|M=kc^>Hzd*iZc5A>h2U~`S=9m%S4qxWXL8%EiXZGKD9@!RYE zHu1m7+r7vW3C`|QMaGfHqMPCRqgY)r2CF^o1V~YtfsOt8Avg-w+lY8x0)GJSsooHy z9m%l@w3Bn&(nf{UPV(1*s%zUPmA$m?xNzyk=qfyQLWvA6bJY93BE4HCkyX9pdp_1j zm3syFQswS_80T}*^XCQyER^1*b>78q=7XIaKiKIcJGn@L5wj+@VA4E&FVmdele!^X zqsa+E7^2k`f7+VPrK?hk!Q?E?QZtrAgr9uFj5pL6m+H)Pr2p`}w>0;V-rWvi#SO+y4+gg}BoubflV45!Cdb{K7USfj|I2EJ59qN3O;7H4 zM+q%s1e2+6ZJGC+mlf@%3SYFX)TcUe)IP8>`Np)m0Sk)yJ>gYBT6&& zr!w_AcVRa1wa=F*vn=%P9Cpgl}jWizf-W?u6 zC@b72!j8=^RX=T)XUc~iA=Kxo6$R7NKV@i8=Scu0(YJ@YqNOU8>(v+b>LD-i2NxOq zZd3iTZs*5yv+ZlluU4YVywCuUe5d*G?YbHJi+2mP+r^wiu?wgQ+WqYU3l9K8g%pi~ z1Oq6tht6fuf=OUw^8N_|PQ3fT97w=>Eot?ULS#H*(b4qrJK{TH0HJ2Cjmwe`sM=Mc2%lw$hSe zaUj<+OVuD>*z;fm0%(OQ1-&Rk<+SEJ*lpeUOO-Ir8sIEJVzyMBgq@XzdX?$a?vFD(MAhc6+f*>o&kizi zi@wHm`hGn5(F+%8%9!Tpwk87Y9hoVU%d!f&MqZ#Rm7D>EjtLiR(*N#(u^=#)Wn6tF z$7{^{^1ea=KwaWgdko7QE#jgd+!*4fC`LsHFaN}-zVY|(_1s*h`fPc_#g!WaU0p?E z{}KnK_=#2N{{GE-rMR_{sxqjjWfkS^B>5^A9)&2MAeaKp2Neub7*aHbf`&vgh6Gg- zP4N3G|8KsI6pbaEXV;toxU^@|G6c#ocxtW~<~hDN(1M1WjMD@kWlI{e ze$`u^%8mEcXPmW;4kVM!r;L<5SePW-FdUS#xk(j^ojJ-r;!Yq}cB>C8<=DhjI^W&} zn@}w6JY)1+W`Iv>uVt1DEUO8}!aZ0JjK?ZuWtL1wEV05SStTe!8WL;Y)6)~-0k5c$ z7y-m_Ff6r?HgG%}`VC6o7U8s;nfa6IG9L=pK%&RtMu`5&`dtN9Wr>$-DP64o*^7!I zRMfZ^g`sHOWoc89kRdHP(9-|llNiGr!+0{E9U1xYe!ywhGZ`@It}C?qd%Nw=t*uyj8PiQZ-L@m~l4GwmktaDmIu>mJheaC7_A6P@9XwNCf36CQlWY8$7#j*EHK72@^*qDjrC&z+V-sBJxAvt4 zhSC`gJ^#?_*`J1AcvTHU`Htm5p$q}uM@5RIn&J#POA8C-j6U^1#SQ;Xr{J;d@f0T6n2oO}t_{ z>(8S{rU>Yu2e&J;baU=w$-z-K2_%S@XjQ3bkt6iabn^Q4P6kseyVTR7eVy1hn?KOP zBSgW$C^2D0$g&sv1_vb!$v8MTSYsr@Ma94ks?q*ACv#cK3>^189^vYHz+cL;g-)QB^m6HTfnQcsKGvxtZ{zDRgktmKPCZ90DE^QB2aFK&p>sU=sd4pG2bM$B)(Q5qD#G;-EZcdil(_H zXKE;4-rquTt=VT7N$nuZ%}$;+EIByhn?xa}sh~)ul810xF@uL;|j z+uv;{Az`>6;)~&gYY7L{s8PALoQe@N%kC;bfC9Ejs*b)68ldjcJzi&f+O|!UydTB8&!&&~LVhK0F2xPqmcB^)rx-Qt_evAevS33wNHtV6{*us4 zm1;;GhXzLgOdft{6~r=m{%n2tZjN&>CkU@?j^O4lE~Dv}a%*sF*+4~CrB>J;jxfB4 z30ZLI3{z3vF5)@RiPlJgrD0}nF;#SNzWHP)AUGZdW8yD#a&nj;nop>Lf{^UYUTLoN z|1tKKL2-0z*mdLX1Shz=1()FN!QI{6U4jLI26uONcMBGRySqDl^E~IgTJMkVt|^K? z^fWcyz4xrW_H~Ph6#zoi6!|I0R~lvf7I$k^4dZ0D{LT?|7f;$L6eYM_$8|$NVofdu*w&l1x+KZK?H2ii8rFfJB`RHeOsUy_D zb8qfljBuZg%yLx2wbo+q`Fy8338|&$=$)PbK52)`soV0Yd@LZlPKO7CHJ7PqMcoiC zxMOkXvcmyFhwg{xwa{37d`j|hYIp3Wlr}AVO_{;_?tN#SBTjksF|eTgp={0C$-)yq z5+Qk?dCPA*p_SG*BA{Uxj~d=GY)d0{k1N(xw+-=gC+isqs;wQ}jj3p8l|%yLv+dNQq0PR0#QpM!d+;x;cXH4DCy--tsXY>%PG z5ha}vF&qnBi_C5e7CK94HCYWd_xnR|UWlT=Yqr}C#x+_k*VBV8aTdo>H8m`BjM}u; z(nt*(6Y`>OWtr*fu4l^}l$2o)AcPbrMEOTu`Sn#gQ8HQMuX7^*uy&YW%|Mw_g-mI-yFLG>(9P zkcjz}G@2PY*-T2Tw|tkJjg5_rEWfW$7=+V6FmU*TA96o7#+TqD9x|;nF0S)jfsIOm zh-yv00Pf!thmcATK+XleKTz%9mGn2<9~-d7e?#0fAwG$Q--H7`1vU0uREQWU(y z{(pLum4U<&Y7F@NvGURK<3i*{+*n{{niXdAv}9;fG@qW9iYUdAk&)3O$w67>Ag=U} zp$YOi?PD31mX?qSf79@hvL4ws1&H>54W*hc42lnFRMsR!o-EweG&f^M4X&@R|KnsX z2w3g+xm&@caT--A?X-H<)}m{ zi&#}K;teh{Akt`qU#eNp;>h-sSOjuW4+b)ctSo8c(|$!jN*?=PKHp`r?$lw+ykWpM z($>&=+HllQHm@Jf&!lmp2`}nnL)57I69tJfRCoV5*C~y>al z`{CeJRHQk^Y2&$qyruvH^k|NE;JIb_;YT*SaxgfBx(>gI5uIHHXY#^i`)dw;meVJS zQBn1fJ01*B0_6i8lCAOT%hEhXXyj4X#;4q5U|hZC5AI|`poK`1J2|@Hag;0uWrO>t z^N^dER1-bBp3l{bjAM1#RoNvcCyR@VM+o%{gYNm+%Uy67z!0W|=*0-3ztTiZ8~{we z9J40p9J6oO=+h6pebJp9wR6os$=ZPNW<6p8BeonK$t`jz zvbcl29w)){LS@q#B=5f;aj-^e|=&K0CYp!;`~)= zua-QQv9Z~x5+;e`L^)*NT0JkH?y}d}=`Vl5c=KxV3(vN}uNV(!bSu$lf4(|n9a`xf zcVu{>F*}ndTwm9?`{MMFcmp?o%=?g%fSD_cXwwV|U$10jO*V`!%8Q3nbMp+ZW< zV^W|hVp8N#Cx8RVrrfbRz90-uZ-D2Pxv3^krnBC+qk5)oX@&XVOV{{2?lVr`-z1w7 zS2rrU+kDV{m*88GHu4+l4od-6(R%7XdL<(DfAvZecBm=7IpmG_@J4WRI~8mL5Ht0((>s%qbY=1XZa@CF+#q(BUkv3{P$DWgRF z-G#dT0Sp1S_2#3|$W(r;GzsNkX>M%9(p*vHoHJ3xd}erT^;PVCYxVMUtXKK0<=brS zQS7C*zt6$HY~$Iu)}II{&RghK=l%!jfy_&gGzn_AqqYH38m&*xjjOBgA8$7GX{B62 zP|}?<4jPFE;%M`1YikQ+FIwemPly!!?weXy72@Gp!u=U=1pedpTt1wE_nko6?Ezzi z7FBuW`c7evk%1@kS)|>|j5RG^J=|C6C7~~DRhpp_3Uc;)BgS6zFs?$sGXfRLy?eu# zW=98w&l6hp&g~u&t?AJXE86#8E2ir{0(8R))?RFeIgJL z7zt-~_*GxsXm6-royH856e@!zQ7%`bR_|=*_b*^Ui_Zi*;afJvji)hdsnBn{Q?oHg- zWqYC}=TsH_%5my*oqkG+1+WLo%r0Wf6Y)8M2;=s^R=)(3*xYjkU@lfUUAd#Lez0#? z15Ud}`r|3bjHR5r)Pa!8WTB9QcW7FUddRdXeE~afM7o3OaHjbi(we4^$( z12z9A8J*vrKK((vR3z!$_HD^dXdI8CKt-p2w9)QNer#1i(*4Zu_z`6t0HDyQkVP#V zwtGe|SB2$<8E&k)7L9bQ#E0I3C!wEt)@N2qG=e)?AR4uhaeYqhytHJCqoD-ewfAsR zKp=*H@#BZjITTVr+N-a3tV9vbWAD#l!Fx7P1P^H2*H?=_CF7WzpzlCTu z@UF;|@}XNc(fX@fr^h`NUlYp_W}2ze;(^pKj+K^1I=GJ_leOo8-(O9-#)Kkj3E^?Y z;J*F1`n*^NC9fNpN0$ycd4d)kBn3!L5A!xT&xu6vm`U0MgXBf;I0w7RnJD~6h#`O7 zZNOmIa{`>tGX&-tPk_}5ghx9CC^Puu$67wL5HCxj-WjCnl@}o4wUJ+@si@OsFDn5Ny72+Kea^T0bFb z7`nROjY8U}9u+Y~po0Um?o-4FDOi+_X=2}SyvA{voHRdqHfRo<7P)3l%0;h&FH-EY zWMh#z;XN70RPqx21|sAAQDf8b|0@62s|7tkqlWwuD{$Q?w%Ce8X_v(@{%o}4RJmD7 zZVi)^o~u@p%f84LAiHx&>`J-&$&KsHPA}s0)GEdOINqn>4mBko$k5i{BscPTpmgKl zE4JZEb#`6-$w}*5yRhoB{&9C$36DW}o>ISlhIcxOlEwGFxVZyb&7j%*zIn-!VHAp8 znl?a<2*fG3Ha9zf_6_Jxs8Uq_c0#tPXmr+K(RITmWkOTO&xH=aeW#8qH<>a6`5Pcu zV*&<%_91hkI6biTE@EkMh$K(J0_gf%{fn3S;UzhNva+o1F+*h5$!UB&qgw5)7wHzO zd6{m_y29|fum!;CA_l+w<~)j_F14i_j{f)k=zNFYBlgb>VwFK{@6K&;_NacNnt7Aj z3^r(&Y{;p}>1mD^6I18CwjDm#c$>22$Xob}h=20gq=Ax|!q(z^qbsDRV}zZ~ikSB- zBzjhSKZ~z-Q{&Lt#}->UEY5?ntpQva=|083eT6QNjdB6W^V&~&-o=oygDGqt36**j+JK1UtRg&?Ka;BWl7>Q`}B;l>;DuHJ# zoUS{QJb^?w@K0pGAn#PS)7_CI0eWPe>;|~k(x#B0AM;AQThgkfR$_-tmvas$gGsyB zT^r&OCW^x64@GM6avGLm%3ImJ zPe5=IEnQj$|3z(Yz?>M84hTK*H`@K*%Ah|>%2Zz!!!9aQu#)%xTN#x072fCA^0OKB zQH4C<=F1AA)Q#BmL<9!pJ}F9{V)X$%zKixJtr&?nh8UEt$!DpjOT|=x3zQ;FB55g( zkcHvNJcGkTD)-5@ez(11JhfJ@+tH%=CZJB`Dt$90|CtG7j1+vsJ(PtaXO)#Tm}57v z{RwK(u&&FNvt46q>B^~pcVyf=$Id%{{7QOoyz&tvZjcDyC}|}d6zO^A<@c6s$Fp!A z$0>$Gp6$f?%ew(KURI;U=qiwN>fwz5?C^g;$l|6l)ak#SdDA_XC_jf@ELX+7zL{76 zI?|8dF-rr|>srd1r2I;da>^2&zNO(>Q{q<4ih4qrS^T(_pX5C+c{mj4EcTE8-S^Ey za32ZL0S2g`=c=U6GI$)o?39Zo)fIic9o+dKdrac`tPgJ|?%>Ls8_!SU?_cMR!fHb4 zNsG9`PL`*z%{Kkl-I9wvv9=sSG8%;Fv(@UD>K%PBIjtGY?OSZyBXJYD11tDmX_FB& zN0-$bO4|hjNu!k<+}WMzy_q6^(p}zA+nupM1Has$A# zupt8MH}}_iW7m8s2UYh4>F>y9ThlHCN|q{a5w*H^;+KBxMG1c4`o*c&Rj`vWCGTBD zL7f$dvBXv3NJjs%?d+c& zB92$BLyuAzjQ0e8g@Veg(&5neL^EaS-UtGd$Llxt3V@WKX_>`2EI>zlHxh}9fS6LK zR$_z9WSB@u3MC_NRu+zPknFc$@^d`$#;-8G`-2 z;BRo2>Cw6V3!JWNBLT+8$(asXh@?K?T>sh8Ta_zyJ=vGZH~S;~oi$f#&0I@w%8U&7qKt#H;Rrmn5%2 z3L$>Pbh%1XyZysPI3Ae+!sZQp)qjDp@w_YfRu^gKuLODqcemx)la^G%`hWHor}HZD z*t{i$)K4xytRy>lMnefpQh(A`HR&CnQ)>jZT0Ocy2j!RC#9EVj;yYB<2t@|0g_j=2OS-Cn|@kNaM8WWV!=qkGyMgEMmfGuwK=((o2sKB0AzF`nhBh~NY zg0@R3@S23UyxwwsDpC6}WN#+hs)za;GLfZItHmnyWZ*2n)V94%?i*eA=404QR(4f- zDi$J@gqnOM3q@&vWk;W>DUd=STTJM!#k?x=dU|YTyrFAb*3PYQ70 z6+7JRyV2IXfFcG2+)AwZ<*GahT%&>mH)`u_nVHk| z!;x*JfId^3s9F$gSB+gIrc;_upcr%lUbV8?#21;3IfXiVPh6+#>`Z1mqWA=7#r81& zua$NyRfH=A{)BvyG9*$7(+3EU-~!px2RBj)yH_4Mj48yhcjk`law?c&O7rh;FH5Zy z$^KxZj*~^GXa+g@`Y+ToAE$qQGC>Nw`mgA8p*{+`WxNc_QtkT3e|MD14Z(c(%zIx4 zyP~4g*XB&gLfAj?aKl-8mHJEFuf+?wXBnrKax44L^eW+CuF4-r9M4loU8xK-SpWUQr{PW9zhCSNL1U39EQNo2exCiopE3D- z2#oJb0?~UHfp3(tq5rMBWQ{I()xk@?_)X^fWI>0~;`EV}*6- z6mp>dRBBK9cp6na<<&Cp-KF8MD8X!KNWfcN7!(utlkOP%n(;}~NPOl)KdmBVnzfK4 z$@85xxJokb@@$-HT194wGdvXZUhl!XR0gvWt(O(a=LPKeoG;0=mFGPLYX(DMAC_;{ z^DI333=n`UO*D?gnn!;P#r21XpGQgM^GSleh<{44t~e|=6v(DP1T?O^LgLQ{$#Da& z-;X#5x+E3y#t)n_gYI_~x!(Jf3nIBLO&v!^(~y&}_rr9iiXD{tq*M`BdWY$FXA%l- zk{0{7;dhuOL|`jh6hAYF{&JZexcJ>3g0UQGplNbQfUI9YHG^^G*^iYqQuVo@wQAm& ziM}+~#|UmpriHIhQb9eBYWwB~*X8bHdMA0%FO(<<47ymZzVC||6SwYbengk%VgJJg z&`!%><;5d$VarDh=<2fM+HUSlQ}R0v;^W9LX6Df()4GY8`L}LKmWzX zc1@mt1X6L>#d_IQ9&sC^RA~iu`&jyxoHUb$q&d+w<#Du1H<+oLi*Md-wKU~e1`3Xf zb{-mtKt5VG8eE85AlOU$tXo}xrH+yLly2UvFH_4Ci79WX%=Qq5;O{lsWkz4oJaC=8 zBLxWo+g2?%P3kFbxY5%6|qBdc7(R=eC&Nn?d?T;t52>wh=aAEUVd>#d>N8$gZQqtY|KxEgJNS%{wscC zaFfBPC(@b46Xm%bPzAE~yh2|-9F|{R-ErZHyHfO}cI`TDHZ^@-qa*~qqgLLXF2q1* z2Rbv*<~!u8N^i(?J073v*&V6x0+AKXZQ570q-VFw`d@LK1!y(jFvMtBz8Q;4sHJM@ zczSRaj;O&5RNDSsZW9~W0~gvJoRle8yU-)B9}}C^d0LMMUwXPQ?&rM7Slx0`)}>O0 zUmH43yP}9$ZWYgn`(~YW4eIG?guV_2Y<75`7`Hb&JzFfG@j5(pSgE_XLDfn~e64_{OEd8nBMfSptc2sOkjRR<(nS;mEf~SpdHnnFk!^Jda3Nz3ff?D$Xw4@e}soHzJpHLBkD zO2x{|$+?xD`sKGek(%f;{l_||MUSJLJs0O)yyHP|ep?G3KF;43m%ndj*-@Wigv{2~ z8%sgSGwuIY`cFn|Ag8f7J-^fVr-c+r zo3*#tHNj*8o>w=mg>4jt3Vv64hCRnWb2YCC7hzD#zi?8uxKXrlYV9i&$T;0OK@rOe z>c)gB*b1QE+}$~;*2=)Zj@InmuHzZ17uJEP7u)sLoV%_t*uBn{rj+;n$Mv+8gO6YO z(`lX&?_5z1PORl8U0!OY4PTEyn zfwaU$7p{6$hl&B9Y2(2KWOOBoLNJHg?-zC6IJ41>zxgf@JBOeS8{8(DUjRAmJIjAJ z?>H|c7(WwWrje_B+ofaJZ}CF68ySgLV|WS}1N9tDN8V}DD1;1M&j+g^bv|h6<*n*4 z!&m-iFnJmq^^R0f@o*qAYG5#T$3y~VRaY%27ktpa3-%EnYOh}k!sox77c1D3_@Qhq z&kKzHfQIqM^bUD(BI10Zr*D%qq!?K3Q{iM`pmeTcBvv~&Fj7tnA_pCV!-nKw(o!$Q z=kjwtI>%QP$$1*!jXB$aV><%PW?B6VO_QeyPWetNXgy=+&0_F~E09)svpw$e$I|UP zqD`TCrM~j0;S6Qb0diX!_V+cJhcyL>Qz~@f7VIx<8y~W1nt3YN4r;h&-whYI_lNVN z(Q}vyFCvHA`l>56zA19tN^BPe7uyl4)otDeqX;GXPn#HRkX|gO4(6ulcTX}Df(a4M z$Yr3R`CS)hGwQTl&K=E}lkJp2DGqTzG#_vx+bDZRu!Wi`@)yNGtHysH+T+bLi4_U6 zk65kqpGAo@Z4s|vkt3YtzR7?rxcKp0q0PuF71eoIdBk}2_bUh zla^#iq@!g6EfcGNg7x2+-Ol)Q9v-3|*Z`q;Tu#ojwFtKuu)|?NTbnOC={@fd4*L%% zvtt7FLIP5pn$L6m*Yd$DT8s3PtG6|;=n2{fXnC>xj9ElShP!{;TrD+pyl~Y_2wK*; z_h&*|eB+;Ru!{u5i<}AXoUl9|vz%5_GtwMwgQe8tpxx`nR87TTgb&0F+2arwVzdW485h>uyFL*v_ zrA-zbszvh<<7_`m^rAIgCYt$F3FBsTWv*fvi#TH0fA(Sf@b zdb|KkjF+H&rf0|A5zuEnDshIldj;b{EvvHJjMp*scs`VkQ7fN$(h?y9f^-=fU&D}X zMd6mRYie7Xu6@Fhl+3mDHGbDgi9p21&i%EOf+E^DiFtH0Hq*S|zEp(!f$17y+&(^@ z5%p4@U8!g*)If_zVoO+A~fa|?TqU9hpHv%2-Vor{d6mN~?@erDge zr&Z^5d^HN=H8-|YSuvhp+R@gyVs@GKwBh3BfY=a<+i6Jiup;zf(D|9 za?e*It!|%H^eg%o{7UeE*1!_h?XtwcF}>PG3h;nt#}>Kb>i|ar7SKnQ$65hNT*^Zf z=^r%rc}H#M8x;4ZE%N$)4-C0!kEnK{4t#JX+z-I&ZQxV9#w0!ounDi^&xx5nQu4XC zX)@&9k^pxY_IxLYMgId3FASgzMDUiqhL5r-(e~RXSu*?m;PHFn^|Zpf`|}~%YH#Pa zKi!R$~Ubmsh**Q&- zm96Q}Q}sn67s`8xXd^WW_=DZB0OkeFh*VVfn{N*%#AG_3-qu4cHIwL^Ueug=c<0oG zgYD>oWjeMDs@KP@nTFr!+rl_H9(wnCqN^Yese8o%6+tlLJN?>Jg95va#!dmn(x|4u zjR}N4AU61 z+k&QK{|;9tX4$&OS>o;q5t?h%4yPJtT~5gpeSa8yKCu@MR5P*O2+ZqjDT4n1pub!a zcjdiMhmIp=sXnaa_zjGKaiHaIq^R2R4~{7uavnLs?Jddn1470YGcgz)A>aWaM|@QT zypuV=X5fzcqx7}aq@lBD%;M?CRHZ%>01GIN?E2KA2C%a?p9r|UK8CL7nfYAKYN4{W ztpxaqK=Uq_Dt|04OzkDH_Z+FnOYRNbvd-935#nZgl4ogJCuKSTpPo% z#}~M)g;HGaZt1;RX*_x1P)^)z4PU&&XT4FnCMRl)%8M71)~pOl9-9T43$8%7BySyB zz1&=@e;;&*SG{=W&!c>H8;)&D#q1_vIO`o&h@FUagxi3Jn$6CRwO=eC>O6_M zHv|PxBrU`V{gd`(c0!5jEb}=X_&u{~I* zgh>8;Pi{6r#u#Qa_a~zz`>J~S=;6rY#%wH)g_z{{BFwD17_`A}{AJ>`GTQWxT8Y$` z+!uix>QRo5&6wX-Qv#7@nla;%?s4&;K^ZP3Rt5OI8Y=>B^Bg)l+V9EA(D}U`h3XxC z-93pmai_wi!!4fn3B%3A8IpMnds0KR7;|&m-Q$60xfEem%wPi%Oep_;L<>f<)fwo? z0b^%>Tu$D?oLxQD5$j?vF7a~zTkz0wRpbTafP1*v_(T!KQFZw;CG88mo=s#uh!OJB z0|P{QqZi`z6E1xTi1n3L|_A6;`>Nl`U4>Km{DRgbG zjUShj?mlIFy=g3e#dd4tbs|%uWb%ADoxR-e2VDS-jTk<{UDmW8j8`EeHpi9brJ>4} zUVRH3b5jqv3&IZ=()gggr;O)*MrUfn@jrEBJ!GY3*KKhTiepYz@-H^O1_lNPH?gyC zxbIN_0j!)`V#2fIA^%iLDYTr=O;ya2|6ZupQ|Zs@TfFVUD1mRBXxj&CAKaZCVRTH@ zHcS4dpcH;%SNL^u3aU=ObQIu%=u`|=SAL=En`C$Hu!9KN;r(3$2LM>dx#dKEO}5sV zXf={~$U3~vntS+8Bc?pxJ3}?seN{`BeP1rnxIgRncV`d5KSsts<=*7xyQUh0wR~Qu zjJMf*M?NkQ*oZ>3wFU2^B8#^)9s)1uJ78l_(xnP!R%hWDXf-Y`C{~B$q@Cks_PX4eeW!YDoK2GP{(aDy4j4cL* z|HLp?!3h&mIu1=1+W4sv{*|Bi8yJ9C)0b}06IOtR22OwF&%9bCly_?3c|lEWb^R)I zIxkgjmslKj0uwR_8@g89$pW?8A^UsO8)*?rp}$f@@stZ@Ypy>YX8lg^{*x?IYG8rR z9sCiQ4P|O$ZpRxfrN)YPWYzq~&GvQSpMK5+UTuVJqLb~@LI!*WhLnO8PLdZKKqKK; zP3Cp--a4|o@t&}T^Xk|`Bm|WP3(snfm}0d>$sd4yaQ)-2wFzaEtMt?sXITFp9-Q)? zbG7D+L~>@H_5HWzpM^zFOaOqK5cIH*yca9MLm{mzx}HW)u0Hmz3ziVM3ysJ3)8m}K z!-zRN=ENbknPn!@g=~#P@uvuG;`~$YGXBcx$7Vcs7ZgC56MeMM_pM)t%8p3Rc4fh~ zOzR11m^1qR)n#pKxJJ0cjChgNj^2&=Vf(u7zp#uF;{kqXvb~vx%j@ zIz9=LJaNXT)+#g7f#9Gb{jPtN-4!-vJCvg`Yx9z2X3iitLgkvQ{JTJB6sVX`||yJ3OjZ}hqN!#xn) z$XhVC6TOUeT0Yfo>g8XoZbmIB8yj{~V1X*R`%_k+?TuQ~vM-G=^^-&(^P5co>*hTlAKCqY1Nf&WIzY|=Rj0tq#A506!+?Q z?kY!3>gO$@Z(@up{}=D`d9EKr@#q(qCM2o)q6Ia!zKyk0(va}6ZAmCQn*IaVkB2l; zvY=n=@a1>AG`%4A9GIm6@8e_mygnCnK8M0fA_x#JaDUc^1w7$WByf((phz7JM!N1w zu$qG7tcRS&J)d6rLw_>8J8)P~d9d8H-LE?~+zh_t&ClX*~nD4rJ z2xvi(G6pmeGkkz0c30hJJ9Rv*si!?XX`+B~&jqhk;c`Zbe#xSMFdPwxWPqx6HZCVI zwQPStk(T?1K=-OQ8j-%AwgjE7=d$TfO0emn3%Mz;4@SDX#Yr=j<2gc<~hO+ROKKmaCnWy?`6^k3FJ zFX;u|2lKN#2W|aZA4mlR7|+k!pheGY5*nlL>u!$e938K!w}~n)Ic`$*?b>417Ggs? z@LB&K_%PB4Q6nvCB1OP6>3Z9-VDZG2p(DAYu!d*xnC~;eqSMgG6s7#ih6+wYihQJ6 z@O;hI-f$$1loY1|k_Of(Hr7qm+<f!nOucbhTe_q2k+GzNW8i1obfl0^jO!g9EGS}0+qxKN#gBQ-`& z#|P?*9FmbL?~}zci`s1p!3>6|Z$+3dtOeau$GkPL8W{erjDiDNZfTcYY%xE;0iO;by+Koz7+-g>Y69A?ussFdzt4lZpxTC@A%W{-c$5l0 z9UZG|^La1Pk#_NuNiF>Vim2%U8JLy`YpUJ#*}>#s+C(W}b|5$YXE3spRt<^Q3I&B2 z1a)=TR6Qqhr&gakf_L9p80QoOR(>zWeGkwTQ57TFkIA4ZeO#C=F zwN^(a2?1s?ypz;6k2bDDV&DfQe|eIer>Jdy5=^Vgayq=0_ZMe3rz-An^-GWaiIGoR zmSnt3-~nCj8?`UPx9Ny2oy5QKOvI}MW=ZSp%^8gL4VkTUr*Y+6Uw4L3dJ3N^(j2!cWJN>eUxX78M>2d)=qZ|8dD=H~5I z$;j%|UpU!BR+XJG;J};!i?gGBi;F|hD|2VVD8oDj8RmLjcEQuxx9@M%4oc-+?H|Jx zsefT!HQ6{sF!ZLG2FJ!y1sRO+kuZ1ccbIf#&|(VQB9T!!E4|F{m#<}+<6PdWE$SkN zu^1%-`5hqUcH|vx@6FB=aPtKvyGw@Ya)))W<`UrdR9|@dyz;B0HoC}MKI?_ut_>3+LMV}?f zWF>TV)Q>^-*9V74PaZKh_l9+OC>hUCFY7sH%u<$f|8(czkL=NEj6o_+hktomPX;mX!k@s%*HmKB|(A>#()OHvIfI5-mVEcmCvW z<(2)glQ{v>?`V{Ka~(fB5)i&)8`I!^h?Bd{N?lW_$mjI#9V!aLO-V3!sjH0t9kUU( zd>s!D5qis4!6NhoCQ@jxf9|Qj3ls1OH-7lqEW?_q2k z-;y2gHSLViF|s*Ccbaz1Ya{F_w$FP4UOR;G zB&9tcyYIZ^tmgRw89?c4M$dVB$nE&+rqwq}Uch~>y!Dq-en(kd*lqaK8iCKXMU{_L z@a$^-VzVoQ#t>*s{%5EMr-6$Ws$Nd2GXg!V0@MmFr!x(QHTEkUmxnG+JbBp!;f&NZSOoYd4dW^+%S>c0aQ@@!%&s^ zddk*-hK@=L0eRl_@LDG~(BN(z`#ir1f_y*G(<)H@t`pRK14ZH@4vR#BF+u_atwGb2 z+i3eaZg=+=#`_bS?0+QKv7cQ<*ru5gz=bY3QfQZ)pY}2*9>Kb@0HDpc|Kjv~ixD~? zTAwt(0v-^e^gXfNJhLo*d*W46vNVDu9;bD)TblV&uR61~e(9W4bCY#j)pPe2uN2p5 zPLw9VDpjWclJ$1-kcvkZ(4qa*wb44g|l2u zqL!2hRf-tUGW%W9pEjei;>J0t@J3SIxVBf>FmR&6F+X8e{#myHce26{?q8(mp$Xvf zG@xZpGeSjjU8s#*f1;U9v|d3G4;3%gyozr~hRhw%&||?bDs-{JE!e*qOXcVNgA1M^ z=>J(VTm9%MmQg$aPZoKj?XPjF(dK#0Ig()cJZkl8s=ZYH`qPsbX=I2k`M=WvD7-cY zX(F{1g35dZKqxSoLHJ6+VcB@qs-uh;rem^qh2Sxu05KIO@d4{RlM9OEw%&%)kBH5b zak$2-L5l*=WQj;(Rcz(?y^L=8sjAgSNa+)(HyklsdW;%a&#I7Wk1AZnKqsO3E>BrY zuopoLn8BMF-KOKg|F0sLvY%#%Z?o$jgNF}s z55nfB`v8GstRX#h+wptaZoyeTxS?YmlkI;0XD3&SI=9Pzb64F_NojY1cx*Hef#Ofb!!>SYT#0ITcwXOdt!T~f zEOcbs)_#d&)@vlyDMLf(C{TQkB$7YIVQ2n1&n+Mb#wdB-H2dAE-cCLb-Hree%SBV%wTRuvJWff9a_Hc?TMlz1eN`_$JW$a zT5PI+p;=Q_GAMfkOZOQjmg|1g#p&>JG#AAhKV!VkV<5=;75B%Ggb5 zW01q$MeGvIB}+(!H&5*#|7}%giK%@br}@~5(Ij2`Uy)&^r;E@WK2qy0&uD|yiQDfI ze~lzjuPxe`M4Um2%OP@VXu;+cM6g9Nlox(dXgR`8g7uElKMzm|Z8NzpMtJW_pqU7&(GxXO!`B<}V zy?@b*S>Uv$A!L9qO^dosoR4luX=b#srGkI}+$Lu%1T@r$O0+zkw1`t@;#92r zz^OIIF_(Y5A56*-h@pF+C%>lQ;#;a=imcv=Jptc?K7X-50 zH9idWJ|NWuG<6O8La|%A_vSgBwhDo`;HT;d-CXfJ7EVWydzL4+@o@Sic$YmyxXcIC8A=#lbiXXJq03~xN@ym zK4LN|SsevMkQ~Y64=T#lNt3F!;v&nG24ypT`=zj%rnHw$5+)fTiQ=N^{e&L)fIF-`u-tk z5H>+5)pKrnyjQC>j%|YyjXob@V zUn<$ZF1!z?Ud`Z1hK6CP^k8viX679Xf}{F3J0DVU=i&?#>p;Y-7XttFY}%O7U;{`SGlmtW$9$98wrtfw7P=1g*(MqxU`Ub%i$||L ztd`k9!H-cM+oOOVz>W&lA2oPfrcKejK8sU-MAH}g|Kozul>VPC7!z_3P#rsvBNuHQ z01w=sQ;N4^L_qqYFJ#3gL@4QYO~oeN9v+Pp#C8@&K$1irXP=bGZ~>PbO)8SN@qh3i z1CG6#sIo;RKgh;K2d?l zO)?W&>}P_`DedfFOYmsf_Z(xihCpT~(7Ljw#`|E^m*(D~U3x;6_NDZHoDhwMxXRvD z^f&*{_Olsp5rLt58^|O^x}c*NB2NDb_}_FdjsKQ?z)$Nj7k-4` zRd1)cQ=DSwf9TdIGbSZbTRuB~&XX}wZoS@ckH#u8tIy3M$xD;@C)s)L6`Ymba?0K~sy=U=H!>G8dl9&Dh~x}bdLn|8arQvQeRe0_^yM29(Vb*RP^%5uZ~X0Tedk(|FM zNL@C@|FHxY^1E+t8Z3u;i@YrKY;&g;J6W}z99g!agj-h1h$%8>pKZJ6FLsKR4ljrX z<#hOaA%!!|{ye;H)hW*5ug)S|{FPTkl%3+>84{mWC8{uRK;pPr3X}GWTe^9^|$|#$IjZ`n)0STg+Z@4MHf@1 zY%Z4)#W7XXXJ8(YeJs98TRFalj{bU<{5j9d{XO>v{5&a*9tIcFcUqh?CgPrLasxoSlG5FXu-%U?CVM<*OC5o=1n{+|M zYn}(=W2ewYWFimAEroL&dtgYfEVPizNN{T*;Ac`q`cy&J5hzfk|D&DbD>*wkYIp1d z*&g*DOJsp;;@wO6s*!WScipTC=El^a909gKWs~t6elVeb{VZq3zYJ%+3h#j*-X6Q7 zl=dJ1goc9h_eNSzpl#LLGZfOS>@N<;1M6-4O8PwyBoAn)K~_reVBufzX~;QnUl-J7{$Yw<-677U z(Hf43fS5f<(e@@JmwfvXpJb;d8}YFD3IcXW16=SKpxlH{tdey_7|&zwnrbEBLj0Vp|KqcAVSJxv3D8EVZHONcI|;{ zOZbenmbKZ^VaId`>LqS^g@DjE zL~VcS?z8#+U-beY>N+X==_r4S9MP{X&T(1>>)IzL%^{z zI$>=&AKb@<>cgCsJXhg*uhdfu;~j0O1ZgTIv5Lfq4lLbfxh; z|4=hGv8tiHL(r5GCC{zQw)z)coN{`+ulcU|3%q*64GI0?Vs~mPQqWDCHB9GdQ`7KVGu_^;(UPHU@+W-N9D`viq1T@Bq9%TZYPit}kiaPLt`d zVZlcU(~>g$Xur$3D%+UO~?KHj|+H>jd0Z9uCJk``HP5%@JgJ|8IGZl6t6L@w7BR>)hJB2!T9lJhaMd zU3pI-D-ToE4yslm7V*Wp_)DHzvV(lM|E&G>U7N~&-TnW{I?Jdynso~|5+o!*a0u@1 zuE8O=yE_DT2m}bhonV8z1-IY?cX#)}3GQ$wIp105+W189n{P?6WxCZjqQ)d|-Ew^iDJ9VsF4APdWCrWA(4HW+oSbmN6Fxe4G zns8p8<~nEnISnY~{b1ue9Wqp@~@$gMJMm z6`h|nL^BYaWKfWKgcrH7*7DRnwW4i)Iz@w}peJnGge2wS^xA&nap%bKU8$r8h!~c8 ziG`Yen#pVXAhp#^`&wWf-PK)9@@31_Opfc}{`Uk)5b}M(z)m|Oe#5DvU;J^wJeZW= z60VzwP><&#{sRH+Pii&^f{Vbo{IZ3MrJ|i*e|Rx$Z7bSOe~7XD?Vp!=^_;lyr0SyyF7l}y0@M*dw~7=c2TB7=MH*p;P~wKgl4qni!qTs-gf{>J{dk0Zqjae3 zEc_>X*TE-RaVQFHg`1iN`w;w#?9Fc+i|;h$*>-NACnm^rR6gX{o?)%n$$ydjIM|xMT*wveOl9vj z$zjcmYEkczcCE+d4Fyn}nNHcM@CWGZrPCj_pGvb0HC&mFbd29UBwWPwe#BGvZ>$1< z;iDt#c~l^5RS>FDBMAp^c(=z{Sz49R_ihQ<#=?v3ES-q!#NY0rAcLXzSJ8HWSpXHb zKtN%t!$feCsWlUm<7!#V4xMryN%kb|a3B&k6^ zX>q^TAJt|guJL6WK3tN$VL-i|!($JdmScXa3`E8QxTu0xLKp#E%ynV zv<RDtb+*ydY>%+(orfyN!f|qz+@CTS>Ss9XVr=1jqTxq|$3~lO4*dy#5iILqzPTB% zFp!1$|KylhWanbw7kpVIUFY_2q;yK#dzujSBD$5Q@b|wT@@9}+->$!fFv5xR4E;#J zHqm!pukxw(1|)-9Cv_o|YU=!qA4d88)5EBarP{uj`DkZ{wb`Usk0jB#L`vBEFZLmG z&&`fcAMLJE22El+J6PH(_Xv5sHcTqzG?wIn@BvU7t=S0-F<@V|Bj)zy_XCktY_ zibJozhs0@P{3=Dw$x+RdYAAQddiO^#Gy2y}eB)1uvE32>sldr9g67+#whcs}({Bh~ zXid4T$Ap5ok4wBv6BE=pldKC7Ax9@uDtM>L6t9pg^3flV__*L4Pr$FF43R zuK*PM_K=2DW$5)1TgLoq_6`OsAU^_G5Ut6*(z%SWQ-DVdGe(r zV)dSOd$(TU)=77-Zf=DZYwH=GqS)FtTZ9rE{td{>Zq&P}+UjFkzGmHmnLcDta!Ltf z!a^lxwOl`7Gj#3mkq=%?jKY}1dbSz|#O4Mbyme|b5t&zrlH;aTnyYt@xF>p*;X4FO zpHl~9$Y!IiezY{NBS`z%7^d*71*6QBEu!G8N$!#^7bjxNjwBeY!(iMW51hG(?NoDP(wBg58BgOc!k zQV63zsL2#))5b-ynsmKr+m6zVfBrELffUVo^==yC(){yYw}aFJ(cqAYT=75XEseR_ z_*J7h{-(D!LBKAtKm7n3=RiycKeT=8$n6a3^x^PIE?JU7IPt$%$hsR6P6?3=WeD9CPfAW=q^bBha%NOz z)MthOez5bGNLx0YG<3Z{%mvgE^X)tw-obpRvQF#b=P=YLK;kY(I^6P9F{&8Nts*x) zh*%HydIS|&hA*Fd~?A1esxW=gfc)s0ao z@3#3S48H4E(O))THx;QK>wRiH$_3kAq}6|sHH5!nV04%=n;dhc?+q1dkqVRfh^B-A zjCa)8Y!}IV9!4Hq4#Y-`Kv2T*gA;vU7*TnKeh%H)dz&JoXndI>a2j6jr8&)%yL~17 zRo+pF%iL=oPnwPBM~Yk9SYjjTWl6mQ+dyG`Q=Vt43|%uEeFWs}_7m}LM|Mh|M?*SB zXkf9=a`NEXJ)VlEs1ys%-!uFj3=}YUFak)j1VxOM*2JAY~{C zn$&P#fj{0%6%Al%nsp|YSk)BK!TX|@X749tn(B2qMCo7%u#Cu05yw#Ca zcptFaoFm(T4|T*nm@X7o{nGgPnUXf^0CRos*E5CgSpIZ$>JUY|t|i;|DQncNd6ied zFHFb7{|J0PQ|H%Grc5y2ezI0G%$`nz&0*Eb6>|lDb=C{XVBJpyZF#! z1F+_n^e7xBp;7>IKssSsh`Q#v*(YsQE6$_qiRpU1l>%sph!(K*UcwvtJ0v|%tL$mR zNm{Yj<;n#B#*drL4a9zr%fnrkW+LwlN4b=qm*}AUS~w=67<=P@My5>9KKxeu~|FIq(W1tKOen<8CU7j)aBs z>*zFVDb~M!W8Zb&VwoguL&tPAa)8fqnd#~SRxvQ(3vRYJ7zpS8Zw3yPdR961&7oeW z$w%d29nWt=g8(B>vb+k?n75~p?!;r?b>w_+$}3>JFoI1}^I7OS7I^yN@OaPi#m^t4 zQKTHBqA3~D-^2x_^pRTK(8tk-fmrP|EgCGWAs*IU>BqusIiJC1FdD7|EW$UbLlvjj zG9@yJSjGfCs>h1>6yKFK8M4KKa0NpKUXt9;wM6n*hk@zLr!R^9G8XLNm~=bU5MT_c zN94IZf0cII>6P3*0>#yIm-dV@qlThxo1#=T%)}RgIrYZjIQuUxozde(Gqy{0CJYy@ zzsVWDkxL6^GNB^eLa?tfD(SesEyqUP0OIMw)Jb+ZC5Qp^;pWuBz$%dmxf~$>{Z7bF zCg8abKO79+S0||zhN13<0;KlU)t1CvA8@r*lJ@3W0;EvYsfq8|S`x?5NZ-8-$+w6> zB)#V$(t(R_na2x%45FrM>lIM4W*G~h)<-v+fVx-V_s8aP9p`nMv(X-_7_BdUJjUA_ zQG#t>{^)e#$7{~-Kgo_xzC+Sdsr>V-&FMG38ZSZ&UUBK@>^#3iC7o3>p74wO{12%0 zm#~8HTbVErwIumrz;Sc)EG(-ail)eP$#-OzGF2IKhuK&x7#| zmJodv>I^$E=k;S-I?*%nw{lkklE7|%rf``kl)6mEgM9ORZip0j!FcF<2-D451ot0N zO*07(fdsu>6a`|7RHnDo`{cGGEMDC|&hyub3Gqe-G4RKhbpWVej0i<6=Z?dElKy07 zpHDR5=g=Z^as0O%(}ZJEnJ!H;-~x>?nt5_6VK4NSpB$Or^THe=;v4I}A9$b8*Lu zUMtR}?c7UKH+y%a_xRqH$Ls0qCK-`dp@gkyNGaV!e27ay;IeYHJ;G2stlJu-qy_Mj zOWhK7W{9D5ro>)YZIJ98cl*Yj+h^tti0{gn*hg*muXd;DEtFn?PNVo2?jw`#@4wo7 z6~hpR0w${_g>U=^eh5zF{NLufhHOym}F)ZD8qP@orj`cyLfbrO%P6LXj>+@Lg;j5 zxe1ccK_l3@*xsjbjjS@1!btReUPzoc@F{xL@2{PPxVw=xKzs}TEn`>dFNzNCgA8uY$ z<$q0o1<*w2LJ?DL=)v3Qba>v(_PMQX1tOW9KOS6bw5Ta&tt>8l?|Wne2eejiR=Dlq z#z+eCpl*v7Z(nID&sSRL4~|S^=<%gLwPu#K(TXGIDRg(iT(`s}MKj(IE_m*CmhsT2 z$Q=qUOM&Rt-k()!$SfW4LAL7L_BNT0kGnInM*cNqL%)*s3B&V@Y%jMJDc_{>AbKow zAFzI9!0XDd)3p#CJ<0!`gUG)asi1N@jnzqy&Rbsi=d(y%Y#8|k_!dv7x@{iy2mNP8 zv3}h_Q&_y|?||PUEWZ&~oe3G&Gyjws-CKZ7BAY0{6U3sTVv@XZQ6z~27_K=Pi&=A?wyGWgYB7)B8zUG9Q zcoR9Q2+~MbSv`I|d>2$eDn5fG+hj_eG0`<+K#Q#ryGkvL0coMCYNiUnBpu2z$G5Km)i7qqP$Hvsn)OX9LnSZ~bQR1W=*$q=ccMdeN17Ixo1`qM?z zd`>5q^VI5!3)|Nv0Ix9hym9A?va4qEUoU2EZtK=alZ0Jnm(O*ti@hh~97eU+~h0zq`SA zfp`P20P2y>2eqGUe)I%Ou*GhSTStyt`fs!OnU@q6&vPT<&` zZfB$kNArlRrT1$w`!%h;LrZjIE8k??+ApfnfB@K~<*CqMmthm%6};WvAsB8|6Er^3R+6Oj$9UMA1tojfO(WNt!B#96 z&JE@dK_8+yWhv1p^r$8e@d{1X5gxYMoz~;G$KRq|$G?B8fOozo(3$0=Hj{tI)%BLE z#Y<%x=*E4wLG^`H48bTrj?>}f7_9jqtSqZ5i0&plhpw8XNK3!WSLe*~3do{?m^syL z9RC;5G+h`@V=%S88BxmhrPU}eR~f3PdzOJ?rTQO!D1ctzAG|0*XrJeSzJVn)bPO+` zhTv!nb+PY%XVmUc&sc8cvO&$1%>Mlb=D`HfR;-8lAn zsqVS9R%v&CmFwZ;fa8AiCtFrz-BbLFBKxJ2*gGLNN@XIlsm4gzJE9Md8xsj^LWp7r zP#=`cWqY8Oue9HTm)A1ZJlxRrU?-z>;%Jp8lFKJiG#-=K?HjD(Ut%t*=pJ5Q%cgiI zVTID^G8Pm`jhk;KrliwF=PK_M1n(rh*+c`fXh#IRe01gl%>G!{-2+}#f*@{S=$cHYN<{^u zwdx{#!z6T$YQ*Yv9vAkSA~`}xAn9(aD;tUjPEaWG3;*;{eR zy|y$4l7Qs;up1xyol|*TNU|I7E?8PjSapFri%k0_5O*yN;Y$eAx*Mrmb!QSTzVV11 zI&3$`>1q!-ZB3q*Tgng>W$O(#?A$ zl^GEMsS8U+)4g#C7r|x>@~rQ%a?NsQ2zJ@-)jwX^s7C15e4XlT%x2q{f{JQt_>e6< z4eXy2&3FW;`Z0JhJU3jfp~8DC%8c%? z@!wFUxHOz*M=>U)Mjo9lQ2CAem#p|!I(td!EOeaBLjLW1919%x_ z29X5Ozmb-G_vPVxTKUPx^b*x}*U@5Lgj3I0skhp`GTbI@nI39?4)sMD*mKe=k-nwoToH&58sAi)Ld*P?zCcCbiL8lw@*a*)V;K_EoWi;JT(cYlqWsQ-yRdN zclaI#for?*B~3k5jn6uhmDWsx8=LyC9IbvhF`amHBy(yUq)_k>v6SoC4}%!$z-I@M zOZv}u-bgQMH7q3131CaXJ4Ci0xV@h8R-g~@cTUSJK0)B<6n1(BQr6?d{RIL2OsDix zm1y64P-jKdqo-v+o#OFx#f#H+ca#3vEixc$xK1FGc=_Sfa=`uk#egKJuWU&V88=%g zoz>~}Zp-CFq&?xq>%erT9G64yrBRw(I6%f-@Kt{+)iMZlLtL9{(BdFIG;-N+X9+!G zi_C<>XY~0-thME(vb)x&&hsry`O*(*icZQJN|QUyuISxR+Zv+9?=Q10sKvw% z9g28^t<|{VZrpc2NBLqj@nnDcvpC%3hO0cjc_?gXP~i{6`t+sGoujkubnoyB4Ax z7w6!OXA(*C^VwaO79vX>=9=I`M~TwK3yvGq21&Kf?DpQc*lLwlEX4ox;?P;ja^9 zkjH+lh({~@^!_s1GG`}m^x8Z7U>d3HI8Py?jqzpw$=&e{VF~JK5Z^Eem_9nBON*wuU<4mP#auB)n>=rX1C0lJiGxN5K+2x}Qc?h)}vIGyD zJ)<(O+ywR&SiENYC}r2h0SIkfI6VqU((yzBBw@+1b!w$>`P4FR*6=&h`tp^mSOFSn zHoLftI5DqU*mWU7b$xJe))Gw8>P03Gx><5qN)06WHi9a1ODP@bm_0iEbgqWsURJwL z$m0&O@}cl)lilyY@k@g)2EWU|CxO}%QWs%SIG&eh%1yzeEHT%ca>nv||N8k-NF4tB z-pz)-y&U`_D2=bF*1Gs0E*vZ?pNdYc(^QkAC4D4kkH2usCrJk%{rb2x^}&=qk$y!> zfQPQyO5ag}OM!WG??dGtr)h7&D?VpubT+z{uY|s*Q!xu&PAc~Bz_!4B?`(>CaO@Cn zF6Uba6TumgZPz$sQC7awdqV4To-#31F?L{%`3N=;O5pDih!(y@Bo_tM`7hNTMAlsKeJA$EpXJiUK9^Deey;m^ z7`~E{i^Iv{3MUEjkBwU6%EmssofWE3t>UbxV`nJ&Xu>!=bbK%1n{F+%jBLMA`DzIw zbvi>Jk)5htjQ>H%rNUZPOCDIsiVj|8j_5Gq`mswX;>)wzv}N1UWOlJj;<+{IteNWI z9cXMZij=ypm&|>P6^71OR~E5((_tTmifwaO92CUc9^#^>*nQ@`J|SjFp1TizZ>!m` z+0x9Bj0@^hIkj+08Y+IQwmEwrwz99fboK4fVk~du_v6!JSAD>G5h1e!@ss-t@24fD zUK|xQH6;u+NUK};R83D8MoPyD+hK?kRDKAkf2jm5Cid@)3zCp6_dJ*FBP9WxF4KUA7uhjMG&kE0#Re%y(<~1w_g)%SUPs#J+fYx z_VFD#y}CCxF`k^7%!Ryx=`-b1ph2i+9pQuz%?YR?jChBhP|_=LY1yMN@W```D<|3w zV_;`){MgvoVj3C&6)#>9eDf}Hq3?3LO#rd*5C}~^HsO|TD_y>D zZVPge-I`a-YZg9n9%3~X45|F-iHOt#gafn-RmY&QI7)Gw9y8lqDEe%D{7M_L9EI-If<0VkQm_eDfU_-0>RH7 zXPZ62$OLF2u;%l&dvUptnLY$ur087xbmjksoPF7?wXJ&klj}ypROD4Kx0?2EIxPVK zqes5gAOo8rb?K@bg7fgWnHo)%au)A{gSC|8%W-dS5Fy48k-VL*;l@Fkeu<(;|5)Av zf8%M}1EXI8od&4Ds#R<&QHq&0Vwa*093@(gJZzWC+l)Ud30iVKI6`}2X@|;AH*~CN zqobjr`I}?;Y;FfeE%>ToX+Z%0OiOk+foTRW!_~5QbHeg;s&g)&Mnke)7*8H5Z4pF) ziHUi<(v1DV)AO$0cFA%X4bhF$yGwz!^F8v|R{mz9e-kK|VzoFSV;x`z{2G+`e`mGb zs)>$FOyeJK5y_TxtWR?rmFQVL&a9Xz_HtNRS*84K8dnG^0C7u?ca68+i~x=6?_0@s zE&k1}hPH}aWKEHNh4vV93+*U~I~I~bXQHBFWR%@^XnXXvW*i+>%HhuNTgsj>;32Te z)ot0~Bj%Ex!<5Xx(DHHNHqNtaMPgy_LrvUC-_Tm9SX3m^c5Z5Svej<+mGVdGTs#?YIec15aB zxE1GVSBb993EQZYlT|08J`e9Q;wV)(;HK`VWz&53i44SopaJWsV;~Nc;M}kzg+jET zt*xwg4n7ppCUQ0GTf`>3(^+`v>B3jd4~)s=V2;FLG2ZTvEwED8&{$HPDWK*|ja*EW zQuOxCh|6LII=csS2|oPa_5amT0|Gh_h-Dd0`b%832U)+~@R{7E;%-QzHKi`kj0fnL zI)FbixSx2fVOGe9-bSZ0QRvM+Lrt>1`DX-&pP=9;h#*CrpQH^>>K#3#fbpkCOaVjM zAem`t^gMQ`1Sr2X?yw{w@+3nJjw%JRUWyH zi)6IXAz;F}_nEAQ3o3)93gI(I5eh%|<|2{z4W~a<#v%g$tZzQb{ApjP8A{oJEK9J^ z&G#fN@fRH%XzsFfHlxO&*SIF7GKavC@9AoJ1LFl$=a=;pI#MFtXBag;l5MaEWO-*9 z)q@3}>#Cp|Os~!^Gn!!~4-X$RpSofsRLW*Id@6VN;Qqv#Uy}|>3Wyx?bUVdWA;rKg z_&W>FIjd)}e{!*!QJ$f|9!p8%q*APRv3TX@am8OqBT>jA($C}rwRW^G8~AwC;@Y?5 z;X1TCB6{ANej^%)*dR&mw)+X~yY-Oxk*(JmF9n6zTauoaTJu)@;j}Dp$utAfGg}D`I-gq^7-6N`irq(b$-3J-;}$k6C$4U8VpM&5#eBcwvoUV61=Im&E9c65j9+wxrU z;pyLrAly+XEul50p%}oIAB%@(LcYxu-ev_Uk*FezE!>5)#7WCRu5?9ckkTuH04bi@ zk~hhaBG||d3;fV)o&~ARo!-dAkxMJ<>+9EeZWd8R{VkE~v{3ycBajnkZ0X$^b;%^6 zK5;7G|5JOJB3RhFvmq{n$H&KBkMEh8r!L$efylT?w7n5Tkn;MsGSB>|u-)tq1zx{?ZK_qT&pUPh@4BoZ`k*8t(m7qMf{u=kYm(+n z66j}n6b%`#kp9G6mt;RlAwZ@Vl0XD83KHURoMdVS$Q2;T^RdDUr4KS_cJP>0AN^>4<{4*1 l6+hHT{JHLvC247CFEG}}26?lJY%c($NsG&im5Ue#{0~DwxKRKA literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/index.html b/h2/src/docsrc/index.html new file mode 100644 index 0000000..2e09c2f --- /dev/null +++ b/h2/src/docsrc/index.html @@ -0,0 +1,44 @@ + + + + + + + + +H2 Database Engine (redirect) + + + + + +

H2 Database Engine

+

+Welcome to H2, the free SQL database. The main feature of H2 are: +

+
    +
  • It is free to use for everybody, source code is included +
  • Written in Java, but also available as native executable +
  • JDBC and (partial) ODBC API +
  • Embedded and client/server modes +
  • Clustering is supported +
  • A web client is included +
+ +

No Javascript

+

+If you are not automatically redirected to the main page, then +Javascript is currently disabled or your browser does not support Javascript. +Some features (for example the integrated search) require Javascript. +Please enable Javascript, or go ahead without it: +

+H2 Database Engine +

+ + diff --git a/h2/src/installer/buildRelease.bat b/h2/src/installer/buildRelease.bat new file mode 100644 index 0000000..56430b8 --- /dev/null +++ b/h2/src/installer/buildRelease.bat @@ -0,0 +1,23 @@ +@echo off +echo %time:~0,8% Start + +setlocal +cd ../.. +set today=%date:~6%%date:~3,2%%date:~0,2% +rmdir /s /q ..\h2web-%today% 2>nul +rmdir /s /q ..\h2web 2>nul +mkdir ..\h2web + +rmdir /s /q bin 2>nul +rmdir /s /q temp 2>nul + +call build -quiet compile +call build -quiet spellcheck javadocImpl +call build -quiet clean compile installer mavenDeployCentral + +rem call build -quiet compile benchmark +rem == Copy the benchmark results and update the performance page and diagram + +ren ..\h2web h2web-%today% + +echo %time:~0,8% Done diff --git a/h2/src/installer/buildRelease.sh b/h2/src/installer/buildRelease.sh new file mode 100644 index 0000000..8782e23 --- /dev/null +++ b/h2/src/installer/buildRelease.sh @@ -0,0 +1,18 @@ +#!/bin/sh +echo $(date "+%H:%M:%S") Start +cd ../.. +rm -rf ../h2web +mkdir ../h2web + +rm -rf bin +rm -rf temp + +./build.sh -quiet compile +./build.sh -quiet spellcheck javadocImpl +./build.sh -quiet clean compile installer mavenDeployCentral + +# ./build.sh -quiet compile benchmark +# == Copy the benchmark results +# == and update the performance page and diagram + +echo $(date "+%H:%M:%S") Done diff --git a/h2/src/installer/checkstyle.xml b/h2/src/installer/checkstyle.xml new file mode 100644 index 0000000..a9a3e4b --- /dev/null +++ b/h2/src/installer/checkstyle.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/h2/src/installer/eclipse.settings/eclipseCodeStyle.xml b/h2/src/installer/eclipse.settings/eclipseCodeStyle.xml new file mode 100644 index 0000000..9ca6d3d --- /dev/null +++ b/h2/src/installer/eclipse.settings/eclipseCodeStyle.xml @@ -0,0 +1,269 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/h2/src/installer/eclipse.settings/org.eclipse.core.resources.prefs b/h2/src/installer/eclipse.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..99f26c0 --- /dev/null +++ b/h2/src/installer/eclipse.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/h2/src/installer/eclipse.settings/org.eclipse.jdt.core.prefs b/h2/src/installer/eclipse.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..2c2017e --- /dev/null +++ b/h2/src/installer/eclipse.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,74 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.builder.cleanOutputFolder=clean +org.eclipse.jdt.core.builder.duplicateResourceTask=warning +org.eclipse.jdt.core.builder.invalidClasspath=abort +org.eclipse.jdt.core.builder.recreateModifiedClassFileInOutputFolder=ignore +org.eclipse.jdt.core.builder.resourceCopyExclusionFilter=*.launch,*.class +org.eclipse.jdt.core.circularClasspath=error +org.eclipse.jdt.core.classpath.exclusionPatterns=enabled +org.eclipse.jdt.core.classpath.multipleOutputLocations=enabled +org.eclipse.jdt.core.compiler.maxProblemPerUnit=100 +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=warning +org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=warning +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=warning +org.eclipse.jdt.core.compiler.problem.localVariableHiding=warning +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=warning +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=warning +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=warning +org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=warning +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=warning +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=enabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedParameter=warning +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning +org.eclipse.jdt.core.incompatibleJDKLevel=ignore +org.eclipse.jdt.core.incompleteClasspath=error diff --git a/h2/src/installer/eclipse.settings/org.eclipse.jdt.launching.prefs b/h2/src/installer/eclipse.settings/org.eclipse.jdt.launching.prefs new file mode 100644 index 0000000..d211d32 --- /dev/null +++ b/h2/src/installer/eclipse.settings/org.eclipse.jdt.launching.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.launching.PREF_STRICTLY_COMPATIBLE_JRE_NOT_AVAILABLE=warning diff --git a/h2/src/installer/eclipse.settings/org.eclipse.jdt.ui.prefs b/h2/src/installer/eclipse.settings/org.eclipse.jdt.ui.prefs new file mode 100644 index 0000000..629c2b9 --- /dev/null +++ b/h2/src/installer/eclipse.settings/org.eclipse.jdt.ui.prefs @@ -0,0 +1,53 @@ +eclipse.preferences.version=1 +editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true +sp_cleanup.add_default_serial_version_id=true +sp_cleanup.add_generated_serial_version_id=false +sp_cleanup.add_missing_annotations=false +sp_cleanup.add_missing_deprecated_annotations=true +sp_cleanup.add_missing_methods=false +sp_cleanup.add_missing_nls_tags=false +sp_cleanup.add_missing_override_annotations=true +sp_cleanup.add_serial_version_id=false +sp_cleanup.always_use_blocks=true +sp_cleanup.always_use_parentheses_in_expressions=true +sp_cleanup.always_use_this_for_non_static_field_access=false +sp_cleanup.always_use_this_for_non_static_method_access=false +sp_cleanup.convert_to_enhanced_for_loop=false +sp_cleanup.correct_indentation=false +sp_cleanup.format_source_code=false +sp_cleanup.format_source_code_changes_only=false +sp_cleanup.make_local_variable_final=false +sp_cleanup.make_parameters_final=false +sp_cleanup.make_private_fields_final=true +sp_cleanup.make_type_abstract_if_missing_method=false +sp_cleanup.make_variable_declarations_final=false +sp_cleanup.never_use_blocks=false +sp_cleanup.never_use_parentheses_in_expressions=false +sp_cleanup.on_save_use_additional_actions=true +sp_cleanup.organize_imports=false +sp_cleanup.qualify_static_field_accesses_with_declaring_class=false +sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +sp_cleanup.qualify_static_member_accesses_with_declaring_class=false +sp_cleanup.qualify_static_method_accesses_with_declaring_class=false +sp_cleanup.remove_private_constructors=true +sp_cleanup.remove_trailing_whitespaces=true +sp_cleanup.remove_trailing_whitespaces_all=true +sp_cleanup.remove_trailing_whitespaces_ignore_empty=false +sp_cleanup.remove_unnecessary_casts=false +sp_cleanup.remove_unnecessary_nls_tags=false +sp_cleanup.remove_unused_imports=false +sp_cleanup.remove_unused_local_variables=false +sp_cleanup.remove_unused_private_fields=true +sp_cleanup.remove_unused_private_members=false +sp_cleanup.remove_unused_private_methods=true +sp_cleanup.remove_unused_private_types=true +sp_cleanup.sort_members=false +sp_cleanup.sort_members_all=false +sp_cleanup.use_blocks=false +sp_cleanup.use_blocks_only_for_return_and_throw=false +sp_cleanup.use_parentheses_in_expressions=false +sp_cleanup.use_this_for_non_static_field_access=false +sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true +sp_cleanup.use_this_for_non_static_method_access=false +sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff --git a/h2/src/installer/favicon.ico b/h2/src/installer/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..fd5e73a416cf2cde3a3e5d9e530e53c3e74742af GIT binary patch literal 4286 zcmcgwU2IfE6rO@~@65JbQG-8;l#uv`L}Pe|jiq;PA@~3(_zMJ+4SVlh2(gXPM7ty! zjnVo9{y_?8p{C38cGsYeUfyHtl;?MUd$g#Fi@@;{1KEQYmbQLrnNd6Mp zjwF8;-*3-_0-5z^^Q*AISrz)uN3eSZ^cnDX6uVnjmwH&MxfXsLhg{8tBA>ry+A!zE zZk7q9coX)A;&6U*`&~9x+BeRBD}4R!-}@J5z&nvf z6f@gaDBB)|?oXj73bt&vZ}`0iYcGOs#=+;83Z7Uy+gg(E>&)Iw*p1wadiHid#oDp2 zFJ}1Q@!t{ao_cGDAIF={+!uk?3pQr&A>TC>yTub)W{S$B z(>nGT`JzPeHu+fpvSs7CDL3bV=k!NX>@j(f$BS4$ZQ8)u>@#&}9-q6%FWYx*F8E2V zD4g>E?O$VH&wEY$*dH&`rR6lj&x6SES4UdFJLpIv$ zv8>kD<2>*c_I;{mr?dS|*s^ldd4c5E`*y_bnASDNdEq0TmaVg}cQY@gI7)J=tM zZ|ds#0^?K9sNv(jpN{b*9}YIOKB6(raiQ=7pKpP!8#C}F|2)ATvwMm5AqD@>xEE1B zvIzBm#Izrx=77Eg@B9pYat49#>`L&*HJ&}D;MdMC&E_0eBICGG=ORp&*GpX4WG z;|W!@%pUPBe$CK>zPMoP(D#11^bW!zOjAzxm-?{DYsMVdhXN_yux(R6Mt%-Op2*v> zh5E*s??kSBPw`K)nU~^T#O4z2LuZk2hk2&f%X}@Pqc9Y-GLGp|$bb4n2Ohed{%Pr2ByW`40T18r@efW?J7Isg?k@ zH(o3GyZWtX>NGmv+m(3$|Nr|0wnmX7NAb>nuHa`LGJ6)ysfSPix<7>5i$6$@QwGq*Qsa`Wxq<1>gVx literal 0 HcmV?d00001 diff --git a/h2/src/installer/h2.bat b/h2/src/installer/h2.bat new file mode 100644 index 0000000..98cae20 --- /dev/null +++ b/h2/src/installer/h2.bat @@ -0,0 +1,2 @@ +@java -cp "h2.jar;%H2DRIVERS%;%CLASSPATH%" org.h2.tools.Console %* +@if errorlevel 1 pause \ No newline at end of file diff --git a/h2/src/installer/h2.nsi b/h2/src/installer/h2.nsi new file mode 100644 index 0000000..ffaf509 --- /dev/null +++ b/h2/src/installer/h2.nsi @@ -0,0 +1,179 @@ + Unicode True + !include "MUI.nsh" + + SetCompressor /SOLID lzma + Name "H2" + Icon "favicon.ico" + OutFile "../../../h2web/h2-setup.exe" + CRCCheck on + + InstallDir "$PROGRAMFILES\H2" + InstallDirRegKey HKCU "Software\H2" "" + RequestExecutionLevel highest + +;-------------------------------- +;Variables + + Var MUI_TEMP + Var STARTMENU_FOLDER + +;-------------------------------- +;Interface Settings + + !define MUI_ABORTWARNING + +;-------------------------------- +;Language Selection Dialog Settings + + ;Remember the installer language + !define MUI_LANGDLL_REGISTRY_ROOT "HKCU" + !define MUI_LANGDLL_REGISTRY_KEY "Software\H2" + !define MUI_LANGDLL_REGISTRY_VALUENAME "Installer Language" + +;-------------------------------- +;Pages + + !insertmacro MUI_PAGE_DIRECTORY + + ;Start Menu Folder Page Configuration + !define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKCU" + !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\H2" + !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder" + !define MUI_FINISHPAGE_SHOWREADME "$INSTDIR\docs\index.html" + + !insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER + !insertmacro MUI_PAGE_INSTFILES + !insertmacro MUI_PAGE_FINISH + + !insertmacro MUI_UNPAGE_CONFIRM + !insertmacro MUI_UNPAGE_INSTFILES + +;-------------------------------- +;Languages + + !insertmacro MUI_LANGUAGE "English" # first language is the default language + !insertmacro MUI_LANGUAGE "German" + !insertmacro MUI_LANGUAGE "Spanish" + !insertmacro MUI_LANGUAGE "French" + !insertmacro MUI_LANGUAGE "SimpChinese" + !insertmacro MUI_LANGUAGE "TradChinese" + !insertmacro MUI_LANGUAGE "Japanese" + !insertmacro MUI_LANGUAGE "Korean" + !insertmacro MUI_LANGUAGE "Italian" + !insertmacro MUI_LANGUAGE "Dutch" + !insertmacro MUI_LANGUAGE "Danish" + !insertmacro MUI_LANGUAGE "Swedish" + !insertmacro MUI_LANGUAGE "Norwegian" + !insertmacro MUI_LANGUAGE "Finnish" + !insertmacro MUI_LANGUAGE "Greek" + !insertmacro MUI_LANGUAGE "Russian" + !insertmacro MUI_LANGUAGE "Portuguese" + !insertmacro MUI_LANGUAGE "PortugueseBR" + !insertmacro MUI_LANGUAGE "Polish" + !insertmacro MUI_LANGUAGE "Ukrainian" + !insertmacro MUI_LANGUAGE "Czech" + !insertmacro MUI_LANGUAGE "Slovak" + !insertmacro MUI_LANGUAGE "Croatian" + !insertmacro MUI_LANGUAGE "Bulgarian" + !insertmacro MUI_LANGUAGE "Hungarian" + !insertmacro MUI_LANGUAGE "Romanian" + !insertmacro MUI_LANGUAGE "Latvian" + !insertmacro MUI_LANGUAGE "Macedonian" + !insertmacro MUI_LANGUAGE "Estonian" + !insertmacro MUI_LANGUAGE "Turkish" + !insertmacro MUI_LANGUAGE "Lithuanian" + !insertmacro MUI_LANGUAGE "Catalan" + !insertmacro MUI_LANGUAGE "Slovenian" + !insertmacro MUI_LANGUAGE "Serbian" + !insertmacro MUI_LANGUAGE "SerbianLatin" + !insertmacro MUI_LANGUAGE "Arabic" + !insertmacro MUI_LANGUAGE "Farsi" + !insertmacro MUI_LANGUAGE "Hebrew" + !insertmacro MUI_LANGUAGE "Indonesian" + !insertmacro MUI_LANGUAGE "Mongolian" + !insertmacro MUI_LANGUAGE "Luxembourgish" + !insertmacro MUI_LANGUAGE "Albanian" + !insertmacro MUI_LANGUAGE "Breton" + !insertmacro MUI_LANGUAGE "Belarusian" + !insertmacro MUI_LANGUAGE "Icelandic" + !insertmacro MUI_LANGUAGE "Malay" + !insertmacro MUI_LANGUAGE "Bosnian" + !insertmacro MUI_LANGUAGE "Kurdish" + +;-------------------------------- +;Reserve Files + + ;These files should be inserted before other files in the data block + ;Keep these lines before any File command + ;Only for solid compression (by default, solid compression is enabled for BZIP2 and LZMA) + + !insertmacro MUI_RESERVEFILE_LANGDLL + +;-------------------------------- +;Installer Sections + +Section "All" + + SetOutPath "$INSTDIR\src" + File /r /x CVS /x .cvsignore /x .svn ..\..\src\*.* + SetOutPath "$INSTDIR\bin" + File /x CVS /x .cvsignore ..\..\bin\h2* + SetOutPath "$INSTDIR\docs" + File /r /x CVS /x .cvsignore /x .jar ..\..\docs\*.* + SetOutPath "$INSTDIR\service" + File /r /x CVS /x .cvsignore /x .svn ..\..\service\*.* + SetOutPath "$INSTDIR" + File /r /x CVS /x .cvsignore ..\..\build.bat + File /r /x CVS /x .cvsignore ..\..\build.sh + + WriteRegStr HKCU "Software\H2" "" $INSTDIR + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\H2" "DisplayName" "H2" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\H2" "UninstallString" "$INSTDIR\Uninstall.exe" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\H2" "InstallLocation" "$INSTDIR" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\H2" "DisplayIcon" "$INSTDIR\src\installer\favicon.ico" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\H2" "NoModify" "1" + WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\H2" "NoRepair" "1" + + WriteUninstaller "$INSTDIR\Uninstall.exe" + + !insertmacro MUI_STARTMENU_WRITE_BEGIN Application + SetOutPath "$INSTDIR\bin" + CreateDirectory "$SMPROGRAMS\$STARTMENU_FOLDER" + CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\H2 Console.lnk" "cmd" "/c h2w.bat" "$INSTDIR\src\installer\favicon.ico" 0 SW_SHOWMINIMIZED "" "Start the Console" + CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\H2 Console (Command Line).lnk" "cmd" "/c h2.bat" "$INSTDIR\src\installer\favicon.ico" 0 SW_SHOWMINIMIZED "" "Start the Console from command line (using h2.bat)" + CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\H2 Documentation.lnk" "$INSTDIR\docs\index.html" +; CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Uninstall.lnk" "$INSTDIR\Uninstall.exe" + !insertmacro MUI_STARTMENU_WRITE_END + +SectionEnd + +;-------------------------------- +;Uninstaller Section + +Section "Uninstall" + + SetOutPath "$INSTDIR\.." + RMDir /r "$INSTDIR" + Delete "$INSTDIR\Uninstall.exe" + + !insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP + + Delete "$SMPROGRAMS\$MUI_TEMP\*.lnk" + + ;Delete empty start menu parent diretories + StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP" + + startMenuDeleteLoop: + ClearErrors + RMDir $MUI_TEMP + GetFullPathName $MUI_TEMP "$MUI_TEMP\.." + + IfErrors startMenuDeleteLoopDone + + StrCmp $MUI_TEMP $SMPROGRAMS startMenuDeleteLoopDone startMenuDeleteLoop + startMenuDeleteLoopDone: + + DeleteRegKey HKCU "Software\H2" + DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\H2" + +SectionEnd diff --git a/h2/src/installer/h2.sh b/h2/src/installer/h2.sh new file mode 100644 index 0000000..01ece6d --- /dev/null +++ b/h2/src/installer/h2.sh @@ -0,0 +1,3 @@ +#!/bin/sh +dir=$(dirname "$0") +java -cp "$dir/h2.jar:$H2DRIVERS:$CLASSPATH" org.h2.tools.Console "$@" diff --git a/h2/src/installer/h2w.bat b/h2/src/installer/h2w.bat new file mode 100644 index 0000000..cb55e87 --- /dev/null +++ b/h2/src/installer/h2w.bat @@ -0,0 +1,2 @@ +@start javaw -cp "h2.jar;%H2DRIVERS%;%CLASSPATH%" org.h2.tools.Console %* +@if errorlevel 1 pause \ No newline at end of file diff --git a/h2/src/installer/mvstore/MANIFEST.MF b/h2/src/installer/mvstore/MANIFEST.MF new file mode 100644 index 0000000..a470ceb --- /dev/null +++ b/h2/src/installer/mvstore/MANIFEST.MF @@ -0,0 +1,23 @@ +Manifest-Version: 1.0 +Implementation-Title: H2 MVStore +Implementation-URL: https://h2database.com +Implementation-Version: ${version} +Build-Jdk: ${buildJdk} +Created-By: ${createdBy} +Automatic-Module-Name: com.h2database.mvstore +Bundle-Description: The MVStore is a persistent, log structured key-value store. +Bundle-DocURL: https://h2database.com/html/mvstore.html +Bundle-ManifestVersion: 2 +Bundle-Name: H2 MVStore +Bundle-SymbolicName: com.h2database.mvstore +Bundle-Vendor: H2 Group +Bundle-Version: ${version} +Bundle-License: https://h2database.com/html/license.html +Bundle-Category: utility +Multi-Release: true +Import-Package: javax.crypto, + javax.crypto.spec +Export-Package: org.h2.mvstore;version="${version}", + org.h2.mvstore.tx;version="${version}", + org.h2.mvstore.type;version="${version}", + org.h2.mvstore.rtree;version="${version}" diff --git a/h2/src/installer/openoffice.txt b/h2/src/installer/openoffice.txt new file mode 100644 index 0000000..dcd6d32 --- /dev/null +++ b/h2/src/installer/openoffice.txt @@ -0,0 +1,133 @@ +REM ***** BASIC ***** + +Sub Main + H2Pdf +End Sub + +sub H2Pdf + BaseDir = "file:///C:/data/h2database/" + REM BaseDir = "file:///Users/tmueller/data/" + + Url = BaseDir & "h2/docs/html/onePage.html" + dim FileProperties(1) As New com.sun.star.beans.PropertyValue + FileProperties(0).Name = "FilterName" + FileProperties(0).Value = "HTML (StarWriter)" + FileProperties(1).Name = "UpdateDocMode" + FileProperties(1).Value = 3 'full update + document = StarDesktop.loadComponentFromURL(Url, "_blank", 0, FileProperties) + docs = ThisComponent.CurrentController.Frame + dispatcher = createUnoService("com.sun.star.frame.DispatchHelper") + dispatcher.executeDispatch(docs, ".uno:UpdateAll", "", 0, Array()) + StyleFamilies = document.StyleFamilies + + ParagraphStyles = StyleFamilies.getByName("ParagraphStyles") + BodyStyle = ParagraphStyles.getByName("Text body") + BodyStyle.ParaOrphans = 2 + BodyStyle.ParaWidows = 2 + + HeadingStyle = ParagraphStyles.getByName("Heading 1") + HeadingStyle.BreakType = 3 ' Insert Page Break Before + HeadingStyle.ParaKeepTogether = false + + For i = 1 to 4 + ParagraphStyles.getByName("Heading " + i).OutlineLevel = i + Next + + images = document.GraphicObjects + For i = 0 to images.getCount() - 1 + image = images.getByIndex(i) + if image.Size.Width <> image.ActualSize.Width or image.Size.Height <> image.ActualSize.Height then + image.Size.Width = image.ActualSize.Width + image.Size.Height = image.ActualSize.Height + wait 100 + end if + Next + dispatcher.executeDispatch(docs, ".uno:UpdateAll", "", 0, Array()) + + PageStyles = StyleFamilies.getByName("PageStyles") + + Standard = PageStyles.getByName("HTML") + Standard.FooterIsOn = True + + dispatcher.executeDispatch(docs, ".uno:UpdateAll", "", 0, Array()) + + oText = Standard.FooterText + oText.setString("") + PageNumber = document.createInstance("com.sun.star.text.TextField.PageNumber") + PageNumber.NumberingType = 4 ' magic constant: 4=Arabic numbers + PageNumber.SubType = 1 ' magic constant: use current page number + + PageCount = document.createInstance("com.sun.star.text.TextField.PageCount") + PageCount.NumberingType = 4 ' magic constant: 4=Arabic numbers + + FooterCursor = oText.Text.createTextCursor() + oText.insertString(FooterCursor, Chr(09)& Chr(09), False) + oText.insertTextContent(FooterCursor, PageNumber, False) + oText.insertString(FooterCursor, " of ", False) + oText.insertTextContent(FooterCursor, PageCount, False) + + Cursor = document.Text.createTextCursor() + Cursor.gotoStart(false) + Cursor.gotoNextParagraph(false) + Cursor.gotoNextParagraph(false) + + dispatcher.executeDispatch(docs, ".uno:UpdateAll", "", 0, Array()) + + toc = document.createInstance("com.sun.star.text.ContentIndex") + toc.Title= "Table of Contents" + toc.CreateFromOutline = True + toc.Level = 4 + toc.IsProtected = false + document.Text.insertTextContent(Cursor, toc, false) + + Cursor.gotoStart(false) + result = true + while result + result = Cursor.gotoNextParagraph(false) + if Cursor.ParaStyleName = "Heading 1" then + Cursor.BreakType = 4 ' Insert Page Break After + end if + wend + + dim linkStart(0) As New com.sun.star.beans.PropertyValue + dim linkEnd(0) As New com.sun.star.beans.PropertyValue + + for i = 1 To 4 + oLevel = toc.LevelFormat.getByIndex(i) + bound = UBound(oLevel) + x = DimArray(bound + 2) + x(0) = linkStart + for j = 0 to bound + x(j + 1) = oLevel(j) + next + x(bound + 2) = linkEnd + linkStart(0).Name = "TokenType" + linkStart(0).Value = "TokenHyperlinkStart" + linkStart(0).Handle = -1 + linkStart(0).State = com.sun.star.beans.PropertyState.DIRECT_VALUE + linkEnd(0).Name = "TokenType" + linkEnd(0).Value = "TokenHyperlinkEnd" + linkEnd(0).Handle = -1 + linkEnd(0).State = com.sun.star.beans.PropertyState.DIRECT_VALUE + toc.LevelFormat.replaceByIndex(i, x) + next + + dispatcher.executeDispatch(docs, ".uno:UpdateAll", "", 0, Array()) + toc.update() + dispatcher.executeDispatch(docs, ".uno:UpdateAll", "", 0, Array()) + toc.update() + dispatcher.executeDispatch(docs, ".uno:UpdateAll", "", 0, Array()) + + pdfTemp = BaseDir & "h2web/h2temp.pdf" + pdf = BaseDir & "h2web/h2.pdf" + + dim pdfProperties(1) as new com.sun.star.beans.PropertyValue + pdfProperties(0).Name = "FilterName" + pdfProperties(0).Value = "writer_pdf_Export" + document.storeToURL(pdfTemp, pdfProperties()) + + fileAccessService = createUnoService("com.sun.star.ucb.SimpleFileAccess") + fileAccessService.move(pdfTemp, pdf) + + ThisComponent.close(true) +end sub diff --git a/h2/src/installer/pom-mvstore-template.xml b/h2/src/installer/pom-mvstore-template.xml new file mode 100644 index 0000000..2a2b2ce --- /dev/null +++ b/h2/src/installer/pom-mvstore-template.xml @@ -0,0 +1,35 @@ + + 4.0.0 + com.h2database + h2-mvstore + @version@ + jar + H2 MVStore + https://h2database.com/html/mvstore.html + H2 MVStore + + + MPL 2.0 + https://www.mozilla.org/en-US/MPL/2.0/ + repo + + + EPL 1.0 + https://opensource.org/licenses/eclipse-1.0.php + repo + + + + scm:git:https://github.com/h2database/h2database + https://github.com/h2database/h2database + + + + thomas.tom.mueller + Thomas Mueller + thomas.tom.mueller at gmail dot com + + + + + \ No newline at end of file diff --git a/h2/src/installer/pom-template.xml b/h2/src/installer/pom-template.xml new file mode 100644 index 0000000..132a1a8 --- /dev/null +++ b/h2/src/installer/pom-template.xml @@ -0,0 +1,35 @@ + + 4.0.0 + com.h2database + h2 + @version@ + jar + H2 Database Engine + https://h2database.com + H2 Database Engine + + + MPL 2.0 + https://www.mozilla.org/en-US/MPL/2.0/ + repo + + + EPL 1.0 + https://opensource.org/licenses/eclipse-1.0.php + repo + + + + scm:git:https://github.com/h2database/h2database + https://github.com/h2database/h2database + + + + thomas.tom.mueller + Thomas Mueller + thomas.tom.mueller at gmail dot com + + + + + \ No newline at end of file diff --git a/h2/src/installer/release.txt b/h2/src/installer/release.txt new file mode 100644 index 0000000..54bc012 --- /dev/null +++ b/h2/src/installer/release.txt @@ -0,0 +1,138 @@ +# Checklist for a release + +## Formatting, Spellchecking, Javadocs + + git pull + +Do this until there are no errors. +Fix typos, add new words to dictionary.txt: + + ./build.sh clean compile spellcheck + +Add documentation for all public methods. Make methods private if possible: + + ./build.sh clean compile javadocImpl + +Ensure lines are not overly long: + + ./build.sh clean compile docs + +## MVStore Jar File Size Verification + +To ensure the MVStore jar file is not too large +(does not reference the database code by accident). +The file size should be about 300 KB: + + ./build.sh jarMVStore + +## Changing Version Numbers + +Update org.h2.engine.Constants.java: + change the version and build number: + set BUILD_DATE to today + increment BUILD_ID, the value must be even (for example, 202) + set VERSION_MAJOR / VERSION_MINOR to the new version number + if the last TCP_PROTOCOL_VERSION_## + doesn't have a release date set it to current BUILD_DATE + check and update if necessary links to the latest releases in previous + series of releases and their checksums in download.html + +Update README.md. + set version to the new version + +Update changelog.html: + * create a new "Next Version (unreleased)" with an empty list + * add a new version + * remove change log entries of the oldest version (keeping about 500 lines) + +Update newsfeed.sql: + * add new version, for example: + * (150, '1.4.200', '2019-10-14'), + * remove oldest entry in that list + +Update download-archive.html: + * add new version under Distribution section + +## Skipped + +* Minor version change: change sourceError.html and source.html +* If a beta, change download.html: Version ${version} (${versionDate}), Beta +* If a beta, change mainWeb.html: Version ${version} (${versionDate}), Beta + +The following can be skipped currently; benchmarks should probably be removed: +* To update benchmark data: use latest versions of other dbs, change version(s) in performance.html + +## Build the Release + +In Build.java, comment "-Xdoclint:...", but don't commit that change. + +Run the following commands: +Non-Windows: + + cd src/installer + ./buildRelease.sh + +Windows: + + cd src/installer + buildRelease.bat + +Scan for viruses. + +Test installer, H2 Console (test new languages). + +Check docs, versions and links in main, downloads, build numbers. + +Check the PDF file size. + +Upload ( = httpdocs and httpsdocs) to ftp://h2database.com//javadoc +Upload ( = httpdocs and httpsdocs) to ftp://h2database.com// +Upload ( = httpdocs and httpsdocs) to ftp://h2database.com//m2-repo + +Github: create a release. + +Newsletter: send (always to BCC!), the following: + + h2-database@googlegroups.com; h2database-news@googlegroups.com; ... + +Create tweet at http://twitter.com + +## Sign files and publish files on Maven Central + +In Build.java, comment "-Xdoclint:none", but don't commit that change. + + ./build.sh clean compile jar mavenDeployCentral + cd /data/h2database/m2-repo/com/h2database + # remove sha and md5 files: + find . -name "*.sha1" -delete + find . -name "*.md5" -delete + cd h2/1 + # for each file separately (-javadoc.jar, -sources.jar, .jar, .pom): + gpg -u "Thomas Mueller Graf " -ab h2-<...> + jar -cvf bundle.jar h2-* + cd ../../h2-mvstore/1 + # for each file separately (-javadoc.jar, -sources.jar, .jar, .pom): + gpg -u "Thomas Mueller Graf " -ab h2-mvstore<...> + jar -cvf bundle.jar h2-* + # http://central.sonatype.org/pages/ossrh-guide.html + # http://central.sonatype.org/pages/manual-staging-bundle-creation-and-deployment.html + # https://oss.sonatype.org/#welcome - Log In "t..." + # sometimes this doesn't work reliably and you will have to retry + # - Staging Upload + # - Upload Mode: Artifact Bundle, Select Bundle to Upload... - /data/h2database/.../h2/.../bundle.jar + # - Upload Bundle + # - Staging Repositories - Refresh - select comh2database-<...> - Release - Confirm + # - Staging Upload + # - Upload Mode: Artifact Bundle, Select Bundle to Upload... - /data/h2database/.../h2-mvstore/.../bundle.jar + # - Upload Bundle + # - Staging Repositories - Refresh - select comh2database-<...> - Release - Confirm + +Update statistics. + +Change version in pom.xml, commit, add version-*.*.*** tag. + +Update org.h2.engine.Constants.java: + increment BUILD_ID again, the value must be odd (for example, 203) +Update h2/pom.xml. + set ...-SNAPSHOT to the next version (with this odd third number) +Commit. diff --git a/h2/src/installer/source-manifest.mf b/h2/src/installer/source-manifest.mf new file mode 100644 index 0000000..bb3c215 --- /dev/null +++ b/h2/src/installer/source-manifest.mf @@ -0,0 +1,7 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: H2 Database Engine Sources +Bundle-SymbolicName: com.h2database.source +Bundle-Vendor: H2 Group +Bundle-Version: ${version} +Eclipse-SourceBundle: com.h2database;version="${version}" diff --git a/h2/src/installer/source-mvstore-manifest.mf b/h2/src/installer/source-mvstore-manifest.mf new file mode 100644 index 0000000..48c8043 --- /dev/null +++ b/h2/src/installer/source-mvstore-manifest.mf @@ -0,0 +1,7 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: H2 MVStore Sources +Bundle-SymbolicName: com.h2database.mvstore.source +Bundle-Vendor: H2 Group +Bundle-Version: ${version} +Eclipse-SourceBundle: com.h2database.mvstore;version="${version}" diff --git a/h2/src/java10/precompiled/org/h2/util/Utils10.class b/h2/src/java10/precompiled/org/h2/util/Utils10.class new file mode 100644 index 0000000000000000000000000000000000000000..1ae38e89d7d2ea6aae364c60b84e4c8b35806c0f GIT binary patch literal 1133 zcma)5T~E_c7=8{Ll+BLs4;6>b58Q@QKt;_6Iu=M47?W&Z!j0*=5!DrXqpHZ8v69#yzg)+HF9ufTQNZgSmH z@dQs*Jj05L=Xk*|-Zd#7I`#)ytcqz|rz_j3X@|?2OR=;dAj3$PP^{FsAv!*&!};Px z@uZw*c$F>;gW^KOg-%QUSEYEp+R$X4 z(gHz<7oiF1L|nZpV$ZAw0Rv~<+%qJ){CDINMg%Hip!uWIEc)mL2Ga9wu#dE(%H^3> zAVIbx7$pm7J4dTThti+X*GA|E!fo`Q6Vl6B#26uA!p4a`fl1m)b{-*mflb%7 literal 0 HcmV?d00001 diff --git a/h2/src/java10/src/org/h2/util/Utils10.java b/h2/src/java10/src/org/h2/util/Utils10.java new file mode 100644 index 0000000..2ba397e --- /dev/null +++ b/h2/src/java10/src/org/h2/util/Utils10.java @@ -0,0 +1,72 @@ +/* + * 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.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.Socket; +import java.nio.charset.Charset; + +import jdk.net.ExtendedSocketOptions; + +/** + * Utilities with specialized implementations for Java 10 and later versions. + * + * This class contains implementations for Java 10 and later versions. + */ +public final class Utils10 { + + /** + * Converts the buffer's contents into a string by decoding the bytes using + * the specified {@link java.nio.charset.Charset charset}. + * + * @param baos + * the buffer to decode + * @param charset + * the charset to use + * @return the decoded string + */ + public static String byteArrayOutputStreamToString(ByteArrayOutputStream baos, Charset charset) { + return baos.toString(charset); + } + + /** + * Returns the value of TCP_QUICKACK option. + * + * @param socket + * the socket + * @return the current value of TCP_QUICKACK option + * @throws IOException + * on I/O exception + * @throws UnsupportedOperationException + * if TCP_QUICKACK is not supported + */ + public static boolean getTcpQuickack(Socket socket) throws IOException { + return socket.getOption(ExtendedSocketOptions.TCP_QUICKACK); + } + + /** + * Sets the value of TCP_QUICKACK option. + * + * @param socket + * the socket + * @param value + * the value to set + * @return whether operation was successful + */ + public static boolean setTcpQuickack(Socket socket, boolean value) { + try { + socket.setOption(ExtendedSocketOptions.TCP_QUICKACK, value); + return true; + } catch (Throwable t) { + return false; + } + } + + private Utils10() { + } + +} diff --git a/h2/src/java10/src/org/h2/util/package.html b/h2/src/java10/src/org/h2/util/package.html new file mode 100644 index 0000000..5860dd0 --- /dev/null +++ b/h2/src/java10/src/org/h2/util/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Internal utility classes reimplemented for Java 10 and later versions. + +

\ No newline at end of file diff --git a/h2/src/java9/precompiled/org/h2/util/Bits.class b/h2/src/java9/precompiled/org/h2/util/Bits.class new file mode 100644 index 0000000000000000000000000000000000000000..c5dabdfb8620d95dc47eb6d456b61ea850170f87 GIT binary patch literal 2361 zcmaJ>ZC4vr5Pp^fHdz)((@G1av1nTpN*jyT)&{9;LSbVFDgj@j#UrD3;jHX!=N zU*IqB7kKp8;yK9ic>L%Gzxwjsf1rOrow=Kr683mUvdC5m{9&acpV40;NFDra}lcl$Kd7F=`ER|SVQ?QPAczIVL-FvKipO>q!zk`!L%~NdSlX0Pkx`Xl%6K3lsuxQe>q{Hjf`o~}wy|rZ>xNxRTlTK=&`hrz z?vi0w>t?14Ef7>GtP~g9yv~6wYHnpsD=f5aPaWAhTyb_D8LnA$ykfInmmnw0*>X0e zO9*G3swrWlVA*D|xwC1yC1bNrnlr~at5(f6tAeSOHD-<;2dvqRHpDfJs&0D{29jk> zXKb{k;$$K$7aY6Rw&&?$ZJoF!A97Apq~W{B(G9y=1`RfwR<-14`<~fgNBMlJtWnlU zznV?Ys;Af1^jt=$BYUppnIb2;x~A)?bqd;6vcIS}_-P49o*{!Bt%xa3?aTx!$EcyF z^o$#rwQS3qlMqU>IWk+Rw-m~%({wB5J&P+G)hw@ZotrPCreX`1Wmqb<@sJNG6?I-N zAR%K%g^g(y&my7XA}==5DkfQa&Ogqmc%CH(85xgMd<<7bgO`_alhwC*nN#sP<|Xu% zbs3(DCU#})srUr@Dn7+7Tc{s>WvY10ack+VnY5@S=Sr&XsIF{on-%J@uM(Jf*ERMD zCATI+!l^Evif`6Tm#V{a(%hlZON!YTX`#CVG~Q+PJ_xF5&7`QI2Cu^c*y%ORBpH{T zhR4%tJ+LZfJo`tO&DF}g_(MP=44bFcHVC1hKpDjt5eZZsO1pb)A5|2zy8^iqi)zMm#; z(8p+kzHyoQFp2G&f?WsS!I3j@W5zXg`f8F5xmHT%pq>`sfElr>-7BIzr#q zbdc}@tpl_Jd4eaA3WA3M;P(kW6$}ptz+Hl0#dODe$WP_~{Zr=;G0>aL=LEkN1eXKh zUl9B{(kHz~7#{tm=c->4{KX)+5(xi_;5RUH()%F8)o**e9})beAox%q{AYsGgKl}( z&`&=g5sUp8xf4D>WGoy?9Aa2VvB*Ud$HI?0A>#;>{44eFH^lHe4a*+eE!=J~t`H+c3`O*2;t=sCooI^a+FykKoBpo`ai~E-oPQCe zG@`hJOeapi$erR)m-t>iain45j1Xrwh(oOk;#?q3f;g{X?qnS5SMm_2d*e(K=NfVD z263oqL7WV6W~mzV?QO-Gq@63>KlMG%^oE#2Sh|Y{v<}=6r3A9^XAUrWgtOnFpT%=R zl;dMqG88t@f*v literal 0 HcmV?d00001 diff --git a/h2/src/java9/src/org/h2/util/Bits.java b/h2/src/java9/src/org/h2/util/Bits.java new file mode 100644 index 0000000..fc323a8 --- /dev/null +++ b/h2/src/java9/src/org/h2/util/Bits.java @@ -0,0 +1,320 @@ +/* + * 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.util; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.UUID; + +/** + * Manipulations with bytes and arrays. Specialized implementation for Java 9 + * and later versions. + */ +public final class Bits { + + /** + * VarHandle giving access to elements of a byte[] array viewed as if it + * were a int[] array on big-endian system. + */ + private static final VarHandle INT_VH_BE = MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.BIG_ENDIAN); + + /** + * VarHandle giving access to elements of a byte[] array viewed as if it + * were a int[] array on little-endian system. + */ + private static final VarHandle INT_VH_LE = MethodHandles.byteArrayViewVarHandle(int[].class, + ByteOrder.LITTLE_ENDIAN); + + /** + * VarHandle giving access to elements of a byte[] array viewed as if it + * were a long[] array on big-endian system. + */ + private static final VarHandle LONG_VH_BE = MethodHandles.byteArrayViewVarHandle(long[].class, + ByteOrder.BIG_ENDIAN); + + /** + * VarHandle giving access to elements of a byte[] array viewed as if it + * were a long[] array on little-endian system. + */ + private static final VarHandle LONG_VH_LE = MethodHandles.byteArrayViewVarHandle(long[].class, + ByteOrder.LITTLE_ENDIAN); + + /** + * VarHandle giving access to elements of a byte[] array viewed as if it + * were a double[] array on big-endian system. + */ + private static final VarHandle DOUBLE_VH_BE = MethodHandles.byteArrayViewVarHandle(double[].class, + ByteOrder.BIG_ENDIAN); + + /** + * VarHandle giving access to elements of a byte[] array viewed as if it + * were a double[] array on little-endian system. + */ + private static final VarHandle DOUBLE_VH_LE = MethodHandles.byteArrayViewVarHandle(double[].class, + ByteOrder.LITTLE_ENDIAN); + + /** + * Compare the contents of two char arrays. If the content or length of the + * first array is smaller than the second array, -1 is returned. If the content + * or length of the second array is smaller than the first array, 1 is returned. + * If the contents and lengths are the same, 0 is returned. + * + * @param data1 + * the first char array (must not be null) + * @param data2 + * the second char array (must not be null) + * @return the result of the comparison (-1, 1 or 0) + */ + public static int compareNotNull(char[] data1, char[] data2) { + return Integer.signum(Arrays.compare(data1, data2)); + } + + /** + * Compare the contents of two byte arrays. If the content or length of the + * first array is smaller than the second array, -1 is returned. If the content + * or length of the second array is smaller than the first array, 1 is returned. + * If the contents and lengths are the same, 0 is returned. + * + *

+ * This method interprets bytes as signed. + *

+ * + * @param data1 + * the first byte array (must not be null) + * @param data2 + * the second byte array (must not be null) + * @return the result of the comparison (-1, 1 or 0) + */ + public static int compareNotNullSigned(byte[] data1, byte[] data2) { + return Integer.signum(Arrays.compare(data1, data2)); + } + + /** + * Compare the contents of two byte arrays. If the content or length of the + * first array is smaller than the second array, -1 is returned. If the content + * or length of the second array is smaller than the first array, 1 is returned. + * If the contents and lengths are the same, 0 is returned. + * + *

+ * This method interprets bytes as unsigned. + *

+ * + * @param data1 + * the first byte array (must not be null) + * @param data2 + * the second byte array (must not be null) + * @return the result of the comparison (-1, 1 or 0) + */ + public static int compareNotNullUnsigned(byte[] data1, byte[] data2) { + return Integer.signum(Arrays.compareUnsigned(data1, data2)); + } + + /** + * Reads a int value from the byte array at the given position in big-endian + * order. + * + * @param buff + * the byte array + * @param pos + * the position + * @return the value + */ + public static int readInt(byte[] buff, int pos) { + return (int) INT_VH_BE.get(buff, pos); + } + + /** + * Reads a int value from the byte array at the given position in + * little-endian order. + * + * @param buff + * the byte array + * @param pos + * the position + * @return the value + */ + public static int readIntLE(byte[] buff, int pos) { + return (int) INT_VH_LE.get(buff, pos); + } + + /** + * Reads a long value from the byte array at the given position in + * big-endian order. + * + * @param buff + * the byte array + * @param pos + * the position + * @return the value + */ + public static long readLong(byte[] buff, int pos) { + return (long) LONG_VH_BE.get(buff, pos); + } + + /** + * Reads a long value from the byte array at the given position in + * little-endian order. + * + * @param buff + * the byte array + * @param pos + * the position + * @return the value + */ + public static long readLongLE(byte[] buff, int pos) { + return (long) LONG_VH_LE.get(buff, pos); + } + + /** + * Reads a double value from the byte array at the given position in + * big-endian order. + * + * @param buff + * the byte array + * @param pos + * the position + * @return the value + */ + public static double readDouble(byte[] buff, int pos) { + return (double) DOUBLE_VH_BE.get(buff, pos); + } + + /** + * Reads a double value from the byte array at the given position in + * little-endian order. + * + * @param buff + * the byte array + * @param pos + * the position + * @return the value + */ + public static double readDoubleLE(byte[] buff, int pos) { + return (double) DOUBLE_VH_LE.get(buff, pos); + } + + /** + * Converts UUID value to byte array in big-endian order. + * + * @param msb + * most significant part of UUID + * @param lsb + * least significant part of UUID + * @return byte array representation + */ + public static byte[] uuidToBytes(long msb, long lsb) { + byte[] buff = new byte[16]; + LONG_VH_BE.set(buff, 0, msb); + LONG_VH_BE.set(buff, 8, lsb); + return buff; + } + + /** + * Converts UUID value to byte array in big-endian order. + * + * @param uuid + * UUID value + * @return byte array representation + */ + public static byte[] uuidToBytes(UUID uuid) { + return uuidToBytes(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits()); + } + + /** + * Writes a int value to the byte array at the given position in big-endian + * order. + * + * @param buff + * the byte array + * @param pos + * the position + * @param x + * the value to write + */ + public static void writeInt(byte[] buff, int pos, int x) { + INT_VH_BE.set(buff, pos, x); + } + + /** + * Writes a int value to the byte array at the given position in + * little-endian order. + * + * @param buff + * the byte array + * @param pos + * the position + * @param x + * the value to write + */ + public static void writeIntLE(byte[] buff, int pos, int x) { + INT_VH_LE.set(buff, pos, x); + } + + /** + * Writes a long value to the byte array at the given position in big-endian + * order. + * + * @param buff + * the byte array + * @param pos + * the position + * @param x + * the value to write + */ + public static void writeLong(byte[] buff, int pos, long x) { + LONG_VH_BE.set(buff, pos, x); + } + + /** + * Writes a long value to the byte array at the given position in + * little-endian order. + * + * @param buff + * the byte array + * @param pos + * the position + * @param x + * the value to write + */ + public static void writeLongLE(byte[] buff, int pos, long x) { + LONG_VH_LE.set(buff, pos, x); + } + + /** + * Writes a double value to the byte array at the given position in + * big-endian order. + * + * @param buff + * the byte array + * @param pos + * the position + * @param x + * the value to write + */ + public static void writeDouble(byte[] buff, int pos, double x) { + DOUBLE_VH_BE.set(buff, pos, x); + } + + /** + * Writes a double value to the byte array at the given position in + * little-endian order. + * + * @param buff + * the byte array + * @param pos + * the position + * @param x + * the value to write + */ + public static void writeDoubleLE(byte[] buff, int pos, double x) { + DOUBLE_VH_LE.set(buff, pos, x); + } + + private Bits() { + } +} diff --git a/h2/src/java9/src/org/h2/util/package.html b/h2/src/java9/src/org/h2/util/package.html new file mode 100644 index 0000000..9ef3d9c --- /dev/null +++ b/h2/src/java9/src/org/h2/util/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Internal utility classes reimplemented for Java 9 and later versions. + +

\ No newline at end of file diff --git a/h2/src/main/META-INF/MANIFEST.MF b/h2/src/main/META-INF/MANIFEST.MF new file mode 100644 index 0000000..c4a0ae3 --- /dev/null +++ b/h2/src/main/META-INF/MANIFEST.MF @@ -0,0 +1,75 @@ +Manifest-Version: 1.0 +Implementation-Title: H2 Database Engine +Implementation-URL: https://h2database.com +Implementation-Version: ${version} +Build-Jdk: ${buildJdk} +Created-By: ${createdBy} +Main-Class: org.h2.tools.Console +Automatic-Module-Name: com.h2database +Bundle-Activator: org.h2.util.DbDriverActivator +Bundle-ManifestVersion: 2 +Bundle-Name: H2 Database Engine +Bundle-SymbolicName: com.h2database +Bundle-Vendor: H2 Group +Bundle-Version: ${version} +Bundle-License: https://h2database.com/html/license.html +Bundle-Category: jdbc +Multi-Release: true +Import-Package: javax.crypto, + javax.crypto.spec, + javax.management, + javax.naming;resolution:=optional, + javax.naming.directory;resolution:=optional, + javax.naming.spi;resolution:=optional, + javax.net, + javax.net.ssl, + javax.script;resolution:=optional, + javax.security.auth.callback;resolution:=optional, + javax.security.auth.login;resolution:=optional, + javax.servlet;resolution:=optional, + javax.servlet.http;resolution:=optional, + jakarta.servlet;resolution:=optional, + jakarta.servlet.http;resolution:=optional, + javax.sql, + javax.tools;resolution:=optional, + javax.transaction.xa;resolution:=optional, + javax.xml.parsers;resolution:=optional, + javax.xml.stream;resolution:=optional, + javax.xml.transform;resolution:=optional, + javax.xml.transform.dom;resolution:=optional, + javax.xml.transform.sax;resolution:=optional, + javax.xml.transform.stax;resolution:=optional, + javax.xml.transform.stream;resolution:=optional, + org.w3c.dom;resolution:=optional, + org.xml.sax;resolution:=optional, + org.xml.sax.helpers;resolution:=optional, + org.apache.lucene.analysis;version="[8.5.2,9.0.0)";resolution:=optional, + org.apache.lucene.analysis.standard;version="[8.5.2,9.0.0)";resolution:=optional, + org.apache.lucene.document;version="[8.5.2,9.0.0)";resolution:=optional, + org.apache.lucene.index;version="[8.5.2,9.0.0)";resolution:=optional, + org.apache.lucene.queryparser;version="[8.5.2,9.0.0)";resolution:=optional, + org.apache.lucene.search;version="[8.5.2,9.0.0)";resolution:=optional, + org.apache.lucene.store;version="[8.5.2,9.0.0)";resolution:=optional, + org.apache.lucene.util;version="[8.5.2,9.0.0)";resolution:=optional, + org.locationtech.jts.geom;version="1.17.0";resolution:=optional, + org.osgi.framework;version="1.5", + org.osgi.service.jdbc;version="1.0";resolution:=optional, + org.slf4j;version="[1.7.0,1.8.0)";resolution:=optional +Export-Package: org.h2;version="${version}", + org.h2.api;version="${version}", + org.h2.constant;version="${version}", + org.h2.fulltext;version="${version}", + org.h2.jdbc;version="${version}", + org.h2.jdbcx;version="${version}", + org.h2.tools;version="${version}", + org.h2.util;version="${version}", + org.h2.value;version="${version}", + org.h2.bnf;version="${version}", + org.h2.bnf.context;version="${version}", + org.h2.mvstore;version="${version}", + org.h2.mvstore.tx;version="${version}", + org.h2.mvstore.type;version="${version}", + org.h2.mvstore.rtree;version="${version}", + org.h2.store.fs;version="${version}" +Provide-Capability: osgi.service;objectClass:List=org.osgi.service.jdbc.DataSourceFactory +Premain-Class: org.h2.util.Profiler diff --git a/h2/src/main/META-INF/services/java.sql.Driver b/h2/src/main/META-INF/services/java.sql.Driver new file mode 100644 index 0000000..679185a --- /dev/null +++ b/h2/src/main/META-INF/services/java.sql.Driver @@ -0,0 +1 @@ +org.h2.Driver \ No newline at end of file diff --git a/h2/src/main/org/h2/Driver.java b/h2/src/main/org/h2/Driver.java new file mode 100644 index 0000000..a0660fc --- /dev/null +++ b/h2/src/main/org/h2/Driver.java @@ -0,0 +1,204 @@ +/* + * 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; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.DriverPropertyInfo; +import java.sql.SQLException; +import java.util.Properties; +import java.util.logging.Logger; +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.jdbc.JdbcConnection; +import org.h2.message.DbException; + +/** + * The database driver. An application should not use this class directly. The + * only thing the application needs to do is load the driver. This can be done + * using Class.forName. To load the driver and open a database connection, use + * the following code: + * + *
+ * Class.forName("org.h2.Driver");
+ * Connection conn = DriverManager.getConnection(
+ *      "jdbc:h2:˜/test", "sa", "sa");
+ * 
+ */ +public class Driver implements java.sql.Driver, JdbcDriverBackwardsCompat { + + private static final Driver INSTANCE = new Driver(); + private static final String DEFAULT_URL = "jdbc:default:connection"; + private static final ThreadLocal DEFAULT_CONNECTION = + new ThreadLocal<>(); + + private static boolean registered; + + static { + load(); + } + + /** + * Open a database connection. + * This method should not be called by an application. + * Instead, the method DriverManager.getConnection should be used. + * + * @param url the database URL + * @param info the connection properties + * @return the new connection or null if the URL is not supported + * @throws SQLException on connection exception or if URL is {@code null} + */ + @Override + public Connection connect(String url, Properties info) throws SQLException { + if (url == null) { + throw DbException.getJdbcSQLException(ErrorCode.URL_FORMAT_ERROR_2, null, Constants.URL_FORMAT, null); + } else if (url.startsWith(Constants.START_URL)) { + return new JdbcConnection(url, info, null, null, false); + } else if (url.equals(DEFAULT_URL)) { + return DEFAULT_CONNECTION.get(); + } else { + return null; + } + } + + /** + * Check if the driver understands this URL. + * This method should not be called by an application. + * + * @param url the database URL + * @return if the driver understands the URL + * @throws SQLException if URL is {@code null} + */ + @Override + public boolean acceptsURL(String url) throws SQLException { + if (url == null) { + throw DbException.getJdbcSQLException(ErrorCode.URL_FORMAT_ERROR_2, null, Constants.URL_FORMAT, null); + } else if (url.startsWith(Constants.START_URL)) { + return true; + } else if (url.equals(DEFAULT_URL)) { + return DEFAULT_CONNECTION.get() != null; + } else { + return false; + } + } + + /** + * Get the major version number of the driver. + * This method should not be called by an application. + * + * @return the major version number + */ + @Override + public int getMajorVersion() { + return Constants.VERSION_MAJOR; + } + + /** + * Get the minor version number of the driver. + * This method should not be called by an application. + * + * @return the minor version number + */ + @Override + public int getMinorVersion() { + return Constants.VERSION_MINOR; + } + + /** + * Get the list of supported properties. + * This method should not be called by an application. + * + * @param url the database URL + * @param info the connection properties + * @return a zero length array + */ + @Override + public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) { + return new DriverPropertyInfo[0]; + } + + /** + * Check if this driver is compliant to the JDBC specification. + * This method should not be called by an application. + * + * @return true + */ + @Override + public boolean jdbcCompliant() { + return true; + } + + /** + * [Not supported] + */ + @Override + public Logger getParentLogger() { + return null; + } + + /** + * INTERNAL + * @return instance of the driver registered with the DriverManager + */ + public static synchronized Driver load() { + try { + if (!registered) { + registered = true; + DriverManager.registerDriver(INSTANCE); + } + } catch (SQLException e) { + DbException.traceThrowable(e); + } + return INSTANCE; + } + + /** + * INTERNAL + */ + public static synchronized void unload() { + try { + if (registered) { + registered = false; + DriverManager.deregisterDriver(INSTANCE); + } + } catch (SQLException e) { + DbException.traceThrowable(e); + } + } + + /** + * INTERNAL + * Sets, on a per-thread basis, the default-connection for + * user-defined functions. + * @param c to set default to + */ + public static void setDefaultConnection(Connection c) { + if (c == null) { + DEFAULT_CONNECTION.remove(); + } else { + DEFAULT_CONNECTION.set(c); + } + } + + /** + * INTERNAL + * @param thread to set context class loader for + */ + public static void setThreadContextClassLoader(Thread thread) { + // Apache Tomcat: use the classloader of the driver to avoid the + // following log message: + // org.apache.catalina.loader.WebappClassLoader clearReferencesThreads + // SEVERE: The web application appears to have started a thread named + // ... but has failed to stop it. + // This is very likely to create a memory leak. + try { + thread.setContextClassLoader(Driver.class.getClassLoader()); + } catch (Throwable t) { + // ignore + } + } + +} diff --git a/h2/src/main/org/h2/JdbcDriverBackwardsCompat.java b/h2/src/main/org/h2/JdbcDriverBackwardsCompat.java new file mode 100644 index 0000000..4d033fd --- /dev/null +++ b/h2/src/main/org/h2/JdbcDriverBackwardsCompat.java @@ -0,0 +1,16 @@ +/* + * 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; + +/** + * Allows us to compile on older platforms, while still implementing the methods + * from the newer JDBC API. + */ +public interface JdbcDriverBackwardsCompat { + + // compatibility interface + +} diff --git a/h2/src/main/org/h2/api/Aggregate.java b/h2/src/main/org/h2/api/Aggregate.java new file mode 100644 index 0000000..6169d0c --- /dev/null +++ b/h2/src/main/org/h2/api/Aggregate.java @@ -0,0 +1,60 @@ +/* + * 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.api; + +import java.sql.Connection; +import java.sql.SQLException; + +/** + * A user-defined aggregate function needs to implement this interface. + * The class must be public and must have a public non-argument constructor. + */ +public interface Aggregate { + + /** + * This method is called when the aggregate function is used. + * A new object is created for each invocation. + * + * @param conn a connection to the database + * @throws SQLException on SQL exception + */ + default void init(Connection conn) throws SQLException { + // Do nothing by default + } + + /** + * This method must return the H2 data type, {@link org.h2.value.Value}, + * of the aggregate function, given the H2 data type of the input data. + * The method should check here if the number of parameters + * passed is correct, and if not it should throw an exception. + * + * @param inputTypes the H2 data type of the parameters, + * @return the H2 data type of the result + * @throws SQLException if the number/type of parameters passed is incorrect + */ + int getInternalType(int[] inputTypes) throws SQLException; + + /** + * This method is called once for each row. + * If the aggregate function is called with multiple parameters, + * those are passed as array. + * + * @param value the value(s) for this row + * @throws SQLException on failure + */ + void add(Object value) throws SQLException; + + /** + * This method returns the computed aggregate value. This method must + * preserve previously added values and must be able to reevaluate result if + * more values were added since its previous invocation. + * + * @return the aggregated value + * @throws SQLException on failure + */ + Object getResult() throws SQLException; + +} \ No newline at end of file diff --git a/h2/src/main/org/h2/api/AggregateFunction.java b/h2/src/main/org/h2/api/AggregateFunction.java new file mode 100644 index 0000000..916853e --- /dev/null +++ b/h2/src/main/org/h2/api/AggregateFunction.java @@ -0,0 +1,64 @@ +/* + * 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.api; + +import java.sql.Connection; +import java.sql.SQLException; + +/** + * A user-defined aggregate function needs to implement this interface. + * The class must be public and must have a public non-argument constructor. + *

+ * Please note this interface only has limited support for data types. + * If you need data types that don't have a corresponding SQL type + * (for example GEOMETRY), then use the {@link Aggregate} interface. + *

+ */ +public interface AggregateFunction { + + /** + * This method is called when the aggregate function is used. + * A new object is created for each invocation. + * + * @param conn a connection to the database + * @throws SQLException on SQL exception + */ + default void init(Connection conn) throws SQLException { + // Do nothing by default + } + + /** + * This method must return the SQL type of the method, given the SQL type of + * the input data. The method should check here if the number of parameters + * passed is correct, and if not it should throw an exception. + * + * @param inputTypes the SQL type of the parameters, {@link java.sql.Types} + * @return the SQL type of the result + * @throws SQLException on failure + */ + int getType(int[] inputTypes) throws SQLException; + + /** + * This method is called once for each row. + * If the aggregate function is called with multiple parameters, + * those are passed as array. + * + * @param value the value(s) for this row + * @throws SQLException on failure + */ + void add(Object value) throws SQLException; + + /** + * This method returns the computed aggregate value. This method must + * preserve previously added values and must be able to reevaluate result if + * more values were added since its previous invocation. + * + * @return the aggregated value + * @throws SQLException on failure + */ + Object getResult() throws SQLException; + +} \ No newline at end of file diff --git a/h2/src/main/org/h2/api/CredentialsValidator.java b/h2/src/main/org/h2/api/CredentialsValidator.java new file mode 100644 index 0000000..79dae86 --- /dev/null +++ b/h2/src/main/org/h2/api/CredentialsValidator.java @@ -0,0 +1,32 @@ +/* + * 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: Alessandro Ventura + */ +package org.h2.api; + +import org.h2.security.auth.AuthenticationInfo; +import org.h2.security.auth.Configurable; + +/** + * A class that implement this interface can be used to validate credentials + * provided by client. + *

+ * This feature is experimental and subject to change + *

+ */ +public interface CredentialsValidator extends Configurable { + + /** + * Validate user credential. + * + * @param authenticationInfo + * = authentication info + * @return true if credentials are valid, otherwise false + * @throws Exception + * any exception occurred (invalid credentials or internal + * issue) prevent user login + */ + boolean validateCredentials(AuthenticationInfo authenticationInfo) throws Exception; + +} diff --git a/h2/src/main/org/h2/api/DatabaseEventListener.java b/h2/src/main/org/h2/api/DatabaseEventListener.java new file mode 100644 index 0000000..67f3c8e --- /dev/null +++ b/h2/src/main/org/h2/api/DatabaseEventListener.java @@ -0,0 +1,112 @@ +/* + * 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.api; + +import java.sql.SQLException; +import java.util.EventListener; + +/** + * A class that implements this interface can get notified about exceptions + * and other events. A database event listener can be registered when + * connecting to a database. Example database URL: + * jdbc:h2:./test;DATABASE_EVENT_LISTENER='com.acme.DbListener' + */ +public interface DatabaseEventListener extends EventListener { + + /** + * This state is used when scanning the database file. + */ + int STATE_SCAN_FILE = 0; + + /** + * This state is used when re-creating an index. + */ + int STATE_CREATE_INDEX = 1; + + /** + * This state is used when re-applying the transaction log or rolling back + * uncommitted transactions. + */ + int STATE_RECOVER = 2; + + /** + * This state is used during the BACKUP command. + */ + int STATE_BACKUP_FILE = 3; + + /** + * This state is used after re-connecting to a database (if auto-reconnect + * is enabled). + */ + int STATE_RECONNECTED = 4; + + /** + * This state is used when a query starts. + */ + int STATE_STATEMENT_START = 5; + + /** + * This state is used when a query ends. + */ + int STATE_STATEMENT_END = 6; + + /** + * This state is used for periodic notification during long-running queries. + */ + int STATE_STATEMENT_PROGRESS = 7; + + /** + * This method is called just after creating the object. + * This is done when opening the database if the listener is specified + * in the database URL, but may be later if the listener is set at + * runtime with the SET SQL statement. + * + * @param url - the database URL + */ + default void init(String url) { + } + + /** + * This method is called after the database has been opened. It is safe to + * connect to the database and execute statements at this point. + */ + default void opened() { + } + + /** + * This method is called if an exception occurred. + * + * @param e the exception + * @param sql the SQL statement + */ + default void exceptionThrown(SQLException e, String sql) { + } + + /** + * This method is called for long running events, such as recovering, + * scanning a file or building an index. + *

+ * More states might be added in future versions, therefore implementations + * should silently ignore states that they don't understand. + *

+ * + * @param state the state + * @param name the object name + * @param x the current position + * @param max the highest possible value or 0 if unknown + */ + default void setProgress(int state, String name, long x, long max) { + } + + /** + * This method is called before the database is closed normally. It is safe + * to connect to the database and execute statements at this point, however + * the connection must be closed before the method returns. + */ + default void closingDatabase() { + } + +} diff --git a/h2/src/main/org/h2/api/ErrorCode.java b/h2/src/main/org/h2/api/ErrorCode.java new file mode 100644 index 0000000..ac92a50 --- /dev/null +++ b/h2/src/main/org/h2/api/ErrorCode.java @@ -0,0 +1,2331 @@ +/* + * 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.api; + +/** + * This class defines the error codes used for SQL exceptions. + * Error messages are formatted as follows: + *
+ * { error message (possibly translated; may include quoted data) }
+ * { error message in English if different }
+ * { SQL statement if applicable }
+ * { [ error code - build number ] }
+ * 
+ * Example: + *
+ * Syntax error in SQL statement "SELECT * FORM[*] TEST ";
+ * SQL statement: select * form test [42000-125]
+ * 
+ * The [*] marks the position of the syntax error + * (FORM instead of FROM in this case). + * The error code is 42000, and the build number is 125, + * meaning version 1.2.125. + */ +public class ErrorCode { + + // 02: no data + + /** + * The error with code 2000 is thrown when + * the result set is positioned before the first or after the last row, or + * not on a valid row for the given operation. + * Example of wrong usage: + *
+     * ResultSet rs = stat.executeQuery("SELECT * FROM DUAL");
+     * rs.getString(1);
+     * 
+ * Correct: + *
+     * ResultSet rs = stat.executeQuery("SELECT * FROM DUAL");
+     * rs.next();
+     * rs.getString(1);
+     * 
+ */ + public static final int NO_DATA_AVAILABLE = 2000; + + // 07: dynamic SQL error + + /** + * The error with code 7001 is thrown when + * trying to call a function with the wrong number of parameters. + * Example: + *
+     * CALL ABS(1, 2)
+     * 
+ */ + public static final int INVALID_PARAMETER_COUNT_2 = 7001; + + // 08: connection exception + + /** + * The error with code 8000 is thrown when + * there was a problem trying to create a database lock. + * See the message and cause for details. + */ + public static final int ERROR_OPENING_DATABASE_1 = 8000; + + // 21: cardinality violation + + /** + * The error with code 21002 is thrown when the number of + * columns does not match. Possible reasons are: for an INSERT or MERGE + * statement, the column count does not match the table or the column list + * specified. For a SELECT UNION statement, both queries return a different + * number of columns. For a constraint, the number of referenced and + * referencing columns does not match. Example: + *
+     * CREATE TABLE TEST(ID INT, NAME VARCHAR);
+     * INSERT INTO TEST VALUES('Hello');
+     * 
+ */ + public static final int COLUMN_COUNT_DOES_NOT_MATCH = 21002; + + // 22: data exception + + /** + * The error with code 22001 is thrown when + * trying to insert a value that is too long for the column. + * Example: + *
+     * CREATE TABLE TEST(ID INT, NAME VARCHAR(2));
+     * INSERT INTO TEST VALUES(1, 'Hello');
+     * 
+ */ + public static final int VALUE_TOO_LONG_2 = 22001; + + /** + * The error with code 22003 is thrown when a value is out of + * range when converting to another data type. Example: + *
+     * CALL CAST(1000000 AS TINYINT);
+     * SELECT CAST(124.34 AS DECIMAL(2, 2));
+     * 
+ */ + public static final int NUMERIC_VALUE_OUT_OF_RANGE_1 = 22003; + + /** + * The error with code 22004 is thrown when a value is out of + * range when converting to another column's data type. + */ + public static final int NUMERIC_VALUE_OUT_OF_RANGE_2 = 22004; + + /** + * The error with code 22007 is thrown when + * a text can not be converted to a date, time, or timestamp constant. + * Examples: + *
+     * CALL DATE '2007-January-01';
+     * CALL TIME '14:61:00';
+     * CALL TIMESTAMP '2001-02-30 12:00:00';
+     * 
+ */ + public static final int INVALID_DATETIME_CONSTANT_2 = 22007; + + /** + * The error with code 22012 is thrown when trying to divide + * a value by zero. Example: + *
+     * CALL 1/0;
+     * 
+ */ + public static final int DIVISION_BY_ZERO_1 = 22012; + + /** + * The error with code 22013 is thrown when preceding or + * following size in a window function is null or negative. Example: + *
+     * FIRST_VALUE(N) OVER(ORDER BY N ROWS -1 PRECEDING)
+     * 
+ */ + public static final int INVALID_PRECEDING_OR_FOLLOWING_1 = 22013; + + /** + * The error with code 22018 is thrown when + * trying to convert a value to a data type where the conversion is + * undefined, or when an error occurred trying to convert. Example: + *
+     * CALL CAST(DATE '2001-01-01' AS BOOLEAN);
+     * CALL CAST('CHF 99.95' AS INT);
+     * 
+ */ + public static final int DATA_CONVERSION_ERROR_1 = 22018; + + /** + * The error with code 22025 is thrown when using an invalid + * escape character sequence for LIKE or REGEXP. The default escape + * character is '\'. The escape character is required when searching for + * the characters '%', '_' and the escape character itself. That means if + * you want to search for the text '10%', you need to use LIKE '10\%'. If + * you want to search for 'C:\temp' you need to use 'C:\\temp'. The escape + * character can be changed using the ESCAPE clause as in LIKE '10+%' ESCAPE + * '+'. Example of wrong usage: + *
+     * CALL 'C:\temp' LIKE 'C:\temp';
+     * CALL '1+1' LIKE '1+1' ESCAPE '+';
+     * 
+ * Correct: + *
+     * CALL 'C:\temp' LIKE 'C:\\temp';
+     * CALL '1+1' LIKE '1++1' ESCAPE '+';
+     * 
+ */ + public static final int LIKE_ESCAPE_ERROR_1 = 22025; + + /** + * The error with code 22030 is thrown when + * an attempt is made to INSERT or UPDATE an ENUM-typed cell, + * but the value is not one of the values enumerated by the + * type. + * + * Example: + *
+     * CREATE TABLE TEST(CASE ENUM('sensitive','insensitive'));
+     * INSERT INTO TEST VALUES('snake');
+     * 
+ */ + public static final int ENUM_VALUE_NOT_PERMITTED = 22030; + + /** + * The error with code 22032 is thrown when an + * attempt is made to add or modify an ENUM-typed column so + * that one or more of its enumerators would be empty. + * + * Example: + *
+     * CREATE TABLE TEST(CASE ENUM(' '));
+     * 
+ */ + public static final int ENUM_EMPTY = 22032; + + /** + * The error with code 22033 is thrown when an + * attempt is made to add or modify an ENUM-typed column so + * that it would have duplicate values. + * + * Example: + *
+     * CREATE TABLE TEST(CASE ENUM('sensitive', 'sensitive'));
+     * 
+ */ + public static final int ENUM_DUPLICATE = 22033; + + /** + * The error with code 22034 is thrown when an + * attempt is made to read non-existing element of an array. + * + * Example: + *
+     * VALUES ARRAY[1, 2][3]
+     * 
+ */ + public static final int ARRAY_ELEMENT_ERROR_2 = 22034; + + // 23: constraint violation + + /** + * The error with code 23502 is thrown when + * trying to insert NULL into a column that does not allow NULL. + * Example: + *
+     * CREATE TABLE TEST(ID INT, NAME VARCHAR NOT NULL);
+     * INSERT INTO TEST(ID) VALUES(1);
+     * 
+ */ + public static final int NULL_NOT_ALLOWED = 23502; + + /** + * The error with code 23503 is thrown when trying to delete + * or update a row when this would violate a referential constraint, because + * there is a child row that would become an orphan. Example: + *
+     * CREATE TABLE TEST(ID INT PRIMARY KEY, PARENT INT);
+     * INSERT INTO TEST VALUES(1, 1), (2, 1);
+     * ALTER TABLE TEST ADD CONSTRAINT TEST_ID_PARENT
+     *       FOREIGN KEY(PARENT) REFERENCES TEST(ID) ON DELETE RESTRICT;
+     * DELETE FROM TEST WHERE ID = 1;
+     * 
+ */ + public static final int REFERENTIAL_INTEGRITY_VIOLATED_CHILD_EXISTS_1 = 23503; + + /** + * The error with code 23505 is thrown when trying to insert + * a row that would violate a unique index or primary key. Example: + *
+     * CREATE TABLE TEST(ID INT PRIMARY KEY);
+     * INSERT INTO TEST VALUES(1);
+     * INSERT INTO TEST VALUES(1);
+     * 
+ */ + public static final int DUPLICATE_KEY_1 = 23505; + + /** + * The error with code 23506 is thrown when trying to insert + * or update a row that would violate a referential constraint, because the + * referenced row does not exist. Example: + *
+     * CREATE TABLE PARENT(ID INT PRIMARY KEY);
+     * CREATE TABLE CHILD(P_ID INT REFERENCES PARENT(ID));
+     * INSERT INTO CHILD VALUES(1);
+     * 
+ */ + public static final int REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1 = 23506; + + /** + * The error with code 23507 is thrown when + * updating or deleting from a table with a foreign key constraint + * that should set the default value, but there is no default value defined. + * Example: + *
+     * CREATE TABLE TEST(ID INT PRIMARY KEY, PARENT INT);
+     * INSERT INTO TEST VALUES(1, 1), (2, 1);
+     * ALTER TABLE TEST ADD CONSTRAINT TEST_ID_PARENT
+     *   FOREIGN KEY(PARENT) REFERENCES TEST(ID) ON DELETE SET DEFAULT;
+     * DELETE FROM TEST WHERE ID = 1;
+     * 
+ */ + public static final int NO_DEFAULT_SET_1 = 23507; + + /** + * The error with code 23513 is thrown when + * a check constraint is violated. Example: + *
+     * CREATE TABLE TEST(ID INT CHECK (ID>0));
+     * INSERT INTO TEST VALUES(0);
+     * 
+ */ + public static final int CHECK_CONSTRAINT_VIOLATED_1 = 23513; + + /** + * The error with code 23514 is thrown when + * evaluation of a check constraint resulted in an error. + */ + public static final int CHECK_CONSTRAINT_INVALID = 23514; + + // 28: invalid authorization specification + + /** + * The error with code 28000 is thrown when + * there is no such user registered in the database, when the user password + * does not match, or when the database encryption password does not match + * (if database encryption is used). + */ + public static final int WRONG_USER_OR_PASSWORD = 28000; + + // 3B: savepoint exception + + /** + * The error with code 40001 is thrown when + * the database engine has detected a deadlock. The transaction of this + * session has been rolled back to solve the problem. A deadlock occurs when + * a session tries to lock a table another session has locked, while the + * other session wants to lock a table the first session has locked. As an + * example, session 1 has locked table A, while session 2 has locked table + * B. If session 1 now tries to lock table B and session 2 tries to lock + * table A, a deadlock has occurred. Deadlocks that involve more than two + * sessions are also possible. To solve deadlock problems, an application + * should lock tables always in the same order, such as always lock table A + * before locking table B. For details, see Wikipedia Deadlock. + */ + public static final int DEADLOCK_1 = 40001; + + // 42: syntax error or access rule violation + + /** + * The error with code 42000 is thrown when + * trying to execute an invalid SQL statement. + * Example: + *
+     * CREATE ALIAS REMAINDER FOR "IEEEremainder";
+     * 
+ */ + public static final int SYNTAX_ERROR_1 = 42000; + + /** + * The error with code 42001 is thrown when + * trying to execute an invalid SQL statement. + * Example: + *
+     * CREATE TABLE TEST(ID INT);
+     * INSERT INTO TEST(1);
+     * 
+ */ + public static final int SYNTAX_ERROR_2 = 42001; + + /** + * The error with code 42101 is thrown when + * trying to create a table or view if an object with this name already + * exists. Example: + *
+     * CREATE TABLE TEST(ID INT);
+     * CREATE TABLE TEST(ID INT PRIMARY KEY);
+     * 
+ */ + public static final int TABLE_OR_VIEW_ALREADY_EXISTS_1 = 42101; + + /** + * The error with code 42102 is thrown when + * trying to query, modify or drop a table or view that does not exists + * in this schema and database. A common cause is that the wrong + * database was opened. + * Example: + *
+     * SELECT * FROM ABC;
+     * 
+ */ + public static final int TABLE_OR_VIEW_NOT_FOUND_1 = 42102; + + /** + * The error with code 42103 is thrown when + * trying to query, modify or drop a table or view that does not exists + * in this schema and database but similar names were found. A common cause + * is that the names are written in different case. + * Example: + *
+     * SELECT * FROM ABC;
+     * 
+ */ + public static final int TABLE_OR_VIEW_NOT_FOUND_WITH_CANDIDATES_2 = 42103; + + /** + * The error with code 42104 is thrown when + * trying to query, modify or drop a table or view that does not exists + * in this schema and database but it is empty anyway. A common cause is + * that the wrong database was opened. + * Example: + *
+     * SELECT * FROM ABC;
+     * 
+ */ + public static final int TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1 = 42104; + + /** + * The error with code 42111 is thrown when + * trying to create an index if an index with the same name already exists. + * Example: + *
+     * CREATE TABLE TEST(ID INT, NAME VARCHAR);
+     * CREATE INDEX IDX_ID ON TEST(ID);
+     * CREATE TABLE ADDRESS(ID INT);
+     * CREATE INDEX IDX_ID ON ADDRESS(ID);
+     * 
+ */ + public static final int INDEX_ALREADY_EXISTS_1 = 42111; + + /** + * The error with code 42112 is thrown when + * trying to drop or reference an index that does not exist. + * Example: + *
+     * DROP INDEX ABC;
+     * 
+ */ + public static final int INDEX_NOT_FOUND_1 = 42112; + + /** + * The error with code 42121 is thrown when trying to create + * a table or insert into a table and use the same column name twice. + * Example: + *
+     * CREATE TABLE TEST(ID INT, ID INT);
+     * 
+ */ + public static final int DUPLICATE_COLUMN_NAME_1 = 42121; + + /** + * The error with code 42122 is thrown when + * referencing an non-existing column. + * Example: + *
+     * CREATE TABLE TEST(ID INT);
+     * SELECT NAME FROM TEST;
+     * 
+ */ + public static final int COLUMN_NOT_FOUND_1 = 42122; + + /** + * The error with code 42131 is thrown when + * identical expressions should be used, but different + * expressions were found. + * Example: + *
+     * SELECT MODE(A ORDER BY B) FROM TEST;
+     * 
+ */ + public static final int IDENTICAL_EXPRESSIONS_SHOULD_BE_USED = 42131; + + /** + * The error with code 42602 is thrown when + * invalid name of identifier is used. + * Example: + *
+     * statement.enquoteIdentifier("\"", true);
+     * 
+ */ + public static final int INVALID_NAME_1 = 42602; + + /** + * The error with code 42622 is thrown when + * name of identifier is too long. + * Example: + *
+     * char[] c = new char[1000];
+     * Arrays.fill(c, 'A');
+     * statement.executeQuery("SELECT 1 " + new String(c));
+     * 
+ */ + public static final int NAME_TOO_LONG_2 = 42622; + + // 54: program limit exceeded + + /** + * The error with code 54011 is thrown when + * too many columns were specified in a table, select statement, + * or row value. + * Example: + *
+     * CREATE TABLE TEST(C1 INTEGER, C2 INTEGER, ..., C20000 INTEGER);
+     * 
+ */ + public static final int TOO_MANY_COLUMNS_1 = 54011; + + // 0A: feature not supported + + // HZ: remote database access + + // + + /** + * The error with code 50000 is thrown when + * something unexpected occurs, for example an internal stack + * overflow. For details about the problem, see the cause of the + * exception in the stack trace. + */ + public static final int GENERAL_ERROR_1 = 50000; + + /** + * The error with code 50004 is thrown when + * creating a table with an unsupported data type, or + * when the data type is unknown because parameters are used. + * Example: + *
+     * CREATE TABLE TEST(ID VERYSMALLINT);
+     * 
+ */ + public static final int UNKNOWN_DATA_TYPE_1 = 50004; + + /** + * The error with code 50100 is thrown when calling an + * unsupported JDBC method or database feature. See the stack trace for + * details. + */ + public static final int FEATURE_NOT_SUPPORTED_1 = 50100; + + /** + * The error with code 50200 is thrown when + * another connection locked an object longer than the lock timeout + * set for this connection, or when a deadlock occurred. + * Example: + *
+     * CREATE TABLE TEST(ID INT);
+     * -- connection 1:
+     * SET AUTOCOMMIT FALSE;
+     * INSERT INTO TEST VALUES(1);
+     * -- connection 2:
+     * SET AUTOCOMMIT FALSE;
+     * INSERT INTO TEST VALUES(1);
+     * 
+ */ + public static final int LOCK_TIMEOUT_1 = 50200; + + /** + * The error with code 57014 is thrown when + * a statement was canceled using Statement.cancel() or + * when the query timeout has been reached. + * Examples: + *
+     * stat.setQueryTimeout(1);
+     * stat.cancel();
+     * 
+ */ + public static final int STATEMENT_WAS_CANCELED = 57014; + + /** + * The error with code 90000 is thrown when + * a function that does not return a result set was used in the FROM clause. + * Example: + *
+     * SELECT * FROM SIN(1);
+     * 
+ */ + public static final int FUNCTION_MUST_RETURN_RESULT_SET_1 = 90000; + + /** + * The error with code 90001 is thrown when + * Statement.executeUpdate() was called for a SELECT statement. + * This is not allowed according to the JDBC specs. + */ + public static final int METHOD_NOT_ALLOWED_FOR_QUERY = 90001; + + /** + * The error with code 90002 is thrown when + * Statement.executeQuery() was called for a statement that does + * not return a result set (for example, an UPDATE statement). + * This is not allowed according to the JDBC specs. + */ + public static final int METHOD_ONLY_ALLOWED_FOR_QUERY = 90002; + + /** + * The error with code 90003 is thrown when + * trying to convert a String to a binary value. Two hex digits + * per byte are required. Example of wrong usage: + *
+     * CALL X'00023';
+     * Hexadecimal string with odd number of characters: 00023
+     * 
+ * Correct: + *
+     * CALL X'000023';
+     * 
+ */ + public static final int HEX_STRING_ODD_1 = 90003; + + /** + * The error with code 90004 is thrown when + * trying to convert a text to binary, but the expression contains + * a non-hexadecimal character. + * Example: + *
+     * CALL X'ABCDEFGH';
+     * CALL CAST('ABCDEFGH' AS BINARY);
+     * 
+ * Conversion from text to binary is supported, but the text must + * represent the hexadecimal encoded bytes. + */ + public static final int HEX_STRING_WRONG_1 = 90004; + + /** + * The error with code 90005 is thrown when + * trying to create a trigger with invalid combination of flags. + */ + public static final int INVALID_TRIGGER_FLAGS_1 = 90005; + + /** + * The error with code 90006 is thrown when + * trying to get a value from a sequence that has run out of numbers + * and does not have cycling enabled. + */ + public static final int SEQUENCE_EXHAUSTED = 90006; + + /** + * The error with code 90007 is thrown when + * trying to call a JDBC method on an object that has been closed. + */ + public static final int OBJECT_CLOSED = 90007; + + /** + * The error with code 90008 is thrown when + * trying to use a value that is not valid for the given operation. + * Example: + *
+     * CREATE SEQUENCE TEST INCREMENT 0;
+     * 
+ */ + public static final int INVALID_VALUE_2 = 90008; + + /** + * The error with code 90009 is thrown when + * trying to create a sequence with an invalid combination + * of attributes (min value, max value, start value, etc). + */ + public static final int SEQUENCE_ATTRIBUTES_INVALID_7 = 90009; + + /** + * The error with code 90010 is thrown when + * trying to format a timestamp or number using TO_CHAR + * with an invalid format. + */ + public static final int INVALID_TO_CHAR_FORMAT = 90010; + + /** + * The error with code 90011 is thrown when + * trying to open a connection to a database using an implicit relative + * path, such as "jdbc:h2:test" (in which case the database file would be + * stored in the current working directory of the application). This is not + * allowed because it can lead to confusion where the database file is, and + * can result in multiple databases because different working directories + * are used. Instead, use "jdbc:h2:~/name" (relative to the current user + * home directory), use an absolute path, set the base directory (baseDir), + * use "jdbc:h2:./name" (explicit relative path), or set the system property + * "h2.implicitRelativePath" to "true" (to prevent this check). For Windows, + * an absolute path also needs to include the drive ("C:/..."). Please see + * the documentation on the supported URL format. Example: + *
+     * jdbc:h2:test
+     * 
+ */ + public static final int URL_RELATIVE_TO_CWD = 90011; + + /** + * The error with code 90012 is thrown when + * trying to execute a statement with an parameter. + * Example: + *
+     * CALL SIN(?);
+     * 
+ */ + public static final int PARAMETER_NOT_SET_1 = 90012; + + /** + * The error with code 90013 is thrown when when trying to access + * a database object with a catalog name that does not match the database + * name. + *
+     * SELECT * FROM database_that_does_not_exist.table_name
+     * 
+ */ + public static final int DATABASE_NOT_FOUND_1 = 90013; + + /** + * The error with code 90014 is thrown when + * trying to parse a date with an unsupported format string, or + * when the date can not be parsed. + * Example: + *
+     * CALL PARSEDATETIME('2001 January', 'yyyy mm');
+     * 
+ */ + public static final int PARSE_ERROR_1 = 90014; + + /** + * The error with code 90015 is thrown when + * using an aggregate function with a data type that is not supported. + * Example: + *
+     * SELECT SUM('Hello') FROM DUAL;
+     * 
+ */ + public static final int SUM_OR_AVG_ON_WRONG_DATATYPE_1 = 90015; + + /** + * The error with code 90016 is thrown when + * a column was used in the expression list or the order by clause of a + * group or aggregate query, and that column is not in the GROUP BY clause. + * Example of wrong usage: + *
+     * CREATE TABLE TEST(ID INT, NAME VARCHAR);
+     * INSERT INTO TEST VALUES(1, 'Hello'), (2, 'World');
+     * SELECT ID, MAX(NAME) FROM TEST;
+     * Column ID must be in the GROUP BY list.
+     * 
+ * Correct: + *
+     * SELECT ID, MAX(NAME) FROM TEST GROUP BY ID;
+     * 
+ */ + public static final int MUST_GROUP_BY_COLUMN_1 = 90016; + + /** + * The error with code 90017 is thrown when + * trying to define a second primary key constraint for this table. + * Example: + *
+     * CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR);
+     * ALTER TABLE TEST ADD CONSTRAINT PK PRIMARY KEY(NAME);
+     * 
+ */ + public static final int SECOND_PRIMARY_KEY = 90017; + + /** + * The error with code 90018 is thrown when + * the connection was opened, but never closed. In the finalizer of the + * connection, this forgotten close was detected and the connection was + * closed automatically, but relying on the finalizer is not good practice + * as it is not guaranteed and behavior is virtual machine dependent. The + * application should close the connection. This exception only appears in + * the .trace.db file. Example of wrong usage: + *
+     * Connection conn;
+     * conn = DriverManager.getConnection("jdbc:h2:˜/test");
+     * conn = null;
+     * The connection was not closed by the application and is
+     * garbage collected
+     * 
+ * Correct: + *
+     * conn.close();
+     * 
+ */ + public static final int TRACE_CONNECTION_NOT_CLOSED = 90018; + + /** + * The error with code 90019 is thrown when + * trying to drop the current user, if there are no other admin users. + * Example: + *
+     * DROP USER SA;
+     * 
+ */ + public static final int CANNOT_DROP_CURRENT_USER = 90019; + + /** + * The error with code 90020 is thrown when trying to open a + * database in embedded mode if this database is already in use in another + * process (or in a different class loader). Multiple connections to the + * same database are supported in the following cases: + *
  • In embedded mode (URL of the form jdbc:h2:~/test) if all + * connections are opened within the same process and class loader. + *
  • In server and cluster mode (URL of the form + * jdbc:h2:tcp://localhost/test) using remote connections. + *
+ * The mixed mode is also supported. This mode requires to start a server + * in the same process where the database is open in embedded mode. + */ + public static final int DATABASE_ALREADY_OPEN_1 = 90020; + + /** + * The error with code 90021 is thrown when + * trying to change a specific database property that conflicts with other + * database properties. + */ + public static final int UNSUPPORTED_SETTING_COMBINATION = 90021; + + /** + * The error with code 90022 is thrown when + * trying to call a unknown function. + * Example: + *
+     * CALL SPECIAL_SIN(10);
+     * 
+ */ + public static final int FUNCTION_NOT_FOUND_1 = 90022; + + /** + * The error with code 90023 is thrown when trying to set a + * primary key on a nullable column or when trying to drop NOT NULL + * constraint on primary key or identity column. + * Examples: + *
+     * CREATE TABLE TEST(ID INT, NAME VARCHAR);
+     * ALTER TABLE TEST ADD CONSTRAINT PK PRIMARY KEY(ID);
+     * 
+ *
+     * CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR);
+     * ALTER TABLE TEST ALTER COLUMN ID DROP NOT NULL;
+     * 
+ *
+     * CREATE TABLE TEST(ID INT GENERATED ALWAYS AS IDENTITY, NAME VARCHAR);
+     * ALTER TABLE TEST ALTER COLUMN ID DROP NOT NULL;
+     * 
+ */ + public static final int COLUMN_MUST_NOT_BE_NULLABLE_1 = 90023; + + /** + * The error with code 90024 is thrown when + * a file could not be renamed. + */ + public static final int FILE_RENAME_FAILED_2 = 90024; + + /** + * The error with code 90025 is thrown when + * a file could not be deleted, because it is still in use + * (only in Windows), or because an error occurred when deleting. + */ + public static final int FILE_DELETE_FAILED_1 = 90025; + + /** + * The error with code 90026 is thrown when + * an object could not be serialized. + */ + public static final int SERIALIZATION_FAILED_1 = 90026; + + /** + * The error with code 90027 is thrown when + * an object could not be de-serialized. + */ + public static final int DESERIALIZATION_FAILED_1 = 90027; + + /** + * The error with code 90028 is thrown when + * an input / output error occurred. For more information, see the root + * cause of the exception. + */ + public static final int IO_EXCEPTION_1 = 90028; + + /** + * The error with code 90029 is thrown when + * calling ResultSet.deleteRow(), insertRow(), or updateRow() + * when the current row is not updatable. + * Example: + *
+     * ResultSet rs = stat.executeQuery("SELECT * FROM TEST");
+     * rs.next();
+     * rs.insertRow();
+     * 
+ */ + public static final int NOT_ON_UPDATABLE_ROW = 90029; + + /** + * The error with code 90030 is thrown when + * the database engine has detected a checksum mismatch in the data + * or index. To solve this problem, restore a backup or use the + * Recovery tool (org.h2.tools.Recover). + */ + public static final int FILE_CORRUPTED_1 = 90030; + + /** + * The error with code 90031 is thrown when + * an input / output error occurred. For more information, see the root + * cause of the exception. + */ + public static final int IO_EXCEPTION_2 = 90031; + + /** + * The error with code 90032 is thrown when + * trying to drop or alter a user that does not exist. + * Example: + *
+     * DROP USER TEST_USER;
+     * 
+ */ + public static final int USER_NOT_FOUND_1 = 90032; + + /** + * The error with code 90033 is thrown when + * trying to create a user or role if a user with this name already exists. + * Example: + *
+     * CREATE USER TEST_USER;
+     * CREATE USER TEST_USER;
+     * 
+ */ + public static final int USER_ALREADY_EXISTS_1 = 90033; + + /** + * The error with code 90034 is thrown when + * writing to the trace file failed, for example because the there + * is an I/O exception. This message is printed to System.out, + * but only once. + */ + public static final int TRACE_FILE_ERROR_2 = 90034; + + /** + * The error with code 90035 is thrown when + * trying to create a sequence if a sequence with this name already + * exists. + * Example: + *
+     * CREATE SEQUENCE TEST_SEQ;
+     * CREATE SEQUENCE TEST_SEQ;
+     * 
+ */ + public static final int SEQUENCE_ALREADY_EXISTS_1 = 90035; + + /** + * The error with code 90036 is thrown when + * trying to access a sequence that does not exist. + * Example: + *
+     * SELECT NEXT VALUE FOR SEQUENCE XYZ;
+     * 
+ */ + public static final int SEQUENCE_NOT_FOUND_1 = 90036; + + /** + * The error with code 90037 is thrown when + * trying to drop or alter a view that does not exist. + * Example: + *
+     * DROP VIEW XYZ;
+     * 
+ */ + public static final int VIEW_NOT_FOUND_1 = 90037; + + /** + * The error with code 90038 is thrown when + * trying to create a view if a view with this name already + * exists. + * Example: + *
+     * CREATE VIEW DUMMY AS SELECT * FROM DUAL;
+     * CREATE VIEW DUMMY AS SELECT * FROM DUAL;
+     * 
+ */ + public static final int VIEW_ALREADY_EXISTS_1 = 90038; + + /** + * The error with code 90039 is thrown when + * trying to access a CLOB or BLOB object that timed out. + * See the database setting LOB_TIMEOUT. + */ + public static final int LOB_CLOSED_ON_TIMEOUT_1 = 90039; + + /** + * The error with code 90040 is thrown when + * a user that is not administrator tries to execute a statement + * that requires admin privileges. + */ + public static final int ADMIN_RIGHTS_REQUIRED = 90040; + + /** + * The error with code 90041 is thrown when + * trying to create a trigger and there is already a trigger with that name. + *
+     * CREATE TABLE TEST(ID INT);
+     * CREATE TRIGGER TRIGGER_A AFTER INSERT ON TEST
+     *      CALL "org.h2.samples.TriggerSample$MyTrigger";
+     * CREATE TRIGGER TRIGGER_A AFTER INSERT ON TEST
+     *      CALL "org.h2.samples.TriggerSample$MyTrigger";
+     * 
+ */ + public static final int TRIGGER_ALREADY_EXISTS_1 = 90041; + + /** + * The error with code 90042 is thrown when + * trying to drop a trigger that does not exist. + * Example: + *
+     * DROP TRIGGER TRIGGER_XYZ;
+     * 
+ */ + public static final int TRIGGER_NOT_FOUND_1 = 90042; + + /** + * The error with code 90043 is thrown when + * there is an error initializing the trigger, for example because the + * class does not implement the Trigger interface. + * See the root cause for details. + * Example: + *
+     * CREATE TABLE TEST(ID INT);
+     * CREATE TRIGGER TRIGGER_A AFTER INSERT ON TEST
+     *      CALL "java.lang.String";
+     * 
+ */ + public static final int ERROR_CREATING_TRIGGER_OBJECT_3 = 90043; + + /** + * The error with code 90044 is thrown when + * an exception or error occurred while calling the triggers fire method. + * See the root cause for details. + */ + public static final int ERROR_EXECUTING_TRIGGER_3 = 90044; + + /** + * The error with code 90045 is thrown when trying to create a + * constraint if an object with this name already exists. Example: + *
+     * CREATE TABLE TEST(ID INT NOT NULL);
+     * ALTER TABLE TEST ADD CONSTRAINT PK PRIMARY KEY(ID);
+     * ALTER TABLE TEST ADD CONSTRAINT PK PRIMARY KEY(ID);
+     * 
+ */ + public static final int CONSTRAINT_ALREADY_EXISTS_1 = 90045; + + /** + * The error with code 90046 is thrown when + * trying to open a connection to a database using an unsupported URL + * format. Please see the documentation on the supported URL format and + * examples. Example: + *
+     * jdbc:h2:;;
+     * 
+ */ + public static final int URL_FORMAT_ERROR_2 = 90046; + + /** + * The error with code 90047 is thrown when + * trying to connect to a TCP server with an incompatible client. + */ + public static final int DRIVER_VERSION_ERROR_2 = 90047; + + /** + * The error with code 90048 is thrown when + * the file header of a database files (*.db) does not match the + * expected version, or if it is corrupted. + */ + public static final int FILE_VERSION_ERROR_1 = 90048; + + /** + * The error with code 90049 is thrown when + * trying to open an encrypted database with the wrong file encryption + * password or algorithm. + */ + public static final int FILE_ENCRYPTION_ERROR_1 = 90049; + + /** + * The error with code 90050 is thrown when trying to open an + * encrypted database, but not separating the file password from the user + * password. The file password is specified in the password field, before + * the user password. A single space needs to be added between the file + * password and the user password; the file password itself may not contain + * spaces. File passwords (as well as user passwords) are case sensitive. + * Example of wrong usage: + *
+     * String url = "jdbc:h2:˜/test;CIPHER=AES";
+     * String passwords = "filePasswordUserPassword";
+     * DriverManager.getConnection(url, "sa", pwds);
+     * 
+ * Correct: + *
+     * String url = "jdbc:h2:˜/test;CIPHER=AES";
+     * String passwords = "filePassword userPassword";
+     * DriverManager.getConnection(url, "sa", pwds);
+     * 
+ */ + public static final int WRONG_PASSWORD_FORMAT = 90050; + + // 90051 was removed + + /** + * The error with code 90052 is thrown when a single-column + * subquery is expected but a subquery with other number of columns was + * specified. + * Example: + *
+     * VALUES ARRAY(SELECT A, B FROM TEST)
+     * 
+ */ + public static final int SUBQUERY_IS_NOT_SINGLE_COLUMN = 90052; + + /** + * The error with code 90053 is thrown when + * a subquery that is used as a value contains more than one row. + * Example: + *
+     * CREATE TABLE TEST(ID INT, NAME VARCHAR);
+     * INSERT INTO TEST VALUES(1, 'Hello'), (1, 'World');
+     * SELECT X, (SELECT NAME FROM TEST WHERE ID=X) FROM DUAL;
+     * 
+ */ + public static final int SCALAR_SUBQUERY_CONTAINS_MORE_THAN_ONE_ROW = 90053; + + /** + * The error with code 90054 is thrown when + * an aggregate function is used where it is not allowed. + * Example: + *
+     * CREATE TABLE TEST(ID INT);
+     * INSERT INTO TEST VALUES(1), (2);
+     * SELECT MAX(ID) FROM TEST WHERE ID = MAX(ID) GROUP BY ID;
+     * 
+ */ + public static final int INVALID_USE_OF_AGGREGATE_FUNCTION_1 = 90054; + + /** + * The error with code 90055 is thrown when + * trying to open a database with an unsupported cipher algorithm. + * Supported is AES. + * Example: + *
+     * jdbc:h2:~/test;CIPHER=DES
+     * 
+ */ + public static final int UNSUPPORTED_CIPHER = 90055; + + /** + * The error with code 90056 is thrown when trying to format a + * timestamp using TO_DATE and TO_TIMESTAMP with an invalid format. + */ + public static final int INVALID_TO_DATE_FORMAT = 90056; + + /** + * The error with code 90057 is thrown when + * trying to drop a constraint that does not exist. + * Example: + *
+     * CREATE TABLE TEST(ID INT);
+     * ALTER TABLE TEST DROP CONSTRAINT CID;
+     * 
+ */ + public static final int CONSTRAINT_NOT_FOUND_1 = 90057; + + /** + * The error with code 90058 is thrown when trying to call + * commit or rollback inside a trigger, or when trying to call a method + * inside a trigger that implicitly commits the current transaction, if an + * object is locked. This is not because it would release the lock too + * early. + */ + public static final int COMMIT_ROLLBACK_NOT_ALLOWED = 90058; + + /** + * The error with code 90059 is thrown when + * a query contains a column that could belong to multiple tables. + * Example: + *
+     * CREATE TABLE PARENT(ID INT, NAME VARCHAR);
+     * CREATE TABLE CHILD(PID INT, NAME VARCHAR);
+     * SELECT ID, NAME FROM PARENT P, CHILD C WHERE P.ID = C.PID;
+     * 
+ */ + public static final int AMBIGUOUS_COLUMN_NAME_1 = 90059; + + /** + * The error with code 90060 is thrown when + * trying to use a file locking mechanism that is not supported. + * Currently only FILE (the default) and SOCKET are supported + * Example: + *
+     * jdbc:h2:~/test;FILE_LOCK=LDAP
+     * 
+ */ + public static final int UNSUPPORTED_LOCK_METHOD_1 = 90060; + + /** + * The error with code 90061 is thrown when + * trying to start a server if a server is already running at the same port. + * It could also be a firewall problem. To find out if another server is + * already running, run the following command on Windows: + *
+     * netstat -ano
+     * 
+ * The column PID is the process id as listed in the Task Manager. + * For Linux, use: + *
+     * netstat -npl
+     * 
+ */ + public static final int EXCEPTION_OPENING_PORT_2 = 90061; + + /** + * The error with code 90062 is thrown when + * a directory or file could not be created. This can occur when + * trying to create a directory if a file with the same name already + * exists, or vice versa. + * + */ + public static final int FILE_CREATION_FAILED_1 = 90062; + + /** + * The error with code 90063 is thrown when + * trying to rollback to a savepoint that is not defined. + * Example: + *
+     * ROLLBACK TO SAVEPOINT S_UNKNOWN;
+     * 
+ */ + public static final int SAVEPOINT_IS_INVALID_1 = 90063; + + /** + * The error with code 90064 is thrown when + * Savepoint.getSavepointName() is called on an unnamed savepoint. + * Example: + *
+     * Savepoint sp = conn.setSavepoint();
+     * sp.getSavepointName();
+     * 
+ */ + public static final int SAVEPOINT_IS_UNNAMED = 90064; + + /** + * The error with code 90065 is thrown when + * Savepoint.getSavepointId() is called on a named savepoint. + * Example: + *
+     * Savepoint sp = conn.setSavepoint("Joe");
+     * sp.getSavepointId();
+     * 
+ */ + public static final int SAVEPOINT_IS_NAMED = 90065; + + /** + * The error with code 90066 is thrown when + * the same property appears twice in the database URL or in + * the connection properties. + * Example: + *
+     * jdbc:h2:~/test;LOCK_TIMEOUT=0;LOCK_TIMEOUT=1
+     * 
+ */ + public static final int DUPLICATE_PROPERTY_1 = 90066; + + /** + * The error with code 90067 is thrown when the client could + * not connect to the database, or if the connection was lost. Possible + * reasons are: the database server is not running at the given port, the + * connection was closed due to a shutdown, or the server was stopped. Other + * possible causes are: the server is not an H2 server, or the network + * connection is broken. + */ + public static final int CONNECTION_BROKEN_1 = 90067; + + /** + * The error with code 90068 is thrown when the given + * expression that is used in the ORDER BY is not in the result list. This + * is required for distinct queries, otherwise the result would be + * ambiguous. + * Example of wrong usage: + *
+     * CREATE TABLE TEST(ID INT, NAME VARCHAR);
+     * INSERT INTO TEST VALUES(2, 'Hello'), (1, 'Hello');
+     * SELECT DISTINCT NAME FROM TEST ORDER BY ID;
+     * Order by expression ID must be in the result list in this case
+     * 
+ * Correct: + *
+     * SELECT DISTINCT ID, NAME FROM TEST ORDER BY ID;
+     * 
+ */ + public static final int ORDER_BY_NOT_IN_RESULT = 90068; + + /** + * The error with code 90069 is thrown when + * trying to create a role if an object with this name already exists. + * Example: + *
+     * CREATE ROLE TEST_ROLE;
+     * CREATE ROLE TEST_ROLE;
+     * 
+ */ + public static final int ROLE_ALREADY_EXISTS_1 = 90069; + + /** + * The error with code 90070 is thrown when + * trying to drop or grant a role that does not exists. + * Example: + *
+     * DROP ROLE TEST_ROLE_2;
+     * 
+ */ + public static final int ROLE_NOT_FOUND_1 = 90070; + + /** + * The error with code 90071 is thrown when + * trying to grant or revoke if no role or user with that name exists. + * Example: + *
+     * GRANT SELECT ON TEST TO UNKNOWN;
+     * 
+ */ + public static final int USER_OR_ROLE_NOT_FOUND_1 = 90071; + + /** + * The error with code 90072 is thrown when + * trying to grant or revoke both roles and rights at the same time. + * Example: + *
+     * GRANT SELECT, TEST_ROLE ON TEST TO SA;
+     * 
+ */ + public static final int ROLES_AND_RIGHT_CANNOT_BE_MIXED = 90072; + + /** + * The error with code 90073 is thrown when trying to create + * an alias for a Java method, if two methods exists in this class that have + * this name and the same number of parameters. + * Example of wrong usage: + *
+     * CREATE ALIAS GET_LONG FOR
+     *      "java.lang.Long.getLong";
+     * 
+ * Correct: + *
+     * CREATE ALIAS GET_LONG FOR
+     *      "java.lang.Long.getLong(java.lang.String, java.lang.Long)";
+     * 
+ */ + public static final int METHODS_MUST_HAVE_DIFFERENT_PARAMETER_COUNTS_2 = 90073; + + /** + * The error with code 90074 is thrown when + * trying to grant a role that has already been granted. + * Example: + *
+     * CREATE ROLE TEST_A;
+     * CREATE ROLE TEST_B;
+     * GRANT TEST_A TO TEST_B;
+     * GRANT TEST_B TO TEST_A;
+     * 
+ */ + public static final int ROLE_ALREADY_GRANTED_1 = 90074; + + /** + * The error with code 90075 is thrown when + * trying to alter a table and allow null for a column that is part of a + * primary key or hash index. + * Example: + *
+     * CREATE TABLE TEST(ID INT PRIMARY KEY);
+     * ALTER TABLE TEST ALTER COLUMN ID NULL;
+     * 
+ */ + public static final int COLUMN_IS_PART_OF_INDEX_1 = 90075; + + /** + * The error with code 90076 is thrown when + * trying to create a function alias for a system function or for a function + * that is already defined. + * Example: + *
+     * CREATE ALIAS SQRT FOR "java.lang.Math.sqrt"
+     * 
+ */ + public static final int FUNCTION_ALIAS_ALREADY_EXISTS_1 = 90076; + + /** + * The error with code 90077 is thrown when + * trying to drop a system function or a function alias that does not exist. + * Example: + *
+     * DROP ALIAS SQRT;
+     * 
+ */ + public static final int FUNCTION_ALIAS_NOT_FOUND_1 = 90077; + + /** + * The error with code 90078 is thrown when + * trying to create a schema if an object with this name already exists. + * Example: + *
+     * CREATE SCHEMA TEST_SCHEMA;
+     * CREATE SCHEMA TEST_SCHEMA;
+     * 
+ */ + public static final int SCHEMA_ALREADY_EXISTS_1 = 90078; + + /** + * The error with code 90079 is thrown when + * trying to drop a schema that does not exist. + * Example: + *
+     * DROP SCHEMA UNKNOWN;
+     * 
+ */ + public static final int SCHEMA_NOT_FOUND_1 = 90079; + + /** + * The error with code 90080 is thrown when + * trying to rename a object to a different schema, or when trying to + * create a related object in another schema. + * For CREATE LINKED TABLE, it is thrown when multiple tables with that + * name exist in different schemas. + * Example: + *
+     * CREATE SCHEMA TEST_SCHEMA;
+     * CREATE TABLE TEST(ID INT);
+     * CREATE INDEX TEST_ID ON TEST(ID);
+     * ALTER INDEX TEST_ID RENAME TO TEST_SCHEMA.IDX_TEST_ID;
+     * 
+ */ + public static final int SCHEMA_NAME_MUST_MATCH = 90080; + + /** + * The error with code 90081 is thrown when + * trying to alter a column to not allow NULL, if there + * is already data in the table where this column is NULL. + * Example: + *
+     * CREATE TABLE TEST(ID INT);
+     * INSERT INTO TEST VALUES(NULL);
+     * ALTER TABLE TEST ALTER COLUMN ID VARCHAR NOT NULL;
+     * 
+ */ + public static final int COLUMN_CONTAINS_NULL_VALUES_1 = 90081; + + /** + * The error with code 90082 is thrown when + * trying to drop a system generated sequence. + */ + public static final int SEQUENCE_BELONGS_TO_A_TABLE_1 = 90082; + + /** + * The error with code 90083 is thrown when + * trying to drop a column that is part of a constraint. + * Example: + *
+     * CREATE TABLE TEST(ID INT, PID INT REFERENCES(ID));
+     * ALTER TABLE TEST DROP COLUMN PID;
+     * 
+ */ + public static final int COLUMN_IS_REFERENCED_1 = 90083; + + /** + * The error with code 90084 is thrown when + * trying to drop the last column of a table. + * Example: + *
+     * CREATE TABLE TEST(ID INT);
+     * ALTER TABLE TEST DROP COLUMN ID;
+     * 
+ */ + public static final int CANNOT_DROP_LAST_COLUMN = 90084; + + /** + * The error with code 90085 is thrown when + * trying to manually drop an index that was generated by the system + * because of a unique or referential constraint. To find + * the owner of the index without attempt to drop it run + *
+     * SELECT CONSTRAINT_SCHEMA, CONSTRAINT_NAME
+     * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
+     * WHERE INDEX_SCHEMA = '<index schema>'
+     * AND INDEX_NAME = '<index name>'
+     * FETCH FIRST ROW ONLY
+     * 
+ * Example of wrong usage: + *
+     * CREATE TABLE TEST(ID INT, CONSTRAINT UID UNIQUE(ID));
+     * DROP INDEX UID_INDEX_0;
+     * Index UID_INDEX_0 belongs to constraint UID
+     * 
+ * Correct: + *
+     * ALTER TABLE TEST DROP CONSTRAINT UID;
+     * 
+ */ + public static final int INDEX_BELONGS_TO_CONSTRAINT_2 = 90085; + + /** + * The error with code 90086 is thrown when + * a class can not be loaded because it is not in the classpath + * or because a related class is not in the classpath. + * Example: + *
+     * CREATE ALIAS TEST FOR "java.lang.invalid.Math.sqrt";
+     * 
+ */ + public static final int CLASS_NOT_FOUND_1 = 90086; + + /** + * The error with code 90087 is thrown when + * a method with matching number of arguments was not found in the class. + * Example: + *
+     * CREATE ALIAS TO_BINARY FOR "java.lang.Long.toBinaryString(long)";
+     * CALL TO_BINARY(10, 2);
+     * 
+ */ + public static final int METHOD_NOT_FOUND_1 = 90087; + + /** + * The error with code 90088 is thrown when + * trying to switch to an unknown mode. + * Example: + *
+     * SET MODE UNKNOWN;
+     * 
+ */ + public static final int UNKNOWN_MODE_1 = 90088; + + /** + * The error with code 90089 is thrown when + * trying to change the collation while there was already data in + * the database. The collation of the database must be set when the + * database is empty. + * Example of wrong usage: + *
+     * CREATE TABLE TEST(NAME VARCHAR PRIMARY KEY);
+     * INSERT INTO TEST VALUES('Hello', 'World');
+     * SET COLLATION DE;
+     * Collation cannot be changed because there is a data table: PUBLIC.TEST
+     * 
+ * Correct: + *
+     * SET COLLATION DE;
+     * CREATE TABLE TEST(NAME VARCHAR PRIMARY KEY);
+     * INSERT INTO TEST VALUES('Hello', 'World');
+     * 
+ */ + public static final int COLLATION_CHANGE_WITH_DATA_TABLE_1 = 90089; + + /** + * The error with code 90090 is thrown when + * trying to drop a schema that may not be dropped (the schema PUBLIC + * and the schema INFORMATION_SCHEMA). + * Example: + *
+     * DROP SCHEMA PUBLIC;
+     * 
+ */ + public static final int SCHEMA_CAN_NOT_BE_DROPPED_1 = 90090; + + /** + * The error with code 90091 is thrown when + * trying to drop the role PUBLIC. + * Example: + *
+     * DROP ROLE PUBLIC;
+     * 
+ */ + public static final int ROLE_CAN_NOT_BE_DROPPED_1 = 90091; + + /** + * The error with code 90093 is thrown when + * trying to connect to a clustered database that runs in standalone + * mode. This can happen if clustering is not enabled on the database, + * or if one of the clients disabled clustering because it can not see + * the other cluster node. + */ + public static final int CLUSTER_ERROR_DATABASE_RUNS_ALONE = 90093; + + /** + * The error with code 90094 is thrown when + * trying to connect to a clustered database that runs together with a + * different cluster node setting than what is used when trying to connect. + */ + public static final int CLUSTER_ERROR_DATABASE_RUNS_CLUSTERED_1 = 90094; + + /** + * The error with code 90095 is thrown when + * calling the method STRINGDECODE with an invalid escape sequence. + * Only Java style escape sequences and Java properties file escape + * sequences are supported. + * Example: + *
+     * CALL STRINGDECODE('\i');
+     * 
+ */ + public static final int STRING_FORMAT_ERROR_1 = 90095; + + /** + * The error with code 90096 is thrown when + * trying to perform an operation with a non-admin user if the + * user does not have enough rights. + */ + public static final int NOT_ENOUGH_RIGHTS_FOR_1 = 90096; + + /** + * The error with code 90097 is thrown when + * trying to delete or update a database if it is open in read-only mode. + * Example: + *
+     * jdbc:h2:~/test;ACCESS_MODE_DATA=R
+     * CREATE TABLE TEST(ID INT);
+     * 
+ */ + public static final int DATABASE_IS_READ_ONLY = 90097; + + /** + * The error with code 90098 is thrown when the database has + * been closed, for example because the system ran out of memory or because + * the self-destruction counter has reached zero. This counter is only used + * for recovery testing, and not set in normal operation. + */ + public static final int DATABASE_IS_CLOSED = 90098; + + /** + * The error with code 90099 is thrown when an error occurred + * trying to initialize the database event listener. Example: + *
+     * jdbc:h2:˜/test;DATABASE_EVENT_LISTENER='java.lang.String'
+     * 
+ */ + public static final int ERROR_SETTING_DATABASE_EVENT_LISTENER_2 = 90099; + + /** + * The error with code 90101 is thrown when + * the XA API detected unsupported transaction names. This can happen + * when mixing application generated transaction names and transaction names + * generated by this databases XAConnection API. + */ + public static final int WRONG_XID_FORMAT_1 = 90101; + + /** + * The error with code 90102 is thrown when + * trying to use unsupported options for the given compression algorithm. + * Example of wrong usage: + *
+     * CALL COMPRESS(STRINGTOUTF8(SPACE(100)), 'DEFLATE l 10');
+     * 
+ * Correct: + *
+     * CALL COMPRESS(STRINGTOUTF8(SPACE(100)), 'DEFLATE l 9');
+     * 
+ */ + public static final int UNSUPPORTED_COMPRESSION_OPTIONS_1 = 90102; + + /** + * The error with code 90103 is thrown when + * trying to use an unsupported compression algorithm. + * Example: + *
+     * CALL COMPRESS(STRINGTOUTF8(SPACE(100)), 'BZIP');
+     * 
+ */ + public static final int UNSUPPORTED_COMPRESSION_ALGORITHM_1 = 90103; + + /** + * The error with code 90104 is thrown when + * the data can not be de-compressed. + * Example: + *
+     * CALL EXPAND(X'00FF');
+     * 
+ */ + public static final int COMPRESSION_ERROR = 90104; + + /** + * The error with code 90105 is thrown when + * an exception occurred in a user-defined method. + * Example: + *
+     * CREATE ALIAS SYS_PROP FOR "java.lang.System.getProperty";
+     * CALL SYS_PROP(NULL);
+     * 
+ */ + public static final int EXCEPTION_IN_FUNCTION_1 = 90105; + + /** + * The error with code 90106 is thrown when + * trying to truncate a table that can not be truncated. + * Tables with referential integrity constraints can not be truncated. + * Also, system tables and view can not be truncated. + * Example: + *
+     * TRUNCATE TABLE INFORMATION_SCHEMA.SETTINGS;
+     * 
+ */ + public static final int CANNOT_TRUNCATE_1 = 90106; + + /** + * The error with code 90107 is thrown when + * trying to drop an object because another object would become invalid. + * Example: + *
+     * CREATE TABLE COUNT(X INT);
+     * CREATE TABLE ITEMS(ID INT DEFAULT SELECT MAX(X)+1 FROM COUNT);
+     * DROP TABLE COUNT;
+     * 
+ */ + public static final int CANNOT_DROP_2 = 90107; + + /** + * The error with code 90108 is thrown when not enough heap + * memory was available. A possible solutions is to increase the memory size + * using java -Xmx128m .... Another solution is to reduce + * the cache size. + */ + public static final int OUT_OF_MEMORY = 90108; + + /** + * The error with code 90109 is thrown when + * trying to run a query against an invalid view. + * Example: + *
+     * CREATE FORCE VIEW TEST_VIEW AS SELECT * FROM TEST;
+     * SELECT * FROM TEST_VIEW;
+     * 
+ */ + public static final int VIEW_IS_INVALID_2 = 90109; + + /** + * The error with code 90110 is thrown when + * trying to compare or combine values of incomparable data types. + * Example: + *
+     * CREATE TABLE test (id INT NOT NULL, name VARCHAR);
+     * select * from test where id = (1, 2);
+     * 
+ */ + public static final int TYPES_ARE_NOT_COMPARABLE_2 = 90110; + + /** + * The error with code 90111 is thrown when + * an exception occurred while accessing a linked table. + */ + public static final int ERROR_ACCESSING_LINKED_TABLE_2 = 90111; + + /** + * The error with code 90112 is thrown when a row was deleted + * twice while locking was disabled. This is an intern exception that should + * never be thrown to the application, because such deleted should be + * detected and the resulting exception ignored inside the database engine. + *
+     * Row not found when trying to delete from index UID_INDEX_0
+     * 
+ */ + public static final int ROW_NOT_FOUND_WHEN_DELETING_1 = 90112; + + /** + * The error with code 90113 is thrown when + * the database URL contains unsupported settings. + * Example: + *
+     * jdbc:h2:~/test;UNKNOWN=TRUE
+     * 
+ */ + public static final int UNSUPPORTED_SETTING_1 = 90113; + + /** + * The error with code 90114 is thrown when + * trying to create a constant if a constant with this name already exists. + * Example: + *
+     * CREATE CONSTANT TEST VALUE 1;
+     * CREATE CONSTANT TEST VALUE 1;
+     * 
+ */ + public static final int CONSTANT_ALREADY_EXISTS_1 = 90114; + + /** + * The error with code 90115 is thrown when + * trying to drop a constant that does not exists. + * Example: + *
+     * DROP CONSTANT UNKNOWN;
+     * 
+ */ + public static final int CONSTANT_NOT_FOUND_1 = 90115; + + /** + * The error with code 90116 is thrown when + * trying use a literal in a SQL statement if literals are disabled. + * If literals are disabled, use PreparedStatement and parameters instead + * of literals in the SQL statement. + * Example: + *
+     * SET ALLOW_LITERALS NONE;
+     * CALL 1+1;
+     * 
+ */ + public static final int LITERALS_ARE_NOT_ALLOWED = 90116; + + /** + * The error with code 90117 is thrown when + * trying to connect to a TCP server from another machine, if remote + * connections are not allowed. To allow remote connections, + * start the TCP server using the option -tcpAllowOthers as in: + *
+     * java org.h2.tools.Server -tcp -tcpAllowOthers
+     * 
+ * Or, when starting the server from an application, use: + *
+     * Server server = Server.createTcpServer("-tcpAllowOthers");
+     * server.start();
+     * 
+ */ + public static final int REMOTE_CONNECTION_NOT_ALLOWED = 90117; + + /** + * The error with code 90118 is thrown when + * trying to drop a table can not be dropped. + * Example: + *
+     * DROP TABLE INFORMATION_SCHEMA.SETTINGS;
+     * 
+ */ + public static final int CANNOT_DROP_TABLE_1 = 90118; + + /** + * The error with code 90119 is thrown when + * trying to create a domain if an object with this name already exists, + * or when trying to overload a built-in data type. + * Example: + *
+     * CREATE DOMAIN INTEGER AS VARCHAR;
+     * CREATE DOMAIN EMAIL AS VARCHAR CHECK LOCATE('@', VALUE) > 0;
+     * CREATE DOMAIN EMAIL AS VARCHAR CHECK LOCATE('@', VALUE) > 0;
+     * 
+ */ + public static final int DOMAIN_ALREADY_EXISTS_1 = 90119; + + /** + * Deprecated since 1.4.198. Use {@link #DOMAIN_ALREADY_EXISTS_1} instead. + */ + @Deprecated + public static final int USER_DATA_TYPE_ALREADY_EXISTS_1 = DOMAIN_ALREADY_EXISTS_1; + + /** + * The error with code 90120 is thrown when + * trying to drop a domain that doesn't exist. + * Example: + *
+     * DROP DOMAIN UNKNOWN;
+     * 
+ */ + public static final int DOMAIN_NOT_FOUND_1 = 90120; + + /** + * Deprecated since 1.4.198. Use {@link #DOMAIN_NOT_FOUND_1} instead. + */ + @Deprecated + public static final int USER_DATA_TYPE_NOT_FOUND_1 = DOMAIN_NOT_FOUND_1; + + /** + * The error with code 90121 is thrown when + * a database operation is started while the virtual machine exits + * (for example in a shutdown hook), or when the session is closed. + */ + public static final int DATABASE_CALLED_AT_SHUTDOWN = 90121; + + /** + * The error with code 90122 is thrown when + * WITH TIES clause is used without ORDER BY clause. + */ + public static final int WITH_TIES_WITHOUT_ORDER_BY = 90122; + + /** + * The error with code 90123 is thrown when + * trying mix regular parameters and indexed parameters in the same + * statement. Example: + *
+     * SELECT ?, ?1 FROM DUAL;
+     * 
+ */ + public static final int CANNOT_MIX_INDEXED_AND_UNINDEXED_PARAMS = 90123; + + /** + * The error with code 90124 is thrown when + * trying to access a file that doesn't exist. This can occur when trying to + * read a lob if the lob file has been deleted by another application. + */ + public static final int FILE_NOT_FOUND_1 = 90124; + + /** + * The error with code 90125 is thrown when + * PreparedStatement.setBigDecimal is called with object that extends the + * class BigDecimal, and the system property h2.allowBigDecimalExtensions is + * not set. Using extensions of BigDecimal is dangerous because the database + * relies on the behavior of BigDecimal. Example of wrong usage: + *
+     * BigDecimal bd = new MyDecimal("$10.3");
+     * prep.setBigDecimal(1, bd);
+     * Invalid class, expected java.math.BigDecimal but got MyDecimal
+     * 
+ * Correct: + *
+     * BigDecimal bd = new BigDecimal("10.3");
+     * prep.setBigDecimal(1, bd);
+     * 
+ */ + public static final int INVALID_CLASS_2 = 90125; + + /** + * The error with code 90126 is thrown when + * trying to call the BACKUP statement for an in-memory database. + * Example: + *
+     * jdbc:h2:mem:
+     * BACKUP TO 'test.zip';
+     * 
+ */ + public static final int DATABASE_IS_NOT_PERSISTENT = 90126; + + /** + * The error with code 90127 is thrown when + * trying to update or delete a row in a result set if the result set is + * not updatable. Result sets are only updatable if: + * the statement was created with updatable concurrency; + * all columns of the result set are from the same table; + * the table is a data table (not a system table or view); + * all columns of the primary key or any unique index are included; + * all columns of the result set are columns of that table. + */ + public static final int RESULT_SET_NOT_UPDATABLE = 90127; + + /** + * The error with code 90128 is thrown when + * trying to call a method of the ResultSet that is only supported + * for scrollable result sets, and the result set is not scrollable. + * Example: + *
+     * rs.first();
+     * 
+ */ + public static final int RESULT_SET_NOT_SCROLLABLE = 90128; + + /** + * The error with code 90129 is thrown when + * trying to commit a transaction that doesn't exist. + * Example: + *
+     * PREPARE COMMIT ABC;
+     * COMMIT TRANSACTION TEST;
+     * 
+ */ + public static final int TRANSACTION_NOT_FOUND_1 = 90129; + + /** + * The error with code 90130 is thrown when + * an execute method of PreparedStatement was called with a SQL statement. + * This is not allowed according to the JDBC specification. Instead, use + * an execute method of Statement. + * Example of wrong usage: + *
+     * PreparedStatement prep = conn.prepareStatement("SELECT * FROM TEST");
+     * prep.execute("DELETE FROM TEST");
+     * 
+ * Correct: + *
+     * Statement stat = conn.createStatement();
+     * stat.execute("DELETE FROM TEST");
+     * 
+ */ + public static final int METHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT = 90130; + + /** + * The error with code 90131 is thrown when using multi version + * concurrency control, and trying to update the same row from within two + * connections at the same time, or trying to insert two rows with the same + * key from two connections. Example: + *
+     * jdbc:h2:~/test
+     * Session 1:
+     * CREATE TABLE TEST(ID INT);
+     * INSERT INTO TEST VALUES(1);
+     * SET AUTOCOMMIT FALSE;
+     * UPDATE TEST SET ID = 2;
+     * Session 2:
+     * SET AUTOCOMMIT FALSE;
+     * UPDATE TEST SET ID = 3;
+     * 
+ */ + public static final int CONCURRENT_UPDATE_1 = 90131; + + /** + * The error with code 90132 is thrown when + * trying to drop a user-defined aggregate function that doesn't exist. + * Example: + *
+     * DROP AGGREGATE UNKNOWN;
+     * 
+ */ + public static final int AGGREGATE_NOT_FOUND_1 = 90132; + + /** + * The error with code 90133 is thrown when + * trying to change a specific database property while the database is + * already open. + */ + public static final int CANNOT_CHANGE_SETTING_WHEN_OPEN_1 = 90133; + + /** + * The error with code 90134 is thrown when + * trying to load a Java class that is not part of the allowed classes. By + * default, all classes are allowed, but this can be changed using the + * system property h2.allowedClasses. + */ + public static final int ACCESS_DENIED_TO_CLASS_1 = 90134; + + /** + * The error with code 90135 is thrown when + * trying to open a connection to a database that is currently open + * in exclusive mode. The exclusive mode is set using: + *
+     * SET EXCLUSIVE TRUE;
+     * 
+ */ + public static final int DATABASE_IS_IN_EXCLUSIVE_MODE = 90135; + + /** + * The error with code 90136 is thrown when + * trying to reference a window that does not exist. + * Example: + *
+     * SELECT LEAD(X) OVER W FROM TEST;
+     * 
+ */ + public static final int WINDOW_NOT_FOUND_1 = 90136; + + /** + * The error with code 90137 is thrown when + * trying to assign a value to something that is not a variable. + *
+     * SELECT AMOUNT, SET(@V, COALESCE(@V, 0)+AMOUNT) FROM TEST;
+     * 
+ */ + public static final int CAN_ONLY_ASSIGN_TO_VARIABLE_1 = 90137; + + /** + * The error with code 90138 is thrown when + * + * trying to open a persistent database using an incorrect database name. + * The name of a persistent database contains the path and file name prefix + * where the data is stored. The file name part of a database name must be + * at least two characters. + * + * Example of wrong usage: + *
+     * DriverManager.getConnection("jdbc:h2:~/t");
+     * DriverManager.getConnection("jdbc:h2:~/test/");
+     * 
+ * Correct: + *
+     * DriverManager.getConnection("jdbc:h2:~/te");
+     * DriverManager.getConnection("jdbc:h2:~/test/te");
+     * 
+ */ + public static final int INVALID_DATABASE_NAME_1 = 90138; + + /** + * The error with code 90139 is thrown when + * the specified public static Java method was not found in the class. + * Example: + *
+     * CREATE ALIAS TEST FOR "java.lang.Math.test";
+     * 
+ */ + public static final int PUBLIC_STATIC_JAVA_METHOD_NOT_FOUND_1 = 90139; + + /** + * The error with code 90140 is thrown when trying to update or + * delete a row in a result set if the statement was not created with + * updatable concurrency. Result sets are only updatable if the statement + * was created with updatable concurrency, and if the result set contains + * all columns of the primary key or of a unique index of a table. + */ + public static final int RESULT_SET_READONLY = 90140; + + /** + * The error with code 90141 is thrown when + * trying to change the java object serializer while there was already data + * in the database. The serializer of the database must be set when the + * database is empty. + */ + public static final int JAVA_OBJECT_SERIALIZER_CHANGE_WITH_DATA_TABLE = 90141; + + /** + * The error with code 90142 is thrown when + * trying to set zero for step size. + */ + public static final int STEP_SIZE_MUST_NOT_BE_ZERO = 90142; + + /** + * The error with code 90143 is thrown when + * trying to fetch a row from the primary index and the row is not there. + */ + public static final int ROW_NOT_FOUND_IN_PRIMARY_INDEX = 90143; + + /** + * The error with code 90144 is thrown when + * user trying to login into a database with AUTHREALM set and + * the target database doesn't have an authenticator defined + *

Authenticator experimental feature can be enabled by + *

+ *
+     * SET AUTHENTICATOR TRUE
+     * 
+ */ + public static final int AUTHENTICATOR_NOT_AVAILABLE = 90144; + + /** + * The error with code 90145 is thrown when trying to execute a + * SELECT statement with non-window aggregates, DISTINCT, GROUP BY, or + * HAVING clauses together with FOR UPDATE clause. + * + *
+     * SELECT DISTINCT NAME FOR UPDATE;
+     * SELECT MAX(VALUE) FOR UPDATE;
+     * 
+ */ + public static final int FOR_UPDATE_IS_NOT_ALLOWED_IN_DISTINCT_OR_GROUPED_SELECT = 90145; + + /** + * The error with code 90146 is thrown when trying to open a + * database that does not exist using the flag IFEXISTS=TRUE + *
+     * jdbc:h2:./database_that_does_not_exist
+     * 
+ */ + public static final int DATABASE_NOT_FOUND_WITH_IF_EXISTS_1 = 90146; + + /** + * The error with code 90147 is thrown when trying to execute a + * statement which closes the transaction (such as commit and rollback) and + * autocommit mode is on. + * + * @see org.h2.engine.SysProperties#FORCE_AUTOCOMMIT_OFF_ON_COMMIT + */ + public static final int METHOD_DISABLED_ON_AUTOCOMMIT_TRUE = 90147; + + /** + * The error with code 90148 is thrown when trying to access + * the current value of a sequence before execution of NEXT VALUE FOR + * sequenceName in the current session. Example: + * + *
+     * SELECT CURRENT VALUE FOR SEQUENCE XYZ;
+     * 
+ */ + public static final int CURRENT_SEQUENCE_VALUE_IS_NOT_DEFINED_IN_SESSION_1 = 90148; + + /** + * The error with code 90149 is thrown when trying to open a + * database that does not exist remotely without enabling remote database + * creation first. + *
+     * jdbc:h2:./database_that_does_not_exist
+     * 
+ */ + public static final int REMOTE_DATABASE_NOT_FOUND_1 = 90149; + + /** + * The error with code 90150 is thrown when + * trying to use an invalid precision. + * Example: + *
+     * CREATE TABLE TABLE1 ( FAIL INTERVAL YEAR(20) );
+     * 
+ */ + public static final int INVALID_VALUE_PRECISION = 90150; + + /** + * The error with code 90151 is thrown when + * trying to use an invalid scale or fractional seconds precision. + * Example: + *
+     * CREATE TABLE TABLE1 ( FAIL TIME(10) );
+     * 
+ */ + public static final int INVALID_VALUE_SCALE = 90151; + + /** + * The error with code 90152 is thrown when trying to manually + * drop a unique or primary key constraint that is referenced by a foreign + * key constraint without a CASCADE clause. + * + *
+     * CREATE TABLE PARENT(ID INT CONSTRAINT P1 PRIMARY KEY);
+     * CREATE TABLE CHILD(ID INT CONSTRAINT P2 PRIMARY KEY, CHILD INT CONSTRAINT C REFERENCES PARENT);
+     * ALTER TABLE PARENT DROP CONSTRAINT P1 RESTRICT;
+     * 
+ */ + public static final int CONSTRAINT_IS_USED_BY_CONSTRAINT_2 = 90152; + + /** + * The error with code 90153 is thrown when trying to reference + * a column of another data type when data types aren't comparable or don't + * have a session-independent compare order between each other. + * + *
+     * CREATE TABLE PARENT(T TIMESTAMP UNIQUE);
+     * CREATE TABLE CHILD(T TIMESTAMP WITH TIME ZONE REFERENCES PARENT(T));
+     * 
+ */ + public static final int UNCOMPARABLE_REFERENCED_COLUMN_2 = 90153; + + /** + * The error with code 90154 is thrown when trying to assign a + * value to a generated column. + * + *
+     * CREATE TABLE TEST(A INT, B INT GENERATED ALWAYS AS (A + 1));
+     * INSERT INTO TEST(A, B) VALUES (1, 1);
+     * 
+ */ + public static final int GENERATED_COLUMN_CANNOT_BE_ASSIGNED_1 = 90154; + + /** + * The error with code 90155 is thrown when trying to create a + * referential constraint that can update a referenced generated column. + * + *
+     * CREATE TABLE PARENT(ID INT PRIMARY KEY, K INT GENERATED ALWAYS AS (ID) UNIQUE);
+     * CREATE TABLE CHILD(ID INT PRIMARY KEY, P INT);
+     * ALTER TABLE CHILD ADD FOREIGN KEY(P) REFERENCES PARENT(K) ON DELETE SET NULL;
+     * 
+ */ + public static final int GENERATED_COLUMN_CANNOT_BE_UPDATABLE_BY_CONSTRAINT_2 = 90155; + + /** + * The error with code 90156 is thrown when trying to create a + * view or a table from a select and some expression doesn't have a column + * name or alias when it is required by a compatibility mode. + * + *
+     * SET MODE DB2;
+     * CREATE TABLE T1(A INT, B INT);
+     * CREATE TABLE T2 AS (SELECT A + B FROM T1) WITH DATA;
+     * 
+ */ + public static final int COLUMN_ALIAS_IS_NOT_SPECIFIED_1 = 90156; + + /** + * The error with code 90157 is thrown when the integer + * index that is used in the GROUP BY is not in the SELECT list + */ + public static final int GROUP_BY_NOT_IN_THE_RESULT = 90157; + + // next is 90158 + + private ErrorCode() { + // utility class + } + + /** + * INTERNAL + * @param errorCode to check + * @return true if provided code is common, false otherwise + */ + public static boolean isCommon(int errorCode) { + // this list is sorted alphabetically + switch (errorCode) { + case DATA_CONVERSION_ERROR_1: + case DUPLICATE_KEY_1: + case FUNCTION_ALIAS_ALREADY_EXISTS_1: + case LOCK_TIMEOUT_1: + case NULL_NOT_ALLOWED: + case NO_DATA_AVAILABLE: + case NUMERIC_VALUE_OUT_OF_RANGE_1: + case OBJECT_CLOSED: + case REFERENTIAL_INTEGRITY_VIOLATED_CHILD_EXISTS_1: + case REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1: + case SYNTAX_ERROR_1: + case SYNTAX_ERROR_2: + case TABLE_OR_VIEW_ALREADY_EXISTS_1: + case TABLE_OR_VIEW_NOT_FOUND_1: + case TABLE_OR_VIEW_NOT_FOUND_WITH_CANDIDATES_2: + case TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1: + case VALUE_TOO_LONG_2: + return true; + } + return false; + } + + /** + * INTERNAL + * @param errorCode to get state for + * @return error state + */ + public static String getState(int errorCode) { + // To convert SQLState to error code, replace + // 21S: 210, 42S: 421, HY: 50, C: 1, T: 2 + + switch (errorCode) { + + // 02: no data + case NO_DATA_AVAILABLE: return "02000"; + + // 07: dynamic SQL error + case INVALID_PARAMETER_COUNT_2: return "07001"; + + // 08: connection exception + case ERROR_OPENING_DATABASE_1: return "08000"; + + // 21: cardinality violation + case COLUMN_COUNT_DOES_NOT_MATCH: return "21S02"; + + // 22: data exception + case ARRAY_ELEMENT_ERROR_2: return "2202E"; + + // 42: syntax error or access rule violation + case TABLE_OR_VIEW_ALREADY_EXISTS_1: return "42S01"; + case TABLE_OR_VIEW_NOT_FOUND_1: return "42S02"; + case TABLE_OR_VIEW_NOT_FOUND_WITH_CANDIDATES_2: return "42S03"; + case TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1: return "42S04"; + case INDEX_ALREADY_EXISTS_1: return "42S11"; + case INDEX_NOT_FOUND_1: return "42S12"; + case DUPLICATE_COLUMN_NAME_1: return "42S21"; + case COLUMN_NOT_FOUND_1: return "42S22"; + case IDENTICAL_EXPRESSIONS_SHOULD_BE_USED: return "42S31"; + + // 0A: feature not supported + + // HZ: remote database access + + // HY + case GENERAL_ERROR_1: return "HY000"; + case UNKNOWN_DATA_TYPE_1: return "HY004"; + + case FEATURE_NOT_SUPPORTED_1: return "HYC00"; + case LOCK_TIMEOUT_1: return "HYT00"; + default: + return Integer.toString(errorCode); + } + } + +} diff --git a/h2/src/main/org/h2/api/H2Type.java b/h2/src/main/org/h2/api/H2Type.java new file mode 100644 index 0000000..ecc6131 --- /dev/null +++ b/h2/src/main/org/h2/api/H2Type.java @@ -0,0 +1,321 @@ +/* + * 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.api; + +import java.sql.SQLType; + +import org.h2.value.ExtTypeInfoRow; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * Data types of H2. + */ +public final class H2Type implements SQLType { + + // Character strings + + /** + * The CHARACTER data type. + */ + public static final H2Type CHAR = new H2Type(TypeInfo.getTypeInfo(Value.CHAR), "CHARACTER"); + + /** + * The CHARACTER VARYING data type. + */ + public static final H2Type VARCHAR = new H2Type(TypeInfo.TYPE_VARCHAR, "CHARACTER VARYING"); + + /** + * The CHARACTER LARGE OBJECT data type. + */ + public static final H2Type CLOB = new H2Type(TypeInfo.TYPE_CLOB, "CHARACTER LARGE OBJECT"); + + /** + * The VARCHAR_IGNORECASE data type. + */ + public static final H2Type VARCHAR_IGNORECASE = new H2Type(TypeInfo.TYPE_VARCHAR_IGNORECASE, "VARCHAR_IGNORECASE"); + + // Binary strings + + /** + * The BINARY data type. + */ + public static final H2Type BINARY = new H2Type(TypeInfo.getTypeInfo(Value.BINARY), "BINARY"); + + /** + * The BINARY VARYING data type. + */ + public static final H2Type VARBINARY = new H2Type(TypeInfo.TYPE_VARBINARY, "BINARY VARYING"); + + /** + * The BINARY LARGE OBJECT data type. + */ + public static final H2Type BLOB = new H2Type(TypeInfo.TYPE_BLOB, "BINARY LARGE OBJECT"); + + // Boolean + + /** + * The BOOLEAN data type + */ + public static final H2Type BOOLEAN = new H2Type(TypeInfo.TYPE_BOOLEAN, "BOOLEAN"); + + // Exact numeric data types + + /** + * The TINYINT data type. + */ + public static final H2Type TINYINT = new H2Type(TypeInfo.TYPE_TINYINT, "TINYINT"); + + /** + * The SMALLINT data type. + */ + public static final H2Type SMALLINT = new H2Type(TypeInfo.TYPE_SMALLINT, "SMALLINT"); + + /** + * The INTEGER data type. + */ + public static final H2Type INTEGER = new H2Type(TypeInfo.TYPE_INTEGER, "INTEGER"); + + /** + * The BIGINT data type. + */ + public static final H2Type BIGINT = new H2Type(TypeInfo.TYPE_BIGINT, "BIGINT"); + + /** + * The NUMERIC data type. + */ + public static final H2Type NUMERIC = new H2Type(TypeInfo.TYPE_NUMERIC_FLOATING_POINT, "NUMERIC"); + + // Approximate numeric data types + + /** + * The REAL data type. + */ + public static final H2Type REAL = new H2Type(TypeInfo.TYPE_REAL, "REAL"); + + /** + * The DOUBLE PRECISION data type. + */ + public static final H2Type DOUBLE_PRECISION = new H2Type(TypeInfo.TYPE_DOUBLE, "DOUBLE PRECISION"); + + // Decimal floating-point type + + /** + * The DECFLOAT data type. + */ + public static final H2Type DECFLOAT = new H2Type(TypeInfo.TYPE_DECFLOAT, "DECFLOAT"); + + // Date-time data types + + /** + * The DATE data type. + */ + public static final H2Type DATE = new H2Type(TypeInfo.TYPE_DATE, "DATE"); + + /** + * The TIME data type. + */ + public static final H2Type TIME = new H2Type(TypeInfo.TYPE_TIME, "TIME"); + + /** + * The TIME WITH TIME ZONE data type. + */ + public static final H2Type TIME_WITH_TIME_ZONE = new H2Type(TypeInfo.TYPE_TIME_TZ, "TIME WITH TIME ZONE"); + + /** + * The TIMESTAMP data type. + */ + public static final H2Type TIMESTAMP = new H2Type(TypeInfo.TYPE_TIMESTAMP, "TIMESTAMP"); + + /** + * The TIMESTAMP WITH TIME ZONE data type. + */ + public static final H2Type TIMESTAMP_WITH_TIME_ZONE = new H2Type(TypeInfo.TYPE_TIMESTAMP_TZ, + "TIMESTAMP WITH TIME ZONE"); + + // Intervals + + /** + * The INTERVAL YEAR data type. + */ + public static final H2Type INTERVAL_YEAR = new H2Type(TypeInfo.getTypeInfo(Value.INTERVAL_YEAR), "INTERVAL_YEAR"); + + /** + * The INTERVAL MONTH data type. + */ + public static final H2Type INTERVAL_MONTH = new H2Type(TypeInfo.getTypeInfo(Value.INTERVAL_MONTH), + "INTERVAL_MONTH"); + + /** + * The INTERVAL DAY data type. + */ + public static final H2Type INTERVAL_DAY = new H2Type(TypeInfo.TYPE_INTERVAL_DAY, "INTERVAL_DAY"); + + /** + * The INTERVAL HOUR data type. + */ + public static final H2Type INTERVAL_HOUR = new H2Type(TypeInfo.getTypeInfo(Value.INTERVAL_HOUR), "INTERVAL_HOUR"); + + /** + * The INTERVAL MINUTE data type. + */ + public static final H2Type INTERVAL_MINUTE = new H2Type(TypeInfo.getTypeInfo(Value.INTERVAL_MINUTE), + "INTERVAL_MINUTE"); + + /** + * The INTERVAL SECOND data type. + */ + public static final H2Type INTERVAL_SECOND = new H2Type(TypeInfo.getTypeInfo(Value.INTERVAL_SECOND), + "INTERVAL_SECOND"); + + /** + * The INTERVAL YEAR TO MONTH data type. + */ + public static final H2Type INTERVAL_YEAR_TO_MONTH = new H2Type(TypeInfo.TYPE_INTERVAL_YEAR_TO_MONTH, + "INTERVAL_YEAR_TO_MONTH"); + + /** + * The INTERVAL DAY TO HOUR data type. + */ + public static final H2Type INTERVAL_DAY_TO_HOUR = new H2Type(TypeInfo.getTypeInfo(Value.INTERVAL_DAY_TO_HOUR), + "INTERVAL_DAY_TO_HOUR"); + + /** + * The INTERVAL DAY TO MINUTE data type. + */ + public static final H2Type INTERVAL_DAY_TO_MINUTE = new H2Type(TypeInfo.getTypeInfo(Value.INTERVAL_DAY_TO_MINUTE), + "INTERVAL_DAY_TO_MINUTE"); + + /** + * The INTERVAL DAY TO SECOND data type. + */ + public static final H2Type INTERVAL_DAY_TO_SECOND = new H2Type(TypeInfo.TYPE_INTERVAL_DAY_TO_SECOND, + "INTERVAL_DAY_TO_SECOND"); + + /** + * The INTERVAL HOUR TO MINUTE data type. + */ + public static final H2Type INTERVAL_HOUR_TO_MINUTE = new H2Type( // + TypeInfo.getTypeInfo(Value.INTERVAL_HOUR_TO_MINUTE), "INTERVAL_HOUR_TO_MINUTE"); + + /** + * The INTERVAL HOUR TO SECOND data type. + */ + public static final H2Type INTERVAL_HOUR_TO_SECOND = new H2Type(TypeInfo.TYPE_INTERVAL_HOUR_TO_SECOND, + "INTERVAL_HOUR_TO_SECOND"); + + /** + * The INTERVAL MINUTE TO SECOND data type. + */ + public static final H2Type INTERVAL_MINUTE_TO_SECOND = new H2Type( + TypeInfo.getTypeInfo(Value.INTERVAL_MINUTE_TO_SECOND), "INTERVAL_MINUTE_TO_SECOND"); + + // Other JDBC + + /** + * The JAVA_OBJECT data type. + */ + public static final H2Type JAVA_OBJECT = new H2Type(TypeInfo.TYPE_JAVA_OBJECT, "JAVA_OBJECT"); + + // Other non-standard + + /** + * The ENUM data type. + */ + public static final H2Type ENUM = new H2Type(TypeInfo.TYPE_ENUM_UNDEFINED, "ENUM"); + + /** + * The GEOMETRY data type. + */ + public static final H2Type GEOMETRY = new H2Type(TypeInfo.TYPE_GEOMETRY, "GEOMETRY"); + + /** + * The JSON data type. + */ + public static final H2Type JSON = new H2Type(TypeInfo.TYPE_JSON, "JSON"); + + /** + * The UUID data type. + */ + public static final H2Type UUID = new H2Type(TypeInfo.TYPE_UUID, "UUID"); + + // Collections + + // Use arrayOf() for ARRAY + + // Use row() for ROW + + /** + * Returns ARRAY data type with the specified component type. + * + * @param componentType + * the type of elements + * @return ARRAY data type + */ + public static H2Type array(H2Type componentType) { + return new H2Type(TypeInfo.getTypeInfo(Value.ARRAY, -1L, -1, componentType.typeInfo), + "array(" + componentType.field + ')'); + } + + /** + * Returns ROW data type with specified types of fields and default names. + * + * @param fieldTypes + * the type of fields + * @return ROW data type + */ + public static H2Type row(H2Type... fieldTypes) { + int degree = fieldTypes.length; + TypeInfo[] row = new TypeInfo[degree]; + StringBuilder builder = new StringBuilder("row("); + for (int i = 0; i < degree; i++) { + H2Type t = fieldTypes[i]; + row[i] = t.typeInfo; + if (i > 0) { + builder.append(", "); + } + builder.append(t.field); + } + return new H2Type(TypeInfo.getTypeInfo(Value.ROW, -1L, -1, new ExtTypeInfoRow(row)), + builder.append(')').toString()); + } + + private TypeInfo typeInfo; + + private String field; + + private H2Type(TypeInfo typeInfo, String field) { + this.typeInfo = typeInfo; + this.field = "H2Type." + field; + } + + @Override + public String getName() { + return typeInfo.toString(); + } + + @Override + public String getVendor() { + return "com.h2database"; + } + + /** + * Returns the vendor specific type number for the data type. The returned + * value is actual only for the current version of H2. + * + * @return the vendor specific data type + */ + @Override + public Integer getVendorTypeNumber() { + return typeInfo.getValueType(); + } + + @Override + public String toString() { + return field; + } + +} diff --git a/h2/src/main/org/h2/api/Interval.java b/h2/src/main/org/h2/api/Interval.java new file mode 100644 index 0000000..42024b9 --- /dev/null +++ b/h2/src/main/org/h2/api/Interval.java @@ -0,0 +1,635 @@ +/* + * 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.api; + +import static org.h2.util.DateTimeUtils.NANOS_PER_MINUTE; +import static org.h2.util.DateTimeUtils.NANOS_PER_SECOND; + +import org.h2.message.DbException; +import org.h2.util.IntervalUtils; + +/** + * INTERVAL representation for result sets. + */ +public final class Interval { + + private final IntervalQualifier qualifier; + + /** + * {@code false} for zero or positive intervals, {@code true} for negative + * intervals. + */ + private final boolean negative; + + /** + * Non-negative long with value of leading field. For INTERVAL SECOND + * contains only integer part of seconds. + */ + private final long leading; + + /** + * Non-negative long with combined value of all remaining field, or 0 for + * single-field intervals, with exception for INTERVAL SECOND that uses this + * field to store fractional part of seconds measured in nanoseconds. + */ + private final long remaining; + + /** + * Creates a new INTERVAL YEAR. + * + * @param years + * years, |years|<1018 + * @return INTERVAL YEAR + */ + public static Interval ofYears(long years) { + return new Interval(IntervalQualifier.YEAR, years < 0, Math.abs(years), 0); + } + + /** + * Creates a new INTERVAL MONTH. + * + * @param months + * months, |months|<1018 + * @return INTERVAL MONTH + */ + public static Interval ofMonths(long months) { + return new Interval(IntervalQualifier.MONTH, months < 0, Math.abs(months), 0); + } + + /** + * Creates a new INTERVAL DAY. + * + * @param days + * days, |days|<1018 + * @return INTERVAL DAY + */ + public static Interval ofDays(long days) { + return new Interval(IntervalQualifier.DAY, days < 0, Math.abs(days), 0); + } + + /** + * Creates a new INTERVAL HOUR. + * + * @param hours + * hours, |hours|<1018 + * @return INTERVAL HOUR + */ + public static Interval ofHours(long hours) { + return new Interval(IntervalQualifier.HOUR, hours < 0, Math.abs(hours), 0); + } + + /** + * Creates a new INTERVAL MINUTE. + * + * @param minutes + * minutes, |minutes|<1018 + * @return interval + */ + public static Interval ofMinutes(long minutes) { + return new Interval(IntervalQualifier.MINUTE, minutes < 0, Math.abs(minutes), 0); + } + + /** + * Creates a new INTERVAL SECOND. + * + * @param seconds + * seconds, |seconds|<1018 + * @return INTERVAL SECOND + */ + public static Interval ofSeconds(long seconds) { + return new Interval(IntervalQualifier.SECOND, seconds < 0, Math.abs(seconds), 0); + } + + /** + * Creates a new INTERVAL SECOND. + * + *

+ * If both arguments are not equal to zero they should have the same sign. + *

+ * + * @param seconds + * seconds, |seconds|<1018 + * @param nanos + * nanoseconds, |nanos|<1,000,000,000 + * @return INTERVAL SECOND + */ + public static Interval ofSeconds(long seconds, int nanos) { + // Interval is negative if any field is negative + boolean negative = (seconds | nanos) < 0; + if (negative) { + // Ensure that all fields are negative or zero + if (seconds > 0 || nanos > 0) { + throw new IllegalArgumentException(); + } + // Make them positive + seconds = -seconds; + nanos = -nanos; + // Long.MIN_VALUE and Integer.MIN_VALUE will be rejected by + // constructor + } + return new Interval(IntervalQualifier.SECOND, negative, seconds, nanos); + } + + /** + * Creates a new INTERVAL SECOND. + * + * @param nanos + * nanoseconds (including seconds) + * @return INTERVAL SECOND + */ + public static Interval ofNanos(long nanos) { + boolean negative = nanos < 0; + if (negative) { + nanos = -nanos; + if (nanos < 0) { + // Long.MIN_VALUE = -9_223_372_036_854_775_808L + return new Interval(IntervalQualifier.SECOND, true, 9_223_372_036L, 854_775_808); + } + } + return new Interval(IntervalQualifier.SECOND, negative, nanos / NANOS_PER_SECOND, nanos % NANOS_PER_SECOND); + } + + /** + * Creates a new INTERVAL YEAR TO MONTH. + * + *

+ * If both arguments are not equal to zero they should have the same sign. + *

+ * + * @param years + * years, |years|<1018 + * @param months + * months, |months|<12 + * @return INTERVAL YEAR TO MONTH + */ + public static Interval ofYearsMonths(long years, int months) { + // Interval is negative if any field is negative + boolean negative = (years | months) < 0; + if (negative) { + // Ensure that all fields are negative or zero + if (years > 0 || months > 0) { + throw new IllegalArgumentException(); + } + // Make them positive + years = -years; + months = -months; + // Long.MIN_VALUE and Integer.MIN_VALUE will be rejected by + // constructor + } + return new Interval(IntervalQualifier.YEAR_TO_MONTH, negative, years, months); + } + + /** + * Creates a new INTERVAL DAY TO HOUR. + * + *

+ * If both arguments are not equal to zero they should have the same sign. + *

+ * + * @param days + * days, |days|<1018 + * @param hours + * hours, |hours|<24 + * @return INTERVAL DAY TO HOUR + */ + public static Interval ofDaysHours(long days, int hours) { + // Interval is negative if any field is negative + boolean negative = (days | hours) < 0; + if (negative) { + // Ensure that all fields are negative or zero + if (days > 0 || hours > 0) { + throw new IllegalArgumentException(); + } + // Make them positive + days = -days; + hours = -hours; + // Long.MIN_VALUE and Integer.MIN_VALUE will be rejected by + // constructor + } + return new Interval(IntervalQualifier.DAY_TO_HOUR, negative, days, hours); + } + + /** + * Creates a new INTERVAL DAY TO MINUTE. + * + *

+ * Non-zero arguments should have the same sign. + *

+ * + * @param days + * days, |days|<1018 + * @param hours + * hours, |hours|<24 + * @param minutes + * minutes, |minutes|<60 + * @return INTERVAL DAY TO MINUTE + */ + public static Interval ofDaysHoursMinutes(long days, int hours, int minutes) { + // Interval is negative if any field is negative + boolean negative = (days | hours | minutes) < 0; + if (negative) { + // Ensure that all fields are negative or zero + if (days > 0 || hours > 0 || minutes > 0) { + throw new IllegalArgumentException(); + } + // Make them positive + days = -days; + hours = -hours; + minutes = -minutes; + if ((hours | minutes) < 0) { + // Integer.MIN_VALUE + throw new IllegalArgumentException(); + } + // days = Long.MIN_VALUE will be rejected by constructor + } + // Check only minutes. + // Overflow in days or hours will be detected by constructor + if (minutes >= 60) { + throw new IllegalArgumentException(); + } + return new Interval(IntervalQualifier.DAY_TO_MINUTE, negative, days, hours * 60L + minutes); + } + + /** + * Creates a new INTERVAL DAY TO SECOND. + * + *

+ * Non-zero arguments should have the same sign. + *

+ * + * @param days + * days, |days|<1018 + * @param hours + * hours, |hours|<24 + * @param minutes + * minutes, |minutes|<60 + * @param seconds + * seconds, |seconds|<60 + * @return INTERVAL DAY TO SECOND + */ + public static Interval ofDaysHoursMinutesSeconds(long days, int hours, int minutes, int seconds) { + return ofDaysHoursMinutesNanos(days, hours, minutes, seconds * NANOS_PER_SECOND); + } + + /** + * Creates a new INTERVAL DAY TO SECOND. + * + *

+ * Non-zero arguments should have the same sign. + *

+ * + * @param days + * days, |days|<1018 + * @param hours + * hours, |hours|<24 + * @param minutes + * minutes, |minutes|<60 + * @param nanos + * nanoseconds, |nanos|<60,000,000,000 + * @return INTERVAL DAY TO SECOND + */ + public static Interval ofDaysHoursMinutesNanos(long days, int hours, int minutes, long nanos) { + // Interval is negative if any field is negative + boolean negative = (days | hours | minutes | nanos) < 0; + if (negative) { + // Ensure that all fields are negative or zero + if (days > 0 || hours > 0 || minutes > 0 || nanos > 0) { + throw new IllegalArgumentException(); + } + // Make them positive + days = -days; + hours = -hours; + minutes = -minutes; + nanos = -nanos; + if ((hours | minutes | nanos) < 0) { + // Integer.MIN_VALUE, Long.MIN_VALUE + throw new IllegalArgumentException(); + } + // days = Long.MIN_VALUE will be rejected by constructor + } + // Check only minutes and nanoseconds. + // Overflow in days or hours will be detected by constructor + if (minutes >= 60 || nanos >= NANOS_PER_MINUTE) { + throw new IllegalArgumentException(); + } + return new Interval(IntervalQualifier.DAY_TO_SECOND, negative, days, + (hours * 60L + minutes) * NANOS_PER_MINUTE + nanos); + } + + /** + * Creates a new INTERVAL HOUR TO MINUTE. + * + *

+ * If both arguments are not equal to zero they should have the same sign. + *

+ * + * @param hours + * hours, |hours|<1018 + * @param minutes + * minutes, |minutes|<60 + * @return INTERVAL HOUR TO MINUTE + */ + public static Interval ofHoursMinutes(long hours, int minutes) { + // Interval is negative if any field is negative + boolean negative = (hours | minutes) < 0; + if (negative) { + // Ensure that all fields are negative or zero + if (hours > 0 || minutes > 0) { + throw new IllegalArgumentException(); + } + // Make them positive + hours = -hours; + minutes = -minutes; + // Long.MIN_VALUE and Integer.MIN_VALUE will be rejected by + // constructor + } + return new Interval(IntervalQualifier.HOUR_TO_MINUTE, negative, hours, minutes); + } + + /** + * Creates a new INTERVAL HOUR TO SECOND. + * + *

+ * Non-zero arguments should have the same sign. + *

+ * + * @param hours + * hours, |hours|<1018 + * @param minutes + * minutes, |minutes|<60 + * @param seconds + * seconds, |seconds|<60 + * @return INTERVAL HOUR TO SECOND + */ + public static Interval ofHoursMinutesSeconds(long hours, int minutes, int seconds) { + return ofHoursMinutesNanos(hours, minutes, seconds * NANOS_PER_SECOND); + } + + /** + * Creates a new INTERVAL HOUR TO SECOND. + * + *

+ * Non-zero arguments should have the same sign. + *

+ * + * @param hours + * hours, |hours|<1018 + * @param minutes + * minutes, |minutes|<60 + * @param nanos + * nanoseconds, |seconds|<60,000,000,000 + * @return INTERVAL HOUR TO SECOND + */ + public static Interval ofHoursMinutesNanos(long hours, int minutes, long nanos) { + // Interval is negative if any field is negative + boolean negative = (hours | minutes | nanos) < 0; + if (negative) { + // Ensure that all fields are negative or zero + if (hours > 0 || minutes > 0 || nanos > 0) { + throw new IllegalArgumentException(); + } + // Make them positive + hours = -hours; + minutes = -minutes; + nanos = -nanos; + if ((minutes | nanos) < 0) { + // Integer.MIN_VALUE, Long.MIN_VALUE + throw new IllegalArgumentException(); + } + // hours = Long.MIN_VALUE will be rejected by constructor + } + // Check only nanoseconds. + // Overflow in hours or minutes will be detected by constructor + if (nanos >= NANOS_PER_MINUTE) { + throw new IllegalArgumentException(); + } + return new Interval(IntervalQualifier.HOUR_TO_SECOND, negative, hours, minutes * NANOS_PER_MINUTE + nanos); + } + + /** + * Creates a new INTERVAL MINUTE TO SECOND. + * + *

+ * If both arguments are not equal to zero they should have the same sign. + *

+ * + * @param minutes + * minutes, |minutes|<1018 + * @param seconds + * seconds, |seconds|<60 + * @return INTERVAL MINUTE TO SECOND + */ + public static Interval ofMinutesSeconds(long minutes, int seconds) { + return ofMinutesNanos(minutes, seconds * NANOS_PER_SECOND); + } + + /** + * Creates a new INTERVAL MINUTE TO SECOND. + * + *

+ * If both arguments are not equal to zero they should have the same sign. + *

+ * + * @param minutes + * minutes, |minutes|<1018 + * @param nanos + * nanoseconds, |nanos|<60,000,000,000 + * @return INTERVAL MINUTE TO SECOND + */ + public static Interval ofMinutesNanos(long minutes, long nanos) { + // Interval is negative if any field is negative + boolean negative = (minutes | nanos) < 0; + if (negative) { + // Ensure that all fields are negative or zero + if (minutes > 0 || nanos > 0) { + throw new IllegalArgumentException(); + } + // Make them positive + minutes = -minutes; + nanos = -nanos; + // Long.MIN_VALUE will be rejected by constructor + } + return new Interval(IntervalQualifier.MINUTE_TO_SECOND, negative, minutes, nanos); + } + + /** + * Creates a new interval. Do not use this constructor, use static methods + * instead. + * + * @param qualifier + * qualifier + * @param negative + * whether interval is negative + * @param leading + * value of leading field + * @param remaining + * combined value of all remaining fields + */ + public Interval(IntervalQualifier qualifier, boolean negative, long leading, long remaining) { + this.qualifier = qualifier; + try { + this.negative = IntervalUtils.validateInterval(qualifier, negative, leading, remaining); + } catch (DbException e) { + throw new IllegalArgumentException(); + } + this.leading = leading; + this.remaining = remaining; + } + + /** + * Returns qualifier of this interval. + * + * @return qualifier + */ + public IntervalQualifier getQualifier() { + return qualifier; + } + + /** + * Returns where the interval is negative. + * + * @return where the interval is negative + */ + public boolean isNegative() { + return negative; + } + + /** + * Returns value of leading field of this interval. For {@code SECOND} + * intervals returns integer part of seconds. + * + * @return value of leading field + */ + public long getLeading() { + return leading; + } + + /** + * Returns combined value of remaining fields of this interval. For + * {@code SECOND} intervals returns nanoseconds. + * + * @return combined value of remaining fields + */ + public long getRemaining() { + return remaining; + } + + /** + * Returns years value, if any. + * + * @return years, or 0 + */ + public long getYears() { + return IntervalUtils.yearsFromInterval(qualifier, negative, leading, remaining); + } + + /** + * Returns months value, if any. + * + * @return months, or 0 + */ + public long getMonths() { + return IntervalUtils.monthsFromInterval(qualifier, negative, leading, remaining); + } + + /** + * Returns days value, if any. + * + * @return days, or 0 + */ + public long getDays() { + return IntervalUtils.daysFromInterval(qualifier, negative, leading, remaining); + } + + /** + * Returns hours value, if any. + * + * @return hours, or 0 + */ + public long getHours() { + return IntervalUtils.hoursFromInterval(qualifier, negative, leading, remaining); + } + + /** + * Returns minutes value, if any. + * + * @return minutes, or 0 + */ + public long getMinutes() { + return IntervalUtils.minutesFromInterval(qualifier, negative, leading, remaining); + } + + /** + * Returns value of integer part of seconds, if any. + * + * @return seconds, or 0 + */ + public long getSeconds() { + if (qualifier == IntervalQualifier.SECOND) { + return negative ? -leading : leading; + } + return getSecondsAndNanos() / NANOS_PER_SECOND; + } + + /** + * Returns value of fractional part of seconds (in nanoseconds), if any. + * + * @return nanoseconds, or 0 + */ + public long getNanosOfSecond() { + if (qualifier == IntervalQualifier.SECOND) { + return negative ? -remaining : remaining; + } + return getSecondsAndNanos() % NANOS_PER_SECOND; + } + + /** + * Returns seconds value measured in nanoseconds, if any. + * + *

+ * This method returns a long value that cannot fit all possible values of + * INTERVAL SECOND. For a very large intervals of this type use + * {@link #getSeconds()} and {@link #getNanosOfSecond()} instead. This + * method can be safely used for intervals of other day-time types. + *

+ * + * @return nanoseconds (including seconds), or 0 + */ + public long getSecondsAndNanos() { + return IntervalUtils.nanosFromInterval(qualifier, negative, leading, remaining); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + qualifier.hashCode(); + result = prime * result + (negative ? 1231 : 1237); + result = prime * result + (int) (leading ^ leading >>> 32); + result = prime * result + (int) (remaining ^ remaining >>> 32); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Interval)) { + return false; + } + Interval other = (Interval) obj; + return qualifier == other.qualifier && negative == other.negative && leading == other.leading + && remaining == other.remaining; + } + + @Override + public String toString() { + return IntervalUtils.appendInterval(new StringBuilder(), getQualifier(), negative, leading, remaining) + .toString(); + } + +} diff --git a/h2/src/main/org/h2/api/IntervalQualifier.java b/h2/src/main/org/h2/api/IntervalQualifier.java new file mode 100644 index 0000000..1772d17 --- /dev/null +++ b/h2/src/main/org/h2/api/IntervalQualifier.java @@ -0,0 +1,352 @@ +/* + * 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.api; + +/** + * Interval qualifier. + */ +public enum IntervalQualifier { + + /** + * {@code YEAR} + */ + YEAR, + + /** + * {@code MONTH} + */ + MONTH, + + /** + * {@code DAY} + */ + DAY, + + /** + * {@code HOUR} + */ + HOUR, + + /** + * {@code MINUTE} + */ + MINUTE, + + /** + * {@code SECOND} + */ + SECOND, + + /** + * {@code YEAR TO MONTH} + */ + YEAR_TO_MONTH, + + /** + * {@code DAY TO HOUR} + */ + DAY_TO_HOUR, + + /** + * {@code DAY TO MINUTE} + */ + DAY_TO_MINUTE, + + /** + * {@code DAY TO SECOND} + */ + DAY_TO_SECOND, + + /** + * {@code HOUR TO MINUTE} + */ + HOUR_TO_MINUTE, + + /** + * {@code HOUR TO SECOND} + */ + HOUR_TO_SECOND, + + /** + * {@code MINUTE TO SECOND} + */ + MINUTE_TO_SECOND; + + private final String string; + + /** + * Returns the interval qualifier with the specified ordinal value. + * + * @param ordinal + * Java ordinal value (0-based) + * @return interval qualifier with the specified ordinal value + */ + public static IntervalQualifier valueOf(int ordinal) { + switch (ordinal) { + case 0: + return YEAR; + case 1: + return MONTH; + case 2: + return DAY; + case 3: + return HOUR; + case 4: + return MINUTE; + case 5: + return SECOND; + case 6: + return YEAR_TO_MONTH; + case 7: + return DAY_TO_HOUR; + case 8: + return DAY_TO_MINUTE; + case 9: + return DAY_TO_SECOND; + case 10: + return HOUR_TO_MINUTE; + case 11: + return HOUR_TO_SECOND; + case 12: + return MINUTE_TO_SECOND; + default: + throw new IllegalArgumentException(); + } + } + + private IntervalQualifier() { + string = name().replace('_', ' ').intern(); + } + + /** + * Returns whether interval with this qualifier is a year-month interval. + * + * @return whether interval with this qualifier is a year-month interval + */ + public boolean isYearMonth() { + return this == YEAR || this == MONTH || this == YEAR_TO_MONTH; + } + + /** + * Returns whether interval with this qualifier is a day-time interval. + * + * @return whether interval with this qualifier is a day-time interval + */ + public boolean isDayTime() { + return !isYearMonth(); + } + + /** + * Returns whether interval with this qualifier has years. + * + * @return whether interval with this qualifier has years + */ + public boolean hasYears() { + return this == YEAR || this == YEAR_TO_MONTH; + } + + /** + * Returns whether interval with this qualifier has months. + * + * @return whether interval with this qualifier has months + */ + public boolean hasMonths() { + return this == MONTH || this == YEAR_TO_MONTH; + } + + /** + * Returns whether interval with this qualifier has days. + * + * @return whether interval with this qualifier has days + */ + public boolean hasDays() { + switch (this) { + case DAY: + case DAY_TO_HOUR: + case DAY_TO_MINUTE: + case DAY_TO_SECOND: + return true; + default: + return false; + } + } + + /** + * Returns whether interval with this qualifier has hours. + * + * @return whether interval with this qualifier has hours + */ + public boolean hasHours() { + switch (this) { + case HOUR: + case DAY_TO_HOUR: + case DAY_TO_MINUTE: + case DAY_TO_SECOND: + case HOUR_TO_MINUTE: + case HOUR_TO_SECOND: + return true; + default: + return false; + } + } + + /** + * Returns whether interval with this qualifier has minutes. + * + * @return whether interval with this qualifier has minutes + */ + public boolean hasMinutes() { + switch (this) { + case MINUTE: + case DAY_TO_MINUTE: + case DAY_TO_SECOND: + case HOUR_TO_MINUTE: + case HOUR_TO_SECOND: + case MINUTE_TO_SECOND: + return true; + default: + return false; + } + } + + /** + * Returns whether interval with this qualifier has seconds. + * + * @return whether interval with this qualifier has seconds + */ + public boolean hasSeconds() { + switch (this) { + case SECOND: + case DAY_TO_SECOND: + case HOUR_TO_SECOND: + case MINUTE_TO_SECOND: + return true; + default: + return false; + } + } + + /** + * Returns whether interval with this qualifier has multiple fields. + * + * @return whether interval with this qualifier has multiple fields + */ + public boolean hasMultipleFields() { + return ordinal() > 5; + } + + @Override + public String toString() { + return string; + } + + /** + * Returns full type name. + * + * @param precision precision, or {@code -1} + * @param scale fractional seconds precision, or {@code -1} + * @return full type name + */ + public String getTypeName(int precision, int scale) { + return getTypeName(new StringBuilder(), precision, scale, false).toString(); + } + + /** + * Appends full type name to the specified string builder. + * + * @param builder string builder + * @param precision precision, or {@code -1} + * @param scale fractional seconds precision, or {@code -1} + * @param qualifierOnly if {@code true}, don't add the INTERVAL prefix + * @return the specified string builder + */ + public StringBuilder getTypeName(StringBuilder builder, int precision, int scale, boolean qualifierOnly) { + if (!qualifierOnly) { + builder.append("INTERVAL "); + } + switch (this) { + case YEAR: + case MONTH: + case DAY: + case HOUR: + case MINUTE: + builder.append(string); + if (precision > 0) { + builder.append('(').append(precision).append(')'); + } + break; + case SECOND: + builder.append(string); + if (precision > 0 || scale >= 0) { + builder.append('(').append(precision > 0 ? precision : 2); + if (scale >= 0) { + builder.append(", ").append(scale); + } + builder.append(')'); + } + break; + case YEAR_TO_MONTH: + builder.append("YEAR"); + if (precision > 0) { + builder.append('(').append(precision).append(')'); + } + builder.append(" TO MONTH"); + break; + case DAY_TO_HOUR: + builder.append("DAY"); + if (precision > 0) { + builder.append('(').append(precision).append(')'); + } + builder.append(" TO HOUR"); + break; + case DAY_TO_MINUTE: + builder.append("DAY"); + if (precision > 0) { + builder.append('(').append(precision).append(')'); + } + builder.append(" TO MINUTE"); + break; + case DAY_TO_SECOND: + builder.append("DAY"); + if (precision > 0) { + builder.append('(').append(precision).append(')'); + } + builder.append(" TO SECOND"); + if (scale >= 0) { + builder.append('(').append(scale).append(')'); + } + break; + case HOUR_TO_MINUTE: + builder.append("HOUR"); + if (precision > 0) { + builder.append('(').append(precision).append(')'); + } + builder.append(" TO MINUTE"); + break; + case HOUR_TO_SECOND: + builder.append("HOUR"); + if (precision > 0) { + builder.append('(').append(precision).append(')'); + } + builder.append(" TO SECOND"); + if (scale >= 0) { + builder.append('(').append(scale).append(')'); + } + break; + case MINUTE_TO_SECOND: + builder.append("MINUTE"); + if (precision > 0) { + builder.append('(').append(precision).append(')'); + } + builder.append(" TO SECOND"); + if (scale >= 0) { + builder.append('(').append(scale).append(')'); + } + } + return builder; + } + +} diff --git a/h2/src/main/org/h2/api/JavaObjectSerializer.java b/h2/src/main/org/h2/api/JavaObjectSerializer.java new file mode 100644 index 0000000..9daa530 --- /dev/null +++ b/h2/src/main/org/h2/api/JavaObjectSerializer.java @@ -0,0 +1,34 @@ +/* + * 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.api; + +/** + * Custom serialization mechanism for java objects being stored in column of + * type OTHER. + * + * @author Sergi Vladykin + */ +public interface JavaObjectSerializer { + + /** + * Serialize object to byte array. + * + * @param obj the object to serialize + * @return the byte array of the serialized object + * @throws Exception on failure + */ + byte[] serialize(Object obj) throws Exception; + + /** + * Deserialize object from byte array. + * + * @param bytes the byte array of the serialized object + * @return the object + * @throws Exception on failure + */ + Object deserialize(byte[] bytes) throws Exception; + +} diff --git a/h2/src/main/org/h2/api/TableEngine.java b/h2/src/main/org/h2/api/TableEngine.java new file mode 100644 index 0000000..497b291 --- /dev/null +++ b/h2/src/main/org/h2/api/TableEngine.java @@ -0,0 +1,27 @@ +/* + * 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.api; + +import org.h2.command.ddl.CreateTableData; +import org.h2.table.Table; + +/** + * A class that implements this interface can create custom table + * implementations. + * + * @author Sergi Vladykin + */ +public interface TableEngine { + + /** + * Create new table. + * + * @param data the data to construct the table + * @return the created table + */ + Table createTable(CreateTableData data); + +} diff --git a/h2/src/main/org/h2/api/Trigger.java b/h2/src/main/org/h2/api/Trigger.java new file mode 100644 index 0000000..37a1cb7 --- /dev/null +++ b/h2/src/main/org/h2/api/Trigger.java @@ -0,0 +1,104 @@ +/* + * 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.api; + +import java.sql.Connection; +import java.sql.SQLException; + +/** + * A class that implements this interface can be used as a trigger. + */ +public interface Trigger { + + /** + * The trigger is called for INSERT statements. + */ + int INSERT = 1; + + /** + * The trigger is called for UPDATE statements. + */ + int UPDATE = 2; + + /** + * The trigger is called for DELETE statements. + */ + int DELETE = 4; + + /** + * The trigger is called for SELECT statements. + */ + int SELECT = 8; + + /** + * This method is called by the database engine once when initializing the + * trigger. It is called when the trigger is created, as well as when the + * database is opened. The type of operation is a bit field with the + * appropriate flags set. As an example, if the trigger is of type INSERT + * and UPDATE, then the parameter type is set to (INSERT | UPDATE). + * + * @param conn a connection to the database (a system connection) + * @param schemaName the name of the schema + * @param triggerName the name of the trigger used in the CREATE TRIGGER + * statement + * @param tableName the name of the table + * @param before whether the fire method is called before or after the + * operation is performed + * @param type the operation type: INSERT, UPDATE, DELETE, SELECT, or a + * combination (this parameter is a bit field) + * @throws SQLException on SQL exception + */ + default void init(Connection conn, String schemaName, String triggerName, + String tableName, boolean before, int type) throws SQLException { + // Does nothing by default + } + + /** + * This method is called for each triggered action. The method is called + * immediately when the operation occurred (before it is committed). A + * transaction rollback will also rollback the operations that were done + * within the trigger, if the operations occurred within the same database. + * If the trigger changes state outside the database, a rollback trigger + * should be used. + *

+ * The row arrays contain all columns of the table, in the same order + * as defined in the table. + *

+ *

+ * The trigger itself may change the data in the newRow array. + *

+ * + * @param conn a connection to the database + * @param oldRow the old row, or null if no old row is available (for + * INSERT) + * @param newRow the new row, or null if no new row is available (for + * DELETE) + * @throws SQLException if the operation must be undone + */ + void fire(Connection conn, Object[] oldRow, Object[] newRow) + throws SQLException; + + /** + * This method is called when the database is closed. + * If the method throws an exception, it will be logged, but + * closing the database will continue. + * + * @throws SQLException on SQL exception + */ + default void close() throws SQLException { + // Does nothing by default + } + + /** + * This method is called when the trigger is dropped. + * + * @throws SQLException on SQL exception + */ + default void remove() throws SQLException { + // Does nothing by default + } + +} diff --git a/h2/src/main/org/h2/api/UserToRolesMapper.java b/h2/src/main/org/h2/api/UserToRolesMapper.java new file mode 100644 index 0000000..55d5946 --- /dev/null +++ b/h2/src/main/org/h2/api/UserToRolesMapper.java @@ -0,0 +1,33 @@ +/* + * 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: Alessandro Ventura + */ +package org.h2.api; + +import java.util.Collection; + +import org.h2.security.auth.AuthenticationException; +import org.h2.security.auth.AuthenticationInfo; +import org.h2.security.auth.Configurable; + +/** + * A class that implement this interface can be used during authentication to + * map external users to database roles. + *

+ * This feature is experimental and subject to change + *

+ */ +public interface UserToRolesMapper extends Configurable { + + /** + * Map user identified by authentication info to a set of granted roles. + * + * @param authenticationInfo + * authentication information + * @return list of roles to be assigned to the user temporary + * @throws AuthenticationException + * on authentication exception + */ + Collection mapUserToRoles(AuthenticationInfo authenticationInfo) throws AuthenticationException; +} diff --git a/h2/src/main/org/h2/api/package.html b/h2/src/main/org/h2/api/package.html new file mode 100644 index 0000000..3dd9f31 --- /dev/null +++ b/h2/src/main/org/h2/api/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Contains interfaces for user-defined extensions, such as triggers and user-defined aggregate functions. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/bnf/Bnf.java b/h2/src/main/org/h2/bnf/Bnf.java new file mode 100644 index 0000000..c32ab48 --- /dev/null +++ b/h2/src/main/org/h2/bnf/Bnf.java @@ -0,0 +1,415 @@ +/* + * 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.bnf; + +import java.io.ByteArrayInputStream; +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; +import java.util.HashMap; +import java.util.StringTokenizer; +import org.h2.bnf.context.DbContextRule; +import org.h2.command.dml.Help; +import org.h2.tools.Csv; +import org.h2.util.StringUtils; +import org.h2.util.Utils; + +/** + * This class can read a file that is similar to BNF (Backus-Naur form). + * It is made specially to support SQL grammar. + */ +public class Bnf { + + /** + * The rule map. The key is lowercase, and all spaces + * are replaces with underscore. + */ + private final HashMap ruleMap = new HashMap<>(); + private String syntax; + private String currentToken; + private String[] tokens; + private char firstChar; + private int index; + private Rule lastRepeat; + private ArrayList statements; + private String currentTopic; + + /** + * Create an instance using the grammar specified in the CSV file. + * + * @param csv if not specified, the help.csv is used + * @return a new instance + * @throws SQLException on failure + * @throws IOException on failure + */ + 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), StandardCharsets.UTF_8); + } + bnf.parse(csv); + return bnf; + } + + /** + * Add an alias for a rule. + * + * @param name for example "procedure" + * @param replacement for example "@func@" + */ + public void addAlias(String name, String replacement) { + RuleHead head = ruleMap.get(replacement); + ruleMap.put(name, head); + } + + private void addFixedRule(String name, int fixedType) { + Rule rule = new RuleFixed(fixedType); + addRule(name, "Fixed", rule); + } + + private RuleHead addRule(String topic, String section, Rule rule) { + RuleHead head = new RuleHead(section, topic, rule); + String key = StringUtils.toLowerEnglish(topic.trim().replace(' ', '_')); + if (ruleMap.putIfAbsent(key, head) != null) { + throw new AssertionError("already exists: " + topic); + } + return head; + } + + private void parse(Reader reader) throws SQLException, IOException { + Rule functions = null; + statements = new ArrayList<>(); + Csv csv = new Csv(); + csv.setLineCommentCharacter('#'); + ResultSet rs = csv.read(reader, null); + while (rs.next()) { + String section = rs.getString("SECTION").trim(); + if (section.startsWith("System")) { + continue; + } + String topic = rs.getString("TOPIC"); + syntax = Help.stripAnnotationsFromSyntax(rs.getString("SYNTAX")); + currentTopic = section; + tokens = tokenize(); + index = 0; + Rule rule = parseRule(); + if (section.startsWith("Command")) { + rule = new RuleList(rule, new RuleElement(";\n\n", currentTopic), false); + } + RuleHead head = addRule(topic, section, rule); + if (section.startsWith("Function")) { + if (functions == null) { + functions = rule; + } else { + functions = new RuleList(rule, functions, true); + } + } else if (section.startsWith("Commands")) { + statements.add(head); + } + } + addRule("@func@", "Function", functions); + addFixedRule("@ymd@", RuleFixed.YMD); + addFixedRule("@hms@", RuleFixed.HMS); + addFixedRule("@nanos@", RuleFixed.NANOS); + addFixedRule("anything_except_single_quote", RuleFixed.ANY_EXCEPT_SINGLE_QUOTE); + addFixedRule("single_character", RuleFixed.ANY_EXCEPT_SINGLE_QUOTE); + addFixedRule("anything_except_double_quote", RuleFixed.ANY_EXCEPT_DOUBLE_QUOTE); + addFixedRule("anything_until_end_of_line", RuleFixed.ANY_UNTIL_EOL); + addFixedRule("anything_until_comment_start_or_end", RuleFixed.ANY_UNTIL_END); + addFixedRule("anything_except_two_dollar_signs", RuleFixed.ANY_EXCEPT_2_DOLLAR); + addFixedRule("anything", RuleFixed.ANY_WORD); + addFixedRule("@hex_start@", RuleFixed.HEX_START); + addFixedRule("@concat@", RuleFixed.CONCAT); + addFixedRule("@az_@", RuleFixed.AZ_UNDERSCORE); + addFixedRule("@af@", RuleFixed.AF); + addFixedRule("@digit@", RuleFixed.DIGIT); + addFixedRule("@open_bracket@", RuleFixed.OPEN_BRACKET); + addFixedRule("@close_bracket@", RuleFixed.CLOSE_BRACKET); + addFixedRule("json_text", RuleFixed.JSON_TEXT); + } + + /** + * Parse the syntax and let the rule call the visitor. + * + * @param visitor the visitor + * @param s the syntax to parse + */ + public void visit(BnfVisitor visitor, String s) { + this.syntax = s; + tokens = tokenize(); + index = 0; + Rule rule = parseRule(); + rule.setLinks(ruleMap); + rule.accept(visitor); + } + + /** + * Check whether the statement starts with a whitespace. + * + * @param s the statement + * @return if the statement is not empty and starts with a whitespace + */ + public static boolean startWithSpace(String s) { + return s.length() > 0 && Character.isWhitespace(s.charAt(0)); + } + + /** + * Convert convert ruleLink to rule_link. + * + * @param token the token + * @return the rule map key + */ + public static String getRuleMapKey(String token) { + StringBuilder buff = new StringBuilder(); + 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 { + buff.append(ch); + } + } + return buff.toString(); + } + + /** + * Get the rule head for the given title. + * + * @param title the title + * @return the rule head, or null + */ + public RuleHead getRuleHead(String title) { + return ruleMap.get(title); + } + + private Rule parseRule() { + read(); + return parseOr(); + } + + private Rule parseOr() { + Rule r = parseList(); + if (firstChar == '|') { + read(); + r = new RuleList(r, parseOr(), true); + } + lastRepeat = r; + return r; + } + + private Rule parseList() { + Rule r = parseToken(); + if (firstChar != '|' && firstChar != ']' && firstChar != '}' + && firstChar != 0) { + r = new RuleList(r, parseList(), false); + } + lastRepeat = r; + return r; + } + + private RuleExtension parseExtension(boolean compatibility) { + read(); + Rule r; + if (firstChar == '[') { + read(); + r = parseOr(); + r = new RuleOptional(r); + if (firstChar != ']') { + throw new AssertionError("expected ], got " + currentToken + " syntax:" + syntax); + } + } else if (firstChar == '{') { + read(); + r = parseOr(); + if (firstChar != '}') { + throw new AssertionError("expected }, got " + currentToken + " syntax:" + syntax); + } + } else { + r = parseOr(); + } + return new RuleExtension(r, compatibility); + } + + private Rule parseToken() { + Rule r; + if ((firstChar >= 'A' && firstChar <= 'Z') + || (firstChar >= 'a' && firstChar <= 'z')) { + // r = new RuleElement(currentToken+ " syntax:" + syntax); + r = new RuleElement(currentToken, currentTopic); + } else if (firstChar == '[') { + read(); + r = parseOr(); + r = new RuleOptional(r); + if (firstChar != ']') { + throw new AssertionError("expected ], got " + currentToken + " syntax:" + syntax); + } + } else if (firstChar == '{') { + read(); + r = parseOr(); + if (firstChar != '}') { + throw new AssertionError("expected }, got " + currentToken + " syntax:" + syntax); + } + } else if (firstChar == '@') { + if ("@commaDots@".equals(currentToken)) { + r = new RuleList(new RuleElement(",", currentTopic), lastRepeat, false); + r = new RuleRepeat(r, true); + } else if ("@dots@".equals(currentToken)) { + r = new RuleRepeat(lastRepeat, false); + } else if ("@c@".equals(currentToken)) { + r = parseExtension(true); + } else if ("@h2@".equals(currentToken)) { + r = parseExtension(false); + } else { + r = new RuleElement(currentToken, currentTopic); + } + } else { + r = new RuleElement(currentToken, currentTopic); + } + lastRepeat = r; + read(); + return r; + } + + private void read() { + if (index < tokens.length) { + currentToken = tokens[index++]; + firstChar = currentToken.charAt(0); + } else { + currentToken = ""; + firstChar = 0; + } + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < index; i++) { + builder.append(tokens[i]).append(' '); + } + builder.append("[*]"); + for (int i = index; i < tokens.length; i++) { + builder.append(' ').append(tokens[i]); + } + return builder.toString(); + } + + private String[] tokenize() { + ArrayList list = new ArrayList<>(); + syntax = StringUtils.replaceAll(syntax, "yyyy-MM-dd", "@ymd@"); + syntax = StringUtils.replaceAll(syntax, "hh:mm:ss", "@hms@"); + syntax = StringUtils.replaceAll(syntax, "hh:mm", "@hms@"); + syntax = StringUtils.replaceAll(syntax, "mm:ss", "@hms@"); + syntax = StringUtils.replaceAll(syntax, "nnnnnnnnn", "@nanos@"); + syntax = StringUtils.replaceAll(syntax, "function", "@func@"); + syntax = StringUtils.replaceAll(syntax, "0x", "@hexStart@"); + syntax = StringUtils.replaceAll(syntax, ",...", "@commaDots@"); + syntax = StringUtils.replaceAll(syntax, "...", "@dots@"); + syntax = StringUtils.replaceAll(syntax, "||", "@concat@"); + syntax = StringUtils.replaceAll(syntax, "a-z|_", "@az_@"); + syntax = StringUtils.replaceAll(syntax, "A-Z|_", "@az_@"); + syntax = StringUtils.replaceAll(syntax, "A-F", "@af@"); + syntax = StringUtils.replaceAll(syntax, "0-9", "@digit@"); + syntax = StringUtils.replaceAll(syntax, "'['", "@openBracket@"); + syntax = StringUtils.replaceAll(syntax, "']'", "@closeBracket@"); + StringTokenizer tokenizer = getTokenizer(syntax); + while (tokenizer.hasMoreTokens()) { + String s = tokenizer.nextToken(); + // avoid duplicate strings + s = StringUtils.cache(s); + if (s.length() == 1) { + if (" \r\n".indexOf(s.charAt(0)) >= 0) { + continue; + } + } + list.add(s); + } + return list.toArray(new String[0]); + } + + /** + * Get the list of tokens that can follow. + * This is the main autocomplete method. + * The returned map for the query 'S' may look like this: + *
+     * key: 1#SELECT, value: ELECT
+     * key: 1#SET, value: ET
+     * 
+ * + * @param query the start of the statement + * @return the map of possible token types / tokens + */ + public HashMap getNextTokenList(String query) { + Sentence sentence = new Sentence(); + sentence.setQuery(query); + try { + for (RuleHead head : statements) { + if (!head.getSection().startsWith("Commands")) { + continue; + } + sentence.start(); + if (head.getRule().autoComplete(sentence)) { + break; + } + } + } catch (IllegalStateException e) { + // ignore + } + return sentence.getNext(); + } + + /** + * Cross-link all statements with each other. + * This method is called after updating the topics. + */ + public void linkStatements() { + for (RuleHead r : ruleMap.values()) { + r.getRule().setLinks(ruleMap); + } + } + + /** + * Update a topic with a context specific rule. + * This is used for autocomplete support. + * + * @param topic the topic + * @param rule the database context rule + */ + public void updateTopic(String topic, DbContextRule rule) { + topic = StringUtils.toLowerEnglish(topic); + RuleHead head = ruleMap.get(topic); + if (head == null) { + head = new RuleHead("db", topic, rule); + ruleMap.put(topic, head); + statements.add(head); + } else { + head.setRule(rule); + } + } + + /** + * Get the list of possible statements. + * + * @return the list of statements + */ + public ArrayList getStatements() { + return statements; + } + + /** + * Get the tokenizer for the given syntax. + * + * @param s the syntax + * @return the tokenizer + */ + public static StringTokenizer getTokenizer(String s) { + return new StringTokenizer(s, " [](){}|.,\r\n<>:-+*/=\"!'$", true); + } + +} diff --git a/h2/src/main/org/h2/bnf/BnfVisitor.java b/h2/src/main/org/h2/bnf/BnfVisitor.java new file mode 100644 index 0000000..1a8ec01 --- /dev/null +++ b/h2/src/main/org/h2/bnf/BnfVisitor.java @@ -0,0 +1,69 @@ +/* + * 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.bnf; + +import java.util.ArrayList; + +/** + * The visitor interface for BNF rules. + */ +public interface BnfVisitor { + + /** + * Visit a rule element. + * + * @param keyword whether this is a keyword + * @param name the element name + * @param link the linked rule if it's not a keyword + */ + void visitRuleElement(boolean keyword, String name, Rule link); + + /** + * Visit a repeat rule. + * + * @param comma whether the comma is repeated as well + * @param rule the element to repeat + */ + void visitRuleRepeat(boolean comma, Rule rule); + + /** + * Visit a fixed rule. + * + * @param type the type + */ + void visitRuleFixed(int type); + + /** + * Visit a rule list. + * + * @param or true for OR, false for AND + * @param list the rules + */ + void visitRuleList(boolean or, ArrayList list); + + /** + * Visit an optional rule. + * + * @param rule the rule + */ + void visitRuleOptional(Rule rule); + + /** + * Visit an OR list of optional rules. + * + * @param list the optional rules + */ + void visitRuleOptional(ArrayList list); + + /** + * Visit a rule with non-standard extension. + * + * @param rule the rule + * @param compatibility whether this rule exists for compatibility only + */ + void visitRuleExtension(Rule rule, boolean compatibility); + +} diff --git a/h2/src/main/org/h2/bnf/Rule.java b/h2/src/main/org/h2/bnf/Rule.java new file mode 100644 index 0000000..0070e4e --- /dev/null +++ b/h2/src/main/org/h2/bnf/Rule.java @@ -0,0 +1,38 @@ +/* + * 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.bnf; + +import java.util.HashMap; + +/** + * Represents a BNF rule. + */ +public interface Rule { + + /** + * Update cross references. + * + * @param ruleMap the reference map + */ + void setLinks(HashMap ruleMap); + + /** + * Add the next possible token(s). If there was a match, the query in the + * sentence is updated (the matched token is removed). + * + * @param sentence the sentence context + * @return true if a full match + */ + boolean autoComplete(Sentence sentence); + + /** + * Call the visit method in the given visitor. + * + * @param visitor the visitor + */ + void accept(BnfVisitor visitor); + +} diff --git a/h2/src/main/org/h2/bnf/RuleElement.java b/h2/src/main/org/h2/bnf/RuleElement.java new file mode 100644 index 0000000..aca9085 --- /dev/null +++ b/h2/src/main/org/h2/bnf/RuleElement.java @@ -0,0 +1,85 @@ +/* + * 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.bnf; + +import java.util.HashMap; + +import org.h2.util.StringUtils; + +/** + * A single terminal rule in a BNF object. + */ +public class RuleElement implements Rule { + + private final boolean keyword; + private final String name; + private Rule link; + private final int type; + + public RuleElement(String name, String topic) { + this.name = name; + this.keyword = name.length() == 1 || + name.equals(StringUtils.toUpperEnglish(name)); + topic = StringUtils.toLowerEnglish(topic); + this.type = topic.startsWith("function") ? + Sentence.FUNCTION : Sentence.KEYWORD; + } + + @Override + public void accept(BnfVisitor visitor) { + visitor.visitRuleElement(keyword, name, link); + } + + @Override + public void setLinks(HashMap ruleMap) { + if (link != null) { + link.setLinks(ruleMap); + } + if (keyword) { + return; + } + String test = Bnf.getRuleMapKey(name); + for (int i = 0; i < test.length(); i++) { + String t = test.substring(i); + RuleHead r = ruleMap.get(t); + if (r != null) { + link = r.getRule(); + return; + } + } + throw new AssertionError("Unknown " + name + "/" + test); + } + + @Override + public boolean autoComplete(Sentence sentence) { + sentence.stopIfRequired(); + if (keyword) { + String query = sentence.getQuery(); + String q = query.trim(); + String up = sentence.getQueryUpper().trim(); + if (up.startsWith(name)) { + query = query.substring(name.length()); + while (!"_".equals(name) && Bnf.startWithSpace(query)) { + query = query.substring(1); + } + sentence.setQuery(query); + return true; + } else if (q.length() == 0 || name.startsWith(up)) { + if (q.length() < name.length()) { + sentence.add(name, name.substring(q.length()), type); + } + } + return false; + } + return link.autoComplete(sentence); + } + + @Override + public String toString() { + return name; + } + +} diff --git a/h2/src/main/org/h2/bnf/RuleExtension.java b/h2/src/main/org/h2/bnf/RuleExtension.java new file mode 100644 index 0000000..217a946 --- /dev/null +++ b/h2/src/main/org/h2/bnf/RuleExtension.java @@ -0,0 +1,49 @@ +/* + * 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.bnf; + +import java.util.HashMap; + +/** + * Represents a non-standard syntax. + */ +public class RuleExtension implements Rule { + + private final Rule rule; + private final boolean compatibility; + + private boolean mapSet; + + public RuleExtension(Rule rule, boolean compatibility) { + this.rule = rule; + this.compatibility = compatibility; + } + + @Override + public void accept(BnfVisitor visitor) { + visitor.visitRuleExtension(rule, compatibility); + } + + @Override + public void setLinks(HashMap ruleMap) { + if (!mapSet) { + rule.setLinks(ruleMap); + mapSet = true; + } + } + @Override + public boolean autoComplete(Sentence sentence) { + sentence.stopIfRequired(); + rule.autoComplete(sentence); + return true; + } + + @Override + public String toString() { + return (compatibility ? "@c@ " : "@h2@ ") + rule.toString(); + } + +} diff --git a/h2/src/main/org/h2/bnf/RuleFixed.java b/h2/src/main/org/h2/bnf/RuleFixed.java new file mode 100644 index 0000000..8557e0a --- /dev/null +++ b/h2/src/main/org/h2/bnf/RuleFixed.java @@ -0,0 +1,218 @@ +/* + * 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.bnf; + +import java.util.HashMap; + +/** + * Represents a hard coded terminal rule in a BNF object. + */ +public class RuleFixed implements Rule { + + public static final int YMD = 0, HMS = 1, NANOS = 2; + public static final int ANY_EXCEPT_SINGLE_QUOTE = 3; + public static final int ANY_EXCEPT_DOUBLE_QUOTE = 4; + public static final int ANY_UNTIL_EOL = 5; + public static final int ANY_UNTIL_END = 6; + public static final int ANY_WORD = 7; + public static final int ANY_EXCEPT_2_DOLLAR = 8; + public static final int HEX_START = 10, CONCAT = 11; + public static final int AZ_UNDERSCORE = 12, AF = 13, DIGIT = 14; + public static final int OPEN_BRACKET = 15, CLOSE_BRACKET = 16; + public static final int JSON_TEXT = 17; + + private final int type; + + RuleFixed(int type) { + this.type = type; + } + + @Override + public void accept(BnfVisitor visitor) { + visitor.visitRuleFixed(type); + } + + @Override + public void setLinks(HashMap ruleMap) { + // nothing to do + } + + @Override + public boolean autoComplete(Sentence sentence) { + sentence.stopIfRequired(); + String query = sentence.getQuery(); + String s = query; + boolean removeTrailingSpaces = false; + switch (type) { + case YMD: + while (s.length() > 0 && "0123456789-".indexOf(s.charAt(0)) >= 0) { + s = s.substring(1); + } + if (s.length() == 0) { + sentence.add("2006-01-01", "1", Sentence.KEYWORD); + } + // needed for timestamps + removeTrailingSpaces = true; + break; + case HMS: + while (s.length() > 0 && "0123456789:".indexOf(s.charAt(0)) >= 0) { + s = s.substring(1); + } + if (s.length() == 0) { + sentence.add("12:00:00", "1", Sentence.KEYWORD); + } + break; + case NANOS: + while (s.length() > 0 && Character.isDigit(s.charAt(0))) { + s = s.substring(1); + } + if (s.length() == 0) { + sentence.add("nanoseconds", "0", Sentence.KEYWORD); + } + removeTrailingSpaces = true; + break; + case ANY_EXCEPT_SINGLE_QUOTE: + while (true) { + while (s.length() > 0 && s.charAt(0) != '\'') { + s = s.substring(1); + } + if (s.startsWith("''")) { + s = s.substring(2); + } else { + break; + } + } + if (s.length() == 0) { + sentence.add("anything", "Hello World", Sentence.KEYWORD); + sentence.add("'", "'", Sentence.KEYWORD); + } + break; + case ANY_EXCEPT_2_DOLLAR: + while (s.length() > 0 && !s.startsWith("$$")) { + s = s.substring(1); + } + if (s.length() == 0) { + sentence.add("anything", "Hello World", Sentence.KEYWORD); + sentence.add("$$", "$$", Sentence.KEYWORD); + } + break; + case ANY_EXCEPT_DOUBLE_QUOTE: + while (true) { + while (s.length() > 0 && s.charAt(0) != '\"') { + s = s.substring(1); + } + if (s.startsWith("\"\"")) { + s = s.substring(2); + } else { + break; + } + } + if (s.length() == 0) { + sentence.add("anything", "identifier", Sentence.KEYWORD); + sentence.add("\"", "\"", Sentence.KEYWORD); + } + break; + case ANY_WORD: + case JSON_TEXT: + while (s.length() > 0 && !Bnf.startWithSpace(s)) { + s = s.substring(1); + } + if (s.length() == 0) { + sentence.add("anything", "anything", Sentence.KEYWORD); + } + break; + case HEX_START: + if (s.startsWith("0X") || s.startsWith("0x")) { + s = s.substring(2); + } else if ("0".equals(s)) { + sentence.add("0x", "x", Sentence.KEYWORD); + } else if (s.length() == 0) { + sentence.add("0x", "0x", Sentence.KEYWORD); + } + break; + case CONCAT: + if (s.equals("|")) { + sentence.add("||", "|", Sentence.KEYWORD); + } else if (s.startsWith("||")) { + s = s.substring(2); + } else if (s.length() == 0) { + sentence.add("||", "||", Sentence.KEYWORD); + } + removeTrailingSpaces = true; + break; + case AZ_UNDERSCORE: + if (s.length() > 0 && + (Character.isLetter(s.charAt(0)) || s.charAt(0) == '_')) { + s = s.substring(1); + } + if (s.length() == 0) { + sentence.add("character", "A", Sentence.KEYWORD); + } + break; + case AF: + if (s.length() > 0) { + char ch = Character.toUpperCase(s.charAt(0)); + if (ch >= 'A' && ch <= 'F') { + s = s.substring(1); + } + } + if (s.length() == 0) { + sentence.add("hex character", "0A", Sentence.KEYWORD); + } + break; + case DIGIT: + if (s.length() > 0 && Character.isDigit(s.charAt(0))) { + s = s.substring(1); + } + if (s.length() == 0) { + sentence.add("digit", "1", Sentence.KEYWORD); + } + break; + case OPEN_BRACKET: + if (s.length() == 0) { + sentence.add("[", "[", Sentence.KEYWORD); + } else if (s.charAt(0) == '[') { + s = s.substring(1); + } + removeTrailingSpaces = true; + break; + case CLOSE_BRACKET: + if (s.length() == 0) { + sentence.add("]", "]", Sentence.KEYWORD); + } else if (s.charAt(0) == ']') { + s = s.substring(1); + } + removeTrailingSpaces = true; + break; + // no autocomplete support for comments + // (comments are not reachable in the bnf tree) + case ANY_UNTIL_EOL: + case ANY_UNTIL_END: + default: + throw new AssertionError("type="+type); + } + if (!s.equals(query)) { + // can not always remove spaces here, because a repeat + // rule for a-z would remove multiple words + // but we have to remove spaces after '||' + // and after ']' + if (removeTrailingSpaces) { + while (Bnf.startWithSpace(s)) { + s = s.substring(1); + } + } + sentence.setQuery(s); + return true; + } + return false; + } + + @Override + public String toString() { + return "#" + type; + } + +} diff --git a/h2/src/main/org/h2/bnf/RuleHead.java b/h2/src/main/org/h2/bnf/RuleHead.java new file mode 100644 index 0000000..95891bd --- /dev/null +++ b/h2/src/main/org/h2/bnf/RuleHead.java @@ -0,0 +1,38 @@ +/* + * 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.bnf; + +/** + * Represents the head of a BNF rule. + */ +public class RuleHead { + private final String section; + private final String topic; + private Rule rule; + + RuleHead(String section, String topic, Rule rule) { + this.section = section; + this.topic = topic; + this.rule = rule; + } + + public String getTopic() { + return topic; + } + + public Rule getRule() { + return rule; + } + + void setRule(Rule rule) { + this.rule = rule; + } + + public String getSection() { + return section; + } + +} diff --git a/h2/src/main/org/h2/bnf/RuleList.java b/h2/src/main/org/h2/bnf/RuleList.java new file mode 100644 index 0000000..30e8f67 --- /dev/null +++ b/h2/src/main/org/h2/bnf/RuleList.java @@ -0,0 +1,90 @@ +/* + * 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.bnf; + +import java.util.ArrayList; +import java.util.HashMap; + +import org.h2.util.Utils; + +/** + * Represents a sequence of BNF rules, or a list of alternative rules. + */ +public class RuleList implements Rule { + + final boolean or; + final ArrayList list; + private boolean mapSet; + + public RuleList(Rule first, Rule next, boolean or) { + list = Utils.newSmallArrayList(); + if (first instanceof RuleList && ((RuleList) first).or == or) { + list.addAll(((RuleList) first).list); + } else { + list.add(first); + } + if (next instanceof RuleList && ((RuleList) next).or == or) { + list.addAll(((RuleList) next).list); + } else { + list.add(next); + } + this.or = or; + } + + @Override + public void accept(BnfVisitor visitor) { + visitor.visitRuleList(or, list); + } + + @Override + public void setLinks(HashMap ruleMap) { + if (!mapSet) { + for (Rule r : list) { + r.setLinks(ruleMap); + } + mapSet = true; + } + } + + @Override + public boolean autoComplete(Sentence sentence) { + sentence.stopIfRequired(); + String old = sentence.getQuery(); + if (or) { + for (Rule r : list) { + sentence.setQuery(old); + if (r.autoComplete(sentence)) { + return true; + } + } + return false; + } + for (Rule r : list) { + if (!r.autoComplete(sentence)) { + sentence.setQuery(old); + return false; + } + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + for (int i = 0, l = list.size(); i < l; i++) { + if (i > 0) { + if (or) { + builder.append(" | "); + } else { + builder.append(' '); + } + } + builder.append(list.get(i).toString()); + } + return builder.toString(); + } + +} diff --git a/h2/src/main/org/h2/bnf/RuleOptional.java b/h2/src/main/org/h2/bnf/RuleOptional.java new file mode 100644 index 0000000..52cfee7 --- /dev/null +++ b/h2/src/main/org/h2/bnf/RuleOptional.java @@ -0,0 +1,52 @@ +/* + * 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.bnf; + +import java.util.HashMap; + +/** + * Represents an optional BNF rule. + */ +public class RuleOptional implements Rule { + private final Rule rule; + private boolean mapSet; + + public RuleOptional(Rule rule) { + this.rule = rule; + } + + @Override + public void accept(BnfVisitor visitor) { + if (rule instanceof RuleList) { + RuleList ruleList = (RuleList) rule; + if (ruleList.or) { + visitor.visitRuleOptional(ruleList.list); + return; + } + } + visitor.visitRuleOptional(rule); + } + + @Override + public void setLinks(HashMap ruleMap) { + if (!mapSet) { + rule.setLinks(ruleMap); + mapSet = true; + } + } + @Override + public boolean autoComplete(Sentence sentence) { + sentence.stopIfRequired(); + rule.autoComplete(sentence); + return true; + } + + @Override + public String toString() { + return '[' + rule.toString() + ']'; + } + +} diff --git a/h2/src/main/org/h2/bnf/RuleRepeat.java b/h2/src/main/org/h2/bnf/RuleRepeat.java new file mode 100644 index 0000000..347d03a --- /dev/null +++ b/h2/src/main/org/h2/bnf/RuleRepeat.java @@ -0,0 +1,52 @@ +/* + * 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.bnf; + +import java.util.HashMap; + +/** + * Represents a loop in a BNF object. + */ +public class RuleRepeat implements Rule { + + private final Rule rule; + private final boolean comma; + + public RuleRepeat(Rule rule, boolean comma) { + this.rule = rule; + this.comma = comma; + } + + @Override + public void accept(BnfVisitor visitor) { + visitor.visitRuleRepeat(comma, rule); + } + + @Override + public void setLinks(HashMap ruleMap) { + // not required, because it's already linked + } + + @Override + public boolean autoComplete(Sentence sentence) { + sentence.stopIfRequired(); + while (rule.autoComplete(sentence)) { + // nothing to do + } + String s = sentence.getQuery(); + while (Bnf.startWithSpace(s)) { + s = s.substring(1); + } + sentence.setQuery(s); + return true; + } + + @Override + public String toString() { + return comma ? ", ..." : " ..."; + } + +} diff --git a/h2/src/main/org/h2/bnf/Sentence.java b/h2/src/main/org/h2/bnf/Sentence.java new file mode 100644 index 0000000..a0993b0 --- /dev/null +++ b/h2/src/main/org/h2/bnf/Sentence.java @@ -0,0 +1,221 @@ +/* + * 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.bnf; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Objects; + +import org.h2.bnf.context.DbSchema; +import org.h2.bnf.context.DbTableOrView; +import org.h2.util.StringUtils; + +/** + * A query context object. It contains the list of table and alias objects. + * Used for autocomplete. + */ +public class Sentence { + + /** + * This token type means the possible choices of the item depend on the + * context. For example the item represents a table name of the current + * database. + */ + public static final int CONTEXT = 0; + + /** + * The token type for a keyword. + */ + public static final int KEYWORD = 1; + + /** + * The token type for a function name. + */ + public static final int FUNCTION = 2; + + private static final int MAX_PROCESSING_TIME = 100; + + /** + * The map of next tokens in the form type#tokenName token. + */ + private final HashMap next = new HashMap<>(); + + /** + * The complete query string. + */ + private String query; + + /** + * The uppercase version of the query string. + */ + private String queryUpper; + + private long stopAtNs; + private DbSchema lastMatchedSchema; + private DbTableOrView lastMatchedTable; + private DbTableOrView lastTable; + private HashSet tables; + private HashMap aliases; + + /** + * Start the timer to make sure processing doesn't take too long. + */ + public void start() { + stopAtNs = System.nanoTime() + MAX_PROCESSING_TIME * 1_000_000L; + } + + /** + * Check if it's time to stop processing. + * Processing auto-complete shouldn't take more than a few milliseconds. + * If processing is stopped, this methods throws an IllegalStateException + */ + public void stopIfRequired() { + if (System.nanoTime() - stopAtNs > 0L) { + throw new IllegalStateException(); + } + } + + /** + * Add a word to the set of next tokens. + * + * @param n the token name + * @param string an example text + * @param type the token type + */ + public void add(String n, String string, int type) { + next.put(type+"#"+n, string); + } + + /** + * Add an alias name and object + * + * @param alias the alias name + * @param table the alias table + */ + public void addAlias(String alias, DbTableOrView table) { + if (aliases == null) { + aliases = new HashMap<>(); + } + aliases.put(alias, table); + } + + /** + * Add a table. + * + * @param table the table + */ + public void addTable(DbTableOrView table) { + lastTable = table; + if (tables == null) { + tables = new HashSet<>(); + } + tables.add(table); + } + + /** + * Get the set of tables. + * + * @return the set of tables + */ + public HashSet getTables() { + return tables; + } + + /** + * Get the alias map. + * + * @return the alias map + */ + public HashMap getAliases() { + return aliases; + } + + /** + * Get the last added table. + * + * @return the last table + */ + public DbTableOrView getLastTable() { + return lastTable; + } + + /** + * Get the last matched schema if the last match was a schema. + * + * @return the last schema or null + */ + public DbSchema getLastMatchedSchema() { + return lastMatchedSchema; + } + + /** + * Set the last matched schema if the last match was a schema, + * or null if it was not. + * + * @param schema the last matched schema or null + */ + public void setLastMatchedSchema(DbSchema schema) { + this.lastMatchedSchema = schema; + } + + /** + * Set the last matched table if the last match was a table. + * + * @param table the last matched table or null + */ + public void setLastMatchedTable(DbTableOrView table) { + this.lastMatchedTable = table; + } + + /** + * Get the last matched table if the last match was a table. + * + * @return the last table or null + */ + public DbTableOrView getLastMatchedTable() { + return lastMatchedTable; + } + + /** + * Set the query string. + * + * @param query the query string + */ + public void setQuery(String query) { + if (!Objects.equals(this.query, query)) { + this.query = query; + this.queryUpper = StringUtils.toUpperEnglish(query); + } + } + + /** + * Get the query string. + * + * @return the query + */ + public String getQuery() { + return query; + } + + /** + * Get the uppercase version of the query string. + * + * @return the uppercase query + */ + public String getQueryUpper() { + return queryUpper; + } + + /** + * Get the map of next tokens. + * + * @return the next token map + */ + public HashMap getNext() { + return next; + } + +} diff --git a/h2/src/main/org/h2/bnf/context/DbColumn.java b/h2/src/main/org/h2/bnf/context/DbColumn.java new file mode 100644 index 0000000..db187c3 --- /dev/null +++ b/h2/src/main/org/h2/bnf/context/DbColumn.java @@ -0,0 +1,116 @@ +/* + * 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.bnf.context; + +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Keeps the meta data information of a column. + * This class is used by the H2 Console. + */ +public class DbColumn { + + private final String name; + + private final String quotedName; + + private final String dataType; + + private final int position; + + private DbColumn(DbContents contents, ResultSet rs, boolean procedureColumn) + throws SQLException { + name = rs.getString("COLUMN_NAME"); + quotedName = contents.quoteIdentifier(name); + position = rs.getInt("ORDINAL_POSITION"); + if (contents.isH2() && !procedureColumn) { + dataType = rs.getString("COLUMN_TYPE"); + return; + } + String type = rs.getString("TYPE_NAME"); + // a procedures column size is identified by PRECISION, for table this + // is COLUMN_SIZE + String precisionColumnName, scaleColumnName; + if (procedureColumn) { + precisionColumnName = "PRECISION"; + scaleColumnName = "SCALE"; + } else { + precisionColumnName = "COLUMN_SIZE"; + scaleColumnName = "DECIMAL_DIGITS"; + } + int precision = rs.getInt(precisionColumnName); + if (precision > 0 && !contents.isSQLite()) { + int scale = rs.getInt(scaleColumnName); + if (scale > 0) { + type = type + '(' + precision + ", " + scale + ')'; + } else { + type = type + '(' + precision + ')'; + } + } + if (rs.getInt("NULLABLE") == DatabaseMetaData.columnNoNulls) { + type += " NOT NULL"; + } + dataType = type; + } + + /** + * Create a column from a DatabaseMetaData.getProcedureColumns row. + * + * @param contents the database contents + * @param rs the result set + * @return the column + * @throws SQLException on failure + */ + public static DbColumn getProcedureColumn(DbContents contents, ResultSet rs) + throws SQLException { + return new DbColumn(contents, rs, true); + } + + /** + * Create a column from a DatabaseMetaData.getColumns row. + * + * @param contents the database contents + * @param rs the result set + * @return the column + * @throws SQLException on failure + */ + public static DbColumn getColumn(DbContents contents, ResultSet rs) + throws SQLException { + return new DbColumn(contents, rs, false); + } + + /** + * @return The data type name (including precision and the NOT NULL flag if + * applicable). + */ + public String getDataType() { + return dataType; + } + + /** + * @return The column name. + */ + public String getName() { + return name; + } + + /** + * @return The quoted table name. + */ + public String getQuotedName() { + return quotedName; + } + + /** + * @return Column index + */ + public int getPosition() { + return position; + } + +} diff --git a/h2/src/main/org/h2/bnf/context/DbContents.java b/h2/src/main/org/h2/bnf/context/DbContents.java new file mode 100644 index 0000000..1cedefb --- /dev/null +++ b/h2/src/main/org/h2/bnf/context/DbContents.java @@ -0,0 +1,292 @@ +/* + * 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.bnf.context; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +import org.h2.engine.Session; +import org.h2.jdbc.JdbcConnection; +import org.h2.util.ParserUtil; +import org.h2.util.StringUtils; +import org.h2.util.Utils; + +/** + * Keeps meta data information about a database. + * This class is used by the H2 Console. + */ +public class DbContents { + + private DbSchema[] schemas; + private DbSchema defaultSchema; + private boolean isOracle; + private boolean isH2; + private boolean isPostgreSQL; + private boolean isDerby; + private boolean isSQLite; + private boolean isMySQL; + private boolean isFirebird; + private boolean isMSSQLServer; + private boolean isDB2; + + private boolean databaseToUpper, databaseToLower; + + private boolean mayHaveStandardViews = true; + + /** + * @return the default schema. + */ + public DbSchema getDefaultSchema() { + return defaultSchema; + } + + /** + * @return true if this is an Apache Derby database. + */ + public boolean isDerby() { + return isDerby; + } + + /** + * @return true if this is a Firebird database. + */ + public boolean isFirebird() { + return isFirebird; + } + + /** + * @return true if this is a H2 database. + */ + public boolean isH2() { + return isH2; + } + + /** + * @return true if this is a MS SQL Server database. + */ + public boolean isMSSQLServer() { + return isMSSQLServer; + } + + /** + * @return true if this is a MySQL database. + */ + public boolean isMySQL() { + return isMySQL; + } + + /** + * @return true if this is an Oracle database. + */ + public boolean isOracle() { + return isOracle; + } + + /** + * @return true if this is a PostgreSQL database. + */ + public boolean isPostgreSQL() { + return isPostgreSQL; + } + + /** + * @return true if this is an SQLite database. + */ + public boolean isSQLite() { + return isSQLite; + } + + /** + * @return true if this is an IBM DB2 database. + */ + public boolean isDB2() { + return isDB2; + } + + /** + * @return the list of schemas. + */ + public DbSchema[] getSchemas() { + return schemas; + } + + /** + * Returns whether standard INFORMATION_SCHEMA.VIEWS may be supported. + * + * @return whether standard INFORMATION_SCHEMA.VIEWS may be supported + */ + public boolean mayHaveStandardViews() { + return mayHaveStandardViews; + } + + /** + * @param mayHaveStandardViews + * whether standard INFORMATION_SCHEMA.VIEWS is detected as + * supported + */ + public void setMayHaveStandardViews(boolean mayHaveStandardViews) { + this.mayHaveStandardViews = mayHaveStandardViews; + } + + /** + * Read the contents of this database from the database meta data. + * + * @param url the database URL + * @param conn the connection + * @throws SQLException on failure + */ + public synchronized void readContents(String url, Connection conn) + throws SQLException { + isH2 = url.startsWith("jdbc:h2:"); + isDB2 = url.startsWith("jdbc:db2:"); + isSQLite = url.startsWith("jdbc:sqlite:"); + isOracle = url.startsWith("jdbc:oracle:"); + // the Vertica engine is based on PostgreSQL + isPostgreSQL = url.startsWith("jdbc:postgresql:") || url.startsWith("jdbc:vertica:"); + // isHSQLDB = url.startsWith("jdbc:hsqldb:"); + isMySQL = url.startsWith("jdbc:mysql:"); + isDerby = url.startsWith("jdbc:derby:"); + isFirebird = url.startsWith("jdbc:firebirdsql:"); + isMSSQLServer = url.startsWith("jdbc:sqlserver:"); + if (isH2) { + Session.StaticSettings settings = ((JdbcConnection) conn).getStaticSettings(); + databaseToUpper = settings.databaseToUpper; + databaseToLower = settings.databaseToLower; + }else if (isMySQL || isPostgreSQL) { + databaseToUpper = false; + databaseToLower = true; + } else { + databaseToUpper = true; + databaseToLower = false; + } + DatabaseMetaData meta = conn.getMetaData(); + String defaultSchemaName = getDefaultSchemaName(meta); + String[] schemaNames = getSchemaNames(meta); + schemas = new DbSchema[schemaNames.length]; + for (int i = 0; i < schemaNames.length; i++) { + String schemaName = schemaNames[i]; + boolean isDefault = defaultSchemaName == null || + defaultSchemaName.equals(schemaName); + DbSchema schema = new DbSchema(this, schemaName, isDefault); + if (isDefault) { + defaultSchema = schema; + } + schemas[i] = schema; + String[] tableTypes = { "TABLE", "SYSTEM TABLE", "VIEW", + "SYSTEM VIEW", "TABLE LINK", "SYNONYM", "EXTERNAL" }; + schema.readTables(meta, tableTypes); + if (!isPostgreSQL && !isDB2) { + schema.readProcedures(meta); + } + } + if (defaultSchema == null) { + String best = null; + for (DbSchema schema : schemas) { + if ("dbo".equals(schema.name)) { + // MS SQL Server + defaultSchema = schema; + break; + } + if (defaultSchema == null || + best == null || + schema.name.length() < best.length()) { + best = schema.name; + defaultSchema = schema; + } + } + } + } + + private String[] getSchemaNames(DatabaseMetaData meta) throws SQLException { + if (isMySQL || isSQLite) { + return new String[] { "" }; + } else if (isFirebird) { + return new String[] { null }; + } + ResultSet rs = meta.getSchemas(); + ArrayList schemaList = Utils.newSmallArrayList(); + while (rs.next()) { + String schema = rs.getString("TABLE_SCHEM"); + String[] ignoreNames = null; + if (isOracle) { + ignoreNames = new String[] { "CTXSYS", "DIP", "DBSNMP", + "DMSYS", "EXFSYS", "FLOWS_020100", "FLOWS_FILES", + "MDDATA", "MDSYS", "MGMT_VIEW", "OLAPSYS", "ORDSYS", + "ORDPLUGINS", "OUTLN", "SI_INFORMTN_SCHEMA", "SYS", + "SYSMAN", "SYSTEM", "TSMSYS", "WMSYS", "XDB" }; + } else if (isMSSQLServer) { + ignoreNames = new String[] { "sys", "db_accessadmin", + "db_backupoperator", "db_datareader", "db_datawriter", + "db_ddladmin", "db_denydatareader", + "db_denydatawriter", "db_owner", "db_securityadmin" }; + } else if (isDB2) { + ignoreNames = new String[] { "NULLID", "SYSFUN", + "SYSIBMINTERNAL", "SYSIBMTS", "SYSPROC", "SYSPUBLIC", + // not empty, but not sure what they contain + "SYSCAT", "SYSIBM", "SYSIBMADM", + "SYSSTAT", "SYSTOOLS", + }; + + } + if (ignoreNames != null) { + for (String ignore : ignoreNames) { + if (ignore.equals(schema)) { + schema = null; + break; + } + } + } + if (schema == null) { + continue; + } + schemaList.add(schema); + } + rs.close(); + return schemaList.toArray(new String[0]); + } + + private String getDefaultSchemaName(DatabaseMetaData meta) { + String defaultSchemaName = ""; + try { + if (isH2) { + return meta.storesLowerCaseIdentifiers() ? "public" : "PUBLIC"; + } else if (isOracle) { + return meta.getUserName(); + } else if (isPostgreSQL) { + return "public"; + } else if (isMySQL) { + return ""; + } else if (isDerby) { + return StringUtils.toUpperEnglish(meta.getUserName()); + } else if (isFirebird) { + return null; + } + } catch (SQLException e) { + // Ignore + } + return defaultSchemaName; + } + + /** + * Add double quotes around an identifier if required. + * + * @param identifier the identifier + * @return the quoted identifier + */ + public String quoteIdentifier(String identifier) { + if (identifier == null) { + return null; + } + if (ParserUtil.isSimpleIdentifier(identifier, databaseToUpper, databaseToLower)) { + return identifier; + } + return StringUtils.quoteIdentifier(identifier); + } + +} diff --git a/h2/src/main/org/h2/bnf/context/DbContextRule.java b/h2/src/main/org/h2/bnf/context/DbContextRule.java new file mode 100644 index 0000000..1d295cd --- /dev/null +++ b/h2/src/main/org/h2/bnf/context/DbContextRule.java @@ -0,0 +1,361 @@ +/* + * 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.bnf.context; + +import java.util.HashMap; +import java.util.HashSet; + +import org.h2.bnf.Bnf; +import org.h2.bnf.BnfVisitor; +import org.h2.bnf.Rule; +import org.h2.bnf.RuleElement; +import org.h2.bnf.RuleHead; +import org.h2.bnf.RuleList; +import org.h2.bnf.Sentence; +import org.h2.message.DbException; +import org.h2.util.ParserUtil; +import org.h2.util.StringUtils; + +/** + * A BNF terminal rule that is linked to the database context information. + * This class is used by the H2 Console, to support auto-complete. + */ +public class DbContextRule implements Rule { + + public static final int COLUMN = 0, TABLE = 1, TABLE_ALIAS = 2; + public static final int NEW_TABLE_ALIAS = 3; + public static final int COLUMN_ALIAS = 4, SCHEMA = 5, PROCEDURE = 6; + + private final DbContents contents; + private final int type; + + private String columnType; + + /** + * BNF terminal rule Constructor + * @param contents Extract rule from this component + * @param type Rule type, one of + * {@link DbContextRule#COLUMN}, + * {@link DbContextRule#TABLE}, + * {@link DbContextRule#TABLE_ALIAS}, + * {@link DbContextRule#NEW_TABLE_ALIAS}, + * {@link DbContextRule#COLUMN_ALIAS}, + * {@link DbContextRule#SCHEMA} + */ + public DbContextRule(DbContents contents, int type) { + this.contents = contents; + this.type = type; + } + + /** + * @param columnType COLUMN Auto completion can be filtered by column type + */ + public void setColumnType(String columnType) { + this.columnType = columnType; + } + + @Override + public void setLinks(HashMap ruleMap) { + // nothing to do + } + + @Override + public void accept(BnfVisitor visitor) { + // nothing to do + } + + @Override + public boolean autoComplete(Sentence sentence) { + String query = sentence.getQuery(), s = query; + String up = sentence.getQueryUpper(); + switch (type) { + case SCHEMA: { + DbSchema[] schemas = contents.getSchemas(); + String best = null; + DbSchema bestSchema = null; + for (DbSchema schema: schemas) { + String name = StringUtils.toUpperEnglish(schema.name); + if (up.startsWith(name)) { + if (best == null || name.length() > best.length()) { + best = name; + bestSchema = schema; + } + } else if (s.length() == 0 || name.startsWith(up)) { + if (s.length() < name.length()) { + sentence.add(name, name.substring(s.length()), type); + sentence.add(schema.quotedName + ".", + schema.quotedName.substring(s.length()) + ".", + Sentence.CONTEXT); + } + } + } + if (best != null) { + sentence.setLastMatchedSchema(bestSchema); + s = s.substring(best.length()); + } + break; + } + case TABLE: { + DbSchema schema = sentence.getLastMatchedSchema(); + if (schema == null) { + schema = contents.getDefaultSchema(); + } + DbTableOrView[] tables = schema.getTables(); + String best = null; + DbTableOrView bestTable = null; + for (DbTableOrView table : tables) { + String compare = up; + String name = StringUtils.toUpperEnglish(table.getName()); + if (table.getQuotedName().length() > name.length()) { + name = table.getQuotedName(); + compare = query; + } + if (compare.startsWith(name)) { + if (best == null || name.length() > best.length()) { + best = name; + bestTable = table; + } + } else if (s.length() == 0 || name.startsWith(compare)) { + if (s.length() < name.length()) { + sentence.add(table.getQuotedName(), + table.getQuotedName().substring(s.length()), + Sentence.CONTEXT); + } + } + } + if (best != null) { + sentence.setLastMatchedTable(bestTable); + sentence.addTable(bestTable); + s = s.substring(best.length()); + } + break; + } + case NEW_TABLE_ALIAS: + s = autoCompleteTableAlias(sentence, true); + break; + case TABLE_ALIAS: + s = autoCompleteTableAlias(sentence, false); + break; + case COLUMN_ALIAS: { + int i = 0; + if (query.indexOf(' ') < 0) { + break; + } + for (; i < up.length(); i++) { + char ch = up.charAt(i); + if (ch != '_' && !Character.isLetterOrDigit(ch)) { + break; + } + } + if (i == 0) { + break; + } + String alias = up.substring(0, i); + if (ParserUtil.isKeyword(alias, false)) { + break; + } + s = s.substring(alias.length()); + break; + } + case COLUMN: { + HashSet set = sentence.getTables(); + String best = null; + DbTableOrView last = sentence.getLastMatchedTable(); + if (last != null && last.getColumns() != null) { + for (DbColumn column : last.getColumns()) { + String compare = up; + String name = StringUtils.toUpperEnglish(column.getName()); + if (column.getQuotedName().length() > name.length()) { + name = column.getQuotedName(); + compare = query; + } + if (compare.startsWith(name) && testColumnType(column)) { + String b = s.substring(name.length()); + if (best == null || b.length() < best.length()) { + best = b; + } else if (s.length() == 0 || name.startsWith(compare)) { + if (s.length() < name.length()) { + sentence.add(column.getName(), + column.getName().substring(s.length()), + Sentence.CONTEXT); + } + } + } + } + } + for (DbSchema schema : contents.getSchemas()) { + for (DbTableOrView table : schema.getTables()) { + if (table != last && set != null && !set.contains(table)) { + continue; + } + if (table == null || table.getColumns() == null) { + continue; + } + for (DbColumn column : table.getColumns()) { + String name = StringUtils.toUpperEnglish(column + .getName()); + if (testColumnType(column)) { + if (up.startsWith(name)) { + String b = s.substring(name.length()); + if (best == null || b.length() < best.length()) { + best = b; + } + } else if (s.length() == 0 || name.startsWith(up)) { + if (s.length() < name.length()) { + sentence.add(column.getName(), + column.getName().substring(s.length()), + Sentence.CONTEXT); + } + } + } + } + } + } + if (best != null) { + s = best; + } + break; + } + case PROCEDURE: + autoCompleteProcedure(sentence); + break; + default: + throw DbException.getInternalError("type=" + type); + } + if (!s.equals(query)) { + while (Bnf.startWithSpace(s)) { + s = s.substring(1); + } + sentence.setQuery(s); + return true; + } + return false; + } + + private boolean testColumnType(DbColumn column) { + if (columnType == null) { + return true; + } + String type = column.getDataType(); + if (columnType.contains("CHAR") || columnType.contains("CLOB")) { + return type.contains("CHAR") || type.contains("CLOB"); + } + if (columnType.contains("BINARY") || columnType.contains("BLOB")) { + return type.contains("BINARY") || type.contains("BLOB"); + } + return type.contains(columnType); + } + + private void autoCompleteProcedure(Sentence sentence) { + DbSchema schema = sentence.getLastMatchedSchema(); + if (schema == null) { + schema = contents.getDefaultSchema(); + } + String incompleteSentence = sentence.getQueryUpper(); + String incompleteFunctionName = incompleteSentence; + int bracketIndex = incompleteSentence.indexOf('('); + if (bracketIndex != -1) { + incompleteFunctionName = StringUtils.trimSubstring(incompleteSentence, 0, bracketIndex); + } + + // Common elements + RuleElement openBracket = new RuleElement("(", "Function"); + RuleElement closeBracket = new RuleElement(")", "Function"); + RuleElement comma = new RuleElement(",", "Function"); + + // Fetch all elements + for (DbProcedure procedure : schema.getProcedures()) { + final String procName = procedure.getName(); + if (procName.startsWith(incompleteFunctionName)) { + // That's it, build a RuleList from this function + RuleElement procedureElement = new RuleElement(procName, + "Function"); + RuleList rl = new RuleList(procedureElement, openBracket, false); + // Go further only if the user use open bracket + if (incompleteSentence.contains("(")) { + for (DbColumn parameter : procedure.getParameters()) { + if (parameter.getPosition() > 1) { + rl = new RuleList(rl, comma, false); + } + DbContextRule columnRule = new DbContextRule(contents, + COLUMN); + String parameterType = parameter.getDataType(); + // Remove precision + if (parameterType.contains("(")) { + parameterType = parameterType.substring(0, + parameterType.indexOf('(')); + } + columnRule.setColumnType(parameterType); + rl = new RuleList(rl, columnRule, false); + } + rl = new RuleList(rl, closeBracket , false); + } + rl.autoComplete(sentence); + } + } + } + + private static String autoCompleteTableAlias(Sentence sentence, + boolean newAlias) { + String s = sentence.getQuery(); + String up = sentence.getQueryUpper(); + int i = 0; + for (; i < up.length(); i++) { + char ch = up.charAt(i); + if (ch != '_' && !Character.isLetterOrDigit(ch)) { + break; + } + } + if (i == 0) { + return s; + } + String alias = up.substring(0, i); + if ("SET".equals(alias) || ParserUtil.isKeyword(alias, false)) { + return s; + } + if (newAlias) { + sentence.addAlias(alias, sentence.getLastTable()); + } + HashMap map = sentence.getAliases(); + if ((map != null && map.containsKey(alias)) || + (sentence.getLastTable() == null)) { + if (newAlias && s.length() == alias.length()) { + return s; + } + s = s.substring(alias.length()); + if (s.length() == 0) { + sentence.add(alias + ".", ".", Sentence.CONTEXT); + } + return s; + } + HashSet tables = sentence.getTables(); + if (tables != null) { + String best = null; + for (DbTableOrView table : tables) { + String tableName = + StringUtils.toUpperEnglish(table.getName()); + if (alias.startsWith(tableName) && + (best == null || tableName.length() > best.length())) { + sentence.setLastMatchedTable(table); + best = tableName; + } else if (s.length() == 0 || tableName.startsWith(alias)) { + sentence.add(tableName + ".", + tableName.substring(s.length()) + ".", + Sentence.CONTEXT); + } + } + if (best != null) { + s = s.substring(best.length()); + if (s.length() == 0) { + sentence.add(alias + ".", ".", Sentence.CONTEXT); + } + return s; + } + } + return s; + } + +} diff --git a/h2/src/main/org/h2/bnf/context/DbProcedure.java b/h2/src/main/org/h2/bnf/context/DbProcedure.java new file mode 100644 index 0000000..0e9a71c --- /dev/null +++ b/h2/src/main/org/h2/bnf/context/DbProcedure.java @@ -0,0 +1,98 @@ +/* + * 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.bnf.context; + +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +import org.h2.util.Utils; + +/** + * Contains meta data information about a procedure. + * This class is used by the H2 Console. + */ +public class DbProcedure { + + private final DbSchema schema; + private final String name; + private final String quotedName; + private final boolean returnsResult; + private DbColumn[] parameters; + + public DbProcedure(DbSchema schema, ResultSet rs) throws SQLException { + this.schema = schema; + name = rs.getString("PROCEDURE_NAME"); + returnsResult = rs.getShort("PROCEDURE_TYPE") == + DatabaseMetaData.procedureReturnsResult; + quotedName = schema.getContents().quoteIdentifier(name); + } + + /** + * @return The schema this table belongs to. + */ + public DbSchema getSchema() { + return schema; + } + + /** + * @return The column list. + */ + public DbColumn[] getParameters() { + return parameters; + } + + /** + * @return The table name. + */ + public String getName() { + return name; + } + + /** + * @return The quoted table name. + */ + public String getQuotedName() { + return quotedName; + } + + /** + * @return True if this function return a value + */ + public boolean isReturnsResult() { + return returnsResult; + } + + /** + * Read the column for this table from the database meta data. + * + * @param meta the database meta data + * @throws SQLException on failure + */ + void readParameters(DatabaseMetaData meta) throws SQLException { + ResultSet rs = meta.getProcedureColumns(null, schema.name, name, null); + ArrayList list = Utils.newSmallArrayList(); + while (rs.next()) { + DbColumn column = DbColumn.getProcedureColumn(schema.getContents(), rs); + if (column.getPosition() > 0) { + // Not the return type + list.add(column); + } + } + rs.close(); + parameters = new DbColumn[list.size()]; + // Store the parameter in the good position [1-n] + for (int i = 0; i < parameters.length; i++) { + DbColumn column = list.get(i); + if (column.getPosition() > 0 + && column.getPosition() <= parameters.length) { + parameters[column.getPosition() - 1] = column; + } + } + } + +} diff --git a/h2/src/main/org/h2/bnf/context/DbSchema.java b/h2/src/main/org/h2/bnf/context/DbSchema.java new file mode 100644 index 0000000..f37e06f --- /dev/null +++ b/h2/src/main/org/h2/bnf/context/DbSchema.java @@ -0,0 +1,177 @@ +/* + * 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.bnf.context; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLSyntaxErrorException; +import java.util.ArrayList; + +import org.h2.engine.SysProperties; +import org.h2.util.StringUtils; +import org.h2.util.Utils; + +/** + * Contains meta data information about a database schema. + * This class is used by the H2 Console. + */ +public class DbSchema { + + private static final String COLUMNS_QUERY_H2_197 = "SELECT COLUMN_NAME, ORDINAL_POSITION, COLUMN_TYPE " + + "FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = ?1 AND TABLE_NAME = ?2"; + + private static final String COLUMNS_QUERY_H2_202 = "SELECT COLUMN_NAME, ORDINAL_POSITION, " + + "DATA_TYPE_SQL(?1, ?2, 'TABLE', ORDINAL_POSITION) COLUMN_TYPE " + + "FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = ?1 AND TABLE_NAME = ?2"; + + /** + * The schema name. + */ + public final String name; + + /** + * True if this is the default schema for this database. + */ + public final boolean isDefault; + + /** + * True if this is a system schema (for example the INFORMATION_SCHEMA). + */ + public final boolean isSystem; + + /** + * The quoted schema name. + */ + public final String quotedName; + + /** + * The database content container. + */ + private final DbContents contents; + + /** + * The table list. + */ + private DbTableOrView[] tables; + + /** + * The procedures list. + */ + private DbProcedure[] procedures; + + DbSchema(DbContents contents, String name, boolean isDefault) { + this.contents = contents; + this.name = name; + this.quotedName = contents.quoteIdentifier(name); + this.isDefault = isDefault; + if (name == null) { + // firebird + isSystem = true; + } else if ("INFORMATION_SCHEMA".equalsIgnoreCase(name)) { + isSystem = true; + } else if (!contents.isH2() && + StringUtils.toUpperEnglish(name).startsWith("INFO")) { + isSystem = true; + } else if (contents.isPostgreSQL() && + StringUtils.toUpperEnglish(name).startsWith("PG_")) { + isSystem = true; + } else if (contents.isDerby() && name.startsWith("SYS")) { + isSystem = true; + } else { + isSystem = false; + } + } + + /** + * @return The database content container. + */ + public DbContents getContents() { + return contents; + } + + /** + * @return The table list. + */ + public DbTableOrView[] getTables() { + return tables; + } + + /** + * @return The procedure list. + */ + public DbProcedure[] getProcedures() { + return procedures; + } + + /** + * Read all tables for this schema from the database meta data. + * + * @param meta the database meta data + * @param tableTypes the table types to read + * @throws SQLException on failure + */ + public void readTables(DatabaseMetaData meta, String[] tableTypes) + throws SQLException { + ResultSet rs = meta.getTables(null, name, null, tableTypes); + ArrayList list = new ArrayList<>(); + while (rs.next()) { + DbTableOrView table = new DbTableOrView(this, rs); + if (contents.isOracle() && table.getName().indexOf('$') > 0) { + continue; + } + list.add(table); + } + rs.close(); + tables = list.toArray(new DbTableOrView[0]); + if (tables.length < SysProperties.CONSOLE_MAX_TABLES_LIST_COLUMNS) { + try (PreparedStatement ps = contents.isH2() ? prepareColumnsQueryH2(meta.getConnection()) : null) { + for (DbTableOrView tab : tables) { + try { + tab.readColumns(meta, ps); + } catch (SQLException e) { + // MySQL: + // View '...' references invalid table(s) or column(s) + // or function(s) or definer/invoker of view + // lack rights to use them HY000/1356 + // ignore + } + } + } + } + } + + private static PreparedStatement prepareColumnsQueryH2(Connection connection) throws SQLException { + try { + return connection.prepareStatement(COLUMNS_QUERY_H2_202); + } catch (SQLSyntaxErrorException ex) { + return connection.prepareStatement(COLUMNS_QUERY_H2_197); + } + } + + /** + * Read all procedures in the database. + * + * @param meta the database meta data + * @throws SQLException Error while fetching procedures + */ + public void readProcedures(DatabaseMetaData meta) throws SQLException { + ResultSet rs = meta.getProcedures(null, name, null); + ArrayList list = Utils.newSmallArrayList(); + while (rs.next()) { + list.add(new DbProcedure(this, rs)); + } + rs.close(); + procedures = list.toArray(new DbProcedure[0]); + if (procedures.length < SysProperties.CONSOLE_MAX_PROCEDURES_LIST_COLUMNS) { + for (DbProcedure procedure : procedures) { + procedure.readParameters(meta); + } + } + } +} diff --git a/h2/src/main/org/h2/bnf/context/DbTableOrView.java b/h2/src/main/org/h2/bnf/context/DbTableOrView.java new file mode 100644 index 0000000..e97ffe4 --- /dev/null +++ b/h2/src/main/org/h2/bnf/context/DbTableOrView.java @@ -0,0 +1,114 @@ +/* + * 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.bnf.context; + +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +/** + * Contains meta data information about a table or a view. + * This class is used by the H2 Console. + */ +public class DbTableOrView { + + /** + * The schema this table belongs to. + */ + private final DbSchema schema; + + /** + * The table name. + */ + private final String name; + + /** + * The quoted table name. + */ + private final String quotedName; + + /** + * True if this represents a view. + */ + private final boolean isView; + + /** + * The column list. + */ + private DbColumn[] columns; + + public DbTableOrView(DbSchema schema, ResultSet rs) throws SQLException { + this.schema = schema; + name = rs.getString("TABLE_NAME"); + String type = rs.getString("TABLE_TYPE"); + isView = "VIEW".equals(type); + quotedName = schema.getContents().quoteIdentifier(name); + } + + /** + * @return The schema this table belongs to. + */ + public DbSchema getSchema() { + return schema; + } + + /** + * @return The column list. + */ + public DbColumn[] getColumns() { + return columns; + } + + /** + * @return The table name. + */ + public String getName() { + return name; + } + + /** + * @return True if this represents a view. + */ + public boolean isView() { + return isView; + } + + /** + * @return The quoted table name. + */ + public String getQuotedName() { + return quotedName; + } + + /** + * Read the column for this table from the database meta data. + * + * @param meta the database meta data + * @param ps prepared statement with custom query for H2 database, null for + * others + * @throws SQLException on failure + */ + public void readColumns(DatabaseMetaData meta, PreparedStatement ps) throws SQLException { + ResultSet rs; + if (schema.getContents().isH2()) { + ps.setString(1, schema.name); + ps.setString(2, name); + rs = ps.executeQuery(); + } else { + rs = meta.getColumns(null, schema.name, name, null); + } + ArrayList list = new ArrayList<>(); + while (rs.next()) { + DbColumn column = DbColumn.getColumn(schema.getContents(), rs); + list.add(column); + } + rs.close(); + columns = list.toArray(new DbColumn[0]); + } + +} diff --git a/h2/src/main/org/h2/bnf/context/package.html b/h2/src/main/org/h2/bnf/context/package.html new file mode 100644 index 0000000..0a6386f --- /dev/null +++ b/h2/src/main/org/h2/bnf/context/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Classes that provide context for the BNF tool, in order to provide BNF-based auto-complete. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/bnf/package.html b/h2/src/main/org/h2/bnf/package.html new file mode 100644 index 0000000..3629673 --- /dev/null +++ b/h2/src/main/org/h2/bnf/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +The implementation of the BNF (Backus-Naur form) parser and tool. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/command/Command.java b/h2/src/main/org/h2/command/Command.java new file mode 100644 index 0000000..f26fb68 --- /dev/null +++ b/h2/src/main/org/h2/command/Command.java @@ -0,0 +1,381 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Set; +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.engine.Database; +import org.h2.engine.DbObject; +import org.h2.engine.Mode.CharPadding; +import org.h2.engine.Session; +import org.h2.engine.SessionLocal; +import org.h2.expression.ParameterInterface; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.result.ResultInterface; +import org.h2.result.ResultWithGeneratedKeys; +import org.h2.result.ResultWithPaddedStrings; +import org.h2.util.Utils; + +/** + * Represents a SQL statement. This object is only used on the server side. + */ +public abstract class Command implements CommandInterface { + + /** + * The session. + */ + protected final SessionLocal session; + + /** + * The last start time. + */ + protected long startTimeNanos; + + /** + * The trace module. + */ + private final Trace trace; + + /** + * If this query was canceled. + */ + private volatile boolean cancel; + + private final String sql; + + private boolean canReuse; + + Command(SessionLocal session, String sql) { + this.session = session; + this.sql = sql; + trace = session.getDatabase().getTrace(Trace.COMMAND); + } + + /** + * Check if this command is transactional. + * If it is not, then it forces the current transaction to commit. + * + * @return true if it is + */ + public abstract boolean isTransactional(); + + /** + * Check if this command is a query. + * + * @return true if it is + */ + @Override + public abstract boolean isQuery(); + + /** + * Get the list of parameters. + * + * @return the list of parameters + */ + @Override + public abstract ArrayList getParameters(); + + /** + * Check if this command is read only. + * + * @return true if it is + */ + public abstract boolean isReadOnly(); + + /** + * Get an empty result set containing the meta data. + * + * @return an empty result set + */ + public abstract ResultInterface queryMeta(); + + /** + * Execute an updating statement (for example insert, delete, or update), if + * this is possible. + * + * @param generatedKeysRequest + * {@code false} if generated keys are not needed, {@code true} if + * generated keys should be configured automatically, {@code int[]} + * to specify column indices to return generated keys from, or + * {@code String[]} to specify column names to return generated keys + * from + * @return the update count and generated keys, if any + * @throws DbException if the command is not an updating statement + */ + public abstract ResultWithGeneratedKeys update(Object generatedKeysRequest); + + /** + * Execute a query statement, if this is possible. + * + * @param maxrows the maximum number of rows returned + * @return the local result set + * @throws DbException if the command is not a query + */ + public abstract ResultInterface query(long maxrows); + + @Override + public final ResultInterface getMetaData() { + return queryMeta(); + } + + /** + * Start the stopwatch. + */ + void start() { + if (trace.isInfoEnabled() || session.getDatabase().getQueryStatistics()) { + startTimeNanos = Utils.currentNanoTime(); + } + } + + void setProgress(int state) { + session.getDatabase().setProgress(state, sql, 0, 0); + } + + /** + * Check if this command has been canceled, and throw an exception if yes. + * + * @throws DbException if the statement has been canceled + */ + protected void checkCanceled() { + if (cancel) { + cancel = false; + throw DbException.get(ErrorCode.STATEMENT_WAS_CANCELED); + } + } + + @Override + public void stop() { + commitIfNonTransactional(); + if (isTransactional() && session.getAutoCommit()) { + session.commit(false); + } + if (trace.isInfoEnabled() && startTimeNanos != 0L) { + long timeMillis = (System.nanoTime() - startTimeNanos) / 1_000_000L; + if (timeMillis > Constants.SLOW_QUERY_LIMIT_MS) { + trace.info("slow query: {0} ms", timeMillis); + } + } + } + + /** + * Execute a query and return the result. + * This method prepares everything and calls {@link #query(long)} finally. + * + * @param maxrows the maximum number of rows to return + * @param scrollable if the result set must be scrollable (ignored) + * @return the result set + */ + @Override + public ResultInterface executeQuery(long maxrows, boolean scrollable) { + startTimeNanos = 0L; + long start = 0L; + Database database = session.getDatabase(); + session.waitIfExclusiveModeEnabled(); + boolean callStop = true; + //noinspection SynchronizationOnLocalVariableOrMethodParameter + synchronized (session) { + session.startStatementWithinTransaction(this); + Session oldSession = session.setThreadLocalSession(); + try { + while (true) { + database.checkPowerOff(); + try { + ResultInterface result = query(maxrows); + callStop = !result.isLazy(); + if (database.getMode().charPadding == CharPadding.IN_RESULT_SETS) { + return ResultWithPaddedStrings.get(result); + } + return result; + } catch (DbException e) { + // cannot retry DDL + if (isCurrentCommandADefineCommand()) { + throw e; + } + start = filterConcurrentUpdate(e, start); + } catch (OutOfMemoryError e) { + callStop = false; + // there is a serious problem: + // the transaction may be applied partially + // in this case we need to panic: + // close the database + database.shutdownImmediately(); + throw DbException.convert(e); + } catch (Throwable e) { + throw DbException.convert(e); + } + } + } catch (DbException e) { + e = e.addSQL(sql); + SQLException s = e.getSQLException(); + database.exceptionThrown(s, sql); + if (s.getErrorCode() == ErrorCode.OUT_OF_MEMORY) { + callStop = false; + database.shutdownImmediately(); + throw e; + } + database.checkPowerOff(); + throw e; + } finally { + session.resetThreadLocalSession(oldSession); + session.endStatement(); + if (callStop) { + stop(); + } + } + } + } + + @Override + public ResultWithGeneratedKeys executeUpdate(Object generatedKeysRequest) { + long start = 0; + Database database = session.getDatabase(); + session.waitIfExclusiveModeEnabled(); + boolean callStop = true; + //noinspection SynchronizationOnLocalVariableOrMethodParameter + synchronized (session) { + commitIfNonTransactional(); + SessionLocal.Savepoint rollback = session.setSavepoint(); + session.startStatementWithinTransaction(this); + DbException ex = null; + Session oldSession = session.setThreadLocalSession(); + try { + while (true) { + database.checkPowerOff(); + try { + return update(generatedKeysRequest); + } catch (DbException e) { + // cannot retry DDL + if (isCurrentCommandADefineCommand()) { + throw e; + } + start = filterConcurrentUpdate(e, start); + } catch (OutOfMemoryError e) { + callStop = false; + database.shutdownImmediately(); + throw DbException.convert(e); + } catch (Throwable e) { + throw DbException.convert(e); + } + } + } catch (DbException e) { + e = e.addSQL(sql); + SQLException s = e.getSQLException(); + database.exceptionThrown(s, sql); + if (s.getErrorCode() == ErrorCode.OUT_OF_MEMORY) { + callStop = false; + database.shutdownImmediately(); + throw e; + } + try { + database.checkPowerOff(); + if (s.getErrorCode() == ErrorCode.DEADLOCK_1) { + session.rollback(); + } else { + session.rollbackTo(rollback); + } + } catch (Throwable nested) { + e.addSuppressed(nested); + } + ex = e; + throw e; + } finally { + session.resetThreadLocalSession(oldSession); + try { + session.endStatement(); + if (callStop) { + stop(); + } + } catch (Throwable nested) { + if (ex == null) { + throw nested; + } else { + ex.addSuppressed(nested); + } + } + } + } + } + + private void commitIfNonTransactional() { + if (!isTransactional()) { + boolean autoCommit = session.getAutoCommit(); + session.commit(true); + if (!autoCommit && session.getAutoCommit()) { + session.begin(); + } + } + } + + private long filterConcurrentUpdate(DbException e, long start) { + int errorCode = e.getErrorCode(); + if (errorCode != ErrorCode.CONCURRENT_UPDATE_1 && errorCode != ErrorCode.ROW_NOT_FOUND_IN_PRIMARY_INDEX + && errorCode != ErrorCode.ROW_NOT_FOUND_WHEN_DELETING_1) { + throw e; + } + long now = Utils.currentNanoTime(); + if (start != 0L && now - start > session.getLockTimeout() * 1_000_000L) { + throw DbException.get(ErrorCode.LOCK_TIMEOUT_1, e); + } + return start == 0L ? now : start; + } + + @Override + public void close() { + canReuse = true; + } + + @Override + public void cancel() { + cancel = true; + } + + @Override + public String toString() { + return sql + Trace.formatParams(getParameters()); + } + + public boolean isCacheable() { + return false; + } + + /** + * Whether the command is already closed (in which case it can be re-used). + * + * @return true if it can be re-used + */ + public boolean canReuse() { + return canReuse; + } + + /** + * The command is now re-used, therefore reset the canReuse flag, and the + * parameter values. + */ + public void reuse() { + canReuse = false; + ArrayList parameters = getParameters(); + for (ParameterInterface param : parameters) { + param.setValue(null, true); + } + } + + public void setCanReuse(boolean canReuse) { + this.canReuse = canReuse; + } + + public abstract Set getDependencies(); + + /** + * Is the command we just tried to execute a DefineCommand (i.e. DDL). + * + * @return true if yes + */ + protected abstract boolean isCurrentCommandADefineCommand(); +} diff --git a/h2/src/main/org/h2/command/CommandContainer.java b/h2/src/main/org/h2/command/CommandContainer.java new file mode 100644 index 0000000..30fcf5b --- /dev/null +++ b/h2/src/main/org/h2/command/CommandContainer.java @@ -0,0 +1,317 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.h2.api.DatabaseEventListener; +import org.h2.api.ErrorCode; +import org.h2.command.ddl.DefineCommand; +import org.h2.command.dml.DataChangeStatement; +import org.h2.engine.Database; +import org.h2.engine.DbObject; +import org.h2.engine.DbSettings; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.expression.Parameter; +import org.h2.expression.ParameterInterface; +import org.h2.index.Index; +import org.h2.message.DbException; +import org.h2.result.LocalResult; +import org.h2.result.ResultInterface; +import org.h2.result.ResultTarget; +import org.h2.result.ResultWithGeneratedKeys; +import org.h2.table.Column; +import org.h2.table.DataChangeDeltaTable.ResultOption; +import org.h2.table.Table; +import org.h2.table.TableView; +import org.h2.util.StringUtils; +import org.h2.util.Utils; +import org.h2.value.Value; + +/** + * Represents a single SQL statements. + * It wraps a prepared statement. + */ +public class CommandContainer extends Command { + + /** + * Collector of generated keys. + */ + private static final class GeneratedKeysCollector implements ResultTarget { + + private final int[] indexes; + private final LocalResult result; + + GeneratedKeysCollector(int[] indexes, LocalResult result) { + this.indexes = indexes; + this.result = result; + } + + @Override + public void limitsWereApplied() { + // Nothing to do + } + + @Override + public long getRowCount() { + // Not required + return 0L; + } + + @Override + public void addRow(Value... values) { + int length = indexes.length; + Value[] row = new Value[length]; + for (int i = 0; i < length; i++) { + row[i] = values[indexes[i]]; + } + result.addRow(row); + } + + } + + private Prepared prepared; + private boolean readOnlyKnown; + private boolean readOnly; + + /** + * Clears CTE views for a specified statement. + * + * @param session the session + * @param prepared prepared statement + */ + static void clearCTE(SessionLocal session, Prepared prepared) { + List cteCleanups = prepared.getCteCleanups(); + if (cteCleanups != null) { + clearCTE(session, cteCleanups); + } + } + + /** + * Clears CTE views. + * + * @param session the session + * @param views list of view + */ + static void clearCTE(SessionLocal session, List views) { + for (TableView view : views) { + // check if view was previously deleted as their name is set to + // null + if (view.getName() != null) { + session.removeLocalTempTable(view); + } + } + } + + public CommandContainer(SessionLocal session, String sql, Prepared prepared) { + super(session, sql); + prepared.setCommand(this); + this.prepared = prepared; + } + + @Override + public ArrayList getParameters() { + return prepared.getParameters(); + } + + @Override + public boolean isTransactional() { + return prepared.isTransactional(); + } + + @Override + public boolean isQuery() { + return prepared.isQuery(); + } + + private void recompileIfRequired() { + if (prepared.needRecompile()) { + // TODO test with 'always recompile' + prepared.setModificationMetaId(0); + String sql = prepared.getSQL(); + ArrayList tokens = prepared.getSQLTokens(); + ArrayList oldParams = prepared.getParameters(); + Parser parser = new Parser(session); + prepared = parser.parse(sql, 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); + } + } + + @Override + public ResultWithGeneratedKeys update(Object generatedKeysRequest) { + recompileIfRequired(); + setProgress(DatabaseEventListener.STATE_STATEMENT_START); + start(); + prepared.checkParameters(); + ResultWithGeneratedKeys result; + if (generatedKeysRequest != null && !Boolean.FALSE.equals(generatedKeysRequest)) { + if (prepared instanceof DataChangeStatement && prepared.getType() != CommandInterface.DELETE) { + result = executeUpdateWithGeneratedKeys((DataChangeStatement) prepared, + generatedKeysRequest); + } else { + result = new ResultWithGeneratedKeys.WithKeys(prepared.update(), new LocalResult()); + } + } else { + result = ResultWithGeneratedKeys.of(prepared.update()); + } + prepared.trace(startTimeNanos, result.getUpdateCount()); + setProgress(DatabaseEventListener.STATE_STATEMENT_END); + return result; + } + + private ResultWithGeneratedKeys executeUpdateWithGeneratedKeys(DataChangeStatement statement, + Object generatedKeysRequest) { + Database db = session.getDatabase(); + Table table = statement.getTable(); + ArrayList expressionColumns; + if (Boolean.TRUE.equals(generatedKeysRequest)) { + expressionColumns = Utils.newSmallArrayList(); + Column[] columns = table.getColumns(); + Index primaryKey = table.findPrimaryKey(); + for (Column column : columns) { + Expression e; + if (column.isIdentity() + || ((e = column.getEffectiveDefaultExpression()) != null && !e.isConstant()) + || (primaryKey != null && primaryKey.getColumnIndex(column) >= 0)) { + expressionColumns.add(new ExpressionColumn(db, column)); + } + } + } else if (generatedKeysRequest instanceof int[]) { + int[] indexes = (int[]) generatedKeysRequest; + Column[] columns = table.getColumns(); + int cnt = columns.length; + expressionColumns = new ArrayList<>(indexes.length); + for (int idx : indexes) { + if (idx < 1 || idx > cnt) { + throw DbException.get(ErrorCode.COLUMN_NOT_FOUND_1, "Index: " + idx); + } + expressionColumns.add(new ExpressionColumn(db, columns[idx - 1])); + } + } else if (generatedKeysRequest instanceof String[]) { + String[] names = (String[]) generatedKeysRequest; + expressionColumns = new ArrayList<>(names.length); + for (String name : names) { + Column column = table.findColumn(name); + if (column == null) { + DbSettings settings = db.getSettings(); + if (settings.databaseToUpper) { + column = table.findColumn(StringUtils.toUpperEnglish(name)); + } else if (settings.databaseToLower) { + column = table.findColumn(StringUtils.toLowerEnglish(name)); + } + search: if (column == null) { + for (Column c : table.getColumns()) { + if (c.getName().equalsIgnoreCase(name)) { + column = c; + break search; + } + } + throw DbException.get(ErrorCode.COLUMN_NOT_FOUND_1, name); + } + } + expressionColumns.add(new ExpressionColumn(db, column)); + } + } else { + throw DbException.getInternalError(); + } + int columnCount = expressionColumns.size(); + if (columnCount == 0) { + return new ResultWithGeneratedKeys.WithKeys(statement.update(), new LocalResult()); + } + int[] indexes = new int[columnCount]; + ExpressionColumn[] expressions = expressionColumns.toArray(new ExpressionColumn[0]); + for (int i = 0; i < columnCount; i++) { + indexes[i] = expressions[i].getColumn().getColumnId(); + } + LocalResult result = new LocalResult(session, expressions, columnCount, columnCount); + return new ResultWithGeneratedKeys.WithKeys( + statement.update(new GeneratedKeysCollector(indexes, result), ResultOption.FINAL), result); + } + + @Override + public ResultInterface query(long maxrows) { + recompileIfRequired(); + setProgress(DatabaseEventListener.STATE_STATEMENT_START); + start(); + prepared.checkParameters(); + ResultInterface result = prepared.query(maxrows); + prepared.trace(startTimeNanos, result.isLazy() ? 0 : result.getRowCount()); + setProgress(DatabaseEventListener.STATE_STATEMENT_END); + return result; + } + + @Override + public void stop() { + super.stop(); + // Clean up after the command was run in the session. + // Must restart query (and dependency construction) to reuse. + clearCTE(session, prepared); + } + + @Override + public boolean canReuse() { + return super.canReuse() && prepared.getCteCleanups() == null; + } + + @Override + public boolean isReadOnly() { + if (!readOnlyKnown) { + readOnly = prepared.isReadOnly(); + readOnlyKnown = true; + } + return readOnly; + } + + @Override + public ResultInterface queryMeta() { + return prepared.queryMeta(); + } + + @Override + public boolean isCacheable() { + return prepared.isCacheable(); + } + + @Override + public int getCommandType() { + return prepared.getType(); + } + + /** + * Clean up any associated CTE. + */ + void clearCTE() { + clearCTE(session, prepared); + } + + @Override + public Set getDependencies() { + HashSet dependencies = new HashSet<>(); + prepared.collectDependencies(dependencies); + return dependencies; + } + + @Override + protected boolean isCurrentCommandADefineCommand() { + return prepared instanceof DefineCommand; + } +} diff --git a/h2/src/main/org/h2/command/CommandInterface.java b/h2/src/main/org/h2/command/CommandInterface.java new file mode 100644 index 0000000..fbe1223 --- /dev/null +++ b/h2/src/main/org/h2/command/CommandInterface.java @@ -0,0 +1,611 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command; + +import java.util.ArrayList; +import org.h2.expression.ParameterInterface; +import org.h2.result.ResultInterface; +import org.h2.result.ResultWithGeneratedKeys; + +/** + * Represents a SQL statement. + */ +public interface CommandInterface extends AutoCloseable { + + /** + * The type for unknown statement. + */ + int UNKNOWN = 0; + + // ddl operations + + /** + * The type of a ALTER INDEX RENAME statement. + */ + int ALTER_INDEX_RENAME = 1; + + /** + * The type of an ALTER SCHEMA RENAME statement. + */ + int ALTER_SCHEMA_RENAME = 2; + + /** + * The type of an ALTER TABLE ADD CHECK statement. + */ + int ALTER_TABLE_ADD_CONSTRAINT_CHECK = 3; + + /** + * The type of an ALTER TABLE ADD UNIQUE statement. + */ + int ALTER_TABLE_ADD_CONSTRAINT_UNIQUE = 4; + + /** + * The type of an ALTER TABLE ADD FOREIGN KEY statement. + */ + int ALTER_TABLE_ADD_CONSTRAINT_REFERENTIAL = 5; + + /** + * The type of an ALTER TABLE ADD PRIMARY KEY statement. + */ + int ALTER_TABLE_ADD_CONSTRAINT_PRIMARY_KEY = 6; + + /** + * The type of an ALTER TABLE ADD statement. + */ + int ALTER_TABLE_ADD_COLUMN = 7; + + /** + * The type of an ALTER TABLE ALTER COLUMN SET NOT NULL statement. + */ + int ALTER_TABLE_ALTER_COLUMN_NOT_NULL = 8; + + /** + * The type of an ALTER TABLE ALTER COLUMN DROP NOT NULL statement. + */ + int ALTER_TABLE_ALTER_COLUMN_DROP_NOT_NULL = 9; + + /** + * The type of an ALTER TABLE ALTER COLUMN SET DEFAULT and ALTER TABLE ALTER + * COLUMN DROP DEFAULT statements. + */ + int ALTER_TABLE_ALTER_COLUMN_DEFAULT = 10; + + /** + * The type of an ALTER TABLE ALTER COLUMN statement that changes the column + * data type. + */ + int ALTER_TABLE_ALTER_COLUMN_CHANGE_TYPE = 11; + + /** + * The type of an ALTER TABLE DROP COLUMN statement. + */ + int ALTER_TABLE_DROP_COLUMN = 12; + + /** + * The type of an ALTER TABLE ALTER COLUMN SELECTIVITY statement. + */ + int ALTER_TABLE_ALTER_COLUMN_SELECTIVITY = 13; + + /** + * The type of an ALTER TABLE DROP CONSTRAINT statement. + */ + int ALTER_TABLE_DROP_CONSTRAINT = 14; + + /** + * The type of an ALTER TABLE RENAME statement. + */ + int ALTER_TABLE_RENAME = 15; + + /** + * The type of an ALTER TABLE ALTER COLUMN RENAME statement. + */ + int ALTER_TABLE_ALTER_COLUMN_RENAME = 16; + + /** + * The type of an ALTER USER ADMIN statement. + */ + int ALTER_USER_ADMIN = 17; + + /** + * The type of an ALTER USER RENAME statement. + */ + int ALTER_USER_RENAME = 18; + + /** + * The type of an ALTER USER SET PASSWORD statement. + */ + int ALTER_USER_SET_PASSWORD = 19; + + /** + * The type of an ALTER VIEW statement. + */ + int ALTER_VIEW = 20; + + /** + * The type of an ANALYZE statement. + */ + int ANALYZE = 21; + + /** + * The type of a CREATE AGGREGATE statement. + */ + int CREATE_AGGREGATE = 22; + + /** + * The type of a CREATE CONSTANT statement. + */ + int CREATE_CONSTANT = 23; + + /** + * The type of a CREATE ALIAS statement. + */ + int CREATE_ALIAS = 24; + + /** + * The type of a CREATE INDEX statement. + */ + int CREATE_INDEX = 25; + + /** + * The type of a CREATE LINKED TABLE statement. + */ + int CREATE_LINKED_TABLE = 26; + + /** + * The type of a CREATE ROLE statement. + */ + int CREATE_ROLE = 27; + + /** + * The type of a CREATE SCHEMA statement. + */ + int CREATE_SCHEMA = 28; + + /** + * The type of a CREATE SEQUENCE statement. + */ + int CREATE_SEQUENCE = 29; + + /** + * The type of a CREATE TABLE statement. + */ + int CREATE_TABLE = 30; + + /** + * The type of a CREATE TRIGGER statement. + */ + int CREATE_TRIGGER = 31; + + /** + * The type of a CREATE USER statement. + */ + int CREATE_USER = 32; + + /** + * The type of a CREATE DOMAIN statement. + */ + int CREATE_DOMAIN = 33; + + /** + * The type of a CREATE VIEW statement. + */ + int CREATE_VIEW = 34; + + /** + * The type of a DEALLOCATE statement. + */ + int DEALLOCATE = 35; + + /** + * The type of a DROP AGGREGATE statement. + */ + int DROP_AGGREGATE = 36; + + /** + * The type of a DROP CONSTANT statement. + */ + int DROP_CONSTANT = 37; + + /** + * The type of a DROP ALL OBJECTS statement. + */ + int DROP_ALL_OBJECTS = 38; + + /** + * The type of a DROP ALIAS statement. + */ + int DROP_ALIAS = 39; + + /** + * The type of a DROP INDEX statement. + */ + int DROP_INDEX = 40; + + /** + * The type of a DROP ROLE statement. + */ + int DROP_ROLE = 41; + + /** + * The type of a DROP SCHEMA statement. + */ + int DROP_SCHEMA = 42; + + /** + * The type of a DROP SEQUENCE statement. + */ + int DROP_SEQUENCE = 43; + + /** + * The type of a DROP TABLE statement. + */ + int DROP_TABLE = 44; + + /** + * The type of a DROP TRIGGER statement. + */ + int DROP_TRIGGER = 45; + + /** + * The type of a DROP USER statement. + */ + int DROP_USER = 46; + + /** + * The type of a DROP DOMAIN statement. + */ + int DROP_DOMAIN = 47; + + /** + * The type of a DROP VIEW statement. + */ + int DROP_VIEW = 48; + + /** + * The type of a GRANT statement. + */ + int GRANT = 49; + + /** + * The type of a REVOKE statement. + */ + int REVOKE = 50; + + /** + * The type of a PREPARE statement. + */ + int PREPARE = 51; + + /** + * The type of a COMMENT statement. + */ + int COMMENT = 52; + + /** + * The type of a TRUNCATE TABLE statement. + */ + int TRUNCATE_TABLE = 53; + + // dml operations + + /** + * The type of an ALTER SEQUENCE statement. + */ + int ALTER_SEQUENCE = 54; + + /** + * The type of an ALTER TABLE SET REFERENTIAL_INTEGRITY statement. + */ + int ALTER_TABLE_SET_REFERENTIAL_INTEGRITY = 55; + + /** + * The type of a BACKUP statement. + */ + int BACKUP = 56; + + /** + * The type of a CALL statement. + */ + int CALL = 57; + + /** + * The type of a DELETE statement. + */ + int DELETE = 58; + + /** + * The type of an EXECUTE statement. + */ + int EXECUTE = 59; + + /** + * The type of an EXPLAIN statement. + */ + int EXPLAIN = 60; + + /** + * The type of an INSERT statement. + */ + int INSERT = 61; + + /** + * The type of a MERGE statement. + */ + int MERGE = 62; + + /** + * The type of a REPLACE statement. + */ + int REPLACE = 63; + + /** + * The type of a no operation statement. + */ + int NO_OPERATION = 63; + + /** + * The type of a RUNSCRIPT statement. + */ + int RUNSCRIPT = 64; + + /** + * The type of a SCRIPT statement. + */ + int SCRIPT = 65; + + /** + * The type of a SELECT statement. + */ + int SELECT = 66; + + /** + * The type of a SET statement. + */ + int SET = 67; + + /** + * The type of an UPDATE statement. + */ + int UPDATE = 68; + + // transaction commands + + /** + * The type of a SET AUTOCOMMIT statement. + */ + int SET_AUTOCOMMIT_TRUE = 69; + + /** + * The type of a SET AUTOCOMMIT statement. + */ + int SET_AUTOCOMMIT_FALSE = 70; + + /** + * The type of a COMMIT statement. + */ + int COMMIT = 71; + + /** + * The type of a ROLLBACK statement. + */ + int ROLLBACK = 72; + + /** + * The type of a CHECKPOINT statement. + */ + int CHECKPOINT = 73; + + /** + * The type of a SAVEPOINT statement. + */ + int SAVEPOINT = 74; + + /** + * The type of a ROLLBACK TO SAVEPOINT statement. + */ + int ROLLBACK_TO_SAVEPOINT = 75; + + /** + * The type of a CHECKPOINT SYNC statement. + */ + int CHECKPOINT_SYNC = 76; + + /** + * The type of a PREPARE COMMIT statement. + */ + int PREPARE_COMMIT = 77; + + /** + * The type of a COMMIT TRANSACTION statement. + */ + int COMMIT_TRANSACTION = 78; + + /** + * The type of a ROLLBACK TRANSACTION statement. + */ + int ROLLBACK_TRANSACTION = 79; + + /** + * The type of a SHUTDOWN statement. + */ + int SHUTDOWN = 80; + + /** + * The type of a SHUTDOWN IMMEDIATELY statement. + */ + int SHUTDOWN_IMMEDIATELY = 81; + + /** + * The type of a SHUTDOWN COMPACT statement. + */ + int SHUTDOWN_COMPACT = 82; + + /** + * The type of a BEGIN {WORK|TRANSACTION} statement. + */ + int BEGIN = 83; + + /** + * The type of a SHUTDOWN DEFRAG statement. + */ + int SHUTDOWN_DEFRAG = 84; + + /** + * The type of an ALTER TABLE RENAME CONSTRAINT statement. + */ + int ALTER_TABLE_RENAME_CONSTRAINT = 85; + + /** + * The type of an EXPLAIN ANALYZE statement. + */ + int EXPLAIN_ANALYZE = 86; + + /** + * The type of an ALTER TABLE ALTER COLUMN SET INVISIBLE statement. + */ + int ALTER_TABLE_ALTER_COLUMN_VISIBILITY = 87; + + /** + * The type of a CREATE SYNONYM statement. + */ + int CREATE_SYNONYM = 88; + + /** + * The type of a DROP SYNONYM statement. + */ + int DROP_SYNONYM = 89; + + /** + * The type of an ALTER TABLE ALTER COLUMN SET ON UPDATE statement. + */ + int ALTER_TABLE_ALTER_COLUMN_ON_UPDATE = 90; + + /** + * The type of an EXECUTE IMMEDIATELY statement. + */ + int EXECUTE_IMMEDIATELY = 91; + + /** + * The type of ALTER DOMAIN ADD CONSTRAINT statement. + */ + int ALTER_DOMAIN_ADD_CONSTRAINT = 92; + + /** + * The type of ALTER DOMAIN DROP CONSTRAINT statement. + */ + int ALTER_DOMAIN_DROP_CONSTRAINT = 93; + + /** + * The type of an ALTER DOMAIN SET DEFAULT and ALTER DOMAIN DROP DEFAULT + * statements. + */ + int ALTER_DOMAIN_DEFAULT = 94; + + /** + * The type of an ALTER DOMAIN SET ON UPDATE and ALTER DOMAIN DROP ON UPDATE + * statements. + */ + int ALTER_DOMAIN_ON_UPDATE = 95; + + /** + * The type of an ALTER DOMAIN RENAME statement. + */ + int ALTER_DOMAIN_RENAME = 96; + + /** + * The type of a HELP statement. + */ + int HELP = 97; + + /** + * The type of an ALTER TABLE ALTER COLUMN DROP EXPRESSION statement. + */ + int ALTER_TABLE_ALTER_COLUMN_DROP_EXPRESSION = 98; + + /** + * The type of an ALTER TABLE ALTER COLUMN DROP IDENTITY statement. + */ + int ALTER_TABLE_ALTER_COLUMN_DROP_IDENTITY = 99; + + /** + * The type of ALTER TABLE ALTER COLUMN SET DEFAULT ON NULL and ALTER TABLE + * ALTER COLUMN DROP DEFAULT ON NULL statements. + */ + int ALTER_TABLE_ALTER_COLUMN_DEFAULT_ON_NULL = 100; + + /** + * The type of an ALTER DOMAIN RENAME CONSTRAINT statement. + */ + int ALTER_DOMAIN_RENAME_CONSTRAINT = 101; + + /** + * Get command type. + * + * @return one of the constants above + */ + int getCommandType(); + + /** + * Check if this is a query. + * + * @return true if it is a query + */ + boolean isQuery(); + + /** + * Get the parameters (if any). + * + * @return the parameters + */ + ArrayList getParameters(); + + /** + * Execute the query. + * + * @param maxRows the maximum number of rows returned + * @param scrollable if the result set must be scrollable + * @return the result + */ + ResultInterface executeQuery(long maxRows, boolean scrollable); + + /** + * Execute the statement + * + * @param generatedKeysRequest + * {@code null} or {@code false} if generated keys are not + * needed, {@code true} if generated keys should be configured + * automatically, {@code int[]} to specify column indices to + * return generated keys from, or {@code String[]} to specify + * column names to return generated keys from + * + * @return the update count and generated keys, if any + */ + ResultWithGeneratedKeys executeUpdate(Object generatedKeysRequest); + + /** + * Stop the command execution, release all locks and resources + */ + void stop(); + + /** + * Close the statement. + */ + @Override + void close(); + + /** + * Cancel the statement if it is still processing. + */ + void cancel(); + + /** + * Get an empty result set containing the meta data of the result. + * + * @return the empty result + */ + ResultInterface getMetaData(); + +} diff --git a/h2/src/main/org/h2/command/CommandList.java b/h2/src/main/org/h2/command/CommandList.java new file mode 100644 index 0000000..f3d17e1 --- /dev/null +++ b/h2/src/main/org/h2/command/CommandList.java @@ -0,0 +1,126 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import org.h2.engine.DbObject; +import org.h2.engine.SessionLocal; +import org.h2.expression.Parameter; +import org.h2.expression.ParameterInterface; +import org.h2.result.ResultInterface; +import org.h2.result.ResultWithGeneratedKeys; + +/** + * Represents a list of SQL statements. + */ +class CommandList extends Command { + + private CommandContainer command; + private final ArrayList commands; + private final ArrayList parameters; + private String remaining; + private Command remainingCommand; + + CommandList(SessionLocal session, String sql, CommandContainer command, ArrayList commands, + ArrayList parameters, String remaining) { + super(session, sql); + this.command = command; + this.commands = commands; + this.parameters = parameters; + this.remaining = remaining; + } + + @Override + public ArrayList getParameters() { + return parameters; + } + + private void executeRemaining() { + for (Prepared prepared : commands) { + prepared.prepare(); + if (prepared.isQuery()) { + prepared.query(0); + } else { + prepared.update(); + } + } + if (remaining != null) { + remainingCommand = session.prepareLocal(remaining); + remaining = null; + if (remainingCommand.isQuery()) { + remainingCommand.query(0); + } else { + remainingCommand.update(null); + } + } + } + + @Override + public ResultWithGeneratedKeys update(Object generatedKeysRequest) { + ResultWithGeneratedKeys result = command.executeUpdate(null); + executeRemaining(); + return result; + } + + @Override + public ResultInterface query(long maxrows) { + ResultInterface result = command.query(maxrows); + executeRemaining(); + return result; + } + + @Override + public void stop() { + command.stop(); + for (Prepared prepared : commands) { + CommandContainer.clearCTE(session, prepared); + } + if (remainingCommand != null) { + remainingCommand.stop(); + } + } + + @Override + public boolean isQuery() { + return command.isQuery(); + } + + @Override + public boolean isTransactional() { + return true; + } + + @Override + public boolean isReadOnly() { + return false; + } + + @Override + public ResultInterface queryMeta() { + return command.queryMeta(); + } + + @Override + public int getCommandType() { + return command.getCommandType(); + } + + @Override + public Set getDependencies() { + HashSet dependencies = new HashSet<>(); + for (Prepared prepared : commands) { + prepared.collectDependencies(dependencies); + } + return dependencies; + } + + @Override + protected boolean isCurrentCommandADefineCommand() { + return command.isCurrentCommandADefineCommand(); + } +} diff --git a/h2/src/main/org/h2/command/CommandRemote.java b/h2/src/main/org/h2/command/CommandRemote.java new file mode 100644 index 0000000..7807ef4 --- /dev/null +++ b/h2/src/main/org/h2/command/CommandRemote.java @@ -0,0 +1,321 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command; + +import java.io.IOException; +import java.util.ArrayList; +import org.h2.engine.GeneratedKeysMode; +import org.h2.engine.SessionRemote; +import org.h2.engine.SysProperties; +import org.h2.expression.ParameterInterface; +import org.h2.expression.ParameterRemote; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.result.ResultInterface; +import org.h2.result.ResultRemote; +import org.h2.result.ResultWithGeneratedKeys; +import org.h2.util.Utils; +import org.h2.value.Transfer; +import org.h2.value.Value; +import org.h2.value.ValueLob; +import org.h2.value.ValueNull; + +/** + * Represents the client-side part of a SQL statement. + * This class is not used in embedded mode. + */ +public class CommandRemote implements CommandInterface { + + private final ArrayList transferList; + private final ArrayList parameters; + private final Trace trace; + private final String sql; + private final int fetchSize; + private SessionRemote session; + private int id; + private boolean isQuery; + private int cmdType = UNKNOWN; + private boolean readonly; + private final int created; + + public CommandRemote(SessionRemote session, + ArrayList transferList, String sql, int fetchSize) { + this.transferList = transferList; + trace = session.getTrace(); + this.sql = sql; + parameters = Utils.newSmallArrayList(); + prepare(session, true); + // set session late because prepare might fail - in this case we don't + // need to close the object + this.session = session; + this.fetchSize = fetchSize; + created = session.getLastReconnect(); + } + + @Override + public void stop() { + // Ignore + } + + private void prepare(SessionRemote s, boolean createParams) { + id = s.getNextId(); + for (int i = 0, count = 0; i < transferList.size(); i++) { + try { + Transfer transfer = transferList.get(i); + + if (createParams) { + s.traceOperation("SESSION_PREPARE_READ_PARAMS2", id); + transfer.writeInt(SessionRemote.SESSION_PREPARE_READ_PARAMS2) + .writeInt(id).writeString(sql); + } else { + s.traceOperation("SESSION_PREPARE", id); + transfer.writeInt(SessionRemote.SESSION_PREPARE). + writeInt(id).writeString(sql); + } + s.done(transfer); + isQuery = transfer.readBoolean(); + readonly = transfer.readBoolean(); + + cmdType = createParams ? transfer.readInt() : UNKNOWN; + + int paramCount = transfer.readInt(); + if (createParams) { + parameters.clear(); + for (int j = 0; j < paramCount; j++) { + ParameterRemote p = new ParameterRemote(j); + p.readMetaData(transfer); + parameters.add(p); + } + } + } catch (IOException e) { + s.removeServer(e, i--, ++count); + } + } + } + + @Override + public boolean isQuery() { + return isQuery; + } + + @Override + public ArrayList getParameters() { + return parameters; + } + + private void prepareIfRequired() { + if (session.getLastReconnect() != created) { + // in this case we need to prepare again in every case + id = Integer.MIN_VALUE; + } + session.checkClosed(); + if (id <= session.getCurrentId() - SysProperties.SERVER_CACHED_OBJECTS) { + // object is too old - we need to prepare again + prepare(session, false); + } + } + + @Override + public ResultInterface getMetaData() { + synchronized (session) { + if (!isQuery) { + return null; + } + int objectId = session.getNextId(); + ResultRemote result = null; + for (int i = 0, count = 0; i < transferList.size(); i++) { + prepareIfRequired(); + Transfer transfer = transferList.get(i); + try { + session.traceOperation("COMMAND_GET_META_DATA", id); + transfer.writeInt(SessionRemote.COMMAND_GET_META_DATA). + writeInt(id).writeInt(objectId); + session.done(transfer); + int columnCount = transfer.readInt(); + result = new ResultRemote(session, transfer, objectId, + columnCount, Integer.MAX_VALUE); + break; + } catch (IOException e) { + session.removeServer(e, i--, ++count); + } + } + session.autoCommitIfCluster(); + return result; + } + } + + @Override + public ResultInterface executeQuery(long maxRows, boolean scrollable) { + checkParameters(); + synchronized (session) { + int objectId = session.getNextId(); + ResultRemote result = null; + for (int i = 0, count = 0; i < transferList.size(); i++) { + prepareIfRequired(); + Transfer transfer = transferList.get(i); + try { + session.traceOperation("COMMAND_EXECUTE_QUERY", id); + transfer.writeInt(SessionRemote.COMMAND_EXECUTE_QUERY).writeInt(id).writeInt(objectId); + transfer.writeRowCount(maxRows); + int fetch; + if (session.isClustered() || scrollable) { + fetch = Integer.MAX_VALUE; + } else { + fetch = fetchSize; + } + transfer.writeInt(fetch); + sendParameters(transfer); + session.done(transfer); + int columnCount = transfer.readInt(); + if (result != null) { + result.close(); + result = null; + } + result = new ResultRemote(session, transfer, objectId, columnCount, fetch); + if (readonly) { + break; + } + } catch (IOException e) { + session.removeServer(e, i--, ++count); + } + } + session.autoCommitIfCluster(); + session.readSessionState(); + return result; + } + } + + @Override + public ResultWithGeneratedKeys executeUpdate(Object generatedKeysRequest) { + checkParameters(); + int generatedKeysMode = GeneratedKeysMode.valueOf(generatedKeysRequest); + boolean readGeneratedKeys = generatedKeysMode != GeneratedKeysMode.NONE; + int objectId = readGeneratedKeys ? session.getNextId() : 0; + synchronized (session) { + long updateCount = 0L; + ResultRemote generatedKeys = null; + boolean autoCommit = false; + for (int i = 0, count = 0; i < transferList.size(); i++) { + prepareIfRequired(); + Transfer transfer = transferList.get(i); + try { + session.traceOperation("COMMAND_EXECUTE_UPDATE", id); + transfer.writeInt(SessionRemote.COMMAND_EXECUTE_UPDATE).writeInt(id); + sendParameters(transfer); + transfer.writeInt(generatedKeysMode); + switch (generatedKeysMode) { + case GeneratedKeysMode.COLUMN_NUMBERS: { + int[] keys = (int[]) generatedKeysRequest; + transfer.writeInt(keys.length); + for (int key : keys) { + transfer.writeInt(key); + } + break; + } + case GeneratedKeysMode.COLUMN_NAMES: { + String[] keys = (String[]) generatedKeysRequest; + transfer.writeInt(keys.length); + for (String key : keys) { + transfer.writeString(key); + } + break; + } + } + session.done(transfer); + updateCount = transfer.readRowCount(); + autoCommit = transfer.readBoolean(); + if (readGeneratedKeys) { + int columnCount = transfer.readInt(); + if (generatedKeys != null) { + generatedKeys.close(); + generatedKeys = null; + } + generatedKeys = new ResultRemote(session, transfer, objectId, columnCount, Integer.MAX_VALUE); + } + } catch (IOException e) { + session.removeServer(e, i--, ++count); + } + } + session.setAutoCommitFromServer(autoCommit); + session.autoCommitIfCluster(); + session.readSessionState(); + if (generatedKeys != null) { + return new ResultWithGeneratedKeys.WithKeys(updateCount, generatedKeys); + } + return ResultWithGeneratedKeys.of(updateCount); + } + } + + private void checkParameters() { + if (cmdType != EXPLAIN) { + for (ParameterInterface p : parameters) { + p.checkSet(); + } + } + } + + private void sendParameters(Transfer transfer) throws IOException { + int len = parameters.size(); + transfer.writeInt(len); + for (ParameterInterface p : parameters) { + Value pVal = p.getParamValue(); + + if (pVal == null && cmdType == EXPLAIN) { + pVal = ValueNull.INSTANCE; + } + + transfer.writeValue(pVal); + } + } + + @Override + public void close() { + if (session == null || session.isClosed()) { + return; + } + synchronized (session) { + session.traceOperation("COMMAND_CLOSE", id); + for (Transfer transfer : transferList) { + try { + transfer.writeInt(SessionRemote.COMMAND_CLOSE).writeInt(id); + } catch (IOException e) { + trace.error(e, "close"); + } + } + } + session = null; + try { + for (ParameterInterface p : parameters) { + Value v = p.getParamValue(); + if (v instanceof ValueLob) { + ((ValueLob) v).remove(); + } + } + } catch (DbException e) { + trace.error(e, "close"); + } + parameters.clear(); + } + + /** + * Cancel this current statement. + */ + @Override + public void cancel() { + session.cancelStatement(id); + } + + @Override + public String toString() { + return sql + Trace.formatParams(getParameters()); + } + + @Override + public int getCommandType() { + return cmdType; + } + +} diff --git a/h2/src/main/org/h2/command/Parser.java b/h2/src/main/org/h2/command/Parser.java new file mode 100644 index 0000000..cc9ce67 --- /dev/null +++ b/h2/src/main/org/h2/command/Parser.java @@ -0,0 +1,9717 @@ +/* + * 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 + * + * Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888 + * Support for the operator "&&" as an alias for SPATIAL_INTERSECTS + */ +package org.h2.command; + +import static org.h2.command.Token.ASTERISK; +import static org.h2.command.Token.AT; +import static org.h2.command.Token.BIGGER; +import static org.h2.command.Token.BIGGER_EQUAL; +import static org.h2.command.Token.CLOSE_BRACE; +import static org.h2.command.Token.CLOSE_BRACKET; +import static org.h2.command.Token.CLOSE_PAREN; +import static org.h2.command.Token.COLON; +import static org.h2.command.Token.COLON_COLON; +import static org.h2.command.Token.COLON_EQ; +import static org.h2.command.Token.COMMA; +import static org.h2.command.Token.CONCATENATION; +import static org.h2.command.Token.DOT; +import static org.h2.command.Token.END_OF_INPUT; +import static org.h2.command.Token.EQUAL; +import static org.h2.command.Token.LITERAL; +import static org.h2.command.Token.MINUS_SIGN; +import static org.h2.command.Token.NOT_EQUAL; +import static org.h2.command.Token.NOT_TILDE; +import static org.h2.command.Token.OPEN_BRACE; +import static org.h2.command.Token.OPEN_BRACKET; +import static org.h2.command.Token.OPEN_PAREN; +import static org.h2.command.Token.PARAMETER; +import static org.h2.command.Token.PERCENT; +import static org.h2.command.Token.PLUS_SIGN; +import static org.h2.command.Token.SEMICOLON; +import static org.h2.command.Token.SLASH; +import static org.h2.command.Token.SMALLER; +import static org.h2.command.Token.SMALLER_EQUAL; +import static org.h2.command.Token.SPATIAL_INTERSECTS; +import static org.h2.command.Token.TILDE; +import static org.h2.command.Token.TOKENS; +import static org.h2.util.ParserUtil.ALL; +import static org.h2.util.ParserUtil.AND; +import static org.h2.util.ParserUtil.ANY; +import static org.h2.util.ParserUtil.ARRAY; +import static org.h2.util.ParserUtil.AS; +import static org.h2.util.ParserUtil.ASYMMETRIC; +import static org.h2.util.ParserUtil.AUTHORIZATION; +import static org.h2.util.ParserUtil.BETWEEN; +import static org.h2.util.ParserUtil.CASE; +import static org.h2.util.ParserUtil.CAST; +import static org.h2.util.ParserUtil.CHECK; +import static org.h2.util.ParserUtil.CONSTRAINT; +import static org.h2.util.ParserUtil.CROSS; +import static org.h2.util.ParserUtil.CURRENT_CATALOG; +import static org.h2.util.ParserUtil.CURRENT_DATE; +import static org.h2.util.ParserUtil.CURRENT_PATH; +import static org.h2.util.ParserUtil.CURRENT_ROLE; +import static org.h2.util.ParserUtil.CURRENT_SCHEMA; +import static org.h2.util.ParserUtil.CURRENT_TIME; +import static org.h2.util.ParserUtil.CURRENT_TIMESTAMP; +import static org.h2.util.ParserUtil.CURRENT_USER; +import static org.h2.util.ParserUtil.DAY; +import static org.h2.util.ParserUtil.DEFAULT; +import static org.h2.util.ParserUtil.DISTINCT; +import static org.h2.util.ParserUtil.ELSE; +import static org.h2.util.ParserUtil.END; +import static org.h2.util.ParserUtil.EXCEPT; +import static org.h2.util.ParserUtil.EXISTS; +import static org.h2.util.ParserUtil.FALSE; +import static org.h2.util.ParserUtil.FETCH; +import static org.h2.util.ParserUtil.FIRST_KEYWORD; +import static org.h2.util.ParserUtil.FOR; +import static org.h2.util.ParserUtil.FOREIGN; +import static org.h2.util.ParserUtil.FROM; +import static org.h2.util.ParserUtil.FULL; +import static org.h2.util.ParserUtil.GROUP; +import static org.h2.util.ParserUtil.HAVING; +import static org.h2.util.ParserUtil.HOUR; +import static org.h2.util.ParserUtil.IDENTIFIER; +import static org.h2.util.ParserUtil.IF; +import static org.h2.util.ParserUtil.IN; +import static org.h2.util.ParserUtil.INNER; +import static org.h2.util.ParserUtil.INTERSECT; +import static org.h2.util.ParserUtil.INTERVAL; +import static org.h2.util.ParserUtil.IS; +import static org.h2.util.ParserUtil.JOIN; +import static org.h2.util.ParserUtil.KEY; +import static org.h2.util.ParserUtil.LAST_KEYWORD; +import static org.h2.util.ParserUtil.LEFT; +import static org.h2.util.ParserUtil.LIKE; +import static org.h2.util.ParserUtil.LIMIT; +import static org.h2.util.ParserUtil.LOCALTIME; +import static org.h2.util.ParserUtil.LOCALTIMESTAMP; +import static org.h2.util.ParserUtil.MINUS; +import static org.h2.util.ParserUtil.MINUTE; +import static org.h2.util.ParserUtil.MONTH; +import static org.h2.util.ParserUtil.NATURAL; +import static org.h2.util.ParserUtil.NOT; +import static org.h2.util.ParserUtil.NULL; +import static org.h2.util.ParserUtil.OFFSET; +import static org.h2.util.ParserUtil.ON; +import static org.h2.util.ParserUtil.OR; +import static org.h2.util.ParserUtil.ORDER; +import static org.h2.util.ParserUtil.PRIMARY; +import static org.h2.util.ParserUtil.QUALIFY; +import static org.h2.util.ParserUtil.RIGHT; +import static org.h2.util.ParserUtil.ROW; +import static org.h2.util.ParserUtil.ROWNUM; +import static org.h2.util.ParserUtil.SECOND; +import static org.h2.util.ParserUtil.SELECT; +import static org.h2.util.ParserUtil.SESSION_USER; +import static org.h2.util.ParserUtil.SET; +import static org.h2.util.ParserUtil.SOME; +import static org.h2.util.ParserUtil.SYMMETRIC; +import static org.h2.util.ParserUtil.SYSTEM_USER; +import static org.h2.util.ParserUtil.TABLE; +import static org.h2.util.ParserUtil.TO; +import static org.h2.util.ParserUtil.TRUE; +import static org.h2.util.ParserUtil.UNION; +import static org.h2.util.ParserUtil.UNIQUE; +import static org.h2.util.ParserUtil.UNKNOWN; +import static org.h2.util.ParserUtil.USER; +import static org.h2.util.ParserUtil.USING; +import static org.h2.util.ParserUtil.VALUE; +import static org.h2.util.ParserUtil.VALUES; +import static org.h2.util.ParserUtil.WHEN; +import static org.h2.util.ParserUtil.WHERE; +import static org.h2.util.ParserUtil.WINDOW; +import static org.h2.util.ParserUtil.WITH; +import static org.h2.util.ParserUtil.YEAR; +import static org.h2.util.ParserUtil._ROWID_; + +import java.nio.charset.Charset; +import java.text.Collator; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.TreeSet; +import org.h2.api.ErrorCode; +import org.h2.api.IntervalQualifier; +import org.h2.api.Trigger; +import org.h2.command.ddl.AlterDomainAddConstraint; +import org.h2.command.ddl.AlterDomainDropConstraint; +import org.h2.command.ddl.AlterDomainExpressions; +import org.h2.command.ddl.AlterDomainRename; +import org.h2.command.ddl.AlterDomainRenameConstraint; +import org.h2.command.ddl.AlterIndexRename; +import org.h2.command.ddl.AlterSchemaRename; +import org.h2.command.ddl.AlterSequence; +import org.h2.command.ddl.AlterTableAddConstraint; +import org.h2.command.ddl.AlterTableAlterColumn; +import org.h2.command.ddl.AlterTableDropConstraint; +import org.h2.command.ddl.AlterTableRename; +import org.h2.command.ddl.AlterTableRenameColumn; +import org.h2.command.ddl.AlterTableRenameConstraint; +import org.h2.command.ddl.AlterUser; +import org.h2.command.ddl.AlterView; +import org.h2.command.ddl.Analyze; +import org.h2.command.ddl.CommandWithColumns; +import org.h2.command.ddl.CreateAggregate; +import org.h2.command.ddl.CreateConstant; +import org.h2.command.ddl.CreateDomain; +import org.h2.command.ddl.CreateFunctionAlias; +import org.h2.command.ddl.CreateIndex; +import org.h2.command.ddl.CreateLinkedTable; +import org.h2.command.ddl.CreateRole; +import org.h2.command.ddl.CreateSchema; +import org.h2.command.ddl.CreateSequence; +import org.h2.command.ddl.CreateSynonym; +import org.h2.command.ddl.CreateTable; +import org.h2.command.ddl.CreateTrigger; +import org.h2.command.ddl.CreateUser; +import org.h2.command.ddl.CreateView; +import org.h2.command.ddl.DeallocateProcedure; +import org.h2.command.ddl.DefineCommand; +import org.h2.command.ddl.DropAggregate; +import org.h2.command.ddl.DropConstant; +import org.h2.command.ddl.DropDatabase; +import org.h2.command.ddl.DropDomain; +import org.h2.command.ddl.DropFunctionAlias; +import org.h2.command.ddl.DropIndex; +import org.h2.command.ddl.DropRole; +import org.h2.command.ddl.DropSchema; +import org.h2.command.ddl.DropSequence; +import org.h2.command.ddl.DropSynonym; +import org.h2.command.ddl.DropTable; +import org.h2.command.ddl.DropTrigger; +import org.h2.command.ddl.DropUser; +import org.h2.command.ddl.DropView; +import org.h2.command.ddl.GrantRevoke; +import org.h2.command.ddl.PrepareProcedure; +import org.h2.command.ddl.SequenceOptions; +import org.h2.command.ddl.SetComment; +import org.h2.command.ddl.TruncateTable; +import org.h2.command.dml.AlterTableSet; +import org.h2.command.dml.BackupCommand; +import org.h2.command.dml.Call; +import org.h2.command.dml.CommandWithValues; +import org.h2.command.dml.DataChangeStatement; +import org.h2.command.dml.Delete; +import org.h2.command.dml.ExecuteImmediate; +import org.h2.command.dml.ExecuteProcedure; +import org.h2.command.dml.Explain; +import org.h2.command.dml.Help; +import org.h2.command.dml.Insert; +import org.h2.command.dml.Merge; +import org.h2.command.dml.MergeUsing; +import org.h2.command.dml.NoOperation; +import org.h2.command.dml.RunScriptCommand; +import org.h2.command.dml.ScriptCommand; +import org.h2.command.dml.Set; +import org.h2.command.dml.SetClauseList; +import org.h2.command.dml.SetSessionCharacteristics; +import org.h2.command.dml.SetTypes; +import org.h2.command.dml.TransactionCommand; +import org.h2.command.dml.Update; +import org.h2.command.query.Query; +import org.h2.command.query.QueryOrderBy; +import org.h2.command.query.Select; +import org.h2.command.query.SelectUnion; +import org.h2.command.query.TableValueConstructor; +import org.h2.constraint.ConstraintActionType; +import org.h2.engine.ConnectionInfo; +import org.h2.engine.Constants; +import org.h2.engine.Database; +import org.h2.engine.DbObject; +import org.h2.engine.DbSettings; +import org.h2.engine.IsolationLevel; +import org.h2.engine.Mode; +import org.h2.engine.Mode.ModeEnum; +import org.h2.engine.Procedure; +import org.h2.engine.Right; +import org.h2.engine.SessionLocal; +import org.h2.engine.User; +import org.h2.expression.Alias; +import org.h2.expression.ArrayConstructorByQuery; +import org.h2.expression.ArrayElementReference; +import org.h2.expression.BinaryOperation; +import org.h2.expression.BinaryOperation.OpType; +import org.h2.expression.ConcatenationOperation; +import org.h2.expression.DomainValueExpression; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.expression.ExpressionList; +import org.h2.expression.ExpressionWithFlags; +import org.h2.expression.ExpressionWithVariableParameters; +import org.h2.expression.FieldReference; +import org.h2.expression.Format; +import org.h2.expression.Format.FormatEnum; +import org.h2.expression.Parameter; +import org.h2.expression.Rownum; +import org.h2.expression.SearchedCase; +import org.h2.expression.SequenceValue; +import org.h2.expression.SimpleCase; +import org.h2.expression.Subquery; +import org.h2.expression.TimeZoneOperation; +import org.h2.expression.TypedValueExpression; +import org.h2.expression.UnaryOperation; +import org.h2.expression.ValueExpression; +import org.h2.expression.Variable; +import org.h2.expression.Wildcard; +import org.h2.expression.aggregate.AbstractAggregate; +import org.h2.expression.aggregate.Aggregate; +import org.h2.expression.aggregate.AggregateType; +import org.h2.expression.aggregate.JavaAggregate; +import org.h2.expression.aggregate.ListaggArguments; +import org.h2.expression.analysis.DataAnalysisOperation; +import org.h2.expression.analysis.Window; +import org.h2.expression.analysis.WindowFrame; +import org.h2.expression.analysis.WindowFrameBound; +import org.h2.expression.analysis.WindowFrameBoundType; +import org.h2.expression.analysis.WindowFrameExclusion; +import org.h2.expression.analysis.WindowFrameUnits; +import org.h2.expression.analysis.WindowFunction; +import org.h2.expression.analysis.WindowFunctionType; +import org.h2.expression.condition.BetweenPredicate; +import org.h2.expression.condition.BooleanTest; +import org.h2.expression.condition.CompareLike; +import org.h2.expression.condition.CompareLike.LikeType; +import org.h2.expression.condition.Comparison; +import org.h2.expression.condition.ConditionAndOr; +import org.h2.expression.condition.ConditionAndOrN; +import org.h2.expression.condition.ConditionIn; +import org.h2.expression.condition.ConditionInParameter; +import org.h2.expression.condition.ConditionInQuery; +import org.h2.expression.condition.ConditionLocalAndGlobal; +import org.h2.expression.condition.ConditionNot; +import org.h2.expression.condition.ExistsPredicate; +import org.h2.expression.condition.IsJsonPredicate; +import org.h2.expression.condition.NullPredicate; +import org.h2.expression.condition.TypePredicate; +import org.h2.expression.condition.UniquePredicate; +import org.h2.expression.function.ArrayFunction; +import org.h2.expression.function.BitFunction; +import org.h2.expression.function.BuiltinFunctions; +import org.h2.expression.function.CSVWriteFunction; +import org.h2.expression.function.CardinalityExpression; +import org.h2.expression.function.CastSpecification; +import org.h2.expression.function.CoalesceFunction; +import org.h2.expression.function.CompatibilitySequenceValueFunction; +import org.h2.expression.function.CompressFunction; +import org.h2.expression.function.ConcatFunction; +import org.h2.expression.function.CryptFunction; +import org.h2.expression.function.CurrentDateTimeValueFunction; +import org.h2.expression.function.CurrentGeneralValueSpecification; +import org.h2.expression.function.DBObjectFunction; +import org.h2.expression.function.DataTypeSQLFunction; +import org.h2.expression.function.DateTimeFormatFunction; +import org.h2.expression.function.DateTimeFunction; +import org.h2.expression.function.DayMonthNameFunction; +import org.h2.expression.function.FileFunction; +import org.h2.expression.function.HashFunction; +import org.h2.expression.function.JavaFunction; +import org.h2.expression.function.JsonConstructorFunction; +import org.h2.expression.function.LengthFunction; +import org.h2.expression.function.MathFunction; +import org.h2.expression.function.MathFunction1; +import org.h2.expression.function.MathFunction2; +import org.h2.expression.function.NullIfFunction; +import org.h2.expression.function.RandFunction; +import org.h2.expression.function.RegexpFunction; +import org.h2.expression.function.SessionControlFunction; +import org.h2.expression.function.SetFunction; +import org.h2.expression.function.SignalFunction; +import org.h2.expression.function.SoundexFunction; +import org.h2.expression.function.StringFunction; +import org.h2.expression.function.StringFunction1; +import org.h2.expression.function.StringFunction2; +import org.h2.expression.function.SubstringFunction; +import org.h2.expression.function.SysInfoFunction; +import org.h2.expression.function.TableInfoFunction; +import org.h2.expression.function.ToCharFunction; +import org.h2.expression.function.TrimFunction; +import org.h2.expression.function.TruncateValueFunction; +import org.h2.expression.function.XMLFunction; +import org.h2.expression.function.table.ArrayTableFunction; +import org.h2.expression.function.table.CSVReadFunction; +import org.h2.expression.function.table.JavaTableFunction; +import org.h2.expression.function.table.LinkSchemaFunction; +import org.h2.expression.function.table.TableFunction; +import org.h2.index.Index; +import org.h2.message.DbException; +import org.h2.mode.FunctionsPostgreSQL; +import org.h2.mode.ModeFunction; +import org.h2.mode.OnDuplicateKeyValues; +import org.h2.mode.Regclass; +import org.h2.result.SortOrder; +import org.h2.schema.Domain; +import org.h2.schema.FunctionAlias; +import org.h2.schema.Schema; +import org.h2.schema.Sequence; +import org.h2.schema.UserAggregate; +import org.h2.schema.UserDefinedFunction; +import org.h2.table.Column; +import org.h2.table.DataChangeDeltaTable; +import org.h2.table.DataChangeDeltaTable.ResultOption; +import org.h2.table.DualTable; +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; +import org.h2.table.TableView; +import org.h2.util.HasSQL; +import org.h2.util.IntervalUtils; +import org.h2.util.ParserUtil; +import org.h2.util.StringUtils; +import org.h2.util.Utils; +import org.h2.util.geometry.EWKTUtils; +import org.h2.util.json.JSONItemType; +import org.h2.util.json.JsonConstructorUtils; +import org.h2.value.CompareMode; +import org.h2.value.DataType; +import org.h2.value.ExtTypeInfoEnum; +import org.h2.value.ExtTypeInfoGeometry; +import org.h2.value.ExtTypeInfoNumeric; +import org.h2.value.ExtTypeInfoRow; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueArray; +import org.h2.value.ValueBigint; +import org.h2.value.ValueDate; +import org.h2.value.ValueDouble; +import org.h2.value.ValueGeometry; +import org.h2.value.ValueInteger; +import org.h2.value.ValueInterval; +import org.h2.value.ValueJson; +import org.h2.value.ValueNull; +import org.h2.value.ValueNumeric; +import org.h2.value.ValueRow; +import org.h2.value.ValueTime; +import org.h2.value.ValueTimeTimeZone; +import org.h2.value.ValueTimestamp; +import org.h2.value.ValueTimestampTimeZone; +import org.h2.value.ValueUuid; +import org.h2.value.ValueVarchar; + +/** + * The parser is used to convert a SQL statement string to an command object. + * + * @author Thomas Mueller + * @author Noel Grandin + * @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888 + */ +public class Parser { + + private static final String WITH_STATEMENT_SUPPORTS_LIMITED_SUB_STATEMENTS = + "WITH statement supports only SELECT, TABLE, VALUES, " + + "CREATE TABLE, INSERT, UPDATE, MERGE or DELETE statements"; + + private final Database database; + private final SessionLocal session; + + /** + * @see org.h2.engine.DbSettings#databaseToLower + */ + private final boolean identifiersToLower; + /** + * @see org.h2.engine.DbSettings#databaseToUpper + */ + private final boolean identifiersToUpper; + + /** + * @see org.h2.engine.SessionLocal#isVariableBinary() + */ + private final boolean variableBinary; + + private final BitSet nonKeywords; + + ArrayList tokens; + int tokenIndex; + Token token; + private int currentTokenType; + private String currentToken; + private String sqlCommand; + private CreateView createView; + private Prepared currentPrepared; + private Select currentSelect; + private List cteCleanups; + private ArrayList parameters; + private ArrayList suppliedParameters; + private String schemaName; + private ArrayList expectedList; + private boolean rightsChecked; + private boolean recompileAlways; + private boolean literalsChecked; + private int orderInFrom; + private boolean parseDomainConstraint; + + /** + * Parses the specified collection of non-keywords. + * + * @param nonKeywords array of non-keywords in upper case + * @return bit set of non-keywords, or {@code null} + */ + public static BitSet parseNonKeywords(String[] nonKeywords) { + if (nonKeywords.length == 0) { + return null; + } + BitSet set = new BitSet(); + for (String nonKeyword : nonKeywords) { + int index = Arrays.binarySearch(TOKENS, FIRST_KEYWORD, LAST_KEYWORD + 1, nonKeyword); + if (index >= 0) { + set.set(index); + } + } + return set.isEmpty() ? null : set; + } + + /** + * Formats a comma-separated list of keywords. + * + * @param nonKeywords bit set of non-keywords, or {@code null} + * @return comma-separated list of non-keywords + */ + public static String formatNonKeywords(BitSet nonKeywords) { + if (nonKeywords == null || nonKeywords.isEmpty()) { + return ""; + } + StringBuilder builder = new StringBuilder(); + for (int i = -1; (i = nonKeywords.nextSetBit(i + 1)) >= 0;) { + if (i >= FIRST_KEYWORD && i <= LAST_KEYWORD) { + if (builder.length() > 0) { + builder.append(','); + } + builder.append(TOKENS[i]); + } + } + return builder.toString(); + } + + /** + * Creates a new instance of parser. + * + * @param session the session + */ + public Parser(SessionLocal session) { + this.database = session.getDatabase(); + DbSettings settings = database.getSettings(); + this.identifiersToLower = settings.databaseToLower; + this.identifiersToUpper = settings.databaseToUpper; + this.variableBinary = session.isVariableBinary(); + this.nonKeywords = session.getNonKeywords(); + this.session = session; + } + + /** + * Creates a new instance of parser for special use cases. + */ + public Parser() { + database = null; + identifiersToLower = false; + identifiersToUpper = false; + variableBinary = false; + nonKeywords = null; + session = null; + } + + /** + * Parse the statement and prepare it for execution. + * + * @param sql the SQL statement to parse + * @return the prepared object + */ + public Prepared prepare(String sql) { + Prepared p = parse(sql, null); + p.prepare(); + if (currentTokenType != END_OF_INPUT) { + throw getSyntaxError(); + } + 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. + * + * @param sql the SQL statement to parse + * @return the command object + */ + public Command prepareCommand(String sql) { + try { + Prepared p = parse(sql, null); + if (currentTokenType != SEMICOLON && currentTokenType != END_OF_INPUT) { + addExpected(SEMICOLON); + throw getSyntaxError(); + } + try { + p.prepare(); + } catch (Throwable t) { + CommandContainer.clearCTE(session, p); + throw t; + } + int sqlIndex = token.start(); + if (sqlIndex < sql.length()) { + sql = sql.substring(0, sqlIndex); + } + CommandContainer c = new CommandContainer(session, sql, p); + while (currentTokenType == SEMICOLON) { + read(); + } + if (currentTokenType != END_OF_INPUT) { + int offset = token.start(); + return prepareCommandList(c, p, sql, sqlCommand.substring(offset), getRemainingTokens(offset)); + } + return c; + } catch (DbException e) { + throw e.addSQL(sqlCommand); + } + } + + private CommandList prepareCommandList(CommandContainer command, Prepared p, String sql, String remainingSql, + ArrayList remainingTokens) { + try { + ArrayList list = Utils.newSmallArrayList(); + for (;;) { + if (p instanceof DefineCommand) { + // 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) { + // This command may depend on results of previous commands. + if (ex.getErrorCode() == ErrorCode.CANNOT_MIX_INDEXED_AND_UNINDEXED_PARAMS) { + throw ex; + } + return new CommandList(session, sql, command, list, parameters, remainingSql); + } + list.add(p); + if (currentTokenType != SEMICOLON && currentTokenType != END_OF_INPUT) { + addExpected(SEMICOLON); + throw getSyntaxError(); + } + while (currentTokenType == SEMICOLON) { + read(); + } + if (currentTokenType == END_OF_INPUT) { + break; + } + int offset = token.start(); + remainingSql = sqlCommand.substring(offset); + remainingTokens = getRemainingTokens(offset); + } + return new CommandList(session, sql, command, list, parameters, null); + } catch (Throwable t) { + command.clearCTE(); + throw t; + } + } + + private ArrayList getRemainingTokens(int offset) { + List subList = tokens.subList(tokenIndex, tokens.size()); + ArrayList remainingTokens = new ArrayList<>(subList); + subList.clear(); + tokens.add(new Token.EndOfInputToken(offset)); + for (Token token : remainingTokens) { + token.subtractFromStart(offset); + } + return remainingTokens; + } + + /** + * Parse the statement, but don't prepare it for execution. + * + * @param sql the SQL statement to parse + * @param tokens tokens, or null + * @return the prepared object + */ + Prepared parse(String sql, ArrayList tokens) { + initialize(sql, tokens, false); + Prepared p; + try { + // first, try the fast variant + p = parse(false); + } catch (DbException e) { + if (e.getErrorCode() == ErrorCode.SYNTAX_ERROR_1) { + // now, get the detailed exception + resetTokenIndex(); + p = parse(true); + } else { + throw e.addSQL(sql); + } + } + p.setPrepareAlways(recompileAlways); + p.setParameterList(parameters); + return p; + } + + 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; + read(); + Prepared p; + try { + p = parsePrepared(); + p.setCteCleanups(cteCleanups); + } catch (Throwable t) { + if (cteCleanups != null) { + CommandContainer.clearCTE(session, cteCleanups); + } + throw t; + } + return p; + } + + private Prepared parsePrepared() { + int start = tokenIndex; + Prepared c = null; + switch (currentTokenType) { + case END_OF_INPUT: + case SEMICOLON: + c = new NoOperation(session); + setSQL(c, start); + return c; + case PARAMETER: + // read the ? as a parameter + // this is an 'out' parameter - set a dummy value + readParameter().setValue(ValueNull.INSTANCE); + read(EQUAL); + start = tokenIndex; + read("CALL"); + c = parseCall(); + break; + case OPEN_PAREN: + case SELECT: + case TABLE: + case VALUES: + c = parseQuery(); + break; + case WITH: + read(); + c = parseWithStatementOrQuery(start); + break; + case SET: + read(); + c = parseSet(); + break; + case IDENTIFIER: + if (token.isQuoted()) { + break; + } + /* + * Convert a-z to A-Z. This method is safe, because only A-Z + * characters are considered below. + * + * Unquoted identifier is never empty. + */ + switch (currentToken.charAt(0) & 0xffdf) { + case 'A': + if (readIf("ALTER")) { + c = parseAlter(); + } else if (readIf("ANALYZE")) { + c = parseAnalyze(); + } + break; + case 'B': + if (readIf("BACKUP")) { + c = parseBackup(); + } else if (readIf("BEGIN")) { + c = parseBegin(); + } + break; + case 'C': + if (readIf("COMMIT")) { + c = parseCommit(); + } else if (readIf("CREATE")) { + c = parseCreate(); + } else if (readIf("CALL")) { + c = parseCall(); + } else if (readIf("CHECKPOINT")) { + c = parseCheckpoint(); + } else if (readIf("COMMENT")) { + c = parseComment(); + } + break; + case 'D': + if (readIf("DELETE")) { + c = parseDelete(start); + } else if (readIf("DROP")) { + c = parseDrop(); + } else if (readIf("DECLARE")) { + // support for DECLARE GLOBAL TEMPORARY TABLE... + c = parseCreate(); + } else if (database.getMode().getEnum() != ModeEnum.MSSQLServer && readIf("DEALLOCATE")) { + /* + * PostgreSQL-style DEALLOCATE is disabled in MSSQLServer + * mode because PostgreSQL-style EXECUTE is redefined in + * this mode. + */ + c = parseDeallocate(); + } + break; + case 'E': + if (readIf("EXPLAIN")) { + c = parseExplain(); + } else if (database.getMode().getEnum() != ModeEnum.MSSQLServer) { + if (readIf("EXECUTE")) { + c = parseExecutePostgre(); + } + } else { + if (readIf("EXEC") || readIf("EXECUTE")) { + c = parseExecuteSQLServer(); + } + } + break; + case 'G': + if (readIf("GRANT")) { + c = parseGrantRevoke(CommandInterface.GRANT); + } + break; + case 'H': + if (readIf("HELP")) { + c = parseHelp(); + } + break; + case 'I': + if (readIf("INSERT")) { + c = parseInsert(start); + } + break; + case 'M': + if (readIf("MERGE")) { + c = parseMerge(start); + } + break; + case 'P': + if (readIf("PREPARE")) { + c = parsePrepare(); + } + break; + case 'R': + if (readIf("ROLLBACK")) { + c = parseRollback(); + } else if (readIf("REVOKE")) { + c = parseGrantRevoke(CommandInterface.REVOKE); + } else if (readIf("RUNSCRIPT")) { + c = parseRunScript(); + } else if (readIf("RELEASE")) { + c = parseReleaseSavepoint(); + } else if (database.getMode().replaceInto && readIf("REPLACE")) { + c = parseReplace(start); + } + break; + case 'S': + if (readIf("SAVEPOINT")) { + c = parseSavepoint(); + } else if (readIf("SCRIPT")) { + c = parseScript(); + } else if (readIf("SHUTDOWN")) { + c = parseShutdown(); + } else if (readIf("SHOW")) { + c = parseShow(); + } + break; + case 'T': + if (readIf("TRUNCATE")) { + c = parseTruncate(); + } + break; + case 'U': + if (readIf("UPDATE")) { + c = parseUpdate(start); + } else if (readIf("USE")) { + c = parseUse(); + } + break; + } + } + 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 { + int index = (int) readLong() - 1; + if (index < 0 || index >= parameters.size()) { + throw getSyntaxError(); + } + Parameter p = parameters.get(index); + if (p == null) { + throw getSyntaxError(); + } + read(COLON); + Expression expr = readExpression(); + expr = expr.optimize(session); + p.setValue(expr.getValue(session)); + } while (readIf(COMMA)); + read(CLOSE_BRACE); + for (Parameter p : parameters) { + p.checkSet(); + } + parameters.clear(); + } + if (withParamValues || c.getSQL() == null) { + setSQL(c, start); + } + return c; + } + + private DbException getSyntaxError() { + if (expectedList == null || expectedList.isEmpty()) { + return DbException.getSyntaxError(sqlCommand, token.start()); + } + return DbException.getSyntaxError(sqlCommand, token.start(), String.join(", ", expectedList)); + } + + private Prepared parseBackup() { + BackupCommand command = new BackupCommand(session); + read(TO); + command.setFileName(readExpression()); + return command; + } + + private Prepared parseAnalyze() { + Analyze command = new Analyze(session); + if (readIf(TABLE)) { + Table table = readTableOrView(); + command.setTable(table); + } + if (readIf("SAMPLE_SIZE")) { + command.setTop(readNonNegativeInt()); + } + return command; + } + + private TransactionCommand parseBegin() { + TransactionCommand command; + if (!readIf("WORK")) { + readIf("TRANSACTION"); + } + command = new TransactionCommand(session, CommandInterface.BEGIN); + return command; + } + + private TransactionCommand parseCommit() { + TransactionCommand command; + if (readIf("TRANSACTION")) { + command = new TransactionCommand(session, CommandInterface.COMMIT_TRANSACTION); + command.setTransactionName(readIdentifier()); + return command; + } + command = new TransactionCommand(session, CommandInterface.COMMIT); + readIf("WORK"); + return command; + } + + private TransactionCommand parseShutdown() { + int type = CommandInterface.SHUTDOWN; + if (readIf("IMMEDIATELY")) { + type = CommandInterface.SHUTDOWN_IMMEDIATELY; + } else if (readIf("COMPACT")) { + type = CommandInterface.SHUTDOWN_COMPACT; + } else if (readIf("DEFRAG")) { + type = CommandInterface.SHUTDOWN_DEFRAG; + } else { + readIf("SCRIPT"); + } + return new TransactionCommand(session, type); + } + + private TransactionCommand parseRollback() { + TransactionCommand command; + if (readIf("TRANSACTION")) { + command = new TransactionCommand(session, CommandInterface.ROLLBACK_TRANSACTION); + command.setTransactionName(readIdentifier()); + return command; + } + readIf("WORK"); + if (readIf(TO)) { + read("SAVEPOINT"); + command = new TransactionCommand(session, CommandInterface.ROLLBACK_TO_SAVEPOINT); + command.setSavepointName(readIdentifier()); + } else { + command = new TransactionCommand(session, CommandInterface.ROLLBACK); + } + return command; + } + + private Prepared parsePrepare() { + if (readIf("COMMIT")) { + TransactionCommand command = new TransactionCommand(session, CommandInterface.PREPARE_COMMIT); + command.setTransactionName(readIdentifier()); + return command; + } + return parsePrepareProcedure(); + } + + private Prepared parsePrepareProcedure() { + if (database.getMode().getEnum() == ModeEnum.MSSQLServer) { + throw getSyntaxError(); + /* + * PostgreSQL-style PREPARE is disabled in MSSQLServer mode + * because PostgreSQL-style EXECUTE is redefined in this + * mode. + */ + } + String procedureName = readIdentifier(); + if (readIf(OPEN_PAREN)) { + ArrayList list = Utils.newSmallArrayList(); + for (int i = 0;; i++) { + Column column = parseColumnForTable("C" + i, true); + list.add(column); + if (!readIfMore()) { + break; + } + } + } + read(AS); + Prepared prep = parsePrepared(); + PrepareProcedure command = new PrepareProcedure(session); + command.setProcedureName(procedureName); + command.setPrepared(prep); + return command; + } + + private TransactionCommand parseSavepoint() { + TransactionCommand command = new TransactionCommand(session, CommandInterface.SAVEPOINT); + command.setSavepointName(readIdentifier()); + return command; + } + + private Prepared parseReleaseSavepoint() { + Prepared command = new NoOperation(session); + readIf("SAVEPOINT"); + readIdentifier(); + return command; + } + + private Schema findSchema(String schemaName) { + if (schemaName == null) { + return null; + } + Schema schema = database.findSchema(schemaName); + if (schema == null) { + if (equalsToken("SESSION", schemaName)) { + // for local temporary tables + schema = database.getSchema(session.getCurrentSchemaName()); + } + } + return schema; + } + + private Schema getSchema(String schemaName) { + if (schemaName == null) { + return null; + } + Schema schema = findSchema(schemaName); + if (schema == null) { + throw DbException.get(ErrorCode.SCHEMA_NOT_FOUND_1, schemaName); + } + return schema; + } + + private Schema getSchema() { + return getSchema(schemaName); + } + /* + * Gets the current schema for scenarios that need a guaranteed, non-null schema object. + * + * This routine is solely here + * because of the function readIdentifierWithSchema(String defaultSchemaName) - which + * is often called with a null parameter (defaultSchemaName) - then 6 lines into the function + * that routine nullifies the state field schemaName - which I believe is a bug. + * + * There are about 7 places where "readIdentifierWithSchema(null)" is called in this file. + * + * In other words when is it legal to not have an active schema defined by schemaName ? + * I don't think it's ever a valid case. I don't understand when that would be allowed. + * I spent a long time trying to figure this out. + * As another proof of this point, the command "SET SCHEMA=NULL" is not a valid command. + * + * I did try to fix this in readIdentifierWithSchema(String defaultSchemaName) + * - but every fix I tried cascaded so many unit test errors - so + * I gave up. I think this needs a bigger effort to fix his, as part of bigger, dedicated story. + * + */ + private Schema getSchemaWithDefault() { + if (schemaName == null) { + schemaName = session.getCurrentSchemaName(); + } + return getSchema(schemaName); + } + + private Column readTableColumn(TableFilter filter) { + String columnName = readIdentifier(); + if (readIf(DOT)) { + columnName = readTableColumn(filter, columnName); + } + return filter.getTable().getColumn(columnName); + } + + private String readTableColumn(TableFilter filter, String tableAlias) { + String columnName = readIdentifier(); + if (readIf(DOT)) { + String schema = tableAlias; + tableAlias = columnName; + columnName = readIdentifier(); + if (readIf(DOT)) { + checkDatabaseName(schema); + schema = tableAlias; + tableAlias = columnName; + columnName = readIdentifier(); + } + if (!equalsToken(schema, filter.getTable().getSchema().getName())) { + throw DbException.get(ErrorCode.SCHEMA_NOT_FOUND_1, schema); + } + } + if (!equalsToken(tableAlias, filter.getTableAlias())) { + throw DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, tableAlias); + } + return columnName; + } + + private Update parseUpdate(int start) { + Update command = new Update(session); + currentPrepared = command; + Expression fetch = null; + if (database.getMode().topInDML && readIf("TOP")) { + read(OPEN_PAREN); + fetch = readTerm().optimize(session); + read(CLOSE_PAREN); + } + TableFilter filter = readSimpleTableFilter(); + command.setTableFilter(filter); + command.setSetClauseList(readUpdateSetClause(filter)); + if (database.getMode().allowUsingFromClauseInUpdateStatement && readIf(FROM)) { + TableFilter fromTable = readTablePrimary(); + command.setFromTableFilter(fromTable); + } + if (readIf(WHERE)) { + command.setCondition(readExpression()); + } + if (fetch == null) { + // for MySQL compatibility + // (this syntax is supported, but ignored) + readIfOrderBy(); + fetch = readFetchOrLimit(); + } + command.setFetch(fetch); + setSQL(command, start); + return command; + } + + private SetClauseList readUpdateSetClause(TableFilter filter) { + read(SET); + SetClauseList list = new SetClauseList(filter.getTable()); + do { + if (readIf(OPEN_PAREN)) { + ArrayList columns = Utils.newSmallArrayList(); + do { + columns.add(readTableColumn(filter)); + } while (readIfMore()); + read(EQUAL); + list.addMultiple(columns, readExpression()); + } else { + Column column = readTableColumn(filter); + read(EQUAL); + list.addSingle(column, readExpressionOrDefault()); + } + } while (readIf(COMMA)); + return list; + } + + private TableFilter readSimpleTableFilter() { + return new TableFilter(session, readTableOrView(), readFromAlias(null), rightsChecked, currentSelect, 0, null); + } + + private Delete parseDelete(int start) { + Delete command = new Delete(session); + Expression fetch = null; + if (database.getMode().topInDML && readIf("TOP")) { + fetch = readTerm().optimize(session); + } + currentPrepared = command; + if (!readIf(FROM) && database.getMode().getEnum() == ModeEnum.MySQL) { + readIdentifierWithSchema(); + read(FROM); + } + command.setTableFilter(readSimpleTableFilter()); + if (readIf(WHERE)) { + command.setCondition(readExpression()); + } + if (fetch == null) { + fetch = readFetchOrLimit(); + } + command.setFetch(fetch); + setSQL(command, start); + return command; + } + + private Expression readFetchOrLimit() { + Expression fetch = null; + if (readIf(FETCH)) { + if (!readIf("FIRST")) { + read("NEXT"); + } + if (readIf(ROW) || readIf("ROWS")) { + fetch = ValueExpression.get(ValueInteger.get(1)); + } else { + fetch = readExpression().optimize(session); + if (!readIf(ROW)) { + read("ROWS"); + } + } + read("ONLY"); + } else if (database.getMode().limit && readIf(LIMIT)) { + fetch = readTerm().optimize(session); + } + return fetch; + } + + private IndexColumn[] parseIndexColumnList() { + ArrayList columns = Utils.newSmallArrayList(); + do { + columns.add(new IndexColumn(readIdentifier(), parseSortType())); + } while (readIfMore()); + return columns.toArray(new IndexColumn[0]); + } + + private int parseSortType() { + int sortType = !readIf("ASC") && readIf("DESC") ? SortOrder.DESCENDING : SortOrder.ASCENDING; + if (readIf("NULLS")) { + if (readIf("FIRST")) { + sortType |= SortOrder.NULLS_FIRST; + } else { + read("LAST"); + sortType |= SortOrder.NULLS_LAST; + } + } + return sortType; + } + + private String[] parseColumnList() { + ArrayList columns = Utils.newSmallArrayList(); + do { + columns.add(readIdentifier()); + } while (readIfMore()); + return columns.toArray(new String[0]); + } + + private Column[] parseColumnList(Table table) { + ArrayList columns = Utils.newSmallArrayList(); + HashSet set = new HashSet<>(); + if (!readIf(CLOSE_PAREN)) { + do { + Column column = parseColumn(table); + if (!set.add(column)) { + throw DbException.get(ErrorCode.DUPLICATE_COLUMN_NAME_1, column.getTraceSQL()); + } + columns.add(column); + } while (readIfMore()); + } + return columns.toArray(new Column[0]); + } + + private Column parseColumn(Table table) { + if (currentTokenType == _ROWID_) { + read(); + return table.getRowIdColumn(); + } + return table.getColumn(readIdentifier()); + } + + /** + * Read comma or closing brace. + * + * @return {@code true} if comma is read, {@code false} if brace is read + */ + private boolean readIfMore() { + if (readIf(COMMA)) { + return true; + } + read(CLOSE_PAREN); + return false; + } + + private Prepared parseHelp() { + HashSet conditions = new HashSet<>(); + while (currentTokenType != END_OF_INPUT) { + conditions.add(StringUtils.toUpperEnglish(currentToken)); + read(); + } + return new Help(session, conditions.toArray(new String[0])); + } + + private Prepared parseShow() { + ArrayList paramValues = Utils.newSmallArrayList(); + StringBuilder buff = new StringBuilder("SELECT "); + if (readIf("CLIENT_ENCODING")) { + // for PostgreSQL compatibility + buff.append("'UNICODE' CLIENT_ENCODING"); + } else if (readIf("DEFAULT_TRANSACTION_ISOLATION")) { + // for PostgreSQL compatibility + buff.append("'read committed' DEFAULT_TRANSACTION_ISOLATION"); + } else if (readIf("TRANSACTION")) { + // for PostgreSQL compatibility + read("ISOLATION"); + read("LEVEL"); + buff.append("LOWER(ISOLATION_LEVEL) TRANSACTION_ISOLATION FROM INFORMATION_SCHEMA.SESSIONS " + + "WHERE SESSION_ID = SESSION_ID()"); + } else if (readIf("DATESTYLE")) { + // for PostgreSQL compatibility + buff.append("'ISO' DATESTYLE"); + } else if (readIf("SEARCH_PATH")) { + // for PostgreSQL compatibility + String[] searchPath = session.getSchemaSearchPath(); + StringBuilder searchPathBuff = new StringBuilder(); + if (searchPath != null) { + for (int i = 0; i < searchPath.length; i ++) { + if (i > 0) { + searchPathBuff.append(", "); + } + ParserUtil.quoteIdentifier(searchPathBuff, searchPath[i], HasSQL.QUOTE_ONLY_WHEN_REQUIRED); + } + } + StringUtils.quoteStringSQL(buff, searchPathBuff.toString()); + buff.append(" SEARCH_PATH"); + } else if (readIf("SERVER_VERSION")) { + // for PostgreSQL compatibility + buff.append("'" + Constants.PG_VERSION + "' SERVER_VERSION"); + } else if (readIf("SERVER_ENCODING")) { + // for PostgreSQL compatibility + buff.append("'UTF8' SERVER_ENCODING"); + } else if (readIf("SSL")) { + // for PostgreSQL compatibility + buff.append("'off' SSL"); + } else if (readIf("TABLES")) { + // for MySQL compatibility + String schema = database.getMainSchema().getName(); + if (readIf(FROM)) { + schema = readIdentifier(); + } + buff.append("TABLE_NAME, TABLE_SCHEMA FROM " + + "INFORMATION_SCHEMA.TABLES " + + "WHERE TABLE_SCHEMA=? ORDER BY TABLE_NAME"); + paramValues.add(ValueVarchar.get(schema)); + } else if (readIf("COLUMNS")) { + // for MySQL compatibility + read(FROM); + String tableName = readIdentifierWithSchema(); + String schemaName = getSchema().getName(); + paramValues.add(ValueVarchar.get(tableName)); + if (readIf(FROM)) { + schemaName = readIdentifier(); + } + buff.append("C.COLUMN_NAME FIELD, "); + boolean oldInformationSchema = session.isOldInformationSchema(); + buff.append(oldInformationSchema + ? "C.COLUMN_TYPE" + : "DATA_TYPE_SQL(?2, ?1, 'TABLE', C.DTD_IDENTIFIER)"); + buff.append(" TYPE, " + + "C.IS_NULLABLE \"NULL\", " + + "CASE (SELECT MAX(I.INDEX_TYPE_NAME) FROM " + + "INFORMATION_SCHEMA.INDEXES I "); + if (!oldInformationSchema) { + buff.append("JOIN INFORMATION_SCHEMA.INDEX_COLUMNS IC "); + } + buff.append("WHERE I.TABLE_SCHEMA=C.TABLE_SCHEMA " + + "AND I.TABLE_NAME=C.TABLE_NAME "); + if (oldInformationSchema) { + buff.append("AND I.COLUMN_NAME=C.COLUMN_NAME"); + } else { + buff.append("AND IC.TABLE_SCHEMA=C.TABLE_SCHEMA " + + "AND IC.TABLE_NAME=C.TABLE_NAME " + + "AND IC.INDEX_SCHEMA=I.INDEX_SCHEMA " + + "AND IC.INDEX_NAME=I.INDEX_NAME " + + "AND IC.COLUMN_NAME=C.COLUMN_NAME"); + } + buff.append(')' + + "WHEN 'PRIMARY KEY' THEN 'PRI' " + + "WHEN 'UNIQUE INDEX' THEN 'UNI' ELSE '' END `KEY`, " + + "COALESCE(COLUMN_DEFAULT, 'NULL') `DEFAULT` " + + "FROM INFORMATION_SCHEMA.COLUMNS C " + + "WHERE C.TABLE_NAME=?1 AND C.TABLE_SCHEMA=?2 " + + "ORDER BY C.ORDINAL_POSITION"); + paramValues.add(ValueVarchar.get(schemaName)); + } 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")) { + // for PostgreSQL compatibility + buff.append("NAME, SETTING FROM PG_CATALOG.PG_SETTINGS"); + } + boolean b = session.getAllowLiterals(); + try { + // need to temporarily enable it, in case we are in + // ALLOW_LITERALS_NUMBERS mode + session.setAllowLiterals(true); + return prepare(session, buff.toString(), paramValues); + } finally { + session.setAllowLiterals(b); + } + } + + private static Prepared prepare(SessionLocal s, String sql, + ArrayList paramValues) { + Prepared prep = s.prepare(sql); + ArrayList params = prep.getParameters(); + if (params != null) { + for (int i = 0, size = params.size(); i < size; i++) { + Parameter p = params.get(i); + p.setValue(paramValues.get(i)); + } + } + return prep; + } + + private boolean isDerivedTable() { + int offset = tokenIndex; + int level = 0; + while (tokens.get(offset).tokenType() == OPEN_PAREN) { + level++; + offset++; + } + boolean query = isDirectQuery(offset); + s: if (query && level > 0) { + offset = scanToCloseParen(offset + 1); + if (offset < 0) { + query = false; + break s; + } + for (;;) { + switch (tokens.get(offset).tokenType()) { + case SEMICOLON: + case END_OF_INPUT: + query = false; + break s; + case OPEN_PAREN: + offset = scanToCloseParen(offset + 1); + if (offset < 0) { + query = false; + break s; + } + break; + case CLOSE_PAREN: + if (--level == 0) { + break s; + } + offset++; + break; + case JOIN: + query = false; + break s; + default: + offset++; + } + } + } + return query; + } + + private boolean isQuery() { + int offset = tokenIndex; + int level = 0; + while (tokens.get(offset).tokenType() == OPEN_PAREN) { + level++; + offset++; + } + boolean query = isDirectQuery(offset); + s: if (query && level > 0) { + offset++; + do { + offset = scanToCloseParen(offset); + if (offset < 0) { + query = false; + break s; + } + switch (tokens.get(offset).tokenType()) { + default: + query = false; + break s; + case END_OF_INPUT: + case SEMICOLON: + case CLOSE_PAREN: + case ORDER: + case OFFSET: + case FETCH: + case LIMIT: + case UNION: + case EXCEPT: + case MINUS: + case INTERSECT: + } + } while (--level > 0); + } + return query; + } + + private int scanToCloseParen(int offset) { + for (int level = 0;;) { + switch (tokens.get(offset).tokenType()) { + case SEMICOLON: + case END_OF_INPUT: + return -1; + case OPEN_PAREN: + level++; + break; + case CLOSE_PAREN: + if (--level < 0) { + return offset + 1; + } + } + offset++; + } + } + + private boolean isQueryQuick() { + int offset = tokenIndex; + while (tokens.get(offset).tokenType() == OPEN_PAREN) { + offset++; + } + return isDirectQuery(offset); + } + + private boolean isDirectQuery(int offset) { + boolean query; + switch (tokens.get(offset).tokenType()) { + case SELECT: + case VALUES: + case WITH: + query = true; + break; + case TABLE: + query = tokens.get(offset + 1).tokenType() != OPEN_PAREN; + break; + default: + query = false; + } + return query; + } + + private Prepared parseMerge(int start) { + read("INTO"); + TableFilter targetTableFilter = readSimpleTableFilter(); + if (readIf(USING)) { + return parseMergeUsing(targetTableFilter, start); + } + return parseMergeInto(targetTableFilter, start); + } + + private Prepared parseMergeInto(TableFilter targetTableFilter, int start) { + Merge command = new Merge(session, false); + currentPrepared = command; + command.setTable(targetTableFilter.getTable()); + Table table = command.getTable(); + if (readIf(OPEN_PAREN)) { + if (isQueryQuick()) { + command.setQuery(parseQuery()); + read(CLOSE_PAREN); + return command; + } + command.setColumns(parseColumnList(table)); + } + if (readIf(KEY)) { + read(OPEN_PAREN); + command.setKeys(parseColumnList(table)); + } + if (readIf(VALUES)) { + parseValuesForCommand(command); + } else { + command.setQuery(parseQuery()); + } + setSQL(command, start); + return command; + } + + private MergeUsing parseMergeUsing(TableFilter targetTableFilter, int start) { + MergeUsing command = new MergeUsing(session, targetTableFilter); + currentPrepared = command; + command.setSourceTableFilter(readTableReference()); + read(ON); + Expression condition = readExpression(); + command.setOnCondition(condition); + + read(WHEN); + do { + boolean matched = readIf("MATCHED"); + if (matched) { + parseWhenMatched(command); + } else { + parseWhenNotMatched(command); + } + } while (readIf(WHEN)); + + setSQL(command, start); + return command; + } + + private void parseWhenMatched(MergeUsing command) { + Expression and = readIf(AND) ? readExpression() : null; + read("THEN"); + MergeUsing.When when; + if (readIf("UPDATE")) { + MergeUsing.WhenMatchedThenUpdate update = command.new WhenMatchedThenUpdate(); + update.setSetClauseList(readUpdateSetClause(command.getTargetTableFilter())); + when = update; + } else { + read("DELETE"); + when = command.new WhenMatchedThenDelete(); + } + if (and == null && database.getMode().mergeWhere && readIf(WHERE)) { + and = readExpression(); + } + when.setAndCondition(and); + command.addWhen(when); + } + + private void parseWhenNotMatched(MergeUsing command) { + read(NOT); + read("MATCHED"); + Expression and = readIf(AND) ? readExpression() : null; + read("THEN"); + read("INSERT"); + Column[] columns = readIf(OPEN_PAREN) ? parseColumnList(command.getTargetTableFilter().getTable()) : null; + Boolean overridingSystem = readIfOverriding(); + read(VALUES); + read(OPEN_PAREN); + ArrayList values = Utils.newSmallArrayList(); + if (!readIf(CLOSE_PAREN)) { + do { + values.add(readExpressionOrDefault()); + } while (readIfMore()); + } + MergeUsing.WhenNotMatched when = command.new WhenNotMatched(columns, overridingSystem, + values.toArray(new Expression[0])); + when.setAndCondition(and); + command.addWhen(when); + } + + private Insert parseInsert(int start) { + Insert command = new Insert(session); + currentPrepared = command; + Mode mode = database.getMode(); + if (mode.onDuplicateKeyUpdate && readIf("IGNORE")) { + command.setIgnore(true); + } + read("INTO"); + Table table = readTableOrView(); + command.setTable(table); + Column[] columns = null; + if (readIf(OPEN_PAREN)) { + if (isQueryQuick()) { + command.setQuery(parseQuery()); + read(CLOSE_PAREN); + return command; + } + columns = parseColumnList(table); + command.setColumns(columns); + } + Boolean overridingSystem = readIfOverriding(); + command.setOverridingSystem(overridingSystem); + boolean requireQuery = false; + if (readIf("DIRECT")) { + requireQuery = true; + command.setInsertFromSelect(true); + } + if (readIf("SORTED")) { + requireQuery = true; + } + readValues: { + if (!requireQuery) { + if (overridingSystem == null && readIf(DEFAULT)) { + read(VALUES); + command.addRow(new Expression[0]); + break readValues; + } + if (readIf(VALUES)) { + parseValuesForCommand(command); + break readValues; + } + if (readIf(SET)) { + parseInsertSet(command, table, columns); + break readValues; + } + } + command.setQuery(parseQuery()); + } + if (mode.onDuplicateKeyUpdate || mode.insertOnConflict || mode.isolationLevelInSelectOrInsertStatement) { + parseInsertCompatibility(command, table, mode); + } + setSQL(command, start); + return command; + } + + private Boolean readIfOverriding() { + Boolean overridingSystem = null; + if (readIf("OVERRIDING")) { + if (readIf(USER)) { + overridingSystem = Boolean.FALSE; + } else { + read("SYSTEM"); + overridingSystem = Boolean.TRUE; + } + read(VALUE); + } + return overridingSystem; + } + + private void parseInsertSet(Insert command, Table table, Column[] columns) { + if (columns != null) { + throw getSyntaxError(); + } + ArrayList columnList = Utils.newSmallArrayList(); + ArrayList values = Utils.newSmallArrayList(); + do { + columnList.add(parseColumn(table)); + read(EQUAL); + values.add(readExpressionOrDefault()); + } while (readIf(COMMA)); + command.setColumns(columnList.toArray(new Column[0])); + command.addRow(values.toArray(new Expression[0])); + } + + private void parseInsertCompatibility(Insert command, Table table, Mode mode) { + if (mode.onDuplicateKeyUpdate) { + if (readIf(ON)) { + read("DUPLICATE"); + read(KEY); + read("UPDATE"); + do { + String columnName = readIdentifier(); + if (readIf(DOT)) { + String schemaOrTableName = columnName; + String tableOrColumnName = readIdentifier(); + if (readIf(DOT)) { + if (!table.getSchema().getName().equals(schemaOrTableName)) { + throw DbException.get(ErrorCode.SCHEMA_NAME_MUST_MATCH); + } + columnName = readIdentifier(); + } else { + columnName = tableOrColumnName; + tableOrColumnName = schemaOrTableName; + } + if (!table.getName().equals(tableOrColumnName)) { + throw DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, tableOrColumnName); + } + } + Column column = table.getColumn(columnName); + read(EQUAL); + command.addAssignmentForDuplicate(column, readExpressionOrDefault()); + } while (readIf(COMMA)); + } + } + if (mode.insertOnConflict) { + if (readIf(ON)) { + read("CONFLICT"); + read("DO"); + read("NOTHING"); + command.setIgnore(true); + } + } + if (mode.isolationLevelInSelectOrInsertStatement) { + parseIsolationClause(); + } + } + + /** + * MySQL compatibility. REPLACE is similar to MERGE. + */ + private Merge parseReplace(int start) { + Merge command = new Merge(session, true); + currentPrepared = command; + read("INTO"); + Table table = readTableOrView(); + command.setTable(table); + if (readIf(OPEN_PAREN)) { + if (isQueryQuick()) { + command.setQuery(parseQuery()); + read(CLOSE_PAREN); + return command; + } + command.setColumns(parseColumnList(table)); + } + if (readIf(VALUES)) { + parseValuesForCommand(command); + } else { + command.setQuery(parseQuery()); + } + setSQL(command, start); + return command; + } + + private void parseValuesForCommand(CommandWithValues command) { + ArrayList values = Utils.newSmallArrayList(); + do { + values.clear(); + boolean multiColumn; + if (readIf(ROW)) { + read(OPEN_PAREN); + multiColumn = true; + } else { + multiColumn = readIf(OPEN_PAREN); + } + if (multiColumn) { + if (!readIf(CLOSE_PAREN)) { + do { + values.add(readExpressionOrDefault()); + } while (readIfMore()); + } + } else { + values.add(readExpressionOrDefault()); + } + command.addRow(values.toArray(new Expression[0])); + } while (readIf(COMMA)); + } + + private TableFilter readTablePrimary() { + Table table; + String alias = null; + label: if (readIf(OPEN_PAREN)) { + if (isDerivedTable()) { + // Derived table + return readDerivedTableWithCorrelation(); + } else { + // Parenthesized joined table + TableFilter tableFilter = readTableReference(); + read(CLOSE_PAREN); + return readCorrelation(tableFilter); + } + } else if (readIf(VALUES)) { + TableValueConstructor query = parseValues(); + alias = session.getNextSystemIdentifier(sqlCommand); + table = query.toTable(alias, null, parameters, createView != null, currentSelect); + } else if (readIf(TABLE)) { + // Table function derived table + read(OPEN_PAREN); + ArrayTableFunction function = readTableFunction(ArrayTableFunction.TABLE); + table = new FunctionTable(database.getMainSchema(), session, function); + } else { + boolean quoted = token.isQuoted(); + String tableName = readIdentifier(); + int backupIndex = tokenIndex; + schemaName = null; + if (readIf(DOT)) { + tableName = readIdentifierWithSchema2(tableName); + } else if (!quoted && readIf(TABLE)) { + table = readDataChangeDeltaTable(upperName(tableName), backupIndex); + break label; + } + Schema schema; + if (schemaName == null) { + schema = null; + } else { + schema = findSchema(schemaName); + if (schema == null) { + if (isDualTable(tableName)) { + table = new DualTable(database); + break label; + } + throw DbException.get(ErrorCode.SCHEMA_NOT_FOUND_1, schemaName); + } + } + boolean foundLeftParen = readIf(OPEN_PAREN); + if (foundLeftParen && readIf("INDEX")) { + // Sybase compatibility with + // "select * from test (index table1_index)" + readIdentifierWithSchema(null); + read(CLOSE_PAREN); + foundLeftParen = false; + } + if (foundLeftParen) { + Schema mainSchema = database.getMainSchema(); + if (equalsToken(tableName, RangeTable.NAME) + || equalsToken(tableName, RangeTable.ALIAS)) { + Expression min = readExpression(); + read(COMMA); + Expression max = readExpression(); + if (readIf(COMMA)) { + Expression step = readExpression(); + read(CLOSE_PAREN); + table = new RangeTable(mainSchema, min, max, step); + } else { + read(CLOSE_PAREN); + table = new RangeTable(mainSchema, min, max); + } + } else { + table = new FunctionTable(mainSchema, session, readTableFunction(tableName, schema)); + } + } else { + table = readTableOrView(tableName); + } + } + ArrayList derivedColumnNames = null; + IndexHints indexHints = null; + if (readIfUseIndex()) { + indexHints = parseIndexHints(table); + } else { + alias = readFromAlias(alias); + if (alias != null) { + derivedColumnNames = readDerivedColumnNames(); + if (readIfUseIndex()) { + indexHints = parseIndexHints(table); + } + } + } + return buildTableFilter(table, alias, derivedColumnNames, indexHints); + } + + private TableFilter readCorrelation(TableFilter tableFilter) { + String alias = readFromAlias(null); + if (alias != null) { + tableFilter.setAlias(alias); + ArrayList derivedColumnNames = readDerivedColumnNames(); + if (derivedColumnNames != null) { + tableFilter.setDerivedColumns(derivedColumnNames); + } + } + return tableFilter; + } + + private TableFilter readDerivedTableWithCorrelation() { + Query query = parseQueryExpression(); + read(CLOSE_PAREN); + Table table; + String alias; + ArrayList derivedColumnNames = null; + IndexHints indexHints = null; + if (readIfUseIndex()) { + alias = session.getNextSystemIdentifier(sqlCommand); + table = query.toTable(alias, null, parameters, createView != null, currentSelect); + indexHints = parseIndexHints(table); + } else { + alias = readFromAlias(null); + if (alias != null) { + derivedColumnNames = readDerivedColumnNames(); + Column[] columnTemplates = null; + if (derivedColumnNames != null) { + query.init(); + 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); + if (readIfUseIndex()) { + indexHints = parseIndexHints(table); + } + } else { + alias = session.getNextSystemIdentifier(sqlCommand); + table = query.toTable(alias, null, parameters, createView != null, currentSelect); + } + } + return buildTableFilter(table, alias, derivedColumnNames, indexHints); + } + + private TableFilter buildTableFilter(Table table, String alias, ArrayList derivedColumnNames, + IndexHints indexHints) { + if (database.getMode().discardWithTableHints) { + discardWithTableHints(); + } + // inherit alias for CTE as views from table name + if (alias == null && table.isView() && table.isTableExpression()) { + alias = table.getName(); + } + TableFilter filter = new TableFilter(session, table, alias, rightsChecked, + currentSelect, orderInFrom++, indexHints); + if (derivedColumnNames != null) { + filter.setDerivedColumns(derivedColumnNames); + } + return filter; + } + + private Table readDataChangeDeltaTable(String resultOptionName, int backupIndex) { + read(OPEN_PAREN); + int start = tokenIndex; + DataChangeStatement statement; + ResultOption resultOption = ResultOption.FINAL; + switch (resultOptionName) { + case "OLD": + resultOption = ResultOption.OLD; + if (readIf("UPDATE")) { + statement = parseUpdate(start); + } else if (readIf("DELETE")) { + statement = parseDelete(start); + } else if (readIf("MERGE")) { + statement = (DataChangeStatement) parseMerge(start); + } else if (database.getMode().replaceInto && readIf("REPLACE")) { + statement = parseReplace(start); + } else { + throw getSyntaxError(); + } + break; + case "NEW": + resultOption = ResultOption.NEW; + //$FALL-THROUGH$ + case "FINAL": + if (readIf("INSERT")) { + statement = parseInsert(start); + } else if (readIf("UPDATE")) { + statement = parseUpdate(start); + } else if (readIf("MERGE")) { + statement = (DataChangeStatement) parseMerge(start); + } else if (database.getMode().replaceInto && readIf("REPLACE")) { + statement = parseReplace(start); + } else { + throw getSyntaxError(); + } + break; + default: + setTokenIndex(backupIndex); + addExpected("OLD TABLE"); + addExpected("NEW TABLE"); + addExpected("FINAL TABLE"); + throw getSyntaxError(); + } + read(CLOSE_PAREN); + if (currentSelect != null) { + // Lobs aren't copied, so use it for more safety + currentSelect.setNeverLazy(true); + } + return new DataChangeDeltaTable(getSchemaWithDefault(), session, statement, resultOption); + } + + private TableFunction readTableFunction(String name, Schema schema) { + if (schema == null) { + switch (upperName(name)) { + case "UNNEST": + return readUnnestFunction(); + case "TABLE_DISTINCT": + return readTableFunction(ArrayTableFunction.TABLE_DISTINCT); + case "CSVREAD": + recompileAlways = true; + return readParameters(new CSVReadFunction()); + case "LINK_SCHEMA": + recompileAlways = true; + return readParameters(new LinkSchemaFunction()); + } + } + FunctionAlias functionAlias = getFunctionAliasWithinPath(name, schema); + if (!functionAlias.isDeterministic()) { + recompileAlways = true; + } + ArrayList argList = Utils.newSmallArrayList(); + if (!readIf(CLOSE_PAREN)) { + do { + argList.add(readExpression()); + } while (readIfMore()); + } + return new JavaTableFunction(functionAlias, argList.toArray(new Expression[0])); + } + + private boolean readIfUseIndex() { + int start = tokenIndex; + if (!readIf("USE")) { + return false; + } + if (!readIf("INDEX")) { + setTokenIndex(start); + return false; + } + return true; + } + + private IndexHints parseIndexHints(Table table) { + read(OPEN_PAREN); + LinkedHashSet indexNames = new LinkedHashSet<>(); + if (!readIf(CLOSE_PAREN)) { + do { + String indexName = readIdentifierWithSchema(); + Index index = table.getIndex(indexName); + indexNames.add(index.getName()); + } while (readIfMore()); + } + return IndexHints.createUseIndexHints(indexNames); + } + + private String readFromAlias(String alias) { + if (readIf(AS) || isIdentifier()) { + alias = readIdentifier(); + } + return alias; + } + + private ArrayList readDerivedColumnNames() { + if (readIf(OPEN_PAREN)) { + ArrayList derivedColumnNames = new ArrayList<>(); + do { + derivedColumnNames.add(readIdentifier()); + } while (readIfMore()); + return derivedColumnNames; + } + return null; + } + + private void discardWithTableHints() { + if (readIf(WITH)) { + read(OPEN_PAREN); + do { + discardTableHint(); + } while (readIfMore()); + } + } + + private void discardTableHint() { + if (readIf("INDEX")) { + if (readIf(OPEN_PAREN)) { + do { + readExpression(); + } while (readIfMore()); + } else { + read(EQUAL); + readExpression(); + } + } else { + readExpression(); + } + } + + private Prepared parseTruncate() { + read(TABLE); + Table table = readTableOrView(); + boolean restart = database.getMode().truncateTableRestartIdentity; + if (readIf("CONTINUE")) { + read("IDENTITY"); + restart = false; + } else if (readIf("RESTART")) { + read("IDENTITY"); + restart = true; + } + TruncateTable command = new TruncateTable(session); + command.setTable(table); + command.setRestart(restart); + return command; + } + + private boolean readIfExists(boolean ifExists) { + if (readIf(IF)) { + read(EXISTS); + ifExists = true; + } + return ifExists; + } + + private Prepared parseComment() { + int type = 0; + read(ON); + boolean column = false; + if (readIf(TABLE) || readIf("VIEW")) { + type = DbObject.TABLE_OR_VIEW; + } else if (readIf("COLUMN")) { + column = true; + type = DbObject.TABLE_OR_VIEW; + } else if (readIf("CONSTANT")) { + type = DbObject.CONSTANT; + } else if (readIf(CONSTRAINT)) { + type = DbObject.CONSTRAINT; + } else if (readIf("ALIAS")) { + type = DbObject.FUNCTION_ALIAS; + } else if (readIf("INDEX")) { + type = DbObject.INDEX; + } else if (readIf("ROLE")) { + type = DbObject.ROLE; + } else if (readIf("SCHEMA")) { + type = DbObject.SCHEMA; + } else if (readIf("SEQUENCE")) { + type = DbObject.SEQUENCE; + } else if (readIf("TRIGGER")) { + type = DbObject.TRIGGER; + } else if (readIf(USER)) { + type = DbObject.USER; + } else if (readIf("DOMAIN")) { + type = DbObject.DOMAIN; + } else { + throw getSyntaxError(); + } + SetComment command = new SetComment(session); + String objectName; + if (column) { + // can't use readIdentifierWithSchema() because + // it would not read [catalog.]schema.table.column correctly + objectName = readIdentifier(); + String tmpSchemaName = null; + read(DOT); + boolean allowEmpty = database.getMode().allowEmptySchemaValuesAsDefaultSchema; + String columnName = allowEmpty && currentTokenType == DOT ? null : readIdentifier(); + if (readIf(DOT)) { + tmpSchemaName = objectName; + objectName = columnName; + columnName = allowEmpty && currentTokenType == DOT ? null : readIdentifier(); + if (readIf(DOT)) { + checkDatabaseName(tmpSchemaName); + tmpSchemaName = objectName; + objectName = columnName; + columnName = readIdentifier(); + } + } + if (columnName == null || objectName == null) { + throw DbException.getSyntaxError(sqlCommand, token.start(), "table.column"); + } + schemaName = tmpSchemaName != null ? tmpSchemaName : session.getCurrentSchemaName(); + command.setColumn(true); + command.setColumnName(columnName); + } else { + objectName = readIdentifierWithSchema(); + } + command.setSchemaName(schemaName); + command.setObjectName(objectName); + command.setObjectType(type); + read(IS); + command.setCommentExpression(readExpression()); + return command; + } + + private Prepared parseDrop() { + if (readIf(TABLE)) { + boolean ifExists = readIfExists(false); + DropTable command = new DropTable(session); + do { + String tableName = readIdentifierWithSchema(); + command.addTable(getSchema(), tableName); + } while (readIf(COMMA)); + ifExists = readIfExists(ifExists); + command.setIfExists(ifExists); + if (readIf("CASCADE")) { + command.setDropAction(ConstraintActionType.CASCADE); + readIf("CONSTRAINTS"); + } else if (readIf("RESTRICT")) { + command.setDropAction(ConstraintActionType.RESTRICT); + } else if (readIf("IGNORE")) { + // TODO SET_DEFAULT works in the same way as CASCADE + command.setDropAction(ConstraintActionType.SET_DEFAULT); + } + return command; + } else if (readIf("INDEX")) { + boolean ifExists = readIfExists(false); + String indexName = readIdentifierWithSchema(); + DropIndex command = new DropIndex(session, getSchema()); + command.setIndexName(indexName); + ifExists = readIfExists(ifExists); + command.setIfExists(ifExists); + //Support for MySQL: DROP INDEX index_name ON tbl_name + if (readIf(ON)) { + readIdentifierWithSchema(); + } + return command; + } else if (readIf(USER)) { + boolean ifExists = readIfExists(false); + DropUser command = new DropUser(session); + command.setUserName(readIdentifier()); + ifExists = readIfExists(ifExists); + readIf("CASCADE"); + command.setIfExists(ifExists); + return command; + } else if (readIf("SEQUENCE")) { + boolean ifExists = readIfExists(false); + String sequenceName = readIdentifierWithSchema(); + DropSequence command = new DropSequence(session, getSchema()); + command.setSequenceName(sequenceName); + ifExists = readIfExists(ifExists); + command.setIfExists(ifExists); + return command; + } else if (readIf("CONSTANT")) { + boolean ifExists = readIfExists(false); + String constantName = readIdentifierWithSchema(); + DropConstant command = new DropConstant(session, getSchema()); + command.setConstantName(constantName); + ifExists = readIfExists(ifExists); + command.setIfExists(ifExists); + return command; + } else if (readIf("TRIGGER")) { + boolean ifExists = readIfExists(false); + String triggerName = readIdentifierWithSchema(); + DropTrigger command = new DropTrigger(session, getSchema()); + command.setTriggerName(triggerName); + ifExists = readIfExists(ifExists); + command.setIfExists(ifExists); + return command; + } else if (readIf("VIEW")) { + boolean ifExists = readIfExists(false); + String viewName = readIdentifierWithSchema(); + DropView command = new DropView(session, getSchema()); + command.setViewName(viewName); + ifExists = readIfExists(ifExists); + command.setIfExists(ifExists); + ConstraintActionType dropAction = parseCascadeOrRestrict(); + if (dropAction != null) { + command.setDropAction(dropAction); + } + return command; + } else if (readIf("ROLE")) { + boolean ifExists = readIfExists(false); + DropRole command = new DropRole(session); + command.setRoleName(readIdentifier()); + ifExists = readIfExists(ifExists); + command.setIfExists(ifExists); + return command; + } else if (readIf("ALIAS")) { + boolean ifExists = readIfExists(false); + String aliasName = readIdentifierWithSchema(); + DropFunctionAlias command = new DropFunctionAlias(session, + getSchema()); + command.setAliasName(aliasName); + ifExists = readIfExists(ifExists); + command.setIfExists(ifExists); + return command; + } else if (readIf("SCHEMA")) { + boolean ifExists = readIfExists(false); + DropSchema command = new DropSchema(session); + command.setSchemaName(readIdentifier()); + ifExists = readIfExists(ifExists); + command.setIfExists(ifExists); + ConstraintActionType dropAction = parseCascadeOrRestrict(); + if (dropAction != null) { + command.setDropAction(dropAction); + } + return command; + } else if (readIf(ALL)) { + read("OBJECTS"); + DropDatabase command = new DropDatabase(session); + command.setDropAllObjects(true); + if (readIf("DELETE")) { + read("FILES"); + command.setDeleteFiles(true); + } + return command; + } else if (readIf("DOMAIN") || readIf("TYPE") || readIf("DATATYPE")) { + return parseDropDomain(); + } else if (readIf("AGGREGATE")) { + return parseDropAggregate(); + } else if (readIf("SYNONYM")) { + boolean ifExists = readIfExists(false); + String synonymName = readIdentifierWithSchema(); + DropSynonym command = new DropSynonym(session, getSchema()); + command.setSynonymName(synonymName); + ifExists = readIfExists(ifExists); + command.setIfExists(ifExists); + return command; + } + throw getSyntaxError(); + } + + private DropDomain parseDropDomain() { + boolean ifExists = readIfExists(false); + String domainName = readIdentifierWithSchema(); + DropDomain command = new DropDomain(session, getSchema()); + command.setDomainName(domainName); + ifExists = readIfExists(ifExists); + command.setIfDomainExists(ifExists); + ConstraintActionType dropAction = parseCascadeOrRestrict(); + if (dropAction != null) { + command.setDropAction(dropAction); + } + return command; + } + + private DropAggregate parseDropAggregate() { + boolean ifExists = readIfExists(false); + String name = readIdentifierWithSchema(); + DropAggregate command = new DropAggregate(session, getSchema()); + command.setName(name); + ifExists = readIfExists(ifExists); + command.setIfExists(ifExists); + return command; + } + + private TableFilter readTableReference() { + for (TableFilter top, last = top = readTablePrimary(), join;; last = join) { + switch (currentTokenType) { + case RIGHT: { + read(); + readIf("OUTER"); + read(JOIN); + // the right hand side is the 'inner' table usually + join = readTableReference(); + Expression on = readJoinSpecification(top, join, true); + addJoin(join, top, true, on); + top = join; + break; + } + case LEFT: { + read(); + readIf("OUTER"); + read(JOIN); + join = readTableReference(); + Expression on = readJoinSpecification(top, join, false); + addJoin(top, join, true, on); + break; + } + case FULL: + read(); + throw getSyntaxError(); + case INNER: { + read(); + read(JOIN); + join = readTableReference(); + Expression on = readJoinSpecification(top, join, false); + addJoin(top, join, false, on); + break; + } + case JOIN: { + read(); + join = readTableReference(); + Expression on = readJoinSpecification(top, join, false); + addJoin(top, join, false, on); + break; + } + case CROSS: { + read(); + read(JOIN); + join = readTablePrimary(); + addJoin(top, join, false, null); + break; + } + case NATURAL: { + read(); + read(JOIN); + join = readTablePrimary(); + Expression on = null; + for (Column column1 : last.getTable().getColumns()) { + Column column2 = join.getColumn(last.getColumnName(column1), true); + if (column2 != null) { + on = addJoinColumn(on, last, join, column1, column2, false); + } + } + addJoin(top, join, false, on); + break; + } + default: + if (expectedList != null) { + // FULL is intentionally excluded + addMultipleExpected(RIGHT, LEFT, INNER, JOIN, CROSS, NATURAL); + } + return top; + } + } + } + + private Expression readJoinSpecification(TableFilter filter1, TableFilter filter2, boolean rightJoin) { + Expression on = null; + if (readIf(ON)) { + on = readExpression(); + } else if (readIf(USING)) { + read(OPEN_PAREN); + do { + String columnName = readIdentifier(); + on = addJoinColumn(on, filter1, filter2, filter1.getColumn(columnName, false), + filter2.getColumn(columnName, false), rightJoin); + } while (readIfMore()); + } + return on; + } + + private Expression addJoinColumn(Expression on, TableFilter filter1, TableFilter filter2, Column column1, + Column column2, boolean rightJoin) { + if (rightJoin) { + filter1.addCommonJoinColumns(column1, column2, filter2); + filter2.addCommonJoinColumnToExclude(column2); + } else { + filter1.addCommonJoinColumns(column1, column1, filter1); + filter2.addCommonJoinColumnToExclude(column2); + } + Expression tableExpr = new ExpressionColumn(database, filter1.getSchemaName(), filter1.getTableAlias(), + filter1.getColumnName(column1)); + Expression joinExpr = new ExpressionColumn(database, filter2.getSchemaName(), filter2.getTableAlias(), + filter2.getColumnName(column2)); + Expression equal = new Comparison(Comparison.EQUAL, tableExpr, joinExpr, false); + if (on == null) { + on = equal; + } else { + on = new ConditionAndOr(ConditionAndOr.AND, on, equal); + } + return on; + } + + /** + * Add one join to another. This method creates nested join between them if + * required. + * + * @param top parent join + * @param join child join + * @param outer if child join is an outer join + * @param on the join condition + * @see TableFilter#addJoin(TableFilter, boolean, Expression) + */ + private void addJoin(TableFilter top, TableFilter join, boolean outer, Expression on) { + if (join.getJoin() != null) { + String joinTable = Constants.PREFIX_JOIN + token.start(); + TableFilter n = new TableFilter(session, new DualTable(database), + joinTable, rightsChecked, currentSelect, join.getOrderInFrom(), + null); + n.setNestedJoin(join); + join = n; + } + top.addJoin(join, outer, on); + } + + private Prepared parseExecutePostgre() { + if (readIf("IMMEDIATE")) { + return new ExecuteImmediate(session, readExpression()); + } + ExecuteProcedure command = new ExecuteProcedure(session); + String procedureName = readIdentifier(); + Procedure p = session.getProcedure(procedureName); + if (p == null) { + throw DbException.get(ErrorCode.FUNCTION_ALIAS_NOT_FOUND_1, + procedureName); + } + command.setProcedure(p); + if (readIf(OPEN_PAREN)) { + for (int i = 0;; i++) { + command.setExpression(i, readExpression()); + if (!readIfMore()) { + break; + } + } + } + return command; + } + + private Prepared parseExecuteSQLServer() { + Call command = new Call(session); + currentPrepared = command; + String schemaName = null; + String name = readIdentifier(); + if (readIf(DOT)) { + schemaName = name; + name = readIdentifier(); + if (readIf(DOT)) { + checkDatabaseName(schemaName); + schemaName = name; + name = readIdentifier(); + } + } + FunctionAlias functionAlias = getFunctionAliasWithinPath(name, + schemaName != null ? database.getSchema(schemaName) : null); + Expression[] args; + ArrayList argList = Utils.newSmallArrayList(); + if (currentTokenType != SEMICOLON && currentTokenType != END_OF_INPUT) { + do { + argList.add(readExpression()); + } while (readIf(COMMA)); + } + args = argList.toArray(new Expression[0]); + command.setExpression(new JavaFunction(functionAlias, args)); + return command; + } + + private FunctionAlias getFunctionAliasWithinPath(String name, Schema schema) { + UserDefinedFunction userDefinedFunction = findUserDefinedFunctionWithinPath(schema, name); + if (userDefinedFunction instanceof FunctionAlias) { + return (FunctionAlias) userDefinedFunction; + } + throw DbException.get(ErrorCode.FUNCTION_NOT_FOUND_1, name); + } + + private DeallocateProcedure parseDeallocate() { + readIf("PLAN"); + DeallocateProcedure command = new DeallocateProcedure(session); + command.setProcedureName(readIdentifier()); + return command; + } + + private Explain parseExplain() { + Explain command = new Explain(session); + if (readIf("ANALYZE")) { + command.setExecuteCommand(true); + } else { + if (readIf("PLAN")) { + readIf(FOR); + } + } + switch (currentTokenType) { + case SELECT: + case TABLE: + case VALUES: + case WITH: + case OPEN_PAREN: + Query query = parseQuery(); + query.setNeverLazy(true); + command.setCommand(query); + break; + default: + int start = tokenIndex; + if (readIf("DELETE")) { + command.setCommand(parseDelete(start)); + } else if (readIf("UPDATE")) { + command.setCommand(parseUpdate(start)); + } else if (readIf("INSERT")) { + command.setCommand(parseInsert(start)); + } else if (readIf("MERGE")) { + command.setCommand(parseMerge(start)); + } else { + throw getSyntaxError(); + } + } + return command; + } + + private Query parseQuery() { + int paramIndex = parameters.size(); + Query command = parseQueryExpression(); + int size = parameters.size(); + ArrayList params = new ArrayList<>(size); + for (int i = paramIndex; i < size; i++) { + params.add(parameters.get(i)); + } + command.setParameterList(params); + command.init(); + return command; + } + + private Prepared parseWithStatementOrQuery(int start) { + int paramIndex = parameters.size(); + Prepared command = parseWith(); + int size = parameters.size(); + ArrayList params = new ArrayList<>(size); + for (int i = paramIndex; i < size; i++) { + params.add(parameters.get(i)); + } + command.setParameterList(params); + if (command instanceof Query) { + Query query = (Query) command; + query.init(); + } + setSQL(command, start); + return command; + } + + private Query parseQueryExpression() { + Query query; + if (readIf(WITH)) { + try { + query = (Query) parseWith(); + } catch (ClassCastException e) { + throw DbException.get(ErrorCode.SYNTAX_ERROR_1, "WITH statement supports only query in this context"); + } + // recursive can not be lazy + query.setNeverLazy(true); + } else { + query = parseQueryExpressionBodyAndEndOfQuery(); + } + return query; + } + + private Query parseQueryExpressionBodyAndEndOfQuery() { + int start = tokenIndex; + Query command = parseQueryExpressionBody(); + parseEndOfQuery(command); + setSQL(command, start); + return command; + } + + private Query parseQueryExpressionBody() { + Query command = parseQueryTerm(); + for (;;) { + SelectUnion.UnionType type; + if (readIf(UNION)) { + if (readIf(ALL)) { + type = SelectUnion.UnionType.UNION_ALL; + } else { + readIf(DISTINCT); + type = SelectUnion.UnionType.UNION; + } + } else if (readIf(EXCEPT) || readIf(MINUS)) { + type = SelectUnion.UnionType.EXCEPT; + } else { + break; + } + command = new SelectUnion(session, type, command, parseQueryTerm()); + } + return command; + } + + private Query parseQueryTerm() { + Query command = parseQueryPrimary(); + while (readIf(INTERSECT)) { + command = new SelectUnion(session, SelectUnion.UnionType.INTERSECT, command, parseQueryPrimary()); + } + return command; + } + + private void parseEndOfQuery(Query command) { + if (readIf(ORDER)) { + read("BY"); + Select oldSelect = currentSelect; + if (command instanceof Select) { + currentSelect = (Select) command; + } + ArrayList orderList = Utils.newSmallArrayList(); + do { + boolean canBeNumber = currentTokenType == LITERAL; + QueryOrderBy order = new QueryOrderBy(); + Expression expr = readExpression(); + if (canBeNumber && expr instanceof ValueExpression && expr.getType().getValueType() == Value.INTEGER) { + order.columnIndexExpr = expr; + } else if (expr instanceof Parameter) { + recompileAlways = true; + order.columnIndexExpr = expr; + } else { + order.expression = expr; + } + order.sortType = parseSortType(); + orderList.add(order); + } while (readIf(COMMA)); + command.setOrder(orderList); + currentSelect = oldSelect; + } + if (command.getFetch() == null) { + // make sure aggregate functions will not work here + Select temp = currentSelect; + currentSelect = null; + boolean hasOffsetOrFetch = false; + // Standard SQL OFFSET / FETCH + if (readIf(OFFSET)) { + hasOffsetOrFetch = true; + command.setOffset(readExpression().optimize(session)); + if (!readIf(ROW)) { + readIf("ROWS"); + } + } + if (readIf(FETCH)) { + hasOffsetOrFetch = true; + if (!readIf("FIRST")) { + read("NEXT"); + } + if (readIf(ROW) || readIf("ROWS")) { + command.setFetch(ValueExpression.get(ValueInteger.get(1))); + } else { + command.setFetch(readExpression().optimize(session)); + if (readIf("PERCENT")) { + command.setFetchPercent(true); + } + if (!readIf(ROW)) { + read("ROWS"); + } + } + if (readIf(WITH)) { + read("TIES"); + command.setWithTies(true); + } else { + read("ONLY"); + } + } + // MySQL-style LIMIT / OFFSET + if (!hasOffsetOrFetch && database.getMode().limit && readIf(LIMIT)) { + Expression limit = readExpression().optimize(session); + if (readIf(OFFSET)) { + command.setOffset(readExpression().optimize(session)); + } else if (readIf(COMMA)) { + // MySQL: [offset, ] rowcount + Expression offset = limit; + limit = readExpression().optimize(session); + command.setOffset(offset); + } + command.setFetch(limit); + } + currentSelect = temp; + } + if (readIf(FOR)) { + if (readIf("UPDATE")) { + if (readIf("OF")) { + do { + readIdentifierWithSchema(); + } while (readIf(COMMA)); + } else if (readIf("NOWAIT")) { + // TODO parser: select for update nowait: should not wait + } + command.setForUpdate(true); + } else if (readIf("READ") || readIf(FETCH)) { + read("ONLY"); + } + } + if (database.getMode().isolationLevelInSelectOrInsertStatement) { + parseIsolationClause(); + } + } + + /** + * DB2 isolation clause + */ + private void parseIsolationClause() { + if (readIf(WITH)) { + if (readIf("RR") || readIf("RS")) { + // concurrent-access-resolution clause + if (readIf("USE")) { + read(AND); + read("KEEP"); + if (readIf("SHARE") || readIf("UPDATE") || + readIf("EXCLUSIVE")) { + // ignore + } + read("LOCKS"); + } + } else if (readIf("CS") || readIf("UR")) { + // ignore + } + } + } + + private Query parseQueryPrimary() { + if (readIf(OPEN_PAREN)) { + Query command = parseQueryExpressionBodyAndEndOfQuery(); + read(CLOSE_PAREN); + return command; + } + int start = tokenIndex; + if (readIf(SELECT)) { + return parseSelect(start); + } else if (readIf(TABLE)) { + return parseExplicitTable(start); + } + read(VALUES); + return parseValues(); + } + + private void parseSelectFromPart(Select command) { + do { + TableFilter top = readTableReference(); + command.addTableFilter(top, true); + boolean isOuter = false; + for (;;) { + TableFilter n = top.getNestedJoin(); + if (n != null) { + n.visit(f -> command.addTableFilter(f, false)); + } + TableFilter join = top.getJoin(); + if (join == null) { + break; + } + isOuter = isOuter | join.isJoinOuter(); + if (isOuter) { + command.addTableFilter(join, false); + } else { + // make flat so the optimizer can work better + Expression on = join.getJoinCondition(); + if (on != null) { + command.addCondition(on); + } + join.removeJoinCondition(); + top.removeJoin(); + command.addTableFilter(join, true); + } + top = join; + } + } while (readIf(COMMA)); + } + + private void parseSelectExpressions(Select command) { + if (database.getMode().topInSelect && readIf("TOP")) { + Select temp = currentSelect; + // make sure aggregate functions will not work in TOP and LIMIT + currentSelect = null; + // can't read more complex expressions here because + // SELECT TOP 1 +? A FROM TEST could mean + // SELECT TOP (1+?) A FROM TEST or + // SELECT TOP 1 (+?) AS A FROM TEST + command.setFetch(readTerm().optimize(session)); + if (readIf("PERCENT")) { + command.setFetchPercent(true); + } + if (readIf(WITH)) { + read("TIES"); + command.setWithTies(true); + } + currentSelect = temp; + } + if (readIf(DISTINCT)) { + if (readIf(ON)) { + read(OPEN_PAREN); + ArrayList distinctExpressions = Utils.newSmallArrayList(); + do { + distinctExpressions.add(readExpression()); + } while (readIfMore()); + command.setDistinct(distinctExpressions.toArray(new Expression[0])); + } else { + command.setDistinct(); + } + } else { + readIf(ALL); + } + ArrayList expressions = Utils.newSmallArrayList(); + do { + if (readIf(ASTERISK)) { + expressions.add(parseWildcard(null, null)); + } else { + switch (currentTokenType) { + case FROM: + case WHERE: + case GROUP: + case HAVING: + case WINDOW: + case QUALIFY: + case ORDER: + case OFFSET: + case FETCH: + case CLOSE_PAREN: + case SEMICOLON: + case END_OF_INPUT: + break; + default: + Expression expr = readExpression(); + if (readIf(AS) || isIdentifier()) { + expr = new Alias(expr, readIdentifier(), database.getMode().aliasColumnName); + } + expressions.add(expr); + } + } + } while (readIf(COMMA)); + command.setExpressions(expressions); + } + + private Select parseSelect(int start) { + Select command = new Select(session, currentSelect); + Select oldSelect = currentSelect; + Prepared oldPrepared = currentPrepared; + currentSelect = command; + currentPrepared = command; + parseSelectExpressions(command); + if (!readIf(FROM)) { + // select without FROM + TableFilter filter = new TableFilter(session, new DualTable(database), null, rightsChecked, + currentSelect, 0, null); + command.addTableFilter(filter, true); + } else { + parseSelectFromPart(command); + } + if (readIf(WHERE)) { + command.addCondition(readExpressionWithGlobalConditions()); + } + // 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"); + command.setGroupQuery(); + ArrayList list = Utils.newSmallArrayList(); + do { + if (isToken(OPEN_PAREN) && isOrdinaryGroupingSet()) { + if (!readIf(CLOSE_PAREN)) { + do { + list.add(readExpression()); + } while (readIfMore()); + } + } else { + Expression expr = readExpression(); + if (database.getMode().groupByColumnIndex && expr instanceof ValueExpression && + expr.getType().getValueType() == Value.INTEGER) { + ArrayList expressions = command.getExpressions(); + for (Expression e : expressions) { + if (e instanceof Wildcard) { + throw getSyntaxError(); + } + } + int idx = expr.getValue(session).getInt(); + if (idx < 1 || idx > expressions.size()) { + throw DbException.get(ErrorCode.GROUP_BY_NOT_IN_THE_RESULT, Integer.toString(idx), + Integer.toString(expressions.size())); + } + list.add(expressions.get(idx-1)); + } else { + list.add(expr); + } + } + } while (readIf(COMMA)); + if (!list.isEmpty()) { + command.setGroupBy(list); + } + } + currentSelect = command; + if (readIf(HAVING)) { + command.setGroupQuery(); + command.setHaving(readExpressionWithGlobalConditions()); + } + if (readIf(WINDOW)) { + do { + int sqlIndex = token.start(); + String name = readIdentifier(); + read(AS); + Window w = readWindowSpecification(); + if (!currentSelect.addWindow(name, w)) { + throw DbException.getSyntaxError(sqlCommand, sqlIndex, "unique identifier"); + } + } while (readIf(COMMA)); + } + if (readIf(QUALIFY)) { + command.setWindowQuery(); + command.setQualify(readExpressionWithGlobalConditions()); + } + command.setParameterList(parameters); + currentSelect = oldSelect; + currentPrepared = oldPrepared; + setSQL(command, start); + return command; + } + + /** + * Checks whether current opening parenthesis can be a start of ordinary + * grouping set. This method reads this parenthesis if it is. + * + * @return whether current opening parenthesis can be a start of ordinary + * grouping set + */ + private boolean isOrdinaryGroupingSet() { + int offset = scanToCloseParen(tokenIndex + 1); + if (offset < 0) { + // Try to parse as expression to get better syntax error + return false; + } + switch (tokens.get(offset).tokenType()) { + // End of query + case CLOSE_PAREN: + case SEMICOLON: + case END_OF_INPUT: + // Next grouping element + case COMMA: + // Next select clause + case HAVING: + case WINDOW: + case QUALIFY: + // Next query expression body clause + case UNION: + case EXCEPT: + case MINUS: + case INTERSECT: + // Next query expression clause + case ORDER: + case OFFSET: + case FETCH: + case LIMIT: + case FOR: + setTokenIndex(tokenIndex + 1); + return true; + default: + return false; + } + } + + private Query parseExplicitTable(int start) { + Table table = readTableOrView(); + Select command = new Select(session, currentSelect); + TableFilter filter = new TableFilter(session, table, null, rightsChecked, + command, orderInFrom++, null); + command.addTableFilter(filter, true); + command.setExplicitTable(); + setSQL(command, start); + return command; + } + + private void setSQL(Prepared command, int start) { + String s = sqlCommand; + int beginIndex = tokens.get(start).start(); + int endIndex = token.start(); + while (beginIndex < endIndex && s.charAt(beginIndex) <= ' ') { + beginIndex++; + } + while (beginIndex < endIndex && s.charAt(endIndex - 1) <= ' ') { + endIndex--; + } + s = s.substring(beginIndex, endIndex); + ArrayList commandTokens; + if (start == 0 && currentTokenType == END_OF_INPUT) { + commandTokens = tokens; + if (beginIndex != 0) { + for (int i = 0, l = commandTokens.size() - 1; i < l; i++) { + commandTokens.get(i).subtractFromStart(beginIndex); + } + } + token.setStart(s.length()); + sqlCommand = s; + } else { + List subList = tokens.subList(start, tokenIndex); + commandTokens = new ArrayList<>(subList.size() + 1); + for (int i = start; i < tokenIndex; i++) { + Token t = tokens.get(i).clone(); + t.subtractFromStart(beginIndex); + commandTokens.add(t); + } + commandTokens.add(new Token.EndOfInputToken(s.length())); + } + command.setSQL(s, commandTokens); + } + + private Expression readExpressionOrDefault() { + if (readIf(DEFAULT)) { + return ValueExpression.DEFAULT; + } + return readExpression(); + } + + private Expression readExpressionWithGlobalConditions() { + Expression r = readCondition(); + if (readIf(AND)) { + r = readAnd(new ConditionAndOr(ConditionAndOr.AND, r, readCondition())); + } else if (readIf("_LOCAL_AND_GLOBAL_")) { + r = readAnd(new ConditionLocalAndGlobal(r, readCondition())); + } + return readExpressionPart2(r); + } + + private Expression readExpression() { + return readExpressionPart2(readAnd(readCondition())); + } + + private Expression readExpressionPart2(Expression r1) { + if (!readIf(OR)) { + return r1; + } + Expression r2 = readAnd(readCondition()); + if (!readIf(OR)) { + return new ConditionAndOr(ConditionAndOr.OR, r1, r2); + } + // Above logic to avoid allocating an ArrayList for the common case. + // We combine into ConditionAndOrN here rather than letting the optimisation + // pass do it, to avoid StackOverflowError during stuff like mapColumns. + final ArrayList expressions = new ArrayList<>(); + expressions.add(r1); + expressions.add(r2); + do { + expressions.add(readAnd(readCondition())); + } + while (readIf(OR)); + return new ConditionAndOrN(ConditionAndOr.OR, expressions); + } + + private Expression readAnd(Expression r) { + if (!readIf(AND)) { + return r; + } + Expression expr2 = readCondition(); + if (!readIf(AND)) { + return new ConditionAndOr(ConditionAndOr.AND, r, expr2); + } + // Above logic to avoid allocating an ArrayList for the common case. + // We combine into ConditionAndOrN here rather than letting the optimisation + // pass do it, to avoid StackOverflowError during stuff like mapColumns. + final ArrayList expressions = new ArrayList<>(); + expressions.add(r); + expressions.add(expr2); + do { + expressions.add(readCondition()); + } + while (readIf(AND)); + return new ConditionAndOrN(ConditionAndOr.AND, expressions); + } + + private Expression readCondition() { + switch (currentTokenType) { + case NOT: + read(); + return new ConditionNot(readCondition()); + case EXISTS: { + read(); + read(OPEN_PAREN); + Query query = parseQuery(); + // can not reduce expression because it might be a union except + // query with distinct + read(CLOSE_PAREN); + return new ExistsPredicate(query); + } + case UNIQUE: { + read(); + read(OPEN_PAREN); + Query query = parseQuery(); + read(CLOSE_PAREN); + 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 (expectedList != null) { + addMultipleExpected(NOT, EXISTS, UNIQUE); + addExpected("INTERSECTS"); + } + } + Expression l, c = readConcat(); + do { + l = c; + // special case: NOT NULL is not part of an expression (as in CREATE + // TABLE TEST(ID INT DEFAULT 0 NOT NULL)) + int backup = tokenIndex; + boolean not = readIf(NOT); + if (not && isToken(NULL)) { + // this really only works for NOT NULL! + setTokenIndex(backup); + break; + } + c = readConditionRightHandSide(l, not, false); + } while (c != null); + return l; + } + + private Expression readConditionRightHandSide(Expression r, boolean not, boolean whenOperand) { + if (!not && readIf(IS)) { + r = readConditionIs(r, whenOperand); + } else { + switch (currentTokenType) { + case BETWEEN: { + read(); + boolean symmetric = readIf(SYMMETRIC); + if (!symmetric) { + readIf(ASYMMETRIC); + } + Expression a = readConcat(); + read(AND); + r = new BetweenPredicate(r, not, whenOperand, symmetric, a, readConcat()); + break; + } + case IN: + read(); + r = readInPredicate(r, not, whenOperand); + break; + case LIKE: { + read(); + r = readLikePredicate(r, LikeType.LIKE, not, whenOperand); + break; + } + default: + if (readIf("ILIKE")) { + r = readLikePredicate(r, LikeType.ILIKE, not, whenOperand); + } else if (readIf("REGEXP")) { + Expression b = readConcat(); + recompileAlways = true; + r = new CompareLike(database, r, not, whenOperand, b, null, LikeType.REGEXP); + } else if (not) { + if (whenOperand) { + return null; + } + if (expectedList != null) { + addMultipleExpected(BETWEEN, IN, LIKE); + } + throw getSyntaxError(); + } else { + int compareType = getCompareType(currentTokenType); + if (compareType < 0) { + return null; + } + read(); + r = readComparison(r, compareType, whenOperand); + } + } + } + return r; + } + + private Expression readConditionIs(Expression left, boolean whenOperand) { + boolean isNot = readIf(NOT); + switch (currentTokenType) { + case NULL: + read(); + left = new NullPredicate(left, isNot, whenOperand); + break; + case DISTINCT: + read(); + read(FROM); + left = readComparison(left, isNot ? Comparison.EQUAL_NULL_SAFE : Comparison.NOT_EQUAL_NULL_SAFE, + whenOperand); + break; + case TRUE: + read(); + left = new BooleanTest(left, isNot, whenOperand, true); + break; + case FALSE: + read(); + left = new BooleanTest(left, isNot, whenOperand, false); + break; + case UNKNOWN: + read(); + left = new BooleanTest(left, isNot, whenOperand, null); + break; + default: + if (readIf("OF")) { + left = readTypePredicate(left, isNot, whenOperand); + } else if (readIf("JSON")) { + left = readJsonPredicate(left, isNot, whenOperand); + } else { + if (expectedList != null) { + addMultipleExpected(NULL, DISTINCT, TRUE, FALSE, UNKNOWN); + } + /* + * Databases that were created in 1.4.199 and older + * versions can contain invalid generated IS [ NOT ] + * expressions. + */ + if (whenOperand || !session.isQuirksMode()) { + throw getSyntaxError(); + } + left = new Comparison(isNot ? Comparison.NOT_EQUAL_NULL_SAFE : Comparison.EQUAL_NULL_SAFE, left, + readConcat(), false); + } + } + return left; + } + + private TypePredicate readTypePredicate(Expression left, boolean not, boolean whenOperand) { + read(OPEN_PAREN); + ArrayList typeList = Utils.newSmallArrayList(); + do { + typeList.add(parseDataType()); + } while (readIfMore()); + return new TypePredicate(left, not, whenOperand, typeList.toArray(new TypeInfo[0])); + } + + private Expression readInPredicate(Expression left, boolean not, boolean whenOperand) { + read(OPEN_PAREN); + if (!whenOperand && database.getMode().allowEmptyInPredicate && readIf(CLOSE_PAREN)) { + return ValueExpression.getBoolean(not); + } + ArrayList v; + if (isQuery()) { + Query query = parseQuery(); + if (!readIfMore()) { + return new ConditionInQuery(left, not, whenOperand, query, false, Comparison.EQUAL); + } + v = Utils.newSmallArrayList(); + v.add(new Subquery(query)); + } else { + v = Utils.newSmallArrayList(); + } + do { + v.add(readExpression()); + } while (readIfMore()); + return new ConditionIn(left, not, whenOperand, v); + } + + private IsJsonPredicate readJsonPredicate(Expression left, boolean not, boolean whenOperand) { + JSONItemType itemType; + if (readIf(VALUE)) { + itemType = JSONItemType.VALUE; + } else if (readIf(ARRAY)) { + itemType = JSONItemType.ARRAY; + } else if (readIf("OBJECT")) { + itemType = JSONItemType.OBJECT; + } else if (readIf("SCALAR")) { + itemType = JSONItemType.SCALAR; + } else { + itemType = JSONItemType.VALUE; + } + boolean unique = false; + if (readIf(WITH)) { + read(UNIQUE); + readIf("KEYS"); + unique = true; + } else if (readIf("WITHOUT")) { + read(UNIQUE); + readIf("KEYS"); + } + return new IsJsonPredicate(left, not, whenOperand, unique, itemType); + } + + private Expression readLikePredicate(Expression left, LikeType likeType, boolean not, boolean whenOperand) { + Expression right = readConcat(); + Expression esc = readIf("ESCAPE") ? readConcat() : null; + recompileAlways = true; + return new CompareLike(database, left, not, whenOperand, right, esc, likeType); + } + + private Expression readComparison(Expression left, int compareType, boolean whenOperand) { + int start = tokenIndex; + if (readIf(ALL)) { + read(OPEN_PAREN); + if (isQuery()) { + Query query = parseQuery(); + left = new ConditionInQuery(left, false, whenOperand, query, true, compareType); + read(CLOSE_PAREN); + } else { + 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 { + left = new Comparison(compareType, left, readConcat(), whenOperand); + } + return left; + } + + private Expression readConcat() { + Expression op1 = readSum(); + for (;;) { + switch (currentTokenType) { + case CONCATENATION: { + read(); + Expression op2 = readSum(); + if (readIf(CONCATENATION)) { + ConcatenationOperation c = new ConcatenationOperation(); + c.addParameter(op1); + c.addParameter(op2); + do { + c.addParameter(readSum()); + } while (readIf(CONCATENATION)); + c.doneWithParameters(); + op1 = c; + } else { + op1 = new ConcatenationOperation(op1, op2); + } + break; + } + case TILDE: // PostgreSQL compatibility + op1 = readTildeCondition(op1, false); + break; + case NOT_TILDE: // PostgreSQL compatibility + op1 = readTildeCondition(op1, true); + break; + default: + // Don't add compatibility operators + addExpected(CONCATENATION); + return op1; + } + } + } + + private Expression readSum() { + Expression r = readFactor(); + while (true) { + if (readIf(PLUS_SIGN)) { + r = new BinaryOperation(OpType.PLUS, r, readFactor()); + } else if (readIf(MINUS_SIGN)) { + r = new BinaryOperation(OpType.MINUS, r, readFactor()); + } else { + return r; + } + } + } + + private Expression readFactor() { + Expression r = readTerm(); + while (true) { + if (readIf(ASTERISK)) { + r = new BinaryOperation(OpType.MULTIPLY, r, readTerm()); + } else if (readIf(SLASH)) { + r = new BinaryOperation(OpType.DIVIDE, r, readTerm()); + } else if (readIf(PERCENT)) { + r = new MathFunction(r, readTerm(), MathFunction.MOD); + } else { + return r; + } + } + } + + private Expression readTildeCondition(Expression r, boolean not) { + read(); + if (readIf(ASTERISK)) { + r = new CastSpecification(r, TypeInfo.TYPE_VARCHAR_IGNORECASE); + } + return new CompareLike(database, r, not, false, readSum(), null, LikeType.REGEXP); + } + + private Expression readAggregate(AggregateType aggregateType, String aggregateName) { + if (currentSelect == null) { + expectedList = null; + throw getSyntaxError(); + } + Aggregate r; + switch (aggregateType) { + case COUNT: + if (readIf(ASTERISK)) { + r = new Aggregate(AggregateType.COUNT_ALL, new Expression[0], currentSelect, false); + } else { + boolean distinct = readDistinctAgg(); + Expression on = readExpression(); + if (on instanceof Wildcard && !distinct) { + // PostgreSQL compatibility: count(t.*) + r = new Aggregate(AggregateType.COUNT_ALL, new Expression[0], currentSelect, false); + } else { + r = new Aggregate(AggregateType.COUNT, new Expression[] { on }, currentSelect, distinct); + } + } + break; + case COVAR_POP: + case COVAR_SAMP: + case CORR: + case REGR_SLOPE: + case REGR_INTERCEPT: + case REGR_COUNT: + case REGR_R2: + case REGR_AVGX: + case REGR_AVGY: + case REGR_SXX: + case REGR_SYY: + case REGR_SXY: + r = new Aggregate(aggregateType, new Expression[] { readExpression(), readNextArgument() }, + currentSelect, false); + break; + case HISTOGRAM: + r = new Aggregate(aggregateType, new Expression[] { readExpression() }, currentSelect, false); + break; + case LISTAGG: { + boolean distinct = readDistinctAgg(); + Expression arg = readExpression(); + ListaggArguments extraArguments = new ListaggArguments(); + ArrayList orderByList; + if ("STRING_AGG".equals(aggregateName)) { + // PostgreSQL compatibility: string_agg(expression, delimiter) + read(COMMA); + extraArguments.setSeparator(readString()); + orderByList = readIfOrderBy(); + } else if ("GROUP_CONCAT".equals(aggregateName)) { + orderByList = readIfOrderBy(); + if (readIf("SEPARATOR")) { + extraArguments.setSeparator(readString()); + } + } else { + if (readIf(COMMA)) { + extraArguments.setSeparator(readString()); + } + if (readIf(ON)) { + read("OVERFLOW"); + if (readIf("TRUNCATE")) { + extraArguments.setOnOverflowTruncate(true); + if (currentTokenType == LITERAL) { + extraArguments.setFilter(readString()); + } + if (!readIf(WITH)) { + read("WITHOUT"); + extraArguments.setWithoutCount(true); + } + read("COUNT"); + } else { + read("ERROR"); + } + } + orderByList = null; + } + Expression[] args = new Expression[] { arg }; + int index = tokenIndex; + read(CLOSE_PAREN); + if (orderByList == null && isToken("WITHIN")) { + r = readWithinGroup(aggregateType, args, distinct, extraArguments, false, false); + } else { + setTokenIndex(index); + r = new Aggregate(AggregateType.LISTAGG, args, currentSelect, distinct); + r.setExtraArguments(extraArguments); + if (orderByList != null) { + r.setOrderByList(orderByList); + } + } + break; + } + case ARRAY_AGG: { + boolean distinct = readDistinctAgg(); + r = new Aggregate(AggregateType.ARRAY_AGG, new Expression[] { readExpression() }, currentSelect, distinct); + r.setOrderByList(readIfOrderBy()); + break; + } + case RANK: + case DENSE_RANK: + case PERCENT_RANK: + case CUME_DIST: { + if (isToken(CLOSE_PAREN)) { + return readWindowFunction(aggregateName); + } + ArrayList expressions = Utils.newSmallArrayList(); + do { + expressions.add(readExpression()); + } while (readIfMore()); + r = readWithinGroup(aggregateType, expressions.toArray(new Expression[0]), false, null, true, false); + break; + } + case PERCENTILE_CONT: + case PERCENTILE_DISC: { + Expression num = readExpression(); + read(CLOSE_PAREN); + r = readWithinGroup(aggregateType, new Expression[] { num }, false, null, false, true); + break; + } + case MODE: { + if (readIf(CLOSE_PAREN)) { + r = readWithinGroup(AggregateType.MODE, new Expression[0], false, null, false, true); + } else { + Expression expr = readExpression(); + r = new Aggregate(AggregateType.MODE, new Expression[0], currentSelect, false); + if (readIf(ORDER)) { + read("BY"); + Expression expr2 = readExpression(); + String sql = expr.getSQL(HasSQL.DEFAULT_SQL_FLAGS), sql2 = expr2.getSQL(HasSQL.DEFAULT_SQL_FLAGS); + if (!sql.equals(sql2)) { + throw DbException.getSyntaxError(ErrorCode.IDENTICAL_EXPRESSIONS_SHOULD_BE_USED, sqlCommand, + token.start(), sql, sql2); + } + readAggregateOrder(r, expr, true); + } else { + readAggregateOrder(r, expr, false); + } + } + break; + } + case JSON_OBJECTAGG: { + boolean withKey = readIf(KEY); + Expression key = readExpression(); + if (withKey) { + read(VALUE); + } else if (!readIf(VALUE)) { + read(COLON); + } + Expression value = readExpression(); + r = new Aggregate(AggregateType.JSON_OBJECTAGG, new Expression[] { key, value }, currentSelect, false); + readJsonObjectFunctionFlags(r, false); + break; + } + case JSON_ARRAYAGG: { + boolean distinct = readDistinctAgg(); + r = new Aggregate(AggregateType.JSON_ARRAYAGG, new Expression[] { readExpression() }, currentSelect, + distinct); + r.setOrderByList(readIfOrderBy()); + r.setFlags(JsonConstructorUtils.JSON_ABSENT_ON_NULL); + readJsonObjectFunctionFlags(r, true); + break; + } + default: + boolean distinct = readDistinctAgg(); + r = new Aggregate(aggregateType, new Expression[] { readExpression() }, currentSelect, distinct); + break; + } + read(CLOSE_PAREN); + readFilterAndOver(r); + return r; + } + + private Aggregate readWithinGroup(AggregateType aggregateType, Expression[] args, boolean distinct, + Object extraArguments, boolean forHypotheticalSet, boolean simple) { + read("WITHIN"); + read(GROUP); + read(OPEN_PAREN); + read(ORDER); + read("BY"); + Aggregate r = new Aggregate(aggregateType, args, currentSelect, distinct); + r.setExtraArguments(extraArguments); + if (forHypotheticalSet) { + int count = args.length; + ArrayList orderList = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + if (i > 0) { + read(COMMA); + } + orderList.add(parseSortSpecification()); + } + r.setOrderByList(orderList); + } else if (simple) { + readAggregateOrder(r, readExpression(), true); + } else { + r.setOrderByList(parseSortSpecificationList()); + } + return r; + } + + private void readAggregateOrder(Aggregate r, Expression expr, boolean parseSortType) { + ArrayList orderList = new ArrayList<>(1); + QueryOrderBy order = new QueryOrderBy(); + order.expression = expr; + if (parseSortType) { + order.sortType = parseSortType(); + } + orderList.add(order); + r.setOrderByList(orderList); + } + + private ArrayList readIfOrderBy() { + if (readIf(ORDER)) { + read("BY"); + return parseSortSpecificationList(); + } + return null; + } + + private ArrayList parseSortSpecificationList() { + ArrayList orderList = Utils.newSmallArrayList(); + do { + orderList.add(parseSortSpecification()); + } while (readIf(COMMA)); + return orderList; + } + + private QueryOrderBy parseSortSpecification() { + QueryOrderBy order = new QueryOrderBy(); + order.expression = readExpression(); + order.sortType = parseSortType(); + return order; + } + + private Expression readUserDefinedFunctionIf(Schema schema, String functionName) { + UserDefinedFunction userDefinedFunction = findUserDefinedFunctionWithinPath(schema, functionName); + if (userDefinedFunction == null) { + return null; + } else if (userDefinedFunction instanceof FunctionAlias) { + FunctionAlias functionAlias = (FunctionAlias) userDefinedFunction; + ArrayList argList = Utils.newSmallArrayList(); + if (!readIf(CLOSE_PAREN)) { + do { + argList.add(readExpression()); + } while (readIfMore()); + } + return new JavaFunction(functionAlias, argList.toArray(new Expression[0])); + } else { + UserAggregate aggregate = (UserAggregate) userDefinedFunction; + boolean distinct = readDistinctAgg(); + ArrayList params = Utils.newSmallArrayList(); + do { + params.add(readExpression()); + } while (readIfMore()); + Expression[] list = params.toArray(new Expression[0]); + JavaAggregate agg = new JavaAggregate(aggregate, list, currentSelect, distinct); + readFilterAndOver(agg); + return agg; + } + } + + private boolean readDistinctAgg() { + if (readIf(DISTINCT)) { + return true; + } + readIf(ALL); + return false; + } + + private void readFilterAndOver(AbstractAggregate aggregate) { + if (readIf("FILTER")) { + read(OPEN_PAREN); + read(WHERE); + Expression filterCondition = readExpression(); + read(CLOSE_PAREN); + aggregate.setFilterCondition(filterCondition); + } + readOver(aggregate); + } + + private void readOver(DataAnalysisOperation operation) { + if (readIf("OVER")) { + operation.setOverCondition(readWindowNameOrSpecification()); + currentSelect.setWindowQuery(); + } else if (operation.isAggregate()) { + currentSelect.setGroupQuery(); + } else { + throw getSyntaxError(); + } + } + + private Window readWindowNameOrSpecification() { + return isToken(OPEN_PAREN) ? readWindowSpecification() : new Window(readIdentifier(), null, null, null); + } + + private Window readWindowSpecification() { + read(OPEN_PAREN); + String parent = null; + if (currentTokenType == IDENTIFIER) { + String current = currentToken; + if (token.isQuoted() || ( // + !equalsToken(current, "PARTITION") // + && !equalsToken(current, "ROWS") // + && !equalsToken(current, "RANGE") // + && !equalsToken(current, "GROUPS"))) { + parent = current; + read(); + } + } + ArrayList partitionBy = null; + if (readIf("PARTITION")) { + read("BY"); + partitionBy = Utils.newSmallArrayList(); + do { + Expression expr = readExpression(); + partitionBy.add(expr); + } while (readIf(COMMA)); + } + ArrayList orderBy = readIfOrderBy(); + WindowFrame frame = readWindowFrame(); + read(CLOSE_PAREN); + return new Window(parent, partitionBy, orderBy, frame); + } + + private WindowFrame readWindowFrame() { + WindowFrameUnits units; + if (readIf("ROWS")) { + units = WindowFrameUnits.ROWS; + } else if (readIf("RANGE")) { + units = WindowFrameUnits.RANGE; + } else if (readIf("GROUPS")) { + units = WindowFrameUnits.GROUPS; + } else { + return null; + } + WindowFrameBound starting, following; + if (readIf(BETWEEN)) { + starting = readWindowFrameRange(); + read(AND); + following = readWindowFrameRange(); + } else { + starting = readWindowFrameStarting(); + following = null; + } + int sqlIndex = token.start(); + WindowFrameExclusion exclusion = WindowFrameExclusion.EXCLUDE_NO_OTHERS; + if (readIf("EXCLUDE")) { + if (readIf("CURRENT")) { + read(ROW); + exclusion = WindowFrameExclusion.EXCLUDE_CURRENT_ROW; + } else if (readIf(GROUP)) { + exclusion = WindowFrameExclusion.EXCLUDE_GROUP; + } else if (readIf("TIES")) { + exclusion = WindowFrameExclusion.EXCLUDE_TIES; + } else { + read("NO"); + read("OTHERS"); + } + } + WindowFrame frame = new WindowFrame(units, starting, following, exclusion); + if (!frame.isValid()) { + throw DbException.getSyntaxError(sqlCommand, sqlIndex); + } + return frame; + } + + private WindowFrameBound readWindowFrameStarting() { + if (readIf("UNBOUNDED")) { + read("PRECEDING"); + return new WindowFrameBound(WindowFrameBoundType.UNBOUNDED_PRECEDING, null); + } + if (readIf("CURRENT")) { + read(ROW); + return new WindowFrameBound(WindowFrameBoundType.CURRENT_ROW, null); + } + Expression value = readExpression(); + read("PRECEDING"); + return new WindowFrameBound(WindowFrameBoundType.PRECEDING, value); + } + + private WindowFrameBound readWindowFrameRange() { + if (readIf("UNBOUNDED")) { + if (readIf("PRECEDING")) { + return new WindowFrameBound(WindowFrameBoundType.UNBOUNDED_PRECEDING, null); + } + read("FOLLOWING"); + return new WindowFrameBound(WindowFrameBoundType.UNBOUNDED_FOLLOWING, null); + } + if (readIf("CURRENT")) { + read(ROW); + return new WindowFrameBound(WindowFrameBoundType.CURRENT_ROW, null); + } + Expression value = readExpression(); + if (readIf("PRECEDING")) { + return new WindowFrameBound(WindowFrameBoundType.PRECEDING, value); + } + read("FOLLOWING"); + return new WindowFrameBound(WindowFrameBoundType.FOLLOWING, value); + } + + private Expression readFunction(Schema schema, String name) { + String upperName = upperName(name); + if (schema != null) { + return readFunctionWithSchema(schema, name, upperName); + } + boolean allowOverride = database.isAllowBuiltinAliasOverride(); + if (allowOverride) { + Expression e = readUserDefinedFunctionIf(null, name); + if (e != null) { + return e; + } + } + AggregateType agg = Aggregate.getAggregateType(upperName); + if (agg != null) { + return readAggregate(agg, upperName); + } + Expression e = readBuiltinFunctionIf(upperName); + if (e != null) { + return e; + } + e = readWindowFunction(upperName); + if (e != null) { + return e; + } + e = readCompatibilityFunction(upperName); + if (e != null) { + return e; + } + if (!allowOverride) { + e = readUserDefinedFunctionIf(null, name); + if (e != null) { + return e; + } + } + throw DbException.get(ErrorCode.FUNCTION_NOT_FOUND_1, name); + } + + private Expression readFunctionWithSchema(Schema schema, String name, String upperName) { + if (database.getMode().getEnum() == ModeEnum.PostgreSQL + && schema.getName().equals(database.sysIdentifier("PG_CATALOG"))) { + FunctionsPostgreSQL function = FunctionsPostgreSQL.getFunction(upperName); + if (function != null) { + return readParameters(function); + } + } + Expression function = readUserDefinedFunctionIf(schema, name); + if (function != null) { + return function; + } + throw DbException.get(ErrorCode.FUNCTION_NOT_FOUND_1, name); + } + + private Expression readCompatibilityFunction(String name) { + switch (name) { + // || + case "ARRAY_APPEND": + case "ARRAY_CAT": + return new ConcatenationOperation(readExpression(), readLastArgument()); + // [] + case "ARRAY_GET": + return new ArrayElementReference(readExpression(), readLastArgument()); + // CARDINALITY + case "ARRAY_LENGTH": + return new CardinalityExpression(readSingleArgument(), false); + // Simple case + case "DECODE": { + Expression caseOperand = readExpression(); + boolean canOptimize = caseOperand.isConstant() && !caseOperand.getValue(session).containsNull(); + Expression a = readNextArgument(), b = readNextArgument(); + SimpleCase.SimpleWhen when = decodeToWhen(caseOperand, canOptimize, a, b), current = when; + Expression elseResult = null; + while (readIf(COMMA)) { + a = readExpression(); + if (readIf(COMMA)) { + b = readExpression(); + SimpleCase.SimpleWhen next = decodeToWhen(caseOperand, canOptimize, a, b); + current.setWhen(next); + current = next; + } else { + elseResult = a; + break; + } + } + read(CLOSE_PAREN); + return new SimpleCase(caseOperand, when, elseResult); + } + // Searched case + case "CASEWHEN": + return readCompatibilityCase(readExpression()); + case "NVL2": + return readCompatibilityCase(new NullPredicate(readExpression(), true, false)); + // Cast specification + case "CONVERT": { + Expression arg; + Column column; + if (database.getMode().swapConvertFunctionParameters) { + column = parseColumnWithType(null); + arg = readNextArgument(); + } else { + arg = readExpression(); + read(COMMA); + column = parseColumnWithType(null); + } + read(CLOSE_PAREN); + return new CastSpecification(arg, column); + } + // COALESCE + case "IFNULL": + return new CoalesceFunction(CoalesceFunction.COALESCE, readExpression(), readLastArgument()); + case "NVL": + return readCoalesceFunction(CoalesceFunction.COALESCE); + // CURRENT_CATALOG + case "DATABASE": + read(CLOSE_PAREN); + return new CurrentGeneralValueSpecification(CurrentGeneralValueSpecification.CURRENT_CATALOG); + // CURRENT_DATE + case "CURDATE": + 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": { + 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": + case "DAYOFMONTH": + return new DateTimeFunction(DateTimeFunction.EXTRACT, DateTimeFunction.DAY, readSingleArgument(), null); + case "DAY_OF_WEEK": + case "DAYOFWEEK": + return new DateTimeFunction(DateTimeFunction.EXTRACT, DateTimeFunction.DAY_OF_WEEK, readSingleArgument(), + null); + case "DAY_OF_YEAR": + case "DAYOFYEAR": + return new DateTimeFunction(DateTimeFunction.EXTRACT, DateTimeFunction.DAY_OF_YEAR, readSingleArgument(), + null); + case "HOUR": + return new DateTimeFunction(DateTimeFunction.EXTRACT, DateTimeFunction.HOUR, readSingleArgument(), null); + case "ISO_DAY_OF_WEEK": + return new DateTimeFunction(DateTimeFunction.EXTRACT, DateTimeFunction.ISO_DAY_OF_WEEK, + readSingleArgument(), null); + case "ISO_WEEK": + return new DateTimeFunction(DateTimeFunction.EXTRACT, DateTimeFunction.ISO_WEEK, readSingleArgument(), + null); + case "ISO_YEAR": + return new DateTimeFunction(DateTimeFunction.EXTRACT, DateTimeFunction.ISO_WEEK_YEAR, readSingleArgument(), + null); + case "MINUTE": + return new DateTimeFunction(DateTimeFunction.EXTRACT, DateTimeFunction.MINUTE, readSingleArgument(), null); + case "MONTH": + return new DateTimeFunction(DateTimeFunction.EXTRACT, DateTimeFunction.MONTH, readSingleArgument(), null); + case "QUARTER": + return new DateTimeFunction(DateTimeFunction.EXTRACT, DateTimeFunction.QUARTER, readSingleArgument(), // + null); + case "SECOND": + return new DateTimeFunction(DateTimeFunction.EXTRACT, DateTimeFunction.SECOND, readSingleArgument(), null); + case "WEEK": + return new DateTimeFunction(DateTimeFunction.EXTRACT, DateTimeFunction.WEEK, readSingleArgument(), null); + case "YEAR": + return new DateTimeFunction(DateTimeFunction.EXTRACT, DateTimeFunction.YEAR, readSingleArgument(), null); + // LOCALTIME + case "CURTIME": + return readCurrentDateTimeValueFunction(CurrentDateTimeValueFunction.LOCALTIME, true, "CURTIME"); + // 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(); + return new StringFunction(readNextArgument(), arg1, readIfArgument(), StringFunction.LOCATE); + } + case "POSITION": { + // can't read expression because IN would be read too early + Expression arg1 = readConcat(); + if (!readIf(COMMA)) { + read(IN); + } + return new StringFunction(arg1, readSingleArgument(), null, StringFunction.LOCATE); + } + // LOWER + case "LCASE": + return new StringFunction1(readSingleArgument(), StringFunction1.LOWER); + // SUBSTRING + case "SUBSTR": + return readSubstringFunction(); + // TRIM + case "LTRIM": + return new TrimFunction(readSingleArgument(), null, TrimFunction.LEADING); + case "RTRIM": + return new TrimFunction(readSingleArgument(), null, TrimFunction.TRAILING); + // UPPER + case "UCASE": + return new StringFunction1(readSingleArgument(), StringFunction1.UPPER); + // Sequence value + case "CURRVAL": + return readCompatibilitySequenceValueFunction(true); + case "NEXTVAL": + return readCompatibilitySequenceValueFunction(false); + default: + return null; + } + } + + private T readParameters(T expression) { + if (!readIf(CLOSE_PAREN)) { + do { + expression.addParameter(readExpression()); + } while (readIfMore()); + } + expression.doneWithParameters(); + return expression; + } + + private SimpleCase.SimpleWhen decodeToWhen(Expression caseOperand, boolean canOptimize, Expression whenOperand, + Expression result) { + if (!canOptimize && (!whenOperand.isConstant() || whenOperand.getValue(session).containsNull())) { + whenOperand = new Comparison(Comparison.EQUAL_NULL_SAFE, caseOperand, whenOperand, true); + } + return new SimpleCase.SimpleWhen(whenOperand, result); + } + + private Expression readCompatibilityCase(Expression when) { + return new SearchedCase(new Expression[] { when, readNextArgument(), readLastArgument() }); + } + + private Expression readCompatibilitySequenceValueFunction(boolean current) { + Expression arg1 = readExpression(), arg2 = readIf(COMMA) ? readExpression() : null; + read(CLOSE_PAREN); + return new CompatibilitySequenceValueFunction(arg1, arg2, current); + } + + private Expression readBuiltinFunctionIf(String upperName) { + switch (upperName) { + case "ABS": + return new MathFunction(readSingleArgument(), null, MathFunction.ABS); + case "MOD": + return new MathFunction(readExpression(), readLastArgument(), MathFunction.MOD); + case "SIN": + return new MathFunction1(readSingleArgument(), MathFunction1.SIN); + case "COS": + return new MathFunction1(readSingleArgument(), MathFunction1.COS); + case "TAN": + return new MathFunction1(readSingleArgument(), MathFunction1.TAN); + case "COT": + return new MathFunction1(readSingleArgument(), MathFunction1.COT); + case "SINH": + return new MathFunction1(readSingleArgument(), MathFunction1.SINH); + case "COSH": + return new MathFunction1(readSingleArgument(), MathFunction1.COSH); + case "TANH": + return new MathFunction1(readSingleArgument(), MathFunction1.TANH); + case "ASIN": + return new MathFunction1(readSingleArgument(), MathFunction1.ASIN); + case "ACOS": + return new MathFunction1(readSingleArgument(), MathFunction1.ACOS); + case "ATAN": + return new MathFunction1(readSingleArgument(), MathFunction1.ATAN); + case "ATAN2": + return new MathFunction2(readExpression(), readLastArgument(), MathFunction2.ATAN2); + case "LOG": { + Expression arg1 = readExpression(); + if (readIf(COMMA)) { + return new MathFunction2(arg1, readSingleArgument(), MathFunction2.LOG); + } else { + read(CLOSE_PAREN); + return new MathFunction1(arg1, + database.getMode().logIsLogBase10 ? MathFunction1.LOG10 : MathFunction1.LN); + } + } + case "LOG10": + return new MathFunction1(readSingleArgument(), MathFunction1.LOG10); + case "LN": + return new MathFunction1(readSingleArgument(), MathFunction1.LN); + case "EXP": + return new MathFunction1(readSingleArgument(), MathFunction1.EXP); + case "POWER": + return new MathFunction2(readExpression(), readLastArgument(), MathFunction2.POWER); + case "SQRT": + return new MathFunction1(readSingleArgument(), MathFunction1.SQRT); + case "FLOOR": + return new MathFunction(readSingleArgument(), null, MathFunction.FLOOR); + case "CEIL": + case "CEILING": + return new MathFunction(readSingleArgument(), null, MathFunction.CEIL); + case "ROUND": + return new MathFunction(readExpression(), readIfArgument(), MathFunction.ROUND); + case "ROUNDMAGIC": + return new MathFunction(readSingleArgument(), null, MathFunction.ROUNDMAGIC); + case "SIGN": + return new MathFunction(readSingleArgument(), null, MathFunction.SIGN); + case "TRUNC": + case "TRUNCATE": + return new MathFunction(readExpression(), readIfArgument(), MathFunction.TRUNC); + case "DEGREES": + return new MathFunction1(readSingleArgument(), MathFunction1.DEGREES); + case "RADIANS": + return new MathFunction1(readSingleArgument(), MathFunction1.RADIANS); + case "BITAND": + return new BitFunction(readExpression(), readLastArgument(), BitFunction.BITAND); + case "BITOR": + return new BitFunction(readExpression(), readLastArgument(), BitFunction.BITOR); + case "BITXOR": + return new BitFunction(readExpression(), readLastArgument(), BitFunction.BITXOR); + case "BITNOT": + return new BitFunction(readSingleArgument(), null, BitFunction.BITNOT); + case "BITNAND": + return new BitFunction(readExpression(), readLastArgument(), BitFunction.BITNAND); + case "BITNOR": + return new BitFunction(readExpression(), readLastArgument(), BitFunction.BITNOR); + case "BITXNOR": + return new BitFunction(readExpression(), readLastArgument(), BitFunction.BITXNOR); + case "BITGET": + return new BitFunction(readExpression(), readLastArgument(), BitFunction.BITGET); + case "BITCOUNT": + return new BitFunction(readSingleArgument(), null, BitFunction.BITCOUNT); + case "LSHIFT": + return new BitFunction(readExpression(), readLastArgument(), BitFunction.LSHIFT); + case "RSHIFT": + return new BitFunction(readExpression(), readLastArgument(), BitFunction.RSHIFT); + case "ULSHIFT": + return new BitFunction(readExpression(), readLastArgument(), BitFunction.ULSHIFT); + case "URSHIFT": + return new BitFunction(readExpression(), readLastArgument(), BitFunction.URSHIFT); + case "ROTATELEFT": + return new BitFunction(readExpression(), readLastArgument(), BitFunction.ROTATELEFT); + case "ROTATERIGHT": + return new BitFunction(readExpression(), readLastArgument(), BitFunction.ROTATERIGHT); + case "EXTRACT": { + int field = readDateTimeField(); + read(FROM); + return new DateTimeFunction(DateTimeFunction.EXTRACT, field, readSingleArgument(), null); + } + case "DATE_TRUNC": + return new DateTimeFunction(DateTimeFunction.DATE_TRUNC, readDateTimeField(), readLastArgument(), null); + case "DATEADD": + case "TIMESTAMPADD": + return new DateTimeFunction(DateTimeFunction.DATEADD, readDateTimeField(), readNextArgument(), + readLastArgument()); + case "DATEDIFF": + case "TIMESTAMPDIFF": + return new DateTimeFunction(DateTimeFunction.DATEDIFF, readDateTimeField(), readNextArgument(), + readLastArgument()); + case "FORMATDATETIME": + return readDateTimeFormatFunction(DateTimeFormatFunction.FORMATDATETIME); + case "PARSEDATETIME": + return readDateTimeFormatFunction(DateTimeFormatFunction.PARSEDATETIME); + case "DAYNAME": + return new DayMonthNameFunction(readSingleArgument(), DayMonthNameFunction.DAYNAME); + case "MONTHNAME": + return new DayMonthNameFunction(readSingleArgument(), DayMonthNameFunction.MONTHNAME); + case "CARDINALITY": + return new CardinalityExpression(readSingleArgument(), false); + case "ARRAY_MAX_CARDINALITY": + return new CardinalityExpression(readSingleArgument(), true); + case "LOCATE": + return new StringFunction(readExpression(), readNextArgument(), readIfArgument(), StringFunction.LOCATE); + case "INSERT": + return new StringFunction(readExpression(), readNextArgument(), readNextArgument(), readLastArgument(), + StringFunction.INSERT); + case "REPLACE": + return new StringFunction(readExpression(), readNextArgument(), readIfArgument(), StringFunction.REPLACE); + case "LPAD": + return new StringFunction(readExpression(), readNextArgument(), readIfArgument(), StringFunction.LPAD); + case "RPAD": + return new StringFunction(readExpression(), readNextArgument(), readIfArgument(), StringFunction.RPAD); + case "TRANSLATE": + return new StringFunction(readExpression(), readNextArgument(), readLastArgument(), + StringFunction.TRANSLATE); + case "UPPER": + return new StringFunction1(readSingleArgument(), StringFunction1.UPPER); + case "LOWER": + return new StringFunction1(readSingleArgument(), StringFunction1.LOWER); + case "ASCII": + return new StringFunction1(readSingleArgument(), StringFunction1.ASCII); + case "CHAR": + case "CHR": + return new StringFunction1(readSingleArgument(), StringFunction1.CHAR); + case "STRINGENCODE": + return new StringFunction1(readSingleArgument(), StringFunction1.STRINGENCODE); + case "STRINGDECODE": + return new StringFunction1(readSingleArgument(), StringFunction1.STRINGDECODE); + case "STRINGTOUTF8": + return new StringFunction1(readSingleArgument(), StringFunction1.STRINGTOUTF8); + case "UTF8TOSTRING": + return new StringFunction1(readSingleArgument(), StringFunction1.UTF8TOSTRING); + case "HEXTORAW": + return new StringFunction1(readSingleArgument(), StringFunction1.HEXTORAW); + case "RAWTOHEX": + return new StringFunction1(readSingleArgument(), StringFunction1.RAWTOHEX); + case "SPACE": + return new StringFunction1(readSingleArgument(), StringFunction1.SPACE); + case "QUOTE_IDENT": + return new StringFunction1(readSingleArgument(), StringFunction1.QUOTE_IDENT); + case "SUBSTRING": + return readSubstringFunction(); + case "TO_CHAR": { + Expression arg1 = readExpression(), arg2, arg3; + if (readIf(COMMA)) { + arg2 = readExpression(); + arg3 = readIf(COMMA) ? readExpression() : null; + } else { + arg3 = arg2 = null; + } + read(CLOSE_PAREN); + return new ToCharFunction(arg1, arg2, arg3); + } + case "REPEAT": + return new StringFunction2(readExpression(), readLastArgument(), StringFunction2.REPEAT); + case "CHAR_LENGTH": + case "CHARACTER_LENGTH": + case "LENGTH": + return new LengthFunction(readIfSingleArgument(), LengthFunction.CHAR_LENGTH); + case "OCTET_LENGTH": + return new LengthFunction(readIfSingleArgument(), LengthFunction.OCTET_LENGTH); + case "BIT_LENGTH": + return new LengthFunction(readIfSingleArgument(), LengthFunction.BIT_LENGTH); + case "TRIM": + return readTrimFunction(); + case "REGEXP_LIKE": + return readParameters(new RegexpFunction(RegexpFunction.REGEXP_LIKE)); + case "REGEXP_REPLACE": + return readParameters(new RegexpFunction(RegexpFunction.REGEXP_REPLACE)); + case "REGEXP_SUBSTR": + return readParameters(new RegexpFunction(RegexpFunction.REGEXP_SUBSTR)); + case "XMLATTR": + return readParameters(new XMLFunction(XMLFunction.XMLATTR)); + case "XMLCDATA": + return readParameters(new XMLFunction(XMLFunction.XMLCDATA)); + case "XMLCOMMENT": + return readParameters(new XMLFunction(XMLFunction.XMLCOMMENT)); + case "XMLNODE": + return readParameters(new XMLFunction(XMLFunction.XMLNODE)); + case "XMLSTARTDOC": + return readParameters(new XMLFunction(XMLFunction.XMLSTARTDOC)); + case "XMLTEXT": + return readParameters(new XMLFunction(XMLFunction.XMLTEXT)); + case "TRIM_ARRAY": + return new ArrayFunction(readExpression(), readLastArgument(), null, ArrayFunction.TRIM_ARRAY); + case "ARRAY_CONTAINS": + return new ArrayFunction(readExpression(), readLastArgument(), null, ArrayFunction.ARRAY_CONTAINS); + case "ARRAY_SLICE": + return new ArrayFunction(readExpression(), readNextArgument(), readLastArgument(), + ArrayFunction.ARRAY_SLICE); + case "COMPRESS": + return new CompressFunction(readExpression(), readIfArgument(), CompressFunction.COMPRESS); + case "EXPAND": + return new CompressFunction(readSingleArgument(), null, CompressFunction.EXPAND); + case "SOUNDEX": + return new SoundexFunction(readSingleArgument(), null, SoundexFunction.SOUNDEX); + case "DIFFERENCE": + return new SoundexFunction(readExpression(), readLastArgument(), SoundexFunction.DIFFERENCE); + case "JSON_OBJECT": { + JsonConstructorFunction function = new JsonConstructorFunction(false); + if (currentTokenType != CLOSE_PAREN && !readJsonObjectFunctionFlags(function, false)) { + do { + boolean withKey = readIf(KEY); + function.addParameter(readExpression()); + if (withKey) { + read(VALUE); + } else if (!readIf(VALUE)) { + read(COLON); + } + function.addParameter(readExpression()); + } while (readIf(COMMA)); + readJsonObjectFunctionFlags(function, false); + } + read(CLOSE_PAREN); + function.doneWithParameters(); + return function; + } + case "JSON_ARRAY": { + JsonConstructorFunction function = new JsonConstructorFunction(true); + function.setFlags(JsonConstructorUtils.JSON_ABSENT_ON_NULL); + if (currentTokenType != CLOSE_PAREN && !readJsonObjectFunctionFlags(function, true)) { + do { + function.addParameter(readExpression()); + } while (readIf(COMMA)); + readJsonObjectFunctionFlags(function, true); + } + read(CLOSE_PAREN); + function.doneWithParameters(); + return function; + } + case "ENCRYPT": + return new CryptFunction(readExpression(), readNextArgument(), readLastArgument(), CryptFunction.ENCRYPT); + case "DECRYPT": + return new CryptFunction(readExpression(), readNextArgument(), readLastArgument(), CryptFunction.DECRYPT); + case "COALESCE": + return readCoalesceFunction(CoalesceFunction.COALESCE); + case "GREATEST": + return readCoalesceFunction(CoalesceFunction.GREATEST); + case "LEAST": + return readCoalesceFunction(CoalesceFunction.LEAST); + case "NULLIF": + return new NullIfFunction(readExpression(), readLastArgument()); + case "CONCAT": + return readConcatFunction(ConcatFunction.CONCAT); + case "CONCAT_WS": + return readConcatFunction(ConcatFunction.CONCAT_WS); + case "HASH": + return new HashFunction(readExpression(), readNextArgument(), readIfArgument(), HashFunction.HASH); + case "ORA_HASH": { + Expression arg1 = readExpression(); + if (readIfMore()) { + return new HashFunction(arg1, readExpression(), readIfArgument(), HashFunction.ORA_HASH); + } + return new HashFunction(arg1, HashFunction.ORA_HASH); + } + case "RAND": + case "RANDOM": + return new RandFunction(readIfSingleArgument(), RandFunction.RAND); + case "SECURE_RAND": + return new RandFunction(readSingleArgument(), RandFunction.SECURE_RAND); + case "RANDOM_UUID": + case "UUID": + read(CLOSE_PAREN); + return new RandFunction(null, RandFunction.RANDOM_UUID); + case "ABORT_SESSION": + return new SessionControlFunction(readIfSingleArgument(), SessionControlFunction.ABORT_SESSION); + case "CANCEL_SESSION": + return new SessionControlFunction(readIfSingleArgument(), SessionControlFunction.CANCEL_SESSION); + case "AUTOCOMMIT": + read(CLOSE_PAREN); + return new SysInfoFunction(SysInfoFunction.AUTOCOMMIT); + case "DATABASE_PATH": + read(CLOSE_PAREN); + return new SysInfoFunction(SysInfoFunction.DATABASE_PATH); + case "H2VERSION": + read(CLOSE_PAREN); + return new SysInfoFunction(SysInfoFunction.H2VERSION); + case "LOCK_MODE": + read(CLOSE_PAREN); + return new SysInfoFunction(SysInfoFunction.LOCK_MODE); + case "LOCK_TIMEOUT": + read(CLOSE_PAREN); + return new SysInfoFunction(SysInfoFunction.LOCK_TIMEOUT); + case "MEMORY_FREE": + read(CLOSE_PAREN); + return new SysInfoFunction(SysInfoFunction.MEMORY_FREE); + case "MEMORY_USED": + read(CLOSE_PAREN); + return new SysInfoFunction(SysInfoFunction.MEMORY_USED); + case "READONLY": + read(CLOSE_PAREN); + return new SysInfoFunction(SysInfoFunction.READONLY); + case "SESSION_ID": + read(CLOSE_PAREN); + return new SysInfoFunction(SysInfoFunction.SESSION_ID); + case "TRANSACTION_ID": + read(CLOSE_PAREN); + return new SysInfoFunction(SysInfoFunction.TRANSACTION_ID); + case "DISK_SPACE_USED": + return new TableInfoFunction(readIfSingleArgument(), null, TableInfoFunction.DISK_SPACE_USED); + case "ESTIMATED_ENVELOPE": + return new TableInfoFunction(readExpression(), readLastArgument(), TableInfoFunction.ESTIMATED_ENVELOPE); + case "FILE_READ": + return new FileFunction(readExpression(), readIfArgument(), FileFunction.FILE_READ); + case "FILE_WRITE": + return new FileFunction(readExpression(), readLastArgument(), FileFunction.FILE_WRITE); + case "DATA_TYPE_SQL": + return new DataTypeSQLFunction(readExpression(), readNextArgument(), readNextArgument(), + readLastArgument()); + case "DB_OBJECT_ID": + return new DBObjectFunction(readExpression(), readNextArgument(), readIfArgument(), + DBObjectFunction.DB_OBJECT_ID); + case "DB_OBJECT_SQL": + return new DBObjectFunction(readExpression(), readNextArgument(), readIfArgument(), + DBObjectFunction.DB_OBJECT_SQL); + case "CSVWRITE": + return readParameters(new CSVWriteFunction()); + case "SIGNAL": + return new SignalFunction(readExpression(), readLastArgument()); + case "TRUNCATE_VALUE": + return new TruncateValueFunction(readExpression(), readNextArgument(), readLastArgument()); + case "ZERO": + read(CLOSE_PAREN); + return ValueExpression.get(ValueInteger.get(0)); + case "PI": + read(CLOSE_PAREN); + return ValueExpression.get(ValueDouble.get(Math.PI)); + } + ModeFunction function = ModeFunction.getFunction(database, upperName); + return function != null ? readParameters(function) : null; + } + + private Expression readDateTimeFormatFunction(int function) { + DateTimeFormatFunction f = new DateTimeFormatFunction(function); + f.addParameter(readExpression()); + read(COMMA); + f.addParameter(readExpression()); + if (readIf(COMMA)) { + f.addParameter(readExpression()); + if (readIf(COMMA)) { + f.addParameter(readExpression()); + } + } + read(CLOSE_PAREN); + f.doneWithParameters(); + return f; + } + + private Expression readTrimFunction() { + int flags; + boolean needFrom = false; + if (readIf("LEADING")) { + flags = TrimFunction.LEADING; + needFrom = true; + } else if (readIf("TRAILING")) { + flags = TrimFunction.TRAILING; + needFrom = true; + } else { + needFrom = readIf("BOTH"); + flags = TrimFunction.LEADING | TrimFunction.TRAILING; + } + Expression from, space = null; + if (needFrom) { + if (!readIf(FROM)) { + space = readExpression(); + read(FROM); + } + from = readExpression(); + } else { + if (readIf(FROM)) { + from = readExpression(); + } else { + from = readExpression(); + if (readIf(FROM)) { + space = from; + from = readExpression(); + } else if (readIf(COMMA)) { + space = readExpression(); + } + } + } + read(CLOSE_PAREN); + return new TrimFunction(from, space, flags); + } + + private ArrayTableFunction readUnnestFunction() { + ArrayTableFunction f = new ArrayTableFunction(ArrayTableFunction.UNNEST); + ArrayList columns = Utils.newSmallArrayList(); + if (!readIf(CLOSE_PAREN)) { + int i = 0; + do { + Expression expr = readExpression(); + TypeInfo columnType = TypeInfo.TYPE_NULL; + if (expr.isConstant()) { + expr = expr.optimize(session); + TypeInfo exprType = expr.getType(); + if (exprType.getValueType() == Value.ARRAY) { + columnType = (TypeInfo) exprType.getExtTypeInfo(); + } + } + f.addParameter(expr); + columns.add(new Column("C" + ++i, columnType)); + } while (readIfMore()); + } + if (readIf(WITH)) { + read("ORDINALITY"); + columns.add(new Column("NORD", TypeInfo.TYPE_INTEGER)); + } + f.setColumns(columns); + f.doneWithParameters(); + return f; + } + + private ArrayTableFunction readTableFunction(int functionType) { + ArrayTableFunction f = new ArrayTableFunction(functionType); + ArrayList columns = Utils.newSmallArrayList(); + do { + columns.add(parseColumnWithType(readIdentifier())); + read(EQUAL); + f.addParameter(readExpression()); + } while (readIfMore()); + f.setColumns(columns); + f.doneWithParameters(); + return f; + } + + private Expression readSingleArgument() { + Expression arg = readExpression(); + read(CLOSE_PAREN); + return arg; + } + + private Expression readNextArgument() { + read(COMMA); + return readExpression(); + } + + private Expression readLastArgument() { + read(COMMA); + Expression arg = readExpression(); + read(CLOSE_PAREN); + return arg; + } + + private Expression readIfSingleArgument() { + Expression arg; + if (readIf(CLOSE_PAREN)) { + arg = null; + } else { + arg = readExpression(); + read(CLOSE_PAREN); + } + return arg; + } + + private Expression readIfArgument() { + Expression arg = readIf(COMMA) ? readExpression() : null; + read(CLOSE_PAREN); + return arg; + } + + private Expression readCoalesceFunction(int function) { + CoalesceFunction f = new CoalesceFunction(function); + f.addParameter(readExpression()); + while (readIfMore()) { + f.addParameter(readExpression()); + } + f.doneWithParameters(); + return f; + } + + private Expression readConcatFunction(int function) { + ConcatFunction f = new ConcatFunction(function); + f.addParameter(readExpression()); + f.addParameter(readNextArgument()); + if (function == ConcatFunction.CONCAT_WS) { + f.addParameter(readNextArgument()); + } + while (readIfMore()) { + f.addParameter(readExpression()); + } + f.doneWithParameters(); + return f; + } + + private Expression readSubstringFunction() { + // Standard variants are: + // SUBSTRING(X FROM 1) + // SUBSTRING(X FROM 1 FOR 1) + // Different non-standard variants include: + // SUBSTRING(X,1) + // SUBSTRING(X,1,1) + // SUBSTRING(X FOR 1) -- Postgres + SubstringFunction function = new SubstringFunction(); + function.addParameter(readExpression()); + if (readIf(FROM)) { + function.addParameter(readExpression()); + if (readIf(FOR)) { + function.addParameter(readExpression()); + } + } else if (readIf(FOR)) { + function.addParameter(ValueExpression.get(ValueInteger.get(1))); + function.addParameter(readExpression()); + } else { + read(COMMA); + function.addParameter(readExpression()); + if (readIf(COMMA)) { + function.addParameter(readExpression()); + } + } + read(CLOSE_PAREN); + function.doneWithParameters(); + return function; + } + + private int readDateTimeField() { + int field = -1; + switch (currentTokenType) { + case IDENTIFIER: + if (!token.isQuoted()) { + field = DateTimeFunction.getField(currentToken); + } + break; + case LITERAL: + if (token.value(session).getValueType() == Value.VARCHAR) { + field = DateTimeFunction.getField(token.value(session).getString()); + } + break; + case YEAR: + field = DateTimeFunction.YEAR; + break; + case MONTH: + field = DateTimeFunction.MONTH; + break; + case DAY: + field = DateTimeFunction.DAY; + break; + case HOUR: + field = DateTimeFunction.HOUR; + break; + case MINUTE: + field = DateTimeFunction.MINUTE; + break; + case SECOND: + field = DateTimeFunction.SECOND; + } + if (field < 0) { + addExpected("date-time field"); + throw getSyntaxError(); + } + read(); + return field; + } + + private WindowFunction readWindowFunction(String name) { + WindowFunctionType type = WindowFunctionType.get(name); + if (type == null) { + return null; + } + if (currentSelect == null) { + throw getSyntaxError(); + } + int numArgs = WindowFunction.getMinArgumentCount(type); + Expression[] args = null; + if (numArgs > 0) { + // There is no functions with numArgs == 0 && numArgsMax > 0 + int numArgsMax = WindowFunction.getMaxArgumentCount(type); + args = new Expression[numArgsMax]; + if (numArgs == numArgsMax) { + for (int i = 0; i < numArgs; i++) { + if (i > 0) { + read(COMMA); + } + args[i] = readExpression(); + } + } else { + int i = 0; + while (i < numArgsMax) { + if (i > 0 && !readIf(COMMA)) { + break; + } + args[i] = readExpression(); + i++; + } + if (i < numArgs) { + throw getSyntaxError(); + } + if (i != numArgsMax) { + args = Arrays.copyOf(args, i); + } + } + } + read(CLOSE_PAREN); + WindowFunction function = new WindowFunction(type, currentSelect, args); + switch (type) { + case NTH_VALUE: + readFromFirstOrLast(function); + //$FALL-THROUGH$ + case LEAD: + case LAG: + case FIRST_VALUE: + case LAST_VALUE: + readRespectOrIgnoreNulls(function); + //$FALL-THROUGH$ + default: + // Avoid warning + } + readOver(function); + return function; + } + + private void readFromFirstOrLast(WindowFunction function) { + if (readIf(FROM) && !readIf("FIRST")) { + read("LAST"); + function.setFromLast(true); + } + } + + private void readRespectOrIgnoreNulls(WindowFunction function) { + if (readIf("RESPECT")) { + read("NULLS"); + } else if (readIf("IGNORE")) { + read("NULLS"); + function.setIgnoreNulls(true); + } + } + + 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 (!forArray) { + if (readIf(WITH)) { + read(UNIQUE); + read("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; + } + } + } + if (result) { + function.setFlags(flags); + } + return result; + } + + private Expression readKeywordCompatibilityFunctionOrColumn() { + boolean nonKeyword = nonKeywords != null && nonKeywords.get(currentTokenType); + String name = currentToken; + read(); + if (readIf(OPEN_PAREN)) { + return readCompatibilityFunction(upperName(name)); + } else if (nonKeyword) { + return readIf(DOT) ? readTermObjectDot(name) : new ExpressionColumn(database, null, null, name); + } + throw getSyntaxError(); + } + + private Expression readCurrentDateTimeValueFunction(int function, boolean hasParen, String name) { + int scale = -1; + if (hasParen) { + if (function != CurrentDateTimeValueFunction.CURRENT_DATE && currentTokenType != 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); + } + if (database.isAllowBuiltinAliasOverride()) { + FunctionAlias functionAlias = database.getSchema(session.getCurrentSchemaName()) + .findFunction(name != null ? name : CurrentDateTimeValueFunction.getName(function)); + if (functionAlias != null) { + return new JavaFunction(functionAlias, + scale >= 0 ? new Expression[] { ValueExpression.get(ValueInteger.get(scale)) } + : new Expression[0]); + } + } + return new CurrentDateTimeValueFunction(function, scale); + } + + private Expression readIfWildcardRowidOrSequencePseudoColumn(String schema, String objectName) { + if (readIf(ASTERISK)) { + return parseWildcard(schema, objectName); + } + if (readIf(_ROWID_)) { + return new ExpressionColumn(database, schema, objectName); + } + if (database.getMode().nextvalAndCurrvalPseudoColumns) { + return readIfSequencePseudoColumn(schema, objectName); + } + return null; + } + + private Wildcard parseWildcard(String schema, String objectName) { + Wildcard wildcard = new Wildcard(schema, objectName); + if (readIf(EXCEPT)) { + read(OPEN_PAREN); + ArrayList exceptColumns = Utils.newSmallArrayList(); + do { + String s = null, t = null; + String name = readIdentifier(); + if (readIf(DOT)) { + t = name; + name = readIdentifier(); + if (readIf(DOT)) { + s = t; + t = name; + name = readIdentifier(); + if (readIf(DOT)) { + checkDatabaseName(s); + s = t; + t = name; + name = readIdentifier(); + } + } + } + exceptColumns.add(new ExpressionColumn(database, s, t, name)); + } while (readIfMore()); + wildcard.setExceptColumns(exceptColumns); + } + return wildcard; + } + + private SequenceValue readIfSequencePseudoColumn(String schema, String objectName) { + if (schema == null) { + schema = session.getCurrentSchemaName(); + } + if (isToken("NEXTVAL")) { + Sequence sequence = findSequence(schema, objectName); + if (sequence != null) { + read(); + return new SequenceValue(sequence, getCurrentPreparedOrSelect()); + } + } else if (isToken("CURRVAL")) { + Sequence sequence = findSequence(schema, objectName); + if (sequence != null) { + read(); + return new SequenceValue(sequence); + } + } + return null; + } + + private Expression readTermObjectDot(String objectName) { + Expression expr = readIfWildcardRowidOrSequencePseudoColumn(null, objectName); + if (expr != null) { + return expr; + } + String name = readIdentifier(); + if (readIf(OPEN_PAREN)) { + return readFunction(database.getSchema(objectName), name); + } else if (readIf(DOT)) { + String schema = objectName; + objectName = name; + expr = readIfWildcardRowidOrSequencePseudoColumn(schema, objectName); + if (expr != null) { + return expr; + } + name = readIdentifier(); + if (readIf(OPEN_PAREN)) { + checkDatabaseName(schema); + return readFunction(database.getSchema(objectName), name); + } else if (readIf(DOT)) { + checkDatabaseName(schema); + schema = objectName; + objectName = name; + expr = readIfWildcardRowidOrSequencePseudoColumn(schema, objectName); + if (expr != null) { + return expr; + } + name = readIdentifier(); + } + return new ExpressionColumn(database, schema, objectName, name); + } + return new ExpressionColumn(database, null, objectName, name); + } + + private void checkDatabaseName(String databaseName) { + if (!database.getIgnoreCatalogs() && !equalsToken(database.getShortName(), databaseName)) { + throw DbException.get(ErrorCode.DATABASE_NOT_FOUND_1, databaseName); + } + } + + private Parameter readParameter() { + int index = ((Token.ParameterToken) token).index(); + read(); + Parameter p; + if (parameters == null) { + parameters = Utils.newSmallArrayList(); + } + if (index > Constants.MAX_PARAMETER_INDEX) { + throw DbException.getInvalidValueException("parameter index", index); + } + index--; + if (parameters.size() <= index) { + parameters.ensureCapacity(index + 1); + while (parameters.size() < index) { + parameters.add(null); + } + p = new Parameter(index); + parameters.add(p); + } else if ((p = parameters.get(index)) == null) { + p = new Parameter(index); + parameters.set(index, p); + } + return p; + } + + private Expression readTerm() { + Expression r; + switch (currentTokenType) { + case AT: + read(); + r = new Variable(session, readIdentifier()); + if (readIf(COLON_EQ)) { + r = new SetFunction(r, readExpression()); + } + break; + case PARAMETER: + r = readParameter(); + break; + case TABLE: + case SELECT: + case WITH: + r = new Subquery(parseQuery()); + break; + case MINUS_SIGN: + read(); + if (currentTokenType == LITERAL) { + r = ValueExpression.get(token.value(session).negate()); + int rType = r.getType().getValueType(); + if (rType == Value.BIGINT && + r.getValue(session).getLong() == Integer.MIN_VALUE) { + // convert Integer.MIN_VALUE to type 'int' + // (Integer.MAX_VALUE+1 is of type 'long') + r = ValueExpression.get(ValueInteger.get(Integer.MIN_VALUE)); + } else if (rType == Value.NUMERIC && + r.getValue(session).getBigDecimal().compareTo(Value.MIN_LONG_DECIMAL) == 0) { + // convert Long.MIN_VALUE to type 'long' + // (Long.MAX_VALUE+1 is of type 'decimal') + r = ValueExpression.get(ValueBigint.MIN); + } + read(); + } else { + r = new UnaryOperation(readTerm()); + } + break; + case PLUS_SIGN: + read(); + r = readTerm(); + break; + case OPEN_PAREN: + read(); + if (readIf(CLOSE_PAREN)) { + r = ValueExpression.get(ValueRow.EMPTY); + } else if (isQuery()) { + r = new Subquery(parseQuery()); + read(CLOSE_PAREN); + } else { + r = readExpression(); + if (readIfMore()) { + ArrayList list = Utils.newSmallArrayList(); + list.add(r); + do { + list.add(readExpression()); + } while (readIfMore()); + r = new ExpressionList(list.toArray(new Expression[0]), false); + } else if (r instanceof BinaryOperation) { + BinaryOperation binaryOperation = (BinaryOperation) r; + if (binaryOperation.getOperationType() == OpType.MINUS) { + TypeInfo ti = readIntervalQualifier(); + if (ti != null) { + binaryOperation.setForcedType(ti); + } + } + } + } + if (readIf(DOT)) { + r = new FieldReference(r, readIdentifier()); + } + break; + case ARRAY: + read(); + if (readIf(OPEN_BRACKET)) { + if (readIf(CLOSE_BRACKET)) { + r = ValueExpression.get(ValueArray.EMPTY); + } else { + ArrayList list = Utils.newSmallArrayList(); + do { + list.add(readExpression()); + } while (readIf(COMMA)); + read(CLOSE_BRACKET); + r = new ExpressionList(list.toArray(new Expression[0]), true); + } + } else { + read(OPEN_PAREN); + Query q = parseQuery(); + read(CLOSE_PAREN); + r = new ArrayConstructorByQuery(q); + } + break; + case INTERVAL: + read(); + r = readInterval(); + break; + case ROW: { + read(); + read(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); + } + break; + } + case TRUE: + read(); + r = ValueExpression.TRUE; + break; + case FALSE: + read(); + r = ValueExpression.FALSE; + break; + case UNKNOWN: + read(); + r = TypedValueExpression.UNKNOWN; + break; + case ROWNUM: + read(); + if (readIf(OPEN_PAREN)) { + read(CLOSE_PAREN); + } + if (currentSelect == null && currentPrepared == null) { + throw getSyntaxError(); + } + r = new Rownum(getCurrentPreparedOrSelect()); + break; + case NULL: + read(); + r = ValueExpression.NULL; + break; + case _ROWID_: + read(); + r = new ExpressionColumn(database, null, null); + break; + case LITERAL: + r = ValueExpression.get(token.value(session)); + read(); + break; + case VALUES: + if (database.getMode().onDuplicateKeyUpdate) { + if (currentPrepared instanceof Insert) { + r = readOnDuplicateKeyValues(((Insert) currentPrepared).getTable(), null); + break; + } else if (currentPrepared instanceof Update) { + Update update = (Update) currentPrepared; + r = readOnDuplicateKeyValues(update.getTable(), update); + break; + } + } + r = new Subquery(parseQuery()); + break; + case CASE: + read(); + r = readCase(); + break; + case CAST: { + read(); + read(OPEN_PAREN); + Expression arg = readExpression(); + read(AS); + Column column = parseColumnWithType(null); + read(CLOSE_PAREN); + r = new CastSpecification(arg, column); + break; + } + case CURRENT_CATALOG: + return readCurrentGeneralValueSpecification(CurrentGeneralValueSpecification.CURRENT_CATALOG); + case CURRENT_DATE: + read(); + r = readCurrentDateTimeValueFunction(CurrentDateTimeValueFunction.CURRENT_DATE, readIf(OPEN_PAREN), null); + break; + case CURRENT_PATH: + return readCurrentGeneralValueSpecification(CurrentGeneralValueSpecification.CURRENT_PATH); + case CURRENT_ROLE: + return readCurrentGeneralValueSpecification(CurrentGeneralValueSpecification.CURRENT_ROLE); + case CURRENT_SCHEMA: + return readCurrentGeneralValueSpecification(CurrentGeneralValueSpecification.CURRENT_SCHEMA); + case CURRENT_TIME: + read(); + r = readCurrentDateTimeValueFunction(CurrentDateTimeValueFunction.CURRENT_TIME, readIf(OPEN_PAREN), null); + break; + case CURRENT_TIMESTAMP: + read(); + r = readCurrentDateTimeValueFunction(CurrentDateTimeValueFunction.CURRENT_TIMESTAMP, readIf(OPEN_PAREN), + null); + break; + case CURRENT_USER: + case USER: + return readCurrentGeneralValueSpecification(CurrentGeneralValueSpecification.CURRENT_USER); + case SESSION_USER: + return readCurrentGeneralValueSpecification(CurrentGeneralValueSpecification.SESSION_USER); + case SYSTEM_USER: + return readCurrentGeneralValueSpecification(CurrentGeneralValueSpecification.SYSTEM_USER); + case ANY: + case SOME: + read(); + read(OPEN_PAREN); + return readAggregate(AggregateType.ANY, "ANY"); + case DAY: + case HOUR: + case MINUTE: + case MONTH: + case SECOND: + case YEAR: + r = readKeywordCompatibilityFunctionOrColumn(); + break; + case LEFT: + r = readColumnIfNotFunction(); + if (r == null) { + r = new StringFunction2(readExpression(), readLastArgument(), StringFunction2.LEFT); + } + break; + case LOCALTIME: + read(); + r = readCurrentDateTimeValueFunction(CurrentDateTimeValueFunction.LOCALTIME, readIf(OPEN_PAREN), null); + break; + case LOCALTIMESTAMP: + read(); + r = readCurrentDateTimeValueFunction(CurrentDateTimeValueFunction.LOCALTIMESTAMP, readIf(OPEN_PAREN), // + null); + break; + case RIGHT: + r = readColumnIfNotFunction(); + if (r == null) { + r = new StringFunction2(readExpression(), readLastArgument(), StringFunction2.RIGHT); + } + break; + case SET: + r = readColumnIfNotFunction(); + if (r == null) { + r = readSetFunction(); + } + break; + case VALUE: + if (parseDomainConstraint) { + read(); + r = new DomainValueExpression(); + break; + } + //$FALL-THROUGH$ + default: + 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); + } + 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)); + } + for (;;) { + TypeInfo ti = readIntervalQualifier(); + if (ti != null) { + r = new CastSpecification(r, ti); + } + int index = tokenIndex; + if (readIf("AT")) { + if (readIf("TIME")) { + read("ZONE"); + r = new TimeZoneOperation(r, readExpression()); + continue; + } else if (readIf("LOCAL")) { + r = new TimeZoneOperation(r, null); + continue; + } else { + setTokenIndex(index); + } + } else if (readIf("FORMAT")) { + if (readIf("JSON")) { + r = new Format(r, FormatEnum.JSON); + continue; + } else { + setTokenIndex(index); + } + } + break; + } + return r; + } + + private Expression readCurrentGeneralValueSpecification(int specification) { + read(); + if (readIf(OPEN_PAREN)) { + read(CLOSE_PAREN); + } + return new CurrentGeneralValueSpecification(specification); + } + + private Expression readColumnIfNotFunction() { + boolean nonKeyword = nonKeywords != null && nonKeywords.get(currentTokenType); + String name = currentToken; + read(); + if (readIf(OPEN_PAREN)) { + return null; + } else if (nonKeyword) { + return readIf(DOT) ? readTermObjectDot(name) : new ExpressionColumn(database, null, null, name); + } + throw getSyntaxError(); + } + + private Expression readSetFunction() { + SetFunction function = new SetFunction(readExpression(), readLastArgument()); + if (database.isAllowBuiltinAliasOverride()) { + FunctionAlias functionAlias = database.getSchema(session.getCurrentSchemaName()).findFunction( + function.getName()); + if (functionAlias != null) { + return new JavaFunction(functionAlias, + new Expression[] { function.getSubexpression(0), function.getSubexpression(1) }); + } + } + return function; + } + + private Expression readOnDuplicateKeyValues(Table table, Update update) { + read(); + read(OPEN_PAREN); + Column c = readTableColumn(new TableFilter(session, table, null, rightsChecked, null, 0, null)); + read(CLOSE_PAREN); + return new OnDuplicateKeyValues(c, update); + } + + private Expression readTermWithIdentifier(String name, boolean quoted) { + /* + * Convert a-z to A-Z. This method is safe, because only A-Z + * characters are considered below. + * + * Unquoted identifier is never empty. + */ + switch (name.charAt(0) & 0xffdf) { + case 'C': + if (equalsToken("CURRENT", name)) { + int index = tokenIndex; + if (readIf(VALUE) && readIf(FOR)) { + return new SequenceValue(readSequence()); + } + setTokenIndex(index); + if (database.getMode().getEnum() == ModeEnum.DB2) { + return parseDB2SpecialRegisters(name); + } + } + break; + case 'D': + if (currentTokenType == LITERAL && token.value(session).getValueType() == Value.VARCHAR && + (equalsToken("DATE", name) || equalsToken("D", name))) { + String date = token.value(session).getString(); + read(); + return ValueExpression.get(ValueDate.parse(date)); + } + break; + case 'E': + if (currentTokenType == LITERAL && token.value(session).getValueType() == Value.VARCHAR // + && equalsToken("E", name)) { + String text = token.value(session).getString(); + // the PostgreSQL ODBC driver uses + // LIKE E'PROJECT\\_DATA' instead of LIKE + // 'PROJECT\_DATA' + // N: SQL-92 "National Language" strings + text = StringUtils.replaceAll(text, "\\\\", "\\"); + read(); + return ValueExpression.get(ValueVarchar.get(text)); + } + break; + case 'G': + if (currentTokenType == LITERAL) { + int t = token.value(session).getValueType(); + if (t == Value.VARCHAR && equalsToken("GEOMETRY", name)) { + ValueExpression v = ValueExpression.get(ValueGeometry.get(token.value(session).getString())); + read(); + return v; + } else if (t == Value.VARBINARY && equalsToken("GEOMETRY", name)) { + ValueExpression v = ValueExpression + .get(ValueGeometry.getFromEWKB(token.value(session).getBytesNoCopy())); + read(); + return v; + } + } + break; + case 'J': + if (currentTokenType == LITERAL) { + int t = token.value(session).getValueType(); + if (t == Value.VARCHAR && equalsToken("JSON", name)) { + ValueExpression v = ValueExpression.get(ValueJson.fromJson(token.value(session).getString())); + read(); + return v; + } else if (t == Value.VARBINARY && equalsToken("JSON", name)) { + ValueExpression v = ValueExpression.get(ValueJson.fromJson(token.value(session).getBytesNoCopy())); + read(); + return v; + } + } + break; + case 'N': + if (equalsToken("NEXT", name)) { + int index = tokenIndex; + if (readIf(VALUE) && readIf(FOR)) { + return new SequenceValue(readSequence(), getCurrentPreparedOrSelect()); + } + setTokenIndex(index); + } + break; + case 'T': + if (equalsToken("TIME", name)) { + if (readIf(WITH)) { + read("TIME"); + read("ZONE"); + if (currentTokenType != LITERAL || token.value(session).getValueType() != Value.VARCHAR) { + throw getSyntaxError(); + } + String time = token.value(session).getString(); + read(); + return ValueExpression.get(ValueTimeTimeZone.parse(time)); + } else { + boolean without = readIf("WITHOUT"); + if (without) { + read("TIME"); + read("ZONE"); + } + if (currentTokenType == LITERAL && token.value(session).getValueType() == Value.VARCHAR) { + String time = token.value(session).getString(); + read(); + return ValueExpression.get(ValueTime.parse(time)); + } else if (without) { + throw getSyntaxError(); + } + } + } else if (equalsToken("TIMESTAMP", name)) { + if (readIf(WITH)) { + read("TIME"); + read("ZONE"); + if (currentTokenType != LITERAL || token.value(session).getValueType() != Value.VARCHAR) { + throw getSyntaxError(); + } + String timestamp = token.value(session).getString(); + read(); + return ValueExpression.get(ValueTimestampTimeZone.parse(timestamp, session)); + } else { + boolean without = readIf("WITHOUT"); + if (without) { + read("TIME"); + read("ZONE"); + } + if (currentTokenType == LITERAL && token.value(session).getValueType() == Value.VARCHAR) { + String timestamp = token.value(session).getString(); + read(); + return ValueExpression.get(ValueTimestamp.parse(timestamp, session)); + } else if (without) { + throw getSyntaxError(); + } + } + } else if (currentTokenType == LITERAL && token.value(session).getValueType() == Value.VARCHAR) { + if (equalsToken("T", name)) { + String time = token.value(session).getString(); + read(); + return ValueExpression.get(ValueTime.parse(time)); + } else if (equalsToken("TS", name)) { + String timestamp = token.value(session).getString(); + read(); + return ValueExpression.get(ValueTimestamp.parse(timestamp, session)); + } + } + break; + case 'U': + if (currentTokenType == LITERAL && token.value(session).getValueType() == Value.VARCHAR + && equalsToken("UUID", name)) { + String uuid = token.value(session).getString(); + read(); + return ValueExpression.get(ValueUuid.get(uuid)); + } + break; + } + return new ExpressionColumn(database, null, null, name, quoted); + } + + private Prepared getCurrentPreparedOrSelect() { + Prepared p = currentPrepared; + return p != null ? p : currentSelect; + } + + private Expression readInterval() { + boolean negative = readIf(MINUS_SIGN); + if (!negative) { + readIf(PLUS_SIGN); + } + if (currentTokenType != LITERAL || token.value(session).getValueType() != Value.VARCHAR) { + addExpected("string"); + throw getSyntaxError(); + } + String s = token.value(session).getString(); + read(); + TypeInfo typeInfo = readIntervalQualifier(); + try { + 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); + } + } + + private Expression parseDB2SpecialRegisters(String name) { + // Only "CURRENT" name is supported + if (readIf("TIMESTAMP")) { + if (readIf(WITH)) { + read("TIME"); + read("ZONE"); + return readCurrentDateTimeValueFunction(CurrentDateTimeValueFunction.CURRENT_TIMESTAMP, + readIf(OPEN_PAREN), null); + } + return readCurrentDateTimeValueFunction(CurrentDateTimeValueFunction.LOCALTIMESTAMP, readIf(OPEN_PAREN), + null); + } else if (readIf("TIME")) { + // Time with fractional seconds is not supported by DB2 + return readCurrentDateTimeValueFunction(CurrentDateTimeValueFunction.LOCALTIME, false, null); + } else if (readIf("DATE")) { + return readCurrentDateTimeValueFunction(CurrentDateTimeValueFunction.CURRENT_DATE, false, null); + } + // No match, parse CURRENT as a column + return new ExpressionColumn(database, null, null, name); + } + + private Expression readCase() { + Expression c; + if (readIf(WHEN)) { + SearchedCase searched = new SearchedCase(); + do { + Expression condition = readExpression(); + read("THEN"); + searched.addParameter(condition); + searched.addParameter(readExpression()); + } while (readIf(WHEN)); + if (readIf(ELSE)) { + searched.addParameter(readExpression()); + } + searched.doneWithParameters(); + c = searched; + } else { + Expression caseOperand = readExpression(); + read(WHEN); + SimpleCase.SimpleWhen when = readSimpleWhenClause(caseOperand), current = when; + while (readIf(WHEN)) { + SimpleCase.SimpleWhen next = readSimpleWhenClause(caseOperand); + current.setWhen(next); + current = next; + } + c = new SimpleCase(caseOperand, when, readIf(ELSE) ? readExpression() : null); + } + read(END); + return c; + } + + private SimpleCase.SimpleWhen readSimpleWhenClause(Expression caseOperand) { + Expression whenOperand = readWhenOperand(caseOperand); + if (readIf(COMMA)) { + ArrayList operands = Utils.newSmallArrayList(); + operands.add(whenOperand); + do { + operands.add(readWhenOperand(caseOperand)); + } while (readIf(COMMA)); + read("THEN"); + return new SimpleCase.SimpleWhen(operands.toArray(new Expression[0]), readExpression()); + } + read("THEN"); + return new SimpleCase.SimpleWhen(whenOperand, readExpression()); + } + + private Expression readWhenOperand(Expression caseOperand) { + int backup = tokenIndex; + boolean not = readIf(NOT); + Expression whenOperand = readConditionRightHandSide(caseOperand, not, true); + if (whenOperand == null) { + if (not) { + setTokenIndex(backup); + } + whenOperand = readExpression(); + } + return whenOperand; + } + + private int readNonNegativeInt() { + int v = readInt(); + if (v < 0) { + throw DbException.getInvalidValueException("non-negative integer", v); + } + return v; + } + + private int readInt() { + boolean minus = false; + if (currentTokenType == MINUS_SIGN) { + minus = true; + read(); + } else if (currentTokenType == PLUS_SIGN) { + read(); + } + if (currentTokenType != LITERAL) { + throw DbException.getSyntaxError(sqlCommand, token.start(), "integer"); + } + Value value = token.value(session); + if (minus) { + // must do that now, otherwise Integer.MIN_VALUE would not work + value = value.negate(); + } + int i = value.getInt(); + read(); + return i; + } + + private long readPositiveLong() { + long v = readLong(); + if (v <= 0) { + throw DbException.getInvalidValueException("positive long", v); + } + return v; + } + + private long readLong() { + boolean minus = false; + if (currentTokenType == MINUS_SIGN) { + minus = true; + read(); + } else if (currentTokenType == PLUS_SIGN) { + read(); + } + if (currentTokenType != LITERAL) { + throw DbException.getSyntaxError(sqlCommand, token.start(), "long"); + } + Value value = token.value(session); + if (minus) { + // must do that now, otherwise Long.MIN_VALUE would not work + value = value.negate(); + } + long i = value.getLong(); + read(); + return i; + } + + private boolean readBooleanSetting() { + switch (currentTokenType) { + case ON: + case TRUE: + read(); + return true; + case FALSE: + read(); + return false; + case LITERAL: + boolean result = token.value(session).getBoolean(); + read(); + return result; + } + if (readIf("OFF")) { + return false; + } else { + if (expectedList != null) { + addMultipleExpected(ON, TRUE, FALSE); + } + throw getSyntaxError(); + } + } + + private String readString() { + int sqlIndex = token.start(); + Expression expr = readExpression(); + try { + String s = expr.optimize(session).getValue(session).getString(); + if (s == null || s.length() <= Constants.MAX_STRING_LENGTH) { + return s; + } + } catch (DbException e) { + } + throw DbException.getSyntaxError(sqlCommand, sqlIndex, "character string"); + } + + // TODO: why does this function allow defaultSchemaName=null - which resets + // the parser schemaName for everyone ? + private String readIdentifierWithSchema(String defaultSchemaName) { + String s = readIdentifier(); + schemaName = defaultSchemaName; + if (readIf(DOT)) { + s = readIdentifierWithSchema2(s); + } + return s; + } + + private String readIdentifierWithSchema2(String s) { + schemaName = s; + if (database.getMode().allowEmptySchemaValuesAsDefaultSchema && readIf(DOT)) { + if (equalsToken(schemaName, database.getShortName()) || database.getIgnoreCatalogs()) { + schemaName = session.getCurrentSchemaName(); + s = readIdentifier(); + } + } else { + s = readIdentifier(); + if (currentTokenType == DOT) { + if (equalsToken(schemaName, database.getShortName()) || database.getIgnoreCatalogs()) { + read(); + schemaName = s; + s = readIdentifier(); + } + } + } + return s; + } + + private String readIdentifierWithSchema() { + return readIdentifierWithSchema(session.getCurrentSchemaName()); + } + + private String readIdentifier() { + if (!isIdentifier()) { + /* + * Sometimes a new keywords are introduced. During metadata + * initialization phase keywords are accepted as identifiers to + * allow migration from older versions. + */ + if (!session.isQuirksMode() || !isKeyword(currentTokenType)) { + throw DbException.getSyntaxError(sqlCommand, token.start(), "identifier"); + } + } + String s = currentToken; + read(); + return s; + } + + private void read(String expected) { + if (token.isQuoted() || !equalsToken(expected, currentToken)) { + addExpected(expected); + throw getSyntaxError(); + } + read(); + } + + private void read(int tokenType) { + if (tokenType != currentTokenType) { + addExpected(tokenType); + throw getSyntaxError(); + } + read(); + } + + private boolean readIf(String tokenName) { + if (!token.isQuoted() && equalsToken(tokenName, currentToken)) { + read(); + return true; + } + addExpected(tokenName); + return false; + } + + private boolean readIf(int tokenType) { + if (tokenType == currentTokenType) { + read(); + return true; + } + addExpected(tokenType); + return false; + } + + private boolean isToken(String tokenName) { + if (!token.isQuoted() && equalsToken(tokenName, currentToken)) { + return true; + } + addExpected(tokenName); + return false; + } + + private boolean isToken(int tokenType) { + if (tokenType == currentTokenType) { + return true; + } + addExpected(tokenType); + return false; + } + + private boolean equalsToken(String a, String b) { + if (a == null) { + return b == null; + } else + return a.equals(b) || !identifiersToUpper && a.equalsIgnoreCase(b); + } + + private boolean isIdentifier() { + return currentTokenType == IDENTIFIER || nonKeywords != null && nonKeywords.get(currentTokenType); + } + + private void addExpected(String token) { + if (expectedList != null) { + expectedList.add(token); + } + } + + private void addExpected(int tokenType) { + if (expectedList != null) { + expectedList.add(TOKENS[tokenType]); + } + } + + private void addMultipleExpected(int ... tokenTypes) { + for (int tokenType : tokenTypes) { + expectedList.add(TOKENS[tokenType]); + } + } + + private void read() { + if (expectedList != null) { + expectedList.clear(); + } + int size = tokens.size(); + if (tokenIndex + 1 < size) { + token = tokens.get(++tokenIndex); + currentTokenType = token.tokenType(); + currentToken = token.asIdentifier(); + if (currentToken != null && currentToken.length() > Constants.MAX_IDENTIFIER_LENGTH) { + throw DbException.get(ErrorCode.NAME_TOO_LONG_2, currentToken.substring(0, 32), + "" + Constants.MAX_IDENTIFIER_LENGTH); + } else if (currentTokenType == LITERAL) { + checkLiterals(); + } + } else { + throw getSyntaxError(); + } + } + + private void checkLiterals() { + if (!literalsChecked && session != null && !session.getAllowLiterals()) { + int allowed = database.getAllowLiterals(); + if (allowed == Constants.ALLOW_LITERALS_NONE + || ((token instanceof Token.CharacterStringToken || token instanceof Token.BinaryStringToken) + && allowed != Constants.ALLOW_LITERALS_ALL)) { + throw DbException.get(ErrorCode.LITERALS_ARE_NOT_ALLOWED); + } + } + } + + private void initialize(String sql, ArrayList tokens, boolean stopOnCloseParen) { + if (sql == null) { + sql = ""; + } + sqlCommand = sql; + this.tokens = tokens == null ? new Tokenizer(database, identifiersToUpper, identifiersToLower, nonKeywords) + .tokenize(sql, stopOnCloseParen) : tokens; + resetTokenIndex(); + } + + private void resetTokenIndex() { + tokenIndex = -1; + token = null; + currentTokenType = -1; + currentToken = null; + } + + void setTokenIndex(int index) { + if (index != tokenIndex) { + if (expectedList != null) { + expectedList.clear(); + } + token = tokens.get(index); + tokenIndex = index; + currentTokenType = token.tokenType(); + currentToken = token.asIdentifier(); + } + } + + private static boolean isKeyword(int tokenType) { + return tokenType >= FIRST_KEYWORD && tokenType <= LAST_KEYWORD; + } + + private boolean isKeyword(String s) { + return ParserUtil.isKeyword(s, !identifiersToUpper); + } + + private String upperName(String name) { + return identifiersToUpper ? name : StringUtils.toUpperEnglish(name); + } + + private Column parseColumnForTable(String columnName, boolean defaultNullable) { + Column column; + Mode mode = database.getMode(); + if (mode.identityDataType && readIf("IDENTITY")) { + column = new Column(columnName, TypeInfo.TYPE_BIGINT); + parseCompatibilityIdentityOptions(column); + column.setPrimaryKey(true); + } else if (mode.serialDataTypes && readIf("BIGSERIAL")) { + column = new Column(columnName, TypeInfo.TYPE_BIGINT); + column.setIdentityOptions(new SequenceOptions(), false); + } else if (mode.serialDataTypes && readIf("SERIAL")) { + column = new Column(columnName, TypeInfo.TYPE_INTEGER); + column.setIdentityOptions(new SequenceOptions(), false); + } else { + column = parseColumnWithType(columnName); + } + if (readIf("INVISIBLE")) { + column.setVisible(false); + } else if (readIf("VISIBLE")) { + column.setVisible(true); + } + boolean defaultOnNull = false; + NullConstraintType nullConstraint = parseNotNullConstraint(); + defaultIdentityGeneration: if (!column.isIdentity()) { + if (readIf(AS)) { + column.setGeneratedExpression(readExpression()); + } else if (readIf(DEFAULT)) { + if (readIf(ON)) { + read(NULL); + defaultOnNull = true; + break defaultIdentityGeneration; + } + column.setDefaultExpression(session, readExpression()); + } else if (readIf("GENERATED")) { + boolean always = readIf("ALWAYS"); + if (!always) { + read("BY"); + read(DEFAULT); + } + read(AS); + if (readIf("IDENTITY")) { + SequenceOptions options = new SequenceOptions(); + if (readIf(OPEN_PAREN)) { + parseSequenceOptions(options, null, false, false); + read(CLOSE_PAREN); + } + column.setIdentityOptions(options, always); + break defaultIdentityGeneration; + } else if (!always) { + throw getSyntaxError(); + } else { + column.setGeneratedExpression(readExpression()); + } + } + if (!column.isGenerated() && readIf(ON)) { + read("UPDATE"); + column.setOnUpdateExpression(session, readExpression()); + } + nullConstraint = parseNotNullConstraint(nullConstraint); + if (parseCompatibilityIdentity(column, mode)) { + nullConstraint = parseNotNullConstraint(nullConstraint); + } + } + switch (nullConstraint) { + case NULL_IS_ALLOWED: + if (column.isIdentity()) { + throw DbException.get(ErrorCode.COLUMN_MUST_NOT_BE_NULLABLE_1, column.getName()); + } + column.setNullable(true); + break; + case NULL_IS_NOT_ALLOWED: + column.setNullable(false); + break; + case NO_NULL_CONSTRAINT_FOUND: + if (!column.isIdentity()) { + column.setNullable(defaultNullable); + } + break; + default: + throw DbException.get(ErrorCode.UNKNOWN_MODE_1, + "Internal Error - unhandled case: " + nullConstraint.name()); + } + if (!defaultOnNull) { + if (readIf(DEFAULT)) { + read(ON); + read(NULL); + defaultOnNull = true; + } else if (readIf("NULL_TO_DEFAULT")) { + defaultOnNull = true; + } + } + if (defaultOnNull) { + column.setDefaultOnNull(true); + } + if (!column.isGenerated()) { + if (readIf("SEQUENCE")) { + column.setSequence(readSequence(), column.isGeneratedAlways()); + } + } + if (readIf("SELECTIVITY")) { + column.setSelectivity(readNonNegativeInt()); + } + if (mode.getEnum() == ModeEnum.MySQL) { + if (readIf("CHARACTER")) { + readIf(SET); + readMySQLCharset(); + } + if (readIf("COLLATE")) { + readMySQLCharset(); + } + } + String comment = readCommentIf(); + if (comment != null) { + column.setComment(comment); + } + return column; + } + + private void parseCompatibilityIdentityOptions(Column column) { + SequenceOptions options = new SequenceOptions(); + if (readIf(OPEN_PAREN)) { + options.setStartValue(ValueExpression.get(ValueBigint.get(readLong()))); + if (readIf(COMMA)) { + options.setIncrement(ValueExpression.get(ValueBigint.get(readLong()))); + } + read(CLOSE_PAREN); + } + column.setIdentityOptions(options, false); + } + + private String readCommentIf() { + if (readIf("COMMENT")) { + readIf(IS); + return readString(); + } + return null; + } + + private Column parseColumnWithType(String columnName) { + TypeInfo typeInfo = readIfDataType(); + if (typeInfo == null) { + String domainName = readIdentifierWithSchema(); + return getColumnWithDomain(columnName, getSchema().getDomain(domainName)); + } + return new Column(columnName, typeInfo); + } + + private TypeInfo parseDataType() { + TypeInfo typeInfo = readIfDataType(); + if (typeInfo == null) { + addExpected("data type"); + throw getSyntaxError(); + } + return typeInfo; + } + + private TypeInfo readIfDataType() { + TypeInfo typeInfo = readIfDataType1(); + if (typeInfo != null) { + while (readIf(ARRAY)) { + typeInfo = parseArrayType(typeInfo); + } + } + return typeInfo; + } + + private TypeInfo readIfDataType1() { + switch (currentTokenType) { + case IDENTIFIER: + if (token.isQuoted()) { + return null; + } + break; + case INTERVAL: { + read(); + TypeInfo typeInfo = readIntervalQualifier(); + if (typeInfo == null) { + throw intervalQualifierError(); + } + return typeInfo; + } + case NULL: + read(); + return TypeInfo.TYPE_NULL; + case ROW: + read(); + return parseRowType(); + case ARRAY: + // Partial compatibility with 1.4.200 and older versions + if (session.isQuirksMode()) { + read(); + return parseArrayType(TypeInfo.TYPE_VARCHAR); + } + addExpected("data type"); + throw getSyntaxError(); + default: + if (isKeyword(currentToken)) { + break; + } + addExpected("data type"); + throw getSyntaxError(); + } + int index = tokenIndex; + String originalCase = currentToken; + read(); + if (currentTokenType == DOT) { + setTokenIndex(index); + return null; + } + String original = upperName(originalCase); + switch (original) { + case "BINARY": + if (readIf("VARYING")) { + original = "BINARY VARYING"; + } else if (readIf("LARGE")) { + read("OBJECT"); + original = "BINARY LARGE OBJECT"; + } else if (variableBinary) { + original = "VARBINARY"; + } + break; + case "CHAR": + if (readIf("VARYING")) { + original = "CHAR VARYING"; + } else if (readIf("LARGE")) { + read("OBJECT"); + original = "CHAR LARGE OBJECT"; + } + break; + case "CHARACTER": + if (readIf("VARYING")) { + original = "CHARACTER VARYING"; + } else if (readIf("LARGE")) { + read("OBJECT"); + original = "CHARACTER LARGE OBJECT"; + } + break; + case "DATETIME": + case "DATETIME2": + return parseDateTimeType(false); + case "DEC": + case "DECIMAL": + return parseNumericType(true); + case "DECFLOAT": + return parseDecfloatType(); + case "DOUBLE": + if (readIf("PRECISION")) { + original = "DOUBLE PRECISION"; + } + break; + case "ENUM": + return parseEnumType(); + case "FLOAT": + return parseFloatType(); + case "GEOMETRY": + return parseGeometryType(); + case "LONG": + if (readIf("RAW")) { + original = "LONG RAW"; + } + break; + case "NATIONAL": + if (readIf("CHARACTER")) { + if (readIf("VARYING")) { + original = "NATIONAL CHARACTER VARYING"; + } else if (readIf("LARGE")) { + read("OBJECT"); + original = "NATIONAL CHARACTER LARGE OBJECT"; + } else { + original = "NATIONAL CHARACTER"; + } + } else { + read("CHAR"); + if (readIf("VARYING")) { + original = "NATIONAL CHAR VARYING"; + } else { + original = "NATIONAL CHAR"; + } + } + break; + case "NCHAR": + if (readIf("VARYING")) { + original = "NCHAR VARYING"; + } else if (readIf("LARGE")) { + read("OBJECT"); + original = "NCHAR LARGE OBJECT"; + } + break; + case "NUMBER": + if (database.getMode().disallowedTypes.contains("NUMBER")) { + throw DbException.get(ErrorCode.UNKNOWN_DATA_TYPE_1, "NUMBER"); + } + if (!isToken(OPEN_PAREN)) { + return TypeInfo.getTypeInfo(Value.DECFLOAT, 40, -1, null); + } + //$FALL-THROUGH$ + case "NUMERIC": + return parseNumericType(false); + case "SMALLDATETIME": + return parseDateTimeType(true); + case "TIME": + return parseTimeType(); + case "TIMESTAMP": + return parseTimestampType(); + } + // Domain names can't have multiple words without quotes + if (originalCase.length() == original.length()) { + Domain domain = database.getSchema(session.getCurrentSchemaName()).findDomain(originalCase); + if (domain != null) { + setTokenIndex(index); + return null; + } + } + Mode mode = database.getMode(); + DataType dataType = DataType.getTypeByName(original, mode); + if (dataType == null || mode.disallowedTypes.contains(original)) { + throw DbException.get(ErrorCode.UNKNOWN_DATA_TYPE_1, original); + } + long precision; + int scale; + if (dataType.specialPrecisionScale) { + precision = dataType.defaultPrecision; + scale = dataType.defaultScale; + } else { + precision = -1L; + scale = -1; + } + int t = dataType.type; + if (database.getIgnoreCase() && t == Value.VARCHAR && !equalsToken("VARCHAR_CASESENSITIVE", original)) { + dataType = DataType.getDataType(t = Value.VARCHAR_IGNORECASE); + } + if ((dataType.supportsPrecision || dataType.supportsScale) && readIf(OPEN_PAREN)) { + if (!readIf("MAX")) { + if (dataType.supportsPrecision) { + precision = readPrecision(t); + if (precision < dataType.minPrecision) { + throw getInvalidPrecisionException(dataType, precision); + } else if (precision > dataType.maxPrecision) + badPrecision: { + if (session.isQuirksMode() || session.isTruncateLargeLength()) { + switch (dataType.type) { + case Value.CHAR: + case Value.VARCHAR: + case Value.VARCHAR_IGNORECASE: + case Value.BINARY: + case Value.VARBINARY: + case Value.JAVA_OBJECT: + case Value.JSON: + precision = dataType.maxPrecision; + break badPrecision; + } + } + throw getInvalidPrecisionException(dataType, precision); + } + if (dataType.supportsScale) { + if (readIf(COMMA)) { + scale = readInt(); + if (scale < dataType.minScale || scale > dataType.maxScale) { + throw DbException.get(ErrorCode.INVALID_VALUE_SCALE, Integer.toString(scale), + Integer.toString(dataType.minScale), Integer.toString(dataType.maxScale)); + } + } + } + } else { + scale = readInt(); + if (scale < dataType.minScale || scale > dataType.maxScale) { + throw DbException.get(ErrorCode.INVALID_VALUE_SCALE, Integer.toString(scale), + Integer.toString(dataType.minScale), Integer.toString(dataType.maxScale)); + } + } + } + read(CLOSE_PAREN); + } + 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. + readNonNegativeInt(); + read(CLOSE_PAREN); + } + readIf("UNSIGNED"); + } + if (mode.forBitData && DataType.isStringType(t)) { + if (readIf(FOR)) { + read("BIT"); + read("DATA"); + dataType = DataType.getDataType(t = Value.VARBINARY); + } + } + return TypeInfo.getTypeInfo(t, precision, scale, null); + } + + private static DbException getInvalidPrecisionException(DataType dataType, long precision) { + return DbException.get(ErrorCode.INVALID_VALUE_PRECISION, Long.toString(precision), + Long.toString(dataType.minPrecision), Long.toString(dataType.maxPrecision)); + } + + private static Column getColumnWithDomain(String columnName, Domain domain) { + Column column = new Column(columnName, domain.getDataType()); + column.setComment(domain.getComment()); + column.setDomain(domain); + return column; + } + + private TypeInfo parseFloatType() { + int type = Value.DOUBLE; + int precision; + if (readIf(OPEN_PAREN)) { + precision = readNonNegativeInt(); + read(CLOSE_PAREN); + if (precision < 1 || precision > 53) { + throw DbException.get(ErrorCode.INVALID_VALUE_PRECISION, Integer.toString(precision), "1", "53"); + } + if (precision <= 24) { + type = Value.REAL; + } + } else { + precision = 0; + } + return TypeInfo.getTypeInfo(type, precision, -1, null); + } + + private TypeInfo parseNumericType(boolean decimal) { + long precision = -1L; + int scale = -1; + if (readIf(OPEN_PAREN)) { + precision = readPrecision(Value.NUMERIC); + if (precision < 1) { + throw getInvalidNumericPrecisionException(precision); + } else if (precision > Constants.MAX_NUMERIC_PRECISION) { + if (session.isQuirksMode() || session.isTruncateLargeLength()) { + precision = Constants.MAX_NUMERIC_PRECISION; + } else { + throw getInvalidNumericPrecisionException(precision); + } + } + if (readIf(COMMA)) { + scale = readInt(); + if (scale < 0 || scale > ValueNumeric.MAXIMUM_SCALE) { + throw DbException.get(ErrorCode.INVALID_VALUE_SCALE, Integer.toString(scale), + "0", "" + ValueNumeric.MAXIMUM_SCALE); + } + } + read(CLOSE_PAREN); + } + return TypeInfo.getTypeInfo(Value.NUMERIC, precision, scale, decimal ? ExtTypeInfoNumeric.DECIMAL : null); + } + + private TypeInfo parseDecfloatType() { + long precision = -1L; + if (readIf(OPEN_PAREN)) { + precision = readPrecision(Value.DECFLOAT); + if (precision < 1 || precision > Constants.MAX_NUMERIC_PRECISION) { + throw getInvalidNumericPrecisionException(precision); + } + read(CLOSE_PAREN); + } + return TypeInfo.getTypeInfo(Value.DECFLOAT, precision, -1, null); + } + + private static DbException getInvalidNumericPrecisionException(long precision) { + return DbException.get(ErrorCode.INVALID_VALUE_PRECISION, Long.toString(precision), "1", + "" + Constants.MAX_NUMERIC_PRECISION); + } + + private TypeInfo parseTimeType() { + int scale = -1; + if (readIf(OPEN_PAREN)) { + scale = readNonNegativeInt(); + if (scale > ValueTime.MAXIMUM_SCALE) { + throw DbException.get(ErrorCode.INVALID_VALUE_SCALE, Integer.toString(scale), "0", + /* Folds to a constant */ "" + ValueTime.MAXIMUM_SCALE); + } + read(CLOSE_PAREN); + } + int type = Value.TIME; + if (readIf(WITH)) { + read("TIME"); + read("ZONE"); + type = Value.TIME_TZ; + } else if (readIf("WITHOUT")) { + read("TIME"); + read("ZONE"); + } + return TypeInfo.getTypeInfo(type, -1L, scale, null); + } + + private TypeInfo parseTimestampType() { + int scale = -1; + if (readIf(OPEN_PAREN)) { + scale = readNonNegativeInt(); + // Allow non-standard TIMESTAMP(..., ...) syntax + if (readIf(COMMA)) { + scale = readNonNegativeInt(); + } + if (scale > ValueTimestamp.MAXIMUM_SCALE) { + throw DbException.get(ErrorCode.INVALID_VALUE_SCALE, Integer.toString(scale), "0", + /* Folds to a constant */ "" + ValueTimestamp.MAXIMUM_SCALE); + } + read(CLOSE_PAREN); + } + int type = Value.TIMESTAMP; + if (readIf(WITH)) { + read("TIME"); + read("ZONE"); + type = Value.TIMESTAMP_TZ; + } else if (readIf("WITHOUT")) { + read("TIME"); + read("ZONE"); + } + return TypeInfo.getTypeInfo(type, -1L, scale, null); + } + + private TypeInfo parseDateTimeType(boolean smallDateTime) { + int scale; + if (smallDateTime) { + scale = 0; + } else { + scale = -1; + if (readIf(OPEN_PAREN)) { + scale = readNonNegativeInt(); + if (scale > ValueTimestamp.MAXIMUM_SCALE) { + throw DbException.get(ErrorCode.INVALID_VALUE_SCALE, Integer.toString(scale), "0", + /* folds to a constant */ "" + ValueTimestamp.MAXIMUM_SCALE); + } + read(CLOSE_PAREN); + } + } + return TypeInfo.getTypeInfo(Value.TIMESTAMP, -1L, scale, null); + } + + private TypeInfo readIntervalQualifier() { + IntervalQualifier qualifier; + int precision = -1, scale = -1; + switch (currentTokenType) { + case YEAR: + read(); + if (readIf(OPEN_PAREN)) { + precision = readNonNegativeInt(); + read(CLOSE_PAREN); + } + if (readIf(TO)) { + read(MONTH); + qualifier = IntervalQualifier.YEAR_TO_MONTH; + } else { + qualifier = IntervalQualifier.YEAR; + } + break; + case MONTH: + read(); + if (readIf(OPEN_PAREN)) { + precision = readNonNegativeInt(); + read(CLOSE_PAREN); + } + qualifier = IntervalQualifier.MONTH; + break; + case DAY: + read(); + if (readIf(OPEN_PAREN)) { + precision = readNonNegativeInt(); + read(CLOSE_PAREN); + } + if (readIf(TO)) { + switch (currentTokenType) { + case HOUR: + read(); + qualifier = IntervalQualifier.DAY_TO_HOUR; + break; + case MINUTE: + read(); + qualifier = IntervalQualifier.DAY_TO_MINUTE; + break; + case SECOND: + read(); + if (readIf(OPEN_PAREN)) { + scale = readNonNegativeInt(); + read(CLOSE_PAREN); + } + qualifier = IntervalQualifier.DAY_TO_SECOND; + break; + default: + throw intervalDayError(); + } + } else { + qualifier = IntervalQualifier.DAY; + } + break; + case HOUR: + read(); + if (readIf(OPEN_PAREN)) { + precision = readNonNegativeInt(); + read(CLOSE_PAREN); + } + if (readIf(TO)) { + switch (currentTokenType) { + case MINUTE: + read(); + qualifier = IntervalQualifier.HOUR_TO_MINUTE; + break; + case SECOND: + read(); + if (readIf(OPEN_PAREN)) { + scale = readNonNegativeInt(); + read(CLOSE_PAREN); + } + qualifier = IntervalQualifier.HOUR_TO_SECOND; + break; + default: + throw intervalHourError(); + } + } else { + qualifier = IntervalQualifier.HOUR; + } + break; + case MINUTE: + read(); + if (readIf(OPEN_PAREN)) { + precision = readNonNegativeInt(); + read(CLOSE_PAREN); + } + if (readIf(TO)) { + read(SECOND); + if (readIf(OPEN_PAREN)) { + scale = readNonNegativeInt(); + read(CLOSE_PAREN); + } + qualifier = IntervalQualifier.MINUTE_TO_SECOND; + } else { + qualifier = IntervalQualifier.MINUTE; + } + break; + case SECOND: + read(); + if (readIf(OPEN_PAREN)) { + precision = readNonNegativeInt(); + if (readIf(COMMA)) { + scale = readNonNegativeInt(); + } + read(CLOSE_PAREN); + } + qualifier = IntervalQualifier.SECOND; + break; + default: + return null; + } + if (precision >= 0) { + if (precision == 0 || precision > ValueInterval.MAXIMUM_PRECISION) { + throw DbException.get(ErrorCode.INVALID_VALUE_PRECISION, Integer.toString(precision), "1", + /* Folds to a constant */ "" + ValueInterval.MAXIMUM_PRECISION); + } + } + if (scale >= 0) { + if (scale > ValueInterval.MAXIMUM_SCALE) { + throw DbException.get(ErrorCode.INVALID_VALUE_SCALE, Integer.toString(scale), "0", + /* Folds to a constant */ "" + ValueInterval.MAXIMUM_SCALE); + } + } + return TypeInfo.getTypeInfo(qualifier.ordinal() + Value.INTERVAL_YEAR, precision, scale, null); + } + + private DbException intervalQualifierError() { + if (expectedList != null) { + addMultipleExpected(YEAR, MONTH, DAY, HOUR, MINUTE, SECOND); + } + return getSyntaxError(); + } + + private DbException intervalDayError() { + if (expectedList != null) { + addMultipleExpected(HOUR, MINUTE, SECOND); + } + return getSyntaxError(); + } + + private DbException intervalHourError() { + if (expectedList != null) { + addMultipleExpected(MINUTE, SECOND); + } + return getSyntaxError(); + } + + private TypeInfo parseArrayType(TypeInfo componentType) { + int precision = -1; + if (readIf(OPEN_BRACKET)) { + // Maximum cardinality may be zero + precision = readNonNegativeInt(); + if (precision > Constants.MAX_ARRAY_CARDINALITY) { + throw DbException.get(ErrorCode.INVALID_VALUE_PRECISION, Integer.toString(precision), "0", + /* Folds to a constant */ "" + Constants.MAX_ARRAY_CARDINALITY); + } + read(CLOSE_BRACKET); + } + return TypeInfo.getTypeInfo(Value.ARRAY, precision, -1, componentType); + } + + private TypeInfo parseEnumType() { + read(OPEN_PAREN); + ArrayList enumeratorList = new ArrayList<>(); + do { + enumeratorList.add(readString()); + } while (readIfMore()); + return TypeInfo.getTypeInfo(Value.ENUM, -1L, -1, new ExtTypeInfoEnum(enumeratorList.toArray(new String[0]))); + } + + private TypeInfo parseGeometryType() { + ExtTypeInfoGeometry extTypeInfo; + if (readIf(OPEN_PAREN)) { + int type = 0; + if (currentTokenType != IDENTIFIER || token.isQuoted()) { + throw getSyntaxError(); + } + if (!readIf("GEOMETRY")) { + try { + type = EWKTUtils.parseGeometryType(currentToken); + read(); + if (type / 1_000 == 0 && currentTokenType == IDENTIFIER && !token.isQuoted()) { + type += EWKTUtils.parseDimensionSystem(currentToken) * 1_000; + read(); + } + } catch (IllegalArgumentException ex) { + throw getSyntaxError(); + } + } + Integer srid = null; + if (readIf(COMMA)) { + srid = readInt(); + } + read(CLOSE_PAREN); + extTypeInfo = new ExtTypeInfoGeometry(type, srid); + } else { + extTypeInfo = null; + } + return TypeInfo.getTypeInfo(Value.GEOMETRY, -1L, -1, extTypeInfo); + } + + private TypeInfo parseRowType() { + read(OPEN_PAREN); + LinkedHashMap fields = new LinkedHashMap<>(); + do { + String name = readIdentifier(); + if (fields.putIfAbsent(name, parseDataType()) != null) { + throw DbException.get(ErrorCode.DUPLICATE_COLUMN_NAME_1, name); + } + } while (readIfMore()); + return TypeInfo.getTypeInfo(Value.ROW, -1L, -1, new ExtTypeInfoRow(fields)); + } + + private long readPrecision(int valueType) { + long p = readPositiveLong(); + if (currentTokenType != IDENTIFIER || token.isQuoted()) { + return p; + } + if ((valueType == Value.BLOB || valueType == Value.CLOB) && currentToken.length() == 1) { + long mul; + /* + * Convert a-z to A-Z. This method is safe, because only A-Z + * characters are considered below. + */ + switch (currentToken.charAt(0) & 0xffdf) { + case 'K': + mul = 1L << 10; + break; + case 'M': + mul = 1L << 20; + break; + case 'G': + mul = 1L << 30; + break; + case 'T': + mul = 1L << 40; + break; + case 'P': + mul = 1L << 50; + break; + default: + throw getSyntaxError(); + } + if (p > Long.MAX_VALUE / mul) { + throw DbException.getInvalidValueException("precision", p + currentToken); + } + p *= mul; + read(); + if (currentTokenType != IDENTIFIER || token.isQuoted()) { + return p; + } + } + switch (valueType) { + case Value.VARCHAR: + case Value.VARCHAR_IGNORECASE: + case Value.CLOB: + case Value.CHAR: + if (!readIf("CHARACTERS") && !readIf("OCTETS")) { + if (database.getMode().charAndByteLengthUnits && !readIf("CHAR")) { + readIf("BYTE"); + } + } + } + return p; + } + + private Prepared parseCreate() { + boolean orReplace = false; + if (readIf(OR)) { + read("REPLACE"); + orReplace = true; + } + boolean force = readIf("FORCE"); + if (readIf("VIEW")) { + return parseCreateView(force, orReplace); + } else if (readIf("ALIAS")) { + return parseCreateFunctionAlias(force); + } else if (readIf("SEQUENCE")) { + return parseCreateSequence(); + } else if (readIf(USER)) { + return parseCreateUser(); + } else if (readIf("TRIGGER")) { + return parseCreateTrigger(force); + } else if (readIf("ROLE")) { + return parseCreateRole(); + } else if (readIf("SCHEMA")) { + return parseCreateSchema(); + } else if (readIf("CONSTANT")) { + return parseCreateConstant(); + } else if (readIf("DOMAIN") || readIf("TYPE") || readIf("DATATYPE")) { + return parseCreateDomain(); + } else if (readIf("AGGREGATE")) { + return parseCreateAggregate(force); + } else if (readIf("LINKED")) { + return parseCreateLinkedTable(false, false, force); + } + // tables or linked tables + boolean memory = false, cached = false; + if (readIf("MEMORY")) { + memory = true; + } else if (readIf("CACHED")) { + cached = true; + } + if (readIf("LOCAL")) { + read("TEMPORARY"); + if (readIf("LINKED")) { + return parseCreateLinkedTable(true, false, force); + } + read(TABLE); + return parseCreateTable(true, false, cached); + } else if (readIf("GLOBAL")) { + read("TEMPORARY"); + if (readIf("LINKED")) { + return parseCreateLinkedTable(true, true, force); + } + read(TABLE); + return parseCreateTable(true, true, cached); + } else if (readIf("TEMP") || readIf("TEMPORARY")) { + if (readIf("LINKED")) { + return parseCreateLinkedTable(true, true, force); + } + read(TABLE); + return parseCreateTable(true, true, cached); + } else if (readIf(TABLE)) { + if (!cached && !memory) { + cached = database.getDefaultTableType() == Table.TYPE_CACHED; + } + return parseCreateTable(false, false, cached); + } else if (readIf("SYNONYM")) { + return parseCreateSynonym(orReplace); + } else { + boolean hash = false, primaryKey = false; + boolean unique = false, spatial = false; + String indexName = null; + Schema oldSchema = null; + boolean ifNotExists = false; + if (session.isQuirksMode() && readIf(PRIMARY)) { + read(KEY); + if (readIf("HASH")) { + hash = true; + } + primaryKey = true; + if (!isToken(ON)) { + ifNotExists = readIfNotExists(); + indexName = readIdentifierWithSchema(null); + oldSchema = getSchema(); + } + } else { + if (readIf(UNIQUE)) { + unique = true; + } + if (readIf("HASH")) { + hash = true; + } else if (!unique && readIf("SPATIAL")) { + spatial = true; + } + read("INDEX"); + if (!isToken(ON)) { + ifNotExists = readIfNotExists(); + indexName = readIdentifierWithSchema(null); + oldSchema = getSchema(); + } + } + read(ON); + String tableName = readIdentifierWithSchema(); + checkSchema(oldSchema); + String comment = readCommentIf(); + if (!readIf(OPEN_PAREN)) { + // PostgreSQL compatibility + if (hash || spatial) { + throw getSyntaxError(); + } + read(USING); + if (readIf("BTREE")) { + // default + } else if (readIf("HASH")) { + hash = true; + } else { + read("RTREE"); + spatial = true; + } + read(OPEN_PAREN); + } + CreateIndex command = new CreateIndex(session, getSchema()); + command.setIfNotExists(ifNotExists); + command.setPrimaryKey(primaryKey); + command.setTableName(tableName); + command.setHash(hash); + command.setSpatial(spatial); + command.setIndexName(indexName); + command.setComment(comment); + IndexColumn[] columns; + int uniqueColumnCount = 0; + if (spatial) { + columns = new IndexColumn[] { new IndexColumn(readIdentifier()) }; + if (unique) { + uniqueColumnCount = 1; + } + read(CLOSE_PAREN); + } else { + columns = parseIndexColumnList(); + if (unique) { + uniqueColumnCount = columns.length; + if (readIf("INCLUDE")) { + read(OPEN_PAREN); + IndexColumn[] columnsToInclude = parseIndexColumnList(); + int nonUniqueCount = columnsToInclude.length; + columns = Arrays.copyOf(columns, uniqueColumnCount + nonUniqueCount); + System.arraycopy(columnsToInclude, 0, columns, uniqueColumnCount, nonUniqueCount); + } + } else if (primaryKey) { + uniqueColumnCount = columns.length; + } + } + command.setIndexColumns(columns); + command.setUniqueColumnCount(uniqueColumnCount); + return command; + } + } + + /** + * @return true if we expect to see a TABLE clause + */ + private boolean addRoleOrRight(GrantRevoke command) { + if (readIf(SELECT)) { + command.addRight(Right.SELECT); + return true; + } else if (readIf("DELETE")) { + command.addRight(Right.DELETE); + return true; + } else if (readIf("INSERT")) { + command.addRight(Right.INSERT); + return true; + } else if (readIf("UPDATE")) { + command.addRight(Right.UPDATE); + return true; + } else if (readIf("CONNECT")) { + // ignore this right + return true; + } else if (readIf("RESOURCE")) { + // ignore this right + return true; + } else { + command.addRoleName(readIdentifier()); + return false; + } + } + + private GrantRevoke parseGrantRevoke(int operationType) { + GrantRevoke command = new GrantRevoke(session); + command.setOperationType(operationType); + boolean tableClauseExpected; + if (readIf(ALL)) { + readIf("PRIVILEGES"); + command.addRight(Right.ALL); + tableClauseExpected = true; + } else if (readIf("ALTER")) { + read(ANY); + read("SCHEMA"); + command.addRight(Right.ALTER_ANY_SCHEMA); + command.addTable(null); + tableClauseExpected = false; + } else { + tableClauseExpected = addRoleOrRight(command); + while (readIf(COMMA)) { + if (addRoleOrRight(command) != tableClauseExpected) { + throw DbException.get(ErrorCode.ROLES_AND_RIGHT_CANNOT_BE_MIXED); + } + } + } + if (tableClauseExpected) { + if (readIf(ON)) { + if (readIf("SCHEMA")) { + command.setSchema(database.getSchema(readIdentifier())); + } else { + readIf(TABLE); + do { + Table table = readTableOrView(); + command.addTable(table); + } while (readIf(COMMA)); + } + } + } + read(operationType == CommandInterface.GRANT ? TO : FROM); + command.setGranteeName(readIdentifier()); + return command; + } + + private TableValueConstructor parseValues() { + ArrayList> rows = Utils.newSmallArrayList(); + ArrayList row = parseValuesRow(Utils.newSmallArrayList()); + rows.add(row); + int columnCount = row.size(); + while (readIf(COMMA)) { + row = parseValuesRow(new ArrayList<>(columnCount)); + if (row.size() != columnCount) { + throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); + } + rows.add(row); + } + return new TableValueConstructor(session, rows); + } + + private ArrayList parseValuesRow(ArrayList row) { + if (readIf(ROW)) { + read(OPEN_PAREN); + } else if (!readIf(OPEN_PAREN)) { + row.add(readExpression()); + return row; + } + do { + row.add(readExpression()); + } while (readIfMore()); + return 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); + command.setTableFunction(readTableFunction(ArrayTableFunction.TABLE)); + return command; + default: + canBeFunction = false; + } + try { + command.setExpression(readExpression()); + } catch (DbException e) { + if (canBeFunction && e.getErrorCode() == ErrorCode.FUNCTION_NOT_FOUND_1) { + setTokenIndex(index); + String schemaName = null, name = readIdentifier(); + if (readIf(DOT)) { + schemaName = name; + name = readIdentifier(); + if (readIf(DOT)) { + checkDatabaseName(schemaName); + schemaName = name; + name = readIdentifier(); + } + } + read(OPEN_PAREN); + Schema schema = schemaName != null ? database.getSchema(schemaName) : null; + command.setTableFunction(readTableFunction(name, schema)); + return command; + } + throw e; + } + return command; + } + + private CreateRole parseCreateRole() { + CreateRole command = new CreateRole(session); + command.setIfNotExists(readIfNotExists()); + command.setRoleName(readIdentifier()); + return command; + } + + private CreateSchema parseCreateSchema() { + CreateSchema command = new CreateSchema(session); + command.setIfNotExists(readIfNotExists()); + String authorization; + if (readIf(AUTHORIZATION)) { + authorization = readIdentifier(); + command.setSchemaName(authorization); + command.setAuthorization(authorization); + } else { + command.setSchemaName(readIdentifier()); + if (readIf(AUTHORIZATION)) { + authorization = readIdentifier(); + } else { + authorization = session.getUser().getName(); + } + } + command.setAuthorization(authorization); + if (readIf(WITH)) { + command.setTableEngineParams(readTableEngineParams()); + } + return command; + } + + private ArrayList readTableEngineParams() { + ArrayList tableEngineParams = Utils.newSmallArrayList(); + do { + tableEngineParams.add(readIdentifier()); + } while (readIf(COMMA)); + return tableEngineParams; + } + + private CreateSequence parseCreateSequence() { + boolean ifNotExists = readIfNotExists(); + String sequenceName = readIdentifierWithSchema(); + CreateSequence command = new CreateSequence(session, getSchema()); + command.setIfNotExists(ifNotExists); + command.setSequenceName(sequenceName); + SequenceOptions options = new SequenceOptions(); + parseSequenceOptions(options, command, true, false); + command.setOptions(options); + return command; + } + + private boolean readIfNotExists() { + if (readIf(IF)) { + read(NOT); + read(EXISTS); + return true; + } + return false; + } + + private CreateConstant parseCreateConstant() { + boolean ifNotExists = readIfNotExists(); + String constantName = readIdentifierWithSchema(); + Schema schema = getSchema(); + if (isKeyword(constantName)) { + throw DbException.get(ErrorCode.CONSTANT_ALREADY_EXISTS_1, + constantName); + } + read(VALUE); + Expression expr = readExpression(); + CreateConstant command = new CreateConstant(session, schema); + command.setConstantName(constantName); + command.setExpression(expr); + command.setIfNotExists(ifNotExists); + return command; + } + + private CreateAggregate parseCreateAggregate(boolean force) { + boolean ifNotExists = readIfNotExists(); + String name = readIdentifierWithSchema(), upperName; + if (isKeyword(name) || BuiltinFunctions.isBuiltinFunction(database, upperName = upperName(name)) + || Aggregate.getAggregateType(upperName) != null) { + throw DbException.get(ErrorCode.FUNCTION_ALIAS_ALREADY_EXISTS_1, name); + } + CreateAggregate command = new CreateAggregate(session, getSchema()); + command.setForce(force); + command.setName(name); + command.setIfNotExists(ifNotExists); + read(FOR); + command.setJavaClassMethod(readStringOrIdentifier()); + return command; + } + + private CreateDomain parseCreateDomain() { + boolean ifNotExists = readIfNotExists(); + String domainName = readIdentifierWithSchema(); + Schema schema = getSchema(); + CreateDomain command = new CreateDomain(session, schema); + command.setIfNotExists(ifNotExists); + command.setTypeName(domainName); + readIf(AS); + TypeInfo dataType = readIfDataType(); + if (dataType != null) { + command.setDataType(dataType); + } else { + String parentDomainName = readIdentifierWithSchema(); + command.setParentDomain(getSchema().getDomain(parentDomainName)); + } + if (readIf(DEFAULT)) { + command.setDefaultExpression(readExpression()); + } + if (readIf(ON)) { + read("UPDATE"); + command.setOnUpdateExpression(readExpression()); + } + // Compatibility with 1.4.200 and older versions + if (readIf("SELECTIVITY")) { + readNonNegativeInt(); + } + String comment = readCommentIf(); + if (comment != null) { + command.setComment(comment); + } + for (;;) { + String constraintName; + if (readIf(CONSTRAINT)) { + constraintName = readIdentifier(); + read(CHECK); + } else if (readIf(CHECK)) { + constraintName = null; + } else { + break; + } + AlterDomainAddConstraint constraint = new AlterDomainAddConstraint(session, schema, ifNotExists); + constraint.setConstraintName(constraintName); + constraint.setDomainName(domainName); + parseDomainConstraint = true; + try { + constraint.setCheckExpression(readExpression()); + } finally { + parseDomainConstraint = false; + } + command.addConstraintCommand(constraint); + } + return command; + } + + private CreateTrigger parseCreateTrigger(boolean force) { + boolean ifNotExists = readIfNotExists(); + String triggerName = readIdentifierWithSchema(null); + Schema schema = getSchema(); + boolean insteadOf, isBefore; + if (readIf("INSTEAD")) { + read("OF"); + isBefore = true; + insteadOf = true; + } else if (readIf("BEFORE")) { + insteadOf = false; + isBefore = true; + } else { + read("AFTER"); + insteadOf = false; + isBefore = false; + } + int typeMask = 0; + boolean onRollback = false; + boolean allowOr = database.getMode().getEnum() == ModeEnum.PostgreSQL; + do { + if (readIf("INSERT")) { + typeMask |= Trigger.INSERT; + } else if (readIf("UPDATE")) { + typeMask |= Trigger.UPDATE; + } else if (readIf("DELETE")) { + typeMask |= Trigger.DELETE; + } else if (readIf(SELECT)) { + typeMask |= Trigger.SELECT; + } else if (readIf("ROLLBACK")) { + onRollback = true; + } else { + throw getSyntaxError(); + } + } while (readIf(COMMA) || allowOr && readIf(OR)); + read(ON); + String tableName = readIdentifierWithSchema(); + checkSchema(schema); + CreateTrigger command = new CreateTrigger(session, getSchema()); + command.setForce(force); + command.setTriggerName(triggerName); + command.setIfNotExists(ifNotExists); + command.setInsteadOf(insteadOf); + command.setBefore(isBefore); + command.setOnRollback(onRollback); + command.setTypeMask(typeMask); + command.setTableName(tableName); + if (readIf(FOR)) { + read("EACH"); + if (readIf(ROW)) { + command.setRowBased(true); + } else { + read("STATEMENT"); + } + } + if (readIf("QUEUE")) { + command.setQueueSize(readNonNegativeInt()); + } + command.setNoWait(readIf("NOWAIT")); + if (readIf(AS)) { + command.setTriggerSource(readString()); + } else { + read("CALL"); + command.setTriggerClassName(readStringOrIdentifier()); + } + return command; + } + + private CreateUser parseCreateUser() { + CreateUser command = new CreateUser(session); + command.setIfNotExists(readIfNotExists()); + command.setUserName(readIdentifier()); + command.setComment(readCommentIf()); + if (readIf("PASSWORD")) { + command.setPassword(readExpression()); + } else if (readIf("SALT")) { + command.setSalt(readExpression()); + read("HASH"); + command.setHash(readExpression()); + } else if (readIf("IDENTIFIED")) { + read("BY"); + // uppercase if not quoted + command.setPassword(ValueExpression.get(ValueVarchar.get(readIdentifier()))); + } else { + throw getSyntaxError(); + } + if (readIf("ADMIN")) { + command.setAdmin(true); + } + return command; + } + + private CreateFunctionAlias parseCreateFunctionAlias(boolean force) { + boolean ifNotExists = readIfNotExists(); + String aliasName; + if (currentTokenType != IDENTIFIER) { + aliasName = currentToken; + read(); + schemaName = session.getCurrentSchemaName(); + } else { + aliasName = readIdentifierWithSchema(); + } + String upperName = upperName(aliasName); + if (isReservedFunctionName(upperName)) { + throw DbException.get(ErrorCode.FUNCTION_ALIAS_ALREADY_EXISTS_1, aliasName); + } + CreateFunctionAlias command = new CreateFunctionAlias(session, getSchema()); + command.setForce(force); + command.setAliasName(aliasName); + command.setIfNotExists(ifNotExists); + command.setDeterministic(readIf("DETERMINISTIC")); + // Compatibility with old versions of H2 + readIf("NOBUFFER"); + if (readIf(AS)) { + command.setSource(readString()); + } else { + read(FOR); + command.setJavaClassMethod(readStringOrIdentifier()); + } + return command; + } + + private String readStringOrIdentifier() { + return currentTokenType != IDENTIFIER ? readString() : readIdentifier(); + } + + private boolean isReservedFunctionName(String name) { + int tokenType = ParserUtil.getTokenType(name, false, false); + if (tokenType != ParserUtil.IDENTIFIER) { + if (database.isAllowBuiltinAliasOverride()) { + switch (tokenType) { + case CURRENT_DATE: + case CURRENT_TIME: + case CURRENT_TIMESTAMP: + case DAY: + case HOUR: + case LOCALTIME: + case LOCALTIMESTAMP: + case MINUTE: + case MONTH: + case SECOND: + case YEAR: + return false; + } + } + return true; + } + return Aggregate.getAggregateType(name) != null + || BuiltinFunctions.isBuiltinFunction(database, name) && !database.isAllowBuiltinAliasOverride(); + } + + private Prepared parseWith() { + List viewsCreated = new ArrayList<>(); + try { + return parseWith1(viewsCreated); + } catch (Throwable t) { + CommandContainer.clearCTE(session, viewsCreated); + throw t; + } + } + + private Prepared parseWith1(List viewsCreated) { + readIf("RECURSIVE"); + + // This WITH statement is not a temporary view - it is part of a persistent view + // as in CREATE VIEW abc AS WITH my_cte - this auto detects that condition. + final boolean isTemporary = !session.isParsingCreateView(); + + do { + viewsCreated.add(parseSingleCommonTableExpression(isTemporary)); + } while (readIf(COMMA)); + + Prepared p; + // Reverse the order of constructed CTE views - as the destruction order + // (since later created view may depend on previously created views - + // we preserve that dependency order in the destruction sequence ) + // used in setCteCleanups. + Collections.reverse(viewsCreated); + + int start = tokenIndex; + if (isQueryQuick()) { + p = parseWithQuery(); + } else if (readIf("INSERT")) { + p = parseInsert(start); + p.setPrepareAlways(true); + } else if (readIf("UPDATE")) { + p = parseUpdate(start); + p.setPrepareAlways(true); + } else if (readIf("MERGE")) { + p = parseMerge(start); + p.setPrepareAlways(true); + } else if (readIf("DELETE")) { + p = parseDelete(start); + p.setPrepareAlways(true); + } else if (readIf("CREATE")) { + if (!isToken(TABLE)) { + throw DbException.get(ErrorCode.SYNTAX_ERROR_1, + WITH_STATEMENT_SUPPORTS_LIMITED_SUB_STATEMENTS); + } + p = parseCreate(); + p.setPrepareAlways(true); + } else { + throw DbException.get(ErrorCode.SYNTAX_ERROR_1, + WITH_STATEMENT_SUPPORTS_LIMITED_SUB_STATEMENTS); + } + + // Clean up temporary views starting with last to first (in case of + // dependencies) - but only if they are not persistent. + if (isTemporary) { + if (cteCleanups == null) { + cteCleanups = new ArrayList<>(viewsCreated.size()); + } + cteCleanups.addAll(viewsCreated); + } + return p; + } + + private Prepared parseWithQuery() { + Query query = parseQueryExpressionBodyAndEndOfQuery(); + query.setPrepareAlways(true); + query.setNeverLazy(true); + return query; + } + + private TableView parseSingleCommonTableExpression(boolean isTemporary) { + String cteViewName = readIdentifierWithSchema(); + Schema schema = getSchema(); + ArrayList columns = Utils.newSmallArrayList(); + String[] cols = null; + + // column names are now optional - they can be inferred from the named + // query, if not supplied by user + if (readIf(OPEN_PAREN)) { + cols = parseColumnList(); + for (String c : cols) { + // we don't really know the type of the column, so STRING will + // have to do, UNKNOWN does not work here + columns.add(new Column(c, TypeInfo.TYPE_VARCHAR)); + } + } + + Table oldViewFound; + if (!isTemporary) { + oldViewFound = getSchema().findTableOrView(session, cteViewName); + } else { + oldViewFound = session.findLocalTempTable(cteViewName); + } + // this persistent check conflicts with check 10 lines down + if (oldViewFound != null) { + if (!(oldViewFound instanceof TableView)) { + throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, + cteViewName); + } + TableView tv = (TableView) oldViewFound; + if (!tv.isTableExpression()) { + throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, + cteViewName); + } + if (!isTemporary) { + oldViewFound.lock(session, Table.EXCLUSIVE_LOCK); + database.removeSchemaObject(session, oldViewFound); + + } else { + session.removeLocalTempTable(oldViewFound); + } + } + /* + * This table is created as a workaround because recursive table + * expressions need to reference something that look like themselves to + * work (its removed after creation in this method). Only create table + * data and table if we don't have a working CTE already. + */ + Table recursiveTable = TableView.createShadowTableForRecursiveTableExpression( + isTemporary, session, cteViewName, schema, columns, database); + List columnTemplateList; + String[] querySQLOutput = new String[1]; + try { + read(AS); + read(OPEN_PAREN); + Query withQuery = parseQuery(); + if (!isTemporary) { + withQuery.session = session; + } + read(CLOSE_PAREN); + columnTemplateList = QueryExpressionTable.createQueryColumnTemplateList(cols, withQuery, querySQLOutput); + + } finally { + TableView.destroyShadowTableForRecursiveExpression(isTemporary, session, recursiveTable); + } + + return createCTEView(cteViewName, + querySQLOutput[0], columnTemplateList, + true/* allowRecursiveQueryDetection */, + true/* add to session */, + isTemporary); + } + + private TableView createCTEView(String cteViewName, String querySQL, + List columnTemplateList, boolean allowRecursiveQueryDetection, + boolean addViewToSession, boolean isTemporary) { + Schema schema = getSchemaWithDefault(); + int id = database.allocateObjectId(); + Column[] columnTemplateArray = columnTemplateList.toArray(new Column[0]); + + // No easy way to determine if this is a recursive query up front, so we just compile + // it twice - once without the flag set, and if we didn't see a recursive term, + // then we just compile it again. + TableView view; + synchronized (session) { + view = new TableView(schema, id, cteViewName, querySQL, + parameters, columnTemplateArray, session, + allowRecursiveQueryDetection, false, true, + isTemporary); + if (!view.isRecursiveQueryDetected() && allowRecursiveQueryDetection) { + if (!isTemporary) { + database.addSchemaObject(session, view); + view.lock(session, Table.EXCLUSIVE_LOCK); + database.removeSchemaObject(session, view); + } else { + session.removeLocalTempTable(view); + } + view = new TableView(schema, id, cteViewName, querySQL, parameters, + columnTemplateArray, session, + false/* assume recursive */, false, true, + isTemporary); + } + // both removeSchemaObject and removeLocalTempTable hold meta locks + database.unlockMeta(session); + } + view.setTableExpression(true); + view.setTemporary(isTemporary); + view.setHidden(true); + view.setOnCommitDrop(false); + if (addViewToSession) { + if (!isTemporary) { + database.addSchemaObject(session, view); + view.unlock(session); + database.unlockMeta(session); + } else { + session.addLocalTempTable(view); + } + } + return view; + } + + private CreateView parseCreateView(boolean force, boolean orReplace) { + boolean ifNotExists = readIfNotExists(); + boolean isTableExpression = readIf("TABLE_EXPRESSION"); + String viewName = readIdentifierWithSchema(); + CreateView command = new CreateView(session, getSchema()); + this.createView = command; + command.setViewName(viewName); + command.setIfNotExists(ifNotExists); + command.setComment(readCommentIf()); + command.setOrReplace(orReplace); + command.setForce(force); + command.setTableExpression(isTableExpression); + if (readIf(OPEN_PAREN)) { + String[] cols = parseColumnList(); + command.setColumnNames(cols); + } + read(AS); + String select = StringUtils.cache(sqlCommand.substring(token.start())); + try { + Query query; + session.setParsingCreateView(true); + try { + query = parseQuery(); + query.prepare(); + } finally { + session.setParsingCreateView(false); + } + command.setSelect(query); + } catch (DbException e) { + if (force) { + command.setSelectSQL(select); + while (currentTokenType != END_OF_INPUT) { + read(); + } + } else { + throw e; + } + } + return command; + } + + private TransactionCommand parseCheckpoint() { + TransactionCommand command; + if (readIf("SYNC")) { + command = new TransactionCommand(session, + CommandInterface.CHECKPOINT_SYNC); + } else { + command = new TransactionCommand(session, + CommandInterface.CHECKPOINT); + } + return command; + } + + private Prepared parseAlter() { + if (readIf(TABLE)) { + return parseAlterTable(); + } else if (readIf(USER)) { + return parseAlterUser(); + } else if (readIf("INDEX")) { + return parseAlterIndex(); + } else if (readIf("SCHEMA")) { + return parseAlterSchema(); + } else if (readIf("SEQUENCE")) { + return parseAlterSequence(); + } else if (readIf("VIEW")) { + return parseAlterView(); + } else if (readIf("DOMAIN")) { + return parseAlterDomain(); + } + throw getSyntaxError(); + } + + private void checkSchema(Schema old) { + if (old != null && getSchema() != old) { + throw DbException.get(ErrorCode.SCHEMA_NAME_MUST_MATCH); + } + } + + private AlterIndexRename parseAlterIndex() { + boolean ifExists = readIfExists(false); + String indexName = readIdentifierWithSchema(); + Schema old = getSchema(); + AlterIndexRename command = new AlterIndexRename(session); + command.setOldSchema(old); + command.setOldName(indexName); + command.setIfExists(ifExists); + read("RENAME"); + read(TO); + String newName = readIdentifierWithSchema(old.getName()); + checkSchema(old); + command.setNewName(newName); + return command; + } + + private DefineCommand parseAlterDomain() { + boolean ifDomainExists = readIfExists(false); + String domainName = readIdentifierWithSchema(); + Schema schema = getSchema(); + if (readIf("ADD")) { + boolean ifNotExists = false; + String constraintName = null; + String comment = null; + if (readIf(CONSTRAINT)) { + ifNotExists = readIfNotExists(); + constraintName = readIdentifierWithSchema(schema.getName()); + checkSchema(schema); + comment = readCommentIf(); + } + read(CHECK); + AlterDomainAddConstraint command = new AlterDomainAddConstraint(session, schema, ifNotExists); + command.setDomainName(domainName); + command.setConstraintName(constraintName); + parseDomainConstraint = true; + try { + command.setCheckExpression(readExpression()); + } finally { + parseDomainConstraint = false; + } + command.setIfDomainExists(ifDomainExists); + command.setComment(comment); + if (readIf("NOCHECK")) { + command.setCheckExisting(false); + } else { + readIf(CHECK); + command.setCheckExisting(true); + } + return command; + } else if (readIf("DROP")) { + if (readIf(CONSTRAINT)) { + boolean ifConstraintExists = readIfExists(false); + String constraintName = readIdentifierWithSchema(schema.getName()); + checkSchema(schema); + AlterDomainDropConstraint command = new AlterDomainDropConstraint(session, getSchema(), + ifConstraintExists); + command.setConstraintName(constraintName); + command.setDomainName(domainName); + command.setIfDomainExists(ifDomainExists); + return command; + } else if (readIf(DEFAULT)) { + AlterDomainExpressions command = new AlterDomainExpressions(session, schema, + CommandInterface.ALTER_DOMAIN_DEFAULT); + command.setDomainName(domainName); + command.setIfDomainExists(ifDomainExists); + command.setExpression(null); + return command; + } else if (readIf(ON)) { + read("UPDATE"); + AlterDomainExpressions command = new AlterDomainExpressions(session, schema, + CommandInterface.ALTER_DOMAIN_ON_UPDATE); + command.setDomainName(domainName); + command.setIfDomainExists(ifDomainExists); + command.setExpression(null); + return command; + } + } else if (readIf("RENAME")) { + if (readIf(CONSTRAINT)) { + String constraintName = readIdentifierWithSchema(schema.getName()); + checkSchema(schema); + read(TO); + AlterDomainRenameConstraint command = new AlterDomainRenameConstraint(session, schema); + command.setDomainName(domainName); + command.setIfDomainExists(ifDomainExists); + command.setConstraintName(constraintName); + command.setNewConstraintName(readIdentifier()); + return command; + } + read(TO); + String newName = readIdentifierWithSchema(schema.getName()); + checkSchema(schema); + AlterDomainRename command = new AlterDomainRename(session, getSchema()); + command.setDomainName(domainName); + command.setIfDomainExists(ifDomainExists); + command.setNewDomainName(newName); + return command; + } else { + read(SET); + if (readIf(DEFAULT)) { + AlterDomainExpressions command = new AlterDomainExpressions(session, schema, + CommandInterface.ALTER_DOMAIN_DEFAULT); + command.setDomainName(domainName); + command.setIfDomainExists(ifDomainExists); + command.setExpression(readExpression()); + return command; + } else if (readIf(ON)) { + read("UPDATE"); + AlterDomainExpressions command = new AlterDomainExpressions(session, schema, + CommandInterface.ALTER_DOMAIN_ON_UPDATE); + command.setDomainName(domainName); + command.setIfDomainExists(ifDomainExists); + command.setExpression(readExpression()); + return command; + } + } + throw getSyntaxError(); + } + + private DefineCommand parseAlterView() { + boolean ifExists = readIfExists(false); + String viewName = readIdentifierWithSchema(); + Schema schema = getSchema(); + Table tableView = schema.findTableOrView(session, viewName); + if (!(tableView instanceof TableView) && !ifExists) { + throw DbException.get(ErrorCode.VIEW_NOT_FOUND_1, viewName); + } + if (readIf("RENAME")) { + read(TO); + String newName = readIdentifierWithSchema(schema.getName()); + checkSchema(schema); + AlterTableRename command = new AlterTableRename(session, getSchema()); + command.setTableName(viewName); + command.setNewTableName(newName); + command.setIfTableExists(ifExists); + return command; + } else { + read("RECOMPILE"); + TableView view = (TableView) tableView; + AlterView command = new AlterView(session); + command.setIfExists(ifExists); + command.setView(view); + return command; + } + } + + private Prepared parseAlterSchema() { + boolean ifExists = readIfExists(false); + String schemaName = readIdentifierWithSchema(); + Schema old = getSchema(); + read("RENAME"); + read(TO); + String newName = readIdentifierWithSchema(old.getName()); + Schema schema = findSchema(schemaName); + if (schema == null) { + if (ifExists) { + return new NoOperation(session); + } + throw DbException.get(ErrorCode.SCHEMA_NOT_FOUND_1, schemaName); + } + AlterSchemaRename command = new AlterSchemaRename(session); + command.setOldSchema(schema); + checkSchema(old); + command.setNewName(newName); + return command; + } + + private AlterSequence parseAlterSequence() { + boolean ifExists = readIfExists(false); + String sequenceName = readIdentifierWithSchema(); + AlterSequence command = new AlterSequence(session, getSchema()); + command.setSequenceName(sequenceName); + command.setIfExists(ifExists); + SequenceOptions options = new SequenceOptions(); + parseSequenceOptions(options, null, false, false); + command.setOptions(options); + return command; + } + + private boolean parseSequenceOptions(SequenceOptions options, CreateSequence command, boolean allowDataType, + boolean forAlterColumn) { + boolean result = false; + for (;;) { + if (allowDataType && readIf(AS)) { + TypeInfo dataType = parseDataType(); + if (!DataType.isNumericType(dataType.getValueType())) { + throw DbException.getUnsupportedException(dataType + .getSQL(new StringBuilder("CREATE SEQUENCE AS "), HasSQL.TRACE_SQL_FLAGS).toString()); + } + options.setDataType(dataType); + } else if (readIf("START")) { + read(WITH); + options.setStartValue(readExpression()); + } else if (readIf("RESTART")) { + options.setRestartValue(readIf(WITH) ? readExpression() : ValueExpression.DEFAULT); + } else if (command != null && parseCreateSequenceOption(command)) { + // + } else if (forAlterColumn) { + int index = tokenIndex; + if (readIf(SET)) { + if (!parseBasicSequenceOption(options)) { + setTokenIndex(index); + break; + } + } else { + break; + } + } else if (!parseBasicSequenceOption(options)) { + break; + } + result = true; + } + return result; + } + + private boolean parseCreateSequenceOption(CreateSequence command) { + if (readIf("BELONGS_TO_TABLE")) { + command.setBelongsToTable(true); + } else if (readIf(ORDER)) { + // Oracle compatibility + } else { + return false; + } + return true; + } + + private boolean parseBasicSequenceOption(SequenceOptions options) { + if (readIf("INCREMENT")) { + readIf("BY"); + options.setIncrement(readExpression()); + } else if (readIf("MINVALUE")) { + options.setMinValue(readExpression()); + } else if (readIf("MAXVALUE")) { + options.setMaxValue(readExpression()); + } else if (readIf("CYCLE")) { + options.setCycle(Sequence.Cycle.CYCLE); + } else if (readIf("NO")) { + if (readIf("MINVALUE")) { + options.setMinValue(ValueExpression.NULL); + } else if (readIf("MAXVALUE")) { + options.setMaxValue(ValueExpression.NULL); + } else if (readIf("CYCLE")) { + options.setCycle(Sequence.Cycle.NO_CYCLE); + } else if (readIf("CACHE")) { + options.setCacheSize(ValueExpression.get(ValueBigint.get(1))); + } else { + throw getSyntaxError(); + } + } else if (readIf("EXHAUSTED")) { + options.setCycle(Sequence.Cycle.EXHAUSTED); + } else if (readIf("CACHE")) { + options.setCacheSize(readExpression()); + // Various compatibility options + } else if (readIf("NOMINVALUE")) { + options.setMinValue(ValueExpression.NULL); + } else if (readIf("NOMAXVALUE")) { + options.setMaxValue(ValueExpression.NULL); + } else if (readIf("NOCYCLE")) { + options.setCycle(Sequence.Cycle.NO_CYCLE); + } else if (readIf("NOCACHE")) { + options.setCacheSize(ValueExpression.get(ValueBigint.get(1))); + } else { + return false; + } + return true; + } + + private AlterUser parseAlterUser() { + String userName = readIdentifier(); + if (readIf(SET)) { + AlterUser command = new AlterUser(session); + command.setType(CommandInterface.ALTER_USER_SET_PASSWORD); + command.setUser(database.getUser(userName)); + if (readIf("PASSWORD")) { + command.setPassword(readExpression()); + } else if (readIf("SALT")) { + command.setSalt(readExpression()); + read("HASH"); + command.setHash(readExpression()); + } else { + throw getSyntaxError(); + } + return command; + } else if (readIf("RENAME")) { + read(TO); + AlterUser command = new AlterUser(session); + command.setType(CommandInterface.ALTER_USER_RENAME); + command.setUser(database.getUser(userName)); + command.setNewName(readIdentifier()); + return command; + } else if (readIf("ADMIN")) { + AlterUser command = new AlterUser(session); + command.setType(CommandInterface.ALTER_USER_ADMIN); + User user = database.getUser(userName); + command.setUser(user); + if (readIf(TRUE)) { + command.setAdmin(true); + } else if (readIf(FALSE)) { + command.setAdmin(false); + } else { + throw getSyntaxError(); + } + return command; + } + throw getSyntaxError(); + } + + private void readIfEqualOrTo() { + if (!readIf(EQUAL)) { + readIf(TO); + } + } + + private Prepared parseSet() { + if (readIf(AT)) { + Set command = new Set(session, SetTypes.VARIABLE); + command.setString(readIdentifier()); + readIfEqualOrTo(); + command.setExpression(readExpression()); + return command; + } else if (readIf("AUTOCOMMIT")) { + readIfEqualOrTo(); + return new TransactionCommand(session, readBooleanSetting() ? CommandInterface.SET_AUTOCOMMIT_TRUE + : CommandInterface.SET_AUTOCOMMIT_FALSE); + } else if (readIf("EXCLUSIVE")) { + readIfEqualOrTo(); + Set command = new Set(session, SetTypes.EXCLUSIVE); + command.setExpression(readExpression()); + return command; + } else if (readIf("IGNORECASE")) { + readIfEqualOrTo(); + Set command = new Set(session, SetTypes.IGNORECASE); + command.setInt(readBooleanSetting() ? 1 : 0); + return command; + } else if (readIf("PASSWORD")) { + readIfEqualOrTo(); + AlterUser command = new AlterUser(session); + command.setType(CommandInterface.ALTER_USER_SET_PASSWORD); + command.setUser(session.getUser()); + command.setPassword(readExpression()); + return command; + } else if (readIf("SALT")) { + readIfEqualOrTo(); + AlterUser command = new AlterUser(session); + command.setType(CommandInterface.ALTER_USER_SET_PASSWORD); + command.setUser(session.getUser()); + command.setSalt(readExpression()); + read("HASH"); + command.setHash(readExpression()); + return command; + } else if (readIf("MODE")) { + readIfEqualOrTo(); + Set command = new Set(session, SetTypes.MODE); + command.setString(readIdentifier()); + return command; + } else if (readIf("DATABASE")) { + readIfEqualOrTo(); + read("COLLATION"); + return parseSetCollation(); + } else if (readIf("COLLATION")) { + readIfEqualOrTo(); + return parseSetCollation(); + } else if (readIf("CLUSTER")) { + readIfEqualOrTo(); + Set command = new Set(session, SetTypes.CLUSTER); + command.setString(readString()); + return command; + } else if (readIf("DATABASE_EVENT_LISTENER")) { + readIfEqualOrTo(); + Set command = new Set(session, SetTypes.DATABASE_EVENT_LISTENER); + command.setString(readString()); + return command; + } else if (readIf("ALLOW_LITERALS")) { + readIfEqualOrTo(); + Set command = new Set(session, SetTypes.ALLOW_LITERALS); + int v; + if (readIf(ALL)) { + v = Constants.ALLOW_LITERALS_ALL; + } else if (readIf("NONE")) { + v = Constants.ALLOW_LITERALS_NONE; + } else if (readIf("NUMBERS")) { + v = Constants.ALLOW_LITERALS_NUMBERS; + } else { + v = readNonNegativeInt(); + } + command.setInt(v); + return command; + } else if (readIf("DEFAULT_TABLE_TYPE")) { + readIfEqualOrTo(); + Set command = new Set(session, SetTypes.DEFAULT_TABLE_TYPE); + int v; + if (readIf("MEMORY")) { + v = Table.TYPE_MEMORY; + } else if (readIf("CACHED")) { + v = Table.TYPE_CACHED; + } else { + v = readNonNegativeInt(); + } + command.setInt(v); + return command; + } else if (readIf("SCHEMA")) { + readIfEqualOrTo(); + Set command = new Set(session, SetTypes.SCHEMA); + command.setExpression(readExpressionOrIdentifier()); + return command; + } else if (readIf("CATALOG")) { + readIfEqualOrTo(); + Set command = new Set(session, SetTypes.CATALOG); + command.setExpression(readExpressionOrIdentifier()); + return command; + } else if (readIf(SetTypes.getTypeName(SetTypes.SCHEMA_SEARCH_PATH))) { + readIfEqualOrTo(); + Set command = new Set(session, SetTypes.SCHEMA_SEARCH_PATH); + ArrayList list = Utils.newSmallArrayList(); + do { + list.add(readIdentifier()); + } while (readIf(COMMA)); + command.setStringArray(list.toArray(new String[0])); + return command; + } else if (readIf("JAVA_OBJECT_SERIALIZER")) { + readIfEqualOrTo(); + Set command = new Set(session, SetTypes.JAVA_OBJECT_SERIALIZER); + command.setString(readString()); + return command; + } else if (readIf("IGNORE_CATALOGS")) { + readIfEqualOrTo(); + Set command = new Set(session, SetTypes.IGNORE_CATALOGS); + command.setInt(readBooleanSetting() ? 1 : 0); + return command; + } else if (readIf("SESSION")) { + read("CHARACTERISTICS"); + read(AS); + read("TRANSACTION"); + return parseSetTransactionMode(); + } else if (readIf("TRANSACTION")) { + // TODO should affect only the current transaction + return parseSetTransactionMode(); + } else if (readIf("TIME")) { + read("ZONE"); + Set command = new Set(session, SetTypes.TIME_ZONE); + if (!readIf("LOCAL")) { + command.setExpression(readExpression()); + } + return command; + } else if (readIf("NON_KEYWORDS")) { + readIfEqualOrTo(); + Set command = new Set(session, SetTypes.NON_KEYWORDS); + ArrayList list = Utils.newSmallArrayList(); + if (currentTokenType != END_OF_INPUT && currentTokenType != SEMICOLON) { + do { + if (currentTokenType < IDENTIFIER || currentTokenType > LAST_KEYWORD) { + throw getSyntaxError(); + } + list.add(StringUtils.toUpperEnglish(currentToken)); + read(); + } while (readIf(COMMA)); + } + command.setStringArray(list.toArray(new String[0])); + return command; + } else if (readIf("DEFAULT_NULL_ORDERING")) { + readIfEqualOrTo(); + Set command = new Set(session, SetTypes.DEFAULT_NULL_ORDERING); + command.setString(readIdentifier()); + return command; + } else if (readIf("LOG")) { + throw DbException.getUnsupportedException("LOG"); + } else { + String upperName = upperName(currentToken); + if (ConnectionInfo.isIgnoredByParser(upperName)) { + read(); + readIfEqualOrTo(); + read(); + return new NoOperation(session); + } + int type = SetTypes.getType(upperName); + if (type >= 0) { + read(); + readIfEqualOrTo(); + Set command = new Set(session, type); + command.setExpression(readExpression()); + return command; + } + ModeEnum modeEnum = database.getMode().getEnum(); + if (modeEnum != ModeEnum.REGULAR) { + Prepared command = readSetCompatibility(modeEnum); + if (command != null) { + return command; + } + } + if (session.isQuirksMode()) { + switch (upperName) { + case "BINARY_COLLATION": + case "UUID_COLLATION": + read(); + readIfEqualOrTo(); + readIdentifier(); + return new NoOperation(session); + } + } + throw getSyntaxError(); + } + } + + private Prepared parseSetTransactionMode() { + IsolationLevel isolationLevel; + read("ISOLATION"); + read("LEVEL"); + if (readIf("READ")) { + if (readIf("UNCOMMITTED")) { + isolationLevel = IsolationLevel.READ_UNCOMMITTED; + } else { + read("COMMITTED"); + isolationLevel = IsolationLevel.READ_COMMITTED; + } + } else if (readIf("REPEATABLE")) { + read("READ"); + isolationLevel = IsolationLevel.REPEATABLE_READ; + } else if (readIf("SNAPSHOT")) { + isolationLevel = IsolationLevel.SNAPSHOT; + } else { + read("SERIALIZABLE"); + isolationLevel = IsolationLevel.SERIALIZABLE; + } + return new SetSessionCharacteristics(session, isolationLevel); + } + + private Expression readExpressionOrIdentifier() { + if (isIdentifier()) { + return ValueExpression.get(ValueVarchar.get(readIdentifier())); + } + return readExpression(); + } + + private Prepared parseUse() { + readIfEqualOrTo(); + Set command = new Set(session, SetTypes.SCHEMA); + command.setExpression(ValueExpression.get(ValueVarchar.get(readIdentifier()))); + return command; + } + + private Set parseSetCollation() { + Set command = new Set(session, SetTypes.COLLATION); + String name = readIdentifier(); + command.setString(name); + if (equalsToken(name, CompareMode.OFF)) { + return command; + } + Collator coll = CompareMode.getCollator(name); + if (coll == null) { + throw DbException.getInvalidValueException("collation", name); + } + if (readIf("STRENGTH")) { + if (readIf(PRIMARY)) { + command.setInt(Collator.PRIMARY); + } else if (readIf("SECONDARY")) { + command.setInt(Collator.SECONDARY); + } else if (readIf("TERTIARY")) { + command.setInt(Collator.TERTIARY); + } else if (readIf("IDENTICAL")) { + command.setInt(Collator.IDENTICAL); + } + } else { + command.setInt(coll.getStrength()); + } + return command; + } + + private Prepared readSetCompatibility(ModeEnum modeEnum) { + switch (modeEnum) { + case Derby: + if (readIf("CREATE")) { + readIfEqualOrTo(); + // (CREATE=TRUE in the database URL) + read(); + return new NoOperation(session); + } + break; + case HSQLDB: + if (readIf("LOGSIZE")) { + readIfEqualOrTo(); + Set command = new Set(session, SetTypes.MAX_LOG_SIZE); + command.setExpression(readExpression()); + return command; + } + break; + case MySQL: + if (readIf("FOREIGN_KEY_CHECKS")) { + readIfEqualOrTo(); + Set command = new Set(session, SetTypes.REFERENTIAL_INTEGRITY); + command.setExpression(readExpression()); + return command; + } else if (readIf("NAMES")) { + // Quercus PHP MySQL driver compatibility + readIfEqualOrTo(); + read(); + return new NoOperation(session); + } + break; + case PostgreSQL: + if (readIf("STATEMENT_TIMEOUT")) { + readIfEqualOrTo(); + Set command = new Set(session, SetTypes.QUERY_TIMEOUT); + command.setInt(readNonNegativeInt()); + return command; + } else if (readIf("CLIENT_ENCODING") || readIf("CLIENT_MIN_MESSAGES") || readIf("JOIN_COLLAPSE_LIMIT")) { + readIfEqualOrTo(); + read(); + return new NoOperation(session); + } else if (readIf("DATESTYLE")) { + readIfEqualOrTo(); + if (!readIf("ISO")) { + String s = readString(); + if (!equalsToken(s, "ISO")) { + throw getSyntaxError(); + } + } + return new NoOperation(session); + } else if (readIf("SEARCH_PATH")) { + readIfEqualOrTo(); + Set command = new Set(session, SetTypes.SCHEMA_SEARCH_PATH); + ArrayList list = Utils.newSmallArrayList(); + String pgCatalog = database.sysIdentifier("PG_CATALOG"); + boolean hasPgCatalog = false; + do { + // some PG clients will send single-quoted alias + String s = currentTokenType == LITERAL ? readString() : readIdentifier(); + if ("$user".equals(s)) { + continue; + } + if (pgCatalog.equals(s)) { + hasPgCatalog = true; + } + list.add(s); + } while (readIf(COMMA)); + // If "pg_catalog" is not in the path then it will be searched before + // searching any of the path items. See + // https://www.postgresql.org/docs/8.2/runtime-config-client.html + if (!hasPgCatalog) { + if (database.findSchema(pgCatalog) != null) { + list.add(0, pgCatalog); + } + } + command.setStringArray(list.toArray(new String[0])); + return command; + } + break; + default: + } + return null; + } + + private RunScriptCommand parseRunScript() { + RunScriptCommand command = new RunScriptCommand(session); + read(FROM); + command.setFileNameExpr(readExpression()); + if (readIf("COMPRESSION")) { + command.setCompressionAlgorithm(readIdentifier()); + } + if (readIf("CIPHER")) { + command.setCipher(readIdentifier()); + if (readIf("PASSWORD")) { + command.setPassword(readExpression()); + } + } + if (readIf("CHARSET")) { + command.setCharset(Charset.forName(readString())); + } + if (readIf("FROM_1X")) { + command.setFrom1X(); + } else { + if (readIf("QUIRKS_MODE")) { + command.setQuirksMode(true); + } + if (readIf("VARIABLE_BINARY")) { + command.setVariableBinary(true); + } + } + return command; + } + + private ScriptCommand parseScript() { + ScriptCommand command = new ScriptCommand(session); + boolean data = true, passwords = true, settings = true, version = true; + boolean dropTables = false, simple = false, withColumns = false; + if (readIf("NODATA")) { + data = false; + } else { + if (readIf("SIMPLE")) { + simple = true; + } + if (readIf("COLUMNS")) { + withColumns = true; + } + } + if (readIf("NOPASSWORDS")) { + passwords = false; + } + if (readIf("NOSETTINGS")) { + settings = false; + } + if (readIf("NOVERSION")) { + version = false; + } + if (readIf("DROP")) { + dropTables = true; + } + if (readIf("BLOCKSIZE")) { + long blockSize = readLong(); + command.setLobBlockSize(blockSize); + } + command.setData(data); + command.setPasswords(passwords); + command.setSettings(settings); + command.setVersion(version); + command.setDrop(dropTables); + command.setSimple(simple); + command.setWithColumns(withColumns); + if (readIf(TO)) { + command.setFileNameExpr(readExpression()); + if (readIf("COMPRESSION")) { + command.setCompressionAlgorithm(readIdentifier()); + } + if (readIf("CIPHER")) { + command.setCipher(readIdentifier()); + if (readIf("PASSWORD")) { + command.setPassword(readExpression()); + } + } + if (readIf("CHARSET")) { + command.setCharset(Charset.forName(readString())); + } + } + if (readIf("SCHEMA")) { + HashSet schemaNames = new HashSet<>(); + do { + schemaNames.add(readIdentifier()); + } while (readIf(COMMA)); + command.setSchemaNames(schemaNames); + } else if (readIf(TABLE)) { + ArrayList tables = Utils.newSmallArrayList(); + do { + tables.add(readTableOrView()); + } while (readIf(COMMA)); + command.setTables(tables); + } + return command; + } + + /** + * Is this the Oracle DUAL table or the IBM/DB2 SYSIBM table? + * + * @param tableName table name. + * @return {@code true} if the table is DUAL special table. Otherwise returns {@code false}. + * @see Wikipedia: DUAL table + */ + private boolean isDualTable(String tableName) { + return ((schemaName == null || equalsToken(schemaName, "SYS")) && equalsToken("DUAL", tableName)) + || (database.getMode().sysDummy1 && (schemaName == null || equalsToken(schemaName, "SYSIBM")) + && equalsToken("SYSDUMMY1", tableName)); + } + + private Table readTableOrView() { + return readTableOrView(readIdentifierWithSchema(null)); + } + + private Table readTableOrView(String tableName) { + if (schemaName != null) { + Table table = getSchema().resolveTableOrView(session, tableName); + if (table != null) { + return table; + } + } else { + Table table = database.getSchema(session.getCurrentSchemaName()) + .resolveTableOrView(session, tableName); + if (table != null) { + return table; + } + String[] schemaNames = session.getSchemaSearchPath(); + if (schemaNames != null) { + for (String name : schemaNames) { + Schema s = database.getSchema(name); + table = s.resolveTableOrView(session, tableName); + if (table != null) { + return table; + } + } + } + } + if (isDualTable(tableName)) { + return new DualTable(database); + } + + throw getTableOrViewNotFoundDbException(tableName); + } + + private DbException getTableOrViewNotFoundDbException(String tableName) { + if (schemaName != null) { + return getTableOrViewNotFoundDbException(schemaName, tableName); + } + + String currentSchemaName = session.getCurrentSchemaName(); + String[] schemaSearchPath = session.getSchemaSearchPath(); + if (schemaSearchPath == null) { + return getTableOrViewNotFoundDbException(Collections.singleton(currentSchemaName), tableName); + } + + LinkedHashSet schemaNames = new LinkedHashSet<>(); + schemaNames.add(currentSchemaName); + schemaNames.addAll(Arrays.asList(schemaSearchPath)); + return getTableOrViewNotFoundDbException(schemaNames, tableName); + } + + private DbException getTableOrViewNotFoundDbException(String schemaName, String tableName) { + return getTableOrViewNotFoundDbException(Collections.singleton(schemaName), tableName); + } + + private DbException getTableOrViewNotFoundDbException( + java.util.Set schemaNames, String tableName) { + if (database == null || database.getFirstUserTable() == null) { + return DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1, tableName); + } + + if (database.getSettings().caseInsensitiveIdentifiers) { + return DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, tableName); + } + + java.util.Set candidates = new TreeSet<>(); + for (String schemaName : schemaNames) { + findTableNameCandidates(schemaName, tableName, candidates); + } + + if (candidates.isEmpty()) { + return DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, tableName); + } + + return DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_WITH_CANDIDATES_2, + tableName, + String.join(", ", candidates)); + } + + private void findTableNameCandidates(String schemaName, String tableName, java.util.Set candidates) { + Schema schema = database.getSchema(schemaName); + String ucTableName = StringUtils.toUpperEnglish(tableName); + Collection
allTablesAndViews = schema.getAllTablesAndViews(session); + for (Table candidate : allTablesAndViews) { + String candidateName = candidate.getName(); + if (ucTableName.equals(StringUtils.toUpperEnglish(candidateName))) { + candidates.add(candidateName); + } + } + } + + private UserDefinedFunction findUserDefinedFunctionWithinPath(Schema schema, String name) { + if (schema != null) { + return schema.findFunctionOrAggregate(name); + } + schema = database.getSchema(session.getCurrentSchemaName()); + UserDefinedFunction userDefinedFunction = schema.findFunctionOrAggregate(name); + if (userDefinedFunction != null) { + return userDefinedFunction; + } + String[] schemaNames = session.getSchemaSearchPath(); + if (schemaNames != null) { + for (String schemaName : schemaNames) { + Schema schemaFromPath = database.getSchema(schemaName); + if (schemaFromPath != schema) { + userDefinedFunction = schemaFromPath.findFunctionOrAggregate(name); + if (userDefinedFunction != null) { + return userDefinedFunction; + } + } + } + } + return null; + } + + private Sequence findSequence(String schema, String sequenceName) { + Sequence sequence = database.getSchema(schema).findSequence( + sequenceName); + if (sequence != null) { + return sequence; + } + String[] schemaNames = session.getSchemaSearchPath(); + if (schemaNames != null) { + for (String n : schemaNames) { + sequence = database.getSchema(n).findSequence(sequenceName); + if (sequence != null) { + return sequence; + } + } + } + return null; + } + + private Sequence readSequence() { + // same algorithm as readTableOrView + String sequenceName = readIdentifierWithSchema(null); + if (schemaName != null) { + return getSchema().getSequence(sequenceName); + } + Sequence sequence = findSequence(session.getCurrentSchemaName(), + sequenceName); + if (sequence != null) { + return sequence; + } + throw DbException.get(ErrorCode.SEQUENCE_NOT_FOUND_1, sequenceName); + } + + private Prepared parseAlterTable() { + boolean ifTableExists = readIfExists(false); + String tableName = readIdentifierWithSchema(); + Schema schema = getSchema(); + if (readIf("ADD")) { + Prepared command = parseTableConstraintIf(tableName, schema, ifTableExists); + if (command != null) { + return command; + } + return parseAlterTableAddColumn(tableName, schema, ifTableExists); + } else if (readIf(SET)) { + return parseAlterTableSet(schema, tableName, ifTableExists); + } else if (readIf("RENAME")) { + return parseAlterTableRename(schema, tableName, ifTableExists); + } else if (readIf("DROP")) { + return parseAlterTableDrop(schema, tableName, ifTableExists); + } else if (readIf("ALTER")) { + return parseAlterTableAlter(schema, tableName, ifTableExists); + } else { + Mode mode = database.getMode(); + if (mode.alterTableExtensionsMySQL || mode.alterTableModifyColumn) { + return parseAlterTableCompatibility(schema, tableName, ifTableExists, mode); + } + } + throw getSyntaxError(); + } + + private Prepared parseAlterTableAlter(Schema schema, String tableName, boolean ifTableExists) { + readIf("COLUMN"); + boolean ifExists = readIfExists(false); + String columnName = readIdentifier(); + Column column = columnIfTableExists(schema, tableName, columnName, ifTableExists, ifExists); + if (readIf("RENAME")) { + read(TO); + AlterTableRenameColumn command = new AlterTableRenameColumn( + session, schema); + command.setTableName(tableName); + command.setIfTableExists(ifTableExists); + command.setIfExists(ifExists); + command.setOldColumnName(columnName); + String newName = readIdentifier(); + command.setNewColumnName(newName); + return command; + } else if (readIf("DROP")) { + if (readIf(DEFAULT)) { + if (readIf(ON)) { + read(NULL); + AlterTableAlterColumn command = new AlterTableAlterColumn(session, schema); + command.setTableName(tableName); + command.setIfTableExists(ifTableExists); + command.setOldColumn(column); + command.setType(CommandInterface.ALTER_TABLE_ALTER_COLUMN_DEFAULT_ON_NULL); + command.setBooleanFlag(false); + return command; + } + return getAlterTableAlterColumnDropDefaultExpression(schema, tableName, ifTableExists, column, + CommandInterface.ALTER_TABLE_ALTER_COLUMN_DEFAULT); + } else if (readIf("EXPRESSION")) { + return getAlterTableAlterColumnDropDefaultExpression(schema, tableName, ifTableExists, column, + CommandInterface.ALTER_TABLE_ALTER_COLUMN_DROP_EXPRESSION); + } else if (readIf("IDENTITY")) { + return getAlterTableAlterColumnDropDefaultExpression(schema, tableName, ifTableExists, column, + CommandInterface.ALTER_TABLE_ALTER_COLUMN_DROP_IDENTITY); + } + if (readIf(ON)) { + read("UPDATE"); + AlterTableAlterColumn command = new AlterTableAlterColumn(session, schema); + command.setTableName(tableName); + command.setIfTableExists(ifTableExists); + command.setOldColumn(column); + command.setType(CommandInterface.ALTER_TABLE_ALTER_COLUMN_ON_UPDATE); + command.setDefaultExpression(null); + return command; + } + read(NOT); + read(NULL); + AlterTableAlterColumn command = new AlterTableAlterColumn( + session, schema); + command.setTableName(tableName); + command.setIfTableExists(ifTableExists); + command.setOldColumn(column); + command.setType(CommandInterface.ALTER_TABLE_ALTER_COLUMN_DROP_NOT_NULL); + return command; + } else if (readIf("TYPE")) { + // PostgreSQL compatibility + return parseAlterTableAlterColumnDataType(schema, tableName, columnName, ifTableExists, ifExists); + } else if (readIf("SELECTIVITY")) { + AlterTableAlterColumn command = new AlterTableAlterColumn( + session, schema); + command.setTableName(tableName); + command.setIfTableExists(ifTableExists); + command.setType(CommandInterface.ALTER_TABLE_ALTER_COLUMN_SELECTIVITY); + command.setOldColumn(column); + command.setSelectivity(readExpression()); + return command; + } + Prepared command = parseAlterTableAlterColumnIdentity(schema, tableName, ifTableExists, column); + if (command != null) { + return command; + } + if (readIf(SET)) { + return parseAlterTableAlterColumnSet(schema, tableName, ifTableExists, ifExists, columnName, column); + } + return parseAlterTableAlterColumnType(schema, tableName, columnName, ifTableExists, ifExists, true); + } + + private Prepared getAlterTableAlterColumnDropDefaultExpression(Schema schema, String tableName, + boolean ifTableExists, Column column, int type) { + AlterTableAlterColumn command = new AlterTableAlterColumn(session, schema); + command.setTableName(tableName); + command.setIfTableExists(ifTableExists); + command.setOldColumn(column); + command.setType(type); + command.setDefaultExpression(null); + return command; + } + + private Prepared parseAlterTableAlterColumnIdentity(Schema schema, String tableName, boolean ifTableExists, + Column column) { + int index = tokenIndex; + Boolean always = null; + if (readIf(SET) && readIf("GENERATED")) { + if (readIf("ALWAYS")) { + always = true; + } else { + read("BY"); + read(DEFAULT); + always = false; + } + } else { + setTokenIndex(index); + } + SequenceOptions options = new SequenceOptions(); + if (!parseSequenceOptions(options, null, false, true) && always == null) { + return null; + } + if (column == null) { + return new NoOperation(session); + } + if (!column.isIdentity()) { + AlterTableAlterColumn command = new AlterTableAlterColumn(session, schema); + parseAlterColumnUsingIf(command); + command.setTableName(tableName); + command.setIfTableExists(ifTableExists); + command.setType(CommandInterface.ALTER_TABLE_ALTER_COLUMN_CHANGE_TYPE); + command.setOldColumn(column); + Column newColumn = column.getClone(); + newColumn.setIdentityOptions(options, always != null && always); + command.setNewColumn(newColumn); + return command; + } + AlterSequence command = new AlterSequence(session, schema); + command.setColumn(column, always); + command.setOptions(options); + return commandIfTableExists(schema, tableName, ifTableExists, command); + } + + private Prepared parseAlterTableAlterColumnSet(Schema schema, String tableName, boolean ifTableExists, + boolean ifExists, String columnName, Column column) { + if (readIf("DATA")) { + read("TYPE"); + return parseAlterTableAlterColumnDataType(schema, tableName, columnName, ifTableExists, ifExists); + } + AlterTableAlterColumn command = new AlterTableAlterColumn( + session, schema); + command.setTableName(tableName); + command.setIfTableExists(ifTableExists); + command.setOldColumn(column); + NullConstraintType nullConstraint = parseNotNullConstraint(); + switch (nullConstraint) { + case NULL_IS_ALLOWED: + command.setType(CommandInterface.ALTER_TABLE_ALTER_COLUMN_DROP_NOT_NULL); + break; + case NULL_IS_NOT_ALLOWED: + command.setType(CommandInterface.ALTER_TABLE_ALTER_COLUMN_NOT_NULL); + break; + case NO_NULL_CONSTRAINT_FOUND: + if (readIf(DEFAULT)) { + if (readIf(ON)) { + read(NULL); + command.setType(CommandInterface.ALTER_TABLE_ALTER_COLUMN_DEFAULT_ON_NULL); + command.setBooleanFlag(true); + break; + } + Expression defaultExpression = readExpression(); + command.setType(CommandInterface.ALTER_TABLE_ALTER_COLUMN_DEFAULT); + command.setDefaultExpression(defaultExpression); + } else if (readIf(ON)) { + read("UPDATE"); + Expression onUpdateExpression = readExpression(); + command.setType(CommandInterface.ALTER_TABLE_ALTER_COLUMN_ON_UPDATE); + command.setDefaultExpression(onUpdateExpression); + } else if (readIf("INVISIBLE")) { + command.setType(CommandInterface.ALTER_TABLE_ALTER_COLUMN_VISIBILITY); + command.setBooleanFlag(false); + } else if (readIf("VISIBLE")) { + command.setType(CommandInterface.ALTER_TABLE_ALTER_COLUMN_VISIBILITY); + command.setBooleanFlag(true); + } + break; + default: + throw DbException.get(ErrorCode.UNKNOWN_MODE_1, + "Internal Error - unhandled case: " + nullConstraint.name()); + } + return command; + } + + private Prepared parseAlterTableDrop(Schema schema, String tableName, boolean ifTableExists) { + if (readIf(CONSTRAINT)) { + boolean ifExists = readIfExists(false); + String constraintName = readIdentifierWithSchema(schema.getName()); + ifExists = readIfExists(ifExists); + checkSchema(schema); + AlterTableDropConstraint command = new AlterTableDropConstraint(session, getSchema(), ifExists); + command.setTableName(tableName); + command.setIfTableExists(ifTableExists); + command.setConstraintName(constraintName); + ConstraintActionType dropAction = parseCascadeOrRestrict(); + if (dropAction != null) { + command.setDropAction(dropAction); + } + return command; + } else if (readIf(PRIMARY)) { + read(KEY); + Table table = tableIfTableExists(schema, tableName, ifTableExists); + if (table == null) { + return new NoOperation(session); + } + Index idx = table.getPrimaryKey(); + DropIndex command = new DropIndex(session, schema); + command.setIndexName(idx.getName()); + return command; + } else if (database.getMode().alterTableExtensionsMySQL) { + Prepared command = parseAlterTableDropCompatibility(schema, tableName, ifTableExists); + if (command != null) { + return command; + } + } + readIf("COLUMN"); + boolean ifExists = readIfExists(false); + ArrayList columnsToRemove = new ArrayList<>(); + Table table = tableIfTableExists(schema, tableName, ifTableExists); + // For Oracle compatibility - open bracket required + boolean openingBracketDetected = readIf(OPEN_PAREN); + do { + String columnName = readIdentifier(); + if (table != null) { + Column column = table.getColumn(columnName, ifExists); + if (column != null) { + columnsToRemove.add(column); + } + } + } while (readIf(COMMA)); + if (openingBracketDetected) { + // For Oracle compatibility - close bracket + read(CLOSE_PAREN); + } + if (table == null || columnsToRemove.isEmpty()) { + return new NoOperation(session); + } + AlterTableAlterColumn command = new AlterTableAlterColumn(session, schema); + command.setType(CommandInterface.ALTER_TABLE_DROP_COLUMN); + command.setTableName(tableName); + command.setIfTableExists(ifTableExists); + command.setColumnsToRemove(columnsToRemove); + return command; + } + + private Prepared parseAlterTableDropCompatibility(Schema schema, String tableName, boolean ifTableExists) { + if (readIf(FOREIGN)) { + read(KEY); + // For MariaDB + boolean ifExists = readIfExists(false); + String constraintName = readIdentifierWithSchema(schema.getName()); + checkSchema(schema); + AlterTableDropConstraint command = new AlterTableDropConstraint(session, getSchema(), ifExists); + command.setTableName(tableName); + command.setIfTableExists(ifTableExists); + command.setConstraintName(constraintName); + return command; + } else if (readIf("INDEX")) { + // For MariaDB + boolean ifExists = readIfExists(false); + String indexOrConstraintName = readIdentifierWithSchema(schema.getName()); + if (schema.findIndex(session, indexOrConstraintName) != null) { + DropIndex dropIndexCommand = new DropIndex(session, getSchema()); + dropIndexCommand.setIndexName(indexOrConstraintName); + return commandIfTableExists(schema, tableName, ifTableExists, dropIndexCommand); + } else { + AlterTableDropConstraint dropCommand = new AlterTableDropConstraint(session, getSchema(), ifExists); + dropCommand.setTableName(tableName); + dropCommand.setIfTableExists(ifTableExists); + dropCommand.setConstraintName(indexOrConstraintName); + return dropCommand; + } + } + return null; + } + + private Prepared parseAlterTableRename(Schema schema, String tableName, boolean ifTableExists) { + if (readIf("COLUMN")) { + // PostgreSQL syntax + String columnName = readIdentifier(); + read(TO); + AlterTableRenameColumn command = new AlterTableRenameColumn( + session, schema); + command.setTableName(tableName); + command.setIfTableExists(ifTableExists); + command.setOldColumnName(columnName); + command.setNewColumnName(readIdentifier()); + return command; + } else if (readIf(CONSTRAINT)) { + String constraintName = readIdentifierWithSchema(schema.getName()); + checkSchema(schema); + read(TO); + AlterTableRenameConstraint command = new AlterTableRenameConstraint(session, schema); + command.setTableName(tableName); + command.setIfTableExists(ifTableExists); + command.setConstraintName(constraintName); + command.setNewConstraintName(readIdentifier()); + return command; + } else { + read(TO); + String newName = readIdentifierWithSchema(schema.getName()); + checkSchema(schema); + AlterTableRename command = new AlterTableRename(session, + getSchema()); + command.setTableName(tableName); + command.setNewTableName(newName); + command.setIfTableExists(ifTableExists); + command.setHidden(readIf("HIDDEN")); + return command; + } + } + + private Prepared parseAlterTableSet(Schema schema, String tableName, boolean ifTableExists) { + read("REFERENTIAL_INTEGRITY"); + int type = CommandInterface.ALTER_TABLE_SET_REFERENTIAL_INTEGRITY; + boolean value = readBooleanSetting(); + AlterTableSet command = new AlterTableSet(session, + schema, type, value); + command.setTableName(tableName); + command.setIfTableExists(ifTableExists); + if (readIf(CHECK)) { + command.setCheckExisting(true); + } else if (readIf("NOCHECK")) { + command.setCheckExisting(false); + } + return command; + } + + private Prepared parseAlterTableCompatibility(Schema schema, String tableName, boolean ifTableExists, Mode mode) { + if (mode.alterTableExtensionsMySQL) { + if (readIf("AUTO_INCREMENT")) { + readIf(EQUAL); + Expression restart = readExpression(); + Table table = tableIfTableExists(schema, tableName, ifTableExists); + if (table == null) { + return new NoOperation(session); + } + Index idx = table.findPrimaryKey(); + if (idx != null) { + for (IndexColumn ic : idx.getIndexColumns()) { + Column column = ic.column; + if (column.isIdentity()) { + AlterSequence command = new AlterSequence(session, schema); + command.setColumn(column, null); + SequenceOptions options = new SequenceOptions(); + options.setRestartValue(restart); + command.setOptions(options); + return command; + } + } + } + throw DbException.get(ErrorCode.COLUMN_NOT_FOUND_1, "AUTO_INCREMENT PRIMARY KEY"); + } else if (readIf("CHANGE")) { + readIf("COLUMN"); + String columnName = readIdentifier(); + String newColumnName = readIdentifier(); + Column column = columnIfTableExists(schema, tableName, columnName, ifTableExists, false); + boolean nullable = column == null ? true : column.isNullable(); + // new column type ignored. RENAME and MODIFY are + // a single command in MySQL but two different commands in H2. + parseColumnForTable(newColumnName, nullable); + AlterTableRenameColumn command = new AlterTableRenameColumn(session, schema); + command.setTableName(tableName); + command.setIfTableExists(ifTableExists); + command.setOldColumnName(columnName); + command.setNewColumnName(newColumnName); + return command; + } else if (readIf("CONVERT")) { + readIf(TO); + readIf("CHARACTER"); + readIf(SET); + readMySQLCharset(); + + if (readIf("COLLATE")) { + readMySQLCharset(); + } + + return new NoOperation(session); + } + } + if (mode.alterTableModifyColumn && readIf("MODIFY")) { + // MySQL compatibility (optional) + readIf("COLUMN"); + // Oracle specifies (but will not require) an opening parenthesis + boolean hasOpeningBracket = readIf(OPEN_PAREN); + String columnName = readIdentifier(); + AlterTableAlterColumn command; + NullConstraintType nullConstraint = parseNotNullConstraint(); + switch (nullConstraint) { + case NULL_IS_ALLOWED: + case NULL_IS_NOT_ALLOWED: + command = new AlterTableAlterColumn(session, schema); + command.setTableName(tableName); + command.setIfTableExists(ifTableExists); + Column column = columnIfTableExists(schema, tableName, columnName, ifTableExists, false); + command.setOldColumn(column); + if (nullConstraint == NullConstraintType.NULL_IS_ALLOWED) { + command.setType(CommandInterface.ALTER_TABLE_ALTER_COLUMN_DROP_NOT_NULL); + } else { + command.setType(CommandInterface.ALTER_TABLE_ALTER_COLUMN_NOT_NULL); + } + break; + case NO_NULL_CONSTRAINT_FOUND: + command = parseAlterTableAlterColumnType(schema, tableName, columnName, ifTableExists, false, + mode.getEnum() != ModeEnum.MySQL); + break; + default: + throw DbException.get(ErrorCode.UNKNOWN_MODE_1, + "Internal Error - unhandled case: " + nullConstraint.name()); + } + if (hasOpeningBracket) { + read(CLOSE_PAREN); + } + return command; + } + throw getSyntaxError(); + } + + private Table tableIfTableExists(Schema schema, String tableName, boolean ifTableExists) { + Table table = schema.resolveTableOrView(session, tableName); + if (table == null && !ifTableExists) { + throw getTableOrViewNotFoundDbException(schema.getName(), tableName); + } + return table; + } + + private Column columnIfTableExists(Schema schema, String tableName, + String columnName, boolean ifTableExists, boolean ifExists) { + Table table = tableIfTableExists(schema, tableName, ifTableExists); + if (table == null) { + return null; + } + return table.getColumn(columnName, ifExists); + } + + private Prepared commandIfTableExists(Schema schema, String tableName, + boolean ifTableExists, Prepared commandIfTableExists) { + return tableIfTableExists(schema, tableName, ifTableExists) == null + ? new NoOperation(session) + : commandIfTableExists; + } + + private AlterTableAlterColumn parseAlterTableAlterColumnType(Schema schema, + String tableName, String columnName, boolean ifTableExists, boolean ifExists, boolean preserveNotNull) { + Column oldColumn = columnIfTableExists(schema, tableName, columnName, ifTableExists, ifExists); + Column newColumn = parseColumnForTable(columnName, + !preserveNotNull || oldColumn == null || oldColumn.isNullable()); + AlterTableAlterColumn command = new AlterTableAlterColumn(session, schema); + parseAlterColumnUsingIf(command); + command.setTableName(tableName); + command.setIfTableExists(ifTableExists); + command.setType(CommandInterface.ALTER_TABLE_ALTER_COLUMN_CHANGE_TYPE); + command.setOldColumn(oldColumn); + command.setNewColumn(newColumn); + return command; + } + + private AlterTableAlterColumn parseAlterTableAlterColumnDataType(Schema schema, + String tableName, String columnName, boolean ifTableExists, boolean ifExists) { + Column oldColumn = columnIfTableExists(schema, tableName, columnName, ifTableExists, ifExists); + Column newColumn = parseColumnWithType(columnName); + if (oldColumn != null) { + if (!oldColumn.isNullable()) { + newColumn.setNullable(false); + } + if (!oldColumn.getVisible()) { + newColumn.setVisible(false); + } + Expression e = oldColumn.getDefaultExpression(); + if (e != null) { + if (oldColumn.isGenerated()) { + newColumn.setGeneratedExpression(e); + } else { + newColumn.setDefaultExpression(session, e); + } + } + e = oldColumn.getOnUpdateExpression(); + if (e != null) { + newColumn.setOnUpdateExpression(session, e); + } + Sequence s = oldColumn.getSequence(); + if (s != null) { + newColumn.setIdentityOptions(new SequenceOptions(s, newColumn.getType()), + oldColumn.isGeneratedAlways()); + } + String c = oldColumn.getComment(); + if (c != null) { + newColumn.setComment(c); + } + } + AlterTableAlterColumn command = new AlterTableAlterColumn(session, schema); + parseAlterColumnUsingIf(command); + command.setTableName(tableName); + command.setIfTableExists(ifTableExists); + command.setType(CommandInterface.ALTER_TABLE_ALTER_COLUMN_CHANGE_TYPE); + command.setOldColumn(oldColumn); + command.setNewColumn(newColumn); + return command; + } + + private AlterTableAlterColumn parseAlterTableAddColumn(String tableName, + Schema schema, boolean ifTableExists) { + readIf("COLUMN"); + AlterTableAlterColumn command = new AlterTableAlterColumn(session, + schema); + command.setType(CommandInterface.ALTER_TABLE_ADD_COLUMN); + command.setTableName(tableName); + command.setIfTableExists(ifTableExists); + if (readIf(OPEN_PAREN)) { + command.setIfNotExists(false); + do { + parseTableColumnDefinition(command, schema, tableName, false); + } while (readIfMore()); + } else { + boolean ifNotExists = readIfNotExists(); + command.setIfNotExists(ifNotExists); + parseTableColumnDefinition(command, schema, tableName, false); + parseAlterColumnUsingIf(command); + } + if (readIf("BEFORE")) { + command.setAddBefore(readIdentifier()); + } else if (readIf("AFTER")) { + command.setAddAfter(readIdentifier()); + } else if (readIf("FIRST")) { + command.setAddFirst(); + } + return command; + } + + private void parseAlterColumnUsingIf(AlterTableAlterColumn command) { + if (readIf(USING)) { + command.setUsingExpression(readExpression()); + } + } + + private ConstraintActionType parseAction() { + ConstraintActionType result = parseCascadeOrRestrict(); + if (result != null) { + return result; + } + if (readIf("NO")) { + read("ACTION"); + return ConstraintActionType.RESTRICT; + } + read(SET); + if (readIf(NULL)) { + return ConstraintActionType.SET_NULL; + } + read(DEFAULT); + return ConstraintActionType.SET_DEFAULT; + } + + private ConstraintActionType parseCascadeOrRestrict() { + if (readIf("CASCADE")) { + return ConstraintActionType.CASCADE; + } else if (readIf("RESTRICT")) { + return ConstraintActionType.RESTRICT; + } else { + return null; + } + } + + private DefineCommand parseTableConstraintIf(String tableName, Schema schema, boolean ifTableExists) { + String constraintName = null, comment = null; + boolean ifNotExists = false; + if (readIf(CONSTRAINT)) { + ifNotExists = readIfNotExists(); + constraintName = readIdentifierWithSchema(schema.getName()); + checkSchema(schema); + comment = readCommentIf(); + } + AlterTableAddConstraint command; + switch (currentTokenType) { + case PRIMARY: + read(); + read(KEY); + command = new AlterTableAddConstraint(session, schema, + CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_PRIMARY_KEY, ifNotExists); + if (readIf("HASH")) { + command.setPrimaryKeyHash(true); + } + read(OPEN_PAREN); + command.setIndexColumns(parseIndexColumnList()); + if (readIf("INDEX")) { + String indexName = readIdentifierWithSchema(); + command.setIndex(getSchema().findIndex(session, indexName)); + } + break; + case UNIQUE: + read(); + // MySQL compatibility + boolean compatibility = database.getMode().indexDefinitionInCreateTable; + if (compatibility) { + if (!readIf(KEY)) { + readIf("INDEX"); + } + if (!isToken(OPEN_PAREN)) { + constraintName = readIdentifier(); + } + } + read(OPEN_PAREN); + command = new AlterTableAddConstraint(session, schema, CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_UNIQUE, + ifNotExists); + if (readIf(VALUE)) { + read(CLOSE_PAREN); + command.setIndexColumns(null); + } else { + command.setIndexColumns(parseIndexColumnList()); + } + if (readIf("INDEX")) { + String indexName = readIdentifierWithSchema(); + command.setIndex(getSchema().findIndex(session, indexName)); + } + if (compatibility && readIf(USING)) { + read("BTREE"); + } + break; + case FOREIGN: + read(); + command = new AlterTableAddConstraint(session, schema, + CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_REFERENTIAL, ifNotExists); + read(KEY); + read(OPEN_PAREN); + command.setIndexColumns(parseIndexColumnList()); + if (readIf("INDEX")) { + String indexName = readIdentifierWithSchema(); + command.setIndex(schema.findIndex(session, indexName)); + } + read("REFERENCES"); + parseReferences(command, schema, tableName); + break; + case CHECK: + read(); + command = new AlterTableAddConstraint(session, schema, CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_CHECK, + ifNotExists); + command.setCheckExpression(readExpression()); + break; + default: + if (constraintName == null) { + Mode mode = database.getMode(); + if (mode.indexDefinitionInCreateTable) { + int start = tokenIndex; + if (readIf(KEY) || readIf("INDEX")) { + // MySQL + // need to read ahead, as it could be a column name + if (DataType.getTypeByName(currentToken, mode) == null) { + CreateIndex createIndex = new CreateIndex(session, schema); + createIndex.setComment(comment); + createIndex.setTableName(tableName); + createIndex.setIfTableExists(ifTableExists); + if (!readIf(OPEN_PAREN)) { + createIndex.setIndexName(readIdentifier()); + read(OPEN_PAREN); + } + createIndex.setIndexColumns(parseIndexColumnList()); + // MySQL compatibility + if (readIf(USING)) { + read("BTREE"); + } + return createIndex; + } else { + // known data type + setTokenIndex(start); + } + } + } + return null; + } else { + if (expectedList != null) { + addMultipleExpected(PRIMARY, UNIQUE, FOREIGN, CHECK); + } + throw getSyntaxError(); + } + } + if (command.getType() != CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_PRIMARY_KEY) { + if (readIf("NOCHECK")) { + command.setCheckExisting(false); + } else { + readIf(CHECK); + command.setCheckExisting(true); + } + } + command.setTableName(tableName); + command.setIfTableExists(ifTableExists); + command.setConstraintName(constraintName); + command.setComment(comment); + return command; + } + + private void parseReferences(AlterTableAddConstraint command, + Schema schema, String tableName) { + if (readIf(OPEN_PAREN)) { + command.setRefTableName(schema, tableName); + command.setRefIndexColumns(parseIndexColumnList()); + } else { + String refTableName = readIdentifierWithSchema(schema.getName()); + command.setRefTableName(getSchema(), refTableName); + if (readIf(OPEN_PAREN)) { + command.setRefIndexColumns(parseIndexColumnList()); + } + } + if (readIf("INDEX")) { + String indexName = readIdentifierWithSchema(); + command.setRefIndex(getSchema().findIndex(session, indexName)); + } + while (readIf(ON)) { + if (readIf("DELETE")) { + command.setDeleteAction(parseAction()); + } else { + read("UPDATE"); + command.setUpdateAction(parseAction()); + } + } + if (readIf(NOT)) { + if (!readIf("DEFERRABLE")) { + setTokenIndex(tokenIndex - 1); + } + } else { + readIf("DEFERRABLE"); + } + } + + private CreateLinkedTable parseCreateLinkedTable(boolean temp, + boolean globalTemp, boolean force) { + read(TABLE); + boolean ifNotExists = readIfNotExists(); + String tableName = readIdentifierWithSchema(); + CreateLinkedTable command = new CreateLinkedTable(session, getSchema()); + command.setTemporary(temp); + command.setGlobalTemporary(globalTemp); + command.setForce(force); + command.setIfNotExists(ifNotExists); + command.setTableName(tableName); + command.setComment(readCommentIf()); + read(OPEN_PAREN); + command.setDriver(readString()); + read(COMMA); + command.setUrl(readString()); + read(COMMA); + command.setUser(readString()); + read(COMMA); + command.setPassword(readString()); + read(COMMA); + String originalTable = readString(); + if (readIf(COMMA)) { + command.setOriginalSchema(originalTable); + originalTable = readString(); + } + command.setOriginalTable(originalTable); + read(CLOSE_PAREN); + if (readIf("EMIT")) { + read("UPDATES"); + command.setEmitUpdates(true); + } else if (readIf("READONLY")) { + command.setReadOnly(true); + } + if (readIf("FETCH_SIZE")) { + command.setFetchSize(readNonNegativeInt()); + } + if(readIf("AUTOCOMMIT")){ + if(readIf("ON")) { + command.setAutoCommit(true); + } + else if(readIf("OFF")){ + command.setAutoCommit(false); + } + } + return command; + } + + private CreateTable parseCreateTable(boolean temp, boolean globalTemp, + boolean persistIndexes) { + boolean ifNotExists = readIfNotExists(); + String tableName = readIdentifierWithSchema(); + if (temp && globalTemp && equalsToken("SESSION", schemaName)) { + // support weird syntax: declare global temporary table session.xy + // (...) not logged + schemaName = session.getCurrentSchemaName(); + globalTemp = false; + } + Schema schema = getSchema(); + CreateTable command = new CreateTable(session, schema); + command.setPersistIndexes(persistIndexes); + command.setTemporary(temp); + command.setGlobalTemporary(globalTemp); + command.setIfNotExists(ifNotExists); + command.setTableName(tableName); + command.setComment(readCommentIf()); + if (readIf(OPEN_PAREN)) { + if (!readIf(CLOSE_PAREN)) { + do { + parseTableColumnDefinition(command, schema, tableName, true); + } while (readIfMore()); + } + } + if (database.getMode().getEnum() == ModeEnum.MySQL) { + parseCreateTableMySQLTableOptions(command); + } + if (readIf("ENGINE")) { + command.setTableEngine(readIdentifier()); + } + if (readIf(WITH)) { + command.setTableEngineParams(readTableEngineParams()); + } + if (temp) { + if (readIf(ON)) { + read("COMMIT"); + if (readIf("DROP")) { + command.setOnCommitDrop(); + } else if (readIf("DELETE")) { + read("ROWS"); + command.setOnCommitTruncate(); + } + } else if (readIf(NOT)) { + if (readIf("PERSISTENT")) { + command.setPersistData(false); + } else { + read("LOGGED"); + } + } + if (readIf("TRANSACTIONAL")) { + command.setTransactional(true); + } + } else if (!persistIndexes && readIf(NOT)) { + read("PERSISTENT"); + command.setPersistData(false); + } + if (readIf("HIDDEN")) { + command.setHidden(true); + } + if (readIf(AS)) { + readIf("SORTED"); + command.setQuery(parseQuery()); + if (readIf(WITH)) { + command.setWithNoData(readIf("NO")); + read("DATA"); + } + } + return command; + } + + private void parseTableColumnDefinition(CommandWithColumns command, Schema schema, String tableName, + boolean forCreateTable) { + DefineCommand c = parseTableConstraintIf(tableName, schema, false); + if (c != null) { + command.addConstraintCommand(c); + return; + } + String columnName = readIdentifier(); + if (forCreateTable && (currentTokenType == COMMA || currentTokenType == CLOSE_PAREN)) { + command.addColumn(new Column(columnName, TypeInfo.TYPE_UNKNOWN)); + return; + } + Column column = parseColumnForTable(columnName, true); + if (column.hasIdentityOptions() && column.isPrimaryKey()) { + command.addConstraintCommand(newPrimaryKeyConstraintCommand(session, schema, tableName, column)); + } + command.addColumn(column); + readColumnConstraints(command, schema, tableName, column); + } + + /** + * Create a new alter table command. + * + * @param session the session + * @param schema the schema + * @param tableName the table + * @param column the column + * @return the command + */ + public static AlterTableAddConstraint newPrimaryKeyConstraintCommand(SessionLocal session, Schema schema, + String tableName, Column column) { + column.setPrimaryKey(false); + AlterTableAddConstraint pk = new AlterTableAddConstraint(session, schema, + CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_PRIMARY_KEY, false); + pk.setTableName(tableName); + pk.setIndexColumns(new IndexColumn[] { new IndexColumn(column.getName()) }); + return pk; + } + + private void readColumnConstraints(CommandWithColumns command, Schema schema, String tableName, Column column) { + String comment = column.getComment(); + boolean hasPrimaryKey = false, hasNotNull = false; + NullConstraintType nullType; + Mode mode = database.getMode(); + for (;;) { + String constraintName; + if (readIf(CONSTRAINT)) { + constraintName = readIdentifier(); + } else if (comment == null && (comment = readCommentIf()) != null) { + // Compatibility: COMMENT may be specified appear after some constraint + column.setComment(comment); + continue; + } else { + constraintName = null; + } + if (!hasPrimaryKey && readIf(PRIMARY)) { + read(KEY); + hasPrimaryKey = true; + boolean hash = readIf("HASH"); + AlterTableAddConstraint pk = new AlterTableAddConstraint(session, schema, + CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_PRIMARY_KEY, false); + pk.setConstraintName(constraintName); + pk.setPrimaryKeyHash(hash); + pk.setTableName(tableName); + pk.setIndexColumns(new IndexColumn[] { new IndexColumn(column.getName()) }); + command.addConstraintCommand(pk); + } else if (readIf(UNIQUE)) { + AlterTableAddConstraint unique = new AlterTableAddConstraint(session, schema, + CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_UNIQUE, false); + unique.setConstraintName(constraintName); + unique.setIndexColumns(new IndexColumn[] { new IndexColumn(column.getName()) }); + unique.setTableName(tableName); + command.addConstraintCommand(unique); + } else if (!hasNotNull + && (nullType = parseNotNullConstraint()) != NullConstraintType.NO_NULL_CONSTRAINT_FOUND) { + hasNotNull = true; + if (nullType == NullConstraintType.NULL_IS_NOT_ALLOWED) { + column.setNullable(false); + } else if (nullType == NullConstraintType.NULL_IS_ALLOWED) { + if (column.isIdentity()) { + throw DbException.get(ErrorCode.COLUMN_MUST_NOT_BE_NULLABLE_1, column.getName()); + } + column.setNullable(true); + } + } else if (readIf(CHECK)) { + AlterTableAddConstraint check = new AlterTableAddConstraint(session, schema, + CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_CHECK, false); + check.setConstraintName(constraintName); + check.setTableName(tableName); + check.setCheckExpression(readExpression()); + command.addConstraintCommand(check); + } else if (readIf("REFERENCES")) { + AlterTableAddConstraint ref = new AlterTableAddConstraint(session, schema, + CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_REFERENTIAL, false); + ref.setConstraintName(constraintName); + ref.setIndexColumns(new IndexColumn[] { new IndexColumn(column.getName()) }); + ref.setTableName(tableName); + parseReferences(ref, schema, tableName); + command.addConstraintCommand(ref); + } else if (constraintName == null) { + if (column.getIdentityOptions() != null || !parseCompatibilityIdentity(column, mode)) { + return; + } + } else { + throw getSyntaxError(); + } + } + } + + private boolean parseCompatibilityIdentity(Column column, Mode mode) { + if (mode.autoIncrementClause && readIf("AUTO_INCREMENT")) { + parseCompatibilityIdentityOptions(column); + return true; + } + if (mode.identityClause && readIf("IDENTITY")) { + parseCompatibilityIdentityOptions(column); + return true; + } + return false; + } + + private void parseCreateTableMySQLTableOptions(CreateTable command) { + boolean requireNext = false; + for (;;) { + if (readIf("AUTO_INCREMENT")) { + readIf(EQUAL); + Expression value = readExpression(); + set: { + AlterTableAddConstraint primaryKey = command.getPrimaryKey(); + if (primaryKey != null) { + for (IndexColumn ic : primaryKey.getIndexColumns()) { + String columnName = ic.columnName; + for (Column column : command.getColumns()) { + if (database.equalsIdentifiers(column.getName(), columnName)) { + SequenceOptions options = column.getIdentityOptions(); + if (options != null) { + options.setStartValue(value); + break set; + } + } + } + } + } + throw DbException.get(ErrorCode.COLUMN_NOT_FOUND_1, "AUTO_INCREMENT PRIMARY KEY"); + } + } else if (readIf(DEFAULT)) { + if (readIf("CHARACTER")) { + read(SET); + } else { + readIf("CHARSET"); + readIf("COLLATE"); + } + readMySQLCharset(); + } else if (readIf("CHARACTER")) { + read(SET); + readMySQLCharset(); + } else if (readIf("COLLATE")) { + readMySQLCharset(); + } else if (readIf("CHARSET")) { + readMySQLCharset(); + } else if (readIf("COMMENT")) { + readIf(EQUAL); + command.setComment(readString()); + } else if (readIf("ENGINE")) { + readIf(EQUAL); + readIdentifier(); + } else if (readIf("ROW_FORMAT")) { + readIf(EQUAL); + readIdentifier(); + } else if (requireNext) { + throw getSyntaxError(); + } else { + break; + } + requireNext = readIf(COMMA); + } + } + + private void readMySQLCharset() { + readIf(EQUAL); + readIdentifier(); + } + + /** + * Enumeration describing null constraints + */ + private enum NullConstraintType { + NULL_IS_ALLOWED, NULL_IS_NOT_ALLOWED, NO_NULL_CONSTRAINT_FOUND + } + + private NullConstraintType parseNotNullConstraint(NullConstraintType nullConstraint) { + if (nullConstraint == NullConstraintType.NO_NULL_CONSTRAINT_FOUND) { + nullConstraint = parseNotNullConstraint(); + } + return nullConstraint; + } + + private NullConstraintType parseNotNullConstraint() { + NullConstraintType nullConstraint; + if (readIf(NOT)) { + read(NULL); + nullConstraint = NullConstraintType.NULL_IS_NOT_ALLOWED; + } else if (readIf(NULL)) { + nullConstraint = NullConstraintType.NULL_IS_ALLOWED; + } else { + return NullConstraintType.NO_NULL_CONSTRAINT_FOUND; + } + if (database.getMode().getEnum() == ModeEnum.Oracle) { + nullConstraint = parseNotNullCompatibility(nullConstraint); + } + return nullConstraint; + } + + private NullConstraintType parseNotNullCompatibility(NullConstraintType nullConstraint) { + if (readIf("ENABLE")) { + if (!readIf("VALIDATE") && readIf("NOVALIDATE")) { + // Turn off constraint, allow NULLs + nullConstraint = NullConstraintType.NULL_IS_ALLOWED; + } + } else if (readIf("DISABLE")) { + // Turn off constraint, allow NULLs + nullConstraint = NullConstraintType.NULL_IS_ALLOWED; + if (!readIf("VALIDATE")) { + readIf("NOVALIDATE"); + } + } + return nullConstraint; + } + + private CreateSynonym parseCreateSynonym(boolean orReplace) { + boolean ifNotExists = readIfNotExists(); + String name = readIdentifierWithSchema(); + Schema synonymSchema = getSchema(); + read(FOR); + String tableName = readIdentifierWithSchema(); + + Schema targetSchema = getSchema(); + CreateSynonym command = new CreateSynonym(session, synonymSchema); + command.setName(name); + command.setSynonymFor(tableName); + command.setSynonymForSchema(targetSchema); + command.setComment(readCommentIf()); + command.setIfNotExists(ifNotExists); + command.setOrReplace(orReplace); + return command; + } + + private static int getCompareType(int tokenType) { + switch (tokenType) { + case EQUAL: + return Comparison.EQUAL; + case BIGGER_EQUAL: + return Comparison.BIGGER_EQUAL; + case BIGGER: + return Comparison.BIGGER; + case SMALLER: + return Comparison.SMALLER; + case SMALLER_EQUAL: + return Comparison.SMALLER_EQUAL; + case NOT_EQUAL: + return Comparison.NOT_EQUAL; + case SPATIAL_INTERSECTS: + return Comparison.SPATIAL_INTERSECTS; + default: + return -1; + } + } + + /** + * Add double quotes around an identifier if required. + * + * @param s the identifier + * @param sqlFlags formatting flags + * @return the quoted identifier + */ + public static String quoteIdentifier(String s, int sqlFlags) { + if (s == null) { + return "\"\""; + } + if ((sqlFlags & HasSQL.QUOTE_ONLY_WHEN_REQUIRED) != 0 && ParserUtil.isSimpleIdentifier(s, false, false)) { + return s; + } + return StringUtils.quoteIdentifier(s); + } + + public void setLiteralsChecked(boolean literalsChecked) { + this.literalsChecked = literalsChecked; + } + + public void setRightsChecked(boolean rightsChecked) { + this.rightsChecked = rightsChecked; + } + + public void setSuppliedParameters(ArrayList suppliedParameters) { + this.suppliedParameters = suppliedParameters; + } + + /** + * Parse a SQL code snippet that represents an expression. + * + * @param sql the code snippet + * @return the expression object + */ + public Expression parseExpression(String sql) { + parameters = Utils.newSmallArrayList(); + initialize(sql, null, false); + read(); + return readExpression(); + } + + /** + * Parse a SQL code snippet that represents an expression for a domain constraint. + * + * @param sql the code snippet + * @return the expression object + */ + public Expression parseDomainConstraintExpression(String sql) { + parameters = Utils.newSmallArrayList(); + initialize(sql, null, false); + read(); + try { + parseDomainConstraint = true; + return readExpression(); + } finally { + parseDomainConstraint = false; + } + } + + /** + * Parse a SQL code snippet that represents a table name. + * + * @param sql the code snippet + * @return the table object + */ + public Table parseTableName(String sql) { + parameters = Utils.newSmallArrayList(); + initialize(sql, null, false); + read(); + return readTableOrView(); + } + + /** + * Parses a list of column names or numbers in parentheses. + * + * @param sql the source SQL + * @param offset the initial offset + * @return the array of column names ({@code String[]}) or numbers + * ({@code int[]}) + * @throws DbException on syntax error + */ + public Object parseColumnList(String sql, int offset) { + initialize(sql, null, true); + for (int i = 0, l = tokens.size(); i < l; i++) { + if (tokens.get(i).start() >= offset) { + setTokenIndex(i); + break; + } + } + read(OPEN_PAREN); + if (readIf(CLOSE_PAREN)) { + return Utils.EMPTY_INT_ARRAY; + } + if (isIdentifier()) { + ArrayList list = Utils.newSmallArrayList(); + do { + if (!isIdentifier()) { + throw getSyntaxError(); + } + list.add(currentToken); + read(); + } while (readIfMore()); + return list.toArray(new String[0]); + } else if (currentTokenType == LITERAL) { + ArrayList list = Utils.newSmallArrayList(); + do { + list.add(readInt()); + } while (readIfMore()); + int count = list.size(); + int[] array = new int[count]; + for (int i = 0; i < count; i++) { + array[i] = list.get(i); + } + return array; + } else { + throw getSyntaxError(); + } + } + + /** + * Returns the last parse index. + * + * @return the last parse index + */ + public int getLastParseIndex() { + return token.start(); + } + + @Override + public String toString() { + return StringUtils.addAsterisk(sqlCommand, token.start()); + } +} diff --git a/h2/src/main/org/h2/command/Prepared.java b/h2/src/main/org/h2/command/Prepared.java new file mode 100644 index 0000000..f9a8883 --- /dev/null +++ b/h2/src/main/org/h2/command/Prepared.java @@ -0,0 +1,473 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import org.h2.api.DatabaseEventListener; +import org.h2.api.ErrorCode; +import org.h2.engine.Database; +import org.h2.engine.DbObject; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.Parameter; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.result.ResultInterface; +import org.h2.table.TableView; +import org.h2.util.HasSQL; + +/** + * A prepared statement. + */ +public abstract class Prepared { + + /** + * The session. + */ + protected SessionLocal session; + + /** + * The SQL string. + */ + protected String sqlStatement; + + /** + * The SQL tokens. + */ + protected ArrayList sqlTokens; + + /** + * Whether to create a new object (for indexes). + */ + protected boolean create = true; + + /** + * The list of parameters. + */ + protected ArrayList parameters; + + /** + * If the query should be prepared before each execution. This is set for + * queries with LIKE ?, because the query plan depends on the parameter + * value. + */ + protected boolean prepareAlways; + + private long modificationMetaId; + private Command command; + /** + * Used to preserve object identities on database startup. {@code 0} if + * object is not stored, {@code -1} if object is stored and its ID is + * already read, {@code >0} if object is stored and its id is not yet read. + */ + private int persistedObjectId; + private long currentRowNumber; + private int rowScanCount; + /** + * Common table expressions (CTE) in queries require us to create temporary views, + * which need to be cleaned up once a command is done executing. + */ + private List cteCleanups; + + /** + * Create a new object. + * + * @param session the session + */ + public Prepared(SessionLocal session) { + this.session = session; + modificationMetaId = session.getDatabase().getModificationMetaId(); + } + + /** + * Check if this command is transactional. + * If it is not, then it forces the current transaction to commit. + * + * @return true if it is + */ + public abstract boolean isTransactional(); + + /** + * Get an empty result set containing the meta data. + * + * @return the result set + */ + public abstract ResultInterface queryMeta(); + + + /** + * Get the command type as defined in CommandInterface + * + * @return the statement type + */ + public abstract int getType(); + + /** + * Check if this command is read only. + * + * @return true if it is + */ + public boolean isReadOnly() { + return false; + } + + /** + * Check if the statement needs to be re-compiled. + * + * @return true if it must + */ + public boolean needRecompile() { + Database db = session.getDatabase(); + if (db == null) { + throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "database closed"); + } + // parser: currently, compiling every create/drop/... twice + // because needRecompile return true even for the first execution + return prepareAlways || + modificationMetaId < db.getModificationMetaId() || + db.getSettings().recompileAlways; + } + + /** + * Get the meta data modification id of the database when this statement was + * compiled. + * + * @return the meta data modification id + */ + long getModificationMetaId() { + return modificationMetaId; + } + + /** + * Set the meta data modification id of this statement. + * + * @param id the new id + */ + void setModificationMetaId(long id) { + this.modificationMetaId = id; + } + + /** + * Set the parameter list of this statement. + * + * @param parameters the parameter list + */ + public void setParameterList(ArrayList parameters) { + this.parameters = parameters; + } + + /** + * Get the parameter list. + * + * @return the parameter list + */ + public ArrayList getParameters() { + return parameters; + } + + /** + * Check if all parameters have been set. + * + * @throws DbException if any parameter has not been set + */ + protected void checkParameters() { + if (persistedObjectId < 0) { + // restore original persistedObjectId on Command re-run + // i.e. due to concurrent update + persistedObjectId = ~persistedObjectId; + } + if (parameters != null) { + for (Parameter param : parameters) { + param.checkSet(); + } + } + } + + /** + * Set the command. + * + * @param command the new command + */ + public void setCommand(Command command) { + this.command = command; + } + + /** + * Check if this object is a query. + * + * @return true if it is + */ + public boolean isQuery() { + return false; + } + + /** + * Prepare this statement. + */ + public void prepare() { + // nothing to do + } + + /** + * Execute the statement. + * + * @return the update count + * @throws DbException if it is a query + */ + public long update() { + throw DbException.get(ErrorCode.METHOD_NOT_ALLOWED_FOR_QUERY); + } + + /** + * Execute the query. + * + * @param maxrows the maximum number of rows to return + * @return the result set + * @throws DbException if it is not a query + */ + @SuppressWarnings("unused") + public ResultInterface query(long maxrows) { + throw DbException.get(ErrorCode.METHOD_ONLY_ALLOWED_FOR_QUERY); + } + + /** + * Set the SQL statement. + * + * @param sql the SQL statement + * @param sqlTokens the SQL tokens + */ + public final void setSQL(String sql, ArrayList sqlTokens) { + this.sqlStatement = sql; + this.sqlTokens = sqlTokens; + } + + /** + * Get the SQL statement. + * + * @return the SQL statement + */ + public final String getSQL() { + return sqlStatement; + } + + /** + * Get the SQL tokens. + * + * @return the SQL tokens + */ + public final ArrayList getSQLTokens() { + return sqlTokens; + } + + /** + * Get the object id to use for the database object that is created in this + * statement. This id is only set when the object is already persisted. + * If not set, this method returns 0. + * + * @return the object id or 0 if not set + */ + public int getPersistedObjectId() { + int id = persistedObjectId; + return id >= 0 ? id : 0; + } + + /** + * Get the current object id, or get a new id from the database. The object + * id is used when creating new database object (CREATE statement). This + * method may be called only once. + * + * @return the object id + */ + protected int getObjectId() { + int id = persistedObjectId; + if (id == 0) { + id = session.getDatabase().allocateObjectId(); + } else if (id < 0) { + throw DbException.getInternalError("Prepared.getObjectId() was called before"); + } + persistedObjectId = ~persistedObjectId; // while negative, it can be restored later + return id; + } + + /** + * Get the SQL statement with the execution plan. + * + * @param sqlFlags formatting flags + * @return the execution plan + */ + public String getPlanSQL(int sqlFlags) { + return null; + } + + /** + * Check if this statement was canceled. + * + * @throws DbException if it was canceled + */ + public void checkCanceled() { + session.checkCanceled(); + Command c = command != null ? command : session.getCurrentCommand(); + if (c != null) { + c.checkCanceled(); + } + } + + /** + * Set the persisted object id for this statement. + * + * @param i the object id + */ + public void setPersistedObjectId(int i) { + this.persistedObjectId = i; + this.create = false; + } + + /** + * Set the session for this statement. + * + * @param currentSession the new session + */ + public void setSession(SessionLocal currentSession) { + this.session = currentSession; + } + + /** + * Print information about the statement executed if info trace level is + * enabled. + * + * @param startTimeNanos when the statement was started + * @param rowCount the query or update row count + */ + void trace(long startTimeNanos, long rowCount) { + if (session.getTrace().isInfoEnabled() && startTimeNanos > 0) { + long deltaTimeNanos = System.nanoTime() - startTimeNanos; + String params = Trace.formatParams(parameters); + session.getTrace().infoSQL(sqlStatement, params, rowCount, deltaTimeNanos / 1_000_000L); + } + // startTime_nanos can be zero for the command that actually turns on + // statistics + if (session.getDatabase().getQueryStatistics() && startTimeNanos != 0) { + long deltaTimeNanos = System.nanoTime() - startTimeNanos; + session.getDatabase().getQueryStatisticsData().update(toString(), deltaTimeNanos, rowCount); + } + } + + /** + * Set the prepare always flag. + * If set, the statement is re-compiled whenever it is executed. + * + * @param prepareAlways the new value + */ + public void setPrepareAlways(boolean prepareAlways) { + this.prepareAlways = prepareAlways; + } + + /** + * Set the current row number. + * + * @param rowNumber the row number + */ + public void setCurrentRowNumber(long rowNumber) { + if ((++rowScanCount & 127) == 0) { + checkCanceled(); + } + this.currentRowNumber = rowNumber; + setProgress(); + } + + /** + * Get the current row number. + * + * @return the row number + */ + public long getCurrentRowNumber() { + return currentRowNumber; + } + + /** + * Notifies query progress via the DatabaseEventListener + */ + private void setProgress() { + if ((currentRowNumber & 127) == 0) { + session.getDatabase().setProgress(DatabaseEventListener.STATE_STATEMENT_PROGRESS, sqlStatement, + currentRowNumber, 0L); + } + } + + /** + * Convert the statement to a String. + * + * @return the SQL statement + */ + @Override + public String toString() { + return sqlStatement; + } + + /** + * Get the SQL snippet of the expression list. + * + * @param list the expression list + * @return the SQL snippet + */ + public static String getSimpleSQL(Expression[] list) { + return Expression.writeExpressions(new StringBuilder(), list, HasSQL.TRACE_SQL_FLAGS).toString(); + } + + /** + * Set the SQL statement of the exception to the given row. + * + * @param e the exception + * @param rowId the row number + * @param values the values of the row + * @return the exception + */ + protected DbException setRow(DbException e, long rowId, String values) { + StringBuilder buff = new StringBuilder(); + if (sqlStatement != null) { + buff.append(sqlStatement); + } + buff.append(" -- "); + if (rowId > 0) { + buff.append("row #").append(rowId + 1).append(' '); + } + buff.append('(').append(values).append(')'); + return e.addSQL(buff.toString()); + } + + public boolean isCacheable() { + return false; + } + + /** + * @return the temporary views created for CTE's. + */ + public List getCteCleanups() { + return cteCleanups; + } + + /** + * Set the temporary views created for CTE's. + * + * @param cteCleanups the temporary views + */ + public void setCteCleanups(List cteCleanups) { + this.cteCleanups = cteCleanups; + } + + public final SessionLocal getSession() { + return session; + } + + /** + * Find and collect all DbObjects, this Prepared depends on. + * + * @param dependencies collection of dependencies to populate + */ + public void collectDependencies(HashSet dependencies) {} +} diff --git a/h2/src/main/org/h2/command/Token.java b/h2/src/main/org/h2/command/Token.java new file mode 100644 index 0000000..888a7e7 --- /dev/null +++ b/h2/src/main/org/h2/command/Token.java @@ -0,0 +1,757 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command; + +import static org.h2.util.ParserUtil.IDENTIFIER; +import static org.h2.util.ParserUtil.LAST_KEYWORD; + +import org.h2.engine.CastDataProvider; +import org.h2.message.DbException; +import org.h2.util.StringUtils; +import org.h2.value.Value; +import org.h2.value.ValueBigint; +import org.h2.value.ValueInteger; +import org.h2.value.ValueVarbinary; +import org.h2.value.ValueVarchar; + +/** + * Token. + */ +public abstract class Token implements Cloneable { + + /** + * Token with parameter. + */ + static final int PARAMETER = LAST_KEYWORD + 1; + + /** + * End of input. + */ + static final int END_OF_INPUT = PARAMETER + 1; + + /** + * Token with literal. + */ + static final int LITERAL = END_OF_INPUT + 1; + + /** + * The token "=". + */ + static final int EQUAL = LITERAL + 1; + + /** + * The token ">=". + */ + static final int BIGGER_EQUAL = EQUAL + 1; + + /** + * The token ">". + */ + static final int BIGGER = BIGGER_EQUAL + 1; + + /** + * The token "<". + */ + static final int SMALLER = BIGGER + 1; + + /** + * The token "<=". + */ + static final int SMALLER_EQUAL = SMALLER + 1; + + /** + * The token "<>" or "!=". + */ + static final int NOT_EQUAL = SMALLER_EQUAL + 1; + + /** + * The token "@". + */ + static final int AT = NOT_EQUAL + 1; + + /** + * The token "-". + */ + static final int MINUS_SIGN = AT + 1; + + /** + * The token "+". + */ + static final int PLUS_SIGN = MINUS_SIGN + 1; + + /** + * The token "||". + */ + static final int CONCATENATION = PLUS_SIGN + 1; + + /** + * The token "(". + */ + static final int OPEN_PAREN = CONCATENATION + 1; + + /** + * The token ")". + */ + static final int CLOSE_PAREN = OPEN_PAREN + 1; + + /** + * The token "&&". + */ + static final int SPATIAL_INTERSECTS = CLOSE_PAREN + 1; + + /** + * The token "*". + */ + static final int ASTERISK = SPATIAL_INTERSECTS + 1; + + /** + * The token ",". + */ + static final int COMMA = ASTERISK + 1; + + /** + * The token ".". + */ + static final int DOT = COMMA + 1; + + /** + * The token "{". + */ + static final int OPEN_BRACE = DOT + 1; + + /** + * The token "}". + */ + static final int CLOSE_BRACE = OPEN_BRACE + 1; + + /** + * The token "/". + */ + static final int SLASH = CLOSE_BRACE + 1; + + /** + * The token "%". + */ + static final int PERCENT = SLASH + 1; + + /** + * The token ";". + */ + static final int SEMICOLON = PERCENT + 1; + + /** + * The token ":". + */ + static final int COLON = SEMICOLON + 1; + + /** + * The token "[". + */ + static final int OPEN_BRACKET = COLON + 1; + + /** + * The token "]". + */ + static final int CLOSE_BRACKET = OPEN_BRACKET + 1; + + /** + * The token "~". + */ + static final int TILDE = CLOSE_BRACKET + 1; + + /** + * The token "::". + */ + static final int COLON_COLON = TILDE + 1; + + /** + * The token ":=". + */ + static final int COLON_EQ = COLON_COLON + 1; + + /** + * The token "!~". + */ + static final int NOT_TILDE = COLON_EQ + 1; + + static final String[] TOKENS = { + // Unused + null, + // KEYWORD + null, + // IDENTIFIER + null, + // ALL + "ALL", + // AND + "AND", + // ANY + "ANY", + // ARRAY + "ARRAY", + // AS + "AS", + // ASYMMETRIC + "ASYMMETRIC", + // AUTHORIZATION + "AUTHORIZATION", + // BETWEEN + "BETWEEN", + // CASE + "CASE", + // CAST + "CAST", + // CHECK + "CHECK", + // CONSTRAINT + "CONSTRAINT", + // CROSS + "CROSS", + // CURRENT_CATALOG + "CURRENT_CATALOG", + // CURRENT_DATE + "CURRENT_DATE", + // CURRENT_PATH + "CURRENT_PATH", + // CURRENT_ROLE + "CURRENT_ROLE", + // CURRENT_SCHEMA + "CURRENT_SCHEMA", + // CURRENT_TIME + "CURRENT_TIME", + // CURRENT_TIMESTAMP + "CURRENT_TIMESTAMP", + // CURRENT_USER + "CURRENT_USER", + // DAY + "DAY", + // DEFAULT + "DEFAULT", + // DISTINCT + "DISTINCT", + // ELSE + "ELSE", + // END + "END", + // EXCEPT + "EXCEPT", + // EXISTS + "EXISTS", + // FALSE + "FALSE", + // FETCH + "FETCH", + // FOR + "FOR", + // FOREIGN + "FOREIGN", + // FROM + "FROM", + // FULL + "FULL", + // GROUP + "GROUP", + // HAVING + "HAVING", + // HOUR + "HOUR", + // IF + "IF", + // IN + "IN", + // INNER + "INNER", + // INTERSECT + "INTERSECT", + // INTERVAL + "INTERVAL", + // IS + "IS", + // JOIN + "JOIN", + // KEY + "KEY", + // LEFT + "LEFT", + // LIKE + "LIKE", + // LIMIT + "LIMIT", + // LOCALTIME + "LOCALTIME", + // LOCALTIMESTAMP + "LOCALTIMESTAMP", + // MINUS + "MINUS", + // MINUTE + "MINUTE", + // MONTH + "MONTH", + // NATURAL + "NATURAL", + // NOT + "NOT", + // NULL + "NULL", + // OFFSET + "OFFSET", + // ON + "ON", + // OR + "OR", + // ORDER + "ORDER", + // PRIMARY + "PRIMARY", + // QUALIFY + "QUALIFY", + // RIGHT + "RIGHT", + // ROW + "ROW", + // ROWNUM + "ROWNUM", + // SECOND + "SECOND", + // SELECT + "SELECT", + // SESSION_USER + "SESSION_USER", + // SET + "SET", + // SOME + "SOME", + // SYMMETRIC + "SYMMETRIC", + // SYSTEM_USER + "SYSTEM_USER", + // TABLE + "TABLE", + // TO + "TO", + // TRUE + "TRUE", + // UESCAPE + "UESCAPE", + // UNION + "UNION", + // UNIQUE + "UNIQUE", + // UNKNOWN + "UNKNOWN", + // USER + "USER", + // USING + "USING", + // VALUE + "VALUE", + // VALUES + "VALUES", + // WHEN + "WHEN", + // WHERE + "WHERE", + // WINDOW + "WINDOW", + // WITH + "WITH", + // YEAR + "YEAR", + // _ROWID_ + "_ROWID_", + // PARAMETER + "?", + // END_OF_INPUT + null, + // LITERAL + null, + // EQUAL + "=", + // BIGGER_EQUAL + ">=", + // BIGGER + ">", + // SMALLER + "<", + // SMALLER_EQUAL + "<=", + // NOT_EQUAL + "<>", + // AT + "@", + // MINUS_SIGN + "-", + // PLUS_SIGN + "+", + // CONCATENATION + "||", + // OPEN_PAREN + "(", + // CLOSE_PAREN + ")", + // SPATIAL_INTERSECTS + "&&", + // ASTERISK + "*", + // COMMA + ",", + // DOT + ".", + // OPEN_BRACE + "{", + // CLOSE_BRACE + "}", + // SLASH + "/", + // PERCENT + "%", + // SEMICOLON + ";", + // COLON + ":", + // OPEN_BRACKET + "[", + // CLOSE_BRACKET + "]", + // TILDE + "~", + // COLON_COLON + "::", + // COLON_EQ + ":=", + // NOT_TILDE + "!~", + // End + }; + + static class IdentifierToken extends Token { + + private String identifier; + + private final boolean quoted; + + private boolean unicode; + + IdentifierToken(int start, String identifier, boolean quoted, boolean unicode) { + super(start); + this.identifier = identifier; + this.quoted = quoted; + this.unicode = unicode; + } + + @Override + int tokenType() { + return IDENTIFIER; + } + + @Override + String asIdentifier() { + return identifier; + } + + @Override + boolean isQuoted() { + return quoted; + } + + @Override + boolean needsUnicodeConversion() { + return unicode; + } + + @Override + void convertUnicode(int uescape) { + if (unicode) { + identifier = StringUtils.decodeUnicodeStringSQL(identifier, uescape); + unicode = false; + } else { + throw DbException.getInternalError(); + } + } + + @Override + public String toString() { + return quoted ? StringUtils.quoteIdentifier(identifier) : identifier; + } + + } + + static final class KeywordToken extends Token { + + private final int type; + + KeywordToken(int start, int type) { + super(start); + this.type = type; + } + + @Override + int tokenType() { + return type; + } + + @Override + String asIdentifier() { + return TOKENS[type]; + } + + @Override + public String toString() { + return TOKENS[type]; + } + + } + + static final class KeywordOrIdentifierToken extends Token { + + private final int type; + + private final String identifier; + + KeywordOrIdentifierToken(int start, int type, String identifier) { + super(start); + this.type = type; + this.identifier = identifier; + } + + @Override + int tokenType() { + return type; + } + + @Override + String asIdentifier() { + return identifier; + } + + @Override + public String toString() { + return identifier; + } + + } + + static abstract class LiteralToken extends Token { + + Value value; + + LiteralToken(int start) { + super(start); + } + + @Override + final int tokenType() { + return LITERAL; + } + + @Override + public final String toString() { + return value(null).getTraceSQL(); + } + + } + + static final class BinaryStringToken extends LiteralToken { + + private final byte[] string; + + BinaryStringToken(int start, byte[] string) { + super(start); + this.string = string; + } + + @Override + Value value(CastDataProvider provider) { + if (value == null) { + value = ValueVarbinary.getNoCopy(string); + } + return value; + } + + } + + static final class CharacterStringToken extends LiteralToken { + + String string; + + private boolean unicode; + + CharacterStringToken(int start, String string, boolean unicode) { + super(start); + this.string = string; + this.unicode = unicode; + } + + @Override + Value value(CastDataProvider provider) { + if (value == null) { + value = ValueVarchar.get(string, provider); + } + return value; + } + + @Override + boolean needsUnicodeConversion() { + return unicode; + } + + @Override + void convertUnicode(int uescape) { + if (unicode) { + string = StringUtils.decodeUnicodeStringSQL(string, uescape); + unicode = false; + } else { + throw DbException.getInternalError(); + } + } + + } + + static final class IntegerToken extends LiteralToken { + + private final int number; + + IntegerToken(int start, int number) { + super(start); + this.number = number; + } + + @Override + Value value(CastDataProvider provider) { + if (value == null) { + value = ValueInteger.get(number); + } + return value; + } + + } + + static final class BigintToken extends LiteralToken { + + private final long number; + + BigintToken(int start, long number) { + super(start); + this.number = number; + } + + @Override + Value value(CastDataProvider provider) { + if (value == null) { + value = ValueBigint.get(number); + } + return value; + } + + } + + static final class ValueToken extends LiteralToken { + + ValueToken(int start, Value value) { + super(start); + this.value = value; + } + + @Override + Value value(CastDataProvider provider) { + return value; + } + + } + + static final class ParameterToken extends Token { + + int index; + + ParameterToken(int start, int index) { + super(start); + this.index = index; + } + + @Override + int tokenType() { + return PARAMETER; + } + + @Override + String asIdentifier() { + return "?"; + } + + int index() { + return index; + } + + @Override + public String toString() { + return index == 0 ? "?" : "?" + index; + } + + } + + static final class EndOfInputToken extends Token { + + EndOfInputToken(int start) { + super(start); + } + + @Override + int tokenType() { + return END_OF_INPUT; + } + + } + + private int start; + + Token(int start) { + this.start = start; + } + + final int start() { + return start; + } + + final void setStart(int offset) { + start = offset; + } + + final void subtractFromStart(int offset) { + start -= offset; + } + + abstract int tokenType(); + + String asIdentifier() { + return null; + } + + boolean isQuoted() { + return false; + } + + Value value(CastDataProvider provider) { + return null; + } + + boolean needsUnicodeConversion() { + return false; + } + + void convertUnicode(int uescape) { + throw DbException.getInternalError(); + } + + @Override + protected Token clone() { + try { + return (Token) super.clone(); + } catch (CloneNotSupportedException e) { + throw DbException.getInternalError(); + } + } + +} diff --git a/h2/src/main/org/h2/command/Tokenizer.java b/h2/src/main/org/h2/command/Tokenizer.java new file mode 100644 index 0000000..f0c413e --- /dev/null +++ b/h2/src/main/org/h2/command/Tokenizer.java @@ -0,0 +1,1400 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command; + +import static org.h2.command.Token.ASTERISK; +import static org.h2.command.Token.AT; +import static org.h2.command.Token.BIGGER; +import static org.h2.command.Token.BIGGER_EQUAL; +import static org.h2.command.Token.CLOSE_BRACE; +import static org.h2.command.Token.CLOSE_BRACKET; +import static org.h2.command.Token.CLOSE_PAREN; +import static org.h2.command.Token.COLON; +import static org.h2.command.Token.COLON_COLON; +import static org.h2.command.Token.COLON_EQ; +import static org.h2.command.Token.COMMA; +import static org.h2.command.Token.CONCATENATION; +import static org.h2.command.Token.DOT; +import static org.h2.command.Token.EQUAL; +import static org.h2.command.Token.MINUS_SIGN; +import static org.h2.command.Token.NOT_EQUAL; +import static org.h2.command.Token.NOT_TILDE; +import static org.h2.command.Token.OPEN_BRACE; +import static org.h2.command.Token.OPEN_BRACKET; +import static org.h2.command.Token.OPEN_PAREN; +import static org.h2.command.Token.PERCENT; +import static org.h2.command.Token.PLUS_SIGN; +import static org.h2.command.Token.SEMICOLON; +import static org.h2.command.Token.SLASH; +import static org.h2.command.Token.SMALLER; +import static org.h2.command.Token.SMALLER_EQUAL; +import static org.h2.command.Token.SPATIAL_INTERSECTS; +import static org.h2.command.Token.TILDE; +import static org.h2.util.ParserUtil.ALL; +import static org.h2.util.ParserUtil.AND; +import static org.h2.util.ParserUtil.ANY; +import static org.h2.util.ParserUtil.ARRAY; +import static org.h2.util.ParserUtil.AS; +import static org.h2.util.ParserUtil.ASYMMETRIC; +import static org.h2.util.ParserUtil.AUTHORIZATION; +import static org.h2.util.ParserUtil.BETWEEN; +import static org.h2.util.ParserUtil.CASE; +import static org.h2.util.ParserUtil.CAST; +import static org.h2.util.ParserUtil.CHECK; +import static org.h2.util.ParserUtil.CONSTRAINT; +import static org.h2.util.ParserUtil.CROSS; +import static org.h2.util.ParserUtil.CURRENT_CATALOG; +import static org.h2.util.ParserUtil.CURRENT_DATE; +import static org.h2.util.ParserUtil.CURRENT_PATH; +import static org.h2.util.ParserUtil.CURRENT_ROLE; +import static org.h2.util.ParserUtil.CURRENT_SCHEMA; +import static org.h2.util.ParserUtil.CURRENT_TIME; +import static org.h2.util.ParserUtil.CURRENT_TIMESTAMP; +import static org.h2.util.ParserUtil.CURRENT_USER; +import static org.h2.util.ParserUtil.DAY; +import static org.h2.util.ParserUtil.DEFAULT; +import static org.h2.util.ParserUtil.DISTINCT; +import static org.h2.util.ParserUtil.ELSE; +import static org.h2.util.ParserUtil.END; +import static org.h2.util.ParserUtil.EXCEPT; +import static org.h2.util.ParserUtil.EXISTS; +import static org.h2.util.ParserUtil.FALSE; +import static org.h2.util.ParserUtil.FETCH; +import static org.h2.util.ParserUtil.FOR; +import static org.h2.util.ParserUtil.FOREIGN; +import static org.h2.util.ParserUtil.FROM; +import static org.h2.util.ParserUtil.FULL; +import static org.h2.util.ParserUtil.GROUP; +import static org.h2.util.ParserUtil.HAVING; +import static org.h2.util.ParserUtil.HOUR; +import static org.h2.util.ParserUtil.IDENTIFIER; +import static org.h2.util.ParserUtil.IF; +import static org.h2.util.ParserUtil.IN; +import static org.h2.util.ParserUtil.INNER; +import static org.h2.util.ParserUtil.INTERSECT; +import static org.h2.util.ParserUtil.INTERVAL; +import static org.h2.util.ParserUtil.IS; +import static org.h2.util.ParserUtil.JOIN; +import static org.h2.util.ParserUtil.KEY; +import static org.h2.util.ParserUtil.LEFT; +import static org.h2.util.ParserUtil.LIKE; +import static org.h2.util.ParserUtil.LIMIT; +import static org.h2.util.ParserUtil.LOCALTIME; +import static org.h2.util.ParserUtil.LOCALTIMESTAMP; +import static org.h2.util.ParserUtil.MINUS; +import static org.h2.util.ParserUtil.MINUTE; +import static org.h2.util.ParserUtil.MONTH; +import static org.h2.util.ParserUtil.NATURAL; +import static org.h2.util.ParserUtil.NOT; +import static org.h2.util.ParserUtil.NULL; +import static org.h2.util.ParserUtil.OFFSET; +import static org.h2.util.ParserUtil.ON; +import static org.h2.util.ParserUtil.OR; +import static org.h2.util.ParserUtil.ORDER; +import static org.h2.util.ParserUtil.PRIMARY; +import static org.h2.util.ParserUtil.QUALIFY; +import static org.h2.util.ParserUtil.RIGHT; +import static org.h2.util.ParserUtil.ROW; +import static org.h2.util.ParserUtil.ROWNUM; +import static org.h2.util.ParserUtil.SECOND; +import static org.h2.util.ParserUtil.SELECT; +import static org.h2.util.ParserUtil.SESSION_USER; +import static org.h2.util.ParserUtil.SET; +import static org.h2.util.ParserUtil.SOME; +import static org.h2.util.ParserUtil.SYMMETRIC; +import static org.h2.util.ParserUtil.SYSTEM_USER; +import static org.h2.util.ParserUtil.TABLE; +import static org.h2.util.ParserUtil.TO; +import static org.h2.util.ParserUtil.TRUE; +import static org.h2.util.ParserUtil.UESCAPE; +import static org.h2.util.ParserUtil.UNION; +import static org.h2.util.ParserUtil.UNIQUE; +import static org.h2.util.ParserUtil.UNKNOWN; +import static org.h2.util.ParserUtil.USER; +import static org.h2.util.ParserUtil.USING; +import static org.h2.util.ParserUtil.VALUE; +import static org.h2.util.ParserUtil.VALUES; +import static org.h2.util.ParserUtil.WHEN; +import static org.h2.util.ParserUtil.WHERE; +import static org.h2.util.ParserUtil.WINDOW; +import static org.h2.util.ParserUtil.WITH; +import static org.h2.util.ParserUtil.YEAR; +import static org.h2.util.ParserUtil._ROWID_; + +import java.io.ByteArrayOutputStream; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.ListIterator; + +import org.h2.api.ErrorCode; +import org.h2.engine.CastDataProvider; +import org.h2.message.DbException; +import org.h2.util.StringUtils; +import org.h2.value.ValueBigint; +import org.h2.value.ValueDecfloat; +import org.h2.value.ValueNumeric; + +/** + * Tokenizer. + */ +public final class Tokenizer { + + private final CastDataProvider provider; + + private final boolean identifiersToUpper; + + private final boolean identifiersToLower; + + private final BitSet nonKeywords; + + Tokenizer(CastDataProvider provider, boolean identifiersToUpper, boolean identifiersToLower, BitSet nonKeywords) { + this.provider = provider; + this.identifiersToUpper = identifiersToUpper; + this.identifiersToLower = identifiersToLower; + this.nonKeywords = nonKeywords; + } + + ArrayList tokenize(String sql, boolean stopOnCloseParen) { + ArrayList tokens = new ArrayList<>(); + int end = sql.length() - 1; + boolean foundUnicode = false; + int lastParameter = 0; + loop: for (int i = 0; i <= end;) { + int tokenStart = i; + char c = sql.charAt(i); + Token token; + switch (c) { + case '!': + if (i < end) { + char c2 = sql.charAt(++i); + if (c2 == '=') { + token = new Token.KeywordToken(tokenStart, NOT_EQUAL); + break; + } + if (c2 == '~') { + token = new Token.KeywordToken(tokenStart, NOT_TILDE); + break; + } + } + throw DbException.getSyntaxError(sql, tokenStart); + case '"': + case '`': + i = readQuotedIdentifier(sql, end, tokenStart, i, c, false, tokens); + continue loop; + case '#': + if (provider.getMode().supportPoundSymbolForColumnNames) { + i = readIdentifier(sql, end, tokenStart, i, c, tokens); + continue loop; + } + throw DbException.getSyntaxError(sql, tokenStart); + case '$': + if (i < end) { + char c2 = sql.charAt(i + 1); + if (c2 == '$') { + i += 2; + int stringEnd = sql.indexOf("$$", i); + if (stringEnd < 0) { + throw DbException.getSyntaxError(sql, tokenStart); + } + token = new Token.CharacterStringToken(tokenStart, sql.substring(i, stringEnd), false); + i = stringEnd + 1; + } else { + i = parseParameterIndex(sql, end, i, tokens); + lastParameter = assignParameterIndex(tokens, lastParameter); + continue loop; + } + } else { + token = new Token.ParameterToken(tokenStart, 0); + } + break; + case '%': + token = new Token.KeywordToken(tokenStart, PERCENT); + break; + case '&': + if (i < end && sql.charAt(i + 1) == '&') { + i++; + token = new Token.KeywordToken(tokenStart, SPATIAL_INTERSECTS); + break; + } + throw DbException.getSyntaxError(sql, tokenStart); + case '\'': + i = readCharacterString(sql, tokenStart, end, i, false, tokens); + continue loop; + case '(': + token = new Token.KeywordToken(tokenStart, OPEN_PAREN); + break; + case ')': + token = new Token.KeywordToken(tokenStart, CLOSE_PAREN); + if (stopOnCloseParen) { + tokens.add(token); + end = skipWhitespace(sql, end, i + 1) - 1; + break loop; + } + break; + case '*': + token = new Token.KeywordToken(tokenStart, ASTERISK); + break; + case '+': + token = new Token.KeywordToken(tokenStart, PLUS_SIGN); + break; + case ',': + token = new Token.KeywordToken(tokenStart, COMMA); + break; + case '-': + if (i < end && sql.charAt(i + 1) == '-') { + i = skipSimpleComment(sql, end, i); + continue loop; + } else { + token = new Token.KeywordToken(tokenStart, MINUS_SIGN); + } + break; + case '.': + if (i < end) { + char c2 = sql.charAt(i + 1); + if (c2 >= '0' && c2 <= '9') { + i = readNumeric(sql, tokenStart, end, i + 1, c2, false, false, tokens); + continue loop; + } + } + token = new Token.KeywordToken(tokenStart, DOT); + break; + case '/': + if (i < end) { + char c2 = sql.charAt(i + 1); + if (c2 == '*') { + i = skipBracketedComment(sql, tokenStart, end, i); + continue loop; + } else if (c2 == '/') { + i = skipSimpleComment(sql, end, i); + continue loop; + } + } + token = new Token.KeywordToken(tokenStart, SLASH); + break; + case '0': + if (i < end) { + char c2 = sql.charAt(i + 1); + if (c2 == 'X' || c2 == 'x') { + i = readHexNumber(sql, provider, tokenStart, end, i + 2, tokens); + continue loop; + } + } + //$FALL-THROUGH$ + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + i = readNumeric(sql, tokenStart, end, i + 1, c, tokens); + continue loop; + case ':': + if (i < end) { + char c2 = sql.charAt(i + 1); + if (c2 == ':') { + i++; + token = new Token.KeywordToken(tokenStart, COLON_COLON); + break; + } else if (c2 == '=') { + i++; + token = new Token.KeywordToken(tokenStart, COLON_EQ); + break; + } + } + token = new Token.KeywordToken(tokenStart, COLON); + break; + case ';': + token = new Token.KeywordToken(tokenStart, SEMICOLON); + break; + case '<': + if (i < end) { + char c2 = sql.charAt(i + 1); + if (c2 == '=') { + i++; + token = new Token.KeywordToken(tokenStart, SMALLER_EQUAL); + break; + } + if (c2 == '>') { + i++; + token = new Token.KeywordToken(tokenStart, NOT_EQUAL); + break; + } + } + token = new Token.KeywordToken(tokenStart, SMALLER); + break; + case '=': + token = new Token.KeywordToken(tokenStart, EQUAL); + break; + case '>': + if (i < end && sql.charAt(i + 1) == '=') { + i++; + token = new Token.KeywordToken(tokenStart, BIGGER_EQUAL); + break; + } + token = new Token.KeywordToken(tokenStart, BIGGER); + break; + case '?': { + if (i + 1 < end && sql.charAt(i + 1) == '?') { + char c3 = sql.charAt(i + 2); + if (c3 == '(') { + i += 2; + token = new Token.KeywordToken(tokenStart, OPEN_BRACKET); + break; + } + if (c3 == ')') { + i += 2; + token = new Token.KeywordToken(tokenStart, CLOSE_BRACKET); + break; + } + } + i = parseParameterIndex(sql, end, i, tokens); + lastParameter = assignParameterIndex(tokens, lastParameter); + continue loop; + } + case '@': + token = new Token.KeywordToken(tokenStart, AT); + break; + case 'A': + case 'a': + i = readA(sql, end, tokenStart, i, tokens); + continue loop; + case 'B': + case 'b': + i = readB(sql, end, tokenStart, i, tokens); + continue loop; + case 'C': + case 'c': + i = readC(sql, end, tokenStart, i, tokens); + continue loop; + case 'D': + case 'd': + i = readD(sql, end, tokenStart, i, tokens); + continue loop; + case 'E': + case 'e': + i = readE(sql, end, tokenStart, i, tokens); + continue loop; + case 'F': + case 'f': + i = readF(sql, end, tokenStart, i, tokens); + continue loop; + case 'G': + case 'g': + i = readG(sql, end, tokenStart, i, tokens); + continue loop; + case 'H': + case 'h': + i = readH(sql, end, tokenStart, i, tokens); + continue loop; + case 'I': + case 'i': + i = readI(sql, end, tokenStart, i, tokens); + continue loop; + case 'J': + case 'j': + i = readJ(sql, end, tokenStart, i, tokens); + continue loop; + case 'K': + case 'k': + i = readK(sql, end, tokenStart, i, tokens); + continue loop; + case 'L': + case 'l': + i = readL(sql, end, tokenStart, i, tokens); + continue loop; + case 'M': + case 'm': + i = readM(sql, end, tokenStart, i, tokens); + continue loop; + case 'N': + case 'n': + if (i < end && sql.charAt(i + 1) == '\'') { + i = readCharacterString(sql, tokenStart, end, i + 1, false, tokens); + } else { + i = readN(sql, end, tokenStart, i, tokens); + } + continue loop; + case 'O': + case 'o': + i = readO(sql, end, tokenStart, i, tokens); + continue loop; + case 'P': + case 'p': + i = readP(sql, end, tokenStart, i, tokens); + continue loop; + case 'Q': + case 'q': + i = readQ(sql, end, tokenStart, i, tokens); + continue loop; + case 'R': + case 'r': + i = readR(sql, end, tokenStart, i, tokens); + continue loop; + case 'S': + case 's': + i = readS(sql, end, tokenStart, i, tokens); + continue loop; + case 'T': + case 't': + i = readT(sql, end, tokenStart, i, tokens); + continue loop; + case 'U': + case 'u': + if (i + 1 < end && sql.charAt(i + 1) == '&') { + char c3 = sql.charAt(i + 2); + if (c3 == '"') { + i = readQuotedIdentifier(sql, end, tokenStart, i + 2, '"', true, tokens); + foundUnicode = true; + continue loop; + } else if (c3 == '\'') { + i = readCharacterString(sql, tokenStart, end, i + 2, true, tokens); + foundUnicode = true; + continue loop; + } + } + i = readU(sql, end, tokenStart, i, tokens); + continue loop; + case 'V': + case 'v': + i = readV(sql, end, tokenStart, i, tokens); + continue loop; + case 'W': + case 'w': + i = readW(sql, end, tokenStart, i, tokens); + continue loop; + case 'X': + case 'x': + if (i < end && sql.charAt(i + 1) == '\'') { + i = readBinaryString(sql, tokenStart, end, i + 1, tokens); + } else { + i = readIdentifier(sql, end, tokenStart, i, c, tokens); + } + continue loop; + case 'Y': + case 'y': + i = readY(sql, end, tokenStart, i, tokens); + continue loop; + case 'Z': + case 'z': + i = readIdentifier(sql, end, tokenStart, i, c, tokens); + continue loop; + case '[': + if (provider.getMode().squareBracketQuotedNames) { + int identifierEnd = sql.indexOf(']', ++i); + if (identifierEnd < 0) { + throw DbException.getSyntaxError(sql, tokenStart); + } + token = new Token.IdentifierToken(tokenStart, sql.substring(i, identifierEnd), true, false); + i = identifierEnd; + } else { + token = new Token.KeywordToken(tokenStart, OPEN_BRACKET); + } + break; + case ']': + token = new Token.KeywordToken(tokenStart, CLOSE_BRACKET); + break; + case '_': + i = read_(sql, end, tokenStart, i, tokens); + continue loop; + case '{': + token = new Token.KeywordToken(tokenStart, OPEN_BRACE); + break; + case '|': + if (i < end && sql.charAt(++i) == '|') { + token = new Token.KeywordToken(tokenStart, CONCATENATION); + break; + } + throw DbException.getSyntaxError(sql, tokenStart); + case '}': + token = new Token.KeywordToken(tokenStart, CLOSE_BRACE); + break; + case '~': + token = new Token.KeywordToken(tokenStart, TILDE); + break; + default: + if (c <= ' ') { + i++; + continue loop; + } else { + int cp = Character.isHighSurrogate(c) ? sql.codePointAt(i++) : c; + if (Character.isSpaceChar(cp)) { + continue loop; + } + if (Character.isJavaIdentifierStart(cp)) { + i = readIdentifier(sql, end, tokenStart, i, cp, tokens); + continue loop; + } + throw DbException.getSyntaxError(sql, tokenStart); + } + } + tokens.add(token); + i++; + } + if (foundUnicode) { + processUescape(sql, tokens); + } + tokens.add(new Token.EndOfInputToken(end + 1)); + return tokens; + } + + private int readIdentifier(String sql, int end, int tokenStart, int i, int cp, ArrayList tokens) { + if (cp >= Character.MIN_SUPPLEMENTARY_CODE_POINT) { + i++; + } + int endIndex = findIdentifierEnd(sql, end, i + Character.charCount(cp) - 1); + tokens.add(new Token.IdentifierToken(tokenStart, extractIdentifier(sql, tokenStart, endIndex), false, false)); + return endIndex; + } + + private int readA(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type; + if (length == 2) { + type = (sql.charAt(tokenStart + 1) & 0xffdf) == 'S' ? AS : IDENTIFIER; + } else { + if (eq("ALL", sql, tokenStart, length)) { + type = ALL; + } else if (eq("AND", sql, tokenStart, length)) { + type = AND; + } else if (eq("ANY", sql, tokenStart, length)) { + type = ANY; + } else if (eq("ARRAY", sql, tokenStart, length)) { + type = ARRAY; + } else if (eq("ASYMMETRIC", sql, tokenStart, length)) { + type = ASYMMETRIC; + } else if (eq("AUTHORIZATION", sql, tokenStart, length)) { + type = AUTHORIZATION; + } else { + type = IDENTIFIER; + } + } + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int readB(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type = eq("BETWEEN", sql, tokenStart, length) ? BETWEEN : IDENTIFIER; + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int readC(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type; + if (eq("CASE", sql, tokenStart, length)) { + type = CASE; + } else if (eq("CAST", sql, tokenStart, length)) { + type = CAST; + } else if (eq("CHECK", sql, tokenStart, length)) { + type = CHECK; + } else if (eq("CONSTRAINT", sql, tokenStart, length)) { + type = CONSTRAINT; + } else if (eq("CROSS", sql, tokenStart, length)) { + type = CROSS; + } else if (length >= 12 && eq("CURRENT_", sql, tokenStart, 8)) { + type = getTokenTypeCurrent(sql, tokenStart, length); + } else { + type = IDENTIFIER; + } + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private static int getTokenTypeCurrent(String s, int tokenStart, int length) { + tokenStart += 8; + switch (length) { + case 12: + if (eqCurrent("CURRENT_DATE", s, tokenStart, length)) { + return CURRENT_DATE; + } else if (eqCurrent("CURRENT_PATH", s, tokenStart, length)) { + return CURRENT_PATH; + } else if (eqCurrent("CURRENT_ROLE", s, tokenStart, length)) { + return CURRENT_ROLE; + } else if (eqCurrent("CURRENT_TIME", s, tokenStart, length)) { + return CURRENT_TIME; + } else if (eqCurrent("CURRENT_USER", s, tokenStart, length)) { + return CURRENT_USER; + } + break; + case 14: + if (eqCurrent("CURRENT_SCHEMA", s, tokenStart, length)) { + return CURRENT_SCHEMA; + } + break; + case 15: + if (eqCurrent("CURRENT_CATALOG", s, tokenStart, length)) { + return CURRENT_CATALOG; + } + break; + case 17: + if (eqCurrent("CURRENT_TIMESTAMP", s, tokenStart, length)) { + return CURRENT_TIMESTAMP; + } + } + return IDENTIFIER; + } + + private static boolean eqCurrent(String expected, String s, int start, int length) { + for (int i = 8; i < length; i++) { + if (expected.charAt(i) != (s.charAt(start++) & 0xffdf)) { + return false; + } + } + return true; + } + + private int readD(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type; + if (eq("DAY", sql, tokenStart, length)) { + type = DAY; + } else if (eq("DEFAULT", sql, tokenStart, length)) { + type = DEFAULT; + } else if (eq("DISTINCT", sql, tokenStart, length)) { + type = DISTINCT; + } else { + type = IDENTIFIER; + } + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int readE(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type; + if (eq("ELSE", sql, tokenStart, length)) { + type = ELSE; + } else if (eq("END", sql, tokenStart, length)) { + type = END; + } else if (eq("EXCEPT", sql, tokenStart, length)) { + type = EXCEPT; + } else if (eq("EXISTS", sql, tokenStart, length)) { + type = EXISTS; + } else { + type = IDENTIFIER; + } + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int readF(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type; + if (eq("FETCH", sql, tokenStart, length)) { + type = FETCH; + } else if (eq("FROM", sql, tokenStart, length)) { + type = FROM; + } else if (eq("FOR", sql, tokenStart, length)) { + type = FOR; + } else if (eq("FOREIGN", sql, tokenStart, length)) { + type = FOREIGN; + } else if (eq("FULL", sql, tokenStart, length)) { + type = FULL; + } else if (eq("FALSE", sql, tokenStart, length)) { + type = FALSE; + } else { + type = IDENTIFIER; + } + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int readG(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type = eq("GROUP", sql, tokenStart, length) ? GROUP : IDENTIFIER; + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int readH(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type; + if (eq("HAVING", sql, tokenStart, length)) { + type = HAVING; + } else if (eq("HOUR", sql, tokenStart, length)) { + type = HOUR; + } else { + type = IDENTIFIER; + } + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int readI(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type; + if (length == 2) { + switch ((sql.charAt(tokenStart + 1) & 0xffdf)) { + case 'F': + type = IF; + break; + case 'N': + type = IN; + break; + case 'S': + type = IS; + break; + default: + type = IDENTIFIER; + } + } else { + if (eq("INNER", sql, tokenStart, length)) { + type = INNER; + } else if (eq("INTERSECT", sql, tokenStart, length)) { + type = INTERSECT; + } else if (eq("INTERVAL", sql, tokenStart, length)) { + type = INTERVAL; + } else { + type = IDENTIFIER; + } + } + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int readJ(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type = eq("JOIN", sql, tokenStart, length) ? JOIN : IDENTIFIER; + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int readK(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type = eq("KEY", sql, tokenStart, length) ? KEY : IDENTIFIER; + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int readL(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type; + if (eq("LEFT", sql, tokenStart, length)) { + type = LEFT; + } else if (eq("LIMIT", sql, tokenStart, length)) { + type = provider.getMode().limit ? LIMIT : IDENTIFIER; + } else if (eq("LIKE", sql, tokenStart, length)) { + type = LIKE; + } else if (eq("LOCALTIME", sql, tokenStart, length)) { + type = LOCALTIME; + } else if (eq("LOCALTIMESTAMP", sql, tokenStart, length)) { + type = LOCALTIMESTAMP; + } else { + type = IDENTIFIER; + } + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int readM(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type; + if (eq("MINUS", sql, tokenStart, length)) { + type = provider.getMode().minusIsExcept ? MINUS : IDENTIFIER; + } else if (eq("MINUTE", sql, tokenStart, length)) { + type = MINUTE; + } else if (eq("MONTH", sql, tokenStart, length)) { + type = MONTH; + } else { + type = IDENTIFIER; + } + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int readN(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type; + if (eq("NOT", sql, tokenStart, length)) { + type = NOT; + } else if (eq("NATURAL", sql, tokenStart, length)) { + type = NATURAL; + } else if (eq("NULL", sql, tokenStart, length)) { + type = NULL; + } else { + type = IDENTIFIER; + } + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int readO(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type; + if (length == 2) { + switch ((sql.charAt(tokenStart + 1) & 0xffdf)) { + case 'N': + type = ON; + break; + case 'R': + type = OR; + break; + default: + type = IDENTIFIER; + } + } else { + if (eq("OFFSET", sql, tokenStart, length)) { + type = OFFSET; + } else if (eq("ORDER", sql, tokenStart, length)) { + type = ORDER; + } else { + type = IDENTIFIER; + } + } + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int readP(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type = eq("PRIMARY", sql, tokenStart, length) ? PRIMARY : IDENTIFIER; + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int readQ(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type = eq("QUALIFY", sql, tokenStart, length) ? QUALIFY : IDENTIFIER; + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int readR(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type; + if (eq("RIGHT", sql, tokenStart, length)) { + type = RIGHT; + } else if (eq("ROW", sql, tokenStart, length)) { + type = ROW; + } else if (eq("ROWNUM", sql, tokenStart, length)) { + type = ROWNUM; + } else { + type = IDENTIFIER; + } + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int readS(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type; + if (eq("SECOND", sql, tokenStart, length)) { + type = SECOND; + } else if (eq("SELECT", sql, tokenStart, length)) { + type = SELECT; + } else if (eq("SESSION_USER", sql, tokenStart, length)) { + type = SESSION_USER; + } else if (eq("SET", sql, tokenStart, length)) { + type = SET; + } else if (eq("SOME", sql, tokenStart, length)) { + type = SOME; + } else if (eq("SYMMETRIC", sql, tokenStart, length)) { + type = SYMMETRIC; + } else if (eq("SYSTEM_USER", sql, tokenStart, length)) { + type = SYSTEM_USER; + } else { + type = IDENTIFIER; + } + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int readT(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type; + if (length == 2) { + type = (sql.charAt(tokenStart + 1) & 0xffdf) == 'O' ? TO : IDENTIFIER; + } else { + if (eq("TABLE", sql, tokenStart, length)) { + type = TABLE; + } else if (eq("TRUE", sql, tokenStart, length)) { + type = TRUE; + } else { + type = IDENTIFIER; + } + } + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int readU(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type; + if (eq("UESCAPE", sql, tokenStart, length)) { + type = UESCAPE; + } else if (eq("UNION", sql, tokenStart, length)) { + type = UNION; + } else if (eq("UNIQUE", sql, tokenStart, length)) { + type = UNIQUE; + } else if (eq("UNKNOWN", sql, tokenStart, length)) { + type = UNKNOWN; + } else if (eq("USER", sql, tokenStart, length)) { + type = USER; + } else if (eq("USING", sql, tokenStart, length)) { + type = USING; + } else { + type = IDENTIFIER; + } + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int readV(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type; + if (eq("VALUE", sql, tokenStart, length)) { + type = VALUE; + } else if (eq("VALUES", sql, tokenStart, length)) { + type = VALUES; + } else { + type = IDENTIFIER; + } + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int readW(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type; + if (eq("WHEN", sql, tokenStart, length)) { + type = WHEN; + } else if (eq("WHERE", sql, tokenStart, length)) { + type = WHERE; + } else if (eq("WINDOW", sql, tokenStart, length)) { + type = WINDOW; + } else if (eq("WITH", sql, tokenStart, length)) { + type = WITH; + } else { + type = IDENTIFIER; + } + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int readY(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int length = endIndex - tokenStart; + int type = eq("YEAR", sql, tokenStart, length) ? YEAR : IDENTIFIER; + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int read_(String sql, int end, int tokenStart, int i, ArrayList tokens) { + int endIndex = findIdentifierEnd(sql, end, i); + int type = endIndex - tokenStart == 7 && "_ROWID_".regionMatches(true, 1, sql, tokenStart + 1, 6) ? _ROWID_ + : IDENTIFIER; + return readIdentifierOrKeyword(sql, tokenStart, tokens, endIndex, type); + } + + private int readIdentifierOrKeyword(String sql, int tokenStart, ArrayList tokens, int endIndex, int type) { + Token token; + if (type == IDENTIFIER) { + token = new Token.IdentifierToken(tokenStart, extractIdentifier(sql, tokenStart, endIndex), false, false); + } else if (nonKeywords != null && nonKeywords.get(type)) { + token = new Token.KeywordOrIdentifierToken(tokenStart, type, extractIdentifier(sql, tokenStart, endIndex)); + } else { + token = new Token.KeywordToken(tokenStart, type); + } + tokens.add(token); + return endIndex; + } + + private static boolean eq(String expected, String s, int start, int length) { + if (length != expected.length()) { + return false; + } + for (int i = 1; i < length; i++) { + if (expected.charAt(i) != (s.charAt(++start) & 0xffdf)) { + return false; + } + } + return true; + } + + private int findIdentifierEnd(String sql, int end, int i) { + i++; + for (;;) { + int cp; + if (i > end || (!Character.isJavaIdentifierPart(cp = sql.codePointAt(i)) + && (cp != '#' || !provider.getMode().supportPoundSymbolForColumnNames))) { + break; + } + i += Character.charCount(cp); + } + return i; + } + + private String extractIdentifier(String sql, int beginIndex, int endIndex) { + return convertCase(sql.substring(beginIndex, endIndex)); + } + + private int readQuotedIdentifier(String sql, int end, int tokenStart, int i, char c, boolean unicode, + ArrayList tokens) { + int identifierEnd = sql.indexOf(c, ++i); + if (identifierEnd < 0) { + throw DbException.getSyntaxError(sql, tokenStart); + } + String s = sql.substring(i, identifierEnd); + i = identifierEnd + 1; + if (i <= end && sql.charAt(i) == c) { + StringBuilder builder = new StringBuilder(s); + do { + identifierEnd = sql.indexOf(c, i + 1); + if (identifierEnd < 0) { + throw DbException.getSyntaxError(sql, tokenStart); + } + builder.append(sql, i, identifierEnd); + i = identifierEnd + 1; + } while (i <= end && sql.charAt(i) == c); + s = builder.toString(); + } + if (c == '`') { + s = convertCase(s); + } + tokens.add(new Token.IdentifierToken(tokenStart, s, true, unicode)); + return i; + } + + private String convertCase(String s) { + if (identifiersToUpper) { + s = StringUtils.toUpperEnglish(s); + } else if (identifiersToLower) { + s = StringUtils.toLowerEnglish(s); + } + return s; + } + + private static int readBinaryString(String sql, int tokenStart, int end, int i, ArrayList tokens) { + ByteArrayOutputStream result = new ByteArrayOutputStream(); + int stringEnd; + do { + stringEnd = sql.indexOf('\'', ++i); + if (stringEnd < 0 || stringEnd < end && sql.charAt(stringEnd + 1) == '\'') { + throw DbException.getSyntaxError(sql, tokenStart); + } + StringUtils.convertHexWithSpacesToBytes(result, sql, i, stringEnd); + i = skipWhitespace(sql, end, stringEnd + 1); + } while (i <= end && sql.charAt(i) == '\''); + tokens.add(new Token.BinaryStringToken(tokenStart, result.toByteArray())); + return i; + } + + private static int readCharacterString(String sql, int tokenStart, int end, int i, boolean unicode, + ArrayList tokens) { + String s = null; + StringBuilder builder = null; + int stringEnd; + do { + stringEnd = sql.indexOf('\'', ++i); + if (stringEnd < 0) { + throw DbException.getSyntaxError(sql, tokenStart); + } + if (s == null) { + s = sql.substring(i, stringEnd); + } else { + if (builder == null) { + builder = new StringBuilder(s); + } + builder.append(sql, i, stringEnd); + } + i = stringEnd + 1; + if (i <= end && sql.charAt(i) == '\'') { + if (builder == null) { + builder = new StringBuilder(s); + } + do { + stringEnd = sql.indexOf('\'', i + 1); + if (stringEnd < 0) { + throw DbException.getSyntaxError(sql, tokenStart); + } + builder.append(sql, i, stringEnd); + i = stringEnd + 1; + } while (i <= end && sql.charAt(i) == '\''); + } + i = skipWhitespace(sql, end, i); + } while (i <= end && sql.charAt(i) == '\''); + if (builder != null) { + s = builder.toString(); + } + tokens.add(new Token.CharacterStringToken(tokenStart, s, unicode)); + return i; + } + + private static int skipWhitespace(String sql, int end, int i) { + while (i <= end) { + int cp = sql.codePointAt(i); + if (!Character.isWhitespace(cp)) { + if (cp == '/' && i < end) { + char c2 = sql.charAt(i + 1); + if (c2 == '*') { + i = skipBracketedComment(sql, i, end, i); + continue; + } else if (c2 == '/') { + i = skipSimpleComment(sql, end, i); + continue; + } + } + break; + } + i += Character.charCount(cp); + } + return i; + } + + private static int readHexNumber(String sql, CastDataProvider provider, int tokenStart, int end, int i, + ArrayList tokens) { + if (provider.getMode().zeroExLiteralsAreBinaryStrings) { + int start = i; + for (char c; i <= end + && (((c = sql.charAt(i)) >= '0' && c <= '9') || ((c &= 0xffdf) >= 'A' && c <= 'F'));) { + i++; + } + if (i <= end && Character.isJavaIdentifierPart(sql.codePointAt(i))) { + throw DbException.get(ErrorCode.HEX_STRING_WRONG_1, sql.substring(start, i + 1)); + } + tokens.add(new Token.BinaryStringToken(start, StringUtils.convertHexToBytes(sql.substring(start, i)))); + return i; + } else { + if (i > end) { + throw DbException.getSyntaxError(sql, tokenStart, "Hex number"); + } + int start = i; + long number = 0; + char c; + do { + c = sql.charAt(i); + if (c >= '0' && c <= '9') { + number = (number << 4) + c - '0'; + // Convert a-z to A-Z + } else if ((c &= 0xffdf) >= 'A' && c <= 'F') { + number = (number << 4) + c - ('A' - 10); + } else if (i == start) { + throw DbException.getSyntaxError(sql, tokenStart, "Hex number"); + } else { + break; + } + if (number > Integer.MAX_VALUE) { + while (++i <= end + && (((c = sql.charAt(i)) >= '0' && c <= '9') || ((c &= 0xffdf) >= 'A' && c <= 'F'))) { + } + return finishBigInteger(sql, tokenStart, end, i, start, i <= end && c == 'L', 16, tokens); + } + } while (++i <= end); + + boolean bigint = i <= end && c == 'L'; + if (bigint) { + i++; + } + if (i <= end && Character.isJavaIdentifierPart(sql.codePointAt(i))) { + throw DbException.getSyntaxError(sql, tokenStart, "Hex number"); + } + tokens.add(bigint ? new Token.BigintToken(start, number) : new Token.IntegerToken(start, (int) number)); + return i; + } + } + + private static int readNumeric(String sql, int tokenStart, int end, int i, char c, ArrayList tokens) { + long number = c - '0'; + for (; i <= end; i++) { + c = sql.charAt(i); + if (c < '0' || c > '9') { + switch (c) { + case '.': + return readNumeric(sql, tokenStart, end, i, c, false, false, tokens); + case 'E': + case 'e': + return readNumeric(sql, tokenStart, end, i, c, false, true, tokens); + case 'L': + case 'l': + return finishBigInteger(sql, tokenStart, end, i, tokenStart, true, 10, tokens); + } + break; + } + number = number * 10 + (c - '0'); + if (number > Integer.MAX_VALUE) { + return readNumeric(sql, tokenStart, end, i, c, true, false, tokens); + } + } + tokens.add(new Token.IntegerToken(tokenStart, (int) number)); + return i; + } + + private static int readNumeric(String sql, int tokenStart, int end, int i, char c, boolean integer, + boolean approximate, ArrayList tokens) { + if (!approximate) { + while (++i <= end) { + c = sql.charAt(i); + if (c == '.') { + integer = false; + } else if (c < '0' || c > '9') { + break; + } + } + } + if (i <= end && (c == 'E' || c == 'e')) { + integer = false; + approximate = true; + if (i == end) { + throw DbException.getSyntaxError(sql, tokenStart); + } + c = sql.charAt(++i); + if (c == '+' || c == '-') { + if (i == end) { + throw DbException.getSyntaxError(sql, tokenStart); + } + c = sql.charAt(++i); + } + if (c < '0' || c > '9') { + throw DbException.getSyntaxError(sql, tokenStart); + } + while (++i <= end && (c = sql.charAt(i)) >= '0' && c <= '9') { + // go until the first non-number + } + } + if (integer) { + return finishBigInteger(sql, tokenStart, end, i, tokenStart, i < end && c == 'L' || c == 'l', 10, tokens); + } + BigDecimal bd; + String string = sql.substring(tokenStart, i); + try { + bd = new BigDecimal(string); + } catch (NumberFormatException e) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, e, string); + } + tokens.add(new Token.ValueToken(tokenStart, approximate ? ValueDecfloat.get(bd) : ValueNumeric.get(bd))); + return i; + } + + private static int finishBigInteger(String sql, int tokenStart, int end, int i, int start, boolean asBigint, + int radix, ArrayList tokens) { + int endIndex = i; + if (asBigint) { + i++; + } + if (radix == 16 && i <= end && Character.isJavaIdentifierPart(sql.codePointAt(i))) { + throw DbException.getSyntaxError(sql, tokenStart, "Hex number"); + } + BigInteger bigInteger = new BigInteger(sql.substring(start, endIndex), radix); + Token token; + if (bigInteger.compareTo(ValueBigint.MAX_BI) > 0) { + if (asBigint) { + throw DbException.getSyntaxError(sql, tokenStart); + } + token = new Token.ValueToken(tokenStart, ValueNumeric.get(bigInteger)); + } else { + token = new Token.BigintToken(start, bigInteger.longValue()); + } + tokens.add(token); + return i; + } + + private static int skipBracketedComment(String sql, int tokenStart, int end, int i) { + i += 2; + for (int level = 1; level > 0;) { + for (;;) { + if (i >= end) { + throw DbException.getSyntaxError(sql, tokenStart); + } + char c = sql.charAt(i++); + if (c == '*') { + if (sql.charAt(i) == '/') { + level--; + i++; + break; + } + } else if (c == '/' && sql.charAt(i) == '*') { + level++; + i++; + } + } + } + return i; + } + + private static int skipSimpleComment(String sql, int end, int i) { + i += 2; + for (char c; i <= end && (c = sql.charAt(i)) != '\n' && c != '\r'; i++) { + // + } + return i; + } + + private static int parseParameterIndex(String sql, int end, int i, ArrayList tokens) { + int tokenStart = i; + long number = 0; + for (char c; ++i <= end && (c = sql.charAt(i)) >= '0' && c <= '9';) { + number = number * 10 + (c - '0'); + if (number > Integer.MAX_VALUE) { + throw DbException.getInvalidValueException("parameter index", number); + } + } + if (i > tokenStart + 1 && number == 0) { + throw DbException.getInvalidValueException("parameter index", number); + } + tokens.add(new Token.ParameterToken(tokenStart, (int) number)); + return i; + } + + private static int assignParameterIndex(ArrayList tokens, int lastParameter) { + Token.ParameterToken parameter = (Token.ParameterToken) tokens.get(tokens.size() - 1); + if (parameter.index == 0) { + if (lastParameter < 0) { + throw DbException.get(ErrorCode.CANNOT_MIX_INDEXED_AND_UNINDEXED_PARAMS); + } + parameter.index = ++lastParameter; + } else if (lastParameter > 0) { + throw DbException.get(ErrorCode.CANNOT_MIX_INDEXED_AND_UNINDEXED_PARAMS); + } else { + lastParameter = -1; + } + return lastParameter; + } + + private static void processUescape(String sql, ArrayList tokens) { + ListIterator i = tokens.listIterator(); + while (i.hasNext()) { + Token token = i.next(); + if (token.needsUnicodeConversion()) { + int uescape = '\\'; + condition: if (i.hasNext()) { + Token t2 = i.next(); + if (t2.tokenType() == UESCAPE) { + i.remove(); + if (i.hasNext()) { + Token t3 = i.next(); + i.remove(); + if (t3 instanceof Token.CharacterStringToken) { + String s = ((Token.CharacterStringToken) t3).string; + if (s.codePointCount(0, s.length()) == 1) { + int escape = s.codePointAt(0); + if (!Character.isWhitespace(escape) && (escape < '0' || escape > '9') + && (escape < 'A' || escape > 'F') && (escape < 'a' || escape > 'f')) { + switch (escape) { + default: + uescape = escape; + break condition; + case '"': + case '\'': + case '+': + } + } + } + } + } + throw DbException.getSyntaxError(sql, t2.start() + 7, "''"); + } + } + token.convertUnicode(uescape); + } + } + } + +} diff --git a/h2/src/main/org/h2/command/ddl/AlterDomain.java b/h2/src/main/org/h2/command/ddl/AlterDomain.java new file mode 100644 index 0000000..4b96f68 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/AlterDomain.java @@ -0,0 +1,111 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import java.util.function.BiPredicate; + +import org.h2.api.ErrorCode; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Domain; +import org.h2.schema.Schema; +import org.h2.table.Column; +import org.h2.table.Table; + +/** + * The base class for ALTER DOMAIN commands. + */ +public abstract class AlterDomain extends SchemaOwnerCommand { + + /** + * Processes all columns and domains that use the specified domain. + * + * @param session + * the session + * @param domain + * the domain to process + * @param columnProcessor + * column handler + * @param domainProcessor + * domain handler + * @param recompileExpressions + * whether processed expressions need to be recompiled + */ + public static void forAllDependencies(SessionLocal session, Domain domain, + BiPredicate columnProcessor, BiPredicate domainProcessor, + boolean recompileExpressions) { + Database db = session.getDatabase(); + for (Schema schema : db.getAllSchemasNoMeta()) { + for (Domain targetDomain : schema.getAllDomains()) { + if (targetDomain.getDomain() == domain) { + if (domainProcessor == null || domainProcessor.test(domain, targetDomain)) { + if (recompileExpressions) { + domain.prepareExpressions(session); + } + db.updateMeta(session, targetDomain); + } + } + } + for (Table t : schema.getAllTablesAndViews(null)) { + if (forTable(session, domain, columnProcessor, recompileExpressions, t)) { + db.updateMeta(session, t); + } + } + } + for (Table t : session.getLocalTempTables()) { + forTable(session, domain, columnProcessor, recompileExpressions, t); + } + } + + private static boolean forTable(SessionLocal session, Domain domain, BiPredicate columnProcessor, + boolean recompileExpressions, Table t) { + boolean modified = false; + for (Column targetColumn : t.getColumns()) { + if (targetColumn.getDomain() == domain) { + boolean m = columnProcessor == null || columnProcessor.test(domain, targetColumn); + if (m) { + if (recompileExpressions) { + targetColumn.prepareExpressions(session); + } + modified = true; + } + } + } + return modified; + } + + String domainName; + + boolean ifDomainExists; + + AlterDomain(SessionLocal session, Schema schema) { + super(session, schema); + } + + public final void setDomainName(String domainName) { + this.domainName = domainName; + } + + public final void setIfDomainExists(boolean b) { + ifDomainExists = b; + } + + @Override + final long update(Schema schema) { + Domain domain = getSchema().findDomain(domainName); + if (domain == null) { + if (ifDomainExists) { + return 0; + } + throw DbException.get(ErrorCode.DOMAIN_NOT_FOUND_1, domainName); + } + return update(schema, domain); + } + + abstract long update(Schema schema, Domain domain); + +} diff --git a/h2/src/main/org/h2/command/ddl/AlterDomainAddConstraint.java b/h2/src/main/org/h2/command/ddl/AlterDomainAddConstraint.java new file mode 100644 index 0000000..d8b8bce --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/AlterDomainAddConstraint.java @@ -0,0 +1,105 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.constraint.ConstraintDomain; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.message.DbException; +import org.h2.schema.Domain; +import org.h2.schema.Schema; + +/** + * This class represents the statement ALTER DOMAIN ADD CONSTRAINT + */ +public class AlterDomainAddConstraint extends AlterDomain { + + private String constraintName; + private Expression checkExpression; + private String comment; + private boolean checkExisting; + private final boolean ifNotExists; + + public AlterDomainAddConstraint(SessionLocal session, Schema schema, boolean ifNotExists) { + super(session, schema); + this.ifNotExists = ifNotExists; + } + + private String generateConstraintName(Domain domain) { + if (constraintName == null) { + constraintName = getSchema().getUniqueDomainConstraintName(session, domain); + } + return constraintName; + } + + @Override + long update(Schema schema, Domain domain) { + try { + return tryUpdate(schema, domain); + } finally { + getSchema().freeUniqueName(constraintName); + } + } + + /** + * Try to execute the statement. + * + * @param schema the schema + * @param domain the domain + * @return the update count + */ + private int tryUpdate(Schema schema, Domain domain) { + if (constraintName != null && schema.findConstraint(session, constraintName) != null) { + if (ifNotExists) { + return 0; + } + throw DbException.get(ErrorCode.CONSTRAINT_ALREADY_EXISTS_1, constraintName); + } + Database db = session.getDatabase(); + db.lockMeta(session); + + int id = getObjectId(); + String name = generateConstraintName(domain); + ConstraintDomain constraint = new ConstraintDomain(schema, id, name, domain); + constraint.setExpression(session, checkExpression); + if (checkExisting) { + constraint.checkExistingData(session); + } + constraint.setComment(comment); + db.addSchemaObject(session, constraint); + domain.addConstraint(constraint); + return 0; + } + + public void setConstraintName(String constraintName) { + this.constraintName = constraintName; + } + + public String getConstraintName() { + return constraintName; + } + + @Override + public int getType() { + return CommandInterface.ALTER_DOMAIN_ADD_CONSTRAINT; + } + + public void setCheckExpression(Expression expression) { + this.checkExpression = expression; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public void setCheckExisting(boolean b) { + this.checkExisting = b; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/AlterDomainDropConstraint.java b/h2/src/main/org/h2/command/ddl/AlterDomainDropConstraint.java new file mode 100644 index 0000000..df9efaa --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/AlterDomainDropConstraint.java @@ -0,0 +1,54 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.constraint.Constraint; +import org.h2.constraint.Constraint.Type; +import org.h2.constraint.ConstraintDomain; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Domain; +import org.h2.schema.Schema; + +/** + * This class represents the statement ALTER DOMAIN DROP CONSTRAINT + */ +public class AlterDomainDropConstraint extends AlterDomain { + + private String constraintName; + private final boolean ifConstraintExists; + + public AlterDomainDropConstraint(SessionLocal session, Schema schema, boolean ifConstraintExists) { + super(session, schema); + this.ifConstraintExists = ifConstraintExists; + } + + public void setConstraintName(String string) { + constraintName = string; + } + + @Override + long update(Schema schema, Domain domain) { + Constraint constraint = schema.findConstraint(session, constraintName); + if (constraint == null || constraint.getConstraintType() != Type.DOMAIN + || ((ConstraintDomain) constraint).getDomain() != domain) { + if (!ifConstraintExists) { + throw DbException.get(ErrorCode.CONSTRAINT_NOT_FOUND_1, constraintName); + } + } else { + session.getDatabase().removeSchemaObject(session, constraint); + } + return 0; + } + + @Override + public int getType() { + return CommandInterface.ALTER_DOMAIN_DROP_CONSTRAINT; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/AlterDomainExpressions.java b/h2/src/main/org/h2/command/ddl/AlterDomainExpressions.java new file mode 100644 index 0000000..a5d519e --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/AlterDomainExpressions.java @@ -0,0 +1,92 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.command.CommandInterface; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.message.DbException; +import org.h2.schema.Domain; +import org.h2.schema.Schema; +import org.h2.table.Column; +import org.h2.table.ColumnTemplate; + +/** + * This class represents the statements + * ALTER DOMAIN SET DEFAULT + * ALTER DOMAIN DROP DEFAULT + * ALTER DOMAIN SET ON UPDATE + * ALTER DOMAIN DROP ON UPDATE + */ +public class AlterDomainExpressions extends AlterDomain { + + private final int type; + + private Expression expression; + + public AlterDomainExpressions(SessionLocal session, Schema schema, int type) { + super(session, schema); + this.type = type; + } + + public void setExpression(Expression expression) { + this.expression = expression; + } + + @Override + long update(Schema schema, Domain domain) { + switch (type) { + case CommandInterface.ALTER_DOMAIN_DEFAULT: + domain.setDefaultExpression(session, expression); + break; + case CommandInterface.ALTER_DOMAIN_ON_UPDATE: + domain.setOnUpdateExpression(session, expression); + break; + default: + throw DbException.getInternalError("type=" + type); + } + if (expression != null) { + forAllDependencies(session, domain, this::copyColumn, this::copyDomain, true); + } + session.getDatabase().updateMeta(session, domain); + return 0; + } + + private boolean copyColumn(Domain domain, Column targetColumn) { + return copyExpressions(session, domain, targetColumn); + } + + private boolean copyDomain(Domain domain, Domain targetDomain) { + return copyExpressions(session, domain, targetDomain); + } + + private boolean copyExpressions(SessionLocal session, Domain domain, ColumnTemplate targetColumn) { + switch (type) { + case CommandInterface.ALTER_DOMAIN_DEFAULT: { + Expression e = domain.getDefaultExpression(); + if (e != null && targetColumn.getDefaultExpression() == null) { + targetColumn.setDefaultExpression(session, e); + return true; + } + break; + } + case CommandInterface.ALTER_DOMAIN_ON_UPDATE: { + Expression e = domain.getOnUpdateExpression(); + if (e != null && targetColumn.getOnUpdateExpression() == null) { + targetColumn.setOnUpdateExpression(session, e); + return true; + } + } + } + return false; + } + + @Override + public int getType() { + return type; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/AlterDomainRename.java b/h2/src/main/org/h2/command/ddl/AlterDomainRename.java new file mode 100644 index 0000000..f0b65e9 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/AlterDomainRename.java @@ -0,0 +1,52 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Domain; +import org.h2.schema.Schema; + +/** + * This class represents the statement + * ALTER DOMAIN RENAME + */ +public class AlterDomainRename extends AlterDomain { + + private String newDomainName; + + public AlterDomainRename(SessionLocal session, Schema schema) { + super(session, schema); + } + + public void setNewDomainName(String name) { + newDomainName = name; + } + + @Override + long update(Schema schema, Domain domain) { + Domain d = schema.findDomain(newDomainName); + if (d != null) { + if (domain != d) { + throw DbException.get(ErrorCode.DOMAIN_ALREADY_EXISTS_1, newDomainName); + } + if (newDomainName.equals(domain.getName())) { + return 0; + } + } + session.getDatabase().renameSchemaObject(session, domain, newDomainName); + forAllDependencies(session, domain, null, null, false); + return 0; + } + + @Override + public int getType() { + return CommandInterface.ALTER_DOMAIN_RENAME; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/AlterDomainRenameConstraint.java b/h2/src/main/org/h2/command/ddl/AlterDomainRenameConstraint.java new file mode 100644 index 0000000..3f4cfba --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/AlterDomainRenameConstraint.java @@ -0,0 +1,59 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.constraint.Constraint; +import org.h2.constraint.Constraint.Type; +import org.h2.constraint.ConstraintDomain; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Domain; +import org.h2.schema.Schema; + +/** + * This class represents the statement + * ALTER DOMAIN RENAME CONSTRAINT + */ +public class AlterDomainRenameConstraint extends AlterDomain { + + private String constraintName; + private String newConstraintName; + + public AlterDomainRenameConstraint(SessionLocal session, Schema schema) { + super(session, schema); + } + + public void setConstraintName(String string) { + constraintName = string; + } + + public void setNewConstraintName(String newName) { + this.newConstraintName = newName; + } + + @Override + long update(Schema schema, Domain domain) { + Constraint constraint = getSchema().findConstraint(session, constraintName); + if (constraint == null || constraint.getConstraintType() != Type.DOMAIN + || ((ConstraintDomain) constraint).getDomain() != domain) { + throw DbException.get(ErrorCode.CONSTRAINT_NOT_FOUND_1, constraintName); + } + if (getSchema().findConstraint(session, newConstraintName) != null + || newConstraintName.equals(constraintName)) { + throw DbException.get(ErrorCode.CONSTRAINT_ALREADY_EXISTS_1, newConstraintName); + } + session.getDatabase().renameSchemaObject(session, constraint, newConstraintName); + return 0; + } + + @Override + public int getType() { + return CommandInterface.ALTER_DOMAIN_RENAME_CONSTRAINT; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/AlterIndexRename.java b/h2/src/main/org/h2/command/ddl/AlterIndexRename.java new file mode 100644 index 0000000..a09d820 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/AlterIndexRename.java @@ -0,0 +1,74 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Database; +import org.h2.engine.Right; +import org.h2.engine.SessionLocal; +import org.h2.index.Index; +import org.h2.message.DbException; +import org.h2.schema.Schema; + +/** + * This class represents the statement + * ALTER INDEX RENAME + */ +public class AlterIndexRename extends DefineCommand { + + private boolean ifExists; + private Schema oldSchema; + private String oldIndexName; + private String newIndexName; + + public AlterIndexRename(SessionLocal session) { + super(session); + } + + public void setIfExists(boolean b) { + ifExists = b; + } + + public void setOldSchema(Schema old) { + oldSchema = old; + } + + public void setOldName(String name) { + oldIndexName = name; + } + + public void setNewName(String name) { + newIndexName = name; + } + + @Override + public long update() { + Database db = session.getDatabase(); + Index oldIndex = oldSchema.findIndex(session, oldIndexName); + if (oldIndex == null) { + if (!ifExists) { + throw DbException.get(ErrorCode.INDEX_NOT_FOUND_1, + newIndexName); + } + return 0; + } + if (oldSchema.findIndex(session, newIndexName) != null || + newIndexName.equals(oldIndexName)) { + throw DbException.get(ErrorCode.INDEX_ALREADY_EXISTS_1, + newIndexName); + } + session.getUser().checkTableRight(oldIndex.getTable(), Right.SCHEMA_OWNER); + db.renameSchemaObject(session, oldIndex, newIndexName); + return 0; + } + + @Override + public int getType() { + return CommandInterface.ALTER_INDEX_RENAME; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/AlterSchemaRename.java b/h2/src/main/org/h2/command/ddl/AlterSchemaRename.java new file mode 100644 index 0000000..3ce0b0f --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/AlterSchemaRename.java @@ -0,0 +1,65 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import java.util.ArrayList; +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.schema.SchemaObject; + +/** + * This class represents the statement + * ALTER SCHEMA RENAME + */ +public class AlterSchemaRename extends DefineCommand { + + private Schema oldSchema; + private String newSchemaName; + + public AlterSchemaRename(SessionLocal session) { + super(session); + } + + public void setOldSchema(Schema schema) { + oldSchema = schema; + } + + public void setNewName(String name) { + newSchemaName = name; + } + + @Override + public long update() { + session.getUser().checkSchemaAdmin(); + Database db = session.getDatabase(); + if (!oldSchema.canDrop()) { + throw DbException.get(ErrorCode.SCHEMA_CAN_NOT_BE_DROPPED_1, oldSchema.getName()); + } + if (db.findSchema(newSchemaName) != null || newSchemaName.equals(oldSchema.getName())) { + throw DbException.get(ErrorCode.SCHEMA_ALREADY_EXISTS_1, newSchemaName); + } + db.renameDatabaseObject(session, oldSchema, newSchemaName); + ArrayList all = new ArrayList<>(); + for (Schema schema : db.getAllSchemas()) { + schema.getAll(all); + for (SchemaObject schemaObject : all) { + db.updateMeta(session, schemaObject); + } + all.clear(); + } + return 0; + } + + @Override + public int getType() { + return CommandInterface.ALTER_SCHEMA_RENAME; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/AlterSequence.java b/h2/src/main/org/h2/command/ddl/AlterSequence.java new file mode 100644 index 0000000..706672a --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/AlterSequence.java @@ -0,0 +1,106 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Right; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.schema.Sequence; +import org.h2.table.Column; + +/** + * This class represents the statement ALTER SEQUENCE. + */ +public class AlterSequence extends SchemaOwnerCommand { + + private boolean ifExists; + + private Column column; + + private Boolean always; + + private String sequenceName; + + private Sequence sequence; + + private SequenceOptions options; + + public AlterSequence(SessionLocal session, Schema schema) { + super(session, schema); + transactional = true; + } + + public void setIfExists(boolean b) { + ifExists = b; + } + + public void setSequenceName(String sequenceName) { + this.sequenceName = sequenceName; + } + + public void setOptions(SequenceOptions options) { + this.options = options; + } + + @Override + public boolean isTransactional() { + return true; + } + + /** + * Set the column + * + * @param column the column + * @param always whether value should be always generated, or null if "set + * generated is not specified + */ + public void setColumn(Column column, Boolean always) { + this.column = column; + this.always = always; + sequence = column.getSequence(); + if (sequence == null && !ifExists) { + throw DbException.get(ErrorCode.SEQUENCE_NOT_FOUND_1, column.getTraceSQL()); + } + } + + @Override + long update(Schema schema) { + if (sequence == null) { + sequence = schema.findSequence(sequenceName); + if (sequence == null) { + if (!ifExists) { + throw DbException.get(ErrorCode.SEQUENCE_NOT_FOUND_1, sequenceName); + } + return 0; + } + } + if (column != null) { + session.getUser().checkTableRight(column.getTable(), Right.SCHEMA_OWNER); + } + options.setDataType(sequence.getDataType()); + Long startValue = options.getStartValue(session); + sequence.modify( + options.getRestartValue(session, startValue != null ? startValue : sequence.getStartValue()), + startValue, + options.getMinValue(sequence, session), options.getMaxValue(sequence, session), + options.getIncrement(session), options.getCycle(), options.getCacheSize(session)); + sequence.flush(session); + if (column != null && always != null) { + column.setSequence(sequence, always); + session.getDatabase().updateMeta(session, column.getTable()); + } + return 0; + } + + @Override + public int getType() { + return CommandInterface.ALTER_SEQUENCE; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/AlterTable.java b/h2/src/main/org/h2/command/ddl/AlterTable.java new file mode 100644 index 0000000..2cfbd7f --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/AlterTable.java @@ -0,0 +1,51 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.engine.Right; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.table.Table; + +/** + * The base class for ALTER TABLE commands. + */ +public abstract class AlterTable extends SchemaCommand { + + String tableName; + + boolean ifTableExists; + + AlterTable(SessionLocal session, Schema schema) { + super(session, schema); + } + + public final void setTableName(String tableName) { + this.tableName = tableName; + } + + public final void setIfTableExists(boolean b) { + ifTableExists = b; + } + + @Override + public final long update() { + Table table = getSchema().findTableOrView(session, tableName); + if (table == null) { + if (ifTableExists) { + return 0; + } + throw DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, tableName); + } + session.getUser().checkTableRight(table, Right.SCHEMA_OWNER); + return update(table); + } + + abstract long update(Table table); + +} diff --git a/h2/src/main/org/h2/command/ddl/AlterTableAddConstraint.java b/h2/src/main/org/h2/command/ddl/AlterTableAddConstraint.java new file mode 100644 index 0000000..05c425b --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/AlterTableAddConstraint.java @@ -0,0 +1,496 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import java.util.ArrayList; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.constraint.Constraint; +import org.h2.constraint.ConstraintActionType; +import org.h2.constraint.ConstraintCheck; +import org.h2.constraint.ConstraintReferential; +import org.h2.constraint.ConstraintUnique; +import org.h2.engine.Constants; +import org.h2.engine.Database; +import org.h2.engine.Right; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.index.Index; +import org.h2.index.IndexType; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.table.Column; +import org.h2.table.IndexColumn; +import org.h2.table.Table; +import org.h2.table.TableFilter; +import org.h2.util.HasSQL; +import org.h2.value.DataType; + +/** + * This class represents the statement + * ALTER TABLE ADD CONSTRAINT + */ +public class AlterTableAddConstraint extends AlterTable { + + private final int type; + private String constraintName; + private IndexColumn[] indexColumns; + private ConstraintActionType deleteAction = ConstraintActionType.RESTRICT; + private ConstraintActionType updateAction = ConstraintActionType.RESTRICT; + private Schema refSchema; + private String refTableName; + private IndexColumn[] refIndexColumns; + private Expression checkExpression; + private Index index, refIndex; + private String comment; + private boolean checkExisting; + private boolean primaryKeyHash; + private final boolean ifNotExists; + private final ArrayList createdIndexes = new ArrayList<>(); + private ConstraintUnique createdUniqueConstraint; + + public AlterTableAddConstraint(SessionLocal session, Schema schema, int type, boolean ifNotExists) { + super(session, schema); + this.ifNotExists = ifNotExists; + this.type = type; + } + + private String generateConstraintName(Table table) { + if (constraintName == null) { + constraintName = getSchema().getUniqueConstraintName(session, table); + } + return constraintName; + } + + @Override + public long update(Table table) { + try { + return tryUpdate(table); + } catch (DbException e) { + try { + if (createdUniqueConstraint != null) { + Index index = createdUniqueConstraint.getIndex(); + session.getDatabase().removeSchemaObject(session, createdUniqueConstraint); + createdIndexes.remove(index); + } + for (Index index : createdIndexes) { + session.getDatabase().removeSchemaObject(session, index); + } + } catch (Throwable ex) { + e.addSuppressed(ex); + } + throw e; + } finally { + getSchema().freeUniqueName(constraintName); + } + } + + /** + * Try to execute the statement. + * + * @return the update count + */ + private int tryUpdate(Table table) { + if (constraintName != null && getSchema().findConstraint(session, constraintName) != null) { + if (ifNotExists) { + return 0; + } + /** + * 1.4.200 and older databases don't always have a unique constraint + * for each referential constraint, so these constraints are created + * and they may use the same generated name as some other not yet + * initialized constraint that may lead to a name conflict. + */ + if (!session.isQuirksMode()) { + throw DbException.get(ErrorCode.CONSTRAINT_ALREADY_EXISTS_1, constraintName); + } + constraintName = null; + } + Database db = session.getDatabase(); + db.lockMeta(session); + table.lock(session, Table.EXCLUSIVE_LOCK); + Constraint constraint; + switch (type) { + case CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_PRIMARY_KEY: { + IndexColumn.mapColumns(indexColumns, table); + index = table.findPrimaryKey(); + ArrayList constraints = table.getConstraints(); + for (int i = 0; constraints != null && i < constraints.size(); i++) { + Constraint c = constraints.get(i); + if (Constraint.Type.PRIMARY_KEY == c.getConstraintType()) { + throw DbException.get(ErrorCode.SECOND_PRIMARY_KEY); + } + } + if (index != null) { + // if there is an index, it must match with the one declared + // we don't test ascending / descending + IndexColumn[] pkCols = index.getIndexColumns(); + if (pkCols.length != indexColumns.length) { + throw DbException.get(ErrorCode.SECOND_PRIMARY_KEY); + } + for (int i = 0; i < pkCols.length; i++) { + if (pkCols[i].column != indexColumns[i].column) { + throw DbException.get(ErrorCode.SECOND_PRIMARY_KEY); + } + } + } else { + IndexType indexType = IndexType.createPrimaryKey( + table.isPersistIndexes(), primaryKeyHash); + String indexName = table.getSchema().getUniqueIndexName( + session, table, Constants.PREFIX_PRIMARY_KEY); + int indexId = session.getDatabase().allocateObjectId(); + try { + index = table.addIndex(session, indexName, indexId, indexColumns, indexColumns.length, indexType, + true, null); + } finally { + getSchema().freeUniqueName(indexName); + } + } + index.getIndexType().setBelongsToConstraint(true); + int id = getObjectId(); + String name = generateConstraintName(table); + ConstraintUnique pk = new ConstraintUnique(getSchema(), + id, name, table, true); + pk.setColumns(indexColumns); + pk.setIndex(index, true); + constraint = pk; + break; + } + case CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_UNIQUE: + if (indexColumns == null) { + Column[] columns = table.getColumns(); + int columnCount = columns.length; + ArrayList list = new ArrayList<>(columnCount); + for (int i = 0; i < columnCount; i++) { + Column c = columns[i]; + if (c.getVisible()) { + IndexColumn indexColumn = new IndexColumn(c.getName()); + indexColumn.column = c; + list.add(indexColumn); + } + } + if (list.isEmpty()) { + throw DbException.get(ErrorCode.SYNTAX_ERROR_1, "UNIQUE(VALUE) on table without columns"); + } + indexColumns = list.toArray(new IndexColumn[0]); + } else { + IndexColumn.mapColumns(indexColumns, table); + } + constraint = createUniqueConstraint(table, index, indexColumns, false); + break; + case CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_CHECK: { + int id = getObjectId(); + String name = generateConstraintName(table); + ConstraintCheck check = new ConstraintCheck(getSchema(), id, name, table); + TableFilter filter = new TableFilter(session, table, null, false, null, 0, null); + checkExpression.mapColumns(filter, 0, Expression.MAP_INITIAL); + checkExpression = checkExpression.optimize(session); + check.setExpression(checkExpression); + check.setTableFilter(filter); + constraint = check; + if (checkExisting) { + check.checkExistingData(session); + } + break; + } + case CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_REFERENTIAL: { + Table refTable = refSchema.resolveTableOrView(session, refTableName); + if (refTable == null) { + throw DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, refTableName); + } + if (refTable != table) { + session.getUser().checkTableRight(refTable, Right.SCHEMA_OWNER); + } + if (!refTable.canReference()) { + StringBuilder builder = new StringBuilder("Reference "); + refTable.getSQL(builder, HasSQL.TRACE_SQL_FLAGS); + throw DbException.getUnsupportedException(builder.toString()); + } + boolean isOwner = false; + IndexColumn.mapColumns(indexColumns, table); + if (refIndexColumns == null) { + refIndexColumns = refTable.getPrimaryKey().getIndexColumns(); + } else { + IndexColumn.mapColumns(refIndexColumns, refTable); + } + int columnCount = indexColumns.length; + if (refIndexColumns.length != columnCount) { + throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); + } + for (IndexColumn indexColumn : indexColumns) { + Column column = indexColumn.column; + if (column.isGeneratedAlways()) { + switch (deleteAction) { + case SET_DEFAULT: + case SET_NULL: + throw DbException.get(ErrorCode.GENERATED_COLUMN_CANNOT_BE_UPDATABLE_BY_CONSTRAINT_2, + column.getSQLWithTable(new StringBuilder(), HasSQL.TRACE_SQL_FLAGS).toString(), + "ON DELETE " + deleteAction.getSqlName()); + default: + // All other actions are allowed + } + switch (updateAction) { + case CASCADE: + case SET_DEFAULT: + case SET_NULL: + throw DbException.get(ErrorCode.GENERATED_COLUMN_CANNOT_BE_UPDATABLE_BY_CONSTRAINT_2, + column.getSQLWithTable(new StringBuilder(), HasSQL.TRACE_SQL_FLAGS).toString(), + "ON UPDATE " + updateAction.getSqlName()); + default: + // All other actions are allowed + } + } + } + for (int i = 0; i < columnCount; i++) { + Column column1 = indexColumns[i].column, column2 = refIndexColumns[i].column; + if (!DataType.areStableComparable(column1.getType(), column2.getType())) { + throw DbException.get(ErrorCode.UNCOMPARABLE_REFERENCED_COLUMN_2, column1.getCreateSQL(), + column2.getCreateSQL()); + } + } + ConstraintUnique unique = getUniqueConstraint(refTable, refIndexColumns); + if (unique == null && !session.isQuirksMode() + && !session.getMode().createUniqueConstraintForReferencedColumns) { + throw DbException.get(ErrorCode.CONSTRAINT_NOT_FOUND_1, IndexColumn.writeColumns( + new StringBuilder("PRIMARY KEY | UNIQUE ("), refIndexColumns, HasSQL.TRACE_SQL_FLAGS) + .append(')').toString()); + } + if (index != null && canUseIndex(index, table, indexColumns, false)) { + isOwner = true; + index.getIndexType().setBelongsToConstraint(true); + } else { + index = getIndex(table, indexColumns, false); + if (index == null) { + index = createIndex(table, indexColumns, false); + isOwner = true; + } + } + int id = getObjectId(); + String name = generateConstraintName(table); + ConstraintReferential refConstraint = new ConstraintReferential(getSchema(), + id, name, table); + refConstraint.setColumns(indexColumns); + refConstraint.setIndex(index, isOwner); + refConstraint.setRefTable(refTable); + refConstraint.setRefColumns(refIndexColumns); + if (unique == null) { + unique = createUniqueConstraint(refTable, refIndex, refIndexColumns, true); + addConstraintToTable(db, refTable, unique); + createdUniqueConstraint = unique; + } + refConstraint.setRefConstraint(unique); + if (checkExisting) { + refConstraint.checkExistingData(session); + } + refTable.addConstraint(refConstraint); + refConstraint.setDeleteAction(deleteAction); + refConstraint.setUpdateAction(updateAction); + constraint = refConstraint; + break; + } + default: + throw DbException.getInternalError("type=" + type); + } + // parent relationship is already set with addConstraint + constraint.setComment(comment); + addConstraintToTable(db, table, constraint); + return 0; + } + + private ConstraintUnique createUniqueConstraint(Table table, Index index, IndexColumn[] indexColumns, + boolean forForeignKey) { + boolean isOwner = false; + if (index != null && canUseIndex(index, table, indexColumns, true)) { + isOwner = true; + index.getIndexType().setBelongsToConstraint(true); + } else { + index = getIndex(table, indexColumns, true); + if (index == null) { + index = createIndex(table, indexColumns, true); + isOwner = true; + } + } + int id; + String name; + Schema tableSchema = table.getSchema(); + if (forForeignKey) { + id = session.getDatabase().allocateObjectId(); + try { + tableSchema.reserveUniqueName(constraintName); + name = tableSchema.getUniqueConstraintName(session, table); + } finally { + tableSchema.freeUniqueName(constraintName); + } + } else { + id = getObjectId(); + name = generateConstraintName(table); + } + ConstraintUnique unique = new ConstraintUnique(tableSchema, id, name, table, false); + unique.setColumns(indexColumns); + unique.setIndex(index, isOwner); + return unique; + } + + private void addConstraintToTable(Database db, Table table, Constraint constraint) { + if (table.isTemporary() && !table.isGlobalTemporary()) { + session.addLocalTempTableConstraint(constraint); + } else { + db.addSchemaObject(session, constraint); + } + table.addConstraint(constraint); + } + + private Index createIndex(Table t, IndexColumn[] cols, boolean unique) { + int indexId = session.getDatabase().allocateObjectId(); + IndexType indexType; + if (unique) { + // for unique constraints + indexType = IndexType.createUnique(t.isPersistIndexes(), false); + } else { + // constraints + indexType = IndexType.createNonUnique(t.isPersistIndexes()); + } + indexType.setBelongsToConstraint(true); + String prefix = constraintName == null ? "CONSTRAINT" : constraintName; + String indexName = t.getSchema().getUniqueIndexName(session, t, + prefix + "_INDEX_"); + try { + Index index = t.addIndex(session, indexName, indexId, cols, unique ? cols.length : 0, indexType, true, + null); + createdIndexes.add(index); + return index; + } finally { + getSchema().freeUniqueName(indexName); + } + } + + public void setDeleteAction(ConstraintActionType action) { + this.deleteAction = action; + } + + public void setUpdateAction(ConstraintActionType action) { + this.updateAction = action; + } + + private static ConstraintUnique getUniqueConstraint(Table t, IndexColumn[] cols) { + ArrayList constraints = t.getConstraints(); + if (constraints != null) { + for (Constraint constraint : constraints) { + if (constraint.getTable() == t) { + Constraint.Type constraintType = constraint.getConstraintType(); + if (constraintType == Constraint.Type.PRIMARY_KEY || constraintType == Constraint.Type.UNIQUE) { + if (canUseIndex(constraint.getIndex(), t, cols, true)) { + return (ConstraintUnique) constraint; + } + } + } + } + } + return null; + } + + private static Index getIndex(Table t, IndexColumn[] cols, boolean unique) { + ArrayList indexes = t.getIndexes(); + Index index = null; + if (indexes != null) { + for (Index idx : indexes) { + if (canUseIndex(idx, t, cols, unique)) { + if (index == null || idx.getIndexColumns().length < index.getIndexColumns().length) { + index = idx; + } + } + } + } + return index; + } + + private static boolean canUseIndex(Index index, Table table, IndexColumn[] cols, boolean unique) { + if (index.getTable() != table) { + return false; + } + int allowedColumns; + if (unique) { + allowedColumns = index.getUniqueColumnCount(); + if (allowedColumns != cols.length) { + return false; + } + } else { + if (index.getCreateSQL() == null || (allowedColumns = index.getColumns().length) != cols.length) { + return false; + } + } + for (IndexColumn col : cols) { + // all columns of the list must be part of the index + int i = index.getColumnIndex(col.column); + if (i < 0 || i >= allowedColumns) { + return false; + } + } + return true; + } + + public void setConstraintName(String constraintName) { + this.constraintName = constraintName; + } + + public String getConstraintName() { + return constraintName; + } + + @Override + public int getType() { + return type; + } + + public void setCheckExpression(Expression expression) { + this.checkExpression = expression; + } + + public void setIndexColumns(IndexColumn[] indexColumns) { + this.indexColumns = indexColumns; + } + + public IndexColumn[] getIndexColumns() { + return indexColumns; + } + + /** + * Set the referenced table. + * + * @param refSchema the schema + * @param ref the table name + */ + public void setRefTableName(Schema refSchema, String ref) { + this.refSchema = refSchema; + this.refTableName = ref; + } + + public void setRefIndexColumns(IndexColumn[] indexColumns) { + this.refIndexColumns = indexColumns; + } + + public void setIndex(Index index) { + this.index = index; + } + + public void setRefIndex(Index refIndex) { + this.refIndex = refIndex; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public void setCheckExisting(boolean b) { + this.checkExisting = b; + } + + public void setPrimaryKeyHash(boolean b) { + this.primaryKeyHash = b; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/AlterTableAlterColumn.java b/h2/src/main/org/h2/command/ddl/AlterTableAlterColumn.java new file mode 100644 index 0000000..2f21bae --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/AlterTableAlterColumn.java @@ -0,0 +1,730 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import java.util.ArrayList; +import java.util.HashSet; +import org.h2.api.ErrorCode; +import org.h2.command.CommandContainer; +import org.h2.command.CommandInterface; +import org.h2.command.Parser; +import org.h2.command.Prepared; +import org.h2.constraint.Constraint; +import org.h2.constraint.ConstraintReferential; +import org.h2.constraint.ConstraintUnique; +import org.h2.engine.Constants; +import org.h2.engine.Database; +import org.h2.engine.DbObject; +import org.h2.engine.Right; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.index.Index; +import org.h2.index.IndexType; +import org.h2.message.DbException; +import org.h2.result.ResultInterface; +import org.h2.result.SearchRow; +import org.h2.schema.Schema; +import org.h2.schema.SchemaObject; +import org.h2.schema.Sequence; +import org.h2.schema.TriggerObject; +import org.h2.table.Column; +import org.h2.table.Table; +import org.h2.table.TableBase; +import org.h2.table.TableView; +import org.h2.util.HasSQL; +import org.h2.util.Utils; + +/** + * This class represents the statements + * ALTER TABLE ADD, + * ALTER TABLE ADD IF NOT EXISTS, + * ALTER TABLE ALTER COLUMN, + * ALTER TABLE ALTER COLUMN SELECTIVITY, + * ALTER TABLE ALTER COLUMN SET DEFAULT, + * ALTER TABLE ALTER COLUMN DROP DEFAULT, + * ALTER TABLE ALTER COLUMN DROP EXPRESSION, + * ALTER TABLE ALTER COLUMN SET NULL, + * ALTER TABLE ALTER COLUMN DROP NULL, + * ALTER TABLE ALTER COLUMN SET VISIBLE, + * ALTER TABLE ALTER COLUMN SET INVISIBLE, + * ALTER TABLE DROP COLUMN + */ +public class AlterTableAlterColumn extends CommandWithColumns { + + private String tableName; + private Column oldColumn; + private Column newColumn; + private int type; + /** + * Default or on update expression. + */ + private Expression defaultExpression; + private Expression newSelectivity; + private Expression usingExpression; + private boolean addFirst; + private String addBefore; + private String addAfter; + private boolean ifTableExists; + private boolean ifNotExists; + private ArrayList columnsToAdd; + private ArrayList columnsToRemove; + private boolean booleanFlag; + + public AlterTableAlterColumn(SessionLocal session, Schema schema) { + super(session, schema); + } + + public void setIfTableExists(boolean b) { + ifTableExists = b; + } + + public void setTableName(String tableName) { + this.tableName = tableName; + } + + public void setOldColumn(Column oldColumn) { + this.oldColumn = oldColumn; + } + + /** + * Add the column as the first column of the table. + */ + public void setAddFirst() { + addFirst = true; + } + + public void setAddBefore(String before) { + this.addBefore = before; + } + + public void setAddAfter(String after) { + this.addAfter = after; + } + + @Override + public long update() { + Database db = session.getDatabase(); + Table table = getSchema().resolveTableOrView(session, tableName); + if (table == null) { + if (ifTableExists) { + return 0; + } + throw DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, tableName); + } + session.getUser().checkTableRight(table, Right.SCHEMA_OWNER); + table.checkSupportAlter(); + table.lock(session, Table.EXCLUSIVE_LOCK); + if (newColumn != null) { + checkDefaultReferencesTable(table, newColumn.getDefaultExpression()); + checkClustering(newColumn); + } + if (columnsToAdd != null) { + for (Column column : columnsToAdd) { + checkDefaultReferencesTable(table, column.getDefaultExpression()); + checkClustering(column); + } + } + switch (type) { + case CommandInterface.ALTER_TABLE_ALTER_COLUMN_NOT_NULL: { + if (oldColumn == null || !oldColumn.isNullable()) { + // no change + break; + } + checkNoNullValues(table); + oldColumn.setNullable(false); + db.updateMeta(session, table); + break; + } + case CommandInterface.ALTER_TABLE_ALTER_COLUMN_DROP_NOT_NULL: { + if (oldColumn == null || oldColumn.isNullable()) { + // no change + break; + } + checkNullable(table); + oldColumn.setNullable(true); + db.updateMeta(session, table); + break; + } + case CommandInterface.ALTER_TABLE_ALTER_COLUMN_DEFAULT: + case CommandInterface.ALTER_TABLE_ALTER_COLUMN_DROP_EXPRESSION: { + if (oldColumn == null) { + break; + } + if (oldColumn.isIdentity()) { + break; + } + if (defaultExpression != null) { + if (oldColumn.isGenerated()) { + break; + } + checkDefaultReferencesTable(table, defaultExpression); + oldColumn.setDefaultExpression(session, defaultExpression); + } else { + if (type == CommandInterface.ALTER_TABLE_ALTER_COLUMN_DROP_EXPRESSION != oldColumn.isGenerated()) { + break; + } + oldColumn.setDefaultExpression(session, null); + } + db.updateMeta(session, table); + break; + } + case CommandInterface.ALTER_TABLE_ALTER_COLUMN_DROP_IDENTITY: { + if (oldColumn == null) { + break; + } + Sequence sequence = oldColumn.getSequence(); + if (sequence == null) { + break; + } + oldColumn.setSequence(null, false); + removeSequence(table, sequence); + db.updateMeta(session, table); + break; + } + case CommandInterface.ALTER_TABLE_ALTER_COLUMN_ON_UPDATE: { + if (oldColumn == null) { + break; + } + if (defaultExpression != null) { + if (oldColumn.isIdentity() || oldColumn.isGenerated()) { + break; + } + checkDefaultReferencesTable(table, defaultExpression); + oldColumn.setOnUpdateExpression(session, defaultExpression); + } else { + oldColumn.setOnUpdateExpression(session, null); + } + db.updateMeta(session, table); + break; + } + case CommandInterface.ALTER_TABLE_ALTER_COLUMN_CHANGE_TYPE: { + if (oldColumn == null) { + break; + } + // if the change is only increasing the precision, then we don't + // need to copy the table because the length is only a constraint, + // and does not affect the storage structure. + if (oldColumn.isWideningConversion(newColumn) && usingExpression == null) { + convertIdentityColumn(table, newColumn); + oldColumn.copy(newColumn); + db.updateMeta(session, table); + } else { + oldColumn.setSequence(null, false); + oldColumn.setDefaultExpression(session, null); + if (oldColumn.isNullable() && !newColumn.isNullable()) { + checkNoNullValues(table); + } else if (!oldColumn.isNullable() && newColumn.isNullable()) { + checkNullable(table); + } + if (oldColumn.getVisible() ^ newColumn.getVisible()) { + oldColumn.setVisible(newColumn.getVisible()); + } + convertIdentityColumn(table, newColumn); + copyData(table, null, true); + } + table.setModified(); + break; + } + case CommandInterface.ALTER_TABLE_ADD_COLUMN: { + // ifNotExists only supported for single column add + if (ifNotExists && columnsToAdd != null && columnsToAdd.size() == 1 && + table.doesColumnExist(columnsToAdd.get(0).getName())) { + break; + } + ArrayList sequences = generateSequences(columnsToAdd, false); + if (columnsToAdd != null) { + changePrimaryKeysToNotNull(columnsToAdd); + } + copyData(table, sequences, true); + break; + } + case CommandInterface.ALTER_TABLE_DROP_COLUMN: { + if (table.getColumns().length - columnsToRemove.size() < 1) { + throw DbException.get(ErrorCode.CANNOT_DROP_LAST_COLUMN, columnsToRemove.get(0).getTraceSQL()); + } + table.dropMultipleColumnsConstraintsAndIndexes(session, columnsToRemove); + copyData(table, null, false); + break; + } + case CommandInterface.ALTER_TABLE_ALTER_COLUMN_SELECTIVITY: { + if (oldColumn == null) { + break; + } + int value = newSelectivity.optimize(session).getValue(session).getInt(); + oldColumn.setSelectivity(value); + db.updateMeta(session, table); + break; + } + case CommandInterface.ALTER_TABLE_ALTER_COLUMN_VISIBILITY: + if (oldColumn == null) { + break; + } + if (oldColumn.getVisible() != booleanFlag) { + oldColumn.setVisible(booleanFlag); + table.setModified(); + db.updateMeta(session, table); + } + break; + case CommandInterface.ALTER_TABLE_ALTER_COLUMN_DEFAULT_ON_NULL: + if (oldColumn == null) { + break; + } + if (oldColumn.isDefaultOnNull() != booleanFlag) { + oldColumn.setDefaultOnNull(booleanFlag); + table.setModified(); + db.updateMeta(session, table); + } + break; + default: + throw DbException.getInternalError("type=" + type); + } + return 0; + } + + private static void checkDefaultReferencesTable(Table table, Expression defaultExpression) { + if (defaultExpression == null) { + return; + } + HashSet dependencies = new HashSet<>(); + ExpressionVisitor visitor = ExpressionVisitor + .getDependenciesVisitor(dependencies); + defaultExpression.isEverything(visitor); + if (dependencies.contains(table)) { + throw DbException.get(ErrorCode.COLUMN_IS_REFERENCED_1, defaultExpression.getTraceSQL()); + } + } + + private void checkClustering(Column c) { + if (!Constants.CLUSTERING_DISABLED + .equals(session.getDatabase().getCluster()) + && c.hasIdentityOptions()) { + throw DbException.getUnsupportedException( + "CLUSTERING && identity columns"); + } + } + + private void convertIdentityColumn(Table table, Column c) { + if (c.hasIdentityOptions()) { + if (c.isPrimaryKey()) { + addConstraintCommand( + Parser.newPrimaryKeyConstraintCommand(session, table.getSchema(), table.getName(), c)); + } + int objId = getObjectId(); + c.initializeSequence(session, getSchema(), objId, table.isTemporary()); + } + } + + private void removeSequence(Table table, Sequence sequence) { + if (sequence != null) { + table.removeSequence(sequence); + sequence.setBelongsToTable(false); + Database db = session.getDatabase(); + db.removeSchemaObject(session, sequence); + } + } + + private void copyData(Table table, ArrayList sequences, boolean createConstraints) { + if (table.isTemporary()) { + throw DbException.getUnsupportedException("TEMP TABLE"); + } + Database db = session.getDatabase(); + String baseName = table.getName(); + String tempName = db.getTempTableName(baseName, session); + Column[] columns = table.getColumns(); + ArrayList newColumns = new ArrayList<>(columns.length); + Table newTable = cloneTableStructure(table, columns, db, tempName, newColumns); + if (sequences != null) { + for (Sequence sequence : sequences) { + table.addSequence(sequence); + } + } + try { + // check if a view would become invalid + // (because the column to drop is referenced or so) + checkViews(table, newTable); + } catch (DbException e) { + StringBuilder builder = new StringBuilder("DROP TABLE "); + newTable.getSQL(builder, HasSQL.DEFAULT_SQL_FLAGS); + execute(builder.toString()); + throw e; + } + String tableName = table.getName(); + ArrayList dependentViews = new ArrayList<>(table.getDependentViews()); + for (TableView view : dependentViews) { + table.removeDependentView(view); + } + StringBuilder builder = new StringBuilder("DROP TABLE "); + table.getSQL(builder, HasSQL.DEFAULT_SQL_FLAGS).append(" IGNORE"); + execute(builder.toString()); + db.renameSchemaObject(session, newTable, tableName); + for (DbObject child : newTable.getChildren()) { + if (child instanceof Sequence) { + continue; + } + String name = child.getName(); + if (name == null || child.getCreateSQL() == null) { + continue; + } + if (name.startsWith(tempName + "_")) { + name = name.substring(tempName.length() + 1); + SchemaObject so = (SchemaObject) child; + if (so instanceof Constraint) { + if (so.getSchema().findConstraint(session, name) != null) { + name = so.getSchema().getUniqueConstraintName(session, newTable); + } + } else if (so instanceof Index) { + if (so.getSchema().findIndex(session, name) != null) { + name = so.getSchema().getUniqueIndexName(session, newTable, name); + } + } + db.renameSchemaObject(session, so, name); + } + } + if (createConstraints) { + createConstraints(); + } + for (TableView view : dependentViews) { + String sql = view.getCreateSQL(true, true); + execute(sql); + } + } + + private Table cloneTableStructure(Table table, Column[] columns, Database db, + String tempName, ArrayList newColumns) { + for (Column col : columns) { + newColumns.add(col.getClone()); + } + switch (type) { + case CommandInterface.ALTER_TABLE_DROP_COLUMN: + for (Column removeCol : columnsToRemove) { + Column foundCol = null; + for (Column newCol : newColumns) { + if (newCol.getName().equals(removeCol.getName())) { + foundCol = newCol; + break; + } + } + if (foundCol == null) { + throw DbException.getInternalError(removeCol.getCreateSQL()); + } + newColumns.remove(foundCol); + } + break; + case CommandInterface.ALTER_TABLE_ADD_COLUMN: { + int position; + if (addFirst) { + position = 0; + } else if (addBefore != null) { + position = table.getColumn(addBefore).getColumnId(); + } else if (addAfter != null) { + position = table.getColumn(addAfter).getColumnId() + 1; + } else { + position = columns.length; + } + if (columnsToAdd != null) { + for (Column column : columnsToAdd) { + newColumns.add(position++, column); + } + } + break; + } + case CommandInterface.ALTER_TABLE_ALTER_COLUMN_CHANGE_TYPE: + newColumns.set(oldColumn.getColumnId(), newColumn); + } + + // create a table object in order to get the SQL statement + // can't just use this table, because most column objects are 'shared' + // with the old table + // still need a new id because using 0 would mean: the new table tries + // to use the rows of the table 0 (the meta table) + int id = db.allocateObjectId(); + CreateTableData data = new CreateTableData(); + data.tableName = tempName; + data.id = id; + data.columns = newColumns; + data.temporary = table.isTemporary(); + data.persistData = table.isPersistData(); + data.persistIndexes = table.isPersistIndexes(); + data.isHidden = table.isHidden(); + data.session = session; + Table newTable = getSchema().createTable(data); + newTable.setComment(table.getComment()); + String newTableSQL = newTable.getCreateSQLForMeta(); + StringBuilder columnNames = new StringBuilder(); + StringBuilder columnValues = new StringBuilder(); + for (Column nc : newColumns) { + if (nc.isGenerated()) { + continue; + } + switch (type) { + case CommandInterface.ALTER_TABLE_ADD_COLUMN: + if (columnsToAdd != null && columnsToAdd.contains(nc)) { + if (usingExpression != null) { + usingExpression.getUnenclosedSQL(addColumn(nc, columnNames, columnValues), + HasSQL.DEFAULT_SQL_FLAGS); + } + continue; + } + break; + case CommandInterface.ALTER_TABLE_ALTER_COLUMN_CHANGE_TYPE: + if (nc.equals(newColumn) && usingExpression != null) { + usingExpression.getUnenclosedSQL(addColumn(nc, columnNames, columnValues), + HasSQL.DEFAULT_SQL_FLAGS); + continue; + } + } + nc.getSQL(addColumn(nc, columnNames, columnValues), HasSQL.DEFAULT_SQL_FLAGS); + } + String newTableName = newTable.getName(); + Schema newTableSchema = newTable.getSchema(); + newTable.removeChildrenAndResources(session); + + execute(newTableSQL); + newTable = newTableSchema.getTableOrView(session, newTableName); + ArrayList children = Utils.newSmallArrayList(); + ArrayList triggers = Utils.newSmallArrayList(); + boolean hasDelegateIndex = false; + for (DbObject child : table.getChildren()) { + if (child instanceof Sequence) { + continue; + } else if (child instanceof Index) { + Index idx = (Index) child; + if (idx.getIndexType().getBelongsToConstraint()) { + continue; + } + } + String createSQL = child.getCreateSQL(); + if (createSQL == null) { + continue; + } + if (child instanceof TableView) { + continue; + } else if (child.getType() == DbObject.TABLE_OR_VIEW) { + throw DbException.getInternalError(); + } + String quotedName = Parser.quoteIdentifier(tempName + "_" + child.getName(), HasSQL.DEFAULT_SQL_FLAGS); + String sql = null; + if (child instanceof ConstraintReferential) { + ConstraintReferential r = (ConstraintReferential) child; + if (r.getTable() != table) { + sql = r.getCreateSQLForCopy(r.getTable(), newTable, quotedName, false); + } + } + if (sql == null) { + sql = child.getCreateSQLForCopy(newTable, quotedName); + } + if (sql != null) { + if (child instanceof TriggerObject) { + triggers.add(sql); + } else { + if (!hasDelegateIndex) { + Index index = null; + if (child instanceof ConstraintUnique) { + ConstraintUnique constraint = (ConstraintUnique) child; + if (constraint.getConstraintType() == Constraint.Type.PRIMARY_KEY) { + index = constraint.getIndex(); + } + } else if (child instanceof Index) { + index = (Index) child; + } + if (index != null + && TableBase.getMainIndexColumn(index.getIndexType(), index.getIndexColumns()) + != SearchRow.ROWID_INDEX) { + execute(sql); + hasDelegateIndex = true; + continue; + } + } + children.add(sql); + } + } + } + StringBuilder builder = newTable.getSQL(new StringBuilder(128).append("INSERT INTO "), // + HasSQL.DEFAULT_SQL_FLAGS) + .append('(').append(columnNames).append(") OVERRIDING SYSTEM VALUE SELECT "); + if (columnValues.length() == 0) { + // special case: insert into test select * from + builder.append('*'); + } else { + builder.append(columnValues); + } + table.getSQL(builder.append(" FROM "), HasSQL.DEFAULT_SQL_FLAGS); + try { + execute(builder.toString()); + } catch (Throwable t) { + // data was not inserted due to data conversion error or some + // unexpected reason + builder = new StringBuilder("DROP TABLE "); + newTable.getSQL(builder, HasSQL.DEFAULT_SQL_FLAGS); + execute(builder.toString()); + throw t; + } + for (String sql : children) { + execute(sql); + } + table.setModified(); + // remove the sequences from the columns (except dropped columns) + // otherwise the sequence is dropped if the table is dropped + for (Column col : newColumns) { + Sequence seq = col.getSequence(); + if (seq != null) { + table.removeSequence(seq); + col.setSequence(null, false); + } + } + for (String sql : triggers) { + execute(sql); + } + return newTable; + } + + private static StringBuilder addColumn(Column column, StringBuilder columnNames, StringBuilder columnValues) { + if (columnNames.length() > 0) { + columnNames.append(", "); + } + column.getSQL(columnNames, HasSQL.DEFAULT_SQL_FLAGS); + if (columnValues.length() > 0) { + columnValues.append(", "); + } + return columnValues; + } + + /** + * Check that all views and other dependent objects. + */ + private void checkViews(SchemaObject sourceTable, SchemaObject newTable) { + String sourceTableName = sourceTable.getName(); + String newTableName = newTable.getName(); + Database db = sourceTable.getDatabase(); + // save the real table under a temporary name + String temp = db.getTempTableName(sourceTableName, session); + db.renameSchemaObject(session, sourceTable, temp); + try { + // have our new table impersonate the target table + db.renameSchemaObject(session, newTable, sourceTableName); + checkViewsAreValid(sourceTable); + } finally { + // always put the source tables back with their proper names + try { + db.renameSchemaObject(session, newTable, newTableName); + } finally { + db.renameSchemaObject(session, sourceTable, sourceTableName); + } + } + } + + /** + * Check that a table or view is still valid. + * + * @param tableOrView the table or view to check + */ + private void checkViewsAreValid(DbObject tableOrView) { + for (DbObject view : tableOrView.getChildren()) { + if (view instanceof TableView) { + 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 + try { + session.prepare(sql); + } catch (DbException e) { + throw DbException.get(ErrorCode.COLUMN_IS_REFERENCED_1, e, view.getTraceSQL()); + } + checkViewsAreValid(view); + } + } + } + + private void execute(String sql) { + Prepared command = session.prepare(sql); + CommandContainer commandContainer = new CommandContainer(session, sql, command); + commandContainer.executeUpdate(null); + } + + private void checkNullable(Table table) { + if (oldColumn.isIdentity()) { + throw DbException.get(ErrorCode.COLUMN_MUST_NOT_BE_NULLABLE_1, oldColumn.getName()); + } + for (Index index : table.getIndexes()) { + if (index.getColumnIndex(oldColumn) < 0) { + continue; + } + IndexType indexType = index.getIndexType(); + if (indexType.isPrimaryKey()) { + throw DbException.get(ErrorCode.COLUMN_MUST_NOT_BE_NULLABLE_1, oldColumn.getName()); + } + } + } + + private void checkNoNullValues(Table table) { + StringBuilder builder = new StringBuilder("SELECT COUNT(*) FROM "); + table.getSQL(builder, HasSQL.DEFAULT_SQL_FLAGS).append(" WHERE "); + oldColumn.getSQL(builder, HasSQL.DEFAULT_SQL_FLAGS).append(" IS NULL"); + String sql = builder.toString(); + Prepared command = session.prepare(sql); + ResultInterface result = command.query(0); + result.next(); + if (result.currentRow()[0].getInt() > 0) { + throw DbException.get(ErrorCode.COLUMN_CONTAINS_NULL_VALUES_1, oldColumn.getTraceSQL()); + } + } + + public void setType(int type) { + this.type = type; + } + + public void setSelectivity(Expression selectivity) { + newSelectivity = selectivity; + } + + /** + * Set default or on update expression. + * + * @param defaultExpression default or on update expression + */ + public void setDefaultExpression(Expression defaultExpression) { + this.defaultExpression = defaultExpression; + } + + /** + * Set using expression. + * + * @param usingExpression using expression + */ + public void setUsingExpression(Expression usingExpression) { + this.usingExpression = usingExpression; + } + + public void setNewColumn(Column newColumn) { + this.newColumn = newColumn; + } + + @Override + public int getType() { + return type; + } + + public void setIfNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + } + + @Override + public void addColumn(Column column) { + if (columnsToAdd == null) { + columnsToAdd = new ArrayList<>(); + } + columnsToAdd.add(column); + } + + public void setColumnsToRemove(ArrayList columnsToRemove) { + this.columnsToRemove = columnsToRemove; + } + + public void setBooleanFlag(boolean booleanFlag) { + this.booleanFlag = booleanFlag; + } +} diff --git a/h2/src/main/org/h2/command/ddl/AlterTableDropConstraint.java b/h2/src/main/org/h2/command/ddl/AlterTableDropConstraint.java new file mode 100644 index 0000000..32a7390 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/AlterTableDropConstraint.java @@ -0,0 +1,82 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.constraint.Constraint; +import org.h2.constraint.Constraint.Type; +import org.h2.constraint.ConstraintActionType; +import org.h2.engine.Right; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.table.Table; + +/** + * This class represents the statement + * ALTER TABLE DROP CONSTRAINT + */ +public class AlterTableDropConstraint extends AlterTable { + + private String constraintName; + private final boolean ifExists; + private ConstraintActionType dropAction; + + public AlterTableDropConstraint(SessionLocal session, Schema schema, boolean ifExists) { + super(session, schema); + this.ifExists = ifExists; + dropAction = session.getDatabase().getSettings().dropRestrict ? + ConstraintActionType.RESTRICT : ConstraintActionType.CASCADE; + } + + public void setConstraintName(String string) { + constraintName = string; + } + + public void setDropAction(ConstraintActionType dropAction) { + this.dropAction = dropAction; + } + + @Override + public long update(Table table) { + Constraint constraint = getSchema().findConstraint(session, constraintName); + Type constraintType; + if (constraint == null || (constraintType = constraint.getConstraintType()) == Type.DOMAIN + || constraint.getTable() != table) { + if (!ifExists) { + throw DbException.get(ErrorCode.CONSTRAINT_NOT_FOUND_1, constraintName); + } + } else { + Table refTable = constraint.getRefTable(); + if (refTable != table) { + session.getUser().checkTableRight(refTable, Right.SCHEMA_OWNER); + } + if (constraintType == Type.PRIMARY_KEY || constraintType == Type.UNIQUE) { + for (Constraint c : constraint.getTable().getConstraints()) { + if (c.getReferencedConstraint() == constraint) { + if (dropAction == ConstraintActionType.RESTRICT) { + throw DbException.get(ErrorCode.CONSTRAINT_IS_USED_BY_CONSTRAINT_2, + constraint.getTraceSQL(), c.getTraceSQL()); + } + Table t = c.getTable(); + if (t != table && t != refTable) { + session.getUser().checkTableRight(t, Right.SCHEMA_OWNER); + } + } + } + } + session.getDatabase().removeSchemaObject(session, constraint); + } + return 0; + } + + @Override + public int getType() { + return CommandInterface.ALTER_TABLE_DROP_CONSTRAINT; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/AlterTableRename.java b/h2/src/main/org/h2/command/ddl/AlterTableRename.java new file mode 100644 index 0000000..948b487 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/AlterTableRename.java @@ -0,0 +1,64 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.table.Table; + +/** + * This class represents the statement + * ALTER TABLE RENAME + */ +public class AlterTableRename extends AlterTable { + + private String newTableName; + private boolean hidden; + + public AlterTableRename(SessionLocal session, Schema schema) { + super(session, schema); + } + + public void setNewTableName(String name) { + newTableName = name; + } + + @Override + public long update(Table table) { + Database db = session.getDatabase(); + Table t = getSchema().findTableOrView(session, newTableName); + if (t != null && hidden && newTableName.equals(table.getName())) { + if (!t.isHidden()) { + t.setHidden(hidden); + table.setHidden(true); + db.updateMeta(session, table); + } + return 0; + } + if (t != null || newTableName.equals(table.getName())) { + throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, newTableName); + } + if (table.isTemporary()) { + throw DbException.getUnsupportedException("temp table"); + } + db.renameSchemaObject(session, table, newTableName); + return 0; + } + + @Override + public int getType() { + return CommandInterface.ALTER_TABLE_RENAME; + } + + public void setHidden(boolean hidden) { + this.hidden = hidden; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/AlterTableRenameColumn.java b/h2/src/main/org/h2/command/ddl/AlterTableRenameColumn.java new file mode 100644 index 0000000..104d514 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/AlterTableRenameColumn.java @@ -0,0 +1,76 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.command.CommandInterface; +import org.h2.constraint.ConstraintReferential; +import org.h2.engine.Database; +import org.h2.engine.DbObject; +import org.h2.engine.SessionLocal; +import org.h2.schema.Schema; +import org.h2.table.Column; +import org.h2.table.Table; + +/** + * This class represents the statement + * ALTER TABLE ALTER COLUMN RENAME + */ +public class AlterTableRenameColumn extends AlterTable { + + private boolean ifExists; + private String oldName; + private String newName; + + public AlterTableRenameColumn(SessionLocal session, Schema schema) { + super(session, schema); + } + + public void setIfExists(boolean b) { + this.ifExists = b; + } + + public void setOldColumnName(String oldName) { + this.oldName = oldName; + } + + public void setNewColumnName(String newName) { + this.newName = newName; + } + + @Override + public long update(Table table) { + Column column = table.getColumn(oldName, ifExists); + if (column == null) { + return 0; + } + table.checkSupportAlter(); + table.renameColumn(column, newName); + table.setModified(); + Database db = session.getDatabase(); + db.updateMeta(session, table); + + // if we have foreign key constraints pointing at this table, we need to update them + for (DbObject childDbObject : table.getChildren()) { + if (childDbObject instanceof ConstraintReferential) { + ConstraintReferential ref = (ConstraintReferential) childDbObject; + ref.updateOnTableColumnRename(); + } + } + + for (DbObject child : table.getChildren()) { + if (child.getCreateSQL() != null) { + db.updateMeta(session, child); + } + } + return 0; + } + + @Override + public int getType() { + return CommandInterface.ALTER_TABLE_ALTER_COLUMN_RENAME; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/AlterTableRenameConstraint.java b/h2/src/main/org/h2/command/ddl/AlterTableRenameConstraint.java new file mode 100644 index 0000000..3dce7f3 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/AlterTableRenameConstraint.java @@ -0,0 +1,66 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.constraint.Constraint; +import org.h2.constraint.Constraint.Type; +import org.h2.engine.Database; +import org.h2.engine.Right; +import org.h2.engine.SessionLocal; +import org.h2.engine.User; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.table.Table; + +/** + * This class represents the statement + * ALTER TABLE RENAME CONSTRAINT + */ +public class AlterTableRenameConstraint extends AlterTable { + + private String constraintName; + private String newConstraintName; + + public AlterTableRenameConstraint(SessionLocal session, Schema schema) { + super(session, schema); + } + + public void setConstraintName(String string) { + constraintName = string; + } + + public void setNewConstraintName(String newName) { + this.newConstraintName = newName; + } + + @Override + public long update(Table table) { + Constraint constraint = getSchema().findConstraint(session, constraintName); + Database db = session.getDatabase(); + if (constraint == null || constraint.getConstraintType() == Type.DOMAIN || constraint.getTable() != table) { + throw DbException.get(ErrorCode.CONSTRAINT_NOT_FOUND_1, constraintName); + } + if (getSchema().findConstraint(session, newConstraintName) != null + || newConstraintName.equals(constraintName)) { + throw DbException.get(ErrorCode.CONSTRAINT_ALREADY_EXISTS_1, newConstraintName); + } + User user = session.getUser(); + Table refTable = constraint.getRefTable(); + if (refTable != table) { + user.checkTableRight(refTable, Right.SCHEMA_OWNER); + } + db.renameSchemaObject(session, constraint, newConstraintName); + return 0; + } + + @Override + public int getType() { + return CommandInterface.ALTER_TABLE_RENAME_CONSTRAINT; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/AlterUser.java b/h2/src/main/org/h2/command/ddl/AlterUser.java new file mode 100644 index 0000000..adaf83e --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/AlterUser.java @@ -0,0 +1,101 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.engine.User; +import org.h2.expression.Expression; +import org.h2.message.DbException; + +/** + * This class represents the statements + * ALTER USER ADMIN, + * ALTER USER RENAME, + * ALTER USER SET PASSWORD + */ +public class AlterUser extends DefineCommand { + + private int type; + private User user; + private String newName; + private Expression password; + private Expression salt; + private Expression hash; + private boolean admin; + + public AlterUser(SessionLocal session) { + super(session); + } + + public void setType(int type) { + this.type = type; + } + + public void setNewName(String newName) { + this.newName = newName; + } + + public void setUser(User user) { + this.user = user; + } + + public void setAdmin(boolean admin) { + this.admin = admin; + } + + public void setSalt(Expression e) { + salt = e; + } + + public void setHash(Expression e) { + hash = e; + } + + public void setPassword(Expression password) { + this.password = password; + } + + @Override + public long update() { + Database db = session.getDatabase(); + switch (type) { + case CommandInterface.ALTER_USER_SET_PASSWORD: + if (user != session.getUser()) { + session.getUser().checkAdmin(); + } + if (hash != null && salt != null) { + CreateUser.setSaltAndHash(user, session, salt, hash); + } else { + CreateUser.setPassword(user, session, password); + } + break; + case CommandInterface.ALTER_USER_RENAME: + session.getUser().checkAdmin(); + if (db.findUser(newName) != null || newName.equals(user.getName())) { + throw DbException.get(ErrorCode.USER_ALREADY_EXISTS_1, newName); + } + db.renameDatabaseObject(session, user, newName); + break; + case CommandInterface.ALTER_USER_ADMIN: + session.getUser().checkAdmin(); + user.setAdmin(admin); + break; + default: + throw DbException.getInternalError("type=" + type); + } + db.updateMeta(session, user); + return 0; + } + + @Override + public int getType() { + return type; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/AlterView.java b/h2/src/main/org/h2/command/ddl/AlterView.java new file mode 100644 index 0000000..2736016 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/AlterView.java @@ -0,0 +1,52 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.command.CommandInterface; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.table.TableView; + +/** + * This class represents the statement + * ALTER VIEW + */ +public class AlterView extends DefineCommand { + + private boolean ifExists; + private TableView view; + + public AlterView(SessionLocal session) { + super(session); + } + + public void setIfExists(boolean b) { + ifExists = b; + } + + public void setView(TableView view) { + this.view = view; + } + + @Override + public long update() { + if (view == null && ifExists) { + return 0; + } + session.getUser().checkSchemaOwner(view.getSchema()); + DbException e = view.recompile(session, false, true); + if (e != null) { + throw e; + } + return 0; + } + + @Override + public int getType() { + return CommandInterface.ALTER_VIEW; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/Analyze.java b/h2/src/main/org/h2/command/ddl/Analyze.java new file mode 100644 index 0000000..166d319 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/Analyze.java @@ -0,0 +1,233 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import java.util.Arrays; + +import org.h2.command.CommandInterface; +import org.h2.engine.Constants; +import org.h2.engine.Database; +import org.h2.engine.Right; +import org.h2.engine.SessionLocal; +import org.h2.index.Cursor; +import org.h2.result.Row; +import org.h2.schema.Schema; +import org.h2.table.Column; +import org.h2.table.Table; +import org.h2.table.TableType; +import org.h2.value.DataType; +import org.h2.value.Value; + +/** + * This class represents the statements + * ANALYZE and ANALYZE TABLE + */ +public class Analyze extends DefineCommand { + + private static final class SelectivityData { + + private long distinctCount; + + /** + * The number of occupied slots, excluding the zero element (if any). + */ + private int size; + + private int[] elements; + + /** + * Whether the zero element is present. + */ + private boolean zeroElement; + + private int maxSize; + + SelectivityData() { + elements = new int[8]; + maxSize = 7; + } + + void add(Value v) { + int currentSize = currentSize(); + if (currentSize >= Constants.SELECTIVITY_DISTINCT_COUNT) { + size = 0; + Arrays.fill(elements, 0); + zeroElement = false; + distinctCount += currentSize; + } + int hash = v.hashCode(); + if (hash == 0) { + zeroElement = true; + } else { + if (size >= maxSize) { + rehash(); + } + add(hash); + } + } + + int getSelectivity(long count) { + int s; + if (count == 0) { + s = 0; + } else { + s = (int) (100 * (distinctCount + currentSize()) / count); + if (s <= 0) { + s = 1; + } + } + return s; + } + + private int currentSize() { + int size = this.size; + if (zeroElement) { + size++; + } + return size; + } + + private void add(int element) { + int len = elements.length; + int mask = len - 1; + int index = element & mask; + int plus = 1; + do { + int k = elements[index]; + if (k == 0) { + // found an empty record + size++; + elements[index] = element; + return; + } else if (k == element) { + // existing element + return; + } + index = (index + plus++) & mask; + } while (plus <= len); + // no space, ignore + } + + private void rehash() { + size = 0; + int[] oldElements = elements; + int len = oldElements.length << 1; + elements = new int[len]; + maxSize = (int) (len * 90L / 100); + for (int k : oldElements) { + if (k != 0) { + add(k); + } + } + } + + } + + /** + * The sample size. + */ + private int sampleRows; + /** + * used in ANALYZE TABLE... + */ + private Table table; + + public Analyze(SessionLocal session) { + super(session); + sampleRows = session.getDatabase().getSettings().analyzeSample; + } + + public void setTable(Table table) { + this.table = table; + } + + @Override + public long update() { + session.getUser().checkAdmin(); + Database db = session.getDatabase(); + if (table != null) { + analyzeTable(session, table, sampleRows, true); + } else { + for (Schema schema : db.getAllSchemasNoMeta()) { + for (Table table : schema.getAllTablesAndViews(null)) { + analyzeTable(session, table, sampleRows, true); + } + } + } + return 0; + } + + /** + * Analyze this table. + * + * @param session the session + * @param table the table + * @param sample the number of sample rows + * @param manual whether the command was called by the user + */ + public static void analyzeTable(SessionLocal session, Table table, int sample, boolean manual) { + if (table.getTableType() != TableType.TABLE // + || table.isHidden() // + || session == null // + || !manual && (session.getDatabase().isSysTableLocked() || table.hasSelectTrigger()) // + || table.isTemporary() && !table.isGlobalTemporary() // + && session.findLocalTempTable(table.getName()) == null // + || table.isLockedExclusively() && !table.isLockedExclusivelyBy(session) + || !session.getUser().hasTableRight(table, Right.SELECT) // + // if the connection is closed and there is something to undo + || session.getCancel() != 0) { + return; + } + table.lock(session, Table.READ_LOCK); + Column[] columns = table.getColumns(); + int columnCount = columns.length; + if (columnCount == 0) { + return; + } + Cursor cursor = table.getScanIndex(session).find(session, null, null); + if (cursor.next()) { + SelectivityData[] array = new SelectivityData[columnCount]; + for (int i = 0; i < columnCount; i++) { + Column col = columns[i]; + if (!DataType.isLargeObject(col.getType().getValueType())) { + array[i] = new SelectivityData(); + } + } + long rowNumber = 0; + do { + Row row = cursor.get(); + for (int i = 0; i < columnCount; i++) { + SelectivityData selectivity = array[i]; + if (selectivity != null) { + selectivity.add(row.getValue(i)); + } + } + rowNumber++; + } while ((sample <= 0 || rowNumber < sample) && cursor.next()); + for (int i = 0; i < columnCount; i++) { + SelectivityData selectivity = array[i]; + if (selectivity != null) { + columns[i].setSelectivity(selectivity.getSelectivity(rowNumber)); + } + } + } else { + for (int i = 0; i < columnCount; i++) { + columns[i].setSelectivity(0); + } + } + session.getDatabase().updateMeta(session, table); + } + + public void setTop(int top) { + this.sampleRows = top; + } + + @Override + public int getType() { + return CommandInterface.ANALYZE; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/CommandWithColumns.java b/h2/src/main/org/h2/command/ddl/CommandWithColumns.java new file mode 100644 index 0000000..b8cb76e --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/CommandWithColumns.java @@ -0,0 +1,165 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import java.util.ArrayList; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Constants; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.schema.Sequence; +import org.h2.table.Column; +import org.h2.table.IndexColumn; + +public abstract class CommandWithColumns extends SchemaCommand { + + private ArrayList constraintCommands; + + private AlterTableAddConstraint primaryKey; + + protected CommandWithColumns(SessionLocal session, Schema schema) { + super(session, schema); + } + + /** + * Add a column to this table. + * + * @param column + * the column to add + */ + public abstract void addColumn(Column column); + + /** + * Add a constraint statement to this statement. The primary key definition is + * one possible constraint statement. + * + * @param command + * the statement to add + */ + public void addConstraintCommand(DefineCommand command) { + if (!(command instanceof CreateIndex)) { + AlterTableAddConstraint con = (AlterTableAddConstraint) command; + if (con.getType() == CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_PRIMARY_KEY) { + if (setPrimaryKey(con)) { + return; + } + } + } + getConstraintCommands().add(command); + } + + /** + * For the given list of columns, disable "nullable" for those columns that + * are primary key columns. + * + * @param columns the list of columns + */ + protected void changePrimaryKeysToNotNull(ArrayList columns) { + if (primaryKey != null) { + IndexColumn[] pkColumns = primaryKey.getIndexColumns(); + for (Column c : columns) { + for (IndexColumn idxCol : pkColumns) { + if (c.getName().equals(idxCol.columnName)) { + c.setNullable(false); + } + } + } + } + } + + /** + * Create the constraints. + */ + protected void createConstraints() { + if (constraintCommands != null) { + for (DefineCommand command : constraintCommands) { + command.setTransactional(transactional); + command.update(); + } + } + } + + /** + * For the given list of columns, create sequences for identity + * columns (if needed), and then get the list of all sequences of the + * columns. + * + * @param columns the columns + * @param temporary whether generated sequences should be temporary + * @return the list of sequences (may be empty) + */ + protected ArrayList generateSequences(ArrayList columns, boolean temporary) { + ArrayList sequences = new ArrayList<>(columns == null ? 0 : columns.size()); + if (columns != null) { + for (Column c : columns) { + if (c.hasIdentityOptions()) { + int objId = session.getDatabase().allocateObjectId(); + c.initializeSequence(session, getSchema(), objId, temporary); + if (!Constants.CLUSTERING_DISABLED.equals(session.getDatabase().getCluster())) { + throw DbException.getUnsupportedException("CLUSTERING && identity columns"); + } + } + Sequence seq = c.getSequence(); + if (seq != null) { + sequences.add(seq); + } + } + } + return sequences; + } + + private ArrayList getConstraintCommands() { + if (constraintCommands == null) { + constraintCommands = new ArrayList<>(); + } + return constraintCommands; + } + + /** + * Set the primary key, but also check if a primary key with different + * columns is already defined. + *

+ * If an unnamed primary key with the same columns is already defined it is + * removed from the list of constraints and this method returns + * {@code false}. + *

+ * + * @param primaryKey + * the primary key + * @return whether another primary key with the same columns was already set + * and the specified primary key should be ignored + */ + private boolean setPrimaryKey(AlterTableAddConstraint primaryKey) { + if (this.primaryKey != null) { + IndexColumn[] oldColumns = this.primaryKey.getIndexColumns(); + IndexColumn[] newColumns = primaryKey.getIndexColumns(); + int len = newColumns.length; + if (len != oldColumns.length) { + throw DbException.get(ErrorCode.SECOND_PRIMARY_KEY); + } + for (int i = 0; i < len; i++) { + if (!newColumns[i].columnName.equals(oldColumns[i].columnName)) { + throw DbException.get(ErrorCode.SECOND_PRIMARY_KEY); + } + } + if (this.primaryKey.getConstraintName() != null) { + return true; + } + // Remove unnamed primary key + constraintCommands.remove(this.primaryKey); + } + this.primaryKey = primaryKey; + return false; + } + + public AlterTableAddConstraint getPrimaryKey() { + return primaryKey; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/CreateAggregate.java b/h2/src/main/org/h2/command/ddl/CreateAggregate.java new file mode 100644 index 0000000..000f09f --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/CreateAggregate.java @@ -0,0 +1,69 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.schema.UserAggregate; + +/** + * This class represents the statement + * CREATE AGGREGATE + */ +public class CreateAggregate extends SchemaCommand { + + private String name; + private String javaClassMethod; + private boolean ifNotExists; + private boolean force; + + public CreateAggregate(SessionLocal session, Schema schema) { + super(session, schema); + } + + @Override + public long update() { + session.getUser().checkAdmin(); + Database db = session.getDatabase(); + Schema schema = getSchema(); + if (schema.findFunctionOrAggregate(name) != null) { + if (!ifNotExists) { + throw DbException.get(ErrorCode.FUNCTION_ALIAS_ALREADY_EXISTS_1, name); + } + } else { + int id = getObjectId(); + UserAggregate aggregate = new UserAggregate(schema, id, name, javaClassMethod, force); + db.addSchemaObject(session, aggregate); + } + return 0; + } + + public void setName(String name) { + this.name = name; + } + + public void setJavaClassMethod(String string) { + this.javaClassMethod = string; + } + + public void setIfNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + } + + public void setForce(boolean force) { + this.force = force; + } + + @Override + public int getType() { + return CommandInterface.CREATE_AGGREGATE; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/CreateConstant.java b/h2/src/main/org/h2/command/ddl/CreateConstant.java new file mode 100644 index 0000000..a66b8c3 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/CreateConstant.java @@ -0,0 +1,67 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.message.DbException; +import org.h2.schema.Constant; +import org.h2.schema.Schema; +import org.h2.value.Value; + +/** + * This class represents the statement + * CREATE CONSTANT + */ +public class CreateConstant extends SchemaOwnerCommand { + + private String constantName; + private Expression expression; + private boolean ifNotExists; + + public CreateConstant(SessionLocal session, Schema schema) { + super(session, schema); + } + + public void setIfNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + } + + @Override + long update(Schema schema) { + Database db = session.getDatabase(); + if (schema.findConstant(constantName) != null) { + if (ifNotExists) { + return 0; + } + throw DbException.get(ErrorCode.CONSTANT_ALREADY_EXISTS_1, constantName); + } + int id = getObjectId(); + Constant constant = new Constant(schema, id, constantName); + expression = expression.optimize(session); + Value value = expression.getValue(session); + constant.setValue(value); + db.addSchemaObject(session, constant); + return 0; + } + + public void setConstantName(String constantName) { + this.constantName = constantName; + } + + public void setExpression(Expression expr) { + this.expression = expr; + } + + @Override + public int getType() { + return CommandInterface.CREATE_CONSTANT; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/CreateDomain.java b/h2/src/main/org/h2/command/ddl/CreateDomain.java new file mode 100644 index 0000000..2af747f --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/CreateDomain.java @@ -0,0 +1,131 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import java.util.ArrayList; +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.message.DbException; +import org.h2.schema.Domain; +import org.h2.schema.Schema; +import org.h2.table.Table; +import org.h2.util.HasSQL; +import org.h2.util.Utils; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * This class represents the statement + * CREATE DOMAIN + */ +public class CreateDomain extends SchemaOwnerCommand { + + private String typeName; + private boolean ifNotExists; + + private TypeInfo dataType; + + private Domain parentDomain; + + private Expression defaultExpression; + + private Expression onUpdateExpression; + + private String comment; + + private ArrayList constraintCommands; + + public CreateDomain(SessionLocal session, Schema schema) { + super(session, schema); + } + + public void setTypeName(String name) { + this.typeName = name; + } + + public void setIfNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + } + + public void setDataType(TypeInfo dataType) { + this.dataType = dataType; + } + + public void setParentDomain(Domain parentDomain) { + this.parentDomain = parentDomain; + } + + public void setDefaultExpression(Expression defaultExpression) { + this.defaultExpression = defaultExpression; + } + + public void setOnUpdateExpression(Expression onUpdateExpression) { + this.onUpdateExpression = onUpdateExpression; + } + + public void setComment(String comment) { + this.comment = comment; + } + + @Override + long update(Schema schema) { + if (schema.findDomain(typeName) != null) { + if (ifNotExists) { + return 0; + } + throw DbException.get(ErrorCode.DOMAIN_ALREADY_EXISTS_1, typeName); + } + if (typeName.indexOf(' ') < 0) { + DataType builtIn = DataType.getTypeByName(typeName, session.getDatabase().getMode()); + if (builtIn != null) { + if (session.getDatabase().equalsIdentifiers(typeName, Value.getTypeName(builtIn.type))) { + throw DbException.get(ErrorCode.DOMAIN_ALREADY_EXISTS_1, typeName); + } + Table table = session.getDatabase().getFirstUserTable(); + if (table != null) { + StringBuilder builder = new StringBuilder(typeName).append(" ("); + table.getSQL(builder, HasSQL.TRACE_SQL_FLAGS).append(')'); + throw DbException.get(ErrorCode.DOMAIN_ALREADY_EXISTS_1, builder.toString()); + } + } + } + int id = getObjectId(); + Domain domain = new Domain(schema, id, typeName); + domain.setDataType(dataType != null ? dataType : parentDomain.getDataType()); + domain.setDomain(parentDomain); + domain.setDefaultExpression(session, defaultExpression); + domain.setOnUpdateExpression(session, onUpdateExpression); + domain.setComment(comment); + schema.getDatabase().addSchemaObject(session, domain); + if (constraintCommands != null) { + for (AlterDomainAddConstraint command : constraintCommands) { + command.update(); + } + } + return 0; + } + + @Override + public int getType() { + return CommandInterface.CREATE_DOMAIN; + } + + /** + * Add a constraint command. + * + * @param command the command to add + */ + public void addConstraintCommand(AlterDomainAddConstraint command) { + if (constraintCommands == null) { + constraintCommands = Utils.newSmallArrayList(); + } + constraintCommands.add(command); + } + +} diff --git a/h2/src/main/org/h2/command/ddl/CreateFunctionAlias.java b/h2/src/main/org/h2/command/ddl/CreateFunctionAlias.java new file mode 100644 index 0000000..0641dbc --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/CreateFunctionAlias.java @@ -0,0 +1,91 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.FunctionAlias; +import org.h2.schema.Schema; +import org.h2.util.StringUtils; + +/** + * This class represents the statement + * CREATE ALIAS + */ +public class CreateFunctionAlias extends SchemaCommand { + + private String aliasName; + private String javaClassMethod; + private boolean deterministic; + private boolean ifNotExists; + private boolean force; + private String source; + + public CreateFunctionAlias(SessionLocal session, Schema schema) { + super(session, schema); + } + + @Override + public long update() { + session.getUser().checkAdmin(); + Database db = session.getDatabase(); + Schema schema = getSchema(); + if (schema.findFunctionOrAggregate(aliasName) != null) { + if (!ifNotExists) { + throw DbException.get(ErrorCode.FUNCTION_ALIAS_ALREADY_EXISTS_1, aliasName); + } + } else { + int id = getObjectId(); + FunctionAlias functionAlias; + if (javaClassMethod != null) { + functionAlias = FunctionAlias.newInstance(schema, id, aliasName, javaClassMethod, force); + } else { + functionAlias = FunctionAlias.newInstanceFromSource(schema, id, aliasName, source, force); + } + functionAlias.setDeterministic(deterministic); + db.addSchemaObject(session, functionAlias); + } + return 0; + } + + public void setAliasName(String name) { + this.aliasName = name; + } + + /** + * Set the qualified method name after removing whitespace. + * + * @param method the qualified method name + */ + public void setJavaClassMethod(String method) { + this.javaClassMethod = StringUtils.replaceAll(method, " ", ""); + } + + public void setIfNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + } + + public void setForce(boolean force) { + this.force = force; + } + + public void setDeterministic(boolean deterministic) { + this.deterministic = deterministic; + } + + public void setSource(String source) { + this.source = source; + } + + @Override + public int getType() { + return CommandInterface.CREATE_ALIAS; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/CreateIndex.java b/h2/src/main/org/h2/command/ddl/CreateIndex.java new file mode 100644 index 0000000..cf00511 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/CreateIndex.java @@ -0,0 +1,132 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Constants; +import org.h2.engine.Database; +import org.h2.engine.Right; +import org.h2.engine.SessionLocal; +import org.h2.index.IndexType; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.table.IndexColumn; +import org.h2.table.Table; + +/** + * This class represents the statement + * CREATE INDEX + */ +public class CreateIndex extends SchemaCommand { + + private String tableName; + private String indexName; + private IndexColumn[] indexColumns; + private int uniqueColumnCount; + private boolean primaryKey, hash, spatial; + private boolean ifTableExists; + private boolean ifNotExists; + private String comment; + + public CreateIndex(SessionLocal session, Schema schema) { + super(session, schema); + } + + public void setIfTableExists(boolean b) { + this.ifTableExists = b; + } + + public void setIfNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + } + + public void setTableName(String tableName) { + this.tableName = tableName; + } + + public void setIndexName(String indexName) { + this.indexName = indexName; + } + + public void setIndexColumns(IndexColumn[] columns) { + this.indexColumns = columns; + } + + @Override + public long update() { + Database db = session.getDatabase(); + boolean persistent = db.isPersistent(); + Table table = getSchema().findTableOrView(session, tableName); + if (table == null) { + if (ifTableExists) { + return 0; + } + throw DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, tableName); + } + if (indexName != null && getSchema().findIndex(session, indexName) != null) { + if (ifNotExists) { + return 0; + } + throw DbException.get(ErrorCode.INDEX_ALREADY_EXISTS_1, indexName); + } + session.getUser().checkTableRight(table, Right.SCHEMA_OWNER); + table.lock(session, Table.EXCLUSIVE_LOCK); + if (!table.isPersistIndexes()) { + persistent = false; + } + int id = getObjectId(); + if (indexName == null) { + if (primaryKey) { + indexName = table.getSchema().getUniqueIndexName(session, + table, Constants.PREFIX_PRIMARY_KEY); + } else { + indexName = table.getSchema().getUniqueIndexName(session, + table, Constants.PREFIX_INDEX); + } + } + IndexType indexType; + if (primaryKey) { + if (table.findPrimaryKey() != null) { + throw DbException.get(ErrorCode.SECOND_PRIMARY_KEY); + } + indexType = IndexType.createPrimaryKey(persistent, hash); + } else if (uniqueColumnCount > 0) { + indexType = IndexType.createUnique(persistent, hash); + } else { + indexType = IndexType.createNonUnique(persistent, hash, spatial); + } + IndexColumn.mapColumns(indexColumns, table); + table.addIndex(session, indexName, id, indexColumns, uniqueColumnCount, indexType, create, comment); + return 0; + } + + public void setPrimaryKey(boolean b) { + this.primaryKey = b; + } + + public void setUniqueColumnCount(int uniqueColumnCount) { + this.uniqueColumnCount = uniqueColumnCount; + } + + public void setHash(boolean b) { + this.hash = b; + } + + public void setSpatial(boolean b) { + this.spatial = b; + } + + public void setComment(String comment) { + this.comment = comment; + } + + @Override + public int getType() { + return CommandInterface.CREATE_INDEX; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/CreateLinkedTable.java b/h2/src/main/org/h2/command/ddl/CreateLinkedTable.java new file mode 100644 index 0000000..d7ea31e --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/CreateLinkedTable.java @@ -0,0 +1,147 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.table.TableLink; + +/** + * This class represents the statement + * CREATE LINKED TABLE + */ +public class CreateLinkedTable extends SchemaCommand { + + private String tableName; + private String driver, url, user, password, originalSchema, originalTable; + private boolean ifNotExists; + private String comment; + private boolean emitUpdates; + private boolean force; + private boolean temporary; + private boolean globalTemporary; + private boolean readOnly; + private int fetchSize; + private boolean autocommit = true; + + public CreateLinkedTable(SessionLocal session, Schema schema) { + super(session, schema); + } + + public void setTableName(String tableName) { + this.tableName = tableName; + } + + public void setDriver(String driver) { + this.driver = driver; + } + + public void setOriginalTable(String originalTable) { + this.originalTable = originalTable; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setUrl(String url) { + this.url = url; + } + + public void setUser(String user) { + this.user = user; + } + + public void setIfNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + } + + /** + * Specify the number of rows fetched by the linked table command + * + * @param fetchSize to set + */ + public void setFetchSize(int fetchSize) { + this.fetchSize = fetchSize; + } + + /** + * Specify if the autocommit mode is activated or not + * + * @param mode to set + */ + public void setAutoCommit(boolean mode) { + this.autocommit= mode; + } + + @Override + public long update() { + session.getUser().checkAdmin(); + Database db = session.getDatabase(); + if (getSchema().resolveTableOrView(session, tableName) != null) { + if (ifNotExists) { + return 0; + } + throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, + tableName); + } + int id = getObjectId(); + TableLink table = getSchema().createTableLink(id, tableName, driver, url, + user, password, originalSchema, originalTable, emitUpdates, force); + table.setTemporary(temporary); + table.setGlobalTemporary(globalTemporary); + table.setComment(comment); + table.setReadOnly(readOnly); + if (fetchSize > 0) { + table.setFetchSize(fetchSize); + } + table.setAutoCommit(autocommit); + if (temporary && !globalTemporary) { + session.addLocalTempTable(table); + } else { + db.addSchemaObject(session, table); + } + return 0; + } + + public void setEmitUpdates(boolean emitUpdates) { + this.emitUpdates = emitUpdates; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public void setForce(boolean force) { + this.force = force; + } + + public void setTemporary(boolean temp) { + this.temporary = temp; + } + + public void setGlobalTemporary(boolean globalTemp) { + this.globalTemporary = globalTemp; + } + + public void setReadOnly(boolean readOnly) { + this.readOnly = readOnly; + } + + public void setOriginalSchema(String originalSchema) { + this.originalSchema = originalSchema; + } + + @Override + public int getType() { + return CommandInterface.CREATE_LINKED_TABLE; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/CreateRole.java b/h2/src/main/org/h2/command/ddl/CreateRole.java new file mode 100644 index 0000000..3add534 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/CreateRole.java @@ -0,0 +1,62 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Database; +import org.h2.engine.RightOwner; +import org.h2.engine.Role; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; + +/** + * This class represents the statement + * CREATE ROLE + */ +public class CreateRole extends DefineCommand { + + private String roleName; + private boolean ifNotExists; + + public CreateRole(SessionLocal session) { + super(session); + } + + public void setIfNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + } + + public void setRoleName(String name) { + this.roleName = name; + } + + @Override + public long update() { + session.getUser().checkAdmin(); + Database db = session.getDatabase(); + RightOwner rightOwner = db.findUserOrRole(roleName); + if (rightOwner != null) { + if (rightOwner instanceof Role) { + if (ifNotExists) { + return 0; + } + throw DbException.get(ErrorCode.ROLE_ALREADY_EXISTS_1, roleName); + } + throw DbException.get(ErrorCode.USER_ALREADY_EXISTS_1, roleName); + } + int id = getObjectId(); + Role role = new Role(db, id, roleName, false); + db.addDatabaseObject(session, role); + return 0; + } + + @Override + public int getType() { + return CommandInterface.CREATE_ROLE; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/CreateSchema.java b/h2/src/main/org/h2/command/ddl/CreateSchema.java new file mode 100644 index 0000000..fbab006 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/CreateSchema.java @@ -0,0 +1,74 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import java.util.ArrayList; +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Database; +import org.h2.engine.RightOwner; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Schema; + +/** + * This class represents the statement + * CREATE SCHEMA + */ +public class CreateSchema extends DefineCommand { + + private String schemaName; + private String authorization; + private boolean ifNotExists; + private ArrayList tableEngineParams; + + public CreateSchema(SessionLocal session) { + super(session); + } + + public void setIfNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + } + + @Override + public long update() { + session.getUser().checkSchemaAdmin(); + Database db = session.getDatabase(); + RightOwner owner = db.findUserOrRole(authorization); + if (owner == null) { + throw DbException.get(ErrorCode.USER_OR_ROLE_NOT_FOUND_1, authorization); + } + if (db.findSchema(schemaName) != null) { + if (ifNotExists) { + return 0; + } + throw DbException.get(ErrorCode.SCHEMA_ALREADY_EXISTS_1, schemaName); + } + int id = getObjectId(); + Schema schema = new Schema(db, id, schemaName, owner, false); + schema.setTableEngineParams(tableEngineParams); + db.addDatabaseObject(session, schema); + return 0; + } + + public void setSchemaName(String name) { + this.schemaName = name; + } + + public void setAuthorization(String userName) { + this.authorization = userName; + } + + public void setTableEngineParams(ArrayList tableEngineParams) { + this.tableEngineParams = tableEngineParams; + } + + @Override + public int getType() { + return CommandInterface.CREATE_SCHEMA; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/CreateSequence.java b/h2/src/main/org/h2/command/ddl/CreateSequence.java new file mode 100644 index 0000000..896a326 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/CreateSequence.java @@ -0,0 +1,70 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.schema.Sequence; + +/** + * This class represents the statement CREATE SEQUENCE. + */ +public class CreateSequence extends SchemaOwnerCommand { + + private String sequenceName; + + private boolean ifNotExists; + + private SequenceOptions options; + + private boolean belongsToTable; + + public CreateSequence(SessionLocal session, Schema schema) { + super(session, schema); + transactional = true; + } + + public void setSequenceName(String sequenceName) { + this.sequenceName = sequenceName; + } + + public void setIfNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + } + + public void setOptions(SequenceOptions options) { + this.options = options; + } + + @Override + long update(Schema schema) { + Database db = session.getDatabase(); + if (schema.findSequence(sequenceName) != null) { + if (ifNotExists) { + return 0; + } + throw DbException.get(ErrorCode.SEQUENCE_ALREADY_EXISTS_1, sequenceName); + } + int id = getObjectId(); + Sequence sequence = new Sequence(session, schema, id, sequenceName, options, belongsToTable); + db.addSchemaObject(session, sequence); + return 0; + } + + public void setBelongsToTable(boolean belongsToTable) { + this.belongsToTable = belongsToTable; + } + + @Override + public int getType() { + return CommandInterface.CREATE_SEQUENCE; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/CreateSynonym.java b/h2/src/main/org/h2/command/ddl/CreateSynonym.java new file mode 100644 index 0000000..5f94ad9 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/CreateSynonym.java @@ -0,0 +1,110 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.table.TableSynonym; + +/** + * This class represents the statement + * CREATE SYNONYM + */ +public class CreateSynonym extends SchemaOwnerCommand { + + private final CreateSynonymData data = new CreateSynonymData(); + private boolean ifNotExists; + private boolean orReplace; + private String comment; + + public CreateSynonym(SessionLocal session, Schema schema) { + super(session, schema); + } + + public void setName(String name) { + data.synonymName = name; + } + + public void setSynonymFor(String tableName) { + data.synonymFor = tableName; + } + + public void setSynonymForSchema(Schema synonymForSchema) { + data.synonymForSchema = synonymForSchema; + } + + public void setIfNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + } + + public void setOrReplace(boolean orReplace) { this.orReplace = orReplace; } + + @Override + long update(Schema schema) { + Database db = session.getDatabase(); + data.session = session; + db.lockMeta(session); + + if (schema.findTableOrView(session, data.synonymName) != null) { + throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, data.synonymName); + } + + if (data.synonymForSchema.findTableOrView(session, data.synonymFor) != null) { + return createTableSynonym(db); + } + + throw DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, + data.synonymForSchema.getName() + "." + data.synonymFor); + + } + + private int createTableSynonym(Database db) { + + TableSynonym old = getSchema().getSynonym(data.synonymName); + if (old != null) { + if (orReplace) { + // ok, we replacing the existing synonym + } else if (ifNotExists) { + return 0; + } else { + throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, data.synonymName); + } + } + + TableSynonym table; + if (old != null) { + table = old; + data.schema = table.getSchema(); + table.updateData(data); + table.setComment(comment); + table.setModified(); + db.updateMeta(session, table); + } else { + data.id = getObjectId(); + table = getSchema().createSynonym(data); + table.setComment(comment); + db.addSchemaObject(session, table); + } + + table.updateSynonymFor(); + return 0; + } + + public void setComment(String comment) { + this.comment = comment; + } + + @Override + public int getType() { + return CommandInterface.CREATE_SYNONYM; + } + + +} diff --git a/h2/src/main/org/h2/command/ddl/CreateSynonymData.java b/h2/src/main/org/h2/command/ddl/CreateSynonymData.java new file mode 100644 index 0000000..6e1122d --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/CreateSynonymData.java @@ -0,0 +1,44 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.engine.SessionLocal; +import org.h2.schema.Schema; + +/** + * The data required to create a synonym. + */ +public class CreateSynonymData { + + /** + * The schema. + */ + public Schema schema; + + /** + * The synonyms name. + */ + public String synonymName; + + /** + * The name of the table the synonym is created for. + */ + public String synonymFor; + + /** Schema synonymFor is located in. */ + public Schema synonymForSchema; + + /** + * The object id. + */ + public int id; + + /** + * The session. + */ + public SessionLocal session; + +} diff --git a/h2/src/main/org/h2/command/ddl/CreateTable.java b/h2/src/main/org/h2/command/ddl/CreateTable.java new file mode 100644 index 0000000..213b178 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/CreateTable.java @@ -0,0 +1,265 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import java.util.ArrayList; +import java.util.HashSet; +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.command.dml.Insert; +import org.h2.command.query.Query; +import org.h2.engine.Database; +import org.h2.engine.DbObject; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.schema.Sequence; +import org.h2.table.Column; +import org.h2.table.Table; +import org.h2.value.Value; + +/** + * This class represents the statement + * CREATE TABLE + */ +public class CreateTable extends CommandWithColumns { + + private final CreateTableData data = new CreateTableData(); + private boolean ifNotExists; + private boolean onCommitDrop; + private boolean onCommitTruncate; + private Query asQuery; + private String comment; + private boolean withNoData; + + public CreateTable(SessionLocal session, Schema schema) { + super(session, schema); + data.persistIndexes = true; + data.persistData = true; + } + + public void setQuery(Query query) { + this.asQuery = query; + } + + public void setTemporary(boolean temporary) { + data.temporary = temporary; + } + + public void setTableName(String tableName) { + data.tableName = tableName; + } + + @Override + public void addColumn(Column column) { + data.columns.add(column); + } + + public ArrayList getColumns() { + return data.columns; + } + + public void setIfNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + } + + @Override + public long update() { + Schema schema = getSchema(); + boolean isSessionTemporary = data.temporary && !data.globalTemporary; + if (!isSessionTemporary) { + session.getUser().checkSchemaOwner(schema); + } + Database db = session.getDatabase(); + if (!db.isPersistent()) { + data.persistIndexes = false; + } + if (!isSessionTemporary) { + db.lockMeta(session); + } + if (schema.resolveTableOrView(session, data.tableName) != null) { + if (ifNotExists) { + return 0; + } + throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, data.tableName); + } + if (asQuery != null) { + asQuery.prepare(); + if (data.columns.isEmpty()) { + generateColumnsFromQuery(); + } else if (data.columns.size() != asQuery.getColumnCount()) { + throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); + } else { + ArrayList columns = data.columns; + for (int i = 0; i < columns.size(); i++) { + Column column = columns.get(i); + if (column.getType().getValueType() == Value.UNKNOWN) { + columns.set(i, new Column(column.getName(), asQuery.getExpressions().get(i).getType())); + } + } + } + } + changePrimaryKeysToNotNull(data.columns); + data.id = getObjectId(); + data.session = session; + Table table = schema.createTable(data); + ArrayList sequences = generateSequences(data.columns, data.temporary); + table.setComment(comment); + if (isSessionTemporary) { + if (onCommitDrop) { + table.setOnCommitDrop(true); + } + if (onCommitTruncate) { + table.setOnCommitTruncate(true); + } + session.addLocalTempTable(table); + } else { + db.lockMeta(session); + db.addSchemaObject(session, table); + } + try { + for (Column c : data.columns) { + c.prepareExpressions(session); + } + for (Sequence sequence : sequences) { + table.addSequence(sequence); + } + createConstraints(); + HashSet set = new HashSet<>(); + table.addDependencies(set); + for (DbObject obj : set) { + if (obj == table) { + continue; + } + if (obj.getType() == DbObject.TABLE_OR_VIEW) { + if (obj instanceof Table) { + Table t = (Table) obj; + if (t.getId() > table.getId()) { + throw DbException.get( + ErrorCode.FEATURE_NOT_SUPPORTED_1, + "Table depends on another table " + + "with a higher ID: " + t + + ", this is currently not supported, " + + "as it would prevent the database from " + + "being re-opened"); + } + } + } + } + if (asQuery != null && !withNoData) { + boolean flushSequences = false; + if (!isSessionTemporary) { + db.unlockMeta(session); + for (Column c : table.getColumns()) { + Sequence s = c.getSequence(); + if (s != null) { + flushSequences = true; + s.setTemporary(true); + } + } + } + try { + session.startStatementWithinTransaction(null); + Insert insert = new Insert(session); + insert.setQuery(asQuery); + insert.setTable(table); + insert.setInsertFromSelect(true); + insert.prepare(); + insert.update(); + } finally { + session.endStatement(); + } + if (flushSequences) { + db.lockMeta(session); + for (Column c : table.getColumns()) { + Sequence s = c.getSequence(); + if (s != null) { + s.setTemporary(false); + s.flush(session); + } + } + } + } + } catch (DbException e) { + try { + db.checkPowerOff(); + db.removeSchemaObject(session, table); + if (!transactional) { + session.commit(true); + } + } catch (Throwable ex) { + e.addSuppressed(ex); + } + throw e; + } + return 0; + } + + private void generateColumnsFromQuery() { + int columnCount = asQuery.getColumnCount(); + ArrayList expressions = asQuery.getExpressions(); + for (int i = 0; i < columnCount; i++) { + Expression expr = expressions.get(i); + addColumn(new Column(expr.getColumnNameForView(session, i), expr.getType())); + } + } + + public void setPersistIndexes(boolean persistIndexes) { + data.persistIndexes = persistIndexes; + } + + public void setGlobalTemporary(boolean globalTemporary) { + data.globalTemporary = globalTemporary; + } + + /** + * This temporary table is dropped on commit. + */ + public void setOnCommitDrop() { + this.onCommitDrop = true; + } + + /** + * This temporary table is truncated on commit. + */ + public void setOnCommitTruncate() { + this.onCommitTruncate = true; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public void setPersistData(boolean persistData) { + data.persistData = persistData; + if (!persistData) { + data.persistIndexes = false; + } + } + + public void setWithNoData(boolean withNoData) { + this.withNoData = withNoData; + } + + public void setTableEngine(String tableEngine) { + data.tableEngine = tableEngine; + } + + public void setTableEngineParams(ArrayList tableEngineParams) { + data.tableEngineParams = tableEngineParams; + } + + public void setHidden(boolean isHidden) { + data.isHidden = isHidden; + } + + @Override + public int getType() { + return CommandInterface.CREATE_TABLE; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/CreateTableData.java b/h2/src/main/org/h2/command/ddl/CreateTableData.java new file mode 100644 index 0000000..7549b15 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/CreateTableData.java @@ -0,0 +1,78 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import java.util.ArrayList; + +import org.h2.engine.SessionLocal; +import org.h2.schema.Schema; +import org.h2.table.Column; + +/** + * The data required to create a table. + */ +public class CreateTableData { + + /** + * The schema. + */ + public Schema schema; + + /** + * The table name. + */ + public String tableName; + + /** + * The object id. + */ + public int id; + + /** + * The column list. + */ + public ArrayList columns = new ArrayList<>(); + + /** + * Whether this is a temporary table. + */ + public boolean temporary; + + /** + * Whether the table is global temporary. + */ + public boolean globalTemporary; + + /** + * Whether the indexes should be persisted. + */ + public boolean persistIndexes; + + /** + * Whether the data should be persisted. + */ + public boolean persistData; + + /** + * The session. + */ + public SessionLocal session; + + /** + * The table engine to use for creating the table. + */ + public String tableEngine; + + /** + * The table engine params to use for creating the table. + */ + public ArrayList tableEngineParams; + + /** + * The table is hidden. + */ + public boolean isHidden; +} diff --git a/h2/src/main/org/h2/command/ddl/CreateTrigger.java b/h2/src/main/org/h2/command/ddl/CreateTrigger.java new file mode 100644 index 0000000..9b098fe --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/CreateTrigger.java @@ -0,0 +1,145 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.api.Trigger; +import org.h2.command.CommandInterface; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.schema.TriggerObject; +import org.h2.table.Table; + +/** + * This class represents the statement + * CREATE TRIGGER + */ +public class CreateTrigger extends SchemaCommand { + + private String triggerName; + private boolean ifNotExists; + + private boolean insteadOf; + private boolean before; + private int typeMask; + private boolean rowBased; + private int queueSize = TriggerObject.DEFAULT_QUEUE_SIZE; + private boolean noWait; + private String tableName; + private String triggerClassName; + private String triggerSource; + private boolean force; + private boolean onRollback; + + public CreateTrigger(SessionLocal session, Schema schema) { + super(session, schema); + } + + public void setInsteadOf(boolean insteadOf) { + this.insteadOf = insteadOf; + } + + public void setBefore(boolean before) { + this.before = before; + } + + public void setTriggerClassName(String triggerClassName) { + this.triggerClassName = triggerClassName; + } + + public void setTriggerSource(String triggerSource) { + this.triggerSource = triggerSource; + } + + public void setTypeMask(int typeMask) { + this.typeMask = typeMask; + } + + public void setRowBased(boolean rowBased) { + this.rowBased = rowBased; + } + + public void setQueueSize(int size) { + this.queueSize = size; + } + + public void setNoWait(boolean noWait) { + this.noWait = noWait; + } + + public void setTableName(String tableName) { + this.tableName = tableName; + } + + public void setTriggerName(String name) { + this.triggerName = name; + } + + public void setIfNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + } + + @Override + public long update() { + session.getUser().checkAdmin(); + Database db = session.getDatabase(); + if (getSchema().findTrigger(triggerName) != null) { + if (ifNotExists) { + return 0; + } + throw DbException.get( + ErrorCode.TRIGGER_ALREADY_EXISTS_1, + triggerName); + } + if ((typeMask & Trigger.SELECT) != 0) { + if (rowBased) { + throw DbException.get(ErrorCode.INVALID_TRIGGER_FLAGS_1, "SELECT + FOR EACH ROW"); + } + if (onRollback) { + throw DbException.get(ErrorCode.INVALID_TRIGGER_FLAGS_1, "SELECT + ROLLBACK"); + } + } else if ((typeMask & (Trigger.INSERT | Trigger.UPDATE | Trigger.DELETE)) == 0) { + if (onRollback) { + throw DbException.get(ErrorCode.INVALID_TRIGGER_FLAGS_1, "(!INSERT & !UPDATE & !DELETE) + ROLLBACK"); + } + throw DbException.getInternalError(); + } + int id = getObjectId(); + Table table = getSchema().getTableOrView(session, tableName); + TriggerObject trigger = new TriggerObject(getSchema(), id, triggerName, table); + trigger.setInsteadOf(insteadOf); + trigger.setBefore(before); + trigger.setNoWait(noWait); + trigger.setQueueSize(queueSize); + trigger.setRowBased(rowBased); + trigger.setTypeMask(typeMask); + trigger.setOnRollback(onRollback); + if (this.triggerClassName != null) { + trigger.setTriggerClassName(triggerClassName, force); + } else { + trigger.setTriggerSource(triggerSource, force); + } + db.addSchemaObject(session, trigger); + table.addTrigger(trigger); + return 0; + } + + public void setForce(boolean force) { + this.force = force; + } + + public void setOnRollback(boolean onRollback) { + this.onRollback = onRollback; + } + + @Override + public int getType() { + return CommandInterface.CREATE_TRIGGER; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/CreateUser.java b/h2/src/main/org/h2/command/ddl/CreateUser.java new file mode 100644 index 0000000..17983aa --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/CreateUser.java @@ -0,0 +1,143 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Database; +import org.h2.engine.RightOwner; +import org.h2.engine.SessionLocal; +import org.h2.engine.User; +import org.h2.expression.Expression; +import org.h2.message.DbException; +import org.h2.security.SHA256; +import org.h2.util.StringUtils; +import org.h2.value.DataType; +import org.h2.value.Value; + +/** + * This class represents the statement + * CREATE USER + */ +public class CreateUser extends DefineCommand { + + private String userName; + private boolean admin; + private Expression password; + private Expression salt; + private Expression hash; + private boolean ifNotExists; + private String comment; + + public CreateUser(SessionLocal session) { + super(session); + } + + public void setIfNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public void setPassword(Expression password) { + this.password = password; + } + + /** + * Set the salt and hash for the given user. + * + * @param user the user + * @param session the session + * @param salt the salt + * @param hash the hash + */ + static void setSaltAndHash(User user, SessionLocal session, Expression salt, Expression hash) { + user.setSaltAndHash(getByteArray(session, salt), getByteArray(session, hash)); + } + + private static byte[] getByteArray(SessionLocal session, Expression e) { + Value value = e.optimize(session).getValue(session); + if (DataType.isBinaryStringType(value.getValueType())) { + byte[] b = value.getBytes(); + return b == null ? new byte[0] : b; + } + String s = value.getString(); + return s == null ? new byte[0] : StringUtils.convertHexToBytes(s); + } + + /** + * Set the password for the given user. + * + * @param user the user + * @param session the session + * @param password the password + */ + static void setPassword(User user, SessionLocal session, Expression password) { + String pwd = password.optimize(session).getValue(session).getString(); + char[] passwordChars = pwd == null ? new char[0] : pwd.toCharArray(); + byte[] userPasswordHash; + String userName = user.getName(); + if (userName.isEmpty() && passwordChars.length == 0) { + userPasswordHash = new byte[0]; + } else { + userPasswordHash = SHA256.getKeyPasswordHash(userName, passwordChars); + } + user.setUserPasswordHash(userPasswordHash); + } + + @Override + public long update() { + session.getUser().checkAdmin(); + Database db = session.getDatabase(); + RightOwner rightOwner = db.findUserOrRole(userName); + if (rightOwner != null) { + if (rightOwner instanceof User) { + if (ifNotExists) { + return 0; + } + throw DbException.get(ErrorCode.USER_ALREADY_EXISTS_1, userName); + } + throw DbException.get(ErrorCode.ROLE_ALREADY_EXISTS_1, userName); + } + int id = getObjectId(); + User user = new User(db, id, userName, false); + user.setAdmin(admin); + user.setComment(comment); + if (hash != null && salt != null) { + setSaltAndHash(user, session, salt, hash); + } else if (password != null) { + setPassword(user, session, password); + } else { + throw DbException.getInternalError(); + } + db.addDatabaseObject(session, user); + return 0; + } + + public void setSalt(Expression e) { + salt = e; + } + + public void setHash(Expression e) { + hash = e; + } + + public void setAdmin(boolean b) { + admin = b; + } + + public void setComment(String comment) { + this.comment = comment; + } + + @Override + public int getType() { + return CommandInterface.CREATE_USER; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/CreateView.java b/h2/src/main/org/h2/command/ddl/CreateView.java new file mode 100644 index 0000000..4134afc --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/CreateView.java @@ -0,0 +1,152 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import java.util.ArrayList; +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.command.query.Query; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.expression.Parameter; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.table.Column; +import org.h2.table.Table; +import org.h2.table.TableType; +import org.h2.table.TableView; +import org.h2.util.HasSQL; +import org.h2.value.TypeInfo; + +/** + * This class represents the statement + * CREATE VIEW + */ +public class CreateView extends SchemaOwnerCommand { + + private Query select; + private String viewName; + private boolean ifNotExists; + private String selectSQL; + private String[] columnNames; + private String comment; + private boolean orReplace; + private boolean force; + private boolean isTableExpression; + + public CreateView(SessionLocal session, Schema schema) { + super(session, schema); + } + + public void setViewName(String name) { + viewName = name; + } + + public void setSelect(Query select) { + this.select = select; + } + + public void setIfNotExists(boolean ifNotExists) { + this.ifNotExists = ifNotExists; + } + + public void setSelectSQL(String selectSQL) { + this.selectSQL = selectSQL; + } + + public void setColumnNames(String[] cols) { + this.columnNames = cols; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public void setOrReplace(boolean orReplace) { + this.orReplace = orReplace; + } + + public void setForce(boolean force) { + this.force = force; + } + + public void setTableExpression(boolean isTableExpression) { + this.isTableExpression = isTableExpression; + } + + @Override + long update(Schema schema) { + Database db = session.getDatabase(); + TableView view = null; + Table old = schema.findTableOrView(session, viewName); + if (old != null) { + if (ifNotExists) { + return 0; + } + if (!orReplace || TableType.VIEW != old.getTableType()) { + throw DbException.get(ErrorCode.VIEW_ALREADY_EXISTS_1, viewName); + } + view = (TableView) old; + } + int id = getObjectId(); + String querySQL; + if (select == null) { + querySQL = selectSQL; + } else { + ArrayList params = select.getParameters(); + if (params != null && !params.isEmpty()) { + throw DbException.getUnsupportedException("parameters in views"); + } + querySQL = select.getPlanSQL(HasSQL.DEFAULT_SQL_FLAGS); + } + Column[] columnTemplatesAsUnknowns = null; + Column[] columnTemplatesAsStrings = null; + if (columnNames != null) { + columnTemplatesAsUnknowns = new Column[columnNames.length]; + columnTemplatesAsStrings = new Column[columnNames.length]; + for (int i = 0; i < columnNames.length; ++i) { + // non table expressions are fine to use unknown column type + columnTemplatesAsUnknowns[i] = new Column(columnNames[i], TypeInfo.TYPE_UNKNOWN); + // table expressions can't have unknown types - so we use string instead + columnTemplatesAsStrings[i] = new Column(columnNames[i], TypeInfo.TYPE_VARCHAR); + } + } + if (view == null) { + if (isTableExpression) { + view = TableView.createTableViewMaybeRecursive(schema, id, viewName, querySQL, null, + columnTemplatesAsStrings, session, false /* literalsChecked */, isTableExpression, + false/*isTemporary*/, db); + } else { + view = new TableView(schema, id, viewName, querySQL, null, columnTemplatesAsUnknowns, session, + false, false, isTableExpression, false); + } + } else { + // TODO support isTableExpression in replace function... + view.replace(querySQL, columnTemplatesAsUnknowns, session, false, force, false); + view.setModified(); + } + if (comment != null) { + view.setComment(comment); + } + if (old == null) { + db.addSchemaObject(session, view); + db.unlockMeta(session); + } else { + db.updateMeta(session, view); + } + + // TODO: if we added any table expressions that aren't used by this view, detect them + // and drop them - otherwise they will leak and never get cleaned up. + + return 0; + } + + @Override + public int getType() { + return CommandInterface.CREATE_VIEW; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/DeallocateProcedure.java b/h2/src/main/org/h2/command/ddl/DeallocateProcedure.java new file mode 100644 index 0000000..dad6d05 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/DeallocateProcedure.java @@ -0,0 +1,38 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.command.CommandInterface; +import org.h2.engine.SessionLocal; + +/** + * This class represents the statement + * DEALLOCATE + */ +public class DeallocateProcedure extends DefineCommand { + + private String procedureName; + + public DeallocateProcedure(SessionLocal session) { + super(session); + } + + @Override + public long update() { + session.removeProcedure(procedureName); + return 0; + } + + public void setProcedureName(String name) { + this.procedureName = name; + } + + @Override + public int getType() { + return CommandInterface.DEALLOCATE; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/DefineCommand.java b/h2/src/main/org/h2/command/ddl/DefineCommand.java new file mode 100644 index 0000000..cf10794 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/DefineCommand.java @@ -0,0 +1,52 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.command.Prepared; +import org.h2.engine.SessionLocal; +import org.h2.result.ResultInterface; + +/** + * This class represents a non-transaction statement, for example a CREATE or + * DROP. + */ +public abstract class DefineCommand extends Prepared { + + /** + * The transactional behavior. The default is disabled, meaning the command + * commits an open transaction. + */ + protected boolean transactional; + + /** + * Create a new command for the given session. + * + * @param session the session + */ + DefineCommand(SessionLocal session) { + super(session); + } + + @Override + public boolean isReadOnly() { + return false; + } + + @Override + public ResultInterface queryMeta() { + return null; + } + + public void setTransactional(boolean transactional) { + this.transactional = transactional; + } + + @Override + public boolean isTransactional() { + return transactional; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/DropAggregate.java b/h2/src/main/org/h2/command/ddl/DropAggregate.java new file mode 100644 index 0000000..08cd6d5 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/DropAggregate.java @@ -0,0 +1,56 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.schema.UserAggregate; + +/** + * This class represents the statement + * DROP AGGREGATE + */ +public class DropAggregate extends SchemaOwnerCommand { + + private String name; + private boolean ifExists; + + public DropAggregate(SessionLocal session, Schema schema) { + super(session, schema); + } + + @Override + long update(Schema schema) { + Database db = session.getDatabase(); + UserAggregate aggregate = schema.findAggregate(name); + if (aggregate == null) { + if (!ifExists) { + throw DbException.get(ErrorCode.AGGREGATE_NOT_FOUND_1, name); + } + } else { + db.removeSchemaObject(session, aggregate); + } + return 0; + } + + public void setName(String name) { + this.name = name; + } + + public void setIfExists(boolean ifExists) { + this.ifExists = ifExists; + } + + @Override + public int getType() { + return CommandInterface.DROP_AGGREGATE; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/DropConstant.java b/h2/src/main/org/h2/command/ddl/DropConstant.java new file mode 100644 index 0000000..565031e --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/DropConstant.java @@ -0,0 +1,56 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Constant; +import org.h2.schema.Schema; + +/** + * This class represents the statement + * DROP CONSTANT + */ +public class DropConstant extends SchemaOwnerCommand { + + private String constantName; + private boolean ifExists; + + public DropConstant(SessionLocal session, Schema schema) { + super(session, schema); + } + + public void setIfExists(boolean b) { + ifExists = b; + } + + public void setConstantName(String constantName) { + this.constantName = constantName; + } + + @Override + long update(Schema schema) { + Database db = session.getDatabase(); + Constant constant = schema.findConstant(constantName); + if (constant == null) { + if (!ifExists) { + throw DbException.get(ErrorCode.CONSTANT_NOT_FOUND_1, constantName); + } + } else { + db.removeSchemaObject(session, constant); + } + return 0; + } + + @Override + public int getType() { + return CommandInterface.DROP_CONSTANT; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/DropDatabase.java b/h2/src/main/org/h2/command/ddl/DropDatabase.java new file mode 100644 index 0000000..a46fae9 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/DropDatabase.java @@ -0,0 +1,165 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import java.util.ArrayList; +import java.util.Collection; + +import org.h2.command.CommandInterface; +import org.h2.engine.Database; +import org.h2.engine.DbObject; +import org.h2.engine.Right; +import org.h2.engine.RightOwner; +import org.h2.engine.Role; +import org.h2.engine.SessionLocal; +import org.h2.engine.User; +import org.h2.schema.Schema; +import org.h2.schema.SchemaObject; +import org.h2.schema.Sequence; +import org.h2.table.Table; +import org.h2.table.TableType; +import org.h2.value.ValueNull; + +/** + * This class represents the statement + * DROP ALL OBJECTS + */ +public class DropDatabase extends DefineCommand { + + private boolean dropAllObjects; + private boolean deleteFiles; + + public DropDatabase(SessionLocal session) { + super(session); + } + + @Override + public long update() { + if (dropAllObjects) { + dropAllObjects(); + } + if (deleteFiles) { + session.getDatabase().setDeleteFilesOnDisconnect(true); + } + return 0; + } + + private void dropAllObjects() { + User user = session.getUser(); + user.checkAdmin(); + Database db = session.getDatabase(); + db.lockMeta(session); + + // There can be dependencies between tables e.g. using computed columns, + // so we might need to loop over them multiple times. + boolean runLoopAgain; + do { + ArrayList
tables = db.getAllTablesAndViews(); + ArrayList
toRemove = new ArrayList<>(tables.size()); + for (Table t : tables) { + if (t.getName() != null && + TableType.VIEW == t.getTableType()) { + toRemove.add(t); + } + } + for (Table t : tables) { + if (t.getName() != null && + TableType.TABLE_LINK == t.getTableType()) { + toRemove.add(t); + } + } + for (Table t : tables) { + if (t.getName() != null && + TableType.TABLE == t.getTableType() && + !t.isHidden()) { + toRemove.add(t); + } + } + for (Table t : tables) { + if (t.getName() != null && + TableType.EXTERNAL_TABLE_ENGINE == t.getTableType() && + !t.isHidden()) { + toRemove.add(t); + } + } + runLoopAgain = false; + for (Table t : toRemove) { + if (t.getName() == null) { + // ignore, already dead + } else if (db.getDependentTable(t, t) == null) { + db.removeSchemaObject(session, t); + } else { + runLoopAgain = true; + } + } + } while (runLoopAgain); + + // TODO session-local temp tables are not removed + Collection schemas = db.getAllSchemasNoMeta(); + for (Schema schema : schemas) { + if (schema.canDrop()) { + db.removeDatabaseObject(session, schema); + } + } + ArrayList list = new ArrayList<>(); + for (Schema schema : schemas) { + for (Sequence sequence : schema.getAllSequences()) { + // ignore these. the ones we want to drop will get dropped when we + // drop their associated tables, and we will ignore the problematic + // ones that belong to session-local temp tables. + if (!sequence.getBelongsToTable()) { + list.add(sequence); + } + } + } + // maybe constraints and triggers on system tables will be allowed in + // the future + addAll(schemas, DbObject.CONSTRAINT, list); + addAll(schemas, DbObject.TRIGGER, list); + addAll(schemas, DbObject.CONSTANT, list); + // Function aliases and aggregates are stored together + addAll(schemas, DbObject.FUNCTION_ALIAS, list); + addAll(schemas, DbObject.DOMAIN, list); + for (SchemaObject obj : list) { + if (!obj.getSchema().isValid() || obj.isHidden()) { + continue; + } + db.removeSchemaObject(session, obj); + } + Role publicRole = db.getPublicRole(); + for (RightOwner rightOwner : db.getAllUsersAndRoles()) { + if (rightOwner != user && rightOwner != publicRole) { + db.removeDatabaseObject(session, rightOwner); + } + } + for (Right right : db.getAllRights()) { + db.removeDatabaseObject(session, right); + } + for (SessionLocal s : db.getSessions(false)) { + s.setLastIdentity(ValueNull.INSTANCE); + } + } + + private static void addAll(Collection schemas, int type, ArrayList list) { + for (Schema schema : schemas) { + schema.getAll(type, list); + } + } + + public void setDropAllObjects(boolean b) { + this.dropAllObjects = b; + } + + public void setDeleteFiles(boolean b) { + this.deleteFiles = b; + } + + @Override + public int getType() { + return CommandInterface.DROP_ALL_OBJECTS; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/DropDomain.java b/h2/src/main/org/h2/command/ddl/DropDomain.java new file mode 100644 index 0000000..8426dc2 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/DropDomain.java @@ -0,0 +1,108 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import java.util.ArrayList; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.constraint.ConstraintActionType; +import org.h2.constraint.ConstraintDomain; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.message.DbException; +import org.h2.schema.Domain; +import org.h2.schema.Schema; +import org.h2.table.Column; +import org.h2.table.ColumnTemplate; +import org.h2.table.Table; + +/** + * This class represents the statement DROP DOMAIN + */ +public class DropDomain extends AlterDomain { + + private ConstraintActionType dropAction; + + public DropDomain(SessionLocal session, Schema schema) { + super(session, schema); + dropAction = session.getDatabase().getSettings().dropRestrict ? ConstraintActionType.RESTRICT + : ConstraintActionType.CASCADE; + } + + public void setDropAction(ConstraintActionType dropAction) { + this.dropAction = dropAction; + } + + @Override + long update(Schema schema, Domain domain) { + forAllDependencies(session, domain, this::copyColumn, this::copyDomain, true); + session.getDatabase().removeSchemaObject(session, domain); + return 0; + } + + private boolean copyColumn(Domain domain, Column targetColumn) { + Table targetTable = targetColumn.getTable(); + if (dropAction == ConstraintActionType.RESTRICT) { + throw DbException.get(ErrorCode.CANNOT_DROP_2, domainName, targetTable.getCreateSQL()); + } + String columnName = targetColumn.getName(); + ArrayList constraints = domain.getConstraints(); + if (constraints != null && !constraints.isEmpty()) { + for (ConstraintDomain constraint : constraints) { + Expression checkCondition = constraint.getCheckConstraint(session, columnName); + AlterTableAddConstraint check = new AlterTableAddConstraint(session, targetTable.getSchema(), + CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_CHECK, false); + check.setTableName(targetTable.getName()); + check.setCheckExpression(checkCondition); + check.update(); + } + } + copyExpressions(session, domain, targetColumn); + return true; + } + + private boolean copyDomain(Domain domain, Domain targetDomain) { + if (dropAction == ConstraintActionType.RESTRICT) { + throw DbException.get(ErrorCode.CANNOT_DROP_2, domainName, targetDomain.getTraceSQL()); + } + ArrayList constraints = domain.getConstraints(); + if (constraints != null && !constraints.isEmpty()) { + for (ConstraintDomain constraint : constraints) { + Expression checkCondition = constraint.getCheckConstraint(session, null); + AlterDomainAddConstraint check = new AlterDomainAddConstraint(session, targetDomain.getSchema(), // + false); + check.setDomainName(targetDomain.getName()); + check.setCheckExpression(checkCondition); + check.update(); + } + } + copyExpressions(session, domain, targetDomain); + return true; + } + + private static boolean copyExpressions(SessionLocal session, Domain domain, ColumnTemplate targetColumn) { + targetColumn.setDomain(domain.getDomain()); + Expression e = domain.getDefaultExpression(); + boolean modified = false; + if (e != null && targetColumn.getDefaultExpression() == null) { + targetColumn.setDefaultExpression(session, e); + modified = true; + } + e = domain.getOnUpdateExpression(); + if (e != null && targetColumn.getOnUpdateExpression() == null) { + targetColumn.setOnUpdateExpression(session, e); + modified = true; + } + return modified; + } + + @Override + public int getType() { + return CommandInterface.DROP_DOMAIN; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/DropFunctionAlias.java b/h2/src/main/org/h2/command/ddl/DropFunctionAlias.java new file mode 100644 index 0000000..2a9fb64 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/DropFunctionAlias.java @@ -0,0 +1,56 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.FunctionAlias; +import org.h2.schema.Schema; + +/** + * This class represents the statement + * DROP ALIAS + */ +public class DropFunctionAlias extends SchemaOwnerCommand { + + private String aliasName; + private boolean ifExists; + + public DropFunctionAlias(SessionLocal session, Schema schema) { + super(session, schema); + } + + @Override + long update(Schema schema) { + Database db = session.getDatabase(); + FunctionAlias functionAlias = schema.findFunction(aliasName); + if (functionAlias == null) { + if (!ifExists) { + throw DbException.get(ErrorCode.FUNCTION_ALIAS_NOT_FOUND_1, aliasName); + } + } else { + db.removeSchemaObject(session, functionAlias); + } + return 0; + } + + public void setAliasName(String name) { + this.aliasName = name; + } + + public void setIfExists(boolean ifExists) { + this.ifExists = ifExists; + } + + @Override + public int getType() { + return CommandInterface.DROP_ALIAS; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/DropIndex.java b/h2/src/main/org/h2/command/ddl/DropIndex.java new file mode 100644 index 0000000..37b66aa --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/DropIndex.java @@ -0,0 +1,87 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import java.util.ArrayList; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.constraint.Constraint; +import org.h2.engine.Database; +import org.h2.engine.Right; +import org.h2.engine.SessionLocal; +import org.h2.index.Index; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.table.Table; + +/** + * This class represents the statement + * DROP INDEX + */ +public class DropIndex extends SchemaCommand { + + private String indexName; + private boolean ifExists; + + public DropIndex(SessionLocal session, Schema schema) { + super(session, schema); + } + + public void setIfExists(boolean b) { + ifExists = b; + } + + public void setIndexName(String indexName) { + this.indexName = indexName; + } + + @Override + public long update() { + Database db = session.getDatabase(); + Index index = getSchema().findIndex(session, indexName); + if (index == null) { + if (!ifExists) { + throw DbException.get(ErrorCode.INDEX_NOT_FOUND_1, indexName); + } + } else { + Table table = index.getTable(); + session.getUser().checkTableRight(index.getTable(), Right.SCHEMA_OWNER); + Constraint pkConstraint = null; + ArrayList constraints = table.getConstraints(); + for (int i = 0; constraints != null && i < constraints.size(); i++) { + Constraint cons = constraints.get(i); + if (cons.usesIndex(index)) { + // can drop primary key index (for compatibility) + if (Constraint.Type.PRIMARY_KEY == cons.getConstraintType()) { + for (Constraint c : constraints) { + if (c.getReferencedConstraint() == cons) { + throw DbException.get(ErrorCode.INDEX_BELONGS_TO_CONSTRAINT_2, indexName, + cons.getName()); + } + } + pkConstraint = cons; + } else { + throw DbException.get(ErrorCode.INDEX_BELONGS_TO_CONSTRAINT_2, indexName, cons.getName()); + } + } + } + index.getTable().setModified(); + if (pkConstraint != null) { + db.removeSchemaObject(session, pkConstraint); + } else { + db.removeSchemaObject(session, index); + } + } + return 0; + } + + @Override + public int getType() { + return CommandInterface.DROP_INDEX; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/DropRole.java b/h2/src/main/org/h2/command/ddl/DropRole.java new file mode 100644 index 0000000..5fdac38 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/DropRole.java @@ -0,0 +1,60 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Database; +import org.h2.engine.Role; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; + +/** + * This class represents the statement + * DROP ROLE + */ +public class DropRole extends DefineCommand { + + private String roleName; + private boolean ifExists; + + public DropRole(SessionLocal session) { + super(session); + } + + public void setRoleName(String roleName) { + this.roleName = roleName; + } + + @Override + public long update() { + session.getUser().checkAdmin(); + Database db = session.getDatabase(); + Role role = db.findRole(roleName); + if (role == null) { + if (!ifExists) { + throw DbException.get(ErrorCode.ROLE_NOT_FOUND_1, roleName); + } + } else { + if (role == db.getPublicRole()) { + throw DbException.get(ErrorCode.ROLE_CAN_NOT_BE_DROPPED_1, roleName); + } + role.checkOwnsNoSchemas(); + db.removeDatabaseObject(session, role); + } + return 0; + } + + public void setIfExists(boolean ifExists) { + this.ifExists = ifExists; + } + + @Override + public int getType() { + return CommandInterface.DROP_ROLE; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/DropSchema.java b/h2/src/main/org/h2/command/ddl/DropSchema.java new file mode 100644 index 0000000..3a8ea29 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/DropSchema.java @@ -0,0 +1,83 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import java.util.ArrayList; +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.constraint.ConstraintActionType; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.schema.SchemaObject; + +/** + * This class represents the statement + * DROP SCHEMA + */ +public class DropSchema extends DefineCommand { + + private String schemaName; + private boolean ifExists; + private ConstraintActionType dropAction; + + public DropSchema(SessionLocal session) { + super(session); + dropAction = session.getDatabase().getSettings().dropRestrict ? + ConstraintActionType.RESTRICT : ConstraintActionType.CASCADE; + } + + public void setSchemaName(String name) { + this.schemaName = name; + } + + @Override + public long update() { + Database db = session.getDatabase(); + Schema schema = db.findSchema(schemaName); + if (schema == null) { + if (!ifExists) { + throw DbException.get(ErrorCode.SCHEMA_NOT_FOUND_1, schemaName); + } + } else { + session.getUser().checkSchemaOwner(schema); + if (!schema.canDrop()) { + throw DbException.get(ErrorCode.SCHEMA_CAN_NOT_BE_DROPPED_1, schemaName); + } + if (dropAction == ConstraintActionType.RESTRICT && !schema.isEmpty()) { + ArrayList all = schema.getAll(null); + int size = all.size(); + if (size > 0) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < size; i++) { + if (i > 0) { + builder.append(", "); + } + builder.append(all.get(i).getName()); + } + throw DbException.get(ErrorCode.CANNOT_DROP_2, schemaName, builder.toString()); + } + } + db.removeDatabaseObject(session, schema); + } + return 0; + } + + public void setIfExists(boolean ifExists) { + this.ifExists = ifExists; + } + + public void setDropAction(ConstraintActionType dropAction) { + this.dropAction = dropAction; + } + + @Override + public int getType() { + return CommandInterface.DROP_SCHEMA; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/DropSequence.java b/h2/src/main/org/h2/command/ddl/DropSequence.java new file mode 100644 index 0000000..451c628 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/DropSequence.java @@ -0,0 +1,57 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.schema.Sequence; + +/** + * This class represents the statement + * DROP SEQUENCE + */ +public class DropSequence extends SchemaOwnerCommand { + + private String sequenceName; + private boolean ifExists; + + public DropSequence(SessionLocal session, Schema schema) { + super(session, schema); + } + + public void setIfExists(boolean b) { + ifExists = b; + } + + public void setSequenceName(String sequenceName) { + this.sequenceName = sequenceName; + } + + @Override + long update(Schema schema) { + Sequence sequence = schema.findSequence(sequenceName); + if (sequence == null) { + if (!ifExists) { + throw DbException.get(ErrorCode.SEQUENCE_NOT_FOUND_1, sequenceName); + } + } else { + if (sequence.getBelongsToTable()) { + throw DbException.get(ErrorCode.SEQUENCE_BELONGS_TO_A_TABLE_1, sequenceName); + } + session.getDatabase().removeSchemaObject(session, sequence); + } + return 0; + } + + @Override + public int getType() { + return CommandInterface.DROP_SEQUENCE; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/DropSynonym.java b/h2/src/main/org/h2/command/ddl/DropSynonym.java new file mode 100644 index 0000000..fcab524 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/DropSynonym.java @@ -0,0 +1,54 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.table.TableSynonym; + +/** + * This class represents the statement + * DROP SYNONYM + */ +public class DropSynonym extends SchemaOwnerCommand { + + private String synonymName; + private boolean ifExists; + + public DropSynonym(SessionLocal session, Schema schema) { + super(session, schema); + } + + public void setSynonymName(String name) { + this.synonymName = name; + } + + @Override + long update(Schema schema) { + TableSynonym synonym = schema.getSynonym(synonymName); + if (synonym == null) { + if (!ifExists) { + throw DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, synonymName); + } + } else { + session.getDatabase().removeSchemaObject(session, synonym); + } + return 0; + } + + public void setIfExists(boolean ifExists) { + this.ifExists = ifExists; + } + + @Override + public int getType() { + return CommandInterface.DROP_SYNONYM; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/DropTable.java b/h2/src/main/org/h2/command/ddl/DropTable.java new file mode 100644 index 0000000..c907d56 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/DropTable.java @@ -0,0 +1,149 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.constraint.Constraint; +import org.h2.constraint.ConstraintActionType; +import org.h2.engine.Database; +import org.h2.engine.Right; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.table.Table; +import org.h2.table.TableView; +import org.h2.util.Utils; + +/** + * This class represents the statement + * DROP TABLE + */ +public class DropTable extends DefineCommand { + + private boolean ifExists; + private ConstraintActionType dropAction; + + private final ArrayList tables = Utils.newSmallArrayList(); + + public DropTable(SessionLocal session) { + super(session); + dropAction = session.getDatabase().getSettings().dropRestrict ? + ConstraintActionType.RESTRICT : + ConstraintActionType.CASCADE; + } + + public void setIfExists(boolean b) { + ifExists = b; + } + + /** + * Add a table to drop. + * + * @param schema the schema + * @param tableName the table name + */ + public void addTable(Schema schema, String tableName) { + tables.add(new SchemaAndTable(schema, tableName)); + } + + private boolean prepareDrop() { + HashSet
tablesToDrop = new HashSet<>(); + for (SchemaAndTable schemaAndTable : tables) { + String tableName = schemaAndTable.tableName; + Table table = schemaAndTable.schema.findTableOrView(session, tableName); + if (table == null) { + if (!ifExists) { + throw DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, tableName); + } + } else { + session.getUser().checkTableRight(table, Right.SCHEMA_OWNER); + if (!table.canDrop()) { + throw DbException.get(ErrorCode.CANNOT_DROP_TABLE_1, tableName); + } + tablesToDrop.add(table); + } + } + if (tablesToDrop.isEmpty()) { + return false; + } + for (Table table : tablesToDrop) { + ArrayList dependencies = new ArrayList<>(); + if (dropAction == ConstraintActionType.RESTRICT) { + CopyOnWriteArrayList dependentViews = table.getDependentViews(); + if (dependentViews != null && !dependentViews.isEmpty()) { + for (TableView v : dependentViews) { + if (!tablesToDrop.contains(v)) { + dependencies.add(v.getName()); + } + } + } + final List constraints = table.getConstraints(); + if (constraints != null && !constraints.isEmpty()) { + for (Constraint c : constraints) { + if (!tablesToDrop.contains(c.getTable())) { + dependencies.add(c.getName()); + } + } + } + if (!dependencies.isEmpty()) { + throw DbException.get(ErrorCode.CANNOT_DROP_2, table.getName(), String.join(", ", dependencies)); + } + } + table.lock(session, Table.EXCLUSIVE_LOCK); + } + return true; + } + + private void executeDrop() { + for (SchemaAndTable schemaAndTable : tables) { + // need to get the table again, because it may be dropped already + // meanwhile (dependent object, or same object) + Table table = schemaAndTable.schema.findTableOrView(session, schemaAndTable.tableName); + if (table != null) { + table.setModified(); + Database db = session.getDatabase(); + db.lockMeta(session); + db.removeSchemaObject(session, table); + } + } + } + + @Override + public long update() { + if (prepareDrop()) { + executeDrop(); + } + return 0; + } + + public void setDropAction(ConstraintActionType dropAction) { + this.dropAction = dropAction; + } + + @Override + public int getType() { + return CommandInterface.DROP_TABLE; + } + + private static final class SchemaAndTable { + + final Schema schema; + + final String tableName; + + SchemaAndTable(Schema schema, String tableName) { + this.schema = schema; + this.tableName = tableName; + } + + } + +} diff --git a/h2/src/main/org/h2/command/ddl/DropTrigger.java b/h2/src/main/org/h2/command/ddl/DropTrigger.java new file mode 100644 index 0000000..3e304bd --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/DropTrigger.java @@ -0,0 +1,60 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Database; +import org.h2.engine.Right; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.schema.TriggerObject; +import org.h2.table.Table; + +/** + * This class represents the statement + * DROP TRIGGER + */ +public class DropTrigger extends SchemaCommand { + + private String triggerName; + private boolean ifExists; + + public DropTrigger(SessionLocal session, Schema schema) { + super(session, schema); + } + + public void setIfExists(boolean b) { + ifExists = b; + } + + public void setTriggerName(String triggerName) { + this.triggerName = triggerName; + } + + @Override + public long update() { + Database db = session.getDatabase(); + TriggerObject trigger = getSchema().findTrigger(triggerName); + if (trigger == null) { + if (!ifExists) { + throw DbException.get(ErrorCode.TRIGGER_NOT_FOUND_1, triggerName); + } + } else { + Table table = trigger.getTable(); + session.getUser().checkTableRight(table, Right.SCHEMA_OWNER); + db.removeSchemaObject(session, trigger); + } + return 0; + } + + @Override + public int getType() { + return CommandInterface.DROP_TRIGGER; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/DropUser.java b/h2/src/main/org/h2/command/ddl/DropUser.java new file mode 100644 index 0000000..3f72099 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/DropUser.java @@ -0,0 +1,74 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Database; +import org.h2.engine.RightOwner; +import org.h2.engine.SessionLocal; +import org.h2.engine.User; +import org.h2.message.DbException; + +/** + * This class represents the statement + * DROP USER + */ +public class DropUser extends DefineCommand { + + private boolean ifExists; + private String userName; + + public DropUser(SessionLocal session) { + super(session); + } + + public void setIfExists(boolean b) { + ifExists = b; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + @Override + public long update() { + session.getUser().checkAdmin(); + Database db = session.getDatabase(); + User user = db.findUser(userName); + if (user == null) { + if (!ifExists) { + throw DbException.get(ErrorCode.USER_NOT_FOUND_1, userName); + } + } else { + if (user == session.getUser()) { + int adminUserCount = 0; + for (RightOwner rightOwner : db.getAllUsersAndRoles()) { + if (rightOwner instanceof User && ((User) rightOwner).isAdmin()) { + adminUserCount++; + } + } + if (adminUserCount == 1) { + throw DbException.get(ErrorCode.CANNOT_DROP_CURRENT_USER); + } + } + user.checkOwnsNoSchemas(); + db.removeDatabaseObject(session, user); + } + return 0; + } + + @Override + public boolean isTransactional() { + return false; + } + + @Override + public int getType() { + return CommandInterface.DROP_USER; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/DropView.java b/h2/src/main/org/h2/command/ddl/DropView.java new file mode 100644 index 0000000..35c8462 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/DropView.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.command.ddl; + +import java.util.ArrayList; +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.constraint.ConstraintActionType; +import org.h2.engine.DbObject; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.table.Table; +import org.h2.table.TableType; +import org.h2.table.TableView; + +/** + * This class represents the statement + * DROP VIEW + */ +public class DropView extends SchemaCommand { + + private String viewName; + private boolean ifExists; + private ConstraintActionType dropAction; + + public DropView(SessionLocal session, Schema schema) { + super(session, schema); + dropAction = session.getDatabase().getSettings().dropRestrict ? + ConstraintActionType.RESTRICT : + ConstraintActionType.CASCADE; + } + + public void setIfExists(boolean b) { + ifExists = b; + } + + public void setDropAction(ConstraintActionType dropAction) { + this.dropAction = dropAction; + } + + public void setViewName(String viewName) { + this.viewName = viewName; + } + + @Override + public long update() { + Table view = getSchema().findTableOrView(session, viewName); + if (view == null) { + if (!ifExists) { + throw DbException.get(ErrorCode.VIEW_NOT_FOUND_1, viewName); + } + } else { + if (TableType.VIEW != view.getTableType()) { + throw DbException.get(ErrorCode.VIEW_NOT_FOUND_1, viewName); + } + session.getUser().checkSchemaOwner(view.getSchema()); + + if (dropAction == ConstraintActionType.RESTRICT) { + for (DbObject child : view.getChildren()) { + if (child instanceof TableView) { + throw DbException.get(ErrorCode.CANNOT_DROP_2, viewName, child.getName()); + } + } + } + + // TODO: Where is the ConstraintReferential.CASCADE style drop processing ? It's + // supported from imported keys - but not for dependent db objects + + TableView tableView = (TableView) view; + ArrayList
copyOfDependencies = new ArrayList<>(tableView.getTables()); + + view.lock(session, Table.EXCLUSIVE_LOCK); + session.getDatabase().removeSchemaObject(session, view); + + // remove dependent table expressions + for (Table childTable: copyOfDependencies) { + if (TableType.VIEW == childTable.getTableType()) { + TableView childTableView = (TableView) childTable; + if (childTableView.isTableExpression() && childTableView.getName() != null) { + session.getDatabase().removeSchemaObject(session, childTableView); + } + } + } + // make sure its all unlocked + session.getDatabase().unlockMeta(session); + } + return 0; + } + + @Override + public int getType() { + return CommandInterface.DROP_VIEW; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/GrantRevoke.java b/h2/src/main/org/h2/command/ddl/GrantRevoke.java new file mode 100644 index 0000000..3fc52cf --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/GrantRevoke.java @@ -0,0 +1,225 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import java.util.ArrayList; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Database; +import org.h2.engine.DbObject; +import org.h2.engine.Right; +import org.h2.engine.RightOwner; +import org.h2.engine.Role; +import org.h2.engine.SessionLocal; +import org.h2.engine.User; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.table.Table; +import org.h2.util.Utils; + +/** + * This class represents the statements + * GRANT RIGHT, + * GRANT ROLE, + * REVOKE RIGHT, + * REVOKE ROLE + */ +public class GrantRevoke extends DefineCommand { + + private ArrayList roleNames; + private int operationType; + private int rightMask; + private final ArrayList
tables = Utils.newSmallArrayList(); + private Schema schema; + private RightOwner grantee; + + public GrantRevoke(SessionLocal session) { + super(session); + } + + public void setOperationType(int operationType) { + this.operationType = operationType; + } + + /** + * Add the specified right bit to the rights bitmap. + * + * @param right the right bit + */ + public void addRight(int right) { + this.rightMask |= right; + } + + /** + * Add the specified role to the list of roles. + * + * @param roleName the role + */ + public void addRoleName(String roleName) { + if (roleNames == null) { + roleNames = Utils.newSmallArrayList(); + } + roleNames.add(roleName); + } + + public void setGranteeName(String granteeName) { + Database db = session.getDatabase(); + grantee = db.findUserOrRole(granteeName); + if (grantee == null) { + throw DbException.get(ErrorCode.USER_OR_ROLE_NOT_FOUND_1, granteeName); + } + } + + @Override + public long update() { + Database db = session.getDatabase(); + User user = session.getUser(); + if (roleNames != null) { + user.checkAdmin(); + for (String name : roleNames) { + Role grantedRole = db.findRole(name); + if (grantedRole == null) { + throw DbException.get(ErrorCode.ROLE_NOT_FOUND_1, name); + } + if (operationType == CommandInterface.GRANT) { + grantRole(grantedRole); + } else if (operationType == CommandInterface.REVOKE) { + revokeRole(grantedRole); + } else { + throw DbException.getInternalError("type=" + operationType); + } + } + } else { + if ((rightMask & Right.ALTER_ANY_SCHEMA) != 0) { + user.checkAdmin(); + } else { + if (schema != null) { + user.checkSchemaOwner(schema); + } + for (Table table : tables) { + user.checkSchemaOwner(table.getSchema()); + } + } + if (operationType == CommandInterface.GRANT) { + grantRight(); + } else if (operationType == CommandInterface.REVOKE) { + revokeRight(); + } else { + throw DbException.getInternalError("type=" + operationType); + } + } + return 0; + } + + private void grantRight() { + if (schema != null) { + grantRight(schema); + } + for (Table table : tables) { + grantRight(table); + } + } + + private void grantRight(DbObject object) { + Database db = session.getDatabase(); + Right right = grantee.getRightForObject(object); + if (right == null) { + int id = getPersistedObjectId(); + if (id == 0) { + id = session.getDatabase().allocateObjectId(); + } + right = new Right(db, id, grantee, rightMask, object); + grantee.grantRight(object, right); + db.addDatabaseObject(session, right); + } else { + right.setRightMask(right.getRightMask() | rightMask); + db.updateMeta(session, right); + } + } + + private void grantRole(Role grantedRole) { + if (grantedRole != grantee && grantee.isRoleGranted(grantedRole)) { + return; + } + if (grantee instanceof Role) { + Role granteeRole = (Role) grantee; + if (grantedRole.isRoleGranted(granteeRole)) { + // cyclic role grants are not allowed + throw DbException.get(ErrorCode.ROLE_ALREADY_GRANTED_1, grantedRole.getTraceSQL()); + } + } + Database db = session.getDatabase(); + int id = getObjectId(); + Right right = new Right(db, id, grantee, grantedRole); + db.addDatabaseObject(session, right); + grantee.grantRole(grantedRole, right); + } + + private void revokeRight() { + if (schema != null) { + revokeRight(schema); + } + for (Table table : tables) { + revokeRight(table); + } + } + + private void revokeRight(DbObject object) { + Right right = grantee.getRightForObject(object); + if (right == null) { + return; + } + int mask = right.getRightMask(); + int newRight = mask & ~rightMask; + Database db = session.getDatabase(); + if (newRight == 0) { + db.removeDatabaseObject(session, right); + } else { + right.setRightMask(newRight); + db.updateMeta(session, right); + } + } + + + private void revokeRole(Role grantedRole) { + Right right = grantee.getRightForRole(grantedRole); + if (right == null) { + return; + } + Database db = session.getDatabase(); + db.removeDatabaseObject(session, right); + } + + @Override + public boolean isTransactional() { + return false; + } + + /** + * Add the specified table to the list of tables. + * + * @param table the table + */ + public void addTable(Table table) { + tables.add(table); + } + + /** + * Set the specified schema + * + * @param schema the schema + */ + public void setSchema(Schema schema) { + this.schema = schema; + } + + @Override + public int getType() { + return operationType; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/PrepareProcedure.java b/h2/src/main/org/h2/command/ddl/PrepareProcedure.java new file mode 100644 index 0000000..028ab2f --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/PrepareProcedure.java @@ -0,0 +1,62 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import java.util.ArrayList; + +import org.h2.command.CommandInterface; +import org.h2.command.Prepared; +import org.h2.engine.Procedure; +import org.h2.engine.SessionLocal; +import org.h2.expression.Parameter; + +/** + * This class represents the statement + * PREPARE + */ +public class PrepareProcedure extends DefineCommand { + + private String procedureName; + private Prepared prepared; + + public PrepareProcedure(SessionLocal session) { + super(session); + } + + @Override + public void checkParameters() { + // no not check parameters + } + + @Override + public long update() { + Procedure proc = new Procedure(procedureName, prepared); + prepared.setParameterList(parameters); + prepared.setPrepareAlways(prepareAlways); + prepared.prepare(); + session.addProcedure(proc); + return 0; + } + + public void setProcedureName(String name) { + this.procedureName = name; + } + + public void setPrepared(Prepared prep) { + this.prepared = prep; + } + + @Override + public ArrayList getParameters() { + return new ArrayList<>(0); + } + + @Override + public int getType() { + return CommandInterface.PREPARE; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/SchemaCommand.java b/h2/src/main/org/h2/command/ddl/SchemaCommand.java new file mode 100644 index 0000000..14cf2c7 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/SchemaCommand.java @@ -0,0 +1,38 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.engine.SessionLocal; +import org.h2.schema.Schema; + +/** + * This class represents a non-transaction statement that involves a schema. + */ +public abstract class SchemaCommand extends DefineCommand { + + private final Schema schema; + + /** + * Create a new command. + * + * @param session the session + * @param schema the schema + */ + public SchemaCommand(SessionLocal session, Schema schema) { + super(session); + this.schema = schema; + } + + /** + * Get the schema + * + * @return the schema + */ + protected final Schema getSchema() { + return schema; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/SchemaOwnerCommand.java b/h2/src/main/org/h2/command/ddl/SchemaOwnerCommand.java new file mode 100644 index 0000000..28d432e --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/SchemaOwnerCommand.java @@ -0,0 +1,38 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.engine.SessionLocal; +import org.h2.schema.Schema; + +/** + * This class represents a non-transaction statement that involves a schema and + * requires schema owner rights. + */ +abstract class SchemaOwnerCommand extends SchemaCommand { + + /** + * Create a new command. + * + * @param session + * the session + * @param schema + * the schema + */ + SchemaOwnerCommand(SessionLocal session, Schema schema) { + super(session, schema); + } + + @Override + public final long update() { + Schema schema = getSchema(); + session.getUser().checkSchemaOwner(schema); + return update(schema); + } + + abstract long update(Schema schema); + +} diff --git a/h2/src/main/org/h2/command/ddl/SequenceOptions.java b/h2/src/main/org/h2/command/ddl/SequenceOptions.java new file mode 100644 index 0000000..801db6e --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/SequenceOptions.java @@ -0,0 +1,362 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ValueExpression; +import org.h2.message.DbException; +import org.h2.schema.Sequence; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBigint; +import org.h2.value.ValueNull; + +/** + * Sequence options. + */ +public class SequenceOptions { + + private TypeInfo dataType; + + private Expression start; + + private Expression restart; + + private Expression increment; + + private Expression maxValue; + + private Expression minValue; + + private Sequence.Cycle cycle; + + private Expression cacheSize; + + private long[] bounds; + + private final Sequence oldSequence; + + private static Long getLong(SessionLocal session, Expression expr) { + if (expr != null) { + Value value = expr.optimize(session).getValue(session); + if (value != ValueNull.INSTANCE) { + return value.getLong(); + } + } + return null; + } + + /** + * Creates new instance of sequence options. + */ + public SequenceOptions() { + oldSequence = null; + } + + /** + * Creates new instance of sequence options. + * + * @param oldSequence + * the sequence to copy options from + * @param dataType + * the new data type + */ + public SequenceOptions(Sequence oldSequence, TypeInfo dataType) { + this.oldSequence = oldSequence; + this.dataType = dataType; + // Check data type correctness immediately + getBounds(); + } + + public TypeInfo getDataType() { + if (oldSequence != null) { + synchronized (oldSequence) { + copyFromOldSequence(); + } + } + return dataType; + } + + private void copyFromOldSequence() { + long bounds[] = getBounds(); + long min = Math.max(oldSequence.getMinValue(), bounds[0]); + long max = Math.min(oldSequence.getMaxValue(), bounds[1]); + if (max < min) { + min = bounds[0]; + max = bounds[1]; + } + minValue = ValueExpression.get(ValueBigint.get(min)); + maxValue = ValueExpression.get(ValueBigint.get(max)); + long v = oldSequence.getStartValue(); + if (v >= min && v <= max) { + start = ValueExpression.get(ValueBigint.get(v)); + } + v = oldSequence.getBaseValue(); + if (v >= min && v <= max) { + restart = ValueExpression.get(ValueBigint.get(v)); + } + increment = ValueExpression.get(ValueBigint.get(oldSequence.getIncrement())); + cycle = oldSequence.getCycle(); + cacheSize = ValueExpression.get(ValueBigint.get(oldSequence.getCacheSize())); + } + + public void setDataType(TypeInfo dataType) { + this.dataType = dataType; + } + + /** + * Gets start value. + * + * @param session The session to calculate the value. + * @return start value or {@code null} if value is not defined. + */ + public Long getStartValue(SessionLocal session) { + return check(getLong(session, start)); + } + + /** + * Sets start value expression. + * + * @param start START WITH value expression. + */ + public void setStartValue(Expression start) { + this.start = start; + } + + /** + * Gets restart value. + * + * @param session + * the session to calculate the value + * @param startValue + * the start value to use if restart without value is specified + * @return restart value or {@code null} if value is not defined. + */ + public Long getRestartValue(SessionLocal session, long startValue) { + return check(restart == ValueExpression.DEFAULT ? (Long) startValue : getLong(session, restart)); + } + + /** + * Sets restart value expression, or {@link ValueExpression#DEFAULT}. + * + * @param restart + * RESTART WITH value expression, or + * {@link ValueExpression#DEFAULT} for simple RESTART + */ + public void setRestartValue(Expression restart) { + this.restart = restart; + } + + /** + * Gets increment value. + * + * @param session The session to calculate the value. + * @return increment value or {@code null} if value is not defined. + */ + public Long getIncrement(SessionLocal session) { + return check(getLong(session, increment)); + } + + /** + * Sets increment value expression. + * + * @param increment INCREMENT BY value expression. + */ + public void setIncrement(Expression increment) { + this.increment = increment; + } + + /** + * Gets max value. + * + * @param sequence the sequence to get default max value. + * @param session The session to calculate the value. + * @return max value when the MAXVALUE expression is set, otherwise returns default max value. + */ + public Long getMaxValue(Sequence sequence, SessionLocal session) { + Long v; + if (maxValue == ValueExpression.NULL && sequence != null) { + v = Sequence.getDefaultMaxValue(getCurrentStart(sequence, session), + increment != null ? getIncrement(session) : sequence.getIncrement(), getBounds()); + } else { + v = getLong(session, maxValue); + } + return check(v); + } + + /** + * Sets max value expression. + * + * @param maxValue MAXVALUE expression. + */ + public void setMaxValue(Expression maxValue) { + this.maxValue = maxValue; + } + + /** + * Gets min value. + * + * @param sequence the sequence to get default min value. + * @param session The session to calculate the value. + * @return min value when the MINVALUE expression is set, otherwise returns default min value. + */ + public Long getMinValue(Sequence sequence, SessionLocal session) { + Long v; + if (minValue == ValueExpression.NULL && sequence != null) { + v = Sequence.getDefaultMinValue(getCurrentStart(sequence, session), + increment != null ? getIncrement(session) : sequence.getIncrement(), getBounds()); + } else { + v = getLong(session, minValue); + } + return check(v); + } + + /** + * Sets min value expression. + * + * @param minValue MINVALUE expression. + */ + public void setMinValue(Expression minValue) { + this.minValue = minValue; + } + + private Long check(Long value) { + if (value == null) { + return null; + } else { + long[] bounds = getBounds(); + long v = value; + if (v < bounds[0] || v > bounds[1]) { + throw DbException.get(ErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_1, Long.toString(v)); + } + } + return value; + } + + public long[] getBounds() { + long[] bounds = this.bounds; + if (bounds == null) { + this.bounds = bounds = getBounds(dataType); + } + return bounds; + } + + /** + * Get the bounds (min, max) of a data type. + * + * @param dataType the data type + * @return the bounds (an array with 2 elements) + */ + public static long[] getBounds(TypeInfo dataType) { + long min, max; + switch (dataType.getValueType()) { + case Value.TINYINT: + min = Byte.MIN_VALUE; + max = Byte.MAX_VALUE; + break; + case Value.SMALLINT: + min = Short.MIN_VALUE; + max = Short.MAX_VALUE; + break; + case Value.INTEGER: + min = Integer.MIN_VALUE; + max = Integer.MAX_VALUE; + break; + case Value.BIGINT: + min = Long.MIN_VALUE; + max = Long.MAX_VALUE; + break; + case Value.REAL: + min = -0x100_0000; + max = 0x100_0000; + break; + case Value.DOUBLE: + min = -0x20_0000_0000_0000L; + max = 0x20_0000_0000_0000L; + break; + case Value.NUMERIC: { + if (dataType.getScale() != 0) { + throw DbException.getUnsupportedException(dataType.getTraceSQL()); + } + long p = (dataType.getPrecision() - dataType.getScale()); + if (p <= 0) { + throw DbException.getUnsupportedException(dataType.getTraceSQL()); + } else if (p > 18) { + min = Long.MIN_VALUE; + max = Long.MAX_VALUE; + } else { + max = 10; + for (int i = 1; i < p; i++) { + max *= 10; + } + min = - --max; + } + break; + } + case Value.DECFLOAT: { + long p = dataType.getPrecision(); + if (p > 18) { + min = Long.MIN_VALUE; + max = Long.MAX_VALUE; + } else { + max = 10; + for (int i = 1; i < p; i++) { + max *= 10; + } + min = -max; + } + break; + } + default: + throw DbException.getUnsupportedException(dataType.getTraceSQL()); + } + long bounds[] = { min, max }; + return bounds; + } + + /** + * Gets cycle option. + * + * @return cycle option value or {@code null} if is not defined. + */ + public Sequence.Cycle getCycle() { + return cycle; + } + + /** + * Sets cycle option. + * + * @param cycle option value. + */ + public void setCycle(Sequence.Cycle cycle) { + this.cycle = cycle; + } + + /** + * Gets cache size. + * + * @param session The session to calculate the value. + * @return cache size or {@code null} if value is not defined. + */ + public Long getCacheSize(SessionLocal session) { + return getLong(session, cacheSize); + } + + /** + * Sets cache size. + * + * @param cacheSize cache size. + */ + public void setCacheSize(Expression cacheSize) { + this.cacheSize = cacheSize; + } + + private long getCurrentStart(Sequence sequence, SessionLocal session) { + return start != null ? getStartValue(session) : sequence.getBaseValue(); + } +} diff --git a/h2/src/main/org/h2/command/ddl/SetComment.java b/h2/src/main/org/h2/command/ddl/SetComment.java new file mode 100644 index 0000000..ba936cc --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/SetComment.java @@ -0,0 +1,186 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Comment; +import org.h2.engine.Database; +import org.h2.engine.DbObject; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.table.Table; + +/** + * This class represents the statement + * COMMENT + */ +public class SetComment extends DefineCommand { + + private String schemaName; + private String objectName; + private boolean column; + private String columnName; + private int objectType; + private Expression expr; + + public SetComment(SessionLocal session) { + super(session); + } + + @Override + public long update() { + Database db = session.getDatabase(); + DbObject object = null; + int errorCode = ErrorCode.GENERAL_ERROR_1; + if (schemaName == null) { + schemaName = session.getCurrentSchemaName(); + } + switch (objectType) { + case DbObject.CONSTANT: { + Schema schema = db.getSchema(schemaName); + session.getUser().checkSchemaOwner(schema); + object = schema.getConstant(objectName); + break; + } + case DbObject.CONSTRAINT: { + Schema schema = db.getSchema(schemaName); + session.getUser().checkSchemaOwner(schema); + object = schema.getConstraint(objectName); + break; + } + case DbObject.FUNCTION_ALIAS: { + Schema schema = db.getSchema(schemaName); + session.getUser().checkSchemaOwner(schema); + object = schema.findFunction(objectName); + errorCode = ErrorCode.FUNCTION_ALIAS_NOT_FOUND_1; + break; + } + case DbObject.INDEX: { + Schema schema = db.getSchema(schemaName); + session.getUser().checkSchemaOwner(schema); + object = schema.getIndex(objectName); + break; + } + case DbObject.ROLE: + session.getUser().checkAdmin(); + schemaName = null; + object = db.findRole(objectName); + errorCode = ErrorCode.ROLE_NOT_FOUND_1; + break; + case DbObject.SCHEMA: { + schemaName = null; + Schema schema = db.getSchema(objectName); + session.getUser().checkSchemaOwner(schema); + object = schema; + break; + } + case DbObject.SEQUENCE: { + Schema schema = db.getSchema(schemaName); + session.getUser().checkSchemaOwner(schema); + object = schema.getSequence(objectName); + break; + } + case DbObject.TABLE_OR_VIEW: { + Schema schema = db.getSchema(schemaName); + session.getUser().checkSchemaOwner(schema); + object = schema.getTableOrView(session, objectName); + break; + } + case DbObject.TRIGGER: { + Schema schema = db.getSchema(schemaName); + session.getUser().checkSchemaOwner(schema); + object = schema.findTrigger(objectName); + errorCode = ErrorCode.TRIGGER_NOT_FOUND_1; + break; + } + case DbObject.USER: + session.getUser().checkAdmin(); + schemaName = null; + object = db.getUser(objectName); + break; + case DbObject.DOMAIN: { + Schema schema = db.getSchema(schemaName); + session.getUser().checkSchemaOwner(schema); + object = schema.findDomain(objectName); + errorCode = ErrorCode.DOMAIN_NOT_FOUND_1; + break; + } + default: + } + if (object == null) { + throw DbException.get(errorCode, objectName); + } + String text = expr.optimize(session).getValue(session).getString(); + if (text != null && text.isEmpty()) { + text = null; + } + if (column) { + Table table = (Table) object; + table.getColumn(columnName).setComment(text); + } else { + object.setComment(text); + } + if (column || objectType == DbObject.TABLE_OR_VIEW || + objectType == DbObject.USER || + objectType == DbObject.INDEX || + objectType == DbObject.CONSTRAINT) { + db.updateMeta(session, object); + } else { + Comment comment = db.findComment(object); + if (comment == null) { + if (text == null) { + // reset a non-existing comment - nothing to do + } else { + int id = getObjectId(); + comment = new Comment(db, id, object); + comment.setCommentText(text); + db.addDatabaseObject(session, comment); + } + } else { + if (text == null) { + db.removeDatabaseObject(session, comment); + } else { + comment.setCommentText(text); + db.updateMeta(session, comment); + } + } + } + return 0; + } + + public void setCommentExpression(Expression expr) { + this.expr = expr; + } + + public void setObjectName(String objectName) { + this.objectName = objectName; + } + + public void setObjectType(int objectType) { + this.objectType = objectType; + } + + public void setColumnName(String columnName) { + this.columnName = columnName; + } + + public void setSchemaName(String schemaName) { + this.schemaName = schemaName; + } + + public void setColumn(boolean column) { + this.column = column; + } + + @Override + public int getType() { + return CommandInterface.COMMENT; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/TruncateTable.java b/h2/src/main/org/h2/command/ddl/TruncateTable.java new file mode 100644 index 0000000..6bb244f --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/TruncateTable.java @@ -0,0 +1,64 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.ddl; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Right; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Sequence; +import org.h2.table.Column; +import org.h2.table.Table; + +/** + * This class represents the statement + * TRUNCATE TABLE + */ +public class TruncateTable extends DefineCommand { + + private Table table; + + private boolean restart; + + public TruncateTable(SessionLocal session) { + super(session); + } + + public void setTable(Table table) { + this.table = table; + } + + public void setRestart(boolean restart) { + this.restart = restart; + } + + @Override + public long update() { + if (!table.canTruncate()) { + throw DbException.get(ErrorCode.CANNOT_TRUNCATE_1, table.getTraceSQL()); + } + session.getUser().checkTableRight(table, Right.DELETE); + table.lock(session, Table.EXCLUSIVE_LOCK); + long result = table.truncate(session); + if (restart) { + for (Column column : table.getColumns()) { + Sequence sequence = column.getSequence(); + if (sequence != null) { + sequence.modify(sequence.getStartValue(), null, null, null, null, null, null); + session.getDatabase().updateMeta(session, sequence); + } + } + } + return result; + } + + @Override + public int getType() { + return CommandInterface.TRUNCATE_TABLE; + } + +} diff --git a/h2/src/main/org/h2/command/ddl/package.html b/h2/src/main/org/h2/command/ddl/package.html new file mode 100644 index 0000000..9862a68 --- /dev/null +++ b/h2/src/main/org/h2/command/ddl/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Contains DDL (data definition language) and related SQL statements. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/command/dml/AlterTableSet.java b/h2/src/main/org/h2/command/dml/AlterTableSet.java new file mode 100644 index 0000000..9d3a3c1 --- /dev/null +++ b/h2/src/main/org/h2/command/dml/AlterTableSet.java @@ -0,0 +1,80 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.command.ddl.SchemaCommand; +import org.h2.engine.Right; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.table.Table; + +/** + * This class represents the statement + * ALTER TABLE SET + */ +public class AlterTableSet extends SchemaCommand { + + private boolean ifTableExists; + private String tableName; + private final int type; + + private final boolean value; + private boolean checkExisting; + + public AlterTableSet(SessionLocal session, Schema schema, int type, boolean value) { + super(session, schema); + this.type = type; + this.value = value; + } + + public void setCheckExisting(boolean b) { + this.checkExisting = b; + } + + @Override + public boolean isTransactional() { + return true; + } + + public void setIfTableExists(boolean b) { + this.ifTableExists = b; + } + + public void setTableName(String tableName) { + this.tableName = tableName; + } + + @Override + public long update() { + Table table = getSchema().resolveTableOrView(session, tableName); + if (table == null) { + if (ifTableExists) { + return 0; + } + throw DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, tableName); + } + session.getUser().checkTableRight(table, Right.SCHEMA_OWNER); + table.lock(session, Table.EXCLUSIVE_LOCK); + switch (type) { + case CommandInterface.ALTER_TABLE_SET_REFERENTIAL_INTEGRITY: + table.setCheckForeignKeyConstraints(session, value, value ? + checkExisting : false); + break; + default: + throw DbException.getInternalError("type="+type); + } + return 0; + } + + @Override + public int getType() { + return type; + } + +} diff --git a/h2/src/main/org/h2/command/dml/BackupCommand.java b/h2/src/main/org/h2/command/dml/BackupCommand.java new file mode 100644 index 0000000..709147d --- /dev/null +++ b/h2/src/main/org/h2/command/dml/BackupCommand.java @@ -0,0 +1,143 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.command.Prepared; +import org.h2.engine.Constants; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.message.DbException; +import org.h2.mvstore.MVStore; +import org.h2.mvstore.db.Store; +import org.h2.result.ResultInterface; +import org.h2.store.FileLister; +import org.h2.store.fs.FileUtils; +import org.h2.util.IOUtils; + +/** + * This class represents the statement + * BACKUP + */ +public class BackupCommand extends Prepared { + + private Expression fileNameExpr; + + public BackupCommand(SessionLocal session) { + super(session); + } + + public void setFileName(Expression fileName) { + this.fileNameExpr = fileName; + } + + @Override + public long update() { + String name = fileNameExpr.getValue(session).getString(); + session.getUser().checkAdmin(); + backupTo(name); + return 0; + } + + private void backupTo(String fileName) { + Database db = session.getDatabase(); + if (!db.isPersistent()) { + throw DbException.get(ErrorCode.DATABASE_IS_NOT_PERSISTENT); + } + try { + Store store = db.getStore(); + store.flush(); + String name = db.getName(); + name = FileUtils.getName(name); + try (OutputStream zip = FileUtils.newOutputStream(fileName, false)) { + ZipOutputStream out = new ZipOutputStream(zip); + db.flush(); + // synchronize on the database, to avoid concurrent temp file + // creation / deletion / backup + String base = FileUtils.getParent(db.getName()); + synchronized (db.getLobSyncObject()) { + String prefix = db.getDatabasePath(); + String dir = FileUtils.getParent(prefix); + dir = FileLister.getDir(dir); + ArrayList fileList = FileLister.getDatabaseFiles(dir, name, true); + for (String n : fileList) { + if (n.endsWith(Constants.SUFFIX_MV_FILE)) { + MVStore s = store.getMvStore(); + boolean before = s.getReuseSpace(); + s.setReuseSpace(false); + try { + InputStream in = store.getInputStream(); + backupFile(out, base, n, in); + } finally { + s.setReuseSpace(before); + } + } + } + } + out.close(); + } + } catch (IOException e) { + throw DbException.convertIOException(e, fileName); + } + } + + private static void backupFile(ZipOutputStream out, String base, String fn, + InputStream in) throws IOException { + String f = FileUtils.toRealPath(fn); + base = FileUtils.toRealPath(base); + if (!f.startsWith(base)) { + throw DbException.getInternalError(f + " does not start with " + base); + } + f = f.substring(base.length()); + f = correctFileName(f); + out.putNextEntry(new ZipEntry(f)); + IOUtils.copyAndCloseInput(in, out); + out.closeEntry(); + } + + @Override + public boolean isTransactional() { + return true; + } + + /** + * Fix the file name, replacing backslash with slash. + * + * @param f the file name + * @return the corrected file name + */ + public static String correctFileName(String f) { + f = f.replace('\\', '/'); + if (f.startsWith("/")) { + f = f.substring(1); + } + return f; + } + + @Override + public boolean needRecompile() { + return false; + } + + @Override + public ResultInterface queryMeta() { + return null; + } + + @Override + public int getType() { + return CommandInterface.BACKUP; + } + +} diff --git a/h2/src/main/org/h2/command/dml/Call.java b/h2/src/main/org/h2/command/dml/Call.java new file mode 100644 index 0000000..7302298 --- /dev/null +++ b/h2/src/main/org/h2/command/dml/Call.java @@ -0,0 +1,131 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import org.h2.command.CommandInterface; +import org.h2.command.Prepared; +import org.h2.engine.SessionLocal; +import org.h2.expression.Alias; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.function.table.TableFunction; +import org.h2.result.LocalResult; +import org.h2.result.ResultInterface; +import org.h2.table.Column; +import org.h2.value.Value; + +/** + * This class represents the statement + * CALL. + */ +public class Call extends Prepared { + + private Expression expression; + + private TableFunction tableFunction; + + private Expression[] expressions; + + public Call(SessionLocal session) { + super(session); + } + + @Override + public ResultInterface queryMeta() { + int columnCount = expressions.length; + LocalResult result = new LocalResult(session, expressions, columnCount, columnCount); + result.done(); + return result; + } + + @Override + public long update() { + if (tableFunction != null) { + // this will throw an exception + // methods returning a result set may not be called like this. + return super.update(); + } + Value v = expression.getValue(session); + int type = v.getValueType(); + switch (type) { + case Value.UNKNOWN: + case Value.NULL: + return 0; + default: + return v.getInt(); + } + } + + @Override + public ResultInterface query(long maxrows) { + setCurrentRowNumber(1); + if (tableFunction != null) { + return tableFunction.getValue(session); + } + LocalResult result = new LocalResult(session, expressions, 1, 1); + result.addRow(expression.getValue(session)); + result.done(); + return result; + } + + @Override + public void prepare() { + if (tableFunction != null) { + prepareAlways = true; + tableFunction.optimize(session); + ResultInterface result = tableFunction.getValueTemplate(session); + int columnCount = result.getVisibleColumnCount(); + expressions = new Expression[columnCount]; + for (int i = 0; i < columnCount; i++) { + String name = result.getColumnName(i); + String alias = result.getAlias(i); + Expression e = new ExpressionColumn(session.getDatabase(), new Column(name, result.getColumnType(i))); + if (!alias.equals(name)) { + e = new Alias(e, alias, false); + } + expressions[i] = e; + } + } else { + expressions = new Expression[] { expression = expression.optimize(session) }; + } + } + + public void setExpression(Expression expression) { + this.expression = expression; + } + + public void setTableFunction(TableFunction tableFunction) { + this.tableFunction = tableFunction; + } + + @Override + public boolean isQuery() { + return true; + } + + @Override + public boolean isTransactional() { + return true; + } + + @Override + public boolean isReadOnly() { + return tableFunction == null && expression.isEverything(ExpressionVisitor.READONLY_VISITOR); + + } + + @Override + public int getType() { + return CommandInterface.CALL; + } + + @Override + public boolean isCacheable() { + return tableFunction == null; + } + +} diff --git a/h2/src/main/org/h2/command/dml/CommandWithValues.java b/h2/src/main/org/h2/command/dml/CommandWithValues.java new file mode 100644 index 0000000..592981a --- /dev/null +++ b/h2/src/main/org/h2/command/dml/CommandWithValues.java @@ -0,0 +1,44 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import java.util.ArrayList; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.util.Utils; + +/** + * Command that supports VALUES clause. + */ +public abstract class CommandWithValues extends DataChangeStatement { + + /** + * Expression data for the VALUES clause. + */ + protected final ArrayList valuesExpressionList = Utils.newSmallArrayList(); + + /** + * Creates new instance of command with VALUES clause. + * + * @param session + * the session + */ + protected CommandWithValues(SessionLocal session) { + super(session); + } + + /** + * Add a row to this command. + * + * @param expr + * the list of values + */ + public void addRow(Expression[] expr) { + valuesExpressionList.add(expr); + } + +} diff --git a/h2/src/main/org/h2/command/dml/DataChangeStatement.java b/h2/src/main/org/h2/command/dml/DataChangeStatement.java new file mode 100644 index 0000000..f3544cc --- /dev/null +++ b/h2/src/main/org/h2/command/dml/DataChangeStatement.java @@ -0,0 +1,88 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import org.h2.command.Prepared; +import org.h2.engine.SessionLocal; +import org.h2.result.ResultInterface; +import org.h2.result.ResultTarget; +import org.h2.table.DataChangeDeltaTable.ResultOption; +import org.h2.table.Table; + +/** + * Data change statement. + */ +public abstract class DataChangeStatement extends Prepared { + + private boolean isPrepared; + + /** + * Creates new instance of DataChangeStatement. + * + * @param session + * the session + */ + 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. + * + * @return the short name of this statement. + */ + public abstract String getStatementName(); + + /** + * Return the target table. + * + * @return the target table + */ + public abstract Table getTable(); + + @Override + public final boolean isTransactional() { + return true; + } + + @Override + public final ResultInterface queryMeta() { + return null; + } + + @Override + public boolean isCacheable() { + return true; + } + + @Override + public final long update() { + return update(null, null); + } + + /** + * Execute the statement with specified delta change collector and collection mode. + * + * @param deltaChangeCollector + * target result + * @param deltaChangeCollectionMode + * collection mode + * @return the update count + */ + public abstract long update(ResultTarget deltaChangeCollector, ResultOption deltaChangeCollectionMode); + +} diff --git a/h2/src/main/org/h2/command/dml/Delete.java b/h2/src/main/org/h2/command/dml/Delete.java new file mode 100644 index 0000000..ac229a3 --- /dev/null +++ b/h2/src/main/org/h2/command/dml/Delete.java @@ -0,0 +1,141 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import java.util.HashSet; + +import org.h2.api.Trigger; +import org.h2.command.CommandInterface; +import org.h2.command.query.AllColumnsForPlan; +import org.h2.engine.DbObject; +import org.h2.engine.Right; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.message.DbException; +import org.h2.result.LocalResult; +import org.h2.result.ResultTarget; +import org.h2.result.Row; +import org.h2.table.DataChangeDeltaTable.ResultOption; +import org.h2.table.PlanItem; +import org.h2.table.Table; +import org.h2.table.TableFilter; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * This class represents the statement + * DELETE + */ +public final class Delete extends FilteredDataChangeStatement { + + public Delete(SessionLocal session) { + super(session); + } + + + @Override + public long update(ResultTarget deltaChangeCollector, ResultOption deltaChangeCollectionMode) { + targetTableFilter.startQuery(session); + targetTableFilter.reset(); + Table table = targetTableFilter.getTable(); + session.getUser().checkTableRight(table, Right.DELETE); + table.fire(session, Trigger.DELETE, true); + table.lock(session, Table.WRITE_LOCK); + long limitRows = -1; + if (fetchExpr != null) { + Value v = fetchExpr.getValue(session); + if (v == ValueNull.INSTANCE || (limitRows = v.getLong()) < 0) { + throw DbException.getInvalidValueException("FETCH", v); + } + } + try (LocalResult rows = LocalResult.forTable(session, table)) { + setCurrentRowNumber(0); + long count = 0; + while (nextRow(limitRows, count)) { + Row row = targetTableFilter.get(); + if (table.isRowLockable()) { + Row lockedRow = table.lockRow(session, row); + if (lockedRow == null) { + continue; + } + if (!row.hasSharedData(lockedRow)) { + row = lockedRow; + targetTableFilter.set(row); + if (condition != null && !condition.getBooleanValue(session)) { + continue; + } + } + } + if (deltaChangeCollectionMode == ResultOption.OLD) { + deltaChangeCollector.addRow(row.getValueList()); + } + if (!table.fireRow() || !table.fireBeforeRow(session, row, null)) { + rows.addRowForTable(row); + } + count++; + } + rows.done(); + long rowScanCount = 0; + while (rows.next()) { + if ((++rowScanCount & 127) == 0) { + checkCanceled(); + } + Row row = rows.currentRowForTable(); + table.removeRow(session, row); + } + if (table.fireRow()) { + for (rows.reset(); rows.next();) { + table.fireAfterRow(session, rows.currentRowForTable(), null, false); + } + } + table.fire(session, Trigger.DELETE, false); + return count; + } + } + + @Override + public String getPlanSQL(int sqlFlags) { + StringBuilder builder = new StringBuilder("DELETE FROM "); + targetTableFilter.getPlanSQL(builder, false, sqlFlags); + appendFilterCondition(builder, sqlFlags); + return builder.toString(); + } + + @Override + void doPrepare() { + if (condition != null) { + condition.mapColumns(targetTableFilter, 0, Expression.MAP_INITIAL); + condition = condition.optimizeCondition(session); + if (condition != null) { + condition.createIndexConditions(session, targetTableFilter); + } + } + TableFilter[] filters = new TableFilter[] { targetTableFilter }; + PlanItem item = targetTableFilter.getBestPlanItem(session, filters, 0, new AllColumnsForPlan(filters)); + targetTableFilter.setPlanItem(item); + targetTableFilter.prepare(); + } + + @Override + public int getType() { + return CommandInterface.DELETE; + } + + @Override + public String getStatementName() { + return "DELETE"; + } + + @Override + public void collectDependencies(HashSet dependencies) { + ExpressionVisitor visitor = ExpressionVisitor.getDependenciesVisitor(dependencies); + if (condition != null) { + condition.isEverything(visitor); + } + } + +} diff --git a/h2/src/main/org/h2/command/dml/ExecuteImmediate.java b/h2/src/main/org/h2/command/dml/ExecuteImmediate.java new file mode 100644 index 0000000..b9e5cfe --- /dev/null +++ b/h2/src/main/org/h2/command/dml/ExecuteImmediate.java @@ -0,0 +1,57 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.command.Prepared; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.message.DbException; +import org.h2.result.ResultInterface; + +/** + * This class represents the statement + * EXECUTE IMMEDIATE. + */ +public class ExecuteImmediate extends Prepared { + + private Expression statement; + + public ExecuteImmediate(SessionLocal session, Expression statement) { + super(session); + this.statement = statement.optimize(session); + } + + @Override + public long update() { + String sql = statement.getValue(session).getString(); + if (sql == null) { + throw DbException.getInvalidValueException("SQL command", null); + } + Prepared command = session.prepare(sql); + if (command.isQuery()) { + throw DbException.get(ErrorCode.SYNTAX_ERROR_2, sql, ""); + } + return command.update(); + } + + @Override + public boolean isTransactional() { + return true; + } + + @Override + public int getType() { + return CommandInterface.EXECUTE_IMMEDIATELY; + } + + @Override + public ResultInterface queryMeta() { + return null; + } + +} diff --git a/h2/src/main/org/h2/command/dml/ExecuteProcedure.java b/h2/src/main/org/h2/command/dml/ExecuteProcedure.java new file mode 100644 index 0000000..0313ea5 --- /dev/null +++ b/h2/src/main/org/h2/command/dml/ExecuteProcedure.java @@ -0,0 +1,93 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import java.util.ArrayList; + +import org.h2.command.CommandInterface; +import org.h2.command.Prepared; +import org.h2.engine.Procedure; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.Parameter; +import org.h2.result.ResultInterface; +import org.h2.util.Utils; + +/** + * This class represents the statement + * EXECUTE + */ +public class ExecuteProcedure extends Prepared { + + private final ArrayList expressions = Utils.newSmallArrayList(); + private Procedure procedure; + + public ExecuteProcedure(SessionLocal session) { + super(session); + } + + public void setProcedure(Procedure procedure) { + this.procedure = procedure; + } + + /** + * Set the expression at the given index. + * + * @param index the index (0 based) + * @param expr the expression + */ + public void setExpression(int index, Expression expr) { + expressions.add(index, expr); + } + + private void setParameters() { + Prepared prepared = procedure.getPrepared(); + ArrayList params = prepared.getParameters(); + for (int i = 0; params != null && i < params.size() && + i < expressions.size(); i++) { + Expression expr = expressions.get(i); + Parameter p = params.get(i); + p.setValue(expr.getValue(session)); + } + } + + @Override + public boolean isQuery() { + Prepared prepared = procedure.getPrepared(); + return prepared.isQuery(); + } + + @Override + public long update() { + setParameters(); + Prepared prepared = procedure.getPrepared(); + return prepared.update(); + } + + @Override + public ResultInterface query(long limit) { + setParameters(); + Prepared prepared = procedure.getPrepared(); + return prepared.query(limit); + } + + @Override + public boolean isTransactional() { + return true; + } + + @Override + public ResultInterface queryMeta() { + Prepared prepared = procedure.getPrepared(); + return prepared.queryMeta(); + } + + @Override + public int getType() { + return CommandInterface.EXECUTE; + } + +} diff --git a/h2/src/main/org/h2/command/dml/Explain.java b/h2/src/main/org/h2/command/dml/Explain.java new file mode 100644 index 0000000..ea677f5 --- /dev/null +++ b/h2/src/main/org/h2/command/dml/Explain.java @@ -0,0 +1,156 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; +import org.h2.command.CommandInterface; +import org.h2.command.Prepared; +import org.h2.engine.Database; +import org.h2.engine.DbObject; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.mvstore.db.Store; +import org.h2.result.LocalResult; +import org.h2.result.ResultInterface; +import org.h2.table.Column; +import org.h2.util.HasSQL; +import org.h2.value.TypeInfo; +import org.h2.value.ValueVarchar; + +/** + * This class represents the statement + * EXPLAIN + */ +public class Explain extends Prepared { + + private Prepared command; + private LocalResult result; + private boolean executeCommand; + + public Explain(SessionLocal session) { + super(session); + } + + public void setCommand(Prepared command) { + this.command = command; + } + + public Prepared getCommand() { + return command; + } + + @Override + public void prepare() { + command.prepare(); + } + + public void setExecuteCommand(boolean executeCommand) { + this.executeCommand = executeCommand; + } + + @Override + public ResultInterface queryMeta() { + return query(-1); + } + + @Override + protected void checkParameters() { + // Check params only in case of EXPLAIN ANALYZE + if (executeCommand) { + super.checkParameters(); + } + } + + @Override + public ResultInterface query(long maxrows) { + Database db = session.getDatabase(); + Expression[] expressions = { new ExpressionColumn(db, new Column("PLAN", TypeInfo.TYPE_VARCHAR)) }; + result = new LocalResult(session, expressions, 1, 1); + int sqlFlags = HasSQL.ADD_PLAN_INFORMATION; + if (maxrows >= 0) { + String plan; + if (executeCommand) { + Store store = null; + if (db.isPersistent()) { + store = db.getStore(); + store.statisticsStart(); + } + if (command.isQuery()) { + command.query(maxrows); + } else { + command.update(); + } + plan = command.getPlanSQL(sqlFlags); + Map statistics = null; + if (store != null) { + statistics = store.statisticsEnd(); + } + if (statistics != null) { + int total = 0; + for (Entry e : statistics.entrySet()) { + total += e.getValue(); + } + if (total > 0) { + statistics = new TreeMap<>(statistics); + StringBuilder buff = new StringBuilder(); + if (statistics.size() > 1) { + buff.append("total: ").append(total).append('\n'); + } + for (Entry e : statistics.entrySet()) { + int value = e.getValue(); + int percent = (int) (100L * value / total); + buff.append(e.getKey()).append(": ").append(value); + if (statistics.size() > 1) { + buff.append(" (").append(percent).append("%)"); + } + buff.append('\n'); + } + plan += "\n/*\n" + buff.toString() + "*/"; + } + } + } else { + plan = command.getPlanSQL(sqlFlags); + } + add(plan); + } + result.done(); + return result; + } + + private void add(String text) { + result.addRow(ValueVarchar.get(text)); + } + + @Override + public boolean isQuery() { + return true; + } + + @Override + public boolean isTransactional() { + return true; + } + + @Override + public boolean isReadOnly() { + return command.isReadOnly(); + } + + @Override + public int getType() { + return executeCommand ? CommandInterface.EXPLAIN_ANALYZE : CommandInterface.EXPLAIN; + } + + @Override + public void collectDependencies(HashSet dependencies) { + command.collectDependencies(dependencies); + } + +} diff --git a/h2/src/main/org/h2/command/dml/FilteredDataChangeStatement.java b/h2/src/main/org/h2/command/dml/FilteredDataChangeStatement.java new file mode 100644 index 0000000..81995ce --- /dev/null +++ b/h2/src/main/org/h2/command/dml/FilteredDataChangeStatement.java @@ -0,0 +1,97 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.table.Table; +import org.h2.table.TableFilter; + +/** + * Data change statement with WHERE criteria and possibly limited number of + * rows. + */ +abstract class FilteredDataChangeStatement extends DataChangeStatement { + + /** + * The WHERE criteria. + */ + Expression condition; + + /** + * The target table filter. + */ + TableFilter targetTableFilter; + + /** + * The expression with optional maximum number of rows. + */ + Expression fetchExpr; + + /** + * Creates new instance of FilteredDataChangeStatement. + * + * @param session + * the session + */ + FilteredDataChangeStatement(SessionLocal session) { + super(session); + } + + @Override + public final Table getTable() { + return targetTableFilter.getTable(); + } + + public final void setTableFilter(TableFilter tableFilter) { + this.targetTableFilter = tableFilter; + } + + public final TableFilter getTableFilter() { + return targetTableFilter; + } + + public final void setCondition(Expression condition) { + this.condition = condition; + } + + public final Expression getCondition() { + return this.condition; + } + + public void setFetch(Expression fetch) { + this.fetchExpr = fetch; + } + + final boolean nextRow(long limitRows, long count) { + if (limitRows < 0 || count < limitRows) { + while (targetTableFilter.next()) { + setCurrentRowNumber(count + 1); + if (condition == null || condition.getBooleanValue(session)) { + return true; + } + } + } + return false; + } + + final void appendFilterCondition(StringBuilder builder, int sqlFlags) { + if (condition != null) { + builder.append("\nWHERE "); + condition.getUnenclosedSQL(builder, sqlFlags); + } + if (fetchExpr != null) { + builder.append("\nFETCH FIRST "); + String count = fetchExpr.getSQL(sqlFlags, Expression.WITHOUT_PARENTHESES); + if ("1".equals(count)) { + builder.append("ROW ONLY"); + } else { + builder.append(count).append(" ROWS ONLY"); + } + } + } + +} diff --git a/h2/src/main/org/h2/command/dml/Help.java b/h2/src/main/org/h2/command/dml/Help.java new file mode 100644 index 0000000..338e2f1 --- /dev/null +++ b/h2/src/main/org/h2/command/dml/Help.java @@ -0,0 +1,163 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import java.io.ByteArrayInputStream; +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; +import org.h2.command.Prepared; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.message.DbException; +import org.h2.result.LocalResult; +import org.h2.result.ResultInterface; +import org.h2.table.Column; +import org.h2.tools.Csv; +import org.h2.util.Utils; +import org.h2.value.TypeInfo; +import org.h2.value.ValueVarchar; + +/** + * This class represents the statement CALL. + */ +public class Help extends Prepared { + + private final String[] conditions; + + private final Expression[] expressions; + + public Help(SessionLocal session, String[] conditions) { + super(session); + this.conditions = conditions; + Database db = session.getDatabase(); + expressions = new Expression[] { // + new ExpressionColumn(db, new Column("SECTION", TypeInfo.TYPE_VARCHAR)), // + new ExpressionColumn(db, new Column("TOPIC", TypeInfo.TYPE_VARCHAR)), // + new ExpressionColumn(db, new Column("SYNTAX", TypeInfo.TYPE_VARCHAR)), // + new ExpressionColumn(db, new Column("TEXT", TypeInfo.TYPE_VARCHAR)), // + }; + } + + @Override + public ResultInterface queryMeta() { + LocalResult result = new LocalResult(session, expressions, 4, 4); + result.done(); + return result; + } + + @Override + public ResultInterface query(long maxrows) { + LocalResult result = new LocalResult(session, expressions, 4, 4); + try { + ResultSet rs = getTable(); + loop: while (rs.next()) { + String topic = rs.getString(2).trim(); + for (String condition : conditions) { + if (!topic.contains(condition)) { + continue loop; + } + } + result.addRow( + // SECTION + ValueVarchar.get(rs.getString(1).trim(), session), + // TOPIC + ValueVarchar.get(topic, session), + // SYNTAX + ValueVarchar.get(stripAnnotationsFromSyntax(rs.getString(3)), session), + // TEXT + ValueVarchar.get(processHelpText(rs.getString(4)), session)); + } + } catch (Exception e) { + throw DbException.convert(e); + } + result.done(); + return result; + } + + /** + * Strip out the special annotations we use to help build the railroad/BNF diagrams + * @param s to process + * @return cleaned text + */ + public static String stripAnnotationsFromSyntax(String s) { + // SYNTAX column - Strip out the special annotations we use to + // help build the railroad/BNF diagrams. + return s.replaceAll("@c@ ", "").replaceAll("@h2@ ", "") + .replaceAll("@c@", "").replaceAll("@h2@", "").trim(); + } + + /** + * Sanitize value read from csv file (i.e. help.csv) + * @param s text to process + * @return text without wrapping quotes and trimmed + */ + public static String processHelpText(String s) { + int len = s.length(); + int end = 0; + for (; end < len; end++) { + char ch = s.charAt(end); + if (ch == '.') { + end++; + break; + } + if (ch == '"') { + do { + end++; + } while (end < len && s.charAt(end) != '"'); + } + } + s = s.substring(0, end); + return s.trim(); + } + + /** + * Returns HELP table. + * + * @return HELP table with columns SECTION,TOPIC,SYNTAX,TEXT + * @throws IOException + * on I/O exception + */ + public static ResultSet getTable() throws IOException { + 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); + } + + @Override + public boolean isQuery() { + return true; + } + + @Override + public boolean isTransactional() { + return true; + } + + @Override + public boolean isReadOnly() { + return true; + } + + @Override + public int getType() { + return CommandInterface.CALL; + } + + @Override + public boolean isCacheable() { + return true; + } + +} diff --git a/h2/src/main/org/h2/command/dml/Insert.java b/h2/src/main/org/h2/command/dml/Insert.java new file mode 100644 index 0000000..724a19a --- /dev/null +++ b/h2/src/main/org/h2/command/dml/Insert.java @@ -0,0 +1,456 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map.Entry; + +import org.h2.api.ErrorCode; +import org.h2.api.Trigger; +import org.h2.command.Command; +import org.h2.command.CommandInterface; +import org.h2.command.query.Query; +import org.h2.engine.DbObject; +import org.h2.engine.Right; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.Parameter; +import org.h2.expression.ValueExpression; +import org.h2.expression.condition.Comparison; +import org.h2.expression.condition.ConditionAndOr; +import org.h2.index.Index; +import org.h2.message.DbException; +import org.h2.mvstore.db.MVPrimaryIndex; +import org.h2.result.ResultInterface; +import org.h2.result.ResultTarget; +import org.h2.result.Row; +import org.h2.table.Column; +import org.h2.table.DataChangeDeltaTable; +import org.h2.table.DataChangeDeltaTable.ResultOption; +import org.h2.table.Table; +import org.h2.util.HasSQL; +import org.h2.value.Value; + +/** + * This class represents the statement + * INSERT + */ +public final class Insert extends CommandWithValues implements ResultTarget { + + private Table table; + private Column[] columns; + private Query query; + private long rowNumber; + private boolean insertFromSelect; + + private Boolean overridingSystem; + + /** + * For MySQL-style INSERT ... ON DUPLICATE KEY UPDATE .... + */ + private HashMap duplicateKeyAssignmentMap; + + private Value[] onDuplicateKeyRow; + + /** + * For MySQL-style INSERT IGNORE and PostgreSQL-style ON CONFLICT DO + * NOTHING. + */ + private boolean ignore; + + private ResultTarget deltaChangeCollector; + + private ResultOption deltaChangeCollectionMode; + + public Insert(SessionLocal session) { + super(session); + } + + @Override + public void setCommand(Command command) { + super.setCommand(command); + if (query != null) { + query.setCommand(command); + } + } + + @Override + public Table getTable() { + return table; + } + + public void setTable(Table table) { + this.table = table; + } + + public void setColumns(Column[] columns) { + this.columns = columns; + } + + /** + * Sets MySQL-style INSERT IGNORE mode or PostgreSQL-style ON CONFLICT + * DO NOTHING. + * + * @param ignore ignore duplicates + */ + public void setIgnore(boolean ignore) { + this.ignore = ignore; + } + + public void setQuery(Query query) { + this.query = query; + } + + public void setOverridingSystem(Boolean overridingSystem) { + this.overridingSystem = overridingSystem; + } + + /** + * Keep a collection of the columns to pass to update if a duplicate key + * happens, for MySQL-style INSERT ... ON DUPLICATE KEY UPDATE .... + * + * @param column the column + * @param expression the expression + */ + public void addAssignmentForDuplicate(Column column, Expression expression) { + if (duplicateKeyAssignmentMap == null) { + duplicateKeyAssignmentMap = new HashMap<>(); + } + if (duplicateKeyAssignmentMap.putIfAbsent(column, expression) != null) { + throw DbException.get(ErrorCode.DUPLICATE_COLUMN_NAME_1, column.getName()); + } + } + + @Override + public long update(ResultTarget deltaChangeCollector, ResultOption deltaChangeCollectionMode) { + this.deltaChangeCollector = deltaChangeCollector; + this.deltaChangeCollectionMode = deltaChangeCollectionMode; + try { + return insertRows(); + } finally { + this.deltaChangeCollector = null; + this.deltaChangeCollectionMode = null; + } + } + + private long insertRows() { + session.getUser().checkTableRight(table, Right.INSERT); + setCurrentRowNumber(0); + table.fire(session, Trigger.INSERT, true); + rowNumber = 0; + int listSize = valuesExpressionList.size(); + if (listSize > 0) { + int columnLen = columns.length; + for (int x = 0; x < listSize; x++) { + Row newRow = table.getTemplateRow(); + Expression[] expr = valuesExpressionList.get(x); + setCurrentRowNumber(x + 1); + for (int i = 0; i < columnLen; i++) { + Column c = columns[i]; + int index = c.getColumnId(); + Expression e = expr[i]; + if (e != ValueExpression.DEFAULT) { + try { + newRow.setValue(index, e.getValue(session)); + } catch (DbException ex) { + throw setRow(ex, x, getSimpleSQL(expr)); + } + } + } + rowNumber++; + table.convertInsertRow(session, newRow, overridingSystem); + if (deltaChangeCollectionMode == ResultOption.NEW) { + deltaChangeCollector.addRow(newRow.getValueList().clone()); + } + if (!table.fireBeforeRow(session, null, newRow)) { + table.lock(session, Table.WRITE_LOCK); + try { + table.addRow(session, newRow); + } catch (DbException de) { + if (handleOnDuplicate(de, null)) { + // MySQL returns 2 for updated row + // TODO: detect no-op change + rowNumber++; + } else { + // INSERT IGNORE case + rowNumber--; + } + continue; + } + DataChangeDeltaTable.collectInsertedFinalRow(session, table, deltaChangeCollector, + deltaChangeCollectionMode, newRow); + table.fireAfterRow(session, null, newRow, false); + } else { + DataChangeDeltaTable.collectInsertedFinalRow(session, table, deltaChangeCollector, + deltaChangeCollectionMode, newRow); + } + } + } else { + table.lock(session, Table.WRITE_LOCK); + if (insertFromSelect) { + query.query(0, this); + } else { + ResultInterface rows = query.query(0); + while (rows.next()) { + Value[] r = rows.currentRow(); + try { + addRow(r); + } catch (DbException de) { + if (handleOnDuplicate(de, r)) { + // MySQL returns 2 for updated row + // TODO: detect no-op change + rowNumber++; + } else { + // INSERT IGNORE case + rowNumber--; + } + } + } + rows.close(); + } + } + table.fire(session, Trigger.INSERT, false); + return rowNumber; + } + + @Override + public void addRow(Value... values) { + Row newRow = table.getTemplateRow(); + setCurrentRowNumber(++rowNumber); + for (int j = 0, len = columns.length; j < len; j++) { + newRow.setValue(columns[j].getColumnId(), values[j]); + } + table.convertInsertRow(session, newRow, overridingSystem); + if (deltaChangeCollectionMode == ResultOption.NEW) { + deltaChangeCollector.addRow(newRow.getValueList().clone()); + } + if (!table.fireBeforeRow(session, null, newRow)) { + table.addRow(session, newRow); + DataChangeDeltaTable.collectInsertedFinalRow(session, table, deltaChangeCollector, + deltaChangeCollectionMode, newRow); + table.fireAfterRow(session, null, newRow, false); + } else { + DataChangeDeltaTable.collectInsertedFinalRow(session, table, deltaChangeCollector, + deltaChangeCollectionMode, newRow); + } + } + + @Override + public long getRowCount() { + // This method is not used in this class + return rowNumber; + } + + @Override + public void limitsWereApplied() { + // Nothing to do + } + + @Override + public String getPlanSQL(int sqlFlags) { + StringBuilder builder = new StringBuilder("INSERT INTO "); + table.getSQL(builder, sqlFlags).append('('); + Column.writeColumns(builder, columns, sqlFlags); + builder.append(")\n"); + if (insertFromSelect) { + builder.append("DIRECT "); + } + if (!valuesExpressionList.isEmpty()) { + builder.append("VALUES "); + int row = 0; + if (valuesExpressionList.size() > 1) { + builder.append('\n'); + } + for (Expression[] expr : valuesExpressionList) { + if (row++ > 0) { + builder.append(",\n"); + } + Expression.writeExpressions(builder.append('('), expr, sqlFlags).append(')'); + } + } else { + builder.append(query.getPlanSQL(sqlFlags)); + } + return builder.toString(); + } + + @Override + void doPrepare() { + if (columns == null) { + if (!valuesExpressionList.isEmpty() && valuesExpressionList.get(0).length == 0) { + // special case where table is used as a sequence + columns = new Column[0]; + } else { + columns = table.getColumns(); + } + } + if (!valuesExpressionList.isEmpty()) { + for (Expression[] expr : valuesExpressionList) { + if (expr.length != columns.length) { + throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); + } + for (int i = 0, len = expr.length; i < len; i++) { + Expression e = expr[i]; + if (e != null) { + e = e.optimize(session); + if (e instanceof Parameter) { + Parameter p = (Parameter) e; + p.setColumn(columns[i]); + } + expr[i] = e; + } + } + } + } else { + query.prepare(); + if (query.getColumnCount() != columns.length) { + throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); + } + } + } + + @Override + public int getType() { + return CommandInterface.INSERT; + } + + @Override + public String getStatementName() { + return "INSERT"; + } + + public void setInsertFromSelect(boolean value) { + this.insertFromSelect = value; + } + + @Override + public boolean isCacheable() { + return duplicateKeyAssignmentMap == null; + } + + /** + * @param de duplicate key exception + * @param currentRow current row values (optional) + * @return {@code true} if row was updated, {@code false} if row was ignored + */ + private boolean handleOnDuplicate(DbException de, Value[] currentRow) { + if (de.getErrorCode() != ErrorCode.DUPLICATE_KEY_1) { + throw de; + } + if (duplicateKeyAssignmentMap == null) { + if (ignore) { + return false; + } + throw de; + } + + int columnCount = columns.length; + Expression[] row = (currentRow == null) ? valuesExpressionList.get((int) getCurrentRowNumber() - 1) + : new Expression[columnCount]; + onDuplicateKeyRow = new Value[table.getColumns().length]; + for (int i = 0; i < columnCount; i++) { + Value value; + if (currentRow != null) { + value = currentRow[i]; + row[i] = ValueExpression.get(value); + } else { + value = row[i].getValue(session); + } + onDuplicateKeyRow[columns[i].getColumnId()] = value; + } + + StringBuilder builder = new StringBuilder("UPDATE "); + table.getSQL(builder, HasSQL.DEFAULT_SQL_FLAGS).append(" SET "); + boolean f = false; + for (Entry entry : duplicateKeyAssignmentMap.entrySet()) { + if (f) { + builder.append(", "); + } + f = true; + entry.getKey().getSQL(builder, HasSQL.DEFAULT_SQL_FLAGS).append('='); + entry.getValue().getUnenclosedSQL(builder, HasSQL.DEFAULT_SQL_FLAGS); + } + builder.append(" WHERE "); + Index foundIndex = (Index) de.getSource(); + if (foundIndex == null) { + throw DbException.getUnsupportedException( + "Unable to apply ON DUPLICATE KEY UPDATE, no index found!"); + } + prepareUpdateCondition(foundIndex, row).getUnenclosedSQL(builder, HasSQL.DEFAULT_SQL_FLAGS); + String sql = builder.toString(); + Update command = (Update) session.prepare(sql); + command.setOnDuplicateKeyInsert(this); + for (Parameter param : command.getParameters()) { + Parameter insertParam = parameters.get(param.getIndex()); + param.setValue(insertParam.getValue(session)); + } + boolean result = command.update() > 0; + onDuplicateKeyRow = null; + return result; + } + + private Expression prepareUpdateCondition(Index foundIndex, Expression[] row) { + // MVPrimaryIndex is playing fast and loose with it's implementation of + // the Index interface. + // It returns all of the columns in the table when we call + // getIndexColumns() or getColumns(). + // Don't have time right now to fix that, so just special-case it. + // PageDataIndex has the same problem. + final Column[] indexedColumns; + if (foundIndex instanceof MVPrimaryIndex) { + MVPrimaryIndex foundMV = (MVPrimaryIndex) foundIndex; + indexedColumns = new Column[] { foundMV.getIndexColumns()[foundMV + .getMainIndexColumn()].column }; + } else { + indexedColumns = foundIndex.getColumns(); + } + + Expression condition = null; + for (Column column : indexedColumns) { + ExpressionColumn expr = new ExpressionColumn(session.getDatabase(), + table.getSchema().getName(), table.getName(), column.getName()); + for (int i = 0; i < columns.length; i++) { + if (expr.getColumnName(session, i).equals(columns[i].getName())) { + if (condition == null) { + condition = new Comparison(Comparison.EQUAL, expr, row[i], false); + } else { + condition = new ConditionAndOr(ConditionAndOr.AND, condition, + new Comparison(Comparison.EQUAL, expr, row[i], false)); + } + break; + } + } + } + return condition; + } + + /** + * Get the value to use for the specified column in case of a duplicate key. + * + * @param columnIndex the column index + * @return the value + */ + public Value getOnDuplicateKeyValue(int columnIndex) { + return onDuplicateKeyRow[columnIndex]; + } + + @Override + public void collectDependencies(HashSet dependencies) { + ExpressionVisitor visitor = ExpressionVisitor.getDependenciesVisitor(dependencies); + if (!valuesExpressionList.isEmpty()) { + for (Expression[] expr : valuesExpressionList) { + for (Expression e : expr) { + e.isEverything(visitor); + } + } + } else { + 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 new file mode 100644 index 0000000..910cb8a --- /dev/null +++ b/h2/src/main/org/h2/command/dml/Merge.java @@ -0,0 +1,349 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import java.util.ArrayList; +import java.util.HashSet; +import org.h2.api.ErrorCode; +import org.h2.api.Trigger; +import org.h2.command.Command; +import org.h2.command.CommandInterface; +import org.h2.command.query.Query; +import org.h2.engine.DbObject; +import org.h2.engine.Right; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.Parameter; +import org.h2.expression.ValueExpression; +import org.h2.index.Index; +import org.h2.message.DbException; +import org.h2.mvstore.db.MVPrimaryIndex; +import org.h2.result.ResultInterface; +import org.h2.result.ResultTarget; +import org.h2.result.Row; +import org.h2.table.Column; +import org.h2.table.DataChangeDeltaTable; +import org.h2.table.DataChangeDeltaTable.ResultOption; +import org.h2.table.Table; +import org.h2.util.HasSQL; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * This class represents the statement + * MERGE + * or the MySQL compatibility statement + * REPLACE + */ +public final class Merge extends CommandWithValues { + + private boolean isReplace; + + private Table table; + private Column[] columns; + private Column[] keys; + private Query query; + private Update update; + + public Merge(SessionLocal session, boolean isReplace) { + super(session); + this.isReplace = isReplace; + } + + @Override + public void setCommand(Command command) { + super.setCommand(command); + if (query != null) { + query.setCommand(command); + } + } + + @Override + public Table getTable() { + return table; + } + + public void setTable(Table table) { + this.table = table; + } + + public void setColumns(Column[] columns) { + this.columns = columns; + } + + public void setKeys(Column[] keys) { + this.keys = keys; + } + + public void setQuery(Query query) { + this.query = query; + } + + @Override + public long update(ResultTarget deltaChangeCollector, ResultOption deltaChangeCollectionMode) { + long count = 0; + session.getUser().checkTableRight(table, Right.INSERT); + session.getUser().checkTableRight(table, Right.UPDATE); + setCurrentRowNumber(0); + if (!valuesExpressionList.isEmpty()) { + // process values in list + for (int x = 0, size = valuesExpressionList.size(); x < size; x++) { + setCurrentRowNumber(x + 1); + Expression[] expr = valuesExpressionList.get(x); + Row newRow = table.getTemplateRow(); + for (int i = 0, len = columns.length; i < len; i++) { + Column c = columns[i]; + int index = c.getColumnId(); + Expression e = expr[i]; + if (e != ValueExpression.DEFAULT) { + try { + newRow.setValue(index, e.getValue(session)); + } catch (DbException ex) { + throw setRow(ex, count, getSimpleSQL(expr)); + } + } + } + count += merge(newRow, expr, deltaChangeCollector, deltaChangeCollectionMode); + } + } else { + // process select data for list + query.setNeverLazy(true); + ResultInterface rows = query.query(0); + table.fire(session, Trigger.UPDATE | Trigger.INSERT, true); + table.lock(session, Table.WRITE_LOCK); + while (rows.next()) { + Value[] r = rows.currentRow(); + Row newRow = table.getTemplateRow(); + setCurrentRowNumber(count); + for (int j = 0; j < columns.length; j++) { + newRow.setValue(columns[j].getColumnId(), r[j]); + } + count += merge(newRow, null, deltaChangeCollector, deltaChangeCollectionMode); + } + rows.close(); + table.fire(session, Trigger.UPDATE | Trigger.INSERT, false); + } + return count; + } + + /** + * Updates an existing row or inserts a new one. + * + * @param row row to replace + * @param expressions source expressions, or null + * @param deltaChangeCollector target result + * @param deltaChangeCollectionMode collection mode + * @return 1 if row was inserted, 1 if row was updated by a MERGE statement, + * and 2 if row was updated by a REPLACE statement + */ + private int merge(Row row, Expression[] expressions, ResultTarget deltaChangeCollector, + ResultOption deltaChangeCollectionMode) { + long count; + if (update == null) { + // if there is no valid primary key, + // the REPLACE statement degenerates to an INSERT + count = 0; + } else { + ArrayList k = update.getParameters(); + int j = 0; + for (int i = 0, l = columns.length; i < l; i++) { + Column col = columns[i]; + if (col.isGeneratedAlways()) { + if (expressions == null || expressions[i] != ValueExpression.DEFAULT) { + throw DbException.get(ErrorCode.GENERATED_COLUMN_CANNOT_BE_ASSIGNED_1, + col.getSQLWithTable(new StringBuilder(), HasSQL.TRACE_SQL_FLAGS).toString()); + } + } else { + Value v = row.getValue(col.getColumnId()); + if (v == null) { + Expression defaultExpression = col.getEffectiveDefaultExpression(); + v = defaultExpression != null ? defaultExpression.getValue(session) : ValueNull.INSTANCE; + } + k.get(j++).setValue(v); + } + } + for (Column col : keys) { + Value v = row.getValue(col.getColumnId()); + if (v == null) { + throw DbException.get(ErrorCode.COLUMN_CONTAINS_NULL_VALUES_1, col.getTraceSQL()); + } + k.get(j++).setValue(v); + } + count = update.update(deltaChangeCollector, deltaChangeCollectionMode); + } + // if update fails try an insert + if (count == 0) { + try { + table.convertInsertRow(session, row, null); + if (deltaChangeCollectionMode == ResultOption.NEW) { + deltaChangeCollector.addRow(row.getValueList().clone()); + } + if (!table.fireBeforeRow(session, null, row)) { + table.lock(session, Table.WRITE_LOCK); + table.addRow(session, row); + DataChangeDeltaTable.collectInsertedFinalRow(session, table, deltaChangeCollector, + deltaChangeCollectionMode, row); + table.fireAfterRow(session, null, row, false); + } else { + DataChangeDeltaTable.collectInsertedFinalRow(session, table, deltaChangeCollector, + deltaChangeCollectionMode, row); + } + return 1; + } catch (DbException e) { + if (e.getErrorCode() == ErrorCode.DUPLICATE_KEY_1) { + // possibly a concurrent merge or insert + Index index = (Index) e.getSource(); + if (index != null) { + // verify the index columns match the key + Column[] indexColumns; + if (index instanceof MVPrimaryIndex) { + MVPrimaryIndex foundMV = (MVPrimaryIndex) index; + indexColumns = new Column[] { + foundMV.getIndexColumns()[foundMV.getMainIndexColumn()].column }; + } else { + indexColumns = index.getColumns(); + } + boolean indexMatchesKeys; + if (indexColumns.length <= keys.length) { + indexMatchesKeys = true; + for (int i = 0; i < indexColumns.length; i++) { + if (indexColumns[i] != keys[i]) { + indexMatchesKeys = false; + break; + } + } + } else { + indexMatchesKeys = false; + } + if (indexMatchesKeys) { + throw DbException.get(ErrorCode.CONCURRENT_UPDATE_1, table.getName()); + } + } + } + throw e; + } + } else if (count == 1) { + return isReplace ? 2 : 1; + } + throw DbException.get(ErrorCode.DUPLICATE_KEY_1, table.getTraceSQL()); + } + + @Override + public String getPlanSQL(int sqlFlags) { + StringBuilder builder = new StringBuilder(isReplace ? "REPLACE INTO " : "MERGE INTO "); + table.getSQL(builder, sqlFlags).append('('); + Column.writeColumns(builder, columns, sqlFlags); + builder.append(')'); + if (!isReplace && keys != null) { + builder.append(" KEY("); + Column.writeColumns(builder, keys, sqlFlags); + builder.append(')'); + } + builder.append('\n'); + if (!valuesExpressionList.isEmpty()) { + builder.append("VALUES "); + int row = 0; + for (Expression[] expr : valuesExpressionList) { + if (row++ > 0) { + builder.append(", "); + } + Expression.writeExpressions(builder.append('('), expr, sqlFlags).append(')'); + } + } else { + builder.append(query.getPlanSQL(sqlFlags)); + } + return builder.toString(); + } + + @Override + void doPrepare() { + if (columns == null) { + if (!valuesExpressionList.isEmpty() && valuesExpressionList.get(0).length == 0) { + // special case where table is used as a sequence + columns = new Column[0]; + } else { + columns = table.getColumns(); + } + } + if (!valuesExpressionList.isEmpty()) { + for (Expression[] expr : valuesExpressionList) { + if (expr.length != columns.length) { + throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); + } + for (int i = 0; i < expr.length; i++) { + Expression e = expr[i]; + if (e != null) { + expr[i] = e.optimize(session); + } + } + } + } else { + query.prepare(); + if (query.getColumnCount() != columns.length) { + throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); + } + } + if (keys == null) { + Index idx = table.getPrimaryKey(); + if (idx == null) { + throw DbException.get(ErrorCode.CONSTRAINT_NOT_FOUND_1, "PRIMARY KEY"); + } + keys = idx.getColumns(); + } + if (isReplace) { + // if there is no valid primary key, + // the REPLACE statement degenerates to an INSERT + for (Column key : keys) { + boolean found = false; + for (Column column : columns) { + if (column.getColumnId() == key.getColumnId()) { + found = true; + break; + } + } + if (!found) { + return; + } + } + } + StringBuilder builder = table.getSQL(new StringBuilder("UPDATE "), HasSQL.DEFAULT_SQL_FLAGS).append(" SET "); + boolean hasColumn = false; + for (int i = 0, l = columns.length; i < l; i++) { + Column column = columns[i]; + if (!column.isGeneratedAlways()) { + if (hasColumn) { + builder.append(", "); + } + hasColumn = true; + column.getSQL(builder, HasSQL.DEFAULT_SQL_FLAGS).append("=?"); + } + } + if (!hasColumn) { + throw DbException.getSyntaxError(sqlStatement, sqlStatement.length(), + "Valid MERGE INTO statement with at least one updatable column"); + } + Column.writeColumns(builder.append(" WHERE "), keys, " AND ", "=?", HasSQL.DEFAULT_SQL_FLAGS); + update = (Update) session.prepare(builder.toString()); + } + + @Override + public int getType() { + return isReplace ? CommandInterface.REPLACE : CommandInterface.MERGE; + } + + @Override + public String getStatementName() { + return isReplace ? "REPLACE" : "MERGE"; + } + + @Override + public void collectDependencies(HashSet dependencies) { + if (query != null) { + 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 new file mode 100644 index 0000000..033e9dd --- /dev/null +++ b/h2/src/main/org/h2/command/dml/MergeUsing.java @@ -0,0 +1,570 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; + +import org.h2.api.ErrorCode; +import org.h2.api.Trigger; +import org.h2.command.CommandInterface; +import org.h2.command.query.AllColumnsForPlan; +import org.h2.engine.DbObject; +import org.h2.engine.Right; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.Parameter; +import org.h2.expression.ValueExpression; +import org.h2.message.DbException; +import org.h2.result.LocalResult; +import org.h2.result.ResultTarget; +import org.h2.result.Row; +import org.h2.table.Column; +import org.h2.table.DataChangeDeltaTable; +import org.h2.table.DataChangeDeltaTable.ResultOption; +import org.h2.table.PlanItem; +import org.h2.table.Table; +import org.h2.table.TableFilter; +import org.h2.util.HasSQL; +import org.h2.util.Utils; + +/** + * This class represents the statement syntax + * MERGE INTO table alias USING... + * + * It does not replace the MERGE INTO... KEYS... form. + */ +public final class MergeUsing extends DataChangeStatement { + + /** + * Target table filter. + */ + TableFilter targetTableFilter; + + /** + * Source table filter. + */ + TableFilter sourceTableFilter; + + /** + * ON condition expression. + */ + Expression onCondition; + + private ArrayList when = Utils.newSmallArrayList(); + + /** + * Contains _ROWID_ of processed rows. Row + * identities are remembered to prevent duplicate updates of the same row. + */ + private final HashSet targetRowidsRemembered = new HashSet<>(); + + public MergeUsing(SessionLocal session, TableFilter targetTableFilter) { + super(session); + this.targetTableFilter = targetTableFilter; + } + + @Override + public long update(ResultTarget deltaChangeCollector, ResultOption deltaChangeCollectionMode) { + long countUpdatedRows = 0; + targetRowidsRemembered.clear(); + checkRights(); + setCurrentRowNumber(0); + sourceTableFilter.startQuery(session); + sourceTableFilter.reset(); + Table table = targetTableFilter.getTable(); + table.fire(session, evaluateTriggerMasks(), true); + table.lock(session, Table.WRITE_LOCK); + setCurrentRowNumber(0); + long count = 0; + Row previousSource = null, missedSource = null; + boolean hasRowId = table.getRowIdColumn() != null; + while (sourceTableFilter.next()) { + Row source = sourceTableFilter.get(); + if (missedSource != null) { + if (source != missedSource) { + Row backupTarget = targetTableFilter.get(); + sourceTableFilter.set(missedSource); + targetTableFilter.set(table.getNullRow()); + countUpdatedRows += merge(true, deltaChangeCollector, deltaChangeCollectionMode); + sourceTableFilter.set(source); + targetTableFilter.set(backupTarget); + count++; + } + missedSource = null; + } + setCurrentRowNumber(count + 1); + boolean nullRow = targetTableFilter.isNullRow(); + if (!nullRow) { + Row targetRow = targetTableFilter.get(); + if (table.isRowLockable()) { + Row lockedRow = table.lockRow(session, targetRow); + if (lockedRow == null) { + if (previousSource != source) { + missedSource = source; + } + continue; + } + if (!targetRow.hasSharedData(lockedRow)) { + targetRow = lockedRow; + targetTableFilter.set(targetRow); + if (!onCondition.getBooleanValue(session)) { + if (previousSource != source) { + missedSource = source; + } + continue; + } + } + } + if (hasRowId) { + long targetRowId = targetRow.getKey(); + if (!targetRowidsRemembered.add(targetRowId)) { + throw DbException.get(ErrorCode.DUPLICATE_KEY_1, + "Merge using ON column expression, " + + "duplicate _ROWID_ target record already processed:_ROWID_=" + + targetRowId + ":in:" + + targetTableFilter.getTable()); + } + } + } + countUpdatedRows += merge(nullRow, deltaChangeCollector, deltaChangeCollectionMode); + count++; + previousSource = source; + } + if (missedSource != null) { + sourceTableFilter.set(missedSource); + targetTableFilter.set(table.getNullRow()); + countUpdatedRows += merge(true, deltaChangeCollector, deltaChangeCollectionMode); + } + targetRowidsRemembered.clear(); + table.fire(session, evaluateTriggerMasks(), false); + return countUpdatedRows; + } + + private int merge(boolean nullRow, ResultTarget deltaChangeCollector, ResultOption deltaChangeCollectionMode) { + for (When w : when) { + if (w.getClass() == WhenNotMatched.class == nullRow) { + Expression condition = w.andCondition; + if (condition == null || condition.getBooleanValue(session)) { + w.merge(session, deltaChangeCollector, deltaChangeCollectionMode); + return 1; + } + } + } + return 0; + } + + private int evaluateTriggerMasks() { + int masks = 0; + for (When w : when) { + masks |= w.evaluateTriggerMasks(); + } + return masks; + } + + private void checkRights() { + for (When w : when) { + w.checkRights(); + } + session.getUser().checkTableRight(targetTableFilter.getTable(), Right.SELECT); + session.getUser().checkTableRight(sourceTableFilter.getTable(), Right.SELECT); + } + + @Override + public String getPlanSQL(int sqlFlags) { + StringBuilder builder = new StringBuilder("MERGE INTO "); + targetTableFilter.getPlanSQL(builder, false, sqlFlags); + builder.append('\n').append("USING "); + sourceTableFilter.getPlanSQL(builder, false, sqlFlags); + for (When w : when) { + w.getSQL(builder.append('\n'), sqlFlags); + } + return builder.toString(); + } + + @Override + void doPrepare() { + onCondition.addFilterConditions(sourceTableFilter); + onCondition.addFilterConditions(targetTableFilter); + + onCondition.mapColumns(sourceTableFilter, 0, Expression.MAP_INITIAL); + onCondition.mapColumns(targetTableFilter, 0, Expression.MAP_INITIAL); + + onCondition = onCondition.optimize(session); + // Create conditions only for target table + onCondition.createIndexConditions(session, targetTableFilter); + + TableFilter[] filters = new TableFilter[] { sourceTableFilter, targetTableFilter }; + sourceTableFilter.addJoin(targetTableFilter, true, onCondition); + PlanItem item = sourceTableFilter.getBestPlanItem(session, filters, 0, new AllColumnsForPlan(filters)); + sourceTableFilter.setPlanItem(item); + sourceTableFilter.prepare(); + + boolean hasFinalNotMatched = false, hasFinalMatched = false; + for (Iterator i = when.iterator(); i.hasNext();) { + When w = i.next(); + if (!w.prepare(session)) { + i.remove(); + } else if (w.getClass() == WhenNotMatched.class) { + if (hasFinalNotMatched) { + i.remove(); + } else if (w.andCondition == null) { + hasFinalNotMatched = true; + } + } else { + if (hasFinalMatched) { + i.remove(); + } else if (w.andCondition == null) { + hasFinalMatched = true; + } + } + } + } + + public void setSourceTableFilter(TableFilter sourceTableFilter) { + this.sourceTableFilter = sourceTableFilter; + } + + public TableFilter getSourceTableFilter() { + return sourceTableFilter; + } + + public void setOnCondition(Expression condition) { + this.onCondition = condition; + } + + public Expression getOnCondition() { + return onCondition; + } + + public ArrayList getWhen() { + return when; + } + + /** + * Adds WHEN command. + * + * @param w new WHEN command to add (update, delete or insert). + */ + public void addWhen(When w) { + when.add(w); + } + + @Override + public Table getTable() { + return targetTableFilter.getTable(); + } + + public void setTargetTableFilter(TableFilter targetTableFilter) { + this.targetTableFilter = targetTableFilter; + } + + public TableFilter getTargetTableFilter() { + return targetTableFilter; + } + + // Prepared interface implementations + + @Override + public int getType() { + return CommandInterface.MERGE; + } + + @Override + public String getStatementName() { + return "MERGE"; + } + + @Override + public void collectDependencies(HashSet dependencies) { + dependencies.add(targetTableFilter.getTable()); + dependencies.add(sourceTableFilter.getTable()); + ExpressionVisitor visitor = ExpressionVisitor.getDependenciesVisitor(dependencies); + for (When w : when) { + w.collectDependencies(visitor); + } + onCondition.isEverything(visitor); + } + + /** + * Abstract WHEN command of the MERGE statement. + */ + public abstract class When implements HasSQL { + + /** + * AND condition of the command. + */ + Expression andCondition; + + When() { + } + + /** + * Sets the specified AND condition. + * + * @param andCondition AND condition to set + */ + public void setAndCondition(Expression andCondition) { + this.andCondition = andCondition; + } + + /** + * Merges rows. + * + * @param session + * the session + * @param deltaChangeCollector + * target result + * @param deltaChangeCollectionMode + * collection mode + */ + abstract void merge(SessionLocal session, ResultTarget deltaChangeCollector, + ResultOption deltaChangeCollectionMode); + + /** + * Prepares WHEN command. + * + * @param session + * the session + * @return {@code false} if this clause may be removed + */ + boolean prepare(SessionLocal session) { + if (andCondition != null) { + andCondition.mapColumns(targetTableFilter, 0, Expression.MAP_INITIAL); + andCondition.mapColumns(sourceTableFilter, 0, Expression.MAP_INITIAL); + andCondition = andCondition.optimize(session); + if (andCondition.isConstant()) { + if (andCondition.getBooleanValue(session)) { + andCondition = null; + } else { + return false; + } + } + } + return true; + } + + /** + * Evaluates trigger mask (UPDATE, INSERT, DELETE). + * + * @return the trigger mask. + */ + abstract int evaluateTriggerMasks(); + + /** + * Checks user's INSERT, UPDATE, DELETE permission in appropriate cases. + */ + abstract void checkRights(); + + /** + * Find and collect all DbObjects, this When object depends on. + * + * @param visitor the expression visitor + */ + void collectDependencies(ExpressionVisitor visitor) { + if (andCondition != null) { + andCondition.isEverything(visitor); + } + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + builder.append("WHEN "); + if (getClass() == WhenNotMatched.class) { + builder.append("NOT "); + } + builder.append("MATCHED"); + if (andCondition != null) { + andCondition.getUnenclosedSQL(builder.append(" AND "), sqlFlags); + } + return builder.append(" THEN "); + } + + } + + public final class WhenMatchedThenDelete extends When { + + @Override + void merge(SessionLocal session, ResultTarget deltaChangeCollector, ResultOption deltaChangeCollectionMode) { + TableFilter targetTableFilter = MergeUsing.this.targetTableFilter; + Table table = targetTableFilter.getTable(); + Row row = targetTableFilter.get(); + if (deltaChangeCollectionMode == ResultOption.OLD) { + deltaChangeCollector.addRow(row.getValueList()); + } + if (!table.fireRow() || !table.fireBeforeRow(session, row, null)) { + table.removeRow(session, row); + table.fireAfterRow(session, row, null, false); + } + } + + @Override + int evaluateTriggerMasks() { + return Trigger.DELETE; + } + + @Override + void checkRights() { + getSession().getUser().checkTableRight(targetTableFilter.getTable(), Right.DELETE); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return super.getSQL(builder, sqlFlags).append("DELETE"); + } + + } + + public final class WhenMatchedThenUpdate extends When { + + private SetClauseList setClauseList; + + public void setSetClauseList(SetClauseList setClauseList) { + this.setClauseList = setClauseList; + } + + @Override + void merge(SessionLocal session, ResultTarget deltaChangeCollector, ResultOption deltaChangeCollectionMode) { + TableFilter targetTableFilter = MergeUsing.this.targetTableFilter; + Table table = targetTableFilter.getTable(); + try (LocalResult rows = LocalResult.forTable(session, table)) { + setClauseList.prepareUpdate(table, session, deltaChangeCollector, deltaChangeCollectionMode, rows, + targetTableFilter.get(), false); + Update.doUpdate(MergeUsing.this, session, table, rows); + } + } + + @Override + boolean prepare(SessionLocal session) { + boolean result = super.prepare(session); + setClauseList.mapAndOptimize(session, targetTableFilter, sourceTableFilter); + return result; + } + + @Override + int evaluateTriggerMasks() { + return Trigger.UPDATE; + } + + @Override + void checkRights() { + getSession().getUser().checkTableRight(targetTableFilter.getTable(), Right.UPDATE); + } + + @Override + void collectDependencies(ExpressionVisitor visitor) { + super.collectDependencies(visitor); + setClauseList.isEverything(visitor); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return setClauseList.getSQL(super.getSQL(builder, sqlFlags).append("UPDATE"), sqlFlags); + } + + } + + public final class WhenNotMatched extends When { + + private Column[] columns; + + private final Boolean overridingSystem; + + private final Expression[] values; + + public WhenNotMatched(Column[] columns, Boolean overridingSystem, Expression[] values) { + this.columns = columns; + this.overridingSystem = overridingSystem; + this.values = values; + } + + @Override + void merge(SessionLocal session, ResultTarget deltaChangeCollector, ResultOption deltaChangeCollectionMode) { + Table table = targetTableFilter.getTable(); + Row newRow = table.getTemplateRow(); + Expression[] expr = values; + for (int i = 0, len = columns.length; i < len; i++) { + Column c = columns[i]; + int index = c.getColumnId(); + Expression e = expr[i]; + if (e != ValueExpression.DEFAULT) { + try { + newRow.setValue(index, e.getValue(session)); + } catch (DbException ex) { + ex.addSQL("INSERT -- " + getSimpleSQL(expr)); + throw ex; + } + } + } + table.convertInsertRow(session, newRow, overridingSystem); + if (deltaChangeCollectionMode == ResultOption.NEW) { + deltaChangeCollector.addRow(newRow.getValueList().clone()); + } + if (!table.fireBeforeRow(session, null, newRow)) { + table.addRow(session, newRow); + DataChangeDeltaTable.collectInsertedFinalRow(session, table, deltaChangeCollector, + deltaChangeCollectionMode, newRow); + table.fireAfterRow(session, null, newRow, false); + } else { + DataChangeDeltaTable.collectInsertedFinalRow(session, table, deltaChangeCollector, + deltaChangeCollectionMode, newRow); + } + } + + @Override + boolean prepare(SessionLocal session) { + boolean result = super.prepare(session); + TableFilter targetTableFilter = MergeUsing.this.targetTableFilter, + sourceTableFilter = MergeUsing.this.sourceTableFilter; + if (columns == null) { + columns = targetTableFilter.getTable().getColumns(); + } + if (values.length != columns.length) { + throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); + } + for (int i = 0, len = values.length; i < len; i++) { + Expression e = values[i]; + e.mapColumns(targetTableFilter, 0, Expression.MAP_INITIAL); + e.mapColumns(sourceTableFilter, 0, Expression.MAP_INITIAL); + e = e.optimize(session); + if (e instanceof Parameter) { + ((Parameter) e).setColumn(columns[i]); + } + values[i] = e; + } + return result; + } + + @Override + int evaluateTriggerMasks() { + return Trigger.INSERT; + } + + @Override + void checkRights() { + getSession().getUser().checkTableRight(targetTableFilter.getTable(), Right.INSERT); + } + + @Override + void collectDependencies(ExpressionVisitor visitor) { + super.collectDependencies(visitor); + for (Expression e : values) { + e.isEverything(visitor); + } + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + super.getSQL(builder, sqlFlags).append("INSERT ("); + Column.writeColumns(builder, columns, sqlFlags).append(")\nVALUES ("); + return Expression.writeExpressions(builder, values, sqlFlags).append(')'); + } + + } + +} diff --git a/h2/src/main/org/h2/command/dml/NoOperation.java b/h2/src/main/org/h2/command/dml/NoOperation.java new file mode 100644 index 0000000..803c520 --- /dev/null +++ b/h2/src/main/org/h2/command/dml/NoOperation.java @@ -0,0 +1,52 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import org.h2.command.CommandInterface; +import org.h2.command.Prepared; +import org.h2.engine.SessionLocal; +import org.h2.result.ResultInterface; + +/** + * Represents an empty statement or a statement that has no effect. + */ +public class NoOperation extends Prepared { + + public NoOperation(SessionLocal session) { + super(session); + } + + @Override + public long update() { + return 0; + } + + @Override + public boolean isTransactional() { + return true; + } + + @Override + public boolean needRecompile() { + return false; + } + + @Override + public boolean isReadOnly() { + return true; + } + + @Override + public ResultInterface queryMeta() { + return null; + } + + @Override + public int getType() { + return CommandInterface.NO_OPERATION; + } + +} diff --git a/h2/src/main/org/h2/command/dml/RunScriptCommand.java b/h2/src/main/org/h2/command/dml/RunScriptCommand.java new file mode 100644 index 0000000..1040e3d --- /dev/null +++ b/h2/src/main/org/h2/command/dml/RunScriptCommand.java @@ -0,0 +1,157 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import org.h2.command.CommandContainer; +import org.h2.command.CommandInterface; +import org.h2.command.Prepared; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.result.ResultInterface; +import org.h2.util.ScriptReader; + +/** + * This class represents the statement + * RUNSCRIPT + */ +public class RunScriptCommand extends ScriptBase { + + /** + * The byte order mark. + * 0xfeff because this is the Unicode char + * represented by the UTF-8 byte order mark (EF BB BF). + */ + private static final char UTF8_BOM = '\uFEFF'; + + private Charset charset = StandardCharsets.UTF_8; + + private boolean quirksMode; + + private boolean variableBinary; + + private boolean from1X; + + public RunScriptCommand(SessionLocal session) { + super(session); + } + + @Override + public long update() { + session.getUser().checkAdmin(); + int count = 0; + boolean oldQuirksMode = session.isQuirksMode(); + boolean oldVariableBinary = session.isVariableBinary(); + try { + openInput(charset); + // if necessary, strip the BOM from the front of the file + reader.mark(1); + if (reader.read() != UTF8_BOM) { + reader.reset(); + } + if (quirksMode) { + session.setQuirksMode(true); + } + if (variableBinary) { + session.setVariableBinary(true); + } + ScriptReader r = new ScriptReader(reader); + while (true) { + String sql = r.readStatement(); + if (sql == null) { + break; + } + execute(sql); + count++; + if ((count & 127) == 0) { + checkCanceled(); + } + } + r.close(); + } catch (IOException e) { + throw DbException.convertIOException(e, null); + } finally { + if (quirksMode) { + session.setQuirksMode(oldQuirksMode); + } + if (variableBinary) { + session.setVariableBinary(oldVariableBinary); + } + closeIO(); + } + return count; + } + + private void execute(String sql) { + if (from1X) { + sql = sql.trim(); + if (sql.startsWith("INSERT INTO SYSTEM_LOB_STREAM VALUES(")) { + int idx = sql.indexOf(", NULL, '"); + if (idx >= 0) { + sql = new StringBuilder(sql.length() + 1).append(sql, 0, idx + 8).append("X'") + .append(sql, idx + 9, sql.length()).toString(); + } + } + } + try { + Prepared command = session.prepare(sql); + CommandContainer commandContainer = new CommandContainer(session, sql, command); + if (commandContainer.isQuery()) { + commandContainer.executeQuery(0, false); + } else { + commandContainer.executeUpdate(null); + } + } catch (DbException e) { + throw e.addSQL(sql); + } + } + + public void setCharset(Charset charset) { + this.charset = charset; + } + + /** + * Enables or disables the quirks mode. + * + * @param quirksMode + * whether quirks mode should be enabled + */ + public void setQuirksMode(boolean quirksMode) { + this.quirksMode = quirksMode; + } + + /** + * Changes parsing of a BINARY data type. + * + * @param variableBinary + * {@code true} to parse BINARY as VARBINARY, {@code false} to + * parse it as is + */ + public void setVariableBinary(boolean variableBinary) { + this.variableBinary = variableBinary; + } + + /** + * Enables quirks for parsing scripts from H2 1.*.*. + */ + public void setFrom1X() { + variableBinary = quirksMode = from1X = true; + } + + @Override + public ResultInterface queryMeta() { + return null; + } + + @Override + public int getType() { + return CommandInterface.RUNSCRIPT; + } + +} diff --git a/h2/src/main/org/h2/command/dml/ScriptBase.java b/h2/src/main/org/h2/command/dml/ScriptBase.java new file mode 100644 index 0000000..8e27b45 --- /dev/null +++ b/h2/src/main/org/h2/command/dml/ScriptBase.java @@ -0,0 +1,203 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.charset.Charset; +import org.h2.api.ErrorCode; +import org.h2.command.Prepared; +import org.h2.engine.Constants; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.engine.SysProperties; +import org.h2.expression.Expression; +import org.h2.message.DbException; +import org.h2.security.SHA256; +import org.h2.store.FileStore; +import org.h2.store.FileStoreInputStream; +import org.h2.store.FileStoreOutputStream; +import org.h2.store.fs.FileUtils; +import org.h2.tools.CompressTool; +import org.h2.util.IOUtils; +import org.h2.util.StringUtils; + +/** + * This class is the base for RunScriptCommand and ScriptCommand. + */ +abstract class ScriptBase extends Prepared { + + /** + * The default name of the script file if .zip compression is used. + */ + private static final String SCRIPT_SQL = "script.sql"; + + /** + * The output stream. + */ + protected OutputStream out; + + /** + * The input reader. + */ + protected BufferedReader reader; + + /** + * The file name (if set). + */ + private Expression fileNameExpr; + + private Expression password; + + private String fileName; + + private String cipher; + private FileStore store; + private String compressionAlgorithm; + + ScriptBase(SessionLocal session) { + super(session); + } + + public void setCipher(String c) { + cipher = c; + } + + private boolean isEncrypted() { + return cipher != null; + } + + public void setPassword(Expression password) { + this.password = password; + } + + public void setFileNameExpr(Expression file) { + this.fileNameExpr = file; + } + + protected String getFileName() { + if (fileNameExpr != null && fileName == null) { + fileName = fileNameExpr.optimize(session).getValue(session).getString(); + if (fileName == null || StringUtils.isWhitespaceOrEmpty(fileName)) { + fileName = "script.sql"; + } + fileName = SysProperties.getScriptDirectory() + fileName; + } + return fileName; + } + + @Override + public boolean isTransactional() { + return false; + } + + /** + * Delete the target file. + */ + void deleteStore() { + String file = getFileName(); + if (file != null) { + if (FileUtils.isRegularFile(file)) { + FileUtils.delete(file); + } + } + } + + private void initStore() { + Database db = session.getDatabase(); + byte[] key = null; + if (cipher != null && password != null) { + char[] pass = password.optimize(session). + getValue(session).getString().toCharArray(); + key = SHA256.getKeyPasswordHash("script", pass); + } + String file = getFileName(); + store = FileStore.open(db, file, "rw", cipher, key); + store.setCheckedWriting(false); + store.init(); + } + + /** + * Open the output stream. + */ + void openOutput() { + String file = getFileName(); + if (file == null) { + return; + } + if (isEncrypted()) { + initStore(); + out = new FileStoreOutputStream(store, compressionAlgorithm); + // always use a big buffer, otherwise end-of-block is written a lot + out = new BufferedOutputStream(out, Constants.IO_BUFFER_SIZE_COMPRESS); + } else { + OutputStream o; + try { + o = FileUtils.newOutputStream(file, false); + } catch (IOException e) { + throw DbException.convertIOException(e, null); + } + out = new BufferedOutputStream(o, Constants.IO_BUFFER_SIZE); + out = CompressTool.wrapOutputStream(out, compressionAlgorithm, SCRIPT_SQL); + } + } + + /** + * Open the input stream. + * + * @param charset the charset to use + */ + void openInput(Charset charset) { + String file = getFileName(); + if (file == null) { + return; + } + InputStream in; + if (isEncrypted()) { + initStore(); + in = new FileStoreInputStream(store, compressionAlgorithm != null, false); + } else { + try { + in = FileUtils.newInputStream(file); + } catch (IOException e) { + throw DbException.convertIOException(e, file); + } + in = CompressTool.wrapInputStream(in, compressionAlgorithm, SCRIPT_SQL); + if (in == null) { + throw DbException.get(ErrorCode.FILE_NOT_FOUND_1, SCRIPT_SQL + " in " + file); + } + } + reader = new BufferedReader(new InputStreamReader(in, charset), Constants.IO_BUFFER_SIZE); + } + + /** + * Close input and output streams. + */ + void closeIO() { + IOUtils.closeSilently(out); + out = null; + IOUtils.closeSilently(reader); + reader = null; + if (store != null) { + store.closeSilently(); + store = null; + } + } + + @Override + public boolean needRecompile() { + return false; + } + + public void setCompressionAlgorithm(String algorithm) { + this.compressionAlgorithm = algorithm; + } + +} diff --git a/h2/src/main/org/h2/command/dml/ScriptCommand.java b/h2/src/main/org/h2/command/dml/ScriptCommand.java new file mode 100644 index 0000000..d613e45 --- /dev/null +++ b/h2/src/main/org/h2/command/dml/ScriptCommand.java @@ -0,0 +1,858 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.constraint.Constraint; +import org.h2.constraint.Constraint.Type; +import org.h2.engine.Comment; +import org.h2.engine.Constants; +import org.h2.engine.Database; +import org.h2.engine.DbObject; +import org.h2.engine.Right; +import org.h2.engine.RightOwner; +import org.h2.engine.Role; +import org.h2.engine.SessionLocal; +import org.h2.engine.Setting; +import org.h2.engine.User; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.index.Cursor; +import org.h2.index.Index; +import org.h2.message.DbException; +import org.h2.mvstore.DataUtils; +import org.h2.result.LocalResult; +import org.h2.result.ResultInterface; +import org.h2.result.Row; +import org.h2.schema.Constant; +import org.h2.schema.Domain; +import org.h2.schema.Schema; +import org.h2.schema.SchemaObject; +import org.h2.schema.Sequence; +import org.h2.schema.TriggerObject; +import org.h2.schema.UserDefinedFunction; +import org.h2.table.Column; +import org.h2.table.PlanItem; +import org.h2.table.Table; +import org.h2.table.TableType; +import org.h2.util.HasSQL; +import org.h2.util.IOUtils; +import org.h2.util.MathUtils; +import org.h2.util.StringUtils; +import org.h2.util.Utils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueVarchar; + +/** + * This class represents the statement + * SCRIPT + */ +public class ScriptCommand extends ScriptBase { + + private static final Comparator BY_NAME_COMPARATOR = (o1, o2) -> { + if (o1 instanceof SchemaObject && o2 instanceof SchemaObject) { + int cmp = ((SchemaObject) o1).getSchema().getName().compareTo(((SchemaObject) o2).getSchema().getName()); + if (cmp != 0) { + return cmp; + } + } + return o1.getName().compareTo(o2.getName()); + }; + + private Charset charset = StandardCharsets.UTF_8; + private Set schemaNames; + private Collection
tables; + private boolean passwords; + + // true if we're generating the INSERT..VALUES statements for row values + private boolean data; + private boolean settings; + + // true if we're generating the DROP statements + private boolean drop; + private boolean simple; + private boolean withColumns; + private boolean version = true; + + private LocalResult result; + private String lineSeparatorString; + private byte[] lineSeparator; + private byte[] buffer; + private boolean tempLobTableCreated; + private int nextLobId; + private int lobBlockSize = Constants.IO_BUFFER_SIZE; + + public ScriptCommand(SessionLocal session) { + super(session); + } + + @Override + public boolean isQuery() { + return true; + } + + // TODO lock all tables for 'script' command + + public void setSchemaNames(Set schemaNames) { + this.schemaNames = schemaNames; + } + + public void setTables(Collection
tables) { + this.tables = tables; + } + + public void setData(boolean data) { + this.data = data; + } + + public void setPasswords(boolean passwords) { + this.passwords = passwords; + } + + public void setSettings(boolean settings) { + this.settings = settings; + } + + public void setLobBlockSize(long blockSize) { + this.lobBlockSize = MathUtils.convertLongToInt(blockSize); + } + + public void setDrop(boolean drop) { + this.drop = drop; + } + + @Override + public ResultInterface queryMeta() { + LocalResult r = createResult(); + r.done(); + return r; + } + + private LocalResult createResult() { + return new LocalResult(session, new Expression[] { + new ExpressionColumn(session.getDatabase(), new Column("SCRIPT", TypeInfo.TYPE_VARCHAR)) }, 1, 1); + } + + @Override + public ResultInterface query(long maxrows) { + session.getUser().checkAdmin(); + reset(); + Database db = session.getDatabase(); + if (schemaNames != null) { + for (String schemaName : schemaNames) { + Schema schema = db.findSchema(schemaName); + if (schema == null) { + throw DbException.get(ErrorCode.SCHEMA_NOT_FOUND_1, + schemaName); + } + } + } + try { + result = createResult(); + deleteStore(); + openOutput(); + if (out != null) { + buffer = new byte[Constants.IO_BUFFER_SIZE]; + } + if (version) { + add("-- H2 " + Constants.VERSION, true); + } + if (settings) { + for (Setting setting : db.getAllSettings()) { + if (setting.getName().equals(SetTypes.getTypeName( + SetTypes.CREATE_BUILD))) { + // don't add CREATE_BUILD to the script + // (it is only set when creating the database) + continue; + } + add(setting.getCreateSQL(), false); + } + } + if (out != null) { + add("", true); + } + RightOwner[] rightOwners = db.getAllUsersAndRoles().toArray(new RightOwner[0]); + // ADMIN users first, other users next, roles last + Arrays.sort(rightOwners, (o1, o2) -> { + boolean b = o1 instanceof User; + if (b != o2 instanceof User) { + return b ? -1 : 1; + } + if (b) { + b = ((User) o1).isAdmin(); + if (b != ((User) o2).isAdmin()) { + return b ? -1 : 1; + } + } + return o1.getName().compareTo(o2.getName()); + }); + for (RightOwner rightOwner : rightOwners) { + if (rightOwner instanceof User) { + add(((User) rightOwner).getCreateSQL(passwords), false); + } else { + add(((Role) rightOwner).getCreateSQL(true), false); + } + } + ArrayList schemas = new ArrayList<>(); + for (Schema schema : db.getAllSchemas()) { + if (excludeSchema(schema)) { + continue; + } + schemas.add(schema); + add(schema.getCreateSQL(), false); + } + dumpDomains(schemas); + for (Schema schema : schemas) { + for (Constant constant : sorted(schema.getAllConstants(), Constant.class)) { + add(constant.getCreateSQL(), false); + } + } + + final ArrayList
tables = db.getAllTablesAndViews(); + // sort by id, so that views are after tables and views on views + // after the base views + tables.sort(Comparator.comparingInt(Table::getId)); + + // Generate the DROP XXX ... IF EXISTS + for (Table table : tables) { + if (excludeSchema(table.getSchema())) { + continue; + } + if (excludeTable(table)) { + continue; + } + if (table.isHidden()) { + continue; + } + table.lock(session, Table.READ_LOCK); + String sql = table.getCreateSQL(); + if (sql == null) { + // null for metadata tables + continue; + } + if (drop) { + add(table.getDropSQL(), false); + } + } + for (Schema schema : schemas) { + for (UserDefinedFunction userDefinedFunction : sorted(schema.getAllFunctionsAndAggregates(), + UserDefinedFunction.class)) { + if (drop) { + add(userDefinedFunction.getDropSQL(), false); + } + add(userDefinedFunction.getCreateSQL(), false); + } + } + for (Schema schema : schemas) { + for (Sequence sequence : sorted(schema.getAllSequences(), Sequence.class)) { + if (sequence.getBelongsToTable()) { + continue; + } + if (drop) { + add(sequence.getDropSQL(), false); + } + add(sequence.getCreateSQL(), false); + } + } + + // Generate CREATE TABLE and INSERT...VALUES + int count = 0; + for (Table table : tables) { + if (excludeSchema(table.getSchema())) { + continue; + } + if (excludeTable(table)) { + continue; + } + if (table.isHidden()) { + continue; + } + table.lock(session, Table.READ_LOCK); + String createTableSql = table.getCreateSQL(); + if (createTableSql == null) { + // null for metadata tables + continue; + } + final TableType tableType = table.getTableType(); + add(createTableSql, false); + final ArrayList constraints = table.getConstraints(); + if (constraints != null) { + for (Constraint constraint : constraints) { + if (Constraint.Type.PRIMARY_KEY == constraint.getConstraintType()) { + add(constraint.getCreateSQLWithoutIndexes(), false); + } + } + } + if (TableType.TABLE == tableType) { + if (table.canGetRowCount(session)) { + StringBuilder builder = new StringBuilder("-- ") + .append(table.getRowCountApproximation(session)) + .append(" +/- SELECT COUNT(*) FROM "); + table.getSQL(builder, HasSQL.TRACE_SQL_FLAGS); + add(builder.toString(), false); + } + if (data) { + count = generateInsertValues(count, table); + } + } + final ArrayList indexes = table.getIndexes(); + for (int j = 0; indexes != null && j < indexes.size(); j++) { + Index index = indexes.get(j); + if (!index.getIndexType().getBelongsToConstraint()) { + add(index.getCreateSQL(), false); + } + } + } + if (tempLobTableCreated) { + add("DROP TABLE IF EXISTS SYSTEM_LOB_STREAM", true); + add("DROP ALIAS IF EXISTS SYSTEM_COMBINE_CLOB", true); + add("DROP ALIAS IF EXISTS SYSTEM_COMBINE_BLOB", true); + tempLobTableCreated = false; + } + // Generate CREATE CONSTRAINT ... + ArrayList constraints = new ArrayList<>(); + for (Schema schema : schemas) { + for (Constraint constraint : schema.getAllConstraints()) { + if (excludeTable(constraint.getTable())) { + continue; + } + Type constraintType = constraint.getConstraintType(); + if (constraintType != Type.DOMAIN && constraint.getTable().isHidden()) { + continue; + } + if (constraintType != Constraint.Type.PRIMARY_KEY) { + constraints.add(constraint); + } + } + } + constraints.sort(null); + for (Constraint constraint : constraints) { + add(constraint.getCreateSQLWithoutIndexes(), false); + } + // Generate CREATE TRIGGER ... + for (Schema schema : schemas) { + for (TriggerObject trigger : schema.getAllTriggers()) { + if (excludeTable(trigger.getTable())) { + continue; + } + add(trigger.getCreateSQL(), false); + } + } + // Generate GRANT ... + dumpRights(db); + // Generate COMMENT ON ... + for (Comment comment : db.getAllComments()) { + add(comment.getCreateSQL(), false); + } + if (out != null) { + out.close(); + } + } catch (IOException e) { + throw DbException.convertIOException(e, getFileName()); + } finally { + closeIO(); + } + result.done(); + LocalResult r = result; + reset(); + return r; + } + + private void dumpDomains(ArrayList schemas) throws IOException { + TreeMap> referencingDomains = new TreeMap<>(BY_NAME_COMPARATOR); + TreeSet known = new TreeSet<>(BY_NAME_COMPARATOR); + for (Schema schema : schemas) { + for (Domain domain : sorted(schema.getAllDomains(), Domain.class)) { + Domain parent = domain.getDomain(); + if (parent == null) { + addDomain(domain); + } else { + TreeSet set = referencingDomains.get(parent); + if (set == null) { + set = new TreeSet<>(BY_NAME_COMPARATOR); + referencingDomains.put(parent, set); + } + set.add(domain); + if (parent.getDomain() == null || !schemas.contains(parent.getSchema())) { + known.add(parent); + } + } + } + } + while (!referencingDomains.isEmpty()) { + TreeSet known2 = new TreeSet<>(BY_NAME_COMPARATOR); + for (Domain d : known) { + TreeSet set = referencingDomains.remove(d); + if (set != null) { + for (Domain d2 : set) { + addDomain(d2); + known2.add(d2); + } + } + } + known = known2; + } + } + + private void dumpRights(Database db) throws IOException { + Right[] rights = db.getAllRights().toArray(new Right[0]); + Arrays.sort(rights, (o1, o2) -> { + Role r1 = o1.getGrantedRole(), r2 = o2.getGrantedRole(); + if ((r1 == null) != (r2 == null)) { + return r1 == null ? -1 : 1; + } + if (r1 == null) { + DbObject g1 = o1.getGrantedObject(), g2 = o2.getGrantedObject(); + if ((g1 == null) != (g2 == null)) { + return g1 == null ? -1 : 1; + } + if (g1 != null) { + if (g1 instanceof Schema != g2 instanceof Schema) { + return g1 instanceof Schema ? -1 : 1; + } + int cmp = g1.getName().compareTo(g2.getName()); + if (cmp != 0) { + return cmp; + } + } + } else { + int cmp = r1.getName().compareTo(r2.getName()); + if (cmp != 0) { + return cmp; + } + } + return o1.getGrantee().getName().compareTo(o2.getGrantee().getName()); + }); + for (Right right : rights) { + DbObject object = right.getGrantedObject(); + if (object != null) { + if (object instanceof Schema) { + if (excludeSchema((Schema) object)) { + continue; + } + } else if (object instanceof Table) { + Table table = (Table) object; + if (excludeSchema(table.getSchema())) { + continue; + } + if (excludeTable(table)) { + continue; + } + } + } + add(right.getCreateSQL(), false); + } + } + + private void addDomain(Domain domain) throws IOException { + if (drop) { + add(domain.getDropSQL(), false); + } + add(domain.getCreateSQL(), false); + } + + private static T[] sorted(Collection collection, Class clazz) { + @SuppressWarnings("unchecked") + T[] array = collection.toArray((T[]) java.lang.reflect.Array.newInstance(clazz, 0)); + Arrays.sort(array, BY_NAME_COMPARATOR); + return array; + } + + private int generateInsertValues(int count, Table table) throws IOException { + PlanItem plan = table.getBestPlanItem(session, null, null, -1, null, null); + Index index = plan.getIndex(); + Cursor cursor = index.find(session, null, null); + Column[] columns = table.getColumns(); + boolean withGenerated = false, withGeneratedAlwaysAsIdentity = false; + for (Column c : columns) { + if (c.isGeneratedAlways()) { + if (c.isIdentity()) { + withGeneratedAlwaysAsIdentity = true; + } else { + withGenerated = true; + } + } + } + StringBuilder builder = new StringBuilder("INSERT INTO "); + table.getSQL(builder, HasSQL.DEFAULT_SQL_FLAGS); + if (withGenerated || withGeneratedAlwaysAsIdentity || withColumns) { + builder.append('('); + boolean needComma = false; + for (Column column : columns) { + if (!column.isGenerated()) { + if (needComma) { + builder.append(", "); + } + needComma = true; + column.getSQL(builder, HasSQL.DEFAULT_SQL_FLAGS); + } + } + builder.append(')'); + if (withGeneratedAlwaysAsIdentity) { + builder.append(" OVERRIDING SYSTEM VALUE"); + } + } + builder.append(" VALUES"); + if (!simple) { + builder.append('\n'); + } + builder.append('('); + String ins = builder.toString(); + builder = null; + int columnCount = columns.length; + while (cursor.next()) { + Row row = cursor.get(); + if (builder == null) { + builder = new StringBuilder(ins); + } else { + builder.append(",\n("); + } + boolean needComma = false; + for (int i = 0; i < columnCount; i++) { + if (columns[i].isGenerated()) { + continue; + } + if (needComma) { + builder.append(", "); + } + needComma = true; + Value v = row.getValue(i); + if (v.getType().getPrecision() > lobBlockSize) { + int id; + if (v.getValueType() == Value.CLOB) { + id = writeLobStream(v); + builder.append("SYSTEM_COMBINE_CLOB(").append(id).append(')'); + } else if (v.getValueType() == Value.BLOB) { + id = writeLobStream(v); + builder.append("SYSTEM_COMBINE_BLOB(").append(id).append(')'); + } else { + v.getSQL(builder, HasSQL.NO_CASTS); + } + } else { + v.getSQL(builder, HasSQL.NO_CASTS); + } + } + builder.append(')'); + count++; + if ((count & 127) == 0) { + checkCanceled(); + } + if (simple || builder.length() > Constants.IO_BUFFER_SIZE) { + add(builder.toString(), true); + builder = null; + } + } + if (builder != null) { + add(builder.toString(), true); + } + return count; + } + + private int writeLobStream(Value v) throws IOException { + if (!tempLobTableCreated) { + add("CREATE CACHED LOCAL TEMPORARY TABLE IF NOT EXISTS SYSTEM_LOB_STREAM" + + "(ID INT NOT NULL, PART INT NOT NULL, " + + "CDATA VARCHAR, BDATA VARBINARY)", + true); + add("ALTER TABLE SYSTEM_LOB_STREAM ADD CONSTRAINT SYSTEM_LOB_STREAM_PRIMARY_KEY PRIMARY KEY(ID, PART)", + true); + String className = getClass().getName(); + add("CREATE ALIAS IF NOT EXISTS " + "SYSTEM_COMBINE_CLOB FOR '" + className + ".combineClob'", true); + add("CREATE ALIAS IF NOT EXISTS " + "SYSTEM_COMBINE_BLOB FOR '" + className + ".combineBlob'", true); + tempLobTableCreated = true; + } + int id = nextLobId++; + switch (v.getValueType()) { + case Value.BLOB: { + byte[] bytes = new byte[lobBlockSize]; + try (InputStream input = v.getInputStream()) { + for (int i = 0;; i++) { + StringBuilder buff = new StringBuilder(lobBlockSize * 2); + buff.append("INSERT INTO SYSTEM_LOB_STREAM VALUES(").append(id) + .append(", ").append(i).append(", NULL, X'"); + int len = IOUtils.readFully(input, bytes, lobBlockSize); + if (len <= 0) { + break; + } + StringUtils.convertBytesToHex(buff, bytes, len).append("')"); + String sql = buff.toString(); + add(sql, true); + } + } + break; + } + case Value.CLOB: { + char[] chars = new char[lobBlockSize]; + + try (Reader reader = v.getReader()) { + for (int i = 0;; i++) { + StringBuilder buff = new StringBuilder(lobBlockSize * 2); + buff.append("INSERT INTO SYSTEM_LOB_STREAM VALUES(").append(id).append(", ").append(i) + .append(", "); + int len = IOUtils.readFully(reader, chars, lobBlockSize); + if (len == 0) { + break; + } + StringUtils.quoteStringSQL(buff, new String(chars, 0, len)). + append(", NULL)"); + String sql = buff.toString(); + add(sql, true); + } + } + break; + } + default: + throw DbException.getInternalError("type:" + v.getValueType()); + } + return id; + } + + /** + * Combine a BLOB. + * This method is called from the script. + * When calling with id -1, the file is deleted. + * + * @param conn a connection + * @param id the lob id + * @return a stream for the combined data + * @throws SQLException on failure + */ + public static InputStream combineBlob(Connection conn, int id) + throws SQLException { + if (id < 0) { + return null; + } + final ResultSet rs = getLobStream(conn, "BDATA", id); + return new InputStream() { + private InputStream current; + private boolean closed; + @Override + public int read() throws IOException { + while (true) { + try { + if (current == null) { + if (closed) { + return -1; + } + if (!rs.next()) { + close(); + return -1; + } + current = rs.getBinaryStream(1); + current = new BufferedInputStream(current); + } + int x = current.read(); + if (x >= 0) { + return x; + } + current = null; + } catch (SQLException e) { + throw DataUtils.convertToIOException(e); + } + } + } + @Override + public void close() throws IOException { + if (closed) { + return; + } + closed = true; + try { + rs.close(); + } catch (SQLException e) { + throw DataUtils.convertToIOException(e); + } + } + }; + } + + /** + * Combine a CLOB. + * This method is called from the script. + * + * @param conn a connection + * @param id the lob id + * @return a reader for the combined data + * @throws SQLException on failure + */ + public static Reader combineClob(Connection conn, int id) throws SQLException { + if (id < 0) { + return null; + } + final ResultSet rs = getLobStream(conn, "CDATA", id); + return new Reader() { + private Reader current; + private boolean closed; + @Override + public int read() throws IOException { + while (true) { + try { + if (current == null) { + if (closed) { + return -1; + } + if (!rs.next()) { + close(); + return -1; + } + current = rs.getCharacterStream(1); + current = new BufferedReader(current); + } + int x = current.read(); + if (x >= 0) { + return x; + } + current = null; + } catch (SQLException e) { + throw DataUtils.convertToIOException(e); + } + } + } + @Override + public void close() throws IOException { + if (closed) { + return; + } + closed = true; + try { + rs.close(); + } catch (SQLException e) { + throw DataUtils.convertToIOException(e); + } + } + @Override + public int read(char[] buffer, int off, int len) throws IOException { + if (len == 0) { + return 0; + } + int c = read(); + if (c == -1) { + return -1; + } + buffer[off] = (char) c; + int i = 1; + for (; i < len; i++) { + c = read(); + if (c == -1) { + break; + } + buffer[off + i] = (char) c; + } + return i; + } + }; + } + + private static ResultSet getLobStream(Connection conn, String column, int id) + throws SQLException { + PreparedStatement prep = conn.prepareStatement("SELECT " + column + + " FROM SYSTEM_LOB_STREAM WHERE ID=? ORDER BY PART"); + prep.setInt(1, id); + return prep.executeQuery(); + } + + private void reset() { + result = null; + buffer = null; + lineSeparatorString = System.lineSeparator(); + lineSeparator = lineSeparatorString.getBytes(charset); + } + + private boolean excludeSchema(Schema schema) { + if (schemaNames != null && !schemaNames.contains(schema.getName())) { + return true; + } + if (tables != null) { + // if filtering on specific tables, only include those schemas + for (Table table : schema.getAllTablesAndViews(session)) { + if (tables.contains(table)) { + return false; + } + } + return true; + } + return false; + } + + private boolean excludeTable(Table table) { + return tables != null && !tables.contains(table); + } + + private void add(String s, boolean insert) throws IOException { + if (s == null) { + return; + } + if (lineSeparator.length > 1 || lineSeparator[0] != '\n') { + s = StringUtils.replaceAll(s, "\n", lineSeparatorString); + } + s += ";"; + if (out != null) { + byte[] buff = s.getBytes(charset); + int len = MathUtils.roundUpInt(buff.length + + lineSeparator.length, Constants.FILE_BLOCK_SIZE); + buffer = Utils.copy(buff, buffer); + + if (len > buffer.length) { + buffer = new byte[len]; + } + System.arraycopy(buff, 0, buffer, 0, buff.length); + for (int i = buff.length; i < len - lineSeparator.length; i++) { + buffer[i] = ' '; + } + for (int j = 0, i = len - lineSeparator.length; i < len; i++, j++) { + buffer[i] = lineSeparator[j]; + } + out.write(buffer, 0, len); + if (!insert) { + result.addRow(ValueVarchar.get(s)); + } + } else { + result.addRow(ValueVarchar.get(s)); + } + } + + public void setSimple(boolean simple) { + this.simple = simple; + } + + public void setWithColumns(boolean withColumns) { + this.withColumns = withColumns; + } + + public void setVersion(boolean version) { + this.version = version; + } + + public void setCharset(Charset charset) { + this.charset = charset; + } + + @Override + public int getType() { + return CommandInterface.SCRIPT; + } + +} diff --git a/h2/src/main/org/h2/command/dml/Set.java b/h2/src/main/org/h2/command/dml/Set.java new file mode 100644 index 0000000..d0020a7 --- /dev/null +++ b/h2/src/main/org/h2/command/dml/Set.java @@ -0,0 +1,652 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import java.text.Collator; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.command.Parser; +import org.h2.command.Prepared; +import org.h2.engine.Constants; +import org.h2.engine.Database; +import org.h2.engine.Mode; +import org.h2.engine.SessionLocal; +import org.h2.engine.Setting; +import org.h2.expression.Expression; +import org.h2.expression.TimeZoneOperation; +import org.h2.expression.ValueExpression; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.mode.DefaultNullOrdering; +import org.h2.result.ResultInterface; +import org.h2.schema.Schema; +import org.h2.security.auth.AuthenticatorFactory; +import org.h2.table.Table; +import org.h2.util.DateTimeUtils; +import org.h2.util.StringUtils; +import org.h2.util.TimeZoneProvider; +import org.h2.value.CompareMode; +import org.h2.value.DataType; +import org.h2.value.Value; +import org.h2.value.ValueInteger; +import org.h2.value.ValueNull; + +/** + * This class represents the statement + * SET + */ +public class Set extends Prepared { + + private final int type; + private Expression expression; + private String stringValue; + private String[] stringValueList; + + public Set(SessionLocal session, int type) { + super(session); + this.type = type; + } + + public void setString(String v) { + this.stringValue = v; + } + + @Override + public boolean isTransactional() { + switch (type) { + case SetTypes.CLUSTER: + case SetTypes.VARIABLE: + case SetTypes.QUERY_TIMEOUT: + case SetTypes.LOCK_TIMEOUT: + case SetTypes.TRACE_LEVEL_SYSTEM_OUT: + case SetTypes.TRACE_LEVEL_FILE: + case SetTypes.THROTTLE: + case SetTypes.SCHEMA: + case SetTypes.SCHEMA_SEARCH_PATH: + case SetTypes.CATALOG: + case SetTypes.RETENTION_TIME: + case SetTypes.LAZY_QUERY_EXECUTION: + case SetTypes.NON_KEYWORDS: + case SetTypes.TIME_ZONE: + case SetTypes.VARIABLE_BINARY: + case SetTypes.TRUNCATE_LARGE_LENGTH: + case SetTypes.WRITE_DELAY: + return true; + default: + } + return false; + } + + @Override + public long update() { + Database database = session.getDatabase(); + String name = SetTypes.getTypeName(type); + switch (type) { + case SetTypes.ALLOW_LITERALS: { + session.getUser().checkAdmin(); + int value = getIntValue(); + if (value < 0 || value > 2) { + throw DbException.getInvalidValueException("ALLOW_LITERALS", value); + } + synchronized (database) { + database.setAllowLiterals(value); + addOrUpdateSetting(name, null, value); + } + break; + } + case SetTypes.CACHE_SIZE: { + session.getUser().checkAdmin(); + int value = getIntValue(); + if (value < 0) { + throw DbException.getInvalidValueException("CACHE_SIZE", value); + } + synchronized (database) { + database.setCacheSize(value); + addOrUpdateSetting(name, null, value); + } + break; + } + case SetTypes.CLUSTER: { + if (Constants.CLUSTERING_ENABLED.equals(stringValue)) { + // this value is used when connecting + // ignore, as the cluster setting is checked later + break; + } + String value = StringUtils.quoteStringSQL(stringValue); + if (!value.equals(database.getCluster())) { + if (!value.equals(Constants.CLUSTERING_DISABLED)) { + // anybody can disable the cluster + // (if he can't access a cluster node) + session.getUser().checkAdmin(); + } + database.setCluster(value); + // use the system session so that the current transaction + // (if any) is not committed + SessionLocal sysSession = database.getSystemSession(); + synchronized (sysSession) { + synchronized (database) { + addOrUpdateSetting(sysSession, name, value, 0); + sysSession.commit(true); + } + } + } + break; + } + case SetTypes.COLLATION: { + session.getUser().checkAdmin(); + CompareMode compareMode; + StringBuilder buff = new StringBuilder(stringValue); + if (stringValue.equals(CompareMode.OFF)) { + compareMode = CompareMode.getInstance(null, 0); + } else { + int strength = getIntValue(); + buff.append(" STRENGTH "); + if (strength == Collator.IDENTICAL) { + buff.append("IDENTICAL"); + } else if (strength == Collator.PRIMARY) { + buff.append("PRIMARY"); + } else if (strength == Collator.SECONDARY) { + buff.append("SECONDARY"); + } else if (strength == Collator.TERTIARY) { + buff.append("TERTIARY"); + } + compareMode = CompareMode.getInstance(stringValue, strength); + } + synchronized (database) { + CompareMode old = database.getCompareMode(); + if (old.equals(compareMode)) { + break; + } + Table table = database.getFirstUserTable(); + if (table != null) { + throw DbException.get(ErrorCode.COLLATION_CHANGE_WITH_DATA_TABLE_1, table.getTraceSQL()); + } + addOrUpdateSetting(name, buff.toString(), 0); + database.setCompareMode(compareMode); + } + break; + } + case SetTypes.CREATE_BUILD: { + session.getUser().checkAdmin(); + if (database.isStarting()) { + // just ignore the command if not starting + // this avoids problems when running recovery scripts + int value = getIntValue(); + synchronized (database) { + addOrUpdateSetting(name, null, value); + } + } + break; + } + case SetTypes.DATABASE_EVENT_LISTENER: { + session.getUser().checkAdmin(); + database.setEventListenerClass(stringValue); + break; + } + case SetTypes.DB_CLOSE_DELAY: { + session.getUser().checkAdmin(); + int value = getIntValue(); + if (value == -1) { + // -1 is a special value for in-memory databases, + // which means "keep the DB alive and use the same + // DB for all connections" + } else if (value < 0) { + throw DbException.getInvalidValueException("DB_CLOSE_DELAY", value); + } + synchronized (database) { + database.setCloseDelay(value); + addOrUpdateSetting(name, null, value); + } + break; + } + case SetTypes.DEFAULT_LOCK_TIMEOUT: { + session.getUser().checkAdmin(); + int value = getIntValue(); + if (value < 0) { + throw DbException.getInvalidValueException("DEFAULT_LOCK_TIMEOUT", value); + } + synchronized (database) { + addOrUpdateSetting(name, null, value); + } + break; + } + case SetTypes.DEFAULT_TABLE_TYPE: { + session.getUser().checkAdmin(); + int value = getIntValue(); + synchronized (database) { + database.setDefaultTableType(value); + addOrUpdateSetting(name, null, value); + } + break; + } + case SetTypes.EXCLUSIVE: { + session.getUser().checkAdmin(); + int value = getIntValue(); + switch (value) { + case 0: + if (!database.unsetExclusiveSession(session)) { + throw DbException.get(ErrorCode.DATABASE_IS_IN_EXCLUSIVE_MODE); + } + break; + case 1: + if (!database.setExclusiveSession(session, false)) { + throw DbException.get(ErrorCode.DATABASE_IS_IN_EXCLUSIVE_MODE); + } + break; + case 2: + if (!database.setExclusiveSession(session, true)) { + throw DbException.get(ErrorCode.DATABASE_IS_IN_EXCLUSIVE_MODE); + } + break; + default: + throw DbException.getInvalidValueException("EXCLUSIVE", value); + } + break; + } + case SetTypes.JAVA_OBJECT_SERIALIZER: { + session.getUser().checkAdmin(); + synchronized (database) { + Table table = database.getFirstUserTable(); + if (table != null) { + throw DbException.get(ErrorCode.JAVA_OBJECT_SERIALIZER_CHANGE_WITH_DATA_TABLE, + table.getTraceSQL()); + } + database.setJavaObjectSerializerName(stringValue); + addOrUpdateSetting(name, stringValue, 0); + } + break; + } + case SetTypes.IGNORECASE: { + session.getUser().checkAdmin(); + int value = getIntValue(); + synchronized (database) { + database.setIgnoreCase(value == 1); + addOrUpdateSetting(name, null, value); + } + break; + } + case SetTypes.LOCK_MODE: { + session.getUser().checkAdmin(); + int value = getIntValue(); + synchronized (database) { + database.setLockMode(value); + addOrUpdateSetting(name, null, value); + } + break; + } + case SetTypes.LOCK_TIMEOUT: { + int value = getIntValue(); + if (value < 0) { + throw DbException.getInvalidValueException("LOCK_TIMEOUT", value); + } + session.setLockTimeout(value); + break; + } + case SetTypes.MAX_LENGTH_INPLACE_LOB: { + session.getUser().checkAdmin(); + int value = getIntValue(); + if (value < 0) { + throw DbException.getInvalidValueException("MAX_LENGTH_INPLACE_LOB", value); + } + synchronized (database) { + database.setMaxLengthInplaceLob(value); + addOrUpdateSetting(name, null, value); + } + break; + } + case SetTypes.MAX_LOG_SIZE: { + session.getUser().checkAdmin(); + int value = getIntValue(); + if (value < 0) { + throw DbException.getInvalidValueException("MAX_LOG_SIZE", value); + } + break; + } + case SetTypes.MAX_MEMORY_ROWS: { + session.getUser().checkAdmin(); + int value = getIntValue(); + if (value < 0) { + throw DbException.getInvalidValueException("MAX_MEMORY_ROWS", value); + } + synchronized (database) { + database.setMaxMemoryRows(value); + addOrUpdateSetting(name, null, value); + } + break; + } + case SetTypes.MAX_MEMORY_UNDO: { + session.getUser().checkAdmin(); + int value = getIntValue(); + if (value < 0) { + throw DbException.getInvalidValueException("MAX_MEMORY_UNDO", value); + } + synchronized (database) { + addOrUpdateSetting(name, null, value); + } + break; + } + case SetTypes.MAX_OPERATION_MEMORY: { + session.getUser().checkAdmin(); + int value = getIntValue(); + if (value < 0) { + throw DbException.getInvalidValueException("MAX_OPERATION_MEMORY", value); + } + database.setMaxOperationMemory(value); + break; + } + case SetTypes.MODE: { + Mode mode = Mode.getInstance(stringValue); + if (mode == null) { + throw DbException.get(ErrorCode.UNKNOWN_MODE_1, stringValue); + } + if (database.getMode() != mode) { + session.getUser().checkAdmin(); + database.setMode(mode); + } + break; + } + case SetTypes.OPTIMIZE_REUSE_RESULTS: { + session.getUser().checkAdmin(); + database.setOptimizeReuseResults(getIntValue() != 0); + break; + } + case SetTypes.QUERY_TIMEOUT: { + int value = getIntValue(); + if (value < 0) { + throw DbException.getInvalidValueException("QUERY_TIMEOUT", value); + } + session.setQueryTimeout(value); + break; + } + case SetTypes.REDO_LOG_BINARY: { + DbException.getUnsupportedException("MV_STORE + SET REDO_LOG_BINARY"); + break; + } + case SetTypes.REFERENTIAL_INTEGRITY: { + session.getUser().checkAdmin(); + int value = getIntValue(); + if (value < 0 || value > 1) { + throw DbException.getInvalidValueException("REFERENTIAL_INTEGRITY", value); + } + database.setReferentialIntegrity(value == 1); + break; + } + case SetTypes.QUERY_STATISTICS: { + session.getUser().checkAdmin(); + int value = getIntValue(); + if (value < 0 || value > 1) { + throw DbException.getInvalidValueException("QUERY_STATISTICS", value); + } + database.setQueryStatistics(value == 1); + break; + } + case SetTypes.QUERY_STATISTICS_MAX_ENTRIES: { + session.getUser().checkAdmin(); + int value = getIntValue(); + if (value < 1) { + throw DbException.getInvalidValueException("QUERY_STATISTICS_MAX_ENTRIES", value); + } + database.setQueryStatisticsMaxEntries(value); + break; + } + case SetTypes.SCHEMA: { + Schema schema = database.getSchema(expression.optimize(session).getValue(session).getString()); + session.setCurrentSchema(schema); + break; + } + case SetTypes.SCHEMA_SEARCH_PATH: { + session.setSchemaSearchPath(stringValueList); + break; + } + case SetTypes.CATALOG: { + String shortName = database.getShortName(); + String value = expression.optimize(session).getValue(session).getString(); + if (value == null || !database.equalsIdentifiers(shortName, value) + && !database.equalsIdentifiers(shortName, value.trim())) { + throw DbException.get(ErrorCode.DATABASE_NOT_FOUND_1, stringValue); + } + break; + } + case SetTypes.TRACE_LEVEL_FILE: + session.getUser().checkAdmin(); + if (getPersistedObjectId() == 0) { + // don't set the property when opening the database + // this is for compatibility with older versions, because + // this setting was persistent + database.getTraceSystem().setLevelFile(getIntValue()); + } + break; + case SetTypes.TRACE_LEVEL_SYSTEM_OUT: + session.getUser().checkAdmin(); + if (getPersistedObjectId() == 0) { + // don't set the property when opening the database + // this is for compatibility with older versions, because + // this setting was persistent + database.getTraceSystem().setLevelSystemOut(getIntValue()); + } + break; + case SetTypes.TRACE_MAX_FILE_SIZE: { + session.getUser().checkAdmin(); + int value = getIntValue(); + if (value < 0) { + throw DbException.getInvalidValueException("TRACE_MAX_FILE_SIZE", value); + } + int size = value * (1024 * 1024); + synchronized (database) { + database.getTraceSystem().setMaxFileSize(size); + addOrUpdateSetting(name, null, value); + } + break; + } + case SetTypes.THROTTLE: { + int value = getIntValue(); + if (value < 0) { + throw DbException.getInvalidValueException("THROTTLE", value); + } + session.setThrottle(value); + break; + } + case SetTypes.VARIABLE: { + Expression expr = expression.optimize(session); + session.setVariable(stringValue, expr.getValue(session)); + break; + } + case SetTypes.WRITE_DELAY: { + session.getUser().checkAdmin(); + int value = getIntValue(); + if (value < 0) { + throw DbException.getInvalidValueException("WRITE_DELAY", value); + } + synchronized (database) { + database.setWriteDelay(value); + addOrUpdateSetting(name, null, value); + } + break; + } + case SetTypes.RETENTION_TIME: { + session.getUser().checkAdmin(); + int value = getIntValue(); + if (value < 0) { + throw DbException.getInvalidValueException("RETENTION_TIME", value); + } + synchronized (database) { + database.setRetentionTime(value); + addOrUpdateSetting(name, null, value); + } + break; + } + case SetTypes.LAZY_QUERY_EXECUTION: { + int value = getIntValue(); + if (value != 0 && value != 1) { + throw DbException.getInvalidValueException("LAZY_QUERY_EXECUTION", + value); + } + session.setLazyQueryExecution(value == 1); + break; + } + case SetTypes.BUILTIN_ALIAS_OVERRIDE: { + session.getUser().checkAdmin(); + int value = getIntValue(); + if (value != 0 && value != 1) { + throw DbException.getInvalidValueException("BUILTIN_ALIAS_OVERRIDE", + value); + } + database.setAllowBuiltinAliasOverride(value == 1); + break; + } + case SetTypes.AUTHENTICATOR: { + session.getUser().checkAdmin(); + boolean value = expression.optimize(session).getBooleanValue(session); + try { + synchronized (database) { + if (value) { + database.setAuthenticator(AuthenticatorFactory.createAuthenticator()); + } else { + database.setAuthenticator(null); + } + addOrUpdateSetting(name, value ? "TRUE" : "FALSE", 0); + } + } catch (Exception e) { + // Errors during start are ignored to allow to open the database + if (database.isStarting()) { + database.getTrace(Trace.DATABASE).error(e, + "{0}: failed to set authenticator during database start ", expression.toString()); + } else { + throw DbException.convert(e); + } + } + break; + } + case SetTypes.IGNORE_CATALOGS: { + session.getUser().checkAdmin(); + int value = getIntValue(); + synchronized (database) { + database.setIgnoreCatalogs(value == 1); + addOrUpdateSetting(name, null, value); + } + break; + } + case SetTypes.NON_KEYWORDS: + session.setNonKeywords(Parser.parseNonKeywords(stringValueList)); + break; + case SetTypes.TIME_ZONE: + session.setTimeZone(expression == null ? DateTimeUtils.getTimeZone() + : parseTimeZone(expression.getValue(session))); + break; + case SetTypes.VARIABLE_BINARY: + session.setVariableBinary(expression.getBooleanValue(session)); + break; + case SetTypes.DEFAULT_NULL_ORDERING: { + DefaultNullOrdering defaultNullOrdering; + try { + defaultNullOrdering = DefaultNullOrdering.valueOf(StringUtils.toUpperEnglish(stringValue)); + } catch (RuntimeException e) { + throw DbException.getInvalidValueException("DEFAULT_NULL_ORDERING", stringValue); + } + if (database.getDefaultNullOrdering() != defaultNullOrdering) { + session.getUser().checkAdmin(); + database.setDefaultNullOrdering(defaultNullOrdering); + } + break; + } + case SetTypes.TRUNCATE_LARGE_LENGTH: + session.setTruncateLargeLength(expression.getBooleanValue(session)); + break; + default: + throw DbException.getInternalError("type="+type); + } + // the meta data information has changed + database.getNextModificationDataId(); + // query caches might be affected as well, for example + // when changing the compatibility mode + database.getNextModificationMetaId(); + return 0; + } + + private static TimeZoneProvider parseTimeZone(Value v) { + if (DataType.isCharacterStringType(v.getValueType())) { + TimeZoneProvider timeZone; + try { + timeZone = TimeZoneProvider.ofId(v.getString()); + } catch (IllegalArgumentException ex) { + throw DbException.getInvalidValueException("TIME ZONE", v.getTraceSQL()); + } + return timeZone; + } else if (v == ValueNull.INSTANCE) { + throw DbException.getInvalidValueException("TIME ZONE", v); + } + return TimeZoneProvider.ofOffset(TimeZoneOperation.parseInterval(v)); + } + + private int getIntValue() { + expression = expression.optimize(session); + return expression.getValue(session).getInt(); + } + + public void setInt(int value) { + this.expression = ValueExpression.get(ValueInteger.get(value)); + } + + public void setExpression(Expression expression) { + this.expression = expression; + } + + private void addOrUpdateSetting(String name, String s, int v) { + addOrUpdateSetting(session, name, s, v); + } + + private void addOrUpdateSetting(SessionLocal session, String name, String s, int v) { + Database database = session.getDatabase(); + assert Thread.holdsLock(database); + if (database.isReadOnly()) { + return; + } + Setting setting = database.findSetting(name); + boolean addNew = false; + if (setting == null) { + addNew = true; + int id = getObjectId(); + setting = new Setting(database, id, name); + } + if (s != null) { + if (!addNew && setting.getStringValue().equals(s)) { + return; + } + setting.setStringValue(s); + } else { + if (!addNew && setting.getIntValue() == v) { + return; + } + setting.setIntValue(v); + } + if (addNew) { + database.addDatabaseObject(session, setting); + } else { + database.updateMeta(session, setting); + } + } + + @Override + public boolean needRecompile() { + return false; + } + + @Override + public ResultInterface queryMeta() { + return null; + } + + public void setStringArray(String[] list) { + this.stringValueList = list; + } + + @Override + public int getType() { + return CommandInterface.SET; + } + +} diff --git a/h2/src/main/org/h2/command/dml/SetClauseList.java b/h2/src/main/org/h2/command/dml/SetClauseList.java new file mode 100644 index 0000000..a17d38b --- /dev/null +++ b/h2/src/main/org/h2/command/dml/SetClauseList.java @@ -0,0 +1,404 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import java.util.ArrayList; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionList; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.Parameter; +import org.h2.expression.ValueExpression; +import org.h2.message.DbException; +import org.h2.result.LocalResult; +import org.h2.result.ResultTarget; +import org.h2.result.Row; +import org.h2.table.Column; +import org.h2.table.ColumnResolver; +import org.h2.table.DataChangeDeltaTable.ResultOption; +import org.h2.table.Table; +import org.h2.util.HasSQL; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * Set clause list. + */ +public final class SetClauseList implements HasSQL { + + private final Table table; + + private final UpdateAction[] actions; + + private boolean onUpdate; + + public SetClauseList(Table table) { + this.table = table; + actions = new UpdateAction[table.getColumns().length]; + } + + /** + * Add a single column. + * + * @param column the column + * @param expression the expression + */ + public void addSingle(Column column, Expression expression) { + int id = column.getColumnId(); + if (actions[id] != null) { + throw DbException.get(ErrorCode.DUPLICATE_COLUMN_NAME_1, column.getName()); + } + if (expression != ValueExpression.DEFAULT) { + actions[id] = new SetSimple(expression); + if (expression instanceof Parameter) { + ((Parameter) expression).setColumn(column); + } + } else { + actions[id] = SetClauseList.UpdateAction.SET_DEFAULT; + } + } + + /** + * Add multiple columns. + * + * @param columns the columns + * @param expression the expression (e.g. an expression list) + */ + public void addMultiple(ArrayList columns, Expression expression) { + int columnCount = columns.size(); + if (expression instanceof ExpressionList) { + ExpressionList expressions = (ExpressionList) expression; + if (!expressions.isArray()) { + if (columnCount != expressions.getSubexpressionCount()) { + throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); + } + for (int i = 0; i < columnCount; i++) { + addSingle(columns.get(i), expressions.getSubexpression(i)); + } + return; + } + } + if (columnCount == 1) { + // Row value special case + addSingle(columns.get(0), expression); + } else { + int[] cols = new int[columnCount]; + RowExpression row = new RowExpression(expression, cols); + int minId = table.getColumns().length - 1, maxId = 0; + for (int i = 0; i < columnCount; i++) { + int id = columns.get(i).getColumnId(); + if (id < minId) { + minId = id; + } + if (id > maxId) { + maxId = id; + } + } + for (int i = 0; i < columnCount; i++) { + Column column = columns.get(i); + int id = column.getColumnId(); + cols[i] = id; + if (actions[id] != null) { + throw DbException.get(ErrorCode.DUPLICATE_COLUMN_NAME_1, column.getName()); + } + actions[id] = new SetMultiple(row, i, id == minId, id == maxId); + } + } + } + + boolean prepareUpdate(Table table, SessionLocal session, ResultTarget deltaChangeCollector, + ResultOption deltaChangeCollectionMode, LocalResult rows, Row oldRow, + boolean updateToCurrentValuesReturnsZero) { + Column[] columns = table.getColumns(); + int columnCount = columns.length; + Row newRow = table.getTemplateRow(); + for (int i = 0; i < columnCount; i++) { + UpdateAction action = actions[i]; + Column column = columns[i]; + Value newValue; + if (action == null || action == UpdateAction.ON_UPDATE) { + newValue = column.isGenerated() ? null : oldRow.getValue(i); + } else if (action == UpdateAction.SET_DEFAULT) { + newValue = !column.isIdentity() ? null : oldRow.getValue(i); + } else { + newValue = action.update(session); + if (newValue == ValueNull.INSTANCE && column.isDefaultOnNull()) { + newValue = !column.isIdentity() ? null : oldRow.getValue(i); + } else if (column.isGeneratedAlways()) { + throw DbException.get(ErrorCode.GENERATED_COLUMN_CANNOT_BE_ASSIGNED_1, + column.getSQLWithTable(new StringBuilder(), TRACE_SQL_FLAGS).toString()); + } + } + newRow.setValue(i, newValue); + } + newRow.setKey(oldRow.getKey()); + table.convertUpdateRow(session, newRow, false); + boolean result = true; + if (onUpdate) { + if (!oldRow.hasSameValues(newRow)) { + for (int i = 0; i < columnCount; i++) { + if (actions[i] == UpdateAction.ON_UPDATE) { + newRow.setValue(i, columns[i].getEffectiveOnUpdateExpression().getValue(session)); + } else if (columns[i].isGenerated()) { + newRow.setValue(i, null); + } + } + // Convert on update expressions and reevaluate + // generated columns + table.convertUpdateRow(session, newRow, false); + } else if (updateToCurrentValuesReturnsZero) { + result = false; + } + } else if (updateToCurrentValuesReturnsZero && oldRow.hasSameValues(newRow)) { + result = false; + } + if (deltaChangeCollectionMode == ResultOption.OLD) { + deltaChangeCollector.addRow(oldRow.getValueList()); + } else if (deltaChangeCollectionMode == ResultOption.NEW) { + deltaChangeCollector.addRow(newRow.getValueList().clone()); + } + if (!table.fireRow() || !table.fireBeforeRow(session, oldRow, newRow)) { + rows.addRowForTable(oldRow); + rows.addRowForTable(newRow); + } + if (deltaChangeCollectionMode == ResultOption.FINAL) { + deltaChangeCollector.addRow(newRow.getValueList()); + } + return result; + } + + /** + * Check if this expression and all sub-expressions can fulfill a criteria. + * If any part returns false, the result is false. + * + * @param visitor + * the visitor + * @return if the criteria can be fulfilled + */ + boolean isEverything(ExpressionVisitor visitor) { + for (UpdateAction action : actions) { + if (action != null) { + if (!action.isEverything(visitor)) { + return false; + } + } + } + return true; + } + + /** + * Map the columns and optimize expressions. + * + * @param session + * the session + * @param resolver1 + * the first column resolver + * @param resolver2 + * the second column resolver, or {@code null} + */ + void mapAndOptimize(SessionLocal session, ColumnResolver resolver1, ColumnResolver resolver2) { + Column[] columns = table.getColumns(); + boolean onUpdate = false; + for (int i = 0; i < actions.length; i++) { + UpdateAction action = actions[i]; + if (action != null) { + action.mapAndOptimize(session, resolver1, resolver2); + } else { + Column column = columns[i]; + if (column.getEffectiveOnUpdateExpression() != null) { + actions[i] = UpdateAction.ON_UPDATE; + onUpdate = true; + } + } + } + this.onUpdate = onUpdate; + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + Column[] columns = table.getColumns(); + builder.append("\nSET\n "); + boolean f = false; + for (int i = 0; i < actions.length; i++) { + UpdateAction action = actions[i]; + if (action != null && action != UpdateAction.ON_UPDATE) { + if (action.getClass() == SetMultiple.class) { + SetMultiple multiple = (SetMultiple) action; + if (multiple.first) { + if (f) { + builder.append(",\n "); + } + f = true; + RowExpression r = multiple.row; + builder.append('('); + int[] cols = r.columns; + for (int j = 0, l = cols.length; j < l; j++) { + if (j > 0) { + builder.append(", "); + } + columns[cols[j]].getSQL(builder, sqlFlags); + } + r.expression.getUnenclosedSQL(builder.append(") = "), sqlFlags); + } + } else { + if (f) { + builder.append(",\n "); + } + f = true; + Column column = columns[i]; + if (action != UpdateAction.SET_DEFAULT) { + action.getSQL(builder, sqlFlags, column); + } else { + column.getSQL(builder, sqlFlags).append(" = DEFAULT"); + } + } + } + } + return builder; + } + + private static class UpdateAction { + + static UpdateAction ON_UPDATE = new UpdateAction(); + + static UpdateAction SET_DEFAULT = new UpdateAction(); + + UpdateAction() { + } + + Value update(SessionLocal session) { + throw DbException.getInternalError(); + } + + boolean isEverything(ExpressionVisitor visitor) { + return true; + } + + void mapAndOptimize(SessionLocal session, ColumnResolver resolver1, ColumnResolver resolver2) { + // Do nothing + } + + void getSQL(StringBuilder builder, int sqlFlags, Column column) { + throw DbException.getInternalError(); + } + + } + + private static final class SetSimple extends UpdateAction { + + private Expression expression; + + SetSimple(Expression expression) { + this.expression = expression; + } + + @Override + Value update(SessionLocal session) { + return expression.getValue(session); + } + + @Override + boolean isEverything(ExpressionVisitor visitor) { + return expression.isEverything(visitor); + } + + @Override + void mapAndOptimize(SessionLocal session, ColumnResolver resolver1, ColumnResolver resolver2) { + expression.mapColumns(resolver1, 0, Expression.MAP_INITIAL); + if (resolver2 != null) { + expression.mapColumns(resolver2, 0, Expression.MAP_INITIAL); + } + expression = expression.optimize(session); + } + + @Override + void getSQL(StringBuilder builder, int sqlFlags, Column column) { + expression.getUnenclosedSQL(column.getSQL(builder, sqlFlags).append(" = "), sqlFlags); + } + + } + + private static final class RowExpression { + + Expression expression; + + final int[] columns; + + Value[] values; + + RowExpression(Expression expression, int[] columns) { + this.expression = expression; + this.columns = columns; + } + + boolean isEverything(ExpressionVisitor visitor) { + return expression.isEverything(visitor); + } + + void mapAndOptimize(SessionLocal session, ColumnResolver resolver1, ColumnResolver resolver2) { + expression.mapColumns(resolver1, 0, Expression.MAP_INITIAL); + if (resolver2 != null) { + expression.mapColumns(resolver2, 0, Expression.MAP_INITIAL); + } + expression = expression.optimize(session); + } + } + + private static final class SetMultiple extends UpdateAction { + + final RowExpression row; + + private final int position; + + boolean first; + + private boolean last; + + SetMultiple(RowExpression row, int position, boolean first, boolean last) { + this.row = row; + this.position = position; + this.first = first; + this.last = last; + } + + @Override + Value update(SessionLocal session) { + Value[] v; + if (first) { + Value value = row.expression.getValue(session); + if (value == ValueNull.INSTANCE) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, "NULL to assigned row value"); + } + row.values = v = value.convertToAnyRow().getList(); + if (v.length != row.columns.length) { + throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); + } + } else { + v = row.values; + if (last) { + row.values = null; + } + } + return v[position]; + } + + @Override + boolean isEverything(ExpressionVisitor visitor) { + return !first || row.isEverything(visitor); + } + + @Override + void mapAndOptimize(SessionLocal session, ColumnResolver resolver1, ColumnResolver resolver2) { + if (first) { + row.mapAndOptimize(session, resolver1, resolver2); + } + } + + } + +} diff --git a/h2/src/main/org/h2/command/dml/SetSessionCharacteristics.java b/h2/src/main/org/h2/command/dml/SetSessionCharacteristics.java new file mode 100644 index 0000000..cb5efc6 --- /dev/null +++ b/h2/src/main/org/h2/command/dml/SetSessionCharacteristics.java @@ -0,0 +1,52 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import org.h2.command.CommandInterface; +import org.h2.command.Prepared; +import org.h2.engine.IsolationLevel; +import org.h2.engine.SessionLocal; +import org.h2.result.ResultInterface; + +/** + * This class represents the statement SET SESSION CHARACTERISTICS + */ +public class SetSessionCharacteristics extends Prepared { + + private final IsolationLevel isolationLevel; + + public SetSessionCharacteristics(SessionLocal session, IsolationLevel isolationLevel) { + super(session); + this.isolationLevel = isolationLevel; + } + + @Override + public boolean isTransactional() { + return false; + } + + @Override + public long update() { + session.setIsolationLevel(isolationLevel); + return 0; + } + + @Override + public boolean needRecompile() { + return false; + } + + @Override + public ResultInterface queryMeta() { + return null; + } + + @Override + public int getType() { + return CommandInterface.SET; + } + +} diff --git a/h2/src/main/org/h2/command/dml/SetTypes.java b/h2/src/main/org/h2/command/dml/SetTypes.java new file mode 100644 index 0000000..464ffc8 --- /dev/null +++ b/h2/src/main/org/h2/command/dml/SetTypes.java @@ -0,0 +1,329 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import java.util.ArrayList; + +/** + * The list of setting for a SET statement. + */ +public class SetTypes { + + /** + * The type of a SET IGNORECASE statement. + */ + public static final int IGNORECASE = 0; + + /** + * The type of a SET MAX_LOG_SIZE statement. + */ + public static final int MAX_LOG_SIZE = IGNORECASE + 1; + + /** + * The type of a SET MODE statement. + */ + public static final int MODE = MAX_LOG_SIZE + 1; + + /** + * The type of a SET READONLY statement. + */ + public static final int READONLY = MODE + 1; + + /** + * The type of a SET LOCK_TIMEOUT statement. + */ + public static final int LOCK_TIMEOUT = READONLY + 1; + + /** + * The type of a SET DEFAULT_LOCK_TIMEOUT statement. + */ + public static final int DEFAULT_LOCK_TIMEOUT = LOCK_TIMEOUT + 1; + + /** + * The type of a SET DEFAULT_TABLE_TYPE statement. + */ + public static final int DEFAULT_TABLE_TYPE = DEFAULT_LOCK_TIMEOUT + 1; + + /** + * The type of a SET CACHE_SIZE statement. + */ + public static final int CACHE_SIZE = DEFAULT_TABLE_TYPE + 1; + + /** + * The type of a SET TRACE_LEVEL_SYSTEM_OUT statement. + */ + public static final int TRACE_LEVEL_SYSTEM_OUT = CACHE_SIZE + 1; + + /** + * The type of a SET TRACE_LEVEL_FILE statement. + */ + public static final int TRACE_LEVEL_FILE = TRACE_LEVEL_SYSTEM_OUT + 1; + + /** + * The type of a SET TRACE_MAX_FILE_SIZE statement. + */ + public static final int TRACE_MAX_FILE_SIZE = TRACE_LEVEL_FILE + 1; + + /** + * The type of a SET COLLATION statement. + */ + public static final int COLLATION = TRACE_MAX_FILE_SIZE + 1; + + /** + * The type of a SET CLUSTER statement. + */ + public static final int CLUSTER = COLLATION + 1; + + /** + * The type of a SET WRITE_DELAY statement. + */ + public static final int WRITE_DELAY = CLUSTER + 1; + + /** + * The type of a SET DATABASE_EVENT_LISTENER statement. + */ + public static final int DATABASE_EVENT_LISTENER = WRITE_DELAY + 1; + + /** + * The type of a SET MAX_MEMORY_ROWS statement. + */ + public static final int MAX_MEMORY_ROWS = DATABASE_EVENT_LISTENER + 1; + + /** + * The type of a SET LOCK_MODE statement. + */ + public static final int LOCK_MODE = MAX_MEMORY_ROWS + 1; + + /** + * The type of a SET DB_CLOSE_DELAY statement. + */ + public static final int DB_CLOSE_DELAY = LOCK_MODE + 1; + + /** + * The type of a SET THROTTLE statement. + */ + public static final int THROTTLE = DB_CLOSE_DELAY + 1; + + /** + * The type of a SET MAX_MEMORY_UNDO statement. + */ + public static final int MAX_MEMORY_UNDO = THROTTLE + 1; + + /** + * The type of a SET MAX_LENGTH_INPLACE_LOB statement. + */ + public static final int MAX_LENGTH_INPLACE_LOB = MAX_MEMORY_UNDO + 1; + + /** + * The type of a SET ALLOW_LITERALS statement. + */ + public static final int ALLOW_LITERALS = MAX_LENGTH_INPLACE_LOB + 1; + + /** + * The type of a SET SCHEMA statement. + */ + public static final int SCHEMA = ALLOW_LITERALS + 1; + + /** + * The type of a SET OPTIMIZE_REUSE_RESULTS statement. + */ + public static final int OPTIMIZE_REUSE_RESULTS = SCHEMA + 1; + + /** + * The type of a SET SCHEMA_SEARCH_PATH statement. + */ + public static final int SCHEMA_SEARCH_PATH = OPTIMIZE_REUSE_RESULTS + 1; + + /** + * The type of a SET REFERENTIAL_INTEGRITY statement. + */ + public static final int REFERENTIAL_INTEGRITY = SCHEMA_SEARCH_PATH + 1; + + /** + * The type of a SET MAX_OPERATION_MEMORY statement. + */ + public static final int MAX_OPERATION_MEMORY = REFERENTIAL_INTEGRITY + 1; + + /** + * The type of a SET EXCLUSIVE statement. + */ + public static final int EXCLUSIVE = MAX_OPERATION_MEMORY + 1; + + /** + * The type of a SET CREATE_BUILD statement. + */ + public static final int CREATE_BUILD = EXCLUSIVE + 1; + + /** + * The type of a SET \@VARIABLE statement. + */ + public static final int VARIABLE = CREATE_BUILD + 1; + + /** + * The type of a SET QUERY_TIMEOUT statement. + */ + public static final int QUERY_TIMEOUT = VARIABLE + 1; + + /** + * The type of a SET REDO_LOG_BINARY statement. + */ + public static final int REDO_LOG_BINARY = QUERY_TIMEOUT + 1; + + /** + * The type of a SET JAVA_OBJECT_SERIALIZER statement. + */ + public static final int JAVA_OBJECT_SERIALIZER = REDO_LOG_BINARY + 1; + + /** + * The type of a SET RETENTION_TIME statement. + */ + public static final int RETENTION_TIME = JAVA_OBJECT_SERIALIZER + 1; + + /** + * The type of a SET QUERY_STATISTICS statement. + */ + public static final int QUERY_STATISTICS = RETENTION_TIME + 1; + + /** + * The type of a SET QUERY_STATISTICS_MAX_ENTRIES statement. + */ + public static final int QUERY_STATISTICS_MAX_ENTRIES = QUERY_STATISTICS + 1; + + /** + * The type of SET LAZY_QUERY_EXECUTION statement. + */ + public static final int LAZY_QUERY_EXECUTION = QUERY_STATISTICS_MAX_ENTRIES + 1; + + /** + * The type of SET BUILTIN_ALIAS_OVERRIDE statement. + */ + public static final int BUILTIN_ALIAS_OVERRIDE = LAZY_QUERY_EXECUTION + 1; + + /** + * The type of a SET AUTHENTICATOR statement. + */ + public static final int AUTHENTICATOR = BUILTIN_ALIAS_OVERRIDE + 1; + + /** + * The type of a SET IGNORE_CATALOGS statement. + */ + public static final int IGNORE_CATALOGS = AUTHENTICATOR + 1; + + /** + * The type of a SET CATALOG statement. + */ + public static final int CATALOG = IGNORE_CATALOGS + 1; + + /** + * The type of a SET NON_KEYWORDS statement. + */ + public static final int NON_KEYWORDS = CATALOG + 1; + + /** + * The type of a SET TIME ZONE statement. + */ + public static final int TIME_ZONE = NON_KEYWORDS + 1; + + /** + * The type of a SET VARIABLE_BINARY statement. + */ + public static final int VARIABLE_BINARY = TIME_ZONE + 1; + + /** + * The type of a SET DEFAULT_NULL_ORDERING statement. + */ + public static final int DEFAULT_NULL_ORDERING = VARIABLE_BINARY + 1; + + /** + * The type of a SET TRUNCATE_LARGE_LENGTH statement. + */ + public static final int TRUNCATE_LARGE_LENGTH = DEFAULT_NULL_ORDERING + 1; + + private static final int COUNT = TRUNCATE_LARGE_LENGTH + 1; + + private static final ArrayList TYPES; + + private SetTypes() { + // utility class + } + + static { + ArrayList list = new ArrayList<>(COUNT); + list.add("IGNORECASE"); + list.add("MAX_LOG_SIZE"); + list.add("MODE"); + list.add("READONLY"); + list.add("LOCK_TIMEOUT"); + list.add("DEFAULT_LOCK_TIMEOUT"); + list.add("DEFAULT_TABLE_TYPE"); + list.add("CACHE_SIZE"); + list.add("TRACE_LEVEL_SYSTEM_OUT"); + list.add("TRACE_LEVEL_FILE"); + list.add("TRACE_MAX_FILE_SIZE"); + list.add("COLLATION"); + list.add("CLUSTER"); + list.add("WRITE_DELAY"); + list.add("DATABASE_EVENT_LISTENER"); + list.add("MAX_MEMORY_ROWS"); + list.add("LOCK_MODE"); + list.add("DB_CLOSE_DELAY"); + list.add("THROTTLE"); + list.add("MAX_MEMORY_UNDO"); + list.add("MAX_LENGTH_INPLACE_LOB"); + list.add("ALLOW_LITERALS"); + list.add("SCHEMA"); + list.add("OPTIMIZE_REUSE_RESULTS"); + list.add("SCHEMA_SEARCH_PATH"); + list.add("REFERENTIAL_INTEGRITY"); + list.add("MAX_OPERATION_MEMORY"); + list.add("EXCLUSIVE"); + list.add("CREATE_BUILD"); + list.add("@"); + list.add("QUERY_TIMEOUT"); + list.add("REDO_LOG_BINARY"); + list.add("JAVA_OBJECT_SERIALIZER"); + list.add("RETENTION_TIME"); + list.add("QUERY_STATISTICS"); + list.add("QUERY_STATISTICS_MAX_ENTRIES"); + list.add("LAZY_QUERY_EXECUTION"); + list.add("BUILTIN_ALIAS_OVERRIDE"); + list.add("AUTHENTICATOR"); + list.add("IGNORE_CATALOGS"); + list.add("CATALOG"); + list.add("NON_KEYWORDS"); + list.add("TIME ZONE"); + list.add("VARIABLE_BINARY"); + list.add("DEFAULT_NULL_ORDERING"); + list.add("TRUNCATE_LARGE_LENGTH"); + TYPES = list; + assert(list.size() == COUNT); + } + + /** + * Get the set type number. + * + * @param name the set type name + * @return the number + */ + public static int getType(String name) { + return TYPES.indexOf(name); + } + + public static ArrayList getTypes() { + return TYPES; + } + + /** + * Get the set type name. + * + * @param type the type number + * @return the name + */ + public static String getTypeName(int type) { + return TYPES.get(type); + } + +} diff --git a/h2/src/main/org/h2/command/dml/TransactionCommand.java b/h2/src/main/org/h2/command/dml/TransactionCommand.java new file mode 100644 index 0000000..c8fa171 --- /dev/null +++ b/h2/src/main/org/h2/command/dml/TransactionCommand.java @@ -0,0 +1,130 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import org.h2.command.CommandInterface; +import org.h2.command.Prepared; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.result.ResultInterface; + +/** + * Represents a transactional statement. + */ +public class TransactionCommand extends Prepared { + + private final int type; + private String savepointName; + private String transactionName; + + public TransactionCommand(SessionLocal session, int type) { + super(session); + this.type = type; + } + + public void setSavepointName(String name) { + this.savepointName = name; + } + + @Override + public long update() { + switch (type) { + case CommandInterface.SET_AUTOCOMMIT_TRUE: + session.setAutoCommit(true); + break; + case CommandInterface.SET_AUTOCOMMIT_FALSE: + session.setAutoCommit(false); + break; + case CommandInterface.BEGIN: + session.begin(); + break; + case CommandInterface.COMMIT: + session.commit(false); + break; + case CommandInterface.ROLLBACK: + session.rollback(); + break; + case CommandInterface.CHECKPOINT: + session.getUser().checkAdmin(); + session.getDatabase().checkpoint(); + break; + case CommandInterface.SAVEPOINT: + session.addSavepoint(savepointName); + break; + case CommandInterface.ROLLBACK_TO_SAVEPOINT: + session.rollbackToSavepoint(savepointName); + break; + case CommandInterface.CHECKPOINT_SYNC: + session.getUser().checkAdmin(); + session.getDatabase().sync(); + break; + case CommandInterface.PREPARE_COMMIT: + session.prepareCommit(transactionName); + break; + case CommandInterface.COMMIT_TRANSACTION: + session.getUser().checkAdmin(); + session.setPreparedTransaction(transactionName, true); + break; + case CommandInterface.ROLLBACK_TRANSACTION: + session.getUser().checkAdmin(); + session.setPreparedTransaction(transactionName, false); + break; + case CommandInterface.SHUTDOWN: + case CommandInterface.SHUTDOWN_COMPACT: + case CommandInterface.SHUTDOWN_DEFRAG: + session.commit(false); + //$FALL-THROUGH$ + case CommandInterface.SHUTDOWN_IMMEDIATELY: { + session.getUser().checkAdmin(); + // throttle, to allow testing concurrent + // execution of shutdown and query + session.throttle(); + Database db = session.getDatabase(); + if (db.setExclusiveSession(session, true)) { + db.setCompactMode(type); + // close the database, but don't update the persistent setting + db.setCloseDelay(0); + session.close(); + } + break; + } + default: + throw DbException.getInternalError("type=" + type); + } + return 0; + } + + @Override + public boolean isTransactional() { + return true; + } + + @Override + public boolean needRecompile() { + return false; + } + + public void setTransactionName(String string) { + this.transactionName = string; + } + + @Override + public ResultInterface queryMeta() { + return null; + } + + @Override + public int getType() { + return type; + } + + @Override + public boolean isCacheable() { + return true; + } + +} diff --git a/h2/src/main/org/h2/command/dml/Update.java b/h2/src/main/org/h2/command/dml/Update.java new file mode 100644 index 0000000..d18bf66 --- /dev/null +++ b/h2/src/main/org/h2/command/dml/Update.java @@ -0,0 +1,187 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.dml; + +import java.util.HashSet; + +import org.h2.api.Trigger; +import org.h2.command.CommandInterface; +import org.h2.command.Prepared; +import org.h2.command.query.AllColumnsForPlan; +import org.h2.engine.DbObject; +import org.h2.engine.Right; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.message.DbException; +import org.h2.result.LocalResult; +import org.h2.result.ResultTarget; +import org.h2.result.Row; +import org.h2.table.DataChangeDeltaTable.ResultOption; +import org.h2.table.PlanItem; +import org.h2.table.Table; +import org.h2.table.TableFilter; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * This class represents the statement + * UPDATE + */ +public final class Update extends FilteredDataChangeStatement { + + private SetClauseList setClauseList; + + private Insert onDuplicateKeyInsert; + + private TableFilter fromTableFilter; + + public Update(SessionLocal session) { + super(session); + } + + public void setSetClauseList(SetClauseList setClauseList) { + this.setClauseList = setClauseList; + } + + public void setFromTableFilter(TableFilter tableFilter) { + this.fromTableFilter = tableFilter; + } + + @Override + public long update(ResultTarget deltaChangeCollector, ResultOption deltaChangeCollectionMode) { + targetTableFilter.startQuery(session); + targetTableFilter.reset(); + Table table = targetTableFilter.getTable(); + try (LocalResult rows = LocalResult.forTable(session, table)) { + session.getUser().checkTableRight(table, Right.UPDATE); + table.fire(session, Trigger.UPDATE, true); + table.lock(session, Table.WRITE_LOCK); + // get the old rows, compute the new rows + setCurrentRowNumber(0); + long count = 0; + long limitRows = -1; + if (fetchExpr != null) { + Value v = fetchExpr.getValue(session); + if (v == ValueNull.INSTANCE || (limitRows = v.getLong()) < 0) { + throw DbException.getInvalidValueException("FETCH", v); + } + } + while (nextRow(limitRows, count)) { + Row oldRow = targetTableFilter.get(); + if (table.isRowLockable()) { + Row lockedRow = table.lockRow(session, oldRow); + if (lockedRow == null) { + continue; + } + if (!oldRow.hasSharedData(lockedRow)) { + oldRow = lockedRow; + targetTableFilter.set(oldRow); + if (condition != null && !condition.getBooleanValue(session)) { + continue; + } + } + } + if (setClauseList.prepareUpdate(table, session, deltaChangeCollector, deltaChangeCollectionMode, + rows, oldRow, onDuplicateKeyInsert != null)) { + count++; + } + } + doUpdate(this, session, table, rows); + table.fire(session, Trigger.UPDATE, false); + return count; + } + } + + static void doUpdate(Prepared prepared, SessionLocal session, Table table, LocalResult rows) { + rows.done(); + // TODO self referencing referential integrity constraints + // don't work if update is multi-row and 'inversed' the condition! + // probably need multi-row triggers with 'deleted' and 'inserted' + // at the same time. anyway good for sql compatibility + // TODO update in-place (but if the key changes, + // we need to update all indexes) before row triggers + + // the cached row is already updated - we need the old values + table.updateRows(prepared, session, rows); + if (table.fireRow()) { + for (rows.reset(); rows.next();) { + Row o = rows.currentRowForTable(); + rows.next(); + Row n = rows.currentRowForTable(); + table.fireAfterRow(session, o, n, false); + } + } + } + + @Override + public String getPlanSQL(int sqlFlags) { + StringBuilder builder = new StringBuilder("UPDATE "); + targetTableFilter.getPlanSQL(builder, false, sqlFlags); + if (fromTableFilter != null) { + builder.append("\nFROM "); + fromTableFilter.getPlanSQL(builder, false, sqlFlags); + } + setClauseList.getSQL(builder, sqlFlags); + appendFilterCondition(builder, sqlFlags); + return builder.toString(); + } + + @Override + void doPrepare() { + if (fromTableFilter != null) { + targetTableFilter.addJoin(fromTableFilter, false, null); + } + if (condition != null) { + condition.mapColumns(targetTableFilter, 0, Expression.MAP_INITIAL); + if (fromTableFilter != null) { + condition.mapColumns(fromTableFilter, 0, Expression.MAP_INITIAL); + } + condition = condition.optimizeCondition(session); + if (condition != null) { + condition.createIndexConditions(session, targetTableFilter); + } + } + setClauseList.mapAndOptimize(session, targetTableFilter, fromTableFilter); + TableFilter[] filters = null; + if (fromTableFilter == null) { + filters = new TableFilter[] { targetTableFilter }; + } else { + filters = new TableFilter[] { targetTableFilter, fromTableFilter }; + } + PlanItem item = targetTableFilter.getBestPlanItem(session, filters, 0, new AllColumnsForPlan(filters)); + targetTableFilter.setPlanItem(item); + targetTableFilter.prepare(); + } + + @Override + public int getType() { + return CommandInterface.UPDATE; + } + + @Override + public String getStatementName() { + return "UPDATE"; + } + + @Override + public void collectDependencies(HashSet dependencies) { + ExpressionVisitor visitor = ExpressionVisitor.getDependenciesVisitor(dependencies); + if (condition != null) { + condition.isEverything(visitor); + } + setClauseList.isEverything(visitor); + } + + public Insert getOnDuplicateKeyInsert() { + return onDuplicateKeyInsert; + } + + void setOnDuplicateKeyInsert(Insert onDuplicateKeyInsert) { + this.onDuplicateKeyInsert = onDuplicateKeyInsert; + } + +} diff --git a/h2/src/main/org/h2/command/dml/package.html b/h2/src/main/org/h2/command/dml/package.html new file mode 100644 index 0000000..077734e --- /dev/null +++ b/h2/src/main/org/h2/command/dml/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Contains DML (data manipulation language) and related SQL statements. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/command/package.html b/h2/src/main/org/h2/command/package.html new file mode 100644 index 0000000..6003e70 --- /dev/null +++ b/h2/src/main/org/h2/command/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +This package contains the parser and the base classes for prepared SQL statements. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/command/query/AllColumnsForPlan.java b/h2/src/main/org/h2/command/query/AllColumnsForPlan.java new file mode 100644 index 0000000..b5b34e5 --- /dev/null +++ b/h2/src/main/org/h2/command/query/AllColumnsForPlan.java @@ -0,0 +1,58 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.query; + +import java.util.ArrayList; +import java.util.HashMap; +import org.h2.expression.ExpressionVisitor; +import org.h2.table.Column; +import org.h2.table.Table; +import org.h2.table.TableFilter; + +/** + * This information is expensive to compute for large queries, so do so + * on-demand. Also store the information pre-mapped by table to avoid expensive + * traversal. + */ +public class AllColumnsForPlan { + + private final TableFilter[] filters; + private HashMap> map; + + public AllColumnsForPlan(TableFilter[] filters) { + this.filters = filters; + } + + /** + * Called by ExpressionVisitor. + * + * @param newCol new column to be added. + */ + public void add(Column newCol) { + ArrayList cols = map.get(newCol.getTable()); + if (cols == null) { + cols = new ArrayList<>(); + map.put(newCol.getTable(), cols); + } + if (!cols.contains(newCol)) + cols.add(newCol); + } + + /** + * Used by index to calculate the cost of a scan. + * + * @param table the table. + * @return all table's referenced columns. + */ + public ArrayList get(Table table) { + if (map == null) { + map = new HashMap<>(); + ExpressionVisitor.allColumnsForTableFilters(filters, this); + } + return map.get(table); + } + +} diff --git a/h2/src/main/org/h2/command/query/Optimizer.java b/h2/src/main/org/h2/command/query/Optimizer.java new file mode 100644 index 0000000..83bd586 --- /dev/null +++ b/h2/src/main/org/h2/command/query/Optimizer.java @@ -0,0 +1,266 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.query; + +import java.util.BitSet; +import java.util.Random; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.table.Plan; +import org.h2.table.PlanItem; +import org.h2.table.TableFilter; +import org.h2.util.Permutations; + +/** + * The optimizer is responsible to find the best execution plan + * for a given query. + */ +class Optimizer { + + private static final int MAX_BRUTE_FORCE_FILTERS = 7; + private static final int MAX_BRUTE_FORCE = 2000; + private static final int MAX_GENETIC = 500; + private long startNs; + private BitSet switched; + + // possible plans for filters, if using brute force: + // 1 filter 1 plan + // 2 filters 2 plans + // 3 filters 6 plans + // 4 filters 24 plans + // 5 filters 120 plans + // 6 filters 720 plans + // 7 filters 5040 plans + // 8 filters 40320 plan + // 9 filters 362880 plans + // 10 filters 3628800 filters + + private final TableFilter[] filters; + private final Expression condition; + private final SessionLocal session; + + private Plan bestPlan; + private TableFilter topFilter; + private double cost; + private Random random; + private final AllColumnsForPlan allColumnsSet; + + Optimizer(TableFilter[] filters, Expression condition, SessionLocal session) { + this.filters = filters; + this.condition = condition; + this.session = session; + allColumnsSet = new AllColumnsForPlan(filters); + } + + /** + * How many filter to calculate using brute force. The remaining filters are + * selected using a greedy algorithm which has a runtime of (1 + 2 + ... + + * n) = (n * (n-1) / 2) for n filters. The brute force algorithm has a + * runtime of n * (n-1) * ... * (n-m) when calculating m brute force of n + * total. The combined runtime is (brute force) * (greedy). + * + * @param filterCount the number of filters total + * @return the number of filters to calculate using brute force + */ + private static int getMaxBruteForceFilters(int filterCount) { + int i = 0, j = filterCount, total = filterCount; + while (j > 0 && total * (j * (j - 1) / 2) < MAX_BRUTE_FORCE) { + j--; + total *= j; + i++; + } + return i; + } + + private void calculateBestPlan() { + cost = -1; + if (filters.length == 1) { + testPlan(filters); + } else { + startNs = System.nanoTime(); + if (filters.length <= MAX_BRUTE_FORCE_FILTERS) { + calculateBruteForceAll(); + } else { + calculateBruteForceSome(); + random = new Random(0); + calculateGenetic(); + } + } + } + + private void calculateFakePlan() { + cost = -1; + bestPlan = new Plan(filters, filters.length, condition); + } + + private boolean canStop(int x) { + return (x & 127) == 0 + // don't calculate for simple queries (no rows or so) + && cost >= 0 + // 100 microseconds * cost + && System.nanoTime() - startNs > cost * 100_000L; + } + + private void calculateBruteForceAll() { + TableFilter[] list = new TableFilter[filters.length]; + Permutations p = Permutations.create(filters, list); + for (int x = 0; !canStop(x) && p.next(); x++) { + testPlan(list); + } + } + + private void calculateBruteForceSome() { + int bruteForce = getMaxBruteForceFilters(filters.length); + TableFilter[] list = new TableFilter[filters.length]; + Permutations p = Permutations.create(filters, list, bruteForce); + for (int x = 0; !canStop(x) && p.next(); x++) { + // find out what filters are not used yet + for (TableFilter f : filters) { + f.setUsed(false); + } + for (int i = 0; i < bruteForce; i++) { + list[i].setUsed(true); + } + // fill the remaining elements with the unused elements (greedy) + for (int i = bruteForce; i < filters.length; i++) { + double costPart = -1.0; + int bestPart = -1; + for (int j = 0; j < filters.length; j++) { + if (!filters[j].isUsed()) { + if (i == filters.length - 1) { + bestPart = j; + break; + } + list[i] = filters[j]; + Plan part = new Plan(list, i+1, condition); + double costNow = part.calculateCost(session, allColumnsSet); + if (costPart < 0 || costNow < costPart) { + costPart = costNow; + bestPart = j; + } + } + } + filters[bestPart].setUsed(true); + list[i] = filters[bestPart]; + } + testPlan(list); + } + } + + private void calculateGenetic() { + TableFilter[] best = new TableFilter[filters.length]; + TableFilter[] list = new TableFilter[filters.length]; + for (int x = 0; x < MAX_GENETIC; x++) { + if (canStop(x)) { + break; + } + boolean generateRandom = (x & 127) == 0; + if (!generateRandom) { + System.arraycopy(best, 0, list, 0, filters.length); + if (!shuffleTwo(list)) { + generateRandom = true; + } + } + if (generateRandom) { + switched = new BitSet(); + System.arraycopy(filters, 0, best, 0, filters.length); + shuffleAll(best); + System.arraycopy(best, 0, list, 0, filters.length); + } + if (testPlan(list)) { + switched = new BitSet(); + System.arraycopy(list, 0, best, 0, filters.length); + } + } + } + + private boolean testPlan(TableFilter[] list) { + Plan p = new Plan(list, list.length, condition); + double costNow = p.calculateCost(session, allColumnsSet); + if (cost < 0 || costNow < cost) { + cost = costNow; + bestPlan = p; + return true; + } + return false; + } + + private void shuffleAll(TableFilter[] f) { + for (int i = 0; i < f.length - 1; i++) { + int j = i + random.nextInt(f.length - i); + if (j != i) { + TableFilter temp = f[i]; + f[i] = f[j]; + f[j] = temp; + } + } + } + + private boolean shuffleTwo(TableFilter[] f) { + int a = 0, b = 0, i = 0; + for (; i < 20; i++) { + a = random.nextInt(f.length); + b = random.nextInt(f.length); + if (a == b) { + continue; + } + if (a < b) { + int temp = a; + a = b; + b = temp; + } + int s = a * f.length + b; + if (switched.get(s)) { + continue; + } + switched.set(s); + break; + } + if (i == 20) { + return false; + } + TableFilter temp = f[a]; + f[a] = f[b]; + f[b] = temp; + return true; + } + + /** + * Calculate the best query plan to use. + * + * @param parse If we do not need to really get the best plan because it is + * a view parsing stage. + */ + void optimize(boolean parse) { + if (parse) { + calculateFakePlan(); + } else { + calculateBestPlan(); + bestPlan.removeUnusableIndexConditions(); + } + TableFilter[] f2 = bestPlan.getFilters(); + topFilter = f2[0]; + for (int i = 0; i < f2.length - 1; i++) { + f2[i].addJoin(f2[i + 1], false, null); + } + if (parse) { + return; + } + for (TableFilter f : f2) { + PlanItem item = bestPlan.getItem(f); + f.setPlanItem(item); + } + } + + public TableFilter getTopFilter() { + return topFilter; + } + + double getCost() { + return cost; + } + +} diff --git a/h2/src/main/org/h2/command/query/Query.java b/h2/src/main/org/h2/command/query/Query.java new file mode 100644 index 0000000..5d45222 --- /dev/null +++ b/h2/src/main/org/h2/command/query/Query.java @@ -0,0 +1,1036 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.query; + +import static org.h2.expression.Expression.WITHOUT_PARENTHESES; +import static org.h2.util.HasSQL.DEFAULT_SQL_FLAGS; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.command.Prepared; +import org.h2.engine.Database; +import org.h2.engine.DbObject; +import org.h2.engine.SessionLocal; +import org.h2.expression.Alias; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.Parameter; +import org.h2.expression.ValueExpression; +import org.h2.message.DbException; +import org.h2.result.LocalResult; +import org.h2.result.ResultInterface; +import org.h2.result.ResultTarget; +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.util.Utils; +import org.h2.value.ExtTypeInfoRow; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueInteger; +import org.h2.value.ValueNull; + +/** + * Represents a SELECT statement (simple, or union). + */ +public abstract class Query extends Prepared { + + /** + * Evaluated values of OFFSET and FETCH clauses. + */ + static final class OffsetFetch { + + /** + * OFFSET value. + */ + final long offset; + + /** + * FETCH value. + */ + final long fetch; + + /** + * Whether FETCH value is a PERCENT value. + */ + final boolean fetchPercent; + + OffsetFetch(long offset, long fetch, boolean fetchPercent) { + this.offset = offset; + this.fetch = fetch; + this.fetchPercent = fetchPercent; + } + + } + + /** + * The column list, including invisible expressions such as order by expressions. + */ + ArrayList expressions; + + /** + * Array of expressions. + * + * @see #expressions + */ + Expression[] expressionArray; + + /** + * Describes elements of the ORDER BY clause of a query. + */ + ArrayList orderList; + + /** + * A sort order represents an ORDER BY clause in a query. + */ + SortOrder sort; + + /** + * The fetch expression as specified in the FETCH, LIMIT, or TOP clause. + */ + Expression fetchExpr; + + /** + * Whether limit expression specifies percentage of rows. + */ + boolean fetchPercent; + + /** + * Whether tied rows should be included in result too. + */ + boolean withTies; + + /** + * The offset expression as specified in the OFFSET clause. + */ + Expression offsetExpr; + + /** + * Whether the result must only contain distinct rows. + */ + boolean distinct; + + /** + * Whether the result needs to support random access. + */ + boolean randomAccessResult; + + /** + * The visible columns (the ones required in the result). + */ + int visibleColumnCount; + + /** + * Number of columns including visible columns and additional virtual + * columns for ORDER BY and DISTINCT ON clauses. This number does not + * include virtual columns for HAVING and QUALIFY. + */ + int resultColumnCount; + + private boolean noCache; + private long lastLimit; + private long lastEvaluated; + private ResultInterface lastResult; + private Boolean lastExists; + private Value[] lastParameters; + private boolean cacheableChecked; + private boolean neverLazy; + + boolean checkInit; + + boolean isPrepared; + + Query(SessionLocal session) { + super(session); + } + + public void setNeverLazy(boolean b) { + this.neverLazy = b; + } + + public boolean isNeverLazy() { + return neverLazy; + } + + /** + * Check if this is a UNION query. + * + * @return {@code true} if this is a UNION query + */ + public abstract boolean isUnion(); + + @Override + public ResultInterface queryMeta() { + LocalResult result = new LocalResult(session, expressionArray, visibleColumnCount, resultColumnCount); + result.done(); + return result; + } + + /** + * Execute the query without checking the cache. If a target is specified, + * the results are written to it, and the method returns null. If no target + * is specified, a new LocalResult is created and returned. + * + * @param limit the limit as specified in the JDBC method call + * @param target the target to write results to + * @return the result + */ + protected abstract ResultInterface queryWithoutCache(long limit, ResultTarget target); + + private ResultInterface queryWithoutCacheLazyCheck(long limit, ResultTarget target) { + boolean disableLazy = neverLazy && session.isLazyQueryExecution(); + if (disableLazy) { + session.setLazyQueryExecution(false); + } + try { + return queryWithoutCache(limit, target); + } finally { + if (disableLazy) { + session.setLazyQueryExecution(true); + } + } + } + + /** + * Initialize the query. + */ + 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. + * + * @return the list of expressions + */ + public ArrayList getExpressions() { + return expressions; + } + + /** + * Calculate the cost to execute this query. + * + * @return the cost + */ + public abstract double getCost(); + + /** + * Calculate the cost when used as a subquery. + * This method returns a value between 10 and 1000000, + * to ensure adding other values can't result in an integer overflow. + * + * @return the estimated cost as an integer + */ + public int getCostAsExpression() { + // ensure the cost is not larger than 1 million, + // so that adding other values can't overflow + return (int) Math.min(1_000_000d, 10d + 10d * getCost()); + } + + /** + * Get all tables that are involved in this query. + * + * @return the set of tables + */ + public abstract HashSet
getTables(); + + /** + * Set the order by list. + * + * @param order the order by list + */ + public void setOrder(ArrayList order) { + orderList = order; + } + + /** + * Whether the query has an order. + * + * @return true if it has + */ + public boolean hasOrder() { + return orderList != null || sort != null; + } + + /** + * Set the 'for update' flag. + * + * @param forUpdate the new setting + */ + public abstract void setForUpdate(boolean forUpdate); + + /** + * Get the column count of this query. + * + * @return the column count + */ + public int getColumnCount() { + return visibleColumnCount; + } + + /** + * Returns data type of rows. + * + * @return data type of rows + */ + public TypeInfo getRowDataType() { + if (visibleColumnCount == 1) { + return expressionArray[0].getType(); + } + return TypeInfo.getTypeInfo(Value.ROW, -1L, -1, new ExtTypeInfoRow(expressionArray, visibleColumnCount)); + } + + /** + * Map the columns to the given column resolver. + * + * @param resolver + * the resolver + * @param level + * the subquery level (0 is the top level query, 1 is the first + * subquery level) + */ + public abstract void mapColumns(ColumnResolver resolver, int level); + + /** + * Change the evaluatable flag. This is used when building the execution + * plan. + * + * @param tableFilter the table filter + * @param b the new value + */ + public abstract void setEvaluatable(TableFilter tableFilter, boolean b); + + /** + * Add a condition to the query. This is used for views. + * + * @param param the parameter + * @param columnId the column index (0 meaning the first column) + * @param comparisonType the comparison type + */ + public abstract void addGlobalCondition(Parameter param, int columnId, + int comparisonType); + + /** + * Check whether adding condition to the query is allowed. This is not + * allowed for views that have an order by and a limit, as it would affect + * the returned results. + * + * @return true if adding global conditions is allowed + */ + public abstract boolean allowGlobalConditions(); + + /** + * Check if this expression and all sub-expressions can fulfill a criteria. + * If any part returns false, the result is false. + * + * @param visitor the visitor + * @return if the criteria can be fulfilled + */ + public abstract boolean isEverything(ExpressionVisitor visitor); + + @Override + public boolean isReadOnly() { + return isEverything(ExpressionVisitor.READONLY_VISITOR); + } + + /** + * Update all aggregate function values. + * + * @param s the session + * @param stage select stage + */ + public abstract void updateAggregate(SessionLocal s, int stage); + + /** + * Call the before triggers on all tables. + */ + public abstract void fireBeforeSelectTriggers(); + + /** + * Set the distinct flag only if it is possible, may be used as a possible + * optimization only. + */ + public void setDistinctIfPossible() { + if (!isAnyDistinct() && offsetExpr == null && fetchExpr == null) { + distinct = true; + } + } + + /** + * @return whether this query is a plain {@code DISTINCT} query + */ + public boolean isStandardDistinct() { + return distinct; + } + + /** + * @return whether this query is a {@code DISTINCT} or + * {@code DISTINCT ON (...)} query + */ + public boolean isAnyDistinct() { + return distinct; + } + + /** + * Returns whether results support random access. + * + * @return whether results support random access + */ + public boolean isRandomAccessResult() { + return randomAccessResult; + } + + /** + * Whether results need to support random access. + * + * @param b the new value + */ + public void setRandomAccessResult(boolean b) { + randomAccessResult = b; + } + + @Override + public boolean isQuery() { + return true; + } + + @Override + public boolean isTransactional() { + return true; + } + + /** + * Disable caching of result sets. + */ + public void disableCache() { + this.noCache = true; + } + + private boolean sameResultAsLast(Value[] params, Value[] lastParams, long lastEval) { + if (!cacheableChecked) { + long max = getMaxDataModificationId(); + noCache = max == Long.MAX_VALUE; + if (!isEverything(ExpressionVisitor.DETERMINISTIC_VISITOR) || + !isEverything(ExpressionVisitor.INDEPENDENT_VISITOR)) { + noCache = true; + } + cacheableChecked = true; + } + if (noCache) { + return false; + } + for (int i = 0; i < params.length; i++) { + Value a = lastParams[i], b = params[i]; + // Derived tables can have gaps in parameters + if (a != null && !a.equals(b)) { + return false; + } + } + return getMaxDataModificationId() <= lastEval; + } + + private Value[] getParameterValues() { + ArrayList list = getParameters(); + if (list == null) { + return Value.EMPTY_VALUES; + } + int size = list.size(); + Value[] params = new Value[size]; + for (int i = 0; i < size; i++) { + Parameter parameter = list.get(i); + // Derived tables can have gaps in parameters + params[i] = parameter != null ? parameter.getParamValue() : null; + } + return params; + } + + @Override + public final ResultInterface query(long maxrows) { + return query(maxrows, null); + } + + /** + * Execute the query, writing the result to the target result. + * + * @param limit the maximum number of rows to return + * @param target the target result (null will return the result) + * @return the result set (if the target is not set). + */ + public final ResultInterface query(long limit, ResultTarget target) { + if (isUnion()) { + // union doesn't always know the parameter list of the left and + // right queries + return queryWithoutCacheLazyCheck(limit, target); + } + fireBeforeSelectTriggers(); + if (noCache || !session.getDatabase().getOptimizeReuseResults() || + (session.isLazyQueryExecution() && !neverLazy)) { + return queryWithoutCacheLazyCheck(limit, target); + } + Value[] params = getParameterValues(); + long now = session.getDatabase().getModificationDataId(); + if (isEverything(ExpressionVisitor.DETERMINISTIC_VISITOR)) { + if (lastResult != null && !lastResult.isClosed() && + limit == lastLimit) { + if (sameResultAsLast(params, lastParameters, lastEvaluated)) { + lastResult = lastResult.createShallowCopy(session); + if (lastResult != null) { + lastResult.reset(); + return lastResult; + } + } + } + } + lastParameters = params; + closeLastResult(); + ResultInterface r = queryWithoutCacheLazyCheck(limit, target); + lastResult = r; + lastExists = null; + lastEvaluated = now; + lastLimit = limit; + return r; + } + + private void closeLastResult() { + if (lastResult != null) { + lastResult.close(); + } + } + + /** + * Execute the EXISTS predicate over the query. + * + * @return EXISTS predicate result + */ + public final boolean exists() { + if (isUnion()) { + // union doesn't always know the parameter list of the left and + // right queries + return executeExists(); + } + fireBeforeSelectTriggers(); + if (noCache || !session.getDatabase().getOptimizeReuseResults()) { + return executeExists(); + } + Value[] params = getParameterValues(); + long now = session.getDatabase().getModificationDataId(); + if (isEverything(ExpressionVisitor.DETERMINISTIC_VISITOR)) { + if (lastExists != null) { + if (sameResultAsLast(params, lastParameters, lastEvaluated)) { + return lastExists; + } + } + } + lastParameters = params; + boolean exists = executeExists(); + lastExists = exists; + lastResult = null; + lastEvaluated = now; + return exists; + } + + private boolean executeExists() { + ResultInterface r = queryWithoutCacheLazyCheck(1L, null); + boolean exists = r.hasNext(); + r.close(); + return exists; + } + + /** + * Initialize the order by list. This call may extend the expressions list. + * + * @param expressionSQL the select list SQL snippets + * @param mustBeInResult all order by expressions must be in the select list + * @param filters the table filters + * @return {@code true} if ORDER BY clause is preserved, {@code false} + * otherwise + */ + boolean initOrder(ArrayList expressionSQL, boolean mustBeInResult, ArrayList filters) { + for (Iterator i = orderList.iterator(); i.hasNext();) { + QueryOrderBy o = i.next(); + Expression e = o.expression; + if (e == null) { + continue; + } + if (e.isConstant()) { + i.remove(); + continue; + } + int idx = initExpression(expressionSQL, e, mustBeInResult, filters); + o.columnIndexExpr = ValueExpression.get(ValueInteger.get(idx + 1)); + o.expression = expressions.get(idx).getNonAliasExpression(); + } + if (orderList.isEmpty()) { + orderList = null; + return false; + } + return true; + } + + /** + * Initialize the 'ORDER BY' or 'DISTINCT' expressions. + * + * @param expressionSQL the select list SQL snippets + * @param e the expression. + * @param mustBeInResult all order by expressions must be in the select list + * @param filters the table filters. + * @return index on the expression in the {@link #expressions} list. + */ + int initExpression(ArrayList expressionSQL, Expression e, boolean mustBeInResult, + ArrayList filters) { + Database db = session.getDatabase(); + // special case: SELECT 1 AS A FROM DUAL ORDER BY A + // (oracle supports it, but only in order by, not in group by and + // not in having): + // SELECT 1 AS A FROM DUAL ORDER BY -A + if (e instanceof ExpressionColumn) { + // order by expression + ExpressionColumn exprCol = (ExpressionColumn) e; + String tableAlias = exprCol.getOriginalTableAliasName(); + String col = exprCol.getOriginalColumnName(); + for (int j = 0, visible = getColumnCount(); j < visible; j++) { + Expression ec = expressions.get(j); + if (ec instanceof ExpressionColumn) { + // select expression + ExpressionColumn c = (ExpressionColumn) ec; + if (!db.equalsIdentifiers(col, c.getColumnName(session, j))) { + continue; + } + if (tableAlias == null) { + return j; + } + String ca = c.getOriginalTableAliasName(); + if (ca != null) { + if (db.equalsIdentifiers(ca, tableAlias)) { + return j; + } + } else if (filters != null) { + // select id from test order by test.id + for (TableFilter f : filters) { + if (db.equalsIdentifiers(f.getTableAlias(), tableAlias)) { + return j; + } + } + } + } else if (ec instanceof Alias) { + if (tableAlias == null && db.equalsIdentifiers(col, ec.getAlias(session, j))) { + return j; + } + Expression ec2 = ec.getNonAliasExpression(); + if (ec2 instanceof ExpressionColumn) { + ExpressionColumn c2 = (ExpressionColumn) ec2; + String ta = exprCol.getSQL(DEFAULT_SQL_FLAGS, WITHOUT_PARENTHESES); + String tb = c2.getSQL(DEFAULT_SQL_FLAGS, WITHOUT_PARENTHESES); + String s2 = c2.getColumnName(session, j); + if (db.equalsIdentifiers(col, s2) && db.equalsIdentifiers(ta, tb)) { + return j; + } + } + } + } + } else if (expressionSQL != null) { + String s = e.getSQL(DEFAULT_SQL_FLAGS, WITHOUT_PARENTHESES); + for (int j = 0, size = expressionSQL.size(); j < size; j++) { + if (db.equalsIdentifiers(expressionSQL.get(j), s)) { + return j; + } + } + } + if (expressionSQL == null + || mustBeInResult && !db.getMode().allowUnrelatedOrderByExpressionsInDistinctQueries + && !checkOrderOther(session, e, expressionSQL)) { + throw DbException.get(ErrorCode.ORDER_BY_NOT_IN_RESULT, e.getTraceSQL()); + } + int idx = expressions.size(); + expressions.add(e); + expressionSQL.add(e.getSQL(DEFAULT_SQL_FLAGS, WITHOUT_PARENTHESES)); + return idx; + } + + /** + * An additional check for expression in ORDER BY list for DISTINCT selects + * that was not matched with selected expressions in regular way. This + * method allows expressions based only on selected expressions in different + * complicated ways with functions, comparisons, or operators. + * + * @param session session + * @param expr expression to check + * @param expressionSQL SQL of allowed expressions + * @return whether the specified expression should be allowed in ORDER BY + * list of DISTINCT select + */ + private static boolean checkOrderOther(SessionLocal session, Expression expr, ArrayList expressionSQL) { + if (expr == null || expr.isConstant()) { + // ValueExpression, null expression in CASE, or other + return true; + } + String exprSQL = expr.getSQL(DEFAULT_SQL_FLAGS, WITHOUT_PARENTHESES); + for (String sql: expressionSQL) { + if (session.getDatabase().equalsIdentifiers(exprSQL, sql)) { + return true; + } + } + int count = expr.getSubexpressionCount(); + if (!expr.isEverything(ExpressionVisitor.DETERMINISTIC_VISITOR)) { + return false; + } else if (count <= 0) { + // Expression is an ExpressionColumn, Parameter, SequenceValue or + // has other unsupported type without subexpressions + return false; + } + for (int i = 0; i < count; i++) { + if (!checkOrderOther(session, expr.getSubexpression(i), expressionSQL)) { + return false; + } + } + return true; + } + + /** + * Create a {@link SortOrder} object given the list of {@link QueryOrderBy} + * objects. + * + * @param orderList a list of {@link QueryOrderBy} elements + * @param expressionCount the number of columns in the query + */ + void prepareOrder(ArrayList orderList, int expressionCount) { + int size = orderList.size(); + int[] index = new int[size]; + int[] sortType = new int[size]; + for (int i = 0; i < size; i++) { + QueryOrderBy o = orderList.get(i); + int idx; + boolean reverse = false; + Value v = o.columnIndexExpr.getValue(null); + if (v == ValueNull.INSTANCE) { + // parameter not yet set - order by first column + idx = 0; + } else { + idx = v.getInt(); + if (idx < 0) { + reverse = true; + idx = -idx; + } + idx -= 1; + if (idx < 0 || idx >= expressionCount) { + throw DbException.get(ErrorCode.ORDER_BY_NOT_IN_RESULT, Integer.toString(idx + 1)); + } + } + index[i] = idx; + int type = o.sortType; + if (reverse) { + // TODO NULLS FIRST / LAST should be inverted too? + type ^= SortOrder.DESCENDING; + } + sortType[i] = type; + } + sort = new SortOrder(session, index, sortType, orderList); + this.orderList = null; + } + + /** + * Removes constant expressions from the sort order. + * + * Some constants are detected only after optimization of expressions, this + * method removes them from the sort order only. They are currently + * preserved in the list of expressions. + */ + void cleanupOrder() { + int sourceIndexes[] = sort.getQueryColumnIndexes(); + int count = sourceIndexes.length; + int constants = 0; + for (int i = 0; i < count; i++) { + if (expressions.get(sourceIndexes[i]).isConstant()) { + constants++; + } + } + if (constants == 0) { + return; + } + if (constants == count) { + sort = null; + return; + } + int size = count - constants; + int[] indexes = new int[size]; + int[] sortTypes = new int[size]; + int[] sourceSortTypes = sort.getSortTypes(); + ArrayList orderList = sort.getOrderList(); + for (int i = 0, j = 0; j < size; i++) { + if (!expressions.get(sourceIndexes[i]).isConstant()) { + indexes[j] = sourceIndexes[i]; + sortTypes[j] = sourceSortTypes[i]; + j++; + } else { + orderList.remove(j); + } + } + sort = new SortOrder(session, indexes, sortTypes, orderList); + } + + @Override + public int getType() { + return CommandInterface.SELECT; + } + + public void setOffset(Expression offset) { + this.offsetExpr = offset; + } + + public Expression getOffset() { + return offsetExpr; + } + + public void setFetch(Expression fetch) { + this.fetchExpr = fetch; + } + + public Expression getFetch() { + return fetchExpr; + } + + public void setFetchPercent(boolean fetchPercent) { + this.fetchPercent = fetchPercent; + } + + public boolean isFetchPercent() { + return fetchPercent; + } + + public void setWithTies(boolean withTies) { + this.withTies = withTies; + } + + public boolean isWithTies() { + return withTies; + } + + /** + * Add a parameter to the parameter list. + * + * @param param the parameter to add + */ + void addParameter(Parameter param) { + if (parameters == null) { + parameters = Utils.newSmallArrayList(); + } + parameters.add(param); + } + + public final long getMaxDataModificationId() { + ExpressionVisitor visitor = ExpressionVisitor.getMaxModificationIdVisitor(); + isEverything(visitor); + return Math.max(visitor.getMaxDataModificationId(), session.getSnapshotDataModificationId()); + } + + /** + * Appends ORDER BY, OFFSET, and FETCH clauses to the plan. + * + * @param builder query plan string builder. + * @param sqlFlags formatting flags + * @param expressions the array of expressions + */ + void appendEndOfQueryToSQL(StringBuilder builder, int sqlFlags, Expression[] expressions) { + if (sort != null) { + sort.getSQL(builder.append("\nORDER BY "), expressions, visibleColumnCount, sqlFlags); + } else if (orderList != null) { + builder.append("\nORDER BY "); + for (int i = 0, l = orderList.size(); i < l; i++) { + if (i > 0) { + builder.append(", "); + } + orderList.get(i).getSQL(builder, sqlFlags); + } + } + if (offsetExpr != null) { + String count = offsetExpr.getSQL(sqlFlags, WITHOUT_PARENTHESES); + builder.append("\nOFFSET ").append(count).append("1".equals(count) ? " ROW" : " ROWS"); + } + if (fetchExpr != null) { + builder.append("\nFETCH ").append(offsetExpr != null ? "NEXT" : "FIRST"); + String count = fetchExpr.getSQL(sqlFlags, WITHOUT_PARENTHESES); + boolean withCount = fetchPercent || !"1".equals(count); + if (withCount) { + builder.append(' ').append(count); + if (fetchPercent) { + builder.append(" PERCENT"); + } + } + builder.append(!withCount ? " ROW" : " ROWS") + .append(withTies ? " WITH TIES" : " ONLY"); + } + } + + /** + * Evaluates OFFSET and FETCH expressions. + * + * @param maxRows + * additional limit + * @return the evaluated values + */ + OffsetFetch getOffsetFetch(long maxRows) { + long offset; + if (offsetExpr != null) { + Value v = offsetExpr.getValue(session); + if (v == ValueNull.INSTANCE || (offset = v.getLong()) < 0) { + throw DbException.getInvalidValueException("result OFFSET", v); + } + } else { + offset = 0; + } + long fetch = maxRows == 0 ? -1 : maxRows; + if (fetchExpr != null) { + Value v = fetchExpr.getValue(session); + long l; + if (v == ValueNull.INSTANCE || (l = v.getLong()) < 0) { + throw DbException.getInvalidValueException("result FETCH", v); + } + fetch = fetch < 0 ? l : Math.min(l, fetch); + } + boolean fetchPercent = this.fetchPercent; + if (fetchPercent) { + if (fetch > 100) { + throw DbException.getInvalidValueException("result FETCH PERCENT", fetch); + } + // 0 PERCENT means 0 + if (fetch == 0) { + fetchPercent = false; + } + } + return new OffsetFetch(offset, fetch, fetchPercent); + } + + /** + * Applies limits, if any, to a result and makes it ready for value + * retrieval. + * + * @param result + * the result + * @param offset + * OFFSET value + * @param fetch + * FETCH value + * @param fetchPercent + * whether FETCH value is a PERCENT value + * @param target + * target result or null + * @return the result or null + */ + LocalResult finishResult(LocalResult result, long offset, long fetch, boolean fetchPercent, ResultTarget target) { + if (offset != 0) { + result.setOffset(offset); + } + if (fetch >= 0) { + result.setLimit(fetch); + result.setFetchPercent(fetchPercent); + if (withTies) { + result.setWithTies(sort); + } + } + result.done(); + if (randomAccessResult && !distinct) { + result = convertToDistinct(result); + } + if (target != null) { + while (result.next()) { + target.addRow(result.currentRow()); + } + result.close(); + return null; + } + return result; + } + + /** + * Convert a result into a distinct result, using the current columns. + * + * @param result the source + * @return the distinct result + */ + LocalResult convertToDistinct(ResultInterface result) { + LocalResult distinctResult = new LocalResult(session, expressionArray, visibleColumnCount, resultColumnCount); + distinctResult.setDistinct(); + result.reset(); + while (result.next()) { + distinctResult.addRow(result.currentRow()); + } + result.close(); + distinctResult.done(); + return distinctResult; + } + + /** + * Converts this query to a table or a view. + * + * @param alias alias name for the view + * @param columnTemplates column templates, or {@code null} + * @param parameters the parameters + * @param forCreateView if true, a system session will be used for the view + * @param topQuery the top level query + * @return the table or the view + */ + public Table toTable(String alias, Column[] columnTemplates, ArrayList parameters, + boolean forCreateView, Query topQuery) { + setParameterList(new ArrayList<>(parameters)); + if (!checkInit) { + init(); + } + return new DerivedTable(forCreateView ? session.getDatabase().getSystemSession() : session, alias, + columnTemplates, this, topQuery); + } + + @Override + public void collectDependencies(HashSet dependencies) { + ExpressionVisitor visitor = ExpressionVisitor.getDependenciesVisitor(dependencies); + isEverything(visitor); + } + + /** + * Check if this query will always return the same value and has no side + * effects. + * + * @return if this query will always return the same value and has no side + * effects. + */ + public boolean isConstantQuery() { + return !hasOrder() && (offsetExpr == null || offsetExpr.isConstant()) + && (fetchExpr == null || fetchExpr.isConstant()); + } + + /** + * If this query is determined as a single-row query, returns a replacement + * expression. + * + * @return the expression, or {@code null} + */ + public Expression getIfSingleRow() { + return null; + } + +} diff --git a/h2/src/main/org/h2/command/query/QueryOrderBy.java b/h2/src/main/org/h2/command/query/QueryOrderBy.java new file mode 100644 index 0000000..8606f30 --- /dev/null +++ b/h2/src/main/org/h2/command/query/QueryOrderBy.java @@ -0,0 +1,44 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.query; + +import org.h2.expression.Expression; +import org.h2.result.SortOrder; + +/** + * Describes one element of the ORDER BY clause of a query. + */ +public class QueryOrderBy { + + /** + * The order by expression. + */ + public Expression expression; + + /** + * The column index expression. This can be a column index number (1 meaning + * the first column of the select list) or a parameter (the parameter is a + * number representing the column index number). + */ + public Expression columnIndexExpr; + + /** + * Sort type for this column. + */ + public int sortType; + + /** + * Appends the order by expression to the specified builder. + * + * @param builder the string builder + * @param sqlFlags formatting flags + */ + public void getSQL(StringBuilder builder, int sqlFlags) { + (expression != null ? expression : columnIndexExpr).getUnenclosedSQL(builder, sqlFlags); + SortOrder.typeToString(builder, sortType); + } + +} diff --git a/h2/src/main/org/h2/command/query/Select.java b/h2/src/main/org/h2/command/query/Select.java new file mode 100644 index 0000000..c1795ec --- /dev/null +++ b/h2/src/main/org/h2/command/query/Select.java @@ -0,0 +1,1924 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.query; + +import static org.h2.expression.Expression.WITHOUT_PARENTHESES; +import static org.h2.util.HasSQL.ADD_PLAN_INFORMATION; +import static org.h2.util.HasSQL.DEFAULT_SQL_FLAGS; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map.Entry; +import org.h2.api.ErrorCode; +import org.h2.api.Trigger; +import org.h2.engine.Constants; +import org.h2.engine.Database; +import org.h2.engine.Mode.ExpressionNames; +import org.h2.engine.SessionLocal; +import org.h2.expression.Alias; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.expression.ExpressionList; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.Parameter; +import org.h2.expression.Wildcard; +import org.h2.expression.analysis.DataAnalysisOperation; +import org.h2.expression.analysis.Window; +import org.h2.expression.condition.Comparison; +import org.h2.expression.condition.ConditionAndOr; +import org.h2.expression.condition.ConditionLocalAndGlobal; +import org.h2.expression.function.CoalesceFunction; +import org.h2.index.Cursor; +import org.h2.index.Index; +import org.h2.index.QueryExpressionIndex; +import org.h2.message.DbException; +import org.h2.mode.DefaultNullOrdering; +import org.h2.result.LazyResult; +import org.h2.result.LocalResult; +import org.h2.result.ResultInterface; +import org.h2.result.ResultTarget; +import org.h2.result.Row; +import org.h2.result.SearchRow; +import org.h2.result.SortOrder; +import org.h2.table.Column; +import org.h2.table.ColumnResolver; +import org.h2.table.IndexColumn; +import org.h2.table.Table; +import org.h2.table.TableFilter; +import org.h2.table.TableType; +import org.h2.table.TableView; +import org.h2.util.ParserUtil; +import org.h2.util.StringUtils; +import org.h2.util.Utils; +import org.h2.value.DataType; +import org.h2.value.Value; +import org.h2.value.ValueRow; + +/** + * This class represents a simple SELECT statement. + * + * For each select statement, + * visibleColumnCount <= distinctColumnCount <= expressionCount. + * The expression list count could include ORDER BY and GROUP BY expressions + * that are not in the select list. + * + * The call sequence is init(), mapColumns() if it's a subquery, prepare(). + * + * @author Thomas Mueller + * @author Joel Turkel (Group sorted query) + */ +public class Select extends Query { + + /** + * The main (top) table filter. + */ + TableFilter topTableFilter; + + private final ArrayList filters = Utils.newSmallArrayList(); + private final ArrayList topFilters = Utils.newSmallArrayList(); + + /** + * Parent select for selects in table filters. + */ + private Select parentSelect; + + /** + * WHERE condition. + */ + private Expression condition; + + /** + * HAVING condition. + */ + private Expression having; + + /** + * QUALIFY condition. + */ + private Expression qualify; + + /** + * {@code DISTINCT ON(...)} expressions. + */ + private Expression[] distinctExpressions; + + private int[] distinctIndexes; + + private ArrayList group; + + /** + * The indexes of the group-by columns. + */ + int[] groupIndex; + + /** + * Whether a column in the expression list is part of a group-by. + */ + boolean[] groupByExpression; + + /** + * Grouped data for aggregates. + */ + SelectGroups groupData; + + private int havingIndex; + + private int qualifyIndex; + + private int[] groupByCopies; + + /** + * Whether this SELECT is an explicit table (TABLE tableName). It is used in + * {@link #getPlanSQL(int)} to generate SQL similar to original query. + */ + private boolean isExplicitTable; + + /** + * This flag is set when SELECT statement contains (non-window) aggregate + * functions, GROUP BY clause or HAVING clause. + */ + boolean isGroupQuery; + private boolean isGroupSortedQuery; + private boolean isWindowQuery; + private boolean isForUpdate; + private double cost; + private boolean isQuickAggregateQuery, isDistinctQuery; + private boolean sortUsingIndex; + + private boolean isGroupWindowStage2; + + private HashMap windows; + + public Select(SessionLocal session, Select parentSelect) { + super(session); + this.parentSelect = parentSelect; + } + + @Override + public boolean isUnion() { + return false; + } + + /** + * Add a table to the query. + * + * @param filter the table to add + * @param isTop if the table can be the first table in the query plan + */ + public void addTableFilter(TableFilter filter, boolean isTop) { + // Oracle doesn't check on duplicate aliases + // String alias = filter.getAlias(); + // if (filterNames.contains(alias)) { + // throw Message.getSQLException( + // ErrorCode.DUPLICATE_TABLE_ALIAS, alias); + // } + // filterNames.add(alias); + filters.add(filter); + if (isTop) { + topFilters.add(filter); + } + } + + public ArrayList getTopFilters() { + return topFilters; + } + + public void setExpressions(ArrayList expressions) { + this.expressions = expressions; + } + + /** + * Convert this SELECT to an explicit table (TABLE tableName). + */ + public void setExplicitTable() { + setWildcard(); + isExplicitTable = true; + } + + /** + * Sets a wildcard expression as in "SELECT * FROM TEST". + */ + public void setWildcard() { + expressions = new ArrayList<>(1); + expressions.add(new Wildcard(null, null)); + } + + /** + * Set when SELECT statement contains (non-window) aggregate functions, + * GROUP BY clause or HAVING clause. + */ + public void setGroupQuery() { + isGroupQuery = true; + } + + /** + * Called if this query contains window functions. + */ + public void setWindowQuery() { + isWindowQuery = true; + } + + public void setGroupBy(ArrayList group) { + this.group = group; + } + + public ArrayList getGroupBy() { + return group; + } + + /** + * Get the group data if there is currently a group-by active. + * + * @param window is this a window function + * @return the grouped data + */ + public SelectGroups getGroupDataIfCurrent(boolean window) { + return groupData != null && (window || groupData.isCurrentGroup()) ? groupData : null; + } + + /** + * Set the distinct flag. + */ + public void setDistinct() { + if (distinctExpressions != null) { + throw DbException.getUnsupportedException("DISTINCT ON together with DISTINCT"); + } + distinct = true; + } + + /** + * Set the DISTINCT ON expressions. + * + * @param distinctExpressions array of expressions + */ + public void setDistinct(Expression[] distinctExpressions) { + if (distinct) { + throw DbException.getUnsupportedException("DISTINCT ON together with DISTINCT"); + } + this.distinctExpressions = distinctExpressions; + } + + @Override + public boolean isAnyDistinct() { + return distinct || distinctExpressions != null; + } + + /** + * Adds a named window definition. + * + * @param name name + * @param window window definition + * @return true if a new definition was added, false if old definition was replaced + */ + public boolean addWindow(String name, Window window) { + if (windows == null) { + windows = new HashMap<>(); + } + return windows.put(name, window) == null; + } + + /** + * Returns a window with specified name, or null. + * + * @param name name of the window + * @return the window with specified name, or null + */ + public Window getWindow(String name) { + return windows != null ? windows.get(name) : null; + } + + /** + * Add a condition to the list of conditions. + * + * @param cond the condition to add + */ + public void addCondition(Expression cond) { + if (condition == null) { + condition = cond; + } else { + condition = new ConditionAndOr(ConditionAndOr.AND, cond, condition); + } + } + + public Expression getCondition() { + return condition; + } + + private LazyResult queryGroupSorted(int columnCount, ResultTarget result, long offset, boolean quickOffset) { + LazyResultGroupSorted lazyResult = new LazyResultGroupSorted(expressionArray, columnCount); + skipOffset(lazyResult, offset, quickOffset); + if (result == null) { + return lazyResult; + } + while (lazyResult.next()) { + result.addRow(lazyResult.currentRow()); + } + return null; + } + + /** + * Create a row with the current values, for queries with group-sort. + * + * @param keyValues the key values + * @param columnCount the number of columns + * @return the row + */ + Value[] createGroupSortedRow(Value[] keyValues, int columnCount) { + Value[] row = constructGroupResultRow(keyValues, columnCount); + if (isHavingNullOrFalse(row)) { + return null; + } + return rowForResult(row, columnCount); + } + + /** + * Removes HAVING and QUALIFY columns from the row. + * + * @param row + * the complete row + * @param columnCount + * the number of columns to keep + * @return the same or the truncated row + */ + private Value[] rowForResult(Value[] row, int columnCount) { + if (columnCount == resultColumnCount) { + return row; + } + return Arrays.copyOf(row, resultColumnCount); + } + + private boolean isHavingNullOrFalse(Value[] row) { + return havingIndex >= 0 && !row[havingIndex].isTrue(); + } + + private Index getGroupSortedIndex() { + if (groupIndex == null || groupByExpression == null) { + return null; + } + ArrayList indexes = topTableFilter.getTable().getIndexes(); + if (indexes != null) { + for (Index index : indexes) { + if (index.getIndexType().isScan()) { + continue; + } + if (index.getIndexType().isHash()) { + // does not allow scanning entries + continue; + } + if (isGroupSortedIndex(topTableFilter, index)) { + return index; + } + } + } + return null; + } + + private boolean isGroupSortedIndex(TableFilter tableFilter, Index index) { + // check that all the GROUP BY expressions are part of the index + Column[] indexColumns = index.getColumns(); + // also check that the first columns in the index are grouped + boolean[] grouped = new boolean[indexColumns.length]; + outerLoop: + for (int i = 0, size = expressions.size(); i < size; i++) { + if (!groupByExpression[i]) { + continue; + } + Expression expr = expressions.get(i).getNonAliasExpression(); + if (!(expr instanceof ExpressionColumn)) { + return false; + } + ExpressionColumn exprCol = (ExpressionColumn) expr; + for (int j = 0; j < indexColumns.length; ++j) { + if (tableFilter == exprCol.getTableFilter()) { + if (indexColumns[j].equals(exprCol.getColumn())) { + grouped[j] = true; + continue outerLoop; + } + } + } + // We didn't find a matching index column + // for one group by expression + return false; + } + // check that the first columns in the index are grouped + // good: index(a, b, c); group by b, a + // bad: index(a, b, c); group by a, c + for (int i = 1; i < grouped.length; i++) { + if (!grouped[i - 1] && grouped[i]) { + return false; + } + } + return true; + } + + boolean isConditionMetForUpdate() { + if (isConditionMet()) { + int count = filters.size(); + boolean notChanged = true; + for (int i = 0; i < count; i++) { + TableFilter tableFilter = filters.get(i); + if (!tableFilter.isJoinOuter() && !tableFilter.isJoinOuterIndirect()) { + Row row = tableFilter.get(); + Table table = tableFilter.getTable(); + // Views, function tables, links, etc. do not support locks + if (table.isRowLockable()) { + Row lockedRow = table.lockRow(session, row); + if (lockedRow == null) { + return false; + } + if (!row.hasSharedData(lockedRow)) { + tableFilter.set(lockedRow); + notChanged = false; + } + } + } + } + return notChanged || isConditionMet(); + } + return false; + } + + boolean isConditionMet() { + return condition == null || condition.getBooleanValue(session); + } + + private void queryWindow(int columnCount, LocalResult result, long offset, boolean quickOffset) { + initGroupData(columnCount); + try { + gatherGroup(columnCount, DataAnalysisOperation.STAGE_WINDOW); + processGroupResult(columnCount, result, offset, quickOffset, false); + } finally { + groupData.reset(); + } + } + + private void queryGroupWindow(int columnCount, LocalResult result, long offset, boolean quickOffset) { + initGroupData(columnCount); + try { + gatherGroup(columnCount, DataAnalysisOperation.STAGE_GROUP); + try { + isGroupWindowStage2 = true; + while (groupData.next() != null) { + if (havingIndex < 0 || expressions.get(havingIndex).getBooleanValue(session)) { + updateAgg(columnCount, DataAnalysisOperation.STAGE_WINDOW); + } else { + groupData.remove(); + } + } + groupData.done(); + processGroupResult(columnCount, result, offset, quickOffset, /* Having was performed earlier */ false); + } finally { + isGroupWindowStage2 = false; + } + } finally { + groupData.reset(); + } + } + + private void queryGroup(int columnCount, LocalResult result, long offset, boolean quickOffset) { + initGroupData(columnCount); + try { + gatherGroup(columnCount, DataAnalysisOperation.STAGE_GROUP); + processGroupResult(columnCount, result, offset, quickOffset, true); + } finally { + groupData.reset(); + } + } + + private void initGroupData(int columnCount) { + if (groupData == null) { + setGroupData(SelectGroups.getInstance(session, expressions, isGroupQuery, groupIndex)); + } else { + updateAgg(columnCount, DataAnalysisOperation.STAGE_RESET); + } + groupData.reset(); + } + + void setGroupData(final SelectGroups groupData) { + this.groupData = groupData; + topTableFilter.visit(f -> { + Select s = f.getSelect(); + if (s != null) { + s.groupData = groupData; + } + }); + } + + private void gatherGroup(int columnCount, int stage) { + long rowNumber = 0; + setCurrentRowNumber(0); + while (topTableFilter.next()) { + setCurrentRowNumber(rowNumber + 1); + if (isForUpdate ? isConditionMetForUpdate() : isConditionMet()) { + rowNumber++; + groupData.nextSource(); + updateAgg(columnCount, stage); + } + } + groupData.done(); + } + + + /** + * Update any aggregate expressions with the query stage. + * @param columnCount number of columns + * @param stage see STAGE_RESET/STAGE_GROUP/STAGE_WINDOW in DataAnalysisOperation + */ + void updateAgg(int columnCount, int stage) { + for (int i = 0; i < columnCount; i++) { + if ((groupByExpression == null || !groupByExpression[i]) + && (groupByCopies == null || groupByCopies[i] < 0)) { + Expression expr = expressions.get(i); + expr.updateAggregate(session, stage); + } + } + } + + private void processGroupResult(int columnCount, LocalResult result, long offset, boolean quickOffset, + boolean withHaving) { + for (ValueRow currentGroupsKey; (currentGroupsKey = groupData.next()) != null;) { + Value[] row = constructGroupResultRow(currentGroupsKey.getList(), columnCount); + if (withHaving && isHavingNullOrFalse(row)) { + continue; + } + if (qualifyIndex >= 0 && !row[qualifyIndex].isTrue()) { + continue; + } + if (quickOffset && offset > 0) { + offset--; + continue; + } + result.addRow(rowForResult(row, columnCount)); + } + } + + private Value[] constructGroupResultRow(Value[] keyValues, int columnCount) { + Value[] row = new Value[columnCount]; + if (groupIndex != null) { + for (int i = 0, l = groupIndex.length; i < l; i++) { + row[groupIndex[i]] = keyValues[i]; + } + } + for (int i = 0; i < columnCount; i++) { + if (groupByExpression != null && groupByExpression[i]) { + continue; + } + if (groupByCopies != null) { + int original = groupByCopies[i]; + if (original >= 0) { + row[i] = row[original]; + continue; + } + } + row[i] = expressions.get(i).getValue(session); + } + return row; + } + + /** + * Get the index that matches the ORDER BY list, if one exists. This is to + * avoid running a separate ORDER BY if an index can be used. This is + * specially important for large result sets, if only the first few rows are + * important (LIMIT is used) + * + * @return the index if one is found + */ + private Index getSortIndex() { + if (sort == null) { + return null; + } + ArrayList sortColumns = Utils.newSmallArrayList(); + int[] queryColumnIndexes = sort.getQueryColumnIndexes(); + int queryIndexesLength = queryColumnIndexes.length; + int[] sortIndex = new int[queryIndexesLength]; + for (int i = 0, j = 0; i < queryIndexesLength; i++) { + int idx = queryColumnIndexes[i]; + if (idx < 0 || idx >= expressions.size()) { + throw DbException.getInvalidValueException("ORDER BY", idx + 1); + } + Expression expr = expressions.get(idx); + expr = expr.getNonAliasExpression(); + if (expr.isConstant()) { + continue; + } + if (!(expr instanceof ExpressionColumn)) { + return null; + } + ExpressionColumn exprCol = (ExpressionColumn) expr; + if (exprCol.getTableFilter() != topTableFilter) { + return null; + } + sortColumns.add(exprCol.getColumn()); + sortIndex[j++] = i; + } + Column[] sortCols = sortColumns.toArray(new Column[0]); + if (sortCols.length == 0) { + // sort just on constants - can use scan index + return topTableFilter.getTable().getScanIndex(session); + } + ArrayList list = topTableFilter.getTable().getIndexes(); + if (list != null) { + int[] sortTypes = sort.getSortTypesWithNullOrdering(); + DefaultNullOrdering defaultNullOrdering = session.getDatabase().getDefaultNullOrdering(); + loop: for (Index index : list) { + if (index.getCreateSQL() == null) { + // can't use the scan index + continue; + } + if (index.getIndexType().isHash()) { + continue; + } + IndexColumn[] indexCols = index.getIndexColumns(); + if (indexCols.length < sortCols.length) { + continue; + } + for (int j = 0; j < sortCols.length; j++) { + // the index and the sort order must start + // with the exact same columns + IndexColumn idxCol = indexCols[j]; + Column sortCol = sortCols[j]; + if (idxCol.column != sortCol) { + continue loop; + } + int sortType = sortTypes[sortIndex[j]]; + if (sortCol.isNullable() + ? defaultNullOrdering.addExplicitNullOrdering(idxCol.sortType) != sortType + : (idxCol.sortType & SortOrder.DESCENDING) != (sortType & SortOrder.DESCENDING)) { + continue loop; + } + } + return index; + } + } + if (sortCols.length == 1 && sortCols[0].getColumnId() == -1) { + // special case: order by _ROWID_ + Index index = topTableFilter.getTable().getScanIndex(session); + if (index.isRowIdIndex()) { + return index; + } + } + return null; + } + + private void queryDistinct(ResultTarget result, long offset, long limitRows, boolean withTies, + boolean quickOffset) { + if (limitRows > 0 && offset > 0) { + limitRows += offset; + if (limitRows < 0) { + // Overflow + limitRows = Long.MAX_VALUE; + } + } + long rowNumber = 0; + setCurrentRowNumber(0); + Index index = topTableFilter.getIndex(); + SearchRow first = null; + int columnIndex = index.getColumns()[0].getColumnId(); + if (!quickOffset) { + offset = 0; + } + while (true) { + setCurrentRowNumber(++rowNumber); + Cursor cursor = index.findNext(session, first, null); + if (!cursor.next()) { + break; + } + SearchRow found = cursor.getSearchRow(); + Value value = found.getValue(columnIndex); + if (first == null) { + first = index.getRowFactory().createRow(); + } + first.setValue(columnIndex, value); + if (offset > 0) { + offset--; + continue; + } + result.addRow(value); + if ((sort == null || sortUsingIndex) && limitRows > 0 && rowNumber >= limitRows && !withTies) { + break; + } + } + } + + private LazyResult queryFlat(int columnCount, ResultTarget result, long offset, long limitRows, boolean withTies, + boolean quickOffset) { + if (limitRows > 0 && offset > 0 && !quickOffset) { + limitRows += offset; + if (limitRows < 0) { + // Overflow + limitRows = Long.MAX_VALUE; + } + } + LazyResultQueryFlat lazyResult = new LazyResultQueryFlat(expressionArray, columnCount, isForUpdate); + skipOffset(lazyResult, offset, quickOffset); + if (result == null) { + return lazyResult; + } + if (limitRows < 0 || sort != null && !sortUsingIndex || withTies && !quickOffset) { + limitRows = Long.MAX_VALUE; + } + Value[] row = null; + while (result.getRowCount() < limitRows && lazyResult.next()) { + row = lazyResult.currentRow(); + result.addRow(row); + } + if (limitRows != Long.MAX_VALUE && withTies && sort != null && row != null) { + Value[] expected = row; + while (lazyResult.next()) { + row = lazyResult.currentRow(); + if (sort.compare(expected, row) != 0) { + break; + } + result.addRow(row); + } + result.limitsWereApplied(); + } + return null; + } + + private static void skipOffset(LazyResultSelect lazyResult, long offset, boolean quickOffset) { + if (quickOffset) { + while (offset > 0 && lazyResult.skip()) { + offset--; + } + } + } + + private void queryQuick(int columnCount, ResultTarget result, boolean skipResult) { + Value[] row = new Value[columnCount]; + for (int i = 0; i < columnCount; i++) { + Expression expr = expressions.get(i); + row[i] = expr.getValue(session); + } + if (!skipResult) { + result.addRow(row); + } + } + + @Override + protected ResultInterface queryWithoutCache(long maxRows, ResultTarget target) { + disableLazyForJoinSubqueries(topTableFilter); + OffsetFetch offsetFetch = getOffsetFetch(maxRows); + long offset = offsetFetch.offset; + long fetch = offsetFetch.fetch; + boolean fetchPercent = offsetFetch.fetchPercent; + boolean lazy = session.isLazyQueryExecution() && + target == null && !isForUpdate && !isQuickAggregateQuery && + fetch != 0 && !fetchPercent && !withTies && offset == 0 && isReadOnly(); + int columnCount = expressions.size(); + LocalResult result = null; + if (!lazy && (target == null || + !session.getDatabase().getSettings().optimizeInsertFromSelect)) { + result = createLocalResult(result); + } + // Do not add rows before OFFSET to result if possible + boolean quickOffset = !fetchPercent; + if (sort != null && (!sortUsingIndex || isAnyDistinct())) { + result = createLocalResult(result); + result.setSortOrder(sort); + if (!sortUsingIndex) { + quickOffset = false; + } + } + if (distinct) { + if (!isDistinctQuery) { + quickOffset = false; + result = createLocalResult(result); + result.setDistinct(); + } + } else if (distinctExpressions != null) { + quickOffset = false; + result = createLocalResult(result); + result.setDistinct(distinctIndexes); + } + if (isWindowQuery || isGroupQuery && !isGroupSortedQuery) { + result = createLocalResult(result); + } + if (!lazy && (fetch >= 0 || offset > 0)) { + result = createLocalResult(result); + } + topTableFilter.startQuery(session); + topTableFilter.reset(); + topTableFilter.lock(session); + ResultTarget to = result != null ? result : target; + lazy &= to == null; + LazyResult lazyResult = null; + if (fetch != 0) { + // Cannot apply limit now if percent is specified + long limit = fetchPercent ? -1 : fetch; + if (isQuickAggregateQuery) { + queryQuick(columnCount, to, quickOffset && offset > 0); + } else if (isWindowQuery) { + if (isGroupQuery) { + queryGroupWindow(columnCount, result, offset, quickOffset); + } else { + queryWindow(columnCount, result, offset, quickOffset); + } + } else if (isGroupQuery) { + if (isGroupSortedQuery) { + lazyResult = queryGroupSorted(columnCount, to, offset, quickOffset); + } else { + queryGroup(columnCount, result, offset, quickOffset); + } + } else if (isDistinctQuery) { + queryDistinct(to, offset, limit, withTies, quickOffset); + } else { + lazyResult = queryFlat(columnCount, to, offset, limit, withTies, quickOffset); + } + if (quickOffset) { + offset = 0; + } + } + assert lazy == (lazyResult != null) : lazy; + if (lazyResult != null) { + if (fetch > 0) { + lazyResult.setLimit(fetch); + } + if (randomAccessResult) { + return convertToDistinct(lazyResult); + } else { + return lazyResult; + } + } + if (result != null) { + return finishResult(result, offset, fetch, fetchPercent, target); + } + return null; + } + + private void disableLazyForJoinSubqueries(final TableFilter top) { + if (session.isLazyQueryExecution()) { + top.visit(f -> { + if (f != top && f.getTable().getTableType() == TableType.VIEW) { + QueryExpressionIndex idx = (QueryExpressionIndex) f.getIndex(); + if (idx != null && idx.getQuery() != null) { + idx.getQuery().setNeverLazy(true); + } + } + }); + } + } + + private LocalResult createLocalResult(LocalResult old) { + return old != null ? old : new LocalResult(session, expressionArray, visibleColumnCount, resultColumnCount); + } + + private void expandColumnList() { + // the expressions may change within the loop + for (int i = 0; i < expressions.size();) { + Expression expr = expressions.get(i); + if (!(expr instanceof Wildcard)) { + i++; + continue; + } + expressions.remove(i); + Wildcard w = (Wildcard) expr; + String tableAlias = w.getTableAlias(); + boolean hasExceptColumns = w.getExceptColumns() != null; + HashMap exceptTableColumns = null; + if (tableAlias == null) { + if (hasExceptColumns) { + for (TableFilter filter : filters) { + w.mapColumns(filter, 1, Expression.MAP_INITIAL); + } + exceptTableColumns = w.mapExceptColumns(); + } + for (TableFilter filter : filters) { + i = expandColumnList(filter, i, false, exceptTableColumns); + } + } else { + Database db = session.getDatabase(); + String schemaName = w.getSchemaName(); + TableFilter filter = null; + for (TableFilter f : filters) { + if (db.equalsIdentifiers(tableAlias, f.getTableAlias())) { + if (schemaName == null || db.equalsIdentifiers(schemaName, f.getSchemaName())) { + if (hasExceptColumns) { + w.mapColumns(f, 1, Expression.MAP_INITIAL); + exceptTableColumns = w.mapExceptColumns(); + } + filter = f; + break; + } + } + } + if (filter == null) { + throw DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, tableAlias); + } + i = expandColumnList(filter, i, true, exceptTableColumns); + } + } + } + + private int expandColumnList(TableFilter filter, int index, boolean forAlias, + HashMap except) { + String schema = filter.getSchemaName(); + String alias = filter.getTableAlias(); + if (forAlias) { + for (Column c : filter.getTable().getColumns()) { + index = addExpandedColumn(filter, index, except, schema, alias, c); + } + } else { + LinkedHashMap commonJoinColumns = filter.getCommonJoinColumns(); + if (commonJoinColumns != null) { + TableFilter replacementFilter = filter.getCommonJoinColumnsFilter(); + String replacementSchema = replacementFilter.getSchemaName(); + String replacementAlias = replacementFilter.getTableAlias(); + for (Entry entry : commonJoinColumns.entrySet()) { + Column left = entry.getKey(), right = entry.getValue(); + if (!filter.isCommonJoinColumnToExclude(right) + && (except == null || except.remove(left) == null && except.remove(right) == null)) { + Database database = session.getDatabase(); + Expression e; + if (left == right + || DataType.hasTotalOrdering(left.getType().getValueType()) + && DataType.hasTotalOrdering(right.getType().getValueType())) { + e = new ExpressionColumn(database, replacementSchema, replacementAlias, + replacementFilter.getColumnName(right)); + } else { + e = new Alias(new CoalesceFunction(CoalesceFunction.COALESCE, + new ExpressionColumn(database, schema, alias, filter.getColumnName(left)), + new ExpressionColumn(database, replacementSchema, replacementAlias, + replacementFilter.getColumnName(right))), // + left.getName(), true); + } + expressions.add(index++, e); + } + } + } + for (Column c : filter.getTable().getColumns()) { + if (commonJoinColumns == null || !commonJoinColumns.containsKey(c)) { + if (!filter.isCommonJoinColumnToExclude(c)) { + index = addExpandedColumn(filter, index, except, schema, alias, c); + } + } + } + } + return index; + } + + private int addExpandedColumn(TableFilter filter, int index, HashMap except, + String schema, String alias, Column c) { + if ((except == null || except.remove(c) == null) && c.getVisible()) { + ExpressionColumn ec = new ExpressionColumn(session.getDatabase(), schema, alias, filter.getColumnName(c)); + expressions.add(index++, ec); + } + return index; + } + + @Override + public void init() { + if (checkInit) { + throw DbException.getInternalError(); + } + filters.sort(TableFilter.ORDER_IN_FROM_COMPARATOR); + expandColumnList(); + if ((visibleColumnCount = expressions.size()) > Constants.MAX_COLUMNS) { + throw DbException.get(ErrorCode.TOO_MANY_COLUMNS_1, "" + Constants.MAX_COLUMNS); + } + ArrayList expressionSQL; + if (distinctExpressions != null || orderList != null || group != null) { + expressionSQL = new ArrayList<>(visibleColumnCount); + for (int i = 0; i < visibleColumnCount; i++) { + Expression expr = expressions.get(i); + expr = expr.getNonAliasExpression(); + expressionSQL.add(expr.getSQL(DEFAULT_SQL_FLAGS, WITHOUT_PARENTHESES)); + } + } else { + expressionSQL = null; + } + if (distinctExpressions != null) { + BitSet set = new BitSet(); + for (Expression e : distinctExpressions) { + set.set(initExpression(expressionSQL, e, false, filters)); + } + int idx = 0, cnt = set.cardinality(); + distinctIndexes = new int[cnt]; + for (int i = 0; i < cnt; i++) { + idx = set.nextSetBit(idx); + distinctIndexes[i] = idx; + idx++; + } + } + if (orderList != null) { + initOrder(expressionSQL, isAnyDistinct(), filters); + } + resultColumnCount = expressions.size(); + if (having != null) { + expressions.add(having); + havingIndex = expressions.size() - 1; + having = null; + } else { + havingIndex = -1; + } + if (qualify != null) { + expressions.add(qualify); + qualifyIndex = expressions.size() - 1; + qualify = null; + } else { + qualifyIndex = -1; + } + + if (withTies && !hasOrder()) { + throw DbException.get(ErrorCode.WITH_TIES_WITHOUT_ORDER_BY); + } + + Database db = session.getDatabase(); + + // first the select list (visible columns), + // then 'ORDER BY' expressions, + // then 'HAVING' expressions, + // and 'GROUP BY' expressions at the end + if (group != null) { + int size = group.size(); + int expSize = expressionSQL.size(); + int fullExpSize = expressions.size(); + if (fullExpSize > expSize) { + expressionSQL.ensureCapacity(fullExpSize); + for (int i = expSize; i < fullExpSize; i++) { + expressionSQL.add(expressions.get(i).getSQL(DEFAULT_SQL_FLAGS, WITHOUT_PARENTHESES)); + } + } + groupIndex = new int[size]; + for (int i = 0; i < size; i++) { + Expression expr = group.get(i); + String sql = expr.getSQL(DEFAULT_SQL_FLAGS, WITHOUT_PARENTHESES); + int found = -1; + for (int j = 0; j < expSize; j++) { + String s2 = expressionSQL.get(j); + if (db.equalsIdentifiers(s2, sql)) { + found = mergeGroupByExpressions(db, j, expressionSQL, false); + break; + } + } + if (found < 0) { + // special case: GROUP BY a column alias + for (int j = 0; j < expSize; j++) { + Expression e = expressions.get(j); + if (db.equalsIdentifiers(sql, e.getAlias(session, j))) { + found = mergeGroupByExpressions(db, j, expressionSQL, true); + break; + } + sql = expr.getAlias(session, j); + if (db.equalsIdentifiers(sql, e.getAlias(session, j))) { + found = mergeGroupByExpressions(db, j, expressionSQL, true); + break; + } + } + } + if (found < 0) { + int index = expressions.size(); + groupIndex[i] = index; + expressions.add(expr); + } else { + groupIndex[i] = found; + } + } + checkUsed: if (groupByCopies != null) { + for (int i : groupByCopies) { + if (i >= 0) { + break checkUsed; + } + } + groupByCopies = null; + } + groupByExpression = new boolean[expressions.size()]; + for (int gi : groupIndex) { + groupByExpression[gi] = true; + } + group = null; + } + // map columns in select list and condition + for (TableFilter f : filters) { + mapColumns(f, 0); + } + mapCondition(havingIndex); + mapCondition(qualifyIndex); + checkInit = true; + } + + private void mapCondition(int index) { + if (index >= 0) { + Expression expr = expressions.get(index); + SelectListColumnResolver res = new SelectListColumnResolver(this); + expr.mapColumns(res, 0, Expression.MAP_INITIAL); + } + } + + private int mergeGroupByExpressions(Database db, int index, ArrayList expressionSQL, // + boolean scanPrevious) { + + /* + * -1: uniqueness of expression is not known yet + * + * -2: expression that is used as a source for a copy or does not have + * copies + * + * >=0: expression is a copy of expression at this index + */ + if (groupByCopies != null) { + int c = groupByCopies[index]; + if (c >= 0) { + return c; + } else if (c == -2) { + return index; + } + } else { + groupByCopies = new int[expressionSQL.size()]; + Arrays.fill(groupByCopies, -1); + } + String sql = expressionSQL.get(index); + if (scanPrevious) { + /* + * If expression was matched using an alias previous expressions may + * be identical. + */ + for (int i = 0; i < index; i++) { + if (db.equalsIdentifiers(sql, expressionSQL.get(i))) { + index = i; + break; + } + } + } + int l = expressionSQL.size(); + for (int i = index + 1; i < l; i++) { + if (db.equalsIdentifiers(sql, expressionSQL.get(i))) { + groupByCopies[i] = index; + } + } + groupByCopies[index] = -2; + return index; + } + + @Override + public void prepareExpressions() { + if (orderList != null) { + prepareOrder(orderList, expressions.size()); + } + ExpressionNames expressionNames = session.getMode().expressionNames; + if (expressionNames == ExpressionNames.ORIGINAL_SQL || expressionNames == ExpressionNames.POSTGRESQL_STYLE) { + optimizeExpressionsAndPreserveAliases(); + } else { + for (int i = 0; i < expressions.size(); i++) { + expressions.set(i, expressions.get(i).optimize(session)); + } + } + if (sort != null) { + cleanupOrder(); + } + if (condition != null) { + condition = condition.optimizeCondition(session); + } + 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 && + expressions.size() == 1 && condition == null) { + Expression expr = expressions.get(0); + expr = expr.getNonAliasExpression(); + if (expr instanceof ExpressionColumn) { + Column column = ((ExpressionColumn) expr).getColumn(); + int selectivity = column.getSelectivity(); + Index columnIndex = topTableFilter.getTable(). + getIndexForColumn(column, false, true); + if (columnIndex != null && + selectivity != Constants.SELECTIVITY_DEFAULT && + selectivity < 20) { + Index current = topTableFilter.getIndex(); + // if another index is faster + if (current == null || current.getIndexType().isScan() || columnIndex == current) { + topTableFilter.setIndex(columnIndex); + isDistinctQuery = true; + } + } + } + } + if (sort != null && !isQuickAggregateQuery && !isGroupQuery) { + Index index = getSortIndex(); + Index current = topTableFilter.getIndex(); + if (index != null && current != null) { + if (current.getIndexType().isScan() || current == index) { + topTableFilter.setIndex(index); + if (!topTableFilter.hasInComparisons()) { + // in(select ...) and in(1,2,3) may return the key in + // another order + sortUsingIndex = true; + } + } else if (index.getIndexColumns() != null + && index.getIndexColumns().length >= current + .getIndexColumns().length) { + IndexColumn[] sortColumns = index.getIndexColumns(); + IndexColumn[] currentColumns = current.getIndexColumns(); + boolean swapIndex = false; + for (int i = 0; i < currentColumns.length; i++) { + if (sortColumns[i].column != currentColumns[i].column) { + swapIndex = false; + break; + } + if (sortColumns[i].sortType != currentColumns[i].sortType) { + swapIndex = true; + } + } + if (swapIndex) { + topTableFilter.setIndex(index); + sortUsingIndex = true; + } + } + } + if (sortUsingIndex && isForUpdate && !topTableFilter.getIndex().isRowIdIndex()) { + sortUsingIndex = false; + } + } + if (!isQuickAggregateQuery && isGroupQuery) { + Index index = getGroupSortedIndex(); + if (index != null) { + Index current = topTableFilter.getIndex(); + if (current != null && (current.getIndexType().isScan() || current == index)) { + topTableFilter.setIndex(index); + isGroupSortedQuery = true; + } + } + } + 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); + } + expressions.set(i, e); + } + } + + @Override + public double getCost() { + return cost; + } + + @Override + public HashSet
getTables() { + HashSet
set = new HashSet<>(); + for (TableFilter filter : filters) { + set.add(filter.getTable()); + } + return set; + } + + @Override + public void fireBeforeSelectTriggers() { + for (TableFilter filter : filters) { + filter.getTable().fire(session, Trigger.SELECT, true); + } + } + + private double preparePlan(boolean parse) { + TableFilter[] topArray = topFilters.toArray(new TableFilter[0]); + for (TableFilter t : topArray) { + t.createIndexConditions(); + t.setFullCondition(condition); + } + + Optimizer optimizer = new Optimizer(topArray, condition, session); + optimizer.optimize(parse); + topTableFilter = optimizer.getTopFilter(); + double planCost = optimizer.getCost(); + + setEvaluatableRecursive(topTableFilter); + + if (!parse) { + topTableFilter.prepare(); + } + return planCost; + } + + private void setEvaluatableRecursive(TableFilter f) { + for (; f != null; f = f.getJoin()) { + f.setEvaluatable(f, true); + if (condition != null) { + condition.setEvaluatable(f, true); + } + TableFilter n = f.getNestedJoin(); + if (n != null) { + setEvaluatableRecursive(n); + } + Expression on = f.getJoinCondition(); + if (on != null) { + if (!on.isEverything(ExpressionVisitor.EVALUATABLE_VISITOR)) { + // need to check that all added are bound to a table + on = on.optimize(session); + if (!f.isJoinOuter() && !f.isJoinOuterIndirect()) { + f.removeJoinCondition(); + addCondition(on); + } + } + } + on = f.getFilterCondition(); + if (on != null) { + if (!on.isEverything(ExpressionVisitor.EVALUATABLE_VISITOR)) { + f.removeFilterCondition(); + addCondition(on); + } + } + // this is only important for subqueries, so they know + // the result columns are evaluatable + for (Expression e : expressions) { + e.setEvaluatable(f, true); + } + } + } + + @Override + public String getPlanSQL(int sqlFlags) { + // can not use the field sqlStatement because the parameter + // indexes may be incorrect: ? may be in fact ?2 for a subquery + // but indexes may be set manually as well + Expression[] exprList = expressions.toArray(new Expression[0]); + StringBuilder builder = new StringBuilder(); + for (TableFilter f : topFilters) { + Table t = f.getTable(); + TableView tableView = t instanceof TableView ? (TableView) t : null; + if (tableView != null && tableView.isRecursive() && tableView.isTableExpression()) { + + if (!tableView.isTemporary()) { + // skip the generation of plan SQL for this already recursive persistent CTEs, + // since using a with statement will re-create the common table expression + // views. + } else { + builder.append("WITH RECURSIVE "); + t.getSchema().getSQL(builder, sqlFlags).append('.'); + ParserUtil.quoteIdentifier(builder, t.getName(), sqlFlags).append('('); + Column.writeColumns(builder, t.getColumns(), sqlFlags); + builder.append(") AS "); + t.getSQL(builder, sqlFlags).append('\n'); + } + } + } + if (isExplicitTable) { + builder.append("TABLE "); + filters.get(0).getPlanSQL(builder, false, sqlFlags); + } else { + builder.append("SELECT"); + if (isAnyDistinct()) { + builder.append(" DISTINCT"); + if (distinctExpressions != null) { + Expression.writeExpressions(builder.append(" ON("), distinctExpressions, sqlFlags).append(')'); + } + } + for (int i = 0; i < visibleColumnCount; i++) { + if (i > 0) { + builder.append(','); + } + builder.append('\n'); + StringUtils.indent(builder, exprList[i].getSQL(sqlFlags, WITHOUT_PARENTHESES), 4, false); + } + TableFilter filter = topTableFilter; + if (filter == null) { + int count = topFilters.size(); + if (count != 1 || !topFilters.get(0).isNoFromClauseFilter()) { + builder.append("\nFROM "); + boolean isJoin = false; + for (int i = 0; i < count; i++) { + isJoin = getPlanFromFilter(builder, sqlFlags, topFilters.get(i), isJoin); + } + } + } else if (!filter.isNoFromClauseFilter()) { + getPlanFromFilter(builder.append("\nFROM "), sqlFlags, filter, false); + } + if (condition != null) { + getFilterSQL(builder, "\nWHERE ", condition, sqlFlags); + } + if (groupIndex != null) { + builder.append("\nGROUP BY "); + for (int i = 0, l = groupIndex.length; i < l; i++) { + if (i > 0) { + builder.append(", "); + } + exprList[groupIndex[i]].getNonAliasExpression().getUnenclosedSQL(builder, sqlFlags); + } + } else if (group != null) { + builder.append("\nGROUP BY "); + for (int i = 0, l = group.size(); i < l; i++) { + if (i > 0) { + builder.append(", "); + } + group.get(i).getUnenclosedSQL(builder, sqlFlags); + } + } else emptyGroupingSet: if (isGroupQuery && having == null && havingIndex < 0) { + for (int i = 0; i < visibleColumnCount; i++) { + if (containsAggregate(exprList[i])) { + break emptyGroupingSet; + } + } + builder.append("\nGROUP BY ()"); + } + getFilterSQL(builder, "\nHAVING ", exprList, having, havingIndex, sqlFlags); + getFilterSQL(builder, "\nQUALIFY ", exprList, qualify, qualifyIndex, sqlFlags); + } + appendEndOfQueryToSQL(builder, sqlFlags, exprList); + if (isForUpdate) { + builder.append("\nFOR UPDATE"); + } + if ((sqlFlags & ADD_PLAN_INFORMATION) != 0) { + if (isQuickAggregateQuery) { + builder.append("\n/* direct lookup */"); + } + if (isDistinctQuery) { + builder.append("\n/* distinct */"); + } + if (sortUsingIndex) { + builder.append("\n/* index sorted */"); + } + if (isGroupQuery) { + if (isGroupSortedQuery) { + builder.append("\n/* group sorted */"); + } + } + // builder.append("\n/* cost: " + cost + " */"); + } + return builder.toString(); + } + + private static boolean getPlanFromFilter(StringBuilder builder, int sqlFlags, TableFilter f, boolean isJoin) { + do { + if (isJoin) { + builder.append('\n'); + } + f.getPlanSQL(builder, isJoin, sqlFlags); + isJoin = true; + } while ((f = f.getJoin()) != null); + return isJoin; + } + + private static void getFilterSQL(StringBuilder builder, String sql, Expression[] exprList, Expression condition, + int conditionIndex, int sqlFlags) { + if (condition != null) { + getFilterSQL(builder, sql, condition, sqlFlags); + } else if (conditionIndex >= 0) { + getFilterSQL(builder, sql, exprList[conditionIndex], sqlFlags); + } + } + + private static void getFilterSQL(StringBuilder builder, String sql, Expression condition, int sqlFlags) { + condition.getUnenclosedSQL(builder.append(sql), sqlFlags); + } + + private static boolean containsAggregate(Expression expression) { + if (expression instanceof DataAnalysisOperation) { + if (((DataAnalysisOperation) expression).isAggregate()) { + return true; + } + } + for (int i = 0, l = expression.getSubexpressionCount(); i < l; i++) { + if (containsAggregate(expression.getSubexpression(i))) { + return true; + } + } + return false; + } + + public void setHaving(Expression having) { + this.having = having; + } + + public Expression getHaving() { + return having; + } + + public void setQualify(Expression qualify) { + this.qualify = qualify; + } + + public Expression getQualify() { + return qualify; + } + + public TableFilter getTopTableFilter() { + return topTableFilter; + } + + @Override + public void setForUpdate(boolean b) { + if (b && (isAnyDistinct() || isGroupQuery)) { + throw DbException.get(ErrorCode.FOR_UPDATE_IS_NOT_ALLOWED_IN_DISTINCT_OR_GROUPED_SELECT); + } + this.isForUpdate = b; + } + + @Override + public void mapColumns(ColumnResolver resolver, int level) { + for (Expression e : expressions) { + e.mapColumns(resolver, level, Expression.MAP_INITIAL); + } + if (condition != null) { + condition.mapColumns(resolver, level, Expression.MAP_INITIAL); + } + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + for (Expression e : expressions) { + e.setEvaluatable(tableFilter, b); + } + if (condition != null) { + condition.setEvaluatable(tableFilter, b); + } + } + + /** + * Check if this is an aggregate query with direct lookup, for example a + * query of the type SELECT COUNT(*) FROM TEST or + * SELECT MAX(ID) FROM TEST. + * + * @return true if a direct lookup is possible + */ + public boolean isQuickAggregateQuery() { + return isQuickAggregateQuery; + } + + /** + * Checks if this query is a group query. + * + * @return whether this query is a group query. + */ + public boolean isGroupQuery() { + return isGroupQuery; + } + + /** + * Checks if this query contains window functions. + * + * @return whether this query contains window functions + */ + public boolean isWindowQuery() { + return isWindowQuery; + } + + /** + * Checks if window stage of group window query is performed. If true, + * column resolver may not be used. + * + * @return true if window stage of group window query is performed + */ + public boolean isGroupWindowStage2() { + return isGroupWindowStage2; + } + + @Override + public void addGlobalCondition(Parameter param, int columnId, int comparisonType) { + addParameter(param); + Expression comp; + Expression col = expressions.get(columnId); + col = col.getNonAliasExpression(); + if (col.isEverything(ExpressionVisitor.QUERY_COMPARABLE_VISITOR)) { + comp = new Comparison(comparisonType, col, param, false); + } else { + // this condition will always evaluate to true, but need to + // add the parameter, so it can be set later + comp = new Comparison(Comparison.EQUAL_NULL_SAFE, param, param, false); + } + comp = comp.optimize(session); + if (isWindowQuery) { + qualify = addGlobalCondition(qualify, comp); + } else if (isGroupQuery) { + for (int i = 0; groupIndex != null && i < groupIndex.length; i++) { + if (groupIndex[i] == columnId) { + condition = addGlobalCondition(condition, comp); + return; + } + } + if (havingIndex >= 0) { + having = expressions.get(havingIndex); + } + having = addGlobalCondition(having, comp); + } else { + condition = addGlobalCondition(condition, comp); + } + } + + private static Expression addGlobalCondition(Expression condition, Expression additional) { + if (!(condition instanceof ConditionLocalAndGlobal)) { + return new ConditionLocalAndGlobal(condition, additional); + } + Expression oldLocal, oldGlobal; + if (condition.getSubexpressionCount() == 1) { + oldLocal = null; + oldGlobal = condition.getSubexpression(0); + } else { + oldLocal = condition.getSubexpression(0); + oldGlobal = condition.getSubexpression(1); + } + return new ConditionLocalAndGlobal(oldLocal, new ConditionAndOr(ConditionAndOr.AND, oldGlobal, additional)); + } + + @Override + public void updateAggregate(SessionLocal s, int stage) { + for (Expression e : expressions) { + e.updateAggregate(s, stage); + } + if (condition != null) { + condition.updateAggregate(s, stage); + } + if (having != null) { + having.updateAggregate(s, stage); + } + if (qualify != null) { + qualify.updateAggregate(s, stage); + } + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + switch (visitor.getType()) { + case ExpressionVisitor.DETERMINISTIC: { + if (isForUpdate) { + return false; + } + for (TableFilter f : filters) { + if (!f.getTable().isDeterministic()) { + return false; + } + } + break; + } + case ExpressionVisitor.SET_MAX_DATA_MODIFICATION_ID: { + for (TableFilter f : filters) { + long m = f.getTable().getMaxDataModificationId(); + visitor.addDataModificationId(m); + } + break; + } + case ExpressionVisitor.EVALUATABLE: { + if (!session.getDatabase().getSettings().optimizeEvaluatableSubqueries) { + return false; + } + break; + } + case ExpressionVisitor.GET_DEPENDENCIES: { + for (TableFilter f : filters) { + Table table = f.getTable(); + visitor.addDependency(table); + table.addDependencies(visitor.getDependencies()); + } + break; + } + default: + } + ExpressionVisitor v2 = visitor.incrementQueryLevel(1); + for (Expression e : expressions) { + if (!e.isEverything(v2)) { + return false; + } + } + if (condition != null && !condition.isEverything(v2)) { + return false; + } + if (having != null && !having.isEverything(v2)) { + return false; + } + if (qualify != null && !qualify.isEverything(v2)) { + return false; + } + return true; + } + + + @Override + public boolean isCacheable() { + return !isForUpdate; + } + + @Override + public boolean allowGlobalConditions() { + return offsetExpr == null && fetchExpr == null && distinctExpressions == null; + } + + public SortOrder getSortOrder() { + return sort; + } + + /** + * Returns parent select, or null. + * + * @return parent select, or null + */ + public Select getParentSelect() { + return parentSelect; + } + + @Override + public boolean isConstantQuery() { + if (!super.isConstantQuery() || distinctExpressions != null || condition != null || isGroupQuery + || isWindowQuery || !isNoFromClause()) { + return false; + } + for (int i = 0; i < visibleColumnCount; i++) { + if (!expressions.get(i).isConstant()) { + return false; + } + } + return true; + } + + @Override + public Expression getIfSingleRow() { + if (offsetExpr != null || fetchExpr != null || condition != null || isGroupQuery || isWindowQuery + || !isNoFromClause()) { + return null; + } + if (visibleColumnCount == 1) { + return expressions.get(0); + } + Expression[] array = new Expression[visibleColumnCount]; + for (int i = 0; i < visibleColumnCount; i++) { + array[i] = expressions.get(i); + } + return new ExpressionList(array, false); + } + + private boolean isNoFromClause() { + if (topTableFilter != null) { + return topTableFilter.isNoFromClauseFilter(); + } else if (topFilters.size() == 1) { + return topFilters.get(0).isNoFromClauseFilter(); + } + return false; + } + + /** + * Lazy execution for this select. + */ + private abstract class LazyResultSelect extends LazyResult { + + long rowNumber; + int columnCount; + + LazyResultSelect(Expression[] expressions, int columnCount) { + super(getSession(), expressions); + this.columnCount = columnCount; + setCurrentRowNumber(0); + } + + @Override + public final int getVisibleColumnCount() { + return visibleColumnCount; + } + + @Override + public void reset() { + super.reset(); + topTableFilter.reset(); + setCurrentRowNumber(0); + rowNumber = 0; + } + } + + /** + * Lazy execution for a flat query. + */ + private final class LazyResultQueryFlat extends LazyResultSelect { + + private boolean forUpdate; + + LazyResultQueryFlat(Expression[] expressions, int columnCount, boolean forUpdate) { + super(expressions, columnCount); + this.forUpdate = forUpdate; + } + + @Override + protected Value[] fetchNextRow() { + while (topTableFilter.next()) { + setCurrentRowNumber(rowNumber + 1); + // This method may lock rows + if (forUpdate ? isConditionMetForUpdate() : isConditionMet()) { + ++rowNumber; + Value[] row = new Value[columnCount]; + for (int i = 0; i < columnCount; i++) { + Expression expr = expressions.get(i); + row[i] = expr.getValue(getSession()); + } + return row; + } + } + return null; + } + + @Override + protected boolean skipNextRow() { + while (topTableFilter.next()) { + setCurrentRowNumber(rowNumber + 1); + // This method does not lock rows + if (isConditionMet()) { + ++rowNumber; + return true; + } + } + return false; + } + + } + + /** + * Lazy execution for a group sorted query. + */ + private final class LazyResultGroupSorted extends LazyResultSelect { + + private Value[] previousKeyValues; + + LazyResultGroupSorted(Expression[] expressions, int columnCount) { + super(expressions, columnCount); + if (groupData == null) { + setGroupData(SelectGroups.getInstance(getSession(), Select.this.expressions, isGroupQuery, + groupIndex)); + } else { + updateAgg(columnCount, DataAnalysisOperation.STAGE_RESET); + groupData.resetLazy(); + } + } + + @Override + public void reset() { + super.reset(); + groupData.resetLazy(); + previousKeyValues = null; + } + + @Override + protected Value[] fetchNextRow() { + while (topTableFilter.next()) { + setCurrentRowNumber(rowNumber + 1); + if (isConditionMet()) { + rowNumber++; + int groupSize = groupIndex.length; + Value[] keyValues = new Value[groupSize]; + // update group + for (int i = 0; i < groupSize; i++) { + int idx = groupIndex[i]; + Expression expr = expressions.get(idx); + keyValues[i] = expr.getValue(getSession()); + } + + Value[] row = null; + if (previousKeyValues == null) { + previousKeyValues = keyValues; + groupData.nextLazyGroup(); + } else { + SessionLocal session = getSession(); + for (int i = 0; i < groupSize; i++) { + if (session.compare(previousKeyValues[i], keyValues[i]) != 0) { + row = createGroupSortedRow(previousKeyValues, columnCount); + previousKeyValues = keyValues; + groupData.nextLazyGroup(); + break; + } + } + } + groupData.nextLazyRow(); + updateAgg(columnCount, DataAnalysisOperation.STAGE_GROUP); + if (row != null) { + return row; + } + } + } + Value[] row = null; + if (previousKeyValues != null) { + row = createGroupSortedRow(previousKeyValues, columnCount); + previousKeyValues = null; + } + return row; + } + } + +} diff --git a/h2/src/main/org/h2/command/query/SelectGroups.java b/h2/src/main/org/h2/command/query/SelectGroups.java new file mode 100644 index 0000000..ef5e157 --- /dev/null +++ b/h2/src/main/org/h2/command/query/SelectGroups.java @@ -0,0 +1,433 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.query; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.analysis.DataAnalysisOperation; +import org.h2.expression.analysis.PartitionData; +import org.h2.value.Value; +import org.h2.value.ValueRow; + +/** + * Grouped data for aggregates. + * + *

+ * Call sequence: + *

+ *
    + *
  • {@link #reset()}.
  • + *
  • For each source row {@link #nextSource()} should be invoked.
  • + *
  • {@link #done()}.
  • + *
  • {@link #next()} is invoked inside a loop until it returns null.
  • + *
+ *

+ * Call sequence for lazy group sorted result: + *

+ *
    + *
  • {@link #resetLazy()} (not required before the first execution).
  • + *
  • For each source group {@link #nextLazyGroup()} should be invoked.
  • + *
  • For each source row {@link #nextLazyRow()} should be invoked. Each group + * can have one or more rows.
  • + *
+ */ +public abstract class SelectGroups { + + private static final class Grouped extends SelectGroups { + + private final int[] groupIndex; + + /** + * Map of group-by key to group-by expression data e.g. AggregateData + */ + private TreeMap groupByData; + + /** + * Key into groupByData that produces currentGroupByExprData. Not used + * in lazy mode. + */ + private ValueRow currentGroupsKey; + + /** + * Cursor for {@link #next()} method. + */ + private Iterator> cursor; + + Grouped(SessionLocal session, ArrayList expressions, int[] groupIndex) { + super(session, expressions); + this.groupIndex = groupIndex; + } + + @Override + public void reset() { + super.reset(); + groupByData = new TreeMap<>(session.getDatabase().getCompareMode()); + currentGroupsKey = null; + cursor = null; + } + + @Override + public void nextSource() { + if (groupIndex == null) { + currentGroupsKey = ValueRow.EMPTY; + } else { + Value[] keyValues = new Value[groupIndex.length]; + // update group + for (int i = 0; i < groupIndex.length; i++) { + int idx = groupIndex[i]; + Expression expr = expressions.get(idx); + keyValues[i] = expr.getValue(session); + } + currentGroupsKey = ValueRow.get(keyValues); + } + Object[] values = groupByData.get(currentGroupsKey); + if (values == null) { + values = createRow(); + groupByData.put(currentGroupsKey, values); + } + currentGroupByExprData = values; + currentGroupRowId++; + } + + @Override + void updateCurrentGroupExprData() { + // this can be null in lazy mode + if (currentGroupsKey != null) { + // since we changed the size of the array, update the object in + // the groups map + groupByData.put(currentGroupsKey, currentGroupByExprData); + } + } + + @Override + public void done() { + super.done(); + if (groupIndex == null && groupByData.size() == 0) { + groupByData.put(ValueRow.EMPTY, createRow()); + } + cursor = groupByData.entrySet().iterator(); + } + + @Override + public ValueRow next() { + if (cursor.hasNext()) { + Map.Entry entry = cursor.next(); + currentGroupByExprData = entry.getValue(); + currentGroupRowId++; + return entry.getKey(); + } + return null; + } + + @Override + public void remove() { + cursor.remove(); + currentGroupByExprData = null; + currentGroupRowId--; + } + + @Override + public void resetLazy() { + super.resetLazy(); + currentGroupsKey = null; + } + } + + private static final class Plain extends SelectGroups { + + private ArrayList rows; + + /** + * Cursor for {@link #next()} method. + */ + private Iterator cursor; + + Plain(SessionLocal session, ArrayList expressions) { + super(session, expressions); + } + + @Override + public void reset() { + super.reset(); + rows = new ArrayList<>(); + cursor = null; + } + + @Override + public void nextSource() { + Object[] values = createRow(); + rows.add(values); + currentGroupByExprData = values; + currentGroupRowId++; + } + + @Override + void updateCurrentGroupExprData() { + rows.set(rows.size() - 1, currentGroupByExprData); + } + + @Override + public void done() { + super.done(); + cursor = rows.iterator(); + } + + @Override + public ValueRow next() { + if (cursor.hasNext()) { + currentGroupByExprData = cursor.next(); + currentGroupRowId++; + return ValueRow.EMPTY; + } + return null; + } + } + + /** + * The database session. + */ + final SessionLocal session; + + /** + * The query's column list, including invisible expressions such as order by expressions. + */ + final ArrayList expressions; + + /** + * The array of current group-by expression data e.g. AggregateData. + */ + Object[] currentGroupByExprData; + + /** + * Maps an expression object to an index, to use in accessing the Object[] + * pointed to by groupByData. + */ + private final HashMap exprToIndexInGroupByData = new HashMap<>(); + + /** + * Maps an window expression object to its data. + */ + private final HashMap windowData = new HashMap<>(); + + /** + * Maps an partitioned window expression object to its data. + */ + private final HashMap> windowPartitionData = new HashMap<>(); + + /** + * The id of the current group. + */ + int currentGroupRowId; + + /** + * Creates new instance of grouped data. + * + * @param session + * the session + * @param expressions + * the expressions + * @param isGroupQuery + * is this query is a group query + * @param groupIndex + * the indexes of group expressions, or null + * @return new instance of the grouped data. + */ + public static SelectGroups getInstance(SessionLocal session, ArrayList expressions, + boolean isGroupQuery, int[] groupIndex) { + return isGroupQuery ? new Grouped(session, expressions, groupIndex) : new Plain(session, expressions); + } + + SelectGroups(SessionLocal session, ArrayList expressions) { + this.session = session; + this.expressions = expressions; + } + + /** + * Is there currently a group-by active. + * + * @return {@code true} if there is currently a group-by active, + * otherwise returns {@code false}. + */ + public boolean isCurrentGroup() { + return currentGroupByExprData != null; + } + + /** + * Get the group-by data for the current group and the passed in expression. + * + * @param expr + * expression + * @return expression data or null + */ + public final Object getCurrentGroupExprData(Expression expr) { + Integer index = exprToIndexInGroupByData.get(expr); + if (index == null) { + return null; + } + return currentGroupByExprData[index]; + } + + /** + * Set the group-by data for the current group and the passed in expression. + * + * @param expr + * expression + * @param obj + * expression data to set + */ + public final void setCurrentGroupExprData(Expression expr, Object obj) { + Integer index = exprToIndexInGroupByData.get(expr); + if (index != null) { + assert currentGroupByExprData[index] == null; + currentGroupByExprData[index] = obj; + return; + } + index = exprToIndexInGroupByData.size(); + exprToIndexInGroupByData.put(expr, index); + if (index >= currentGroupByExprData.length) { + currentGroupByExprData = Arrays.copyOf(currentGroupByExprData, currentGroupByExprData.length * 2); + updateCurrentGroupExprData(); + } + currentGroupByExprData[index] = obj; + } + + /** + * Creates new object arrays to holds group-by data. + * + * @return new object array to holds group-by data. + */ + final Object[] createRow() { + return new Object[Math.max(exprToIndexInGroupByData.size(), expressions.size())]; + } + + /** + * Get the window data for the specified expression. + * + * @param expr + * expression + * @param partitionKey + * a key of partition + * @return expression data or null + */ + public final PartitionData getWindowExprData(DataAnalysisOperation expr, Value partitionKey) { + if (partitionKey == null) { + return windowData.get(expr); + } else { + TreeMap map = windowPartitionData.get(expr); + return map != null ? map.get(partitionKey) : null; + } + } + + /** + * Set the window data for the specified expression. + * + * @param expr + * expression + * @param partitionKey + * a key of partition + * @param obj + * window expression data to set + */ + public final void setWindowExprData(DataAnalysisOperation expr, Value partitionKey, PartitionData obj) { + if (partitionKey == null) { + Object old = windowData.put(expr, obj); + assert old == null; + } else { + TreeMap map = windowPartitionData.get(expr); + if (map == null) { + map = new TreeMap<>(session.getDatabase().getCompareMode()); + windowPartitionData.put(expr, map); + } + map.put(partitionKey, obj); + } + } + + /** + * Update group-by data specified by implementation. + */ + abstract void updateCurrentGroupExprData(); + + /** + * Returns identity of the current row. Used by aggregates to check whether + * they already processed this row or not. + * + * @return identity of the current row + */ + public int getCurrentGroupRowId() { + return currentGroupRowId; + } + + /** + * Resets this group data for reuse. + */ + public void reset() { + currentGroupByExprData = null; + exprToIndexInGroupByData.clear(); + windowData.clear(); + windowPartitionData.clear(); + currentGroupRowId = 0; + } + + /** + * Invoked for each source row to evaluate group key and setup all necessary + * data for aggregates. + */ + public abstract void nextSource(); + + /** + * Invoked after all source rows are evaluated. + */ + public void done() { + currentGroupRowId = 0; + } + + /** + * Returns the key of the next group. + * + * @return the key of the next group, or null + */ + public abstract ValueRow next(); + + /** + * Removes the data for the current key. + * + * @see #next() + */ + public void remove() { + throw new UnsupportedOperationException(); + } + + /** + * Resets this group data for reuse in lazy mode. + */ + public void resetLazy() { + currentGroupByExprData = null; + currentGroupRowId = 0; + } + + /** + * Moves group data to the next group in lazy mode. + */ + public void nextLazyGroup() { + currentGroupByExprData = new Object[Math.max(exprToIndexInGroupByData.size(), expressions.size())]; + } + + /** + * Moves group data to the next row in lazy mode. + */ + public void nextLazyRow() { + currentGroupRowId++; + } + +} diff --git a/h2/src/main/org/h2/command/query/SelectListColumnResolver.java b/h2/src/main/org/h2/command/query/SelectListColumnResolver.java new file mode 100644 index 0000000..ec62787 --- /dev/null +++ b/h2/src/main/org/h2/command/query/SelectListColumnResolver.java @@ -0,0 +1,80 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.query; + +import java.util.ArrayList; + +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.table.Column; +import org.h2.table.ColumnResolver; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * This class represents a column resolver for the column list of a SELECT + * statement. It is used to resolve select column aliases in the HAVING clause. + * Example: + *

+ * SELECT X/3 AS A, COUNT(*) FROM SYSTEM_RANGE(1, 10) GROUP BY A HAVING A > 2; + *

+ * + * @author Thomas Mueller + */ +public class SelectListColumnResolver implements ColumnResolver { + + private final Select select; + private final Expression[] expressions; + private final Column[] columns; + + SelectListColumnResolver(Select select) { + this.select = select; + int columnCount = select.getColumnCount(); + columns = new Column[columnCount]; + expressions = new Expression[columnCount]; + ArrayList columnList = select.getExpressions(); + SessionLocal session = select.getSession(); + for (int i = 0; i < columnCount; i++) { + Expression expr = columnList.get(i); + columns[i] = new Column(expr.getAlias(session, i), TypeInfo.TYPE_NULL, null, i); + expressions[i] = expr.getNonAliasExpression(); + } + } + + @Override + public Column[] getColumns() { + return columns; + } + + @Override + public Column findColumn(String name) { + Database db = select.getSession().getDatabase(); + for (Column column : columns) { + if (db.equalsIdentifiers(column.getName(), name)) { + return column; + } + } + return null; + } + + @Override + public Select getSelect() { + return select; + } + + @Override + public Value getValue(Column column) { + return null; + } + + @Override + public Expression optimize(ExpressionColumn expressionColumn, Column column) { + return expressions[column.getColumnId()]; + } + +} diff --git a/h2/src/main/org/h2/command/query/SelectUnion.java b/h2/src/main/org/h2/command/query/SelectUnion.java new file mode 100644 index 0000000..978a225 --- /dev/null +++ b/h2/src/main/org/h2/command/query/SelectUnion.java @@ -0,0 +1,459 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.query; + +import java.util.ArrayList; +import java.util.HashSet; + +import org.h2.api.ErrorCode; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.Parameter; +import org.h2.message.DbException; +import org.h2.result.LazyResult; +import org.h2.result.LocalResult; +import org.h2.result.ResultInterface; +import org.h2.result.ResultTarget; +import org.h2.table.Column; +import org.h2.table.ColumnResolver; +import org.h2.table.Table; +import org.h2.table.TableFilter; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * Represents a union SELECT statement. + */ +public class SelectUnion extends Query { + + public enum UnionType { + /** + * The type of a UNION statement. + */ + UNION, + + /** + * The type of a UNION ALL statement. + */ + UNION_ALL, + + /** + * The type of an EXCEPT statement. + */ + EXCEPT, + + /** + * The type of an INTERSECT statement. + */ + INTERSECT + } + + private final UnionType unionType; + + /** + * The left hand side of the union (the first subquery). + */ + final Query left; + + /** + * The right hand side of the union (the second subquery). + */ + final Query right; + + private boolean isForUpdate; + + public SelectUnion(SessionLocal session, UnionType unionType, Query query, Query right) { + super(session); + this.unionType = unionType; + this.left = query; + this.right = right; + } + + @Override + public boolean isUnion() { + return true; + } + + public UnionType getUnionType() { + return unionType; + } + + public Query getLeft() { + return left; + } + + public Query getRight() { + return right; + } + + private Value[] convert(Value[] values, int columnCount) { + Value[] newValues; + if (columnCount == values.length) { + // re-use the array if possible + newValues = values; + } else { + // create a new array if needed, + // for the value hash set + newValues = new Value[columnCount]; + } + for (int i = 0; i < columnCount; i++) { + Expression e = expressions.get(i); + newValues[i] = values[i].convertTo(e.getType(), session); + } + return newValues; + } + + public LocalResult getEmptyResult() { + int columnCount = left.getColumnCount(); + return createLocalResult(columnCount); + } + + @Override + protected ResultInterface queryWithoutCache(long maxRows, ResultTarget target) { + OffsetFetch offsetFetch = getOffsetFetch(maxRows); + long offset = offsetFetch.offset; + long fetch = offsetFetch.fetch; + boolean fetchPercent = offsetFetch.fetchPercent; + Database db = session.getDatabase(); + if (db.getSettings().optimizeInsertFromSelect) { + if (unionType == UnionType.UNION_ALL && target != null) { + if (sort == null && !distinct && fetch < 0 && offset == 0) { + left.query(0, target); + right.query(0, target); + return null; + } + } + } + int columnCount = left.getColumnCount(); + if (session.isLazyQueryExecution() && unionType == UnionType.UNION_ALL && !distinct && + sort == null && !randomAccessResult && !isForUpdate && + offset == 0 && !fetchPercent && !withTies && isReadOnly()) { + // limit 0 means no rows + if (fetch != 0) { + LazyResultUnion lazyResult = new LazyResultUnion(expressionArray, columnCount); + if (fetch > 0) { + lazyResult.setLimit(fetch); + } + return lazyResult; + } + } + LocalResult result = createLocalResult(columnCount); + if (sort != null) { + result.setSortOrder(sort); + } + if (distinct) { + left.setDistinctIfPossible(); + right.setDistinctIfPossible(); + result.setDistinct(); + } + switch (unionType) { + case UNION: + case EXCEPT: + left.setDistinctIfPossible(); + right.setDistinctIfPossible(); + result.setDistinct(); + break; + case UNION_ALL: + break; + case INTERSECT: + left.setDistinctIfPossible(); + right.setDistinctIfPossible(); + break; + default: + throw DbException.getInternalError("type=" + unionType); + } + ResultInterface l = left.query(0); + ResultInterface r = right.query(0); + l.reset(); + r.reset(); + switch (unionType) { + case UNION_ALL: + case UNION: { + while (l.next()) { + result.addRow(convert(l.currentRow(), columnCount)); + } + while (r.next()) { + result.addRow(convert(r.currentRow(), columnCount)); + } + break; + } + case EXCEPT: { + while (l.next()) { + result.addRow(convert(l.currentRow(), columnCount)); + } + while (r.next()) { + result.removeDistinct(convert(r.currentRow(), columnCount)); + } + break; + } + case INTERSECT: { + LocalResult temp = createLocalResult(columnCount); + temp.setDistinct(); + while (l.next()) { + temp.addRow(convert(l.currentRow(), columnCount)); + } + while (r.next()) { + Value[] values = convert(r.currentRow(), columnCount); + if (temp.containsDistinct(values)) { + result.addRow(values); + } + } + temp.close(); + break; + } + default: + throw DbException.getInternalError("type=" + unionType); + } + l.close(); + r.close(); + return finishResult(result, offset, fetch, fetchPercent, target); + } + + private LocalResult createLocalResult(int columnCount) { + return new LocalResult(session, expressionArray, columnCount, columnCount); + } + + @Override + public void init() { + if (checkInit) { + throw DbException.getInternalError(); + } + checkInit = true; + left.init(); + right.init(); + int len = left.getColumnCount(); + if (len != right.getColumnCount()) { + throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); + } + ArrayList le = left.getExpressions(); + // set the expressions to get the right column count and names, + // but can't validate at this time + expressions = new ArrayList<>(len); + for (int i = 0; i < len; i++) { + Expression l = le.get(i); + expressions.add(l); + } + visibleColumnCount = len; + if (withTies && !hasOrder()) { + throw DbException.get(ErrorCode.WITH_TIES_WITHOUT_ORDER_BY); + } + } + + @Override + public void prepareExpressions() { + left.prepareExpressions(); + right.prepareExpressions(); + int len = left.getColumnCount(); + // set the correct expressions now + expressions = new ArrayList<>(len); + ArrayList le = left.getExpressions(); + ArrayList re = right.getExpressions(); + for (int i = 0; i < len; i++) { + Expression l = le.get(i); + Expression r = re.get(i); + Column col = new Column(l.getAlias(session, i), TypeInfo.getHigherType(l.getType(), r.getType())); + Expression e = new ExpressionColumn(session.getDatabase(), col); + expressions.add(e); + } + if (orderList != null) { + if (initOrder(null, true, null)) { + prepareOrder(orderList, expressions.size()); + cleanupOrder(); + } + } + resultColumnCount = expressions.size(); + 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(); + } + + @Override + public HashSet
getTables() { + HashSet
set = left.getTables(); + set.addAll(right.getTables()); + return set; + } + + @Override + public void setForUpdate(boolean forUpdate) { + left.setForUpdate(forUpdate); + right.setForUpdate(forUpdate); + isForUpdate = forUpdate; + } + + @Override + public void mapColumns(ColumnResolver resolver, int level) { + left.mapColumns(resolver, level); + right.mapColumns(resolver, level); + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + left.setEvaluatable(tableFilter, b); + right.setEvaluatable(tableFilter, b); + } + + @Override + public void addGlobalCondition(Parameter param, int columnId, + int comparisonType) { + addParameter(param); + switch (unionType) { + case UNION_ALL: + case UNION: + case INTERSECT: { + left.addGlobalCondition(param, columnId, comparisonType); + right.addGlobalCondition(param, columnId, comparisonType); + break; + } + case EXCEPT: { + left.addGlobalCondition(param, columnId, comparisonType); + break; + } + default: + throw DbException.getInternalError("type=" + unionType); + } + } + + @Override + public String getPlanSQL(int sqlFlags) { + StringBuilder buff = new StringBuilder(); + buff.append('(').append(left.getPlanSQL(sqlFlags)).append(')'); + switch (unionType) { + case UNION_ALL: + buff.append("\nUNION ALL\n"); + break; + case UNION: + buff.append("\nUNION\n"); + break; + case INTERSECT: + buff.append("\nINTERSECT\n"); + break; + case EXCEPT: + buff.append("\nEXCEPT\n"); + break; + default: + throw DbException.getInternalError("type=" + unionType); + } + buff.append('(').append(right.getPlanSQL(sqlFlags)).append(')'); + appendEndOfQueryToSQL(buff, sqlFlags, expressions.toArray(new Expression[0])); + if (isForUpdate) { + buff.append("\nFOR UPDATE"); + } + return buff.toString(); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return left.isEverything(visitor) && right.isEverything(visitor); + } + + @Override + public void updateAggregate(SessionLocal s, int stage) { + left.updateAggregate(s, stage); + right.updateAggregate(s, stage); + } + + @Override + public void fireBeforeSelectTriggers() { + left.fireBeforeSelectTriggers(); + right.fireBeforeSelectTriggers(); + } + + @Override + public boolean allowGlobalConditions() { + return left.allowGlobalConditions() && right.allowGlobalConditions(); + } + + @Override + public boolean isConstantQuery() { + return super.isConstantQuery() && left.isConstantQuery() && right.isConstantQuery(); + } + + /** + * Lazy execution for this union. + */ + private final class LazyResultUnion extends LazyResult { + + int columnCount; + ResultInterface l; + ResultInterface r; + boolean leftDone; + boolean rightDone; + + LazyResultUnion(Expression[] expressions, int columnCount) { + super(getSession(), expressions); + this.columnCount = columnCount; + } + + @Override + public int getVisibleColumnCount() { + return columnCount; + } + + @Override + protected Value[] fetchNextRow() { + if (rightDone) { + return null; + } + if (!leftDone) { + if (l == null) { + l = left.query(0); + l.reset(); + } + if (l.next()) { + return l.currentRow(); + } + leftDone = true; + } + if (r == null) { + r = right.query(0); + r.reset(); + } + if (r.next()) { + return r.currentRow(); + } + rightDone = true; + return null; + } + + @Override + public void close() { + super.close(); + if (l != null) { + l.close(); + } + if (r != null) { + r.close(); + } + } + + @Override + public void reset() { + super.reset(); + if (l != null) { + l.reset(); + } + if (r != null) { + r.reset(); + } + leftDone = false; + rightDone = false; + } + } +} diff --git a/h2/src/main/org/h2/command/query/TableValueConstructor.java b/h2/src/main/org/h2/command/query/TableValueConstructor.java new file mode 100644 index 0000000..713b154 --- /dev/null +++ b/h2/src/main/org/h2/command/query/TableValueConstructor.java @@ -0,0 +1,397 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.command.query; + +import static org.h2.expression.Expression.WITHOUT_PARENTHESES; +import static org.h2.util.HasSQL.DEFAULT_SQL_FLAGS; + +import java.util.ArrayList; +import java.util.HashSet; + +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.expression.ExpressionList; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.Parameter; +import org.h2.message.DbException; +import org.h2.result.LocalResult; +import org.h2.result.ResultInterface; +import org.h2.result.ResultTarget; +import org.h2.table.Column; +import org.h2.table.ColumnResolver; +import org.h2.table.Table; +import org.h2.table.TableFilter; +import org.h2.table.TableValueConstructorTable; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * Table value constructor. + */ +public class TableValueConstructor extends Query { + + private final ArrayList> rows; + + /** + * The table. + */ + TableValueConstructorTable table; + + private TableValueColumnResolver columnResolver; + + private double cost; + + /** + * Creates new instance of table value constructor. + * + * @param session + * the session + * @param rows + * the rows + */ + public TableValueConstructor(SessionLocal session, ArrayList> rows) { + super(session); + this.rows = rows; + if ((visibleColumnCount = rows.get(0).size()) > Constants.MAX_COLUMNS) { + throw DbException.get(ErrorCode.TOO_MANY_COLUMNS_1, "" + Constants.MAX_COLUMNS); + } + for (ArrayList row : rows) { + for (Expression column : row) { + if (!column.isConstant()) { + return; + } + } + } + createTable(); + } + + /** + * Appends visible columns of all rows to the specified result. + * + * @param session + * the session + * @param result + * the result + * @param columns + * the columns + * @param rows + * the rows with data + */ + public static void getVisibleResult(SessionLocal session, ResultTarget result, Column[] columns, + ArrayList> rows) { + int count = columns.length; + for (ArrayList row : rows) { + Value[] values = new Value[count]; + for (int i = 0; i < count; i++) { + values[i] = row.get(i).getValue(session).convertTo(columns[i].getType(), session); + } + result.addRow(values); + } + } + + /** + * Appends the SQL of the values to the specified string builder.. + * + * @param builder + * string builder + * @param sqlFlags + * formatting flags + * @param rows + * the values + */ + public static void getValuesSQL(StringBuilder builder, int sqlFlags, ArrayList> rows) { + builder.append("VALUES "); + int rowCount = rows.size(); + for (int i = 0; i < rowCount; i++) { + if (i > 0) { + builder.append(", "); + } + Expression.writeExpressions(builder.append('('), rows.get(i), sqlFlags).append(')'); + } + } + + @Override + public boolean isUnion() { + return false; + } + + @Override + protected ResultInterface queryWithoutCache(long limit, ResultTarget target) { + OffsetFetch offsetFetch = getOffsetFetch(limit); + long offset = offsetFetch.offset; + long fetch = offsetFetch.fetch; + boolean fetchPercent = offsetFetch.fetchPercent; + int visibleColumnCount = this.visibleColumnCount, resultColumnCount = this.resultColumnCount; + LocalResult result = new LocalResult(session, expressionArray, visibleColumnCount, resultColumnCount); + if (sort != null) { + result.setSortOrder(sort); + } + if (distinct) { + result.setDistinct(); + } + Column[] columns = table.getColumns(); + if (visibleColumnCount == resultColumnCount) { + getVisibleResult(session, result, columns, rows); + } else { + for (ArrayList row : rows) { + Value[] values = new Value[resultColumnCount]; + for (int i = 0; i < visibleColumnCount; i++) { + values[i] = row.get(i).getValue(session).convertTo(columns[i].getType(), session); + } + columnResolver.currentRow = values; + for (int i = visibleColumnCount; i < resultColumnCount; i++) { + values[i] = expressionArray[i].getValue(session); + } + result.addRow(values); + } + columnResolver.currentRow = null; + } + return finishResult(result, offset, fetch, fetchPercent, target); + } + + @Override + public void init() { + if (checkInit) { + throw DbException.getInternalError(); + } + checkInit = true; + if (withTies && !hasOrder()) { + throw DbException.get(ErrorCode.WITH_TIES_WITHOUT_ORDER_BY); + } + } + + @Override + public void prepareExpressions() { + if (columnResolver == null) { + createTable(); + } + if (orderList != null) { + ArrayList expressionsSQL = new ArrayList<>(); + for (Expression e : expressions) { + expressionsSQL.add(e.getSQL(DEFAULT_SQL_FLAGS, WITHOUT_PARENTHESES)); + } + if (initOrder(expressionsSQL, false, null)) { + prepareOrder(orderList, expressions.size()); + } + } + resultColumnCount = expressions.size(); + for (int i = 0; i < resultColumnCount; i++) { + expressions.get(i).mapColumns(columnResolver, 0, Expression.MAP_INITIAL); + } + for (int i = visibleColumnCount; i < resultColumnCount; i++) { + expressions.set(i, expressions.get(i).optimize(session)); + } + if (sort != null) { + cleanupOrder(); + } + expressionArray = expressions.toArray(new Expression[0]); + } + + @Override + public void preparePlan() { + double cost = 0; + int columnCount = visibleColumnCount; + for (ArrayList r : rows) { + for (int i = 0; i < columnCount; i++) { + cost += r.get(i).getCost(); + } + } + this.cost = cost + rows.size(); + isPrepared = true; + } + + private void createTable() { + int rowCount = rows.size(); + ArrayList row = rows.get(0); + int columnCount = row.size(); + TypeInfo[] types = new TypeInfo[columnCount]; + for (int c = 0; c < columnCount; c++) { + Expression e = row.get(c).optimize(session); + row.set(c, e); + TypeInfo type = e.getType(); + if (type.getValueType() == Value.UNKNOWN) { + type = TypeInfo.TYPE_VARCHAR; + } + types[c] = type; + } + for (int r = 1; r < rowCount; r++) { + row = rows.get(r); + for (int c = 0; c < columnCount; c++) { + Expression e = row.get(c).optimize(session); + row.set(c, e); + types[c] = TypeInfo.getHigherType(types[c], e.getType()); + } + } + Column[] columns = new Column[columnCount]; + for (int c = 0; c < columnCount;) { + TypeInfo type = types[c]; + columns[c] = new Column("C" + ++c, type); + } + Database database = session.getDatabase(); + ArrayList expressions = new ArrayList<>(columnCount); + for (int i = 0; i < columnCount; i++) { + expressions.add(new ExpressionColumn(database, null, null, columns[i].getName())); + } + this.expressions = expressions; + table = new TableValueConstructorTable(session.getDatabase().getMainSchema(), session, columns, rows); + columnResolver = new TableValueColumnResolver(); + } + + @Override + public double getCost() { + return cost; + } + + @Override + public HashSet
getTables() { + HashSet
tables = new HashSet<>(1, 1f); + tables.add(table); + return tables; + } + + @Override + public void setForUpdate(boolean forUpdate) { + throw DbException.get(ErrorCode.RESULT_SET_READONLY); + } + + @Override + public void mapColumns(ColumnResolver resolver, int level) { + int columnCount = visibleColumnCount; + for (ArrayList row : rows) { + for (int i = 0; i < columnCount; i++) { + row.get(i).mapColumns(resolver, level, Expression.MAP_INITIAL); + } + } + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + int columnCount = visibleColumnCount; + for (ArrayList row : rows) { + for (int i = 0; i < columnCount; i++) { + row.get(i).setEvaluatable(tableFilter, b); + } + } + } + + @Override + public void addGlobalCondition(Parameter param, int columnId, int comparisonType) { + // Can't add + } + + @Override + public boolean allowGlobalConditions() { + return false; + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + ExpressionVisitor v2 = visitor.incrementQueryLevel(1); + for (Expression e : expressionArray) { + if (!e.isEverything(v2)) { + return false; + } + } + return true; + } + + @Override + public void updateAggregate(SessionLocal s, int stage) { + int columnCount = visibleColumnCount; + for (ArrayList row : rows) { + for (int i = 0; i < columnCount; i++) { + row.get(i).updateAggregate(s, stage); + } + } + } + + @Override + public void fireBeforeSelectTriggers() { + // Nothing to do + } + + @Override + public String getPlanSQL(int sqlFlags) { + StringBuilder builder = new StringBuilder(); + getValuesSQL(builder, sqlFlags, rows); + appendEndOfQueryToSQL(builder, sqlFlags, expressionArray); + return builder.toString(); + } + + @Override + public Table toTable(String alias, Column[] columnTemplates, ArrayList parameters, + boolean forCreateView, Query topQuery) { + if (!hasOrder() && offsetExpr == null && fetchExpr == null && table != null) { + return table; + } + return super.toTable(alias, columnTemplates, parameters, forCreateView, topQuery); + } + + @Override + public boolean isConstantQuery() { + if (!super.isConstantQuery()) { + return false; + } + for (ArrayList row : rows) { + for (int i = 0; i < visibleColumnCount; i++) { + if (!row.get(i).isConstant()) { + return false; + } + } + } + return true; + } + + @Override + public Expression getIfSingleRow() { + if (offsetExpr != null || fetchExpr != null || rows.size() != 1) { + return null; + } + ArrayList row = rows.get(0); + if (visibleColumnCount == 1) { + return row.get(0); + } + Expression[] array = new Expression[visibleColumnCount]; + for (int i = 0; i < visibleColumnCount; i++) { + array[i] = row.get(i); + } + return new ExpressionList(array, false); + } + + private final class TableValueColumnResolver implements ColumnResolver { + + Value[] currentRow; + + TableValueColumnResolver() { + } + + @Override + public Column[] getColumns() { + return table.getColumns(); + } + + @Override + public Column findColumn(String name) { + return table.findColumn(name); + } + + @Override + public Value getValue(Column column) { + return currentRow[column.getColumnId()]; + } + + @Override + public Expression optimize(ExpressionColumn expressionColumn, Column column) { + return expressions.get(column.getColumnId()); + } + + } + +} diff --git a/h2/src/main/org/h2/command/query/package.html b/h2/src/main/org/h2/command/query/package.html new file mode 100644 index 0000000..80f0d16 --- /dev/null +++ b/h2/src/main/org/h2/command/query/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Contains queries. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/compress/CompressDeflate.java b/h2/src/main/org/h2/compress/CompressDeflate.java new file mode 100644 index 0000000..0a1f722 --- /dev/null +++ b/h2/src/main/org/h2/compress/CompressDeflate.java @@ -0,0 +1,95 @@ +/* + * 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.compress; + +import java.util.StringTokenizer; +import java.util.zip.DataFormatException; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +import org.h2.api.ErrorCode; +import org.h2.mvstore.DataUtils; + +/** + * This is a wrapper class for the Deflater class. + * This algorithm supports the following options: + *
    + *
  • l or level: -1 (default), 0 (no compression), + * 1 (best speed), ..., 9 (best compression) + *
  • s or strategy: 0 (default), + * 1 (filtered), 2 (huffman only) + *
+ * See also java.util.zip.Deflater for details. + */ +public class CompressDeflate implements Compressor { + + private int level = Deflater.DEFAULT_COMPRESSION; + private int strategy = Deflater.DEFAULT_STRATEGY; + + @Override + public void setOptions(String options) { + if (options == null) { + return; + } + try { + StringTokenizer tokenizer = new StringTokenizer(options); + while (tokenizer.hasMoreElements()) { + String option = tokenizer.nextToken(); + if ("level".equals(option) || "l".equals(option)) { + level = Integer.parseInt(tokenizer.nextToken()); + } else if ("strategy".equals(option) || "s".equals(option)) { + strategy = Integer.parseInt(tokenizer.nextToken()); + } + Deflater deflater = new Deflater(level); + deflater.setStrategy(strategy); + } + } catch (Exception e) { + throw DataUtils.newMVStoreException(ErrorCode.UNSUPPORTED_COMPRESSION_OPTIONS_1, options); + } + } + + @Override + public int compress(byte[] in, int inPos, int inLen, byte[] out, int outPos) { + Deflater deflater = new Deflater(level); + deflater.setStrategy(strategy); + deflater.setInput(in, inPos, inLen); + deflater.finish(); + int compressed = deflater.deflate(out, outPos, out.length - outPos); + if (compressed == 0) { + // the compressed length is 0, meaning compression didn't work + // (sounds like a JDK bug) + // try again, using the default strategy and compression level + strategy = Deflater.DEFAULT_STRATEGY; + level = Deflater.DEFAULT_COMPRESSION; + return compress(in, inPos, inLen, out, outPos); + } + deflater.end(); + return outPos + compressed; + } + + @Override + public int getAlgorithm() { + return Compressor.DEFLATE; + } + + @Override + public void expand(byte[] in, int inPos, int inLen, byte[] out, int outPos, + int outLen) { + Inflater decompresser = new Inflater(); + decompresser.setInput(in, inPos, inLen); + decompresser.finished(); + try { + int len = decompresser.inflate(out, outPos, outLen); + if (len != outLen) { + throw new DataFormatException(len + " " + outLen); + } + } catch (DataFormatException e) { + throw DataUtils.newMVStoreException(ErrorCode.COMPRESSION_ERROR, e.getMessage(), e); + } + decompresser.end(); + } + +} diff --git a/h2/src/main/org/h2/compress/CompressLZF.java b/h2/src/main/org/h2/compress/CompressLZF.java new file mode 100644 index 0000000..952a4e5 --- /dev/null +++ b/h2/src/main/org/h2/compress/CompressLZF.java @@ -0,0 +1,473 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * + * This code is based on the LZF algorithm from Marc Lehmann. It is a + * re-implementation of the C code: + * http://cvs.schmorp.de/liblzf/lzf_c.c?view=markup + * http://cvs.schmorp.de/liblzf/lzf_d.c?view=markup + * + * According to a mail from Marc Lehmann, it's OK to use his algorithm: + * Date: 2010-07-15 15:57 + * Subject: Re: Question about LZF licensing + * ... + * The algorithm is not copyrighted (and cannot be copyrighted afaik) - as long + * as you wrote everything yourself, without copying my code, that's just fine + * (looking is of course fine too). + * ... + * + * Still I would like to keep his copyright info: + * + * Copyright (c) 2000-2005 Marc Alexander Lehmann + * Copyright (c) 2005 Oren J. Maurice + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.h2.compress; + +import java.nio.ByteBuffer; + +/** + *

+ * This class implements the LZF lossless data compression algorithm. LZF is a + * Lempel-Ziv variant with byte-aligned output, and optimized for speed. + *

+ *

+ * Safety/Use Notes: + *

+ *
    + *
  • Each instance should be used by a single thread only.
  • + *
  • The data buffers should be smaller than 1 GB.
  • + *
  • For performance reasons, safety checks on expansion are omitted.
  • + *
  • Invalid compressed data can cause an ArrayIndexOutOfBoundsException.
  • + *
+ *

+ * The LZF compressed format knows literal runs and back-references: + *

+ *
    + *
  • Literal run: directly copy bytes from input to output.
  • + *
  • Back-reference: copy previous data to output stream, with specified + * offset from location and length. The length is at least 3 bytes.
  • + *
+ *

+ * The first byte of the compressed stream is the control byte. For literal + * runs, the highest three bits of the control byte are not set, the lower + * bits are the literal run length, and the next bytes are data to copy directly + * into the output. For back-references, the highest three bits of the control + * byte are the back-reference length. If all three bits are set, then the + * back-reference length is stored in the next byte. The lower bits of the + * control byte combined with the next byte form the offset for the + * back-reference. + *

+ */ +public final class CompressLZF implements Compressor { + + /** + * The number of entries in the hash table. The size is a trade-off between + * hash collisions (reduced compression) and speed (amount that fits in CPU + * cache). + */ + private static final int HASH_SIZE = 1 << 14; + + /** + * The maximum number of literals in a chunk (32). + */ + private static final int MAX_LITERAL = 1 << 5; + + /** + * The maximum offset allowed for a back-reference (8192). + */ + private static final int MAX_OFF = 1 << 13; + + /** + * The maximum back-reference length (264). + */ + private static final int MAX_REF = (1 << 8) + (1 << 3); + + /** + * Hash table for matching byte sequences (reused for performance). + */ + private int[] cachedHashTable; + + @Override + public void setOptions(String options) { + // nothing to do + } + + /** + * Return the integer with the first two bytes 0, then the bytes at the + * index, then at index+1. + */ + private static int first(byte[] in, int inPos) { + return (in[inPos] << 8) | (in[inPos + 1] & 255); + } + + /** + * Return the integer with the first two bytes 0, then the bytes at the + * index, then at index+1. + */ + private static int first(ByteBuffer in, int inPos) { + return (in.get(inPos) << 8) | (in.get(inPos + 1) & 255); + } + + /** + * Shift the value 1 byte left, and add the byte at index inPos+2. + */ + private static int next(int v, byte[] in, int inPos) { + return (v << 8) | (in[inPos + 2] & 255); + } + + /** + * Shift the value 1 byte left, and add the byte at index inPos+2. + */ + private static int next(int v, ByteBuffer in, int inPos) { + return (v << 8) | (in.get(inPos + 2) & 255); + } + + /** + * Compute the address in the hash table. + */ + private static int hash(int h) { + return ((h * 2777) >> 9) & (HASH_SIZE - 1); + } + + @Override + public int compress(byte[] in, int inPos, int inLen, byte[] out, int outPos) { + int offset = inPos; + inLen += inPos; + if (cachedHashTable == null) { + cachedHashTable = new int[HASH_SIZE]; + } + int[] hashTab = cachedHashTable; + int literals = 0; + outPos++; + int future = first(in, inPos); + while (inPos < inLen - 4) { + byte p2 = in[inPos + 2]; + // next + future = (future << 8) + (p2 & 255); + int off = hash(future); + int ref = hashTab[off]; + hashTab[off] = inPos; + // if (ref < inPos + // && ref > 0 + // && (off = inPos - ref - 1) < MAX_OFF + // && in[ref + 2] == p2 + // && (((in[ref] & 255) << 8) | (in[ref + 1] & 255)) == + // ((future >> 8) & 0xffff)) { + if (ref < inPos + && ref > offset + && (off = inPos - ref - 1) < MAX_OFF + && in[ref + 2] == p2 + && in[ref + 1] == (byte) (future >> 8) + && in[ref] == (byte) (future >> 16)) { + // match + int maxLen = inLen - inPos - 2; + if (maxLen > MAX_REF) { + maxLen = MAX_REF; + } + if (literals == 0) { + // multiple back-references, + // so there is no literal run control byte + outPos--; + } else { + // set the control byte at the start of the literal run + // to store the number of literals + out[outPos - literals - 1] = (byte) (literals - 1); + literals = 0; + } + int len = 3; + while (len < maxLen && in[ref + len] == in[inPos + len]) { + len++; + } + len -= 2; + if (len < 7) { + out[outPos++] = (byte) ((off >> 8) + (len << 5)); + } else { + out[outPos++] = (byte) ((off >> 8) + (7 << 5)); + out[outPos++] = (byte) (len - 7); + } + out[outPos++] = (byte) off; + // move one byte forward to allow for a literal run control byte + outPos++; + inPos += len; + // rebuild the future, and store the last bytes to the + // hashtable. Storing hashes of the last bytes in back-reference + // improves the compression ratio and only reduces speed + // slightly. + future = first(in, inPos); + future = next(future, in, inPos); + hashTab[hash(future)] = inPos++; + future = next(future, in, inPos); + hashTab[hash(future)] = inPos++; + } else { + // copy one byte from input to output as part of literal + out[outPos++] = in[inPos++]; + literals++; + // at the end of this literal chunk, write the length + // to the control byte and start a new chunk + if (literals == MAX_LITERAL) { + out[outPos - literals - 1] = (byte) (literals - 1); + literals = 0; + // move ahead one byte to allow for the + // literal run control byte + outPos++; + } + } + } + // write the remaining few bytes as literals + while (inPos < inLen) { + out[outPos++] = in[inPos++]; + literals++; + if (literals == MAX_LITERAL) { + out[outPos - literals - 1] = (byte) (literals - 1); + literals = 0; + outPos++; + } + } + // writes the final literal run length to the control byte + out[outPos - literals - 1] = (byte) (literals - 1); + if (literals == 0) { + outPos--; + } + return outPos; + } + + /** + * Compress a number of bytes. + * + * @param in the input data + * @param inPos the offset at the input buffer + * @param out the output area + * @param outPos the offset at the output array + * @return the end position + */ + public int compress(ByteBuffer in, int inPos, byte[] out, int outPos) { + int offset = inPos; + int inLen = in.capacity(); + if (cachedHashTable == null) { + cachedHashTable = new int[HASH_SIZE]; + } + int[] hashTab = cachedHashTable; + int literals = 0; + outPos++; + int future = first(in, inPos); + while (inPos < inLen - 4) { + byte p2 = in.get(inPos + 2); + // next + future = (future << 8) + (p2 & 255); + int off = hash(future); + int ref = hashTab[off]; + hashTab[off] = inPos; + // if (ref < inPos + // && ref > 0 + // && (off = inPos - ref - 1) < MAX_OFF + // && in[ref + 2] == p2 + // && (((in[ref] & 255) << 8) | (in[ref + 1] & 255)) == + // ((future >> 8) & 0xffff)) { + if (ref < inPos + && ref > offset + && (off = inPos - ref - 1) < MAX_OFF + && in.get(ref + 2) == p2 + && in.get(ref + 1) == (byte) (future >> 8) + && in.get(ref) == (byte) (future >> 16)) { + // match + int maxLen = inLen - inPos - 2; + if (maxLen > MAX_REF) { + maxLen = MAX_REF; + } + if (literals == 0) { + // multiple back-references, + // so there is no literal run control byte + outPos--; + } else { + // set the control byte at the start of the literal run + // to store the number of literals + out[outPos - literals - 1] = (byte) (literals - 1); + literals = 0; + } + int len = 3; + while (len < maxLen && in.get(ref + len) == in.get(inPos + len)) { + len++; + } + len -= 2; + if (len < 7) { + out[outPos++] = (byte) ((off >> 8) + (len << 5)); + } else { + out[outPos++] = (byte) ((off >> 8) + (7 << 5)); + out[outPos++] = (byte) (len - 7); + } + out[outPos++] = (byte) off; + // move one byte forward to allow for a literal run control byte + outPos++; + inPos += len; + // rebuild the future, and store the last bytes to the + // hashtable. Storing hashes of the last bytes in back-reference + // improves the compression ratio and only reduces speed + // slightly. + future = first(in, inPos); + future = next(future, in, inPos); + hashTab[hash(future)] = inPos++; + future = next(future, in, inPos); + hashTab[hash(future)] = inPos++; + } else { + // copy one byte from input to output as part of literal + out[outPos++] = in.get(inPos++); + literals++; + // at the end of this literal chunk, write the length + // to the control byte and start a new chunk + if (literals == MAX_LITERAL) { + out[outPos - literals - 1] = (byte) (literals - 1); + literals = 0; + // move ahead one byte to allow for the + // literal run control byte + outPos++; + } + } + } + // write the remaining few bytes as literals + while (inPos < inLen) { + out[outPos++] = in.get(inPos++); + literals++; + if (literals == MAX_LITERAL) { + out[outPos - literals - 1] = (byte) (literals - 1); + literals = 0; + outPos++; + } + } + // writes the final literal run length to the control byte + out[outPos - literals - 1] = (byte) (literals - 1); + if (literals == 0) { + outPos--; + } + return outPos; + } + + @Override + public void expand(byte[] in, int inPos, int inLen, byte[] out, int outPos, + int outLen) { + // if ((inPos | outPos | outLen) < 0) { + if (inPos < 0 || outPos < 0 || outLen < 0) { + throw new IllegalArgumentException(); + } + do { + int ctrl = in[inPos++] & 255; + if (ctrl < MAX_LITERAL) { + // literal run of length = ctrl + 1, + ctrl++; + // copy to output and move forward this many bytes + // while (ctrl-- > 0) { + // out[outPos++] = in[inPos++]; + // } + System.arraycopy(in, inPos, out, outPos, ctrl); + outPos += ctrl; + inPos += ctrl; + } else { + // back reference + // the highest 3 bits are the match length + int len = ctrl >> 5; + // if the length is maxed, add the next byte to the length + if (len == 7) { + len += in[inPos++] & 255; + } + // minimum back-reference is 3 bytes, + // so 2 was subtracted before storing size + len += 2; + + // ctrl is now the offset for a back-reference... + // the logical AND operation removes the length bits + ctrl = -((ctrl & 0x1f) << 8) - 1; + + // the next byte augments/increases the offset + ctrl -= in[inPos++] & 255; + + // copy the back-reference bytes from the given + // location in output to current position + ctrl += outPos; + if (outPos + len >= out.length) { + // reduce array bounds checking + throw new ArrayIndexOutOfBoundsException(); + } + for (int i = 0; i < len; i++) { + out[outPos++] = out[ctrl++]; + } + } + } while (outPos < outLen); + } + + /** + * Expand a number of compressed bytes. + * + * @param in the compressed data + * @param out the output area + */ + public static void expand(ByteBuffer in, ByteBuffer out) { + do { + int ctrl = in.get() & 255; + if (ctrl < MAX_LITERAL) { + // literal run of length = ctrl + 1, + ctrl++; + // copy to output and move forward this many bytes + // (maybe slice would be faster) + for (int i = 0; i < ctrl; i++) { + out.put(in.get()); + } + } else { + // back reference + // the highest 3 bits are the match length + int len = ctrl >> 5; + // if the length is maxed, add the next byte to the length + if (len == 7) { + len += in.get() & 255; + } + // minimum back-reference is 3 bytes, + // so 2 was subtracted before storing size + len += 2; + + // ctrl is now the offset for a back-reference... + // the logical AND operation removes the length bits + ctrl = -((ctrl & 0x1f) << 8) - 1; + + // the next byte augments/increases the offset + ctrl -= in.get() & 255; + + // copy the back-reference bytes from the given + // location in output to current position + // (maybe slice would be faster) + ctrl += out.position(); + for (int i = 0; i < len; i++) { + out.put(out.get(ctrl++)); + } + } + } while (out.position() < out.capacity()); + } + + @Override + public int getAlgorithm() { + return Compressor.LZF; + } + +} \ No newline at end of file diff --git a/h2/src/main/org/h2/compress/CompressNo.java b/h2/src/main/org/h2/compress/CompressNo.java new file mode 100644 index 0000000..df7c1fb --- /dev/null +++ b/h2/src/main/org/h2/compress/CompressNo.java @@ -0,0 +1,37 @@ +/* + * 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.compress; + +/** + * This class implements a data compression algorithm that does in fact not + * compress. This is useful if the data can not be compressed because it is + * encrypted, already compressed, or random. + */ +public class CompressNo implements Compressor { + + @Override + public int getAlgorithm() { + return Compressor.NO; + } + + @Override + public void setOptions(String options) { + // nothing to do + } + + @Override + public int compress(byte[] in, int inPos, int inLen, byte[] out, int outPos) { + System.arraycopy(in, inPos, out, outPos, inLen); + return outPos + inLen; + } + + @Override + public void expand(byte[] in, int inPos, int inLen, byte[] out, int outPos, + int outLen) { + System.arraycopy(in, inPos, out, outPos, outLen); + } + +} diff --git a/h2/src/main/org/h2/compress/Compressor.java b/h2/src/main/org/h2/compress/Compressor.java new file mode 100644 index 0000000..4970ff0 --- /dev/null +++ b/h2/src/main/org/h2/compress/Compressor.java @@ -0,0 +1,68 @@ +/* + * 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.compress; + + +/** + * Each data compression algorithm must implement this interface. + */ +public interface Compressor { + + /** + * No compression is used. + */ + int NO = 0; + + /** + * The LZF compression algorithm is used + */ + int LZF = 1; + + /** + * The DEFLATE compression algorithm is used. + */ + int DEFLATE = 2; + + /** + * Get the compression algorithm type. + * + * @return the type + */ + int getAlgorithm(); + + /** + * Compress a number of bytes. + * + * @param in the input data + * @param inPos the offset at the input array + * @param inLen the number of bytes to compress + * @param out the output area + * @param outPos the offset at the output array + * @return the end position + */ + int compress(byte[] in, int inPos, int inLen, byte[] out, int outPos); + + /** + * Expand a number of compressed bytes. + * + * @param in the compressed data + * @param inPos the offset at the input array + * @param inLen the number of bytes to read + * @param out the output area + * @param outPos the offset at the output array + * @param outLen the size of the uncompressed data + */ + void expand(byte[] in, int inPos, int inLen, byte[] out, int outPos, + int outLen); + + /** + * Set the compression options. This may include settings for + * higher performance but less compression. + * + * @param options the options + */ + void setOptions(String options); +} diff --git a/h2/src/main/org/h2/compress/LZFInputStream.java b/h2/src/main/org/h2/compress/LZFInputStream.java new file mode 100644 index 0000000..5586841 --- /dev/null +++ b/h2/src/main/org/h2/compress/LZFInputStream.java @@ -0,0 +1,133 @@ +/* + * 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.compress; + +import java.io.IOException; +import java.io.InputStream; +import org.h2.mvstore.DataUtils; +import org.h2.util.Utils; + +/** + * An input stream to read from an LZF stream. + * The data is automatically expanded. + */ +public class LZFInputStream extends InputStream { + + private final InputStream in; + private CompressLZF decompress = new CompressLZF(); + private int pos; + private int bufferLength; + private byte[] inBuffer; + private byte[] buffer; + + public LZFInputStream(InputStream in) throws IOException { + this.in = in; + if (readInt() != LZFOutputStream.MAGIC) { + throw new IOException("Not an LZFInputStream"); + } + } + + private static byte[] ensureSize(byte[] buff, int len) { + return buff == null || buff.length < len ? Utils.newBytes(len) : buff; + } + + private void fillBuffer() throws IOException { + if (buffer != null && pos < bufferLength) { + return; + } + int len = readInt(); + if (decompress == null) { + // EOF + this.bufferLength = 0; + } else if (len < 0) { + len = -len; + buffer = ensureSize(buffer, len); + readFully(buffer, len); + this.bufferLength = len; + } else { + inBuffer = ensureSize(inBuffer, len); + int size = readInt(); + readFully(inBuffer, len); + buffer = ensureSize(buffer, size); + try { + decompress.expand(inBuffer, 0, len, buffer, 0, size); + } catch (ArrayIndexOutOfBoundsException e) { + throw DataUtils.convertToIOException(e); + } + this.bufferLength = size; + } + pos = 0; + } + + private void readFully(byte[] buff, int len) throws IOException { + int off = 0; + while (len > 0) { + int l = in.read(buff, off, len); + len -= l; + off += l; + } + } + + private int readInt() throws IOException { + int x = in.read(); + if (x < 0) { + decompress = null; + return 0; + } + x = (x << 24) + (in.read() << 16) + (in.read() << 8) + in.read(); + return x; + } + + @Override + public int read() throws IOException { + fillBuffer(); + if (pos >= bufferLength) { + return -1; + } + return buffer[pos++] & 255; + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (len == 0) { + return 0; + } + int read = 0; + while (len > 0) { + int r = readBlock(b, off, len); + if (r < 0) { + break; + } + read += r; + off += r; + len -= r; + } + return read == 0 ? -1 : read; + } + + private int readBlock(byte[] b, int off, int len) throws IOException { + fillBuffer(); + if (pos >= bufferLength) { + return -1; + } + int max = Math.min(len, bufferLength - pos); + max = Math.min(max, b.length - off); + System.arraycopy(buffer, pos, b, off, max); + pos += max; + return max; + } + + @Override + public void close() throws IOException { + in.close(); + } + +} diff --git a/h2/src/main/org/h2/compress/LZFOutputStream.java b/h2/src/main/org/h2/compress/LZFOutputStream.java new file mode 100644 index 0000000..e2b7aa2 --- /dev/null +++ b/h2/src/main/org/h2/compress/LZFOutputStream.java @@ -0,0 +1,102 @@ +/* + * 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.compress; + +import java.io.IOException; +import java.io.OutputStream; +import org.h2.engine.Constants; + +/** + * An output stream to write an LZF stream. + * The data is automatically compressed. + */ +public class LZFOutputStream extends OutputStream { + + /** + * The file header of a LZF file. + */ + static final int MAGIC = ('H' << 24) | ('2' << 16) | ('I' << 8) | 'S'; + + private final OutputStream out; + private final CompressLZF compress = new CompressLZF(); + private final byte[] buffer; + private int pos; + private byte[] outBuffer; + + public LZFOutputStream(OutputStream out) throws IOException { + this.out = out; + int len = Constants.IO_BUFFER_SIZE_COMPRESS; + buffer = new byte[len]; + ensureOutput(len); + writeInt(MAGIC); + } + + private void ensureOutput(int len) { + // TODO calculate the maximum overhead (worst case) for the output + // buffer + int outputLen = (len < 100 ? len + 100 : len) * 2; + if (outBuffer == null || outBuffer.length < outputLen) { + outBuffer = new byte[outputLen]; + } + } + + @Override + public void write(int b) throws IOException { + if (pos >= buffer.length) { + flush(); + } + buffer[pos++] = (byte) b; + } + + private void compressAndWrite(byte[] buff, int len) throws IOException { + if (len > 0) { + ensureOutput(len); + int compressed = compress.compress(buff, 0, len, outBuffer, 0); + if (compressed > len) { + writeInt(-len); + out.write(buff, 0, len); + } else { + writeInt(compressed); + writeInt(len); + out.write(outBuffer, 0, compressed); + } + } + } + + private void writeInt(int x) throws IOException { + out.write((byte) (x >> 24)); + out.write((byte) (x >> 16)); + out.write((byte) (x >> 8)); + out.write((byte) x); + } + + @Override + public void write(byte[] buff, int off, int len) throws IOException { + while (len > 0) { + int copy = Math.min(buffer.length - pos, len); + System.arraycopy(buff, off, buffer, pos, copy); + pos += copy; + if (pos >= buffer.length) { + flush(); + } + off += copy; + len -= copy; + } + } + + @Override + public void flush() throws IOException { + compressAndWrite(buffer, pos); + pos = 0; + } + + @Override + public void close() throws IOException { + flush(); + out.close(); + } + +} diff --git a/h2/src/main/org/h2/compress/package.html b/h2/src/main/org/h2/compress/package.html new file mode 100644 index 0000000..3c1c6d9 --- /dev/null +++ b/h2/src/main/org/h2/compress/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Lossless data compression classes. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/constraint/Constraint.java b/h2/src/main/org/h2/constraint/Constraint.java new file mode 100644 index 0000000..762b267 --- /dev/null +++ b/h2/src/main/org/h2/constraint/Constraint.java @@ -0,0 +1,212 @@ +/* + * 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.constraint; + +import java.util.HashSet; +import org.h2.engine.DbObject; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.index.Index; +import org.h2.message.Trace; +import org.h2.result.Row; +import org.h2.schema.Schema; +import org.h2.schema.SchemaObject; +import org.h2.table.Column; +import org.h2.table.Table; + +/** + * The base class for constraint checking. + */ +public abstract class Constraint extends SchemaObject implements Comparable { + + public enum Type { + /** + * The constraint type for check constraints. + */ + CHECK, + /** + * The constraint type for primary key constraints. + */ + PRIMARY_KEY, + /** + * The constraint type for unique constraints. + */ + UNIQUE, + /** + * The constraint type for referential constraints. + */ + REFERENTIAL, + /** + * The constraint type for domain constraints. + */ + DOMAIN; + + /** + * Get standard SQL type name. + * + * @return standard SQL type name + */ + public String getSqlName() { + if (this == Constraint.Type.PRIMARY_KEY) { + return "PRIMARY KEY"; + } + if (this == Constraint.Type.REFERENTIAL) { + return "FOREIGN KEY"; + } + return name(); + } + + } + + /** + * The table for which this constraint is defined. + */ + protected Table table; + + Constraint(Schema schema, int id, String name, Table table) { + super(schema, id, name, Trace.CONSTRAINT); + this.table = table; + if (table != null) { + this.setTemporary(table.isTemporary()); + } + } + + /** + * The constraint type name + * + * @return the name + */ + public abstract Type getConstraintType(); + + /** + * Check if this row fulfils the constraint. + * This method throws an exception if not. + * + * @param session the session + * @param t the table + * @param oldRow the old row + * @param newRow the new row + */ + public abstract void checkRow(SessionLocal session, Table t, Row oldRow, Row newRow); + + /** + * Check if this constraint needs the specified index. + * + * @param index the index + * @return true if the index is used + */ + public abstract boolean usesIndex(Index index); + + /** + * This index is now the owner of the specified index. + * + * @param index the index + */ + public abstract void setIndexOwner(Index index); + + /** + * Get all referenced columns. + * + * @param table the table + * @return the set of referenced columns + */ + public abstract HashSet getReferencedColumns(Table table); + + /** + * Returns the CHECK expression or null. + * + * @return the CHECK expression or null. + */ + public Expression getExpression() { + return null; + } + + /** + * Get the SQL statement to create this constraint. + * + * @return the SQL statement + */ + public abstract String getCreateSQLWithoutIndexes(); + + /** + * Check if this constraint needs to be checked before updating the data. + * + * @return true if it must be checked before updating + */ + public abstract boolean isBefore(); + + /** + * Check the existing data. This method is called if the constraint is added + * after data has been inserted into the table. + * + * @param session the session + */ + public abstract void checkExistingData(SessionLocal session); + + /** + * This method is called after a related table has changed + * (the table was renamed, or columns have been renamed). + */ + public abstract void rebuild(); + + /** + * Get the index of this constraint in the source table, or null if no index + * is used. + * + * @return the index + */ + public Index getIndex() { + return null; + } + + /** + * Returns the referenced unique constraint, or null. + * + * @return the referenced unique constraint, or null + */ + public ConstraintUnique getReferencedConstraint() { + return null; + } + + @Override + public int getType() { + return DbObject.CONSTRAINT; + } + + public Table getTable() { + return table; + } + + public Table getRefTable() { + return table; + } + + @Override + public int compareTo(Constraint other) { + if (this == other) { + return 0; + } + return Integer.compare(getConstraintType().ordinal(), other.getConstraintType().ordinal()); + } + + @Override + public boolean isHidden() { + return table != null && table.isHidden(); + } + + /** + * Visit all elements in the constraint. + * + * @param visitor the visitor + * @return true if every visited expression returned true, or if there are + * no expressions + */ + public boolean isEverything(@SuppressWarnings("unused") ExpressionVisitor visitor) { + return true; + } + +} diff --git a/h2/src/main/org/h2/constraint/ConstraintActionType.java b/h2/src/main/org/h2/constraint/ConstraintActionType.java new file mode 100644 index 0000000..b5e3b8f --- /dev/null +++ b/h2/src/main/org/h2/constraint/ConstraintActionType.java @@ -0,0 +1,44 @@ +/* + * 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.constraint; + +public enum ConstraintActionType { + /** + * The action is to restrict the operation. + */ + RESTRICT, + + /** + * The action is to cascade the operation. + */ + CASCADE, + + /** + * The action is to set the value to the default value. + */ + SET_DEFAULT, + + /** + * The action is to set the value to NULL. + */ + SET_NULL; + + /** + * Get standard SQL type name. + * + * @return standard SQL type name + */ + public String getSqlName() { + if (this == ConstraintActionType.SET_DEFAULT) { + return "SET DEFAULT"; + } + if (this == SET_NULL) { + return "SET NULL"; + } + return name(); + } + +} \ No newline at end of file diff --git a/h2/src/main/org/h2/constraint/ConstraintCheck.java b/h2/src/main/org/h2/constraint/ConstraintCheck.java new file mode 100644 index 0000000..a453b23 --- /dev/null +++ b/h2/src/main/org/h2/constraint/ConstraintCheck.java @@ -0,0 +1,167 @@ +/* + * 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.constraint; + +import java.util.HashSet; +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.index.Index; +import org.h2.message.DbException; +import org.h2.result.ResultInterface; +import org.h2.result.Row; +import org.h2.schema.Schema; +import org.h2.table.Column; +import org.h2.table.Table; +import org.h2.table.TableFilter; +import org.h2.util.StringUtils; +import org.h2.value.Value; + +/** + * A check constraint. + */ +public class ConstraintCheck extends Constraint { + + private TableFilter filter; + private Expression expr; + + public ConstraintCheck(Schema schema, int id, String name, Table table) { + super(schema, id, name, table); + } + + @Override + public Type getConstraintType() { + return Constraint.Type.CHECK; + } + + public void setTableFilter(TableFilter filter) { + this.filter = filter; + } + + public void setExpression(Expression expr) { + this.expr = expr; + } + + @Override + public String getCreateSQLForCopy(Table forTable, String quotedName) { + StringBuilder buff = new StringBuilder("ALTER TABLE "); + forTable.getSQL(buff, DEFAULT_SQL_FLAGS).append(" ADD CONSTRAINT "); + if (forTable.isHidden()) { + buff.append("IF NOT EXISTS "); + } + buff.append(quotedName); + if (comment != null) { + buff.append(" COMMENT "); + StringUtils.quoteStringSQL(buff, comment); + } + buff.append(" CHECK"); + expr.getEnclosedSQL(buff, DEFAULT_SQL_FLAGS).append(" NOCHECK"); + return buff.toString(); + } + + private String getShortDescription() { + StringBuilder builder = new StringBuilder().append(getName()).append(": "); + expr.getTraceSQL(); + return builder.toString(); + } + + @Override + public String getCreateSQLWithoutIndexes() { + return getCreateSQL(); + } + + @Override + public String getCreateSQL() { + return getCreateSQLForCopy(table, getSQL(DEFAULT_SQL_FLAGS)); + } + + @Override + public void removeChildrenAndResources(SessionLocal session) { + table.removeConstraint(this); + database.removeMeta(session, getId()); + filter = null; + expr = null; + table = null; + invalidate(); + } + + @Override + public void checkRow(SessionLocal session, Table t, Row oldRow, Row newRow) { + if (newRow == null) { + return; + } + boolean b; + try { + Value v; + synchronized (this) { + filter.set(newRow); + v = expr.getValue(session); + } + // Both TRUE and NULL are ok + b = v.isFalse(); + } catch (DbException ex) { + throw DbException.get(ErrorCode.CHECK_CONSTRAINT_INVALID, ex, getShortDescription()); + } + if (b) { + throw DbException.get(ErrorCode.CHECK_CONSTRAINT_VIOLATED_1, getShortDescription()); + } + } + + @Override + public boolean usesIndex(Index index) { + return false; + } + + @Override + public void setIndexOwner(Index index) { + throw DbException.getInternalError(toString()); + } + + @Override + public HashSet getReferencedColumns(Table table) { + HashSet columns = new HashSet<>(); + expr.isEverything(ExpressionVisitor.getColumnsVisitor(columns, table)); + return columns; + } + + @Override + public Expression getExpression() { + return expr; + } + + @Override + public boolean isBefore() { + return true; + } + + @Override + public void checkExistingData(SessionLocal session) { + if (session.getDatabase().isStarting()) { + // don't check at startup + return; + } + StringBuilder builder = new StringBuilder().append("SELECT NULL FROM "); + filter.getTable().getSQL(builder, DEFAULT_SQL_FLAGS).append(" WHERE NOT "); + expr.getSQL(builder, DEFAULT_SQL_FLAGS, Expression.AUTO_PARENTHESES); + String sql = builder.toString(); + ResultInterface r = session.prepare(sql).query(1); + if (r.next()) { + throw DbException.get(ErrorCode.CHECK_CONSTRAINT_VIOLATED_1, getName()); + } + } + + @Override + public void rebuild() { + // nothing to do + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return expr.isEverything(visitor); + } + +} diff --git a/h2/src/main/org/h2/constraint/ConstraintDomain.java b/h2/src/main/org/h2/constraint/ConstraintDomain.java new file mode 100644 index 0000000..c866c80 --- /dev/null +++ b/h2/src/main/org/h2/constraint/ConstraintDomain.java @@ -0,0 +1,240 @@ +/* + * 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.constraint; + +import java.util.HashSet; + +import org.h2.api.ErrorCode; +import org.h2.command.Parser; +import org.h2.command.ddl.AlterDomain; +import org.h2.command.query.AllColumnsForPlan; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.index.Index; +import org.h2.message.DbException; +import org.h2.result.Row; +import org.h2.schema.Domain; +import org.h2.schema.Schema; +import org.h2.table.Column; +import org.h2.table.PlanItem; +import org.h2.table.Table; +import org.h2.table.TableFilter; +import org.h2.util.StringUtils; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * A domain constraint. + */ +public class ConstraintDomain extends Constraint { + + private Domain domain; + + private Expression expr; + + private DomainColumnResolver resolver; + + public ConstraintDomain(Schema schema, int id, String name, Domain domain) { + super(schema, id, name, null); + this.domain = domain; + resolver = new DomainColumnResolver(domain.getDataType()); + } + + @Override + public Type getConstraintType() { + return Constraint.Type.DOMAIN; + } + + /** + * Returns the domain of this constraint. + * + * @return the domain + */ + public Domain getDomain() { + return domain; + } + + /** + * Set the expression. + * + * @param session the session + * @param expr the expression + */ + public void setExpression(SessionLocal session, Expression expr) { + expr.mapColumns(resolver, 0, Expression.MAP_INITIAL); + expr = expr.optimize(session); + // check if the column is mapped + synchronized (this) { + resolver.setValue(ValueNull.INSTANCE); + expr.getValue(session); + } + this.expr = expr; + } + + @Override + public String getCreateSQLForCopy(Table forTable, String quotedName) { + throw DbException.getInternalError(toString()); + } + + @Override + public String getCreateSQLWithoutIndexes() { + return getCreateSQL(); + } + + @Override + public String getCreateSQL() { + StringBuilder builder = new StringBuilder("ALTER DOMAIN "); + domain.getSQL(builder, DEFAULT_SQL_FLAGS).append(" ADD CONSTRAINT "); + getSQL(builder, DEFAULT_SQL_FLAGS); + if (comment != null) { + builder.append(" COMMENT "); + StringUtils.quoteStringSQL(builder, comment); + } + builder.append(" CHECK"); + expr.getEnclosedSQL(builder, DEFAULT_SQL_FLAGS).append(" NOCHECK"); + return builder.toString(); + } + + @Override + public void removeChildrenAndResources(SessionLocal session) { + domain.removeConstraint(this); + database.removeMeta(session, getId()); + domain = null; + expr = null; + invalidate(); + } + + @Override + public void checkRow(SessionLocal session, Table t, Row oldRow, Row newRow) { + throw DbException.getInternalError(toString()); + } + + /** + * Check the specified value. + * + * @param session + * the session + * @param value + * the value to check + */ + public void check(SessionLocal session, Value value) { + Value v; + synchronized (this) { + resolver.setValue(value); + v = expr.getValue(session); + } + // Both TRUE and NULL are OK + if (v.isFalse()) { + throw DbException.get(ErrorCode.CHECK_CONSTRAINT_VIOLATED_1, expr.getTraceSQL()); + } + } + + /** + * Get the check constraint expression for this column. + * + * @param session the session + * @param columnName the column name + * @return the expression + */ + public Expression getCheckConstraint(SessionLocal session, String columnName) { + String sql; + if (columnName != null) { + synchronized (this) { + try { + resolver.setColumnName(columnName); + sql = expr.getSQL(DEFAULT_SQL_FLAGS); + } finally { + resolver.resetColumnName(); + } + } + return new Parser(session).parseExpression(sql); + } else { + synchronized (this) { + sql = expr.getSQL(DEFAULT_SQL_FLAGS); + } + return new Parser(session).parseDomainConstraintExpression(sql); + } + } + + @Override + public boolean usesIndex(Index index) { + return false; + } + + @Override + public void setIndexOwner(Index index) { + throw DbException.getInternalError(toString()); + } + + @Override + public HashSet getReferencedColumns(Table table) { + HashSet columns = new HashSet<>(); + expr.isEverything(ExpressionVisitor.getColumnsVisitor(columns, table)); + return columns; + } + + @Override + public Expression getExpression() { + return expr; + } + + @Override + public boolean isBefore() { + return true; + } + + @Override + public void checkExistingData(SessionLocal session) { + if (session.getDatabase().isStarting()) { + // don't check at startup + return; + } + new CheckExistingData(session, domain); + } + + @Override + public void rebuild() { + // nothing to do + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return expr.isEverything(visitor); + } + + private class CheckExistingData { + + private final SessionLocal session; + + CheckExistingData(SessionLocal session, Domain domain) { + this.session = session; + checkDomain(null, domain); + } + + private boolean checkColumn(Domain domain, Column targetColumn) { + Table table = targetColumn.getTable(); + TableFilter filter = new TableFilter(session, table, null, true, null, 0, null); + TableFilter[] filters = { filter }; + PlanItem item = filter.getBestPlanItem(session, filters, 0, new AllColumnsForPlan(filters)); + filter.setPlanItem(item); + filter.prepare(); + filter.startQuery(session); + filter.reset(); + while (filter.next()) { + check(session, filter.getValue(targetColumn)); + } + return false; + } + + private boolean checkDomain(Domain domain, Domain targetDomain) { + AlterDomain.forAllDependencies(session, targetDomain, this::checkColumn, this::checkDomain, false); + return false; + } + + } + +} diff --git a/h2/src/main/org/h2/constraint/ConstraintReferential.java b/h2/src/main/org/h2/constraint/ConstraintReferential.java new file mode 100644 index 0000000..7bdde5c --- /dev/null +++ b/h2/src/main/org/h2/constraint/ConstraintReferential.java @@ -0,0 +1,630 @@ +/* + * 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.constraint; + +import java.util.ArrayList; +import java.util.HashSet; +import org.h2.api.ErrorCode; +import org.h2.command.Prepared; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.Parameter; +import org.h2.index.Cursor; +import org.h2.index.Index; +import org.h2.message.DbException; +import org.h2.result.ResultInterface; +import org.h2.result.Row; +import org.h2.result.SearchRow; +import org.h2.schema.Schema; +import org.h2.table.Column; +import org.h2.table.IndexColumn; +import org.h2.table.Table; +import org.h2.util.StringUtils; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * A referential constraint. + */ +public class ConstraintReferential extends Constraint { + + private IndexColumn[] columns; + private IndexColumn[] refColumns; + private ConstraintActionType deleteAction = ConstraintActionType.RESTRICT; + private ConstraintActionType updateAction = ConstraintActionType.RESTRICT; + private Table refTable; + private Index index; + private ConstraintUnique refConstraint; + private boolean indexOwner; + private String deleteSQL, updateSQL; + private boolean skipOwnTable; + + public ConstraintReferential(Schema schema, int id, String name, Table table) { + super(schema, id, name, table); + } + + @Override + public Type getConstraintType() { + return Constraint.Type.REFERENTIAL; + } + + /** + * Create the SQL statement of this object so a copy of the table can be + * made. + * + * @param forTable the table to create the object for + * @param quotedName the name of this object (quoted if necessary) + * @return the SQL statement + */ + @Override + public String getCreateSQLForCopy(Table forTable, String quotedName) { + return getCreateSQLForCopy(forTable, refTable, quotedName, true); + } + + /** + * Create the SQL statement of this object so a copy of the table can be + * made. + * + * @param forTable the table to create the object for + * @param forRefTable the referenced table + * @param quotedName the name of this object (quoted if necessary) + * @param internalIndex add the index name to the statement + * @return the SQL statement + */ + public String getCreateSQLForCopy(Table forTable, Table forRefTable, + String quotedName, boolean internalIndex) { + StringBuilder builder = new StringBuilder("ALTER TABLE "); + forTable.getSQL(builder, DEFAULT_SQL_FLAGS).append(" ADD CONSTRAINT "); + if (forTable.isHidden()) { + builder.append("IF NOT EXISTS "); + } + builder.append(quotedName); + if (comment != null) { + builder.append(" COMMENT "); + StringUtils.quoteStringSQL(builder, comment); + } + IndexColumn[] cols = columns; + IndexColumn[] refCols = refColumns; + builder.append(" FOREIGN KEY("); + IndexColumn.writeColumns(builder, cols, DEFAULT_SQL_FLAGS); + builder.append(')'); + if (internalIndex && indexOwner && forTable == this.table) { + builder.append(" INDEX "); + index.getSQL(builder, DEFAULT_SQL_FLAGS); + } + builder.append(" REFERENCES "); + if (this.table == this.refTable) { + // self-referencing constraints: need to use new table + forTable.getSQL(builder, DEFAULT_SQL_FLAGS); + } else { + forRefTable.getSQL(builder, DEFAULT_SQL_FLAGS); + } + builder.append('('); + IndexColumn.writeColumns(builder, refCols, DEFAULT_SQL_FLAGS); + builder.append(')'); + if (deleteAction != ConstraintActionType.RESTRICT) { + builder.append(" ON DELETE ").append(deleteAction.getSqlName()); + } + if (updateAction != ConstraintActionType.RESTRICT) { + builder.append(" ON UPDATE ").append(updateAction.getSqlName()); + } + return builder.append(" NOCHECK").toString(); + } + + + /** + * Get a short description of the constraint. This includes the constraint + * name (if set), and the constraint expression. + * + * @param searchIndex the index, or null + * @param check the row, or null + * @return the description + */ + private String getShortDescription(Index searchIndex, SearchRow check) { + StringBuilder builder = new StringBuilder(getName()).append(": "); + table.getSQL(builder, TRACE_SQL_FLAGS).append(" FOREIGN KEY("); + IndexColumn.writeColumns(builder, columns, TRACE_SQL_FLAGS); + builder.append(") REFERENCES "); + refTable.getSQL(builder, TRACE_SQL_FLAGS).append('('); + IndexColumn.writeColumns(builder, refColumns, TRACE_SQL_FLAGS); + builder.append(')'); + if (searchIndex != null && check != null) { + builder.append(" ("); + Column[] cols = searchIndex.getColumns(); + int len = Math.min(columns.length, cols.length); + for (int i = 0; i < len; i++) { + int idx = cols[i].getColumnId(); + Value c = check.getValue(idx); + if (i > 0) { + builder.append(", "); + } + builder.append(c == null ? "" : c.toString()); + } + builder.append(')'); + } + return builder.toString(); + } + + @Override + public String getCreateSQLWithoutIndexes() { + return getCreateSQLForCopy(table, refTable, getSQL(DEFAULT_SQL_FLAGS), false); + } + + @Override + public String getCreateSQL() { + return getCreateSQLForCopy(table, getSQL(DEFAULT_SQL_FLAGS)); + } + + public void setColumns(IndexColumn[] cols) { + columns = cols; + } + + public IndexColumn[] getColumns() { + return columns; + } + + @Override + public HashSet getReferencedColumns(Table table) { + HashSet result = new HashSet<>(); + if (table == this.table) { + for (IndexColumn c : columns) { + result.add(c.column); + } + } else if (table == this.refTable) { + for (IndexColumn c : refColumns) { + result.add(c.column); + } + } + return result; + } + + public void setRefColumns(IndexColumn[] refCols) { + refColumns = refCols; + } + + public IndexColumn[] getRefColumns() { + return refColumns; + } + + public void setRefTable(Table refTable) { + this.refTable = refTable; + if (refTable.isTemporary()) { + setTemporary(true); + } + } + + /** + * Set the index to use for this constraint. + * + * @param index the index + * @param isOwner true if the index is generated by the system and belongs + * to this constraint + */ + public void setIndex(Index index, boolean isOwner) { + this.index = index; + this.indexOwner = isOwner; + } + + /** + * Set the unique constraint of the referenced table to use for this + * constraint. + * + * @param refConstraint + * the unique constraint + */ + public void setRefConstraint(ConstraintUnique refConstraint) { + this.refConstraint = refConstraint; + } + + @Override + public void removeChildrenAndResources(SessionLocal session) { + table.removeConstraint(this); + refTable.removeConstraint(this); + if (indexOwner) { + table.removeIndexOrTransferOwnership(session, index); + } + database.removeMeta(session, getId()); + refTable = null; + index = null; + refConstraint = null; + columns = null; + refColumns = null; + deleteSQL = null; + updateSQL = null; + table = null; + invalidate(); + } + + @Override + public void checkRow(SessionLocal session, Table t, Row oldRow, Row newRow) { + if (!database.getReferentialIntegrity()) { + return; + } + if (!table.getCheckForeignKeyConstraints() || + !refTable.getCheckForeignKeyConstraints()) { + return; + } + if (t == table) { + if (!skipOwnTable) { + checkRowOwnTable(session, oldRow, newRow); + } + } + if (t == refTable) { + checkRowRefTable(session, oldRow, newRow); + } + } + + private void checkRowOwnTable(SessionLocal session, Row oldRow, Row newRow) { + if (newRow == null) { + return; + } + boolean constraintColumnsEqual = oldRow != null; + for (IndexColumn col : columns) { + int idx = col.column.getColumnId(); + Value v = newRow.getValue(idx); + if (v == ValueNull.INSTANCE) { + // return early if one of the columns is NULL + return; + } + if (constraintColumnsEqual) { + if (!session.areEqual(v, oldRow.getValue(idx))) { + constraintColumnsEqual = false; + } + } + } + if (constraintColumnsEqual) { + // return early if the key columns didn't change + return; + } + if (refTable == table) { + // special case self referencing constraints: + // check the inserted row first + boolean self = true; + for (int i = 0, len = columns.length; i < len; i++) { + int idx = columns[i].column.getColumnId(); + Value v = newRow.getValue(idx); + Column refCol = refColumns[i].column; + int refIdx = refCol.getColumnId(); + Value r = newRow.getValue(refIdx); + if (!session.areEqual(r, v)) { + self = false; + break; + } + } + if (self) { + return; + } + } + Row check = refTable.getTemplateRow(); + for (int i = 0, len = columns.length; i < len; i++) { + int idx = columns[i].column.getColumnId(); + Value v = newRow.getValue(idx); + Column refCol = refColumns[i].column; + int refIdx = refCol.getColumnId(); + check.setValue(refIdx, refCol.convert(session, v)); + } + Index refIndex = refConstraint.getIndex(); + if (!existsRow(session, refIndex, check, null)) { + throw DbException.get(ErrorCode.REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1, + getShortDescription(refIndex, check)); + } + } + + private boolean existsRow(SessionLocal session, Index searchIndex, + SearchRow check, Row excluding) { + Table searchTable = searchIndex.getTable(); + searchTable.lock(session, Table.READ_LOCK); + Cursor cursor = searchIndex.find(session, check, check); + while (cursor.next()) { + SearchRow found; + found = cursor.getSearchRow(); + if (excluding != null && found.getKey() == excluding.getKey()) { + continue; + } + Column[] cols = searchIndex.getColumns(); + boolean allEqual = true; + int len = Math.min(columns.length, cols.length); + for (int i = 0; i < len; i++) { + int idx = cols[i].getColumnId(); + Value c = check.getValue(idx); + Value f = found.getValue(idx); + if (searchTable.compareValues(session, c, f) != 0) { + allEqual = false; + break; + } + } + if (allEqual) { + return true; + } + } + return false; + } + + private boolean isEqual(Row oldRow, Row newRow) { + return refConstraint.getIndex().compareRows(oldRow, newRow) == 0; + } + + private void checkRow(SessionLocal session, Row oldRow) { + SearchRow check = table.getRowFactory().createRow(); + for (int i = 0, len = columns.length; i < len; i++) { + Column refCol = refColumns[i].column; + int refIdx = refCol.getColumnId(); + Column col = columns[i].column; + Value v = col.convert(session, oldRow.getValue(refIdx)); + if (v == ValueNull.INSTANCE) { + return; + } + check.setValue(col.getColumnId(), v); + } + // exclude the row only for self-referencing constraints + Row excluding = (refTable == table) ? oldRow : null; + if (existsRow(session, index, check, excluding)) { + throw DbException.get(ErrorCode.REFERENTIAL_INTEGRITY_VIOLATED_CHILD_EXISTS_1, + getShortDescription(index, check)); + } + } + + private void checkRowRefTable(SessionLocal session, Row oldRow, Row newRow) { + if (oldRow == null) { + // this is an insert + return; + } + if (newRow != null && isEqual(oldRow, newRow)) { + // on an update, if both old and new are the same, don't do anything + return; + } + if (newRow == null) { + // this is a delete + if (deleteAction == ConstraintActionType.RESTRICT) { + checkRow(session, oldRow); + } else { + int i = deleteAction == ConstraintActionType.CASCADE ? 0 : columns.length; + Prepared deleteCommand = getDelete(session); + setWhere(deleteCommand, i, oldRow); + updateWithSkipCheck(deleteCommand); + } + } else { + // this is an update + if (updateAction == ConstraintActionType.RESTRICT) { + checkRow(session, oldRow); + } else { + Prepared updateCommand = getUpdate(session); + if (updateAction == ConstraintActionType.CASCADE) { + ArrayList params = updateCommand.getParameters(); + for (int i = 0, len = columns.length; i < len; i++) { + Parameter param = params.get(i); + Column refCol = refColumns[i].column; + param.setValue(newRow.getValue(refCol.getColumnId())); + } + } + setWhere(updateCommand, columns.length, oldRow); + updateWithSkipCheck(updateCommand); + } + } + } + + private void updateWithSkipCheck(Prepared prep) { + // TODO constraints: maybe delay the update or support delayed checks + // (until commit) + try { + // TODO multithreaded kernel: this works only if nobody else updates + // this or the ref table at the same time + skipOwnTable = true; + prep.update(); + } finally { + skipOwnTable = false; + } + } + + private void setWhere(Prepared command, int pos, Row row) { + for (int i = 0, len = refColumns.length; i < len; i++) { + int idx = refColumns[i].column.getColumnId(); + Value v = row.getValue(idx); + ArrayList params = command.getParameters(); + Parameter param = params.get(pos + i); + param.setValue(v); + } + } + + public ConstraintActionType getDeleteAction() { + return deleteAction; + } + + /** + * Set the action to apply (restrict, cascade,...) on a delete. + * + * @param action the action + */ + public void setDeleteAction(ConstraintActionType action) { + if (action == deleteAction && deleteSQL == null) { + return; + } + if (deleteAction != ConstraintActionType.RESTRICT) { + throw DbException.get(ErrorCode.CONSTRAINT_ALREADY_EXISTS_1, "ON DELETE"); + } + this.deleteAction = action; + buildDeleteSQL(); + } + + /** + * Update the constraint SQL when a referenced column is renamed. + */ + public void updateOnTableColumnRename() { + if (deleteAction != null) { + deleteSQL = null; + buildDeleteSQL(); + } + if (updateAction != null) { + updateSQL = null; + buildUpdateSQL(); + } + } + + private void buildDeleteSQL() { + if (deleteAction == ConstraintActionType.RESTRICT) { + return; + } + StringBuilder builder = new StringBuilder(); + if (deleteAction == ConstraintActionType.CASCADE) { + builder.append("DELETE FROM "); + table.getSQL(builder, DEFAULT_SQL_FLAGS); + } else { + appendUpdate(builder); + } + appendWhere(builder); + deleteSQL = builder.toString(); + } + + private Prepared getUpdate(SessionLocal session) { + return prepare(session, updateSQL, updateAction); + } + + private Prepared getDelete(SessionLocal session) { + return prepare(session, deleteSQL, deleteAction); + } + + public ConstraintActionType getUpdateAction() { + return updateAction; + } + + /** + * Set the action to apply (restrict, cascade,...) on an update. + * + * @param action the action + */ + public void setUpdateAction(ConstraintActionType action) { + if (action == updateAction && updateSQL == null) { + return; + } + if (updateAction != ConstraintActionType.RESTRICT) { + throw DbException.get(ErrorCode.CONSTRAINT_ALREADY_EXISTS_1, "ON UPDATE"); + } + this.updateAction = action; + buildUpdateSQL(); + } + + private void buildUpdateSQL() { + if (updateAction == ConstraintActionType.RESTRICT) { + return; + } + StringBuilder builder = new StringBuilder(); + appendUpdate(builder); + appendWhere(builder); + updateSQL = builder.toString(); + } + + @Override + public void rebuild() { + buildUpdateSQL(); + buildDeleteSQL(); + } + + private Prepared prepare(SessionLocal session, String sql, ConstraintActionType action) { + Prepared command = session.prepare(sql); + if (action != ConstraintActionType.CASCADE) { + ArrayList params = command.getParameters(); + for (int i = 0, len = columns.length; i < len; i++) { + Column column = columns[i].column; + Parameter param = params.get(i); + Value value; + if (action == ConstraintActionType.SET_NULL) { + value = ValueNull.INSTANCE; + } else { + Expression expr = column.getEffectiveDefaultExpression(); + if (expr == null) { + throw DbException.get(ErrorCode.NO_DEFAULT_SET_1, column.getName()); + } + value = expr.getValue(session); + } + param.setValue(value); + } + } + return command; + } + + private void appendUpdate(StringBuilder builder) { + builder.append("UPDATE "); + table.getSQL(builder, DEFAULT_SQL_FLAGS).append(" SET "); + IndexColumn.writeColumns(builder, columns, ", ", "=?", IndexColumn.SQL_NO_ORDER); + } + + private void appendWhere(StringBuilder builder) { + builder.append(" WHERE "); + IndexColumn.writeColumns(builder, columns, " AND ", "=?", IndexColumn.SQL_NO_ORDER); + } + + @Override + public Table getRefTable() { + return refTable; + } + + @Override + public boolean usesIndex(Index idx) { + return idx == index; + } + + @Override + public void setIndexOwner(Index index) { + if (this.index == index) { + indexOwner = true; + } else { + throw DbException.getInternalError(index + " " + toString()); + } + } + + @Override + public boolean isBefore() { + return false; + } + + @Override + public void checkExistingData(SessionLocal session) { + if (session.getDatabase().isStarting()) { + // don't check at startup + return; + } + StringBuilder builder = new StringBuilder("SELECT 1 FROM (SELECT "); + IndexColumn.writeColumns(builder, columns, IndexColumn.SQL_NO_ORDER); + builder.append(" FROM "); + table.getSQL(builder, DEFAULT_SQL_FLAGS).append(" WHERE "); + IndexColumn.writeColumns(builder, columns, " AND ", " IS NOT NULL ", IndexColumn.SQL_NO_ORDER); + builder.append(" ORDER BY "); + IndexColumn.writeColumns(builder, columns, DEFAULT_SQL_FLAGS); + builder.append(") C WHERE NOT EXISTS(SELECT 1 FROM "); + refTable.getSQL(builder, DEFAULT_SQL_FLAGS).append(" P WHERE "); + for (int i = 0, l = columns.length; i < l; i++) { + if (i > 0) { + builder.append(" AND "); + } + builder.append("C."); + columns[i].column.getSQL(builder, DEFAULT_SQL_FLAGS).append('=').append("P."); + refColumns[i].column.getSQL(builder, DEFAULT_SQL_FLAGS); + } + builder.append(')'); + + session.startStatementWithinTransaction(null); + try { + ResultInterface r = session.prepare(builder.toString()).query(1); + if (r.next()) { + throw DbException.get(ErrorCode.REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1, + getShortDescription(null, null)); + } + } finally { + session.endStatement(); + } + } + + @Override + public Index getIndex() { + return index; + } + + @Override + public ConstraintUnique getReferencedConstraint() { + return refConstraint; + } + +} diff --git a/h2/src/main/org/h2/constraint/ConstraintUnique.java b/h2/src/main/org/h2/constraint/ConstraintUnique.java new file mode 100644 index 0000000..3da09e0 --- /dev/null +++ b/h2/src/main/org/h2/constraint/ConstraintUnique.java @@ -0,0 +1,162 @@ +/* + * 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.constraint; + +import java.util.ArrayList; +import java.util.HashSet; +import org.h2.engine.SessionLocal; +import org.h2.index.Index; +import org.h2.result.Row; +import org.h2.schema.Schema; +import org.h2.table.Column; +import org.h2.table.IndexColumn; +import org.h2.table.Table; +import org.h2.util.StringUtils; + +/** + * A unique constraint. This object always backed by a unique index. + */ +public class ConstraintUnique extends Constraint { + + private Index index; + private boolean indexOwner; + private IndexColumn[] columns; + private final boolean primaryKey; + + public ConstraintUnique(Schema schema, int id, String name, Table table, + boolean primaryKey) { + super(schema, id, name, table); + this.primaryKey = primaryKey; + } + + @Override + public Type getConstraintType() { + return primaryKey ? Constraint.Type.PRIMARY_KEY : Constraint.Type.UNIQUE; + } + + @Override + public String getCreateSQLForCopy(Table forTable, String quotedName) { + return getCreateSQLForCopy(forTable, quotedName, true); + } + + private String getCreateSQLForCopy(Table forTable, String quotedName, boolean internalIndex) { + StringBuilder builder = new StringBuilder("ALTER TABLE "); + forTable.getSQL(builder, DEFAULT_SQL_FLAGS).append(" ADD CONSTRAINT "); + if (forTable.isHidden()) { + builder.append("IF NOT EXISTS "); + } + builder.append(quotedName); + if (comment != null) { + builder.append(" COMMENT "); + StringUtils.quoteStringSQL(builder, comment); + } + builder.append(' ').append(getConstraintType().getSqlName()).append('('); + IndexColumn.writeColumns(builder, columns, DEFAULT_SQL_FLAGS).append(')'); + if (internalIndex && indexOwner && forTable == this.table) { + builder.append(" INDEX "); + index.getSQL(builder, DEFAULT_SQL_FLAGS); + } + return builder.toString(); + } + + @Override + public String getCreateSQLWithoutIndexes() { + return getCreateSQLForCopy(table, getSQL(DEFAULT_SQL_FLAGS), false); + } + + @Override + public String getCreateSQL() { + return getCreateSQLForCopy(table, getSQL(DEFAULT_SQL_FLAGS)); + } + + public void setColumns(IndexColumn[] columns) { + this.columns = columns; + } + + public IndexColumn[] getColumns() { + return columns; + } + + /** + * Set the index to use for this unique constraint. + * + * @param index the index + * @param isOwner true if the index is generated by the system and belongs + * to this constraint + */ + public void setIndex(Index index, boolean isOwner) { + this.index = index; + this.indexOwner = isOwner; + } + + @Override + public void removeChildrenAndResources(SessionLocal session) { + ArrayList constraints = table.getConstraints(); + if (constraints != null) { + constraints = new ArrayList<>(table.getConstraints()); + for (Constraint c : constraints) { + if (c.getReferencedConstraint() == this) { + database.removeSchemaObject(session, c); + } + } + } + table.removeConstraint(this); + if (indexOwner) { + table.removeIndexOrTransferOwnership(session, index); + } + database.removeMeta(session, getId()); + index = null; + columns = null; + table = null; + invalidate(); + } + + @Override + public void checkRow(SessionLocal session, Table t, Row oldRow, Row newRow) { + // unique index check is enough + } + + @Override + public boolean usesIndex(Index idx) { + return idx == index; + } + + @Override + public void setIndexOwner(Index index) { + indexOwner = true; + } + + @Override + public HashSet getReferencedColumns(Table table) { + HashSet result = new HashSet<>(); + for (IndexColumn c : columns) { + result.add(c.column); + } + return result; + } + + @Override + public boolean isBefore() { + return true; + } + + @Override + public void checkExistingData(SessionLocal session) { + // no need to check: when creating the unique index any problems are + // found + } + + @Override + public Index getIndex() { + return index; + } + + @Override + public void rebuild() { + // nothing to do + } + +} diff --git a/h2/src/main/org/h2/constraint/DomainColumnResolver.java b/h2/src/main/org/h2/constraint/DomainColumnResolver.java new file mode 100644 index 0000000..1d01e1a --- /dev/null +++ b/h2/src/main/org/h2/constraint/DomainColumnResolver.java @@ -0,0 +1,72 @@ +/* + * 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.constraint; + +import org.h2.table.Column; +import org.h2.table.ColumnResolver; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * The single column resolver resolves the VALUE column. + * It is used to parse a domain constraint. + */ +public class DomainColumnResolver implements ColumnResolver { + + private final Column column; + private Value value; + private String name; + + public DomainColumnResolver(TypeInfo typeInfo) { + this.column = new Column("VALUE", typeInfo); + } + + public void setValue(Value value) { + this.value = value; + } + + @Override + public Value getValue(Column col) { + return value; + } + + @Override + public Column[] getColumns() { + return new Column[] { column }; + } + + @Override + public Column findColumn(String name) { + return null; + } + + void setColumnName(String newName) { + name = newName; + } + + void resetColumnName() { + name = null; + } + + /** + * Return column name to use or null. + * + * @return column name to use or null + */ + public String getColumnName() { + return name; + } + + /** + * Return the type of the column. + * + * @return the type of the column + */ + public TypeInfo getValueType() { + return column.getType(); + } + +} diff --git a/h2/src/main/org/h2/constraint/package.html b/h2/src/main/org/h2/constraint/package.html new file mode 100644 index 0000000..a7e1d88 --- /dev/null +++ b/h2/src/main/org/h2/constraint/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Database constraints such as check constraints, unique constraints, and referential constraints. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/engine/CastDataProvider.java b/h2/src/main/org/h2/engine/CastDataProvider.java new file mode 100644 index 0000000..9682dda --- /dev/null +++ b/h2/src/main/org/h2/engine/CastDataProvider.java @@ -0,0 +1,53 @@ +/* + * 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.engine; + +import org.h2.api.JavaObjectSerializer; +import org.h2.util.TimeZoneProvider; +import org.h2.value.ValueTimestampTimeZone; + +/** + * Provides information for type casts and comparison operations. + */ +public interface CastDataProvider { + + /** + * Returns the current timestamp with maximum resolution. The value must be + * the same within a transaction or within execution of a command. + * + * @return the current timestamp for CURRENT_TIMESTAMP(9) + */ + ValueTimestampTimeZone currentTimestamp(); + + /** + * Returns the current time zone. + * + * @return the current time zone + */ + TimeZoneProvider currentTimeZone(); + + /** + * Returns the database mode. + * + * @return the database mode + */ + Mode getMode(); + + /** + * Returns the custom Java object serializer, or {@code null}. + * + * @return the custom Java object serializer, or {@code null} + */ + JavaObjectSerializer getJavaObjectSerializer(); + + /** + * Returns are ENUM values 0-based. + * + * @return are ENUM values 0-based + */ + boolean zeroBasedEnums(); + +} diff --git a/h2/src/main/org/h2/engine/Comment.java b/h2/src/main/org/h2/engine/Comment.java new file mode 100644 index 0000000..e3af80f --- /dev/null +++ b/h2/src/main/org/h2/engine/Comment.java @@ -0,0 +1,114 @@ +/* + * 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.engine; + +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.table.Table; +import org.h2.util.StringUtils; + +/** + * Represents a database object comment. + */ +public final class Comment extends DbObject { + + private final int objectType; + private final String quotedObjectName; + private String commentText; + + public Comment(Database database, int id, DbObject obj) { + super(database, id, getKey(obj), Trace.DATABASE); + this.objectType = obj.getType(); + 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: + return "CONSTANT"; + case DbObject.CONSTRAINT: + return "CONSTRAINT"; + case DbObject.FUNCTION_ALIAS: + return "ALIAS"; + case DbObject.INDEX: + return "INDEX"; + case DbObject.ROLE: + return "ROLE"; + case DbObject.SCHEMA: + return "SCHEMA"; + case DbObject.SEQUENCE: + return "SEQUENCE"; + case DbObject.TABLE_OR_VIEW: + return "TABLE"; + case DbObject.TRIGGER: + return "TRIGGER"; + case DbObject.USER: + return "USER"; + case DbObject.DOMAIN: + return "DOMAIN"; + default: + // not supported by parser, but required when trying to find a + // comment + return "type" + type; + } + } + + @Override + public String getCreateSQL() { + StringBuilder buff = new StringBuilder("COMMENT ON "); + buff.append(getTypeName(objectType)).append(' '). + append(quotedObjectName).append(" IS "); + if (commentText == null) { + buff.append("NULL"); + } else { + StringUtils.quoteStringSQL(buff, commentText); + } + return buff.toString(); + } + + @Override + public int getType() { + return DbObject.COMMENT; + } + + @Override + public void removeChildrenAndResources(SessionLocal session) { + database.removeMeta(session, getId()); + } + + @Override + public void checkRename() { + throw DbException.getInternalError(); + } + + /** + * Get the comment key name for the given database object. This key name is + * used internally to associate the comment to the object. + * + * @param obj the object + * @return the key name + */ + static String getKey(DbObject obj) { + StringBuilder builder = new StringBuilder(getTypeName(obj.getType())).append(' '); + obj.getSQL(builder, DEFAULT_SQL_FLAGS); + return builder.toString(); + } + + /** + * Set the comment text. + * + * @param comment the text + */ + public void setCommentText(String comment) { + this.commentText = comment; + } + +} diff --git a/h2/src/main/org/h2/engine/ConnectionInfo.java b/h2/src/main/org/h2/engine/ConnectionInfo.java new file mode 100644 index 0000000..fdd0ee2 --- /dev/null +++ b/h2/src/main/org/h2/engine/ConnectionInfo.java @@ -0,0 +1,761 @@ +/* + * 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.engine; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Properties; + +import org.h2.api.ErrorCode; +import org.h2.command.dml.SetTypes; +import org.h2.message.DbException; +import org.h2.security.SHA256; +import org.h2.store.fs.FileUtils; +import org.h2.store.fs.encrypt.FilePathEncrypt; +import org.h2.store.fs.rec.FilePathRec; +import org.h2.util.IOUtils; +import org.h2.util.NetworkConnectionInfo; +import org.h2.util.SortedProperties; +import org.h2.util.StringUtils; +import org.h2.util.TimeZoneProvider; +import org.h2.util.Utils; + +/** + * Encapsulates the connection settings, including user name and password. + */ +public class ConnectionInfo implements Cloneable { + + private static final HashSet KNOWN_SETTINGS; + + private static final HashSet IGNORED_BY_PARSER; + + private Properties prop = new Properties(); + private String originalURL; + private String url; + private String user; + private byte[] filePasswordHash; + private byte[] fileEncryptionKey; + private byte[] userPasswordHash; + + private TimeZoneProvider timeZone; + + /** + * The database name + */ + private String name; + private String nameNormalized; + private boolean remote; + private boolean ssl; + private boolean persistent; + private boolean unnamed; + + private NetworkConnectionInfo networkConnectionInfo; + + /** + * Create a connection info object. + * + * @param name the database name (including tags), but without the + * "jdbc:h2:" prefix + */ + public ConnectionInfo(String name) { + this.name = name; + this.url = Constants.START_URL + name; + parseName(); + } + + /** + * Create a connection info object. + * + * @param u the database URL (must start with jdbc:h2:) + * @param info the connection properties or {@code null} + * @param user the user name or {@code null} + * @param password + * the password as {@code String} or {@code char[]}, or + * {@code null} + */ + public ConnectionInfo(String u, Properties info, String user, Object password) { + u = remapURL(u); + originalURL = url = u; + if (!u.startsWith(Constants.START_URL)) { + throw getFormatException(); + } + if (info != null) { + readProperties(info); + } + if (user != null) { + prop.put("USER", user); + } + if (password != null) { + prop.put("PASSWORD", password); + } + readSettingsFromURL(); + Object timeZoneName = prop.remove("TIME ZONE"); + if (timeZoneName != null) { + timeZone = TimeZoneProvider.ofId(timeZoneName.toString()); + } + setUserName(removeProperty("USER", "")); + name = url.substring(Constants.START_URL.length()); + parseName(); + convertPasswords(); + String recoverTest = removeProperty("RECOVER_TEST", null); + if (recoverTest != null) { + FilePathRec.register(); + try { + Utils.callStaticMethod("org.h2.store.RecoverTester.init", recoverTest); + } catch (Exception e) { + throw DbException.convert(e); + } + name = "rec:" + name; + } + } + + static { + String[] commonSettings = { // + "ACCESS_MODE_DATA", "AUTO_RECONNECT", "AUTO_SERVER", "AUTO_SERVER_PORT", // + "CACHE_TYPE", // + "FILE_LOCK", // + "JMX", // + "NETWORK_TIMEOUT", // + "OLD_INFORMATION_SCHEMA", "OPEN_NEW", // + "PAGE_SIZE", // + "RECOVER", // + }; + String[] settings = { // + "AUTHREALM", "AUTHZPWD", "AUTOCOMMIT", // + "CIPHER", "CREATE", // + "FORBID_CREATION", // + "IGNORE_UNKNOWN_SETTINGS", "IFEXISTS", "INIT", // + "NO_UPGRADE", // + "PASSWORD", "PASSWORD_HASH", // + "RECOVER_TEST", // + "USER" // + }; + HashSet set = new HashSet<>(128); + set.addAll(SetTypes.getTypes()); + for (String setting : commonSettings) { + if (!set.add(setting)) { + throw DbException.getInternalError(setting); + } + } + for (String setting : settings) { + if (!set.add(setting)) { + throw DbException.getInternalError(setting); + } + } + KNOWN_SETTINGS = set; + settings = new String[] { // + "ASSERT", // + "BINARY_COLLATION", // + "DB_CLOSE_ON_EXIT", // + "PAGE_STORE", // + "UUID_COLLATION", // + }; + set = new HashSet<>(32); + for (String setting : commonSettings) { + set.add(setting); + } + for (String setting : settings) { + set.add(setting); + } + IGNORED_BY_PARSER = set; + } + + private static boolean isKnownSetting(String s) { + return KNOWN_SETTINGS.contains(s); + } + + /** + * Returns whether setting with the specified name should be ignored by + * parser. + * + * @param name + * the name of the setting + * @return whether setting with the specified name should be ignored by + * parser + */ + public static boolean isIgnoredByParser(String name) { + return IGNORED_BY_PARSER.contains(name); + } + + @Override + public ConnectionInfo clone() throws CloneNotSupportedException { + ConnectionInfo clone = (ConnectionInfo) super.clone(); + clone.prop = (Properties) prop.clone(); + clone.filePasswordHash = Utils.cloneByteArray(filePasswordHash); + clone.fileEncryptionKey = Utils.cloneByteArray(fileEncryptionKey); + clone.userPasswordHash = Utils.cloneByteArray(userPasswordHash); + return clone; + } + + private void parseName() { + if (".".equals(name)) { + name = "mem:"; + } + if (name.startsWith("tcp:")) { + remote = true; + name = name.substring("tcp:".length()); + } else if (name.startsWith("ssl:")) { + remote = true; + ssl = true; + name = name.substring("ssl:".length()); + } else if (name.startsWith("mem:")) { + persistent = false; + if ("mem:".equals(name)) { + unnamed = true; + } + } else if (name.startsWith("file:")) { + name = name.substring("file:".length()); + persistent = true; + } else { + persistent = true; + } + if (persistent && !remote) { + name = IOUtils.nameSeparatorsToNative(name); + } + } + + /** + * Set the base directory of persistent databases, unless the database is in + * the user home folder (~). + * + * @param dir the new base directory + */ + public void setBaseDir(String dir) { + if (persistent) { + String absDir = FileUtils.unwrap(FileUtils.toRealPath(dir)); + boolean absolute = FileUtils.isAbsolute(name); + String n; + String prefix = null; + if (dir.endsWith(File.separator)) { + dir = dir.substring(0, dir.length() - 1); + } + if (absolute) { + n = name; + } else { + n = FileUtils.unwrap(name); + prefix = name.substring(0, name.length() - n.length()); + n = dir + File.separatorChar + n; + } + String normalizedName = FileUtils.unwrap(FileUtils.toRealPath(n)); + if (normalizedName.equals(absDir) || !normalizedName.startsWith(absDir)) { + // database name matches the baseDir or + // database name is clearly outside of the baseDir + throw DbException.get(ErrorCode.IO_EXCEPTION_1, normalizedName + " outside " + + absDir); + } + if (absDir.endsWith("/") || absDir.endsWith("\\")) { + // no further checks are needed for C:/ and similar + } else if (normalizedName.charAt(absDir.length()) != '/') { + // database must be within the directory + // (with baseDir=/test, the database name must not be + // /test2/x and not /test2) + throw DbException.get(ErrorCode.IO_EXCEPTION_1, normalizedName + " outside " + + absDir); + } + if (!absolute) { + name = prefix + dir + File.separatorChar + FileUtils.unwrap(name); + } + } + } + + /** + * Check if this is a remote connection. + * + * @return true if it is + */ + public boolean isRemote() { + return remote; + } + + /** + * Check if the referenced database is persistent. + * + * @return true if it is + */ + public boolean isPersistent() { + return persistent; + } + + /** + * Check if the referenced database is an unnamed in-memory database. + * + * @return true if it is + */ + boolean isUnnamedInMemory() { + return unnamed; + } + + private void readProperties(Properties info) { + Object[] list = info.keySet().toArray(); + DbSettings s = null; + for (Object k : list) { + String key = StringUtils.toUpperEnglish(k.toString()); + if (prop.containsKey(key)) { + throw DbException.get(ErrorCode.DUPLICATE_PROPERTY_1, key); + } + Object value = info.get(k); + if (isKnownSetting(key)) { + prop.put(key, value); + } else { + if (s == null) { + s = getDbSettings(); + } + if (s.containsKey(key)) { + prop.put(key, value); + } + } + } + } + + private void readSettingsFromURL() { + DbSettings defaultSettings = DbSettings.DEFAULT; + int idx = url.indexOf(';'); + if (idx >= 0) { + String settings = url.substring(idx + 1); + url = url.substring(0, idx); + String unknownSetting = null; + String[] list = StringUtils.arraySplit(settings, ';', false); + for (String setting : list) { + if (setting.isEmpty()) { + continue; + } + int equal = setting.indexOf('='); + if (equal < 0) { + throw getFormatException(); + } + String value = setting.substring(equal + 1); + String key = setting.substring(0, equal); + key = StringUtils.toUpperEnglish(key); + if (isKnownSetting(key) || defaultSettings.containsKey(key)) { + String old = prop.getProperty(key); + if (old != null && !old.equals(value)) { + throw DbException.get(ErrorCode.DUPLICATE_PROPERTY_1, key); + } + prop.setProperty(key, value); + } else { + unknownSetting = key; + } + } + if (unknownSetting != null // + && !Utils.parseBoolean(prop.getProperty("IGNORE_UNKNOWN_SETTINGS"), false, false)) { + throw DbException.get(ErrorCode.UNSUPPORTED_SETTING_1, unknownSetting); + } + } + } + + private void preservePasswordForAuthentication(Object password) { + if ((!isRemote() || isSSL()) && prop.containsKey("AUTHREALM") && password!=null) { + prop.put("AUTHZPWD",password instanceof char[] ? new String((char[])password) : password); + } + } + + private char[] removePassword() { + Object p = prop.remove("PASSWORD"); + preservePasswordForAuthentication(p); + if (p == null) { + return new char[0]; + } else if (p instanceof char[]) { + return (char[]) p; + } else { + return p.toString().toCharArray(); + } + } + + /** + * Split the password property into file password and user password if + * necessary, and convert them to the internal hash format. + */ + private void convertPasswords() { + char[] password = removePassword(); + boolean passwordHash = removeProperty("PASSWORD_HASH", false); + if (getProperty("CIPHER", null) != null) { + // split password into (filePassword+' '+userPassword) + int space = -1; + for (int i = 0, len = password.length; i < len; i++) { + if (password[i] == ' ') { + space = i; + break; + } + } + if (space < 0) { + throw DbException.get(ErrorCode.WRONG_PASSWORD_FORMAT); + } + char[] np = Arrays.copyOfRange(password, space + 1, password.length); + char[] filePassword = Arrays.copyOf(password, space); + Arrays.fill(password, (char) 0); + password = np; + fileEncryptionKey = FilePathEncrypt.getPasswordBytes(filePassword); + filePasswordHash = hashPassword(passwordHash, "file", filePassword); + } + userPasswordHash = hashPassword(passwordHash, user, password); + } + + private static byte[] hashPassword(boolean passwordHash, String userName, + char[] password) { + if (passwordHash) { + return StringUtils.convertHexToBytes(new String(password)); + } + if (userName.isEmpty() && password.length == 0) { + return new byte[0]; + } + return SHA256.getKeyPasswordHash(userName, password); + } + + /** + * Get a boolean property if it is set and return the value. + * + * @param key the property name + * @param defaultValue the default value + * @return the value + */ + public boolean getProperty(String key, boolean defaultValue) { + return Utils.parseBoolean(getProperty(key, null), defaultValue, false); + } + + /** + * Remove a boolean property if it is set and return the value. + * + * @param key the property name + * @param defaultValue the default value + * @return the value + */ + public boolean removeProperty(String key, boolean defaultValue) { + return Utils.parseBoolean(removeProperty(key, null), defaultValue, false); + } + + /** + * Remove a String property if it is set and return the value. + * + * @param key the property name + * @param defaultValue the default value + * @return the value + */ + String removeProperty(String key, String defaultValue) { + if (SysProperties.CHECK && !isKnownSetting(key)) { + throw DbException.getInternalError(key); + } + Object x = prop.remove(key); + return x == null ? defaultValue : x.toString(); + } + + /** + * Get the unique and normalized database name (excluding settings). + * + * @return the database name + */ + public String getName() { + if (!persistent) { + return name; + } + if (nameNormalized == null) { + if (!FileUtils.isAbsolute(name) && !name.contains("./") && !name.contains(".\\") && !name.contains(":/") + && !name.contains(":\\")) { + // the name could start with "./", or + // it could start with a prefix such as "nioMapped:./" + // for Windows, the path "\test" is not considered + // absolute as the drive letter is missing, + // but we consider it absolute + throw DbException.get(ErrorCode.URL_RELATIVE_TO_CWD, originalURL); + } + String suffix = Constants.SUFFIX_MV_FILE; + String n = FileUtils.toRealPath(name + suffix); + String fileName = FileUtils.getName(n); + if (fileName.length() < suffix.length() + 1) { + throw DbException.get(ErrorCode.INVALID_DATABASE_NAME_1, name); + } + nameNormalized = n.substring(0, n.length() - suffix.length()); + } + return nameNormalized; + } + + /** + * Get the file password hash if it is set. + * + * @return the password hash or null + */ + public byte[] getFilePasswordHash() { + return filePasswordHash; + } + + byte[] getFileEncryptionKey() { + return fileEncryptionKey; + } + + /** + * Get the name of the user. + * + * @return the user name + */ + public String getUserName() { + return user; + } + + /** + * Get the user password hash. + * + * @return the password hash + */ + byte[] getUserPasswordHash() { + return userPasswordHash; + } + + /** + * Get the property keys. + * + * @return the property keys + */ + String[] getKeys() { + return prop.keySet().toArray(new String[prop.size()]); + } + + /** + * Get the value of the given property. + * + * @param key the property key + * @return the value as a String + */ + String getProperty(String key) { + Object value = prop.get(key); + if (!(value instanceof String)) { + return null; + } + return value.toString(); + } + + /** + * Get the value of the given property. + * + * @param key the property key + * @param defaultValue the default value + * @return the value as a String + */ + int getProperty(String key, int defaultValue) { + if (SysProperties.CHECK && !isKnownSetting(key)) { + throw DbException.getInternalError(key); + } + String s = getProperty(key); + return s == null ? defaultValue : Integer.parseInt(s); + } + + /** + * Get the value of the given property. + * + * @param key the property key + * @param defaultValue the default value + * @return the value as a String + */ + public String getProperty(String key, String defaultValue) { + if (SysProperties.CHECK && !isKnownSetting(key)) { + throw DbException.getInternalError(key); + } + String s = getProperty(key); + return s == null ? defaultValue : s; + } + + /** + * Get the value of the given property. + * + * @param setting the setting id + * @param defaultValue the default value + * @return the value as a String + */ + String getProperty(int setting, String defaultValue) { + String key = SetTypes.getTypeName(setting); + String s = getProperty(key); + return s == null ? defaultValue : s; + } + + /** + * Get the value of the given property. + * + * @param setting the setting id + * @param defaultValue the default value + * @return the value as an integer + */ + int getIntProperty(int setting, int defaultValue) { + String key = SetTypes.getTypeName(setting); + String s = getProperty(key, null); + try { + return s == null ? defaultValue : Integer.decode(s); + } catch (NumberFormatException e) { + return defaultValue; + } + } + + /** + * Check if this is a remote connection with SSL enabled. + * + * @return true if it is + */ + boolean isSSL() { + return ssl; + } + + /** + * Overwrite the user name. The user name is case-insensitive and stored in + * uppercase. English conversion is used. + * + * @param name the user name + */ + public void setUserName(String name) { + this.user = StringUtils.toUpperEnglish(name); + } + + /** + * Set the user password hash. + * + * @param hash the new hash value + */ + public void setUserPasswordHash(byte[] hash) { + this.userPasswordHash = hash; + } + + /** + * Set the file password hash. + * + * @param hash the new hash value + */ + public void setFilePasswordHash(byte[] hash) { + this.filePasswordHash = hash; + } + + public void setFileEncryptionKey(byte[] key) { + this.fileEncryptionKey = key; + } + + /** + * Overwrite a property. + * + * @param key the property name + * @param value the value + */ + public void setProperty(String key, String value) { + // value is null if the value is an object + if (value != null) { + prop.setProperty(key, value); + } + } + + /** + * Get the database URL. + * + * @return the URL + */ + public String getURL() { + return url; + } + + /** + * Get the complete original database URL. + * + * @return the database URL + */ + public String getOriginalURL() { + return originalURL; + } + + /** + * Set the original database URL. + * + * @param url the database url + */ + public void setOriginalURL(String url) { + originalURL = url; + } + + /** + * Returns the time zone. + * + * @return the time zone + */ + public TimeZoneProvider getTimeZone() { + return timeZone; + } + + /** + * Generate a URL format exception. + * + * @return the exception + */ + DbException getFormatException() { + return DbException.get(ErrorCode.URL_FORMAT_ERROR_2, Constants.URL_FORMAT, url); + } + + /** + * Switch to server mode, and set the server name and database key. + * + * @param serverKey the server name, '/', and the security key + */ + public void setServerKey(String serverKey) { + remote = true; + persistent = false; + this.name = serverKey; + } + + /** + * Returns the network connection information, or {@code null}. + * + * @return the network connection information, or {@code null} + */ + public NetworkConnectionInfo getNetworkConnectionInfo() { + return networkConnectionInfo; + } + + /** + * Sets the network connection information. + * + * @param networkConnectionInfo the network connection information + */ + public void setNetworkConnectionInfo(NetworkConnectionInfo networkConnectionInfo) { + this.networkConnectionInfo = networkConnectionInfo; + } + + public DbSettings getDbSettings() { + DbSettings defaultSettings = DbSettings.DEFAULT; + HashMap s = new HashMap<>(DbSettings.TABLE_SIZE); + for (Object k : prop.keySet()) { + String key = k.toString(); + if (!isKnownSetting(key) && defaultSettings.containsKey(key)) { + s.put(key, prop.getProperty(key)); + } + } + return DbSettings.getInstance(s); + } + + private static String remapURL(String url) { + String urlMap = SysProperties.URL_MAP; + if (urlMap != null && !urlMap.isEmpty()) { + try { + SortedProperties prop; + prop = SortedProperties.loadProperties(urlMap); + String url2 = prop.getProperty(url); + if (url2 == null) { + prop.put(url, ""); + prop.store(urlMap); + } else { + url2 = url2.trim(); + if (!url2.isEmpty()) { + return url2; + } + } + } catch (IOException e) { + throw DbException.convert(e); + } + } + return url; + } + + /** + * Clear authentication properties. + */ + public void cleanAuthenticationInfo() { + removeProperty("AUTHREALM", false); + removeProperty("AUTHZPWD", false); + } +} diff --git a/h2/src/main/org/h2/engine/Constants.java b/h2/src/main/org/h2/engine/Constants.java new file mode 100644 index 0000000..d85c844 --- /dev/null +++ b/h2/src/main/org/h2/engine/Constants.java @@ -0,0 +1,508 @@ +/* + * 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.engine; + +import java.sql.ResultSet; + +/** + * Constants are fixed values that are used in the whole database code. + */ +public class Constants { + + /** + * The build date is updated for each public release. + */ + public static final String BUILD_DATE = "2022-04-09"; + + /** + * Sequential version number. Even numbers are used for official releases, + * odd numbers are used for development builds. + */ + public static final int BUILD_ID = 212; + + /** + * Whether this is a snapshot version. + */ + public static final boolean BUILD_SNAPSHOT = false; + + /** + * If H2 is compiled to be included in a product, this should be set to + * a unique vendor id (to distinguish from official releases). + * Additionally, a version number should be set to distinguish releases. + * Example: ACME_SVN1651_BUILD3 + */ + public static final String BUILD_VENDOR_AND_VERSION = null; + + /** + * The TCP protocol version number 17. + * @since 1.4.197 (2018-03-18) + */ + public static final int TCP_PROTOCOL_VERSION_17 = 17; + + /** + * The TCP protocol version number 18. + * @since 1.4.198 (2019-02-22) + */ + public static final int TCP_PROTOCOL_VERSION_18 = 18; + + /** + * The TCP protocol version number 19. + * @since 1.4.200 (2019-10-14) + */ + public static final int TCP_PROTOCOL_VERSION_19 = 19; + + /** + * The TCP protocol version number 20. + * @since 2.0.202 (2021-11-25) + */ + public static final int TCP_PROTOCOL_VERSION_20 = 20; + + /** + * Minimum supported version of TCP protocol. + */ + public static final int TCP_PROTOCOL_VERSION_MIN_SUPPORTED = TCP_PROTOCOL_VERSION_17; + + /** + * Maximum supported version of TCP protocol. + */ + public static final int TCP_PROTOCOL_VERSION_MAX_SUPPORTED = TCP_PROTOCOL_VERSION_20; + + /** + * The major version of this database. + */ + public static final int VERSION_MAJOR = 2; + + /** + * The minor version of this database. + */ + public static final int VERSION_MINOR = 1; + + /** + * The lock mode that means no locking is used at all. + */ + public static final int LOCK_MODE_OFF = 0; + + /** + * The lock mode that means read locks are acquired, but they are released + * immediately after the statement is executed. + */ + public static final int LOCK_MODE_READ_COMMITTED = 3; + + /** + * The lock mode that means table level locking is used for reads and + * writes. + */ + public static final int LOCK_MODE_TABLE = 1; + + /** + * The lock mode that means table level locking is used for reads and + * writes. If a table is locked, System.gc is called to close forgotten + * connections. + */ + public static final int LOCK_MODE_TABLE_GC = 2; + + /** + * Constant meaning both numbers and text is allowed in SQL statements. + */ + public static final int ALLOW_LITERALS_ALL = 2; + + /** + * Constant meaning no literals are allowed in SQL statements. + */ + public static final int ALLOW_LITERALS_NONE = 0; + + /** + * Constant meaning only numbers are allowed in SQL statements (but no + * texts). + */ + public static final int ALLOW_LITERALS_NUMBERS = 1; + + /** + * SNAPSHOT isolation level of transaction. + */ + public static final int TRANSACTION_SNAPSHOT = 6; + + /** + * Whether searching in Blob values should be supported. + */ + public static final boolean BLOB_SEARCH = false; + + /** + * The minimum number of entries to keep in the cache. + */ + public static final int CACHE_MIN_RECORDS = 16; + + /** + * The default cache type. + */ + public static final String CACHE_TYPE_DEFAULT = "LRU"; + + /** + * The value of the cluster setting if clustering is disabled. + */ + public static final String CLUSTERING_DISABLED = "''"; + + /** + * The value of the cluster setting if clustering is enabled (the actual + * value is checked later). + */ + public static final String CLUSTERING_ENABLED = "TRUE"; + + /** + * The database URL used when calling a function if only the column list + * should be returned. + */ + public static final String CONN_URL_COLUMNLIST = "jdbc:columnlist:connection"; + + /** + * The database URL used when calling a function if the data should be + * returned. + */ + public static final String CONN_URL_INTERNAL = "jdbc:default:connection"; + + /** + * The cost is calculated on rowcount + this offset, + * to avoid using the wrong or no index if the table + * contains no rows _currently_ (when preparing the statement) + */ + public static final int COST_ROW_OFFSET = 1000; + + /** + * The number of milliseconds after which to check for a deadlock if locking + * is not successful. + */ + public static final int DEADLOCK_CHECK = 100; + + /** + * The default port number of the HTTP server (for the H2 Console). + * This value is also in the documentation and in the Server javadoc. + */ + public static final int DEFAULT_HTTP_PORT = 8082; + + /** + * The default value for the LOCK_MODE setting. + */ + public static final int DEFAULT_LOCK_MODE = LOCK_MODE_READ_COMMITTED; + + /** + * The default maximum length of an LOB that is stored with the record + * itself, and not in a separate place. + */ + public static final int DEFAULT_MAX_LENGTH_INPLACE_LOB = 256; + + /** + * The default for the setting MAX_OPERATION_MEMORY. + */ + public static final int DEFAULT_MAX_OPERATION_MEMORY = 100_000; + + /** + * The default page size to use for new databases. + */ + public static final int DEFAULT_PAGE_SIZE = 4096; + + /** + * The default result set concurrency for statements created with + * Connection.createStatement() or prepareStatement(String sql). + */ + public static final int DEFAULT_RESULT_SET_CONCURRENCY = + ResultSet.CONCUR_READ_ONLY; + + /** + * The default port of the TCP server. + * This port is also used in the documentation and in the Server javadoc. + */ + public static final int DEFAULT_TCP_PORT = 9092; + + /** + * The default delay in milliseconds before the transaction log is written. + */ + public static final int DEFAULT_WRITE_DELAY = 500; + + /** + * The password is hashed this many times + * to slow down dictionary attacks. + */ + public static final int ENCRYPTION_KEY_HASH_ITERATIONS = 1024; + + /** + * The block of a file. It is also the encryption block size. + */ + public static final int FILE_BLOCK_SIZE = 16; + + /** + * For testing, the lock timeout is smaller than for interactive use cases. + * This value could be increased to about 5 or 10 seconds. + */ + public static final int INITIAL_LOCK_TIMEOUT = 2000; + + /** + * The block size for I/O operations. + */ + public static final int IO_BUFFER_SIZE = 4 * 1024; + + /** + * The block size used to compress data in the LZFOutputStream. + */ + public static final int IO_BUFFER_SIZE_COMPRESS = 128 * 1024; + + /** + * The number of milliseconds to wait between checking the .lock.db file + * still exists once a database is locked. + */ + public static final int LOCK_SLEEP = 1000; + + /** + * The maximum allowed length of identifiers. + */ + public static final int MAX_IDENTIFIER_LENGTH = 256; + + /** + * The maximum number of columns in a table, select statement or row value. + */ + public static final int MAX_COLUMNS = 16_384; + + /** + * 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 = 1000_000_000; + + /** + * The maximum allowed precision of numeric data types. + */ + public static final int MAX_NUMERIC_PRECISION = 100_000; + + /** + * The maximum allowed cardinality of array. + */ + public static final int MAX_ARRAY_CARDINALITY = 65_536; + + /** + * The highest possible parameter index. + */ + public static final int MAX_PARAMETER_INDEX = 100_000; + + /** + * The memory needed by a regular object with at least one field. + */ + // Java 6, 64 bit: 24 + // Java 6, 32 bit: 12 + public static final int MEMORY_OBJECT = 24; + + /** + * The memory needed by an array. + */ + public static final int MEMORY_ARRAY = 24; + + /** + * The memory needed by a pointer. + */ + // Java 6, 64 bit: 8 + // Java 6, 32 bit: 4 + public static final int MEMORY_POINTER = 8; + + /** + * The memory needed by a Row. + */ + public static final int MEMORY_ROW = 40; + + /** + * The name prefix used for indexes that are not explicitly named. + */ + public static final String PREFIX_INDEX = "INDEX_"; + + /** + * The name prefix used for synthetic nested join tables. + */ + public static final String PREFIX_JOIN = "SYSTEM_JOIN_"; + + /** + * The name prefix used for primary key constraints that are not explicitly + * named. + */ + public static final String PREFIX_PRIMARY_KEY = "PRIMARY_KEY_"; + + /** + * The name prefix used for query aliases that are not explicitly named. + */ + public static final String PREFIX_QUERY_ALIAS = "QUERY_ALIAS_"; + + /** + * Every user belongs to this role. + */ + public static final String PUBLIC_ROLE_NAME = "PUBLIC"; + + /** + * The number of bytes in random salt that is used to hash passwords. + */ + public static final int SALT_LEN = 8; + + /** + * The identity of INFORMATION_SCHEMA. + */ + public static final int INFORMATION_SCHEMA_ID = -1; + + /** + * The identity of PUBLIC schema. + */ + public static final int MAIN_SCHEMA_ID = 0; + + /** + * The name of the default schema. + */ + public static final String SCHEMA_MAIN = "PUBLIC"; + + /** + * The identity of pg_catalog schema. + */ + public static final int PG_CATALOG_SCHEMA_ID = -1_000; + + /** + * The name of the pg_catalog schema. + */ + public static final String SCHEMA_PG_CATALOG = "PG_CATALOG"; + + /** + * The default selectivity (used if the selectivity is not calculated). + */ + public static final int SELECTIVITY_DEFAULT = 50; + + /** + * The number of distinct values to keep in memory when running ANALYZE. + */ + public static final int SELECTIVITY_DISTINCT_COUNT = 10_000; + + /** + * The default directory name of the server properties file for the H2 + * Console. + */ + public static final String SERVER_PROPERTIES_DIR = "~"; + + /** + * The name of the server properties file for the H2 Console. + */ + public static final String SERVER_PROPERTIES_NAME = ".h2.server.properties"; + + /** + * Queries that take longer than this number of milliseconds are written to + * the trace file with the level info. + */ + public static final long SLOW_QUERY_LIMIT_MS = 100; + + /** + * The database URL prefix of this database. + */ + public static final String START_URL = "jdbc:h2:"; + + /** + * The file name suffix of file lock files that are used to make sure a + * database is open by only one process at any time. + */ + public static final String SUFFIX_LOCK_FILE = ".lock.db"; + + /** + * The file name suffix of a H2 version 1.1 database file. + */ + public static final String SUFFIX_OLD_DATABASE_FILE = ".data.db"; + + /** + * The file name suffix of a MVStore file. + */ + public static final String SUFFIX_MV_FILE = ".mv.db"; + + /** + * The file name suffix of a new MVStore file, used when compacting a store. + */ + public static final String SUFFIX_MV_STORE_NEW_FILE = ".newFile"; + + /** + * The file name suffix of a temporary MVStore file, used when compacting a + * store. + */ + public static final String SUFFIX_MV_STORE_TEMP_FILE = ".tempFile"; + + /** + * The file name suffix of temporary files. + */ + public static final String SUFFIX_TEMP_FILE = ".temp.db"; + + /** + * The file name suffix of trace files. + */ + public static final String SUFFIX_TRACE_FILE = ".trace.db"; + + /** + * How often we check to see if we need to apply a throttling delay if SET + * THROTTLE has been used. + */ + public static final int THROTTLE_DELAY = 50; + + /** + * The database URL format in simplified Backus-Naur form. + */ + public static final String URL_FORMAT = START_URL + + "{ {.|mem:}[name] | [file:]fileName | " + + "{tcp|ssl}:[//]server[:port][,server2[:port]]/name }[;key=value...]"; + + /** + * The package name of user defined classes. + */ + public static final String USER_PACKAGE = "org.h2.dynamic"; + + /** + * The maximum time in milliseconds to keep the cost of a view. + * 10000 means 10 seconds. + */ + public static final int VIEW_COST_CACHE_MAX_AGE = 10_000; + + /** + * The name of the index cache that is used for temporary view (subqueries + * used as tables). + */ + public static final int VIEW_INDEX_CACHE_SIZE = 64; + + /** + * The maximum number of entries in query statistics. + */ + public static final int QUERY_STATISTICS_MAX_ENTRIES = 100; + + /** + * Announced version for PgServer. + */ + public static final String PG_VERSION = "8.2.23"; + + /** + * The version of this product, consisting of major version, minor + * version, and build id. + */ + public static final String VERSION; + + /** + * The complete version number of this database, consisting of + * the major version, the minor version, the build id, and the build date. + */ + public static final String FULL_VERSION; + + static { + String version = VERSION_MAJOR + "." + VERSION_MINOR + '.' + BUILD_ID; + if (BUILD_VENDOR_AND_VERSION != null) { + version += '_' + BUILD_VENDOR_AND_VERSION; + } + if (BUILD_SNAPSHOT) { + version += "-SNAPSHOT"; + } + VERSION = version; + FULL_VERSION = version + (" (" + BUILD_DATE + ')'); + } + + private Constants() { + // utility class + } + +} diff --git a/h2/src/main/org/h2/engine/Database.java b/h2/src/main/org/h2/engine/Database.java new file mode 100644 index 0000000..6f49bec --- /dev/null +++ b/h2/src/main/org/h2/engine/Database.java @@ -0,0 +1,2479 @@ +/* + * 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.engine; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import org.h2.api.DatabaseEventListener; +import org.h2.api.ErrorCode; +import org.h2.api.JavaObjectSerializer; +import org.h2.api.TableEngine; +import org.h2.command.CommandInterface; +import org.h2.command.Prepared; +import org.h2.command.ddl.CreateTableData; +import org.h2.command.dml.SetTypes; +import org.h2.constraint.Constraint; +import org.h2.constraint.Constraint.Type; +import org.h2.engine.Mode.ModeEnum; +import org.h2.index.Cursor; +import org.h2.index.Index; +import org.h2.index.IndexType; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.message.TraceSystem; +import org.h2.mode.DefaultNullOrdering; +import org.h2.mode.PgCatalogSchema; +import org.h2.mvstore.MVStoreException; +import org.h2.mvstore.db.LobStorageMap; +import org.h2.mvstore.db.Store; +import org.h2.result.Row; +import org.h2.result.RowFactory; +import org.h2.result.SearchRow; +import org.h2.schema.InformationSchema; +import org.h2.schema.Schema; +import org.h2.schema.SchemaObject; +import org.h2.schema.Sequence; +import org.h2.schema.TriggerObject; +import org.h2.security.auth.Authenticator; +import org.h2.store.DataHandler; +import org.h2.store.FileLock; +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; +import org.h2.table.Column; +import org.h2.table.IndexColumn; +import org.h2.table.Table; +import org.h2.table.TableLinkConnection; +import org.h2.table.TableSynonym; +import org.h2.table.TableType; +import org.h2.table.TableView; +import org.h2.tools.DeleteDbFiles; +import org.h2.tools.Server; +import org.h2.util.JdbcUtils; +import org.h2.util.MathUtils; +import org.h2.util.NetUtils; +import org.h2.util.NetworkConnectionInfo; +import org.h2.util.SmallLRUCache; +import org.h2.util.SourceCompiler; +import org.h2.util.StringUtils; +import org.h2.util.TempFileDeleter; +import org.h2.util.TimeZoneProvider; +import org.h2.util.Utils; +import org.h2.value.CaseInsensitiveConcurrentMap; +import org.h2.value.CaseInsensitiveMap; +import org.h2.value.CompareMode; +import org.h2.value.TypeInfo; +import org.h2.value.ValueInteger; +import org.h2.value.ValueTimestampTimeZone; + +/** + * There is one database object per open database. + * + * The format of the meta data table is: + * id int, 0, objectType int, sql varchar + * + * @since 2004-04-15 22:49 + */ +public final class Database implements DataHandler, CastDataProvider { + + private static int initialPowerOffCount; + + private static final boolean ASSERT; + + private static final ThreadLocal META_LOCK_DEBUGGING; + private static final ThreadLocal META_LOCK_DEBUGGING_DB; + private static final ThreadLocal META_LOCK_DEBUGGING_STACK; + private static final SessionLocal[] EMPTY_SESSION_ARRAY = new SessionLocal[0]; + + static { + boolean a = false; + // Intentional side-effect + assert a = true; + ASSERT = a; + if (a) { + META_LOCK_DEBUGGING = new ThreadLocal<>(); + META_LOCK_DEBUGGING_DB = new ThreadLocal<>(); + META_LOCK_DEBUGGING_STACK = new ThreadLocal<>(); + } else { + META_LOCK_DEBUGGING = null; + META_LOCK_DEBUGGING_DB = null; + META_LOCK_DEBUGGING_STACK = null; + } + } + + /** + * The default name of the system user. This name is only used as long as + * there is no administrator user registered. + */ + private static final String SYSTEM_USER_NAME = "DBA"; + + private final boolean persistent; + private final String databaseName; + private final String databaseShortName; + 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<>(); + private final ConcurrentHashMap schemas = new ConcurrentHashMap<>(); + private final ConcurrentHashMap rights = new ConcurrentHashMap<>(); + private final ConcurrentHashMap comments = new ConcurrentHashMap<>(); + + private final HashMap tableEngines = new HashMap<>(); + + private final Set userSessions = Collections.synchronizedSet(new HashSet<>()); + private final AtomicReference exclusiveSession = new AtomicReference<>(); + private final BitSet objectIds = new BitSet(); + private final Object lobSyncObject = new Object(); + + private final Schema mainSchema; + private final Schema infoSchema; + private final Schema pgCatalogSchema; + private int nextSessionId; + private int nextTempTableId; + private final User systemUser; + private SessionLocal systemSession; + private SessionLocal lobSession; + private final Table meta; + private final Index metaIdIndex; + private FileLock lock; + private volatile boolean starting; + private final TraceSystem traceSystem; + private final Trace trace; + private final FileLockMethod fileLockMethod; + private final Role publicRole; + private final AtomicLong modificationDataId = new AtomicLong(); + private final AtomicLong modificationMetaId = new AtomicLong(); + /** + * Used to trigger the client side to reload some of the settings. + */ + private final AtomicLong remoteSettingsId = new AtomicLong(); + private CompareMode compareMode; + private String cluster = Constants.CLUSTERING_DISABLED; + private boolean readOnly; + private DatabaseEventListener eventListener; + private int maxMemoryRows = SysProperties.MAX_MEMORY_ROWS; + private int lockMode; + private int maxLengthInplaceLob; + private int allowLiterals = Constants.ALLOW_LITERALS_ALL; + + private int powerOffCount = initialPowerOffCount; + private volatile int closeDelay; + private DelayedDatabaseCloser delayedCloser; + private volatile boolean closing; + private boolean ignoreCase; + private boolean deleteFilesOnDisconnect; + private boolean optimizeReuseResults = true; + private final String cacheType; + private boolean referentialIntegrity = true; + private Mode mode = Mode.getRegular(); + private DefaultNullOrdering defaultNullOrdering = DefaultNullOrdering.LOW; + private int maxOperationMemory = + Constants.DEFAULT_MAX_OPERATION_MEMORY; + private SmallLRUCache lobFileListCache; + private final boolean autoServerMode; + private final int autoServerPort; + private Server server; + private HashMap linkConnections; + private final TempFileDeleter tempFileDeleter = TempFileDeleter.getInstance(); + private int compactMode; + private SourceCompiler compiler; + private final LobStorageInterface lobStorage; + private final int pageSize; + private int defaultTableType = Table.TYPE_CACHED; + private final DbSettings dbSettings; + private final Store store; + private boolean allowBuiltinAliasOverride; + private final AtomicReference backgroundException = new AtomicReference<>(); + private JavaObjectSerializer javaObjectSerializer; + private String javaObjectSerializerName; + private volatile boolean javaObjectSerializerInitialized; + private volatile boolean queryStatistics; + private int queryStatisticsMaxEntries = Constants.QUERY_STATISTICS_MAX_ENTRIES; + private QueryStatisticsData queryStatisticsData; + private RowFactory rowFactory = RowFactory.getRowFactory(); + private boolean ignoreCatalogs; + + private Authenticator authenticator; + + public Database(ConnectionInfo ci, String cipher) { + if (ASSERT) { + META_LOCK_DEBUGGING.set(null); + META_LOCK_DEBUGGING_DB.set(null); + META_LOCK_DEBUGGING_STACK.set(null); + } + String databaseName = ci.getName(); + this.dbSettings = ci.getDbSettings(); + 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; + this.cipher = cipher; + this.autoServerMode = ci.getProperty("AUTO_SERVER", false); + this.autoServerPort = ci.getProperty("AUTO_SERVER_PORT", 0); + pageSize = ci.getProperty("PAGE_SIZE", Constants.DEFAULT_PAGE_SIZE); + if (cipher != null && pageSize % FileEncrypt.BLOCK_SIZE != 0) { + throw DbException.getUnsupportedException("CIPHER && PAGE_SIZE=" + pageSize); + } + String accessModeData = StringUtils.toLowerEnglish(ci.getProperty("ACCESS_MODE_DATA", "rw")); + if ("r".equals(accessModeData)) { + readOnly = true; + } + String lockMethodName = ci.getProperty("FILE_LOCK", null); + fileLockMethod = lockMethodName != null ? FileLock.getFileLockMethod(lockMethodName) : + autoServerMode ? FileLockMethod.FILE : FileLockMethod.FS; + this.databaseURL = ci.getURL(); + String s = ci.removeProperty("DATABASE_EVENT_LISTENER", null); + if (s != null) { + setEventListenerClass(StringUtils.trim(s, true, true, "'")); + } + s = ci.removeProperty("MODE", null); + if (s != null) { + mode = Mode.getInstance(s); + if (mode == null) { + throw DbException.get(ErrorCode.UNKNOWN_MODE_1, s); + } + } + s = ci.removeProperty("DEFAULT_NULL_ORDERING", null); + if (s != null) { + try { + defaultNullOrdering = DefaultNullOrdering.valueOf(StringUtils.toUpperEnglish(s)); + } catch (RuntimeException e) { + throw DbException.getInvalidValueException("DEFAULT_NULL_ORDERING", s); + } + } + s = ci.getProperty("JAVA_OBJECT_SERIALIZER", null); + if (s != null) { + s = StringUtils.trim(s, true, true, "'"); + javaObjectSerializerName = s; + } + this.allowBuiltinAliasOverride = ci.getProperty("BUILTIN_ALIAS_OVERRIDE", false); + boolean closeAtVmShutdown = dbSettings.dbCloseOnExit; + 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); + this.cacheType = StringUtils.toUpperEnglish(ci.removeProperty("CACHE_TYPE", Constants.CACHE_TYPE_DEFAULT)); + this.ignoreCatalogs = ci.getProperty("IGNORE_CATALOGS", dbSettings.ignoreCatalogs); + this.lockMode = ci.getProperty("LOCK_MODE", Constants.DEFAULT_LOCK_MODE); + String traceFile; + if (persistent) { + if (readOnly) { + if (traceLevelFile >= TraceSystem.DEBUG) { + traceFile = Utils.getProperty("java.io.tmpdir", ".") + "/h2_" + System.currentTimeMillis() + + Constants.SUFFIX_TRACE_FILE; + } else { + traceFile = null; + } + } else { + traceFile = databaseName + Constants.SUFFIX_TRACE_FILE; + } + } else { + traceFile = null; + } + traceSystem = new TraceSystem(traceFile); + traceSystem.setLevelFile(traceLevelFile); + traceSystem.setLevelSystemOut(traceLevelSystemOut); + trace = traceSystem.getTrace(Trace.DATABASE); + trace.info("opening {0} (build {1})", databaseName, Constants.BUILD_ID); + try { + if (autoServerMode && (readOnly || !persistent || fileLockMethod == FileLockMethod.NO + || fileLockMethod == FileLockMethod.FS)) { + throw DbException.getUnsupportedException( + "AUTO_SERVER=TRUE && (readOnly || inMemory || FILE_LOCK=NO || FILE_LOCK=FS)"); + } + if (persistent) { + String lockFileName = databaseName + Constants.SUFFIX_LOCK_FILE; + if (readOnly) { + if (FileUtils.exists(lockFileName)) { + throw DbException.get(ErrorCode.DATABASE_ALREADY_OPEN_1, "Lock file exists: " + lockFileName); + } + } else if (fileLockMethod != FileLockMethod.NO && fileLockMethod != FileLockMethod.FS) { + lock = new FileLock(traceSystem, lockFileName, Constants.LOCK_SLEEP); + lock.lock(fileLockMethod); + if (autoServerMode) { + startServer(lock.getUniqueId()); + } + } + deleteOldTempFiles(); + } + starting = true; + if (dbSettings.mvStore) { + store = new Store(this); + } else { + throw new UnsupportedOperationException(); + } + starting = false; + systemUser = new User(this, 0, SYSTEM_USER_NAME, true); + systemUser.setAdmin(true); + mainSchema = new Schema(this, Constants.MAIN_SCHEMA_ID, sysIdentifier(Constants.SCHEMA_MAIN), systemUser, + true); + infoSchema = new InformationSchema(this, systemUser); + schemas.put(mainSchema.getName(), mainSchema); + schemas.put(infoSchema.getName(), infoSchema); + if (mode.getEnum() == ModeEnum.PostgreSQL) { + pgCatalogSchema = new PgCatalogSchema(this, systemUser); + schemas.put(pgCatalogSchema.getName(), pgCatalogSchema); + } else { + pgCatalogSchema = null; + } + publicRole = new Role(this, 0, sysIdentifier(Constants.PUBLIC_ROLE_NAME), true); + usersAndRoles.put(publicRole.getName(), publicRole); + systemSession = createSession(systemUser); + lobSession = createSession(systemUser); + Set settingKeys = dbSettings.getSettings().keySet(); + store.getTransactionStore().init(lobSession); + settingKeys.removeIf(name -> name.startsWith("PAGE_STORE_")); + CreateTableData data = createSysTableData(); + starting = true; + meta = mainSchema.createTable(data); + IndexColumn[] pkCols = IndexColumn.wrap(new Column[] { data.columns.get(0) }); + metaIdIndex = meta.addIndex(systemSession, "SYS_ID", 0, pkCols, 1, + IndexType.createPrimaryKey(false, false), true, null); + systemSession.commit(true); + objectIds.set(0); + executeMeta(); + systemSession.commit(true); + store.getTransactionStore().endLeftoverTransactions(); + store.removeTemporaryMaps(objectIds); + recompileInvalidViews(); + starting = false; + if (!readOnly) { + // set CREATE_BUILD in a new database + String settingName = SetTypes.getTypeName(SetTypes.CREATE_BUILD); + Setting setting = settings.get(settingName); + if (setting == null) { + setting = new Setting(this, allocateObjectId(), settingName); + setting.setIntValue(Constants.BUILD_ID); + lockMeta(systemSession); + addDatabaseObject(systemSession, setting); + } + } + lobStorage = new LobStorageMap(this); + lobSession.commit(true); + systemSession.commit(true); + trace.info("opened {0}", databaseName); + if (persistent) { + int writeDelay = ci.getProperty("WRITE_DELAY", Constants.DEFAULT_WRITE_DELAY); + setWriteDelay(writeDelay); + } + if (closeAtVmShutdown) { + OnExitDatabaseCloser.register(this); + } + } catch (Throwable e) { + try { + if (e instanceof OutOfMemoryError) { + e.fillInStackTrace(); + } + if (e instanceof DbException) { + if (((DbException) e).getErrorCode() == ErrorCode.DATABASE_ALREADY_OPEN_1) { + stopServer(); + } else { + // only write if the database is not already in use + trace.error(e, "opening {0}", databaseName); + } + } + traceSystem.close(); + closeOpenFilesAndUnlock(); + } catch (Throwable ex) { + e.addSuppressed(ex); + } + throw DbException.convert(e); + } + } + + public int getLockTimeout() { + Setting setting = findSetting(SetTypes.getTypeName(SetTypes.DEFAULT_LOCK_TIMEOUT)); + return setting == null ? Constants.INITIAL_LOCK_TIMEOUT : setting.getIntValue(); + } + + public RowFactory getRowFactory() { + return rowFactory; + } + + public void setRowFactory(RowFactory rowFactory) { + this.rowFactory = rowFactory; + } + + public static void setInitialPowerOffCount(int count) { + initialPowerOffCount = count; + } + + public void setPowerOffCount(int count) { + if (powerOffCount == -1) { + return; + } + powerOffCount = count; + } + + public Store getStore() { + return store; + } + + public long getModificationDataId() { + return modificationDataId.get(); + } + + public long getNextModificationDataId() { + return modificationDataId.incrementAndGet(); + } + + public long getModificationMetaId() { + return modificationMetaId.get(); + } + + public long getNextModificationMetaId() { + // if the meta data has been modified, the data is modified as well + // (because MetaTable returns modificationDataId) + modificationDataId.incrementAndGet(); + return modificationMetaId.incrementAndGet() - 1; + } + + public long getRemoteSettingsId() { + return remoteSettingsId.get(); + } + + public long getNextRemoteSettingsId() { + return remoteSettingsId.incrementAndGet(); + } + + public int getPowerOffCount() { + return powerOffCount; + } + + @Override + public void checkPowerOff() { + if (powerOffCount != 0) { + checkPowerOff2(); + } + } + + private void checkPowerOff2() { + if (powerOffCount > 1) { + powerOffCount--; + return; + } + if (powerOffCount != -1) { + try { + powerOffCount = -1; + store.closeImmediately(); + if (lock != null) { + stopServer(); + // allow testing shutdown + lock.unlock(); + lock = null; + } + if (traceSystem != null) { + traceSystem.close(); + } + } catch (DbException e) { + DbException.traceThrowable(e); + } + } + Engine.close(databaseName); + throw DbException.get(ErrorCode.DATABASE_IS_CLOSED); + } + + /** + * Get the trace object for the given module id. + * + * @param moduleId the module id + * @return the trace object + */ + public Trace getTrace(int moduleId) { + return traceSystem.getTrace(moduleId); + } + + @Override + public FileStore openFile(String name, String openMode, boolean mustExist) { + if (mustExist && !FileUtils.exists(name)) { + throw DbException.get(ErrorCode.FILE_NOT_FOUND_1, name); + } + FileStore store = FileStore.open(this, name, openMode, cipher, + filePasswordHash); + try { + store.init(); + } catch (DbException e) { + store.closeSilently(); + throw e; + } + return store; + } + + /** + * Check if the file password hash is correct. + * + * @param testCipher the cipher algorithm + * @param testHash the hash code + * @return true if the cipher algorithm and the password match + */ + boolean validateFilePasswordHash(String testCipher, byte[] testHash) { + if (!Objects.equals(testCipher, this.cipher)) { + return false; + } + return Utils.compareSecure(testHash, filePasswordHash); + } + + private String parseDatabaseShortName() { + String n = databaseName; + int l = n.length(), i = l; + loop: while (--i >= 0) { + char ch = n.charAt(i); + switch (ch) { + case '/': + case ':': + case '\\': + break loop; + } + } + n = ++i == l ? "UNNAMED" : n.substring(i); + return StringUtils.truncateString( + dbSettings.databaseToUpper ? StringUtils.toUpperEnglish(n) + : dbSettings.databaseToLower ? StringUtils.toLowerEnglish(n) : n, + Constants.MAX_IDENTIFIER_LENGTH); + } + + private CreateTableData createSysTableData() { + CreateTableData data = new CreateTableData(); + ArrayList cols = data.columns; + Column columnId = new Column("ID", TypeInfo.TYPE_INTEGER); + columnId.setNullable(false); + cols.add(columnId); + cols.add(new Column("HEAD", TypeInfo.TYPE_INTEGER)); + cols.add(new Column("TYPE", TypeInfo.TYPE_INTEGER)); + cols.add(new Column("SQL", TypeInfo.TYPE_VARCHAR)); + data.tableName = "SYS"; + data.id = 0; + data.temporary = false; + data.persistData = persistent; + data.persistIndexes = persistent; + data.isHidden = true; + data.session = systemSession; + return data; + } + + private void executeMeta() { + Cursor cursor = metaIdIndex.find(systemSession, null, null); + ArrayList firstRecords = new ArrayList<>(), domainRecords = new ArrayList<>(), + middleRecords = new ArrayList<>(), constraintRecords = new ArrayList<>(), + lastRecords = new ArrayList<>(); + while (cursor.next()) { + MetaRecord rec = new MetaRecord(cursor.get()); + objectIds.set(rec.getId()); + switch (rec.getObjectType()) { + case DbObject.SETTING: + case DbObject.USER: + case DbObject.SCHEMA: + case DbObject.FUNCTION_ALIAS: + firstRecords.add(rec); + break; + case DbObject.DOMAIN: + domainRecords.add(rec); + break; + case DbObject.SEQUENCE: + case DbObject.CONSTANT: + case DbObject.TABLE_OR_VIEW: + case DbObject.INDEX: + middleRecords.add(rec); + break; + case DbObject.CONSTRAINT: + constraintRecords.add(rec); + break; + default: + lastRecords.add(rec); + } + } + synchronized (systemSession) { + executeMeta(firstRecords); + // Domains may depend on other domains + int count = domainRecords.size(); + if (count > 0) { + for (int j = 0;; count = j) { + DbException exception = null; + for (int i = 0; i < count; i++) { + MetaRecord rec = domainRecords.get(i); + try { + rec.prepareAndExecute(this, systemSession, eventListener); + } catch (DbException ex) { + if (exception == null) { + exception = ex; + } + domainRecords.set(j++, rec); + } + } + if (exception == null) { + break; + } + if (count == j) { + throw exception; + } + } + } + executeMeta(middleRecords); + // Prepare, but don't create all constraints and sort them + count = constraintRecords.size(); + if (count > 0) { + ArrayList constraints = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + Prepared prepared = constraintRecords.get(i).prepare(this, systemSession, eventListener); + if (prepared != null) { + constraints.add(prepared); + } + } + constraints.sort(MetaRecord.CONSTRAINTS_COMPARATOR); + // Create constraints in order (unique and primary key before + // all others) + for (Prepared constraint : constraints) { + MetaRecord.execute(this, constraint, eventListener, constraint.getSQL()); + } + } + executeMeta(lastRecords); + } + } + + private void executeMeta(ArrayList records) { + if (!records.isEmpty()) { + records.sort(null); + for (MetaRecord rec : records) { + rec.prepareAndExecute(this, systemSession, eventListener); + } + } + } + + private void startServer(String key) { + try { + server = Server.createTcpServer( + "-tcpPort", Integer.toString(autoServerPort), + "-tcpAllowOthers", + "-tcpDaemon", + "-key", key, databaseName); + server.start(); + } catch (SQLException e) { + throw DbException.convert(e); + } + String localAddress = NetUtils.getLocalAddress(); + String address = localAddress + ":" + server.getPort(); + lock.setProperty("server", address); + String hostName = NetUtils.getHostName(localAddress); + lock.setProperty("hostName", hostName); + lock.save(); + } + + private void stopServer() { + if (server != null) { + Server s = server; + // avoid calling stop recursively + // because stopping the server will + // try to close the database as well + server = null; + s.stop(); + } + } + + private void recompileInvalidViews() { + boolean atLeastOneRecompiledSuccessfully; + do { + atLeastOneRecompiledSuccessfully = false; + for (Schema schema : schemas.values()) { + for (Table obj : schema.getAllTablesAndViews(null)) { + if (obj instanceof TableView) { + TableView view = (TableView) obj; + if (view.isInvalid()) { + view.recompile(systemSession, true, false); + if (!view.isInvalid()) { + atLeastOneRecompiledSuccessfully = true; + } + } + } + } + } + } while (atLeastOneRecompiledSuccessfully); + TableView.clearIndexCaches(this); + } + + private void addMeta(SessionLocal session, DbObject obj) { + assert Thread.holdsLock(this); + int id = obj.getId(); + if (id > 0 && !obj.isTemporary()) { + if (!isReadOnly()) { + Row r = meta.getTemplateRow(); + MetaRecord.populateRowFromDBObject(obj, r); + assert objectIds.get(id); + if (SysProperties.CHECK) { + verifyMetaLocked(session); + } + Cursor cursor = metaIdIndex.find(session, r, r); + if (!cursor.next()) { + meta.addRow(session, r); + } else { + assert starting; + Row oldRow = cursor.get(); + MetaRecord rec = new MetaRecord(oldRow); + assert rec.getId() == obj.getId(); + assert rec.getObjectType() == obj.getType(); + if (!rec.getSQL().equals(obj.getCreateSQLForMeta())) { + meta.updateRow(session, oldRow, r); + } + } + } + } + } + + /** + * Verify the meta table is locked. + * + * @param session the session + */ + public void verifyMetaLocked(SessionLocal session) { + if (lockMode != Constants.LOCK_MODE_OFF && meta != null && !meta.isLockedExclusivelyBy(session)) { + throw DbException.getInternalError(); + } + } + + /** + * Lock the metadata table for updates. + * + * @param session the session + * @return whether it was already locked before by this session + */ + public boolean lockMeta(SessionLocal session) { + // this method can not be synchronized on the database object, + // as unlocking is also synchronized on the database object - + // so if locking starts just before unlocking, locking could + // never be successful + if (meta == null) { + return true; + } + if (ASSERT) { + lockMetaAssertion(session); + } + return meta.lock(session, Table.EXCLUSIVE_LOCK); + } + + private void lockMetaAssertion(SessionLocal session) { + // If we are locking two different databases in the same stack, just ignore it. + // This only happens in TestLinkedTable where we connect to another h2 DB in the + // same process. + if (META_LOCK_DEBUGGING_DB.get() != null && META_LOCK_DEBUGGING_DB.get() != this) { + final SessionLocal prev = META_LOCK_DEBUGGING.get(); + if (prev == null) { + META_LOCK_DEBUGGING.set(session); + META_LOCK_DEBUGGING_DB.set(this); + META_LOCK_DEBUGGING_STACK.set(new Throwable("Last meta lock granted in this stack trace, " + + "this is debug information for following IllegalStateException")); + } else if (prev != session) { + META_LOCK_DEBUGGING_STACK.get().printStackTrace(); + throw new IllegalStateException("meta currently locked by " + prev + ", sessionid=" + prev.getId() + + " and trying to be locked by different session, " + session + ", sessionid=" // + + session.getId() + " on same thread"); + } + } + } + + /** + * Unlock the metadata table. + * + * @param session the session + */ + public void unlockMeta(SessionLocal session) { + if (meta != null) { + unlockMetaDebug(session); + meta.unlock(session); + session.unlock(meta); + } + } + + /** + * This method doesn't actually unlock the metadata table, all it does it + * reset the debugging flags. + * + * @param session the session + */ + static void unlockMetaDebug(SessionLocal session) { + if (ASSERT) { + if (META_LOCK_DEBUGGING.get() == session) { + META_LOCK_DEBUGGING.set(null); + META_LOCK_DEBUGGING_DB.set(null); + META_LOCK_DEBUGGING_STACK.set(null); + } + } + } + + /** + * Remove the given object from the meta data. + * + * @param session the session + * @param id the id of the object to remove + */ + public void removeMeta(SessionLocal session, int id) { + if (id > 0 && !starting) { + SearchRow r = meta.getRowFactory().createRow(); + r.setValue(0, ValueInteger.get(id)); + boolean wasLocked = lockMeta(session); + try { + Cursor cursor = metaIdIndex.find(session, r, r); + if (cursor.next()) { + Row found = cursor.get(); + meta.removeRow(session, found); + if (SysProperties.CHECK) { + checkMetaFree(session, id); + } + } + } finally { + if (!wasLocked) { + // must not keep the lock if it was not locked + // otherwise updating sequences may cause a deadlock + unlockMeta(session); + } + } + // release of the object id has to be postponed until the end of the transaction, + // otherwise it might be re-used prematurely, and it would make + // rollback impossible or lead to MVMaps name collision, + // so until then ids are accumulated within session + session.scheduleDatabaseObjectIdForRelease(id); + } + } + + /** + * Mark some database ids as unused. + * @param idsToRelease the ids to release + */ + public void releaseDatabaseObjectIds(BitSet idsToRelease) { + synchronized (objectIds) { + objectIds.andNot(idsToRelease); + } + } + + @SuppressWarnings("unchecked") + private Map getMap(int type) { + Map result; + switch (type) { + case DbObject.USER: + case DbObject.ROLE: + result = usersAndRoles; + break; + case DbObject.SETTING: + result = settings; + break; + case DbObject.RIGHT: + result = rights; + break; + case DbObject.SCHEMA: + result = schemas; + break; + case DbObject.COMMENT: + result = comments; + break; + default: + throw DbException.getInternalError("type=" + type); + } + return (Map) result; + } + + /** + * Add a schema object to the database. + * + * @param session the session + * @param obj the object to add + */ + public void addSchemaObject(SessionLocal session, SchemaObject obj) { + int id = obj.getId(); + if (id > 0 && !starting) { + checkWritingAllowed(); + } + lockMeta(session); + synchronized (this) { + obj.getSchema().add(obj); + addMeta(session, obj); + } + } + + /** + * Add an object to the database. + * + * @param session the session + * @param obj the object to add + */ + public synchronized void addDatabaseObject(SessionLocal session, DbObject obj) { + int id = obj.getId(); + if (id > 0 && !starting) { + checkWritingAllowed(); + } + Map map = getMap(obj.getType()); + if (obj.getType() == DbObject.USER) { + User user = (User) obj; + if (user.isAdmin() && systemUser.getName().equals(SYSTEM_USER_NAME)) { + systemUser.rename(user.getName()); + } + } + String name = obj.getName(); + if (SysProperties.CHECK && map.get(name) != null) { + throw DbException.getInternalError("object already exists"); + } + lockMeta(session); + addMeta(session, obj); + map.put(name, obj); + } + + /** + * Get the comment for the given database object if one exists, or null if + * not. + * + * @param object the database object + * @return the comment or null + */ + public Comment findComment(DbObject object) { + if (object.getType() == DbObject.COMMENT) { + return null; + } + String key = Comment.getKey(object); + return comments.get(key); + } + + /** + * Get the role if it exists, or null if not. + * + * @param roleName the name of the role + * @return the role or null + */ + public Role findRole(String roleName) { + RightOwner rightOwner = findUserOrRole(roleName); + return rightOwner instanceof Role ? (Role) rightOwner : null; + } + + /** + * Get the schema if it exists, or null if not. + * + * @param schemaName the name of the schema + * @return the schema or null + */ + public Schema findSchema(String schemaName) { + if (schemaName == null) { + return null; + } + return schemas.get(schemaName); + } + + /** + * Get the setting if it exists, or null if not. + * + * @param name the name of the setting + * @return the setting or null + */ + public Setting findSetting(String name) { + return settings.get(name); + } + + /** + * Get the user if it exists, or null if not. + * + * @param name the name of the user + * @return the user or null + */ + public User findUser(String name) { + RightOwner rightOwner = findUserOrRole(name); + return rightOwner instanceof User ? (User) rightOwner : null; + } + + /** + * Get user with the given name. This method throws an exception if the user + * does not exist. + * + * @param name the user name + * @return the user + * @throws DbException if the user does not exist + */ + public User getUser(String name) { + User user = findUser(name); + if (user == null) { + throw DbException.get(ErrorCode.USER_NOT_FOUND_1, name); + } + return user; + } + + /** + * Get the user or role if it exists, or {@code null} if not. + * + * @param name the name of the user or role + * @return the user, the role, or {@code null} + */ + public RightOwner findUserOrRole(String name) { + return usersAndRoles.get(StringUtils.toUpperEnglish(name)); + } + + /** + * Create a session for the given user. + * + * @param user the user + * @param networkConnectionInfo the network connection information, or {@code null} + * @return the session, or null if the database is currently closing + * @throws DbException if the database is in exclusive mode + */ + synchronized SessionLocal createSession(User user, NetworkConnectionInfo networkConnectionInfo) { + if (closing) { + return null; + } + if (exclusiveSession.get() != null) { + throw DbException.get(ErrorCode.DATABASE_IS_IN_EXCLUSIVE_MODE); + } + SessionLocal session = createSession(user); + session.setNetworkConnectionInfo(networkConnectionInfo); + userSessions.add(session); + trace.info("connecting session #{0} to {1}", session.getId(), databaseName); + if (delayedCloser != null) { + delayedCloser.reset(); + delayedCloser = null; + } + return session; + } + + private SessionLocal createSession(User user) { + int id = ++nextSessionId; + return new SessionLocal(this, user, id); + } + + /** + * Remove a session. This method is called after the user has disconnected. + * + * @param session the session + */ + public synchronized void removeSession(SessionLocal session) { + if (session != null) { + exclusiveSession.compareAndSet(session, null); + if (userSessions.remove(session)) { + trace.info("disconnecting session #{0}", session.getId()); + } + } + if (isUserSession(session)) { + if (userSessions.isEmpty()) { + if (closeDelay == 0) { + close(false); + } else if (closeDelay < 0) { + return; + } else { + delayedCloser = new DelayedDatabaseCloser(this, closeDelay * 1000); + } + } + if (session != null) { + trace.info("disconnected session #{0}", session.getId()); + } + } + } + + boolean isUserSession(SessionLocal session) { + return session != systemSession && session != lobSession; + } + + private synchronized void closeAllSessionsExcept(SessionLocal except) { + SessionLocal[] all = userSessions.toArray(EMPTY_SESSION_ARRAY); + for (SessionLocal s : all) { + if (s != except) { + // indicate that session need to be closed ASAP + s.suspend(); + } + } + + int timeout = 2 * getLockTimeout(); + long start = System.currentTimeMillis(); + // 'sleep' should be strictly greater than zero, otherwise real time is not taken into consideration + // and the thread simply waits until notified + long sleep = Math.max(timeout / 20, 1); + boolean done = false; + while (!done) { + try { + // although nobody going to notify us + // it is vital to give up lock on a database + wait(sleep); + } catch (InterruptedException e1) { + // ignore + } + if (System.currentTimeMillis() - start > timeout) { + for (SessionLocal s : all) { + if (s != except && !s.isClosed()) { + try { + // this will rollback outstanding transaction + s.close(); + } catch (Throwable e) { + trace.error(e, "disconnecting session #{0}", s.getId()); + } + } + } + break; + } + done = true; + for (SessionLocal s : all) { + if (s != except && !s.isClosed()) { + done = false; + break; + } + } + } + } + + /** + * Close the database. + * + * @param fromShutdownHook true if this method is called from the shutdown + * hook + */ + void close(boolean fromShutdownHook) { + DbException b = backgroundException.getAndSet(null); + try { + closeImpl(fromShutdownHook); + } catch (Throwable t) { + if (b != null) { + t.addSuppressed(b); + } + throw t; + } + if (b != null) { + // wrap the exception, so we see it was thrown here + throw DbException.get(b.getErrorCode(), b, b.getMessage()); + } + } + + private void closeImpl(boolean fromShutdownHook) { + synchronized (this) { + if (closing || !fromShutdownHook && !userSessions.isEmpty()) { + return; + } + closing = true; + stopServer(); + if (!userSessions.isEmpty()) { + assert fromShutdownHook; + trace.info("closing {0} from shutdown hook", databaseName); + closeAllSessionsExcept(null); + } + trace.info("closing {0}", databaseName); + if (eventListener != null) { + // allow the event listener to connect to the database + closing = false; + DatabaseEventListener e = eventListener; + // set it to null, to make sure it's called only once + eventListener = null; + e.closingDatabase(); + closing = true; + if (!userSessions.isEmpty()) { + trace.info("event listener {0} left connection open", e.getClass().getName()); + // if listener left an open connection + closeAllSessionsExcept(null); + } + } + if (!this.isReadOnly()) { + removeOrphanedLobs(); + } + } + try { + try { + if (systemSession != null) { + if (powerOffCount != -1) { + for (Schema schema : schemas.values()) { + for (Table table : schema.getAllTablesAndViews(null)) { + if (table.isGlobalTemporary()) { + table.removeChildrenAndResources(systemSession); + } else { + table.close(systemSession); + } + } + } + for (Schema schema : schemas.values()) { + for (Sequence sequence : schema.getAllSequences()) { + sequence.close(); + } + } + } + for (Schema schema : schemas.values()) { + for (TriggerObject trigger : schema.getAllTriggers()) { + try { + trigger.close(); + } catch (SQLException e) { + trace.error(e, "close"); + } + } + } + if (powerOffCount != -1) { + 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) { + systemSession.close(); + systemSession = null; + } + closeOpenFilesAndUnlock(); + } catch (DbException e) { + trace.error(e, "close"); + } + trace.info("closed"); + traceSystem.close(); + OnExitDatabaseCloser.unregister(this); + if (deleteFilesOnDisconnect && persistent) { + deleteFilesOnDisconnect = false; + try { + String directory = FileUtils.getParent(databaseName); + String name = FileUtils.getName(databaseName); + DeleteDbFiles.execute(directory, name, true); + } catch (Exception e) { + // ignore (the trace is closed already) + } + } + } finally { + Engine.close(databaseName); + } + } + + 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 { + if (!store.getMvStore().isClosed()) { + if (compactMode == CommandInterface.SHUTDOWN_IMMEDIATELY) { + store.closeImmediately(); + } else { + int allowedCompactionTime = + compactMode == CommandInterface.SHUTDOWN_COMPACT || + compactMode == CommandInterface.SHUTDOWN_DEFRAG || + dbSettings.defragAlways ? -1 : dbSettings.maxCompactTime; + store.close(allowedCompactionTime); + } + } + if (persistent) { + // Don't delete temp files if everything is already closed + // (maybe in checkPowerOff), the database could be open now + // (even from within another process). + if (lock != null || fileLockMethod == FileLockMethod.NO || fileLockMethod == FileLockMethod.FS) { + deleteOldTempFiles(); + } + } + } finally { + if (lock != null) { + lock.unlock(); + lock = null; + } + } + } + + private synchronized void closeFiles() { + try { + store.closeImmediately(); + } catch (DbException e) { + trace.error(e, "close"); + } + } + + private void checkMetaFree(SessionLocal session, int id) { + SearchRow r = meta.getRowFactory().createRow(); + r.setValue(0, ValueInteger.get(id)); + Cursor cursor = metaIdIndex.find(session, r, r); + if (cursor.next()) { + throw DbException.getInternalError(); + } + } + + /** + * Allocate a new object id. + * + * @return the id + */ + public int allocateObjectId() { + int i; + synchronized (objectIds) { + i = objectIds.nextClearBit(0); + objectIds.set(i); + } + return i; + } + + /** + * Returns system user. + * + * @return system user + */ + public User getSystemUser() { + return systemUser; + } + + /** + * Returns main schema (usually PUBLIC). + * + * @return main schema (usually PUBLIC) + */ + public Schema getMainSchema() { + return mainSchema; + } + + public ArrayList getAllComments() { + return new ArrayList<>(comments.values()); + } + + public int getAllowLiterals() { + if (starting) { + return Constants.ALLOW_LITERALS_ALL; + } + return allowLiterals; + } + + public ArrayList getAllRights() { + return new ArrayList<>(rights.values()); + } + + /** + * Get all tables and views. Meta data tables may be excluded. + * + * @return all objects of that type + */ + public ArrayList

getAllTablesAndViews() { + ArrayList
list = new ArrayList<>(); + for (Schema schema : schemas.values()) { + list.addAll(schema.getAllTablesAndViews(null)); + } + return list; + } + + /** + * Get all synonyms. + * + * @return all objects of that type + */ + public ArrayList getAllSynonyms() { + ArrayList list = new ArrayList<>(); + for (Schema schema : schemas.values()) { + list.addAll(schema.getAllSynonyms()); + } + return list; + } + + public Collection getAllSchemas() { + return schemas.values(); + } + + public Collection getAllSchemasNoMeta() { + return schemas.values(); + } + + public Collection getAllSettings() { + return settings.values(); + } + + public Collection getAllUsersAndRoles() { + return usersAndRoles.values(); + } + + public String getCacheType() { + return cacheType; + } + + public String getCluster() { + return cluster; + } + + @Override + public CompareMode getCompareMode() { + return compareMode; + } + + @Override + public String getDatabasePath() { + if (persistent) { + return FileUtils.toRealPath(databaseName); + } + return null; + } + + public String getShortName() { + return databaseShortName; + } + + public String getName() { + return databaseName; + } + + /** + * Get all sessions that are currently connected to the database. + * + * @param includingSystemSession if the system session should also be + * included + * @return the list of sessions + */ + public SessionLocal[] getSessions(boolean includingSystemSession) { + ArrayList list; + // need to synchronized on this database, + // otherwise the list may contain null elements + synchronized (this) { + list = new ArrayList<>(userSessions); + } + if (includingSystemSession) { + // copy, to ensure the reference is stable + SessionLocal s = systemSession; + if (s != null) { + list.add(s); + } + s = lobSession; + if (s != null) { + list.add(s); + } + } + return list.toArray(new SessionLocal[0]); + } + + /** + * Update an object in the system table. + * + * @param session the session + * @param obj the database object + */ + public void updateMeta(SessionLocal session, DbObject obj) { + int id = obj.getId(); + if (id > 0) { + if (!starting && !obj.isTemporary()) { + Row newRow = meta.getTemplateRow(); + MetaRecord.populateRowFromDBObject(obj, newRow); + Row oldRow = metaIdIndex.getRow(session, id); + if (oldRow != null) { + meta.updateRow(session, oldRow, newRow); + } + } + // for temporary objects + synchronized (objectIds) { + objectIds.set(id); + } + } + } + + /** + * Rename a schema object. + * + * @param session the session + * @param obj the object + * @param newName the new name + */ + public synchronized void renameSchemaObject(SessionLocal session, + SchemaObject obj, String newName) { + checkWritingAllowed(); + obj.getSchema().rename(obj, newName); + updateMetaAndFirstLevelChildren(session, obj); + } + + private synchronized void updateMetaAndFirstLevelChildren(SessionLocal session, DbObject obj) { + ArrayList list = obj.getChildren(); + Comment comment = findComment(obj); + if (comment != null) { + throw DbException.getInternalError(comment.toString()); + } + updateMeta(session, obj); + // remember that this scans only one level deep! + if (list != null) { + for (DbObject o : list) { + if (o.getCreateSQL() != null) { + updateMeta(session, o); + } + } + } + } + + /** + * Rename a database object. + * + * @param session the session + * @param obj the object + * @param newName the new name + */ + public synchronized void renameDatabaseObject(SessionLocal session, + DbObject obj, String newName) { + checkWritingAllowed(); + int type = obj.getType(); + Map map = getMap(type); + if (SysProperties.CHECK) { + if (!map.containsKey(obj.getName())) { + throw DbException.getInternalError("not found: " + obj.getName()); + } + if (obj.getName().equals(newName) || map.containsKey(newName)) { + throw DbException.getInternalError("object already exists: " + newName); + } + } + obj.checkRename(); + map.remove(obj.getName()); + obj.rename(newName); + map.put(newName, obj); + updateMetaAndFirstLevelChildren(session, obj); + } + + private void deleteOldTempFiles() { + String path = FileUtils.getParent(databaseName); + for (String name : FileUtils.newDirectoryStream(path)) { + if (name.endsWith(Constants.SUFFIX_TEMP_FILE) && + name.startsWith(databaseName)) { + // can't always delete the files, they may still be open + FileUtils.tryDelete(name); + } + } + } + + /** + * Get the schema. If the schema does not exist, an exception is thrown. + * + * @param schemaName the name of the schema + * @return the schema + * @throws DbException no schema with that name exists + */ + public Schema getSchema(String schemaName) { + Schema schema = findSchema(schemaName); + if (schema == null) { + throw DbException.get(ErrorCode.SCHEMA_NOT_FOUND_1, schemaName); + } + return schema; + } + + /** + * Remove the object from the database. + * + * @param session the session + * @param obj the object to remove + */ + public synchronized void removeDatabaseObject(SessionLocal session, DbObject obj) { + checkWritingAllowed(); + String objName = obj.getName(); + int type = obj.getType(); + Map map = getMap(type); + if (SysProperties.CHECK && !map.containsKey(objName)) { + throw DbException.getInternalError("not found: " + objName); + } + Comment comment = findComment(obj); + lockMeta(session); + if (comment != null) { + removeDatabaseObject(session, comment); + } + int id = obj.getId(); + obj.removeChildrenAndResources(session); + map.remove(objName); + removeMeta(session, id); + } + + /** + * Get the first table that depends on this object. + * + * @param obj the object to find + * @param except the table to exclude (or null) + * @return the first dependent table, or null + */ + public Table getDependentTable(SchemaObject obj, Table except) { + switch (obj.getType()) { + case DbObject.COMMENT: + case DbObject.CONSTRAINT: + case DbObject.INDEX: + case DbObject.RIGHT: + case DbObject.TRIGGER: + case DbObject.USER: + return null; + default: + } + HashSet set = new HashSet<>(); + for (Schema schema : schemas.values()) { + for (Table t : schema.getAllTablesAndViews(null)) { + if (except == t || TableType.VIEW == t.getTableType()) { + continue; + } + set.clear(); + t.addDependencies(set); + if (set.contains(obj)) { + return t; + } + } + } + return null; + } + + /** + * Remove an object from the system table. + * + * @param session the session + * @param obj the object to be removed + */ + public void removeSchemaObject(SessionLocal session, + SchemaObject obj) { + int type = obj.getType(); + if (type == DbObject.TABLE_OR_VIEW) { + Table table = (Table) obj; + if (table.isTemporary() && !table.isGlobalTemporary()) { + session.removeLocalTempTable(table); + return; + } + } else if (type == DbObject.INDEX) { + Index index = (Index) obj; + Table table = index.getTable(); + if (table.isTemporary() && !table.isGlobalTemporary()) { + session.removeLocalTempTableIndex(index); + return; + } + } else if (type == DbObject.CONSTRAINT) { + Constraint constraint = (Constraint) obj; + if (constraint.getConstraintType() != Type.DOMAIN) { + Table table = constraint.getTable(); + if (table.isTemporary() && !table.isGlobalTemporary()) { + session.removeLocalTempTableConstraint(constraint); + return; + } + } + } + checkWritingAllowed(); + lockMeta(session); + synchronized (this) { + Comment comment = findComment(obj); + if (comment != null) { + removeDatabaseObject(session, comment); + } + obj.getSchema().remove(obj); + int id = obj.getId(); + if (!starting) { + Table t = getDependentTable(obj, null); + if (t != null) { + obj.getSchema().add(obj); + throw DbException.get(ErrorCode.CANNOT_DROP_2, obj.getTraceSQL(), t.getTraceSQL()); + } + obj.removeChildrenAndResources(session); + } + removeMeta(session, id); + } + } + + /** + * Check if this database is disk-based. + * + * @return true if it is disk-based, false if it is in-memory only. + */ + public boolean isPersistent() { + return persistent; + } + + public TraceSystem getTraceSystem() { + return traceSystem; + } + + public synchronized void setCacheSize(int kb) { + if (starting) { + int max = MathUtils.convertLongToInt(Utils.getMemoryMax()) / 2; + kb = Math.min(kb, max); + } + store.setCacheSize(Math.max(1, kb)); + } + + public synchronized void setMasterUser(User user) { + lockMeta(systemSession); + addDatabaseObject(systemSession, user); + systemSession.commit(true); + } + + public Role getPublicRole() { + return publicRole; + } + + /** + * Get a unique temporary table name. + * + * @param baseName the prefix of the returned name + * @param session the session + * @return a unique name + */ + public synchronized String getTempTableName(String baseName, SessionLocal session) { + String tempName; + do { + tempName = baseName + "_COPY_" + session.getId() + + "_" + nextTempTableId++; + } while (mainSchema.findTableOrView(session, tempName) != null); + return tempName; + } + + public void setCompareMode(CompareMode compareMode) { + this.compareMode = compareMode; + } + + public void setCluster(String cluster) { + this.cluster = cluster; + } + + @Override + public void checkWritingAllowed() { + if (readOnly) { + throw DbException.get(ErrorCode.DATABASE_IS_READ_ONLY); + } + } + + public boolean isReadOnly() { + return readOnly; + } + + public void setWriteDelay(int value) { + store.getMvStore().setAutoCommitDelay(value < 0 ? 0 : value); + } + + public int getRetentionTime() { + return store.getMvStore().getRetentionTime(); + } + + public void setRetentionTime(int value) { + store.getMvStore().setRetentionTime(value); + } + + public void setAllowBuiltinAliasOverride(boolean b) { + allowBuiltinAliasOverride = b; + } + + public boolean isAllowBuiltinAliasOverride() { + return allowBuiltinAliasOverride; + } + + /** + * Get the list of in-doubt transactions. + * + * @return the list + */ + public ArrayList getInDoubtTransactions() { + return store.getInDoubtTransactions(); + } + + /** + * Prepare a transaction. + * + * @param session the session + * @param transaction the name of the transaction + */ + synchronized void prepareCommit(SessionLocal session, String transaction) { + if (!readOnly) { + store.prepareCommit(session, transaction); + } + } + + /** + * If there is a background store thread, and if there wasn an exception in + * that thread, throw it now. + */ + void throwLastBackgroundException() { + if (!store.getMvStore().isBackgroundThread()) { + DbException b = backgroundException.getAndSet(null); + if (b != null) { + // wrap the exception, so we see it was thrown here + throw DbException.get(b.getErrorCode(), b, b.getMessage()); + } + } + } + + public void setBackgroundException(DbException e) { + if (backgroundException.compareAndSet(null, e)) { + TraceSystem t = getTraceSystem(); + if (t != null) { + t.getTrace(Trace.DATABASE).error(e, "flush"); + } + } + } + + public Throwable getBackgroundException() { + MVStoreException exception = store.getMvStore().getPanicException(); + if(exception != null) { + return exception; + } + return backgroundException.getAndSet(null); + } + + + /** + * Flush all pending changes to the transaction log. + */ + public synchronized void flush() { + if (!readOnly) { + try { + store.flush(); + } catch (RuntimeException e) { + backgroundException.compareAndSet(null, DbException.convert(e)); + throw e; + } + } + } + + public void setEventListener(DatabaseEventListener eventListener) { + this.eventListener = eventListener; + } + + public void setEventListenerClass(String className) { + if (className == null || className.isEmpty()) { + eventListener = null; + } else { + try { + eventListener = (DatabaseEventListener) + JdbcUtils.loadUserClass(className).getDeclaredConstructor().newInstance(); + String url = databaseURL; + if (cipher != null) { + url += ";CIPHER=" + cipher; + } + eventListener.init(url); + } catch (Throwable e) { + throw DbException.get( + ErrorCode.ERROR_SETTING_DATABASE_EVENT_LISTENER_2, e, + className, e.toString()); + } + } + } + + /** + * Set the progress of a long running operation. + * This method calls the {@link DatabaseEventListener} if one is registered. + * + * @param state the {@link DatabaseEventListener} state + * @param name the object name + * @param x the current position + * @param max the highest value or 0 if unknown + */ + public void setProgress(int state, String name, long x, long max) { + if (eventListener != null) { + try { + eventListener.setProgress(state, name, x, max); + } catch (Exception e2) { + // ignore this (user made) exception + } + } + } + + /** + * This method is called after an exception occurred, to inform the database + * event listener (if one is set). + * + * @param e the exception + * @param sql the SQL statement + */ + public void exceptionThrown(SQLException e, String sql) { + if (eventListener != null) { + try { + eventListener.exceptionThrown(e, sql); + } catch (Exception e2) { + // ignore this (user made) exception + } + } + } + + /** + * Synchronize the files with the file system. This method is called when + * executing the SQL statement CHECKPOINT SYNC. + */ + public synchronized void sync() { + if (readOnly) { + return; + } + store.sync(); + } + + public int getMaxMemoryRows() { + return maxMemoryRows; + } + + public void setMaxMemoryRows(int value) { + this.maxMemoryRows = value; + } + + public void setLockMode(int lockMode) { + switch (lockMode) { + case Constants.LOCK_MODE_OFF: + case Constants.LOCK_MODE_READ_COMMITTED: + break; + case Constants.LOCK_MODE_TABLE: + case Constants.LOCK_MODE_TABLE_GC: + lockMode = Constants.LOCK_MODE_READ_COMMITTED; + break; + default: + throw DbException.getInvalidValueException("lock mode", lockMode); + } + this.lockMode = lockMode; + } + + public int getLockMode() { + return lockMode; + } + + public void setCloseDelay(int value) { + this.closeDelay = value; + } + + public SessionLocal getSystemSession() { + return systemSession; + } + + /** + * Check if the database is in the process of closing. + * + * @return true if the database is closing + */ + public boolean isClosing() { + return closing; + } + + public void setMaxLengthInplaceLob(int value) { + this.maxLengthInplaceLob = value; + } + + @Override + public int getMaxLengthInplaceLob() { + return maxLengthInplaceLob; + } + + public void setIgnoreCase(boolean b) { + ignoreCase = b; + } + + public boolean getIgnoreCase() { + if (starting) { + // tables created at startup must not be converted to ignorecase + return false; + } + return ignoreCase; + } + + public void setIgnoreCatalogs(boolean b) { + ignoreCatalogs = b; + } + + public boolean getIgnoreCatalogs() { + return ignoreCatalogs; + } + + + public synchronized void setDeleteFilesOnDisconnect(boolean b) { + this.deleteFilesOnDisconnect = b; + } + + public void setAllowLiterals(int value) { + this.allowLiterals = value; + } + + public boolean getOptimizeReuseResults() { + return optimizeReuseResults; + } + + public void setOptimizeReuseResults(boolean b) { + optimizeReuseResults = b; + } + + @Override + public Object getLobSyncObject() { + return lobSyncObject; + } + + public int getSessionCount() { + return userSessions.size(); + } + + public void setReferentialIntegrity(boolean b) { + referentialIntegrity = b; + } + + public boolean getReferentialIntegrity() { + return referentialIntegrity; + } + + public void setQueryStatistics(boolean b) { + queryStatistics = b; + synchronized (this) { + if (!b) { + queryStatisticsData = null; + } + } + } + + public boolean getQueryStatistics() { + return queryStatistics; + } + + public void setQueryStatisticsMaxEntries(int n) { + queryStatisticsMaxEntries = n; + if (queryStatisticsData != null) { + synchronized (this) { + if (queryStatisticsData != null) { + queryStatisticsData.setMaxQueryEntries(queryStatisticsMaxEntries); + } + } + } + } + + public QueryStatisticsData getQueryStatisticsData() { + if (!queryStatistics) { + return null; + } + if (queryStatisticsData == null) { + synchronized (this) { + if (queryStatisticsData == null) { + queryStatisticsData = new QueryStatisticsData(queryStatisticsMaxEntries); + } + } + } + return queryStatisticsData; + } + + /** + * Check if the database is currently opening. This is true until all stored + * SQL statements have been executed. + * + * @return true if the database is still starting + */ + public boolean isStarting() { + return starting; + } + + /** + * Called after the database has been opened and initialized. This method + * notifies the event listener if one has been set. + */ + void opened() { + if (eventListener != null) { + eventListener.opened(); + } + } + + public void setMode(Mode mode) { + this.mode = mode; + getNextRemoteSettingsId(); + } + + @Override + public Mode getMode() { + return mode; + } + + public void setDefaultNullOrdering(DefaultNullOrdering defaultNullOrdering) { + this.defaultNullOrdering = defaultNullOrdering; + } + + public DefaultNullOrdering getDefaultNullOrdering() { + return defaultNullOrdering; + } + + public void setMaxOperationMemory(int maxOperationMemory) { + this.maxOperationMemory = maxOperationMemory; + } + + public int getMaxOperationMemory() { + return maxOperationMemory; + } + + public SessionLocal getExclusiveSession() { + return exclusiveSession.get(); + } + + /** + * Set the session that can exclusively access the database. + * + * @param session the session + * @param closeOthers whether other sessions are closed + * @return true if success or if database is in exclusive mode + * set by this session already, false otherwise + */ + public boolean setExclusiveSession(SessionLocal session, boolean closeOthers) { + if (exclusiveSession.get() != session && + !exclusiveSession.compareAndSet(null, session)) { + return false; + } + if (closeOthers) { + closeAllSessionsExcept(session); + } + return true; + } + + /** + * Stop exclusive access the database by provided session. + * + * @param session the session + * @return true if success or if database is in non-exclusive mode already, + * false otherwise + */ + public boolean unsetExclusiveSession(SessionLocal session) { + return exclusiveSession.get() == null + || exclusiveSession.compareAndSet(session, null); + } + + @Override + public SmallLRUCache getLobFileListCache() { + if (lobFileListCache == null) { + lobFileListCache = SmallLRUCache.newInstance(128); + } + return lobFileListCache; + } + + /** + * Checks if the system table (containing the catalog) is locked. + * + * @return true if it is currently locked + */ + public boolean isSysTableLocked() { + return meta == null || meta.isLockedExclusively(); + } + + /** + * Checks if the system table (containing the catalog) is locked by the + * given session. + * + * @param session the session + * @return true if it is currently locked + */ + public boolean isSysTableLockedBy(SessionLocal session) { + return meta == null || meta.isLockedExclusivelyBy(session); + } + + /** + * Open a new connection or get an existing connection to another database. + * + * @param driver the database driver or null + * @param url the database URL + * @param user the user name + * @param password the password + * @return the connection + */ + public TableLinkConnection getLinkConnection(String driver, String url, + String user, String password) { + if (linkConnections == null) { + linkConnections = new HashMap<>(); + } + return TableLinkConnection.open(linkConnections, driver, url, user, + password, dbSettings.shareLinkedConnections); + } + + @Override + public String toString() { + return databaseShortName + ":" + super.toString(); + } + + /** + * Immediately close the database. + */ + public void shutdownImmediately() { + closing = true; + setPowerOffCount(1); + try { + checkPowerOff(); + } catch (DbException e) { + // ignore + } + closeFiles(); + powerOffCount = 0; + } + + @Override + public TempFileDeleter getTempFileDeleter() { + return tempFileDeleter; + } + + /** + * Get the first user defined table, excluding the LOB_BLOCKS table that the + * Recover tool creates. + * + * @return the table or null if no table is defined + */ + public Table getFirstUserTable() { + for (Schema schema : schemas.values()) { + for (Table table : schema.getAllTablesAndViews(null)) { + if (table.getCreateSQL() == null || table.isHidden()) { + continue; + } + // exclude the LOB_MAP that the Recover tool creates + if (schema.getId() == Constants.INFORMATION_SCHEMA_ID + && table.getName().equalsIgnoreCase("LOB_BLOCKS")) { + continue; + } + return table; + } + } + return null; + } + + /** + * Flush all changes and open a new transaction log. + */ + public void checkpoint() { + if (persistent) { + store.flush(); + } + getTempFileDeleter().deleteUnused(); + } + + /** + * Switch the database to read-only mode. + * + * @param readOnly the new value + */ + public void setReadOnly(boolean readOnly) { + this.readOnly = readOnly; + } + + public void setCompactMode(int compactMode) { + this.compactMode = compactMode; + } + + public SourceCompiler getCompiler() { + if (compiler == null) { + compiler = new SourceCompiler(); + } + return compiler; + } + + @Override + public LobStorageInterface getLobStorage() { + return lobStorage; + } + + public SessionLocal getLobSession() { + return lobSession; + } + + public int getDefaultTableType() { + return defaultTableType; + } + + public void setDefaultTableType(int defaultTableType) { + this.defaultTableType = defaultTableType; + } + + public DbSettings getSettings() { + return dbSettings; + } + + /** + * Create a new hash map. Depending on the configuration, the key is case + * sensitive or case insensitive. + * + * @param the value type + * @return the hash map + */ + public HashMap newStringMap() { + return dbSettings.caseInsensitiveIdentifiers ? new CaseInsensitiveMap<>() : new HashMap<>(); + } + + /** + * Create a new hash map. Depending on the configuration, the key is case + * sensitive or case insensitive. + * + * @param the value type + * @param initialCapacity the initial capacity + * @return the hash map + */ + public HashMap newStringMap(int initialCapacity) { + return dbSettings.caseInsensitiveIdentifiers ? new CaseInsensitiveMap<>(initialCapacity) + : new HashMap<>(initialCapacity); + } + + /** + * Create a new hash map. Depending on the configuration, the key is case + * sensitive or case insensitive. + * + * @param the value type + * @return the hash map + */ + public ConcurrentHashMap newConcurrentStringMap() { + return dbSettings.caseInsensitiveIdentifiers ? new CaseInsensitiveConcurrentMap<>() + : new ConcurrentHashMap<>(); + } + + /** + * Compare two identifiers (table names, column names,...) and verify they + * are equal. Case sensitivity depends on the configuration. + * + * @param a the first identifier + * @param b the second identifier + * @return true if they match + */ + public boolean equalsIdentifiers(String a, String b) { + return a.equals(b) || dbSettings.caseInsensitiveIdentifiers && a.equalsIgnoreCase(b); + } + + /** + * Returns identifier in upper or lower case depending on database settings. + * + * @param upperName + * identifier in the upper case + * @return identifier in upper or lower case + */ + public String sysIdentifier(String upperName) { + assert isUpperSysIdentifier(upperName); + return dbSettings.databaseToLower ? StringUtils.toLowerEnglish(upperName) : upperName; + } + + private static boolean isUpperSysIdentifier(String upperName) { + int l = upperName.length(); + if (l == 0) { + return false; + } + char c = upperName.charAt(0); + if (c < 'A' || c > 'Z') { + return false; + } + l--; + for (int i = 1; i < l; i++) { + c = upperName.charAt(i); + if ((c < 'A' || c > 'Z') && c != '_') { + return false; + } + } + if (l > 0) { + c = upperName.charAt(l); + if (c < 'A' || c > 'Z') { + return false; + } + } + return true; + } + + @Override + public int readLob(long lobId, byte[] hmac, long offset, byte[] buff, int off, int length) { + throw DbException.getInternalError(); + } + + public byte[] getFileEncryptionKey() { + return fileEncryptionKey; + } + + public int getPageSize() { + return pageSize; + } + + @Override + public JavaObjectSerializer getJavaObjectSerializer() { + initJavaObjectSerializer(); + return javaObjectSerializer; + } + + private void initJavaObjectSerializer() { + if (javaObjectSerializerInitialized) { + return; + } + synchronized (this) { + if (javaObjectSerializerInitialized) { + return; + } + String serializerName = javaObjectSerializerName; + if (serializerName != null) { + serializerName = serializerName.trim(); + if (!serializerName.isEmpty() && + !serializerName.equals("null")) { + try { + javaObjectSerializer = (JavaObjectSerializer) + JdbcUtils.loadUserClass(serializerName).getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw DbException.convert(e); + } + } + } + javaObjectSerializerInitialized = true; + } + } + + public void setJavaObjectSerializerName(String serializerName) { + synchronized (this) { + javaObjectSerializerInitialized = false; + javaObjectSerializerName = serializerName; + getNextRemoteSettingsId(); + } + } + + /** + * Get the table engine class, loading it if needed. + * + * @param tableEngine the table engine name + * @return the class + */ + public TableEngine getTableEngine(String tableEngine) { + assert Thread.holdsLock(this); + + TableEngine engine = tableEngines.get(tableEngine); + if (engine == null) { + try { + engine = (TableEngine) JdbcUtils.loadUserClass(tableEngine).getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw DbException.convert(e); + } + tableEngines.put(tableEngine, engine); + } + return engine; + } + + /** + * get authenticator for database users + * @return authenticator set for database + */ + public Authenticator getAuthenticator() { + return authenticator; + } + + /** + * Set current database authenticator + * + * @param authenticator = authenticator to set, null to revert to the Internal authenticator + */ + public void setAuthenticator(Authenticator authenticator) { + if (authenticator!=null) { + authenticator.init(this); + } + this.authenticator=authenticator; + } + + @Override + public ValueTimestampTimeZone currentTimestamp() { + Session session = SessionLocal.getThreadLocalSession(); + if (session != null) { + return session.currentTimestamp(); + } + throw DbException.getUnsupportedException("Unsafe comparison or cast"); + } + + @Override + public TimeZoneProvider currentTimeZone() { + Session session = SessionLocal.getThreadLocalSession(); + if (session != null) { + return session.currentTimeZone(); + } + throw DbException.getUnsupportedException("Unsafe comparison or cast"); + } + + @Override + public boolean zeroBasedEnums() { + return dbSettings.zeroBasedEnums; + } + +} diff --git a/h2/src/main/org/h2/engine/DbObject.java b/h2/src/main/org/h2/engine/DbObject.java new file mode 100644 index 0000000..7464f97 --- /dev/null +++ b/h2/src/main/org/h2/engine/DbObject.java @@ -0,0 +1,331 @@ +/* + * 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.engine; + +import java.util.ArrayList; + +import org.h2.command.Parser; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.table.Table; +import org.h2.util.HasSQL; +import org.h2.util.ParserUtil; + +/** + * A database object such as a table, an index, or a user. + */ +public abstract class DbObject implements HasSQL { + + /** + * The object is of the type table or view. + */ + public static final int TABLE_OR_VIEW = 0; + + /** + * This object is an index. + */ + public static final int INDEX = 1; + + /** + * This object is a user. + */ + public static final int USER = 2; + + /** + * This object is a sequence. + */ + public static final int SEQUENCE = 3; + + /** + * This object is a trigger. + */ + public static final int TRIGGER = 4; + + /** + * This object is a constraint (check constraint, unique constraint, or + * referential constraint). + */ + public static final int CONSTRAINT = 5; + + /** + * This object is a setting. + */ + public static final int SETTING = 6; + + /** + * This object is a role. + */ + public static final int ROLE = 7; + + /** + * This object is a right. + */ + public static final int RIGHT = 8; + + /** + * This object is an alias for a Java function. + */ + public static final int FUNCTION_ALIAS = 9; + + /** + * This object is a schema. + */ + public static final int SCHEMA = 10; + + /** + * This object is a constant. + */ + public static final int CONSTANT = 11; + + /** + * This object is a domain. + */ + public static final int DOMAIN = 12; + + /** + * This object is a comment. + */ + public static final int COMMENT = 13; + + /** + * This object is a user-defined aggregate function. + */ + public static final int AGGREGATE = 14; + + /** + * This object is a synonym. + */ + public static final int SYNONYM = 15; + + /** + * The database. + */ + protected Database database; + + /** + * The trace module. + */ + protected Trace trace; + + /** + * The comment (if set). + */ + protected String comment; + + private int id; + + private String objectName; + + private long modificationId; + + private boolean temporary; + + /** + * Initialize some attributes of this object. + * + * @param db the database + * @param objectId the object id + * @param name the name + * @param traceModuleId the trace module id + */ + protected DbObject(Database db, int objectId, String name, int traceModuleId) { + this.database = db; + this.trace = db.getTrace(traceModuleId); + this.id = objectId; + this.objectName = name; + this.modificationId = db.getModificationMetaId(); + } + + /** + * Tell the object that is was modified. + */ + public final void setModified() { + this.modificationId = database == null ? -1 : database.getNextModificationMetaId(); + } + + public final long getModificationId() { + return modificationId; + } + + protected final void setObjectName(String name) { + objectName = name; + } + + @Override + public String getSQL(int sqlFlags) { + return Parser.quoteIdentifier(objectName, sqlFlags); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return ParserUtil.quoteIdentifier(builder, objectName, sqlFlags); + } + + /** + * Get the list of dependent children (for tables, this includes indexes and + * so on). + * + * @return the list of children, or {@code null} + */ + public ArrayList getChildren() { + return null; + } + + /** + * Get the database. + * + * @return the database + */ + public final Database getDatabase() { + return database; + } + + /** + * Get the unique object id. + * + * @return the object id + */ + public final int getId() { + return id; + } + + /** + * Get the name. + * + * @return the name + */ + public final String getName() { + return objectName; + } + + /** + * Set the main attributes to null to make sure the object is no longer + * used. + */ + protected void invalidate() { + if (id == -1) { + throw DbException.getInternalError(); + } + setModified(); + id = -1; + database = null; + trace = null; + objectName = null; + } + + public final boolean isValid() { + return id != -1; + } + + /** + * Build a SQL statement to re-create the object, or to create a copy of the + * object with a different name or referencing a different table + * + * @param table the new table + * @param quotedName the quoted name + * @return the SQL statement + */ + public abstract String getCreateSQLForCopy(Table table, String quotedName); + + /** + * Construct the CREATE ... SQL statement for this object for meta table. + * + * @return the SQL statement + */ + public String getCreateSQLForMeta() { + return getCreateSQL(); + } + + /** + * Construct the CREATE ... SQL statement for this object. + * + * @return the SQL statement + */ + public abstract String getCreateSQL(); + + /** + * Construct a DROP ... SQL statement for this object. + * + * @return the SQL statement + */ + public String getDropSQL() { + return null; + } + + /** + * Get the object type. + * + * @return the object type + */ + public abstract int getType(); + + /** + * Delete all dependent children objects and resources of this object. + * + * @param session the session + */ + public abstract void removeChildrenAndResources(SessionLocal session); + + /** + * Check if renaming is allowed. Does nothing when allowed. + */ + public void checkRename() { + // Allowed by default + } + + /** + * Rename the object. + * + * @param newName the new name + */ + public void rename(String newName) { + checkRename(); + objectName = newName; + setModified(); + } + + /** + * Check if this object is temporary (for example, a temporary table). + * + * @return true if is temporary + */ + public boolean isTemporary() { + return temporary; + } + + /** + * Tell this object that it is temporary or not. + * + * @param temporary the new value + */ + public void setTemporary(boolean temporary) { + this.temporary = temporary; + } + + /** + * Change the comment of this object. + * + * @param comment the new comment, or null for no comment + */ + public void setComment(String comment) { + this.comment = comment != null && !comment.isEmpty() ? comment : null; + } + + /** + * Get the current comment of this object. + * + * @return the comment, or null if not set + */ + public String getComment() { + return comment; + } + + @Override + public String toString() { + return objectName + ":" + id + ":" + super.toString(); + } + +} diff --git a/h2/src/main/org/h2/engine/DbSettings.java b/h2/src/main/org/h2/engine/DbSettings.java new file mode 100644 index 0000000..c4baede --- /dev/null +++ b/h2/src/main/org/h2/engine/DbSettings.java @@ -0,0 +1,329 @@ +/* + * 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.engine; + +import java.util.HashMap; + +import org.h2.api.ErrorCode; +import org.h2.message.DbException; + +/** + * This class contains various database-level settings. To override the + * documented default value for a database, append the setting in the database + * URL: "jdbc:h2:./test;ANALYZE_SAMPLE=1000" when opening the first connection + * to the database. The settings can not be changed once the database is open. + *

+ * Some settings are a last resort and temporary solution to work around a + * problem in the application or database engine. Also, there are system + * properties to enable features that are not yet fully tested or that are not + * backward compatible. + *

+ */ +public class DbSettings extends SettingsBase { + + /** + * The initial size of the hash table. + */ + static final int TABLE_SIZE = 64; + + /** + * INTERNAL. + * The default settings. Those must not be modified. + */ + public static final DbSettings DEFAULT = new DbSettings(new HashMap<>(TABLE_SIZE)); + + /** + * Database setting ANALYZE_AUTO (default: 2000). + * After changing this many rows, ANALYZE is automatically run for a table. + * Automatically running ANALYZE is disabled if set to 0. If set to 1000, + * then ANALYZE will run against each user table after about 1000 changes to + * that table. The time between running ANALYZE doubles each time since + * starting the database. It is not run on local temporary tables, and + * tables that have a trigger on SELECT. + */ + public final int analyzeAuto = get("ANALYZE_AUTO", 2000); + + /** + * Database setting ANALYZE_SAMPLE (default: 10000). + * The default sample size when analyzing a table. + */ + public final int analyzeSample = get("ANALYZE_SAMPLE", 10_000); + + /** + * Database setting AUTO_COMPACT_FILL_RATE + * (default: 90, which means 90%, 0 disables auto-compacting). + * Set the auto-compact target fill rate. If the average fill rate (the + * percentage of the storage space that contains active data) of the + * chunks is lower, then the chunks with a low fill rate are re-written. + * Also, if the percentage of empty space between chunks is higher than + * this value, then chunks at the end of the file are moved. Compaction + * stops if the target fill rate is reached. + * This setting only affects MVStore engine. + */ + public final int autoCompactFillRate = get("AUTO_COMPACT_FILL_RATE", 90); + + /** + * Database setting DATABASE_TO_LOWER (default: false). + * When set to true unquoted identifiers and short name of database are + * converted to lower case. Value of this setting should not be changed + * after creation of database. Setting this to "true" is experimental. + */ + public final boolean databaseToLower; + + /** + * Database setting DATABASE_TO_UPPER (default: true). + * When set to true unquoted identifiers and short name of database are + * converted to upper case. + */ + public final boolean databaseToUpper; + + /** + * Database setting CASE_INSENSITIVE_IDENTIFIERS (default: + * false). + * When set to true, all identifier names (table names, column names) are + * case insensitive. Setting this to "true" is experimental. + */ + public final boolean caseInsensitiveIdentifiers = get("CASE_INSENSITIVE_IDENTIFIERS", false); + + /** + * Database setting DB_CLOSE_ON_EXIT (default: true). + * Close the database when the virtual machine exits normally, using a + * shutdown hook. + */ + public final boolean dbCloseOnExit = get("DB_CLOSE_ON_EXIT", true); + + /** + * Database setting DEFAULT_CONNECTION (default: false). + * Whether Java functions can use + * DriverManager.getConnection("jdbc:default:connection") to + * get a database connection. This feature is disabled by default for + * performance reasons. Please note the Oracle JDBC driver will try to + * resolve this database URL if it is loaded before the H2 driver. + */ + public final boolean defaultConnection = get("DEFAULT_CONNECTION", false); + + /** + * Database setting DEFAULT_ESCAPE (default: \). + * The default escape character for LIKE comparisons. To select no escape + * character, use an empty string. + */ + public final String defaultEscape = get("DEFAULT_ESCAPE", "\\"); + + /** + * Database setting DEFRAG_ALWAYS (default: false) + * Each time the database is closed normally, it is fully defragmented (the + * same as SHUTDOWN DEFRAG). If you execute SHUTDOWN COMPACT, then this + * setting is ignored. + */ + public final boolean defragAlways = get("DEFRAG_ALWAYS", false); + + /** + * Database setting DROP_RESTRICT (default: true) + * Whether the default action for DROP TABLE, DROP VIEW, DROP SCHEMA, DROP + * DOMAIN, and DROP CONSTRAINT is RESTRICT. + */ + public final boolean dropRestrict = get("DROP_RESTRICT", true); + + /** + * Database setting ESTIMATED_FUNCTION_TABLE_ROWS (default: + * 1000). + * The estimated number of rows in a function table (for example, CSVREAD or + * FTL_SEARCH). This value is used by the optimizer. + */ + public final int estimatedFunctionTableRows = get( + "ESTIMATED_FUNCTION_TABLE_ROWS", 1000); + + /** + * Database setting LOB_TIMEOUT (default: 300000, + * which means 5 minutes). + * The number of milliseconds a temporary LOB reference is kept until it + * times out. After the timeout, the LOB is no longer accessible using this + * reference. + */ + public final int lobTimeout = get("LOB_TIMEOUT", 300_000); + + /** + * Database setting MAX_COMPACT_TIME (default: 200). + * The maximum time in milliseconds used to compact a database when closing. + */ + public final int maxCompactTime = get("MAX_COMPACT_TIME", 200); + + /** + * Database setting MAX_QUERY_TIMEOUT (default: 0). + * The maximum timeout of a query in milliseconds. The default is 0, meaning + * no limit. Please note the actual query timeout may be set to a lower + * value. + */ + public final int maxQueryTimeout = get("MAX_QUERY_TIMEOUT", 0); + + /** + * Database setting OPTIMIZE_DISTINCT (default: true). + * Improve the performance of simple DISTINCT queries if an index is + * available for the given column. The optimization is used if: + *
    + *
  • The select is a single column query without condition
  • + *
  • The query contains only one table, and no group by
  • + *
  • There is only one table involved
  • + *
  • There is an ascending index on the column
  • + *
  • The selectivity of the column is below 20
  • + *
+ */ + public final boolean optimizeDistinct = get("OPTIMIZE_DISTINCT", true); + + /** + * Database setting OPTIMIZE_EVALUATABLE_SUBQUERIES (default: + * true). + * Optimize subqueries that are not dependent on the outer query. + */ + public final boolean optimizeEvaluatableSubqueries = get( + "OPTIMIZE_EVALUATABLE_SUBQUERIES", true); + + /** + * Database setting OPTIMIZE_INSERT_FROM_SELECT + * (default: true). + * Insert into table from query directly bypassing temporary disk storage. + * This also applies to create table as select. + */ + public final boolean optimizeInsertFromSelect = get( + "OPTIMIZE_INSERT_FROM_SELECT", true); + + /** + * Database setting OPTIMIZE_IN_LIST (default: true). + * Optimize IN(...) and IN(SELECT ...) comparisons. This includes + * optimization for SELECT, DELETE, and UPDATE. + */ + public final boolean optimizeInList = get("OPTIMIZE_IN_LIST", true); + + /** + * Database setting OPTIMIZE_IN_SELECT (default: true). + * Optimize IN(SELECT ...) comparisons. This includes + * optimization for SELECT, DELETE, and UPDATE. + */ + public final boolean optimizeInSelect = get("OPTIMIZE_IN_SELECT", true); + + /** + * Database setting OPTIMIZE_OR (default: true). + * Convert (C=? OR C=?) to (C IN(?, ?)). + */ + public final boolean optimizeOr = get("OPTIMIZE_OR", true); + + /** + * Database setting OPTIMIZE_TWO_EQUALS (default: true). + * Optimize expressions of the form A=B AND B=1. In this case, AND A=1 is + * added so an index on A can be used. + */ + public final boolean optimizeTwoEquals = get("OPTIMIZE_TWO_EQUALS", true); + + /** + * Database setting OPTIMIZE_SIMPLE_SINGLE_ROW_SUBQUERIES (default: true). + * Optimize expressions of the form (SELECT A) to A. + */ + public final boolean optimizeSimpleSingleRowSubqueries = get("OPTIMIZE_SIMPLE_SINGLE_ROW_SUBQUERIES", true); + + /** + * Database setting QUERY_CACHE_SIZE (default: 8). + * The size of the query cache, in number of cached statements. Each session + * has it's own cache with the given size. The cache is only used if the SQL + * statement and all parameters match. Only the last returned result per + * query is cached. The following statement types are cached: SELECT + * statements are cached (excluding UNION and FOR UPDATE statements), CALL + * if it returns a single value, DELETE, INSERT, MERGE, UPDATE, and + * transactional statements such as COMMIT. This works for both statements + * and prepared statement. + */ + public final int queryCacheSize = get("QUERY_CACHE_SIZE", 8); + + /** + * Database setting RECOMPILE_ALWAYS (default: false). + * Always recompile prepared statements. + */ + public final boolean recompileAlways = get("RECOMPILE_ALWAYS", false); + + /** + * Database setting REUSE_SPACE (default: true). + * If disabled, all changes are appended to the database file, and existing + * content is never overwritten. This setting has no effect if the database + * is already open. + */ + public final boolean reuseSpace = get("REUSE_SPACE", true); + + /** + * Database setting SHARE_LINKED_CONNECTIONS + * (default: true). + * Linked connections should be shared, that means connections to the same + * database should be used for all linked tables that connect to the same + * database. + */ + public final boolean shareLinkedConnections = get( + "SHARE_LINKED_CONNECTIONS", true); + + /** + * Database setting DEFAULT_TABLE_ENGINE + * (default: null). + * The default table engine to use for new tables. + */ + public final String defaultTableEngine = get("DEFAULT_TABLE_ENGINE", null); + + /** + * Database setting MV_STORE + * (default: true). + * Use the MVStore storage engine. + */ + public final boolean mvStore = get("MV_STORE", true); + + /** + * Database setting COMPRESS + * (default: false). + * Compress data when storing. + */ + public final boolean compressData = get("COMPRESS", false); + + /** + * Database setting IGNORE_CATALOGS + * (default: false). + * If set, all catalog names in identifiers are silently accepted + * without comparing them with the short name of the database. + */ + public final boolean ignoreCatalogs = get("IGNORE_CATALOGS", false); + + /** + * Database setting ZERO_BASED_ENUMS + * (default: false). + * If set, ENUM ordinal values are 0-based. + */ + public final boolean zeroBasedEnums = get("ZERO_BASED_ENUMS", false); + + private DbSettings(HashMap s) { + super(s); + boolean lower = get("DATABASE_TO_LOWER", false); + boolean upperSet = containsKey("DATABASE_TO_UPPER"); + boolean upper = get("DATABASE_TO_UPPER", true); + if (lower && upper) { + if (upperSet) { + throw DbException.get(ErrorCode.UNSUPPORTED_SETTING_COMBINATION, + "DATABASE_TO_LOWER & DATABASE_TO_UPPER"); + } + upper = false; + } + databaseToLower = lower; + databaseToUpper = upper; + HashMap settings = getSettings(); + settings.put("DATABASE_TO_LOWER", Boolean.toString(lower)); + settings.put("DATABASE_TO_UPPER", Boolean.toString(upper)); + } + + /** + * INTERNAL. + * Get the settings for the given properties (may not be null). + * + * @param s the settings + * @return the settings + */ + static DbSettings getInstance(HashMap s) { + return new DbSettings(s); + } + +} diff --git a/h2/src/main/org/h2/engine/DelayedDatabaseCloser.java b/h2/src/main/org/h2/engine/DelayedDatabaseCloser.java new file mode 100644 index 0000000..2e6083f --- /dev/null +++ b/h2/src/main/org/h2/engine/DelayedDatabaseCloser.java @@ -0,0 +1,76 @@ +/* + * 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.engine; + +import java.lang.ref.WeakReference; + +import org.h2.message.Trace; + +/** + * This class is responsible to close a database after the specified delay. A + * database closer object only exists if there is no user connected to the + * database. + */ +class DelayedDatabaseCloser extends Thread { + + private final Trace trace; + private volatile WeakReference databaseRef; + private int delayInMillis; + + DelayedDatabaseCloser(Database db, int delayInMillis) { + databaseRef = new WeakReference<>(db); + this.delayInMillis = delayInMillis; + trace = db.getTrace(Trace.DATABASE); + setName("H2 Close Delay " + db.getShortName()); + setDaemon(true); + start(); + } + + /** + * Stop and disable the database closer. This method is called after a session + * has been created. + */ + void reset() { + databaseRef = null; + } + + @Override + public void run() { + while (delayInMillis > 0) { + try { + int step = 100; + Thread.sleep(step); + delayInMillis -= step; + } catch (Exception e) { + // ignore InterruptedException + } + WeakReference ref = databaseRef; + if (ref == null || ref.get() == null) { + return; + } + } + Database database; + WeakReference ref = databaseRef; + if (ref != null && (database = ref.get()) != null) { + try { + database.close(false); + } catch (RuntimeException e) { + // this can happen when stopping a web application, + // if loading classes is no longer allowed + // it would throw an IllegalStateException + try { + trace.error(e, "could not close the database"); + // if this was successful, we ignore the exception + // otherwise not + } catch (Throwable e2) { + e.addSuppressed(e2); + throw e; + } + } + } + } + +} diff --git a/h2/src/main/org/h2/engine/Engine.java b/h2/src/main/org/h2/engine/Engine.java new file mode 100644 index 0000000..2ee7732 --- /dev/null +++ b/h2/src/main/org/h2/engine/Engine.java @@ -0,0 +1,408 @@ +/* + * 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.engine; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.command.dml.SetTypes; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.security.auth.AuthenticationException; +import org.h2.security.auth.AuthenticationInfo; +import org.h2.security.auth.Authenticator; +import org.h2.store.fs.FileUtils; +import org.h2.util.DateTimeUtils; +import org.h2.util.MathUtils; +import org.h2.util.ParserUtil; +import org.h2.util.StringUtils; +import org.h2.util.ThreadDeadlockDetector; +import org.h2.util.TimeZoneProvider; +import org.h2.util.Utils; + +/** + * The engine contains a map of all open databases. + * It is also responsible for opening and creating new databases. + * This is a singleton class. + */ +public final class Engine { + + private static final Map DATABASES = new HashMap<>(); + + private static volatile long WRONG_PASSWORD_DELAY = SysProperties.DELAY_WRONG_PASSWORD_MIN; + + private static boolean JMX; + + static { + if (SysProperties.THREAD_DEADLOCK_DETECTOR) { + ThreadDeadlockDetector.init(); + } + } + + private static SessionLocal openSession(ConnectionInfo ci, boolean ifExists, boolean forbidCreation, + String cipher) { + String name = ci.getName(); + Database database; + ci.removeProperty("NO_UPGRADE", false); + boolean openNew = ci.getProperty("OPEN_NEW", false); + boolean opened = false; + User user = null; + DatabaseHolder databaseHolder; + if (!ci.isUnnamedInMemory()) { + synchronized (DATABASES) { + databaseHolder = DATABASES.computeIfAbsent(name, (key) -> new DatabaseHolder()); + } + } else { + databaseHolder = new DatabaseHolder(); + } + synchronized (databaseHolder) { + database = databaseHolder.database; + if (database == null || openNew) { + if (ci.isPersistent()) { + String p = ci.getProperty("MV_STORE"); + String fileName; + if (p == null) { + fileName = name + Constants.SUFFIX_MV_FILE; + if (!FileUtils.exists(fileName)) { + throwNotFound(ifExists, forbidCreation, name); + fileName = name + Constants.SUFFIX_OLD_DATABASE_FILE; + if (FileUtils.exists(fileName)) { + throw DbException.getFileVersionError(fileName); + } + fileName = null; + } + } else { + fileName = name + Constants.SUFFIX_MV_FILE; + if (!FileUtils.exists(fileName)) { + throwNotFound(ifExists, forbidCreation, name); + fileName = null; + } + } + if (fileName != null && !FileUtils.canWrite(fileName)) { + ci.setProperty("ACCESS_MODE_DATA", "r"); + } + } else { + throwNotFound(ifExists, forbidCreation, name); + } + database = new Database(ci, cipher); + opened = true; + boolean found = false; + for (RightOwner rightOwner : database.getAllUsersAndRoles()) { + if (rightOwner instanceof User) { + found = true; + break; + } + } + if (!found) { + // users is the last thing we add, so if no user is around, + // the database is new (or not initialized correctly) + user = new User(database, database.allocateObjectId(), ci.getUserName(), false); + user.setAdmin(true); + user.setUserPasswordHash(ci.getUserPasswordHash()); + database.setMasterUser(user); + } + databaseHolder.database = database; + } + } + + if (opened) { + // start the thread when already synchronizing on the database + // otherwise a deadlock can occur when the writer thread + // opens a new database (as in recovery testing) + database.opened(); + } + if (database.isClosing()) { + return null; + } + if (user == null) { + if (database.validateFilePasswordHash(cipher, ci.getFilePasswordHash())) { + if (ci.getProperty("AUTHREALM")== null) { + user = database.findUser(ci.getUserName()); + if (user != null) { + if (!user.validateUserPasswordHash(ci.getUserPasswordHash())) { + user = null; + } + } + } else { + Authenticator authenticator = database.getAuthenticator(); + if (authenticator==null) { + throw DbException.get(ErrorCode.AUTHENTICATOR_NOT_AVAILABLE, name); + } else { + try { + AuthenticationInfo authenticationInfo=new AuthenticationInfo(ci); + user = database.getAuthenticator().authenticate(authenticationInfo, database); + } catch (AuthenticationException authenticationError) { + database.getTrace(Trace.DATABASE).error(authenticationError, + "an error occurred during authentication; user: \"" + + ci.getUserName() + "\""); + } + } + } + } + if (opened && (user == null || !user.isAdmin())) { + // reset - because the user is not an admin, and has no + // right to listen to exceptions + database.setEventListener(null); + } + } + if (user == null) { + DbException er = DbException.get(ErrorCode.WRONG_USER_OR_PASSWORD); + database.getTrace(Trace.DATABASE).error(er, "wrong user or password; user: \"" + + ci.getUserName() + "\""); + database.removeSession(null); + throw er; + } + //Prevent to set _PASSWORD + ci.cleanAuthenticationInfo(); + checkClustering(ci, database); + SessionLocal session = database.createSession(user, ci.getNetworkConnectionInfo()); + if (session == null) { + // concurrently closing + return null; + } + if (ci.getProperty("OLD_INFORMATION_SCHEMA", false)) { + session.setOldInformationSchema(true); + } + if (ci.getProperty("JMX", false)) { + try { + Utils.callStaticMethod( + "org.h2.jmx.DatabaseInfo.registerMBean", ci, database); + } catch (Exception e) { + database.removeSession(session); + throw DbException.get(ErrorCode.FEATURE_NOT_SUPPORTED_1, e, "JMX"); + } + JMX = true; + } + return session; + } + + private static void throwNotFound(boolean ifExists, boolean forbidCreation, String name) { + if (ifExists) { + throw DbException.get(ErrorCode.DATABASE_NOT_FOUND_WITH_IF_EXISTS_1, name); + } + if (forbidCreation) { + throw DbException.get(ErrorCode.REMOTE_DATABASE_NOT_FOUND_1, name); + } + } + + /** + * Open a database connection with the given connection information. + * + * @param ci the connection information + * @return the session + */ + public static SessionLocal createSession(ConnectionInfo ci) { + try { + SessionLocal session = openSession(ci); + validateUserAndPassword(true); + return session; + } catch (DbException e) { + if (e.getErrorCode() == ErrorCode.WRONG_USER_OR_PASSWORD) { + validateUserAndPassword(false); + } + throw e; + } + } + + private static SessionLocal openSession(ConnectionInfo ci) { + boolean ifExists = ci.removeProperty("IFEXISTS", false); + boolean forbidCreation = ci.removeProperty("FORBID_CREATION", false); + boolean ignoreUnknownSetting = ci.removeProperty( + "IGNORE_UNKNOWN_SETTINGS", false); + String cipher = ci.removeProperty("CIPHER", null); + String init = ci.removeProperty("INIT", null); + SessionLocal session; + long start = System.nanoTime(); + for (;;) { + session = openSession(ci, ifExists, forbidCreation, cipher); + if (session != null) { + break; + } + // we found a database that is currently closing + // wait a bit to avoid a busy loop (the method is synchronized) + if (System.nanoTime() - start > DateTimeUtils.NANOS_PER_MINUTE) { + throw DbException.get(ErrorCode.DATABASE_ALREADY_OPEN_1, + "Waited for database closing longer than 1 minute"); + } + try { + Thread.sleep(1); + } catch (InterruptedException e) { + throw DbException.get(ErrorCode.DATABASE_CALLED_AT_SHUTDOWN); + } + } + synchronized (session) { + session.setAllowLiterals(true); + DbSettings defaultSettings = DbSettings.DEFAULT; + for (String setting : ci.getKeys()) { + if (defaultSettings.containsKey(setting)) { + // database setting are only used when opening the database + continue; + } + String value = ci.getProperty(setting); + StringBuilder builder = new StringBuilder("SET ").append(setting).append(' '); + if (!ParserUtil.isSimpleIdentifier(setting, false, false)) { + if (!setting.equalsIgnoreCase("TIME ZONE")) { + throw DbException.get(ErrorCode.UNSUPPORTED_SETTING_1, setting); + } + StringUtils.quoteStringSQL(builder, value); + } else { + builder.append(value); + } + try { + CommandInterface command = session.prepareLocal(builder.toString()); + command.executeUpdate(null); + } catch (DbException e) { + if (e.getErrorCode() == ErrorCode.ADMIN_RIGHTS_REQUIRED) { + session.getTrace().error(e, "admin rights required; user: \"" + + ci.getUserName() + "\""); + } else { + session.getTrace().error(e, ""); + } + if (!ignoreUnknownSetting) { + session.close(); + throw e; + } + } + } + TimeZoneProvider timeZone = ci.getTimeZone(); + if (timeZone != null) { + session.setTimeZone(timeZone); + } + if (init != null) { + try { + CommandInterface command = session.prepareLocal(init); + command.executeUpdate(null); + } catch (DbException e) { + if (!ignoreUnknownSetting) { + session.close(); + throw e; + } + } + } + session.setAllowLiterals(false); + session.commit(true); + } + return session; + } + + private static void checkClustering(ConnectionInfo ci, Database database) { + String clusterSession = ci.getProperty(SetTypes.CLUSTER, null); + if (Constants.CLUSTERING_DISABLED.equals(clusterSession)) { + // in this case, no checking is made + // (so that a connection can be made to disable/change clustering) + return; + } + String clusterDb = database.getCluster(); + if (!Constants.CLUSTERING_DISABLED.equals(clusterDb)) { + if (!Constants.CLUSTERING_ENABLED.equals(clusterSession)) { + if (!Objects.equals(clusterSession, clusterDb)) { + if (clusterDb.equals(Constants.CLUSTERING_DISABLED)) { + throw DbException.get( + ErrorCode.CLUSTER_ERROR_DATABASE_RUNS_ALONE); + } + throw DbException.get( + ErrorCode.CLUSTER_ERROR_DATABASE_RUNS_CLUSTERED_1, + clusterDb); + } + } + } + } + + /** + * Called after a database has been closed, to remove the object from the + * list of open databases. + * + * @param name the database name + */ + static void close(String name) { + if (JMX) { + try { + Utils.callStaticMethod("org.h2.jmx.DatabaseInfo.unregisterMBean", name); + } catch (Exception e) { + throw DbException.get(ErrorCode.FEATURE_NOT_SUPPORTED_1, e, "JMX"); + } + } + synchronized (DATABASES) { + DATABASES.remove(name); + } + } + + /** + * This method is called after validating user name and password. If user + * name and password were correct, the sleep time is reset, otherwise this + * method waits some time (to make brute force / rainbow table attacks + * harder) and then throws a 'wrong user or password' exception. The delay + * is a bit randomized to protect against timing attacks. Also the delay + * doubles after each unsuccessful logins, to make brute force attacks + * harder. + * + * There is only one exception message both for wrong user and for + * wrong password, to make it harder to get the list of user names. This + * method must only be called from one place, so it is not possible from the + * stack trace to see if the user name was wrong or the password. + * + * @param correct if the user name or the password was correct + * @throws DbException the exception 'wrong user or password' + */ + private static void validateUserAndPassword(boolean correct) { + int min = SysProperties.DELAY_WRONG_PASSWORD_MIN; + if (correct) { + long delay = WRONG_PASSWORD_DELAY; + if (delay > min && delay > 0) { + // the first correct password must be blocked, + // otherwise parallel attacks are possible + synchronized (Engine.class) { + // delay up to the last delay + // an attacker can't know how long it will be + delay = MathUtils.secureRandomInt((int) delay); + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + // ignore + } + WRONG_PASSWORD_DELAY = min; + } + } + } else { + // this method is not synchronized on the Engine, so that + // regular successful attempts are not blocked + synchronized (Engine.class) { + long delay = WRONG_PASSWORD_DELAY; + int max = SysProperties.DELAY_WRONG_PASSWORD_MAX; + if (max <= 0) { + max = Integer.MAX_VALUE; + } + WRONG_PASSWORD_DELAY += WRONG_PASSWORD_DELAY; + if (WRONG_PASSWORD_DELAY > max || WRONG_PASSWORD_DELAY < 0) { + WRONG_PASSWORD_DELAY = max; + } + if (min > 0) { + // a bit more to protect against timing attacks + delay += Math.abs(MathUtils.secureRandomLong() % 100); + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + // ignore + } + } + throw DbException.get(ErrorCode.WRONG_USER_OR_PASSWORD); + } + } + } + + private Engine() { + } + + private static final class DatabaseHolder { + + DatabaseHolder() { + } + + volatile Database database; + } +} diff --git a/h2/src/main/org/h2/engine/GeneratedKeysMode.java b/h2/src/main/org/h2/engine/GeneratedKeysMode.java new file mode 100644 index 0000000..bf5f707 --- /dev/null +++ b/h2/src/main/org/h2/engine/GeneratedKeysMode.java @@ -0,0 +1,65 @@ +/* + * 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.engine; + +import org.h2.message.DbException; + +/** + * Modes of generated keys' gathering. + */ +public final class GeneratedKeysMode { + + /** + * Generated keys are not needed. + */ + public static final int NONE = 0; + + /** + * Generated keys should be configured automatically. + */ + public static final int AUTO = 1; + + /** + * Use specified column indices to return generated keys from. + */ + public static final int COLUMN_NUMBERS = 2; + + /** + * Use specified column names to return generated keys from. + */ + public static final int COLUMN_NAMES = 3; + + /** + * Determines mode of generated keys' gathering. + * + * @param generatedKeysRequest + * {@code null} or {@code false} if generated keys are not + * needed, {@code true} if generated keys should be configured + * automatically, {@code int[]} to specify column indices to + * return generated keys from, or {@code String[]} to specify + * column names to return generated keys from + * @return mode for the specified generated keys request + */ + public static int valueOf(Object generatedKeysRequest) { + if (generatedKeysRequest == null || Boolean.FALSE.equals(generatedKeysRequest)) { + return NONE; + } + if (Boolean.TRUE.equals(generatedKeysRequest)) { + return AUTO; + } + if (generatedKeysRequest instanceof int[]) { + return ((int[]) generatedKeysRequest).length > 0 ? COLUMN_NUMBERS : NONE; + } + if (generatedKeysRequest instanceof String[]) { + return ((String[]) generatedKeysRequest).length > 0 ? COLUMN_NAMES : NONE; + } + throw DbException.getInternalError(); + } + + private GeneratedKeysMode() { + } + +} diff --git a/h2/src/main/org/h2/engine/IsolationLevel.java b/h2/src/main/org/h2/engine/IsolationLevel.java new file mode 100644 index 0000000..26309cb --- /dev/null +++ b/h2/src/main/org/h2/engine/IsolationLevel.java @@ -0,0 +1,162 @@ +/* + * 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.engine; + +import java.sql.Connection; + +import org.h2.message.DbException; + +/** + * Level of isolation. + */ +public enum IsolationLevel { + + /** + * Dirty reads, non-repeatable reads and phantom reads are allowed. + */ + READ_UNCOMMITTED(Connection.TRANSACTION_READ_UNCOMMITTED, Constants.LOCK_MODE_OFF), + + /** + * Dirty reads aren't allowed; non-repeatable reads and phantom reads are + * allowed. + */ + READ_COMMITTED(Connection.TRANSACTION_READ_COMMITTED, Constants.LOCK_MODE_READ_COMMITTED), + + /** + * Dirty reads and non-repeatable reads aren't allowed; phantom reads are + * allowed. + */ + REPEATABLE_READ(Connection.TRANSACTION_REPEATABLE_READ, Constants.LOCK_MODE_TABLE), + + /** + * Dirty reads, non-repeatable reads and phantom reads are'n allowed. + */ + SNAPSHOT(Constants.TRANSACTION_SNAPSHOT, Constants.LOCK_MODE_TABLE), + + /** + * Dirty reads, non-repeatable reads and phantom reads are'n allowed. + * Concurrent and serial execution of transactions with this isolation level + * should have the same effect. + */ + SERIALIZABLE(Connection.TRANSACTION_SERIALIZABLE, Constants.LOCK_MODE_TABLE); + + /** + * Returns the isolation level from LOCK_MODE equivalent for PageStore and + * old versions of H2. + * + * @param level + * the LOCK_MODE value + * @return the isolation level + */ + public static IsolationLevel fromJdbc(int level) { + switch (level) { + case Connection.TRANSACTION_READ_UNCOMMITTED: + return IsolationLevel.READ_UNCOMMITTED; + case Connection.TRANSACTION_READ_COMMITTED: + return IsolationLevel.READ_COMMITTED; + case Connection.TRANSACTION_REPEATABLE_READ: + return IsolationLevel.REPEATABLE_READ; + case Constants.TRANSACTION_SNAPSHOT: + return IsolationLevel.SNAPSHOT; + case Connection.TRANSACTION_SERIALIZABLE: + return IsolationLevel.SERIALIZABLE; + default: + throw DbException.getInvalidValueException("isolation level", level); + } + } + + /** + * Returns the isolation level from LOCK_MODE equivalent for PageStore and + * old versions of H2. + * + * @param lockMode + * the LOCK_MODE value + * @return the isolation level + */ + public static IsolationLevel fromLockMode(int lockMode) { + switch (lockMode) { + case Constants.LOCK_MODE_OFF: + return IsolationLevel.READ_UNCOMMITTED; + case Constants.LOCK_MODE_READ_COMMITTED: + default: + return IsolationLevel.READ_COMMITTED; + case Constants.LOCK_MODE_TABLE: + case Constants.LOCK_MODE_TABLE_GC: + return IsolationLevel.SERIALIZABLE; + } + } + + /** + * Returns the isolation level from its SQL name. + * + * @param sql + * the SQL name + * @return the isolation level from its SQL name + */ + public static IsolationLevel fromSql(String sql) { + switch (sql) { + case "READ UNCOMMITTED": + return READ_UNCOMMITTED; + case "READ COMMITTED": + return READ_COMMITTED; + case "REPEATABLE READ": + return REPEATABLE_READ; + case "SNAPSHOT": + return SNAPSHOT; + case "SERIALIZABLE": + return SERIALIZABLE; + default: + throw DbException.getInvalidValueException("isolation level", sql); + } + } + + private final String sql; + + private final int jdbc, lockMode; + + private IsolationLevel(int jdbc, int lockMode) { + sql = name().replace('_', ' ').intern(); + this.jdbc = jdbc; + this.lockMode = lockMode; + } + + /** + * Returns the SQL representation of this isolation level. + * + * @return SQL representation of this isolation level + */ + public String getSQL() { + return sql; + } + + /** + * Returns the JDBC constant for this isolation level. + * + * @return the JDBC constant for this isolation level + */ + public int getJdbc() { + return jdbc; + } + + /** + * Returns the LOCK_MODE equivalent for PageStore and old versions of H2. + * + * @return the LOCK_MODE equivalent + */ + public int getLockMode() { + return lockMode; + } + + /** + * Returns whether a non-repeatable read phenomena is allowed. + * + * @return whether a non-repeatable read phenomena is allowed + */ + public boolean allowNonRepeatableRead() { + return ordinal() < REPEATABLE_READ.ordinal(); + } + +} diff --git a/h2/src/main/org/h2/engine/MetaRecord.java b/h2/src/main/org/h2/engine/MetaRecord.java new file mode 100644 index 0000000..b0016e4 --- /dev/null +++ b/h2/src/main/org/h2/engine/MetaRecord.java @@ -0,0 +1,208 @@ +/* + * 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.engine; + +import java.sql.SQLException; +import java.util.Comparator; +import org.h2.api.DatabaseEventListener; +import org.h2.command.CommandInterface; +import org.h2.command.Prepared; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.result.SearchRow; +import org.h2.value.ValueInteger; +import org.h2.value.ValueVarchar; + +/** + * A record in the system table of the database. + * It contains the SQL statement to create the database object. + */ +public class MetaRecord implements Comparable { + + /** + * Comparator for prepared constraints, sorts unique and primary key + * constraints first. + */ + static final Comparator CONSTRAINTS_COMPARATOR = (o1, o2) -> { + int t1 = o1.getType(), t2 = o2.getType(); + boolean u1 = t1 == CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_PRIMARY_KEY + || t1 == CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_UNIQUE; + boolean u2 = t2 == CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_PRIMARY_KEY + || t2 == CommandInterface.ALTER_TABLE_ADD_CONSTRAINT_UNIQUE; + if (u1 == u2) { + return o1.getPersistedObjectId() - o2.getPersistedObjectId(); + } + return u1 ? -1 : 1; + }; + + private final int id; + private final int objectType; + private final String sql; + + /** + * Copy metadata from the specified object into specified search row. + * + * @param obj + * database object + * @param r + * search row + */ + public static void populateRowFromDBObject(DbObject obj, SearchRow r) { + r.setValue(0, ValueInteger.get(obj.getId())); + r.setValue(1, ValueInteger.get(0)); + r.setValue(2, ValueInteger.get(obj.getType())); + r.setValue(3, ValueVarchar.get(obj.getCreateSQLForMeta())); + } + + public MetaRecord(SearchRow r) { + id = r.getValue(0).getInt(); + objectType = r.getValue(2).getInt(); + sql = r.getValue(3).getString(); + } + + /** + * Execute the meta data statement. + * + * @param db the database + * @param systemSession the system session + * @param listener the database event listener + */ + void prepareAndExecute(Database db, SessionLocal systemSession, DatabaseEventListener listener) { + try { + Prepared command = systemSession.prepare(sql); + command.setPersistedObjectId(id); + command.update(); + } catch (DbException e) { + throwException(db, listener, e, sql); + } + } + + /** + * Prepares the meta data statement. + * + * @param db the database + * @param systemSession the system session + * @param listener the database event listener + * @return the prepared command + */ + Prepared prepare(Database db, SessionLocal systemSession, DatabaseEventListener listener) { + try { + Prepared command = systemSession.prepare(sql); + command.setPersistedObjectId(id); + return command; + } catch (DbException e) { + throwException(db, listener, e, sql); + return null; + } + } + + /** + * Execute the meta data statement. + * + * @param db the database + * @param command the prepared command + * @param listener the database event listener + * @param sql SQL + */ + static void execute(Database db, Prepared command, DatabaseEventListener listener, String sql) { + try { + command.update(); + } catch (DbException e) { + throwException(db, listener, e, sql); + } + } + + private static void throwException(Database db, DatabaseEventListener listener, DbException e, String sql) { + e = e.addSQL(sql); + SQLException s = e.getSQLException(); + db.getTrace(Trace.DATABASE).error(s, sql); + if (listener != null) { + listener.exceptionThrown(s, sql); + // continue startup in this case + } else { + throw e; + } + } + + public int getId() { + return id; + } + + public int getObjectType() { + return objectType; + } + + public String getSQL() { + return sql; + } + + /** + * Sort the list of meta records by 'create order'. + * + * @param other the other record + * @return -1, 0, or 1 + */ + @Override + public int compareTo(MetaRecord other) { + int c1 = getCreateOrder(); + int c2 = other.getCreateOrder(); + if (c1 != c2) { + return c1 - c2; + } + return getId() - other.getId(); + } + + /** + * Get the sort order id for this object type. Objects are created in this + * order when opening a database. + * + * @return the sort index + */ + private int getCreateOrder() { + switch (objectType) { + case DbObject.SETTING: + return 0; + case DbObject.USER: + return 1; + case DbObject.SCHEMA: + return 2; + case DbObject.FUNCTION_ALIAS: + return 3; + case DbObject.DOMAIN: + return 4; + case DbObject.SEQUENCE: + return 5; + case DbObject.CONSTANT: + return 6; + case DbObject.TABLE_OR_VIEW: + return 7; + case DbObject.INDEX: + return 8; + case DbObject.CONSTRAINT: + return 9; + case DbObject.TRIGGER: + return 10; + case DbObject.SYNONYM: + return 11; + case DbObject.ROLE: + return 12; + case DbObject.RIGHT: + return 13; + case DbObject.AGGREGATE: + return 14; + case DbObject.COMMENT: + return 15; + default: + throw DbException.getInternalError("type=" + objectType); + } + } + + @Override + public String toString() { + return "MetaRecord [id=" + id + ", objectType=" + objectType + ", sql=" + sql + ']'; + } + +} diff --git a/h2/src/main/org/h2/engine/Mode.java b/h2/src/main/org/h2/engine/Mode.java new file mode 100644 index 0000000..26f875b --- /dev/null +++ b/h2/src/main/org/h2/engine/Mode.java @@ -0,0 +1,732 @@ +/* + * 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.engine; + +import java.sql.Types; +import java.util.Collections; +import java.util.HashMap; +import java.util.Set; +import java.util.regex.Pattern; + +import org.h2.util.StringUtils; +import org.h2.value.DataType; +import org.h2.value.Value; + +/** + * The compatibility modes. There is a fixed set of modes (for example + * PostgreSQL, MySQL). Each mode has different settings. + */ +public class Mode { + + public enum ModeEnum { + REGULAR, STRICT, LEGACY, DB2, Derby, MariaDB, MSSQLServer, HSQLDB, MySQL, Oracle, PostgreSQL + } + + /** + * Determines how rows with {@code NULL} values in indexed columns are handled + * in unique indexes. + */ + public enum UniqueIndexNullsHandling { + /** + * Multiple rows with identical values in indexed columns with at least one + * indexed {@code NULL} value are allowed in unique index. + */ + ALLOW_DUPLICATES_WITH_ANY_NULL, + + /** + * Multiple rows with identical values in indexed columns with all indexed + * {@code NULL} values are allowed in unique index. + */ + ALLOW_DUPLICATES_WITH_ALL_NULLS, + + /** + * Multiple rows with identical values in indexed columns are not allowed in + * unique index. + */ + FORBID_ANY_DUPLICATES + } + + /** + * Generation of column names for expressions. + */ + public enum ExpressionNames { + /** + * Use optimized SQL representation of expression. + */ + OPTIMIZED_SQL, + + /** + * Use original SQL representation of expression. + */ + ORIGINAL_SQL, + + /** + * Generate empty name. + */ + EMPTY, + + /** + * Use ordinal number of a column. + */ + NUMBER, + + /** + * Use ordinal number of a column with C prefix. + */ + C_NUMBER, + + /** + * Use function name for functions and ?column? for other expressions + */ + POSTGRESQL_STYLE, + } + + /** + * Generation of column names for expressions to be used in a view. + */ + public enum ViewExpressionNames { + /** + * Use both specified and generated names as is. + */ + AS_IS, + + /** + * Throw exception for unspecified names. + */ + EXCEPTION, + + /** + * Use both specified and generated names as is, but replace too long + * generated names with {@code Name_exp_###}. + */ + MYSQL_STYLE, + } + + /** + * When CHAR values are right-padded with spaces. + */ + public enum CharPadding { + /** + * CHAR values are always right-padded with spaces. + */ + ALWAYS, + + /** + * 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 + */ + IN_RESULT_SETS, + + /** + * Spaces are trimmed from the right side of CHAR values. + */ + NEVER + } + + private static final HashMap MODES = new HashMap<>(); + + // Modes are also documented in the features section + + /** + * When enabled, aliased columns (as in SELECT ID AS I FROM TEST) return the + * alias (I in this case) in ResultSetMetaData.getColumnName() and 'null' in + * getTableName(). If disabled, the real column name (ID in this case) and + * table name is returned. + */ + public boolean aliasColumnName; + + /** + * When converting the scale of decimal data, the number is only converted + * if the new scale is smaller than the current scale. Usually, the scale is + * converted and 0s are added if required. + */ + public boolean convertOnlyToSmallerScale; + + /** + * Creating indexes in the CREATE TABLE statement is allowed using + * INDEX(..) or KEY(..). + * Example: create table test(id int primary key, name varchar(255), + * key idx_name(name)); + */ + public boolean indexDefinitionInCreateTable; + + /** + * Identifiers may be quoted using square brackets as in [Test]. + */ + public boolean squareBracketQuotedNames; + + /** + * The system columns 'ctid' and 'oid' are supported. + */ + public boolean systemColumns; + + /** + * Determines how rows with {@code NULL} values in indexed columns are handled + * in unique indexes. + */ + public UniqueIndexNullsHandling uniqueIndexNullsHandling = UniqueIndexNullsHandling.ALLOW_DUPLICATES_WITH_ANY_NULL; + + /** + * Empty strings are treated like NULL values. Useful for Oracle emulation. + */ + public boolean treatEmptyStringsAsNull; + + /** + * Support the pseudo-table SYSIBM.SYSDUMMY1. + */ + public boolean sysDummy1; + + /** + * Text can be concatenated using '+'. + */ + public boolean allowPlusForStringConcat; + + /** + * The single-argument function LOG() uses base 10 instead of E. + */ + public boolean logIsLogBase10; + + /** + * Swap the parameters of LOG() function. + */ + public boolean swapLogFunctionParameters; + + /** + * The function REGEXP_REPLACE() uses \ for back-references. + */ + public boolean regexpReplaceBackslashReferences; + + /** + * Swap the parameters of the CONVERT function. + */ + public boolean swapConvertFunctionParameters; + + /** + * can set the isolation level using WITH {RR|RS|CS|UR} + */ + public boolean isolationLevelInSelectOrInsertStatement; + + /** + * MySQL style INSERT ... ON DUPLICATE KEY UPDATE ... and INSERT IGNORE. + */ + public boolean onDuplicateKeyUpdate; + + /** + * MySQL style REPLACE INTO. + */ + public boolean replaceInto; + + /** + * PostgreSQL style INSERT ... ON CONFLICT DO NOTHING. + */ + public boolean insertOnConflict; + + /** + * Pattern describing the keys the java.sql.Connection.setClientInfo() + * method accepts. + */ + public Pattern supportedClientInfoPropertiesRegEx; + + /** + * Support the # for column names + */ + public boolean supportPoundSymbolForColumnNames; + + /** + * Whether IN predicate may have an empty value list. + */ + public boolean allowEmptyInPredicate; + + /** + * How to pad or trim CHAR values. + */ + public CharPadding charPadding = CharPadding.ALWAYS; + + /** + * Whether DB2 TIMESTAMP formats are allowed. + */ + public boolean allowDB2TimestampFormat; + + /** + * Discard SQLServer table hints (e.g. "SELECT * FROM table WITH (NOLOCK)") + */ + public boolean discardWithTableHints; + + /** + * If {@code true}, datetime value function return the same value within a + * transaction, if {@code false} datetime value functions return the same + * value within a command. + */ + public boolean dateTimeValueWithinTransaction; + + /** + * If {@code true} {@code 0x}-prefixed numbers are parsed as binary string + * literals, if {@code false} they are parsed as hexadecimal numeric values. + */ + public boolean zeroExLiteralsAreBinaryStrings; + + /** + * If {@code true} unrelated ORDER BY expression are allowed in DISTINCT + * queries, if {@code false} they are disallowed. + */ + public boolean allowUnrelatedOrderByExpressionsInDistinctQueries; + + /** + * If {@code true} some additional non-standard ALTER TABLE commands are allowed. + */ + public boolean alterTableExtensionsMySQL; + + /** + * If {@code true} non-standard ALTER TABLE MODIFY COLUMN is allowed. + */ + public boolean alterTableModifyColumn; + + /** + * If {@code true} TRUNCATE TABLE uses RESTART IDENTITY by default. + */ + public boolean truncateTableRestartIdentity; + + /** + * If {@code true} NEXT VALUE FOR SEQUENCE, CURRENT VALUE FOR SEQUENCE, + * SEQUENCE.NEXTVAL, and SEQUENCE.CURRVAL return values with DECIMAL/NUMERIC + * data type instead of BIGINT. + */ + public boolean decimalSequences; + + /** + * If {@code true} constructs like 'CREATE TABLE CATALOG..TABLE_NAME' are allowed, + * the default schema is used. + */ + public boolean allowEmptySchemaValuesAsDefaultSchema; + + /** + * If {@code true} all numeric data types may have precision and 'UNSIGNED' + * clause. + */ + public boolean allNumericTypesHavePrecision; + + /** + * If {@code true} 'FOR BIT DATA' clauses are allowed for character string + * data types. + */ + public boolean forBitData; + + /** + * If {@code true} 'CHAR' and 'BYTE' length units are allowed. + */ + public boolean charAndByteLengthUnits; + + /** + * If {@code true}, sequence.NEXTVAL and sequence.CURRVAL pseudo columns are + * supported. + */ + public boolean nextvalAndCurrvalPseudoColumns; + + /** + * If {@code true}, the next value expression returns different values when + * invoked multiple times within a row. This setting does not affect + * NEXTVAL() function. + */ + public boolean nextValueReturnsDifferentValues; + + /** + * If {@code true}, sequences of generated by default identity columns are + * updated when value is provided by user. + */ + public boolean updateSequenceOnManualIdentityInsertion; + + /** + * If {@code true}, last identity of the session is updated on insertion of + * a new value into identity column. + */ + public boolean takeInsertedIdentity; + + /** + * If {@code true}, last identity of the session is updated on generation of + * a new sequence value. + */ + public boolean takeGeneratedSequenceValue; + + /** + * If {@code true}, identity columns have DEFAULT ON NULL clause. + */ + public boolean identityColumnsHaveDefaultOnNull; + + /** + * If {@code true}, merge when matched clause may have WHERE clause. + */ + public boolean mergeWhere; + + /** + * If {@code true}, allow using from clause in update statement. + */ + public boolean allowUsingFromClauseInUpdateStatement; + + /** + * If {@code true}, referential constraints will create a unique constraint + * on referenced columns if it doesn't exist instead of throwing an + * exception. + */ + public boolean createUniqueConstraintForReferencedColumns; + + /** + * How column names are generated for expressions. + */ + public ExpressionNames expressionNames = ExpressionNames.OPTIMIZED_SQL; + + /** + * How column names are generated for views. + */ + public ViewExpressionNames viewExpressionNames = ViewExpressionNames.AS_IS; + + /** + * Whether TOP clause in SELECT queries is supported. + */ + public boolean topInSelect; + + /** + * Whether TOP clause in DML commands is supported. + */ + public boolean topInDML; + + /** + * Whether LIMIT / OFFSET clauses are supported. + */ + public boolean limit; + + /** + * Whether MINUS can be used as EXCEPT. + */ + public boolean minusIsExcept; + + /** + * Whether IDENTITY pseudo data type is supported. + */ + public boolean identityDataType; + + /** + * Whether SERIAL and BIGSERIAL pseudo data types are supported. + */ + public boolean serialDataTypes; + + /** + * Whether SQL Server-style IDENTITY clause is supported. + */ + public boolean identityClause; + + /** + * Whether MySQL-style AUTO_INCREMENT clause is supported. + */ + public boolean autoIncrementClause; + + /** + * An optional Set of hidden/disallowed column types. + * Certain DBMSs don't support all column types provided by H2, such as + * "NUMBER" when using PostgreSQL mode. + */ + public Set disallowedTypes = Collections.emptySet(); + + /** + * Custom mappings from type names to data types. + */ + public HashMap typeByNameMap = new HashMap<>(); + + /** + * Allow to use GROUP BY n, where n is column index in the SELECT list, similar to ORDER BY + */ + public boolean groupByColumnIndex; + + /** + * Allow to compare numeric with BOOLEAN. + */ + public boolean numericWithBooleanComparison; + + private final String name; + + private final ModeEnum modeEnum; + + static { + Mode mode = new Mode(ModeEnum.REGULAR); + mode.allowEmptyInPredicate = true; + mode.dateTimeValueWithinTransaction = true; + mode.topInSelect = true; + mode.limit = true; + mode.minusIsExcept = true; + mode.identityDataType = true; + mode.serialDataTypes = true; + mode.autoIncrementClause = true; + add(mode); + + mode = new Mode(ModeEnum.STRICT); + mode.dateTimeValueWithinTransaction = true; + add(mode); + + mode = new Mode(ModeEnum.LEGACY); + // Features of REGULAR mode + mode.allowEmptyInPredicate = true; + mode.dateTimeValueWithinTransaction = true; + mode.topInSelect = true; + mode.limit = true; + mode.minusIsExcept = true; + mode.identityDataType = true; + mode.serialDataTypes = true; + mode.autoIncrementClause = true; + // Legacy identity and sequence features + mode.identityClause = true; + mode.updateSequenceOnManualIdentityInsertion = true; + mode.takeInsertedIdentity = true; + mode.identityColumnsHaveDefaultOnNull = true; + mode.nextvalAndCurrvalPseudoColumns = true; + // Legacy DML features + mode.topInDML = true; + mode.mergeWhere = true; + // Legacy DDL features + mode.createUniqueConstraintForReferencedColumns = true; + // Legacy numeric with boolean comparison + mode.numericWithBooleanComparison = true; + add(mode); + + mode = new Mode(ModeEnum.DB2); + mode.aliasColumnName = true; + mode.sysDummy1 = true; + mode.isolationLevelInSelectOrInsertStatement = true; + // See + // https://www.ibm.com/support/knowledgecenter/SSEPEK_11.0.0/ + // com.ibm.db2z11.doc.java/src/tpc/imjcc_r0052001.dita + mode.supportedClientInfoPropertiesRegEx = + Pattern.compile("ApplicationName|ClientAccountingInformation|" + + "ClientUser|ClientCorrelationToken"); + mode.allowDB2TimestampFormat = true; + mode.forBitData = true; + mode.takeInsertedIdentity = true; + mode.expressionNames = ExpressionNames.NUMBER; + mode.viewExpressionNames = ViewExpressionNames.EXCEPTION; + mode.limit = true; + mode.minusIsExcept = true; + mode.numericWithBooleanComparison = true; + add(mode); + + mode = new Mode(ModeEnum.Derby); + mode.aliasColumnName = true; + mode.uniqueIndexNullsHandling = UniqueIndexNullsHandling.FORBID_ANY_DUPLICATES; + mode.sysDummy1 = true; + mode.isolationLevelInSelectOrInsertStatement = true; + // Derby does not support client info properties as of version 10.12.1.1 + mode.supportedClientInfoPropertiesRegEx = null; + mode.forBitData = true; + mode.takeInsertedIdentity = true; + mode.expressionNames = ExpressionNames.NUMBER; + mode.viewExpressionNames = ViewExpressionNames.EXCEPTION; + add(mode); + + mode = new Mode(ModeEnum.HSQLDB); + mode.allowPlusForStringConcat = true; + mode.identityColumnsHaveDefaultOnNull = true; + // HSQLDB does not support client info properties. See + // http://hsqldb.org/doc/apidocs/org/hsqldb/jdbc/JDBCConnection.html#setClientInfo-java.lang.String-java.lang.String- + mode.supportedClientInfoPropertiesRegEx = null; + mode.expressionNames = ExpressionNames.C_NUMBER; + mode.topInSelect = true; + mode.limit = true; + mode.minusIsExcept = true; + mode.numericWithBooleanComparison = true; + add(mode); + + mode = new Mode(ModeEnum.MSSQLServer); + mode.aliasColumnName = true; + mode.squareBracketQuotedNames = true; + mode.uniqueIndexNullsHandling = UniqueIndexNullsHandling.FORBID_ANY_DUPLICATES; + mode.allowPlusForStringConcat = true; + mode.swapLogFunctionParameters = true; + mode.swapConvertFunctionParameters = true; + mode.supportPoundSymbolForColumnNames = true; + mode.discardWithTableHints = true; + // MS SQL Server does not support client info properties. See + // https://msdn.microsoft.com/en-Us/library/dd571296%28v=sql.110%29.aspx + mode.supportedClientInfoPropertiesRegEx = null; + mode.zeroExLiteralsAreBinaryStrings = true; + mode.truncateTableRestartIdentity = true; + mode.takeInsertedIdentity = true; + DataType dt = DataType.createNumeric(19, 4); + dt.type = Value.NUMERIC; + dt.sqlType = Types.NUMERIC; + dt.specialPrecisionScale = true; + mode.typeByNameMap.put("MONEY", dt); + dt = DataType.createNumeric(10, 4); + dt.type = Value.NUMERIC; + dt.sqlType = Types.NUMERIC; + dt.specialPrecisionScale = true; + mode.typeByNameMap.put("SMALLMONEY", dt); + mode.typeByNameMap.put("UNIQUEIDENTIFIER", DataType.getDataType(Value.UUID)); + mode.allowEmptySchemaValuesAsDefaultSchema = true; + mode.expressionNames = ExpressionNames.EMPTY; + mode.viewExpressionNames = ViewExpressionNames.EXCEPTION; + mode.topInSelect = true; + mode.topInDML = true; + mode.identityClause = true; + mode.numericWithBooleanComparison = true; + add(mode); + + mode = new Mode(ModeEnum.MariaDB); + mode.indexDefinitionInCreateTable = true; + mode.regexpReplaceBackslashReferences = true; + mode.onDuplicateKeyUpdate = true; + mode.replaceInto = true; + mode.charPadding = CharPadding.NEVER; + mode.supportedClientInfoPropertiesRegEx = Pattern.compile(".*"); + mode.zeroExLiteralsAreBinaryStrings = true; + mode.allowUnrelatedOrderByExpressionsInDistinctQueries = true; + mode.alterTableExtensionsMySQL = true; + mode.alterTableModifyColumn = true; + mode.truncateTableRestartIdentity = true; + mode.allNumericTypesHavePrecision = true; + mode.nextValueReturnsDifferentValues = true; + mode.updateSequenceOnManualIdentityInsertion = true; + mode.takeInsertedIdentity = true; + mode.identityColumnsHaveDefaultOnNull = true; + mode.expressionNames = ExpressionNames.ORIGINAL_SQL; + mode.viewExpressionNames = ViewExpressionNames.MYSQL_STYLE; + mode.limit = true; + mode.autoIncrementClause = true; + mode.typeByNameMap.put("YEAR", DataType.getDataType(Value.SMALLINT)); + mode.groupByColumnIndex = true; + mode.numericWithBooleanComparison = true; + add(mode); + + mode = new Mode(ModeEnum.MySQL); + mode.indexDefinitionInCreateTable = true; + mode.regexpReplaceBackslashReferences = true; + mode.onDuplicateKeyUpdate = true; + mode.replaceInto = true; + mode.charPadding = CharPadding.NEVER; + // MySQL allows to use any key for client info entries. See + // https://github.com/mysql/mysql-connector-j/blob/5.1.47/src/com/mysql/jdbc/JDBC4CommentClientInfoProvider.java + mode.supportedClientInfoPropertiesRegEx = + Pattern.compile(".*"); + mode.zeroExLiteralsAreBinaryStrings = true; + mode.allowUnrelatedOrderByExpressionsInDistinctQueries = true; + mode.alterTableExtensionsMySQL = true; + mode.alterTableModifyColumn = true; + mode.truncateTableRestartIdentity = true; + mode.allNumericTypesHavePrecision = true; + mode.updateSequenceOnManualIdentityInsertion = true; + mode.takeInsertedIdentity = true; + mode.identityColumnsHaveDefaultOnNull = true; + mode.createUniqueConstraintForReferencedColumns = true; + mode.expressionNames = ExpressionNames.ORIGINAL_SQL; + mode.viewExpressionNames = ViewExpressionNames.MYSQL_STYLE; + mode.limit = true; + mode.autoIncrementClause = true; + mode.typeByNameMap.put("YEAR", DataType.getDataType(Value.SMALLINT)); + mode.groupByColumnIndex = true; + mode.numericWithBooleanComparison = true; + add(mode); + + mode = new Mode(ModeEnum.Oracle); + mode.aliasColumnName = true; + mode.convertOnlyToSmallerScale = true; + mode.uniqueIndexNullsHandling = UniqueIndexNullsHandling.ALLOW_DUPLICATES_WITH_ALL_NULLS; + mode.treatEmptyStringsAsNull = true; + mode.regexpReplaceBackslashReferences = true; + mode.supportPoundSymbolForColumnNames = true; + // Oracle accepts keys of the form .*. See + // https://docs.oracle.com/database/121/JJDBC/jdbcvers.htm#JJDBC29006 + mode.supportedClientInfoPropertiesRegEx = + Pattern.compile(".*\\..*"); + mode.alterTableModifyColumn = true; + mode.decimalSequences = true; + mode.charAndByteLengthUnits = true; + mode.nextvalAndCurrvalPseudoColumns = true; + mode.mergeWhere = true; + mode.minusIsExcept = true; + mode.expressionNames = ExpressionNames.ORIGINAL_SQL; + mode.viewExpressionNames = ViewExpressionNames.EXCEPTION; + 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); + mode.aliasColumnName = true; + mode.systemColumns = true; + mode.logIsLogBase10 = true; + mode.regexpReplaceBackslashReferences = true; + mode.insertOnConflict = true; + // PostgreSQL only supports the ApplicationName property. See + // https://github.com/hhru/postgres-jdbc/blob/master/postgresql-jdbc-9.2-1002.src/ + // org/postgresql/jdbc4/AbstractJdbc4Connection.java + mode.supportedClientInfoPropertiesRegEx = + Pattern.compile("ApplicationName"); + mode.charPadding = CharPadding.IN_RESULT_SETS; + mode.nextValueReturnsDifferentValues = true; + mode.takeGeneratedSequenceValue = true; + mode.expressionNames = ExpressionNames.POSTGRESQL_STYLE; + mode.allowUsingFromClauseInUpdateStatement = true; + mode.limit = true; + mode.serialDataTypes = true; + // Enumerate all H2 types NOT supported by PostgreSQL: + Set disallowedTypes = new java.util.HashSet<>(); + disallowedTypes.add("NUMBER"); + disallowedTypes.add("TINYINT"); + disallowedTypes.add("BLOB"); + disallowedTypes.add("VARCHAR_IGNORECASE"); + mode.disallowedTypes = disallowedTypes; + dt = DataType.getDataType(Value.JSON); + mode.typeByNameMap.put("JSONB", dt); + dt = DataType.createNumeric(19, 2); + dt.type = Value.NUMERIC; + dt.sqlType = Types.NUMERIC; + dt.specialPrecisionScale = true; + mode.typeByNameMap.put("MONEY", dt); + dt = DataType.getDataType(Value.INTEGER); + mode.typeByNameMap.put("OID", dt); + mode.dateTimeValueWithinTransaction = true; + mode.groupByColumnIndex = true; + add(mode); + } + + private Mode(ModeEnum modeEnum) { + this.name = modeEnum.name(); + this.modeEnum = modeEnum; + } + + private static void add(Mode mode) { + MODES.put(StringUtils.toUpperEnglish(mode.name), mode); + } + + /** + * Get the mode with the given name. + * + * @param name the name of the mode + * @return the mode object + */ + public static Mode getInstance(String name) { + return MODES.get(StringUtils.toUpperEnglish(name)); + } + + public static Mode getRegular() { + return getInstance(ModeEnum.REGULAR.name()); + } + + public String getName() { + return name; + } + + public ModeEnum getEnum() { + return this.modeEnum; + } + + @Override + public String toString() { + return name; + } + +} diff --git a/h2/src/main/org/h2/engine/OnExitDatabaseCloser.java b/h2/src/main/org/h2/engine/OnExitDatabaseCloser.java new file mode 100644 index 0000000..d8022ac --- /dev/null +++ b/h2/src/main/org/h2/engine/OnExitDatabaseCloser.java @@ -0,0 +1,117 @@ +/* + * 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.engine; + +import java.util.WeakHashMap; + +import org.h2.message.Trace; + +/** + * This class is responsible to close a database on JVM shutdown. + */ +class OnExitDatabaseCloser extends Thread { + + private static final WeakHashMap DATABASES = new WeakHashMap<>(); + + private static final Thread INSTANCE = new OnExitDatabaseCloser(); + + private static boolean registered; + + private static boolean terminated; + + /** + * Register database instance to close one on the JVM process shutdown. + * + * @param db Database instance. + */ + static synchronized void register(Database db) { + if (terminated) { + // Shutdown in progress + return; + } + DATABASES.put(db, null); + if (!registered) { + // Mark as registered unconditionally to avoid further attempts to register a + // shutdown hook in case of exception. + registered = true; + try { + Runtime.getRuntime().addShutdownHook(INSTANCE); + } catch (IllegalStateException e) { + // shutdown in progress - just don't register the handler + // (maybe an application wants to write something into a + // database at shutdown time) + } catch (SecurityException e) { + // applets may not do that - ignore + // Google App Engine doesn't allow + // to instantiate classes that extend Thread + } + } + } + + /** + * Unregister database instance. + * + * @param db Database instance. + */ + static synchronized void unregister(Database db) { + if (terminated) { + // Shutdown in progress, do nothing + // This method can be called from the onShutdown() + return; + } + DATABASES.remove(db); + if (DATABASES.isEmpty() && registered) { + try { + Runtime.getRuntime().removeShutdownHook(INSTANCE); + } catch (IllegalStateException e) { + // ignore + } catch (SecurityException e) { + // applets may not do that - ignore + } + registered = false; + } + } + + private static void onShutdown() { + synchronized(OnExitDatabaseCloser.class) { + terminated = true; + } + RuntimeException root = null; + for (Database database : DATABASES.keySet()) { + try { + database.close(true); + } catch (RuntimeException e) { + // this can happen when stopping a web application, + // if loading classes is no longer allowed + // it would throw an IllegalStateException + try { + database.getTrace(Trace.DATABASE).error(e, "could not close the database"); + // if this was successful, we ignore the exception + // otherwise not + } catch (Throwable e2) { + e.addSuppressed(e2); + if (root == null) { + root = e; + } else { + root.addSuppressed(e); + } + } + } + } + if (root != null) { + throw root; + } + } + + private OnExitDatabaseCloser() { + } + + @Override + public void run() { + onShutdown(); + } + +} diff --git a/h2/src/main/org/h2/engine/Procedure.java b/h2/src/main/org/h2/engine/Procedure.java new file mode 100644 index 0000000..899309b --- /dev/null +++ b/h2/src/main/org/h2/engine/Procedure.java @@ -0,0 +1,32 @@ +/* + * 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.engine; + +import org.h2.command.Prepared; + +/** + * Represents a procedure. Procedures are implemented for PostgreSQL + * compatibility. + */ +public class Procedure { + + private final String name; + private final Prepared prepared; + + public Procedure(String name, Prepared prepared) { + this.name = name; + this.prepared = prepared; + } + + public String getName() { + return name; + } + + public Prepared getPrepared() { + return prepared; + } + +} diff --git a/h2/src/main/org/h2/engine/QueryStatisticsData.java b/h2/src/main/org/h2/engine/QueryStatisticsData.java new file mode 100644 index 0000000..9d805e8 --- /dev/null +++ b/h2/src/main/org/h2/engine/QueryStatisticsData.java @@ -0,0 +1,192 @@ +/* + * 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.engine; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; + +/** + * Maintains query statistics. + */ +public class QueryStatisticsData { + + private static final Comparator QUERY_ENTRY_COMPARATOR = + Comparator.comparingLong(q -> q.lastUpdateTime); + + private final HashMap map = new HashMap<>(); + + private int maxQueryEntries; + + public QueryStatisticsData(int maxQueryEntries) { + this.maxQueryEntries = maxQueryEntries; + } + + public synchronized void setMaxQueryEntries(int maxQueryEntries) { + this.maxQueryEntries = maxQueryEntries; + } + + public synchronized List getQueries() { + // return a copy of the map so we don't have to + // worry about external synchronization + ArrayList list = new ArrayList<>(map.values()); + // only return the newest 100 entries + list.sort(QUERY_ENTRY_COMPARATOR); + return list.subList(0, Math.min(list.size(), maxQueryEntries)); + } + + /** + * Update query statistics. + * + * @param sqlStatement the statement being executed + * @param executionTimeNanos the time in nanoseconds the query/update took + * to execute + * @param rowCount the query or update row count + */ + public synchronized void update(String sqlStatement, long executionTimeNanos, long rowCount) { + QueryEntry entry = map.get(sqlStatement); + if (entry == null) { + entry = new QueryEntry(sqlStatement); + map.put(sqlStatement, entry); + } + entry.update(executionTimeNanos, rowCount); + + // Age-out the oldest entries if the map gets too big. + // Test against 1.5 x max-size so we don't do this too often + if (map.size() > maxQueryEntries * 1.5f) { + // Sort the entries by age + ArrayList list = new ArrayList<>(map.values()); + list.sort(QUERY_ENTRY_COMPARATOR); + // Create a set of the oldest 1/3 of the entries + HashSet oldestSet = + new HashSet<>(list.subList(0, list.size() / 3)); + // Loop over the map using the set and remove + // the oldest 1/3 of the entries. + for (Iterator> it = + map.entrySet().iterator(); it.hasNext();) { + Entry mapEntry = it.next(); + if (oldestSet.contains(mapEntry.getValue())) { + it.remove(); + } + } + } + } + + /** + * The collected statistics for one query. + */ + public static final class QueryEntry { + + /** + * The SQL statement. + */ + public final String sqlStatement; + + /** + * The number of times the statement was executed. + */ + public int count; + + /** + * The last time the statistics for this entry were updated, + * in milliseconds since 1970. + */ + public long lastUpdateTime; + + /** + * The minimum execution time, in nanoseconds. + */ + public long executionTimeMinNanos; + + /** + * The maximum execution time, in nanoseconds. + */ + public long executionTimeMaxNanos; + + /** + * The total execution time. + */ + public long executionTimeCumulativeNanos; + + /** + * The minimum number of rows. + */ + public long rowCountMin; + + /** + * The maximum number of rows. + */ + public long rowCountMax; + + /** + * The total number of rows. + */ + public long rowCountCumulative; + + /** + * The mean execution time. + */ + public double executionTimeMeanNanos; + + /** + * The mean number of rows. + */ + public double rowCountMean; + + // Using Welford's method, see also + // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance + // https://www.johndcook.com/blog/standard_deviation/ + + private double executionTimeM2Nanos; + private double rowCountM2; + + public QueryEntry(String sql) { + this.sqlStatement = sql; + } + + /** + * Update the statistics entry. + * + * @param timeNanos the execution time in nanos + * @param rows the number of rows + */ + void update(long timeNanos, long rows) { + count++; + executionTimeMinNanos = Math.min(timeNanos, executionTimeMinNanos); + executionTimeMaxNanos = Math.max(timeNanos, executionTimeMaxNanos); + rowCountMin = Math.min(rows, rowCountMin); + rowCountMax = Math.max(rows, rowCountMax); + + double rowDelta = rows - rowCountMean; + rowCountMean += rowDelta / count; + rowCountM2 += rowDelta * (rows - rowCountMean); + + double timeDelta = timeNanos - executionTimeMeanNanos; + executionTimeMeanNanos += timeDelta / count; + executionTimeM2Nanos += timeDelta * (timeNanos - executionTimeMeanNanos); + + executionTimeCumulativeNanos += timeNanos; + rowCountCumulative += rows; + lastUpdateTime = System.currentTimeMillis(); + } + + public double getExecutionTimeStandardDeviation() { + // population standard deviation + return Math.sqrt(executionTimeM2Nanos / count); + } + + public double getRowCountStandardDeviation() { + // population standard deviation + return Math.sqrt(rowCountM2 / count); + } + + } + +} diff --git a/h2/src/main/org/h2/engine/Right.java b/h2/src/main/org/h2/engine/Right.java new file mode 100644 index 0000000..3f171b7 --- /dev/null +++ b/h2/src/main/org/h2/engine/Right.java @@ -0,0 +1,191 @@ +/* + * 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.engine; + +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.schema.Schema; +import org.h2.table.Table; + +/** + * An access right. Rights are regular database objects, but have generated + * names. + */ +public final class Right extends DbObject { + + /** + * The right bit mask that means: selecting from a table is allowed. + */ + public static final int SELECT = 1; + + /** + * The right bit mask that means: deleting rows from a table is allowed. + */ + public static final int DELETE = 2; + + /** + * The right bit mask that means: inserting rows into a table is allowed. + */ + public static final int INSERT = 4; + + /** + * The right bit mask that means: updating data is allowed. + */ + public static final int UPDATE = 8; + + /** + * The right bit mask that means: create/alter/drop schema is allowed. + */ + public static final int ALTER_ANY_SCHEMA = 16; + + /** + * The right bit mask that means: user is a schema owner. This mask isn't + * used in GRANT / REVOKE statements. + */ + public static final int SCHEMA_OWNER = 32; + + /** + * The right bit mask that means: select, insert, update, delete, and update + * for this object is allowed. + */ + public static final int ALL = SELECT | DELETE | INSERT | UPDATE; + + /** + * To whom the right is granted. + */ + private RightOwner grantee; + + /** + * The granted role, or null if a right was granted. + */ + private Role grantedRole; + + /** + * The granted right. + */ + private int grantedRight; + + /** + * The object. If the right is global, this is null. + */ + private DbObject grantedObject; + + public Right(Database db, int id, RightOwner grantee, Role grantedRole) { + super(db, id, "RIGHT_" + id, Trace.USER); + this.grantee = grantee; + this.grantedRole = grantedRole; + } + + public Right(Database db, int id, RightOwner grantee, int grantedRight, DbObject grantedObject) { + super(db, id, Integer.toString(id), Trace.USER); + this.grantee = grantee; + this.grantedRight = grantedRight; + this.grantedObject = grantedObject; + } + + private static boolean appendRight(StringBuilder buff, int right, int mask, String name, boolean comma) { + if ((right & mask) != 0) { + if (comma) { + buff.append(", "); + } + buff.append(name); + return true; + } + return comma; + } + + public String getRights() { + StringBuilder buff = new StringBuilder(); + if (grantedRight == ALL) { + buff.append("ALL"); + } else { + boolean comma = false; + comma = appendRight(buff, grantedRight, SELECT, "SELECT", comma); + comma = appendRight(buff, grantedRight, DELETE, "DELETE", comma); + comma = appendRight(buff, grantedRight, INSERT, "INSERT", comma); + comma = appendRight(buff, grantedRight, UPDATE, "UPDATE", comma); + appendRight(buff, grantedRight, ALTER_ANY_SCHEMA, "ALTER ANY SCHEMA", comma); + } + return buff.toString(); + } + + public Role getGrantedRole() { + return grantedRole; + } + + public DbObject getGrantedObject() { + return grantedObject; + } + + public DbObject getGrantee() { + return grantee; + } + + @Override + public String getCreateSQLForCopy(Table table, String quotedName) { + return getCreateSQLForCopy(table); + } + + private String getCreateSQLForCopy(DbObject object) { + StringBuilder builder = new StringBuilder(); + builder.append("GRANT "); + if (grantedRole != null) { + grantedRole.getSQL(builder, DEFAULT_SQL_FLAGS); + } else { + builder.append(getRights()); + if (object != null) { + if (object instanceof Schema) { + builder.append(" ON SCHEMA "); + object.getSQL(builder, DEFAULT_SQL_FLAGS); + } else if (object instanceof Table) { + builder.append(" ON "); + object.getSQL(builder, DEFAULT_SQL_FLAGS); + } + } + } + builder.append(" TO "); + grantee.getSQL(builder, DEFAULT_SQL_FLAGS); + return builder.toString(); + } + + @Override + public String getCreateSQL() { + return getCreateSQLForCopy(grantedObject); + } + + @Override + public int getType() { + return DbObject.RIGHT; + } + + @Override + public void removeChildrenAndResources(SessionLocal session) { + if (grantedRole != null) { + grantee.revokeRole(grantedRole); + } else { + grantee.revokeRight(grantedObject); + } + database.removeMeta(session, getId()); + grantedRole = null; + grantedObject = null; + grantee = null; + invalidate(); + } + + @Override + public void checkRename() { + throw DbException.getInternalError(); + } + + public void setRightMask(int rightMask) { + grantedRight = rightMask; + } + + public int getRightMask() { + return grantedRight; + } + +} diff --git a/h2/src/main/org/h2/engine/RightOwner.java b/h2/src/main/org/h2/engine/RightOwner.java new file mode 100644 index 0000000..bcd5e0e --- /dev/null +++ b/h2/src/main/org/h2/engine/RightOwner.java @@ -0,0 +1,259 @@ +/* + * 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.engine; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map.Entry; + +import org.h2.api.ErrorCode; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.table.Table; +import org.h2.util.StringUtils; + +/** + * A right owner (sometimes called principal). + */ +public abstract class RightOwner extends DbObject { + + /** + * The map of granted roles. + */ + private HashMap grantedRoles; + + /** + * The map of granted rights. + */ + private HashMap grantedRights; + + protected RightOwner(Database database, int id, String name, int traceModuleId) { + super(database, id, StringUtils.toUpperEnglish(name), traceModuleId); + } + + @Override + public void rename(String newName) { + super.rename(StringUtils.toUpperEnglish(newName)); + } + + /** + * Check if a role has been granted for this right owner. + * + * @param grantedRole the role + * @return true if the role has been granted + */ + public boolean isRoleGranted(Role grantedRole) { + if (grantedRole == this) { + return true; + } + if (grantedRoles != null) { + for (Role role : grantedRoles.keySet()) { + if (role == grantedRole) { + return true; + } + if (role.isRoleGranted(grantedRole)) { + return true; + } + } + } + return false; + } + + /** + * Checks if a right is already granted to this object or to objects that + * were granted to this object. The rights of schemas will be valid for + * every each table in the related schema. The ALTER ANY SCHEMA right gives + * all rights to all tables. + * + * @param table + * the table to check + * @param rightMask + * the right mask to check + * @return true if the right was already granted + */ + final boolean isTableRightGrantedRecursive(Table table, int rightMask) { + Schema schema = table.getSchema(); + if (schema.getOwner() == this) { + return true; + } + if (grantedRights != null) { + Right right = grantedRights.get(null); + if (right != null && (right.getRightMask() & Right.ALTER_ANY_SCHEMA) == Right.ALTER_ANY_SCHEMA) { + return true; + } + right = grantedRights.get(schema); + if (right != null && (right.getRightMask() & rightMask) == rightMask) { + return true; + } + right = grantedRights.get(table); + if (right != null && (right.getRightMask() & rightMask) == rightMask) { + return true; + } + } + if (grantedRoles != null) { + for (Role role : grantedRoles.keySet()) { + if (role.isTableRightGrantedRecursive(table, rightMask)) { + return true; + } + } + } + return false; + } + + /** + * Checks if a schema owner right is already granted to this object or to + * objects that were granted to this object. The ALTER ANY SCHEMA right + * gives rights to all schemas. + * + * @param schema + * the schema to check, or {@code null} to check for ALTER ANY + * SCHEMA right only + * @return true if the right was already granted + */ + final boolean isSchemaRightGrantedRecursive(Schema schema) { + if (schema != null && schema.getOwner() == this) { + return true; + } + if (grantedRights != null) { + Right right = grantedRights.get(null); + if (right != null && (right.getRightMask() & Right.ALTER_ANY_SCHEMA) == Right.ALTER_ANY_SCHEMA) { + return true; + } + } + if (grantedRoles != null) { + for (Role role : grantedRoles.keySet()) { + if (role.isSchemaRightGrantedRecursive(schema)) { + return true; + } + } + } + return false; + } + + /** + * Grant a right for the given table. Only one right object per table is + * supported. + * + * @param object the object (table or schema) + * @param right the right + */ + public void grantRight(DbObject object, Right right) { + if (grantedRights == null) { + grantedRights = new HashMap<>(); + } + grantedRights.put(object, right); + } + + /** + * Revoke the right for the given object (table or schema). + * + * @param object the object + */ + void revokeRight(DbObject object) { + if (grantedRights == null) { + return; + } + grantedRights.remove(object); + if (grantedRights.size() == 0) { + grantedRights = null; + } + } + + /** + * Grant a role to this object. + * + * @param role the role + * @param right the right to grant + */ + public void grantRole(Role role, Right right) { + if (grantedRoles == null) { + grantedRoles = new HashMap<>(); + } + grantedRoles.put(role, right); + } + + /** + * Remove the right for the given role. + * + * @param role the role to revoke + */ + void revokeRole(Role role) { + if (grantedRoles == null) { + return; + } + Right right = grantedRoles.get(role); + if (right == null) { + return; + } + grantedRoles.remove(role); + if (grantedRoles.size() == 0) { + grantedRoles = null; + } + } + + /** + * Remove all the temporary rights granted on roles + */ + public void revokeTemporaryRightsOnRoles() { + if (grantedRoles == null) { + return; + } + List rolesToRemove= new ArrayList<>(); + for (Entry currentEntry : grantedRoles.entrySet()) { + if ( currentEntry.getValue().isTemporary() || !currentEntry.getValue().isValid()) { + rolesToRemove.add(currentEntry.getKey()); + } + } + for (Role currentRoleToRemove : rolesToRemove) { + revokeRole(currentRoleToRemove); + } + } + + + + /** + * Get the 'grant schema' right of this object. + * + * @param object the granted object (table or schema) + * @return the right or null if the right has not been granted + */ + public Right getRightForObject(DbObject object) { + if (grantedRights == null) { + return null; + } + return grantedRights.get(object); + } + + /** + * Get the 'grant role' right of this object. + * + * @param role the granted role + * @return the right or null if the right has not been granted + */ + public Right getRightForRole(Role role) { + if (grantedRoles == null) { + return null; + } + return grantedRoles.get(role); + } + + /** + * Check that this right owner does not own any schema. An exception is + * thrown if it owns one or more schemas. + * + * @throws DbException + * if this right owner owns a schema + */ + public final void checkOwnsNoSchemas() { + for (Schema s : database.getAllSchemas()) { + if (this == s.getOwner()) { + throw DbException.get(ErrorCode.CANNOT_DROP_2, getName(), s.getName()); + } + } + } + +} diff --git a/h2/src/main/org/h2/engine/Role.java b/h2/src/main/org/h2/engine/Role.java new file mode 100644 index 0000000..7fec06c --- /dev/null +++ b/h2/src/main/org/h2/engine/Role.java @@ -0,0 +1,87 @@ +/* + * 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.engine; + +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. + */ +public final class Role extends RightOwner { + + private final boolean system; + + public Role(Database database, int id, String roleName, boolean system) { + super(database, id, roleName, Trace.USER); + this.system = system; + } + + @Override + public String getCreateSQLForCopy(Table table, String quotedName) { + throw DbException.getInternalError(toString()); + } + + /** + * Get the CREATE SQL statement for this object. + * + * @param ifNotExists true if IF NOT EXISTS should be used + * @return the SQL statement + */ + public String getCreateSQL(boolean ifNotExists) { + if (system) { + return null; + } + StringBuilder builder = new StringBuilder("CREATE ROLE "); + if (ifNotExists) { + builder.append("IF NOT EXISTS "); + } + return getSQL(builder, DEFAULT_SQL_FLAGS).toString(); + } + + @Override + public String getCreateSQL() { + return getCreateSQL(false); + } + + @Override + public int getType() { + return DbObject.ROLE; + } + + @Override + public ArrayList getChildren() { + ArrayList children = new ArrayList<>(); + for (Schema schema : database.getAllSchemas()) { + if (schema.getOwner() == this) { + children.add(schema); + } + } + return children; + } + + @Override + public void removeChildrenAndResources(SessionLocal session) { + for (RightOwner rightOwner : database.getAllUsersAndRoles()) { + Right right = rightOwner.getRightForRole(this); + if (right != null) { + database.removeDatabaseObject(session, right); + } + } + for (Right right : database.getAllRights()) { + if (right.getGrantee() == this) { + database.removeDatabaseObject(session, right); + } + } + database.removeMeta(session, getId()); + invalidate(); + } + +} diff --git a/h2/src/main/org/h2/engine/Session.java b/h2/src/main/org/h2/engine/Session.java new file mode 100644 index 0000000..654458c --- /dev/null +++ b/h2/src/main/org/h2/engine/Session.java @@ -0,0 +1,310 @@ +/* + * 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.engine; + +import java.util.ArrayList; + +import org.h2.command.CommandInterface; +import org.h2.jdbc.meta.DatabaseMeta; +import org.h2.message.Trace; +import org.h2.result.ResultInterface; +import org.h2.store.DataHandler; +import org.h2.util.NetworkConnectionInfo; +import org.h2.util.TimeZoneProvider; +import org.h2.util.Utils; +import org.h2.value.ValueLob; + +/** + * A local or remote session. A session represents a database connection. + */ +public abstract class Session implements CastDataProvider, AutoCloseable { + + /** + * Static settings. + */ + public static final class StaticSettings { + + /** + * Whether unquoted identifiers are converted to upper case. + */ + public final boolean databaseToUpper; + + /** + * Whether unquoted identifiers are converted to lower case. + */ + public final boolean databaseToLower; + + /** + * Whether all identifiers are case insensitive. + */ + public final boolean caseInsensitiveIdentifiers; + + /** + * Creates new instance of static settings. + * + * @param databaseToUpper + * whether unquoted identifiers are converted to upper case + * @param databaseToLower + * whether unquoted identifiers are converted to lower case + * @param caseInsensitiveIdentifiers + * whether all identifiers are case insensitive + */ + public StaticSettings(boolean databaseToUpper, boolean databaseToLower, boolean caseInsensitiveIdentifiers) { + this.databaseToUpper = databaseToUpper; + this.databaseToLower = databaseToLower; + this.caseInsensitiveIdentifiers = caseInsensitiveIdentifiers; + } + + } + + /** + * Dynamic settings. + */ + public static final class DynamicSettings { + + /** + * The database mode. + */ + public final Mode mode; + + /** + * The current time zone. + */ + public final TimeZoneProvider timeZone; + + /** + * Creates new instance of dynamic settings. + * + * @param mode + * the database mode + * @param timeZone + * the current time zone + */ + public DynamicSettings(Mode mode, TimeZoneProvider timeZone) { + this.mode = mode; + this.timeZone = timeZone; + } + + } + + private ArrayList sessionState; + + boolean sessionStateChanged; + + private boolean sessionStateUpdating; + + volatile StaticSettings staticSettings; + + Session() { + } + + /** + * Get the list of the cluster servers for this session. + * + * @return A list of "ip:port" strings for the cluster servers in this + * session. + */ + public abstract ArrayList getClusterServers(); + + /** + * Parse a command and prepare it for execution. + * + * @param sql the SQL statement + * @param fetchSize the number of rows to fetch in one step + * @return the prepared command + */ + public abstract CommandInterface prepareCommand(String sql, int fetchSize); + + /** + * Roll back pending transactions and close the session. + */ + @Override + public abstract void close(); + + /** + * Get the trace object + * + * @return the trace object + */ + public abstract Trace getTrace(); + + /** + * Check if close was called. + * + * @return if the session has been closed + */ + public abstract boolean isClosed(); + + /** + * Get the data handler object. + * + * @return the data handler + */ + public abstract DataHandler getDataHandler(); + + /** + * Check whether this session has a pending transaction. + * + * @return true if it has + */ + public abstract boolean hasPendingTransaction(); + + /** + * Cancel the current or next command (called when closing a connection). + */ + public abstract void cancel(); + + /** + * Check if this session is in auto-commit mode. + * + * @return true if the session is in auto-commit mode + */ + public abstract boolean getAutoCommit(); + + /** + * Set the auto-commit mode. This call doesn't commit the current + * transaction. + * + * @param autoCommit the new value + */ + public abstract void setAutoCommit(boolean autoCommit); + + /** + * Add a temporary LOB, which is closed when the session commits. + * + * @param v the value + * @return the specified value + */ + public abstract ValueLob addTemporaryLob(ValueLob v); + + /** + * Check if this session is remote or embedded. + * + * @return true if this session is remote + */ + public abstract boolean isRemote(); + + /** + * Set current schema. + * + * @param schema the schema name + */ + public abstract void setCurrentSchemaName(String schema); + + /** + * Get current schema. + * + * @return the current schema name + */ + public abstract String getCurrentSchemaName(); + + /** + * Sets the network connection information if possible. + * + * @param networkConnectionInfo the network connection information + */ + public abstract void setNetworkConnectionInfo(NetworkConnectionInfo networkConnectionInfo); + + /** + * Returns the isolation level. + * + * @return the isolation level + */ + public abstract IsolationLevel getIsolationLevel(); + + /** + * Sets the isolation level. + * + * @param isolationLevel the isolation level to set + */ + public abstract void setIsolationLevel(IsolationLevel isolationLevel); + + /** + * Returns static settings. These settings cannot be changed during + * lifecycle of session. + * + * @return static settings + */ + public abstract StaticSettings getStaticSettings(); + + /** + * Returns dynamic settings. These settings can be changed during lifecycle + * of session. + * + * @return dynamic settings + */ + public abstract DynamicSettings getDynamicSettings(); + + /** + * Returns database meta information. + * + * @return database meta information + */ + public abstract DatabaseMeta getDatabaseMeta(); + + /** + * Returns whether INFORMATION_SCHEMA contains old-style tables. + * + * @return whether INFORMATION_SCHEMA contains old-style tables + */ + public abstract boolean isOldInformationSchema(); + + /** + * Re-create the session state using the stored sessionState list. + */ + void recreateSessionState() { + if (sessionState != null && !sessionState.isEmpty()) { + sessionStateUpdating = true; + try { + for (String sql : sessionState) { + CommandInterface ci = prepareCommand(sql, Integer.MAX_VALUE); + ci.executeUpdate(null); + } + } finally { + sessionStateUpdating = false; + sessionStateChanged = false; + } + } + } + + /** + * Read the session state if necessary. + */ + public void readSessionState() { + if (!sessionStateChanged || sessionStateUpdating) { + return; + } + sessionStateChanged = false; + sessionState = Utils.newSmallArrayList(); + CommandInterface ci = prepareCommand(!isOldInformationSchema() + ? "SELECT STATE_COMMAND FROM INFORMATION_SCHEMA.SESSION_STATE" + : "SELECT SQL FROM INFORMATION_SCHEMA.SESSION_STATE", Integer.MAX_VALUE); + ResultInterface result = ci.executeQuery(0, false); + while (result.next()) { + sessionState.add(result.currentRow()[0].getString()); + } + } + + /** + * Sets this session as thread local session, if this session is a local + * session. + * + * @return old thread local session, or {@code null} + */ + public Session setThreadLocalSession() { + return null; + } + + /** + * Resets old thread local session. + * + * @param oldSession + * the old thread local session, or {@code null} + */ + public void resetThreadLocalSession(Session oldSession) { + } + +} diff --git a/h2/src/main/org/h2/engine/SessionLocal.java b/h2/src/main/org/h2/engine/SessionLocal.java new file mode 100644 index 0000000..97460ab --- /dev/null +++ b/h2/src/main/org/h2/engine/SessionLocal.java @@ -0,0 +1,2086 @@ +/* + * 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.engine; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.atomic.AtomicReference; +import org.h2.api.ErrorCode; +import org.h2.api.JavaObjectSerializer; +import org.h2.command.Command; +import org.h2.command.CommandInterface; +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.QueryExpressionIndex; +import org.h2.jdbc.JdbcConnection; +import org.h2.jdbc.meta.DatabaseMeta; +import org.h2.jdbc.meta.DatabaseMetaLocal; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.message.TraceSystem; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.db.MVIndex; +import org.h2.mvstore.db.MVTable; +import org.h2.mvstore.db.Store; +import org.h2.mvstore.tx.Transaction; +import org.h2.mvstore.tx.TransactionStore; +import org.h2.result.Row; +import org.h2.schema.Schema; +import org.h2.schema.Sequence; +import org.h2.store.DataHandler; +import org.h2.store.InDoubtTransaction; +import org.h2.store.LobStorageFrontend; +import org.h2.table.Table; +import org.h2.util.DateTimeUtils; +import org.h2.util.HasSQL; +import org.h2.util.NetworkConnectionInfo; +import org.h2.util.SmallLRUCache; +import org.h2.util.TimeZoneProvider; +import org.h2.util.Utils; +import org.h2.value.Value; +import org.h2.value.ValueLob; +import org.h2.value.ValueNull; +import org.h2.value.ValueTimestampTimeZone; +import org.h2.value.ValueVarchar; +import org.h2.value.VersionedValue; +import org.h2.value.lob.LobData; +import org.h2.value.lob.LobDataDatabase; +import org.h2.value.lob.LobDataInMemory; + +/** + * A session represents an embedded database connection. When using the server + * mode, this object resides on the server side and communicates with a + * SessionRemote object on the client side. + */ +public final class SessionLocal extends Session implements TransactionStore.RollbackListener { + + public enum State { INIT, RUNNING, BLOCKED, SLEEP, THROTTLED, SUSPENDED, CLOSED } + + private static final class SequenceAndPrepared { + + private final Sequence sequence; + + private final Prepared prepared; + + SequenceAndPrepared(Sequence sequence, Prepared prepared) { + this.sequence = sequence; + this.prepared = prepared; + } + + @Override + public int hashCode() { + return 31 * (31 + prepared.hashCode()) + sequence.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != SequenceAndPrepared.class) { + return false; + } + SequenceAndPrepared other = (SequenceAndPrepared) obj; + return sequence == other.sequence && prepared == other.prepared; + } + + } + + private static final class RowNumberAndValue { + + long rowNumber; + + Value nextValue; + + RowNumberAndValue(long rowNumber, Value nextValue) { + this.rowNumber = rowNumber; + this.nextValue = nextValue; + } + + } + + /** + * The prefix of generated identifiers. It may not have letters, because + * they are case sensitive. + */ + private static final String SYSTEM_IDENTIFIER_PREFIX = "_"; + private static int nextSerialId; + + /** + * Thread local session for comparison operations between different data types. + */ + private static final ThreadLocal THREAD_LOCAL_SESSION = new ThreadLocal<>(); + + static Session getThreadLocalSession() { + Session session = THREAD_LOCAL_SESSION.get(); + if (session == null) { + THREAD_LOCAL_SESSION.remove(); + } + return session; + } + + private final int serialId = nextSerialId++; + private final Database database; + private final User user; + private final int id; + + private NetworkConnectionInfo networkConnectionInfo; + + private final ArrayList
locks = Utils.newSmallArrayList(); + private boolean autoCommit = true; + private Random random; + private int lockTimeout; + + private HashMap nextValueFor; + private WeakHashMap currentValueFor; + private Value lastIdentity = ValueNull.INSTANCE; + + private HashMap savepoints; + private HashMap localTempTables; + private HashMap localTempTableIndexes; + private HashMap localTempTableConstraints; + private int throttleMs; + private long lastThrottleNs; + private Command currentCommand; + private boolean allowLiterals; + private String currentSchemaName; + private String[] schemaSearchPath; + private Trace trace; + private HashMap removeLobMap; + private int systemIdentifier; + private HashMap procedures; + private boolean autoCommitAtTransactionEnd; + private String currentTransactionName; + private volatile long cancelAtNs; + private final ValueTimestampTimeZone sessionStart; + private Instant commandStartOrEnd; + private ValueTimestampTimeZone currentTimestamp; + private HashMap variables; + private int queryTimeout; + private boolean commitOrRollbackDisabled; + private Table waitForLock; + private Thread waitForLockThread; + private int modificationId; + private int objectId; + private final int queryCacheSize; + private SmallLRUCache queryCache; + private long modificationMetaID = -1; + private int createViewLevel; + private volatile SmallLRUCache viewIndexCache; + private HashMap derivedTableIndexCache; + private boolean lazyQueryExecution; + + private BitSet nonKeywords; + + private TimeZoneProvider timeZone; + + /** + * Tables marked for ANALYZE after the current transaction is committed. + * Prevents us calling ANALYZE repeatedly in large transactions. + */ + private HashSet
tablesToAnalyze; + + /** + * Temporary LOBs from result sets. Those are kept for some time. The + * problem is that transactions are committed before the result is returned, + * and in some cases the next transaction is already started before the + * result is read (for example when using the server mode, when accessing + * metadata methods). We can't simply free those values up when starting the + * next transaction, because they would be removed too early. + */ + private LinkedList temporaryResultLobs; + + /** + * The temporary LOBs that need to be removed on commit. + */ + private ArrayList temporaryLobs; + + private Transaction transaction; + private final AtomicReference state = new AtomicReference<>(State.INIT); + private long startStatement = -1; + + /** + * Isolation level. + */ + private IsolationLevel isolationLevel = IsolationLevel.READ_COMMITTED; + + /** + * The snapshot data modification id. If isolation level doesn't allow + * non-repeatable reads the session uses a snapshot versions of data. After + * commit or rollback these snapshots are discarded and cached results of + * queries may became invalid. Commit and rollback allocate a new data + * modification id and store it here to forbid usage of older results. + */ + private long snapshotDataModificationId; + + /** + * Set of database object ids to be released at the end of transaction + */ + private BitSet idsToRelease; + + /** + * Whether length in definitions of data types is truncated. + */ + private boolean truncateLargeLength; + + /** + * Whether BINARY is parsed as VARBINARY. + */ + private boolean variableBinary; + + /** + * Whether INFORMATION_SCHEMA contains old-style tables. + */ + private boolean oldInformationSchema; + + /** + * Whether commands are executed in quirks mode to support scripts from older versions of H2. + */ + private boolean quirksMode; + + public SessionLocal(Database database, User user, int id) { + this.database = database; + this.queryTimeout = database.getSettings().maxQueryTimeout; + this.queryCacheSize = database.getSettings().queryCacheSize; + this.user = user; + this.id = id; + this.lockTimeout = database.getLockTimeout(); + Schema mainSchema = database.getMainSchema(); + this.currentSchemaName = mainSchema != null ? mainSchema.getName() + : database.sysIdentifier(Constants.SCHEMA_MAIN); + timeZone = DateTimeUtils.getTimeZone(); + sessionStart = DateTimeUtils.currentTimestamp(timeZone, commandStartOrEnd = Instant.now()); + } + + public void setLazyQueryExecution(boolean lazyQueryExecution) { + this.lazyQueryExecution = lazyQueryExecution; + } + + public boolean isLazyQueryExecution() { + return lazyQueryExecution; + } + + /** + * This method is called before and after parsing of view definition and may + * be called recursively. + * + * @param parsingView + * {@code true} if this method is called before parsing of view + * definition, {@code false} if it is called after it. + */ + public void setParsingCreateView(boolean parsingView) { + createViewLevel += parsingView ? 1 : -1; + } + + public boolean isParsingCreateView() { + return createViewLevel != 0; + } + + @Override + public ArrayList getClusterServers() { + return new ArrayList<>(); + } + + public boolean setCommitOrRollbackDisabled(boolean x) { + boolean old = commitOrRollbackDisabled; + commitOrRollbackDisabled = x; + return old; + } + + private void initVariables() { + if (variables == null) { + variables = database.newStringMap(); + } + } + + /** + * Set the value of the given variable for this session. + * + * @param name the name of the variable (may not be null) + * @param value the new value (may not be null) + */ + public void setVariable(String name, Value value) { + initVariables(); + modificationId++; + Value old; + if (value == ValueNull.INSTANCE) { + old = variables.remove(name); + } else { + if (value instanceof ValueLob) { + // link LOB values, to make sure we have our own object + value = ((ValueLob) value).copy(database, LobStorageFrontend.TABLE_ID_SESSION_VARIABLE); + } + old = variables.put(name, value); + } + if (old instanceof ValueLob) { + ((ValueLob) old).remove(); + } + } + + /** + * Get the value of the specified user defined variable. This method always + * returns a value; it returns ValueNull.INSTANCE if the variable doesn't + * exist. + * + * @param name the variable name + * @return the value, or NULL + */ + public Value getVariable(String name) { + initVariables(); + Value v = variables.get(name); + return v == null ? ValueNull.INSTANCE : v; + } + + /** + * Get the list of variable names that are set for this session. + * + * @return the list of names + */ + public String[] getVariableNames() { + if (variables == null) { + return new String[0]; + } + return variables.keySet().toArray(new String[0]); + } + + /** + * Get the local temporary table if one exists with that name, or null if + * not. + * + * @param name the table name + * @return the table, or null + */ + public Table findLocalTempTable(String name) { + if (localTempTables == null) { + return null; + } + return localTempTables.get(name); + } + + public List
getLocalTempTables() { + if (localTempTables == null) { + return Collections.emptyList(); + } + return new ArrayList<>(localTempTables.values()); + } + + /** + * Add a local temporary table to this session. + * + * @param table the table to add + * @throws DbException if a table with this name already exists + */ + public void addLocalTempTable(Table table) { + if (localTempTables == null) { + localTempTables = database.newStringMap(); + } + if (localTempTables.putIfAbsent(table.getName(), table) != null) { + StringBuilder builder = new StringBuilder(); + table.getSQL(builder, HasSQL.TRACE_SQL_FLAGS).append(" AS "); + Parser.quoteIdentifier(table.getName(), HasSQL.TRACE_SQL_FLAGS); + throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, builder.toString()); + } + modificationId++; + } + + /** + * Drop and remove the given local temporary table from this session. + * + * @param table the table + */ + public void removeLocalTempTable(Table table) { + modificationId++; + if (localTempTables != null) { + localTempTables.remove(table.getName()); + } + synchronized (database) { + table.removeChildrenAndResources(this); + } + } + + /** + * Get the local temporary index if one exists with that name, or null if + * not. + * + * @param name the table name + * @return the table, or null + */ + public Index findLocalTempTableIndex(String name) { + if (localTempTableIndexes == null) { + return null; + } + return localTempTableIndexes.get(name); + } + + public HashMap getLocalTempTableIndexes() { + if (localTempTableIndexes == null) { + return new HashMap<>(); + } + return localTempTableIndexes; + } + + /** + * Add a local temporary index to this session. + * + * @param index the index to add + * @throws DbException if a index with this name already exists + */ + public void addLocalTempTableIndex(Index index) { + if (localTempTableIndexes == null) { + localTempTableIndexes = database.newStringMap(); + } + if (localTempTableIndexes.putIfAbsent(index.getName(), index) != null) { + throw DbException.get(ErrorCode.INDEX_ALREADY_EXISTS_1, index.getTraceSQL()); + } + } + + /** + * Drop and remove the given local temporary index from this session. + * + * @param index the index + */ + public void removeLocalTempTableIndex(Index index) { + if (localTempTableIndexes != null) { + localTempTableIndexes.remove(index.getName()); + synchronized (database) { + index.removeChildrenAndResources(this); + } + } + } + + /** + * Get the local temporary constraint if one exists with that name, or + * null if not. + * + * @param name the constraint name + * @return the constraint, or null + */ + public Constraint findLocalTempTableConstraint(String name) { + if (localTempTableConstraints == null) { + return null; + } + return localTempTableConstraints.get(name); + } + + /** + * Get the map of constraints for all constraints on local, temporary + * tables, if any. The map's keys are the constraints' names. + * + * @return the map of constraints, or null + */ + public HashMap getLocalTempTableConstraints() { + if (localTempTableConstraints == null) { + return new HashMap<>(); + } + return localTempTableConstraints; + } + + /** + * Add a local temporary constraint to this session. + * + * @param constraint the constraint to add + * @throws DbException if a constraint with the same name already exists + */ + public void addLocalTempTableConstraint(Constraint constraint) { + if (localTempTableConstraints == null) { + localTempTableConstraints = database.newStringMap(); + } + String name = constraint.getName(); + if (localTempTableConstraints.putIfAbsent(name, constraint) != null) { + throw DbException.get(ErrorCode.CONSTRAINT_ALREADY_EXISTS_1, constraint.getTraceSQL()); + } + } + + /** + * Drop and remove the given local temporary constraint from this session. + * + * @param constraint the constraint + */ + void removeLocalTempTableConstraint(Constraint constraint) { + if (localTempTableConstraints != null) { + localTempTableConstraints.remove(constraint.getName()); + synchronized (database) { + constraint.removeChildrenAndResources(this); + } + } + } + + @Override + public boolean getAutoCommit() { + return autoCommit; + } + + public User getUser() { + return user; + } + + @Override + public void setAutoCommit(boolean b) { + autoCommit = b; + } + + public int getLockTimeout() { + return lockTimeout; + } + + public void setLockTimeout(int lockTimeout) { + this.lockTimeout = lockTimeout; + if (hasTransaction()) { + transaction.setTimeoutMillis(lockTimeout); + } + } + + @Override + public synchronized CommandInterface prepareCommand(String sql, + int fetchSize) { + return prepareLocal(sql); + } + + /** + * Parse and prepare the given SQL statement. This method also checks the + * rights. + * + * @param sql the SQL statement + * @return the prepared statement + */ + public Prepared prepare(String sql) { + return prepare(sql, false, false); + } + + /** + * Parse and prepare the given SQL statement. + * + * @param sql the SQL statement + * @param rightsChecked true if the rights have already been checked + * @param literalsChecked true if the sql string has already been checked + * for literals (only used if ALLOW_LITERALS NONE is set). + * @return the prepared statement + */ + public Prepared prepare(String sql, boolean rightsChecked, boolean literalsChecked) { + Parser parser = new Parser(this); + parser.setRightsChecked(rightsChecked); + parser.setLiteralsChecked(literalsChecked); + 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. + * + * @param sql the SQL statement + * @return the prepared statement + */ + public Command prepareLocal(String sql) { + if (isClosed()) { + throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, + "session closed"); + } + Command command; + if (queryCacheSize > 0) { + if (queryCache == null) { + queryCache = SmallLRUCache.newInstance(queryCacheSize); + modificationMetaID = database.getModificationMetaId(); + } else { + long newModificationMetaID = database.getModificationMetaId(); + if (newModificationMetaID != modificationMetaID) { + queryCache.clear(); + modificationMetaID = newModificationMetaID; + } + command = queryCache.get(sql); + if (command != null && command.canReuse()) { + command.reuse(); + return command; + } + } + } + Parser parser = new Parser(this); + try { + command = parser.prepareCommand(sql); + } finally { + // we can't reuse indexes of derived tables, so just drop the whole cache + derivedTableIndexCache = null; + } + if (queryCache != null) { + if (command.isCacheable()) { + queryCache.put(sql, command); + } + } + return command; + } + + /** + * Arranges for the specified database object id to be released + * at the end of the current transaction. + * @param id to be scheduled + */ + void scheduleDatabaseObjectIdForRelease(int id) { + if (idsToRelease == null) { + idsToRelease = new BitSet(); + } + idsToRelease.set(id); + } + + public Database getDatabase() { + return database; + } + + /** + * Commit the current transaction. If the statement was not a data + * definition statement, and if there are temporary tables that should be + * dropped or truncated at commit, this is done as well. + * + * @param ddl if the statement was a data definition statement + */ + public void commit(boolean ddl) { + beforeCommitOrRollback(); + if (hasTransaction()) { + try { + markUsedTablesAsUpdated(); + transaction.commit(); + removeTemporaryLobs(true); + endTransaction(); + } finally { + transaction = null; + } + if (!ddl) { + // do not clean the temp tables if the last command was a + // create/drop + cleanTempTables(false); + if (autoCommitAtTransactionEnd) { + autoCommit = true; + autoCommitAtTransactionEnd = false; + } + } + analyzeTables(); + } + } + + private void markUsedTablesAsUpdated() { + // TODO should not rely on locking + if (!locks.isEmpty()) { + for (Table t : locks) { + if (t instanceof MVTable) { + ((MVTable) t).commit(); + } + } + } + } + + private void analyzeTables() { + // On rare occasions it can be called concurrently (i.e. from close()) + // without proper locking, but instead of oversynchronizing + // we just skip this optional operation in such case + if (tablesToAnalyze != null && + Thread.holdsLock(this)) { + // take a local copy and clear because in rare cases we can call + // back into markTableForAnalyze while iterating here + HashSet
tablesToAnalyzeLocal = tablesToAnalyze; + tablesToAnalyze = null; + int rowCount = getDatabase().getSettings().analyzeSample / 10; + for (Table table : tablesToAnalyzeLocal) { + Analyze.analyzeTable(this, table, rowCount, false); + } + // analyze can lock the meta + database.unlockMeta(this); + // table analysis opens a new transaction(s), + // so we need to commit afterwards whatever leftovers might be + commit(true); + } + } + + private void removeTemporaryLobs(boolean onTimeout) { + if (temporaryLobs != null) { + for (ValueLob v : temporaryLobs) { + if (!v.isLinkedToTable()) { + v.remove(); + } + } + temporaryLobs.clear(); + } + if (temporaryResultLobs != null && !temporaryResultLobs.isEmpty()) { + long keepYoungerThan = System.nanoTime() - database.getSettings().lobTimeout * 1_000_000L; + while (!temporaryResultLobs.isEmpty()) { + TimeoutValue tv = temporaryResultLobs.getFirst(); + if (onTimeout && tv.created - keepYoungerThan >= 0) { + break; + } + ValueLob v = temporaryResultLobs.removeFirst().value; + if (!v.isLinkedToTable()) { + v.remove(); + } + } + } + } + + private void beforeCommitOrRollback() { + if (commitOrRollbackDisabled && !locks.isEmpty()) { + throw DbException.get(ErrorCode.COMMIT_ROLLBACK_NOT_ALLOWED); + } + currentTransactionName = null; + currentTimestamp = null; + database.throwLastBackgroundException(); + } + + private void endTransaction() { + if (removeLobMap != null && !removeLobMap.isEmpty()) { + for (ValueLob v : removeLobMap.values()) { + v.remove(); + } + removeLobMap = null; + } + unlockAll(); + if (idsToRelease != null) { + database.releaseDatabaseObjectIds(idsToRelease); + idsToRelease = null; + } + if (hasTransaction() && !transaction.allowNonRepeatableRead()) { + snapshotDataModificationId = database.getNextModificationDataId(); + } + } + + /** + * Returns the data modification id of transaction's snapshot, or 0 if + * isolation level doesn't use snapshots. + * + * @return the data modification id of transaction's snapshot, or 0 + */ + public long getSnapshotDataModificationId() { + return snapshotDataModificationId; + } + + /** + * Fully roll back the current transaction. + */ + public void rollback() { + beforeCommitOrRollback(); + if (hasTransaction()) { + rollbackTo(null); + } + idsToRelease = null; + cleanTempTables(false); + if (autoCommitAtTransactionEnd) { + autoCommit = true; + autoCommitAtTransactionEnd = false; + } + endTransaction(); + } + + /** + * Partially roll back the current transaction. + * + * @param savepoint the savepoint to which should be rolled back + */ + public void rollbackTo(Savepoint savepoint) { + int index = savepoint == null ? 0 : savepoint.logIndex; + if (hasTransaction()) { + markUsedTablesAsUpdated(); + if (savepoint == null) { + transaction.rollback(); + transaction = null; + } else { + transaction.rollbackToSavepoint(savepoint.transactionSavepoint); + } + } + if (savepoints != null) { + String[] names = savepoints.keySet().toArray(new String[0]); + for (String name : names) { + Savepoint sp = savepoints.get(name); + int savepointIndex = sp.logIndex; + if (savepointIndex > index) { + savepoints.remove(name); + } + } + } + + // Because cache may have captured query result (in Query.lastResult), + // which is based on data from uncommitted transaction., + // It is not valid after rollback, therefore cache has to be cleared. + if (queryCache != null) { + queryCache.clear(); + } + } + + @Override + public boolean hasPendingTransaction() { + return hasTransaction() && transaction.hasChanges() && transaction.getStatus() != Transaction.STATUS_PREPARED; + } + + /** + * Create a savepoint to allow rolling back to this state. + * + * @return the savepoint + */ + public Savepoint setSavepoint() { + Savepoint sp = new Savepoint(); + sp.transactionSavepoint = getStatementSavepoint(); + return sp; + } + + public int getId() { + return id; + } + + @Override + public void cancel() { + cancelAtNs = Utils.currentNanoTime(); + } + + /** + * Cancel the transaction and close the session if needed. + */ + void suspend() { + cancel(); + if (transitionToState(State.SUSPENDED, false) == State.SLEEP) { + close(); + } + } + + @Override + public void close() { + // this is the only operation that can be invoked concurrently + // so, we should prevent double-closure + if (state.getAndSet(State.CLOSED) != State.CLOSED) { + try { + database.throwLastBackgroundException(); + + database.checkPowerOff(); + + // release any open table locks + if (hasPreparedTransaction()) { + if (currentTransactionName != null) { + removeLobMap = null; + } + endTransaction(); + } else { + rollback(); + removeTemporaryLobs(false); + cleanTempTables(true); + commit(true); // temp table removal may have opened new transaction + } + + // Table#removeChildrenAndResources can take the meta lock, + // and we need to unlock before we call removeSession(), which might + // want to take the meta lock using the system session. + database.unlockMeta(this); + } finally { + database.removeSession(this); + } + } + } + + /** + * Register table as locked within current transaction. + * Table is unlocked on commit or rollback. + * It also assumes that table will be modified by transaction. + * + * @param table the table that is locked + */ + public void registerTableAsLocked(Table table) { + if (SysProperties.CHECK) { + if (locks.contains(table)) { + throw DbException.getInternalError(table.toString()); + } + } + locks.add(table); + } + + /** + * Register table as updated within current transaction. + * This is used instead of table locking when lock mode is LOCK_MODE_OFF. + * + * @param table to register + */ + public void registerTableAsUpdated(Table table) { + if (!locks.contains(table)) { + locks.add(table); + } + } + + /** + * Unlock just this table. + * + * @param t the table to unlock + */ + void unlock(Table t) { + locks.remove(t); + } + + + private boolean hasTransaction() { + return transaction != null; + } + + private void unlockAll() { + if (!locks.isEmpty()) { + Table[] array = locks.toArray(new Table[0]); + for (Table t : array) { + if (t != null) { + t.unlock(this); + } + } + locks.clear(); + } + Database.unlockMetaDebug(this); + savepoints = null; + sessionStateChanged = true; + } + + private void cleanTempTables(boolean closeSession) { + if (localTempTables != null && !localTempTables.isEmpty()) { + Iterator
it = localTempTables.values().iterator(); + while (it.hasNext()) { + Table table = it.next(); + if (closeSession || table.getOnCommitDrop()) { + modificationId++; + table.setModified(); + it.remove(); + // Exception thrown in org.h2.engine.Database.removeMeta + // if line below is missing with TestDeadlock + database.lockMeta(this); + table.removeChildrenAndResources(this); + if (closeSession) { + database.throwLastBackgroundException(); + } + } else if (table.getOnCommitTruncate()) { + table.truncate(this); + } + } + } + } + + public Random getRandom() { + if (random == null) { + random = new Random(); + } + return random; + } + + @Override + public Trace getTrace() { + if (trace != null && !isClosed()) { + return trace; + } + String traceModuleName = "jdbc[" + id + "]"; + if (isClosed()) { + return new TraceSystem(null).getTrace(traceModuleName); + } + trace = database.getTraceSystem().getTrace(traceModuleName); + return trace; + } + + /** + * Returns the next value of the sequence in this session. + * + * @param sequence + * the sequence + * @param prepared + * current prepared command, select, or {@code null} + * @return the next value of the sequence in this session + */ + public Value getNextValueFor(Sequence sequence, Prepared prepared) { + Value value; + Mode mode = database.getMode(); + if (mode.nextValueReturnsDifferentValues || prepared == null) { + value = sequence.getNext(this); + } else { + if (nextValueFor == null) { + nextValueFor = new HashMap<>(); + } + SequenceAndPrepared key = new SequenceAndPrepared(sequence, prepared); + RowNumberAndValue data = nextValueFor.get(key); + long rowNumber = prepared.getCurrentRowNumber(); + if (data != null) { + if (data.rowNumber == rowNumber) { + value = data.nextValue; + } else { + data.nextValue = value = sequence.getNext(this); + data.rowNumber = rowNumber; + } + } else { + value = sequence.getNext(this); + nextValueFor.put(key, new RowNumberAndValue(rowNumber, value)); + } + } + WeakHashMap currentValueFor = this.currentValueFor; + if (currentValueFor == null) { + this.currentValueFor = currentValueFor = new WeakHashMap<>(); + } + currentValueFor.put(sequence, value); + if (mode.takeGeneratedSequenceValue) { + lastIdentity = value; + } + return value; + } + + /** + * Returns the current value of the sequence in this session. + * + * @param sequence + * the sequence + * @return the current value of the sequence in this session + * @throws DbException + * if current value is not defined + */ + public Value getCurrentValueFor(Sequence sequence) { + WeakHashMap currentValueFor = this.currentValueFor; + if (currentValueFor != null) { + Value value = currentValueFor.get(sequence); + if (value != null) { + return value; + } + } + throw DbException.get(ErrorCode.CURRENT_SEQUENCE_VALUE_IS_NOT_DEFINED_IN_SESSION_1, sequence.getTraceSQL()); + } + + public void setLastIdentity(Value last) { + this.lastIdentity = last; + } + + public Value getLastIdentity() { + return lastIdentity; + } + + /** + * Whether the session contains any uncommitted changes. + * + * @return true if yes + */ + public boolean containsUncommitted() { + return transaction != null && transaction.hasChanges(); + } + + /** + * Create a savepoint that is linked to the current log position. + * + * @param name the savepoint name + */ + public void addSavepoint(String name) { + if (savepoints == null) { + savepoints = database.newStringMap(); + } + savepoints.put(name, setSavepoint()); + } + + /** + * Undo all operations back to the log position of the given savepoint. + * + * @param name the savepoint name + */ + public void rollbackToSavepoint(String name) { + beforeCommitOrRollback(); + Savepoint savepoint; + if (savepoints == null || (savepoint = savepoints.get(name)) == null) { + throw DbException.get(ErrorCode.SAVEPOINT_IS_INVALID_1, name); + } + rollbackTo(savepoint); + } + + /** + * Prepare the given transaction. + * + * @param transactionName the name of the transaction + */ + public void prepareCommit(String transactionName) { + if (hasPendingTransaction()) { + // need to commit even if rollback is not possible (create/drop + // table and so on) + database.prepareCommit(this, transactionName); + } + currentTransactionName = transactionName; + } + + /** + * Checks presence of prepared transaction in this session. + * + * @return {@code true} if there is a prepared transaction, + * {@code false} otherwise + */ + public boolean hasPreparedTransaction() { + return currentTransactionName != null; + } + + /** + * Commit or roll back the given transaction. + * + * @param transactionName the name of the transaction + * @param commit true for commit, false for rollback + */ + public void setPreparedTransaction(String transactionName, boolean commit) { + if (hasPreparedTransaction() && currentTransactionName.equals(transactionName)) { + if (commit) { + commit(false); + } else { + rollback(); + } + } else { + ArrayList list = database.getInDoubtTransactions(); + int state = commit ? InDoubtTransaction.COMMIT : InDoubtTransaction.ROLLBACK; + boolean found = false; + for (InDoubtTransaction p: list) { + if (p.getTransactionName().equals(transactionName)) { + p.setState(state); + found = true; + break; + } + } + if (!found) { + throw DbException.get(ErrorCode.TRANSACTION_NOT_FOUND_1, + transactionName); + } + } + } + + @Override + public boolean isClosed() { + return state.get() == State.CLOSED; + } + + public boolean isOpen() { + State current = state.get(); + checkSuspended(current); + return current != State.CLOSED; + } + + public void setThrottle(int throttle) { + this.throttleMs = throttle; + } + + /** + * Wait for some time if this session is throttled (slowed down). + */ + public void throttle() { + if (throttleMs == 0) { + return; + } + long time = System.nanoTime(); + if (lastThrottleNs != 0L && time - lastThrottleNs < Constants.THROTTLE_DELAY * 1_000_000L) { + return; + } + lastThrottleNs = Utils.nanoTimePlusMillis(time, throttleMs); + State prevState = transitionToState(State.THROTTLED, false); + try { + Thread.sleep(throttleMs); + } catch (InterruptedException ignore) { + } finally { + transitionToState(prevState, false); + } + } + + /** + * Set the current command of this session. This is done just before + * executing the statement. + * + * @param command the command + */ + private void setCurrentCommand(Command command) { + State targetState = command == null ? State.SLEEP : State.RUNNING; + transitionToState(targetState, true); + if (isOpen()) { + currentCommand = command; + commandStartOrEnd = Instant.now(); + if (command != null) { + if (queryTimeout > 0) { + cancelAtNs = Utils.currentNanoTimePlusMillis(queryTimeout); + } + } else { + if (currentTimestamp != null && !database.getMode().dateTimeValueWithinTransaction) { + currentTimestamp = null; + } + if (nextValueFor != null) { + nextValueFor.clear(); + } + } + } + } + + private State transitionToState(State targetState, boolean checkSuspended) { + State currentState; + while((currentState = state.get()) != State.CLOSED && + (!checkSuspended || checkSuspended(currentState)) && + !state.compareAndSet(currentState, targetState)) {/**/} + return currentState; + } + + private boolean checkSuspended(State currentState) { + if (currentState == State.SUSPENDED) { + close(); + throw DbException.get(ErrorCode.DATABASE_IS_IN_EXCLUSIVE_MODE); + } + return true; + } + + /** + * Check if the current transaction is canceled by calling + * Statement.cancel() or because a session timeout was set and expired. + * + * @throws DbException if the transaction is canceled + */ + public void checkCanceled() { + throttle(); + long cancel = cancelAtNs; + if (cancel == 0L) { + return; + } + if (System.nanoTime() - cancel >= 0L) { + cancelAtNs = 0L; + throw DbException.get(ErrorCode.STATEMENT_WAS_CANCELED); + } + } + + /** + * Get the cancel time. + * + * @return the time or 0 if not set + */ + public long getCancel() { + return cancelAtNs; + } + + public Command getCurrentCommand() { + return currentCommand; + } + + public ValueTimestampTimeZone getCommandStartOrEnd() { + return DateTimeUtils.currentTimestamp(timeZone, commandStartOrEnd); + } + + public boolean getAllowLiterals() { + return allowLiterals; + } + + public void setAllowLiterals(boolean b) { + this.allowLiterals = b; + } + + public void setCurrentSchema(Schema schema) { + modificationId++; + if (queryCache != null) { + queryCache.clear(); + } + this.currentSchemaName = schema.getName(); + } + + @Override + public String getCurrentSchemaName() { + return currentSchemaName; + } + + @Override + public void setCurrentSchemaName(String schemaName) { + Schema schema = database.getSchema(schemaName); + setCurrentSchema(schema); + } + + /** + * Create an internal connection. This connection is used when initializing + * triggers, and when calling user defined functions. + * + * @param columnList if the url should be 'jdbc:columnlist:connection' + * @return the internal connection + */ + public JdbcConnection createConnection(boolean columnList) { + String url; + if (columnList) { + url = Constants.CONN_URL_COLUMNLIST; + } else { + url = Constants.CONN_URL_INTERNAL; + } + return new JdbcConnection(this, getUser().getName(), url); + } + + @Override + public DataHandler getDataHandler() { + return database; + } + + /** + * Remember that the given LOB value must be removed at commit. + * + * @param v the value + */ + public void removeAtCommit(ValueLob v) { + if (v.isLinkedToTable()) { + if (removeLobMap == null) { + removeLobMap = new HashMap<>(); + } + removeLobMap.put(v.toString(), v); + } + } + + /** + * Do not remove this LOB value at commit any longer. + * + * @param v the value + */ + public void removeAtCommitStop(ValueLob v) { + if (v.isLinkedToTable() && removeLobMap != null) { + removeLobMap.remove(v.toString()); + } + } + + /** + * Get the next system generated identifiers. The identifier returned does + * not occur within the given SQL statement. + * + * @param sql the SQL statement + * @return the new identifier + */ + public String getNextSystemIdentifier(String sql) { + String identifier; + do { + identifier = SYSTEM_IDENTIFIER_PREFIX + systemIdentifier++; + } while (sql.contains(identifier)); + return identifier; + } + + /** + * Add a procedure to this session. + * + * @param procedure the procedure to add + */ + public void addProcedure(Procedure procedure) { + if (procedures == null) { + procedures = database.newStringMap(); + } + procedures.put(procedure.getName(), procedure); + } + + /** + * Remove a procedure from this session. + * + * @param name the name of the procedure to remove + */ + public void removeProcedure(String name) { + if (procedures != null) { + procedures.remove(name); + } + } + + /** + * Get the procedure with the given name, or null + * if none exists. + * + * @param name the procedure name + * @return the procedure or null + */ + public Procedure getProcedure(String name) { + if (procedures == null) { + return null; + } + return procedures.get(name); + } + + public void setSchemaSearchPath(String[] schemas) { + modificationId++; + this.schemaSearchPath = schemas; + } + + public String[] getSchemaSearchPath() { + return schemaSearchPath; + } + + @Override + public int hashCode() { + return serialId; + } + + @Override + public String toString() { + return "#" + serialId + " (user: " + (user == null ? "" : user.getName()) + ", " + state.get() + ")"; + } + + /** + * Begin a transaction. + */ + public void begin() { + autoCommitAtTransactionEnd = true; + autoCommit = false; + } + + public ValueTimestampTimeZone getSessionStart() { + return sessionStart; + } + + public Set
getLocks() { + /* + * This implementation needs to be lock-free. + */ + if (database.getLockMode() == Constants.LOCK_MODE_OFF || locks.isEmpty()) { + return Collections.emptySet(); + } + /* + * Do not use ArrayList.toArray(T[]) here, its implementation is not + * thread-safe. + */ + Object[] array = locks.toArray(); + /* + * The returned array may contain null elements and may contain + * duplicates due to concurrent remove(). + */ + switch (array.length) { + case 1: { + Object table = array[0]; + if (table != null) { + return Collections.singleton((Table) table); + } + } + //$FALL-THROUGH$ + case 0: + return Collections.emptySet(); + default: { + HashSet
set = new HashSet<>(); + for (Object table : array) { + if (table != null) { + set.add((Table) table); + } + } + return set; + } + } + } + + /** + * Wait if the exclusive mode has been enabled for another session. This + * method returns as soon as the exclusive mode has been disabled. + */ + public void waitIfExclusiveModeEnabled() { + transitionToState(State.RUNNING, true); + // Even in exclusive mode, we have to let the LOB session proceed, or we + // will get deadlocks. + if (database.getLobSession() == this) { + return; + } + while (isOpen()) { + SessionLocal exclusive = database.getExclusiveSession(); + if (exclusive == null || exclusive == this) { + break; + } + if (Thread.holdsLock(exclusive)) { + // if another connection is used within the connection + break; + } + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // ignore + } + } + } + + /** + * 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 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 derivedTableIndexCache; + } + SmallLRUCache cache = viewIndexCache; + if (cache == null) { + viewIndexCache = cache = SmallLRUCache.newInstance(Constants.VIEW_INDEX_CACHE_SIZE); + } + return cache; + } + + public void setQueryTimeout(int queryTimeout) { + int max = database.getSettings().maxQueryTimeout; + if (max != 0 && (max < queryTimeout || queryTimeout == 0)) { + // the value must be at most max + queryTimeout = max; + } + this.queryTimeout = queryTimeout; + // must reset the cancel at here, + // otherwise it is still used + cancelAtNs = 0L; + } + + public int getQueryTimeout() { + return queryTimeout; + } + + /** + * Set the table this session is waiting for, and the thread that is + * waiting. + * + * @param waitForLock the table + * @param waitForLockThread the current thread (the one that is waiting) + */ + public void setWaitForLock(Table waitForLock, Thread waitForLockThread) { + this.waitForLock = waitForLock; + this.waitForLockThread = waitForLockThread; + } + + public Table getWaitForLock() { + return waitForLock; + } + + public Thread getWaitForLockThread() { + return waitForLockThread; + } + + public int getModificationId() { + return modificationId; + } + + public Value getTransactionId() { + if (transaction == null || !transaction.hasChanges()) { + return ValueNull.INSTANCE; + } + return ValueVarchar.get(Long.toString(transaction.getSequenceNum())); + } + + /** + * Get the next object id. + * + * @return the next object id + */ + public int nextObjectId() { + return objectId++; + } + + /** + * Get the transaction to use for this session. + * + * @return the transaction + */ + public Transaction getTransaction() { + if (transaction == null) { + Store store = database.getStore(); + if (store.getMvStore().isClosed()) { + Throwable backgroundException = database.getBackgroundException(); + database.shutdownImmediately(); + throw DbException.get(ErrorCode.DATABASE_IS_CLOSED, backgroundException); + } + transaction = store.getTransactionStore().begin(this, this.lockTimeout, id, isolationLevel); + startStatement = -1; + } + return transaction; + } + + private long getStatementSavepoint() { + if (startStatement == -1) { + startStatement = getTransaction().setSavepoint(); + } + return startStatement; + } + + /** + * Start a new statement within a transaction. + * @param command about to be started + */ + @SuppressWarnings("incomplete-switch") + public void startStatementWithinTransaction(Command command) { + Transaction transaction = getTransaction(); + if (transaction != null) { + HashSet>> maps = new HashSet<>(); + if (command != null) { + Set dependencies = command.getDependencies(); + switch (transaction.getIsolationLevel()) { + case SNAPSHOT: + case SERIALIZABLE: + if (!transaction.hasStatementDependencies()) { + for (Schema schema : database.getAllSchemasNoMeta()) { + for (Table table : schema.getAllTablesAndViews(null)) { + if (table instanceof MVTable) { + addTableToDependencies((MVTable)table, maps); + } + } + } + break; + } + //$FALL-THROUGH$ + case READ_COMMITTED: + case READ_UNCOMMITTED: + for (DbObject dependency : dependencies) { + if (dependency instanceof MVTable) { + addTableToDependencies((MVTable)dependency, maps); + } + } + break; + case REPEATABLE_READ: + HashSet processed = new HashSet<>(); + for (DbObject dependency : dependencies) { + if (dependency instanceof MVTable) { + addTableToDependencies((MVTable)dependency, maps, processed); + } + } + break; + } + } + transaction.markStatementStart(maps); + } + startStatement = -1; + if (command != null) { + setCurrentCommand(command); + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private static void addTableToDependencies(MVTable table, HashSet>> maps) { + for (Index index : table.getIndexes()) { + if (index instanceof MVIndex) { + maps.add(((MVIndex) index).getMVMap()); + } + } + } + + private static void addTableToDependencies(MVTable table, HashSet>> maps, + HashSet processed) { + if (!processed.add(table)) { + return; + } + addTableToDependencies(table, maps); + ArrayList constraints = table.getConstraints(); + if (constraints != null) { + for (Constraint constraint : constraints) { + Table ref = constraint.getTable(); + if (ref != table && ref instanceof MVTable) { + addTableToDependencies((MVTable) ref, maps, processed); + } + } + } + } + + /** + * Mark the statement as completed. This also close all temporary result + * set, and deletes all temporary files held by the result sets. + */ + public void endStatement() { + setCurrentCommand(null); + if (hasTransaction()) { + transaction.markStatementEnd(); + } + startStatement = -1; + } + + /** + * Clear the view cache for this session. + */ + public void clearViewIndexCache() { + viewIndexCache = null; + } + + @Override + public ValueLob addTemporaryLob(ValueLob v) { + LobData lobData = v.getLobData(); + if (lobData instanceof LobDataInMemory) { + return v; + } + int tableId = ((LobDataDatabase) lobData).getTableId(); + if (tableId == LobStorageFrontend.TABLE_RESULT || tableId == LobStorageFrontend.TABLE_TEMP) { + if (temporaryResultLobs == null) { + temporaryResultLobs = new LinkedList<>(); + } + temporaryResultLobs.add(new TimeoutValue(v)); + } else { + if (temporaryLobs == null) { + temporaryLobs = new ArrayList<>(); + } + temporaryLobs.add(v); + } + return v; + } + + @Override + public boolean isRemote() { + return false; + } + + /** + * Mark that the given table needs to be analyzed on commit. + * + * @param table the table + */ + public void markTableForAnalyze(Table table) { + if (tablesToAnalyze == null) { + tablesToAnalyze = new HashSet<>(); + } + tablesToAnalyze.add(table); + } + + public State getState() { + return getBlockingSessionId() != 0 ? State.BLOCKED : state.get(); + } + + public int getBlockingSessionId() { + return transaction == null ? 0 : transaction.getBlockerId(); + } + + @Override + public void onRollback(MVMap> map, Object key, + VersionedValue existingValue, + VersionedValue restoredValue) { + // Here we are relying on the fact that map which backs table's primary index + // has the same name as the table itself + Store store = database.getStore(); + MVTable table = store.getTable(map.getName()); + if (table != null) { + Row oldRow = existingValue == null ? null : (Row) existingValue.getCurrentValue(); + Row newRow = restoredValue == null ? null : (Row) restoredValue.getCurrentValue(); + table.fireAfterRow(this, oldRow, newRow, true); + + if (table.getContainsLargeObject()) { + if (oldRow != null) { + for (int i = 0, len = oldRow.getColumnCount(); i < len; i++) { + Value v = oldRow.getValue(i); + if (v instanceof ValueLob) { + removeAtCommit((ValueLob) v); + } + } + } + if (newRow != null) { + for (int i = 0, len = newRow.getColumnCount(); i < len; i++) { + Value v = newRow.getValue(i); + if (v instanceof ValueLob) { + removeAtCommitStop((ValueLob) v); + } + } + } + } + } + } + + /** + * Represents a savepoint (a position in a transaction to where one can roll + * back to). + */ + public static class Savepoint { + + /** + * The undo log index. + */ + int logIndex; + + /** + * The transaction savepoint id. + */ + long transactionSavepoint; + } + + /** + * An LOB object with a timeout. + */ + public static class TimeoutValue { + + /** + * The time when this object was created. + */ + final long created = System.nanoTime(); + + /** + * The value. + */ + final ValueLob value; + + TimeoutValue(ValueLob v) { + this.value = v; + } + + } + + /** + * Returns the network connection information, or {@code null}. + * + * @return the network connection information, or {@code null} + */ + public NetworkConnectionInfo getNetworkConnectionInfo() { + return networkConnectionInfo; + } + + @Override + public void setNetworkConnectionInfo(NetworkConnectionInfo networkConnectionInfo) { + this.networkConnectionInfo = networkConnectionInfo; + } + + @Override + public ValueTimestampTimeZone currentTimestamp() { + ValueTimestampTimeZone ts = currentTimestamp; + if (ts == null) { + currentTimestamp = ts = DateTimeUtils.currentTimestamp(timeZone, commandStartOrEnd); + } + return ts; + } + + @Override + public Mode getMode() { + return database.getMode(); + } + + @Override + public JavaObjectSerializer getJavaObjectSerializer() { + return database.getJavaObjectSerializer(); + } + + @Override + public IsolationLevel getIsolationLevel() { + return isolationLevel; + } + + @Override + public void setIsolationLevel(IsolationLevel isolationLevel) { + commit(false); + this.isolationLevel = isolationLevel; + } + + /** + * Gets bit set of non-keywords. + * + * @return set of non-keywords, or {@code null} + */ + public BitSet getNonKeywords() { + return nonKeywords; + } + + /** + * Sets bit set of non-keywords. + * + * @param nonKeywords set of non-keywords, or {@code null} + */ + public void setNonKeywords(BitSet nonKeywords) { + this.nonKeywords = nonKeywords; + } + + @Override + public StaticSettings getStaticSettings() { + StaticSettings settings = staticSettings; + if (settings == null) { + DbSettings dbSettings = database.getSettings(); + staticSettings = settings = new StaticSettings(dbSettings.databaseToUpper, dbSettings.databaseToLower, + dbSettings.caseInsensitiveIdentifiers); + } + return settings; + } + + @Override + public DynamicSettings getDynamicSettings() { + return new DynamicSettings(database.getMode(), timeZone); + } + + @Override + public TimeZoneProvider currentTimeZone() { + return timeZone; + } + + /** + * Sets current time zone. + * + * @param timeZone time zone + */ + public void setTimeZone(TimeZoneProvider timeZone) { + if (!timeZone.equals(this.timeZone)) { + this.timeZone = timeZone; + ValueTimestampTimeZone ts = currentTimestamp; + if (ts != null) { + long dateValue = ts.getDateValue(); + long timeNanos = ts.getTimeNanos(); + int offsetSeconds = ts.getTimeZoneOffsetSeconds(); + currentTimestamp = DateTimeUtils.timestampTimeZoneAtOffset(dateValue, timeNanos, offsetSeconds, // + timeZone.getTimeZoneOffsetUTC( + DateTimeUtils.getEpochSeconds(dateValue, timeNanos, offsetSeconds))); + } + modificationId++; + } + } + + /** + * Check if two values are equal with the current comparison mode. + * + * @param a the first value + * @param b the second value + * @return true if both objects are equal + */ + public boolean areEqual(Value a, Value b) { + // can not use equals because ValueDecimal 0.0 is not equal to 0.00. + return a.compareTo(b, this, database.getCompareMode()) == 0; + } + + /** + * Compare two values with the current comparison mode. The values may have + * different data types including NULL. + * + * @param a the first value + * @param b the second value + * @return 0 if both values are equal, -1 if the first value is smaller, and + * 1 otherwise + */ + public int compare(Value a, Value b) { + return a.compareTo(b, this, database.getCompareMode()); + } + + /** + * Compare two values with the current comparison mode. The values may have + * different data types including NULL. + * + * @param a the first value + * @param b the second value + * @param forEquality perform only check for equality (= or <>) + * @return 0 if both values are equal, -1 if the first value is smaller, 1 + * if the second value is larger, {@link Integer#MIN_VALUE} if order + * is not defined due to NULL comparison + */ + public int compareWithNull(Value a, Value b, boolean forEquality) { + return a.compareWithNull(b, forEquality, this, database.getCompareMode()); + } + + /** + * Compare two values with the current comparison mode. The values must be + * of the same type. + * + * @param a the first value + * @param b the second value + * @return 0 if both values are equal, -1 if the first value is smaller, and + * 1 otherwise + */ + public int compareTypeSafe(Value a, Value b) { + return a.compareTypeSafe(b, database.getCompareMode(), this); + } + + /** + * Changes parsing mode of data types with too large length. + * + * @param truncateLargeLength + * {@code true} to truncate to valid bound, {@code false} to + * throw an exception + */ + public void setTruncateLargeLength(boolean truncateLargeLength) { + this.truncateLargeLength = truncateLargeLength; + } + + /** + * Returns parsing mode of data types with too large length. + * + * @return {@code true} if large length is truncated, {@code false} if an + * exception is thrown + */ + public boolean isTruncateLargeLength() { + return truncateLargeLength; + } + + /** + * Changes parsing of a BINARY data type. + * + * @param variableBinary + * {@code true} to parse BINARY as VARBINARY, {@code false} to + * parse it as is + */ + public void setVariableBinary(boolean variableBinary) { + this.variableBinary = variableBinary; + } + + /** + * Returns BINARY data type parsing mode. + * + * @return {@code true} if BINARY should be parsed as VARBINARY, + * {@code false} if it should be parsed as is + */ + public boolean isVariableBinary() { + return variableBinary; + } + + /** + * Changes INFORMATION_SCHEMA content. + * + * @param oldInformationSchema + * {@code true} to have old-style tables in INFORMATION_SCHEMA, + * {@code false} to have modern tables + */ + public void setOldInformationSchema(boolean oldInformationSchema) { + this.oldInformationSchema = oldInformationSchema; + } + + @Override + public boolean isOldInformationSchema() { + return oldInformationSchema; + } + + @Override + public DatabaseMeta getDatabaseMeta() { + return new DatabaseMetaLocal(this); + } + + @Override + public boolean zeroBasedEnums() { + return database.zeroBasedEnums(); + } + + /** + * Enables or disables the quirks mode. + * + * @param quirksMode + * whether quirks mode should be enabled + */ + public void setQuirksMode(boolean quirksMode) { + this.quirksMode = quirksMode; + } + + /** + * Returns whether quirks mode is enabled explicitly or implicitly. + * + * @return {@code true} if database is starting or quirks mode was enabled + * explicitly, {@code false} otherwise + */ + public boolean isQuirksMode() { + return quirksMode || database.isStarting(); + } + + @Override + public Session setThreadLocalSession() { + Session oldSession = THREAD_LOCAL_SESSION.get(); + THREAD_LOCAL_SESSION.set(this); + return oldSession; + } + + @Override + public void resetThreadLocalSession(Session oldSession) { + if (oldSession == null) { + THREAD_LOCAL_SESSION.remove(); + } else { + THREAD_LOCAL_SESSION.set(oldSession); + } + } + +} diff --git a/h2/src/main/org/h2/engine/SessionRemote.java b/h2/src/main/org/h2/engine/SessionRemote.java new file mode 100644 index 0000000..6045e11 --- /dev/null +++ b/h2/src/main/org/h2/engine/SessionRemote.java @@ -0,0 +1,987 @@ +/* + * 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.engine; + +import java.io.IOException; +import java.net.Socket; +import java.sql.SQLException; +import java.util.ArrayList; +import org.h2.api.DatabaseEventListener; +import org.h2.api.ErrorCode; +import org.h2.api.JavaObjectSerializer; +import org.h2.command.CommandInterface; +import org.h2.command.CommandRemote; +import org.h2.command.dml.SetTypes; +import org.h2.engine.Mode.ModeEnum; +import org.h2.expression.ParameterInterface; +import org.h2.jdbc.JdbcException; +import org.h2.jdbc.meta.DatabaseMeta; +import org.h2.jdbc.meta.DatabaseMetaLegacy; +import org.h2.jdbc.meta.DatabaseMetaRemote; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.message.TraceSystem; +import org.h2.result.ResultInterface; +import org.h2.store.DataHandler; +import org.h2.store.FileStore; +import org.h2.store.LobStorageFrontend; +import org.h2.store.fs.FileUtils; +import org.h2.util.DateTimeUtils; +import org.h2.util.JdbcUtils; +import org.h2.util.MathUtils; +import org.h2.util.NetUtils; +import org.h2.util.NetworkConnectionInfo; +import org.h2.util.SmallLRUCache; +import org.h2.util.StringUtils; +import org.h2.util.TempFileDeleter; +import org.h2.util.TimeZoneProvider; +import org.h2.util.Utils; +import org.h2.value.CompareMode; +import org.h2.value.Transfer; +import org.h2.value.Value; +import org.h2.value.ValueInteger; +import org.h2.value.ValueLob; +import org.h2.value.ValueTimestampTimeZone; +import org.h2.value.ValueVarchar; + +/** + * The client side part of a session when using the server mode. This object + * communicates with a Session on the server side. + */ +public final class SessionRemote extends Session implements DataHandler { + + public static final int SESSION_PREPARE = 0; + public static final int SESSION_CLOSE = 1; + public static final int COMMAND_EXECUTE_QUERY = 2; + public static final int COMMAND_EXECUTE_UPDATE = 3; + public static final int COMMAND_CLOSE = 4; + public static final int RESULT_FETCH_ROWS = 5; + public static final int RESULT_RESET = 6; + public static final int RESULT_CLOSE = 7; + public static final int COMMAND_COMMIT = 8; + public static final int CHANGE_ID = 9; + public static final int COMMAND_GET_META_DATA = 10; + // 11 was used for SESSION_PREPARE_READ_PARAMS + public static final int SESSION_SET_ID = 12; + public static final int SESSION_CANCEL_STATEMENT = 13; + public static final int SESSION_CHECK_KEY = 14; + public static final int SESSION_SET_AUTOCOMMIT = 15; + public static final int SESSION_HAS_PENDING_TRANSACTION = 16; + public static final int LOB_READ = 17; + public static final int SESSION_PREPARE_READ_PARAMS2 = 18; + public static final int GET_JDBC_META = 19; + + public static final int STATUS_ERROR = 0; + public static final int STATUS_OK = 1; + public static final int STATUS_CLOSED = 2; + public static final int STATUS_OK_STATE_CHANGED = 3; + + private TraceSystem traceSystem; + private Trace trace; + private ArrayList transferList = Utils.newSmallArrayList(); + private int nextId; + private boolean autoCommit = true; + private ConnectionInfo connectionInfo; + private String databaseName; + private String cipher; + private byte[] fileEncryptionKey; + private final Object lobSyncObject = new Object(); + private String sessionId; + private int clientVersion; + private boolean autoReconnect; + private int lastReconnect; + private Session embedded; + private DatabaseEventListener eventListener; + private LobStorageFrontend lobStorage; + private boolean cluster; + private TempFileDeleter tempFileDeleter; + + private JavaObjectSerializer javaObjectSerializer; + + private final CompareMode compareMode = CompareMode.getInstance(null, 0); + + private final boolean oldInformationSchema; + + private String currentSchemaName; + + private volatile DynamicSettings dynamicSettings; + + public SessionRemote(ConnectionInfo ci) { + this.connectionInfo = ci; + oldInformationSchema = ci.getProperty("OLD_INFORMATION_SCHEMA", false); + } + + @Override + public ArrayList getClusterServers() { + ArrayList serverList = new ArrayList<>(); + for (Transfer transfer : transferList) { + serverList.add(transfer.getSocket().getInetAddress(). + getHostAddress() + ":" + + transfer.getSocket().getPort()); + } + return serverList; + } + + private Transfer initTransfer(ConnectionInfo ci, String db, String server) + throws IOException { + Socket socket = NetUtils.createSocket(server, Constants.DEFAULT_TCP_PORT, ci.isSSL(), + ci.getProperty("NETWORK_TIMEOUT", 0)); + Transfer trans = new Transfer(this, socket); + trans.setSSL(ci.isSSL()); + trans.init(); + trans.writeInt(Constants.TCP_PROTOCOL_VERSION_MIN_SUPPORTED); + trans.writeInt(Constants.TCP_PROTOCOL_VERSION_MAX_SUPPORTED); + trans.writeString(db); + trans.writeString(ci.getOriginalURL()); + trans.writeString(ci.getUserName()); + trans.writeBytes(ci.getUserPasswordHash()); + trans.writeBytes(ci.getFilePasswordHash()); + String[] keys = ci.getKeys(); + trans.writeInt(keys.length); + for (String key : keys) { + trans.writeString(key).writeString(ci.getProperty(key)); + } + try { + done(trans); + clientVersion = trans.readInt(); + trans.setVersion(clientVersion); + if (ci.getFileEncryptionKey() != null) { + trans.writeBytes(ci.getFileEncryptionKey()); + } + trans.writeInt(SessionRemote.SESSION_SET_ID); + trans.writeString(sessionId); + if (clientVersion >= Constants.TCP_PROTOCOL_VERSION_20) { + TimeZoneProvider timeZone = ci.getTimeZone(); + if (timeZone == null) { + timeZone = DateTimeUtils.getTimeZone(); + } + trans.writeString(timeZone.getId()); + } + done(trans); + autoCommit = trans.readBoolean(); + return trans; + } catch (DbException e) { + trans.close(); + throw e; + } + } + + @Override + public boolean hasPendingTransaction() { + for (int i = 0, count = 0; i < transferList.size(); i++) { + Transfer transfer = transferList.get(i); + try { + traceOperation("SESSION_HAS_PENDING_TRANSACTION", 0); + transfer.writeInt( + SessionRemote.SESSION_HAS_PENDING_TRANSACTION); + done(transfer); + return transfer.readInt() != 0; + } catch (IOException e) { + removeServer(e, i--, ++count); + } + } + return true; + } + + @Override + public void cancel() { + // this method is called when closing the connection + // the statement that is currently running is not canceled in this case + // however Statement.cancel is supported + } + + /** + * Cancel the statement with the given id. + * + * @param id the statement id + */ + public void cancelStatement(int id) { + for (Transfer transfer : transferList) { + try { + Transfer trans = transfer.openNewConnection(); + trans.init(); + trans.writeInt(clientVersion); + trans.writeInt(clientVersion); + trans.writeString(null); + trans.writeString(null); + trans.writeString(sessionId); + trans.writeInt(SessionRemote.SESSION_CANCEL_STATEMENT); + trans.writeInt(id); + trans.close(); + } catch (IOException e) { + trace.debug(e, "could not cancel statement"); + } + } + } + + private void checkClusterDisableAutoCommit(String serverList) { + if (autoCommit && transferList.size() > 1) { + setAutoCommitSend(false); + CommandInterface c = prepareCommand( + "SET CLUSTER " + serverList, Integer.MAX_VALUE); + // this will set autoCommit to false + c.executeUpdate(null); + // so we need to switch it on + autoCommit = true; + cluster = true; + } + } + + /** + * Returns the TCP protocol version of remote connection. + * + * @return the TCP protocol version + */ + public int getClientVersion() { + return clientVersion; + } + + @Override + public boolean getAutoCommit() { + return autoCommit; + } + + @Override + public void setAutoCommit(boolean autoCommit) { + if (!cluster) { + setAutoCommitSend(autoCommit); + } + this.autoCommit = autoCommit; + } + + public void setAutoCommitFromServer(boolean autoCommit) { + if (cluster) { + if (autoCommit) { + // the user executed SET AUTOCOMMIT TRUE + setAutoCommitSend(false); + this.autoCommit = true; + } + } else { + this.autoCommit = autoCommit; + } + } + + private synchronized void setAutoCommitSend(boolean autoCommit) { + for (int i = 0, count = 0; i < transferList.size(); i++) { + Transfer transfer = transferList.get(i); + try { + traceOperation("SESSION_SET_AUTOCOMMIT", autoCommit ? 1 : 0); + transfer.writeInt(SessionRemote.SESSION_SET_AUTOCOMMIT). + writeBoolean(autoCommit); + done(transfer); + } catch (IOException e) { + removeServer(e, i--, ++count); + } + } + } + + /** + * Calls COMMIT if the session is in cluster mode. + */ + public void autoCommitIfCluster() { + if (autoCommit && cluster) { + // server side auto commit is off because of race conditions + // (update set id=1 where id=0, but update set id=2 where id=0 is + // faster) + for (int i = 0, count = 0; i < transferList.size(); i++) { + Transfer transfer = transferList.get(i); + try { + traceOperation("COMMAND_COMMIT", 0); + transfer.writeInt(SessionRemote.COMMAND_COMMIT); + done(transfer); + } catch (IOException e) { + removeServer(e, i--, ++count); + } + } + } + } + + private String getFilePrefix(String dir) { + StringBuilder buff = new StringBuilder(dir); + buff.append('/'); + for (int i = 0; i < databaseName.length(); i++) { + char ch = databaseName.charAt(i); + if (Character.isLetterOrDigit(ch)) { + buff.append(ch); + } else { + buff.append('_'); + } + } + return buff.toString(); + } + + /** + * Open a new (remote or embedded) session. + * + * @param openNew whether to open a new session in any case + * @return the session + */ + public Session connectEmbeddedOrServer(boolean openNew) { + ConnectionInfo ci = connectionInfo; + if (ci.isRemote()) { + connectServer(ci); + return this; + } + boolean autoServerMode = ci.getProperty("AUTO_SERVER", false); + ConnectionInfo backup = null; + try { + if (autoServerMode) { + backup = ci.clone(); + connectionInfo = ci.clone(); + } + if (openNew) { + ci.setProperty("OPEN_NEW", "true"); + } + return Engine.createSession(ci); + } catch (Exception re) { + DbException e = DbException.convert(re); + if (e.getErrorCode() == ErrorCode.DATABASE_ALREADY_OPEN_1) { + if (autoServerMode) { + String serverKey = ((JdbcException) e.getSQLException()).getSQL(); + if (serverKey != null) { + backup.setServerKey(serverKey); + // OPEN_NEW must be removed now, otherwise + // opening a session with AUTO_SERVER fails + // if another connection is already open + backup.removeProperty("OPEN_NEW", null); + connectServer(backup); + return this; + } + } + } + throw e; + } + } + + private void connectServer(ConnectionInfo ci) { + String name = ci.getName(); + if (name.startsWith("//")) { + name = name.substring("//".length()); + } + int idx = name.indexOf('/'); + if (idx < 0) { + throw ci.getFormatException(); + } + databaseName = name.substring(idx + 1); + String server = name.substring(0, idx); + traceSystem = new TraceSystem(null); + String traceLevelFile = ci.getProperty( + SetTypes.TRACE_LEVEL_FILE, null); + if (traceLevelFile != null) { + int level = Integer.parseInt(traceLevelFile); + String prefix = getFilePrefix( + SysProperties.CLIENT_TRACE_DIRECTORY); + try { + traceSystem.setLevelFile(level); + if (level > 0 && level < 4) { + String file = FileUtils.createTempFile(prefix, + Constants.SUFFIX_TRACE_FILE, false); + traceSystem.setFileName(file); + } + } catch (IOException e) { + throw DbException.convertIOException(e, prefix); + } + } + String traceLevelSystemOut = ci.getProperty( + SetTypes.TRACE_LEVEL_SYSTEM_OUT, null); + if (traceLevelSystemOut != null) { + int level = Integer.parseInt(traceLevelSystemOut); + traceSystem.setLevelSystemOut(level); + } + trace = traceSystem.getTrace(Trace.JDBC); + String serverList = null; + if (server.indexOf(',') >= 0) { + serverList = StringUtils.quoteStringSQL(server); + ci.setProperty("CLUSTER", Constants.CLUSTERING_ENABLED); + } + autoReconnect = ci.getProperty("AUTO_RECONNECT", false); + // AUTO_SERVER implies AUTO_RECONNECT + boolean autoServer = ci.getProperty("AUTO_SERVER", false); + if (autoServer && serverList != null) { + throw DbException + .getUnsupportedException("autoServer && serverList != null"); + } + autoReconnect |= autoServer; + if (autoReconnect) { + String className = ci.getProperty("DATABASE_EVENT_LISTENER"); + if (className != null) { + className = StringUtils.trim(className, true, true, "'"); + try { + eventListener = (DatabaseEventListener) JdbcUtils + .loadUserClass(className).getDeclaredConstructor().newInstance(); + } catch (Throwable e) { + throw DbException.convert(e); + } + } + } + cipher = ci.getProperty("CIPHER"); + if (cipher != null) { + fileEncryptionKey = MathUtils.secureRandomBytes(32); + } + String[] servers = StringUtils.arraySplit(server, ',', true); + int len = servers.length; + transferList.clear(); + sessionId = StringUtils.convertBytesToHex(MathUtils.secureRandomBytes(32)); + // TODO cluster: support more than 2 connections + boolean switchOffCluster = false; + try { + for (String s : servers) { + try { + Transfer trans = initTransfer(ci, databaseName, s); + transferList.add(trans); + } catch (IOException e) { + if (len == 1) { + throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, e, e + ": " + s); + } + switchOffCluster = true; + } + } + checkClosed(); + if (switchOffCluster) { + switchOffCluster(); + } + checkClusterDisableAutoCommit(serverList); + } catch (DbException e) { + traceSystem.close(); + throw e; + } + getDynamicSettings(); + } + + private void switchOffCluster() { + CommandInterface ci = prepareCommand("SET CLUSTER ''", Integer.MAX_VALUE); + ci.executeUpdate(null); + } + + /** + * Remove a server from the list of cluster nodes and disables the cluster + * mode. + * + * @param e the exception (used for debugging) + * @param i the index of the server to remove + * @param count the retry count index + */ + public void removeServer(IOException e, int i, int count) { + trace.debug(e, "removing server because of exception"); + transferList.remove(i); + if (transferList.isEmpty() && autoReconnect(count)) { + return; + } + checkClosed(); + switchOffCluster(); + } + + @Override + public synchronized CommandInterface prepareCommand(String sql, int fetchSize) { + checkClosed(); + return new CommandRemote(this, transferList, sql, fetchSize); + } + + /** + * Automatically re-connect if necessary and if configured to do so. + * + * @param count the retry count index + * @return true if reconnected + */ + private boolean autoReconnect(int count) { + if (!isClosed()) { + return false; + } + if (!autoReconnect) { + return false; + } + if (!cluster && !autoCommit) { + return false; + } + if (count > SysProperties.MAX_RECONNECT) { + return false; + } + lastReconnect++; + while (true) { + try { + embedded = connectEmbeddedOrServer(false); + break; + } catch (DbException e) { + if (e.getErrorCode() != ErrorCode.DATABASE_IS_IN_EXCLUSIVE_MODE) { + throw e; + } + // exclusive mode: re-try endlessly + try { + Thread.sleep(500); + } catch (Exception e2) { + // ignore + } + } + } + if (embedded == this) { + // connected to a server somewhere else + embedded = null; + } else { + // opened an embedded connection now - + // must connect to this database in server mode + // unfortunately + connectEmbeddedOrServer(true); + } + recreateSessionState(); + if (eventListener != null) { + eventListener.setProgress(DatabaseEventListener.STATE_RECONNECTED, + databaseName, count, SysProperties.MAX_RECONNECT); + } + return true; + } + + /** + * Check if this session is closed and throws an exception if so. + * + * @throws DbException if the session is closed + */ + public void checkClosed() { + if (isClosed()) { + throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "session closed"); + } + } + + @Override + public void close() { + RuntimeException closeError = null; + if (transferList != null) { + synchronized (this) { + for (Transfer transfer : transferList) { + try { + traceOperation("SESSION_CLOSE", 0); + transfer.writeInt(SessionRemote.SESSION_CLOSE); + done(transfer); + transfer.close(); + } catch (RuntimeException e) { + trace.error(e, "close"); + closeError = e; + } catch (Exception e) { + trace.error(e, "close"); + } + } + } + transferList = null; + } + traceSystem.close(); + if (embedded != null) { + embedded.close(); + embedded = null; + } + if (closeError != null) { + throw closeError; + } + } + + @Override + public Trace getTrace() { + return traceSystem.getTrace(Trace.JDBC); + } + + public int getNextId() { + return nextId++; + } + + public int getCurrentId() { + return nextId; + } + + /** + * Called to flush the output after data has been sent to the server and + * just before receiving data. This method also reads the status code from + * the server and throws any exception the server sent. + * + * @param transfer the transfer object + * @throws DbException if the server sent an exception + * @throws IOException if there is a communication problem between client + * and server + */ + public void done(Transfer transfer) throws IOException { + transfer.flush(); + int status = transfer.readInt(); + switch (status) { + case STATUS_ERROR: + throw readException(transfer); + case STATUS_OK: + break; + case STATUS_CLOSED: + transferList = null; + break; + case STATUS_OK_STATE_CHANGED: + sessionStateChanged = true; + currentSchemaName = null; + dynamicSettings = null; + break; + default: + throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "unexpected status " + status); + } + } + + /** + * Reads an exception. + * + * @param transfer + * the transfer object + * @return the exception + * @throws IOException + * on I/O exception + */ + public static DbException readException(Transfer transfer) throws IOException { + String sqlstate = transfer.readString(); + String message = transfer.readString(); + String sql = transfer.readString(); + int errorCode = transfer.readInt(); + String stackTrace = transfer.readString(); + SQLException s = DbException.getJdbcSQLException(message, sql, sqlstate, errorCode, null, stackTrace); + if (errorCode == ErrorCode.CONNECTION_BROKEN_1) { + // allow re-connect + throw new IOException(s.toString(), s); + } + return DbException.convert(s); + } + + /** + * Returns true if the connection was opened in cluster mode. + * + * @return true if it is + */ + public boolean isClustered() { + return cluster; + } + + @Override + public boolean isClosed() { + return transferList == null || transferList.isEmpty(); + } + + /** + * Write the operation to the trace system if debug trace is enabled. + * + * @param operation the operation performed + * @param id the id of the operation + */ + public void traceOperation(String operation, int id) { + if (trace.isDebugEnabled()) { + trace.debug("{0} {1}", operation, id); + } + } + + @Override + public void checkPowerOff() { + // ok + } + + @Override + public void checkWritingAllowed() { + // ok + } + + @Override + public String getDatabasePath() { + return ""; + } + + @Override + public int getMaxLengthInplaceLob() { + return SysProperties.LOB_CLIENT_MAX_SIZE_MEMORY; + } + + @Override + public FileStore openFile(String name, String mode, boolean mustExist) { + if (mustExist && !FileUtils.exists(name)) { + throw DbException.get(ErrorCode.FILE_NOT_FOUND_1, name); + } + FileStore store; + if (cipher == null) { + store = FileStore.open(this, name, mode); + } else { + store = FileStore.open(this, name, mode, cipher, fileEncryptionKey, 0); + } + store.setCheckedWriting(false); + try { + store.init(); + } catch (DbException e) { + store.closeSilently(); + throw e; + } + return store; + } + + @Override + public DataHandler getDataHandler() { + return this; + } + + @Override + public Object getLobSyncObject() { + return lobSyncObject; + } + + @Override + public SmallLRUCache getLobFileListCache() { + return null; + } + + public int getLastReconnect() { + return lastReconnect; + } + + @Override + public TempFileDeleter getTempFileDeleter() { + if (tempFileDeleter == null) { + tempFileDeleter = TempFileDeleter.getInstance(); + } + return tempFileDeleter; + } + + @Override + public LobStorageFrontend getLobStorage() { + if (lobStorage == null) { + lobStorage = new LobStorageFrontend(this); + } + return lobStorage; + } + + @Override + public synchronized int readLob(long lobId, byte[] hmac, long offset, + byte[] buff, int off, int length) { + checkClosed(); + for (int i = 0, count = 0; i < transferList.size(); i++) { + Transfer transfer = transferList.get(i); + try { + traceOperation("LOB_READ", (int) lobId); + transfer.writeInt(SessionRemote.LOB_READ); + transfer.writeLong(lobId); + transfer.writeBytes(hmac); + transfer.writeLong(offset); + transfer.writeInt(length); + done(transfer); + length = transfer.readInt(); + if (length <= 0) { + return length; + } + transfer.readBytes(buff, off, length); + return length; + } catch (IOException e) { + removeServer(e, i--, ++count); + } + } + return 1; + } + + @Override + public JavaObjectSerializer getJavaObjectSerializer() { + if (dynamicSettings == null) { + getDynamicSettings(); + } + return javaObjectSerializer; + } + + @Override + public ValueLob addTemporaryLob(ValueLob v) { + // do nothing + return v; + } + + @Override + public CompareMode getCompareMode() { + return compareMode; + } + + @Override + public boolean isRemote() { + return true; + } + + @Override + public String getCurrentSchemaName() { + String schema = currentSchemaName; + if (schema == null) { + synchronized (this) { + try (CommandInterface command = prepareCommand("CALL SCHEMA()", 1); + ResultInterface result = command.executeQuery(1, false)) { + result.next(); + currentSchemaName = schema = result.currentRow()[0].getString(); + } + } + } + return schema; + } + + @Override + public synchronized void setCurrentSchemaName(String schema) { + currentSchemaName = null; + try (CommandInterface command = prepareCommand( + StringUtils.quoteIdentifier(new StringBuilder("SET SCHEMA "), schema).toString(), 0)) { + command.executeUpdate(null); + currentSchemaName = schema; + } + } + + @Override + public void setNetworkConnectionInfo(NetworkConnectionInfo networkConnectionInfo) { + // Not supported + } + + @Override + public IsolationLevel getIsolationLevel() { + if (clientVersion >= Constants.TCP_PROTOCOL_VERSION_19) { + try (CommandInterface command = prepareCommand(!isOldInformationSchema() + ? "SELECT ISOLATION_LEVEL FROM INFORMATION_SCHEMA.SESSIONS WHERE SESSION_ID = SESSION_ID()" + : "SELECT ISOLATION_LEVEL FROM INFORMATION_SCHEMA.SESSIONS WHERE ID = SESSION_ID()", 1); + ResultInterface result = command.executeQuery(1, false)) { + result.next(); + return IsolationLevel.fromSql(result.currentRow()[0].getString()); + } + } else { + try (CommandInterface command = prepareCommand("CALL LOCK_MODE()", 1); + ResultInterface result = command.executeQuery(1, false)) { + result.next(); + return IsolationLevel.fromLockMode(result.currentRow()[0].getInt()); + } + } + } + + @Override + public void setIsolationLevel(IsolationLevel isolationLevel) { + if (clientVersion >= Constants.TCP_PROTOCOL_VERSION_19) { + try (CommandInterface command = prepareCommand( + "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL " + isolationLevel.getSQL(), 0)) { + command.executeUpdate(null); + } + } else { + try (CommandInterface command = prepareCommand("SET LOCK_MODE ?", 0)) { + command.getParameters().get(0).setValue(ValueInteger.get(isolationLevel.getLockMode()), false); + command.executeUpdate(null); + } + } + } + + @Override + public StaticSettings getStaticSettings() { + StaticSettings settings = staticSettings; + if (settings == null) { + boolean databaseToUpper = true, databaseToLower = false, caseInsensitiveIdentifiers = false; + try (CommandInterface command = getSettingsCommand(" IN (?, ?, ?)")) { + ArrayList parameters = command.getParameters(); + parameters.get(0).setValue(ValueVarchar.get("DATABASE_TO_UPPER"), false); + parameters.get(1).setValue(ValueVarchar.get("DATABASE_TO_LOWER"), false); + parameters.get(2).setValue(ValueVarchar.get("CASE_INSENSITIVE_IDENTIFIERS"), false); + try (ResultInterface result = command.executeQuery(0, false)) { + while (result.next()) { + Value[] row = result.currentRow(); + String value = row[1].getString(); + switch (row[0].getString()) { + case "DATABASE_TO_UPPER": + databaseToUpper = Boolean.valueOf(value); + break; + case "DATABASE_TO_LOWER": + databaseToLower = Boolean.valueOf(value); + break; + case "CASE_INSENSITIVE_IDENTIFIERS": + caseInsensitiveIdentifiers = Boolean.valueOf(value); + } + } + } + } + if (clientVersion < Constants.TCP_PROTOCOL_VERSION_18) { + caseInsensitiveIdentifiers = !databaseToUpper; + } + staticSettings = settings = new StaticSettings(databaseToUpper, databaseToLower, + caseInsensitiveIdentifiers); + } + return settings; + } + + @Override + public DynamicSettings getDynamicSettings() { + DynamicSettings settings = dynamicSettings; + if (settings == null) { + String modeName = ModeEnum.REGULAR.name(); + TimeZoneProvider timeZone = DateTimeUtils.getTimeZone(); + String javaObjectSerializerName = null; + try (CommandInterface command = getSettingsCommand(" IN (?, ?, ?)")) { + ArrayList parameters = command.getParameters(); + parameters.get(0).setValue(ValueVarchar.get("MODE"), false); + parameters.get(1).setValue(ValueVarchar.get("TIME ZONE"), false); + parameters.get(2).setValue(ValueVarchar.get("JAVA_OBJECT_SERIALIZER"), false); + try (ResultInterface result = command.executeQuery(0, false)) { + while (result.next()) { + Value[] row = result.currentRow(); + String value = row[1].getString(); + switch (row[0].getString()) { + case "MODE": + modeName = value; + break; + case "TIME ZONE": + timeZone = TimeZoneProvider.ofId(value); + break; + case "JAVA_OBJECT_SERIALIZER": + javaObjectSerializerName = value; + } + } + } + } + Mode mode = Mode.getInstance(modeName); + if (mode == null) { + mode = Mode.getRegular(); + } + dynamicSettings = settings = new DynamicSettings(mode, timeZone); + if (javaObjectSerializerName != null + && !(javaObjectSerializerName = javaObjectSerializerName.trim()).isEmpty() + && !javaObjectSerializerName.equals("null")) { + try { + javaObjectSerializer = (JavaObjectSerializer) JdbcUtils + .loadUserClass(javaObjectSerializerName).getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw DbException.convert(e); + } + } else { + javaObjectSerializer = null; + } + } + return settings; + } + + private CommandInterface getSettingsCommand(String args) { + return prepareCommand( + (!isOldInformationSchema() + ? "SELECT SETTING_NAME, SETTING_VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME" + : "SELECT NAME, `VALUE` FROM INFORMATION_SCHEMA.SETTINGS WHERE NAME") + args, + Integer.MAX_VALUE); + } + + @Override + public ValueTimestampTimeZone currentTimestamp() { + return DateTimeUtils.currentTimestamp(getDynamicSettings().timeZone); + } + + @Override + public TimeZoneProvider currentTimeZone() { + return getDynamicSettings().timeZone; + } + + @Override + public Mode getMode() { + return getDynamicSettings().mode; + } + + @Override + public DatabaseMeta getDatabaseMeta() { + return clientVersion >= Constants.TCP_PROTOCOL_VERSION_20 ? new DatabaseMetaRemote(this, transferList) + : new DatabaseMetaLegacy(this); + } + + @Override + public boolean isOldInformationSchema() { + return oldInformationSchema || clientVersion < Constants.TCP_PROTOCOL_VERSION_20; + } + + @Override + public boolean zeroBasedEnums() { + return false; + } + +} diff --git a/h2/src/main/org/h2/engine/Setting.java b/h2/src/main/org/h2/engine/Setting.java new file mode 100644 index 0000000..3d8cc24 --- /dev/null +++ b/h2/src/main/org/h2/engine/Setting.java @@ -0,0 +1,83 @@ +/* + * 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.engine; + +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.table.Table; + +/** + * A persistent database setting. + */ +public final class Setting extends DbObject { + + private int intValue; + private String stringValue; + + public Setting(Database database, int id, String settingName) { + super(database, id, settingName, Trace.SETTING); + } + + @Override + public String getSQL(int sqlFlags) { + return getName(); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return builder.append(getName()); + } + + public void setIntValue(int value) { + intValue = value; + } + + public int getIntValue() { + return intValue; + } + + public void setStringValue(String value) { + stringValue = value; + } + + 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 "); + getSQL(buff, DEFAULT_SQL_FLAGS).append(' '); + if (stringValue != null) { + buff.append(stringValue); + } else { + buff.append(intValue); + } + return buff.toString(); + } + + @Override + public int getType() { + return DbObject.SETTING; + } + + @Override + public void removeChildrenAndResources(SessionLocal session) { + database.removeMeta(session, getId()); + invalidate(); + } + + @Override + public void checkRename() { + throw DbException.getUnsupportedException("RENAME"); + } + +} diff --git a/h2/src/main/org/h2/engine/SettingsBase.java b/h2/src/main/org/h2/engine/SettingsBase.java new file mode 100644 index 0000000..bfca552 --- /dev/null +++ b/h2/src/main/org/h2/engine/SettingsBase.java @@ -0,0 +1,134 @@ +/* + * 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.engine; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.h2.api.ErrorCode; +import org.h2.message.DbException; +import org.h2.util.Utils; + +/** + * The base class for settings. + */ +public class SettingsBase { + + private final HashMap settings; + + protected SettingsBase(HashMap s) { + this.settings = s; + } + + /** + * Get the setting for the given key. + * + * @param key the key + * @param defaultValue the default value + * @return the setting + */ + protected boolean get(String key, boolean defaultValue) { + String s = get(key, Boolean.toString(defaultValue)); + try { + return Utils.parseBoolean(s, defaultValue, true); + } catch (IllegalArgumentException e) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, + e, "key:" + key + " value:" + s); + } + } + + /** + * Set an entry in the key-value pair. + * + * @param key the key + * @param value the value + */ + void set(String key, boolean value) { + settings.put(key, Boolean.toString(value)); + } + + /** + * Get the setting for the given key. + * + * @param key the key + * @param defaultValue the default value + * @return the setting + */ + protected int get(String key, int defaultValue) { + String s = get(key, Integer.toString(defaultValue)); + try { + return Integer.decode(s); + } catch (NumberFormatException e) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, + e, "key:" + key + " value:" + s); + } + } + + /** + * Get the setting for the given key. + * + * @param key the key + * @param defaultValue the default value + * @return the setting + */ + protected String get(String key, String defaultValue) { + String v = settings.get(key); + if (v != null) { + return v; + } + StringBuilder buff = new StringBuilder("h2."); + boolean nextUpper = false; + for (int i = 0, l = key.length(); i < l; i++) { + char c = key.charAt(i); + if (c == '_') { + nextUpper = true; + } else { + // Character.toUpperCase / toLowerCase ignores the locale + buff.append(nextUpper ? Character.toUpperCase(c) : Character.toLowerCase(c)); + nextUpper = false; + } + } + String sysProperty = buff.toString(); + v = Utils.getProperty(sysProperty, defaultValue); + settings.put(key, v); + return v; + } + + /** + * Check if the settings contains the given key. + * + * @param k the key + * @return true if they do + */ + protected boolean containsKey(String k) { + return settings.containsKey(k); + } + + /** + * Get all settings. + * + * @return the settings + */ + public HashMap getSettings() { + return settings; + } + + /** + * Get all settings in alphabetical order. + * + * @return the settings + */ + public Entry[] getSortedSettings() { + @SuppressWarnings("unchecked") + Map.Entry[] entries = settings.entrySet().toArray(new Map.Entry[0]); + Arrays.sort(entries, Comparator.comparing(Entry::getKey)); + return entries; + } + +} diff --git a/h2/src/main/org/h2/engine/SysProperties.java b/h2/src/main/org/h2/engine/SysProperties.java new file mode 100644 index 0000000..bf07188 --- /dev/null +++ b/h2/src/main/org/h2/engine/SysProperties.java @@ -0,0 +1,449 @@ +/* + * 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.engine; + +import org.h2.util.MathUtils; +import org.h2.util.Utils; + +/** + * The constants defined in this class are initialized from system properties. + * Some system properties are per machine settings, and others are as a last + * resort and temporary solution to work around a problem in the application or + * database engine. Also, there are system properties to enable features that + * are not yet fully tested or that are not backward compatible. + *

+ * System properties can be set when starting the virtual machine: + *

+ * + *
+ * java -Dh2.baseDir=/temp
+ * 
+ * + * They can be set within the application, but this must be done before loading + * any classes of this database (before loading the JDBC driver): + * + *
+ * System.setProperty("h2.baseDir", "/temp");
+ * 
+ */ +public class SysProperties { + + /** + * INTERNAL + */ + public static final String H2_SCRIPT_DIRECTORY = "h2.scriptDirectory"; + + /** + * INTERNAL + */ + public static final String H2_BROWSER = "h2.browser"; + + /** + * System property user.home (empty string if not set). + * It is usually set by the system, and used as a replacement for ~ in file + * names. + */ + public static final String USER_HOME = + Utils.getProperty("user.home", ""); + + /** + * System property h2.allowedClasses (default: *). + * Comma separated list of class names or prefixes. + */ + public static final String ALLOWED_CLASSES = + Utils.getProperty("h2.allowedClasses", "*"); + + /** + * System property h2.enableAnonymousTLS (default: true). + * When using TLS connection, the anonymous cipher suites should be enabled. + */ + public static final boolean ENABLE_ANONYMOUS_TLS = + Utils.getProperty("h2.enableAnonymousTLS", true); + + /** + * System property h2.bindAddress (default: null). + * The bind address to use. + */ + public static final String BIND_ADDRESS = + Utils.getProperty("h2.bindAddress", null); + + /** + * System property h2.check + * (default: true for JDK/JRE, false for Android). + * Optional additional checks in the database engine. + */ + public static final boolean CHECK = + Utils.getProperty("h2.check", !"0.9".equals(Utils.getProperty("java.specification.version", null))); + + /** + * System property h2.clientTraceDirectory (default: + * trace.db/). + * Directory where the trace files of the JDBC client are stored (only for + * client / server). + */ + public static final String CLIENT_TRACE_DIRECTORY = + Utils.getProperty("h2.clientTraceDirectory", "trace.db/"); + + /** + * System property h2.collatorCacheSize (default: 3 + * 2000). + * The cache size for collation keys (in elements). Used when a collator has + * been set for the database. + */ + public static final int COLLATOR_CACHE_SIZE = + Utils.getProperty("h2.collatorCacheSize", 32_000); + + /** + * System property h2.consoleTableIndexes + * (default: 100). + * Up to this many tables, the column type and indexes are listed. + */ + public static final int CONSOLE_MAX_TABLES_LIST_INDEXES = + Utils.getProperty("h2.consoleTableIndexes", 100); + + /** + * System property h2.consoleTableColumns + * (default: 500). + * Up to this many tables, the column names are listed. + */ + public static final int CONSOLE_MAX_TABLES_LIST_COLUMNS = + Utils.getProperty("h2.consoleTableColumns", 500); + + /** + * System property h2.consoleProcedureColumns + * (default: 500). + * Up to this many procedures, the column names are listed. + */ + public static final int CONSOLE_MAX_PROCEDURES_LIST_COLUMNS = + Utils.getProperty("h2.consoleProcedureColumns", 300); + + /** + * System property h2.consoleStream (default: true). + * H2 Console: stream query results. + */ + public static final boolean CONSOLE_STREAM = + Utils.getProperty("h2.consoleStream", true); + + /** + * System property h2.consoleTimeout (default: 1800000). + * H2 Console: session timeout in milliseconds. The default is 30 minutes. + */ + public static final int CONSOLE_TIMEOUT = + Utils.getProperty("h2.consoleTimeout", 30 * 60 * 1000); + + /** + * System property h2.dataSourceTraceLevel (default: 1). + * The trace level of the data source implementation. Default is 1 for + * error. + */ + public static final int DATASOURCE_TRACE_LEVEL = + Utils.getProperty("h2.dataSourceTraceLevel", 1); + + /** + * System property h2.delayWrongPasswordMin + * (default: 250). + * The minimum delay in milliseconds before an exception is thrown for using + * the wrong user name or password. This slows down brute force attacks. The + * delay is reset to this value after a successful login. Unsuccessful + * logins will double the time until DELAY_WRONG_PASSWORD_MAX. + * To disable the delay, set this system property to 0. + */ + public static final int DELAY_WRONG_PASSWORD_MIN = + Utils.getProperty("h2.delayWrongPasswordMin", 250); + + /** + * System property h2.delayWrongPasswordMax + * (default: 4000). + * The maximum delay in milliseconds before an exception is thrown for using + * the wrong user name or password. This slows down brute force attacks. The + * delay is reset after a successful login. The value 0 means there is no + * maximum delay. + */ + public static final int DELAY_WRONG_PASSWORD_MAX = + Utils.getProperty("h2.delayWrongPasswordMax", 4000); + + /** + * System property h2.javaSystemCompiler (default: true). + * Whether to use the Java system compiler + * (ToolProvider.getSystemJavaCompiler()) if it is available to compile user + * defined functions. If disabled or if the system compiler is not + * available, the com.sun.tools.javac compiler is used if available, and + * "javac" (as an external process) is used if not. + */ + public static final boolean JAVA_SYSTEM_COMPILER = + Utils.getProperty("h2.javaSystemCompiler", true); + + /** + * System property h2.lobCloseBetweenReads + * (default: false). + * Close LOB files between read operations. + */ + public static boolean lobCloseBetweenReads = + Utils.getProperty("h2.lobCloseBetweenReads", false); + + /** + * System property h2.lobClientMaxSizeMemory (default: + * 1048576). + * The maximum size of a LOB object to keep in memory on the client side + * when using the server mode. + */ + public static final int LOB_CLIENT_MAX_SIZE_MEMORY = + Utils.getProperty("h2.lobClientMaxSizeMemory", 1024 * 1024); + + /** + * System property h2.maxFileRetry (default: 16). + * Number of times to retry file delete and rename. in Windows, files can't + * be deleted if they are open. Waiting a bit can help (sometimes the + * Windows Explorer opens the files for a short time) may help. Sometimes, + * running garbage collection may close files if the user forgot to call + * Connection.close() or InputStream.close(). + */ + public static final int MAX_FILE_RETRY = + Math.max(1, Utils.getProperty("h2.maxFileRetry", 16)); + + /** + * System property h2.maxReconnect (default: 3). + * The maximum number of tries to reconnect in a row. + */ + public static final int MAX_RECONNECT = + Utils.getProperty("h2.maxReconnect", 3); + + /** + * System property h2.maxMemoryRows + * (default: 40000 per GB of available RAM). + * The default maximum number of rows to be kept in memory in a result set. + */ + public static final int MAX_MEMORY_ROWS = + getAutoScaledForMemoryProperty("h2.maxMemoryRows", 40_000); + + /** + * System property h2.maxTraceDataLength + * (default: 65535). + * The maximum size of a LOB value that is written as data to the trace + * system. + */ + public static final long MAX_TRACE_DATA_LENGTH = + Utils.getProperty("h2.maxTraceDataLength", 65535); + + /** + * System property h2.nioLoadMapped (default: false). + * If the mapped buffer should be loaded when the file is opened. + * This can improve performance. + */ + public static final boolean NIO_LOAD_MAPPED = + Utils.getProperty("h2.nioLoadMapped", false); + + /** + * System property h2.nioCleanerHack (default: false). + * If enabled, use the reflection hack to un-map the mapped file if + * possible. If disabled, System.gc() is called in a loop until the object + * is garbage collected. See also + * https://bugs.openjdk.java.net/browse/JDK-4724038 + */ + public static final boolean NIO_CLEANER_HACK = + Utils.getProperty("h2.nioCleanerHack", false); + + /** + * System property h2.objectCache (default: true). + * Cache commonly used values (numbers, strings). There is a shared cache + * for all values. + */ + public static final boolean OBJECT_CACHE = + Utils.getProperty("h2.objectCache", true); + + /** + * System property h2.objectCacheMaxPerElementSize (default: + * 4096). + * The maximum size (precision) of an object in the cache. + */ + public static final int OBJECT_CACHE_MAX_PER_ELEMENT_SIZE = + Utils.getProperty("h2.objectCacheMaxPerElementSize", 4096); + + /** + * System property h2.objectCacheSize (default: 1024). + * The maximum number of objects in the cache. + * This value must be a power of 2. + */ + public static final int OBJECT_CACHE_SIZE; + static { + try { + OBJECT_CACHE_SIZE = MathUtils.nextPowerOf2( + Utils.getProperty("h2.objectCacheSize", 1024)); + } catch (IllegalArgumentException e) { + throw new IllegalStateException("Invalid h2.objectCacheSize", e); + } + } + + /** + * System property h2.pgClientEncoding (default: UTF-8). + * Default client encoding for PG server. It is used if the client does not + * sends his encoding. + */ + public static final String PG_DEFAULT_CLIENT_ENCODING = + Utils.getProperty("h2.pgClientEncoding", "UTF-8"); + + /** + * System property h2.prefixTempFile (default: h2.temp). + * The prefix for temporary files in the temp directory. + */ + public static final String PREFIX_TEMP_FILE = + Utils.getProperty("h2.prefixTempFile", "h2.temp"); + + /** + * System property h2.forceAutoCommitOffOnCommit (default: false). + * Throw error if transaction's auto-commit property is true when a commit is executed. + */ + public static boolean FORCE_AUTOCOMMIT_OFF_ON_COMMIT = + Utils.getProperty("h2.forceAutoCommitOffOnCommit", false); + + /** + * System property h2.serverCachedObjects (default: 64). + * TCP Server: number of cached objects per session. + */ + public static final int SERVER_CACHED_OBJECTS = + Utils.getProperty("h2.serverCachedObjects", 64); + + /** + * System property h2.serverResultSetFetchSize + * (default: 100). + * The default result set fetch size when using the server mode. + */ + public static final int SERVER_RESULT_SET_FETCH_SIZE = + Utils.getProperty("h2.serverResultSetFetchSize", 100); + + /** + * System property h2.socketConnectRetry (default: 16). + * The number of times to retry opening a socket. Windows sometimes fails + * to open a socket, see bug + * https://bugs.openjdk.java.net/browse/JDK-6213296 + */ + public static final int SOCKET_CONNECT_RETRY = + Utils.getProperty("h2.socketConnectRetry", 16); + + /** + * System property h2.socketConnectTimeout + * (default: 2000). + * The timeout in milliseconds to connect to a server. + */ + public static final int SOCKET_CONNECT_TIMEOUT = + Utils.getProperty("h2.socketConnectTimeout", 2000); + + /** + * System property h2.splitFileSizeShift (default: 30). + * The maximum file size of a split file is 1L << x. + */ + public static final long SPLIT_FILE_SIZE_SHIFT = + Utils.getProperty("h2.splitFileSizeShift", 30); + + /** + * System property h2.traceIO (default: false). + * Trace all I/O operations. + */ + public static final boolean TRACE_IO = + Utils.getProperty("h2.traceIO", false); + + /** + * System property h2.threadDeadlockDetector + * (default: false). + * Detect thread deadlocks in a background thread. + */ + public static final boolean THREAD_DEADLOCK_DETECTOR = + Utils.getProperty("h2.threadDeadlockDetector", false); + + /** + * System property h2.urlMap (default: null). + * A properties file that contains a mapping between database URLs. New + * connections are written into the file. An empty value in the map means no + * redirection is used for the given URL. + */ + public static final String URL_MAP = + Utils.getProperty("h2.urlMap", null); + + /** + * System property h2.useThreadContextClassLoader + * (default: false). + * Instead of using the default class loader when deserializing objects, the + * current thread-context class loader will be used. + */ + public static final boolean USE_THREAD_CONTEXT_CLASS_LOADER = + Utils.getProperty("h2.useThreadContextClassLoader", false); + + /** + * System property h2.javaObjectSerializer + * (default: null). + * The JavaObjectSerializer class name for java objects being stored in + * column of type OTHER. It must be the same on client and server to work + * correctly. + */ + public static final String JAVA_OBJECT_SERIALIZER = + Utils.getProperty("h2.javaObjectSerializer", null); + + /** + * System property h2.authConfigFile + * (default: null). + * authConfigFile define the URL of configuration file + * of {@link org.h2.security.auth.DefaultAuthenticator} + * + */ + public static final String AUTH_CONFIG_FILE = + Utils.getProperty("h2.authConfigFile", null); + + private static final String H2_BASE_DIR = "h2.baseDir"; + + private SysProperties() { + // utility class + } + + /** + * INTERNAL + * @param dir base directory + */ + public static void setBaseDir(String dir) { + if (!dir.endsWith("/")) { + dir += "/"; + } + System.setProperty(H2_BASE_DIR, dir); + } + + /** + * INTERNAL + * @return base directory + */ + public static String getBaseDir() { + return Utils.getProperty(H2_BASE_DIR, null); + } + + /** + * System property h2.scriptDirectory (default: empty + * string). + * Relative or absolute directory where the script files are stored to or + * read from. + * + * @return the current value + */ + public static String getScriptDirectory() { + return Utils.getProperty(H2_SCRIPT_DIRECTORY, ""); + } + + /** + * This method attempts to auto-scale some of our properties to take + * advantage of more powerful machines out of the box. We assume that our + * default properties are set correctly for approx. 1G of memory, and scale + * them up if we have more. + */ + private static int getAutoScaledForMemoryProperty(String key, int defaultValue) { + String s = Utils.getProperty(key, null); + if (s != null) { + try { + return Integer.decode(s); + } catch (NumberFormatException e) { + // ignore + } + } + return Utils.scaleForAvailableMemory(defaultValue); + } + +} diff --git a/h2/src/main/org/h2/engine/User.java b/h2/src/main/org/h2/engine/User.java new file mode 100644 index 0000000..281d691 --- /dev/null +++ b/h2/src/main/org/h2/engine/User.java @@ -0,0 +1,269 @@ +/* + * 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.engine; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.h2.api.ErrorCode; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.schema.Schema; +import org.h2.security.SHA256; +import org.h2.table.DualTable; +import org.h2.table.MetaTable; +import org.h2.table.RangeTable; +import org.h2.table.Table; +import org.h2.table.TableType; +import org.h2.util.MathUtils; +import org.h2.util.StringUtils; +import org.h2.util.Utils; + +/** + * Represents a user object. + */ +public final class User extends RightOwner { + + private final boolean systemUser; + private byte[] salt; + private byte[] passwordHash; + private boolean admin; + + public User(Database database, int id, String userName, boolean systemUser) { + super(database, id, userName, Trace.USER); + this.systemUser = systemUser; + } + + public void setAdmin(boolean admin) { + this.admin = admin; + } + + public boolean isAdmin() { + return admin; + } + + /** + * Set the salt and hash of the password for this user. + * + * @param salt the salt + * @param hash the password hash + */ + public void setSaltAndHash(byte[] salt, byte[] hash) { + this.salt = salt; + this.passwordHash = hash; + } + + /** + * Set the user name password hash. A random salt is generated as well. + * The parameter is filled with zeros after use. + * + * @param userPasswordHash the user name password hash + */ + public void setUserPasswordHash(byte[] userPasswordHash) { + if (userPasswordHash != null) { + if (userPasswordHash.length == 0) { + salt = passwordHash = userPasswordHash; + } else { + salt = new byte[Constants.SALT_LEN]; + MathUtils.randomBytes(salt); + passwordHash = SHA256.getHashWithSalt(userPasswordHash, salt); + } + } + } + + @Override + public String getCreateSQLForCopy(Table table, String quotedName) { + throw DbException.getInternalError(toString()); + } + + @Override + public String getCreateSQL() { + return getCreateSQL(true); + } + + /** + * Get the CREATE SQL statement for this object. + * + * @param password true if the password (actually the salt and hash) should + * be returned + * @return the SQL statement + */ + public String getCreateSQL(boolean password) { + StringBuilder buff = new StringBuilder("CREATE USER IF NOT EXISTS "); + getSQL(buff, DEFAULT_SQL_FLAGS); + if (comment != null) { + buff.append(" COMMENT "); + StringUtils.quoteStringSQL(buff, comment); + } + if (password) { + buff.append(" SALT '"); + StringUtils.convertBytesToHex(buff, salt). + append("' HASH '"); + StringUtils.convertBytesToHex(buff, passwordHash). + append('\''); + } else { + buff.append(" PASSWORD ''"); + } + if (admin) { + buff.append(" ADMIN"); + } + return buff.toString(); + } + + /** + * Check the password of this user. + * + * @param userPasswordHash the password data (the user password hash) + * @return true if the user password hash is correct + */ + boolean validateUserPasswordHash(byte[] userPasswordHash) { + if (userPasswordHash.length == 0 && passwordHash.length == 0) { + return true; + } + if (userPasswordHash.length == 0) { + userPasswordHash = SHA256.getKeyPasswordHash(getName(), new char[0]); + } + byte[] hash = SHA256.getHashWithSalt(userPasswordHash, salt); + return Utils.compareSecure(hash, passwordHash); + } + + /** + * Checks if this user has admin rights. An exception is thrown if user + * doesn't have them. + * + * @throws DbException if this user is not an admin + */ + public void checkAdmin() { + if (!admin) { + throw DbException.get(ErrorCode.ADMIN_RIGHTS_REQUIRED); + } + } + + /** + * Checks if this user has schema admin rights for every schema. An + * exception is thrown if user doesn't have them. + * + * @throws DbException if this user is not a schema admin + */ + public void checkSchemaAdmin() { + if (!hasSchemaRight(null)) { + throw DbException.get(ErrorCode.ADMIN_RIGHTS_REQUIRED); + } + } + + /** + * Checks if this user has schema owner rights for the specified schema. An + * exception is thrown if user doesn't have them. + * + * @param schema the schema + * @throws DbException if this user is not a schema owner + */ + public void checkSchemaOwner(Schema schema) { + if (!hasSchemaRight(schema)) { + throw DbException.get(ErrorCode.NOT_ENOUGH_RIGHTS_FOR_1, schema.getTraceSQL()); + } + } + + /** + * See if this user has owner rights for the specified schema + * + * @param schema the schema + * @return true if the user has the rights + */ + private boolean hasSchemaRight(Schema schema) { + if (admin) { + return true; + } + Role publicRole = database.getPublicRole(); + if (publicRole.isSchemaRightGrantedRecursive(schema)) { + return true; + } + return isSchemaRightGrantedRecursive(schema); + } + + /** + * Checks that this user has the given rights for the specified table. + * + * @param table the table + * @param rightMask the rights required + * @throws DbException if this user does not have the required rights + */ + public void checkTableRight(Table table, int rightMask) { + if (!hasTableRight(table, rightMask)) { + throw DbException.get(ErrorCode.NOT_ENOUGH_RIGHTS_FOR_1, table.getTraceSQL()); + } + } + + /** + * See if this user has the given rights for this database object. + * + * @param table the database object, or null for schema-only check + * @param rightMask the rights required + * @return true if the user has the rights + */ + public boolean hasTableRight(Table table, int rightMask) { + if (rightMask != Right.SELECT && !systemUser) { + table.checkWritingAllowed(); + } + if (admin) { + return true; + } + Role publicRole = database.getPublicRole(); + if (publicRole.isTableRightGrantedRecursive(table, rightMask)) { + return true; + } + if (table instanceof MetaTable || table instanceof DualTable || table instanceof RangeTable) { + // everybody has access to the metadata information + return true; + } + TableType tableType = table.getTableType(); + if (tableType == null) { + // derived or function table + return true; + } + if (table.isTemporary() && !table.isGlobalTemporary()) { + // the owner has all rights on local temporary tables + return true; + } + return isTableRightGrantedRecursive(table, rightMask); + } + + @Override + public int getType() { + return DbObject.USER; + } + + @Override + public ArrayList getChildren() { + ArrayList children = new ArrayList<>(); + for (Right right : database.getAllRights()) { + if (right.getGrantee() == this) { + children.add(right); + } + } + for (Schema schema : database.getAllSchemas()) { + if (schema.getOwner() == this) { + children.add(schema); + } + } + return children; + } + + @Override + public void removeChildrenAndResources(SessionLocal session) { + for (Right right : database.getAllRights()) { + if (right.getGrantee() == this) { + database.removeDatabaseObject(session, right); + } + } + database.removeMeta(session, getId()); + salt = null; + Arrays.fill(passwordHash, (byte) 0); + passwordHash = null; + invalidate(); + } + +} diff --git a/h2/src/main/org/h2/engine/UserBuilder.java b/h2/src/main/org/h2/engine/UserBuilder.java new file mode 100644 index 0000000..658c805 --- /dev/null +++ b/h2/src/main/org/h2/engine/UserBuilder.java @@ -0,0 +1,36 @@ +/* + * 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.engine; + +import org.h2.security.auth.AuthenticationInfo; +import org.h2.util.MathUtils; + +public class UserBuilder { + + /** + * Build the database user starting from authentication informations. + * + * @param authenticationInfo + * authentication info + * @param database + * target database + * @param persistent + * true if the user will be persisted in the database + * @return user bean + */ + public static User buildUser(AuthenticationInfo authenticationInfo, Database database, boolean persistent) { + User user = new User(database, persistent ? database.allocateObjectId() : -1, + authenticationInfo.getFullyQualifiedName(), false); + // In case of external authentication fill the password hash with random + // data + user.setUserPasswordHash( + authenticationInfo.getRealm() == null ? authenticationInfo.getConnectionInfo().getUserPasswordHash() + : MathUtils.secureRandomBytes(64)); + user.setTemporary(!persistent); + return user; + } + +} diff --git a/h2/src/main/org/h2/engine/package.html b/h2/src/main/org/h2/engine/package.html new file mode 100644 index 0000000..09d0a56 --- /dev/null +++ b/h2/src/main/org/h2/engine/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Contains high level classes of the database and classes that don't fit in another sub-package. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/expression/Alias.java b/h2/src/main/org/h2/expression/Alias.java new file mode 100644 index 0000000..afae60c --- /dev/null +++ b/h2/src/main/org/h2/expression/Alias.java @@ -0,0 +1,126 @@ +/* + * 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.expression; + +import org.h2.engine.SessionLocal; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.util.ParserUtil; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * A column alias as in SELECT 'Hello' AS NAME ... + */ +public final class Alias extends Expression { + + private final String alias; + private Expression expr; + private final boolean aliasColumnName; + + public Alias(Expression expression, String alias, boolean aliasColumnName) { + this.expr = expression; + this.alias = alias; + this.aliasColumnName = aliasColumnName; + } + + @Override + public Expression getNonAliasExpression() { + return expr; + } + + @Override + public Value getValue(SessionLocal session) { + return expr.getValue(session); + } + + @Override + public TypeInfo getType() { + return expr.getType(); + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + expr.mapColumns(resolver, level, state); + } + + @Override + public Expression optimize(SessionLocal session) { + expr = expr.optimize(session); + return this; + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + expr.setEvaluatable(tableFilter, b); + } + + @Override + public boolean isIdentity() { + return expr.isIdentity(); + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + expr.getUnenclosedSQL(builder, sqlFlags).append(" AS "); + return ParserUtil.quoteIdentifier(builder, alias, sqlFlags); + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + expr.updateAggregate(session, stage); + } + + @Override + public String getAlias(SessionLocal session, int columnIndex) { + return alias; + } + + @Override + public String getColumnNameForView(SessionLocal session, int columnIndex) { + return alias; + } + + @Override + public int getNullable() { + return expr.getNullable(); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return expr.isEverything(visitor); + } + + @Override + public int getCost() { + return expr.getCost(); + } + + @Override + public String getSchemaName() { + if (aliasColumnName) { + return null; + } + return expr.getSchemaName(); + } + + @Override + public String getTableName() { + if (aliasColumnName) { + return null; + } + return expr.getTableName(); + } + + @Override + public String getColumnName(SessionLocal session, int columnIndex) { + if (!(expr instanceof ExpressionColumn) || aliasColumnName) { + return alias; + } + return expr.getColumnName(session, columnIndex); + } + +} diff --git a/h2/src/main/org/h2/expression/ArrayConstructorByQuery.java b/h2/src/main/org/h2/expression/ArrayConstructorByQuery.java new file mode 100644 index 0000000..9ed16bd --- /dev/null +++ b/h2/src/main/org/h2/expression/ArrayConstructorByQuery.java @@ -0,0 +1,102 @@ +/* + * 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.expression; + +import java.util.ArrayList; + +import org.h2.api.ErrorCode; +import org.h2.command.query.Query; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.result.ResultInterface; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.util.StringUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueArray; + +/** + * Array value constructor by query. + */ +public final class ArrayConstructorByQuery extends Expression { + + /** + * The subquery. + */ + private final Query query; + + private TypeInfo componentType, type; + + /** + * Creates new instance of array value constructor by query. + * + * @param query + * the query + */ + public ArrayConstructorByQuery(Query query) { + this.query = query; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return StringUtils.indent(builder.append("ARRAY ("), query.getPlanSQL(sqlFlags), 4, false).append(')'); + } + + @Override + public Value getValue(SessionLocal session) { + query.setSession(session); + ArrayList values = new ArrayList<>(); + try (ResultInterface result = query.query(0)) { + while (result.next()) { + values.add(result.currentRow()[0]); + } + } + return ValueArray.get(componentType, values.toArray(new Value[0]), session); + } + + @Override + public TypeInfo getType() { + return type; + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + query.mapColumns(resolver, level + 1); + } + + @Override + public Expression optimize(SessionLocal session) { + query.prepare(); + if (query.getColumnCount() != 1) { + throw DbException.get(ErrorCode.SUBQUERY_IS_NOT_SINGLE_COLUMN); + } + componentType = query.getExpressions().get(0).getType(); + type = TypeInfo.getTypeInfo(Value.ARRAY, -1L, -1, componentType); + return this; + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean value) { + query.setEvaluatable(tableFilter, value); + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + query.updateAggregate(session, stage); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return query.isEverything(visitor); + } + + @Override + public int getCost() { + return query.getCostAsExpression(); + } + +} diff --git a/h2/src/main/org/h2/expression/ArrayElementReference.java b/h2/src/main/org/h2/expression/ArrayElementReference.java new file mode 100644 index 0000000..77f00ee --- /dev/null +++ b/h2/src/main/org/h2/expression/ArrayElementReference.java @@ -0,0 +1,68 @@ +/* + * 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.expression; + +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; +import org.h2.value.ValueNull; + +/** + * Array element reference. + */ +public final class ArrayElementReference extends Operation2 { + + public ArrayElementReference(Expression left, Expression right) { + super(left, right); + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + left.getSQL(builder, sqlFlags, AUTO_PARENTHESES).append('['); + return right.getUnenclosedSQL(builder, sqlFlags).append(']'); + } + + @Override + public Value getValue(SessionLocal session) { + Value l = left.getValue(session); + Value r = right.getValue(session); + if (l != ValueNull.INSTANCE && r != ValueNull.INSTANCE) { + Value[] list = ((ValueArray) l).getList(); + int element = r.getInt(); + int cardinality = list.length; + if (element >= 1 && element <= cardinality) { + return list[element - 1]; + } + throw DbException.get(ErrorCode.ARRAY_ELEMENT_ERROR_2, Integer.toString(element), "1.." + cardinality); + } + return ValueNull.INSTANCE; + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + right = right.optimize(session); + TypeInfo leftType = left.getType(); + switch (leftType.getValueType()) { + case Value.NULL: + return ValueExpression.NULL; + case Value.ARRAY: + type = (TypeInfo) leftType.getExtTypeInfo(); + if (left.isConstant() && right.isConstant()) { + return TypedValueExpression.get(getValue(session), type); + } + break; + default: + throw Store.getInvalidExpressionTypeException("Array", left); + } + return this; + } + +} diff --git a/h2/src/main/org/h2/expression/BinaryOperation.java b/h2/src/main/org/h2/expression/BinaryOperation.java new file mode 100644 index 0000000..9c91051 --- /dev/null +++ b/h2/src/main/org/h2/expression/BinaryOperation.java @@ -0,0 +1,447 @@ +/* + * 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.expression; + +import org.h2.engine.SessionLocal; +import org.h2.expression.IntervalOperation.IntervalOpType; +import org.h2.expression.function.DateTimeFunction; +import org.h2.message.DbException; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueInteger; +import org.h2.value.ValueNull; +import org.h2.value.ValueNumeric; + +/** + * A mathematical expression, or string concatenation. + */ +public class BinaryOperation extends Operation2 { + + public enum OpType { + /** + * This operation represents an addition as in 1 + 2. + */ + PLUS, + + /** + * This operation represents a subtraction as in 2 - 1. + */ + MINUS, + + /** + * This operation represents a multiplication as in 2 * 3. + */ + MULTIPLY, + + /** + * This operation represents a division as in 4 / 2. + */ + DIVIDE + } + + private OpType opType; + private TypeInfo forcedType; + private boolean convertRight = true; + + public BinaryOperation(OpType opType, Expression left, Expression right) { + super(left, right); + this.opType = opType; + } + + /** + * Sets a forced data type of a datetime minus datetime operation. + * + * @param forcedType the forced data type + */ + public void setForcedType(TypeInfo forcedType) { + if (opType != OpType.MINUS) { + throw getUnexpectedForcedTypeException(); + } + this.forcedType = forcedType; + } + + @Override + public boolean needParentheses() { + return true; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + // don't remove the space, otherwise it might end up some thing like + // --1 which is a line remark + left.getSQL(builder, sqlFlags, AUTO_PARENTHESES).append(' ').append(getOperationToken()).append(' '); + return right.getSQL(builder, sqlFlags, AUTO_PARENTHESES); + } + + private String getOperationToken() { + switch (opType) { + case PLUS: + return "+"; + case MINUS: + return "-"; + case MULTIPLY: + return "*"; + case DIVIDE: + return "/"; + default: + throw DbException.getInternalError("opType=" + opType); + } + } + + @Override + public Value getValue(SessionLocal session) { + Value l = left.getValue(session).convertTo(type, session); + Value r = right.getValue(session); + if (convertRight) { + r = r.convertTo(type, session); + } + switch (opType) { + case PLUS: + if (l == ValueNull.INSTANCE || r == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + return l.add(r); + case MINUS: + if (l == ValueNull.INSTANCE || r == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + return l.subtract(r); + case MULTIPLY: + if (l == ValueNull.INSTANCE || r == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + return l.multiply(r); + case DIVIDE: + if (l == ValueNull.INSTANCE || r == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + return l.divide(r, type); + default: + throw DbException.getInternalError("type=" + opType); + } + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + right = right.optimize(session); + TypeInfo leftType = left.getType(), rightType = right.getType(); + int l = leftType.getValueType(), r = rightType.getValueType(); + if ((l == Value.NULL && r == Value.NULL) || (l == Value.UNKNOWN && r == Value.UNKNOWN)) { + // (? + ?) - use decimal by default (the most safe data type) or + // string when text concatenation with + is enabled + if (opType == OpType.PLUS && session.getDatabase().getMode().allowPlusForStringConcat) { + return new ConcatenationOperation(left, right).optimize(session); + } else { + type = TypeInfo.TYPE_NUMERIC_FLOATING_POINT; + } + } else if (DataType.isIntervalType(l) || DataType.isIntervalType(r)) { + if (forcedType != null) { + throw getUnexpectedForcedTypeException(); + } + return optimizeInterval(l, r); + } else if (DataType.isDateTimeType(l) || DataType.isDateTimeType(r)) { + return optimizeDateTime(session, l, r); + } else if (forcedType != null) { + throw getUnexpectedForcedTypeException(); + } else { + int dataType = Value.getHigherOrder(l, r); + if (dataType == Value.NUMERIC) { + optimizeNumeric(leftType, rightType); + } else if (dataType == Value.DECFLOAT) { + optimizeDecfloat(leftType, rightType); + } else if (dataType == Value.ENUM) { + type = TypeInfo.TYPE_INTEGER; + } else if (DataType.isCharacterStringType(dataType) + && opType == OpType.PLUS && session.getDatabase().getMode().allowPlusForStringConcat) { + return new ConcatenationOperation(left, right).optimize(session); + } else { + type = TypeInfo.getTypeInfo(dataType); + } + } + if (left.isConstant() && right.isConstant()) { + return ValueExpression.get(getValue(session)); + } + return this; + } + + private void optimizeNumeric(TypeInfo leftType, TypeInfo rightType) { + leftType = leftType.toNumericType(); + rightType = rightType.toNumericType(); + long leftPrecision = leftType.getPrecision(), rightPrecision = rightType.getPrecision(); + int leftScale = leftType.getScale(), rightScale = rightType.getScale(); + long precision; + int scale; + switch (opType) { + case PLUS: + case MINUS: + // Precision is implementation-defined. + // Scale must be max(leftScale, rightScale). + // Choose the largest scale and adjust the precision of other + // argument. + if (leftScale < rightScale) { + leftPrecision += rightScale - leftScale; + scale = rightScale; + } else { + rightPrecision += leftScale - rightScale; + scale = leftScale; + } + // Add one extra digit to the largest precision. + precision = Math.max(leftPrecision, rightPrecision) + 1; + break; + case MULTIPLY: + // Precision is implementation-defined. + // Scale must be leftScale + rightScale. + // Use sum of precisions. + precision = leftPrecision + rightPrecision; + scale = leftScale + rightScale; + break; + case DIVIDE: { + // Precision and scale are implementation-defined. + long scaleAsLong = leftScale - rightScale + rightPrecision * 2; + if (scaleAsLong >= ValueNumeric.MAXIMUM_SCALE) { + scale = ValueNumeric.MAXIMUM_SCALE; + } else if (scaleAsLong <= 0) { + scale = 0; + } else { + scale = (int) scaleAsLong; + } + // Divider can be effectively multiplied by no more than + // 10^rightScale, so add rightScale to its precision and adjust the + // result to the changes in scale. + precision = leftPrecision + rightScale - leftScale + scale; + break; + } + default: + throw DbException.getInternalError("type=" + opType); + } + type = TypeInfo.getTypeInfo(Value.NUMERIC, precision, scale, null); + } + + private void optimizeDecfloat(TypeInfo leftType, TypeInfo rightType) { + leftType = leftType.toDecfloatType(); + rightType = rightType.toDecfloatType(); + long leftPrecision = leftType.getPrecision(), rightPrecision = rightType.getPrecision(); + long precision; + switch (opType) { + case PLUS: + case MINUS: + case DIVIDE: + // Add one extra digit to the largest precision. + precision = Math.max(leftPrecision, rightPrecision) + 1; + break; + case MULTIPLY: + // Use sum of precisions. + precision = leftPrecision + rightPrecision; + break; + default: + throw DbException.getInternalError("type=" + opType); + } + type = TypeInfo.getTypeInfo(Value.DECFLOAT, precision, 0, null); + } + + private Expression optimizeInterval(int l, int r) { + boolean lInterval = false, lNumeric = false, lDateTime = false; + if (DataType.isIntervalType(l)) { + lInterval = true; + } else if (DataType.isNumericType(l)) { + lNumeric = true; + } else if (DataType.isDateTimeType(l)) { + lDateTime = true; + } else { + throw getUnsupported(l, r); + } + boolean rInterval = false, rNumeric = false, rDateTime = false; + if (DataType.isIntervalType(r)) { + rInterval = true; + } else if (DataType.isNumericType(r)) { + rNumeric = true; + } else if (DataType.isDateTimeType(r)) { + rDateTime = true; + } else { + throw getUnsupported(l, r); + } + switch (opType) { + case PLUS: + if (lInterval && rInterval) { + if (DataType.isYearMonthIntervalType(l) == DataType.isYearMonthIntervalType(r)) { + return new IntervalOperation(IntervalOpType.INTERVAL_PLUS_INTERVAL, left, right); + } + } else if (lInterval && rDateTime) { + if (r == Value.TIME && DataType.isYearMonthIntervalType(l)) { + break; + } + return new IntervalOperation(IntervalOpType.DATETIME_PLUS_INTERVAL, right, left); + } else if (lDateTime && rInterval) { + if (l == Value.TIME && DataType.isYearMonthIntervalType(r)) { + break; + } + return new IntervalOperation(IntervalOpType.DATETIME_PLUS_INTERVAL, left, right); + } + break; + case MINUS: + if (lInterval && rInterval) { + if (DataType.isYearMonthIntervalType(l) == DataType.isYearMonthIntervalType(r)) { + return new IntervalOperation(IntervalOpType.INTERVAL_MINUS_INTERVAL, left, right); + } + } else if (lDateTime && rInterval) { + if (l == Value.TIME && DataType.isYearMonthIntervalType(r)) { + break; + } + return new IntervalOperation(IntervalOpType.DATETIME_MINUS_INTERVAL, left, right); + } + break; + case MULTIPLY: + if (lInterval && rNumeric) { + return new IntervalOperation(IntervalOpType.INTERVAL_MULTIPLY_NUMERIC, left, right); + } else if (lNumeric && rInterval) { + return new IntervalOperation(IntervalOpType.INTERVAL_MULTIPLY_NUMERIC, right, left); + } + break; + case DIVIDE: + if (lInterval) { + if (rNumeric) { + return new IntervalOperation(IntervalOpType.INTERVAL_DIVIDE_NUMERIC, left, right); + } else if (rInterval && DataType.isYearMonthIntervalType(l) == DataType.isYearMonthIntervalType(r)) { + // Non-standard + return new IntervalOperation(IntervalOpType.INTERVAL_DIVIDE_INTERVAL, left, right); + } + } + break; + default: + } + throw getUnsupported(l, r); + } + + private Expression optimizeDateTime(SessionLocal session, int l, int r) { + switch (opType) { + case PLUS: { + if (DataType.isDateTimeType(l)) { + if (DataType.isDateTimeType(r)) { + if (l > r) { + swap(); + int t = l; + l = r; + r = t; + } + return new CompatibilityDatePlusTimeOperation(right, left).optimize(session); + } + swap(); + int t = l; + l = r; + r = t; + } + switch (l) { + case Value.INTEGER: + // Oracle date add + return new DateTimeFunction(DateTimeFunction.DATEADD, DateTimeFunction.DAY, left, right) + .optimize(session); + case Value.NUMERIC: + case Value.REAL: + case Value.DOUBLE: + case Value.DECFLOAT: + // Oracle date add + return new DateTimeFunction(DateTimeFunction.DATEADD, DateTimeFunction.SECOND, + new BinaryOperation(OpType.MULTIPLY, ValueExpression.get(ValueInteger.get(60 * 60 * 24)), + left), right).optimize(session); + } + break; + } + case MINUS: + switch (l) { + case Value.DATE: + case Value.TIMESTAMP: + case Value.TIMESTAMP_TZ: + switch (r) { + case Value.INTEGER: { + if (forcedType != null) { + throw getUnexpectedForcedTypeException(); + } + // Oracle date subtract + return new DateTimeFunction(DateTimeFunction.DATEADD, DateTimeFunction.DAY, + new UnaryOperation(right), left).optimize(session); + } + case Value.NUMERIC: + case Value.REAL: + case Value.DOUBLE: + case Value.DECFLOAT: { + if (forcedType != null) { + throw getUnexpectedForcedTypeException(); + } + // Oracle date subtract + return new DateTimeFunction(DateTimeFunction.DATEADD, DateTimeFunction.SECOND, + new BinaryOperation(OpType.MULTIPLY, ValueExpression.get(ValueInteger.get(-60 * 60 * 24)), + right), left).optimize(session); + } + case Value.TIME: + case Value.TIME_TZ: + case Value.DATE: + case Value.TIMESTAMP: + case Value.TIMESTAMP_TZ: + return new IntervalOperation(IntervalOpType.DATETIME_MINUS_DATETIME, left, right, forcedType); + } + break; + case Value.TIME: + case Value.TIME_TZ: + if (DataType.isDateTimeType(r)) { + return new IntervalOperation(IntervalOpType.DATETIME_MINUS_DATETIME, left, right, forcedType); + } + break; + } + break; + case MULTIPLY: + if (l == Value.TIME) { + type = TypeInfo.TYPE_TIME; + convertRight = false; + return this; + } else if (r == Value.TIME) { + swap(); + type = TypeInfo.TYPE_TIME; + convertRight = false; + return this; + } + break; + case DIVIDE: + if (l == Value.TIME) { + type = TypeInfo.TYPE_TIME; + convertRight = false; + return this; + } + break; + default: + } + throw getUnsupported(l, r); + } + + private DbException getUnsupported(int l, int r) { + return DbException.getUnsupportedException( + Value.getTypeName(l) + ' ' + getOperationToken() + ' ' + Value.getTypeName(r)); + } + + private DbException getUnexpectedForcedTypeException() { + StringBuilder builder = getUnenclosedSQL(new StringBuilder(), TRACE_SQL_FLAGS); + int index = builder.length(); + return DbException.getSyntaxError( + IntervalOperation.getForcedTypeSQL(builder.append(' '), forcedType).toString(), index, ""); + } + + private void swap() { + Expression temp = left; + left = right; + right = temp; + } + + /** + * Returns the type of this binary operation. + * + * @return the type of this binary operation + */ + public OpType getOperationType() { + return opType; + } + +} diff --git a/h2/src/main/org/h2/expression/CompatibilityDatePlusTimeOperation.java b/h2/src/main/org/h2/expression/CompatibilityDatePlusTimeOperation.java new file mode 100644 index 0000000..f1f4132 --- /dev/null +++ b/h2/src/main/org/h2/expression/CompatibilityDatePlusTimeOperation.java @@ -0,0 +1,117 @@ +/* + * 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.expression; + +import static org.h2.util.DateTimeUtils.NANOS_PER_DAY; + +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.util.DateTimeUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueDate; +import org.h2.value.ValueNull; +import org.h2.value.ValueTime; +import org.h2.value.ValueTimeTimeZone; +import org.h2.value.ValueTimestamp; +import org.h2.value.ValueTimestampTimeZone; + +/** + * A compatibility mathematical operation with datetime values. + */ +public class CompatibilityDatePlusTimeOperation extends Operation2 { + + public CompatibilityDatePlusTimeOperation(Expression left, Expression right) { + super(left, right); + TypeInfo l = left.getType(), r = right.getType(); + int t; + switch (l.getValueType()) { + case Value.TIMESTAMP_TZ: + if (r.getValueType() == Value.TIME_TZ) { + throw DbException.getUnsupportedException("TIMESTAMP WITH TIME ZONE + TIME WITH TIME ZONE"); + } + //$FALL-THROUGH$ + case Value.TIME: + t = r.getValueType() == Value.DATE ? Value.TIMESTAMP : l.getValueType(); + break; + case Value.TIME_TZ: + if (r.getValueType() == Value.TIME_TZ) { + throw DbException.getUnsupportedException("TIME WITH TIME ZONE + TIME WITH TIME ZONE"); + } + t = r.getValueType() == Value.DATE ? Value.TIMESTAMP_TZ : l.getValueType(); + break; + case Value.TIMESTAMP: + t = r.getValueType() == Value.TIME_TZ ? Value.TIMESTAMP_TZ : Value.TIMESTAMP; + break; + default: + throw DbException.getUnsupportedException( + Value.getTypeName(l.getValueType()) + " + " + Value.getTypeName(r.getValueType())); + } + type = TypeInfo.getTypeInfo(t, 0L, Math.max(l.getScale(), r.getScale()), null); + } + + @Override + public boolean needParentheses() { + return true; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + left.getSQL(builder, sqlFlags, AUTO_PARENTHESES).append(" + "); + return right.getSQL(builder, sqlFlags, AUTO_PARENTHESES); + } + + @Override + public Value getValue(SessionLocal session) { + Value l = left.getValue(session); + Value r = right.getValue(session); + if (l == ValueNull.INSTANCE || r == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + switch (l.getValueType()) { + case Value.TIME: + if (r.getValueType() == Value.DATE) { + return ValueTimestamp.fromDateValueAndNanos(((ValueDate) r).getDateValue(), // + ((ValueTime) l).getNanos()); + } + break; + case Value.TIME_TZ: + if (r.getValueType() == Value.DATE) { + ValueTimeTimeZone t = (ValueTimeTimeZone) l; + return ValueTimestampTimeZone.fromDateValueAndNanos(((ValueDate) r).getDateValue(), t.getNanos(), + t.getTimeZoneOffsetSeconds()); + } + break; + case Value.TIMESTAMP: { + if (r.getValueType() == Value.TIME_TZ) { + ValueTimestamp ts = (ValueTimestamp) l; + l = ValueTimestampTimeZone.fromDateValueAndNanos(ts.getDateValue(), ts.getTimeNanos(), + ((ValueTimeTimeZone) r).getTimeZoneOffsetSeconds()); + } + break; + } + } + long[] a = DateTimeUtils.dateAndTimeFromValue(l, session); + long dateValue = a[0], timeNanos = a[1] + + (r instanceof ValueTime ? ((ValueTime) r).getNanos() : ((ValueTimeTimeZone) r).getNanos()); + if (timeNanos >= NANOS_PER_DAY) { + timeNanos -= NANOS_PER_DAY; + dateValue = DateTimeUtils.incrementDateValue(dateValue); + } + return DateTimeUtils.dateTimeToValue(l, dateValue, timeNanos); + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + right = right.optimize(session); + if (left.isConstant() && right.isConstant()) { + return ValueExpression.get(getValue(session)); + } + return this; + } + +} diff --git a/h2/src/main/org/h2/expression/ConcatenationOperation.java b/h2/src/main/org/h2/expression/ConcatenationOperation.java new file mode 100644 index 0000000..18baace --- /dev/null +++ b/h2/src/main/org/h2/expression/ConcatenationOperation.java @@ -0,0 +1,281 @@ +/* + * 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.expression; + +import java.util.Arrays; + +import org.h2.engine.SessionLocal; +import org.h2.expression.function.CastSpecification; +import org.h2.expression.function.ConcatFunction; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueArray; +import org.h2.value.ValueNull; +import org.h2.value.ValueVarbinary; +import org.h2.value.ValueVarchar; + +/** + * Character string concatenation as in {@code 'Hello' || 'World'}, binary + * string concatenation as in {@code X'01' || X'AB'} or an array concatenation + * as in {@code ARRAY[1, 2] || 3}. + */ +public final class ConcatenationOperation extends OperationN { + + public ConcatenationOperation() { + super(new Expression[4]); + } + + public ConcatenationOperation(Expression op1, Expression op2) { + super(new Expression[] { op1, op2 }); + argsCount = 2; + } + + @Override + public boolean needParentheses() { + return true; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + for (int i = 0, l = args.length; i < l; i++) { + if (i > 0) { + builder.append(" || "); + } + args[i].getSQL(builder, sqlFlags, AUTO_PARENTHESES); + } + return builder; + } + + @Override + public Value getValue(SessionLocal session) { + int l = args.length; + if (l == 2) { + Value v1 = args[0].getValue(session); + v1 = v1.convertTo(type, session); + if (v1 == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + Value v2 = args[1].getValue(session); + v2 = v2.convertTo(type, session); + if (v2 == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + return getValue(session, v1, v2); + } + return getValue(session, l); + } + + private Value getValue(SessionLocal session, Value l, Value r) { + int valueType = type.getValueType(); + if (valueType == Value.VARCHAR) { + String s1 = l.getString(), s2 = r.getString(); + return ValueVarchar.get(new StringBuilder(s1.length() + s2.length()).append(s1).append(s2).toString()); + } else if (valueType == Value.VARBINARY) { + byte[] leftBytes = l.getBytesNoCopy(), rightBytes = r.getBytesNoCopy(); + int leftLength = leftBytes.length, rightLength = rightBytes.length; + byte[] bytes = Arrays.copyOf(leftBytes, leftLength + rightLength); + System.arraycopy(rightBytes, 0, bytes, leftLength, rightLength); + return ValueVarbinary.getNoCopy(bytes); + } else { + Value[] leftValues = ((ValueArray) l).getList(), rightValues = ((ValueArray) r).getList(); + int leftLength = leftValues.length, rightLength = rightValues.length; + Value[] values = Arrays.copyOf(leftValues, leftLength + rightLength); + System.arraycopy(rightValues, 0, values, leftLength, rightLength); + return ValueArray.get((TypeInfo) type.getExtTypeInfo(), values, session); + } + } + + private Value getValue(SessionLocal session, int l) { + Value[] values = new Value[l]; + for (int i = 0; i < l; i++) { + Value v = args[i].getValue(session).convertTo(type, session); + if (v == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + values[i] = v; + } + int valueType = type.getValueType(); + if (valueType == Value.VARCHAR) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < l; i++) { + builder.append(values[i].getString()); + } + return ValueVarchar.get(builder.toString(), session); + } else if (valueType == Value.VARBINARY) { + int totalLength = 0; + for (int i = 0; i < l; i++) { + totalLength += values[i].getBytesNoCopy().length; + } + byte[] v = new byte[totalLength]; + int offset = 0; + for (int i = 0; i < l; i++) { + byte[] a = values[i].getBytesNoCopy(); + int length = a.length; + System.arraycopy(a, 0, v, offset, length); + offset += length; + } + return ValueVarbinary.getNoCopy(v); + } else { + int totalLength = 0; + for (int i = 0; i < l; i++) { + totalLength += ((ValueArray) values[i]).getList().length; + } + Value[] v = new Value[totalLength]; + int offset = 0; + for (int i = 0; i < l; i++) { + Value[] a = ((ValueArray) values[i]).getList(); + int length = a.length; + System.arraycopy(a, 0, v, offset, length); + offset += length; + } + return ValueArray.get((TypeInfo) type.getExtTypeInfo(), v, session); + } + } + + @Override + public Expression optimize(SessionLocal session) { + determineType(session); + inlineArguments(); + if (type.getValueType() == Value.VARCHAR && session.getMode().treatEmptyStringsAsNull) { + return new ConcatFunction(ConcatFunction.CONCAT, args).optimize(session); + } + int l = args.length; + boolean allConst = true, anyConst = false; + for (int i = 0; i < l; i++) { + if (args[i].isConstant()) { + anyConst = true; + } else { + allConst = false; + } + } + if (allConst) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + if (anyConst) { + int offset = 0; + for (int i = 0; i < l; i++) { + Expression arg1 = args[i]; + if (arg1.isConstant()) { + Value v1 = arg1.getValue(session).convertTo(type, session); + if (v1 == ValueNull.INSTANCE) { + return TypedValueExpression.get(ValueNull.INSTANCE, type); + } + if (isEmpty(v1)) { + continue; + } + for (Expression arg2; i + 1 < l && (arg2 = args[i + 1]).isConstant(); i++) { + Value v2 = arg2.getValue(session).convertTo(type, session); + if (v2 == ValueNull.INSTANCE) { + return TypedValueExpression.get(ValueNull.INSTANCE, type); + } + if (!isEmpty(v2)) { + v1 = getValue(session, v1, v2); + } + } + arg1 = ValueExpression.get(v1); + } + args[offset++] = arg1; + } + if (offset == 1) { + Expression arg = args[0]; + TypeInfo argType = arg.getType(); + if (TypeInfo.areSameTypes(type, argType)) { + return arg; + } + return new CastSpecification(arg, type); + } + argsCount = offset; + doneWithParameters(); + } + return this; + } + + private void determineType(SessionLocal session) { + int l = args.length; + boolean anyArray = false, allBinary = true, allCharacter = true; + for (int i = 0; i < l; i++) { + Expression arg = args[i].optimize(session); + args[i] = arg; + int t = arg.getType().getValueType(); + if (t == Value.ARRAY) { + anyArray = true; + allBinary = allCharacter = false; + } else if (t == Value.NULL) { + // Ignore NULL literals + } else if (DataType.isBinaryStringType(t)) { + allCharacter = false; + } else if (DataType.isCharacterStringType(t)) { + allBinary = false; + } else { + allBinary = allCharacter = false; + } + } + if (anyArray) { + type = TypeInfo.getTypeInfo(Value.ARRAY, -1, 0, TypeInfo.getHigherType(args).getExtTypeInfo()); + } else if (allBinary) { + long precision = getPrecision(0); + for (int i = 1; i < l; i++) { + precision = DataType.addPrecision(precision, getPrecision(i)); + } + type = TypeInfo.getTypeInfo(Value.VARBINARY, precision, 0, null); + } else if (allCharacter) { + long precision = getPrecision(0); + for (int i = 1; i < l; i++) { + precision = DataType.addPrecision(precision, getPrecision(i)); + } + type = TypeInfo.getTypeInfo(Value.VARCHAR, precision, 0, null); + } else { + type = TypeInfo.TYPE_VARCHAR; + } + } + + private long getPrecision(int i) { + TypeInfo t = args[i].getType(); + return t.getValueType() != Value.NULL ? t.getPrecision() : 0L; + } + + private void inlineArguments() { + int valueType = type.getValueType(); + int l = args.length; + int count = l; + for (int i = 0; i < l; i++) { + Expression arg = args[i]; + if (arg instanceof ConcatenationOperation && arg.getType().getValueType() == valueType) { + count += arg.getSubexpressionCount() - 1; + } + } + if (count > l) { + Expression[] newArguments = new Expression[count]; + for (int i = 0, offset = 0; i < l; i++) { + Expression arg = args[i]; + if (arg instanceof ConcatenationOperation && arg.getType().getValueType() == valueType) { + ConcatenationOperation c = (ConcatenationOperation) arg; + Expression[] innerArgs = c.args; + int innerLength = innerArgs.length; + System.arraycopy(innerArgs, 0, newArguments, offset, innerLength); + offset += innerLength; + } else { + newArguments[offset++] = arg; + } + } + args = newArguments; + argsCount = count; + } + } + + private static boolean isEmpty(Value v) { + int valueType = v.getValueType(); + if (valueType == Value.VARCHAR) { + return v.getString().isEmpty(); + } else if (valueType == Value.VARBINARY) { + return v.getBytesNoCopy().length == 0; + } else { + return ((ValueArray) v).getList().length == 0; + } + } + +} diff --git a/h2/src/main/org/h2/expression/DomainValueExpression.java b/h2/src/main/org/h2/expression/DomainValueExpression.java new file mode 100644 index 0000000..e183120 --- /dev/null +++ b/h2/src/main/org/h2/expression/DomainValueExpression.java @@ -0,0 +1,78 @@ +/* + * 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.expression; + +import org.h2.api.ErrorCode; +import org.h2.constraint.DomainColumnResolver; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.table.ColumnResolver; +import org.h2.util.ParserUtil; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * An expression representing a value for domain constraint. + */ +public final class DomainValueExpression extends Operation0 { + + private DomainColumnResolver columnResolver; + + public DomainValueExpression() { + } + + @Override + public Value getValue(SessionLocal session) { + return columnResolver.getValue(null); + } + + @Override + public TypeInfo getType() { + return columnResolver.getValueType(); + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + if (resolver instanceof DomainColumnResolver) { + columnResolver = (DomainColumnResolver) resolver; + } + } + + @Override + public Expression optimize(SessionLocal session) { + if (columnResolver == null) { + throw DbException.get(ErrorCode.COLUMN_NOT_FOUND_1, "VALUE"); + } + return this; + } + + @Override + public boolean isValueSet() { + return columnResolver.getValue(null) != null; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + if (columnResolver != null) { + String name = columnResolver.getColumnName(); + if (name != null) { + return ParserUtil.quoteIdentifier(builder, name, sqlFlags); + } + } + return builder.append("VALUE"); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return true; + } + + @Override + public int getCost() { + return 1; + } + +} diff --git a/h2/src/main/org/h2/expression/Expression.java b/h2/src/main/org/h2/expression/Expression.java new file mode 100644 index 0000000..7718e6e --- /dev/null +++ b/h2/src/main/org/h2/expression/Expression.java @@ -0,0 +1,536 @@ +/* + * 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.expression; + +import java.util.List; + +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.engine.SessionLocal; +import org.h2.expression.function.NamedExpression; +import org.h2.message.DbException; +import org.h2.table.Column; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.util.HasSQL; +import org.h2.util.StringUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Typed; +import org.h2.value.Value; + +/** + * An expression is a operation, a value, or a function in a query. + */ +public abstract class Expression implements HasSQL, Typed { + + /** + * Initial state for {@link #mapColumns(ColumnResolver, int, int)}. + */ + public static final int MAP_INITIAL = 0; + + /** + * State for expressions inside a window function for + * {@link #mapColumns(ColumnResolver, int, int)}. + */ + public static final int MAP_IN_WINDOW = 1; + + /** + * State for expressions inside an aggregate for + * {@link #mapColumns(ColumnResolver, int, int)}. + */ + public static final int MAP_IN_AGGREGATE = 2; + + /** + * Wrap expression in parentheses only if it can't be safely included into + * other expressions without them. + */ + public static final int AUTO_PARENTHESES = 0; + + /** + * Wrap expression in parentheses unconditionally. + */ + public static final int WITH_PARENTHESES = 1; + + /** + * Do not wrap expression in parentheses. + */ + public static final int WITHOUT_PARENTHESES = 2; + + private boolean addedToFilter; + + /** + * Get the SQL snippet for a list of expressions. + * + * @param builder the builder to append the SQL to + * @param expressions the list of expressions + * @param sqlFlags formatting flags + * @return the specified string builder + */ + public static StringBuilder writeExpressions(StringBuilder builder, List expressions, + int sqlFlags) { + for (int i = 0, length = expressions.size(); i < length; i++) { + if (i > 0) { + builder.append(", "); + } + expressions.get(i).getUnenclosedSQL(builder, sqlFlags); + } + return builder; + } + + /** + * Get the SQL snippet for an array of expressions. + * + * @param builder the builder to append the SQL to + * @param expressions the list of expressions + * @param sqlFlags formatting flags + * @return the specified string builder + */ + public static StringBuilder writeExpressions(StringBuilder builder, Expression[] expressions, int sqlFlags) { + for (int i = 0, length = expressions.length; i < length; i++) { + if (i > 0) { + builder.append(", "); + } + Expression e = expressions[i]; + if (e == null) { + builder.append("DEFAULT"); + } else { + e.getUnenclosedSQL(builder, sqlFlags); + } + } + return builder; + } + + /** + * Return the resulting value for the current row. + * + * @param session the session + * @return the result + */ + public abstract Value getValue(SessionLocal session); + + /** + * Returns the data type. The data type may be unknown before the + * optimization phase. + * + * @return the data type + */ + @Override + public abstract TypeInfo getType(); + + /** + * Map the columns of the resolver to expression columns. + * + * @param resolver the column resolver + * @param level the subquery nesting level + * @param state current state for nesting checks, initial value is + * {@link #MAP_INITIAL} + */ + public abstract void mapColumns(ColumnResolver resolver, int level, int state); + + /** + * Try to optimize the expression. + * + * @param session the session + * @return the optimized expression + */ + public abstract Expression optimize(SessionLocal session); + + /** + * Try to optimize or remove the condition. + * + * @param session the session + * @return the optimized condition, or {@code null} + */ + public final Expression optimizeCondition(SessionLocal session) { + Expression e = optimize(session); + if (e.isConstant()) { + return e.getBooleanValue(session) ? null : ValueExpression.FALSE; + } + return e; + } + + /** + * Tell the expression columns whether the table filter can return values + * now. This is used when optimizing the query. + * + * @param tableFilter the table filter + * @param value true if the table filter can return value + */ + public abstract void setEvaluatable(TableFilter tableFilter, boolean value); + + @Override + public final String getSQL(int sqlFlags) { + return getSQL(new StringBuilder(), sqlFlags, AUTO_PARENTHESES).toString(); + } + + @Override + public final StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return getSQL(builder, sqlFlags, AUTO_PARENTHESES); + } + + /** + * Get the SQL statement of this expression. This may not always be the + * original SQL statement, especially after optimization. + * + * @param sqlFlags + * formatting flags + * @param parentheses + * parentheses mode + * @return the SQL statement + */ + public final String getSQL(int sqlFlags, int parentheses) { + return getSQL(new StringBuilder(), sqlFlags, parentheses).toString(); + } + + /** + * Get the SQL statement of this expression. This may not always be the + * original SQL statement, especially after optimization. + * + * @param builder + * string builder + * @param sqlFlags + * formatting flags + * @param parentheses + * parentheses mode + * @return the specified string builder + */ + public final StringBuilder getSQL(StringBuilder builder, int sqlFlags, int parentheses) { + return parentheses == WITH_PARENTHESES || parentheses != WITHOUT_PARENTHESES && needParentheses() + ? getUnenclosedSQL(builder.append('('), sqlFlags).append(')') + : getUnenclosedSQL(builder, sqlFlags); + } + + /** + * Returns whether this expressions needs to be wrapped in parentheses when + * it is used as an argument of other expressions. + * + * @return {@code true} if it is + */ + public boolean needParentheses() { + return false; + } + + /** + * Get the SQL statement of this expression. This may not always be the + * original SQL statement, especially after optimization. Enclosing '(' and + * ')' are always appended. + * + * @param builder + * string builder + * @param sqlFlags + * formatting flags + * @return the specified string builder + */ + public final StringBuilder getEnclosedSQL(StringBuilder builder, int sqlFlags) { + return getUnenclosedSQL(builder.append('('), sqlFlags).append(')'); + } + + /** + * Get the SQL statement of this expression. This may not always be the + * original SQL statement, especially after optimization. Enclosing '(' and + * ')' are never appended. + * + * @param builder + * string builder + * @param sqlFlags + * formatting flags + * @return the specified string builder + */ + public abstract StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags); + + /** + * Update an aggregate value. This method is called at statement execution + * time. It is usually called once for each row, but if the expression is + * used multiple times (for example in the column list, and as part of the + * HAVING expression) it is called multiple times - the row counter needs to + * be used to make sure the internal state is only updated once. + * + * @param session the session + * @param stage select stage + */ + public abstract void updateAggregate(SessionLocal session, int stage); + + /** + * Check if this expression and all sub-expressions can fulfill a criteria. + * If any part returns false, the result is false. + * + * @param visitor the visitor + * @return if the criteria can be fulfilled + */ + public abstract boolean isEverything(ExpressionVisitor visitor); + + /** + * Estimate the cost to process the expression. + * Used when optimizing the query, to calculate the query plan + * with the lowest estimated cost. + * + * @return the estimated cost + */ + public abstract int getCost(); + + /** + * If it is possible, return the negated expression. This is used + * to optimize NOT expressions: NOT ID>10 can be converted to + * ID<=10. Returns null if negating is not possible. + * + * @param session the session + * @return the negated expression, or null + */ + public Expression getNotIfPossible(@SuppressWarnings("unused") SessionLocal session) { + // by default it is not possible + return null; + } + + /** + * Check if this expression will always return the same value. + * + * @return if the expression is constant + */ + public boolean isConstant() { + return false; + } + + /** + * Check if this expression will always return the NULL value. + * + * @return if the expression is constant NULL value + */ + public boolean isNullConstant() { + return false; + } + + /** + * Is the value of a parameter set. + * + * @return true if set + */ + public boolean isValueSet() { + return false; + } + + /** + * Check if this is an identity column. + * + * @return true if it is an identity column + */ + public boolean isIdentity() { + return false; + } + + /** + * Get the value in form of a boolean expression. + * Returns true or false. + * In this database, everything can be a condition. + * + * @param session the session + * @return the result + */ + public boolean getBooleanValue(SessionLocal session) { + return getValue(session).isTrue(); + } + + /** + * Create index conditions if possible and attach them to the table filter. + * + * @param session the session + * @param filter the table filter + */ + @SuppressWarnings("unused") + public void createIndexConditions(SessionLocal session, TableFilter filter) { + // default is do nothing + } + + /** + * Get the column name or alias name of this expression. + * + * @param session the session + * @param columnIndex 0-based column index + * @return the column name + */ + public String getColumnName(SessionLocal session, int columnIndex) { + return getAlias(session, columnIndex); + } + + /** + * Get the schema name, or null + * + * @return the schema name + */ + public String getSchemaName() { + return null; + } + + /** + * Get the table name, or null + * + * @return the table name + */ + public String getTableName() { + return null; + } + + /** + * Check whether this expression is a column and can store NULL. + * + * @return whether NULL is allowed + */ + public int getNullable() { + return Column.NULLABLE_UNKNOWN; + } + + /** + * Get the table alias name or null + * if this expression does not represent a column. + * + * @return the table alias name + */ + public String getTableAlias() { + return null; + } + + /** + * Get the alias name of a column or SQL expression + * if it is not an aliased expression. + * + * @param session the session + * @param columnIndex 0-based column index + * @return the alias name + */ + public String getAlias(SessionLocal session, int columnIndex) { + switch (session.getMode().expressionNames) { + default: { + String sql = getSQL(QUOTE_ONLY_WHEN_REQUIRED | NO_CASTS, WITHOUT_PARENTHESES); + if (sql.length() <= Constants.MAX_IDENTIFIER_LENGTH) { + return sql; + } + } + //$FALL-THROUGH$ + case C_NUMBER: + return "C" + (columnIndex + 1); + case EMPTY: + return ""; + case NUMBER: + return Integer.toString(columnIndex + 1); + case POSTGRESQL_STYLE: + if (this instanceof NamedExpression) { + return StringUtils.toLowerEnglish(((NamedExpression) this).getName()); + } + return "?column?"; + } + } + + /** + * Get the column name of this expression for a view. + * + * @param session the session + * @param columnIndex 0-based column index + * @return the column name for a view + */ + public String getColumnNameForView(SessionLocal session, int columnIndex) { + switch (session.getMode().viewExpressionNames) { + case AS_IS: + default: + return getAlias(session, columnIndex); + case EXCEPTION: + throw DbException.get(ErrorCode.COLUMN_ALIAS_IS_NOT_SPECIFIED_1, getTraceSQL()); + case MYSQL_STYLE: { + String name = getSQL(QUOTE_ONLY_WHEN_REQUIRED | NO_CASTS, WITHOUT_PARENTHESES); + if (name.length() > 64) { + name = "Name_exp_" + (columnIndex + 1); + } + return name; + } + } + } + + /** + * Returns the main expression, skipping aliases. + * + * @return the expression + */ + public Expression getNonAliasExpression() { + return this; + } + + /** + * Add conditions to a table filter if they can be evaluated. + * + * @param filter the table filter + */ + public void addFilterConditions(TableFilter filter) { + if (!addedToFilter && isEverything(ExpressionVisitor.EVALUATABLE_VISITOR)) { + filter.addFilterCondition(this, false); + addedToFilter = true; + } + } + + /** + * Convert this expression to a String. + * + * @return the string representation + */ + @Override + public String toString() { + return getTraceSQL(); + } + + /** + * Returns count of subexpressions. + * + * @return count of subexpressions + */ + public int getSubexpressionCount() { + return 0; + } + + /** + * Returns subexpression with specified index. + * + * @param index 0-based index + * @return subexpression with specified index, may be null + * @throws IndexOutOfBoundsException if specified index is not valid + */ + public Expression getSubexpression(int index) { + throw new IndexOutOfBoundsException(); + } + + /** + * Return the resulting value of when operand for the current row. + * + * @param session + * the session + * @param left + * value on the left side + * @return the result + */ + public boolean getWhenValue(SessionLocal session, Value left) { + return session.compareWithNull(left, getValue(session), true) == 0; + } + + /** + * Appends the SQL statement of this when operand to the specified builder. + * + * @param builder + * string builder + * @param sqlFlags + * formatting flags + * @return the specified string builder + */ + public StringBuilder getWhenSQL(StringBuilder builder, int sqlFlags) { + return getUnenclosedSQL(builder.append(' '), sqlFlags); + } + + /** + * Returns whether this expression is a right side of condition in a when + * operand. + * + * @return {@code true} if it is, {@code false} otherwise + */ + public boolean isWhenConditionOperand() { + return false; + } + +} diff --git a/h2/src/main/org/h2/expression/ExpressionColumn.java b/h2/src/main/org/h2/expression/ExpressionColumn.java new file mode 100644 index 0000000..5bc7a7d --- /dev/null +++ b/h2/src/main/org/h2/expression/ExpressionColumn.java @@ -0,0 +1,490 @@ +/* + * 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.expression; + +import org.h2.api.ErrorCode; +import org.h2.command.query.Select; +import org.h2.command.query.SelectGroups; +import org.h2.command.query.SelectListColumnResolver; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.expression.analysis.DataAnalysisOperation; +import org.h2.expression.condition.Comparison; +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; +import org.h2.table.ColumnResolver; +import org.h2.table.Table; +import org.h2.table.TableFilter; +import org.h2.util.ParserUtil; +import org.h2.util.StringUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBigint; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueDecfloat; +import org.h2.value.ValueDouble; +import org.h2.value.ValueInteger; +import org.h2.value.ValueNumeric; +import org.h2.value.ValueReal; +import org.h2.value.ValueSmallint; +import org.h2.value.ValueTinyint; + +/** + * A column reference expression that represents a column of a table or view. + */ +public final class ExpressionColumn extends Expression { + + private final Database database; + private final String schemaName; + private final String tableAlias; + private final String columnName; + private final boolean rowId; + private final boolean quotedName; + private ColumnResolver columnResolver; + private int queryLevel; + private Column column; + + /** + * Creates a new column reference for metadata of queries; should not be + * used as normal expression. + * + * @param database + * the database + * @param column + * the column + */ + public ExpressionColumn(Database database, Column column) { + this.database = database; + this.column = column; + columnName = tableAlias = schemaName = null; + rowId = column.isRowId(); + quotedName = true; + } + + /** + * Creates a new instance of column reference for regular columns as normal + * expression. + * + * @param database + * the database + * @param schemaName + * the schema name, or {@code null} + * @param tableAlias + * the table alias name, table name, or {@code null} + * @param columnName + * the column name + */ + public ExpressionColumn(Database database, String schemaName, String tableAlias, String columnName) { + this(database, schemaName, tableAlias, columnName, true); + } + + /** + * Creates a new instance of column reference for regular columns as normal + * expression. + * + * @param database + * the database + * @param schemaName + * the schema name, or {@code null} + * @param tableAlias + * the table alias name, table name, or {@code null} + * @param columnName + * the column name + * @param quotedName + * whether name was quoted + */ + public ExpressionColumn(Database database, String schemaName, String tableAlias, String columnName, + boolean quotedName) { + this.database = database; + this.schemaName = schemaName; + this.tableAlias = tableAlias; + this.columnName = columnName; + rowId = false; + this.quotedName = quotedName; + } + + /** + * Creates a new instance of column reference for {@code _ROWID_} column as + * normal expression. + * + * @param database + * the database + * @param schemaName + * the schema name, or {@code null} + * @param tableAlias + * the table alias name, table name, or {@code null} + */ + public ExpressionColumn(Database database, String schemaName, String tableAlias) { + this.database = database; + this.schemaName = schemaName; + this.tableAlias = tableAlias; + columnName = Column.ROWID; + quotedName = rowId = true; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + if (schemaName != null) { + ParserUtil.quoteIdentifier(builder, schemaName, sqlFlags).append('.'); + } + if (tableAlias != null) { + ParserUtil.quoteIdentifier(builder, tableAlias, sqlFlags).append('.'); + } + if (column != null) { + if (columnResolver != null && columnResolver.hasDerivedColumnList()) { + ParserUtil.quoteIdentifier(builder, columnResolver.getColumnName(column), sqlFlags); + } else { + column.getSQL(builder, sqlFlags); + } + } else if (rowId) { + builder.append(columnName); + } else { + ParserUtil.quoteIdentifier(builder, columnName, sqlFlags); + } + return builder; + } + + public TableFilter getTableFilter() { + return columnResolver == null ? null : columnResolver.getTableFilter(); + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + if (tableAlias != null && !database.equalsIdentifiers(tableAlias, resolver.getTableAlias())) { + return; + } + if (schemaName != null && !database.equalsIdentifiers(schemaName, resolver.getSchemaName())) { + return; + } + if (rowId) { + Column col = resolver.getRowIdColumn(); + if (col != null) { + mapColumn(resolver, col, level); + } + return; + } + Column col = resolver.findColumn(columnName); + if (col != null) { + mapColumn(resolver, col, level); + return; + } + Column[] columns = resolver.getSystemColumns(); + for (int i = 0; columns != null && i < columns.length; i++) { + col = columns[i]; + if (database.equalsIdentifiers(columnName, col.getName())) { + mapColumn(resolver, col, level); + return; + } + } + } + + private void mapColumn(ColumnResolver resolver, Column col, int level) { + if (this.columnResolver == null) { + queryLevel = level; + column = col; + this.columnResolver = resolver; + } else if (queryLevel == level && this.columnResolver != resolver) { + if (resolver instanceof SelectListColumnResolver) { + // ignore - already mapped, that's ok + } else { + throw DbException.get(ErrorCode.AMBIGUOUS_COLUMN_NAME_1, columnName); + } + } + } + + @Override + public Expression optimize(SessionLocal session) { + if (columnResolver == null) { + Schema schema = session.getDatabase().findSchema( + tableAlias == null ? session.getCurrentSchemaName() : tableAlias); + if (schema != null) { + Constant constant = schema.findConstant(columnName); + if (constant != null) { + return constant.getValue(); + } + } + return optimizeOther(); + } + return columnResolver.optimize(this, column); + } + + private Expression optimizeOther() { + if (tableAlias == null && !quotedName) { + Expression e = ModeFunction.getCompatibilityDateTimeValueFunction(database, + StringUtils.toUpperEnglish(columnName), -1); + if (e != null) { + return e; + } + } + throw getColumnException(ErrorCode.COLUMN_NOT_FOUND_1); + } + + /** + * Get exception to throw, with column and table info added + * + * @param code SQL error code + * @return DbException + */ + public DbException getColumnException(int code) { + String name = columnName; + if (tableAlias != null) { + if (schemaName != null) { + name = schemaName + '.' + tableAlias + '.' + name; + } else { + name = tableAlias + '.' + name; + } + } + return DbException.get(code, name); + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + Select select = columnResolver.getSelect(); + if (select == null) { + throw DbException.get(ErrorCode.MUST_GROUP_BY_COLUMN_1, getTraceSQL()); + } + if (stage == DataAnalysisOperation.STAGE_RESET) { + return; + } + SelectGroups groupData = select.getGroupDataIfCurrent(false); + if (groupData == null) { + // this is a different level (the enclosing query) + return; + } + Value v = (Value) groupData.getCurrentGroupExprData(this); + if (v == null) { + groupData.setCurrentGroupExprData(this, columnResolver.getValue(column)); + } else if (!select.isGroupWindowStage2()) { + if (!session.areEqual(columnResolver.getValue(column), v)) { + throw DbException.get(ErrorCode.MUST_GROUP_BY_COLUMN_1, getTraceSQL()); + } + } + } + + @Override + public Value getValue(SessionLocal session) { + Select select = columnResolver.getSelect(); + if (select != null) { + SelectGroups groupData = select.getGroupDataIfCurrent(false); + if (groupData != null) { + Value v = (Value) groupData.getCurrentGroupExprData(this); + if (v != null) { + return v; + } + if (select.isGroupWindowStage2()) { + throw DbException.get(ErrorCode.MUST_GROUP_BY_COLUMN_1, getTraceSQL()); + } + } + } + Value value = columnResolver.getValue(column); + if (value == null) { + if (select == null) { + throw DbException.get(ErrorCode.NULL_NOT_ALLOWED, getTraceSQL()); + } else { + throw DbException.get(ErrorCode.MUST_GROUP_BY_COLUMN_1, getTraceSQL()); + } + } + return value; + } + + @Override + public TypeInfo getType() { + return column != null ? column.getType() : rowId ? TypeInfo.TYPE_BIGINT : TypeInfo.TYPE_UNKNOWN; + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + } + + public Column getColumn() { + return column; + } + + public String getOriginalColumnName() { + return columnName; + } + + public String getOriginalTableAliasName() { + return tableAlias; + } + + @Override + public String getColumnName(SessionLocal session, int columnIndex) { + if (column != null) { + if (columnResolver != null) { + return columnResolver.getColumnName(column); + } + return column.getName(); + } + return columnName; + } + + @Override + public String getSchemaName() { + Table table = column.getTable(); + return table == null ? null : table.getSchema().getName(); + } + + @Override + public String getTableName() { + Table table = column.getTable(); + return table == null ? null : table.getName(); + } + + @Override + public String getAlias(SessionLocal session, int columnIndex) { + if (column != null) { + if (columnResolver != null) { + return columnResolver.getColumnName(column); + } + return column.getName(); + } + if (tableAlias != null) { + return tableAlias + '.' + columnName; + } + return columnName; + } + + @Override + public String getColumnNameForView(SessionLocal session, int columnIndex) { + return getAlias(session, columnIndex); + } + + @Override + public boolean isIdentity() { + return column.isIdentity(); + } + + @Override + public int getNullable() { + return column.isNullable() ? Column.NULLABLE : Column.NOT_NULLABLE; + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + switch (visitor.getType()) { + case ExpressionVisitor.OPTIMIZABLE_AGGREGATE: + return false; + case ExpressionVisitor.INDEPENDENT: + return this.queryLevel < visitor.getQueryLevel(); + case ExpressionVisitor.EVALUATABLE: + // if this column belongs to a 'higher level' query and is + // therefore just a parameter + if (visitor.getQueryLevel() < this.queryLevel) { + return true; + } + if (getTableFilter() == null) { + return false; + } + return getTableFilter().isEvaluatable(); + case ExpressionVisitor.SET_MAX_DATA_MODIFICATION_ID: + visitor.addDataModificationId(column.getTable().getMaxDataModificationId()); + return true; + case ExpressionVisitor.NOT_FROM_RESOLVER: + return columnResolver != visitor.getResolver(); + case ExpressionVisitor.GET_DEPENDENCIES: + if (column != null) { + visitor.addDependency(column.getTable()); + } + return true; + case ExpressionVisitor.GET_COLUMNS1: + if (column == null) { + throw DbException.get(ErrorCode.COLUMN_NOT_FOUND_1, getTraceSQL()); + } + visitor.addColumn1(column); + return true; + case ExpressionVisitor.GET_COLUMNS2: + if (column == null) { + throw DbException.get(ErrorCode.COLUMN_NOT_FOUND_1, getTraceSQL()); + } + visitor.addColumn2(column); + return true; + case ExpressionVisitor.DECREMENT_QUERY_LEVEL: { + if (column == null) { + throw DbException.get(ErrorCode.COLUMN_NOT_FOUND_1, getTraceSQL()); + } + if (visitor.getColumnResolvers().contains(columnResolver)) { + int decrement = visitor.getQueryLevel(); + if (decrement > 0) { + if (queryLevel > 0) { + queryLevel--; + return true; + } + throw DbException.getInternalError("queryLevel=0"); + } + return queryLevel > 0; + } + } + //$FALL-THROUGH$ + default: + return true; + } + } + + @Override + public int getCost() { + return 2; + } + + @Override + public void createIndexConditions(SessionLocal session, TableFilter filter) { + TableFilter tf = getTableFilter(); + if (filter == tf && column.getType().getValueType() == Value.BOOLEAN) { + filter.addIndexCondition(IndexCondition.get(Comparison.EQUAL, this, ValueExpression.TRUE)); + } + } + + @Override + public Expression getNotIfPossible(SessionLocal session) { + Expression o = optimize(session); + if (o != this) { + return o.getNotIfPossible(session); + } + Value v; + switch (column.getType().getValueType()) { + case Value.BOOLEAN: + v = ValueBoolean.FALSE; + break; + case Value.TINYINT: + v = ValueTinyint.get((byte) 0); + break; + case Value.SMALLINT: + v = ValueSmallint.get((short) 0); + break; + case Value.INTEGER: + v = ValueInteger.get(0); + break; + case Value.BIGINT: + v = ValueBigint.get(0L); + break; + case Value.NUMERIC: + v = ValueNumeric.ZERO; + break; + case Value.REAL: + v = ValueReal.ZERO; + break; + case Value.DOUBLE: + v = ValueDouble.ZERO; + break; + case Value.DECFLOAT: + v = ValueDecfloat.ZERO; + break; + default: + /* + * Can be replaced with CAST(column AS BOOLEAN) = FALSE, but this + * replacement can't be optimized further, so it's better to leave + * NOT (column) as is. + */ + return null; + } + return new Comparison(Comparison.EQUAL, this, ValueExpression.get(v), false); + } + +} diff --git a/h2/src/main/org/h2/expression/ExpressionList.java b/h2/src/main/org/h2/expression/ExpressionList.java new file mode 100644 index 0000000..25c38c1 --- /dev/null +++ b/h2/src/main/org/h2/expression/ExpressionList.java @@ -0,0 +1,140 @@ +/* + * 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.expression; + +import org.h2.engine.SessionLocal; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.value.ExtTypeInfoRow; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueArray; +import org.h2.value.ValueRow; + +/** + * A list of expressions, as in (ID, NAME). + * The result of this expression is a row or an array. + */ +public final class ExpressionList extends Expression { + + private final Expression[] list; + private final boolean isArray; + private TypeInfo type; + + public ExpressionList(Expression[] list, boolean isArray) { + this.list = list; + this.isArray = isArray; + } + + @Override + public Value getValue(SessionLocal session) { + Value[] v = new Value[list.length]; + for (int i = 0; i < list.length; i++) { + v[i] = list[i].getValue(session); + } + return isArray ? ValueArray.get((TypeInfo) type.getExtTypeInfo(), v, session) : ValueRow.get(type, v); + } + + @Override + public TypeInfo getType() { + return type; + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + for (Expression e : list) { + e.mapColumns(resolver, level, state); + } + } + + @Override + public Expression optimize(SessionLocal session) { + boolean allConst = true; + int count = list.length; + for (int i = 0; i < count; i++) { + Expression e = list[i].optimize(session); + if (!e.isConstant()) { + allConst = false; + } + list[i] = e; + } + initializeType(); + if (allConst) { + return ValueExpression.get(getValue(session)); + } + return this; + } + + void initializeType() { + type = isArray ? TypeInfo.getTypeInfo(Value.ARRAY, list.length, 0, TypeInfo.getHigherType(list)) + : TypeInfo.getTypeInfo(Value.ROW, 0, 0, new ExtTypeInfoRow(list)); + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + for (Expression e : list) { + e.setEvaluatable(tableFilter, b); + } + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return isArray // + ? writeExpressions(builder.append("ARRAY ["), list, sqlFlags).append(']') + : writeExpressions(builder.append("ROW ("), list, sqlFlags).append(')'); + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + for (Expression e : list) { + e.updateAggregate(session, stage); + } + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + for (Expression e : list) { + if (!e.isEverything(visitor)) { + return false; + } + } + return true; + } + + @Override + public int getCost() { + int cost = 1; + for (Expression e : list) { + cost += e.getCost(); + } + return cost; + } + + @Override + public boolean isConstant() { + for (Expression e : list) { + if (!e.isConstant()) { + return false; + } + } + return true; + } + + @Override + public int getSubexpressionCount() { + return list.length; + } + + @Override + public Expression getSubexpression(int index) { + return list[index]; + } + + public boolean isArray() { + return isArray; + } + +} diff --git a/h2/src/main/org/h2/expression/ExpressionVisitor.java b/h2/src/main/org/h2/expression/ExpressionVisitor.java new file mode 100644 index 0000000..7f2660f --- /dev/null +++ b/h2/src/main/org/h2/expression/ExpressionVisitor.java @@ -0,0 +1,416 @@ +/* + * 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.expression; + +import java.util.HashSet; +import org.h2.command.query.AllColumnsForPlan; +import org.h2.engine.DbObject; +import org.h2.table.Column; +import org.h2.table.ColumnResolver; +import org.h2.table.Table; +import org.h2.table.TableFilter; + +/** + * The visitor pattern is used to iterate through all expressions of a query + * to optimize a statement. + */ +public final class ExpressionVisitor { + + /** + * Is the value independent on unset parameters or on columns of a higher + * level query, or sequence values (that means can it be evaluated right + * now)? + */ + public static final int INDEPENDENT = 0; + + /** + * The visitor singleton for the type INDEPENDENT. + */ + public static final ExpressionVisitor INDEPENDENT_VISITOR = + new ExpressionVisitor(INDEPENDENT); + + /** + * Are all aggregates MIN(column), MAX(column), COUNT(*), MEDIAN(column), + * ENVELOPE(count) for the given table (getTable)? + */ + public static final int OPTIMIZABLE_AGGREGATE = 1; + + /** + * Does the expression return the same results for the same parameters? + */ + public static final int DETERMINISTIC = 2; + + /** + * The visitor singleton for the type DETERMINISTIC. + */ + public static final ExpressionVisitor DETERMINISTIC_VISITOR = + new ExpressionVisitor(DETERMINISTIC); + + /** + * Can the expression be evaluated, that means are all columns set to + * 'evaluatable'? + */ + public static final int EVALUATABLE = 3; + + /** + * The visitor singleton for the type EVALUATABLE. + */ + public static final ExpressionVisitor EVALUATABLE_VISITOR = + new ExpressionVisitor(EVALUATABLE); + + /** + * Count of cached INDEPENDENT and EVALUATABLE visitors with different query + * level. + */ + private static final int CACHED = 8; + + /** + * INDEPENDENT listeners with query level 0, 1, ... + */ + private static final ExpressionVisitor[] INDEPENDENT_VISITORS; + + /** + * EVALUATABLE listeners with query level 0, 1, ... + */ + private static final ExpressionVisitor[] EVALUATABLE_VISITORS; + + static { + ExpressionVisitor[] a = new ExpressionVisitor[CACHED]; + a[0] = INDEPENDENT_VISITOR; + for (int i = 1; i < CACHED; i++) { + a[i] = new ExpressionVisitor(INDEPENDENT, i); + } + INDEPENDENT_VISITORS = a; + a = new ExpressionVisitor[CACHED]; + a[0] = EVALUATABLE_VISITOR; + for (int i = 1; i < CACHED; i++) { + a[i] = new ExpressionVisitor(EVALUATABLE, i); + } + EVALUATABLE_VISITORS = a; + } + + /** + * Request to set the latest modification id (addDataModificationId). + */ + public static final int SET_MAX_DATA_MODIFICATION_ID = 4; + + /** + * Does the expression have no side effects (change the data)? + */ + public static final int READONLY = 5; + + /** + * The visitor singleton for the type EVALUATABLE. + */ + public static final ExpressionVisitor READONLY_VISITOR = + new ExpressionVisitor(READONLY); + + /** + * Does an expression have no relation to the given table filter + * (getResolver)? + */ + public static final int NOT_FROM_RESOLVER = 6; + + /** + * Request to get the set of dependencies (addDependency). + */ + public static final int GET_DEPENDENCIES = 7; + + /** + * Can the expression be added to a condition of an outer query. Example: + * ROWNUM() can't be added as a condition to the inner query of select id + * from (select t.*, rownum as r from test t) where r between 2 and 3; Also + * a sequence expression must not be used. + */ + public static final int QUERY_COMPARABLE = 8; + + /** + * Get all referenced columns for the optimiser. + */ + public static final int GET_COLUMNS1 = 9; + + /** + * Get all referenced columns. + */ + public static final int GET_COLUMNS2 = 10; + + /** + * Decrement query level of all expression columns. + */ + public static final int DECREMENT_QUERY_LEVEL = 11; + + /** + * The visitor singleton for the type QUERY_COMPARABLE. + */ + public static final ExpressionVisitor QUERY_COMPARABLE_VISITOR = + new ExpressionVisitor(QUERY_COMPARABLE); + + private final int type; + private final int queryLevel; + private final HashSet set; + private final AllColumnsForPlan columns1; + private final Table table; + private final long[] maxDataModificationId; + private final ColumnResolver resolver; + + private ExpressionVisitor(int type, + int queryLevel, + HashSet set, + AllColumnsForPlan columns1, Table table, ColumnResolver resolver, + long[] maxDataModificationId) { + this.type = type; + this.queryLevel = queryLevel; + this.set = set; + this.columns1 = columns1; + this.table = table; + this.resolver = resolver; + this.maxDataModificationId = maxDataModificationId; + } + + private ExpressionVisitor(int type) { + this.type = type; + this.queryLevel = 0; + this.set = null; + this.columns1 = null; + this.table = null; + this.resolver = null; + this.maxDataModificationId = null; + } + + private ExpressionVisitor(int type, int queryLevel) { + this.type = type; + this.queryLevel = queryLevel; + this.set = null; + this.columns1 = null; + this.table = null; + this.resolver = null; + this.maxDataModificationId = null; + } + + /** + * Create a new visitor object to collect dependencies. + * + * @param dependencies the dependencies set + * @return the new visitor + */ + public static ExpressionVisitor getDependenciesVisitor( + HashSet dependencies) { + return new ExpressionVisitor(GET_DEPENDENCIES, 0, dependencies, null, + null, null, null); + } + + /** + * Create a new visitor to check if all aggregates are for the given table. + * + * @param table the table + * @return the new visitor + */ + public static ExpressionVisitor getOptimizableVisitor(Table table) { + return new ExpressionVisitor(OPTIMIZABLE_AGGREGATE, 0, null, + null, table, null, null); + } + + /** + * Create a new visitor to check if no expression depends on the given + * resolver. + * + * @param resolver the resolver + * @return the new visitor + */ + public static ExpressionVisitor getNotFromResolverVisitor(ColumnResolver resolver) { + return new ExpressionVisitor(NOT_FROM_RESOLVER, 0, null, null, null, + resolver, null); + } + + /** + * Create a new visitor to get all referenced columns. + * + * @param columns the columns map + * @return the new visitor + */ + public static ExpressionVisitor getColumnsVisitor(AllColumnsForPlan columns) { + return new ExpressionVisitor(GET_COLUMNS1, 0, null, columns, null, null, null); + } + + /** + * Create a new visitor to get all referenced columns. + * + * @param columns the columns map + * @param table table to gather columns from, or {@code null} to gather all columns + * @return the new visitor + */ + public static ExpressionVisitor getColumnsVisitor(HashSet columns, Table table) { + return new ExpressionVisitor(GET_COLUMNS2, 0, columns, null, table, null, null); + } + + public static ExpressionVisitor getMaxModificationIdVisitor() { + return new ExpressionVisitor(SET_MAX_DATA_MODIFICATION_ID, 0, null, + null, null, null, new long[1]); + } + + /** + * Create a new visitor to decrement query level in columns with the + * specified resolvers. + * + * @param columnResolvers + * column resolvers + * @param queryDecrement + * 0 to check whether operation is allowed, 1 to actually perform + * the decrement + * @return the new visitor + */ + public static ExpressionVisitor getDecrementQueryLevelVisitor(HashSet columnResolvers, + int queryDecrement) { + return new ExpressionVisitor(DECREMENT_QUERY_LEVEL, queryDecrement, columnResolvers, null, null, null, null); + } + + /** + * Add a new dependency to the set of dependencies. + * This is used for GET_DEPENDENCIES visitors. + * + * @param obj the additional dependency. + */ + @SuppressWarnings("unchecked") + public void addDependency(DbObject obj) { + ((HashSet) set).add(obj); + } + + /** + * Add a new column to the set of columns. + * This is used for GET_COLUMNS visitors. + * + * @param column the additional column. + */ + void addColumn1(Column column) { + columns1.add(column); + } + + /** + * Add a new column to the set of columns. + * This is used for GET_COLUMNS2 visitors. + * + * @param column the additional column. + */ + @SuppressWarnings("unchecked") + void addColumn2(Column column) { + if (table == null || table == column.getTable()) { + ((HashSet) set).add(column); + } + } + + /** + * Get the dependency set. + * This is used for GET_DEPENDENCIES visitors. + * + * @return the set + */ + @SuppressWarnings("unchecked") + public HashSet getDependencies() { + return (HashSet) set; + } + + /** + * Increment or decrement the query level. + * + * @param offset 1 to increment, -1 to decrement + * @return this visitor or its clone with the changed query level + */ + public ExpressionVisitor incrementQueryLevel(int offset) { + if (type == INDEPENDENT) { + offset += queryLevel; + return offset < CACHED ? INDEPENDENT_VISITORS[offset] : new ExpressionVisitor(INDEPENDENT, offset); + } else if (type == EVALUATABLE) { + offset += queryLevel; + return offset < CACHED ? EVALUATABLE_VISITORS[offset] : new ExpressionVisitor(EVALUATABLE, offset); + } else { + return this; + } + } + + /** + * Get the column resolver. + * This is used for NOT_FROM_RESOLVER visitors. + * + * @return the column resolver + */ + public ColumnResolver getResolver() { + return resolver; + } + + /** + * Get the set of column resolvers. + * This is used for {@link #DECREMENT_QUERY_LEVEL} visitors. + * + * @return the set + */ + @SuppressWarnings("unchecked") + public HashSet getColumnResolvers() { + return (HashSet) set; + } + + /** + * Update the field maxDataModificationId if this value is higher + * than the current value. + * This is used for SET_MAX_DATA_MODIFICATION_ID visitors. + * + * @param value the data modification id + */ + public void addDataModificationId(long value) { + long m = maxDataModificationId[0]; + if (value > m) { + maxDataModificationId[0] = value; + } + } + + /** + * Get the last data modification. + * This is used for SET_MAX_DATA_MODIFICATION_ID visitors. + * + * @return the maximum modification id + */ + public long getMaxDataModificationId() { + return maxDataModificationId[0]; + } + + int getQueryLevel() { + assert type == INDEPENDENT || type == EVALUATABLE || type == DECREMENT_QUERY_LEVEL; + return queryLevel; + } + + /** + * Get the table. + * This is used for OPTIMIZABLE_MIN_MAX_COUNT_ALL visitors. + * + * @return the table + */ + public Table getTable() { + return table; + } + + /** + * Get the visitor type. + * + * @return the type + */ + public int getType() { + return type; + } + + /** + * Get the set of columns of all tables. + * + * @param filters the filters + * @param allColumnsSet the on-demand all-columns set + */ + public static void allColumnsForTableFilters(TableFilter[] filters, AllColumnsForPlan allColumnsSet) { + for (TableFilter filter : filters) { + if (filter.getSelect() != null) { + filter.getSelect().isEverything(ExpressionVisitor.getColumnsVisitor(allColumnsSet)); + } + } + } + +} diff --git a/h2/src/main/org/h2/expression/ExpressionWithFlags.java b/h2/src/main/org/h2/expression/ExpressionWithFlags.java new file mode 100644 index 0000000..6100d5d --- /dev/null +++ b/h2/src/main/org/h2/expression/ExpressionWithFlags.java @@ -0,0 +1,28 @@ +/* + * 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.expression; + +/** + * Expression with flags. + */ +public interface ExpressionWithFlags { + + /** + * Set the flags for this expression. + * + * @param flags + * the flags to set + */ + void setFlags(int flags); + + /** + * Returns the flags. + * + * @return the flags + */ + int getFlags(); + +} diff --git a/h2/src/main/org/h2/expression/ExpressionWithVariableParameters.java b/h2/src/main/org/h2/expression/ExpressionWithVariableParameters.java new file mode 100644 index 0000000..a7c0d54 --- /dev/null +++ b/h2/src/main/org/h2/expression/ExpressionWithVariableParameters.java @@ -0,0 +1,33 @@ +/* + * 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.expression; + +import org.h2.message.DbException; + +/** + * An expression with variable number of parameters. + */ +public interface ExpressionWithVariableParameters { + + /** + * Adds the parameter expression. + * + * @param param + * the expression + */ + void addParameter(Expression param); + + /** + * This method must be called after all the parameters have been set. It + * checks if the parameter count is correct when required by the + * implementation. + * + * @throws DbException + * if the parameter count is incorrect. + */ + void doneWithParameters() throws DbException; + +} diff --git a/h2/src/main/org/h2/expression/FieldReference.java b/h2/src/main/org/h2/expression/FieldReference.java new file mode 100644 index 0000000..64f33b8 --- /dev/null +++ b/h2/src/main/org/h2/expression/FieldReference.java @@ -0,0 +1,72 @@ +/* + * 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.expression; + +import java.util.Map.Entry; + +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; +import org.h2.value.Value; +import org.h2.value.ValueNull; +import org.h2.value.ValueRow; + +/** + * Field reference. + */ +public final class FieldReference extends Operation1 { + + private final String fieldName; + + private int ordinal; + + public FieldReference(Expression arg, String fieldName) { + super(arg); + this.fieldName = fieldName; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return ParserUtil.quoteIdentifier(arg.getEnclosedSQL(builder, sqlFlags).append('.'), fieldName, sqlFlags); + } + + @Override + public Value getValue(SessionLocal session) { + Value l = arg.getValue(session); + if (l != ValueNull.INSTANCE) { + return ((ValueRow) l).getList()[ordinal]; + } + return ValueNull.INSTANCE; + } + + @Override + public Expression optimize(SessionLocal session) { + arg = arg.optimize(session); + TypeInfo type = arg.getType(); + if (type.getValueType() != Value.ROW) { + throw Store.getInvalidExpressionTypeException("ROW", arg); + } + int ordinal = 0; + for (Entry entry : ((ExtTypeInfoRow) type.getExtTypeInfo()).getFields()) { + if (fieldName.equals(entry.getKey())) { + type = entry.getValue(); + this.type = type; + this.ordinal = ordinal; + if (arg.isConstant()) { + return TypedValueExpression.get(getValue(session), type); + } + return this; + } + ordinal++; + } + throw DbException.get(ErrorCode.COLUMN_NOT_FOUND_1, fieldName); + } + +} diff --git a/h2/src/main/org/h2/expression/Format.java b/h2/src/main/org/h2/expression/Format.java new file mode 100644 index 0000000..6ba27ea --- /dev/null +++ b/h2/src/main/org/h2/expression/Format.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.expression; + +import org.h2.engine.SessionLocal; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueJson; + +/** + * A format clause such as FORMAT JSON. + */ +public final class Format extends Operation1 { + + /** + * Supported formats. + */ + public enum FormatEnum { + /** + * JSON. + */ + JSON; + } + + private final FormatEnum format; + + public Format(Expression arg, FormatEnum format) { + super(arg); + this.format = format; + } + + @Override + public Value getValue(SessionLocal session) { + return getValue(arg.getValue(session)); + } + + /** + * Returns the value with applied format. + * + * @param value + * the value + * @return the value with applied format + */ + public Value getValue(Value value) { + switch (value.getValueType()) { + case Value.NULL: + return ValueJson.NULL; + case Value.VARCHAR: + case Value.VARCHAR_IGNORECASE: + case Value.CHAR: + case Value.CLOB: + return ValueJson.fromJson(value.getString()); + default: + return value.convertTo(TypeInfo.TYPE_JSON); + } + } + + @Override + public Expression optimize(SessionLocal session) { + arg = arg.optimize(session); + if (arg.isConstant()) { + return ValueExpression.get(getValue(session)); + } + if (arg instanceof Format && format == ((Format) arg).format) { + return arg; + } + type = TypeInfo.TYPE_JSON; + return this; + } + + @Override + public boolean isIdentity() { + return arg.isIdentity(); + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return arg.getSQL(builder, sqlFlags, AUTO_PARENTHESES).append(" FORMAT ").append(format.name()); + } + + @Override + public int getNullable() { + return arg.getNullable(); + } + + @Override + public String getTableName() { + return arg.getTableName(); + } + + @Override + public String getColumnName(SessionLocal session, int columnIndex) { + return arg.getColumnName(session, columnIndex); + } + +} diff --git a/h2/src/main/org/h2/expression/IntervalOperation.java b/h2/src/main/org/h2/expression/IntervalOperation.java new file mode 100644 index 0000000..8182b9c --- /dev/null +++ b/h2/src/main/org/h2/expression/IntervalOperation.java @@ -0,0 +1,380 @@ +/* + * 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.expression; + +import static org.h2.util.DateTimeUtils.NANOS_PER_DAY; +import static org.h2.util.DateTimeUtils.NANOS_PER_HOUR; +import static org.h2.util.DateTimeUtils.NANOS_PER_SECOND; +import static org.h2.util.DateTimeUtils.absoluteDayFromDateValue; +import static org.h2.util.DateTimeUtils.dateAndTimeFromValue; +import static org.h2.util.DateTimeUtils.dateTimeToValue; +import static org.h2.util.DateTimeUtils.dateValueFromAbsoluteDay; +import static org.h2.util.IntervalUtils.NANOS_PER_DAY_BI; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import org.h2.api.ErrorCode; +import org.h2.api.IntervalQualifier; +import org.h2.engine.SessionLocal; +import org.h2.expression.function.DateTimeFunction; +import org.h2.message.DbException; +import org.h2.util.DateTimeUtils; +import org.h2.util.IntervalUtils; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueDate; +import org.h2.value.ValueInterval; +import org.h2.value.ValueNull; +import org.h2.value.ValueNumeric; +import org.h2.value.ValueTime; +import org.h2.value.ValueTimeTimeZone; +import org.h2.value.ValueTimestampTimeZone; + +/** + * A mathematical operation with intervals. + */ +public class IntervalOperation extends Operation2 { + + public enum IntervalOpType { + /** + * Interval plus interval. + */ + INTERVAL_PLUS_INTERVAL, + + /** + * Interval minus interval. + */ + INTERVAL_MINUS_INTERVAL, + + /** + * Interval divided by interval (non-standard). + */ + INTERVAL_DIVIDE_INTERVAL, + + /** + * Date-time plus interval. + */ + DATETIME_PLUS_INTERVAL, + + /** + * Date-time minus interval. + */ + DATETIME_MINUS_INTERVAL, + + /** + * Interval multiplied by numeric. + */ + INTERVAL_MULTIPLY_NUMERIC, + + /** + * Interval divided by numeric. + */ + INTERVAL_DIVIDE_NUMERIC, + + /** + * Date-time minus date-time. + */ + DATETIME_MINUS_DATETIME + } + + /** + * Number of digits enough to hold + * {@code INTERVAL '999999999999999999' YEAR / INTERVAL '1' MONTH}. + */ + private static final int INTERVAL_YEAR_DIGITS = 20; + + /** + * Number of digits enough to hold + * {@code INTERVAL '999999999999999999' DAY / INTERVAL '0.000000001' SECOND}. + */ + private static final int INTERVAL_DAY_DIGITS = 32; + + private static final TypeInfo INTERVAL_DIVIDE_INTERVAL_YEAR_TYPE = TypeInfo.getTypeInfo(Value.NUMERIC, + INTERVAL_YEAR_DIGITS * 3, INTERVAL_YEAR_DIGITS * 2, null); + + private static final TypeInfo INTERVAL_DIVIDE_INTERVAL_DAY_TYPE = TypeInfo.getTypeInfo(Value.NUMERIC, + INTERVAL_DAY_DIGITS * 3, INTERVAL_DAY_DIGITS * 2, null); + + private final IntervalOpType opType; + + private TypeInfo forcedType; + + private static BigInteger nanosFromValue(SessionLocal session, Value v) { + long[] a = dateAndTimeFromValue(v, session); + return BigInteger.valueOf(absoluteDayFromDateValue(a[0])).multiply(NANOS_PER_DAY_BI) + .add(BigInteger.valueOf(a[1])); + } + + public IntervalOperation(IntervalOpType opType, Expression left, Expression right, TypeInfo forcedType) { + this(opType, left, right); + this.forcedType = forcedType; + } + + public IntervalOperation(IntervalOpType opType, Expression left, Expression right) { + super(left, right); + this.opType = opType; + int l = left.getType().getValueType(), r = right.getType().getValueType(); + switch (opType) { + case INTERVAL_PLUS_INTERVAL: + case INTERVAL_MINUS_INTERVAL: + type = TypeInfo.getTypeInfo(Value.getHigherOrder(l, r)); + break; + case INTERVAL_DIVIDE_INTERVAL: + type = DataType.isYearMonthIntervalType(l) ? INTERVAL_DIVIDE_INTERVAL_YEAR_TYPE + : INTERVAL_DIVIDE_INTERVAL_DAY_TYPE; + break; + case DATETIME_PLUS_INTERVAL: + case DATETIME_MINUS_INTERVAL: + case INTERVAL_MULTIPLY_NUMERIC: + case INTERVAL_DIVIDE_NUMERIC: + type = left.getType(); + break; + case DATETIME_MINUS_DATETIME: + if (forcedType != null) { + type = forcedType; + } else if ((l == Value.TIME || l == Value.TIME_TZ) && (r == Value.TIME || r == Value.TIME_TZ)) { + type = TypeInfo.TYPE_INTERVAL_HOUR_TO_SECOND; + } else if (l == Value.DATE && r == Value.DATE) { + type = TypeInfo.TYPE_INTERVAL_DAY; + } else { + type = TypeInfo.TYPE_INTERVAL_DAY_TO_SECOND; + } + } + } + + @Override + public boolean needParentheses() { + return forcedType == null; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + if (forcedType != null) { + getInnerSQL2(builder.append('('), sqlFlags); + getForcedTypeSQL(builder.append(") "), forcedType); + } else { + getInnerSQL2(builder, sqlFlags); + } + return builder; + } + + private void getInnerSQL2(StringBuilder builder, int sqlFlags) { + left.getSQL(builder, sqlFlags, AUTO_PARENTHESES).append(' ').append(getOperationToken()).append(' '); + right.getSQL(builder, sqlFlags, AUTO_PARENTHESES); + } + + static StringBuilder getForcedTypeSQL(StringBuilder builder, TypeInfo forcedType) { + int precision = (int) forcedType.getPrecision(); + int scale = forcedType.getScale(); + return IntervalQualifier.valueOf(forcedType.getValueType() - Value.INTERVAL_YEAR).getTypeName(builder, + precision == ValueInterval.DEFAULT_PRECISION ? -1 : (int) precision, + scale == ValueInterval.DEFAULT_SCALE ? -1 : scale, true); + } + + private char getOperationToken() { + switch (opType) { + case INTERVAL_PLUS_INTERVAL: + case DATETIME_PLUS_INTERVAL: + return '+'; + case INTERVAL_MINUS_INTERVAL: + case DATETIME_MINUS_INTERVAL: + case DATETIME_MINUS_DATETIME: + return '-'; + case INTERVAL_MULTIPLY_NUMERIC: + return '*'; + case INTERVAL_DIVIDE_INTERVAL: + case INTERVAL_DIVIDE_NUMERIC: + return '/'; + default: + throw DbException.getInternalError("opType=" + opType); + } + } + + @Override + public Value getValue(SessionLocal session) { + Value l = left.getValue(session); + Value r = right.getValue(session); + if (l == ValueNull.INSTANCE || r == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + int lType = l.getValueType(), rType = r.getValueType(); + switch (opType) { + case INTERVAL_PLUS_INTERVAL: + case INTERVAL_MINUS_INTERVAL: { + BigInteger a1 = IntervalUtils.intervalToAbsolute((ValueInterval) l); + BigInteger a2 = IntervalUtils.intervalToAbsolute((ValueInterval) r); + return IntervalUtils.intervalFromAbsolute( + IntervalQualifier.valueOf(Value.getHigherOrder(lType, rType) - Value.INTERVAL_YEAR), + opType == IntervalOpType.INTERVAL_PLUS_INTERVAL ? a1.add(a2) : a1.subtract(a2)); + } + case INTERVAL_DIVIDE_INTERVAL: + return ValueNumeric.get(IntervalUtils.intervalToAbsolute((ValueInterval) l)) + .divide(ValueNumeric.get(IntervalUtils.intervalToAbsolute((ValueInterval) r)), type); + case DATETIME_PLUS_INTERVAL: + case DATETIME_MINUS_INTERVAL: + return getDateTimeWithInterval(session, l, r, lType, rType); + case INTERVAL_MULTIPLY_NUMERIC: + case INTERVAL_DIVIDE_NUMERIC: { + BigDecimal a1 = new BigDecimal(IntervalUtils.intervalToAbsolute((ValueInterval) l)); + BigDecimal a2 = r.getBigDecimal(); + return IntervalUtils.intervalFromAbsolute(IntervalQualifier.valueOf(lType - Value.INTERVAL_YEAR), + (opType == IntervalOpType.INTERVAL_MULTIPLY_NUMERIC ? a1.multiply(a2) : a1.divide(a2)) + .toBigInteger()); + } + case DATETIME_MINUS_DATETIME: { + Value result; + if ((lType == Value.TIME || lType == Value.TIME_TZ) && (rType == Value.TIME || rType == Value.TIME_TZ)) { + long diff; + if (lType == Value.TIME && rType == Value.TIME) { + diff = ((ValueTime) l).getNanos() - ((ValueTime) r).getNanos(); + } else { + ValueTimeTimeZone left = (ValueTimeTimeZone) l.convertTo(TypeInfo.TYPE_TIME_TZ, session), + right = (ValueTimeTimeZone) r.convertTo(TypeInfo.TYPE_TIME_TZ, session); + diff = left.getNanos() - right.getNanos() + + (right.getTimeZoneOffsetSeconds() - left.getTimeZoneOffsetSeconds()) + * DateTimeUtils.NANOS_PER_SECOND; + } + boolean negative = diff < 0; + if (negative) { + diff = -diff; + } + result = ValueInterval.from(IntervalQualifier.HOUR_TO_SECOND, negative, diff / NANOS_PER_HOUR, + diff % NANOS_PER_HOUR); + } else if (forcedType != null && DataType.isYearMonthIntervalType(forcedType.getValueType())) { + long[] dt1 = dateAndTimeFromValue(l, session), dt2 = dateAndTimeFromValue(r, session); + long dateValue1 = lType == Value.TIME || lType == Value.TIME_TZ + ? session.currentTimestamp().getDateValue() + : dt1[0]; + long dateValue2 = rType == Value.TIME || rType == Value.TIME_TZ + ? session.currentTimestamp().getDateValue() + : dt2[0]; + long leading = 12L + * (DateTimeUtils.yearFromDateValue(dateValue1) - DateTimeUtils.yearFromDateValue(dateValue2)) + + DateTimeUtils.monthFromDateValue(dateValue1) - DateTimeUtils.monthFromDateValue(dateValue2); + int d1 = DateTimeUtils.dayFromDateValue(dateValue1); + int d2 = DateTimeUtils.dayFromDateValue(dateValue2); + if (leading >= 0) { + if (d1 < d2 || d1 == d2 && dt1[1] < dt2[1]) { + leading--; + } + } else if (d1 > d2 || d1 == d2 && dt1[1] > dt2[1]) { + leading++; + } + boolean negative; + if (leading < 0) { + negative = true; + leading = -leading; + } else { + negative = false; + } + result = ValueInterval.from(IntervalQualifier.MONTH, negative, leading, 0L); + } else if (lType == Value.DATE && rType == Value.DATE) { + long diff = absoluteDayFromDateValue(((ValueDate) l).getDateValue()) + - absoluteDayFromDateValue(((ValueDate) r).getDateValue()); + boolean negative = diff < 0; + if (negative) { + diff = -diff; + } + result = ValueInterval.from(IntervalQualifier.DAY, negative, diff, 0L); + } else { + BigInteger diff = nanosFromValue(session, l).subtract(nanosFromValue(session, r)); + if (lType == Value.TIMESTAMP_TZ || rType == Value.TIMESTAMP_TZ) { + l = l.convertTo(TypeInfo.TYPE_TIMESTAMP_TZ, session); + r = r.convertTo(TypeInfo.TYPE_TIMESTAMP_TZ, session); + diff = diff.add(BigInteger.valueOf((((ValueTimestampTimeZone) r).getTimeZoneOffsetSeconds() + - ((ValueTimestampTimeZone) l).getTimeZoneOffsetSeconds()) * NANOS_PER_SECOND)); + } + result = IntervalUtils.intervalFromAbsolute(IntervalQualifier.DAY_TO_SECOND, diff); + } + if (forcedType != null) { + result = result.castTo(forcedType, session); + } + return result; + } + } + throw DbException.getInternalError("type=" + opType); + } + + private Value getDateTimeWithInterval(SessionLocal session, Value l, Value r, int lType, int rType) { + switch (lType) { + case Value.TIME: + if (DataType.isYearMonthIntervalType(rType)) { + throw DbException.getInternalError("type=" + rType); + } + return ValueTime.fromNanos(getTimeWithInterval(r, ((ValueTime) l).getNanos())); + case Value.TIME_TZ: { + if (DataType.isYearMonthIntervalType(rType)) { + throw DbException.getInternalError("type=" + rType); + } + ValueTimeTimeZone t = (ValueTimeTimeZone) l; + return ValueTimeTimeZone.fromNanos(getTimeWithInterval(r, t.getNanos()), t.getTimeZoneOffsetSeconds()); + } + case Value.DATE: + case Value.TIMESTAMP: + case Value.TIMESTAMP_TZ: + if (DataType.isYearMonthIntervalType(rType)) { + long m = IntervalUtils.intervalToAbsolute((ValueInterval) r).longValue(); + if (opType == IntervalOpType.DATETIME_MINUS_INTERVAL) { + m = -m; + } + return DateTimeFunction.dateadd(session, DateTimeFunction.MONTH, m, l); + } else { + BigInteger a2 = IntervalUtils.intervalToAbsolute((ValueInterval) r); + if (lType == Value.DATE) { + BigInteger a1 = BigInteger.valueOf(absoluteDayFromDateValue(((ValueDate) l).getDateValue())); + a2 = a2.divide(NANOS_PER_DAY_BI); + BigInteger n = opType == IntervalOpType.DATETIME_PLUS_INTERVAL ? a1.add(a2) : a1.subtract(a2); + return ValueDate.fromDateValue(dateValueFromAbsoluteDay(n.longValue())); + } else { + long[] a = dateAndTimeFromValue(l, session); + long absoluteDay = absoluteDayFromDateValue(a[0]); + long timeNanos = a[1]; + BigInteger[] dr = a2.divideAndRemainder(NANOS_PER_DAY_BI); + if (opType == IntervalOpType.DATETIME_PLUS_INTERVAL) { + absoluteDay += dr[0].longValue(); + timeNanos += dr[1].longValue(); + } else { + absoluteDay -= dr[0].longValue(); + timeNanos -= dr[1].longValue(); + } + if (timeNanos >= NANOS_PER_DAY) { + timeNanos -= NANOS_PER_DAY; + absoluteDay++; + } else if (timeNanos < 0) { + timeNanos += NANOS_PER_DAY; + absoluteDay--; + } + return dateTimeToValue(l, dateValueFromAbsoluteDay(absoluteDay), timeNanos); + } + } + } + throw DbException.getInternalError("type=" + opType); + } + + private long getTimeWithInterval(Value r, long nanos) { + BigInteger a1 = BigInteger.valueOf(nanos); + BigInteger a2 = IntervalUtils.intervalToAbsolute((ValueInterval) r); + BigInteger n = opType == IntervalOpType.DATETIME_PLUS_INTERVAL ? a1.add(a2) : a1.subtract(a2); + if (n.signum() < 0 || n.compareTo(NANOS_PER_DAY_BI) >= 0) { + throw DbException.get(ErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_1, n.toString()); + } + nanos = n.longValue(); + return nanos; + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + right = right.optimize(session); + if (left.isConstant() && right.isConstant()) { + return ValueExpression.get(getValue(session)); + } + return this; + } + +} diff --git a/h2/src/main/org/h2/expression/Operation0.java b/h2/src/main/org/h2/expression/Operation0.java new file mode 100644 index 0000000..23349d2 --- /dev/null +++ b/h2/src/main/org/h2/expression/Operation0.java @@ -0,0 +1,40 @@ +/* + * 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.expression; + +import org.h2.engine.SessionLocal; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; + +/** + * Operation without subexpressions. + */ +public abstract class Operation0 extends Expression { + + protected Operation0() { + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + // Nothing to do + } + + @Override + public Expression optimize(SessionLocal session) { + return this; + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean value) { + // Nothing to do + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + // Nothing to do + } + +} diff --git a/h2/src/main/org/h2/expression/Operation1.java b/h2/src/main/org/h2/expression/Operation1.java new file mode 100644 index 0000000..a4ff48c --- /dev/null +++ b/h2/src/main/org/h2/expression/Operation1.java @@ -0,0 +1,75 @@ +/* + * 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.expression; + +import org.h2.engine.SessionLocal; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.value.TypeInfo; + +/** + * Operation with one argument. + */ +public abstract class Operation1 extends Expression { + + /** + * The argument of the operation. + */ + protected Expression arg; + + /** + * The type of the result. + */ + protected TypeInfo type; + + protected Operation1(Expression arg) { + this.arg = arg; + } + + @Override + public TypeInfo getType() { + return type; + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + arg.mapColumns(resolver, level, state); + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean value) { + arg.setEvaluatable(tableFilter, value); + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + arg.updateAggregate(session, stage); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return arg.isEverything(visitor); + } + + @Override + public int getCost() { + return arg.getCost() + 1; + } + + @Override + public int getSubexpressionCount() { + return 1; + } + + @Override + public Expression getSubexpression(int index) { + if (index == 0) { + return arg; + } + throw new IndexOutOfBoundsException(); + } + +} diff --git a/h2/src/main/org/h2/expression/Operation1_2.java b/h2/src/main/org/h2/expression/Operation1_2.java new file mode 100644 index 0000000..78bed31 --- /dev/null +++ b/h2/src/main/org/h2/expression/Operation1_2.java @@ -0,0 +1,97 @@ +/* + * 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.expression; + +import org.h2.engine.SessionLocal; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.value.TypeInfo; + +/** + * Operation with one or two arguments. + */ +public abstract class Operation1_2 extends Expression { + + /** + * The left part of the operation (the first argument). + */ + protected Expression left; + + /** + * The right part of the operation (the second argument). + */ + protected Expression right; + + /** + * The type of the result. + */ + protected TypeInfo type; + + protected Operation1_2(Expression left, Expression right) { + this.left = left; + this.right = right; + } + + @Override + public TypeInfo getType() { + return type; + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + left.mapColumns(resolver, level, state); + if (right != null) { + right.mapColumns(resolver, level, state); + } + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean value) { + left.setEvaluatable(tableFilter, value); + if (right != null) { + right.setEvaluatable(tableFilter, value); + } + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + left.updateAggregate(session, stage); + if (right != null) { + right.updateAggregate(session, stage); + } + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return left.isEverything(visitor) && (right == null || right.isEverything(visitor)); + } + + @Override + public int getCost() { + int cost = left.getCost() + 1; + if (right != null) { + cost += right.getCost(); + } + return cost; + } + + @Override + public int getSubexpressionCount() { + return right != null ? 2 : 1; + } + + @Override + public Expression getSubexpression(int index) { + if (index == 0) { + return left; + } + if (index == 1 && right != null) { + return right; + } + throw new IndexOutOfBoundsException(); + } + +} diff --git a/h2/src/main/org/h2/expression/Operation2.java b/h2/src/main/org/h2/expression/Operation2.java new file mode 100644 index 0000000..d729157 --- /dev/null +++ b/h2/src/main/org/h2/expression/Operation2.java @@ -0,0 +1,88 @@ +/* + * 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.expression; + +import org.h2.engine.SessionLocal; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.value.TypeInfo; + +/** + * Operation with two arguments. + */ +public abstract class Operation2 extends Expression { + + /** + * The left part of the operation (the first argument). + */ + protected Expression left; + + /** + * The right part of the operation (the second argument). + */ + protected Expression right; + + /** + * The type of the result. + */ + protected TypeInfo type; + + protected Operation2(Expression left, Expression right) { + this.left = left; + this.right = right; + } + + @Override + public TypeInfo getType() { + return type; + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + left.mapColumns(resolver, level, state); + right.mapColumns(resolver, level, state); + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean value) { + left.setEvaluatable(tableFilter, value); + right.setEvaluatable(tableFilter, value); + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + left.updateAggregate(session, stage); + right.updateAggregate(session, stage); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return left.isEverything(visitor) && right.isEverything(visitor); + } + + @Override + public int getCost() { + return left.getCost() + right.getCost() + 1; + } + + @Override + public int getSubexpressionCount() { + return 2; + } + + @Override + public Expression getSubexpression(int index) { + switch (index) { + case 0: + return left; + case 1: + return right; + default: + throw new IndexOutOfBoundsException(); + } + } + +} diff --git a/h2/src/main/org/h2/expression/OperationN.java b/h2/src/main/org/h2/expression/OperationN.java new file mode 100644 index 0000000..ff964ea --- /dev/null +++ b/h2/src/main/org/h2/expression/OperationN.java @@ -0,0 +1,132 @@ +/* + * 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.expression; + +import java.util.Arrays; + +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.value.TypeInfo; + +/** + * Operation with many arguments. + */ +public abstract class OperationN extends Expression implements ExpressionWithVariableParameters { + + /** + * The array of arguments. + */ + protected Expression[] args; + + /** + * The number of arguments. + */ + protected int argsCount; + + /** + * The type of the result. + */ + protected TypeInfo type; + + protected OperationN(Expression[] args) { + this.args = args; + } + + @Override + public void addParameter(Expression param) { + int capacity = args.length; + if (argsCount >= capacity) { + args = Arrays.copyOf(args, capacity * 2); + } + args[argsCount++] = param; + } + + @Override + public void doneWithParameters() throws DbException { + if (args.length != argsCount) { + args = Arrays.copyOf(args, argsCount); + } + } + + @Override + public TypeInfo getType() { + return type; + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + for (Expression e : args) { + e.mapColumns(resolver, level, state); + } + } + + /** + * Optimizes arguments. + * + * @param session + * the session + * @param allConst + * whether operation is deterministic + * @return whether operation is deterministic and all arguments are + * constants + */ + protected boolean optimizeArguments(SessionLocal session, boolean allConst) { + for (int i = 0, l = args.length; i < l; i++) { + Expression e = args[i].optimize(session); + args[i] = e; + if (allConst && !e.isConstant()) { + allConst = false; + } + } + return allConst; + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean value) { + for (Expression e : args) { + e.setEvaluatable(tableFilter, value); + } + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + for (Expression e : args) { + e.updateAggregate(session, stage); + } + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + for (Expression e : args) { + if (!e.isEverything(visitor)) { + return false; + } + } + return true; + } + + @Override + public int getCost() { + int cost = args.length + 1; + for (Expression e : args) { + cost += e.getCost(); + } + return cost; + } + + @Override + public int getSubexpressionCount() { + return args.length; + } + + @Override + public Expression getSubexpression(int index) { + return args[index]; + } + +} diff --git a/h2/src/main/org/h2/expression/Parameter.java b/h2/src/main/org/h2/expression/Parameter.java new file mode 100644 index 0000000..5c30d6f --- /dev/null +++ b/h2/src/main/org/h2/expression/Parameter.java @@ -0,0 +1,122 @@ +/* + * 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.expression; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.expression.condition.Comparison; +import org.h2.message.DbException; +import org.h2.table.Column; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueNull; +import org.h2.value.ValueVarchar; + +/** + * A parameter of a prepared statement. + */ +public final class Parameter extends Operation0 implements ParameterInterface { + + private Value value; + private Column column; + private final int index; + + public Parameter(int index) { + this.index = index; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return builder.append('?').append(index + 1); + } + + @Override + public void setValue(Value v, boolean closeOld) { + // don't need to close the old value as temporary files are anyway + // removed + this.value = v; + } + + public void setValue(Value v) { + this.value = v; + } + + @Override + public Value getParamValue() { + if (value == null) { + // to allow parameters in function tables + return ValueNull.INSTANCE; + } + return value; + } + + @Override + public Value getValue(SessionLocal session) { + return getParamValue(); + } + + @Override + public TypeInfo getType() { + if (value != null) { + return value.getType(); + } + if (column != null) { + return column.getType(); + } + return TypeInfo.TYPE_UNKNOWN; + } + + @Override + public void checkSet() { + if (value == null) { + throw DbException.get(ErrorCode.PARAMETER_NOT_SET_1, "#" + (index + 1)); + } + } + + @Override + public Expression optimize(SessionLocal session) { + if (session.getDatabase().getMode().treatEmptyStringsAsNull) { + if (value instanceof ValueVarchar && value.getString().isEmpty()) { + value = ValueNull.INSTANCE; + } + } + return this; + } + + @Override + public boolean isValueSet() { + return value != null; + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + switch (visitor.getType()) { + case ExpressionVisitor.INDEPENDENT: + return value != null; + default: + return true; + } + } + + @Override + public int getCost() { + return 0; + } + + @Override + public Expression getNotIfPossible(SessionLocal session) { + return new Comparison(Comparison.EQUAL, this, ValueExpression.FALSE, false); + } + + public void setColumn(Column column) { + this.column = column; + } + + public int getIndex() { + return index; + } + +} diff --git a/h2/src/main/org/h2/expression/ParameterInterface.java b/h2/src/main/org/h2/expression/ParameterInterface.java new file mode 100644 index 0000000..2f84052 --- /dev/null +++ b/h2/src/main/org/h2/expression/ParameterInterface.java @@ -0,0 +1,61 @@ +/* + * 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.expression; + +import org.h2.message.DbException; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * The interface for client side (remote) and server side parameters. + */ +public interface ParameterInterface { + + /** + * Set the value of the parameter. + * + * @param value the new value + * @param closeOld if the old value (if one is set) should be closed + */ + void setValue(Value value, boolean closeOld); + + /** + * Get the value of the parameter if set. + * + * @return the value or null + */ + Value getParamValue(); + + /** + * Check if the value is set. + * + * @throws DbException if not set. + */ + void checkSet() throws DbException; + + /** + * Is the value of a parameter set. + * + * @return true if set + */ + boolean isValueSet(); + + /** + * Returns the expected data type if no value is set, or the + * data type of the value if one is set. + * + * @return the data type + */ + TypeInfo getType(); + + /** + * Check if this column is nullable. + * + * @return Column.NULLABLE_* + */ + int getNullable(); + +} diff --git a/h2/src/main/org/h2/expression/ParameterRemote.java b/h2/src/main/org/h2/expression/ParameterRemote.java new file mode 100644 index 0000000..fe6a46b --- /dev/null +++ b/h2/src/main/org/h2/expression/ParameterRemote.java @@ -0,0 +1,89 @@ +/* + * 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.expression; + +import java.io.IOException; +import java.sql.ResultSetMetaData; + +import org.h2.api.ErrorCode; +import org.h2.message.DbException; +import org.h2.value.Transfer; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueLob; + +/** + * A client side (remote) parameter. + */ +public class ParameterRemote implements ParameterInterface { + + private Value value; + private final int index; + private TypeInfo type = TypeInfo.TYPE_UNKNOWN; + private int nullable = ResultSetMetaData.columnNullableUnknown; + + public ParameterRemote(int index) { + this.index = index; + } + + @Override + public void setValue(Value newValue, boolean closeOld) { + if (closeOld && value instanceof ValueLob) { + ((ValueLob) value).remove(); + } + value = newValue; + } + + @Override + public Value getParamValue() { + return value; + } + + @Override + public void checkSet() { + if (value == null) { + throw DbException.get(ErrorCode.PARAMETER_NOT_SET_1, "#" + (index + 1)); + } + } + + @Override + public boolean isValueSet() { + return value != null; + } + + @Override + public TypeInfo getType() { + return value == null ? type : value.getType(); + } + + @Override + public int getNullable() { + return nullable; + } + + /** + * Read the parameter meta data from the transfer object. + * + * @param transfer the transfer object + * @throws IOException on failure + */ + public void readMetaData(Transfer transfer) throws IOException { + type = transfer.readTypeInfo(); + nullable = transfer.readInt(); + } + + /** + * Write the parameter meta data to the transfer object. + * + * @param transfer the transfer object + * @param p the parameter + * @throws IOException on failure + */ + public static void writeMetaData(Transfer transfer, ParameterInterface p) throws IOException { + transfer.writeTypeInfo(p.getType()).writeInt(p.getNullable()); + } + +} diff --git a/h2/src/main/org/h2/expression/Rownum.java b/h2/src/main/org/h2/expression/Rownum.java new file mode 100644 index 0000000..0b7db71 --- /dev/null +++ b/h2/src/main/org/h2/expression/Rownum.java @@ -0,0 +1,75 @@ +/* + * 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.expression; + +import org.h2.command.Prepared; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBigint; + +/** + * Represents the ROWNUM function. + */ +public final class Rownum extends Operation0 { + + private final Prepared prepared; + + private boolean singleRow; + + public Rownum(Prepared prepared) { + if (prepared == null) { + throw DbException.getInternalError(); + } + this.prepared = prepared; + } + + @Override + public Value getValue(SessionLocal session) { + return ValueBigint.get(prepared.getCurrentRowNumber()); + } + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_BIGINT; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return builder.append("ROWNUM()"); + } + + @Override + public Expression optimize(SessionLocal session) { + return singleRow ? ValueExpression.get(ValueBigint.get(1L)) : this; + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + switch (visitor.getType()) { + case ExpressionVisitor.QUERY_COMPARABLE: + case ExpressionVisitor.OPTIMIZABLE_AGGREGATE: + case ExpressionVisitor.DETERMINISTIC: + case ExpressionVisitor.INDEPENDENT: + case ExpressionVisitor.EVALUATABLE: + return false; + case ExpressionVisitor.DECREMENT_QUERY_LEVEL: + if (visitor.getQueryLevel() > 0) { + singleRow = true; + } + //$FALL-THROUGH$ + default: + return true; + } + } + + @Override + public int getCost() { + return 0; + } + +} diff --git a/h2/src/main/org/h2/expression/SearchedCase.java b/h2/src/main/org/h2/expression/SearchedCase.java new file mode 100644 index 0000000..05ba345 --- /dev/null +++ b/h2/src/main/org/h2/expression/SearchedCase.java @@ -0,0 +1,95 @@ +/* + * 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.expression; + +import org.h2.engine.SessionLocal; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * A searched case. + */ +public final class SearchedCase extends OperationN { + + public SearchedCase() { + super(new Expression[4]); + } + + public SearchedCase(Expression[] args) { + super(args); + } + + @Override + public Value getValue(SessionLocal session) { + int len = args.length - 1; + for (int i = 0; i < len; i += 2) { + if (args[i].getBooleanValue(session)) { + return args[i + 1].getValue(session).convertTo(type, session); + } + } + if ((len & 1) == 0) { + return args[len].getValue(session).convertTo(type, session); + } + return ValueNull.INSTANCE; + } + + @Override + public Expression optimize(SessionLocal session) { + TypeInfo typeInfo = TypeInfo.TYPE_UNKNOWN; + int len = args.length - 1; + boolean allConst = true; + for (int i = 0; i < len; i += 2) { + Expression condition = args[i].optimize(session); + Expression result = args[i + 1].optimize(session); + if (allConst) { + if (condition.isConstant()) { + if (condition.getBooleanValue(session)) { + return result; + } + } else { + allConst = false; + } + } + args[i] = condition; + args[i + 1] = result; + typeInfo = SimpleCase.combineTypes(typeInfo, result); + } + if ((len & 1) == 0) { + Expression result = args[len].optimize(session); + if (allConst) { + return result; + } + args[len] = result; + typeInfo = SimpleCase.combineTypes(typeInfo, result); + } else if (allConst) { + return ValueExpression.NULL; + } + if (typeInfo.getValueType() == Value.UNKNOWN) { + typeInfo = TypeInfo.TYPE_VARCHAR; + } + type = typeInfo; + return this; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + builder.append("CASE"); + int len = args.length - 1; + for (int i = 0; i < len; i += 2) { + builder.append(" WHEN "); + args[i].getUnenclosedSQL(builder, sqlFlags); + builder.append(" THEN "); + args[i + 1].getUnenclosedSQL(builder, sqlFlags); + } + if ((len & 1) == 0) { + builder.append(" ELSE "); + args[len].getUnenclosedSQL(builder, sqlFlags); + } + return builder.append(" END"); + } + +} diff --git a/h2/src/main/org/h2/expression/SequenceValue.java b/h2/src/main/org/h2/expression/SequenceValue.java new file mode 100644 index 0000000..96a4410 --- /dev/null +++ b/h2/src/main/org/h2/expression/SequenceValue.java @@ -0,0 +1,92 @@ +/* + * 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.expression; + +import org.h2.command.Prepared; +import org.h2.engine.SessionLocal; +import org.h2.schema.Sequence; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * Wraps a sequence when used in a statement. + */ +public final class SequenceValue extends Operation0 { + + private final Sequence sequence; + + private final boolean current; + + private final Prepared prepared; + + /** + * Creates new instance of NEXT VALUE FOR expression. + * + * @param sequence + * the sequence + * @param prepared + * the owner command, or {@code null} + */ + public SequenceValue(Sequence sequence, Prepared prepared) { + this.sequence = sequence; + current = false; + this.prepared = prepared; + } + + /** + * Creates new instance of CURRENT VALUE FOR expression. + * + * @param sequence + * the sequence + */ + public SequenceValue(Sequence sequence) { + this.sequence = sequence; + current = true; + prepared = null; + } + + @Override + public Value getValue(SessionLocal session) { + return current ? session.getCurrentValueFor(sequence) : session.getNextValueFor(sequence, prepared); + } + + @Override + public TypeInfo getType() { + return sequence.getDataType(); + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + builder.append(current ? "CURRENT" : "NEXT").append(" VALUE FOR "); + return sequence.getSQL(builder, sqlFlags); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + switch (visitor.getType()) { + case ExpressionVisitor.DETERMINISTIC: + case ExpressionVisitor.INDEPENDENT: + case ExpressionVisitor.QUERY_COMPARABLE: + return false; + case ExpressionVisitor.SET_MAX_DATA_MODIFICATION_ID: + visitor.addDataModificationId(sequence.getModificationId()); + return true; + case ExpressionVisitor.GET_DEPENDENCIES: + visitor.addDependency(sequence); + return true; + case ExpressionVisitor.READONLY: + return current; + default: + return true; + } + } + + @Override + public int getCost() { + return 1; + } + +} diff --git a/h2/src/main/org/h2/expression/SimpleCase.java b/h2/src/main/org/h2/expression/SimpleCase.java new file mode 100644 index 0000000..1fc46fa --- /dev/null +++ b/h2/src/main/org/h2/expression/SimpleCase.java @@ -0,0 +1,273 @@ +/* + * 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.expression; + +import org.h2.engine.SessionLocal; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * A simple case. + */ +public final class SimpleCase extends Expression { + + public static final class SimpleWhen { + + Expression[] operands; + + Expression result; + + SimpleWhen next; + + public SimpleWhen(Expression operand, Expression result) { + this(new Expression[] { operand }, result); + } + + public SimpleWhen(Expression[] operands, Expression result) { + this.operands = operands; + this.result = result; + } + + public void setWhen(SimpleWhen next) { + this.next = next; + } + + } + + private Expression operand; + + private SimpleWhen when; + + private Expression elseResult; + + private TypeInfo type; + + public SimpleCase(Expression operand, SimpleWhen when, Expression elseResult) { + this.operand = operand; + this.when = when; + this.elseResult = elseResult; + } + + @Override + public Value getValue(SessionLocal session) { + Value v = operand.getValue(session); + for (SimpleWhen when = this.when; when != null; when = when.next) { + for (Expression e : when.operands) { + if (e.getWhenValue(session, v)) { + return when.result.getValue(session).convertTo(type, session); + } + } + } + if (elseResult != null) { + return elseResult.getValue(session).convertTo(type, session); + } + return ValueNull.INSTANCE; + } + + @Override + public Expression optimize(SessionLocal session) { + TypeInfo typeInfo = TypeInfo.TYPE_UNKNOWN; + operand = operand.optimize(session); + boolean allConst = operand.isConstant(); + Value v = null; + if (allConst) { + v = operand.getValue(session); + } + TypeInfo operandType = operand.getType(); + for (SimpleWhen when = this.when; when != null; when = when.next) { + Expression[] operands = when.operands; + for (int i = 0; i < operands.length; i++) { + Expression e = operands[i].optimize(session); + if (!e.isWhenConditionOperand()) { + TypeInfo.checkComparable(operandType, e.getType()); + } + if (allConst) { + if (e.isConstant()) { + if (e.getWhenValue(session, v)) { + return when.result.optimize(session); + } + } else { + allConst = false; + } + } + operands[i] = e; + } + when.result = when.result.optimize(session); + typeInfo = combineTypes(typeInfo, when.result); + } + if (elseResult != null) { + elseResult = elseResult.optimize(session); + if (allConst) { + return elseResult; + } + typeInfo = combineTypes(typeInfo, elseResult); + } else if (allConst) { + return ValueExpression.NULL; + } + if (typeInfo.getValueType() == Value.UNKNOWN) { + typeInfo = TypeInfo.TYPE_VARCHAR; + } + type = typeInfo; + return this; + } + + static TypeInfo combineTypes(TypeInfo typeInfo, Expression e) { + if (!e.isNullConstant()) { + TypeInfo type = e.getType(); + int valueType = type.getValueType(); + if (valueType != Value.UNKNOWN && valueType != Value.NULL) { + typeInfo = TypeInfo.getHigherType(typeInfo, type); + } + } + return typeInfo; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + operand.getUnenclosedSQL(builder.append("CASE "), sqlFlags); + for (SimpleWhen when = this.when; when != null; when = when.next) { + builder.append(" WHEN"); + Expression[] operands = when.operands; + for (int i = 0, len = operands.length; i < len; i++) { + if (i > 0) { + builder.append(','); + } + operands[i].getWhenSQL(builder, sqlFlags); + } + when.result.getUnenclosedSQL(builder.append(" THEN "), sqlFlags); + } + if (elseResult != null) { + elseResult.getUnenclosedSQL(builder.append(" ELSE "), sqlFlags); + } + return builder.append(" END"); + } + + @Override + public TypeInfo getType() { + return type; + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + operand.mapColumns(resolver, level, state); + for (SimpleWhen when = this.when; when != null; when = when.next) { + for (Expression e : when.operands) { + e.mapColumns(resolver, level, state); + } + when.result.mapColumns(resolver, level, state); + } + if (elseResult != null) { + elseResult.mapColumns(resolver, level, state); + } + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean value) { + operand.setEvaluatable(tableFilter, value); + for (SimpleWhen when = this.when; when != null; when = when.next) { + for (Expression e : when.operands) { + e.setEvaluatable(tableFilter, value); + } + when.result.setEvaluatable(tableFilter, value); + } + if (elseResult != null) { + elseResult.setEvaluatable(tableFilter, value); + } + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + operand.updateAggregate(session, stage); + for (SimpleWhen when = this.when; when != null; when = when.next) { + for (Expression e : when.operands) { + e.updateAggregate(session, stage); + } + when.result.updateAggregate(session, stage); + } + if (elseResult != null) { + elseResult.updateAggregate(session, stage); + } + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + if (!operand.isEverything(visitor)) { + return false; + } + for (SimpleWhen when = this.when; when != null; when = when.next) { + for (Expression e : when.operands) { + if (!e.isEverything(visitor)) { + return false; + } + } + if (!when.result.isEverything(visitor)) { + return false; + } + } + if (elseResult != null && !elseResult.isEverything(visitor)) { + return false; + } + return true; + } + + @Override + public int getCost() { + int cost = 1, resultCost = 0; + cost += operand.getCost(); + for (SimpleWhen when = this.when; when != null; when = when.next) { + for (Expression e : when.operands) { + cost += e.getCost(); + } + resultCost = Math.max(resultCost, when.result.getCost()); + } + if (elseResult != null) { + resultCost = Math.max(resultCost, elseResult.getCost()); + } + return cost + resultCost; + } + + @Override + public int getSubexpressionCount() { + int count = 1; + for (SimpleWhen when = this.when; when != null; when = when.next) { + count += when.operands.length + 1; + } + if (elseResult != null) { + count++; + } + return count; + } + + @Override + public Expression getSubexpression(int index) { + if (index >= 0) { + if (index == 0) { + return operand; + } + int ptr = 1; + for (SimpleWhen when = this.when; when != null; when = when.next) { + Expression[] operands = when.operands; + int count = operands.length; + int offset = index - ptr; + if (offset < count) { + return operands[offset]; + } + ptr += count; + if (index == ptr++) { + return when.result; + } + } + if (elseResult != null && index == ptr) { + return elseResult; + } + } + throw new IndexOutOfBoundsException(); + } + +} diff --git a/h2/src/main/org/h2/expression/Subquery.java b/h2/src/main/org/h2/expression/Subquery.java new file mode 100644 index 0000000..236a538 --- /dev/null +++ b/h2/src/main/org/h2/expression/Subquery.java @@ -0,0 +1,169 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.expression; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; + +import org.h2.api.ErrorCode; +import org.h2.command.query.Query; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.result.ResultInterface; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.value.ExtTypeInfoRow; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueNull; +import org.h2.value.ValueRow; + +/** + * A query returning a single value. + * Subqueries are used inside other statements. + */ +public final class Subquery extends Expression { + + private final Query query; + + private Expression expression; + + private Value nullValue; + + private HashSet outerResolvers = new HashSet<>(); + + public Subquery(Query query) { + this.query = query; + } + + @Override + public Value getValue(SessionLocal session) { + query.setSession(session); + try (ResultInterface result = query.query(2)) { + Value v; + if (!result.next()) { + return nullValue; + } else { + v = readRow(result); + if (result.hasNext()) { + throw DbException.get(ErrorCode.SCALAR_SUBQUERY_CONTAINS_MORE_THAN_ONE_ROW); + } + } + return v; + } + } + + /** + * Evaluates and returns all rows of the subquery. + * + * @param session + * the session + * @return values in all rows + */ + public ArrayList getAllRows(SessionLocal session) { + ArrayList list = new ArrayList<>(); + query.setSession(session); + try (ResultInterface result = query.query(Integer.MAX_VALUE)) { + while (result.next()) { + list.add(readRow(result)); + } + } + return list; + } + + private Value readRow(ResultInterface result) { + Value[] values = result.currentRow(); + int visible = result.getVisibleColumnCount(); + return visible == 1 ? values[0] + : ValueRow.get(getType(), visible == values.length ? values : Arrays.copyOf(values, visible)); + } + + @Override + public TypeInfo getType() { + return expression.getType(); + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + outerResolvers.add(resolver); + query.mapColumns(resolver, level + 1); + } + + @Override + public Expression optimize(SessionLocal session) { + query.prepare(); + if (query.isConstantQuery()) { + setType(); + return ValueExpression.get(getValue(session)); + } + if (outerResolvers != null && session.getDatabase().getSettings().optimizeSimpleSingleRowSubqueries) { + Expression e = query.getIfSingleRow(); + if (e != null && e.isEverything(ExpressionVisitor.getDecrementQueryLevelVisitor(outerResolvers, 0))) { + e.isEverything(ExpressionVisitor.getDecrementQueryLevelVisitor(outerResolvers, 1)); + return e.optimize(session); + } + } + outerResolvers = null; + setType(); + return this; + } + + private void setType() { + ArrayList expressions = query.getExpressions(); + int columnCount = query.getColumnCount(); + if (columnCount == 1) { + expression = expressions.get(0); + nullValue = ValueNull.INSTANCE; + } else { + Expression[] list = new Expression[columnCount]; + Value[] nulls = new Value[columnCount]; + for (int i = 0; i < columnCount; i++) { + list[i] = expressions.get(i); + nulls[i] = ValueNull.INSTANCE; + } + ExpressionList expressionList = new ExpressionList(list, false); + expressionList.initializeType(); + expression = expressionList; + nullValue = ValueRow.get(new ExtTypeInfoRow(list), nulls); + } + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + query.setEvaluatable(tableFilter, b); + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return builder.append('(').append(query.getPlanSQL(sqlFlags)).append(')'); + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + query.updateAggregate(session, stage); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return query.isEverything(visitor); + } + + public Query getQuery() { + return query; + } + + @Override + public int getCost() { + return query.getCostAsExpression(); + } + + @Override + public boolean isConstant() { + return query.isConstantQuery(); + } + +} diff --git a/h2/src/main/org/h2/expression/TimeZoneOperation.java b/h2/src/main/org/h2/expression/TimeZoneOperation.java new file mode 100644 index 0000000..3c7de63 --- /dev/null +++ b/h2/src/main/org/h2/expression/TimeZoneOperation.java @@ -0,0 +1,146 @@ +/* + * 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.expression; + +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.util.DateTimeUtils; +import org.h2.util.TimeZoneProvider; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueInterval; +import org.h2.value.ValueNull; +import org.h2.value.ValueTimeTimeZone; +import org.h2.value.ValueTimestamp; +import org.h2.value.ValueTimestampTimeZone; + +/** + * A time zone specification (AT { TIME ZONE | LOCAL }). + */ +public final class TimeZoneOperation extends Operation1_2 { + + public TimeZoneOperation(Expression left, Expression right) { + super(left, right); + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + left.getSQL(builder, sqlFlags, AUTO_PARENTHESES).append(" AT "); + if (right != null) { + right.getSQL(builder.append("TIME ZONE "), sqlFlags, AUTO_PARENTHESES); + } else { + builder.append("LOCAL"); + } + return builder; + } + + @Override + public Value getValue(SessionLocal session) { + Value a = left.getValue(session).convertTo(type, session); + int valueType = a.getValueType(); + if ((valueType == Value.TIMESTAMP_TZ || valueType == Value.TIME_TZ) && right != null) { + Value b = right.getValue(session); + if (b != ValueNull.INSTANCE) { + if (valueType == Value.TIMESTAMP_TZ) { + ValueTimestampTimeZone v = (ValueTimestampTimeZone) a; + long dateValue = v.getDateValue(); + long timeNanos = v.getTimeNanos(); + int offsetSeconds = v.getTimeZoneOffsetSeconds(); + int newOffset = parseTimeZone(b, dateValue, timeNanos, offsetSeconds, true); + if (offsetSeconds != newOffset) { + a = DateTimeUtils.timestampTimeZoneAtOffset(dateValue, timeNanos, offsetSeconds, newOffset); + } + } else { + ValueTimeTimeZone v = (ValueTimeTimeZone) a; + long timeNanos = v.getNanos(); + int offsetSeconds = v.getTimeZoneOffsetSeconds(); + int newOffset = parseTimeZone(b, DateTimeUtils.EPOCH_DATE_VALUE, timeNanos, offsetSeconds, false); + if (offsetSeconds != newOffset) { + timeNanos += (newOffset - offsetSeconds) * DateTimeUtils.NANOS_PER_SECOND; + a = ValueTimeTimeZone.fromNanos(DateTimeUtils.normalizeNanosOfDay(timeNanos), newOffset); + } + } + } else { + a = ValueNull.INSTANCE; + } + } + return a; + } + + private static int parseTimeZone(Value b, long dateValue, long timeNanos, int offsetSeconds, + boolean allowTimeZoneName) { + if (DataType.isCharacterStringType(b.getValueType())) { + TimeZoneProvider timeZone; + try { + timeZone = TimeZoneProvider.ofId(b.getString()); + } catch (RuntimeException ex) { + throw DbException.getInvalidValueException("time zone", b.getTraceSQL()); + } + if (!allowTimeZoneName && !timeZone.hasFixedOffset()) { + throw DbException.getInvalidValueException("time zone", b.getTraceSQL()); + } + return timeZone.getTimeZoneOffsetUTC(DateTimeUtils.getEpochSeconds(dateValue, timeNanos, offsetSeconds)); + } + return parseInterval(b); + } + + /** + * Parses a daytime interval as time zone offset. + * + * @param interval the interval + * @return the time zone offset in seconds + */ + public static int parseInterval(Value interval) { + ValueInterval i = (ValueInterval) interval.convertTo(TypeInfo.TYPE_INTERVAL_HOUR_TO_SECOND); + long h = i.getLeading(), seconds = i.getRemaining(); + if (h > 18 || h == 18 && seconds != 0 || seconds % DateTimeUtils.NANOS_PER_SECOND != 0) { + throw DbException.getInvalidValueException("time zone", i.getTraceSQL()); + } + int newOffset = (int) (h * 3_600 + seconds / DateTimeUtils.NANOS_PER_SECOND); + if (i.isNegative()) { + newOffset = -newOffset; + } + return newOffset; + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + if (right != null) { + right = right.optimize(session); + } + TypeInfo type = left.getType(); + int valueType = Value.TIMESTAMP_TZ, scale = ValueTimestamp.MAXIMUM_SCALE; + switch (type.getValueType()) { + case Value.TIMESTAMP: + case Value.TIMESTAMP_TZ: + scale = type.getScale(); + break; + case Value.TIME: + case Value.TIME_TZ: + valueType = Value.TIME_TZ; + scale = type.getScale(); + break; + default: + StringBuilder builder = left.getSQL(new StringBuilder(), TRACE_SQL_FLAGS, AUTO_PARENTHESES); + int offset = builder.length(); + builder.append(" AT "); + if (right != null) { + right.getSQL(builder.append("TIME ZONE "), TRACE_SQL_FLAGS, AUTO_PARENTHESES); + } else { + builder.append("LOCAL"); + } + throw DbException.getSyntaxError(builder.toString(), offset, "time, timestamp"); + } + this.type = TypeInfo.getTypeInfo(valueType, -1, scale, null); + if (left.isConstant() && (right == null || right.isConstant())) { + return ValueExpression.get(getValue(session)); + } + return this; + } + +} diff --git a/h2/src/main/org/h2/expression/TypedValueExpression.java b/h2/src/main/org/h2/expression/TypedValueExpression.java new file mode 100644 index 0000000..dd16296 --- /dev/null +++ b/h2/src/main/org/h2/expression/TypedValueExpression.java @@ -0,0 +1,103 @@ +/* + * 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.expression; + +import java.util.Objects; + +import org.h2.value.DataType; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * An expression representing a constant value with a type cast. + */ +public class TypedValueExpression extends ValueExpression { + + /** + * The expression represents the SQL UNKNOWN value. + */ + public static final TypedValueExpression UNKNOWN = new TypedValueExpression(ValueNull.INSTANCE, + TypeInfo.TYPE_BOOLEAN); + + /** + * Create a new expression with the given value and type. + * + * @param value + * the value + * @param type + * the value type + * @return the expression + */ + public static ValueExpression get(Value value, TypeInfo type) { + return getImpl(value, type, true); + } + + /** + * Create a new typed value expression with the given value and type if + * value is {@code NULL}, or a plain value expression otherwise. + * + * @param value + * the value + * @param type + * the value type + * @return the expression + */ + public static ValueExpression getTypedIfNull(Value value, TypeInfo type) { + return getImpl(value, type, false); + } + + private static ValueExpression getImpl(Value value, TypeInfo type, boolean preserveStrictType) { + if (value == ValueNull.INSTANCE) { + switch (type.getValueType()) { + case Value.NULL: + return ValueExpression.NULL; + case Value.BOOLEAN: + return UNKNOWN; + } + return new TypedValueExpression(value, type); + } + if (preserveStrictType) { + DataType dt = DataType.getDataType(type.getValueType()); + TypeInfo vt = value.getType(); + if (dt.supportsPrecision && type.getPrecision() != vt.getPrecision() + || dt.supportsScale && type.getScale() != vt.getScale() + || !Objects.equals(type.getExtTypeInfo(), vt.getExtTypeInfo())) { + return new TypedValueExpression(value, type); + } + } + return ValueExpression.get(value); + } + + private final TypeInfo type; + + private TypedValueExpression(Value value, TypeInfo type) { + super(value); + this.type = type; + } + + @Override + public TypeInfo getType() { + return type; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + if (this == UNKNOWN) { + builder.append("UNKNOWN"); + } else { + value.getSQL(builder.append("CAST("), sqlFlags | NO_CASTS).append(" AS "); + type.getSQL(builder, sqlFlags).append(')'); + } + return builder; + } + + @Override + public boolean isNullConstant() { + return value == ValueNull.INSTANCE; + } + +} diff --git a/h2/src/main/org/h2/expression/UnaryOperation.java b/h2/src/main/org/h2/expression/UnaryOperation.java new file mode 100644 index 0000000..6860d7e --- /dev/null +++ b/h2/src/main/org/h2/expression/UnaryOperation.java @@ -0,0 +1,55 @@ +/* + * 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.expression; + +import org.h2.engine.SessionLocal; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * Unary operation. Only negation operation is currently supported. + */ +public class UnaryOperation extends Operation1 { + + public UnaryOperation(Expression arg) { + super(arg); + } + + @Override + public boolean needParentheses() { + return true; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + // don't remove the space, otherwise it might end up some thing like + // --1 which is a line remark + return arg.getSQL(builder.append("- "), sqlFlags, AUTO_PARENTHESES); + } + + @Override + public Value getValue(SessionLocal session) { + Value a = arg.getValue(session).convertTo(type, session); + return a == ValueNull.INSTANCE ? a : a.negate(); + } + + @Override + public Expression optimize(SessionLocal session) { + arg = arg.optimize(session); + type = arg.getType(); + if (type.getValueType() == Value.UNKNOWN) { + type = TypeInfo.TYPE_NUMERIC_FLOATING_POINT; + } else if (type.getValueType() == Value.ENUM) { + type = TypeInfo.TYPE_INTEGER; + } + if (arg.isConstant()) { + return ValueExpression.get(getValue(session)); + } + return this; + } + +} diff --git a/h2/src/main/org/h2/expression/ValueExpression.java b/h2/src/main/org/h2/expression/ValueExpression.java new file mode 100644 index 0000000..d0515e7 --- /dev/null +++ b/h2/src/main/org/h2/expression/ValueExpression.java @@ -0,0 +1,152 @@ +/* + * 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.expression; + +import org.h2.engine.SessionLocal; +import org.h2.expression.condition.Comparison; +import org.h2.index.IndexCondition; +import org.h2.table.TableFilter; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueNull; + +/** + * An expression representing a constant value. + */ +public class ValueExpression extends Operation0 { + + /** + * The expression represents ValueNull.INSTANCE. + */ + public static final ValueExpression NULL = new ValueExpression(ValueNull.INSTANCE); + + /** + * This special expression represents the default value. It is used for + * UPDATE statements of the form SET COLUMN = DEFAULT. The value is + * ValueNull.INSTANCE, but should never be accessed. + */ + public static final ValueExpression DEFAULT = new ValueExpression(ValueNull.INSTANCE); + + /** + * The expression represents ValueBoolean.TRUE. + */ + public static final ValueExpression TRUE = new ValueExpression(ValueBoolean.TRUE); + + /** + * The expression represents ValueBoolean.FALSE. + */ + public static final ValueExpression FALSE = new ValueExpression(ValueBoolean.FALSE); + + /** + * The value. + */ + final Value value; + + ValueExpression(Value value) { + this.value = value; + } + + /** + * Create a new expression with the given value. + * + * @param value the value + * @return the expression + */ + public static ValueExpression get(Value value) { + if (value == ValueNull.INSTANCE) { + return NULL; + } + if (value.getValueType() == Value.BOOLEAN) { + return getBoolean(value.getBoolean()); + } + return new ValueExpression(value); + } + + /** + * Create a new expression with the given boolean value. + * + * @param value the boolean value + * @return the expression + */ + public static ValueExpression getBoolean(Value value) { + if (value == ValueNull.INSTANCE) { + return TypedValueExpression.UNKNOWN; + } + return getBoolean(value.getBoolean()); + } + + /** + * Create a new expression with the given boolean value. + * + * @param value the boolean value + * @return the expression + */ + public static ValueExpression getBoolean(boolean value) { + return value ? TRUE : FALSE; + } + + @Override + public Value getValue(SessionLocal session) { + return value; + } + + @Override + public TypeInfo getType() { + return value.getType(); + } + + @Override + public void createIndexConditions(SessionLocal session, TableFilter filter) { + if (value.getValueType() == Value.BOOLEAN && !value.getBoolean()) { + filter.addIndexCondition(IndexCondition.get(Comparison.FALSE, null, this)); + } + } + + @Override + public Expression getNotIfPossible(SessionLocal session) { + if (value == ValueNull.INSTANCE) { + return TypedValueExpression.UNKNOWN; + } + return getBoolean(!value.getBoolean()); + } + + @Override + public boolean isConstant() { + return true; + } + + @Override + public boolean isNullConstant() { + return this == NULL; + } + + @Override + public boolean isValueSet() { + return true; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + if (this == DEFAULT) { + builder.append("DEFAULT"); + } else { + value.getSQL(builder, sqlFlags); + } + return builder; + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return true; + } + + @Override + public int getCost() { + return 0; + } + +} diff --git a/h2/src/main/org/h2/expression/Variable.java b/h2/src/main/org/h2/expression/Variable.java new file mode 100644 index 0000000..b1d8da2 --- /dev/null +++ b/h2/src/main/org/h2/expression/Variable.java @@ -0,0 +1,61 @@ +/* + * 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.expression; + +import org.h2.engine.SessionLocal; +import org.h2.util.ParserUtil; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * A user-defined variable, for example: @ID. + */ +public final class Variable extends Operation0 { + + private final String name; + private Value lastValue; + + public Variable(SessionLocal session, String name) { + this.name = name; + lastValue = session.getVariable(name); + } + + @Override + public int getCost() { + return 0; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return ParserUtil.quoteIdentifier(builder.append('@'), name, sqlFlags); + } + + @Override + public TypeInfo getType() { + return lastValue.getType(); + } + + @Override + public Value getValue(SessionLocal session) { + lastValue = session.getVariable(name); + return lastValue; + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + switch (visitor.getType()) { + case ExpressionVisitor.DETERMINISTIC: + return false; + default: + return true; + } + } + + public String getName() { + return name; + } + +} diff --git a/h2/src/main/org/h2/expression/Wildcard.java b/h2/src/main/org/h2/expression/Wildcard.java new file mode 100644 index 0000000..17d8cc9 --- /dev/null +++ b/h2/src/main/org/h2/expression/Wildcard.java @@ -0,0 +1,135 @@ +/* + * 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.expression; + +import java.util.ArrayList; +import java.util.HashMap; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.table.Column; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.util.StringUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * A wildcard expression as in SELECT * FROM TEST. + * This object is only used temporarily during the parsing phase, and later + * replaced by column expressions. + */ +public final class Wildcard extends Expression { + + private final String schema; + private final String table; + + private ArrayList exceptColumns; + + public Wildcard(String schema, String table) { + this.schema = schema; + this.table = table; + } + + public ArrayList getExceptColumns() { + return exceptColumns; + } + + public void setExceptColumns(ArrayList exceptColumns) { + this.exceptColumns = exceptColumns; + } + + /** + * Returns map of excluded table columns to expression columns and validates + * that all columns are resolved and not duplicated. + * + * @return map of excluded table columns to expression columns + */ + public HashMap mapExceptColumns() { + HashMap exceptTableColumns = new HashMap<>(); + for (ExpressionColumn ec : exceptColumns) { + Column column = ec.getColumn(); + if (column == null) { + throw ec.getColumnException(ErrorCode.COLUMN_NOT_FOUND_1); + } + if (exceptTableColumns.putIfAbsent(column, ec) != null) { + throw ec.getColumnException(ErrorCode.DUPLICATE_COLUMN_NAME_1); + } + } + return exceptTableColumns; + } + + @Override + public Value getValue(SessionLocal session) { + throw DbException.getInternalError(toString()); + } + + @Override + public TypeInfo getType() { + throw DbException.getInternalError(toString()); + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + if (exceptColumns != null) { + for (ExpressionColumn column : exceptColumns) { + column.mapColumns(resolver, level, state); + } + } + } + + @Override + public Expression optimize(SessionLocal session) { + throw DbException.get(ErrorCode.SYNTAX_ERROR_1, table); + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + throw DbException.getInternalError(toString()); + } + + @Override + public String getTableAlias() { + return table; + } + + @Override + public String getSchemaName() { + return schema; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + if (table != null) { + StringUtils.quoteIdentifier(builder, table).append('.'); + } + builder.append('*'); + if (exceptColumns != null) { + writeExpressions(builder.append(" EXCEPT ("), exceptColumns, sqlFlags).append(')'); + } + return builder; + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + throw DbException.getInternalError(toString()); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + if (visitor.getType() == ExpressionVisitor.QUERY_COMPARABLE) { + return true; + } + throw DbException.getInternalError(Integer.toString(visitor.getType())); + } + + @Override + public int getCost() { + throw DbException.getInternalError(toString()); + } + +} diff --git a/h2/src/main/org/h2/expression/aggregate/AbstractAggregate.java b/h2/src/main/org/h2/expression/aggregate/AbstractAggregate.java new file mode 100644 index 0000000..09dbf84 --- /dev/null +++ b/h2/src/main/org/h2/expression/aggregate/AbstractAggregate.java @@ -0,0 +1,324 @@ +/* + * 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.expression.aggregate; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; + +import org.h2.command.query.Select; +import org.h2.command.query.SelectGroups; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.analysis.DataAnalysisOperation; +import org.h2.expression.analysis.WindowFrame; +import org.h2.expression.analysis.WindowFrameBound; +import org.h2.expression.analysis.WindowFrameBoundType; +import org.h2.expression.analysis.WindowFrameExclusion; +import org.h2.expression.analysis.WindowFrameUnits; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * A base class for aggregate functions. + */ +public abstract class AbstractAggregate extends DataAnalysisOperation { + + /** + * is this a DISTINCT aggregate + */ + protected final boolean distinct; + + /** + * The arguments. + */ + protected final Expression[] args; + + /** + * FILTER condition for aggregate + */ + protected Expression filterCondition; + + /** + * The type of the result. + */ + protected TypeInfo type; + + AbstractAggregate(Select select, Expression[] args, boolean distinct) { + super(select); + this.args = args; + this.distinct = distinct; + } + + @Override + public final boolean isAggregate() { + return true; + } + + /** + * Sets the FILTER condition. + * + * @param filterCondition + * FILTER condition + */ + public void setFilterCondition(Expression filterCondition) { + this.filterCondition = filterCondition; + } + + @Override + public TypeInfo getType() { + return type; + } + + @Override + public void mapColumnsAnalysis(ColumnResolver resolver, int level, int innerState) { + for (Expression arg : args) { + arg.mapColumns(resolver, level, innerState); + } + if (filterCondition != null) { + filterCondition.mapColumns(resolver, level, innerState); + } + super.mapColumnsAnalysis(resolver, level, innerState); + } + + @Override + public Expression optimize(SessionLocal session) { + for (int i = 0; i < args.length; i++) { + args[i] = args[i].optimize(session); + } + if (filterCondition != null) { + filterCondition = filterCondition.optimizeCondition(session); + } + return super.optimize(session); + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + for (Expression arg : args) { + arg.setEvaluatable(tableFilter, b); + } + if (filterCondition != null) { + filterCondition.setEvaluatable(tableFilter, b); + } + super.setEvaluatable(tableFilter, b); + } + + @Override + protected void getOrderedResultLoop(SessionLocal session, HashMap result, + ArrayList ordered, int rowIdColumn) { + WindowFrame frame = over.getWindowFrame(); + /* + * With RANGE (default) or GROUPS units and EXCLUDE GROUP or EXCLUDE NO + * OTHERS (default) exclusion all rows in the group have the same value + * of window aggregate function. + */ + boolean grouped = frame == null + || frame.getUnits() != WindowFrameUnits.ROWS && frame.getExclusion().isGroupOrNoOthers(); + if (frame == null) { + aggregateFastPartition(session, result, ordered, rowIdColumn, grouped); + return; + } + boolean variableBounds = frame.isVariableBounds(); + if (variableBounds) { + variableBounds = checkVariableBounds(frame, ordered); + } + if (variableBounds) { + grouped = false; + } else if (frame.getExclusion() == WindowFrameExclusion.EXCLUDE_NO_OTHERS) { + WindowFrameBound following = frame.getFollowing(); + boolean unboundedFollowing = following != null + && following.getType() == WindowFrameBoundType.UNBOUNDED_FOLLOWING; + if (frame.getStarting().getType() == WindowFrameBoundType.UNBOUNDED_PRECEDING) { + if (unboundedFollowing) { + aggregateWholePartition(session, result, ordered, rowIdColumn); + } else { + aggregateFastPartition(session, result, ordered, rowIdColumn, grouped); + } + return; + } + if (unboundedFollowing) { + aggregateFastPartitionInReverse(session, result, ordered, rowIdColumn, grouped); + return; + } + } + // All other types of frames (slow) + int size = ordered.size(); + for (int i = 0; i < size;) { + Object aggregateData = createAggregateData(); + for (Iterator iter = WindowFrame.iterator(over, session, ordered, getOverOrderBySort(), i, + false); iter.hasNext();) { + updateFromExpressions(session, aggregateData, iter.next()); + } + Value r = getAggregatedValue(session, aggregateData); + i = processGroup(result, r, ordered, rowIdColumn, i, size, grouped); + } + } + + private static boolean checkVariableBounds(WindowFrame frame, ArrayList ordered) { + int size = ordered.size(); + WindowFrameBound bound = frame.getStarting(); + if (bound.isVariable()) { + int offset = bound.getExpressionIndex(); + Value v = ordered.get(0)[offset]; + for (int i = 1; i < size; i++) { + if (!v.equals(ordered.get(i)[offset])) { + return true; + } + } + } + bound = frame.getFollowing(); + if (bound != null && bound.isVariable()) { + int offset = bound.getExpressionIndex(); + Value v = ordered.get(0)[offset]; + for (int i = 1; i < size; i++) { + if (!v.equals(ordered.get(i)[offset])) { + return true; + } + } + } + return false; + } + + private void aggregateFastPartition(SessionLocal session, HashMap result, + ArrayList ordered, int rowIdColumn, boolean grouped) { + Object aggregateData = createAggregateData(); + int size = ordered.size(); + int lastIncludedRow = -1; + Value r = null; + for (int i = 0; i < size;) { + int newLast = WindowFrame.getEndIndex(over, session, ordered, getOverOrderBySort(), i); + assert newLast >= lastIncludedRow; + if (newLast > lastIncludedRow) { + for (int j = lastIncludedRow + 1; j <= newLast; j++) { + updateFromExpressions(session, aggregateData, ordered.get(j)); + } + lastIncludedRow = newLast; + r = getAggregatedValue(session, aggregateData); + } else if (r == null) { + r = getAggregatedValue(session, aggregateData); + } + i = processGroup(result, r, ordered, rowIdColumn, i, size, grouped); + } + } + + private void aggregateFastPartitionInReverse(SessionLocal session, HashMap result, + ArrayList ordered, int rowIdColumn, boolean grouped) { + Object aggregateData = createAggregateData(); + int firstIncludedRow = ordered.size(); + Value r = null; + for (int i = firstIncludedRow - 1; i >= 0;) { + int newLast = over.getWindowFrame().getStartIndex(session, ordered, getOverOrderBySort(), i); + assert newLast <= firstIncludedRow; + if (newLast < firstIncludedRow) { + for (int j = firstIncludedRow - 1; j >= newLast; j--) { + updateFromExpressions(session, aggregateData, ordered.get(j)); + } + firstIncludedRow = newLast; + r = getAggregatedValue(session, aggregateData); + } else if (r == null) { + r = getAggregatedValue(session, aggregateData); + } + Value[] lastRowInGroup = ordered.get(i), currentRowInGroup = lastRowInGroup; + do { + result.put(currentRowInGroup[rowIdColumn].getInt(), r); + } while (--i >= 0 && grouped + && overOrderBySort.compare(lastRowInGroup, currentRowInGroup = ordered.get(i)) == 0); + } + } + + private int processGroup(HashMap result, Value r, ArrayList ordered, + int rowIdColumn, int i, int size, boolean grouped) { + Value[] firstRowInGroup = ordered.get(i), currentRowInGroup = firstRowInGroup; + do { + result.put(currentRowInGroup[rowIdColumn].getInt(), r); + } while (++i < size && grouped + && overOrderBySort.compare(firstRowInGroup, currentRowInGroup = ordered.get(i)) == 0); + return i; + } + + private void aggregateWholePartition(SessionLocal session, HashMap result, + ArrayList ordered, int rowIdColumn) { + // Aggregate values from the whole partition + Object aggregateData = createAggregateData(); + for (Value[] row : ordered) { + updateFromExpressions(session, aggregateData, row); + } + // All rows have the same value + Value value = getAggregatedValue(session, aggregateData); + for (Value[] row : ordered) { + result.put(row[rowIdColumn].getInt(), value); + } + } + + /** + * Updates the provided aggregate data from the remembered expressions. + * + * @param session + * the session + * @param aggregateData + * aggregate data + * @param array + * values of expressions + */ + protected abstract void updateFromExpressions(SessionLocal session, Object aggregateData, Value[] array); + + @Override + protected void updateAggregate(SessionLocal session, SelectGroups groupData, int groupRowId) { + if (filterCondition == null || filterCondition.getBooleanValue(session)) { + if (over != null) { + if (over.isOrdered()) { + updateOrderedAggregate(session, groupData, groupRowId, over.getOrderBy()); + } else { + updateAggregate(session, getWindowData(session, groupData, false)); + } + } else { + updateAggregate(session, getGroupData(groupData, false)); + } + } else if (over != null && over.isOrdered()) { + updateOrderedAggregate(session, groupData, groupRowId, over.getOrderBy()); + } + } + + /** + * Updates an aggregate value. + * + * @param session + * the session + * @param aggregateData + * aggregate data + */ + protected abstract void updateAggregate(SessionLocal session, Object aggregateData); + + @Override + protected void updateGroupAggregates(SessionLocal session, int stage) { + if (filterCondition != null) { + filterCondition.updateAggregate(session, stage); + } + super.updateGroupAggregates(session, stage); + } + + @Override + protected StringBuilder appendTailConditions(StringBuilder builder, int sqlFlags, boolean forceOrderBy) { + if (filterCondition != null) { + builder.append(" FILTER (WHERE "); + filterCondition.getUnenclosedSQL(builder, sqlFlags).append(')'); + } + return super.appendTailConditions(builder, sqlFlags, forceOrderBy); + } + + @Override + public int getSubexpressionCount() { + return args.length; + } + + @Override + public Expression getSubexpression(int index) { + return args[index]; + } + +} diff --git a/h2/src/main/org/h2/expression/aggregate/Aggregate.java b/h2/src/main/org/h2/expression/aggregate/Aggregate.java new file mode 100644 index 0000000..a31e43f --- /dev/null +++ b/h2/src/main/org/h2/expression/aggregate/Aggregate.java @@ -0,0 +1,1347 @@ +/* + * 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.expression.aggregate; + +import java.io.ByteArrayOutputStream; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map.Entry; +import java.util.TreeMap; + +import org.h2.api.ErrorCode; +import org.h2.command.query.QueryOrderBy; +import org.h2.command.query.Select; +import org.h2.engine.Constants; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.ExpressionWithFlags; +import org.h2.expression.ValueExpression; +import org.h2.expression.aggregate.AggregateDataCollecting.NullCollectionMode; +import org.h2.expression.analysis.Window; +import org.h2.expression.function.BitFunction; +import org.h2.expression.function.JsonConstructorFunction; +import org.h2.index.Cursor; +import org.h2.index.Index; +import org.h2.message.DbException; +import org.h2.mvstore.db.MVSpatialIndex; +import org.h2.result.SearchRow; +import org.h2.result.SortOrder; +import org.h2.table.Column; +import org.h2.table.ColumnResolver; +import org.h2.table.Table; +import org.h2.table.TableFilter; +import org.h2.util.StringUtils; +import org.h2.util.json.JsonConstructorUtils; +import org.h2.value.CompareMode; +import org.h2.value.DataType; +import org.h2.value.ExtTypeInfoRow; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueArray; +import org.h2.value.ValueBigint; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueDouble; +import org.h2.value.ValueInteger; +import org.h2.value.ValueInterval; +import org.h2.value.ValueJson; +import org.h2.value.ValueNull; +import org.h2.value.ValueNumeric; +import org.h2.value.ValueRow; +import org.h2.value.ValueVarchar; + +/** + * Implements the integrated aggregate functions, such as COUNT, MAX, SUM. + */ +public class Aggregate extends AbstractAggregate implements ExpressionWithFlags { + + /** + * The additional result precision in decimal digits for a SUM aggregate function. + */ + private static final int ADDITIONAL_SUM_PRECISION = 10; + + /** + * The additional precision and scale in decimal digits for an AVG aggregate function. + */ + private static final int ADDITIONAL_AVG_SCALE = 10; + + private static final HashMap AGGREGATES = new HashMap<>(128); + + private final AggregateType aggregateType; + + private ArrayList orderByList; + private SortOrder orderBySort; + + private Object extraArguments; + + private int flags; + + /** + * Create a new aggregate object. + * + * @param aggregateType + * the aggregate type + * @param args + * the aggregated expressions + * @param select + * the select statement + * @param distinct + * if distinct is used + */ + public Aggregate(AggregateType aggregateType, Expression[] args, Select select, boolean distinct) { + super(select, args, distinct); + if (distinct && aggregateType == AggregateType.COUNT_ALL) { + throw DbException.getInternalError(); + } + this.aggregateType = aggregateType; + } + + static { + /* + * Update initial size of AGGREGATES after editing the following list. + */ + addAggregate("COUNT", AggregateType.COUNT); + addAggregate("SUM", AggregateType.SUM); + addAggregate("MIN", AggregateType.MIN); + addAggregate("MAX", AggregateType.MAX); + addAggregate("AVG", AggregateType.AVG); + addAggregate("LISTAGG", AggregateType.LISTAGG); + // MySQL compatibility: group_concat(expression, delimiter) + addAggregate("GROUP_CONCAT", AggregateType.LISTAGG); + // PostgreSQL compatibility: string_agg(expression, delimiter) + addAggregate("STRING_AGG", AggregateType.LISTAGG); + addAggregate("STDDEV_SAMP", AggregateType.STDDEV_SAMP); + addAggregate("STDDEV", AggregateType.STDDEV_SAMP); + addAggregate("STDDEV_POP", AggregateType.STDDEV_POP); + addAggregate("STDDEVP", AggregateType.STDDEV_POP); + addAggregate("VAR_POP", AggregateType.VAR_POP); + addAggregate("VARP", AggregateType.VAR_POP); + addAggregate("VAR_SAMP", AggregateType.VAR_SAMP); + addAggregate("VAR", AggregateType.VAR_SAMP); + addAggregate("VARIANCE", AggregateType.VAR_SAMP); + addAggregate("ANY", AggregateType.ANY); + addAggregate("SOME", AggregateType.ANY); + // PostgreSQL compatibility + addAggregate("BOOL_OR", AggregateType.ANY); + addAggregate("EVERY", AggregateType.EVERY); + // PostgreSQL compatibility + addAggregate("BOOL_AND", AggregateType.EVERY); + addAggregate("HISTOGRAM", AggregateType.HISTOGRAM); + addAggregate("BIT_AND_AGG", AggregateType.BIT_AND_AGG); + addAggregate("BIT_AND", AggregateType.BIT_AND_AGG); + addAggregate("BIT_OR_AGG", AggregateType.BIT_OR_AGG); + addAggregate("BIT_OR", AggregateType.BIT_OR_AGG); + addAggregate("BIT_XOR_AGG", AggregateType.BIT_XOR_AGG); + addAggregate("BIT_NAND_AGG", AggregateType.BIT_NAND_AGG); + addAggregate("BIT_NOR_AGG", AggregateType.BIT_NOR_AGG); + addAggregate("BIT_XNOR_AGG", AggregateType.BIT_XNOR_AGG); + + addAggregate("COVAR_POP", AggregateType.COVAR_POP); + addAggregate("COVAR_SAMP", AggregateType.COVAR_SAMP); + addAggregate("CORR", AggregateType.CORR); + addAggregate("REGR_SLOPE", AggregateType.REGR_SLOPE); + addAggregate("REGR_INTERCEPT", AggregateType.REGR_INTERCEPT); + addAggregate("REGR_COUNT", AggregateType.REGR_COUNT); + addAggregate("REGR_R2", AggregateType.REGR_R2); + addAggregate("REGR_AVGX", AggregateType.REGR_AVGX); + addAggregate("REGR_AVGY", AggregateType.REGR_AVGY); + addAggregate("REGR_SXX", AggregateType.REGR_SXX); + addAggregate("REGR_SYY", AggregateType.REGR_SYY); + addAggregate("REGR_SXY", AggregateType.REGR_SXY); + + addAggregate("RANK", AggregateType.RANK); + addAggregate("DENSE_RANK", AggregateType.DENSE_RANK); + addAggregate("PERCENT_RANK", AggregateType.PERCENT_RANK); + addAggregate("CUME_DIST", AggregateType.CUME_DIST); + + addAggregate("PERCENTILE_CONT", AggregateType.PERCENTILE_CONT); + addAggregate("PERCENTILE_DISC", AggregateType.PERCENTILE_DISC); + addAggregate("MEDIAN", AggregateType.MEDIAN); + + addAggregate("ARRAY_AGG", AggregateType.ARRAY_AGG); + addAggregate("MODE", AggregateType.MODE); + // Oracle compatibility + addAggregate("STATS_MODE", AggregateType.MODE); + addAggregate("ENVELOPE", AggregateType.ENVELOPE); + + addAggregate("JSON_OBJECTAGG", AggregateType.JSON_OBJECTAGG); + addAggregate("JSON_ARRAYAGG", AggregateType.JSON_ARRAYAGG); + } + + private static void addAggregate(String name, AggregateType type) { + AGGREGATES.put(name, type); + } + + /** + * Get the aggregate type for this name, or -1 if no aggregate has been + * found. + * + * @param name + * the aggregate function name + * @return null if no aggregate function has been found, or the aggregate + * type + */ + public static AggregateType getAggregateType(String name) { + return AGGREGATES.get(name); + } + + /** + * Set the order for ARRAY_AGG() or GROUP_CONCAT() aggregate. + * + * @param orderByList + * the order by list + */ + public void setOrderByList(ArrayList orderByList) { + this.orderByList = orderByList; + } + + /** + * Returns the type of this aggregate. + * + * @return the type of this aggregate + */ + public AggregateType getAggregateType() { + return aggregateType; + } + + /** + * Sets the additional arguments. + * + * @param extraArguments the additional arguments + */ + public void setExtraArguments(Object extraArguments) { + this.extraArguments = extraArguments; + } + + /** + * Returns the additional arguments. + * + * @return the additional arguments + */ + public Object getExtraArguments() { + return extraArguments; + } + + @Override + public void setFlags(int flags) { + this.flags = flags; + } + + @Override + public int getFlags() { + return flags; + } + + private void sortWithOrderBy(Value[] array) { + final SortOrder sortOrder = orderBySort; + Arrays.sort(array, + sortOrder != null + ? (v1, v2) -> sortOrder.compare(((ValueRow) v1).getList(), ((ValueRow) v2).getList()) + : select.getSession().getDatabase().getCompareMode()); + } + + @Override + protected void updateAggregate(SessionLocal session, Object aggregateData) { + AggregateData data = (AggregateData) aggregateData; + Value v = args.length == 0 ? null : args[0].getValue(session); + updateData(session, data, v, null); + } + + private void updateData(SessionLocal session, AggregateData data, Value v, Value[] remembered) { + switch (aggregateType) { + case COVAR_POP: + case COVAR_SAMP: + case CORR: + case REGR_SLOPE: + case REGR_INTERCEPT: + case REGR_R2: + case REGR_SXY: { + Value x; + if (v == ValueNull.INSTANCE || (x = getSecondValue(session, remembered)) == ValueNull.INSTANCE) { + return; + } + ((AggregateDataBinarySet) data).add(session, v, x); + return; + } + case REGR_COUNT: + case REGR_AVGY: + case REGR_SYY: + if (v == ValueNull.INSTANCE || getSecondValue(session, remembered) == ValueNull.INSTANCE) { + return; + } + break; + case REGR_AVGX: + case REGR_SXX: + if (v == ValueNull.INSTANCE || (v = getSecondValue(session, remembered)) == ValueNull.INSTANCE) { + return; + } + break; + case LISTAGG: + if (v == ValueNull.INSTANCE) { + return; + } + v = updateCollecting(session, v.convertTo(TypeInfo.TYPE_VARCHAR), remembered); + break; + case ARRAY_AGG: + v = updateCollecting(session, v, remembered); + break; + case RANK: + case DENSE_RANK: + case PERCENT_RANK: + case CUME_DIST: { + int count = args.length; + Value[] a = new Value[count]; + for (int i = 0; i < count; i++) { + a[i] = remembered != null ? remembered[i] : args[i].getValue(session); + } + ((AggregateDataCollecting) data).setSharedArgument(ValueRow.get(a)); + a = new Value[count]; + for (int i = 0; i < count; i++) { + a[i] = remembered != null ? remembered[count + i] : orderByList.get(i).expression.getValue(session); + } + v = ValueRow.get(a); + break; + } + case PERCENTILE_CONT: + case PERCENTILE_DISC: + ((AggregateDataCollecting) data).setSharedArgument(v); + v = remembered != null ? remembered[1] : orderByList.get(0).expression.getValue(session); + break; + case MODE: + v = remembered != null ? remembered[0] : orderByList.get(0).expression.getValue(session); + break; + case JSON_ARRAYAGG: + v = updateCollecting(session, v, remembered); + break; + case JSON_OBJECTAGG: { + Value key = v; + Value value = getSecondValue(session, remembered); + if (key == ValueNull.INSTANCE) { + throw DbException.getInvalidValueException("JSON_OBJECTAGG key", "NULL"); + } + v = ValueRow.get(new Value[] { key, value }); + break; + } + default: + // Use argument as is + } + data.add(session, v); + } + + private Value getSecondValue(SessionLocal session, Value[] remembered) { + return remembered != null ? remembered[1] : args[1].getValue(session); + } + + @Override + protected void updateGroupAggregates(SessionLocal session, int stage) { + super.updateGroupAggregates(session, stage); + for (Expression arg : args) { + arg.updateAggregate(session, stage); + } + if (orderByList != null) { + for (QueryOrderBy orderBy : orderByList) { + orderBy.expression.updateAggregate(session, stage); + } + } + } + + private Value updateCollecting(SessionLocal session, Value v, Value[] remembered) { + if (orderByList != null) { + int size = orderByList.size(); + Value[] row = new Value[1 + size]; + row[0] = v; + if (remembered == null) { + for (int i = 0; i < size; i++) { + QueryOrderBy o = orderByList.get(i); + row[i + 1] = o.expression.getValue(session); + } + } else { + System.arraycopy(remembered, 1, row, 1, size); + } + v = ValueRow.get(row); + } + return v; + } + + @Override + protected int getNumExpressions() { + int n = args.length; + if (orderByList != null) { + n += orderByList.size(); + } + if (filterCondition != null) { + n++; + } + return n; + } + + @Override + protected void rememberExpressions(SessionLocal session, Value[] array) { + int offset = 0; + for (Expression arg : args) { + array[offset++] = arg.getValue(session); + } + if (orderByList != null) { + for (QueryOrderBy o : orderByList) { + array[offset++] = o.expression.getValue(session); + } + } + if (filterCondition != null) { + array[offset] = ValueBoolean.get(filterCondition.getBooleanValue(session)); + } + } + + @Override + protected void updateFromExpressions(SessionLocal session, Object aggregateData, Value[] array) { + if (filterCondition == null || array[getNumExpressions() - 1].isTrue()) { + AggregateData data = (AggregateData) aggregateData; + Value v = args.length == 0 ? null : array[0]; + updateData(session, data, v, array); + } + } + + @Override + protected Object createAggregateData() { + switch (aggregateType) { + case COUNT_ALL: + case REGR_COUNT: + return new AggregateDataCount(true); + case COUNT: + if (!distinct) { + return new AggregateDataCount(false); + } + break; + case RANK: + case DENSE_RANK: + case PERCENT_RANK: + case CUME_DIST: + case PERCENTILE_CONT: + case PERCENTILE_DISC: + case MEDIAN: + break; + case SUM: + case BIT_XOR_AGG: + case BIT_XNOR_AGG: + if (distinct) { + break; + } + //$FALL-THROUGH$ + case MIN: + case MAX: + case BIT_AND_AGG: + case BIT_OR_AGG: + case BIT_NAND_AGG: + case BIT_NOR_AGG: + case ANY: + case EVERY: + return new AggregateDataDefault(aggregateType, type); + case AVG: + if (distinct) { + break; + } + //$FALL-THROUGH$ + case REGR_AVGX: + case REGR_AVGY: + return new AggregateDataAvg(type); + case STDDEV_POP: + case STDDEV_SAMP: + case VAR_POP: + case VAR_SAMP: + if (distinct) { + break; + } + //$FALL-THROUGH$ + case REGR_SXX: + case REGR_SYY: + return new AggregateDataStdVar(aggregateType); + case HISTOGRAM: + return new AggregateDataDistinctWithCounts(false, Constants.SELECTIVITY_DISTINCT_COUNT); + case COVAR_POP: + case COVAR_SAMP: + case REGR_SXY: + return new AggregateDataCovar(aggregateType); + case CORR: + case REGR_SLOPE: + case REGR_INTERCEPT: + case REGR_R2: + return new AggregateDataCorr(aggregateType); + case LISTAGG: // NULL values are excluded by Aggregate + case ARRAY_AGG: + return new AggregateDataCollecting(distinct, orderByList != null, NullCollectionMode.USED_OR_IMPOSSIBLE); + case MODE: + return new AggregateDataDistinctWithCounts(true, Integer.MAX_VALUE); + case ENVELOPE: + return new AggregateDataEnvelope(); + case JSON_ARRAYAGG: + return new AggregateDataCollecting(distinct, orderByList != null, + (flags & JsonConstructorUtils.JSON_ABSENT_ON_NULL) != 0 ? NullCollectionMode.EXCLUDED + : NullCollectionMode.USED_OR_IMPOSSIBLE); + case JSON_OBJECTAGG: + // ROW(key, value) are collected, so NULL values can't be passed + return new AggregateDataCollecting(distinct, false, NullCollectionMode.USED_OR_IMPOSSIBLE); + default: + throw DbException.getInternalError("type=" + aggregateType); + } + return new AggregateDataCollecting(distinct, false, NullCollectionMode.IGNORED); + } + + @Override + public Value getValue(SessionLocal session) { + return select.isQuickAggregateQuery() ? getValueQuick(session) : super.getValue(session); + } + + private Value getValueQuick(SessionLocal session) { + switch (aggregateType) { + case COUNT: + case COUNT_ALL: + Table table = select.getTopTableFilter().getTable(); + return ValueBigint.get(table.getRowCount(session)); + case MIN: + case MAX: { + boolean first = aggregateType == AggregateType.MIN; + Index index = getMinMaxColumnIndex(); + int sortType = index.getIndexColumns()[0].sortType; + if ((sortType & SortOrder.DESCENDING) != 0) { + first = !first; + } + Cursor cursor = index.findFirstOrLast(session, first); + SearchRow row = cursor.getSearchRow(); + Value v; + if (row == null) { + v = ValueNull.INSTANCE; + } else { + v = row.getValue(index.getColumns()[0].getColumnId()); + } + return v; + } + case PERCENTILE_CONT: + case PERCENTILE_DISC: { + Value v = args[0].getValue(session); + if (v == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + BigDecimal arg = v.getBigDecimal(); + if (arg.signum() >= 0 && arg.compareTo(BigDecimal.ONE) <= 0) { + return Percentile.getFromIndex(session, orderByList.get(0).expression, type.getValueType(), + orderByList, arg, aggregateType == AggregateType.PERCENTILE_CONT); + } else { + throw DbException.getInvalidValueException(aggregateType == AggregateType.PERCENTILE_CONT ? + "PERCENTILE_CONT argument" : "PERCENTILE_DISC argument", arg); + } + } + case MEDIAN: + return Percentile.getFromIndex(session, args[0], type.getValueType(), orderByList, Percentile.HALF, true); + case ENVELOPE: + return ((MVSpatialIndex) AggregateDataEnvelope.getGeometryColumnIndex(args[0])).getBounds(session); + default: + throw DbException.getInternalError("type=" + aggregateType); + } + } + + @Override + public Value getAggregatedValue(SessionLocal session, Object aggregateData) { + AggregateData data = (AggregateData) aggregateData; + if (data == null) { + data = (AggregateData) createAggregateData(); + } + switch (aggregateType) { + case COUNT: + if (distinct) { + return ValueBigint.get(((AggregateDataCollecting) data).getCount()); + } + break; + case SUM: + case BIT_XOR_AGG: + case BIT_XNOR_AGG: + if (distinct) { + AggregateDataCollecting c = ((AggregateDataCollecting) data); + if (c.getCount() == 0) { + return ValueNull.INSTANCE; + } + return collect(session, c, new AggregateDataDefault(aggregateType, type)); + } + break; + case AVG: + if (distinct) { + AggregateDataCollecting c = ((AggregateDataCollecting) data); + if (c.getCount() == 0) { + return ValueNull.INSTANCE; + } + return collect(session, c, new AggregateDataAvg(type)); + } + break; + case STDDEV_POP: + case STDDEV_SAMP: + case VAR_POP: + case VAR_SAMP: + if (distinct) { + AggregateDataCollecting c = ((AggregateDataCollecting) data); + if (c.getCount() == 0) { + return ValueNull.INSTANCE; + } + return collect(session, c, new AggregateDataStdVar(aggregateType)); + } + break; + case HISTOGRAM: + return getHistogram(session, data); + case LISTAGG: + return getListagg(session, data); + case ARRAY_AGG: { + Value[] array = ((AggregateDataCollecting) data).getArray(); + if (array == null) { + return ValueNull.INSTANCE; + } + if (orderByList != null || distinct) { + sortWithOrderBy(array); + } + if (orderByList != null) { + for (int i = 0; i < array.length; i++) { + array[i] = ((ValueRow) array[i]).getList()[0]; + } + } + return ValueArray.get((TypeInfo) type.getExtTypeInfo(), array, session); + } + case RANK: + case DENSE_RANK: + case PERCENT_RANK: + case CUME_DIST: + return getHypotheticalSet(session, data); + case PERCENTILE_CONT: + case PERCENTILE_DISC: { + AggregateDataCollecting collectingData = (AggregateDataCollecting) data; + Value[] array = collectingData.getArray(); + if (array == null) { + return ValueNull.INSTANCE; + } + Value v = collectingData.getSharedArgument(); + if (v == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + BigDecimal arg = v.getBigDecimal(); + if (arg.signum() >= 0 && arg.compareTo(BigDecimal.ONE) <= 0) { + return Percentile.getValue(session, array, type.getValueType(), orderByList, arg, + aggregateType == AggregateType.PERCENTILE_CONT); + } else { + throw DbException.getInvalidValueException(aggregateType == AggregateType.PERCENTILE_CONT ? + "PERCENTILE_CONT argument" : "PERCENTILE_DISC argument", arg); + } + } + case MEDIAN: { + Value[] array = ((AggregateDataCollecting) data).getArray(); + if (array == null) { + return ValueNull.INSTANCE; + } + return Percentile.getValue(session, array, type.getValueType(), orderByList, Percentile.HALF, true); + } + case MODE: + return getMode(session, data); + case JSON_ARRAYAGG: { + Value[] array = ((AggregateDataCollecting) data).getArray(); + if (array == null) { + return ValueNull.INSTANCE; + } + if (orderByList != null) { + sortWithOrderBy(array); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write('['); + for (Value v : array) { + if (orderByList != null) { + v = ((ValueRow) v).getList()[0]; + } + JsonConstructorUtils.jsonArrayAppend(baos, v != ValueNull.INSTANCE ? v : ValueJson.NULL, flags); + } + baos.write(']'); + return ValueJson.getInternal(baos.toByteArray()); + } + case JSON_OBJECTAGG: { + Value[] array = ((AggregateDataCollecting) data).getArray(); + if (array == null) { + return ValueNull.INSTANCE; + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write('{'); + for (Value v : array) { + Value[] row = ((ValueRow) v).getList(); + String key = row[0].getString(); + if (key == null) { + throw DbException.getInvalidValueException("JSON_OBJECTAGG key", "NULL"); + } + Value value = row[1]; + if (value == ValueNull.INSTANCE) { + if ((flags & JsonConstructorUtils.JSON_ABSENT_ON_NULL) != 0) { + continue; + } + value = ValueJson.NULL; + } + JsonConstructorUtils.jsonObjectAppend(baos, key, value); + } + return JsonConstructorUtils.jsonObjectFinish(baos, flags); + } + default: + // Avoid compiler warning + } + return data.getValue(session); + } + + private static Value collect(SessionLocal session, AggregateDataCollecting c, AggregateData d) { + for (Value v : c) { + d.add(session, v); + } + return d.getValue(session); + } + + private Value getHypotheticalSet(SessionLocal session, AggregateData data) { + AggregateDataCollecting collectingData = (AggregateDataCollecting) data; + Value arg = collectingData.getSharedArgument(); + if (arg == null) { + switch (aggregateType) { + case RANK: + case DENSE_RANK: + return ValueInteger.get(1); + case PERCENT_RANK: + return ValueDouble.ZERO; + case CUME_DIST: + return ValueDouble.ONE; + default: + throw DbException.getUnsupportedException("aggregateType=" + aggregateType); + } + } + collectingData.add(session, arg); + Value[] array = collectingData.getArray(); + Comparator sort = orderBySort.getRowValueComparator(); + Arrays.sort(array, sort); + return aggregateType == AggregateType.CUME_DIST ? getCumeDist(array, arg, sort) : getRank(array, arg, sort); + } + + private Value getRank(Value[] ordered, Value arg, Comparator sort) { + int size = ordered.length; + int number = 0; + for (int i = 0; i < size; i++) { + Value row = ordered[i]; + if (i == 0) { + number = 1; + } else if (sort.compare(ordered[i - 1], row) != 0) { + if (aggregateType == AggregateType.DENSE_RANK) { + number++; + } else { + number = i + 1; + } + } + Value v; + if (aggregateType == AggregateType.PERCENT_RANK) { + int nm = number - 1; + v = nm == 0 ? ValueDouble.ZERO : ValueDouble.get((double) nm / (size - 1)); + } else { + v = ValueBigint.get(number); + } + if (sort.compare(row, arg) == 0) { + return v; + } + } + throw DbException.getInternalError(); + } + + private static Value getCumeDist(Value[] ordered, Value arg, Comparator sort) { + int size = ordered.length; + for (int start = 0; start < size;) { + Value array = ordered[start]; + int end = start + 1; + while (end < size && sort.compare(array, ordered[end]) == 0) { + end++; + } + ValueDouble v = ValueDouble.get((double) end / size); + for (int i = start; i < end; i++) { + if (sort.compare(ordered[i], arg) == 0) { + return v; + } + } + start = end; + } + throw DbException.getInternalError(); + } + + private Value getListagg(SessionLocal session, AggregateData data) { + AggregateDataCollecting collectingData = (AggregateDataCollecting) data; + Value[] array = collectingData.getArray(); + if (array == null) { + return ValueNull.INSTANCE; + } + if (array.length == 1) { + Value v = array[0]; + if (orderByList != null) { + v = ((ValueRow) v).getList()[0]; + } + return v.convertTo(Value.VARCHAR, session); + } + if (orderByList != null || distinct) { + sortWithOrderBy(array); + } + ListaggArguments arguments = (ListaggArguments) extraArguments; + String separator = arguments.getEffectiveSeparator(); + return ValueVarchar + .get((arguments.getOnOverflowTruncate() + ? getListaggTruncate(array, separator, arguments.getEffectiveFilter(), + arguments.isWithoutCount()) + : getListaggError(array, separator)).toString(), session); + } + + private StringBuilder getListaggError(Value[] array, String separator) { + StringBuilder builder = new StringBuilder(getListaggItem(array[0])); + for (int i = 1, count = array.length; i < count; i++) { + builder.append(separator).append(getListaggItem(array[i])); + if (builder.length() > Constants.MAX_STRING_LENGTH) { + throw DbException.getValueTooLongException("CHARACTER VARYING", builder.substring(0, 81), -1L); + } + } + return builder; + } + + private StringBuilder getListaggTruncate(Value[] array, String separator, String filter, + boolean withoutCount) { + int count = array.length; + String[] strings = new String[count]; + String s = getListaggItem(array[0]); + strings[0] = 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(); + if (length > Constants.MAX_STRING_LENGTH) { + for (; i > 0; i--) { + length -= strings[i].length(); + builder.setLength(length); + builder.append(filter); + if (!withoutCount) { + builder.append('(').append(count - i).append(')'); + } + if (builder.length() <= Constants.MAX_STRING_LENGTH) { + break loop; + } + length -= separator.length(); + } + builder.setLength(0); + builder.append(filter).append('(').append(count).append(')'); + break; + } + } + return builder; + } + + private String getListaggItem(Value v) { + if (orderByList != null) { + v = ((ValueRow) v).getList()[0]; + } + return v.getString(); + } + + private Value getHistogram(SessionLocal session, AggregateData data) { + TreeMap distinctValues = ((AggregateDataDistinctWithCounts) data).getValues(); + TypeInfo rowType = (TypeInfo) type.getExtTypeInfo(); + if (distinctValues == null) { + return ValueArray.get(rowType, Value.EMPTY_VALUES, session); + } + ValueRow[] values = new ValueRow[distinctValues.size()]; + int i = 0; + for (Entry entry : distinctValues.entrySet()) { + LongDataCounter d = entry.getValue(); + values[i] = ValueRow.get(rowType, new Value[] { entry.getKey(), ValueBigint.get(d.count) }); + i++; + } + Database db = session.getDatabase(); + CompareMode compareMode = db.getCompareMode(); + Arrays.sort(values, (v1, v2) -> v1.getList()[0].compareTo(v2.getList()[0], session, compareMode)); + return ValueArray.get(rowType, values, session); + } + + private Value getMode(SessionLocal session, AggregateData data) { + Value v = ValueNull.INSTANCE; + TreeMap distinctValues = ((AggregateDataDistinctWithCounts) data).getValues(); + if (distinctValues == null) { + return v; + } + long count = 0L; + if (orderByList != null) { + boolean desc = (orderByList.get(0).sortType & SortOrder.DESCENDING) != 0; + for (Entry entry : distinctValues.entrySet()) { + long c = entry.getValue().count; + if (c > count) { + v = entry.getKey(); + count = c; + } else if (c == count) { + Value v2 = entry.getKey(); + int cmp = session.compareTypeSafe(v, v2); + if (desc) { + if (cmp >= 0) { + continue; + } + } else if (cmp <= 0) { + continue; + } + v = v2; + } + } + } else { + for (Entry entry : distinctValues.entrySet()) { + long c = entry.getValue().count; + if (c > count) { + v = entry.getKey(); + count = c; + } + } + } + return v; + } + + @Override + public void mapColumnsAnalysis(ColumnResolver resolver, int level, int innerState) { + if (orderByList != null) { + for (QueryOrderBy o : orderByList) { + o.expression.mapColumns(resolver, level, innerState); + } + } + super.mapColumnsAnalysis(resolver, level, innerState); + } + + @Override + public Expression optimize(SessionLocal session) { + super.optimize(session); + if (args.length == 1) { + type = args[0].getType(); + } + if (orderByList != null) { + int offset; + switch (aggregateType) { + case ARRAY_AGG: + case LISTAGG: + case JSON_ARRAYAGG: + offset = 1; + break; + default: + offset = 0; + } + for (Iterator i = orderByList.iterator(); i.hasNext();) { + QueryOrderBy o = i.next(); + Expression e = o.expression.optimize(session); + if (offset != 0 && e.isConstant()) { + i.remove(); + } else { + o.expression = e; + } + } + if (orderByList.isEmpty()) { + orderByList = null; + } else { + orderBySort = createOrder(session, orderByList, offset); + } + } + switch (aggregateType) { + case LISTAGG: + type = TypeInfo.TYPE_VARCHAR; + break; + case COUNT: + if (args[0].isConstant()) { + if (args[0].getValue(session) == ValueNull.INSTANCE) { + return ValueExpression.get(ValueBigint.get(0L)); + } + if (!distinct) { + Aggregate aggregate = new Aggregate(AggregateType.COUNT_ALL, new Expression[0], select, false); + aggregate.setFilterCondition(filterCondition); + aggregate.setOverCondition(over); + return aggregate.optimize(session); + } + } + //$FALL-THROUGH$ + case COUNT_ALL: + case REGR_COUNT: + type = TypeInfo.TYPE_BIGINT; + break; + case HISTOGRAM: { + LinkedHashMap fields = new LinkedHashMap<>(4); + fields.put("VALUE", type); + fields.put("COUNT", TypeInfo.TYPE_BIGINT); + type = TypeInfo.getTypeInfo(Value.ARRAY, -1, 0, + TypeInfo.getTypeInfo(Value.ROW, -1, -1, new ExtTypeInfoRow(fields))); + break; + } + case SUM: + if ((type = getSumType(type)) == null) { + throw DbException.get(ErrorCode.SUM_OR_AVG_ON_WRONG_DATATYPE_1, getTraceSQL()); + } + break; + case AVG: + if ((type = getAvgType(type)) == null) { + throw DbException.get(ErrorCode.SUM_OR_AVG_ON_WRONG_DATATYPE_1, getTraceSQL()); + } + break; + case MIN: + case MAX: + break; + case STDDEV_POP: + case STDDEV_SAMP: + case VAR_POP: + case VAR_SAMP: + case COVAR_POP: + case COVAR_SAMP: + case CORR: + case REGR_SLOPE: + case REGR_INTERCEPT: + case REGR_R2: + case REGR_SXX: + case REGR_SYY: + case REGR_SXY: + type = TypeInfo.TYPE_DOUBLE; + break; + case REGR_AVGX: + if ((type = getAvgType(args[1].getType())) == null) { + throw DbException.get(ErrorCode.SUM_OR_AVG_ON_WRONG_DATATYPE_1, getTraceSQL()); + } + break; + case REGR_AVGY: + if ((type = getAvgType(args[0].getType())) == null) { + throw DbException.get(ErrorCode.SUM_OR_AVG_ON_WRONG_DATATYPE_1, getTraceSQL()); + } + break; + case RANK: + case DENSE_RANK: + type = TypeInfo.TYPE_BIGINT; + break; + case PERCENT_RANK: + case CUME_DIST: + type = TypeInfo.TYPE_DOUBLE; + break; + case PERCENTILE_CONT: + type = orderByList.get(0).expression.getType(); + //$FALL-THROUGH$ + case MEDIAN: + switch (type.getValueType()) { + case Value.TINYINT: + case Value.SMALLINT: + case Value.INTEGER: + case Value.BIGINT: + case Value.NUMERIC: + case Value.REAL: + case Value.DOUBLE: + case Value.DECFLOAT: + type = TypeInfo.TYPE_NUMERIC_FLOATING_POINT; + break; + } + break; + case PERCENTILE_DISC: + case MODE: + type = orderByList.get(0).expression.getType(); + break; + case EVERY: + case ANY: + type = TypeInfo.TYPE_BOOLEAN; + break; + case BIT_AND_AGG: + case BIT_OR_AGG: + case BIT_XOR_AGG: + case BIT_NAND_AGG: + case BIT_NOR_AGG: + case BIT_XNOR_AGG: + BitFunction.checkArgType(args[0]); + break; + case ARRAY_AGG: + type = TypeInfo.getTypeInfo(Value.ARRAY, -1, 0, args[0].getType()); + break; + case ENVELOPE: + type = TypeInfo.TYPE_GEOMETRY; + break; + case JSON_OBJECTAGG: + case JSON_ARRAYAGG: + type = TypeInfo.TYPE_JSON; + break; + default: + throw DbException.getInternalError("type=" + aggregateType); + } + return this; + } + + private static TypeInfo getSumType(TypeInfo type) { + int valueType = type.getValueType(); + switch (valueType) { + case Value.BOOLEAN: + case Value.TINYINT: + case Value.SMALLINT: + case Value.INTEGER: + return TypeInfo.TYPE_BIGINT; + case Value.BIGINT: + return TypeInfo.getTypeInfo(Value.NUMERIC, ValueBigint.DECIMAL_PRECISION + ADDITIONAL_SUM_PRECISION, -1, + null); + case Value.NUMERIC: + return TypeInfo.getTypeInfo(Value.NUMERIC, type.getPrecision() + ADDITIONAL_SUM_PRECISION, + type.getDeclaredScale(), null); + case Value.REAL: + return TypeInfo.TYPE_DOUBLE; + case Value.DOUBLE: + return TypeInfo.getTypeInfo(Value.DECFLOAT, ValueDouble.DECIMAL_PRECISION + ADDITIONAL_SUM_PRECISION, -1, + null); + case Value.DECFLOAT: + return TypeInfo.getTypeInfo(Value.DECFLOAT, type.getPrecision() + ADDITIONAL_SUM_PRECISION, -1, null); + default: + if (DataType.isIntervalType(valueType)) { + return TypeInfo.getTypeInfo(valueType, ValueInterval.MAXIMUM_PRECISION, type.getDeclaredScale(), null); + } + return null; + } + } + + private static TypeInfo getAvgType(TypeInfo type) { + switch (type.getValueType()) { + case Value.TINYINT: + case Value.SMALLINT: + case Value.INTEGER: + case Value.REAL: + return TypeInfo.TYPE_DOUBLE; + case Value.BIGINT: + return TypeInfo.getTypeInfo(Value.NUMERIC, ValueBigint.DECIMAL_PRECISION + ADDITIONAL_AVG_SCALE, + ADDITIONAL_AVG_SCALE, null); + case Value.NUMERIC: { + int additionalScale = Math.min(ValueNumeric.MAXIMUM_SCALE - type.getScale(), + Math.min(Constants.MAX_NUMERIC_PRECISION - (int) type.getPrecision(), ADDITIONAL_AVG_SCALE)); + return TypeInfo.getTypeInfo(Value.NUMERIC, type.getPrecision() + additionalScale, + type.getScale() + additionalScale, null); + } + case Value.DOUBLE: + return TypeInfo.getTypeInfo(Value.DECFLOAT, ValueDouble.DECIMAL_PRECISION + ADDITIONAL_AVG_SCALE, -1, // + null); + case Value.DECFLOAT: + return TypeInfo.getTypeInfo(Value.DECFLOAT, type.getPrecision() + ADDITIONAL_AVG_SCALE, -1, null); + case Value.INTERVAL_YEAR: + case Value.INTERVAL_YEAR_TO_MONTH: + return TypeInfo.getTypeInfo(Value.INTERVAL_YEAR_TO_MONTH, type.getDeclaredPrecision(), 0, null); + case Value.INTERVAL_MONTH: + return TypeInfo.getTypeInfo(Value.INTERVAL_MONTH, type.getDeclaredPrecision(), 0, null); + case Value.INTERVAL_DAY: + case Value.INTERVAL_DAY_TO_HOUR: + case Value.INTERVAL_DAY_TO_MINUTE: + case Value.INTERVAL_DAY_TO_SECOND: + return TypeInfo.getTypeInfo(Value.INTERVAL_DAY_TO_SECOND, type.getDeclaredPrecision(), + ValueInterval.MAXIMUM_SCALE, null); + case Value.INTERVAL_HOUR: + case Value.INTERVAL_HOUR_TO_MINUTE: + case Value.INTERVAL_HOUR_TO_SECOND: + return TypeInfo.getTypeInfo(Value.INTERVAL_HOUR_TO_SECOND, type.getDeclaredPrecision(), + ValueInterval.MAXIMUM_SCALE, null); + case Value.INTERVAL_MINUTE: + case Value.INTERVAL_MINUTE_TO_SECOND: + return TypeInfo.getTypeInfo(Value.INTERVAL_MINUTE_TO_SECOND, type.getDeclaredPrecision(), + ValueInterval.MAXIMUM_SCALE, null); + case Value.INTERVAL_SECOND: + return TypeInfo.getTypeInfo(Value.INTERVAL_SECOND, type.getDeclaredPrecision(), // + ValueInterval.MAXIMUM_SCALE, null); + default: + return null; + } + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + if (orderByList != null) { + for (QueryOrderBy o : orderByList) { + o.expression.setEvaluatable(tableFilter, b); + } + } + super.setEvaluatable(tableFilter, b); + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + switch (aggregateType) { + case COUNT_ALL: + return appendTailConditions(builder.append("COUNT(*)"), sqlFlags, false); + case LISTAGG: + return getSQLListagg(builder, sqlFlags); + case ARRAY_AGG: + return getSQLArrayAggregate(builder, sqlFlags); + case JSON_OBJECTAGG: + return getSQLJsonObjectAggregate(builder, sqlFlags); + case JSON_ARRAYAGG: + return getSQLJsonArrayAggregate(builder, sqlFlags); + default: + } + builder.append(aggregateType.name()); + if (distinct) { + builder.append("(DISTINCT "); + } else { + builder.append('('); + } + writeExpressions(builder, args, sqlFlags).append(')'); + if (orderByList != null) { + builder.append(" WITHIN GROUP ("); + Window.appendOrderBy(builder, orderByList, sqlFlags, false); + builder.append(')'); + } + return appendTailConditions(builder, sqlFlags, false); + } + + private StringBuilder getSQLArrayAggregate(StringBuilder builder, int sqlFlags) { + builder.append("ARRAY_AGG("); + if (distinct) { + builder.append("DISTINCT "); + } + args[0].getUnenclosedSQL(builder, sqlFlags); + Window.appendOrderBy(builder, orderByList, sqlFlags, false); + builder.append(')'); + return appendTailConditions(builder, sqlFlags, false); + } + + private StringBuilder getSQLListagg(StringBuilder builder, int sqlFlags) { + builder.append("LISTAGG("); + if (distinct) { + builder.append("DISTINCT "); + } + args[0].getUnenclosedSQL(builder, sqlFlags); + ListaggArguments arguments = (ListaggArguments) extraArguments; + String s = arguments.getSeparator(); + if (s != null) { + StringUtils.quoteStringSQL(builder.append(", "), s); + } + if (arguments.getOnOverflowTruncate()) { + builder.append(" ON OVERFLOW TRUNCATE "); + s = arguments.getFilter(); + if (s != null) { + StringUtils.quoteStringSQL(builder, s).append(' '); + } + builder.append(arguments.isWithoutCount() ? "WITHOUT" : "WITH").append(" COUNT"); + } + builder.append(')'); + builder.append(" WITHIN GROUP ("); + Window.appendOrderBy(builder, orderByList, sqlFlags, true); + builder.append(')'); + return appendTailConditions(builder, sqlFlags, false); + } + + private StringBuilder getSQLJsonObjectAggregate(StringBuilder builder, int sqlFlags) { + builder.append("JSON_OBJECTAGG("); + args[0].getUnenclosedSQL(builder, sqlFlags).append(": "); + args[1].getUnenclosedSQL(builder, sqlFlags); + JsonConstructorFunction.getJsonFunctionFlagsSQL(builder, flags, false).append(')'); + return appendTailConditions(builder, sqlFlags, false); + } + + private StringBuilder getSQLJsonArrayAggregate(StringBuilder builder, int sqlFlags) { + builder.append("JSON_ARRAYAGG("); + if (distinct) { + builder.append("DISTINCT "); + } + args[0].getUnenclosedSQL(builder, sqlFlags); + JsonConstructorFunction.getJsonFunctionFlagsSQL(builder, flags, true); + Window.appendOrderBy(builder, orderByList, sqlFlags, false); + builder.append(')'); + return appendTailConditions(builder, sqlFlags, false); + } + + private Index getMinMaxColumnIndex() { + Expression arg = args[0]; + if (arg instanceof ExpressionColumn) { + ExpressionColumn col = (ExpressionColumn) arg; + Column column = col.getColumn(); + TableFilter filter = col.getTableFilter(); + if (filter != null) { + Table table = filter.getTable(); + return table.getIndexForColumn(column, true, false); + } + } + return null; + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + if (!super.isEverything(visitor)) { + return false; + } + if (filterCondition != null && !filterCondition.isEverything(visitor)) { + return false; + } + if (visitor.getType() == ExpressionVisitor.OPTIMIZABLE_AGGREGATE) { + switch (aggregateType) { + case COUNT: + if (distinct || args[0].getNullable() != Column.NOT_NULLABLE) { + return false; + } + //$FALL-THROUGH$ + case COUNT_ALL: + return visitor.getTable().canGetRowCount(select.getSession()); + case MIN: + case MAX: + return getMinMaxColumnIndex() != null; + case PERCENTILE_CONT: + case PERCENTILE_DISC: + return args[0].isConstant() && Percentile.getColumnIndex(select.getSession().getDatabase(), + orderByList.get(0).expression) != null; + case MEDIAN: + if (distinct) { + return false; + } + return Percentile.getColumnIndex(select.getSession().getDatabase(), args[0]) != null; + case ENVELOPE: + return AggregateDataEnvelope.getGeometryColumnIndex(args[0]) != null; + default: + return false; + } + } + for (Expression arg : args) { + if (!arg.isEverything(visitor)) { + return false; + } + } + if (orderByList != null) { + for (QueryOrderBy o : orderByList) { + if (!o.expression.isEverything(visitor)) { + return false; + } + } + } + return true; + } + + @Override + public int getCost() { + int cost = 1; + for (Expression arg : args) { + cost += arg.getCost(); + } + if (orderByList != null) { + for (QueryOrderBy o : orderByList) { + cost += o.expression.getCost(); + } + } + if (filterCondition != null) { + cost += filterCondition.getCost(); + } + return cost; + } + + /** + * Returns the select statement. + * @return the select statement + */ + public Select getSelect() { + return select; + } + + /** + * Returns if distinct is used. + * + * @return if distinct is used + */ + public boolean isDistinct() { + return distinct; + } + +} diff --git a/h2/src/main/org/h2/expression/aggregate/AggregateData.java b/h2/src/main/org/h2/expression/aggregate/AggregateData.java new file mode 100644 index 0000000..97986b4 --- /dev/null +++ b/h2/src/main/org/h2/expression/aggregate/AggregateData.java @@ -0,0 +1,32 @@ +/* + * 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.expression.aggregate; + +import org.h2.engine.SessionLocal; +import org.h2.value.Value; + +/** + * Abstract class for the computation of an aggregate. + */ +abstract class AggregateData { + + /** + * Add a value to this aggregate. + * + * @param session the session + * @param v the value + */ + abstract void add(SessionLocal session, Value v); + + /** + * Get the aggregate result. + * + * @param session the session + * @return the value + */ + abstract Value getValue(SessionLocal session); + +} diff --git a/h2/src/main/org/h2/expression/aggregate/AggregateDataAvg.java b/h2/src/main/org/h2/expression/aggregate/AggregateDataAvg.java new file mode 100644 index 0000000..283ad62 --- /dev/null +++ b/h2/src/main/org/h2/expression/aggregate/AggregateDataAvg.java @@ -0,0 +1,90 @@ +/* + * 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.expression.aggregate; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; + +import org.h2.api.IntervalQualifier; +import org.h2.engine.SessionLocal; +import org.h2.util.IntervalUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueDecfloat; +import org.h2.value.ValueDouble; +import org.h2.value.ValueInterval; +import org.h2.value.ValueNull; +import org.h2.value.ValueNumeric; + +/** + * Data stored while calculating an AVG aggregate. + */ +final class AggregateDataAvg extends AggregateData { + + private final TypeInfo dataType; + private long count; + private double doubleValue; + private BigDecimal decimalValue; + private BigInteger integerValue; + + /** + * @param dataType + * the data type of the computed result + */ + AggregateDataAvg(TypeInfo dataType) { + this.dataType = dataType; + } + + @Override + void add(SessionLocal session, Value v) { + if (v == ValueNull.INSTANCE) { + return; + } + count++; + switch (dataType.getValueType()) { + case Value.DOUBLE: + doubleValue += v.getDouble(); + break; + case Value.NUMERIC: + case Value.DECFLOAT: { + BigDecimal bd = v.getBigDecimal(); + decimalValue = decimalValue == null ? bd : decimalValue.add(bd); + break; + } + default: { + BigInteger bi = IntervalUtils.intervalToAbsolute((ValueInterval) v); + integerValue = integerValue == null ? bi : integerValue.add(bi); + } + } + } + + @Override + Value getValue(SessionLocal session) { + if (count == 0) { + return ValueNull.INSTANCE; + } + Value v; + int valueType = dataType.getValueType(); + switch (valueType) { + case Value.DOUBLE: + v = ValueDouble.get(doubleValue / count); + break; + case Value.NUMERIC: + v = ValueNumeric + .get(decimalValue.divide(BigDecimal.valueOf(count), dataType.getScale(), RoundingMode.HALF_DOWN)); + break; + case Value.DECFLOAT: + v = ValueDecfloat.divide(decimalValue, BigDecimal.valueOf(count), dataType); + break; + default: + v = IntervalUtils.intervalFromAbsolute(IntervalQualifier.valueOf(valueType - Value.INTERVAL_YEAR), + integerValue.divide(BigInteger.valueOf(count))); + } + return v.castTo(dataType, session); + } + +} diff --git a/h2/src/main/org/h2/expression/aggregate/AggregateDataBinarySet.java b/h2/src/main/org/h2/expression/aggregate/AggregateDataBinarySet.java new file mode 100644 index 0000000..fc788db --- /dev/null +++ b/h2/src/main/org/h2/expression/aggregate/AggregateDataBinarySet.java @@ -0,0 +1,24 @@ +/* + * 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.expression.aggregate; + +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.value.Value; + +/** + * Aggregate data of binary set functions. + */ +abstract class AggregateDataBinarySet extends AggregateData { + + abstract void add(SessionLocal session, Value yValue, Value xValue); + + @Override + final void add(SessionLocal session, Value v) { + throw DbException.getInternalError(); + } + +} diff --git a/h2/src/main/org/h2/expression/aggregate/AggregateDataCollecting.java b/h2/src/main/org/h2/expression/aggregate/AggregateDataCollecting.java new file mode 100644 index 0000000..af1e267 --- /dev/null +++ b/h2/src/main/org/h2/expression/aggregate/AggregateDataCollecting.java @@ -0,0 +1,168 @@ +/* + * 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.expression.aggregate; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.TreeSet; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.value.Value; +import org.h2.value.ValueNull; +import org.h2.value.ValueRow; + +/** + * Data stored while calculating an aggregate that needs collecting of all + * values or a distinct aggregate. + * + *

+ * NULL values are not collected. {@link #getValue(SessionLocal)} method + * returns {@code null}. Use {@link #getArray()} for instances of this class + * instead. + *

+ */ +final class AggregateDataCollecting extends AggregateData implements Iterable { + + /** + * NULL values collection mode. + */ + enum NullCollectionMode { + + /** + * Rows with NULL value are completely ignored. + */ + IGNORED, + + /** + * Rows with NULL values are processed causing the result to be not + * NULL, but NULL values aren't collected. + */ + EXCLUDED, + + /** + * Rows with NULL values are aggregated just like rows with any other + * values, should also be used when NULL values aren't passed to + * {@linkplain AggregateDataCollecting}. + */ + USED_OR_IMPOSSIBLE; + + } + + private final boolean distinct; + + private final boolean orderedWithOrder; + + private final NullCollectionMode nullCollectionMode; + + Collection values; + + private Value shared; + + /** + * Creates new instance of data for collecting aggregates. + * + * @param distinct + * if distinct is used + * @param orderedWithOrder + * if aggregate is an ordered aggregate with ORDER BY clause + * @param nullCollectionMode + * NULL values collection mode + */ + AggregateDataCollecting(boolean distinct, boolean orderedWithOrder, NullCollectionMode nullCollectionMode) { + this.distinct = distinct; + this.orderedWithOrder = orderedWithOrder; + this.nullCollectionMode = nullCollectionMode; + } + + @Override + void add(SessionLocal session, Value v) { + if (nullCollectionMode == NullCollectionMode.IGNORED && isNull(v)) { + return; + } + Collection c = values; + if (c == null) { + if (distinct) { + Comparator comparator = session.getDatabase().getCompareMode(); + if (orderedWithOrder) { + comparator = Comparator.comparing(t -> ((ValueRow) t).getList()[0], comparator); + } + c = new TreeSet<>(comparator); + } else { + c = new ArrayList<>(); + } + values = c; + } + if (nullCollectionMode == NullCollectionMode.EXCLUDED && isNull(v)) { + return; + } + c.add(v); + } + + private boolean isNull(Value v) { + return (orderedWithOrder ? ((ValueRow) v).getList()[0] : v) == ValueNull.INSTANCE; + } + + @Override + Value getValue(SessionLocal session) { + return null; + } + + /** + * Returns the count of values. + * + * @return the count of values + */ + int getCount() { + return values != null ? values.size() : 0; + } + + /** + * Returns array with values or {@code null}. + * + * @return array with values or {@code null} + */ + Value[] getArray() { + Collection values = this.values; + if (values == null) { + return null; + } + return values.toArray(Value.EMPTY_VALUES); + } + + @Override + public Iterator iterator() { + return values != null ? values.iterator() : Collections.emptyIterator(); + } + + /** + * Sets value of a shared argument. + * + * @param shared the shared value + */ + void setSharedArgument(Value shared) { + if (this.shared == null) { + this.shared = shared; + } else if (!this.shared.equals(shared)) { + throw DbException.get(ErrorCode.INVALID_VALUE_2, "Inverse distribution function argument", + this.shared.getTraceSQL() + "<>" + shared.getTraceSQL()); + } + } + + /** + * Returns value of a shared argument. + * + * @return value of a shared argument + */ + Value getSharedArgument() { + return shared; + } + +} diff --git a/h2/src/main/org/h2/expression/aggregate/AggregateDataCorr.java b/h2/src/main/org/h2/expression/aggregate/AggregateDataCorr.java new file mode 100644 index 0000000..28b6160 --- /dev/null +++ b/h2/src/main/org/h2/expression/aggregate/AggregateDataCorr.java @@ -0,0 +1,96 @@ +/* + * 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.expression.aggregate; + +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.value.Value; +import org.h2.value.ValueDouble; +import org.h2.value.ValueNull; + +/** + * Data stored while calculating a CORR, REG_SLOPE, REG_INTERCEPT, or REGR_R2 + * aggregate. + */ +final class AggregateDataCorr extends AggregateDataBinarySet { + + private final AggregateType aggregateType; + + private long count; + + private double sumY, sumX, sumYX; + + private double m2y, meanY; + + private double m2x, meanX; + + AggregateDataCorr(AggregateType aggregateType) { + this.aggregateType = aggregateType; + } + + @Override + void add(SessionLocal session, Value yValue, Value xValue) { + double y = yValue.getDouble(), x = xValue.getDouble(); + sumY += y; + sumX += x; + sumYX += y * x; + if (++count == 1) { + meanY = y; + meanX = x; + m2x = m2y = 0; + } else { + double delta = y - meanY; + meanY += delta / count; + m2y += delta * (y - meanY); + delta = x - meanX; + meanX += delta / count; + m2x += delta * (x - meanX); + } + } + + @Override + Value getValue(SessionLocal session) { + if (count < 1) { + return ValueNull.INSTANCE; + } + double v; + switch (aggregateType) { + case CORR: + if (m2y == 0 || m2x == 0) { + return ValueNull.INSTANCE; + } + v = (sumYX - sumX * sumY / count) / Math.sqrt(m2y * m2x); + break; + case REGR_SLOPE: + if (m2x == 0) { + return ValueNull.INSTANCE; + } + v = (sumYX - sumX * sumY / count) / m2x; + break; + case REGR_INTERCEPT: + if (m2x == 0) { + return ValueNull.INSTANCE; + } + v = meanY - (sumYX - sumX * sumY / count) / m2x * meanX; + break; + case REGR_R2: { + if (m2x == 0) { + return ValueNull.INSTANCE; + } + if (m2y == 0) { + return ValueDouble.ONE; + } + v = sumYX - sumX * sumY / count; + v = v * v / (m2y * m2x); + break; + } + default: + throw DbException.getInternalError("type=" + aggregateType); + } + return ValueDouble.get(v); + } + +} diff --git a/h2/src/main/org/h2/expression/aggregate/AggregateDataCount.java b/h2/src/main/org/h2/expression/aggregate/AggregateDataCount.java new file mode 100644 index 0000000..b0841b1 --- /dev/null +++ b/h2/src/main/org/h2/expression/aggregate/AggregateDataCount.java @@ -0,0 +1,38 @@ +/* + * 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.expression.aggregate; + +import org.h2.engine.SessionLocal; +import org.h2.value.Value; +import org.h2.value.ValueBigint; +import org.h2.value.ValueNull; + +/** + * Data stored while calculating a COUNT aggregate. + */ +final class AggregateDataCount extends AggregateData { + + private final boolean all; + + private long count; + + AggregateDataCount(boolean all) { + this.all = all; + } + + @Override + void add(SessionLocal session, Value v) { + if (all || v != ValueNull.INSTANCE) { + count++; + } + } + + @Override + Value getValue(SessionLocal session) { + return ValueBigint.get(count); + } + +} diff --git a/h2/src/main/org/h2/expression/aggregate/AggregateDataCovar.java b/h2/src/main/org/h2/expression/aggregate/AggregateDataCovar.java new file mode 100644 index 0000000..acd0031 --- /dev/null +++ b/h2/src/main/org/h2/expression/aggregate/AggregateDataCovar.java @@ -0,0 +1,70 @@ +/* + * 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.expression.aggregate; + +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.value.Value; +import org.h2.value.ValueDouble; +import org.h2.value.ValueNull; + +/** + * Data stored while calculating a COVAR_POP, COVAR_SAMP, or REGR_SXY aggregate. + */ +final class AggregateDataCovar extends AggregateDataBinarySet { + + private final AggregateType aggregateType; + + private long count; + + private double sumY, sumX, sumYX; + + /** + * @param aggregateType + * the type of the aggregate operation + */ + AggregateDataCovar(AggregateType aggregateType) { + this.aggregateType = aggregateType; + } + + @Override + void add(SessionLocal session, Value yValue, Value xValue) { + double y = yValue.getDouble(), x = xValue.getDouble(); + sumY += y; + sumX += x; + sumYX += y * x; + count++; + } + + @Override + Value getValue(SessionLocal session) { + double v; + switch (aggregateType) { + case COVAR_POP: + if (count < 1) { + return ValueNull.INSTANCE; + } + v = (sumYX - sumX * sumY / count) / count; + break; + case COVAR_SAMP: + if (count < 2) { + return ValueNull.INSTANCE; + } + v = (sumYX - sumX * sumY / count) / (count - 1); + break; + case REGR_SXY: + if (count < 1) { + return ValueNull.INSTANCE; + } + v = sumYX - sumX * sumY / count; + break; + default: + throw DbException.getInternalError("type=" + aggregateType); + } + return ValueDouble.get(v); + } + +} diff --git a/h2/src/main/org/h2/expression/aggregate/AggregateDataDefault.java b/h2/src/main/org/h2/expression/aggregate/AggregateDataDefault.java new file mode 100644 index 0000000..0ff71f2 --- /dev/null +++ b/h2/src/main/org/h2/expression/aggregate/AggregateDataDefault.java @@ -0,0 +1,119 @@ +/* + * 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.expression.aggregate; + +import org.h2.engine.SessionLocal; +import org.h2.expression.function.BitFunction; +import org.h2.message.DbException; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueNull; + +/** + * Data stored while calculating an aggregate. + */ +final class AggregateDataDefault extends AggregateData { + + private final AggregateType aggregateType; + private final TypeInfo dataType; + private Value value; + + /** + * @param aggregateType the type of the aggregate operation + * @param dataType the data type of the computed result + */ + AggregateDataDefault(AggregateType aggregateType, TypeInfo dataType) { + this.aggregateType = aggregateType; + this.dataType = dataType; + } + + @Override + void add(SessionLocal session, Value v) { + if (v == ValueNull.INSTANCE) { + return; + } + switch (aggregateType) { + case SUM: + if (value == null) { + value = v.convertTo(dataType.getValueType()); + } else { + v = v.convertTo(value.getValueType()); + value = value.add(v); + } + break; + case MIN: + if (value == null || session.compare(v, value) < 0) { + value = v; + } + break; + case MAX: + if (value == null || session.compare(v, value) > 0) { + value = v; + } + break; + case EVERY: + v = v.convertToBoolean(); + if (value == null) { + value = v; + } else { + value = ValueBoolean.get(value.getBoolean() && v.getBoolean()); + } + break; + case ANY: + v = v.convertToBoolean(); + if (value == null) { + value = v; + } else { + value = ValueBoolean.get(value.getBoolean() || v.getBoolean()); + } + break; + case BIT_AND_AGG: + case BIT_NAND_AGG: + if (value == null) { + value = v; + } else { + value = BitFunction.getBitwise(BitFunction.BITAND, dataType, value, v); + } + break; + case BIT_OR_AGG: + case BIT_NOR_AGG: + if (value == null) { + value = v; + } else { + value = BitFunction.getBitwise(BitFunction.BITOR, dataType, value, v); + } + break; + case BIT_XOR_AGG: + case BIT_XNOR_AGG: + if (value == null) { + value = v; + } else { + value = BitFunction.getBitwise(BitFunction.BITXOR, dataType, value, v); + } + break; + default: + throw DbException.getInternalError("type=" + aggregateType); + } + } + + @SuppressWarnings("incomplete-switch") + @Override + Value getValue(SessionLocal session) { + Value v = value; + if (v == null) { + return ValueNull.INSTANCE; + } + switch (aggregateType) { + case BIT_NAND_AGG: + case BIT_NOR_AGG: + case BIT_XNOR_AGG: + v = BitFunction.getBitwise(BitFunction.BITNOT, dataType, v, null); + } + return v.convertTo(dataType); + } + +} diff --git a/h2/src/main/org/h2/expression/aggregate/AggregateDataDistinctWithCounts.java b/h2/src/main/org/h2/expression/aggregate/AggregateDataDistinctWithCounts.java new file mode 100644 index 0000000..60bd31e --- /dev/null +++ b/h2/src/main/org/h2/expression/aggregate/AggregateDataDistinctWithCounts.java @@ -0,0 +1,72 @@ +/* + * 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.expression.aggregate; + +import java.util.TreeMap; +import org.h2.engine.SessionLocal; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * Data stored while calculating an aggregate that needs distinct values with + * their counts. + */ +final class AggregateDataDistinctWithCounts extends AggregateData { + + private final boolean ignoreNulls; + + private final int maxDistinctCount; + + private TreeMap values; + + /** + * Creates new instance of data for aggregate that needs distinct values + * with their counts. + * + * @param ignoreNulls + * whether NULL values should be ignored + * @param maxDistinctCount + * maximum count of distinct values to collect + */ + AggregateDataDistinctWithCounts(boolean ignoreNulls, int maxDistinctCount) { + this.ignoreNulls = ignoreNulls; + this.maxDistinctCount = maxDistinctCount; + } + + @Override + void add(SessionLocal session, Value v) { + if (ignoreNulls && v == ValueNull.INSTANCE) { + return; + } + if (values == null) { + values = new TreeMap<>(session.getDatabase().getCompareMode()); + } + LongDataCounter a = values.get(v); + if (a == null) { + if (values.size() >= maxDistinctCount) { + return; + } + a = new LongDataCounter(); + values.put(v, a); + } + a.count++; + } + + @Override + Value getValue(SessionLocal session) { + return null; + } + + /** + * Returns map with values and their counts. + * + * @return map with values and their counts + */ + TreeMap getValues() { + return values; + } + +} diff --git a/h2/src/main/org/h2/expression/aggregate/AggregateDataEnvelope.java b/h2/src/main/org/h2/expression/aggregate/AggregateDataEnvelope.java new file mode 100644 index 0000000..a221524 --- /dev/null +++ b/h2/src/main/org/h2/expression/aggregate/AggregateDataEnvelope.java @@ -0,0 +1,72 @@ +/* + * 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.expression.aggregate; + +import java.util.ArrayList; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.index.Index; +import org.h2.mvstore.db.MVSpatialIndex; +import org.h2.table.Column; +import org.h2.table.TableFilter; +import org.h2.util.geometry.GeometryUtils; +import org.h2.value.Value; +import org.h2.value.ValueGeometry; +import org.h2.value.ValueNull; + +/** + * Data stored while calculating an aggregate. + */ +final class AggregateDataEnvelope extends AggregateData { + + private double[] envelope; + + /** + * Get the index (if any) for the column specified in the geometry + * aggregate. + * + * @param on + * the expression (usually a column expression) + * @return the index, or null + */ + static Index getGeometryColumnIndex(Expression on) { + if (on instanceof ExpressionColumn) { + ExpressionColumn col = (ExpressionColumn) on; + Column column = col.getColumn(); + if (column.getType().getValueType() == Value.GEOMETRY) { + TableFilter filter = col.getTableFilter(); + if (filter != null) { + ArrayList indexes = filter.getTable().getIndexes(); + if (indexes != null) { + for (int i = 1, size = indexes.size(); i < size; i++) { + Index index = indexes.get(i); + if (index instanceof MVSpatialIndex && index.isFirstColumn(column)) { + return index; + } + } + } + } + } + } + return null; + } + + @Override + void add(SessionLocal session, Value v) { + if (v == ValueNull.INSTANCE) { + return; + } + envelope = GeometryUtils.union(envelope, v.convertToGeometry(null).getEnvelopeNoCopy()); + } + + @Override + Value getValue(SessionLocal session) { + return ValueGeometry.fromEnvelope(envelope); + } + +} diff --git a/h2/src/main/org/h2/expression/aggregate/AggregateDataStdVar.java b/h2/src/main/org/h2/expression/aggregate/AggregateDataStdVar.java new file mode 100644 index 0000000..2c64503 --- /dev/null +++ b/h2/src/main/org/h2/expression/aggregate/AggregateDataStdVar.java @@ -0,0 +1,90 @@ +/* + * 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.expression.aggregate; + +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.value.Value; +import org.h2.value.ValueDouble; +import org.h2.value.ValueNull; + +/** + * Data stored while calculating a STDDEV_POP, STDDEV_SAMP, VAR_SAMP, VAR_POP, + * REGR_SXX, or REGR_SYY aggregate. + */ +final class AggregateDataStdVar extends AggregateData { + + private final AggregateType aggregateType; + + private long count; + + private double m2, mean; + + /** + * @param aggregateType + * the type of the aggregate operation + */ + AggregateDataStdVar(AggregateType aggregateType) { + this.aggregateType = aggregateType; + } + + @Override + void add(SessionLocal session, Value v) { + if (v == ValueNull.INSTANCE) { + return; + } + // Using Welford's method, see also + // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance + // https://www.johndcook.com/standard_deviation.html + double x = v.getDouble(); + if (++count == 1) { + mean = x; + m2 = 0; + } else { + double delta = x - mean; + mean += delta / count; + m2 += delta * (x - mean); + } + } + + @Override + Value getValue(SessionLocal session) { + double v; + switch (aggregateType) { + case STDDEV_SAMP: + case VAR_SAMP: + if (count < 2) { + return ValueNull.INSTANCE; + } + v = m2 / (count - 1); + if (aggregateType == AggregateType.STDDEV_SAMP) { + v = Math.sqrt(v); + } + break; + case STDDEV_POP: + case VAR_POP: + if (count < 1) { + return ValueNull.INSTANCE; + } + v = m2 / count; + if (aggregateType == AggregateType.STDDEV_POP) { + v = Math.sqrt(v); + } + break; + case REGR_SXX: + case REGR_SYY: + if (count < 1) { + return ValueNull.INSTANCE; + } + v = m2; + break; + default: + throw DbException.getInternalError("type=" + aggregateType); + } + return ValueDouble.get(v); + } + +} diff --git a/h2/src/main/org/h2/expression/aggregate/AggregateType.java b/h2/src/main/org/h2/expression/aggregate/AggregateType.java new file mode 100644 index 0000000..23df562 --- /dev/null +++ b/h2/src/main/org/h2/expression/aggregate/AggregateType.java @@ -0,0 +1,233 @@ +/* + * 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.expression.aggregate; + +/** + * The type of an aggregate function. + */ +public enum AggregateType { + + /** + * The aggregate type for COUNT(*). + */ + COUNT_ALL, + + /** + * The aggregate type for COUNT(expression). + */ + COUNT, + + /** + * The aggregate type for SUM(expression). + */ + SUM, + + /** + * The aggregate type for MIN(expression). + */ + MIN, + + /** + * The aggregate type for MAX(expression). + */ + MAX, + + /** + * The aggregate type for AVG(expression). + */ + AVG, + + /** + * The aggregate type for STDDEV_POP(expression). + */ + STDDEV_POP, + + /** + * The aggregate type for STDDEV_SAMP(expression). + */ + STDDEV_SAMP, + + /** + * The aggregate type for VAR_POP(expression). + */ + VAR_POP, + + /** + * The aggregate type for VAR_SAMP(expression). + */ + VAR_SAMP, + + /** + * The aggregate type for ANY(expression). + */ + ANY, + + /** + * The aggregate type for EVERY(expression). + */ + EVERY, + + /** + * The aggregate type for BIT_AND_AGG(expression). + */ + BIT_AND_AGG, + + /** + * The aggregate type for BIT_OR_AGG(expression). + */ + BIT_OR_AGG, + + /** + * The aggregate type for BIT_XOR_AGG(expression). + */ + BIT_XOR_AGG, + + /** + * The aggregate type for BIT_NAND_AGG(expression). + */ + BIT_NAND_AGG, + + /** + * The aggregate type for BIT_NOR_AGG(expression). + */ + BIT_NOR_AGG, + + /** + * The aggregate type for BIT_XNOR_AGG(expression). + */ + BIT_XNOR_AGG, + + /** + * The aggregate type for HISTOGRAM(expression). + */ + HISTOGRAM, + + /** + * The aggregate type for COVAR_POP binary set function. + */ + COVAR_POP, + + /** + * The aggregate type for COVAR_SAMP binary set function. + */ + COVAR_SAMP, + + /** + * The aggregate type for CORR binary set function. + */ + CORR, + + /** + * The aggregate type for REGR_SLOPE binary set function. + */ + REGR_SLOPE, + + /** + * The aggregate type for REGR_INTERCEPT binary set function. + */ + REGR_INTERCEPT, + + /** + * The aggregate type for REGR_COUNT binary set function. + */ + REGR_COUNT, + + /** + * The aggregate type for REGR_R2 binary set function. + */ + REGR_R2, + + /** + * The aggregate type for REGR_AVGX binary set function. + */ + REGR_AVGX, + + /** + * The aggregate type for REGR_AVGY binary set function. + */ + REGR_AVGY, + + /** + * The aggregate type for REGR_SXX binary set function. + */ + REGR_SXX, + + /** + * The aggregate type for REGR_SYY binary set function. + */ + REGR_SYY, + + /** + * The aggregate type for REGR_SXY binary set function. + */ + REGR_SXY, + + /** + * The type for RANK() hypothetical set function. + */ + RANK, + + /** + * The type for DENSE_RANK() hypothetical set function. + */ + DENSE_RANK, + + /** + * The type for PERCENT_RANK() hypothetical set function. + */ + PERCENT_RANK, + + /** + * The type for CUME_DIST() hypothetical set function. + */ + CUME_DIST, + + /** + * The aggregate type for PERCENTILE_CONT(expression). + */ + PERCENTILE_CONT, + + /** + * The aggregate type for PERCENTILE_DISC(expression). + */ + PERCENTILE_DISC, + + /** + * The aggregate type for MEDIAN(expression). + */ + MEDIAN, + + /** + * The aggregate type for LISTAGG(...). + */ + LISTAGG, + + /** + * The aggregate type for ARRAY_AGG(expression). + */ + ARRAY_AGG, + + /** + * The aggregate type for MODE(expression). + */ + MODE, + + /** + * The aggregate type for ENVELOPE(expression). + */ + ENVELOPE, + + /** + * The aggregate type for JSON_OBJECTAGG(expression: expression). + */ + JSON_OBJECTAGG, + + /** + * The aggregate type for JSON_ARRAYAGG(expression). + */ + JSON_ARRAYAGG, + +} diff --git a/h2/src/main/org/h2/expression/aggregate/JavaAggregate.java b/h2/src/main/org/h2/expression/aggregate/JavaAggregate.java new file mode 100644 index 0000000..d4ce365 --- /dev/null +++ b/h2/src/main/org/h2/expression/aggregate/JavaAggregate.java @@ -0,0 +1,225 @@ +/* + * 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.expression.aggregate; + +import java.sql.SQLException; +import org.h2.api.Aggregate; +import org.h2.command.query.Select; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.aggregate.AggregateDataCollecting.NullCollectionMode; +import org.h2.jdbc.JdbcConnection; +import org.h2.message.DbException; +import org.h2.schema.UserAggregate; +import org.h2.util.ParserUtil; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueNull; +import org.h2.value.ValueRow; +import org.h2.value.ValueToObjectConverter; + +/** + * This class wraps a user-defined aggregate. + */ +public class JavaAggregate extends AbstractAggregate { + + private final UserAggregate userAggregate; + private int[] argTypes; + private int dataType; + private JdbcConnection userConnection; + + public JavaAggregate(UserAggregate userAggregate, Expression[] args, Select select, boolean distinct) { + super(select, args, distinct); + this.userAggregate = userAggregate; + } + + @Override + public int getCost() { + int cost = 5; + for (Expression e : args) { + cost += e.getCost(); + } + if (filterCondition != null) { + cost += filterCondition.getCost(); + } + return cost; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + ParserUtil.quoteIdentifier(builder, userAggregate.getName(), sqlFlags).append('('); + writeExpressions(builder, args, sqlFlags).append(')'); + return appendTailConditions(builder, sqlFlags, false); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + if (!super.isEverything(visitor)) { + return false; + } + switch (visitor.getType()) { + case ExpressionVisitor.DETERMINISTIC: + // TODO optimization: some functions are deterministic, but we don't + // know (no setting for that) + case ExpressionVisitor.OPTIMIZABLE_AGGREGATE: + // user defined aggregate functions can not be optimized + return false; + case ExpressionVisitor.GET_DEPENDENCIES: + visitor.addDependency(userAggregate); + break; + default: + } + for (Expression e : args) { + if (e != null && !e.isEverything(visitor)) { + return false; + } + } + return filterCondition == null || filterCondition.isEverything(visitor); + } + + @Override + public Expression optimize(SessionLocal session) { + super.optimize(session); + userConnection = session.createConnection(false); + int len = args.length; + argTypes = new int[len]; + for (int i = 0; i < len; i++) { + int type = args[i].getType().getValueType(); + argTypes[i] = type; + } + try { + Aggregate aggregate = getInstance(); + dataType = aggregate.getInternalType(argTypes); + type = TypeInfo.getTypeInfo(dataType); + } catch (SQLException e) { + throw DbException.convert(e); + } + return this; + } + + private Aggregate getInstance() { + Aggregate agg = userAggregate.getInstance(); + try { + agg.init(userConnection); + } catch (SQLException ex) { + throw DbException.convert(ex); + } + return agg; + } + + @Override + public Value getAggregatedValue(SessionLocal session, Object aggregateData) { + try { + Aggregate agg; + if (distinct) { + agg = getInstance(); + AggregateDataCollecting data = (AggregateDataCollecting) aggregateData; + if (data != null) { + for (Value value : data.values) { + if (args.length == 1) { + agg.add(ValueToObjectConverter.valueToDefaultObject(value, userConnection, false)); + } else { + Value[] values = ((ValueRow) value).getList(); + Object[] argValues = new Object[args.length]; + for (int i = 0, len = args.length; i < len; i++) { + argValues[i] = ValueToObjectConverter.valueToDefaultObject(values[i], userConnection, + false); + } + agg.add(argValues); + } + } + } + } else { + agg = (Aggregate) aggregateData; + if (agg == null) { + agg = getInstance(); + } + } + Object obj = agg.getResult(); + if (obj == null) { + return ValueNull.INSTANCE; + } + return ValueToObjectConverter.objectToValue(session, obj, dataType); + } catch (SQLException e) { + throw DbException.convert(e); + } + } + + @Override + protected void updateAggregate(SessionLocal session, Object aggregateData) { + updateData(session, aggregateData, null); + } + + private void updateData(SessionLocal session, Object aggregateData, Value[] remembered) { + try { + if (distinct) { + AggregateDataCollecting data = (AggregateDataCollecting) aggregateData; + Value[] argValues = new Value[args.length]; + Value arg = null; + for (int i = 0, len = args.length; i < len; i++) { + arg = remembered == null ? args[i].getValue(session) : remembered[i]; + argValues[i] = arg; + } + data.add(session, args.length == 1 ? arg : ValueRow.get(argValues)); + } else { + Aggregate agg = (Aggregate) aggregateData; + Object[] argValues = new Object[args.length]; + Object arg = null; + for (int i = 0, len = args.length; i < len; i++) { + Value v = remembered == null ? args[i].getValue(session) : remembered[i]; + arg = ValueToObjectConverter.valueToDefaultObject(v, userConnection, false); + argValues[i] = arg; + } + agg.add(args.length == 1 ? arg : argValues); + } + } catch (SQLException e) { + throw DbException.convert(e); + } + } + + @Override + protected void updateGroupAggregates(SessionLocal session, int stage) { + super.updateGroupAggregates(session, stage); + for (Expression expr : args) { + expr.updateAggregate(session, stage); + } + } + + @Override + protected int getNumExpressions() { + int n = args.length; + if (filterCondition != null) { + n++; + } + return n; + } + + @Override + protected void rememberExpressions(SessionLocal session, Value[] array) { + int length = args.length; + for (int i = 0; i < length; i++) { + array[i] = args[i].getValue(session); + } + if (filterCondition != null) { + array[length] = ValueBoolean.get(filterCondition.getBooleanValue(session)); + } + } + + @Override + protected void updateFromExpressions(SessionLocal session, Object aggregateData, Value[] array) { + if (filterCondition == null || array[getNumExpressions() - 1].isTrue()) { + updateData(session, aggregateData, array); + } + } + + @Override + protected Object createAggregateData() { + return distinct ? new AggregateDataCollecting(true, false, NullCollectionMode.IGNORED) : getInstance(); + } + +} diff --git a/h2/src/main/org/h2/expression/aggregate/ListaggArguments.java b/h2/src/main/org/h2/expression/aggregate/ListaggArguments.java new file mode 100644 index 0000000..ee134f7 --- /dev/null +++ b/h2/src/main/org/h2/expression/aggregate/ListaggArguments.java @@ -0,0 +1,126 @@ +/* + * 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.expression.aggregate; + +/** + * Additional arguments of LISTAGG aggregate function. + */ +public final class ListaggArguments { + + private String separator; + + private boolean onOverflowTruncate; + + private String filter; + + private boolean withoutCount; + + /** + * Creates a new instance of additional arguments of LISTAGG aggregate + * function. + */ + public ListaggArguments() { + } + + /** + * Sets the custom LISTAGG separator. + * + * @param separator + * the LISTAGG separator, {@code null} or empty string means no + * separator + */ + public void setSeparator(String separator) { + this.separator = separator != null ? separator : ""; + } + + /** + * Returns the LISTAGG separator. + * + * @return the LISTAGG separator, {@code null} means the default + */ + public String getSeparator() { + return separator; + } + + /** + * Returns the effective LISTAGG separator. + * + * @return the effective LISTAGG separator + */ + public String getEffectiveSeparator() { + return separator != null ? separator : ","; + } + + /** + * Sets the LISTAGG overflow behavior. + * + * @param onOverflowTruncate + * {@code true} for ON OVERFLOW TRUNCATE, {@code false} for ON + * OVERFLOW ERROR + */ + public void setOnOverflowTruncate(boolean onOverflowTruncate) { + this.onOverflowTruncate = onOverflowTruncate; + } + + /** + * Returns the LISTAGG overflow behavior. + * + * @return {@code true} for ON OVERFLOW TRUNCATE, {@code false} for ON + * OVERFLOW ERROR + */ + public boolean getOnOverflowTruncate() { + return onOverflowTruncate; + } + + /** + * Sets the custom LISTAGG truncation filter. + * + * @param filter + * the LISTAGG truncation filter, {@code null} or empty string + * means no truncation filter + */ + public void setFilter(String filter) { + this.filter = filter != null ? filter : ""; + } + + /** + * Returns the LISTAGG truncation filter. + * + * @return the LISTAGG truncation filter, {@code null} means the default + */ + public String getFilter() { + return filter; + } + + /** + * Returns the effective LISTAGG truncation filter. + * + * @return the effective LISTAGG truncation filter + */ + public String getEffectiveFilter() { + return filter != null ? filter : "..."; + } + + /** + * Sets the LISTAGG count indication. + * + * @param withoutCount + * {@code true} for WITHOUT COUNT, {@code false} for WITH COUNT + */ + public void setWithoutCount(boolean withoutCount) { + this.withoutCount = withoutCount; + } + + /** + * Returns the LISTAGG count indication. + * + * @return {@code true} for WITHOUT COUNT, {@code false} for WITH COUNT + */ + public boolean isWithoutCount() { + return withoutCount; + } + +} diff --git a/h2/src/main/org/h2/expression/aggregate/LongDataCounter.java b/h2/src/main/org/h2/expression/aggregate/LongDataCounter.java new file mode 100644 index 0000000..2bd5086 --- /dev/null +++ b/h2/src/main/org/h2/expression/aggregate/LongDataCounter.java @@ -0,0 +1,18 @@ +/* + * 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.expression.aggregate; + +/** + * Counter. + */ +final class LongDataCounter { + + /** + * The value. + */ + long count; + +} diff --git a/h2/src/main/org/h2/expression/aggregate/Percentile.java b/h2/src/main/org/h2/expression/aggregate/Percentile.java new file mode 100644 index 0000000..39bae3c --- /dev/null +++ b/h2/src/main/org/h2/expression/aggregate/Percentile.java @@ -0,0 +1,384 @@ +/* + * 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.expression.aggregate; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Arrays; + +import org.h2.api.IntervalQualifier; +import org.h2.command.query.QueryOrderBy; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.index.Cursor; +import org.h2.index.Index; +import org.h2.mode.DefaultNullOrdering; +import org.h2.result.SearchRow; +import org.h2.result.SortOrder; +import org.h2.table.Column; +import org.h2.table.Table; +import org.h2.table.TableFilter; +import org.h2.util.DateTimeUtils; +import org.h2.util.IntervalUtils; +import org.h2.value.CompareMode; +import org.h2.value.Value; +import org.h2.value.ValueDate; +import org.h2.value.ValueInterval; +import org.h2.value.ValueNull; +import org.h2.value.ValueNumeric; +import org.h2.value.ValueTime; +import org.h2.value.ValueTimeTimeZone; +import org.h2.value.ValueTimestamp; +import org.h2.value.ValueTimestampTimeZone; + +/** + * PERCENTILE_CONT, PERCENTILE_DISC, and MEDIAN inverse distribution functions. + */ +final class Percentile { + + /** + * BigDecimal value of 0.5. + */ + static final BigDecimal HALF = BigDecimal.valueOf(0.5d); + + private static boolean isNullsLast(DefaultNullOrdering defaultNullOrdering, Index index) { + return defaultNullOrdering.compareNull(true, index.getIndexColumns()[0].sortType) > 0; + } + + /** + * Get the index (if any) for the column specified in the inverse + * distribution function. + * + * @param database the database + * @param on the expression (usually a column expression) + * @return the index, or null + */ + static Index getColumnIndex(Database database, Expression on) { + DefaultNullOrdering defaultNullOrdering = database.getDefaultNullOrdering(); + if (on instanceof ExpressionColumn) { + ExpressionColumn col = (ExpressionColumn) on; + Column column = col.getColumn(); + TableFilter filter = col.getTableFilter(); + if (filter != null) { + Table table = filter.getTable(); + ArrayList indexes = table.getIndexes(); + Index result = null; + if (indexes != null) { + boolean nullable = column.isNullable(); + for (int i = 1, size = indexes.size(); i < size; i++) { + Index index = indexes.get(i); + if (!index.canFindNext()) { + continue; + } + if (!index.isFirstColumn(column)) { + continue; + } + // Prefer index without nulls last for nullable columns + if (result == null || result.getColumns().length > index.getColumns().length + || nullable && isNullsLast(defaultNullOrdering, result) + && !isNullsLast(defaultNullOrdering, index)) { + result = index; + } + } + } + return result; + } + } + return null; + } + + /** + * Get the result from the array of values. + * + * @param session the session + * @param array array with values + * @param dataType the data type + * @param orderByList ORDER BY list + * @param percentile argument of percentile function, or 0.5d for median + * @param interpolate whether value should be interpolated + * @return the result + */ + static Value getValue(SessionLocal session, Value[] array, int dataType, ArrayList orderByList, + BigDecimal percentile, boolean interpolate) { + final CompareMode compareMode = session.getDatabase().getCompareMode(); + Arrays.sort(array, compareMode); + int count = array.length; + boolean reverseIndex = orderByList != null && (orderByList.get(0).sortType & SortOrder.DESCENDING) != 0; + BigDecimal fpRow = BigDecimal.valueOf(count - 1).multiply(percentile); + int rowIdx1 = fpRow.intValue(); + BigDecimal factor = fpRow.subtract(BigDecimal.valueOf(rowIdx1)); + int rowIdx2; + if (factor.signum() == 0) { + interpolate = false; + rowIdx2 = rowIdx1; + } else { + rowIdx2 = rowIdx1 + 1; + if (!interpolate) { + if (factor.compareTo(HALF) > 0) { + rowIdx1 = rowIdx2; + } else { + rowIdx2 = rowIdx1; + } + } + } + if (reverseIndex) { + rowIdx1 = count - 1 - rowIdx1; + rowIdx2 = count - 1 - rowIdx2; + } + Value v = array[rowIdx1]; + if (!interpolate) { + return v; + } + return interpolate(v, array[rowIdx2], factor, dataType, session, compareMode); + } + + /** + * Get the result from the index. + * + * @param session the session + * @param expression the expression + * @param dataType the data type + * @param orderByList ORDER BY list + * @param percentile argument of percentile function, or 0.5d for median + * @param interpolate whether value should be interpolated + * @return the result + */ + static Value getFromIndex(SessionLocal session, Expression expression, int dataType, + ArrayList orderByList, BigDecimal percentile, boolean interpolate) { + Database db = session.getDatabase(); + Index index = getColumnIndex(db, expression); + long count = index.getRowCount(session); + if (count == 0) { + return ValueNull.INSTANCE; + } + Cursor cursor = index.find(session, null, null); + cursor.next(); + int columnId = index.getColumns()[0].getColumnId(); + ExpressionColumn expr = (ExpressionColumn) expression; + if (expr.getColumn().isNullable()) { + boolean hasNulls = false; + SearchRow row; + // Try to skip nulls from the start first with the same cursor that + // will be used to read values. + while (count > 0) { + row = cursor.getSearchRow(); + if (row == null) { + return ValueNull.INSTANCE; + } + if (row.getValue(columnId) == ValueNull.INSTANCE) { + count--; + cursor.next(); + hasNulls = true; + } else { + break; + } + } + if (count == 0) { + return ValueNull.INSTANCE; + } + // If no nulls found and if index orders nulls last create a second + // cursor to count nulls at the end. + if (!hasNulls && isNullsLast(db.getDefaultNullOrdering(), index)) { + TableFilter tableFilter = expr.getTableFilter(); + SearchRow check = tableFilter.getTable().getTemplateSimpleRow(true); + check.setValue(columnId, ValueNull.INSTANCE); + Cursor nullsCursor = index.find(session, check, check); + while (nullsCursor.next()) { + count--; + } + if (count <= 0) { + return ValueNull.INSTANCE; + } + } + } + boolean reverseIndex = (orderByList != null ? orderByList.get(0).sortType & SortOrder.DESCENDING : 0) + != (index.getIndexColumns()[0].sortType & SortOrder.DESCENDING); + BigDecimal fpRow = BigDecimal.valueOf(count - 1).multiply(percentile); + long rowIdx1 = fpRow.longValue(); + BigDecimal factor = fpRow.subtract(BigDecimal.valueOf(rowIdx1)); + long rowIdx2; + if (factor.signum() == 0) { + interpolate = false; + rowIdx2 = rowIdx1; + } else { + rowIdx2 = rowIdx1 + 1; + if (!interpolate) { + if (factor.compareTo(HALF) > 0) { + rowIdx1 = rowIdx2; + } else { + rowIdx2 = rowIdx1; + } + } + } + long skip = reverseIndex ? count - 1 - rowIdx2 : rowIdx1; + for (int i = 0; i < skip; i++) { + cursor.next(); + } + SearchRow row = cursor.getSearchRow(); + if (row == null) { + return ValueNull.INSTANCE; + } + Value v = row.getValue(columnId); + if (v == ValueNull.INSTANCE) { + return v; + } + if (interpolate) { + cursor.next(); + row = cursor.getSearchRow(); + if (row == null) { + return v; + } + Value v2 = row.getValue(columnId); + if (v2 == ValueNull.INSTANCE) { + return v; + } + if (reverseIndex) { + Value t = v; + v = v2; + v2 = t; + } + return interpolate(v, v2, factor, dataType, session, db.getCompareMode()); + } + return v; + } + + private static Value interpolate(Value v0, Value v1, BigDecimal factor, int dataType, SessionLocal session, + CompareMode compareMode) { + if (v0.compareTo(v1, session, compareMode) == 0) { + return v0; + } + switch (dataType) { + case Value.TINYINT: + case Value.SMALLINT: + case Value.INTEGER: + return ValueNumeric.get( + interpolateDecimal(BigDecimal.valueOf(v0.getInt()), BigDecimal.valueOf(v1.getInt()), factor)); + case Value.BIGINT: + return ValueNumeric.get( + interpolateDecimal(BigDecimal.valueOf(v0.getLong()), BigDecimal.valueOf(v1.getLong()), factor)); + case Value.NUMERIC: + case Value.DECFLOAT: + return ValueNumeric.get(interpolateDecimal(v0.getBigDecimal(), v1.getBigDecimal(), factor)); + case Value.REAL: + case Value.DOUBLE: + return ValueNumeric.get( + interpolateDecimal( + BigDecimal.valueOf(v0.getDouble()), BigDecimal.valueOf(v1.getDouble()), factor)); + case Value.TIME: { + ValueTime t0 = (ValueTime) v0, t1 = (ValueTime) v1; + BigDecimal n0 = BigDecimal.valueOf(t0.getNanos()); + BigDecimal n1 = BigDecimal.valueOf(t1.getNanos()); + return ValueTime.fromNanos(interpolateDecimal(n0, n1, factor).longValue()); + } + case Value.TIME_TZ: { + ValueTimeTimeZone t0 = (ValueTimeTimeZone) v0, t1 = (ValueTimeTimeZone) v1; + BigDecimal n0 = BigDecimal.valueOf(t0.getNanos()); + BigDecimal n1 = BigDecimal.valueOf(t1.getNanos()); + BigDecimal offset = BigDecimal.valueOf(t0.getTimeZoneOffsetSeconds()) + .multiply(BigDecimal.ONE.subtract(factor)) + .add(BigDecimal.valueOf(t1.getTimeZoneOffsetSeconds()).multiply(factor)); + int intOffset = offset.intValue(); + BigDecimal intOffsetBD = BigDecimal.valueOf(intOffset); + BigDecimal bd = interpolateDecimal(n0, n1, factor); + if (offset.compareTo(intOffsetBD) != 0) { + bd = bd.add( + offset.subtract(intOffsetBD).multiply(BigDecimal.valueOf(DateTimeUtils.NANOS_PER_SECOND))); + } + long timeNanos = bd.longValue(); + if (timeNanos < 0L) { + timeNanos += DateTimeUtils.NANOS_PER_SECOND; + intOffset++; + } else if (timeNanos >= DateTimeUtils.NANOS_PER_DAY) { + timeNanos -= DateTimeUtils.NANOS_PER_SECOND; + intOffset--; + } + return ValueTimeTimeZone.fromNanos(timeNanos, intOffset); + } + case Value.DATE: { + ValueDate d0 = (ValueDate) v0, d1 = (ValueDate) v1; + BigDecimal a0 = BigDecimal.valueOf(DateTimeUtils.absoluteDayFromDateValue(d0.getDateValue())); + BigDecimal a1 = BigDecimal.valueOf(DateTimeUtils.absoluteDayFromDateValue(d1.getDateValue())); + return ValueDate.fromDateValue( + DateTimeUtils.dateValueFromAbsoluteDay(interpolateDecimal(a0, a1, factor).longValue())); + } + case Value.TIMESTAMP: { + ValueTimestamp ts0 = (ValueTimestamp) v0, ts1 = (ValueTimestamp) v1; + BigDecimal a0 = timestampToDecimal(ts0.getDateValue(), ts0.getTimeNanos()); + BigDecimal a1 = timestampToDecimal(ts1.getDateValue(), ts1.getTimeNanos()); + BigInteger[] dr = interpolateDecimal(a0, a1, factor).toBigInteger() + .divideAndRemainder(IntervalUtils.NANOS_PER_DAY_BI); + long absoluteDay = dr[0].longValue(); + long timeNanos = dr[1].longValue(); + if (timeNanos < 0) { + timeNanos += DateTimeUtils.NANOS_PER_DAY; + absoluteDay--; + } + return ValueTimestamp.fromDateValueAndNanos( + DateTimeUtils.dateValueFromAbsoluteDay(absoluteDay), timeNanos); + } + case Value.TIMESTAMP_TZ: { + ValueTimestampTimeZone ts0 = (ValueTimestampTimeZone) v0, ts1 = (ValueTimestampTimeZone) v1; + BigDecimal a0 = timestampToDecimal(ts0.getDateValue(), ts0.getTimeNanos()); + BigDecimal a1 = timestampToDecimal(ts1.getDateValue(), ts1.getTimeNanos()); + BigDecimal offset = BigDecimal.valueOf(ts0.getTimeZoneOffsetSeconds()) + .multiply(BigDecimal.ONE.subtract(factor)) + .add(BigDecimal.valueOf(ts1.getTimeZoneOffsetSeconds()).multiply(factor)); + int intOffset = offset.intValue(); + BigDecimal intOffsetBD = BigDecimal.valueOf(intOffset); + BigDecimal bd = interpolateDecimal(a0, a1, factor); + if (offset.compareTo(intOffsetBD) != 0) { + bd = bd.add( + offset.subtract(intOffsetBD).multiply(BigDecimal.valueOf(DateTimeUtils.NANOS_PER_SECOND))); + } + BigInteger[] dr = bd.toBigInteger().divideAndRemainder(IntervalUtils.NANOS_PER_DAY_BI); + long absoluteDay = dr[0].longValue(); + long timeNanos = dr[1].longValue(); + if (timeNanos < 0) { + timeNanos += DateTimeUtils.NANOS_PER_DAY; + absoluteDay--; + } + return ValueTimestampTimeZone.fromDateValueAndNanos(DateTimeUtils.dateValueFromAbsoluteDay(absoluteDay), + timeNanos, intOffset); + } + case Value.INTERVAL_YEAR: + case Value.INTERVAL_MONTH: + case Value.INTERVAL_DAY: + case Value.INTERVAL_HOUR: + case Value.INTERVAL_MINUTE: + case Value.INTERVAL_SECOND: + case Value.INTERVAL_YEAR_TO_MONTH: + case Value.INTERVAL_DAY_TO_HOUR: + case Value.INTERVAL_DAY_TO_MINUTE: + case Value.INTERVAL_DAY_TO_SECOND: + case Value.INTERVAL_HOUR_TO_MINUTE: + case Value.INTERVAL_HOUR_TO_SECOND: + case Value.INTERVAL_MINUTE_TO_SECOND: + return IntervalUtils.intervalFromAbsolute(IntervalQualifier.valueOf(dataType - Value.INTERVAL_YEAR), + interpolateDecimal(new BigDecimal(IntervalUtils.intervalToAbsolute((ValueInterval) v0)), + new BigDecimal(IntervalUtils.intervalToAbsolute((ValueInterval) v1)), factor) + .toBigInteger()); + default: + // Use the same rules as PERCENTILE_DISC + return (factor.compareTo(HALF) > 0 ? v1 : v0); + } + } + + private static BigDecimal timestampToDecimal(long dateValue, long timeNanos) { + return new BigDecimal(BigInteger.valueOf(DateTimeUtils.absoluteDayFromDateValue(dateValue)) + .multiply(IntervalUtils.NANOS_PER_DAY_BI).add(BigInteger.valueOf(timeNanos))); + } + + private static BigDecimal interpolateDecimal(BigDecimal d0, BigDecimal d1, BigDecimal factor) { + return d0.multiply(BigDecimal.ONE.subtract(factor)).add(d1.multiply(factor)); + } + + private Percentile() { + } + +} diff --git a/h2/src/main/org/h2/expression/aggregate/package.html b/h2/src/main/org/h2/expression/aggregate/package.html new file mode 100644 index 0000000..e20a45a --- /dev/null +++ b/h2/src/main/org/h2/expression/aggregate/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Aggregate functions. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/expression/analysis/DataAnalysisOperation.java b/h2/src/main/org/h2/expression/analysis/DataAnalysisOperation.java new file mode 100644 index 0000000..8cb6ebd --- /dev/null +++ b/h2/src/main/org/h2/expression/analysis/DataAnalysisOperation.java @@ -0,0 +1,536 @@ +/* + * 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.expression.analysis; + +import java.util.ArrayList; +import java.util.HashMap; + +import org.h2.api.ErrorCode; +import org.h2.command.query.QueryOrderBy; +import org.h2.command.query.Select; +import org.h2.command.query.SelectGroups; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.message.DbException; +import org.h2.result.SortOrder; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.value.Value; +import org.h2.value.ValueInteger; + +/** + * A base class for data analysis operations such as aggregates and window + * functions. + */ +public abstract class DataAnalysisOperation extends Expression { + + /** + * Reset stage. Used to reset internal data to its initial state. + */ + public static final int STAGE_RESET = 0; + + /** + * Group stage, used for explicit or implicit GROUP BY operation. + */ + public static final int STAGE_GROUP = 1; + + /** + * Window processing stage. + */ + public static final int STAGE_WINDOW = 2; + + /** + * SELECT + */ + protected final Select select; + + /** + * OVER clause + */ + protected Window over; + + /** + * Sort order for OVER + */ + protected SortOrder overOrderBySort; + + private int numFrameExpressions; + + private int lastGroupRowId; + + /** + * Create sort order. + * + * @param session + * database session + * @param orderBy + * array of order by expressions + * @param offset + * index offset + * @return the SortOrder + */ + protected static SortOrder createOrder(SessionLocal session, ArrayList orderBy, int offset) { + int size = orderBy.size(); + int[] index = new int[size]; + int[] sortType = new int[size]; + for (int i = 0; i < size; i++) { + QueryOrderBy o = orderBy.get(i); + index[i] = i + offset; + sortType[i] = o.sortType; + } + return new SortOrder(session, index, sortType, null); + } + + protected DataAnalysisOperation(Select select) { + this.select = select; + } + + /** + * Sets the OVER condition. + * + * @param over + * OVER condition + */ + public void setOverCondition(Window over) { + this.over = over; + } + + /** + * Checks whether this expression is an aggregate function. + * + * @return true if this is an aggregate function (including aggregates with + * OVER clause), false if this is a window function + */ + public abstract boolean isAggregate(); + + /** + * Returns the sort order for OVER clause. + * + * @return the sort order for OVER clause + */ + protected SortOrder getOverOrderBySort() { + return overOrderBySort; + } + + @Override + public final void mapColumns(ColumnResolver resolver, int level, int state) { + if (over != null) { + if (state != MAP_INITIAL) { + throw DbException.get(ErrorCode.INVALID_USE_OF_AGGREGATE_FUNCTION_1, getTraceSQL()); + } + state = MAP_IN_WINDOW; + } else { + if (state == MAP_IN_AGGREGATE) { + throw DbException.get(ErrorCode.INVALID_USE_OF_AGGREGATE_FUNCTION_1, getTraceSQL()); + } + state = MAP_IN_AGGREGATE; + } + mapColumnsAnalysis(resolver, level, state); + } + + /** + * Map the columns of the resolver to expression columns. + * + * @param resolver + * the column resolver + * @param level + * the subquery nesting level + * @param innerState + * one of the Expression MAP_IN_* values + */ + protected void mapColumnsAnalysis(ColumnResolver resolver, int level, int innerState) { + if (over != null) { + over.mapColumns(resolver, level); + } + } + + @Override + public Expression optimize(SessionLocal session) { + if (over != null) { + over.optimize(session); + ArrayList orderBy = over.getOrderBy(); + if (orderBy != null) { + overOrderBySort = createOrder(session, orderBy, getNumExpressions()); + } else if (!isAggregate()) { + overOrderBySort = new SortOrder(session, new int[getNumExpressions()]); + } + WindowFrame frame = over.getWindowFrame(); + if (frame != null) { + int index = getNumExpressions(); + int orderBySize = 0; + if (orderBy != null) { + orderBySize = orderBy.size(); + index += orderBySize; + } + int n = 0; + WindowFrameBound bound = frame.getStarting(); + if (bound.isParameterized()) { + checkOrderBy(frame.getUnits(), orderBySize); + if (bound.isVariable()) { + bound.setExpressionIndex(index); + n++; + } + } + bound = frame.getFollowing(); + if (bound != null && bound.isParameterized()) { + checkOrderBy(frame.getUnits(), orderBySize); + if (bound.isVariable()) { + bound.setExpressionIndex(index + n); + n++; + } + } + numFrameExpressions = n; + } + } + return this; + } + + private void checkOrderBy(WindowFrameUnits units, int orderBySize) { + switch (units) { + case RANGE: + if (orderBySize != 1) { + String sql = getTraceSQL(); + throw DbException.getSyntaxError(sql, sql.length() - 1, + "exactly one sort key is required for RANGE units"); + } + break; + case GROUPS: + if (orderBySize < 1) { + String sql = getTraceSQL(); + throw DbException.getSyntaxError(sql, sql.length() - 1, + "a sort key is required for GROUPS units"); + } + break; + default: + } + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + if (over != null) { + over.setEvaluatable(tableFilter, b); + } + } + + @Override + public final void updateAggregate(SessionLocal session, int stage) { + if (stage == STAGE_RESET) { + updateGroupAggregates(session, STAGE_RESET); + lastGroupRowId = 0; + return; + } + boolean window = stage == STAGE_WINDOW; + if (window != (over != null)) { + if (!window && select.isWindowQuery()) { + updateGroupAggregates(session, stage); + } + return; + } + SelectGroups groupData = select.getGroupDataIfCurrent(window); + if (groupData == null) { + // this is a different level (the enclosing query) + return; + } + + int groupRowId = groupData.getCurrentGroupRowId(); + if (lastGroupRowId == groupRowId) { + // already visited + return; + } + lastGroupRowId = groupRowId; + + if (over != null) { + if (!select.isGroupQuery()) { + over.updateAggregate(session, stage); + } + } + updateAggregate(session, groupData, groupRowId); + } + + /** + * Update a row of an aggregate. + * + * @param session + * the database session + * @param groupData + * data for the aggregate group + * @param groupRowId + * row id of group + */ + protected abstract void updateAggregate(SessionLocal session, SelectGroups groupData, int groupRowId); + + /** + * Invoked when processing group stage of grouped window queries to update + * arguments of this aggregate. + * + * @param session + * the session + * @param stage + * select stage + */ + protected void updateGroupAggregates(SessionLocal session, int stage) { + if (over != null) { + over.updateAggregate(session, stage); + } + } + + /** + * Returns the number of expressions, excluding OVER clause. + * + * @return the number of expressions + */ + protected abstract int getNumExpressions(); + + /** + * Returns the number of window frame expressions. + * + * @return the number of window frame expressions + */ + private int getNumFrameExpressions() { + return numFrameExpressions; + } + + /** + * Stores current values of expressions into the specified array. + * + * @param session + * the session + * @param array + * array to store values of expressions + */ + protected abstract void rememberExpressions(SessionLocal session, Value[] array); + + /** + * Get the aggregate data for a window clause. + * + * @param session + * database session + * @param groupData + * aggregate group data + * @param forOrderBy + * true if this is for ORDER BY + * @return the aggregate data object, specific to each kind of aggregate. + */ + protected Object getWindowData(SessionLocal session, SelectGroups groupData, boolean forOrderBy) { + Object data; + Value key = over.getCurrentKey(session); + PartitionData partition = groupData.getWindowExprData(this, key); + if (partition == null) { + data = forOrderBy ? new ArrayList<>() : createAggregateData(); + groupData.setWindowExprData(this, key, new PartitionData(data)); + } else { + data = partition.getData(); + } + return data; + } + + /** + * Get the aggregate group data object from the collector object. + * + * @param groupData + * the collector object + * @param ifExists + * if true, return null if object not found, if false, return new + * object if nothing found + * @return group data object + */ + protected Object getGroupData(SelectGroups groupData, boolean ifExists) { + Object data; + data = groupData.getCurrentGroupExprData(this); + if (data == null) { + if (ifExists) { + return null; + } + data = createAggregateData(); + groupData.setCurrentGroupExprData(this, data); + } + return data; + } + + /** + * Create aggregate data object specific to the subclass. + * + * @return aggregate-specific data object. + */ + protected abstract Object createAggregateData(); + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + if (over == null) { + return true; + } + switch (visitor.getType()) { + case ExpressionVisitor.QUERY_COMPARABLE: + case ExpressionVisitor.OPTIMIZABLE_AGGREGATE: + case ExpressionVisitor.DETERMINISTIC: + case ExpressionVisitor.INDEPENDENT: + case ExpressionVisitor.DECREMENT_QUERY_LEVEL: + return false; + default: + return true; + } + } + + @Override + public Value getValue(SessionLocal session) { + SelectGroups groupData = select.getGroupDataIfCurrent(over != null); + if (groupData == null) { + throw DbException.get(ErrorCode.INVALID_USE_OF_AGGREGATE_FUNCTION_1, getTraceSQL()); + } + return over == null ? getAggregatedValue(session, getGroupData(groupData, true)) + : getWindowResult(session, groupData); + } + + /** + * Returns result of this window function or window aggregate. This method + * is not used for plain aggregates. + * + * @param session + * the session + * @param groupData + * the group data + * @return result of this function + */ + private Value getWindowResult(SessionLocal session, SelectGroups groupData) { + PartitionData partition; + Object data; + boolean isOrdered = over.isOrdered(); + Value key = over.getCurrentKey(session); + partition = groupData.getWindowExprData(this, key); + if (partition == null) { + // Window aggregates with FILTER clause may have no collected values + data = isOrdered ? new ArrayList<>() : createAggregateData(); + partition = new PartitionData(data); + groupData.setWindowExprData(this, key, partition); + } else { + data = partition.getData(); + } + if (isOrdered || !isAggregate()) { + Value result = getOrderedResult(session, groupData, partition, data); + if (result == null) { + return getAggregatedValue(session, null); + } + return result; + } + // Window aggregate without ORDER BY clause in window specification + Value result = partition.getResult(); + if (result == null) { + result = getAggregatedValue(session, data); + partition.setResult(result); + } + return result; + } + + /*** + * Returns aggregated value. + * + * @param session + * the session + * @param aggregateData + * the aggregate data + * @return aggregated value. + */ + protected abstract Value getAggregatedValue(SessionLocal session, Object aggregateData); + + /** + * Update a row of an ordered aggregate. + * + * @param session + * the database session + * @param groupData + * data for the aggregate group + * @param groupRowId + * row id of group + * @param orderBy + * list of order by expressions + */ + protected void updateOrderedAggregate(SessionLocal session, SelectGroups groupData, int groupRowId, + ArrayList orderBy) { + int ne = getNumExpressions(); + int size = orderBy != null ? orderBy.size() : 0; + int frameSize = getNumFrameExpressions(); + Value[] array = new Value[ne + size + frameSize + 1]; + rememberExpressions(session, array); + for (int i = 0; i < size; i++) { + @SuppressWarnings("null") + QueryOrderBy o = orderBy.get(i); + array[ne++] = o.expression.getValue(session); + } + if (frameSize > 0) { + WindowFrame frame = over.getWindowFrame(); + WindowFrameBound bound = frame.getStarting(); + if (bound.isVariable()) { + array[ne++] = bound.getValue().getValue(session); + } + bound = frame.getFollowing(); + if (bound != null && bound.isVariable()) { + array[ne++] = bound.getValue().getValue(session); + } + } + array[ne] = ValueInteger.get(groupRowId); + @SuppressWarnings("unchecked") + ArrayList data = (ArrayList) getWindowData(session, groupData, true); + data.add(array); + } + + private Value getOrderedResult(SessionLocal session, SelectGroups groupData, PartitionData partition, // + Object data) { + HashMap result = partition.getOrderedResult(); + if (result == null) { + result = new HashMap<>(); + @SuppressWarnings("unchecked") + ArrayList orderedData = (ArrayList) data; + int rowIdColumn = getNumExpressions(); + ArrayList orderBy = over.getOrderBy(); + if (orderBy != null) { + rowIdColumn += orderBy.size(); + orderedData.sort(overOrderBySort); + } + rowIdColumn += getNumFrameExpressions(); + getOrderedResultLoop(session, result, orderedData, rowIdColumn); + partition.setOrderedResult(result); + } + return result.get(groupData.getCurrentGroupRowId()); + } + + /** + * Returns result of this window function or window aggregate. This method + * may not be called on window aggregate without window order clause. + * + * @param session + * the session + * @param result + * the map to append result to + * @param ordered + * ordered data + * @param rowIdColumn + * the index of row id value + */ + protected abstract void getOrderedResultLoop(SessionLocal session, HashMap result, + ArrayList ordered, int rowIdColumn); + + /** + * Used to create SQL for the OVER and FILTER clauses. + * + * @param builder + * string builder + * @param sqlFlags + * formatting flags + * @param forceOrderBy + * whether synthetic ORDER BY clause should be generated when it + * is missing + * @return the builder object + */ + protected StringBuilder appendTailConditions(StringBuilder builder, int sqlFlags, boolean forceOrderBy) { + if (over != null) { + builder.append(' '); + over.getSQL(builder, sqlFlags, forceOrderBy); + } + return builder; + } + +} diff --git a/h2/src/main/org/h2/expression/analysis/PartitionData.java b/h2/src/main/org/h2/expression/analysis/PartitionData.java new file mode 100644 index 0000000..afa7494 --- /dev/null +++ b/h2/src/main/org/h2/expression/analysis/PartitionData.java @@ -0,0 +1,91 @@ +/* + * 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.expression.analysis; + +import java.util.HashMap; + +import org.h2.value.Value; + +/** + * Partition data of a window aggregate. + */ +public final class PartitionData { + + /** + * Aggregate data. + */ + private Object data; + + /** + * Evaluated result. + */ + private Value result; + + /** + * Evaluated ordered result. + */ + private HashMap orderedResult; + + /** + * Creates new instance of partition data. + * + * @param data + * aggregate data + */ + PartitionData(Object data) { + this.data = data; + } + + /** + * Returns the aggregate data. + * + * @return the aggregate data + */ + Object getData() { + return data; + } + + /** + * Returns the result. + * + * @return the result + */ + Value getResult() { + return result; + } + + /** + * Sets the result. + * + * @param result + * the result to set + */ + void setResult(Value result) { + this.result = result; + data = null; + } + + /** + * Returns the ordered result. + * + * @return the ordered result + */ + HashMap getOrderedResult() { + return orderedResult; + } + + /** + * Sets the ordered result. + * + * @param orderedResult + * the ordered result to set + */ + void setOrderedResult(HashMap orderedResult) { + this.orderedResult = orderedResult; + data = null; + } + +} diff --git a/h2/src/main/org/h2/expression/analysis/Window.java b/h2/src/main/org/h2/expression/analysis/Window.java new file mode 100644 index 0000000..7a26d1f --- /dev/null +++ b/h2/src/main/org/h2/expression/analysis/Window.java @@ -0,0 +1,338 @@ +/* + * 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.expression.analysis; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.ListIterator; + +import org.h2.api.ErrorCode; +import org.h2.command.query.QueryOrderBy; +import org.h2.command.query.Select; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.message.DbException; +import org.h2.result.SortOrder; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.util.HasSQL; +import org.h2.value.Value; +import org.h2.value.ValueRow; + +/** + * Window clause. + */ +public final class Window { + + private ArrayList partitionBy; + + private ArrayList orderBy; + + private WindowFrame frame; + + private String parent; + + /** + * Appends ORDER BY clause to the specified builder. + * + * @param builder + * string builder + * @param orderBy + * ORDER BY clause, or null + * @param sqlFlags + * formatting flags + * @param forceOrderBy + * whether synthetic ORDER BY clause should be generated when it + * is missing + */ + public static void appendOrderBy(StringBuilder builder, ArrayList orderBy, int sqlFlags, + boolean forceOrderBy) { + if (orderBy != null && !orderBy.isEmpty()) { + appendOrderByStart(builder); + for (int i = 0; i < orderBy.size(); i++) { + QueryOrderBy o = orderBy.get(i); + if (i > 0) { + builder.append(", "); + } + o.expression.getUnenclosedSQL(builder, sqlFlags); + SortOrder.typeToString(builder, o.sortType); + } + } else if (forceOrderBy) { + appendOrderByStart(builder); + builder.append("NULL"); + } + } + + private static void appendOrderByStart(StringBuilder builder) { + if (builder.charAt(builder.length() - 1) != '(') { + builder.append(' '); + } + builder.append("ORDER BY "); + } + + /** + * Creates a new instance of window clause. + * + * @param parent + * name of the parent window + * @param partitionBy + * PARTITION BY clause, or null + * @param orderBy + * ORDER BY clause, or null + * @param frame + * window frame clause, or null + */ + public Window(String parent, ArrayList partitionBy, ArrayList orderBy, + WindowFrame frame) { + this.parent = parent; + this.partitionBy = partitionBy; + this.orderBy = orderBy; + this.frame = frame; + } + + /** + * Map the columns of the resolver to expression columns. + * + * @param resolver + * the column resolver + * @param level + * the subquery nesting level + * @see Expression#mapColumns(ColumnResolver, int, int) + */ + public void mapColumns(ColumnResolver resolver, int level) { + resolveWindows(resolver); + if (partitionBy != null) { + for (Expression e : partitionBy) { + e.mapColumns(resolver, level, Expression.MAP_IN_WINDOW); + } + } + if (orderBy != null) { + for (QueryOrderBy o : orderBy) { + o.expression.mapColumns(resolver, level, Expression.MAP_IN_WINDOW); + } + } + if (frame != null) { + frame.mapColumns(resolver, level, Expression.MAP_IN_WINDOW); + } + } + + private void resolveWindows(ColumnResolver resolver) { + if (parent != null) { + Select select = resolver.getSelect(); + Window p; + while ((p = select.getWindow(parent)) == null) { + select = select.getParentSelect(); + if (select == null) { + throw DbException.get(ErrorCode.WINDOW_NOT_FOUND_1, parent); + } + } + p.resolveWindows(resolver); + if (partitionBy == null) { + partitionBy = p.partitionBy; + } + if (orderBy == null) { + orderBy = p.orderBy; + } + if (frame == null) { + frame = p.frame; + } + parent = null; + } + } + + /** + * Try to optimize the window conditions. + * + * @param session + * the session + */ + public void optimize(SessionLocal session) { + if (partitionBy != null) { + for (ListIterator i = partitionBy.listIterator(); i.hasNext();) { + Expression e = i.next().optimize(session); + if (e.isConstant()) { + i.remove(); + } else { + i.set(e); + } + } + if (partitionBy.isEmpty()) { + partitionBy = null; + } + } + if (orderBy != null) { + for (Iterator i = orderBy.iterator(); i.hasNext();) { + QueryOrderBy o = i.next(); + Expression e = o.expression.optimize(session); + if (e.isConstant()) { + i.remove(); + } else { + o.expression = e; + } + } + if (orderBy.isEmpty()) { + orderBy = null; + } + } + if (frame != null) { + frame.optimize(session); + } + } + + /** + * Tell the expression columns whether the table filter can return values + * now. This is used when optimizing the query. + * + * @param tableFilter + * the table filter + * @param value + * true if the table filter can return value + * @see Expression#setEvaluatable(TableFilter, boolean) + */ + public void setEvaluatable(TableFilter tableFilter, boolean value) { + if (partitionBy != null) { + for (Expression e : partitionBy) { + e.setEvaluatable(tableFilter, value); + } + } + if (orderBy != null) { + for (QueryOrderBy o : orderBy) { + o.expression.setEvaluatable(tableFilter, value); + } + } + } + + /** + * Returns ORDER BY clause. + * + * @return ORDER BY clause, or null + */ + public ArrayList getOrderBy() { + return orderBy; + } + + /** + * Returns window frame, or null. + * + * @return window frame, or null + */ + public WindowFrame getWindowFrame() { + return frame; + } + + /** + * Returns {@code true} if window ordering clause is specified or ROWS unit + * is used. + * + * @return {@code true} if window ordering clause is specified or ROWS unit + * is used + */ + public boolean isOrdered() { + if (orderBy != null) { + return true; + } + if (frame != null && frame.getUnits() == WindowFrameUnits.ROWS) { + if (frame.getStarting().getType() == WindowFrameBoundType.UNBOUNDED_PRECEDING) { + WindowFrameBound following = frame.getFollowing(); + if (following != null && following.getType() == WindowFrameBoundType.UNBOUNDED_FOLLOWING) { + return false; + } + } + return true; + } + return false; + } + + /** + * Returns the key for the current group. + * + * @param session + * session + * @return key for the current group, or null + */ + public Value getCurrentKey(SessionLocal session) { + if (partitionBy == null) { + return null; + } + int len = partitionBy.size(); + if (len == 1) { + return partitionBy.get(0).getValue(session); + } else { + Value[] keyValues = new Value[len]; + // update group + for (int i = 0; i < len; i++) { + Expression expr = partitionBy.get(i); + keyValues[i] = expr.getValue(session); + } + return ValueRow.get(keyValues); + } + } + + /** + * Appends SQL representation to the specified builder. + * + * @param builder + * string builder + * @param sqlFlags + * formatting flags + * @param forceOrderBy + * whether synthetic ORDER BY clause should be generated when it + * is missing + * @return the specified string builder + * @see Expression#getSQL(StringBuilder, int, int) + */ + public StringBuilder getSQL(StringBuilder builder, int sqlFlags, boolean forceOrderBy) { + builder.append("OVER ("); + if (partitionBy != null) { + builder.append("PARTITION BY "); + for (int i = 0; i < partitionBy.size(); i++) { + if (i > 0) { + builder.append(", "); + } + partitionBy.get(i).getUnenclosedSQL(builder, sqlFlags); + } + } + appendOrderBy(builder, orderBy, sqlFlags, forceOrderBy); + if (frame != null) { + if (builder.charAt(builder.length() - 1) != '(') { + builder.append(' '); + } + frame.getSQL(builder, sqlFlags); + } + return builder.append(')'); + } + + /** + * Update an aggregate value. + * + * @param session + * the session + * @param stage + * select stage + * @see Expression#updateAggregate(SessionLocal, int) + */ + public void updateAggregate(SessionLocal session, int stage) { + if (partitionBy != null) { + for (Expression expr : partitionBy) { + expr.updateAggregate(session, stage); + } + } + if (orderBy != null) { + for (QueryOrderBy o : orderBy) { + o.expression.updateAggregate(session, stage); + } + } + if (frame != null) { + frame.updateAggregate(session, stage); + } + } + + @Override + public String toString() { + return getSQL(new StringBuilder(), HasSQL.TRACE_SQL_FLAGS, false).toString(); + } + +} diff --git a/h2/src/main/org/h2/expression/analysis/WindowFrame.java b/h2/src/main/org/h2/expression/analysis/WindowFrame.java new file mode 100644 index 0000000..a5b4072 --- /dev/null +++ b/h2/src/main/org/h2/expression/analysis/WindowFrame.java @@ -0,0 +1,877 @@ +/* + * 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.expression.analysis; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.expression.BinaryOperation; +import org.h2.expression.BinaryOperation.OpType; +import org.h2.expression.Expression; +import org.h2.expression.ValueExpression; +import org.h2.message.DbException; +import org.h2.result.SortOrder; +import org.h2.table.ColumnResolver; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * Window frame clause. + */ +public final class WindowFrame { + + private abstract static class Itr implements Iterator { + + final ArrayList orderedRows; + + int cursor; + + Itr(ArrayList orderedRows) { + this.orderedRows = orderedRows; + } + + } + + private static class PlainItr extends Itr { + + final int endIndex; + + PlainItr(ArrayList orderedRows, int startIndex, int endIndex) { + super(orderedRows); + this.endIndex = endIndex; + cursor = startIndex; + } + + @Override + public boolean hasNext() { + return cursor <= endIndex; + } + + @Override + public Value[] next() { + if (cursor > endIndex) { + throw new NoSuchElementException(); + } + return orderedRows.get(cursor++); + } + + } + + private static class PlainReverseItr extends Itr { + + final int startIndex; + + PlainReverseItr(ArrayList orderedRows, int startIndex, int endIndex) { + super(orderedRows); + this.startIndex = startIndex; + cursor = endIndex; + } + + @Override + public boolean hasNext() { + return cursor >= startIndex; + } + + @Override + public Value[] next() { + if (cursor < startIndex) { + throw new NoSuchElementException(); + } + return orderedRows.get(cursor--); + } + + } + + private static class BiItr extends PlainItr { + + final int end1, start1; + + BiItr(ArrayList orderedRows, int startIndex1, int endIndex1, int startIndex2, int endIndex2) { + super(orderedRows, startIndex1, endIndex2); + end1 = endIndex1; + start1 = startIndex2; + } + + @Override + public Value[] next() { + if (cursor > endIndex) { + throw new NoSuchElementException(); + } + Value[] r = orderedRows.get(cursor); + cursor = cursor != end1 ? cursor + 1 : start1; + return r; + } + + } + + private static class BiReverseItr extends PlainReverseItr { + + final int end1, start1; + + BiReverseItr(ArrayList orderedRows, int startIndex1, int endIndex1, int startIndex2, int endIndex2) { + super(orderedRows, startIndex1, endIndex2); + end1 = endIndex1; + start1 = startIndex2; + } + + @Override + public Value[] next() { + if (cursor < startIndex) { + throw new NoSuchElementException(); + } + Value[] r = orderedRows.get(cursor); + cursor = cursor != start1 ? cursor - 1 : end1; + return r; + } + + } + + private static final class TriItr extends BiItr { + + private final int end2, start2; + + TriItr(ArrayList orderedRows, int startIndex1, int endIndex1, int startIndex2, int endIndex2, + int startIndex3, int endIndex3) { + super(orderedRows, startIndex1, endIndex1, startIndex2, endIndex3); + end2 = endIndex2; + start2 = startIndex3; + } + + @Override + public Value[] next() { + if (cursor > endIndex) { + throw new NoSuchElementException(); + } + Value[] r = orderedRows.get(cursor); + cursor = cursor != end1 ? cursor != end2 ? cursor + 1 : start2 : start1; + return r; + } + + } + + private static final class TriReverseItr extends BiReverseItr { + + private final int end2, start2; + + TriReverseItr(ArrayList orderedRows, int startIndex1, int endIndex1, int startIndex2, int endIndex2, + int startIndex3, int endIndex3) { + super(orderedRows, startIndex1, endIndex1, startIndex2, endIndex3); + end2 = endIndex2; + start2 = startIndex3; + } + + @Override + public Value[] next() { + if (cursor < startIndex) { + throw new NoSuchElementException(); + } + Value[] r = orderedRows.get(cursor); + cursor = cursor != start1 ? cursor != start2 ? cursor - 1 : end2 : end1; + return r; + } + + } + + private final WindowFrameUnits units; + + private final WindowFrameBound starting; + + private final WindowFrameBound following; + + private final WindowFrameExclusion exclusion; + + /** + * Returns iterator for the specified frame, or default iterator if frame is + * null. + * + * @param over + * window + * @param session + * the session + * @param orderedRows + * ordered rows + * @param sortOrder + * sort order + * @param currentRow + * index of the current row + * @param reverse + * whether iterator should iterate in reverse order + * @return iterator + */ + public static Iterator iterator(Window over, SessionLocal session, ArrayList orderedRows, + SortOrder sortOrder, int currentRow, boolean reverse) { + WindowFrame frame = over.getWindowFrame(); + if (frame != null) { + return frame.iterator(session, orderedRows, sortOrder, currentRow, reverse); + } + int endIndex = orderedRows.size() - 1; + return plainIterator(orderedRows, 0, + over.getOrderBy() == null ? endIndex : toGroupEnd(orderedRows, sortOrder, currentRow, endIndex), + reverse); + } + + /** + * Returns end index for the specified frame, or default end index if frame + * is null. + * + * @param over + * window + * @param session + * the session + * @param orderedRows + * ordered rows + * @param sortOrder + * sort order + * @param currentRow + * index of the current row + * @return end index + * @throws UnsupportedOperationException + * if over is not null and its exclusion clause is not EXCLUDE + * NO OTHERS + */ + public static int getEndIndex(Window over, SessionLocal session, ArrayList orderedRows, + SortOrder sortOrder, int currentRow) { + WindowFrame frame = over.getWindowFrame(); + if (frame != null) { + return frame.getEndIndex(session, orderedRows, sortOrder, currentRow); + } + int endIndex = orderedRows.size() - 1; + return over.getOrderBy() == null ? endIndex : toGroupEnd(orderedRows, sortOrder, currentRow, endIndex); + } + + private static Iterator plainIterator(ArrayList orderedRows, int startIndex, int endIndex, + boolean reverse) { + if (endIndex < startIndex) { + return Collections.emptyIterator(); + } + return reverse ? new PlainReverseItr(orderedRows, startIndex, endIndex) + : new PlainItr(orderedRows, startIndex, endIndex); + } + + private static Iterator biIterator(ArrayList orderedRows, int startIndex1, int endIndex1, + int startIndex2, int endIndex2, boolean reverse) { + return reverse ? new BiReverseItr(orderedRows, startIndex1, endIndex1, startIndex2, endIndex2) + : new BiItr(orderedRows, startIndex1, endIndex1, startIndex2, endIndex2); + } + + private static Iterator triIterator(ArrayList orderedRows, int startIndex1, int endIndex1, + int startIndex2, int endIndex2, int startIndex3, int endIndex3, boolean reverse) { + return reverse ? new TriReverseItr(orderedRows, startIndex1, endIndex1, startIndex2, endIndex2, // + startIndex3, endIndex3) + : new TriItr(orderedRows, startIndex1, endIndex1, startIndex2, endIndex2, startIndex3, endIndex3); + } + + private static int toGroupStart(ArrayList orderedRows, SortOrder sortOrder, int offset, int minOffset) { + Value[] row = orderedRows.get(offset); + while (offset > minOffset && sortOrder.compare(row, orderedRows.get(offset - 1)) == 0) { + offset--; + } + return offset; + } + + private static int toGroupEnd(ArrayList orderedRows, SortOrder sortOrder, int offset, int maxOffset) { + Value[] row = orderedRows.get(offset); + while (offset < maxOffset && sortOrder.compare(row, orderedRows.get(offset + 1)) == 0) { + offset++; + } + return offset; + } + + private static int getIntOffset(WindowFrameBound bound, Value[] values, SessionLocal session) { + Value v = bound.isVariable() ? values[bound.getExpressionIndex()] : bound.getValue().getValue(session); + int value; + if (v == ValueNull.INSTANCE || (value = v.getInt()) < 0) { + throw DbException.get(ErrorCode.INVALID_PRECEDING_OR_FOLLOWING_1, v.getTraceSQL()); + } + return value; + } + + /** + * Appends bound value to the current row and produces row for comparison + * operations. + * + * @param session + * the session + * @param orderedRows + * rows in partition + * @param sortOrder + * the sort order + * @param currentRow + * index of the current row + * @param bound + * window frame bound + * @param add + * false for PRECEDING, true for FOLLOWING + * @return row for comparison operations, or null if result is out of range + * and should be treated as UNLIMITED + */ + private static Value[] getCompareRow(SessionLocal session, ArrayList orderedRows, SortOrder sortOrder, + int currentRow, WindowFrameBound bound, boolean add) { + int sortIndex = sortOrder.getQueryColumnIndexes()[0]; + Value[] row = orderedRows.get(currentRow); + Value currentValue = row[sortIndex]; + int type = currentValue.getValueType(); + Value newValue; + Value range = getValueOffset(bound, orderedRows.get(currentRow), session); + switch (type) { + case Value.NULL: + newValue = ValueNull.INSTANCE; + break; + case Value.TINYINT: + case Value.SMALLINT: + case Value.INTEGER: + case Value.BIGINT: + case Value.NUMERIC: + case Value.REAL: + case Value.DOUBLE: + case Value.DECFLOAT: + case Value.TIME: + case Value.TIME_TZ: + case Value.DATE: + case Value.TIMESTAMP: + case Value.TIMESTAMP_TZ: + case Value.INTERVAL_YEAR: + case Value.INTERVAL_MONTH: + case Value.INTERVAL_DAY: + case Value.INTERVAL_HOUR: + case Value.INTERVAL_MINUTE: + case Value.INTERVAL_SECOND: + case Value.INTERVAL_YEAR_TO_MONTH: + case Value.INTERVAL_DAY_TO_HOUR: + case Value.INTERVAL_DAY_TO_MINUTE: + case Value.INTERVAL_DAY_TO_SECOND: + case Value.INTERVAL_HOUR_TO_MINUTE: + case Value.INTERVAL_HOUR_TO_SECOND: + case Value.INTERVAL_MINUTE_TO_SECOND: + OpType opType = add ^ (sortOrder.getSortTypes()[0] & SortOrder.DESCENDING) != 0 ? OpType.PLUS + : OpType.MINUS; + try { + newValue = new BinaryOperation(opType, ValueExpression.get(currentValue), ValueExpression.get(range)) + .optimize(session).getValue(session).convertTo(type); + } catch (DbException ex) { + switch (ex.getErrorCode()) { + case ErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_1: + case ErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_2: + return null; + } + throw ex; + } + break; + default: + throw DbException.getInvalidValueException("unsupported type of sort key for RANGE units", + currentValue.getTraceSQL()); + } + Value[] newRow = row.clone(); + newRow[sortIndex] = newValue; + return newRow; + } + + private static Value getValueOffset(WindowFrameBound bound, Value[] values, SessionLocal session) { + Value value = bound.isVariable() ? values[bound.getExpressionIndex()] : bound.getValue().getValue(session); + if (value == ValueNull.INSTANCE || value.getSignum() < 0) { + throw DbException.get(ErrorCode.INVALID_PRECEDING_OR_FOLLOWING_1, value.getTraceSQL()); + } + return value; + } + + /** + * Creates new instance of window frame clause. + * + * @param units + * units + * @param starting + * starting clause + * @param following + * following clause + * @param exclusion + * exclusion clause + */ + public WindowFrame(WindowFrameUnits units, WindowFrameBound starting, WindowFrameBound following, + WindowFrameExclusion exclusion) { + this.units = units; + this.starting = starting; + if (following != null && following.getType() == WindowFrameBoundType.CURRENT_ROW) { + following = null; + } + this.following = following; + this.exclusion = exclusion; + } + + /** + * Returns the units. + * + * @return the units + */ + public WindowFrameUnits getUnits() { + return units; + } + + /** + * Returns the starting clause. + * + * @return the starting clause + */ + public WindowFrameBound getStarting() { + return starting; + } + + /** + * Returns the following clause. + * + * @return the following clause, or null + */ + public WindowFrameBound getFollowing() { + return following; + } + + /** + * Returns the exclusion clause. + * + * @return the exclusion clause + */ + public WindowFrameExclusion getExclusion() { + return exclusion; + } + + /** + * Checks validity of this frame. + * + * @return whether bounds of this frame valid + */ + public boolean isValid() { + WindowFrameBoundType s = starting.getType(), + f = following != null ? following.getType() : WindowFrameBoundType.CURRENT_ROW; + return s != WindowFrameBoundType.UNBOUNDED_FOLLOWING && f != WindowFrameBoundType.UNBOUNDED_PRECEDING + && s.compareTo(f) <= 0; + } + + /** + * Check if bounds of this frame has variable expressions. This method may + * be used only after {@link #optimize(SessionLocal)} invocation. + * + * @return if bounds of this frame has variable expressions + */ + public boolean isVariableBounds() { + if (starting.isVariable()) { + return true; + } + if (following != null && following.isVariable()) { + return true; + } + return false; + } + + /** + * Map the columns of the resolver to expression columns. + * + * @param resolver + * the column resolver + * @param level + * the subquery nesting level + * @param state + * current state for nesting checks + */ + void mapColumns(ColumnResolver resolver, int level, int state) { + starting.mapColumns(resolver, level, state); + if (following != null) { + following.mapColumns(resolver, level, state); + } + } + + /** + * Try to optimize bound expressions. + * + * @param session + * the session + */ + void optimize(SessionLocal session) { + starting.optimize(session); + if (following != null) { + following.optimize(session); + } + } + + /** + * Update an aggregate value. + * + * @param session + * the session + * @param stage + * select stage + * @see Expression#updateAggregate(SessionLocal, int) + */ + void updateAggregate(SessionLocal session, int stage) { + starting.updateAggregate(session, stage); + if (following != null) { + following.updateAggregate(session, stage); + } + } + + /** + * Returns iterator. + * + * @param session + * the session + * @param orderedRows + * ordered rows + * @param sortOrder + * sort order + * @param currentRow + * index of the current row + * @param reverse + * whether iterator should iterate in reverse order + * @return iterator + */ + public Iterator iterator(SessionLocal session, ArrayList orderedRows, SortOrder sortOrder, + int currentRow, boolean reverse) { + int startIndex = getIndex(session, orderedRows, sortOrder, currentRow, starting, false); + int endIndex = following != null ? getIndex(session, orderedRows, sortOrder, currentRow, following, true) + : units == WindowFrameUnits.ROWS ? currentRow + : toGroupEnd(orderedRows, sortOrder, currentRow, orderedRows.size() - 1); + if (endIndex < startIndex) { + return Collections.emptyIterator(); + } + int size = orderedRows.size(); + if (startIndex >= size || endIndex < 0) { + return Collections.emptyIterator(); + } + if (startIndex < 0) { + startIndex = 0; + } + if (endIndex >= size) { + endIndex = size - 1; + } + return exclusion != WindowFrameExclusion.EXCLUDE_NO_OTHERS + ? complexIterator(orderedRows, sortOrder, currentRow, startIndex, endIndex, reverse) + : plainIterator(orderedRows, startIndex, endIndex, reverse); + } + + /** + * Returns start index of this frame, + * + * @param session + * the session + * @param orderedRows + * ordered rows + * @param sortOrder + * sort order + * @param currentRow + * index of the current row + * @return start index + * @throws UnsupportedOperationException + * if exclusion clause is not EXCLUDE NO OTHERS + */ + public int getStartIndex(SessionLocal session, ArrayList orderedRows, SortOrder sortOrder, // + int currentRow) { + if (exclusion != WindowFrameExclusion.EXCLUDE_NO_OTHERS) { + throw new UnsupportedOperationException(); + } + int startIndex = getIndex(session, orderedRows, sortOrder, currentRow, starting, false); + if (startIndex < 0) { + startIndex = 0; + } + return startIndex; + } + + /** + * Returns end index of this frame, + * + * @param session + * the session + * @param orderedRows + * ordered rows + * @param sortOrder + * sort order + * @param currentRow + * index of the current row + * @return end index + * @throws UnsupportedOperationException + * if exclusion clause is not EXCLUDE NO OTHERS + */ + private int getEndIndex(SessionLocal session, ArrayList orderedRows, SortOrder sortOrder, // + int currentRow) { + if (exclusion != WindowFrameExclusion.EXCLUDE_NO_OTHERS) { + throw new UnsupportedOperationException(); + } + int endIndex = following != null ? getIndex(session, orderedRows, sortOrder, currentRow, following, true) + : units == WindowFrameUnits.ROWS ? currentRow + : toGroupEnd(orderedRows, sortOrder, currentRow, orderedRows.size() - 1); + int size = orderedRows.size(); + if (endIndex >= size) { + endIndex = size - 1; + } + return endIndex; + } + + /** + * Returns starting or ending index of a window frame. + * + * @param session + * the session + * @param orderedRows + * rows in partition + * @param sortOrder + * the sort order + * @param currentRow + * index of the current row + * @param bound + * window frame bound + * @param forFollowing + * false for start index, true for end index + * @return starting or ending index of a window frame (inclusive), can be 0 + * or be equal to the number of rows if frame is not limited from + * that side + */ + private int getIndex(SessionLocal session, ArrayList orderedRows, SortOrder sortOrder, int currentRow, + WindowFrameBound bound, boolean forFollowing) { + int size = orderedRows.size(); + int last = size - 1; + int index; + switch (bound.getType()) { + case UNBOUNDED_PRECEDING: + index = -1; + break; + case PRECEDING: + switch (units) { + case ROWS: { + int value = getIntOffset(bound, orderedRows.get(currentRow), session); + index = value > currentRow ? -1 : currentRow - value; + break; + } + case GROUPS: { + int value = getIntOffset(bound, orderedRows.get(currentRow), session); + if (!forFollowing) { + index = toGroupStart(orderedRows, sortOrder, currentRow, 0); + while (value > 0 && index > 0) { + value--; + index = toGroupStart(orderedRows, sortOrder, index - 1, 0); + } + if (value > 0) { + index = -1; + } + } else { + if (value == 0) { + index = toGroupEnd(orderedRows, sortOrder, currentRow, last); + } else { + index = currentRow; + while (value > 0 && index >= 0) { + value--; + index = toGroupStart(orderedRows, sortOrder, index, 0) - 1; + } + } + } + break; + } + case RANGE: { + index = currentRow; + Value[] row = getCompareRow(session, orderedRows, sortOrder, index, bound, false); + if (row != null) { + index = Collections.binarySearch(orderedRows, row, sortOrder); + if (index >= 0) { + if (!forFollowing) { + while (index > 0 && sortOrder.compare(row, orderedRows.get(index - 1)) == 0) { + index--; + } + } else { + while (index < last && sortOrder.compare(row, orderedRows.get(index + 1)) == 0) { + index++; + } + } + } else { + index = ~index; + if (!forFollowing) { + if (index == 0) { + index = -1; + } + } else { + index--; + } + } + } else { + index = -1; + } + break; + } + default: + throw DbException.getUnsupportedException("units=" + units); + } + break; + case CURRENT_ROW: + switch (units) { + case ROWS: + index = currentRow; + break; + case GROUPS: + case RANGE: + index = forFollowing ? toGroupEnd(orderedRows, sortOrder, currentRow, last) + : toGroupStart(orderedRows, sortOrder, currentRow, 0); + break; + default: + throw DbException.getUnsupportedException("units=" + units); + } + break; + case FOLLOWING: + switch (units) { + case ROWS: { + int value = getIntOffset(bound, orderedRows.get(currentRow), session); + int rem = last - currentRow; + index = value > rem ? size : currentRow + value; + break; + } + case GROUPS: { + int value = getIntOffset(bound, orderedRows.get(currentRow), session); + if (forFollowing) { + index = toGroupEnd(orderedRows, sortOrder, currentRow, last); + while (value > 0 && index < last) { + value--; + index = toGroupEnd(orderedRows, sortOrder, index + 1, last); + } + if (value > 0) { + index = size; + } + } else { + if (value == 0) { + index = toGroupStart(orderedRows, sortOrder, currentRow, 0); + } else { + index = currentRow; + while (value > 0 && index <= last) { + value--; + index = toGroupEnd(orderedRows, sortOrder, index, last) + 1; + } + } + } + break; + } + case RANGE: { + index = currentRow; + Value[] row = getCompareRow(session, orderedRows, sortOrder, index, bound, true); + if (row != null) { + index = Collections.binarySearch(orderedRows, row, sortOrder); + if (index >= 0) { + if (forFollowing) { + while (index < last && sortOrder.compare(row, orderedRows.get(index + 1)) == 0) { + index++; + } + } else { + while (index > 0 && sortOrder.compare(row, orderedRows.get(index - 1)) == 0) { + index--; + } + } + } else { + index = ~index; + if (forFollowing) { + if (index != size) { + index--; + } + } + } + } else { + index = size; + } + break; + } + default: + throw DbException.getUnsupportedException("units=" + units); + } + break; + case UNBOUNDED_FOLLOWING: + index = size; + break; + default: + throw DbException.getUnsupportedException("window frame bound type=" + bound.getType()); + } + return index; + } + + private Iterator complexIterator(ArrayList orderedRows, SortOrder sortOrder, int currentRow, + int startIndex, int endIndex, boolean reverse) { + if (exclusion == WindowFrameExclusion.EXCLUDE_CURRENT_ROW) { + if (currentRow < startIndex || currentRow > endIndex) { + // Nothing to exclude + } else if (currentRow == startIndex) { + startIndex++; + } else if (currentRow == endIndex) { + endIndex--; + } else { + return biIterator(orderedRows, startIndex, currentRow - 1, currentRow + 1, endIndex, reverse); + } + } else { + // Do not include previous rows if they are not in the range + int exStart = toGroupStart(orderedRows, sortOrder, currentRow, startIndex); + // Do not include next rows if they are not in the range + int exEnd = toGroupEnd(orderedRows, sortOrder, currentRow, endIndex); + boolean includeCurrentRow = exclusion == WindowFrameExclusion.EXCLUDE_TIES; + if (includeCurrentRow) { + // Simplify exclusion if possible + if (currentRow == exStart) { + exStart++; + includeCurrentRow = false; + } else if (currentRow == exEnd) { + exEnd--; + includeCurrentRow = false; + } + } + if (exStart > exEnd || exEnd < startIndex || exStart > endIndex) { + // Empty range or nothing to exclude + } else if (includeCurrentRow) { + if (startIndex == exStart) { + if (endIndex == exEnd) { + return Collections.singleton(orderedRows.get(currentRow)).iterator(); + } else { + return biIterator(orderedRows, currentRow, currentRow, exEnd + 1, endIndex, reverse); + } + } else { + if (endIndex == exEnd) { + return biIterator(orderedRows, startIndex, exStart - 1, currentRow, currentRow, reverse); + } else { + return triIterator(orderedRows, startIndex, exStart - 1, currentRow, currentRow, exEnd + 1, + endIndex, reverse); + } + } + } else { + if (startIndex >= exStart) { + startIndex = exEnd + 1; + } else if (endIndex <= exEnd) { + endIndex = exStart - 1; + } else { + return biIterator(orderedRows, startIndex, exStart - 1, exEnd + 1, endIndex, reverse); + } + } + } + return plainIterator(orderedRows, startIndex, endIndex, reverse); + } + + /** + * Append SQL representation to the specified builder. + * + * @param builder + * string builder + * @param formattingFlags + * quote all identifiers + * @return the specified string builder + * @see org.h2.expression.Expression#getSQL(StringBuilder, int, int) + */ + public StringBuilder getSQL(StringBuilder builder, int formattingFlags) { + builder.append(units.getSQL()); + if (following == null) { + builder.append(' '); + starting.getSQL(builder, false, formattingFlags); + } else { + builder.append(" BETWEEN "); + starting.getSQL(builder, false, formattingFlags).append(" AND "); + following.getSQL(builder, true, formattingFlags); + } + if (exclusion != WindowFrameExclusion.EXCLUDE_NO_OTHERS) { + builder.append(' ').append(exclusion.getSQL()); + } + return builder; + } + +} diff --git a/h2/src/main/org/h2/expression/analysis/WindowFrameBound.java b/h2/src/main/org/h2/expression/analysis/WindowFrameBound.java new file mode 100644 index 0000000..ca52045 --- /dev/null +++ b/h2/src/main/org/h2/expression/analysis/WindowFrameBound.java @@ -0,0 +1,164 @@ +/* + * 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.expression.analysis; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.table.ColumnResolver; + +/** + * Window frame bound. + */ +public class WindowFrameBound { + + private final WindowFrameBoundType type; + + private Expression value; + + private boolean isVariable; + + private int expressionIndex = -1; + + /** + * Creates new instance of window frame bound. + * + * @param type + * bound type + * @param value + * bound value, if any + */ + public WindowFrameBound(WindowFrameBoundType type, Expression value) { + this.type = type; + if (type == WindowFrameBoundType.PRECEDING || type == WindowFrameBoundType.FOLLOWING) { + this.value = value; + } else { + this.value = null; + } + } + + /** + * Returns the type + * + * @return the type + */ + public WindowFrameBoundType getType() { + return type; + } + + /** + * Returns the value. + * + * @return the value + */ + public Expression getValue() { + return value; + } + + /** + * Returns whether bound is defined as n PRECEDING or n FOLLOWING. + * + * @return whether bound is defined as n PRECEDING or n FOLLOWING + */ + public boolean isParameterized() { + return type == WindowFrameBoundType.PRECEDING || type == WindowFrameBoundType.FOLLOWING; + } + + /** + * Returns whether bound is defined with a variable. This method may be used + * only after {@link #optimize(SessionLocal)} invocation. + * + * @return whether bound is defined with a variable + */ + public boolean isVariable() { + return isVariable; + } + + /** + * Returns the index of preserved expression. + * + * @return the index of preserved expression, or -1 + */ + public int getExpressionIndex() { + return expressionIndex; + } + + /** + * Sets the index of preserved expression. + * + * @param expressionIndex + * the index to set + */ + void setExpressionIndex(int expressionIndex) { + this.expressionIndex = expressionIndex; + } + + /** + * Map the columns of the resolver to expression columns. + * + * @param resolver + * the column resolver + * @param level + * the subquery nesting level + * @param state + * current state for nesting checks + */ + void mapColumns(ColumnResolver resolver, int level, int state) { + if (value != null) { + value.mapColumns(resolver, level, state); + } + } + + /** + * Try to optimize bound expression. + * + * @param session + * the session + */ + void optimize(SessionLocal session) { + if (value != null) { + value = value.optimize(session); + if (!value.isConstant()) { + isVariable = true; + } + } + } + + /** + * Update an aggregate value. + * + * @param session + * the session + * @param stage + * select stage + * @see Expression#updateAggregate(SessionLocal, int) + */ + void updateAggregate(SessionLocal session, int stage) { + if (value != null) { + value.updateAggregate(session, stage); + } + } + + /** + * Appends SQL representation to the specified builder. + * + * @param builder + * string builder + * @param following + * if false return SQL for starting clause, if true return SQL + * for following clause + * @param sqlFlags + * formatting flags + * @return the specified string builder + * @see Expression#getSQL(StringBuilder, int, int) + */ + public StringBuilder getSQL(StringBuilder builder, boolean following, int sqlFlags) { + if (type == WindowFrameBoundType.PRECEDING || type == WindowFrameBoundType.FOLLOWING) { + value.getUnenclosedSQL(builder, sqlFlags).append(' '); + } + return builder.append(type.getSQL()); + } + +} diff --git a/h2/src/main/org/h2/expression/analysis/WindowFrameBoundType.java b/h2/src/main/org/h2/expression/analysis/WindowFrameBoundType.java new file mode 100644 index 0000000..27b2e3a --- /dev/null +++ b/h2/src/main/org/h2/expression/analysis/WindowFrameBoundType.java @@ -0,0 +1,54 @@ +/* + * 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.expression.analysis; + +/** + * Window frame bound type. + */ +public enum WindowFrameBoundType { + + /** + * UNBOUNDED PRECEDING clause. + */ + UNBOUNDED_PRECEDING("UNBOUNDED PRECEDING"), + + /** + * PRECEDING clause. + */ + PRECEDING("PRECEDING"), + + /** + * CURRENT_ROW clause. + */ + CURRENT_ROW("CURRENT ROW"), + + /** + * FOLLOWING clause. + */ + FOLLOWING("FOLLOWING"), + + /** + * UNBOUNDED FOLLOWING clause. + */ + UNBOUNDED_FOLLOWING("UNBOUNDED FOLLOWING"); + + private final String sql; + + private WindowFrameBoundType(String sql) { + this.sql = sql; + } + + /** + * Returns SQL representation. + * + * @return SQL representation. + * @see org.h2.expression.Expression#getSQL(int) + */ + public String getSQL() { + return sql; + } + +} diff --git a/h2/src/main/org/h2/expression/analysis/WindowFrameExclusion.java b/h2/src/main/org/h2/expression/analysis/WindowFrameExclusion.java new file mode 100644 index 0000000..e587732 --- /dev/null +++ b/h2/src/main/org/h2/expression/analysis/WindowFrameExclusion.java @@ -0,0 +1,62 @@ +/* + * 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.expression.analysis; + +/** + * Window frame exclusion clause. + */ +public enum WindowFrameExclusion { + + /** + * EXCLUDE CURRENT ROW exclusion clause. + */ + EXCLUDE_CURRENT_ROW("EXCLUDE CURRENT ROW"), + + /** + * EXCLUDE GROUP exclusion clause. + */ + EXCLUDE_GROUP("EXCLUDE GROUP"), + + /** + * EXCLUDE TIES exclusion clause. + */ + EXCLUDE_TIES("EXCLUDE TIES"), + + /** + * EXCLUDE NO OTHERS exclusion clause. + */ + EXCLUDE_NO_OTHERS("EXCLUDE NO OTHERS"), + + ; + + private final String sql; + + private WindowFrameExclusion(String sql) { + this.sql = sql; + } + + /** + * Returns true if this exclusion clause excludes or includes the whole + * group. + * + * @return true if this exclusion clause is {@link #EXCLUDE_GROUP} or + * {@link #EXCLUDE_NO_OTHERS} + */ + public boolean isGroupOrNoOthers() { + return this == WindowFrameExclusion.EXCLUDE_GROUP || this == EXCLUDE_NO_OTHERS; + } + + /** + * Returns SQL representation. + * + * @return SQL representation. + * @see org.h2.expression.Expression#getSQL(int) + */ + public String getSQL() { + return sql; + } + +} diff --git a/h2/src/main/org/h2/expression/analysis/WindowFrameUnits.java b/h2/src/main/org/h2/expression/analysis/WindowFrameUnits.java new file mode 100644 index 0000000..081438e --- /dev/null +++ b/h2/src/main/org/h2/expression/analysis/WindowFrameUnits.java @@ -0,0 +1,40 @@ +/* + * 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.expression.analysis; + +/** + * Window frame units. + */ +public enum WindowFrameUnits { + + /** + * ROWS unit. + */ + ROWS, + + /** + * RANGE unit. + */ + RANGE, + + /** + * GROUPS unit. + */ + GROUPS, + + ; + + /** + * Returns SQL representation. + * + * @return SQL representation. + * @see org.h2.expression.Expression#getSQL(int) + */ + public String getSQL() { + return name(); + } + +} diff --git a/h2/src/main/org/h2/expression/analysis/WindowFunction.java b/h2/src/main/org/h2/expression/analysis/WindowFunction.java new file mode 100644 index 0000000..c3ddc40 --- /dev/null +++ b/h2/src/main/org/h2/expression/analysis/WindowFunction.java @@ -0,0 +1,544 @@ +/* + * 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.expression.analysis; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; + +import org.h2.command.query.Select; +import org.h2.command.query.SelectGroups; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ValueExpression; +import org.h2.message.DbException; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBigint; +import org.h2.value.ValueDouble; +import org.h2.value.ValueNull; + +/** + * A window function. + */ +public class WindowFunction extends DataAnalysisOperation { + + private final WindowFunctionType type; + + private final Expression[] args; + + private boolean fromLast; + + private boolean ignoreNulls; + + /** + * Returns minimal number of arguments for the specified type. + * + * @param type + * the type of a window function + * @return minimal number of arguments + */ + public static int getMinArgumentCount(WindowFunctionType type) { + switch (type) { + case NTILE: + case LEAD: + case LAG: + case FIRST_VALUE: + case LAST_VALUE: + case RATIO_TO_REPORT: + return 1; + case NTH_VALUE: + return 2; + default: + return 0; + } + } + + /** + * Returns maximal number of arguments for the specified type. + * + * @param type + * the type of a window function + * @return maximal number of arguments + */ + public static int getMaxArgumentCount(WindowFunctionType type) { + switch (type) { + case NTILE: + case FIRST_VALUE: + case LAST_VALUE: + case RATIO_TO_REPORT: + return 1; + case LEAD: + case LAG: + return 3; + case NTH_VALUE: + return 2; + default: + return 0; + } + } + + private static Value getNthValue(Iterator iterator, int number, boolean ignoreNulls) { + Value v = ValueNull.INSTANCE; + int cnt = 0; + while (iterator.hasNext()) { + Value t = iterator.next()[0]; + if (!ignoreNulls || t != ValueNull.INSTANCE) { + if (cnt++ == number) { + v = t; + break; + } + } + } + return v; + } + + /** + * Creates new instance of a window function. + * + * @param type + * the type + * @param select + * the select statement + * @param args + * arguments, or null + */ + public WindowFunction(WindowFunctionType type, Select select, Expression[] args) { + super(select); + this.type = type; + this.args = args; + } + + /** + * Returns the type of this function. + * + * @return the type of this function + */ + public WindowFunctionType getFunctionType() { + return type; + } + + /** + * Sets FROM FIRST or FROM LAST clause value. + * + * @param fromLast + * whether FROM LAST clause was specified. + */ + public void setFromLast(boolean fromLast) { + this.fromLast = fromLast; + } + + /** + * Sets RESPECT NULLS or IGNORE NULLS clause value. + * + * @param ignoreNulls + * whether IGNORE NULLS clause was specified + */ + public void setIgnoreNulls(boolean ignoreNulls) { + this.ignoreNulls = ignoreNulls; + } + + @Override + public boolean isAggregate() { + return false; + } + + @Override + protected void updateAggregate(SessionLocal session, SelectGroups groupData, int groupRowId) { + updateOrderedAggregate(session, groupData, groupRowId, over.getOrderBy()); + } + + @Override + protected void updateGroupAggregates(SessionLocal session, int stage) { + super.updateGroupAggregates(session, stage); + if (args != null) { + for (Expression expr : args) { + expr.updateAggregate(session, stage); + } + } + } + + @Override + protected int getNumExpressions() { + return args != null ? args.length : 0; + } + + @Override + protected void rememberExpressions(SessionLocal session, Value[] array) { + if (args != null) { + for (int i = 0, cnt = args.length; i < cnt; i++) { + array[i] = args[i].getValue(session); + } + } + } + + @Override + protected Object createAggregateData() { + throw DbException.getUnsupportedException("Window function"); + } + + @Override + protected void getOrderedResultLoop(SessionLocal session, HashMap result, + ArrayList ordered, int rowIdColumn) { + switch (type) { + case ROW_NUMBER: + for (int i = 0, size = ordered.size(); i < size;) { + result.put(ordered.get(i)[rowIdColumn].getInt(), ValueBigint.get(++i)); + } + break; + case RANK: + case DENSE_RANK: + case PERCENT_RANK: + getRank(result, ordered, rowIdColumn); + break; + case CUME_DIST: + getCumeDist(result, ordered, rowIdColumn); + break; + case NTILE: + getNtile(result, ordered, rowIdColumn); + break; + case LEAD: + case LAG: + getLeadLag(result, ordered, rowIdColumn, session); + break; + case FIRST_VALUE: + case LAST_VALUE: + case NTH_VALUE: + getNth(session, result, ordered, rowIdColumn); + break; + case RATIO_TO_REPORT: + getRatioToReport(result, ordered, rowIdColumn); + break; + default: + throw DbException.getInternalError("type=" + type); + } + } + + private void getRank(HashMap result, ArrayList ordered, int rowIdColumn) { + int size = ordered.size(); + int number = 0; + for (int i = 0; i < size; i++) { + Value[] row = ordered.get(i); + if (i == 0) { + number = 1; + } else if (getOverOrderBySort().compare(ordered.get(i - 1), row) != 0) { + if (type == WindowFunctionType.DENSE_RANK) { + number++; + } else { + number = i + 1; + } + } + Value v; + if (type == WindowFunctionType.PERCENT_RANK) { + int nm = number - 1; + v = nm == 0 ? ValueDouble.ZERO : ValueDouble.get((double) nm / (size - 1)); + } else { + v = ValueBigint.get(number); + } + result.put(row[rowIdColumn].getInt(), v); + } + } + + private void getCumeDist(HashMap result, ArrayList orderedData, int rowIdColumn) { + int size = orderedData.size(); + for (int start = 0; start < size;) { + Value[] array = orderedData.get(start); + int end = start + 1; + while (end < size && overOrderBySort.compare(array, orderedData.get(end)) == 0) { + end++; + } + ValueDouble v = ValueDouble.get((double) end / size); + for (int i = start; i < end; i++) { + int rowId = orderedData.get(i)[rowIdColumn].getInt(); + result.put(rowId, v); + } + start = end; + } + } + + private static void getNtile(HashMap result, ArrayList orderedData, int rowIdColumn) { + int size = orderedData.size(); + for (int i = 0; i < size; i++) { + Value[] array = orderedData.get(i); + long buckets = array[0].getLong(); + if (buckets <= 0) { + throw DbException.getInvalidValueException("number of tiles", buckets); + } + long perTile = size / buckets; + long numLarger = size - perTile * buckets; + long largerGroup = numLarger * (perTile + 1); + long v; + if (i >= largerGroup) { + v = (i - largerGroup) / perTile + numLarger + 1; + } else { + v = i / (perTile + 1) + 1; + } + result.put(orderedData.get(i)[rowIdColumn].getInt(), ValueBigint.get(v)); + } + } + + private void getLeadLag(HashMap result, ArrayList ordered, int rowIdColumn, + SessionLocal session) { + int size = ordered.size(); + int numExpressions = getNumExpressions(); + TypeInfo dataType = args[0].getType(); + for (int i = 0; i < size; i++) { + Value[] row = ordered.get(i); + int rowId = row[rowIdColumn].getInt(); + int n; + if (numExpressions >= 2) { + n = row[1].getInt(); + // 0 is valid here + if (n < 0) { + throw DbException.getInvalidValueException("nth row", n); + } + } else { + n = 1; + } + Value v = null; + if (n == 0) { + v = ordered.get(i)[0]; + } else if (type == WindowFunctionType.LEAD) { + if (ignoreNulls) { + for (int j = i + 1; n > 0 && j < size; j++) { + v = ordered.get(j)[0]; + if (v != ValueNull.INSTANCE) { + n--; + } + } + if (n > 0) { + v = null; + } + } else { + if (n <= size - i - 1) { + v = ordered.get(i + n)[0]; + } + } + } else /* LAG */ { + if (ignoreNulls) { + for (int j = i - 1; n > 0 && j >= 0; j--) { + v = ordered.get(j)[0]; + if (v != ValueNull.INSTANCE) { + n--; + } + } + if (n > 0) { + v = null; + } + } else { + if (n <= i) { + v = ordered.get(i - n)[0]; + } + } + } + if (v == null) { + if (numExpressions >= 3) { + v = row[2].convertTo(dataType, session); + } else { + v = ValueNull.INSTANCE; + } + } + result.put(rowId, v); + } + } + + private void getNth(SessionLocal session, HashMap result, ArrayList ordered, + int rowIdColumn) { + int size = ordered.size(); + for (int i = 0; i < size; i++) { + Value[] row = ordered.get(i); + int rowId = row[rowIdColumn].getInt(); + Value v; + switch (type) { + case FIRST_VALUE: + v = getNthValue(WindowFrame.iterator(over, session, ordered, getOverOrderBySort(), i, false), 0, + ignoreNulls); + break; + case LAST_VALUE: + v = getNthValue(WindowFrame.iterator(over, session, ordered, getOverOrderBySort(), i, true), 0, + ignoreNulls); + break; + case NTH_VALUE: { + int n = row[1].getInt(); + if (n <= 0) { + throw DbException.getInvalidValueException("nth row", n); + } + n--; + Iterator iter = WindowFrame.iterator(over, session, ordered, getOverOrderBySort(), i, + fromLast); + v = getNthValue(iter, n, ignoreNulls); + break; + } + default: + throw DbException.getInternalError("type=" + type); + } + result.put(rowId, v); + } + } + + private static void getRatioToReport(HashMap result, ArrayList ordered, int rowIdColumn) { + int size = ordered.size(); + Value value = null; + for (int i = 0; i < size; i++) { + Value v = ordered.get(i)[0]; + if (v != ValueNull.INSTANCE) { + if (value == null) { + value = v.convertToDouble(); + } else { + value = value.add(v.convertToDouble()); + } + } + } + if (value != null && value.getSignum() == 0) { + value = null; + } + for (int i = 0; i < size; i++) { + Value[] row = ordered.get(i); + Value v; + if (value == null) { + v = ValueNull.INSTANCE; + } else { + v = row[0]; + if (v != ValueNull.INSTANCE) { + v = v.convertToDouble().divide(value, TypeInfo.TYPE_DOUBLE); + } + } + result.put(row[rowIdColumn].getInt(), v); + } + } + + @Override + protected Value getAggregatedValue(SessionLocal session, Object aggregateData) { + throw DbException.getUnsupportedException("Window function"); + } + + @Override + public void mapColumnsAnalysis(ColumnResolver resolver, int level, int innerState) { + if (args != null) { + for (Expression arg : args) { + arg.mapColumns(resolver, level, innerState); + } + } + super.mapColumnsAnalysis(resolver, level, innerState); + } + + @Override + public Expression optimize(SessionLocal session) { + if (over.getWindowFrame() != null) { + switch (type) { + case FIRST_VALUE: + case LAST_VALUE: + case NTH_VALUE: + break; + default: + String sql = getTraceSQL(); + throw DbException.getSyntaxError(sql, sql.length() - 1); + } + } + if (over.getOrderBy() == null) { + if (type.requiresWindowOrdering()) { + String sql = getTraceSQL(); + throw DbException.getSyntaxError(sql, sql.length() - 1, "ORDER BY"); + } + } else if (type == WindowFunctionType.RATIO_TO_REPORT) { + String sql = getTraceSQL(); + throw DbException.getSyntaxError(sql, sql.length() - 1); + } + super.optimize(session); + // Need to re-test, because optimization may remove the window ordering + // clause. + if (over.getOrderBy() == null) { + switch (type) { + case RANK: + case DENSE_RANK: + return ValueExpression.get(ValueBigint.get(1L)); + case PERCENT_RANK: + return ValueExpression.get(ValueDouble.ZERO); + case CUME_DIST: + return ValueExpression.get(ValueDouble.ONE); + default: + } + } + if (args != null) { + for (int i = 0; i < args.length; i++) { + args[i] = args[i].optimize(session); + } + } + return this; + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + if (args != null) { + for (Expression e : args) { + e.setEvaluatable(tableFilter, b); + } + } + super.setEvaluatable(tableFilter, b); + } + + @Override + public TypeInfo getType() { + switch (type) { + case ROW_NUMBER: + case RANK: + case DENSE_RANK: + case NTILE: + return TypeInfo.TYPE_BIGINT; + case PERCENT_RANK: + case CUME_DIST: + case RATIO_TO_REPORT: + return TypeInfo.TYPE_DOUBLE; + case LEAD: + case LAG: + case FIRST_VALUE: + case LAST_VALUE: + case NTH_VALUE: + return args[0].getType(); + default: + throw DbException.getInternalError("type=" + type); + } + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + builder.append(type.getSQL()).append('('); + if (args != null) { + writeExpressions(builder, args, sqlFlags); + } + builder.append(')'); + if (fromLast && type == WindowFunctionType.NTH_VALUE) { + builder.append(" FROM LAST"); + } + if (ignoreNulls) { + switch (type) { + case LEAD: + case LAG: + case FIRST_VALUE: + case LAST_VALUE: + case NTH_VALUE: + builder.append(" IGNORE NULLS"); + //$FALL-THROUGH$ + default: + } + } + return appendTailConditions(builder, sqlFlags, type.requiresWindowOrdering()); + } + + @Override + public int getCost() { + int cost = 1; + if (args != null) { + for (Expression expr : args) { + cost += expr.getCost(); + } + } + return cost; + } + +} diff --git a/h2/src/main/org/h2/expression/analysis/WindowFunctionType.java b/h2/src/main/org/h2/expression/analysis/WindowFunctionType.java new file mode 100644 index 0000000..cc46815 --- /dev/null +++ b/h2/src/main/org/h2/expression/analysis/WindowFunctionType.java @@ -0,0 +1,142 @@ +/* + * 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.expression.analysis; + +/** + * A type of a window function. + */ +public enum WindowFunctionType { + + /** + * The type for ROW_NUMBER() window function. + */ + ROW_NUMBER, + + /** + * The type for RANK() window function. + */ + RANK, + + /** + * The type for DENSE_RANK() window function. + */ + DENSE_RANK, + + /** + * The type for PERCENT_RANK() window function. + */ + PERCENT_RANK, + + /** + * The type for CUME_DIST() window function. + */ + CUME_DIST, + + /** + * The type for NTILE() window function. + */ + NTILE, + + /** + * The type for LEAD() window function. + */ + LEAD, + + /** + * The type for LAG() window function. + */ + LAG, + + /** + * The type for FIRST_VALUE() window function. + */ + FIRST_VALUE, + + /** + * The type for LAST_VALUE() window function. + */ + LAST_VALUE, + + /** + * The type for NTH_VALUE() window function. + */ + NTH_VALUE, + + /** + * The type for RATIO_TO_REPORT() window function. + */ + RATIO_TO_REPORT, + + ; + + /** + * Returns the type of window function with the specified name, or null. + * + * @param name + * name of a window function + * @return the type of window function, or null. + */ + public static WindowFunctionType get(String name) { + switch (name) { + case "ROW_NUMBER": + return ROW_NUMBER; + case "RANK": + return RANK; + case "DENSE_RANK": + return DENSE_RANK; + case "PERCENT_RANK": + return PERCENT_RANK; + case "CUME_DIST": + return CUME_DIST; + case "NTILE": + return NTILE; + case "LEAD": + return LEAD; + case "LAG": + return LAG; + case "FIRST_VALUE": + return FIRST_VALUE; + case "LAST_VALUE": + return LAST_VALUE; + case "NTH_VALUE": + return NTH_VALUE; + case "RATIO_TO_REPORT": + return RATIO_TO_REPORT; + default: + return null; + } + } + + /** + * Returns SQL representation. + * + * @return SQL representation. + * @see org.h2.expression.Expression#getSQL(int) + */ + public String getSQL() { + return name(); + } + + /** + * Returns whether window function of this type requires window ordering + * clause. + * + * @return {@code true} if it does, {@code false} if it may be omitted + */ + public boolean requiresWindowOrdering() { + switch (this) { + case RANK: + case DENSE_RANK: + case NTILE: + case LEAD: + case LAG: + return true; + default: + return false; + } + } + +} \ No newline at end of file diff --git a/h2/src/main/org/h2/expression/analysis/package.html b/h2/src/main/org/h2/expression/analysis/package.html new file mode 100644 index 0000000..5cc4ba0 --- /dev/null +++ b/h2/src/main/org/h2/expression/analysis/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Base classes for data analysis operations and implementations of window functions. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/expression/condition/BetweenPredicate.java b/h2/src/main/org/h2/expression/condition/BetweenPredicate.java new file mode 100644 index 0000000..b5b7b11 --- /dev/null +++ b/h2/src/main/org/h2/expression/condition/BetweenPredicate.java @@ -0,0 +1,207 @@ +/* + * 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.expression.condition; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.TypedValueExpression; +import org.h2.expression.ValueExpression; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueNull; + +/** + * BETWEEN predicate. + */ +public final class BetweenPredicate extends Condition { + + private Expression left; + + private final boolean not; + + private final boolean whenOperand; + + private boolean symmetric; + + private Expression a, b; + + public BetweenPredicate(Expression left, boolean not, boolean whenOperand, boolean symmetric, Expression a, + Expression b) { + this.left = left; + this.not = not; + this.whenOperand = whenOperand; + this.symmetric = symmetric; + this.a = a; + this.b = b; + } + + @Override + public boolean needParentheses() { + return true; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return getWhenSQL(left.getSQL(builder, sqlFlags, AUTO_PARENTHESES), sqlFlags); + } + + @Override + public StringBuilder getWhenSQL(StringBuilder builder, int sqlFlags) { + if (not) { + builder.append(" NOT"); + } + builder.append(" BETWEEN "); + if (symmetric) { + builder.append("SYMMETRIC "); + } + a.getSQL(builder, sqlFlags, AUTO_PARENTHESES).append(" AND "); + return b.getSQL(builder, sqlFlags, AUTO_PARENTHESES); + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + a = a.optimize(session); + b = b.optimize(session); + TypeInfo leftType = left.getType(); + TypeInfo.checkComparable(leftType, a.getType()); + TypeInfo.checkComparable(leftType, b.getType()); + if (whenOperand) { + return this; + } + Value value = left.isConstant() ? left.getValue(session) : null, + aValue = a.isConstant() ? a.getValue(session) : null, + bValue = b.isConstant() ? b.getValue(session) : null; + if (value != null) { + if (value == ValueNull.INSTANCE) { + return TypedValueExpression.UNKNOWN; + } + if (aValue != null && bValue != null) { + return ValueExpression.getBoolean(getValue(session, value, aValue, bValue)); + } + } + if (symmetric) { + if (aValue == ValueNull.INSTANCE || bValue == ValueNull.INSTANCE) { + return TypedValueExpression.UNKNOWN; + } + } else if (aValue == ValueNull.INSTANCE && bValue == ValueNull.INSTANCE) { + return TypedValueExpression.UNKNOWN; + } + if (aValue != null && bValue != null && session.compareWithNull(aValue, bValue, false) == 0) { + return new Comparison(not ? Comparison.NOT_EQUAL : Comparison.EQUAL, left, a, false).optimize(session); + } + return this; + } + + @Override + public Value getValue(SessionLocal session) { + Value value = left.getValue(session); + if (value == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + return getValue(session, value, a.getValue(session), b.getValue(session)); + } + + @Override + public boolean getWhenValue(SessionLocal session, Value left) { + if (!whenOperand) { + return super.getWhenValue(session, left); + } + if (left == ValueNull.INSTANCE) { + return false; + } + return getValue(session, left, a.getValue(session), b.getValue(session)).isTrue(); + } + + private Value getValue(SessionLocal session, Value value, Value aValue, Value bValue) { + int cmp1 = session.compareWithNull(aValue, value, false); + int cmp2 = session.compareWithNull(value, bValue, false); + if (cmp1 == Integer.MIN_VALUE) { + return symmetric || cmp2 <= 0 ? ValueNull.INSTANCE : ValueBoolean.get(not); + } else if (cmp2 == Integer.MIN_VALUE) { + return symmetric || cmp1 <= 0 ? ValueNull.INSTANCE : ValueBoolean.get(not); + } else { + return ValueBoolean.get(not ^ // + (symmetric ? cmp1 <= 0 && cmp2 <= 0 || cmp1 >= 0 && cmp2 >= 0 : cmp1 <= 0 && cmp2 <= 0)); + } + } + + @Override + public boolean isWhenConditionOperand() { + return whenOperand; + } + + @Override + public Expression getNotIfPossible(SessionLocal session) { + if (whenOperand) { + return null; + } + return new BetweenPredicate(left, !not, false, symmetric, a, b); + } + + @Override + public void createIndexConditions(SessionLocal session, TableFilter filter) { + if (!not && !whenOperand && !symmetric) { + Comparison.createIndexConditions(filter, a, left, Comparison.SMALLER_EQUAL); + Comparison.createIndexConditions(filter, left, b, Comparison.SMALLER_EQUAL); + } + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean value) { + left.setEvaluatable(tableFilter, value); + a.setEvaluatable(tableFilter, value); + b.setEvaluatable(tableFilter, value); + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + left.updateAggregate(session, stage); + a.updateAggregate(session, stage); + b.updateAggregate(session, stage); + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + left.mapColumns(resolver, level, state); + a.mapColumns(resolver, level, state); + b.mapColumns(resolver, level, state); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return left.isEverything(visitor) && a.isEverything(visitor) && b.isEverything(visitor); + } + + @Override + public int getCost() { + return left.getCost() + a.getCost() + b.getCost() + 1; + } + + @Override + public int getSubexpressionCount() { + return 3; + } + + @Override + public Expression getSubexpression(int index) { + switch (index) { + case 0: + return left; + case 1: + return a; + case 2: + return b; + default: + throw new IndexOutOfBoundsException(); + } + } + +} diff --git a/h2/src/main/org/h2/expression/condition/BooleanTest.java b/h2/src/main/org/h2/expression/condition/BooleanTest.java new file mode 100644 index 0000000..47a0774 --- /dev/null +++ b/h2/src/main/org/h2/expression/condition/BooleanTest.java @@ -0,0 +1,91 @@ +/* + * 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.expression.condition; + +import java.util.ArrayList; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.expression.TypedValueExpression; +import org.h2.expression.ValueExpression; +import org.h2.index.IndexCondition; +import org.h2.table.TableFilter; +import org.h2.value.Value; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueNull; + +/** + * Boolean test (IS [NOT] { TRUE | FALSE | UNKNOWN }). + */ +public final class BooleanTest extends SimplePredicate { + + private final Boolean right; + + public BooleanTest(Expression left, boolean not, boolean whenOperand, Boolean right) { + super(left, not, whenOperand); + this.right = right; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return getWhenSQL(left.getSQL(builder, sqlFlags, AUTO_PARENTHESES), sqlFlags); + } + + @Override + public StringBuilder getWhenSQL(StringBuilder builder, int sqlFlags) { + return builder.append(not ? " IS NOT " : " IS ").append(right == null ? "UNKNOWN" : right ? "TRUE" : "FALSE"); + } + + @Override + public Value getValue(SessionLocal session) { + return ValueBoolean.get(getValue(left.getValue(session))); + } + + @Override + public boolean getWhenValue(SessionLocal session, Value left) { + if (!whenOperand) { + return super.getWhenValue(session, left); + } + return getValue(left); + } + + private boolean getValue(Value left) { + return (left == ValueNull.INSTANCE ? right == null : right != null && right == left.getBoolean()) ^ not; + } + + @Override + public Expression getNotIfPossible(SessionLocal session) { + if (whenOperand) { + return null; + } + return new BooleanTest(left, !not, false, right); + } + + @Override + public void createIndexConditions(SessionLocal session, TableFilter filter) { + if (whenOperand || !filter.getTable().isQueryComparable()) { + return; + } + if (left instanceof ExpressionColumn) { + ExpressionColumn c = (ExpressionColumn) left; + if (c.getType().getValueType() == Value.BOOLEAN && filter == c.getTableFilter()) { + if (not) { + if (right == null && c.getColumn().isNullable()) { + ArrayList list = new ArrayList<>(2); + list.add(ValueExpression.FALSE); + list.add(ValueExpression.TRUE); + filter.addIndexCondition(IndexCondition.getInList(c, list)); + } + } else { + filter.addIndexCondition(IndexCondition.get(Comparison.EQUAL_NULL_SAFE, c, + right == null ? TypedValueExpression.UNKNOWN : ValueExpression.getBoolean(right))); + } + } + } + } + +} diff --git a/h2/src/main/org/h2/expression/condition/CompareLike.java b/h2/src/main/org/h2/expression/condition/CompareLike.java new file mode 100644 index 0000000..e62dbaa --- /dev/null +++ b/h2/src/main/org/h2/expression/condition/CompareLike.java @@ -0,0 +1,634 @@ +/* + * 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.expression.condition; + +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import org.h2.api.ErrorCode; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.SearchedCase; +import org.h2.expression.TypedValueExpression; +import org.h2.expression.ValueExpression; +import org.h2.index.IndexCondition; +import org.h2.message.DbException; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.value.CompareMode; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueNull; +import org.h2.value.ValueVarchar; +import org.h2.value.ValueVarcharIgnoreCase; + +/** + * Pattern matching comparison expression: WHERE NAME LIKE ? + */ +public final class CompareLike extends Condition { + + /** + * The type of comparison. + */ + public enum LikeType { + /** + * LIKE. + */ + LIKE, + + /** + * ILIKE (case-insensitive LIKE). + */ + ILIKE, + + /** + * REGEXP + */ + REGEXP + } + + private static final int MATCH = 0, ONE = 1, ANY = 2; + + private final CompareMode compareMode; + private final String defaultEscape; + + private final LikeType likeType; + private Expression left; + + private final boolean not; + + private final boolean whenOperand; + + private Expression right; + private Expression escape; + + private boolean isInit; + + private char[] patternChars; + private String patternString; + /** one of MATCH / ONE / ANY */ + private int[] patternTypes; + private int patternLength; + + private Pattern patternRegexp; + + private boolean ignoreCase; + private boolean fastCompare; + private boolean invalidPattern; + /** indicates that we can shortcut the comparison and use startsWith */ + private boolean shortcutToStartsWith; + /** indicates that we can shortcut the comparison and use endsWith */ + private boolean shortcutToEndsWith; + /** indicates that we can shortcut the comparison and use contains */ + private boolean shortcutToContains; + + public CompareLike(Database db, Expression left, boolean not, boolean whenOperand, Expression right, + Expression escape, LikeType likeType) { + this(db.getCompareMode(), db.getSettings().defaultEscape, left, not, whenOperand, right, escape, likeType); + } + + public CompareLike(CompareMode compareMode, String defaultEscape, Expression left, boolean not, + boolean whenOperand, Expression right, Expression escape, LikeType likeType) { + this.compareMode = compareMode; + this.defaultEscape = defaultEscape; + this.likeType = likeType; + this.left = left; + this.not = not; + this.whenOperand = whenOperand; + this.right = right; + this.escape = escape; + } + + private static Character getEscapeChar(String s) { + return s == null || s.isEmpty() ? null : s.charAt(0); + } + + @Override + public boolean needParentheses() { + return true; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return getWhenSQL(left.getSQL(builder, sqlFlags, AUTO_PARENTHESES), sqlFlags); + } + + @Override + public StringBuilder getWhenSQL(StringBuilder builder, int sqlFlags) { + if (not) { + builder.append(" NOT"); + } + switch (likeType) { + case LIKE: + case ILIKE: + builder.append(likeType == LikeType.LIKE ? " LIKE " : " ILIKE "); + right.getSQL(builder, sqlFlags, AUTO_PARENTHESES); + if (escape != null) { + escape.getSQL(builder.append(" ESCAPE "), sqlFlags, AUTO_PARENTHESES); + } + break; + case REGEXP: + builder.append(" REGEXP "); + right.getSQL(builder, sqlFlags, AUTO_PARENTHESES); + break; + default: + throw DbException.getUnsupportedException(likeType.name()); + } + return builder; + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + right = right.optimize(session); + if (likeType == LikeType.ILIKE || left.getType().getValueType() == Value.VARCHAR_IGNORECASE) { + ignoreCase = true; + } + if (escape != null) { + escape = escape.optimize(session); + } + if (whenOperand) { + return this; + } + if (left.isValueSet()) { + Value l = left.getValue(session); + if (l == ValueNull.INSTANCE) { + // NULL LIKE something > NULL + return TypedValueExpression.UNKNOWN; + } + } + if (right.isValueSet() && (escape == null || escape.isValueSet())) { + if (left.isValueSet()) { + return ValueExpression.getBoolean(getValue(session)); + } + Value r = right.getValue(session); + if (r == ValueNull.INSTANCE) { + // something LIKE NULL > NULL + return TypedValueExpression.UNKNOWN; + } + Value e = escape == null ? null : escape.getValue(session); + if (e == ValueNull.INSTANCE) { + return TypedValueExpression.UNKNOWN; + } + String p = r.getString(); + initPattern(p, getEscapeChar(e)); + if (invalidPattern) { + return TypedValueExpression.UNKNOWN; + } + if (likeType != LikeType.REGEXP && "%".equals(p)) { + // optimization for X LIKE '%' + return new SearchedCase(new Expression[] { new NullPredicate(left, true, false), + ValueExpression.getBoolean(!not), TypedValueExpression.UNKNOWN }).optimize(session); + } + if (isFullMatch()) { + // optimization for X LIKE 'Hello': convert to X = 'Hello' + Value value = ignoreCase ? ValueVarcharIgnoreCase.get(patternString) : ValueVarchar.get(patternString); + Expression expr = ValueExpression.get(value); + return new Comparison(not ? Comparison.NOT_EQUAL : Comparison.EQUAL, left, expr, false) + .optimize(session); + } + isInit = true; + } + return this; + } + + private Character getEscapeChar(Value e) { + if (e == null) { + return getEscapeChar(defaultEscape); + } + String es = e.getString(); + Character esc; + if (es == null) { + esc = getEscapeChar(defaultEscape); + } else if (es.length() == 0) { + esc = null; + } else if (es.length() > 1) { + throw DbException.get(ErrorCode.LIKE_ESCAPE_ERROR_1, es); + } else { + esc = es.charAt(0); + } + return esc; + } + + @Override + public void createIndexConditions(SessionLocal session, TableFilter filter) { + if (not || whenOperand || likeType == LikeType.REGEXP || !(left instanceof ExpressionColumn)) { + return; + } + ExpressionColumn l = (ExpressionColumn) left; + if (filter != l.getTableFilter() || !TypeInfo.haveSameOrdering(l.getType(), + ignoreCase ? TypeInfo.TYPE_VARCHAR_IGNORECASE : TypeInfo.TYPE_VARCHAR)) { + return; + } + // parameters are always evaluatable, but + // we need to check if the value is set + // (at prepare time) + // otherwise we would need to prepare at execute time, + // which may be slower (possibly not in this case) + if (!right.isEverything(ExpressionVisitor.INDEPENDENT_VISITOR)) { + return; + } + if (escape != null && !escape.isEverything(ExpressionVisitor.INDEPENDENT_VISITOR)) { + return; + } + String p = right.getValue(session).getString(); + if (!isInit) { + Value e = escape == null ? null : escape.getValue(session); + if (e == ValueNull.INSTANCE) { + // should already be optimized + throw DbException.getInternalError(); + } + initPattern(p, getEscapeChar(e)); + } + if (invalidPattern) { + return; + } + if (patternLength <= 0 || patternTypes[0] != MATCH) { + // can't use an index + return; + } + if (!DataType.isStringType(l.getColumn().getType().getValueType())) { + // column is not a varchar - can't use the index + return; + } + // Get the MATCH prefix and see if we can create an index condition from + // that. + int maxMatch = 0; + StringBuilder buff = new StringBuilder(); + while (maxMatch < patternLength && patternTypes[maxMatch] == MATCH) { + buff.append(patternChars[maxMatch++]); + } + String begin = buff.toString(); + if (maxMatch == patternLength) { + filter.addIndexCondition(IndexCondition.get(Comparison.EQUAL, l, + ValueExpression.get(ValueVarchar.get(begin)))); + } else { + // TODO check if this is correct according to Unicode rules + // (code points) + String end; + if (begin.length() > 0) { + filter.addIndexCondition(IndexCondition.get( + Comparison.BIGGER_EQUAL, l, + ValueExpression.get(ValueVarchar.get(begin)))); + char next = begin.charAt(begin.length() - 1); + // search the 'next' unicode character (or at least a character + // that is higher) + for (int i = 1; i < 2000; i++) { + end = begin.substring(0, begin.length() - 1) + (char) (next + i); + if (compareMode.compareString(begin, end, ignoreCase) < 0) { + filter.addIndexCondition(IndexCondition.get( + Comparison.SMALLER, l, + ValueExpression.get(ValueVarchar.get(end)))); + break; + } + } + } + } + } + + @Override + public Value getValue(SessionLocal session) { + return getValue(session, left.getValue(session)); + } + + @Override + public boolean getWhenValue(SessionLocal session, Value left) { + if (!whenOperand) { + return super.getWhenValue(session, left); + } + return getValue(session, left).isTrue(); + } + + private Value getValue(SessionLocal session, Value left) { + if (left == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + if (!isInit) { + Value r = right.getValue(session); + if (r == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + String p = r.getString(); + Value e = escape == null ? null : escape.getValue(session); + if (e == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + initPattern(p, getEscapeChar(e)); + } + if (invalidPattern) { + return ValueNull.INSTANCE; + } + String value = left.getString(); + boolean result; + if (likeType == LikeType.REGEXP) { + result = patternRegexp.matcher(value).find(); + } else if (shortcutToStartsWith) { + result = value.regionMatches(ignoreCase, 0, patternString, 0, patternLength - 1); + } else if (shortcutToEndsWith) { + result = value.regionMatches(ignoreCase, value.length() - + patternLength + 1, patternString, 1, patternLength - 1); + } else if (shortcutToContains) { + String p = patternString.substring(1, patternString.length() - 1); + if (ignoreCase) { + result = containsIgnoreCase(value, p); + } else { + result = value.contains(p); + } + } else { + result = compareAt(value, 0, 0, value.length(), patternChars, patternTypes); + } + return ValueBoolean.get(not ^ result); + } + + private static boolean containsIgnoreCase(String src, String what) { + final int length = what.length(); + if (length == 0) { + // Empty string is contained + return true; + } + + final char firstLo = Character.toLowerCase(what.charAt(0)); + final char firstUp = Character.toUpperCase(what.charAt(0)); + + for (int i = src.length() - length; i >= 0; i--) { + // Quick check before calling the more expensive regionMatches() + final char ch = src.charAt(i); + if (ch != firstLo && ch != firstUp) { + continue; + } + if (src.regionMatches(true, i, what, 0, length)) { + return true; + } + } + + return false; + } + + private boolean compareAt(String s, int pi, int si, int sLen, + char[] pattern, int[] types) { + for (; pi < patternLength; pi++) { + switch (types[pi]) { + case MATCH: + if ((si >= sLen) || !compare(pattern, s, pi, si++)) { + return false; + } + break; + case ONE: + if (si++ >= sLen) { + return false; + } + break; + case ANY: + if (++pi >= patternLength) { + return true; + } + while (si < sLen) { + if (compare(pattern, s, pi, si) && + compareAt(s, pi, si, sLen, pattern, types)) { + return true; + } + si++; + } + return false; + default: + throw DbException.getInternalError(Integer.toString(types[pi])); + } + } + return si == sLen; + } + + private boolean compare(char[] pattern, String s, int pi, int si) { + return pattern[pi] == s.charAt(si) || + (!fastCompare && compareMode.equalsChars(patternString, pi, s, + si, ignoreCase)); + } + + @Override + public boolean isWhenConditionOperand() { + return whenOperand; + } + + /** + * Test if the value matches the pattern. + * + * @param testPattern the pattern + * @param value the value + * @param escapeChar the escape character + * @return true if the value matches + */ + public boolean test(String testPattern, String value, char escapeChar) { + initPattern(testPattern, escapeChar); + return test(value); + } + + /** + * Test if the value matches the initialized pattern. + * + * @param value the value + * @return true if the value matches + */ + public boolean test(String value) { + if (invalidPattern) { + return false; + } + return compareAt(value, 0, 0, value.length(), patternChars, patternTypes); + } + + /** + * Initializes the pattern. + * + * @param p the pattern + * @param escapeChar the escape character + */ + public void initPattern(String p, Character escapeChar) { + if (compareMode.getName().equals(CompareMode.OFF) && !ignoreCase) { + fastCompare = true; + } + if (likeType == LikeType.REGEXP) { + patternString = p; + try { + if (ignoreCase) { + patternRegexp = Pattern.compile(p, Pattern.CASE_INSENSITIVE); + } else { + patternRegexp = Pattern.compile(p); + } + } catch (PatternSyntaxException e) { + throw DbException.get(ErrorCode.LIKE_ESCAPE_ERROR_1, e, p); + } + return; + } + patternLength = 0; + if (p == null) { + patternTypes = null; + patternChars = null; + return; + } + int len = p.length(); + patternChars = new char[len]; + patternTypes = new int[len]; + boolean lastAny = false; + for (int i = 0; i < len; i++) { + char c = p.charAt(i); + int type; + if (escapeChar != null && escapeChar == c) { + if (i >= len - 1) { + invalidPattern = true; + return; + } + c = p.charAt(++i); + type = MATCH; + lastAny = false; + } else if (c == '%') { + if (lastAny) { + continue; + } + type = ANY; + lastAny = true; + } else if (c == '_') { + type = ONE; + } else { + type = MATCH; + lastAny = false; + } + patternTypes[patternLength] = type; + patternChars[patternLength++] = c; + } + for (int i = 0; i < patternLength - 1; i++) { + if ((patternTypes[i] == ANY) && (patternTypes[i + 1] == ONE)) { + patternTypes[i] = ONE; + patternTypes[i + 1] = ANY; + } + } + patternString = new String(patternChars, 0, patternLength); + + // Clear optimizations + shortcutToStartsWith = false; + shortcutToEndsWith = false; + shortcutToContains = false; + + // optimizes the common case of LIKE 'foo%' + if (compareMode.getName().equals(CompareMode.OFF) && patternLength > 1) { + int maxMatch = 0; + while (maxMatch < patternLength && patternTypes[maxMatch] == MATCH) { + maxMatch++; + } + if (maxMatch == patternLength - 1 && patternTypes[patternLength - 1] == ANY) { + shortcutToStartsWith = true; + return; + } + } + // optimizes the common case of LIKE '%foo' + if (compareMode.getName().equals(CompareMode.OFF) && patternLength > 1) { + if (patternTypes[0] == ANY) { + int maxMatch = 1; + while (maxMatch < patternLength && patternTypes[maxMatch] == MATCH) { + maxMatch++; + } + if (maxMatch == patternLength) { + shortcutToEndsWith = true; + return; + } + } + } + // optimizes the common case of LIKE '%foo%' + if (compareMode.getName().equals(CompareMode.OFF) && patternLength > 2) { + if (patternTypes[0] == ANY) { + int maxMatch = 1; + while (maxMatch < patternLength && patternTypes[maxMatch] == MATCH) { + maxMatch++; + } + if (maxMatch == patternLength - 1 && patternTypes[patternLength - 1] == ANY) { + shortcutToContains = true; + } + } + } + } + + private boolean isFullMatch() { + if (patternTypes == null) { + return false; + } + for (int type : patternTypes) { + if (type != MATCH) { + return false; + } + } + return true; + } + + @Override + public Expression getNotIfPossible(SessionLocal session) { + if (whenOperand) { + return null; + } + return new CompareLike(compareMode, defaultEscape, left, !not, false, right, escape, likeType); + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + left.mapColumns(resolver, level, state); + right.mapColumns(resolver, level, state); + if (escape != null) { + escape.mapColumns(resolver, level, state); + } + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + left.setEvaluatable(tableFilter, b); + right.setEvaluatable(tableFilter, b); + if (escape != null) { + escape.setEvaluatable(tableFilter, b); + } + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + left.updateAggregate(session, stage); + right.updateAggregate(session, stage); + if (escape != null) { + escape.updateAggregate(session, stage); + } + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return left.isEverything(visitor) && right.isEverything(visitor) + && (escape == null || escape.isEverything(visitor)); + } + + @Override + public int getCost() { + return left.getCost() + right.getCost() + 3; + } + + @Override + public int getSubexpressionCount() { + return escape == null ? 2 : 3; + } + + @Override + public Expression getSubexpression(int index) { + switch (index) { + case 0: + return left; + case 1: + return right; + case 2: + if (escape != null) { + return escape; + } + //$FALL-THROUGH$ + default: + throw new IndexOutOfBoundsException(); + } + } + +} diff --git a/h2/src/main/org/h2/expression/condition/Comparison.java b/h2/src/main/org/h2/expression/condition/Comparison.java new file mode 100644 index 0000000..666f406 --- /dev/null +++ b/h2/src/main/org/h2/expression/condition/Comparison.java @@ -0,0 +1,599 @@ +/* + * 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.expression.condition; + +import java.util.ArrayList; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.Parameter; +import org.h2.expression.TypedValueExpression; +import org.h2.expression.ValueExpression; +import org.h2.expression.aggregate.Aggregate; +import org.h2.expression.aggregate.AggregateType; +import org.h2.index.IndexCondition; +import org.h2.message.DbException; +import org.h2.table.Column; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueNull; + +/** + * Example comparison expressions are ID=1, NAME=NAME, NAME IS NULL. + * + * @author Thomas Mueller + * @author Noel Grandin + * @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888 + */ +public final class Comparison extends Condition { + + /** + * The comparison type meaning = as in ID=1. + */ + public static final int EQUAL = 0; + + /** + * The comparison type meaning <> as in ID<>1. + */ + public static final int NOT_EQUAL = 1; + + /** + * The comparison type meaning < as in ID<1. + */ + public static final int SMALLER = 2; + + /** + * The comparison type meaning > as in ID>1. + */ + public static final int BIGGER = 3; + + /** + * The comparison type meaning <= as in ID<=1. + */ + public static final int SMALLER_EQUAL = 4; + + /** + * The comparison type meaning >= as in ID>=1. + */ + public static final int BIGGER_EQUAL = 5; + + /** + * The comparison type meaning ID IS NOT DISTINCT FROM 1. + */ + public static final int EQUAL_NULL_SAFE = 6; + + /** + * The comparison type meaning ID IS DISTINCT FROM 1. + */ + public static final int NOT_EQUAL_NULL_SAFE = 7; + + /** + * This is a comparison type that is only used for spatial index + * conditions (operator "&&"). + */ + public static final int SPATIAL_INTERSECTS = 8; + + static final String[] COMPARE_TYPES = { "=", "<>", "<", ">", "<=", ">=", // + "IS NOT DISTINCT FROM", "IS DISTINCT FROM", // + "&&" }; + + /** + * This is a pseudo comparison type that is only used for index conditions. + * It means the comparison will always yield FALSE. Example: 1=0. + */ + public static final int FALSE = 9; + + /** + * This is a pseudo comparison type that is only used for index conditions. + * It means equals any value of a list. Example: IN(1, 2, 3). + */ + public static final int IN_LIST = 10; + + /** + * This is a pseudo comparison type that is only used for index conditions. + * It means equals any value of a list. Example: IN(SELECT ...). + */ + public static final int IN_QUERY = 11; + + private int compareType; + private Expression left; + private Expression right; + private final boolean whenOperand; + + public Comparison(int compareType, Expression left, Expression right, boolean whenOperand) { + this.left = left; + this.right = right; + this.compareType = compareType; + this.whenOperand = whenOperand; + } + + @Override + public boolean needParentheses() { + return true; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return getWhenSQL(left.getSQL(builder, sqlFlags, AUTO_PARENTHESES), sqlFlags); + } + + @Override + public StringBuilder getWhenSQL(StringBuilder builder, int sqlFlags) { + builder.append(' ').append(COMPARE_TYPES[compareType]).append(' '); + return right.getSQL(builder, sqlFlags, + right instanceof Aggregate && ((Aggregate) right).getAggregateType() == AggregateType.ANY + ? WITH_PARENTHESES + : AUTO_PARENTHESES); + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + right = right.optimize(session); + check: { + TypeInfo leftType = left.getType(), rightType = right.getType(); + if (session.getMode().numericWithBooleanComparison) { + switch (compareType) { + case EQUAL: + case NOT_EQUAL: + case EQUAL_NULL_SAFE: + case NOT_EQUAL_NULL_SAFE: + int lValueType = leftType.getValueType(); + if (lValueType == Value.BOOLEAN) { + if (DataType.isNumericType(rightType.getValueType())) { + break check; + } + } else if (DataType.isNumericType(lValueType) && rightType.getValueType() == Value.BOOLEAN) { + break check; + } + } + } + TypeInfo.checkComparable(leftType, rightType); + } + if (whenOperand) { + return this; + } + if (right instanceof ExpressionColumn) { + if (left.isConstant() || left instanceof Parameter) { + Expression temp = left; + left = right; + right = temp; + compareType = getReversedCompareType(compareType); + } + } + if (left instanceof ExpressionColumn) { + if (right.isConstant()) { + Value r = right.getValue(session); + if (r == ValueNull.INSTANCE) { + if ((compareType & ~1) != EQUAL_NULL_SAFE) { + return TypedValueExpression.UNKNOWN; + } + } + TypeInfo colType = left.getType(), constType = r.getType(); + int constValueType = constType.getValueType(); + if (constValueType != colType.getValueType() || constValueType >= Value.ARRAY) { + TypeInfo resType = TypeInfo.getHigherType(colType, constType); + // If not, the column values will need to be promoted + // to constant type, but vise versa, then let's do this here + // once. + if (constValueType != resType.getValueType() || constValueType >= Value.ARRAY) { + Column column = ((ExpressionColumn) left).getColumn(); + right = ValueExpression.get(r.convertTo(resType, session, column)); + } + } + } else if (right instanceof Parameter) { + ((Parameter) right).setColumn(((ExpressionColumn) left).getColumn()); + } + } + if (left.isConstant() && right.isConstant()) { + return ValueExpression.getBoolean(getValue(session)); + } + if (left.isNullConstant() || right.isNullConstant()) { + // TODO NULL handling: maybe issue a warning when comparing with + // a NULL constants + if ((compareType & ~1) != EQUAL_NULL_SAFE) { + return TypedValueExpression.UNKNOWN; + } else { + Expression e = left.isNullConstant() ? right : left; + int type = e.getType().getValueType(); + if (type != Value.UNKNOWN && type != Value.ROW) { + return new NullPredicate(e, compareType == NOT_EQUAL_NULL_SAFE, false); + } + } + } + return this; + } + + @Override + public Value getValue(SessionLocal session) { + Value l = left.getValue(session); + // Optimization: do not evaluate right if not necessary + if (l == ValueNull.INSTANCE && (compareType & ~1) != EQUAL_NULL_SAFE) { + return ValueNull.INSTANCE; + } + return compare(session, l, right.getValue(session), compareType); + } + + @Override + public boolean getWhenValue(SessionLocal session, Value left) { + if (!whenOperand) { + return super.getWhenValue(session, left); + } + // Optimization: do not evaluate right if not necessary + if (left == ValueNull.INSTANCE && (compareType & ~1) != EQUAL_NULL_SAFE) { + return false; + } + return compare(session, left, right.getValue(session), compareType).isTrue(); + } + + /** + * Compare two values. + * + * @param session the session + * @param l the first value + * @param r the second value + * @param compareType the compare type + * @return result of comparison, either TRUE, FALSE, or NULL + */ + static Value compare(SessionLocal session, Value l, Value r, int compareType) { + Value result; + switch (compareType) { + case EQUAL: { + int cmp = session.compareWithNull(l, r, true); + if (cmp == 0) { + result = ValueBoolean.TRUE; + } else if (cmp == Integer.MIN_VALUE) { + result = ValueNull.INSTANCE; + } else { + result = ValueBoolean.FALSE; + } + break; + } + case EQUAL_NULL_SAFE: + result = ValueBoolean.get(session.areEqual(l, r)); + break; + case NOT_EQUAL: { + int cmp = session.compareWithNull(l, r, true); + if (cmp == 0) { + result = ValueBoolean.FALSE; + } else if (cmp == Integer.MIN_VALUE) { + result = ValueNull.INSTANCE; + } else { + result = ValueBoolean.TRUE; + } + break; + } + case NOT_EQUAL_NULL_SAFE: + result = ValueBoolean.get(!session.areEqual(l, r)); + break; + case BIGGER_EQUAL: { + int cmp = session.compareWithNull(l, r, false); + if (cmp >= 0) { + result = ValueBoolean.TRUE; + } else if (cmp == Integer.MIN_VALUE) { + result = ValueNull.INSTANCE; + } else { + result = ValueBoolean.FALSE; + } + break; + } + case BIGGER: { + int cmp = session.compareWithNull(l, r, false); + if (cmp > 0) { + result = ValueBoolean.TRUE; + } else if (cmp == Integer.MIN_VALUE) { + result = ValueNull.INSTANCE; + } else { + result = ValueBoolean.FALSE; + } + break; + } + case SMALLER_EQUAL: { + int cmp = session.compareWithNull(l, r, false); + if (cmp == Integer.MIN_VALUE) { + result = ValueNull.INSTANCE; + } else { + result = ValueBoolean.get(cmp <= 0); + } + break; + } + case SMALLER: { + int cmp = session.compareWithNull(l, r, false); + if (cmp == Integer.MIN_VALUE) { + result = ValueNull.INSTANCE; + } else { + result = ValueBoolean.get(cmp < 0); + } + break; + } + case SPATIAL_INTERSECTS: { + if (l == ValueNull.INSTANCE || r == ValueNull.INSTANCE) { + result = ValueNull.INSTANCE; + } else { + result = ValueBoolean.get(l.convertToGeometry(null).intersectsBoundingBox(r.convertToGeometry(null))); + } + break; + } + default: + throw DbException.getInternalError("type=" + compareType); + } + return result; + } + + @Override + public boolean isWhenConditionOperand() { + return whenOperand; + } + + private static int getReversedCompareType(int type) { + switch (type) { + case EQUAL: + case EQUAL_NULL_SAFE: + case NOT_EQUAL: + case NOT_EQUAL_NULL_SAFE: + case SPATIAL_INTERSECTS: + return type; + case BIGGER_EQUAL: + return SMALLER_EQUAL; + case BIGGER: + return SMALLER; + case SMALLER_EQUAL: + return BIGGER_EQUAL; + case SMALLER: + return BIGGER; + default: + throw DbException.getInternalError("type=" + type); + } + } + + @Override + public Expression getNotIfPossible(SessionLocal session) { + if (compareType == SPATIAL_INTERSECTS || whenOperand) { + return null; + } + int type = getNotCompareType(); + return new Comparison(type, left, right, false); + } + + private int getNotCompareType() { + switch (compareType) { + case EQUAL: + return NOT_EQUAL; + case EQUAL_NULL_SAFE: + return NOT_EQUAL_NULL_SAFE; + case NOT_EQUAL: + return EQUAL; + case NOT_EQUAL_NULL_SAFE: + return EQUAL_NULL_SAFE; + case BIGGER_EQUAL: + return SMALLER; + case BIGGER: + return SMALLER_EQUAL; + case SMALLER_EQUAL: + return BIGGER; + case SMALLER: + return BIGGER_EQUAL; + default: + throw DbException.getInternalError("type=" + compareType); + } + } + + @Override + public void createIndexConditions(SessionLocal session, TableFilter filter) { + if (!whenOperand) { + createIndexConditions(filter, left, right, compareType); + } + } + + static void createIndexConditions(TableFilter filter, Expression left, Expression right, int compareType) { + if (!filter.getTable().isQueryComparable()) { + return; + } + ExpressionColumn l = null; + if (left instanceof ExpressionColumn) { + l = (ExpressionColumn) left; + if (filter != l.getTableFilter()) { + l = null; + } + } + ExpressionColumn r = null; + if (right instanceof ExpressionColumn) { + r = (ExpressionColumn) right; + if (filter != r.getTableFilter()) { + r = null; + } + } + // one side must be from the current filter + if ((l == null) == (r == null)) { + return; + } + if (l == null) { + if (!left.isEverything(ExpressionVisitor.getNotFromResolverVisitor(filter))) { + return; + } + } else { // r == null + if (!right.isEverything(ExpressionVisitor.getNotFromResolverVisitor(filter))) { + return; + } + } + switch (compareType) { + case NOT_EQUAL: + case NOT_EQUAL_NULL_SAFE: + break; + case EQUAL: + case EQUAL_NULL_SAFE: + case BIGGER: + case BIGGER_EQUAL: + case SMALLER_EQUAL: + case SMALLER: + case SPATIAL_INTERSECTS: + if (l != null) { + TypeInfo colType = l.getType(); + if (TypeInfo.haveSameOrdering(colType, TypeInfo.getHigherType(colType, right.getType()))) { + filter.addIndexCondition(IndexCondition.get(compareType, l, right)); + } + } else { + @SuppressWarnings("null") + TypeInfo colType = r.getType(); + if (TypeInfo.haveSameOrdering(colType, TypeInfo.getHigherType(colType, left.getType()))) { + filter.addIndexCondition(IndexCondition.get(getReversedCompareType(compareType), r, left)); + } + } + break; + default: + throw DbException.getInternalError("type=" + compareType); + } + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + left.setEvaluatable(tableFilter, b); + if (right != null) { + right.setEvaluatable(tableFilter, b); + } + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + left.updateAggregate(session, stage); + if (right != null) { + right.updateAggregate(session, stage); + } + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + left.mapColumns(resolver, level, state); + right.mapColumns(resolver, level, state); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return left.isEverything(visitor) && right.isEverything(visitor); + } + + @Override + public int getCost() { + return left.getCost() + right.getCost() + 1; + } + + /** + * Get the other expression if this is an equals comparison and the other + * expression matches. + * + * @param match the expression that should match + * @return null if no match, the other expression if there is a match + */ + Expression getIfEquals(Expression match) { + if (compareType == EQUAL) { + String sql = match.getSQL(DEFAULT_SQL_FLAGS); + if (left.getSQL(DEFAULT_SQL_FLAGS).equals(sql)) { + return right; + } else if (right.getSQL(DEFAULT_SQL_FLAGS).equals(sql)) { + return left; + } + } + return null; + } + + /** + * Get an additional condition if possible. Example: given two conditions + * A=B AND B=C, the new condition A=C is returned. + * + * @param session the session + * @param other the second condition + * @return null or the third condition for indexes + */ + Expression getAdditionalAnd(SessionLocal session, Comparison other) { + if (compareType == EQUAL && other.compareType == EQUAL && !whenOperand) { + boolean lc = left.isConstant(); + boolean rc = right.isConstant(); + boolean l2c = other.left.isConstant(); + boolean r2c = other.right.isConstant(); + String l = left.getSQL(DEFAULT_SQL_FLAGS); + String l2 = other.left.getSQL(DEFAULT_SQL_FLAGS); + String r = right.getSQL(DEFAULT_SQL_FLAGS); + String r2 = other.right.getSQL(DEFAULT_SQL_FLAGS); + // a=b AND a=c + // must not compare constants. example: NOT(B=2 AND B=3) + if (!(rc && r2c) && l.equals(l2)) { + return new Comparison(EQUAL, right, other.right, false); + } else if (!(rc && l2c) && l.equals(r2)) { + return new Comparison(EQUAL, right, other.left, false); + } else if (!(lc && r2c) && r.equals(l2)) { + return new Comparison(EQUAL, left, other.right, false); + } else if (!(lc && l2c) && r.equals(r2)) { + return new Comparison(EQUAL, left, other.left, false); + } + } + return null; + } + + /** + * Replace the OR condition with IN condition if possible. Example: given + * the two conditions A=1 OR A=2, the new condition A IN(1, 2) is returned. + * + * @param session the session + * @param other the second condition + * @return null or the joined IN condition + */ + Expression optimizeOr(SessionLocal session, Comparison other) { + if (compareType == EQUAL && other.compareType == EQUAL) { + Expression left2 = other.left; + Expression right2 = other.right; + String l2 = left2.getSQL(DEFAULT_SQL_FLAGS); + String r2 = right2.getSQL(DEFAULT_SQL_FLAGS); + if (left.isEverything(ExpressionVisitor.DETERMINISTIC_VISITOR)) { + String l = left.getSQL(DEFAULT_SQL_FLAGS); + if (l.equals(l2)) { + return getConditionIn(left, right, right2); + } else if (l.equals(r2)) { + return getConditionIn(left, right, left2); + } + } + if (right.isEverything(ExpressionVisitor.DETERMINISTIC_VISITOR)) { + String r = right.getSQL(DEFAULT_SQL_FLAGS); + if (r.equals(l2)) { + return getConditionIn(right, left, right2); + } else if (r.equals(r2)) { + return getConditionIn(right, left, left2); + } + } + } + return null; + } + + private static ConditionIn getConditionIn(Expression left, Expression value1, + Expression value2) { + ArrayList right = new ArrayList<>(2); + right.add(value1); + right.add(value2); + return new ConditionIn(left, false, false, right); + } + + @Override + public int getSubexpressionCount() { + return 2; + } + + @Override + public Expression getSubexpression(int index) { + switch (index) { + case 0: + return left; + case 1: + return right; + default: + throw new IndexOutOfBoundsException(); + } + } + +} diff --git a/h2/src/main/org/h2/expression/condition/Condition.java b/h2/src/main/org/h2/expression/condition/Condition.java new file mode 100644 index 0000000..ba3d509 --- /dev/null +++ b/h2/src/main/org/h2/expression/condition/Condition.java @@ -0,0 +1,38 @@ +/* + * 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.expression.condition; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.function.CastSpecification; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * Represents a condition returning a boolean value, or NULL. + */ +abstract class Condition extends Expression { + + /** + * Add a cast around the expression (if necessary) so that the type is boolean. + * + * @param session the session + * @param expression the expression + * @return the new expression + */ + static Expression castToBoolean(SessionLocal session, Expression expression) { + if (expression.getType().getValueType() == Value.BOOLEAN) { + return expression; + } + return new CastSpecification(expression, TypeInfo.TYPE_BOOLEAN); + } + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_BOOLEAN; + } + +} diff --git a/h2/src/main/org/h2/expression/condition/ConditionAndOr.java b/h2/src/main/org/h2/expression/condition/ConditionAndOr.java new file mode 100644 index 0000000..82dc4fb --- /dev/null +++ b/h2/src/main/org/h2/expression/condition/ConditionAndOr.java @@ -0,0 +1,367 @@ +/* + * 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.expression.condition; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.TypedValueExpression; +import org.h2.expression.ValueExpression; +import org.h2.message.DbException; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.value.Value; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueNull; + +/** + * An 'and' or 'or' condition as in WHERE ID=1 AND NAME=? + */ +public class ConditionAndOr extends Condition { + + /** + * The AND condition type as in ID=1 AND NAME='Hello'. + */ + public static final int AND = 0; + + /** + * The OR condition type as in ID=1 OR NAME='Hello'. + */ + public static final int OR = 1; + + private final int andOrType; + private Expression left, right; + + /** + * Additional condition for index only. + */ + private Expression added; + + public ConditionAndOr(int andOrType, Expression left, Expression right) { + if (left == null || right == null) { + throw DbException.getInternalError(left + " " + right); + } + this.andOrType = andOrType; + this.left = left; + this.right = right; + } + + int getAndOrType() { + return this.andOrType; + } + + @Override + public boolean needParentheses() { + return true; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + left.getSQL(builder, sqlFlags, AUTO_PARENTHESES); + switch (andOrType) { + case AND: + builder.append("\n AND "); + break; + case OR: + builder.append("\n OR "); + break; + default: + throw DbException.getInternalError("andOrType=" + andOrType); + } + return right.getSQL(builder, sqlFlags, AUTO_PARENTHESES); + } + + @Override + public void createIndexConditions(SessionLocal session, TableFilter filter) { + if (andOrType == AND) { + left.createIndexConditions(session, filter); + right.createIndexConditions(session, filter); + if (added != null) { + added.createIndexConditions(session, filter); + } + } + } + + @Override + public Expression getNotIfPossible(SessionLocal session) { + // (NOT (A OR B)): (NOT(A) AND NOT(B)) + // (NOT (A AND B)): (NOT(A) OR NOT(B)) + Expression l = left.getNotIfPossible(session); + if (l == null) { + l = new ConditionNot(left); + } + Expression r = right.getNotIfPossible(session); + if (r == null) { + r = new ConditionNot(right); + } + int reversed = andOrType == AND ? OR : AND; + return new ConditionAndOr(reversed, l, r); + } + + @Override + public Value getValue(SessionLocal session) { + Value l = left.getValue(session); + Value r; + switch (andOrType) { + case AND: { + if (l.isFalse() || (r = right.getValue(session)).isFalse()) { + return ValueBoolean.FALSE; + } + if (l == ValueNull.INSTANCE || r == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + return ValueBoolean.TRUE; + } + case OR: { + if (l.isTrue() || (r = right.getValue(session)).isTrue()) { + return ValueBoolean.TRUE; + } + if (l == ValueNull.INSTANCE || r == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + return ValueBoolean.FALSE; + } + default: + throw DbException.getInternalError("type=" + andOrType); + } + } + + @Override + public Expression optimize(SessionLocal session) { + // NULL handling: see wikipedia, + // http://www-cs-students.stanford.edu/~wlam/compsci/sqlnulls + left = left.optimize(session); + right = right.optimize(session); + int lc = left.getCost(), rc = right.getCost(); + if (rc < lc) { + Expression t = left; + left = right; + right = t; + } + switch (andOrType) { + case AND: + if (!session.getDatabase().getSettings().optimizeTwoEquals) { + break; + } + // this optimization does not work in the following case, + // but NOT is optimized before: + // CREATE TABLE TEST(A INT, B INT); + // INSERT INTO TEST VALUES(1, NULL); + // SELECT * FROM TEST WHERE NOT (B=A AND B=0); // no rows + // SELECT * FROM TEST WHERE NOT (B=A AND B=0 AND A=0); // 1, NULL + // try to add conditions (A=B AND B=1: add A=1) + if (left instanceof Comparison && right instanceof Comparison) { + // try to add conditions (A=B AND B=1: add A=1) + Expression added = ((Comparison) left).getAdditionalAnd(session, (Comparison) right); + if (added != null) { + this.added = added.optimize(session); + } + } + break; + case OR: + if (!session.getDatabase().getSettings().optimizeOr) { + break; + } + Expression reduced; + if (left instanceof Comparison && right instanceof Comparison) { + reduced = ((Comparison) left).optimizeOr(session, (Comparison) right); + } else if (left instanceof ConditionIn && right instanceof Comparison) { + reduced = ((ConditionIn) left).getAdditional((Comparison) right); + } else if (right instanceof ConditionIn && left instanceof Comparison) { + reduced = ((ConditionIn) right).getAdditional((Comparison) left); + } else if (left instanceof ConditionInConstantSet && right instanceof Comparison) { + reduced = ((ConditionInConstantSet) left).getAdditional(session, (Comparison) right); + } else if (right instanceof ConditionInConstantSet && left instanceof Comparison) { + reduced = ((ConditionInConstantSet) right).getAdditional(session, (Comparison) left); + } else if (left instanceof ConditionAndOr && right instanceof ConditionAndOr) { + reduced = optimizeConditionAndOr((ConditionAndOr)left, (ConditionAndOr)right); + } else { + // TODO optimization: convert .. OR .. to UNION if the cost is lower + break; + } + if (reduced != null) { + return reduced.optimize(session); + } + } + Expression e = optimizeIfConstant(session, andOrType, left, right); + if (e == null) { + return optimizeN(this); + } + if (e instanceof ConditionAndOr) { + return optimizeN((ConditionAndOr) e); + } + return e; + } + + private static Expression optimizeN(ConditionAndOr condition) { + if (condition.right instanceof ConditionAndOr) { + ConditionAndOr rightCondition = (ConditionAndOr) condition.right; + if (rightCondition.andOrType == condition.andOrType) { + return new ConditionAndOrN(condition.andOrType, condition.left, rightCondition.left, + rightCondition.right); + } + } + if (condition.right instanceof ConditionAndOrN) { + ConditionAndOrN rightCondition = (ConditionAndOrN) condition.right; + if (rightCondition.getAndOrType() == condition.andOrType) { + rightCondition.addFirst(condition.left); + return rightCondition; + } + } + return condition; + } + + /** + * Optimize the condition if at least one part is constant. + * + * @param session the session + * @param andOrType the type + * @param left the left part of the condition + * @param right the right part of the condition + * @return the optimized condition, or {@code null} if condition cannot be optimized + */ + static Expression optimizeIfConstant(SessionLocal session, int andOrType, Expression left, Expression right) { + if (!left.isConstant()) { + if (!right.isConstant()) { + return null; + } else { + return optimizeConstant(session, andOrType, right.getValue(session), left); + } + } + Value l = left.getValue(session); + if (!right.isConstant()) { + return optimizeConstant(session, andOrType, l, right); + } + Value r = right.getValue(session); + switch (andOrType) { + case AND: { + if (l.isFalse() || r.isFalse()) { + return ValueExpression.FALSE; + } + if (l == ValueNull.INSTANCE || r == ValueNull.INSTANCE) { + return TypedValueExpression.UNKNOWN; + } + return ValueExpression.TRUE; + } + case OR: { + if (l.isTrue() || r.isTrue()) { + return ValueExpression.TRUE; + } + if (l == ValueNull.INSTANCE || r == ValueNull.INSTANCE) { + return TypedValueExpression.UNKNOWN; + } + return ValueExpression.FALSE; + } + default: + throw DbException.getInternalError("type=" + andOrType); + } + } + + private static Expression optimizeConstant(SessionLocal session, int andOrType, Value l, Expression right) { + if (l != ValueNull.INSTANCE) { + switch (andOrType) { + case AND: + return l.getBoolean() ? castToBoolean(session, right) : ValueExpression.FALSE; + case OR: + return l.getBoolean() ? ValueExpression.TRUE : castToBoolean(session, right); + default: + throw DbException.getInternalError("type=" + andOrType); + } + } + return null; + } + + @Override + public void addFilterConditions(TableFilter filter) { + if (andOrType == AND) { + left.addFilterConditions(filter); + right.addFilterConditions(filter); + } else { + super.addFilterConditions(filter); + } + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + left.mapColumns(resolver, level, state); + right.mapColumns(resolver, level, state); + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + left.setEvaluatable(tableFilter, b); + right.setEvaluatable(tableFilter, b); + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + left.updateAggregate(session, stage); + right.updateAggregate(session, stage); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return left.isEverything(visitor) && right.isEverything(visitor); + } + + @Override + public int getCost() { + return left.getCost() + right.getCost(); + } + + @Override + public int getSubexpressionCount() { + return 2; + } + + @Override + public Expression getSubexpression(int index) { + switch (index) { + case 0: + return left; + case 1: + return right; + default: + throw new IndexOutOfBoundsException(); + } + } + + /** + * Optimize query according to the given condition. Example: + * (A AND B) OR (C AND B), the new condition B AND (A OR C) is returned + * + * @param left the session + * @param right the second condition + * @return null or the third condition + */ + static Expression optimizeConditionAndOr(ConditionAndOr left, ConditionAndOr right) { + if (left.andOrType != AND || right.andOrType != AND) { + return null; + } + Expression leftLeft = left.getSubexpression(0), leftRight = left.getSubexpression(1); + Expression rightLeft = right.getSubexpression(0), rightRight = right.getSubexpression(1); + String rightLeftSQL = rightLeft.getSQL(DEFAULT_SQL_FLAGS); + String rightRightSQL = rightRight.getSQL(DEFAULT_SQL_FLAGS); + if (leftLeft.isEverything(ExpressionVisitor.DETERMINISTIC_VISITOR)) { + String leftLeftSQL = leftLeft.getSQL(DEFAULT_SQL_FLAGS); + if (leftLeftSQL.equals(rightLeftSQL)) { + return new ConditionAndOr(AND, leftLeft, new ConditionAndOr(OR, leftRight, rightRight)); + } + if (leftLeftSQL.equals(rightRightSQL)) { + return new ConditionAndOr(AND, leftLeft, new ConditionAndOr(OR, leftRight, rightLeft)); + } + } + if (leftRight.isEverything(ExpressionVisitor.DETERMINISTIC_VISITOR)) { + String leftRightSQL = leftRight.getSQL(DEFAULT_SQL_FLAGS); + if (leftRightSQL.equals(rightLeftSQL)) { + return new ConditionAndOr(AND, leftRight, new ConditionAndOr(OR, leftLeft, rightRight)); + } else if (leftRightSQL.equals(rightRightSQL)) { + return new ConditionAndOr(AND, leftRight, new ConditionAndOr(OR, leftLeft, rightLeft)); + } + } + return null; + } +} diff --git a/h2/src/main/org/h2/expression/condition/ConditionAndOrN.java b/h2/src/main/org/h2/expression/condition/ConditionAndOrN.java new file mode 100644 index 0000000..51ed2b1 --- /dev/null +++ b/h2/src/main/org/h2/expression/condition/ConditionAndOrN.java @@ -0,0 +1,341 @@ +/* + * 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.expression.condition; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.message.DbException; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.value.Value; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueNull; + +/** + * An 'and' or 'or' condition as in WHERE ID=1 AND NAME=? with N operands. + * Mostly useful for optimisation and preventing stack overflow where generated + * SQL has tons of conditions. + */ +public class ConditionAndOrN extends Condition { + + private final int andOrType; + /** + * Use an ArrayDeque because we primarily insert at the front. + */ + private final List expressions; + + /** + * Additional conditions for index only. + */ + private List added; + + public ConditionAndOrN(int andOrType, Expression expr1, Expression expr2, Expression expr3) { + this.andOrType = andOrType; + this.expressions = new ArrayList<>(3); + expressions.add(expr1); + expressions.add(expr2); + expressions.add(expr3); + } + + public ConditionAndOrN(int andOrType, List expressions) { + this.andOrType = andOrType; + this.expressions = expressions; + } + + int getAndOrType() { + return andOrType; + } + + /** + * Add the expression at the beginning of the list. + * + * @param e the expression + */ + void addFirst(Expression e) { + expressions.add(0, e); + } + + @Override + public boolean needParentheses() { + return true; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + Iterator it = expressions.iterator(); + it.next().getSQL(builder, sqlFlags, AUTO_PARENTHESES); + while (it.hasNext()) { + switch (andOrType) { + case ConditionAndOr.AND: + builder.append("\n AND "); + break; + case ConditionAndOr.OR: + builder.append("\n OR "); + break; + default: + throw DbException.getInternalError("andOrType=" + andOrType); + } + it.next().getSQL(builder, sqlFlags, AUTO_PARENTHESES); + } + return builder; + } + + @Override + public void createIndexConditions(SessionLocal session, TableFilter filter) { + if (andOrType == ConditionAndOr.AND) { + for (Expression e : expressions) { + e.createIndexConditions(session, filter); + } + if (added != null) { + for (Expression e : added) { + e.createIndexConditions(session, filter); + } + } + } + } + + @Override + public Expression getNotIfPossible(SessionLocal session) { + // (NOT (A OR B)): (NOT(A) AND NOT(B)) + // (NOT (A AND B)): (NOT(A) OR NOT(B)) + final ArrayList newList = new ArrayList<>(expressions.size()); + for (Expression e : expressions) { + Expression l = e.getNotIfPossible(session); + if (l == null) { + l = new ConditionNot(e); + } + newList.add(l); + } + int reversed = andOrType == ConditionAndOr.AND ? ConditionAndOr.OR : ConditionAndOr.AND; + return new ConditionAndOrN(reversed, newList); + } + + @Override + public Value getValue(SessionLocal session) { + boolean hasNull = false; + switch (andOrType) { + case ConditionAndOr.AND: { + for (Expression e : expressions) { + Value v = e.getValue(session); + if (v == ValueNull.INSTANCE) { + hasNull = true; + } else if (!v.getBoolean()) { + return ValueBoolean.FALSE; + } + } + return hasNull ? ValueNull.INSTANCE : ValueBoolean.TRUE; + } + case ConditionAndOr.OR: { + for (Expression e : expressions) { + Value v = e.getValue(session); + if (v == ValueNull.INSTANCE) { + hasNull = true; + } else if (v.getBoolean()) { + return ValueBoolean.TRUE; + } + } + return hasNull ? ValueNull.INSTANCE : ValueBoolean.FALSE; + } + default: + throw DbException.getInternalError("type=" + andOrType); + } + } + + private static final Comparator COMPARE_BY_COST = new Comparator() { + @Override + public int compare(Expression lhs, Expression rhs) { + return lhs.getCost() - rhs.getCost(); + } + + }; + + @Override + public Expression optimize(SessionLocal session) { + // NULL handling: see wikipedia, + // http://www-cs-students.stanford.edu/~wlam/compsci/sqlnulls + + // first pass, optimize individual sub-expressions + for (int i = 0; i < expressions.size(); i++ ) { + expressions.set(i, expressions.get(i).optimize(session)); + } + + Collections.sort(expressions, COMPARE_BY_COST); + + // TODO we're only matching pairs so that are next to each other, so in complex expressions + // we will miss opportunities + + // second pass, optimize combinations + optimizeMerge(0); + for (int i = 1; i < expressions.size(); ) { + Expression left = expressions.get(i-1); + Expression right = expressions.get(i); + switch (andOrType) { + case ConditionAndOr.AND: + if (!session.getDatabase().getSettings().optimizeTwoEquals) { + break; + } + // this optimization does not work in the following case, + // but NOT is optimized before: + // CREATE TABLE TEST(A INT, B INT); + // INSERT INTO TEST VALUES(1, NULL); + // SELECT * FROM TEST WHERE NOT (B=A AND B=0); // no rows + // SELECT * FROM TEST WHERE NOT (B=A AND B=0 AND A=0); // 1, + // NULL + // try to add conditions (A=B AND B=1: add A=1) + if (left instanceof Comparison && right instanceof Comparison) { + // try to add conditions (A=B AND B=1: add A=1) + Expression added = ((Comparison) left).getAdditionalAnd(session, (Comparison) right); + if (added != null) { + if (this.added == null) { + this.added = new ArrayList<>(); + } + this.added.add(added.optimize(session)); + } + } + break; + case ConditionAndOr.OR: + if (!session.getDatabase().getSettings().optimizeOr) { + break; + } + Expression reduced; + if (left instanceof Comparison && right instanceof Comparison) { + reduced = ((Comparison) left).optimizeOr(session, (Comparison) right); + } else if (left instanceof ConditionIn && right instanceof Comparison) { + reduced = ((ConditionIn) left).getAdditional((Comparison) right); + } else if (right instanceof ConditionIn && left instanceof Comparison) { + reduced = ((ConditionIn) right).getAdditional((Comparison) left); + } else if (left instanceof ConditionInConstantSet && right instanceof Comparison) { + reduced = ((ConditionInConstantSet) left).getAdditional(session, (Comparison) right); + } else if (right instanceof ConditionInConstantSet && left instanceof Comparison) { + reduced = ((ConditionInConstantSet) right).getAdditional(session, (Comparison) left); + } else if (left instanceof ConditionAndOr && right instanceof ConditionAndOr) { + reduced = ConditionAndOr.optimizeConditionAndOr((ConditionAndOr) left, (ConditionAndOr) right); + } else { + // TODO optimization: convert .. OR .. to UNION if the cost + // is lower + break; + } + if (reduced != null) { + expressions.remove(i); + expressions.set(i - 1, reduced.optimize(session)); + continue; // because we don't want to increment, we want to compare the new pair exposed + } + } + + Expression e = ConditionAndOr.optimizeIfConstant(session, andOrType, left, right); + if (e != null) { + expressions.remove(i); + expressions.set(i-1, e); + continue; // because we don't want to increment, we want to compare the new pair exposed + } + + if (optimizeMerge(i)) { + continue; + } + + i++; + } + + Collections.sort(expressions, COMPARE_BY_COST); + + if (expressions.size() == 1) { + return Condition.castToBoolean(session, expressions.get(0)); + } + return this; + } + + + private boolean optimizeMerge(int i) { + Expression e = expressions.get(i); + // If we have a ConditionAndOrN as a sub-expression, see if we can merge it + // into this one. + if (e instanceof ConditionAndOrN) { + ConditionAndOrN rightCondition = (ConditionAndOrN) e; + if (this.andOrType == rightCondition.andOrType) { + expressions.remove(i); + expressions.addAll(i, rightCondition.expressions); + return true; + } + } + else if (e instanceof ConditionAndOr) { + ConditionAndOr rightCondition = (ConditionAndOr) e; + if (this.andOrType == rightCondition.getAndOrType()) { + expressions.set(i, rightCondition.getSubexpression(0)); + expressions.add(i+1, rightCondition.getSubexpression(1)); + return true; + } + } + return false; + } + + @Override + public void addFilterConditions(TableFilter filter) { + if (andOrType == ConditionAndOr.AND) { + for (Expression e : expressions) { + e.addFilterConditions(filter); + } + } else { + super.addFilterConditions(filter); + } + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + for (Expression e : expressions) { + e.mapColumns(resolver, level, state); + } + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + for (Expression e : expressions) { + e.setEvaluatable(tableFilter, b); + } + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + for (Expression e : expressions) { + e.updateAggregate(session, stage); + } + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + for (Expression e : expressions) { + if (!e.isEverything(visitor)) { + return false; + } + } + return true; + } + + @Override + public int getCost() { + int cost = 0; + for (Expression e : expressions) { + cost += e.getCost(); + } + return cost; + } + + @Override + public int getSubexpressionCount() { + return expressions.size(); + } + + @Override + public Expression getSubexpression(int index) { + return expressions.get(index); + } + +} diff --git a/h2/src/main/org/h2/expression/condition/ConditionIn.java b/h2/src/main/org/h2/expression/condition/ConditionIn.java new file mode 100644 index 0000000..663f6fc --- /dev/null +++ b/h2/src/main/org/h2/expression/condition/ConditionIn.java @@ -0,0 +1,270 @@ +/* + * 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.expression.condition; + +import java.util.ArrayList; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.Parameter; +import org.h2.expression.TypedValueExpression; +import org.h2.expression.ValueExpression; +import org.h2.index.IndexCondition; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueNull; + +/** + * An 'in' condition with a list of values, as in WHERE NAME IN(...) + */ +public final class ConditionIn extends Condition { + + private Expression left; + private final boolean not; + private final boolean whenOperand; + private final ArrayList valueList; + + /** + * Create a new IN(..) condition. + * + * @param left the expression before IN + * @param not whether the result should be negated + * @param whenOperand whether this is a when operand + * @param values the value list (at least one element) + */ + public ConditionIn(Expression left, boolean not, boolean whenOperand, ArrayList values) { + this.left = left; + this.not = not; + this.whenOperand = whenOperand; + this.valueList = values; + } + + @Override + public Value getValue(SessionLocal session) { + return getValue(session, left.getValue(session)); + } + + @Override + public boolean getWhenValue(SessionLocal session, Value left) { + if (!whenOperand) { + return super.getWhenValue(session, left); + } + return getValue(session, left).isTrue(); + } + + private Value getValue(SessionLocal session, Value left) { + if (left.containsNull()) { + return ValueNull.INSTANCE; + } + boolean hasNull = false; + for (Expression e : valueList) { + Value r = e.getValue(session); + Value cmp = Comparison.compare(session, left, r, Comparison.EQUAL); + if (cmp == ValueNull.INSTANCE) { + hasNull = true; + } else if (cmp == ValueBoolean.TRUE) { + return ValueBoolean.get(!not); + } + } + if (hasNull) { + return ValueNull.INSTANCE; + } + return ValueBoolean.get(not); + } + + @Override + public boolean isWhenConditionOperand() { + return whenOperand; + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + left.mapColumns(resolver, level, state); + for (Expression e : valueList) { + e.mapColumns(resolver, level, state); + } + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + boolean constant = !whenOperand && left.isConstant(); + if (constant && left.isNullConstant()) { + return TypedValueExpression.UNKNOWN; + } + boolean allValuesConstant = true; + boolean allValuesNull = true; + TypeInfo leftType = left.getType(); + for (int i = 0, l = valueList.size(); i < l; i++) { + Expression e = valueList.get(i); + e = e.optimize(session); + TypeInfo.checkComparable(leftType, e.getType()); + if (e.isConstant() && !e.getValue(session).containsNull()) { + allValuesNull = false; + } + if (allValuesConstant && !e.isConstant()) { + allValuesConstant = false; + } + if (left instanceof ExpressionColumn && e instanceof Parameter) { + ((Parameter) e).setColumn(((ExpressionColumn) left).getColumn()); + } + valueList.set(i, e); + } + return optimize2(session, constant, allValuesConstant, allValuesNull, valueList); + } + + private Expression optimize2(SessionLocal session, boolean constant, boolean allValuesConstant, + boolean allValuesNull, ArrayList values) { + if (constant && allValuesConstant) { + return ValueExpression.getBoolean(getValue(session)); + } + if (values.size() == 1) { + return new Comparison(not ? Comparison.NOT_EQUAL : Comparison.EQUAL, left, values.get(0), whenOperand) + .optimize(session); + } + if (allValuesConstant && !allValuesNull) { + int leftType = left.getType().getValueType(); + if (leftType == Value.UNKNOWN) { + return this; + } + if (leftType == Value.ENUM && !(left instanceof ExpressionColumn)) { + return this; + } + return new ConditionInConstantSet(session, left, not, whenOperand, values).optimize(session); + } + return this; + } + + @Override + public Expression getNotIfPossible(SessionLocal session) { + if (whenOperand) { + return null; + } + return new ConditionIn(left, !not, false, valueList); + } + + @Override + public void createIndexConditions(SessionLocal session, TableFilter filter) { + if (not || whenOperand || !(left instanceof ExpressionColumn)) { + return; + } + ExpressionColumn l = (ExpressionColumn) left; + if (filter != l.getTableFilter()) { + return; + } + if (session.getDatabase().getSettings().optimizeInList) { + ExpressionVisitor visitor = ExpressionVisitor.getNotFromResolverVisitor(filter); + TypeInfo colType = l.getType(); + for (Expression e : valueList) { + if (!e.isEverything(visitor) + || !TypeInfo.haveSameOrdering(colType, TypeInfo.getHigherType(colType, e.getType()))) { + return; + } + } + filter.addIndexCondition(IndexCondition.getInList(l, valueList)); + } + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + left.setEvaluatable(tableFilter, b); + for (Expression e : valueList) { + e.setEvaluatable(tableFilter, b); + } + } + + @Override + public boolean needParentheses() { + return true; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return getWhenSQL(left.getSQL(builder, sqlFlags, AUTO_PARENTHESES), sqlFlags); + } + + @Override + public StringBuilder getWhenSQL(StringBuilder builder, int sqlFlags) { + if (not) { + builder.append(" NOT"); + } + return writeExpressions(builder.append(" IN("), valueList, sqlFlags).append(')'); + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + left.updateAggregate(session, stage); + for (Expression e : valueList) { + e.updateAggregate(session, stage); + } + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + if (!left.isEverything(visitor)) { + return false; + } + return areAllValues(visitor); + } + + private boolean areAllValues(ExpressionVisitor visitor) { + for (Expression e : valueList) { + if (!e.isEverything(visitor)) { + return false; + } + } + return true; + } + + @Override + public int getCost() { + int cost = left.getCost(); + for (Expression e : valueList) { + cost += e.getCost(); + } + return cost; + } + + /** + * Add an additional element if possible. Example: given two conditions + * A IN(1, 2) OR A=3, the constant 3 is added: A IN(1, 2, 3). + * + * @param other the second condition + * @return null if the condition was not added, or the new condition + */ + Expression getAdditional(Comparison other) { + if (!not && !whenOperand && left.isEverything(ExpressionVisitor.DETERMINISTIC_VISITOR)) { + Expression add = other.getIfEquals(left); + if (add != null) { + ArrayList list = new ArrayList<>(valueList.size() + 1); + list.addAll(valueList); + list.add(add); + return new ConditionIn(left, false, false, list); + } + } + return null; + } + + @Override + public int getSubexpressionCount() { + return 1 + valueList.size(); + } + + @Override + public Expression getSubexpression(int index) { + if (index == 0) { + return left; + } else if (index > 0 && index <= valueList.size()) { + return valueList.get(index - 1); + } + throw new IndexOutOfBoundsException(); + } + +} diff --git a/h2/src/main/org/h2/expression/condition/ConditionInConstantSet.java b/h2/src/main/org/h2/expression/condition/ConditionInConstantSet.java new file mode 100644 index 0000000..4174e8b --- /dev/null +++ b/h2/src/main/org/h2/expression/condition/ConditionInConstantSet.java @@ -0,0 +1,219 @@ +/* + * 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.expression.condition; + +import java.util.ArrayList; +import java.util.TreeSet; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.expression.ExpressionVisitor; +import org.h2.index.IndexCondition; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueNull; + +/** + * Used for optimised IN(...) queries where the contents of the IN list are all + * constant and of the same type. + */ +public final class ConditionInConstantSet extends Condition { + + private Expression left; + private final boolean not; + private final boolean whenOperand; + private final ArrayList valueList; + // HashSet cannot be used here, because we need to compare values of + // different type or scale properly. + private final TreeSet valueSet; + private boolean hasNull; + private final TypeInfo type; + + /** + * Create a new IN(..) condition. + * + * @param session the session + * @param left + * the expression before IN. Cannot have {@link Value#UNKNOWN} + * data type and {@link Value#ENUM} type is also supported only + * for {@link ExpressionColumn}. + * @param not whether the result should be negated + * @param whenOperand whether this is a when operand + * @param valueList + * the value list (at least two elements); all values must be + * comparable with left value + */ + ConditionInConstantSet(SessionLocal session, Expression left, boolean not, boolean whenOperand, + ArrayList valueList) { + this.left = left; + this.not = not; + this.whenOperand = whenOperand; + this.valueList = valueList; + this.valueSet = new TreeSet<>(session.getDatabase().getCompareMode()); + TypeInfo type = left.getType(); + for (Expression expression : valueList) { + type = TypeInfo.getHigherType(type, expression.getType()); + } + this.type = type; + for (Expression expression : valueList) { + add(expression.getValue(session), session); + } + } + + private void add(Value v, SessionLocal session) { + if ((v = v.convertTo(type, session)).containsNull()) { + hasNull = true; + } else { + valueSet.add(v); + } + } + + @Override + public Value getValue(SessionLocal session) { + return getValue(left.getValue(session), session); + } + + @Override + public boolean getWhenValue(SessionLocal session, Value left) { + if (!whenOperand) { + return super.getWhenValue(session, left); + } + return getValue(left, session).isTrue(); + } + + private Value getValue(Value left, SessionLocal session) { + if ((left = left.convertTo(type, session)).containsNull()) { + return ValueNull.INSTANCE; + } + boolean result = valueSet.contains(left); + if (!result && hasNull) { + return ValueNull.INSTANCE; + } + return ValueBoolean.get(not ^ result); + } + + @Override + public boolean isWhenConditionOperand() { + return whenOperand; + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + left.mapColumns(resolver, level, state); + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + return this; + } + + @Override + public Expression getNotIfPossible(SessionLocal session) { + if (whenOperand) { + return null; + } + return new ConditionInConstantSet(session, left, !not, false, valueList); + } + + @Override + public void createIndexConditions(SessionLocal session, TableFilter filter) { + if (not || whenOperand || !(left instanceof ExpressionColumn)) { + return; + } + ExpressionColumn l = (ExpressionColumn) left; + if (filter != l.getTableFilter()) { + return; + } + if (session.getDatabase().getSettings().optimizeInList) { + TypeInfo colType = l.getType(); + if (TypeInfo.haveSameOrdering(colType, TypeInfo.getHigherType(colType, type))) { + filter.addIndexCondition(IndexCondition.getInList(l, valueList)); + } + } + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + left.setEvaluatable(tableFilter, b); + } + + @Override + public boolean needParentheses() { + return true; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return getWhenSQL(left.getSQL(builder, sqlFlags, AUTO_PARENTHESES), sqlFlags); + } + + @Override + public StringBuilder getWhenSQL(StringBuilder builder, int sqlFlags) { + if (not) { + builder.append(" NOT"); + } + return writeExpressions(builder.append(" IN("), valueList, sqlFlags).append(')'); + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + left.updateAggregate(session, stage); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return left.isEverything(visitor); + } + + @Override + public int getCost() { + return left.getCost(); + } + + /** + * Add an additional element if possible. Example: given two conditions + * A IN(1, 2) OR A=3, the constant 3 is added: A IN(1, 2, 3). + * + * @param session the session + * @param other the second condition + * @return null if the condition was not added, or the new condition + */ + Expression getAdditional(SessionLocal session, Comparison other) { + if (!not && !whenOperand && left.isEverything(ExpressionVisitor.DETERMINISTIC_VISITOR)) { + Expression add = other.getIfEquals(left); + if (add != null) { + if (add.isConstant()) { + ArrayList list = new ArrayList<>(valueList.size() + 1); + list.addAll(valueList); + list.add(add); + return new ConditionInConstantSet(session, left, false, false, list); + } + } + } + return null; + } + + @Override + public int getSubexpressionCount() { + return 1 + valueList.size(); + } + + @Override + public Expression getSubexpression(int index) { + if (index == 0) { + return left; + } else if (index > 0 && index <= valueList.size()) { + return valueList.get(index - 1); + } + throw new IndexOutOfBoundsException(); + } + +} diff --git a/h2/src/main/org/h2/expression/condition/ConditionInParameter.java b/h2/src/main/org/h2/expression/condition/ConditionInParameter.java new file mode 100644 index 0000000..6bbf2f8 --- /dev/null +++ b/h2/src/main/org/h2/expression/condition/ConditionInParameter.java @@ -0,0 +1,224 @@ +/* + * 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.expression.condition; + +import java.util.AbstractList; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.Parameter; +import org.h2.expression.TypedValueExpression; +import org.h2.expression.ValueExpression; +import org.h2.index.IndexCondition; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.value.Value; +import org.h2.value.ValueArray; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueNull; + +/** + * A condition with parameter as {@code = ANY(?)}. + */ +public final class ConditionInParameter extends Condition { + private static final class ParameterList extends AbstractList { + private final Parameter parameter; + + ParameterList(Parameter parameter) { + this.parameter = parameter; + } + + @Override + public Expression get(int index) { + Value value = parameter.getParamValue(); + if (value instanceof ValueArray) { + return ValueExpression.get(((ValueArray) value).getList()[index]); + } + if (index != 0) { + throw new IndexOutOfBoundsException(); + } + return ValueExpression.get(value); + } + + @Override + public int size() { + if (!parameter.isValueSet()) { + return 0; + } + Value value = parameter.getParamValue(); + if (value instanceof ValueArray) { + return ((ValueArray) value).getList().length; + } + return 1; + } + } + + private Expression left; + + private boolean not; + + private boolean whenOperand; + + private final Parameter parameter; + + /** + * Gets evaluated condition value. + * + * @param session the session + * @param l left value. + * @param not whether the result should be negated + * @param value parameter value. + * @return Evaluated condition value. + */ + static Value getValue(SessionLocal session, Value l, boolean not, Value value) { + boolean hasNull = false; + if (value.containsNull()) { + hasNull = true; + } else { + for (Value r : value.convertToAnyArray(session).getList()) { + Value cmp = Comparison.compare(session, l, r, Comparison.EQUAL); + if (cmp == ValueNull.INSTANCE) { + hasNull = true; + } else if (cmp == ValueBoolean.TRUE) { + return ValueBoolean.get(!not); + } + } + } + if (hasNull) { + return ValueNull.INSTANCE; + } + return ValueBoolean.get(not); + } + + /** + * Create a new {@code = ANY(?)} condition. + * + * @param left + * the expression before {@code = ANY(?)} + * @param not whether the result should be negated + * @param whenOperand whether this is a when operand + * @param parameter + * parameter + */ + public ConditionInParameter(Expression left, boolean not, boolean whenOperand, Parameter parameter) { + this.left = left; + this.not = not; + this.whenOperand = whenOperand; + this.parameter = parameter; + } + + @Override + public Value getValue(SessionLocal session) { + Value l = left.getValue(session); + if (l == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + return getValue(session, l, not, parameter.getValue(session)); + } + + @Override + public boolean getWhenValue(SessionLocal session, Value left) { + if (!whenOperand) { + return super.getWhenValue(session, left); + } + if (left == ValueNull.INSTANCE) { + return false; + } + return getValue(session, left, not, parameter.getValue(session)).isTrue(); + } + + @Override + public boolean isWhenConditionOperand() { + return whenOperand; + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + left.mapColumns(resolver, level, state); + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + if (!whenOperand && left.isNullConstant()) { + return TypedValueExpression.UNKNOWN; + } + return this; + } + + @Override + public Expression getNotIfPossible(SessionLocal session) { + if (whenOperand) { + return null; + } + return new ConditionInParameter(left, !not, false, parameter); + } + + @Override + public void createIndexConditions(SessionLocal session, TableFilter filter) { + if (not || whenOperand || !(left instanceof ExpressionColumn)) { + return; + } + ExpressionColumn l = (ExpressionColumn) left; + if (filter != l.getTableFilter()) { + return; + } + filter.addIndexCondition(IndexCondition.getInList(l, new ParameterList(parameter))); + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + left.setEvaluatable(tableFilter, b); + } + + @Override + public boolean needParentheses() { + return true; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + if (not) { + builder.append("NOT ("); + } + left.getSQL(builder, sqlFlags, AUTO_PARENTHESES); + parameter.getSQL(builder.append(" = ANY("), sqlFlags, AUTO_PARENTHESES).append(')'); + if (not) { + builder.append(')'); + } + return builder; + } + + @Override + public StringBuilder getWhenSQL(StringBuilder builder, int sqlFlags) { + if (not) { + builder.append(" NOT IN(UNNEST("); + parameter.getSQL(builder, sqlFlags, AUTO_PARENTHESES).append("))"); + } else { + builder.append(" = ANY("); + parameter.getSQL(builder, sqlFlags, AUTO_PARENTHESES).append(')'); + } + return builder; + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + left.updateAggregate(session, stage); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return left.isEverything(visitor) && parameter.isEverything(visitor); + } + + @Override + public int getCost() { + return left.getCost(); + } + +} diff --git a/h2/src/main/org/h2/expression/condition/ConditionInQuery.java b/h2/src/main/org/h2/expression/condition/ConditionInQuery.java new file mode 100644 index 0000000..700aea1 --- /dev/null +++ b/h2/src/main/org/h2/expression/condition/ConditionInQuery.java @@ -0,0 +1,256 @@ +/* + * 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.expression.condition; + +import org.h2.command.query.Query; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.expression.ExpressionVisitor; +import org.h2.index.IndexCondition; +import org.h2.result.LocalResult; +import org.h2.result.ResultInterface; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueNull; +import org.h2.value.ValueRow; + +/** + * An IN() condition with a subquery, as in WHERE ID IN(SELECT ...) + */ +public final class ConditionInQuery extends PredicateWithSubquery { + + private Expression left; + private final boolean not; + private final boolean whenOperand; + private final boolean all; + private final int compareType; + + public ConditionInQuery(Expression left, boolean not, boolean whenOperand, Query query, boolean all, + int compareType) { + super(query); + this.left = left; + this.not = not; + this.whenOperand = whenOperand; + /* + * Need to do it now because other methods may be invoked in different + * order. + */ + query.setRandomAccessResult(true); + query.setNeverLazy(true); + query.setDistinctIfPossible(); + this.all = all; + this.compareType = compareType; + } + + @Override + public Value getValue(SessionLocal session) { + return getValue(session, left.getValue(session)); + } + + @Override + public boolean getWhenValue(SessionLocal session, Value left) { + if (!whenOperand) { + return super.getWhenValue(session, left); + } + return getValue(session, left).isTrue(); + } + + private Value getValue(SessionLocal session, Value left) { + query.setSession(session); + LocalResult rows = (LocalResult) query.query(0); + if (!rows.hasNext()) { + return ValueBoolean.get(not ^ all); + } + if ((compareType & ~1) == Comparison.EQUAL_NULL_SAFE) { + return getNullSafeValueSlow(session, rows, left); + } + if (left.containsNull()) { + return ValueNull.INSTANCE; + } + if (all || compareType != Comparison.EQUAL || !session.getDatabase().getSettings().optimizeInSelect) { + return getValueSlow(session, rows, left); + } + int columnCount = query.getColumnCount(); + if (columnCount != 1) { + Value[] leftValue = left.convertToAnyRow().getList(); + if (columnCount == leftValue.length && rows.containsDistinct(leftValue)) { + return ValueBoolean.get(!not); + } + } else { + TypeInfo colType = rows.getColumnType(0); + if (colType.getValueType() == Value.NULL) { + return ValueNull.INSTANCE; + } + if (left.getValueType() == Value.ROW) { + left = ((ValueRow) left).getList()[0]; + } + if (rows.containsDistinct(new Value[] { left })) { + return ValueBoolean.get(!not); + } + } + if (rows.containsNull()) { + return ValueNull.INSTANCE; + } + return ValueBoolean.get(not); + } + + private Value getValueSlow(SessionLocal session, ResultInterface rows, Value l) { + // this only returns the correct result if the result has at least one + // row, and if l is not null + boolean simple = l.getValueType() != Value.ROW && query.getColumnCount() == 1; + boolean hasNull = false; + ValueBoolean searched = ValueBoolean.get(!all); + while (rows.next()) { + Value[] currentRow = rows.currentRow(); + Value cmp = Comparison.compare(session, l, simple ? currentRow[0] : ValueRow.get(currentRow), + compareType); + if (cmp == ValueNull.INSTANCE) { + hasNull = true; + } else if (cmp == searched) { + return ValueBoolean.get(not == all); + } + } + if (hasNull) { + return ValueNull.INSTANCE; + } + return ValueBoolean.get(not ^ all); + } + + private Value getNullSafeValueSlow(SessionLocal session, ResultInterface rows, Value l) { + boolean simple = l.getValueType() != Value.ROW && query.getColumnCount() == 1; + boolean searched = all == (compareType == Comparison.NOT_EQUAL_NULL_SAFE); + while (rows.next()) { + Value[] currentRow = rows.currentRow(); + if (session.areEqual(l, simple ? currentRow[0] : ValueRow.get(currentRow)) == searched) { + return ValueBoolean.get(not == all); + } + } + return ValueBoolean.get(not ^ all); + } + + @Override + public boolean isWhenConditionOperand() { + return whenOperand; + } + + @Override + public Expression getNotIfPossible(SessionLocal session) { + if (whenOperand) { + return null; + } + return new ConditionInQuery(left, !not, false, query, all, compareType); + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + left.mapColumns(resolver, level, state); + super.mapColumns(resolver, level, state); + } + + @Override + public Expression optimize(SessionLocal session) { + super.optimize(session); + left = left.optimize(session); + TypeInfo.checkComparable(left.getType(), query.getRowDataType()); + return this; + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + left.setEvaluatable(tableFilter, b); + super.setEvaluatable(tableFilter, b); + } + + @Override + public boolean needParentheses() { + return true; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + boolean outerNot = not && (all || compareType != Comparison.EQUAL); + if (outerNot) { + builder.append("NOT ("); + } + left.getSQL(builder, sqlFlags, AUTO_PARENTHESES); + getWhenSQL(builder, sqlFlags); + if (outerNot) { + builder.append(')'); + } + return builder; + } + + @Override + public StringBuilder getWhenSQL(StringBuilder builder, int sqlFlags) { + if (all) { + builder.append(Comparison.COMPARE_TYPES[compareType]).append(" ALL"); + } else if (compareType == Comparison.EQUAL) { + if (not) { + builder.append(" NOT"); + } + builder.append(" IN"); + } else { + builder.append(' ').append(Comparison.COMPARE_TYPES[compareType]).append(" ANY"); + } + return super.getUnenclosedSQL(builder, sqlFlags); + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + left.updateAggregate(session, stage); + super.updateAggregate(session, stage); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return left.isEverything(visitor) && super.isEverything(visitor); + } + + @Override + public int getCost() { + return left.getCost() + super.getCost(); + } + + @Override + public void createIndexConditions(SessionLocal session, TableFilter filter) { + if (!session.getDatabase().getSettings().optimizeInList) { + return; + } + if (not || compareType != Comparison.EQUAL) { + return; + } + if (query.getColumnCount() != 1) { + return; + } + if (!(left instanceof ExpressionColumn)) { + return; + } + TypeInfo colType = left.getType(); + TypeInfo queryType = query.getExpressions().get(0).getType(); + if (!TypeInfo.haveSameOrdering(colType, TypeInfo.getHigherType(colType, queryType))) { + return; + } + int leftType = colType.getValueType(); + if (!DataType.hasTotalOrdering(leftType) && leftType != queryType.getValueType()) { + return; + } + ExpressionColumn l = (ExpressionColumn) left; + if (filter != l.getTableFilter()) { + return; + } + ExpressionVisitor visitor = ExpressionVisitor.getNotFromResolverVisitor(filter); + if (!query.isEverything(visitor)) { + return; + } + filter.addIndexCondition(IndexCondition.getInQuery(l, query)); + } + +} diff --git a/h2/src/main/org/h2/expression/condition/ConditionLocalAndGlobal.java b/h2/src/main/org/h2/expression/condition/ConditionLocalAndGlobal.java new file mode 100644 index 0000000..032604b --- /dev/null +++ b/h2/src/main/org/h2/expression/condition/ConditionLocalAndGlobal.java @@ -0,0 +1,152 @@ +/* + * 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.expression.condition; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.message.DbException; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.value.Value; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueNull; + +/** + * A global condition or combination of local and global conditions. May be used + * only as a top-level expression in a WHERE, HAVING, or QUALIFY clause of a + * SELECT. + */ +public class ConditionLocalAndGlobal extends Condition { + + private Expression local, global; + + public ConditionLocalAndGlobal(Expression local, Expression global) { + if (global == null) { + throw DbException.getInternalError(); + } + this.local = local; + this.global = global; + } + + @Override + public boolean needParentheses() { + return local != null || global.needParentheses(); + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + if (local == null) { + return global.getUnenclosedSQL(builder, sqlFlags); + } + local.getSQL(builder, sqlFlags, AUTO_PARENTHESES); + builder.append("\n _LOCAL_AND_GLOBAL_ "); + return global.getSQL(builder, sqlFlags, AUTO_PARENTHESES); + } + + @Override + public void createIndexConditions(SessionLocal session, TableFilter filter) { + if (local != null) { + local.createIndexConditions(session, filter); + } + global.createIndexConditions(session, filter); + } + + @Override + public Value getValue(SessionLocal session) { + if (local == null) { + return global.getValue(session); + } + Value l = local.getValue(session), r; + if (l.isFalse() || (r = global.getValue(session)).isFalse()) { + return ValueBoolean.FALSE; + } + if (l == ValueNull.INSTANCE || r == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + return ValueBoolean.TRUE; + } + + @Override + public Expression optimize(SessionLocal session) { + global = global.optimize(session); + if (local != null) { + local = local.optimize(session); + Expression e = ConditionAndOr.optimizeIfConstant(session, ConditionAndOr.AND, local, global); + if (e != null) { + return e; + } + } + return this; + } + + @Override + public void addFilterConditions(TableFilter filter) { + if (local != null) { + local.addFilterConditions(filter); + } + global.addFilterConditions(filter); + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + if (local != null) { + local.mapColumns(resolver, level, state); + } + global.mapColumns(resolver, level, state); + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + if (local != null) { + local.setEvaluatable(tableFilter, b); + } + global.setEvaluatable(tableFilter, b); + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + if (local != null) { + local.updateAggregate(session, stage); + } + global.updateAggregate(session, stage); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return (local == null || local.isEverything(visitor)) && global.isEverything(visitor); + } + + @Override + public int getCost() { + int cost = global.getCost(); + if (local != null) { + cost += local.getCost(); + } + return cost; + } + + @Override + public int getSubexpressionCount() { + return local == null ? 1 : 2; + } + + @Override + public Expression getSubexpression(int index) { + switch (index) { + case 0: + return local != null ? local : global; + case 1: + if (local != null) { + return global; + } + //$FALL-THROUGH$ + default: + throw new IndexOutOfBoundsException(); + } + } + +} diff --git a/h2/src/main/org/h2/expression/condition/ConditionNot.java b/h2/src/main/org/h2/expression/condition/ConditionNot.java new file mode 100644 index 0000000..215926c --- /dev/null +++ b/h2/src/main/org/h2/expression/condition/ConditionNot.java @@ -0,0 +1,109 @@ +/* + * 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.expression.condition; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.TypedValueExpression; +import org.h2.expression.ValueExpression; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * A NOT condition. + */ +public class ConditionNot extends Condition { + + private Expression condition; + + public ConditionNot(Expression condition) { + this.condition = condition; + } + + @Override + public Expression getNotIfPossible(SessionLocal session) { + return castToBoolean(session, condition.optimize(session)); + } + + @Override + public Value getValue(SessionLocal session) { + Value v = condition.getValue(session); + if (v == ValueNull.INSTANCE) { + return v; + } + return v.convertToBoolean().negate(); + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + condition.mapColumns(resolver, level, state); + } + + @Override + public Expression optimize(SessionLocal session) { + Expression e2 = condition.getNotIfPossible(session); + if (e2 != null) { + return e2.optimize(session); + } + Expression expr = condition.optimize(session); + if (expr.isConstant()) { + Value v = expr.getValue(session); + if (v == ValueNull.INSTANCE) { + return TypedValueExpression.UNKNOWN; + } + return ValueExpression.getBoolean(!v.getBoolean()); + } + condition = expr; + return this; + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + condition.setEvaluatable(tableFilter, b); + } + + @Override + public boolean needParentheses() { + return true; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return condition.getSQL(builder.append("NOT "), sqlFlags, AUTO_PARENTHESES); + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + condition.updateAggregate(session, stage); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return condition.isEverything(visitor); + } + + @Override + public int getCost() { + return condition.getCost(); + } + + @Override + public int getSubexpressionCount() { + return 1; + } + + @Override + public Expression getSubexpression(int index) { + if (index == 0) { + return condition; + } + throw new IndexOutOfBoundsException(); + } + +} diff --git a/h2/src/main/org/h2/expression/condition/ExistsPredicate.java b/h2/src/main/org/h2/expression/condition/ExistsPredicate.java new file mode 100644 index 0000000..be487b4 --- /dev/null +++ b/h2/src/main/org/h2/expression/condition/ExistsPredicate.java @@ -0,0 +1,33 @@ +/* + * 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.expression.condition; + +import org.h2.command.query.Query; +import org.h2.engine.SessionLocal; +import org.h2.value.Value; +import org.h2.value.ValueBoolean; + +/** + * Exists predicate as in EXISTS(SELECT ...) + */ +public class ExistsPredicate extends PredicateWithSubquery { + + public ExistsPredicate(Query query) { + super(query); + } + + @Override + public Value getValue(SessionLocal session) { + query.setSession(session); + return ValueBoolean.get(query.exists()); + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return super.getUnenclosedSQL(builder.append("EXISTS"), sqlFlags); + } + +} diff --git a/h2/src/main/org/h2/expression/condition/IsJsonPredicate.java b/h2/src/main/org/h2/expression/condition/IsJsonPredicate.java new file mode 100644 index 0000000..67b56ea --- /dev/null +++ b/h2/src/main/org/h2/expression/condition/IsJsonPredicate.java @@ -0,0 +1,217 @@ +/* + * 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.expression.condition; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.ValueExpression; +import org.h2.message.DbException; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.util.json.JSONBytesSource; +import org.h2.util.json.JSONItemType; +import org.h2.util.json.JSONStringSource; +import org.h2.util.json.JSONValidationTarget; +import org.h2.util.json.JSONValidationTargetWithUniqueKeys; +import org.h2.util.json.JSONValidationTargetWithoutUniqueKeys; +import org.h2.value.Value; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueJson; +import org.h2.value.ValueNull; + +/** + * IS JSON predicate. + */ +public final class IsJsonPredicate extends Condition { + + private Expression left; + private final boolean not; + private final boolean whenOperand; + private final boolean withUniqueKeys; + private final JSONItemType itemType; + + public IsJsonPredicate(Expression left, boolean not, boolean whenOperand, boolean withUniqueKeys, + JSONItemType itemType) { + this.left = left; + this.whenOperand = whenOperand; + this.not = not; + this.withUniqueKeys = withUniqueKeys; + this.itemType = itemType; + } + + @Override + public boolean needParentheses() { + return true; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return getWhenSQL(left.getSQL(builder, sqlFlags, AUTO_PARENTHESES), sqlFlags); + } + + @Override + public StringBuilder getWhenSQL(StringBuilder builder, int sqlFlags) { + builder.append(" IS"); + if (not) { + builder.append(" NOT"); + } + builder.append(" JSON"); + switch (itemType) { + case VALUE: + break; + case ARRAY: + builder.append(" ARRAY"); + break; + case OBJECT: + builder.append(" OBJECT"); + break; + case SCALAR: + builder.append(" SCALAR"); + break; + default: + throw DbException.getInternalError("itemType=" + itemType); + } + if (withUniqueKeys) { + builder.append(" WITH UNIQUE KEYS"); + } + return builder; + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + if (!whenOperand && left.isConstant()) { + return ValueExpression.getBoolean(getValue(session)); + } + return this; + } + + @Override + public Value getValue(SessionLocal session) { + Value l = left.getValue(session); + if (l == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + return ValueBoolean.get(getValue(l)); + } + + @Override + public boolean getWhenValue(SessionLocal session, Value left) { + if (!whenOperand) { + return super.getWhenValue(session, left); + } + if (left == ValueNull.INSTANCE) { + return false; + } + return getValue(left); + } + + private boolean getValue(Value left) { + boolean result; + switch (left.getValueType()) { + case Value.VARBINARY: + case Value.BINARY: + case Value.BLOB: { + byte[] bytes = left.getBytesNoCopy(); + JSONValidationTarget target = withUniqueKeys ? new JSONValidationTargetWithUniqueKeys() + : new JSONValidationTargetWithoutUniqueKeys(); + try { + result = itemType.includes(JSONBytesSource.parse(bytes, target)) ^ not; + } catch (RuntimeException ex) { + result = not; + } + break; + } + case Value.JSON: { + JSONItemType valueItemType = ((ValueJson) left).getItemType(); + if (!itemType.includes(valueItemType)) { + result = not; + break; + } else if (!withUniqueKeys || valueItemType == JSONItemType.SCALAR) { + result = !not; + break; + } + } + //$FALL-THROUGH$ + case Value.VARCHAR: + case Value.VARCHAR_IGNORECASE: + case Value.CHAR: + case Value.CLOB: { + String string = left.getString(); + JSONValidationTarget target = withUniqueKeys ? new JSONValidationTargetWithUniqueKeys() + : new JSONValidationTargetWithoutUniqueKeys(); + try { + result = itemType.includes(JSONStringSource.parse(string, target)) ^ not; + } catch (RuntimeException ex) { + result = not; + } + break; + } + default: + result = not; + } + return result; + } + + @Override + public boolean isWhenConditionOperand() { + return whenOperand; + } + + @Override + public Expression getNotIfPossible(SessionLocal session) { + if (whenOperand) { + return null; + } + return new IsJsonPredicate(left, !not, false, withUniqueKeys, itemType); + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + left.setEvaluatable(tableFilter, b); + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + left.updateAggregate(session, stage); + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + left.mapColumns(resolver, level, state); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return left.isEverything(visitor); + } + + @Override + public int getCost() { + int cost = left.getCost(); + if (left.getType().getValueType() == Value.JSON && (!withUniqueKeys || itemType == JSONItemType.SCALAR)) { + cost++; + } else { + cost += 10; + } + return cost; + } + + @Override + public int getSubexpressionCount() { + return 1; + } + + @Override + public Expression getSubexpression(int index) { + if (index == 0) { + return left; + } + throw new IndexOutOfBoundsException(); + } + +} diff --git a/h2/src/main/org/h2/expression/condition/NullPredicate.java b/h2/src/main/org/h2/expression/condition/NullPredicate.java new file mode 100644 index 0000000..46ae3bf --- /dev/null +++ b/h2/src/main/org/h2/expression/condition/NullPredicate.java @@ -0,0 +1,153 @@ +/* + * 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.expression.condition; + +import java.util.ArrayList; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.expression.ExpressionList; +import org.h2.expression.ValueExpression; +import org.h2.index.IndexCondition; +import org.h2.table.TableFilter; +import org.h2.value.Value; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueNull; +import org.h2.value.ValueRow; + +/** + * Null predicate (IS [NOT] NULL). + */ +public final class NullPredicate extends SimplePredicate { + + private boolean optimized; + + public NullPredicate(Expression left, boolean not, boolean whenOperand) { + super(left, not, whenOperand); + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return getWhenSQL(left.getSQL(builder, sqlFlags, AUTO_PARENTHESES), sqlFlags); + } + + @Override + public StringBuilder getWhenSQL(StringBuilder builder, int sqlFlags) { + return builder.append(not ? " IS NOT NULL" : " IS NULL"); + } + + @Override + public Expression optimize(SessionLocal session) { + if (optimized) { + return this; + } + Expression o = super.optimize(session); + if (o != this) { + return o; + } + optimized = true; + if (!whenOperand && left instanceof ExpressionList) { + ExpressionList list = (ExpressionList) left; + if (!list.isArray()) { + for (int i = 0, count = list.getSubexpressionCount(); i < count; i++) { + if (list.getSubexpression(i).isNullConstant()) { + if (not) { + return ValueExpression.FALSE; + } + ArrayList newList = new ArrayList<>(count - 1); + for (int j = 0; j < i; j++) { + newList.add(list.getSubexpression(j)); + } + for (int j = i + 1; j < count; j++) { + Expression e = list.getSubexpression(j); + if (!e.isNullConstant()) { + newList.add(e); + } + } + left = newList.size() == 1 ? newList.get(0) // + : new ExpressionList(newList.toArray(new Expression[0]), false); + break; + } + } + } + } + return this; + } + + @Override + public Value getValue(SessionLocal session) { + return ValueBoolean.get(getValue(left.getValue(session))); + } + + @Override + public boolean getWhenValue(SessionLocal session, Value left) { + if (!whenOperand) { + return super.getWhenValue(session, left); + } + return getValue(left); + } + + private boolean getValue(Value left) { + if (left.getType().getValueType() == Value.ROW) { + for (Value v : ((ValueRow) left).getList()) { + if (v != ValueNull.INSTANCE ^ not) { + return false; + } + } + return true; + } + return left == ValueNull.INSTANCE ^ not; + } + + @Override + public Expression getNotIfPossible(SessionLocal session) { + if (whenOperand) { + return null; + } + Expression o = optimize(session); + if (o != this) { + return o.getNotIfPossible(session); + } + switch (left.getType().getValueType()) { + case Value.UNKNOWN: + case Value.ROW: + return null; + } + return new NullPredicate(left, !not, false); + } + + @Override + public void createIndexConditions(SessionLocal session, TableFilter filter) { + if (not || whenOperand || !filter.getTable().isQueryComparable()) { + return; + } + if (left instanceof ExpressionColumn) { + createNullIndexCondition(filter, (ExpressionColumn) left); + } else if (left instanceof ExpressionList) { + ExpressionList list = (ExpressionList) left; + if (!list.isArray()) { + for (int i = 0, count = list.getSubexpressionCount(); i < count; i++) { + Expression e = list.getSubexpression(i); + if (e instanceof ExpressionColumn) { + createNullIndexCondition(filter, (ExpressionColumn) e); + } + } + } + } + } + + private static void createNullIndexCondition(TableFilter filter, ExpressionColumn c) { + /* + * Columns with row value data type aren't valid, but perform such check + * to be sure. + */ + if (filter == c.getTableFilter() && c.getType().getValueType() != Value.ROW) { + filter.addIndexCondition(IndexCondition.get(Comparison.EQUAL_NULL_SAFE, c, ValueExpression.NULL)); + } + } + +} diff --git a/h2/src/main/org/h2/expression/condition/PredicateWithSubquery.java b/h2/src/main/org/h2/expression/condition/PredicateWithSubquery.java new file mode 100644 index 0000000..8065315 --- /dev/null +++ b/h2/src/main/org/h2/expression/condition/PredicateWithSubquery.java @@ -0,0 +1,66 @@ +/* + * 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.expression.condition; + +import org.h2.command.query.Query; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.util.StringUtils; + +/** + * Base class for predicates with a subquery. + */ +abstract class PredicateWithSubquery extends Condition { + + /** + * The subquery. + */ + final Query query; + + PredicateWithSubquery(Query query) { + this.query = query; + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + query.mapColumns(resolver, level + 1); + } + + @Override + public Expression optimize(SessionLocal session) { + query.prepare(); + return this; + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean value) { + query.setEvaluatable(tableFilter, value); + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return StringUtils.indent(builder.append('('), query.getPlanSQL(sqlFlags), 4, false).append(')'); + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + query.updateAggregate(session, stage); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return query.isEverything(visitor); + } + + @Override + public int getCost() { + return query.getCostAsExpression(); + } + +} diff --git a/h2/src/main/org/h2/expression/condition/SimplePredicate.java b/h2/src/main/org/h2/expression/condition/SimplePredicate.java new file mode 100644 index 0000000..6a23513 --- /dev/null +++ b/h2/src/main/org/h2/expression/condition/SimplePredicate.java @@ -0,0 +1,98 @@ +/* + * 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.expression.condition; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.ValueExpression; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; + +/** + * Base class for simple predicates. + */ +public abstract class SimplePredicate extends Condition { + + /** + * The left hand side of the expression. + */ + Expression left; + + /** + * Whether it is a "not" condition (e.g. "is not null"). + */ + final boolean not; + + /** + * Where this is the when operand of the simple case. + */ + final boolean whenOperand; + + SimplePredicate(Expression left, boolean not, boolean whenOperand) { + this.left = left; + this.not = not; + this.whenOperand = whenOperand; + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + if (!whenOperand && left.isConstant()) { + return ValueExpression.getBoolean(getValue(session)); + } + return this; + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + left.setEvaluatable(tableFilter, b); + } + + @Override + public final boolean needParentheses() { + return true; + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + left.updateAggregate(session, stage); + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + left.mapColumns(resolver, level, state); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return left.isEverything(visitor); + } + + @Override + public int getCost() { + return left.getCost() + 1; + } + + @Override + public int getSubexpressionCount() { + return 1; + } + + @Override + public Expression getSubexpression(int index) { + if (index == 0) { + return left; + } + throw new IndexOutOfBoundsException(); + } + + @Override + public final boolean isWhenConditionOperand() { + return whenOperand; + } + +} diff --git a/h2/src/main/org/h2/expression/condition/TypePredicate.java b/h2/src/main/org/h2/expression/condition/TypePredicate.java new file mode 100644 index 0000000..74ce12e --- /dev/null +++ b/h2/src/main/org/h2/expression/condition/TypePredicate.java @@ -0,0 +1,90 @@ +/* + * 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.expression.condition; + +import java.util.Arrays; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueNull; + +/** + * Type predicate (IS [NOT] OF). + */ +public final class TypePredicate extends SimplePredicate { + + private final TypeInfo[] typeList; + private int[] valueTypes; + + public TypePredicate(Expression left, boolean not, boolean whenOperand, TypeInfo[] typeList) { + super(left, not, whenOperand); + this.typeList = typeList; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return getWhenSQL(left.getSQL(builder, sqlFlags, AUTO_PARENTHESES), sqlFlags); + } + + @Override + public StringBuilder getWhenSQL(StringBuilder builder, int sqlFlags) { + builder.append(" IS"); + if (not) { + builder.append(" NOT"); + } + builder.append(" OF ("); + for (int i = 0; i < typeList.length; i++) { + if (i > 0) { + builder.append(", "); + } + typeList[i].getSQL(builder, sqlFlags); + } + return builder.append(')'); + } + + @Override + public Expression optimize(SessionLocal session) { + int count = typeList.length; + valueTypes = new int[count]; + for (int i = 0; i < count; i++) { + valueTypes[i] = typeList[i].getValueType(); + } + Arrays.sort(valueTypes); + return super.optimize(session); + } + + @Override + public Value getValue(SessionLocal session) { + Value l = left.getValue(session); + if (l == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + return ValueBoolean.get(Arrays.binarySearch(valueTypes, l.getValueType()) >= 0 ^ not); + } + + @Override + public boolean getWhenValue(SessionLocal session, Value left) { + if (!whenOperand) { + return super.getWhenValue(session, left); + } + if (left == ValueNull.INSTANCE) { + return false; + } + return Arrays.binarySearch(valueTypes, left.getValueType()) >= 0 ^ not; + } + + @Override + public Expression getNotIfPossible(SessionLocal session) { + if (whenOperand) { + return null; + } + return new TypePredicate(left, !not, false, typeList); + } + +} diff --git a/h2/src/main/org/h2/expression/condition/UniquePredicate.java b/h2/src/main/org/h2/expression/condition/UniquePredicate.java new file mode 100644 index 0000000..745e242 --- /dev/null +++ b/h2/src/main/org/h2/expression/condition/UniquePredicate.java @@ -0,0 +1,102 @@ +/* + * 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.expression.condition; + +import java.util.Arrays; + +import org.h2.command.query.Query; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ValueExpression; +import org.h2.result.LocalResult; +import org.h2.result.ResultTarget; +import org.h2.value.Value; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueNull; + +/** + * Unique predicate as in UNIQUE(SELECT ...) + */ +public class UniquePredicate extends PredicateWithSubquery { + + private static final class Target implements ResultTarget { + + private final int columnCount; + + private final LocalResult result; + + boolean hasDuplicates; + + Target(int columnCount, LocalResult result) { + this.columnCount = columnCount; + this.result = result; + } + + @Override + public void limitsWereApplied() { + // Nothing to do + } + + @Override + public long getRowCount() { + // Not required + return 0L; + } + + @Override + public void addRow(Value... values) { + if (hasDuplicates) { + return; + } + for (int i = 0; i < columnCount; i++) { + if (values[i] == ValueNull.INSTANCE) { + return; + } + } + if (values.length != columnCount) { + values = Arrays.copyOf(values, columnCount); + } + long expected = result.getRowCount() + 1; + result.addRow(values); + if (expected != result.getRowCount()) { + hasDuplicates = true; + result.close(); + } + } + } + + public UniquePredicate(Query query) { + super(query); + } + + @Override + public Expression optimize(SessionLocal session) { + super.optimize(session); + if (query.isStandardDistinct()) { + return ValueExpression.TRUE; + } + return this; + } + + @Override + public Value getValue(SessionLocal session) { + query.setSession(session); + int columnCount = query.getColumnCount(); + LocalResult result = new LocalResult(session, + query.getExpressions().toArray(new Expression[0]), columnCount, columnCount); + result.setDistinct(); + Target target = new Target(columnCount, result); + query.query(Integer.MAX_VALUE, target); + result.close(); + return ValueBoolean.get(!target.hasDuplicates); + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return super.getUnenclosedSQL(builder.append("UNIQUE"), sqlFlags); + } + +} diff --git a/h2/src/main/org/h2/expression/condition/package.html b/h2/src/main/org/h2/expression/condition/package.html new file mode 100644 index 0000000..b8c56e2 --- /dev/null +++ b/h2/src/main/org/h2/expression/condition/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Condition expressions. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/expression/function/ArrayFunction.java b/h2/src/main/org/h2/expression/function/ArrayFunction.java new file mode 100644 index 0000000..1da2c40 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/ArrayFunction.java @@ -0,0 +1,177 @@ +/* + * 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.expression.function; + +import java.util.Arrays; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.engine.Mode.ModeEnum; +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; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueCollectionBase; +import org.h2.value.ValueNull; + +/** + * An array function. + */ +public final class ArrayFunction extends FunctionN { + + /** + * TRIM_ARRAY(). + */ + public static final int TRIM_ARRAY = 0; + + /** + * ARRAY_CONTAINS() (non-standard). + */ + public static final int ARRAY_CONTAINS = TRIM_ARRAY + 1; + + /** + * ARRAY_SLICE() (non-standard). + */ + public static final int ARRAY_SLICE = ARRAY_CONTAINS + 1; + + private static final String[] NAMES = { // + "TRIM_ARRAY", "ARRAY_CONTAINS", "ARRAY_SLICE" // + }; + + private final int function; + + public ArrayFunction(Expression arg1, Expression arg2, Expression arg3, int function) { + super(arg3 == null ? new Expression[] { arg1, arg2 } : new Expression[] { arg1, arg2, arg3 }); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session) { + Value v1 = args[0].getValue(session), v2 = args[1].getValue(session); + switch (function) { + case TRIM_ARRAY: { + if (v2 == ValueNull.INSTANCE) { + v1 = ValueNull.INSTANCE; + break; + } + int trim = v2.getInt(); + if (trim < 0) { + // This exception should be thrown even when array is null + throw DbException.get(ErrorCode.ARRAY_ELEMENT_ERROR_2, Integer.toString(trim), // + "0..CARDINALITY(array)"); + } + if (v1 == ValueNull.INSTANCE) { + break; + } + final ValueArray array = v1.convertToAnyArray(session); + Value[] elements = array.getList(); + int length = elements.length; + if (trim > length) { + throw DbException.get(ErrorCode.ARRAY_ELEMENT_ERROR_2, Integer.toString(trim), "0.." + length); + } else if (trim == 0) { + v1 = array; + } else { + v1 = ValueArray.get(array.getComponentType(), Arrays.copyOf(elements, length - trim), session); + } + break; + } + case ARRAY_CONTAINS: { + int t = v1.getValueType(); + if (t == Value.ARRAY || t == Value.ROW) { + Value[] list = ((ValueCollectionBase) v1).getList(); + v1 = ValueBoolean.FALSE; + for (Value v : list) { + if (session.areEqual(v, v2)) { + v1 = ValueBoolean.TRUE; + break; + } + } + } else { + v1 = ValueNull.INSTANCE; + } + break; + } + case ARRAY_SLICE: { + Value v3; + if (v1 == ValueNull.INSTANCE || v2 == ValueNull.INSTANCE + || (v3 = args[2].getValue(session)) == ValueNull.INSTANCE) { + v1 = ValueNull.INSTANCE; + break; + } + ValueArray array = v1.convertToAnyArray(session); + // SQL is 1-based + int index1 = v2.getInt() - 1; + // 1-based and inclusive as postgreSQL (-1+1) + int index2 = v3.getInt(); + // https://www.postgresql.org/docs/current/arrays.html#ARRAYS-ACCESSING + // For historical reasons postgreSQL ignore invalid indexes + final boolean isPG = session.getMode().getEnum() == ModeEnum.PostgreSQL; + if (index1 > index2) { + v1 = isPG ? ValueArray.get(array.getComponentType(), Value.EMPTY_VALUES, session) : ValueNull.INSTANCE; + break; + } + if (index1 < 0) { + if (isPG) { + index1 = 0; + } else { + v1 = ValueNull.INSTANCE; + break; + } + } + if (index2 > array.getList().length) { + if (isPG) { + index2 = array.getList().length; + } else { + v1 = ValueNull.INSTANCE; + break; + } + } + v1 = ValueArray.get(array.getComponentType(), Arrays.copyOfRange(array.getList(), index1, index2), // + session); + break; + } + default: + throw DbException.getInternalError("function=" + function); + } + return v1; + } + + @Override + public Expression optimize(SessionLocal session) { + boolean allConst = optimizeArguments(session, true); + switch (function) { + case TRIM_ARRAY: + case ARRAY_SLICE: { + Expression arg = args[0]; + type = arg.getType(); + int t = type.getValueType(); + if (t != Value.ARRAY && t != Value.NULL) { + throw Store.getInvalidExpressionTypeException(getName() + " array argument", arg); + } + break; + } + case ARRAY_CONTAINS: + type = TypeInfo.TYPE_BOOLEAN; + break; + default: + throw DbException.getInternalError("function=" + function); + } + if (allConst) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/BitFunction.java b/h2/src/main/org/h2/expression/function/BitFunction.java new file mode 100644 index 0000000..ac9cd23 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/BitFunction.java @@ -0,0 +1,725 @@ +/* + * 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.expression.function; + +import java.util.Arrays; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +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; +import org.h2.value.Value; +import org.h2.value.ValueBigint; +import org.h2.value.ValueBinary; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueInteger; +import org.h2.value.ValueSmallint; +import org.h2.value.ValueTinyint; +import org.h2.value.ValueVarbinary; + +/** + * A bitwise function. + */ +public final class BitFunction extends Function1_2 { + + /** + * BITAND() (non-standard). + */ + public static final int BITAND = 0; + + /** + * BITOR() (non-standard). + */ + public static final int BITOR = BITAND + 1; + + /** + * BITXOR() (non-standard). + */ + public static final int BITXOR = BITOR + 1; + + /** + * BITNOT() (non-standard). + */ + public static final int BITNOT = BITXOR + 1; + + /** + * BITNAND() (non-standard). + */ + public static final int BITNAND = BITNOT + 1; + + /** + * BITNOR() (non-standard). + */ + public static final int BITNOR = BITNAND + 1; + + /** + * BITXNOR() (non-standard). + */ + public static final int BITXNOR = BITNOR + 1; + + /** + * BITGET() (non-standard). + */ + public static final int BITGET = BITXNOR + 1; + + /** + * BITCOUNT() (non-standard). + */ + public static final int BITCOUNT = BITGET + 1; + + /** + * LSHIFT() (non-standard). + */ + public static final int LSHIFT = BITCOUNT + 1; + + /** + * RSHIFT() (non-standard). + */ + public static final int RSHIFT = LSHIFT + 1; + + /** + * ULSHIFT() (non-standard). + */ + public static final int ULSHIFT = RSHIFT + 1; + + /** + * URSHIFT() (non-standard). + */ + public static final int URSHIFT = ULSHIFT + 1; + + /** + * ROTATELEFT() (non-standard). + */ + public static final int ROTATELEFT = URSHIFT + 1; + + /** + * ROTATERIGHT() (non-standard). + */ + public static final int ROTATERIGHT = ROTATELEFT + 1; + + private static final String[] NAMES = { // + "BITAND", "BITOR", "BITXOR", "BITNOT", "BITNAND", "BITNOR", "BITXNOR", "BITGET", "BITCOUNT", "LSHIFT", + "RSHIFT", "ULSHIFT", "URSHIFT", "ROTATELEFT", "ROTATERIGHT" // + }; + + private final int function; + + public BitFunction(Expression arg1, Expression arg2, int function) { + super(arg1, arg2); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session, Value v1, Value v2) { + switch (function) { + case BITGET: + return bitGet(v1, v2); + case BITCOUNT: + return bitCount(v1); + case LSHIFT: + return shift(v1, v2.getLong(), false); + case RSHIFT: { + long offset = v2.getLong(); + return shift(v1, offset != Long.MIN_VALUE ? -offset : Long.MAX_VALUE, false); + } + case ULSHIFT: + return shift(v1, v2.getLong(), true); + case URSHIFT: + return shift(v1, -v2.getLong(), true); + case ROTATELEFT: + return rotate(v1, v2.getLong(), false); + case ROTATERIGHT: + return rotate(v1, v2.getLong(), true); + } + return getBitwise(function, type, v1, v2); + } + + private static ValueBoolean bitGet(Value v1, Value v2) { + long offset = v2.getLong(); + boolean b; + if (offset >= 0L) { + switch (v1.getValueType()) { + case Value.BINARY: + case Value.VARBINARY: { + byte[] bytes = v1.getBytesNoCopy(); + int bit = (int) (offset & 0x7); + offset >>>= 3; + b = offset < bytes.length && (bytes[(int) offset] & (1 << bit)) != 0; + break; + } + case Value.TINYINT: + b = offset < 8 && (v1.getByte() & (1 << offset)) != 0; + break; + case Value.SMALLINT: + b = offset < 16 && (v1.getShort() & (1 << offset)) != 0; + break; + case Value.INTEGER: + b = offset < 32 && (v1.getInt() & (1 << offset)) != 0; + break; + case Value.BIGINT: + b = (v1.getLong() & (1L << offset)) != 0; + break; + default: + throw DbException.getInvalidValueException("bit function parameter", v1.getTraceSQL()); + } + } else { + b = false; + } + return ValueBoolean.get(b); + } + + private static ValueBigint bitCount(Value v1) { + long c; + switch (v1.getValueType()) { + case Value.BINARY: + case Value.VARBINARY: { + byte[] bytes = v1.getBytesNoCopy(); + int l = bytes.length; + c = 0L; + int blocks = l >>> 3; + for (int i = 0; i < blocks; i++) { + c += Long.bitCount(Bits.readLong(bytes, i)); + } + for (int i = blocks << 3; i < l; i++) { + c += Integer.bitCount(bytes[i] & 0xff); + } + break; + } + case Value.TINYINT: + c = Integer.bitCount(v1.getByte() & 0xff); + break; + case Value.SMALLINT: + c = Integer.bitCount(v1.getShort() & 0xffff); + break; + case Value.INTEGER: + c = Integer.bitCount(v1.getInt()); + break; + case Value.BIGINT: + c = Long.bitCount(v1.getLong()); + break; + default: + throw DbException.getInvalidValueException("bit function parameter", v1.getTraceSQL()); + } + return ValueBigint.get(c); + } + + private static Value shift(Value v1, long offset, boolean unsigned) { + if (offset == 0L) { + return v1; + } + int vt = v1.getValueType(); + switch (vt) { + case Value.BINARY: + case Value.VARBINARY: { + byte[] bytes = v1.getBytesNoCopy(); + int length = bytes.length; + if (length == 0) { + return v1; + } + byte[] newBytes = new byte[length]; + if (offset > -8L * length && offset < 8L * length) { + if (offset > 0) { + int nBytes = (int) (offset >> 3); + int nBits = ((int) offset) & 0x7; + if (nBits == 0) { + System.arraycopy(bytes, nBytes, newBytes, 0, length - nBytes); + } else { + int nBits2 = 8 - nBits; + int dstIndex = 0, srcIndex = nBytes; + length--; + while (srcIndex < length) { + newBytes[dstIndex++] = (byte) (bytes[srcIndex++] << nBits + | (bytes[srcIndex] & 0xff) >>> nBits2); + } + newBytes[dstIndex] = (byte) (bytes[srcIndex] << nBits); + } + } else { + offset = -offset; + int nBytes = (int) (offset >> 3); + int nBits = ((int) offset) & 0x7; + if (nBits == 0) { + System.arraycopy(bytes, 0, newBytes, nBytes, length - nBytes); + } else { + int nBits2 = 8 - nBits; + int dstIndex = nBytes, srcIndex = 0; + newBytes[dstIndex++] = (byte) ((bytes[srcIndex] & 0xff) >>> nBits); + while (dstIndex < length) { + newBytes[dstIndex++] = (byte) (bytes[srcIndex++] << nBits2 + | (bytes[srcIndex] & 0xff) >>> nBits); + } + } + } + } + return vt == Value.BINARY ? ValueBinary.getNoCopy(newBytes) : ValueVarbinary.getNoCopy(newBytes); + } + case Value.TINYINT: { + byte v; + if (offset < 8) { + v = v1.getByte(); + if (offset > -8) { + if (offset > 0) { + v <<= (int) offset; + } else if (unsigned) { + v = (byte) ((v & 0xFF) >>> (int) -offset); + } else { + v >>= (int) -offset; + } + } else if (unsigned) { + v = 0; + } else { + v >>= 7; + } + } else { + v = 0; + } + return ValueTinyint.get(v); + } + case Value.SMALLINT: { + short v; + if (offset < 16) { + v = v1.getShort(); + if (offset > -16) { + if (offset > 0) { + v <<= (int) offset; + } else if (unsigned) { + v = (short) ((v & 0xFFFF) >>> (int) -offset); + } else { + v >>= (int) -offset; + } + } else if (unsigned) { + v = 0; + } else { + v >>= 15; + } + } else { + v = 0; + } + return ValueSmallint.get(v); + } + case Value.INTEGER: { + int v; + if (offset < 32) { + v = v1.getInt(); + if (offset > -32) { + if (offset > 0) { + v <<= (int) offset; + } else if (unsigned) { + v >>>= (int) -offset; + } else { + v >>= (int) -offset; + } + } else if (unsigned) { + v = 0; + } else { + v >>= 31; + } + } else { + v = 0; + } + return ValueInteger.get(v); + } + case Value.BIGINT: { + long v; + if (offset < 64) { + v = v1.getLong(); + if (offset > -64) { + if (offset > 0) { + v <<= offset; + } else if (unsigned) { + v >>>= -offset; + } else { + v >>= -offset; + } + } else if (unsigned) { + v = 0; + } else { + v >>= 63; + } + } else { + v = 0; + } + return ValueBigint.get(v); + } + default: + throw DbException.getInvalidValueException("bit function parameter", v1.getTraceSQL()); + } + } + + private static Value rotate(Value v1, long offset, boolean right) { + int vt = v1.getValueType(); + switch (vt) { + case Value.BINARY: + case Value.VARBINARY: { + byte[] bytes = v1.getBytesNoCopy(); + int length = bytes.length; + if (length == 0) { + return v1; + } + long bitLength = length << 3L; + offset %= bitLength; + if (right) { + offset = -offset; + } + if (offset == 0L) { + return v1; + } else if (offset < 0) { + offset += bitLength; + } + byte[] newBytes = new byte[length]; + int nBytes = (int) (offset >> 3); + int nBits = ((int) offset) & 0x7; + if (nBits == 0) { + System.arraycopy(bytes, nBytes, newBytes, 0, length - nBytes); + System.arraycopy(bytes, 0, newBytes, length - nBytes, nBytes); + } else { + int nBits2 = 8 - nBits; + for (int dstIndex = 0, srcIndex = nBytes; dstIndex < length;) { + newBytes[dstIndex++] = (byte) (bytes[srcIndex] << nBits + | (bytes[srcIndex = (srcIndex + 1) % length] & 0xFF) >>> nBits2); + } + } + return vt == Value.BINARY ? ValueBinary.getNoCopy(newBytes) : ValueVarbinary.getNoCopy(newBytes); + } + case Value.TINYINT: { + int o = (int) offset; + if (right) { + o = -o; + } + if ((o &= 0x7) == 0) { + return v1; + } + int v = v1.getByte() & 0xFF; + return ValueTinyint.get((byte) ((v << o) | (v >>> 8 - o))); + } + case Value.SMALLINT: { + int o = (int) offset; + if (right) { + o = -o; + } + if ((o &= 0xF) == 0) { + return v1; + } + int v = v1.getShort() & 0xFFFF; + return ValueSmallint.get((short) ((v << o) | (v >>> 16 - o))); + } + case Value.INTEGER: { + int o = (int) offset; + if (right) { + o = -o; + } + if ((o &= 0x1F) == 0) { + return v1; + } + return ValueInteger.get(Integer.rotateLeft(v1.getInt(), o)); + } + case Value.BIGINT: { + int o = (int) offset; + if (right) { + o = -o; + } + if ((o &= 0x3F) == 0) { + return v1; + } + return ValueBigint.get(Long.rotateLeft(v1.getLong(), o)); + } + default: + throw DbException.getInvalidValueException("bit function parameter", v1.getTraceSQL()); + } + } + + /** + * Computes the value of bitwise function. + * + * @param function + * one of {@link #BITAND}, {@link #BITOR}, {@link #BITXOR}, + * {@link #BITNOT}, {@link #BITNAND}, {@link #BITNOR}, + * {@link #BITXNOR} + * @param type + * the type of result + * @param v1 + * the value of first argument + * @param v2 + * the value of second argument, or {@code null} + * @return the resulting value + */ + public static Value getBitwise(int function, TypeInfo type, Value v1, Value v2) { + return type.getValueType() < Value.TINYINT ? getBinaryString(function, type, v1, v2) + : getNumeric(function, type, v1, v2); + } + + private static Value getBinaryString(int function, TypeInfo type, Value v1, Value v2) { + byte[] bytes; + if (function == BITNOT) { + bytes = v1.getBytes(); + for (int i = 0, l = bytes.length; i < l; i++) { + bytes[i] = (byte) ~bytes[i]; + } + } else { + byte[] bytes1 = v1.getBytesNoCopy(), bytes2 = v2.getBytesNoCopy(); + int length1 = bytes1.length, length2 = bytes2.length; + int min, max; + if (length1 <= length2) { + min = length1; + max = length2; + } else { + min = length2; + max = length1; + byte[] t = bytes1; + bytes1 = bytes2; + bytes2 = t; + } + int limit = (int) type.getPrecision(); + if (min > limit) { + max = min = limit; + } else if (max > limit) { + max = limit; + } + bytes = new byte[max]; + int i = 0; + switch (function) { + case BITAND: + for (; i < min; i++) { + bytes[i] = (byte) (bytes1[i] & bytes2[i]); + } + break; + case BITOR: + for (; i < min; i++) { + bytes[i] = (byte) (bytes1[i] | bytes2[i]); + } + System.arraycopy(bytes2, i, bytes, i, max - i); + break; + case BITXOR: + for (; i < min; i++) { + bytes[i] = (byte) (bytes1[i] ^ bytes2[i]); + } + System.arraycopy(bytes2, i, bytes, i, max - i); + break; + case BITNAND: + for (; i < min; i++) { + bytes[i] = (byte) ~(bytes1[i] & bytes2[i]); + } + Arrays.fill(bytes, i, max, (byte) -1); + break; + case BITNOR: + for (; i < min; i++) { + bytes[i] = (byte) ~(bytes1[i] | bytes2[i]); + } + for (; i < max; i++) { + bytes[i] = (byte) ~bytes2[i]; + } + break; + case BITXNOR: + for (; i < min; i++) { + bytes[i] = (byte) ~(bytes1[i] ^ bytes2[i]); + } + for (; i < max; i++) { + bytes[i] = (byte) ~bytes2[i]; + } + break; + default: + throw DbException.getInternalError("function=" + function); + } + } + return type.getValueType() == Value.BINARY ? ValueBinary.getNoCopy(bytes) : ValueVarbinary.getNoCopy(bytes); + } + + private static Value getNumeric(int function, TypeInfo type, Value v1, Value v2) { + long l1 = v1.getLong(); + switch (function) { + case BITAND: + l1 &= v2.getLong(); + break; + case BITOR: + l1 |= v2.getLong(); + break; + case BITXOR: + l1 ^= v2.getLong(); + break; + case BITNOT: + l1 = ~l1; + break; + case BITNAND: + l1 = ~(l1 & v2.getLong()); + break; + case BITNOR: + l1 = ~(l1 | v2.getLong()); + break; + case BITXNOR: + l1 = ~(l1 ^ v2.getLong()); + break; + default: + throw DbException.getInternalError("function=" + function); + } + switch (type.getValueType()) { + case Value.TINYINT: + return ValueTinyint.get((byte) l1); + case Value.SMALLINT: + return ValueSmallint.get((short) l1); + case Value.INTEGER: + return ValueInteger.get((int) l1); + case Value.BIGINT: + return ValueBigint.get(l1); + default: + throw DbException.getInternalError(); + } + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + if (right != null) { + right = right.optimize(session); + } + switch (function) { + case BITNOT: + return optimizeNot(session); + case BITGET: + type = TypeInfo.TYPE_BOOLEAN; + break; + case BITCOUNT: + type = TypeInfo.TYPE_BIGINT; + break; + case LSHIFT: + case RSHIFT: + case ULSHIFT: + case URSHIFT: + case ROTATELEFT: + case ROTATERIGHT: + type = checkArgType(left); + break; + default: + type = getCommonType(left, right); + break; + } + if (left.isConstant() && (right == null || right.isConstant())) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + private Expression optimizeNot(SessionLocal session) { + type = checkArgType(left); + if (left.isConstant()) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } else if (left instanceof BitFunction) { + BitFunction l = (BitFunction) left; + int f = l.function; + switch (f) { + case BITAND: + case BITOR: + case BITXOR: + f += BITNAND - BITAND; + break; + case BITNOT: + return l.left; + case BITNAND: + case BITNOR: + case BITXNOR: + f -= BITNAND - BITAND; + break; + default: + return this; + } + return new BitFunction(l.left, l.right, f).optimize(session); + } else if (left instanceof Aggregate) { + Aggregate l = (Aggregate) left; + AggregateType t; + switch (l.getAggregateType()) { + case BIT_AND_AGG: + t = AggregateType.BIT_NAND_AGG; + break; + case BIT_OR_AGG: + t = AggregateType.BIT_NOR_AGG; + break; + case BIT_XOR_AGG: + t = AggregateType.BIT_XNOR_AGG; + break; + case BIT_NAND_AGG: + t = AggregateType.BIT_AND_AGG; + break; + case BIT_NOR_AGG: + t = AggregateType.BIT_OR_AGG; + break; + case BIT_XNOR_AGG: + t = AggregateType.BIT_XOR_AGG; + break; + default: + return this; + } + return new Aggregate(t, new Expression[] { l.getSubexpression(0) }, l.getSelect(), l.isDistinct()) + .optimize(session); + } + return this; + } + + private static TypeInfo getCommonType(Expression arg1, Expression arg2) { + TypeInfo t1 = checkArgType(arg1), t2 = checkArgType(arg2); + int vt1 = t1.getValueType(), vt2 = t2.getValueType(); + boolean bs = DataType.isBinaryStringType(vt1); + if (bs != DataType.isBinaryStringType(vt2)) { + throw DbException.getInvalidValueException("bit function parameters", + t2.getSQL(t1.getSQL(new StringBuilder(), TRACE_SQL_FLAGS).append(" vs "), TRACE_SQL_FLAGS) + .toString()); + } + if (bs) { + long precision; + if (vt1 == Value.BINARY) { + precision = t1.getDeclaredPrecision(); + if (vt2 == Value.BINARY) { + precision = Math.max(precision, t2.getDeclaredPrecision()); + } + } else { + if (vt2 == Value.BINARY) { + vt1 = Value.BINARY; + precision = t2.getDeclaredPrecision(); + } else { + long precision1 = t1.getDeclaredPrecision(), precision2 = t2.getDeclaredPrecision(); + precision = precision1 <= 0L || precision2 <= 0L ? -1L : Math.max(precision1, precision2); + } + } + return TypeInfo.getTypeInfo(vt1, precision, 0, null); + } + return TypeInfo.getTypeInfo(Math.max(vt1, vt2)); + } + + /** + * Checks the type of an argument of bitwise function (one of + * {@link #BITAND}, {@link #BITOR}, {@link #BITXOR}, {@link #BITNOT}, + * {@link #BITNAND}, {@link #BITNOR}, {@link #BITXNOR}). + * + * @param arg + * the argument + * @return the type of the specified argument + * @throws DbException + * if argument type is not supported by bitwise functions + */ + public static TypeInfo checkArgType(Expression arg) { + TypeInfo t = arg.getType(); + switch (t.getValueType()) { + case Value.NULL: + case Value.BINARY: + case Value.VARBINARY: + case Value.TINYINT: + case Value.SMALLINT: + case Value.INTEGER: + case Value.BIGINT: + return t; + } + throw Store.getInvalidExpressionTypeException("bit function argument", arg); + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/BuiltinFunctions.java b/h2/src/main/org/h2/expression/function/BuiltinFunctions.java new file mode 100644 index 0000000..efb1187 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/BuiltinFunctions.java @@ -0,0 +1,136 @@ +/* + * 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.expression.function; + +import java.util.HashSet; + +import org.h2.engine.Database; +import org.h2.mode.ModeFunction; + +/** + * Maintains the list of built-in functions. + */ +public final class BuiltinFunctions { + + private static final HashSet FUNCTIONS; + + static { + String[] names = { // + // MathFunction + "ABS", "MOD", "FLOOR", "CEIL", "ROUND", "ROUNDMAGIC", "SIGN", "TRUNC", "TRUNCATE", + // MathFunction1 + "SIN", "COS", "TAN", "COT", "SINH", "COSH", "TANH", "ASIN", "ACOS", "ATAN", // + "LOG10", "LN", "EXP", "SQRT", "DEGREES", "RADIANS", + // MathFunction2 + "ATAN2", "LOG", "POWER", + // BitFunction + "BITAND", "BITOR", "BITXOR", "BITNOT", "BITNAND", "BITNOR", "BITXNOR", "BITGET", "BITCOUNT", "LSHIFT", + "RSHIFT", "ULSHIFT", "URSHIFT", "ROTATELEFT", "ROTATERIGHT", + // DateTimeFunction + "EXTRACT", "DATE_TRUNC", "DATEADD", "DATEDIFF", // + "TIMESTAMPADD", "TIMESTAMPDIFF", + // DateTimeFormatFunction + "FORMATDATETIME", "PARSEDATETIME", + // DayMonthNameFunction + "DAYNAME", "MONTHNAME", + // CardinalityExpression + "CARDINALITY", "ARRAY_MAX_CARDINALITY", + // StringFunction + "LOCATE", "INSERT", "REPLACE", "LPAD", "RPAD", "TRANSLATE", + // StringFunction1 + "UPPER", "LOWER", "ASCII", "CHAR", "CHR", "STRINGENCODE", "STRINGDECODE", "STRINGTOUTF8", + "UTF8TOSTRING", "HEXTORAW", "RAWTOHEX", "SPACE", "QUOTE_IDENT", + // StringFunction2 + /* LEFT and RIGHT are keywords */ "REPEAT", + // SubstringFunction + "SUBSTRING", + // ToCharFunction + "TO_CHAR", + // LengthFunction + "CHAR_LENGTH", "CHARACTER_LENGTH", "LENGTH", "OCTET_LENGTH", "BIT_LENGTH", + // TrimFunction + "TRIM", + // RegexpFunction + "REGEXP_LIKE", "REGEXP_REPLACE", "REGEXP_SUBSTR", + // XMLFunction + "XMLATTR", "XMLCDATA", "XMLCOMMENT", "XMLNODE", "XMLSTARTDOC", "XMLTEXT", + // ArrayFunction + "TRIM_ARRAY", "ARRAY_CONTAINS", "ARRAY_SLICE", + // CompressFunction + "COMPRESS", "EXPAND", + // SoundexFunction + "SOUNDEX", "DIFFERENCE", + // JsonConstructorFunction + "JSON_OBJECT", "JSON_ARRAY", + // CryptFunction + "ENCRYPT", "DECRYPT", + // CoalesceFunction + "COALESCE", "GREATEST", "LEAST", + // NullIfFunction + "NULLIF", + // ConcatFunction + "CONCAT", "CONCAT_WS", + // HashFunction + "HASH", "ORA_HASH", + // RandFunction + "RAND", "RANDOM", "SECURE_RAND", "RANDOM_UUID", "UUID", + // SessionControlFunction + "ABORT_SESSION", "CANCEL_SESSION", + // SysInfoFunction + "AUTOCOMMIT", "DATABASE_PATH", "H2VERSION", "LOCK_MODE", "LOCK_TIMEOUT", "MEMORY_FREE", "MEMORY_USED", + "READONLY", "SESSION_ID", "TRANSACTION_ID", + // TableInfoFunction + "DISK_SPACE_USED", "ESTIMATED_ENVELOPE", + // FileFunction + "FILE_READ", "FILE_WRITE", + // DataTypeSQLFunction + "DATA_TYPE_SQL", + // DBObjectFunction + "DB_OBJECT_ID", "DB_OBJECT_SQL", + // CSVWriteFunction + "CSVWRITE", + // SetFunction + /* SET is keyword */ + // SignalFunction + "SIGNAL", + // TruncateValueFunction + "TRUNCATE_VALUE", + // CompatibilitySequenceValueFunction + "CURRVAL", "NEXTVAL", + // Constants + "ZERO", "PI", + // ArrayTableFunction + "UNNEST", /* TABLE is a keyword */ "TABLE_DISTINCT", + // CSVReadFunction + "CSVREAD", + // LinkSchemaFunction + "LINK_SCHEMA", + // + }; + HashSet set = new HashSet<>(128); + for (String n : names) { + set.add(n); + } + FUNCTIONS = set; + } + + /** + * Returns whether specified function is a non-keyword built-in function. + * + * @param database + * the database + * @param upperName + * the name of the function in upper case + * @return {@code true} if it is + */ + public static boolean isBuiltinFunction(Database database, String upperName) { + return FUNCTIONS.contains(upperName) || ModeFunction.getFunction(database, upperName) != null; + } + + private BuiltinFunctions() { + } + +} diff --git a/h2/src/main/org/h2/expression/function/CSVWriteFunction.java b/h2/src/main/org/h2/expression/function/CSVWriteFunction.java new file mode 100644 index 0000000..ce1e379 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/CSVWriteFunction.java @@ -0,0 +1,126 @@ +/* + * 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.expression.function; + +import java.sql.Connection; +import java.sql.SQLException; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.message.DbException; +import org.h2.tools.Csv; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueInteger; + +/** + * A CSVWRITE function. + */ +public final class CSVWriteFunction extends FunctionN { + + public CSVWriteFunction() { + super(new Expression[4]); + } + + @Override + public Value getValue(SessionLocal session) { + session.getUser().checkAdmin(); + Connection conn = session.createConnection(false); + Csv csv = new Csv(); + String options = getValue(session, 2); + String charset = null; + if (options != null && options.indexOf('=') >= 0) { + charset = csv.setOptions(options); + } else { + charset = options; + String fieldSeparatorWrite = getValue(session, 3); + String fieldDelimiter = getValue(session, 4); + String escapeCharacter = getValue(session, 5); + String nullString = getValue(session, 6); + String lineSeparator = getValue(session, 7); + setCsvDelimiterEscape(csv, fieldSeparatorWrite, fieldDelimiter, escapeCharacter); + csv.setNullString(nullString); + if (lineSeparator != null) { + csv.setLineSeparator(lineSeparator); + } + } + try { + return ValueInteger.get(csv.write(conn, args[0].getValue(session).getString(), + args[1].getValue(session).getString(), charset)); + } catch (SQLException e) { + throw DbException.convert(e); + } + } + + private String getValue(SessionLocal session, int index) { + return index < args.length ? args[index].getValue(session).getString() : null; + } + + /** + * Sets delimiter options. + * + * @param csv + * the CSV utility instance + * @param fieldSeparator + * the field separator + * @param fieldDelimiter + * the field delimiter + * @param escapeCharacter + * the escape character + */ + public static void setCsvDelimiterEscape(Csv csv, String fieldSeparator, String fieldDelimiter, + String escapeCharacter) { + if (fieldSeparator != null) { + csv.setFieldSeparatorWrite(fieldSeparator); + if (!fieldSeparator.isEmpty()) { + char fs = fieldSeparator.charAt(0); + csv.setFieldSeparatorRead(fs); + } + } + if (fieldDelimiter != null) { + char fd = fieldDelimiter.isEmpty() ? 0 : fieldDelimiter.charAt(0); + csv.setFieldDelimiter(fd); + } + if (escapeCharacter != null) { + char ec = escapeCharacter.isEmpty() ? 0 : escapeCharacter.charAt(0); + csv.setEscapeCharacter(ec); + } + } + + @Override + public Expression optimize(SessionLocal session) { + optimizeArguments(session, false); + int len = args.length; + if (len < 2 || len > 8) { + throw DbException.get(ErrorCode.INVALID_PARAMETER_COUNT_2, getName(), "2..8"); + } + type = TypeInfo.TYPE_INTEGER; + return this; + } + + @Override + public String getName() { + return "CSVWRITE"; + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + if (!super.isEverything(visitor)) { + return false; + } + switch (visitor.getType()) { + case ExpressionVisitor.DETERMINISTIC: + case ExpressionVisitor.QUERY_COMPARABLE: + case ExpressionVisitor.READONLY: + return false; + default: + return true; + } + } + +} diff --git a/h2/src/main/org/h2/expression/function/CardinalityExpression.java b/h2/src/main/org/h2/expression/function/CardinalityExpression.java new file mode 100644 index 0000000..f565a80 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/CardinalityExpression.java @@ -0,0 +1,78 @@ +/* + * 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.expression.function; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.message.DbException; +import org.h2.util.MathUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueArray; +import org.h2.value.ValueInteger; +import org.h2.value.ValueNull; + +/** + * Cardinality expression. + */ +public final class CardinalityExpression extends Function1 { + + private final boolean max; + + /** + * Creates new instance of cardinality expression. + * + * @param arg + * argument + * @param max + * {@code false} for {@code CARDINALITY}, {@code true} for + * {@code ARRAY_MAX_CARDINALITY} + */ + public CardinalityExpression(Expression arg, boolean max) { + super(arg); + this.max = max; + } + + @Override + public Value getValue(SessionLocal session) { + int result; + if (max) { + TypeInfo t = arg.getType(); + if (t.getValueType() == Value.ARRAY) { + result = MathUtils.convertLongToInt(t.getPrecision()); + } else { + throw DbException.getInvalidValueException("array", arg.getValue(session).getTraceSQL()); + } + } else { + Value v = arg.getValue(session); + if (v == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + if (v.getValueType() != Value.ARRAY) { + throw DbException.getInvalidValueException("array", v.getTraceSQL()); + } + result = ((ValueArray) v).getList().length; + } + return ValueInteger.get(result); + } + + @Override + public Expression optimize(SessionLocal session) { + arg = arg.optimize(session); + type = TypeInfo.TYPE_INTEGER; + if (arg.isConstant()) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public String getName() { + return max ? "ARRAY_MAX_CARDINALITY" : "CARDINALITY"; + } + +} diff --git a/h2/src/main/org/h2/expression/function/CastSpecification.java b/h2/src/main/org/h2/expression/function/CastSpecification.java new file mode 100644 index 0000000..d0a54bf --- /dev/null +++ b/h2/src/main/org/h2/expression/function/CastSpecification.java @@ -0,0 +1,115 @@ +/* + * 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.expression.function; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.expression.ValueExpression; +import org.h2.schema.Domain; +import org.h2.table.Column; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * A cast specification. + */ +public final class CastSpecification extends Function1 { + + private Domain domain; + + public CastSpecification(Expression arg, Column column) { + super(arg); + type = column.getType(); + domain = column.getDomain(); + } + + public CastSpecification(Expression arg, TypeInfo type) { + super(arg); + this.type = type; + } + + @Override + public Value getValue(SessionLocal session) { + Value v = arg.getValue(session).castTo(type, session); + if (domain != null) { + domain.checkConstraints(session, v); + } + return v; + } + + @Override + public Expression optimize(SessionLocal session) { + arg = arg.optimize(session); + if (arg.isConstant()) { + Value v = getValue(session); + if (v == ValueNull.INSTANCE || canOptimizeCast(arg.getType().getValueType(), type.getValueType())) { + return TypedValueExpression.get(v, type); + } + } + return this; + } + + @Override + public boolean isConstant() { + return arg instanceof ValueExpression && canOptimizeCast(arg.getType().getValueType(), type.getValueType()); + } + + private static boolean canOptimizeCast(int src, int dst) { + switch (src) { + case Value.TIME: + switch (dst) { + case Value.TIME_TZ: + case Value.TIMESTAMP: + case Value.TIMESTAMP_TZ: + return false; + } + break; + case Value.TIME_TZ: + switch (dst) { + case Value.TIME: + case Value.TIMESTAMP: + case Value.TIMESTAMP_TZ: + return false; + } + break; + case Value.DATE: + if (dst == Value.TIMESTAMP_TZ) { + return false; + } + break; + case Value.TIMESTAMP: + switch (dst) { + case Value.TIME_TZ: + case Value.TIMESTAMP_TZ: + return false; + } + break; + case Value.TIMESTAMP_TZ: + switch (dst) { + case Value.TIME: + case Value.DATE: + case Value.TIMESTAMP: + return false; + } + } + return true; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + builder.append("CAST("); + arg.getUnenclosedSQL(builder, arg instanceof ValueExpression ? sqlFlags | NO_CASTS : sqlFlags).append(" AS "); + return (domain != null ? domain : type).getSQL(builder, sqlFlags).append(')'); + } + + @Override + public String getName() { + return "CAST"; + } + +} diff --git a/h2/src/main/org/h2/expression/function/CoalesceFunction.java b/h2/src/main/org/h2/expression/function/CoalesceFunction.java new file mode 100644 index 0000000..3d5377f --- /dev/null +++ b/h2/src/main/org/h2/expression/function/CoalesceFunction.java @@ -0,0 +1,111 @@ +/* + * 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.expression.function; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.message.DbException; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * A COALESCE, GREATEST, or LEAST function. + */ +public final class CoalesceFunction extends FunctionN { + + /** + * COALESCE(). + */ + public static final int COALESCE = 0; + + /** + * GREATEST() (non-standard). + */ + public static final int GREATEST = COALESCE + 1; + + /** + * LEAST() (non-standard). + */ + public static final int LEAST = GREATEST + 1; + + private static final String[] NAMES = { // + "COALESCE", "GREATEST", "LEAST" // + }; + + private final int function; + + public CoalesceFunction(int function) { + this(function, new Expression[4]); + } + + public CoalesceFunction(int function, Expression... args) { + super(args); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session) { + Value v = ValueNull.INSTANCE; + switch (function) { + case COALESCE: { + for (int i = 0, l = args.length; i < l; i++) { + Value v2 = args[i].getValue(session); + if (v2 != ValueNull.INSTANCE) { + v = v2.convertTo(type, session); + break; + } + } + break; + } + case GREATEST: + case LEAST: { + for (int i = 0, l = args.length; i < l; i++) { + Value v2 = args[i].getValue(session); + if (v2 != ValueNull.INSTANCE) { + v2 = v2.convertTo(type, session); + if (v == ValueNull.INSTANCE) { + v = v2; + } else { + int comp = session.compareTypeSafe(v, v2); + if (function == GREATEST) { + if (comp < 0) { + v = v2; + } + } else if (comp > 0) { + v = v2; + } + } + } + } + break; + } + default: + throw DbException.getInternalError("function=" + function); + } + return v; + } + + @Override + public Expression optimize(SessionLocal session) { + boolean allConst = optimizeArguments(session, true); + type = TypeInfo.getHigherType(args); + if (type.getValueType() <= Value.NULL) { + type = TypeInfo.TYPE_VARCHAR; + } + if (allConst) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/CompatibilitySequenceValueFunction.java b/h2/src/main/org/h2/expression/function/CompatibilitySequenceValueFunction.java new file mode 100644 index 0000000..2d9fd62 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/CompatibilitySequenceValueFunction.java @@ -0,0 +1,100 @@ +/* + * 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.expression.function; + +import org.h2.command.Parser; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.expression.ExpressionVisitor; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.schema.Sequence; +import org.h2.util.StringUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * NEXTVAL() and CURRVAL() compatibility functions. + */ +public final class CompatibilitySequenceValueFunction extends Function1_2 { + + private final boolean current; + + public CompatibilitySequenceValueFunction(Expression left, Expression right, boolean current) { + super(left, right); + this.current = current; + } + + @Override + public Value getValue(SessionLocal session, Value v1, Value v2) { + String schemaName, sequenceName; + if (v2 == null) { + Parser p = new Parser(session); + String sql = v1.getString(); + Expression expr = p.parseExpression(sql); + if (expr instanceof ExpressionColumn) { + ExpressionColumn seq = (ExpressionColumn) expr; + schemaName = seq.getOriginalTableAliasName(); + if (schemaName == null) { + schemaName = session.getCurrentSchemaName(); + sequenceName = sql; + } else { + sequenceName = seq.getColumnName(session, -1); + } + } else { + throw DbException.getSyntaxError(sql, 1); + } + } else { + schemaName = v1.getString(); + sequenceName = v2.getString(); + } + Database database = session.getDatabase(); + Schema s = database.findSchema(schemaName); + if (s == null) { + schemaName = StringUtils.toUpperEnglish(schemaName); + s = database.getSchema(schemaName); + } + Sequence seq = s.findSequence(sequenceName); + if (seq == null) { + sequenceName = StringUtils.toUpperEnglish(sequenceName); + seq = s.getSequence(sequenceName); + } + return (current ? session.getCurrentValueFor(seq) : session.getNextValueFor(seq, null)).convertTo(type); + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + if (right != null) { + right = right.optimize(session); + } + type = session.getMode().decimalSequences ? TypeInfo.TYPE_NUMERIC_BIGINT : TypeInfo.TYPE_BIGINT; + return this; + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + switch (visitor.getType()) { + case ExpressionVisitor.INDEPENDENT: + case ExpressionVisitor.DETERMINISTIC: + case ExpressionVisitor.QUERY_COMPARABLE: + return false; + case ExpressionVisitor.READONLY: + if (!current) { + return false; + } + } + return super.isEverything(visitor); + } + + @Override + public String getName() { + return current ? "CURRVAL" : "NEXTVAL"; + } + +} diff --git a/h2/src/main/org/h2/expression/function/CompressFunction.java b/h2/src/main/org/h2/expression/function/CompressFunction.java new file mode 100644 index 0000000..348c872 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/CompressFunction.java @@ -0,0 +1,77 @@ +/* + * 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.expression.function; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.message.DbException; +import org.h2.tools.CompressTool; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueVarbinary; + +/** + * A COMPRESS or EXPAND function. + */ +public final class CompressFunction extends Function1_2 { + + /** + * COMPRESS() (non-standard). + */ + public static final int COMPRESS = 0; + + /** + * EXPAND() (non-standard). + */ + public static final int EXPAND = COMPRESS + 1; + + private static final String[] NAMES = { // + "COMPRESS", "EXPAND" // + }; + + private final int function; + + public CompressFunction(Expression arg1, Expression arg2, int function) { + super(arg1, arg2); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session, Value v1, Value v2) { + switch (function) { + case COMPRESS: + v1 = ValueVarbinary.getNoCopy( + CompressTool.getInstance().compress(v1.getBytesNoCopy(), v2 != null ? v2.getString() : null)); + break; + case EXPAND: + v1 = ValueVarbinary.getNoCopy(CompressTool.getInstance().expand(v1.getBytesNoCopy())); + break; + default: + throw DbException.getInternalError("function=" + function); + } + return v1; + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + if (right != null) { + right = right.optimize(session); + } + type = TypeInfo.TYPE_VARBINARY; + if (left.isConstant() && (right == null || right.isConstant())) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/ConcatFunction.java b/h2/src/main/org/h2/expression/function/ConcatFunction.java new file mode 100644 index 0000000..14f5646 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/ConcatFunction.java @@ -0,0 +1,118 @@ +/* + * 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.expression.function; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueNull; +import org.h2.value.ValueVarchar; + +/** + * A CONCAT or CONCAT_WS function. + */ +public final class ConcatFunction extends FunctionN { + + /** + * CONCAT() (non-standard). + */ + public static final int CONCAT = 0; + + /** + * CONCAT_WS() (non-standard). + */ + public static final int CONCAT_WS = CONCAT + 1; + + private static final String[] NAMES = { // + "CONCAT", "CONCAT_WS" // + }; + + private final int function; + + public ConcatFunction(int function) { + this(function, new Expression[4]); + } + + public ConcatFunction(int function, Expression... args) { + super(args); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session) { + int i = 0; + String separator = null; + if (function == CONCAT_WS) { + i = 1; + separator = args[0].getValue(session).getString(); + } + StringBuilder builder = new StringBuilder(); + boolean f = false; + for (int l = args.length; i < l; i++) { + Value v = args[i].getValue(session); + if (v != ValueNull.INSTANCE) { + if (separator != null) { + if (f) { + builder.append(separator); + } + f = true; + } + builder.append(v.getString()); + } + } + return ValueVarchar.get(builder.toString(), session); + } + + @Override + public Expression optimize(SessionLocal session) { + boolean allConst = optimizeArguments(session, true); + int i = 0; + long extra = 0L; + if (function == CONCAT_WS) { + i = 1; + extra = getPrecision(0); + } + long precision = 0L; + int l = args.length; + boolean f = false; + for (; i < l; i++) { + if (args[i].isNullConstant()) { + continue; + } + precision = DataType.addPrecision(precision, getPrecision(i)); + if (extra != 0L && f) { + precision = DataType.addPrecision(precision, extra); + } + f = true; + } + type = TypeInfo.getTypeInfo(Value.VARCHAR, precision, 0, null); + if (allConst) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + private long getPrecision(int i) { + TypeInfo t = args[i].getType(); + int valueType = t.getValueType(); + if (valueType == Value.NULL) { + return 0L; + } else if (DataType.isCharacterStringType(valueType)) { + return t.getPrecision(); + } else { + return Long.MAX_VALUE; + } + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/CryptFunction.java b/h2/src/main/org/h2/expression/function/CryptFunction.java new file mode 100644 index 0000000..47fbb96 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/CryptFunction.java @@ -0,0 +1,87 @@ +/* + * 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.expression.function; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.message.DbException; +import org.h2.security.BlockCipher; +import org.h2.security.CipherFactory; +import org.h2.util.MathUtils; +import org.h2.util.Utils; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueVarbinary; + +/** + * An ENCRYPT or DECRYPT function. + */ +public final class CryptFunction extends FunctionN { + + /** + * ENCRYPT() (non-standard). + */ + public static final int ENCRYPT = 0; + + /** + * DECRYPT() (non-standard). + */ + public static final int DECRYPT = ENCRYPT + 1; + + private static final String[] NAMES = { // + "ENCRYPT", "DECRYPT" // + }; + + private final int function; + + public CryptFunction(Expression arg1, Expression arg2, Expression arg3, int function) { + super(new Expression[] { arg1, arg2, arg3 }); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session, Value v1, Value v2, Value v3) { + BlockCipher cipher = CipherFactory.getBlockCipher(v1.getString()); + cipher.setKey(getPaddedArrayCopy(v2.getBytesNoCopy(), cipher.getKeyLength())); + byte[] newData = getPaddedArrayCopy(v3.getBytesNoCopy(), BlockCipher.ALIGN); + switch (function) { + case ENCRYPT: + cipher.encrypt(newData, 0, newData.length); + break; + case DECRYPT: + cipher.decrypt(newData, 0, newData.length); + break; + default: + throw DbException.getInternalError("function=" + function); + } + return ValueVarbinary.getNoCopy(newData); + } + + private static byte[] getPaddedArrayCopy(byte[] data, int blockSize) { + return Utils.copyBytes(data, MathUtils.roundUpInt(data.length, blockSize)); + } + + @Override + public Expression optimize(SessionLocal session) { + boolean allConst = optimizeArguments(session, true); + TypeInfo t = args[2].getType(); + type = DataType.isBinaryStringType(t.getValueType()) + ? TypeInfo.getTypeInfo(Value.VARBINARY, t.getPrecision(), 0, null) + : TypeInfo.TYPE_VARBINARY; + if (allConst) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/CurrentDateTimeValueFunction.java b/h2/src/main/org/h2/expression/function/CurrentDateTimeValueFunction.java new file mode 100644 index 0000000..de11882 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/CurrentDateTimeValueFunction.java @@ -0,0 +1,112 @@ +/* + * 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.expression.function; + +import org.h2.engine.SessionLocal; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.Operation0; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueTime; +import org.h2.value.ValueTimestamp; + +/** + * Current datetime value function. + */ +public final class CurrentDateTimeValueFunction extends Operation0 implements NamedExpression { + + /** + * The function "CURRENT_DATE" + */ + public static final int CURRENT_DATE = 0; + + /** + * The function "CURRENT_TIME" + */ + public static final int CURRENT_TIME = 1; + + /** + * The function "LOCALTIME" + */ + public static final int LOCALTIME = 2; + + /** + * The function "CURRENT_TIMESTAMP" + */ + public static final int CURRENT_TIMESTAMP = 3; + + /** + * The function "LOCALTIMESTAMP" + */ + public static final int LOCALTIMESTAMP = 4; + + private static final int[] TYPES = { Value.DATE, Value.TIME_TZ, Value.TIME, Value.TIMESTAMP_TZ, Value.TIMESTAMP }; + + private static final String[] NAMES = { "CURRENT_DATE", "CURRENT_TIME", "LOCALTIME", "CURRENT_TIMESTAMP", + "LOCALTIMESTAMP" }; + + /** + * Get the name for this function id. + * + * @param function the function id + * @return the name + */ + public static String getName(int function) { + return NAMES[function]; + } + + private final int function, scale; + + private final TypeInfo type; + + public CurrentDateTimeValueFunction(int function, int scale) { + this.function = function; + this.scale = scale; + if (scale < 0) { + scale = function >= CURRENT_TIMESTAMP ? ValueTimestamp.DEFAULT_SCALE : ValueTime.DEFAULT_SCALE; + } + type = TypeInfo.getTypeInfo(TYPES[function], 0L, scale, null); + } + + @Override + public Value getValue(SessionLocal session) { + return session.currentTimestamp().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/expression/function/CurrentGeneralValueSpecification.java b/h2/src/main/org/h2/expression/function/CurrentGeneralValueSpecification.java new file mode 100644 index 0000000..ca76fa7 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/CurrentGeneralValueSpecification.java @@ -0,0 +1,147 @@ +/* + * 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.expression.function; + +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.Operation0; +import org.h2.message.DbException; +import org.h2.util.HasSQL; +import org.h2.util.ParserUtil; +import org.h2.util.StringUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueNull; +import org.h2.value.ValueVarchar; + +/** + * Simple general value specifications. + */ +public final class CurrentGeneralValueSpecification extends Operation0 implements NamedExpression { + + /** + * The "CURRENT_CATALOG" general value specification. + */ + public static final int CURRENT_CATALOG = 0; + + /** + * The "CURRENT_PATH" general value specification. + */ + public static final int CURRENT_PATH = CURRENT_CATALOG + 1; + + /** + * The function "CURRENT_ROLE" general value specification. + */ + public static final int CURRENT_ROLE = CURRENT_PATH + 1; + + /** + * The function "CURRENT_SCHEMA" general value specification. + */ + public static final int CURRENT_SCHEMA = CURRENT_ROLE + 1; + + /** + * The function "CURRENT_USER" general value specification. + */ + public static final int CURRENT_USER = CURRENT_SCHEMA + 1; + + /** + * The function "SESSION_USER" general value specification. + */ + public static final int SESSION_USER = CURRENT_USER + 1; + + /** + * The function "SYSTEM_USER" general value specification. + */ + public static final int SYSTEM_USER = SESSION_USER + 1; + + private static final String[] NAMES = { "CURRENT_CATALOG", "CURRENT_PATH", "CURRENT_ROLE", "CURRENT_SCHEMA", + "CURRENT_USER", "SESSION_USER", "SYSTEM_USER" }; + + private final int specification; + + public CurrentGeneralValueSpecification(int specification) { + this.specification = specification; + } + + @Override + public Value getValue(SessionLocal session) { + String s; + switch (specification) { + case CURRENT_CATALOG: + s = session.getDatabase().getShortName(); + break; + case CURRENT_PATH: { + String[] searchPath = session.getSchemaSearchPath(); + if (searchPath != null) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < searchPath.length; i++) { + if (i > 0) { + builder.append(','); + } + ParserUtil.quoteIdentifier(builder, searchPath[i], HasSQL.DEFAULT_SQL_FLAGS); + } + s = builder.toString(); + } else { + s = ""; + } + break; + } + case CURRENT_ROLE: { + Database db = session.getDatabase(); + s = db.getPublicRole().getName(); + if (db.getSettings().databaseToLower) { + s = StringUtils.toLowerEnglish(s); + } + break; + } + case CURRENT_SCHEMA: + s = session.getCurrentSchemaName(); + break; + case CURRENT_USER: + case SESSION_USER: + case SYSTEM_USER: + s = session.getUser().getName(); + if (session.getDatabase().getSettings().databaseToLower) { + s = StringUtils.toLowerEnglish(s); + } + break; + default: + throw DbException.getInternalError("specification=" + specification); + } + return s != null ? ValueVarchar.get(s, session) : ValueNull.INSTANCE; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return builder.append(getName()); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + switch (visitor.getType()) { + case ExpressionVisitor.DETERMINISTIC: + return false; + } + return true; + } + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_VARCHAR; + } + + @Override + public int getCost() { + return 1; + } + + @Override + public String getName() { + return NAMES[specification]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/DBObjectFunction.java b/h2/src/main/org/h2/expression/function/DBObjectFunction.java new file mode 100644 index 0000000..55441dc --- /dev/null +++ b/h2/src/main/org/h2/expression/function/DBObjectFunction.java @@ -0,0 +1,144 @@ +/* + * 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.expression.function; + +import org.h2.engine.Database; +import org.h2.engine.DbObject; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueInteger; +import org.h2.value.ValueNull; +import org.h2.value.ValueVarchar; + +/** + * DB_OBJECT_ID() and DB_OBJECT_SQL() functions. + */ +public final class DBObjectFunction extends FunctionN { + + /** + * DB_OBJECT_ID() (non-standard). + */ + public static final int DB_OBJECT_ID = 0; + + /** + * DB_OBJECT_SQL() (non-standard). + */ + public static final int DB_OBJECT_SQL = DB_OBJECT_ID + 1; + + private static final String[] NAMES = { // + "DB_OBJECT_ID", "DB_OBJECT_SQL" // + }; + + private final int function; + + public DBObjectFunction(Expression objectType, Expression arg1, Expression arg2, int function) { + super(arg2 == null ? new Expression[] { objectType, arg1, } : new Expression[] { objectType, arg1, arg2 }); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session, Value v1, Value v2, Value v3) { + session.getUser().checkAdmin(); + String objectType = v1.getString(); + DbObject object; + if (v3 != null) { + Schema schema = session.getDatabase().findSchema(v2.getString()); + if (schema == null) { + return ValueNull.INSTANCE; + } + String objectName = v3.getString(); + switch (objectType) { + case "CONSTANT": + object = schema.findConstant(objectName); + break; + case "CONSTRAINT": + object = schema.findConstraint(session, objectName); + break; + case "DOMAIN": + object = schema.findDomain(objectName); + break; + case "INDEX": + object = schema.findIndex(session, objectName); + break; + case "ROUTINE": + object = schema.findFunctionOrAggregate(objectName); + break; + case "SEQUENCE": + object = schema.findSequence(objectName); + break; + case "SYNONYM": + object = schema.getSynonym(objectName); + break; + case "TABLE": + object = schema.findTableOrView(session, objectName); + break; + case "TRIGGER": + object = schema.findTrigger(objectName); + break; + default: + return ValueNull.INSTANCE; + } + } else { + String objectName = v2.getString(); + Database database = session.getDatabase(); + switch (objectType) { + case "ROLE": + object = database.findRole(objectName); + break; + case "SETTING": + object = database.findSetting(objectName); + break; + case "SCHEMA": + object = database.findSchema(objectName); + break; + case "USER": + object = database.findUser(objectName); + break; + default: + return ValueNull.INSTANCE; + } + } + if (object == null) { + return ValueNull.INSTANCE; + } + switch (function) { + case DB_OBJECT_ID: + return ValueInteger.get(object.getId()); + case DB_OBJECT_SQL: + String sql = object.getCreateSQLForMeta(); + return sql != null ? ValueVarchar.get(sql, session) : ValueNull.INSTANCE; + default: + throw DbException.getInternalError("function=" + function); + } + } + + @Override + public Expression optimize(SessionLocal session) { + optimizeArguments(session, false); + type = function == DB_OBJECT_ID ? TypeInfo.TYPE_INTEGER : TypeInfo.TYPE_VARCHAR; + return this; + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + switch (visitor.getType()) { + case ExpressionVisitor.DETERMINISTIC: + return false; + } + return super.isEverything(visitor); + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/DataTypeSQLFunction.java b/h2/src/main/org/h2/expression/function/DataTypeSQLFunction.java new file mode 100644 index 0000000..39c7751 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/DataTypeSQLFunction.java @@ -0,0 +1,157 @@ +/* + * 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.expression.function; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.message.DbException; +import org.h2.schema.Constant; +import org.h2.schema.Domain; +import org.h2.schema.FunctionAlias; +import org.h2.schema.Schema; +import org.h2.schema.FunctionAlias.JavaMethod; +import org.h2.table.Column; +import org.h2.table.Table; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueNull; +import org.h2.value.ValueToObjectConverter2; +import org.h2.value.ValueVarchar; + +/** + * DATA_TYPE_SQL() function. + */ +public final class DataTypeSQLFunction extends FunctionN { + + public DataTypeSQLFunction(Expression objectSchema, Expression objectName, Expression objectType, + Expression typeIdentifier) { + super(new Expression[] { objectSchema, objectName, objectType, typeIdentifier }); + } + + @Override + public Value getValue(SessionLocal session, Value v1, Value v2, Value v3) { + Schema schema = session.getDatabase().findSchema(v1.getString()); + if (schema == null) { + return ValueNull.INSTANCE; + } + String objectName = v2.getString(); + String objectType = v3.getString(); + String typeIdentifier = args[3].getValue(session).getString(); + if (typeIdentifier == null) { + return ValueNull.INSTANCE; + } + TypeInfo t; + switch (objectType) { + case "CONSTANT": { + Constant constant = schema.findConstant(objectName); + if (constant == null || !typeIdentifier.equals("TYPE")) { + return ValueNull.INSTANCE; + } + t = constant.getValue().getType(); + break; + } + case "DOMAIN": { + Domain domain = schema.findDomain(objectName); + if (domain == null || !typeIdentifier.equals("TYPE")) { + return ValueNull.INSTANCE; + } + t = domain.getDataType(); + break; + } + case "ROUTINE": { + int idx = objectName.lastIndexOf('_'); + if (idx < 0) { + return ValueNull.INSTANCE; + } + FunctionAlias function = schema.findFunction(objectName.substring(0, idx)); + if (function == null) { + return ValueNull.INSTANCE; + } + int ordinal; + try { + ordinal = Integer.parseInt(objectName.substring(idx + 1)); + } catch (NumberFormatException e) { + return ValueNull.INSTANCE; + } + JavaMethod[] methods; + try { + methods = function.getJavaMethods(); + } catch (DbException e) { + return ValueNull.INSTANCE; + } + if (ordinal < 1 || ordinal > methods.length) { + return ValueNull.INSTANCE; + } + FunctionAlias.JavaMethod method = methods[ordinal - 1]; + if (typeIdentifier.equals("RESULT")) { + t = method.getDataType(); + } else { + try { + ordinal = Integer.parseInt(typeIdentifier); + } catch (NumberFormatException e) { + return ValueNull.INSTANCE; + } + if (ordinal < 1) { + return ValueNull.INSTANCE; + } + if (!method.hasConnectionParam()) { + ordinal--; + } + Class[] columnList = method.getColumnClasses(); + if (ordinal >= columnList.length) { + return ValueNull.INSTANCE; + } + t = ValueToObjectConverter2.classToType(columnList[ordinal]); + } + break; + } + case "TABLE": { + Table table = schema.findTableOrView(session, objectName); + if (table == null) { + return ValueNull.INSTANCE; + } + int ordinal; + try { + ordinal = Integer.parseInt(typeIdentifier); + } catch (NumberFormatException e) { + return ValueNull.INSTANCE; + } + Column[] columns = table.getColumns(); + if (ordinal < 1 || ordinal > columns.length) { + return ValueNull.INSTANCE; + } + t = columns[ordinal - 1].getType(); + break; + } + default: + return ValueNull.INSTANCE; + } + return ValueVarchar.get(t.getSQL(DEFAULT_SQL_FLAGS)); + } + + @Override + public Expression optimize(SessionLocal session) { + optimizeArguments(session, false); + type = TypeInfo.TYPE_VARCHAR; + return this; + } + + @Override + public String getName() { + return "DATA_TYPE_SQL"; + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + switch (visitor.getType()) { + case ExpressionVisitor.DETERMINISTIC: + return false; + } + return true; + } + +} diff --git a/h2/src/main/org/h2/expression/function/DateTimeFormatFunction.java b/h2/src/main/org/h2/expression/function/DateTimeFormatFunction.java new file mode 100644 index 0000000..361836e --- /dev/null +++ b/h2/src/main/org/h2/expression/function/DateTimeFormatFunction.java @@ -0,0 +1,313 @@ +/* + * 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.expression.function; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoField; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalQueries; +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Objects; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.message.DbException; +import org.h2.util.JSR310Utils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueTime; +import org.h2.value.ValueTimestampTimeZone; +import org.h2.value.ValueVarchar; + +/** + * A date-time format function. + */ +public final class DateTimeFormatFunction extends FunctionN { + + private static final class CacheKey { + + private final String format; + + private final String locale; + + private final String timeZone; + + CacheKey(String format, String locale, String timeZone) { + this.format = format; + this.locale = locale; + this.timeZone = timeZone; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + format.hashCode(); + result = prime * result + ((locale == null) ? 0 : locale.hashCode()); + result = prime * result + ((timeZone == null) ? 0 : timeZone.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof CacheKey)) { + return false; + } + CacheKey other = (CacheKey) obj; + return format.equals(other.format) && Objects.equals(locale, other.locale) + && Objects.equals(timeZone, other.timeZone); + } + + } + + private static final class CacheValue { + + final DateTimeFormatter formatter; + + final ZoneId zoneId; + + CacheValue(DateTimeFormatter formatter, ZoneId zoneId) { + this.formatter = formatter; + this.zoneId = zoneId; + } + + } + + /** + * FORMATDATETIME() (non-standard). + */ + public static final int FORMATDATETIME = 0; + + /** + * PARSEDATETIME() (non-standard). + */ + public static final int PARSEDATETIME = FORMATDATETIME + 1; + + private static final String[] NAMES = { // + "FORMATDATETIME", "PARSEDATETIME" // + }; + + private static final LinkedHashMap CACHE = new LinkedHashMap() { + + private static final long serialVersionUID = 1L; + + @Override + protected boolean removeEldestEntry(java.util.Map.Entry eldest) { + return size() > 100; + } + + }; + + private final int function; + + public DateTimeFormatFunction(int function) { + super(new Expression[4]); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session, Value v1, Value v2, Value v3) { + String format = v2.getString(), locale, tz; + if (v3 != null) { + locale = v3.getString(); + tz = args.length > 3 ? args[3].getValue(session).getString() : null; + } else { + tz = locale = null; + } + switch (function) { + case FORMATDATETIME: + v1 = ValueVarchar.get(formatDateTime(session, v1, format, locale, tz)); + break; + case PARSEDATETIME: + v1 = parseDateTime(session, v1.getString(), format, locale, tz); + break; + default: + throw DbException.getInternalError("function=" + function); + } + return v1; + } + + /** + * Formats a date using a format string. + * + * @param session + * the session + * @param date + * the date to format + * @param format + * the format string + * @param locale + * the locale + * @param timeZone + * the time zone + * @return the formatted date + */ + public static String formatDateTime(SessionLocal session, Value date, String format, String locale, + String timeZone) { + CacheValue formatAndZone = getDateFormat(format, locale, timeZone); + ZoneId zoneId = formatAndZone.zoneId; + TemporalAccessor value; + if (date instanceof ValueTimestampTimeZone) { + OffsetDateTime dateTime = JSR310Utils.valueToOffsetDateTime(date, session); + ZoneId zoneToSet; + if (zoneId != null) { + zoneToSet = zoneId; + } else { + ZoneOffset offset = dateTime.getOffset(); + zoneToSet = ZoneId.ofOffset(offset.getTotalSeconds() == 0 ? "UTC" : "GMT", offset); + } + value = dateTime.atZoneSameInstant(zoneToSet); + } else { + LocalDateTime dateTime = JSR310Utils.valueToLocalDateTime(date, session); + value = dateTime.atZone(zoneId != null ? zoneId : ZoneId.of(session.currentTimeZone().getId())); + } + return formatAndZone.formatter.format(value); + } + + /** + * Parses a date using a format string. + * + * @param session + * the session + * @param date + * the date to parse + * @param format + * the parsing format + * @param locale + * the locale + * @param timeZone + * the time zone + * @return the parsed date + */ + public static ValueTimestampTimeZone parseDateTime(SessionLocal session, String date, String format, String locale, + String timeZone) { + CacheValue formatAndZone = getDateFormat(format, locale, timeZone); + try { + ValueTimestampTimeZone result; + TemporalAccessor parsed = formatAndZone.formatter.parse(date); + ZoneId parsedZoneId = parsed.query(TemporalQueries.zoneId()); + if (parsed.isSupported(ChronoField.OFFSET_SECONDS)) { + result = JSR310Utils.offsetDateTimeToValue(OffsetDateTime.from(parsed)); + } else { + if (parsed.isSupported(ChronoField.INSTANT_SECONDS)) { + Instant instant = Instant.from(parsed); + if (parsedZoneId == null) { + parsedZoneId = formatAndZone.zoneId; + } + if (parsedZoneId != null) { + result = JSR310Utils.zonedDateTimeToValue(instant.atZone(parsedZoneId)); + } else { + result = JSR310Utils.offsetDateTimeToValue(instant.atOffset(ZoneOffset.ofTotalSeconds( // + session.currentTimeZone().getTimeZoneOffsetUTC(instant.getEpochSecond())))); + } + } else { + LocalDate localDate = parsed.query(TemporalQueries.localDate()); + LocalTime localTime = parsed.query(TemporalQueries.localTime()); + if (parsedZoneId == null) { + parsedZoneId = formatAndZone.zoneId; + } + if (localDate != null) { + LocalDateTime localDateTime = localTime != null ? LocalDateTime.of(localDate, localTime) + : localDate.atStartOfDay(); + result = parsedZoneId != null + ? JSR310Utils.zonedDateTimeToValue(localDateTime.atZone(parsedZoneId)) + : (ValueTimestampTimeZone) JSR310Utils.localDateTimeToValue(localDateTime) + .convertTo(Value.TIMESTAMP_TZ, session); + } else { + result = parsedZoneId != null + ? JSR310Utils.zonedDateTimeToValue( + JSR310Utils.valueToInstant(session.currentTimestamp(), session) + .atZone(parsedZoneId).with(localTime)) + : (ValueTimestampTimeZone) ValueTime.fromNanos(localTime.toNanoOfDay()) + .convertTo(Value.TIMESTAMP_TZ, session); + } + } + } + return result; + } catch (RuntimeException e) { + throw DbException.get(ErrorCode.PARSE_ERROR_1, e, date); + } + } + + private static CacheValue getDateFormat(String format, String locale, String timeZone) { + Exception ex = null; + if (format.length() <= 100) { + try { + CacheValue value; + CacheKey key = new CacheKey(format, locale, timeZone); + synchronized (CACHE) { + value = CACHE.get(key); + if (value == null) { + DateTimeFormatter df; + if (locale == null) { + df = DateTimeFormatter.ofPattern(format); + } else { + df = DateTimeFormatter.ofPattern(format, new Locale(locale)); + } + ZoneId zoneId; + if (timeZone != null) { + zoneId = getZoneId(timeZone); + df = df.withZone(zoneId); + } else { + zoneId = null; + } + value = new CacheValue(df, zoneId); + CACHE.put(key, value); + } + } + return value; + } catch (Exception e) { + ex = e; + } + } + throw DbException.get(ErrorCode.PARSE_ERROR_1, ex, format + '/' + locale); + } + + private static ZoneId getZoneId(String timeZone) { + try { + return ZoneId.of(timeZone, ZoneId.SHORT_IDS); + } catch (RuntimeException e) { + throw DbException.getInvalidValueException("TIME ZONE", timeZone); + } + } + + @Override + public Expression optimize(SessionLocal session) { + boolean allConst = optimizeArguments(session, true); + switch (function) { + case FORMATDATETIME: + type = TypeInfo.TYPE_VARCHAR; + break; + case PARSEDATETIME: + type = TypeInfo.TYPE_TIMESTAMP_TZ; + break; + default: + throw DbException.getInternalError("function=" + function); + } + if (allConst) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/DateTimeFunction.java b/h2/src/main/org/h2/expression/function/DateTimeFunction.java new file mode 100644 index 0000000..8dae5d5 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/DateTimeFunction.java @@ -0,0 +1,1038 @@ +/* + * 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.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; +import static org.h2.util.DateTimeUtils.NANOS_PER_MINUTE; +import static org.h2.util.DateTimeUtils.NANOS_PER_SECOND; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.time.temporal.WeekFields; +import java.util.Locale; + +import org.h2.api.IntervalQualifier; +import org.h2.engine.Mode.ModeEnum; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.message.DbException; +import org.h2.util.DateTimeUtils; +import org.h2.util.IntervalUtils; +import org.h2.util.MathUtils; +import org.h2.util.StringUtils; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBigint; +import org.h2.value.ValueDate; +import org.h2.value.ValueInteger; +import org.h2.value.ValueInterval; +import org.h2.value.ValueNumeric; +import org.h2.value.ValueTime; +import org.h2.value.ValueTimeTimeZone; +import org.h2.value.ValueTimestamp; +import org.h2.value.ValueTimestampTimeZone; + +/** + * A date-time function. + */ +public final class DateTimeFunction extends Function1_2 { + + /** + * EXTRACT(). + */ + public static final int EXTRACT = 0; + + /** + * DATE_TRUNC() (non-standard). + */ + public static final int DATE_TRUNC = EXTRACT + 1; + + /** + * DATEADD() (non-standard). + */ + public static final int DATEADD = DATE_TRUNC + 1; + + /** + * DATEDIFF() (non-standard). + */ + public static final int DATEDIFF = DATEADD + 1; + + private static final String[] NAMES = { // + "EXTRACT", "DATE_TRUNC", "DATEADD", "DATEDIFF" // + }; + + // Standard fields + + /** + * Year. + */ + public static final int YEAR = 0; + + /** + * Month. + */ + public static final int MONTH = YEAR + 1; + + /** + * Day of month. + */ + public static final int DAY = MONTH + 1; + + /** + * Hour. + */ + public static final int HOUR = DAY + 1; + + /** + * Minute. + */ + public static final int MINUTE = HOUR + 1; + + /** + * Second. + */ + public static final int SECOND = MINUTE + 1; + + /** + * Time zone hour. + */ + public static final int TIMEZONE_HOUR = SECOND + 1; + + /** + * Time zone minute. + */ + public static final int TIMEZONE_MINUTE = TIMEZONE_HOUR + 1; + + // Additional fields + + /** + * Time zone second. + */ + public static final int TIMEZONE_SECOND = TIMEZONE_MINUTE + 1; + + /** + * Millennium. + */ + public static final int MILLENNIUM = TIMEZONE_SECOND + 1; + + /** + * Century. + */ + public static final int CENTURY = MILLENNIUM + 1; + + /** + * Decade. + */ + public static final int DECADE = CENTURY + 1; + + /** + * Quarter. + */ + public static final int QUARTER = DECADE + 1; + + /** + * Millisecond. + */ + public static final int MILLISECOND = QUARTER + 1; + + /** + * Microsecond. + */ + public static final int MICROSECOND = MILLISECOND + 1; + + /** + * Nanosecond. + */ + public static final int NANOSECOND = MICROSECOND + 1; + + /** + * Day of year. + */ + public static final int DAY_OF_YEAR = NANOSECOND + 1; + + /** + * ISO day of week. + */ + public static final int ISO_DAY_OF_WEEK = DAY_OF_YEAR + 1; + + /** + * ISO week. + */ + public static final int ISO_WEEK = ISO_DAY_OF_WEEK + 1; + + /** + * ISO week-based year. + */ + public static final int ISO_WEEK_YEAR = ISO_WEEK + 1; + + /** + * Day of week (locale-specific). + */ + public static final int DAY_OF_WEEK = ISO_WEEK_YEAR + 1; + + /** + * Week (locale-specific). + */ + public static final int WEEK = DAY_OF_WEEK + 1; + + /** + * Week-based year (locale-specific). + */ + public static final int WEEK_YEAR = WEEK + 1; + + /** + * Epoch. + */ + public static final int EPOCH = WEEK_YEAR + 1; + + /** + * Day of week (locale-specific) for PostgreSQL compatibility. + */ + public static final int DOW = EPOCH + 1; + + private static final int FIELDS_COUNT = DOW + 1; + + private static final String[] FIELD_NAMES = { // + "YEAR", "MONTH", "DAY", // + "HOUR", "MINUTE", "SECOND", // + "TIMEZONE_HOUR", "TIMEZONE_MINUTE", "TIMEZONE_SECOND", // + "MILLENNIUM", "CENTURY", "DECADE", // + "QUARTER", // + "MILLISECOND", "MICROSECOND", "NANOSECOND", // + "DAY_OF_YEAR", // + "ISO_DAY_OF_WEEK", "ISO_WEEK", "ISO_WEEK_YEAR", // + "DAY_OF_WEEK", "WEEK", "WEEK_YEAR", // + "EPOCH", "DOW", // + }; + + private static final BigDecimal BD_SECONDS_PER_DAY = new BigDecimal(DateTimeUtils.SECONDS_PER_DAY); + + private static final BigInteger BI_SECONDS_PER_DAY = BigInteger.valueOf(DateTimeUtils.SECONDS_PER_DAY); + + private static final BigDecimal BD_NANOS_PER_SECOND = new BigDecimal(NANOS_PER_SECOND); + + /** + * Local definitions of day-of-week, week-of-month, and week-of-year. + */ + private static volatile WeekFields WEEK_FIELDS; + + /** + * Get date-time field for the specified name. + * + * @param name + * the name + * @return the date-time field + * @throws DbException + * on unknown field name + */ + public static int getField(String name) { + switch (StringUtils.toUpperEnglish(name)) { + case "YEAR": + case "YY": + case "YYYY": + case "SQL_TSI_YEAR": + return YEAR; + case "MONTH": + case "M": + case "MM": + case "SQL_TSI_MONTH": + return MONTH; + case "DAY": + case "D": + case "DD": + case "SQL_TSI_DAY": + return DAY; + case "HOUR": + case "HH": + case "SQL_TSI_HOUR": + return HOUR; + case "MINUTE": + case "MI": + case "N": + case "SQL_TSI_MINUTE": + return MINUTE; + case "SECOND": + case "S": + case "SS": + case "SQL_TSI_SECOND": + return SECOND; + case "TIMEZONE_HOUR": + return TIMEZONE_HOUR; + case "TIMEZONE_MINUTE": + return TIMEZONE_MINUTE; + case "TIMEZONE_SECOND": + return TIMEZONE_SECOND; + case "MILLENNIUM": + return MILLENNIUM; + case "CENTURY": + return CENTURY; + case "DECADE": + return DECADE; + case "QUARTER": + return QUARTER; + case "MILLISECOND": + case "MILLISECONDS": + case "MS": + return MILLISECOND; + case "MICROSECOND": + case "MICROSECONDS": + case "MCS": + return MICROSECOND; + case "NANOSECOND": + case "NS": + return NANOSECOND; + case "DAY_OF_YEAR": + case "DAYOFYEAR": + case "DY": + case "DOY": + return DAY_OF_YEAR; + case "ISO_DAY_OF_WEEK": + case "ISODOW": + return ISO_DAY_OF_WEEK; + case "ISO_WEEK": + return ISO_WEEK; + case "ISO_WEEK_YEAR": + case "ISO_YEAR": + case "ISOYEAR": + return ISO_WEEK_YEAR; + case "DAY_OF_WEEK": + case "DAYOFWEEK": + return DAY_OF_WEEK; + case "WEEK": + case "WK": + case "WW": + case "SQL_TSI_WEEK": + return WEEK; + case "WEEK_YEAR": + return WEEK_YEAR; + case "EPOCH": + return EPOCH; + case "DOW": + return DOW; + default: + throw DbException.getInvalidValueException("date-time field", name); + } + } + + /** + * Get the name of the specified date-time field. + * + * @param field + * the date-time field + * @return the name of the specified field + */ + public static String getFieldName(int field) { + if (field < 0 || field >= FIELDS_COUNT) { + throw DbException.getUnsupportedException("datetime field " + field); + } + return FIELD_NAMES[field]; + } + + private final int function, field; + + public DateTimeFunction(int function, int field, Expression arg1, Expression arg2) { + super(arg1, arg2); + this.function = function; + this.field = field; + } + + @Override + public Value getValue(SessionLocal session, Value v1, Value v2) { + switch (function) { + case EXTRACT: + v1 = field == EPOCH ? extractEpoch(session, v1) : ValueInteger.get(extractInteger(session, v1, field)); + break; + case DATE_TRUNC: + v1 = truncateDate(session, field, v1); + break; + case DATEADD: + v1 = dateadd(session, field, v1.getLong(), v2); + break; + case DATEDIFF: + v1 = ValueBigint.get(datediff(session, field, v1, v2)); + break; + default: + throw DbException.getInternalError("function=" + function); + } + return v1; + } + + /** + * Get the specified field of a date, however with years normalized to + * positive or negative, and month starting with 1. + * + * @param session + * the session + * @param date + * the date value + * @param field + * the field type + * @return the value + */ + private static int extractInteger(SessionLocal session, Value date, int field) { + return date instanceof ValueInterval ? extractInterval(date, field) : extractDateTime(session, date, field); + } + + private static int extractInterval(Value date, int field) { + ValueInterval interval = (ValueInterval) date; + IntervalQualifier qualifier = interval.getQualifier(); + boolean negative = interval.isNegative(); + long leading = interval.getLeading(), remaining = interval.getRemaining(); + long v; + switch (field) { + case YEAR: + v = IntervalUtils.yearsFromInterval(qualifier, negative, leading, remaining); + break; + case MONTH: + v = IntervalUtils.monthsFromInterval(qualifier, negative, leading, remaining); + break; + case DAY: + case DAY_OF_YEAR: + v = IntervalUtils.daysFromInterval(qualifier, negative, leading, remaining); + break; + case HOUR: + v = IntervalUtils.hoursFromInterval(qualifier, negative, leading, remaining); + break; + case MINUTE: + v = IntervalUtils.minutesFromInterval(qualifier, negative, leading, remaining); + break; + case SECOND: + v = IntervalUtils.nanosFromInterval(qualifier, negative, leading, remaining) / NANOS_PER_SECOND; + break; + case MILLISECOND: + v = IntervalUtils.nanosFromInterval(qualifier, negative, leading, remaining) / 1_000_000 % 1_000; + break; + case MICROSECOND: + v = IntervalUtils.nanosFromInterval(qualifier, negative, leading, remaining) / 1_000 % 1_000_000; + break; + case NANOSECOND: + v = IntervalUtils.nanosFromInterval(qualifier, negative, leading, remaining) % NANOS_PER_SECOND; + break; + default: + throw DbException.getUnsupportedException("getDatePart(" + date + ", " + field + ')'); + } + return (int) v; + } + + static int extractDateTime(SessionLocal session, Value date, int field) { + long[] a = DateTimeUtils.dateAndTimeFromValue(date, session); + long dateValue = a[0]; + long timeNanos = a[1]; + switch (field) { + case YEAR: + return DateTimeUtils.yearFromDateValue(dateValue); + case MONTH: + return DateTimeUtils.monthFromDateValue(dateValue); + case DAY: + return DateTimeUtils.dayFromDateValue(dateValue); + case HOUR: + return (int) (timeNanos / NANOS_PER_HOUR % 24); + case MINUTE: + return (int) (timeNanos / NANOS_PER_MINUTE % 60); + case SECOND: + return (int) (timeNanos / NANOS_PER_SECOND % 60); + case MILLISECOND: + return (int) (timeNanos / 1_000_000 % 1_000); + case MICROSECOND: + return (int) (timeNanos / 1_000 % 1_000_000); + case NANOSECOND: + return (int) (timeNanos % NANOS_PER_SECOND); + case MILLENNIUM: + return millennium(DateTimeUtils.yearFromDateValue(dateValue)); + case CENTURY: + return century(DateTimeUtils.yearFromDateValue(dateValue)); + case DECADE: + return decade(DateTimeUtils.yearFromDateValue(dateValue)); + case DAY_OF_YEAR: + return DateTimeUtils.getDayOfYear(dateValue); + case DOW: + if (session.getMode().getEnum() == ModeEnum.PostgreSQL) { + return DateTimeUtils.getSundayDayOfWeek(dateValue) - 1; + } + //$FALL-THROUGH$ + case DAY_OF_WEEK: + return getLocalDayOfWeek(dateValue); + case WEEK: + return getLocalWeekOfYear(dateValue); + case WEEK_YEAR: { + WeekFields wf = getWeekFields(); + return DateTimeUtils.getWeekYear(dateValue, wf.getFirstDayOfWeek().getValue(), + wf.getMinimalDaysInFirstWeek()); + } + case QUARTER: + return (DateTimeUtils.monthFromDateValue(dateValue) - 1) / 3 + 1; + case ISO_WEEK_YEAR: + return DateTimeUtils.getIsoWeekYear(dateValue); + case ISO_WEEK: + return DateTimeUtils.getIsoWeekOfYear(dateValue); + case ISO_DAY_OF_WEEK: + return DateTimeUtils.getIsoDayOfWeek(dateValue); + case TIMEZONE_HOUR: + case TIMEZONE_MINUTE: + case TIMEZONE_SECOND: { + int offsetSeconds; + if (date instanceof ValueTimestampTimeZone) { + offsetSeconds = ((ValueTimestampTimeZone) date).getTimeZoneOffsetSeconds(); + } else if (date instanceof ValueTimeTimeZone) { + offsetSeconds = ((ValueTimeTimeZone) date).getTimeZoneOffsetSeconds(); + } else { + offsetSeconds = session.currentTimeZone().getTimeZoneOffsetLocal(dateValue, timeNanos); + } + if (field == TIMEZONE_HOUR) { + return offsetSeconds / 3_600; + } else if (field == TIMEZONE_MINUTE) { + return offsetSeconds % 3_600 / 60; + } else { + return offsetSeconds % 60; + } + } + default: + throw DbException.getUnsupportedException("EXTRACT(" + getFieldName(field) + " FROM " + date + ')'); + } + } + + /** + * Truncate the given date-time value to the specified field. + * + * @param session + * the session + * @param field + * the date-time field + * @param value + * the date-time value + * @return date the truncated value + */ + private static Value truncateDate(SessionLocal session, int field, Value value) { + long[] fieldDateAndTime = DateTimeUtils.dateAndTimeFromValue(value, session); + long dateValue = fieldDateAndTime[0]; + long timeNanos = fieldDateAndTime[1]; + switch (field) { + case MICROSECOND: + timeNanos = timeNanos / 1_000L * 1_000L; + break; + case MILLISECOND: + timeNanos = timeNanos / 1_000_000L * 1_000_000L; + break; + case SECOND: + timeNanos = timeNanos / NANOS_PER_SECOND * NANOS_PER_SECOND; + break; + case MINUTE: + timeNanos = timeNanos / NANOS_PER_MINUTE * NANOS_PER_MINUTE; + break; + case HOUR: + timeNanos = timeNanos / NANOS_PER_HOUR * NANOS_PER_HOUR; + break; + case DAY: + timeNanos = 0L; + break; + case ISO_WEEK: + dateValue = truncateToWeek(dateValue, 1); + timeNanos = 0L; + break; + case WEEK: + dateValue = truncateToWeek(dateValue, getWeekFields().getFirstDayOfWeek().getValue()); + timeNanos = 0L; + break; + case ISO_WEEK_YEAR: + dateValue = truncateToWeekYear(dateValue, 1, 4); + timeNanos = 0L; + break; + case WEEK_YEAR: { + WeekFields weekFields = getWeekFields(); + dateValue = truncateToWeekYear(dateValue, weekFields.getFirstDayOfWeek().getValue(), + weekFields.getMinimalDaysInFirstWeek()); + break; + } + case MONTH: + dateValue = dateValue & (-1L << DateTimeUtils.SHIFT_MONTH) | 1L; + timeNanos = 0L; + break; + case QUARTER: + dateValue = DateTimeUtils.dateValue(DateTimeUtils.yearFromDateValue(dateValue), + ((DateTimeUtils.monthFromDateValue(dateValue) - 1) / 3) * 3 + 1, 1); + timeNanos = 0L; + break; + case YEAR: + dateValue = dateValue & (-1L << DateTimeUtils.SHIFT_YEAR) | (1L << DateTimeUtils.SHIFT_MONTH | 1L); + timeNanos = 0L; + break; + case DECADE: { + int year = DateTimeUtils.yearFromDateValue(dateValue); + if (year >= 0) { + year = year / 10 * 10; + } else { + year = (year - 9) / 10 * 10; + } + dateValue = DateTimeUtils.dateValue(year, 1, 1); + timeNanos = 0L; + break; + } + case CENTURY: { + int year = DateTimeUtils.yearFromDateValue(dateValue); + if (year > 0) { + year = (year - 1) / 100 * 100 + 1; + } else { + year = year / 100 * 100 - 99; + } + dateValue = DateTimeUtils.dateValue(year, 1, 1); + timeNanos = 0L; + break; + } + case MILLENNIUM: { + int year = DateTimeUtils.yearFromDateValue(dateValue); + if (year > 0) { + year = (year - 1) / 1000 * 1000 + 1; + } else { + year = year / 1000 * 1000 - 999; + } + dateValue = DateTimeUtils.dateValue(year, 1, 1); + timeNanos = 0L; + break; + } + default: + throw DbException.getUnsupportedException("DATE_TRUNC " + getFieldName(field)); + } + Value result = DateTimeUtils.dateTimeToValue(value, dateValue, timeNanos); + if (session.getMode().getEnum() == ModeEnum.PostgreSQL && result.getValueType() == Value.DATE) { + result = result.convertTo(Value.TIMESTAMP_TZ, session); + } + return result; + } + + private static long truncateToWeek(long dateValue, int firstDayOfWeek) { + long absoluteDay = DateTimeUtils.absoluteDayFromDateValue(dateValue); + int dayOfWeek = DateTimeUtils.getDayOfWeekFromAbsolute(absoluteDay, firstDayOfWeek); + if (dayOfWeek != 1) { + dateValue = DateTimeUtils.dateValueFromAbsoluteDay(absoluteDay - dayOfWeek + 1); + } + return dateValue; + } + + private static long truncateToWeekYear(long dateValue, int firstDayOfWeek, int minimalDaysInFirstWeek) { + long abs = DateTimeUtils.absoluteDayFromDateValue(dateValue); + int year = DateTimeUtils.yearFromDateValue(dateValue); + long base = DateTimeUtils.getWeekYearAbsoluteStart(year, firstDayOfWeek, minimalDaysInFirstWeek); + if (abs < base) { + base = DateTimeUtils.getWeekYearAbsoluteStart(year - 1, firstDayOfWeek, minimalDaysInFirstWeek); + } else if (DateTimeUtils.monthFromDateValue(dateValue) == 12 + && 24 + minimalDaysInFirstWeek < DateTimeUtils.dayFromDateValue(dateValue)) { + long next = DateTimeUtils.getWeekYearAbsoluteStart(year + 1, firstDayOfWeek, minimalDaysInFirstWeek); + if (abs >= next) { + base = next; + } + } + return DateTimeUtils.dateValueFromAbsoluteDay(base); + } + + /** + * DATEADD function. + * + * @param session + * the session + * @param field + * the date-time field + * @param count + * count to add + * @param v + * value to add to + * @return result + */ + public static Value dateadd(SessionLocal session, int field, long count, Value v) { + if (field != MILLISECOND && field != MICROSECOND && field != NANOSECOND + && (count > Integer.MAX_VALUE || count < Integer.MIN_VALUE)) { + throw DbException.getInvalidValueException("DATEADD count", count); + } + long[] a = DateTimeUtils.dateAndTimeFromValue(v, session); + long dateValue = a[0]; + long timeNanos = a[1]; + int type = v.getValueType(); + switch (field) { + case MILLENNIUM: + return addYearsMonths(field, true, count * 1_000, v, type, dateValue, timeNanos); + case CENTURY: + return addYearsMonths(field, true, count * 100, v, type, dateValue, timeNanos); + case DECADE: + return addYearsMonths(field, true, count * 10, v, type, dateValue, timeNanos); + case YEAR: + return addYearsMonths(field, true, count, v, type, dateValue, timeNanos); + case QUARTER: + return addYearsMonths(field, false, count *= 3, v, type, dateValue, timeNanos); + case MONTH: + return addYearsMonths(field, false, count, v, type, dateValue, timeNanos); + case WEEK: + case ISO_WEEK: + count *= 7; + //$FALL-THROUGH$ + case DAY_OF_WEEK: + case DOW: + case ISO_DAY_OF_WEEK: + case DAY: + case DAY_OF_YEAR: + if (type == Value.TIME || type == Value.TIME_TZ) { + throw DbException.getInvalidValueException("DATEADD time part", getFieldName(field)); + } + dateValue = DateTimeUtils + .dateValueFromAbsoluteDay(DateTimeUtils.absoluteDayFromDateValue(dateValue) + count); + return DateTimeUtils.dateTimeToValue(v, dateValue, timeNanos); + case HOUR: + count *= NANOS_PER_HOUR; + break; + case MINUTE: + count *= NANOS_PER_MINUTE; + break; + case SECOND: + case EPOCH: + count *= NANOS_PER_SECOND; + break; + case MILLISECOND: + count *= 1_000_000; + break; + case MICROSECOND: + count *= 1_000; + break; + case NANOSECOND: + break; + case TIMEZONE_HOUR: + return addToTimeZone(field, count * 3_600, v, type, dateValue, timeNanos); + case TIMEZONE_MINUTE: + return addToTimeZone(field, count * 60, v, type, dateValue, timeNanos); + case TIMEZONE_SECOND: + return addToTimeZone(field, count, v, type, dateValue, timeNanos); + default: + throw DbException.getUnsupportedException("DATEADD " + getFieldName(field)); + } + timeNanos += count; + if (timeNanos >= NANOS_PER_DAY || timeNanos < 0) { + long d; + if (timeNanos >= NANOS_PER_DAY) { + d = timeNanos / NANOS_PER_DAY; + } else { + d = (timeNanos - NANOS_PER_DAY + 1) / NANOS_PER_DAY; + } + dateValue = DateTimeUtils.dateValueFromAbsoluteDay(DateTimeUtils.absoluteDayFromDateValue(dateValue) + d); + timeNanos -= d * NANOS_PER_DAY; + } + if (type == Value.DATE) { + return ValueTimestamp.fromDateValueAndNanos(dateValue, timeNanos); + } + return DateTimeUtils.dateTimeToValue(v, dateValue, timeNanos); + } + + private static Value addYearsMonths(int field, boolean years, long count, Value v, int type, long dateValue, + long timeNanos) { + if (type == Value.TIME || type == Value.TIME_TZ) { + throw DbException.getInvalidValueException("DATEADD time part", getFieldName(field)); + } + long year = DateTimeUtils.yearFromDateValue(dateValue); + long month = DateTimeUtils.monthFromDateValue(dateValue); + if (years) { + year += count; + } else { + month += count; + } + return DateTimeUtils.dateTimeToValue(v, + DateTimeUtils.dateValueFromDenormalizedDate(year, month, DateTimeUtils.dayFromDateValue(dateValue)), + timeNanos); + } + + private static Value addToTimeZone(int field, long count, Value v, int type, long dateValue, long timeNanos) { + if (type == Value.TIMESTAMP_TZ) { + return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, timeNanos, + MathUtils.convertLongToInt(count + ((ValueTimestampTimeZone) v).getTimeZoneOffsetSeconds())); + } else if (type == Value.TIME_TZ) { + return ValueTimeTimeZone.fromNanos(timeNanos, + MathUtils.convertLongToInt(count + ((ValueTimeTimeZone) v).getTimeZoneOffsetSeconds())); + } else { + throw DbException.getUnsupportedException("DATEADD " + getFieldName(field)); + } + } + + /** + * Calculate the number of crossed unit boundaries between two timestamps. + * This method is supported for MS SQL Server compatibility. + * + *
+     * DATEDIFF(YEAR, '2004-12-31', '2005-01-01') = 1
+     * 
+ * + * @param session + * the session + * @param field + * the date-time field + * @param v1 + * the first date-time value + * @param v2 + * the second date-time value + * @return the number of crossed boundaries + */ + private static long datediff(SessionLocal session, int field, Value v1, Value v2) { + long[] a1 = DateTimeUtils.dateAndTimeFromValue(v1, session); + long dateValue1 = a1[0]; + long absolute1 = DateTimeUtils.absoluteDayFromDateValue(dateValue1); + long[] a2 = DateTimeUtils.dateAndTimeFromValue(v2, session); + long dateValue2 = a2[0]; + long absolute2 = DateTimeUtils.absoluteDayFromDateValue(dateValue2); + switch (field) { + case NANOSECOND: + case MICROSECOND: + case MILLISECOND: + case SECOND: + case EPOCH: + case MINUTE: + case HOUR: + long timeNanos1 = a1[1]; + long timeNanos2 = a2[1]; + switch (field) { + case NANOSECOND: + return (absolute2 - absolute1) * NANOS_PER_DAY + (timeNanos2 - timeNanos1); + case MICROSECOND: + return (absolute2 - absolute1) * (MILLIS_PER_DAY * 1_000) + (timeNanos2 / 1_000 - timeNanos1 / 1_000); + case MILLISECOND: + return (absolute2 - absolute1) * MILLIS_PER_DAY + (timeNanos2 / 1_000_000 - timeNanos1 / 1_000_000); + case SECOND: + case EPOCH: + return (absolute2 - absolute1) * 86_400 + + (timeNanos2 / NANOS_PER_SECOND - timeNanos1 / NANOS_PER_SECOND); + case MINUTE: + return (absolute2 - absolute1) * 1_440 + + (timeNanos2 / NANOS_PER_MINUTE - timeNanos1 / NANOS_PER_MINUTE); + case HOUR: + return (absolute2 - absolute1) * 24 + (timeNanos2 / NANOS_PER_HOUR - timeNanos1 / NANOS_PER_HOUR); + } + // Fake fall-through + // $FALL-THROUGH$ + case DAY: + case DAY_OF_YEAR: + case DAY_OF_WEEK: + case DOW: + case ISO_DAY_OF_WEEK: + return absolute2 - absolute1; + case WEEK: + return weekdiff(absolute1, absolute2, getWeekFields().getFirstDayOfWeek().getValue()); + case ISO_WEEK: + return weekdiff(absolute1, absolute2, 1); + case MONTH: + return (DateTimeUtils.yearFromDateValue(dateValue2) - DateTimeUtils.yearFromDateValue(dateValue1)) * 12 + + DateTimeUtils.monthFromDateValue(dateValue2) - DateTimeUtils.monthFromDateValue(dateValue1); + case QUARTER: + return (DateTimeUtils.yearFromDateValue(dateValue2) - DateTimeUtils.yearFromDateValue(dateValue1)) * 4 + + (DateTimeUtils.monthFromDateValue(dateValue2) - 1) / 3 + - (DateTimeUtils.monthFromDateValue(dateValue1) - 1) / 3; + case MILLENNIUM: + return millennium(DateTimeUtils.yearFromDateValue(dateValue2)) + - millennium(DateTimeUtils.yearFromDateValue(dateValue1)); + case CENTURY: + return century(DateTimeUtils.yearFromDateValue(dateValue2)) + - century(DateTimeUtils.yearFromDateValue(dateValue1)); + case DECADE: + return decade(DateTimeUtils.yearFromDateValue(dateValue2)) + - decade(DateTimeUtils.yearFromDateValue(dateValue1)); + case YEAR: + return DateTimeUtils.yearFromDateValue(dateValue2) - DateTimeUtils.yearFromDateValue(dateValue1); + case TIMEZONE_HOUR: + case TIMEZONE_MINUTE: + case TIMEZONE_SECOND: { + int offsetSeconds1; + if (v1 instanceof ValueTimestampTimeZone) { + offsetSeconds1 = ((ValueTimestampTimeZone) v1).getTimeZoneOffsetSeconds(); + } else if (v1 instanceof ValueTimeTimeZone) { + offsetSeconds1 = ((ValueTimeTimeZone) v1).getTimeZoneOffsetSeconds(); + } else { + offsetSeconds1 = session.currentTimeZone().getTimeZoneOffsetLocal(dateValue1, a1[1]); + } + int offsetSeconds2; + if (v2 instanceof ValueTimestampTimeZone) { + offsetSeconds2 = ((ValueTimestampTimeZone) v2).getTimeZoneOffsetSeconds(); + } else if (v2 instanceof ValueTimeTimeZone) { + offsetSeconds2 = ((ValueTimeTimeZone) v2).getTimeZoneOffsetSeconds(); + } else { + offsetSeconds2 = session.currentTimeZone().getTimeZoneOffsetLocal(dateValue2, a2[1]); + } + if (field == TIMEZONE_HOUR) { + return (offsetSeconds2 / 3_600) - (offsetSeconds1 / 3_600); + } else if (field == TIMEZONE_MINUTE) { + return (offsetSeconds2 / 60) - (offsetSeconds1 / 60); + } else { + return offsetSeconds2 - offsetSeconds1; + } + } + default: + throw DbException.getUnsupportedException("DATEDIFF " + getFieldName(field)); + } + } + + private static long weekdiff(long absolute1, long absolute2, int firstDayOfWeek) { + absolute1 += 4 - firstDayOfWeek; + long r1 = absolute1 / 7; + if (absolute1 < 0 && (r1 * 7 != absolute1)) { + r1--; + } + absolute2 += 4 - firstDayOfWeek; + long r2 = absolute2 / 7; + if (absolute2 < 0 && (r2 * 7 != absolute2)) { + r2--; + } + return r2 - r1; + } + + private static int millennium(int year) { + return year > 0 ? (year + 999) / 1_000 : year / 1_000; + } + + private static int century(int year) { + return year > 0 ? (year + 99) / 100 : year / 100; + } + + private static int decade(int year) { + return year >= 0 ? year / 10 : (year - 9) / 10; + } + + private static int getLocalDayOfWeek(long dateValue) { + return DateTimeUtils.getDayOfWeek(dateValue, getWeekFields().getFirstDayOfWeek().getValue()); + } + + private static int getLocalWeekOfYear(long dateValue) { + WeekFields weekFields = getWeekFields(); + return DateTimeUtils.getWeekOfYear(dateValue, weekFields.getFirstDayOfWeek().getValue(), + weekFields.getMinimalDaysInFirstWeek()); + } + + private static WeekFields getWeekFields() { + WeekFields weekFields = WEEK_FIELDS; + if (weekFields == null) { + WEEK_FIELDS = weekFields = WeekFields.of(Locale.getDefault()); + } + return weekFields; + } + + private static ValueNumeric extractEpoch(SessionLocal session, Value value) { + ValueNumeric result; + if (value instanceof ValueInterval) { + ValueInterval interval = (ValueInterval) value; + if (interval.getQualifier().isYearMonth()) { + interval = (ValueInterval) interval.convertTo(TypeInfo.TYPE_INTERVAL_YEAR_TO_MONTH); + long leading = interval.getLeading(); + long remaining = interval.getRemaining(); + BigInteger bi = BigInteger.valueOf(leading).multiply(BigInteger.valueOf(31557600)) + .add(BigInteger.valueOf(remaining * 2592000)); + if (interval.isNegative()) { + bi = bi.negate(); + } + return ValueNumeric.get(bi); + } else { + return ValueNumeric + .get(new BigDecimal(IntervalUtils.intervalToAbsolute(interval)).divide(BD_NANOS_PER_SECOND)); + } + } + long[] a = DateTimeUtils.dateAndTimeFromValue(value, session); + long dateValue = a[0]; + long timeNanos = a[1]; + if (value instanceof ValueTime) { + result = ValueNumeric.get(BigDecimal.valueOf(timeNanos).divide(BD_NANOS_PER_SECOND)); + } else if (value instanceof ValueDate) { + result = ValueNumeric.get(BigInteger.valueOf(DateTimeUtils.absoluteDayFromDateValue(dateValue)) // + .multiply(BI_SECONDS_PER_DAY)); + } else { + BigDecimal bd = BigDecimal.valueOf(timeNanos).divide(BD_NANOS_PER_SECOND) + .add(BigDecimal.valueOf(DateTimeUtils.absoluteDayFromDateValue(dateValue)) // + .multiply(BD_SECONDS_PER_DAY)); + if (value instanceof ValueTimestampTimeZone) { + result = ValueNumeric.get( + bd.subtract(BigDecimal.valueOf(((ValueTimestampTimeZone) value).getTimeZoneOffsetSeconds()))); + } else if (value instanceof ValueTimeTimeZone) { + result = ValueNumeric + .get(bd.subtract(BigDecimal.valueOf(((ValueTimeTimeZone) value).getTimeZoneOffsetSeconds()))); + } else { + result = ValueNumeric.get(bd); + } + } + return result; + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + if (right != null) { + right = right.optimize(session); + } + switch (function) { + case EXTRACT: + type = field == EPOCH ? TypeInfo.getTypeInfo(Value.NUMERIC, + ValueBigint.DECIMAL_PRECISION + ValueTimestamp.MAXIMUM_SCALE, ValueTimestamp.MAXIMUM_SCALE, null) + : TypeInfo.TYPE_INTEGER; + break; + case DATE_TRUNC: { + type = left.getType(); + int valueType = type.getValueType(); + // TODO set scale when possible + if (!DataType.isDateTimeType(valueType)) { + throw Store.getInvalidExpressionTypeException("DATE_TRUNC datetime argument", left); + } else if (session.getMode().getEnum() == ModeEnum.PostgreSQL && valueType == Value.DATE) { + type = TypeInfo.TYPE_TIMESTAMP_TZ; + } + break; + } + case DATEADD: { + int valueType = right.getType().getValueType(); + if (valueType == Value.DATE) { + switch (field) { + case HOUR: + case MINUTE: + case SECOND: + case MILLISECOND: + case MICROSECOND: + case NANOSECOND: + case EPOCH: + valueType = Value.TIMESTAMP; + } + } + type = TypeInfo.getTypeInfo(valueType); + break; + } + case DATEDIFF: + type = TypeInfo.TYPE_BIGINT; + break; + default: + throw DbException.getInternalError("function=" + function); + } + if (left.isConstant() && (right == null || right.isConstant())) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + builder.append(getName()).append('(').append(getFieldName(field)); + switch (function) { + case EXTRACT: + left.getUnenclosedSQL(builder.append(" FROM "), sqlFlags); + break; + case DATE_TRUNC: + left.getUnenclosedSQL(builder.append(", "), sqlFlags); + break; + case DATEADD: + case DATEDIFF: + left.getUnenclosedSQL(builder.append(", "), sqlFlags).append(", "); + right.getUnenclosedSQL(builder, sqlFlags); + break; + default: + throw DbException.getInternalError("function=" + function); + } + return builder.append(')'); + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/DayMonthNameFunction.java b/h2/src/main/org/h2/expression/function/DayMonthNameFunction.java new file mode 100644 index 0000000..a6d521a --- /dev/null +++ b/h2/src/main/org/h2/expression/function/DayMonthNameFunction.java @@ -0,0 +1,107 @@ +/* + * 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.expression.function; + +import java.text.DateFormatSymbols; +import java.util.Locale; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.message.DbException; +import org.h2.util.DateTimeUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueNull; +import org.h2.value.ValueVarchar; + +/** + * A DAYNAME() or MONTHNAME() function. + */ +public final class DayMonthNameFunction extends Function1 { + + /** + * DAYNAME() (non-standard). + */ + public static final int DAYNAME = 0; + + /** + * MONTHNAME() (non-standard). + */ + public static final int MONTHNAME = DAYNAME + 1; + + private static final String[] NAMES = { // + "DAYNAME", "MONTHNAME" // + }; + + /** + * English names of months and week days. + */ + private static volatile String[][] MONTHS_AND_WEEKS; + + private final int function; + + public DayMonthNameFunction(Expression arg, int function) { + super(arg); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session) { + Value v = arg.getValue(session); + if (v == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + long dateValue = DateTimeUtils.dateAndTimeFromValue(v, session)[0]; + String result; + switch (function) { + case DAYNAME: + result = getMonthsAndWeeks(1)[DateTimeUtils.getDayOfWeek(dateValue, 0)]; + break; + case MONTHNAME: + result = getMonthsAndWeeks(0)[DateTimeUtils.monthFromDateValue(dateValue) - 1]; + break; + default: + throw DbException.getInternalError("function=" + function); + } + return ValueVarchar.get(result, session); + } + + /** + * Return names of month or weeks. + * + * @param field + * 0 for months, 1 for weekdays + * @return names of month or weeks + */ + private static String[] getMonthsAndWeeks(int field) { + String[][] result = MONTHS_AND_WEEKS; + if (result == null) { + result = new String[2][]; + DateFormatSymbols dfs = DateFormatSymbols.getInstance(Locale.ENGLISH); + result[0] = dfs.getMonths(); + result[1] = dfs.getWeekdays(); + MONTHS_AND_WEEKS = result; + } + return result[field]; + } + + @Override + public Expression optimize(SessionLocal session) { + arg = arg.optimize(session); + type = TypeInfo.getTypeInfo(Value.VARCHAR, 20, 0, null); + if (arg.isConstant()) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/FileFunction.java b/h2/src/main/org/h2/expression/function/FileFunction.java new file mode 100644 index 0000000..123582d --- /dev/null +++ b/h2/src/main/org/h2/expression/function/FileFunction.java @@ -0,0 +1,145 @@ +/* + * 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.expression.function; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.nio.file.Files; +import java.nio.file.Paths; + +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.message.DbException; +import org.h2.store.fs.FileUtils; +import org.h2.util.IOUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBigint; +import org.h2.value.ValueLob; +import org.h2.value.ValueNull; + +/** + * A FILE_READ or FILE_WRITE function. + */ +public final class FileFunction extends Function1_2 { + + /** + * FILE_READ() (non-standard). + */ + public static final int FILE_READ = 0; + + /** + * FILE_WRITE() (non-standard). + */ + public static final int FILE_WRITE = FILE_READ + 1; + + private static final String[] NAMES = { // + "FILE_READ", "FILE_WRITE" // + }; + + private final int function; + + public FileFunction(Expression arg1, Expression arg2, int function) { + super(arg1, arg2); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session) { + session.getUser().checkAdmin(); + Value v1 = left.getValue(session); + if (v1 == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + switch (function) { + case FILE_READ: { + String fileName = v1.getString(); + Database database = session.getDatabase(); + try { + long fileLength = FileUtils.size(fileName); + ValueLob lob; + try (InputStream in = FileUtils.newInputStream(fileName)) { + if (right == null) { + lob = database.getLobStorage().createBlob(in, fileLength); + } else { + Value v2 = right.getValue(session); + Reader reader = v2 == ValueNull.INSTANCE ? new InputStreamReader(in) + : new InputStreamReader(in, v2.getString()); + lob = database.getLobStorage().createClob(reader, fileLength); + } + } + v1 = session.addTemporaryLob(lob); + } catch (IOException e) { + throw DbException.convertIOException(e, fileName); + } + break; + } + case FILE_WRITE: { + Value v2 = right.getValue(session); + if (v2 == ValueNull.INSTANCE) { + v1 = ValueNull.INSTANCE; + } else { + String fileName = v2.getString(); + try (OutputStream fileOutputStream = Files.newOutputStream(Paths.get(fileName)); + InputStream in = v1.getInputStream()) { + v1 = ValueBigint.get(IOUtils.copy(in, fileOutputStream)); + } catch (IOException e) { + throw DbException.convertIOException(e, fileName); + } + } + break; + } + default: + throw DbException.getInternalError("function=" + function); + } + return v1; + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + if (right != null) { + right = right.optimize(session); + } + switch (function) { + case FILE_READ: + type = right == null ? TypeInfo.getTypeInfo(Value.BLOB, Integer.MAX_VALUE, 0, null) + : TypeInfo.getTypeInfo(Value.CLOB, Integer.MAX_VALUE, 0, null); + break; + case FILE_WRITE: + type = TypeInfo.TYPE_BIGINT; + break; + default: + throw DbException.getInternalError("function=" + function); + } + return this; + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + switch (visitor.getType()) { + case ExpressionVisitor.DETERMINISTIC: + case ExpressionVisitor.QUERY_COMPARABLE: + return false; + case ExpressionVisitor.READONLY: + if (function == FILE_WRITE) { + return false; + } + } + return super.isEverything(visitor); + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/Function0_1.java b/h2/src/main/org/h2/expression/function/Function0_1.java new file mode 100644 index 0000000..a255c69 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/Function0_1.java @@ -0,0 +1,96 @@ +/* + * 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.expression.function; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.value.TypeInfo; + +/** + * Function with one optional argument. + */ +public abstract class Function0_1 extends Expression implements NamedExpression { + + /** + * The argument of the operation. + */ + protected Expression arg; + + /** + * The type of the result. + */ + protected TypeInfo type; + + protected Function0_1(Expression arg) { + this.arg = arg; + } + + @Override + public TypeInfo getType() { + return type; + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + if (arg != null) { + arg.mapColumns(resolver, level, state); + } + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean value) { + if (arg != null) { + arg.setEvaluatable(tableFilter, value); + } + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + if (arg != null) { + arg.updateAggregate(session, stage); + } + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return arg == null || arg.isEverything(visitor); + } + + @Override + public int getCost() { + int cost = 1; + if (arg != null) { + cost += arg.getCost(); + } + return cost; + } + + @Override + public int getSubexpressionCount() { + return arg != null ? 1 : 0; + } + + @Override + public Expression getSubexpression(int index) { + if (index == 0 && arg != null) { + return arg; + } + throw new IndexOutOfBoundsException(); + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + builder.append(getName()).append('('); + if (arg != null) { + arg.getUnenclosedSQL(builder, sqlFlags); + } + return builder.append(')'); + } + +} diff --git a/h2/src/main/org/h2/expression/function/Function1.java b/h2/src/main/org/h2/expression/function/Function1.java new file mode 100644 index 0000000..190113a --- /dev/null +++ b/h2/src/main/org/h2/expression/function/Function1.java @@ -0,0 +1,25 @@ +/* + * 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.expression.function; + +import org.h2.expression.Expression; +import org.h2.expression.Operation1; + +/** + * Function with one argument. + */ +public abstract class Function1 extends Operation1 implements NamedExpression { + + protected Function1(Expression arg) { + super(arg); + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return arg.getUnenclosedSQL(builder.append(getName()).append('('), sqlFlags).append(')'); + } + +} diff --git a/h2/src/main/org/h2/expression/function/Function1_2.java b/h2/src/main/org/h2/expression/function/Function1_2.java new file mode 100644 index 0000000..75b0d0e --- /dev/null +++ b/h2/src/main/org/h2/expression/function/Function1_2.java @@ -0,0 +1,66 @@ +/* + * 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.expression.function; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.Operation1_2; +import org.h2.message.DbException; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * Function with two arguments. + */ +public abstract class Function1_2 extends Operation1_2 implements NamedExpression { + + protected Function1_2(Expression left, Expression right) { + super(left, right); + } + + @Override + public Value getValue(SessionLocal session) { + Value v1 = left.getValue(session); + if (v1 == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + Value v2; + if (right != null) { + v2 = right.getValue(session); + if (v2 == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + } else { + v2 = null; + } + return getValue(session, v1, v2); + } + + /** + * Returns the value of this function. + * + * @param session + * the session + * @param v1 + * the value of first argument + * @param v2 + * the value of second argument, or {@code null} + * @return the resulting value + */ + protected Value getValue(SessionLocal session, Value v1, Value v2) { + throw DbException.getInternalError(); + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + left.getUnenclosedSQL(builder.append(getName()).append('('), sqlFlags); + if (right != null) { + right.getUnenclosedSQL(builder.append(", "), sqlFlags); + } + return builder.append(')'); + } + +} diff --git a/h2/src/main/org/h2/expression/function/Function2.java b/h2/src/main/org/h2/expression/function/Function2.java new file mode 100644 index 0000000..cfb340f --- /dev/null +++ b/h2/src/main/org/h2/expression/function/Function2.java @@ -0,0 +1,58 @@ +/* + * 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.expression.function; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.Operation2; +import org.h2.message.DbException; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * Function with two arguments. + */ +public abstract class Function2 extends Operation2 implements NamedExpression { + + protected Function2(Expression left, Expression right) { + super(left, right); + } + + @Override + public Value getValue(SessionLocal session) { + Value v1 = left.getValue(session); + if (v1 == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + Value v2 = right.getValue(session); + if (v2 == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + return getValue(session, v1, v2); + } + + /** + * Returns the value of this function. + * + * @param session + * the session + * @param v1 + * the value of first argument + * @param v2 + * the value of second argument + * @return the resulting value + */ + protected Value getValue(SessionLocal session, Value v1, Value v2) { + throw DbException.getInternalError(); + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + left.getUnenclosedSQL(builder.append(getName()).append('('), sqlFlags).append(", "); + return right.getUnenclosedSQL(builder, sqlFlags).append(')'); + } + +} diff --git a/h2/src/main/org/h2/expression/function/FunctionN.java b/h2/src/main/org/h2/expression/function/FunctionN.java new file mode 100644 index 0000000..079191a --- /dev/null +++ b/h2/src/main/org/h2/expression/function/FunctionN.java @@ -0,0 +1,77 @@ +/* + * 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.expression.function; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.OperationN; +import org.h2.message.DbException; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * Function with many arguments. + */ +public abstract class FunctionN extends OperationN implements NamedExpression { + + protected FunctionN(Expression[] args) { + super(args); + } + + @Override + public Value getValue(SessionLocal session) { + Value v1, v2, v3; + int count = args.length; + if (count >= 1) { + v1 = args[0].getValue(session); + if (v1 == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + if (count >= 2) { + v2 = args[1].getValue(session); + if (v2 == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + if (count >= 3) { + v3 = args[2].getValue(session); + if (v3 == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + } else { + v3 = null; + } + } else { + v3 = v2 = null; + } + } else { + v3 = v2 = v1 = null; + } + return getValue(session, v1, v2, v3); + } + + /** + * Returns the value of this function. + * + * @param session + * the session + * @param v1 + * the value of first argument, or {@code null} + * @param v2 + * the value of second argument, or {@code null} + * @param v3 + * the value of third argument, or {@code null} + * @return the resulting value + */ + protected Value getValue(SessionLocal session, Value v1, Value v2, Value v3) { + throw DbException.getInternalError(); + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return writeExpressions(builder.append(getName()).append('('), args, sqlFlags).append(')'); + } + +} diff --git a/h2/src/main/org/h2/expression/function/HashFunction.java b/h2/src/main/org/h2/expression/function/HashFunction.java new file mode 100644 index 0000000..5ea0057 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/HashFunction.java @@ -0,0 +1,193 @@ +/* + * 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.expression.function; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.message.DbException; +import org.h2.security.SHA3; +import org.h2.util.Bits; +import org.h2.util.StringUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBigint; +import org.h2.value.ValueNull; +import org.h2.value.ValueVarbinary; + +/** + * A HASH or ORA_HASH function. + */ +public final class HashFunction extends FunctionN { + + /** + * HASH() (non-standard). + */ + public static final int HASH = 0; + + /** + * ORA_HASH() (non-standard). + */ + public static final int ORA_HASH = HASH + 1; + + private static final String[] NAMES = { // + "HASH", "ORA_HASH" // + }; + + private final int function; + + public HashFunction(Expression arg, int function) { + super(new Expression[] { arg }); + this.function = function; + } + + public HashFunction(Expression arg1, Expression arg2, Expression arg3, int function) { + super(arg3 == null ? new Expression[] { arg1, arg2 } : new Expression[] { arg1, arg2, arg3 }); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session, Value v1, Value v2, Value v3) { + switch (function) { + case HASH: + v1 = getHash(v1.getString(), v2, v3 == null ? 1 : v3.getInt()); + break; + case ORA_HASH: + v1 = oraHash(v1, v2 == null ? 0xffff_ffffL : v2.getLong(), v3 == null ? 0L : v3.getLong()); + break; + default: + throw DbException.getInternalError("function=" + function); + } + return v1; + } + + private static Value getHash(String algorithm, Value value, int iterations) { + if (iterations <= 0) { + throw DbException.getInvalidValueException("iterations", iterations); + } + MessageDigest md; + switch (StringUtils.toUpperEnglish(algorithm)) { + case "MD5": + case "SHA-1": + case "SHA-224": + case "SHA-256": + case "SHA-384": + case "SHA-512": + md = hashImpl(value, algorithm); + break; + case "SHA256": + md = hashImpl(value, "SHA-256"); + break; + case "SHA3-224": + md = hashImpl(value, SHA3.getSha3_224()); + break; + case "SHA3-256": + md = hashImpl(value, SHA3.getSha3_256()); + break; + case "SHA3-384": + md = hashImpl(value, SHA3.getSha3_384()); + break; + case "SHA3-512": + md = hashImpl(value, SHA3.getSha3_512()); + break; + default: + throw DbException.getInvalidValueException("algorithm", algorithm); + } + byte[] b = md.digest(); + for (int i = 1; i < iterations; i++) { + b = md.digest(b); + } + return ValueVarbinary.getNoCopy(b); + } + + private static Value oraHash(Value value, long bucket, long seed) { + if ((bucket & 0xffff_ffff_0000_0000L) != 0L) { + throw DbException.getInvalidValueException("bucket", bucket); + } + if ((seed & 0xffff_ffff_0000_0000L) != 0L) { + throw DbException.getInvalidValueException("seed", seed); + } + MessageDigest md = hashImpl(value, "SHA-1"); + if (md == null) { + return ValueNull.INSTANCE; + } + if (seed != 0L) { + byte[] b = new byte[4]; + Bits.writeInt(b, 0, (int) seed); + md.update(b); + } + long hc = Bits.readLong(md.digest(), 0); + // Strip sign and use modulo operation to get value from 0 to bucket + // inclusive + return ValueBigint.get((hc & Long.MAX_VALUE) % (bucket + 1)); + } + + private static MessageDigest hashImpl(Value value, String algorithm) { + MessageDigest md; + try { + md = MessageDigest.getInstance(algorithm); + } catch (Exception ex) { + throw DbException.convert(ex); + } + return hashImpl(value, md); + } + + private static MessageDigest hashImpl(Value value, MessageDigest md) { + try { + switch (value.getValueType()) { + case Value.VARCHAR: + case Value.CHAR: + case Value.VARCHAR_IGNORECASE: + md.update(value.getString().getBytes(StandardCharsets.UTF_8)); + break; + case Value.BLOB: + case Value.CLOB: { + byte[] buf = new byte[4096]; + try (InputStream is = value.getInputStream()) { + for (int r; (r = is.read(buf)) > 0;) { + md.update(buf, 0, r); + } + } + break; + } + default: + md.update(value.getBytesNoCopy()); + } + return md; + } catch (Exception ex) { + throw DbException.convert(ex); + } + } + + @Override + public Expression optimize(SessionLocal session) { + boolean allConst = optimizeArguments(session, true); + switch (function) { + case HASH: + type = TypeInfo.TYPE_VARBINARY; + break; + case ORA_HASH: + type = TypeInfo.TYPE_BIGINT; + break; + default: + throw DbException.getInternalError("function=" + function); + } + if (allConst) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/JavaFunction.java b/h2/src/main/org/h2/expression/function/JavaFunction.java new file mode 100644 index 0000000..afc617c --- /dev/null +++ b/h2/src/main/org/h2/expression/function/JavaFunction.java @@ -0,0 +1,140 @@ +/* + * 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.expression.function; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.ValueExpression; +import org.h2.message.DbException; +import org.h2.schema.FunctionAlias; +import org.h2.table.ColumnResolver; +import org.h2.table.TableFilter; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * This class wraps a user-defined function. + */ +public final class JavaFunction extends Expression implements NamedExpression { + + private final FunctionAlias functionAlias; + private final FunctionAlias.JavaMethod javaMethod; + private final Expression[] args; + + public JavaFunction(FunctionAlias functionAlias, Expression[] args) { + this.functionAlias = functionAlias; + this.javaMethod = functionAlias.findJavaMethod(args); + if (javaMethod.getDataType() == null) { + throw DbException.get(ErrorCode.FUNCTION_NOT_FOUND_1, getName()); + } + this.args = args; + } + + @Override + public Value getValue(SessionLocal session) { + return javaMethod.getValue(session, args, false); + } + + @Override + public TypeInfo getType() { + return javaMethod.getDataType(); + } + + @Override + public void mapColumns(ColumnResolver resolver, int level, int state) { + for (Expression e : args) { + e.mapColumns(resolver, level, state); + } + } + + @Override + public Expression optimize(SessionLocal session) { + boolean allConst = functionAlias.isDeterministic(); + for (int i = 0, len = args.length; i < len; i++) { + Expression e = args[i].optimize(session); + args[i] = e; + allConst &= e.isConstant(); + } + if (allConst) { + return ValueExpression.get(getValue(session)); + } + return this; + } + + @Override + public void setEvaluatable(TableFilter tableFilter, boolean b) { + for (Expression e : args) { + if (e != null) { + e.setEvaluatable(tableFilter, b); + } + } + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return writeExpressions(functionAlias.getSQL(builder, sqlFlags).append('('), args, sqlFlags).append(')'); + } + + @Override + public void updateAggregate(SessionLocal session, int stage) { + for (Expression e : args) { + if (e != null) { + e.updateAggregate(session, stage); + } + } + } + + @Override + public String getName() { + return functionAlias.getName(); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + switch (visitor.getType()) { + case ExpressionVisitor.DETERMINISTIC: + case ExpressionVisitor.READONLY: + case ExpressionVisitor.QUERY_COMPARABLE: + if (!functionAlias.isDeterministic()) { + return false; + } + // only if all parameters are deterministic as well + break; + case ExpressionVisitor.GET_DEPENDENCIES: + visitor.addDependency(functionAlias); + break; + default: + } + for (Expression e : args) { + if (e != null && !e.isEverything(visitor)) { + return false; + } + } + return true; + } + + @Override + public int getCost() { + int cost = javaMethod.hasConnectionParam() ? 25 : 5; + for (Expression e : args) { + cost += e.getCost(); + } + return cost; + } + + @Override + public int getSubexpressionCount() { + return args.length; + } + + @Override + public Expression getSubexpression(int index) { + return args[index]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/JsonConstructorFunction.java b/h2/src/main/org/h2/expression/function/JsonConstructorFunction.java new file mode 100644 index 0000000..87ab740 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/JsonConstructorFunction.java @@ -0,0 +1,171 @@ +/* + * 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.expression.function; + +import java.io.ByteArrayOutputStream; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionWithFlags; +import org.h2.expression.Format; +import org.h2.expression.OperationN; +import org.h2.expression.Subquery; +import org.h2.expression.TypedValueExpression; +import org.h2.message.DbException; +import org.h2.util.json.JsonConstructorUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueJson; +import org.h2.value.ValueNull; + +/** + * JSON constructor function. + */ +public final class JsonConstructorFunction extends OperationN implements ExpressionWithFlags, NamedExpression { + + private final boolean array; + + private int flags; + + /** + * Creates a new instance of JSON constructor function. + * + * @param array + * {@code false} for {@code JSON_OBJECT}, {@code true} for + * {@code JSON_ARRAY}. + */ + public JsonConstructorFunction(boolean array) { + super(new Expression[4]); + this.array = array; + } + + @Override + public void setFlags(int flags) { + this.flags = flags; + } + + @Override + public int getFlags() { + return flags; + } + + @Override + public Value getValue(SessionLocal session) { + return array ? jsonArray(session, args) : jsonObject(session, args); + } + + private Value jsonObject(SessionLocal session, Expression[] args) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write('{'); + for (int i = 0, l = args.length; i < l;) { + String name = args[i++].getValue(session).getString(); + if (name == null) { + throw DbException.getInvalidValueException("JSON_OBJECT key", "NULL"); + } + Value value = args[i++].getValue(session); + if (value == ValueNull.INSTANCE) { + if ((flags & JsonConstructorUtils.JSON_ABSENT_ON_NULL) != 0) { + continue; + } else { + value = ValueJson.NULL; + } + } + JsonConstructorUtils.jsonObjectAppend(baos, name, value); + } + return JsonConstructorUtils.jsonObjectFinish(baos, flags); + } + + private Value jsonArray(SessionLocal session, Expression[] args) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write('['); + int l = args.length; + evaluate: { + if (l == 1) { + Expression arg0 = args[0]; + if (arg0 instanceof Subquery) { + Subquery q = (Subquery) arg0; + for (Value value : q.getAllRows(session)) { + JsonConstructorUtils.jsonArrayAppend(baos, value, flags); + } + break evaluate; + } else if (arg0 instanceof Format) { + Format format = (Format) arg0; + arg0 = format.getSubexpression(0); + if (arg0 instanceof Subquery) { + Subquery q = (Subquery) arg0; + for (Value value : q.getAllRows(session)) { + JsonConstructorUtils.jsonArrayAppend(baos, format.getValue(value), flags); + } + break evaluate; + } + } + } + for (int i = 0; i < l;) { + JsonConstructorUtils.jsonArrayAppend(baos, args[i++].getValue(session), flags); + } + } + baos.write(']'); + return ValueJson.getInternal(baos.toByteArray()); + } + + @Override + public Expression optimize(SessionLocal session) { + boolean allConst = optimizeArguments(session, true); + type = TypeInfo.TYPE_JSON; + if (allConst) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + builder.append(getName()).append('('); + if (array) { + writeExpressions(builder, args, sqlFlags); + } else { + for (int i = 0, l = args.length; i < l;) { + if (i > 0) { + builder.append(", "); + } + args[i++].getUnenclosedSQL(builder, sqlFlags).append(": "); + args[i++].getUnenclosedSQL(builder, sqlFlags); + } + } + return getJsonFunctionFlagsSQL(builder, flags, array).append(')'); + } + + /** + * Appends flags of a JSON function to the specified string builder. + * + * @param builder + * string builder to append to + * @param flags + * flags to append + * @param forArray + * whether the function is an array function + * @return the specified string builder + */ + public static StringBuilder getJsonFunctionFlagsSQL(StringBuilder builder, int flags, boolean forArray) { + if ((flags & JsonConstructorUtils.JSON_ABSENT_ON_NULL) != 0) { + if (!forArray) { + builder.append(" ABSENT ON NULL"); + } + } else if (forArray) { + builder.append(" NULL ON NULL"); + } + if (!forArray && (flags & JsonConstructorUtils.JSON_WITH_UNIQUE_KEYS) != 0) { + builder.append(" WITH UNIQUE KEYS"); + } + return builder; + } + + @Override + public String getName() { + return array ? "JSON_ARRAY" : "JSON_OBJECT"; + } + +} diff --git a/h2/src/main/org/h2/expression/function/LengthFunction.java b/h2/src/main/org/h2/expression/function/LengthFunction.java new file mode 100644 index 0000000..199837d --- /dev/null +++ b/h2/src/main/org/h2/expression/function/LengthFunction.java @@ -0,0 +1,86 @@ +/* + * 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.expression.function; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.message.DbException; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBigint; +import org.h2.value.ValueNull; + +/** + * CHAR_LENGTH(), or OCTET_LENGTH() function. + */ +public final class LengthFunction extends Function1 { + + /** + * CHAR_LENGTH(). + */ + public static final int CHAR_LENGTH = 0; + + /** + * OCTET_LENGTH(). + */ + public static final int OCTET_LENGTH = CHAR_LENGTH + 1; + + /** + * BIT_LENGTH() (non-standard). + */ + public static final int BIT_LENGTH = OCTET_LENGTH + 1; + + private static final String[] NAMES = { // + "CHAR_LENGTH", "OCTET_LENGTH", "BIT_LENGTH" // + }; + + private final int function; + + public LengthFunction(Expression arg, int function) { + super(arg); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session) { + Value v = arg.getValue(session); + if (v == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + long l; + switch (function) { + case CHAR_LENGTH: + l = v.charLength(); + break; + case OCTET_LENGTH: + l = v.octetLength(); + break; + case BIT_LENGTH: + l = v.octetLength() * 8; + break; + default: + throw DbException.getInternalError("function=" + function); + } + return ValueBigint.get(l); + } + + @Override + public Expression optimize(SessionLocal session) { + arg = arg.optimize(session); + type = TypeInfo.TYPE_BIGINT; + if (arg.isConstant()) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/MathFunction.java b/h2/src/main/org/h2/expression/function/MathFunction.java new file mode 100644 index 0000000..62731d6 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/MathFunction.java @@ -0,0 +1,395 @@ +/* + * 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.expression.function; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +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; +import org.h2.value.ValueBigint; +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; + +/** + * A math function. + */ +public final class MathFunction extends Function1_2 { + + /** + * ABS(). + */ + public static final int ABS = 0; + + /** + * MOD(). + */ + public static final int MOD = ABS + 1; + + /** + * FLOOR(). + */ + public static final int FLOOR = MOD + 1; + + /** + * CEIL() or CEILING(). + */ + public static final int CEIL = FLOOR + 1; + + /** + * ROUND() (non-standard) + */ + public static final int ROUND = CEIL + 1; + + /** + * ROUNDMAGIC() (non-standard) + */ + public static final int ROUNDMAGIC = ROUND + 1; + + /** + * SIGN() (non-standard) + */ + public static final int SIGN = ROUNDMAGIC + 1; + + /** + * TRUNC() (non-standard) + */ + public static final int TRUNC = SIGN + 1; + + private static final String[] NAMES = { // + "ABS", "MOD", "FLOOR", "CEIL", "ROUND", "ROUNDMAGIC", "SIGN", "TRUNC" // + }; + + private final int function; + + private TypeInfo commonType; + + public MathFunction(Expression arg1, Expression arg2, int function) { + super(arg1, arg2); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session, Value v1, Value v2) { + switch (function) { + case ABS: + if (v1.getSignum() < 0) { + v1 = v1.negate(); + } + break; + case MOD: + v1 = v1.convertTo(commonType, session).modulus(v2.convertTo(commonType, session)).convertTo(type, session); + break; + case FLOOR: + v1 = round(v1, v2, RoundingMode.FLOOR); + break; + case CEIL: + v1 = round(v1, v2, RoundingMode.CEILING); + break; + case ROUND: + v1 = round(v1, v2, RoundingMode.HALF_UP); + break; + case ROUNDMAGIC: + v1 = ValueDouble.get(roundMagic(v1.getDouble())); + break; + case SIGN: + v1 = ValueInteger.get(v1.getSignum()); + break; + case TRUNC: + v1 = round(v1, v2, RoundingMode.DOWN); + break; + default: + throw DbException.getInternalError("function=" + function); + } + return v1; + } + + @SuppressWarnings("incomplete-switch") + private Value round(Value v1, Value v2, RoundingMode roundingMode) { + int scale = v2 != null ? v2.getInt() : 0; + int t = type.getValueType(); + c: switch (t) { + case Value.TINYINT: + case Value.SMALLINT: + case Value.INTEGER: + case Value.BIGINT: { + if (scale < 0) { + long original = v1.getLong(); + long scaled = BigDecimal.valueOf(original).setScale(scale, roundingMode).longValue(); + if (original != scaled) { + v1 = ValueBigint.get(scaled).convertTo(type); + } + } + break; + } + case Value.NUMERIC: { + int targetScale = type.getScale(); + BigDecimal bd = v1.getBigDecimal(); + if (scale < targetScale) { + bd = bd.setScale(scale, roundingMode); + } + v1 = ValueNumeric.get(bd.setScale(targetScale, roundingMode)); + break; + } + case Value.REAL: + case Value.DOUBLE: { + l: if (scale == 0) { + double d; + switch (roundingMode) { + case DOWN: + d = v1.getDouble(); + d = d < 0 ? Math.ceil(d) : Math.floor(d); + break; + case CEILING: + d = Math.ceil(v1.getDouble()); + break; + case FLOOR: + d = Math.floor(v1.getDouble()); + break; + default: + break l; + } + v1 = t == Value.REAL ? ValueReal.get((float) d) : ValueDouble.get(d); + break c; + } + BigDecimal bd = v1.getBigDecimal().setScale(scale, roundingMode); + v1 = t == Value.REAL ? ValueReal.get(bd.floatValue()) : ValueDouble.get(bd.doubleValue()); + break; + } + case Value.DECFLOAT: + v1 = ValueDecfloat.get(v1.getBigDecimal().setScale(scale, roundingMode)); + } + return v1; + } + + private static double roundMagic(double d) { + if ((d < 0.000_000_000_000_1) && (d > -0.000_000_000_000_1)) { + return 0.0; + } + if ((d > 1_000_000_000_000d) || (d < -1_000_000_000_000d)) { + return d; + } + StringBuilder s = new StringBuilder(); + s.append(d); + if (s.toString().indexOf('E') >= 0) { + return d; + } + int len = s.length(); + if (len < 16) { + return d; + } + if (s.toString().indexOf('.') > len - 3) { + return d; + } + s.delete(len - 2, len); + len -= 2; + char c1 = s.charAt(len - 2); + char c2 = s.charAt(len - 3); + char c3 = s.charAt(len - 4); + if ((c1 == '0') && (c2 == '0') && (c3 == '0')) { + s.setCharAt(len - 1, '0'); + } else if ((c1 == '9') && (c2 == '9') && (c3 == '9')) { + s.setCharAt(len - 1, '9'); + s.append('9'); + s.append('9'); + s.append('9'); + } + return Double.parseDouble(s.toString()); + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + if (right != null) { + right = right.optimize(session); + } + switch (function) { + case ABS: + type = left.getType(); + if (type.getValueType() == Value.NULL) { + type = TypeInfo.TYPE_NUMERIC_FLOATING_POINT; + } + break; + case FLOOR: + case CEIL: { + Expression e = optimizeRound(0, true, false, true); + if (e != null) { + return e; + } + break; + } + case MOD: + TypeInfo divisorType = right.getType(); + commonType = TypeInfo.getHigherType(left.getType(), divisorType); + int valueType = commonType.getValueType(); + if (valueType == Value.NULL) { + commonType = TypeInfo.TYPE_BIGINT; + } else if (!DataType.isNumericType(valueType)) { + throw Store.getInvalidExpressionTypeException("MOD argument", + DataType.isNumericType(left.getType().getValueType()) ? right : left); + } + type = DataType.isNumericType(divisorType.getValueType()) ? divisorType : commonType; + break; + case ROUND: { + Expression e = optimizeRoundWithScale(session, true); + if (e != null) { + return e; + } + break; + } + case ROUNDMAGIC: + type = TypeInfo.TYPE_DOUBLE; + break; + case SIGN: + type = TypeInfo.TYPE_INTEGER; + break; + case TRUNC: + switch (left.getType().getValueType()) { + case Value.VARCHAR: + left = new CastSpecification(left, TypeInfo.getTypeInfo(Value.TIMESTAMP, -1L, 0, null)) + .optimize(session); + //$FALL-THROUGH$ + case Value.TIMESTAMP: + case Value.TIMESTAMP_TZ: + if (right != null) { + throw DbException.get(ErrorCode.INVALID_PARAMETER_COUNT_2, "TRUNC", "1"); + } + return new DateTimeFunction(DateTimeFunction.DATE_TRUNC, DateTimeFunction.DAY, left, null) + .optimize(session); + case Value.DATE: + if (right != null) { + throw DbException.get(ErrorCode.INVALID_PARAMETER_COUNT_2, "TRUNC", "1"); + } + return new CastSpecification(left, TypeInfo.getTypeInfo(Value.TIMESTAMP, -1L, 0, null)) + .optimize(session); + default: { + Expression e = optimizeRoundWithScale(session, false); + if (e != null) { + return e; + } + } + } + break; + default: + throw DbException.getInternalError("function=" + function); + } + if (left.isConstant() && (right == null || right.isConstant())) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + private Expression optimizeRoundWithScale(SessionLocal session, boolean possibleRoundUp) { + int scale; + boolean scaleIsKnown = false, scaleIsNull = false; + if (right != null) { + if (right.isConstant()) { + Value scaleValue = right.getValue(session); + scaleIsKnown = true; + if (scaleValue != ValueNull.INSTANCE) { + scale = scaleValue.getInt(); + } else { + scale = -1; + scaleIsNull = true; + } + } else { + scale = -1; + } + } else { + scale = 0; + scaleIsKnown = true; + } + return optimizeRound(scale, scaleIsKnown, scaleIsNull, possibleRoundUp); + } + + /** + * Optimizes rounding and truncation functions. + * + * @param scale + * the scale, if known + * @param scaleIsKnown + * whether scale is known + * @param scaleIsNull + * whether scale is {@code NULL} + * @param possibleRoundUp + * {@code true} if result of rounding can have larger precision + * than precision of argument, {@code false} otherwise + * @return the optimized expression or {@code null} if this function should + * be used + */ + private Expression optimizeRound(int scale, boolean scaleIsKnown, boolean scaleIsNull, boolean possibleRoundUp) { + TypeInfo leftType = left.getType(); + switch (leftType.getValueType()) { + case Value.NULL: + type = TypeInfo.TYPE_NUMERIC_SCALE_0; + break; + case Value.TINYINT: + case Value.SMALLINT: + case Value.INTEGER: + case Value.BIGINT: + if (scaleIsKnown && scale >= 0) { + return left; + } + type = leftType; + break; + case Value.REAL: + case Value.DOUBLE: + case Value.DECFLOAT: + type = leftType; + break; + case Value.NUMERIC: { + long precision; + int originalScale = leftType.getScale(); + if (scaleIsKnown) { + if (originalScale <= scale) { + return left; + } else { + if (scale < 0) { + scale = 0; + } else if (scale > ValueNumeric.MAXIMUM_SCALE) { + scale = ValueNumeric.MAXIMUM_SCALE; + } + precision = leftType.getPrecision() - originalScale + scale; + if (possibleRoundUp) { + precision++; + } + } + } else { + precision = leftType.getPrecision(); + if (possibleRoundUp) { + precision++; + } + scale = originalScale; + } + type = TypeInfo.getTypeInfo(Value.NUMERIC, precision, scale, null); + break; + } + default: + throw Store.getInvalidExpressionTypeException(getName() + " argument", left); + } + if (scaleIsNull) { + return TypedValueExpression.get(ValueNull.INSTANCE, type); + } + return null; + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/MathFunction1.java b/h2/src/main/org/h2/expression/function/MathFunction1.java new file mode 100644 index 0000000..416b093 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/MathFunction1.java @@ -0,0 +1,212 @@ +/* + * 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.expression.function; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.message.DbException; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueDouble; +import org.h2.value.ValueNull; + +/** + * A math function with one argument and DOUBLE PRECISION result. + */ +public final class MathFunction1 extends Function1 { + + // Trigonometric functions + + /** + * SIN(). + */ + public static final int SIN = 0; + + /** + * COS(). + */ + public static final int COS = SIN + 1; + + /** + * TAN(). + */ + public static final int TAN = COS + 1; + + /** + * COT() (non-standard). + */ + public static final int COT = TAN + 1; + + /** + * SINH(). + */ + public static final int SINH = COT + 1; + + /** + * COSH(). + */ + public static final int COSH = SINH + 1; + + /** + * TANH(). + */ + public static final int TANH = COSH + 1; + + /** + * ASIN(). + */ + public static final int ASIN = TANH + 1; + + /** + * ACOS(). + */ + public static final int ACOS = ASIN + 1; + + /** + * ATAN(). + */ + public static final int ATAN = ACOS + 1; + + // Logarithm functions + + /** + * LOG10(). + */ + public static final int LOG10 = ATAN + 1; + + /** + * LN(). + */ + public static final int LN = LOG10 + 1; + + // Exponential function + + /** + * EXP(). + */ + public static final int EXP = LN + 1; + + // Square root + + /** + * SQRT(). + */ + public static final int SQRT = EXP + 1; + + // Other non-standard + + /** + * DEGREES() (non-standard). + */ + public static final int DEGREES = SQRT + 1; + + /** + * RADIANS() (non-standard). + */ + public static final int RADIANS = DEGREES + 1; + + private static final String[] NAMES = { // + "SIN", "COS", "TAN", "COT", "SINH", "COSH", "TANH", "ASIN", "ACOS", "ATAN", // + "LOG10", "LN", "EXP", "SQRT", "DEGREES", "RADIANS" // + }; + + private final int function; + + public MathFunction1(Expression arg, int function) { + super(arg); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session) { + Value v = arg.getValue(session); + if (v == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + double d = v.getDouble(); + switch (function) { + case SIN: + d = Math.sin(d); + break; + case COS: + d = Math.cos(d); + break; + case TAN: + d = Math.tan(d); + break; + case COT: + d = Math.tan(d); + if (d == 0.0) { + throw DbException.get(ErrorCode.DIVISION_BY_ZERO_1, getTraceSQL()); + } + d = 1d / d; + break; + case SINH: + d = Math.sinh(d); + break; + case COSH: + d = Math.cosh(d); + break; + case TANH: + d = Math.tanh(d); + break; + case ASIN: + d = Math.asin(d); + break; + case ACOS: + d = Math.acos(d); + break; + case ATAN: + d = Math.atan(d); + break; + case LOG10: + if (d <= 0) { + throw DbException.getInvalidValueException("LOG10() argument", d); + } + d = Math.log10(d); + break; + case LN: + if (d <= 0) { + throw DbException.getInvalidValueException("LN() argument", d); + } + d = Math.log(d); + break; + case EXP: + d = Math.exp(d); + break; + case SQRT: + d = Math.sqrt(d); + break; + case DEGREES: + d = Math.toDegrees(d); + break; + case RADIANS: + d = Math.toRadians(d); + break; + default: + throw DbException.getInternalError("function=" + function); + } + return ValueDouble.get(d); + } + + @Override + public Expression optimize(SessionLocal session) { + arg = arg.optimize(session); + type = TypeInfo.TYPE_DOUBLE; + if (arg.isConstant()) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/MathFunction2.java b/h2/src/main/org/h2/expression/function/MathFunction2.java new file mode 100644 index 0000000..52dff56 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/MathFunction2.java @@ -0,0 +1,100 @@ +/* + * 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.expression.function; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.message.DbException; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueDouble; + +/** + * A math function with two arguments and DOUBLE PRECISION result. + */ +public final class MathFunction2 extends Function2 { + + /** + * ATAN2() (non-standard). + */ + public static final int ATAN2 = 0; + + /** + * LOG(). + */ + public static final int LOG = ATAN2 + 1; + + /** + * POWER(). + */ + public static final int POWER = LOG + 1; + + private static final String[] NAMES = { // + "ATAN2", "LOG", "POWER" // + }; + + private final int function; + + public MathFunction2(Expression arg1, Expression arg2, int function) { + super(arg1, arg2); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session, Value v1, Value v2) { + double d1 = v1.getDouble(), d2 = v2.getDouble(); + switch (function) { + case ATAN2: + d1 = Math.atan2(d1, d2); + break; + case LOG: { + if (session.getMode().swapLogFunctionParameters) { + double t = d2; + d2 = d1; + d1 = t; + } + if (d2 <= 0) { + throw DbException.getInvalidValueException("LOG() argument", d2); + } + if (d1 <= 0 || d1 == 1) { + throw DbException.getInvalidValueException("LOG() base", d1); + } + if (d1 == Math.E) { + d1 = Math.log(d2); + } else if (d1 == 10d) { + d1 = Math.log10(d2); + } else { + d1 = Math.log(d2) / Math.log(d1); + } + break; + } + case POWER: + d1 = Math.pow(d1, d2); + break; + default: + throw DbException.getInternalError("function=" + function); + } + return ValueDouble.get(d1); + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + right = right.optimize(session); + type = TypeInfo.TYPE_DOUBLE; + if (left.isConstant() && right.isConstant()) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/NamedExpression.java b/h2/src/main/org/h2/expression/function/NamedExpression.java new file mode 100644 index 0000000..021c87e --- /dev/null +++ b/h2/src/main/org/h2/expression/function/NamedExpression.java @@ -0,0 +1,20 @@ +/* + * 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.expression.function; + +/** + * A function-like expression with a name. + */ +public interface NamedExpression { + + /** + * Get the name. + * + * @return the name in uppercase + */ + String getName(); + +} diff --git a/h2/src/main/org/h2/expression/function/NullIfFunction.java b/h2/src/main/org/h2/expression/function/NullIfFunction.java new file mode 100644 index 0000000..b4b32d6 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/NullIfFunction.java @@ -0,0 +1,50 @@ +/* + * 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.expression.function; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * A NULLIF function. + */ +public final class NullIfFunction extends Function2 { + + public NullIfFunction(Expression arg1, Expression arg2) { + super(arg1, arg2); + } + + @Override + public Value getValue(SessionLocal session) { + Value v = left.getValue(session); + if (session.compareWithNull(v, right.getValue(session), true) == 0) { + v = ValueNull.INSTANCE; + } + return v; + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + right = right.optimize(session); + type = left.getType(); + TypeInfo.checkComparable(type, right.getType()); + if (left.isConstant() && right.isConstant()) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public String getName() { + return "NULLIF"; + } + +} diff --git a/h2/src/main/org/h2/expression/function/RandFunction.java b/h2/src/main/org/h2/expression/function/RandFunction.java new file mode 100644 index 0000000..9b4c3af --- /dev/null +++ b/h2/src/main/org/h2/expression/function/RandFunction.java @@ -0,0 +1,124 @@ +/* + * 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.expression.function; + +import java.util.Random; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.message.DbException; +import org.h2.util.MathUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueDouble; +import org.h2.value.ValueNull; +import org.h2.value.ValueUuid; +import org.h2.value.ValueVarbinary; + +/** + * A RAND, SECURE_RAND, or RANDOM_UUID function. + */ +public final class RandFunction extends Function0_1 { + + /** + * RAND() (non-standard). + */ + public static final int RAND = 0; + + /** + * SECURE_RAND() (non-standard). + */ + public static final int SECURE_RAND = RAND + 1; + + /** + * RANDOM_UUID() (non-standard). + */ + public static final int RANDOM_UUID = SECURE_RAND + 1; + + private static final String[] NAMES = { // + "RAND", "SECURE_RAND", "RANDOM_UUID" // + }; + + private final int function; + + public RandFunction(Expression arg, int function) { + super(arg); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session) { + Value v; + if (arg != null) { + v = arg.getValue(session); + if (v == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + } else { + v = null; + } + switch (function) { + case RAND: { + Random random = session.getRandom(); + if (v != null) { + random.setSeed(v.getInt()); + } + v = ValueDouble.get(random.nextDouble()); + break; + } + case SECURE_RAND: + v = ValueVarbinary.getNoCopy(MathUtils.secureRandomBytes(v.getInt())); + break; + case RANDOM_UUID: + v = ValueUuid.getNewRandom(); + break; + default: + throw DbException.getInternalError("function=" + function); + } + return v; + } + + @Override + public Expression optimize(SessionLocal session) { + if (arg != null) { + arg = arg.optimize(session); + } + switch (function) { + case RAND: + type = TypeInfo.TYPE_DOUBLE; + break; + case SECURE_RAND: { + Value v; + type = arg.isConstant() && (v = arg.getValue(session)) != ValueNull.INSTANCE + ? TypeInfo.getTypeInfo(Value.VARBINARY, Math.max(v.getInt(), 1), 0, null) + : TypeInfo.TYPE_VARBINARY; + break; + } + case RANDOM_UUID: + type = TypeInfo.TYPE_UUID; + break; + default: + throw DbException.getInternalError("function=" + function); + } + return this; + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + switch (visitor.getType()) { + case ExpressionVisitor.DETERMINISTIC: + return false; + } + return super.isEverything(visitor); + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/RegexpFunction.java b/h2/src/main/org/h2/expression/function/RegexpFunction.java new file mode 100644 index 0000000..a3c1928 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/RegexpFunction.java @@ -0,0 +1,270 @@ +/* + * 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.expression.function; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import org.h2.api.ErrorCode; +import org.h2.engine.Mode; +import org.h2.engine.Mode.ModeEnum; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.message.DbException; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueNull; +import org.h2.value.ValueVarchar; + +/** + * A regular expression function. + */ +public final class RegexpFunction extends FunctionN { + + /** + * REGEXP_LIKE() (non-standard). + */ + public static final int REGEXP_LIKE = 0; + + /** + * REGEXP_REPLACE() (non-standard). + */ + public static final int REGEXP_REPLACE = REGEXP_LIKE + 1; + + /** + * REGEXP_SUBSTR() (non-standard). + */ + public static final int REGEXP_SUBSTR = REGEXP_REPLACE + 1; + + private static final String[] NAMES = { // + "REGEXP_LIKE", "REGEXP_REPLACE", "REGEXP_SUBSTR" // + }; + + private final int function; + + public RegexpFunction(int function) { + super(new Expression[function == REGEXP_LIKE ? 3 : 6]); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session) { + Value v1 = args[0].getValue(session); + Value v2 = args[1].getValue(session); + int length = args.length; + switch (function) { + case REGEXP_LIKE: { + Value v3 = length >= 3 ? args[2].getValue(session) : null; + if (v1 == ValueNull.INSTANCE || v2 == ValueNull.INSTANCE || v3 == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + String regexp = v2.getString(); + String regexpMode = v3 != null ? v3.getString() : null; + int flags = makeRegexpFlags(regexpMode, false); + try { + v1 = ValueBoolean.get(Pattern.compile(regexp, flags).matcher(v1.getString()).find()); + } catch (PatternSyntaxException e) { + throw DbException.get(ErrorCode.LIKE_ESCAPE_ERROR_1, e, regexp); + } + break; + } + case REGEXP_REPLACE: { + String input = v1.getString(); + if (session.getMode().getEnum() == ModeEnum.Oracle) { + String replacement = args[2].getValue(session).getString(); + int position = length >= 4 ? args[3].getValue(session).getInt() : 1; + int occurrence = length >= 5 ? args[4].getValue(session).getInt() : 0; + String regexpMode = length >= 6 ? args[5].getValue(session).getString() : null; + if (input == null) { + v1 = ValueNull.INSTANCE; + } else { + String regexp = v2.getString(); + v1 = regexpReplace(session, input, regexp != null ? regexp : "", + replacement != null ? replacement : "", position, occurrence, regexpMode); + } + } else { + if (length > 4) { + throw DbException.get(ErrorCode.INVALID_PARAMETER_COUNT_2, getName(), "3..4"); + } + Value v3 = args[2].getValue(session); + Value v4 = length == 4 ? args[3].getValue(session) : null; + if (v1 == ValueNull.INSTANCE || v2 == ValueNull.INSTANCE || v3 == ValueNull.INSTANCE + || v4 == ValueNull.INSTANCE) { + v1 = ValueNull.INSTANCE; + } else { + v1 = regexpReplace(session, input, v2.getString(), v3.getString(), 1, 0, + v4 != null ? v4.getString() : null); + } + } + break; + } + case REGEXP_SUBSTR: { + Value v3 = length >= 3 ? args[2].getValue(session) : null; + Value v4 = length >= 4 ? args[3].getValue(session) : null; + Value v5 = length >= 5 ? args[4].getValue(session) : null; + Value v6 = length >= 6 ? args[5].getValue(session) : null; + v1 = regexpSubstr(v1, v2, v3, v4, v5, v6, session); + break; + } + default: + throw DbException.getInternalError("function=" + function); + } + return v1; + } + + private static Value regexpReplace(SessionLocal session, String input, String regexp, String replacement, + int position, int occurrence, String regexpMode) { + Mode mode = session.getMode(); + if (mode.regexpReplaceBackslashReferences) { + if ((replacement.indexOf('\\') >= 0) || (replacement.indexOf('$') >= 0)) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < replacement.length(); i++) { + char c = replacement.charAt(i); + if (c == '$') { + sb.append('\\'); + } else if (c == '\\' && ++i < replacement.length()) { + c = replacement.charAt(i); + sb.append(c >= '0' && c <= '9' ? '$' : '\\'); + } + sb.append(c); + } + replacement = sb.toString(); + } + } + boolean isInPostgreSqlMode = mode.getEnum() == ModeEnum.PostgreSQL; + int flags = makeRegexpFlags(regexpMode, isInPostgreSqlMode); + if (isInPostgreSqlMode && (regexpMode == null || regexpMode.isEmpty() || !regexpMode.contains("g"))) { + occurrence = 1; + } + try { + Matcher matcher = Pattern.compile(regexp, flags).matcher(input).region(position - 1, input.length()); + if (occurrence == 0) { + return ValueVarchar.get(matcher.replaceAll(replacement), session); + } else { + StringBuffer sb = new StringBuffer(); + int index = 1; + while (matcher.find()) { + if (index == occurrence) { + matcher.appendReplacement(sb, replacement); + break; + } + index++; + } + matcher.appendTail(sb); + return ValueVarchar.get(sb.toString(), session); + } + } catch (PatternSyntaxException e) { + throw DbException.get(ErrorCode.LIKE_ESCAPE_ERROR_1, e, regexp); + } catch (StringIndexOutOfBoundsException | IllegalArgumentException e) { + throw DbException.get(ErrorCode.LIKE_ESCAPE_ERROR_1, e, replacement); + } + } + + private static Value regexpSubstr(Value inputString, Value regexpArg, Value positionArg, Value occurrenceArg, + Value regexpModeArg, Value subexpressionArg, SessionLocal session) { + if (inputString == ValueNull.INSTANCE || regexpArg == ValueNull.INSTANCE || positionArg == ValueNull.INSTANCE + || occurrenceArg == ValueNull.INSTANCE || subexpressionArg == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + String regexp = regexpArg.getString(); + + int position = positionArg != null ? positionArg.getInt() - 1 : 0; + int requestedOccurrence = occurrenceArg != null ? occurrenceArg.getInt() : 1; + String regexpMode = regexpModeArg != null ? regexpModeArg.getString() : null; + int subexpression = subexpressionArg != null ? subexpressionArg.getInt() : 0; + int flags = makeRegexpFlags(regexpMode, false); + try { + Matcher m = Pattern.compile(regexp, flags).matcher(inputString.getString()); + + boolean found = m.find(position); + for (int occurrence = 1; occurrence < requestedOccurrence && found; occurrence++) { + found = m.find(); + } + + if (!found) { + return ValueNull.INSTANCE; + } else { + return ValueVarchar.get(m.group(subexpression), session); + } + } catch (PatternSyntaxException e) { + throw DbException.get(ErrorCode.LIKE_ESCAPE_ERROR_1, e, regexp); + } catch (IndexOutOfBoundsException e) { + return ValueNull.INSTANCE; + } + } + + private static int makeRegexpFlags(String stringFlags, boolean ignoreGlobalFlag) { + int flags = Pattern.UNICODE_CASE; + if (stringFlags != null) { + for (int i = 0; i < stringFlags.length(); ++i) { + switch (stringFlags.charAt(i)) { + case 'i': + flags |= Pattern.CASE_INSENSITIVE; + break; + case 'c': + flags &= ~Pattern.CASE_INSENSITIVE; + break; + case 'n': + flags |= Pattern.DOTALL; + break; + case 'm': + flags |= Pattern.MULTILINE; + break; + case 'g': + if (ignoreGlobalFlag) { + break; + } + //$FALL-THROUGH$ + default: + throw DbException.get(ErrorCode.INVALID_VALUE_2, stringFlags); + } + } + } + return flags; + } + + @Override + public Expression optimize(SessionLocal session) { + boolean allConst = optimizeArguments(session, true); + int min, max; + switch (function) { + case REGEXP_LIKE: + min = 2; + max = 3; + type = TypeInfo.TYPE_BOOLEAN; + break; + case REGEXP_REPLACE: + min = 3; + max = 6; + type = TypeInfo.TYPE_VARCHAR; + break; + case REGEXP_SUBSTR: + min = 2; + max = 6; + type = TypeInfo.TYPE_VARCHAR; + break; + default: + throw DbException.getInternalError("function=" + function); + } + int len = args.length; + if (len < min || len > max) { + throw DbException.get(ErrorCode.INVALID_PARAMETER_COUNT_2, getName(), min + ".." + max); + } + if (allConst) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/SessionControlFunction.java b/h2/src/main/org/h2/expression/function/SessionControlFunction.java new file mode 100644 index 0000000..c8d3024 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/SessionControlFunction.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.expression.function; + +import org.h2.command.Command; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.message.DbException; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueNull; + +/** + * An ABORT_SESSION() or CANCEL_SESSION() function. + */ +public final class SessionControlFunction extends Function1 { + + /** + * ABORT_SESSION(). + */ + public static final int ABORT_SESSION = 0; + + /** + * CANCEL_SESSION(). + */ + public static final int CANCEL_SESSION = ABORT_SESSION + 1; + + private static final String[] NAMES = { // + "ABORT_SESSION", "CANCEL_SESSION" // + }; + + private final int function; + + public SessionControlFunction(Expression arg, int function) { + super(arg); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session) { + Value v = arg.getValue(session); + if (v == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + int targetSessionId = v.getInt(); + session.getUser().checkAdmin(); + loop: for (SessionLocal s : session.getDatabase().getSessions(false)) { + if (s.getId() == targetSessionId) { + Command c = s.getCurrentCommand(); + switch (function) { + case ABORT_SESSION: + if (c != null) { + c.cancel(); + } + s.close(); + return ValueBoolean.TRUE; + case CANCEL_SESSION: + if (c != null) { + c.cancel(); + return ValueBoolean.TRUE; + } + break loop; + default: + throw DbException.getInternalError("function=" + function); + } + } + } + return ValueBoolean.FALSE; + } + + @Override + public Expression optimize(SessionLocal session) { + arg = arg.optimize(session); + type = TypeInfo.TYPE_BOOLEAN; + return this; + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + switch (visitor.getType()) { + case ExpressionVisitor.DETERMINISTIC: + case ExpressionVisitor.READONLY: + case ExpressionVisitor.QUERY_COMPARABLE: + return false; + } + return super.isEverything(visitor); + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/SetFunction.java b/h2/src/main/org/h2/expression/function/SetFunction.java new file mode 100644 index 0000000..6b85efc --- /dev/null +++ b/h2/src/main/org/h2/expression/function/SetFunction.java @@ -0,0 +1,64 @@ +/* + * 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.expression.function; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.Variable; +import org.h2.message.DbException; +import org.h2.value.Value; + +/** + * A SET function. + */ +public final class SetFunction extends Function2 { + + public SetFunction(Expression arg1, Expression arg2) { + super(arg1, arg2); + } + + @Override + public Value getValue(SessionLocal session) { + Variable var = (Variable) left; + Value v = right.getValue(session); + session.setVariable(var.getName(), v); + return v; + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + right = right.optimize(session); + type = right.getType(); + if (!(left instanceof Variable)) { + throw DbException.get(ErrorCode.CAN_ONLY_ASSIGN_TO_VARIABLE_1, left.getTraceSQL()); + } + return this; + } + + @Override + public String getName() { + return "SET"; + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + if (!super.isEverything(visitor)) { + return false; + } + switch (visitor.getType()) { + case ExpressionVisitor.DETERMINISTIC: + case ExpressionVisitor.QUERY_COMPARABLE: + case ExpressionVisitor.READONLY: + return false; + default: + return true; + } + } + +} diff --git a/h2/src/main/org/h2/expression/function/SignalFunction.java b/h2/src/main/org/h2/expression/function/SignalFunction.java new file mode 100644 index 0000000..b8f42d2 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/SignalFunction.java @@ -0,0 +1,49 @@ +/* + * 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.expression.function; + +import java.util.regex.Pattern; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.message.DbException; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * A SIGNAL function. + */ +public final class SignalFunction extends Function2 { + + private static final Pattern SIGNAL_PATTERN = Pattern.compile("[0-9A-Z]{5}"); + + public SignalFunction(Expression arg1, Expression arg2) { + super(arg1, arg2); + } + + @Override + public Value getValue(SessionLocal session, Value v1, Value v2) { + String sqlState = v1.getString(); + if (sqlState.startsWith("00") || !SIGNAL_PATTERN.matcher(sqlState).matches()) { + throw DbException.getInvalidValueException("SQLSTATE", sqlState); + } + throw DbException.fromUser(sqlState, v2.getString()); + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + right = right.optimize(session); + type = TypeInfo.TYPE_NULL; + return this; + } + + @Override + public String getName() { + return "SIGNAL"; + } + +} diff --git a/h2/src/main/org/h2/expression/function/SoundexFunction.java b/h2/src/main/org/h2/expression/function/SoundexFunction.java new file mode 100644 index 0000000..b7165c3 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/SoundexFunction.java @@ -0,0 +1,128 @@ +/* + * 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.expression.function; + +import java.nio.charset.StandardCharsets; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.message.DbException; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueInteger; +import org.h2.value.ValueVarchar; + +/** + * A SOUNDEX or DIFFERENCE function. + */ +public final class SoundexFunction extends Function1_2 { + + /** + * SOUNDEX() (non-standard). + */ + public static final int SOUNDEX = 0; + + /** + * DIFFERENCE() (non-standard). + */ + public static final int DIFFERENCE = SOUNDEX + 1; + + private static final String[] NAMES = { // + "SOUNDEX", "DIFFERENCE" // + }; + + private static final byte[] SOUNDEX_INDEX = // + "71237128722455712623718272\000\000\000\000\000\00071237128722455712623718272" + .getBytes(StandardCharsets.ISO_8859_1); + + private final int function; + + public SoundexFunction(Expression arg1, Expression arg2, int function) { + super(arg1, arg2); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session, Value v1, Value v2) { + switch (function) { + case SOUNDEX: + v1 = ValueVarchar.get(new String(getSoundex(v1.getString()), StandardCharsets.ISO_8859_1), session); + break; + case DIFFERENCE: { + v1 = ValueInteger.get(getDifference(v1.getString(), v2.getString())); + break; + } + default: + throw DbException.getInternalError("function=" + function); + } + return v1; + } + + private static int getDifference(String s1, String s2) { + // TODO function difference: compatibility with SQL Server and HSQLDB + byte[] b1 = getSoundex(s1), b2 = getSoundex(s2); + int e = 0; + for (int i = 0; i < 4; i++) { + if (b1[i] == b2[i]) { + e++; + } + } + return e; + } + + private static byte[] getSoundex(String s) { + byte[] chars = { '0', '0', '0', '0' }; + byte lastDigit = '0'; + for (int i = 0, j = 0, l = s.length(); i < l && j < 4; i++) { + char c = s.charAt(i); + if (c >= 'A' && c <= 'z') { + byte newDigit = SOUNDEX_INDEX[c - 'A']; + if (newDigit != 0) { + if (j == 0) { + chars[j++] = (byte) c; + lastDigit = newDigit; + } else if (newDigit <= '6') { + if (newDigit != lastDigit) { + chars[j++] = lastDigit = newDigit; + } + } else if (newDigit == '7') { + lastDigit = newDigit; + } + } + } + } + return chars; + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + if (right != null) { + right = right.optimize(session); + } + switch (function) { + case SOUNDEX: + type = TypeInfo.getTypeInfo(Value.VARCHAR, 4, 0, null); + break; + case DIFFERENCE: + type = TypeInfo.TYPE_INTEGER; + break; + default: + throw DbException.getInternalError("function=" + function); + } + if (left.isConstant() && (right == null || right.isConstant())) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/StringFunction.java b/h2/src/main/org/h2/expression/function/StringFunction.java new file mode 100644 index 0000000..d34cfad --- /dev/null +++ b/h2/src/main/org/h2/expression/function/StringFunction.java @@ -0,0 +1,244 @@ +/* + * 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.expression.function; + +import org.h2.engine.Mode.ModeEnum; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.message.DbException; +import org.h2.util.StringUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueInteger; +import org.h2.value.ValueNull; +import org.h2.value.ValueVarchar; + +/** + * An string function with multiple arguments. + */ +public final class StringFunction extends FunctionN { + + /** + * LOCATE() (non-standard). + */ + public static final int LOCATE = 0; + + /** + * INSERT() (non-standard). + */ + public static final int INSERT = LOCATE + 1; + + /** + * REPLACE() (non-standard). + */ + public static final int REPLACE = INSERT + 1; + + /** + * LPAD() (non-standard). + */ + public static final int LPAD = REPLACE + 1; + + /** + * RPAD() (non-standard). + */ + public static final int RPAD = LPAD + 1; + + /** + * TRANSLATE() (non-standard). + */ + public static final int TRANSLATE = RPAD + 1; + + private static final String[] NAMES = { // + "LOCATE", "INSERT", "REPLACE", "LPAD", "RPAD", "TRANSLATE" // + }; + + private final int function; + + public StringFunction(Expression arg1, Expression arg2, Expression arg3, int function) { + super(arg3 == null ? new Expression[] { arg1, arg2 } : new Expression[] { arg1, arg2, arg3 }); + this.function = function; + } + + public StringFunction(Expression arg1, Expression arg2, Expression arg3, Expression arg4, int function) { + super(new Expression[] { arg1, arg2, arg3, arg4 }); + this.function = function; + } + + public StringFunction(Expression[] args, int function) { + super(args); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session) { + Value v1 = args[0].getValue(session), v2 = args[1].getValue(session); + switch (function) { + case LOCATE: { + if (v1 == ValueNull.INSTANCE || v2 == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + Value v3 = args.length >= 3 ? args[2].getValue(session) : null; + if (v3 == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + v1 = ValueInteger.get(locate(v1.getString(), v2.getString(), v3 == null ? 1 : v3.getInt())); + break; + } + case INSERT: { + Value v3 = args[2].getValue(session), v4 = args[3].getValue(session); + if (v2 != ValueNull.INSTANCE && v3 != ValueNull.INSTANCE) { + String s = insert(v1.getString(), v2.getInt(), v3.getInt(), v4.getString()); + v1 = s != null ? ValueVarchar.get(s, session) : ValueNull.INSTANCE; + } + break; + } + case REPLACE: { + if (v1 == ValueNull.INSTANCE || v2 == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + String after; + if (args.length >= 3) { + Value v3 = args[2].getValue(session); + if (v3 == ValueNull.INSTANCE && session.getMode().getEnum() != ModeEnum.Oracle) { + return ValueNull.INSTANCE; + } + after = v3.getString(); + if (after == null) { + after = ""; + } + } else { + after = ""; + } + v1 = ValueVarchar.get(StringUtils.replaceAll(v1.getString(), v2.getString(), after), session); + break; + } + case LPAD: + case RPAD: + if (v1 == ValueNull.INSTANCE || v2 == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + String padding; + if (args.length >= 3) { + Value v3 = args[2].getValue(session); + if (v3 == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + padding = v3.getString(); + } else { + padding = null; + } + v1 = ValueVarchar.get(StringUtils.pad(v1.getString(), v2.getInt(), padding, function == RPAD), session); + break; + case TRANSLATE: { + if (v1 == ValueNull.INSTANCE || v2 == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + Value v3 = args[2].getValue(session); + if (v3 == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + String matching = v2.getString(); + String replacement = v3.getString(); + if (session.getMode().getEnum() == ModeEnum.DB2) { + String t = matching; + matching = replacement; + replacement = t; + } + v1 = ValueVarchar.get(translate(v1.getString(), matching, replacement), session); + break; + } + default: + throw DbException.getInternalError("function=" + function); + } + return v1; + } + + private static int locate(String search, String s, int start) { + if (start < 0) { + return s.lastIndexOf(search, s.length() + start) + 1; + } + return s.indexOf(search, start == 0 ? 0 : start - 1) + 1; + } + + private static String insert(String s1, int start, int length, String s2) { + if (s1 == null) { + return s2; + } + if (s2 == null) { + return s1; + } + int len1 = s1.length(); + int len2 = s2.length(); + start--; + if (start < 0 || length <= 0 || len2 == 0 || start > len1) { + return s1; + } + if (start + length > len1) { + length = len1 - start; + } + return s1.substring(0, start) + s2 + s1.substring(start + length); + } + + private static String translate(String original, String findChars, String replaceChars) { + if (StringUtils.isNullOrEmpty(original) || StringUtils.isNullOrEmpty(findChars)) { + return original; + } + // if it stays null, then no replacements have been made + StringBuilder builder = null; + // if shorter than findChars, then characters are removed + // (if null, we don't access replaceChars at all) + int replaceSize = replaceChars == null ? 0 : replaceChars.length(); + for (int i = 0, size = original.length(); i < size; i++) { + char ch = original.charAt(i); + int index = findChars.indexOf(ch); + if (index >= 0) { + if (builder == null) { + builder = new StringBuilder(size); + if (i > 0) { + builder.append(original, 0, i); + } + } + if (index < replaceSize) { + ch = replaceChars.charAt(index); + } + } + if (builder != null) { + builder.append(ch); + } + } + return builder == null ? original : builder.toString(); + } + + @Override + public Expression optimize(SessionLocal session) { + boolean allConst = optimizeArguments(session, true); + switch (function) { + case LOCATE: + type = TypeInfo.TYPE_INTEGER; + break; + case INSERT: + case REPLACE: + case LPAD: + case RPAD: + case TRANSLATE: + type = TypeInfo.TYPE_VARCHAR; + break; + default: + throw DbException.getInternalError("function=" + function); + } + if (allConst) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/StringFunction1.java b/h2/src/main/org/h2/expression/function/StringFunction1.java new file mode 100644 index 0000000..9b24996 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/StringFunction1.java @@ -0,0 +1,283 @@ +/* + * 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.expression.function; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import org.h2.api.ErrorCode; +import org.h2.engine.Mode; +import org.h2.engine.Mode.ModeEnum; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.message.DbException; +import org.h2.util.StringUtils; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueInteger; +import org.h2.value.ValueNull; +import org.h2.value.ValueVarbinary; +import org.h2.value.ValueVarchar; + +/** + * A string function with one argument. + */ +public final class StringFunction1 extends Function1 { + + // Fold functions + + /** + * UPPER(). + */ + public static final int UPPER = 0; + + /** + * LOWER(). + */ + public static final int LOWER = UPPER + 1; + + // Various non-standard functions + + /** + * ASCII() (non-standard). + */ + public static final int ASCII = LOWER + 1; + + /** + * CHAR() (non-standard). + */ + public static final int CHAR = ASCII + 1; + + /** + * STRINGENCODE() (non-standard). + */ + public static final int STRINGENCODE = CHAR + 1; + + /** + * STRINGDECODE() (non-standard). + */ + public static final int STRINGDECODE = STRINGENCODE + 1; + + /** + * STRINGTOUTF8() (non-standard). + */ + public static final int STRINGTOUTF8 = STRINGDECODE + 1; + + /** + * UTF8TOSTRING() (non-standard). + */ + public static final int UTF8TOSTRING = STRINGTOUTF8 + 1; + + /** + * HEXTORAW() (non-standard). + */ + public static final int HEXTORAW = UTF8TOSTRING + 1; + + /** + * RAWTOHEX() (non-standard). + */ + public static final int RAWTOHEX = HEXTORAW + 1; + + /** + * SPACE() (non-standard). + */ + public static final int SPACE = RAWTOHEX + 1; + + /** + * QUOTE_IDENT() (non-standard). + */ + public static final int QUOTE_IDENT = SPACE + 1; + + private static final String[] NAMES = { // + "UPPER", "LOWER", "ASCII", "CHAR", "STRINGENCODE", "STRINGDECODE", "STRINGTOUTF8", "UTF8TOSTRING", + "HEXTORAW", "RAWTOHEX", "SPACE", "QUOTE_IDENT" // + }; + + private final int function; + + public StringFunction1(Expression arg, int function) { + super(arg); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session) { + Value v = arg.getValue(session); + if (v == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + switch (function) { + case UPPER: + // TODO this is locale specific, need to document or provide a way + // to set the locale + v = ValueVarchar.get(v.getString().toUpperCase(), session); + break; + case LOWER: + // TODO this is locale specific, need to document or provide a way + // to set the locale + v = ValueVarchar.get(v.getString().toLowerCase(), session); + break; + case ASCII: { + String s = v.getString(); + v = s.isEmpty() ? ValueNull.INSTANCE : ValueInteger.get(s.charAt(0)); + break; + } + case CHAR: + v = ValueVarchar.get(String.valueOf((char) v.getInt()), session); + break; + case STRINGENCODE: + v = ValueVarchar.get(StringUtils.javaEncode(v.getString()), session); + break; + case STRINGDECODE: + v = ValueVarchar.get(StringUtils.javaDecode(v.getString()), session); + break; + case STRINGTOUTF8: + v = ValueVarbinary.getNoCopy(v.getString().getBytes(StandardCharsets.UTF_8)); + break; + case UTF8TOSTRING: + v = ValueVarchar.get(new String(v.getBytesNoCopy(), StandardCharsets.UTF_8), session); + break; + case HEXTORAW: + v = hexToRaw(v.getString(), session); + break; + case RAWTOHEX: + v = ValueVarchar.get(rawToHex(v, session.getMode()), session); + break; + case SPACE: { + byte[] chars = new byte[Math.max(0, v.getInt())]; + Arrays.fill(chars, (byte) ' '); + v = ValueVarchar.get(new String(chars, StandardCharsets.ISO_8859_1), session); + break; + } + case QUOTE_IDENT: + v = ValueVarchar.get(StringUtils.quoteIdentifier(v.getString()), session); + break; + default: + throw DbException.getInternalError("function=" + function); + } + return v; + } + + private static Value hexToRaw(String s, SessionLocal session) { + if (session.getMode().getEnum() == ModeEnum.Oracle) { + return ValueVarbinary.get(StringUtils.convertHexToBytes(s)); + } + int len = s.length(); + if (len % 4 != 0) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, s); + } + StringBuilder builder = new StringBuilder(len / 4); + for (int i = 0; i < len; i += 4) { + try { + builder.append((char) Integer.parseInt(s.substring(i, i + 4), 16)); + } catch (NumberFormatException e) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, s); + } + } + return ValueVarchar.get(builder.toString(), session); + } + + private static String rawToHex(Value v, Mode mode) { + if (DataType.isBinaryStringOrSpecialBinaryType(v.getValueType())) { + return StringUtils.convertBytesToHex(v.getBytesNoCopy()); + } + String s = v.getString(); + if (mode.getEnum() == ModeEnum.Oracle) { + return StringUtils.convertBytesToHex(s.getBytes(StandardCharsets.UTF_8)); + } + int length = s.length(); + StringBuilder buff = new StringBuilder(4 * length); + for (int i = 0; i < length; i++) { + String hex = Integer.toHexString(s.charAt(i) & 0xffff); + for (int j = hex.length(); j < 4; j++) { + buff.append('0'); + } + buff.append(hex); + } + return buff.toString(); + } + + @Override + public Expression optimize(SessionLocal session) { + arg = arg.optimize(session); + switch (function) { + /* + * UPPER and LOWER may return string of different length for some + * characters. + */ + case UPPER: + case LOWER: + case STRINGENCODE: + case SPACE: + case QUOTE_IDENT: + type = TypeInfo.TYPE_VARCHAR; + break; + case ASCII: + type = TypeInfo.TYPE_INTEGER; + break; + case CHAR: + type = TypeInfo.getTypeInfo(Value.VARCHAR, 1L, 0, null); + break; + case STRINGDECODE: { + TypeInfo t = arg.getType(); + type = DataType.isCharacterStringType(t.getValueType()) + ? TypeInfo.getTypeInfo(Value.VARCHAR, t.getPrecision(), 0, null) + : TypeInfo.TYPE_VARCHAR; + break; + } + case STRINGTOUTF8: + type = TypeInfo.TYPE_VARBINARY; + break; + case UTF8TOSTRING: { + TypeInfo t = arg.getType(); + type = DataType.isBinaryStringType(t.getValueType()) + ? TypeInfo.getTypeInfo(Value.VARCHAR, t.getPrecision(), 0, null) + : TypeInfo.TYPE_VARCHAR; + break; + } + case HEXTORAW: { + TypeInfo t = arg.getType(); + if (session.getMode().getEnum() == ModeEnum.Oracle) { + if (DataType.isCharacterStringType(t.getValueType())) { + type = TypeInfo.getTypeInfo(Value.VARBINARY, t.getPrecision() / 2, 0, null); + } else { + type = TypeInfo.TYPE_VARBINARY; + } + } else { + if (DataType.isCharacterStringType(t.getValueType())) { + type = TypeInfo.getTypeInfo(Value.VARCHAR, t.getPrecision() / 4, 0, null); + } else { + type = TypeInfo.TYPE_VARCHAR; + } + } + break; + } + case RAWTOHEX: { + TypeInfo t = arg.getType(); + long precision = t.getPrecision(); + int mul = DataType.isBinaryStringOrSpecialBinaryType(t.getValueType()) ? 2 + : session.getMode().getEnum() == ModeEnum.Oracle ? 6 : 4; + type = TypeInfo.getTypeInfo(Value.VARCHAR, + precision <= Long.MAX_VALUE / mul ? precision * mul : Long.MAX_VALUE, 0, null); + break; + } + default: + throw DbException.getInternalError("function=" + function); + } + if (arg.isConstant()) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/StringFunction2.java b/h2/src/main/org/h2/expression/function/StringFunction2.java new file mode 100644 index 0000000..6b7395c --- /dev/null +++ b/h2/src/main/org/h2/expression/function/StringFunction2.java @@ -0,0 +1,108 @@ +/* + * 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.expression.function; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.message.DbException; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueVarchar; + +/** + * A string function with two arguments. + */ +public final class StringFunction2 extends Function2 { + + /** + * LEFT() (non-standard). + */ + public static final int LEFT = 0; + + /** + * RIGHT() (non-standard). + */ + public static final int RIGHT = LEFT + 1; + + /** + * REPEAT() (non-standard). + */ + public static final int REPEAT = RIGHT + 1; + + private static final String[] NAMES = { // + "LEFT", "RIGHT", "REPEAT" // + }; + + private final int function; + + public StringFunction2(Expression arg1, Expression arg2, int function) { + super(arg1, arg2); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session, Value v1, Value v2) { + String s = v1.getString(); + int count = v2.getInt(); + if (count <= 0) { + return ValueVarchar.get("", session); + } + int length = s.length(); + switch (function) { + case LEFT: + if (count > length) { + count = length; + } + s = s.substring(0, count); + break; + case RIGHT: + if (count > length) { + count = length; + } + s = s.substring(length - count); + break; + case REPEAT: { + StringBuilder builder = new StringBuilder(length * count); + while (count-- > 0) { + builder.append(s); + } + s = builder.toString(); + break; + } + default: + throw DbException.getInternalError("function=" + function); + } + return ValueVarchar.get(s, session); + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + right = right.optimize(session); + switch (function) { + case LEFT: + case RIGHT: + type = TypeInfo.getTypeInfo(Value.VARCHAR, left.getType().getPrecision(), 0, null); + break; + case REPEAT: + type = TypeInfo.TYPE_VARCHAR; + break; + default: + throw DbException.getInternalError("function=" + function); + } + if (left.isConstant() && right.isConstant()) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/SubstringFunction.java b/h2/src/main/org/h2/expression/function/SubstringFunction.java new file mode 100644 index 0000000..b93e464 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/SubstringFunction.java @@ -0,0 +1,126 @@ +/* + * 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.expression.function; + +import java.util.Arrays; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.message.DbException; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueNull; +import org.h2.value.ValueVarbinary; +import org.h2.value.ValueVarchar; + +/** + * A SUBSTRING function. + */ +public final class SubstringFunction extends FunctionN { + + public SubstringFunction() { + super(new Expression[3]); + } + + @Override + public Value getValue(SessionLocal session, Value v1, Value v2, Value v3) { + if (type.getValueType() == Value.VARBINARY) { + byte[] s = v1.getBytesNoCopy(); + int sl = s.length; + int start = v2.getInt(); + // These compatibility conditions violate the Standard + if (start == 0) { + start = 1; + } else if (start < 0) { + start = sl + start + 1; + } + int end = v3 == null ? Math.max(sl + 1, start) : start + v3.getInt(); + // SQL Standard requires "data exception - substring error" when + // end < start but H2 does not throw it for compatibility + start = Math.max(start, 1); + end = Math.min(end, sl + 1); + if (start > sl || end <= start) { + return ValueVarbinary.EMPTY; + } + start--; + end--; + if (start == 0 && end == s.length) { + return v1.convertTo(TypeInfo.TYPE_VARBINARY); + } + return ValueVarbinary.getNoCopy(Arrays.copyOfRange(s, start, end)); + } else { + String s = v1.getString(); + int sl = s.length(); + int start = v2.getInt(); + // These compatibility conditions violate the Standard + if (start == 0) { + start = 1; + } else if (start < 0) { + start = sl + start + 1; + } + int end = v3 == null ? Math.max(sl + 1, start) : start + v3.getInt(); + // SQL Standard requires "data exception - substring error" when + // end < start but H2 does not throw it for compatibility + start = Math.max(start, 1); + end = Math.min(end, sl + 1); + if (start > sl || end <= start) { + return session.getMode().treatEmptyStringsAsNull ? ValueNull.INSTANCE : ValueVarchar.EMPTY; + } + return ValueVarchar.get(s.substring(start - 1, end - 1), null); + } + } + + @Override + public Expression optimize(SessionLocal session) { + boolean allConst = optimizeArguments(session, true); + int len = args.length; + if (len < 2 || len > 3) { + throw DbException.get(ErrorCode.INVALID_PARAMETER_COUNT_2, getName(), "2..3"); + } + TypeInfo argType = args[0].getType(); + long p = argType.getPrecision(); + Expression arg = args[1]; + Value v; + if (arg.isConstant() && (v = arg.getValue(session)) != ValueNull.INSTANCE) { + // if only two arguments are used, + // subtract offset from first argument length + p -= v.getLong() - 1; + } + if (args.length == 3) { + arg = args[2]; + if (arg.isConstant() && (v = arg.getValue(session)) != ValueNull.INSTANCE) { + // if the third argument is constant it is at most this value + p = Math.min(p, v.getLong()); + } + } + p = Math.max(0, p); + type = TypeInfo.getTypeInfo( + DataType.isBinaryStringType(argType.getValueType()) ? Value.VARBINARY : Value.VARCHAR, p, 0, null); + if (allConst) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + args[0].getUnenclosedSQL(builder.append(getName()).append('('), sqlFlags); + args[1].getUnenclosedSQL(builder.append(" FROM "), sqlFlags); + if (args.length > 2) { + args[2].getUnenclosedSQL(builder.append(" FOR "), sqlFlags); + } + return builder.append(')'); + } + + @Override + public String getName() { + return "SUBSTRING"; + } + +} diff --git a/h2/src/main/org/h2/expression/function/SysInfoFunction.java b/h2/src/main/org/h2/expression/function/SysInfoFunction.java new file mode 100644 index 0000000..dd02010 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/SysInfoFunction.java @@ -0,0 +1,176 @@ +/* + * 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.expression.function; + +import org.h2.engine.Constants; +import org.h2.engine.SessionLocal; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.Operation0; +import org.h2.message.DbException; +import org.h2.util.Utils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBigint; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueInteger; +import org.h2.value.ValueNull; +import org.h2.value.ValueVarchar; + +/** + * Database or session information function. + */ +public final class SysInfoFunction extends Operation0 implements NamedExpression { + + /** + * AUTOCOMMIT(). + */ + public static final int AUTOCOMMIT = 0; + + /** + * DATABASE_PATH(). + */ + public static final int DATABASE_PATH = AUTOCOMMIT + 1; + + /** + * H2VERSION(). + */ + public static final int H2VERSION = DATABASE_PATH + 1; + + /** + * LOCK_MODE(). + */ + public static final int LOCK_MODE = H2VERSION + 1; + + /** + * LOCK_TIMEOUT(). + */ + public static final int LOCK_TIMEOUT = LOCK_MODE + 1; + + /** + * MEMORY_FREE(). + */ + public static final int MEMORY_FREE = LOCK_TIMEOUT + 1; + + /** + * MEMORY_USED(). + */ + public static final int MEMORY_USED = MEMORY_FREE + 1; + + /** + * READONLY(). + */ + public static final int READONLY = MEMORY_USED + 1; + + /** + * SESSION_ID(). + */ + public static final int SESSION_ID = READONLY + 1; + + /** + * TRANSACTION_ID(). + */ + public static final int TRANSACTION_ID = SESSION_ID + 1; + + private static final int[] TYPES = { Value.BOOLEAN, Value.VARCHAR, Value.VARCHAR, Value.INTEGER, Value.INTEGER, + Value.BIGINT, Value.BIGINT, Value.BOOLEAN, Value.INTEGER, Value.VARCHAR }; + + private static final String[] NAMES = { "AUTOCOMMIT", "DATABASE_PATH", "H2VERSION", "LOCK_MODE", "LOCK_TIMEOUT", + "MEMORY_FREE", "MEMORY_USED", "READONLY", "SESSION_ID", "TRANSACTION_ID" }; + + /** + * Get the name for this function id. + * + * @param function + * the function id + * @return the name + */ + public static String getName(int function) { + return NAMES[function]; + } + + private final int function; + + private final TypeInfo type; + + public SysInfoFunction(int function) { + this.function = function; + type = TypeInfo.getTypeInfo(TYPES[function]); + } + + @Override + public Value getValue(SessionLocal session) { + Value result; + switch (function) { + case AUTOCOMMIT: + result = ValueBoolean.get(session.getAutoCommit()); + break; + case DATABASE_PATH: { + String path = session.getDatabase().getDatabasePath(); + result = path != null ? ValueVarchar.get(path, session) : ValueNull.INSTANCE; + break; + } + case H2VERSION: + result = ValueVarchar.get(Constants.VERSION, session); + break; + case LOCK_MODE: + result = ValueInteger.get(session.getDatabase().getLockMode()); + break; + case LOCK_TIMEOUT: + result = ValueInteger.get(session.getLockTimeout()); + break; + case MEMORY_FREE: + session.getUser().checkAdmin(); + result = ValueBigint.get(Utils.getMemoryFree()); + break; + case MEMORY_USED: + session.getUser().checkAdmin(); + result = ValueBigint.get(Utils.getMemoryUsed()); + break; + case READONLY: + result = ValueBoolean.get(session.getDatabase().isReadOnly()); + break; + case SESSION_ID: + result = ValueInteger.get(session.getId()); + break; + case TRANSACTION_ID: + result = session.getTransactionId(); + break; + default: + throw DbException.getInternalError("function=" + function); + } + return result; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return builder.append(getName()).append("()"); + } + + @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/expression/function/TableInfoFunction.java b/h2/src/main/org/h2/expression/function/TableInfoFunction.java new file mode 100644 index 0000000..c447033 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/TableInfoFunction.java @@ -0,0 +1,111 @@ +/* + * 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.expression.function; + +import java.util.ArrayList; + +import org.h2.command.Parser; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.index.Index; +import org.h2.message.DbException; +import org.h2.mvstore.db.MVSpatialIndex; +import org.h2.table.Column; +import org.h2.table.Table; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBigint; +import org.h2.value.ValueNull; + +/** + * A table information function. + */ +public final class TableInfoFunction extends Function1_2 { + + /** + * DISK_SPACE_USED() (non-standard). + */ + public static final int DISK_SPACE_USED = 0; + + /** + * ESTIMATED_ENVELOPE(). + */ + public static final int ESTIMATED_ENVELOPE = DISK_SPACE_USED + 1; + + private static final String[] NAMES = { // + "DISK_SPACE_USED", "ESTIMATED_ENVELOPE" // + }; + + private final int function; + + public TableInfoFunction(Expression arg1, Expression arg2, int function) { + super(arg1, arg2); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session, Value v1, Value v2) { + Table table = new Parser(session).parseTableName(v1.getString()); + l: switch (function) { + case DISK_SPACE_USED: + v1 = ValueBigint.get(table.getDiskSpaceUsed()); + break; + case ESTIMATED_ENVELOPE: { + Column column = table.getColumn(v2.getString()); + ArrayList indexes = table.getIndexes(); + if (indexes != null) { + for (int i = 1, size = indexes.size(); i < size; i++) { + Index index = indexes.get(i); + if (index instanceof MVSpatialIndex && index.isFirstColumn(column)) { + v1 = ((MVSpatialIndex) index).getEstimatedBounds(session); + break l; + } + } + } + v1 = ValueNull.INSTANCE; + break; + } + default: + throw DbException.getInternalError("function=" + function); + } + return v1; + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + if (right != null) { + right = right.optimize(session); + } + switch (function) { + case DISK_SPACE_USED: + type = TypeInfo.TYPE_BIGINT; + break; + case ESTIMATED_ENVELOPE: + type = TypeInfo.TYPE_GEOMETRY; + break; + default: + throw DbException.getInternalError("function=" + function); + } + return this; + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + switch (visitor.getType()) { + case ExpressionVisitor.DETERMINISTIC: + return false; + } + return super.isEverything(visitor); + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/ToCharFunction.java b/h2/src/main/org/h2/expression/function/ToCharFunction.java new file mode 100644 index 0000000..9eb1780 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/ToCharFunction.java @@ -0,0 +1,1127 @@ +/* + * 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: Daniel Gredler + */ +package org.h2.expression.function; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.DateFormatSymbols; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Currency; +import java.util.Locale; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.message.DbException; +import org.h2.util.DateTimeUtils; +import org.h2.util.StringUtils; +import org.h2.util.TimeZoneProvider; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueTimeTimeZone; +import org.h2.value.ValueTimestamp; +import org.h2.value.ValueTimestampTimeZone; +import org.h2.value.ValueVarchar; + +/** + * Emulates Oracle's TO_CHAR function. + */ +public final class ToCharFunction extends FunctionN { + + /** + * The beginning of the Julian calendar. + */ + public static final int JULIAN_EPOCH = -2_440_588; + + private static final int[] ROMAN_VALUES = { 1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, + 5, 4, 1 }; + + private static final String[] ROMAN_NUMERALS = { "M", "CM", "D", "CD", "C", "XC", + "L", "XL", "X", "IX", "V", "IV", "I" }; + + /** + * The month field. + */ + public static final int MONTHS = 0; + + /** + * The month field (short form). + */ + public static final int SHORT_MONTHS = 1; + + /** + * The weekday field. + */ + public static final int WEEKDAYS = 2; + + /** + * The weekday field (short form). + */ + public static final int SHORT_WEEKDAYS = 3; + + /** + * The AM / PM field. + */ + static final int AM_PM = 4; + + private static volatile String[][] NAMES; + + /** + * Emulates Oracle's TO_CHAR(number) function. + * + *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
TO_CHAR(number) function
InputOutputClosest {@link DecimalFormat} Equivalent
,Grouping separator.,
.Decimal separator..
$Leading dollar sign.$
0Leading or trailing zeroes.0
9Digit.#
BBlanks integer part of a fixed point number less than 1.#
CISO currency symbol.\u00A4
DLocal decimal separator..
EEEEReturns a value in scientific notation.E
FMReturns values with no leading or trailing spaces.None.
GLocal grouping separator.,
LLocal currency symbol.\u00A4
MINegative values get trailing minus sign, + * positive get trailing space.-
PRNegative values get enclosing angle brackets, + * positive get spaces.None.
RNReturns values in Roman numerals.None.
SReturns values with leading/trailing +/- signs.None.
TMReturns smallest number of characters possible.None.
UReturns the dual currency symbol.None.
VReturns a value multiplied by 10^n.None.
XHex value.None.
+ * See also TO_CHAR(number) and number format models + * in the Oracle documentation. + * + * @param number the number to format + * @param format the format pattern to use (if any) + * @param nlsParam the NLS parameter (if any) + * @return the formatted number + */ + public static String toChar(BigDecimal number, String format, + @SuppressWarnings("unused") String nlsParam) { + + // short-circuit logic for formats that don't follow common logic below + String formatUp = format != null ? StringUtils.toUpperEnglish(format) : null; + if (formatUp == null || formatUp.equals("TM") || formatUp.equals("TM9")) { + String s = number.toPlainString(); + return s.startsWith("0.") ? s.substring(1) : s; + } else if (formatUp.equals("TME")) { + int pow = number.precision() - number.scale() - 1; + number = number.movePointLeft(pow); + return number.toPlainString() + "E" + + (pow < 0 ? '-' : '+') + (Math.abs(pow) < 10 ? "0" : "") + Math.abs(pow); + } else if (formatUp.equals("RN")) { + boolean lowercase = format.startsWith("r"); + String rn = StringUtils.pad(toRomanNumeral(number.intValue()), 15, " ", false); + return lowercase ? rn.toLowerCase() : rn; + } else if (formatUp.equals("FMRN")) { + boolean lowercase = format.charAt(2) == 'r'; + String rn = toRomanNumeral(number.intValue()); + return lowercase ? rn.toLowerCase() : rn; + } else if (formatUp.endsWith("X")) { + return toHex(number, format); + } + + String originalFormat = format; + DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(); + char localGrouping = symbols.getGroupingSeparator(); + char localDecimal = symbols.getDecimalSeparator(); + + boolean leadingSign = formatUp.startsWith("S"); + if (leadingSign) { + format = format.substring(1); + } + + boolean trailingSign = formatUp.endsWith("S"); + if (trailingSign) { + format = format.substring(0, format.length() - 1); + } + + boolean trailingMinus = formatUp.endsWith("MI"); + if (trailingMinus) { + format = format.substring(0, format.length() - 2); + } + + boolean angleBrackets = formatUp.endsWith("PR"); + if (angleBrackets) { + format = format.substring(0, format.length() - 2); + } + + int v = formatUp.indexOf('V'); + if (v >= 0) { + int digits = 0; + for (int i = v + 1; i < format.length(); i++) { + char c = format.charAt(i); + if (c == '0' || c == '9') { + digits++; + } + } + number = number.movePointRight(digits); + format = format.substring(0, v) + format.substring(v + 1); + } + + Integer power; + if (format.endsWith("EEEE")) { + power = number.precision() - number.scale() - 1; + number = number.movePointLeft(power); + format = format.substring(0, format.length() - 4); + } else { + power = null; + } + + int maxLength = 1; + boolean fillMode = !formatUp.startsWith("FM"); + if (!fillMode) { + format = format.substring(2); + } + + // blanks flag doesn't seem to actually do anything + format = format.replaceAll("[Bb]", ""); + + // if we need to round the number to fit into the format specified, + // go ahead and do that first + int separator = findDecimalSeparator(format); + int formatScale = calculateScale(format, separator); + int numberScale = number.scale(); + if (formatScale < numberScale) { + number = number.setScale(formatScale, RoundingMode.HALF_UP); + } else if (numberScale < 0) { + number = number.setScale(0); + } + + // any 9s to the left of the decimal separator but to the right of a + // 0 behave the same as a 0, e.g. "09999.99" -> "00000.99" + for (int i = format.indexOf('0'); i >= 0 && i < separator; i++) { + if (format.charAt(i) == '9') { + format = format.substring(0, i) + "0" + format.substring(i + 1); + } + } + + StringBuilder output = new StringBuilder(); + String unscaled = (number.abs().compareTo(BigDecimal.ONE) < 0 ? + zeroesAfterDecimalSeparator(number) : "") + + number.unscaledValue().abs().toString(); + + // start at the decimal point and fill in the numbers to the left, + // working our way from right to left + int i = separator - 1; + int j = unscaled.length() - number.scale() - 1; + for (; i >= 0; i--) { + char c = format.charAt(i); + maxLength++; + if (c == '9' || c == '0') { + if (j >= 0) { + char digit = unscaled.charAt(j); + output.insert(0, digit); + j--; + } else if (c == '0' && power == null) { + output.insert(0, '0'); + } + } else if (c == ',') { + // only add the grouping separator if we have more numbers + if (j >= 0 || (i > 0 && format.charAt(i - 1) == '0')) { + output.insert(0, c); + } + } else if (c == 'G' || c == 'g') { + // only add the grouping separator if we have more numbers + if (j >= 0 || (i > 0 && format.charAt(i - 1) == '0')) { + output.insert(0, localGrouping); + } + } else if (c == 'C' || c == 'c') { + Currency currency = getCurrency(); + output.insert(0, currency.getCurrencyCode()); + maxLength += 6; + } else if (c == 'L' || c == 'l' || c == 'U' || c == 'u') { + Currency currency = getCurrency(); + output.insert(0, currency.getSymbol()); + maxLength += 9; + } else if (c == '$') { + Currency currency = getCurrency(); + String cs = currency.getSymbol(); + output.insert(0, cs); + } else { + throw DbException.get( + ErrorCode.INVALID_TO_CHAR_FORMAT, originalFormat); + } + } + + // if the format (to the left of the decimal point) was too small + // to hold the number, return a big "######" string + if (j >= 0) { + return StringUtils.pad("", format.length() + 1, "#", true); + } + + if (separator < format.length()) { + + // add the decimal point + maxLength++; + char pt = format.charAt(separator); + if (pt == 'd' || pt == 'D') { + output.append(localDecimal); + } else { + output.append(pt); + } + + // start at the decimal point and fill in the numbers to the right, + // working our way from left to right + i = separator + 1; + j = unscaled.length() - number.scale(); + for (; i < format.length(); i++) { + char c = format.charAt(i); + maxLength++; + if (c == '9' || c == '0') { + if (j < unscaled.length()) { + char digit = unscaled.charAt(j); + output.append(digit); + j++; + } else { + if (c == '0' || fillMode) { + output.append('0'); + } + } + } else { + throw DbException.get( + ErrorCode.INVALID_TO_CHAR_FORMAT, originalFormat); + } + } + } + + addSign(output, number.signum(), leadingSign, trailingSign, + trailingMinus, angleBrackets, fillMode); + + if (power != null) { + output.append('E'); + output.append(power < 0 ? '-' : '+'); + output.append(Math.abs(power) < 10 ? "0" : ""); + output.append(Math.abs(power)); + } + + if (fillMode) { + if (power != null) { + output.insert(0, ' '); + } else { + while (output.length() < maxLength) { + output.insert(0, ' '); + } + } + } + + return output.toString(); + } + + private static Currency getCurrency() { + Locale locale = Locale.getDefault(); + return Currency.getInstance(locale.getCountry().length() == 2 ? locale : Locale.US); + } + + private static String zeroesAfterDecimalSeparator(BigDecimal number) { + final String numberStr = number.toPlainString(); + final int idx = numberStr.indexOf('.'); + if (idx < 0) { + return ""; + } + int i = idx + 1; + boolean allZeroes = true; + int length = numberStr.length(); + for (; i < length; i++) { + if (numberStr.charAt(i) != '0') { + allZeroes = false; + break; + } + } + final char[] zeroes = new char[allZeroes ? length - idx - 1: i - 1 - idx]; + Arrays.fill(zeroes, '0'); + return String.valueOf(zeroes); + } + + private static void addSign(StringBuilder output, int signum, + boolean leadingSign, boolean trailingSign, boolean trailingMinus, + boolean angleBrackets, boolean fillMode) { + if (angleBrackets) { + if (signum < 0) { + output.insert(0, '<'); + output.append('>'); + } else if (fillMode) { + output.insert(0, ' '); + output.append(' '); + } + } else { + String sign; + if (signum == 0) { + sign = ""; + } else if (signum < 0) { + sign = "-"; + } else { + if (leadingSign || trailingSign) { + sign = "+"; + } else if (fillMode) { + sign = " "; + } else { + sign = ""; + } + } + if (trailingMinus || trailingSign) { + output.append(sign); + } else { + output.insert(0, sign); + } + } + } + + private static int findDecimalSeparator(String format) { + int index = format.indexOf('.'); + if (index == -1) { + index = format.indexOf('D'); + if (index == -1) { + index = format.indexOf('d'); + if (index == -1) { + index = format.length(); + } + } + } + return index; + } + + private static int calculateScale(String format, int separator) { + int scale = 0; + for (int i = separator; i < format.length(); i++) { + char c = format.charAt(i); + if (c == '0' || c == '9') { + scale++; + } + } + return scale; + } + + private static String toRomanNumeral(int number) { + StringBuilder result = new StringBuilder(); + for (int i = 0; i < ROMAN_VALUES.length; i++) { + int value = ROMAN_VALUES[i]; + String numeral = ROMAN_NUMERALS[i]; + while (number >= value) { + result.append(numeral); + number -= value; + } + } + return result.toString(); + } + + private static String toHex(BigDecimal number, String format) { + + boolean fillMode = !StringUtils.toUpperEnglish(format).startsWith("FM"); + boolean uppercase = !format.contains("x"); + boolean zeroPadded = format.startsWith("0"); + int digits = 0; + for (int i = 0; i < format.length(); i++) { + char c = format.charAt(i); + if (c == '0' || c == 'X' || c == 'x') { + digits++; + } + } + + int i = number.setScale(0, RoundingMode.HALF_UP).intValue(); + String hex = Integer.toHexString(i); + if (digits < hex.length()) { + hex = StringUtils.pad("", digits + 1, "#", true); + } else { + if (uppercase) { + hex = StringUtils.toUpperEnglish(hex); + } + if (zeroPadded) { + hex = StringUtils.pad(hex, digits, "0", false); + } + if (fillMode) { + hex = StringUtils.pad(hex, format.length() + 1, " ", false); + } + } + + return hex; + } + + /** + * Get the date (month / weekday / ...) names. + * + * @param names the field + * @return the names + */ + public static String[] getDateNames(int names) { + String[][] result = NAMES; + if (result == null) { + result = new String[5][]; + DateFormatSymbols dfs = DateFormatSymbols.getInstance(); + result[MONTHS] = dfs.getMonths(); + String[] months = dfs.getShortMonths(); + for (int i = 0; i < 12; i++) { + String month = months[i]; + if (month.endsWith(".")) { + months[i] = month.substring(0, month.length() - 1); + } + } + result[SHORT_MONTHS] = months; + result[WEEKDAYS] = dfs.getWeekdays(); + result[SHORT_WEEKDAYS] = dfs.getShortWeekdays(); + result[AM_PM] = dfs.getAmPmStrings(); + NAMES = result; + } + return result[names]; + } + + /** + * Used for testing. + */ + public static void clearNames() { + NAMES = null; + } + + /** + * Returns time zone display name or ID for the specified date-time value. + * + * @param session + * the session + * @param value + * value + * @param tzd + * if {@code true} return TZD (time zone region with Daylight Saving + * Time information included), if {@code false} return TZR (time zone + * region) + * @return time zone display name or ID + */ + private static String getTimeZone(SessionLocal session, Value value, boolean tzd) { + if (value instanceof ValueTimestampTimeZone) { + return DateTimeUtils.timeZoneNameFromOffsetSeconds(((ValueTimestampTimeZone) value) + .getTimeZoneOffsetSeconds()); + } else if (value instanceof ValueTimeTimeZone) { + return DateTimeUtils.timeZoneNameFromOffsetSeconds(((ValueTimeTimeZone) value) + .getTimeZoneOffsetSeconds()); + } else { + TimeZoneProvider tz = session.currentTimeZone(); + if (tzd) { + ValueTimestamp v = (ValueTimestamp) value.convertTo(TypeInfo.TYPE_TIMESTAMP, session); + return tz.getShortId(tz.getEpochSecondsFromLocal(v.getDateValue(), v.getTimeNanos())); + } + return tz.getId(); + } + } + + /** + * Emulates Oracle's TO_CHAR(datetime) function. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
TO_CHAR(datetime) function
InputOutputClosest {@link SimpleDateFormat} Equivalent
- / , . ; : "text"Reproduced verbatim.'text'
A.D. AD B.C. BCEra designator, with or without periods.G
A.M. AM P.M. PMAM/PM marker.a
CC SCCCentury.None.
DDay of week.u
DAYName of day.EEEE
DYAbbreviated day name.EEE
DDDay of month.d
DDDDay of year.D
DLLong date format.EEEE, MMMM d, yyyy
DSShort date format.MM/dd/yyyy
EAbbreviated era name (Japanese, Chinese, Thai)None.
EEFull era name (Japanese, Chinese, Thai)None.
FF[1-9]Fractional seconds.S
FMReturns values with no leading or trailing spaces.None.
FXRequires exact matches between character data and format model.None.
HH HH12Hour in AM/PM (1-12).hh
HH24Hour in day (0-23).HH
IWWeek in year.w
WWWeek in year.w
WWeek in month.W
IYYY IYY IY ILast 4/3/2/1 digit(s) of ISO year.yyyy yyy yy y
RRRR RRLast 4/2 digits of year.yyyy yy
Y,YYYYear with comma.None.
YEAR SYEARYear spelled out (S prefixes BC years with minus sign).None.
YYYY SYYYY4-digit year (S prefixes BC years with minus sign).yyyy
YYY YY YLast 3/2/1 digit(s) of year.yyy yy y
JJulian day (number of days since January 1, 4712 BC).None.
MIMinute in hour.mm
MMMonth in year.MM
MONAbbreviated name of month.MMM
MONTHName of month, padded with spaces.MMMM
RMRoman numeral month.None.
QQuarter of year.None.
SSSeconds in minute.ss
SSSSSSeconds in day.None.
TSShort time format.h:mm:ss aa
TZDDaylight savings time zone abbreviation.z
TZRTime zone region information.zzzz
XLocal radix character.None.
+ *

+ * See also TO_CHAR(datetime) and datetime format models + * in the Oracle documentation. + * + * @param session the session + * @param value the date-time value to format + * @param format the format pattern to use (if any) + * @param nlsParam the NLS parameter (if any) + * + * @return the formatted timestamp + */ + public static String toCharDateTime(SessionLocal session, Value value, String format, + @SuppressWarnings("unused") String nlsParam) { + long[] a = DateTimeUtils.dateAndTimeFromValue(value, session); + long dateValue = a[0]; + long timeNanos = a[1]; + int year = DateTimeUtils.yearFromDateValue(dateValue); + int monthOfYear = DateTimeUtils.monthFromDateValue(dateValue); + int dayOfMonth = DateTimeUtils.dayFromDateValue(dateValue); + int posYear = Math.abs(year); + int second = (int) (timeNanos / 1_000_000_000); + int nanos = (int) (timeNanos - second * 1_000_000_000); + int minute = second / 60; + second -= minute * 60; + int hour = minute / 60; + minute -= hour * 60; + int h12 = (hour + 11) % 12 + 1; + boolean isAM = hour < 12; + if (format == null) { + format = "DD-MON-YY HH.MI.SS.FF PM"; + } + + StringBuilder output = new StringBuilder(); + boolean fillMode = true; + + for (int i = 0, length = format.length(); i < length;) { + + Capitalization cap; + + // AD / BC + + if ((cap = containsAt(format, i, "A.D.", "B.C.")) != null) { + String era = year > 0 ? "A.D." : "B.C."; + output.append(cap.apply(era)); + i += 4; + } else if ((cap = containsAt(format, i, "AD", "BC")) != null) { + String era = year > 0 ? "AD" : "BC"; + output.append(cap.apply(era)); + i += 2; + + // AM / PM + + } else if ((cap = containsAt(format, i, "A.M.", "P.M.")) != null) { + String am = isAM ? "A.M." : "P.M."; + output.append(cap.apply(am)); + i += 4; + } else if ((cap = containsAt(format, i, "AM", "PM")) != null) { + String am = isAM ? "AM" : "PM"; + output.append(cap.apply(am)); + i += 2; + + // Long/short date/time format + + } else if (containsAt(format, i, "DL") != null) { + String day = getDateNames(WEEKDAYS)[DateTimeUtils.getSundayDayOfWeek(dateValue)]; + String month = getDateNames(MONTHS)[monthOfYear - 1]; + output.append(day).append(", ").append(month).append(' ').append(dayOfMonth).append(", "); + StringUtils.appendZeroPadded(output, 4, posYear); + i += 2; + } else if (containsAt(format, i, "DS") != null) { + StringUtils.appendTwoDigits(output, monthOfYear).append('/'); + StringUtils.appendTwoDigits(output, dayOfMonth).append('/'); + StringUtils.appendZeroPadded(output, 4, posYear); + i += 2; + } else if (containsAt(format, i, "TS") != null) { + output.append(h12).append(':'); + StringUtils.appendTwoDigits(output, minute).append(':'); + StringUtils.appendTwoDigits(output, second).append(' ').append(getDateNames(AM_PM)[isAM ? 0 : 1]); + i += 2; + + // Day + + } else if (containsAt(format, i, "DDD") != null) { + output.append(DateTimeUtils.getDayOfYear(dateValue)); + i += 3; + } else if (containsAt(format, i, "DD") != null) { + StringUtils.appendTwoDigits(output, dayOfMonth); + i += 2; + } else if ((cap = containsAt(format, i, "DY")) != null) { + String day = getDateNames(SHORT_WEEKDAYS)[DateTimeUtils.getSundayDayOfWeek(dateValue)]; + output.append(cap.apply(day)); + i += 2; + } else if ((cap = containsAt(format, i, "DAY")) != null) { + String day = getDateNames(WEEKDAYS)[DateTimeUtils.getSundayDayOfWeek(dateValue)]; + if (fillMode) { + day = StringUtils.pad(day, "Wednesday".length(), " ", true); + } + output.append(cap.apply(day)); + i += 3; + } else if (containsAt(format, i, "D") != null) { + output.append(DateTimeUtils.getSundayDayOfWeek(dateValue)); + i += 1; + } else if (containsAt(format, i, "J") != null) { + output.append(DateTimeUtils.absoluteDayFromDateValue(dateValue) - JULIAN_EPOCH); + i += 1; + + // Hours + + } else if (containsAt(format, i, "HH24") != null) { + StringUtils.appendTwoDigits(output, hour); + i += 4; + } else if (containsAt(format, i, "HH12") != null) { + StringUtils.appendTwoDigits(output, h12); + i += 4; + } else if (containsAt(format, i, "HH") != null) { + StringUtils.appendTwoDigits(output, h12); + i += 2; + + // Minutes + + } else if (containsAt(format, i, "MI") != null) { + StringUtils.appendTwoDigits(output, minute); + i += 2; + + // Seconds + + } else if (containsAt(format, i, "SSSSS") != null) { + int seconds = (int) (timeNanos / 1_000_000_000); + output.append(seconds); + i += 5; + } else if (containsAt(format, i, "SS") != null) { + StringUtils.appendTwoDigits(output, second); + i += 2; + + // Fractional seconds + + } else if (containsAt(format, i, "FF1", "FF2", + "FF3", "FF4", "FF5", "FF6", "FF7", "FF8", "FF9") != null) { + int x = format.charAt(i + 2) - '0'; + int ff = (int) (nanos * Math.pow(10, x - 9)); + StringUtils.appendZeroPadded(output, x, ff); + i += 3; + } else if (containsAt(format, i, "FF") != null) { + StringUtils.appendZeroPadded(output, 9, nanos); + i += 2; + + // Time zone + + } else if (containsAt(format, i, "TZR") != null) { + output.append(getTimeZone(session, value, false)); + i += 3; + } else if (containsAt(format, i, "TZD") != null) { + output.append(getTimeZone(session, value, true)); + i += 3; + } else if (containsAt(format, i, "TZH") != null) { + int hours = DateTimeFunction.extractDateTime(session, value, DateTimeFunction.TIMEZONE_HOUR); + output.append( hours < 0 ? '-' : '+'); + StringUtils.appendTwoDigits(output, Math.abs(hours)); + i += 3; + + } else if (containsAt(format, i, "TZM") != null) { + StringUtils.appendTwoDigits(output, + Math.abs(DateTimeFunction.extractDateTime(session, value, DateTimeFunction.TIMEZONE_MINUTE))); + i += 3; + + // Week + } else if (containsAt(format, i, "WW") != null) { + StringUtils.appendTwoDigits(output, (DateTimeUtils.getDayOfYear(dateValue) - 1) / 7 + 1); + i += 2; + } else if (containsAt(format, i, "IW") != null) { + StringUtils.appendTwoDigits(output, DateTimeUtils.getIsoWeekOfYear(dateValue)); + i += 2; + } else if (containsAt(format, i, "W") != null) { + output.append((dayOfMonth - 1) / 7 + 1); + i += 1; + + // Year + + } else if (containsAt(format, i, "Y,YYY") != null) { + output.append(new DecimalFormat("#,###").format(posYear)); + i += 5; + } else if (containsAt(format, i, "SYYYY") != null) { + // Should be <= 0, but Oracle prints negative years with off-by-one difference + if (year < 0) { + output.append('-'); + } + StringUtils.appendZeroPadded(output, 4, posYear); + i += 5; + } else if (containsAt(format, i, "YYYY", "RRRR") != null) { + StringUtils.appendZeroPadded(output, 4, posYear); + i += 4; + } else if (containsAt(format, i, "IYYY") != null) { + StringUtils.appendZeroPadded(output, 4, Math.abs(DateTimeUtils.getIsoWeekYear(dateValue))); + i += 4; + } else if (containsAt(format, i, "YYY") != null) { + StringUtils.appendZeroPadded(output, 3, posYear % 1000); + i += 3; + } else if (containsAt(format, i, "IYY") != null) { + StringUtils.appendZeroPadded(output, 3, Math.abs(DateTimeUtils.getIsoWeekYear(dateValue)) % 1000); + i += 3; + } else if (containsAt(format, i, "YY", "RR") != null) { + StringUtils.appendTwoDigits(output, posYear % 100); + i += 2; + } else if (containsAt(format, i, "IY") != null) { + StringUtils.appendTwoDigits(output, Math.abs(DateTimeUtils.getIsoWeekYear(dateValue)) % 100); + i += 2; + } else if (containsAt(format, i, "Y") != null) { + output.append(posYear % 10); + i += 1; + } else if (containsAt(format, i, "I") != null) { + output.append(Math.abs(DateTimeUtils.getIsoWeekYear(dateValue)) % 10); + i += 1; + + // Month / quarter + + } else if ((cap = containsAt(format, i, "MONTH")) != null) { + String month = getDateNames(MONTHS)[monthOfYear - 1]; + if (fillMode) { + month = StringUtils.pad(month, "September".length(), " ", true); + } + output.append(cap.apply(month)); + i += 5; + } else if ((cap = containsAt(format, i, "MON")) != null) { + String month = getDateNames(SHORT_MONTHS)[monthOfYear - 1]; + output.append(cap.apply(month)); + i += 3; + } else if (containsAt(format, i, "MM") != null) { + StringUtils.appendTwoDigits(output, monthOfYear); + i += 2; + } else if ((cap = containsAt(format, i, "RM")) != null) { + output.append(cap.apply(toRomanNumeral(monthOfYear))); + i += 2; + } else if (containsAt(format, i, "Q") != null) { + int q = 1 + ((monthOfYear - 1) / 3); + output.append(q); + i += 1; + + // Local radix character + + } else if (containsAt(format, i, "X") != null) { + char c = DecimalFormatSymbols.getInstance().getDecimalSeparator(); + output.append(c); + i += 1; + + // Format modifiers + + } else if (containsAt(format, i, "FM") != null) { + fillMode = !fillMode; + i += 2; + } else if (containsAt(format, i, "FX") != null) { + i += 2; + + // Literal text + + } else if (containsAt(format, i, "\"") != null) { + for (i = i + 1; i < format.length(); i++) { + char c = format.charAt(i); + if (c != '"') { + output.append(c); + } else { + i++; + break; + } + } + } else if (format.charAt(i) == '-' + || format.charAt(i) == '/' + || format.charAt(i) == ',' + || format.charAt(i) == '.' + || format.charAt(i) == ';' + || format.charAt(i) == ':' + || format.charAt(i) == ' ') { + output.append(format.charAt(i)); + i += 1; + + // Anything else + + } else { + throw DbException.get(ErrorCode.INVALID_TO_CHAR_FORMAT, format); + } + } + + return output.toString(); + } + + /** + * Returns a capitalization strategy if the specified string contains any of + * the specified substrings at the specified index. The capitalization + * strategy indicates the casing of the substring that was found. If none of + * the specified substrings are found, this method returns null + * . + * + * @param s the string to check + * @param index the index to check at + * @param substrings the substrings to check for within the string + * @return a capitalization strategy if the specified string contains any of + * the specified substrings at the specified index, + * null otherwise + */ + private static Capitalization containsAt(String s, int index, + String... substrings) { + for (String substring : substrings) { + if (index + substring.length() <= s.length()) { + boolean found = true; + Boolean up1 = null; + Boolean up2 = null; + for (int i = 0; i < substring.length(); i++) { + char c1 = s.charAt(index + i); + char c2 = substring.charAt(i); + if (c1 != c2 && Character.toUpperCase(c1) != Character.toUpperCase(c2)) { + found = false; + break; + } else if (Character.isLetter(c1)) { + if (up1 == null) { + up1 = Character.isUpperCase(c1); + } else if (up2 == null) { + up2 = Character.isUpperCase(c1); + } + } + } + if (found) { + return Capitalization.toCapitalization(up1, up2); + } + } + } + return null; + } + + /** Represents a capitalization / casing strategy. */ + public enum Capitalization { + + /** + * All letters are uppercased. + */ + UPPERCASE, + + /** + * All letters are lowercased. + */ + LOWERCASE, + + /** + * The string is capitalized (first letter uppercased, subsequent + * letters lowercased). + */ + CAPITALIZE; + + /** + * Returns the capitalization / casing strategy which should be used + * when the first and second letters have the specified casing. + * + * @param up1 whether or not the first letter is uppercased + * @param up2 whether or not the second letter is uppercased + * @return the capitalization / casing strategy which should be used + * when the first and second letters have the specified casing + */ + static Capitalization toCapitalization(Boolean up1, Boolean up2) { + if (up1 == null) { + return Capitalization.CAPITALIZE; + } else if (up2 == null) { + return up1 ? Capitalization.UPPERCASE : Capitalization.LOWERCASE; + } else if (up1) { + return up2 ? Capitalization.UPPERCASE : Capitalization.CAPITALIZE; + } else { + return Capitalization.LOWERCASE; + } + } + + /** + * Applies this capitalization strategy to the specified string. + * + * @param s the string to apply this strategy to + * @return the resultant string + */ + public String apply(String s) { + if (s == null || s.isEmpty()) { + return s; + } + switch (this) { + case UPPERCASE: + return StringUtils.toUpperEnglish(s); + case LOWERCASE: + return StringUtils.toLowerEnglish(s); + case CAPITALIZE: + return Character.toUpperCase(s.charAt(0)) + + (s.length() > 1 ? StringUtils.toLowerEnglish(s).substring(1) : ""); + default: + throw new IllegalArgumentException( + "Unknown capitalization strategy: " + this); + } + } + } + + public ToCharFunction(Expression arg1, Expression arg2, Expression arg3) { + super(arg2 == null ? new Expression[] { arg1 } + : arg3 == null ? new Expression[] { arg1, arg2 } : new Expression[] { arg1, arg2, arg3 }); + } + + @Override + public Value getValue(SessionLocal session, Value v1, Value v2, Value v3) { + switch (v1.getValueType()) { + case Value.TIME: + case Value.DATE: + case Value.TIMESTAMP: + case Value.TIMESTAMP_TZ: + v1 = ValueVarchar.get(toCharDateTime(session, v1, v2 == null ? null : v2.getString(), + v3 == null ? null : v3.getString()), session); + break; + case Value.SMALLINT: + case Value.INTEGER: + case Value.BIGINT: + case Value.NUMERIC: + case Value.DOUBLE: + case Value.REAL: + v1 = ValueVarchar.get(toChar(v1.getBigDecimal(), v2 == null ? null : v2.getString(), + v3 == null ? null : v3.getString()), session); + break; + default: + v1 = ValueVarchar.get(v1.getString(), session); + } + return v1; + } + + @Override + public Expression optimize(SessionLocal session) { + boolean allConst = optimizeArguments(session, true); + type = TypeInfo.TYPE_VARCHAR; + if (allConst) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public String getName() { + return "TO_CHAR"; + } + +} diff --git a/h2/src/main/org/h2/expression/function/TrimFunction.java b/h2/src/main/org/h2/expression/function/TrimFunction.java new file mode 100644 index 0000000..21f56a6 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/TrimFunction.java @@ -0,0 +1,86 @@ +/* + * 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.expression.function; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.util.StringUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueVarchar; + +/** + * A TRIM function. + */ +public final class TrimFunction extends Function1_2 { + + /** + * The LEADING flag. + */ + public static final int LEADING = 1; + + /** + * The TRAILING flag. + */ + public static final int TRAILING = 2; + + private int flags; + + public TrimFunction(Expression from, Expression space, int flags) { + super(from, space); + this.flags = flags; + } + + @Override + public Value getValue(SessionLocal session, Value v1, Value v2) { + return ValueVarchar.get(StringUtils.trim(v1.getString(), (flags & LEADING) != 0, (flags & TRAILING) != 0, + v2 != null ? v2.getString() : " "), session); + } + + @Override + public Expression optimize(SessionLocal session) { + left = left.optimize(session); + if (right != null) { + right = right.optimize(session); + } + type = TypeInfo.getTypeInfo(Value.VARCHAR, left.getType().getPrecision(), 0, null); + if (left.isConstant() && (right == null || right.isConstant())) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + builder.append(getName()).append('('); + boolean needFrom = false; + switch (flags) { + case LEADING: + builder.append("LEADING "); + needFrom = true; + break; + case TRAILING: + builder.append("TRAILING "); + needFrom = true; + break; + } + if (right != null) { + right.getUnenclosedSQL(builder, sqlFlags); + needFrom = true; + } + if (needFrom) { + builder.append(" FROM "); + } + return left.getUnenclosedSQL(builder, sqlFlags).append(')'); + } + + @Override + public String getName() { + return "TRIM"; + } + +} diff --git a/h2/src/main/org/h2/expression/function/TruncateValueFunction.java b/h2/src/main/org/h2/expression/function/TruncateValueFunction.java new file mode 100644 index 0000000..4bbedf9 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/TruncateValueFunction.java @@ -0,0 +1,105 @@ +/* + * 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.expression.function; + +import java.math.BigDecimal; +import java.math.MathContext; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.message.DbException; +import org.h2.util.MathUtils; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueDecfloat; +import org.h2.value.ValueNumeric; + +/** + * A TRUNCATE_VALUE function. + */ +public final class TruncateValueFunction extends FunctionN { + + public TruncateValueFunction(Expression arg1, Expression arg2, Expression arg3) { + super(new Expression[] { arg1, arg2, arg3 }); + } + + @Override + public Value getValue(SessionLocal session, Value v1, Value v2, Value v3) { + long precision = v2.getLong(); + boolean force = v3.getBoolean(); + if (precision <= 0) { + throw DbException.get(ErrorCode.INVALID_VALUE_PRECISION, Long.toString(precision), "1", + "" + Integer.MAX_VALUE); + } + TypeInfo t = v1.getType(); + int valueType = t.getValueType(); + if (DataType.getDataType(valueType).supportsPrecision) { + if (precision < t.getPrecision()) { + switch (valueType) { + case Value.NUMERIC: { + BigDecimal bd = v1.getBigDecimal().round(new MathContext(MathUtils.convertLongToInt(precision))); + if (bd.scale() < 0) { + bd = bd.setScale(0); + } + return ValueNumeric.get(bd); + } + case Value.DECFLOAT: + return ValueDecfloat + .get(v1.getBigDecimal().round(new MathContext(MathUtils.convertLongToInt(precision)))); + default: + return v1.castTo(TypeInfo.getTypeInfo(valueType, precision, t.getScale(), t.getExtTypeInfo()), + session); + } + } + } else if (force) { + BigDecimal bd; + switch (valueType) { + case Value.TINYINT: + case Value.SMALLINT: + case Value.INTEGER: + bd = BigDecimal.valueOf(v1.getInt()); + break; + case Value.BIGINT: + bd = BigDecimal.valueOf(v1.getLong()); + break; + case Value.REAL: + case Value.DOUBLE: + bd = v1.getBigDecimal(); + break; + default: + return v1; + } + bd = bd.round(new MathContext(MathUtils.convertLongToInt(precision))); + if (valueType == Value.DECFLOAT) { + return ValueDecfloat.get(bd); + } + if (bd.scale() < 0) { + bd = bd.setScale(0); + } + return ValueNumeric.get(bd).convertTo(valueType); + } + return v1; + } + + @Override + public Expression optimize(SessionLocal session) { + boolean allConst = optimizeArguments(session, true); + type = args[0].getType(); + if (allConst) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public String getName() { + return "TRUNCATE_VALUE"; + } + +} diff --git a/h2/src/main/org/h2/expression/function/XMLFunction.java b/h2/src/main/org/h2/expression/function/XMLFunction.java new file mode 100644 index 0000000..fb4491b --- /dev/null +++ b/h2/src/main/org/h2/expression/function/XMLFunction.java @@ -0,0 +1,161 @@ +/* + * 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.expression.function; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.message.DbException; +import org.h2.util.StringUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueNull; +import org.h2.value.ValueVarchar; + +/** + * An XML function. + */ +public final class XMLFunction extends FunctionN { + + /** + * XMLATTR() (non-standard). + */ + public static final int XMLATTR = 0; + + /** + * XMLCDATA() (non-standard). + */ + public static final int XMLCDATA = XMLATTR + 1; + + /** + * XMLCOMMENT() (non-standard). + */ + public static final int XMLCOMMENT = XMLCDATA + 1; + + /** + * XMLNODE() (non-standard). + */ + public static final int XMLNODE = XMLCOMMENT + 1; + + /** + * XMLSTARTDOC() (non-standard). + */ + public static final int XMLSTARTDOC = XMLNODE + 1; + + /** + * XMLTEXT() (non-standard). + */ + public static final int XMLTEXT = XMLSTARTDOC + 1; + + private static final String[] NAMES = { // + "XMLATTR", "XMLCDATA", "XMLCOMMENT", "XMLNODE", "XMLSTARTDOC", "XMLTEXT" // + }; + + private final int function; + + public XMLFunction(int function) { + super(new Expression[4]); + this.function = function; + } + + @Override + public Value getValue(SessionLocal session) { + switch (function) { + case XMLNODE: + return xmlNode(session); + case XMLSTARTDOC: + return ValueVarchar.get(StringUtils.xmlStartDoc(), session); + default: + return super.getValue(session); + } + } + + private Value xmlNode(SessionLocal session) { + Value v1 = args[0].getValue(session); + if (v1 == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + int length = args.length; + String attr = length >= 2 ? args[1].getValue(session).getString() : null; + String content = length >= 3 ? args[2].getValue(session).getString() : null; + boolean indent; + if (length >= 4) { + Value v4 = args[3].getValue(session); + if (v4 == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + indent = v4.getBoolean(); + } else { + indent = true; + } + return ValueVarchar.get(StringUtils.xmlNode(v1.getString(), attr, content, indent), session); + } + + @Override + protected Value getValue(SessionLocal session, Value v1, Value v2, Value v3) { + switch (function) { + case XMLATTR: + v1 = ValueVarchar.get(StringUtils.xmlAttr(v1.getString(), v2.getString()), session); + break; + case XMLCDATA: + v1 = ValueVarchar.get(StringUtils.xmlCData(v1.getString()), session); + break; + case XMLCOMMENT: + v1 = ValueVarchar.get(StringUtils.xmlComment(v1.getString()), session); + break; + case XMLTEXT: + v1 = ValueVarchar.get(StringUtils.xmlText(v1.getString(), v2 != null && v2.getBoolean()), session); + break; + default: + throw DbException.getInternalError("function=" + function); + } + return v1; + } + + @Override + public Expression optimize(SessionLocal session) { + boolean allConst = optimizeArguments(session, true); + int min, max; + switch (function) { + case XMLATTR: + max = min = 2; + break; + case XMLNODE: + min = 1; + max = 4; + break; + case XMLCDATA: + case XMLCOMMENT: + max = min = 1; + break; + case XMLSTARTDOC: + max = min = 0; + break; + case XMLTEXT: + min = 1; + max = 2; + break; + default: + throw DbException.getInternalError("function=" + function); + } + int len = args.length; + if (len < min || len > max) { + throw DbException.get(ErrorCode.INVALID_PARAMETER_COUNT_2, getName(), min + ".." + max); + } + type = TypeInfo.TYPE_VARCHAR; + if (allConst) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + return this; + } + + @Override + public String getName() { + return NAMES[function]; + } + +} diff --git a/h2/src/main/org/h2/expression/function/package.html b/h2/src/main/org/h2/expression/function/package.html new file mode 100644 index 0000000..934f342 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Functions. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/expression/function/table/ArrayTableFunction.java b/h2/src/main/org/h2/expression/function/table/ArrayTableFunction.java new file mode 100644 index 0000000..eb5b5c7 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/table/ArrayTableFunction.java @@ -0,0 +1,178 @@ +/* + * 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.expression.function.table; + +import java.util.ArrayList; + +import org.h2.api.ErrorCode; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.message.DbException; +import org.h2.result.LocalResult; +import org.h2.result.ResultInterface; +import org.h2.table.Column; +import org.h2.value.Value; +import org.h2.value.ValueCollectionBase; +import org.h2.value.ValueInteger; +import org.h2.value.ValueNull; + +/** + * A table value function. + */ +public final class ArrayTableFunction extends TableFunction { + + /** + * UNNEST(). + */ + public static final int UNNEST = 0; + + /** + * TABLE() (non-standard). + */ + public static final int TABLE = UNNEST + 1; + + /** + * TABLE_DISTINCT() (non-standard). + */ + public static final int TABLE_DISTINCT = TABLE + 1; + + private Column[] columns; + + private static final String[] NAMES = { // + "UNNEST", "TABLE", "TABLE_DISTINCT" // + }; + + private final int function; + + public ArrayTableFunction(int function) { + super(new Expression[1]); + this.function = function; + } + + @Override + public ResultInterface getValue(SessionLocal session) { + return getTable(session, false); + } + + @Override + public void optimize(SessionLocal session) { + super.optimize(session); + if (args.length < 1) { + throw DbException.get(ErrorCode.INVALID_PARAMETER_COUNT_2, getName(), ">0"); + } + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + if (function == UNNEST) { + super.getSQL(builder, sqlFlags); + if (args.length < columns.length) { + builder.append(" WITH ORDINALITY"); + } + } else { + builder.append(getName()).append('('); + for (int i = 0; i < args.length; i++) { + if (i > 0) { + builder.append(", "); + } + builder.append(columns[i].getCreateSQL()).append('='); + args[i].getUnenclosedSQL(builder, sqlFlags); + } + builder.append(')'); + } + return builder; + } + + @Override + public ResultInterface getValueTemplate(SessionLocal session) { + return getTable(session, true); + } + + public void setColumns(ArrayList columns) { + this.columns = columns.toArray(new Column[0]); + } + + private ResultInterface getTable(SessionLocal session, boolean onlyColumnList) { + int totalColumns = columns.length; + Expression[] header = new Expression[totalColumns]; + Database db = session.getDatabase(); + for (int i = 0; i < totalColumns; i++) { + Column c = columns[i]; + ExpressionColumn col = new ExpressionColumn(db, c); + header[i] = col; + } + LocalResult result = new LocalResult(session, header, totalColumns, totalColumns); + if (!onlyColumnList && function == TABLE_DISTINCT) { + result.setDistinct(); + } + if (!onlyColumnList) { + int len = totalColumns; + boolean unnest = function == UNNEST, addNumber = false; + if (unnest) { + len = args.length; + if (len < totalColumns) { + addNumber = true; + } + } + Value[][] list = new Value[len][]; + int rows = 0; + for (int i = 0; i < len; i++) { + Value v = args[i].getValue(session); + if (v == ValueNull.INSTANCE) { + list[i] = Value.EMPTY_VALUES; + } else { + int type = v.getValueType(); + if (type != Value.ARRAY && type != Value.ROW) { + v = v.convertToAnyArray(session); + } + Value[] l = ((ValueCollectionBase) v).getList(); + list[i] = l; + rows = Math.max(rows, l.length); + } + } + for (int row = 0; row < rows; row++) { + Value[] r = new Value[totalColumns]; + for (int j = 0; j < len; j++) { + Value[] l = list[j]; + Value v; + if (l.length <= row) { + v = ValueNull.INSTANCE; + } else { + Column c = columns[j]; + v = l[row]; + if (!unnest) { + v = v.convertForAssignTo(c.getType(), session, c); + } + } + r[j] = v; + } + if (addNumber) { + r[len] = ValueInteger.get(row + 1); + } + result.addRow(r); + } + } + result.done(); + return result; + } + + @Override + public String getName() { + return NAMES[function]; + } + + @Override + public boolean isDeterministic() { + return true; + } + + public int getFunctionType() { + return function; + } + +} diff --git a/h2/src/main/org/h2/expression/function/table/CSVReadFunction.java b/h2/src/main/org/h2/expression/function/table/CSVReadFunction.java new file mode 100644 index 0000000..f03ad1c --- /dev/null +++ b/h2/src/main/org/h2/expression/function/table/CSVReadFunction.java @@ -0,0 +1,119 @@ +/* + * 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.expression.function.table; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.function.CSVWriteFunction; +import org.h2.message.DbException; +import org.h2.result.ResultInterface; +import org.h2.schema.FunctionAlias.JavaMethod; +import org.h2.tools.Csv; +import org.h2.util.StringUtils; + +/** + * A CSVREAD function. + */ +public final class CSVReadFunction extends TableFunction { + + public CSVReadFunction() { + super(new Expression[4]); + } + + @Override + public ResultInterface getValue(SessionLocal session) { + session.getUser().checkAdmin(); + String fileName = getValue(session, 0); + String columnList = getValue(session, 1); + Csv csv = new Csv(); + String options = getValue(session, 2); + String charset = null; + if (options != null && options.indexOf('=') >= 0) { + charset = csv.setOptions(options); + } else { + charset = options; + String fieldSeparatorRead = getValue(session, 3); + String fieldDelimiter = getValue(session, 4); + String escapeCharacter = getValue(session, 5); + String nullString = getValue(session, 6); + CSVWriteFunction.setCsvDelimiterEscape(csv, fieldSeparatorRead, fieldDelimiter, escapeCharacter); + csv.setNullString(nullString); + } + char fieldSeparator = csv.getFieldSeparatorRead(); + String[] columns = StringUtils.arraySplit(columnList, fieldSeparator, true); + try { + // TODO create result directly + return JavaMethod.resultSetToResult(session, csv.read(fileName, columns, charset), Integer.MAX_VALUE); + } catch (SQLException e) { + throw DbException.convert(e); + } + } + + private String getValue(SessionLocal session, int index) { + return getValue(session, args, index); + } + + @Override + public void optimize(SessionLocal session) { + super.optimize(session); + int len = args.length; + if (len < 1 || len > 7) { + throw DbException.get(ErrorCode.INVALID_PARAMETER_COUNT_2, getName(), "1..7"); + } + } + + @Override + public ResultInterface getValueTemplate(SessionLocal session) { + session.getUser().checkAdmin(); + String fileName = getValue(session, args, 0); + if (fileName == null) { + throw DbException.get(ErrorCode.PARAMETER_NOT_SET_1, "fileName"); + } + String columnList = getValue(session, args, 1); + Csv csv = new Csv(); + String options = getValue(session, args, 2); + String charset = null; + if (options != null && options.indexOf('=') >= 0) { + charset = csv.setOptions(options); + } else { + charset = options; + String fieldSeparatorRead = getValue(session, args, 3); + String fieldDelimiter = getValue(session, args, 4); + String escapeCharacter = getValue(session, args, 5); + CSVWriteFunction.setCsvDelimiterEscape(csv, fieldSeparatorRead, fieldDelimiter, escapeCharacter); + } + char fieldSeparator = csv.getFieldSeparatorRead(); + String[] columns = StringUtils.arraySplit(columnList, fieldSeparator, true); + ResultInterface result; + try (ResultSet rs = csv.read(fileName, columns, charset)) { + result = JavaMethod.resultSetToResult(session, rs, 0); + } catch (SQLException e) { + throw DbException.convert(e); + } finally { + csv.close(); + } + return result; + } + + private static String getValue(SessionLocal session, Expression[] args, int index) { + return index < args.length ? args[index].getValue(session).getString() : null; + } + + @Override + public String getName() { + return "CSVREAD"; + } + + @Override + public boolean isDeterministic() { + return false; + } + +} diff --git a/h2/src/main/org/h2/expression/function/table/JavaTableFunction.java b/h2/src/main/org/h2/expression/function/table/JavaTableFunction.java new file mode 100644 index 0000000..dc74497 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/table/JavaTableFunction.java @@ -0,0 +1,63 @@ +/* + * 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.expression.function.table; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.message.DbException; +import org.h2.result.ResultInterface; +import org.h2.schema.FunctionAlias; + +/** + * This class wraps a user-defined function. + */ +public final class JavaTableFunction extends TableFunction { + + private final FunctionAlias functionAlias; + private final FunctionAlias.JavaMethod javaMethod; + + public JavaTableFunction(FunctionAlias functionAlias, Expression[] args) { + super(args); + this.functionAlias = functionAlias; + this.javaMethod = functionAlias.findJavaMethod(args); + if (javaMethod.getDataType() != null) { + throw DbException.get(ErrorCode.FUNCTION_MUST_RETURN_RESULT_SET_1, getName()); + } + } + + @Override + public ResultInterface getValue(SessionLocal session) { + return javaMethod.getTableValue(session, args, false); + } + + @Override + public ResultInterface getValueTemplate(SessionLocal session) { + return javaMethod.getTableValue(session, args, true); + } + + @Override + public void optimize(SessionLocal session) { + super.optimize(session); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return Expression.writeExpressions(functionAlias.getSQL(builder, sqlFlags).append('('), args, sqlFlags) + .append(')'); + } + + @Override + public String getName() { + return functionAlias.getName(); + } + + @Override + public boolean isDeterministic() { + return functionAlias.isDeterministic(); + } + +} diff --git a/h2/src/main/org/h2/expression/function/table/LinkSchemaFunction.java b/h2/src/main/org/h2/expression/function/table/LinkSchemaFunction.java new file mode 100644 index 0000000..2a17b97 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/table/LinkSchemaFunction.java @@ -0,0 +1,125 @@ +/* + * 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.expression.function.table; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.message.DbException; +import org.h2.result.ResultInterface; +import org.h2.result.SimpleResult; +import org.h2.util.JdbcUtils; +import org.h2.util.StringUtils; +import org.h2.value.TypeInfo; +import org.h2.value.ValueVarchar; + +/** + * A LINK_SCHEMA function. + */ +public final class LinkSchemaFunction extends TableFunction { + + public LinkSchemaFunction() { + super(new Expression[6]); + } + + @Override + public ResultInterface getValue(SessionLocal session) { + session.getUser().checkAdmin(); + String targetSchema = getValue(session, 0); + String driver = getValue(session, 1); + String url = getValue(session, 2); + String user = getValue(session, 3); + String password = getValue(session, 4); + String sourceSchema = getValue(session, 5); + if (targetSchema == null || driver == null || url == null || user == null || password == null + || sourceSchema == null) { + return getValueTemplate(session); + } + Connection conn = session.createConnection(false); + Connection c2 = null; + Statement stat = null; + ResultSet rs = null; + SimpleResult result = new SimpleResult(); + result.addColumn("TABLE_NAME", TypeInfo.TYPE_VARCHAR); + try { + c2 = JdbcUtils.getConnection(driver, url, user, password); + stat = conn.createStatement(); + stat.execute(StringUtils.quoteIdentifier(new StringBuilder("CREATE SCHEMA IF NOT EXISTS "), targetSchema) + .toString()); + // Workaround for PostgreSQL to avoid index names + if (url.startsWith("jdbc:postgresql:")) { + rs = c2.getMetaData().getTables(null, sourceSchema, null, + new String[] { "TABLE", "LINKED TABLE", "VIEW", "EXTERNAL" }); + } else { + rs = c2.getMetaData().getTables(null, sourceSchema, null, null); + } + while (rs.next()) { + String table = rs.getString("TABLE_NAME"); + StringBuilder buff = new StringBuilder(); + buff.append("DROP TABLE IF EXISTS "); + StringUtils.quoteIdentifier(buff, targetSchema).append('.'); + StringUtils.quoteIdentifier(buff, table); + stat.execute(buff.toString()); + buff.setLength(0); + buff.append("CREATE LINKED TABLE "); + StringUtils.quoteIdentifier(buff, targetSchema).append('.'); + StringUtils.quoteIdentifier(buff, table).append('('); + StringUtils.quoteStringSQL(buff, driver).append(", "); + StringUtils.quoteStringSQL(buff, url).append(", "); + StringUtils.quoteStringSQL(buff, user).append(", "); + StringUtils.quoteStringSQL(buff, password).append(", "); + StringUtils.quoteStringSQL(buff, sourceSchema).append(", "); + StringUtils.quoteStringSQL(buff, table).append(')'); + stat.execute(buff.toString()); + result.addRow(ValueVarchar.get(table, session)); + } + } catch (SQLException e) { + result.close(); + throw DbException.convert(e); + } finally { + JdbcUtils.closeSilently(rs); + JdbcUtils.closeSilently(c2); + JdbcUtils.closeSilently(stat); + } + return result; + } + + private String getValue(SessionLocal session, int index) { + return args[index].getValue(session).getString(); + } + + @Override + public void optimize(SessionLocal session) { + super.optimize(session); + int len = args.length; + if (len != 6) { + throw DbException.get(ErrorCode.INVALID_PARAMETER_COUNT_2, getName(), "6"); + } + } + + @Override + public ResultInterface getValueTemplate(SessionLocal session) { + SimpleResult result = new SimpleResult(); + result.addColumn("TABLE_NAME", TypeInfo.TYPE_VARCHAR); + return result; + } + + @Override + public String getName() { + return "LINK_SCHEMA"; + } + + @Override + public boolean isDeterministic() { + return false; + } + +} diff --git a/h2/src/main/org/h2/expression/function/table/TableFunction.java b/h2/src/main/org/h2/expression/function/table/TableFunction.java new file mode 100644 index 0000000..729421f --- /dev/null +++ b/h2/src/main/org/h2/expression/function/table/TableFunction.java @@ -0,0 +1,90 @@ +/* + * 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.expression.function.table; + +import java.util.Arrays; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionWithVariableParameters; +import org.h2.expression.function.NamedExpression; +import org.h2.message.DbException; +import org.h2.result.ResultInterface; +import org.h2.util.HasSQL; + +/** + * A table value function. + */ +public abstract class TableFunction implements HasSQL, NamedExpression, ExpressionWithVariableParameters { + + protected Expression[] args; + + private int argsCount; + + protected TableFunction(Expression[] args) { + this.args = args; + } + + @Override + public void addParameter(Expression param) { + int capacity = args.length; + if (argsCount >= capacity) { + args = Arrays.copyOf(args, capacity * 2); + } + args[argsCount++] = param; + } + + @Override + public void doneWithParameters() throws DbException { + if (args.length != argsCount) { + args = Arrays.copyOf(args, argsCount); + } + } + + /** + * Get a result with. + * + * @param session + * the session + * @return the result + */ + public abstract ResultInterface getValue(SessionLocal session); + + /** + * Get an empty result with the column names set. + * + * @param session + * the session + * @return the empty result + */ + public abstract ResultInterface getValueTemplate(SessionLocal session); + + /** + * Try to optimize this table function + * + * @param session + * the session + */ + public void optimize(SessionLocal session) { + for (int i = 0, l = args.length; i < l; i++) { + args[i] = args[i].optimize(session); + } + } + + /** + * Whether the function always returns the same result for the same + * parameters. + * + * @return true if it does + */ + public abstract boolean isDeterministic(); + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return Expression.writeExpressions(builder.append(getName()).append('('), args, sqlFlags).append(')'); + } + +} diff --git a/h2/src/main/org/h2/expression/function/table/package.html b/h2/src/main/org/h2/expression/function/table/package.html new file mode 100644 index 0000000..8dd9d74 --- /dev/null +++ b/h2/src/main/org/h2/expression/function/table/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Table value functions. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/expression/package.html b/h2/src/main/org/h2/expression/package.html new file mode 100644 index 0000000..7bf9c96 --- /dev/null +++ b/h2/src/main/org/h2/expression/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Expressions include mathematical operations, simple values, and others. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/fulltext/FullText.java b/h2/src/main/org/h2/fulltext/FullText.java new file mode 100644 index 0000000..8d7dd71 --- /dev/null +++ b/h2/src/main/org/h2/fulltext/FullText.java @@ -0,0 +1,1167 @@ +/* + * 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.fulltext; + +import java.io.IOException; +import java.io.Reader; +import java.io.StreamTokenizer; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.UUID; + +import org.h2.api.Trigger; +import org.h2.command.Parser; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.expression.ValueExpression; +import org.h2.expression.condition.Comparison; +import org.h2.expression.condition.ConditionAndOr; +import org.h2.jdbc.JdbcConnection; +import org.h2.message.DbException; +import org.h2.tools.SimpleResultSet; +import org.h2.util.IOUtils; +import org.h2.util.StringUtils; +import org.h2.util.Utils; + +/** + * This class implements the native full text search. + * Most methods can be called using SQL statements as well. + */ +public class FullText { + + /** + * A column name of the result set returned by the searchData method. + */ + private static final String FIELD_SCHEMA = "SCHEMA"; + + /** + * A column name of the result set returned by the searchData method. + */ + private static final String FIELD_TABLE = "TABLE"; + + /** + * A column name of the result set returned by the searchData method. + */ + private static final String FIELD_COLUMNS = "COLUMNS"; + + /** + * A column name of the result set returned by the searchData method. + */ + private static final String FIELD_KEYS = "KEYS"; + + /** + * The hit score. + */ + private static final String FIELD_SCORE = "SCORE"; + + private static final String TRIGGER_PREFIX = "FT_"; + private static final String SCHEMA = "FT"; + private static final String SELECT_MAP_BY_WORD_ID = + "SELECT ROWID FROM " + SCHEMA + ".MAP WHERE WORDID=?"; + private static final String SELECT_ROW_BY_ID = + "SELECT `KEY`, INDEXID FROM " + SCHEMA + ".ROWS WHERE ID=?"; + + /** + * The column name of the result set returned by the search method. + */ + private static final String FIELD_QUERY = "QUERY"; + + /** + * Initializes full text search functionality for this database. This adds + * the following Java functions to the database: + *
    + *
  • FT_CREATE_INDEX(schemaNameString, tableNameString, + * columnListString)
  • + *
  • FT_SEARCH(queryString, limitInt, offsetInt): result set
  • + *
  • FT_REINDEX()
  • + *
  • FT_DROP_ALL()
  • + *
+ * It also adds a schema FT to the database where bookkeeping information + * is stored. This function may be called from a Java application, or by + * using the SQL statements: + * + *
+     * CREATE ALIAS IF NOT EXISTS FT_INIT FOR
+     *      "org.h2.fulltext.FullText.init";
+     * CALL FT_INIT();
+     * 
+ * + * @param conn the connection + * @throws SQLException on failure + */ + public static void init(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("CREATE SCHEMA IF NOT EXISTS " + SCHEMA); + stat.execute("CREATE TABLE IF NOT EXISTS " + SCHEMA + + ".INDEXES(ID INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, " + + "SCHEMA VARCHAR, `TABLE` VARCHAR, COLUMNS VARCHAR, " + + "UNIQUE(SCHEMA, `TABLE`))"); + stat.execute("CREATE TABLE IF NOT EXISTS " + SCHEMA + + ".WORDS(ID INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, " + + "NAME VARCHAR, UNIQUE(NAME))"); + stat.execute("CREATE TABLE IF NOT EXISTS " + SCHEMA + + ".ROWS(ID BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, HASH INT, INDEXID INT, " + + "`KEY` VARCHAR, UNIQUE(HASH, INDEXID, `KEY`))"); + stat.execute("CREATE TABLE IF NOT EXISTS " + SCHEMA + + ".MAP(ROWID INT, WORDID INT, PRIMARY KEY(WORDID, ROWID))"); + stat.execute("CREATE TABLE IF NOT EXISTS " + SCHEMA + + ".IGNORELIST(LIST VARCHAR)"); + stat.execute("CREATE TABLE IF NOT EXISTS " + SCHEMA + + ".SETTINGS(`KEY` VARCHAR PRIMARY KEY, `VALUE` VARCHAR)"); + String className = FullText.class.getName(); + stat.execute("CREATE ALIAS IF NOT EXISTS FT_CREATE_INDEX FOR '" + className + ".createIndex'"); + stat.execute("CREATE ALIAS IF NOT EXISTS FT_DROP_INDEX FOR '" + className + ".dropIndex'"); + stat.execute("CREATE ALIAS IF NOT EXISTS FT_SEARCH FOR '" + className + ".search'"); + stat.execute("CREATE ALIAS IF NOT EXISTS FT_SEARCH_DATA FOR '" + className + ".searchData'"); + stat.execute("CREATE ALIAS IF NOT EXISTS FT_REINDEX FOR '" + className + ".reindex'"); + stat.execute("CREATE ALIAS IF NOT EXISTS FT_DROP_ALL FOR '" + className + ".dropAll'"); + FullTextSettings setting = FullTextSettings.getInstance(conn); + ResultSet rs = stat.executeQuery("SELECT * FROM " + SCHEMA + + ".IGNORELIST"); + while (rs.next()) { + String commaSeparatedList = rs.getString(1); + setIgnoreList(setting, commaSeparatedList); + } + rs = stat.executeQuery("SELECT * FROM " + SCHEMA + ".SETTINGS"); + while (rs.next()) { + String key = rs.getString(1); + if ("whitespaceChars".equals(key)) { + String value = rs.getString(2); + setting.setWhitespaceChars(value); + } + } + rs = stat.executeQuery("SELECT * FROM " + SCHEMA + ".WORDS"); + while (rs.next()) { + String word = rs.getString("NAME"); + int id = rs.getInt("ID"); + word = setting.convertWord(word); + if (word != null) { + setting.addWord(word, id); + } + } + setting.setInitialized(true); + } + + /** + * Create a new full text index for a table and column list. Each table may + * only have one index at any time. + * + * @param conn the connection + * @param schema the schema name of the table (case sensitive) + * @param table the table name (case sensitive) + * @param columnList the column list (null for all columns) + * @throws SQLException on failure + */ + public static void createIndex(Connection conn, String schema, + String table, String columnList) throws SQLException { + init(conn); + PreparedStatement prep = conn.prepareStatement("INSERT INTO " + SCHEMA + + ".INDEXES(SCHEMA, `TABLE`, COLUMNS) VALUES(?, ?, ?)"); + prep.setString(1, schema); + prep.setString(2, table); + prep.setString(3, columnList); + prep.execute(); + createTrigger(conn, schema, table); + indexExistingRows(conn, schema, table); + } + + /** + * Re-creates the full text index for this database. Calling this method is + * usually not needed, as the index is kept up-to-date automatically. + * + * @param conn the connection + * @throws SQLException on failure + */ + public static void reindex(Connection conn) throws SQLException { + init(conn); + removeAllTriggers(conn, TRIGGER_PREFIX); + FullTextSettings setting = FullTextSettings.getInstance(conn); + setting.clearWordList(); + Statement stat = conn.createStatement(); + stat.execute("TRUNCATE TABLE " + SCHEMA + ".WORDS"); + stat.execute("TRUNCATE TABLE " + SCHEMA + ".ROWS"); + stat.execute("TRUNCATE TABLE " + SCHEMA + ".MAP"); + ResultSet rs = stat.executeQuery("SELECT * FROM " + SCHEMA + ".INDEXES"); + while (rs.next()) { + String schema = rs.getString("SCHEMA"); + String table = rs.getString("TABLE"); + createTrigger(conn, schema, table); + indexExistingRows(conn, schema, table); + } + } + + /** + * Drop an existing full text index for a table. This method returns + * silently if no index for this table exists. + * + * @param conn the connection + * @param schema the schema name of the table (case sensitive) + * @param table the table name (case sensitive) + * @throws SQLException on failure + */ + public static void dropIndex(Connection conn, String schema, String table) + throws SQLException { + init(conn); + PreparedStatement prep = conn.prepareStatement("SELECT ID FROM " + SCHEMA + + ".INDEXES WHERE SCHEMA=? AND `TABLE`=?"); + prep.setString(1, schema); + prep.setString(2, table); + ResultSet rs = prep.executeQuery(); + if (!rs.next()) { + return; + } + int indexId = rs.getInt(1); + prep = conn.prepareStatement("DELETE FROM " + SCHEMA + + ".INDEXES WHERE ID=?"); + prep.setInt(1, indexId); + prep.execute(); + createOrDropTrigger(conn, schema, table, false); + prep = conn.prepareStatement("DELETE FROM " + SCHEMA + + ".ROWS WHERE INDEXID=? AND ROWNUM<10000"); + while (true) { + prep.setInt(1, indexId); + int deleted = prep.executeUpdate(); + if (deleted == 0) { + break; + } + } + prep = conn.prepareStatement("DELETE FROM " + SCHEMA + ".MAP " + + "WHERE NOT EXISTS (SELECT * FROM " + SCHEMA + + ".ROWS R WHERE R.ID=ROWID) AND ROWID<10000"); + while (true) { + int deleted = prep.executeUpdate(); + if (deleted == 0) { + break; + } + } + } + + /** + * Drops all full text indexes from the database. + * + * @param conn the connection + * @throws SQLException on failure + */ + public static void dropAll(Connection conn) throws SQLException { + init(conn); + Statement stat = conn.createStatement(); + stat.execute("DROP SCHEMA IF EXISTS " + SCHEMA + " CASCADE"); + removeAllTriggers(conn, TRIGGER_PREFIX); + FullTextSettings setting = FullTextSettings.getInstance(conn); + setting.removeAllIndexes(); + setting.clearIgnored(); + setting.clearWordList(); + } + + /** + * Searches from the full text index for this database. + * The returned result set has the following column: + *
  • QUERY (varchar): the query to use to get the data. + * The query does not include 'SELECT * FROM '. Example: + * PUBLIC.TEST WHERE ID = 1 + *
  • SCORE (float) the relevance score. This value is always 1.0 + * for the native fulltext search. + *
+ * + * @param conn the connection + * @param text the search query + * @param limit the maximum number of rows or 0 for no limit + * @param offset the offset or 0 for no offset + * @return the result set + * @throws SQLException on failure + */ + public static ResultSet search(Connection conn, String text, int limit, + int offset) throws SQLException { + try { + return search(conn, text, limit, offset, false); + } catch (DbException e) { + throw DbException.toSQLException(e); + } + } + + /** + * Searches from the full text index for this database. The result contains + * the primary key data as an array. The returned result set has the + * following columns: + *
    + *
  • SCHEMA (varchar): the schema name. Example: PUBLIC
  • + *
  • TABLE (varchar): the table name. Example: TEST
  • + *
  • COLUMNS (array of varchar): comma separated list of quoted column + * names. The column names are quoted if necessary. Example: (ID)
  • + *
  • KEYS (array of values): comma separated list of values. Example: (1) + *
  • + *
  • SCORE (float) the relevance score. This value is always 1.0 + * for the native fulltext search. + *
  • + *
+ * + * @param conn the connection + * @param text the search query + * @param limit the maximum number of rows or 0 for no limit + * @param offset the offset or 0 for no offset + * @return the result set + * @throws SQLException on failure + */ + public static ResultSet searchData(Connection conn, String text, int limit, + int offset) throws SQLException { + try { + return search(conn, text, limit, offset, true); + } catch (DbException e) { + throw DbException.toSQLException(e); + } + } + + /** + * Change the ignore list. The ignore list is a comma separated list of + * common words that must not be indexed. The default ignore list is empty. + * If indexes already exist at the time this list is changed, reindex must + * be called. + * + * @param conn the connection + * @param commaSeparatedList the list + * @throws SQLException on failure + */ + public static void setIgnoreList(Connection conn, String commaSeparatedList) + throws SQLException { + try { + init(conn); + FullTextSettings setting = FullTextSettings.getInstance(conn); + setIgnoreList(setting, commaSeparatedList); + Statement stat = conn.createStatement(); + stat.execute("TRUNCATE TABLE " + SCHEMA + ".IGNORELIST"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO " + + SCHEMA + ".IGNORELIST VALUES(?)"); + prep.setString(1, commaSeparatedList); + prep.execute(); + } catch (DbException e) { + throw DbException.toSQLException(e); + } + } + + /** + * Change the whitespace characters. The whitespace characters are used to + * separate words. If indexes already exist at the time this list is + * changed, reindex must be called. + * + * @param conn the connection + * @param whitespaceChars the list of characters + * @throws SQLException on failure + */ + public static void setWhitespaceChars(Connection conn, + String whitespaceChars) throws SQLException { + try { + init(conn); + FullTextSettings setting = FullTextSettings.getInstance(conn); + setting.setWhitespaceChars(whitespaceChars); + PreparedStatement prep = conn.prepareStatement("MERGE INTO " + + SCHEMA + ".SETTINGS VALUES(?, ?)"); + prep.setString(1, "whitespaceChars"); + prep.setString(2, whitespaceChars); + prep.execute(); + } catch (DbException e) { + throw DbException.toSQLException(e); + } + } + + /** + * INTERNAL. + * Convert the object to a string. + * + * @param data the object + * @param type the SQL type + * @return the string + * @throws SQLException on failure + */ + protected static String asString(Object data, int type) throws SQLException { + if (data == null) { + return "NULL"; + } + switch (type) { + case Types.BIT: + case Types.BOOLEAN: + case Types.INTEGER: + case Types.BIGINT: + case Types.DECIMAL: + case Types.DOUBLE: + case Types.FLOAT: + case Types.NUMERIC: + case Types.REAL: + case Types.SMALLINT: + case Types.TINYINT: + case Types.DATE: + case Types.TIME: + case Types.TIMESTAMP: + case Types.LONGVARCHAR: + case Types.CHAR: + case Types.VARCHAR: + return data.toString(); + case Types.CLOB: + try { + if (data instanceof Clob) { + data = ((Clob) data).getCharacterStream(); + } + return IOUtils.readStringAndClose((Reader) data, -1); + } catch (IOException e) { + throw DbException.toSQLException(e); + } + case Types.VARBINARY: + case Types.LONGVARBINARY: + case Types.BINARY: + case Types.JAVA_OBJECT: + case Types.OTHER: + case Types.BLOB: + case Types.STRUCT: + case Types.REF: + case Types.NULL: + case Types.ARRAY: + case Types.DATALINK: + case Types.DISTINCT: + throw throwException("Unsupported column data type: " + type); + default: + return ""; + } + } + + /** + * Create an empty search result and initialize the columns. + * + * @param data true if the result set should contain the primary key data as + * an array. + * @return the empty result set + */ + protected static SimpleResultSet createResultSet(boolean data) { + SimpleResultSet result = new SimpleResultSet(); + if (data) { + result.addColumn(FullText.FIELD_SCHEMA, Types.VARCHAR, 0, 0); + result.addColumn(FullText.FIELD_TABLE, Types.VARCHAR, 0, 0); + result.addColumn(FullText.FIELD_COLUMNS, Types.ARRAY, "VARCHAR ARRAY", 0, 0); + result.addColumn(FullText.FIELD_KEYS, Types.ARRAY, "VARCHAR ARRAY", 0, 0); + } else { + result.addColumn(FullText.FIELD_QUERY, Types.VARCHAR, 0, 0); + } + result.addColumn(FullText.FIELD_SCORE, Types.FLOAT, 0, 0); + return result; + } + + /** + * Parse a primary key condition into the primary key columns. + * + * @param conn the database connection + * @param key the primary key condition as a string + * @return an array containing the column name list and the data list + */ + protected static String[][] parseKey(Connection conn, String key) { + ArrayList columns = Utils.newSmallArrayList(); + ArrayList data = Utils.newSmallArrayList(); + JdbcConnection c = (JdbcConnection) conn; + SessionLocal session = (SessionLocal) c.getSession(); + Parser p = new Parser(session); + Expression expr = p.parseExpression(key); + addColumnData(session, columns, data, expr); + String[] col = columns.toArray(new String[0]); + String[] dat = data.toArray(new String[0]); + String[][] columnData = { col, dat }; + return columnData; + } + + /** + * INTERNAL. + * Convert an object to a String as used in a SQL statement. + * + * @param data the object + * @param type the SQL type + * @return the SQL String + * @throws SQLException on failure + */ + protected static String quoteSQL(Object data, int type) throws SQLException { + if (data == null) { + return "NULL"; + } + switch (type) { + case Types.BIT: + case Types.BOOLEAN: + case Types.INTEGER: + case Types.BIGINT: + case Types.DECIMAL: + case Types.DOUBLE: + case Types.FLOAT: + case Types.NUMERIC: + case Types.REAL: + case Types.SMALLINT: + case Types.TINYINT: + return data.toString(); + case Types.DATE: + case Types.TIME: + case Types.TIMESTAMP: + case Types.LONGVARCHAR: + case Types.CHAR: + case Types.VARCHAR: + return quoteString(data.toString()); + case Types.VARBINARY: + case Types.LONGVARBINARY: + case Types.BINARY: + if (data instanceof UUID) { + return "'" + data + "'"; + } + byte[] bytes = (byte[]) data; + StringBuilder builder = new StringBuilder(bytes.length * 2 + 2).append('\''); + StringUtils.convertBytesToHex(builder, bytes).append('\''); + return builder.toString(); + case Types.CLOB: + case Types.JAVA_OBJECT: + case Types.OTHER: + case Types.BLOB: + case Types.STRUCT: + case Types.REF: + case Types.NULL: + case Types.ARRAY: + case Types.DATALINK: + case Types.DISTINCT: + throw throwException("Unsupported key data type: " + type); + default: + return ""; + } + } + + /** + * Remove all triggers that start with the given prefix. + * + * @param conn the database connection + * @param prefix the prefix + * @throws SQLException on failure + */ + protected static void removeAllTriggers(Connection conn, String prefix) + throws SQLException { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery( + "SELECT DISTINCT TRIGGER_SCHEMA, TRIGGER_NAME FROM INFORMATION_SCHEMA.TRIGGERS"); + Statement stat2 = conn.createStatement(); + while (rs.next()) { + String schema = rs.getString("TRIGGER_SCHEMA"); + String name = rs.getString("TRIGGER_NAME"); + if (name.startsWith(prefix)) { + name = StringUtils.quoteIdentifier(schema) + "." + + StringUtils.quoteIdentifier(name); + stat2.execute("DROP TRIGGER " + name); + } + } + } + + /** + * Set the column indices of a set of keys. + * + * @param index the column indices (will be modified) + * @param keys the key list + * @param columns the column list + * @throws SQLException on failure + */ + protected static void setColumns(int[] index, ArrayList keys, + ArrayList columns) throws SQLException { + for (int i = 0, keySize = keys.size(); i < keySize; i++) { + String key = keys.get(i); + int found = -1; + int columnsSize = columns.size(); + for (int j = 0; found == -1 && j < columnsSize; j++) { + String column = columns.get(j); + if (column.equals(key)) { + found = j; + } + } + if (found < 0) { + throw throwException("Column not found: " + key); + } + index[i] = found; + } + } + + /** + * Do the search. + * + * @param conn the database connection + * @param text the query + * @param limit the limit + * @param offset the offset + * @param data whether the raw data should be returned + * @return the result set + * @throws SQLException on failure + */ + protected static ResultSet search(Connection conn, String text, int limit, + int offset, boolean data) throws SQLException { + SimpleResultSet result = createResultSet(data); + if (conn.getMetaData().getURL().startsWith("jdbc:columnlist:")) { + // this is just to query the result set columns + return result; + } + if (text == null || StringUtils.isWhitespaceOrEmpty(text)) { + return result; + } + FullTextSettings setting = FullTextSettings.getInstance(conn); + if (!setting.isInitialized()) { + init(conn); + } + Set words = new HashSet<>(); + addWords(setting, words, text); + Set rIds = null, lastRowIds; + + PreparedStatement prepSelectMapByWordId = setting.prepare(conn, + SELECT_MAP_BY_WORD_ID); + for (String word : words) { + lastRowIds = rIds; + rIds = new HashSet<>(); + Integer wId = setting.getWordId(word); + if (wId == null) { + continue; + } + prepSelectMapByWordId.setInt(1, wId); + ResultSet rs = prepSelectMapByWordId.executeQuery(); + while (rs.next()) { + Integer rId = rs.getInt(1); + if (lastRowIds == null || lastRowIds.contains(rId)) { + rIds.add(rId); + } + } + } + if (rIds == null || rIds.isEmpty()) { + return result; + } + PreparedStatement prepSelectRowById = setting.prepare(conn, SELECT_ROW_BY_ID); + int rowCount = 0; + for (int rowId : rIds) { + prepSelectRowById.setInt(1, rowId); + ResultSet rs = prepSelectRowById.executeQuery(); + if (!rs.next()) { + continue; + } + if (offset > 0) { + offset--; + } else { + String key = rs.getString(1); + int indexId = rs.getInt(2); + IndexInfo index = setting.getIndexInfo(indexId); + if (data) { + String[][] columnData = parseKey(conn, key); + result.addRow( + index.schema, + index.table, + columnData[0], + columnData[1], + 1.0); + } else { + String query = StringUtils.quoteIdentifier(index.schema) + + "." + StringUtils.quoteIdentifier(index.table) + + " WHERE " + key; + result.addRow(query, 1.0); + } + rowCount++; + if (limit > 0 && rowCount >= limit) { + break; + } + } + } + return result; + } + + private static void addColumnData(SessionLocal session, ArrayList columns, ArrayList data, + Expression expr) { + if (expr instanceof ConditionAndOr) { + ConditionAndOr and = (ConditionAndOr) expr; + addColumnData(session, columns, data, and.getSubexpression(0)); + addColumnData(session, columns, data, and.getSubexpression(1)); + } else { + Comparison comp = (Comparison) expr; + ExpressionColumn ec = (ExpressionColumn) comp.getSubexpression(0); + String columnName = ec.getColumnName(session, -1); + columns.add(columnName); + if (expr.getSubexpressionCount() == 1) { + data.add(null); + } else { + ValueExpression ev = (ValueExpression) comp.getSubexpression(1); + data.add(ev.getValue(null).getString()); + } + } + } + + /** + * Add all words in the given text to the hash set. + * + * @param setting the fulltext settings + * @param set the hash set + * @param reader the reader + */ + protected static void addWords(FullTextSettings setting, + Set set, Reader reader) { + StreamTokenizer tokenizer = new StreamTokenizer(reader); + tokenizer.resetSyntax(); + tokenizer.wordChars(' ' + 1, 255); + char[] whitespaceChars = setting.getWhitespaceChars().toCharArray(); + for (char ch : whitespaceChars) { + tokenizer.whitespaceChars(ch, ch); + } + try { + while (true) { + int token = tokenizer.nextToken(); + if (token == StreamTokenizer.TT_EOF) { + break; + } else if (token == StreamTokenizer.TT_WORD) { + String word = tokenizer.sval; + word = setting.convertWord(word); + if (word != null) { + set.add(word); + } + } + } + } catch (IOException e) { + throw DbException.convertIOException(e, "Tokenizer error"); + } + } + + /** + * Add all words in the given text to the hash set. + * + * @param setting the fulltext settings + * @param set the hash set + * @param text the text + */ + protected static void addWords(FullTextSettings setting, + Set set, String text) { + String whitespaceChars = setting.getWhitespaceChars(); + StringTokenizer tokenizer = new StringTokenizer(text, whitespaceChars); + while (tokenizer.hasMoreTokens()) { + String word = tokenizer.nextToken(); + word = setting.convertWord(word); + if (word != null) { + set.add(word); + } + } + } + + /** + * Create the trigger. + * + * @param conn the database connection + * @param schema the schema name + * @param table the table name + * @throws SQLException on failure + */ + private static void createTrigger(Connection conn, String schema, + String table) throws SQLException { + createOrDropTrigger(conn, schema, table, true); + } + + private static void createOrDropTrigger(Connection conn, + String schema, String table, boolean create) throws SQLException { + try (Statement stat = conn.createStatement()) { + String trigger = StringUtils.quoteIdentifier(schema) + "." + + StringUtils.quoteIdentifier(TRIGGER_PREFIX + table); + stat.execute("DROP TRIGGER IF EXISTS " + trigger); + if (create) { + StringBuilder buff = new StringBuilder( + "CREATE TRIGGER IF NOT EXISTS "); + // unless multithread, trigger needs to be called on rollback as well, + // because we use the init connection do to changes in the index + // (not the user connection) + buff.append(trigger). + append(" AFTER INSERT, UPDATE, DELETE"); + buff.append(" ON "); + StringUtils.quoteIdentifier(buff, schema). + append('.'); + StringUtils.quoteIdentifier(buff, table). + append(" FOR EACH ROW CALL \""). + append(FullText.FullTextTrigger.class.getName()). + append('"'); + stat.execute(buff.toString()); + } + } + } + + /** + * Add the existing data to the index. + * + * @param conn the database connection + * @param schema the schema name + * @param table the table name + * @throws SQLException on failure + */ + private static void indexExistingRows(Connection conn, String schema, + String table) throws SQLException { + FullText.FullTextTrigger existing = new FullText.FullTextTrigger(); + existing.init(conn, schema, null, table, false, Trigger.INSERT); + String sql = "SELECT * FROM " + StringUtils.quoteIdentifier(schema) + + "." + StringUtils.quoteIdentifier(table); + ResultSet rs = conn.createStatement().executeQuery(sql); + int columnCount = rs.getMetaData().getColumnCount(); + while (rs.next()) { + Object[] row = new Object[columnCount]; + for (int i = 0; i < columnCount; i++) { + row[i] = rs.getObject(i + 1); + } + existing.fire(conn, null, row); + } + } + + private static String quoteString(String data) { + if (data.indexOf('\'') < 0) { + return "'" + data + "'"; + } + int len = data.length(); + StringBuilder buff = new StringBuilder(len + 2); + buff.append('\''); + for (int i = 0; i < len; i++) { + char ch = data.charAt(i); + if (ch == '\'') { + buff.append(ch); + } + buff.append(ch); + } + buff.append('\''); + return buff.toString(); + } + + private static void setIgnoreList(FullTextSettings setting, + String commaSeparatedList) { + String[] list = StringUtils.arraySplit(commaSeparatedList, ',', true); + setting.addIgnored(Arrays.asList(list)); + } + + /** + * Check if a the indexed columns of a row probably have changed. It may + * return true even if the change was minimal (for example from 0.0 to + * 0.00). + * + * @param oldRow the old row + * @param newRow the new row + * @param indexColumns the indexed columns + * @return true if the indexed columns don't match + */ + protected static boolean hasChanged(Object[] oldRow, Object[] newRow, + int[] indexColumns) { + for (int c : indexColumns) { + Object o = oldRow[c], n = newRow[c]; + if (o == null) { + if (n != null) { + return true; + } + } else if (!o.equals(n)) { + return true; + } + } + return false; + } + + /** + * Trigger updates the index when a inserting, updating, or deleting a row. + */ + public static final class FullTextTrigger implements Trigger { + private FullTextSettings setting; + private IndexInfo index; + private int[] columnTypes; + + private static final int INSERT_WORD = 0; + private static final int INSERT_ROW = 1; + private static final int INSERT_MAP = 2; + private static final int DELETE_ROW = 3; + private static final int DELETE_MAP = 4; + private static final int SELECT_ROW = 5; + + private static final String[] SQL = { + "MERGE INTO " + SCHEMA + ".WORDS(NAME) KEY(NAME) VALUES(?)", + "INSERT INTO " + SCHEMA + ".ROWS(HASH, INDEXID, `KEY`) VALUES(?, ?, ?)", + "INSERT INTO " + SCHEMA + ".MAP(ROWID, WORDID) VALUES(?, ?)", + "DELETE FROM " + SCHEMA + ".ROWS WHERE HASH=? AND INDEXID=? AND `KEY`=?", + "DELETE FROM " + SCHEMA + ".MAP WHERE ROWID=? AND WORDID=?", + "SELECT ID FROM " + SCHEMA + ".ROWS WHERE HASH=? AND INDEXID=? AND `KEY`=?" + }; + + /** + * INTERNAL + * @see Trigger#init(Connection, String, String, String, boolean, int) + */ + @Override + public void init(Connection conn, String schemaName, String triggerName, + String tableName, boolean before, int type) throws SQLException { + setting = FullTextSettings.getInstance(conn); + if (!setting.isInitialized()) { + FullText.init(conn); + } + ArrayList keyList = Utils.newSmallArrayList(); + DatabaseMetaData meta = conn.getMetaData(); + ResultSet rs = meta.getColumns(null, + StringUtils.escapeMetaDataPattern(schemaName), + StringUtils.escapeMetaDataPattern(tableName), + null); + ArrayList columnList = Utils.newSmallArrayList(); + while (rs.next()) { + columnList.add(rs.getString("COLUMN_NAME")); + } + columnTypes = new int[columnList.size()]; + index = new IndexInfo(); + index.schema = schemaName; + index.table = tableName; + index.columns = columnList.toArray(new String[0]); + rs = meta.getColumns(null, + StringUtils.escapeMetaDataPattern(schemaName), + StringUtils.escapeMetaDataPattern(tableName), + null); + for (int i = 0; rs.next(); i++) { + columnTypes[i] = rs.getInt("DATA_TYPE"); + } + if (keyList.isEmpty()) { + rs = meta.getPrimaryKeys(null, + StringUtils.escapeMetaDataPattern(schemaName), + tableName); + while (rs.next()) { + keyList.add(rs.getString("COLUMN_NAME")); + } + } + if (keyList.isEmpty()) { + throw throwException("No primary key for table " + tableName); + } + ArrayList indexList = Utils.newSmallArrayList(); + PreparedStatement prep = conn.prepareStatement( + "SELECT ID, COLUMNS FROM " + SCHEMA + ".INDEXES" + + " WHERE SCHEMA=? AND `TABLE`=?"); + prep.setString(1, schemaName); + prep.setString(2, tableName); + rs = prep.executeQuery(); + if (rs.next()) { + index.id = rs.getInt(1); + String columns = rs.getString(2); + if (columns != null) { + Collections.addAll(indexList, StringUtils.arraySplit(columns, ',', true)); + } + } + if (indexList.isEmpty()) { + indexList.addAll(columnList); + } + index.keys = new int[keyList.size()]; + setColumns(index.keys, keyList, columnList); + index.indexColumns = new int[indexList.size()]; + setColumns(index.indexColumns, indexList, columnList); + setting.addIndexInfo(index); + } + + /** + * INTERNAL + * @see Trigger#fire(Connection, Object[], Object[]) + */ + @Override + public void fire(Connection conn, Object[] oldRow, Object[] newRow) + throws SQLException { + if (oldRow != null) { + if (newRow != null) { + // update + if (hasChanged(oldRow, newRow, index.indexColumns)) { + delete(conn, oldRow); + insert(conn, newRow); + } + } else { + // delete + delete(conn, oldRow); + } + } else if (newRow != null) { + // insert + insert(conn, newRow); + } + } + + /** + * INTERNAL + */ + @Override + public void close() { + setting.removeIndexInfo(index); + } + + /** + * INTERNAL + */ + @Override + public void remove() { + setting.removeIndexInfo(index); + } + + /** + * Add a row to the index. + * + * @param conn to use + * @param row the row + * @throws SQLException on failure + */ + private void insert(Connection conn, Object[] row) throws SQLException { + PreparedStatement prepInsertRow = null; + PreparedStatement prepInsertMap = null; + try { + String key = getKey(row); + int hash = key.hashCode(); + prepInsertRow = getStatement(conn, INSERT_ROW); + prepInsertRow.setInt(1, hash); + prepInsertRow.setInt(2, index.id); + prepInsertRow.setString(3, key); + prepInsertRow.execute(); + ResultSet rs = prepInsertRow.getGeneratedKeys(); + rs.next(); + int rowId = rs.getInt(1); + + prepInsertMap = getStatement(conn, INSERT_MAP); + prepInsertMap.setInt(1, rowId); + int[] wordIds = getWordIds(conn, row); + for (int id : wordIds) { + prepInsertMap.setInt(2, id); + prepInsertMap.execute(); + } + } finally { + IOUtils.closeSilently(prepInsertRow); + IOUtils.closeSilently(prepInsertMap); + } + } + + /** + * Delete a row from the index. + * + * @param conn to use + * @param row the row + * @throws SQLException on failure + */ + private void delete(Connection conn, Object[] row) throws SQLException { + PreparedStatement prepSelectRow = null; + PreparedStatement prepDeleteMap = null; + PreparedStatement prepDeleteRow = null; + try { + String key = getKey(row); + int hash = key.hashCode(); + prepSelectRow = getStatement(conn, SELECT_ROW); + prepSelectRow.setInt(1, hash); + prepSelectRow.setInt(2, index.id); + prepSelectRow.setString(3, key); + ResultSet rs = prepSelectRow.executeQuery(); + prepDeleteMap = getStatement(conn, DELETE_MAP); + prepDeleteRow = getStatement(conn, DELETE_ROW); + if (rs.next()) { + int rowId = rs.getInt(1); + prepDeleteMap.setInt(1, rowId); + int[] wordIds = getWordIds(conn, row); + for (int id : wordIds) { + prepDeleteMap.setInt(2, id); + prepDeleteMap.executeUpdate(); + } + prepDeleteRow.setInt(1, hash); + prepDeleteRow.setInt(2, index.id); + prepDeleteRow.setString(3, key); + prepDeleteRow.executeUpdate(); + } + } finally { + IOUtils.closeSilently(prepSelectRow); + IOUtils.closeSilently(prepDeleteMap); + IOUtils.closeSilently(prepDeleteRow); + } + } + + private int[] getWordIds(Connection conn, Object[] row) throws SQLException { + HashSet words = new HashSet<>(); + for (int idx : index.indexColumns) { + int type = columnTypes[idx]; + Object data = row[idx]; + if (type == Types.CLOB && data != null) { + Reader reader; + if (data instanceof Reader) { + reader = (Reader) data; + } else { + reader = ((Clob) data).getCharacterStream(); + } + addWords(setting, words, reader); + } else { + String string = asString(data, type); + addWords(setting, words, string); + } + } + PreparedStatement prepInsertWord = null; + try { + prepInsertWord = getStatement(conn, INSERT_WORD); + int[] wordIds = new int[words.size()]; + int i = 0; + for (String word : words) { + int wordId; + Integer wId; + while((wId = setting.getWordId(word)) == null) { + prepInsertWord.setString(1, word); + prepInsertWord.execute(); + ResultSet rs = prepInsertWord.getGeneratedKeys(); + if (rs.next()) { + wordId = rs.getInt(1); + if (wordId != 0) { + setting.addWord(word, wordId); + wId = wordId; + break; + } + } + } + wordIds[i++] = wId; + } + Arrays.sort(wordIds); + return wordIds; + } finally { + IOUtils.closeSilently(prepInsertWord); + } + } + + private String getKey(Object[] row) throws SQLException { + StringBuilder builder = new StringBuilder(); + int[] keys = index.keys; + for (int i = 0, l = keys.length; i < l; i++) { + if (i > 0) { + builder.append(" AND "); + } + int columnIndex = keys[i]; + StringUtils.quoteIdentifier(builder, index.columns[columnIndex]); + Object o = row[columnIndex]; + if (o == null) { + builder.append(" IS NULL"); + } else { + builder.append('=').append(quoteSQL(o, columnTypes[columnIndex])); + } + } + return builder.toString(); + } + + private static PreparedStatement getStatement(Connection conn, int index) throws SQLException { + return conn.prepareStatement(SQL[index], Statement.RETURN_GENERATED_KEYS); + } + + } + + /** + * INTERNAL + * Close all fulltext settings, freeing up memory. + */ + public static void closeAll() { + FullTextSettings.closeAll(); + } + + /** + * Throw a SQLException with the given message. + * + * @param message the message + * @return never returns normally + * @throws SQLException the exception + */ + protected static SQLException throwException(String message) + throws SQLException { + throw new SQLException(message, "FULLTEXT"); + } +} diff --git a/h2/src/main/org/h2/fulltext/FullTextLucene.java b/h2/src/main/org/h2/fulltext/FullTextLucene.java new file mode 100644 index 0000000..802563c --- /dev/null +++ b/h2/src/main/org/h2/fulltext/FullTextLucene.java @@ -0,0 +1,764 @@ +/* + * 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.fulltext; + +import java.io.IOException; +import java.nio.file.Paths; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; + +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.standard.StandardAnalyzer; +import org.apache.lucene.document.DateTools; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.document.FieldType; +import org.apache.lucene.document.TextField; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexFormatTooOldException; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.index.Term; +import org.apache.lucene.queryparser.flexible.standard.StandardQueryParser; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.TopDocs; +import org.apache.lucene.store.ByteBuffersDirectory; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.FSDirectory; +import org.h2.api.Trigger; +import org.h2.command.Parser; +import org.h2.engine.SessionLocal; +import org.h2.expression.ExpressionColumn; +import org.h2.jdbc.JdbcConnection; +import org.h2.store.fs.FileUtils; +import org.h2.tools.SimpleResultSet; +import org.h2.util.StringUtils; +import org.h2.util.Utils; + +/** + * This class implements the full text search based on Apache Lucene. + * Most methods can be called using SQL statements as well. + */ +public class FullTextLucene extends FullText { + + /** + * Whether the text content should be stored in the Lucene index. + */ + protected static final boolean STORE_DOCUMENT_TEXT_IN_INDEX = + Utils.getProperty("h2.storeDocumentTextInIndex", false); + + private static final HashMap INDEX_ACCESS = new HashMap<>(); + private static final String TRIGGER_PREFIX = "FTL_"; + private static final String SCHEMA = "FTL"; + private static final String LUCENE_FIELD_DATA = "_DATA"; + private static final String LUCENE_FIELD_QUERY = "_QUERY"; + private static final String LUCENE_FIELD_MODIFIED = "_modified"; + private static final String LUCENE_FIELD_COLUMN_PREFIX = "_"; + + /** + * The prefix for a in-memory path. This prefix is only used internally + * within this class and not related to the database URL. + */ + private static final String IN_MEMORY_PREFIX = "mem:"; + + /** + * Initializes full text search functionality for this database. This adds + * the following Java functions to the database: + *
    + *
  • FTL_CREATE_INDEX(schemaNameString, tableNameString, + * columnListString)
  • + *
  • FTL_SEARCH(queryString, limitInt, offsetInt): result set
  • + *
  • FTL_REINDEX()
  • + *
  • FTL_DROP_ALL()
  • + *
+ * It also adds a schema FTL to the database where bookkeeping information + * is stored. This function may be called from a Java application, or by + * using the SQL statements: + * + *
+     * CREATE ALIAS IF NOT EXISTS FTL_INIT FOR
+     *      "org.h2.fulltext.FullTextLucene.init";
+     * CALL FTL_INIT();
+     * 
+ * + * @param conn the connection + * @throws SQLException on failure + */ + public static void init(Connection conn) throws SQLException { + try (Statement stat = conn.createStatement()) { + stat.execute("CREATE SCHEMA IF NOT EXISTS " + SCHEMA); + stat.execute("CREATE TABLE IF NOT EXISTS " + SCHEMA + + ".INDEXES(SCHEMA VARCHAR, `TABLE` VARCHAR, " + + "COLUMNS VARCHAR, PRIMARY KEY(SCHEMA, `TABLE`))"); + String className = FullTextLucene.class.getName(); + stat.execute("CREATE ALIAS IF NOT EXISTS FTL_CREATE_INDEX FOR '" + className + ".createIndex'"); + stat.execute("CREATE ALIAS IF NOT EXISTS FTL_DROP_INDEX FOR '" + className + ".dropIndex'"); + stat.execute("CREATE ALIAS IF NOT EXISTS FTL_SEARCH FOR '" + className + ".search'"); + stat.execute("CREATE ALIAS IF NOT EXISTS FTL_SEARCH_DATA FOR '" + className + ".searchData'"); + stat.execute("CREATE ALIAS IF NOT EXISTS FTL_REINDEX FOR '" + className + ".reindex'"); + stat.execute("CREATE ALIAS IF NOT EXISTS FTL_DROP_ALL FOR '" + className + ".dropAll'"); + } + } + + /** + * Create a new full text index for a table and column list. Each table may + * only have one index at any time. + * + * @param conn the connection + * @param schema the schema name of the table (case sensitive) + * @param table the table name (case sensitive) + * @param columnList the column list (null for all columns) + * @throws SQLException on failure + */ + public static void createIndex(Connection conn, String schema, + String table, String columnList) throws SQLException { + init(conn); + PreparedStatement prep = conn.prepareStatement("INSERT INTO " + SCHEMA + + ".INDEXES(SCHEMA, `TABLE`, COLUMNS) VALUES(?, ?, ?)"); + prep.setString(1, schema); + prep.setString(2, table); + prep.setString(3, columnList); + prep.execute(); + createTrigger(conn, schema, table); + indexExistingRows(conn, schema, table); + } + + /** + * Drop an existing full text index for a table. This method returns + * silently if no index for this table exists. + * + * @param conn the connection + * @param schema the schema name of the table (case sensitive) + * @param table the table name (case sensitive) + * @throws SQLException on failure + */ + public static void dropIndex(Connection conn, String schema, String table) + throws SQLException { + init(conn); + + PreparedStatement prep = conn.prepareStatement("DELETE FROM " + SCHEMA + + ".INDEXES WHERE SCHEMA=? AND `TABLE`=?"); + prep.setString(1, schema); + prep.setString(2, table); + int rowCount = prep.executeUpdate(); + if (rowCount != 0) { + reindex(conn); + } + } + + /** + * Re-creates the full text index for this database. Calling this method is + * usually not needed, as the index is kept up-to-date automatically. + * + * @param conn the connection + * @throws SQLException on failure + */ + public static void reindex(Connection conn) throws SQLException { + init(conn); + removeAllTriggers(conn, TRIGGER_PREFIX); + removeIndexFiles(conn); + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("SELECT * FROM " + SCHEMA + ".INDEXES"); + while (rs.next()) { + String schema = rs.getString("SCHEMA"); + String table = rs.getString("TABLE"); + createTrigger(conn, schema, table); + indexExistingRows(conn, schema, table); + } + } + + /** + * Drops all full text indexes from the database. + * + * @param conn the connection + * @throws SQLException on failure + */ + public static void dropAll(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("DROP SCHEMA IF EXISTS " + SCHEMA + " CASCADE"); + removeAllTriggers(conn, TRIGGER_PREFIX); + removeIndexFiles(conn); + } + + /** + * Searches from the full text index for this database. + * The returned result set has the following column: + *
  • QUERY (varchar): the query to use to get the data. + * The query does not include 'SELECT * FROM '. Example: + * PUBLIC.TEST WHERE ID = 1 + *
  • SCORE (float) the relevance score as returned by Lucene. + *
+ * + * @param conn the connection + * @param text the search query + * @param limit the maximum number of rows or 0 for no limit + * @param offset the offset or 0 for no offset + * @return the result set + * @throws SQLException on failure + */ + public static ResultSet search(Connection conn, String text, int limit, + int offset) throws SQLException { + return search(conn, text, limit, offset, false); + } + + /** + * Searches from the full text index for this database. The result contains + * the primary key data as an array. The returned result set has the + * following columns: + *
    + *
  • SCHEMA (varchar): the schema name. Example: PUBLIC
  • + *
  • TABLE (varchar): the table name. Example: TEST
  • + *
  • COLUMNS (array of varchar): comma separated list of quoted column + * names. The column names are quoted if necessary. Example: (ID)
  • + *
  • KEYS (array of values): comma separated list of values. + * Example: (1)
  • + *
  • SCORE (float) the relevance score as returned by Lucene.
  • + *
+ * + * @param conn the connection + * @param text the search query + * @param limit the maximum number of rows or 0 for no limit + * @param offset the offset or 0 for no offset + * @return the result set + * @throws SQLException on failure + */ + public static ResultSet searchData(Connection conn, String text, int limit, + int offset) throws SQLException { + return search(conn, text, limit, offset, true); + } + + /** + * Convert an exception to a fulltext exception. + * + * @param e the original exception + * @return the converted SQL exception + */ + protected static SQLException convertException(Exception e) { + return new SQLException("Error while indexing document", "FULLTEXT", e); + } + + /** + * Create the trigger. + * + * @param conn the database connection + * @param schema the schema name + * @param table the table name + * @throws SQLException on failure + */ + private static void createTrigger(Connection conn, String schema, + String table) throws SQLException { + createOrDropTrigger(conn, schema, table, true); + } + + private static void createOrDropTrigger(Connection conn, + String schema, String table, boolean create) throws SQLException { + Statement stat = conn.createStatement(); + String trigger = StringUtils.quoteIdentifier(schema) + "." + + StringUtils.quoteIdentifier(TRIGGER_PREFIX + table); + stat.execute("DROP TRIGGER IF EXISTS " + trigger); + if (create) { + StringBuilder builder = new StringBuilder( + "CREATE TRIGGER IF NOT EXISTS "); + // the trigger is also called on rollback because transaction + // rollback will not undo the changes in the Lucene index + builder.append(trigger). + append(" AFTER INSERT, UPDATE, DELETE, ROLLBACK ON "); + StringUtils.quoteIdentifier(builder, schema). + append('.'); + StringUtils.quoteIdentifier(builder, table). + append(" FOR EACH ROW CALL \""). + append(FullTextLucene.FullTextTrigger.class.getName()). + append('\"'); + stat.execute(builder.toString()); + } + } + + /** + * Get the index writer/searcher wrapper for the given connection. + * + * @param conn the connection + * @return the index access wrapper + * @throws SQLException on failure + */ + protected static IndexAccess getIndexAccess(Connection conn) + throws SQLException { + String path = getIndexPath(conn); + synchronized (INDEX_ACCESS) { + IndexAccess access = INDEX_ACCESS.get(path); + while (access == null) { + try { + Directory indexDir = path.startsWith(IN_MEMORY_PREFIX) ? + new ByteBuffersDirectory() : FSDirectory.open(Paths.get(path)); + Analyzer analyzer = new StandardAnalyzer(); + IndexWriterConfig conf = new IndexWriterConfig(analyzer); + conf.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND); + IndexWriter writer = new IndexWriter(indexDir, conf); + //see https://cwiki.apache.org/confluence/display/lucene/NearRealtimeSearch + access = new IndexAccess(writer); + } catch (IndexFormatTooOldException e) { + reindex(conn); + continue; + } catch (IOException e) { + throw convertException(e); + } + INDEX_ACCESS.put(path, access); + break; + } + return access; + } + } + + /** + * Get the path of the Lucene index for this database. + * + * @param conn the database connection + * @return the path + * @throws SQLException on failure + */ + protected static String getIndexPath(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("CALL DATABASE_PATH()"); + rs.next(); + String path = rs.getString(1); + if (path == null) { + return IN_MEMORY_PREFIX + conn.getCatalog(); + } + int index = path.lastIndexOf(':'); + // position 1 means a windows drive letter is used, ignore that + if (index > 1) { + path = path.substring(index + 1); + } + rs.close(); + return path; + } + + /** + * Add the existing data to the index. + * + * @param conn the database connection + * @param schema the schema name + * @param table the table name + * @throws SQLException on failure + */ + private static void indexExistingRows(Connection conn, String schema, + String table) throws SQLException { + FullTextLucene.FullTextTrigger existing = new FullTextLucene.FullTextTrigger(); + existing.init(conn, schema, null, table, false, Trigger.INSERT); + String sql = "SELECT * FROM " + StringUtils.quoteIdentifier(schema) + + "." + StringUtils.quoteIdentifier(table); + ResultSet rs = conn.createStatement().executeQuery(sql); + int columnCount = rs.getMetaData().getColumnCount(); + while (rs.next()) { + Object[] row = new Object[columnCount]; + for (int i = 0; i < columnCount; i++) { + row[i] = rs.getObject(i + 1); + } + existing.insert(row, false); + } + existing.commitIndex(); + } + + private static void removeIndexFiles(Connection conn) throws SQLException { + String path = getIndexPath(conn); + removeIndexAccess(path); + if (!path.startsWith(IN_MEMORY_PREFIX)) { + FileUtils.deleteRecursive(path, false); + } + } + + /** + * Close the index writer and searcher and remove them from the index access + * set. + * + * @param indexPath the index path + * @throws SQLException on failure + */ + protected static void removeIndexAccess(String indexPath) + throws SQLException { + synchronized (INDEX_ACCESS) { + try { + IndexAccess access = INDEX_ACCESS.remove(indexPath); + if(access != null) { + access.close(); + } + } catch (Exception e) { + throw convertException(e); + } + } + } + + /** + * Do the search. + * + * @param conn the database connection + * @param text the query + * @param limit the limit + * @param offset the offset + * @param data whether the raw data should be returned + * @return the result set + * @throws SQLException on failure + */ + protected static ResultSet search(Connection conn, String text, + int limit, int offset, boolean data) throws SQLException { + SimpleResultSet result = createResultSet(data); + if (conn.getMetaData().getURL().startsWith("jdbc:columnlist:")) { + // this is just to query the result set columns + return result; + } + if (text == null || StringUtils.isWhitespaceOrEmpty(text)) { + return result; + } + try { + IndexAccess access = getIndexAccess(conn); + // take a reference as the searcher may change + IndexSearcher searcher = access.getSearcher(); + try { + // reuse the same analyzer; it's thread-safe; + // also allows subclasses to control the analyzer used. + Analyzer analyzer = access.writer.getAnalyzer(); + StandardQueryParser parser = new StandardQueryParser(analyzer); + Query query = parser.parse(text, LUCENE_FIELD_DATA); + // Lucene insists on a hard limit and will not provide + // a total hits value. Take at least 100 which is + // an optimal limit for Lucene as any more + // will trigger writing results to disk. + int maxResults = (limit == 0 ? 100 : limit) + offset; + TopDocs docs = searcher.search(query, maxResults); + long totalHits = docs.totalHits.value; + if (limit == 0) { + // in this context it's safe to cast + limit = (int) totalHits; + } + for (int i = 0, len = docs.scoreDocs.length; i < limit + && i + offset < totalHits + && i + offset < len; i++) { + ScoreDoc sd = docs.scoreDocs[i + offset]; + Document doc = searcher.doc(sd.doc); + float score = sd.score; + String q = doc.get(LUCENE_FIELD_QUERY); + if (data) { + int idx = q.indexOf(" WHERE "); + JdbcConnection c = (JdbcConnection) conn; + SessionLocal session = (SessionLocal) c.getSession(); + Parser p = new Parser(session); + String tab = q.substring(0, idx); + ExpressionColumn expr = (ExpressionColumn) p + .parseExpression(tab); + String schemaName = expr.getOriginalTableAliasName(); + String tableName = expr.getColumnName(session, -1); + q = q.substring(idx + " WHERE ".length()); + String[][] columnData = parseKey(conn, q); + result.addRow(schemaName, tableName, columnData[0], + columnData[1], score); + } else { + result.addRow(q, score); + } + } + } finally { + access.returnSearcher(searcher); + } + } catch (Exception e) { + throw convertException(e); + } + return result; + } + + /** + * Trigger updates the index when a inserting, updating, or deleting a row. + */ + public static final class FullTextTrigger implements Trigger { + + private String schema; + private String table; + private int[] keys; + private int[] indexColumns; + private String[] columns; + private int[] columnTypes; + private String indexPath; + private IndexAccess indexAccess; + + private final FieldType DOC_ID_FIELD_TYPE; + + public FullTextTrigger() { + DOC_ID_FIELD_TYPE = new FieldType(TextField.TYPE_STORED); + DOC_ID_FIELD_TYPE.setTokenized(false); + DOC_ID_FIELD_TYPE.freeze(); + } + + /** + * INTERNAL + * @see Trigger#init(Connection, String, String, String, boolean, int) + */ + @Override + public void init(Connection conn, String schemaName, String triggerName, + String tableName, boolean before, int type) throws SQLException { + this.schema = schemaName; + this.table = tableName; + this.indexPath = getIndexPath(conn); + this.indexAccess = getIndexAccess(conn); + ArrayList keyList = Utils.newSmallArrayList(); + DatabaseMetaData meta = conn.getMetaData(); + ResultSet rs = meta.getColumns(null, + StringUtils.escapeMetaDataPattern(schemaName), + StringUtils.escapeMetaDataPattern(tableName), + null); + ArrayList columnList = Utils.newSmallArrayList(); + while (rs.next()) { + columnList.add(rs.getString("COLUMN_NAME")); + } + columnTypes = new int[columnList.size()]; + columns = columnList.toArray(new String[0]); + rs = meta.getColumns(null, + StringUtils.escapeMetaDataPattern(schemaName), + StringUtils.escapeMetaDataPattern(tableName), + null); + for (int i = 0; rs.next(); i++) { + columnTypes[i] = rs.getInt("DATA_TYPE"); + } + if (keyList.isEmpty()) { + rs = meta.getPrimaryKeys(null, + StringUtils.escapeMetaDataPattern(schemaName), + tableName); + while (rs.next()) { + keyList.add(rs.getString("COLUMN_NAME")); + } + } + if (keyList.isEmpty()) { + throw throwException("No primary key for table " + tableName); + } + ArrayList indexList = Utils.newSmallArrayList(); + PreparedStatement prep = conn.prepareStatement( + "SELECT COLUMNS FROM " + SCHEMA + + ".INDEXES WHERE SCHEMA=? AND `TABLE`=?"); + prep.setString(1, schemaName); + prep.setString(2, tableName); + rs = prep.executeQuery(); + if (rs.next()) { + String cols = rs.getString(1); + if (cols != null) { + Collections.addAll(indexList, + StringUtils.arraySplit(cols, ',', true)); + } + } + if (indexList.isEmpty()) { + indexList.addAll(columnList); + } + keys = new int[keyList.size()]; + setColumns(keys, keyList, columnList); + indexColumns = new int[indexList.size()]; + setColumns(indexColumns, indexList, columnList); + } + + /** + * INTERNAL + * @see Trigger#fire(Connection, Object[], Object[]) + */ + @Override + public void fire(Connection conn, Object[] oldRow, Object[] newRow) + throws SQLException { + if (oldRow != null) { + if (newRow != null) { + // update + if (hasChanged(oldRow, newRow, indexColumns)) { + delete(oldRow, false); + insert(newRow, true); + } + } else { + // delete + delete(oldRow, true); + } + } else if (newRow != null) { + // insert + insert(newRow, true); + } + } + + /** + * INTERNAL + */ + @Override + public void close() throws SQLException { + removeIndexAccess(indexPath); + } + + /** + * Commit all changes to the Lucene index. + * @throws SQLException on failure + */ + void commitIndex() throws SQLException { + try { + indexAccess.commit(); + } catch (IOException e) { + throw convertException(e); + } + } + + /** + * Add a row to the index. + * + * @param row the row + * @param commitIndex whether to commit the changes to the Lucene index + * @throws SQLException on failure + */ + void insert(Object[] row, boolean commitIndex) throws SQLException { + String query = getQuery(row); + Document doc = new Document(); + doc.add(new Field(LUCENE_FIELD_QUERY, query, DOC_ID_FIELD_TYPE)); + long time = System.currentTimeMillis(); + doc.add(new Field(LUCENE_FIELD_MODIFIED, + DateTools.timeToString(time, DateTools.Resolution.SECOND), + TextField.TYPE_STORED)); + StringBuilder builder = new StringBuilder(); + for (int i = 0, length = indexColumns.length; i < length; i++) { + int index = indexColumns[i]; + String columnName = columns[index]; + String data = asString(row[index], columnTypes[index]); + // column names that start with _ + // must be escaped to avoid conflicts + // with internal field names (_DATA, _QUERY, _modified) + if (columnName.startsWith(LUCENE_FIELD_COLUMN_PREFIX)) { + columnName = LUCENE_FIELD_COLUMN_PREFIX + columnName; + } + doc.add(new Field(columnName, data, TextField.TYPE_NOT_STORED)); + if (i > 0) { + builder.append(' '); + } + builder.append(data); + } + FieldType dataFieldType = STORE_DOCUMENT_TEXT_IN_INDEX ? + TextField.TYPE_STORED : TextField.TYPE_NOT_STORED; + doc.add(new Field(LUCENE_FIELD_DATA, builder.toString(), dataFieldType)); + try { + indexAccess.writer.addDocument(doc); + if (commitIndex) { + commitIndex(); + } + } catch (IOException e) { + throw convertException(e); + } + } + + /** + * Delete a row from the index. + * + * @param row the row + * @param commitIndex whether to commit the changes to the Lucene index + * @throws SQLException on failure + */ + private void delete(Object[] row, boolean commitIndex) throws SQLException { + String query = getQuery(row); + try { + Term term = new Term(LUCENE_FIELD_QUERY, query); + indexAccess.writer.deleteDocuments(term); + if (commitIndex) { + commitIndex(); + } + } catch (IOException e) { + throw convertException(e); + } + } + + private String getQuery(Object[] row) throws SQLException { + StringBuilder builder = new StringBuilder(); + if (schema != null) { + StringUtils.quoteIdentifier(builder, schema).append('.'); + } + StringUtils.quoteIdentifier(builder, table).append(" WHERE "); + for (int i = 0, length = keys.length; i < length; i++) { + if (i > 0) { + builder.append(" AND "); + } + int columnIndex = keys[i]; + StringUtils.quoteIdentifier(builder, columns[columnIndex]); + Object o = row[columnIndex]; + if (o == null) { + builder.append(" IS NULL"); + } else { + builder.append('=').append(FullText.quoteSQL(o, columnTypes[columnIndex])); + } + } + return builder.toString(); + } + } + + /** + * A wrapper for the Lucene writer and searcher. + */ + private static final class IndexAccess { + + /** + * The index writer. + */ + final IndexWriter writer; + + /** + * The index searcher. + */ + private IndexSearcher searcher; + + IndexAccess(IndexWriter writer) throws IOException { + this.writer = writer; + initializeSearcher(); + } + + /** + * Start using the searcher. + * + * @return the searcher + * @throws IOException on failure + */ + synchronized IndexSearcher getSearcher() throws IOException { + if (!searcher.getIndexReader().tryIncRef()) { + initializeSearcher(); + } + return searcher; + } + + private void initializeSearcher() throws IOException { + IndexReader reader = DirectoryReader.open(writer); + searcher = new IndexSearcher(reader); + } + + /** + * Stop using the searcher. + * + * @param searcher the searcher + * @throws IOException on failure + */ + synchronized void returnSearcher(IndexSearcher searcher) throws IOException { + searcher.getIndexReader().decRef(); + } + + /** + * Commit the changes. + * @throws IOException on failure + */ + public synchronized void commit() throws IOException { + writer.commit(); + returnSearcher(searcher); + searcher = new IndexSearcher(DirectoryReader.open(writer)); + } + + /** + * Close the index. + * @throws IOException on failure + */ + public synchronized void close() throws IOException { + searcher = null; + writer.close(); + } + } +} diff --git a/h2/src/main/org/h2/fulltext/FullTextSettings.java b/h2/src/main/org/h2/fulltext/FullTextSettings.java new file mode 100644 index 0000000..8b867e4 --- /dev/null +++ b/h2/src/main/org/h2/fulltext/FullTextSettings.java @@ -0,0 +1,278 @@ +/* + * 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.fulltext; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.HashMap; +import java.util.HashSet; +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; + +import org.h2.util.SoftValuesHashMap; + +/** + * The global settings of a full text search. + */ +final class FullTextSettings { + + /** + * The settings of open indexes. + */ + private static final HashMap SETTINGS = new HashMap<>(); + + /** + * Whether this instance has been initialized. + */ + private boolean initialized; + + /** + * The set of words not to index (stop words). + */ + private final HashSet ignoreList = new HashSet<>(); + + /** + * The set of words / terms. + */ + private final HashMap words = new HashMap<>(); + + /** + * The set of indexes in this database. + */ + private final ConcurrentHashMap indexes = new ConcurrentHashMap<>(); + + /** + * The prepared statement cache. + */ + private final WeakHashMap> cache = new WeakHashMap<>(); + + /** + * The whitespace characters. + */ + private String whitespaceChars = " \t\n\r\f+\"*%&/()=?'!,.;:-_#@|^~`{}[]<>\\"; + + /** + * Create a new instance. + */ + private FullTextSettings() { + // don't allow construction + } + + /** + * Clear set of ignored words + */ + public void clearIgnored() { + synchronized (ignoreList) { + ignoreList.clear(); + } + } + + /** + * Amend set of ignored words + * @param words to add + */ + public void addIgnored(Iterable words) { + synchronized (ignoreList) { + for (String word : words) { + word = normalizeWord(word); + ignoreList.add(word); + } + } + } + + /** + * Clear set of searchable words + */ + public void clearWordList() { + synchronized (words) { + words.clear(); + } + } + + /** + * Get id for a searchable word + * @param word to find id for + * @return Integer id or null if word is not found + */ + public Integer getWordId(String word) { + synchronized (words) { + return words.get(word); + } + } + + /** + * Register searchable word + * @param word to register + * @param id to register with + */ + public void addWord(String word, Integer id) { + synchronized (words) { + words.putIfAbsent(word, id); + } + } + + /** + * Get the index information for the given index id. + * + * @param indexId the index id + * @return the index info + */ + IndexInfo getIndexInfo(int indexId) { + return indexes.get(indexId); + } + + /** + * Add an index. + * + * @param index the index + */ + void addIndexInfo(IndexInfo index) { + indexes.put(index.id, index); + } + + /** + * Convert a word to uppercase. This method returns null if the word is in + * the ignore list. + * + * @param word the word to convert and check + * @return the uppercase version of the word or null + */ + String convertWord(String word) { + word = normalizeWord(word); + synchronized (ignoreList) { + if (ignoreList.contains(word)) { + return null; + } + } + return word; + } + + /** + * Get or create the fulltext settings for this database. + * + * @param conn the connection + * @return the settings + * @throws SQLException on failure + */ + static FullTextSettings getInstance(Connection conn) + throws SQLException { + String path = getIndexPath(conn); + FullTextSettings setting; + synchronized (SETTINGS) { + setting = SETTINGS.get(path); + if (setting == null) { + setting = new FullTextSettings(); + SETTINGS.put(path, setting); + } + } + return setting; + } + + /** + * Get the file system path. + * + * @param conn the connection + * @return the file system path + */ + private static String getIndexPath(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery( + "CALL COALESCE(DATABASE_PATH(), 'MEM:' || DATABASE())"); + rs.next(); + String path = rs.getString(1); + if ("MEM:UNNAMED".equals(path)) { + throw FullText.throwException( + "Fulltext search for private (unnamed) " + + "in-memory databases is not supported."); + } + rs.close(); + return path; + } + + /** + * Prepare a statement. The statement is cached in a soft reference cache. + * + * @param conn the connection + * @param sql the statement + * @return the prepared statement + * @throws SQLException on failure + */ + synchronized PreparedStatement prepare(Connection conn, String sql) throws SQLException { + SoftValuesHashMap c = cache.get(conn); + if (c == null) { + c = new SoftValuesHashMap<>(); + cache.put(conn, c); + } + PreparedStatement prep = c.get(sql); + if (prep != null && prep.getConnection().isClosed()) { + prep = null; + } + if (prep == null) { + prep = conn.prepareStatement(sql); + c.put(sql, prep); + } + return prep; + } + + /** + * Remove all indexes from the settings. + */ + void removeAllIndexes() { + indexes.clear(); + } + + /** + * Remove an index from the settings. + * + * @param index the index to remove + */ + void removeIndexInfo(IndexInfo index) { + indexes.remove(index.id); + } + + /** + * Set the initialized flag. + * + * @param b the new value + */ + void setInitialized(boolean b) { + this.initialized = b; + } + + /** + * Get the initialized flag. + * + * @return whether this instance is initialized + */ + boolean isInitialized() { + return initialized; + } + + /** + * Close all fulltext settings, freeing up memory. + */ + static void closeAll() { + synchronized (SETTINGS) { + SETTINGS.clear(); + } + } + + void setWhitespaceChars(String whitespaceChars) { + this.whitespaceChars = whitespaceChars; + } + + String getWhitespaceChars() { + return whitespaceChars; + } + + private static String normalizeWord(String word) { + // TODO this is locale specific, document + return word.toUpperCase(); + } +} diff --git a/h2/src/main/org/h2/fulltext/IndexInfo.java b/h2/src/main/org/h2/fulltext/IndexInfo.java new file mode 100644 index 0000000..22c5498 --- /dev/null +++ b/h2/src/main/org/h2/fulltext/IndexInfo.java @@ -0,0 +1,42 @@ +/* + * 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.fulltext; + +/** + * The settings of one full text search index. + */ +public class IndexInfo { + + /** + * The index id. + */ + protected int id; + + /** + * The schema name. + */ + protected String schema; + + /** + * The table name. + */ + protected String table; + + /** + * The column indexes of the key columns. + */ + protected int[] keys; + + /** + * The column indexes of the index columns. + */ + protected int[] indexColumns; + + /** + * The column names. + */ + protected String[] columns; +} diff --git a/h2/src/main/org/h2/fulltext/package.html b/h2/src/main/org/h2/fulltext/package.html new file mode 100644 index 0000000..d3c0462 --- /dev/null +++ b/h2/src/main/org/h2/fulltext/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +The native full text search implementation, and the wrapper for the Lucene full text search implementation. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/index/Cursor.java b/h2/src/main/org/h2/index/Cursor.java new file mode 100644 index 0000000..a8e768a --- /dev/null +++ b/h2/src/main/org/h2/index/Cursor.java @@ -0,0 +1,53 @@ +/* + * 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.index; + +import org.h2.result.Row; +import org.h2.result.SearchRow; + +/** + * A cursor is a helper object to iterate through an index. + * For indexes are sorted (such as the b tree index), it can iterate + * to the very end of the index. For other indexes that don't support + * that (such as a hash index), only one row is returned. + * The cursor is initially positioned before the first row, that means + * next() must be called before accessing data. + * + */ +public interface Cursor { + + /** + * Get the complete current row. + * All column are available. + * + * @return the complete row + */ + Row get(); + + /** + * Get the current row. + * Only the data for indexed columns is available in this row. + * + * @return the search row + */ + SearchRow getSearchRow(); + + /** + * Skip to the next row if one is available. + * + * @return true if another row is available + */ + boolean next(); + + /** + * Skip to the previous row if one is available. + * No filtering is made here. + * + * @return true if another row is available + */ + boolean previous(); + +} diff --git a/h2/src/main/org/h2/index/DualCursor.java b/h2/src/main/org/h2/index/DualCursor.java new file mode 100644 index 0000000..e49a8bc --- /dev/null +++ b/h2/src/main/org/h2/index/DualCursor.java @@ -0,0 +1,48 @@ +/* + * 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.index; + +import org.h2.message.DbException; +import org.h2.result.Row; +import org.h2.result.SearchRow; +import org.h2.value.Value; + +/** + * The cursor implementation for the DUAL index. + */ +class DualCursor implements Cursor { + + private Row currentRow; + + DualCursor() { + } + + @Override + public Row get() { + return currentRow; + } + + @Override + public SearchRow getSearchRow() { + return currentRow; + } + + @Override + public boolean next() { + if (currentRow == null) { + currentRow = Row.get(Value.EMPTY_VALUES, 1); + return true; + } else { + return false; + } + } + + @Override + public boolean previous() { + throw DbException.getInternalError(toString()); + } + +} diff --git a/h2/src/main/org/h2/index/DualIndex.java b/h2/src/main/org/h2/index/DualIndex.java new file mode 100644 index 0000000..74539c4 --- /dev/null +++ b/h2/src/main/org/h2/index/DualIndex.java @@ -0,0 +1,58 @@ +/* + * 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.index; + +import org.h2.command.query.AllColumnsForPlan; +import org.h2.engine.SessionLocal; +import org.h2.result.Row; +import org.h2.result.SearchRow; +import org.h2.result.SortOrder; +import org.h2.table.DualTable; +import org.h2.table.IndexColumn; +import org.h2.table.TableFilter; +import org.h2.value.Value; + +/** + * An index for the DUAL table. + */ +public class DualIndex extends VirtualTableIndex { + + public DualIndex(DualTable table) { + super(table, "DUAL_INDEX", new IndexColumn[0]); + } + + @Override + public Cursor find(SessionLocal session, SearchRow first, SearchRow last) { + return new DualCursor(); + } + + @Override + public double getCost(SessionLocal session, int[] masks, TableFilter[] filters, int filter, SortOrder sortOrder, + AllColumnsForPlan allColumnsSet) { + return 1d; + } + + @Override + public String getCreateSQL() { + return null; + } + + @Override + public boolean canGetFirstOrLast() { + return true; + } + + @Override + public Cursor findFirstOrLast(SessionLocal session, boolean first) { + return new SingleRowCursor(Row.get(Value.EMPTY_VALUES, 1)); + } + + @Override + public String getPlanSQL() { + return "dual index"; + } + +} diff --git a/h2/src/main/org/h2/index/Index.java b/h2/src/main/org/h2/index/Index.java new file mode 100644 index 0000000..b0104db --- /dev/null +++ b/h2/src/main/org/h2/index/Index.java @@ -0,0 +1,743 @@ +/* + * 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.index; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.h2.api.ErrorCode; +import org.h2.command.query.AllColumnsForPlan; +import org.h2.engine.Constants; +import org.h2.engine.DbObject; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.result.Row; +import org.h2.result.RowFactory; +import org.h2.result.SearchRow; +import org.h2.result.SortOrder; +import org.h2.schema.SchemaObject; +import org.h2.table.Column; +import org.h2.table.IndexColumn; +import org.h2.table.Table; +import org.h2.table.TableFilter; +import org.h2.util.StringUtils; +import org.h2.value.CompareMode; +import org.h2.value.DataType; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * An index. Indexes are used to speed up searching data. + */ +public abstract class Index extends SchemaObject { + + /** + * Check that the index columns are not CLOB or BLOB. + * + * @param columns the columns + */ + protected static void checkIndexColumnTypes(IndexColumn[] columns) { + for (IndexColumn c : columns) { + if (!DataType.isIndexable(c.column.getType())) { + throw DbException.getUnsupportedException("Index on column: " + c.column.getCreateSQL()); + } + } + } + + /** + * Columns of this index. + */ + protected IndexColumn[] indexColumns; + + /** + * Table columns used in this index. + */ + protected Column[] columns; + + /** + * Identities of table columns. + */ + protected int[] columnIds; + + /** + * Count of unique columns. Unique columns, if any, are always first columns + * in the lists. + */ + protected final int uniqueColumnColumn; + + /** + * The table. + */ + protected final Table table; + + /** + * The index type. + */ + protected final IndexType indexType; + + private final RowFactory rowFactory; + + private final RowFactory uniqueRowFactory; + + /** + * Initialize the index. + * + * @param newTable the table + * @param id the object id + * @param name the index name + * @param newIndexColumns the columns that are indexed or null if this is + * not yet known + * @param uniqueColumnCount count of unique columns + * @param newIndexType the index type + */ + protected Index(Table newTable, int id, String name, IndexColumn[] newIndexColumns, int uniqueColumnCount, + IndexType newIndexType) { + super(newTable.getSchema(), id, name, Trace.INDEX); + this.uniqueColumnColumn = uniqueColumnCount; + this.indexType = newIndexType; + this.table = newTable; + if (newIndexColumns != null) { + this.indexColumns = newIndexColumns; + columns = new Column[newIndexColumns.length]; + int len = columns.length; + columnIds = new int[len]; + for (int i = 0; i < len; i++) { + Column col = newIndexColumns[i].column; + columns[i] = col; + columnIds[i] = col.getColumnId(); + } + } + RowFactory databaseRowFactory = database.getRowFactory(); + CompareMode compareMode = database.getCompareMode(); + Column[] tableColumns = table.getColumns(); + rowFactory = databaseRowFactory.createRowFactory(database, compareMode, database, tableColumns, + newIndexType.isScan() ? null : newIndexColumns, true); + RowFactory uniqueRowFactory; + if (uniqueColumnCount > 0) { + if (newIndexColumns == null || uniqueColumnCount == newIndexColumns.length) { + uniqueRowFactory = rowFactory; + } else { + uniqueRowFactory = databaseRowFactory.createRowFactory(database, compareMode, database, tableColumns, + Arrays.copyOf(newIndexColumns, uniqueColumnCount), true); + } + } else { + uniqueRowFactory = null; + } + this.uniqueRowFactory = uniqueRowFactory; + } + + @Override + public final int getType() { + return DbObject.INDEX; + } + + @Override + public void removeChildrenAndResources(SessionLocal session) { + table.removeIndex(this); + remove(session); + database.removeMeta(session, getId()); + } + + @Override + public final boolean isHidden() { + return table.isHidden(); + } + + @Override + public String getCreateSQLForCopy(Table targetTable, String quotedName) { + StringBuilder builder = new StringBuilder("CREATE "); + builder.append(indexType.getSQL()); + builder.append(' '); + if (table.isHidden()) { + builder.append("IF NOT EXISTS "); + } + builder.append(quotedName); + builder.append(" ON "); + targetTable.getSQL(builder, DEFAULT_SQL_FLAGS); + if (comment != null) { + builder.append(" COMMENT "); + StringUtils.quoteStringSQL(builder, comment); + } + return getColumnListSQL(builder, DEFAULT_SQL_FLAGS).toString(); + } + + /** + * Get the list of columns as a string. + * + * @param sqlFlags formatting flags + * @return the list of columns + */ + private StringBuilder getColumnListSQL(StringBuilder builder, int sqlFlags) { + builder.append('('); + int length = indexColumns.length; + if (uniqueColumnColumn > 0 && uniqueColumnColumn < length) { + IndexColumn.writeColumns(builder, indexColumns, 0, uniqueColumnColumn, sqlFlags).append(") INCLUDE("); + IndexColumn.writeColumns(builder, indexColumns, uniqueColumnColumn, length, sqlFlags); + } else { + IndexColumn.writeColumns(builder, indexColumns, 0, length, sqlFlags); + } + return builder.append(')'); + } + + @Override + public String getCreateSQL() { + return getCreateSQLForCopy(table, getSQL(DEFAULT_SQL_FLAGS)); + } + + /** + * Get the message to show in a EXPLAIN statement. + * + * @return the plan + */ + public String getPlanSQL() { + return getSQL(TRACE_SQL_FLAGS | ADD_PLAN_INFORMATION); + } + + /** + * Close this index. + * + * @param session the session used to write data + */ + public abstract void close(SessionLocal session); + + /** + * Add a row to the index. + * + * @param session the session to use + * @param row the row to add + */ + public abstract void add(SessionLocal session, Row row); + + /** + * Remove a row from the index. + * + * @param session the session + * @param row the row + */ + public abstract void remove(SessionLocal session, Row row); + + /** + * Update index after row change. + * + * @param session the session + * @param oldRow row before the update + * @param newRow row after the update + */ + public void update(SessionLocal session, Row oldRow, Row newRow) { + remove(session, oldRow); + add(session, newRow); + } + + /** + * Returns {@code true} if {@code find()} implementation performs scan over all + * index, {@code false} if {@code find()} performs the fast lookup. + * + * @return {@code true} if {@code find()} implementation performs scan over all + * index, {@code false} if {@code find()} performs the fast lookup + */ + public boolean isFindUsingFullTableScan() { + return false; + } + + /** + * Find a row or a list of rows and create a cursor to iterate over the + * result. + * + * @param session the session + * @param first the first row, or null for no limit + * @param last the last row, or null for no limit + * @return the cursor to iterate over the results + */ + public abstract Cursor find(SessionLocal session, SearchRow first, SearchRow last); + + /** + * Estimate the cost to search for rows given the search mask. + * There is one element per column in the search mask. + * For possible search masks, see IndexCondition. + * + * @param session the session + * @param masks per-column comparison bit masks, null means 'always false', + * see constants in IndexCondition + * @param filters all joined table filters + * @param filter the current table filter index + * @param sortOrder the sort order + * @param allColumnsSet the set of all columns + * @return the estimated cost + */ + public abstract double getCost(SessionLocal session, int[] masks, TableFilter[] filters, int filter, + SortOrder sortOrder, AllColumnsForPlan allColumnsSet); + + /** + * Remove the index. + * + * @param session the session + */ + public abstract void remove(SessionLocal session); + + /** + * Remove all rows from the index. + * + * @param session the session + */ + public abstract void truncate(SessionLocal session); + + /** + * Check if the index can directly look up the lowest or highest value of a + * column. + * + * @return true if it can + */ + public boolean canGetFirstOrLast() { + return false; + } + + /** + * Check if the index can get the next higher value. + * + * @return true if it can + */ + public boolean canFindNext() { + return false; + } + + /** + * Find a row or a list of rows that is larger and create a cursor to + * iterate over the result. + * + * @param session the session + * @param higherThan the lower limit (excluding) + * @param last the last row, or null for no limit + * @return the cursor + */ + public Cursor findNext(SessionLocal session, SearchRow higherThan, SearchRow last) { + throw DbException.getInternalError(toString()); + } + + /** + * Find the first (or last) value of this index. The cursor returned is + * positioned on the correct row, or on null if no row has been found. + * + * @param session the session + * @param first true if the first (lowest for ascending indexes) or last + * value should be returned + * @return a cursor (never null) + */ + public Cursor findFirstOrLast(SessionLocal session, boolean first) { + throw DbException.getInternalError(toString()); + } + + /** + * Check if the index needs to be rebuilt. + * This method is called after opening an index. + * + * @return true if a rebuild is required. + */ + public abstract boolean needRebuild(); + + /** + * Get the row count of this table, for the given session. + * + * @param session the session + * @return the row count + */ + public abstract long getRowCount(SessionLocal session); + + /** + * Get the approximated row count for this table. + * + * @param session the session + * @return the approximated row count + */ + public abstract long getRowCountApproximation(SessionLocal session); + + /** + * Get the used disk space for this index. + * + * @return the estimated number of bytes + */ + public long getDiskSpaceUsed() { + return 0L; + } + + /** + * Compare two rows. + * + * @param rowData the first row + * @param compare the second row + * @return 0 if both rows are equal, -1 if the first row is smaller, + * otherwise 1 + */ + public final int compareRows(SearchRow rowData, SearchRow compare) { + if (rowData == compare) { + return 0; + } + for (int i = 0, len = indexColumns.length; i < len; i++) { + int index = columnIds[i]; + Value v1 = rowData.getValue(index); + Value v2 = compare.getValue(index); + if (v1 == null || v2 == null) { + // can't compare further + return 0; + } + int c = compareValues(v1, v2, indexColumns[i].sortType); + if (c != 0) { + return c; + } + } + return 0; + } + + private int compareValues(Value a, Value b, int sortType) { + if (a == b) { + return 0; + } + boolean aNull = a == ValueNull.INSTANCE; + if (aNull || b == ValueNull.INSTANCE) { + return table.getDatabase().getDefaultNullOrdering().compareNull(aNull, sortType); + } + int comp = table.compareValues(database, a, b); + if ((sortType & SortOrder.DESCENDING) != 0) { + comp = -comp; + } + return comp; + } + + /** + * Get the index of a column in the list of index columns + * + * @param col the column + * @return the index (0 meaning first column) + */ + public int getColumnIndex(Column col) { + for (int i = 0, len = columns.length; i < len; i++) { + if (columns[i].equals(col)) { + return i; + } + } + return -1; + } + + /** + * Check if the given column is the first for this index + * + * @param column the column + * @return true if the given columns is the first + */ + public boolean isFirstColumn(Column column) { + return column.equals(columns[0]); + } + + /** + * Get the indexed columns as index columns (with ordering information). + * + * @return the index columns + */ + public final IndexColumn[] getIndexColumns() { + return indexColumns; + } + + /** + * Get the indexed columns. + * + * @return the columns + */ + public final Column[] getColumns() { + return columns; + } + + /** + * Returns count of unique columns. Unique columns, if any, are always first + * columns in the lists. Unique indexes may have additional indexed + * non-unique columns. + * + * @return count of unique columns, or 0 if index isn't unique + */ + public final int getUniqueColumnCount() { + return uniqueColumnColumn; + } + + /** + * Get the index type. + * + * @return the index type + */ + public final IndexType getIndexType() { + return indexType; + } + + /** + * Get the table on which this index is based. + * + * @return the table + */ + public Table getTable() { + return table; + } + + /** + * Get the row with the given key. + * + * @param session the session + * @param key the unique key + * @return the row + */ + public Row getRow(SessionLocal session, long key) { + throw DbException.getUnsupportedException(toString()); + } + + /** + * Does this index support lookup by row id? + * + * @return true if it does + */ + public boolean isRowIdIndex() { + return false; + } + + /** + * Can this index iterate over all rows? + * + * @return true if it can + */ + public boolean canScan() { + return true; + } + + /** + * Create a duplicate key exception with a message that contains the index + * name. + * + * @param key the key values + * @return the exception + */ + public DbException getDuplicateKeyException(String key) { + StringBuilder builder = new StringBuilder(); + getSQL(builder, TRACE_SQL_FLAGS).append(" ON "); + table.getSQL(builder, TRACE_SQL_FLAGS); + getColumnListSQL(builder, TRACE_SQL_FLAGS); + if (key != null) { + builder.append(" VALUES ").append(key); + } + DbException e = DbException.get(ErrorCode.DUPLICATE_KEY_1, builder.toString()); + e.setSource(this); + return e; + } + + /** + * Get "PRIMARY KEY ON <table> [(column)]". + * + * @param mainIndexColumn the column index + * @return the message + */ + protected StringBuilder getDuplicatePrimaryKeyMessage(int mainIndexColumn) { + StringBuilder builder = new StringBuilder("PRIMARY KEY ON "); + table.getSQL(builder, TRACE_SQL_FLAGS); + if (mainIndexColumn >= 0 && mainIndexColumn < indexColumns.length) { + builder.append('('); + indexColumns[mainIndexColumn].getSQL(builder, TRACE_SQL_FLAGS).append(')'); + } + return builder; + } + + /** + * Calculate the cost for the given mask as if this index was a typical + * b-tree range index. This is the estimated cost required to search one + * row, and then iterate over the given number of rows. + * + * @param masks the IndexCondition search masks, one for each column in the + * table + * @param rowCount the number of rows in the index + * @param filters all joined table filters + * @param filter the current table filter index + * @param sortOrder the sort order + * @param isScanIndex whether this is a "table scan" index + * @param allColumnsSet the set of all columns + * @return the estimated cost + */ + protected final long getCostRangeIndex(int[] masks, long rowCount, TableFilter[] filters, int filter, + SortOrder sortOrder, boolean isScanIndex, AllColumnsForPlan allColumnsSet) { + rowCount += Constants.COST_ROW_OFFSET; + int totalSelectivity = 0; + long rowsCost = rowCount; + if (masks != null) { + int i = 0, len = columns.length; + boolean tryAdditional = false; + while (i < len) { + Column column = columns[i++]; + int index = column.getColumnId(); + int mask = masks[index]; + if ((mask & IndexCondition.EQUALITY) == IndexCondition.EQUALITY) { + if (i > 0 && i == uniqueColumnColumn) { + rowsCost = 3; + break; + } + totalSelectivity = 100 - ((100 - totalSelectivity) * + (100 - column.getSelectivity()) / 100); + long distinctRows = rowCount * totalSelectivity / 100; + if (distinctRows <= 0) { + distinctRows = 1; + } + rowsCost = 2 + Math.max(rowCount / distinctRows, 1); + } else if ((mask & IndexCondition.RANGE) == IndexCondition.RANGE) { + rowsCost = 2 + rowsCost / 4; + tryAdditional = true; + break; + } else if ((mask & IndexCondition.START) == IndexCondition.START) { + rowsCost = 2 + rowsCost / 3; + tryAdditional = true; + break; + } else if ((mask & IndexCondition.END) == IndexCondition.END) { + rowsCost = rowsCost / 3; + tryAdditional = true; + break; + } else { + if (mask == 0) { + // Adjust counter of used columns (i) + i--; + } + break; + } + } + // Some additional columns can still be used + if (tryAdditional) { + while (i < len && masks[columns[i].getColumnId()] != 0) { + i++; + rowsCost--; + } + } + // Increase cost of indexes with additional unused columns + rowsCost += len - i; + } + // If the ORDER BY clause matches the ordering of this index, + // it will be cheaper than another index, so adjust the cost + // accordingly. + long sortingCost = 0; + if (sortOrder != null) { + sortingCost = 100 + rowCount / 10; + } + if (sortOrder != null && !isScanIndex) { + boolean sortOrderMatches = true; + int coveringCount = 0; + int[] sortTypes = sortOrder.getSortTypesWithNullOrdering(); + TableFilter tableFilter = filters == null ? null : filters[filter]; + for (int i = 0, len = sortTypes.length; i < len; i++) { + if (i >= indexColumns.length) { + // We can still use this index if we are sorting by more + // than it's columns, it's just that the coveringCount + // is lower than with an index that contains + // more of the order by columns. + break; + } + Column col = sortOrder.getColumn(i, tableFilter); + if (col == null) { + sortOrderMatches = false; + break; + } + IndexColumn indexCol = indexColumns[i]; + if (!col.equals(indexCol.column)) { + sortOrderMatches = false; + break; + } + int sortType = sortTypes[i]; + if (sortType != indexCol.sortType) { + sortOrderMatches = false; + break; + } + coveringCount++; + } + if (sortOrderMatches) { + // "coveringCount" makes sure that when we have two + // or more covering indexes, we choose the one + // that covers more. + sortingCost = 100 - coveringCount; + } + } + // If we have two indexes with the same cost, and one of the indexes can + // satisfy the query without needing to read from the primary table + // (scan index), make that one slightly lower cost. + boolean needsToReadFromScanIndex; + if (!isScanIndex && allColumnsSet != null) { + needsToReadFromScanIndex = false; + ArrayList foundCols = allColumnsSet.get(getTable()); + if (foundCols != null) { + int main = table.getMainIndexColumn(); + loop: for (Column c : foundCols) { + int id = c.getColumnId(); + if (id == SearchRow.ROWID_INDEX || id == main) { + continue; + } + for (Column c2 : columns) { + if (c == c2) { + continue loop; + } + } + needsToReadFromScanIndex = true; + break; + } + } + } else { + needsToReadFromScanIndex = true; + } + long rc; + if (isScanIndex) { + rc = rowsCost + sortingCost + 20; + } else if (needsToReadFromScanIndex) { + rc = rowsCost + rowsCost + sortingCost + 20; + } else { + // The (20-x) calculation makes sure that when we pick a covering + // index, we pick the covering index that has the smallest number of + // columns (the more columns we have in index - the higher cost). + // This is faster because a smaller index will fit into fewer data + // blocks. + rc = rowsCost + sortingCost + columns.length; + } + return rc; + } + + + /** + * Check if this row may have duplicates with the same indexed values in the + * current compatibility mode. Duplicates with {@code NULL} values are + * allowed in some modes. + * + * @param searchRow + * the row to check + * @return {@code true} if specified row may have duplicates, + * {@code false otherwise} + */ + public final boolean mayHaveNullDuplicates(SearchRow searchRow) { + switch (database.getMode().uniqueIndexNullsHandling) { + case ALLOW_DUPLICATES_WITH_ANY_NULL: + for (int i = 0; i < uniqueColumnColumn; i++) { + int index = columnIds[i]; + if (searchRow.getValue(index) == ValueNull.INSTANCE) { + return true; + } + } + return false; + case ALLOW_DUPLICATES_WITH_ALL_NULLS: + for (int i = 0; i < uniqueColumnColumn; i++) { + int index = columnIds[i]; + if (searchRow.getValue(index) != ValueNull.INSTANCE) { + return false; + } + } + return true; + default: + return false; + } + } + + public RowFactory getRowFactory() { + return rowFactory; + } + + public RowFactory getUniqueRowFactory() { + return uniqueRowFactory; + } + +} diff --git a/h2/src/main/org/h2/index/IndexCondition.java b/h2/src/main/org/h2/index/IndexCondition.java new file mode 100644 index 0000000..d4b32d0 --- /dev/null +++ b/h2/src/main/org/h2/index/IndexCondition.java @@ -0,0 +1,434 @@ +/* + * 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.index; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.TreeSet; + +import org.h2.command.query.Query; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.condition.Comparison; +import org.h2.message.DbException; +import org.h2.result.ResultInterface; +import org.h2.table.Column; +import org.h2.table.TableType; +import org.h2.value.Value; + +/** + * A index condition object is made for each condition that can potentially use + * an index. This class does not extend expression, but in general there is one + * expression that maps to each index condition. + * + * @author Thomas Mueller + * @author Noel Grandin + * @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888 + */ +public class IndexCondition { + + /** + * A bit of a search mask meaning 'equal'. + */ + public static final int EQUALITY = 1; + + /** + * A bit of a search mask meaning 'larger or equal'. + */ + public static final int START = 2; + + /** + * A bit of a search mask meaning 'smaller or equal'. + */ + public static final int END = 4; + + /** + * A search mask meaning 'between'. + */ + public static final int RANGE = START | END; + + /** + * A bit of a search mask meaning 'the condition is always false'. + */ + public static final int ALWAYS_FALSE = 8; + + /** + * A bit of a search mask meaning 'spatial intersection'. + */ + public static final int SPATIAL_INTERSECTS = 16; + + private final Column column; + /** + * see constants in {@link Comparison} + */ + private final int compareType; + + private final Expression expression; + private List expressionList; + private Query expressionQuery; + + /** + * @param compareType the comparison type, see constants in + * {@link Comparison} + */ + private IndexCondition(int compareType, ExpressionColumn column, + Expression expression) { + this.compareType = compareType; + this.column = column == null ? null : column.getColumn(); + this.expression = expression; + } + + /** + * Create an index condition with the given parameters. + * + * @param compareType the comparison type, see constants in + * {@link Comparison} + * @param column the column + * @param expression the expression + * @return the index condition + */ + public static IndexCondition get(int compareType, ExpressionColumn column, + Expression expression) { + return new IndexCondition(compareType, column, expression); + } + + /** + * Create an index condition with the compare type IN_LIST and with the + * given parameters. + * + * @param column the column + * @param list the expression list + * @return the index condition + */ + public static IndexCondition getInList(ExpressionColumn column, + List list) { + IndexCondition cond = new IndexCondition(Comparison.IN_LIST, column, + null); + cond.expressionList = list; + return cond; + } + + /** + * Create an index condition with the compare type IN_QUERY and with the + * given parameters. + * + * @param column the column + * @param query the select statement + * @return the index condition + */ + public static IndexCondition getInQuery(ExpressionColumn column, Query query) { + assert query.isRandomAccessResult(); + IndexCondition cond = new IndexCondition(Comparison.IN_QUERY, column, null); + cond.expressionQuery = query; + return cond; + } + + /** + * Get the current value of the expression. + * + * @param session the session + * @return the value + */ + public Value getCurrentValue(SessionLocal session) { + return expression.getValue(session); + } + + /** + * Get the current value list of the expression. The value list is of the + * same type as the column, distinct, and sorted. + * + * @param session the session + * @return the value list + */ + public Value[] getCurrentValueList(SessionLocal session) { + TreeSet valueSet = new TreeSet<>(session.getDatabase().getCompareMode()); + for (Expression e : expressionList) { + Value v = e.getValue(session); + v = column.convert(session, v); + valueSet.add(v); + } + Value[] array = valueSet.toArray(new Value[valueSet.size()]); + Arrays.sort(array, session.getDatabase().getCompareMode()); + return array; + } + + /** + * Get the current result of the expression. The rows may not be of the same + * type, therefore the rows may not be unique. + * + * @return the result + */ + public ResultInterface getCurrentResult() { + return expressionQuery.query(0); + } + + /** + * Get the SQL snippet of this comparison. + * + * @param sqlFlags formatting flags + * @return the SQL snippet + */ + public String getSQL(int sqlFlags) { + if (compareType == Comparison.FALSE) { + return "FALSE"; + } + StringBuilder builder = new StringBuilder(); + column.getSQL(builder, sqlFlags); + switch (compareType) { + case Comparison.EQUAL: + builder.append(" = "); + break; + case Comparison.EQUAL_NULL_SAFE: + builder.append(expression.isNullConstant() + || column.getType().getValueType() == Value.BOOLEAN && expression.isConstant() // + ? " IS " + : " IS NOT DISTINCT FROM "); + break; + case Comparison.BIGGER_EQUAL: + builder.append(" >= "); + break; + case Comparison.BIGGER: + builder.append(" > "); + break; + case Comparison.SMALLER_EQUAL: + builder.append(" <= "); + break; + case Comparison.SMALLER: + builder.append(" < "); + break; + case Comparison.IN_LIST: + Expression.writeExpressions(builder.append(" IN("), expressionList, sqlFlags).append(')'); + break; + case Comparison.IN_QUERY: + builder.append(" IN("); + builder.append(expressionQuery.getPlanSQL(sqlFlags)); + builder.append(')'); + break; + case Comparison.SPATIAL_INTERSECTS: + builder.append(" && "); + break; + default: + throw DbException.getInternalError("type=" + compareType); + } + if (expression != null) { + expression.getSQL(builder, sqlFlags, Expression.AUTO_PARENTHESES); + } + return builder.toString(); + } + + /** + * Get the comparison bit mask. + * + * @param indexConditions all index conditions + * @return the mask + */ + public int getMask(ArrayList indexConditions) { + switch (compareType) { + case Comparison.FALSE: + return ALWAYS_FALSE; + case Comparison.EQUAL: + case Comparison.EQUAL_NULL_SAFE: + return EQUALITY; + case Comparison.IN_LIST: + case Comparison.IN_QUERY: + if (indexConditions.size() > 1) { + if (TableType.TABLE != column.getTable().getTableType()) { + // if combined with other conditions, + // IN(..) can only be used for regular tables + // test case: + // create table test(a int, b int, primary key(id, name)); + // create unique index c on test(b, a); + // insert into test values(1, 10), (2, 20); + // select * from (select * from test) + // where a=1 and b in(10, 20); + return 0; + } + } + return EQUALITY; + case Comparison.BIGGER_EQUAL: + case Comparison.BIGGER: + return START; + case Comparison.SMALLER_EQUAL: + case Comparison.SMALLER: + return END; + case Comparison.SPATIAL_INTERSECTS: + return SPATIAL_INTERSECTS; + default: + throw DbException.getInternalError("type=" + compareType); + } + } + + /** + * Check if the result is always false. + * + * @return true if the result will always be false + */ + public boolean isAlwaysFalse() { + return compareType == Comparison.FALSE; + } + + /** + * Check if this index condition is of the type column larger or equal to + * value. + * + * @return true if this is a start condition + */ + public boolean isStart() { + switch (compareType) { + case Comparison.EQUAL: + case Comparison.EQUAL_NULL_SAFE: + case Comparison.BIGGER_EQUAL: + case Comparison.BIGGER: + return true; + default: + return false; + } + } + + /** + * Check if this index condition is of the type column smaller or equal to + * value. + * + * @return true if this is a end condition + */ + public boolean isEnd() { + switch (compareType) { + case Comparison.EQUAL: + case Comparison.EQUAL_NULL_SAFE: + case Comparison.SMALLER_EQUAL: + case Comparison.SMALLER: + return true; + default: + return false; + } + } + + /** + * Check if this index condition is of the type spatial column intersects + * value. + * + * @return true if this is a spatial intersects condition + */ + public boolean isSpatialIntersects() { + switch (compareType) { + case Comparison.SPATIAL_INTERSECTS: + return true; + default: + return false; + } + } + + public int getCompareType() { + return compareType; + } + + /** + * Get the referenced column. + * + * @return the column + */ + public Column getColumn() { + return column; + } + + /** + * Get expression. + * + * @return Expression. + */ + public Expression getExpression() { + return expression; + } + + /** + * Get expression list. + * + * @return Expression list. + */ + public List getExpressionList() { + return expressionList; + } + + /** + * Get expression query. + * + * @return Expression query. + */ + public Query getExpressionQuery() { + return expressionQuery; + } + + /** + * Check if the expression can be evaluated. + * + * @return true if it can be evaluated + */ + public boolean isEvaluatable() { + if (expression != null) { + return expression + .isEverything(ExpressionVisitor.EVALUATABLE_VISITOR); + } + if (expressionList != null) { + for (Expression e : expressionList) { + if (!e.isEverything(ExpressionVisitor.EVALUATABLE_VISITOR)) { + return false; + } + } + return true; + } + return expressionQuery + .isEverything(ExpressionVisitor.EVALUATABLE_VISITOR); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("column=").append(column).append(", compareType="); + return compareTypeToString(builder, compareType) + .append(", expression=").append(expression) + .append(", expressionList=").append(expressionList) + .append(", expressionQuery=").append(expressionQuery).toString(); + } + + private static StringBuilder compareTypeToString(StringBuilder builder, int i) { + boolean f = false; + if ((i & EQUALITY) == EQUALITY) { + f = true; + builder.append("EQUALITY"); + } + if ((i & START) == START) { + if (f) { + builder.append(", "); + } + f = true; + builder.append("START"); + } + if ((i & END) == END) { + if (f) { + builder.append(", "); + } + f = true; + builder.append("END"); + } + if ((i & ALWAYS_FALSE) == ALWAYS_FALSE) { + if (f) { + builder.append(", "); + } + f = true; + builder.append("ALWAYS_FALSE"); + } + if ((i & SPATIAL_INTERSECTS) == SPATIAL_INTERSECTS) { + if (f) { + builder.append(", "); + } + builder.append("SPATIAL_INTERSECTS"); + } + return builder; + } + +} diff --git a/h2/src/main/org/h2/index/IndexCursor.java b/h2/src/main/org/h2/index/IndexCursor.java new file mode 100644 index 0000000..2fe8d6f --- /dev/null +++ b/h2/src/main/org/h2/index/IndexCursor.java @@ -0,0 +1,326 @@ +/* + * 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.index; + +import java.util.ArrayList; + +import org.h2.engine.SessionLocal; +import org.h2.expression.condition.Comparison; +import org.h2.message.DbException; +import org.h2.result.ResultInterface; +import org.h2.result.Row; +import org.h2.result.SearchRow; +import org.h2.result.SortOrder; +import org.h2.table.Column; +import org.h2.table.IndexColumn; +import org.h2.table.Table; +import org.h2.value.Value; +import org.h2.value.ValueGeometry; +import org.h2.value.ValueNull; + +/** + * The filter used to walk through an index. This class supports IN(..) + * and IN(SELECT ...) optimizations. + * + * @author Thomas Mueller + * @author Noel Grandin + * @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888 + */ +public class IndexCursor implements Cursor { + + private SessionLocal session; + private Index index; + private Table table; + private IndexColumn[] indexColumns; + private boolean alwaysFalse; + + private SearchRow start, end, intersects; + private Cursor cursor; + private Column inColumn; + private int inListIndex; + private Value[] inList; + private ResultInterface inResult; + + public IndexCursor() { + } + + public void setIndex(Index index) { + this.index = index; + this.table = index.getTable(); + Column[] columns = table.getColumns(); + indexColumns = new IndexColumn[columns.length]; + IndexColumn[] idxCols = index.getIndexColumns(); + if (idxCols != null) { + for (int i = 0, len = columns.length; i < len; i++) { + int idx = index.getColumnIndex(columns[i]); + if (idx >= 0) { + indexColumns[i] = idxCols[idx]; + } + } + } + } + + /** + * Prepare this index cursor to make a lookup in index. + * + * @param s Session. + * @param indexConditions Index conditions. + */ + public void prepare(SessionLocal s, ArrayList indexConditions) { + session = s; + alwaysFalse = false; + start = end = null; + inList = null; + inColumn = null; + inResult = null; + intersects = null; + for (IndexCondition condition : indexConditions) { + if (condition.isAlwaysFalse()) { + alwaysFalse = true; + break; + } + // If index can perform only full table scan do not try to use it for regular + // lookups, each such lookup will perform an own table scan. + if (index.isFindUsingFullTableScan()) { + continue; + } + Column column = condition.getColumn(); + if (condition.getCompareType() == Comparison.IN_LIST) { + if (start == null && end == null) { + if (canUseIndexForIn(column)) { + this.inColumn = column; + inList = condition.getCurrentValueList(s); + inListIndex = 0; + } + } + } else if (condition.getCompareType() == Comparison.IN_QUERY) { + if (start == null && end == null) { + if (canUseIndexForIn(column)) { + this.inColumn = column; + inResult = condition.getCurrentResult(); + } + } + } else { + Value v = condition.getCurrentValue(s); + boolean isStart = condition.isStart(); + boolean isEnd = condition.isEnd(); + boolean isIntersects = condition.isSpatialIntersects(); + int columnId = column.getColumnId(); + if (columnId != SearchRow.ROWID_INDEX) { + IndexColumn idxCol = indexColumns[columnId]; + if (idxCol != null && (idxCol.sortType & SortOrder.DESCENDING) != 0) { + // if the index column is sorted the other way, we swap + // end and start NULLS_FIRST / NULLS_LAST is not a + // problem, as nulls never match anyway + boolean temp = isStart; + isStart = isEnd; + isEnd = temp; + } + } + if (isStart) { + start = getSearchRow(start, columnId, v, true); + } + if (isEnd) { + end = getSearchRow(end, columnId, v, false); + } + if (isIntersects) { + intersects = getSpatialSearchRow(intersects, columnId, v); + } + // An X=? condition will produce less rows than + // an X IN(..) condition, unless the X IN condition can use the index. + if ((isStart || isEnd) && !canUseIndexFor(inColumn)) { + inColumn = null; + inList = null; + inResult = null; + } + } + } + if (inColumn != null) { + start = table.getTemplateRow(); + } + } + + /** + * Re-evaluate the start and end values of the index search for rows. + * + * @param s the session + * @param indexConditions the index conditions + */ + public void find(SessionLocal s, ArrayList indexConditions) { + prepare(s, indexConditions); + if (inColumn != null) { + return; + } + if (!alwaysFalse) { + if (intersects != null && index instanceof SpatialIndex) { + cursor = ((SpatialIndex) index).findByGeometry(session, start, end, intersects); + } else if (index != null) { + cursor = index.find(session, start, end); + } + } + } + + private boolean canUseIndexForIn(Column column) { + if (inColumn != null) { + // only one IN(..) condition can be used at the same time + return false; + } + return canUseIndexFor(column); + } + + private boolean canUseIndexFor(Column column) { + // The first column of the index must match this column, + // or it must be a VIEW index (where the column is null). + // Multiple IN conditions with views are not supported, see + // IndexCondition.getMask. + IndexColumn[] cols = index.getIndexColumns(); + if (cols == null) { + return true; + } + IndexColumn idxCol = cols[0]; + return idxCol == null || idxCol.column == column; + } + + private SearchRow getSpatialSearchRow(SearchRow row, int columnId, Value v) { + if (row == null) { + row = table.getTemplateRow(); + } else if (row.getValue(columnId) != null) { + // if an object needs to overlap with both a and b, + // then it needs to overlap with the union of a and b + // (not the intersection) + ValueGeometry vg = row.getValue(columnId).convertToGeometry(null); + v = v.convertToGeometry(null).getEnvelopeUnion(vg); + } + if (columnId == SearchRow.ROWID_INDEX) { + row.setKey(v == ValueNull.INSTANCE ? Long.MIN_VALUE : v.getLong()); + } else { + row.setValue(columnId, v); + } + return row; + } + + private SearchRow getSearchRow(SearchRow row, int columnId, Value v, boolean max) { + if (row == null) { + row = table.getTemplateRow(); + } else { + v = getMax(row.getValue(columnId), v, max); + } + if (columnId == SearchRow.ROWID_INDEX) { + row.setKey(v == ValueNull.INSTANCE ? Long.MIN_VALUE : v.getLong()); + } else { + row.setValue(columnId, v); + } + return row; + } + + private Value getMax(Value a, Value b, boolean bigger) { + if (a == null) { + return b; + } else if (b == null) { + return a; + } + // IS NULL must be checked later + if (a == ValueNull.INSTANCE) { + return b; + } else if (b == ValueNull.INSTANCE) { + return a; + } + int comp = session.compare(a, b); + if (comp == 0) { + return a; + } + return (comp > 0) == bigger ? a : b; + } + + /** + * Check if the result is empty for sure. + * + * @return true if it is + */ + public boolean isAlwaysFalse() { + return alwaysFalse; + } + + /** + * Get start search row. + * + * @return search row + */ + public SearchRow getStart() { + return start; + } + + /** + * Get end search row. + * + * @return search row + */ + public SearchRow getEnd() { + return end; + } + + @Override + public Row get() { + if (cursor == null) { + return null; + } + return cursor.get(); + } + + @Override + public SearchRow getSearchRow() { + return cursor.getSearchRow(); + } + + @Override + public boolean next() { + while (true) { + if (cursor == null) { + nextCursor(); + if (cursor == null) { + return false; + } + } + if (cursor.next()) { + return true; + } + cursor = null; + } + } + + private void nextCursor() { + if (inList != null) { + while (inListIndex < inList.length) { + Value v = inList[inListIndex++]; + if (v != ValueNull.INSTANCE) { + find(v); + break; + } + } + } else if (inResult != null) { + while (inResult.next()) { + Value v = inResult.currentRow()[0]; + if (v != ValueNull.INSTANCE) { + find(v); + break; + } + } + } + } + + private void find(Value v) { + v = inColumn.convert(session, v); + int id = inColumn.getColumnId(); + start.setValue(id, v); + cursor = index.find(session, start, start); + } + + @Override + public boolean previous() { + throw DbException.getInternalError(toString()); + } + +} diff --git a/h2/src/main/org/h2/index/IndexType.java b/h2/src/main/org/h2/index/IndexType.java new file mode 100644 index 0000000..6949b61 --- /dev/null +++ b/h2/src/main/org/h2/index/IndexType.java @@ -0,0 +1,187 @@ +/* + * 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.index; + +/** + * Represents information about the properties of an index + */ +public class IndexType { + + private boolean primaryKey, persistent, unique, hash, scan, spatial; + private boolean belongsToConstraint; + + /** + * Create a primary key index. + * + * @param persistent if the index is persistent + * @param hash if a hash index should be used + * @return the index type + */ + public static IndexType createPrimaryKey(boolean persistent, boolean hash) { + IndexType type = new IndexType(); + type.primaryKey = true; + type.persistent = persistent; + type.hash = hash; + type.unique = true; + return type; + } + + /** + * Create a unique index. + * + * @param persistent if the index is persistent + * @param hash if a hash index should be used + * @return the index type + */ + public static IndexType createUnique(boolean persistent, boolean hash) { + IndexType type = new IndexType(); + type.unique = true; + type.persistent = persistent; + type.hash = hash; + return type; + } + + /** + * Create a non-unique index. + * + * @param persistent if the index is persistent + * @return the index type + */ + public static IndexType createNonUnique(boolean persistent) { + return createNonUnique(persistent, false, false); + } + + /** + * Create a non-unique index. + * + * @param persistent if the index is persistent + * @param hash if a hash index should be used + * @param spatial if a spatial index should be used + * @return the index type + */ + public static IndexType createNonUnique(boolean persistent, boolean hash, + boolean spatial) { + IndexType type = new IndexType(); + type.persistent = persistent; + type.hash = hash; + type.spatial = spatial; + return type; + } + + /** + * Create a scan pseudo-index. + * + * @param persistent if the index is persistent + * @return the index type + */ + public static IndexType createScan(boolean persistent) { + IndexType type = new IndexType(); + type.persistent = persistent; + type.scan = true; + return type; + } + + /** + * Sets if this index belongs to a constraint. + * + * @param belongsToConstraint if the index belongs to a constraint + */ + public void setBelongsToConstraint(boolean belongsToConstraint) { + this.belongsToConstraint = belongsToConstraint; + } + + /** + * If the index is created because of a constraint. Such indexes are to be + * dropped once the constraint is dropped. + * + * @return if the index belongs to a constraint + */ + public boolean getBelongsToConstraint() { + return belongsToConstraint; + } + + /** + * Is this a hash index? + * + * @return true if it is a hash index + */ + public boolean isHash() { + return hash; + } + + /** + * Is this a spatial index? + * + * @return true if it is a spatial index + */ + public boolean isSpatial() { + return spatial; + } + + /** + * Is this index persistent? + * + * @return true if it is persistent + */ + public boolean isPersistent() { + return persistent; + } + + /** + * Does this index belong to a primary key constraint? + * + * @return true if it references a primary key constraint + */ + public boolean isPrimaryKey() { + return primaryKey; + } + + /** + * Is this a unique index? + * + * @return true if it is + */ + public boolean isUnique() { + return unique; + } + + /** + * Get the SQL snippet to create such an index. + * + * @return the SQL snippet + */ + public String getSQL() { + StringBuilder buff = new StringBuilder(); + if (primaryKey) { + buff.append("PRIMARY KEY"); + if (hash) { + buff.append(" HASH"); + } + } else { + if (unique) { + buff.append("UNIQUE "); + } + if (hash) { + buff.append("HASH "); + } + if (spatial) { + buff.append("SPATIAL "); + } + buff.append("INDEX"); + } + return buff.toString(); + } + + /** + * Is this a table scan pseudo-index? + * + * @return true if it is + */ + public boolean isScan() { + return scan; + } + +} diff --git a/h2/src/main/org/h2/index/LinkedCursor.java b/h2/src/main/org/h2/index/LinkedCursor.java new file mode 100644 index 0000000..75fb1e3 --- /dev/null +++ b/h2/src/main/org/h2/index/LinkedCursor.java @@ -0,0 +1,75 @@ +/* + * 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.index; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.result.Row; +import org.h2.result.SearchRow; +import org.h2.table.TableLink; +import org.h2.value.ValueToObjectConverter2; + +/** + * The cursor implementation for the linked index. + */ +public class LinkedCursor implements Cursor { + + private final TableLink tableLink; + private final PreparedStatement prep; + private final String sql; + private final SessionLocal session; + private final ResultSet rs; + private Row current; + + LinkedCursor(TableLink tableLink, ResultSet rs, SessionLocal session, + String sql, PreparedStatement prep) { + this.session = session; + this.tableLink = tableLink; + this.rs = rs; + this.sql = sql; + this.prep = prep; + } + + @Override + public Row get() { + return current; + } + + @Override + public SearchRow getSearchRow() { + return current; + } + + @Override + public boolean next() { + try { + boolean result = rs.next(); + if (!result) { + rs.close(); + tableLink.reusePreparedStatement(prep, sql); + current = null; + return false; + } + } catch (SQLException e) { + throw DbException.convert(e); + } + current = tableLink.getTemplateRow(); + for (int i = 0; i < current.getColumnCount(); i++) { + current.setValue(i, ValueToObjectConverter2.readValue(session, rs, i + 1, + tableLink.getColumn(i).getType().getValueType())); + } + return true; + } + + @Override + public boolean previous() { + throw DbException.getInternalError(toString()); + } + +} diff --git a/h2/src/main/org/h2/index/LinkedIndex.java b/h2/src/main/org/h2/index/LinkedIndex.java new file mode 100644 index 0000000..b5b9a00 --- /dev/null +++ b/h2/src/main/org/h2/index/LinkedIndex.java @@ -0,0 +1,266 @@ +/* + * 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.index; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.ArrayList; + +import org.h2.command.query.AllColumnsForPlan; +import org.h2.engine.Constants; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.result.Row; +import org.h2.result.SearchRow; +import org.h2.result.SortOrder; +import org.h2.table.Column; +import org.h2.table.IndexColumn; +import org.h2.table.TableFilter; +import org.h2.table.TableLink; +import org.h2.util.Utils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * A linked index is a index for a linked (remote) table. + * It is backed by an index on the remote table which is accessed over JDBC. + */ +public class LinkedIndex extends Index { + + private final TableLink link; + private final String targetTableName; + private long rowCount; + + private final int sqlFlags = QUOTE_ONLY_WHEN_REQUIRED; + + public LinkedIndex(TableLink table, int id, IndexColumn[] columns, int uniqueColumnCount, IndexType indexType) { + super(table, id, null, columns, uniqueColumnCount, indexType); + link = table; + targetTableName = link.getQualifiedTable(); + } + + @Override + public String getCreateSQL() { + return null; + } + + @Override + public void close(SessionLocal session) { + // nothing to do + } + + private static boolean isNull(Value v) { + return v == null || v == ValueNull.INSTANCE; + } + + @Override + public void add(SessionLocal session, Row row) { + ArrayList params = Utils.newSmallArrayList(); + StringBuilder buff = new StringBuilder("INSERT INTO "); + buff.append(targetTableName).append(" VALUES("); + for (int i = 0; i < row.getColumnCount(); i++) { + Value v = row.getValue(i); + if (i > 0) { + buff.append(", "); + } + if (v == null) { + buff.append("DEFAULT"); + } else if (isNull(v)) { + buff.append("NULL"); + } else { + buff.append('?'); + params.add(v); + } + } + buff.append(')'); + String sql = buff.toString(); + try { + link.execute(sql, params, true, session); + rowCount++; + } catch (Exception e) { + throw TableLink.wrapException(sql, e); + } + } + + @Override + public Cursor find(SessionLocal session, SearchRow first, SearchRow last) { + ArrayList params = Utils.newSmallArrayList(); + StringBuilder builder = new StringBuilder("SELECT * FROM ").append(targetTableName).append(" T"); + boolean f = false; + for (int i = 0; first != null && i < first.getColumnCount(); i++) { + Value v = first.getValue(i); + if (v != null) { + builder.append(f ? " AND " : " WHERE "); + f = true; + Column col = table.getColumn(i); + col.getSQL(builder, sqlFlags); + if (v == ValueNull.INSTANCE) { + builder.append(" IS NULL"); + } else { + builder.append(">="); + addParameter(builder, col); + params.add(v); + } + } + } + for (int i = 0; last != null && i < last.getColumnCount(); i++) { + Value v = last.getValue(i); + if (v != null) { + builder.append(f ? " AND " : " WHERE "); + f = true; + Column col = table.getColumn(i); + col.getSQL(builder, sqlFlags); + if (v == ValueNull.INSTANCE) { + builder.append(" IS NULL"); + } else { + builder.append("<="); + addParameter(builder, col); + params.add(v); + } + } + } + String sql = builder.toString(); + try { + PreparedStatement prep = link.execute(sql, params, false, session); + ResultSet rs = prep.getResultSet(); + return new LinkedCursor(link, rs, session, sql, prep); + } catch (Exception e) { + throw TableLink.wrapException(sql, e); + } + } + + private void addParameter(StringBuilder builder, Column col) { + TypeInfo type = col.getType(); + if (type.getValueType() == Value.CHAR && link.isOracle()) { + // workaround for Oracle + // create table test(id int primary key, name char(15)); + // insert into test values(1, 'Hello') + // select * from test where name = ? -- where ? = "Hello" > no rows + builder.append("CAST(? AS CHAR(").append(type.getPrecision()).append("))"); + } else { + builder.append('?'); + } + } + + @Override + public double getCost(SessionLocal session, int[] masks, + TableFilter[] filters, int filter, SortOrder sortOrder, + AllColumnsForPlan allColumnsSet) { + return 100 + getCostRangeIndex(masks, rowCount + + Constants.COST_ROW_OFFSET, filters, filter, sortOrder, false, allColumnsSet); + } + + @Override + public void remove(SessionLocal session) { + // nothing to do + } + + @Override + public void truncate(SessionLocal session) { + // nothing to do + } + + @Override + public void checkRename() { + throw DbException.getUnsupportedException("LINKED"); + } + + @Override + public boolean needRebuild() { + return false; + } + + @Override + public void remove(SessionLocal session, Row row) { + ArrayList params = Utils.newSmallArrayList(); + StringBuilder builder = new StringBuilder("DELETE FROM ").append(targetTableName).append(" WHERE "); + for (int i = 0; i < row.getColumnCount(); i++) { + if (i > 0) { + builder.append("AND "); + } + Column col = table.getColumn(i); + col.getSQL(builder, sqlFlags); + Value v = row.getValue(i); + if (isNull(v)) { + builder.append(" IS NULL "); + } else { + builder.append('='); + addParameter(builder, col); + params.add(v); + builder.append(' '); + } + } + String sql = builder.toString(); + try { + PreparedStatement prep = link.execute(sql, params, false, session); + int count = prep.executeUpdate(); + link.reusePreparedStatement(prep, sql); + rowCount -= count; + } catch (Exception e) { + throw TableLink.wrapException(sql, e); + } + } + + /** + * Update a row using a UPDATE statement. This method is to be called if the + * emit updates option is enabled. + * + * @param oldRow the old data + * @param newRow the new data + * @param session the session + */ + public void update(Row oldRow, Row newRow, SessionLocal session) { + ArrayList params = Utils.newSmallArrayList(); + StringBuilder builder = new StringBuilder("UPDATE ").append(targetTableName).append(" SET "); + for (int i = 0; i < newRow.getColumnCount(); i++) { + if (i > 0) { + builder.append(", "); + } + table.getColumn(i).getSQL(builder, sqlFlags).append('='); + Value v = newRow.getValue(i); + if (v == null) { + builder.append("DEFAULT"); + } else { + builder.append('?'); + params.add(v); + } + } + builder.append(" WHERE "); + for (int i = 0; i < oldRow.getColumnCount(); i++) { + Column col = table.getColumn(i); + if (i > 0) { + builder.append(" AND "); + } + col.getSQL(builder, sqlFlags); + Value v = oldRow.getValue(i); + if (isNull(v)) { + builder.append(" IS NULL"); + } else { + builder.append('='); + params.add(v); + addParameter(builder, col); + } + } + String sql = builder.toString(); + try { + link.execute(sql, params, true, session); + } catch (Exception e) { + throw TableLink.wrapException(sql, e); + } + } + + @Override + public long getRowCount(SessionLocal session) { + return rowCount; + } + + @Override + public long getRowCountApproximation(SessionLocal session) { + return rowCount; + } + +} diff --git a/h2/src/main/org/h2/index/MetaCursor.java b/h2/src/main/org/h2/index/MetaCursor.java new file mode 100644 index 0000000..8932d01 --- /dev/null +++ b/h2/src/main/org/h2/index/MetaCursor.java @@ -0,0 +1,48 @@ +/* + * 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.index; + +import java.util.ArrayList; +import org.h2.message.DbException; +import org.h2.result.Row; +import org.h2.result.SearchRow; + +/** + * An index for a meta data table. + * This index can only scan through all rows, search is not supported. + */ +public class MetaCursor implements Cursor { + + private Row current; + private final ArrayList rows; + private int index; + + MetaCursor(ArrayList rows) { + this.rows = rows; + } + + @Override + public Row get() { + return current; + } + + @Override + public SearchRow getSearchRow() { + return current; + } + + @Override + public boolean next() { + current = index >= rows.size() ? null : rows.get(index++); + return current != null; + } + + @Override + public boolean previous() { + throw DbException.getInternalError(toString()); + } + +} diff --git a/h2/src/main/org/h2/index/MetaIndex.java b/h2/src/main/org/h2/index/MetaIndex.java new file mode 100644 index 0000000..86ee869 --- /dev/null +++ b/h2/src/main/org/h2/index/MetaIndex.java @@ -0,0 +1,129 @@ +/* + * 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.index; + +import java.util.ArrayList; + +import org.h2.command.query.AllColumnsForPlan; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.result.Row; +import org.h2.result.SearchRow; +import org.h2.result.SortOrder; +import org.h2.table.Column; +import org.h2.table.IndexColumn; +import org.h2.table.MetaTable; +import org.h2.table.TableFilter; + +/** + * The index implementation for meta data tables. + */ +public class MetaIndex extends Index { + + private final MetaTable meta; + private final boolean scan; + + public MetaIndex(MetaTable meta, IndexColumn[] columns, boolean scan) { + super(meta, 0, null, columns, 0, IndexType.createNonUnique(true)); + this.meta = meta; + this.scan = scan; + } + + @Override + public void close(SessionLocal session) { + // nothing to do + } + + @Override + public void add(SessionLocal session, Row row) { + throw DbException.getUnsupportedException("META"); + } + + @Override + public void remove(SessionLocal session, Row row) { + throw DbException.getUnsupportedException("META"); + } + + @Override + public Cursor find(SessionLocal session, SearchRow first, SearchRow last) { + ArrayList rows = meta.generateRows(session, first, last); + return new MetaCursor(rows); + } + + @Override + public double getCost(SessionLocal session, int[] masks, + TableFilter[] filters, int filter, SortOrder sortOrder, + AllColumnsForPlan allColumnsSet) { + if (scan) { + return 10 * MetaTable.ROW_COUNT_APPROXIMATION; + } + return getCostRangeIndex(masks, MetaTable.ROW_COUNT_APPROXIMATION, + filters, filter, sortOrder, false, allColumnsSet); + } + + @Override + public void truncate(SessionLocal session) { + throw DbException.getUnsupportedException("META"); + } + + @Override + public void remove(SessionLocal session) { + throw DbException.getUnsupportedException("META"); + } + + @Override + public int getColumnIndex(Column col) { + if (scan) { + // the scan index cannot use any columns + return -1; + } + return super.getColumnIndex(col); + } + + @Override + public boolean isFirstColumn(Column column) { + if (scan) { + return false; + } + return super.isFirstColumn(column); + } + + @Override + public void checkRename() { + throw DbException.getUnsupportedException("META"); + } + + @Override + public boolean needRebuild() { + return false; + } + + @Override + public String getCreateSQL() { + return null; + } + + @Override + public long getRowCount(SessionLocal session) { + return MetaTable.ROW_COUNT_APPROXIMATION; + } + + @Override + public long getRowCountApproximation(SessionLocal session) { + return MetaTable.ROW_COUNT_APPROXIMATION; + } + + @Override + public long getDiskSpaceUsed() { + return meta.getDiskSpaceUsed(); + } + + @Override + public String getPlanSQL() { + return "meta"; + } + +} diff --git a/h2/src/main/org/h2/index/QueryExpressionCursor.java b/h2/src/main/org/h2/index/QueryExpressionCursor.java new file mode 100644 index 0000000..e8e3cb9 --- /dev/null +++ b/h2/src/main/org/h2/index/QueryExpressionCursor.java @@ -0,0 +1,86 @@ +/* + * 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.index; + +import org.h2.message.DbException; +import org.h2.result.ResultInterface; +import org.h2.result.Row; +import org.h2.result.SearchRow; +import org.h2.table.Table; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * The cursor implementation of a query expression index. + */ +public class QueryExpressionCursor implements Cursor { + + private final Table table; + private final QueryExpressionIndex index; + private final ResultInterface result; + private final SearchRow first, last; + private Row current; + + public QueryExpressionCursor(QueryExpressionIndex index, ResultInterface result, SearchRow first, SearchRow last) { + this.table = index.getTable(); + this.index = index; + this.result = result; + this.first = first; + this.last = last; + } + + @Override + public Row get() { + return current; + } + + @Override + public SearchRow getSearchRow() { + return current; + } + + @Override + public boolean next() { + while (true) { + boolean res = result.next(); + if (!res) { + if (index.isRecursive()) { + result.reset(); + } else { + result.close(); + } + current = null; + return false; + } + current = table.getTemplateRow(); + Value[] values = result.currentRow(); + for (int i = 0, len = current.getColumnCount(); i < len; i++) { + Value v = i < values.length ? values[i] : ValueNull.INSTANCE; + current.setValue(i, v); + } + int comp; + if (first != null) { + comp = index.compareRows(current, first); + if (comp < 0) { + continue; + } + } + if (last != null) { + comp = index.compareRows(current, last); + if (comp > 0) { + continue; + } + } + return true; + } + } + + @Override + public boolean previous() { + throw DbException.getInternalError(toString()); + } + +} diff --git a/h2/src/main/org/h2/index/QueryExpressionIndex.java b/h2/src/main/org/h2/index/QueryExpressionIndex.java new file mode 100644 index 0000000..c1adef1 --- /dev/null +++ b/h2/src/main/org/h2/index/QueryExpressionIndex.java @@ -0,0 +1,414 @@ +/* + * 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.index; + +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; + +import org.h2.api.ErrorCode; +import org.h2.command.Parser; +import org.h2.command.query.AllColumnsForPlan; +import org.h2.command.query.Query; +import org.h2.command.query.SelectUnion; +import org.h2.engine.Constants; +import org.h2.engine.SessionLocal; +import org.h2.expression.Parameter; +import org.h2.expression.condition.Comparison; +import org.h2.message.DbException; +import org.h2.result.LocalResult; +import org.h2.result.ResultInterface; +import org.h2.result.Row; +import org.h2.result.SearchRow; +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 expression. + */ +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 QueryExpressionTable table; + private final String querySQL; + private final ArrayList originalParameters; + private boolean recursive; + private final int[] indexMasks; + private Query query; + private final SessionLocal createSession; + + /** + * The time in nanoseconds when this index (and its cost) was calculated. + */ + private final long evaluatedAt; + + /** + * Constructor for the original index in {@link TableView}. + * + * @param table the query expression table + * @param querySQL the query SQL + * @param originalParameters the original parameters + * @param recursive if the view is recursive + */ + public QueryExpressionIndex(QueryExpressionTable table, String querySQL, + ArrayList originalParameters, boolean recursive) { + super(table, 0, null, null, 0, IndexType.createNonUnique(false)); + this.table = table; + this.querySQL = querySQL; + this.originalParameters = originalParameters; + this.recursive = recursive; + columns = new Column[0]; + this.createSession = null; + this.indexMasks = null; + // this is a main index of TableView, it does not need eviction time + // stamp + evaluatedAt = Long.MIN_VALUE; + } + + /** + * Constructor for plan item generation. Over this index the query will be + * executed. + * + * @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 QueryExpressionIndex(QueryExpressionTable table, QueryExpressionIndex index, SessionLocal session, + int[] masks, TableFilter[] filters, int filter, SortOrder sortOrder) { + super(table, 0, null, null, 0, IndexType.createNonUnique(false)); + this.table = table; + this.querySQL = index.querySQL; + this.originalParameters = index.originalParameters; + this.recursive = index.recursive; + this.indexMasks = masks; + this.createSession = session; + columns = new Column[0]; + if (!recursive) { + query = getQuery(session, masks); + } + if (recursive || table.getTopQuery() != null) { + evaluatedAt = Long.MAX_VALUE; + } else { + long time = System.nanoTime(); + if (time == Long.MAX_VALUE) { + time++; + } + evaluatedAt = time; + } + } + + public SessionLocal getSession() { + return createSession; + } + + public boolean isExpired() { + assert evaluatedAt != Long.MIN_VALUE : "must not be called for main index of TableView"; + return !recursive && table.getTopQuery() == null && System.nanoTime() - evaluatedAt > MAX_AGE_NANOS; + } + + @Override + public String getPlanSQL() { + return query == null ? null : query.getPlanSQL(TRACE_SQL_FLAGS | ADD_PLAN_INFORMATION); + } + + @Override + public void close(SessionLocal session) { + // nothing to do + } + + @Override + public void add(SessionLocal session, Row row) { + throw DbException.getUnsupportedException("VIEW"); + } + + @Override + public void remove(SessionLocal session, Row row) { + throw DbException.getUnsupportedException("VIEW"); + } + + @Override + public double getCost(SessionLocal session, int[] masks, + TableFilter[] filters, int filter, SortOrder sortOrder, + AllColumnsForPlan allColumnsSet) { + return recursive ? 1000 : query.getCost(); + } + + @Override + public Cursor find(SessionLocal session, SearchRow first, SearchRow last) { + return find(session, first, last, null); + } + + @Override + public Cursor findByGeometry(SessionLocal session, SearchRow first, SearchRow last, SearchRow intersection) { + return find(session, first, last, intersection); + } + + private Cursor findRecursive(SearchRow first, SearchRow last) { + TableView view = (TableView) table; + ResultInterface recursiveResult = view.getRecursiveResult(); + if (recursiveResult != null) { + recursiveResult.reset(); + return new QueryExpressionCursor(this, recursiveResult, first, last); + } + if (query == null) { + Parser parser = new Parser(createSession); + parser.setRightsChecked(true); + parser.setSuppliedParameters(originalParameters); + query = (Query) parser.prepare(querySQL); + query.setNeverLazy(true); + } + if (!query.isUnion()) { + throw DbException.get(ErrorCode.SYNTAX_ERROR_2, + "recursive queries without UNION"); + } + SelectUnion union = (SelectUnion) query; + Query left = union.getLeft(); + left.setNeverLazy(true); + // to ensure the last result is not closed + left.disableCache(); + ResultInterface resultInterface = left.query(0); + LocalResult localResult = union.getEmptyResult(); + // ensure it is not written to disk, + // because it is not closed normally + localResult.setMaxMemoryRows(Integer.MAX_VALUE); + while (resultInterface.next()) { + Value[] cr = resultInterface.currentRow(); + localResult.addRow(cr); + } + Query right = union.getRight(); + right.setNeverLazy(true); + resultInterface.reset(); + view.setRecursiveResult(resultInterface); + // to ensure the last result is not closed + right.disableCache(); + while (true) { + resultInterface = right.query(0); + if (!resultInterface.hasNext()) { + break; + } + while (resultInterface.next()) { + Value[] cr = resultInterface.currentRow(); + localResult.addRow(cr); + } + resultInterface.reset(); + view.setRecursiveResult(resultInterface); + } + view.setRecursiveResult(null); + localResult.done(); + return new QueryExpressionCursor(this, localResult, first, last); + } + + /** + * Set the query parameters. + * + * @param session the session + * @param first the lower bound + * @param last the upper bound + * @param intersection the intersection + */ + public void setupQueryParameters(SessionLocal session, SearchRow first, SearchRow last, + SearchRow intersection) { + ArrayList paramList = query.getParameters(); + if (originalParameters != null) { + for (Parameter orig : originalParameters) { + if (orig != null) { + int idx = orig.getIndex(); + Value value = orig.getValue(session); + setParameter(paramList, idx, value); + } + } + } + int len; + if (first != null) { + len = first.getColumnCount(); + } else if (last != null) { + len = last.getColumnCount(); + } else if (intersection != null) { + len = intersection.getColumnCount(); + } else { + len = 0; + } + int idx = table.getParameterOffset(originalParameters); + for (int i = 0; i < len; i++) { + int mask = indexMasks[i]; + if ((mask & IndexCondition.EQUALITY) != 0) { + setParameter(paramList, idx++, first.getValue(i)); + } + if ((mask & IndexCondition.START) != 0) { + setParameter(paramList, idx++, first.getValue(i)); + } + if ((mask & IndexCondition.END) != 0) { + setParameter(paramList, idx++, last.getValue(i)); + } + if ((mask & IndexCondition.SPATIAL_INTERSECTS) != 0) { + setParameter(paramList, idx++, intersection.getValue(i)); + } + } + } + + private Cursor find(SessionLocal session, SearchRow first, SearchRow last, + SearchRow intersection) { + if (recursive) { + return findRecursive(first, last); + } + setupQueryParameters(session, first, last, intersection); + ResultInterface result = query.query(0); + return new QueryExpressionCursor(this, result, first, last); + } + + private static void setParameter(ArrayList paramList, int x, + Value v) { + if (x >= paramList.size()) { + // the parameter may be optimized away as in + // select * from (select null as x) where x=1; + return; + } + Parameter param = paramList.get(x); + param.setValue(v); + } + + public Query getQuery() { + return query; + } + + private Query getQuery(SessionLocal session, int[] masks) { + Query q = session.prepareQueryExpression(querySQL); + if (masks == null || !q.allowGlobalConditions()) { + q.preparePlan(); + return q; + } + 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) + IntArray paramColumnIndex = new IntArray(); + int indexColumnCount = 0; + for (int i = 0; i < masks.length; i++) { + int mask = masks[i]; + if (mask == 0) { + continue; + } + indexColumnCount++; + // the number of parameters depends on the mask; + // for range queries it is 2: >= x AND <= y + // but bitMask could also be 7 (=, and <=, and >=) + int bitCount = Integer.bitCount(mask); + for (int j = 0; j < bitCount; j++) { + paramColumnIndex.add(i); + } + } + int len = paramColumnIndex.size(); + ArrayList columnList = new ArrayList<>(len); + for (int i = 0; i < len;) { + int idx = paramColumnIndex.get(i); + columnList.add(table.getColumn(idx)); + int mask = masks[idx]; + if ((mask & IndexCondition.EQUALITY) != 0) { + Parameter param = new Parameter(firstIndexParam + i); + q.addGlobalCondition(param, idx, Comparison.EQUAL_NULL_SAFE); + i++; + } + if ((mask & IndexCondition.START) != 0) { + Parameter param = new Parameter(firstIndexParam + i); + q.addGlobalCondition(param, idx, Comparison.BIGGER_EQUAL); + i++; + } + if ((mask & IndexCondition.END) != 0) { + Parameter param = new Parameter(firstIndexParam + i); + q.addGlobalCondition(param, idx, Comparison.SMALLER_EQUAL); + i++; + } + if ((mask & IndexCondition.SPATIAL_INTERSECTS) != 0) { + Parameter param = new Parameter(firstIndexParam + i); + q.addGlobalCondition(param, idx, Comparison.SPATIAL_INTERSECTS); + i++; + } + } + columns = columnList.toArray(new Column[0]); + + // reconstruct the index columns from the masks + this.indexColumns = new IndexColumn[indexColumnCount]; + this.columnIds = new int[indexColumnCount]; + for (int type = 0, indexColumnId = 0; type < 2; type++) { + for (int i = 0; i < masks.length; i++) { + int mask = masks[i]; + if (mask == 0) { + continue; + } + if (type == 0) { + if ((mask & IndexCondition.EQUALITY) == 0) { + // the first columns need to be equality conditions + continue; + } + } else { + if ((mask & IndexCondition.EQUALITY) != 0) { + // after that only range conditions + continue; + } + } + Column column = table.getColumn(i); + indexColumns[indexColumnId] = new IndexColumn(column); + columnIds[indexColumnId] = column.getColumnId(); + indexColumnId++; + } + } + String sql = q.getPlanSQL(DEFAULT_SQL_FLAGS); + if (!sql.equals(querySQL)) { + q = session.prepareQueryExpression(sql); + } + q.preparePlan(); + return q; + } + + @Override + public void remove(SessionLocal session) { + throw DbException.getUnsupportedException("VIEW"); + } + + @Override + public void truncate(SessionLocal session) { + throw DbException.getUnsupportedException("VIEW"); + } + + @Override + public void checkRename() { + throw DbException.getUnsupportedException("VIEW"); + } + + @Override + public boolean needRebuild() { + return false; + } + + public void setRecursive(boolean value) { + this.recursive = value; + } + + @Override + public long getRowCount(SessionLocal session) { + return 0; + } + + @Override + public long getRowCountApproximation(SessionLocal session) { + return 0; + } + + public boolean isRecursive() { + return recursive; + } +} diff --git a/h2/src/main/org/h2/index/RangeCursor.java b/h2/src/main/org/h2/index/RangeCursor.java new file mode 100644 index 0000000..e51e1d0 --- /dev/null +++ b/h2/src/main/org/h2/index/RangeCursor.java @@ -0,0 +1,58 @@ +/* + * 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.index; + +import org.h2.message.DbException; +import org.h2.result.Row; +import org.h2.result.SearchRow; +import org.h2.value.Value; +import org.h2.value.ValueBigint; + +/** + * The cursor implementation for the range index. + */ +class RangeCursor implements Cursor { + + private boolean beforeFirst; + private long current; + private Row currentRow; + private final long start, end, step; + + RangeCursor(long start, long end, long step) { + this.start = start; + this.end = end; + this.step = step; + beforeFirst = true; + } + + @Override + public Row get() { + return currentRow; + } + + @Override + public SearchRow getSearchRow() { + return currentRow; + } + + @Override + public boolean next() { + if (beforeFirst) { + beforeFirst = false; + current = start; + } else { + current += step; + } + currentRow = Row.get(new Value[]{ValueBigint.get(current)}, 1); + return step > 0 ? current <= end : current >= end; + } + + @Override + public boolean previous() { + throw DbException.getInternalError(toString()); + } + +} diff --git a/h2/src/main/org/h2/index/RangeIndex.java b/h2/src/main/org/h2/index/RangeIndex.java new file mode 100644 index 0000000..30f3bab --- /dev/null +++ b/h2/src/main/org/h2/index/RangeIndex.java @@ -0,0 +1,107 @@ +/* + * 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.index; + +import org.h2.api.ErrorCode; +import org.h2.command.query.AllColumnsForPlan; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.result.Row; +import org.h2.result.SearchRow; +import org.h2.result.SortOrder; +import org.h2.table.IndexColumn; +import org.h2.table.RangeTable; +import org.h2.table.TableFilter; +import org.h2.value.Value; +import org.h2.value.ValueBigint; + +/** + * An index for the SYSTEM_RANGE table. + * This index can only scan through all rows, search is not supported. + */ +public class RangeIndex extends VirtualTableIndex { + + private final RangeTable rangeTable; + + public RangeIndex(RangeTable table, IndexColumn[] columns) { + super(table, "RANGE_INDEX", columns); + this.rangeTable = table; + } + + @Override + public Cursor find(SessionLocal session, SearchRow first, SearchRow last) { + long min = rangeTable.getMin(session); + long max = rangeTable.getMax(session); + long step = rangeTable.getStep(session); + if (step == 0L) { + throw DbException.get(ErrorCode.STEP_SIZE_MUST_NOT_BE_ZERO); + } + if (first != null) { + try { + long v = first.getValue(0).getLong(); + if (step > 0) { + if (v > min) { + min += (v - min + step - 1) / step * step; + } + } else if (v > max) { + max = v; + } + } catch (DbException e) { + // error when converting the value - ignore + } + } + if (last != null) { + try { + long v = last.getValue(0).getLong(); + if (step > 0) { + if (v < max) { + max = v; + } + } else if (v < min) { + min -= (min - v - step - 1) / step * step; + } + } catch (DbException e) { + // error when converting the value - ignore + } + } + return new RangeCursor(min, max, step); + } + + @Override + public double getCost(SessionLocal session, int[] masks, + TableFilter[] filters, int filter, SortOrder sortOrder, + AllColumnsForPlan allColumnsSet) { + return 1d; + } + + @Override + public String getCreateSQL() { + return null; + } + + @Override + public boolean canGetFirstOrLast() { + return true; + } + + @Override + public Cursor findFirstOrLast(SessionLocal session, boolean first) { + long min = rangeTable.getMin(session); + long max = rangeTable.getMax(session); + long step = rangeTable.getStep(session); + if (step == 0L) { + throw DbException.get(ErrorCode.STEP_SIZE_MUST_NOT_BE_ZERO); + } + return new SingleRowCursor((step > 0 ? min <= max : min >= max) + ? Row.get(new Value[]{ ValueBigint.get(first ^ min >= max ? min : max) }, 1) : null); + } + + @Override + public String getPlanSQL() { + return "range index"; + } + +} diff --git a/h2/src/main/org/h2/index/SingleRowCursor.java b/h2/src/main/org/h2/index/SingleRowCursor.java new file mode 100644 index 0000000..1ef602b --- /dev/null +++ b/h2/src/main/org/h2/index/SingleRowCursor.java @@ -0,0 +1,53 @@ +/* + * 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.index; + +import org.h2.message.DbException; +import org.h2.result.Row; +import org.h2.result.SearchRow; + +/** + * A cursor with at most one row. + */ +public class SingleRowCursor implements Cursor { + private Row row; + private boolean end; + + /** + * Create a new cursor. + * + * @param row - the single row (if null then cursor is empty) + */ + public SingleRowCursor(Row row) { + this.row = row; + } + + @Override + public Row get() { + return row; + } + + @Override + public SearchRow getSearchRow() { + return row; + } + + @Override + public boolean next() { + if (row == null || end) { + row = null; + return false; + } + end = true; + return true; + } + + @Override + public boolean previous() { + throw DbException.getInternalError(toString()); + } + +} diff --git a/h2/src/main/org/h2/index/SpatialIndex.java b/h2/src/main/org/h2/index/SpatialIndex.java new file mode 100644 index 0000000..1494d36 --- /dev/null +++ b/h2/src/main/org/h2/index/SpatialIndex.java @@ -0,0 +1,30 @@ +/* + * 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.index; + +import org.h2.engine.SessionLocal; +import org.h2.result.SearchRow; + +/** + * A spatial index. Spatial indexes are used to speed up searching + * spatial/geometric data. + */ +public interface SpatialIndex { + + /** + * Find a row or a list of rows and create a cursor to iterate over the + * result. + * + * @param session the session + * @param first the lower bound + * @param last the upper bound + * @param intersection the geometry which values should intersect with, or + * null for anything + * @return the cursor to iterate over the results + */ + Cursor findByGeometry(SessionLocal session, SearchRow first, SearchRow last, SearchRow intersection); + +} diff --git a/h2/src/main/org/h2/index/VirtualConstructedTableIndex.java b/h2/src/main/org/h2/index/VirtualConstructedTableIndex.java new file mode 100644 index 0000000..bde72c8 --- /dev/null +++ b/h2/src/main/org/h2/index/VirtualConstructedTableIndex.java @@ -0,0 +1,66 @@ +/* + * 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.index; + +import org.h2.command.query.AllColumnsForPlan; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.result.SearchRow; +import org.h2.result.SortOrder; +import org.h2.table.FunctionTable; +import org.h2.table.IndexColumn; +import org.h2.table.TableFilter; +import org.h2.table.VirtualConstructedTable; + +/** + * An index for a virtual table that returns a result set. Search in this index + * performs scan over all rows and should be avoided. + */ +public class VirtualConstructedTableIndex extends VirtualTableIndex { + + private final VirtualConstructedTable table; + + public VirtualConstructedTableIndex(VirtualConstructedTable table, IndexColumn[] columns) { + super(table, null, columns); + this.table = table; + } + + @Override + public boolean isFindUsingFullTableScan() { + return true; + } + + @Override + public Cursor find(SessionLocal session, SearchRow first, SearchRow last) { + return new VirtualTableCursor(this, first, last, table.getResult(session)); + } + + @Override + public double getCost(SessionLocal session, int[] masks, TableFilter[] filters, int filter, SortOrder sortOrder, + AllColumnsForPlan allColumnsSet) { + if (masks != null) { + throw DbException.getUnsupportedException("Virtual table"); + } + long expectedRows; + if (table.canGetRowCount(session)) { + expectedRows = table.getRowCountApproximation(session); + } else { + expectedRows = database.getSettings().estimatedFunctionTableRows; + } + return expectedRows * 10; + } + + @Override + public String getPlanSQL() { + return table instanceof FunctionTable ? "function" : "table scan"; + } + + @Override + public boolean canScan() { + return false; + } + +} diff --git a/h2/src/main/org/h2/index/VirtualTableCursor.java b/h2/src/main/org/h2/index/VirtualTableCursor.java new file mode 100644 index 0000000..0831454 --- /dev/null +++ b/h2/src/main/org/h2/index/VirtualTableCursor.java @@ -0,0 +1,112 @@ +/* + * 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.index; + +import org.h2.message.DbException; +import org.h2.result.ResultInterface; +import org.h2.result.Row; +import org.h2.result.SearchRow; +import org.h2.value.Value; + +/** + * A cursor for a virtual table. This implementation filters the rows (only + * returns entries that are larger or equal to "first", and smaller than last or + * equal to "last"). + */ +class VirtualTableCursor implements Cursor { + + private final VirtualTableIndex index; + + private final SearchRow first; + + private final SearchRow last; + + private final ResultInterface result; + + Value[] values; + + Row row; + + /** + * @param index + * index + * @param first + * first row + * @param last + * last row + * @param result + * the result + */ + VirtualTableCursor(VirtualTableIndex index, SearchRow first, SearchRow last, + ResultInterface result) { + this.index = index; + this.first = first; + this.last = last; + this.result = result; + } + + @Override + public Row get() { + if (values == null) { + return null; + } + if (row == null) { + row = Row.get(values, 1); + } + return row; + } + + @Override + public SearchRow getSearchRow() { + return get(); + } + + @Override + public boolean next() { + final SearchRow first = this.first, last = this.last; + if (first == null && last == null) { + return nextImpl(); + } + while (nextImpl()) { + Row current = get(); + if (first != null) { + int comp = index.compareRows(current, first); + if (comp < 0) { + continue; + } + } + if (last != null) { + int comp = index.compareRows(current, last); + if (comp > 0) { + continue; + } + } + return true; + } + return false; + } + + /** + * Skip to the next row if one is available. This method does not filter. + * + * @return true if another row is available + */ + private boolean nextImpl() { + row = null; + if (result != null && result.next()) { + values = result.currentRow(); + } else { + values = null; + } + return values != null; + } + + @Override + public boolean previous() { + throw DbException.getInternalError(toString()); + } + +} diff --git a/h2/src/main/org/h2/index/VirtualTableIndex.java b/h2/src/main/org/h2/index/VirtualTableIndex.java new file mode 100644 index 0000000..eee94df --- /dev/null +++ b/h2/src/main/org/h2/index/VirtualTableIndex.java @@ -0,0 +1,68 @@ +/* + * 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.index; + +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.result.Row; +import org.h2.table.IndexColumn; +import org.h2.table.VirtualTable; + +/** + * An base class for indexes of virtual tables. + */ +public abstract class VirtualTableIndex extends Index { + + protected VirtualTableIndex(VirtualTable table, String name, IndexColumn[] columns) { + super(table, 0, name, columns, 0, IndexType.createNonUnique(true)); + } + + @Override + public void close(SessionLocal session) { + // nothing to do + } + + @Override + public void add(SessionLocal session, Row row) { + throw DbException.getUnsupportedException("Virtual table"); + } + + @Override + public void remove(SessionLocal session, Row row) { + throw DbException.getUnsupportedException("Virtual table"); + } + + @Override + public void remove(SessionLocal session) { + throw DbException.getUnsupportedException("Virtual table"); + } + + @Override + public void truncate(SessionLocal session) { + throw DbException.getUnsupportedException("Virtual table"); + } + + @Override + public boolean needRebuild() { + return false; + } + + @Override + public void checkRename() { + throw DbException.getUnsupportedException("Virtual table"); + } + + @Override + public long getRowCount(SessionLocal session) { + return table.getRowCount(session); + } + + @Override + public long getRowCountApproximation(SessionLocal session) { + return table.getRowCountApproximation(session); + } + +} diff --git a/h2/src/main/org/h2/index/package.html b/h2/src/main/org/h2/index/package.html new file mode 100644 index 0000000..40a1703 --- /dev/null +++ b/h2/src/main/org/h2/index/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Various table index implementations, as well as cursors to navigate in an index. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/jdbc/JdbcArray.java b/h2/src/main/org/h2/jdbc/JdbcArray.java new file mode 100644 index 0000000..90c745d --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcArray.java @@ -0,0 +1,313 @@ +/* + * 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.jdbc; + +import java.sql.Array; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Map; + +import org.h2.api.ErrorCode; +import org.h2.message.DbException; +import org.h2.message.TraceObject; +import org.h2.result.SimpleResult; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueArray; +import org.h2.value.ValueBigint; +import org.h2.value.ValueToObjectConverter; + +/** + * Represents an ARRAY value. + */ +public final class JdbcArray extends TraceObject implements Array { + + private ValueArray value; + private final JdbcConnection conn; + + /** + * INTERNAL + * @param conn it belongs to + * @param value of + * @param id of the trace object + */ + public JdbcArray(JdbcConnection conn, Value value, int id) { + setTrace(conn.getSession().getTrace(), TraceObject.ARRAY, id); + this.conn = conn; + this.value = value.convertToAnyArray(conn); + } + + /** + * Returns the value as a Java array. + * This method always returns an Object[]. + * + * @return the Object array + */ + @Override + public Object getArray() throws SQLException { + try { + debugCodeCall("getArray"); + checkClosed(); + return get(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value as a Java array. + * This method always returns an Object[]. + * + * @param map is ignored. Only empty or null maps are supported + * @return the Object array + */ + @Override + public Object getArray(Map> map) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getArray(" + quoteMap(map) + ')'); + } + JdbcConnection.checkMap(map); + checkClosed(); + return get(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value as a Java array. A subset of the array is returned, + * starting from the index (1 meaning the first element) and up to the given + * object count. This method always returns an Object[]. + * + * @param index the start index of the subset (starting with 1) + * @param count the maximum number of values + * @return the Object array + */ + @Override + public Object getArray(long index, int count) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getArray(" + index + ", " + count + ')'); + } + checkClosed(); + return get(index, count); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value as a Java array. A subset of the array is returned, + * starting from the index (1 meaning the first element) and up to the given + * object count. This method always returns an Object[]. + * + * @param index the start index of the subset (starting with 1) + * @param count the maximum number of values + * @param map is ignored. Only empty or null maps are supported + * @return the Object array + */ + @Override + public Object getArray(long index, int count, Map> map) + throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getArray(" + index + ", " + count + ", " + quoteMap(map) + ')'); + } + checkClosed(); + JdbcConnection.checkMap(map); + return get(index, count); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the base type of the array. + * + * @return the base type or Types.NULL + */ + @Override + public int getBaseType() throws SQLException { + try { + debugCodeCall("getBaseType"); + checkClosed(); + return DataType.convertTypeToSQLType(value.getComponentType()); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the base type name of the array. This database does support mixed + * type arrays and therefore there is no base type. + * + * @return the base type name or "NULL" + */ + @Override + public String getBaseTypeName() throws SQLException { + try { + debugCodeCall("getBaseTypeName"); + checkClosed(); + return value.getComponentType().getDeclaredTypeName(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value as a result set. + * The first column contains the index + * (starting with 1) and the second column the value. + * + * @return the result set + */ + @Override + public ResultSet getResultSet() throws SQLException { + try { + debugCodeCall("getResultSet"); + checkClosed(); + return getResultSetImpl(1L, Integer.MAX_VALUE); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value as a result set. The first column contains the index + * (starting with 1) and the second column the value. + * + * @param map is ignored. Only empty or null maps are supported + * @return the result set + */ + @Override + public ResultSet getResultSet(Map> map) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getResultSet(" + quoteMap(map) + ')'); + } + checkClosed(); + JdbcConnection.checkMap(map); + return getResultSetImpl(1L, Integer.MAX_VALUE); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value as a result set. The first column contains the index + * (starting with 1) and the second column the value. A subset of the array + * is returned, starting from the index (1 meaning the first element) and + * up to the given object count. + * + * @param index the start index of the subset (starting with 1) + * @param count the maximum number of values + * @return the result set + */ + @Override + public ResultSet getResultSet(long index, int count) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getResultSet(" + index + ", " + count + ')'); + } + checkClosed(); + return getResultSetImpl(index, count); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value as a result set. + * The first column contains the index + * (starting with 1) and the second column the value. + * A subset of the array is returned, starting from the index + * (1 meaning the first element) and up to the given object count. + * + * @param index the start index of the subset (starting with 1) + * @param count the maximum number of values + * @param map is ignored. Only empty or null maps are supported + * @return the result set + */ + @Override + public ResultSet getResultSet(long index, int count, + Map> map) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getResultSet(" + index + ", " + count + ", " + quoteMap(map) + ')'); + } + checkClosed(); + JdbcConnection.checkMap(map); + return getResultSetImpl(index, count); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Release all resources of this object. + */ + @Override + public void free() { + debugCodeCall("free"); + value = null; + } + + private ResultSet getResultSetImpl(long index, int count) { + int id = getNextId(TraceObject.RESULT_SET); + SimpleResult rs = new SimpleResult(); + rs.addColumn("INDEX", TypeInfo.TYPE_BIGINT); + rs.addColumn("VALUE", value.getComponentType()); + Value[] values = value.getList(); + count = checkRange(index, count, values.length); + for (int i = (int) index; i < index + count; i++) { + rs.addRow(ValueBigint.get(i), values[i - 1]); + } + return new JdbcResultSet(conn, null, null, rs, id, true, false, false); + } + + private void checkClosed() { + conn.checkClosed(); + if (value == null) { + throw DbException.get(ErrorCode.OBJECT_CLOSED); + } + } + + private Object get() { + return ValueToObjectConverter.valueToDefaultArray(value, conn, true); + } + + private Object get(long index, int count) { + Value[] values = value.getList(); + count = checkRange(index, count, values.length); + Object[] a = new Object[count]; + for (int i = 0, j = (int) index - 1; i < count; i++, j++) { + a[i] = ValueToObjectConverter.valueToDefaultObject(values[j], conn, true); + } + return a; + } + + private static int checkRange(long index, int count, int len) { + if (index < 1 || (index != 1 && index > len)) { + throw DbException.getInvalidValueException("index (1.." + len + ')', index); + } + int rem = len - (int) index + 1; + if (count < 0) { + throw DbException.getInvalidValueException("count (0.." + rem + ')', count); + } + return Math.min(rem, count); + } + + /** + * INTERNAL + */ + @Override + public String toString() { + return value == null ? "null" : + (getTraceObjectName() + ": " + value.getTraceSQL()); + } +} diff --git a/h2/src/main/org/h2/jdbc/JdbcBatchUpdateException.java b/h2/src/main/org/h2/jdbc/JdbcBatchUpdateException.java new file mode 100644 index 0000000..e8040c8 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcBatchUpdateException.java @@ -0,0 +1,78 @@ +/* + * 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.jdbc; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.sql.BatchUpdateException; +import java.sql.SQLException; + +/** + * Represents a batch update database exception. + */ +public final class JdbcBatchUpdateException extends BatchUpdateException { + + private static final long serialVersionUID = 1L; + + /** + * INTERNAL + * @param next exception + * @param updateCounts affected record counts + */ + JdbcBatchUpdateException(SQLException next, int[] updateCounts) { + super(next.getMessage(), next.getSQLState(), next.getErrorCode(), updateCounts); + setNextException(next); + } + + /** + * INTERNAL + * @param next exception + * @param updateCounts affected record counts + */ + JdbcBatchUpdateException(SQLException next, long[] updateCounts) { + super(next.getMessage(), next.getSQLState(), next.getErrorCode(), updateCounts, null); + setNextException(next); + } + + /** + * INTERNAL + */ + @Override + public void printStackTrace() { + // The default implementation already does that, + // but we do it again to avoid problems. + // If it is not implemented, somebody might implement it + // later on which would be a problem if done in the wrong way. + printStackTrace(System.err); + } + + /** + * INTERNAL + */ + @Override + public void printStackTrace(PrintWriter s) { + if (s != null) { + super.printStackTrace(s); + if (getNextException() != null) { + getNextException().printStackTrace(s); + } + } + } + + /** + * INTERNAL + */ + @Override + public void printStackTrace(PrintStream s) { + if (s != null) { + super.printStackTrace(s); + if (getNextException() != null) { + getNextException().printStackTrace(s); + } + } + } + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcBlob.java b/h2/src/main/org/h2/jdbc/JdbcBlob.java new file mode 100644 index 0000000..b6a49b1 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcBlob.java @@ -0,0 +1,316 @@ +/* + * 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.jdbc; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PipedInputStream; +import java.sql.Blob; +import java.sql.SQLException; + +import org.h2.engine.Constants; +import org.h2.message.DbException; +import org.h2.message.TraceObject; +import org.h2.util.IOUtils; +import org.h2.util.Task; +import org.h2.value.Value; + +/** + * Represents a BLOB value. + */ +public final class JdbcBlob extends JdbcLob implements Blob { + + /** + * INTERNAL + * @param conn it belongs to + * @param value of + * @param state of the LOB + * @param id of the trace object + */ + public JdbcBlob(JdbcConnection conn, Value value, State state, int id) { + super(conn, value, state, TraceObject.BLOB, id); + } + + /** + * Returns the length. + * + * @return the length + */ + @Override + public long length() throws SQLException { + try { + debugCodeCall("length"); + checkReadable(); + if (value.getValueType() == Value.BLOB) { + long precision = value.getType().getPrecision(); + if (precision > 0) { + return precision; + } + } + return IOUtils.copyAndCloseInput(value.getInputStream(), null); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * [Not supported] Truncates the object. + * + * @param len the new length + */ + @Override + public void truncate(long len) throws SQLException { + throw unsupported("LOB update"); + } + + /** + * Returns some bytes of the object. + * + * @param pos the index, the first byte is at position 1 + * @param length the number of bytes + * @return the bytes, at most length bytes + */ + @Override + public byte[] getBytes(long pos, int length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getBytes(" + pos + ", " + length + ')'); + } + checkReadable(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (InputStream in = value.getInputStream()) { + IOUtils.skipFully(in, pos - 1); + IOUtils.copy(in, out, length); + } + return out.toByteArray(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Fills the Blob. This is only supported for new, empty Blob objects that + * were created with Connection.createBlob(). The position + * must be 1, meaning the whole Blob data is set. + * + * @param pos where to start writing (the first byte is at position 1) + * @param bytes the bytes to set + * @return the length of the added data + */ + @Override + public int setBytes(long pos, byte[] bytes) throws SQLException { + if (bytes == null) { + throw new NullPointerException(); + } + try { + if (isDebugEnabled()) { + debugCode("setBytes(" + pos + ", " + quoteBytes(bytes) + ')'); + } + checkEditable(); + if (pos != 1) { + throw DbException.getInvalidValueException("pos", pos); + } + completeWrite(conn.createBlob(new ByteArrayInputStream(bytes), -1)); + return bytes.length; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets some bytes of the object. + * + * @param pos the write position + * @param bytes the bytes to set + * @param offset the bytes offset + * @param len the number of bytes to write + * @return how many bytes have been written + */ + @Override + public int setBytes(long pos, byte[] bytes, int offset, int len) + throws SQLException { + if (bytes == null) { + throw new NullPointerException(); + } + try { + if (isDebugEnabled()) { + debugCode("setBytes(" + pos + ", " + quoteBytes(bytes) + ", " + offset + ", " + len + ')'); + } + checkEditable(); + if (pos != 1) { + throw DbException.getInvalidValueException("pos", pos); + } + completeWrite(conn.createBlob(new ByteArrayInputStream(bytes, offset, len), -1)); + return (int) value.getType().getPrecision(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + @Override + public InputStream getBinaryStream() throws SQLException { + return super.getBinaryStream(); + } + + /** + * Get a writer to update the Blob. This is only supported for new, empty + * Blob objects that were created with Connection.createBlob(). The Blob is + * created in a separate thread, and the object is only updated when + * OutputStream.close() is called. The position must be 1, meaning the whole + * Blob data is set. + * + * @param pos where to start writing (the first byte is at position 1) + * @return an output stream + */ + @Override + public OutputStream setBinaryStream(long pos) throws SQLException { + try { + if (isDebugEnabled()) { + debugCodeCall("setBinaryStream", pos); + } + checkEditable(); + if (pos != 1) { + throw DbException.getInvalidValueException("pos", pos); + } + final PipedInputStream in = new PipedInputStream(); + final Task task = new Task() { + @Override + public void call() { + completeWrite(conn.createBlob(in, -1)); + } + }; + LobPipedOutputStream out = new LobPipedOutputStream(in, task); + task.execute(); + state = State.SET_CALLED; + return new BufferedOutputStream(out); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * [Not supported] Searches a pattern and return the position. + * + * @param pattern the pattern to search + * @param start the index, the first byte is at position 1 + * @return the position (first byte is at position 1), or -1 for not found + */ + @Override + public long position(byte[] pattern, long start) throws SQLException { + if (isDebugEnabled()) { + debugCode("position(" + quoteBytes(pattern) + ", " + start + ')'); + } + if (Constants.BLOB_SEARCH) { + try { + checkReadable(); + if (pattern == null) { + return -1; + } + if (pattern.length == 0) { + return 1; + } + // TODO performance: blob pattern search is slow + BufferedInputStream in = new BufferedInputStream(value.getInputStream()); + IOUtils.skipFully(in, start - 1); + int pos = 0; + int patternPos = 0; + while (true) { + int x = in.read(); + if (x < 0) { + break; + } + if (x == (pattern[patternPos] & 0xff)) { + if (patternPos == 0) { + in.mark(pattern.length); + } + if (patternPos == pattern.length) { + return pos - patternPos; + } + patternPos++; + } else { + if (patternPos > 0) { + in.reset(); + pos -= patternPos; + } + } + pos++; + } + return -1; + } catch (Exception e) { + throw logAndConvert(e); + } + } + throw unsupported("LOB search"); + } + + /** + * [Not supported] Searches a pattern and return the position. + * + * @param blobPattern the pattern to search + * @param start the index, the first byte is at position 1 + * @return the position (first byte is at position 1), or -1 for not found + */ + @Override + public long position(Blob blobPattern, long start) throws SQLException { + if (isDebugEnabled()) { + debugCode("position(blobPattern, " + start + ')'); + } + if (Constants.BLOB_SEARCH) { + try { + checkReadable(); + if (blobPattern == null) { + return -1; + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + InputStream in = blobPattern.getBinaryStream(); + while (true) { + int x = in.read(); + if (x < 0) { + break; + } + out.write(x); + } + return position(out.toByteArray(), start); + } catch (Exception e) { + throw logAndConvert(e); + } + } + throw unsupported("LOB subset"); + } + + /** + * Returns the input stream, starting from an offset. + * + * @param pos where to start reading + * @param length the number of bytes that will be read + * @return the input stream to read + */ + @Override + public InputStream getBinaryStream(long pos, long length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getBinaryStream(" + pos + ", " + length + ')'); + } + checkReadable(); + if (state == State.NEW) { + if (pos != 1) { + throw DbException.getInvalidValueException("pos", pos); + } + if (length != 0) { + throw DbException.getInvalidValueException("length", pos); + } + } + return value.getInputStream(pos, length); + } catch (Exception e) { + throw logAndConvert(e); + } + } + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcCallableStatement.java b/h2/src/main/org/h2/jdbc/JdbcCallableStatement.java new file mode 100644 index 0000000..6541722 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcCallableStatement.java @@ -0,0 +1,1862 @@ +/* + * 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.jdbc; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.CallableStatement; +import java.sql.Clob; +import java.sql.Date; +import java.sql.NClob; +import java.sql.Ref; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLType; +import java.sql.SQLXML; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.BitSet; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; +import org.h2.api.ErrorCode; +import org.h2.expression.ParameterInterface; +import org.h2.message.DbException; +import org.h2.message.TraceObject; +import org.h2.value.ValueNull; + +/** + * Represents a callable statement. + *

+ * Thread safety: the callable statement is not thread-safe. If the same + * callable statement is used by multiple threads access to it must be + * synchronized. The single synchronized block must include assignment of + * parameters, execution of the command and all operations with its result. + *

+ *
+ * synchronized (call) {
+ *     call.setInt(1, 10);
+ *     try (ResultSet rs = call.executeQuery()) {
+ *         while (rs.next) {
+ *             // Do something
+ *         }
+ *     }
+ * }
+ * synchronized (call) {
+ *     call.setInt(1, 15);
+ *     updateCount = call.executeUpdate();
+ * }
+ * 
+ * @author Sergi Vladykin + * @author Thomas Mueller + */ +public final class JdbcCallableStatement extends JdbcPreparedStatement implements CallableStatement { + + private BitSet outParameters; + private int maxOutParameters; + private HashMap namedParameters; + + JdbcCallableStatement(JdbcConnection conn, String sql, int id, int resultSetType, int resultSetConcurrency) { + super(conn, sql, id, resultSetType, resultSetConcurrency, null); + setTrace(session.getTrace(), TraceObject.CALLABLE_STATEMENT, id); + } + + /** + * Executes a statement (insert, update, delete, create, drop) + * and returns the update count. + * If another result set exists for this statement, this will be closed + * (even if this statement fails). + * + * If auto commit is on, this statement will be committed. + * If the statement is a DDL statement (create, drop, alter) and does not + * throw an exception, the current transaction (if any) is committed after + * executing the statement. + * + * @return the update count (number of row affected by an insert, update or + * delete, or 0 if no rows or the statement was a create, drop, + * commit or rollback) + * @throws SQLException if this object is closed or invalid + */ + @Override + public int executeUpdate() throws SQLException { + try { + checkClosed(); + if (command.isQuery()) { + super.executeQuery(); + return 0; + } + return super.executeUpdate(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Executes a statement (insert, update, delete, create, drop) + * and returns the update count. + * If another result set exists for this statement, this will be closed + * (even if this statement fails). + * + * If auto commit is on, this statement will be committed. + * If the statement is a DDL statement (create, drop, alter) and does not + * throw an exception, the current transaction (if any) is committed after + * executing the statement. + * + * @return the update count (number of row affected by an insert, update or + * delete, or 0 if no rows or the statement was a create, drop, + * commit or rollback) + * @throws SQLException if this object is closed or invalid + */ + @Override + public long executeLargeUpdate() throws SQLException { + try { + checkClosed(); + if (command.isQuery()) { + super.executeQuery(); + return 0; + } + return super.executeLargeUpdate(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Registers the given OUT parameter. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param sqlType the data type (Types.x) - ignored + */ + @Override + public void registerOutParameter(int parameterIndex, int sqlType) + throws SQLException { + registerOutParameter(parameterIndex); + } + + /** + * Registers the given OUT parameter. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param sqlType the data type (Types.x) - ignored + * @param typeName the SQL type name - ignored + */ + @Override + public void registerOutParameter(int parameterIndex, int sqlType, + String typeName) throws SQLException { + registerOutParameter(parameterIndex); + } + + /** + * Registers the given OUT parameter. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param sqlType the data type (Types.x) + * @param scale is ignored + */ + @Override + public void registerOutParameter(int parameterIndex, int sqlType, int scale) + throws SQLException { + registerOutParameter(parameterIndex); + } + + /** + * Registers the given OUT parameter. + * + * @param parameterName the parameter name + * @param sqlType the data type (Types.x) - ignored + * @param typeName the SQL type name - ignored + */ + @Override + public void registerOutParameter(String parameterName, int sqlType, + String typeName) throws SQLException { + registerOutParameter(getIndexForName(parameterName), sqlType, typeName); + } + + /** + * Registers the given OUT parameter. + * + * @param parameterName the parameter name + * @param sqlType the data type (Types.x) - ignored + * @param scale is ignored + */ + @Override + public void registerOutParameter(String parameterName, int sqlType, + int scale) throws SQLException { + registerOutParameter(getIndexForName(parameterName), sqlType, scale); + } + + /** + * Registers the given OUT parameter. + * + * @param parameterName the parameter name + * @param sqlType the data type (Types.x) - ignored + */ + @Override + public void registerOutParameter(String parameterName, int sqlType) + throws SQLException { + registerOutParameter(getIndexForName(parameterName), sqlType); + } + + /** + * Returns whether the last column accessed was null. + * + * @return true if the last column accessed was null + */ + @Override + public boolean wasNull() throws SQLException { + return getOpenResultSet().wasNull(); + } + + /** + * [Not supported] + */ + @Override + public URL getURL(int parameterIndex) throws SQLException { + throw unsupported("url"); + } + + /** + * Returns the value of the specified column as a String. + * + * @param parameterIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public String getString(int parameterIndex) throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getString(parameterIndex); + } + + /** + * Returns the value of the specified column as a boolean. + * + * @param parameterIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public boolean getBoolean(int parameterIndex) throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getBoolean(parameterIndex); + } + + /** + * Returns the value of the specified column as a byte. + * + * @param parameterIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public byte getByte(int parameterIndex) throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getByte(parameterIndex); + } + + /** + * Returns the value of the specified column as a short. + * + * @param parameterIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public short getShort(int parameterIndex) throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getShort(parameterIndex); + } + + /** + * Returns the value of the specified column as an int. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public int getInt(int parameterIndex) throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getInt(parameterIndex); + } + + /** + * Returns the value of the specified column as a long. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public long getLong(int parameterIndex) throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getLong(parameterIndex); + } + + /** + * Returns the value of the specified column as a float. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public float getFloat(int parameterIndex) throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getFloat(parameterIndex); + } + + /** + * Returns the value of the specified column as a double. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public double getDouble(int parameterIndex) throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getDouble(parameterIndex); + } + + /** + * Returns the value of the specified column as a BigDecimal. + * + * @deprecated use {@link #getBigDecimal(int)} + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param scale is ignored + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Deprecated + @Override + public BigDecimal getBigDecimal(int parameterIndex, int scale) + throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getBigDecimal(parameterIndex, scale); + } + + /** + * Returns the value of the specified column as a byte array. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public byte[] getBytes(int parameterIndex) throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getBytes(parameterIndex); + } + + /** + * Returns the value of the specified column as a java.sql.Date. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(columnIndex, LocalDate.class)} instead. + *

+ * + * @param parameterIndex the parameter index (1, 2, ...) + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + * @see #getObject(int, Class) + */ + @Override + public Date getDate(int parameterIndex) throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getDate(parameterIndex); + } + + /** + * Returns the value of the specified column as a java.sql.Time. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(columnIndex, LocalTime.class)} instead. + *

+ * + * @param parameterIndex the parameter index (1, 2, ...) + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + * @see #getObject(int, Class) + */ + @Override + public Time getTime(int parameterIndex) throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getTime(parameterIndex); + } + + /** + * Returns the value of the specified column as a java.sql.Timestamp. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(columnIndex, LocalDateTime.class)} instead. + *

+ * + * @param parameterIndex the parameter index (1, 2, ...) + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + * @see #getObject(int, Class) + */ + @Override + public Timestamp getTimestamp(int parameterIndex) throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getTimestamp(parameterIndex); + } + + /** + * Returns a column value as a Java object. The data is + * de-serialized into a Java object (on the client side). + * + * @param parameterIndex the parameter index (1, 2, ...) + * @return the value or null + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public Object getObject(int parameterIndex) throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getObject(parameterIndex); + } + + /** + * Returns the value of the specified column as a BigDecimal. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public BigDecimal getBigDecimal(int parameterIndex) throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getBigDecimal(parameterIndex); + } + + /** + * [Not supported] Gets a column as a object using the specified type + * mapping. + */ + @Override + public Object getObject(int parameterIndex, Map> map) + throws SQLException { + throw unsupported("map"); + } + + /** + * [Not supported] Gets a column as a reference. + */ + @Override + public Ref getRef(int parameterIndex) throws SQLException { + throw unsupported("ref"); + } + + /** + * Returns the value of the specified column as a Blob. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public Blob getBlob(int parameterIndex) throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getBlob(parameterIndex); + } + + /** + * Returns the value of the specified column as a Clob. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public Clob getClob(int parameterIndex) throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getClob(parameterIndex); + } + + /** + * Returns the value of the specified column as an Array. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public Array getArray(int parameterIndex) throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getArray(parameterIndex); + } + + /** + * Returns the value of the specified column as a java.sql.Date using a + * specified time zone. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(columnIndex, LocalDate.class)} instead. + *

+ * + * @param parameterIndex the parameter index (1, 2, ...) + * @param cal the calendar + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + * @see #getObject(int, Class) + */ + @Override + public Date getDate(int parameterIndex, Calendar cal) throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getDate(parameterIndex, cal); + } + + /** + * Returns the value of the specified column as a java.sql.Time using a + * specified time zone. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(columnIndex, LocalTime.class)} instead. + *

+ * + * @param parameterIndex the parameter index (1, 2, ...) + * @param cal the calendar + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + * @see #getObject(int, Class) + */ + @Override + public Time getTime(int parameterIndex, Calendar cal) throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getTime(parameterIndex, cal); + } + + /** + * Returns the value of the specified column as a java.sql.Timestamp using a + * specified time zone. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(columnIndex, LocalDateTime.class)} instead. + *

+ * + * @param parameterIndex the parameter index (1, 2, ...) + * @param cal the calendar + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + * @see #getObject(int, Class) + */ + @Override + public Timestamp getTimestamp(int parameterIndex, Calendar cal) throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getTimestamp(parameterIndex, cal); + } + + /** + * [Not supported] + */ + @Override + public URL getURL(String parameterName) throws SQLException { + throw unsupported("url"); + } + + /** + * Returns the value of the specified column as a java.sql.Timestamp using a + * specified time zone. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(parameterName, LocalDateTime.class)} instead. + *

+ * + * @param parameterName the parameter name + * @param cal the calendar + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + * @see #getObject(String, Class) + */ + @Override + public Timestamp getTimestamp(String parameterName, Calendar cal) throws SQLException { + return getTimestamp(getIndexForName(parameterName), cal); + } + + /** + * Returns the value of the specified column as a java.sql.Time using a + * specified time zone. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(parameterName, LocalTime.class)} instead. + *

+ * + * @param parameterName the parameter name + * @param cal the calendar + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + * @see #getObject(String, Class) + */ + @Override + public Time getTime(String parameterName, Calendar cal) throws SQLException { + return getTime(getIndexForName(parameterName), cal); + } + + /** + * Returns the value of the specified column as a java.sql.Date using a + * specified time zone. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(parameterName, LocalDate.class)} instead. + *

+ * + * @param parameterName the parameter name + * @param cal the calendar + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + * @see #getObject(String, Class) + */ + @Override + public Date getDate(String parameterName, Calendar cal) throws SQLException { + return getDate(getIndexForName(parameterName), cal); + } + + /** + * Returns the value of the specified column as an Array. + * + * @param parameterName the parameter name + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public Array getArray(String parameterName) throws SQLException { + return getArray(getIndexForName(parameterName)); + } + + /** + * Returns the value of the specified column as a Clob. + * + * @param parameterName the parameter name + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public Clob getClob(String parameterName) throws SQLException { + return getClob(getIndexForName(parameterName)); + } + + /** + * Returns the value of the specified column as a Blob. + * + * @param parameterName the parameter name + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public Blob getBlob(String parameterName) throws SQLException { + return getBlob(getIndexForName(parameterName)); + } + + /** + * [Not supported] Gets a column as a reference. + */ + @Override + public Ref getRef(String parameterName) throws SQLException { + throw unsupported("ref"); + } + + /** + * [Not supported] Gets a column as a object using the specified type + * mapping. + */ + @Override + public Object getObject(String parameterName, Map> map) + throws SQLException { + throw unsupported("map"); + } + + /** + * Returns the value of the specified column as a BigDecimal. + * + * @param parameterName the parameter name + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public BigDecimal getBigDecimal(String parameterName) throws SQLException { + return getBigDecimal(getIndexForName(parameterName)); + } + + /** + * Returns a column value as a Java object. The data is + * de-serialized into a Java object (on the client side). + * + * @param parameterName the parameter name + * @return the value or null + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public Object getObject(String parameterName) throws SQLException { + return getObject(getIndexForName(parameterName)); + } + + /** + * Returns the value of the specified column as a java.sql.Timestamp. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(parameterName, LocalDateTime.class)} instead. + *

+ * + * @param parameterName the parameter name + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + * @see #getObject(String, Class) + */ + @Override + public Timestamp getTimestamp(String parameterName) throws SQLException { + return getTimestamp(getIndexForName(parameterName)); + } + + /** + * Returns the value of the specified column as a java.sql.Time. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(parameterName, LocalTime.class)} instead. + *

+ * + * @param parameterName the parameter name + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + * @see #getObject(String, Class) + */ + @Override + public Time getTime(String parameterName) throws SQLException { + return getTime(getIndexForName(parameterName)); + } + + /** + * Returns the value of the specified column as a java.sql.Date. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(parameterName, LocalDate.class)} instead. + *

+ * + * @param parameterName the parameter name + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + * @see #getObject(String, Class) + */ + @Override + public Date getDate(String parameterName) throws SQLException { + return getDate(getIndexForName(parameterName)); + } + + /** + * Returns the value of the specified column as a byte array. + * + * @param parameterName the parameter name + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public byte[] getBytes(String parameterName) throws SQLException { + return getBytes(getIndexForName(parameterName)); + } + + /** + * Returns the value of the specified column as a double. + * + * @param parameterName the parameter name + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public double getDouble(String parameterName) throws SQLException { + return getDouble(getIndexForName(parameterName)); + } + + /** + * Returns the value of the specified column as a float. + * + * @param parameterName the parameter name + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public float getFloat(String parameterName) throws SQLException { + return getFloat(getIndexForName(parameterName)); + } + + /** + * Returns the value of the specified column as a long. + * + * @param parameterName the parameter name + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public long getLong(String parameterName) throws SQLException { + return getLong(getIndexForName(parameterName)); + } + + /** + * Returns the value of the specified column as an int. + * + * @param parameterName the parameter name + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public int getInt(String parameterName) throws SQLException { + return getInt(getIndexForName(parameterName)); + } + + /** + * Returns the value of the specified column as a short. + * + * @param parameterName the parameter name + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public short getShort(String parameterName) throws SQLException { + return getShort(getIndexForName(parameterName)); + } + + /** + * Returns the value of the specified column as a byte. + * + * @param parameterName the parameter name + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public byte getByte(String parameterName) throws SQLException { + return getByte(getIndexForName(parameterName)); + } + + /** + * Returns the value of the specified column as a boolean. + * + * @param parameterName the parameter name + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public boolean getBoolean(String parameterName) throws SQLException { + return getBoolean(getIndexForName(parameterName)); + } + + /** + * Returns the value of the specified column as a String. + * + * @param parameterName the parameter name + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public String getString(String parameterName) throws SQLException { + return getString(getIndexForName(parameterName)); + } + + /** + * [Not supported] Returns the value of the specified column as a row id. + * + * @param parameterIndex the parameter index (1, 2, ...) + */ + @Override + public RowId getRowId(int parameterIndex) throws SQLException { + throw unsupported("rowId"); + } + + /** + * [Not supported] Returns the value of the specified column as a row id. + * + * @param parameterName the parameter name + */ + @Override + public RowId getRowId(String parameterName) throws SQLException { + throw unsupported("rowId"); + } + + /** + * Returns the value of the specified column as a Clob. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public NClob getNClob(int parameterIndex) throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getNClob(parameterIndex); + } + + /** + * Returns the value of the specified column as a Clob. + * + * @param parameterName the parameter name + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public NClob getNClob(String parameterName) throws SQLException { + return getNClob(getIndexForName(parameterName)); + } + + /** + * Returns the value of the specified column as a SQLXML object. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public SQLXML getSQLXML(int parameterIndex) throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getSQLXML(parameterIndex); + } + + /** + * Returns the value of the specified column as a SQLXML object. + * + * @param parameterName the parameter name + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public SQLXML getSQLXML(String parameterName) throws SQLException { + return getSQLXML(getIndexForName(parameterName)); + } + + /** + * Returns the value of the specified column as a String. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public String getNString(int parameterIndex) throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getNString(parameterIndex); + } + + /** + * Returns the value of the specified column as a String. + * + * @param parameterName the parameter name + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public String getNString(String parameterName) throws SQLException { + return getNString(getIndexForName(parameterName)); + } + + /** + * Returns the value of the specified column as a reader. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public Reader getNCharacterStream(int parameterIndex) + throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getNCharacterStream(parameterIndex); + } + + /** + * Returns the value of the specified column as a reader. + * + * @param parameterName the parameter name + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public Reader getNCharacterStream(String parameterName) + throws SQLException { + return getNCharacterStream(getIndexForName(parameterName)); + } + + /** + * Returns the value of the specified column as a reader. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public Reader getCharacterStream(int parameterIndex) + throws SQLException { + checkRegistered(parameterIndex); + return getOpenResultSet().getCharacterStream(parameterIndex); + } + + /** + * Returns the value of the specified column as a reader. + * + * @param parameterName the parameter name + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public Reader getCharacterStream(String parameterName) + throws SQLException { + return getCharacterStream(getIndexForName(parameterName)); + } + + // ============================================================= + + /** + * Sets a parameter to null. + * + * @param parameterName the parameter name + * @param sqlType the data type (Types.x) + * @param typeName this parameter is ignored + * @throws SQLException if this object is closed + */ + @Override + public void setNull(String parameterName, int sqlType, String typeName) + throws SQLException { + setNull(getIndexForName(parameterName), sqlType, typeName); + } + + /** + * Sets a parameter to null. + * + * @param parameterName the parameter name + * @param sqlType the data type (Types.x) + * @throws SQLException if this object is closed + */ + @Override + public void setNull(String parameterName, int sqlType) throws SQLException { + setNull(getIndexForName(parameterName), sqlType); + } + + /** + * Sets the timestamp using a specified time zone. The value will be + * converted to the local time zone. + *

+ * Usage of this method is discouraged. Use + * {@code setObject(parameterName, value)} with + * {@link java.time.LocalDateTime} parameter instead. + *

+ * + * @param parameterName the parameter name + * @param x the value + * @param cal the calendar + * @throws SQLException if this object is closed + * @see #setObject(String, Object) + */ + @Override + public void setTimestamp(String parameterName, Timestamp x, Calendar cal) throws SQLException { + setTimestamp(getIndexForName(parameterName), x, cal); + } + + /** + * Sets the time using a specified time zone. The value will be converted to + * the local time zone. + *

+ * Usage of this method is discouraged. Use + * {@code setObject(parameterName, value)} with {@link java.time.LocalTime} + * parameter instead. + *

+ * + * @param parameterName the parameter name + * @param x the value + * @param cal the calendar + * @throws SQLException if this object is closed + * @see #setObject(String, Object) + */ + @Override + public void setTime(String parameterName, Time x, Calendar cal) throws SQLException { + setTime(getIndexForName(parameterName), x, cal); + } + + /** + * Sets the date using a specified time zone. The value will be converted to + * the local time zone. + *

+ * Usage of this method is discouraged. Use + * {@code setObject(parameterName, value)} with {@link java.time.LocalDate} + * parameter instead. + *

+ * + * @param parameterName the parameter name + * @param x the value + * @param cal the calendar + * @throws SQLException if this object is closed + * @see #setObject(String, Object) + */ + @Override + public void setDate(String parameterName, Date x, Calendar cal) throws SQLException { + setDate(getIndexForName(parameterName), x, cal); + } + + /** + * Sets the value of a parameter as a character stream. + * This method does not close the reader. + * The reader may be closed after executing the statement. + * + * @param parameterName the parameter name + * @param x the value + * @param length the maximum number of characters + * @throws SQLException if this object is closed + */ + @Override + public void setCharacterStream(String parameterName, Reader x, int length) + throws SQLException { + setCharacterStream(getIndexForName(parameterName), x, length); + } + + /** + * Sets the value of a parameter. + * Objects of unknown classes are serialized (on the client side). + * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setObject(String parameterName, Object x) throws SQLException { + setObject(getIndexForName(parameterName), x); + } + + /** + * Sets the value of a parameter. The object is converted, if required, to + * the specified data type before sending to the database. + * Objects of unknown classes are serialized (on the client side). + * + * @param parameterName the parameter name + * @param x the value, null is allowed + * @param targetSqlType the type as defined in java.sql.Types + * @throws SQLException if this object is closed + */ + @Override + public void setObject(String parameterName, Object x, int targetSqlType) + throws SQLException { + setObject(getIndexForName(parameterName), x, targetSqlType); + } + + /** + * Sets the value of a parameter. The object is converted, if required, to + * the specified data type before sending to the database. + * Objects of unknown classes are serialized (on the client side). + * + * @param parameterName the parameter name + * @param x the value, null is allowed + * @param targetSqlType the type as defined in java.sql.Types + * @param scale is ignored + * @throws SQLException if this object is closed + */ + @Override + public void setObject(String parameterName, Object x, int targetSqlType, + int scale) throws SQLException { + setObject(getIndexForName(parameterName), x, targetSqlType, scale); + } + + /** + * Sets the value of a parameter. The object is converted, if required, to + * the specified data type before sending to the database. + * Objects of unknown classes are serialized (on the client side). + * + * @param parameterName the parameter name + * @param x the value, null is allowed + * @param targetSqlType the type + * @throws SQLException if this object is closed + */ + @Override + public void setObject(String parameterName, Object x, SQLType targetSqlType) throws SQLException { + setObject(getIndexForName(parameterName), x, targetSqlType); + } + + /** + * Sets the value of a parameter. The object is converted, if required, to + * the specified data type before sending to the database. + * Objects of unknown classes are serialized (on the client side). + * + * @param parameterName the parameter name + * @param x the value, null is allowed + * @param targetSqlType the type + * @param scaleOrLength is ignored + * @throws SQLException if this object is closed + */ + @Override + public void setObject(String parameterName, Object x, SQLType targetSqlType, int scaleOrLength) + throws SQLException { + setObject(getIndexForName(parameterName), x, targetSqlType, scaleOrLength); + } + + /** + * Sets the value of a parameter as an input stream. + * This method does not close the stream. + * The stream may be closed after executing the statement. + * + * @param parameterName the parameter name + * @param x the value + * @param length the maximum number of bytes + * @throws SQLException if this object is closed + */ + @Override + public void setBinaryStream(String parameterName, InputStream x, int length) + throws SQLException { + setBinaryStream(getIndexForName(parameterName), x, length); + } + + /** + * Sets the value of a parameter as an ASCII stream. + * This method does not close the stream. + * The stream may be closed after executing the statement. + * + * @param parameterName the parameter name + * @param x the value + * @param length the maximum number of bytes + * @throws SQLException if this object is closed + */ + @Override + public void setAsciiStream(String parameterName, + InputStream x, long length) throws SQLException { + setAsciiStream(getIndexForName(parameterName), x, length); + } + + /** + * Sets the value of a parameter. + *

+ * Usage of this method is discouraged. Use + * {@code setObject(parameterName, value)} with + * {@link java.time.LocalDateTime} parameter instead. + *

+ * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + * @see #setObject(String, Object) + */ + @Override + public void setTimestamp(String parameterName, Timestamp x) throws SQLException { + setTimestamp(getIndexForName(parameterName), x); + } + + /** + * Sets the time using a specified time zone. + *

+ * Usage of this method is discouraged. Use + * {@code setObject(parameterName, value)} with {@link java.time.LocalTime} + * parameter instead. + *

+ * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + * @see #setObject(String, Object) + */ + @Override + public void setTime(String parameterName, Time x) throws SQLException { + setTime(getIndexForName(parameterName), x); + } + + /** + * Sets the value of a parameter. + *

+ * Usage of this method is discouraged. Use + * {@code setObject(parameterName, value)} with {@link java.time.LocalDate} + * parameter instead. + *

+ * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + * @see #setObject(String, Object) + */ + @Override + public void setDate(String parameterName, Date x) throws SQLException { + setDate(getIndexForName(parameterName), x); + } + + /** + * Sets the value of a parameter as a byte array. + * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setBytes(String parameterName, byte[] x) throws SQLException { + setBytes(getIndexForName(parameterName), x); + } + + /** + * Sets the value of a parameter. + * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setString(String parameterName, String x) throws SQLException { + setString(getIndexForName(parameterName), x); + } + + /** + * Sets the value of a parameter. + * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setBigDecimal(String parameterName, BigDecimal x) + throws SQLException { + setBigDecimal(getIndexForName(parameterName), x); + } + + /** + * Sets the value of a parameter. + * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setDouble(String parameterName, double x) throws SQLException { + setDouble(getIndexForName(parameterName), x); + } + + /** + * Sets the value of a parameter. + * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setFloat(String parameterName, float x) throws SQLException { + setFloat(getIndexForName(parameterName), x); + } + + /** + * Sets the value of a parameter. + * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setLong(String parameterName, long x) throws SQLException { + setLong(getIndexForName(parameterName), x); + } + + /** + * Sets the value of a parameter. + * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setInt(String parameterName, int x) throws SQLException { + setInt(getIndexForName(parameterName), x); + } + + /** + * Sets the value of a parameter. + * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setShort(String parameterName, short x) throws SQLException { + setShort(getIndexForName(parameterName), x); + } + + /** + * Sets the value of a parameter. + * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setByte(String parameterName, byte x) throws SQLException { + setByte(getIndexForName(parameterName), x); + } + + /** + * Sets the value of a parameter. + * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setBoolean(String parameterName, boolean x) throws SQLException { + setBoolean(getIndexForName(parameterName), x); + } + + /** + * [Not supported] + */ + @Override + public void setURL(String parameterName, URL val) throws SQLException { + throw unsupported("url"); + } + + /** + * [Not supported] Sets the value of a parameter as a row id. + */ + @Override + public void setRowId(String parameterName, RowId x) + throws SQLException { + throw unsupported("rowId"); + } + + /** + * Sets the value of a parameter. + * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setNString(String parameterName, String x) + throws SQLException { + setNString(getIndexForName(parameterName), x); + } + + /** + * Sets the value of a parameter as a character stream. + * This method does not close the reader. + * The reader may be closed after executing the statement. + * + * @param parameterName the parameter name + * @param x the value + * @param length the maximum number of characters + * @throws SQLException if this object is closed + */ + @Override + public void setNCharacterStream(String parameterName, + Reader x, long length) throws SQLException { + setNCharacterStream(getIndexForName(parameterName), x, length); + } + + /** + * Sets the value of a parameter as a Clob. + * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setNClob(String parameterName, NClob x) + throws SQLException { + setNClob(getIndexForName(parameterName), x); + } + + /** + * Sets the value of a parameter as a Clob. + * This method does not close the reader. + * The reader may be closed after executing the statement. + * + * @param parameterName the parameter name + * @param x the value + * @param length the maximum number of characters + * @throws SQLException if this object is closed + */ + @Override + public void setClob(String parameterName, Reader x, + long length) throws SQLException { + setClob(getIndexForName(parameterName), x, length); + } + + /** + * Sets the value of a parameter as a Blob. + * This method does not close the stream. + * The stream may be closed after executing the statement. + * + * @param parameterName the parameter name + * @param x the value + * @param length the maximum number of bytes + * @throws SQLException if this object is closed + */ + @Override + public void setBlob(String parameterName, InputStream x, + long length) throws SQLException { + setBlob(getIndexForName(parameterName), x, length); + } + + /** + * Sets the value of a parameter as a Clob. + * This method does not close the reader. + * The reader may be closed after executing the statement. + * + * @param parameterName the parameter name + * @param x the value + * @param length the maximum number of characters + * @throws SQLException if this object is closed + */ + @Override + public void setNClob(String parameterName, Reader x, + long length) throws SQLException { + setNClob(getIndexForName(parameterName), x, length); + } + + /** + * Sets the value of a parameter as a Blob. + * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setBlob(String parameterName, Blob x) + throws SQLException { + setBlob(getIndexForName(parameterName), x); + } + + /** + * Sets the value of a parameter as a Clob. + * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setClob(String parameterName, Clob x) throws SQLException { + setClob(getIndexForName(parameterName), x); + } + + /** + * Sets the value of a parameter as an ASCII stream. + * This method does not close the stream. + * The stream may be closed after executing the statement. + * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setAsciiStream(String parameterName, InputStream x) + throws SQLException { + setAsciiStream(getIndexForName(parameterName), x); + } + + /** + * Sets the value of a parameter as an ASCII stream. + * This method does not close the stream. + * The stream may be closed after executing the statement. + * + * @param parameterName the parameter name + * @param x the value + * @param length the maximum number of bytes + * @throws SQLException if this object is closed + */ + @Override + public void setAsciiStream(String parameterName, + InputStream x, int length) throws SQLException { + setAsciiStream(getIndexForName(parameterName), x, length); + } + + /** + * Sets the value of a parameter as an input stream. + * This method does not close the stream. + * The stream may be closed after executing the statement. + * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setBinaryStream(String parameterName, + InputStream x) throws SQLException { + setBinaryStream(getIndexForName(parameterName), x); + } + + /** + * Sets the value of a parameter as an input stream. + * This method does not close the stream. + * The stream may be closed after executing the statement. + * + * @param parameterName the parameter name + * @param x the value + * @param length the maximum number of bytes + * @throws SQLException if this object is closed + */ + @Override + public void setBinaryStream(String parameterName, + InputStream x, long length) throws SQLException { + setBinaryStream(getIndexForName(parameterName), x, length); + } + + /** + * Sets the value of a parameter as a Blob. + * This method does not close the stream. + * The stream may be closed after executing the statement. + * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setBlob(String parameterName, InputStream x) + throws SQLException { + setBlob(getIndexForName(parameterName), x); + } + + /** + * Sets the value of a parameter as a character stream. + * This method does not close the reader. + * The reader may be closed after executing the statement. + * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setCharacterStream(String parameterName, Reader x) + throws SQLException { + setCharacterStream(getIndexForName(parameterName), x); + } + + /** + * Sets the value of a parameter as a character stream. + * This method does not close the reader. + * The reader may be closed after executing the statement. + * + * @param parameterName the parameter name + * @param x the value + * @param length the maximum number of characters + * @throws SQLException if this object is closed + */ + @Override + public void setCharacterStream(String parameterName, + Reader x, long length) throws SQLException { + setCharacterStream(getIndexForName(parameterName), x, length); + } + + /** + * Sets the value of a parameter as a character stream. + * This method does not close the reader. + * The reader may be closed after executing the statement. + * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setClob(String parameterName, Reader x) throws SQLException { + setClob(getIndexForName(parameterName), x); + } + + /** + * Sets the value of a parameter as a character stream. + * This method does not close the reader. + * The reader may be closed after executing the statement. + * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setNCharacterStream(String parameterName, Reader x) + throws SQLException { + setNCharacterStream(getIndexForName(parameterName), x); + } + + /** + * Sets the value of a parameter as a Clob. + * This method does not close the reader. + * The reader may be closed after executing the statement. + * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setNClob(String parameterName, Reader x) + throws SQLException { + setNClob(getIndexForName(parameterName), x); + } + + /** + * Sets the value of a parameter as a SQLXML object. + * + * @param parameterName the parameter name + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setSQLXML(String parameterName, SQLXML x) + throws SQLException { + setSQLXML(getIndexForName(parameterName), x); + } + + /** + * Returns the value of the specified column as a Java object of the + * specified type. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param type the class of the returned value + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public T getObject(int parameterIndex, Class type) throws SQLException { + return getOpenResultSet().getObject(parameterIndex, type); + } + + /** + * Returns the value of the specified column as a Java object of the + * specified type. + * + * @param parameterName the parameter name + * @param type the class of the returned value + * @return the value + * @throws SQLException if the column is not found or if this object is + * closed + */ + @Override + public T getObject(String parameterName, Class type) throws SQLException { + return getObject(getIndexForName(parameterName), type); + } + + private ResultSetMetaData getCheckedMetaData() throws SQLException { + ResultSetMetaData meta = getMetaData(); + if (meta == null) { + throw DbException.getUnsupportedException( + "Supported only for calling stored procedures"); + } + return meta; + } + + private void checkIndexBounds(int parameterIndex) { + checkClosed(); + if (parameterIndex < 1 || parameterIndex > maxOutParameters) { + throw DbException.getInvalidValueException("parameterIndex", parameterIndex); + } + } + + private void registerOutParameter(int parameterIndex) throws SQLException { + try { + checkClosed(); + if (outParameters == null) { + maxOutParameters = Math.min( + getParameterMetaData().getParameterCount(), + getCheckedMetaData().getColumnCount()); + outParameters = new BitSet(); + } + checkIndexBounds(parameterIndex); + ParameterInterface param = command.getParameters().get(--parameterIndex); + if (!param.isValueSet()) { + param.setValue(ValueNull.INSTANCE, false); + } + outParameters.set(parameterIndex); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private void checkRegistered(int parameterIndex) throws SQLException { + try { + checkIndexBounds(parameterIndex); + if (!outParameters.get(parameterIndex - 1)) { + throw DbException.getInvalidValueException("parameterIndex", parameterIndex); + } + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private int getIndexForName(String parameterName) throws SQLException { + try { + checkClosed(); + if (namedParameters == null) { + ResultSetMetaData meta = getCheckedMetaData(); + int columnCount = meta.getColumnCount(); + HashMap map = new HashMap<>(); + for (int i = 1; i <= columnCount; i++) { + map.put(meta.getColumnLabel(i), i); + } + namedParameters = map; + } + Integer index = namedParameters.get(parameterName); + if (index == null) { + throw DbException.getInvalidValueException("parameterName", parameterName); + } + return index; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private JdbcResultSet getOpenResultSet() throws SQLException { + try { + checkClosed(); + if (resultSet == null) { + throw DbException.get(ErrorCode.NO_DATA_AVAILABLE); + } + if (resultSet.isBeforeFirst()) { + resultSet.next(); + } + return resultSet; + } catch (Exception e) { + throw logAndConvert(e); + } + } + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcClob.java b/h2/src/main/org/h2/jdbc/JdbcClob.java new file mode 100644 index 0000000..d23dbfa --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcClob.java @@ -0,0 +1,264 @@ +/* + * 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.jdbc; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.sql.Clob; +import java.sql.NClob; +import java.sql.SQLException; + +import org.h2.engine.Constants; +import org.h2.message.DbException; +import org.h2.message.TraceObject; +import org.h2.store.RangeReader; +import org.h2.util.IOUtils; +import org.h2.value.Value; + +/** + * Represents a CLOB value. + */ +public final class JdbcClob extends JdbcLob implements NClob { + + /** + * INTERNAL + * @param conn it belongs to + * @param value of + * @param state of the LOB + * @param id of the trace object + */ + public JdbcClob(JdbcConnection conn, Value value, State state, int id) { + super(conn, value, state, TraceObject.CLOB, id); + } + + /** + * Returns the length. + * + * @return the length + */ + @Override + public long length() throws SQLException { + try { + debugCodeCall("length"); + checkReadable(); + if (value.getValueType() == Value.CLOB) { + long precision = value.getType().getPrecision(); + if (precision > 0) { + return precision; + } + } + return IOUtils.copyAndCloseInput(value.getReader(), null, Long.MAX_VALUE); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * [Not supported] Truncates the object. + */ + @Override + public void truncate(long len) throws SQLException { + throw unsupported("LOB update"); + } + + /** + * Returns the input stream. + * + * @return the input stream + */ + @Override + public InputStream getAsciiStream() throws SQLException { + try { + debugCodeCall("getAsciiStream"); + checkReadable(); + String s = value.getString(); + return IOUtils.getInputStreamFromString(s); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * [Not supported] Returns an output stream. + */ + @Override + public OutputStream setAsciiStream(long pos) throws SQLException { + throw unsupported("LOB update"); + } + + @Override + public Reader getCharacterStream() throws SQLException { + return super.getCharacterStream(); + } + + /** + * Get a writer to update the Clob. This is only supported for new, empty + * Clob objects that were created with Connection.createClob() or + * createNClob(). The Clob is created in a separate thread, and the object + * is only updated when Writer.close() is called. The position must be 1, + * meaning the whole Clob data is set. + * + * @param pos where to start writing (the first character is at position 1) + * @return a writer + */ + @Override + public Writer setCharacterStream(long pos) throws SQLException { + try { + if (isDebugEnabled()) { + debugCodeCall("setCharacterStream", pos); + } + checkEditable(); + if (pos != 1) { + throw DbException.getInvalidValueException("pos", pos); + } + state = State.SET_CALLED; + return setCharacterStreamImpl(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns a substring. + * + * @param pos the position (the first character is at position 1) + * @param length the number of characters + * @return the string + */ + @Override + public String getSubString(long pos, int length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getSubString(" + pos + ", " + length + ')'); + } + checkReadable(); + if (pos < 1) { + throw DbException.getInvalidValueException("pos", pos); + } + if (length < 0) { + throw DbException.getInvalidValueException("length", length); + } + StringWriter writer = new StringWriter( + Math.min(Constants.IO_BUFFER_SIZE, length)); + try (Reader reader = value.getReader()) { + IOUtils.skipFully(reader, pos - 1); + IOUtils.copyAndCloseInput(reader, writer, length); + } + return writer.toString(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Fills the Clob. This is only supported for new, empty Clob objects that + * were created with Connection.createClob() or createNClob(). The position + * must be 1, meaning the whole Clob data is set. + * + * @param pos where to start writing (the first character is at position 1) + * @param str the string to add + * @return the length of the added text + * @throws SQLException on failure + */ + @Override + public int setString(long pos, String str) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setString(" + pos + ", " + quote(str) + ')'); + } + checkEditable(); + if (pos != 1) { + throw DbException.getInvalidValueException("pos", pos); + } else if (str == null) { + throw DbException.getInvalidValueException("str", str); + } + completeWrite(conn.createClob(new StringReader(str), -1)); + return str.length(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Fills the Clob. This is only supported for new, empty Clob objects that + * were created with Connection.createClob() or createNClob(). The position + * must be 1, meaning the whole Clob data is set. + * + * @param pos where to start writing (the first character is at position 1) + * @param str the string to add + * @param offset the string offset + * @param len the number of characters to read + * @return the length of the added text + */ + @Override + public int setString(long pos, String str, int offset, int len) + throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setString(" + pos + ", " + quote(str) + ", " + offset + ", " + len + ')'); + } + checkEditable(); + if (pos != 1) { + throw DbException.getInvalidValueException("pos", pos); + } else if (str == null) { + throw DbException.getInvalidValueException("str", str); + } + completeWrite(conn.createClob(new RangeReader(new StringReader(str), offset, len), -1)); + return (int) value.getType().getPrecision(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * [Not supported] Searches a pattern and return the position. + */ + @Override + public long position(String pattern, long start) throws SQLException { + throw unsupported("LOB search"); + } + + /** + * [Not supported] Searches a pattern and return the position. + */ + @Override + public long position(Clob clobPattern, long start) throws SQLException { + throw unsupported("LOB search"); + } + + /** + * Returns the reader, starting from an offset. + * + * @param pos 1-based offset + * @param length length of requested area + * @return the reader + */ + @Override + public Reader getCharacterStream(long pos, long length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getCharacterStream(" + pos + ", " + length + ')'); + } + checkReadable(); + if (state == State.NEW) { + if (pos != 1) { + throw DbException.getInvalidValueException("pos", pos); + } + if (length != 0) { + throw DbException.getInvalidValueException("length", pos); + } + } + return value.getReader(pos, length); + } catch (Exception e) { + throw logAndConvert(e); + } + } + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcConnection.java b/h2/src/main/org/h2/jdbc/JdbcConnection.java new file mode 100644 index 0000000..9834e7a --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcConnection.java @@ -0,0 +1,1880 @@ +/* + * 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.jdbc; + +import java.io.InputStream; +import java.io.Reader; +import java.sql.Array; +import java.sql.Blob; +import java.sql.CallableStatement; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.NClob; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLClientInfoException; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Savepoint; +import java.sql.Statement; +import java.sql.Struct; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.concurrent.Executor; +import java.util.regex.Pattern; + +import org.h2.api.ErrorCode; +import org.h2.api.JavaObjectSerializer; +import org.h2.command.CommandInterface; +import org.h2.engine.CastDataProvider; +import org.h2.engine.ConnectionInfo; +import org.h2.engine.Constants; +import org.h2.engine.IsolationLevel; +import org.h2.engine.Mode; +import org.h2.engine.Session; +import org.h2.engine.Session.StaticSettings; +import org.h2.engine.SessionRemote; +import org.h2.engine.SysProperties; +import org.h2.message.DbException; +import org.h2.message.TraceObject; +import org.h2.result.ResultInterface; +import org.h2.util.CloseWatcher; +import org.h2.util.TimeZoneProvider; +import org.h2.value.CompareMode; +import org.h2.value.Value; +import org.h2.value.ValueInteger; +import org.h2.value.ValueNull; +import org.h2.value.ValueTimestampTimeZone; +import org.h2.value.ValueToObjectConverter; +import org.h2.value.ValueVarbinary; +import org.h2.value.ValueVarchar; + +/** + * Represents a connection (session) to a database. + *

+ * Thread safety: the connection is thread-safe, because access is synchronized. + * Different statements from the same connection may try to execute their + * commands in parallel, but they will be executed sequentially. If real + * concurrent execution of these commands is needed, different connections + * should be used. + *

+ */ +public class JdbcConnection extends TraceObject implements Connection, JdbcConnectionBackwardsCompat, + CastDataProvider { + + private static final String NUM_SERVERS = "numServers"; + private static final String PREFIX_SERVER = "server"; + + private static boolean keepOpenStackTrace; + + private final String url; + private final String user; + + // ResultSet.HOLD_CURSORS_OVER_COMMIT + private int holdability = 1; + + private Session session; + private CommandInterface commit, rollback; + private CommandInterface getReadOnly, getGeneratedKeys; + private CommandInterface setQueryTimeout, getQueryTimeout; + + private int savepointId; + private String catalog; + private Statement executingStatement; + private final CloseWatcher watcher; + private int queryTimeoutCache = -1; + + private Map clientInfo; + + /** + * INTERNAL + * the session closable object does not leak as Eclipse warns - due to the + * CloseWatcher. + * @param url of this connection + * @param info of this connection + * @param user of this connection + * @param password for the user + * @param forbidCreation whether database creation is forbidden + * @throws SQLException on failure + */ + @SuppressWarnings("resource") + public JdbcConnection(String url, Properties info, String user, Object password, boolean forbidCreation) + throws SQLException { + try { + ConnectionInfo ci = new ConnectionInfo(url, info, user, password); + if (forbidCreation) { + ci.setProperty("FORBID_CREATION", "TRUE"); + } + String baseDir = SysProperties.getBaseDir(); + if (baseDir != null) { + ci.setBaseDir(baseDir); + } + // this will return an embedded or server connection + session = new SessionRemote(ci).connectEmbeddedOrServer(false); + setTrace(session.getTrace(), TraceObject.CONNECTION, getNextId(TraceObject.CONNECTION)); + this.user = ci.getUserName(); + if (isInfoEnabled()) { + trace.infoCode("Connection " + getTraceObjectName() + + " = DriverManager.getConnection(" + + quote(ci.getOriginalURL()) + ", " + quote(this.user) + + ", \"\");"); + } + this.url = ci.getURL(); + closeOld(); + watcher = CloseWatcher.register(this, session, keepOpenStackTrace); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * INTERNAL + * @param clone connection to clone + */ + public JdbcConnection(JdbcConnection clone) { + this.session = clone.session; + setTrace(session.getTrace(), TraceObject.CONNECTION, getNextId(TraceObject.CONNECTION)); + this.user = clone.user; + this.url = clone.url; + this.catalog = clone.catalog; + this.commit = clone.commit; + this.getGeneratedKeys = clone.getGeneratedKeys; + this.getQueryTimeout = clone.getQueryTimeout; + this.getReadOnly = clone.getReadOnly; + this.rollback = clone.rollback; + this.watcher = null; + if (clone.clientInfo != null) { + this.clientInfo = new HashMap<>(clone.clientInfo); + } + } + + /** + * INTERNAL + * @param session of this connection + * @param user of this connection + * @param url of this connection + */ + public JdbcConnection(Session session, String user, String url) { + this.session = session; + setTrace(session.getTrace(), TraceObject.CONNECTION, getNextId(TraceObject.CONNECTION)); + this.user = user; + this.url = url; + this.watcher = null; + } + + private void closeOld() { + while (true) { + CloseWatcher w = CloseWatcher.pollUnclosed(); + if (w == null) { + break; + } + try { + w.getCloseable().close(); + } catch (Exception e) { + trace.error(e, "closing session"); + } + // there was an unclosed object - + // keep the stack trace from now on + keepOpenStackTrace = true; + String s = w.getOpenStackTrace(); + Exception ex = DbException + .get(ErrorCode.TRACE_CONNECTION_NOT_CLOSED); + trace.error(ex, s); + } + } + + /** + * Creates a new statement. + * + * @return the new statement + * @throws SQLException if the connection is closed + */ + @Override + public Statement createStatement() throws SQLException { + try { + int id = getNextId(TraceObject.STATEMENT); + debugCodeAssign("Statement", TraceObject.STATEMENT, id, "createStatement()"); + checkClosed(); + return new JdbcStatement(this, id, ResultSet.TYPE_FORWARD_ONLY, Constants.DEFAULT_RESULT_SET_CONCURRENCY); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Creates a statement with the specified result set type and concurrency. + * + * @param resultSetType the result set type (ResultSet.TYPE_*) + * @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*) + * @return the statement + * @throws SQLException if the connection is closed or the result set type + * or concurrency are not supported + */ + @Override + public Statement createStatement(int resultSetType, + int resultSetConcurrency) throws SQLException { + try { + int id = getNextId(TraceObject.STATEMENT); + if (isDebugEnabled()) { + debugCodeAssign("Statement", TraceObject.STATEMENT, id, + "createStatement(" + resultSetType + ", " + resultSetConcurrency + ')'); + } + checkTypeConcurrency(resultSetType, resultSetConcurrency); + checkClosed(); + return new JdbcStatement(this, id, resultSetType, resultSetConcurrency); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Creates a statement with the specified result set type, concurrency, and + * holdability. + * + * @param resultSetType the result set type (ResultSet.TYPE_*) + * @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*) + * @param resultSetHoldability the holdability (ResultSet.HOLD* / CLOSE*) + * @return the statement + * @throws SQLException if the connection is closed or the result set type, + * concurrency, or holdability are not supported + */ + @Override + public Statement createStatement(int resultSetType, + int resultSetConcurrency, int resultSetHoldability) + throws SQLException { + try { + int id = getNextId(TraceObject.STATEMENT); + if (isDebugEnabled()) { + debugCodeAssign("Statement", TraceObject.STATEMENT, id, + "createStatement(" + resultSetType + ", " + + resultSetConcurrency + ", " + + resultSetHoldability + ')'); + } + checkTypeConcurrency(resultSetType, resultSetConcurrency); + checkHoldability(resultSetHoldability); + checkClosed(); + return new JdbcStatement(this, id, resultSetType, resultSetConcurrency); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Creates a new prepared statement. + * + * @param sql the SQL statement + * @return the prepared statement + * @throws SQLException if the connection is closed + */ + @Override + public PreparedStatement prepareStatement(String sql) throws SQLException { + try { + int id = getNextId(TraceObject.PREPARED_STATEMENT); + if (isDebugEnabled()) { + debugCodeAssign("PreparedStatement", TraceObject.PREPARED_STATEMENT, id, + "prepareStatement(" + quote(sql) + ')'); + } + checkClosed(); + sql = translateSQL(sql); + return new JdbcPreparedStatement(this, sql, id, ResultSet.TYPE_FORWARD_ONLY, + Constants.DEFAULT_RESULT_SET_CONCURRENCY, null); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the database meta data for this database. + * + * @return the database meta data + * @throws SQLException if the connection is closed + */ + @Override + public DatabaseMetaData getMetaData() throws SQLException { + try { + int id = getNextId(TraceObject.DATABASE_META_DATA); + debugCodeAssign("DatabaseMetaData", TraceObject.DATABASE_META_DATA, id, "getMetaData()"); + checkClosed(); + return new JdbcDatabaseMetaData(this, trace, id); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * INTERNAL + * @return session + */ + public Session getSession() { + return session; + } + + /** + * Closes this connection. All open statements, prepared statements and + * result sets that where created by this connection become invalid after + * calling this method. If there is an uncommitted transaction, it will be + * rolled back. + */ + @Override + public synchronized void close() throws SQLException { + try { + debugCodeCall("close"); + if (session == null) { + return; + } + CloseWatcher.unregister(watcher); + session.cancel(); + synchronized (session) { + if (executingStatement != null) { + try { + executingStatement.cancel(); + } catch (NullPointerException | SQLException e) { + // ignore + } + } + try { + if (!session.isClosed()) { + try { + if (session.hasPendingTransaction()) { + try { + rollbackInternal(); + } catch (DbException e) { + // ignore if the connection is broken or database shut down + if (e.getErrorCode() != ErrorCode.CONNECTION_BROKEN_1 && + e.getErrorCode() != ErrorCode.DATABASE_IS_CLOSED) { + throw e; + } + } + } + closePreparedCommands(); + } finally { + session.close(); + } + } + } finally { + session = null; + } + } + } catch (Throwable e) { + throw logAndConvert(e); + } + } + + private void closePreparedCommands() { + commit = closeAndSetNull(commit); + rollback = closeAndSetNull(rollback); + getReadOnly = closeAndSetNull(getReadOnly); + getGeneratedKeys = closeAndSetNull(getGeneratedKeys); + getQueryTimeout = closeAndSetNull(getQueryTimeout); + setQueryTimeout = closeAndSetNull(setQueryTimeout); + } + + private static CommandInterface closeAndSetNull(CommandInterface command) { + if (command != null) { + command.close(); + } + return null; + } + + /** + * Switches auto commit on or off. Enabling it commits an uncommitted + * transaction, if there is one. + * + * @param autoCommit true for auto commit on, false for off + * @throws SQLException if the connection is closed + */ + @Override + public synchronized void setAutoCommit(boolean autoCommit) + throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setAutoCommit(" + autoCommit + ')'); + } + checkClosed(); + synchronized (session) { + if (autoCommit && !session.getAutoCommit()) { + commit(); + } + session.setAutoCommit(autoCommit); + } + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the current setting for auto commit. + * + * @return true for on, false for off + * @throws SQLException if the connection is closed + */ + @Override + public synchronized boolean getAutoCommit() throws SQLException { + try { + checkClosed(); + debugCodeCall("getAutoCommit"); + return session.getAutoCommit(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Commits the current transaction. This call has only an effect if auto + * commit is switched off. + * + * @throws SQLException if the connection is closed + */ + @Override + public synchronized void commit() throws SQLException { + try { + debugCodeCall("commit"); + checkClosed(); + if (SysProperties.FORCE_AUTOCOMMIT_OFF_ON_COMMIT + && getAutoCommit()) { + throw DbException.get(ErrorCode.METHOD_DISABLED_ON_AUTOCOMMIT_TRUE, "commit()"); + } + commit = prepareCommand("COMMIT", commit); + commit.executeUpdate(null); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Rolls back the current transaction. This call has only an effect if auto + * commit is switched off. + * + * @throws SQLException if the connection is closed + */ + @Override + public synchronized void rollback() throws SQLException { + try { + debugCodeCall("rollback"); + checkClosed(); + if (SysProperties.FORCE_AUTOCOMMIT_OFF_ON_COMMIT + && getAutoCommit()) { + throw DbException.get(ErrorCode.METHOD_DISABLED_ON_AUTOCOMMIT_TRUE, "rollback()"); + } + rollbackInternal(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns true if this connection has been closed. + * + * @return true if close was called + */ + @Override + public boolean isClosed() throws SQLException { + try { + debugCodeCall("isClosed"); + return session == null || session.isClosed(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Translates a SQL statement into the database grammar. + * + * @param sql the SQL statement with or without JDBC escape sequences + * @return the translated statement + * @throws SQLException if the connection is closed + */ + @Override + public String nativeSQL(String sql) throws SQLException { + try { + debugCodeCall("nativeSQL", sql); + checkClosed(); + return translateSQL(sql); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * According to the JDBC specs, this setting is only a hint to the database + * to enable optimizations - it does not cause writes to be prohibited. + * + * @param readOnly ignored + * @throws SQLException if the connection is closed + */ + @Override + public void setReadOnly(boolean readOnly) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setReadOnly(" + readOnly + ')'); + } + checkClosed(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns true if the database is read-only. + * + * @return if the database is read-only + * @throws SQLException if the connection is closed + */ + @Override + public boolean isReadOnly() throws SQLException { + try { + debugCodeCall("isReadOnly"); + checkClosed(); + getReadOnly = prepareCommand("CALL READONLY()", getReadOnly); + ResultInterface result = getReadOnly.executeQuery(0, false); + result.next(); + return result.currentRow()[0].getBoolean(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Set the default catalog name. This call is ignored. + * + * @param catalog ignored + * @throws SQLException if the connection is closed + */ + @Override + public void setCatalog(String catalog) throws SQLException { + try { + debugCodeCall("setCatalog", catalog); + checkClosed(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the current catalog name. + * + * @return the catalog name + * @throws SQLException if the connection is closed + */ + @Override + public String getCatalog() throws SQLException { + try { + debugCodeCall("getCatalog"); + checkClosed(); + if (catalog == null) { + CommandInterface cat = prepareCommand("CALL DATABASE()", + Integer.MAX_VALUE); + ResultInterface result = cat.executeQuery(0, false); + result.next(); + catalog = result.currentRow()[0].getString(); + cat.close(); + } + return catalog; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the first warning reported by calls on this object. + * + * @return null + */ + @Override + public SQLWarning getWarnings() throws SQLException { + try { + debugCodeCall("getWarnings"); + checkClosed(); + return null; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Clears all warnings. + */ + @Override + public void clearWarnings() throws SQLException { + try { + debugCodeCall("clearWarnings"); + checkClosed(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Creates a prepared statement with the specified result set type and + * concurrency. + * + * @param sql the SQL statement + * @param resultSetType the result set type (ResultSet.TYPE_*) + * @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*) + * @return the prepared statement + * @throws SQLException if the connection is closed or the result set type + * or concurrency are not supported + */ + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, + int resultSetConcurrency) throws SQLException { + try { + int id = getNextId(TraceObject.PREPARED_STATEMENT); + if (isDebugEnabled()) { + debugCodeAssign("PreparedStatement", TraceObject.PREPARED_STATEMENT, id, + "prepareStatement(" + quote(sql) + ", " + resultSetType + ", " + resultSetConcurrency + ')'); + } + checkTypeConcurrency(resultSetType, resultSetConcurrency); + checkClosed(); + sql = translateSQL(sql); + return new JdbcPreparedStatement(this, sql, id, resultSetType, resultSetConcurrency, null); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Changes the current transaction isolation level. Calling this method will + * commit an open transaction, even if the new level is the same as the old + * one. + * + * @param level the new transaction isolation level: + * Connection.TRANSACTION_READ_UNCOMMITTED, + * Connection.TRANSACTION_READ_COMMITTED, + * Connection.TRANSACTION_REPEATABLE_READ, + * 6 (SNAPSHOT), or + * Connection.TRANSACTION_SERIALIZABLE + * @throws SQLException if the connection is closed or the isolation level + * is not valid + */ + @Override + public void setTransactionIsolation(int level) throws SQLException { + try { + debugCodeCall("setTransactionIsolation", level); + checkClosed(); + if (!getAutoCommit()) { + commit(); + } + session.setIsolationLevel(IsolationLevel.fromJdbc(level)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * INTERNAL + */ + void setQueryTimeout(int seconds) throws SQLException { + try { + debugCodeCall("setQueryTimeout", seconds); + checkClosed(); + setQueryTimeout = prepareCommand("SET QUERY_TIMEOUT ?", + setQueryTimeout); + setQueryTimeout.getParameters().get(0) + .setValue(ValueInteger.get(seconds * 1000), false); + setQueryTimeout.executeUpdate(null); + queryTimeoutCache = seconds; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * INTERNAL + */ + int getQueryTimeout() throws SQLException { + try { + if (queryTimeoutCache == -1) { + checkClosed(); + getQueryTimeout = prepareCommand(!session.isOldInformationSchema() + ? "SELECT SETTING_VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME=?" + : "SELECT `VALUE` FROM INFORMATION_SCHEMA.SETTINGS WHERE NAME=?", getQueryTimeout); + getQueryTimeout.getParameters().get(0) + .setValue(ValueVarchar.get("QUERY_TIMEOUT"), false); + ResultInterface result = getQueryTimeout.executeQuery(0, false); + result.next(); + int queryTimeout = result.currentRow()[0].getInt(); + result.close(); + if (queryTimeout != 0) { + // round to the next second, otherwise 999 millis would + // return 0 seconds + queryTimeout = (queryTimeout + 999) / 1000; + } + queryTimeoutCache = queryTimeout; + } + return queryTimeoutCache; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the current transaction isolation level. + * + * @return the isolation level + * @throws SQLException if the connection is closed + */ + @Override + public int getTransactionIsolation() throws SQLException { + try { + debugCodeCall("getTransactionIsolation"); + checkClosed(); + return session.getIsolationLevel().getJdbc(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Changes the current result set holdability. + * + * @param holdability ResultSet.HOLD_CURSORS_OVER_COMMIT or + * ResultSet.CLOSE_CURSORS_AT_COMMIT; + * @throws SQLException if the connection is closed or the holdability is + * not supported + */ + @Override + public void setHoldability(int holdability) throws SQLException { + try { + debugCodeCall("setHoldability", holdability); + checkClosed(); + checkHoldability(holdability); + this.holdability = holdability; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the current result set holdability. + * + * @return the holdability + * @throws SQLException if the connection is closed + */ + @Override + public int getHoldability() throws SQLException { + try { + debugCodeCall("getHoldability"); + checkClosed(); + return holdability; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the type map. + * + * @return null + * @throws SQLException if the connection is closed + */ + @Override + public Map> getTypeMap() throws SQLException { + try { + debugCodeCall("getTypeMap"); + checkClosed(); + return null; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * [Partially supported] Sets the type map. This is only supported if the + * map is empty or null. + */ + @Override + public void setTypeMap(Map> map) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setTypeMap(" + quoteMap(map) + ')'); + } + checkMap(map); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Creates a new callable statement. + * + * @param sql the SQL statement + * @return the callable statement + * @throws SQLException if the connection is closed or the statement is not + * valid + */ + @Override + public CallableStatement prepareCall(String sql) throws SQLException { + try { + int id = getNextId(TraceObject.CALLABLE_STATEMENT); + if (isDebugEnabled()) { + debugCodeAssign("CallableStatement", TraceObject.CALLABLE_STATEMENT, id, + "prepareCall(" + quote(sql) + ')'); + } + checkClosed(); + sql = translateSQL(sql); + return new JdbcCallableStatement(this, sql, id, + ResultSet.TYPE_FORWARD_ONLY, + Constants.DEFAULT_RESULT_SET_CONCURRENCY); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Creates a callable statement with the specified result set type and + * concurrency. + * + * @param sql the SQL statement + * @param resultSetType the result set type (ResultSet.TYPE_*) + * @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*) + * @return the callable statement + * @throws SQLException if the connection is closed or the result set type + * or concurrency are not supported + */ + @Override + public CallableStatement prepareCall(String sql, int resultSetType, + int resultSetConcurrency) throws SQLException { + try { + int id = getNextId(TraceObject.CALLABLE_STATEMENT); + if (isDebugEnabled()) { + debugCodeAssign("CallableStatement", TraceObject.CALLABLE_STATEMENT, id, + "prepareCall(" + quote(sql) + ", " + resultSetType + ", " + resultSetConcurrency + ')'); + } + checkTypeConcurrency(resultSetType, resultSetConcurrency); + checkClosed(); + sql = translateSQL(sql); + return new JdbcCallableStatement(this, sql, id, resultSetType, + resultSetConcurrency); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Creates a callable statement with the specified result set type, + * concurrency, and holdability. + * + * @param sql the SQL statement + * @param resultSetType the result set type (ResultSet.TYPE_*) + * @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*) + * @param resultSetHoldability the holdability (ResultSet.HOLD* / CLOSE*) + * @return the callable statement + * @throws SQLException if the connection is closed or the result set type, + * concurrency, or holdability are not supported + */ + @Override + public CallableStatement prepareCall(String sql, int resultSetType, + int resultSetConcurrency, int resultSetHoldability) + throws SQLException { + try { + int id = getNextId(TraceObject.CALLABLE_STATEMENT); + if (isDebugEnabled()) { + debugCodeAssign("CallableStatement", TraceObject.CALLABLE_STATEMENT, id, + "prepareCall(" + quote(sql) + ", " + resultSetType + ", " + resultSetConcurrency + ", " + + resultSetHoldability + ')'); + } + checkTypeConcurrency(resultSetType, resultSetConcurrency); + checkHoldability(resultSetHoldability); + checkClosed(); + sql = translateSQL(sql); + return new JdbcCallableStatement(this, sql, id, resultSetType, + resultSetConcurrency); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Creates a new unnamed savepoint. + * + * @return the new savepoint + */ + @Override + public Savepoint setSavepoint() throws SQLException { + try { + int id = getNextId(TraceObject.SAVEPOINT); + debugCodeAssign("Savepoint", TraceObject.SAVEPOINT, id, "setSavepoint()"); + checkClosed(); + CommandInterface set = prepareCommand( + "SAVEPOINT " + JdbcSavepoint.getName(null, savepointId), + Integer.MAX_VALUE); + set.executeUpdate(null); + JdbcSavepoint savepoint = new JdbcSavepoint(this, savepointId, null, + trace, id); + savepointId++; + return savepoint; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Creates a new named savepoint. + * + * @param name the savepoint name + * @return the new savepoint + */ + @Override + public Savepoint setSavepoint(String name) throws SQLException { + try { + int id = getNextId(TraceObject.SAVEPOINT); + if (isDebugEnabled()) { + debugCodeAssign("Savepoint", TraceObject.SAVEPOINT, id, "setSavepoint(" + quote(name) + ')'); + } + checkClosed(); + CommandInterface set = prepareCommand( + "SAVEPOINT " + JdbcSavepoint.getName(name, 0), + Integer.MAX_VALUE); + set.executeUpdate(null); + return new JdbcSavepoint(this, 0, name, trace, id); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Rolls back to a savepoint. + * + * @param savepoint the savepoint + */ + @Override + public void rollback(Savepoint savepoint) throws SQLException { + try { + JdbcSavepoint sp = convertSavepoint(savepoint); + if (isDebugEnabled()) { + debugCode("rollback(" + sp.getTraceObjectName() + ')'); + } + checkClosed(); + sp.rollback(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Releases a savepoint. + * + * @param savepoint the savepoint to release + */ + @Override + public void releaseSavepoint(Savepoint savepoint) throws SQLException { + try { + debugCode("releaseSavepoint(savepoint)"); + checkClosed(); + convertSavepoint(savepoint).release(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private static JdbcSavepoint convertSavepoint(Savepoint savepoint) { + if (!(savepoint instanceof JdbcSavepoint)) { + throw DbException.get(ErrorCode.SAVEPOINT_IS_INVALID_1, + String.valueOf(savepoint)); + } + return (JdbcSavepoint) savepoint; + } + + /** + * Creates a prepared statement with the specified result set type, + * concurrency, and holdability. + * + * @param sql the SQL statement + * @param resultSetType the result set type (ResultSet.TYPE_*) + * @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*) + * @param resultSetHoldability the holdability (ResultSet.HOLD* / CLOSE*) + * @return the prepared statement + * @throws SQLException if the connection is closed or the result set type, + * concurrency, or holdability are not supported + */ + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, + int resultSetConcurrency, int resultSetHoldability) + throws SQLException { + try { + int id = getNextId(TraceObject.PREPARED_STATEMENT); + if (isDebugEnabled()) { + debugCodeAssign("PreparedStatement", TraceObject.PREPARED_STATEMENT, id, + "prepareStatement(" + quote(sql) + ", " + resultSetType + ", " + resultSetConcurrency + ", " + + resultSetHoldability + ')'); + } + checkTypeConcurrency(resultSetType, resultSetConcurrency); + checkHoldability(resultSetHoldability); + checkClosed(); + sql = translateSQL(sql); + return new JdbcPreparedStatement(this, sql, id, resultSetType, resultSetConcurrency, null); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Creates a new prepared statement. + * + * @param sql the SQL statement + * @param autoGeneratedKeys + * {@link Statement#RETURN_GENERATED_KEYS} if generated keys should + * be available for retrieval, {@link Statement#NO_GENERATED_KEYS} if + * generated keys should not be available + * @return the prepared statement + * @throws SQLException if the connection is closed + */ + @Override + public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) + throws SQLException { + try { + int id = getNextId(TraceObject.PREPARED_STATEMENT); + if (isDebugEnabled()) { + debugCodeAssign("PreparedStatement", TraceObject.PREPARED_STATEMENT, id, + "prepareStatement(" + quote(sql) + ", " + autoGeneratedKeys + ')'); + } + checkClosed(); + sql = translateSQL(sql); + return new JdbcPreparedStatement(this, sql, id, ResultSet.TYPE_FORWARD_ONLY, + Constants.DEFAULT_RESULT_SET_CONCURRENCY, autoGeneratedKeys == Statement.RETURN_GENERATED_KEYS); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Creates a new prepared statement. + * + * @param sql the SQL statement + * @param columnIndexes + * an array of column indexes indicating the columns with generated + * keys that should be returned from the inserted row + * @return the prepared statement + * @throws SQLException if the connection is closed + */ + @Override + public PreparedStatement prepareStatement(String sql, int[] columnIndexes) + throws SQLException { + try { + int id = getNextId(TraceObject.PREPARED_STATEMENT); + if (isDebugEnabled()) { + debugCodeAssign("PreparedStatement", TraceObject.PREPARED_STATEMENT, id, + "prepareStatement(" + quote(sql) + ", " + quoteIntArray(columnIndexes) + ')'); + } + checkClosed(); + sql = translateSQL(sql); + return new JdbcPreparedStatement(this, sql, id, ResultSet.TYPE_FORWARD_ONLY, + Constants.DEFAULT_RESULT_SET_CONCURRENCY, columnIndexes); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Creates a new prepared statement. + * + * @param sql the SQL statement + * @param columnNames + * an array of column names indicating the columns with generated + * keys that should be returned from the inserted row + * @return the prepared statement + * @throws SQLException if the connection is closed + */ + @Override + public PreparedStatement prepareStatement(String sql, String[] columnNames) + throws SQLException { + try { + int id = getNextId(TraceObject.PREPARED_STATEMENT); + if (isDebugEnabled()) { + debugCodeAssign("PreparedStatement", TraceObject.PREPARED_STATEMENT, id, + "prepareStatement(" + quote(sql) + ", " + quoteArray(columnNames) + ')'); + } + checkClosed(); + sql = translateSQL(sql); + return new JdbcPreparedStatement(this, sql, id, ResultSet.TYPE_FORWARD_ONLY, + Constants.DEFAULT_RESULT_SET_CONCURRENCY, columnNames); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + // ============================================================= + + /** + * Prepare an command. This will parse the SQL statement. + * + * @param sql the SQL statement + * @param fetchSize the fetch size (used in remote connections) + * @return the command + */ + CommandInterface prepareCommand(String sql, int fetchSize) { + return session.prepareCommand(sql, fetchSize); + } + + private CommandInterface prepareCommand(String sql, CommandInterface old) { + return old == null ? session.prepareCommand(sql, Integer.MAX_VALUE) + : old; + } + + private static int translateGetEnd(String sql, int i, char c) { + int len = sql.length(); + switch (c) { + case '$': { + if (i < len - 1 && sql.charAt(i + 1) == '$' + && (i == 0 || sql.charAt(i - 1) <= ' ')) { + int j = sql.indexOf("$$", i + 2); + if (j < 0) { + throw DbException.getSyntaxError(sql, i); + } + return j + 1; + } + return i; + } + case '\'': { + int j = sql.indexOf('\'', i + 1); + if (j < 0) { + throw DbException.getSyntaxError(sql, i); + } + return j; + } + case '"': { + int j = sql.indexOf('"', i + 1); + if (j < 0) { + throw DbException.getSyntaxError(sql, i); + } + return j; + } + case '/': { + checkRunOver(i + 1, len, sql); + if (sql.charAt(i + 1) == '*') { + // block comment + int j = sql.indexOf("*/", i + 2); + if (j < 0) { + throw DbException.getSyntaxError(sql, i); + } + i = j + 1; + } else if (sql.charAt(i + 1) == '/') { + // single line comment + i += 2; + while (i < len && (c = sql.charAt(i)) != '\r' && c != '\n') { + i++; + } + } + return i; + } + case '-': { + checkRunOver(i + 1, len, sql); + if (sql.charAt(i + 1) == '-') { + // single line comment + i += 2; + while (i < len && (c = sql.charAt(i)) != '\r' && c != '\n') { + i++; + } + } + return i; + } + default: + throw DbException.getInternalError("c=" + c); + } + } + + /** + * Convert JDBC escape sequences in the SQL statement. This method throws an + * exception if the SQL statement is null. + * + * @param sql the SQL statement with or without JDBC escape sequences + * @return the SQL statement without JDBC escape sequences + */ + private static String translateSQL(String sql) { + return translateSQL(sql, true); + } + + /** + * Convert JDBC escape sequences in the SQL statement if required. This + * method throws an exception if the SQL statement is null. + * + * @param sql the SQL statement with or without JDBC escape sequences + * @param escapeProcessing whether escape sequences should be replaced + * @return the SQL statement without JDBC escape sequences + */ + static String translateSQL(String sql, boolean escapeProcessing) { + if (sql == null) { + throw DbException.getInvalidValueException("SQL", null); + } + if (!escapeProcessing || sql.indexOf('{') < 0) { + return sql; + } + return translateSQLImpl(sql); + } + + private static String translateSQLImpl(String sql) { + int len = sql.length(); + char[] chars = null; + int level = 0; + for (int i = 0; i < len; i++) { + char c = sql.charAt(i); + switch (c) { + case '\'': + case '"': + case '/': + case '-': + i = translateGetEnd(sql, i, c); + break; + case '{': + level++; + if (chars == null) { + chars = sql.toCharArray(); + } + chars[i] = ' '; + while (Character.isSpaceChar(chars[i])) { + i++; + checkRunOver(i, len, sql); + } + int start = i; + if (chars[i] >= '0' && chars[i] <= '9') { + chars[i - 1] = '{'; + while (true) { + checkRunOver(i, len, sql); + c = chars[i]; + if (c == '}') { + break; + } + switch (c) { + case '\'': + case '"': + case '/': + case '-': + i = translateGetEnd(sql, i, c); + break; + default: + } + i++; + } + level--; + break; + } else if (chars[i] == '?') { + i++; + checkRunOver(i, len, sql); + while (Character.isSpaceChar(chars[i])) { + i++; + checkRunOver(i, len, sql); + } + if (sql.charAt(i) != '=') { + throw DbException.getSyntaxError(sql, i, "="); + } + i++; + checkRunOver(i, len, sql); + while (Character.isSpaceChar(chars[i])) { + i++; + checkRunOver(i, len, sql); + } + } + while (!Character.isSpaceChar(chars[i])) { + i++; + checkRunOver(i, len, sql); + } + int remove = 0; + if (found(sql, start, "fn")) { + remove = 2; + } else if (found(sql, start, "escape")) { + break; + } else if (found(sql, start, "call")) { + break; + } else if (found(sql, start, "oj")) { + remove = 2; + } else if (found(sql, start, "ts")) { + break; + } else if (found(sql, start, "t")) { + break; + } else if (found(sql, start, "d")) { + break; + } else if (found(sql, start, "params")) { + remove = "params".length(); + } + for (i = start; remove > 0; i++, remove--) { + chars[i] = ' '; + } + break; + case '}': + if (--level < 0) { + throw DbException.getSyntaxError(sql, i); + } + chars[i] = ' '; + break; + case '$': + i = translateGetEnd(sql, i, c); + break; + default: + } + } + if (level != 0) { + throw DbException.getSyntaxError(sql, sql.length() - 1); + } + if (chars != null) { + sql = new String(chars); + } + return sql; + } + + private static void checkRunOver(int i, int len, String sql) { + if (i >= len) { + throw DbException.getSyntaxError(sql, i); + } + } + + private static boolean found(String sql, int start, String other) { + return sql.regionMatches(true, start, other, 0, other.length()); + } + + private static void checkTypeConcurrency(int resultSetType, + int resultSetConcurrency) { + switch (resultSetType) { + case ResultSet.TYPE_FORWARD_ONLY: + case ResultSet.TYPE_SCROLL_INSENSITIVE: + case ResultSet.TYPE_SCROLL_SENSITIVE: + break; + default: + throw DbException.getInvalidValueException("resultSetType", + resultSetType); + } + switch (resultSetConcurrency) { + case ResultSet.CONCUR_READ_ONLY: + case ResultSet.CONCUR_UPDATABLE: + break; + default: + throw DbException.getInvalidValueException("resultSetConcurrency", + resultSetConcurrency); + } + } + + private static void checkHoldability(int resultSetHoldability) { + // TODO compatibility / correctness: DBPool uses + // ResultSet.HOLD_CURSORS_OVER_COMMIT + if (resultSetHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT + && resultSetHoldability != ResultSet.CLOSE_CURSORS_AT_COMMIT) { + throw DbException.getInvalidValueException("resultSetHoldability", + resultSetHoldability); + } + } + + /** + * INTERNAL. Check if this connection is closed. + * + * @throws DbException if the connection or session is closed + */ + protected void checkClosed() { + if (session == null) { + throw DbException.get(ErrorCode.OBJECT_CLOSED); + } + if (session.isClosed()) { + throw DbException.get(ErrorCode.DATABASE_CALLED_AT_SHUTDOWN); + } + } + + String getURL() { + checkClosed(); + return url; + } + + String getUser() { + checkClosed(); + return user; + } + + private void rollbackInternal() { + rollback = prepareCommand("ROLLBACK", rollback); + rollback.executeUpdate(null); + } + + /** + * INTERNAL + */ + void setExecutingStatement(Statement stat) { + executingStatement = stat; + } + + /** + * Create a new empty Clob object. + * + * @return the object + */ + @Override + public Clob createClob() throws SQLException { + try { + int id = getNextId(TraceObject.CLOB); + debugCodeAssign("Clob", TraceObject.CLOB, id, "createClob()"); + checkClosed(); + return new JdbcClob(this, ValueVarchar.EMPTY, JdbcLob.State.NEW, id); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Create a new empty Blob object. + * + * @return the object + */ + @Override + public Blob createBlob() throws SQLException { + try { + int id = getNextId(TraceObject.BLOB); + debugCodeAssign("Blob", TraceObject.BLOB, id, "createClob()"); + checkClosed(); + return new JdbcBlob(this, ValueVarbinary.EMPTY, JdbcLob.State.NEW, id); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Create a new empty NClob object. + * + * @return the object + */ + @Override + public NClob createNClob() throws SQLException { + try { + int id = getNextId(TraceObject.CLOB); + debugCodeAssign("NClob", TraceObject.CLOB, id, "createNClob()"); + checkClosed(); + return new JdbcClob(this, ValueVarchar.EMPTY, JdbcLob.State.NEW, id); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Create a new SQLXML object with no data. + * + * @return the object + */ + @Override + public SQLXML createSQLXML() throws SQLException { + try { + int id = getNextId(TraceObject.SQLXML); + debugCodeAssign("SQLXML", TraceObject.SQLXML, id, "createSQLXML()"); + checkClosed(); + return new JdbcSQLXML(this, ValueVarchar.EMPTY, JdbcLob.State.NEW, id); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Create a new Array object. + * + * @param typeName the type name + * @param elements the values + * @return the array + */ + @Override + public Array createArrayOf(String typeName, Object[] elements) + throws SQLException { + try { + int id = getNextId(TraceObject.ARRAY); + debugCodeAssign("Array", TraceObject.ARRAY, id, "createArrayOf()"); + checkClosed(); + Value value = ValueToObjectConverter.objectToValue(session, elements, Value.ARRAY); + return new JdbcArray(this, value, id); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * [Not supported] Create a new empty Struct object. + */ + @Override + public Struct createStruct(String typeName, Object[] attributes) + throws SQLException { + throw unsupported("Struct"); + } + + /** + * Returns true if this connection is still valid. + * + * @param timeout the number of seconds to wait for the database to respond + * (ignored) + * @return true if the connection is valid. + */ + @Override + public synchronized boolean isValid(int timeout) { + try { + debugCodeCall("isValid", timeout); + if (session == null || session.isClosed()) { + return false; + } + // force a network round trip (if networked) + getTransactionIsolation(); + return true; + } catch (Exception e) { + // this method doesn't throw an exception, but it logs it + logAndConvert(e); + return false; + } + } + + /** + * Set a client property. This method always throws a SQLClientInfoException + * in standard mode. In compatibility mode the following properties are + * supported: + *
    + *
  • DB2: The properties: ApplicationName, ClientAccountingInformation, + * ClientUser and ClientCorrelationToken are supported.
  • + *
  • MySQL: All property names are supported.
  • + *
  • Oracle: All properties in the form <namespace>.<key name> + * are supported.
  • + *
  • PostgreSQL: The ApplicationName property is supported.
  • + *
+ * For unsupported properties a SQLClientInfoException is thrown. + * + * @param name the name of the property + * @param value the value + */ + @Override + public void setClientInfo(String name, String value) + throws SQLClientInfoException { + try { + if (isDebugEnabled()) { + debugCode("setClientInfo(" + quote(name) + ", " + quote(value) + ')'); + } + checkClosed(); + + // no change to property: Ignore call. This early exit fixes a + // problem with websphere liberty resetting the client info of a + // pooled connection to its initial values. + if (Objects.equals(value, getClientInfo(name))) { + return; + } + + if (isInternalProperty(name)) { + throw new SQLClientInfoException( + "Property name '" + name + " is used internally by H2.", + Collections.emptyMap()); + } + + Pattern clientInfoNameRegEx = getMode().supportedClientInfoPropertiesRegEx; + + if (clientInfoNameRegEx != null + && clientInfoNameRegEx.matcher(name).matches()) { + if (clientInfo == null) { + clientInfo = new HashMap<>(); + } + clientInfo.put(name, value); + } else { + throw new SQLClientInfoException( + "Client info name '" + name + "' not supported.", + Collections.emptyMap()); + } + } catch (Exception e) { + throw convertToClientInfoException(logAndConvert(e)); + } + } + + private static boolean isInternalProperty(String name) { + return NUM_SERVERS.equals(name) || name.startsWith(PREFIX_SERVER); + } + + private static SQLClientInfoException convertToClientInfoException( + SQLException x) { + if (x instanceof SQLClientInfoException) { + return (SQLClientInfoException) x; + } + return new SQLClientInfoException(x.getMessage(), x.getSQLState(), + x.getErrorCode(), null, null); + } + + /** + * Set the client properties. This replaces all existing properties. This + * method always throws a SQLClientInfoException in standard mode. In + * compatibility mode some properties may be supported (see + * setProperty(String, String) for details). + * + * @param properties the properties (ignored) + */ + @Override + public void setClientInfo(Properties properties) + throws SQLClientInfoException { + try { + if (isDebugEnabled()) { + debugCode("setClientInfo(properties)"); + } + checkClosed(); + if (clientInfo == null) { + clientInfo = new HashMap<>(); + } else { + clientInfo.clear(); + } + for (Map.Entry entry : properties.entrySet()) { + setClientInfo((String) entry.getKey(), + (String) entry.getValue()); + } + } catch (Exception e) { + throw convertToClientInfoException(logAndConvert(e)); + } + } + + /** + * Get the client properties. + * + * @return the property list + */ + @Override + public Properties getClientInfo() throws SQLException { + try { + debugCodeCall("getClientInfo"); + checkClosed(); + ArrayList serverList = session.getClusterServers(); + Properties p = new Properties(); + + if (clientInfo != null) { + for (Map.Entry entry : clientInfo.entrySet()) { + p.setProperty(entry.getKey(), entry.getValue()); + } + } + + p.setProperty(NUM_SERVERS, Integer.toString(serverList.size())); + for (int i = 0; i < serverList.size(); i++) { + p.setProperty(PREFIX_SERVER + i, serverList.get(i)); + } + + return p; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Get a client property. + * + * @param name the client info name + * @return the property value or null if the property is not found or not + * supported. + */ + @Override + public String getClientInfo(String name) throws SQLException { + try { + if (isDebugEnabled()) { + debugCodeCall("getClientInfo", name); + } + checkClosed(); + if (name == null) { + throw DbException.getInvalidValueException("name", null); + } + return getClientInfo().getProperty(name); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Return an object of this class if possible. + * + * @param iface the class + * @return this + */ + @Override + @SuppressWarnings("unchecked") + public T unwrap(Class iface) throws SQLException { + try { + if (isWrapperFor(iface)) { + return (T) this; + } + throw DbException.getInvalidValueException("iface", iface); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if unwrap can return an object of this class. + * + * @param iface the class + * @return whether or not the interface is assignable from this class + */ + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return iface != null && iface.isAssignableFrom(getClass()); + } + + /** + * Create a Clob value from this reader. + * + * @param x the reader + * @param length the length (if smaller or equal than 0, all data until the + * end of file is read) + * @return the value + */ + Value createClob(Reader x, long length) { + if (x == null) { + return ValueNull.INSTANCE; + } + if (length <= 0) { + length = -1; + } + return session.addTemporaryLob(session.getDataHandler().getLobStorage().createClob(x, length)); + } + + /** + * Create a Blob value from this input stream. + * + * @param x the input stream + * @param length the length (if smaller or equal than 0, all data until the + * end of file is read) + * @return the value + */ + Value createBlob(InputStream x, long length) { + if (x == null) { + return ValueNull.INSTANCE; + } + if (length <= 0) { + length = -1; + } + return session.addTemporaryLob(session.getDataHandler().getLobStorage().createBlob(x, length)); + } + + /** + * Sets the given schema name to access. Current implementation is case + * sensitive, i.e. requires schema name to be passed in correct case. + * + * @param schema the schema name + */ + @Override + public void setSchema(String schema) throws SQLException { + try { + if (isDebugEnabled()) { + debugCodeCall("setSchema", schema); + } + checkClosed(); + session.setCurrentSchemaName(schema); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Retrieves this current schema name for this connection. + * + * @return current schema name + */ + @Override + public String getSchema() throws SQLException { + try { + debugCodeCall("getSchema"); + checkClosed(); + return session.getCurrentSchemaName(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * [Not supported] + * + * @param executor the executor used by this method + */ + @Override + public void abort(Executor executor) { + // not supported + } + + /** + * [Not supported] + * + * @param executor the executor used by this method + * @param milliseconds the TCP connection timeout + */ + @Override + public void setNetworkTimeout(Executor executor, int milliseconds) { + // not supported + } + + /** + * [Not supported] + */ + @Override + public int getNetworkTimeout() { + return 0; + } + + /** + * Check that the given type map is either null or empty. + * + * @param map the type map + * @throws DbException if the map is not empty + */ + static void checkMap(Map> map) { + if (map != null && map.size() > 0) { + throw DbException.getUnsupportedException("map.size > 0"); + } + } + + /** + * INTERNAL + */ + @Override + public String toString() { + return getTraceObjectName() + ": url=" + url + " user=" + user; + } + + CompareMode getCompareMode() { + return session.getDataHandler().getCompareMode(); + } + + @Override + public Mode getMode() { + return session.getMode(); + } + + /** + * INTERNAL + * @return StaticSettings + */ + public StaticSettings getStaticSettings() { + checkClosed(); + return session.getStaticSettings(); + } + + @Override + public ValueTimestampTimeZone currentTimestamp() { + Session session = this.session; + if (session == null) { + throw DbException.get(ErrorCode.OBJECT_CLOSED); + } + return session.currentTimestamp(); + } + + @Override + public TimeZoneProvider currentTimeZone() { + Session session = this.session; + if (session == null) { + throw DbException.get(ErrorCode.OBJECT_CLOSED); + } + return session.currentTimeZone(); + } + + @Override + public JavaObjectSerializer getJavaObjectSerializer() { + Session session = this.session; + if (session == null) { + throw DbException.get(ErrorCode.OBJECT_CLOSED); + } + return session.getJavaObjectSerializer(); + } + + @Override + public boolean zeroBasedEnums() { + Session session = this.session; + if (session == null) { + throw DbException.get(ErrorCode.OBJECT_CLOSED); + } + return session.zeroBasedEnums(); + } + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcConnectionBackwardsCompat.java b/h2/src/main/org/h2/jdbc/JdbcConnectionBackwardsCompat.java new file mode 100644 index 0000000..ba85d7d --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcConnectionBackwardsCompat.java @@ -0,0 +1,16 @@ +/* + * 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.jdbc; + +/** + * Allows us to compile on older platforms, while still implementing the methods + * from the newer JDBC API. + */ +public interface JdbcConnectionBackwardsCompat { + + // compatibility interface + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcDatabaseMetaData.java b/h2/src/main/org/h2/jdbc/JdbcDatabaseMetaData.java new file mode 100644 index 0000000..842f3ae --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcDatabaseMetaData.java @@ -0,0 +1,2756 @@ +/* + * 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.jdbc; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.RowIdLifetime; +import java.sql.SQLException; +import java.util.Map.Entry; +import java.util.Properties; + +import org.h2.engine.Constants; +import org.h2.engine.Session; +import org.h2.jdbc.meta.DatabaseMeta; +import org.h2.jdbc.meta.DatabaseMetaLegacy; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.message.TraceObject; +import org.h2.mode.DefaultNullOrdering; +import org.h2.result.ResultInterface; +import org.h2.result.SimpleResult; +import org.h2.value.TypeInfo; +import org.h2.value.ValueInteger; +import org.h2.value.ValueVarchar; + +/** + * Represents the meta data for a database. + */ +public final class JdbcDatabaseMetaData extends TraceObject + implements DatabaseMetaData, JdbcDatabaseMetaDataBackwardsCompat { + + private final JdbcConnection conn; + + private final DatabaseMeta meta; + + JdbcDatabaseMetaData(JdbcConnection conn, Trace trace, int id) { + setTrace(trace, TraceObject.DATABASE_META_DATA, id); + this.conn = conn; + Session session = conn.getSession(); + meta = session.isOldInformationSchema() ? new DatabaseMetaLegacy(session) + : conn.getSession().getDatabaseMeta(); + } + + /** + * Returns the major version of this driver. + * + * @return the major version number + */ + @Override + public int getDriverMajorVersion() { + debugCodeCall("getDriverMajorVersion"); + return Constants.VERSION_MAJOR; + } + + /** + * Returns the minor version of this driver. + * + * @return the minor version number + */ + @Override + public int getDriverMinorVersion() { + debugCodeCall("getDriverMinorVersion"); + return Constants.VERSION_MINOR; + } + + /** + * Gets the database product name. + * + * @return the product name ("H2") + */ + @Override + public String getDatabaseProductName() { + debugCodeCall("getDatabaseProductName"); + // This value must stay like that, see + // https://hibernate.atlassian.net/browse/HHH-2682 + return "H2"; + } + + /** + * Gets the product version of the database. + * + * @return the product version + */ + @Override + public String getDatabaseProductVersion() throws SQLException { + try { + debugCodeCall("getDatabaseProductVersion"); + return meta.getDatabaseProductVersion(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the name of the JDBC driver. + * + * @return the driver name ("H2 JDBC Driver") + */ + @Override + public String getDriverName() { + debugCodeCall("getDriverName"); + return "H2 JDBC Driver"; + } + + /** + * Gets the version number of the driver. The format is + * [MajorVersion].[MinorVersion]. + * + * @return the version number + */ + @Override + public String getDriverVersion() { + debugCodeCall("getDriverVersion"); + return Constants.FULL_VERSION; + } + + /** + * Gets the list of tables in the database. The result set is sorted by + * TABLE_TYPE, TABLE_SCHEM, and TABLE_NAME. + * + *
    + *
  1. TABLE_CAT (String) table catalog
  2. + *
  3. TABLE_SCHEM (String) table schema
  4. + *
  5. TABLE_NAME (String) table name
  6. + *
  7. TABLE_TYPE (String) table type
  8. + *
  9. REMARKS (String) comment
  10. + *
  11. TYPE_CAT (String) always null
  12. + *
  13. TYPE_SCHEM (String) always null
  14. + *
  15. TYPE_NAME (String) always null
  16. + *
  17. SELF_REFERENCING_COL_NAME (String) always null
  18. + *
  19. REF_GENERATION (String) always null
  20. + *
  21. SQL (String) the create table statement or NULL for systems tables.
  22. + *
+ * + * @param catalog null (to get all objects) or the catalog name + * @param schemaPattern null (to get all objects) or a schema name + * (uppercase for unquoted names) + * @param tableNamePattern null (to get all objects) or a table name + * (uppercase for unquoted names) + * @param types null or a list of table types + * @return the list of columns + * @throws SQLException if the connection is closed + */ + @Override + public ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types) + throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getTables(" + quote(catalog) + ", " + quote(schemaPattern) + ", " + quote(tableNamePattern) + + ", " + quoteArray(types) + ')'); + } + return getResultSet(meta.getTables(catalog, schemaPattern, tableNamePattern, types)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the list of columns. The result set is sorted by TABLE_SCHEM, + * TABLE_NAME, and ORDINAL_POSITION. + * + *
    + *
  1. TABLE_CAT (String) table catalog
  2. + *
  3. TABLE_SCHEM (String) table schema
  4. + *
  5. TABLE_NAME (String) table name
  6. + *
  7. COLUMN_NAME (String) column name
  8. + *
  9. DATA_TYPE (int) data type (see java.sql.Types)
  10. + *
  11. TYPE_NAME (String) data type name ("INTEGER", "VARCHAR",...)
  12. + *
  13. COLUMN_SIZE (int) precision + * (values larger than 2 GB are returned as 2 GB)
  14. + *
  15. BUFFER_LENGTH (int) unused
  16. + *
  17. DECIMAL_DIGITS (int) scale (0 for INTEGER and VARCHAR)
  18. + *
  19. NUM_PREC_RADIX (int) radix
  20. + *
  21. NULLABLE (int) columnNoNulls or columnNullable
  22. + *
  23. REMARKS (String) comment
  24. + *
  25. COLUMN_DEF (String) default value
  26. + *
  27. SQL_DATA_TYPE (int) unused
  28. + *
  29. SQL_DATETIME_SUB (int) unused
  30. + *
  31. CHAR_OCTET_LENGTH (int) unused
  32. + *
  33. ORDINAL_POSITION (int) the column index (1,2,...)
  34. + *
  35. IS_NULLABLE (String) "NO" or "YES"
  36. + *
  37. SCOPE_CATALOG (String) always null
  38. + *
  39. SCOPE_SCHEMA (String) always null
  40. + *
  41. SCOPE_TABLE (String) always null
  42. + *
  43. SOURCE_DATA_TYPE (short) null
  44. + *
  45. IS_AUTOINCREMENT (String) "NO" or "YES"
  46. + *
  47. IS_GENERATEDCOLUMN (String) "NO" or "YES"
  48. + *
+ * + * @param catalog null (to get all objects) or the catalog name + * @param schemaPattern null (to get all objects) or a schema name + * (uppercase for unquoted names) + * @param tableNamePattern null (to get all objects) or a table name + * (uppercase for unquoted names) + * @param columnNamePattern null (to get all objects) or a column name + * (uppercase for unquoted names) + * @return the list of columns + * @throws SQLException if the connection is closed + */ + @Override + public ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, + String columnNamePattern) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getColumns(" + quote(catalog)+", " + +quote(schemaPattern)+", " + +quote(tableNamePattern)+", " + +quote(columnNamePattern)+')'); + } + return getResultSet(meta.getColumns(catalog, schemaPattern, tableNamePattern, columnNamePattern)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the list of indexes for this database. The primary key index (if + * there is one) is also listed, with the name PRIMARY_KEY. The result set + * is sorted by NON_UNIQUE ('false' first), TYPE, TABLE_SCHEM, INDEX_NAME, + * and ORDINAL_POSITION. + * + *
    + *
  1. TABLE_CAT (String) table catalog
  2. + *
  3. TABLE_SCHEM (String) table schema
  4. + *
  5. TABLE_NAME (String) table name
  6. + *
  7. NON_UNIQUE (boolean) 'true' if non-unique
  8. + *
  9. INDEX_QUALIFIER (String) index catalog
  10. + *
  11. INDEX_NAME (String) index name
  12. + *
  13. TYPE (short) the index type (tableIndexOther or tableIndexHash for + * unique indexes on non-nullable columns, tableIndexStatistics for other + * indexes)
  14. + *
  15. ORDINAL_POSITION (short) column index (1, 2, ...)
  16. + *
  17. COLUMN_NAME (String) column name
  18. + *
  19. ASC_OR_DESC (String) ascending or descending (always 'A')
  20. + *
  21. CARDINALITY (long) number of rows or numbers of unique values for + * unique indexes on non-nullable columns
  22. + *
  23. PAGES (long) number of pages use
  24. + *
  25. FILTER_CONDITION (String) filter condition (always empty)
  26. + *
+ * + * @param catalog null or the catalog name + * @param schema null (to get all objects) or a schema name + * (uppercase for unquoted names) + * @param table table name (must be specified) + * @param unique only unique indexes + * @param approximate if true, return fast, but approximate CARDINALITY + * @return the list of indexes and columns + * @throws SQLException if the connection is closed + */ + @Override + public ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) + throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getIndexInfo(" + quote(catalog) + ", " + quote(schema) + ", " + quote(table) + ", " + unique + + ", " + approximate + ')'); + } + return getResultSet(meta.getIndexInfo(catalog, schema, table, unique, approximate)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the primary key columns for a table. The result set is sorted by + * TABLE_SCHEM, and COLUMN_NAME (and not by KEY_SEQ). + * + *
    + *
  1. TABLE_CAT (String) table catalog
  2. + *
  3. TABLE_SCHEM (String) table schema
  4. + *
  5. TABLE_NAME (String) table name
  6. + *
  7. COLUMN_NAME (String) column name
  8. + *
  9. KEY_SEQ (short) the column index of this column (1,2,...)
  10. + *
  11. PK_NAME (String) the name of the primary key index
  12. + *
+ * + * @param catalog null or the catalog name + * @param schema null (to get all objects) or a schema name + * (uppercase for unquoted names) + * @param table table name (must be specified) + * @return the list of primary key columns + * @throws SQLException if the connection is closed + */ + @Override + public ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getPrimaryKeys(" + quote(catalog) + ", " + quote(schema) + ", " + quote(table) + ')'); + } + return getResultSet(meta.getPrimaryKeys(catalog, schema, table)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if all procedures callable. + * + * @return true + */ + @Override + public boolean allProceduresAreCallable() { + debugCodeCall("allProceduresAreCallable"); + return true; + } + + /** + * Checks if it possible to query all tables returned by getTables. + * + * @return true + */ + @Override + public boolean allTablesAreSelectable() { + debugCodeCall("allTablesAreSelectable"); + return true; + } + + /** + * Returns the database URL for this connection. + * + * @return the url + */ + @Override + public String getURL() throws SQLException { + try { + debugCodeCall("getURL"); + return conn.getURL(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the user name as passed to DriverManager.getConnection(url, user, + * password). + * + * @return the user name + */ + @Override + public String getUserName() throws SQLException { + try { + debugCodeCall("getUserName"); + return conn.getUser(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the same as Connection.isReadOnly(). + * + * @return if read only optimization is switched on + */ + @Override + public boolean isReadOnly() throws SQLException { + try { + debugCodeCall("isReadOnly"); + return conn.isReadOnly(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if NULL values are sorted high (bigger than anything that is not + * null). + * + * @return if NULL values are sorted high + */ + @Override + public boolean nullsAreSortedHigh() throws SQLException { + try { + debugCodeCall("nullsAreSortedHigh"); + return meta.defaultNullOrdering() == DefaultNullOrdering.HIGH; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if NULL values are sorted low (smaller than anything that is not + * null). + * + * @return if NULL values are sorted low + */ + @Override + public boolean nullsAreSortedLow() throws SQLException { + try { + debugCodeCall("nullsAreSortedLow"); + return meta.defaultNullOrdering() == DefaultNullOrdering.LOW; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if NULL values are sorted at the beginning (no matter if ASC or + * DESC is used). + * + * @return if NULL values are sorted at the beginning + */ + @Override + public boolean nullsAreSortedAtStart() throws SQLException { + try { + debugCodeCall("nullsAreSortedAtStart"); + return meta.defaultNullOrdering() == DefaultNullOrdering.FIRST; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if NULL values are sorted at the end (no matter if ASC or DESC is + * used). + * + * @return if NULL values are sorted at the end + */ + @Override + public boolean nullsAreSortedAtEnd() throws SQLException { + try { + debugCodeCall("nullsAreSortedAtEnd"); + return meta.defaultNullOrdering() == DefaultNullOrdering.LAST; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the connection that created this object. + * + * @return the connection + */ + @Override + public Connection getConnection() { + debugCodeCall("getConnection"); + return conn; + } + + /** + * Gets the list of procedures. The result set is sorted by PROCEDURE_SCHEM, + * PROCEDURE_NAME, and NUM_INPUT_PARAMS. There are potentially multiple + * procedures with the same name, each with a different number of input + * parameters. + * + *
    + *
  1. PROCEDURE_CAT (String) catalog
  2. + *
  3. PROCEDURE_SCHEM (String) schema
  4. + *
  5. PROCEDURE_NAME (String) name
  6. + *
  7. reserved
  8. + *
  9. reserved
  10. + *
  11. reserved
  12. + *
  13. REMARKS (String) description
  14. + *
  15. PROCEDURE_TYPE (short) if this procedure returns a result + * (procedureNoResult or procedureReturnsResult)
  16. + *
  17. SPECIFIC_NAME (String) non-ambiguous name to distinguish + * overloads
  18. + *
+ * + * @param catalog null or the catalog name + * @param schemaPattern null (to get all objects) or a schema name + * (uppercase for unquoted names) + * @param procedureNamePattern the procedure name pattern + * @return the procedures + * @throws SQLException if the connection is closed + */ + @Override + public ResultSet getProcedures(String catalog, String schemaPattern, + String procedureNamePattern) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getProcedures(" + +quote(catalog)+", " + +quote(schemaPattern)+", " + +quote(procedureNamePattern)+')'); + } + return getResultSet(meta.getProcedures(catalog, schemaPattern, procedureNamePattern)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the list of procedure columns. The result set is sorted by + * PROCEDURE_SCHEM, PROCEDURE_NAME, NUM_INPUT_PARAMS, and POS. + * There are potentially multiple procedures with the same name, each with a + * different number of input parameters. + * + *
    + *
  1. PROCEDURE_CAT (String) catalog
  2. + *
  3. PROCEDURE_SCHEM (String) schema
  4. + *
  5. PROCEDURE_NAME (String) name
  6. + *
  7. COLUMN_NAME (String) column name
  8. + *
  9. COLUMN_TYPE (short) column type + * (always DatabaseMetaData.procedureColumnIn)
  10. + *
  11. DATA_TYPE (short) sql type
  12. + *
  13. TYPE_NAME (String) type name
  14. + *
  15. PRECISION (int) precision
  16. + *
  17. LENGTH (int) length
  18. + *
  19. SCALE (short) scale
  20. + *
  21. RADIX (int)
  22. + *
  23. NULLABLE (short) nullable + * (DatabaseMetaData.columnNoNulls for primitive data types, + * DatabaseMetaData.columnNullable otherwise)
  24. + *
  25. REMARKS (String) description
  26. + *
  27. COLUMN_DEF (String) always null
  28. + *
  29. SQL_DATA_TYPE (int) for future use
  30. + *
  31. SQL_DATETIME_SUB (int) for future use
  32. + *
  33. CHAR_OCTET_LENGTH (int)
  34. + *
  35. ORDINAL_POSITION (int) the parameter index + * starting from 1 (0 is the return value)
  36. + *
  37. IS_NULLABLE (String) always "YES"
  38. + *
  39. SPECIFIC_NAME (String) non-ambiguous procedure name to distinguish + * overloads
  40. + *
+ * + * @param catalog null or the catalog name + * @param schemaPattern null (to get all objects) or a schema name + * (uppercase for unquoted names) + * @param procedureNamePattern the procedure name pattern + * @param columnNamePattern the procedure name pattern + * @return the procedure columns + * @throws SQLException if the connection is closed + */ + @Override + public ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, + String columnNamePattern) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getProcedureColumns(" + quote(catalog) + ", " + quote(schemaPattern) + ", " + + quote(procedureNamePattern) + ", " + quote(columnNamePattern) + ')'); + } + checkClosed(); + return getResultSet( + meta.getProcedureColumns(catalog, schemaPattern, procedureNamePattern, columnNamePattern)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the list of schemas. + * The result set is sorted by TABLE_SCHEM. + * + *
    + *
  1. TABLE_SCHEM (String) schema name
  2. + *
  3. TABLE_CATALOG (String) catalog name
  4. + *
+ * + * @return the schema list + * @throws SQLException if the connection is closed + */ + @Override + public ResultSet getSchemas() throws SQLException { + try { + debugCodeCall("getSchemas"); + return getResultSet(meta.getSchemas()); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the list of catalogs. + * The result set is sorted by TABLE_CAT. + * + *
    + *
  1. TABLE_CAT (String) catalog name
  2. + *
+ * + * @return the catalog list + * @throws SQLException if the connection is closed + */ + @Override + public ResultSet getCatalogs() throws SQLException { + try { + debugCodeCall("getCatalogs"); + return getResultSet(meta.getCatalogs()); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the list of table types. This call returns a result set with five + * records: "SYSTEM TABLE", "TABLE", "VIEW", "TABLE LINK" and "EXTERNAL". + *
    + *
  1. TABLE_TYPE (String) table type
  2. + *
+ * + * @return the table types + * @throws SQLException if the connection is closed + */ + @Override + public ResultSet getTableTypes() throws SQLException { + try { + debugCodeCall("getTableTypes"); + return getResultSet(meta.getTableTypes()); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the list of column privileges. The result set is sorted by + * COLUMN_NAME and PRIVILEGE + * + *
    + *
  1. TABLE_CAT (String) table catalog
  2. + *
  3. TABLE_SCHEM (String) table schema
  4. + *
  5. TABLE_NAME (String) table name
  6. + *
  7. COLUMN_NAME (String) column name
  8. + *
  9. GRANTOR (String) grantor of access
  10. + *
  11. GRANTEE (String) grantee of access
  12. + *
  13. PRIVILEGE (String) SELECT, INSERT, UPDATE, DELETE or REFERENCES + * (only one per row)
  14. + *
  15. IS_GRANTABLE (String) YES means the grantee can grant access to + * others
  16. + *
+ * + * @param catalog null (to get all objects) or the catalog name + * @param schema null (to get all objects) or a schema name + * (uppercase for unquoted names) + * @param table a table name (uppercase for unquoted names) + * @param columnNamePattern null (to get all objects) or a column name + * (uppercase for unquoted names) + * @return the list of privileges + * @throws SQLException if the connection is closed + */ + @Override + public ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) + throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getColumnPrivileges(" + quote(catalog) + ", " + quote(schema) + ", " + quote(table) + ", " + + quote(columnNamePattern) + ')'); + } + return getResultSet(meta.getColumnPrivileges(catalog, schema, table, columnNamePattern)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the list of table privileges. The result set is sorted by + * TABLE_SCHEM, TABLE_NAME, and PRIVILEGE. + * + *
    + *
  1. TABLE_CAT (String) table catalog
  2. + *
  3. TABLE_SCHEM (String) table schema
  4. + *
  5. TABLE_NAME (String) table name
  6. + *
  7. GRANTOR (String) grantor of access
  8. + *
  9. GRANTEE (String) grantee of access
  10. + *
  11. PRIVILEGE (String) SELECT, INSERT, UPDATE, DELETE or REFERENCES + * (only one per row)
  12. + *
  13. IS_GRANTABLE (String) YES means the grantee can grant access to + * others
  14. + *
+ * + * @param catalog null (to get all objects) or the catalog name + * @param schemaPattern null (to get all objects) or a schema name + * (uppercase for unquoted names) + * @param tableNamePattern null (to get all objects) or a table name + * (uppercase for unquoted names) + * @return the list of privileges + * @throws SQLException if the connection is closed + */ + @Override + public ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) + throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getTablePrivileges(" + quote(catalog) + ", " + quote(schemaPattern) + ", " + + quote(tableNamePattern) + ')'); + } + checkClosed(); + return getResultSet(meta.getTablePrivileges(catalog, schemaPattern, tableNamePattern)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the list of columns that best identifier a row in a table. + * The list is ordered by SCOPE. + * + *
    + *
  1. SCOPE (short) scope of result (always bestRowSession)
  2. + *
  3. COLUMN_NAME (String) column name
  4. + *
  5. DATA_TYPE (short) SQL data type, see also java.sql.Types
  6. + *
  7. TYPE_NAME (String) type name
  8. + *
  9. COLUMN_SIZE (int) precision + * (values larger than 2 GB are returned as 2 GB)
  10. + *
  11. BUFFER_LENGTH (int) unused
  12. + *
  13. DECIMAL_DIGITS (short) scale
  14. + *
  15. PSEUDO_COLUMN (short) (always bestRowNotPseudo)
  16. + *
+ * + * @param catalog null (to get all objects) or the catalog name + * @param schema null (to get all objects) or a schema name + * (uppercase for unquoted names) + * @param table table name (must be specified) + * @param scope ignored + * @param nullable ignored + * @return the primary key index + * @throws SQLException if the connection is closed + */ + @Override + public ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) + throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getBestRowIdentifier(" + quote(catalog) + ", " + quote(schema) + ", " + quote(table) + ", " + + scope + ", " + nullable + ')'); + } + return getResultSet(meta.getBestRowIdentifier(catalog, schema, table, scope, nullable)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Get the list of columns that are update when any value is updated. + * The result set is always empty. + * + *
    + *
  1. 1 SCOPE (int) not used
  2. + *
  3. 2 COLUMN_NAME (String) column name
  4. + *
  5. 3 DATA_TYPE (int) SQL data type - see also java.sql.Types
  6. + *
  7. 4 TYPE_NAME (String) data type name
  8. + *
  9. 5 COLUMN_SIZE (int) precision + * (values larger than 2 GB are returned as 2 GB)
  10. + *
  11. 6 BUFFER_LENGTH (int) length (bytes)
  12. + *
  13. 7 DECIMAL_DIGITS (int) scale
  14. + *
  15. 8 PSEUDO_COLUMN (int) is this column a pseudo column
  16. + *
+ * + * @param catalog null (to get all objects) or the catalog name + * @param schema null (to get all objects) or a schema name + * @param table table name (must be specified) + * @return an empty result set + * @throws SQLException if the connection is closed + */ + @Override + public ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getVersionColumns(" + quote(catalog) + ", " + quote(schema) + ", " + quote(table) + ')'); + } + return getResultSet(meta.getVersionColumns(catalog, schema, table)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the list of primary key columns that are referenced by a table. The + * result set is sorted by PKTABLE_CAT, PKTABLE_SCHEM, PKTABLE_NAME, + * FK_NAME, KEY_SEQ. + * + *
    + *
  1. PKTABLE_CAT (String) primary catalog
  2. + *
  3. PKTABLE_SCHEM (String) primary schema
  4. + *
  5. PKTABLE_NAME (String) primary table
  6. + *
  7. PKCOLUMN_NAME (String) primary column
  8. + *
  9. FKTABLE_CAT (String) foreign catalog
  10. + *
  11. FKTABLE_SCHEM (String) foreign schema
  12. + *
  13. FKTABLE_NAME (String) foreign table
  14. + *
  15. FKCOLUMN_NAME (String) foreign column
  16. + *
  17. KEY_SEQ (short) sequence number (1, 2, ...)
  18. + *
  19. UPDATE_RULE (short) action on update (see + * DatabaseMetaData.importedKey...)
  20. + *
  21. DELETE_RULE (short) action on delete (see + * DatabaseMetaData.importedKey...)
  22. + *
  23. FK_NAME (String) foreign key name
  24. + *
  25. PK_NAME (String) primary key name
  26. + *
  27. DEFERRABILITY (short) deferrable or not (always + * importedKeyNotDeferrable)
  28. + *
+ * + * @param catalog null (to get all objects) or the catalog name + * @param schema the schema name of the foreign table + * @param table the name of the foreign table + * @return the result set + * @throws SQLException if the connection is closed + */ + @Override + public ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getImportedKeys(" + quote(catalog) + ", " + quote(schema) + ", " + quote(table) + ')'); + } + return getResultSet(meta.getImportedKeys(catalog, schema, table)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the list of foreign key columns that reference a table. The result + * set is sorted by FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, FK_NAME, + * KEY_SEQ. + * + *
    + *
  1. PKTABLE_CAT (String) primary catalog
  2. + *
  3. PKTABLE_SCHEM (String) primary schema
  4. + *
  5. PKTABLE_NAME (String) primary table
  6. + *
  7. PKCOLUMN_NAME (String) primary column
  8. + *
  9. FKTABLE_CAT (String) foreign catalog
  10. + *
  11. FKTABLE_SCHEM (String) foreign schema
  12. + *
  13. FKTABLE_NAME (String) foreign table
  14. + *
  15. FKCOLUMN_NAME (String) foreign column
  16. + *
  17. KEY_SEQ (short) sequence number (1,2,...)
  18. + *
  19. UPDATE_RULE (short) action on update (see + * DatabaseMetaData.importedKey...)
  20. + *
  21. DELETE_RULE (short) action on delete (see + * DatabaseMetaData.importedKey...)
  22. + *
  23. FK_NAME (String) foreign key name
  24. + *
  25. PK_NAME (String) primary key name
  26. + *
  27. DEFERRABILITY (short) deferrable or not (always + * importedKeyNotDeferrable)
  28. + *
+ * + * @param catalog null or the catalog name + * @param schema the schema name of the primary table + * @param table the name of the primary table + * @return the result set + * @throws SQLException if the connection is closed + */ + @Override + public ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getExportedKeys(" + quote(catalog) + ", " + quote(schema) + ", " + quote(table) + ')'); + } + return getResultSet(meta.getExportedKeys(catalog, schema, table)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the list of foreign key columns that references a table, as well as + * the list of primary key columns that are references by a table. The + * result set is sorted by FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, + * FK_NAME, KEY_SEQ. + * + *
    + *
  1. PKTABLE_CAT (String) primary catalog
  2. + *
  3. PKTABLE_SCHEM (String) primary schema
  4. + *
  5. PKTABLE_NAME (String) primary table
  6. + *
  7. PKCOLUMN_NAME (String) primary column
  8. + *
  9. FKTABLE_CAT (String) foreign catalog
  10. + *
  11. FKTABLE_SCHEM (String) foreign schema
  12. + *
  13. FKTABLE_NAME (String) foreign table
  14. + *
  15. FKCOLUMN_NAME (String) foreign column
  16. + *
  17. KEY_SEQ (short) sequence number (1,2,...)
  18. + *
  19. UPDATE_RULE (short) action on update (see + * DatabaseMetaData.importedKey...)
  20. + *
  21. DELETE_RULE (short) action on delete (see + * DatabaseMetaData.importedKey...)
  22. + *
  23. FK_NAME (String) foreign key name
  24. + *
  25. PK_NAME (String) primary key name
  26. + *
  27. DEFERRABILITY (short) deferrable or not (always + * importedKeyNotDeferrable)
  28. + *
+ * + * @param primaryCatalog null or the catalog name + * @param primarySchema the schema name of the primary table + * (optional) + * @param primaryTable the name of the primary table (must be specified) + * @param foreignCatalog null or the catalog name + * @param foreignSchema the schema name of the foreign table + * (optional) + * @param foreignTable the name of the foreign table (must be specified) + * @return the result set + * @throws SQLException if the connection is closed + */ + @Override + public ResultSet getCrossReference(String primaryCatalog, String primarySchema, String primaryTable, + String foreignCatalog, String foreignSchema, String foreignTable) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getCrossReference(" + quote(primaryCatalog) + ", " + quote(primarySchema) + ", " + + quote(primaryTable) + ", " + quote(foreignCatalog) + ", " + quote(foreignSchema) + ", " + + quote(foreignTable) + ')'); + } + return getResultSet(meta.getCrossReference(primaryCatalog, primarySchema, primaryTable, foreignCatalog, + foreignSchema, foreignTable)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the list of user-defined data types. + * This call returns an empty result set. + * + *
    + *
  1. TYPE_CAT (String) catalog
  2. + *
  3. TYPE_SCHEM (String) schema
  4. + *
  5. TYPE_NAME (String) type name
  6. + *
  7. CLASS_NAME (String) Java class
  8. + *
  9. DATA_TYPE (short) SQL Type - see also java.sql.Types
  10. + *
  11. REMARKS (String) description
  12. + *
  13. BASE_TYPE (short) base type - see also java.sql.Types
  14. + *
+ * + * @param catalog ignored + * @param schemaPattern ignored + * @param typeNamePattern ignored + * @param types ignored + * @return an empty result set + * @throws SQLException if the connection is closed + */ + @Override + public ResultSet getUDTs(String catalog, String schemaPattern, + String typeNamePattern, int[] types) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getUDTs(" + +quote(catalog)+", " + +quote(schemaPattern)+", " + +quote(typeNamePattern)+", " + +quoteIntArray(types)+')'); + } + return getResultSet(meta.getUDTs(catalog, schemaPattern, typeNamePattern, types)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the list of data types. The result set is sorted by DATA_TYPE and + * afterwards by how closely the data type maps to the corresponding JDBC + * SQL type (best match first). + * + *
    + *
  1. TYPE_NAME (String) type name
  2. + *
  3. DATA_TYPE (short) SQL data type - see also java.sql.Types
  4. + *
  5. PRECISION (int) maximum precision
  6. + *
  7. LITERAL_PREFIX (String) prefix used to quote a literal
  8. + *
  9. LITERAL_SUFFIX (String) suffix used to quote a literal
  10. + *
  11. CREATE_PARAMS (String) parameters used (may be null)
  12. + *
  13. NULLABLE (short) typeNoNulls (NULL not allowed) or typeNullable
  14. + *
  15. CASE_SENSITIVE (boolean) case sensitive
  16. + *
  17. SEARCHABLE (short) typeSearchable
  18. + *
  19. UNSIGNED_ATTRIBUTE (boolean) unsigned
  20. + *
  21. FIXED_PREC_SCALE (boolean) fixed precision
  22. + *
  23. AUTO_INCREMENT (boolean) auto increment
  24. + *
  25. LOCAL_TYPE_NAME (String) localized version of the data type
  26. + *
  27. MINIMUM_SCALE (short) minimum scale
  28. + *
  29. MAXIMUM_SCALE (short) maximum scale
  30. + *
  31. SQL_DATA_TYPE (int) unused
  32. + *
  33. SQL_DATETIME_SUB (int) unused
  34. + *
  35. NUM_PREC_RADIX (int) 2 for binary, 10 for decimal
  36. + *
+ * + * @return the list of data types + * @throws SQLException if the connection is closed + */ + @Override + public ResultSet getTypeInfo() throws SQLException { + try { + debugCodeCall("getTypeInfo"); + return getResultSet(meta.getTypeInfo()); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if this database store data in local files. + * + * @return true + */ + @Override + public boolean usesLocalFiles() { + debugCodeCall("usesLocalFiles"); + return true; + } + + /** + * Checks if this database use one file per table. + * + * @return false + */ + @Override + public boolean usesLocalFilePerTable() { + debugCodeCall("usesLocalFilePerTable"); + return false; + } + + /** + * Returns the string used to quote identifiers. + * + * @return a double quote + */ + @Override + public String getIdentifierQuoteString() { + debugCodeCall("getIdentifierQuoteString"); + return "\""; + } + + /** + * Gets the comma-separated list of all SQL keywords that are not supported + * as unquoted identifiers, in addition to the SQL:2003 reserved words. + *

+ * List of keywords in H2 may depend on compatibility mode and other + * settings. + *

+ * + * @return a list of additional keywords + */ + @Override + public String getSQLKeywords() throws SQLException { + try { + debugCodeCall("getSQLKeywords"); + return meta.getSQLKeywords(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the list of numeric functions supported by this database. + * + * @return the list + */ + @Override + public String getNumericFunctions() throws SQLException { + try { + debugCodeCall("getNumericFunctions"); + return meta.getNumericFunctions(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the list of string functions supported by this database. + * + * @return the list + */ + @Override + public String getStringFunctions() throws SQLException { + try { + debugCodeCall("getStringFunctions"); + return meta.getStringFunctions(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the list of system functions supported by this database. + * + * @return the list + */ + @Override + public String getSystemFunctions() throws SQLException { + try { + debugCodeCall("getSystemFunctions"); + return meta.getSystemFunctions(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the list of date and time functions supported by this database. + * + * @return the list + */ + @Override + public String getTimeDateFunctions() throws SQLException { + try { + debugCodeCall("getTimeDateFunctions"); + return meta.getTimeDateFunctions(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the default escape character for DatabaseMetaData search + * patterns. + * + * @return the default escape character (always '\', independent on the + * mode) + */ + @Override + public String getSearchStringEscape() throws SQLException { + try { + debugCodeCall("getSearchStringEscape"); + return meta.getSearchStringEscape(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the characters that are allowed for identifiers in addiction to + * A-Z, a-z, 0-9 and '_'. + * + * @return an empty String ("") + */ + @Override + public String getExtraNameCharacters() { + debugCodeCall("getExtraNameCharacters"); + return ""; + } + + /** + * Returns whether alter table with add column is supported. + * + * @return true + */ + @Override + public boolean supportsAlterTableWithAddColumn() { + debugCodeCall("supportsAlterTableWithAddColumn"); + return true; + } + + /** + * Returns whether alter table with drop column is supported. + * + * @return true + */ + @Override + public boolean supportsAlterTableWithDropColumn() { + debugCodeCall("supportsAlterTableWithDropColumn"); + return true; + } + + /** + * Returns whether column aliasing is supported. + * + * @return true + */ + @Override + public boolean supportsColumnAliasing() { + debugCodeCall("supportsColumnAliasing"); + return true; + } + + /** + * Returns whether NULL+1 is NULL or not. + * + * @return true + */ + @Override + public boolean nullPlusNonNullIsNull() { + debugCodeCall("nullPlusNonNullIsNull"); + return true; + } + + /** + * Returns whether CONVERT is supported. + * + * @return true + */ + @Override + public boolean supportsConvert() { + debugCodeCall("supportsConvert"); + return true; + } + + /** + * Returns whether CONVERT is supported for one datatype to another. + * + * @param fromType the source SQL type + * @param toType the target SQL type + * @return true + */ + @Override + public boolean supportsConvert(int fromType, int toType) { + if (isDebugEnabled()) { + debugCode("supportsConvert(" + fromType + ", " + toType + ')'); + } + return true; + } + + /** + * Returns whether table correlation names (table alias) are supported. + * + * @return true + */ + @Override + public boolean supportsTableCorrelationNames() { + debugCodeCall("supportsTableCorrelationNames"); + return true; + } + + /** + * Returns whether table correlation names (table alias) are restricted to + * be different than table names. + * + * @return false + */ + @Override + public boolean supportsDifferentTableCorrelationNames() { + debugCodeCall("supportsDifferentTableCorrelationNames"); + return false; + } + + /** + * Returns whether expression in ORDER BY are supported. + * + * @return true + */ + @Override + public boolean supportsExpressionsInOrderBy() { + debugCodeCall("supportsExpressionsInOrderBy"); + return true; + } + + /** + * Returns whether ORDER BY is supported if the column is not in the SELECT + * list. + * + * @return true + */ + @Override + public boolean supportsOrderByUnrelated() { + debugCodeCall("supportsOrderByUnrelated"); + return true; + } + + /** + * Returns whether GROUP BY is supported. + * + * @return true + */ + @Override + public boolean supportsGroupBy() { + debugCodeCall("supportsGroupBy"); + return true; + } + + /** + * Returns whether GROUP BY is supported if the column is not in the SELECT + * list. + * + * @return true + */ + @Override + public boolean supportsGroupByUnrelated() { + debugCodeCall("supportsGroupByUnrelated"); + return true; + } + + /** + * Checks whether a GROUP BY clause can use columns that are not in the + * SELECT clause, provided that it specifies all the columns in the SELECT + * clause. + * + * @return true + */ + @Override + public boolean supportsGroupByBeyondSelect() { + debugCodeCall("supportsGroupByBeyondSelect"); + return true; + } + + /** + * Returns whether LIKE... ESCAPE is supported. + * + * @return true + */ + @Override + public boolean supportsLikeEscapeClause() { + debugCodeCall("supportsLikeEscapeClause"); + return true; + } + + /** + * Returns whether multiple result sets are supported. + * + * @return false + */ + @Override + public boolean supportsMultipleResultSets() { + debugCodeCall("supportsMultipleResultSets"); + return false; + } + + /** + * Returns whether multiple transactions (on different connections) are + * supported. + * + * @return true + */ + @Override + public boolean supportsMultipleTransactions() { + debugCodeCall("supportsMultipleTransactions"); + return true; + } + + /** + * Returns whether columns with NOT NULL are supported. + * + * @return true + */ + @Override + public boolean supportsNonNullableColumns() { + debugCodeCall("supportsNonNullableColumns"); + return true; + } + + /** + * Returns whether ODBC Minimum SQL grammar is supported. + * + * @return true + */ + @Override + public boolean supportsMinimumSQLGrammar() { + debugCodeCall("supportsMinimumSQLGrammar"); + return true; + } + + /** + * Returns whether ODBC Core SQL grammar is supported. + * + * @return true + */ + @Override + public boolean supportsCoreSQLGrammar() { + debugCodeCall("supportsCoreSQLGrammar"); + return true; + } + + /** + * Returns whether ODBC Extended SQL grammar is supported. + * + * @return false + */ + @Override + public boolean supportsExtendedSQLGrammar() { + debugCodeCall("supportsExtendedSQLGrammar"); + return false; + } + + /** + * Returns whether SQL-92 entry level grammar is supported. + * + * @return true + */ + @Override + public boolean supportsANSI92EntryLevelSQL() { + debugCodeCall("supportsANSI92EntryLevelSQL"); + return true; + } + + /** + * Returns whether SQL-92 intermediate level grammar is supported. + * + * @return false + */ + @Override + public boolean supportsANSI92IntermediateSQL() { + debugCodeCall("supportsANSI92IntermediateSQL"); + return false; + } + + /** + * Returns whether SQL-92 full level grammar is supported. + * + * @return false + */ + @Override + public boolean supportsANSI92FullSQL() { + debugCodeCall("supportsANSI92FullSQL"); + return false; + } + + /** + * Returns whether referential integrity is supported. + * + * @return true + */ + @Override + public boolean supportsIntegrityEnhancementFacility() { + debugCodeCall("supportsIntegrityEnhancementFacility"); + return true; + } + + /** + * Returns whether outer joins are supported. + * + * @return true + */ + @Override + public boolean supportsOuterJoins() { + debugCodeCall("supportsOuterJoins"); + return true; + } + + /** + * Returns whether full outer joins are supported. + * + * @return false + */ + @Override + public boolean supportsFullOuterJoins() { + debugCodeCall("supportsFullOuterJoins"); + return false; + } + + /** + * Returns whether limited outer joins are supported. + * + * @return true + */ + @Override + public boolean supportsLimitedOuterJoins() { + debugCodeCall("supportsLimitedOuterJoins"); + return true; + } + + /** + * Returns the term for "schema". + * + * @return "schema" + */ + @Override + public String getSchemaTerm() { + debugCodeCall("getSchemaTerm"); + return "schema"; + } + + /** + * Returns the term for "procedure". + * + * @return "procedure" + */ + @Override + public String getProcedureTerm() { + debugCodeCall("getProcedureTerm"); + return "procedure"; + } + + /** + * Returns the term for "catalog". + * + * @return "catalog" + */ + @Override + public String getCatalogTerm() { + debugCodeCall("getCatalogTerm"); + return "catalog"; + } + + /** + * Returns whether the catalog is at the beginning. + * + * @return true + */ + @Override + public boolean isCatalogAtStart() { + debugCodeCall("isCatalogAtStart"); + return true; + } + + /** + * Returns the catalog separator. + * + * @return "." + */ + @Override + public String getCatalogSeparator() { + debugCodeCall("getCatalogSeparator"); + return "."; + } + + /** + * Returns whether the schema name in INSERT, UPDATE, DELETE is supported. + * + * @return true + */ + @Override + public boolean supportsSchemasInDataManipulation() { + debugCodeCall("supportsSchemasInDataManipulation"); + return true; + } + + /** + * Returns whether the schema name in procedure calls is supported. + * + * @return true + */ + @Override + public boolean supportsSchemasInProcedureCalls() { + debugCodeCall("supportsSchemasInProcedureCalls"); + return true; + } + + /** + * Returns whether the schema name in CREATE TABLE is supported. + * + * @return true + */ + @Override + public boolean supportsSchemasInTableDefinitions() { + debugCodeCall("supportsSchemasInTableDefinitions"); + return true; + } + + /** + * Returns whether the schema name in CREATE INDEX is supported. + * + * @return true + */ + @Override + public boolean supportsSchemasInIndexDefinitions() { + debugCodeCall("supportsSchemasInIndexDefinitions"); + return true; + } + + /** + * Returns whether the schema name in GRANT is supported. + * + * @return true + */ + @Override + public boolean supportsSchemasInPrivilegeDefinitions() { + debugCodeCall("supportsSchemasInPrivilegeDefinitions"); + return true; + } + + /** + * Returns whether the catalog name in INSERT, UPDATE, DELETE is supported. + * + * @return true + */ + @Override + public boolean supportsCatalogsInDataManipulation() { + debugCodeCall("supportsCatalogsInDataManipulation"); + return true; + } + + /** + * Returns whether the catalog name in procedure calls is supported. + * + * @return false + */ + @Override + public boolean supportsCatalogsInProcedureCalls() { + debugCodeCall("supportsCatalogsInProcedureCalls"); + return false; + } + + /** + * Returns whether the catalog name in CREATE TABLE is supported. + * + * @return true + */ + @Override + public boolean supportsCatalogsInTableDefinitions() { + debugCodeCall("supportsCatalogsInTableDefinitions"); + return true; + } + + /** + * Returns whether the catalog name in CREATE INDEX is supported. + * + * @return true + */ + @Override + public boolean supportsCatalogsInIndexDefinitions() { + debugCodeCall("supportsCatalogsInIndexDefinitions"); + return true; + } + + /** + * Returns whether the catalog name in GRANT is supported. + * + * @return true + */ + @Override + public boolean supportsCatalogsInPrivilegeDefinitions() { + debugCodeCall("supportsCatalogsInPrivilegeDefinitions"); + return true; + } + + /** + * Returns whether positioned deletes are supported. + * + * @return false + */ + @Override + public boolean supportsPositionedDelete() { + debugCodeCall("supportsPositionedDelete"); + return false; + } + + /** + * Returns whether positioned updates are supported. + * + * @return false + */ + @Override + public boolean supportsPositionedUpdate() { + debugCodeCall("supportsPositionedUpdate"); + return false; + } + + /** + * Returns whether SELECT ... FOR UPDATE is supported. + * + * @return true + */ + @Override + public boolean supportsSelectForUpdate() { + debugCodeCall("supportsSelectForUpdate"); + return true; + } + + /** + * Returns whether stored procedures are supported. + * + * @return false + */ + @Override + public boolean supportsStoredProcedures() { + debugCodeCall("supportsStoredProcedures"); + return false; + } + + /** + * Returns whether subqueries (SELECT) in comparisons are supported. + * + * @return true + */ + @Override + public boolean supportsSubqueriesInComparisons() { + debugCodeCall("supportsSubqueriesInComparisons"); + return true; + } + + /** + * Returns whether SELECT in EXISTS is supported. + * + * @return true + */ + @Override + public boolean supportsSubqueriesInExists() { + debugCodeCall("supportsSubqueriesInExists"); + return true; + } + + /** + * Returns whether IN(SELECT...) is supported. + * + * @return true + */ + @Override + public boolean supportsSubqueriesInIns() { + debugCodeCall("supportsSubqueriesInIns"); + return true; + } + + /** + * Returns whether subqueries in quantified expression are supported. + * + * @return true + */ + @Override + public boolean supportsSubqueriesInQuantifieds() { + debugCodeCall("supportsSubqueriesInQuantifieds"); + return true; + } + + /** + * Returns whether correlated subqueries are supported. + * + * @return true + */ + @Override + public boolean supportsCorrelatedSubqueries() { + debugCodeCall("supportsCorrelatedSubqueries"); + return true; + } + + /** + * Returns whether UNION SELECT is supported. + * + * @return true + */ + @Override + public boolean supportsUnion() { + debugCodeCall("supportsUnion"); + return true; + } + + /** + * Returns whether UNION ALL SELECT is supported. + * + * @return true + */ + @Override + public boolean supportsUnionAll() { + debugCodeCall("supportsUnionAll"); + return true; + } + + /** + * Returns whether open result sets across commits are supported. + * + * @return false + */ + @Override + public boolean supportsOpenCursorsAcrossCommit() { + debugCodeCall("supportsOpenCursorsAcrossCommit"); + return false; + } + + /** + * Returns whether open result sets across rollback are supported. + * + * @return false + */ + @Override + public boolean supportsOpenCursorsAcrossRollback() { + debugCodeCall("supportsOpenCursorsAcrossRollback"); + return false; + } + + /** + * Returns whether open statements across commit are supported. + * + * @return true + */ + @Override + public boolean supportsOpenStatementsAcrossCommit() { + debugCodeCall("supportsOpenStatementsAcrossCommit"); + return true; + } + + /** + * Returns whether open statements across rollback are supported. + * + * @return true + */ + @Override + public boolean supportsOpenStatementsAcrossRollback() { + debugCodeCall("supportsOpenStatementsAcrossRollback"); + return true; + } + + /** + * Returns whether transactions are supported. + * + * @return true + */ + @Override + public boolean supportsTransactions() { + debugCodeCall("supportsTransactions"); + return true; + } + + /** + * Returns whether a specific transaction isolation level is supported. + * + * @param level the transaction isolation level (Connection.TRANSACTION_*) + * @return true + */ + @Override + public boolean supportsTransactionIsolationLevel(int level) throws SQLException { + debugCodeCall("supportsTransactionIsolationLevel"); + switch (level) { + case Connection.TRANSACTION_READ_UNCOMMITTED: + case Connection.TRANSACTION_READ_COMMITTED: + case Connection.TRANSACTION_REPEATABLE_READ: + case Constants.TRANSACTION_SNAPSHOT: + case Connection.TRANSACTION_SERIALIZABLE: + return true; + default: + return false; + } + } + + /** + * Returns whether data manipulation and CREATE/DROP is supported in + * transactions. + * + * @return false + */ + @Override + public boolean supportsDataDefinitionAndDataManipulationTransactions() { + debugCodeCall("supportsDataDefinitionAndDataManipulationTransactions"); + return false; + } + + /** + * Returns whether only data manipulations are supported in transactions. + * + * @return true + */ + @Override + public boolean supportsDataManipulationTransactionsOnly() { + debugCodeCall("supportsDataManipulationTransactionsOnly"); + return true; + } + + /** + * Returns whether CREATE/DROP commit an open transaction. + * + * @return true + */ + @Override + public boolean dataDefinitionCausesTransactionCommit() { + debugCodeCall("dataDefinitionCausesTransactionCommit"); + return true; + } + + /** + * Returns whether CREATE/DROP do not affect transactions. + * + * @return false + */ + @Override + public boolean dataDefinitionIgnoredInTransactions() { + debugCodeCall("dataDefinitionIgnoredInTransactions"); + return false; + } + + /** + * Returns whether a specific result set type is supported. + * ResultSet.TYPE_SCROLL_SENSITIVE is not supported. + * + * @param type the result set type + * @return true for all types except ResultSet.TYPE_SCROLL_SENSITIVE + */ + @Override + public boolean supportsResultSetType(int type) { + debugCodeCall("supportsResultSetType", type); + return type != ResultSet.TYPE_SCROLL_SENSITIVE; + } + + /** + * Returns whether a specific result set concurrency is supported. + * ResultSet.TYPE_SCROLL_SENSITIVE is not supported. + * + * @param type the result set type + * @param concurrency the result set concurrency + * @return true if the type is not ResultSet.TYPE_SCROLL_SENSITIVE + */ + @Override + public boolean supportsResultSetConcurrency(int type, int concurrency) { + if (isDebugEnabled()) { + debugCode("supportsResultSetConcurrency(" + type + ", " + concurrency + ')'); + } + return type != ResultSet.TYPE_SCROLL_SENSITIVE; + } + + /** + * Returns whether own updates are visible. + * + * @param type the result set type + * @return true + */ + @Override + public boolean ownUpdatesAreVisible(int type) { + debugCodeCall("ownUpdatesAreVisible", type); + return true; + } + + /** + * Returns whether own deletes are visible. + * + * @param type the result set type + * @return false + */ + @Override + public boolean ownDeletesAreVisible(int type) { + debugCodeCall("ownDeletesAreVisible", type); + return false; + } + + /** + * Returns whether own inserts are visible. + * + * @param type the result set type + * @return false + */ + @Override + public boolean ownInsertsAreVisible(int type) { + debugCodeCall("ownInsertsAreVisible", type); + return false; + } + + /** + * Returns whether other updates are visible. + * + * @param type the result set type + * @return false + */ + @Override + public boolean othersUpdatesAreVisible(int type) { + debugCodeCall("othersUpdatesAreVisible", type); + return false; + } + + /** + * Returns whether other deletes are visible. + * + * @param type the result set type + * @return false + */ + @Override + public boolean othersDeletesAreVisible(int type) { + debugCodeCall("othersDeletesAreVisible", type); + return false; + } + + /** + * Returns whether other inserts are visible. + * + * @param type the result set type + * @return false + */ + @Override + public boolean othersInsertsAreVisible(int type) { + debugCodeCall("othersInsertsAreVisible", type); + return false; + } + + /** + * Returns whether updates are detected. + * + * @param type the result set type + * @return false + */ + @Override + public boolean updatesAreDetected(int type) { + debugCodeCall("updatesAreDetected", type); + return false; + } + + /** + * Returns whether deletes are detected. + * + * @param type the result set type + * @return false + */ + @Override + public boolean deletesAreDetected(int type) { + debugCodeCall("deletesAreDetected", type); + return false; + } + + /** + * Returns whether inserts are detected. + * + * @param type the result set type + * @return false + */ + @Override + public boolean insertsAreDetected(int type) { + debugCodeCall("insertsAreDetected", type); + return false; + } + + /** + * Returns whether batch updates are supported. + * + * @return true + */ + @Override + public boolean supportsBatchUpdates() { + debugCodeCall("supportsBatchUpdates"); + return true; + } + + /** + * Returns whether the maximum row size includes blobs. + * + * @return false + */ + @Override + public boolean doesMaxRowSizeIncludeBlobs() { + debugCodeCall("doesMaxRowSizeIncludeBlobs"); + return false; + } + + /** + * Returns the default transaction isolation level. + * + * @return Connection.TRANSACTION_READ_COMMITTED + */ + @Override + public int getDefaultTransactionIsolation() { + debugCodeCall("getDefaultTransactionIsolation"); + return Connection.TRANSACTION_READ_COMMITTED; + } + + /** + * Checks if for CREATE TABLE Test(ID INT), getTables returns Test as the + * table name and identifiers are case sensitive. + * + * @return true is so, false otherwise + */ + @Override + public boolean supportsMixedCaseIdentifiers() throws SQLException { + debugCodeCall("supportsMixedCaseIdentifiers"); + Session.StaticSettings settings = conn.getStaticSettings(); + return !settings.databaseToUpper && !settings.databaseToLower && !settings.caseInsensitiveIdentifiers; + } + + /** + * Checks if for CREATE TABLE Test(ID INT), getTables returns TEST as the + * table name. + * + * @return true is so, false otherwise + */ + @Override + public boolean storesUpperCaseIdentifiers() throws SQLException { + debugCodeCall("storesUpperCaseIdentifiers"); + return conn.getStaticSettings().databaseToUpper; + } + + /** + * Checks if for CREATE TABLE Test(ID INT), getTables returns test as the + * table name. + * + * @return true is so, false otherwise + */ + @Override + public boolean storesLowerCaseIdentifiers() throws SQLException { + debugCodeCall("storesLowerCaseIdentifiers"); + return conn.getStaticSettings().databaseToLower; + } + + /** + * Checks if for CREATE TABLE Test(ID INT), getTables returns Test as the + * table name and identifiers are not case sensitive. + * + * @return true is so, false otherwise + */ + @Override + public boolean storesMixedCaseIdentifiers() throws SQLException { + debugCodeCall("storesMixedCaseIdentifiers"); + Session.StaticSettings settings = conn.getStaticSettings(); + return !settings.databaseToUpper && !settings.databaseToLower && settings.caseInsensitiveIdentifiers; + } + + /** + * Checks if a table created with CREATE TABLE "Test"(ID INT) is a different + * table than a table created with CREATE TABLE "TEST"(ID INT). + * + * @return true is so, false otherwise + */ + @Override + public boolean supportsMixedCaseQuotedIdentifiers() throws SQLException { + debugCodeCall("supportsMixedCaseQuotedIdentifiers"); + return !conn.getStaticSettings().caseInsensitiveIdentifiers; + } + + /** + * Checks if for CREATE TABLE "Test"(ID INT), getTables returns TEST as the + * table name. + * + * @return false + */ + @Override + public boolean storesUpperCaseQuotedIdentifiers() throws SQLException { + debugCodeCall("storesUpperCaseQuotedIdentifiers"); + return false; + } + + /** + * Checks if for CREATE TABLE "Test"(ID INT), getTables returns test as the + * table name. + * + * @return false + */ + @Override + public boolean storesLowerCaseQuotedIdentifiers() throws SQLException { + debugCodeCall("storesLowerCaseQuotedIdentifiers"); + return false; + } + + /** + * Checks if for CREATE TABLE "Test"(ID INT), getTables returns Test as the + * table name and identifiers are case insensitive. + * + * @return true is so, false otherwise + */ + @Override + public boolean storesMixedCaseQuotedIdentifiers() throws SQLException { + debugCodeCall("storesMixedCaseQuotedIdentifiers"); + return conn.getStaticSettings().caseInsensitiveIdentifiers; + } + + /** + * Returns the maximum length for hex values (characters). + * + * @return 0 for limit is unknown + */ + @Override + public int getMaxBinaryLiteralLength() { + debugCodeCall("getMaxBinaryLiteralLength"); + return 0; + } + + /** + * Returns the maximum length for literals. + * + * @return 0 for limit is unknown + */ + @Override + public int getMaxCharLiteralLength() { + debugCodeCall("getMaxCharLiteralLength"); + return 0; + } + + /** + * Returns the maximum length for column names. + * + * @return 0 for limit is unknown + */ + @Override + public int getMaxColumnNameLength() { + debugCodeCall("getMaxColumnNameLength"); + return 0; + } + + /** + * Returns the maximum number of columns in GROUP BY. + * + * @return 0 for limit is unknown + */ + @Override + public int getMaxColumnsInGroupBy() { + debugCodeCall("getMaxColumnsInGroupBy"); + return 0; + } + + /** + * Returns the maximum number of columns in CREATE INDEX. + * + * @return 0 for limit is unknown + */ + @Override + public int getMaxColumnsInIndex() { + debugCodeCall("getMaxColumnsInIndex"); + return 0; + } + + /** + * Returns the maximum number of columns in ORDER BY. + * + * @return 0 for limit is unknown + */ + @Override + public int getMaxColumnsInOrderBy() { + debugCodeCall("getMaxColumnsInOrderBy"); + return 0; + } + + /** + * Returns the maximum number of columns in SELECT. + * + * @return 0 for limit is unknown + */ + @Override + public int getMaxColumnsInSelect() { + debugCodeCall("getMaxColumnsInSelect"); + return 0; + } + + /** + * Returns the maximum number of columns in CREATE TABLE. + * + * @return 0 for limit is unknown + */ + @Override + public int getMaxColumnsInTable() { + debugCodeCall("getMaxColumnsInTable"); + return 0; + } + + /** + * Returns the maximum number of open connection. + * + * @return 0 for limit is unknown + */ + @Override + public int getMaxConnections() { + debugCodeCall("getMaxConnections"); + return 0; + } + + /** + * Returns the maximum length for a cursor name. + * + * @return 0 for limit is unknown + */ + @Override + public int getMaxCursorNameLength() { + debugCodeCall("getMaxCursorNameLength"); + return 0; + } + + /** + * Returns the maximum length for an index (in bytes). + * + * @return 0 for limit is unknown + */ + @Override + public int getMaxIndexLength() { + debugCodeCall("getMaxIndexLength"); + return 0; + } + + /** + * Returns the maximum length for a schema name. + * + * @return 0 for limit is unknown + */ + @Override + public int getMaxSchemaNameLength() { + debugCodeCall("getMaxSchemaNameLength"); + return 0; + } + + /** + * Returns the maximum length for a procedure name. + * + * @return 0 for limit is unknown + */ + @Override + public int getMaxProcedureNameLength() { + debugCodeCall("getMaxProcedureNameLength"); + return 0; + } + + /** + * Returns the maximum length for a catalog name. + * + * @return 0 for limit is unknown + */ + @Override + public int getMaxCatalogNameLength() { + debugCodeCall("getMaxCatalogNameLength"); + return 0; + } + + /** + * Returns the maximum size of a row (in bytes). + * + * @return 0 for limit is unknown + */ + @Override + public int getMaxRowSize() { + debugCodeCall("getMaxRowSize"); + return 0; + } + + /** + * Returns the maximum length of a statement. + * + * @return 0 for limit is unknown + */ + @Override + public int getMaxStatementLength() { + debugCodeCall("getMaxStatementLength"); + return 0; + } + + /** + * Returns the maximum number of open statements. + * + * @return 0 for limit is unknown + */ + @Override + public int getMaxStatements() { + debugCodeCall("getMaxStatements"); + return 0; + } + + /** + * Returns the maximum length for a table name. + * + * @return 0 for limit is unknown + */ + @Override + public int getMaxTableNameLength() { + debugCodeCall("getMaxTableNameLength"); + return 0; + } + + /** + * Returns the maximum number of tables in a SELECT. + * + * @return 0 for limit is unknown + */ + @Override + public int getMaxTablesInSelect() { + debugCodeCall("getMaxTablesInSelect"); + return 0; + } + + /** + * Returns the maximum length for a user name. + * + * @return 0 for limit is unknown + */ + @Override + public int getMaxUserNameLength() { + debugCodeCall("getMaxUserNameLength"); + return 0; + } + + /** + * Does the database support savepoints. + * + * @return true + */ + @Override + public boolean supportsSavepoints() { + debugCodeCall("supportsSavepoints"); + return true; + } + + /** + * Does the database support named parameters. + * + * @return false + */ + @Override + public boolean supportsNamedParameters() { + debugCodeCall("supportsNamedParameters"); + return false; + } + + /** + * Does the database support multiple open result sets returned from a + * CallableStatement. + * + * @return false + */ + @Override + public boolean supportsMultipleOpenResults() { + debugCodeCall("supportsMultipleOpenResults"); + return false; + } + + /** + * Does the database support getGeneratedKeys. + * + * @return true + */ + @Override + public boolean supportsGetGeneratedKeys() { + debugCodeCall("supportsGetGeneratedKeys"); + return true; + } + + /** + * [Not supported] + */ + @Override + public ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getSuperTypes(" + quote(catalog) + ", " + quote(schemaPattern) + ", " + + quote(typeNamePattern) + ')'); + } + return getResultSet(meta.getSuperTypes(catalog, schemaPattern, typeNamePattern)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Get the list of super tables of a table. This method currently returns an + * empty result set. + *
    + *
  1. TABLE_CAT (String) table catalog
  2. + *
  3. TABLE_SCHEM (String) table schema
  4. + *
  5. TABLE_NAME (String) table name
  6. + *
  7. SUPERTABLE_NAME (String) the name of the super table
  8. + *
+ * + * @param catalog null (to get all objects) or the catalog name + * @param schemaPattern null (to get all objects) or a schema name + * (uppercase for unquoted names) + * @param tableNamePattern null (to get all objects) or a table name pattern + * (uppercase for unquoted names) + * @return an empty result set + */ + @Override + public ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) // + throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getSuperTables(" + quote(catalog) + ", " + quote(schemaPattern) + ", " + + quote(tableNamePattern) + ')'); + } + return getResultSet(meta.getSuperTables(catalog, schemaPattern, tableNamePattern)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * [Not supported] + */ + @Override + public ResultSet getAttributes(String catalog, String schemaPattern, String typeNamePattern, + String attributeNamePattern) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getAttributes(" + quote(catalog) + ", " + quote(schemaPattern) + ", " + + quote(typeNamePattern) + ", " + quote(attributeNamePattern) + ')'); + } + return getResultSet(meta.getAttributes(catalog, schemaPattern, typeNamePattern, attributeNamePattern)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Does this database supports a result set holdability. + * + * @param holdability ResultSet.HOLD_CURSORS_OVER_COMMIT or + * CLOSE_CURSORS_AT_COMMIT + * @return true if the holdability is ResultSet.CLOSE_CURSORS_AT_COMMIT + */ + @Override + public boolean supportsResultSetHoldability(int holdability) { + debugCodeCall("supportsResultSetHoldability", holdability); + return holdability == ResultSet.CLOSE_CURSORS_AT_COMMIT; + } + + /** + * Gets the result set holdability. + * + * @return ResultSet.CLOSE_CURSORS_AT_COMMIT + */ + @Override + public int getResultSetHoldability() { + debugCodeCall("getResultSetHoldability"); + return ResultSet.CLOSE_CURSORS_AT_COMMIT; + } + + /** + * Gets the major version of the database. + * + * @return the major version + */ + @Override + public int getDatabaseMajorVersion() throws SQLException { + try { + debugCodeCall("getDatabaseMajorVersion"); + return meta.getDatabaseMajorVersion(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the minor version of the database. + * + * @return the minor version + */ + @Override + public int getDatabaseMinorVersion() throws SQLException { + try { + debugCodeCall("getDatabaseMinorVersion"); + return meta.getDatabaseMinorVersion(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the major version of the supported JDBC API. + * + * @return the major version (4) + */ + @Override + public int getJDBCMajorVersion() { + debugCodeCall("getJDBCMajorVersion"); + return 4; + } + + /** + * Gets the minor version of the supported JDBC API. + * + * @return the minor version (2) + */ + @Override + public int getJDBCMinorVersion() { + debugCodeCall("getJDBCMinorVersion"); + return 2; + } + + /** + * Gets the SQL State type. + * + * @return {@link DatabaseMetaData#sqlStateSQL} + */ + @Override + public int getSQLStateType() { + debugCodeCall("getSQLStateType"); + return DatabaseMetaData.sqlStateSQL; + } + + /** + * Does the database make a copy before updating. + * + * @return false + */ + @Override + public boolean locatorsUpdateCopy() { + debugCodeCall("locatorsUpdateCopy"); + return false; + } + + /** + * Does the database support statement pooling. + * + * @return false + */ + @Override + public boolean supportsStatementPooling() { + debugCodeCall("supportsStatementPooling"); + return false; + } + + // ============================================================= + + private void checkClosed() { + conn.checkClosed(); + } + + /** + * Get the lifetime of a rowid. + * + * @return ROWID_UNSUPPORTED + */ + @Override + public RowIdLifetime getRowIdLifetime() { + debugCodeCall("getRowIdLifetime"); + return RowIdLifetime.ROWID_UNSUPPORTED; + } + + /** + * Gets the list of schemas in the database. + * The result set is sorted by TABLE_SCHEM. + * + *
    + *
  1. TABLE_SCHEM (String) schema name
  2. + *
  3. TABLE_CATALOG (String) catalog name
  4. + *
+ * + * @param catalogPattern null (to get all objects) or the catalog name + * @param schemaPattern null (to get all objects) or a schema name + * (uppercase for unquoted names) + * @return the schema list + * @throws SQLException if the connection is closed + */ + @Override + public ResultSet getSchemas(String catalogPattern, String schemaPattern) + throws SQLException { + try { + debugCodeCall("getSchemas(String,String)"); + return getResultSet(meta.getSchemas(catalogPattern, schemaPattern)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns whether the database supports calling functions using the call + * syntax. + * + * @return true + */ + @Override + public boolean supportsStoredFunctionsUsingCallSyntax() { + debugCodeCall("supportsStoredFunctionsUsingCallSyntax"); + return true; + } + + /** + * Returns whether an exception while auto commit is on closes all result + * sets. + * + * @return false + */ + @Override + public boolean autoCommitFailureClosesAllResultSets() { + debugCodeCall("autoCommitFailureClosesAllResultSets"); + return false; + } + + @Override + public ResultSet getClientInfoProperties() throws SQLException { + Properties clientInfo = conn.getClientInfo(); + SimpleResult result = new SimpleResult(); + result.addColumn("NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("MAX_LEN", TypeInfo.TYPE_INTEGER); + result.addColumn("DEFAULT_VALUE", TypeInfo.TYPE_VARCHAR); + result.addColumn("DESCRIPTION", TypeInfo.TYPE_VARCHAR); + // Non-standard column + result.addColumn("VALUE", TypeInfo.TYPE_VARCHAR); + for (Entry entry : clientInfo.entrySet()) { + result.addRow(ValueVarchar.get((String) entry.getKey()), ValueInteger.get(Integer.MAX_VALUE), + ValueVarchar.EMPTY, ValueVarchar.EMPTY, ValueVarchar.get((String) entry.getValue())); + } + int id = getNextId(TraceObject.RESULT_SET); + debugCodeAssign("ResultSet", TraceObject.RESULT_SET, id, "getClientInfoProperties()"); + return new JdbcResultSet(conn, null, null, result, id, true, false, false); + } + + /** + * Return an object of this class if possible. + * + * @param iface the class + * @return this + */ + @Override + @SuppressWarnings("unchecked") + public T unwrap(Class iface) throws SQLException { + try { + if (isWrapperFor(iface)) { + return (T) this; + } + throw DbException.getInvalidValueException("iface", iface); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if unwrap can return an object of this class. + * + * @param iface the class + * @return whether or not the interface is assignable from this class + */ + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return iface != null && iface.isAssignableFrom(getClass()); + } + + /** + * [Not supported] Gets the list of function columns. + */ + @Override + public ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern, + String columnNamePattern) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getFunctionColumns(" + quote(catalog) + ", " + quote(schemaPattern) + ", " + + quote(functionNamePattern) + ", " + quote(columnNamePattern) + ')'); + } + return getResultSet( + meta.getFunctionColumns(catalog, schemaPattern, functionNamePattern, columnNamePattern)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * [Not supported] Gets the list of functions. + */ + @Override + public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) + throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getFunctions(" + quote(catalog) + ", " + quote(schemaPattern) + ", " + + quote(functionNamePattern) + ')'); + } + return getResultSet(meta.getFunctions(catalog, schemaPattern, functionNamePattern)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns whether database always returns generated keys if valid names or + * indexes of columns were specified and command was completed successfully. + * + * @return true + */ + @Override + public boolean generatedKeyAlwaysReturned() { + return true; + } + + /** + * Gets the list of pseudo and invisible columns. The result set is sorted + * by TABLE_SCHEM, TABLE_NAME, and COLUMN_NAME. + * + *
    + *
  1. TABLE_CAT (String) table catalog
  2. + *
  3. TABLE_SCHEM (String) table schema
  4. + *
  5. TABLE_NAME (String) table name
  6. + *
  7. COLUMN_NAME (String) column name
  8. + *
  9. DATA_TYPE (int) data type (see java.sql.Types)
  10. + *
  11. COLUMN_SIZE (int) precision + * (values larger than 2 GB are returned as 2 GB)
  12. + *
  13. DECIMAL_DIGITS (int) scale (0 for INTEGER and VARCHAR)
  14. + *
  15. NUM_PREC_RADIX (int) radix
  16. + *
  17. COLUMN_USAGE (String) he allowed usage for the column, + * see {@link java.sql.PseudoColumnUsage}
  18. + *
  19. REMARKS (String) comment
  20. + *
  21. CHAR_OCTET_LENGTH (int) for char types the + * maximum number of bytes in the column
  22. + *
  23. IS_NULLABLE (String) "NO" or "YES"
  24. + *
+ * + * @param catalog null (to get all objects) or the catalog name + * @param schemaPattern null (to get all objects) or a schema name + * (uppercase for unquoted names) + * @param tableNamePattern null (to get all objects) or a table name + * (uppercase for unquoted names) + * @param columnNamePattern null (to get all objects) or a column name + * (uppercase for unquoted names) + * @return the list of pseudo and invisible columns + */ + @Override + public ResultSet getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, + String columnNamePattern) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getPseudoColumns(" + quote(catalog) + ", " + quote(schemaPattern) + ", " + + quote(tableNamePattern) + ", " + quote(columnNamePattern) + ')'); + } + return getResultSet(meta.getPseudoColumns(catalog, schemaPattern, tableNamePattern, columnNamePattern)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * INTERNAL + */ + @Override + public String toString() { + return getTraceObjectName() + ": " + conn; + } + + private JdbcResultSet getResultSet(ResultInterface result) { + return new JdbcResultSet(conn, null, null, result, getNextId(TraceObject.RESULT_SET), true, false, false); + } + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcDatabaseMetaDataBackwardsCompat.java b/h2/src/main/org/h2/jdbc/JdbcDatabaseMetaDataBackwardsCompat.java new file mode 100644 index 0000000..9dafb7a --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcDatabaseMetaDataBackwardsCompat.java @@ -0,0 +1,16 @@ +/* + * 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.jdbc; + +/** + * Allows us to compile on older platforms, while still implementing the methods + * from the newer JDBC API. + */ +public interface JdbcDatabaseMetaDataBackwardsCompat { + + // compatibility interface + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcException.java b/h2/src/main/org/h2/jdbc/JdbcException.java new file mode 100644 index 0000000..4578f57 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcException.java @@ -0,0 +1,51 @@ +/* + * 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.jdbc; + +/** + * This interface contains additional methods for database exceptions. + */ +public interface JdbcException { + + /** + * Returns the H2-specific error code. + * + * @return the H2-specific error code + */ + public int getErrorCode(); + + /** + * INTERNAL + * @return original message + */ + String getOriginalMessage(); + + /** + * Returns the SQL statement. + *

+ * SQL statements that contain '--hide--' are not listed. + *

+ * + * @return the SQL statement + */ + String getSQL(); + + /** + * INTERNAL + * @param sql to set + */ + void setSQL(String sql); + + /** + * Returns the class name, the message, and in the server mode, the stack + * trace of the server + * + * @return the string representation + */ + @Override + String toString(); + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcLob.java b/h2/src/main/org/h2/jdbc/JdbcLob.java new file mode 100644 index 0000000..6862c1b --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcLob.java @@ -0,0 +1,230 @@ +/* + * 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.jdbc; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.io.Reader; +import java.io.Writer; +import java.sql.SQLException; + +import org.h2.api.ErrorCode; +import org.h2.message.DbException; +import org.h2.message.TraceObject; +import org.h2.mvstore.DataUtils; +import org.h2.util.IOUtils; +import org.h2.util.Task; +import org.h2.value.Value; + +/** + * Represents a large object value. + */ +public abstract class JdbcLob extends TraceObject { + + static final class LobPipedOutputStream extends PipedOutputStream { + private final Task task; + + LobPipedOutputStream(PipedInputStream snk, Task task) throws IOException { + super(snk); + this.task = task; + } + + @Override + public void close() throws IOException { + super.close(); + try { + task.get(); + } catch (Exception e) { + throw DataUtils.convertToIOException(e); + } + } + } + + /** + * State of the object. + */ + public enum State { + /** + * New object without a value. + */ + NEW, + + /** + * One of setter methods is invoked, but stream is not closed yet. + */ + SET_CALLED, + + /** + * A value is set. + */ + WITH_VALUE, + + /** + * Object is closed. + */ + CLOSED; + } + + /** + * JDBC connection. + */ + final JdbcConnection conn; + + /** + * Value. + */ + Value value; + + /** + * State. + */ + State state; + + JdbcLob(JdbcConnection conn, Value value, State state, int type, int id) { + setTrace(conn.getSession().getTrace(), type, id); + this.conn = conn; + this.value = value; + this.state = state; + } + + /** + * Check that connection and LOB is not closed, otherwise throws exception with + * error code {@link org.h2.api.ErrorCode#OBJECT_CLOSED}. + */ + void checkClosed() { + conn.checkClosed(); + if (state == State.CLOSED) { + throw DbException.get(ErrorCode.OBJECT_CLOSED); + } + } + + /** + * Check the state of the LOB and throws the exception when check failed + * (set is supported only for a new LOB). + */ + void checkEditable() { + checkClosed(); + if (state != State.NEW) { + throw DbException.getUnsupportedException("Allocate a new object to set its value."); + } + } + + /** + * Check the state of the LOB and throws the exception when check failed + * (the LOB must be set completely before read). + * + * @throws SQLException on SQL exception + * @throws IOException on I/O exception + */ + void checkReadable() throws SQLException, IOException { + checkClosed(); + if (state == State.SET_CALLED) { + throw DbException.getUnsupportedException("Stream setter is not yet closed."); + } + } + + /** + * Change the state LOB state (LOB value is set completely and available to read). + * @param blob LOB value. + */ + void completeWrite(Value blob) { + checkClosed(); + state = State.WITH_VALUE; + value = blob; + } + + /** + * Release all resources of this object. + */ + public void free() { + debugCodeCall("free"); + state = State.CLOSED; + value = null; + } + + /** + * Returns the input stream. + * + * @return the input stream + * @throws SQLException on failure + */ + InputStream getBinaryStream() throws SQLException { + try { + debugCodeCall("getBinaryStream"); + checkReadable(); + return value.getInputStream(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the reader. + * + * @return the reader + * @throws SQLException on failure + */ + Reader getCharacterStream() throws SQLException { + try { + debugCodeCall("getCharacterStream"); + checkReadable(); + return value.getReader(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the writer. + * + * @return Writer. + * @throws IOException If an I/O error occurs. + */ + Writer setCharacterStreamImpl() throws IOException { + return IOUtils.getBufferedWriter(setClobOutputStreamImpl()); + } + + /** + * Returns the writer stream. + * + * @return Output stream.. + * @throws IOException If an I/O error occurs. + */ + LobPipedOutputStream setClobOutputStreamImpl() throws IOException { + // PipedReader / PipedWriter are a lot slower + // than PipedInputStream / PipedOutputStream + // (Sun/Oracle Java 1.6.0_20) + final PipedInputStream in = new PipedInputStream(); + final Task task = new Task() { + @Override + public void call() { + completeWrite(conn.createClob(IOUtils.getReader(in), -1)); + } + }; + LobPipedOutputStream out = new LobPipedOutputStream(in, task); + task.execute(); + return out; + } + + /** + * INTERNAL + */ + @Override + public String toString() { + StringBuilder builder = new StringBuilder().append(getTraceObjectName()).append(": "); + if (state == State.SET_CALLED) { + builder.append(""); + } else if (state == State.CLOSED) { + builder.append(""); + } else { + builder.append(value.getTraceSQL()); + } + return builder.toString(); + } + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcParameterMetaData.java b/h2/src/main/org/h2/jdbc/JdbcParameterMetaData.java new file mode 100644 index 0000000..febbe79 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcParameterMetaData.java @@ -0,0 +1,257 @@ +/* + * 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.jdbc; + +import java.sql.ParameterMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import org.h2.command.CommandInterface; +import org.h2.expression.ParameterInterface; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.message.TraceObject; +import org.h2.util.MathUtils; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueToObjectConverter; + +/** + * Information about the parameters of a prepared statement. + */ +public final class JdbcParameterMetaData extends TraceObject implements ParameterMetaData { + + private final JdbcPreparedStatement prep; + private final int paramCount; + private final ArrayList parameters; + + JdbcParameterMetaData(Trace trace, JdbcPreparedStatement prep, + CommandInterface command, int id) { + setTrace(trace, TraceObject.PARAMETER_META_DATA, id); + this.prep = prep; + this.parameters = command.getParameters(); + this.paramCount = parameters.size(); + } + + /** + * Returns the number of parameters. + * + * @return the number + */ + @Override + public int getParameterCount() throws SQLException { + try { + debugCodeCall("getParameterCount"); + checkClosed(); + return paramCount; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the parameter mode. + * Always returns parameterModeIn. + * + * @param param the column index (1,2,...) + * @return parameterModeIn + */ + @Override + public int getParameterMode(int param) throws SQLException { + try { + debugCodeCall("getParameterMode", param); + getParameter(param); + return parameterModeIn; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the parameter type. + * java.sql.Types.VARCHAR is returned if the data type is not known. + * + * @param param the column index (1,2,...) + * @return the data type + */ + @Override + public int getParameterType(int param) throws SQLException { + try { + debugCodeCall("getParameterType", param); + TypeInfo type = getParameter(param).getType(); + if (type.getValueType() == Value.UNKNOWN) { + type = TypeInfo.TYPE_VARCHAR; + } + return DataType.convertTypeToSQLType(type); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the parameter precision. + * The value 0 is returned if the precision is not known. + * + * @param param the column index (1,2,...) + * @return the precision + */ + @Override + public int getPrecision(int param) throws SQLException { + try { + debugCodeCall("getPrecision", param); + TypeInfo type = getParameter(param).getType(); + return type.getValueType() == Value.UNKNOWN ? 0 : MathUtils.convertLongToInt(type.getPrecision()); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the parameter scale. + * The value 0 is returned if the scale is not known. + * + * @param param the column index (1,2,...) + * @return the scale + */ + @Override + public int getScale(int param) throws SQLException { + try { + debugCodeCall("getScale", param); + TypeInfo type = getParameter(param).getType(); + return type.getValueType() == Value.UNKNOWN ? 0 : type.getScale(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if this is nullable parameter. + * Returns ResultSetMetaData.columnNullableUnknown.. + * + * @param param the column index (1,2,...) + * @return ResultSetMetaData.columnNullableUnknown + */ + @Override + public int isNullable(int param) throws SQLException { + try { + debugCodeCall("isNullable", param); + return getParameter(param).getNullable(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if this parameter is signed. + * It always returns true. + * + * @param param the column index (1,2,...) + * @return true + */ + @Override + public boolean isSigned(int param) throws SQLException { + try { + debugCodeCall("isSigned", param); + getParameter(param); + return true; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the Java class name of the parameter. + * "java.lang.String" is returned if the type is not known. + * + * @param param the column index (1,2,...) + * @return the Java class name + */ + @Override + public String getParameterClassName(int param) throws SQLException { + try { + debugCodeCall("getParameterClassName", param); + int type = getParameter(param).getType().getValueType(); + if (type == Value.UNKNOWN) { + type = Value.VARCHAR; + } + return ValueToObjectConverter.getDefaultClass(type, true).getName(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the parameter type name. + * "VARCHAR" is returned if the type is not known. + * + * @param param the column index (1,2,...) + * @return the type name + */ + @Override + public String getParameterTypeName(int param) throws SQLException { + try { + debugCodeCall("getParameterTypeName", param); + TypeInfo type = getParameter(param).getType(); + if (type.getValueType() == Value.UNKNOWN) { + type = TypeInfo.TYPE_VARCHAR; + } + return type.getDeclaredTypeName(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private ParameterInterface getParameter(int param) { + checkClosed(); + if (param < 1 || param > paramCount) { + throw DbException.getInvalidValueException("param", param); + } + return parameters.get(param - 1); + } + + private void checkClosed() { + prep.checkClosed(); + } + + /** + * Return an object of this class if possible. + * + * @param iface the class + * @return this + */ + @Override + @SuppressWarnings("unchecked") + public T unwrap(Class iface) throws SQLException { + try { + if (isWrapperFor(iface)) { + return (T) this; + } + throw DbException.getInvalidValueException("iface", iface); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if unwrap can return an object of this class. + * + * @param iface the class + * @return whether or not the interface is assignable from this class + */ + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return iface != null && iface.isAssignableFrom(getClass()); + } + + /** + * INTERNAL + */ + @Override + public String toString() { + return getTraceObjectName() + ": parameterCount=" + paramCount; + } + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcPreparedStatement.java b/h2/src/main/org/h2/jdbc/JdbcPreparedStatement.java new file mode 100644 index 0000000..9533d97 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcPreparedStatement.java @@ -0,0 +1,1627 @@ +/* + * 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.jdbc; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.NClob; +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLType; +import java.sql.SQLXML; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.HashMap; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.expression.ParameterInterface; +import org.h2.message.DbException; +import org.h2.message.TraceObject; +import org.h2.result.MergedResult; +import org.h2.result.ResultInterface; +import org.h2.result.ResultWithGeneratedKeys; +import org.h2.util.IOUtils; +import org.h2.util.LegacyDateTimeUtils; +import org.h2.util.Utils; +import org.h2.value.DataType; +import org.h2.value.Value; +import org.h2.value.ValueBigint; +import org.h2.value.ValueBoolean; +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.ValueTinyint; +import org.h2.value.ValueToObjectConverter; +import org.h2.value.ValueVarbinary; +import org.h2.value.ValueVarchar; + +/** + * Represents a prepared statement. + *

+ * Thread safety: the prepared statement is not thread-safe. If the same + * prepared statement is used by multiple threads access to it must be + * synchronized. The single synchronized block must include assignment of + * parameters, execution of the command and all operations with its result. + *

+ *
+ * synchronized (prep) {
+ *     prep.setInt(1, 10);
+ *     try (ResultSet rs = prep.executeQuery()) {
+ *         while (rs.next) {
+ *             // Do something
+ *         }
+ *     }
+ * }
+ * synchronized (prep) {
+ *     prep.setInt(1, 15);
+ *     updateCount = prep.executeUpdate();
+ * }
+ * 
+ */ +public class JdbcPreparedStatement extends JdbcStatement implements PreparedStatement { + + protected CommandInterface command; + private ArrayList batchParameters; + private MergedResult batchIdentities; + private HashMap cachedColumnLabelMap; + private final Object generatedKeysRequest; + + JdbcPreparedStatement(JdbcConnection conn, String sql, int id, int resultSetType, int resultSetConcurrency, + Object generatedKeysRequest) { + super(conn, id, resultSetType, resultSetConcurrency); + this.generatedKeysRequest = generatedKeysRequest; + setTrace(session.getTrace(), TraceObject.PREPARED_STATEMENT, id); + command = conn.prepareCommand(sql, fetchSize); + } + + /** + * Cache the column labels (looking up the column index can sometimes show + * up on the performance profile). + * + * @param cachedColumnLabelMap the column map + */ + void setCachedColumnLabelMap(HashMap cachedColumnLabelMap) { + this.cachedColumnLabelMap = cachedColumnLabelMap; + } + + /** + * Executes a query (select statement) and returns the result set. If + * another result set exists for this statement, this will be closed (even + * if this statement fails). + * + * @return the result set + * @throws SQLException if this object is closed or invalid + */ + @Override + public ResultSet executeQuery() throws SQLException { + try { + int id = getNextId(TraceObject.RESULT_SET); + debugCodeAssign("ResultSet", TraceObject.RESULT_SET, id, "executeQuery()"); + batchIdentities = null; + synchronized (session) { + checkClosed(); + closeOldResultSet(); + ResultInterface result; + boolean lazy = false; + boolean scrollable = resultSetType != ResultSet.TYPE_FORWARD_ONLY; + boolean updatable = resultSetConcurrency == ResultSet.CONCUR_UPDATABLE; + try { + setExecutingStatement(command); + result = command.executeQuery(maxRows, scrollable); + lazy = result.isLazy(); + } finally { + if (!lazy) { + setExecutingStatement(null); + } + } + resultSet = new JdbcResultSet(conn, this, command, result, id, scrollable, updatable, + cachedColumnLabelMap); + } + return resultSet; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Executes a statement (insert, update, delete, create, drop) + * and returns the update count. + * If another result set exists for this statement, this will be closed + * (even if this statement fails). + * + * If auto commit is on, this statement will be committed. + * If the statement is a DDL statement (create, drop, alter) and does not + * throw an exception, the current transaction (if any) is committed after + * executing the statement. + * + * @return the update count (number of affected rows by a DML statement or + * other statement able to return number of rows, or 0 if no rows + * were affected or the statement returns nothing, or + * {@link #SUCCESS_NO_INFO} if number of rows is too large for + * {@code int} data type) + * @throws SQLException if this object is closed or invalid + * @see #executeLargeUpdate() + */ + @Override + public int executeUpdate() throws SQLException { + try { + debugCodeCall("executeUpdate"); + checkClosed(); + batchIdentities = null; + long updateCount = executeUpdateInternal(); + return updateCount <= Integer.MAX_VALUE ? (int) updateCount : SUCCESS_NO_INFO; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Executes a statement (insert, update, delete, create, drop) + * and returns the update count. + * If another result set exists for this statement, this will be closed + * (even if this statement fails). + * + * If auto commit is on, this statement will be committed. + * If the statement is a DDL statement (create, drop, alter) and does not + * throw an exception, the current transaction (if any) is committed after + * executing the statement. + * + * @return the update count (number of affected rows by a DML statement or + * other statement able to return number of rows, or 0 if no rows + * were affected or the statement returns nothing) + * @throws SQLException if this object is closed or invalid + */ + @Override + public long executeLargeUpdate() throws SQLException { + try { + debugCodeCall("executeLargeUpdate"); + checkClosed(); + batchIdentities = null; + return executeUpdateInternal(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private long executeUpdateInternal() { + closeOldResultSet(); + synchronized (session) { + try { + setExecutingStatement(command); + ResultWithGeneratedKeys result = command.executeUpdate(generatedKeysRequest); + updateCount = result.getUpdateCount(); + ResultInterface gk = result.getGeneratedKeys(); + if (gk != null) { + int id = getNextId(TraceObject.RESULT_SET); + generatedKeys = new JdbcResultSet(conn, this, command, gk, id, true, false, false); + } + } finally { + setExecutingStatement(null); + } + } + return updateCount; + } + + /** + * Executes an arbitrary statement. If another result set exists for this + * statement, this will be closed (even if this statement fails). If auto + * commit is on, and the statement is not a select, this statement will be + * committed. + * + * @return true if a result set is available, false if not + * @throws SQLException if this object is closed or invalid + */ + @Override + public boolean execute() throws SQLException { + try { + int id = getNextId(TraceObject.RESULT_SET); + debugCodeCall("execute"); + checkClosed(); + boolean returnsResultSet; + synchronized (session) { + closeOldResultSet(); + boolean lazy = false; + try { + setExecutingStatement(command); + if (command.isQuery()) { + returnsResultSet = true; + boolean scrollable = resultSetType != ResultSet.TYPE_FORWARD_ONLY; + boolean updatable = resultSetConcurrency == ResultSet.CONCUR_UPDATABLE; + ResultInterface result = command.executeQuery(maxRows, scrollable); + lazy = result.isLazy(); + resultSet = new JdbcResultSet(conn, this, command, result, id, scrollable, updatable, + cachedColumnLabelMap); + } else { + returnsResultSet = false; + ResultWithGeneratedKeys result = command.executeUpdate(generatedKeysRequest); + updateCount = result.getUpdateCount(); + ResultInterface gk = result.getGeneratedKeys(); + if (gk != null) { + generatedKeys = new JdbcResultSet(conn, this, command, gk, id, true, false, false); + } + } + } finally { + if (!lazy) { + setExecutingStatement(null); + } + } + } + return returnsResultSet; + } catch (Throwable e) { + throw logAndConvert(e); + } + } + + /** + * Clears all parameters. + * + * @throws SQLException if this object is closed or invalid + */ + @Override + public void clearParameters() throws SQLException { + try { + debugCodeCall("clearParameters"); + checkClosed(); + ArrayList parameters = command.getParameters(); + for (ParameterInterface param : parameters) { + // can only delete old temp files if they are not in the batch + param.setValue(null, batchParameters == null); + } + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Calling this method is not legal on a PreparedStatement. + * + * @param sql ignored + * @throws SQLException Unsupported Feature + */ + @Override + public ResultSet executeQuery(String sql) throws SQLException { + try { + debugCodeCall("executeQuery", sql); + throw DbException.get(ErrorCode.METHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Calling this method is not legal on a PreparedStatement. + * + * @param sql ignored + * @throws SQLException Unsupported Feature + */ + @Override + public void addBatch(String sql) throws SQLException { + try { + debugCodeCall("addBatch", sql); + throw DbException.get(ErrorCode.METHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + // ============================================================= + + /** + * Sets a parameter to null. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param sqlType the data type (Types.x) + * @throws SQLException if this object is closed + */ + @Override + public void setNull(int parameterIndex, int sqlType) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setNull(" + parameterIndex + ", " + sqlType + ')'); + } + setParameter(parameterIndex, ValueNull.INSTANCE); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setInt(int parameterIndex, int x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setInt(" + parameterIndex + ", " + x + ')'); + } + setParameter(parameterIndex, ValueInteger.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setString(int parameterIndex, String x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setString(" + parameterIndex + ", " + quote(x) + ')'); + } + setParameter(parameterIndex, x == null ? ValueNull.INSTANCE : ValueVarchar.get(x, conn)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setBigDecimal(" + parameterIndex + ", " + quoteBigDecimal(x) + ')'); + } + setParameter(parameterIndex, x == null ? ValueNull.INSTANCE : ValueNumeric.getAnyScale(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter. + *

+ * Usage of this method is discouraged. Use + * {@code setObject(parameterIndex, value)} with {@link java.time.LocalDate} + * parameter instead. + *

+ * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + * @see #setObject(int, Object) + */ + @Override + public void setDate(int parameterIndex, java.sql.Date x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setDate(" + parameterIndex + ", " + quoteDate(x) + ')'); + } + setParameter(parameterIndex, x == null ? ValueNull.INSTANCE : LegacyDateTimeUtils.fromDate(conn, null, x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter. + *

+ * Usage of this method is discouraged. Use + * {@code setObject(parameterIndex, value)} with {@link java.time.LocalTime} + * parameter instead. + *

+ * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + * @see #setObject(int, Object) + */ + @Override + public void setTime(int parameterIndex, java.sql.Time x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setTime(" + parameterIndex + ", " + quoteTime(x) + ')'); + } + setParameter(parameterIndex, x == null ? ValueNull.INSTANCE : LegacyDateTimeUtils.fromTime(conn, null, x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter. + *

+ * Usage of this method is discouraged. Use + * {@code setObject(parameterIndex, value)} with + * {@link java.time.LocalDateTime} parameter instead. + *

+ * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + * @see #setObject(int, Object) + */ + @Override + public void setTimestamp(int parameterIndex, java.sql.Timestamp x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setTimestamp(" + parameterIndex + ", " + quoteTimestamp(x) + ')'); + } + setParameter(parameterIndex, + x == null ? ValueNull.INSTANCE : LegacyDateTimeUtils.fromTimestamp(conn, null, x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter. + * Objects of unknown classes are serialized (on the client side). + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setObject(int parameterIndex, Object x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setObject(" + parameterIndex + ", x)"); + } + if (x == null) { + setParameter(parameterIndex, ValueNull.INSTANCE); + } else { + setParameter(parameterIndex, ValueToObjectConverter.objectToValue(session, x, Value.UNKNOWN)); + } + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter. The object is converted, if required, to + * the specified data type before sending to the database. + * Objects of unknown classes are serialized (on the client side). + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value, null is allowed + * @param targetSqlType the type as defined in java.sql.Types + * @throws SQLException if this object is closed + */ + @Override + public void setObject(int parameterIndex, Object x, int targetSqlType) + throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setObject(" + parameterIndex + ", x, " + targetSqlType + ')'); + } + setObjectWithType(parameterIndex, x, DataType.convertSQLTypeToValueType(targetSqlType)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter. The object is converted, if required, to + * the specified data type before sending to the database. + * Objects of unknown classes are serialized (on the client side). + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value, null is allowed + * @param targetSqlType the type as defined in java.sql.Types + * @param scale is ignored + * @throws SQLException if this object is closed + */ + @Override + public void setObject(int parameterIndex, Object x, int targetSqlType, + int scale) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setObject(" + parameterIndex + ", x, " + targetSqlType + ", " + scale + ')'); + } + setObjectWithType(parameterIndex, x, DataType.convertSQLTypeToValueType(targetSqlType)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter. The object is converted, if required, to + * the specified data type before sending to the database. + * Objects of unknown classes are serialized (on the client side). + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value, null is allowed + * @param targetSqlType the SQL type + * @throws SQLException if this object is closed + */ + @Override + public void setObject(int parameterIndex, Object x, SQLType targetSqlType) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setObject(" + parameterIndex + ", x, " + DataType.sqlTypeToString(targetSqlType) + ')'); + } + setObjectWithType(parameterIndex, x, DataType.convertSQLTypeToValueType(targetSqlType)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter. The object is converted, if required, to + * the specified data type before sending to the database. + * Objects of unknown classes are serialized (on the client side). + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value, null is allowed + * @param targetSqlType the SQL type + * @param scaleOrLength is ignored + * @throws SQLException if this object is closed + */ + @Override + public void setObject(int parameterIndex, Object x, SQLType targetSqlType, int scaleOrLength) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setObject(" + parameterIndex + ", x, " + DataType.sqlTypeToString(targetSqlType) + ", " + + scaleOrLength + ')'); + } + setObjectWithType(parameterIndex, x, DataType.convertSQLTypeToValueType(targetSqlType)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private void setObjectWithType(int parameterIndex, Object x, int type) { + if (x == null) { + setParameter(parameterIndex, ValueNull.INSTANCE); + } else { + Value v = ValueToObjectConverter.objectToValue(conn.getSession(), x, type); + if (type != Value.UNKNOWN) { + v = v.convertTo(type, conn); + } + setParameter(parameterIndex, v); + } + } + + /** + * Sets the value of a parameter. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setBoolean(int parameterIndex, boolean x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setBoolean(" + parameterIndex + ", " + x + ')'); + } + setParameter(parameterIndex, ValueBoolean.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setByte(int parameterIndex, byte x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setByte(" + parameterIndex + ", " + x + ')'); + } + setParameter(parameterIndex, ValueTinyint.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setShort(int parameterIndex, short x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setShort(" + parameterIndex + ", (short) " + x + ')'); + } + setParameter(parameterIndex, ValueSmallint.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setLong(int parameterIndex, long x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setLong(" + parameterIndex + ", " + x + "L)"); + } + setParameter(parameterIndex, ValueBigint.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setFloat(int parameterIndex, float x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setFloat(" + parameterIndex + ", " + x + "f)"); + } + setParameter(parameterIndex, ValueReal.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setDouble(int parameterIndex, double x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setDouble(" + parameterIndex + ", " + x + "d)"); + } + setParameter(parameterIndex, ValueDouble.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * [Not supported] Sets the value of a column as a reference. + */ + @Override + public void setRef(int parameterIndex, Ref x) throws SQLException { + throw unsupported("ref"); + } + + /** + * Sets the date using a specified time zone. The value will be converted to + * the local time zone. + *

+ * Usage of this method is discouraged. Use + * {@code setObject(parameterIndex, value)} with {@link java.time.LocalDate} + * parameter instead. + *

+ * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @param calendar the calendar + * @throws SQLException if this object is closed + * @see #setObject(int, Object) + */ + @Override + public void setDate(int parameterIndex, java.sql.Date x, Calendar calendar) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setDate(" + parameterIndex + ", " + quoteDate(x) + ", calendar)"); + } + if (x == null) { + setParameter(parameterIndex, ValueNull.INSTANCE); + } else { + setParameter(parameterIndex, + LegacyDateTimeUtils.fromDate(conn, calendar != null ? calendar.getTimeZone() : null, x)); + } + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the time using a specified time zone. The value will be converted to + * the local time zone. + *

+ * Usage of this method is discouraged. Use + * {@code setObject(parameterIndex, value)} with {@link java.time.LocalTime} + * parameter instead. + *

+ * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @param calendar the calendar + * @throws SQLException if this object is closed + * @see #setObject(int, Object) + */ + @Override + public void setTime(int parameterIndex, java.sql.Time x, Calendar calendar) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setTime(" + parameterIndex + ", " + quoteTime(x) + ", calendar)"); + } + if (x == null) { + setParameter(parameterIndex, ValueNull.INSTANCE); + } else { + setParameter(parameterIndex, + LegacyDateTimeUtils.fromTime(conn, calendar != null ? calendar.getTimeZone() : null, x)); + } + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the timestamp using a specified time zone. The value will be + * converted to the local time zone. + *

+ * Usage of this method is discouraged. Use + * {@code setObject(parameterIndex, value)} with + * {@link java.time.LocalDateTime} parameter instead. + *

+ * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @param calendar the calendar + * @throws SQLException if this object is closed + * @see #setObject(int, Object) + */ + @Override + public void setTimestamp(int parameterIndex, java.sql.Timestamp x, Calendar calendar) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setTimestamp(" + parameterIndex + ", " + quoteTimestamp(x) + ", calendar)"); + } + if (x == null) { + setParameter(parameterIndex, ValueNull.INSTANCE); + } else { + setParameter(parameterIndex, + LegacyDateTimeUtils.fromTimestamp(conn, calendar != null ? calendar.getTimeZone() : null, x)); + } + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * [Not supported] This feature is deprecated and not supported. + * + * @deprecated since JDBC 2.0, use setCharacterStream + */ + @Deprecated + @Override + public void setUnicodeStream(int parameterIndex, InputStream x, int length) + throws SQLException { + throw unsupported("unicodeStream"); + } + + /** + * Sets a parameter to null. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param sqlType the data type (Types.x) + * @param typeName this parameter is ignored + * @throws SQLException if this object is closed + */ + @Override + public void setNull(int parameterIndex, int sqlType, String typeName) + throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setNull(" + parameterIndex + ", " + sqlType + ", " + quote(typeName) + ')'); + } + setNull(parameterIndex, sqlType); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter as a Blob. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setBlob(int parameterIndex, Blob x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setBlob(" + parameterIndex + ", x)"); + } + checkClosed(); + Value v; + if (x == null) { + v = ValueNull.INSTANCE; + } else { + v = conn.createBlob(x.getBinaryStream(), -1); + } + setParameter(parameterIndex, v); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter as a Blob. + * This method does not close the stream. + * The stream may be closed after executing the statement. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setBlob(int parameterIndex, InputStream x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setBlob(" + parameterIndex + ", x)"); + } + checkClosed(); + Value v = conn.createBlob(x, -1); + setParameter(parameterIndex, v); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter as a Clob. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setClob(int parameterIndex, Clob x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setClob(" + parameterIndex + ", x)"); + } + checkClosed(); + Value v; + if (x == null) { + v = ValueNull.INSTANCE; + } else { + v = conn.createClob(x.getCharacterStream(), -1); + } + setParameter(parameterIndex, v); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter as a Clob. + * This method does not close the reader. + * The reader may be closed after executing the statement. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setClob(int parameterIndex, Reader x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setClob(" + parameterIndex + ", x)"); + } + checkClosed(); + Value v; + if (x == null) { + v = ValueNull.INSTANCE; + } else { + v = conn.createClob(x, -1); + } + setParameter(parameterIndex, v); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter as an Array. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setArray(int parameterIndex, Array x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setArray(" + parameterIndex + ", x)"); + } + checkClosed(); + Value v; + if (x == null) { + v = ValueNull.INSTANCE; + } else { + v = ValueToObjectConverter.objectToValue(session, x.getArray(), Value.ARRAY); + } + setParameter(parameterIndex, v); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter as a byte array. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setBytes(int parameterIndex, byte[] x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setBytes(" + parameterIndex + ", " + quoteBytes(x) + ')'); + } + setParameter(parameterIndex, x == null ? ValueNull.INSTANCE : ValueVarbinary.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter as an input stream. + * This method does not close the stream. + * The stream may be closed after executing the statement. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @param length the maximum number of bytes + * @throws SQLException if this object is closed + */ + @Override + public void setBinaryStream(int parameterIndex, InputStream x, long length) + throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setBinaryStream(" + parameterIndex + ", x, " + length + "L)"); + } + checkClosed(); + Value v = conn.createBlob(x, length); + setParameter(parameterIndex, v); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter as an input stream. + * This method does not close the stream. + * The stream may be closed after executing the statement. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @param length the maximum number of bytes + * @throws SQLException if this object is closed + */ + @Override + public void setBinaryStream(int parameterIndex, InputStream x, int length) + throws SQLException { + setBinaryStream(parameterIndex, x, (long) length); + } + + /** + * Sets the value of a parameter as an input stream. + * This method does not close the stream. + * The stream may be closed after executing the statement. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setBinaryStream(int parameterIndex, InputStream x) + throws SQLException { + setBinaryStream(parameterIndex, x, -1); + } + + /** + * Sets the value of a parameter as an ASCII stream. + * This method does not close the stream. + * The stream may be closed after executing the statement. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @param length the maximum number of bytes + * @throws SQLException if this object is closed + */ + @Override + public void setAsciiStream(int parameterIndex, InputStream x, int length) + throws SQLException { + setAsciiStream(parameterIndex, x, (long) length); + } + + /** + * Sets the value of a parameter as an ASCII stream. + * This method does not close the stream. + * The stream may be closed after executing the statement. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @param length the maximum number of bytes + * @throws SQLException if this object is closed + */ + @Override + public void setAsciiStream(int parameterIndex, InputStream x, long length) + throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setAsciiStream(" + parameterIndex + ", x, " + length + "L)"); + } + checkClosed(); + Value v = conn.createClob(IOUtils.getAsciiReader(x), length); + setParameter(parameterIndex, v); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter as an ASCII stream. + * This method does not close the stream. + * The stream may be closed after executing the statement. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setAsciiStream(int parameterIndex, InputStream x) + throws SQLException { + setAsciiStream(parameterIndex, x, -1); + } + + /** + * Sets the value of a parameter as a character stream. + * This method does not close the reader. + * The reader may be closed after executing the statement. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @param length the maximum number of characters + * @throws SQLException if this object is closed + */ + @Override + public void setCharacterStream(int parameterIndex, Reader x, int length) + throws SQLException { + setCharacterStream(parameterIndex, x, (long) length); + } + + /** + * Sets the value of a parameter as a character stream. + * This method does not close the reader. + * The reader may be closed after executing the statement. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setCharacterStream(int parameterIndex, Reader x) + throws SQLException { + setCharacterStream(parameterIndex, x, -1); + } + + /** + * Sets the value of a parameter as a character stream. + * This method does not close the reader. + * The reader may be closed after executing the statement. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @param length the maximum number of characters + * @throws SQLException if this object is closed + */ + @Override + public void setCharacterStream(int parameterIndex, Reader x, long length) + throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setCharacterStream(" + parameterIndex + ", x, " + length + "L)"); + } + checkClosed(); + Value v = conn.createClob(x, length); + setParameter(parameterIndex, v); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * [Not supported] + */ + @Override + public void setURL(int parameterIndex, URL x) throws SQLException { + throw unsupported("url"); + } + + /** + * Gets the result set metadata of the query returned when the statement is + * executed. If this is not a query, this method returns null. + * + * @return the meta data or null if this is not a query + * @throws SQLException if this object is closed + */ + @Override + public ResultSetMetaData getMetaData() throws SQLException { + try { + debugCodeCall("getMetaData"); + checkClosed(); + ResultInterface result = command.getMetaData(); + if (result == null) { + return null; + } + int id = getNextId(TraceObject.RESULT_SET_META_DATA); + debugCodeAssign("ResultSetMetaData", TraceObject.RESULT_SET_META_DATA, id, "getMetaData()"); + String catalog = conn.getCatalog(); + return new JdbcResultSetMetaData(null, this, result, catalog, session.getTrace(), id); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Clears the batch. + */ + @Override + public void clearBatch() throws SQLException { + try { + debugCodeCall("clearBatch"); + checkClosed(); + batchParameters = null; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Closes this statement. + * All result sets that where created by this statement + * become invalid after calling this method. + */ + @Override + public void close() throws SQLException { + try { + super.close(); + batchParameters = null; + batchIdentities = null; + if (command != null) { + command.close(); + command = null; + } + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Executes the batch. + * If one of the batched statements fails, this database will continue. + * + * @return the array of update counts + * @see #executeLargeBatch() + */ + @Override + public int[] executeBatch() throws SQLException { + try { + debugCodeCall("executeBatch"); + if (batchParameters == null) { + // Empty batch is allowed, see JDK-4639504 and other issues + batchParameters = new ArrayList<>(); + } + batchIdentities = new MergedResult(); + int size = batchParameters.size(); + int[] result = new int[size]; + SQLException exception = new SQLException(); + checkClosed(); + for (int i = 0; i < size; i++) { + long updateCount = executeBatchElement(batchParameters.get(i), exception); + result[i] = updateCount <= Integer.MAX_VALUE ? (int) updateCount : SUCCESS_NO_INFO; + } + batchParameters = null; + exception = exception.getNextException(); + if (exception != null) { + throw new JdbcBatchUpdateException(exception, result); + } + return result; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Executes the batch. + * If one of the batched statements fails, this database will continue. + * + * @return the array of update counts + */ + @Override + public long[] executeLargeBatch() throws SQLException { + try { + debugCodeCall("executeLargeBatch"); + if (batchParameters == null) { + // Empty batch is allowed, see JDK-4639504 and other issues + batchParameters = new ArrayList<>(); + } + batchIdentities = new MergedResult(); + int size = batchParameters.size(); + long[] result = new long[size]; + SQLException exception = new SQLException(); + checkClosed(); + for (int i = 0; i < size; i++) { + result[i] = executeBatchElement(batchParameters.get(i), exception); + } + batchParameters = null; + exception = exception.getNextException(); + if (exception != null) { + throw new JdbcBatchUpdateException(exception, result); + } + return result; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private long executeBatchElement(Value[] set, SQLException exception) { + ArrayList parameters = command.getParameters(); + for (int i = 0, l = set.length; i < l; i++) { + parameters.get(i).setValue(set[i], false); + } + long updateCount; + try { + updateCount = executeUpdateInternal(); + // Cannot use own implementation, it returns batch identities + ResultSet rs = super.getGeneratedKeys(); + batchIdentities.add(((JdbcResultSet) rs).result); + } catch (Exception e) { + exception.setNextException(logAndConvert(e)); + updateCount = Statement.EXECUTE_FAILED; + } + return updateCount; + } + + @Override + public ResultSet getGeneratedKeys() throws SQLException { + if (batchIdentities != null) { + try { + int id = getNextId(TraceObject.RESULT_SET); + debugCodeAssign("ResultSet", TraceObject.RESULT_SET, id, "getGeneratedKeys()"); + checkClosed(); + generatedKeys = new JdbcResultSet(conn, this, null, batchIdentities.getResult(), id, true, false, + false); + } catch (Exception e) { + throw logAndConvert(e); + } + } + return super.getGeneratedKeys(); + } + + /** + * Adds the current settings to the batch. + */ + @Override + public void addBatch() throws SQLException { + try { + debugCodeCall("addBatch"); + checkClosed(); + ArrayList parameters = + command.getParameters(); + int size = parameters.size(); + Value[] set = new Value[size]; + for (int i = 0; i < size; i++) { + ParameterInterface param = parameters.get(i); + param.checkSet(); + Value value = param.getParamValue(); + set[i] = value; + } + if (batchParameters == null) { + batchParameters = Utils.newSmallArrayList(); + } + batchParameters.add(set); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Get the parameter meta data of this prepared statement. + * + * @return the meta data + */ + @Override + public ParameterMetaData getParameterMetaData() throws SQLException { + try { + int id = getNextId(TraceObject.PARAMETER_META_DATA); + debugCodeAssign("ParameterMetaData", TraceObject.PARAMETER_META_DATA, id, "getParameterMetaData()"); + checkClosed(); + return new JdbcParameterMetaData(session.getTrace(), this, command, id); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + // ============================================================= + + private void setParameter(int parameterIndex, Value value) { + checkClosed(); + parameterIndex--; + ArrayList parameters = command.getParameters(); + if (parameterIndex < 0 || parameterIndex >= parameters.size()) { + throw DbException.getInvalidValueException("parameterIndex", + parameterIndex + 1); + } + ParameterInterface param = parameters.get(parameterIndex); + // can only delete old temp files if they are not in the batch + param.setValue(value, batchParameters == null); + } + + /** + * [Not supported] Sets the value of a parameter as a row id. + */ + @Override + public void setRowId(int parameterIndex, RowId x) throws SQLException { + throw unsupported("rowId"); + } + + /** + * Sets the value of a parameter. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setNString(int parameterIndex, String x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setNString(" + parameterIndex + ", " + quote(x) + ')'); + } + setParameter(parameterIndex, x == null ? ValueNull.INSTANCE : ValueVarchar.get(x, conn)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter as a character stream. + * This method does not close the reader. + * The reader may be closed after executing the statement. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @param length the maximum number of characters + * @throws SQLException if this object is closed + */ + @Override + public void setNCharacterStream(int parameterIndex, Reader x, long length) + throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setNCharacterStream(" + parameterIndex + ", x, " + length + "L)"); + } + checkClosed(); + Value v = conn.createClob(x, length); + setParameter(parameterIndex, v); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter as a character stream. + * This method does not close the reader. + * The reader may be closed after executing the statement. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setNCharacterStream(int parameterIndex, Reader x) + throws SQLException { + setNCharacterStream(parameterIndex, x, -1); + } + + /** + * Sets the value of a parameter as a Clob. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setNClob(int parameterIndex, NClob x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setNClob(" + parameterIndex + ", x)"); + } + checkClosed(); + Value v; + if (x == null) { + v = ValueNull.INSTANCE; + } else { + v = conn.createClob(x.getCharacterStream(), -1); + } + setParameter(parameterIndex, v); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter as a Clob. + * This method does not close the reader. + * The reader may be closed after executing the statement. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setNClob(int parameterIndex, Reader x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setNClob(" + parameterIndex + ", x)"); + } + checkClosed(); + Value v = conn.createClob(x, -1); + setParameter(parameterIndex, v); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter as a Clob. This method does not close the + * reader. The reader may be closed after executing the statement. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @param length the maximum number of characters + * @throws SQLException if this object is closed + */ + @Override + public void setClob(int parameterIndex, Reader x, long length) + throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setClob(" + parameterIndex + ", x, " + length + "L)"); + } + checkClosed(); + Value v = conn.createClob(x, length); + setParameter(parameterIndex, v); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter as a Blob. + * This method does not close the stream. + * The stream may be closed after executing the statement. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @param length the maximum number of bytes + * @throws SQLException if this object is closed + */ + @Override + public void setBlob(int parameterIndex, InputStream x, long length) + throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setBlob(" + parameterIndex + ", x, " + length + "L)"); + } + checkClosed(); + Value v = conn.createBlob(x, length); + setParameter(parameterIndex, v); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter as a Clob. + * This method does not close the reader. + * The reader may be closed after executing the statement. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @param length the maximum number of characters + * @throws SQLException if this object is closed + */ + @Override + public void setNClob(int parameterIndex, Reader x, long length) + throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setNClob(" + parameterIndex + ", x, " + length + "L)"); + } + checkClosed(); + Value v = conn.createClob(x, length); + setParameter(parameterIndex, v); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the value of a parameter as a SQLXML. + * + * @param parameterIndex the parameter index (1, 2, ...) + * @param x the value + * @throws SQLException if this object is closed + */ + @Override + public void setSQLXML(int parameterIndex, SQLXML x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setSQLXML(" + parameterIndex + ", x)"); + } + checkClosed(); + Value v; + if (x == null) { + v = ValueNull.INSTANCE; + } else { + v = conn.createClob(x.getCharacterStream(), -1); + } + setParameter(parameterIndex, v); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * INTERNAL + */ + @Override + public String toString() { + return getTraceObjectName() + ": " + command; + } + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcResultSet.java b/h2/src/main/org/h2/jdbc/JdbcResultSet.java new file mode 100644 index 0000000..5984628 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcResultSet.java @@ -0,0 +1,4290 @@ +/* + * 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.jdbc; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.NClob; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLType; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Session; +import org.h2.engine.SysProperties; +import org.h2.message.DbException; +import org.h2.message.TraceObject; +import org.h2.result.ResultInterface; +import org.h2.result.UpdatableRow; +import org.h2.util.IOUtils; +import org.h2.util.LegacyDateTimeUtils; +import org.h2.util.StringUtils; +import org.h2.value.CompareMode; +import org.h2.value.DataType; +import org.h2.value.Value; +import org.h2.value.ValueBigint; +import org.h2.value.ValueBoolean; +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.ValueTinyint; +import org.h2.value.ValueToObjectConverter; +import org.h2.value.ValueVarbinary; +import org.h2.value.ValueVarchar; + +/** + * Represents a result set. + *

+ * Column labels are case-insensitive, quotes are not supported. The first + * column has the column index 1. + *

+ *

+ * Thread safety: the result set is not thread-safe and must not be used by + * multiple threads concurrently. + *

+ *

+ * Updatable result sets: Result sets are updatable when the result only + * contains columns from one table, and if it contains all columns of a unique + * index (primary key or other) of this table. Key columns may not contain NULL + * (because multiple rows with NULL could exist). In updatable result sets, own + * changes are visible, but not own inserts and deletes. + *

+ */ +public final class JdbcResultSet extends TraceObject implements ResultSet { + + private final boolean scrollable; + private final boolean updatable; + private final boolean triggerUpdatable; + ResultInterface result; + private JdbcConnection conn; + private JdbcStatement stat; + private int columnCount; + private boolean wasNull; + private Value[] insertRow; + private Value[] updateRow; + private HashMap columnLabelMap; + private HashMap patchedRows; + private JdbcPreparedStatement preparedStatement; + private final CommandInterface command; + + public JdbcResultSet(JdbcConnection conn, JdbcStatement stat, CommandInterface command, ResultInterface result, + int id, boolean scrollable, boolean updatable, boolean triggerUpdatable) { + setTrace(conn.getSession().getTrace(), TraceObject.RESULT_SET, id); + this.conn = conn; + this.stat = stat; + this.command = command; + this.result = result; + this.columnCount = result.getVisibleColumnCount(); + this.scrollable = scrollable; + this.updatable = updatable; + this.triggerUpdatable = triggerUpdatable; + } + + JdbcResultSet(JdbcConnection conn, JdbcPreparedStatement preparedStatement, CommandInterface command, + ResultInterface result, int id, boolean scrollable, boolean updatable, + HashMap columnLabelMap) { + this(conn, preparedStatement, command, result, id, scrollable, updatable, false); + this.columnLabelMap = columnLabelMap; + this.preparedStatement = preparedStatement; + } + + /** + * Moves the cursor to the next row of the result set. + * + * @return true if successful, false if there are no more rows + */ + @Override + public boolean next() throws SQLException { + try { + debugCodeCall("next"); + checkClosed(); + return nextRow(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the meta data of this result set. + * + * @return the meta data + */ + @Override + public ResultSetMetaData getMetaData() throws SQLException { + try { + int id = getNextId(TraceObject.RESULT_SET_META_DATA); + debugCodeAssign("ResultSetMetaData", TraceObject.RESULT_SET_META_DATA, id, "getMetaData()"); + checkClosed(); + String catalog = conn.getCatalog(); + return new JdbcResultSetMetaData(this, null, result, catalog, conn.getSession().getTrace(), id); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns whether the last column accessed was null. + * + * @return true if the last column accessed was null + */ + @Override + public boolean wasNull() throws SQLException { + try { + debugCodeCall("wasNull"); + checkClosed(); + return wasNull; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Searches for a specific column in the result set. A case-insensitive + * search is made. + * + * @param columnLabel the column label + * @return the column index (1,2,...) + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public int findColumn(String columnLabel) throws SQLException { + try { + debugCodeCall("findColumn", columnLabel); + return getColumnIndex(columnLabel); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Closes the result set. + */ + @Override + public void close() throws SQLException { + try { + debugCodeCall("close"); + closeInternal(false); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Close the result set. This method also closes the statement if required. + * @param fromStatement if true - close statement in the end + */ + void closeInternal(boolean fromStatement) { + if (result != null) { + try { + if (result.isLazy()) { + stat.onLazyResultSetClose(command, preparedStatement == null); + } + result.close(); + } finally { + JdbcStatement s = stat; + columnCount = 0; + result = null; + stat = null; + conn = null; + insertRow = null; + updateRow = null; + if (!fromStatement && s != null) { + s.closeIfCloseOnCompletion(); + } + } + } + } + + /** + * Returns the statement that created this object. + * + * @return the statement or prepared statement, or null if created by a + * DatabaseMetaData call. + */ + @Override + public Statement getStatement() throws SQLException { + try { + debugCodeCall("getStatement"); + checkClosed(); + return stat; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the first warning reported by calls on this object. + * + * @return null + */ + @Override + public SQLWarning getWarnings() throws SQLException { + try { + debugCodeCall("getWarnings"); + checkClosed(); + return null; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Clears all warnings. + */ + @Override + public void clearWarnings() throws SQLException { + try { + debugCodeCall("clearWarnings"); + checkClosed(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + // ============================================================= + + /** + * Returns the value of the specified column as a String. + * + * @param columnIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public String getString(int columnIndex) throws SQLException { + try { + debugCodeCall("getString", columnIndex); + return get(checkColumnIndex(columnIndex)).getString(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a String. + * + * @param columnLabel the column label + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public String getString(String columnLabel) throws SQLException { + try { + debugCodeCall("getString", columnLabel); + return get(getColumnIndex(columnLabel)).getString(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as an int. + * + * @param columnIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public int getInt(int columnIndex) throws SQLException { + try { + debugCodeCall("getInt", columnIndex); + return getIntInternal(checkColumnIndex(columnIndex)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as an int. + * + * @param columnLabel the column label + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public int getInt(String columnLabel) throws SQLException { + try { + debugCodeCall("getInt", columnLabel); + return getIntInternal(getColumnIndex(columnLabel)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private int getIntInternal(int columnIndex) { + Value v = getInternal(columnIndex); + int result; + if (v != ValueNull.INSTANCE) { + wasNull = false; + result = v.getInt(); + } else { + wasNull = true; + result = 0; + } + return result; + } + + /** + * Returns the value of the specified column as a BigDecimal. + * + * @param columnIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public BigDecimal getBigDecimal(int columnIndex) throws SQLException { + try { + debugCodeCall("getBigDecimal", columnIndex); + return get(checkColumnIndex(columnIndex)).getBigDecimal(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a java.sql.Date. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(columnIndex, LocalDate.class)} instead. + *

+ * + * @param columnIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + * @see #getObject(int, Class) + */ + @Override + public Date getDate(int columnIndex) throws SQLException { + try { + debugCodeCall("getDate", columnIndex); + return LegacyDateTimeUtils.toDate(conn, null, get(checkColumnIndex(columnIndex))); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a java.sql.Time. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(columnIndex, LocalTime.class)} instead. + *

+ * + * @param columnIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + * @see #getObject(int, Class) + */ + @Override + public Time getTime(int columnIndex) throws SQLException { + try { + debugCodeCall("getTime", columnIndex); + return LegacyDateTimeUtils.toTime(conn, null, get(checkColumnIndex(columnIndex))); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a java.sql.Timestamp. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(columnIndex, LocalDateTime.class)} instead. + *

+ * + * @param columnIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + * @see #getObject(int, Class) + */ + @Override + public Timestamp getTimestamp(int columnIndex) throws SQLException { + try { + debugCodeCall("getTimestamp", columnIndex); + return LegacyDateTimeUtils.toTimestamp(conn, null, get(checkColumnIndex(columnIndex))); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a BigDecimal. + * + * @param columnLabel the column label + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public BigDecimal getBigDecimal(String columnLabel) throws SQLException { + try { + debugCodeCall("getBigDecimal", columnLabel); + return get(getColumnIndex(columnLabel)).getBigDecimal(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a java.sql.Date. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(columnLabel, LocalDate.class)} instead. + *

+ * + * @param columnLabel the column label + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + * @see #getObject(String, Class) + */ + @Override + public Date getDate(String columnLabel) throws SQLException { + try { + debugCodeCall("getDate", columnLabel); + return LegacyDateTimeUtils.toDate(conn, null, get(getColumnIndex(columnLabel))); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a java.sql.Time. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(columnLabel, LocalTime.class)} instead. + *

+ * + * @param columnLabel the column label + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + * @see #getObject(String, Class) + */ + @Override + public Time getTime(String columnLabel) throws SQLException { + try { + debugCodeCall("getTime", columnLabel); + return LegacyDateTimeUtils.toTime(conn, null, get(getColumnIndex(columnLabel))); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a java.sql.Timestamp. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(columnLabel, LocalDateTime.class)} instead. + *

+ * + * @param columnLabel the column label + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + * @see #getObject(String, Class) + */ + @Override + public Timestamp getTimestamp(String columnLabel) throws SQLException { + try { + debugCodeCall("getTimestamp", columnLabel); + return LegacyDateTimeUtils.toTimestamp(conn, null, get(getColumnIndex(columnLabel))); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns a column value as a Java object. The data is + * de-serialized into a Java object (on the client side). + * + * @param columnIndex (1,2,...) + * @return the value or null + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public Object getObject(int columnIndex) throws SQLException { + try { + debugCodeCall("getObject", columnIndex); + return ValueToObjectConverter.valueToDefaultObject(get(checkColumnIndex(columnIndex)), conn, true); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns a column value as a Java object. The data is + * de-serialized into a Java object (on the client side). + * + * @param columnLabel the column label + * @return the value or null + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public Object getObject(String columnLabel) throws SQLException { + try { + debugCodeCall("getObject", columnLabel); + return ValueToObjectConverter.valueToDefaultObject(get(getColumnIndex(columnLabel)), conn, true); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a boolean. + * + * @param columnIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public boolean getBoolean(int columnIndex) throws SQLException { + try { + debugCodeCall("getBoolean", columnIndex); + return getBooleanInternal(checkColumnIndex(columnIndex)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a boolean. + * + * @param columnLabel the column label + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public boolean getBoolean(String columnLabel) throws SQLException { + try { + debugCodeCall("getBoolean", columnLabel); + return getBooleanInternal(getColumnIndex(columnLabel)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private boolean getBooleanInternal(int columnIndex) { + Value v = getInternal(columnIndex); + boolean result; + if (v != ValueNull.INSTANCE) { + wasNull = false; + result = v.getBoolean(); + } else { + wasNull = true; + result = false; + } + return result; + } + + /** + * Returns the value of the specified column as a byte. + * + * @param columnIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public byte getByte(int columnIndex) throws SQLException { + try { + debugCodeCall("getByte", columnIndex); + return getByteInternal(checkColumnIndex(columnIndex)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a byte. + * + * @param columnLabel the column label + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public byte getByte(String columnLabel) throws SQLException { + try { + debugCodeCall("getByte", columnLabel); + return getByteInternal(getColumnIndex(columnLabel)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private byte getByteInternal(int columnIndex) { + Value v = getInternal(columnIndex); + byte result; + if (v != ValueNull.INSTANCE) { + wasNull = false; + result = v.getByte(); + } else { + wasNull = true; + result = 0; + } + return result; + } + + /** + * Returns the value of the specified column as a short. + * + * @param columnIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public short getShort(int columnIndex) throws SQLException { + try { + debugCodeCall("getShort", columnIndex); + return getShortInternal(checkColumnIndex(columnIndex)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a short. + * + * @param columnLabel the column label + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public short getShort(String columnLabel) throws SQLException { + try { + debugCodeCall("getShort", columnLabel); + return getShortInternal(getColumnIndex(columnLabel)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private short getShortInternal(int columnIndex) { + Value v = getInternal(columnIndex); + short result; + if (v != ValueNull.INSTANCE) { + wasNull = false; + result = v.getShort(); + } else { + wasNull = true; + result = 0; + } + return result; + } + + /** + * Returns the value of the specified column as a long. + * + * @param columnIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public long getLong(int columnIndex) throws SQLException { + try { + debugCodeCall("getLong", columnIndex); + return getLongInternal(checkColumnIndex(columnIndex)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a long. + * + * @param columnLabel the column label + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public long getLong(String columnLabel) throws SQLException { + try { + debugCodeCall("getLong", columnLabel); + return getLongInternal(getColumnIndex(columnLabel)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private long getLongInternal(int columnIndex) { + Value v = getInternal(columnIndex); + long result; + if (v != ValueNull.INSTANCE) { + wasNull = false; + result = v.getLong(); + } else { + wasNull = true; + result = 0L; + } + return result; + } + + /** + * Returns the value of the specified column as a float. + * + * @param columnIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public float getFloat(int columnIndex) throws SQLException { + try { + debugCodeCall("getFloat", columnIndex); + return getFloatInternal(checkColumnIndex(columnIndex)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a float. + * + * @param columnLabel the column label + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public float getFloat(String columnLabel) throws SQLException { + try { + debugCodeCall("getFloat", columnLabel); + return getFloatInternal(getColumnIndex(columnLabel)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private float getFloatInternal(int columnIndex) { + Value v = getInternal(columnIndex); + float result; + if (v != ValueNull.INSTANCE) { + wasNull = false; + result = v.getFloat(); + } else { + wasNull = true; + result = 0f; + } + return result; + } + + /** + * Returns the value of the specified column as a double. + * + * @param columnIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public double getDouble(int columnIndex) throws SQLException { + try { + debugCodeCall("getDouble", columnIndex); + return getDoubleInternal(checkColumnIndex(columnIndex)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a double. + * + * @param columnLabel the column label + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public double getDouble(String columnLabel) throws SQLException { + try { + debugCodeCall("getDouble", columnLabel); + return getDoubleInternal(getColumnIndex(columnLabel)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private double getDoubleInternal(int columnIndex) { + Value v = getInternal(columnIndex); + double result; + if (v != ValueNull.INSTANCE) { + wasNull = false; + result = v.getDouble(); + } else { + wasNull = true; + result = 0d; + } + return result; + } + + /** + * Returns the value of the specified column as a BigDecimal. + * + * @deprecated use {@link #getBigDecimal(String)} + * + * @param columnLabel the column label + * @param scale the scale of the returned value + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Deprecated + @Override + public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getBigDecimal(" + quote(columnLabel) + ", " + scale + ')'); + } + if (scale < 0) { + throw DbException.getInvalidValueException("scale", scale); + } + BigDecimal bd = get(getColumnIndex(columnLabel)).getBigDecimal(); + return bd == null ? null : ValueNumeric.setScale(bd, scale); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a BigDecimal. + * + * @deprecated use {@link #getBigDecimal(int)} + * + * @param columnIndex (1,2,...) + * @param scale the scale of the returned value + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Deprecated + @Override + public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getBigDecimal(" + columnIndex + ", " + scale + ')'); + } + if (scale < 0) { + throw DbException.getInvalidValueException("scale", scale); + } + BigDecimal bd = get(checkColumnIndex(columnIndex)).getBigDecimal(); + return bd == null ? null : ValueNumeric.setScale(bd, scale); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * [Not supported] + * @deprecated since JDBC 2.0, use getCharacterStream + */ + @Deprecated + @Override + public InputStream getUnicodeStream(int columnIndex) throws SQLException { + throw unsupported("unicodeStream"); + } + + /** + * [Not supported] + * @deprecated since JDBC 2.0, use setCharacterStream + */ + @Deprecated + @Override + public InputStream getUnicodeStream(String columnLabel) throws SQLException { + throw unsupported("unicodeStream"); + } + + /** + * [Not supported] Gets a column as a object using the specified type + * mapping. + */ + @Override + public Object getObject(int columnIndex, Map> map) + throws SQLException { + throw unsupported("map"); + } + + /** + * [Not supported] Gets a column as a object using the specified type + * mapping. + */ + @Override + public Object getObject(String columnLabel, Map> map) + throws SQLException { + throw unsupported("map"); + } + + /** + * [Not supported] Gets a column as a reference. + */ + @Override + public Ref getRef(int columnIndex) throws SQLException { + throw unsupported("ref"); + } + + /** + * [Not supported] Gets a column as a reference. + */ + @Override + public Ref getRef(String columnLabel) throws SQLException { + throw unsupported("ref"); + } + + /** + * Returns the value of the specified column as a java.sql.Date using a + * specified time zone. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(columnIndex, LocalDate.class)} instead. + *

+ * + * @param columnIndex (1,2,...) + * @param calendar the calendar + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + * @see #getObject(int, Class) + */ + @Override + public Date getDate(int columnIndex, Calendar calendar) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getDate(" + columnIndex + ", calendar)"); + } + return LegacyDateTimeUtils.toDate(conn, calendar != null ? calendar.getTimeZone() : null, + get(checkColumnIndex(columnIndex))); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a java.sql.Date using a + * specified time zone. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(columnLabel, LocalDate.class)} instead. + *

+ * + * @param columnLabel the column label + * @param calendar the calendar + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + * @see #getObject(String, Class) + */ + @Override + public Date getDate(String columnLabel, Calendar calendar) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getDate(" + quote(columnLabel) + ", calendar)"); + } + return LegacyDateTimeUtils.toDate(conn, calendar != null ? calendar.getTimeZone() : null, + get(getColumnIndex(columnLabel))); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a java.sql.Time using a + * specified time zone. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(columnIndex, LocalTime.class)} instead. + *

+ * + * @param columnIndex (1,2,...) + * @param calendar the calendar + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + * @see #getObject(int, Class) + */ + @Override + public Time getTime(int columnIndex, Calendar calendar) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getTime(" + columnIndex + ", calendar)"); + } + return LegacyDateTimeUtils.toTime(conn, calendar != null ? calendar.getTimeZone() : null, + get(checkColumnIndex(columnIndex))); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a java.sql.Time using a + * specified time zone. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(columnLabel, LocalTime.class)} instead. + *

+ * + * @param columnLabel the column label + * @param calendar the calendar + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + * @see #getObject(String, Class) + */ + @Override + public Time getTime(String columnLabel, Calendar calendar) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getTime(" + quote(columnLabel) + ", calendar)"); + } + return LegacyDateTimeUtils.toTime(conn, calendar != null ? calendar.getTimeZone() : null, + get(getColumnIndex(columnLabel))); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a java.sql.Timestamp using a + * specified time zone. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(columnIndex, LocalDateTime.class)} instead. + *

+ * + * @param columnIndex (1,2,...) + * @param calendar the calendar + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + * @see #getObject(int, Class) + */ + @Override + public Timestamp getTimestamp(int columnIndex, Calendar calendar) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getTimestamp(" + columnIndex + ", calendar)"); + } + return LegacyDateTimeUtils.toTimestamp(conn, calendar != null ? calendar.getTimeZone() : null, + get(checkColumnIndex(columnIndex))); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a java.sql.Timestamp. + *

+ * Usage of this method is discouraged. Use + * {@code getObject(columnLabel, LocalDateTime.class)} instead. + *

+ * + * @param columnLabel the column label + * @param calendar the calendar + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + * @see #getObject(String, Class) + */ + @Override + public Timestamp getTimestamp(String columnLabel, Calendar calendar) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("getTimestamp(" + quote(columnLabel) + ", calendar)"); + } + return LegacyDateTimeUtils.toTimestamp(conn, calendar != null ? calendar.getTimeZone() : null, + get(getColumnIndex(columnLabel))); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a Blob. + * + * @param columnIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public Blob getBlob(int columnIndex) throws SQLException { + try { + int id = getNextId(TraceObject.BLOB); + if (isDebugEnabled()) { + debugCodeAssign("Blob", TraceObject.BLOB, id, "getBlob(" + columnIndex + ')'); + } + return getBlob(id, checkColumnIndex(columnIndex)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a Blob. + * + * @param columnLabel the column label + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public Blob getBlob(String columnLabel) throws SQLException { + try { + int id = getNextId(TraceObject.BLOB); + if (isDebugEnabled()) { + debugCodeAssign("Blob", TraceObject.BLOB, id, "getBlob(" + quote(columnLabel) + ')'); + } + return getBlob(id, getColumnIndex(columnLabel)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private JdbcBlob getBlob(int id, int columnIndex) { + Value v = getInternal(columnIndex); + JdbcBlob result; + if (v != ValueNull.INSTANCE) { + wasNull = false; + result = new JdbcBlob(conn, v, JdbcLob.State.WITH_VALUE, id); + } else { + wasNull = true; + result = null; + } + return result; + } + + /** + * Returns the value of the specified column as a byte array. + * + * @param columnIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public byte[] getBytes(int columnIndex) throws SQLException { + try { + debugCodeCall("getBytes", columnIndex); + return get(checkColumnIndex(columnIndex)).getBytes(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a byte array. + * + * @param columnLabel the column label + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public byte[] getBytes(String columnLabel) throws SQLException { + try { + debugCodeCall("getBytes", columnLabel); + return get(getColumnIndex(columnLabel)).getBytes(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as an input stream. + * + * @param columnIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public InputStream getBinaryStream(int columnIndex) throws SQLException { + try { + debugCodeCall("getBinaryStream", columnIndex); + return get(checkColumnIndex(columnIndex)).getInputStream(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as an input stream. + * + * @param columnLabel the column label + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public InputStream getBinaryStream(String columnLabel) throws SQLException { + try { + debugCodeCall("getBinaryStream", columnLabel); + return get(getColumnIndex(columnLabel)).getInputStream(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + + /** + * Returns the value of the specified column as a Clob. + * + * @param columnIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public Clob getClob(int columnIndex) throws SQLException { + try { + int id = getNextId(TraceObject.CLOB); + if (isDebugEnabled()) { + debugCodeAssign("Clob", TraceObject.CLOB, id, "getClob(" + columnIndex + ')'); + } + return getClob(id, checkColumnIndex(columnIndex)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a Clob. + * + * @param columnLabel the column label + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public Clob getClob(String columnLabel) throws SQLException { + try { + int id = getNextId(TraceObject.CLOB); + if (isDebugEnabled()) { + debugCodeAssign("Clob", TraceObject.CLOB, id, "getClob(" + quote(columnLabel) + ')'); + } + return getClob(id, getColumnIndex(columnLabel)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as an Array. + * + * @param columnIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public Array getArray(int columnIndex) throws SQLException { + try { + int id = getNextId(TraceObject.ARRAY); + if (isDebugEnabled()) { + debugCodeAssign("Array", TraceObject.ARRAY, id, "getArray(" + columnIndex + ')'); + } + return getArray(id, checkColumnIndex(columnIndex)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as an Array. + * + * @param columnLabel the column label + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public Array getArray(String columnLabel) throws SQLException { + try { + int id = getNextId(TraceObject.ARRAY); + if (isDebugEnabled()) { + debugCodeAssign("Array", TraceObject.ARRAY, id, "getArray(" + quote(columnLabel) + ')'); + } + return getArray(id, getColumnIndex(columnLabel)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private Array getArray(int id, int columnIndex) { + Value v = getInternal(columnIndex); + JdbcArray result; + if (v != ValueNull.INSTANCE) { + wasNull = false; + result = new JdbcArray(conn, v, id); + } else { + wasNull = true; + result = null; + } + return result; + } + + /** + * Returns the value of the specified column as an input stream. + * + * @param columnIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public InputStream getAsciiStream(int columnIndex) throws SQLException { + try { + debugCodeCall("getAsciiStream", columnIndex); + String s = get(checkColumnIndex(columnIndex)).getString(); + return s == null ? null : IOUtils.getInputStreamFromString(s); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as an input stream. + * + * @param columnLabel the column label + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public InputStream getAsciiStream(String columnLabel) throws SQLException { + try { + debugCodeCall("getAsciiStream", columnLabel); + String s = get(getColumnIndex(columnLabel)).getString(); + return IOUtils.getInputStreamFromString(s); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a reader. + * + * @param columnIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public Reader getCharacterStream(int columnIndex) throws SQLException { + try { + debugCodeCall("getCharacterStream", columnIndex); + return get(checkColumnIndex(columnIndex)).getReader(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a reader. + * + * @param columnLabel the column label + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public Reader getCharacterStream(String columnLabel) throws SQLException { + try { + debugCodeCall("getCharacterStream", columnLabel); + return get(getColumnIndex(columnLabel)).getReader(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * [Not supported] + */ + @Override + public URL getURL(int columnIndex) throws SQLException { + throw unsupported("url"); + } + + /** + * [Not supported] + */ + @Override + public URL getURL(String columnLabel) throws SQLException { + throw unsupported("url"); + } + + // ============================================================= + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateNull(int columnIndex) throws SQLException { + try { + debugCodeCall("updateNull", columnIndex); + update(checkColumnIndex(columnIndex), ValueNull.INSTANCE); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateNull(String columnLabel) throws SQLException { + try { + debugCodeCall("updateNull", columnLabel); + update(getColumnIndex(columnLabel), ValueNull.INSTANCE); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateBoolean(int columnIndex, boolean x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateBoolean(" + columnIndex + ", " + x + ')'); + } + update(checkColumnIndex(columnIndex), ValueBoolean.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if result set is closed or not updatable + */ + @Override + public void updateBoolean(String columnLabel, boolean x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateBoolean(" + quote(columnLabel) + ", " + x + ')'); + } + update(getColumnIndex(columnLabel), ValueBoolean.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateByte(int columnIndex, byte x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateByte(" + columnIndex + ", " + x + ')'); + } + update(checkColumnIndex(columnIndex), ValueTinyint.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateByte(String columnLabel, byte x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateByte(" + quote(columnLabel) + ", " + x + ')'); + } + update(getColumnIndex(columnLabel), ValueTinyint.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateBytes(int columnIndex, byte[] x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateBytes(" + columnIndex + ", x)"); + } + update(checkColumnIndex(columnIndex), x == null ? ValueNull.INSTANCE : ValueVarbinary.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateBytes(String columnLabel, byte[] x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateBytes(" + quote(columnLabel) + ", x)"); + } + update(getColumnIndex(columnLabel), x == null ? ValueNull.INSTANCE : ValueVarbinary.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateShort(int columnIndex, short x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateShort(" + columnIndex + ", (short) " + x + ')'); + } + update(checkColumnIndex(columnIndex), ValueSmallint.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateShort(String columnLabel, short x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateShort(" + quote(columnLabel) + ", (short) " + x + ')'); + } + update(getColumnIndex(columnLabel), ValueSmallint.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateInt(int columnIndex, int x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateInt(" + columnIndex + ", " + x + ')'); + } + update(checkColumnIndex(columnIndex), ValueInteger.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateInt(String columnLabel, int x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateInt(" + quote(columnLabel) + ", " + x + ')'); + } + update(getColumnIndex(columnLabel), ValueInteger.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateLong(int columnIndex, long x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateLong(" + columnIndex + ", " + x + "L)"); + } + update(checkColumnIndex(columnIndex), ValueBigint.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateLong(String columnLabel, long x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateLong(" + quote(columnLabel) + ", " + x + "L)"); + } + update(getColumnIndex(columnLabel), ValueBigint.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateFloat(int columnIndex, float x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateFloat(" + columnIndex + ", " + x + "f)"); + } + update(checkColumnIndex(columnIndex), ValueReal.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateFloat(String columnLabel, float x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateFloat(" + quote(columnLabel) + ", " + x + "f)"); + } + update(getColumnIndex(columnLabel), ValueReal.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateDouble(int columnIndex, double x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateDouble(" + columnIndex + ", " + x + "d)"); + } + update(checkColumnIndex(columnIndex), ValueDouble.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateDouble(String columnLabel, double x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateDouble(" + quote(columnLabel) + ", " + x + "d)"); + } + update(getColumnIndex(columnLabel), ValueDouble.get(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateBigDecimal(" + columnIndex + ", " + quoteBigDecimal(x) + ')'); + } + update(checkColumnIndex(columnIndex), x == null ? ValueNull.INSTANCE : ValueNumeric.getAnyScale(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateBigDecimal(String columnLabel, BigDecimal x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateBigDecimal(" + quote(columnLabel) + ", " + quoteBigDecimal(x) + ')'); + } + update(getColumnIndex(columnLabel), x == null ? ValueNull.INSTANCE : ValueNumeric.getAnyScale(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateString(int columnIndex, String x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateString(" + columnIndex + ", " + quote(x) + ')'); + } + update(checkColumnIndex(columnIndex), x == null ? ValueNull.INSTANCE : ValueVarchar.get(x, conn)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateString(String columnLabel, String x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateString(" + quote(columnLabel) + ", " + quote(x) + ')'); + } + update(getColumnIndex(columnLabel), x == null ? ValueNull.INSTANCE : ValueVarchar.get(x, conn)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + *

+ * Usage of this method is discouraged. Use + * {@code updateObject(columnIndex, value)} with {@link java.time.LocalDate} + * parameter instead. + *

+ * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + * @see #updateObject(int, Object) + */ + @Override + public void updateDate(int columnIndex, Date x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateDate(" + columnIndex + ", " + quoteDate(x) + ')'); + } + update(checkColumnIndex(columnIndex), + x == null ? ValueNull.INSTANCE : LegacyDateTimeUtils.fromDate(conn, null, x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + *

+ * Usage of this method is discouraged. Use + * {@code updateObject(columnLabel, value)} with {@link java.time.LocalDate} + * parameter instead. + *

+ * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + * @see #updateObject(String, Object) + */ + @Override + public void updateDate(String columnLabel, Date x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateDate(" + quote(columnLabel) + ", " + quoteDate(x) + ')'); + } + update(getColumnIndex(columnLabel), + x == null ? ValueNull.INSTANCE : LegacyDateTimeUtils.fromDate(conn, null, x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + *

+ * Usage of this method is discouraged. Use + * {@code updateObject(columnIndex, value)} with {@link java.time.LocalTime} + * parameter instead. + *

+ * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + * @see #updateObject(int, Object) + */ + @Override + public void updateTime(int columnIndex, Time x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateTime(" + columnIndex + ", " + quoteTime(x) + ')'); + } + update(checkColumnIndex(columnIndex), + x == null ? ValueNull.INSTANCE : LegacyDateTimeUtils.fromTime(conn, null, x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + *

+ * Usage of this method is discouraged. Use + * {@code updateObject(columnLabel, value)} with {@link java.time.LocalTime} + * parameter instead. + *

+ * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + * @see #updateObject(String, Object) + */ + @Override + public void updateTime(String columnLabel, Time x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateTime(" + quote(columnLabel) + ", " + quoteTime(x) + ')'); + } + update(getColumnIndex(columnLabel), + x == null ? ValueNull.INSTANCE : LegacyDateTimeUtils.fromTime(conn, null, x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + *

+ * Usage of this method is discouraged. Use + * {@code updateObject(columnIndex, value)} with + * {@link java.time.LocalDateTime} parameter instead. + *

+ * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + * @see #updateObject(int, Object) + */ + @Override + public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateTimestamp(" + columnIndex + ", " + quoteTimestamp(x) + ')'); + } + update(checkColumnIndex(columnIndex), + x == null ? ValueNull.INSTANCE : LegacyDateTimeUtils.fromTimestamp(conn, null, x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + *

+ * Usage of this method is discouraged. Use + * {@code updateObject(columnLabel, value)} with + * {@link java.time.LocalDateTime} parameter instead. + *

+ * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + * @see #updateObject(String, Object) + */ + @Override + public void updateTimestamp(String columnLabel, Timestamp x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateTimestamp(" + quote(columnLabel) + ", " + quoteTimestamp(x) + ')'); + } + update(getColumnIndex(columnLabel), + x == null ? ValueNull.INSTANCE : LegacyDateTimeUtils.fromTimestamp(conn, null, x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @param length the number of characters + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateAsciiStream(" + columnIndex + ", x, " + length + ')'); + } + updateAscii(checkColumnIndex(columnIndex), x, length); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateAsciiStream(" + columnIndex + ", x)"); + } + updateAscii(checkColumnIndex(columnIndex), x, -1L); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @param length the number of characters + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateAsciiStream(" + columnIndex + ", x, " + length + "L)"); + } + updateAscii(checkColumnIndex(columnIndex), x, length); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @param length the number of characters + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateAsciiStream(String columnLabel, InputStream x, int length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateAsciiStream(" + quote(columnLabel) + ", x, " + length + ')'); + } + updateAscii(getColumnIndex(columnLabel), x, length); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed + */ + @Override + public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateAsciiStream(" + quote(columnLabel) + ", x)"); + } + updateAscii(getColumnIndex(columnLabel), x, -1L); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @param length the number of characters + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateAsciiStream(" + quote(columnLabel) + ", x, " + length + "L)"); + } + updateAscii(getColumnIndex(columnLabel), x, length); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private void updateAscii(int columnIndex, InputStream x, long length) { + update(columnIndex, conn.createClob(IOUtils.getAsciiReader(x), length)); + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @param length the number of characters + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateBinaryStream(" + columnIndex + ", x, " + length + ')'); + } + updateBlobImpl(checkColumnIndex(columnIndex), x, length); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateBinaryStream(" + columnIndex + ", x)"); + } + updateBlobImpl(checkColumnIndex(columnIndex), x, -1L); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @param length the number of characters + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateBinaryStream(" + columnIndex + ", x, " + length + "L)"); + } + updateBlobImpl(checkColumnIndex(columnIndex), x, length); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateBinaryStream(" + quote(columnLabel) + ", x)"); + } + updateBlobImpl(getColumnIndex(columnLabel), x, -1L); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @param length the number of characters + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateBinaryStream(String columnLabel, InputStream x, int length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateBinaryStream(" + quote(columnLabel) + ", x, " + length + ')'); + } + updateBlobImpl(getColumnIndex(columnLabel), x, length); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @param length the number of characters + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateBinaryStream(" + quote(columnLabel) + ", x, " + length + "L)"); + } + updateBlobImpl(getColumnIndex(columnLabel), x, length); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @param length the number of characters + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateCharacterStream(" + columnIndex + ", x, " + length + "L)"); + } + updateClobImpl(checkColumnIndex(columnIndex), x, length); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @param length the number of characters + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateCharacterStream(" + columnIndex + ", x, " + length + ')'); + } + updateClobImpl(checkColumnIndex(columnIndex), x, length); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateCharacterStream(int columnIndex, Reader x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateCharacterStream(" + columnIndex + ", x)"); + } + updateClobImpl(checkColumnIndex(columnIndex), x, -1L); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @param length the number of characters + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateCharacterStream(String columnLabel, Reader x, int length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateCharacterStream(" + quote(columnLabel) + ", x, " + length + ')'); + } + updateClobImpl(getColumnIndex(columnLabel), x, length); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateCharacterStream(String columnLabel, Reader x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateCharacterStream(" + quote(columnLabel) + ", x)"); + } + updateClobImpl(getColumnIndex(columnLabel), x, -1L); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @param length the number of characters + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateCharacterStream(String columnLabel, Reader x, long length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateCharacterStream(" + quote(columnLabel) + ", x, " + length + "L)"); + } + updateClobImpl(getColumnIndex(columnLabel), x, length); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @param scale is ignored + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateObject(int columnIndex, Object x, int scale) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateObject(" + columnIndex + ", x, " + scale + ')'); + } + update(checkColumnIndex(columnIndex), convertToUnknownValue(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @param scale is ignored + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateObject(String columnLabel, Object x, int scale) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateObject(" + quote(columnLabel) + ", x, " + scale + ')'); + } + update(getColumnIndex(columnLabel), convertToUnknownValue(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateObject(int columnIndex, Object x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateObject(" + columnIndex + ", x)"); + } + update(checkColumnIndex(columnIndex), convertToUnknownValue(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateObject(String columnLabel, Object x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateObject(" + quote(columnLabel) + ", x)"); + } + update(getColumnIndex(columnLabel), convertToUnknownValue(x)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @param targetSqlType the SQL type + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateObject(int columnIndex, Object x, SQLType targetSqlType) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateObject(" + columnIndex + ", x, " + DataType.sqlTypeToString(targetSqlType) + ')'); + } + update(checkColumnIndex(columnIndex), convertToValue(x, targetSqlType)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @param targetSqlType the SQL type + * @param scaleOrLength is ignored + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateObject(int columnIndex, Object x, SQLType targetSqlType, int scaleOrLength) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateObject(" + columnIndex + ", x, " + DataType.sqlTypeToString(targetSqlType) + ", " + + scaleOrLength + ')'); + } + update(checkColumnIndex(columnIndex), convertToValue(x, targetSqlType)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @param targetSqlType the SQL type + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateObject(String columnLabel, Object x, SQLType targetSqlType) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateObject(" + quote(columnLabel) + ", x, " + DataType.sqlTypeToString(targetSqlType) + + ')'); + } + update(getColumnIndex(columnLabel), convertToValue(x, targetSqlType)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @param targetSqlType the SQL type + * @param scaleOrLength is ignored + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateObject(String columnLabel, Object x, SQLType targetSqlType, int scaleOrLength) + throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateObject(" + quote(columnLabel) + ", x, " + DataType.sqlTypeToString(targetSqlType) + + ", " + scaleOrLength + ')'); + } + update(getColumnIndex(columnLabel), convertToValue(x, targetSqlType)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * [Not supported] + */ + @Override + public void updateRef(int columnIndex, Ref x) throws SQLException { + throw unsupported("ref"); + } + + /** + * [Not supported] + */ + @Override + public void updateRef(String columnLabel, Ref x) throws SQLException { + throw unsupported("ref"); + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateBlob(int columnIndex, InputStream x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateBlob(" + columnIndex + ", (InputStream) x)"); + } + updateBlobImpl(checkColumnIndex(columnIndex), x, -1L); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @param length the length + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateBlob(int columnIndex, InputStream x, long length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateBlob(" + columnIndex + ", (InputStream) x, " + length + "L)"); + } + updateBlobImpl(checkColumnIndex(columnIndex), x, length); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateBlob(int columnIndex, Blob x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateBlob(" + columnIndex + ", (Blob) x)"); + } + updateBlobImpl(checkColumnIndex(columnIndex), x, -1L); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateBlob(String columnLabel, Blob x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateBlob(" + quote(columnLabel) + ", (Blob) x)"); + } + updateBlobImpl(getColumnIndex(columnLabel), x, -1L); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private void updateBlobImpl(int columnIndex, Blob x, long length) throws SQLException { + update(columnIndex, x == null ? ValueNull.INSTANCE : conn.createBlob(x.getBinaryStream(), length)); + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateBlob(String columnLabel, InputStream x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateBlob(" + quote(columnLabel) + ", (InputStream) x)"); + } + updateBlobImpl(getColumnIndex(columnLabel), x, -1L); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @param length the length + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateBlob(String columnLabel, InputStream x, long length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateBlob(" + quote(columnLabel) + ", (InputStream) x, " + length + "L)"); + } + updateBlobImpl(getColumnIndex(columnLabel), x, length); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private void updateBlobImpl(int columnIndex, InputStream x, long length) { + update(columnIndex, conn.createBlob(x, length)); + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateClob(int columnIndex, Clob x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateClob(" + columnIndex + ", (Clob) x)"); + } + updateClobImpl(checkColumnIndex(columnIndex), x); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateClob(int columnIndex, Reader x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateClob(" + columnIndex + ", (Reader) x)"); + } + updateClobImpl(checkColumnIndex(columnIndex), x, -1L); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @param length the length + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateClob(int columnIndex, Reader x, long length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateClob(" + columnIndex + ", (Reader) x, " + length + "L)"); + } + updateClobImpl(checkColumnIndex(columnIndex), x, length); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateClob(String columnLabel, Clob x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateClob(" + quote(columnLabel) + ", (Clob) x)"); + } + updateClobImpl(getColumnIndex(columnLabel), x); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateClob(String columnLabel, Reader x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateClob(" + quote(columnLabel) + ", (Reader) x)"); + } + updateClobImpl(getColumnIndex(columnLabel), x, -1L); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @param length the length + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateClob(String columnLabel, Reader x, long length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateClob(" + quote(columnLabel) + ", (Reader) x, " + length + "L)"); + } + updateClobImpl(getColumnIndex(columnLabel), x, length); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateArray(int columnIndex, Array x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateArray(" + columnIndex + ", x)"); + } + updateArrayImpl(checkColumnIndex(columnIndex), x); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateArray(String columnLabel, Array x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateArray(" + quote(columnLabel) + ", x)"); + } + updateArrayImpl(getColumnIndex(columnLabel), x); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private void updateArrayImpl(int columnIndex, Array x) throws SQLException { + update(columnIndex, x == null ? ValueNull.INSTANCE + : ValueToObjectConverter.objectToValue(stat.session, x.getArray(), Value.ARRAY)); + } + + /** + * [Not supported] Gets the cursor name if it was defined. This feature is + * superseded by updateX methods. This method throws a SQLException because + * cursor names are not supported. + */ + @Override + public String getCursorName() throws SQLException { + throw unsupported("cursorName"); + } + + /** + * Gets the current row number. The first row is row 1, the second 2 and so + * on. This method returns 0 before the first and after the last row. + * + * @return the row number + */ + @Override + public int getRow() throws SQLException { + try { + debugCodeCall("getRow"); + checkClosed(); + if (result.isAfterLast()) { + return 0; + } + long rowNumber = result.getRowId() + 1; + return rowNumber <= Integer.MAX_VALUE ? (int) rowNumber : Statement.SUCCESS_NO_INFO; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the result set concurrency. Result sets are only updatable if the + * statement was created with updatable concurrency, and if the result set + * contains all columns of the primary key or of a unique index of a table. + * + * @return ResultSet.CONCUR_UPDATABLE if the result set is updatable, or + * ResultSet.CONCUR_READ_ONLY otherwise + */ + @Override + public int getConcurrency() throws SQLException { + try { + debugCodeCall("getConcurrency"); + checkClosed(); + if (!updatable) { + return ResultSet.CONCUR_READ_ONLY; + } + UpdatableRow row = new UpdatableRow(conn, result); + return row.isUpdatable() ? ResultSet.CONCUR_UPDATABLE + : ResultSet.CONCUR_READ_ONLY; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the fetch direction. + * + * @return the direction: FETCH_FORWARD + */ + @Override + public int getFetchDirection() throws SQLException { + try { + debugCodeCall("getFetchDirection"); + checkClosed(); + return ResultSet.FETCH_FORWARD; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the number of rows suggested to read in one step. + * + * @return the current fetch size + */ + @Override + public int getFetchSize() throws SQLException { + try { + debugCodeCall("getFetchSize"); + checkClosed(); + return result.getFetchSize(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the number of rows suggested to read in one step. This value cannot + * be higher than the maximum rows (setMaxRows) set by the statement or + * prepared statement, otherwise an exception is throws. Setting the value + * to 0 will set the default value. The default value can be changed using + * the system property h2.serverResultSetFetchSize. + * + * @param rows the number of rows + */ + @Override + public void setFetchSize(int rows) throws SQLException { + try { + debugCodeCall("setFetchSize", rows); + checkClosed(); + if (rows < 0) { + throw DbException.getInvalidValueException("rows", rows); + } else if (rows > 0) { + if (stat != null) { + int maxRows = stat.getMaxRows(); + if (maxRows > 0 && rows > maxRows) { + throw DbException.getInvalidValueException("rows", rows); + } + } + } else { + rows = SysProperties.SERVER_RESULT_SET_FETCH_SIZE; + } + result.setFetchSize(rows); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * [Not supported] + * Sets (changes) the fetch direction for this result set. This method + * should only be called for scrollable result sets, otherwise it will throw + * an exception (no matter what direction is used). + * + * @param direction the new fetch direction + * @throws SQLException Unsupported Feature if the method is called for a + * forward-only result set + */ + @Override + public void setFetchDirection(int direction) throws SQLException { + debugCodeCall("setFetchDirection", direction); + // ignore FETCH_FORWARD, that's the default value, which we do support + if (direction != ResultSet.FETCH_FORWARD) { + throw unsupported("setFetchDirection"); + } + } + + /** + * Get the result set type. + * + * @return the result set type (TYPE_FORWARD_ONLY, TYPE_SCROLL_INSENSITIVE + * or TYPE_SCROLL_SENSITIVE) + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public int getType() throws SQLException { + try { + debugCodeCall("getType"); + checkClosed(); + return stat == null ? ResultSet.TYPE_FORWARD_ONLY : stat.resultSetType; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if the current position is before the first row, that means next() + * was not called yet, and there is at least one row. + * + * @return if there are results and the current position is before the first + * row + * @throws SQLException if the result set is closed + */ + @Override + public boolean isBeforeFirst() throws SQLException { + try { + debugCodeCall("isBeforeFirst"); + checkClosed(); + return result.getRowId() < 0 && result.hasNext(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if the current position is after the last row, that means next() + * was called and returned false, and there was at least one row. + * + * @return if there are results and the current position is after the last + * row + * @throws SQLException if the result set is closed + */ + @Override + public boolean isAfterLast() throws SQLException { + try { + debugCodeCall("isAfterLast"); + checkClosed(); + return result.getRowId() > 0 && result.isAfterLast(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if the current position is row 1, that means next() was called + * once and returned true. + * + * @return if the current position is the first row + * @throws SQLException if the result set is closed + */ + @Override + public boolean isFirst() throws SQLException { + try { + debugCodeCall("isFirst"); + checkClosed(); + return result.getRowId() == 0 && !result.isAfterLast(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if the current position is the last row, that means next() was + * called and did not yet returned false, but will in the next call. + * + * @return if the current position is the last row + * @throws SQLException if the result set is closed + */ + @Override + public boolean isLast() throws SQLException { + try { + debugCodeCall("isLast"); + checkClosed(); + long rowId = result.getRowId(); + return rowId >= 0 && !result.isAfterLast() && !result.hasNext(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Moves the current position to before the first row, that means resets the + * result set. + * + * @throws SQLException if the result set is closed + */ + @Override + public void beforeFirst() throws SQLException { + try { + debugCodeCall("beforeFirst"); + checkClosed(); + if (result.getRowId() >= 0) { + resetResult(); + } + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Moves the current position to after the last row, that means after the + * end. + * + * @throws SQLException if the result set is closed + */ + @Override + public void afterLast() throws SQLException { + try { + debugCodeCall("afterLast"); + checkClosed(); + while (nextRow()) { + // nothing + } + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Moves the current position to the first row. This is the same as calling + * beforeFirst() followed by next(). + * + * @return true if there is a row available, false if not + * @throws SQLException if the result set is closed + */ + @Override + public boolean first() throws SQLException { + try { + debugCodeCall("first"); + checkClosed(); + if (result.getRowId() >= 0) { + resetResult(); + } + return nextRow(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Moves the current position to the last row. + * + * @return true if there is a row available, false if not + * @throws SQLException if the result set is closed + */ + @Override + public boolean last() throws SQLException { + try { + debugCodeCall("last"); + checkClosed(); + if (result.isAfterLast()) { + resetResult(); + } + while (result.hasNext()) { + nextRow(); + } + return isOnValidRow(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Moves the current position to a specific row. + * + * @param rowNumber the row number. 0 is not allowed, 1 means the first row, + * 2 the second. -1 means the last row, -2 the row before the + * last row. If the value is too large, the position is moved + * after the last row, if the value is too small it is moved + * before the first row. + * @return true if there is a row available, false if not + * @throws SQLException if the result set is closed + */ + @Override + public boolean absolute(int rowNumber) throws SQLException { + try { + debugCodeCall("absolute", rowNumber); + checkClosed(); + long longRowNumber = rowNumber >= 0 ? rowNumber : result.getRowCount() + rowNumber + 1; + if (--longRowNumber < result.getRowId()) { + resetResult(); + } + while (result.getRowId() < longRowNumber) { + if (!nextRow()) { + return false; + } + } + return isOnValidRow(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Moves the current position to a specific row relative to the current row. + * + * @param rowCount 0 means don't do anything, 1 is the next row, -1 the + * previous. If the value is too large, the position is moved + * after the last row, if the value is too small it is moved + * before the first row. + * @return true if there is a row available, false if not + * @throws SQLException if the result set is closed + */ + @Override + public boolean relative(int rowCount) throws SQLException { + try { + debugCodeCall("relative", rowCount); + checkClosed(); + long longRowCount; + if (rowCount < 0) { + longRowCount = result.getRowId() + rowCount + 1; + resetResult(); + } else { + longRowCount = rowCount; + } + while (longRowCount-- > 0) { + if (!nextRow()) { + return false; + } + } + return isOnValidRow(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Moves the cursor to the last row, or row before first row if the current + * position is the first row. + * + * @return true if there is a row available, false if not + * @throws SQLException if the result set is closed + */ + @Override + public boolean previous() throws SQLException { + try { + debugCodeCall("previous"); + checkClosed(); + return relative(-1); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Moves the current position to the insert row. The current row is + * remembered. + * + * @throws SQLException if the result set is closed or is not updatable + */ + @Override + public void moveToInsertRow() throws SQLException { + try { + debugCodeCall("moveToInsertRow"); + checkUpdatable(); + insertRow = new Value[columnCount]; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Moves the current position to the current row. + * + * @throws SQLException if the result set is closed or is not updatable + */ + @Override + public void moveToCurrentRow() throws SQLException { + try { + debugCodeCall("moveToCurrentRow"); + checkUpdatable(); + insertRow = null; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Detects if the row was updated (by somebody else or the caller). + * + * @return false because this driver does not detect this + */ + @Override + public boolean rowUpdated() throws SQLException { + try { + debugCodeCall("rowUpdated"); + return false; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Detects if the row was inserted. + * + * @return false because this driver does not detect this + */ + @Override + public boolean rowInserted() throws SQLException { + try { + debugCodeCall("rowInserted"); + return false; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Detects if the row was deleted (by somebody else or the caller). + * + * @return false because this driver does not detect this + */ + @Override + public boolean rowDeleted() throws SQLException { + try { + debugCodeCall("rowDeleted"); + return false; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Inserts the current row. The current position must be the insert row. + * + * @throws SQLException if the result set is closed or if not on the insert + * row, or if the result set it not updatable + */ + @Override + public void insertRow() throws SQLException { + try { + debugCodeCall("insertRow"); + checkUpdatable(); + if (insertRow == null) { + throw DbException.get(ErrorCode.NOT_ON_UPDATABLE_ROW); + } + getUpdatableRow().insertRow(insertRow); + insertRow = null; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates the current row. + * + * @throws SQLException if the result set is closed, if the current row is + * the insert row or if not on a valid row, or if the result set + * it not updatable + */ + @Override + public void updateRow() throws SQLException { + try { + debugCodeCall("updateRow"); + checkUpdatable(); + if (insertRow != null) { + throw DbException.get(ErrorCode.NOT_ON_UPDATABLE_ROW); + } + checkOnValidRow(); + if (updateRow != null) { + UpdatableRow row = getUpdatableRow(); + Value[] current = new Value[columnCount]; + for (int i = 0; i < updateRow.length; i++) { + current[i] = getInternal(checkColumnIndex(i + 1)); + } + row.updateRow(current, updateRow); + for (int i = 0; i < updateRow.length; i++) { + if (updateRow[i] == null) { + updateRow[i] = current[i]; + } + } + Value[] patch = row.readRow(updateRow); + patchCurrentRow(patch); + updateRow = null; + } + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Deletes the current row. + * + * @throws SQLException if the result set is closed, if the current row is + * the insert row or if not on a valid row, or if the result set + * it not updatable + */ + @Override + public void deleteRow() throws SQLException { + try { + debugCodeCall("deleteRow"); + checkUpdatable(); + if (insertRow != null) { + throw DbException.get(ErrorCode.NOT_ON_UPDATABLE_ROW); + } + checkOnValidRow(); + getUpdatableRow().deleteRow(result.currentRow()); + updateRow = null; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Re-reads the current row from the database. + * + * @throws SQLException if the result set is closed or if the current row is + * the insert row or if the row has been deleted or if not on a + * valid row + */ + @Override + public void refreshRow() throws SQLException { + try { + debugCodeCall("refreshRow"); + checkClosed(); + if (insertRow != null) { + throw DbException.get(ErrorCode.NO_DATA_AVAILABLE); + } + checkOnValidRow(); + patchCurrentRow(getUpdatableRow().readRow(result.currentRow())); + updateRow = null; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Cancels updating a row. + * + * @throws SQLException if the result set is closed or if the current row is + * the insert row + */ + @Override + public void cancelRowUpdates() throws SQLException { + try { + debugCodeCall("cancelRowUpdates"); + checkClosed(); + if (insertRow != null) { + throw DbException.get(ErrorCode.NO_DATA_AVAILABLE); + } + updateRow = null; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + // ============================================================= + + private UpdatableRow getUpdatableRow() throws SQLException { + UpdatableRow row = new UpdatableRow(conn, result); + if (!row.isUpdatable()) { + throw DbException.get(ErrorCode.RESULT_SET_NOT_UPDATABLE); + } + return row; + } + + private int getColumnIndex(String columnLabel) { + checkClosed(); + if (columnLabel == null) { + throw DbException.getInvalidValueException("columnLabel", null); + } + if (columnCount >= 3) { + // use a hash table if more than 2 columns + if (columnLabelMap == null) { + HashMap map = new HashMap<>(); + // column labels have higher priority + for (int i = 0; i < columnCount; i++) { + String c = StringUtils.toUpperEnglish(result.getAlias(i)); + // Don't override previous mapping + map.putIfAbsent(c, i); + } + for (int i = 0; i < columnCount; i++) { + String colName = result.getColumnName(i); + if (colName != null) { + colName = StringUtils.toUpperEnglish(colName); + // Don't override previous mapping + map.putIfAbsent(colName, i); + String tabName = result.getTableName(i); + if (tabName != null) { + colName = StringUtils.toUpperEnglish(tabName) + '.' + colName; + // Don't override previous mapping + map.putIfAbsent(colName, i); + } + } + } + // assign at the end so concurrent access is supported + columnLabelMap = map; + if (preparedStatement != null) { + preparedStatement.setCachedColumnLabelMap(columnLabelMap); + } + } + Integer index = columnLabelMap.get(StringUtils.toUpperEnglish(columnLabel)); + if (index == null) { + throw DbException.get(ErrorCode.COLUMN_NOT_FOUND_1, columnLabel); + } + return index + 1; + } + for (int i = 0; i < columnCount; i++) { + if (columnLabel.equalsIgnoreCase(result.getAlias(i))) { + return i + 1; + } + } + int idx = columnLabel.indexOf('.'); + if (idx > 0) { + String table = columnLabel.substring(0, idx); + String col = columnLabel.substring(idx+1); + for (int i = 0; i < columnCount; i++) { + if (table.equalsIgnoreCase(result.getTableName(i)) && + col.equalsIgnoreCase(result.getColumnName(i))) { + return i + 1; + } + } + } else { + for (int i = 0; i < columnCount; i++) { + if (columnLabel.equalsIgnoreCase(result.getColumnName(i))) { + return i + 1; + } + } + } + throw DbException.get(ErrorCode.COLUMN_NOT_FOUND_1, columnLabel); + } + + private int checkColumnIndex(int columnIndex) { + checkClosed(); + if (columnIndex < 1 || columnIndex > columnCount) { + throw DbException.getInvalidValueException("columnIndex", columnIndex); + } + return columnIndex; + } + + /** + * Check if this result set is closed. + * + * @throws DbException if it is closed + */ + void checkClosed() { + if (result == null) { + throw DbException.get(ErrorCode.OBJECT_CLOSED); + } + if (stat != null) { + stat.checkClosed(); + } + if (conn != null) { + conn.checkClosed(); + } + } + + private boolean isOnValidRow() { + return result.getRowId() >= 0 && !result.isAfterLast(); + } + + private void checkOnValidRow() { + if (!isOnValidRow()) { + throw DbException.get(ErrorCode.NO_DATA_AVAILABLE); + } + } + + private Value get(int columnIndex) { + Value value = getInternal(columnIndex); + wasNull = value == ValueNull.INSTANCE; + return value; + } + + /** + * INTERNAL + * + * @param columnIndex + * index of a column + * @return internal representation of the value in the specified column + */ + public Value getInternal(int columnIndex) { + checkOnValidRow(); + Value[] list; + if (patchedRows == null || (list = patchedRows.get(result.getRowId())) == null) { + list = result.currentRow(); + } + return list[columnIndex - 1]; + } + + private void update(int columnIndex, Value v) { + if (!triggerUpdatable) { + checkUpdatable(); + } + if (insertRow != null) { + insertRow[columnIndex - 1] = v; + } else { + if (updateRow == null) { + updateRow = new Value[columnCount]; + } + updateRow[columnIndex - 1] = v; + } + } + + private boolean nextRow() { + boolean next = result.isLazy() ? nextLazyRow() : result.next(); + if (!next && !scrollable) { + result.close(); + } + return next; + } + + private boolean nextLazyRow() { + Session session; + if (stat.isCancelled() || conn == null || (session = conn.getSession()) == null) { + throw DbException.get(ErrorCode.STATEMENT_WAS_CANCELED); + } + Session oldSession = session.setThreadLocalSession(); + boolean next; + try { + next = result.next(); + } finally { + session.resetThreadLocalSession(oldSession); + } + return next; + } + + private void resetResult() { + if (!scrollable) { + throw DbException.get(ErrorCode.RESULT_SET_NOT_SCROLLABLE); + } + result.reset(); + } + + /** + * [Not supported] Returns the value of the specified column as a row id. + * + * @param columnIndex (1,2,...) + */ + @Override + public RowId getRowId(int columnIndex) throws SQLException { + throw unsupported("rowId"); + } + + /** + * [Not supported] Returns the value of the specified column as a row id. + * + * @param columnLabel the column label + */ + @Override + public RowId getRowId(String columnLabel) throws SQLException { + throw unsupported("rowId"); + } + + /** + * [Not supported] Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + */ + @Override + public void updateRowId(int columnIndex, RowId x) throws SQLException { + throw unsupported("rowId"); + } + + /** + * [Not supported] Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + */ + @Override + public void updateRowId(String columnLabel, RowId x) throws SQLException { + throw unsupported("rowId"); + } + + /** + * Returns the current result set holdability. + * + * @return the holdability + * @throws SQLException if the connection is closed + */ + @Override + public int getHoldability() throws SQLException { + try { + debugCodeCall("getHoldability"); + checkClosed(); + return conn.getHoldability(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns whether this result set is closed. + * + * @return true if the result set is closed + */ + @Override + public boolean isClosed() throws SQLException { + try { + debugCodeCall("isClosed"); + return result == null; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateNString(int columnIndex, String x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateNString(" + columnIndex + ", " + quote(x) + ')'); + } + update(checkColumnIndex(columnIndex), x == null ? ValueNull.INSTANCE : ValueVarchar.get(x, conn)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateNString(String columnLabel, String x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateNString(" + quote(columnLabel) + ", " + quote(x) + ')'); + } + update(getColumnIndex(columnLabel), x == null ? ValueNull.INSTANCE : ValueVarchar.get(x, conn)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateNClob(int columnIndex, NClob x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateNClob(" + columnIndex + ", (NClob) x)"); + } + updateClobImpl(checkColumnIndex(columnIndex), x); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateNClob(int columnIndex, Reader x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateNClob(" + columnIndex + ", (Reader) x)"); + } + updateClobImpl(checkColumnIndex(columnIndex), x, -1L); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @param length the length + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateNClob(int columnIndex, Reader x, long length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateNClob(" + columnIndex + ", (Reader) x, " + length + "L)"); + } + updateClobImpl(checkColumnIndex(columnIndex), x, length); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateNClob(String columnLabel, Reader x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateNClob(" + quote(columnLabel) + ", (Reader) x)"); + } + updateClobImpl(getColumnIndex(columnLabel), x, -1L); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @param length the length + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateNClob(String columnLabel, Reader x, long length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateNClob(" + quote(columnLabel) + ", (Reader) x, " + length + "L)"); + } + updateClobImpl(getColumnIndex(columnLabel), x, length); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateNClob(String columnLabel, NClob x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateNClob(" + quote(columnLabel) + ", (NClob) x)"); + } + updateClobImpl(getColumnIndex(columnLabel), x); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private void updateClobImpl(int columnIndex, Clob x) throws SQLException { + update(columnIndex, x == null ? ValueNull.INSTANCE : conn.createClob(x.getCharacterStream(), -1)); + } + + /** + * Returns the value of the specified column as a Clob. + * + * @param columnIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public NClob getNClob(int columnIndex) throws SQLException { + try { + int id = getNextId(TraceObject.CLOB); + if (isDebugEnabled()) { + debugCodeAssign("NClob", TraceObject.CLOB, id, "getNClob(" + columnIndex + ')'); + } + return getClob(id, checkColumnIndex(columnIndex)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a Clob. + * + * @param columnLabel the column label + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public NClob getNClob(String columnLabel) throws SQLException { + try { + int id = getNextId(TraceObject.CLOB); + if (isDebugEnabled()) { + debugCodeAssign("NClob", TraceObject.CLOB, id, "getNClob(" + quote(columnLabel) + ')'); + } + return getClob(id, getColumnIndex(columnLabel)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private JdbcClob getClob(int id, int columnIndex) { + Value v = getInternal(columnIndex); + JdbcClob result; + if (v != ValueNull.INSTANCE) { + wasNull = false; + result = new JdbcClob(conn, v, JdbcLob.State.WITH_VALUE, id); + } else { + wasNull = true; + result = null; + } + return result; + } + + /** + * Returns the value of the specified column as a SQLXML. + * + * @param columnIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public SQLXML getSQLXML(int columnIndex) throws SQLException { + try { + int id = getNextId(TraceObject.SQLXML); + if (isDebugEnabled()) { + debugCodeAssign("SQLXML", TraceObject.SQLXML, id, "getSQLXML(" + columnIndex + ')'); + } + Value v = get(checkColumnIndex(columnIndex)); + return v == ValueNull.INSTANCE ? null : new JdbcSQLXML(conn, v, JdbcLob.State.WITH_VALUE, id); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a SQLXML. + * + * @param columnLabel the column label + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public SQLXML getSQLXML(String columnLabel) throws SQLException { + try { + int id = getNextId(TraceObject.SQLXML); + if (isDebugEnabled()) { + debugCodeAssign("SQLXML", TraceObject.SQLXML, id, "getSQLXML(" + quote(columnLabel) + ')'); + } + Value v = get(getColumnIndex(columnLabel)); + return v == ValueNull.INSTANCE ? null : new JdbcSQLXML(conn, v, JdbcLob.State.WITH_VALUE, id); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param xmlObject the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateSQLXML(" + columnIndex + ", x)"); + } + updateSQLXMLImpl(checkColumnIndex(columnIndex), xmlObject); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param xmlObject the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateSQLXML(" + quote(columnLabel) + ", x)"); + } + updateSQLXMLImpl(getColumnIndex(columnLabel), xmlObject); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private void updateSQLXMLImpl(int columnIndex, SQLXML xmlObject) throws SQLException { + update(columnIndex, + xmlObject == null ? ValueNull.INSTANCE : conn.createClob(xmlObject.getCharacterStream(), -1)); + } + + /** + * Returns the value of the specified column as a String. + * + * @param columnIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public String getNString(int columnIndex) throws SQLException { + try { + debugCodeCall("getNString", columnIndex); + return get(checkColumnIndex(columnIndex)).getString(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a String. + * + * @param columnLabel the column label + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public String getNString(String columnLabel) throws SQLException { + try { + debugCodeCall("getNString", columnLabel); + return get(getColumnIndex(columnLabel)).getString(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a reader. + * + * @param columnIndex (1,2,...) + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public Reader getNCharacterStream(int columnIndex) throws SQLException { + try { + debugCodeCall("getNCharacterStream", columnIndex); + return get(checkColumnIndex(columnIndex)).getReader(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the value of the specified column as a reader. + * + * @param columnLabel the column label + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public Reader getNCharacterStream(String columnLabel) throws SQLException { + try { + debugCodeCall("getNCharacterStream", columnLabel); + return get(getColumnIndex(columnLabel)).getReader(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateNCharacterStream(" + columnIndex + ", x)"); + } + updateClobImpl(checkColumnIndex(columnIndex), x, -1L); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnIndex (1,2,...) + * @param x the value + * @param length the number of characters + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateNCharacterStream(" + columnIndex + ", x, " + length + "L)"); + } + updateClobImpl(checkColumnIndex(columnIndex), x, length); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateNCharacterStream(String columnLabel, Reader x) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateNCharacterStream(" + quote(columnLabel) + ", x)"); + } + updateClobImpl(getColumnIndex(columnLabel), x, -1L); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Updates a column in the current or insert row. + * + * @param columnLabel the column label + * @param x the value + * @param length the number of characters + * @throws SQLException if the result set is closed or not updatable + */ + @Override + public void updateNCharacterStream(String columnLabel, Reader x, long length) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("updateNCharacterStream(" + quote(columnLabel) + ", x, " + length + "L)"); + } + updateClobImpl(getColumnIndex(columnLabel), x, length); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private void updateClobImpl(int columnIndex, Reader x, long length) { + update(columnIndex, conn.createClob(x, length)); + } + + /** + * Return an object of this class if possible. + * + * @param iface the class + * @return this + */ + @Override + @SuppressWarnings("unchecked") + public T unwrap(Class iface) throws SQLException { + try { + if (isWrapperFor(iface)) { + return (T) this; + } + throw DbException.getInvalidValueException("iface", iface); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if unwrap can return an object of this class. + * + * @param iface the class + * @return whether or not the interface is assignable from this class + */ + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return iface != null && iface.isAssignableFrom(getClass()); + } + + /** + * Returns a column value as a Java object of the specified type. + * + * @param columnIndex the column index (1, 2, ...) + * @param type the class of the returned value + * @return the value + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public T getObject(int columnIndex, Class type) throws SQLException { + try { + if (type == null) { + throw DbException.getInvalidValueException("type", type); + } + debugCodeCall("getObject", columnIndex); + return ValueToObjectConverter.valueToObject(type, get(checkColumnIndex(columnIndex)), conn); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns a column value as a Java object of the specified type. + * + * @param columnName the column name + * @param type the class of the returned value + * @return the value + */ + @Override + public T getObject(String columnName, Class type) throws SQLException { + try { + if (type == null) { + throw DbException.getInvalidValueException("type", type); + } + debugCodeCall("getObject", columnName); + return ValueToObjectConverter.valueToObject(type, get(getColumnIndex(columnName)), conn); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * INTERNAL + */ + @Override + public String toString() { + return getTraceObjectName() + ": " + result; + } + + private void patchCurrentRow(Value[] row) { + boolean changed = false; + Value[] current = result.currentRow(); + CompareMode compareMode = conn.getCompareMode(); + for (int i = 0; i < row.length; i++) { + if (row[i].compareTo(current[i], conn, compareMode) != 0) { + changed = true; + break; + } + } + if (patchedRows == null) { + patchedRows = new HashMap<>(); + } + Long rowId = result.getRowId(); + if (!changed) { + patchedRows.remove(rowId); + } else { + patchedRows.put(rowId, row); + } + } + + private Value convertToValue(Object x, SQLType targetSqlType) { + if (x == null) { + return ValueNull.INSTANCE; + } else { + int type = DataType.convertSQLTypeToValueType(targetSqlType); + Value v = ValueToObjectConverter.objectToValue(conn.getSession(), x, type); + return v.convertTo(type, conn); + } + } + + private Value convertToUnknownValue(Object x) { + return ValueToObjectConverter.objectToValue(conn.getSession(), x, Value.UNKNOWN); + } + + private void checkUpdatable() { + checkClosed(); + if (!updatable) { + throw DbException.get(ErrorCode.RESULT_SET_READONLY); + } + } + + /** + * INTERNAL + * + * @return array of column values for the current row + */ + public Value[] getUpdateRow() { + return updateRow; + } + + /** + * INTERNAL + * + * @return result + */ + public ResultInterface getResult() { + return result; + } + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcResultSetMetaData.java b/h2/src/main/org/h2/jdbc/JdbcResultSetMetaData.java new file mode 100644 index 0000000..e3658d6 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcResultSetMetaData.java @@ -0,0 +1,465 @@ +/* + * 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.jdbc; + +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.message.TraceObject; +import org.h2.result.ResultInterface; +import org.h2.util.MathUtils; +import org.h2.value.DataType; +import org.h2.value.ValueToObjectConverter; + +/** + * Represents the meta data for a ResultSet. + */ +public final class JdbcResultSetMetaData extends TraceObject implements ResultSetMetaData { + + private final String catalog; + private final JdbcResultSet rs; + private final JdbcPreparedStatement prep; + private final ResultInterface result; + private final int columnCount; + + JdbcResultSetMetaData(JdbcResultSet rs, JdbcPreparedStatement prep, + ResultInterface result, String catalog, Trace trace, int id) { + setTrace(trace, TraceObject.RESULT_SET_META_DATA, id); + this.catalog = catalog; + this.rs = rs; + this.prep = prep; + this.result = result; + this.columnCount = result.getVisibleColumnCount(); + } + + /** + * Returns the number of columns. + * + * @return the number of columns + * @throws SQLException if the result set is closed or invalid + */ + @Override + public int getColumnCount() throws SQLException { + try { + debugCodeCall("getColumnCount"); + checkClosed(); + return columnCount; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the column label. + * + * @param column the column index (1,2,...) + * @return the column label + * @throws SQLException if the result set is closed or invalid + */ + @Override + public String getColumnLabel(int column) throws SQLException { + try { + return result.getAlias(getColumn("getColumnLabel", column)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the column name. + * + * @param column the column index (1,2,...) + * @return the column name + * @throws SQLException if the result set is closed or invalid + */ + @Override + public String getColumnName(int column) throws SQLException { + try { + return result.getColumnName(getColumn("getColumnName", column)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the data type of a column. + * See also java.sql.Type. + * + * @param column the column index (1,2,...) + * @return the data type + * @throws SQLException if the result set is closed or invalid + */ + @Override + public int getColumnType(int column) throws SQLException { + try { + return DataType.convertTypeToSQLType(result.getColumnType(getColumn("getColumnType", column))); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the data type name of a column. + * + * @param column the column index (1,2,...) + * @return the data type name + * @throws SQLException if the result set is closed or invalid + */ + @Override + public String getColumnTypeName(int column) throws SQLException { + try { + return result.getColumnType(getColumn("getColumnTypeName", column)).getDeclaredTypeName(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the schema name. + * + * @param column the column index (1,2,...) + * @return the schema name, or "" (an empty string) if not applicable + * @throws SQLException if the result set is closed or invalid + */ + @Override + public String getSchemaName(int column) throws SQLException { + try { + String schema = result.getSchemaName(getColumn("getSchemaName", column)); + return schema == null ? "" : schema; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the table name. + * + * @param column the column index (1,2,...) + * @return the table name + * @throws SQLException if the result set is closed or invalid + */ + @Override + public String getTableName(int column) throws SQLException { + try { + String table = result.getTableName(getColumn("getTableName", column)); + return table == null ? "" : table; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the catalog name. + * + * @param column the column index (1,2,...) + * @return the catalog name + * @throws SQLException if the result set is closed or invalid + */ + @Override + public String getCatalogName(int column) throws SQLException { + try { + getColumn("getCatalogName", column); + return catalog == null ? "" : catalog; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if this an autoincrement column. + * + * @param column the column index (1,2,...) + * @return false + * @throws SQLException if the result set is closed or invalid + */ + @Override + public boolean isAutoIncrement(int column) throws SQLException { + try { + return result.isIdentity(getColumn("isAutoIncrement", column)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if this column is case sensitive. + * It always returns true. + * + * @param column the column index (1,2,...) + * @return true + * @throws SQLException if the result set is closed or invalid + */ + @Override + public boolean isCaseSensitive(int column) throws SQLException { + try { + getColumn("isCaseSensitive", column); + return true; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if this column is searchable. + * It always returns true. + * + * @param column the column index (1,2,...) + * @return true + * @throws SQLException if the result set is closed or invalid + */ + @Override + public boolean isSearchable(int column) throws SQLException { + try { + getColumn("isSearchable", column); + return true; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if this is a currency column. + * It always returns false. + * + * @param column the column index (1,2,...) + * @return false + * @throws SQLException if the result set is closed or invalid + */ + @Override + public boolean isCurrency(int column) throws SQLException { + try { + getColumn("isCurrency", column); + return false; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if this is nullable column. Returns + * ResultSetMetaData.columnNullableUnknown if this is not a column of a + * table. Otherwise, it returns ResultSetMetaData.columnNoNulls if the + * column is not nullable, and ResultSetMetaData.columnNullable if it is + * nullable. + * + * @param column the column index (1,2,...) + * @return ResultSetMetaData.column* + * @throws SQLException if the result set is closed or invalid + */ + @Override + public int isNullable(int column) throws SQLException { + try { + return result.getNullable(getColumn("isNullable", column)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if this column is signed. + * Returns true for numeric columns. + * + * @param column the column index (1,2,...) + * @return true for numeric columns + * @throws SQLException if the result set is closed or invalid + */ + @Override + public boolean isSigned(int column) throws SQLException { + try { + return DataType.isNumericType(result.getColumnType(getColumn("isSigned", column)).getValueType()); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if this column is read only. + * It always returns false. + * + * @param column the column index (1,2,...) + * @return false + * @throws SQLException if the result set is closed or invalid + */ + @Override + public boolean isReadOnly(int column) throws SQLException { + try { + getColumn("isReadOnly", column); + return false; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks whether it is possible for a write on this column to succeed. + * It always returns true. + * + * @param column the column index (1,2,...) + * @return true + * @throws SQLException if the result set is closed or invalid + */ + @Override + public boolean isWritable(int column) throws SQLException { + try { + getColumn("isWritable", column); + return true; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks whether a write on this column will definitely succeed. + * It always returns false. + * + * @param column the column index (1,2,...) + * @return false + * @throws SQLException if the result set is closed or invalid + */ + @Override + public boolean isDefinitelyWritable(int column) throws SQLException { + try { + getColumn("isDefinitelyWritable", column); + return false; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the Java class name of the object that will be returned + * if ResultSet.getObject is called. + * + * @param column the column index (1,2,...) + * @return the Java class name + * @throws SQLException if the result set is closed or invalid + */ + @Override + public String getColumnClassName(int column) throws SQLException { + try { + int type = result.getColumnType(getColumn("getColumnClassName", column)).getValueType(); + return ValueToObjectConverter.getDefaultClass(type, true).getName(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the precision for this column. + * + * @param column the column index (1,2,...) + * @return the precision + * @throws SQLException if the result set is closed or invalid + */ + @Override + public int getPrecision(int column) throws SQLException { + try { + return MathUtils.convertLongToInt(result.getColumnType(getColumn("getPrecision", column)).getPrecision()); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the scale for this column. + * + * @param column the column index (1,2,...) + * @return the scale + * @throws SQLException if the result set is closed or invalid + */ + @Override + public int getScale(int column) throws SQLException { + try { + return result.getColumnType(getColumn("getScale", column)).getScale(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the maximum display size for this column. + * + * @param column the column index (1,2,...) + * @return the display size + * @throws SQLException if the result set is closed or invalid + */ + @Override + public int getColumnDisplaySize(int column) throws SQLException { + try { + return result.getColumnType(getColumn("getColumnDisplaySize", column)).getDisplaySize(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private void checkClosed() { + if (rs != null) { + rs.checkClosed(); + } + if (prep != null) { + prep.checkClosed(); + } + } + + /** + * Writes trace information and checks validity of this object and + * parameter. + * + * @param methodName + * the called method name + * @param columnIndex + * 1-based column index + * @return 0-based column index + */ + private int getColumn(String methodName, int columnIndex) { + debugCodeCall(methodName, columnIndex); + checkClosed(); + if (columnIndex < 1 || columnIndex > columnCount) { + throw DbException.getInvalidValueException("columnIndex", columnIndex); + } + return columnIndex - 1; + } + + /** + * Return an object of this class if possible. + * + * @param iface the class + * @return this + */ + @Override + @SuppressWarnings("unchecked") + public T unwrap(Class iface) throws SQLException { + try { + if (isWrapperFor(iface)) { + return (T) this; + } + throw DbException.getInvalidValueException("iface", iface); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if unwrap can return an object of this class. + * + * @param iface the class + * @return whether or not the interface is assignable from this class + */ + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return iface != null && iface.isAssignableFrom(getClass()); + } + + /** + * INTERNAL + */ + @Override + public String toString() { + return getTraceObjectName() + ": columns=" + columnCount; + } + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcSQLDataException.java b/h2/src/main/org/h2/jdbc/JdbcSQLDataException.java new file mode 100644 index 0000000..0016f23 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcSQLDataException.java @@ -0,0 +1,87 @@ +/* + * 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.jdbc; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.sql.SQLDataException; + +import org.h2.message.DbException; + +/** + * Represents a database exception. + */ +public final class JdbcSQLDataException extends SQLDataException implements JdbcException { + + private static final long serialVersionUID = 1L; + + private final String originalMessage; + private final String stackTrace; + private String message; + private String sql; + + /** + * Creates a SQLDataException. + * + * @param message the reason + * @param sql the SQL statement + * @param state the SQL state + * @param errorCode the error code + * @param cause the exception that was the reason for this exception + * @param stackTrace the stack trace + */ + public JdbcSQLDataException(String message, String sql, String state, + int errorCode, Throwable cause, String stackTrace) { + super(message, state, errorCode); + this.originalMessage = message; + this.stackTrace = stackTrace; + // setSQL() also generates message + setSQL(sql); + initCause(cause); + } + + @Override + public String getMessage() { + return message; + } + + @Override + public String getOriginalMessage() { + return originalMessage; + } + + @Override + public void printStackTrace(PrintWriter s) { + super.printStackTrace(s); + DbException.printNextExceptions(this, s); + } + + @Override + public void printStackTrace(PrintStream s) { + super.printStackTrace(s); + DbException.printNextExceptions(this, s); + } + + @Override + public String getSQL() { + return sql; + } + + @Override + public void setSQL(String sql) { + this.sql = sql; + message = DbException.buildMessageForException(this); + } + + @Override + public String toString() { + if (stackTrace == null) { + return super.toString(); + } + return stackTrace; + } + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcSQLException.java b/h2/src/main/org/h2/jdbc/JdbcSQLException.java new file mode 100644 index 0000000..de08d17 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcSQLException.java @@ -0,0 +1,87 @@ +/* + * 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.jdbc; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.sql.SQLException; + +import org.h2.message.DbException; + +/** + * Represents a database exception. + */ +public final class JdbcSQLException extends SQLException implements JdbcException { + + private static final long serialVersionUID = 1L; + + private final String originalMessage; + private final String stackTrace; + private String message; + private String sql; + + /** + * Creates a SQLException. + * + * @param message the reason + * @param sql the SQL statement + * @param state the SQL state + * @param errorCode the error code + * @param cause the exception that was the reason for this exception + * @param stackTrace the stack trace + */ + public JdbcSQLException(String message, String sql, String state, + int errorCode, Throwable cause, String stackTrace) { + super(message, state, errorCode); + this.originalMessage = message; + this.stackTrace = stackTrace; + // setSQL() also generates message + setSQL(sql); + initCause(cause); + } + + @Override + public String getMessage() { + return message; + } + + @Override + public String getOriginalMessage() { + return originalMessage; + } + + @Override + public void printStackTrace(PrintWriter s) { + super.printStackTrace(s); + DbException.printNextExceptions(this, s); + } + + @Override + public void printStackTrace(PrintStream s) { + super.printStackTrace(s); + DbException.printNextExceptions(this, s); + } + + @Override + public String getSQL() { + return sql; + } + + @Override + public void setSQL(String sql) { + this.sql = sql; + message = DbException.buildMessageForException(this); + } + + @Override + public String toString() { + if (stackTrace == null) { + return super.toString(); + } + return stackTrace; + } + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcSQLFeatureNotSupportedException.java b/h2/src/main/org/h2/jdbc/JdbcSQLFeatureNotSupportedException.java new file mode 100644 index 0000000..bf9416b --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcSQLFeatureNotSupportedException.java @@ -0,0 +1,88 @@ +/* + * 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.jdbc; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.sql.SQLFeatureNotSupportedException; + +import org.h2.message.DbException; + +/** + * Represents a database exception. + */ +public final class JdbcSQLFeatureNotSupportedException extends SQLFeatureNotSupportedException + implements JdbcException { + + private static final long serialVersionUID = 1L; + + private final String originalMessage; + private final String stackTrace; + private String message; + private String sql; + + /** + * Creates a SQLFeatureNotSupportedException. + * + * @param message the reason + * @param sql the SQL statement + * @param state the SQL state + * @param errorCode the error code + * @param cause the exception that was the reason for this exception + * @param stackTrace the stack trace + */ + public JdbcSQLFeatureNotSupportedException(String message, String sql, String state, + int errorCode, Throwable cause, String stackTrace) { + super(message, state, errorCode); + this.originalMessage = message; + this.stackTrace = stackTrace; + // setSQL() also generates message + setSQL(sql); + initCause(cause); + } + + @Override + public String getMessage() { + return message; + } + + @Override + public String getOriginalMessage() { + return originalMessage; + } + + @Override + public void printStackTrace(PrintWriter s) { + super.printStackTrace(s); + DbException.printNextExceptions(this, s); + } + + @Override + public void printStackTrace(PrintStream s) { + super.printStackTrace(s); + DbException.printNextExceptions(this, s); + } + + @Override + public String getSQL() { + return sql; + } + + @Override + public void setSQL(String sql) { + this.sql = sql; + message = DbException.buildMessageForException(this); + } + + @Override + public String toString() { + if (stackTrace == null) { + return super.toString(); + } + return stackTrace; + } + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcSQLIntegrityConstraintViolationException.java b/h2/src/main/org/h2/jdbc/JdbcSQLIntegrityConstraintViolationException.java new file mode 100644 index 0000000..6ce2421 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcSQLIntegrityConstraintViolationException.java @@ -0,0 +1,88 @@ +/* + * 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.jdbc; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.sql.SQLIntegrityConstraintViolationException; + +import org.h2.message.DbException; + +/** + * Represents a database exception. + */ +public final class JdbcSQLIntegrityConstraintViolationException extends SQLIntegrityConstraintViolationException + implements JdbcException { + + private static final long serialVersionUID = 1L; + + private final String originalMessage; + private final String stackTrace; + private String message; + private String sql; + + /** + * Creates a SQLIntegrityConstraintViolationException. + * + * @param message the reason + * @param sql the SQL statement + * @param state the SQL state + * @param errorCode the error code + * @param cause the exception that was the reason for this exception + * @param stackTrace the stack trace + */ + public JdbcSQLIntegrityConstraintViolationException(String message, String sql, String state, + int errorCode, Throwable cause, String stackTrace) { + super(message, state, errorCode); + this.originalMessage = message; + this.stackTrace = stackTrace; + // setSQL() also generates message + setSQL(sql); + initCause(cause); + } + + @Override + public String getMessage() { + return message; + } + + @Override + public String getOriginalMessage() { + return originalMessage; + } + + @Override + public void printStackTrace(PrintWriter s) { + super.printStackTrace(s); + DbException.printNextExceptions(this, s); + } + + @Override + public void printStackTrace(PrintStream s) { + super.printStackTrace(s); + DbException.printNextExceptions(this, s); + } + + @Override + public String getSQL() { + return sql; + } + + @Override + public void setSQL(String sql) { + this.sql = sql; + message = DbException.buildMessageForException(this); + } + + @Override + public String toString() { + if (stackTrace == null) { + return super.toString(); + } + return stackTrace; + } + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcSQLInvalidAuthorizationSpecException.java b/h2/src/main/org/h2/jdbc/JdbcSQLInvalidAuthorizationSpecException.java new file mode 100644 index 0000000..d06886c --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcSQLInvalidAuthorizationSpecException.java @@ -0,0 +1,88 @@ +/* + * 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.jdbc; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.sql.SQLInvalidAuthorizationSpecException; + +import org.h2.message.DbException; + +/** + * Represents a database exception. + */ +public final class JdbcSQLInvalidAuthorizationSpecException extends SQLInvalidAuthorizationSpecException + implements JdbcException { + + private static final long serialVersionUID = 1L; + + private final String originalMessage; + private final String stackTrace; + private String message; + private String sql; + + /** + * Creates a SQLInvalidAuthorizationSpecException. + * + * @param message the reason + * @param sql the SQL statement + * @param state the SQL state + * @param errorCode the error code + * @param cause the exception that was the reason for this exception + * @param stackTrace the stack trace + */ + public JdbcSQLInvalidAuthorizationSpecException(String message, String sql, String state, + int errorCode, Throwable cause, String stackTrace) { + super(message, state, errorCode); + this.originalMessage = message; + this.stackTrace = stackTrace; + // setSQL() also generates message + setSQL(sql); + initCause(cause); + } + + @Override + public String getMessage() { + return message; + } + + @Override + public String getOriginalMessage() { + return originalMessage; + } + + @Override + public void printStackTrace(PrintWriter s) { + super.printStackTrace(s); + DbException.printNextExceptions(this, s); + } + + @Override + public void printStackTrace(PrintStream s) { + super.printStackTrace(s); + DbException.printNextExceptions(this, s); + } + + @Override + public String getSQL() { + return sql; + } + + @Override + public void setSQL(String sql) { + this.sql = sql; + message = DbException.buildMessageForException(this); + } + + @Override + public String toString() { + if (stackTrace == null) { + return super.toString(); + } + return stackTrace; + } + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcSQLNonTransientConnectionException.java b/h2/src/main/org/h2/jdbc/JdbcSQLNonTransientConnectionException.java new file mode 100644 index 0000000..b76dd0d --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcSQLNonTransientConnectionException.java @@ -0,0 +1,88 @@ +/* + * 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.jdbc; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.sql.SQLNonTransientConnectionException; + +import org.h2.message.DbException; + +/** + * Represents a database exception. + */ +public final class JdbcSQLNonTransientConnectionException extends SQLNonTransientConnectionException + implements JdbcException { + + private static final long serialVersionUID = 1L; + + private final String originalMessage; + private final String stackTrace; + private String message; + private String sql; + + /** + * Creates a SQLNonTransientConnectionException. + * + * @param message the reason + * @param sql the SQL statement + * @param state the SQL state + * @param errorCode the error code + * @param cause the exception that was the reason for this exception + * @param stackTrace the stack trace + */ + public JdbcSQLNonTransientConnectionException(String message, String sql, String state, + int errorCode, Throwable cause, String stackTrace) { + super(message, state, errorCode); + this.originalMessage = message; + this.stackTrace = stackTrace; + // setSQL() also generates message + setSQL(sql); + initCause(cause); + } + + @Override + public String getMessage() { + return message; + } + + @Override + public String getOriginalMessage() { + return originalMessage; + } + + @Override + public void printStackTrace(PrintWriter s) { + super.printStackTrace(s); + DbException.printNextExceptions(this, s); + } + + @Override + public void printStackTrace(PrintStream s) { + super.printStackTrace(s); + DbException.printNextExceptions(this, s); + } + + @Override + public String getSQL() { + return sql; + } + + @Override + public void setSQL(String sql) { + this.sql = sql; + message = DbException.buildMessageForException(this); + } + + @Override + public String toString() { + if (stackTrace == null) { + return super.toString(); + } + return stackTrace; + } + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcSQLNonTransientException.java b/h2/src/main/org/h2/jdbc/JdbcSQLNonTransientException.java new file mode 100644 index 0000000..858a564 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcSQLNonTransientException.java @@ -0,0 +1,87 @@ +/* + * 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.jdbc; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.sql.SQLNonTransientException; + +import org.h2.message.DbException; + +/** + * Represents a database exception. + */ +public final class JdbcSQLNonTransientException extends SQLNonTransientException implements JdbcException { + + private static final long serialVersionUID = 1L; + + private final String originalMessage; + private final String stackTrace; + private String message; + private String sql; + + /** + * Creates a SQLNonTransientException. + * + * @param message the reason + * @param sql the SQL statement + * @param state the SQL state + * @param errorCode the error code + * @param cause the exception that was the reason for this exception + * @param stackTrace the stack trace + */ + public JdbcSQLNonTransientException(String message, String sql, String state, + int errorCode, Throwable cause, String stackTrace) { + super(message, state, errorCode); + this.originalMessage = message; + this.stackTrace = stackTrace; + // setSQL() also generates message + setSQL(sql); + initCause(cause); + } + + @Override + public String getMessage() { + return message; + } + + @Override + public String getOriginalMessage() { + return originalMessage; + } + + @Override + public void printStackTrace(PrintWriter s) { + super.printStackTrace(s); + DbException.printNextExceptions(this, s); + } + + @Override + public void printStackTrace(PrintStream s) { + super.printStackTrace(s); + DbException.printNextExceptions(this, s); + } + + @Override + public String getSQL() { + return sql; + } + + @Override + public void setSQL(String sql) { + this.sql = sql; + message = DbException.buildMessageForException(this); + } + + @Override + public String toString() { + if (stackTrace == null) { + return super.toString(); + } + return stackTrace; + } + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcSQLSyntaxErrorException.java b/h2/src/main/org/h2/jdbc/JdbcSQLSyntaxErrorException.java new file mode 100644 index 0000000..97bb472 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcSQLSyntaxErrorException.java @@ -0,0 +1,87 @@ +/* + * 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.jdbc; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.sql.SQLSyntaxErrorException; + +import org.h2.message.DbException; + +/** + * Represents a database exception. + */ +public final class JdbcSQLSyntaxErrorException extends SQLSyntaxErrorException implements JdbcException { + + private static final long serialVersionUID = 1L; + + private final String originalMessage; + private final String stackTrace; + private String message; + private String sql; + + /** + * Creates a SQLSyntaxErrorException. + * + * @param message the reason + * @param sql the SQL statement + * @param state the SQL state + * @param errorCode the error code + * @param cause the exception that was the reason for this exception + * @param stackTrace the stack trace + */ + public JdbcSQLSyntaxErrorException(String message, String sql, String state, + int errorCode, Throwable cause, String stackTrace) { + super(message, state, errorCode); + this.originalMessage = message; + this.stackTrace = stackTrace; + // setSQL() also generates message + setSQL(sql); + initCause(cause); + } + + @Override + public String getMessage() { + return message; + } + + @Override + public String getOriginalMessage() { + return originalMessage; + } + + @Override + public void printStackTrace(PrintWriter s) { + super.printStackTrace(s); + DbException.printNextExceptions(this, s); + } + + @Override + public void printStackTrace(PrintStream s) { + super.printStackTrace(s); + DbException.printNextExceptions(this, s); + } + + @Override + public String getSQL() { + return sql; + } + + @Override + public void setSQL(String sql) { + this.sql = sql; + message = DbException.buildMessageForException(this); + } + + @Override + public String toString() { + if (stackTrace == null) { + return super.toString(); + } + return stackTrace; + } + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcSQLTimeoutException.java b/h2/src/main/org/h2/jdbc/JdbcSQLTimeoutException.java new file mode 100644 index 0000000..7e8ee1a --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcSQLTimeoutException.java @@ -0,0 +1,87 @@ +/* + * 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.jdbc; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.sql.SQLTimeoutException; + +import org.h2.message.DbException; + +/** + * Represents a database exception. + */ +public final class JdbcSQLTimeoutException extends SQLTimeoutException implements JdbcException { + + private static final long serialVersionUID = 1L; + + private final String originalMessage; + private final String stackTrace; + private String message; + private String sql; + + /** + * Creates a SQLTimeoutException. + * + * @param message the reason + * @param sql the SQL statement + * @param state the SQL state + * @param errorCode the error code + * @param cause the exception that was the reason for this exception + * @param stackTrace the stack trace + */ + public JdbcSQLTimeoutException(String message, String sql, String state, + int errorCode, Throwable cause, String stackTrace) { + super(message, state, errorCode); + this.originalMessage = message; + this.stackTrace = stackTrace; + // setSQL() also generates message + setSQL(sql); + initCause(cause); + } + + @Override + public String getMessage() { + return message; + } + + @Override + public String getOriginalMessage() { + return originalMessage; + } + + @Override + public void printStackTrace(PrintWriter s) { + super.printStackTrace(s); + DbException.printNextExceptions(this, s); + } + + @Override + public void printStackTrace(PrintStream s) { + super.printStackTrace(s); + DbException.printNextExceptions(this, s); + } + + @Override + public String getSQL() { + return sql; + } + + @Override + public void setSQL(String sql) { + this.sql = sql; + message = DbException.buildMessageForException(this); + } + + @Override + public String toString() { + if (stackTrace == null) { + return super.toString(); + } + return stackTrace; + } + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcSQLTransactionRollbackException.java b/h2/src/main/org/h2/jdbc/JdbcSQLTransactionRollbackException.java new file mode 100644 index 0000000..34e54b3 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcSQLTransactionRollbackException.java @@ -0,0 +1,88 @@ +/* + * 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.jdbc; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.sql.SQLTransactionRollbackException; + +import org.h2.message.DbException; + +/** + * Represents a database exception. + */ +public final class JdbcSQLTransactionRollbackException extends SQLTransactionRollbackException + implements JdbcException { + + private static final long serialVersionUID = 1L; + + private final String originalMessage; + private final String stackTrace; + private String message; + private String sql; + + /** + * Creates a SQLTransactionRollbackException. + * + * @param message the reason + * @param sql the SQL statement + * @param state the SQL state + * @param errorCode the error code + * @param cause the exception that was the reason for this exception + * @param stackTrace the stack trace + */ + public JdbcSQLTransactionRollbackException(String message, String sql, String state, + int errorCode, Throwable cause, String stackTrace) { + super(message, state, errorCode); + this.originalMessage = message; + this.stackTrace = stackTrace; + // setSQL() also generates message + setSQL(sql); + initCause(cause); + } + + @Override + public String getMessage() { + return message; + } + + @Override + public String getOriginalMessage() { + return originalMessage; + } + + @Override + public void printStackTrace(PrintWriter s) { + super.printStackTrace(s); + DbException.printNextExceptions(this, s); + } + + @Override + public void printStackTrace(PrintStream s) { + super.printStackTrace(s); + DbException.printNextExceptions(this, s); + } + + @Override + public String getSQL() { + return sql; + } + + @Override + public void setSQL(String sql) { + this.sql = sql; + message = DbException.buildMessageForException(this); + } + + @Override + public String toString() { + if (stackTrace == null) { + return super.toString(); + } + return stackTrace; + } + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcSQLTransientException.java b/h2/src/main/org/h2/jdbc/JdbcSQLTransientException.java new file mode 100644 index 0000000..6566d1d --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcSQLTransientException.java @@ -0,0 +1,87 @@ +/* + * 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.jdbc; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.sql.SQLTransientException; + +import org.h2.message.DbException; + +/** + * Represents a database exception. + */ +public final class JdbcSQLTransientException extends SQLTransientException implements JdbcException { + + private static final long serialVersionUID = 1L; + + private final String originalMessage; + private final String stackTrace; + private String message; + private String sql; + + /** + * Creates a SQLTransientException. + * + * @param message the reason + * @param sql the SQL statement + * @param state the SQL state + * @param errorCode the error code + * @param cause the exception that was the reason for this exception + * @param stackTrace the stack trace + */ + public JdbcSQLTransientException(String message, String sql, String state, + int errorCode, Throwable cause, String stackTrace) { + super(message, state, errorCode); + this.originalMessage = message; + this.stackTrace = stackTrace; + // setSQL() also generates message + setSQL(sql); + initCause(cause); + } + + @Override + public String getMessage() { + return message; + } + + @Override + public String getOriginalMessage() { + return originalMessage; + } + + @Override + public void printStackTrace(PrintWriter s) { + super.printStackTrace(s); + DbException.printNextExceptions(this, s); + } + + @Override + public void printStackTrace(PrintStream s) { + super.printStackTrace(s); + DbException.printNextExceptions(this, s); + } + + @Override + public String getSQL() { + return sql; + } + + @Override + public void setSQL(String sql) { + this.sql = sql; + message = DbException.buildMessageForException(this); + } + + @Override + public String toString() { + if (stackTrace == null) { + return super.toString(); + } + return stackTrace; + } + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcSQLXML.java b/h2/src/main/org/h2/jdbc/JdbcSQLXML.java new file mode 100644 index 0000000..83a0a6a --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcSQLXML.java @@ -0,0 +1,270 @@ +/* + * 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.jdbc; + +import java.io.BufferedOutputStream; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.sql.SQLException; +import java.sql.SQLXML; +import java.util.HashMap; +import java.util.Map; + +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.URIResolver; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.sax.TransformerHandler; +import javax.xml.transform.stax.StAXResult; +import javax.xml.transform.stax.StAXSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.h2.message.DbException; +import org.h2.message.TraceObject; +import org.h2.value.Value; +import org.w3c.dom.Node; +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; + +/** + * Represents a SQLXML value. + */ +public final class JdbcSQLXML extends JdbcLob implements SQLXML { + + private static final Map secureFeatureMap = new HashMap<>(); + private static final EntityResolver NOOP_ENTITY_RESOLVER = (pubId, sysId) -> new InputSource(new StringReader("")); + private static final URIResolver NOOP_URI_RESOLVER = (href, base) -> new StreamSource(new StringReader("")); + + static { + secureFeatureMap.put(XMLConstants.FEATURE_SECURE_PROCESSING, true); + secureFeatureMap.put("http://apache.org/xml/features/disallow-doctype-decl", true); + secureFeatureMap.put("http://xml.org/sax/features/external-general-entities", false); + secureFeatureMap.put("http://xml.org/sax/features/external-parameter-entities", false); + secureFeatureMap.put("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + } + + private DOMResult domResult; + + /** + * Underlying stream for SAXResult, StAXResult, and StreamResult. + */ + private Closeable closable; + + /** + * INTERNAL + * @param conn to use + * @param value for this JdbcSQLXML + * @param state of the LOB + * @param id of the trace object + */ + public JdbcSQLXML(JdbcConnection conn, Value value, State state, int id) { + super(conn, value, state, TraceObject.SQLXML, id); + } + + @Override + void checkReadable() throws SQLException, IOException { + checkClosed(); + if (state == State.SET_CALLED) { + if (domResult != null) { + Node node = domResult.getNode(); + domResult = null; + TransformerFactory factory = TransformerFactory.newInstance(); + try { + Transformer transformer = factory.newTransformer(); + DOMSource domSource = new DOMSource(node); + StringWriter stringWriter = new StringWriter(); + StreamResult streamResult = new StreamResult(stringWriter); + transformer.transform(domSource, streamResult); + completeWrite(conn.createClob(new StringReader(stringWriter.toString()), -1)); + } catch (Exception e) { + throw logAndConvert(e); + } + return; + } else if (closable != null) { + closable.close(); + closable = null; + return; + } + throw DbException.getUnsupportedException("Stream setter is not yet closed."); + } + } + + @Override + public InputStream getBinaryStream() throws SQLException { + return super.getBinaryStream(); + } + + @Override + public Reader getCharacterStream() throws SQLException { + return super.getCharacterStream(); + } + + @SuppressWarnings("unchecked") + @Override + public T getSource(Class sourceClass) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode( + "getSource(" + (sourceClass != null ? sourceClass.getSimpleName() + ".class" : "null") + ')'); + } + checkReadable(); + // see https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html + if (sourceClass == null || sourceClass == DOMSource.class) { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + for (Map.Entry entry : secureFeatureMap.entrySet()) { + try { + dbf.setFeature(entry.getKey(), entry.getValue()); + } catch (Exception ignore) {/**/} + } + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); + dbf.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + DocumentBuilder db = dbf.newDocumentBuilder(); + db.setEntityResolver(NOOP_ENTITY_RESOLVER); + return (T) new DOMSource(db.parse(new InputSource(value.getInputStream()))); + } else if (sourceClass == SAXSource.class) { + SAXParserFactory spf = SAXParserFactory.newInstance(); + for (Map.Entry entry : secureFeatureMap.entrySet()) { + try { + spf.setFeature(entry.getKey(), entry.getValue()); + } catch (Exception ignore) {/**/} + } + XMLReader reader = spf.newSAXParser().getXMLReader(); + reader.setEntityResolver(NOOP_ENTITY_RESOLVER); + return (T) new SAXSource(reader, new InputSource(value.getInputStream())); + } else if (sourceClass == StAXSource.class) { + XMLInputFactory xif = XMLInputFactory.newInstance(); + xif.setProperty(XMLInputFactory.SUPPORT_DTD, false); + xif.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + xif.setProperty("javax.xml.stream.isSupportingExternalEntities", false); + return (T) new StAXSource(xif.createXMLStreamReader(value.getInputStream())); + } else if (sourceClass == StreamSource.class) { + TransformerFactory tf = TransformerFactory.newInstance(); + tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); + tf.setURIResolver(NOOP_URI_RESOLVER); + tf.newTransformer().transform(new StreamSource(value.getInputStream()), + new SAXResult(new DefaultHandler())); + return (T) new StreamSource(value.getInputStream()); + } + throw unsupported(sourceClass.getName()); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + @Override + public String getString() throws SQLException { + try { + debugCodeCall("getString"); + checkReadable(); + return value.getString(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + @Override + public OutputStream setBinaryStream() throws SQLException { + try { + debugCodeCall("setBinaryStream"); + checkEditable(); + state = State.SET_CALLED; + return new BufferedOutputStream(setClobOutputStreamImpl()); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + @Override + public Writer setCharacterStream() throws SQLException { + try { + debugCodeCall("setCharacterStream"); + checkEditable(); + state = State.SET_CALLED; + return setCharacterStreamImpl(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + @SuppressWarnings("unchecked") + @Override + public T setResult(Class resultClass) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode( + "setResult(" + (resultClass != null ? resultClass.getSimpleName() + ".class" : "null") + ')'); + } + checkEditable(); + if (resultClass == null || resultClass == DOMResult.class) { + domResult = new DOMResult(); + state = State.SET_CALLED; + return (T) domResult; + } else if (resultClass == SAXResult.class) { + SAXTransformerFactory transformerFactory = (SAXTransformerFactory) TransformerFactory.newInstance(); + TransformerHandler transformerHandler = transformerFactory.newTransformerHandler(); + Writer writer = setCharacterStreamImpl(); + transformerHandler.setResult(new StreamResult(writer)); + SAXResult saxResult = new SAXResult(transformerHandler); + closable = writer; + state = State.SET_CALLED; + return (T) saxResult; + } else if (resultClass == StAXResult.class) { + XMLOutputFactory xof = XMLOutputFactory.newInstance(); + Writer writer = setCharacterStreamImpl(); + StAXResult staxResult = new StAXResult(xof.createXMLStreamWriter(writer)); + closable = writer; + state = State.SET_CALLED; + return (T) staxResult; + } else if (StreamResult.class.equals(resultClass)) { + Writer writer = setCharacterStreamImpl(); + StreamResult streamResult = new StreamResult(writer); + closable = writer; + state = State.SET_CALLED; + return (T) streamResult; + } + throw unsupported(resultClass.getName()); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + @Override + public void setString(String value) throws SQLException { + try { + if (isDebugEnabled()) { + debugCodeCall("getSource", value); + } + checkEditable(); + completeWrite(conn.createClob(new StringReader(value), -1)); + } catch (Exception e) { + throw logAndConvert(e); + } + } + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcSavepoint.java b/h2/src/main/org/h2/jdbc/JdbcSavepoint.java new file mode 100644 index 0000000..f08eabd --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcSavepoint.java @@ -0,0 +1,123 @@ +/* + * 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.jdbc; + +import java.sql.SQLException; +import java.sql.Savepoint; + +import org.h2.api.ErrorCode; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.message.TraceObject; +import org.h2.util.StringUtils; + +/** + * A savepoint is a point inside a transaction to where a transaction can be + * rolled back. The tasks that where done before the savepoint are not rolled + * back in this case. + */ +public final class JdbcSavepoint extends TraceObject implements Savepoint { + + private static final String SYSTEM_SAVEPOINT_PREFIX = "SYSTEM_SAVEPOINT_"; + + private final int savepointId; + private final String name; + private JdbcConnection conn; + + JdbcSavepoint(JdbcConnection conn, int savepointId, String name, + Trace trace, int id) { + setTrace(trace, TraceObject.SAVEPOINT, id); + this.conn = conn; + this.savepointId = savepointId; + this.name = name; + } + + /** + * Release this savepoint. This method only set the connection to null and + * does not execute a statement. + */ + void release() { + this.conn = null; + } + + /** + * Get the savepoint name for this name or id. + * If the name is null, the id is used. + * + * @param name the name (may be null) + * @param id the id + * @return the savepoint name + */ + static String getName(String name, int id) { + if (name != null) { + return StringUtils.quoteJavaString(name); + } + return SYSTEM_SAVEPOINT_PREFIX + id; + } + + /** + * Roll back to this savepoint. + */ + void rollback() { + checkValid(); + conn.prepareCommand( + "ROLLBACK TO SAVEPOINT " + getName(name, savepointId), + Integer.MAX_VALUE).executeUpdate(null); + } + + private void checkValid() { + if (conn == null) { + throw DbException.get(ErrorCode.SAVEPOINT_IS_INVALID_1, + getName(name, savepointId)); + } + } + + /** + * Get the generated id of this savepoint. + * @return the id + */ + @Override + public int getSavepointId() throws SQLException { + try { + debugCodeCall("getSavepointId"); + checkValid(); + if (name != null) { + throw DbException.get(ErrorCode.SAVEPOINT_IS_NAMED); + } + return savepointId; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Get the name of this savepoint. + * @return the name + */ + @Override + public String getSavepointName() throws SQLException { + try { + debugCodeCall("getSavepointName"); + checkValid(); + if (name == null) { + throw DbException.get(ErrorCode.SAVEPOINT_IS_UNNAMED); + } + return name; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * INTERNAL + */ + @Override + public String toString() { + return getTraceObjectName() + ": id=" + savepointId + " name=" + name; + } + + +} diff --git a/h2/src/main/org/h2/jdbc/JdbcStatement.java b/h2/src/main/org/h2/jdbc/JdbcStatement.java new file mode 100644 index 0000000..80ce508 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcStatement.java @@ -0,0 +1,1472 @@ +/* + * 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.jdbc; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; +import java.util.ArrayList; +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Session; +import org.h2.engine.SysProperties; +import org.h2.message.DbException; +import org.h2.message.TraceObject; +import org.h2.result.ResultInterface; +import org.h2.result.ResultWithGeneratedKeys; +import org.h2.result.SimpleResult; +import org.h2.util.ParserUtil; +import org.h2.util.StringUtils; +import org.h2.util.Utils; + +/** + * Represents a statement. + *

+ * Thread safety: the statement is not thread-safe. If the same statement is + * used by multiple threads access to it must be synchronized. The single + * synchronized block must include execution of the command and all operations + * with its result. + *

+ *
+ * synchronized (stat) {
+ *     try (ResultSet rs = stat.executeQuery(queryString)) {
+ *         while (rs.next) {
+ *             // Do something
+ *         }
+ *     }
+ * }
+ * synchronized (stat) {
+ *     updateCount = stat.executeUpdate(commandString);
+ * }
+ * 
+ */ +public class JdbcStatement extends TraceObject implements Statement, JdbcStatementBackwardsCompat { + + protected JdbcConnection conn; + protected Session session; + protected JdbcResultSet resultSet; + protected long maxRows; + protected int fetchSize = SysProperties.SERVER_RESULT_SET_FETCH_SIZE; + protected long updateCount; + protected JdbcResultSet generatedKeys; + protected final int resultSetType; + protected final int resultSetConcurrency; + private volatile CommandInterface executingCommand; + private ArrayList batchCommands; + private boolean escapeProcessing = true; + private volatile boolean cancelled; + private boolean closeOnCompletion; + + JdbcStatement(JdbcConnection conn, int id, int resultSetType, int resultSetConcurrency) { + this.conn = conn; + this.session = conn.getSession(); + setTrace(session.getTrace(), TraceObject.STATEMENT, id); + this.resultSetType = resultSetType; + this.resultSetConcurrency = resultSetConcurrency; + } + + /** + * Executes a query (select statement) and returns the result set. + * If another result set exists for this statement, this will be closed + * (even if this statement fails). + * + * @param sql the SQL statement to execute + * @return the result set + */ + @Override + public ResultSet executeQuery(String sql) throws SQLException { + try { + int id = getNextId(TraceObject.RESULT_SET); + if (isDebugEnabled()) { + debugCodeAssign("ResultSet", TraceObject.RESULT_SET, id, "executeQuery(" + quote(sql) + ')'); + } + synchronized (session) { + checkClosed(); + closeOldResultSet(); + sql = JdbcConnection.translateSQL(sql, escapeProcessing); + CommandInterface command = conn.prepareCommand(sql, fetchSize); + ResultInterface result; + boolean lazy = false; + boolean scrollable = resultSetType != ResultSet.TYPE_FORWARD_ONLY; + boolean updatable = resultSetConcurrency == ResultSet.CONCUR_UPDATABLE; + setExecutingStatement(command); + try { + result = command.executeQuery(maxRows, scrollable); + lazy = result.isLazy(); + } finally { + if (!lazy) { + setExecutingStatement(null); + } + } + if (!lazy) { + command.close(); + } + resultSet = new JdbcResultSet(conn, this, command, result, id, scrollable, updatable, false); + } + return resultSet; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Executes a statement (insert, update, delete, create, drop) + * and returns the update count. This method is not + * allowed for prepared statements. + * If another result set exists for this statement, this will be closed + * (even if this statement fails). + * + * If auto commit is on, this statement will be committed. + * If the statement is a DDL statement (create, drop, alter) and does not + * throw an exception, the current transaction (if any) is committed after + * executing the statement. + * + * @param sql the SQL statement + * @return the update count (number of affected rows by a DML statement or + * other statement able to return number of rows, or 0 if no rows + * were affected or the statement returned nothing, or + * {@link #SUCCESS_NO_INFO} if number of rows is too large for the + * {@code int} data type) + * @throws SQLException if a database error occurred or a + * select statement was executed + * @see #executeLargeUpdate(String) + */ + @Override + public final int executeUpdate(String sql) throws SQLException { + try { + debugCodeCall("executeUpdate", sql); + long updateCount = executeUpdateInternal(sql, null); + return updateCount <= Integer.MAX_VALUE ? (int) updateCount : SUCCESS_NO_INFO; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Executes a statement (insert, update, delete, create, drop) + * and returns the update count. This method is not + * allowed for prepared statements. + * If another result set exists for this statement, this will be closed + * (even if this statement fails). + * + * If auto commit is on, this statement will be committed. + * If the statement is a DDL statement (create, drop, alter) and does not + * throw an exception, the current transaction (if any) is committed after + * executing the statement. + * + * @param sql the SQL statement + * @return the update count (number of affected rows by a DML statement or + * other statement able to return number of rows, or 0 if no rows + * were affected or the statement returned nothing) + * @throws SQLException if a database error occurred or a + * select statement was executed + */ + @Override + public final long executeLargeUpdate(String sql) throws SQLException { + try { + debugCodeCall("executeLargeUpdate", sql); + return executeUpdateInternal(sql, null); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private long executeUpdateInternal(String sql, Object generatedKeysRequest) { + if (getClass() != JdbcStatement.class) { + throw DbException.get(ErrorCode.METHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT); + } + checkClosed(); + closeOldResultSet(); + sql = JdbcConnection.translateSQL(sql, escapeProcessing); + CommandInterface command = conn.prepareCommand(sql, fetchSize); + synchronized (session) { + setExecutingStatement(command); + try { + ResultWithGeneratedKeys result = command.executeUpdate(generatedKeysRequest); + updateCount = result.getUpdateCount(); + ResultInterface gk = result.getGeneratedKeys(); + if (gk != null) { + int id = getNextId(TraceObject.RESULT_SET); + generatedKeys = new JdbcResultSet(conn, this, command, gk, id, true, false, false); + } + } finally { + setExecutingStatement(null); + } + } + command.close(); + return updateCount; + } + + /** + * Executes a statement and returns type of its result. This method is not + * allowed for prepared statements. + * If another result set exists for this + * statement, this will be closed (even if this statement fails). + * + * If the statement is a create or drop and does not throw an exception, the + * current transaction (if any) is committed after executing the statement. + * If auto commit is on, and the statement is not a select, this statement + * will be committed. + * + * @param sql the SQL statement to execute + * @return true if result is a result set, false otherwise + */ + @Override + public final boolean execute(String sql) throws SQLException { + try { + debugCodeCall("execute", sql); + return executeInternal(sql, false); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private boolean executeInternal(String sql, Object generatedKeysRequest) { + if (getClass() != JdbcStatement.class) { + throw DbException.get(ErrorCode.METHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT); + } + int id = getNextId(TraceObject.RESULT_SET); + checkClosed(); + closeOldResultSet(); + sql = JdbcConnection.translateSQL(sql, escapeProcessing); + CommandInterface command = conn.prepareCommand(sql, fetchSize); + boolean lazy = false; + boolean returnsResultSet; + synchronized (session) { + setExecutingStatement(command); + try { + if (command.isQuery()) { + returnsResultSet = true; + boolean scrollable = resultSetType != ResultSet.TYPE_FORWARD_ONLY; + boolean updatable = resultSetConcurrency == ResultSet.CONCUR_UPDATABLE; + ResultInterface result = command.executeQuery(maxRows, scrollable); + lazy = result.isLazy(); + resultSet = new JdbcResultSet(conn, this, command, result, id, scrollable, updatable, false); + } else { + returnsResultSet = false; + ResultWithGeneratedKeys result = command.executeUpdate(generatedKeysRequest); + updateCount = result.getUpdateCount(); + ResultInterface gk = result.getGeneratedKeys(); + if (gk != null) { + generatedKeys = new JdbcResultSet(conn, this, command, gk, id, true, false, false); + } + } + } finally { + if (!lazy) { + setExecutingStatement(null); + } + } + } + if (!lazy) { + command.close(); + } + return returnsResultSet; + } + + /** + * Returns the last result set produces by this statement. + * + * @return the result set + */ + @Override + public ResultSet getResultSet() throws SQLException { + try { + checkClosed(); + if (resultSet != null) { + int id = resultSet.getTraceId(); + debugCodeAssign("ResultSet", TraceObject.RESULT_SET, id, "getResultSet()"); + } else { + debugCodeCall("getResultSet"); + } + return resultSet; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the last update count of this statement. + * + * @return the update count (number of affected rows by a DML statement or + * other statement able to return number of rows, or 0 if no rows + * were affected or the statement returned nothing, or -1 if + * statement was a query, or {@link #SUCCESS_NO_INFO} if number of + * rows is too large for the {@code int} data type) + * @throws SQLException if this object is closed or invalid + * @see #getLargeUpdateCount() + */ + @Override + public final int getUpdateCount() throws SQLException { + try { + debugCodeCall("getUpdateCount"); + checkClosed(); + return updateCount <= Integer.MAX_VALUE ? (int) updateCount : SUCCESS_NO_INFO; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns the last update count of this statement. + * + * @return the update count (number of affected rows by a DML statement or + * other statement able to return number of rows, or 0 if no rows + * were affected or the statement returned nothing, or -1 if + * statement was a query) + * @throws SQLException if this object is closed or invalid + */ + @Override + public final long getLargeUpdateCount() throws SQLException { + try { + debugCodeCall("getLargeUpdateCount"); + checkClosed(); + return updateCount; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Closes this statement. + * All result sets that where created by this statement + * become invalid after calling this method. + */ + @Override + public void close() throws SQLException { + try { + debugCodeCall("close"); + closeInternal(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private void closeInternal() { + synchronized (session) { + closeOldResultSet(); + if (conn != null) { + conn = null; + } + } + } + + /** + * Returns the connection that created this object. + * + * @return the connection + */ + @Override + public Connection getConnection() { + debugCodeCall("getConnection"); + return conn; + } + + /** + * Gets the first warning reported by calls on this object. + * This driver does not support warnings, and will always return null. + * + * @return null + */ + @Override + public SQLWarning getWarnings() throws SQLException { + try { + debugCodeCall("getWarnings"); + checkClosed(); + return null; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Clears all warnings. As this driver does not support warnings, + * this call is ignored. + */ + @Override + public void clearWarnings() throws SQLException { + try { + debugCodeCall("clearWarnings"); + checkClosed(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the name of the cursor. This call is ignored. + * + * @param name ignored + * @throws SQLException if this object is closed + */ + @Override + public void setCursorName(String name) throws SQLException { + try { + debugCodeCall("setCursorName", name); + checkClosed(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the fetch direction. + * This call is ignored by this driver. + * + * @param direction ignored + * @throws SQLException if this object is closed + */ + @Override + public void setFetchDirection(int direction) throws SQLException { + try { + debugCodeCall("setFetchDirection", direction); + checkClosed(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the fetch direction. + * + * @return FETCH_FORWARD + * @throws SQLException if this object is closed + */ + @Override + public int getFetchDirection() throws SQLException { + try { + debugCodeCall("getFetchDirection"); + checkClosed(); + return ResultSet.FETCH_FORWARD; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the maximum number of rows for a ResultSet. + * + * @return the number of rows where 0 means no limit + * @throws SQLException if this object is closed + */ + @Override + public int getMaxRows() throws SQLException { + try { + debugCodeCall("getMaxRows"); + checkClosed(); + return maxRows <= Integer.MAX_VALUE ? (int) maxRows : 0; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the maximum number of rows for a ResultSet. + * + * @return the number of rows where 0 means no limit + * @throws SQLException if this object is closed + */ + @Override + public long getLargeMaxRows() throws SQLException { + try { + debugCodeCall("getLargeMaxRows"); + checkClosed(); + return maxRows; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the maximum number of rows for a ResultSet. + * + * @param maxRows the number of rows where 0 means no limit + * @throws SQLException if this object is closed + */ + @Override + public void setMaxRows(int maxRows) throws SQLException { + try { + debugCodeCall("setMaxRows", maxRows); + checkClosed(); + if (maxRows < 0) { + throw DbException.getInvalidValueException("maxRows", maxRows); + } + this.maxRows = maxRows; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the maximum number of rows for a ResultSet. + * + * @param maxRows the number of rows where 0 means no limit + * @throws SQLException if this object is closed + */ + @Override + public void setLargeMaxRows(long maxRows) throws SQLException { + try { + debugCodeCall("setLargeMaxRows", maxRows); + checkClosed(); + if (maxRows < 0) { + throw DbException.getInvalidValueException("maxRows", maxRows); + } + this.maxRows = maxRows; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the number of rows suggested to read in one step. + * This value cannot be higher than the maximum rows (setMaxRows) + * set by the statement or prepared statement, otherwise an exception + * is throws. Setting the value to 0 will set the default value. + * The default value can be changed using the system property + * h2.serverResultSetFetchSize. + * + * @param rows the number of rows + * @throws SQLException if this object is closed + */ + @Override + public void setFetchSize(int rows) throws SQLException { + try { + debugCodeCall("setFetchSize", rows); + checkClosed(); + if (rows < 0 || (rows > 0 && maxRows > 0 && rows > maxRows)) { + throw DbException.getInvalidValueException("rows", rows); + } + if (rows == 0) { + rows = SysProperties.SERVER_RESULT_SET_FETCH_SIZE; + } + fetchSize = rows; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the number of rows suggested to read in one step. + * + * @return the current fetch size + * @throws SQLException if this object is closed + */ + @Override + public int getFetchSize() throws SQLException { + try { + debugCodeCall("getFetchSize"); + checkClosed(); + return fetchSize; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the result set concurrency created by this object. + * + * @return the concurrency + */ + @Override + public int getResultSetConcurrency() throws SQLException { + try { + debugCodeCall("getResultSetConcurrency"); + checkClosed(); + return resultSetConcurrency; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the result set type. + * + * @return the type + * @throws SQLException if this object is closed + */ + @Override + public int getResultSetType() throws SQLException { + try { + debugCodeCall("getResultSetType"); + checkClosed(); + return resultSetType; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the maximum number of bytes for a result set column. + * + * @return always 0 for no limit + * @throws SQLException if this object is closed + */ + @Override + public int getMaxFieldSize() throws SQLException { + try { + debugCodeCall("getMaxFieldSize"); + checkClosed(); + return 0; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the maximum number of bytes for a result set column. + * This method does currently do nothing for this driver. + * + * @param max the maximum size - ignored + * @throws SQLException if this object is closed + */ + @Override + public void setMaxFieldSize(int max) throws SQLException { + try { + debugCodeCall("setMaxFieldSize", max); + checkClosed(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Enables or disables processing or JDBC escape syntax. + * See also Connection.nativeSQL. + * + * @param enable - true (default) or false (no conversion is attempted) + * @throws SQLException if this object is closed + */ + @Override + public void setEscapeProcessing(boolean enable) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("setEscapeProcessing(" + enable + ')'); + } + checkClosed(); + escapeProcessing = enable; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Cancels a currently running statement. + * This method must be called from within another + * thread than the execute method. + * Operations on large objects are not interrupted, + * only operations that process many rows. + * + * @throws SQLException if this object is closed + */ + @Override + public void cancel() throws SQLException { + try { + debugCodeCall("cancel"); + checkClosed(); + // executingCommand can be reset by another thread + CommandInterface c = executingCommand; + try { + if (c != null) { + c.cancel(); + cancelled = true; + } + } finally { + setExecutingStatement(null); + } + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Check whether the statement was cancelled. + * + * @return true if yes + */ + public boolean isCancelled() { + return cancelled; + } + + /** + * Gets the current query timeout in seconds. + * This method will return 0 if no query timeout is set. + * The result is rounded to the next second. + * For performance reasons, only the first call to this method + * will query the database. If the query timeout was changed in another + * way than calling setQueryTimeout, this method will always return + * the last value. + * + * @return the timeout in seconds + * @throws SQLException if this object is closed + */ + @Override + public int getQueryTimeout() throws SQLException { + try { + debugCodeCall("getQueryTimeout"); + checkClosed(); + return conn.getQueryTimeout(); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Sets the current query timeout in seconds. + * Changing the value will affect all statements of this connection. + * This method does not commit a transaction, + * and rolling back a transaction does not affect this setting. + * + * @param seconds the timeout in seconds - 0 means no timeout, values + * smaller 0 will throw an exception + * @throws SQLException if this object is closed + */ + @Override + public void setQueryTimeout(int seconds) throws SQLException { + try { + debugCodeCall("setQueryTimeout", seconds); + checkClosed(); + if (seconds < 0) { + throw DbException.getInvalidValueException("seconds", seconds); + } + conn.setQueryTimeout(seconds); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Adds a statement to the batch. + * + * @param sql the SQL statement + */ + @Override + public void addBatch(String sql) throws SQLException { + try { + debugCodeCall("addBatch", sql); + checkClosed(); + sql = JdbcConnection.translateSQL(sql, escapeProcessing); + if (batchCommands == null) { + batchCommands = Utils.newSmallArrayList(); + } + batchCommands.add(sql); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Clears the batch. + */ + @Override + public void clearBatch() throws SQLException { + try { + debugCodeCall("clearBatch"); + checkClosed(); + batchCommands = null; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Executes the batch. + * If one of the batched statements fails, this database will continue. + * + * @return the array of update counts + * @see #executeLargeBatch() + */ + @Override + public int[] executeBatch() throws SQLException { + try { + debugCodeCall("executeBatch"); + checkClosed(); + if (batchCommands == null) { + batchCommands = new ArrayList<>(); + } + int size = batchCommands.size(); + int[] result = new int[size]; + SQLException exception = new SQLException(); + for (int i = 0; i < size; i++) { + long updateCount = executeBatchElement(batchCommands.get(i), exception); + result[i] = updateCount <= Integer.MAX_VALUE ? (int) updateCount : SUCCESS_NO_INFO; + } + batchCommands = null; + exception = exception.getNextException(); + if (exception != null) { + throw new JdbcBatchUpdateException(exception, result); + } + return result; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Executes the batch. + * If one of the batched statements fails, this database will continue. + * + * @return the array of update counts + */ + @Override + public long[] executeLargeBatch() throws SQLException { + try { + debugCodeCall("executeLargeBatch"); + checkClosed(); + if (batchCommands == null) { + batchCommands = new ArrayList<>(); + } + int size = batchCommands.size(); + long[] result = new long[size]; + SQLException exception = new SQLException(); + for (int i = 0; i < size; i++) { + result[i] = executeBatchElement(batchCommands.get(i), exception); + } + batchCommands = null; + exception = exception.getNextException(); + if (exception != null) { + throw new JdbcBatchUpdateException(exception, result); + } + return result; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private long executeBatchElement(String sql, SQLException exception) { + long updateCount; + try { + updateCount = executeUpdateInternal(sql, null); + } catch (Exception e) { + exception.setNextException(logAndConvert(e)); + updateCount = Statement.EXECUTE_FAILED; + } + return updateCount; + } + + /** + * Return a result set with generated keys from the latest executed command + * or an empty result set if keys were not generated or were not requested + * with {@link Statement#RETURN_GENERATED_KEYS}, column indexes, or column + * names. + *

+ * Generated keys are only returned from from {@code INSERT}, + * {@code UPDATE}, {@code MERGE INTO}, and {@code MERGE INTO ... USING} + * commands. + *

+ *

+ * If SQL command inserts or updates multiple rows with generated keys each + * such inserted or updated row is returned. Batch methods are also + * supported. + *

+ *

+ * When {@link Statement#RETURN_GENERATED_KEYS} is used H2 chooses columns + * to return automatically. The following columns are chosen: + *

+ *
    + *
  • Columns with sequences including {@code IDENTITY} columns and columns + * with {@code AUTO_INCREMENT}.
  • + *
  • Columns with other default values that are not evaluated into + * constant expressions (like {@code DEFAULT RANDOM_UUID()}).
  • + *
  • Columns that are included into the PRIMARY KEY constraint.
  • + *
+ *

+ * Exact required columns for the returning result set may be specified on + * execution of command with names or indexes of columns. + *

+ * + * @return the possibly empty result set with generated keys + * @throws SQLException if this object is closed + */ + @Override + public ResultSet getGeneratedKeys() throws SQLException { + try { + int id = generatedKeys != null ? generatedKeys.getTraceId() : getNextId(TraceObject.RESULT_SET); + if (isDebugEnabled()) { + debugCodeAssign("ResultSet", TraceObject.RESULT_SET, id, "getGeneratedKeys()"); + } + checkClosed(); + if (generatedKeys == null) { + generatedKeys = new JdbcResultSet(conn, this, null, new SimpleResult(), id, true, false, false); + } + return generatedKeys; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Moves to the next result set - however there is always only one result + * set. This call also closes the current result set (if there is one). + * Returns true if there is a next result set (that means - it always + * returns false). + * + * @return false + * @throws SQLException if this object is closed. + */ + @Override + public boolean getMoreResults() throws SQLException { + try { + debugCodeCall("getMoreResults"); + checkClosed(); + closeOldResultSet(); + return false; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Move to the next result set. + * This method always returns false. + * + * @param current Statement.CLOSE_CURRENT_RESULT, + * Statement.KEEP_CURRENT_RESULT, + * or Statement.CLOSE_ALL_RESULTS + * @return false + */ + @Override + public boolean getMoreResults(int current) throws SQLException { + try { + debugCodeCall("getMoreResults", current); + switch (current) { + case Statement.CLOSE_CURRENT_RESULT: + case Statement.CLOSE_ALL_RESULTS: + checkClosed(); + closeOldResultSet(); + break; + case Statement.KEEP_CURRENT_RESULT: + // nothing to do + break; + default: + throw DbException.getInvalidValueException("current", current); + } + return false; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Executes a statement and returns the update count. This method is not + * allowed for prepared statements. + * + * @param sql the SQL statement + * @param autoGeneratedKeys + * {@link Statement#RETURN_GENERATED_KEYS} if generated keys should + * be available for retrieval, {@link Statement#NO_GENERATED_KEYS} if + * generated keys should not be available + * @return the update count (number of affected rows by a DML statement or + * other statement able to return number of rows, or 0 if no rows + * were affected or the statement returned nothing, or + * {@link #SUCCESS_NO_INFO} if number of rows is too large for the + * {@code int} data type) + * @throws SQLException if a database error occurred or a + * select statement was executed + * @see #executeLargeUpdate(String, int) + */ + @Override + public final int executeUpdate(String sql, int autoGeneratedKeys) + throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("executeUpdate(" + quote(sql) + ", " + autoGeneratedKeys + ')'); + } + long updateCount = executeUpdateInternal(sql, autoGeneratedKeys == RETURN_GENERATED_KEYS); + return updateCount <= Integer.MAX_VALUE ? (int) updateCount : SUCCESS_NO_INFO; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Executes a statement and returns the update count. This method is not + * allowed for prepared statements. + * + * @param sql the SQL statement + * @param autoGeneratedKeys + * {@link Statement#RETURN_GENERATED_KEYS} if generated keys should + * be available for retrieval, {@link Statement#NO_GENERATED_KEYS} if + * generated keys should not be available + * @return the update count (number of affected rows by a DML statement or + * other statement able to return number of rows, or 0 if no rows + * were affected or the statement returned nothing) + * @throws SQLException if a database error occurred or a + * select statement was executed + */ + @Override + public final long executeLargeUpdate(String sql, int autoGeneratedKeys) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("executeLargeUpdate(" + quote(sql) + ", " + autoGeneratedKeys + ')'); + } + return executeUpdateInternal(sql, autoGeneratedKeys == RETURN_GENERATED_KEYS); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Executes a statement and returns the update count. This method is not + * allowed for prepared statements. + * + * @param sql the SQL statement + * @param columnIndexes + * an array of column indexes indicating the columns with generated + * keys that should be returned from the inserted row + * @return the update count (number of affected rows by a DML statement or + * other statement able to return number of rows, or 0 if no rows + * were affected or the statement returned nothing, or + * {@link #SUCCESS_NO_INFO} if number of rows is too large for the + * {@code int} data type) + * @throws SQLException if a database error occurred or a + * select statement was executed + * @see #executeLargeUpdate(String, int[]) + */ + @Override + public final int executeUpdate(String sql, int[] columnIndexes) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("executeUpdate(" + quote(sql) + ", " + quoteIntArray(columnIndexes) + ')'); + } + long updateCount = executeUpdateInternal(sql, columnIndexes); + return updateCount <= Integer.MAX_VALUE ? (int) updateCount : SUCCESS_NO_INFO; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Executes a statement and returns the update count. This method is not + * allowed for prepared statements. + * + * @param sql the SQL statement + * @param columnIndexes + * an array of column indexes indicating the columns with generated + * keys that should be returned from the inserted row + * @return the update count (number of affected rows by a DML statement or + * other statement able to return number of rows, or 0 if no rows + * were affected or the statement returned nothing) + * @throws SQLException if a database error occurred or a + * select statement was executed + */ + @Override + public final long executeLargeUpdate(String sql, int columnIndexes[]) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("executeLargeUpdate(" + quote(sql) + ", " + quoteIntArray(columnIndexes) + ')'); + } + return executeUpdateInternal(sql, columnIndexes); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Executes a statement and returns the update count. This method is not + * allowed for prepared statements. + * + * @param sql the SQL statement + * @param columnNames + * an array of column names indicating the columns with generated + * keys that should be returned from the inserted row + * @return the update count (number of affected rows by a DML statement or + * other statement able to return number of rows, or 0 if no rows + * were affected or the statement returned nothing, or + * {@link #SUCCESS_NO_INFO} if number of rows is too large for the + * {@code int} data type) + * @throws SQLException if a database error occurred or a + * select statement was executed + * @see #executeLargeUpdate(String, String[]) + */ + @Override + public final int executeUpdate(String sql, String[] columnNames) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("executeUpdate(" + quote(sql) + ", " + quoteArray(columnNames) + ')'); + } + long updateCount = executeUpdateInternal(sql, columnNames); + return updateCount <= Integer.MAX_VALUE ? (int) updateCount : SUCCESS_NO_INFO; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Executes a statement and returns the update count. This method is not + * allowed for prepared statements. + * + * @param sql the SQL statement + * @param columnNames + * an array of column names indicating the columns with generated + * keys that should be returned from the inserted row + * @return the update count (number of row affected by an insert, + * update or delete, or 0 if no rows or the statement was a + * create, drop, commit or rollback) + * @throws SQLException if a database error occurred or a + * select statement was executed + */ + @Override + public final long executeLargeUpdate(String sql, String columnNames[]) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("executeLargeUpdate(" + quote(sql) + ", " + quoteArray(columnNames) + ')'); + } + return executeUpdateInternal(sql, columnNames); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Executes a statement and returns type of its result. This method is not + * allowed for prepared statements. + * + * @param sql the SQL statement + * @param autoGeneratedKeys + * {@link Statement#RETURN_GENERATED_KEYS} if generated keys should + * be available for retrieval, {@link Statement#NO_GENERATED_KEYS} if + * generated keys should not be available + * @return true if result is a result set, false otherwise + * @throws SQLException if a database error occurred or a + * select statement was executed + */ + @Override + public final boolean execute(String sql, int autoGeneratedKeys) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("execute(" + quote(sql) + ", " + autoGeneratedKeys + ')'); + } + return executeInternal(sql, autoGeneratedKeys == RETURN_GENERATED_KEYS); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Executes a statement and returns type of its result. This method is not + * allowed for prepared statements. + * + * @param sql the SQL statement + * @param columnIndexes + * an array of column indexes indicating the columns with generated + * keys that should be returned from the inserted row + * @return true if result is a result set, false otherwise + * @throws SQLException if a database error occurred or a + * select statement was executed + */ + @Override + public final boolean execute(String sql, int[] columnIndexes) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("execute(" + quote(sql) + ", " + quoteIntArray(columnIndexes) + ')'); + } + return executeInternal(sql, columnIndexes); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Executes a statement and returns type of its result. This method is not + * allowed for prepared statements. + * + * @param sql the SQL statement + * @param columnNames + * an array of column names indicating the columns with generated + * keys that should be returned from the inserted row + * @return true if result is a result set, false otherwise + * @throws SQLException if a database error occurred or a + * select statement was executed + */ + @Override + public final boolean execute(String sql, String[] columnNames) throws SQLException { + try { + if (isDebugEnabled()) { + debugCode("execute(" + quote(sql) + ", " + quoteArray(columnNames) + ')'); + } + return executeInternal(sql, columnNames); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Gets the result set holdability. + * + * @return the holdability + */ + @Override + public int getResultSetHoldability() throws SQLException { + try { + debugCodeCall("getResultSetHoldability"); + checkClosed(); + return ResultSet.HOLD_CURSORS_OVER_COMMIT; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Specifies that this statement will be closed when its dependent result + * set is closed. + * + * @throws SQLException + * if this statement is closed + */ + @Override + public void closeOnCompletion() throws SQLException { + try { + debugCodeCall("closeOnCompletion"); + checkClosed(); + closeOnCompletion = true; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Returns whether this statement will be closed when its dependent result + * set is closed. + * + * @return {@code true} if this statement will be closed when its dependent + * result set is closed + * @throws SQLException + * if this statement is closed + */ + @Override + public boolean isCloseOnCompletion() throws SQLException { + try { + debugCodeCall("isCloseOnCompletion"); + checkClosed(); + return closeOnCompletion; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + void closeIfCloseOnCompletion() { + if (closeOnCompletion) { + try { + closeInternal(); + } catch (Exception e) { + // Don't re-throw + logAndConvert(e); + } + } + } + + // ============================================================= + + /** + * Check if this connection is closed. + * + * @throws DbException if the connection or session is closed + */ + void checkClosed() { + if (conn == null) { + throw DbException.get(ErrorCode.OBJECT_CLOSED); + } + conn.checkClosed(); + } + + /** + * INTERNAL. + * Close and old result set if there is still one open. + */ + protected void closeOldResultSet() { + try { + if (resultSet != null) { + resultSet.closeInternal(true); + } + if (generatedKeys != null) { + generatedKeys.closeInternal(true); + } + } finally { + cancelled = false; + resultSet = null; + updateCount = -1; + generatedKeys = null; + } + } + + /** + * INTERNAL. + * Set the statement that is currently running. + * + * @param c the command + */ + void setExecutingStatement(CommandInterface c) { + if (c == null) { + conn.setExecutingStatement(null); + } else { + conn.setExecutingStatement(this); + } + executingCommand = c; + } + + /** + * Called when the result set is closed. + * + * @param command the command + * @param closeCommand whether to close the command + */ + void onLazyResultSetClose(CommandInterface command, boolean closeCommand) { + setExecutingStatement(null); + command.stop(); + if (closeCommand) { + command.close(); + } + } + + /** + * Returns whether this statement is closed. + * + * @return true if the statement is closed + */ + @Override + public boolean isClosed() throws SQLException { + try { + debugCodeCall("isClosed"); + return conn == null; + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Return an object of this class if possible. + * + * @param iface the class + * @return this + */ + @Override + @SuppressWarnings("unchecked") + public T unwrap(Class iface) throws SQLException { + try { + if (isWrapperFor(iface)) { + return (T) this; + } + throw DbException.getInvalidValueException("iface", iface); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if unwrap can return an object of this class. + * + * @param iface the class + * @return whether or not the interface is assignable from this class + */ + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return iface != null && iface.isAssignableFrom(getClass()); + } + + /** + * Returns whether this object is poolable. + * @return false + */ + @Override + public boolean isPoolable() { + debugCodeCall("isPoolable"); + return false; + } + + /** + * Requests that this object should be pooled or not. + * This call is ignored. + * + * @param poolable the requested value + */ + @Override + public void setPoolable(boolean poolable) { + if (isDebugEnabled()) { + debugCode("setPoolable(" + poolable + ')'); + } + } + + /** + * @param identifier + * identifier to quote if required, may be quoted or unquoted + * @param alwaysQuote + * if {@code true} identifier will be quoted unconditionally + * @return specified identifier quoted if required, explicitly requested, or + * if it was already quoted + * @throws NullPointerException + * if identifier is {@code null} + * @throws SQLException + * if identifier is not a valid identifier + */ + @Override + public String enquoteIdentifier(String identifier, boolean alwaysQuote) throws SQLException { + if (isSimpleIdentifier(identifier)) { + return alwaysQuote ? '"' + identifier + '"': identifier; + } + try { + int length = identifier.length(); + if (length > 0) { + if (identifier.charAt(0) == '"') { + checkQuotes(identifier, 1, length); + return identifier; + } else if (identifier.startsWith("U&\"") || identifier.startsWith("u&\"")) { + // Check validity of double quotes + checkQuotes(identifier, 3, length); + // Check validity of escape sequences + StringUtils.decodeUnicodeStringSQL(identifier, '\\'); + return identifier; + } + } + return StringUtils.quoteIdentifier(identifier); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + private static void checkQuotes(String identifier, int offset, int length) { + boolean quoted = true; + for (int i = offset; i < length; i++) { + if (identifier.charAt(i) == '"') { + quoted = !quoted; + } else if (!quoted) { + throw DbException.get(ErrorCode.INVALID_NAME_1, identifier); + } + } + if (quoted) { + throw DbException.get(ErrorCode.INVALID_NAME_1, identifier); + } + } + + /** + * @param identifier + * identifier to check + * @return is specified identifier may be used without quotes + * @throws NullPointerException + * if identifier is {@code null} + */ + @Override + public boolean isSimpleIdentifier(String identifier) throws SQLException { + Session.StaticSettings settings; + try { + checkClosed(); + settings = conn.getStaticSettings(); + } catch (Exception e) { + throw logAndConvert(e); + } + return ParserUtil.isSimpleIdentifier(identifier, settings.databaseToUpper, settings.databaseToLower); + } + + /** + * INTERNAL + */ + @Override + public String toString() { + return getTraceObjectName(); + } + +} + diff --git a/h2/src/main/org/h2/jdbc/JdbcStatementBackwardsCompat.java b/h2/src/main/org/h2/jdbc/JdbcStatementBackwardsCompat.java new file mode 100644 index 0000000..5406337 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/JdbcStatementBackwardsCompat.java @@ -0,0 +1,41 @@ +/* + * 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.jdbc; + +import java.sql.SQLException; + +/** + * Allows us to compile on older platforms, while still implementing the methods + * from the newer JDBC API. + */ +public interface JdbcStatementBackwardsCompat { + + // compatibility interface + + // JDBC 4.3 (incomplete) + + /** + * Enquotes the specified identifier. + * + * @param identifier + * identifier to quote if required + * @param alwaysQuote + * if {@code true} identifier will be quoted unconditionally + * @return specified identifier quoted if required or explicitly requested + * @throws SQLException on failure + */ + String enquoteIdentifier(String identifier, boolean alwaysQuote) throws SQLException; + + /** + * Checks if specified identifier may be used without quotes. + * + * @param identifier + * identifier to check + * @return is specified identifier may be used without quotes + * @throws SQLException on failure + */ + boolean isSimpleIdentifier(String identifier) throws SQLException; +} diff --git a/h2/src/main/org/h2/jdbc/meta/DatabaseMeta.java b/h2/src/main/org/h2/jdbc/meta/DatabaseMeta.java new file mode 100644 index 0000000..0b7da24 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/meta/DatabaseMeta.java @@ -0,0 +1,395 @@ +/* + * 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.jdbc.meta; + +import org.h2.mode.DefaultNullOrdering; +import org.h2.result.ResultInterface; + +/** + * Database meta information. + */ +public abstract class DatabaseMeta { + + /** + * INTERNAL + * + * @see java.sql.DatabaseMetaData#nullsAreSortedHigh() + * @see java.sql.DatabaseMetaData#nullsAreSortedLow() + * @see java.sql.DatabaseMetaData#nullsAreSortedAtStart() + * @see java.sql.DatabaseMetaData#nullsAreSortedAtEnd() + * @return DefaultNullOrdering + */ + public abstract DefaultNullOrdering defaultNullOrdering(); + + /** + * INTERNAL + * + * @see java.sql.DatabaseMetaData#getDatabaseProductVersion() + * @return product version as String + */ + public abstract String getDatabaseProductVersion(); + + /** + * INTERNAL + * + * @see java.sql.DatabaseMetaData#getSQLKeywords() + * @return list of supported SQL keywords + */ + public abstract String getSQLKeywords(); + + /** + * INTERNAL + * + * @see java.sql.DatabaseMetaData#getNumericFunctions() + * @return list of supported numeric functions + */ + public abstract String getNumericFunctions(); + + /** + * INTERNAL + * + * @see java.sql.DatabaseMetaData#getStringFunctions() + * @return list of supported string functions + */ + public abstract String getStringFunctions(); + + /** + * INTERNAL + * + * @see java.sql.DatabaseMetaData#getSystemFunctions() + * @return list of supported system functions + */ + public abstract String getSystemFunctions(); + + /** + * INTERNAL + * + * @see java.sql.DatabaseMetaData#getTimeDateFunctions() + * @return list of supported time/date functions + */ + public abstract String getTimeDateFunctions(); + + /** + * INTERNAL + * + * @see java.sql.DatabaseMetaData#getSearchStringEscape() + * @return search string escape sequence + */ + public abstract String getSearchStringEscape(); + + /** + * INTERNAL + * @param catalog to inspect + * @param schemaPattern "LIKE" style pattern to filter result + * @param procedureNamePattern "LIKE" style pattern to filter result + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getProcedures(String, String, String) + */ + public abstract ResultInterface getProcedures(String catalog, String schemaPattern, String procedureNamePattern); + + /** + * INTERNAL + * @param catalog to inspect + * @param schemaPattern "LIKE" style pattern to filter result + * @param procedureNamePattern "LIKE" style pattern to filter result + * @param columnNamePattern "LIKE" style pattern to filter result + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getProcedureColumns(String, String, + * String, String) + */ + public abstract ResultInterface getProcedureColumns(String catalog, String schemaPattern, + String procedureNamePattern, String columnNamePattern); + + /** + * INTERNAL + * @param catalog to inspect + * @param schemaPattern "LIKE" style pattern to filter result + * @param tableNamePattern "LIKE" style pattern to filter result + * @param types String[] + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getTables(String, String, String, + * String[]) + */ + public abstract ResultInterface getTables(String catalog, String schemaPattern, String tableNamePattern, + String[] types); + + /** + * INTERNAL + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getSchemas() + */ + public abstract ResultInterface getSchemas(); + + /** + * INTERNAL + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getCatalogs() + */ + public abstract ResultInterface getCatalogs(); + + /** + * INTERNAL + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getTableTypes() + */ + public abstract ResultInterface getTableTypes(); + + /** + * INTERNAL + * @param catalog to inspect + * @param schemaPattern "LIKE" style pattern to filter result + * @param tableNamePattern "LIKE" style pattern to filter result + * @param columnNamePattern "LIKE" style pattern to filter result + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getColumns(String, String, String, String) + */ + public abstract ResultInterface getColumns(String catalog, String schemaPattern, String tableNamePattern, + String columnNamePattern); + + /** + * INTERNAL + * @param catalog to inspect + * @param schema to inspect + * @param table to inspect + * @param columnNamePattern "LIKE" style pattern to filter result + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getColumnPrivileges(String, String, + * String, String) + */ + public abstract ResultInterface getColumnPrivileges(String catalog, String schema, String table, + String columnNamePattern); + + /** + * INTERNAL + * @param catalog to inspect + * @param schemaPattern "LIKE" style pattern to filter result + * @param tableNamePattern "LIKE" style pattern to filter result + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getTablePrivileges(String, String, String) + */ + public abstract ResultInterface getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern); + + /** + * INTERNAL + * @param catalogPattern "LIKE" style pattern to filter result + * @param schemaPattern "LIKE" style pattern to filter result + * @param tableName table of interest + * @param scope of interest + * @param nullable include nullable columns + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getBestRowIdentifier(String, String, + * String, int, boolean) + */ + public abstract ResultInterface getBestRowIdentifier(String catalogPattern, String schemaPattern, String tableName, + int scope, boolean nullable); + + /** + * INTERNAL + * @param catalog to inspect + * @param schema to inspect + * @param table to inspect + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getVersionColumns(String, String, String) + */ + public abstract ResultInterface getVersionColumns(String catalog, String schema, String table); + + /** + * INTERNAL + * @param catalog to inspect + * @param schema to inspect + * @param table to inspect + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getPrimaryKeys(String, String, String) + */ + public abstract ResultInterface getPrimaryKeys(String catalog, String schema, String table); + + /** + * INTERNAL + * @param catalog to inspect + * @param schema to inspect + * @param table to inspect + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getImportedKeys(String, String, String) + */ + public abstract ResultInterface getImportedKeys(String catalog, String schema, String table); + + /** + * INTERNAL + * @param catalog to inspect + * @param schema to inspect + * @param table to inspect + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getExportedKeys(String, String, String) + */ + public abstract ResultInterface getExportedKeys(String catalog, String schema, String table); + + /** + * INTERNAL + * @param primaryCatalog to inspect + * @param primarySchema to inspect + * @param primaryTable to inspect + * @param foreignCatalog to inspect + * @param foreignSchema to inspect + * @param foreignTable to inspect + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getCrossReference(String, String, String, + * String, String, String) + */ + public abstract ResultInterface getCrossReference(String primaryCatalog, String primarySchema, String primaryTable, + String foreignCatalog, String foreignSchema, String foreignTable); + + /** + * INTERNAL + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getTypeInfo() + */ + public abstract ResultInterface getTypeInfo(); + + /** + * INTERNAL + * @param catalog to inspect + * @param schema to inspect + * @param table to inspect + * @param unique only + * @param approximate allowed + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getIndexInfo(String, String, String, + * boolean, boolean) + */ + public abstract ResultInterface getIndexInfo(String catalog, String schema, String table, boolean unique, + boolean approximate); + + /** + * INTERNAL + * @param catalog to inspect + * @param schemaPattern "LIKE" style pattern to filter result + * @param typeNamePattern "LIKE" style pattern to filter result + * @param types int[] + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getUDTs(String, String, String, int[]) + */ + public abstract ResultInterface getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types); + + /** + * INTERNAL + * @param catalog to inspect + * @param schemaPattern "LIKE" style pattern to filter result + * @param typeNamePattern "LIKE" style pattern to filter result + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getSuperTypes(String, String, String) + */ + public abstract ResultInterface getSuperTypes(String catalog, String schemaPattern, String typeNamePattern); + + /** + * INTERNAL + * @param catalog to inspect + * @param schemaPattern "LIKE" style pattern to filter result + * @param tableNamePattern "LIKE" style pattern to filter result + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getSuperTables(String, String, String) + */ + public abstract ResultInterface getSuperTables(String catalog, String schemaPattern, String tableNamePattern); + + /** + * INTERNAL + * @param catalog to inspect + * @param schemaPattern "LIKE" style pattern to filter result + * @param typeNamePattern "LIKE" style pattern to filter result + * @param attributeNamePattern "LIKE" style pattern to filter result + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getAttributes(String, String, String, + * String) + */ + public abstract ResultInterface getAttributes(String catalog, String schemaPattern, String typeNamePattern, + String attributeNamePattern); + + /** + * INTERNAL + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getDatabaseMajorVersion() + */ + public abstract int getDatabaseMajorVersion(); + + /** + * INTERNAL + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getDatabaseMinorVersion() + */ + public abstract int getDatabaseMinorVersion(); + + /** + * INTERNAL + * @param catalog to inspect + * @param schemaPattern "LIKE" style pattern to filter result + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getSchemas(String, String) + */ + public abstract ResultInterface getSchemas(String catalog, String schemaPattern); + + /** + * INTERNAL + * @param catalog to inspect + * @param schemaPattern "LIKE" style pattern to filter result + * @param functionNamePattern "LIKE" style pattern to filter result + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getFunctions(String, String, String) + */ + public abstract ResultInterface getFunctions(String catalog, String schemaPattern, String functionNamePattern); + + /** + * INTERNAL + * @param catalog to inspect + * @param schemaPattern "LIKE" style pattern to filter result + * @param functionNamePattern "LIKE" style pattern to filter result + * @param columnNamePattern "LIKE" style pattern to filter result + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getFunctionColumns(String, String, String, + * String) + */ + public abstract ResultInterface getFunctionColumns(String catalog, String schemaPattern, // + String functionNamePattern, String columnNamePattern); + + /** + * INTERNAL + * @param catalog to inspect + * @param schemaPattern "LIKE" style pattern to filter result + * @param tableNamePattern "LIKE" style pattern to filter result + * @param columnNamePattern "LIKE" style pattern to filter result + * @return ResultInterface + * + * @see java.sql.DatabaseMetaData#getPseudoColumns(String, String, String, + * String) + */ + public abstract ResultInterface getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, + String columnNamePattern); + +} diff --git a/h2/src/main/org/h2/jdbc/meta/DatabaseMetaLegacy.java b/h2/src/main/org/h2/jdbc/meta/DatabaseMetaLegacy.java new file mode 100644 index 0000000..c33ff10 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/meta/DatabaseMetaLegacy.java @@ -0,0 +1,691 @@ +/* + * 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.jdbc.meta; + +import java.sql.DatabaseMetaData; +import java.util.ArrayList; +import java.util.Arrays; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.Constants; +import org.h2.engine.Session; +import org.h2.expression.ParameterInterface; +import org.h2.message.DbException; +import org.h2.mode.DefaultNullOrdering; +import org.h2.result.ResultInterface; +import org.h2.util.StringUtils; +import org.h2.value.Value; +import org.h2.value.ValueInteger; +import org.h2.value.ValueNull; +import org.h2.value.ValueVarchar; + +/** + * Legacy implementation of database meta information. + */ +public final class DatabaseMetaLegacy extends DatabaseMetaLocalBase { + + private static final Value PERCENT = ValueVarchar.get("%"); + + private static final Value BACKSLASH = ValueVarchar.get("\\"); + + private static final Value YES = ValueVarchar.get("YES"); + + private static final Value NO = ValueVarchar.get("NO"); + + private static final Value SCHEMA_MAIN = ValueVarchar.get(Constants.SCHEMA_MAIN); + + private final Session session; + + public DatabaseMetaLegacy(Session session) { + this.session = session; + } + + @Override + public final DefaultNullOrdering defaultNullOrdering() { + return DefaultNullOrdering.LOW; + } + + @Override + public String getSQLKeywords() { + return "CURRENT_CATALOG," // + + "CURRENT_SCHEMA," // + + "GROUPS," // + + "IF,ILIKE,INTERSECTS," // + + "KEY," // + + "LIMIT," // + + "MINUS," // + + "OFFSET," // + + "QUALIFY," // + + "REGEXP,ROWNUM," // + + "SYSDATE,SYSTIME,SYSTIMESTAMP," // + + "TODAY,TOP,"// + + "_ROWID_"; + } + + @Override + public String getNumericFunctions() { + return getFunctions("Functions (Numeric)"); + } + + @Override + public String getStringFunctions() { + return getFunctions("Functions (String)"); + } + + @Override + public String getSystemFunctions() { + return getFunctions("Functions (System)"); + } + + @Override + public String getTimeDateFunctions() { + return getFunctions("Functions (Time and Date)"); + } + + private String getFunctions(String section) { + String sql = "SELECT TOPIC FROM INFORMATION_SCHEMA.HELP WHERE SECTION = ?"; + Value[] args = new Value[] { getString(section) }; + ResultInterface result = executeQuery(sql, args); + StringBuilder builder = new StringBuilder(); + while (result.next()) { + String s = result.currentRow()[0].getString().trim(); + String[] array = StringUtils.arraySplit(s, ',', true); + for (String a : array) { + if (builder.length() != 0) { + builder.append(','); + } + String f = a.trim(); + int spaceIndex = f.indexOf(' '); + if (spaceIndex >= 0) { + // remove 'Function' from 'INSERT Function' + StringUtils.trimSubstring(builder, f, 0, spaceIndex); + } else { + builder.append(f); + } + } + } + return builder.toString(); + } + + @Override + public String getSearchStringEscape() { + return "\\"; + } + + @Override + public ResultInterface getProcedures(String catalog, String schemaPattern, String procedureNamePattern) { + return executeQuery("SELECT " // + + "ALIAS_CATALOG PROCEDURE_CAT, " // + + "ALIAS_SCHEMA PROCEDURE_SCHEM, " // + + "ALIAS_NAME PROCEDURE_NAME, " // + + "COLUMN_COUNT NUM_INPUT_PARAMS, " // + + "ZERO() NUM_OUTPUT_PARAMS, " // + + "ZERO() NUM_RESULT_SETS, " // + + "REMARKS, " // + + "RETURNS_RESULT PROCEDURE_TYPE, " // + + "ALIAS_NAME SPECIFIC_NAME " // + + "FROM INFORMATION_SCHEMA.FUNCTION_ALIASES " // + + "WHERE ALIAS_CATALOG LIKE ?1 ESCAPE ?4 " // + + "AND ALIAS_SCHEMA LIKE ?2 ESCAPE ?4 " // + + "AND ALIAS_NAME LIKE ?3 ESCAPE ?4 " // + + "ORDER BY PROCEDURE_SCHEM, PROCEDURE_NAME, NUM_INPUT_PARAMS", // + getCatalogPattern(catalog), // + getSchemaPattern(schemaPattern), // + getPattern(procedureNamePattern), // + BACKSLASH); + } + + @Override + public ResultInterface getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, + String columnNamePattern) { + return executeQuery("SELECT " // + + "ALIAS_CATALOG PROCEDURE_CAT, " // + + "ALIAS_SCHEMA PROCEDURE_SCHEM, " // + + "ALIAS_NAME PROCEDURE_NAME, " // + + "COLUMN_NAME, " // + + "COLUMN_TYPE, " // + + "DATA_TYPE, " // + + "TYPE_NAME, " // + + "PRECISION, " // + + "PRECISION LENGTH, " // + + "SCALE, " // + + "RADIX, " // + + "NULLABLE, " // + + "REMARKS, " // + + "COLUMN_DEFAULT COLUMN_DEF, " // + + "ZERO() SQL_DATA_TYPE, " // + + "ZERO() SQL_DATETIME_SUB, " // + + "ZERO() CHAR_OCTET_LENGTH, " // + + "POS ORDINAL_POSITION, " // + + "?1 IS_NULLABLE, " // + + "ALIAS_NAME SPECIFIC_NAME " // + + "FROM INFORMATION_SCHEMA.FUNCTION_COLUMNS " // + + "WHERE ALIAS_CATALOG LIKE ?2 ESCAPE ?6 " // + + "AND ALIAS_SCHEMA LIKE ?3 ESCAPE ?6 " // + + "AND ALIAS_NAME LIKE ?4 ESCAPE ?6 " // + + "AND COLUMN_NAME LIKE ?5 ESCAPE ?6 " // + + "ORDER BY PROCEDURE_SCHEM, PROCEDURE_NAME, ORDINAL_POSITION", // + YES, // + getCatalogPattern(catalog), // + getSchemaPattern(schemaPattern), // + getPattern(procedureNamePattern), // + getPattern(columnNamePattern), // + BACKSLASH); + } + + @Override + public ResultInterface getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types) { + int typesLength = types != null ? types.length : 0; + boolean includeSynonyms = types == null || Arrays.asList(types).contains("SYNONYM"); + // (1024 - 16) is enough for the most cases + StringBuilder select = new StringBuilder(1008); + if (includeSynonyms) { + select.append("SELECT " // + + "TABLE_CAT, " // + + "TABLE_SCHEM, " // + + "TABLE_NAME, " // + + "TABLE_TYPE, " // + + "REMARKS, " // + + "TYPE_CAT, " // + + "TYPE_SCHEM, " // + + "TYPE_NAME, " // + + "SELF_REFERENCING_COL_NAME, " // + + "REF_GENERATION, " // + + "SQL " // + + "FROM (" // + + "SELECT " // + + "SYNONYM_CATALOG TABLE_CAT, " // + + "SYNONYM_SCHEMA TABLE_SCHEM, " // + + "SYNONYM_NAME as TABLE_NAME, " // + + "TYPE_NAME AS TABLE_TYPE, " // + + "REMARKS, " // + + "TYPE_NAME TYPE_CAT, " // + + "TYPE_NAME TYPE_SCHEM, " // + + "TYPE_NAME AS TYPE_NAME, " // + + "TYPE_NAME SELF_REFERENCING_COL_NAME, " // + + "TYPE_NAME REF_GENERATION, " // + + "NULL AS SQL " // + + "FROM INFORMATION_SCHEMA.SYNONYMS " // + + "WHERE SYNONYM_CATALOG LIKE ?1 ESCAPE ?4 " // + + "AND SYNONYM_SCHEMA LIKE ?2 ESCAPE ?4 " // + + "AND SYNONYM_NAME LIKE ?3 ESCAPE ?4 " // + + "UNION "); + } + select.append("SELECT " // + + "TABLE_CATALOG TABLE_CAT, " // + + "TABLE_SCHEMA TABLE_SCHEM, " // + + "TABLE_NAME, " // + + "TABLE_TYPE, " // + + "REMARKS, " // + + "TYPE_NAME TYPE_CAT, " // + + "TYPE_NAME TYPE_SCHEM, " // + + "TYPE_NAME, " // + + "TYPE_NAME SELF_REFERENCING_COL_NAME, " // + + "TYPE_NAME REF_GENERATION, " // + + "SQL " // + + "FROM INFORMATION_SCHEMA.TABLES " // + + "WHERE TABLE_CATALOG LIKE ?1 ESCAPE ?4 " // + + "AND TABLE_SCHEMA LIKE ?2 ESCAPE ?4 " // + + "AND TABLE_NAME LIKE ?3 ESCAPE ?4"); + if (typesLength > 0) { + select.append(" AND TABLE_TYPE IN("); + for (int i = 0; i < typesLength; i++) { + if (i > 0) { + select.append(", "); + } + select.append('?').append(i + 5); + } + select.append(')'); + } + if (includeSynonyms) { + select.append(')'); + } + Value[] args = new Value[typesLength + 4]; + args[0] = getCatalogPattern(catalog); + args[1] = getSchemaPattern(schemaPattern); + args[2] = getPattern(tableNamePattern); + args[3] = BACKSLASH; + for (int i = 0; i < typesLength; i++) { + args[i + 4] = getString(types[i]); + } + return executeQuery(select.append(" ORDER BY TABLE_TYPE, TABLE_SCHEM, TABLE_NAME").toString(), args); + } + + @Override + public ResultInterface getSchemas() { + return executeQuery("SELECT " // + + "SCHEMA_NAME TABLE_SCHEM, " // + + "CATALOG_NAME TABLE_CATALOG " // + + "FROM INFORMATION_SCHEMA.SCHEMATA " // + + "ORDER BY SCHEMA_NAME"); + } + + @Override + public ResultInterface getCatalogs() { + return executeQuery("SELECT CATALOG_NAME TABLE_CAT " // + + "FROM INFORMATION_SCHEMA.CATALOGS"); + } + + @Override + public ResultInterface getTableTypes() { + return executeQuery("SELECT " // + + "TYPE TABLE_TYPE " // + + "FROM INFORMATION_SCHEMA.TABLE_TYPES " // + + "ORDER BY TABLE_TYPE"); + } + + @Override + public ResultInterface getColumns(String catalog, String schemaPattern, String tableNamePattern, + String columnNamePattern) { + return executeQuery("SELECT " // + + "TABLE_CAT, " // + + "TABLE_SCHEM, " // + + "TABLE_NAME, " // + + "COLUMN_NAME, " // + + "DATA_TYPE, " // + + "TYPE_NAME, " // + + "COLUMN_SIZE, " // + + "BUFFER_LENGTH, " // + + "DECIMAL_DIGITS, " // + + "NUM_PREC_RADIX, " // + + "NULLABLE, " // + + "REMARKS, " // + + "COLUMN_DEF, " // + + "SQL_DATA_TYPE, " // + + "SQL_DATETIME_SUB, " // + + "CHAR_OCTET_LENGTH, " // + + "ORDINAL_POSITION, " // + + "IS_NULLABLE, " // + + "SCOPE_CATALOG, " // + + "SCOPE_SCHEMA, " // + + "SCOPE_TABLE, " // + + "SOURCE_DATA_TYPE, " // + + "IS_AUTOINCREMENT, " // + + "IS_GENERATEDCOLUMN " // + + "FROM (" // + + "SELECT " // + + "s.SYNONYM_CATALOG TABLE_CAT, " // + + "s.SYNONYM_SCHEMA TABLE_SCHEM, " // + + "s.SYNONYM_NAME TABLE_NAME, " // + + "c.COLUMN_NAME, " // + + "c.DATA_TYPE, " // + + "c.TYPE_NAME, " // + + "c.CHARACTER_MAXIMUM_LENGTH COLUMN_SIZE, " // + + "c.CHARACTER_MAXIMUM_LENGTH BUFFER_LENGTH, " // + + "c.NUMERIC_SCALE DECIMAL_DIGITS, " // + + "c.NUMERIC_PRECISION_RADIX NUM_PREC_RADIX, " // + + "c.NULLABLE, " // + + "c.REMARKS, " // + + "c.COLUMN_DEFAULT COLUMN_DEF, " // + + "c.DATA_TYPE SQL_DATA_TYPE, " // + + "ZERO() SQL_DATETIME_SUB, " // + + "c.CHARACTER_OCTET_LENGTH CHAR_OCTET_LENGTH, " // + + "c.ORDINAL_POSITION, " // + + "c.IS_NULLABLE IS_NULLABLE, " // + + "CAST(c.SOURCE_DATA_TYPE AS VARCHAR) SCOPE_CATALOG, " // + + "CAST(c.SOURCE_DATA_TYPE AS VARCHAR) SCOPE_SCHEMA, " // + + "CAST(c.SOURCE_DATA_TYPE AS VARCHAR) SCOPE_TABLE, " // + + "c.SOURCE_DATA_TYPE, " // + + "CASE WHEN c.SEQUENCE_NAME IS NULL THEN " // + + "CAST(?1 AS VARCHAR) ELSE CAST(?2 AS VARCHAR) END IS_AUTOINCREMENT, " // + + "CASE WHEN c.IS_COMPUTED THEN " // + + "CAST(?2 AS VARCHAR) ELSE CAST(?1 AS VARCHAR) END IS_GENERATEDCOLUMN " // + + "FROM INFORMATION_SCHEMA.COLUMNS c JOIN INFORMATION_SCHEMA.SYNONYMS s ON " // + + "s.SYNONYM_FOR = c.TABLE_NAME " // + + "AND s.SYNONYM_FOR_SCHEMA = c.TABLE_SCHEMA " // + + "WHERE s.SYNONYM_CATALOG LIKE ?3 ESCAPE ?7 " // + + "AND s.SYNONYM_SCHEMA LIKE ?4 ESCAPE ?7 " // + + "AND s.SYNONYM_NAME LIKE ?5 ESCAPE ?7 " // + + "AND c.COLUMN_NAME LIKE ?6 ESCAPE ?7 " // + + "UNION SELECT " // + + "TABLE_CATALOG TABLE_CAT, " // + + "TABLE_SCHEMA TABLE_SCHEM, " // + + "TABLE_NAME, " // + + "COLUMN_NAME, " // + + "DATA_TYPE, " // + + "TYPE_NAME, " // + + "CHARACTER_MAXIMUM_LENGTH COLUMN_SIZE, " // + + "CHARACTER_MAXIMUM_LENGTH BUFFER_LENGTH, " // + + "NUMERIC_SCALE DECIMAL_DIGITS, " // + + "NUMERIC_PRECISION_RADIX NUM_PREC_RADIX, " // + + "NULLABLE, " // + + "REMARKS, " // + + "COLUMN_DEFAULT COLUMN_DEF, " // + + "DATA_TYPE SQL_DATA_TYPE, " // + + "ZERO() SQL_DATETIME_SUB, " // + + "CHARACTER_OCTET_LENGTH CHAR_OCTET_LENGTH, " // + + "ORDINAL_POSITION, " // + + "IS_NULLABLE IS_NULLABLE, " // + + "CAST(SOURCE_DATA_TYPE AS VARCHAR) SCOPE_CATALOG, " // + + "CAST(SOURCE_DATA_TYPE AS VARCHAR) SCOPE_SCHEMA, " // + + "CAST(SOURCE_DATA_TYPE AS VARCHAR) SCOPE_TABLE, " // + + "SOURCE_DATA_TYPE, " // + + "CASE WHEN SEQUENCE_NAME IS NULL THEN " // + + "CAST(?1 AS VARCHAR) ELSE CAST(?2 AS VARCHAR) END IS_AUTOINCREMENT, " // + + "CASE WHEN IS_COMPUTED THEN " // + + "CAST(?2 AS VARCHAR) ELSE CAST(?1 AS VARCHAR) END IS_GENERATEDCOLUMN " // + + "FROM INFORMATION_SCHEMA.COLUMNS " // + + "WHERE TABLE_CATALOG LIKE ?3 ESCAPE ?7 " // + + "AND TABLE_SCHEMA LIKE ?4 ESCAPE ?7 " // + + "AND TABLE_NAME LIKE ?5 ESCAPE ?7 " // + + "AND COLUMN_NAME LIKE ?6 ESCAPE ?7) " // + + "ORDER BY TABLE_SCHEM, TABLE_NAME, ORDINAL_POSITION", // + NO, // + YES, // + getCatalogPattern(catalog), // + getSchemaPattern(schemaPattern), // + getPattern(tableNamePattern), // + getPattern(columnNamePattern), // + BACKSLASH); + } + + @Override + public ResultInterface getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) { + return executeQuery("SELECT " // + + "TABLE_CATALOG TABLE_CAT, " // + + "TABLE_SCHEMA TABLE_SCHEM, " // + + "TABLE_NAME, " // + + "COLUMN_NAME, " // + + "GRANTOR, " // + + "GRANTEE, " // + + "PRIVILEGE_TYPE PRIVILEGE, " // + + "IS_GRANTABLE " // + + "FROM INFORMATION_SCHEMA.COLUMN_PRIVILEGES " // + + "WHERE TABLE_CATALOG LIKE ?1 ESCAPE ?5 " // + + "AND TABLE_SCHEMA LIKE ?2 ESCAPE ?5 " // + + "AND TABLE_NAME = ?3 " // + + "AND COLUMN_NAME LIKE ?4 ESCAPE ?5 " // + + "ORDER BY COLUMN_NAME, PRIVILEGE", // + getCatalogPattern(catalog), // + getSchemaPattern(schema), // + getString(table), // + getPattern(columnNamePattern), // + BACKSLASH); + } + + @Override + public ResultInterface getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) { + return executeQuery("SELECT " // + + "TABLE_CATALOG TABLE_CAT, " // + + "TABLE_SCHEMA TABLE_SCHEM, " // + + "TABLE_NAME, " // + + "GRANTOR, " // + + "GRANTEE, " // + + "PRIVILEGE_TYPE PRIVILEGE, " // + + "IS_GRANTABLE " // + + "FROM INFORMATION_SCHEMA.TABLE_PRIVILEGES " // + + "WHERE TABLE_CATALOG LIKE ?1 ESCAPE ?4 " // + + "AND TABLE_SCHEMA LIKE ?2 ESCAPE ?4 " // + + "AND TABLE_NAME LIKE ?3 ESCAPE ?4 " // + + "ORDER BY TABLE_SCHEM, TABLE_NAME, PRIVILEGE", // + getCatalogPattern(catalog), // + getSchemaPattern(schemaPattern), // + getPattern(tableNamePattern), // + BACKSLASH); + } + + @Override + public ResultInterface getBestRowIdentifier(String catalogPattern, String schemaPattern, String tableName, + int scope, boolean nullable) { + return executeQuery("SELECT " // + + "CAST(?1 AS SMALLINT) SCOPE, " // + + "C.COLUMN_NAME, " // + + "C.DATA_TYPE, " // + + "C.TYPE_NAME, " // + + "C.CHARACTER_MAXIMUM_LENGTH COLUMN_SIZE, " // + + "C.CHARACTER_MAXIMUM_LENGTH BUFFER_LENGTH, " // + + "CAST(C.NUMERIC_SCALE AS SMALLINT) DECIMAL_DIGITS, " // + + "CAST(?2 AS SMALLINT) PSEUDO_COLUMN " // + + "FROM INFORMATION_SCHEMA.INDEXES I, " // + + "INFORMATION_SCHEMA.COLUMNS C " // + + "WHERE C.TABLE_NAME = I.TABLE_NAME " // + + "AND C.COLUMN_NAME = I.COLUMN_NAME " // + + "AND C.TABLE_CATALOG LIKE ?3 ESCAPE ?6 " // + + "AND C.TABLE_SCHEMA LIKE ?4 ESCAPE ?6 " // + + "AND C.TABLE_NAME = ?5 " // + + "AND I.PRIMARY_KEY = TRUE " // + + "ORDER BY SCOPE", // + // SCOPE + ValueInteger.get(DatabaseMetaData.bestRowSession), // + // PSEUDO_COLUMN + ValueInteger.get(DatabaseMetaData.bestRowNotPseudo), // + getCatalogPattern(catalogPattern), // + getSchemaPattern(schemaPattern), // + getString(tableName), // + BACKSLASH); + } + + @Override + public ResultInterface getPrimaryKeys(String catalog, String schema, String table) { + return executeQuery("SELECT " // + + "TABLE_CATALOG TABLE_CAT, " // + + "TABLE_SCHEMA TABLE_SCHEM, " // + + "TABLE_NAME, " // + + "COLUMN_NAME, " // + + "ORDINAL_POSITION KEY_SEQ, " // + + "COALESCE(CONSTRAINT_NAME, INDEX_NAME) PK_NAME " // + + "FROM INFORMATION_SCHEMA.INDEXES " // + + "WHERE TABLE_CATALOG LIKE ?1 ESCAPE ?4 " // + + "AND TABLE_SCHEMA LIKE ?2 ESCAPE ?4 " // + + "AND TABLE_NAME = ?3 " // + + "AND PRIMARY_KEY = TRUE " // + + "ORDER BY COLUMN_NAME", // + getCatalogPattern(catalog), // + getSchemaPattern(schema), // + getString(table), // + BACKSLASH); + } + + @Override + public ResultInterface getImportedKeys(String catalog, String schema, String table) { + return executeQuery("SELECT " // + + "PKTABLE_CATALOG PKTABLE_CAT, " // + + "PKTABLE_SCHEMA PKTABLE_SCHEM, " // + + "PKTABLE_NAME PKTABLE_NAME, " // + + "PKCOLUMN_NAME, " // + + "FKTABLE_CATALOG FKTABLE_CAT, " // + + "FKTABLE_SCHEMA FKTABLE_SCHEM, " // + + "FKTABLE_NAME, " // + + "FKCOLUMN_NAME, " // + + "ORDINAL_POSITION KEY_SEQ, " // + + "UPDATE_RULE, " // + + "DELETE_RULE, " // + + "FK_NAME, " // + + "PK_NAME, " // + + "DEFERRABILITY " // + + "FROM INFORMATION_SCHEMA.CROSS_REFERENCES " // + + "WHERE FKTABLE_CATALOG LIKE ?1 ESCAPE ?4 " // + + "AND FKTABLE_SCHEMA LIKE ?2 ESCAPE ?4 " // + + "AND FKTABLE_NAME = ?3 " // + + "ORDER BY PKTABLE_CAT, PKTABLE_SCHEM, PKTABLE_NAME, FK_NAME, KEY_SEQ", // + getCatalogPattern(catalog), // + getSchemaPattern(schema), // + getString(table), // + BACKSLASH); + } + + @Override + public ResultInterface getExportedKeys(String catalog, String schema, String table) { + return executeQuery("SELECT " // + + "PKTABLE_CATALOG PKTABLE_CAT, " // + + "PKTABLE_SCHEMA PKTABLE_SCHEM, " // + + "PKTABLE_NAME PKTABLE_NAME, " // + + "PKCOLUMN_NAME, " // + + "FKTABLE_CATALOG FKTABLE_CAT, " // + + "FKTABLE_SCHEMA FKTABLE_SCHEM, " // + + "FKTABLE_NAME, " // + + "FKCOLUMN_NAME, " // + + "ORDINAL_POSITION KEY_SEQ, " // + + "UPDATE_RULE, " // + + "DELETE_RULE, " // + + "FK_NAME, " // + + "PK_NAME, " // + + "DEFERRABILITY " // + + "FROM INFORMATION_SCHEMA.CROSS_REFERENCES " // + + "WHERE PKTABLE_CATALOG LIKE ?1 ESCAPE ?4 " // + + "AND PKTABLE_SCHEMA LIKE ?2 ESCAPE ?4 " // + + "AND PKTABLE_NAME = ?3 " // + + "ORDER BY FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, FK_NAME, KEY_SEQ", // + getCatalogPattern(catalog), // + getSchemaPattern(schema), // + getString(table), // + BACKSLASH); + } + + @Override + public ResultInterface getCrossReference(String primaryCatalog, String primarySchema, String primaryTable, + String foreignCatalog, String foreignSchema, String foreignTable) { + return executeQuery("SELECT " // + + "PKTABLE_CATALOG PKTABLE_CAT, " // + + "PKTABLE_SCHEMA PKTABLE_SCHEM, " // + + "PKTABLE_NAME PKTABLE_NAME, " // + + "PKCOLUMN_NAME, " // + + "FKTABLE_CATALOG FKTABLE_CAT, " // + + "FKTABLE_SCHEMA FKTABLE_SCHEM, " // + + "FKTABLE_NAME, " // + + "FKCOLUMN_NAME, " // + + "ORDINAL_POSITION KEY_SEQ, " // + + "UPDATE_RULE, " // + + "DELETE_RULE, " // + + "FK_NAME, " // + + "PK_NAME, " // + + "DEFERRABILITY " // + + "FROM INFORMATION_SCHEMA.CROSS_REFERENCES " // + + "WHERE PKTABLE_CATALOG LIKE ?1 ESCAPE ?7 " // + + "AND PKTABLE_SCHEMA LIKE ?2 ESCAPE ?7 " // + + "AND PKTABLE_NAME = ?3 " // + + "AND FKTABLE_CATALOG LIKE ?4 ESCAPE ?7 " // + + "AND FKTABLE_SCHEMA LIKE ?5 ESCAPE ?7 " // + + "AND FKTABLE_NAME = ?6 " // + + "ORDER BY FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, FK_NAME, KEY_SEQ", // + getCatalogPattern(primaryCatalog), // + getSchemaPattern(primarySchema), // + getString(primaryTable), // + getCatalogPattern(foreignCatalog), // + getSchemaPattern(foreignSchema), // + getString(foreignTable), // + BACKSLASH); + } + + @Override + public ResultInterface getTypeInfo() { + return executeQuery("SELECT " // + + "TYPE_NAME, " // + + "DATA_TYPE, " // + + "PRECISION, " // + + "PREFIX LITERAL_PREFIX, " // + + "SUFFIX LITERAL_SUFFIX, " // + + "PARAMS CREATE_PARAMS, " // + + "NULLABLE, " // + + "CASE_SENSITIVE, " // + + "SEARCHABLE, " // + + "FALSE UNSIGNED_ATTRIBUTE, " // + + "FALSE FIXED_PREC_SCALE, " // + + "AUTO_INCREMENT, " // + + "TYPE_NAME LOCAL_TYPE_NAME, " // + + "MINIMUM_SCALE, " // + + "MAXIMUM_SCALE, " // + + "DATA_TYPE SQL_DATA_TYPE, " // + + "ZERO() SQL_DATETIME_SUB, " // + + "RADIX NUM_PREC_RADIX " // + + "FROM INFORMATION_SCHEMA.TYPE_INFO " // + + "ORDER BY DATA_TYPE, POS"); + } + + @Override + public ResultInterface getIndexInfo(String catalog, String schema, String table, boolean unique, + boolean approximate) { + String uniqueCondition = unique ? "NON_UNIQUE=FALSE" : "TRUE"; + return executeQuery("SELECT " // + + "TABLE_CATALOG TABLE_CAT, " // + + "TABLE_SCHEMA TABLE_SCHEM, " // + + "TABLE_NAME, " // + + "NON_UNIQUE, " // + + "TABLE_CATALOG INDEX_QUALIFIER, " // + + "INDEX_NAME, " // + + "INDEX_TYPE TYPE, " // + + "ORDINAL_POSITION, " // + + "COLUMN_NAME, " // + + "ASC_OR_DESC, " // + // TODO meta data for number of unique values in an index + + "CARDINALITY, " // + + "PAGES, " // + + "FILTER_CONDITION, " // + + "SORT_TYPE " // + + "FROM INFORMATION_SCHEMA.INDEXES " // + + "WHERE TABLE_CATALOG LIKE ?1 ESCAPE ?4 " // + + "AND TABLE_SCHEMA LIKE ?2 ESCAPE ?4 " // + + "AND (" + uniqueCondition + ") " // + + "AND TABLE_NAME = ?3 " // + + "ORDER BY NON_UNIQUE, TYPE, TABLE_SCHEM, INDEX_NAME, ORDINAL_POSITION", // + getCatalogPattern(catalog), // + getSchemaPattern(schema), // + getString(table), // + BACKSLASH); + } + + @Override + public ResultInterface getSchemas(String catalog, String schemaPattern) { + return executeQuery("SELECT " // + + "SCHEMA_NAME TABLE_SCHEM, " // + + "CATALOG_NAME TABLE_CATALOG " // + + "FROM INFORMATION_SCHEMA.SCHEMATA " // + + "WHERE CATALOG_NAME LIKE ?1 ESCAPE ?3 " // + + "AND SCHEMA_NAME LIKE ?2 ESCAPE ?3 " // + + "ORDER BY SCHEMA_NAME", // + getCatalogPattern(catalog), // + getSchemaPattern(schemaPattern), // + BACKSLASH); + } + + @Override + public ResultInterface getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, + String columnNamePattern) { + return getPseudoColumnsResult(); + } + + private ResultInterface executeQuery(String sql, Value... args) { + checkClosed(); + synchronized (session) { + CommandInterface command = session.prepareCommand(sql, Integer.MAX_VALUE); + int l = args.length; + if (l > 0) { + ArrayList parameters = command.getParameters(); + for (int i = 0; i < l; i++) { + parameters.get(i).setValue(args[i], true); + } + } + ResultInterface result = command.executeQuery(0, false); + command.close(); + return result; + } + } + + @Override + void checkClosed() { + if (session.isClosed()) { + throw DbException.get(ErrorCode.DATABASE_CALLED_AT_SHUTDOWN); + } + } + + private Value getString(String string) { + return string != null ? ValueVarchar.get(string, session) : ValueNull.INSTANCE; + } + + private Value getPattern(String pattern) { + return pattern == null ? PERCENT : getString(pattern); + } + + private Value getSchemaPattern(String pattern) { + return pattern == null ? PERCENT : pattern.isEmpty() ? SCHEMA_MAIN : getString(pattern); + } + + private Value getCatalogPattern(String catalogPattern) { + return catalogPattern == null || catalogPattern.isEmpty() ? PERCENT : getString(catalogPattern); + } + +} diff --git a/h2/src/main/org/h2/jdbc/meta/DatabaseMetaLocal.java b/h2/src/main/org/h2/jdbc/meta/DatabaseMetaLocal.java new file mode 100644 index 0000000..fa43376 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/meta/DatabaseMetaLocal.java @@ -0,0 +1,1523 @@ +/* + * 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.jdbc.meta; + +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; + +import org.h2.api.ErrorCode; +import org.h2.command.dml.Help; +import org.h2.constraint.Constraint; +import org.h2.constraint.ConstraintActionType; +import org.h2.constraint.ConstraintReferential; +import org.h2.constraint.ConstraintUnique; +import org.h2.engine.Database; +import org.h2.engine.DbObject; +import org.h2.engine.Mode; +import org.h2.engine.Right; +import org.h2.engine.SessionLocal; +import org.h2.engine.User; +import org.h2.expression.condition.CompareLike; +import org.h2.index.Index; +import org.h2.message.DbException; +import org.h2.mode.DefaultNullOrdering; +import org.h2.result.ResultInterface; +import org.h2.result.SimpleResult; +import org.h2.result.SortOrder; +import org.h2.schema.FunctionAlias; +import org.h2.schema.FunctionAlias.JavaMethod; +import org.h2.schema.Schema; +import org.h2.schema.SchemaObject; +import org.h2.schema.UserDefinedFunction; +import org.h2.table.Column; +import org.h2.table.IndexColumn; +import org.h2.table.Table; +import org.h2.table.TableSynonym; +import org.h2.util.MathUtils; +import org.h2.util.StringUtils; +import org.h2.util.Utils; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBigint; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueInteger; +import org.h2.value.ValueNull; +import org.h2.value.ValueSmallint; +import org.h2.value.ValueToObjectConverter2; +import org.h2.value.ValueVarchar; + +/** + * Local implementation of database meta information. + */ +public final class DatabaseMetaLocal extends DatabaseMetaLocalBase { + + private static final Value YES = ValueVarchar.get("YES"); + + private static final Value NO = ValueVarchar.get("NO"); + + private static final ValueSmallint BEST_ROW_SESSION = ValueSmallint.get((short) DatabaseMetaData.bestRowSession); + + private static final ValueSmallint BEST_ROW_NOT_PSEUDO = ValueSmallint + .get((short) DatabaseMetaData.bestRowNotPseudo); + + private static final ValueInteger COLUMN_NO_NULLS = ValueInteger.get(DatabaseMetaData.columnNoNulls); + + private static final ValueSmallint COLUMN_NO_NULLS_SMALL = ValueSmallint + .get((short) DatabaseMetaData.columnNoNulls); + + private static final ValueInteger COLUMN_NULLABLE = ValueInteger.get(DatabaseMetaData.columnNullable); + + private static final ValueSmallint COLUMN_NULLABLE_UNKNOWN_SMALL = ValueSmallint + .get((short) DatabaseMetaData.columnNullableUnknown); + + private static final ValueSmallint IMPORTED_KEY_CASCADE = ValueSmallint + .get((short) DatabaseMetaData.importedKeyCascade); + + private static final ValueSmallint IMPORTED_KEY_RESTRICT = ValueSmallint + .get((short) DatabaseMetaData.importedKeyRestrict); + + private static final ValueSmallint IMPORTED_KEY_DEFAULT = ValueSmallint + .get((short) DatabaseMetaData.importedKeySetDefault); + + private static final ValueSmallint IMPORTED_KEY_SET_NULL = ValueSmallint + .get((short) DatabaseMetaData.importedKeySetNull); + + private static final ValueSmallint IMPORTED_KEY_NOT_DEFERRABLE = ValueSmallint + .get((short) DatabaseMetaData.importedKeyNotDeferrable); + + private static final ValueSmallint PROCEDURE_COLUMN_IN = ValueSmallint + .get((short) DatabaseMetaData.procedureColumnIn); + + private static final ValueSmallint PROCEDURE_COLUMN_RETURN = ValueSmallint + .get((short) DatabaseMetaData.procedureColumnReturn); + + private static final ValueSmallint PROCEDURE_NO_RESULT = ValueSmallint + .get((short) DatabaseMetaData.procedureNoResult); + + private static final ValueSmallint PROCEDURE_RETURNS_RESULT = ValueSmallint + .get((short) DatabaseMetaData.procedureReturnsResult); + + private static final ValueSmallint TABLE_INDEX_HASHED = ValueSmallint.get(DatabaseMetaData.tableIndexHashed); + + private static final ValueSmallint TABLE_INDEX_OTHER = ValueSmallint.get(DatabaseMetaData.tableIndexOther); + + // This list must be ordered + private static final String[] TABLE_TYPES = { "BASE TABLE", "GLOBAL TEMPORARY", "LOCAL TEMPORARY", "SYNONYM", + "VIEW" }; + + private static final ValueSmallint TYPE_NULLABLE = ValueSmallint.get((short) DatabaseMetaData.typeNullable); + + private static final ValueSmallint TYPE_SEARCHABLE = ValueSmallint.get((short) DatabaseMetaData.typeSearchable); + + private static final Value NO_USAGE_RESTRICTIONS = ValueVarchar.get("NO_USAGE_RESTRICTIONS"); + + private final SessionLocal session; + + public DatabaseMetaLocal(SessionLocal session) { + this.session = session; + } + + @Override + public final DefaultNullOrdering defaultNullOrdering() { + return session.getDatabase().getDefaultNullOrdering(); + } + + @Override + public String getSQLKeywords() { + StringBuilder builder = new StringBuilder(103).append( // + "CURRENT_CATALOG," // + + "CURRENT_SCHEMA," // + + "GROUPS," // + + "IF,ILIKE," // + + "KEY,"); + Mode mode = session.getMode(); + if (mode.limit) { + builder.append("LIMIT,"); + } + if (mode.minusIsExcept) { + builder.append("MINUS,"); + } + builder.append( // + "OFFSET," // + + "QUALIFY," // + + "REGEXP,ROWNUM,"); + if (mode.topInSelect || mode.topInDML) { + builder.append("TOP,"); + } + return builder.append("_ROWID_") // + .toString(); + } + + @Override + public String getNumericFunctions() { + return getFunctions("Functions (Numeric)"); + } + + @Override + public String getStringFunctions() { + return getFunctions("Functions (String)"); + } + + @Override + public String getSystemFunctions() { + return getFunctions("Functions (System)"); + } + + @Override + public String getTimeDateFunctions() { + return getFunctions("Functions (Time and Date)"); + } + + private String getFunctions(String section) { + checkClosed(); + StringBuilder builder = new StringBuilder(); + try { + ResultSet rs = Help.getTable(); + while (rs.next()) { + if (rs.getString(1).trim().equals(section)) { + if (builder.length() != 0) { + builder.append(','); + } + String topic = rs.getString(2).trim(); + int spaceIndex = topic.indexOf(' '); + if (spaceIndex >= 0) { + // remove 'Function' from 'INSERT Function' + StringUtils.trimSubstring(builder, topic, 0, spaceIndex); + } else { + builder.append(topic); + } + } + } + } catch (Exception e) { + throw DbException.convert(e); + } + return builder.toString(); + } + + @Override + public String getSearchStringEscape() { + return session.getDatabase().getSettings().defaultEscape; + } + + @Override + public ResultInterface getProcedures(String catalog, String schemaPattern, String procedureNamePattern) { + checkClosed(); + SimpleResult result = new SimpleResult(); + result.addColumn("PROCEDURE_CAT", TypeInfo.TYPE_VARCHAR); + result.addColumn("PROCEDURE_SCHEM", TypeInfo.TYPE_VARCHAR); + result.addColumn("PROCEDURE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("RESERVED1", TypeInfo.TYPE_NULL); + result.addColumn("RESERVED2", TypeInfo.TYPE_NULL); + result.addColumn("RESERVED3", TypeInfo.TYPE_NULL); + result.addColumn("REMARKS", TypeInfo.TYPE_VARCHAR); + result.addColumn("PROCEDURE_TYPE", TypeInfo.TYPE_SMALLINT); + result.addColumn("SPECIFIC_NAME", TypeInfo.TYPE_VARCHAR); + if (!checkCatalogName(catalog)) { + return result; + } + Database db = session.getDatabase(); + Value catalogValue = getString(db.getShortName()); + CompareLike procedureLike = getLike(procedureNamePattern); + for (Schema s : getSchemasForPattern(schemaPattern)) { + Value schemaValue = getString(s.getName()); + for (UserDefinedFunction userDefinedFunction : s.getAllFunctionsAndAggregates()) { + String procedureName = userDefinedFunction.getName(); + if (procedureLike != null && !procedureLike.test(procedureName)) { + continue; + } + Value procedureNameValue = getString(procedureName); + if (userDefinedFunction instanceof FunctionAlias) { + JavaMethod[] methods; + try { + methods = ((FunctionAlias) userDefinedFunction).getJavaMethods(); + } catch (DbException e) { + continue; + } + for (int i = 0; i < methods.length; i++) { + JavaMethod method = methods[i]; + TypeInfo typeInfo = method.getDataType(); + getProceduresAdd(result, catalogValue, schemaValue, procedureNameValue, + userDefinedFunction.getComment(), + typeInfo == null || typeInfo.getValueType() != Value.NULL ? PROCEDURE_RETURNS_RESULT + : PROCEDURE_NO_RESULT, + getString(procedureName + '_' + (i + 1))); + } + } else { + getProceduresAdd(result, catalogValue, schemaValue, procedureNameValue, + userDefinedFunction.getComment(), PROCEDURE_RETURNS_RESULT, procedureNameValue); + } + } + } + // PROCEDURE_CAT, PROCEDURE_SCHEM, PROCEDURE_NAME, SPECIFIC_ NAME + result.sortRows(new SortOrder(session, new int[] { 1, 2, 8 })); + return result; + } + + private void getProceduresAdd(SimpleResult result, Value catalogValue, Value schemaValue, Value procedureNameValue, + String comment, ValueSmallint procedureType, Value specificNameValue) { + result.addRow( + // PROCEDURE_CAT + catalogValue, + // PROCEDURE_SCHEM + schemaValue, + // PROCEDURE_NAME + procedureNameValue, + // RESERVED1 + ValueNull.INSTANCE, + // RESERVED2 + ValueNull.INSTANCE, + // RESERVED3 + ValueNull.INSTANCE, + // REMARKS + getString(comment), + // PROCEDURE_TYPE + procedureType, + // SPECIFIC_NAME + specificNameValue); + } + + @Override + public ResultInterface getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, + String columnNamePattern) { + checkClosed(); + SimpleResult result = new SimpleResult(); + result.addColumn("PROCEDURE_CAT", TypeInfo.TYPE_VARCHAR); + result.addColumn("PROCEDURE_SCHEM", TypeInfo.TYPE_VARCHAR); + result.addColumn("PROCEDURE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("COLUMN_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("COLUMN_TYPE", TypeInfo.TYPE_SMALLINT); + result.addColumn("DATA_TYPE", TypeInfo.TYPE_INTEGER); + result.addColumn("TYPE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("PRECISION", TypeInfo.TYPE_INTEGER); + result.addColumn("LENGTH", TypeInfo.TYPE_INTEGER); + result.addColumn("SCALE", TypeInfo.TYPE_SMALLINT); + result.addColumn("RADIX", TypeInfo.TYPE_SMALLINT); + result.addColumn("NULLABLE", TypeInfo.TYPE_SMALLINT); + result.addColumn("REMARKS", TypeInfo.TYPE_VARCHAR); + result.addColumn("COLUMN_DEF", TypeInfo.TYPE_VARCHAR); + result.addColumn("SQL_DATA_TYPE", TypeInfo.TYPE_INTEGER); + result.addColumn("SQL_DATETIME_SUB", TypeInfo.TYPE_INTEGER); + result.addColumn("CHAR_OCTET_LENGTH", TypeInfo.TYPE_INTEGER); + result.addColumn("ORDINAL_POSITION", TypeInfo.TYPE_INTEGER); + result.addColumn("IS_NULLABLE", TypeInfo.TYPE_VARCHAR); + result.addColumn("SPECIFIC_NAME", TypeInfo.TYPE_VARCHAR); + if (!checkCatalogName(catalog)) { + return result; + } + Database db = session.getDatabase(); + Value catalogValue = getString(db.getShortName()); + CompareLike procedureLike = getLike(procedureNamePattern); + for (Schema s : getSchemasForPattern(schemaPattern)) { + Value schemaValue = getString(s.getName()); + for (UserDefinedFunction userDefinedFunction : s.getAllFunctionsAndAggregates()) { + if (!(userDefinedFunction instanceof FunctionAlias)) { + continue; + } + String procedureName = userDefinedFunction.getName(); + if (procedureLike != null && !procedureLike.test(procedureName)) { + continue; + } + Value procedureNameValue = getString(procedureName); + JavaMethod[] methods; + try { + methods = ((FunctionAlias) userDefinedFunction).getJavaMethods(); + } catch (DbException e) { + continue; + } + for (int i = 0, l = methods.length; i < l; i++) { + JavaMethod method = methods[i]; + Value specificNameValue = getString(procedureName + '_' + (i + 1)); + TypeInfo typeInfo = method.getDataType(); + if (typeInfo != null && typeInfo.getValueType() != Value.NULL) { + getProcedureColumnAdd(result, catalogValue, schemaValue, procedureNameValue, specificNameValue, + typeInfo, method.getClass().isPrimitive(), 0); + } + Class[] columnList = method.getColumnClasses(); + for (int o = 1, p = method.hasConnectionParam() ? 1 : 0, n = columnList.length; p < n; o++, p++) { + Class clazz = columnList[p]; + getProcedureColumnAdd(result, catalogValue, schemaValue, procedureNameValue, specificNameValue, + ValueToObjectConverter2.classToType(clazz), clazz.isPrimitive(), o); + } + } + } + } + // PROCEDURE_CAT, PROCEDURE_SCHEM, PROCEDURE_NAME, SPECIFIC_NAME, return + // value first + result.sortRows(new SortOrder(session, new int[] { 1, 2, 19 })); + return result; + } + + private void getProcedureColumnAdd(SimpleResult result, Value catalogValue, Value schemaValue, + Value procedureNameValue, Value specificNameValue, TypeInfo type, boolean notNull, int ordinal) { + int valueType = type.getValueType(); + DataType dt = DataType.getDataType(valueType); + ValueInteger precisionValue = ValueInteger.get(MathUtils.convertLongToInt(type.getPrecision())); + result.addRow( + // PROCEDURE_CAT + catalogValue, + // PROCEDURE_SCHEM + schemaValue, + // PROCEDURE_NAME + procedureNameValue, + // COLUMN_NAME + getString(ordinal == 0 ? "RESULT" : "P" + ordinal), + // COLUMN_TYPE + ordinal == 0 ? PROCEDURE_COLUMN_RETURN : PROCEDURE_COLUMN_IN, + // DATA_TYPE + ValueInteger.get(DataType.convertTypeToSQLType(type)), + // TYPE_NAME + getDataTypeName(type), + // PRECISION + precisionValue, + // LENGTH + precisionValue, + // SCALE + dt.supportsScale // + ? ValueSmallint.get(MathUtils.convertIntToShort(dt.defaultScale)) + : ValueNull.INSTANCE, + // RADIX + getRadix(valueType, true), + // NULLABLE + notNull ? COLUMN_NO_NULLS_SMALL : COLUMN_NULLABLE_UNKNOWN_SMALL, + // REMARKS + ValueNull.INSTANCE, + // COLUMN_DEF + ValueNull.INSTANCE, + // SQL_DATA_TYPE + ValueNull.INSTANCE, + // SQL_DATETIME_SUB + ValueNull.INSTANCE, + // CHAR_OCTET_LENGTH + DataType.isBinaryStringType(valueType) || DataType.isCharacterStringType(valueType) ? precisionValue + : ValueNull.INSTANCE, + // ORDINAL_POSITION + ValueInteger.get(ordinal), + // IS_NULLABLE + ValueVarchar.EMPTY, + // SPECIFIC_NAME + specificNameValue); + } + + @Override + public ResultInterface getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types) { + SimpleResult result = new SimpleResult(); + result.addColumn("TABLE_CAT", TypeInfo.TYPE_VARCHAR); + result.addColumn("TABLE_SCHEM", TypeInfo.TYPE_VARCHAR); + result.addColumn("TABLE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("TABLE_TYPE", TypeInfo.TYPE_VARCHAR); + result.addColumn("REMARKS", TypeInfo.TYPE_VARCHAR); + result.addColumn("TYPE_CAT", TypeInfo.TYPE_VARCHAR); + result.addColumn("TYPE_SCHEM", TypeInfo.TYPE_VARCHAR); + result.addColumn("TYPE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("SELF_REFERENCING_COL_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("REF_GENERATION", TypeInfo.TYPE_VARCHAR); + if (!checkCatalogName(catalog)) { + return result; + } + Database db = session.getDatabase(); + Value catalogValue = getString(db.getShortName()); + HashSet typesSet; + if (types != null) { + typesSet = new HashSet<>(8); + for (String type : types) { + int idx = Arrays.binarySearch(TABLE_TYPES, type); + if (idx >= 0) { + typesSet.add(TABLE_TYPES[idx]); + } else if (type.equals("TABLE")) { + typesSet.add("BASE TABLE"); + } + } + if (typesSet.isEmpty()) { + return result; + } + } else { + typesSet = null; + } + for (Schema schema : getSchemasForPattern(schemaPattern)) { + Value schemaValue = getString(schema.getName()); + for (SchemaObject object : getTablesForPattern(schema, tableNamePattern)) { + Value tableName = getString(object.getName()); + if (object instanceof Table) { + Table t = (Table) object; + if (!t.isHidden()) { + getTablesAdd(result, catalogValue, schemaValue, tableName, t, false, typesSet); + } + } else { + getTablesAdd(result, catalogValue, schemaValue, tableName, ((TableSynonym) object).getSynonymFor(), + true, typesSet); + } + } + } + // TABLE_TYPE, TABLE_CAT, TABLE_SCHEM, TABLE_NAME + result.sortRows(new SortOrder(session, new int[] { 3, 1, 2 })); + return result; + } + + private void getTablesAdd(SimpleResult result, Value catalogValue, Value schemaValue, Value tableName, Table t, + boolean synonym, HashSet typesSet) { + String type = synonym ? "SYNONYM" : t.getSQLTableType(); + if (typesSet != null && !typesSet.contains(type)) { + return; + } + result.addRow( + // TABLE_CAT + catalogValue, + // TABLE_SCHEM + schemaValue, + // TABLE_NAME + tableName, + // TABLE_TYPE + getString(type), + // REMARKS + getString(t.getComment()), + // TYPE_CAT + ValueNull.INSTANCE, + // TYPE_SCHEM + ValueNull.INSTANCE, + // TYPE_NAME + ValueNull.INSTANCE, + // SELF_REFERENCING_COL_NAME + ValueNull.INSTANCE, + // REF_GENERATION + ValueNull.INSTANCE); + } + + @Override + public ResultInterface getSchemas() { + return getSchemas(null, null); + } + + @Override + public ResultInterface getCatalogs() { + checkClosed(); + SimpleResult result = new SimpleResult(); + result.addColumn("TABLE_CAT", TypeInfo.TYPE_VARCHAR); + result.addRow(getString(session.getDatabase().getShortName())); + return result; + } + + @Override + public ResultInterface getTableTypes() { + SimpleResult result = new SimpleResult(); + result.addColumn("TABLE_TYPE", TypeInfo.TYPE_VARCHAR); + // Order by TABLE_TYPE + result.addRow(getString("BASE TABLE")); + result.addRow(getString("GLOBAL TEMPORARY")); + result.addRow(getString("LOCAL TEMPORARY")); + result.addRow(getString("SYNONYM")); + result.addRow(getString("VIEW")); + return result; + } + + @Override + public ResultInterface getColumns(String catalog, String schemaPattern, String tableNamePattern, + String columnNamePattern) { + SimpleResult result = new SimpleResult(); + result.addColumn("TABLE_CAT", TypeInfo.TYPE_VARCHAR); + result.addColumn("TABLE_SCHEM", TypeInfo.TYPE_VARCHAR); + result.addColumn("TABLE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("COLUMN_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("DATA_TYPE", TypeInfo.TYPE_INTEGER); + result.addColumn("TYPE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("COLUMN_SIZE", TypeInfo.TYPE_INTEGER); + result.addColumn("BUFFER_LENGTH", TypeInfo.TYPE_INTEGER); + result.addColumn("DECIMAL_DIGITS", TypeInfo.TYPE_INTEGER); + result.addColumn("NUM_PREC_RADIX", TypeInfo.TYPE_INTEGER); + result.addColumn("NULLABLE", TypeInfo.TYPE_INTEGER); + result.addColumn("REMARKS", TypeInfo.TYPE_VARCHAR); + result.addColumn("COLUMN_DEF", TypeInfo.TYPE_VARCHAR); + result.addColumn("SQL_DATA_TYPE", TypeInfo.TYPE_INTEGER); + result.addColumn("SQL_DATETIME_SUB", TypeInfo.TYPE_INTEGER); + result.addColumn("CHAR_OCTET_LENGTH", TypeInfo.TYPE_INTEGER); + result.addColumn("ORDINAL_POSITION", TypeInfo.TYPE_INTEGER); + result.addColumn("IS_NULLABLE", TypeInfo.TYPE_VARCHAR); + result.addColumn("SCOPE_CATALOG", TypeInfo.TYPE_VARCHAR); + result.addColumn("SCOPE_SCHEMA", TypeInfo.TYPE_VARCHAR); + result.addColumn("SCOPE_TABLE", TypeInfo.TYPE_VARCHAR); + result.addColumn("SOURCE_DATA_TYPE", TypeInfo.TYPE_SMALLINT); + result.addColumn("IS_AUTOINCREMENT", TypeInfo.TYPE_VARCHAR); + result.addColumn("IS_GENERATEDCOLUMN", TypeInfo.TYPE_VARCHAR); + if (!checkCatalogName(catalog)) { + return result; + } + Database db = session.getDatabase(); + Value catalogValue = getString(db.getShortName()); + CompareLike columnLike = getLike(columnNamePattern); + for (Schema schema : getSchemasForPattern(schemaPattern)) { + Value schemaValue = getString(schema.getName()); + for (SchemaObject object : getTablesForPattern(schema, tableNamePattern)) { + Value tableName = getString(object.getName()); + if (object instanceof Table) { + Table t = (Table) object; + if (!t.isHidden()) { + getColumnsAdd(result, catalogValue, schemaValue, tableName, t, columnLike); + } + } else { + TableSynonym s = (TableSynonym) object; + Table t = s.getSynonymFor(); + getColumnsAdd(result, catalogValue, schemaValue, tableName, t, columnLike); + } + } + } + // TABLE_CAT, TABLE_SCHEM, TABLE_NAME, ORDINAL_POSITION + result.sortRows(new SortOrder(session, new int[] { 1, 2, 16 })); + return result; + } + + private void getColumnsAdd(SimpleResult result, Value catalogValue, Value schemaValue, Value tableName, Table t, + CompareLike columnLike) { + int ordinal = 0; + for (Column c : t.getColumns()) { + if (!c.getVisible()) { + continue; + } + ordinal++; + String name = c.getName(); + if (columnLike != null && !columnLike.test(name)) { + continue; + } + TypeInfo type = c.getType(); + ValueInteger precision = ValueInteger.get(MathUtils.convertLongToInt(type.getPrecision())); + boolean nullable = c.isNullable(), isGenerated = c.isGenerated(); + result.addRow( + // TABLE_CAT + catalogValue, + // TABLE_SCHEM + schemaValue, + // TABLE_NAME + tableName, + // COLUMN_NAME + getString(name), + // DATA_TYPE + ValueInteger.get(DataType.convertTypeToSQLType(type)), + // TYPE_NAME + getDataTypeName(type), + // COLUMN_SIZE + precision, + // BUFFER_LENGTH + ValueNull.INSTANCE, + // DECIMAL_DIGITS + ValueInteger.get(type.getScale()), + // NUM_PREC_RADIX + getRadix(type.getValueType(), false), + // NULLABLE + nullable ? COLUMN_NULLABLE : COLUMN_NO_NULLS, + // REMARKS + getString(c.getComment()), + // COLUMN_DEF + isGenerated ? ValueNull.INSTANCE : getString(c.getDefaultSQL()), + // SQL_DATA_TYPE (unused) + ValueNull.INSTANCE, + // SQL_DATETIME_SUB (unused) + ValueNull.INSTANCE, + // CHAR_OCTET_LENGTH + precision, + // ORDINAL_POSITION + ValueInteger.get(ordinal), + // IS_NULLABLE + nullable ? YES : NO, + // SCOPE_CATALOG + ValueNull.INSTANCE, + // SCOPE_SCHEMA + ValueNull.INSTANCE, + // SCOPE_TABLE + ValueNull.INSTANCE, + // SOURCE_DATA_TYPE + ValueNull.INSTANCE, + // IS_AUTOINCREMENT + c.isIdentity() ? YES : NO, + // IS_GENERATEDCOLUMN + isGenerated ? YES : NO); + } + } + + @Override + public ResultInterface getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) { + if (table == null) { + throw DbException.getInvalidValueException("table", null); + } + checkClosed(); + SimpleResult result = new SimpleResult(); + result.addColumn("TABLE_CAT", TypeInfo.TYPE_VARCHAR); + result.addColumn("TABLE_SCHEM", TypeInfo.TYPE_VARCHAR); + result.addColumn("TABLE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("COLUMN_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("GRANTOR", TypeInfo.TYPE_VARCHAR); + result.addColumn("GRANTEE", TypeInfo.TYPE_VARCHAR); + result.addColumn("PRIVILEGE", TypeInfo.TYPE_VARCHAR); + result.addColumn("IS_GRANTABLE", TypeInfo.TYPE_VARCHAR); + if (!checkCatalogName(catalog)) { + return result; + } + Database db = session.getDatabase(); + Value catalogValue = getString(db.getShortName()); + CompareLike columnLike = getLike(columnNamePattern); + for (Right r : db.getAllRights()) { + DbObject object = r.getGrantedObject(); + if (!(object instanceof Table)) { + continue; + } + Table t = (Table) object; + if (t.isHidden()) { + continue; + } + String tableName = t.getName(); + if (!db.equalsIdentifiers(table, tableName)) { + continue; + } + Schema s = t.getSchema(); + if (!checkSchema(schema, s)) { + continue; + } + addPrivileges(result, catalogValue, s.getName(), tableName, r.getGrantee(), r.getRightMask(), columnLike, + t.getColumns()); + } + // COLUMN_NAME, PRIVILEGE + result.sortRows(new SortOrder(session, new int[] { 3, 6 })); + return result; + } + + @Override + public ResultInterface getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) { + checkClosed(); + SimpleResult result = new SimpleResult(); + result.addColumn("TABLE_CAT", TypeInfo.TYPE_VARCHAR); + result.addColumn("TABLE_SCHEM", TypeInfo.TYPE_VARCHAR); + result.addColumn("TABLE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("GRANTOR", TypeInfo.TYPE_VARCHAR); + result.addColumn("GRANTEE", TypeInfo.TYPE_VARCHAR); + result.addColumn("PRIVILEGE", TypeInfo.TYPE_VARCHAR); + result.addColumn("IS_GRANTABLE", TypeInfo.TYPE_VARCHAR); + if (!checkCatalogName(catalog)) { + return result; + } + Database db = session.getDatabase(); + Value catalogValue = getString(db.getShortName()); + CompareLike schemaLike = getLike(schemaPattern); + CompareLike tableLike = getLike(tableNamePattern); + for (Right r : db.getAllRights()) { + DbObject object = r.getGrantedObject(); + if (!(object instanceof Table)) { + continue; + } + Table table = (Table) object; + if (table.isHidden()) { + continue; + } + String tableName = table.getName(); + if (tableLike != null && !tableLike.test(tableName)) { + continue; + } + Schema schema = table.getSchema(); + String schemaName = schema.getName(); + if (schemaPattern != null) { + if (schemaPattern.isEmpty()) { + if (schema != db.getMainSchema()) { + continue; + } + } else { + if (!schemaLike.test(schemaName)) { + continue; + } + } + } + addPrivileges(result, catalogValue, schemaName, tableName, r.getGrantee(), r.getRightMask(), null, null); + } + // TABLE_CAT, TABLE_SCHEM, TABLE_NAME, PRIVILEGE + result.sortRows(new SortOrder(session, new int[] { 1, 2, 5 })); + return result; + } + + private void addPrivileges(SimpleResult result, Value catalogValue, String schemaName, String tableName, + DbObject grantee, int rightMask, CompareLike columnLike, Column[] columns) { + Value schemaValue = getString(schemaName); + Value tableValue = getString(tableName); + Value granteeValue = getString(grantee.getName()); + boolean isAdmin = grantee.getType() == DbObject.USER && ((User) grantee).isAdmin(); + if ((rightMask & Right.SELECT) != 0) { + addPrivilege(result, catalogValue, schemaValue, tableValue, granteeValue, "SELECT", isAdmin, columnLike, + columns); + } + if ((rightMask & Right.INSERT) != 0) { + addPrivilege(result, catalogValue, schemaValue, tableValue, granteeValue, "INSERT", isAdmin, columnLike, + columns); + } + if ((rightMask & Right.UPDATE) != 0) { + addPrivilege(result, catalogValue, schemaValue, tableValue, granteeValue, "UPDATE", isAdmin, columnLike, + columns); + } + if ((rightMask & Right.DELETE) != 0) { + addPrivilege(result, catalogValue, schemaValue, tableValue, granteeValue, "DELETE", isAdmin, columnLike, + columns); + } + } + + private void addPrivilege(SimpleResult result, Value catalogValue, Value schemaValue, Value tableValue, + Value granteeValue, String right, boolean isAdmin, CompareLike columnLike, Column[] columns) { + if (columns == null) { + result.addRow( + // TABLE_CAT + catalogValue, + // TABLE_SCHEM + schemaValue, + // TABLE_NAME + tableValue, + // GRANTOR + ValueNull.INSTANCE, + // GRANTEE + granteeValue, + // PRIVILEGE + getString(right), + // IS_GRANTABLE + isAdmin ? YES : NO); + } else { + for (Column column : columns) { + String columnName = column.getName(); + if (columnLike != null && !columnLike.test(columnName)) { + continue; + } + result.addRow( + // TABLE_CAT + catalogValue, + // TABLE_SCHEM + schemaValue, + // TABLE_NAME + tableValue, + // COLUMN_NAME + getString(columnName), + // GRANTOR + ValueNull.INSTANCE, + // GRANTEE + granteeValue, + // PRIVILEGE + getString(right), + // IS_GRANTABLE + isAdmin ? YES : NO); + } + } + } + + @Override + public ResultInterface getBestRowIdentifier(String catalog, String schema, String table, int scope, + boolean nullable) { + if (table == null) { + throw DbException.getInvalidValueException("table", null); + } + checkClosed(); + SimpleResult result = new SimpleResult(); + result.addColumn("SCOPE", TypeInfo.TYPE_SMALLINT); + result.addColumn("COLUMN_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("DATA_TYPE", TypeInfo.TYPE_INTEGER); + result.addColumn("TYPE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("COLUMN_SIZE", TypeInfo.TYPE_INTEGER); + result.addColumn("BUFFER_LENGTH", TypeInfo.TYPE_INTEGER); + result.addColumn("DECIMAL_DIGITS", TypeInfo.TYPE_SMALLINT); + result.addColumn("PSEUDO_COLUMN", TypeInfo.TYPE_SMALLINT); + if (!checkCatalogName(catalog)) { + return result; + } + for (Schema s : getSchemas(schema)) { + Table t = s.findTableOrView(session, table); + if (t == null || t.isHidden()) { + continue; + } + ArrayList constraints = t.getConstraints(); + if (constraints == null) { + continue; + } + for (Constraint constraint : constraints) { + if (constraint.getConstraintType() != Constraint.Type.PRIMARY_KEY) { + continue; + } + IndexColumn[] columns = ((ConstraintUnique) constraint).getColumns(); + for (int i = 0, l = columns.length; i < l; i++) { + IndexColumn ic = columns[i]; + Column c = ic.column; + TypeInfo type = c.getType(); + DataType dt = DataType.getDataType(type.getValueType()); + result.addRow( + // SCOPE + BEST_ROW_SESSION, + // COLUMN_NAME + getString(c.getName()), + // DATA_TYPE + ValueInteger.get(DataType.convertTypeToSQLType(type)), + // TYPE_NAME + getDataTypeName(type), + // COLUMN_SIZE + ValueInteger.get(MathUtils.convertLongToInt(type.getPrecision())), + // BUFFER_LENGTH + ValueNull.INSTANCE, + // DECIMAL_DIGITS + dt.supportsScale ? ValueSmallint.get(MathUtils.convertIntToShort(type.getScale())) + : ValueNull.INSTANCE, + // PSEUDO_COLUMN + BEST_ROW_NOT_PSEUDO); + } + } + } + // Order by SCOPE (always the same) + return result; + } + + private Value getDataTypeName(TypeInfo typeInfo) { + return getString(typeInfo.getDeclaredTypeName()); + } + + @Override + public ResultInterface getPrimaryKeys(String catalog, String schema, String table) { + if (table == null) { + throw DbException.getInvalidValueException("table", null); + } + checkClosed(); + SimpleResult result = new SimpleResult(); + result.addColumn("TABLE_CAT", TypeInfo.TYPE_VARCHAR); + result.addColumn("TABLE_SCHEM", TypeInfo.TYPE_VARCHAR); + result.addColumn("TABLE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("COLUMN_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("KEY_SEQ", TypeInfo.TYPE_SMALLINT); + result.addColumn("PK_NAME", TypeInfo.TYPE_VARCHAR); + if (!checkCatalogName(catalog)) { + return result; + } + Database db = session.getDatabase(); + Value catalogValue = getString(db.getShortName()); + for (Schema s : getSchemas(schema)) { + Table t = s.findTableOrView(session, table); + if (t == null || t.isHidden()) { + continue; + } + ArrayList constraints = t.getConstraints(); + if (constraints == null) { + continue; + } + for (Constraint constraint : constraints) { + if (constraint.getConstraintType() != Constraint.Type.PRIMARY_KEY) { + continue; + } + Value schemaValue = getString(s.getName()); + Value tableValue = getString(t.getName()); + Value pkValue = getString(constraint.getName()); + IndexColumn[] columns = ((ConstraintUnique) constraint).getColumns(); + for (int i = 0, l = columns.length; i < l;) { + result.addRow( + // TABLE_CAT + catalogValue, + // TABLE_SCHEM + schemaValue, + // TABLE_NAME + tableValue, + // COLUMN_NAME + getString(columns[i].column.getName()), + // KEY_SEQ + ValueSmallint.get((short) ++i), + // PK_NAME + pkValue); + } + } + } + // COLUMN_NAME + result.sortRows(new SortOrder(session, new int[] { 3 })); + return result; + } + + @Override + public ResultInterface getImportedKeys(String catalog, String schema, String table) { + if (table == null) { + throw DbException.getInvalidValueException("table", null); + } + SimpleResult result = initCrossReferenceResult(); + if (!checkCatalogName(catalog)) { + return result; + } + Database db = session.getDatabase(); + Value catalogValue = getString(db.getShortName()); + for (Schema s : getSchemas(schema)) { + Table t = s.findTableOrView(session, table); + if (t == null || t.isHidden()) { + continue; + } + ArrayList constraints = t.getConstraints(); + if (constraints == null) { + continue; + } + for (Constraint constraint : constraints) { + if (constraint.getConstraintType() != Constraint.Type.REFERENTIAL) { + continue; + } + ConstraintReferential fk = (ConstraintReferential) constraint; + Table fkTable = fk.getTable(); + if (fkTable != t) { + continue; + } + Table pkTable = fk.getRefTable(); + addCrossReferenceResult(result, catalogValue, pkTable.getSchema().getName(), pkTable, + fkTable.getSchema().getName(), fkTable, fk); + } + } + // PKTABLE_CAT, PKTABLE_SCHEM, PKTABLE_NAME, KEY_SEQ + result.sortRows(new SortOrder(session, new int[] { 1, 2, 8 })); + return result; + } + + @Override + public ResultInterface getExportedKeys(String catalog, String schema, String table) { + if (table == null) { + throw DbException.getInvalidValueException("table", null); + } + SimpleResult result = initCrossReferenceResult(); + if (!checkCatalogName(catalog)) { + return result; + } + Database db = session.getDatabase(); + Value catalogValue = getString(db.getShortName()); + for (Schema s : getSchemas(schema)) { + Table t = s.findTableOrView(session, table); + if (t == null || t.isHidden()) { + continue; + } + ArrayList constraints = t.getConstraints(); + if (constraints == null) { + continue; + } + for (Constraint constraint : constraints) { + if (constraint.getConstraintType() != Constraint.Type.REFERENTIAL) { + continue; + } + ConstraintReferential fk = (ConstraintReferential) constraint; + Table pkTable = fk.getRefTable(); + if (pkTable != t) { + continue; + } + Table fkTable = fk.getTable(); + addCrossReferenceResult(result, catalogValue, pkTable.getSchema().getName(), pkTable, + fkTable.getSchema().getName(), fkTable, fk); + } + } + // FKTABLE_CAT FKTABLE_SCHEM, FKTABLE_NAME, KEY_SEQ + result.sortRows(new SortOrder(session, new int[] { 5, 6, 8 })); + return result; + } + + @Override + public ResultInterface getCrossReference(String primaryCatalog, String primarySchema, String primaryTable, + String foreignCatalog, String foreignSchema, String foreignTable) { + if (primaryTable == null) { + throw DbException.getInvalidValueException("primaryTable", null); + } + if (foreignTable == null) { + throw DbException.getInvalidValueException("foreignTable", null); + } + SimpleResult result = initCrossReferenceResult(); + if (!checkCatalogName(primaryCatalog) || !checkCatalogName(foreignCatalog)) { + return result; + } + Database db = session.getDatabase(); + Value catalogValue = getString(db.getShortName()); + for (Schema s : getSchemas(foreignSchema)) { + Table t = s.findTableOrView(session, foreignTable); + if (t == null || t.isHidden()) { + continue; + } + ArrayList constraints = t.getConstraints(); + if (constraints == null) { + continue; + } + for (Constraint constraint : constraints) { + if (constraint.getConstraintType() != Constraint.Type.REFERENTIAL) { + continue; + } + ConstraintReferential fk = (ConstraintReferential) constraint; + Table fkTable = fk.getTable(); + if (fkTable != t) { + continue; + } + Table pkTable = fk.getRefTable(); + if (!db.equalsIdentifiers(pkTable.getName(), primaryTable)) { + continue; + } + Schema pkSchema = pkTable.getSchema(); + if (!checkSchema(primarySchema, pkSchema)) { + continue; + } + addCrossReferenceResult(result, catalogValue, pkSchema.getName(), pkTable, + fkTable.getSchema().getName(), fkTable, fk); + } + } + // FKTABLE_CAT FKTABLE_SCHEM, FKTABLE_NAME, KEY_SEQ + result.sortRows(new SortOrder(session, new int[] { 5, 6, 8 })); + return result; + } + + private SimpleResult initCrossReferenceResult() { + checkClosed(); + SimpleResult result = new SimpleResult(); + result.addColumn("PKTABLE_CAT", TypeInfo.TYPE_VARCHAR); + result.addColumn("PKTABLE_SCHEM", TypeInfo.TYPE_VARCHAR); + result.addColumn("PKTABLE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("PKCOLUMN_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("FKTABLE_CAT", TypeInfo.TYPE_VARCHAR); + result.addColumn("FKTABLE_SCHEM", TypeInfo.TYPE_VARCHAR); + result.addColumn("FKTABLE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("FKCOLUMN_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("KEY_SEQ", TypeInfo.TYPE_SMALLINT); + result.addColumn("UPDATE_RULE", TypeInfo.TYPE_SMALLINT); + result.addColumn("DELETE_RULE", TypeInfo.TYPE_SMALLINT); + result.addColumn("FK_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("PK_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("DEFERRABILITY", TypeInfo.TYPE_SMALLINT); + return result; + } + + private void addCrossReferenceResult(SimpleResult result, Value catalog, String pkSchema, Table pkTable, + String fkSchema, Table fkTable, ConstraintReferential fk) { + Value pkSchemaValue = getString(pkSchema); + Value pkTableValue = getString(pkTable.getName()); + Value fkSchemaValue = getString(fkSchema); + Value fkTableValue = getString(fkTable.getName()); + IndexColumn[] pkCols = fk.getRefColumns(); + IndexColumn[] fkCols = fk.getColumns(); + Value update = getRefAction(fk.getUpdateAction()); + Value delete = getRefAction(fk.getDeleteAction()); + Value fkNameValue = getString(fk.getName()); + Value pkNameValue = getString(fk.getReferencedConstraint().getName()); + for (int j = 0, len = fkCols.length; j < len; j++) { + result.addRow( + // PKTABLE_CAT + catalog, + // PKTABLE_SCHEM + pkSchemaValue, + // PKTABLE_NAME + pkTableValue, + // PKCOLUMN_NAME + getString(pkCols[j].column.getName()), + // FKTABLE_CAT + catalog, + // FKTABLE_SCHEM + fkSchemaValue, + // FKTABLE_NAME + fkTableValue, + // FKCOLUMN_NAME + getString(fkCols[j].column.getName()), + // KEY_SEQ + ValueSmallint.get((short) (j + 1)), + // UPDATE_RULE + update, + // DELETE_RULE + delete, + // FK_NAME + fkNameValue, + // PK_NAME + pkNameValue, + // DEFERRABILITY + IMPORTED_KEY_NOT_DEFERRABLE); + } + } + + private static ValueSmallint getRefAction(ConstraintActionType action) { + switch (action) { + case CASCADE: + return IMPORTED_KEY_CASCADE; + case RESTRICT: + return IMPORTED_KEY_RESTRICT; + case SET_DEFAULT: + return IMPORTED_KEY_DEFAULT; + case SET_NULL: + return IMPORTED_KEY_SET_NULL; + default: + throw DbException.getInternalError("action=" + action); + } + } + + @Override + public ResultInterface getTypeInfo() { + checkClosed(); + SimpleResult result = new SimpleResult(); + result.addColumn("TYPE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("DATA_TYPE", TypeInfo.TYPE_INTEGER); + result.addColumn("PRECISION", TypeInfo.TYPE_INTEGER); + result.addColumn("LITERAL_PREFIX", TypeInfo.TYPE_VARCHAR); + result.addColumn("LITERAL_SUFFIX", TypeInfo.TYPE_VARCHAR); + result.addColumn("CREATE_PARAMS", TypeInfo.TYPE_VARCHAR); + result.addColumn("NULLABLE", TypeInfo.TYPE_SMALLINT); + result.addColumn("CASE_SENSITIVE", TypeInfo.TYPE_BOOLEAN); + result.addColumn("SEARCHABLE", TypeInfo.TYPE_SMALLINT); + result.addColumn("UNSIGNED_ATTRIBUTE", TypeInfo.TYPE_BOOLEAN); + result.addColumn("FIXED_PREC_SCALE", TypeInfo.TYPE_BOOLEAN); + result.addColumn("AUTO_INCREMENT", TypeInfo.TYPE_BOOLEAN); + result.addColumn("LOCAL_TYPE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("MINIMUM_SCALE", TypeInfo.TYPE_SMALLINT); + result.addColumn("MAXIMUM_SCALE", TypeInfo.TYPE_SMALLINT); + result.addColumn("SQL_DATA_TYPE", TypeInfo.TYPE_INTEGER); + result.addColumn("SQL_DATETIME_SUB", TypeInfo.TYPE_INTEGER); + result.addColumn("NUM_PREC_RADIX", TypeInfo.TYPE_INTEGER); + for (int i = 1, l = Value.TYPE_COUNT; i < l; i++) { + DataType t = DataType.getDataType(i); + Value name = getString(Value.getTypeName(t.type)); + result.addRow( + // TYPE_NAME + name, + // DATA_TYPE + ValueInteger.get(t.sqlType), + // PRECISION + ValueInteger.get(MathUtils.convertLongToInt(t.maxPrecision)), + // LITERAL_PREFIX + getString(t.prefix), + // LITERAL_SUFFIX + getString(t.suffix), + // CREATE_PARAMS + getString(t.params), + // NULLABLE + TYPE_NULLABLE, + // CASE_SENSITIVE + ValueBoolean.get(t.caseSensitive), + // SEARCHABLE + TYPE_SEARCHABLE, + // UNSIGNED_ATTRIBUTE + ValueBoolean.FALSE, + // FIXED_PREC_SCALE + ValueBoolean.get(t.type == Value.NUMERIC), + // AUTO_INCREMENT + ValueBoolean.FALSE, + // LOCAL_TYPE_NAME + name, + // MINIMUM_SCALE + ValueSmallint.get(MathUtils.convertIntToShort(t.minScale)), + // MAXIMUM_SCALE + ValueSmallint.get(MathUtils.convertIntToShort(t.maxScale)), + // SQL_DATA_TYPE (unused) + ValueNull.INSTANCE, + // SQL_DATETIME_SUB (unused) + ValueNull.INSTANCE, + // NUM_PREC_RADIX + getRadix(t.type, false)); + } + // DATA_TYPE, better types first + result.sortRows(new SortOrder(session, new int[] { 1 })); + return result; + } + + private static Value getRadix(int valueType, boolean small) { + if (DataType.isNumericType(valueType)) { + int radix = valueType == Value.NUMERIC || valueType == Value.DECFLOAT ? 10 : 2; + return small ? ValueSmallint.get((short) radix) : ValueInteger.get(radix); + } + return ValueNull.INSTANCE; + } + + @Override + public ResultInterface getIndexInfo(String catalog, String schema, String table, boolean unique, + boolean approximate) { + if (table == null) { + throw DbException.getInvalidValueException("table", null); + } + checkClosed(); + SimpleResult result = new SimpleResult(); + result.addColumn("TABLE_CAT", TypeInfo.TYPE_VARCHAR); + result.addColumn("TABLE_SCHEM", TypeInfo.TYPE_VARCHAR); + result.addColumn("TABLE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("NON_UNIQUE", TypeInfo.TYPE_BOOLEAN); + result.addColumn("INDEX_QUALIFIER", TypeInfo.TYPE_VARCHAR); + result.addColumn("INDEX_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("TYPE", TypeInfo.TYPE_SMALLINT); + result.addColumn("ORDINAL_POSITION", TypeInfo.TYPE_SMALLINT); + result.addColumn("COLUMN_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("ASC_OR_DESC", TypeInfo.TYPE_VARCHAR); + result.addColumn("CARDINALITY", TypeInfo.TYPE_BIGINT); + result.addColumn("PAGES", TypeInfo.TYPE_BIGINT); + result.addColumn("FILTER_CONDITION", TypeInfo.TYPE_VARCHAR); + if (!checkCatalogName(catalog)) { + return result; + } + Database db = session.getDatabase(); + Value catalogValue = getString(db.getShortName()); + for (Schema s : getSchemas(schema)) { + Table t = s.findTableOrView(session, table); + if (t == null || t.isHidden()) { + continue; + } + getIndexInfo(catalogValue, getString(s.getName()), t, unique, approximate, result, db); + } + // NON_UNIQUE, TYPE, INDEX_NAME, ORDINAL_POSITION + result.sortRows(new SortOrder(session, new int[] { 3, 6, 5, 7 })); + return result; + } + + private void getIndexInfo(Value catalogValue, Value schemaValue, Table table, boolean unique, boolean approximate, + SimpleResult result, Database db) { + ArrayList indexes = table.getIndexes(); + if (indexes != null) { + for (Index index : indexes) { + if (index.getCreateSQL() == null) { + continue; + } + int uniqueColumnCount = index.getUniqueColumnCount(); + if (unique && uniqueColumnCount == 0) { + continue; + } + Value tableValue = getString(table.getName()); + Value indexValue = getString(index.getName()); + IndexColumn[] cols = index.getIndexColumns(); + ValueSmallint type = index.getIndexType().isHash() ? TABLE_INDEX_HASHED : TABLE_INDEX_OTHER; + for (int i = 0, l = cols.length; i < l; i++) { + IndexColumn c = cols[i]; + boolean nonUnique = i >= uniqueColumnCount; + if (unique && nonUnique) { + break; + } + result.addRow( + // TABLE_CAT + catalogValue, + // TABLE_SCHEM + schemaValue, + // TABLE_NAME + tableValue, + // NON_UNIQUE + ValueBoolean.get(nonUnique), + // INDEX_QUALIFIER + catalogValue, + // INDEX_NAME + indexValue, + // TYPE + type, + // ORDINAL_POSITION + ValueSmallint.get((short) (i + 1)), + // COLUMN_NAME + getString(c.column.getName()), + // ASC_OR_DESC + getString((c.sortType & SortOrder.DESCENDING) != 0 ? "D" : "A"), + // CARDINALITY + ValueBigint.get(approximate // + ? index.getRowCountApproximation(session) + : index.getRowCount(session)), + // PAGES + ValueBigint.get(index.getDiskSpaceUsed() / db.getPageSize()), + // FILTER_CONDITION + ValueNull.INSTANCE); + } + } + } + } + + @Override + public ResultInterface getSchemas(String catalog, String schemaPattern) { + checkClosed(); + SimpleResult result = new SimpleResult(); + result.addColumn("TABLE_SCHEM", TypeInfo.TYPE_VARCHAR); + result.addColumn("TABLE_CATALOG", TypeInfo.TYPE_VARCHAR); + if (!checkCatalogName(catalog)) { + return result; + } + CompareLike schemaLike = getLike(schemaPattern); + Collection allSchemas = session.getDatabase().getAllSchemas(); + Value catalogValue = getString(session.getDatabase().getShortName()); + if (schemaLike == null) { + for (Schema s : allSchemas) { + result.addRow(getString(s.getName()), catalogValue); + } + } else { + for (Schema s : allSchemas) { + String name = s.getName(); + if (schemaLike.test(name)) { + result.addRow(getString(s.getName()), catalogValue); + } + } + } + // TABLE_CATALOG, TABLE_SCHEM + result.sortRows(new SortOrder(session, new int[] { 0 })); + return result; + } + + @Override + public ResultInterface getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, + String columnNamePattern) { + SimpleResult result = getPseudoColumnsResult(); + if (!checkCatalogName(catalog)) { + return result; + } + Database db = session.getDatabase(); + Value catalogValue = getString(db.getShortName()); + CompareLike columnLike = getLike(columnNamePattern); + for (Schema schema : getSchemasForPattern(schemaPattern)) { + Value schemaValue = getString(schema.getName()); + for (SchemaObject object : getTablesForPattern(schema, tableNamePattern)) { + Value tableName = getString(object.getName()); + if (object instanceof Table) { + Table t = (Table) object; + if (!t.isHidden()) { + getPseudoColumnsAdd(result, catalogValue, schemaValue, tableName, t, columnLike); + } + } else { + TableSynonym s = (TableSynonym) object; + Table t = s.getSynonymFor(); + getPseudoColumnsAdd(result, catalogValue, schemaValue, tableName, t, columnLike); + } + } + } + // TABLE_CAT, TABLE_SCHEM, TABLE_NAME, COLUMN_NAME + result.sortRows(new SortOrder(session, new int[] { 1, 2, 3 })); + return result; + } + + private void getPseudoColumnsAdd(SimpleResult result, Value catalogValue, Value schemaValue, Value tableName, + Table t, CompareLike columnLike) { + Column rowId = t.getRowIdColumn(); + if (rowId != null) { + getPseudoColumnsAdd(result, catalogValue, schemaValue, tableName, columnLike, rowId); + } + for (Column c : t.getColumns()) { + if (!c.getVisible()) { + getPseudoColumnsAdd(result, catalogValue, schemaValue, tableName, columnLike, c); + } + } + } + + private void getPseudoColumnsAdd(SimpleResult result, Value catalogValue, Value schemaValue, Value tableName, + CompareLike columnLike, Column c) { + String name = c.getName(); + if (columnLike != null && !columnLike.test(name)) { + return; + } + TypeInfo type = c.getType(); + ValueInteger precision = ValueInteger.get(MathUtils.convertLongToInt(type.getPrecision())); + result.addRow( + // TABLE_CAT + catalogValue, + // TABLE_SCHEM + schemaValue, + // TABLE_NAME + tableName, + // COLUMN_NAME + getString(name), + // DATA_TYPE + ValueInteger.get(DataType.convertTypeToSQLType(type)), + // COLUMN_SIZE + precision, + // DECIMAL_DIGITS + ValueInteger.get(type.getScale()), + // NUM_PREC_RADIX + getRadix(type.getValueType(), false), + // COLUMN_USAGE + NO_USAGE_RESTRICTIONS, + // REMARKS + getString(c.getComment()), + // CHAR_OCTET_LENGTH + precision, + // IS_NULLABLE + c.isNullable() ? YES : NO); + } + + @Override + void checkClosed() { + if (session.isClosed()) { + throw DbException.get(ErrorCode.DATABASE_CALLED_AT_SHUTDOWN); + } + } + + Value getString(String string) { + return string != null ? ValueVarchar.get(string, session) : ValueNull.INSTANCE; + } + + private boolean checkCatalogName(String catalog) { + if (catalog != null && !catalog.isEmpty()) { + Database db = session.getDatabase(); + return db.equalsIdentifiers(catalog, db.getShortName()); + } + return true; + } + + private Collection getSchemas(String schema) { + Database db = session.getDatabase(); + if (schema == null) { + return db.getAllSchemas(); + } else if (schema.isEmpty()) { + return Collections.singleton(db.getMainSchema()); + } else { + Schema s = db.findSchema(schema); + if (s != null) { + return Collections.singleton(s); + } + return Collections.emptySet(); + } + } + + private Collection getSchemasForPattern(String schemaPattern) { + Database db = session.getDatabase(); + if (schemaPattern == null) { + return db.getAllSchemas(); + } else if (schemaPattern.isEmpty()) { + return Collections.singleton(db.getMainSchema()); + } else { + ArrayList list = Utils.newSmallArrayList(); + CompareLike like = getLike(schemaPattern); + for (Schema s : db.getAllSchemas()) { + if (like.test(s.getName())) { + list.add(s); + } + } + return list; + } + } + + private Collection getTablesForPattern(Schema schema, String tablePattern) { + Collection tables = schema.getAllTablesAndViews(session); + Collection synonyms = schema.getAllSynonyms(); + if (tablePattern == null) { + if (tables.isEmpty()) { + return synonyms; + } else if (synonyms.isEmpty()) { + return tables; + } + ArrayList list = new ArrayList<>(tables.size() + synonyms.size()); + list.addAll(tables); + list.addAll(synonyms); + return list; + } else if (tables.isEmpty() && synonyms.isEmpty()) { + return Collections.emptySet(); + } else { + ArrayList list = Utils.newSmallArrayList(); + CompareLike like = getLike(tablePattern); + for (Table t : tables) { + if (like.test(t.getName())) { + list.add(t); + } + } + for (TableSynonym t : synonyms) { + if (like.test(t.getName())) { + list.add(t); + } + } + return list; + } + } + + private boolean checkSchema(String schemaName, Schema schema) { + if (schemaName == null) { + return true; + } else if (schemaName.isEmpty()) { + return schema == session.getDatabase().getMainSchema(); + } else { + return session.getDatabase().equalsIdentifiers(schemaName, schema.getName()); + } + } + + private CompareLike getLike(String pattern) { + if (pattern == null) { + return null; + } + CompareLike like = new CompareLike(session.getDatabase().getCompareMode(), "\\", null, false, false, null, // + null, CompareLike.LikeType.LIKE); + like.initPattern(pattern, '\\'); + return like; + } + +} diff --git a/h2/src/main/org/h2/jdbc/meta/DatabaseMetaLocalBase.java b/h2/src/main/org/h2/jdbc/meta/DatabaseMetaLocalBase.java new file mode 100644 index 0000000..70a96e6 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/meta/DatabaseMetaLocalBase.java @@ -0,0 +1,173 @@ +/* + * 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.jdbc.meta; + +import org.h2.engine.Constants; +import org.h2.result.ResultInterface; +import org.h2.result.SimpleResult; +import org.h2.value.TypeInfo; + +/** + * Base implementation of database meta information. + */ +abstract class DatabaseMetaLocalBase extends DatabaseMeta { + + @Override + public final String getDatabaseProductVersion() { + return Constants.FULL_VERSION; + } + + @Override + public final ResultInterface getVersionColumns(String catalog, String schema, String table) { + checkClosed(); + SimpleResult result = new SimpleResult(); + result.addColumn("SCOPE", TypeInfo.TYPE_SMALLINT); + result.addColumn("COLUMN_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("DATA_TYPE", TypeInfo.TYPE_INTEGER); + result.addColumn("TYPE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("COLUMN_SIZE", TypeInfo.TYPE_INTEGER); + result.addColumn("BUFFER_LENGTH", TypeInfo.TYPE_INTEGER); + result.addColumn("DECIMAL_DIGITS", TypeInfo.TYPE_SMALLINT); + result.addColumn("PSEUDO_COLUMN", TypeInfo.TYPE_SMALLINT); + return result; + } + + @Override + public final ResultInterface getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) { + checkClosed(); + SimpleResult result = new SimpleResult(); + result.addColumn("TYPE_CAT", TypeInfo.TYPE_VARCHAR); + result.addColumn("TYPE_SCHEM", TypeInfo.TYPE_VARCHAR); + result.addColumn("TYPE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("CLASS_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("DATA_TYPE", TypeInfo.TYPE_INTEGER); + result.addColumn("REMARKS", TypeInfo.TYPE_VARCHAR); + result.addColumn("BASE_TYPE", TypeInfo.TYPE_SMALLINT); + return result; + } + + @Override + public final ResultInterface getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) { + checkClosed(); + SimpleResult result = new SimpleResult(); + result.addColumn("TYPE_CAT", TypeInfo.TYPE_VARCHAR); + result.addColumn("TYPE_SCHEM", TypeInfo.TYPE_VARCHAR); + result.addColumn("TYPE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("SUPERTYPE_CAT", TypeInfo.TYPE_VARCHAR); + result.addColumn("SUPERTYPE_SCHEM", TypeInfo.TYPE_VARCHAR); + result.addColumn("SUPERTYPE_NAME", TypeInfo.TYPE_VARCHAR); + return result; + } + + @Override + public final ResultInterface getSuperTables(String catalog, String schemaPattern, String tableNamePattern) { + checkClosed(); + SimpleResult result = new SimpleResult(); + result.addColumn("TABLE_CAT", TypeInfo.TYPE_VARCHAR); + result.addColumn("TABLE_SCHEM", TypeInfo.TYPE_VARCHAR); + result.addColumn("TABLE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("SUPERTABLE_NAME", TypeInfo.TYPE_VARCHAR); + return result; + } + + @Override + public final ResultInterface getAttributes(String catalog, String schemaPattern, String typeNamePattern, + String attributeNamePattern) { + checkClosed(); + SimpleResult result = new SimpleResult(); + result.addColumn("TYPE_CAT", TypeInfo.TYPE_VARCHAR); + result.addColumn("TYPE_SCHEM", TypeInfo.TYPE_VARCHAR); + result.addColumn("TYPE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("ATTR_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("DATA_TYPE", TypeInfo.TYPE_INTEGER); + result.addColumn("ATTR_TYPE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("ATTR_SIZE", TypeInfo.TYPE_INTEGER); + result.addColumn("DECIMAL_DIGITS", TypeInfo.TYPE_INTEGER); + result.addColumn("NUM_PREC_RADIX", TypeInfo.TYPE_INTEGER); + result.addColumn("NULLABLE", TypeInfo.TYPE_INTEGER); + result.addColumn("REMARKS", TypeInfo.TYPE_VARCHAR); + result.addColumn("ATTR_DEF", TypeInfo.TYPE_VARCHAR); + result.addColumn("SQL_DATA_TYPE", TypeInfo.TYPE_INTEGER); + result.addColumn("SQL_DATETIME_SUB", TypeInfo.TYPE_INTEGER); + result.addColumn("CHAR_OCTET_LENGTH", TypeInfo.TYPE_INTEGER); + result.addColumn("ORDINAL_POSITION", TypeInfo.TYPE_INTEGER); + result.addColumn("IS_NULLABLE", TypeInfo.TYPE_VARCHAR); + result.addColumn("SCOPE_CATALOG", TypeInfo.TYPE_VARCHAR); + result.addColumn("SCOPE_SCHEMA", TypeInfo.TYPE_VARCHAR); + result.addColumn("SCOPE_TABLE", TypeInfo.TYPE_VARCHAR); + result.addColumn("SOURCE_DATA_TYPE", TypeInfo.TYPE_SMALLINT); + return result; + } + + @Override + public final int getDatabaseMajorVersion() { + return Constants.VERSION_MAJOR; + } + + @Override + public final int getDatabaseMinorVersion() { + return Constants.VERSION_MINOR; + } + + @Override + public final ResultInterface getFunctions(String catalog, String schemaPattern, String functionNamePattern) { + checkClosed(); + SimpleResult result = new SimpleResult(); + result.addColumn("FUNCTION_CAT", TypeInfo.TYPE_VARCHAR); + result.addColumn("FUNCTION_SCHEM", TypeInfo.TYPE_VARCHAR); + result.addColumn("FUNCTION_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("REMARKS", TypeInfo.TYPE_VARCHAR); + result.addColumn("FUNCTION_TYPE", TypeInfo.TYPE_SMALLINT); + result.addColumn("SPECIFIC_NAME", TypeInfo.TYPE_VARCHAR); + return result; + } + + @Override + public final ResultInterface getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern, + String columnNamePattern) { + checkClosed(); + SimpleResult result = new SimpleResult(); + result.addColumn("FUNCTION_CAT", TypeInfo.TYPE_VARCHAR); + result.addColumn("FUNCTION_SCHEM", TypeInfo.TYPE_VARCHAR); + result.addColumn("FUNCTION_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("COLUMN_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("COLUMN_TYPE", TypeInfo.TYPE_SMALLINT); + result.addColumn("DATA_TYPE", TypeInfo.TYPE_INTEGER); + result.addColumn("TYPE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("PRECISION", TypeInfo.TYPE_INTEGER); + result.addColumn("LENGTH", TypeInfo.TYPE_INTEGER); + result.addColumn("SCALE", TypeInfo.TYPE_SMALLINT); + result.addColumn("RADIX", TypeInfo.TYPE_SMALLINT); + result.addColumn("NULLABLE", TypeInfo.TYPE_SMALLINT); + result.addColumn("REMARKS", TypeInfo.TYPE_VARCHAR); + result.addColumn("CHAR_OCTET_LENGTH", TypeInfo.TYPE_INTEGER); + result.addColumn("ORDINAL_POSITION", TypeInfo.TYPE_INTEGER); + result.addColumn("IS_NULLABLE", TypeInfo.TYPE_VARCHAR); + result.addColumn("SPECIFIC_NAME", TypeInfo.TYPE_VARCHAR); + return result; + } + + final SimpleResult getPseudoColumnsResult() { + checkClosed(); + SimpleResult result = new SimpleResult(); + result.addColumn("TABLE_CAT", TypeInfo.TYPE_VARCHAR); + result.addColumn("TABLE_SCHEM", TypeInfo.TYPE_VARCHAR); + result.addColumn("TABLE_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("COLUMN_NAME", TypeInfo.TYPE_VARCHAR); + result.addColumn("DATA_TYPE", TypeInfo.TYPE_INTEGER); + result.addColumn("COLUMN_SIZE", TypeInfo.TYPE_INTEGER); + result.addColumn("DECIMAL_DIGITS", TypeInfo.TYPE_INTEGER); + result.addColumn("NUM_PREC_RADIX", TypeInfo.TYPE_INTEGER); + result.addColumn("COLUMN_USAGE", TypeInfo.TYPE_VARCHAR); + result.addColumn("REMARKS", TypeInfo.TYPE_VARCHAR); + result.addColumn("CHAR_OCTET_LENGTH", TypeInfo.TYPE_INTEGER); + result.addColumn("IS_NULLABLE", TypeInfo.TYPE_VARCHAR); + return result; + } + + abstract void checkClosed(); + +} diff --git a/h2/src/main/org/h2/jdbc/meta/DatabaseMetaRemote.java b/h2/src/main/org/h2/jdbc/meta/DatabaseMetaRemote.java new file mode 100644 index 0000000..8c09983 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/meta/DatabaseMetaRemote.java @@ -0,0 +1,383 @@ +/* + * 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.jdbc.meta; + +import java.io.IOException; +import java.util.ArrayList; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionRemote; +import org.h2.message.DbException; +import org.h2.mode.DefaultNullOrdering; +import org.h2.result.ResultInterface; +import org.h2.result.ResultRemote; +import org.h2.value.Transfer; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueArray; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueInteger; +import org.h2.value.ValueNull; +import org.h2.value.ValueVarchar; + +/** + * Remote implementation of database meta information. + */ +public class DatabaseMetaRemote extends DatabaseMeta { + + static final int DEFAULT_NULL_ORDERING = 0; + + static final int GET_DATABASE_PRODUCT_VERSION = 1; + + static final int GET_SQL_KEYWORDS = 2; + + static final int GET_NUMERIC_FUNCTIONS = 3; + + static final int GET_STRING_FUNCTIONS = 4; + + static final int GET_SYSTEM_FUNCTIONS = 5; + + static final int GET_TIME_DATE_FUNCTIONS = 6; + + static final int GET_SEARCH_STRING_ESCAPE = 7; + + static final int GET_PROCEDURES_3 = 8; + + static final int GET_PROCEDURE_COLUMNS_4 = 9; + + static final int GET_TABLES_4 = 10; + + static final int GET_SCHEMAS = 11; + + static final int GET_CATALOGS = 12; + + static final int GET_TABLE_TYPES = 13; + + static final int GET_COLUMNS_4 = 14; + + static final int GET_COLUMN_PRIVILEGES_4 = 15; + + static final int GET_TABLE_PRIVILEGES_3 = 16; + + static final int GET_BEST_ROW_IDENTIFIER_5 = 17; + + static final int GET_VERSION_COLUMNS_3 = 18; + + static final int GET_PRIMARY_KEYS_3 = 19; + + static final int GET_IMPORTED_KEYS_3 = 20; + + static final int GET_EXPORTED_KEYS_3 = 21; + + static final int GET_CROSS_REFERENCE_6 = 22; + + static final int GET_TYPE_INFO = 23; + + static final int GET_INDEX_INFO_5 = 24; + + static final int GET_UDTS_4 = 25; + + static final int GET_SUPER_TYPES_3 = 26; + + static final int GET_SUPER_TABLES_3 = 27; + + static final int GET_ATTRIBUTES_4 = 28; + + static final int GET_DATABASE_MAJOR_VERSION = 29; + + static final int GET_DATABASE_MINOR_VERSION = 30; + + static final int GET_SCHEMAS_2 = 31; + + static final int GET_FUNCTIONS_3 = 32; + + static final int GET_FUNCTION_COLUMNS_4 = 33; + + static final int GET_PSEUDO_COLUMNS_4 = 34; + + private final SessionRemote session; + + private final ArrayList transferList; + + public DatabaseMetaRemote(SessionRemote session, ArrayList transferList) { + this.session = session; + this.transferList = transferList; + } + + @Override + public DefaultNullOrdering defaultNullOrdering() { + ResultInterface result = executeQuery(DEFAULT_NULL_ORDERING); + result.next(); + return DefaultNullOrdering.valueOf(result.currentRow()[0].getInt()); + } + + @Override + public String getDatabaseProductVersion() { + ResultInterface result = executeQuery(GET_DATABASE_PRODUCT_VERSION); + result.next(); + return result.currentRow()[0].getString(); + } + + @Override + public String getSQLKeywords() { + ResultInterface result = executeQuery(GET_SQL_KEYWORDS); + result.next(); + return result.currentRow()[0].getString(); + } + + @Override + public String getNumericFunctions() { + ResultInterface result = executeQuery(GET_NUMERIC_FUNCTIONS); + result.next(); + return result.currentRow()[0].getString(); + } + + @Override + public String getStringFunctions() { + ResultInterface result = executeQuery(GET_STRING_FUNCTIONS); + result.next(); + return result.currentRow()[0].getString(); + } + + @Override + public String getSystemFunctions() { + ResultInterface result = executeQuery(GET_SYSTEM_FUNCTIONS); + result.next(); + return result.currentRow()[0].getString(); + } + + @Override + public String getTimeDateFunctions() { + ResultInterface result = executeQuery(GET_TIME_DATE_FUNCTIONS); + result.next(); + return result.currentRow()[0].getString(); + } + + @Override + public String getSearchStringEscape() { + ResultInterface result = executeQuery(GET_SEARCH_STRING_ESCAPE); + result.next(); + return result.currentRow()[0].getString(); + } + + @Override + public ResultInterface getProcedures(String catalog, String schemaPattern, String procedureNamePattern) { + return executeQuery(GET_PROCEDURES_3, getString(catalog), getString(schemaPattern), + getString(procedureNamePattern)); + } + + @Override + public ResultInterface getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, + String columnNamePattern) { + return executeQuery(GET_PROCEDURE_COLUMNS_4, getString(catalog), getString(schemaPattern), + getString(procedureNamePattern), getString(columnNamePattern)); + } + + @Override + public ResultInterface getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types) { + return executeQuery(GET_TABLES_4, getString(catalog), getString(schemaPattern), getString(tableNamePattern), + getStringArray(types)); + } + + @Override + public ResultInterface getSchemas() { + return executeQuery(GET_SCHEMAS); + } + + @Override + public ResultInterface getCatalogs() { + return executeQuery(GET_CATALOGS); + } + + @Override + public ResultInterface getTableTypes() { + return executeQuery(GET_TABLE_TYPES); + } + + @Override + public ResultInterface getColumns(String catalog, String schemaPattern, String tableNamePattern, + String columnNamePattern) { + return executeQuery(GET_COLUMNS_4, getString(catalog), getString(schemaPattern), getString(tableNamePattern), + getString(columnNamePattern)); + } + + @Override + public ResultInterface getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) { + return executeQuery(GET_COLUMN_PRIVILEGES_4, getString(catalog), getString(schema), getString(table), + getString(columnNamePattern)); + } + + @Override + public ResultInterface getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) { + return executeQuery(GET_TABLE_PRIVILEGES_3, getString(catalog), getString(schemaPattern), // + getString(tableNamePattern)); + } + + @Override + public ResultInterface getBestRowIdentifier(String catalog, String schema, String table, int scope, + boolean nullable) { + return executeQuery(GET_BEST_ROW_IDENTIFIER_5, getString(catalog), getString(schema), getString(table), + ValueInteger.get(scope), ValueBoolean.get(nullable)); + } + + @Override + public ResultInterface getVersionColumns(String catalog, String schema, String table) { + return executeQuery(GET_VERSION_COLUMNS_3, getString(catalog), getString(schema), getString(table)); + } + + @Override + public ResultInterface getPrimaryKeys(String catalog, String schema, String table) { + return executeQuery(GET_PRIMARY_KEYS_3, getString(catalog), getString(schema), getString(table)); + } + + @Override + public ResultInterface getImportedKeys(String catalog, String schema, String table) { + return executeQuery(GET_IMPORTED_KEYS_3, getString(catalog), getString(schema), getString(table)); + } + + @Override + public ResultInterface getExportedKeys(String catalog, String schema, String table) { + return executeQuery(GET_EXPORTED_KEYS_3, getString(catalog), getString(schema), getString(table)); + } + + @Override + public ResultInterface getCrossReference(String primaryCatalog, String primarySchema, String primaryTable, + String foreignCatalog, String foreignSchema, String foreignTable) { + return executeQuery(GET_CROSS_REFERENCE_6, getString(primaryCatalog), getString(primarySchema), + getString(primaryTable), getString(foreignCatalog), getString(foreignSchema), getString(foreignTable)); + } + + @Override + public ResultInterface getTypeInfo() { + return executeQuery(GET_TYPE_INFO); + } + + @Override + public ResultInterface getIndexInfo(String catalog, String schema, String table, boolean unique, + boolean approximate) { + return executeQuery(GET_INDEX_INFO_5, getString(catalog), getString(schema), // + getString(table), ValueBoolean.get(unique), ValueBoolean.get(approximate)); + } + + @Override + public ResultInterface getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) { + return executeQuery(GET_UDTS_4, getString(catalog), getString(schemaPattern), getString(typeNamePattern), + getIntArray(types)); + } + + @Override + public ResultInterface getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) { + return executeQuery(GET_SUPER_TYPES_3, getString(catalog), getString(schemaPattern), + getString(typeNamePattern)); + } + + @Override + public ResultInterface getSuperTables(String catalog, String schemaPattern, String tableNamePattern) { + return executeQuery(GET_SUPER_TABLES_3, getString(catalog), getString(schemaPattern), + getString(tableNamePattern)); + } + + @Override + public ResultInterface getAttributes(String catalog, String schemaPattern, String typeNamePattern, + String attributeNamePattern) { + return executeQuery(GET_ATTRIBUTES_4, getString(catalog), getString(schemaPattern), getString(typeNamePattern), + getString(attributeNamePattern)); + } + + @Override + public int getDatabaseMajorVersion() { + ResultInterface result = executeQuery(GET_DATABASE_MAJOR_VERSION); + result.next(); + return result.currentRow()[0].getInt(); + } + + @Override + public int getDatabaseMinorVersion() { + ResultInterface result = executeQuery(GET_DATABASE_MINOR_VERSION); + result.next(); + return result.currentRow()[0].getInt(); + } + + @Override + public ResultInterface getSchemas(String catalog, String schemaPattern) { + return executeQuery(GET_SCHEMAS_2, getString(catalog), getString(schemaPattern)); + } + + @Override + public ResultInterface getFunctions(String catalog, String schemaPattern, String functionNamePattern) { + return executeQuery(GET_FUNCTIONS_3, getString(catalog), getString(schemaPattern), + getString(functionNamePattern)); + } + + @Override + public ResultInterface getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern, + String columnNamePattern) { + return executeQuery(GET_FUNCTION_COLUMNS_4, getString(catalog), getString(schemaPattern), + getString(functionNamePattern), getString(columnNamePattern)); + } + + @Override + public ResultInterface getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, + String columnNamePattern) { + return executeQuery(GET_PSEUDO_COLUMNS_4, getString(catalog), getString(schemaPattern), + getString(tableNamePattern), getString(columnNamePattern)); + } + + private ResultInterface executeQuery(int code, Value... args) { + if (session.isClosed()) { + throw DbException.get(ErrorCode.DATABASE_CALLED_AT_SHUTDOWN); + } + synchronized (session) { + int objectId = session.getNextId(); + for (int i = 0, count = 0; i < transferList.size(); i++) { + Transfer transfer = transferList.get(i); + try { + session.traceOperation("GET_META", objectId); + int len = args.length; + transfer.writeInt(SessionRemote.GET_JDBC_META).writeInt(code).writeInt(len); + for (int j = 0; j < len; j++) { + transfer.writeValue(args[j]); + } + session.done(transfer); + int columnCount = transfer.readInt(); + return new ResultRemote(session, transfer, objectId, columnCount, Integer.MAX_VALUE); + } catch (IOException e) { + session.removeServer(e, i--, ++count); + } + } + return null; + } + } + + private Value getIntArray(int[] array) { + if (array == null) { + return ValueNull.INSTANCE; + } + int cardinality = array.length; + Value[] values = new Value[cardinality]; + for (int i = 0; i < cardinality; i++) { + values[i] = ValueInteger.get(array[i]); + } + return ValueArray.get(TypeInfo.TYPE_INTEGER, values, session); + } + + private Value getStringArray(String[] array) { + if (array == null) { + return ValueNull.INSTANCE; + } + int cardinality = array.length; + Value[] values = new Value[cardinality]; + for (int i = 0; i < cardinality; i++) { + values[i] = getString(array[i]); + } + return ValueArray.get(TypeInfo.TYPE_VARCHAR, values, session); + } + + private Value getString(String string) { + return string != null ? ValueVarchar.get(string, session) : ValueNull.INSTANCE; + } + +} diff --git a/h2/src/main/org/h2/jdbc/meta/DatabaseMetaServer.java b/h2/src/main/org/h2/jdbc/meta/DatabaseMetaServer.java new file mode 100644 index 0000000..9559233 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/meta/DatabaseMetaServer.java @@ -0,0 +1,198 @@ +/* + * 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.jdbc.meta; + +import static org.h2.jdbc.meta.DatabaseMetaRemote.DEFAULT_NULL_ORDERING; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_ATTRIBUTES_4; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_BEST_ROW_IDENTIFIER_5; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_CATALOGS; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_COLUMNS_4; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_COLUMN_PRIVILEGES_4; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_CROSS_REFERENCE_6; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_DATABASE_MAJOR_VERSION; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_DATABASE_MINOR_VERSION; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_DATABASE_PRODUCT_VERSION; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_EXPORTED_KEYS_3; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_FUNCTIONS_3; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_FUNCTION_COLUMNS_4; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_IMPORTED_KEYS_3; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_INDEX_INFO_5; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_NUMERIC_FUNCTIONS; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_PRIMARY_KEYS_3; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_PROCEDURES_3; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_PROCEDURE_COLUMNS_4; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_PSEUDO_COLUMNS_4; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_SCHEMAS; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_SCHEMAS_2; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_SEARCH_STRING_ESCAPE; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_SQL_KEYWORDS; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_STRING_FUNCTIONS; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_SUPER_TABLES_3; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_SUPER_TYPES_3; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_SYSTEM_FUNCTIONS; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_TABLES_4; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_TABLE_PRIVILEGES_3; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_TABLE_TYPES; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_TIME_DATE_FUNCTIONS; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_TYPE_INFO; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_UDTS_4; +import static org.h2.jdbc.meta.DatabaseMetaRemote.GET_VERSION_COLUMNS_3; + +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.result.ResultInterface; +import org.h2.result.SimpleResult; +import org.h2.value.Value; +import org.h2.value.ValueArray; +import org.h2.value.ValueInteger; +import org.h2.value.ValueNull; +import org.h2.value.ValueVarchar; + +/** + * Server side support of database meta information. + */ +public final class DatabaseMetaServer { + + /** + * Process a database meta data request. + * + * @param session the session + * @param code the operation code + * @param args the arguments + * @return the result + */ + public static ResultInterface process(SessionLocal session, int code, Value[] args) { + DatabaseMeta meta = session.getDatabaseMeta(); + switch (code) { + case DEFAULT_NULL_ORDERING: + return result(meta.defaultNullOrdering().ordinal()); + case GET_DATABASE_PRODUCT_VERSION: + return result(session, meta.getDatabaseProductVersion()); + case GET_SQL_KEYWORDS: + return result(session, meta.getSQLKeywords()); + case GET_NUMERIC_FUNCTIONS: + return result(session, meta.getNumericFunctions()); + case GET_STRING_FUNCTIONS: + return result(session, meta.getStringFunctions()); + case GET_SYSTEM_FUNCTIONS: + return result(session, meta.getSystemFunctions()); + case GET_TIME_DATE_FUNCTIONS: + return result(session, meta.getTimeDateFunctions()); + case GET_SEARCH_STRING_ESCAPE: + return result(session, meta.getSearchStringEscape()); + case GET_PROCEDURES_3: + return meta.getProcedures(args[0].getString(), args[1].getString(), args[2].getString()); + case GET_PROCEDURE_COLUMNS_4: + return meta.getProcedureColumns(args[0].getString(), args[1].getString(), args[2].getString(), + args[3].getString()); + case GET_TABLES_4: + return meta.getTables(args[0].getString(), args[1].getString(), args[2].getString(), + toStringArray(args[3])); + case GET_SCHEMAS: + return meta.getSchemas(); + case GET_CATALOGS: + return meta.getCatalogs(); + case GET_TABLE_TYPES: + return meta.getTableTypes(); + case GET_COLUMNS_4: + return meta.getColumns(args[0].getString(), args[1].getString(), args[2].getString(), args[3].getString()); + case GET_COLUMN_PRIVILEGES_4: + return meta.getColumnPrivileges(args[0].getString(), args[1].getString(), args[2].getString(), + args[3].getString()); + case GET_TABLE_PRIVILEGES_3: + return meta.getTablePrivileges(args[0].getString(), args[1].getString(), args[2].getString()); + case GET_BEST_ROW_IDENTIFIER_5: + return meta.getBestRowIdentifier(args[0].getString(), args[1].getString(), args[2].getString(), + args[3].getInt(), args[4].getBoolean()); + case GET_VERSION_COLUMNS_3: + return meta.getVersionColumns(args[0].getString(), args[1].getString(), args[2].getString()); + case GET_PRIMARY_KEYS_3: + return meta.getPrimaryKeys(args[0].getString(), args[1].getString(), args[2].getString()); + case GET_IMPORTED_KEYS_3: + return meta.getImportedKeys(args[0].getString(), args[1].getString(), args[2].getString()); + case GET_EXPORTED_KEYS_3: + return meta.getExportedKeys(args[0].getString(), args[1].getString(), args[2].getString()); + case GET_CROSS_REFERENCE_6: + return meta.getCrossReference(args[0].getString(), args[1].getString(), args[2].getString(), + args[3].getString(), args[4].getString(), args[5].getString()); + case GET_TYPE_INFO: + return meta.getTypeInfo(); + case GET_INDEX_INFO_5: + return meta.getIndexInfo(args[0].getString(), args[1].getString(), args[2].getString(), + args[3].getBoolean(), args[4].getBoolean()); + case GET_UDTS_4: + return meta.getUDTs(args[0].getString(), args[1].getString(), args[2].getString(), toIntArray(args[3])); + case GET_SUPER_TYPES_3: + return meta.getSuperTypes(args[0].getString(), args[1].getString(), args[2].getString()); + case GET_SUPER_TABLES_3: + return meta.getSuperTables(args[0].getString(), args[1].getString(), args[2].getString()); + case GET_ATTRIBUTES_4: + return meta.getAttributes(args[0].getString(), args[1].getString(), args[2].getString(), + args[3].getString()); + case GET_DATABASE_MAJOR_VERSION: + return result(meta.getDatabaseMajorVersion()); + case GET_DATABASE_MINOR_VERSION: + return result(meta.getDatabaseMinorVersion()); + case GET_SCHEMAS_2: + return meta.getSchemas(args[0].getString(), args[1].getString()); + case GET_FUNCTIONS_3: + return meta.getFunctions(args[0].getString(), args[1].getString(), args[2].getString()); + case GET_FUNCTION_COLUMNS_4: + return meta.getFunctionColumns(args[0].getString(), args[1].getString(), args[2].getString(), + args[3].getString()); + case GET_PSEUDO_COLUMNS_4: + return meta.getPseudoColumns(args[0].getString(), args[1].getString(), args[2].getString(), + args[3].getString()); + default: + throw DbException.getUnsupportedException("META " + code); + } + } + + private static String[] toStringArray(Value value) { + if (value == ValueNull.INSTANCE) { + return null; + } + Value[] list = ((ValueArray) value).getList(); + int l = list.length; + String[] result = new String[l]; + for (int i = 0; i < l; i++) { + result[i] = list[i].getString(); + } + return result; + } + + private static int[] toIntArray(Value value) { + if (value == ValueNull.INSTANCE) { + return null; + } + Value[] list = ((ValueArray) value).getList(); + int l = list.length; + int[] result = new int[l]; + for (int i = 0; i < l; i++) { + result[i] = list[i].getInt(); + } + return result; + } + + private static ResultInterface result(int value) { + return result(ValueInteger.get(value)); + } + + private static ResultInterface result(SessionLocal session, String value) { + return result(ValueVarchar.get(value, session)); + } + + private static ResultInterface result(Value v) { + SimpleResult result = new SimpleResult(); + result.addColumn("RESULT", v.getType()); + result.addRow(v); + return result; + } + + private DatabaseMetaServer() { + } + +} diff --git a/h2/src/main/org/h2/jdbc/meta/package.html b/h2/src/main/org/h2/jdbc/meta/package.html new file mode 100644 index 0000000..68e7171 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/meta/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Implementation of the JDBC database metadata API (package java.sql). + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/jdbc/package.html b/h2/src/main/org/h2/jdbc/package.html new file mode 100644 index 0000000..ffc7f90 --- /dev/null +++ b/h2/src/main/org/h2/jdbc/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Implementation of the JDBC API (package java.sql). + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/jdbcx/JdbcConnectionPool.java b/h2/src/main/org/h2/jdbcx/JdbcConnectionPool.java new file mode 100644 index 0000000..705060f --- /dev/null +++ b/h2/src/main/org/h2/jdbcx/JdbcConnectionPool.java @@ -0,0 +1,358 @@ +/* + * 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: Christian d'Heureuse, www.source-code.biz + * + * This class is multi-licensed under LGPL, MPL 2.0, and EPL 1.0. + * + * This module is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * See https://www.gnu.org/licenses/lgpl-3.0.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + */ +package org.h2.jdbcx; + +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Logger; + +import javax.sql.ConnectionEvent; +import javax.sql.ConnectionEventListener; +import javax.sql.ConnectionPoolDataSource; +import javax.sql.DataSource; +import javax.sql.PooledConnection; + +import org.h2.message.DbException; + +/** + * A simple standalone JDBC connection pool. + * It is based on the + * + * MiniConnectionPoolManager written by Christian d'Heureuse (Java 1.5) + * . It is used as follows: + *
+ * import java.sql.*;
+ * import org.h2.jdbcx.JdbcConnectionPool;
+ * public class Test {
+ *     public static void main(String... args) throws Exception {
+ *         JdbcConnectionPool cp = JdbcConnectionPool.create(
+ *             "jdbc:h2:~/test", "sa", "sa");
+ *         for (String sql : args) {
+ *             Connection conn = cp.getConnection();
+ *             conn.createStatement().execute(sql);
+ *             conn.close();
+ *         }
+ *         cp.dispose();
+ *     }
+ * }
+ * 
+ * + * @author Christian d'Heureuse + * (www.source-code.biz) + * @author Thomas Mueller + */ +public final class JdbcConnectionPool + implements DataSource, ConnectionEventListener, JdbcConnectionPoolBackwardsCompat { + + private static final int DEFAULT_TIMEOUT = 30; + private static final int DEFAULT_MAX_CONNECTIONS = 10; + + private final ConnectionPoolDataSource dataSource; + private final Queue recycledConnections = new ConcurrentLinkedQueue<>(); + private PrintWriter logWriter; + private volatile int maxConnections = DEFAULT_MAX_CONNECTIONS; + private volatile int timeout = DEFAULT_TIMEOUT; + private AtomicInteger activeConnections = new AtomicInteger(); + private AtomicBoolean isDisposed = new AtomicBoolean(); + + private JdbcConnectionPool(ConnectionPoolDataSource dataSource) { + this.dataSource = dataSource; + if (dataSource != null) { + try { + logWriter = dataSource.getLogWriter(); + } catch (SQLException e) { + // ignore + } + } + } + + /** + * Constructs a new connection pool. + * + * @param dataSource the data source to create connections + * @return the connection pool + */ + public static JdbcConnectionPool create(ConnectionPoolDataSource dataSource) { + return new JdbcConnectionPool(dataSource); + } + + /** + * Constructs a new connection pool for H2 databases. + * + * @param url the database URL of the H2 connection + * @param user the user name + * @param password the password + * @return the connection pool + */ + public static JdbcConnectionPool create(String url, String user, + String password) { + JdbcDataSource ds = new JdbcDataSource(); + ds.setURL(url); + ds.setUser(user); + ds.setPassword(password); + return new JdbcConnectionPool(ds); + } + + /** + * Sets the maximum number of connections to use from now on. + * The default value is 10 connections. + * + * @param max the maximum number of connections + */ + public void setMaxConnections(int max) { + if (max < 1) { + throw new IllegalArgumentException("Invalid maxConnections value: " + max); + } + this.maxConnections = max; + } + + /** + * Gets the maximum number of connections to use. + * + * @return the max the maximum number of connections + */ + public int getMaxConnections() { + return maxConnections; + } + + /** + * Gets the maximum time in seconds to wait for a free connection. + * + * @return the timeout in seconds + */ + @Override + public int getLoginTimeout() { + return timeout; + } + + /** + * Sets the maximum time in seconds to wait for a free connection. + * The default timeout is 30 seconds. Calling this method with the + * value 0 will set the timeout to the default value. + * + * @param seconds the timeout, 0 meaning the default + */ + @Override + public void setLoginTimeout(int seconds) { + if (seconds == 0) { + seconds = DEFAULT_TIMEOUT; + } + this.timeout = seconds; + } + + /** + * Closes all unused pooled connections. + * Exceptions while closing are written to the log stream (if set). + */ + public void dispose() { + isDisposed.set(true); + + PooledConnection pc; + while ((pc = recycledConnections.poll()) != null) { + closeConnection(pc); + } + } + + /** + * Retrieves a connection from the connection pool. If + * maxConnections connections are already in use, the method + * waits until a connection becomes available or timeout + * seconds elapsed. When the application is finished using the connection, + * it must close it in order to return it to the pool. + * If no connection becomes available within the given timeout, an exception + * with SQL state 08001 and vendor code 8001 is thrown. + * + * @return a new Connection object. + * @throws SQLException when a new connection could not be established, + * or a timeout occurred + */ + @Override + public Connection getConnection() throws SQLException { + long max = System.nanoTime() + timeout * 1_000_000_000L; + int spin = 0; + do { + if (activeConnections.incrementAndGet() <= maxConnections) { + try { + return getConnectionNow(); + } catch (Throwable t) { + activeConnections.decrementAndGet(); + throw t; + } + } else { + activeConnections.decrementAndGet(); + } + if (--spin >= 0) { + continue; + } + try { + spin = 3; + Thread.sleep(1); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } while (System.nanoTime() - max <= 0); + throw new SQLException("Login timeout", "08001", 8001); + } + + /** + * INTERNAL + */ + @Override + public Connection getConnection(String user, String password) { + throw new UnsupportedOperationException(); + } + + private Connection getConnectionNow() throws SQLException { + if (isDisposed.get()) { + throw new IllegalStateException("Connection pool has been disposed."); + } + PooledConnection pc = recycledConnections.poll(); + if (pc == null) { + pc = dataSource.getPooledConnection(); + } + Connection conn = pc.getConnection(); + pc.addConnectionEventListener(this); + return conn; + } + + /** + * This method usually puts the connection back into the pool. There are + * some exceptions: if the pool is disposed, the connection is disposed as + * well. If the pool is full, the connection is closed. + * + * @param pc the pooled connection + */ + private void recycleConnection(PooledConnection pc) { + int active = activeConnections.decrementAndGet(); + if (active < 0) { + activeConnections.incrementAndGet(); + throw new AssertionError(); + } + if (!isDisposed.get() && active < maxConnections) { + recycledConnections.add(pc); + if (isDisposed.get()) { + dispose(); + } + } else { + closeConnection(pc); + } + } + + private void closeConnection(PooledConnection pc) { + try { + pc.close(); + } catch (SQLException e) { + if (logWriter != null) { + e.printStackTrace(logWriter); + } + } + } + + /** + * INTERNAL + */ + @Override + public void connectionClosed(ConnectionEvent event) { + PooledConnection pc = (PooledConnection) event.getSource(); + pc.removeConnectionEventListener(this); + recycleConnection(pc); + } + + /** + * INTERNAL + */ + @Override + public void connectionErrorOccurred(ConnectionEvent event) { + // not used + } + + /** + * Returns the number of active (open) connections of this pool. This is the + * number of Connection objects that have been issued by + * getConnection() for which Connection.close() has + * not yet been called. + * + * @return the number of active connections. + */ + public int getActiveConnections() { + return activeConnections.get(); + } + + /** + * INTERNAL + */ + @Override + public PrintWriter getLogWriter() { + return logWriter; + } + + /** + * INTERNAL + */ + @Override + public void setLogWriter(PrintWriter logWriter) { + this.logWriter = logWriter; + } + + /** + * Return an object of this class if possible. + * + * @param iface the class + * @return this + */ + @Override + @SuppressWarnings("unchecked") + public T unwrap(Class iface) throws SQLException { + try { + if (isWrapperFor(iface)) { + return (T) this; + } + throw DbException.getInvalidValueException("iface", iface); + } catch (Exception e) { + throw DbException.toSQLException(e); + } + } + + /** + * Checks if unwrap can return an object of this class. + * + * @param iface the class + * @return whether or not the interface is assignable from this class + */ + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return iface != null && iface.isAssignableFrom(getClass()); + } + + /** + * [Not supported] + */ + @Override + public Logger getParentLogger() { + return null; + } + + +} diff --git a/h2/src/main/org/h2/jdbcx/JdbcConnectionPoolBackwardsCompat.java b/h2/src/main/org/h2/jdbcx/JdbcConnectionPoolBackwardsCompat.java new file mode 100644 index 0000000..b901d49 --- /dev/null +++ b/h2/src/main/org/h2/jdbcx/JdbcConnectionPoolBackwardsCompat.java @@ -0,0 +1,16 @@ +/* + * 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.jdbcx; + +/** + * Allows us to compile on older platforms, while still implementing the methods + * from the newer JDBC API. + */ +public interface JdbcConnectionPoolBackwardsCompat { + + // compatibility interface + +} diff --git a/h2/src/main/org/h2/jdbcx/JdbcDataSource.java b/h2/src/main/org/h2/jdbcx/JdbcDataSource.java new file mode 100644 index 0000000..4c0ab0c --- /dev/null +++ b/h2/src/main/org/h2/jdbcx/JdbcDataSource.java @@ -0,0 +1,418 @@ +/* + * 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.jdbcx; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.PrintWriter; +import java.io.Serializable; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.logging.Logger; +import javax.naming.Reference; +import javax.naming.Referenceable; +import javax.naming.StringRefAddr; +import javax.sql.ConnectionPoolDataSource; +import javax.sql.DataSource; +import javax.sql.PooledConnection; +import javax.sql.XAConnection; +import javax.sql.XADataSource; +import org.h2.jdbc.JdbcConnection; +import org.h2.message.DbException; +import org.h2.message.TraceObject; +import org.h2.util.StringUtils; + +/** + * A data source for H2 database connections. It is a factory for XAConnection + * and Connection objects. This class is usually registered in a JNDI naming + * service. To create a data source object and register it with a JNDI service, + * use the following code: + * + *
+ * import org.h2.jdbcx.JdbcDataSource;
+ * import javax.naming.Context;
+ * import javax.naming.InitialContext;
+ * JdbcDataSource ds = new JdbcDataSource();
+ * ds.setURL("jdbc:h2:˜/test");
+ * ds.setUser("sa");
+ * ds.setPassword("sa");
+ * Context ctx = new InitialContext();
+ * ctx.bind("jdbc/dsName", ds);
+ * 
+ * + * To use a data source that is already registered, use the following code: + * + *
+ * import java.sql.Connection;
+ * import javax.sql.DataSource;
+ * import javax.naming.Context;
+ * import javax.naming.InitialContext;
+ * Context ctx = new InitialContext();
+ * DataSource ds = (DataSource) ctx.lookup("jdbc/dsName");
+ * Connection conn = ds.getConnection();
+ * 
+ * + * In this example the user name and password are serialized as + * well; this may be a security problem in some cases. + */ +public final class JdbcDataSource extends TraceObject implements XADataSource, DataSource, ConnectionPoolDataSource, + Serializable, Referenceable, JdbcDataSourceBackwardsCompat { + + private static final long serialVersionUID = 1288136338451857771L; + + private transient JdbcDataSourceFactory factory; + private transient PrintWriter logWriter; + private int loginTimeout; + private String userName = ""; + private char[] passwordChars = { }; + private String url = ""; + private String description; + + /** + * The public constructor. + */ + public JdbcDataSource() { + initFactory(); + int id = getNextId(TraceObject.DATA_SOURCE); + setTrace(factory.getTrace(), TraceObject.DATA_SOURCE, id); + } + + /** + * Called when de-serializing the object. + * + * @param in the input stream + * @throws IOException on failure + * @throws ClassNotFoundException on failure + */ + private void readObject(ObjectInputStream in) throws IOException, + ClassNotFoundException { + initFactory(); + in.defaultReadObject(); + } + + private void initFactory() { + factory = new JdbcDataSourceFactory(); + } + + /** + * Get the login timeout in seconds, 0 meaning no timeout. + * + * @return the timeout in seconds + */ + @Override + public int getLoginTimeout() { + debugCodeCall("getLoginTimeout"); + return loginTimeout; + } + + /** + * Set the login timeout in seconds, 0 meaning no timeout. + * The default value is 0. + * This value is ignored by this database. + * + * @param timeout the timeout in seconds + */ + @Override + public void setLoginTimeout(int timeout) { + debugCodeCall("setLoginTimeout", timeout); + this.loginTimeout = timeout; + } + + /** + * Get the current log writer for this object. + * + * @return the log writer + */ + @Override + public PrintWriter getLogWriter() { + debugCodeCall("getLogWriter"); + return logWriter; + } + + /** + * Set the current log writer for this object. + * This value is ignored by this database. + * + * @param out the log writer + */ + @Override + public void setLogWriter(PrintWriter out) { + debugCodeCall("setLogWriter(out)"); + logWriter = out; + } + + /** + * Open a new connection using the current URL, user name and password. + * + * @return the connection + */ + @Override + public Connection getConnection() throws SQLException { + debugCodeCall("getConnection"); + return new JdbcConnection(url, null, userName, StringUtils.cloneCharArray(passwordChars), false); + } + + /** + * Open a new connection using the current URL and the specified user name + * and password. + * + * @param user the user name + * @param password the password + * @return the connection + */ + @Override + public Connection getConnection(String user, String password) + throws SQLException { + if (isDebugEnabled()) { + debugCode("getConnection(" + quote(user) + ", \"\")"); + } + return new JdbcConnection(url, null, user, password, false); + } + + /** + * Get the current URL. + * + * @return the URL + */ + public String getURL() { + debugCodeCall("getURL"); + return url; + } + + /** + * Set the current URL. + * + * @param url the new URL + */ + public void setURL(String url) { + debugCodeCall("setURL", url); + this.url = url; + } + + /** + * Get the current URL. + * This method does the same as getURL, but this methods signature conforms + * the JavaBean naming convention. + * + * @return the URL + */ + public String getUrl() { + debugCodeCall("getUrl"); + return url; + } + + /** + * Set the current URL. + * This method does the same as setURL, but this methods signature conforms + * the JavaBean naming convention. + * + * @param url the new URL + */ + public void setUrl(String url) { + debugCodeCall("setUrl", url); + this.url = url; + } + + /** + * Set the current password. + * + * @param password the new password. + */ + public void setPassword(String password) { + debugCodeCall("setPassword", ""); + this.passwordChars = password == null ? null : password.toCharArray(); + } + + /** + * Set the current password in the form of a char array. + * + * @param password the new password in the form of a char array. + */ + public void setPasswordChars(char[] password) { + if (isDebugEnabled()) { + debugCode("setPasswordChars(new char[0])"); + } + this.passwordChars = password; + } + + private static String convertToString(char[] a) { + return a == null ? null : new String(a); + } + + /** + * Get the current password. + * + * @return the password + */ + public String getPassword() { + debugCodeCall("getPassword"); + return convertToString(passwordChars); + } + + /** + * Get the current user name. + * + * @return the user name + */ + public String getUser() { + debugCodeCall("getUser"); + return userName; + } + + /** + * Set the current user name. + * + * @param user the new user name + */ + public void setUser(String user) { + debugCodeCall("setUser", user); + this.userName = user; + } + + /** + * Get the current description. + * + * @return the description + */ + public String getDescription() { + debugCodeCall("getDescription"); + return description; + } + + /** + * Set the description. + * + * @param description the new description + */ + public void setDescription(String description) { + debugCodeCall("getDescription", description); + this.description = description; + } + + /** + * Get a new reference for this object, using the current settings. + * + * @return the new reference + */ + @Override + public Reference getReference() { + debugCodeCall("getReference"); + String factoryClassName = JdbcDataSourceFactory.class.getName(); + Reference ref = new Reference(getClass().getName(), factoryClassName, null); + ref.add(new StringRefAddr("url", url)); + ref.add(new StringRefAddr("user", userName)); + ref.add(new StringRefAddr("password", convertToString(passwordChars))); + ref.add(new StringRefAddr("loginTimeout", Integer.toString(loginTimeout))); + ref.add(new StringRefAddr("description", description)); + return ref; + } + + /** + * Open a new XA connection using the current URL, user name and password. + * + * @return the connection + */ + @Override + public XAConnection getXAConnection() throws SQLException { + debugCodeCall("getXAConnection"); + return new JdbcXAConnection(factory, getNextId(XA_DATA_SOURCE), + new JdbcConnection(url, null, userName, StringUtils.cloneCharArray(passwordChars), false)); + } + + /** + * Open a new XA connection using the current URL and the specified user + * name and password. + * + * @param user the user name + * @param password the password + * @return the connection + */ + @Override + public XAConnection getXAConnection(String user, String password) + throws SQLException { + if (isDebugEnabled()) { + debugCode("getXAConnection(" + quote(user) + ", \"\")"); + } + return new JdbcXAConnection(factory, getNextId(XA_DATA_SOURCE), + new JdbcConnection(url, null, user, password, false)); + } + + /** + * Open a new pooled connection using the current URL, user name and + * password. + * + * @return the connection + */ + @Override + public PooledConnection getPooledConnection() throws SQLException { + debugCodeCall("getPooledConnection"); + return getXAConnection(); + } + + /** + * Open a new pooled connection using the current URL and the specified user + * name and password. + * + * @param user the user name + * @param password the password + * @return the connection + */ + @Override + public PooledConnection getPooledConnection(String user, String password) + throws SQLException { + if (isDebugEnabled()) { + debugCode("getPooledConnection(" + quote(user) + ", \"\")"); + } + return getXAConnection(user, password); + } + + /** + * Return an object of this class if possible. + * + * @param iface the class + * @return this + */ + @Override + @SuppressWarnings("unchecked") + public T unwrap(Class iface) throws SQLException { + try { + if (isWrapperFor(iface)) { + return (T) this; + } + throw DbException.getInvalidValueException("iface", iface); + } catch (Exception e) { + throw logAndConvert(e); + } + } + + /** + * Checks if unwrap can return an object of this class. + * + * @param iface the class + * @return whether or not the interface is assignable from this class + */ + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return iface != null && iface.isAssignableFrom(getClass()); + } + + /** + * [Not supported] + */ + @Override + public Logger getParentLogger() { + return null; + } + + /** + * INTERNAL + */ + @Override + public String toString() { + return getTraceObjectName() + ": url=" + url + " user=" + userName; + } + +} diff --git a/h2/src/main/org/h2/jdbcx/JdbcDataSourceBackwardsCompat.java b/h2/src/main/org/h2/jdbcx/JdbcDataSourceBackwardsCompat.java new file mode 100644 index 0000000..cf00ae6 --- /dev/null +++ b/h2/src/main/org/h2/jdbcx/JdbcDataSourceBackwardsCompat.java @@ -0,0 +1,16 @@ +/* + * 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.jdbcx; + +/** + * Allows us to compile on older platforms, while still implementing the methods + * from the newer JDBC API. + */ +public interface JdbcDataSourceBackwardsCompat { + + // compatibility interface + +} diff --git a/h2/src/main/org/h2/jdbcx/JdbcDataSourceFactory.java b/h2/src/main/org/h2/jdbcx/JdbcDataSourceFactory.java new file mode 100644 index 0000000..07673ff --- /dev/null +++ b/h2/src/main/org/h2/jdbcx/JdbcDataSourceFactory.java @@ -0,0 +1,90 @@ +/* + * 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.jdbcx; + +import java.util.Hashtable; + +import javax.naming.Context; +import javax.naming.Name; +import javax.naming.Reference; +import javax.naming.spi.ObjectFactory; + +import org.h2.engine.Constants; +import org.h2.engine.SysProperties; +import org.h2.message.Trace; +import org.h2.message.TraceSystem; + +/** + * This class is used to create new DataSource objects. + * An application should not use this class directly. + */ +public final class JdbcDataSourceFactory implements ObjectFactory { + + private static final TraceSystem traceSystem; + + private final Trace trace; + + static { + traceSystem = new TraceSystem(SysProperties.CLIENT_TRACE_DIRECTORY + "h2datasource" + + Constants.SUFFIX_TRACE_FILE); + traceSystem.setLevelFile(SysProperties.DATASOURCE_TRACE_LEVEL); + } + + /** + * The public constructor to create new factory objects. + */ + public JdbcDataSourceFactory() { + trace = traceSystem.getTrace(Trace.JDBCX); + } + + /** + * Creates a new object using the specified location or reference + * information. + * + * @param obj the reference (this factory only supports objects of type + * javax.naming.Reference) + * @param name unused + * @param nameCtx unused + * @param environment unused + * @return the new JdbcDataSource, or null if the reference class name is + * not JdbcDataSource. + */ + @Override + public synchronized Object getObjectInstance(Object obj, Name name, + Context nameCtx, Hashtable environment) { + if (trace.isDebugEnabled()) { + trace.debug("getObjectInstance obj={0} name={1} " + + "nameCtx={2} environment={3}", obj, name, nameCtx, environment); + } + if (obj instanceof Reference) { + Reference ref = (Reference) obj; + if (ref.getClassName().equals(JdbcDataSource.class.getName())) { + JdbcDataSource dataSource = new JdbcDataSource(); + dataSource.setURL((String) ref.get("url").getContent()); + dataSource.setUser((String) ref.get("user").getContent()); + dataSource.setPassword((String) ref.get("password").getContent()); + dataSource.setDescription((String) ref.get("description").getContent()); + String s = (String) ref.get("loginTimeout").getContent(); + dataSource.setLoginTimeout(Integer.parseInt(s)); + return dataSource; + } + } + return null; + } + + /** + * INTERNAL + * @return TraceSystem + */ + public static TraceSystem getTraceSystem() { + return traceSystem; + } + + Trace getTrace() { + return trace; + } + +} diff --git a/h2/src/main/org/h2/jdbcx/JdbcXAConnection.java b/h2/src/main/org/h2/jdbcx/JdbcXAConnection.java new file mode 100644 index 0000000..fe7cbe5 --- /dev/null +++ b/h2/src/main/org/h2/jdbcx/JdbcXAConnection.java @@ -0,0 +1,478 @@ +/* + * 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.jdbcx; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; + +import javax.sql.ConnectionEvent; +import javax.sql.ConnectionEventListener; +import javax.sql.StatementEventListener; +import javax.sql.XAConnection; +import javax.transaction.xa.XAException; +import javax.transaction.xa.XAResource; +import javax.transaction.xa.Xid; + +import org.h2.api.ErrorCode; +import org.h2.jdbc.JdbcConnection; +import org.h2.message.DbException; +import org.h2.message.TraceObject; +import org.h2.util.Utils; + + +/** + * This class provides support for distributed transactions. + * An application developer usually does not use this interface. + * It is used by the transaction manager internally. + */ +public final class JdbcXAConnection extends TraceObject implements XAConnection, + XAResource { + + private final JdbcDataSourceFactory factory; + + // this connection is kept open as long as the XAConnection is alive + private JdbcConnection physicalConn; + + // this connection is replaced whenever getConnection is called + private volatile Connection handleConn; + private final ArrayList listeners = Utils.newSmallArrayList(); + private Xid currentTransaction; + private boolean prepared; + + JdbcXAConnection(JdbcDataSourceFactory factory, int id, + JdbcConnection physicalConn) { + this.factory = factory; + setTrace(factory.getTrace(), TraceObject.XA_DATA_SOURCE, id); + this.physicalConn = physicalConn; + } + + /** + * Get the XAResource object. + * + * @return itself + */ + @Override + public XAResource getXAResource() { + debugCodeCall("getXAResource"); + return this; + } + + /** + * Close the physical connection. + * This method is usually called by the connection pool. + */ + @Override + public void close() throws SQLException { + debugCodeCall("close"); + Connection lastHandle = handleConn; + if (lastHandle != null) { + listeners.clear(); + lastHandle.close(); + } + if (physicalConn != null) { + try { + physicalConn.close(); + } finally { + physicalConn = null; + } + } + } + + /** + * Get a connection that is a handle to the physical connection. This method + * is usually called by the connection pool. This method closes the last + * connection handle if one exists. + * + * @return the connection + */ + @Override + public Connection getConnection() throws SQLException { + debugCodeCall("getConnection"); + Connection lastHandle = handleConn; + if (lastHandle != null) { + lastHandle.close(); + } + // this will ensure the rollback command is cached + physicalConn.rollback(); + handleConn = new PooledJdbcConnection(physicalConn); + return handleConn; + } + + /** + * Register a new listener for the connection. + * + * @param listener the event listener + */ + @Override + public void addConnectionEventListener(ConnectionEventListener listener) { + debugCode("addConnectionEventListener(listener)"); + listeners.add(listener); + } + + /** + * Remove the event listener. + * + * @param listener the event listener + */ + @Override + public void removeConnectionEventListener(ConnectionEventListener listener) { + debugCode("removeConnectionEventListener(listener)"); + listeners.remove(listener); + } + + /** + * INTERNAL + */ + void closedHandle() { + debugCodeCall("closedHandle"); + ConnectionEvent event = new ConnectionEvent(this); + // go backward so that a listener can remove itself + // (otherwise we need to clone the list) + for (int i = listeners.size() - 1; i >= 0; i--) { + ConnectionEventListener listener = listeners.get(i); + listener.connectionClosed(event); + } + handleConn = null; + } + + /** + * Get the transaction timeout. + * + * @return 0 + */ + @Override + public int getTransactionTimeout() { + debugCodeCall("getTransactionTimeout"); + return 0; + } + + /** + * Set the transaction timeout. + * + * @param seconds ignored + * @return false + */ + @Override + public boolean setTransactionTimeout(int seconds) { + debugCodeCall("setTransactionTimeout", seconds); + return false; + } + + /** + * Checks if this is the same XAResource. + * + * @param xares the other object + * @return true if this is the same object + */ + @Override + public boolean isSameRM(XAResource xares) { + debugCode("isSameRM(xares)"); + return xares == this; + } + + /** + * Get the list of prepared transaction branches. This method is called by + * the transaction manager during recovery. + * + * @param flag TMSTARTRSCAN, TMENDRSCAN, or TMNOFLAGS. If no other flags are + * set, TMNOFLAGS must be used. + * @return zero or more Xid objects + */ + @Override + public Xid[] recover(int flag) throws XAException { + debugCodeCall("recover", quoteFlags(flag)); + checkOpen(); + try (Statement stat = physicalConn.createStatement()) { + ResultSet rs = stat.executeQuery("SELECT * FROM INFORMATION_SCHEMA.IN_DOUBT ORDER BY TRANSACTION_NAME"); + ArrayList list = Utils.newSmallArrayList(); + while (rs.next()) { + String tid = rs.getString("TRANSACTION_NAME"); + int id = getNextId(XID); + Xid xid = new JdbcXid(factory, id, tid); + list.add(xid); + } + rs.close(); + Xid[] result = list.toArray(new Xid[0]); + if (!list.isEmpty()) { + prepared = true; + } + return result; + } catch (SQLException e) { + XAException xa = new XAException(XAException.XAER_RMERR); + xa.initCause(e); + throw xa; + } + } + + /** + * Prepare a transaction. + * + * @param xid the transaction id + * @return XA_OK + */ + @Override + public int prepare(Xid xid) throws XAException { + if (isDebugEnabled()) { + debugCode("prepare(" + quoteXid(xid) + ')'); + } + checkOpen(); + if (!currentTransaction.equals(xid)) { + throw new XAException(XAException.XAER_INVAL); + } + + try (Statement stat = physicalConn.createStatement()) { + stat.execute(JdbcXid.toString(new StringBuilder("PREPARE COMMIT \""), xid).append('"').toString()); + prepared = true; + } catch (SQLException e) { + throw convertException(e); + } + return XA_OK; + } + + /** + * Forget a transaction. + * This method does not have an effect for this database. + * + * @param xid the transaction id + */ + @Override + public void forget(Xid xid) { + if (isDebugEnabled()) { + debugCode("forget(" + quoteXid(xid) + ')'); + } + prepared = false; + } + + /** + * Roll back a transaction. + * + * @param xid the transaction id + */ + @Override + public void rollback(Xid xid) throws XAException { + if (isDebugEnabled()) { + debugCode("rollback(" + quoteXid(xid) + ')'); + } + try { + if (prepared) { + try (Statement stat = physicalConn.createStatement()) { + stat.execute(JdbcXid.toString( // + new StringBuilder("ROLLBACK TRANSACTION \""), xid).append('"').toString()); + } + prepared = false; + } else { + physicalConn.rollback(); + } + physicalConn.setAutoCommit(true); + } catch (SQLException e) { + throw convertException(e); + } + currentTransaction = null; + } + + /** + * End a transaction. + * + * @param xid the transaction id + * @param flags TMSUCCESS, TMFAIL, or TMSUSPEND + */ + @Override + public void end(Xid xid, int flags) throws XAException { + if (isDebugEnabled()) { + debugCode("end(" + quoteXid(xid) + ", " + quoteFlags(flags) + ')'); + } + // TODO transaction end: implement this method + if (flags == TMSUSPEND) { + return; + } + if (!currentTransaction.equals(xid)) { + throw new XAException(XAException.XAER_OUTSIDE); + } + prepared = false; + } + + /** + * Start or continue to work on a transaction. + * + * @param xid the transaction id + * @param flags TMNOFLAGS, TMJOIN, or TMRESUME + */ + @Override + public void start(Xid xid, int flags) throws XAException { + if (isDebugEnabled()) { + debugCode("start(" + quoteXid(xid) + ", " + quoteFlags(flags) + ')'); + } + if (flags == TMRESUME) { + return; + } + if (flags == TMJOIN) { + if (currentTransaction != null && !currentTransaction.equals(xid)) { + throw new XAException(XAException.XAER_RMERR); + } + } else if (currentTransaction != null) { + throw new XAException(XAException.XAER_NOTA); + } + try { + physicalConn.setAutoCommit(false); + } catch (SQLException e) { + throw convertException(e); + } + currentTransaction = xid; + prepared = false; + } + + /** + * Commit a transaction. + * + * @param xid the transaction id + * @param onePhase use a one-phase protocol if true + */ + @Override + public void commit(Xid xid, boolean onePhase) throws XAException { + if (isDebugEnabled()) { + debugCode("commit(" + quoteXid(xid) + ", " + onePhase + ')'); + } + + try { + if (onePhase) { + physicalConn.commit(); + } else { + try (Statement stat = physicalConn.createStatement()) { + stat.execute( + JdbcXid.toString(new StringBuilder("COMMIT TRANSACTION \""), xid).append('"').toString()); + prepared = false; + } + } + physicalConn.setAutoCommit(true); + } catch (SQLException e) { + throw convertException(e); + } + currentTransaction = null; + } + + /** + * [Not supported] Add a statement event listener. + * + * @param listener the new statement event listener + */ + @Override + public void addStatementEventListener(StatementEventListener listener) { + throw new UnsupportedOperationException(); + } + + /** + * [Not supported] Remove a statement event listener. + * + * @param listener the statement event listener + */ + @Override + public void removeStatementEventListener(StatementEventListener listener) { + throw new UnsupportedOperationException(); + } + + /** + * INTERNAL + */ + @Override + public String toString() { + return getTraceObjectName() + ": " + physicalConn; + } + + private static XAException convertException(SQLException e) { + XAException xa = new XAException(e.getMessage()); + xa.initCause(e); + return xa; + } + + private static String quoteXid(Xid xid) { + return JdbcXid.toString(new StringBuilder(), xid).toString().replace('-', '$'); + } + + private static String quoteFlags(int flags) { + StringBuilder buff = new StringBuilder(); + if ((flags & XAResource.TMENDRSCAN) != 0) { + buff.append("|XAResource.TMENDRSCAN"); + } + if ((flags & XAResource.TMFAIL) != 0) { + buff.append("|XAResource.TMFAIL"); + } + if ((flags & XAResource.TMJOIN) != 0) { + buff.append("|XAResource.TMJOIN"); + } + if ((flags & XAResource.TMONEPHASE) != 0) { + buff.append("|XAResource.TMONEPHASE"); + } + if ((flags & XAResource.TMRESUME) != 0) { + buff.append("|XAResource.TMRESUME"); + } + if ((flags & XAResource.TMSTARTRSCAN) != 0) { + buff.append("|XAResource.TMSTARTRSCAN"); + } + if ((flags & XAResource.TMSUCCESS) != 0) { + buff.append("|XAResource.TMSUCCESS"); + } + if ((flags & XAResource.TMSUSPEND) != 0) { + buff.append("|XAResource.TMSUSPEND"); + } + if ((flags & XAResource.XA_RDONLY) != 0) { + buff.append("|XAResource.XA_RDONLY"); + } + if (buff.length() == 0) { + buff.append("|XAResource.TMNOFLAGS"); + } + return buff.substring(1); + } + + private void checkOpen() throws XAException { + if (physicalConn == null) { + throw new XAException(XAException.XAER_RMERR); + } + } + + /** + * A pooled connection. + */ + final class PooledJdbcConnection extends JdbcConnection { + + private boolean isClosed; + + public PooledJdbcConnection(JdbcConnection conn) { + super(conn); + } + + @Override + public synchronized void close() throws SQLException { + if (!isClosed) { + try { + rollback(); + setAutoCommit(true); + } catch (SQLException e) { + // ignore + } + closedHandle(); + isClosed = true; + } + } + + @Override + public synchronized boolean isClosed() throws SQLException { + return isClosed || super.isClosed(); + } + + @Override + protected synchronized void checkClosed() { + if (isClosed) { + throw DbException.get(ErrorCode.OBJECT_CLOSED); + } + super.checkClosed(); + } + + } + +} diff --git a/h2/src/main/org/h2/jdbcx/JdbcXid.java b/h2/src/main/org/h2/jdbcx/JdbcXid.java new file mode 100644 index 0000000..c31cc0f --- /dev/null +++ b/h2/src/main/org/h2/jdbcx/JdbcXid.java @@ -0,0 +1,89 @@ +/* + * 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.jdbcx; + +import java.util.Base64; +import javax.transaction.xa.Xid; + +import org.h2.api.ErrorCode; +import org.h2.message.DbException; +import org.h2.message.TraceObject; + +/** + * An object of this class represents a transaction id. + */ +public final class JdbcXid extends TraceObject implements Xid { + + private static final String PREFIX = "XID"; + + private static final Base64.Encoder ENCODER = Base64.getUrlEncoder().withoutPadding(); + + private final int formatId; + private final byte[] branchQualifier; + private final byte[] globalTransactionId; + + JdbcXid(JdbcDataSourceFactory factory, int id, String tid) { + setTrace(factory.getTrace(), TraceObject.XID, id); + try { + String[] splits = tid.split("\\|"); + if (splits.length == 4 && PREFIX.equals(splits[0])) { + formatId = Integer.parseInt(splits[1]); + Base64.Decoder decoder = Base64.getUrlDecoder(); + branchQualifier = decoder.decode(splits[2]); + globalTransactionId = decoder.decode(splits[3]); + return; + } + } catch (IllegalArgumentException e) { + } + throw DbException.get(ErrorCode.WRONG_XID_FORMAT_1, tid); + } + + /** + * INTERNAL + * @param builder to put result into + * @param xid to provide string representation for + * @return provided StringBuilder + */ + static StringBuilder toString(StringBuilder builder, Xid xid) { + return builder.append(PREFIX).append('|').append(xid.getFormatId()) // + .append('|').append(ENCODER.encodeToString(xid.getBranchQualifier())) // + .append('|').append(ENCODER.encodeToString(xid.getGlobalTransactionId())); + } + + /** + * Get the format id. + * + * @return the format id + */ + @Override + public int getFormatId() { + debugCodeCall("getFormatId"); + return formatId; + } + + /** + * The transaction branch identifier. + * + * @return the identifier + */ + @Override + public byte[] getBranchQualifier() { + debugCodeCall("getBranchQualifier"); + return branchQualifier; + } + + /** + * The global transaction identifier. + * + * @return the transaction id + */ + @Override + public byte[] getGlobalTransactionId() { + debugCodeCall("getGlobalTransactionId"); + return globalTransactionId; + } + +} diff --git a/h2/src/main/org/h2/jdbcx/package.html b/h2/src/main/org/h2/jdbcx/package.html new file mode 100644 index 0000000..aae3de2 --- /dev/null +++ b/h2/src/main/org/h2/jdbcx/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Implementation of the extended JDBC API (package javax.sql). + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/jmx/DatabaseInfo.java b/h2/src/main/org/h2/jmx/DatabaseInfo.java new file mode 100644 index 0000000..9e14dfd --- /dev/null +++ b/h2/src/main/org/h2/jmx/DatabaseInfo.java @@ -0,0 +1,228 @@ +/* + * 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.jmx; + +import java.lang.management.ManagementFactory; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; +import java.util.Map.Entry; +import javax.management.JMException; +import javax.management.MBeanServer; +import javax.management.ObjectName; +import org.h2.command.Command; +import org.h2.engine.ConnectionInfo; +import org.h2.engine.Constants; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.table.Table; +import org.h2.util.NetworkConnectionInfo; + +/** + * The MBean implementation. + * + * @author Eric Dong + * @author Thomas Mueller + */ +public class DatabaseInfo implements DatabaseInfoMBean { + + private static final Map MBEANS = new HashMap<>(); + + /** Database. */ + private final Database database; + + private DatabaseInfo(Database database) { + if (database == null) { + throw new IllegalArgumentException("Argument 'database' must not be null"); + } + this.database = database; + } + + /** + * Returns a JMX new ObjectName instance. + * + * @param name name of the MBean + * @param path the path + * @return a new ObjectName instance + * @throws JMException if the ObjectName could not be created + */ + private static ObjectName getObjectName(String name, String path) + throws JMException { + name = name.replace(':', '_'); + path = path.replace(':', '_'); + Hashtable map = new Hashtable<>(); + map.put("name", name); + map.put("path", path); + return new ObjectName("org.h2", map); + } + + /** + * Registers an MBean for the database. + * + * @param connectionInfo connection info + * @param database database + * @throws JMException on failure + */ + public static void registerMBean(ConnectionInfo connectionInfo, + Database database) throws JMException { + String path = connectionInfo.getName(); + if (!MBEANS.containsKey(path)) { + MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); + String name = database.getShortName(); + ObjectName mbeanObjectName = getObjectName(name, path); + MBEANS.put(path, mbeanObjectName); + DatabaseInfo info = new DatabaseInfo(database); + Object mbean = new DocumentedMBean(info, DatabaseInfoMBean.class); + mbeanServer.registerMBean(mbean, mbeanObjectName); + } + } + + /** + * Unregisters the MBean for the database if one is registered. + * + * @param name database name + * @throws JMException on failure + */ + public static void unregisterMBean(String name) throws Exception { + ObjectName mbeanObjectName = MBEANS.remove(name); + if (mbeanObjectName != null) { + MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); + mbeanServer.unregisterMBean(mbeanObjectName); + } + } + + @Override + public boolean isExclusive() { + return database.getExclusiveSession() != null; + } + + @Override + public boolean isReadOnly() { + return database.isReadOnly(); + } + + @Override + public String getMode() { + return database.getMode().getName(); + } + + @Override + public int getTraceLevel() { + return database.getTraceSystem().getLevelFile(); + } + + @Override + public void setTraceLevel(int level) { + database.getTraceSystem().setLevelFile(level); + } + + @Override + public long getFileWriteCount() { + if (database.isPersistent()) { + return database.getStore().getMvStore().getFileStore().getWriteCount(); + } + return 0; + } + + @Override + public long getFileReadCount() { + if (database.isPersistent()) { + return database.getStore().getMvStore().getFileStore().getReadCount(); + } + return 0; + } + + @Override + public long getFileSize() { + long size = 0; + if (database.isPersistent()) { + size = database.getStore().getMvStore().getFileStore().size(); + } + return size / 1024; + } + + @Override + public int getCacheSizeMax() { + if (database.isPersistent()) { + return database.getStore().getMvStore().getCacheSize() * 1024; + } + return 0; + } + + @Override + public void setCacheSizeMax(int kb) { + if (database.isPersistent()) { + database.setCacheSize(kb); + } + } + + @Override + public int getCacheSize() { + if (database.isPersistent()) { + return database.getStore().getMvStore().getCacheSizeUsed() * 1024; + } + return 0; + } + + @Override + public String getVersion() { + return Constants.FULL_VERSION; + } + + @Override + public String listSettings() { + StringBuilder builder = new StringBuilder(); + for (Entry e : database.getSettings().getSortedSettings()) { + builder.append(e.getKey()).append(" = ").append(e.getValue()).append('\n'); + } + return builder.toString(); + } + + @Override + public String listSessions() { + StringBuilder buff = new StringBuilder(); + for (SessionLocal session : database.getSessions(false)) { + buff.append("session id: ").append(session.getId()); + buff.append(" user: "). + append(session.getUser().getName()). + append('\n'); + NetworkConnectionInfo networkConnectionInfo = session.getNetworkConnectionInfo(); + if (networkConnectionInfo != null) { + buff.append("server: ").append(networkConnectionInfo.getServer()).append('\n') // + .append("clientAddr: ").append(networkConnectionInfo.getClient()).append('\n'); + String clientInfo = networkConnectionInfo.getClientInfo(); + if (clientInfo != null) { + buff.append("clientInfo: ").append(clientInfo).append('\n'); + } + } + buff.append("connected: "). + append(session.getSessionStart().getString()). + append('\n'); + Command command = session.getCurrentCommand(); + if (command != null) { + buff.append("statement: ") + .append(command) + .append('\n') + .append("started: ") + .append(session.getCommandStartOrEnd().getString()) + .append('\n'); + } + for (Table table : session.getLocks()) { + if (table.isLockedExclusivelyBy(session)) { + buff.append("write lock on "); + } else { + buff.append("read lock on "); + } + buff.append(table.getSchema().getName()). + append('.').append(table.getName()). + append('\n'); + } + buff.append('\n'); + } + return buff.toString(); + } + +} diff --git a/h2/src/main/org/h2/jmx/DatabaseInfoMBean.java b/h2/src/main/org/h2/jmx/DatabaseInfoMBean.java new file mode 100644 index 0000000..15f994d --- /dev/null +++ b/h2/src/main/org/h2/jmx/DatabaseInfoMBean.java @@ -0,0 +1,116 @@ +/* + * 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.jmx; + +/** + * Information and management operations for the given database. + * + * @author Eric Dong + * @author Thomas Mueller + */ +public interface DatabaseInfoMBean { + + /** + * Is the database open in exclusive mode? + * + * @return true if the database is open in exclusive mode, false otherwise + */ + boolean isExclusive(); + + /** + * Is the database read-only? + * + * @return true if the database is read-only, false otherwise + */ + boolean isReadOnly(); + + /** + * The database compatibility mode (REGULAR if no compatibility mode is + * used). + * + * @return the database mode + */ + String getMode(); + + /** + * The number of write operations since the database was opened. + * + * @return the write count + */ + long getFileWriteCount(); + + /** + * The file read count since the database was opened. + * + * @return the read count + */ + long getFileReadCount(); + + /** + * The database file size in KB. + * + * @return the number of pages + */ + long getFileSize(); + + /** + * The maximum cache size in KB. + * + * @return the maximum size + */ + int getCacheSizeMax(); + + /** + * Change the maximum size. + * + * @param kb the cache size in KB. + */ + void setCacheSizeMax(int kb); + + /** + * The current cache size in KB. + * + * @return the current size + */ + int getCacheSize(); + + /** + * The database version. + * + * @return the version + */ + String getVersion(); + + /** + * The trace level (0 disabled, 1 error, 2 info, 3 debug). + * + * @return the level + */ + int getTraceLevel(); + + /** + * Set the trace level. + * + * @param level the new value + */ + void setTraceLevel(int level); + + /** + * List the database settings. + * + * @return the database settings + */ + String listSettings(); + + /** + * List sessions, including the queries that are in + * progress, and locked tables. + * + * @return information about the sessions + */ + String listSessions(); + +} diff --git a/h2/src/main/org/h2/jmx/DocumentedMBean.java b/h2/src/main/org/h2/jmx/DocumentedMBean.java new file mode 100644 index 0000000..e36fd10 --- /dev/null +++ b/h2/src/main/org/h2/jmx/DocumentedMBean.java @@ -0,0 +1,76 @@ +/* + * 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.jmx; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Properties; +import javax.management.MBeanAttributeInfo; +import javax.management.MBeanInfo; +import javax.management.MBeanOperationInfo; +import javax.management.NotCompliantMBeanException; +import javax.management.StandardMBean; +import org.h2.util.Utils; + +/** + * An MBean that reads the documentation from a resource file. + */ +public class DocumentedMBean extends StandardMBean { + + private final String interfaceName; + private Properties resources; + + public DocumentedMBean(T impl, Class mbeanInterface) + throws NotCompliantMBeanException { + super(impl, mbeanInterface); + this.interfaceName = impl.getClass().getName() + "MBean"; + } + + private Properties getResources() { + if (resources == null) { + resources = new Properties(); + String resourceName = "/org/h2/res/javadoc.properties"; + try { + byte[] buff = Utils.getResource(resourceName); + if (buff != null) { + resources.load(new ByteArrayInputStream(buff)); + } + } catch (IOException e) { + // ignore + } + } + return resources; + } + + @Override + protected String getDescription(MBeanInfo info) { + String s = getResources().getProperty(interfaceName); + return s == null ? super.getDescription(info) : s; + } + + @Override + protected String getDescription(MBeanOperationInfo op) { + String s = getResources().getProperty(interfaceName + "." + op.getName()); + return s == null ? super.getDescription(op) : s; + } + + @Override + protected String getDescription(MBeanAttributeInfo info) { + String prefix = info.isIs() ? "is" : "get"; + String s = getResources().getProperty( + interfaceName + "." + prefix + info.getName()); + return s == null ? super.getDescription(info) : s; + } + + @Override + protected int getImpact(MBeanOperationInfo info) { + if (info.getName().startsWith("list")) { + return MBeanOperationInfo.INFO; + } + return MBeanOperationInfo.ACTION; + } + +} diff --git a/h2/src/main/org/h2/jmx/package.html b/h2/src/main/org/h2/jmx/package.html new file mode 100644 index 0000000..01ab355 --- /dev/null +++ b/h2/src/main/org/h2/jmx/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Implementation of the Java Management Extension (JMX) features. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/message/DbException.java b/h2/src/main/org/h2/message/DbException.java new file mode 100644 index 0000000..756dcca --- /dev/null +++ b/h2/src/main/org/h2/message/DbException.java @@ -0,0 +1,767 @@ +/* + * 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.message; + +import static org.h2.api.ErrorCode.*; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.text.MessageFormat; +import java.util.Locale; +import java.util.Map.Entry; +import java.util.Properties; + +import org.h2.engine.Constants; +import org.h2.jdbc.JdbcException; +import org.h2.jdbc.JdbcSQLDataException; +import org.h2.jdbc.JdbcSQLException; +import org.h2.jdbc.JdbcSQLFeatureNotSupportedException; +import org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException; +import org.h2.jdbc.JdbcSQLInvalidAuthorizationSpecException; +import org.h2.jdbc.JdbcSQLNonTransientConnectionException; +import org.h2.jdbc.JdbcSQLNonTransientException; +import org.h2.jdbc.JdbcSQLSyntaxErrorException; +import org.h2.jdbc.JdbcSQLTimeoutException; +import org.h2.jdbc.JdbcSQLTransactionRollbackException; +import org.h2.jdbc.JdbcSQLTransientException; +import org.h2.util.SortedProperties; +import org.h2.util.StringUtils; +import org.h2.util.Utils; + +/** + * This exception wraps a checked exception. + * It is used in methods where checked exceptions are not supported, + * for example in a Comparator. + */ +public class DbException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + /** + * If the SQL statement contains this text, then it is never added to the + * SQL exception. Hiding the SQL statement may be important if it contains a + * passwords, such as a CREATE LINKED TABLE statement. + */ + public static final String HIDE_SQL = "--hide--"; + + private static final Properties MESSAGES = new Properties(); + + /** + * Thrown when OOME exception happens on handle error + * inside {@link #convert(java.lang.Throwable)}. + */ + public static final SQLException SQL_OOME = + new SQLException("OutOfMemoryError", "HY000", OUT_OF_MEMORY, new OutOfMemoryError()); + private static final DbException OOME = new DbException(SQL_OOME); + + private Object source; + + static { + try { + byte[] messages = Utils.getResource("/org/h2/res/_messages_en.prop"); + if (messages != null) { + MESSAGES.load(new ByteArrayInputStream(messages)); + } + String language = Locale.getDefault().getLanguage(); + if (!"en".equals(language)) { + byte[] translations = Utils.getResource( + "/org/h2/res/_messages_" + language + ".prop"); + // message: translated message + english + // (otherwise certain applications don't work) + if (translations != null) { + Properties p = SortedProperties.fromLines( + new String(translations, StandardCharsets.UTF_8)); + for (Entry e : p.entrySet()) { + String key = (String) e.getKey(); + String translation = (String) e.getValue(); + if (translation != null && !translation.startsWith("#")) { + String original = MESSAGES.getProperty(key); + String message = translation + "\n" + original; + MESSAGES.put(key, message); + } + } + } + } + } catch (OutOfMemoryError | IOException e) { + DbException.traceThrowable(e); + } + } + + private DbException(SQLException e) { + super(e.getMessage(), e); + } + + private static String translate(String key, String... params) { + String message = MESSAGES.getProperty(key); + if (message == null) { + message = "(Message " + key + " not found)"; + } + if (params != null) { + for (int i = 0; i < params.length; i++) { + String s = params[i]; + if (s != null && s.length() > 0) { + params[i] = quote(s); + } + } + message = MessageFormat.format(message, (Object[]) params); + } + return message; + } + + private static String quote(String s) { + int l = s.length(); + StringBuilder builder = new StringBuilder(l + 2).append('"'); + for (int i = 0; i < l;) { + int cp = s.codePointAt(i); + i += Character.charCount(cp); + int t = Character.getType(cp); + if (t == 0 || t >= Character.SPACE_SEPARATOR && t <= Character.SURROGATE && cp != ' ') { + if (cp <= 0xffff) { + StringUtils.appendHex(builder.append('\\'), cp, 2); + } else { + StringUtils.appendHex(builder.append("\\+"), cp, 3); + } + } else { + if (cp == '"' || cp == '\\') { + builder.append((char) cp); + } + builder.appendCodePoint(cp); + } + } + return builder.append('"').toString(); + } + + /** + * Get the SQLException object. + * + * @return the exception + */ + public SQLException getSQLException() { + return (SQLException) getCause(); + } + + /** + * Get the error code. + * + * @return the error code + */ + public int getErrorCode() { + return getSQLException().getErrorCode(); + } + + /** + * Set the SQL statement of the given exception. + * This method may create a new object. + * + * @param sql the SQL statement + * @return the exception + */ + public DbException addSQL(String sql) { + SQLException e = getSQLException(); + if (e instanceof JdbcException) { + JdbcException j = (JdbcException) e; + if (j.getSQL() == null) { + j.setSQL(filterSQL(sql)); + } + return this; + } + e = getJdbcSQLException(e.getMessage(), sql, e.getSQLState(), e.getErrorCode(), e, null); + return new DbException(e); + } + + /** + * Create a database exception for a specific error code. + * + * @param errorCode the error code + * @return the exception + */ + public static DbException get(int errorCode) { + return get(errorCode, (String) null); + } + + /** + * Create a database exception for a specific error code. + * + * @param errorCode the error code + * @param p1 the first parameter of the message + * @return the exception + */ + public static DbException get(int errorCode, String p1) { + return get(errorCode, new String[] { p1 }); + } + + /** + * Create a database exception for a specific error code. + * + * @param errorCode the error code + * @param cause the cause of the exception + * @param params the list of parameters of the message + * @return the exception + */ + public static DbException get(int errorCode, Throwable cause, + String... params) { + return new DbException(getJdbcSQLException(errorCode, cause, params)); + } + + /** + * Create a database exception for a specific error code. + * + * @param errorCode the error code + * @param params the list of parameters of the message + * @return the exception + */ + public static DbException get(int errorCode, String... params) { + return new DbException(getJdbcSQLException(errorCode, null, params)); + } + + /** + * Create a database exception for an arbitrary SQLState. + * + * @param sqlstate the state to use + * @param message the message to use + * @return the exception + */ + public static DbException fromUser(String sqlstate, String message) { + // do not translate as sqlstate is arbitrary : avoid "message not found" + return new DbException(getJdbcSQLException(message, null, sqlstate, 0, null, null)); + } + + /** + * Create a syntax error exception. + * + * @param sql the SQL statement + * @param index the position of the error in the SQL statement + * @return the exception + */ + public static DbException getSyntaxError(String sql, int index) { + sql = StringUtils.addAsterisk(sql, index); + return get(SYNTAX_ERROR_1, sql); + } + + /** + * Create a syntax error exception. + * + * @param sql the SQL statement + * @param index the position of the error in the SQL statement + * @param message the message + * @return the exception + */ + public static DbException getSyntaxError(String sql, int index, + String message) { + sql = StringUtils.addAsterisk(sql, index); + return new DbException(getJdbcSQLException(SYNTAX_ERROR_2, null, sql, message)); + } + + /** + * Create a syntax error exception for a specific error code. + * + * @param errorCode the error code + * @param sql the SQL statement + * @param index the position of the error in the SQL statement + * @param params the list of parameters of the message + * @return the exception + */ + public static DbException getSyntaxError(int errorCode, String sql, int index, String... params) { + sql = StringUtils.addAsterisk(sql, index); + String sqlstate = getState(errorCode); + String message = translate(sqlstate, params); + return new DbException(getJdbcSQLException(message, sql, sqlstate, errorCode, null, null)); + } + + /** + * Gets a SQL exception meaning this feature is not supported. + * + * @param message what exactly is not supported + * @return the exception + */ + public static DbException getUnsupportedException(String message) { + return get(FEATURE_NOT_SUPPORTED_1, message); + } + + /** + * Gets a SQL exception meaning this value is invalid. + * + * @param param the name of the parameter + * @param value the value passed + * @return the exception + */ + public static DbException getInvalidValueException(String param, Object value) { + return get(INVALID_VALUE_2, value == null ? "null" : value.toString(), param); + } + + /** + * Gets a SQL exception meaning this value is too long. + * + * @param columnOrType + * column with data type or data type name + * @param value + * string representation of value, will be truncated to 80 + * characters + * @param valueLength + * the actual length of value, {@code -1L} if unknown + * @return the exception + */ + public static DbException getValueTooLongException(String columnOrType, String value, long valueLength) { + int length = value.length(); + int m = valueLength >= 0 ? 22 : 0; + StringBuilder builder = length > 80 // + ? new StringBuilder(83 + m).append(value, 0, 80).append("...") + : new StringBuilder(length + m).append(value); + if (valueLength >= 0) { + builder.append(" (").append(valueLength).append(')'); + } + return get(VALUE_TOO_LONG_2, columnOrType, builder.toString()); + } + + /** + * Gets a file version exception. + * + * @param dataFileName the name of the database + * @return the exception + */ + public static DbException getFileVersionError(String dataFileName) { + return DbException.get(FILE_VERSION_ERROR_1, "Old database: " + dataFileName + + " - please convert the database to a SQL script and re-create it."); + } + + /** + * Gets an internal error. + * + * @param s the message + * @return the RuntimeException object + */ + public static RuntimeException getInternalError(String s) { + RuntimeException e = new RuntimeException(s); + DbException.traceThrowable(e); + return e; + } + + /** + * Gets an internal error. + * + * @return the RuntimeException object + */ + public static RuntimeException getInternalError() { + return getInternalError("Unexpected code path"); + } + + /** + * Convert an exception to a SQL exception using the default mapping. + * + * @param e the root cause + * @return the SQL exception object + */ + public static SQLException toSQLException(Throwable e) { + if (e instanceof SQLException) { + return (SQLException) e; + } + return convert(e).getSQLException(); + } + + /** + * Convert a throwable to an SQL exception using the default mapping. All + * errors except the following are re-thrown: StackOverflowError, + * LinkageError. + * + * @param e the root cause + * @return the exception object + */ + public static DbException convert(Throwable e) { + try { + if (e instanceof DbException) { + return (DbException) e; + } else if (e instanceof SQLException) { + return new DbException((SQLException) e); + } else if (e instanceof InvocationTargetException) { + return convertInvocation((InvocationTargetException) e, null); + } else if (e instanceof IOException) { + return get(IO_EXCEPTION_1, e, e.toString()); + } else if (e instanceof OutOfMemoryError) { + return get(OUT_OF_MEMORY, e); + } else if (e instanceof StackOverflowError || e instanceof LinkageError) { + return get(GENERAL_ERROR_1, e, e.toString()); + } else if (e instanceof Error) { + throw (Error) e; + } + return get(GENERAL_ERROR_1, e, e.toString()); + } catch (OutOfMemoryError ignore) { + return OOME; + } catch (Throwable ex) { + try { + DbException dbException = new DbException( + new SQLException("GeneralError", "HY000", GENERAL_ERROR_1, e)); + dbException.addSuppressed(ex); + return dbException; + } catch (OutOfMemoryError ignore) { + return OOME; + } + } + } + + /** + * Convert an InvocationTarget exception to a database exception. + * + * @param te the root cause + * @param message the added message or null + * @return the database exception object + */ + public static DbException convertInvocation(InvocationTargetException te, + String message) { + Throwable t = te.getTargetException(); + if (t instanceof SQLException || t instanceof DbException) { + return convert(t); + } + message = message == null ? t.getMessage() : message + ": " + t.getMessage(); + return get(EXCEPTION_IN_FUNCTION_1, t, message); + } + + /** + * Convert an IO exception to a database exception. + * + * @param e the root cause + * @param message the message or null + * @return the database exception object + */ + public static DbException convertIOException(IOException e, String message) { + if (message == null) { + Throwable t = e.getCause(); + if (t instanceof DbException) { + return (DbException) t; + } + return get(IO_EXCEPTION_1, e, e.toString()); + } + return get(IO_EXCEPTION_2, e, e.toString(), message); + } + + /** + * Gets the SQL exception object for a specific error code. + * + * @param errorCode the error code + * @return the SQLException object + */ + public static SQLException getJdbcSQLException(int errorCode) { + return getJdbcSQLException(errorCode, (Throwable)null); + } + + /** + * Gets the SQL exception object for a specific error code. + * + * @param errorCode the error code + * @param p1 the first parameter of the message + * @return the SQLException object + */ + public static SQLException getJdbcSQLException(int errorCode, String p1) { + return getJdbcSQLException(errorCode, null, p1); + } + + /** + * Gets the SQL exception object for a specific error code. + * + * @param errorCode the error code + * @param cause the cause of the exception + * @param params the list of parameters of the message + * @return the SQLException object + */ + public static SQLException getJdbcSQLException(int errorCode, Throwable cause, String... params) { + String sqlstate = getState(errorCode); + String message = translate(sqlstate, params); + return getJdbcSQLException(message, null, sqlstate, errorCode, cause, null); + } + + /** + * Creates a SQLException. + * + * @param message the reason + * @param sql the SQL statement + * @param state the SQL state + * @param errorCode the error code + * @param cause the exception that was the reason for this exception + * @param stackTrace the stack trace + * @return the SQLException object + */ + public static SQLException getJdbcSQLException(String message, String sql, String state, int errorCode, + Throwable cause, String stackTrace) { + sql = filterSQL(sql); + // Use SQLState class value to detect type + switch (errorCode / 1_000) { + case 2: + return new JdbcSQLNonTransientException(message, sql, state, errorCode, cause, stackTrace); + case 7: + case 21: + case 42: + case 54: + return new JdbcSQLSyntaxErrorException(message, sql, state, errorCode, cause, stackTrace); + case 8: + return new JdbcSQLNonTransientConnectionException(message, sql, state, errorCode, cause, stackTrace); + case 22: + return new JdbcSQLDataException(message, sql, state, errorCode, cause, stackTrace); + case 23: + return new JdbcSQLIntegrityConstraintViolationException(message, sql, state, errorCode, cause, stackTrace); + case 28: + return new JdbcSQLInvalidAuthorizationSpecException(message, sql, state, errorCode, cause, stackTrace); + case 40: + return new JdbcSQLTransactionRollbackException(message, sql, state, errorCode, cause, stackTrace); + } + // Check error code + switch (errorCode){ + case GENERAL_ERROR_1: + case UNKNOWN_DATA_TYPE_1: + case METHOD_NOT_ALLOWED_FOR_QUERY: + case METHOD_ONLY_ALLOWED_FOR_QUERY: + case SEQUENCE_EXHAUSTED: + case OBJECT_CLOSED: + case CANNOT_DROP_CURRENT_USER: + case UNSUPPORTED_SETTING_COMBINATION: + case FILE_RENAME_FAILED_2: + case FILE_DELETE_FAILED_1: + case IO_EXCEPTION_1: + case NOT_ON_UPDATABLE_ROW: + case IO_EXCEPTION_2: + case TRACE_FILE_ERROR_2: + case ADMIN_RIGHTS_REQUIRED: + case ERROR_EXECUTING_TRIGGER_3: + case COMMIT_ROLLBACK_NOT_ALLOWED: + case FILE_CREATION_FAILED_1: + case SAVEPOINT_IS_INVALID_1: + case SAVEPOINT_IS_UNNAMED: + case SAVEPOINT_IS_NAMED: + case NOT_ENOUGH_RIGHTS_FOR_1: + case DATABASE_IS_READ_ONLY: + case WRONG_XID_FORMAT_1: + case UNSUPPORTED_COMPRESSION_OPTIONS_1: + case UNSUPPORTED_COMPRESSION_ALGORITHM_1: + case COMPRESSION_ERROR: + case EXCEPTION_IN_FUNCTION_1: + case ERROR_ACCESSING_LINKED_TABLE_2: + case FILE_NOT_FOUND_1: + case INVALID_CLASS_2: + case DATABASE_IS_NOT_PERSISTENT: + case RESULT_SET_NOT_UPDATABLE: + case RESULT_SET_NOT_SCROLLABLE: + case METHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT: + case ACCESS_DENIED_TO_CLASS_1: + case RESULT_SET_READONLY: + case CURRENT_SEQUENCE_VALUE_IS_NOT_DEFINED_IN_SESSION_1: + return new JdbcSQLNonTransientException(message, sql, state, errorCode, cause, stackTrace); + case FEATURE_NOT_SUPPORTED_1: + return new JdbcSQLFeatureNotSupportedException(message, sql, state, errorCode, cause, stackTrace); + case LOCK_TIMEOUT_1: + case STATEMENT_WAS_CANCELED: + case LOB_CLOSED_ON_TIMEOUT_1: + return new JdbcSQLTimeoutException(message, sql, state, errorCode, cause, stackTrace); + case FUNCTION_MUST_RETURN_RESULT_SET_1: + case INVALID_TRIGGER_FLAGS_1: + case SUM_OR_AVG_ON_WRONG_DATATYPE_1: + case MUST_GROUP_BY_COLUMN_1: + case SECOND_PRIMARY_KEY: + case FUNCTION_NOT_FOUND_1: + case COLUMN_MUST_NOT_BE_NULLABLE_1: + case USER_NOT_FOUND_1: + case USER_ALREADY_EXISTS_1: + case SEQUENCE_ALREADY_EXISTS_1: + case SEQUENCE_NOT_FOUND_1: + case VIEW_NOT_FOUND_1: + case VIEW_ALREADY_EXISTS_1: + case TRIGGER_ALREADY_EXISTS_1: + case TRIGGER_NOT_FOUND_1: + case ERROR_CREATING_TRIGGER_OBJECT_3: + case CONSTRAINT_ALREADY_EXISTS_1: + case SUBQUERY_IS_NOT_SINGLE_COLUMN: + case INVALID_USE_OF_AGGREGATE_FUNCTION_1: + case CONSTRAINT_NOT_FOUND_1: + case AMBIGUOUS_COLUMN_NAME_1: + case ORDER_BY_NOT_IN_RESULT: + case ROLE_ALREADY_EXISTS_1: + case ROLE_NOT_FOUND_1: + case USER_OR_ROLE_NOT_FOUND_1: + case ROLES_AND_RIGHT_CANNOT_BE_MIXED: + case METHODS_MUST_HAVE_DIFFERENT_PARAMETER_COUNTS_2: + case ROLE_ALREADY_GRANTED_1: + case COLUMN_IS_PART_OF_INDEX_1: + case FUNCTION_ALIAS_ALREADY_EXISTS_1: + case FUNCTION_ALIAS_NOT_FOUND_1: + case SCHEMA_ALREADY_EXISTS_1: + case SCHEMA_NOT_FOUND_1: + case SCHEMA_NAME_MUST_MATCH: + case COLUMN_CONTAINS_NULL_VALUES_1: + case SEQUENCE_BELONGS_TO_A_TABLE_1: + case COLUMN_IS_REFERENCED_1: + case CANNOT_DROP_LAST_COLUMN: + case INDEX_BELONGS_TO_CONSTRAINT_2: + case CLASS_NOT_FOUND_1: + case METHOD_NOT_FOUND_1: + case COLLATION_CHANGE_WITH_DATA_TABLE_1: + case SCHEMA_CAN_NOT_BE_DROPPED_1: + case ROLE_CAN_NOT_BE_DROPPED_1: + case CANNOT_TRUNCATE_1: + case CANNOT_DROP_2: + case VIEW_IS_INVALID_2: + case TYPES_ARE_NOT_COMPARABLE_2: + case CONSTANT_ALREADY_EXISTS_1: + case CONSTANT_NOT_FOUND_1: + case LITERALS_ARE_NOT_ALLOWED: + case CANNOT_DROP_TABLE_1: + case DOMAIN_ALREADY_EXISTS_1: + case DOMAIN_NOT_FOUND_1: + case WITH_TIES_WITHOUT_ORDER_BY: + case CANNOT_MIX_INDEXED_AND_UNINDEXED_PARAMS: + case TRANSACTION_NOT_FOUND_1: + case AGGREGATE_NOT_FOUND_1: + case WINDOW_NOT_FOUND_1: + case CAN_ONLY_ASSIGN_TO_VARIABLE_1: + case PUBLIC_STATIC_JAVA_METHOD_NOT_FOUND_1: + case JAVA_OBJECT_SERIALIZER_CHANGE_WITH_DATA_TABLE: + case FOR_UPDATE_IS_NOT_ALLOWED_IN_DISTINCT_OR_GROUPED_SELECT: + case INVALID_VALUE_PRECISION: + case INVALID_VALUE_SCALE: + case CONSTRAINT_IS_USED_BY_CONSTRAINT_2: + case UNCOMPARABLE_REFERENCED_COLUMN_2: + case GENERATED_COLUMN_CANNOT_BE_ASSIGNED_1: + case GENERATED_COLUMN_CANNOT_BE_UPDATABLE_BY_CONSTRAINT_2: + case COLUMN_ALIAS_IS_NOT_SPECIFIED_1: + case GROUP_BY_NOT_IN_THE_RESULT: + return new JdbcSQLSyntaxErrorException(message, sql, state, errorCode, cause, stackTrace); + case HEX_STRING_ODD_1: + case HEX_STRING_WRONG_1: + case INVALID_VALUE_2: + case SEQUENCE_ATTRIBUTES_INVALID_7: + case INVALID_TO_CHAR_FORMAT: + case PARAMETER_NOT_SET_1: + case PARSE_ERROR_1: + case INVALID_TO_DATE_FORMAT: + case STRING_FORMAT_ERROR_1: + case SERIALIZATION_FAILED_1: + case DESERIALIZATION_FAILED_1: + case SCALAR_SUBQUERY_CONTAINS_MORE_THAN_ONE_ROW: + case STEP_SIZE_MUST_NOT_BE_ZERO: + return new JdbcSQLDataException(message, sql, state, errorCode, cause, stackTrace); + case URL_RELATIVE_TO_CWD: + case DATABASE_NOT_FOUND_1: + case DATABASE_NOT_FOUND_WITH_IF_EXISTS_1: + case REMOTE_DATABASE_NOT_FOUND_1: + case TRACE_CONNECTION_NOT_CLOSED: + case DATABASE_ALREADY_OPEN_1: + case FILE_CORRUPTED_1: + case URL_FORMAT_ERROR_2: + case DRIVER_VERSION_ERROR_2: + case FILE_VERSION_ERROR_1: + case FILE_ENCRYPTION_ERROR_1: + case WRONG_PASSWORD_FORMAT: + case UNSUPPORTED_CIPHER: + case UNSUPPORTED_LOCK_METHOD_1: + case EXCEPTION_OPENING_PORT_2: + case DUPLICATE_PROPERTY_1: + case CONNECTION_BROKEN_1: + case UNKNOWN_MODE_1: + case CLUSTER_ERROR_DATABASE_RUNS_ALONE: + case CLUSTER_ERROR_DATABASE_RUNS_CLUSTERED_1: + case DATABASE_IS_CLOSED: + case ERROR_SETTING_DATABASE_EVENT_LISTENER_2: + case OUT_OF_MEMORY: + case UNSUPPORTED_SETTING_1: + case REMOTE_CONNECTION_NOT_ALLOWED: + case DATABASE_CALLED_AT_SHUTDOWN: + case CANNOT_CHANGE_SETTING_WHEN_OPEN_1: + case DATABASE_IS_IN_EXCLUSIVE_MODE: + case INVALID_DATABASE_NAME_1: + case AUTHENTICATOR_NOT_AVAILABLE: + case METHOD_DISABLED_ON_AUTOCOMMIT_TRUE: + return new JdbcSQLNonTransientConnectionException(message, sql, state, errorCode, cause, stackTrace); + case ROW_NOT_FOUND_WHEN_DELETING_1: + case CONCURRENT_UPDATE_1: + case ROW_NOT_FOUND_IN_PRIMARY_INDEX: + return new JdbcSQLTransientException(message, sql, state, errorCode, cause, stackTrace); + } + // Default + return new JdbcSQLException(message, sql, state, errorCode, cause, stackTrace); + } + + private static String filterSQL(String sql) { + return sql == null || !sql.contains(HIDE_SQL) ? sql : "-"; + } + + /** + * Builds message for an exception. + * + * @param e exception + * @return message + */ + public static String buildMessageForException(JdbcException e) { + String s = e.getOriginalMessage(); + StringBuilder buff = new StringBuilder(s != null ? s : "- "); + s = e.getSQL(); + if (s != null) { + buff.append("; SQL statement:\n").append(s); + } + buff.append(" [").append(e.getErrorCode()).append('-').append(Constants.BUILD_ID).append(']'); + return buff.toString(); + } + + /** + * Prints up to 100 next exceptions for a specified SQL exception. + * + * @param e SQL exception + * @param s print writer + */ + public static void printNextExceptions(SQLException e, PrintWriter s) { + // getNextException().printStackTrace(s) would be very slow + // if many exceptions are joined + int i = 0; + while ((e = e.getNextException()) != null) { + if (i++ == 100) { + s.println("(truncated)"); + return; + } + s.println(e.toString()); + } + } + + /** + * Prints up to 100 next exceptions for a specified SQL exception. + * + * @param e SQL exception + * @param s print stream + */ + public static void printNextExceptions(SQLException e, PrintStream s) { + // getNextException().printStackTrace(s) would be very slow + // if many exceptions are joined + int i = 0; + while ((e = e.getNextException()) != null) { + if (i++ == 100) { + s.println("(truncated)"); + return; + } + s.println(e.toString()); + } + } + + public Object getSource() { + return source; + } + + public void setSource(Object source) { + this.source = source; + } + + /** + * Write the exception to the driver manager log writer if configured. + * + * @param e the exception + */ + public static void traceThrowable(Throwable e) { + PrintWriter writer = DriverManager.getLogWriter(); + if (writer != null) { + e.printStackTrace(writer); + } + } + +} diff --git a/h2/src/main/org/h2/message/Trace.java b/h2/src/main/org/h2/message/Trace.java new file mode 100644 index 0000000..dd84fd6 --- /dev/null +++ b/h2/src/main/org/h2/message/Trace.java @@ -0,0 +1,356 @@ +/* + * 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.message; + +import java.text.MessageFormat; +import java.util.ArrayList; + +import org.h2.expression.ParameterInterface; +import org.h2.util.StringUtils; + +/** + * This class represents a trace module. + */ +public final class Trace { + + /** + * The trace module id for commands. + */ + public static final int COMMAND = 0; + + /** + * The trace module id for constraints. + */ + public static final int CONSTRAINT = 1; + + /** + * The trace module id for databases. + */ + public static final int DATABASE = 2; + + /** + * The trace module id for functions. + */ + public static final int FUNCTION = 3; + + /** + * The trace module id for file locks. + */ + public static final int FILE_LOCK = 4; + + /** + * The trace module id for indexes. + */ + public static final int INDEX = 5; + + /** + * The trace module id for the JDBC API. + */ + public static final int JDBC = 6; + + /** + * The trace module id for locks. + */ + public static final int LOCK = 7; + + /** + * The trace module id for schemas. + */ + public static final int SCHEMA = 8; + + /** + * The trace module id for sequences. + */ + public static final int SEQUENCE = 9; + + /** + * The trace module id for settings. + */ + public static final int SETTING = 10; + + /** + * The trace module id for tables. + */ + public static final int TABLE = 11; + + /** + * The trace module id for triggers. + */ + public static final int TRIGGER = 12; + + /** + * The trace module id for users. + */ + public static final int USER = 13; + + /** + * The trace module id for the JDBCX API + */ + public static final int JDBCX = 14; + + /** + * Module names by their ids as array indexes. + */ + static final String[] MODULE_NAMES = { + "command", + "constraint", + "database", + "function", + "fileLock", + "index", + "jdbc", + "lock", + "schema", + "sequence", + "setting", + "table", + "trigger", + "user", + "JDBCX" + }; + + private final TraceWriter traceWriter; + private final String module; + private final String lineSeparator; + private int traceLevel = TraceSystem.PARENT; + + Trace(TraceWriter traceWriter, int moduleId) { + this(traceWriter, MODULE_NAMES[moduleId]); + } + + Trace(TraceWriter traceWriter, String module) { + this.traceWriter = traceWriter; + this.module = module; + this.lineSeparator = System.lineSeparator(); + } + + /** + * Set the trace level of this component. This setting overrides the parent + * trace level. + * + * @param level the new level + */ + public void setLevel(int level) { + this.traceLevel = level; + } + + private boolean isEnabled(int level) { + if (this.traceLevel == TraceSystem.PARENT) { + return traceWriter.isEnabled(level); + } + return level <= this.traceLevel; + } + + /** + * Check if the trace level is equal or higher than INFO. + * + * @return true if it is + */ + public boolean isInfoEnabled() { + return isEnabled(TraceSystem.INFO); + } + + /** + * Check if the trace level is equal or higher than DEBUG. + * + * @return true if it is + */ + public boolean isDebugEnabled() { + return isEnabled(TraceSystem.DEBUG); + } + + /** + * Write a message with trace level ERROR to the trace system. + * + * @param t the exception + * @param s the message + */ + public void error(Throwable t, String s) { + if (isEnabled(TraceSystem.ERROR)) { + traceWriter.write(TraceSystem.ERROR, module, s, t); + } + } + + /** + * Write a message with trace level ERROR to the trace system. + * + * @param t the exception + * @param s the message + * @param params the parameters + */ + public void error(Throwable t, String s, Object... params) { + if (isEnabled(TraceSystem.ERROR)) { + s = MessageFormat.format(s, params); + traceWriter.write(TraceSystem.ERROR, module, s, t); + } + } + + /** + * Write a message with trace level INFO to the trace system. + * + * @param s the message + */ + public void info(String s) { + if (isEnabled(TraceSystem.INFO)) { + traceWriter.write(TraceSystem.INFO, module, s, null); + } + } + + /** + * Write a message with trace level INFO to the trace system. + * + * @param s the message + * @param params the parameters + */ + public void info(String s, Object... params) { + if (isEnabled(TraceSystem.INFO)) { + s = MessageFormat.format(s, params); + traceWriter.write(TraceSystem.INFO, module, s, null); + } + } + + /** + * Write a message with trace level INFO to the trace system. + * + * @param t the exception + * @param s the message + */ + void info(Throwable t, String s) { + if (isEnabled(TraceSystem.INFO)) { + traceWriter.write(TraceSystem.INFO, module, s, t); + } + } + + /** + * Format the parameter list. + * + * @param parameters the parameter list + * @return the formatted text + */ + public static String formatParams(ArrayList parameters) { + if (parameters.isEmpty()) { + return ""; + } + StringBuilder builder = new StringBuilder(); + int i = 0; + for (ParameterInterface p : parameters) { + if (p.isValueSet()) { + builder.append(i == 0 ? " {" : ", ") // + .append(++i).append(": ") // + .append(p.getParamValue().getTraceSQL()); + } + } + if (i != 0) { + builder.append('}'); + } + return builder.toString(); + } + + /** + * Write a SQL statement with trace level INFO to the trace system. + * + * @param sql the SQL statement + * @param params the parameters used, in the for {1:...} + * @param count the update count + * @param time the time it took to run the statement in ms + */ + public void infoSQL(String sql, String params, long count, long time) { + if (!isEnabled(TraceSystem.INFO)) { + return; + } + StringBuilder buff = new StringBuilder(sql.length() + params.length() + 20); + buff.append(lineSeparator).append("/*SQL"); + boolean space = false; + if (params.length() > 0) { + // This looks like a bug, but it is intentional: + // If there are no parameters, the SQL statement is + // the rest of the line. If there are parameters, they + // are appended at the end of the line. Knowing the size + // of the statement simplifies separating the SQL statement + // from the parameters (no need to parse). + space = true; + buff.append(" l:").append(sql.length()); + } + if (count > 0) { + space = true; + buff.append(" #:").append(count); + } + if (time > 0) { + space = true; + buff.append(" t:").append(time); + } + if (!space) { + buff.append(' '); + } + buff.append("*/"); + StringUtils.javaEncode(sql, buff, false); + StringUtils.javaEncode(params, buff, false); + buff.append(';'); + sql = buff.toString(); + traceWriter.write(TraceSystem.INFO, module, sql, null); + } + + /** + * Write a message with trace level DEBUG to the trace system. + * + * @param s the message + * @param params the parameters + */ + public void debug(String s, Object... params) { + if (isEnabled(TraceSystem.DEBUG)) { + s = MessageFormat.format(s, params); + traceWriter.write(TraceSystem.DEBUG, module, s, null); + } + } + + /** + * Write a message with trace level DEBUG to the trace system. + * + * @param s the message + */ + public void debug(String s) { + if (isEnabled(TraceSystem.DEBUG)) { + traceWriter.write(TraceSystem.DEBUG, module, s, null); + } + } + + /** + * Write a message with trace level DEBUG to the trace system. + * @param t the exception + * @param s the message + */ + public void debug(Throwable t, String s) { + if (isEnabled(TraceSystem.DEBUG)) { + traceWriter.write(TraceSystem.DEBUG, module, s, t); + } + } + + + /** + * Write Java source code with trace level INFO to the trace system. + * + * @param java the source code + */ + public void infoCode(String java) { + if (isEnabled(TraceSystem.INFO)) { + traceWriter.write(TraceSystem.INFO, module, lineSeparator + + "/**/" + java, null); + } + } + + /** + * Write Java source code with trace level DEBUG to the trace system. + * + * @param java the source code + */ + void debugCode(String java) { + if (isEnabled(TraceSystem.DEBUG)) { + traceWriter.write(TraceSystem.DEBUG, module, lineSeparator + + "/**/" + java, null); + } + } + +} diff --git a/h2/src/main/org/h2/message/TraceObject.java b/h2/src/main/org/h2/message/TraceObject.java new file mode 100644 index 0000000..5844478 --- /dev/null +++ b/h2/src/main/org/h2/message/TraceObject.java @@ -0,0 +1,403 @@ +/* + * 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.message; + +import java.math.BigDecimal; +import java.sql.SQLException; +import java.util.Map; +import java.util.concurrent.atomic.AtomicIntegerArray; + +import org.h2.api.ErrorCode; +import org.h2.util.StringUtils; + +/** + * The base class for objects that can print trace information about themselves. + */ +public abstract class TraceObject { + + /** + * The trace type id for callable statements. + */ + protected static final int CALLABLE_STATEMENT = 0; + + /** + * The trace type id for connections. + */ + protected static final int CONNECTION = 1; + + /** + * The trace type id for database meta data objects. + */ + protected static final int DATABASE_META_DATA = 2; + + /** + * The trace type id for prepared statements. + */ + protected static final int PREPARED_STATEMENT = 3; + + /** + * The trace type id for result sets. + */ + protected static final int RESULT_SET = 4; + + /** + * The trace type id for result set meta data objects. + */ + protected static final int RESULT_SET_META_DATA = 5; + + /** + * The trace type id for savepoint objects. + */ + protected static final int SAVEPOINT = 6; + + /** + * The trace type id for statements. + */ + protected static final int STATEMENT = 8; + + /** + * The trace type id for blobs. + */ + protected static final int BLOB = 9; + + /** + * The trace type id for clobs. + */ + protected static final int CLOB = 10; + + /** + * The trace type id for parameter meta data objects. + */ + protected static final int PARAMETER_META_DATA = 11; + + /** + * The trace type id for data sources. + */ + protected static final int DATA_SOURCE = 12; + + /** + * The trace type id for XA data sources. + */ + protected static final int XA_DATA_SOURCE = 13; + + /** + * The trace type id for transaction ids. + */ + protected static final int XID = 15; + + /** + * The trace type id for array objects. + */ + protected static final int ARRAY = 16; + + /** + * The trace type id for SQLXML objects. + */ + protected static final int SQLXML = 17; + + private static final int LAST = SQLXML + 1; + private static final AtomicIntegerArray ID = new AtomicIntegerArray(LAST); + + private static final String[] PREFIX = { "call", "conn", "dbMeta", "prep", + "rs", "rsMeta", "sp", "ex", "stat", "blob", "clob", "pMeta", "ds", + "xads", "xares", "xid", "ar", "sqlxml" }; + + private static final SQLException SQL_OOME = DbException.SQL_OOME; + + /** + * The trace module used by this object. + */ + protected Trace trace; + + private int traceType; + private int id; + + /** + * Set the options to use when writing trace message. + * + * @param trace the trace object + * @param type the trace object type + * @param id the trace object id + */ + protected void setTrace(Trace trace, int type, int id) { + this.trace = trace; + this.traceType = type; + this.id = id; + } + + /** + * INTERNAL + * @return id + */ + public int getTraceId() { + return id; + } + + /** + * INTERNAL + * @return object name + */ + public String getTraceObjectName() { + return PREFIX[traceType] + id; + } + + /** + * Get the next trace object id for this object type. + * + * @param type the object type + * @return the new trace object id + */ + protected static int getNextId(int type) { + return ID.getAndIncrement(type); + } + + /** + * Check if the debug trace level is enabled. + * + * @return true if it is + */ + protected final boolean isDebugEnabled() { + return trace.isDebugEnabled(); + } + + /** + * Check if info trace level is enabled. + * + * @return true if it is + */ + protected final boolean isInfoEnabled() { + return trace.isInfoEnabled(); + } + + /** + * Write trace information as an assignment in the form + * className prefixId = objectName.value. + * + * @param className the class name of the result + * @param newType the prefix type + * @param newId the trace object id of the created object + * @param value the value to assign this new object to + */ + protected final void debugCodeAssign(String className, int newType, int newId, String value) { + if (trace.isDebugEnabled()) { + trace.debugCode(className + ' ' + PREFIX[newType] + newId + " = " + getTraceObjectName() + '.' + value + + ';'); + } + } + + /** + * Write trace information as a method call in the form + * objectName.methodName(). + * + * @param methodName the method name + */ + protected final void debugCodeCall(String methodName) { + if (trace.isDebugEnabled()) { + trace.debugCode(getTraceObjectName() + '.' + methodName + "();"); + } + } + + /** + * Write trace information as a method call in the form + * objectName.methodName(param) where the parameter is formatted as a long + * value. + * + * @param methodName the method name + * @param param one single long parameter + */ + protected final void debugCodeCall(String methodName, long param) { + if (trace.isDebugEnabled()) { + trace.debugCode(getTraceObjectName() + '.' + methodName + '(' + param + ");"); + } + } + + /** + * Write trace information as a method call in the form + * objectName.methodName(param) where the parameter is formatted as a Java + * string. + * + * @param methodName the method name + * @param param one single string parameter + */ + protected final void debugCodeCall(String methodName, String param) { + if (trace.isDebugEnabled()) { + trace.debugCode(getTraceObjectName() + '.' + methodName + '(' + quote(param) + ");"); + } + } + + /** + * Write trace information in the form objectName.text. + * + * @param text the trace text + */ + protected final void debugCode(String text) { + if (trace.isDebugEnabled()) { + trace.debugCode(getTraceObjectName() + '.' + text + ';'); + } + } + + /** + * Format a string as a Java string literal. + * + * @param s the string to convert + * @return the Java string literal + */ + protected static String quote(String s) { + return StringUtils.quoteJavaString(s); + } + + /** + * Format a time to the Java source code that represents this object. + * + * @param x the time to convert + * @return the Java source code + */ + protected static String quoteTime(java.sql.Time x) { + if (x == null) { + return "null"; + } + return "Time.valueOf(\"" + x.toString() + "\")"; + } + + /** + * Format a timestamp to the Java source code that represents this object. + * + * @param x the timestamp to convert + * @return the Java source code + */ + protected static String quoteTimestamp(java.sql.Timestamp x) { + if (x == null) { + return "null"; + } + return "Timestamp.valueOf(\"" + x.toString() + "\")"; + } + + /** + * Format a date to the Java source code that represents this object. + * + * @param x the date to convert + * @return the Java source code + */ + protected static String quoteDate(java.sql.Date x) { + if (x == null) { + return "null"; + } + return "Date.valueOf(\"" + x.toString() + "\")"; + } + + /** + * Format a big decimal to the Java source code that represents this object. + * + * @param x the big decimal to convert + * @return the Java source code + */ + protected static String quoteBigDecimal(BigDecimal x) { + if (x == null) { + return "null"; + } + return "new BigDecimal(\"" + x.toString() + "\")"; + } + + /** + * Format a byte array to the Java source code that represents this object. + * + * @param x the byte array to convert + * @return the Java source code + */ + protected static String quoteBytes(byte[] x) { + if (x == null) { + return "null"; + } + StringBuilder builder = new StringBuilder(x.length * 2 + 45) + .append("org.h2.util.StringUtils.convertHexToBytes(\""); + return StringUtils.convertBytesToHex(builder, x).append("\")").toString(); + } + + /** + * Format a string array to the Java source code that represents this + * object. + * + * @param s the string array to convert + * @return the Java source code + */ + protected static String quoteArray(String[] s) { + return StringUtils.quoteJavaStringArray(s); + } + + /** + * Format an int array to the Java source code that represents this object. + * + * @param s the int array to convert + * @return the Java source code + */ + protected static String quoteIntArray(int[] s) { + return StringUtils.quoteJavaIntArray(s); + } + + /** + * Format a map to the Java source code that represents this object. + * + * @param map the map to convert + * @return the Java source code + */ + protected static String quoteMap(Map> map) { + if (map == null) { + return "null"; + } + if (map.size() == 0) { + return "new Map()"; + } + return "new Map() /* " + map.toString() + " */"; + } + + /** + * Log an exception and convert it to a SQL exception if required. + * + * @param ex the exception + * @return the SQL exception object + */ + protected SQLException logAndConvert(Throwable ex) { + SQLException e = null; + try { + e = DbException.toSQLException(ex); + if (trace == null) { + DbException.traceThrowable(e); + } else { + int errorCode = e.getErrorCode(); + if (errorCode >= 23000 && errorCode < 24000) { + trace.info(e, "exception"); + } else { + trace.error(e, "exception"); + } + } + } catch(Throwable another) { + if (e == null) { + try { + e = new SQLException("GeneralError", "HY000", ErrorCode.GENERAL_ERROR_1, ex); + } catch (OutOfMemoryError | NoClassDefFoundError ignored) { + return SQL_OOME; + } + } + e.addSuppressed(another); + } + return e; + } + + /** + * Get a SQL exception meaning this feature is not supported. + * + * @param message the message + * @return the SQL exception + */ + protected SQLException unsupported(String message) { + try { + throw DbException.getUnsupportedException(message); + } catch (Exception e) { + return logAndConvert(e); + } + } + +} diff --git a/h2/src/main/org/h2/message/TraceSystem.java b/h2/src/main/org/h2/message/TraceSystem.java new file mode 100644 index 0000000..96743a2 --- /dev/null +++ b/h2/src/main/org/h2/message/TraceSystem.java @@ -0,0 +1,353 @@ +/* + * 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.message; + +import java.io.IOException; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.Writer; +import java.text.SimpleDateFormat; +import java.util.concurrent.atomic.AtomicReferenceArray; +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.jdbc.JdbcException; +import org.h2.store.fs.FileUtils; +import org.h2.util.IOUtils; + +/** + * The trace mechanism is the logging facility of this database. There is + * usually one trace system per database. It is called 'trace' because the term + * 'log' is already used in the database domain and means 'transaction log'. It + * is possible to write after close was called, but that means for each write + * the file will be opened and closed again (which is slower). + */ +public class TraceSystem implements TraceWriter { + + /** + * The parent trace level should be used. + */ + public static final int PARENT = -1; + + /** + * This trace level means nothing should be written. + */ + public static final int OFF = 0; + + /** + * This trace level means only errors should be written. + */ + public static final int ERROR = 1; + + /** + * This trace level means errors and informational messages should be + * written. + */ + public static final int INFO = 2; + + /** + * This trace level means all type of messages should be written. + */ + public static final int DEBUG = 3; + + /** + * This trace level means all type of messages should be written, but + * instead of using the trace file the messages should be written to SLF4J. + */ + public static final int ADAPTER = 4; + + /** + * The default level for system out trace messages. + */ + public static final int DEFAULT_TRACE_LEVEL_SYSTEM_OUT = OFF; + + /** + * The default level for file trace messages. + */ + public static final int DEFAULT_TRACE_LEVEL_FILE = ERROR; + + /** + * The default maximum trace file size. It is currently 64 MB. Additionally, + * there could be a .old file of the same size. + */ + private static final int DEFAULT_MAX_FILE_SIZE = 64 * 1024 * 1024; + + private static final int CHECK_SIZE_EACH_WRITES = 4096; + + private int levelSystemOut = DEFAULT_TRACE_LEVEL_SYSTEM_OUT; + private int levelFile = DEFAULT_TRACE_LEVEL_FILE; + private int levelMax; + private int maxFileSize = DEFAULT_MAX_FILE_SIZE; + private String fileName; + private final AtomicReferenceArray traces = + new AtomicReferenceArray<>(Trace.MODULE_NAMES.length); + private SimpleDateFormat dateFormat; + private Writer fileWriter; + private PrintWriter printWriter; + /** + * Starts at -1 so that we check the file size immediately upon open. This + * Can be important if we open and close the trace file without managing to + * have written CHECK_SIZE_EACH_WRITES bytes each time. + */ + private int checkSize = -1; + private boolean closed; + private boolean writingErrorLogged; + private TraceWriter writer = this; + private PrintStream sysOut = System.out; + + /** + * Create a new trace system object. + * + * @param fileName the file name + */ + public TraceSystem(String fileName) { + this.fileName = fileName; + updateLevel(); + } + + private void updateLevel() { + levelMax = Math.max(levelSystemOut, levelFile); + } + + /** + * Set the print stream to use instead of System.out. + * + * @param out the new print stream + */ + public void setSysOut(PrintStream out) { + this.sysOut = out; + } + + /** + * Get or create a trace object for this module id. Trace modules with id + * are cached. + * + * @param moduleId module id + * @return the trace object + */ + public Trace getTrace(int moduleId) { + Trace t = traces.get(moduleId); + if (t == null) { + t = new Trace(writer, moduleId); + if (!traces.compareAndSet(moduleId, null, t)) { + t = traces.get(moduleId); + } + } + return t; + } + + /** + * Create a trace object for this module. Trace modules with names are not + * cached. + * + * @param module the module name + * @return the trace object + */ + public Trace getTrace(String module) { + return new Trace(writer, module); + } + + @Override + public boolean isEnabled(int level) { + if (levelMax == ADAPTER) { + return writer.isEnabled(level); + } + return level <= this.levelMax; + } + + /** + * Set the trace file name. + * + * @param name the file name + */ + public void setFileName(String name) { + this.fileName = name; + } + + /** + * Set the maximum trace file size in bytes. + * + * @param max the maximum size + */ + public void setMaxFileSize(int max) { + this.maxFileSize = max; + } + + /** + * Set the trace level to use for System.out + * + * @param level the new level + */ + public void setLevelSystemOut(int level) { + levelSystemOut = level; + updateLevel(); + } + + /** + * Set the file trace level. + * + * @param level the new level + */ + public void setLevelFile(int level) { + if (level == ADAPTER) { + String adapterClass = "org.h2.message.TraceWriterAdapter"; + try { + writer = (TraceWriter) Class.forName(adapterClass).getDeclaredConstructor().newInstance(); + } catch (Throwable e) { + e = DbException.get(ErrorCode.CLASS_NOT_FOUND_1, e, adapterClass); + write(ERROR, Trace.DATABASE, adapterClass, e); + return; + } + String name = fileName; + if (name != null) { + if (name.endsWith(Constants.SUFFIX_TRACE_FILE)) { + name = name.substring(0, name.length() - Constants.SUFFIX_TRACE_FILE.length()); + } + int idx = Math.max(name.lastIndexOf('/'), name.lastIndexOf('\\')); + if (idx >= 0) { + name = name.substring(idx + 1); + } + writer.setName(name); + } + } + levelFile = level; + updateLevel(); + } + + public int getLevelFile() { + return levelFile; + } + + private synchronized String format(String module, String s) { + if (dateFormat == null) { + dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss "); + } + return dateFormat.format(System.currentTimeMillis()) + module + ": " + s; + } + + @Override + public void write(int level, int moduleId, String s, Throwable t) { + write(level, Trace.MODULE_NAMES[moduleId], s, t); + } + + @Override + public void write(int level, String module, String s, Throwable t) { + if (level <= levelSystemOut || level > this.levelMax) { + // level <= levelSystemOut: the system out level is set higher + // level > this.level: the level for this module is set higher + sysOut.println(format(module, s)); + if (t != null && levelSystemOut == DEBUG) { + t.printStackTrace(sysOut); + } + } + if (fileName != null) { + if (level <= levelFile) { + writeFile(format(module, s), t); + } + } + } + + private synchronized void writeFile(String s, Throwable t) { + try { + checkSize = (checkSize + 1) % CHECK_SIZE_EACH_WRITES; + if (checkSize == 0) { + closeWriter(); + if (maxFileSize > 0 && FileUtils.size(fileName) > maxFileSize) { + String old = fileName + ".old"; + FileUtils.delete(old); + FileUtils.move(fileName, old); + } + } + if (!openWriter()) { + return; + } + printWriter.println(s); + if (t != null) { + if (levelFile == ERROR && t instanceof JdbcException) { + JdbcException se = (JdbcException) t; + int code = se.getErrorCode(); + if (ErrorCode.isCommon(code)) { + printWriter.println(t); + } else { + t.printStackTrace(printWriter); + } + } else { + t.printStackTrace(printWriter); + } + } + printWriter.flush(); + if (closed) { + closeWriter(); + } + } catch (Exception e) { + logWritingError(e); + } + } + + private void logWritingError(Exception e) { + if (writingErrorLogged) { + return; + } + writingErrorLogged = true; + Exception se = DbException.get( + ErrorCode.TRACE_FILE_ERROR_2, e, fileName, e.toString()); + // print this error only once + fileName = null; + sysOut.println(se); + se.printStackTrace(); + } + + private boolean openWriter() { + if (printWriter == null) { + try { + FileUtils.createDirectories(FileUtils.getParent(fileName)); + if (FileUtils.exists(fileName) && !FileUtils.canWrite(fileName)) { + // read only database: don't log error if the trace file + // can't be opened + return false; + } + fileWriter = IOUtils.getBufferedWriter( + FileUtils.newOutputStream(fileName, true)); + printWriter = new PrintWriter(fileWriter, true); + } catch (Exception e) { + logWritingError(e); + return false; + } + } + return true; + } + + private synchronized void closeWriter() { + if (printWriter != null) { + printWriter.flush(); + printWriter.close(); + printWriter = null; + } + if (fileWriter != null) { + try { + fileWriter.close(); + } catch (IOException e) { + // ignore + } + fileWriter = null; + } + } + + /** + * Close the writers, and the files if required. It is still possible to + * write after closing, however after each write the file is closed again + * (slowing down tracing). + */ + public void close() { + closeWriter(); + closed = true; + } + + @Override + public void setName(String name) { + // nothing to do (the file name is already set) + } + +} diff --git a/h2/src/main/org/h2/message/TraceWriter.java b/h2/src/main/org/h2/message/TraceWriter.java new file mode 100644 index 0000000..368411e --- /dev/null +++ b/h2/src/main/org/h2/message/TraceWriter.java @@ -0,0 +1,52 @@ +/* + * 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.message; + +/** + * The backend of the trace system must implement this interface. Two + * implementations are supported: the (default) native trace writer + * implementation that can write to a file and to system out, and an adapter + * that uses SLF4J (Simple Logging Facade for Java). + */ +interface TraceWriter { + + /** + * Set the name of the database or trace object. + * + * @param name the new name + */ + void setName(String name); + + /** + * Write a message. + * + * @param level the trace level + * @param module the name of the module + * @param s the message + * @param t the exception (may be null) + */ + void write(int level, String module, String s, Throwable t); + + /** + * Write a message. + * + * @param level the trace level + * @param moduleId the id of the module + * @param s the message + * @param t the exception (may be null) + */ + void write(int level, int moduleId, String s, Throwable t); + + + /** + * Check the given trace / log level is enabled. + * + * @param level the level + * @return true if the level is enabled + */ + boolean isEnabled(int level); + +} diff --git a/h2/src/main/org/h2/message/TraceWriterAdapter.java b/h2/src/main/org/h2/message/TraceWriterAdapter.java new file mode 100644 index 0000000..2ec4867 --- /dev/null +++ b/h2/src/main/org/h2/message/TraceWriterAdapter.java @@ -0,0 +1,76 @@ +/* + * 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.message; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This adapter sends log output to SLF4J. SLF4J supports multiple + * implementations such as Logback, Log4j, Jakarta Commons Logging (JCL), JDK + * 1.4 logging, x4juli, and Simple Log. To use SLF4J, you need to add the + * required jar files to the classpath, and set the trace level to 4 when + * opening a database: + * + *
+ * jdbc:h2:˜/test;TRACE_LEVEL_FILE=4
+ * 
+ * + * The logger name is 'h2database'. + */ +public class TraceWriterAdapter implements TraceWriter { + + private String name; + private final Logger logger = LoggerFactory.getLogger("h2database"); + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public boolean isEnabled(int level) { + switch (level) { + case TraceSystem.DEBUG: + return logger.isDebugEnabled(); + case TraceSystem.INFO: + return logger.isInfoEnabled(); + case TraceSystem.ERROR: + return logger.isErrorEnabled(); + default: + return false; + } + } + + @Override + public void write(int level, int moduleId, String s, Throwable t) { + write(level, Trace.MODULE_NAMES[moduleId], s, t); + } + + @Override + public void write(int level, String module, String s, Throwable t) { + if (isEnabled(level)) { + if (name != null) { + s = name + ":" + module + " " + s; + } else { + s = module + " " + s; + } + switch (level) { + case TraceSystem.DEBUG: + logger.debug(s, t); + break; + case TraceSystem.INFO: + logger.info(s, t); + break; + case TraceSystem.ERROR: + logger.error(s, t); + break; + default: + } + } + } + +} diff --git a/h2/src/main/org/h2/message/package.html b/h2/src/main/org/h2/message/package.html new file mode 100644 index 0000000..ccdcc35 --- /dev/null +++ b/h2/src/main/org/h2/message/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Trace (logging facility) and error message tool. + +

\ No newline at end of file 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 0000000..54a0f29 --- /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/DefaultNullOrdering.java b/h2/src/main/org/h2/mode/DefaultNullOrdering.java new file mode 100644 index 0000000..32c4e4a --- /dev/null +++ b/h2/src/main/org/h2/mode/DefaultNullOrdering.java @@ -0,0 +1,102 @@ +/* + * 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 static org.h2.result.SortOrder.DESCENDING; +import static org.h2.result.SortOrder.NULLS_FIRST; +import static org.h2.result.SortOrder.NULLS_LAST; + +/** + * Default ordering of NULL values. + */ +public enum DefaultNullOrdering { + + /** + * NULL values are considered as smaller than other values during sorting. + */ + LOW(NULLS_FIRST, NULLS_LAST), + + /** + * NULL values are considered as larger than other values during sorting. + */ + HIGH(NULLS_LAST, NULLS_FIRST), + + /** + * NULL values are sorted before other values, no matter if ascending or + * descending order is used. + */ + FIRST(NULLS_FIRST, NULLS_FIRST), + + /** + * NULL values are sorted after other values, no matter if ascending or + * descending order is used. + */ + LAST(NULLS_LAST, NULLS_LAST); + + private static final DefaultNullOrdering[] VALUES = values(); + + /** + * Returns default ordering of NULL values for the specified ordinal number. + * + * @param ordinal + * ordinal number + * @return default ordering of NULL values for the specified ordinal number + * @see #ordinal() + */ + public static DefaultNullOrdering valueOf(int ordinal) { + return VALUES[ordinal]; + } + + private final int defaultAscNulls, defaultDescNulls; + + private final int nullAsc, nullDesc; + + private DefaultNullOrdering(int defaultAscNulls, int defaultDescNulls) { + this.defaultAscNulls = defaultAscNulls; + this.defaultDescNulls = defaultDescNulls; + nullAsc = defaultAscNulls == NULLS_FIRST ? -1 : 1; + nullDesc = defaultDescNulls == NULLS_FIRST ? -1 : 1; + } + + /** + * Returns a sort type bit mask with {@link org.h2.result.SortOrder#NULLS_FIRST} or + * {@link org.h2.result.SortOrder#NULLS_LAST} explicitly set + * + * @param sortType + * sort type bit mask + * @return bit mask with {@link org.h2.result.SortOrder#NULLS_FIRST} or {@link org.h2.result.SortOrder#NULLS_LAST} + * explicitly set + */ + public int addExplicitNullOrdering(int sortType) { + if ((sortType & (NULLS_FIRST | NULLS_LAST)) == 0) { + sortType |= ((sortType & DESCENDING) == 0 ? defaultAscNulls : defaultDescNulls); + } + return sortType; + } + + /** + * Compare two expressions where one of them is NULL. + * + * @param aNull + * whether the first expression is null + * @param sortType + * the sort bit mask to use + * @return the result of the comparison (-1 meaning the first expression + * should appear before the second, 0 if they are equal) + */ + public int compareNull(boolean aNull, int sortType) { + if ((sortType & NULLS_FIRST) != 0) { + return aNull ? -1 : 1; + } else if ((sortType & NULLS_LAST) != 0) { + return aNull ? 1 : -1; + } else if ((sortType & DESCENDING) == 0) { + return aNull ? nullAsc : -nullAsc; + } else { + return aNull ? nullDesc : -nullDesc; + } + } + +} diff --git a/h2/src/main/org/h2/mode/FunctionInfo.java b/h2/src/main/org/h2/mode/FunctionInfo.java new file mode 100644 index 0000000..ba47964 --- /dev/null +++ b/h2/src/main/org/h2/mode/FunctionInfo.java @@ -0,0 +1,89 @@ +/* + * 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; + +/** + * This class contains information about a built-in function. + */ +public final class FunctionInfo { + + /** + * The name of the function. + */ + public final String name; + + /** + * The function type. + */ + public final int type; + + /** + * The number of parameters. + */ + final int parameterCount; + + /** + * The data type of the return value. + */ + public final int returnDataType; + + /** + * If the result of the function is NULL if any of the parameters is NULL. + */ + public final boolean nullIfParameterIsNull; + + /** + * If this function always returns the same value for the same parameters. + */ + public final boolean deterministic; + + /** + * Creates new instance of built-in function information. + * + * @param name + * the name of the function + * @param type + * the function type + * @param parameterCount + * the number of parameters + * @param returnDataType + * the data type of the return value + * @param nullIfParameterIsNull + * if the result of the function is NULL if any of the parameters + * is NULL + * @param deterministic + * if this function always returns the same value for the same + * parameters + */ + public FunctionInfo(String name, int type, int parameterCount, int returnDataType, boolean nullIfParameterIsNull, + boolean deterministic) { + this.name = name; + this.type = type; + this.parameterCount = parameterCount; + this.returnDataType = returnDataType; + this.nullIfParameterIsNull = nullIfParameterIsNull; + this.deterministic = deterministic; + } + + /** + * Creates a copy of built-in function information with a different name. A + * copy will require parentheses. + * + * @param source + * the source information + * @param name + * the new name + */ + public FunctionInfo(FunctionInfo source, String name) { + this.name = name; + type = source.type; + returnDataType = source.returnDataType; + parameterCount = source.parameterCount; + nullIfParameterIsNull = source.nullIfParameterIsNull; + deterministic = source.deterministic; + } + +} diff --git a/h2/src/main/org/h2/mode/FunctionsDB2Derby.java b/h2/src/main/org/h2/mode/FunctionsDB2Derby.java new file mode 100644 index 0000000..bc61364 --- /dev/null +++ b/h2/src/main/org/h2/mode/FunctionsDB2Derby.java @@ -0,0 +1,73 @@ +/* + * 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 java.util.HashMap; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.message.DbException; +import org.h2.value.ExtTypeInfoNumeric; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * Functions for {@link org.h2.engine.Mode.ModeEnum#DB2} and + * {@link org.h2.engine.Mode.ModeEnum#Derby} compatibility modes. + */ +public final class FunctionsDB2Derby extends ModeFunction { + + private static final int IDENTITY_VAL_LOCAL = 5001; + + private static final HashMap FUNCTIONS = new HashMap<>(); + + private static final TypeInfo IDENTITY_VAL_LOCAL_TYPE = TypeInfo.getTypeInfo(Value.NUMERIC, 31, 0, + ExtTypeInfoNumeric.DECIMAL); + + static { + FUNCTIONS.put("IDENTITY_VAL_LOCAL", + new FunctionInfo("IDENTITY_VAL_LOCAL", IDENTITY_VAL_LOCAL, 0, Value.BIGINT, true, false)); + } + + /** + * Returns mode-specific function for a given name, or {@code null}. + * + * @param upperName + * the upper-case name of a function + * @return the function with specified name or {@code null} + */ + public static FunctionsDB2Derby getFunction(String upperName) { + FunctionInfo info = FUNCTIONS.get(upperName); + return info != null ? new FunctionsDB2Derby(info) : null; + } + + private FunctionsDB2Derby(FunctionInfo info) { + super(info); + } + + @Override + public Value getValue(SessionLocal session) { + switch (info.type) { + case IDENTITY_VAL_LOCAL: + return session.getLastIdentity().convertTo(type); + default: + throw DbException.getInternalError("type=" + info.type); + } + } + + @Override + public Expression optimize(SessionLocal session) { + switch (info.type) { + case IDENTITY_VAL_LOCAL: + type = IDENTITY_VAL_LOCAL_TYPE; + break; + default: + throw DbException.getInternalError("type=" + info.type); + } + return this; + } + +} diff --git a/h2/src/main/org/h2/mode/FunctionsLegacy.java b/h2/src/main/org/h2/mode/FunctionsLegacy.java new file mode 100644 index 0000000..64df770 --- /dev/null +++ b/h2/src/main/org/h2/mode/FunctionsLegacy.java @@ -0,0 +1,69 @@ +/* + * 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 java.util.HashMap; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.message.DbException; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * This class implements some legacy functions not available in Regular mode. + */ +public class FunctionsLegacy extends ModeFunction { + + private static final HashMap FUNCTIONS = new HashMap<>(); + + private static final int IDENTITY = 6001; + + private static final int SCOPE_IDENTITY = IDENTITY + 1; + + static { + FUNCTIONS.put("IDENTITY", new FunctionInfo("IDENTITY", IDENTITY, 0, Value.BIGINT, true, false)); + FUNCTIONS.put("SCOPE_IDENTITY", + new FunctionInfo("SCOPE_IDENTITY", SCOPE_IDENTITY, 0, Value.BIGINT, true, false)); + } + + /** + * Returns mode-specific function for a given name, or {@code null}. + * + * @param upperName + * the upper-case name of a function + * @return the function with specified name or {@code null} + */ + public static FunctionsLegacy getFunction(String upperName) { + FunctionInfo info = FUNCTIONS.get(upperName); + if (info != null) { + return new FunctionsLegacy(info); + } + return null; + } + + private FunctionsLegacy(FunctionInfo info) { + super(info); + } + + @Override + public Value getValue(SessionLocal session) { + switch (info.type) { + case IDENTITY: + case SCOPE_IDENTITY: + return session.getLastIdentity().convertTo(type); + default: + throw DbException.getInternalError("type=" + info.type); + } + } + + @Override + public Expression optimize(SessionLocal session) { + type = TypeInfo.getTypeInfo(info.returnDataType); + return this; + } + +} diff --git a/h2/src/main/org/h2/mode/FunctionsMSSQLServer.java b/h2/src/main/org/h2/mode/FunctionsMSSQLServer.java new file mode 100644 index 0000000..92cfca0 --- /dev/null +++ b/h2/src/main/org/h2/mode/FunctionsMSSQLServer.java @@ -0,0 +1,143 @@ +/* + * 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 java.util.HashMap; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.TypedValueExpression; +import org.h2.expression.function.CoalesceFunction; +import org.h2.expression.function.CurrentDateTimeValueFunction; +import org.h2.expression.function.RandFunction; +import org.h2.expression.function.StringFunction; +import org.h2.message.DbException; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBigint; +import org.h2.value.ValueNull; + +/** + * Functions for {@link org.h2.engine.Mode.ModeEnum#MSSQLServer} compatibility + * mode. + */ +public final class FunctionsMSSQLServer extends ModeFunction { + + private static final HashMap FUNCTIONS = new HashMap<>(); + + private static final int CHARINDEX = 4001; + + private static final int GETDATE = CHARINDEX + 1; + + private static final int ISNULL = GETDATE + 1; + + private static final int LEN = ISNULL + 1; + + private static final int NEWID = LEN + 1; + + private static final int SCOPE_IDENTITY = NEWID + 1; + + private static final TypeInfo SCOPE_IDENTITY_TYPE = TypeInfo.getTypeInfo(Value.NUMERIC, 38, 0, null); + + static { + FUNCTIONS.put("CHARINDEX", new FunctionInfo("CHARINDEX", CHARINDEX, VAR_ARGS, Value.INTEGER, true, true)); + FUNCTIONS.put("GETDATE", new FunctionInfo("GETDATE", GETDATE, 0, Value.TIMESTAMP, false, true)); + FUNCTIONS.put("LEN", new FunctionInfo("LEN", LEN, 1, Value.INTEGER, true, true)); + FUNCTIONS.put("NEWID", new FunctionInfo("NEWID", NEWID, 0, Value.UUID, true, false)); + FUNCTIONS.put("ISNULL", new FunctionInfo("ISNULL", ISNULL, 2, Value.NULL, false, true)); + FUNCTIONS.put("SCOPE_IDENTITY", + new FunctionInfo("SCOPE_IDENTITY", SCOPE_IDENTITY, 0, Value.NUMERIC, true, false)); + } + + /** + * Returns mode-specific function for a given name, or {@code null}. + * + * @param upperName + * the upper-case name of a function + * @return the function with specified name or {@code null} + */ + public static FunctionsMSSQLServer getFunction(String upperName) { + FunctionInfo info = FUNCTIONS.get(upperName); + if (info != null) { + return new FunctionsMSSQLServer(info); + } + return null; + } + + private FunctionsMSSQLServer(FunctionInfo info) { + super(info); + } + + @Override + protected void checkParameterCount(int len) { + int min, max; + switch (info.type) { + case CHARINDEX: + min = 2; + max = 3; + break; + default: + throw DbException.getInternalError("type=" + info.type); + } + if (len < min || len > max) { + throw DbException.get(ErrorCode.INVALID_PARAMETER_COUNT_2, info.name, min + ".." + max); + } + } + + @Override + public Value getValue(SessionLocal session) { + Value[] values = getArgumentsValues(session, args); + if (values == null) { + return ValueNull.INSTANCE; + } + Value v0 = getNullOrValue(session, args, values, 0); + switch (info.type) { + case LEN: { + long len; + if (v0.getValueType() == Value.CHAR) { + String s = v0.getString(); + int l = s.length(); + while (l > 0 && s.charAt(l - 1) == ' ') { + l--; + } + len = l; + } else { + len = v0.charLength(); + } + return ValueBigint.get(len); + } + case SCOPE_IDENTITY: + return session.getLastIdentity().convertTo(type); + default: + throw DbException.getInternalError("type=" + info.type); + } + } + + @Override + public Expression optimize(SessionLocal session) { + switch (info.type) { + case CHARINDEX: + return new StringFunction(args, StringFunction.LOCATE).optimize(session); + case GETDATE: + return new CurrentDateTimeValueFunction(CurrentDateTimeValueFunction.LOCALTIMESTAMP, 3).optimize(session); + case ISNULL: + return new CoalesceFunction(CoalesceFunction.COALESCE, args).optimize(session); + case NEWID: + return new RandFunction(null, RandFunction.RANDOM_UUID).optimize(session); + case SCOPE_IDENTITY: + type = SCOPE_IDENTITY_TYPE; + break; + default: + type = TypeInfo.getTypeInfo(info.returnDataType); + if (optimizeArguments(session)) { + return TypedValueExpression.getTypedIfNull(getValue(session), type); + } + } + return this; + } + +} diff --git a/h2/src/main/org/h2/mode/FunctionsMySQL.java b/h2/src/main/org/h2/mode/FunctionsMySQL.java new file mode 100644 index 0000000..480100e --- /dev/null +++ b/h2/src/main/org/h2/mode/FunctionsMySQL.java @@ -0,0 +1,258 @@ +/* + * 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: Jason Brittain (jason.brittain at gmail.com) + */ +package org.h2.mode; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ValueExpression; +import org.h2.message.DbException; +import org.h2.util.DateTimeUtils; +import org.h2.util.StringUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBigint; +import org.h2.value.ValueInteger; +import org.h2.value.ValueNull; +import org.h2.value.ValueTimestamp; +import org.h2.value.ValueTimestampTimeZone; +import org.h2.value.ValueVarchar; + +/** + * This class implements some MySQL-specific functions. + * + * @author Jason Brittain + * @author Thomas Mueller + */ +public final class FunctionsMySQL extends ModeFunction { + + private static final int UNIX_TIMESTAMP = 1001, FROM_UNIXTIME = 1002, DATE = 1003, LAST_INSERT_ID = 1004; + + private static final HashMap FUNCTIONS = new HashMap<>(); + + static { + FUNCTIONS.put("UNIX_TIMESTAMP", + new FunctionInfo("UNIX_TIMESTAMP", UNIX_TIMESTAMP, VAR_ARGS, Value.INTEGER, false, false)); + FUNCTIONS.put("FROM_UNIXTIME", + new FunctionInfo("FROM_UNIXTIME", FROM_UNIXTIME, VAR_ARGS, Value.VARCHAR, false, true)); + FUNCTIONS.put("DATE", new FunctionInfo("DATE", DATE, 1, Value.DATE, false, true)); + FUNCTIONS.put("LAST_INSERT_ID", + new FunctionInfo("LAST_INSERT_ID", LAST_INSERT_ID, VAR_ARGS, Value.BIGINT, false, false)); + } + + /** + * The date format of a MySQL formatted date/time. + * Example: 2008-09-25 08:40:59 + */ + private static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; + + /** + * Format replacements for MySQL date formats. + * See + * https://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-format + */ + private static final String[] FORMAT_REPLACE = { + "%a", "EEE", + "%b", "MMM", + "%c", "MM", + "%d", "dd", + "%e", "d", + "%H", "HH", + "%h", "hh", + "%I", "hh", + "%i", "mm", + "%j", "DDD", + "%k", "H", + "%l", "h", + "%M", "MMMM", + "%m", "MM", + "%p", "a", + "%r", "hh:mm:ss a", + "%S", "ss", + "%s", "ss", + "%T", "HH:mm:ss", + "%W", "EEEE", + "%w", "F", + "%Y", "yyyy", + "%y", "yy", + "%%", "%", + }; + + /** + * Get the seconds since 1970-01-01 00:00:00 UTC of the given timestamp. + * See + * https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_unix-timestamp + * + * @param session the session + * @param value the timestamp + * @return the timestamp in seconds since EPOCH + */ + public static int unixTimestamp(SessionLocal session, Value value) { + long seconds; + if (value instanceof ValueTimestampTimeZone) { + ValueTimestampTimeZone t = (ValueTimestampTimeZone) value; + long timeNanos = t.getTimeNanos(); + seconds = DateTimeUtils.absoluteDayFromDateValue(t.getDateValue()) * DateTimeUtils.SECONDS_PER_DAY + + timeNanos / DateTimeUtils.NANOS_PER_SECOND - t.getTimeZoneOffsetSeconds(); + } else { + ValueTimestamp t = (ValueTimestamp) value.convertTo(TypeInfo.TYPE_TIMESTAMP, session); + long timeNanos = t.getTimeNanos(); + seconds = session.currentTimeZone().getEpochSecondsFromLocal(t.getDateValue(), timeNanos); + } + return (int) seconds; + } + + /** + * See + * https://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_from-unixtime + * + * @param seconds The current timestamp in seconds. + * @return a formatted date/time String in the format "yyyy-MM-dd HH:mm:ss". + */ + public static String fromUnixTime(int seconds) { + SimpleDateFormat formatter = new SimpleDateFormat(DATE_TIME_FORMAT, + Locale.ENGLISH); + return formatter.format(new Date(seconds * 1_000L)); + } + + /** + * See + * https://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_from-unixtime + * + * @param seconds The current timestamp in seconds. + * @param format The format of the date/time String to return. + * @return a formatted date/time String in the given format. + */ + public static String fromUnixTime(int seconds, String format) { + format = convertToSimpleDateFormat(format); + SimpleDateFormat formatter = new SimpleDateFormat(format, Locale.ENGLISH); + return formatter.format(new Date(seconds * 1_000L)); + } + + private static String convertToSimpleDateFormat(String format) { + String[] replace = FORMAT_REPLACE; + for (int i = 0; i < replace.length; i += 2) { + format = StringUtils.replaceAll(format, replace[i], replace[i + 1]); + } + return format; + } + + /** + * Returns mode-specific function for a given name, or {@code null}. + * + * @param upperName + * the upper-case name of a function + * @return the function with specified name or {@code null} + */ + public static FunctionsMySQL getFunction(String upperName) { + FunctionInfo info = FUNCTIONS.get(upperName); + return info != null ? new FunctionsMySQL(info) : null; + } + + FunctionsMySQL(FunctionInfo info) { + super(info); + } + + @Override + protected void checkParameterCount(int len) { + int min, max; + switch (info.type) { + case UNIX_TIMESTAMP: + min = 0; + max = 1; + break; + case FROM_UNIXTIME: + min = 1; + max = 2; + break; + case DATE: + min = 1; + max = 1; + break; + case LAST_INSERT_ID: + min = 0; + max = 1; + break; + default: + throw DbException.getInternalError("type=" + info.type); + } + if (len < min || len > max) { + throw DbException.get(ErrorCode.INVALID_PARAMETER_COUNT_2, info.name, min + ".." + max); + } + } + + @Override + public Expression optimize(SessionLocal session) { + boolean allConst = optimizeArguments(session); + type = TypeInfo.getTypeInfo(info.returnDataType); + if (allConst) { + return ValueExpression.get(getValue(session)); + } + return this; + } + + @Override + public Value getValue(SessionLocal session) { + Value[] values = new Value[args.length]; + Value v0 = getNullOrValue(session, args, values, 0); + Value v1 = getNullOrValue(session, args, values, 1); + Value result; + switch (info.type) { + case UNIX_TIMESTAMP: + result = ValueInteger.get(unixTimestamp(session, v0 == null ? session.currentTimestamp() : v0)); + break; + case FROM_UNIXTIME: + result = ValueVarchar.get( + v1 == null ? fromUnixTime(v0.getInt()) : fromUnixTime(v0.getInt(), v1.getString())); + break; + case DATE: + switch (v0.getValueType()) { + case Value.NULL: + case Value.DATE: + result = v0; + break; + default: + try { + v0 = v0.convertTo(TypeInfo.TYPE_TIMESTAMP, session); + } catch (DbException ex) { + result = ValueNull.INSTANCE; + break; + } + //$FALL-THROUGH$ + case Value.TIMESTAMP: + case Value.TIMESTAMP_TZ: + result = v0.convertToDate(session); + } + break; + case LAST_INSERT_ID: + if (args.length == 0) { + result = session.getLastIdentity(); + if (result == ValueNull.INSTANCE) { + result = ValueBigint.get(0L); + } else { + result = result.convertToBigint(null); + } + } else { + result = v0; + if (result == ValueNull.INSTANCE) { + session.setLastIdentity(ValueNull.INSTANCE); + } else { + session.setLastIdentity(result = result.convertToBigint(null)); + } + } + break; + default: + throw DbException.getInternalError("type=" + info.type); + } + return result; + } + +} diff --git a/h2/src/main/org/h2/mode/FunctionsOracle.java b/h2/src/main/org/h2/mode/FunctionsOracle.java new file mode 100644 index 0000000..d950752 --- /dev/null +++ b/h2/src/main/org/h2/mode/FunctionsOracle.java @@ -0,0 +1,135 @@ +/* + * 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 java.util.HashMap; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ValueExpression; +import org.h2.expression.function.DateTimeFunction; +import org.h2.message.DbException; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueNull; +import org.h2.value.ValueUuid; + +/** + * Functions for {@link org.h2.engine.Mode.ModeEnum#Oracle} compatibility mode. + */ +public final class FunctionsOracle extends ModeFunction { + + private static final int ADD_MONTHS = 2001; + + private static final int SYS_GUID = ADD_MONTHS + 1; + + private static final int TO_DATE = SYS_GUID + 1; + + private static final int TO_TIMESTAMP = TO_DATE + 1; + + private static final int TO_TIMESTAMP_TZ = TO_TIMESTAMP + 1; + + private static final HashMap FUNCTIONS = new HashMap<>(); + + static { + FUNCTIONS.put("ADD_MONTHS", + new FunctionInfo("ADD_MONTHS", ADD_MONTHS, 2, Value.TIMESTAMP, true, true)); + FUNCTIONS.put("SYS_GUID", + new FunctionInfo("SYS_GUID", SYS_GUID, 0, Value.VARBINARY, false, false)); + FUNCTIONS.put("TO_DATE", + new FunctionInfo("TO_DATE", TO_DATE, VAR_ARGS, Value.TIMESTAMP, true, true)); + FUNCTIONS.put("TO_TIMESTAMP", + new FunctionInfo("TO_TIMESTAMP", TO_TIMESTAMP, VAR_ARGS, Value.TIMESTAMP, true, true)); + FUNCTIONS.put("TO_TIMESTAMP_TZ", + new FunctionInfo("TO_TIMESTAMP_TZ", TO_TIMESTAMP_TZ, VAR_ARGS, Value.TIMESTAMP_TZ, true, true)); + } + + /** + * Returns mode-specific function for a given name, or {@code null}. + * + * @param upperName + * the upper-case name of a function + * @return the function with specified name or {@code null} + */ + public static FunctionsOracle getFunction(String upperName) { + FunctionInfo info = FUNCTIONS.get(upperName); + return info != null ? new FunctionsOracle(info) : null; + } + + private FunctionsOracle(FunctionInfo info) { + super(info); + } + + @Override + protected void checkParameterCount(int len) { + int min = 0, max = Integer.MAX_VALUE; + switch (info.type) { + case TO_TIMESTAMP: + case TO_TIMESTAMP_TZ: + min = 1; + max = 2; + break; + case TO_DATE: + min = 1; + max = 3; + break; + default: + throw DbException.getInternalError("type=" + info.type); + } + if (len < min || len > max) { + throw DbException.get(ErrorCode.INVALID_PARAMETER_COUNT_2, info.name, min + ".." + max); + } + } + + @Override + public Expression optimize(SessionLocal session) { + boolean allConst = optimizeArguments(session); + switch (info.type) { + case SYS_GUID: + type = TypeInfo.getTypeInfo(Value.VARBINARY, 16, 0, null); + break; + default: + type = TypeInfo.getTypeInfo(info.returnDataType); + } + if (allConst) { + return ValueExpression.get(getValue(session)); + } + return this; + } + + @Override + public Value getValue(SessionLocal session) { + Value[] values = getArgumentsValues(session, args); + if (values == null) { + return ValueNull.INSTANCE; + } + Value v0 = getNullOrValue(session, args, values, 0); + Value v1 = getNullOrValue(session, args, values, 1); + Value result; + switch (info.type) { + case ADD_MONTHS: + result = DateTimeFunction.dateadd(session, DateTimeFunction.MONTH, v1.getInt(), v0); + break; + case SYS_GUID: + result = ValueUuid.getNewRandom().convertTo(TypeInfo.TYPE_VARBINARY); + break; + case TO_DATE: + result = ToDateParser.toDate(session, v0.getString(), v1 == null ? null : v1.getString()); + break; + case TO_TIMESTAMP: + result = ToDateParser.toTimestamp(session, v0.getString(), v1 == null ? null : v1.getString()); + break; + case TO_TIMESTAMP_TZ: + result = ToDateParser.toTimestampTz(session, v0.getString(), v1 == null ? null : v1.getString()); + break; + default: + throw DbException.getInternalError("type=" + info.type); + } + return result; + } + +} diff --git a/h2/src/main/org/h2/mode/FunctionsPostgreSQL.java b/h2/src/main/org/h2/mode/FunctionsPostgreSQL.java new file mode 100644 index 0000000..ad2be4d --- /dev/null +++ b/h2/src/main/org/h2/mode/FunctionsPostgreSQL.java @@ -0,0 +1,377 @@ +/* + * 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 java.util.HashMap; +import java.util.StringJoiner; + +import org.h2.api.ErrorCode; +import org.h2.command.Parser; +import org.h2.engine.Constants; +import org.h2.engine.RightOwner; +import org.h2.engine.SessionLocal; +import org.h2.engine.User; +import org.h2.expression.Expression; +import org.h2.expression.ValueExpression; +import org.h2.expression.function.CurrentGeneralValueSpecification; +import org.h2.index.Index; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.server.pg.PgServer; +import org.h2.table.Column; +import org.h2.table.Table; +import org.h2.util.StringUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueArray; +import org.h2.value.ValueBigint; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueInteger; +import org.h2.value.ValueNull; +import org.h2.value.ValueVarchar; + +/** + * Functions for {@link org.h2.engine.Mode.ModeEnum#PostgreSQL} compatibility + * mode. + */ +public final class FunctionsPostgreSQL extends ModeFunction { + + private static final int CURRENT_DATABASE = 3001; + + private static final int CURRTID2 = CURRENT_DATABASE + 1; + + private static final int FORMAT_TYPE = CURRTID2 + 1; + + private static final int HAS_DATABASE_PRIVILEGE = FORMAT_TYPE + 1; + + private static final int HAS_SCHEMA_PRIVILEGE = HAS_DATABASE_PRIVILEGE + 1; + + private static final int HAS_TABLE_PRIVILEGE = HAS_SCHEMA_PRIVILEGE + 1; + + private static final int LASTVAL = HAS_TABLE_PRIVILEGE + 1; + + private static final int VERSION = LASTVAL + 1; + + private static final int OBJ_DESCRIPTION = VERSION + 1; + + private static final int PG_ENCODING_TO_CHAR = OBJ_DESCRIPTION + 1; + + private static final int PG_GET_EXPR = PG_ENCODING_TO_CHAR + 1; + + private static final int PG_GET_INDEXDEF = PG_GET_EXPR + 1; + + private static final int PG_GET_USERBYID = PG_GET_INDEXDEF + 1; + + private static final int PG_POSTMASTER_START_TIME = PG_GET_USERBYID + 1; + + private static final int PG_RELATION_SIZE = PG_POSTMASTER_START_TIME + 1; + + private static final int PG_TABLE_IS_VISIBLE = PG_RELATION_SIZE + 1; + + private static final int SET_CONFIG = PG_TABLE_IS_VISIBLE + 1; + + private static final int ARRAY_TO_STRING = SET_CONFIG + 1; + + private static final int PG_STAT_GET_NUMSCANS = ARRAY_TO_STRING + 1; + + private static final int TO_DATE = PG_STAT_GET_NUMSCANS + 1; + + private static final int TO_TIMESTAMP = TO_DATE + 1; + + private static final HashMap FUNCTIONS = new HashMap<>(32); + + static { + FUNCTIONS.put("CURRENT_DATABASE", + new FunctionInfo("CURRENT_DATABASE", CURRENT_DATABASE, 0, Value.VARCHAR, true, false)); + FUNCTIONS.put("CURRTID2", new FunctionInfo("CURRTID2", CURRTID2, 2, Value.INTEGER, true, false)); + FUNCTIONS.put("FORMAT_TYPE", new FunctionInfo("FORMAT_TYPE", FORMAT_TYPE, 2, Value.VARCHAR, false, true)); + FUNCTIONS.put("HAS_DATABASE_PRIVILEGE", new FunctionInfo("HAS_DATABASE_PRIVILEGE", HAS_DATABASE_PRIVILEGE, + VAR_ARGS, Value.BOOLEAN, true, false)); + FUNCTIONS.put("HAS_SCHEMA_PRIVILEGE", + new FunctionInfo("HAS_SCHEMA_PRIVILEGE", HAS_SCHEMA_PRIVILEGE, VAR_ARGS, Value.BOOLEAN, true, false)); + FUNCTIONS.put("HAS_TABLE_PRIVILEGE", + new FunctionInfo("HAS_TABLE_PRIVILEGE", HAS_TABLE_PRIVILEGE, VAR_ARGS, Value.BOOLEAN, true, false)); + FUNCTIONS.put("LASTVAL", new FunctionInfo("LASTVAL", LASTVAL, 0, Value.BIGINT, true, false)); + FUNCTIONS.put("VERSION", new FunctionInfo("VERSION", VERSION, 0, Value.VARCHAR, true, false)); + FUNCTIONS.put("OBJ_DESCRIPTION", + new FunctionInfo("OBJ_DESCRIPTION", OBJ_DESCRIPTION, VAR_ARGS, Value.VARCHAR, true, false)); + FUNCTIONS.put("PG_ENCODING_TO_CHAR", + new FunctionInfo("PG_ENCODING_TO_CHAR", PG_ENCODING_TO_CHAR, 1, Value.VARCHAR, true, true)); + FUNCTIONS.put("PG_GET_EXPR", // + new FunctionInfo("PG_GET_EXPR", PG_GET_EXPR, VAR_ARGS, Value.VARCHAR, true, true)); + FUNCTIONS.put("PG_GET_INDEXDEF", + new FunctionInfo("PG_GET_INDEXDEF", PG_GET_INDEXDEF, VAR_ARGS, Value.VARCHAR, true, false)); + FUNCTIONS.put("PG_GET_USERBYID", + new FunctionInfo("PG_GET_USERBYID", PG_GET_USERBYID, 1, Value.VARCHAR, true, false)); + FUNCTIONS.put("PG_POSTMASTER_START_TIME", // + new FunctionInfo("PG_POSTMASTER_START_TIME", PG_POSTMASTER_START_TIME, 0, Value.TIMESTAMP_TZ, true, + false)); + FUNCTIONS.put("PG_RELATION_SIZE", + new FunctionInfo("PG_RELATION_SIZE", PG_RELATION_SIZE, VAR_ARGS, Value.BIGINT, true, false)); + FUNCTIONS.put("PG_TABLE_IS_VISIBLE", + new FunctionInfo("PG_TABLE_IS_VISIBLE", PG_TABLE_IS_VISIBLE, 1, Value.BOOLEAN, true, false)); + FUNCTIONS.put("SET_CONFIG", new FunctionInfo("SET_CONFIG", SET_CONFIG, 3, Value.VARCHAR, true, false)); + FUNCTIONS.put("ARRAY_TO_STRING", + new FunctionInfo("ARRAY_TO_STRING", ARRAY_TO_STRING, VAR_ARGS, Value.VARCHAR, false, true)); + FUNCTIONS.put("PG_STAT_GET_NUMSCANS", + new FunctionInfo("PG_STAT_GET_NUMSCANS", PG_STAT_GET_NUMSCANS, 1, Value.INTEGER, true, true)); + FUNCTIONS.put("TO_DATE", new FunctionInfo("TO_DATE", TO_DATE, 2, Value.DATE, true, true)); + FUNCTIONS.put("TO_TIMESTAMP", + new FunctionInfo("TO_TIMESTAMP", TO_TIMESTAMP, 2, Value.TIMESTAMP_TZ, true, true)); + + } + + /** + * Returns mode-specific function for a given name, or {@code null}. + * + * @param upperName + * the upper-case name of a function + * @return the function with specified name or {@code null} + */ + public static FunctionsPostgreSQL getFunction(String upperName) { + FunctionInfo info = FUNCTIONS.get(upperName); + if (info != null) { + return new FunctionsPostgreSQL(info); + } + return null; + } + + private FunctionsPostgreSQL(FunctionInfo info) { + super(info); + } + + @Override + protected void checkParameterCount(int len) { + int min, max; + switch (info.type) { + case HAS_DATABASE_PRIVILEGE: + case HAS_SCHEMA_PRIVILEGE: + case HAS_TABLE_PRIVILEGE: + min = 2; + max = 3; + break; + case OBJ_DESCRIPTION: + case PG_RELATION_SIZE: + min = 1; + max = 2; + break; + case PG_GET_INDEXDEF: + if (len != 1 && len != 3) { + throw DbException.get(ErrorCode.INVALID_PARAMETER_COUNT_2, info.name, "1, 3"); + } + return; + case PG_GET_EXPR: + case ARRAY_TO_STRING: + min = 2; + max = 3; + break; + default: + throw DbException.getInternalError("type=" + info.type); + } + if (len < min || len > max) { + throw DbException.get(ErrorCode.INVALID_PARAMETER_COUNT_2, info.name, min + ".." + max); + } + } + + @Override + public Expression optimize(SessionLocal session) { + switch (info.type) { + case CURRENT_DATABASE: + return new CurrentGeneralValueSpecification(CurrentGeneralValueSpecification.CURRENT_CATALOG) + .optimize(session); + default: + boolean allConst = optimizeArguments(session); + type = TypeInfo.getTypeInfo(info.returnDataType); + if (allConst) { + return ValueExpression.get(getValue(session)); + } + } + return this; + } + + @Override + public Value getValue(SessionLocal session) { + Value[] values = getArgumentsValues(session, args); + if (values == null) { + return ValueNull.INSTANCE; + } + Value v0 = getNullOrValue(session, args, values, 0); + Value v1 = getNullOrValue(session, args, values, 1); + Value v2 = getNullOrValue(session, args, values, 2); + Value result; + switch (info.type) { + case CURRTID2: + // Not implemented + result = ValueInteger.get(1); + break; + case FORMAT_TYPE: + result = v0 != ValueNull.INSTANCE ? ValueVarchar.get(PgServer.formatType(v0.getInt())) // + : ValueNull.INSTANCE; + break; + case HAS_DATABASE_PRIVILEGE: + case HAS_SCHEMA_PRIVILEGE: + case HAS_TABLE_PRIVILEGE: + case PG_TABLE_IS_VISIBLE: + // Not implemented + result = ValueBoolean.TRUE; + break; + case LASTVAL: + result = session.getLastIdentity(); + if (result == ValueNull.INSTANCE) { + throw DbException.get(ErrorCode.CURRENT_SEQUENCE_VALUE_IS_NOT_DEFINED_IN_SESSION_1, "lastval()"); + } + result = result.convertToBigint(null); + break; + case VERSION: + result = ValueVarchar + .get("PostgreSQL " + Constants.PG_VERSION + " server protocol using H2 " + Constants.FULL_VERSION); + break; + case OBJ_DESCRIPTION: + // Not implemented + result = ValueNull.INSTANCE; + break; + case PG_ENCODING_TO_CHAR: + result = ValueVarchar.get(encodingToChar(v0.getInt())); + break; + case PG_GET_EXPR: + // Not implemented + result = ValueNull.INSTANCE; + break; + case PG_GET_INDEXDEF: + result = getIndexdef(session, v0.getInt(), v1, v2); + break; + case PG_GET_USERBYID: + result = ValueVarchar.get(getUserbyid(session, v0.getInt())); + break; + case PG_POSTMASTER_START_TIME: + result = session.getDatabase().getSystemSession().getSessionStart(); + break; + case PG_RELATION_SIZE: + // Optional second argument is ignored + result = relationSize(session, v0); + break; + case SET_CONFIG: + // Not implemented + result = v1.convertTo(Value.VARCHAR); + break; + case ARRAY_TO_STRING: + if (v0 == ValueNull.INSTANCE || v1 == ValueNull.INSTANCE) { + result = ValueNull.INSTANCE; + break; + } + StringJoiner joiner = new StringJoiner(v1.getString()); + if (v0.getValueType() != Value.ARRAY) { + throw DbException.getInvalidValueException("ARRAY_TO_STRING array", v0); + } + String nullString = null; + if (v2 != null) { + nullString = v2.getString(); + } + for (Value v : ((ValueArray) v0).getList()) { + if (v != ValueNull.INSTANCE) { + joiner.add(v.getString()); + } else if (nullString != null) { + joiner.add(nullString); + } + } + result = ValueVarchar.get(joiner.toString()); + break; + case PG_STAT_GET_NUMSCANS: + // Not implemented + result = ValueInteger.get(0); + break; + case TO_DATE: + result = ToDateParser.toDate(session, v0.getString(), v1.getString()).convertToDate(session); + break; + case TO_TIMESTAMP: + result = ToDateParser.toTimestampTz(session, v0.getString(), v1.getString()); + break; + default: + throw DbException.getInternalError("type=" + info.type); + } + return result; + } + + private static String encodingToChar(int code) { + switch (code) { + case 0: + return "SQL_ASCII"; + case 6: + return "UTF8"; + case 8: + return "LATIN1"; + default: + // This function returns empty string for unknown encodings + return code < 40 ? "UTF8" : ""; + } + } + + private static Value getIndexdef(SessionLocal session, int indexId, Value ordinalPosition, Value pretty) { + for (Schema schema : session.getDatabase().getAllSchemasNoMeta()) { + for (Index index : schema.getAllIndexes()) { + if (index.getId() == indexId) { + if (!index.getTable().isHidden()) { + int ordinal; + if (ordinalPosition == null || (ordinal = ordinalPosition.getInt()) == 0) { + return ValueVarchar.get(index.getCreateSQL()); + } + Column[] columns; + if (ordinal >= 1 && ordinal <= (columns = index.getColumns()).length) { + return ValueVarchar.get(columns[ordinal - 1].getName()); + } + } + break; + } + } + } + return ValueNull.INSTANCE; + } + + private static String getUserbyid(SessionLocal session, int uid) { + User u = session.getUser(); + String name; + search: { + if (u.getId() == uid) { + name = u.getName(); + break search; + } else { + if (u.isAdmin()) { + for (RightOwner rightOwner : session.getDatabase().getAllUsersAndRoles()) { + if (rightOwner.getId() == uid) { + name = rightOwner.getName(); + break search; + } + } + } + } + return "unknown (OID=" + uid + ')'; + } + if (session.getDatabase().getSettings().databaseToLower) { + name = StringUtils.toLowerEnglish(name); + } + return name; + } + + private static Value relationSize(SessionLocal session, Value tableOidOrName) { + Table t; + if (tableOidOrName.getValueType() == Value.INTEGER) { + int tid = tableOidOrName.getInt(); + for (Schema schema : session.getDatabase().getAllSchemasNoMeta()) { + for (Table table : schema.getAllTablesAndViews(session)) { + if (tid == table.getId()) { + t = table; + break; + } + } + } + return ValueNull.INSTANCE; + } else { + t = new Parser(session).parseTableName(tableOidOrName.getString()); + } + return ValueBigint.get(t.getDiskSpaceUsed()); + } + +} diff --git a/h2/src/main/org/h2/mode/ModeFunction.java b/h2/src/main/org/h2/mode/ModeFunction.java new file mode 100644 index 0000000..ba75a40 --- /dev/null +++ b/h2/src/main/org/h2/mode/ModeFunction.java @@ -0,0 +1,222 @@ +/* + * 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.api.ErrorCode; +import org.h2.engine.Database; +import org.h2.engine.Mode.ModeEnum; +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; +import org.h2.value.ValueNull; + +/** + * Base class for mode-specific functions. + */ +public abstract class ModeFunction extends FunctionN { + + /** + * Constant for variable number of arguments. + */ + protected static final int VAR_ARGS = -1; + + /** + * The information about this function. + */ + protected final FunctionInfo info; + + /** + * Get an instance of the given function 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 + * @return the function object or null + */ + public static ModeFunction getFunction(Database database, String name) { + ModeEnum modeEnum = database.getMode().getEnum(); + if (modeEnum != ModeEnum.REGULAR) { + return getCompatibilityModeFunction(name, modeEnum); + } + return null; + } + + private static ModeFunction getCompatibilityModeFunction(String name, ModeEnum modeEnum) { + switch (modeEnum) { + case LEGACY: + return FunctionsLegacy.getFunction(name); + case DB2: + case Derby: + return FunctionsDB2Derby.getFunction(name); + case MSSQLServer: + return FunctionsMSSQLServer.getFunction(name); + case MySQL: + return FunctionsMySQL.getFunction(name); + case Oracle: + return FunctionsOracle.getFunction(name); + case PostgreSQL: + return FunctionsPostgreSQL.getFunction(name); + default: + return null; + } + } + + /** + * 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. + * + * @param info function information + */ + ModeFunction(FunctionInfo info) { + super(new Expression[info.parameterCount != VAR_ARGS ? info.parameterCount : 4]); + this.info = info; + } + + /** + * Get value transformed by expression, or null if i is out of range or + * the input value is null. + * + * @param session database session + * @param args expressions + * @param values array of input values + * @param i index of value of transform + * @return value or null + */ + static Value getNullOrValue(SessionLocal session, Expression[] args, + Value[] values, int i) { + if (i >= args.length) { + return null; + } + Value v = values[i]; + if (v == null) { + Expression e = args[i]; + if (e == null) { + return null; + } + v = values[i] = e.getValue(session); + } + return v; + } + + /** + * Gets values of arguments and checks them for NULL values if function + * returns NULL on NULL argument. + * + * @param session + * the session + * @param args + * the arguments + * @return the values, or {@code null} if function should return NULL due to + * NULL argument + */ + final Value[] getArgumentsValues(SessionLocal session, Expression[] args) { + Value[] values = new Value[args.length]; + if (info.nullIfParameterIsNull) { + for (int i = 0, l = args.length; i < l; i++) { + Value v = args[i].getValue(session); + if (v == ValueNull.INSTANCE) { + return null; + } + values[i] = v; + } + } + return values; + } + + /** + * Check if the parameter count is correct. + * + * @param len the number of parameters set + * @throws DbException if the parameter count is incorrect + */ + void checkParameterCount(int len) { + throw DbException.getInternalError("type=" + info.type); + } + + @Override + public void doneWithParameters() { + int count = info.parameterCount; + if (count == VAR_ARGS) { + checkParameterCount(argsCount); + super.doneWithParameters(); + } else if (count != argsCount) { + throw DbException.get(ErrorCode.INVALID_PARAMETER_COUNT_2, info.name, Integer.toString(argsCount)); + } + } + + /** + * Optimizes arguments. + * + * @param session + * the session + * @return whether all arguments are constants and function is deterministic + */ + final boolean optimizeArguments(SessionLocal session) { + return optimizeArguments(session, info.deterministic); + } + + @Override + public String getName() { + return info.name; + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + if (!super.isEverything(visitor)) { + return false; + } + switch (visitor.getType()) { + case ExpressionVisitor.DETERMINISTIC: + case ExpressionVisitor.QUERY_COMPARABLE: + case ExpressionVisitor.READONLY: + return info.deterministic; + default: + return true; + } + } + +} diff --git a/h2/src/main/org/h2/mode/OnDuplicateKeyValues.java b/h2/src/main/org/h2/mode/OnDuplicateKeyValues.java new file mode 100644 index 0000000..44c2456 --- /dev/null +++ b/h2/src/main/org/h2/mode/OnDuplicateKeyValues.java @@ -0,0 +1,64 @@ +/* + * 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.command.dml.Update; +import org.h2.engine.SessionLocal; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.Operation0; +import org.h2.message.DbException; +import org.h2.table.Column; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * VALUES(column) function for ON DUPLICATE KEY UPDATE clause. + */ +public final class OnDuplicateKeyValues extends Operation0 { + + private final Column column; + + private final Update update; + + public OnDuplicateKeyValues(Column column, Update update) { + this.column = column; + this.update = update; + } + + @Override + public Value getValue(SessionLocal session) { + Value v = update.getOnDuplicateKeyInsert().getOnDuplicateKeyValue(column.getColumnId()); + if (v == null) { + throw DbException.getUnsupportedException(getTraceSQL()); + } + return v; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return column.getSQL(builder.append("VALUES("), sqlFlags).append(')'); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + switch (visitor.getType()) { + case ExpressionVisitor.DETERMINISTIC: + return false; + } + return true; + } + + @Override + public TypeInfo getType() { + return column.getType(); + } + + @Override + public int getCost() { + return 1; + } + +} diff --git a/h2/src/main/org/h2/mode/PgCatalogSchema.java b/h2/src/main/org/h2/mode/PgCatalogSchema.java new file mode 100644 index 0000000..e88f20a --- /dev/null +++ b/h2/src/main/org/h2/mode/PgCatalogSchema.java @@ -0,0 +1,59 @@ +/* + * 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 java.util.HashMap; +import java.util.Map; + +import org.h2.engine.Constants; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.engine.User; +import org.h2.schema.MetaSchema; +import org.h2.table.Table; + +/** + * {@code pg_catalog} schema. + */ +public final class PgCatalogSchema extends MetaSchema { + + private volatile HashMap tables; + + /** + * Creates new instance of {@code pg_catalog} schema. + * + * @param database + * the database + * @param owner + * the owner of the schema (system user) + */ + public PgCatalogSchema(Database database, User owner) { + super(database, Constants.PG_CATALOG_SCHEMA_ID, database.sysIdentifier(Constants.SCHEMA_PG_CATALOG), owner); + } + + @Override + protected Map getMap(SessionLocal session) { + HashMap map = tables; + if (map == null) { + map = fillMap(); + } + return map; + } + + private synchronized HashMap fillMap() { + HashMap map = tables; + if (map == null) { + map = database.newStringMap(); + for (int type = 0; type < PgCatalogTable.META_TABLE_TYPE_COUNT; type++) { + PgCatalogTable table = new PgCatalogTable(this, Constants.PG_CATALOG_SCHEMA_ID - type, type); + map.put(table.getName(), table); + } + tables = map; + } + return map; + } + +} diff --git a/h2/src/main/org/h2/mode/PgCatalogTable.java b/h2/src/main/org/h2/mode/PgCatalogTable.java new file mode 100644 index 0000000..ac7afd0 --- /dev/null +++ b/h2/src/main/org/h2/mode/PgCatalogTable.java @@ -0,0 +1,720 @@ +/* + * 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 java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +import org.h2.constraint.Constraint; +import org.h2.engine.Constants; +import org.h2.engine.RightOwner; +import org.h2.engine.SessionLocal; +import org.h2.engine.User; +import org.h2.index.Index; +import org.h2.message.DbException; +import org.h2.result.Row; +import org.h2.result.SearchRow; +import org.h2.schema.Schema; +import org.h2.schema.TriggerObject; +import org.h2.server.pg.PgServer; +import org.h2.table.Column; +import org.h2.table.MetaTable; +import org.h2.table.Table; +import org.h2.util.StringUtils; +import org.h2.util.Utils; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueArray; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueDouble; +import org.h2.value.ValueInteger; +import org.h2.value.ValueSmallint; + +/** + * This class is responsible to build the pg_catalog tables. + */ +public final class PgCatalogTable extends MetaTable { + + private static final int PG_AM = 0; + + private static final int PG_ATTRDEF = PG_AM + 1; + + private static final int PG_ATTRIBUTE = PG_ATTRDEF + 1; + + private static final int PG_AUTHID = PG_ATTRIBUTE + 1; + + private static final int PG_CLASS = PG_AUTHID + 1; + + private static final int PG_CONSTRAINT = PG_CLASS + 1; + + private static final int PG_DATABASE = PG_CONSTRAINT + 1; + + private static final int PG_DESCRIPTION = PG_DATABASE + 1; + + private static final int PG_GROUP = PG_DESCRIPTION + 1; + + private static final int PG_INDEX = PG_GROUP + 1; + + private static final int PG_INHERITS = PG_INDEX + 1; + + private static final int PG_NAMESPACE = PG_INHERITS + 1; + + private static final int PG_PROC = PG_NAMESPACE + 1; + + private static final int PG_ROLES = PG_PROC + 1; + + private static final int PG_SETTINGS = PG_ROLES + 1; + + private static final int PG_TABLESPACE = PG_SETTINGS + 1; + + private static final int PG_TRIGGER = PG_TABLESPACE + 1; + + private static final int PG_TYPE = PG_TRIGGER + 1; + + private static final int PG_USER = PG_TYPE + 1; + + /** + * The number of meta table types. Supported meta table types are + * {@code 0..META_TABLE_TYPE_COUNT - 1}. + */ + public static final int META_TABLE_TYPE_COUNT = PG_USER + 1; + + private static final Object[][] PG_EXTRA_TYPES = { + { 18, "char", 1, 0 }, + { 19, "name", 64, 18 }, + { 22, "int2vector", -1, 21 }, + { 24, "regproc", 4, 0 }, + { PgServer.PG_TYPE_INT2_ARRAY, "_int2", -1, PgServer.PG_TYPE_INT2 }, + { PgServer.PG_TYPE_INT4_ARRAY, "_int4", -1, PgServer.PG_TYPE_INT4 }, + { PgServer.PG_TYPE_VARCHAR_ARRAY, "_varchar", -1, PgServer.PG_TYPE_VARCHAR }, + { 2205, "regclass", 4, 0 }, + }; + + /** + * Create a new metadata table. + * + * @param schema + * the schema + * @param id + * the object id + * @param type + * the meta table type + */ + public PgCatalogTable(Schema schema, int id, int type) { + super(schema, id, type); + Column[] cols; + switch (type) { + case PG_AM: + setMetaTableName("PG_AM"); + cols = new Column[] { // + column("OID", TypeInfo.TYPE_INTEGER), // + column("AMNAME", TypeInfo.TYPE_VARCHAR), // + }; + break; + case PG_ATTRDEF: + setMetaTableName("PG_ATTRDEF"); + cols = new Column[] { // + column("OID", TypeInfo.TYPE_INTEGER), // + column("ADSRC", TypeInfo.TYPE_INTEGER), // + column("ADRELID", TypeInfo.TYPE_INTEGER), // + column("ADNUM", TypeInfo.TYPE_INTEGER), // + column("ADBIN", TypeInfo.TYPE_VARCHAR), // pg_node_tree + }; + break; + case PG_ATTRIBUTE: + setMetaTableName("PG_ATTRIBUTE"); + cols = new Column[] { // + column("OID", TypeInfo.TYPE_INTEGER), // + column("ATTRELID", TypeInfo.TYPE_INTEGER), // + column("ATTNAME", TypeInfo.TYPE_VARCHAR), // + column("ATTTYPID", TypeInfo.TYPE_INTEGER), // + column("ATTLEN", TypeInfo.TYPE_INTEGER), // + column("ATTNUM", TypeInfo.TYPE_INTEGER), // + column("ATTTYPMOD", TypeInfo.TYPE_INTEGER), // + column("ATTNOTNULL", TypeInfo.TYPE_BOOLEAN), // + column("ATTISDROPPED", TypeInfo.TYPE_BOOLEAN), // + column("ATTHASDEF", TypeInfo.TYPE_BOOLEAN), // + }; + break; + case PG_AUTHID: + setMetaTableName("PG_AUTHID"); + cols = new Column[] { // + column("OID", TypeInfo.TYPE_INTEGER), // + column("ROLNAME", TypeInfo.TYPE_VARCHAR), // + column("ROLSUPER", TypeInfo.TYPE_BOOLEAN), // + column("ROLINHERIT", TypeInfo.TYPE_BOOLEAN), // + column("ROLCREATEROLE", TypeInfo.TYPE_BOOLEAN), // + column("ROLCREATEDB", TypeInfo.TYPE_BOOLEAN), // + column("ROLCATUPDATE", TypeInfo.TYPE_BOOLEAN), // + column("ROLCANLOGIN", TypeInfo.TYPE_BOOLEAN), // + column("ROLCONNLIMIT", TypeInfo.TYPE_BOOLEAN), // + column("ROLPASSWORD", TypeInfo.TYPE_BOOLEAN), // + column("ROLVALIDUNTIL", TypeInfo.TYPE_TIMESTAMP_TZ), // + column("ROLCONFIG", TypeInfo.getTypeInfo(Value.ARRAY, -1L, 0, TypeInfo.TYPE_VARCHAR)), // + }; + break; + case PG_CLASS: + setMetaTableName("PG_CLASS"); + cols = new Column[] { // + column("OID", TypeInfo.TYPE_INTEGER), // + column("RELNAME", TypeInfo.TYPE_VARCHAR), // + column("RELNAMESPACE", TypeInfo.TYPE_INTEGER), // + column("RELKIND", TypeInfo.TYPE_CHAR), // + column("RELAM", TypeInfo.TYPE_INTEGER), // + column("RELTUPLES", TypeInfo.TYPE_DOUBLE), // + column("RELTABLESPACE", TypeInfo.TYPE_INTEGER), // + column("RELPAGES", TypeInfo.TYPE_INTEGER), // + column("RELHASINDEX", TypeInfo.TYPE_BOOLEAN), // + column("RELHASRULES", TypeInfo.TYPE_BOOLEAN), // + column("RELHASOIDS", TypeInfo.TYPE_BOOLEAN), // + column("RELCHECKS", TypeInfo.TYPE_SMALLINT), // + column("RELTRIGGERS", TypeInfo.TYPE_INTEGER), // + }; + break; + case PG_CONSTRAINT: + setMetaTableName("PG_CONSTRAINT"); + cols = new Column[] { // + column("OID", TypeInfo.TYPE_INTEGER), // + column("CONNAME", TypeInfo.TYPE_VARCHAR), // + column("CONTYPE", TypeInfo.TYPE_VARCHAR), // + column("CONRELID", TypeInfo.TYPE_INTEGER), // + column("CONFRELID", TypeInfo.TYPE_INTEGER), // + column("CONKEY", TypeInfo.getTypeInfo(Value.ARRAY, -1L, 0, TypeInfo.TYPE_SMALLINT)), // + }; + break; + case PG_DATABASE: + setMetaTableName("PG_DATABASE"); + cols = new Column[] { // + column("OID", TypeInfo.TYPE_INTEGER), // + column("DATNAME", TypeInfo.TYPE_VARCHAR), // + column("ENCODING", TypeInfo.TYPE_INTEGER), // + column("DATLASTSYSOID", TypeInfo.TYPE_INTEGER), // + column("DATALLOWCONN", TypeInfo.TYPE_BOOLEAN), // + column("DATCONFIG", TypeInfo.getTypeInfo(Value.ARRAY, -1L, 0, TypeInfo.TYPE_VARCHAR)), // + column("DATACL", TypeInfo.getTypeInfo(Value.ARRAY, -1L, 0, TypeInfo.TYPE_VARCHAR)), // aclitem[] + column("DATDBA", TypeInfo.TYPE_INTEGER), // + column("DATTABLESPACE", TypeInfo.TYPE_INTEGER), // + }; + break; + case PG_DESCRIPTION: + setMetaTableName("PG_DESCRIPTION"); + cols = new Column[] { // + column("OBJOID", TypeInfo.TYPE_INTEGER), // + column("OBJSUBID", TypeInfo.TYPE_INTEGER), // + column("CLASSOID", TypeInfo.TYPE_INTEGER), // + column("DESCRIPTION", TypeInfo.TYPE_VARCHAR), // + }; + break; + case PG_GROUP: + setMetaTableName("PG_GROUP"); + cols = new Column[] { // + column("OID", TypeInfo.TYPE_INTEGER), // + column("GRONAME", TypeInfo.TYPE_VARCHAR), // + }; + break; + case PG_INDEX: + setMetaTableName("PG_INDEX"); + cols = new Column[] { // + column("OID", TypeInfo.TYPE_INTEGER), // + column("INDEXRELID", TypeInfo.TYPE_INTEGER), // + column("INDRELID", TypeInfo.TYPE_INTEGER), // + column("INDISCLUSTERED", TypeInfo.TYPE_BOOLEAN), // + column("INDISUNIQUE", TypeInfo.TYPE_BOOLEAN), // + column("INDISPRIMARY", TypeInfo.TYPE_BOOLEAN), // + column("INDEXPRS", TypeInfo.TYPE_VARCHAR), // + column("INDKEY", TypeInfo.getTypeInfo(Value.ARRAY, -1L, 0, TypeInfo.TYPE_INTEGER)), // + column("INDPRED", TypeInfo.TYPE_VARCHAR), // pg_node_tree + }; + break; + case PG_INHERITS: + setMetaTableName("PG_INHERITS"); + cols = new Column[] { // + column("INHRELID", TypeInfo.TYPE_INTEGER), // + column("INHPARENT", TypeInfo.TYPE_INTEGER), // + column("INHSEQNO", TypeInfo.TYPE_INTEGER), // + }; + break; + case PG_NAMESPACE: + setMetaTableName("PG_NAMESPACE"); + cols = new Column[] { // + column("ID", TypeInfo.TYPE_INTEGER), // + column("NSPNAME", TypeInfo.TYPE_VARCHAR), // + }; + break; + case PG_PROC: + setMetaTableName("PG_PROC"); + cols = new Column[] { // + column("OID", TypeInfo.TYPE_INTEGER), // + column("PRONAME", TypeInfo.TYPE_VARCHAR), // + column("PRORETTYPE", TypeInfo.TYPE_INTEGER), // + column("PROARGTYPES", TypeInfo.getTypeInfo(Value.ARRAY, -1L, 0, TypeInfo.TYPE_INTEGER)), // + column("PRONAMESPACE", TypeInfo.TYPE_INTEGER), // + }; + break; + case PG_ROLES: + setMetaTableName("PG_ROLES"); + cols = new Column[] { // + column("OID", TypeInfo.TYPE_INTEGER), // + column("ROLNAME", TypeInfo.TYPE_VARCHAR), // + column("ROLSUPER", TypeInfo.TYPE_CHAR), // + column("ROLCREATEROLE", TypeInfo.TYPE_CHAR), // + column("ROLCREATEDB", TypeInfo.TYPE_CHAR), // + }; + break; + case PG_SETTINGS: + setMetaTableName("PG_SETTINGS"); + cols = new Column[] { // + column("OID", TypeInfo.TYPE_INTEGER), // + column("NAME", TypeInfo.TYPE_VARCHAR), // + column("SETTING", TypeInfo.TYPE_VARCHAR), // + }; + break; + case PG_TABLESPACE: + setMetaTableName("PG_TABLESPACE"); + cols = new Column[] { // + column("OID", TypeInfo.TYPE_INTEGER), // + column("SPCNAME", TypeInfo.TYPE_VARCHAR), // + column("SPCLOCATION", TypeInfo.TYPE_VARCHAR), // + column("SPCOWNER", TypeInfo.TYPE_INTEGER), // + column("SPCACL", TypeInfo.getTypeInfo(Value.ARRAY, -1L, 0, TypeInfo.TYPE_VARCHAR)), // ACLITEM[] + }; + break; + case PG_TRIGGER: + setMetaTableName("PG_TRIGGER"); + cols = new Column[] { // + column("OID", TypeInfo.TYPE_INTEGER), // + column("TGCONSTRRELID", TypeInfo.TYPE_INTEGER), // + column("TGFOID", TypeInfo.TYPE_INTEGER), // + column("TGARGS", TypeInfo.TYPE_INTEGER), // + column("TGNARGS", TypeInfo.TYPE_INTEGER), // + column("TGDEFERRABLE", TypeInfo.TYPE_BOOLEAN), // + column("TGINITDEFERRED", TypeInfo.TYPE_BOOLEAN), // + column("TGCONSTRNAME", TypeInfo.TYPE_VARCHAR), // + column("TGRELID", TypeInfo.TYPE_INTEGER), // + }; + break; + case PG_TYPE: + setMetaTableName("PG_TYPE"); + cols = new Column[] { // + column("OID", TypeInfo.TYPE_INTEGER), // + column("TYPNAME", TypeInfo.TYPE_VARCHAR), // + column("TYPNAMESPACE", TypeInfo.TYPE_INTEGER), // + column("TYPLEN", TypeInfo.TYPE_INTEGER), // + column("TYPTYPE", TypeInfo.TYPE_VARCHAR), // + column("TYPDELIM", TypeInfo.TYPE_VARCHAR), // + column("TYPRELID", TypeInfo.TYPE_INTEGER), // + column("TYPELEM", TypeInfo.TYPE_INTEGER), // + column("TYPBASETYPE", TypeInfo.TYPE_INTEGER), // + column("TYPTYPMOD", TypeInfo.TYPE_INTEGER), // + column("TYPNOTNULL", TypeInfo.TYPE_BOOLEAN), // + column("TYPINPUT", TypeInfo.TYPE_VARCHAR), // + }; + break; + case PG_USER: + setMetaTableName("PG_USER"); + cols = new Column[] { // + column("OID", TypeInfo.TYPE_INTEGER), // + column("USENAME", TypeInfo.TYPE_VARCHAR), // + column("USECREATEDB", TypeInfo.TYPE_BOOLEAN), // + column("USESUPER", TypeInfo.TYPE_BOOLEAN), // + }; + break; + default: + throw DbException.getInternalError("type=" + type); + } + setColumns(cols); + indexColumn = -1; + metaIndex = null; + } + + @Override + public ArrayList generateRows(SessionLocal session, SearchRow first, SearchRow last) { + ArrayList rows = Utils.newSmallArrayList(); + String catalog = database.getShortName(); + boolean admin = session.getUser().isAdmin(); + switch (type) { + case PG_AM: { + String[] am = { "btree", "hash" }; + for (int i = 0, l = am.length; i < l; i++) { + add(session, rows, + // OID + ValueInteger.get(i), + // AMNAME + am[i]); + } + break; + } + case PG_ATTRDEF: + break; + case PG_ATTRIBUTE: + for (Schema schema : database.getAllSchemas()) { + for (Table table : schema.getAllTablesAndViews(session)) { + if (!hideTable(table, session)) { + pgAttribute(session, rows, table); + } + } + } + for (Table table: session.getLocalTempTables()) { + if (!hideTable(table, session)) { + pgAttribute(session, rows, table); + } + } + break; + case PG_AUTHID: + break; + case PG_CLASS: + for (Schema schema : database.getAllSchemas()) { + for (Table table : schema.getAllTablesAndViews(session)) { + if (!hideTable(table, session)) { + pgClass(session, rows, table); + } + } + } + for (Table table: session.getLocalTempTables()) { + if (!hideTable(table, session)) { + pgClass(session, rows, table); + } + } + break; + case PG_CONSTRAINT: + pgConstraint(session, rows); + break; + case PG_DATABASE: { + int uid = Integer.MAX_VALUE; + for (RightOwner rightOwner : database.getAllUsersAndRoles()) { + if (rightOwner instanceof User && ((User) rightOwner).isAdmin()) { + int id = rightOwner.getId(); + if (id < uid) { + uid = id; + } + } + } + add(session, rows, + // OID + ValueInteger.get(100_001), + // DATNAME + catalog, + // ENCODING INT, + ValueInteger.get(6), // UTF-8 + // DATLASTSYSOID INT, + ValueInteger.get(100_000), + // DATALLOWCONN BOOLEAN, + ValueBoolean.TRUE, + // DATCONFIG ARRAY, -- TEXT[] + null, + // DATACL ARRAY, -- ACLITEM[] + null, + // DATDBA INT, + ValueInteger.get(uid), + // DATTABLESPACE INT + ValueInteger.get(0)); + break; + } + case PG_DESCRIPTION: + add(session, rows, + // OBJOID + ValueInteger.get(0), + // OBJSUBID + ValueInteger.get(0), + // CLASSOID + ValueInteger.get(-1), + // DESCRIPTION + catalog); + break; + case PG_GROUP: + // The next one returns no rows due to MS Access problem opening + // tables with primary key + case PG_INDEX: + case PG_INHERITS: + break; + case PG_NAMESPACE: + for (Schema schema : database.getAllSchemas()) { + add(session, rows, + // ID + ValueInteger.get(schema.getId()), + // NSPNAME + schema.getName()); + } + break; + case PG_PROC: + break; + case PG_ROLES: + for (RightOwner rightOwner : database.getAllUsersAndRoles()) { + if (admin || session.getUser() == rightOwner) { + String r = rightOwner instanceof User && ((User) rightOwner).isAdmin() ? "t" : "f"; + add(session, rows, + // OID + ValueInteger.get(rightOwner.getId()), + // ROLNAME + identifier(rightOwner.getName()), + // ROLSUPER + r, + // ROLCREATEROLE + r, + // ROLCREATEDB; + r); + } + } + break; + case PG_SETTINGS: { + String[][] settings = { { "autovacuum", "on" }, { "stats_start_collector", "on" }, + { "stats_row_level", "on" } }; + for (int i = 0, l = settings.length; i < l; i++) { + String[] setting = settings[i]; + add(session, rows, + // OID + ValueInteger.get(i), + // NAME + setting[0], + // SETTING + setting[1]); + } + break; + } + case PG_TABLESPACE: + add(session, rows, + // OID INTEGER + ValueInteger.get(0), + // SPCNAME + "main", + // SPCLOCATION + "?", + // SPCOWNER + ValueInteger.get(0), + // SPCACL + null); + break; + case PG_TRIGGER: + break; + case PG_TYPE: { + HashSet types = new HashSet<>(); + for (int i = 1, l = Value.TYPE_COUNT; i < l; i++) { + DataType t = DataType.getDataType(i); + if (t.type == Value.ARRAY) { + continue; + } + int pgType = PgServer.convertType(TypeInfo.getTypeInfo(t.type)); + if (pgType == PgServer.PG_TYPE_UNKNOWN || !types.add(pgType)) { + continue; + } + add(session, rows, + // OID + ValueInteger.get(pgType), + // TYPNAME + Value.getTypeName(t.type), + // TYPNAMESPACE + ValueInteger.get(Constants.PG_CATALOG_SCHEMA_ID), + // TYPLEN + ValueInteger.get(-1), + // TYPTYPE + "b", + // TYPDELIM + ",", + // TYPRELID + ValueInteger.get(0), + // TYPELEM + ValueInteger.get(0), + // TYPBASETYPE + ValueInteger.get(0), + // TYPTYPMOD + ValueInteger.get(-1), + // TYPNOTNULL + ValueBoolean.FALSE, + // TYPINPUT + null); + } + for (Object[] pgType : PG_EXTRA_TYPES) { + add(session, rows, + // OID + ValueInteger.get((int) pgType[0]), + // TYPNAME + pgType[1], + // TYPNAMESPACE + ValueInteger.get(Constants.PG_CATALOG_SCHEMA_ID), + // TYPLEN + ValueInteger.get((int) pgType[2]), + // TYPTYPE + "b", + // TYPDELIM + ",", + // TYPRELID + ValueInteger.get(0), + // TYPELEM + ValueInteger.get((int) pgType[3]), + // TYPBASETYPE + ValueInteger.get(0), + // TYPTYPMOD + ValueInteger.get(-1), + // TYPNOTNULL + ValueBoolean.FALSE, + // TYPINPUT + null); + } + break; + } + case PG_USER: + for (RightOwner rightOwner : database.getAllUsersAndRoles()) { + if (rightOwner instanceof User) { + User u = (User) rightOwner; + if (admin || session.getUser() == u) { + ValueBoolean r = ValueBoolean.get(u.isAdmin()); + add(session, rows, + // OID + ValueInteger.get(u.getId()), + // USENAME + identifier(u.getName()), + // USECREATEDB + r, + // USESUPER; + r); + } + } + } + break; + default: + throw DbException.getInternalError("type=" + type); + } + return rows; + + } + + private void pgAttribute(SessionLocal session, ArrayList rows, Table table) { + Column[] cols = table.getColumns(); + int tableId = table.getId(); + for (int i = 0; i < cols.length;) { + Column column = cols[i++]; + addAttribute(session, rows, tableId * 10_000 + i, tableId, column, i); + } + for (Index index : table.getIndexes()) { + if (index.getCreateSQL() == null) { + continue; + } + cols = index.getColumns(); + 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, column, i); + } + } + } + + private void pgClass(SessionLocal session, ArrayList rows, Table table) { + ArrayList triggers = table.getTriggers(); + addClass(session, rows, table.getId(), table.getName(), table.getSchema().getId(), + table.isView() ? "v" : "r", false, triggers != null ? triggers.size() : 0); + ArrayList indexes = table.getIndexes(); + if (indexes != null) { + for (Index index : indexes) { + if (index.getCreateSQL() == null) { + continue; + } + addClass(session, rows, index.getId(), index.getName(), index.getSchema().getId(), "i", true, + 0); + } + } + } + + private void pgConstraint(SessionLocal session, ArrayList rows) { + for (Schema schema : database.getAllSchemasNoMeta()) { + for (Constraint constraint : schema.getAllConstraints()) { + Constraint.Type constraintType = constraint.getConstraintType(); + if (constraintType == Constraint.Type.DOMAIN) { + continue; + } + Table table = constraint.getTable(); + if (hideTable(table, session)) { + continue; + } + List conkey = new ArrayList<>(); + for (Column column : constraint.getReferencedColumns(table)) { + conkey.add(ValueSmallint.get((short) (column.getColumnId() + 1))); + } + Table refTable = constraint.getRefTable(); + add(session, + rows, + // OID + ValueInteger.get(constraint.getId()), + // CONNAME + constraint.getName(), + // CONTYPE + StringUtils.toLowerEnglish(constraintType.getSqlName().substring(0, 1)), + // CONRELID + ValueInteger.get(table.getId()), + // CONFRELID + ValueInteger.get(refTable != null && refTable != table + && !hideTable(refTable, session) ? table.getId() : 0), + // CONKEY + ValueArray.get(TypeInfo.TYPE_SMALLINT, conkey.toArray(Value.EMPTY_VALUES), null) + ); + } + } + } + + private void addAttribute(SessionLocal session, ArrayList rows, int id, int relId, Column column, + int ordinal) { + long precision = column.getType().getPrecision(); + add(session, rows, + // OID + ValueInteger.get(id), + // ATTRELID + ValueInteger.get(relId), + // ATTNAME + column.getName(), + // ATTTYPID + ValueInteger.get(PgServer.convertType(column.getType())), + // ATTLEN + ValueInteger.get(precision > 255 ? -1 : (int) precision), + // ATTNUM + ValueInteger.get(ordinal), + // ATTTYPMOD + ValueInteger.get(-1), + // ATTNOTNULL + ValueBoolean.get(!column.isNullable()), + // ATTISDROPPED + ValueBoolean.FALSE, + // ATTHASDEF + ValueBoolean.FALSE); + } + + private void addClass(SessionLocal session, ArrayList rows, int id, String name, int schema, String kind, + boolean index, int triggers) { + add(session, rows, + // OID + ValueInteger.get(id), + // RELNAME + name, + // RELNAMESPACE + ValueInteger.get(schema), + // RELKIND + kind, + // RELAM + ValueInteger.get(0), + // RELTUPLES + ValueDouble.get(0d), + // RELTABLESPACE + ValueInteger.get(0), + // RELPAGES + ValueInteger.get(0), + // RELHASINDEX + ValueBoolean.get(index), + // RELHASRULES + ValueBoolean.FALSE, + // RELHASOIDS + ValueBoolean.FALSE, + // RELCHECKS + ValueSmallint.get((short) 0), + // RELTRIGGERS + ValueInteger.get(triggers)); + } + + @Override + public long getMaxDataModificationId() { + return database.getModificationDataId(); + } + +} diff --git a/h2/src/main/org/h2/mode/Regclass.java b/h2/src/main/org/h2/mode/Regclass.java new file mode 100644 index 0000000..e3fc923 --- /dev/null +++ b/h2/src/main/org/h2/mode/Regclass.java @@ -0,0 +1,82 @@ +/* + * 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.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.Operation1; +import org.h2.expression.ValueExpression; +import org.h2.index.Index; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.table.Table; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueInteger; +import org.h2.value.ValueNull; + +/** + * A ::regclass expression. + */ +public final class Regclass extends Operation1 { + + public Regclass(Expression arg) { + super(arg); + } + + @Override + public Value getValue(SessionLocal session) { + Value value = arg.getValue(session); + if (value == ValueNull.INSTANCE) { + return ValueNull.INSTANCE; + } + int valueType = value.getValueType(); + if (valueType >= Value.TINYINT && valueType <= Value.INTEGER) { + return value.convertToInt(null); + } + if (valueType == Value.BIGINT) { + return ValueInteger.get((int) value.getLong()); + } + String name = value.getString(); + for (Schema schema : session.getDatabase().getAllSchemas()) { + Table table = schema.findTableOrView(session, name); + if (table != null && !table.isHidden()) { + return ValueInteger.get(table.getId()); + } + Index index = schema.findIndex(session, name); + if (index != null && index.getCreateSQL() != null) { + return ValueInteger.get(index.getId()); + } + } + throw DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, name); + } + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_INTEGER; + } + + @Override + public Expression optimize(SessionLocal session) { + arg = arg.optimize(session); + if (arg.isConstant()) { + return ValueExpression.get(getValue(session)); + } + return this; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + return arg.getSQL(builder, sqlFlags, AUTO_PARENTHESES).append("::REGCLASS"); + } + + @Override + public int getCost() { + return arg.getCost() + 100; + } + +} diff --git a/h2/src/main/org/h2/mode/ToDateParser.java b/h2/src/main/org/h2/mode/ToDateParser.java new file mode 100644 index 0000000..b789555 --- /dev/null +++ b/h2/src/main/org/h2/mode/ToDateParser.java @@ -0,0 +1,376 @@ +/* + * 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: Daniel Gredler + */ +package org.h2.mode; + +import static java.lang.String.format; + +import java.util.List; + +import org.h2.engine.SessionLocal; +import org.h2.util.DateTimeUtils; +import org.h2.util.TimeZoneProvider; +import org.h2.value.ValueTimestamp; +import org.h2.value.ValueTimestampTimeZone; + +/** + * Emulates Oracle's TO_DATE function.
+ * This class holds and handles the input data form the TO_DATE-method + */ +public final class ToDateParser { + + private final SessionLocal session; + + private final String unmodifiedInputStr; + private final String unmodifiedFormatStr; + private final ConfigParam functionName; + private String inputStr; + private String formatStr; + + private boolean doyValid = false, absoluteDayValid = false, + hour12Valid = false, + timeZoneHMValid = false; + + private boolean bc; + + private long absoluteDay; + + private int year, month, day = 1; + + private int dayOfYear; + + private int hour, minute, second, nanos; + + private int hour12; + + private boolean isAM = true; + + private TimeZoneProvider timeZone; + + private int timeZoneHour, timeZoneMinute; + + private int currentYear, currentMonth; + + /** + * @param session the database session + * @param functionName one of [TO_DATE, TO_TIMESTAMP] (both share the same + * code) + * @param input the input date with the date-time info + * @param format the format of date-time info + */ + private ToDateParser(SessionLocal session, ConfigParam functionName, String input, String format) { + this.session = session; + this.functionName = functionName; + inputStr = input.trim(); + // Keep a copy + unmodifiedInputStr = inputStr; + if (format == null || format.isEmpty()) { + // default Oracle format. + formatStr = functionName.getDefaultFormatStr(); + } else { + formatStr = format.trim(); + } + // Keep a copy + unmodifiedFormatStr = formatStr; + } + + private static ToDateParser getTimestampParser(SessionLocal session, ConfigParam param, String input, + String format) { + ToDateParser result = new ToDateParser(session, param, input, format); + parse(result); + return result; + } + + private ValueTimestamp getResultingValue() { + long dateValue; + if (absoluteDayValid) { + dateValue = DateTimeUtils.dateValueFromAbsoluteDay(absoluteDay); + } else { + int year = this.year; + if (year == 0) { + year = getCurrentYear(); + } + if (bc) { + year = 1 - year; + } + if (doyValid) { + dateValue = DateTimeUtils.dateValueFromAbsoluteDay( + DateTimeUtils.absoluteDayFromYear(year) + dayOfYear - 1); + } else { + int month = this.month; + if (month == 0) { + // Oracle uses current month as default + month = getCurrentMonth(); + } + dateValue = DateTimeUtils.dateValue(year, month, day); + } + } + int hour; + if (hour12Valid) { + hour = hour12 % 12; + if (!isAM) { + hour += 12; + } + } else { + hour = this.hour; + } + long timeNanos = ((((hour * 60) + minute) * 60) + second) * 1_000_000_000L + nanos; + return ValueTimestamp.fromDateValueAndNanos(dateValue, timeNanos); + } + + private ValueTimestampTimeZone getResultingValueWithTimeZone() { + ValueTimestamp ts = getResultingValue(); + long dateValue = ts.getDateValue(), timeNanos = ts.getTimeNanos(); + int offset; + if (timeZoneHMValid) { + offset = (timeZoneHour * 60 + ((timeZoneHour >= 0) ? timeZoneMinute : -timeZoneMinute)) * 60; + } else { + offset = (timeZone != null ? timeZone : session.currentTimeZone()) + .getTimeZoneOffsetLocal(dateValue, timeNanos); + } + return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, ts.getTimeNanos(), offset); + } + + String getInputStr() { + return inputStr; + } + + String getFormatStr() { + return formatStr; + } + + String getFunctionName() { + return functionName.name(); + } + + private void queryCurrentYearAndMonth() { + long dateValue = session.currentTimestamp().getDateValue(); + currentYear = DateTimeUtils.yearFromDateValue(dateValue); + currentMonth = DateTimeUtils.monthFromDateValue(dateValue); + } + + int getCurrentYear() { + if (currentYear == 0) { + queryCurrentYearAndMonth(); + } + return currentYear; + } + + int getCurrentMonth() { + if (currentMonth == 0) { + queryCurrentYearAndMonth(); + } + return currentMonth; + } + + void setAbsoluteDay(int absoluteDay) { + doyValid = false; + absoluteDayValid = true; + this.absoluteDay = absoluteDay; + } + + void setBC(boolean bc) { + doyValid = false; + absoluteDayValid = false; + this.bc = bc; + } + + void setYear(int year) { + doyValid = false; + absoluteDayValid = false; + this.year = year; + } + + void setMonth(int month) { + doyValid = false; + absoluteDayValid = false; + this.month = month; + if (year == 0) { + year = 1970; + } + } + + void setDay(int day) { + doyValid = false; + absoluteDayValid = false; + this.day = day; + if (year == 0) { + year = 1970; + } + } + + void setDayOfYear(int dayOfYear) { + doyValid = true; + absoluteDayValid = false; + this.dayOfYear = dayOfYear; + } + + void setHour(int hour) { + hour12Valid = false; + this.hour = hour; + } + + void setMinute(int minute) { + this.minute = minute; + } + + void setSecond(int second) { + this.second = second; + } + + void setNanos(int nanos) { + this.nanos = nanos; + } + + void setAmPm(boolean isAM) { + hour12Valid = true; + this.isAM = isAM; + } + + void setHour12(int hour12) { + hour12Valid = true; + this.hour12 = hour12; + } + + void setTimeZone(TimeZoneProvider timeZone) { + timeZoneHMValid = false; + this.timeZone = timeZone; + } + + void setTimeZoneHour(int timeZoneHour) { + timeZoneHMValid = true; + this.timeZoneHour = timeZoneHour; + } + + void setTimeZoneMinute(int timeZoneMinute) { + timeZoneHMValid = true; + this.timeZoneMinute = timeZoneMinute; + } + + private boolean hasToParseData() { + return !formatStr.isEmpty(); + } + + private void removeFirstChar() { + if (!formatStr.isEmpty()) { + formatStr = formatStr.substring(1); + } + if (!inputStr.isEmpty()) { + inputStr = inputStr.substring(1); + } + } + + private static ToDateParser parse(ToDateParser p) { + while (p.hasToParseData()) { + List tokenList = + ToDateTokenizer.FormatTokenEnum.getTokensInQuestion(p.getFormatStr()); + if (tokenList == null) { + p.removeFirstChar(); + continue; + } + boolean foundAnToken = false; + for (ToDateTokenizer.FormatTokenEnum token : tokenList) { + if (token.parseFormatStrWithToken(p)) { + foundAnToken = true; + break; + } + } + if (!foundAnToken) { + p.removeFirstChar(); + } + } + return p; + } + + /** + * Remove a token from a string. + * + * @param inputFragmentStr the input fragment + * @param formatFragment the format fragment + */ + void remove(String inputFragmentStr, String formatFragment) { + if (inputFragmentStr != null && inputStr.length() >= inputFragmentStr.length()) { + inputStr = inputStr.substring(inputFragmentStr.length()); + } + if (formatFragment != null && formatStr.length() >= formatFragment.length()) { + formatStr = formatStr.substring(formatFragment.length()); + } + } + + @Override + public String toString() { + int inputStrLen = inputStr.length(); + int orgInputLen = unmodifiedInputStr.length(); + int currentInputPos = orgInputLen - inputStrLen; + int restInputLen = inputStrLen <= 0 ? inputStrLen : inputStrLen - 1; + + int orgFormatLen = unmodifiedFormatStr.length(); + int currentFormatPos = orgFormatLen - formatStr.length(); + + return format("\n %s('%s', '%s')", functionName, unmodifiedInputStr, unmodifiedFormatStr) + + format("\n %s^%s , %s^ <-- Parsing failed at this point", + format("%" + (functionName.name().length() + currentInputPos) + "s", ""), + restInputLen <= 0 ? "" : format("%" + restInputLen + "s", ""), + currentFormatPos <= 0 ? "" : format("%" + currentFormatPos + "s", "")); + } + + /** + * Parse a string as a timestamp with the given format. + * + * @param session the database session + * @param input the input + * @param format the format + * @return the timestamp + */ + public static ValueTimestamp toTimestamp(SessionLocal session, String input, String format) { + ToDateParser parser = getTimestampParser(session, ConfigParam.TO_TIMESTAMP, input, format); + return parser.getResultingValue(); + } + + /** + * Parse a string as a timestamp with the given format. + * + * @param session the database session + * @param input the input + * @param format the format + * @return the timestamp + */ + public static ValueTimestampTimeZone toTimestampTz(SessionLocal session, String input, String format) { + ToDateParser parser = getTimestampParser(session, ConfigParam.TO_TIMESTAMP_TZ, input, format); + return parser.getResultingValueWithTimeZone(); + } + + /** + * Parse a string as a date with the given format. + * + * @param session the database session + * @param input the input + * @param format the format + * @return the date as a timestamp + */ + public static ValueTimestamp toDate(SessionLocal session, String input, String format) { + ToDateParser parser = getTimestampParser(session, ConfigParam.TO_DATE, input, format); + return parser.getResultingValue(); + } + + /** + * The configuration of the date parser. + */ + private enum ConfigParam { + TO_DATE("DD MON YYYY"), + TO_TIMESTAMP("DD MON YYYY HH:MI:SS"), + TO_TIMESTAMP_TZ("DD MON YYYY HH:MI:SS TZR"); + + private final String defaultFormatStr; + ConfigParam(String defaultFormatStr) { + this.defaultFormatStr = defaultFormatStr; + } + String getDefaultFormatStr() { + return defaultFormatStr; + } + + } + +} diff --git a/h2/src/main/org/h2/mode/ToDateTokenizer.java b/h2/src/main/org/h2/mode/ToDateTokenizer.java new file mode 100644 index 0000000..1cf8346 --- /dev/null +++ b/h2/src/main/org/h2/mode/ToDateTokenizer.java @@ -0,0 +1,717 @@ +/* + * 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: Daniel Gredler + */ +package org.h2.mode; + +import static java.lang.String.format; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.h2.api.ErrorCode; +import org.h2.expression.function.ToCharFunction; +import org.h2.message.DbException; +import org.h2.util.TimeZoneProvider; + +/** + * Emulates Oracle's TO_DATE function. This class knows all about the + * TO_DATE-format conventions and how to parse the corresponding data. + */ +final class ToDateTokenizer { + + /** + * The pattern for a number. + */ + static final Pattern PATTERN_INLINE = Pattern.compile("(\"[^\"]*\")"); + + /** + * The pattern for a number. + */ + static final Pattern PATTERN_NUMBER = Pattern.compile("^([+-]?[0-9]+)"); + + /** + * The pattern for four digits (typically a year). + */ + static final Pattern PATTERN_FOUR_DIGITS = Pattern + .compile("^([+-]?[0-9]{4})"); + + /** + * The pattern 2-4 digits (e.g. for RRRR). + */ + static final Pattern PATTERN_TWO_TO_FOUR_DIGITS = Pattern + .compile("^([+-]?[0-9]{2,4})"); + /** + * The pattern for three digits. + */ + static final Pattern PATTERN_THREE_DIGITS = Pattern + .compile("^([+-]?[0-9]{3})"); + + /** + * The pattern for two digits. + */ + static final Pattern PATTERN_TWO_DIGITS = Pattern + .compile("^([+-]?[0-9]{2})"); + + /** + * The pattern for one or two digits. + */ + static final Pattern PATTERN_TWO_DIGITS_OR_LESS = Pattern + .compile("^([+-]?[0-9][0-9]?)"); + + /** + * The pattern for one digit. + */ + static final Pattern PATTERN_ONE_DIGIT = Pattern.compile("^([+-]?[0-9])"); + + /** + * The pattern for a fraction (of a second for example). + */ + static final Pattern PATTERN_FF = Pattern.compile("^(FF[0-9]?)", + Pattern.CASE_INSENSITIVE); + + /** + * The pattern for "am" or "pm". + */ + static final Pattern PATTERN_AM_PM = Pattern + .compile("^(AM|A\\.M\\.|PM|P\\.M\\.)", Pattern.CASE_INSENSITIVE); + + /** + * The pattern for "bc" or "ad". + */ + static final Pattern PATTERN_BC_AD = Pattern + .compile("^(BC|B\\.C\\.|AD|A\\.D\\.)", Pattern.CASE_INSENSITIVE); + + /** + * The parslet for a year. + */ + static final YearParslet PARSLET_YEAR = new YearParslet(); + + /** + * The parslet for a month. + */ + static final MonthParslet PARSLET_MONTH = new MonthParslet(); + + /** + * The parslet for a day. + */ + static final DayParslet PARSLET_DAY = new DayParslet(); + + /** + * The parslet for time. + */ + static final TimeParslet PARSLET_TIME = new TimeParslet(); + + /** + * The inline parslet. E.g. 'YYYY-MM-DD"T"HH24:MI:SS"Z"' where "T" and "Z" + * are inlined + */ + static final InlineParslet PARSLET_INLINE = new InlineParslet(); + + /** + * Interface of the classes that can parse a specialized small bit of the + * TO_DATE format-string. + */ + interface ToDateParslet { + + /** + * Parse a date part. + * + * @param params the parameters that contains the string + * @param formatTokenEnum the format + * @param formatTokenStr the format string + */ + void parse(ToDateParser params, FormatTokenEnum formatTokenEnum, + String formatTokenStr); + } + + /** + * Parslet responsible for parsing year parameter + */ + static class YearParslet implements ToDateParslet { + + @Override + public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum, + String formatTokenStr) { + String inputFragmentStr = null; + int dateNr = 0; + switch (formatTokenEnum) { + case SYYYY: + case YYYY: + inputFragmentStr = matchStringOrThrow(PATTERN_FOUR_DIGITS, + params, formatTokenEnum); + dateNr = Integer.parseInt(inputFragmentStr); + // Gregorian calendar does not have a year 0. + // 0 = 0001 BC, -1 = 0002 BC, ... so we adjust + if (dateNr == 0) { + throwException(params, "Year may not be zero"); + } + params.setYear(dateNr >= 0 ? dateNr : dateNr + 1); + break; + case YYY: + inputFragmentStr = matchStringOrThrow(PATTERN_THREE_DIGITS, + params, formatTokenEnum); + dateNr = Integer.parseInt(inputFragmentStr); + if (dateNr > 999) { + throwException(params, "Year may have only three digits with specified format"); + } + dateNr += (params.getCurrentYear() / 1_000) * 1_000; + // Gregorian calendar does not have a year 0. + // 0 = 0001 BC, -1 = 0002 BC, ... so we adjust + params.setYear(dateNr >= 0 ? dateNr : dateNr + 1); + break; + case RRRR: + inputFragmentStr = matchStringOrThrow( + PATTERN_TWO_TO_FOUR_DIGITS, params, formatTokenEnum); + dateNr = Integer.parseInt(inputFragmentStr); + if (inputFragmentStr.length() < 4) { + if (dateNr < 50) { + dateNr += 2000; + } else if (dateNr < 100) { + dateNr += 1900; + } + } + if (dateNr == 0) { + throwException(params, "Year may not be zero"); + } + params.setYear(dateNr); + break; + case RR: + int cc = params.getCurrentYear() / 100; + inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS, + params, formatTokenEnum); + dateNr = Integer.parseInt(inputFragmentStr) + cc * 100; + params.setYear(dateNr); + break; + case EE /* NOT supported yet */: + throwException(params, format("token '%s' not supported yet.", + formatTokenEnum.name())); + break; + case E /* NOT supported yet */: + throwException(params, format("token '%s' not supported yet.", + formatTokenEnum.name())); + break; + case YY: + inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS, + params, formatTokenEnum); + dateNr = Integer.parseInt(inputFragmentStr); + if (dateNr > 99) { + throwException(params, "Year may have only two digits with specified format"); + } + dateNr += (params.getCurrentYear() / 100) * 100; + // Gregorian calendar does not have a year 0. + // 0 = 0001 BC, -1 = 0002 BC, ... so we adjust + params.setYear(dateNr >= 0 ? dateNr : dateNr + 1); + break; + case SCC: + case CC: + inputFragmentStr = matchStringOrThrow(PATTERN_TWO_DIGITS, + params, formatTokenEnum); + dateNr = Integer.parseInt(inputFragmentStr) * 100; + params.setYear(dateNr); + break; + case Y: + inputFragmentStr = matchStringOrThrow(PATTERN_ONE_DIGIT, params, + formatTokenEnum); + dateNr = Integer.parseInt(inputFragmentStr); + if (dateNr > 9) { + throwException(params, "Year may have only two digits with specified format"); + } + dateNr += (params.getCurrentYear() / 10) * 10; + // Gregorian calendar does not have a year 0. + // 0 = 0001 BC, -1 = 0002 BC, ... so we adjust + params.setYear(dateNr >= 0 ? dateNr : dateNr + 1); + break; + case BC_AD: + inputFragmentStr = matchStringOrThrow(PATTERN_BC_AD, params, + formatTokenEnum); + params.setBC(inputFragmentStr.toUpperCase().startsWith("B")); + break; + default: + throw new IllegalArgumentException(format( + "%s: Internal Error. Unhandled case: %s", + this.getClass().getSimpleName(), formatTokenEnum)); + } + params.remove(inputFragmentStr, formatTokenStr); + } + } + + /** + * Parslet responsible for parsing month parameter + */ + static class MonthParslet implements ToDateParslet { + private static final String[] ROMAN_MONTH = { "I", "II", "III", "IV", + "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII" }; + + @Override + public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum, + String formatTokenStr) { + String s = params.getInputStr(); + String inputFragmentStr = null; + int dateNr = 0; + switch (formatTokenEnum) { + case MONTH: + inputFragmentStr = setByName(params, ToCharFunction.MONTHS); + break; + case Q /* NOT supported yet */: + throwException(params, format("token '%s' not supported yet.", + formatTokenEnum.name())); + break; + case MON: + inputFragmentStr = setByName(params, ToCharFunction.SHORT_MONTHS); + break; + case MM: + // Note: In Calendar Month go from 0 - 11 + inputFragmentStr = matchStringOrThrow( + PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); + dateNr = Integer.parseInt(inputFragmentStr); + params.setMonth(dateNr); + break; + case RM: + dateNr = 0; + for (String monthName : ROMAN_MONTH) { + dateNr++; + int len = monthName.length(); + if (s.length() >= len && monthName + .equalsIgnoreCase(s.substring(0, len))) { + params.setMonth(dateNr + 1); + inputFragmentStr = monthName; + break; + } + } + if (inputFragmentStr == null || inputFragmentStr.isEmpty()) { + throwException(params, + format("Issue happened when parsing token '%s'. " + + "Expected one of: %s", + formatTokenEnum.name(), + Arrays.toString(ROMAN_MONTH))); + } + break; + default: + throw new IllegalArgumentException(format( + "%s: Internal Error. Unhandled case: %s", + this.getClass().getSimpleName(), formatTokenEnum)); + } + params.remove(inputFragmentStr, formatTokenStr); + } + } + + /** + * Parslet responsible for parsing day parameter + */ + static class DayParslet implements ToDateParslet { + @Override + public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum, + String formatTokenStr) { + String inputFragmentStr = null; + int dateNr = 0; + switch (formatTokenEnum) { + case DDD: + inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params, + formatTokenEnum); + dateNr = Integer.parseInt(inputFragmentStr); + params.setDayOfYear(dateNr); + break; + case DD: + inputFragmentStr = matchStringOrThrow( + PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); + dateNr = Integer.parseInt(inputFragmentStr); + params.setDay(dateNr); + break; + case D: + inputFragmentStr = matchStringOrThrow(PATTERN_ONE_DIGIT, params, + formatTokenEnum); + dateNr = Integer.parseInt(inputFragmentStr); + params.setDay(dateNr); + break; + case DAY: + inputFragmentStr = setByName(params, ToCharFunction.WEEKDAYS); + break; + case DY: + inputFragmentStr = setByName(params, ToCharFunction.SHORT_WEEKDAYS); + break; + case J: + inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params, + formatTokenEnum); + dateNr = Integer.parseInt(inputFragmentStr); + params.setAbsoluteDay(dateNr + ToCharFunction.JULIAN_EPOCH); + break; + default: + throw new IllegalArgumentException(format( + "%s: Internal Error. Unhandled case: %s", + this.getClass().getSimpleName(), formatTokenEnum)); + } + params.remove(inputFragmentStr, formatTokenStr); + } + } + + /** + * Parslet responsible for parsing time parameter + */ + static class TimeParslet implements ToDateParslet { + + @Override + public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum, + String formatTokenStr) { + String inputFragmentStr = null; + int dateNr = 0; + switch (formatTokenEnum) { + case HH24: + inputFragmentStr = matchStringOrThrow( + PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); + dateNr = Integer.parseInt(inputFragmentStr); + params.setHour(dateNr); + break; + case HH12: + case HH: + inputFragmentStr = matchStringOrThrow( + PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); + dateNr = Integer.parseInt(inputFragmentStr); + params.setHour12(dateNr); + break; + case MI: + inputFragmentStr = matchStringOrThrow( + PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); + dateNr = Integer.parseInt(inputFragmentStr); + params.setMinute(dateNr); + break; + case SS: + inputFragmentStr = matchStringOrThrow( + PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); + dateNr = Integer.parseInt(inputFragmentStr); + params.setSecond(dateNr); + break; + case SSSSS: { + inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params, + formatTokenEnum); + dateNr = Integer.parseInt(inputFragmentStr); + int second = dateNr % 60; + dateNr /= 60; + int minute = dateNr % 60; + dateNr /= 60; + int hour = dateNr % 24; + params.setHour(hour); + params.setMinute(minute); + params.setSecond(second); + break; + } + case FF: + inputFragmentStr = matchStringOrThrow(PATTERN_NUMBER, params, + formatTokenEnum); + String paddedRightNrStr = format("%-9s", inputFragmentStr) + .replace(' ', '0'); + paddedRightNrStr = paddedRightNrStr.substring(0, 9); + double nineDigits = Double.parseDouble(paddedRightNrStr); + params.setNanos((int) nineDigits); + break; + case AM_PM: + inputFragmentStr = matchStringOrThrow(PATTERN_AM_PM, params, + formatTokenEnum); + if (inputFragmentStr.toUpperCase().startsWith("A")) { + params.setAmPm(true); + } else { + params.setAmPm(false); + } + break; + case TZH: + inputFragmentStr = matchStringOrThrow( + PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); + dateNr = Integer.parseInt(inputFragmentStr); + params.setTimeZoneHour(dateNr); + break; + case TZM: + inputFragmentStr = matchStringOrThrow( + PATTERN_TWO_DIGITS_OR_LESS, params, formatTokenEnum); + dateNr = Integer.parseInt(inputFragmentStr); + params.setTimeZoneMinute(dateNr); + break; + case TZR: + case TZD: + String tzName = params.getInputStr(); + params.setTimeZone(TimeZoneProvider.ofId(tzName)); + inputFragmentStr = tzName; + break; + default: + throw new IllegalArgumentException(format( + "%s: Internal Error. Unhandled case: %s", + this.getClass().getSimpleName(), formatTokenEnum)); + } + params.remove(inputFragmentStr, formatTokenStr); + } + } + + /** + * Parslet responsible for parsing year parameter + */ + static class InlineParslet implements ToDateParslet { + @Override + public void parse(ToDateParser params, FormatTokenEnum formatTokenEnum, + String formatTokenStr) { + String inputFragmentStr = null; + switch (formatTokenEnum) { + case INLINE: + inputFragmentStr = formatTokenStr.replace("\"", ""); + break; + default: + throw new IllegalArgumentException(format( + "%s: Internal Error. Unhandled case: %s", + this.getClass().getSimpleName(), formatTokenEnum)); + } + params.remove(inputFragmentStr, formatTokenStr); + } + + } + + /** + * Match the pattern, or if not possible throw an exception. + * + * @param p the pattern + * @param params the parameters with the input string + * @param aEnum the pattern name + * @return the matched value + */ + static String matchStringOrThrow(Pattern p, ToDateParser params, + Enum aEnum) { + String s = params.getInputStr(); + Matcher matcher = p.matcher(s); + if (!matcher.find()) { + throwException(params, format( + "Issue happened when parsing token '%s'", aEnum.name())); + } + return matcher.group(1); + } + + /** + * Set the given field in the calendar. + * + * @param params the parameters with the input string + * @param field the field to set + * @return the matched value + */ + static String setByName(ToDateParser params, int field) { + String inputFragmentStr = null; + String s = params.getInputStr(); + String[] values = ToCharFunction.getDateNames(field); + for (int i = 0; i < values.length; i++) { + String dayName = values[i]; + if (dayName == null) { + continue; + } + int len = dayName.length(); + if (dayName.equalsIgnoreCase(s.substring(0, len))) { + switch (field) { + case ToCharFunction.MONTHS: + case ToCharFunction.SHORT_MONTHS: + params.setMonth(i + 1); + break; + case ToCharFunction.WEEKDAYS: + case ToCharFunction.SHORT_WEEKDAYS: + // TODO + break; + default: + throw new IllegalArgumentException(); + } + inputFragmentStr = dayName; + break; + } + } + if (inputFragmentStr == null || inputFragmentStr.isEmpty()) { + throwException(params, format( + "Tried to parse one of '%s' but failed (may be an internal error?)", + Arrays.toString(values))); + } + return inputFragmentStr; + } + + /** + * Throw a parse exception. + * + * @param params the parameters with the input string + * @param errorStr the error string + */ + static void throwException(ToDateParser params, String errorStr) { + throw DbException.get(ErrorCode.INVALID_TO_DATE_FORMAT, + params.getFunctionName(), + format(" %s. Details: %s", errorStr, params)); + } + + /** + * The format tokens. + */ + public enum FormatTokenEnum { + // 4-digit year + YYYY(PARSLET_YEAR), + // 4-digit year with sign (- = B.C.) + SYYYY(PARSLET_YEAR), + // 3-digit year + YYY(PARSLET_YEAR), + // 2-digit year + YY(PARSLET_YEAR), + // Two-digit century with sign (- = B.C.) + SCC(PARSLET_YEAR), + // Two-digit century. + CC(PARSLET_YEAR), + // 2-digit -> 4-digit year 0-49 -> 20xx , 50-99 -> 19xx + RRRR(PARSLET_YEAR), + // last 2-digit of the year using "current" century value. + RR(PARSLET_YEAR), + // Meridian indicator + BC_AD(PARSLET_YEAR, PATTERN_BC_AD), + // Full Name of month + MONTH(PARSLET_MONTH), + // Abbreviated name of month. + MON(PARSLET_MONTH), + // Month (01-12; JAN = 01). + MM(PARSLET_MONTH), + // Roman numeral month (I-XII; JAN = I). + RM(PARSLET_MONTH), + // Day of year (1-366). + DDD(PARSLET_DAY), + // Name of day. + DAY(PARSLET_DAY), + // Day of month (1-31). + DD(PARSLET_DAY), + // Abbreviated name of day. + DY(PARSLET_DAY), HH24(PARSLET_TIME), HH12(PARSLET_TIME), + // Hour of day (1-12). + HH(PARSLET_TIME), + // Min + MI(PARSLET_TIME), + // Seconds past midnight (0-86399) + SSSSS(PARSLET_TIME), SS(PARSLET_TIME), + // Fractional seconds + FF(PARSLET_TIME, PATTERN_FF), + // Time zone hour. + TZH(PARSLET_TIME), + // Time zone minute. + TZM(PARSLET_TIME), + // Time zone region ID + TZR(PARSLET_TIME), + // Daylight savings information. Example: + // PST (for US/Pacific standard time); + TZD(PARSLET_TIME), + // Meridian indicator + AM_PM(PARSLET_TIME, PATTERN_AM_PM), + // NOT supported yet - + // Full era name (Japanese Imperial, ROC Official, + // and Thai Buddha calendars). + EE(PARSLET_YEAR), + // NOT supported yet - + // Abbreviated era name (Japanese Imperial, + // ROC Official, and Thai Buddha calendars). + E(PARSLET_YEAR), Y(PARSLET_YEAR), + // Quarter of year (1, 2, 3, 4; JAN-MAR = 1). + Q(PARSLET_MONTH), + // Day of week (1-7). + D(PARSLET_DAY), + // NOT supported yet - + // Julian day; the number of days since Jan 1, 4712 BC. + J(PARSLET_DAY), + // Inline text e.g. to_date('2017-04-21T00:00:00Z', + // 'YYYY-MM-DD"T"HH24:MI:SS"Z"') + // where "T" and "Z" are inlined + INLINE(PARSLET_INLINE, PATTERN_INLINE); + + private static final List INLINE_LIST = Collections.singletonList(INLINE); + + private static List[] TOKENS; + private final ToDateParslet toDateParslet; + private final Pattern patternToUse; + + /** + * Construct a format token. + * + * @param toDateParslet the date parslet + * @param patternToUse the pattern + */ + FormatTokenEnum(ToDateParslet toDateParslet, Pattern patternToUse) { + this.toDateParslet = toDateParslet; + this.patternToUse = patternToUse; + } + + /** + * Construct a format token. + * + * @param toDateParslet the date parslet + */ + FormatTokenEnum(ToDateParslet toDateParslet) { + this.toDateParslet = toDateParslet; + patternToUse = Pattern.compile(format("^(%s)", name()), + Pattern.CASE_INSENSITIVE); + } + + /** + * Optimization: Only return a list of {@link FormatTokenEnum} that + * share the same 1st char using the 1st char of the 'to parse' + * formatStr. Or return {@code null} if no match. + * + * @param formatStr the format string + * @return the list of tokens, or {@code null} + */ + static List getTokensInQuestion(String formatStr) { + if (formatStr != null && !formatStr.isEmpty()) { + char key = Character.toUpperCase(formatStr.charAt(0)); + if (key >= 'A' && key <= 'Y') { + List[] tokens = TOKENS; + if (tokens == null) { + tokens = initTokens(); + } + return tokens[key - 'A']; + } else if (key == '"') { + return INLINE_LIST; + } + } + return null; + } + + @SuppressWarnings("unchecked") + private static List[] initTokens() { + List[] tokens = new List[25]; + for (FormatTokenEnum token : FormatTokenEnum.values()) { + String name = token.name(); + if (name.indexOf('_') >= 0) { + for (String tokenLets : name.split("_")) { + putToCache(tokens, token, tokenLets); + } + } else { + putToCache(tokens, token, name); + } + } + return TOKENS = tokens; + } + + private static void putToCache(List[] cache, FormatTokenEnum token, String name) { + int idx = Character.toUpperCase(name.charAt(0)) - 'A'; + List l = cache[idx]; + if (l == null) { + l = new ArrayList<>(1); + cache[idx] = l; + } + l.add(token); + } + + /** + * Parse the format-string with passed token of {@link FormatTokenEnum}. + * If token matches return true, otherwise false. + * + * @param params the parameters + * @return true if it matches + */ + boolean parseFormatStrWithToken(ToDateParser params) { + Matcher matcher = patternToUse.matcher(params.getFormatStr()); + boolean foundToken = matcher.find(); + if (foundToken) { + String formatTokenStr = matcher.group(1); + toDateParslet.parse(params, this, formatTokenStr); + } + return foundToken; + } + } + + private ToDateTokenizer() { + } + +} diff --git a/h2/src/main/org/h2/mode/package.html b/h2/src/main/org/h2/mode/package.html new file mode 100644 index 0000000..b1194fe --- /dev/null +++ b/h2/src/main/org/h2/mode/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Utility classes for compatibility with other database, for example MySQL. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/mvstore/Chunk.java b/h2/src/main/org/h2/mvstore/Chunk.java new file mode 100644 index 0000000..c6da22f --- /dev/null +++ b/h2/src/main/org/h2/mvstore/Chunk.java @@ -0,0 +1,548 @@ +/* + * 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.mvstore; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.BitSet; +import java.util.Comparator; +import java.util.Map; + +import org.h2.util.StringUtils; + +/** + * A chunk of data, containing one or multiple pages. + *

+ * Minimum chunk size is usually 4096 bytes, and it grows in those fixed increments (blocks). + * Chunk's length and it's position in the underlying filestore + * are multiples of that increment (block size), + * therefore they both are measured in blocks, instead of bytes. + * There are at most 67 million (2^26) chunks, + * and each chunk is at most 2 GB large. + */ +public final class Chunk { + + /** + * The maximum chunk id. + */ + public static final int MAX_ID = (1 << 26) - 1; + + /** + * The maximum length of a chunk header, in bytes. + */ + static final int MAX_HEADER_LENGTH = 1024; + + /** + * The length of the chunk footer. The longest footer is: + * chunk:ffffffff,block:ffffffffffffffff, + * version:ffffffffffffffff,fletcher:ffffffff + */ + static final int FOOTER_LENGTH = 128; + + private static final String ATTR_CHUNK = "chunk"; + private static final String ATTR_BLOCK = "block"; + private static final String ATTR_LEN = "len"; + private static final String ATTR_MAP = "map"; + private static final String ATTR_MAX = "max"; + private static final String ATTR_NEXT = "next"; + private static final String ATTR_PAGES = "pages"; + private static final String ATTR_ROOT = "root"; + private static final String ATTR_TIME = "time"; + private static final String ATTR_VERSION = "version"; + private static final String ATTR_LIVE_MAX = "liveMax"; + private static final String ATTR_LIVE_PAGES = "livePages"; + private static final String ATTR_UNUSED = "unused"; + private static final String ATTR_UNUSED_AT_VERSION = "unusedAtVersion"; + private static final String ATTR_PIN_COUNT = "pinCount"; + private static final String ATTR_TOC = "toc"; + private static final String ATTR_OCCUPANCY = "occupancy"; + private static final String ATTR_FLETCHER = "fletcher"; + + /** + * The chunk id. + */ + public final int id; + + /** + * The start block number within the file. + */ + public volatile long block; + + /** + * The length in number of blocks. + */ + public int len; + + /** + * The total number of pages in this chunk. + */ + int pageCount; + + /** + * The number of pages that are still alive in the latest version of the store. + */ + int pageCountLive; + + /** + * Offset (from the beginning of the chunk) for the table of content. + * Table of content is holding a value of type "long" for each page in the chunk. + * This value consists of map id, page offset, page length and page type. + * Format is the same as page's position id, but with map id replacing chunk id. + * + * @see DataUtils#getTocElement(int, int, int, int) for field format details + */ + int tocPos; + + /** + * Collection of "deleted" flags for all pages in the chunk. + */ + BitSet occupancy; + + /** + * The sum of the max length of all pages. + */ + public long maxLen; + + /** + * The sum of the length of all pages that are still alive. + */ + public long maxLenLive; + + /** + * The garbage collection priority. Priority 0 means it needs to be + * collected, a high value means low priority. + */ + int collectPriority; + + /** + * The position of the root of layout map. + */ + long layoutRootPos; + + /** + * The version stored in this chunk. + */ + public long version; + + /** + * When this chunk was created, in milliseconds after the store was created. + */ + public long time; + + /** + * When this chunk was no longer needed, in milliseconds after the store was + * created. After this, the chunk is kept alive a bit longer (in case it is + * referenced in older versions). + */ + public long unused; + + /** + * Version of the store at which chunk become unused and therefore can be + * considered "dead" and collected after this version is no longer in use. + */ + long unusedAtVersion; + + /** + * The last used map id. + */ + public int mapId; + + /** + * The predicted position of the next chunk. + */ + public long next; + + /** + * Number of live pinned pages. + */ + private int pinCount; + + + private Chunk(String s) { + this(DataUtils.parseMap(s), true); + } + + Chunk(Map map) { + this(map, false); + } + + private Chunk(Map map, boolean full) { + this(DataUtils.readHexInt(map, ATTR_CHUNK, 0)); + block = DataUtils.readHexLong(map, ATTR_BLOCK, 0); + version = DataUtils.readHexLong(map, ATTR_VERSION, id); + if (full) { + len = DataUtils.readHexInt(map, ATTR_LEN, 0); + pageCount = DataUtils.readHexInt(map, ATTR_PAGES, 0); + pageCountLive = DataUtils.readHexInt(map, ATTR_LIVE_PAGES, pageCount); + mapId = DataUtils.readHexInt(map, ATTR_MAP, 0); + maxLen = DataUtils.readHexLong(map, ATTR_MAX, 0); + maxLenLive = DataUtils.readHexLong(map, ATTR_LIVE_MAX, maxLen); + layoutRootPos = DataUtils.readHexLong(map, ATTR_ROOT, 0); + time = DataUtils.readHexLong(map, ATTR_TIME, 0); + unused = DataUtils.readHexLong(map, ATTR_UNUSED, 0); + unusedAtVersion = DataUtils.readHexLong(map, ATTR_UNUSED_AT_VERSION, 0); + next = DataUtils.readHexLong(map, ATTR_NEXT, 0); + pinCount = DataUtils.readHexInt(map, ATTR_PIN_COUNT, 0); + tocPos = DataUtils.readHexInt(map, ATTR_TOC, 0); + byte[] bytes = DataUtils.parseHexBytes(map, ATTR_OCCUPANCY); + if (bytes == null) { + occupancy = new BitSet(); + } else { + occupancy = BitSet.valueOf(bytes); + if (pageCount - pageCountLive != occupancy.cardinality()) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_FILE_CORRUPT, "Inconsistent occupancy info {0} - {1} != {2} {3}", + pageCount, pageCountLive, occupancy.cardinality(), this); + } + } + } + } + + Chunk(int id) { + this.id = id; + if (id <= 0) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_FILE_CORRUPT, "Invalid chunk id {0}", id); + } + } + + /** + * Read the header from the byte buffer. + * + * @param buff the source buffer + * @param start the start of the chunk in the file + * @return the chunk + */ + static Chunk readChunkHeader(ByteBuffer buff, long start) { + int pos = buff.position(); + byte[] data = new byte[Math.min(buff.remaining(), MAX_HEADER_LENGTH)]; + buff.get(data); + try { + for (int i = 0; i < data.length; i++) { + if (data[i] == '\n') { + // set the position to the start of the first page + buff.position(pos + i + 1); + String s = new String(data, 0, i, StandardCharsets.ISO_8859_1).trim(); + return fromString(s); + } + } + } catch (Exception e) { + // there could be various reasons + throw DataUtils.newMVStoreException( + DataUtils.ERROR_FILE_CORRUPT, + "File corrupt reading chunk at position {0}", start, e); + } + throw DataUtils.newMVStoreException( + DataUtils.ERROR_FILE_CORRUPT, + "File corrupt reading chunk at position {0}", start); + } + + /** + * Write the chunk header. + * + * @param buff the target buffer + * @param minLength the minimum length + */ + void writeChunkHeader(WriteBuffer buff, int minLength) { + long delimiterPosition = buff.position() + minLength - 1; + buff.put(asString().getBytes(StandardCharsets.ISO_8859_1)); + while (buff.position() < delimiterPosition) { + buff.put((byte) ' '); + } + if (minLength != 0 && buff.position() > delimiterPosition) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_INTERNAL, + "Chunk metadata too long"); + } + buff.put((byte) '\n'); + } + + /** + * Get the metadata key for the given chunk id. + * + * @param chunkId the chunk id + * @return the metadata key + */ + static String getMetaKey(int chunkId) { + return ATTR_CHUNK + "." + Integer.toHexString(chunkId); + } + + /** + * Build a block from the given string. + * + * @param s the string + * @return the block + */ + public static Chunk fromString(String s) { + return new Chunk(s); + } + + /** + * Calculate the fill rate in %. 0 means empty, 100 means full. + * + * @return the fill rate + */ + int getFillRate() { + assert maxLenLive <= maxLen : maxLenLive + " > " + maxLen; + if (maxLenLive <= 0) { + return 0; + } else if (maxLenLive == maxLen) { + return 100; + } + return 1 + (int) (98 * maxLenLive / maxLen); + } + + @Override + public int hashCode() { + return id; + } + + @Override + public boolean equals(Object o) { + return o instanceof Chunk && ((Chunk) o).id == id; + } + + /** + * Get the chunk data as a string. + * + * @return the string + */ + public String asString() { + StringBuilder buff = new StringBuilder(240); + DataUtils.appendMap(buff, ATTR_CHUNK, id); + DataUtils.appendMap(buff, ATTR_BLOCK, block); + DataUtils.appendMap(buff, ATTR_LEN, len); + if (maxLen != maxLenLive) { + DataUtils.appendMap(buff, ATTR_LIVE_MAX, maxLenLive); + } + if (pageCount != pageCountLive) { + DataUtils.appendMap(buff, ATTR_LIVE_PAGES, pageCountLive); + } + DataUtils.appendMap(buff, ATTR_MAP, mapId); + DataUtils.appendMap(buff, ATTR_MAX, maxLen); + if (next != 0) { + DataUtils.appendMap(buff, ATTR_NEXT, next); + } + DataUtils.appendMap(buff, ATTR_PAGES, pageCount); + DataUtils.appendMap(buff, ATTR_ROOT, layoutRootPos); + DataUtils.appendMap(buff, ATTR_TIME, time); + if (unused != 0) { + DataUtils.appendMap(buff, ATTR_UNUSED, unused); + } + if (unusedAtVersion != 0) { + DataUtils.appendMap(buff, ATTR_UNUSED_AT_VERSION, unusedAtVersion); + } + DataUtils.appendMap(buff, ATTR_VERSION, version); + if (pinCount > 0) { + DataUtils.appendMap(buff, ATTR_PIN_COUNT, pinCount); + } + if (tocPos > 0) { + DataUtils.appendMap(buff, ATTR_TOC, tocPos); + } + if (!occupancy.isEmpty()) { + DataUtils.appendMap(buff, ATTR_OCCUPANCY, + StringUtils.convertBytesToHex(occupancy.toByteArray())); + } + return buff.toString(); + } + + byte[] getFooterBytes() { + StringBuilder buff = new StringBuilder(FOOTER_LENGTH); + DataUtils.appendMap(buff, ATTR_CHUNK, id); + DataUtils.appendMap(buff, ATTR_BLOCK, block); + DataUtils.appendMap(buff, ATTR_VERSION, version); + byte[] bytes = buff.toString().getBytes(StandardCharsets.ISO_8859_1); + int checksum = DataUtils.getFletcher32(bytes, 0, bytes.length); + DataUtils.appendMap(buff, ATTR_FLETCHER, checksum); + while (buff.length() < FOOTER_LENGTH - 1) { + buff.append(' '); + } + buff.append('\n'); + return buff.toString().getBytes(StandardCharsets.ISO_8859_1); + } + + boolean isSaved() { + return block != Long.MAX_VALUE; + } + + boolean isLive() { + return pageCountLive > 0; + } + + boolean isRewritable() { + return isSaved() + && isLive() + && pageCountLive < pageCount // not fully occupied + && isEvacuatable(); + } + + private boolean isEvacuatable() { + return pinCount == 0; + } + + /** + * Read a page of data into a ByteBuffer. + * + * @param fileStore to use + * @param offset of the page data + * @param pos page pos + * @return ByteBuffer containing page data. + */ + ByteBuffer readBufferForPage(FileStore fileStore, int offset, long pos) { + assert isSaved() : this; + while (true) { + long originalBlock = block; + try { + long filePos = originalBlock * MVStore.BLOCK_SIZE; + long maxPos = filePos + (long) len * MVStore.BLOCK_SIZE; + filePos += offset; + if (filePos < 0) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_FILE_CORRUPT, + "Negative position {0}; p={1}, c={2}", filePos, pos, toString()); + } + + int length = DataUtils.getPageMaxLength(pos); + if (length == DataUtils.PAGE_LARGE) { + // read the first bytes to figure out actual length + length = fileStore.readFully(filePos, 128).getInt(); + // pageNo is deliberately not included into length to preserve compatibility + // TODO: remove this adjustment when page on disk format is re-organized + length += 4; + } + length = (int) Math.min(maxPos - filePos, length); + if (length < 0) { + throw DataUtils.newMVStoreException(DataUtils.ERROR_FILE_CORRUPT, + "Illegal page length {0} reading at {1}; max pos {2} ", length, filePos, maxPos); + } + + ByteBuffer buff = fileStore.readFully(filePos, length); + + if (originalBlock == block) { + return buff; + } + } catch (MVStoreException ex) { + if (originalBlock == block) { + throw ex; + } + } + } + } + + long[] readToC(FileStore fileStore) { + assert isSaved() : this; + assert tocPos > 0; + while (true) { + long originalBlock = block; + try { + long filePos = originalBlock * MVStore.BLOCK_SIZE + tocPos; + int length = pageCount * 8; + long[] toc = new long[pageCount]; + fileStore.readFully(filePos, length).asLongBuffer().get(toc); + if (originalBlock == block) { + return toc; + } + } catch (MVStoreException ex) { + if (originalBlock == block) { + throw ex; + } + } + } + } + + /** + * Modifies internal state to reflect the fact that one more page is stored + * within this chunk. + * @param pageLengthOnDisk + * size of the page + * @param singleWriter + * indicates whether page belongs to append mode capable map + * (single writer map). Such pages are "pinned" to the chunk, + * they can't be evacuated (moved to a different chunk) while + */ + void accountForWrittenPage(int pageLengthOnDisk, boolean singleWriter) { + maxLen += pageLengthOnDisk; + pageCount++; + maxLenLive += pageLengthOnDisk; + pageCountLive++; + if (singleWriter) { + pinCount++; + } + assert pageCount - pageCountLive == occupancy.cardinality() + : pageCount + " - " + pageCountLive + " <> " + occupancy.cardinality() + " : " + occupancy; + } + + /** + * Modifies internal state to reflect the fact that one the pages within + * this chunk was removed from the map. + * + * @param pageNo + * sequential page number within the chunk + * @param pageLength + * on disk of the removed page + * @param pinned + * whether removed page was pinned + * @param now + * is a moment in time (since creation of the store), when + * removal is recorded, and retention period starts + * @param version + * at which page was removed + * @return true if all of the pages, this chunk contains, were already + * removed, and false otherwise + */ + boolean accountForRemovedPage(int pageNo, int pageLength, boolean pinned, long now, long version) { + assert isSaved() : this; + // legacy chunks do not have a table of content, + // therefore pageNo is not valid, skip + if (tocPos > 0) { + assert pageNo >= 0 && pageNo < pageCount : pageNo + " // " + pageCount; + assert !occupancy.get(pageNo) : pageNo + " " + this + " " + occupancy; + assert pageCount - pageCountLive == occupancy.cardinality() + : pageCount + " - " + pageCountLive + " <> " + occupancy.cardinality() + " : " + occupancy; + occupancy.set(pageNo); + } + + maxLenLive -= pageLength; + pageCountLive--; + if (pinned) { + pinCount--; + } + + if (unusedAtVersion < version) { + unusedAtVersion = version; + } + + assert pinCount >= 0 : this; + assert pageCountLive >= 0 : this; + assert pinCount <= pageCountLive : this; + assert maxLenLive >= 0 : this; + assert (pageCountLive == 0) == (maxLenLive == 0) : this; + + if (!isLive()) { + unused = now; + return true; + } + return false; + } + + @Override + public String toString() { + return asString(); + } + + + public static final class PositionComparator implements Comparator { + public static final Comparator INSTANCE = new PositionComparator(); + + private PositionComparator() {} + + @Override + public int compare(Chunk one, Chunk two) { + return Long.compare(one.block, two.block); + } + } +} + diff --git a/h2/src/main/org/h2/mvstore/Cursor.java b/h2/src/main/org/h2/mvstore/Cursor.java new file mode 100644 index 0000000..d60ca8c --- /dev/null +++ b/h2/src/main/org/h2/mvstore/Cursor.java @@ -0,0 +1,185 @@ +/* + * 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.mvstore; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +/** + * A cursor to iterate over elements in ascending or descending order. + * + * @param the key type + * @param the value type + */ +public final class Cursor implements Iterator { + private final boolean reverse; + private final K to; + private CursorPos cursorPos; + private CursorPos keeper; + private K current; + private K last; + private V lastValue; + private Page lastPage; + + + public Cursor(RootReference rootReference, K from, K to) { + this(rootReference, from, to, false); + } + + /** + * @param rootReference of the tree + * @param from starting key (inclusive), if null start from the first / last key + * @param to ending key (inclusive), if null there is no boundary + * @param reverse true if tree should be iterated in key's descending order + */ + public Cursor(RootReference rootReference, K from, K to, boolean reverse) { + this.lastPage = rootReference.root; + this.cursorPos = traverseDown(lastPage, from, reverse); + this.to = to; + this.reverse = reverse; + } + + @Override + public boolean hasNext() { + if (cursorPos != null) { + int increment = reverse ? -1 : 1; + while (current == null) { + Page page = cursorPos.page; + int index = cursorPos.index; + if (reverse ? index < 0 : index >= upperBound(page)) { + // traversal of this page is over, going up a level or stop if at the root already + CursorPos tmp = cursorPos; + cursorPos = cursorPos.parent; + if (cursorPos == null) { + return false; + } + tmp.parent = keeper; + keeper = tmp; + } else { + // traverse down to the leaf taking the leftmost path + while (!page.isLeaf()) { + page = page.getChildPage(index); + index = reverse ? upperBound(page) - 1 : 0; + if (keeper == null) { + cursorPos = new CursorPos<>(page, index, cursorPos); + } else { + CursorPos tmp = keeper; + keeper = keeper.parent; + tmp.parent = cursorPos; + tmp.page = page; + tmp.index = index; + cursorPos = tmp; + } + } + if (reverse ? index >= 0 : index < page.getKeyCount()) { + K key = page.getKey(index); + if (to != null && Integer.signum(page.map.getKeyType().compare(key, to)) == increment) { + return false; + } + current = last = key; + lastValue = page.getValue(index); + lastPage = page; + } + } + cursorPos.index += increment; + } + } + return current != null; + } + + @Override + public K next() { + if(!hasNext()) { + throw new NoSuchElementException(); + } + current = null; + return last; + } + + /** + * Get the last read key if there was one. + * + * @return the key or null + */ + public K getKey() { + return last; + } + + /** + * Get the last read value if there was one. + * + * @return the value or null + */ + public V getValue() { + return lastValue; + } + + /** + * Get the page where last retrieved key is located. + * + * @return the page + */ + @SuppressWarnings("unused") + Page getPage() { + return lastPage; + } + + /** + * Skip over that many entries. This method is relatively fast (for this map + * implementation) even if many entries need to be skipped. + * + * @param n the number of entries to skip + */ + public void skip(long n) { + if (n < 10) { + while (n-- > 0 && hasNext()) { + next(); + } + } else if(hasNext()) { + assert cursorPos != null; + CursorPos cp = cursorPos; + CursorPos parent; + while ((parent = cp.parent) != null) cp = parent; + Page root = cp.page; + MVMap map = root.map; + long index = map.getKeyIndex(next()); + last = map.getKey(index + (reverse ? -n : n)); + this.cursorPos = traverseDown(root, last, reverse); + } + } + + /** + * Fetch the next entry that is equal or larger than the given key, starting + * from the given page. This method returns the path. + * + * @param key type + * @param value type + * + * @param page to start from as a root + * @param key to search for, null means search for the first available key + * @param reverse true if traversal is in reverse direction, false otherwise + * @return CursorPos representing path from the entry found, + * or from insertion point if not, + * all the way up to to the root page provided + */ + static CursorPos traverseDown(Page page, K key, boolean reverse) { + CursorPos cursorPos = key != null ? CursorPos.traverseDown(page, key) : + reverse ? page.getAppendCursorPos(null) : page.getPrependCursorPos(null); + int index = cursorPos.index; + if (index < 0) { + index = ~index; + if (reverse) { + --index; + } + cursorPos.index = index; + } + return cursorPos; + } + + private static int upperBound(Page page) { + return page.isLeaf() ? page.getKeyCount() : page.map.getChildPageCount(page); + } +} diff --git a/h2/src/main/org/h2/mvstore/CursorPos.java b/h2/src/main/org/h2/mvstore/CursorPos.java new file mode 100644 index 0000000..15334bc --- /dev/null +++ b/h2/src/main/org/h2/mvstore/CursorPos.java @@ -0,0 +1,89 @@ +/* + * 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.mvstore; + +/** + * A position in a cursor. + * Instance represents a node in the linked list, which traces path + * from a specific (target) key within a leaf node all the way up to te root + * (bottom up path). + */ +public final class CursorPos { + + /** + * The page at the current level. + */ + public Page page; + + /** + * Index of the key (within page above) used to go down to a lower level + * in case of intermediate nodes, or index of the target key for leaf a node. + * In a later case, it could be negative, if the key is not present. + */ + public int index; + + /** + * Next node in the linked list, representing the position within parent level, + * or null, if we are at the root level already. + */ + public CursorPos parent; + + + public CursorPos(Page page, int index, CursorPos parent) { + this.page = page; + this.index = index; + this.parent = parent; + } + + /** + * Searches for a given key and creates a breadcrumb trail through a B-tree + * rooted at a given Page. Resulting path starts at "insertion point" for a + * given key and goes back to the root. + * + * @param key type + * @param value type + * + * @param page root of the tree + * @param key the key to search for + * @return head of the CursorPos chain (insertion point) + */ + static CursorPos traverseDown(Page page, K key) { + CursorPos cursorPos = null; + while (!page.isLeaf()) { + int index = page.binarySearch(key) + 1; + if (index < 0) { + index = -index; + } + cursorPos = new CursorPos<>(page, index, cursorPos); + page = page.getChildPage(index); + } + return new CursorPos<>(page, page.binarySearch(key), cursorPos); + } + + /** + * Calculate the memory used by changes that are not yet stored. + * + * @param version the version + * @return the amount of memory + */ + int processRemovalInfo(long version) { + int unsavedMemory = 0; + for (CursorPos head = this; head != null; head = head.parent) { + unsavedMemory += head.page.removePage(version); + } + return unsavedMemory; + } + + @Override + public String toString() { + return "CursorPos{" + + "page=" + page + + ", index=" + index + + ", parent=" + parent + + '}'; + } +} + diff --git a/h2/src/main/org/h2/mvstore/DataUtils.java b/h2/src/main/org/h2/mvstore/DataUtils.java new file mode 100644 index 0000000..c784b8c --- /dev/null +++ b/h2/src/main/org/h2/mvstore/DataUtils.java @@ -0,0 +1,1190 @@ +/* + * 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.mvstore; + +import java.io.EOFException; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.h2.engine.Constants; +import org.h2.jdbc.JdbcException; +import org.h2.util.StringUtils; + +/** + * Utility methods + */ +public final class DataUtils { + + /** + * An error occurred while reading from the file. + */ + public static final int ERROR_READING_FAILED = 1; + + /** + * An error occurred when trying to write to the file. + */ + public static final int ERROR_WRITING_FAILED = 2; + + /** + * An internal error occurred. This could be a bug, or a memory corruption + * (for example caused by out of memory). + */ + public static final int ERROR_INTERNAL = 3; + + /** + * The object is already closed. + */ + public static final int ERROR_CLOSED = 4; + + /** + * The file format is not supported. + */ + public static final int ERROR_UNSUPPORTED_FORMAT = 5; + + /** + * The file is corrupt or (for encrypted files) the encryption key is wrong. + */ + public static final int ERROR_FILE_CORRUPT = 6; + + /** + * The file is locked. + */ + public static final int ERROR_FILE_LOCKED = 7; + + /** + * An error occurred when serializing or de-serializing. + */ + public static final int ERROR_SERIALIZATION = 8; + + /** + * The application was trying to read data from a chunk that is no longer + * available. + */ + public static final int ERROR_CHUNK_NOT_FOUND = 9; + + /** + * The block in the stream store was not found. + */ + public static final int ERROR_BLOCK_NOT_FOUND = 50; + + /** + * The transaction store is corrupt. + */ + public static final int ERROR_TRANSACTION_CORRUPT = 100; + + /** + * An entry is still locked by another transaction. + */ + public static final int ERROR_TRANSACTION_LOCKED = 101; + + /** + * There are too many open transactions. + */ + public static final int ERROR_TOO_MANY_OPEN_TRANSACTIONS = 102; + + /** + * The transaction store is in an illegal state (for example, not yet + * initialized). + */ + public static final int ERROR_TRANSACTION_ILLEGAL_STATE = 103; + + /** + * The transaction contains too many changes. + */ + public static final int ERROR_TRANSACTION_TOO_BIG = 104; + + /** + * Deadlock discovered and one of transactions involved chosen as victim and rolled back. + */ + public static final int ERROR_TRANSACTIONS_DEADLOCK = 105; + + /** + * The transaction store can not be initialized because data type + * is not found in type registry. + */ + public static final int ERROR_UNKNOWN_DATA_TYPE = 106; + + /** + * The type for leaf page. + */ + public static final int PAGE_TYPE_LEAF = 0; + + /** + * The type for node page. + */ + public static final int PAGE_TYPE_NODE = 1; + + /** + * The bit mask for compressed pages (compression level fast). + */ + public static final int PAGE_COMPRESSED = 2; + + /** + * The bit mask for compressed pages (compression level high). + */ + public static final int PAGE_COMPRESSED_HIGH = 2 + 4; + + /** + * The bit mask for pages with page sequential number. + */ + public static final int PAGE_HAS_PAGE_NO = 8; + + /** + * The maximum length of a variable size int. + */ + public static final int MAX_VAR_INT_LEN = 5; + + /** + * The maximum length of a variable size long. + */ + public static final int MAX_VAR_LONG_LEN = 10; + + /** + * The maximum integer that needs less space when using variable size + * encoding (only 3 bytes instead of 4). + */ + public static final int COMPRESSED_VAR_INT_MAX = 0x1fffff; + + /** + * The maximum long that needs less space when using variable size + * encoding (only 7 bytes instead of 8). + */ + public static final long COMPRESSED_VAR_LONG_MAX = 0x1ffffffffffffL; + + /** + * The marker size of a very large page. + */ + public static final int PAGE_LARGE = 2 * 1024 * 1024; + + // The following are key prefixes used in layout map + + /** + * The prefix for chunks ("chunk."). This, plus the chunk id (hex encoded) + * is the key, and the serialized chunk metadata is the value. + */ + public static final String META_CHUNK = "chunk."; + + /** + * The prefix for root positions of maps ("root."). This, plus the map id + * (hex encoded) is the key, and the position (hex encoded) is the value. + */ + public static final String META_ROOT = "root."; + + // The following are key prefixes used in meta map + + /** + * The prefix for names ("name."). This, plus the name of the map, is the + * key, and the map id (hex encoded) is the value. + */ + public static final String META_NAME = "name."; + + /** + * The prefix for maps ("map."). This, plus the map id (hex encoded) is the + * key, and the serialized in the map metadata is the value. + */ + public static final String META_MAP = "map."; + + /** + * Get the length of the variable size int. + * + * @param x the value + * @return the length in bytes + */ + public static int getVarIntLen(int x) { + if ((x & (-1 << 7)) == 0) { + return 1; + } else if ((x & (-1 << 14)) == 0) { + return 2; + } else if ((x & (-1 << 21)) == 0) { + return 3; + } else if ((x & (-1 << 28)) == 0) { + return 4; + } + return 5; + } + + /** + * Get the length of the variable size long. + * + * @param x the value + * @return the length in bytes + */ + public static int getVarLongLen(long x) { + int i = 1; + while (true) { + x >>>= 7; + if (x == 0) { + return i; + } + i++; + } + } + + /** + * Read a variable size int. + * + * @param buff the source buffer + * @return the value + */ + public static int readVarInt(ByteBuffer buff) { + int b = buff.get(); + if (b >= 0) { + return b; + } + // a separate function so that this one can be inlined + return readVarIntRest(buff, b); + } + + private static int readVarIntRest(ByteBuffer buff, int b) { + int x = b & 0x7f; + b = buff.get(); + if (b >= 0) { + return x | (b << 7); + } + x |= (b & 0x7f) << 7; + b = buff.get(); + if (b >= 0) { + return x | (b << 14); + } + x |= (b & 0x7f) << 14; + b = buff.get(); + if (b >= 0) { + return x | b << 21; + } + x |= ((b & 0x7f) << 21) | (buff.get() << 28); + return x; + } + + /** + * Read a variable size long. + * + * @param buff the source buffer + * @return the value + */ + public static long readVarLong(ByteBuffer buff) { + long x = buff.get(); + if (x >= 0) { + return x; + } + x &= 0x7f; + for (int s = 7; s < 64; s += 7) { + long b = buff.get(); + x |= (b & 0x7f) << s; + if (b >= 0) { + break; + } + } + return x; + } + + /** + * Write a variable size int. + * + * @param out the output stream + * @param x the value + * @throws IOException if some data could not be written + */ + public static void writeVarInt(OutputStream out, int x) throws IOException { + while ((x & ~0x7f) != 0) { + out.write((byte) (x | 0x80)); + x >>>= 7; + } + out.write((byte) x); + } + + /** + * Write a variable size int. + * + * @param buff the source buffer + * @param x the value + */ + public static void writeVarInt(ByteBuffer buff, int x) { + while ((x & ~0x7f) != 0) { + buff.put((byte) (x | 0x80)); + x >>>= 7; + } + buff.put((byte) x); + } + + /** + * Write characters from a string (without the length). + * + * @param buff the target buffer (must be large enough) + * @param s the string + * @param len the number of characters + */ + public static void writeStringData(ByteBuffer buff, + String s, int len) { + for (int i = 0; i < len; i++) { + int c = s.charAt(i); + if (c < 0x80) { + buff.put((byte) c); + } else if (c >= 0x800) { + buff.put((byte) (0xe0 | (c >> 12))); + buff.put((byte) ((c >> 6) & 0x3f)); + buff.put((byte) (c & 0x3f)); + } else { + buff.put((byte) (0xc0 | (c >> 6))); + buff.put((byte) (c & 0x3f)); + } + } + } + + /** + * Read a string. + * + * @param buff the source buffer + * @return the value + */ + public static String readString(ByteBuffer buff) { + return readString(buff, readVarInt(buff)); + } + + /** + * Read a string. + * + * @param buff the source buffer + * @param len the number of characters + * @return the value + */ + public static String readString(ByteBuffer buff, int len) { + char[] chars = new char[len]; + for (int i = 0; i < len; i++) { + int x = buff.get() & 0xff; + if (x < 0x80) { + chars[i] = (char) x; + } else if (x >= 0xe0) { + chars[i] = (char) (((x & 0xf) << 12) + + ((buff.get() & 0x3f) << 6) + (buff.get() & 0x3f)); + } else { + chars[i] = (char) (((x & 0x1f) << 6) + (buff.get() & 0x3f)); + } + } + return new String(chars); + } + + /** + * Write a variable size long. + * + * @param buff the target buffer + * @param x the value + */ + public static void writeVarLong(ByteBuffer buff, long x) { + while ((x & ~0x7f) != 0) { + buff.put((byte) (x | 0x80)); + x >>>= 7; + } + buff.put((byte) x); + } + + /** + * Write a variable size long. + * + * @param out the output stream + * @param x the value + * @throws IOException if some data could not be written + */ + public static void writeVarLong(OutputStream out, long x) + throws IOException { + while ((x & ~0x7f) != 0) { + out.write((byte) (x | 0x80)); + x >>>= 7; + } + out.write((byte) x); + } + + /** + * Copy the elements of an array, with a gap. + * + * @param src the source array + * @param dst the target array + * @param oldSize the size of the old array + * @param gapIndex the index of the gap + */ + public static void copyWithGap(Object src, Object dst, int oldSize, + int gapIndex) { + if (gapIndex > 0) { + System.arraycopy(src, 0, dst, 0, gapIndex); + } + if (gapIndex < oldSize) { + System.arraycopy(src, gapIndex, dst, gapIndex + 1, oldSize + - gapIndex); + } + } + + /** + * Copy the elements of an array, and remove one element. + * + * @param src the source array + * @param dst the target array + * @param oldSize the size of the old array + * @param removeIndex the index of the entry to remove + */ + public static void copyExcept(Object src, Object dst, int oldSize, + int removeIndex) { + if (removeIndex > 0 && oldSize > 0) { + System.arraycopy(src, 0, dst, 0, removeIndex); + } + if (removeIndex < oldSize) { + System.arraycopy(src, removeIndex + 1, dst, removeIndex, oldSize + - removeIndex - 1); + } + } + + /** + * Read from a file channel until the buffer is full. + * The buffer is rewind after reading. + * + * @param file the file channel + * @param pos the absolute position within the file + * @param dst the byte buffer + * @throws MVStoreException if some data could not be read + */ + public static void readFully(FileChannel file, long pos, ByteBuffer dst) { + try { + do { + int len = file.read(dst, pos); + if (len < 0) { + throw new EOFException(); + } + pos += len; + } while (dst.remaining() > 0); + dst.rewind(); + } catch (IOException e) { + long size; + try { + size = file.size(); + } catch (IOException e2) { + size = -1; + } + throw newMVStoreException( + ERROR_READING_FAILED, + "Reading from file {0} failed at {1} (length {2}), " + + "read {3}, remaining {4}", + file, pos, size, dst.position(), dst.remaining(), e); + } + } + + /** + * Write to a file channel. + * + * @param file the file channel + * @param pos the absolute position within the file + * @param src the source buffer + */ + public static void writeFully(FileChannel file, long pos, ByteBuffer src) { + try { + int off = 0; + do { + int len = file.write(src, pos + off); + off += len; + } while (src.remaining() > 0); + } catch (IOException e) { + throw newMVStoreException( + ERROR_WRITING_FAILED, + "Writing to {0} failed; length {1} at {2}", + file, src.remaining(), pos, e); + } + } + + /** + * Convert the length to a length code 0..31. 31 means more than 1 MB. + * + * @param len the length + * @return the length code + */ + public static int encodeLength(int len) { + if (len <= 32) { + return 0; + } + int code = Integer.numberOfLeadingZeros(len); + int remaining = len << (code + 1); + code += code; + if ((remaining & (1 << 31)) != 0) { + code--; + } + if ((remaining << 1) != 0) { + code--; + } + code = Math.min(31, 52 - code); + // alternative code (slower): + // int x = len; + // int shift = 0; + // while (x > 3) { + // shift++; + // x = (x >>> 1) + (x & 1); + // } + // shift = Math.max(0, shift - 4); + // int code = (shift << 1) + (x & 1); + // code = Math.min(31, code); + return code; + } + + /** + * Get the chunk id from the position. + * + * @param pos the position + * @return the chunk id + */ + public static int getPageChunkId(long pos) { + return (int) (pos >>> 38); + } + + /** + * Get the map id from the chunk's table of content element. + * + * @param tocElement packed table of content element + * @return the map id + */ + public static int getPageMapId(long tocElement) { + return (int) (tocElement >>> 38); + } + + /** + * Get the maximum length for the given page position. + * + * @param pos the position + * @return the maximum length + */ + public static int getPageMaxLength(long pos) { + int code = (int) ((pos >> 1) & 31); + return decodePageLength(code); + } + + /** + * Get the maximum length for the given code. + * For the code 31, PAGE_LARGE is returned. + * + * @param code encoded page length + * @return the maximum length + */ + public static int decodePageLength(int code) { + if (code == 31) { + return PAGE_LARGE; + } + return (2 + (code & 1)) << ((code >> 1) + 4); + } + + /** + * Get the offset from the position. + * + * @param tocElement packed table of content element + * @return the offset + */ + public static int getPageOffset(long tocElement) { + return (int) (tocElement >> 6); + } + + /** + * Get the page type from the position. + * + * @param pos the position + * @return the page type (PAGE_TYPE_NODE or PAGE_TYPE_LEAF) + */ + public static int getPageType(long pos) { + return ((int) pos) & 1; + } + + /** + * Determines whether specified file position corresponds to a leaf page + * @param pos the position + * @return true if it is a leaf, false otherwise + */ + public static boolean isLeafPosition(long pos) { + return getPageType(pos) == PAGE_TYPE_LEAF; + } + + /** + * Find out if page was saved. + * + * @param pos the position + * @return true if page has been saved + */ + public static boolean isPageSaved(long pos) { + return (pos & ~1L) != 0; + } + + /** + * Find out if page was removed. + * + * @param pos the position + * @return true if page has been removed (no longer accessible from the + * current root of the tree) + */ + static boolean isPageRemoved(long pos) { + return pos == 1L; + } + + /** + * Get the position of this page. The following information is encoded in + * the position: the chunk id, the page sequential number, the maximum length, and the type + * (node or leaf). + * + * @param chunkId the chunk id + * @param offset the offset + * @param length the length + * @param type the page type (1 for node, 0 for leaf) + * @return the position + */ + public static long getPagePos(int chunkId, int offset, int length, int type) { + long pos = (long) chunkId << 38; + pos |= (long) offset << 6; + pos |= encodeLength(length) << 1; + pos |= type; + return pos; + } + + /** + * Convert tocElement into pagePos by replacing mapId with chunkId. + * + * @param chunkId the chunk id + * @param tocElement the element + * @return the page position + */ + public static long getPagePos(int chunkId, long tocElement) { + return (tocElement & 0x3FFFFFFFFFL) | ((long) chunkId << 38); + } + + /** + * Create table of content element. The following information is encoded in it: + * the map id, the page offset, the maximum length, and the type + * (node or leaf). + * + * @param mapId the chunk id + * @param offset the offset + * @param length the length + * @param type the page type (1 for node, 0 for leaf) + * @return the position + */ + public static long getTocElement(int mapId, int offset, int length, int type) { + long pos = (long) mapId << 38; + pos |= (long) offset << 6; + pos |= encodeLength(length) << 1; + pos |= type; + return pos; + } + + /** + * Calculate a check value for the given integer. A check value is mean to + * verify the data is consistent with a high probability, but not meant to + * protect against media failure or deliberate changes. + * + * @param x the value + * @return the check value + */ + public static short getCheckValue(int x) { + return (short) ((x >> 16) ^ x); + } + + /** + * Append a map to the string builder, sorted by key. + * + * @param buff the target buffer + * @param map the map + * @return the string builder + */ + public static StringBuilder appendMap(StringBuilder buff, HashMap map) { + Object[] keys = map.keySet().toArray(); + Arrays.sort(keys); + for (Object k : keys) { + String key = (String) k; + Object value = map.get(key); + if (value instanceof Long) { + appendMap(buff, key, (long) value); + } else if (value instanceof Integer) { + appendMap(buff, key, (int) value); + } else { + appendMap(buff, key, value.toString()); + } + } + return buff; + } + + private static StringBuilder appendMapKey(StringBuilder buff, String key) { + if (buff.length() > 0) { + buff.append(','); + } + return buff.append(key).append(':'); + } + + /** + * Append a key-value pair to the string builder. Keys may not contain a + * colon. Values that contain a comma or a double quote are enclosed in + * double quotes, with special characters escaped using a backslash. + * + * @param buff the target buffer + * @param key the key + * @param value the value + */ + public static void appendMap(StringBuilder buff, String key, String value) { + appendMapKey(buff, key); + if (value.indexOf(',') < 0 && value.indexOf('\"') < 0) { + buff.append(value); + } else { + buff.append('\"'); + for (int i = 0, size = value.length(); i < size; i++) { + char c = value.charAt(i); + if (c == '\"') { + buff.append('\\'); + } + buff.append(c); + } + buff.append('\"'); + } + } + + /** + * Append a key-value pair to the string builder. Keys may not contain a + * colon. + * + * @param buff the target buffer + * @param key the key + * @param value the value + */ + public static void appendMap(StringBuilder buff, String key, long value) { + appendMapKey(buff, key).append(Long.toHexString(value)); + } + + /** + * Append a key-value pair to the string builder. Keys may not contain a + * colon. + * + * @param buff the target buffer + * @param key the key + * @param value the value + */ + public static void appendMap(StringBuilder buff, String key, int value) { + appendMapKey(buff, key).append(Integer.toHexString(value)); + } + + /** + * @param buff output buffer, should be empty + * @param s parsed string + * @param i offset to parse from + * @param size stop offset (exclusive) + * @return new offset + */ + private static int parseMapValue(StringBuilder buff, String s, int i, int size) { + while (i < size) { + char c = s.charAt(i++); + if (c == ',') { + break; + } else if (c == '\"') { + while (i < size) { + c = s.charAt(i++); + if (c == '\\') { + if (i == size) { + throw newMVStoreException(ERROR_FILE_CORRUPT, "Not a map: {0}", s); + } + c = s.charAt(i++); + } else if (c == '\"') { + break; + } + buff.append(c); + } + } else { + buff.append(c); + } + } + return i; + } + + /** + * Parse a key-value pair list. + * + * @param s the list + * @return the map + * @throws MVStoreException if parsing failed + */ + public static HashMap parseMap(String s) { + HashMap map = new HashMap<>(); + StringBuilder buff = new StringBuilder(); + for (int i = 0, size = s.length(); i < size;) { + int startKey = i; + i = s.indexOf(':', i); + if (i < 0) { + throw newMVStoreException(ERROR_FILE_CORRUPT, "Not a map: {0}", s); + } + String key = s.substring(startKey, i++); + i = parseMapValue(buff, s, i, size); + map.put(key, buff.toString()); + buff.setLength(0); + } + return map; + } + + /** + * Parse a key-value pair list and checks its checksum. + * + * @param bytes encoded map + * @return the map without mapping for {@code "fletcher"}, or {@code null} if checksum is wrong + * or parameter do not represent a properly formatted map serialization + */ + static HashMap parseChecksummedMap(byte[] bytes) { + int start = 0, end = bytes.length; + while (start < end && bytes[start] <= ' ') { + start++; + } + while (start < end && bytes[end - 1] <= ' ') { + end--; + } + String s = new String(bytes, start, end - start, StandardCharsets.ISO_8859_1); + HashMap map = new HashMap<>(); + StringBuilder buff = new StringBuilder(); + for (int i = 0, size = s.length(); i < size;) { + int startKey = i; + i = s.indexOf(':', i); + if (i < 0) { + // Corrupted map + return null; + } + if (i - startKey == 8 && s.regionMatches(startKey, "fletcher", 0, 8)) { + parseMapValue(buff, s, i + 1, size); + int check = (int) Long.parseLong(buff.toString(), 16); + if (check == getFletcher32(bytes, start, startKey - 1)) { + return map; + } + // Corrupted map + return null; + } + String key = s.substring(startKey, i++); + i = parseMapValue(buff, s, i, size); + map.put(key, buff.toString()); + buff.setLength(0); + } + // Corrupted map + return null; + } + + /** + * Parse a name from key-value pair list. + * + * @param s the list + * @return value of name item, or {@code null} + * @throws MVStoreException if parsing failed + */ + public static String getMapName(String s) { + return getFromMap(s, "name"); + } + + /** + * Parse a specified pair from key-value pair list. + * + * @param s the list + * @param key the name of the key + * @return value of the specified item, or {@code null} + * @throws MVStoreException if parsing failed + */ + public static String getFromMap(String s, String key) { + int keyLength = key.length(); + for (int i = 0, size = s.length(); i < size;) { + int startKey = i; + i = s.indexOf(':', i); + if (i < 0) { + throw newMVStoreException(ERROR_FILE_CORRUPT, "Not a map: {0}", s); + } + if (i++ - startKey == keyLength && s.regionMatches(startKey, key, 0, keyLength)) { + StringBuilder buff = new StringBuilder(); + parseMapValue(buff, s, i, size); + return buff.toString(); + } else { + while (i < size) { + char c = s.charAt(i++); + if (c == ',') { + break; + } else if (c == '\"') { + while (i < size) { + c = s.charAt(i++); + if (c == '\\') { + if (i++ == size) { + throw newMVStoreException(ERROR_FILE_CORRUPT, "Not a map: {0}", s); + } + } else if (c == '\"') { + break; + } + } + } + } + } + } + return null; + } + + /** + * Calculate the Fletcher32 checksum. + * + * @param bytes the bytes + * @param offset initial offset + * @param length the message length (if odd, 0 is appended) + * @return the checksum + */ + public static int getFletcher32(byte[] bytes, int offset, int length) { + int s1 = 0xffff, s2 = 0xffff; + int i = offset, len = offset + (length & ~1); + while (i < len) { + // reduce after 360 words (each word is two bytes) + for (int end = Math.min(i + 720, len); i < end;) { + int x = ((bytes[i++] & 0xff) << 8) | (bytes[i++] & 0xff); + s2 += s1 += x; + } + s1 = (s1 & 0xffff) + (s1 >>> 16); + s2 = (s2 & 0xffff) + (s2 >>> 16); + } + if ((length & 1) != 0) { + // odd length: append 0 + int x = (bytes[i] & 0xff) << 8; + s2 += s1 += x; + } + s1 = (s1 & 0xffff) + (s1 >>> 16); + s2 = (s2 & 0xffff) + (s2 >>> 16); + return (s2 << 16) | s1; + } + + /** + * Throw an IllegalArgumentException if the argument is invalid. + * + * @param test true if the argument is valid + * @param message the message + * @param arguments the arguments + * @throws IllegalArgumentException if the argument is invalid + */ + public static void checkArgument(boolean test, String message, + Object... arguments) { + if (!test) { + throw newIllegalArgumentException(message, arguments); + } + } + + /** + * Create a new IllegalArgumentException. + * + * @param message the message + * @param arguments the arguments + * @return the exception + */ + public static IllegalArgumentException newIllegalArgumentException( + String message, Object... arguments) { + return initCause(new IllegalArgumentException( + formatMessage(0, message, arguments)), + arguments); + } + + /** + * Create a new UnsupportedOperationException. + * + * @param message the message + * @return the exception + */ + public static UnsupportedOperationException + newUnsupportedOperationException(String message) { + return new UnsupportedOperationException(formatMessage(0, message)); + } + + /** + * Create a new MVStoreException. + * + * @param errorCode the error code + * @param message the message + * @param arguments the arguments + * @return the exception + */ + public static MVStoreException newMVStoreException( + int errorCode, String message, Object... arguments) { + return initCause(new MVStoreException(errorCode, + formatMessage(errorCode, message, arguments)), + arguments); + } + + private static T initCause(T e, Object... arguments) { + int size = arguments.length; + if (size > 0) { + Object o = arguments[size - 1]; + if (o instanceof Throwable) { + e.initCause((Throwable) o); + } + } + return e; + } + + /** + * Format an error message. + * + * @param errorCode the error code + * @param message the message + * @param arguments the arguments + * @return the formatted message + */ + public static String formatMessage(int errorCode, String message, + Object... arguments) { + // convert arguments to strings, to avoid locale specific formatting + arguments = arguments.clone(); + for (int i = 0; i < arguments.length; i++) { + Object a = arguments[i]; + if (!(a instanceof Exception)) { + String s = a == null ? "null" : a.toString(); + if (s.length() > 1000) { + s = s.substring(0, 1000) + "..."; + } + arguments[i] = s; + } + } + return MessageFormat.format(message, arguments) + + " [" + Constants.VERSION_MAJOR + "." + + Constants.VERSION_MINOR + "." + Constants.BUILD_ID + + "/" + errorCode + "]"; + } + + /** + * Read a hex long value from a map. + * + * @param map the map + * @param key the key + * @param defaultValue if the value is null + * @return the parsed value + * @throws MVStoreException if parsing fails + */ + public static long readHexLong(Map map, String key, long defaultValue) { + Object v = map.get(key); + if (v == null) { + return defaultValue; + } else if (v instanceof Long) { + return (Long) v; + } + try { + return parseHexLong((String) v); + } catch (NumberFormatException e) { + throw newMVStoreException(ERROR_FILE_CORRUPT, + "Error parsing the value {0}", v, e); + } + } + + /** + * Parse an unsigned, hex long. + * + * @param x the string + * @return the parsed value + * @throws MVStoreException if parsing fails + */ + public static long parseHexLong(String x) { + try { + if (x.length() == 16) { + // avoid problems with overflow + // in Java 8, this special case is not needed + return (Long.parseLong(x.substring(0, 8), 16) << 32) | + Long.parseLong(x.substring(8, 16), 16); + } + return Long.parseLong(x, 16); + } catch (NumberFormatException e) { + throw newMVStoreException(ERROR_FILE_CORRUPT, + "Error parsing the value {0}", x, e); + } + } + + /** + * Parse an unsigned, hex long. + * + * @param x the string + * @return the parsed value + * @throws MVStoreException if parsing fails + */ + public static int parseHexInt(String x) { + try { + // avoid problems with overflow + // in Java 8, we can use Integer.parseLong(x, 16); + return (int) Long.parseLong(x, 16); + } catch (NumberFormatException e) { + throw newMVStoreException(ERROR_FILE_CORRUPT, + "Error parsing the value {0}", x, e); + } + } + + /** + * Read a hex int value from a map. + * + * @param map the map + * @param key the key + * @param defaultValue if the value is null + * @return the parsed value + * @throws MVStoreException if parsing fails + */ + static int readHexInt(Map map, String key, int defaultValue) { + Object v = map.get(key); + if (v == null) { + return defaultValue; + } else if (v instanceof Integer) { + return (Integer) v; + } + try { + // support unsigned hex value + return (int) Long.parseLong((String) v, 16); + } catch (NumberFormatException e) { + throw newMVStoreException(ERROR_FILE_CORRUPT, + "Error parsing the value {0}", v, e); + } + } + + /** + * Parse the hex-encoded bytes of an entry in the map. + * + * @param map the map + * @param key the key + * @return the byte array, or null if not in the map + */ + static byte[] parseHexBytes(Map map, String key) { + Object v = map.get(key); + if (v == null) { + return null; + } + return StringUtils.convertHexToBytes((String)v); + } + + /** + * Get the configuration parameter value, or default. + * + * @param config the configuration + * @param key the key + * @param defaultValue the default + * @return the configured value or default + */ + static int getConfigParam(Map config, String key, int defaultValue) { + Object o = config.get(key); + if (o instanceof Number) { + return ((Number) o).intValue(); + } else if (o != null) { + try { + return Integer.decode(o.toString()); + } catch (NumberFormatException e) { + // ignore + } + } + return defaultValue; + } + + /** + * Convert an exception to an IO exception. + * + * @param e the root cause + * @return the IO exception + */ + public static IOException convertToIOException(Throwable e) { + if (e instanceof IOException) { + return (IOException) e; + } + if (e instanceof JdbcException) { + if (e.getCause() != null) { + e = e.getCause(); + } + } + return new IOException(e.toString(), e); + } +} diff --git a/h2/src/main/org/h2/mvstore/FileStore.java b/h2/src/main/org/h2/mvstore/FileStore.java new file mode 100644 index 0000000..dc1142f --- /dev/null +++ b/h2/src/main/org/h2/mvstore/FileStore.java @@ -0,0 +1,427 @@ +/* + * 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.mvstore; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.OverlappingFileLockException; +import java.util.concurrent.atomic.AtomicLong; +import org.h2.mvstore.cache.FilePathCache; +import org.h2.store.fs.FilePath; +import org.h2.store.fs.encrypt.FileEncrypt; +import org.h2.store.fs.encrypt.FilePathEncrypt; + +/** + * The default storage mechanism of the MVStore. This implementation persists + * data to a file. The file store is responsible to persist data and for free + * space management. + */ +public class FileStore { + + /** + * The number of read operations. + */ + protected final AtomicLong readCount = new AtomicLong(); + + /** + * The number of read bytes. + */ + protected final AtomicLong readBytes = new AtomicLong(); + + /** + * The number of write operations. + */ + protected final AtomicLong writeCount = new AtomicLong(); + + /** + * The number of written bytes. + */ + protected final AtomicLong writeBytes = new AtomicLong(); + + /** + * The free spaces between the chunks. The first block to use is block 2 + * (the first two blocks are the store header). + */ + protected final FreeSpaceBitSet freeSpace = + new FreeSpaceBitSet(2, MVStore.BLOCK_SIZE); + + /** + * The file name. + */ + private String fileName; + + /** + * Whether this store is read-only. + */ + private boolean readOnly; + + /** + * The file size (cached). + */ + protected long fileSize; + + /** + * The file. + */ + private FileChannel file; + + /** + * The encrypted file (if encryption is used). + */ + private FileChannel encryptedFile; + + /** + * The file lock. + */ + private FileLock fileLock; + + @Override + public String toString() { + return fileName; + } + + /** + * Read from the file. + * + * @param pos the write position + * @param len the number of bytes to read + * @return the byte buffer + */ + public ByteBuffer readFully(long pos, int len) { + ByteBuffer dst = ByteBuffer.allocate(len); + DataUtils.readFully(file, pos, dst); + readCount.incrementAndGet(); + readBytes.addAndGet(len); + return dst; + } + + /** + * Write to the file. + * + * @param pos the write position + * @param src the source buffer + */ + public void writeFully(long pos, ByteBuffer src) { + int len = src.remaining(); + fileSize = Math.max(fileSize, pos + len); + DataUtils.writeFully(file, pos, src); + writeCount.incrementAndGet(); + writeBytes.addAndGet(len); + } + + /** + * Try to open the file. + * + * @param fileName the file name + * @param readOnly whether the file should only be opened in read-only mode, + * even if the file is writable + * @param encryptionKey the encryption key, or null if encryption is not + * used + */ + public void open(String fileName, boolean readOnly, char[] encryptionKey) { + if (file != null) { + return; + } + // ensure the Cache file system is registered + FilePathCache.INSTANCE.getScheme(); + this.fileName = fileName; + FilePath f = FilePath.get(fileName); + FilePath parent = f.getParent(); + if (parent != null && !parent.exists()) { + throw DataUtils.newIllegalArgumentException( + "Directory does not exist: {0}", parent); + } + if (f.exists() && !f.canWrite()) { + readOnly = true; + } + this.readOnly = readOnly; + try { + file = f.open(readOnly ? "r" : "rw"); + if (encryptionKey != null) { + byte[] key = FilePathEncrypt.getPasswordBytes(encryptionKey); + encryptedFile = file; + file = new FileEncrypt(fileName, key, file); + } + try { + if (readOnly) { + fileLock = file.tryLock(0, Long.MAX_VALUE, true); + } else { + fileLock = file.tryLock(); + } + } catch (OverlappingFileLockException e) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_FILE_LOCKED, + "The file is locked: {0}", fileName, e); + } + if (fileLock == null) { + try { close(); } catch (Exception ignore) {} + throw DataUtils.newMVStoreException( + DataUtils.ERROR_FILE_LOCKED, + "The file is locked: {0}", fileName); + } + fileSize = file.size(); + } catch (IOException e) { + try { close(); } catch (Exception ignore) {} + throw DataUtils.newMVStoreException( + DataUtils.ERROR_READING_FAILED, + "Could not open file {0}", fileName, e); + } + } + + /** + * Close this store. + */ + public void close() { + try { + if(file != null && file.isOpen()) { + if (fileLock != null) { + fileLock.release(); + } + file.close(); + } + } catch (Exception e) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_WRITING_FAILED, + "Closing failed for file {0}", fileName, e); + } finally { + fileLock = null; + file = null; + } + } + + /** + * Flush all changes. + */ + public void sync() { + if (file != null) { + try { + file.force(true); + } catch (IOException e) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_WRITING_FAILED, + "Could not sync file {0}", fileName, e); + } + } + } + + /** + * Get the file size. + * + * @return the file size + */ + public long size() { + return fileSize; + } + + /** + * Truncate the file. + * + * @param size the new file size + */ + public void truncate(long size) { + int attemptCount = 0; + while (true) { + try { + writeCount.incrementAndGet(); + file.truncate(size); + fileSize = Math.min(fileSize, size); + return; + } catch (IOException e) { + if (++attemptCount == 10) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_WRITING_FAILED, + "Could not truncate file {0} to size {1}", + fileName, size, e); + } + System.gc(); + Thread.yield(); + } + } + } + + /** + * Get the file instance in use. + *

+ * The application may read from the file (for example for online backup), + * but not write to it or truncate it. + * + * @return the file + */ + public FileChannel getFile() { + return file; + } + + /** + * Get the encrypted file instance, if encryption is used. + *

+ * The application may read from the file (for example for online backup), + * but not write to it or truncate it. + * + * @return the encrypted file, or null if encryption is not used + */ + public FileChannel getEncryptedFile() { + return encryptedFile; + } + + /** + * Get the number of write operations since this store was opened. + * For file based stores, this is the number of file write operations. + * + * @return the number of write operations + */ + public long getWriteCount() { + return writeCount.get(); + } + + /** + * Get the number of written bytes since this store was opened. + * + * @return the number of write operations + */ + public long getWriteBytes() { + return writeBytes.get(); + } + + /** + * Get the number of read operations since this store was opened. + * For file based stores, this is the number of file read operations. + * + * @return the number of read operations + */ + public long getReadCount() { + return readCount.get(); + } + + /** + * Get the number of read bytes since this store was opened. + * + * @return the number of write operations + */ + public long getReadBytes() { + return readBytes.get(); + } + + public boolean isReadOnly() { + return readOnly; + } + + /** + * Get the default retention time for this store in milliseconds. + * + * @return the retention time + */ + public int getDefaultRetentionTime() { + return 45_000; + } + + /** + * Mark the space as in use. + * + * @param pos the position in bytes + * @param length the number of bytes + */ + public void markUsed(long pos, int length) { + freeSpace.markUsed(pos, length); + } + + /** + * Allocate a number of blocks and mark them as used. + * + * @param length the number of bytes to allocate + * @param reservedLow start block index of the reserved area (inclusive) + * @param reservedHigh end block index of the reserved area (exclusive), + * special value -1 means beginning of the infinite free area + * @return the start position in bytes + */ + long allocate(int length, long reservedLow, long reservedHigh) { + return freeSpace.allocate(length, reservedLow, reservedHigh); + } + + /** + * Calculate starting position of the prospective allocation. + * + * @param blocks the number of blocks to allocate + * @param reservedLow start block index of the reserved area (inclusive) + * @param reservedHigh end block index of the reserved area (exclusive), + * special value -1 means beginning of the infinite free area + * @return the starting block index + */ + long predictAllocation(int blocks, long reservedLow, long reservedHigh) { + return freeSpace.predictAllocation(blocks, reservedLow, reservedHigh); + } + + boolean isFragmented() { + return freeSpace.isFragmented(); + } + + /** + * Mark the space as free. + * + * @param pos the position in bytes + * @param length the number of bytes + */ + public void free(long pos, int length) { + freeSpace.free(pos, length); + } + + public int getFillRate() { + return freeSpace.getFillRate(); + } + + /** + * Calculates a prospective fill rate, which store would have after rewrite + * of sparsely populated chunk(s) and evacuation of still live data into a + * new chunk. + * + * @param vacatedBlocks + * number of blocks vacated + * @return prospective fill rate (0 - 100) + */ + public int getProjectedFillRate(int vacatedBlocks) { + return freeSpace.getProjectedFillRate(vacatedBlocks); + } + + long getFirstFree() { + return freeSpace.getFirstFree(); + } + + long getFileLengthInUse() { + return freeSpace.getLastFree(); + } + + /** + * Calculates relative "priority" for chunk to be moved. + * + * @param block where chunk starts + * @return priority, bigger number indicate that chunk need to be moved sooner + */ + int getMovePriority(int block) { + return freeSpace.getMovePriority(block); + } + + long getAfterLastBlock() { + return freeSpace.getAfterLastBlock(); + } + + /** + * Mark the file as empty. + */ + public void clear() { + freeSpace.clear(); + } + + /** + * Get the file name. + * + * @return the file name + */ + public String getFileName() { + return fileName; + } + +} diff --git a/h2/src/main/org/h2/mvstore/FreeSpaceBitSet.java b/h2/src/main/org/h2/mvstore/FreeSpaceBitSet.java new file mode 100644 index 0000000..f302283 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/FreeSpaceBitSet.java @@ -0,0 +1,351 @@ +/* + * 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.mvstore; + +import java.util.BitSet; + +import org.h2.util.MathUtils; + +/** + * A free space bit set. + */ +public class FreeSpaceBitSet { + + private static final boolean DETAILED_INFO = false; + + /** + * The first usable block. + */ + private final int firstFreeBlock; + + /** + * The block size in bytes. + */ + private final int blockSize; + + /** + * The bit set. + */ + private final BitSet set = new BitSet(); + + /** + * Left-shifting register, which holds outcomes of recent allocations. Only + * allocations done in "reuseSpace" mode are recorded here. For example, + * rightmost bit set to 1 means that last allocation failed to find a hole + * big enough, and next bit set to 0 means that previous allocation request + * have found one. + */ + private int failureFlags; + + + /** + * Create a new free space map. + * + * @param firstFreeBlock the first free block + * @param blockSize the block size + */ + public FreeSpaceBitSet(int firstFreeBlock, int blockSize) { + this.firstFreeBlock = firstFreeBlock; + this.blockSize = blockSize; + clear(); + } + + /** + * Reset the list. + */ + public void clear() { + set.clear(); + set.set(0, firstFreeBlock); + } + + /** + * Check whether one of the blocks is in use. + * + * @param pos the position in bytes + * @param length the number of bytes + * @return true if a block is in use + */ + public boolean isUsed(long pos, int length) { + int start = getBlock(pos); + int blocks = getBlockCount(length); + for (int i = start; i < start + blocks; i++) { + if (!set.get(i)) { + return false; + } + } + return true; + } + + /** + * Check whether one of the blocks is free. + * + * @param pos the position in bytes + * @param length the number of bytes + * @return true if a block is free + */ + public boolean isFree(long pos, int length) { + int start = getBlock(pos); + int blocks = getBlockCount(length); + for (int i = start; i < start + blocks; i++) { + if (set.get(i)) { + return false; + } + } + return true; + } + + /** + * Allocate a number of blocks and mark them as used. + * + * @param length the number of bytes to allocate + * @return the start position in bytes + */ + public long allocate(int length) { + return allocate(length, 0, 0); + } + + /** + * Allocate a number of blocks and mark them as used. + * + * @param length the number of bytes to allocate + * @param reservedLow start block index of the reserved area (inclusive) + * @param reservedHigh end block index of the reserved area (exclusive), + * special value -1 means beginning of the infinite free area + * @return the start position in bytes + */ + long allocate(int length, long reservedLow, long reservedHigh) { + return getPos(allocate(getBlockCount(length), (int)reservedLow, (int)reservedHigh, true)); + } + + /** + * Calculate starting position of the prospective allocation. + * + * @param blocks the number of blocks to allocate + * @param reservedLow start block index of the reserved area (inclusive) + * @param reservedHigh end block index of the reserved area (exclusive), + * special value -1 means beginning of the infinite free area + * @return the starting block index + */ + long predictAllocation(int blocks, long reservedLow, long reservedHigh) { + return allocate(blocks, (int)reservedLow, (int)reservedHigh, false); + } + + boolean isFragmented() { + return Integer.bitCount(failureFlags & 0x0F) > 1; + } + + private int allocate(int blocks, int reservedLow, int reservedHigh, boolean allocate) { + int freeBlocksTotal = 0; + for (int i = 0;;) { + int start = set.nextClearBit(i); + int end = set.nextSetBit(start + 1); + int freeBlocks = end - start; + if (end < 0 || freeBlocks >= blocks) { + if ((reservedHigh < 0 || start < reservedHigh) && start + blocks > reservedLow) { // overlap detected + if (reservedHigh < 0) { + start = getAfterLastBlock(); + end = -1; + } else { + i = reservedHigh; + continue; + } + } + assert set.nextSetBit(start) == -1 || set.nextSetBit(start) >= start + blocks : + "Double alloc: " + Integer.toHexString(start) + "/" + Integer.toHexString(blocks) + " " + this; + if (allocate) { + set.set(start, start + blocks); + } else { + failureFlags <<= 1; + if (end < 0 && freeBlocksTotal > 4 * blocks) { + failureFlags |= 1; + } + } + return start; + } + freeBlocksTotal += freeBlocks; + i = end; + } + } + + /** + * Mark the space as in use. + * + * @param pos the position in bytes + * @param length the number of bytes + */ + public void markUsed(long pos, int length) { + int start = getBlock(pos); + int blocks = getBlockCount(length); + // this is not an assert because we get called during file opening + if (set.nextSetBit(start) != -1 && set.nextSetBit(start) < start + blocks ) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_FILE_CORRUPT, + "Double mark: " + Integer.toHexString(start) + + "/" + Integer.toHexString(blocks) + " " + this); + } + set.set(start, start + blocks); + } + + /** + * Mark the space as free. + * + * @param pos the position in bytes + * @param length the number of bytes + */ + public void free(long pos, int length) { + int start = getBlock(pos); + int blocks = getBlockCount(length); + assert set.nextClearBit(start) >= start + blocks : + "Double free: " + Integer.toHexString(start) + "/" + Integer.toHexString(blocks) + " " + this; + set.clear(start, start + blocks); + } + + private long getPos(int block) { + return (long) block * (long) blockSize; + } + + private int getBlock(long pos) { + return (int) (pos / blockSize); + } + + private int getBlockCount(int length) { + return MathUtils.roundUpInt(length, blockSize) / blockSize; + } + + /** + * Get the fill rate of the space in percent. The value 0 means the space is + * completely free, and 100 means it is completely full. + * + * @return the fill rate (0 - 100) + */ + int getFillRate() { + return getProjectedFillRate(0); + } + + /** + * Calculates a prospective fill rate, which store would have after rewrite + * of sparsely populated chunk(s) and evacuation of still live data into a + * new chunk. + * + * @param vacatedBlocks + * number of blocks vacated as a result of live data evacuation less + * number of blocks in prospective chunk with evacuated live data + * @return prospective fill rate (0 - 100) + */ + int getProjectedFillRate(int vacatedBlocks) { + // it's not bullet-proof against race condition but should be good enough + // to get approximation without holding a store lock + int usedBlocks; + int totalBlocks; + // to prevent infinite loop, which I saw once + int cnt = 3; + do { + if (--cnt == 0) { + return 100; + } + totalBlocks = set.length(); + usedBlocks = set.cardinality(); + } while (totalBlocks != set.length() || usedBlocks > totalBlocks); + usedBlocks -= firstFreeBlock + vacatedBlocks; + totalBlocks -= firstFreeBlock; + return usedBlocks == 0 ? 0 : (int)((100L * usedBlocks + totalBlocks - 1) / totalBlocks); + } + + /** + * Get the position of the first free space. + * + * @return the position. + */ + long getFirstFree() { + return getPos(set.nextClearBit(0)); + } + + /** + * Get the position of the last (infinite) free space. + * + * @return the position. + */ + long getLastFree() { + return getPos(getAfterLastBlock()); + } + + /** + * Get the index of the first block after last occupied one. + * It marks the beginning of the last (infinite) free space. + * + * @return block index + */ + int getAfterLastBlock() { + return set.previousSetBit(set.size() - 1) + 1; + } + + /** + * Calculates relative "priority" for chunk to be moved. + * + * @param block where chunk starts + * @return priority, bigger number indicate that chunk need to be moved sooner + */ + int getMovePriority(int block) { + // The most desirable chunks to move are the ones sitting within + // a relatively short span of occupied blocks which is surrounded + // from both sides by relatively long free spans + int prevEnd = set.previousClearBit(block); + int freeSize; + if (prevEnd < 0) { + prevEnd = firstFreeBlock; + freeSize = 0; + } else { + freeSize = prevEnd - set.previousSetBit(prevEnd); + } + + int nextStart = set.nextClearBit(block); + int nextEnd = set.nextSetBit(nextStart); + if (nextEnd >= 0) { + freeSize += nextEnd - nextStart; + } + return (nextStart - prevEnd - 1) * 1000 / (freeSize + 1); + } + + @Override + public String toString() { + StringBuilder buff = new StringBuilder(); + if (DETAILED_INFO) { + int onCount = 0, offCount = 0; + int on = 0; + for (int i = 0; i < set.length(); i++) { + if (set.get(i)) { + onCount++; + on++; + } else { + offCount++; + } + if ((i & 1023) == 1023) { + buff.append(String.format("%3x", on)).append(' '); + on = 0; + } + } + buff.append('\n') + .append(" on ").append(onCount).append(" off ").append(offCount) + .append(' ').append(100 * onCount / (onCount+offCount)).append("% used "); + } + buff.append('['); + for (int i = 0;;) { + if (i > 0) { + buff.append(", "); + } + int start = set.nextClearBit(i); + buff.append(Integer.toHexString(start)).append('-'); + int end = set.nextSetBit(start + 1); + if (end < 0) { + break; + } + buff.append(Integer.toHexString(end - 1)); + i = end + 1; + } + buff.append(']'); + return buff.toString(); + } +} \ No newline at end of file diff --git a/h2/src/main/org/h2/mvstore/MVMap.java b/h2/src/main/org/h2/mvstore/MVMap.java new file mode 100644 index 0000000..89dd588 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/MVMap.java @@ -0,0 +1,2125 @@ +/* + * 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.mvstore; + +import static org.h2.engine.Constants.MEMORY_POINTER; + +import java.util.AbstractList; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +import org.h2.mvstore.type.DataType; +import org.h2.mvstore.type.ObjectDataType; +import org.h2.util.MemoryEstimator; + +/** + * A stored map. + *

+ * All read and write operations can happen concurrently with all other + * operations, without risk of corruption. + * + * @param the key class + * @param the value class + */ +public class MVMap extends AbstractMap implements ConcurrentMap { + + /** + * The store. + */ + public final MVStore store; + + /** + * Reference to the current root page. + */ + private final AtomicReference> root; + + private final int id; + private final long createVersion; + private final DataType keyType; + private final DataType valueType; + private final int keysPerPage; + private final boolean singleWriter; + private final K[] keysBuffer; + private final V[] valuesBuffer; + + private final Object lock = new Object(); + private volatile boolean notificationRequested; + + /** + * Whether the map is closed. Volatile so we don't accidentally write to a + * closed map in multithreaded mode. + */ + private volatile boolean closed; + private boolean readOnly; + private boolean isVolatile; + private final AtomicLong avgKeySize; + private final AtomicLong avgValSize; + + /** + * This designates the "last stored" version for a store which was + * just open for the first time. + */ + static final long INITIAL_VERSION = -1; + + + protected MVMap(Map config, DataType keyType, DataType valueType) { + this((MVStore) config.get("store"), keyType, valueType, + DataUtils.readHexInt(config, "id", 0), + DataUtils.readHexLong(config, "createVersion", 0), + new AtomicReference<>(), + ((MVStore) config.get("store")).getKeysPerPage(), + config.containsKey("singleWriter") && (Boolean) config.get("singleWriter") + ); + setInitialRoot(createEmptyLeaf(), store.getCurrentVersion()); + } + + // constructor for cloneIt() + @SuppressWarnings("CopyConstructorMissesField") + protected MVMap(MVMap source) { + this(source.store, source.keyType, source.valueType, source.id, source.createVersion, + new AtomicReference<>(source.root.get()), source.keysPerPage, source.singleWriter); + } + + // meta map constructor + MVMap(MVStore store, int id, DataType keyType, DataType valueType) { + this(store, keyType, valueType, id, 0, new AtomicReference<>(), store.getKeysPerPage(), false); + setInitialRoot(createEmptyLeaf(), store.getCurrentVersion()); + } + + private MVMap(MVStore store, DataType keyType, DataType valueType, int id, long createVersion, + AtomicReference> root, int keysPerPage, boolean singleWriter) { + this.store = store; + this.id = id; + this.createVersion = createVersion; + this.keyType = keyType; + this.valueType = valueType; + this.root = root; + this.keysPerPage = keysPerPage; + this.keysBuffer = singleWriter ? keyType.createStorage(keysPerPage) : null; + this.valuesBuffer = singleWriter ? valueType.createStorage(keysPerPage) : null; + this.singleWriter = singleWriter; + this.avgKeySize = keyType.isMemoryEstimationAllowed() ? new AtomicLong() : null; + this.avgValSize = valueType.isMemoryEstimationAllowed() ? new AtomicLong() : null; + + } + + /** + * Clone the current map. + * + * @return clone of this. + */ + protected MVMap cloneIt() { + return new MVMap<>(this); + } + + /** + * Get the metadata key for the root of the given map id. + * + * @param mapId the map id + * @return the metadata key + */ + static String getMapRootKey(int mapId) { + return DataUtils.META_ROOT + Integer.toHexString(mapId); + } + + /** + * Get the metadata key for the given map id. + * + * @param mapId the map id + * @return the metadata key + */ + static String getMapKey(int mapId) { + return DataUtils.META_MAP + Integer.toHexString(mapId); + } + + /** + * Add or replace a key-value pair. + * + * @param key the key (may not be null) + * @param value the value (may not be null) + * @return the old value if the key existed, or null otherwise + */ + @Override + public V put(K key, V value) { + DataUtils.checkArgument(value != null, "The value may not be null"); + return operate(key, value, DecisionMaker.PUT); + } + + /** + * Get the first key, or null if the map is empty. + * + * @return the first key, or null + */ + public final K firstKey() { + return getFirstLast(true); + } + + /** + * Get the last key, or null if the map is empty. + * + * @return the last key, or null + */ + public final K lastKey() { + return getFirstLast(false); + } + + /** + * Get the key at the given index. + *

+ * This is a O(log(size)) operation. + * + * @param index the index + * @return the key + */ + public final K getKey(long index) { + if (index < 0 || index >= sizeAsLong()) { + return null; + } + Page p = getRootPage(); + long offset = 0; + while (true) { + if (p.isLeaf()) { + if (index >= offset + p.getKeyCount()) { + return null; + } + K key = p.getKey((int) (index - offset)); + return key; + } + int i = 0, size = getChildPageCount(p); + for (; i < size; i++) { + long c = p.getCounts(i); + if (index < c + offset) { + break; + } + offset += c; + } + if (i == size) { + return null; + } + p = p.getChildPage(i); + } + } + + /** + * Get the key list. The list is a read-only representation of all keys. + *

+ * The get and indexOf methods are O(log(size)) operations. The result of + * indexOf is cast to an int. + * + * @return the key list + */ + public final List keyList() { + return new AbstractList() { + + @Override + public K get(int index) { + return getKey(index); + } + + @Override + public int size() { + return MVMap.this.size(); + } + + @Override + @SuppressWarnings("unchecked") + public int indexOf(Object key) { + return (int) getKeyIndex((K) key); + } + + }; + } + + /** + * Get the index of the given key in the map. + *

+ * This is a O(log(size)) operation. + *

+ * If the key was found, the returned value is the index in the key array. + * If not found, the returned value is negative, where -1 means the provided + * key is smaller than any keys. See also Arrays.binarySearch. + * + * @param key the key + * @return the index + */ + public final long getKeyIndex(K key) { + Page p = getRootPage(); + if (p.getTotalCount() == 0) { + return -1; + } + long offset = 0; + while (true) { + int x = p.binarySearch(key); + if (p.isLeaf()) { + if (x < 0) { + offset = -offset; + } + return offset + x; + } + if (x++ < 0) { + x = -x; + } + for (int i = 0; i < x; i++) { + offset += p.getCounts(i); + } + p = p.getChildPage(x); + } + } + + /** + * Get the first (lowest) or last (largest) key. + * + * @param first whether to retrieve the first key + * @return the key, or null if the map is empty + */ + private K getFirstLast(boolean first) { + Page p = getRootPage(); + return getFirstLast(p, first); + } + + private K getFirstLast(Page p, boolean first) { + if (p.getTotalCount() == 0) { + return null; + } + while (true) { + if (p.isLeaf()) { + return p.getKey(first ? 0 : p.getKeyCount() - 1); + } + p = p.getChildPage(first ? 0 : getChildPageCount(p) - 1); + } + } + + /** + * Get the smallest key that is larger than the given key (next key in ascending order), + * or null if no such key exists. + * + * @param key the key + * @return the result + */ + public final K higherKey(K key) { + return getMinMax(key, false, true); + } + + /** + * Get the smallest key that is larger than the given key, for the given + * root page, or null if no such key exists. + * + * @param rootRef the root reference of the map + * @param key to start from + * @return the result + */ + public final K higherKey(RootReference rootRef, K key) { + return getMinMax(rootRef, key, false, true); + } + + /** + * Get the smallest key that is larger or equal to this key. + * + * @param key the key + * @return the result + */ + public final K ceilingKey(K key) { + return getMinMax(key, false, false); + } + + /** + * Get the largest key that is smaller or equal to this key. + * + * @param key the key + * @return the result + */ + public final K floorKey(K key) { + return getMinMax(key, true, false); + } + + /** + * Get the largest key that is smaller than the given key, or null if no + * such key exists. + * + * @param key the key + * @return the result + */ + public final K lowerKey(K key) { + return getMinMax(key, true, true); + } + + /** + * Get the largest key that is smaller than the given key, for the given + * root page, or null if no such key exists. + * + * @param rootRef the root page + * @param key the key + * @return the result + */ + public final K lowerKey(RootReference rootRef, K key) { + return getMinMax(rootRef, key, true, true); + } + + /** + * Get the smallest or largest key using the given bounds. + * + * @param key the key + * @param min whether to retrieve the smallest key + * @param excluding if the given upper/lower bound is exclusive + * @return the key, or null if no such key exists + */ + private K getMinMax(K key, boolean min, boolean excluding) { + return getMinMax(flushAndGetRoot(), key, min, excluding); + } + + private K getMinMax(RootReference rootRef, K key, boolean min, boolean excluding) { + return getMinMax(rootRef.root, key, min, excluding); + } + + private K getMinMax(Page p, K key, boolean min, boolean excluding) { + int x = p.binarySearch(key); + if (p.isLeaf()) { + if (x < 0) { + x = -x - (min ? 2 : 1); + } else if (excluding) { + x += min ? -1 : 1; + } + if (x < 0 || x >= p.getKeyCount()) { + return null; + } + return p.getKey(x); + } + if (x++ < 0) { + x = -x; + } + while (true) { + if (x < 0 || x >= getChildPageCount(p)) { + return null; + } + K k = getMinMax(p.getChildPage(x), key, min, excluding); + if (k != null) { + return k; + } + x += min ? -1 : 1; + } + } + + + /** + * Get the value for the given key, or null if not found. + * + * @param key the key + * @return the value, or null if not found + * @throws ClassCastException if type of the specified key is not compatible with this map + */ + @SuppressWarnings("unchecked") + @Override + public final V get(Object key) { + return get(getRootPage(), (K) key); + } + + /** + * Get the value for the given key from a snapshot, or null if not found. + * + * @param p the root of a snapshot + * @param key the key + * @return the value, or null if not found + * @throws ClassCastException if type of the specified key is not compatible with this map + */ + public V get(Page p, K key) { + return Page.get(p, key); + } + + @Override + public final boolean containsKey(Object key) { + return get(key) != null; + } + + /** + * Remove all entries. + */ + @Override + public void clear() { + clearIt(); + } + + /** + * Remove all entries and return the root reference. + * + * @return the new root reference + */ + RootReference clearIt() { + Page emptyRootPage = createEmptyLeaf(); + int attempt = 0; + while (true) { + RootReference rootReference = flushAndGetRoot(); + if (rootReference.getTotalCount() == 0) { + return rootReference; + } + boolean locked = rootReference.isLockedByCurrentThread(); + if (!locked) { + if (attempt++ == 0) { + beforeWrite(); + } else if (attempt > 3 || rootReference.isLocked()) { + rootReference = lockRoot(rootReference, attempt); + locked = true; + } + } + Page rootPage = rootReference.root; + long version = rootReference.version; + try { + if (!locked) { + rootReference = rootReference.updateRootPage(emptyRootPage, attempt); + if (rootReference == null) { + continue; + } + } + if (isPersistent()) { + store.registerUnsavedMemory(rootPage.removeAllRecursive(version)); + } + rootPage = emptyRootPage; + return rootReference; + } finally { + if(locked) { + unlockRoot(rootPage); + } + } + } + } + + /** + * Close the map. Accessing the data is still possible (to allow concurrent + * reads), but it is marked as closed. + */ + final void close() { + closed = true; + } + + public final boolean isClosed() { + return closed; + } + + /** + * Remove a key-value pair, if the key exists. + * + * @param key the key (may not be null) + * @return the old value if the key existed, or null otherwise + * @throws ClassCastException if type of the specified key is not compatible with this map + */ + @Override + @SuppressWarnings("unchecked") + public V remove(Object key) { + return operate((K)key, null, DecisionMaker.REMOVE); + } + + /** + * Add a key-value pair if it does not yet exist. + * + * @param key the key (may not be null) + * @param value the new value + * @return the old value if the key existed, or null otherwise + */ + @Override + public final V putIfAbsent(K key, V value) { + return operate(key, value, DecisionMaker.IF_ABSENT); + } + + /** + * Remove a key-value pair if the value matches the stored one. + * + * @param key the key (may not be null) + * @param value the expected value + * @return true if the item was removed + */ + @SuppressWarnings("unchecked") + @Override + public boolean remove(Object key, Object value) { + EqualsDecisionMaker decisionMaker = new EqualsDecisionMaker<>(valueType, (V)value); + operate((K)key, null, decisionMaker); + return decisionMaker.getDecision() != Decision.ABORT; + } + + /** + * Check whether the two values are equal. + * + * @param type of values to compare + * + * @param a the first value + * @param b the second value + * @param datatype to use for comparison + * @return true if they are equal + */ + static boolean areValuesEqual(DataType datatype, X a, X b) { + return a == b + || a != null && b != null && datatype.compare(a, b) == 0; + } + + /** + * Replace a value for an existing key, if the value matches. + * + * @param key the key (may not be null) + * @param oldValue the expected value + * @param newValue the new value + * @return true if the value was replaced + */ + @Override + public final boolean replace(K key, V oldValue, V newValue) { + EqualsDecisionMaker decisionMaker = new EqualsDecisionMaker<>(valueType, oldValue); + V result = operate(key, newValue, decisionMaker); + boolean res = decisionMaker.getDecision() != Decision.ABORT; + assert !res || areValuesEqual(valueType, oldValue, result) : oldValue + " != " + result; + return res; + } + + /** + * Replace a value for an existing key. + * + * @param key the key (may not be null) + * @param value the new value + * @return the old value, if the value was replaced, or null + */ + @Override + public final V replace(K key, V value) { + return operate(key, value, DecisionMaker.IF_PRESENT); + } + + /** + * Compare two keys. + * + * @param a the first key + * @param b the second key + * @return -1 if the first key is smaller, 1 if bigger, 0 if equal + */ + @SuppressWarnings("unused") + final int compare(K a, K b) { + return keyType.compare(a, b); + } + + /** + * Get the key type. + * + * @return the key type + */ + public final DataType getKeyType() { + return keyType; + } + + /** + * Get the value type. + * + * @return the value type + */ + public final DataType getValueType() { + return valueType; + } + + boolean isSingleWriter() { + return singleWriter; + } + + /** + * Read a page. + * + * @param pos the position of the page + * @return the page + */ + final Page readPage(long pos) { + return store.readPage(this, pos); + } + + /** + * Set the position of the root page. + * @param rootPos the position, 0 for empty + * @param version to set for this map + * + */ + final void setRootPos(long rootPos, long version) { + Page root = readOrCreateRootPage(rootPos); + if (root.map != this) { + // this can only happen on concurrent opening of existing map, + // when second thread picks up some cached page already owned by + // the first map's instantiation (both maps share the same id) + assert id == root.map.id; + // since it is unknown which one will win the race, + // let each map instance to have it's own copy + root = root.copy(this, false); + } + setInitialRoot(root, version); + setWriteVersion(store.getCurrentVersion()); + } + + private Page readOrCreateRootPage(long rootPos) { + Page root = rootPos == 0 ? createEmptyLeaf() : readPage(rootPos); + return root; + } + + /** + * Iterate over a number of keys. + * + * @param from the first key to return + * @return the iterator + */ + public final Iterator keyIterator(K from) { + return cursor(from, null, false); + } + + /** + * Iterate over a number of keys in reverse order + * + * @param from the first key to return + * @return the iterator + */ + public final Iterator keyIteratorReverse(K from) { + return cursor(from, null, true); + } + + final boolean rewritePage(long pagePos) { + Page p = readPage(pagePos); + if (p.getKeyCount()==0) { + return true; + } + assert p.isSaved(); + K key = p.getKey(0); + if (!isClosed()) { + RewriteDecisionMaker decisionMaker = new RewriteDecisionMaker<>(p.getPos()); + V result = operate(key, null, decisionMaker); + boolean res = decisionMaker.getDecision() != Decision.ABORT; + assert !res || result != null; + return res; + } + return false; + } + + /** + * Get a cursor to iterate over a number of keys and values in the latest version of this map. + * + * @param from the first key to return + * @return the cursor + */ + public final Cursor cursor(K from) { + return cursor(from, null, false); + } + + /** + * Get a cursor to iterate over a number of keys and values in the latest version of this map. + * + * @param from the first key to return + * @param to the last key to return + * @param reverse if true, iterate in reverse (descending) order + * @return the cursor + */ + public final Cursor cursor(K from, K to, boolean reverse) { + return cursor(flushAndGetRoot(), from, to, reverse); + } + + /** + * Get a cursor to iterate over a number of keys and values. + * + * @param rootReference of this map's version to iterate over + * @param from the first key to return + * @param to the last key to return + * @param reverse if true, iterate in reverse (descending) order + * @return the cursor + */ + public Cursor cursor(RootReference rootReference, K from, K to, boolean reverse) { + return new Cursor<>(rootReference, from, to, reverse); + } + + @Override + public final Set> entrySet() { + final RootReference rootReference = flushAndGetRoot(); + return new AbstractSet>() { + + @Override + public Iterator> iterator() { + final Cursor cursor = cursor(rootReference, null, null, false); + return new Iterator>() { + + @Override + public boolean hasNext() { + return cursor.hasNext(); + } + + @Override + public Entry next() { + K k = cursor.next(); + return new SimpleImmutableEntry<>(k, cursor.getValue()); + } + }; + + } + + @Override + public int size() { + return MVMap.this.size(); + } + + @Override + public boolean contains(Object o) { + return MVMap.this.containsKey(o); + } + + }; + + } + + @Override + public Set keySet() { + final RootReference rootReference = flushAndGetRoot(); + return new AbstractSet() { + + @Override + public Iterator iterator() { + return cursor(rootReference, null, null, false); + } + + @Override + public int size() { + return MVMap.this.size(); + } + + @Override + public boolean contains(Object o) { + return MVMap.this.containsKey(o); + } + + }; + } + + /** + * Get the map name. + * + * @return the name + */ + public final String getName() { + return store.getMapName(id); + } + + public final MVStore getStore() { + return store; + } + + protected final boolean isPersistent() { + return store.getFileStore() != null && !isVolatile; + } + + /** + * Get the map id. Please note the map id may be different after compacting + * a store. + * + * @return the map id + */ + public final int getId() { + return id; + } + + /** + * The current root page (may not be null). + * + * @return the root page + */ + public final Page getRootPage() { + return flushAndGetRoot().root; + } + + public RootReference getRoot() { + return root.get(); + } + + /** + * Get the root reference, flushing any current append buffer. + * + * @return current root reference + */ + public RootReference flushAndGetRoot() { + RootReference rootReference = getRoot(); + if (singleWriter && rootReference.getAppendCounter() > 0) { + return flushAppendBuffer(rootReference, true); + } + return rootReference; + } + + /** + * Set the initial root. + * + * @param rootPage root page + * @param version initial version + */ + final void setInitialRoot(Page rootPage, long version) { + root.set(new RootReference<>(rootPage, version)); + } + + /** + * Compare and set the root reference. + * + * @param expectedRootReference the old (expected) + * @param updatedRootReference the new + * @return whether updating worked + */ + final boolean compareAndSetRoot(RootReference expectedRootReference, + RootReference updatedRootReference) { + return root.compareAndSet(expectedRootReference, updatedRootReference); + } + + /** + * Rollback to the given version. + * + * @param version the version + */ + final void rollbackTo(long version) { + // check if the map was removed and re-created later ? + if (version > createVersion) { + rollbackRoot(version); + } + } + + /** + * Roll the root back to the specified version. + * + * @param version to rollback to + * @return true if rollback was a success, false if there was not enough in-memory history + */ + boolean rollbackRoot(long version) { + RootReference rootReference = flushAndGetRoot(); + RootReference previous; + while (rootReference.version >= version && (previous = rootReference.previous) != null) { + if (root.compareAndSet(rootReference, previous)) { + rootReference = previous; + closed = false; + } + } + setWriteVersion(version); + return rootReference.version < version; + } + + /** + * Use the new root page from now on. + * + * @param the key class + * @param the value class + * @param expectedRootReference expected current root reference + * @param newRootPage the new root page + * @param attemptUpdateCounter how many attempt (including current) + * were made to update root + * @return new RootReference or null if update failed + */ + protected static boolean updateRoot(RootReference expectedRootReference, Page newRootPage, + int attemptUpdateCounter) { + return expectedRootReference.updateRootPage(newRootPage, attemptUpdateCounter) != null; + } + + /** + * Forget those old versions that are no longer needed. + * @param rootReference to inspect + */ + private void removeUnusedOldVersions(RootReference rootReference) { + rootReference.removeUnusedOldVersions(store.getOldestVersionToKeep()); + } + + public final boolean isReadOnly() { + return readOnly; + } + + /** + * Set the volatile flag of the map. + * + * @param isVolatile the volatile flag + */ + public final void setVolatile(boolean isVolatile) { + this.isVolatile = isVolatile; + } + + /** + * Whether this is volatile map, meaning that changes + * are not persisted. By default (even if the store is not persisted), + * maps are not volatile. + * + * @return whether this map is volatile + */ + public final boolean isVolatile() { + return isVolatile; + } + + /** + * This method is called before writing to the map. The default + * implementation checks whether writing is allowed, and tries + * to detect concurrent modification. + * + * @throws UnsupportedOperationException if the map is read-only, + * or if another thread is concurrently writing + */ + protected final void beforeWrite() { + assert !getRoot().isLockedByCurrentThread() : getRoot(); + if (closed) { + int id = getId(); + String mapName = store.getMapName(id); + throw DataUtils.newMVStoreException( + DataUtils.ERROR_CLOSED, "Map {0}({1}) is closed. {2}", mapName, id, store.getPanicException()); + } + if (readOnly) { + throw DataUtils.newUnsupportedOperationException( + "This map is read-only"); + } + store.beforeWrite(this); + } + + @Override + public final int hashCode() { + return id; + } + + @Override + public final boolean equals(Object o) { + return this == o; + } + + /** + * Get the number of entries, as a integer. {@link Integer#MAX_VALUE} is + * returned if there are more than this entries. + * + * @return the number of entries, as an integer + * @see #sizeAsLong() + */ + @Override + public final int size() { + long size = sizeAsLong(); + return size > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) size; + } + + /** + * Get the number of entries, as a long. + * + * @return the number of entries + */ + public final long sizeAsLong() { + return getRoot().getTotalCount(); + } + + @Override + public boolean isEmpty() { + return sizeAsLong() == 0; + } + + final long getCreateVersion() { + return createVersion; + } + + /** + * Open an old version for the given map. + * It will restore map at last known state of the version specified. + * (at the point right before the commit() call, which advanced map to the next version) + * Map is opened in read-only mode. + * + * @param version the version + * @return the map + */ + public final MVMap openVersion(long version) { + if (readOnly) { + throw DataUtils.newUnsupportedOperationException( + "This map is read-only; need to call " + + "the method on the writable map"); + } + DataUtils.checkArgument(version >= createVersion, + "Unknown version {0}; this map was created in version is {1}", + version, createVersion); + RootReference rootReference = flushAndGetRoot(); + removeUnusedOldVersions(rootReference); + RootReference previous; + while ((previous = rootReference.previous) != null && previous.version >= version) { + rootReference = previous; + } + if (previous == null && version < store.getOldestVersionToKeep()) { + throw DataUtils.newIllegalArgumentException("Unknown version {0}", version); + } + MVMap m = openReadOnly(rootReference.root, version); + assert m.getVersion() <= version : m.getVersion() + " <= " + version; + return m; + } + + /** + * Open a copy of the map in read-only mode. + * + * @param rootPos position of the root page + * @param version to open + * @return the opened map + */ + final MVMap openReadOnly(long rootPos, long version) { + Page root = readOrCreateRootPage(rootPos); + return openReadOnly(root, version); + } + + private MVMap openReadOnly(Page root, long version) { + MVMap m = cloneIt(); + m.readOnly = true; + m.setInitialRoot(root, version); + return m; + } + + /** + * Get version of the map, which is the version of the store, + * at the moment when map was modified last time. + * + * @return version + */ + public final long getVersion() { + return getRoot().getVersion(); + } + + /** + * Does the root have changes since the specified version? + * + * @param version root version + * @return true if has changes + */ + final boolean hasChangesSince(long version) { + return getRoot().hasChangesSince(version, isPersistent()); + } + + /** + * Get the child page count for this page. This is to allow another map + * implementation to override the default, in case the last child is not to + * be used. + * + * @param p the page + * @return the number of direct children + */ + protected int getChildPageCount(Page p) { + return p.getRawChildPageCount(); + } + + /** + * Get the map type. When opening an existing map, the map type must match. + * + * @return the map type + */ + public String getType() { + return null; + } + + /** + * Get the map metadata as a string. + * + * @param name the map name (or null) + * @return the string + */ + protected String asString(String name) { + StringBuilder buff = new StringBuilder(); + if (name != null) { + DataUtils.appendMap(buff, "name", name); + } + if (createVersion != 0) { + DataUtils.appendMap(buff, "createVersion", createVersion); + } + String type = getType(); + if (type != null) { + DataUtils.appendMap(buff, "type", type); + } + return buff.toString(); + } + + final RootReference setWriteVersion(long writeVersion) { + int attempt = 0; + while(true) { + RootReference rootReference = flushAndGetRoot(); + if(rootReference.version >= writeVersion) { + return rootReference; + } else if (isClosed()) { + // map was closed a while back and can not possibly be in use by now + // it's time to remove it completely from the store (it was anonymous already) + if (rootReference.getVersion() + 1 < store.getOldestVersionToKeep()) { + store.deregisterMapRoot(id); + return null; + } + } + + RootReference lockedRootReference = null; + if (++attempt > 3 || rootReference.isLocked()) { + lockedRootReference = lockRoot(rootReference, attempt); + rootReference = flushAndGetRoot(); + } + + try { + rootReference = rootReference.tryUnlockAndUpdateVersion(writeVersion, attempt); + if (rootReference != null) { + lockedRootReference = null; + removeUnusedOldVersions(rootReference); + return rootReference; + } + } finally { + if (lockedRootReference != null) { + unlockRoot(); + } + } + } + } + + /** + * Create empty leaf node page. + * + * @return new page + */ + protected Page createEmptyLeaf() { + return Page.createEmptyLeaf(this); + } + + /** + * Create empty internal node page. + * + * @return new page + */ + protected Page createEmptyNode() { + return Page.createEmptyNode(this); + } + + /** + * Copy a map. All pages are copied. + * + * @param sourceMap the source map + */ + final void copyFrom(MVMap sourceMap) { + MVStore.TxCounter txCounter = store.registerVersionUsage(); + try { + beforeWrite(); + copy(sourceMap.getRootPage(), null, 0); + } finally { + store.deregisterVersionUsage(txCounter); + } + } + + private void copy(Page source, Page parent, int index) { + Page target = source.copy(this, true); + if (parent == null) { + setInitialRoot(target, INITIAL_VERSION); + } else { + parent.setChild(index, target); + } + if (!source.isLeaf()) { + for (int i = 0; i < getChildPageCount(target); i++) { + if (source.getChildPagePos(i) != 0) { + // position 0 means no child + // (for example the last entry of an r-tree node) + // (the MVMap is also used for r-trees for compacting) + copy(source.getChildPage(i), target, i); + } + } + target.setComplete(); + } + store.registerUnsavedMemory(target.getMemory()); + if (store.isSaveNeeded()) { + store.commit(); + } + } + + /** + * If map was used in append mode, this method will ensure that append buffer + * is flushed - emptied with all entries inserted into map as a new leaf. + * @param rootReference current RootReference + * @param fullFlush whether buffer should be completely flushed, + * otherwise just a single empty slot is required + * @return potentially updated RootReference + */ + private RootReference flushAppendBuffer(RootReference rootReference, boolean fullFlush) { + boolean preLocked = rootReference.isLockedByCurrentThread(); + boolean locked = preLocked; + int keysPerPage = store.getKeysPerPage(); + try { + IntValueHolder unsavedMemoryHolder = new IntValueHolder(); + int attempt = 0; + int keyCount; + int availabilityThreshold = fullFlush ? 0 : keysPerPage - 1; + while ((keyCount = rootReference.getAppendCounter()) > availabilityThreshold) { + if (!locked) { + // instead of just calling lockRoot() we loop here and check if someone else + // already flushed the buffer, then we don't need a lock + rootReference = tryLock(rootReference, ++attempt); + if (rootReference == null) { + rootReference = getRoot(); + continue; + } + locked = true; + } + + Page rootPage = rootReference.root; + long version = rootReference.version; + CursorPos pos = rootPage.getAppendCursorPos(null); + assert pos != null; + assert pos.index < 0 : pos.index; + int index = -pos.index - 1; + assert index == pos.page.getKeyCount() : index + " != " + pos.page.getKeyCount(); + Page p = pos.page; + CursorPos tip = pos; + pos = pos.parent; + + int remainingBuffer = 0; + Page page = null; + int available = keysPerPage - p.getKeyCount(); + if (available > 0) { + p = p.copy(); + if (keyCount <= available) { + p.expand(keyCount, keysBuffer, valuesBuffer); + } else { + p.expand(available, keysBuffer, valuesBuffer); + keyCount -= available; + if (fullFlush) { + K[] keys = p.createKeyStorage(keyCount); + V[] values = p.createValueStorage(keyCount); + System.arraycopy(keysBuffer, available, keys, 0, keyCount); + if (valuesBuffer != null) { + System.arraycopy(valuesBuffer, available, values, 0, keyCount); + } + page = Page.createLeaf(this, keys, values, 0); + } else { + System.arraycopy(keysBuffer, available, keysBuffer, 0, keyCount); + if (valuesBuffer != null) { + System.arraycopy(valuesBuffer, available, valuesBuffer, 0, keyCount); + } + remainingBuffer = keyCount; + } + } + } else { + tip = tip.parent; + page = Page.createLeaf(this, + Arrays.copyOf(keysBuffer, keyCount), + valuesBuffer == null ? null : Arrays.copyOf(valuesBuffer, keyCount), + 0); + } + + unsavedMemoryHolder.value = 0; + if (page != null) { + assert page.map == this; + assert page.getKeyCount() > 0; + K key = page.getKey(0); + unsavedMemoryHolder.value += page.getMemory(); + while (true) { + if (pos == null) { + if (p.getKeyCount() == 0) { + p = page; + } else { + K[] keys = p.createKeyStorage(1); + keys[0] = key; + Page.PageReference[] children = Page.createRefStorage(2); + children[0] = new Page.PageReference<>(p); + children[1] = new Page.PageReference<>(page); + unsavedMemoryHolder.value += p.getMemory(); + p = Page.createNode(this, keys, children, p.getTotalCount() + page.getTotalCount(), 0); + } + break; + } + Page c = p; + p = pos.page; + index = pos.index; + pos = pos.parent; + p = p.copy(); + p.setChild(index, page); + p.insertNode(index, key, c); + keyCount = p.getKeyCount(); + int at = keyCount - (p.isLeaf() ? 1 : 2); + if (keyCount <= keysPerPage && + (p.getMemory() < store.getMaxPageSize() || at <= 0)) { + break; + } + key = p.getKey(at); + page = p.split(at); + unsavedMemoryHolder.value += p.getMemory() + page.getMemory(); + } + } + p = replacePage(pos, p, unsavedMemoryHolder); + rootReference = rootReference.updatePageAndLockedStatus(p, preLocked || isPersistent(), + remainingBuffer); + if (rootReference != null) { + // should always be the case, except for spurious failure? + locked = preLocked || isPersistent(); + if (isPersistent() && tip != null) { + store.registerUnsavedMemory(unsavedMemoryHolder.value + tip.processRemovalInfo(version)); + } + assert rootReference.getAppendCounter() <= availabilityThreshold; + break; + } + rootReference = getRoot(); + } + } finally { + if (locked && !preLocked) { + rootReference = unlockRoot(); + } + } + return rootReference; + } + + private static Page replacePage(CursorPos path, Page replacement, + IntValueHolder unsavedMemoryHolder) { + int unsavedMemory = replacement.isSaved() ? 0 : replacement.getMemory(); + while (path != null) { + Page parent = path.page; + // condition below should always be true, but older versions (up to 1.4.197) + // may create single-childed (with no keys) internal nodes, which we skip here + if (parent.getKeyCount() > 0) { + Page child = replacement; + replacement = parent.copy(); + replacement.setChild(path.index, child); + unsavedMemory += replacement.getMemory(); + } + path = path.parent; + } + unsavedMemoryHolder.value += unsavedMemory; + return replacement; + } + + /** + * Appends entry to this map. this method is NOT thread safe and can not be used + * neither concurrently, nor in combination with any method that updates this map. + * Non-updating method may be used concurrently, but latest appended values + * are not guaranteed to be visible. + * @param key should be higher in map's order than any existing key + * @param value to be appended + */ + public void append(K key, V value) { + if (singleWriter) { + beforeWrite(); + RootReference rootReference = lockRoot(getRoot(), 1); + int appendCounter = rootReference.getAppendCounter(); + try { + if (appendCounter >= keysPerPage) { + rootReference = flushAppendBuffer(rootReference, false); + appendCounter = rootReference.getAppendCounter(); + assert appendCounter < keysPerPage; + } + keysBuffer[appendCounter] = key; + if (valuesBuffer != null) { + valuesBuffer[appendCounter] = value; + } + ++appendCounter; + } finally { + unlockRoot(appendCounter); + } + } else { + put(key, value); + } + } + + /** + * Removes last entry from this map. this method is NOT thread safe and can not be used + * neither concurrently, nor in combination with any method that updates this map. + * Non-updating method may be used concurrently, but latest removal may not be visible. + */ + public void trimLast() { + if (singleWriter) { + RootReference rootReference = getRoot(); + int appendCounter = rootReference.getAppendCounter(); + boolean useRegularRemove = appendCounter == 0; + if (!useRegularRemove) { + rootReference = lockRoot(rootReference, 1); + try { + appendCounter = rootReference.getAppendCounter(); + useRegularRemove = appendCounter == 0; + if (!useRegularRemove) { + --appendCounter; + } + } finally { + unlockRoot(appendCounter); + } + } + if (useRegularRemove) { + Page lastLeaf = rootReference.root.getAppendCursorPos(null).page; + assert lastLeaf.isLeaf(); + assert lastLeaf.getKeyCount() > 0; + Object key = lastLeaf.getKey(lastLeaf.getKeyCount() - 1); + remove(key); + } + } else { + remove(lastKey()); + } + } + + @Override + public final String toString() { + return asString(null); + } + + /** + * A builder for maps. + * + * @param the map type + * @param the key type + * @param the value type + */ + public interface MapBuilder, K, V> { + + /** + * Create a new map of the given type. + * @param store which will own this map + * @param config configuration + * + * @return the map + */ + M create(MVStore store, Map config); + + DataType getKeyType(); + + DataType getValueType(); + + void setKeyType(DataType dataType); + + void setValueType(DataType dataType); + + } + + /** + * A builder for this class. + * + * @param the key type + * @param the value type + */ + public abstract static class BasicBuilder, K, V> implements MapBuilder { + + private DataType keyType; + private DataType valueType; + + /** + * Create a new builder with the default key and value data types. + */ + protected BasicBuilder() { + // ignore + } + + @Override + public DataType getKeyType() { + return keyType; + } + + @Override + public DataType getValueType() { + return valueType; + } + + @SuppressWarnings("unchecked") + @Override + public void setKeyType(DataType keyType) { + this.keyType = (DataType)keyType; + } + + @SuppressWarnings("unchecked") + @Override + public void setValueType(DataType valueType) { + this.valueType = (DataType)valueType; + } + + /** + * Set the key data type. + * + * @param keyType the key type + * @return this + */ + public BasicBuilder keyType(DataType keyType) { + setKeyType(keyType); + return this; + } + + /** + * Set the value data type. + * + * @param valueType the value type + * @return this + */ + public BasicBuilder valueType(DataType valueType) { + setValueType(valueType); + return this; + } + + @Override + public M create(MVStore store, Map config) { + if (getKeyType() == null) { + setKeyType(new ObjectDataType()); + } + if (getValueType() == null) { + setValueType(new ObjectDataType()); + } + DataType keyType = getKeyType(); + DataType valueType = getValueType(); + config.put("store", store); + config.put("key", keyType); + config.put("val", valueType); + return create(config); + } + + /** + * Create map from config. + * @param config config map + * @return new map + */ + protected abstract M create(Map config); + + } + + /** + * A builder for this class. + * + * @param the key type + * @param the value type + */ + public static class Builder extends BasicBuilder, K, V> { + private boolean singleWriter; + + public Builder() {} + + @Override + public Builder keyType(DataType dataType) { + setKeyType(dataType); + return this; + } + + @Override + public Builder valueType(DataType dataType) { + setValueType(dataType); + return this; + } + + /** + * Set up this Builder to produce MVMap, which can be used in append mode + * by a single thread. + * @see MVMap#append(Object, Object) + * @return this Builder for chained execution + */ + public Builder singleWriter() { + singleWriter = true; + return this; + } + + @Override + protected MVMap create(Map config) { + config.put("singleWriter", singleWriter); + Object type = config.get("type"); + if(type == null || type.equals("rtree")) { + return new MVMap<>(config, getKeyType(), getValueType()); + } + throw new IllegalArgumentException("Incompatible map type"); + } + } + + /** + * The decision on what to do on an update. + */ + public enum Decision { ABORT, REMOVE, PUT, REPEAT } + + /** + * Class DecisionMaker provides callback interface (and should become a such in Java 8) + * for MVMap.operate method. + * It provides control logic to make a decision about how to proceed with update + * at the point in execution when proper place and possible existing value + * for insert/update/delete key is found. + * Revised value for insert/update is also provided based on original input value + * and value currently existing in the map. + * + * @param value type of the map + */ + public abstract static class DecisionMaker { + /** + * Decision maker for transaction rollback. + */ + public static final DecisionMaker DEFAULT = new DecisionMaker() { + @Override + public Decision decide(Object existingValue, Object providedValue) { + return providedValue == null ? Decision.REMOVE : Decision.PUT; + } + + @Override + public String toString() { + return "default"; + } + }; + + /** + * Decision maker for put(). + */ + public static final DecisionMaker PUT = new DecisionMaker() { + @Override + public Decision decide(Object existingValue, Object providedValue) { + return Decision.PUT; + } + + @Override + public String toString() { + return "put"; + } + }; + + /** + * Decision maker for remove(). + */ + public static final DecisionMaker REMOVE = new DecisionMaker() { + @Override + public Decision decide(Object existingValue, Object providedValue) { + return Decision.REMOVE; + } + + @Override + public String toString() { + return "remove"; + } + }; + + /** + * Decision maker for putIfAbsent() key/value. + */ + static final DecisionMaker IF_ABSENT = new DecisionMaker() { + @Override + public Decision decide(Object existingValue, Object providedValue) { + return existingValue == null ? Decision.PUT : Decision.ABORT; + } + + @Override + public String toString() { + return "if_absent"; + } + }; + + /** + * Decision maker for replace(). + */ + static final DecisionMaker IF_PRESENT= new DecisionMaker() { + @Override + public Decision decide(Object existingValue, Object providedValue) { + return existingValue != null ? Decision.PUT : Decision.ABORT; + } + + @Override + public String toString() { + return "if_present"; + } + }; + + /** + * Makes a decision about how to proceed with the update. + * + * @param existingValue the old value + * @param providedValue the new value + * @param tip the cursor position + * @return the decision + */ + public Decision decide(V existingValue, V providedValue, CursorPos tip) { + return decide(existingValue, providedValue); + } + + /** + * Makes a decision about how to proceed with the update. + * @param existingValue value currently exists in the map + * @param providedValue original input value + * @return PUT if a new value need to replace existing one or + * a new value to be inserted if there is none + * REMOVE if existing value should be deleted + * ABORT if update operation should be aborted or repeated later + * REPEAT if update operation should be repeated immediately + */ + public abstract Decision decide(V existingValue, V providedValue); + + /** + * Provides revised value for insert/update based on original input value + * and value currently existing in the map. + * This method is only invoked after call to decide(), if it returns PUT. + * @param existingValue value currently exists in the map + * @param providedValue original input value + * @param value type + * @return value to be used by insert/update + */ + public T selectValue(T existingValue, T providedValue) { + return providedValue; + } + + /** + * Resets internal state (if any) of a this DecisionMaker to it's initial state. + * This method is invoked whenever concurrent update failure is encountered, + * so we can re-start update process. + */ + public void reset() {} + } + + /** + * Add, replace or remove a key-value pair. + * + * @param key the key (may not be null) + * @param value new value, it may be null when removal is intended + * @param decisionMaker command object to make choices during transaction. + * @return previous value, if mapping for that key existed, or null otherwise + */ + public V operate(K key, V value, DecisionMaker decisionMaker) { + IntValueHolder unsavedMemoryHolder = new IntValueHolder(); + int attempt = 0; + while(true) { + RootReference rootReference = flushAndGetRoot(); + boolean locked = rootReference.isLockedByCurrentThread(); + if (!locked) { + if (attempt++ == 0) { + beforeWrite(); + } + if (attempt > 3 || rootReference.isLocked()) { + rootReference = lockRoot(rootReference, attempt); + locked = true; + } + } + Page rootPage = rootReference.root; + long version = rootReference.version; + CursorPos tip; + V result; + unsavedMemoryHolder.value = 0; + try { + CursorPos pos = CursorPos.traverseDown(rootPage, key); + if(!locked && rootReference != getRoot()) { + continue; + } + Page p = pos.page; + int index = pos.index; + tip = pos; + pos = pos.parent; + result = index < 0 ? null : p.getValue(index); + Decision decision = decisionMaker.decide(result, value, tip); + + switch (decision) { + case REPEAT: + decisionMaker.reset(); + continue; + case ABORT: + if(!locked && rootReference != getRoot()) { + decisionMaker.reset(); + continue; + } + return result; + case REMOVE: { + if (index < 0) { + if(!locked && rootReference != getRoot()) { + decisionMaker.reset(); + continue; + } + return null; + } + + if (p.getTotalCount() == 1 && pos != null) { + int keyCount; + do { + p = pos.page; + index = pos.index; + pos = pos.parent; + keyCount = p.getKeyCount(); + // condition below should always be false, but older + // versions (up to 1.4.197) may create + // single-childed (with no keys) internal nodes, + // which we skip here + } while (keyCount == 0 && pos != null); + + if (keyCount <= 1) { + if (keyCount == 1) { + assert index <= 1; + p = p.getChildPage(1 - index); + } else { + // if root happens to be such single-childed + // (with no keys) internal node, then just + // replace it with empty leaf + p = Page.createEmptyLeaf(this); + } + break; + } + } + p = p.copy(); + p.remove(index); + break; + } + case PUT: { + value = decisionMaker.selectValue(result, value); + p = p.copy(); + if (index < 0) { + p.insertLeaf(-index - 1, key, value); + int keyCount; + while ((keyCount = p.getKeyCount()) > store.getKeysPerPage() + || p.getMemory() > store.getMaxPageSize() + && keyCount > (p.isLeaf() ? 1 : 2)) { + long totalCount = p.getTotalCount(); + int at = keyCount >> 1; + K k = p.getKey(at); + Page split = p.split(at); + unsavedMemoryHolder.value += p.getMemory() + split.getMemory(); + if (pos == null) { + K[] keys = p.createKeyStorage(1); + keys[0] = k; + Page.PageReference[] children = Page.createRefStorage(2); + children[0] = new Page.PageReference<>(p); + children[1] = new Page.PageReference<>(split); + p = Page.createNode(this, keys, children, totalCount, 0); + break; + } + Page c = p; + p = pos.page; + index = pos.index; + pos = pos.parent; + p = p.copy(); + p.setChild(index, split); + p.insertNode(index, k, c); + } + } else { + p.setValue(index, value); + } + break; + } + } + rootPage = replacePage(pos, p, unsavedMemoryHolder); + if (!locked) { + rootReference = rootReference.updateRootPage(rootPage, attempt); + if (rootReference == null) { + decisionMaker.reset(); + continue; + } + } + if (isPersistent()) { + store.registerUnsavedMemory(unsavedMemoryHolder.value + tip.processRemovalInfo(version)); + } + return result; + } finally { + if(locked) { + unlockRoot(rootPage); + } + } + } + } + + private RootReference lockRoot(RootReference rootReference, int attempt) { + while(true) { + RootReference lockedRootReference = tryLock(rootReference, attempt++); + if (lockedRootReference != null) { + return lockedRootReference; + } + rootReference = getRoot(); + } + } + + /** + * Try to lock the root. + * + * @param rootReference the old root reference + * @param attempt the number of attempts so far + * @return the new root reference + */ + protected RootReference tryLock(RootReference rootReference, int attempt) { + RootReference lockedRootReference = rootReference.tryLock(attempt); + if (lockedRootReference != null) { + return lockedRootReference; + } + assert !rootReference.isLockedByCurrentThread() : rootReference; + RootReference oldRootReference = rootReference.previous; + int contention = 1; + if (oldRootReference != null) { + long updateAttemptCounter = rootReference.updateAttemptCounter - + oldRootReference.updateAttemptCounter; + assert updateAttemptCounter >= 0 : updateAttemptCounter; + long updateCounter = rootReference.updateCounter - oldRootReference.updateCounter; + assert updateCounter >= 0 : updateCounter; + assert updateAttemptCounter >= updateCounter : updateAttemptCounter + " >= " + updateCounter; + contention += (int)((updateAttemptCounter+1) / (updateCounter+1)); + } + + if(attempt > 4) { + if (attempt <= 12) { + Thread.yield(); + } else if (attempt <= 70 - 2 * contention) { + try { + Thread.sleep(contention); + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + } else { + synchronized (lock) { + notificationRequested = true; + try { + lock.wait(5); + } catch (InterruptedException ignore) { + } + } + } + } + return null; + } + + /** + * Unlock the root page, the new root being null. + * + * @return the new root reference (never null) + */ + private RootReference unlockRoot() { + return unlockRoot(null, -1); + } + + /** + * Unlock the root page. + * + * @param newRootPage the new root + * @return the new root reference (never null) + */ + protected RootReference unlockRoot(Page newRootPage) { + return unlockRoot(newRootPage, -1); + } + + private void unlockRoot(int appendCounter) { + unlockRoot(null, appendCounter); + } + + private RootReference unlockRoot(Page newRootPage, int appendCounter) { + RootReference updatedRootReference; + do { + RootReference rootReference = getRoot(); + assert rootReference.isLockedByCurrentThread(); + updatedRootReference = rootReference.updatePageAndLockedStatus( + newRootPage == null ? rootReference.root : newRootPage, + false, + appendCounter == -1 ? rootReference.getAppendCounter() : appendCounter + ); + } while(updatedRootReference == null); + + notifyWaiters(); + return updatedRootReference; + } + + private void notifyWaiters() { + if (notificationRequested) { + synchronized (lock) { + notificationRequested = false; + lock.notify(); + } + } + } + + final boolean isMemoryEstimationAllowed() { + return avgKeySize != null || avgValSize != null; + } + + final int evaluateMemoryForKeys(K[] storage, int count) { + if (avgKeySize == null) { + return calculateMemory(keyType, storage, count); + } + return MemoryEstimator.estimateMemory(avgKeySize, keyType, storage, count); + } + + final int evaluateMemoryForValues(V[] storage, int count) { + if (avgValSize == null) { + return calculateMemory(valueType, storage, count); + } + return MemoryEstimator.estimateMemory(avgValSize, valueType, storage, count); + } + + private static int calculateMemory(DataType keyType, T[] storage, int count) { + int mem = count * MEMORY_POINTER; + for (int i = 0; i < count; i++) { + mem += keyType.getMemory(storage[i]); + } + return mem; + } + + final int evaluateMemoryForKey(K key) { + if (avgKeySize == null) { + return keyType.getMemory(key); + } + return MemoryEstimator.estimateMemory(avgKeySize, keyType, key); + } + + final int evaluateMemoryForValue(V value) { + if (avgValSize == null) { + return valueType.getMemory(value); + } + return MemoryEstimator.estimateMemory(avgValSize, valueType, value); + } + + static int samplingPct(AtomicLong stats) { + return MemoryEstimator.samplingPct(stats); + } + + private static final class EqualsDecisionMaker extends DecisionMaker { + private final DataType dataType; + private final V expectedValue; + private Decision decision; + + EqualsDecisionMaker(DataType dataType, V expectedValue) { + this.dataType = dataType; + this.expectedValue = expectedValue; + } + + @Override + public Decision decide(V existingValue, V providedValue) { + assert decision == null; + decision = !areValuesEqual(dataType, expectedValue, existingValue) ? Decision.ABORT : + providedValue == null ? Decision.REMOVE : Decision.PUT; + return decision; + } + + @Override + public void reset() { + decision = null; + } + + Decision getDecision() { + return decision; + } + + @Override + public String toString() { + return "equals_to "+expectedValue; + } + } + + private static final class RewriteDecisionMaker extends DecisionMaker { + private final long pagePos; + private Decision decision; + + RewriteDecisionMaker(long pagePos) { + this.pagePos = pagePos; + } + + @Override + public Decision decide(V existingValue, V providedValue, CursorPos tip) { + assert decision == null; + decision = Decision.ABORT; + if(!DataUtils.isLeafPosition(pagePos)) { + while ((tip = tip.parent) != null) { + if (tip.page.getPos() == pagePos) { + decision = decide(existingValue, providedValue); + break; + } + } + } else if (tip.page.getPos() == pagePos) { + decision = decide(existingValue, providedValue); + } + return decision; + } + + @Override + public Decision decide(V existingValue, V providedValue) { + decision = existingValue == null ? Decision.ABORT : Decision.PUT; + return decision; + } + + @Override + public T selectValue(T existingValue, T providedValue) { + return existingValue; + } + + @Override + public void reset() { + decision = null; + } + + Decision getDecision() { + return decision; + } + + @Override + public String toString() { + return "rewrite"; + } + } + + private static final class IntValueHolder { + int value; + + IntValueHolder() {} + } +} diff --git a/h2/src/main/org/h2/mvstore/MVStore.java b/h2/src/main/org/h2/mvstore/MVStore.java new file mode 100644 index 0000000..61342b3 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/MVStore.java @@ -0,0 +1,4159 @@ +/* + * 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.mvstore; + +import static org.h2.mvstore.MVMap.INITIAL_VERSION; +import java.lang.Thread.UncaughtExceptionHandler; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collections; +import java.util.Comparator; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; +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; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Predicate; +import java.util.function.Supplier; +import org.h2.compress.CompressDeflate; +import org.h2.compress.CompressLZF; +import org.h2.compress.Compressor; +import org.h2.mvstore.cache.CacheLongKeyLIRS; +import org.h2.mvstore.type.StringDataType; +import org.h2.util.MathUtils; +import org.h2.util.Utils; + +/* + +TODO: + +Documentation +- rolling docs review: at "Metadata Map" +- better document that writes are in background thread +- better document how to do non-unique indexes +- document pluggable store and OffHeapStore + +TransactionStore: +- ability to disable the transaction log, + if there is only one connection + +MVStore: +- better and clearer memory usage accounting rules + (heap memory versus disk memory), so that even there is + never an out of memory + even for a small heap, and so that chunks + are still relatively big on average +- make sure serialization / deserialization errors don't corrupt the file +- test and possibly improve compact operation (for large dbs) +- automated 'kill process' and 'power failure' test +- defragment (re-creating maps, specially those with small pages) +- store number of write operations per page (maybe defragment + if much different than count) +- r-tree: nearest neighbor search +- use a small object value cache (StringCache), test on Android + for default serialization +- MVStoreTool.dump should dump the data if possible; + possibly using a callback for serialization +- implement a sharded map (in one store, multiple stores) + to support concurrent updates and writes, and very large maps +- to save space when persisting very small transactions, + use a transaction log where only the deltas are stored +- serialization for lists, sets, sets, sorted sets, maps, sorted maps +- maybe rename 'rollback' to 'revert' to distinguish from transactions +- support other compression algorithms (deflate, LZ4,...) +- remove features that are not really needed; simplify the code + possibly using a separate layer or tools + (retainVersion?) +- optional pluggable checksum mechanism (per page), which + requires that everything is a page (including headers) +- rename "store" to "save", as "store" is used in "storeVersion" +- rename setStoreVersion to setDataVersion, setSchemaVersion or similar +- temporary file storage +- simple rollback method (rollback to last committed version) +- MVMap to implement SortedMap, then NavigableMap +- storage that splits database into multiple files, + to speed up compact and allow using trim + (by truncating / deleting empty files) +- add new feature to the file system API to avoid copying data + (reads that returns a ByteBuffer instead of writing into one) + for memory mapped files and off-heap storage +- support log structured merge style operations (blind writes) + using one map per level plus bloom filter +- have a strict call order MVStore -> MVMap -> Page -> FileStore +- autocommit commits, stores, and compacts from time to time; + the background thread should wait at least 90% of the + configured write delay to store changes +- compact* should also store uncommitted changes (if there are any) +- write a LSM-tree (log structured merge tree) utility on top of the MVStore + with blind writes and/or a bloom filter that + internally uses regular maps and merge sort +- chunk metadata: maybe split into static and variable, + or use a small page size for metadata +- data type "string": maybe use prefix compression for keys +- test chunk id rollover +- feature to auto-compact from time to time and on close +- compact very small chunks +- Page: to save memory, combine keys & values into one array + (also children & counts). Maybe remove some other + fields (childrenCount for example) +- Support SortedMap for MVMap +- compact: copy whole pages (without having to open all maps) +- maybe change the length code to have lower gaps +- test with very low limits (such as: short chunks, small pages) +- maybe allow to read beyond the retention time: + when compacting, move live pages in old chunks + to a map (possibly the metadata map) - + this requires a change in the compaction code, plus + a map lookup when reading old data; also, this + old data map needs to be cleaned up somehow; + maybe using an additional timeout +*/ + +/** + * A persistent storage for maps. + */ +public class MVStore implements AutoCloseable { + + // The following are attribute names (keys) in store header map + private static final String HDR_H = "H"; + private static final String HDR_BLOCK_SIZE = "blockSize"; + private static final String HDR_FORMAT = "format"; + private static final String HDR_CREATED = "created"; + private static final String HDR_FORMAT_READ = "formatRead"; + private static final String HDR_CHUNK = "chunk"; + private static final String HDR_BLOCK = "block"; + private static final String HDR_VERSION = "version"; + private static final String HDR_CLEAN = "clean"; + private static final String HDR_FLETCHER = "fletcher"; + + /** + * The key for the entry within "layout" map, which contains id of "meta" map. + * Entry value (hex encoded) is usually equal to 1, unless it's a legacy + * (upgraded) database and id 1 has been taken already by another map. + */ + public static final String META_ID_KEY = "meta.id"; + + /** + * The block size (physical sector size) of the disk. The store header is + * written twice, one copy in each block, to ensure it survives a crash. + */ + static final int BLOCK_SIZE = 4 * 1024; + + private static final int FORMAT_WRITE_MIN = 2; + private static final int FORMAT_WRITE_MAX = 2; + private static final int FORMAT_READ_MIN = 2; + private static final int FORMAT_READ_MAX = 2; + + /** + * Store is open. + */ + private static final int STATE_OPEN = 0; + + /** + * Store is about to close now, but is still operational. + * Outstanding store operation by background writer or other thread may be in progress. + * New updates must not be initiated, unless they are part of a closing procedure itself. + */ + private static final int STATE_STOPPING = 1; + + /** + * Store is closing now, and any operation on it may fail. + */ + private static final int STATE_CLOSING = 2; + + /** + * Store is closed. + */ + private static final int STATE_CLOSED = 3; + + private static final int PIPE_LENGTH = 1; + + + /** + * Lock which governs access to major store operations: store(), close(), ... + * It serves as a replacement for synchronized(this), except it allows for + * non-blocking lock attempts. + */ + private final ReentrantLock storeLock = new ReentrantLock(true); + private final ReentrantLock serializationLock = new ReentrantLock(true); + private final ReentrantLock saveChunkLock = new ReentrantLock(true); + + /** + * Reference to a background thread, which is expected to be running, if any. + */ + private final AtomicReference backgroundWriterThread = new AtomicReference<>(); + + /** + * Single-threaded executor for serialization of the store snapshot into ByteBuffer + */ + private ThreadPoolExecutor serializationExecutor; + + /** + * Single-threaded executor for saving ByteBuffer as a new Chunk + */ + private ThreadPoolExecutor bufferSaveExecutor; + + private volatile boolean reuseSpace = true; + + private volatile int state; + + private final FileStore fileStore; + + private final boolean fileStoreIsProvided; + + private final int pageSplitSize; + + private final int keysPerPage; + + /** + * The page cache. The default size is 16 MB, and the average size is 2 KB. + * It is split in 16 segments. The stack move distance is 2% of the expected + * number of entries. + */ + private final CacheLongKeyLIRS> cache; + + /** + * Cache for chunks "Table of Content" used to translate page's + * sequential number within containing chunk into byte position + * within chunk's image. Cache keyed by chunk id. + */ + private final CacheLongKeyLIRS chunksToC; + + /** + * The newest chunk. If nothing was stored yet, this field is not set. + */ + private volatile Chunk lastChunk; + + /** + * The map of chunks. + */ + private final ConcurrentHashMap chunks = new ConcurrentHashMap<>(); + + private final Queue removedPages = new PriorityBlockingQueue<>(); + + private final Deque deadChunks = new ArrayDeque<>(); + + private long updateCounter = 0; + private long updateAttemptCounter = 0; + + /** + * The layout map. Contains chunks metadata and root locations for all maps. + * This is relatively fast changing part of metadata + */ + private final MVMap layout; + + /** + * The metadata map. Holds name -> id and id -> name and id -> metadata + * mapping for all maps. This is relatively slow changing part of metadata + */ + private final MVMap meta; + + private final ConcurrentHashMap> maps = new ConcurrentHashMap<>(); + + private final HashMap storeHeader = new HashMap<>(); + + private final Queue writeBufferPool = new ArrayBlockingQueue<>(PIPE_LENGTH + 1); + + private final AtomicInteger lastMapId = new AtomicInteger(); + + private int lastChunkId; + + private int versionsToKeep = 5; + + /** + * The compression level for new pages (0 for disabled, 1 for fast, 2 for + * high). Even if disabled, the store may contain (old) compressed pages. + */ + private final int compressionLevel; + + private Compressor compressorFast; + + private Compressor compressorHigh; + + private final boolean recoveryMode; + + public final UncaughtExceptionHandler backgroundExceptionHandler; + + private volatile long currentVersion; + + /** + * Oldest store version in use. All version beyond this can be safely dropped + */ + private final AtomicLong oldestVersionToKeep = new AtomicLong(); + + /** + * Ordered collection of all version usage counters for all versions starting + * from oldestVersionToKeep and up to current. + */ + private final Deque versions = new LinkedList<>(); + + /** + * Counter of open transactions for the latest (current) store version + */ + private volatile TxCounter currentTxCounter = new TxCounter(currentVersion); + + /** + * The estimated memory used by unsaved pages. This number is not accurate, + * also because it may be changed concurrently, and because temporary pages + * are counted. + */ + private int unsavedMemory; + private final int autoCommitMemory; + private volatile boolean saveNeeded; + + /** + * The time the store was created, in milliseconds since 1970. + */ + private long creationTime; + + /** + * How long to retain old, persisted chunks, in milliseconds. For larger or + * equal to zero, a chunk is never directly overwritten if unused, but + * instead, the unused field is set. If smaller zero, chunks are directly + * overwritten if unused. + */ + private int retentionTime; + + private long lastCommitTime; + + /** + * The version of the current store operation (if any). + */ + private volatile long currentStoreVersion = INITIAL_VERSION; + + private volatile boolean metaChanged; + + /** + * The delay in milliseconds to automatically commit and write changes. + */ + private int autoCommitDelay; + + private final int autoCompactFillRate; + private long autoCompactLastFileOpCount; + + private volatile MVStoreException panicException; + + private long lastTimeAbsolute; + + private long leafCount; + private long nonLeafCount; + + /** + * Callback for maintenance after some unused store versions were dropped + */ + private Cleaner cleaner; + + + /** + * Create and open the store. + * + * @param config the configuration to use + * @throws MVStoreException if the file is corrupt, or an exception + * occurred while opening + * @throws IllegalArgumentException if the directory does not exist + */ + MVStore(Map config) { + recoveryMode = config.containsKey("recoveryMode"); + compressionLevel = DataUtils.getConfigParam(config, "compress", 0); + String fileName = (String) config.get("fileName"); + FileStore fileStore = (FileStore) config.get("fileStore"); + if (fileStore == null) { + fileStoreIsProvided = false; + if (fileName != null) { + fileStore = new FileStore(); + } + } else { + if (fileName != null) { + throw new IllegalArgumentException("fileName && fileStore"); + } + fileStoreIsProvided = true; + } + this.fileStore = fileStore; + + int pgSplitSize = 48; // for "mem:" case it is # of keys + CacheLongKeyLIRS.Config cc = null; + CacheLongKeyLIRS.Config cc2 = null; + if (this.fileStore != null) { + int mb = DataUtils.getConfigParam(config, "cacheSize", 16); + if (mb > 0) { + cc = new CacheLongKeyLIRS.Config(); + cc.maxMemory = mb * 1024L * 1024L; + Object o = config.get("cacheConcurrency"); + if (o != null) { + cc.segmentCount = (Integer)o; + } + } + cc2 = new CacheLongKeyLIRS.Config(); + cc2.maxMemory = 1024L * 1024L; + pgSplitSize = 16 * 1024; + } + if (cc != null) { + cache = new CacheLongKeyLIRS<>(cc); + } else { + cache = null; + } + chunksToC = cc2 == null ? null : new CacheLongKeyLIRS<>(cc2); + + pgSplitSize = DataUtils.getConfigParam(config, "pageSplitSize", pgSplitSize); + // Make sure pages will fit into cache + if (cache != null && pgSplitSize > cache.getMaxItemSize()) { + pgSplitSize = (int)cache.getMaxItemSize(); + } + pageSplitSize = pgSplitSize; + keysPerPage = DataUtils.getConfigParam(config, "keysPerPage", 48); + backgroundExceptionHandler = + (UncaughtExceptionHandler)config.get("backgroundExceptionHandler"); + layout = new MVMap<>(this, 0, StringDataType.INSTANCE, StringDataType.INSTANCE); + if (this.fileStore != null) { + retentionTime = this.fileStore.getDefaultRetentionTime(); + // 19 KB memory is about 1 KB storage + int kb = Math.max(1, Math.min(19, Utils.scaleForAvailableMemory(64))) * 1024; + kb = DataUtils.getConfigParam(config, "autoCommitBufferSize", kb); + autoCommitMemory = kb * 1024; + autoCompactFillRate = DataUtils.getConfigParam(config, "autoCompactFillRate", 90); + char[] encryptionKey = (char[]) config.get("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) { + boolean readOnly = config.containsKey("readOnly"); + this.fileStore.open(fileName, readOnly, encryptionKey); + } + if (this.fileStore.size() == 0) { + creationTime = getTimeAbsolute(); + storeHeader.put(HDR_H, 2); + storeHeader.put(HDR_BLOCK_SIZE, BLOCK_SIZE); + storeHeader.put(HDR_FORMAT, FORMAT_WRITE_MAX); + storeHeader.put(HDR_CREATED, creationTime); + setLastChunk(null); + writeStoreHeader(); + } else { + readStoreHeader(); + } + } finally { + saveChunkLock.unlock(); + } + } catch (MVStoreException e) { + panic(e); + } finally { + if (encryptionKey != null) { + Arrays.fill(encryptionKey, (char) 0); + } + unlockAndCheckPanicCondition(); + } + lastCommitTime = getTimeSinceCreation(); + + meta = openMetaMap(); + scrubLayoutMap(); + scrubMetaMap(); + + // setAutoCommitDelay starts the thread, but only if + // the parameter is different from the old value + int delay = DataUtils.getConfigParam(config, "autoCommitDelay", 1000); + setAutoCommitDelay(delay); + } else { + autoCommitMemory = 0; + autoCompactFillRate = 0; + meta = openMetaMap(); + } + onVersionChange(currentVersion); + } + + private MVMap openMetaMap() { + String metaIdStr = layout.get(META_ID_KEY); + int metaId; + if (metaIdStr == null) { + metaId = lastMapId.incrementAndGet(); + layout.put(META_ID_KEY, Integer.toHexString(metaId)); + } else { + metaId = DataUtils.parseHexInt(metaIdStr); + } + MVMap map = new MVMap<>(this, metaId, StringDataType.INSTANCE, StringDataType.INSTANCE); + map.setRootPos(getRootPos(map.getId()), currentVersion - 1); + return map; + } + + private void scrubLayoutMap() { + Set keysToRemove = new HashSet<>(); + + // split meta map off layout map + for (String prefix : new String[]{ DataUtils.META_NAME, DataUtils.META_MAP }) { + for (Iterator it = layout.keyIterator(prefix); it.hasNext(); ) { + String key = it.next(); + if (!key.startsWith(prefix)) { + break; + } + meta.putIfAbsent(key, layout.get(key)); + markMetaChanged(); + keysToRemove.add(key); + } + } + + // remove roots of non-existent maps (leftover after unfinished map removal) + for (Iterator it = layout.keyIterator(DataUtils.META_ROOT); it.hasNext();) { + String key = it.next(); + if (!key.startsWith(DataUtils.META_ROOT)) { + break; + } + String mapIdStr = key.substring(key.lastIndexOf('.') + 1); + if(!meta.containsKey(DataUtils.META_MAP + mapIdStr) && DataUtils.parseHexInt(mapIdStr) != meta.getId()) { + keysToRemove.add(key); + } + } + + for (String key : keysToRemove) { + layout.remove(key); + } + } + + private void scrubMetaMap() { + Set keysToRemove = new HashSet<>(); + + // ensure that there is only one name mapped to each id + // this could be a leftover of an unfinished map rename + for (Iterator it = meta.keyIterator(DataUtils.META_NAME); it.hasNext();) { + String key = it.next(); + if (!key.startsWith(DataUtils.META_NAME)) { + break; + } + String mapName = key.substring(DataUtils.META_NAME.length()); + int mapId = DataUtils.parseHexInt(meta.get(key)); + String realMapName = getMapName(mapId); + if(!mapName.equals(realMapName)) { + keysToRemove.add(key); + } + } + + for (String key : keysToRemove) { + meta.remove(key); + markMetaChanged(); + } + + for (Iterator it = meta.keyIterator(DataUtils.META_MAP); it.hasNext();) { + String key = it.next(); + if (!key.startsWith(DataUtils.META_MAP)) { + break; + } + String mapName = DataUtils.getMapName(meta.get(key)); + String mapIdStr = key.substring(DataUtils.META_MAP.length()); + // ensure that last map id is not smaller than max of any existing map ids + int mapId = DataUtils.parseHexInt(mapIdStr); + if (mapId > lastMapId.get()) { + lastMapId.set(mapId); + } + // each map should have a proper name + if(!mapIdStr.equals(meta.get(DataUtils.META_NAME + mapName))) { + meta.put(DataUtils.META_NAME + mapName, mapIdStr); + markMetaChanged(); + } + } + } + + private void unlockAndCheckPanicCondition() { + storeLock.unlock(); + if (getPanicException() != null) { + closeImmediately(); + } + } + + private void panic(MVStoreException e) { + if (isOpen()) { + handleException(e); + panicException = e; + } + throw e; + } + + public MVStoreException getPanicException() { + return panicException; + } + + /** + * Open a store in exclusive mode. For a file-based store, the parent + * directory must already exist. + * + * @param fileName the file name (null for in-memory) + * @return the store + */ + public static MVStore open(String fileName) { + HashMap config = new HashMap<>(); + config.put("fileName", fileName); + return new MVStore(config); + } + + /** + * Open a map with the default settings. The map is automatically create if + * it does not yet exist. If a map with this name is already open, this map + * is returned. + * + * @param the key type + * @param the value type + * @param name the name of the map + * @return the map + */ + public MVMap openMap(String name) { + return openMap(name, new MVMap.Builder<>()); + } + + /** + * Open a map with the given builder. The map is automatically create if it + * does not yet exist. If a map with this name is already open, this map is + * returned. + * + * @param the map type + * @param the key type + * @param the value type + * @param name the name of the map + * @param builder the map builder + * @return the map + */ + public , K, V> M openMap(String name, MVMap.MapBuilder builder) { + int id = getMapId(name); + if (id >= 0) { + @SuppressWarnings("unchecked") + M map = (M) getMap(id); + if(map == null) { + map = openMap(id, builder); + } + assert builder.getKeyType() == null || map.getKeyType().getClass().equals(builder.getKeyType().getClass()); + assert builder.getValueType() == null + || map.getValueType().getClass().equals(builder.getValueType().getClass()); + return map; + } else { + HashMap c = new HashMap<>(); + id = lastMapId.incrementAndGet(); + assert getMap(id) == null; + c.put("id", id); + c.put("createVersion", currentVersion); + M map = builder.create(this, c); + String x = Integer.toHexString(id); + meta.put(MVMap.getMapKey(id), map.asString(name)); + String existing = meta.putIfAbsent(DataUtils.META_NAME + name, x); + if (existing != null) { + // looks like map was created concurrently, cleanup and re-start + meta.remove(MVMap.getMapKey(id)); + return openMap(name, builder); + } + long lastStoredVersion = currentVersion - 1; + map.setRootPos(0, lastStoredVersion); + markMetaChanged(); + @SuppressWarnings("unchecked") + M existingMap = (M) maps.putIfAbsent(id, map); + if (existingMap != null) { + map = existingMap; + } + return map; + } + } + + /** + * Open an existing map with the given builder. + * + * @param the map type + * @param the key type + * @param the value type + * @param id the map id + * @param builder the map builder + * @return the map + */ + @SuppressWarnings("unchecked") + public , K, V> M openMap(int id, MVMap.MapBuilder builder) { + M map; + while ((map = (M)getMap(id)) == null) { + String configAsString = meta.get(MVMap.getMapKey(id)); + DataUtils.checkArgument(configAsString != null, "Missing map with id {0}", id); + HashMap config = new HashMap<>(DataUtils.parseMap(configAsString)); + config.put("id", id); + map = builder.create(this, config); + long root = getRootPos(id); + long lastStoredVersion = currentVersion - 1; + map.setRootPos(root, lastStoredVersion); + if (maps.putIfAbsent(id, map) == null) { + break; + } + // looks like map has been concurrently created already, re-start + } + return map; + } + + /** + * Get map by id. + * + * @param the key type + * @param the value type + * @param id map id + * @return Map + */ + public MVMap getMap(int id) { + checkOpen(); + @SuppressWarnings("unchecked") + MVMap map = (MVMap) maps.get(id); + return map; + } + + /** + * Get the set of all map names. + * + * @return the set of names + */ + public Set getMapNames() { + HashSet set = new HashSet<>(); + checkOpen(); + for (Iterator it = meta.keyIterator(DataUtils.META_NAME); it.hasNext();) { + String x = it.next(); + if (!x.startsWith(DataUtils.META_NAME)) { + break; + } + String mapName = x.substring(DataUtils.META_NAME.length()); + set.add(mapName); + } + return set; + } + + /** + * Get this store's layout map. This data is for informational purposes only. The + * data is subject to change in future versions. + *

+ * The data in this map should not be modified (changing system data may corrupt the store). + *

+ * The layout map contains the following entries: + *

+     * chunk.{chunkId} = {chunk metadata}
+     * root.{mapId} = {root position}
+     * 
+ * + * @return the metadata map + */ + public MVMap getLayoutMap() { + checkOpen(); + return layout; + } + + /** + * Get the metadata map. This data is for informational purposes only. The + * data is subject to change in future versions. + *

+ * The data in this map should not be modified (changing system data may corrupt the store). + *

+ * The metadata map contains the following entries: + *

+     * name.{name} = {mapId}
+     * map.{mapId} = {map metadata}
+     * setting.storeVersion = {version}
+     * 
+ * + * @return the metadata map + */ + public MVMap getMetaMap() { + checkOpen(); + return meta; + } + + private MVMap getLayoutMap(long version) { + Chunk c = getChunkForVersion(version); + DataUtils.checkArgument(c != null, "Unknown version {0}", version); + long block = c.block; + c = readChunkHeader(block); + MVMap oldMap = layout.openReadOnly(c.layoutRootPos, version); + return oldMap; + } + + private Chunk getChunkForVersion(long version) { + Chunk newest = null; + for (Chunk c : chunks.values()) { + if (c.version <= version) { + if (newest == null || c.id > newest.id) { + newest = c; + } + } + } + return newest; + } + + /** + * Check whether a given map exists. + * + * @param name the map name + * @return true if it exists + */ + public boolean hasMap(String name) { + return meta.containsKey(DataUtils.META_NAME + name); + } + + /** + * Check whether a given map exists and has data. + * + * @param name the map name + * @return true if it exists and has data. + */ + public boolean hasData(String name) { + return hasMap(name) && getRootPos(getMapId(name)) != 0; + } + + private void markMetaChanged() { + // changes in the metadata alone are usually not detected, as the meta + // map is changed after storing + metaChanged = true; + } + + private void readStoreHeader() { + Chunk newest = null; + boolean assumeCleanShutdown = true; + boolean validStoreHeader = false; + // find out which chunk and version are the newest + // read the first two blocks + ByteBuffer fileHeaderBlocks = fileStore.readFully(0, 2 * BLOCK_SIZE); + byte[] buff = new byte[BLOCK_SIZE]; + for (int i = 0; i <= BLOCK_SIZE; i += BLOCK_SIZE) { + fileHeaderBlocks.get(buff); + // the following can fail for various reasons + try { + HashMap m = DataUtils.parseChecksummedMap(buff); + if (m == null) { + assumeCleanShutdown = false; + continue; + } + long version = DataUtils.readHexLong(m, HDR_VERSION, 0); + // if both header blocks do agree on version + // we'll continue on happy path - assume that previous shutdown was clean + assumeCleanShutdown = assumeCleanShutdown && (newest == null || version == newest.version); + if (newest == null || version > newest.version) { + validStoreHeader = true; + storeHeader.putAll(m); + creationTime = DataUtils.readHexLong(m, HDR_CREATED, 0); + int chunkId = DataUtils.readHexInt(m, HDR_CHUNK, 0); + long block = DataUtils.readHexLong(m, HDR_BLOCK, 2); + Chunk test = readChunkHeaderAndFooter(block, chunkId); + if (test != null) { + newest = test; + } + } + } catch (Exception ignore) { + assumeCleanShutdown = false; + } + } + + if (!validStoreHeader) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_FILE_CORRUPT, + "Store header is corrupt: {0}", fileStore); + } + int blockSize = DataUtils.readHexInt(storeHeader, HDR_BLOCK_SIZE, BLOCK_SIZE); + if (blockSize != BLOCK_SIZE) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_UNSUPPORTED_FORMAT, + "Block size {0} is currently not supported", + blockSize); + } + long format = DataUtils.readHexLong(storeHeader, HDR_FORMAT, 1); + if (!fileStore.isReadOnly()) { + if (format > FORMAT_WRITE_MAX) { + throw getUnsupportedWriteFormatException(format, FORMAT_WRITE_MAX, + "The write format {0} is larger than the supported format {1}"); + } else if (format < FORMAT_WRITE_MIN) { + throw getUnsupportedWriteFormatException(format, FORMAT_WRITE_MIN, + "The write format {0} is smaller than the supported format {1}"); + } + } + format = DataUtils.readHexLong(storeHeader, HDR_FORMAT_READ, format); + if (format > FORMAT_READ_MAX) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_UNSUPPORTED_FORMAT, + "The read format {0} is larger than the supported format {1}", + format, FORMAT_READ_MAX); + } else if (format < FORMAT_READ_MIN) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_UNSUPPORTED_FORMAT, + "The read format {0} is smaller than the supported format {1}", + format, FORMAT_READ_MIN); + } + + assumeCleanShutdown = assumeCleanShutdown && newest != null && !recoveryMode; + if (assumeCleanShutdown) { + assumeCleanShutdown = DataUtils.readHexInt(storeHeader, HDR_CLEAN, 0) != 0; + } + chunks.clear(); + long now = System.currentTimeMillis(); + // calculate the year (doesn't have to be exact; + // we assume 365.25 days per year, * 4 = 1461) + int year = 1970 + (int) (now / (1000L * 60 * 60 * 6 * 1461)); + if (year < 2014) { + // if the year is before 2014, + // we assume the system doesn't have a real-time clock, + // and we set the creationTime to the past, so that + // existing chunks are overwritten + creationTime = now - fileStore.getDefaultRetentionTime(); + } else if (now < creationTime) { + // the system time was set to the past: + // we change the creation time + creationTime = now; + storeHeader.put(HDR_CREATED, creationTime); + } + + long fileSize = fileStore.size(); + long blocksInStore = fileSize / BLOCK_SIZE; + + Comparator chunkComparator = (one, two) -> { + int result = Long.compare(two.version, one.version); + if (result == 0) { + // out of two copies of the same chunk we prefer the one + // close to the beginning of file (presumably later version) + result = Long.compare(one.block, two.block); + } + return result; + }; + + Map validChunksByLocation = new HashMap<>(); + if (!assumeCleanShutdown) { + 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; + } + } + + if (newest != null) { + // read the chunk header and footer, + // and follow the chain of next chunks + while (true) { + validChunksByLocation.put(newest.block, newest); + if (newest.next == 0 || newest.next >= blocksInStore) { + // no (valid) next + break; + } + Chunk test = readChunkHeaderAndFooter(newest.next, newest.id + 1); + if (test == null || test.version <= newest.version) { + break; + } + // if shutdown was really clean then chain should be empty + assumeCleanShutdown = false; + newest = test; + } + } + } + + if (assumeCleanShutdown) { + // quickly check latest 20 chunks referenced in meta table + Queue chunksToVerify = new PriorityQueue<>(20, Collections.reverseOrder(chunkComparator)); + try { + setLastChunk(newest); + // load the chunk metadata: although meta's root page resides in the lastChunk, + // traversing meta map might recursively load another chunk(s) + Cursor cursor = layout.cursor(DataUtils.META_CHUNK); + while (cursor.hasNext() && cursor.next().startsWith(DataUtils.META_CHUNK)) { + Chunk c = Chunk.fromString(cursor.getValue()); + assert c.version <= currentVersion; + // might be there already, due to meta traversal + // see readPage() ... getChunkIfFound() + chunks.putIfAbsent(c.id, c); + chunksToVerify.offer(c); + if (chunksToVerify.size() == 20) { + chunksToVerify.poll(); + } + } + Chunk c; + while (assumeCleanShutdown && (c = chunksToVerify.poll()) != null) { + Chunk test = readChunkHeaderAndFooter(c.block, c.id); + assumeCleanShutdown = test != null; + if (assumeCleanShutdown) { + validChunksByLocation.put(test.block, test); + } + } + } catch(MVStoreException ignored) { + assumeCleanShutdown = false; + } + } + + if (!assumeCleanShutdown) { + boolean quickRecovery = false; + if (!recoveryMode) { + // now we know, that previous shutdown did not go well and file + // is possibly corrupted but there is still hope for a quick + // recovery + + // this collection will hold potential candidates for lastChunk to fall back to, + // in order from the most to least likely + Chunk[] lastChunkCandidates = validChunksByLocation.values().toArray(new Chunk[0]); + Arrays.sort(lastChunkCandidates, chunkComparator); + Map validChunksById = new HashMap<>(); + for (Chunk chunk : lastChunkCandidates) { + validChunksById.put(chunk.id, chunk); + } + quickRecovery = findLastChunkWithCompleteValidChunkSet(lastChunkCandidates, validChunksByLocation, + validChunksById, false); + } + + if (!quickRecovery) { + // scan whole file and try to fetch chunk header and/or footer out of every block + // matching pairs with nothing in-between are considered as valid chunk + long block = blocksInStore; + Chunk tailChunk; + while ((tailChunk = discoverChunk(block)) != null) { + block = tailChunk.block; + validChunksByLocation.put(block, tailChunk); + } + + // this collection will hold potential candidates for lastChunk to fall back to, + // in order from the most to least likely + Chunk[] lastChunkCandidates = validChunksByLocation.values().toArray(new Chunk[0]); + Arrays.sort(lastChunkCandidates, chunkComparator); + Map validChunksById = new HashMap<>(); + for (Chunk chunk : lastChunkCandidates) { + validChunksById.put(chunk.id, chunk); + } + if (!findLastChunkWithCompleteValidChunkSet(lastChunkCandidates, validChunksByLocation, + validChunksById, true) && lastChunk != null) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_FILE_CORRUPT, + "File is corrupted - unable to recover a valid set of chunks"); + + } + } + } + + fileStore.clear(); + // build the free space list + for (Chunk c : chunks.values()) { + if (c.isSaved()) { + long start = c.block * BLOCK_SIZE; + int length = c.len * BLOCK_SIZE; + fileStore.markUsed(start, length); + } + if (!c.isLive()) { + deadChunks.offer(c); + } + } + assert validateFileLength("on open"); + } + + private MVStoreException getUnsupportedWriteFormatException(long format, int expectedFormat, String s) { + format = DataUtils.readHexLong(storeHeader, HDR_FORMAT_READ, format); + if (format >= FORMAT_READ_MIN && format <= FORMAT_READ_MAX) { + s += ", and the file was not opened in read-only mode"; + } + return DataUtils.newMVStoreException(DataUtils.ERROR_UNSUPPORTED_FORMAT, s, format, expectedFormat); + } + + private boolean findLastChunkWithCompleteValidChunkSet(Chunk[] lastChunkCandidates, + Map validChunksByLocation, + Map validChunksById, + boolean afterFullScan) { + // Try candidates for "last chunk" in order from newest to oldest + // until suitable is found. Suitable one should have meta map + // where all chunk references point to valid locations. + for (Chunk chunk : lastChunkCandidates) { + boolean verified = true; + try { + setLastChunk(chunk); + // load the chunk metadata: although meta's root page resides in the lastChunk, + // traversing meta map might recursively load another chunk(s) + Cursor cursor = layout.cursor(DataUtils.META_CHUNK); + while (cursor.hasNext() && cursor.next().startsWith(DataUtils.META_CHUNK)) { + Chunk c = Chunk.fromString(cursor.getValue()); + assert c.version <= currentVersion; + // might be there already, due to meta traversal + // see readPage() ... getChunkIfFound() + Chunk test = chunks.putIfAbsent(c.id, c); + if (test != null) { + c = test; + } + assert chunks.get(c.id) == c; + if ((test = validChunksByLocation.get(c.block)) == null || test.id != c.id) { + if ((test = validChunksById.get(c.id)) != null) { + // We do not have a valid chunk at that location, + // but there is a copy of same chunk from original + // location. + // Chunk header at original location does not have + // any dynamic (occupancy) metadata, so it can't be + // used here as is, re-point our chunk to original + // location instead. + c.block = test.block; + } else if (c.isLive() && (afterFullScan || readChunkHeaderAndFooter(c.block, c.id) == null)) { + // chunk reference is invalid + // this "last chunk" candidate is not suitable + verified = false; + break; + } + } + if (!c.isLive()) { + // we can just remove entry from meta, referencing to this chunk, + // but store maybe R/O, and it's not properly started yet, + // so lets make this chunk "dead" and taking no space, + // and it will be automatically removed later. + c.block = Long.MAX_VALUE; + c.len = Integer.MAX_VALUE; + if (c.unused == 0) { + c.unused = creationTime; + } + if (c.unusedAtVersion == 0) { + c.unusedAtVersion = INITIAL_VERSION; + } + } + } + } catch(Exception ignored) { + verified = false; + } + if (verified) { + return true; + } + } + return false; + } + + void adoptMetaFrom(MVStore source) { + currentVersion = source.currentVersion; + lastMapId.set(source.lastMapId.get()); + } + + private void setLastChunk(Chunk last) { + chunks.clear(); + lastChunk = last; + lastChunkId = 0; + currentVersion = lastChunkVersion(); + long layoutRootPos = 0; + int mapId = 0; + if (last != null) { // there is a valid chunk + lastChunkId = last.id; + currentVersion = last.version; + layoutRootPos = last.layoutRootPos; + mapId = last.mapId; + chunks.put(last.id, last); + } + lastMapId.set(mapId); + layout.setRootPos(layoutRootPos, currentVersion - 1); + } + + /** + * Discover a valid chunk, searching file backwards from the given block + * + * @param block to start search from (found chunk footer should be no + * further than block-1) + * @return valid chunk or null if none found + */ + private Chunk discoverChunk(long block) { + long candidateLocation = Long.MAX_VALUE; + Chunk candidate = null; + while (true) { + if (block == candidateLocation) { + return candidate; + } + if (block == 2) { // number of blocks occupied by headers + return null; + } + Chunk test = readChunkFooter(block); + if (test != null) { + // if we encounter chunk footer (with or without corresponding header) + // in the middle of prospective chunk, stop considering it + candidateLocation = Long.MAX_VALUE; + test = readChunkHeaderOptionally(test.block, test.id); + if (test != null) { + // if that footer has a corresponding header, + // consider them as a new candidate for a valid chunk + candidate = test; + candidateLocation = test.block; + } + } + + // if we encounter chunk header without corresponding footer + // (due to incomplete write?) in the middle of prospective + // chunk, stop considering it + if (--block > candidateLocation && readChunkHeaderOptionally(block) != null) { + candidateLocation = Long.MAX_VALUE; + } + } + } + + + /** + * Read a chunk header and footer, and verify the stored data is consistent. + * + * @param block the block + * @param expectedId of the chunk + * @return the chunk, or null if the header or footer don't match or are not + * consistent + */ + private Chunk readChunkHeaderAndFooter(long block, int expectedId) { + Chunk header = readChunkHeaderOptionally(block, expectedId); + if (header != null) { + Chunk footer = readChunkFooter(block + header.len); + if (footer == null || footer.id != expectedId || footer.block != header.block) { + return null; + } + } + return header; + } + + /** + * Try to read a chunk footer. + * + * @param block the index of the next block after the chunk + * @return the chunk, or null if not successful + */ + private Chunk readChunkFooter(long block) { + // the following can fail for various reasons + try { + // read the chunk footer of the last block of the file + long pos = block * BLOCK_SIZE - Chunk.FOOTER_LENGTH; + if(pos < 0) { + return null; + } + ByteBuffer lastBlock = fileStore.readFully(pos, Chunk.FOOTER_LENGTH); + byte[] buff = new byte[Chunk.FOOTER_LENGTH]; + lastBlock.get(buff); + HashMap m = DataUtils.parseChecksummedMap(buff); + if (m != null) { + return new Chunk(m); + } + } catch (Exception e) { + // ignore + } + return null; + } + + private void writeStoreHeader() { + Chunk lastChunk = this.lastChunk; + if (lastChunk != null) { + storeHeader.put(HDR_BLOCK, lastChunk.block); + storeHeader.put(HDR_CHUNK, lastChunk.id); + storeHeader.put(HDR_VERSION, lastChunk.version); + } + StringBuilder buff = new StringBuilder(112); + DataUtils.appendMap(buff, storeHeader); + byte[] bytes = buff.toString().getBytes(StandardCharsets.ISO_8859_1); + int checksum = DataUtils.getFletcher32(bytes, 0, bytes.length); + DataUtils.appendMap(buff, HDR_FLETCHER, checksum); + buff.append('\n'); + bytes = buff.toString().getBytes(StandardCharsets.ISO_8859_1); + ByteBuffer header = ByteBuffer.allocate(2 * BLOCK_SIZE); + header.put(bytes); + header.position(BLOCK_SIZE); + header.put(bytes); + header.rewind(); + write(0, header); + } + + private void write(long pos, ByteBuffer buffer) { + try { + fileStore.writeFully(pos, buffer); + } catch (MVStoreException e) { + panic(e); + } + } + + /** + * Close the file and the store. Unsaved changes are written to disk first. + */ + @Override + public void close() { + closeStore(true, 0); + } + + /** + * Close the file and the store. Unsaved changes are written to disk first, + * and compaction (up to a specified number of milliseconds) is attempted. + * + * @param allowedCompactionTime the allowed time for compaction (in + * milliseconds) + */ + public void close(int allowedCompactionTime) { + closeStore(true, allowedCompactionTime); + } + + /** + * Close the file and the store, without writing anything. + * This will try to stop the background thread (without waiting for it). + * This method ignores all errors. + */ + public void closeImmediately() { + try { + closeStore(false, 0); + } catch (Throwable e) { + handleException(e); + } + } + + private void closeStore(boolean normalShutdown, int allowedCompactionTime) { + // If any other thead have already initiated closure procedure, + // isClosed() would wait until closure is done and then we jump out of the loop. + // This is a subtle difference between !isClosed() and isOpen(). + while (!isClosed()) { + stopBackgroundThread(normalShutdown); + storeLock.lock(); + try { + if (state == STATE_OPEN) { + state = STATE_STOPPING; + try { + try { + if (normalShutdown && fileStore != null && !fileStore.isReadOnly()) { + // remove all dead LOBs before maps are closed + notifyCleaner(currentVersion); + for (MVMap map : maps.values()) { + if (map.isClosed()) { + deregisterMapRoot(map.getId()); + } + } + setRetentionTime(0); + commit(); + if (allowedCompactionTime > 0) { + compactFile(allowedCompactionTime); + } else if (allowedCompactionTime < 0) { + doMaintenance(autoCompactFillRate); + } + + saveChunkLock.lock(); + try { + shrinkFileIfPossible(0); + storeHeader.put(HDR_CLEAN, 1); + writeStoreHeader(); + sync(); + assert validateFileLength("on close"); + } finally { + saveChunkLock.unlock(); + } + } + + state = STATE_CLOSING; + + // release memory early - this is important when called + // because of out of memory + clearCaches(); + for (MVMap m : new ArrayList<>(maps.values())) { + m.close(); + } + chunks.clear(); + maps.clear(); + } finally { + if (fileStore != null && !fileStoreIsProvided) { + fileStore.close(); + } + } + } finally { + state = STATE_CLOSED; + } + } + } finally { + storeLock.unlock(); + } + } + } + + 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. + * + * @param pos the position + * @return the chunk + */ + private Chunk getChunk(long pos) { + int chunkId = DataUtils.getPageChunkId(pos); + Chunk c = chunks.get(chunkId); + if (c == null) { + checkOpen(); + String s = layout.get(Chunk.getMetaKey(chunkId)); + if (s == null) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_CHUNK_NOT_FOUND, + "Chunk {0} not found", chunkId); + } + c = Chunk.fromString(s); + if (!c.isSaved()) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_FILE_CORRUPT, + "Chunk {0} is invalid", chunkId); + } + chunks.put(c.id, c); + } + return c; + } + + private void setWriteVersion(long version) { + for (Iterator> iter = maps.values().iterator(); iter.hasNext(); ) { + MVMap map = iter.next(); + assert map != layout && map != meta; + if (map.setWriteVersion(version) == null) { + iter.remove(); + } + } + meta.setWriteVersion(version); + layout.setWriteVersion(version); + onVersionChange(version); + } + + /** + * Unlike regular commit this method returns immediately if there is commit + * in progress on another thread, otherwise it acts as regular commit. + * + * This method may return BEFORE this thread changes are actually persisted! + * + * @return the new version (incremented if there were changes) + */ + public long tryCommit() { + return tryCommit(x -> true); + } + + private long tryCommit(Predicate check) { + // we need to prevent re-entrance, which may be possible, + // because meta map is modified within storeNow() and that + // causes beforeWrite() call with possibility of going back here + if ((!storeLock.isHeldByCurrentThread() || currentStoreVersion < 0) && + storeLock.tryLock()) { + try { + if (check.test(this)) { + store(false); + } + } finally { + unlockAndCheckPanicCondition(); + } + } + return currentVersion; + } + + /** + * Commit the changes. + *

+ * This method does nothing if there are no unsaved changes, + * otherwise it increments the current version + * and stores the data (for file based stores). + *

+ * It is not necessary to call this method when auto-commit is enabled (the default + * setting), as in this case it is automatically called from time to time or + * when enough changes have accumulated. However, it may still be called to + * flush all changes to disk. + *

+ * At most one store operation may run at any time. + * + * @return the new version (incremented if there were changes) + */ + public long commit() { + return commit(x -> true); + } + + private long commit(Predicate check) { + // we need to prevent re-entrance, which may be possible, + // because meta map is modified within storeNow() and that + // causes beforeWrite() call with possibility of going back here + if(!storeLock.isHeldByCurrentThread() || currentStoreVersion < 0) { + storeLock.lock(); + try { + if (check.test(this)) { + store(true); + } + } finally { + unlockAndCheckPanicCondition(); + } + } + return currentVersion; + } + + private void store(boolean syncWrite) { + assert storeLock.isHeldByCurrentThread(); + assert !saveChunkLock.isHeldByCurrentThread(); + if (isOpenOrStopping()) { + if (hasUnsavedChanges()) { + dropUnusedChunks(); + try { + currentStoreVersion = currentVersion; + if (fileStore == null) { + //noinspection NonAtomicOperationOnVolatileField + ++currentVersion; + setWriteVersion(currentVersion); + metaChanged = false; + } else { + if (fileStore.isReadOnly()) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_WRITING_FAILED, "This store is read-only"); + } + storeNow(syncWrite, 0, () -> reuseSpace ? 0 : getAfterLastBlock()); + } + } finally { + // in any case reset the current store version, + // to allow closing the store + currentStoreVersion = -1; + } + } + } + } + + private void storeNow(boolean syncWrite, long reservedLow, Supplier reservedHighSupplier) { + try { + lastCommitTime = getTimeSinceCreation(); + int currentUnsavedPageCount = unsavedMemory; + // it is ok, since that path suppose to be single-threaded under storeLock + //noinspection NonAtomicOperationOnVolatileField + long version = ++currentVersion; + ArrayList> changed = collectChangedMapRoots(version); + + assert storeLock.isHeldByCurrentThread(); + submitOrRun(serializationExecutor, + () -> serializeAndStore(syncWrite, reservedLow, reservedHighSupplier, + changed, lastCommitTime, version), + syncWrite); + + // some pages might have been changed in the meantime (in the newest + // version) + saveNeeded = false; + unsavedMemory = Math.max(0, unsavedMemory - currentUnsavedPageCount); + } catch (MVStoreException e) { + panic(e); + } catch (Throwable e) { + panic(DataUtils.newMVStoreException(DataUtils.ERROR_INTERNAL, "{0}", e.toString(), + e)); + } + } + + private static void submitOrRun(ThreadPoolExecutor executor, Runnable action, + boolean syncRun) throws ExecutionException { + if (executor != null) { + try { + Future future = executor.submit(action); + if (syncRun || executor.getQueue().size() > PIPE_LENGTH) { + try { + future.get(); + } catch (InterruptedException ignore) {/**/} + } + return; + } catch (RejectedExecutionException ex) { + assert executor.isShutdown(); + shutdownExecutor(executor); + } + } + action.run(); + } + + private ArrayList> collectChangedMapRoots(long version) { + long lastStoredVersion = version - 2; + ArrayList> changed = new ArrayList<>(); + for (Iterator> iter = maps.values().iterator(); iter.hasNext(); ) { + MVMap map = iter.next(); + RootReference rootReference = map.setWriteVersion(version); + if (rootReference == null) { + iter.remove(); + } else if (map.getCreateVersion() < version && // if map was created after storing started, skip it + !map.isVolatile() && + map.hasChangesSince(lastStoredVersion)) { + assert rootReference.version <= version : rootReference.version + " > " + version; + Page rootPage = rootReference.root; + if (!rootPage.isSaved() || + // after deletion previously saved leaf + // may pop up as a root, but we still need + // to save new root pos in meta + rootPage.isLeaf()) { + changed.add(rootPage); + } + } + } + RootReference rootReference = meta.setWriteVersion(version); + if (meta.hasChangesSince(lastStoredVersion) || metaChanged) { + assert rootReference != null && rootReference.version <= version + : rootReference == null ? "null" : rootReference.version + " > " + version; + Page rootPage = rootReference.root; + if (!rootPage.isSaved() || + // after deletion previously saved leaf + // may pop up as a root, but we still need + // to save new root pos in meta + rootPage.isLeaf()) { + changed.add(rootPage); + } + } + return changed; + } + + private void serializeAndStore(boolean syncRun, long reservedLow, Supplier reservedHighSupplier, + ArrayList> changed, long time, long version) { + serializationLock.lock(); + try { + Chunk c = createChunk(time, version); + chunks.put(c.id, c); + WriteBuffer buff = getWriteBuffer(); + serializeToBuffer(buff, changed, c, reservedLow, reservedHighSupplier); + + submitOrRun(bufferSaveExecutor, () -> storeBuffer(c, buff, changed), syncRun); + + } catch (MVStoreException e) { + panic(e); + } catch (Throwable e) { + panic(DataUtils.newMVStoreException(DataUtils.ERROR_INTERNAL, "{0}", e.toString(), e)); + } finally { + serializationLock.unlock(); + } + } + + private Chunk createChunk(long time, long version) { + int chunkId = lastChunkId; + if (chunkId != 0) { + chunkId &= Chunk.MAX_ID; + Chunk lastChunk = chunks.get(chunkId); + assert lastChunk != null; + assert lastChunk.isSaved(); + assert lastChunk.version + 1 == version : lastChunk.version + " " + version; + // the metadata of the last chunk was not stored so far, and needs to be + // set now (it's better not to update right after storing, because that + // would modify the meta map again) + layout.put(Chunk.getMetaKey(chunkId), lastChunk.asString()); + // never go backward in time + time = Math.max(lastChunk.time, time); + } + int newChunkId; + while (true) { + newChunkId = ++lastChunkId & Chunk.MAX_ID; + Chunk old = chunks.get(newChunkId); + if (old == null) { + break; + } + if (!old.isSaved()) { + MVStoreException e = DataUtils.newMVStoreException( + DataUtils.ERROR_INTERNAL, + "Last block {0} not stored, possibly due to out-of-memory", old); + panic(e); + } + } + Chunk c = new Chunk(newChunkId); + c.pageCount = 0; + c.pageCountLive = 0; + c.maxLen = 0; + c.maxLenLive = 0; + c.layoutRootPos = Long.MAX_VALUE; + c.block = Long.MAX_VALUE; + c.len = Integer.MAX_VALUE; + c.time = time; + c.version = version; + c.next = Long.MAX_VALUE; + c.occupancy = new BitSet(); + return c; + } + + private void serializeToBuffer(WriteBuffer buff, ArrayList> changed, Chunk c, + long reservedLow, Supplier reservedHighSupplier) { + // need to patch the header later + c.writeChunkHeader(buff, 0); + int headerLength = buff.position() + 44; + buff.position(headerLength); + + long version = c.version; + List toc = new ArrayList<>(); + for (Page p : changed) { + String key = MVMap.getMapRootKey(p.getMapId()); + if (p.getTotalCount() == 0) { + layout.remove(key); + } else { + p.writeUnsavedRecursive(c, buff, toc); + long root = p.getPos(); + layout.put(key, Long.toHexString(root)); + } + } + + acceptChunkOccupancyChanges(c.time, version); + + RootReference layoutRootReference = layout.setWriteVersion(version); + assert layoutRootReference != null; + assert layoutRootReference.version == version : layoutRootReference.version + " != " + version; + metaChanged = false; + + acceptChunkOccupancyChanges(c.time, version); + + onVersionChange(version); + + Page layoutRoot = layoutRootReference.root; + layoutRoot.writeUnsavedRecursive(c, buff, toc); + c.layoutRootPos = layoutRoot.getPos(); + changed.add(layoutRoot); + + // last allocated map id should be captured after the meta map was saved, because + // this will ensure that concurrently created map, which made it into meta before save, + // will have it's id reflected in mapid field of currently written chunk + c.mapId = lastMapId.get(); + + c.tocPos = buff.position(); + long[] tocArray = new long[toc.size()]; + int index = 0; + for (long tocElement : toc) { + tocArray[index++] = tocElement; + buff.putLong(tocElement); + if (DataUtils.isLeafPosition(tocElement)) { + ++leafCount; + } else { + ++nonLeafCount; + } + } + chunksToC.put(c.id, tocArray); + int chunkLength = buff.position(); + + // add the store header and round to the next block + int length = MathUtils.roundUpInt(chunkLength + + Chunk.FOOTER_LENGTH, BLOCK_SIZE); + buff.limit(length); + + saveChunkLock.lock(); + try { + Long reservedHigh = reservedHighSupplier.get(); + long filePos = fileStore.allocate(buff.limit(), reservedLow, reservedHigh); + c.len = buff.limit() / BLOCK_SIZE; + c.block = filePos / BLOCK_SIZE; + assert validateFileLength(c.asString()); + // calculate and set the likely next position + if (reservedLow > 0 || reservedHigh == reservedLow) { + c.next = fileStore.predictAllocation(c.len, 0, 0); + } else { + // just after this chunk + c.next = 0; + } + assert c.pageCountLive == c.pageCount : c; + assert c.occupancy.cardinality() == 0 : c; + + buff.position(0); + assert c.pageCountLive == c.pageCount : c; + assert c.occupancy.cardinality() == 0 : c; + c.writeChunkHeader(buff, headerLength); + + buff.position(buff.limit() - Chunk.FOOTER_LENGTH); + buff.put(c.getFooterBytes()); + } finally { + saveChunkLock.unlock(); + } + } + + private void storeBuffer(Chunk c, WriteBuffer buff, ArrayList> changed) { + saveChunkLock.lock(); + try { + buff.position(0); + long filePos = c.block * BLOCK_SIZE; + write(filePos, buff.getBuffer()); + releaseWriteBuffer(buff); + + // end of the used space is not necessarily the end of the file + boolean storeAtEndOfFile = filePos + buff.limit() >= fileStore.size(); + boolean writeStoreHeader = isWriteStoreHeader(c, storeAtEndOfFile); + lastChunk = c; + if (writeStoreHeader) { + writeStoreHeader(); + } + if (!storeAtEndOfFile) { + // may only shrink after the store header was written + shrinkFileIfPossible(1); + } + } catch (MVStoreException e) { + panic(e); + } catch (Throwable e) { + panic(DataUtils.newMVStoreException(DataUtils.ERROR_INTERNAL, "{0}", e.toString(), e)); + } finally { + saveChunkLock.unlock(); + } + + for (Page p : changed) { + p.releaseSavedPages(); + } + } + + private boolean isWriteStoreHeader(Chunk c, boolean storeAtEndOfFile) { + // whether we need to write the store header + boolean writeStoreHeader = false; + if (!storeAtEndOfFile) { + Chunk lastChunk = this.lastChunk; + if (lastChunk == null) { + writeStoreHeader = true; + } else if (lastChunk.next != c.block) { + // the last prediction did not matched + writeStoreHeader = true; + } else { + long headerVersion = DataUtils.readHexLong(storeHeader, HDR_VERSION, 0); + if (lastChunk.version - headerVersion > 20) { + // we write after at least every 20 versions + writeStoreHeader = true; + } else { + for (int chunkId = DataUtils.readHexInt(storeHeader, HDR_CHUNK, 0); + !writeStoreHeader && chunkId <= lastChunk.id; ++chunkId) { + // one of the chunks in between + // was removed + writeStoreHeader = !chunks.containsKey(chunkId); + } + } + } + } + + if (storeHeader.remove(HDR_CLEAN) != null) { + writeStoreHeader = true; + } + return writeStoreHeader; + } + + /** + * Get a buffer for writing. This caller must synchronize on the store + * before calling the method and until after using the buffer. + * + * @return the buffer + */ + private WriteBuffer getWriteBuffer() { + WriteBuffer buff = writeBufferPool.poll(); + if (buff != null) { + buff.clear(); + } else { + buff = new WriteBuffer(); + } + return buff; + } + + /** + * Release a buffer for writing. This caller must synchronize on the store + * before calling the method and until after using the buffer. + * + * @param buff the buffer than can be re-used + */ + private void releaseWriteBuffer(WriteBuffer buff) { + if (buff.capacity() <= 4 * 1024 * 1024) { + writeBufferPool.offer(buff); + } + } + + private static boolean canOverwriteChunk(Chunk c, long oldestVersionToKeep) { + return !c.isLive() && c.unusedAtVersion < oldestVersionToKeep; + } + + private boolean isSeasonedChunk(Chunk chunk, long time) { + return retentionTime < 0 || chunk.time + retentionTime <= time; + } + + private long getTimeSinceCreation() { + return Math.max(0, getTimeAbsolute() - creationTime); + } + + private long getTimeAbsolute() { + long now = System.currentTimeMillis(); + if (lastTimeAbsolute != 0 && now < lastTimeAbsolute) { + // time seems to have run backwards - this can happen + // when the system time is adjusted, for example + // on a leap second + now = lastTimeAbsolute; + } else { + lastTimeAbsolute = now; + } + return now; + } + + /** + * Apply the freed space to the chunk metadata. The metadata is updated, but + * completely free chunks are not removed from the set of chunks, and the + * disk space is not yet marked as free. They are queued instead and wait until + * their usage is over. + */ + private void acceptChunkOccupancyChanges(long time, long version) { + assert serializationLock.isHeldByCurrentThread(); + if (lastChunk != null) { + Set modifiedChunks = new HashSet<>(); + while (true) { + RemovedPageInfo rpi; + while ((rpi = removedPages.peek()) != null && rpi.version < version) { + rpi = removedPages.poll(); // could be different from the peeked one + assert rpi != null; // since nobody else retrieves from queue + assert rpi.version < version : rpi + " < " + version; + int chunkId = rpi.getPageChunkId(); + Chunk chunk = chunks.get(chunkId); + assert !isOpen() || chunk != null : chunkId; + if (chunk != null) { + modifiedChunks.add(chunk); + if (chunk.accountForRemovedPage(rpi.getPageNo(), rpi.getPageLength(), + rpi.isPinned(), time, rpi.version)) { + deadChunks.offer(chunk); + } + } + } + if (modifiedChunks.isEmpty()) { + return; + } + for (Chunk chunk : modifiedChunks) { + int chunkId = chunk.id; + layout.put(Chunk.getMetaKey(chunkId), chunk.asString()); + } + modifiedChunks.clear(); + } + } + } + + /** + * Shrink the file if possible, and if at least a given percentage can be + * saved. + * + * @param minPercent the minimum percentage to save + */ + private void shrinkFileIfPossible(int minPercent) { + assert saveChunkLock.isHeldByCurrentThread(); + if (fileStore.isReadOnly()) { + return; + } + long end = getFileLengthInUse(); + long fileSize = fileStore.size(); + if (end >= fileSize) { + return; + } + if (minPercent > 0 && fileSize - end < BLOCK_SIZE) { + return; + } + int savedPercent = (int) (100 - (end * 100 / fileSize)); + if (savedPercent < minPercent) { + return; + } + if (isOpenOrStopping()) { + sync(); + } + fileStore.truncate(end); + } + + /** + * Get the position right after the last used byte. + * + * @return the position + */ + private long getFileLengthInUse() { + assert saveChunkLock.isHeldByCurrentThread(); + long result = fileStore.getFileLengthInUse(); + assert result == measureFileLengthInUse() : result + " != " + measureFileLengthInUse(); + return result; + } + + /** + * Get the index of the first block after last occupied one. + * It marks the beginning of the last (infinite) free space. + * + * @return block index + */ + private long getAfterLastBlock() { + assert saveChunkLock.isHeldByCurrentThread(); + return fileStore.getAfterLastBlock(); + } + + private long measureFileLengthInUse() { + assert saveChunkLock.isHeldByCurrentThread(); + long size = 2; + for (Chunk c : chunks.values()) { + if (c.isSaved()) { + size = Math.max(size, c.block + c.len); + } + } + return size * BLOCK_SIZE; + } + + /** + * Check whether there are any unsaved changes. + * + * @return if there are any changes + */ + public boolean hasUnsavedChanges() { + if (metaChanged) { + return true; + } + long lastStoredVersion = currentVersion - 1; + for (MVMap m : maps.values()) { + if (!m.isClosed()) { + if(m.hasChangesSince(lastStoredVersion)) { + return true; + } + } + } + return layout.hasChangesSince(lastStoredVersion) && lastStoredVersion > INITIAL_VERSION; + } + + private Chunk readChunkHeader(long block) { + long p = block * BLOCK_SIZE; + ByteBuffer buff = fileStore.readFully(p, Chunk.MAX_HEADER_LENGTH); + return Chunk.readChunkHeader(buff, p); + } + + private Chunk readChunkHeaderOptionally(long block) { + try { + Chunk chunk = readChunkHeader(block); + return chunk.block != block ? null : chunk; + } catch (Exception ignore) { + return null; + } + } + + private Chunk readChunkHeaderOptionally(long block, int expectedId) { + Chunk chunk = readChunkHeaderOptionally(block); + return chunk == null || chunk.id != expectedId ? null : chunk; + } + + /** + * Compact by moving all chunks next to each other. + */ + public void compactMoveChunks() { + compactMoveChunks(100, Long.MAX_VALUE); + } + + /** + * Compact the store by moving all chunks next to each other, if there is + * free space between chunks. This might temporarily increase the file size. + * Chunks are overwritten irrespective of the current retention time. Before + * overwriting chunks and before resizing the file, syncFile() is called. + * + * @param targetFillRate do nothing if the file store fill rate is higher + * than this + * @param moveSize the number of bytes to move + * @return true if any chunks were moved as result of this operation, false otherwise + */ + boolean compactMoveChunks(int targetFillRate, long moveSize) { + boolean res = false; + storeLock.lock(); + try { + checkOpen(); + // because serializationExecutor is a single-threaded one and + // 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); + 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); + saveChunkLock.lock(); + try { + if (lastChunk != null && reuseSpace && getFillRate() <= targetFillRate) { + res = compactMoveChunks(moveSize); + } + } finally { + saveChunkLock.unlock(); + } + } finally { + serializationLock.unlock(); + } + } catch (MVStoreException e) { + panic(e); + } catch (Throwable e) { + panic(DataUtils.newMVStoreException( + DataUtils.ERROR_INTERNAL, "{0}", e.toString(), e)); + } finally { + unlockAndCheckPanicCondition(); + } + return res; + } + + private boolean compactMoveChunks(long moveSize) { + assert storeLock.isHeldByCurrentThread(); + dropUnusedChunks(); + long start = fileStore.getFirstFree() / BLOCK_SIZE; + Iterable chunksToMove = findChunksToMove(start, moveSize); + if (chunksToMove == null) { + return false; + } + compactMoveChunks(chunksToMove); + return true; + } + + private Iterable findChunksToMove(long startBlock, long moveSize) { + long maxBlocksToMove = moveSize / BLOCK_SIZE; + Iterable result = null; + if (maxBlocksToMove > 0) { + PriorityQueue queue = new PriorityQueue<>(chunks.size() / 2 + 1, + (o1, o2) -> { + // instead of selection just closest to beginning of the file, + // pick smaller chunk(s) which sit in between bigger holes + int res = Integer.compare(o2.collectPriority, o1.collectPriority); + if (res != 0) { + return res; + } + return Long.signum(o2.block - o1.block); + }); + long size = 0; + for (Chunk chunk : chunks.values()) { + if (chunk.isSaved() && chunk.block > startBlock) { + chunk.collectPriority = getMovePriority(chunk); + queue.offer(chunk); + size += chunk.len; + while (size > maxBlocksToMove) { + Chunk removed = queue.poll(); + if (removed == null) { + break; + } + size -= removed.len; + } + } + } + if (!queue.isEmpty()) { + ArrayList list = new ArrayList<>(queue); + list.sort(Chunk.PositionComparator.INSTANCE); + result = list; + } + } + return result; + } + + private int getMovePriority(Chunk chunk) { + return fileStore.getMovePriority((int)chunk.block); + } + + private void compactMoveChunks(Iterable move) { + assert storeLock.isHeldByCurrentThread(); + assert serializationLock.isHeldByCurrentThread(); + assert saveChunkLock.isHeldByCurrentThread(); + if (move != null) { + // this will ensure better recognition of the last chunk + // in case of power failure, since we are going to move older chunks + // to the end of the file + writeStoreHeader(); + sync(); + + Iterator iterator = move.iterator(); + assert iterator.hasNext(); + long leftmostBlock = iterator.next().block; + long originalBlockCount = getAfterLastBlock(); + // we need to ensure that chunks moved within the following loop + // do not overlap with space just released by chunks moved before them, + // hence the need to reserve this area [leftmostBlock, originalBlockCount) + for (Chunk chunk : move) { + moveChunk(chunk, leftmostBlock, originalBlockCount); + } + // update the metadata (hopefully within the file) + store(leftmostBlock, originalBlockCount); + sync(); + + Chunk chunkToMove = lastChunk; + assert chunkToMove != null; + long postEvacuationBlockCount = getAfterLastBlock(); + + boolean chunkToMoveIsAlreadyInside = chunkToMove.block < leftmostBlock; + boolean movedToEOF = !chunkToMoveIsAlreadyInside; + // move all chunks, which previously did not fit before reserved area + // now we can re-use previously reserved area [leftmostBlock, originalBlockCount), + // but need to reserve [originalBlockCount, postEvacuationBlockCount) + for (Chunk c : move) { + if (c.block >= originalBlockCount && + moveChunk(c, originalBlockCount, postEvacuationBlockCount)) { + assert c.block < originalBlockCount; + movedToEOF = true; + } + } + assert postEvacuationBlockCount >= getAfterLastBlock(); + + if (movedToEOF) { + boolean moved = moveChunkInside(chunkToMove, originalBlockCount); + + // store a new chunk with updated metadata (hopefully within a file) + store(originalBlockCount, postEvacuationBlockCount); + sync(); + // if chunkToMove did not fit within originalBlockCount (move is + // false), and since now previously reserved area + // [originalBlockCount, postEvacuationBlockCount) also can be + // used, lets try to move that chunk into this area, closer to + // the beginning of the file + long lastBoundary = moved || chunkToMoveIsAlreadyInside ? + postEvacuationBlockCount : chunkToMove.block; + moved = !moved && moveChunkInside(chunkToMove, lastBoundary); + if (moveChunkInside(lastChunk, lastBoundary) || moved) { + store(lastBoundary, -1); + } + } + + shrinkFileIfPossible(0); + sync(); + } + } + + private void store(long reservedLow, long reservedHigh) { + saveChunkLock.unlock(); + try { + serializationLock.unlock(); + try { + storeNow(true, reservedLow, () -> reservedHigh); + } finally { + serializationLock.lock(); + } + } finally { + saveChunkLock.lock(); + } + } + + private boolean moveChunkInside(Chunk chunkToMove, long boundary) { + boolean res = chunkToMove.block >= boundary && + fileStore.predictAllocation(chunkToMove.len, boundary, -1) < boundary && + moveChunk(chunkToMove, boundary, -1); + assert !res || chunkToMove.block + chunkToMove.len <= boundary; + return res; + } + + /** + * Move specified chunk into free area of the file. "Reserved" area + * specifies file interval to be avoided, when un-allocated space will be + * chosen for a new chunk's location. + * + * @param chunk to move + * @param reservedAreaLow low boundary of reserved area, inclusive + * @param reservedAreaHigh high boundary of reserved area, exclusive + * @return true if block was moved, false otherwise + */ + private boolean moveChunk(Chunk chunk, long reservedAreaLow, long reservedAreaHigh) { + // ignore if already removed during the previous store operations + // those are possible either as explicit commit calls + // or from meta map updates at the end of this method + if (!chunks.containsKey(chunk.id)) { + return false; + } + long start = chunk.block * BLOCK_SIZE; + int length = chunk.len * BLOCK_SIZE; + long block; + WriteBuffer buff = getWriteBuffer(); + try { + buff.limit(length); + ByteBuffer readBuff = fileStore.readFully(start, length); + Chunk chunkFromFile = Chunk.readChunkHeader(readBuff, start); + int chunkHeaderLen = readBuff.position(); + buff.position(chunkHeaderLen); + buff.put(readBuff); + long pos = fileStore.allocate(length, reservedAreaLow, reservedAreaHigh); + block = pos / BLOCK_SIZE; + // in the absence of a reserved area, + // block should always move closer to the beginning of the file + assert reservedAreaHigh > 0 || block <= chunk.block : block + " " + chunk; + buff.position(0); + // can not set chunk's new block/len until it's fully written at new location, + // because concurrent reader can pick it up prematurely, + // also occupancy accounting fields should not leak into header + chunkFromFile.block = block; + chunkFromFile.next = 0; + chunkFromFile.writeChunkHeader(buff, chunkHeaderLen); + buff.position(length - Chunk.FOOTER_LENGTH); + buff.put(chunkFromFile.getFooterBytes()); + buff.position(0); + write(pos, buff.getBuffer()); + } finally { + releaseWriteBuffer(buff); + } + fileStore.free(start, length); + chunk.block = block; + chunk.next = 0; + layout.put(Chunk.getMetaKey(chunk.id), chunk.asString()); + return true; + } + + /** + * Force all stored changes to be written to the storage. The default + * implementation calls FileChannel.force(true). + */ + public void sync() { + checkOpen(); + FileStore f = fileStore; + if (f != null) { + f.sync(); + } + } + + /** + * Compact store file, that is, compact blocks that have a low + * fill rate, and move chunks next to each other. This will typically + * shrink the file. Changes are flushed to the file, and old + * chunks are overwritten. + * + * @param maxCompactTime the maximum time in milliseconds to compact + */ + public void compactFile(int maxCompactTime) { + setRetentionTime(0); + long stopAt = System.nanoTime() + maxCompactTime * 1_000_000L; + while (compact(95, 16 * 1024 * 1024)) { + sync(); + compactMoveChunks(95, 16 * 1024 * 1024); + if (System.nanoTime() - stopAt > 0L) { + break; + } + } + } + + /** + * Try to increase the fill rate by re-writing partially full chunks. Chunks + * with a low number of live items are re-written. + *

+ * If the current fill rate is higher than the target fill rate, nothing is + * done. + *

+ * Please note this method will not necessarily reduce the file size, as + * empty chunks are not overwritten. + *

+ * Only data of open maps can be moved. For maps that are not open, the old + * chunk is still referenced. Therefore, it is recommended to open all maps + * before calling this method. + * + * @param targetFillRate the minimum percentage of live entries + * @param write the minimum number of bytes to write + * @return if a chunk was re-written + */ + public boolean compact(int targetFillRate, int write) { + if (reuseSpace && lastChunk != null) { + checkOpen(); + if (targetFillRate > 0 && getChunksFillRate() < targetFillRate) { + // We can't wait forever for the lock here, + // because if called from the background thread, + // it might go into deadlock with concurrent database closure + // and attempt to stop this thread. + try { + if (storeLock.tryLock(10, TimeUnit.MILLISECONDS)) { + try { + return rewriteChunks(write, 100); + } finally { + storeLock.unlock(); + } + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + return false; + } + + private boolean rewriteChunks(int writeLimit, int targetFillRate) { + serializationLock.lock(); + try { + TxCounter txCounter = registerVersionUsage(); + try { + acceptChunkOccupancyChanges(getTimeSinceCreation(), currentVersion); + Iterable old = findOldChunks(writeLimit, targetFillRate); + if (old != null) { + HashSet idSet = createIdSet(old); + return !idSet.isEmpty() && compactRewrite(idSet) > 0; + } + } finally { + deregisterVersionUsage(txCounter); + } + return false; + } finally { + serializationLock.unlock(); + } + } + + /** + * Get the current fill rate (percentage of used space in the file). Unlike + * the fill rate of the store, here we only account for chunk data; the fill + * rate here is how much of the chunk data is live (still referenced). Young + * chunks are considered live. + * + * @return the fill rate, in percent (100 is completely full) + */ + public int getChunksFillRate() { + return getChunksFillRate(true); + } + + public int getRewritableChunksFillRate() { + return getChunksFillRate(false); + } + + private int getChunksFillRate(boolean all) { + long maxLengthSum = 1; + long maxLengthLiveSum = 1; + long time = getTimeSinceCreation(); + for (Chunk c : chunks.values()) { + if (all || isRewritable(c, time)) { + assert c.maxLen >= 0; + maxLengthSum += c.maxLen; + maxLengthLiveSum += c.maxLenLive; + } + } + // the fill rate of all chunks combined + int fillRate = (int) (100 * maxLengthLiveSum / maxLengthSum); + return fillRate; + } + + /** + * Get data chunks count. + * + * @return number of existing chunks in store. + */ + public int getChunkCount() { + return chunks.size(); + } + + /** + * Get data pages count. + * + * @return number of existing pages in store. + */ + public int getPageCount() { + int count = 0; + for (Chunk chunk : chunks.values()) { + count += chunk.pageCount; + } + return count; + } + + /** + * Get live data pages count. + * + * @return number of existing live pages in store. + */ + public int getLivePageCount() { + int count = 0; + for (Chunk chunk : chunks.values()) { + count += chunk.pageCountLive; + } + return count; + } + + private int getProjectedFillRate(int thresholdChunkFillRate) { + saveChunkLock.lock(); + try { + int vacatedBlocks = 0; + long maxLengthSum = 1; + long maxLengthLiveSum = 1; + long time = getTimeSinceCreation(); + for (Chunk c : chunks.values()) { + assert c.maxLen >= 0; + if (isRewritable(c, time) && c.getFillRate() <= thresholdChunkFillRate) { + assert c.maxLen >= c.maxLenLive; + vacatedBlocks += c.len; + maxLengthSum += c.maxLen; + maxLengthLiveSum += c.maxLenLive; + } + } + int additionalBlocks = (int) (vacatedBlocks * maxLengthLiveSum / maxLengthSum); + int fillRate = fileStore.getProjectedFillRate(vacatedBlocks - additionalBlocks); + return fillRate; + } finally { + saveChunkLock.unlock(); + } + } + + public int getFillRate() { + saveChunkLock.lock(); + try { + return fileStore.getFillRate(); + } finally { + saveChunkLock.unlock(); + } + } + + private Iterable findOldChunks(int writeLimit, int targetFillRate) { + assert lastChunk != null; + long time = getTimeSinceCreation(); + + // the queue will contain chunks we want to free up + // the smaller the collectionPriority, the more desirable this chunk's re-write is + // queue will be ordered in descending order of collectionPriority values, + // so most desirable chunks will stay at the tail + PriorityQueue queue = new PriorityQueue<>(this.chunks.size() / 4 + 1, + (o1, o2) -> { + int comp = Integer.compare(o2.collectPriority, o1.collectPriority); + if (comp == 0) { + comp = Long.compare(o2.maxLenLive, o1.maxLenLive); + } + return comp; + }); + + long totalSize = 0; + long latestVersion = lastChunk.version + 1; + for (Chunk chunk : chunks.values()) { + // only look at chunk older than the retention time + // (it's possible to compact chunks earlier, but right + // now we don't do that) + int fillRate = chunk.getFillRate(); + if (isRewritable(chunk, time) && fillRate <= targetFillRate) { + long age = Math.max(1, latestVersion - chunk.version); + chunk.collectPriority = (int) (fillRate * 1000 / age); + totalSize += chunk.maxLenLive; + queue.offer(chunk); + while (totalSize > writeLimit) { + Chunk removed = queue.poll(); + if (removed == null) { + break; + } + totalSize -= removed.maxLenLive; + } + } + } + + return queue.isEmpty() ? null : queue; + } + + private boolean isRewritable(Chunk chunk, long time) { + return chunk.isRewritable() && isSeasonedChunk(chunk, time); + } + + private int compactRewrite(Set set) { + assert storeLock.isHeldByCurrentThread(); + assert currentStoreVersion < 0; // we should be able to do tryCommit() -> store() + acceptChunkOccupancyChanges(getTimeSinceCreation(), currentVersion); + int rewrittenPageCount = rewriteChunks(set, false); + acceptChunkOccupancyChanges(getTimeSinceCreation(), currentVersion); + rewrittenPageCount += rewriteChunks(set, true); + return rewrittenPageCount; + } + + private int rewriteChunks(Set set, boolean secondPass) { + int rewrittenPageCount = 0; + for (int chunkId : set) { + Chunk chunk = chunks.get(chunkId); + long[] toc = getToC(chunk); + if (toc != null) { + for (int pageNo = 0; (pageNo = chunk.occupancy.nextClearBit(pageNo)) < chunk.pageCount; ++pageNo) { + long tocElement = toc[pageNo]; + int mapId = DataUtils.getPageMapId(tocElement); + MVMap map = mapId == layout.getId() ? layout : mapId == meta.getId() ? meta : getMap(mapId); + if (map != null && !map.isClosed()) { + assert !map.isSingleWriter(); + if (secondPass || DataUtils.isLeafPosition(tocElement)) { + long pagePos = DataUtils.getPagePos(chunkId, tocElement); + serializationLock.unlock(); + try { + if (map.rewritePage(pagePos)) { + ++rewrittenPageCount; + if (map == meta) { + markMetaChanged(); + } + } + } finally { + serializationLock.lock(); + } + } + } + } + } + } + return rewrittenPageCount; + } + + private static HashSet createIdSet(Iterable toCompact) { + HashSet set = new HashSet<>(); + for (Chunk c : toCompact) { + set.add(c.id); + } + return set; + } + + /** + * Read a page. + * + * @param key type + * @param value type + * + * @param map the map + * @param pos the page position + * @return the page + */ + Page readPage(MVMap map, long pos) { + try { + if (!DataUtils.isPageSaved(pos)) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_FILE_CORRUPT, "Position 0"); + } + Page p = readPageFromCache(pos); + if (p == null) { + Chunk chunk = getChunk(pos); + int pageOffset = DataUtils.getPageOffset(pos); + try { + ByteBuffer buff = chunk.readBufferForPage(fileStore, pageOffset, pos); + p = Page.read(buff, pos, map); + } catch (MVStoreException e) { + throw e; + } catch (Exception e) { + throw DataUtils.newMVStoreException(DataUtils.ERROR_FILE_CORRUPT, + "Unable to read the page at position {0}, chunk {1}, offset {2}", + pos, chunk.id, pageOffset, e); + } + cachePage(p); + } + return p; + } catch (MVStoreException e) { + if (recoveryMode) { + return map.createEmptyLeaf(); + } + throw e; + } + } + + private long[] getToC(Chunk chunk) { + if (chunk.tocPos == 0) { + // legacy chunk without table of content + return null; + } + long[] toc = chunksToC.get(chunk.id); + if (toc == null) { + toc = chunk.readToC(fileStore); + chunksToC.put(chunk.id, toc, toc.length * 8); + } + assert toc.length == chunk.pageCount : toc.length + " != " + chunk.pageCount; + return toc; + } + + @SuppressWarnings("unchecked") + private Page readPageFromCache(long pos) { + return cache == null ? null : (Page)cache.get(pos); + } + + /** + * Remove a page. + * @param pos the position of the page + * @param version at which page was removed + * @param pinned whether page is considered pinned + * @param pageNo sequential page number within chunk + */ + void accountForRemovedPage(long pos, long version, boolean pinned, int pageNo) { + assert DataUtils.isPageSaved(pos); + if (pageNo < 0) { + pageNo = calculatePageNo(pos); + } + RemovedPageInfo rpi = new RemovedPageInfo(pos, pinned, version, pageNo); + removedPages.add(rpi); + } + + private int calculatePageNo(long pos) { + int pageNo = -1; + Chunk chunk = getChunk(pos); + long[] toC = getToC(chunk); + if (toC != null) { + int offset = DataUtils.getPageOffset(pos); + int low = 0; + int high = toC.length - 1; + while (low <= high) { + int mid = (low + high) >>> 1; + long midVal = DataUtils.getPageOffset(toC[mid]); + if (midVal < offset) { + low = mid + 1; + } else if (midVal > offset) { + high = mid - 1; + } else { + pageNo = mid; + break; + } + } + } + return pageNo; + } + + Compressor getCompressorFast() { + if (compressorFast == null) { + compressorFast = new CompressLZF(); + } + return compressorFast; + } + + Compressor getCompressorHigh() { + if (compressorHigh == null) { + compressorHigh = new CompressDeflate(); + } + return compressorHigh; + } + + int getCompressionLevel() { + return compressionLevel; + } + + public int getPageSplitSize() { + return pageSplitSize; + } + + public int getKeysPerPage() { + return keysPerPage; + } + + public long getMaxPageSize() { + return cache == null ? Long.MAX_VALUE : cache.getMaxItemSize() >> 4; + } + + public boolean getReuseSpace() { + return reuseSpace; + } + + /** + * Whether empty space in the file should be re-used. If enabled, old data + * is overwritten (default). If disabled, writes are appended at the end of + * the file. + *

+ * This setting is specially useful for online backup. To create an online + * backup, disable this setting, then copy the file (starting at the + * beginning of the file). In this case, concurrent backup and write + * operations are possible (obviously the backup process needs to be faster + * than the write operations). + * + * @param reuseSpace the new value + */ + public void setReuseSpace(boolean reuseSpace) { + this.reuseSpace = reuseSpace; + } + + public int getRetentionTime() { + return retentionTime; + } + + /** + * How long to retain old, persisted chunks, in milliseconds. Chunks that + * are older may be overwritten once they contain no live data. + *

+ * The default value is 45000 (45 seconds) when using the default file + * store. It is assumed that a file system and hard disk will flush all + * write buffers within this time. Using a lower value might be dangerous, + * unless the file system and hard disk flush the buffers earlier. To + * manually flush the buffers, use + * MVStore.getFile().force(true), however please note that + * according to various tests this does not always work as expected + * depending on the operating system and hardware. + *

+ * The retention time needs to be long enough to allow reading old chunks + * while traversing over the entries of a map. + *

+ * This setting is not persisted. + * + * @param ms how many milliseconds to retain old chunks (0 to overwrite them + * as early as possible) + */ + 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. + * + * @param count the number of versions to keep + */ + public void setVersionsToKeep(int count) { + this.versionsToKeep = count; + } + + /** + * Get the oldest version to retain in memory (for in-memory stores). + * + * @return the version + */ + public long getVersionsToKeep() { + return versionsToKeep; + } + + /** + * Get the oldest version to retain. + * We keep at least number of previous versions specified by "versionsToKeep" + * configuration parameter (default 5). + * Previously it was used only in case of non-persistent MVStore. + * Now it's honored in all cases (although H2 always sets it to zero). + * Oldest version determination also takes into account calls (de)registerVersionUsage(), + * an will not release the version, while version is still in use. + * + * @return the version + */ + long getOldestVersionToKeep() { + long v = oldestVersionToKeep.get(); + v = Math.max(v - versionsToKeep, INITIAL_VERSION); + if (fileStore != null) { + long storeVersion = lastChunkVersion() - 1; + if (storeVersion != INITIAL_VERSION && storeVersion < v) { + v = storeVersion; + } + } + return v; + } + + private void setOldestVersionToKeep(long oldestVersionToKeep) { + boolean success; + do { + long current = this.oldestVersionToKeep.get(); + // Oldest version may only advance, never goes back + success = oldestVersionToKeep <= current || + this.oldestVersionToKeep.compareAndSet(current, oldestVersionToKeep); + } while (!success); + notifyAboutOldestVersion(oldestVersionToKeep); + } + + public void setCleaner(Cleaner cleaner) { + this.cleaner = cleaner; + } + + private void notifyAboutOldestVersion(long oldestVersionToKeep) { + if (cleaner != null && cleaner.needCleanup() && bufferSaveExecutor != null) { + Runnable blobCleaner = () -> { + notifyCleaner(oldestVersionToKeep); + }; + try { + bufferSaveExecutor.execute(blobCleaner); + } catch (RejectedExecutionException ignore) { + } catch (MVStoreException e) { + panic(e); + } catch (Throwable e) { + panic(DataUtils.newMVStoreException(DataUtils.ERROR_INTERNAL, "{0}", e.toString(), e)); + } + } + } + + public void notifyCleaner(long oldestVersionToKeep) { + if (cleaner != null && cleaner.needCleanup()) { + cleaner.cleanup(oldestVersionToKeep); + } + } + + private long lastChunkVersion() { + Chunk chunk = lastChunk; + return chunk == null ? INITIAL_VERSION + 1 : chunk.version; + } + + /** + * Check whether all data can be read from this version. This requires that + * all chunks referenced by this version are still available (not + * overwritten). + * + * @param version the version + * @return true if all data can be read + */ + private boolean isKnownVersion(long version) { + if (version > currentVersion || version < 0) { + return false; + } + if (version == currentVersion || chunks.isEmpty()) { + // no stored data + return true; + } + // need to check if a chunk for this version exists + Chunk c = getChunkForVersion(version); + if (c == null) { + return false; + } + // also, all chunks referenced by this version + // need to be available in the file + MVMap oldLayoutMap = getLayoutMap(version); + try { + for (Iterator it = oldLayoutMap.keyIterator(DataUtils.META_CHUNK); it.hasNext();) { + String chunkKey = it.next(); + if (!chunkKey.startsWith(DataUtils.META_CHUNK)) { + break; + } + if (!layout.containsKey(chunkKey)) { + String s = oldLayoutMap.get(chunkKey); + Chunk c2 = Chunk.fromString(s); + Chunk test = readChunkHeaderAndFooter(c2.block, c2.id); + if (test == null) { + return false; + } + } + } + } catch (MVStoreException e) { + // the chunk missing where the metadata is stored + return false; + } + return true; + } + + /** + * Adjust amount of "unsaved memory" meaning amount of RAM occupied by pages + * not saved yet to the file. This is the amount which triggers auto-commit. + * + * @param memory adjustment + */ + public void registerUnsavedMemory(int memory) { + // this counter was intentionally left unprotected against race + // condition for performance reasons + // TODO: evaluate performance impact of atomic implementation, + // since updates to unsavedMemory are largely aggregated now + unsavedMemory += memory; + int newValue = unsavedMemory; + if (newValue > autoCommitMemory && autoCommitMemory > 0) { + saveNeeded = true; + } + } + + boolean isSaveNeeded() { + return saveNeeded; + } + + /** + * This method is called before writing to a map. + * + * @param map the map + */ + void beforeWrite(MVMap map) { + if (saveNeeded && fileStore != null && isOpenOrStopping() && + // condition below is to prevent potential deadlock, + // because we should never seek storeLock while holding + // map root lock + (storeLock.isHeldByCurrentThread() || !map.getRoot().isLockedByCurrentThread()) && + // to avoid infinite recursion via store() -> dropUnusedChunks() -> layout.remove() + map != layout) { + + saveNeeded = false; + // check again, because it could have been written by now + if (autoCommitMemory > 0 && needStore()) { + // if unsaved memory creation rate is to high, + // some back pressure need to be applied + // to slow things down and avoid OOME + if (requireStore() && !map.isSingleWriter()) { + commit(MVStore::requireStore); + } else { + tryCommit(MVStore::needStore); + } + } + } + } + + private boolean requireStore() { + return 3 * unsavedMemory > 4 * autoCommitMemory; + } + + private boolean needStore() { + return unsavedMemory > autoCommitMemory; + } + + /** + * Get the store version. The store version is usually used to upgrade the + * structure of the store after upgrading the application. Initially the + * store version is 0, until it is changed. + * + * @return the store version + */ + public int getStoreVersion() { + checkOpen(); + String x = meta.get("setting.storeVersion"); + return x == null ? 0 : DataUtils.parseHexInt(x); + } + + /** + * Update the store version. + * + * @param version the new store version + */ + public void setStoreVersion(int version) { + storeLock.lock(); + try { + checkOpen(); + markMetaChanged(); + meta.put("setting.storeVersion", Integer.toHexString(version)); + } finally { + storeLock.unlock(); + } + } + + /** + * Revert to the beginning of the current version, reverting all uncommitted + * changes. + */ + public void rollback() { + rollbackTo(currentVersion); + } + + /** + * Revert to the beginning of the given version. All later changes (stored + * or not) are forgotten. All maps that were created later are closed. A + * rollback to a version before the last stored version is immediately + * persisted. Rollback to version 0 means all data is removed. + * + * @param version the version to revert to + */ + public void rollbackTo(long version) { + storeLock.lock(); + try { + checkOpen(); + currentVersion = version; + if (version == 0) { + // special case: remove all data + layout.setInitialRoot(layout.createEmptyLeaf(), INITIAL_VERSION); + meta.setInitialRoot(meta.createEmptyLeaf(), INITIAL_VERSION); + layout.put(META_ID_KEY, Integer.toHexString(meta.getId())); + deadChunks.clear(); + removedPages.clear(); + chunks.clear(); + clearCaches(); + if (fileStore != null) { + saveChunkLock.lock(); + try { + fileStore.clear(); + } finally { + saveChunkLock.unlock(); + } + } + lastChunk = null; + versions.clear(); + setWriteVersion(version); + metaChanged = false; + for (MVMap m : maps.values()) { + m.close(); + } + return; + } + DataUtils.checkArgument( + isKnownVersion(version), + "Unknown version {0}", version); + + TxCounter txCounter; + while ((txCounter = versions.peekLast()) != null && txCounter.version >= version) { + versions.removeLast(); + } + currentTxCounter = new TxCounter(version); + + if (!layout.rollbackRoot(version)) { + MVMap layoutMap = getLayoutMap(version); + layout.setInitialRoot(layoutMap.getRootPage(), version); + } + if (!meta.rollbackRoot(version)) { + meta.setRootPos(getRootPos(meta.getId()), version - 1); + } + metaChanged = false; + + for (MVMap m : new ArrayList<>(maps.values())) { + int id = m.getId(); + if (m.getCreateVersion() >= version) { + m.close(); + maps.remove(id); + } else { + if (!m.rollbackRoot(version)) { + m.setRootPos(getRootPos(id), version - 1); + } + } + } + + deadChunks.clear(); + removedPages.clear(); + clearCaches(); + + serializationLock.lock(); + try { + Chunk keep = getChunkForVersion(version); + if (keep != null) { + saveChunkLock.lock(); + try { + setLastChunk(keep); + storeHeader.put(HDR_CLEAN, 1); + writeStoreHeader(); + readStoreHeader(); + } finally { + saveChunkLock.unlock(); + } + } + } finally { + serializationLock.unlock(); + } + onVersionChange(currentVersion); + assert !hasUnsavedChanges(); + } finally { + unlockAndCheckPanicCondition(); + } + } + + private void clearCaches() { + if (cache != null) { + cache.clear(); + } + if (chunksToC != null) { + chunksToC.clear(); + } + } + + private long getRootPos(int mapId) { + String root = layout.get(MVMap.getMapRootKey(mapId)); + return root == null ? 0 : DataUtils.parseHexLong(root); + } + + /** + * Get the current version of the data. When a new store is created, the + * version is 0. + * + * @return the version + */ + public long getCurrentVersion() { + return currentVersion; + } + + /** + * Get the file store. + * + * @return the file store + */ + public FileStore getFileStore() { + return fileStore; + } + + /** + * Get the store header. This data is for informational purposes only. The + * data is subject to change in future versions. The data should not be + * modified (doing so may corrupt the store). + * + * @return the store header + */ + public Map getStoreHeader() { + return storeHeader; + } + + private void checkOpen() { + if (!isOpenOrStopping()) { + throw DataUtils.newMVStoreException(DataUtils.ERROR_CLOSED, + "This store is closed", panicException); + } + } + + /** + * Rename a map. + * + * @param map the map + * @param newName the new name + */ + public void renameMap(MVMap map, String newName) { + checkOpen(); + DataUtils.checkArgument(map != layout && map != meta, + "Renaming the meta map is not allowed"); + int id = map.getId(); + String oldName = getMapName(id); + if (oldName != null && !oldName.equals(newName)) { + String idHexStr = Integer.toHexString(id); + // at first create a new name as an "alias" + String existingIdHexStr = meta.putIfAbsent(DataUtils.META_NAME + newName, idHexStr); + // we need to cope with the case of previously unfinished rename + DataUtils.checkArgument( + existingIdHexStr == null || existingIdHexStr.equals(idHexStr), + "A map named {0} already exists", newName); + // switch roles of a new and old names - old one is an alias now + meta.put(MVMap.getMapKey(id), map.asString(newName)); + // get rid of the old name completely + meta.remove(DataUtils.META_NAME + oldName); + markMetaChanged(); + } + } + + /** + * Remove a map from the current version of the store. + * + * @param map the map to remove + */ + public void removeMap(MVMap map) { + storeLock.lock(); + try { + checkOpen(); + DataUtils.checkArgument(layout != meta && map != meta, + "Removing the meta map is not allowed"); + RootReference rootReference = map.clearIt(); + map.close(); + + updateCounter += rootReference.updateCounter; + updateAttemptCounter += rootReference.updateAttemptCounter; + + int id = map.getId(); + String name = getMapName(id); + if (meta.remove(MVMap.getMapKey(id)) != null) { + markMetaChanged(); + } + 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(); + } + } + + /** + * Performs final stage of map removal - delete root location info from the layout table. + * Map is supposedly closed and anonymous and has no outstanding usage by now. + * + * @param mapId to deregister + */ + void deregisterMapRoot(int mapId) { + if (layout.remove(MVMap.getMapRootKey(mapId)) != null) { + markMetaChanged(); + } + } + + /** + * Remove map by name. + * + * @param name the map name + */ + public void removeMap(String name) { + int id = getMapId(name); + if(id > 0) { + MVMap map = getMap(id); + if (map == null) { + map = openMap(name, MVStoreTool.getGenericMapBuilder()); + } + removeMap(map); + } + } + + /** + * Get the name of the given map. + * + * @param id the map id + * @return the name, or null if not found + */ + public String getMapName(int id) { + String m = meta.get(MVMap.getMapKey(id)); + return m == null ? null : DataUtils.getMapName(m); + } + + private int getMapId(String name) { + String m = meta.get(DataUtils.META_NAME + name); + return m == null ? -1 : DataUtils.parseHexInt(m); + } + + /** + * Commit and save all changes, if there are any, and compact the store if + * needed. + */ + void writeInBackground() { + try { + if (!isOpenOrStopping() || isReadOnly()) { + return; + } + + // could also commit when there are many unsaved pages, + // but according to a test it doesn't really help + + long time = getTimeSinceCreation(); + if (time > lastCommitTime + autoCommitDelay) { + tryCommit(); + if (autoCompactFillRate < 0) { + compact(-getTargetFillRate(), autoCommitMemory); + } + } + int fillRate = getFillRate(); + if (fileStore.isFragmented() && fillRate < autoCompactFillRate) { + if (storeLock.tryLock(10, TimeUnit.MILLISECONDS)) { + try { + int moveSize = autoCommitMemory; + if (isIdle()) { + moveSize *= 4; + } + compactMoveChunks(101, moveSize); + } finally { + unlockAndCheckPanicCondition(); + } + } + } else if (fillRate >= autoCompactFillRate && lastChunk != null) { + int chunksFillRate = getRewritableChunksFillRate(); + chunksFillRate = isIdle() ? 100 - (100 - chunksFillRate) / 2 : chunksFillRate; + if (chunksFillRate < getTargetFillRate()) { + if (storeLock.tryLock(10, TimeUnit.MILLISECONDS)) { + try { + int writeLimit = autoCommitMemory * fillRate / Math.max(chunksFillRate, 1); + if (!isIdle()) { + writeLimit /= 4; + } + if (rewriteChunks(writeLimit, chunksFillRate)) { + dropUnusedChunks(); + } + } finally { + storeLock.unlock(); + } + } + } + } + autoCompactLastFileOpCount = fileStore.getWriteCount() + fileStore.getReadCount(); + } catch (InterruptedException ignore) { + } catch (Throwable e) { + handleException(e); + if (backgroundExceptionHandler == null) { + throw e; + } + } + } + + private void doMaintenance(int targetFillRate) { + if (autoCompactFillRate > 0 && lastChunk != null && reuseSpace) { + try { + int lastProjectedFillRate = -1; + for (int cnt = 0; cnt < 5; cnt++) { + int fillRate = getFillRate(); + int projectedFillRate = fillRate; + if (fillRate > targetFillRate) { + projectedFillRate = getProjectedFillRate(100); + if (projectedFillRate > targetFillRate || projectedFillRate <= lastProjectedFillRate) { + break; + } + } + lastProjectedFillRate = projectedFillRate; + // We can't wait forever for the lock here, + // because if called from the background thread, + // it might go into deadlock with concurrent database closure + // and attempt to stop this thread. + if (!storeLock.tryLock(10, TimeUnit.MILLISECONDS)) { + break; + } + try { + int writeLimit = autoCommitMemory * targetFillRate / Math.max(projectedFillRate, 1); + if (projectedFillRate < fillRate) { + if ((!rewriteChunks(writeLimit, targetFillRate) || dropUnusedChunks() == 0) && cnt > 0) { + break; + } + } + if (!compactMoveChunks(101, writeLimit)) { + break; + } + } finally { + unlockAndCheckPanicCondition(); + } + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + private int getTargetFillRate() { + int targetRate = autoCompactFillRate; + // use a lower fill rate if there were any file operations since the last time + if (!isIdle()) { + targetRate /= 2; + } + return targetRate; + } + + private boolean isIdle() { + return autoCompactLastFileOpCount == fileStore.getWriteCount() + fileStore.getReadCount(); + } + + private void handleException(Throwable ex) { + if (backgroundExceptionHandler != null) { + try { + backgroundExceptionHandler.uncaughtException(Thread.currentThread(), ex); + } catch(Throwable e) { + if (ex != e) { // OOME may be the same + ex.addSuppressed(e); + } + } + } + } + + /** + * Set the read cache size in MB. + * + * @param mb the cache size in MB. + */ + public void setCacheSize(int mb) { + final long bytes = (long) mb * 1024 * 1024; + if (cache != null) { + cache.setMaxMemory(bytes); + cache.clear(); + } + } + + private boolean isOpen() { + return state == STATE_OPEN; + } + + /** + * Determine that store is open, or wait for it to be closed (by other thread) + * @return true if store is open, false otherwise + */ + public boolean isClosed() { + if (isOpen()) { + return false; + } + storeLock.lock(); + try { + return state == STATE_CLOSED; + } finally { + storeLock.unlock(); + } + } + + private boolean isOpenOrStopping() { + return state <= STATE_STOPPING; + } + + private void stopBackgroundThread(boolean waitForIt) { + // Loop here is not strictly necessary, except for case of a spurious failure, + // which should not happen with non-weak flavour of CAS operation, + // but I've seen it, so just to be safe... + BackgroundWriterThread t; + while ((t = backgroundWriterThread.get()) != null) { + if (backgroundWriterThread.compareAndSet(t, null)) { + // if called from within the thread itself - can not join + if (t != Thread.currentThread()) { + synchronized (t.sync) { + t.sync.notifyAll(); + } + + if (waitForIt) { + try { + t.join(); + } catch (Exception e) { + // ignore + } + } + } + shutdownExecutor(serializationExecutor); + serializationExecutor = null; + shutdownExecutor(bufferSaveExecutor); + bufferSaveExecutor = null; + break; + } + } + } + + /** + * Set the maximum delay in milliseconds to auto-commit changes. + *

+ * To disable auto-commit, set the value to 0. In this case, changes are + * only committed when explicitly calling commit. + *

+ * The default is 1000, meaning all changes are committed after at most one + * second. + * + * @param millis the maximum delay + */ + public void setAutoCommitDelay(int millis) { + if (autoCommitDelay == millis) { + return; + } + autoCommitDelay = millis; + if (fileStore == null || fileStore.isReadOnly()) { + return; + } + stopBackgroundThread(true); + // start the background thread if needed + if (millis > 0 && isOpen()) { + int sleep = Math.max(1, millis / 10); + BackgroundWriterThread t = + new BackgroundWriterThread(this, sleep, + fileStore.toString()); + if (backgroundWriterThread.compareAndSet(null, t)) { + t.start(); + serializationExecutor = createSingleThreadExecutor("H2-serialization"); + bufferSaveExecutor = 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(); + } + + /** + * Get the auto-commit delay. + * + * @return the delay in milliseconds, or 0 if auto-commit is disabled. + */ + public int getAutoCommitDelay() { + return autoCommitDelay; + } + + /** + * Get the maximum memory (in bytes) used for unsaved pages. If this number + * is exceeded, unsaved changes are stored to disk. + * + * @return the memory in bytes + */ + public int getAutoCommitMemory() { + return autoCommitMemory; + } + + /** + * Get the estimated memory (in bytes) of unsaved data. If the value exceeds + * the auto-commit memory, the changes are committed. + *

+ * The returned value is an estimation only. + * + * @return the memory in bytes + */ + public int getUnsavedMemory() { + return unsavedMemory; + } + + /** + * Put the page in the cache. + * @param page the page + */ + void cachePage(Page page) { + if (cache != null) { + cache.put(page.getPos(), page, page.getMemory()); + } + } + + /** + * Get the amount of memory used for caching, in MB. + * Note that this does not include the page chunk references cache, which is + * 25% of the size of the page cache. + * + * @return the amount of memory used for caching + */ + public int getCacheSizeUsed() { + if (cache == null) { + return 0; + } + return (int) (cache.getUsedMemory() >> 20); + } + + /** + * Get the maximum cache size, in MB. + * Note that this does not include the page chunk references cache, which is + * 25% of the size of the page cache. + * + * @return the cache size + */ + public int getCacheSize() { + if (cache == null) { + return 0; + } + return (int) (cache.getMaxMemory() >> 20); + } + + /** + * Get the cache. + * + * @return the cache + */ + public CacheLongKeyLIRS> getCache() { + return cache; + } + + /** + * Whether the store is read-only. + * + * @return true if it is + */ + public boolean isReadOnly() { + return fileStore != null && fileStore.isReadOnly(); + } + + public int getCacheHitRatio() { + return getCacheHitRatio(cache); + } + + public int getTocCacheHitRatio() { + return getCacheHitRatio(chunksToC); + } + + private static int getCacheHitRatio(CacheLongKeyLIRS cache) { + if (cache == null) { + return 0; + } + long hits = cache.getHits(); + return (int) (100 * hits / (hits + cache.getMisses() + 1)); + } + + public int getLeafRatio() { + return (int)(leafCount * 100 / Math.max(1, leafCount + nonLeafCount)); + } + + public double getUpdateFailureRatio() { + long updateCounter = this.updateCounter; + long updateAttemptCounter = this.updateAttemptCounter; + RootReference rootReference = layout.getRoot(); + updateCounter += rootReference.updateCounter; + updateAttemptCounter += rootReference.updateAttemptCounter; + rootReference = meta.getRoot(); + updateCounter += rootReference.updateCounter; + updateAttemptCounter += rootReference.updateAttemptCounter; + for (MVMap map : maps.values()) { + RootReference root = map.getRoot(); + updateCounter += root.updateCounter; + updateAttemptCounter += root.updateAttemptCounter; + } + return updateAttemptCounter == 0 ? 0 : 1 - ((double)updateCounter / updateAttemptCounter); + } + + /** + * Register opened operation (transaction). + * This would increment usage counter for the current version. + * This version (and all after it) should not be dropped until all + * transactions involved are closed and usage counter goes to zero. + * @return TxCounter to be decremented when operation finishes (transaction closed). + */ + public TxCounter registerVersionUsage() { + TxCounter txCounter; + while(true) { + txCounter = currentTxCounter; + if(txCounter.incrementAndGet() > 0) { + return txCounter; + } + // The only way for counter to be negative + // if it was retrieved right before onVersionChange() + // and now onVersionChange() is done. + // This version is eligible for reclamation now + // and should not be used here, so restore count + // not to upset accounting and try again with a new + // version (currentTxCounter should have changed). + assert txCounter != currentTxCounter : txCounter; + txCounter.decrementAndGet(); + } + } + + /** + * De-register (close) completed operation (transaction). + * This will decrement usage counter for the corresponding version. + * If counter reaches zero, that version (and all unused after it) + * can be dropped immediately. + * + * @param txCounter to be decremented, obtained from registerVersionUsage() + */ + public void deregisterVersionUsage(TxCounter txCounter) { + if(decrementVersionUsageCounter(txCounter)) { + if (storeLock.isHeldByCurrentThread()) { + dropUnusedVersions(); + } else if (storeLock.tryLock()) { + try { + dropUnusedVersions(); + } 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; + versions.add(txCounter); + currentTxCounter = new TxCounter(version); + txCounter.decrementAndGet(); + dropUnusedVersions(); + } + + private void dropUnusedVersions() { + TxCounter txCounter; + while ((txCounter = versions.peek()) != null + && txCounter.get() < 0) { + versions.poll(); + } + long oldestVersionToKeep = (txCounter != null ? txCounter : currentTxCounter).version; + setOldestVersionToKeep(oldestVersionToKeep); + } + + private int dropUnusedChunks() { + assert storeLock.isHeldByCurrentThread(); + int count = 0; + if (!deadChunks.isEmpty()) { + long oldestVersionToKeep = getOldestVersionToKeep(); + long time = getTimeSinceCreation(); + saveChunkLock.lock(); + try { + Chunk chunk; + while ((chunk = deadChunks.poll()) != null && + (isSeasonedChunk(chunk, time) && canOverwriteChunk(chunk, oldestVersionToKeep) || + // if chunk is not ready yet, put it back and exit + // since this deque is unbounded, offerFirst() always return true + !deadChunks.offerFirst(chunk))) { + + if (chunks.remove(chunk.id) != null) { + // purge dead pages from cache + long[] toc = chunksToC.remove(chunk.id); + if (toc != null && cache != null) { + for (long tocElement : toc) { + long pagePos = DataUtils.getPagePos(chunk.id, tocElement); + cache.remove(pagePos); + } + } + + if (layout.remove(Chunk.getMetaKey(chunk.id)) != null) { + markMetaChanged(); + } + if (chunk.isSaved()) { + freeChunkSpace(chunk); + } + ++count; + } + } + } finally { + saveChunkLock.unlock(); + } + } + return count; + } + + private void freeChunkSpace(Chunk chunk) { + long start = chunk.block * BLOCK_SIZE; + int length = chunk.len * BLOCK_SIZE; + freeFileSpace(start, length); + } + + private void freeFileSpace(long start, int length) { + fileStore.free(start, length); + assert validateFileLength(start + ":" + length); + } + + private boolean validateFileLength(String msg) { + assert saveChunkLock.isHeldByCurrentThread(); + assert fileStore.getFileLengthInUse() == measureFileLengthInUse() : + fileStore.getFileLengthInUse() + " != " + measureFileLengthInUse() + " " + msg; + return true; + } + + /** + * Class TxCounter is a simple data structure to hold version of the store + * along with the counter of open transactions, + * which are still operating on this version. + */ + public static final class TxCounter { + + /** + * Version of a store, this TxCounter is related to + */ + public final long version; + + /** + * Counter of outstanding operation on this version of a store + */ + private volatile int counter; + + private static final AtomicIntegerFieldUpdater counterUpdater = + AtomicIntegerFieldUpdater.newUpdater(TxCounter.class, "counter"); + + + TxCounter(long version) { + this.version = version; + } + + int get() { + return counter; + } + + /** + * Increment and get the counter value. + * + * @return the new value + */ + int incrementAndGet() { + return counterUpdater.incrementAndGet(this); + } + + /** + * Decrement and get the counter values. + * + * @return the new value + */ + int decrementAndGet() { + return counterUpdater.decrementAndGet(this); + } + + @Override + public String toString() { + return "v=" + version + " / cnt=" + counter; + } + } + + /** + * A background writer thread to automatically store changes from time to + * time. + */ + private static class BackgroundWriterThread extends Thread { + + public final Object sync = new Object(); + private final MVStore store; + private final int sleep; + + BackgroundWriterThread(MVStore store, int sleep, String fileStoreName) { + super("MVStore background writer " + fileStoreName); + this.store = store; + this.sleep = sleep; + setDaemon(true); + } + + @Override + public void run() { + while (store.isBackgroundThread()) { + synchronized (sync) { + try { + sync.wait(sleep); + } catch (InterruptedException ignore) { + } + } + if (!store.isBackgroundThread()) { + break; + } + store.writeInBackground(); + } + } + } + + private static class RemovedPageInfo implements Comparable { + final long version; + final long removedPageInfo; + + RemovedPageInfo(long pagePos, boolean pinned, long version, int pageNo) { + this.removedPageInfo = createRemovedPageInfo(pagePos, pinned, pageNo); + this.version = version; + } + + @Override + public int compareTo(RemovedPageInfo other) { + return Long.compare(version, other.version); + } + + int getPageChunkId() { + return DataUtils.getPageChunkId(removedPageInfo); + } + + int getPageNo() { + return DataUtils.getPageOffset(removedPageInfo); + } + + int getPageLength() { + return DataUtils.getPageMaxLength(removedPageInfo); + } + + /** + * Find out if removed page was pinned (can not be evacuated to a new chunk). + * @return true if page has been pinned + */ + boolean isPinned() { + return (removedPageInfo & 1) == 1; + } + + /** + * Transforms saved page position into removed page info by + * replacing "page offset" with "page sequential number" and + * "page type" bit with "pinned page" flag. + * @param pagePos of the saved page + * @param isPinned whether page belong to a "single writer" map + * @param pageNo 0-based sequential page number within containing chunk + * @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); + if (isPinned) { + result |= 1; + } + return result; + } + + @Override + public String toString() { + return "RemovedPageInfo{" + + "version=" + version + + ", chunk=" + getPageChunkId() + + ", pageNo=" + getPageNo() + + ", len=" + getPageLength() + + (isPinned() ? ", pinned" : "") + + '}'; + } + } + + /** + * A builder for an MVStore. + */ + public static final class Builder { + + private final HashMap config; + + private Builder(HashMap config) { + this.config = config; + } + + /** + * Creates new instance of MVStore.Builder. + */ + public Builder() { + config = new HashMap<>(); + } + + private Builder set(String key, Object value) { + config.put(key, value); + return this; + } + + /** + * Disable auto-commit, by setting the auto-commit delay and auto-commit + * buffer size to 0. + * + * @return this + */ + public Builder autoCommitDisabled() { + // we have a separate config option so that + // no thread is started if the write delay is 0 + // (if we only had a setter in the MVStore, + // the thread would need to be started in any case) + //set("autoCommitBufferSize", 0); + return set("autoCommitDelay", 0); + } + + /** + * Set the size of the write buffer, in KB disk space (for file-based + * stores). Unless auto-commit is disabled, changes are automatically + * saved if there are more than this amount of changes. + *

+ * The default is 1024 KB. + *

+ * When the value is set to 0 or lower, data is not automatically + * stored. + * + * @param kb the write buffer size, in kilobytes + * @return this + */ + public Builder autoCommitBufferSize(int kb) { + return set("autoCommitBufferSize", kb); + } + + /** + * Set the auto-compact target fill rate. If the average fill rate (the + * percentage of the storage space that contains active data) of the + * chunks is lower, then the chunks with a low fill rate are re-written. + * Also, if the percentage of empty space between chunks is higher than + * this value, then chunks at the end of the file are moved. Compaction + * stops if the target fill rate is reached. + *

+ * The default value is 90 (90%). The value 0 disables auto-compacting. + *

+ * + * @param percent the target fill rate + * @return this + */ + public Builder autoCompactFillRate(int percent) { + return set("autoCompactFillRate", percent); + } + + /** + * Use the following file name. If the file does not exist, it is + * automatically created. The parent directory already must exist. + * + * @param fileName the file name + * @return this + */ + public Builder fileName(String fileName) { + return set("fileName", fileName); + } + + /** + * Encrypt / decrypt the file using the given password. This method has + * no effect for in-memory stores. The password is passed as a + * char array so that it can be cleared as soon as possible. Please note + * there is still a small risk that password stays in memory (due to + * Java garbage collection). Also, the hashed encryption key is kept in + * memory as long as the file is open. + * + * @param password the password + * @return this + */ + public Builder encryptionKey(char[] password) { + return set("encryptionKey", password); + } + + /** + * Open the file in read-only mode. In this case, a shared lock will be + * acquired to ensure the file is not concurrently opened in write mode. + *

+ * If this option is not used, the file is locked exclusively. + *

+ * Please note a store may only be opened once in every JVM (no matter + * whether it is opened in read-only or read-write mode), because each + * file may be locked only once in a process. + * + * @return this + */ + public Builder readOnly() { + return set("readOnly", 1); + } + + /** + * Set the number of keys per page. + * + * @param keyCount the number of keys + * @return this + */ + public Builder keysPerPage(int keyCount) { + return set("keysPerPage", keyCount); + } + + /** + * Open the file in recovery mode, where some errors may be ignored. + * + * @return this + */ + public Builder recoveryMode() { + return set("recoveryMode", 1); + } + + /** + * Set the read cache size in MB. The default is 16 MB. + * + * @param mb the cache size in megabytes + * @return this + */ + public Builder cacheSize(int mb) { + return set("cacheSize", mb); + } + + /** + * Set the read cache concurrency. The default is 16, meaning 16 + * segments are used. + * + * @param concurrency the cache concurrency + * @return this + */ + public Builder cacheConcurrency(int concurrency) { + return set("cacheConcurrency", concurrency); + } + + /** + * Compress data before writing using the LZF algorithm. This will save + * about 50% of the disk space, but will slow down read and write + * operations slightly. + *

+ * This setting only affects writes; it is not necessary to enable + * compression when reading, even if compression was enabled when + * writing. + * + * @return this + */ + public Builder compress() { + return set("compress", 1); + } + + /** + * Compress data before writing using the Deflate algorithm. This will + * save more disk space, but will slow down read and write operations + * quite a bit. + *

+ * This setting only affects writes; it is not necessary to enable + * compression when reading, even if compression was enabled when + * writing. + * + * @return this + */ + public Builder compressHigh() { + return set("compress", 2); + } + + /** + * Set the amount of memory a page should contain at most, in bytes, + * before it is split. The default is 16 KB for persistent stores and 4 + * KB for in-memory stores. This is not a limit in the page size, as + * pages with one entry can get larger. It is just the point where pages + * that contain more than one entry are split. + * + * @param pageSplitSize the page size + * @return this + */ + public Builder pageSplitSize(int pageSplitSize) { + return set("pageSplitSize", pageSplitSize); + } + + /** + * Set the listener to be used for exceptions that occur when writing in + * the background thread. + * + * @param exceptionHandler the handler + * @return this + */ + public Builder backgroundExceptionHandler( + Thread.UncaughtExceptionHandler exceptionHandler) { + return set("backgroundExceptionHandler", exceptionHandler); + } + + /** + * Use the provided file store instead of the default one. + *

+ * File stores passed in this way need to be open. They are not closed + * when closing the store. + *

+ * Please note that any kind of store (including an off-heap store) is + * considered a "persistence", while an "in-memory store" means objects + * are not persisted and fully kept in the JVM heap. + * + * @param store the file store + * @return this + */ + public Builder fileStore(FileStore store) { + return set("fileStore", store); + } + + /** + * Open the store. + * + * @return the opened store + */ + public MVStore open() { + return new MVStore(config); + } + + @Override + public String toString() { + return DataUtils.appendMap(new StringBuilder(), config).toString(); + } + + /** + * Read the configuration from a string. + * + * @param s the string representation + * @return the builder + */ + @SuppressWarnings({"unchecked", "rawtypes", "unused"}) + public static Builder fromString(String s) { + // Cast from HashMap to HashMap is safe + return new Builder((HashMap) DataUtils.parseMap(s)); + } + } + + /** + * Callback interface to perform cleanup after some unused store versions were dropped. + * Currently removes LOBs, which are known to be out of scope. + */ + public interface Cleaner { + /** + * Determine if cleanup is needed. + * This is mainly performance optimization to avoid async call to cleanup(). + * + * @return true if cleanup is required at this time, false otherwise + */ + boolean needCleanup(); + + /** + * Actual procedure for cleanup after some unused store versions were dropped + * + * @param oldestVersionToKeep in this MVStore + */ + void cleanup(long oldestVersionToKeep); + } +} diff --git a/h2/src/main/org/h2/mvstore/MVStoreException.java b/h2/src/main/org/h2/mvstore/MVStoreException.java new file mode 100644 index 0000000..0cd1b95 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/MVStoreException.java @@ -0,0 +1,25 @@ +/* + * 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.mvstore; + +/** + * Various kinds of MVStore problems, along with associated error code. + */ +public class MVStoreException extends RuntimeException { + + private static final long serialVersionUID = 2847042930249663807L; + + private final int errorCode; + + public MVStoreException(int errorCode, String message) { + super(message); + this.errorCode = errorCode; + } + + public int getErrorCode() { + return errorCode; + } +} diff --git a/h2/src/main/org/h2/mvstore/MVStoreTool.java b/h2/src/main/org/h2/mvstore/MVStoreTool.java new file mode 100644 index 0000000..feefd0b --- /dev/null +++ b/h2/src/main/org/h2/mvstore/MVStoreTool.java @@ -0,0 +1,742 @@ +/* + * 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.mvstore; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Writer; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; +import java.sql.Timestamp; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; + +import org.h2.compress.CompressDeflate; +import org.h2.compress.CompressLZF; +import org.h2.compress.Compressor; +import org.h2.engine.Constants; +import org.h2.mvstore.tx.TransactionStore; +import org.h2.mvstore.type.BasicDataType; +import org.h2.mvstore.type.StringDataType; +import org.h2.store.fs.FilePath; +import org.h2.store.fs.FileUtils; +import org.h2.util.Utils; + +/** + * Utility methods used in combination with the MVStore. + */ +public class MVStoreTool { + + /** + * Runs this tool. + * Options are case sensitive. Supported options are: + *

+ * + * + * + * + * + * + * + * + * + *
Command line options
[-dump <fileName>]Dump the contends of the file
[-info <fileName>]Get summary information about a file
[-compact <fileName>]Compact a store
[-compress <fileName>]Compact a store with compression enabled
+ * + * @param args the command line arguments + */ + public static void main(String... args) { + for (int i = 0; i < args.length; i++) { + if ("-dump".equals(args[i])) { + String fileName = args[++i]; + dump(fileName, new PrintWriter(System.out), true); + } else if ("-info".equals(args[i])) { + String fileName = args[++i]; + info(fileName, new PrintWriter(System.out)); + } else if ("-compact".equals(args[i])) { + String fileName = args[++i]; + compact(fileName, false); + } else if ("-compress".equals(args[i])) { + String fileName = args[++i]; + compact(fileName, true); + } else if ("-rollback".equals(args[i])) { + String fileName = args[++i]; + long targetVersion = Long.decode(args[++i]); + rollback(fileName, targetVersion, new PrintWriter(System.out)); + } else if ("-repair".equals(args[i])) { + String fileName = args[++i]; + repair(fileName); + } + } + } + + /** + * Read the contents of the file and write them to system out. + * + * @param fileName the name of the file + * @param details whether to print details + */ + public static void dump(String fileName, boolean details) { + dump(fileName, new PrintWriter(System.out), details); + } + + /** + * Read the summary information of the file and write them to system out. + * + * @param fileName the name of the file + */ + public static void info(String fileName) { + info(fileName, new PrintWriter(System.out)); + } + + /** + * Read the contents of the file and display them in a human-readable + * format. + * + * @param fileName the name of the file + * @param writer the print writer + * @param details print the page details + */ + public static void dump(String fileName, Writer writer, boolean details) { + PrintWriter pw = new PrintWriter(writer, true); + if (!FilePath.get(fileName).exists()) { + pw.println("File not found: " + fileName); + return; + } + long size = FileUtils.size(fileName); + pw.printf("File %s, %d bytes, %d MB\n", fileName, size, size / 1024 / 1024); + int blockSize = MVStore.BLOCK_SIZE; + TreeMap mapSizesTotal = + new TreeMap<>(); + long pageSizeTotal = 0; + try (FileChannel file = FilePath.get(fileName).open("r")) { + long fileSize = file.size(); + int len = Long.toHexString(fileSize).length(); + ByteBuffer block = ByteBuffer.allocate(4096); + long pageCount = 0; + for (long pos = 0; pos < fileSize; ) { + block.rewind(); + // Bugfix - An MVStoreException that wraps EOFException is + // thrown when partial writes happens in the case of power off + // or file system issues. + // So we should skip the broken block at end of the DB file. + try { + DataUtils.readFully(file, pos, block); + } catch (MVStoreException e) { + pos += blockSize; + pw.printf("ERROR illegal position %d%n", pos); + continue; + } + block.rewind(); + int headerType = block.get(); + if (headerType == 'H') { + String header = new String(block.array(), StandardCharsets.ISO_8859_1).trim(); + pw.printf("%0" + len + "x fileHeader %s%n", + pos, header); + pos += blockSize; + continue; + } + if (headerType != 'c') { + pos += blockSize; + continue; + } + block.position(0); + Chunk c; + try { + c = Chunk.readChunkHeader(block, pos); + } catch (MVStoreException e) { + pos += blockSize; + continue; + } + if (c.len <= 0) { + // not a chunk + pos += blockSize; + continue; + } + int length = c.len * MVStore.BLOCK_SIZE; + pw.printf("%n%0" + len + "x chunkHeader %s%n", + pos, c.toString()); + ByteBuffer chunk = ByteBuffer.allocate(length); + DataUtils.readFully(file, pos, chunk); + int p = block.position(); + pos += length; + int remaining = c.pageCount; + pageCount += c.pageCount; + TreeMap mapSizes = + new TreeMap<>(); + int pageSizeSum = 0; + while (remaining > 0) { + int start = p; + try { + chunk.position(p); + } catch (IllegalArgumentException e) { + // too far + pw.printf("ERROR illegal position %d%n", p); + break; + } + int pageSize = chunk.getInt(); + // check value (ignored) + chunk.getShort(); + /*int pageNo =*/ DataUtils.readVarInt(chunk); + int mapId = DataUtils.readVarInt(chunk); + int entries = DataUtils.readVarInt(chunk); + int type = chunk.get(); + boolean compressed = (type & DataUtils.PAGE_COMPRESSED) != 0; + boolean node = (type & DataUtils.PAGE_TYPE_NODE) != 0; + if (details) { + pw.printf( + "+%0" + len + + "x %s, map %x, %d entries, %d bytes, maxLen %x%n", + p, + (node ? "node" : "leaf") + + (compressed ? " compressed" : ""), + mapId, + node ? entries + 1 : entries, + pageSize, + DataUtils.getPageMaxLength(DataUtils.getPagePos(0, 0, pageSize, 0)) + ); + } + p += pageSize; + Integer mapSize = mapSizes.get(mapId); + if (mapSize == null) { + mapSize = 0; + } + mapSizes.put(mapId, mapSize + pageSize); + Long total = mapSizesTotal.get(mapId); + if (total == null) { + total = 0L; + } + mapSizesTotal.put(mapId, total + pageSize); + pageSizeSum += pageSize; + pageSizeTotal += pageSize; + remaining--; + long[] children = null; + long[] counts = null; + if (node) { + children = new long[entries + 1]; + for (int i = 0; i <= entries; i++) { + children[i] = chunk.getLong(); + } + counts = new long[entries + 1]; + for (int i = 0; i <= entries; i++) { + long s = DataUtils.readVarLong(chunk); + counts[i] = s; + } + } + String[] keys = new String[entries]; + if (mapId == 0 && details) { + ByteBuffer data; + if (compressed) { + boolean fast = (type & DataUtils.PAGE_COMPRESSED_HIGH) != DataUtils.PAGE_COMPRESSED_HIGH; + Compressor compressor = getCompressor(fast); + int lenAdd = DataUtils.readVarInt(chunk); + int compLen = pageSize + start - chunk.position(); + byte[] comp = Utils.newBytes(compLen); + chunk.get(comp); + int l = compLen + lenAdd; + data = ByteBuffer.allocate(l); + compressor.expand(comp, 0, compLen, data.array(), 0, l); + } else { + data = chunk; + } + for (int i = 0; i < entries; i++) { + String k = StringDataType.INSTANCE.read(data); + keys[i] = k; + } + if (node) { + // meta map node + for (int i = 0; i < entries; i++) { + long cp = children[i]; + pw.printf(" %d children < %s @ " + + "chunk %x +%0" + + len + "x%n", + counts[i], + keys[i], + DataUtils.getPageChunkId(cp), + DataUtils.getPageOffset(cp)); + } + long cp = children[entries]; + pw.printf(" %d children >= %s @ chunk %x +%0" + + len + "x%n", + counts[entries], + keys.length >= entries ? null : keys[entries], + DataUtils.getPageChunkId(cp), + DataUtils.getPageOffset(cp)); + } else { + // meta map leaf + String[] values = new String[entries]; + for (int i = 0; i < entries; i++) { + String v = StringDataType.INSTANCE.read(data); + values[i] = v; + } + for (int i = 0; i < entries; i++) { + pw.println(" " + keys[i] + + " = " + values[i]); + } + } + } else { + if (node && details) { + for (int i = 0; i <= entries; i++) { + long cp = children[i]; + pw.printf(" %d children @ chunk %x +%0" + + len + "x%n", + counts[i], + DataUtils.getPageChunkId(cp), + DataUtils.getPageOffset(cp)); + } + } + } + } + pageSizeSum = Math.max(1, pageSizeSum); + for (Integer mapId : mapSizes.keySet()) { + int percent = 100 * mapSizes.get(mapId) / pageSizeSum; + pw.printf("map %x: %d bytes, %d%%%n", mapId, mapSizes.get(mapId), percent); + } + int footerPos = chunk.limit() - Chunk.FOOTER_LENGTH; + try { + chunk.position(footerPos); + pw.printf( + "+%0" + len + "x chunkFooter %s%n", + footerPos, + new String(chunk.array(), chunk.position(), + Chunk.FOOTER_LENGTH, StandardCharsets.ISO_8859_1).trim()); + } catch (IllegalArgumentException e) { + // too far + pw.printf("ERROR illegal footer position %d%n", footerPos); + } + } + pw.printf("%n%0" + len + "x eof%n", fileSize); + pw.printf("\n"); + pageCount = Math.max(1, pageCount); + pw.printf("page size total: %d bytes, page count: %d, average page size: %d bytes\n", + pageSizeTotal, pageCount, pageSizeTotal / pageCount); + pageSizeTotal = Math.max(1, pageSizeTotal); + for (Integer mapId : mapSizesTotal.keySet()) { + int percent = (int) (100 * mapSizesTotal.get(mapId) / pageSizeTotal); + pw.printf("map %x: %d bytes, %d%%%n", mapId, mapSizesTotal.get(mapId), percent); + } + } catch (IOException e) { + pw.println("ERROR: " + e); + e.printStackTrace(pw); + } + // ignore + pw.flush(); + } + + private static Compressor getCompressor(boolean fast) { + return fast ? new CompressLZF() : new CompressDeflate(); + } + + /** + * Read the summary information of the file and write them to system out. + * + * @param fileName the name of the file + * @param writer the print writer + * @return null if successful (if there was no error), otherwise the error + * message + */ + public static String info(String fileName, Writer writer) { + PrintWriter pw = new PrintWriter(writer, true); + if (!FilePath.get(fileName).exists()) { + pw.println("File not found: " + fileName); + return "File not found: " + fileName; + } + long fileLength = FileUtils.size(fileName); + try (MVStore store = new MVStore.Builder(). + fileName(fileName).recoveryMode(). + readOnly().open()) { + MVMap layout = store.getLayoutMap(); + Map header = store.getStoreHeader(); + long fileCreated = DataUtils.readHexLong(header, "created", 0L); + TreeMap chunks = new TreeMap<>(); + long chunkLength = 0; + long maxLength = 0; + long maxLengthLive = 0; + long maxLengthNotEmpty = 0; + for (Entry e : layout.entrySet()) { + String k = e.getKey(); + if (k.startsWith(DataUtils.META_CHUNK)) { + Chunk c = Chunk.fromString(e.getValue()); + chunks.put(c.id, c); + chunkLength += c.len * MVStore.BLOCK_SIZE; + maxLength += c.maxLen; + maxLengthLive += c.maxLenLive; + if (c.maxLenLive > 0) { + maxLengthNotEmpty += c.maxLen; + } + } + } + pw.printf("Created: %s\n", formatTimestamp(fileCreated, fileCreated)); + pw.printf("Last modified: %s\n", + formatTimestamp(FileUtils.lastModified(fileName), fileCreated)); + pw.printf("File length: %d\n", fileLength); + pw.printf("The last chunk is not listed\n"); + pw.printf("Chunk length: %d\n", chunkLength); + pw.printf("Chunk count: %d\n", chunks.size()); + pw.printf("Used space: %d%%\n", getPercent(chunkLength, fileLength)); + pw.printf("Chunk fill rate: %d%%\n", maxLength == 0 ? 100 : + getPercent(maxLengthLive, maxLength)); + pw.printf("Chunk fill rate excluding empty chunks: %d%%\n", + maxLengthNotEmpty == 0 ? 100 : + getPercent(maxLengthLive, maxLengthNotEmpty)); + for (Entry e : chunks.entrySet()) { + Chunk c = e.getValue(); + long created = fileCreated + c.time; + pw.printf(" Chunk %d: %s, %d%% used, %d blocks", + c.id, formatTimestamp(created, fileCreated), + getPercent(c.maxLenLive, c.maxLen), + c.len + ); + if (c.maxLenLive == 0) { + pw.printf(", unused: %s", + formatTimestamp(fileCreated + c.unused, fileCreated)); + } + pw.printf("\n"); + } + pw.printf("\n"); + } catch (Exception e) { + pw.println("ERROR: " + e); + e.printStackTrace(pw); + return e.getMessage(); + } + pw.flush(); + return null; + } + + private static String formatTimestamp(long t, long start) { + String x = new Timestamp(t).toString(); + String s = x.substring(0, 19); + s += " (+" + ((t - start) / 1000) + " s)"; + return s; + } + + private static int getPercent(long value, long max) { + if (value == 0) { + return 0; + } else if (value == max) { + return 100; + } + return (int) (1 + 98 * value / Math.max(1, max)); + } + + /** + * Compress the store by creating a new file and copying the live pages + * there. Temporarily, a file with the suffix ".tempFile" is created. This + * file is then renamed, replacing the original file, if possible. If not, + * the new file is renamed to ".newFile", then the old file is removed, and + * the new file is renamed. This might be interrupted, so it's better to + * compactCleanUp before opening a store, in case this method was used. + * + * @param fileName the file name + * @param compress whether to compress the data + */ + public static void compact(String fileName, boolean compress) { + String tempName = fileName + Constants.SUFFIX_MV_STORE_TEMP_FILE; + FileUtils.delete(tempName); + compact(fileName, tempName, compress); + try { + FileUtils.moveAtomicReplace(tempName, fileName); + } catch (MVStoreException e) { + String newName = fileName + Constants.SUFFIX_MV_STORE_NEW_FILE; + FileUtils.delete(newName); + FileUtils.move(tempName, newName); + FileUtils.delete(fileName); + FileUtils.move(newName, fileName); + } + } + + /** + * Clean up if needed, in a case a compact operation was interrupted due to + * killing the process or a power failure. This will delete temporary files + * (if any), and in case atomic file replacements were not used, rename the + * new file. + * + * @param fileName the file name + */ + public static void compactCleanUp(String fileName) { + String tempName = fileName + Constants.SUFFIX_MV_STORE_TEMP_FILE; + if (FileUtils.exists(tempName)) { + FileUtils.delete(tempName); + } + String newName = fileName + Constants.SUFFIX_MV_STORE_NEW_FILE; + if (FileUtils.exists(newName)) { + if (FileUtils.exists(fileName)) { + FileUtils.delete(newName); + } else { + FileUtils.move(newName, fileName); + } + } + } + + /** + * Copy all live pages from the source store to the target store. + * + * @param sourceFileName the name of the source store + * @param targetFileName the name of the target store + * @param compress whether to compress the data + */ + public static void compact(String sourceFileName, String targetFileName, boolean compress) { + try (MVStore source = new MVStore.Builder(). + fileName(sourceFileName).readOnly().open()) { + // Bugfix - Add double "try-finally" statements to close source and target stores for + //releasing lock and file resources in these stores even if OOM occurs. + // Fix issues such as "Cannot delete file "/h2/data/test.mv.db.tempFile" [90025-197]" + //when client connects to this server and reopens this store database in this process. + // @since 2018-09-13 little-pan + FileUtils.delete(targetFileName); + MVStore.Builder b = new MVStore.Builder(). + fileName(targetFileName); + if (compress) { + b.compress(); + } + try (MVStore target = b.open()) { + compact(source, target); + } + } + } + + /** + * Copy all live pages from the source store to the target store. + * + * @param source the source store + * @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 { + target.setReuseSpace(false); // disable unused chunks collection + target.setAutoCommitDelay(0); // disable autocommit + MVMap sourceMeta = source.getMetaMap(); + MVMap targetMeta = target.getMetaMap(); + for (Entry m : sourceMeta.entrySet()) { + String key = m.getKey(); + if (key.startsWith(DataUtils.META_MAP)) { + // ignore + } else if (key.startsWith(DataUtils.META_NAME)) { + // ignore + } else { + targetMeta.put(key, m.getValue()); + } + } + // We are going to cheat a little bit in the copyFrom() by employing "incomplete" pages, + // which would be spared of saving, but save completed pages underneath, + // and those may appear as dead (non-reachable). + // That's why it is important to preserve all chunks + // created in the process, especially if retention time + // is set to a lower value, or even 0. + for (String mapName : source.getMapNames()) { + MVMap.Builder mp = getGenericMapBuilder(); + // This is a hack to preserve chunks occupancy rate accounting. + // It exposes design deficiency flaw in MVStore related to lack of + // map's type metadata. + // TODO: Introduce type metadata which will allow to open any store + // TODO: without prior knowledge of keys / values types and map implementation + // TODO: (MVMap vs MVRTreeMap, regular vs. singleWriter etc.) + if (mapName.startsWith(TransactionStore.UNDO_LOG_NAME_PREFIX)) { + mp.singleWriter(); + } + MVMap sourceMap = source.openMap(mapName, mp); + MVMap targetMap = target.openMap(mapName, mp); + targetMap.copyFrom(sourceMap); + targetMeta.put(MVMap.getMapKey(targetMap.getId()), sourceMeta.get(MVMap.getMapKey(sourceMap.getId()))); + } + // this will end hacky mode of operation with incomplete pages + // end ensure that all pages are saved + target.commit(); + } finally { + target.setAutoCommitDelay(autoCommitDelay); + target.setReuseSpace(reuseSpace); + } + } + + /** + * Repair a store by rolling back to the newest good version. + * + * @param fileName the file name + */ + public static void repair(String fileName) { + PrintWriter pw = new PrintWriter(System.out); + long version = Long.MAX_VALUE; + OutputStream ignore = new OutputStream() { + @Override + public void write(int b) { + // ignore + } + }; + while (version >= 0) { + pw.println(version == Long.MAX_VALUE ? "Trying latest version" + : ("Trying version " + version)); + pw.flush(); + version = rollback(fileName, version, new PrintWriter(ignore)); + try { + String error = info(fileName + ".temp", new PrintWriter(ignore)); + if (error == null) { + FilePath.get(fileName).moveTo(FilePath.get(fileName + ".back"), true); + FilePath.get(fileName + ".temp").moveTo(FilePath.get(fileName), true); + pw.println("Success"); + break; + } + pw.println(" ... failed: " + error); + } catch (Exception e) { + pw.println("Fail: " + e.getMessage()); + pw.flush(); + } + version--; + } + pw.flush(); + } + + /** + * Roll back to a given revision into a file called *.temp. + * + * @param fileName the file name + * @param targetVersion the version to roll back to (Long.MAX_VALUE for the + * latest version) + * @param writer the log writer + * @return the version rolled back to (-1 if no version) + */ + public static long rollback(String fileName, long targetVersion, Writer writer) { + long newestVersion = -1; + PrintWriter pw = new PrintWriter(writer, true); + if (!FilePath.get(fileName).exists()) { + pw.println("File not found: " + fileName); + return newestVersion; + } + FileChannel file = null; + FileChannel target = null; + int blockSize = MVStore.BLOCK_SIZE; + try { + file = FilePath.get(fileName).open("r"); + FilePath.get(fileName + ".temp").delete(); + target = FilePath.get(fileName + ".temp").open("rw"); + long fileSize = file.size(); + ByteBuffer block = ByteBuffer.allocate(4096); + Chunk newestChunk = null; + for (long pos = 0; pos < fileSize;) { + block.rewind(); + DataUtils.readFully(file, pos, block); + block.rewind(); + int headerType = block.get(); + if (headerType == 'H') { + block.rewind(); + target.write(block, pos); + pos += blockSize; + continue; + } + if (headerType != 'c') { + pos += blockSize; + continue; + } + Chunk c; + try { + c = Chunk.readChunkHeader(block, pos); + } catch (MVStoreException e) { + pos += blockSize; + continue; + } + if (c.len <= 0) { + // not a chunk + pos += blockSize; + continue; + } + int length = c.len * MVStore.BLOCK_SIZE; + ByteBuffer chunk = ByteBuffer.allocate(length); + DataUtils.readFully(file, pos, chunk); + if (c.version > targetVersion) { + // newer than the requested version + pos += length; + continue; + } + chunk.rewind(); + target.write(chunk, pos); + if (newestChunk == null || c.version > newestChunk.version) { + newestChunk = c; + newestVersion = c.version; + } + pos += length; + } + int length = newestChunk.len * MVStore.BLOCK_SIZE; + ByteBuffer chunk = ByteBuffer.allocate(length); + DataUtils.readFully(file, newestChunk.block * MVStore.BLOCK_SIZE, chunk); + chunk.rewind(); + target.write(chunk, fileSize); + } catch (IOException e) { + pw.println("ERROR: " + e); + e.printStackTrace(pw); + } finally { + if (file != null) { + try { + file.close(); + } catch (IOException e) { + // ignore + } + } + if (target != null) { + try { + target.close(); + } catch (IOException e) { + // ignore + } + } + } + pw.flush(); + return newestVersion; + } + + @SuppressWarnings({"rawtypes","unchecked"}) + static MVMap.Builder getGenericMapBuilder() { + return (MVMap.Builder)new MVMap.Builder(). + keyType(GenericDataType.INSTANCE). + valueType(GenericDataType.INSTANCE); + } + + /** + * A data type that can read any data that is persisted, and converts it to + * a byte array. + */ + private static class GenericDataType extends BasicDataType { + static GenericDataType INSTANCE = new GenericDataType(); + + private GenericDataType() {} + + @Override + public boolean isMemoryEstimationAllowed() { + return false; + } + + @Override + public int getMemory(byte[] obj) { + return obj == null ? 0 : obj.length * 8; + } + + @Override + public byte[][] createStorage(int size) { + return new byte[size][]; + } + + @Override + public void write(WriteBuffer buff, byte[] obj) { + if (obj != null) { + buff.put(obj); + } + } + + @Override + public byte[] read(ByteBuffer buff) { + int len = buff.remaining(); + if (len == 0) { + return null; + } + byte[] data = new byte[len]; + buff.get(data); + return data; + } + } +} diff --git a/h2/src/main/org/h2/mvstore/OffHeapStore.java b/h2/src/main/org/h2/mvstore/OffHeapStore.java new file mode 100644 index 0000000..6dc9d87 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/OffHeapStore.java @@ -0,0 +1,148 @@ +/* + * 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.mvstore; + +import java.nio.ByteBuffer; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.TreeMap; + +/** + * A storage mechanism that "persists" data in the off-heap area of the main + * memory. + */ +public class OffHeapStore extends FileStore { + + private final TreeMap memory = + new TreeMap<>(); + + @Override + public void open(String fileName, boolean readOnly, char[] encryptionKey) { + memory.clear(); + } + + @Override + public String toString() { + return memory.toString(); + } + + @Override + public ByteBuffer readFully(long pos, int len) { + Entry memEntry = memory.floorEntry(pos); + if (memEntry == null) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_READING_FAILED, + "Could not read from position {0}", pos); + } + readCount.incrementAndGet(); + readBytes.addAndGet(len); + ByteBuffer buff = memEntry.getValue(); + ByteBuffer read = buff.duplicate(); + int offset = (int) (pos - memEntry.getKey()); + read.position(offset); + read.limit(len + offset); + return read.slice(); + } + + @Override + public void free(long pos, int length) { + freeSpace.free(pos, length); + ByteBuffer buff = memory.remove(pos); + if (buff == null) { + // nothing was written (just allocated) + } else if (buff.remaining() != length) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_READING_FAILED, + "Partial remove is not supported at position {0}", pos); + } + } + + @Override + public void writeFully(long pos, ByteBuffer src) { + fileSize = Math.max(fileSize, pos + src.remaining()); + Entry mem = memory.floorEntry(pos); + if (mem == null) { + // not found: create a new entry + writeNewEntry(pos, src); + return; + } + long prevPos = mem.getKey(); + ByteBuffer buff = mem.getValue(); + int prevLength = buff.capacity(); + int length = src.remaining(); + if (prevPos == pos) { + if (prevLength != length) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_READING_FAILED, + "Could not write to position {0}; " + + "partial overwrite is not supported", pos); + } + writeCount.incrementAndGet(); + writeBytes.addAndGet(length); + buff.rewind(); + buff.put(src); + return; + } + if (prevPos + prevLength > pos) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_READING_FAILED, + "Could not write to position {0}; " + + "partial overwrite is not supported", pos); + } + writeNewEntry(pos, src); + } + + private void writeNewEntry(long pos, ByteBuffer src) { + int length = src.remaining(); + writeCount.incrementAndGet(); + writeBytes.addAndGet(length); + ByteBuffer buff = ByteBuffer.allocateDirect(length); + buff.put(src); + buff.rewind(); + memory.put(pos, buff); + } + + @Override + public void truncate(long size) { + writeCount.incrementAndGet(); + if (size == 0) { + fileSize = 0; + memory.clear(); + return; + } + fileSize = size; + for (Iterator it = memory.keySet().iterator(); it.hasNext();) { + long pos = it.next(); + if (pos < size) { + break; + } + ByteBuffer buff = memory.get(pos); + if (buff.capacity() > size) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_READING_FAILED, + "Could not truncate to {0}; " + + "partial truncate is not supported", pos); + } + it.remove(); + } + } + + @Override + public void close() { + memory.clear(); + } + + @Override + public void sync() { + // nothing to do + } + + @Override + public int getDefaultRetentionTime() { + return 0; + } + +} diff --git a/h2/src/main/org/h2/mvstore/Page.java b/h2/src/main/org/h2/mvstore/Page.java new file mode 100644 index 0000000..5ff8b34 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/Page.java @@ -0,0 +1,1662 @@ +/* + * 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.mvstore; + +import static org.h2.engine.Constants.MEMORY_ARRAY; +import static org.h2.engine.Constants.MEMORY_OBJECT; +import static org.h2.engine.Constants.MEMORY_POINTER; +import static org.h2.mvstore.DataUtils.PAGE_TYPE_LEAF; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicLongFieldUpdater; +import org.h2.compress.Compressor; +import org.h2.util.Utils; + +/** + * A page (a node or a leaf). + *

+ * For b-tree nodes, the key at a given index is larger than the largest key of + * the child at the same index. + *

+ * Serialized format: + * length of a serialized page in bytes (including this field): int + * check value: short + * page number (0-based sequential number within a chunk): varInt + * map id: varInt + * number of keys: varInt + * type: byte (0: leaf, 1: node; +2: compressed) + * children of the non-leaf node (1 more than keys) + * compressed: bytes saved (varInt) + * keys + * values of the leaf node (one for each key) + */ +public abstract class Page implements Cloneable { + + /** + * Map this page belongs to + */ + public final MVMap map; + + /** + * Position of this page's saved image within a Chunk + * or 0 if this page has not been saved yet + * or 1 if this page has not been saved yet, but already removed + * This "removed" flag is to keep track of pages that concurrently + * changed while they are being stored, in which case the live bookkeeping + * needs to be aware of such cases. + * Field need to be volatile to avoid races between saving thread setting it + * and other thread reading it to access the page. + * On top of this update atomicity is required so removal mark and saved position + * can be set concurrently. + * + * @see DataUtils#getPagePos(int, int, int, int) for field format details + */ + private volatile long pos; + + /** + * Sequential 0-based number of the page within containing chunk. + */ + public int pageNo = -1; + + /** + * The last result of a find operation is cached. + */ + private int cachedCompare; + + /** + * The estimated memory used in persistent case, IN_MEMORY marker value otherwise. + */ + private int memory; + + /** + * Amount of used disk space by this page only in persistent case. + */ + private int diskSpaceUsed; + + /** + * The keys. + */ + private K[] keys; + + /** + * Updater for pos field, which can be updated when page is saved, + * but can be concurrently marked as removed + */ + @SuppressWarnings("rawtypes") + private static final AtomicLongFieldUpdater posUpdater = + AtomicLongFieldUpdater.newUpdater(Page.class, "pos"); + /** + * The estimated number of bytes used per child entry. + */ + static final int PAGE_MEMORY_CHILD = MEMORY_POINTER + 16; // 16 = two longs + + /** + * The estimated number of bytes used per base page. + */ + private static final int PAGE_MEMORY = + MEMORY_OBJECT + // this + 2 * MEMORY_POINTER + // map, keys + MEMORY_ARRAY + // Object[] keys + 17; // pos, cachedCompare, memory, removedInMemory + /** + * The estimated number of bytes used per empty internal page object. + */ + static final int PAGE_NODE_MEMORY = + PAGE_MEMORY + // super + MEMORY_POINTER + // children + MEMORY_ARRAY + // Object[] children + 8; // totalCount + + /** + * The estimated number of bytes used per empty leaf page. + */ + static final int PAGE_LEAF_MEMORY = + PAGE_MEMORY + // super + MEMORY_POINTER + // values + MEMORY_ARRAY; // Object[] values + + /** + * Marker value for memory field, meaning that memory accounting is replaced by key count. + */ + private static final int IN_MEMORY = Integer.MIN_VALUE; + + @SuppressWarnings("rawtypes") + private static final PageReference[] SINGLE_EMPTY = { PageReference.EMPTY }; + + + Page(MVMap map) { + this.map = map; + } + + Page(MVMap map, Page source) { + this(map, source.keys); + memory = source.memory; + } + + Page(MVMap map, K[] keys) { + this.map = map; + this.keys = keys; + } + + /** + * Create a new, empty leaf page. + * + * @param key type + * @param value type + * + * @param map the map + * @return the new page + */ + static Page createEmptyLeaf(MVMap map) { + return createLeaf(map, map.getKeyType().createStorage(0), + map.getValueType().createStorage(0), PAGE_LEAF_MEMORY); + } + + /** + * Create a new, empty internal node page. + * + * @param key type + * @param value type + * + * @param map the map + * @return the new page + */ + @SuppressWarnings("unchecked") + static Page createEmptyNode(MVMap map) { + return createNode(map, map.getKeyType().createStorage(0), SINGLE_EMPTY, 0, + PAGE_NODE_MEMORY + MEMORY_POINTER + PAGE_MEMORY_CHILD); // there is always one child + } + + /** + * Create a new non-leaf page. The arrays are not cloned. + * + * @param the key class + * @param the value class + * @param map the map + * @param keys the keys + * @param children the child page positions + * @param totalCount the total number of keys + * @param memory the memory used in bytes + * @return the page + */ + public static Page createNode(MVMap map, K[] keys, PageReference[] children, + long totalCount, int memory) { + assert keys != null; + Page page = new NonLeaf<>(map, keys, children, totalCount); + page.initMemoryAccount(memory); + return page; + } + + /** + * Create a new leaf page. The arrays are not cloned. + * + * @param key type + * @param value type + * + * @param map the map + * @param keys the keys + * @param values the values + * @param memory the memory used in bytes + * @return the page + */ + static Page createLeaf(MVMap map, K[] keys, V[] values, int memory) { + assert keys != null; + Page page = new Leaf<>(map, keys, values); + page.initMemoryAccount(memory); + return page; + } + + private void initMemoryAccount(int memoryCount) { + if(!map.isPersistent()) { + memory = IN_MEMORY; + } else if (memoryCount == 0) { + recalculateMemory(); + } else { + addMemory(memoryCount); + assert memoryCount == getMemory(); + } + } + + /** + * Get the value for the given key, or null if not found. + * Search is done in the tree rooted at given page. + * + * @param key type + * @param value type + * + * @param key the key + * @param p the root page + * @return the value, or null if not found + */ + static V get(Page p, K key) { + while (true) { + int index = p.binarySearch(key); + if (p.isLeaf()) { + return index >= 0 ? p.getValue(index) : null; + } else if (index++ < 0) { + index = -index; + } + p = p.getChildPage(index); + } + } + + /** + * Read a page. + * + * @param key type + * @param value type + * + * @param buff ByteBuffer containing serialized page info + * @param pos the position + * @param map the map + * @return the page + */ + static Page read(ByteBuffer buff, long pos, MVMap map) { + boolean leaf = (DataUtils.getPageType(pos) & 1) == PAGE_TYPE_LEAF; + Page p = leaf ? new Leaf<>(map) : new NonLeaf<>(map); + p.pos = pos; + p.read(buff); + return p; + } + + /** + * Get the id of the page's owner map + * @return id + */ + public final int getMapId() { + return map.getId(); + } + + /** + * Create a copy of this page with potentially different owning map. + * This is used exclusively during bulk map copying. + * Child page references for nodes are cleared (re-pointed to an empty page) + * to be filled-in later to copying procedure. This way it can be saved + * mid-process without tree integrity violation + * + * @param map new map to own resulting page + * @param eraseChildrenRefs whether cloned Page should have no child references or keep originals + * @return the page + */ + abstract Page copy(MVMap map, boolean eraseChildrenRefs); + + /** + * Get the key at the given index. + * + * @param index the index + * @return the key + */ + public K getKey(int index) { + return keys[index]; + } + + /** + * Get the child page at the given index. + * + * @param index the index + * @return the child page + */ + public abstract Page getChildPage(int index); + + /** + * Get the position of the child. + * + * @param index the index + * @return the position + */ + public abstract long getChildPagePos(int index); + + /** + * Get the value at the given index. + * + * @param index the index + * @return the value + */ + public abstract V getValue(int index); + + /** + * Get the number of keys in this page. + * + * @return the number of keys + */ + public final int getKeyCount() { + return keys.length; + } + + /** + * Check whether this is a leaf page. + * + * @return true if it is a leaf + */ + public final boolean isLeaf() { + return getNodeType() == PAGE_TYPE_LEAF; + } + + public abstract int getNodeType(); + + /** + * Get the position of the page + * + * @return the position + */ + public final long getPos() { + return pos; + } + + @Override + public String toString() { + StringBuilder buff = new StringBuilder(); + dump(buff); + return buff.toString(); + } + + /** + * Dump debug data for this page. + * + * @param buff append buffer + */ + protected void dump(StringBuilder buff) { + buff.append("id: ").append(System.identityHashCode(this)).append('\n'); + buff.append("pos: ").append(Long.toHexString(pos)).append('\n'); + if (isSaved()) { + int chunkId = DataUtils.getPageChunkId(pos); + buff.append("chunk: ").append(Long.toHexString(chunkId)).append('\n'); + } + } + + /** + * Create a copy of this page. + * + * @return a mutable copy of this page + */ + public final Page copy() { + Page newPage = clone(); + newPage.pos = 0; + return newPage; + } + + @SuppressWarnings("unchecked") + @Override + protected final Page clone() { + Page clone; + try { + clone = (Page) super.clone(); + } catch (CloneNotSupportedException impossible) { + throw new RuntimeException(impossible); + } + return clone; + } + + /** + * Search the key in this page using a binary search. Instead of always + * starting the search in the middle, the last found index is cached. + *

+ * If the key was found, the returned value is the index in the key array. + * If not found, the returned value is negative, where -1 means the provided + * key is smaller than any keys in this page. See also Arrays.binarySearch. + * + * @param key the key + * @return the value or null + */ + int binarySearch(K key) { + int res = map.getKeyType().binarySearch(key, keys, getKeyCount(), cachedCompare); + cachedCompare = res < 0 ? ~res : res + 1; + return res; + } + + /** + * Split the page. This modifies the current page. + * + * @param at the split index + * @return the page with the entries after the split index + */ + abstract Page split(int at); + + /** + * Split the current keys array into two arrays. + * + * @param aCount size of the first array. + * @param bCount size of the second array/ + * @return the second array. + */ + final K[] splitKeys(int aCount, int bCount) { + assert aCount + bCount <= getKeyCount(); + K[] aKeys = createKeyStorage(aCount); + K[] bKeys = createKeyStorage(bCount); + System.arraycopy(keys, 0, aKeys, 0, aCount); + System.arraycopy(keys, getKeyCount() - bCount, bKeys, 0, bCount); + keys = aKeys; + return bKeys; + } + + /** + * Append additional key/value mappings to this Page. + * New mappings suppose to be in correct key order. + * + * @param extraKeyCount number of mappings to be added + * @param extraKeys to be added + * @param extraValues to be added + */ + abstract void expand(int extraKeyCount, K[] extraKeys, V[] extraValues); + + /** + * Expand the keys array. + * + * @param extraKeyCount number of extra key entries to create + * @param extraKeys extra key values + */ + final void expandKeys(int extraKeyCount, K[] extraKeys) { + int keyCount = getKeyCount(); + K[] newKeys = createKeyStorage(keyCount + extraKeyCount); + System.arraycopy(keys, 0, newKeys, 0, keyCount); + System.arraycopy(extraKeys, 0, newKeys, keyCount, extraKeyCount); + keys = newKeys; + } + + /** + * Get the total number of key-value pairs, including child pages. + * + * @return the number of key-value pairs + */ + public abstract long getTotalCount(); + + /** + * Get the number of key-value pairs for a given child. + * + * @param index the child index + * @return the descendant count + */ + abstract long getCounts(int index); + + /** + * Replace the child page. + * + * @param index the index + * @param c the new child page + */ + public abstract void setChild(int index, Page c); + + /** + * Replace the key at an index in this page. + * + * @param index the index + * @param key the new key + */ + public final void setKey(int index, K key) { + keys = keys.clone(); + if(isPersistent()) { + K old = keys[index]; + if (!map.isMemoryEstimationAllowed() || old == null) { + int mem = map.evaluateMemoryForKey(key); + if (old != null) { + mem -= map.evaluateMemoryForKey(old); + } + addMemory(mem); + } + } + keys[index] = key; + } + + /** + * Replace the value at an index in this page. + * + * @param index the index + * @param value the new value + * @return the old value + */ + public abstract V setValue(int index, V value); + + /** + * Insert a key-value pair into this leaf. + * + * @param index the index + * @param key the key + * @param value the value + */ + public abstract void insertLeaf(int index, K key, V value); + + /** + * Insert a child page into this node. + * + * @param index the index + * @param key the key + * @param childPage the child page + */ + public abstract void insertNode(int index, K key, Page childPage); + + /** + * Insert a key into the key array + * + * @param index index to insert at + * @param key the key value + */ + final void insertKey(int index, K key) { + int keyCount = getKeyCount(); + assert index <= keyCount : index + " > " + keyCount; + K[] newKeys = createKeyStorage(keyCount + 1); + DataUtils.copyWithGap(keys, newKeys, keyCount, index); + keys = newKeys; + + keys[index] = key; + + if (isPersistent()) { + addMemory(MEMORY_POINTER + map.evaluateMemoryForKey(key)); + } + } + + /** + * Remove the key and value (or child) at the given index. + * + * @param index the index + */ + public void remove(int index) { + int keyCount = getKeyCount(); + if (index == keyCount) { + --index; + } + if(isPersistent()) { + if (!map.isMemoryEstimationAllowed()) { + K old = getKey(index); + addMemory(-MEMORY_POINTER - map.evaluateMemoryForKey(old)); + } + } + K[] newKeys = createKeyStorage(keyCount - 1); + DataUtils.copyExcept(keys, newKeys, keyCount, index); + keys = newKeys; + } + + /** + * Read the page from the buffer. + * + * @param buff the buffer to read from + */ + private void read(ByteBuffer buff) { + int chunkId = DataUtils.getPageChunkId(pos); + int offset = DataUtils.getPageOffset(pos); + + int start = buff.position(); + int pageLength = buff.getInt(); // does not include optional part (pageNo) + int remaining = buff.remaining() + 4; + if (pageLength > remaining || pageLength < 4) { + throw DataUtils.newMVStoreException(DataUtils.ERROR_FILE_CORRUPT, + "File corrupted in chunk {0}, expected page length 4..{1}, got {2}", chunkId, remaining, + pageLength); + } + + short check = buff.getShort(); + int checkTest = DataUtils.getCheckValue(chunkId) + ^ DataUtils.getCheckValue(offset) + ^ DataUtils.getCheckValue(pageLength); + if (check != (short) checkTest) { + throw DataUtils.newMVStoreException(DataUtils.ERROR_FILE_CORRUPT, + "File corrupted in chunk {0}, expected check value {1}, got {2}", chunkId, checkTest, check); + } + + pageNo = DataUtils.readVarInt(buff); + if (pageNo < 0) { + throw DataUtils.newMVStoreException(DataUtils.ERROR_FILE_CORRUPT, + "File corrupted in chunk {0}, got negative page No {1}", chunkId, pageNo); + } + + int mapId = DataUtils.readVarInt(buff); + if (mapId != map.getId()) { + throw DataUtils.newMVStoreException(DataUtils.ERROR_FILE_CORRUPT, + "File corrupted in chunk {0}, expected map id {1}, got {2}", chunkId, map.getId(), mapId); + } + + int keyCount = DataUtils.readVarInt(buff); + keys = createKeyStorage(keyCount); + int type = buff.get(); + if(isLeaf() != ((type & 1) == PAGE_TYPE_LEAF)) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_FILE_CORRUPT, + "File corrupted in chunk {0}, expected node type {1}, got {2}", + chunkId, isLeaf() ? "0" : "1" , type); + } + + // to restrain hacky GenericDataType, which grabs the whole remainder of the buffer + buff.limit(start + pageLength); + + if (!isLeaf()) { + readPayLoad(buff); + } + boolean compressed = (type & DataUtils.PAGE_COMPRESSED) != 0; + if (compressed) { + Compressor compressor; + if ((type & DataUtils.PAGE_COMPRESSED_HIGH) == + DataUtils.PAGE_COMPRESSED_HIGH) { + compressor = map.getStore().getCompressorHigh(); + } else { + compressor = map.getStore().getCompressorFast(); + } + int lenAdd = DataUtils.readVarInt(buff); + int compLen = buff.remaining(); + byte[] comp; + int pos = 0; + if (buff.hasArray()) { + comp = buff.array(); + pos = buff.arrayOffset() + buff.position(); + } else { + comp = Utils.newBytes(compLen); + buff.get(comp); + } + int l = compLen + lenAdd; + buff = ByteBuffer.allocate(l); + compressor.expand(comp, pos, compLen, buff.array(), + buff.arrayOffset(), l); + } + map.getKeyType().read(buff, keys, keyCount); + if (isLeaf()) { + readPayLoad(buff); + } + diskSpaceUsed = pageLength; + recalculateMemory(); + } + + /** + * Read the page payload from the buffer. + * + * @param buff the buffer + */ + protected abstract void readPayLoad(ByteBuffer buff); + + public final boolean isSaved() { + return DataUtils.isPageSaved(pos); + } + + public final boolean isRemoved() { + return DataUtils.isPageRemoved(pos); + } + + /** + * Mark this page as removed "in memory". That means that only adjustment of + * "unsaved memory" amount is required. On the other hand, if page was + * persisted, it's removal should be reflected in occupancy of the + * containing chunk. + * + * @return true if it was marked by this call or has been marked already, + * false if page has been saved already. + */ + private boolean markAsRemoved() { + assert getTotalCount() > 0 : this; + long pagePos; + do { + pagePos = pos; + if (DataUtils.isPageSaved(pagePos)) { + return false; + } + assert !DataUtils.isPageRemoved(pagePos); + } while (!posUpdater.compareAndSet(this, 0L, 1L)); + return true; + } + + /** + * Store the page and update the position. + * + * @param chunk the chunk + * @param buff the target buffer + * @param toc prospective table of content + * @return the position of the buffer just after the type + */ + protected final int write(Chunk chunk, WriteBuffer buff, List toc) { + pageNo = toc.size(); + int keyCount = getKeyCount(); + int start = buff.position(); + buff.putInt(0) // placeholder for pageLength + .putShort((byte)0) // placeholder for check + .putVarInt(pageNo) + .putVarInt(map.getId()) + .putVarInt(keyCount); + int typePos = buff.position(); + int type = isLeaf() ? PAGE_TYPE_LEAF : DataUtils.PAGE_TYPE_NODE; + buff.put((byte)type); + int childrenPos = buff.position(); + writeChildren(buff, true); + int compressStart = buff.position(); + map.getKeyType().write(buff, keys, keyCount); + writeValues(buff); + MVStore store = map.getStore(); + int expLen = buff.position() - compressStart; + if (expLen > 16) { + int compressionLevel = store.getCompressionLevel(); + if (compressionLevel > 0) { + Compressor compressor; + int compressType; + if (compressionLevel == 1) { + compressor = store.getCompressorFast(); + compressType = DataUtils.PAGE_COMPRESSED; + } else { + compressor = store.getCompressorHigh(); + compressType = DataUtils.PAGE_COMPRESSED_HIGH; + } + byte[] comp = new byte[expLen * 2]; + ByteBuffer byteBuffer = buff.getBuffer(); + int pos = 0; + byte[] exp; + if (byteBuffer.hasArray()) { + exp = byteBuffer.array(); + pos = byteBuffer.arrayOffset() + compressStart; + } else { + exp = Utils.newBytes(expLen); + buff.position(compressStart).get(exp); + } + int compLen = compressor.compress(exp, pos, expLen, comp, 0); + int plus = DataUtils.getVarIntLen(expLen - compLen); + if (compLen + plus < expLen) { + buff.position(typePos) + .put((byte) (type | compressType)); + buff.position(compressStart) + .putVarInt(expLen - compLen) + .put(comp, 0, compLen); + } + } + } + int pageLength = buff.position() - start; + long tocElement = DataUtils.getTocElement(getMapId(), start, buff.position() - start, type); + toc.add(tocElement); + int chunkId = chunk.id; + int check = DataUtils.getCheckValue(chunkId) + ^ DataUtils.getCheckValue(start) + ^ DataUtils.getCheckValue(pageLength); + buff.putInt(start, pageLength). + putShort(start + 4, (short) check); + if (isSaved()) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_INTERNAL, "Page already stored"); + } + long pagePos = DataUtils.getPagePos(chunkId, tocElement); + boolean isDeleted = isRemoved(); + while (!posUpdater.compareAndSet(this, isDeleted ? 1L : 0L, pagePos)) { + isDeleted = isRemoved(); + } + store.cachePage(this); + if (type == DataUtils.PAGE_TYPE_NODE) { + // cache again - this will make sure nodes stays in the cache + // for a longer time + store.cachePage(this); + } + int pageLengthEncoded = DataUtils.getPageMaxLength(pos); + boolean singleWriter = map.isSingleWriter(); + chunk.accountForWrittenPage(pageLengthEncoded, singleWriter); + if (isDeleted) { + store.accountForRemovedPage(pagePos, chunk.version + 1, singleWriter, pageNo); + } + diskSpaceUsed = pageLengthEncoded != DataUtils.PAGE_LARGE ? pageLengthEncoded : pageLength; + return childrenPos; + } + + /** + * Write values that the buffer contains to the buff. + * + * @param buff the target buffer + */ + protected abstract void writeValues(WriteBuffer buff); + + /** + * Write page children to the buff. + * + * @param buff the target buffer + * @param withCounts true if the descendant counts should be written + */ + protected abstract void writeChildren(WriteBuffer buff, boolean withCounts); + + /** + * Store this page and all children that are changed, in reverse order, and + * update the position and the children. + * @param chunk the chunk + * @param buff the target buffer + * @param toc prospective table of content + */ + abstract void writeUnsavedRecursive(Chunk chunk, WriteBuffer buff, List toc); + + /** + * Unlink the children recursively after all data is written. + */ + abstract void releaseSavedPages(); + + public abstract int getRawChildPageCount(); + + protected final boolean isPersistent() { + return memory != IN_MEMORY; + } + + public final int getMemory() { + if (isPersistent()) { +// assert memory == calculateMemory() : +// "Memory calculation error " + memory + " != " + calculateMemory(); + return memory; + } + return 0; + } + + /** + * Amount of used disk space in persistent case including child pages. + * + * @return amount of used disk space in persistent case + */ + public long getDiskSpaceUsed() { + long r = 0; + if (isPersistent()) { + r += diskSpaceUsed; + if (!isLeaf()) { + for (int i = 0; i < getRawChildPageCount(); i++) { + long pos = getChildPagePos(i); + if (pos != 0) { + r += getChildPage(i).getDiskSpaceUsed(); + } + } + } + } + return r; + } + + /** + * Increase estimated memory used in persistent case. + * + * @param mem additional memory size. + */ + final void addMemory(int mem) { + memory += mem; + assert memory >= 0; + } + + /** + * Recalculate estimated memory used in persistent case. + */ + final void recalculateMemory() { + assert isPersistent(); + memory = calculateMemory(); + } + + /** + * Calculate estimated memory used in persistent case. + * + * @return memory in bytes + */ + protected int calculateMemory() { +//* + return map.evaluateMemoryForKeys(keys, getKeyCount()); +/*/ + int keyCount = getKeyCount(); + int mem = keyCount * MEMORY_POINTER; + DataType keyType = map.getKeyType(); + for (int i = 0; i < keyCount; i++) { + mem += getMemory(keyType, keys[i]); + } + return mem; +//*/ + } + + public boolean isComplete() { + return true; + } + + /** + * Called when done with copying page. + */ + public void setComplete() {} + + /** + * Make accounting changes (chunk occupancy or "unsaved" RAM), related to + * this page removal. + * + * @param version at which page was removed + * @return amount (negative), by which "unsaved memory" should be adjusted, + * if page is unsaved one, and 0 for page that was already saved, or + * in case of non-persistent map + */ + public final int removePage(long version) { + if(isPersistent() && getTotalCount() > 0) { + MVStore store = map.store; + if (!markAsRemoved()) { // only if it has been saved already + long pagePos = pos; + store.accountForRemovedPage(pagePos, version, map.isSingleWriter(), pageNo); + } else { + return -memory; + } + } + return 0; + } + + /** + * Extend path from a given CursorPos chain to "prepend point" in a B-tree, rooted at this Page. + * + * @param cursorPos presumably pointing to this Page (null if real root), to build upon + * @return new head of the CursorPos chain + */ + public abstract CursorPos getPrependCursorPos(CursorPos cursorPos); + + /** + * Extend path from a given CursorPos chain to "append point" in a B-tree, rooted at this Page. + * + * @param cursorPos presumably pointing to this Page (null if real root), to build upon + * @return new head of the CursorPos chain + */ + public abstract CursorPos getAppendCursorPos(CursorPos cursorPos); + + /** + * Remove all page data recursively. + * @param version at which page got removed + * @return adjustment for "unsaved memory" amount + */ + public abstract int removeAllRecursive(long version); + + /** + * Create array for keys storage. + * + * @param size number of entries + * @return values array + */ + public final K[] createKeyStorage(int size) { + return map.getKeyType().createStorage(size); + } + + /** + * Create array for values storage. + * + * @param size number of entries + * @return values array + */ + final V[] createValueStorage(int size) { + return map.getValueType().createStorage(size); + } + + /** + * Create an array of page references. + * + * @param the key class + * @param the value class + * @param size the number of entries + * @return the array + */ + @SuppressWarnings("unchecked") + public static PageReference[] createRefStorage(int size) { + return new PageReference[size]; + } + + /** + * A pointer to a page, either in-memory or using a page position. + */ + public static final class PageReference { + + /** + * Singleton object used when arrays of PageReference have not yet been filled. + */ + @SuppressWarnings("rawtypes") + static final PageReference EMPTY = new PageReference<>(null, 0, 0); + + /** + * The position, if known, or 0. + */ + private long pos; + + /** + * The page, if in memory, or null. + */ + private Page page; + + /** + * The descendant count for this child page. + */ + final long count; + + /** + * Get an empty page reference. + * + * @param the key class + * @param the value class + * @return the page reference + */ + @SuppressWarnings("unchecked") + public static PageReference empty() { + return EMPTY; + } + + public PageReference(Page page) { + this(page, page.getPos(), page.getTotalCount()); + } + + PageReference(long pos, long count) { + this(null, pos, count); + assert DataUtils.isPageSaved(pos); + } + + private PageReference(Page page, long pos, long count) { + this.page = page; + this.pos = pos; + this.count = count; + } + + public Page getPage() { + return page; + } + + /** + * Clear if necessary, reference to the actual child Page object, + * so it can be garbage collected if not actively used elsewhere. + * Reference is cleared only if corresponding page was already saved on a disk. + */ + void clearPageReference() { + if (page != null) { + page.releaseSavedPages(); + assert page.isSaved() || !page.isComplete(); + if (page.isSaved()) { + assert pos == page.getPos(); + assert count == page.getTotalCount() : count + " != " + page.getTotalCount(); + page = null; + } + } + } + + long getPos() { + return pos; + } + + /** + * Re-acquire position from in-memory page. + */ + void resetPos() { + Page p = page; + if (p != null && p.isSaved()) { + pos = p.getPos(); + assert count == p.getTotalCount(); + } + } + + @Override + public String toString() { + return "Cnt:" + count + ", pos:" + (pos == 0 ? "0" : DataUtils.getPageChunkId(pos) + + (page == null ? "" : "/" + page.pageNo) + + "-" + DataUtils.getPageOffset(pos) + ":" + DataUtils.getPageMaxLength(pos)) + + ((page == null ? DataUtils.getPageType(pos) == 0 : page.isLeaf()) ? " leaf" : " node") + + ", page:{" + page + "}"; + } + } + + + private static class NonLeaf extends Page { + /** + * The child page references. + */ + private PageReference[] children; + + /** + * The total entry count of this page and all children. + */ + private long totalCount; + + NonLeaf(MVMap map) { + super(map); + } + + NonLeaf(MVMap map, NonLeaf source, PageReference[] children, long totalCount) { + super(map, source); + this.children = children; + this.totalCount = totalCount; + } + + NonLeaf(MVMap map, K[] keys, PageReference[] children, long totalCount) { + super(map, keys); + this.children = children; + this.totalCount = totalCount; + } + + @Override + public int getNodeType() { + return DataUtils.PAGE_TYPE_NODE; + } + + @Override + public Page copy(MVMap map, boolean eraseChildrenRefs) { + return eraseChildrenRefs ? + new IncompleteNonLeaf<>(map, this) : + new NonLeaf<>(map, this, children, totalCount); + } + + @Override + public Page getChildPage(int index) { + PageReference ref = children[index]; + Page page = ref.getPage(); + if(page == null) { + page = map.readPage(ref.getPos()); + assert ref.getPos() == page.getPos(); + assert ref.count == page.getTotalCount(); + } + return page; + } + + @Override + public long getChildPagePos(int index) { + return children[index].getPos(); + } + + @Override + public V getValue(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public Page split(int at) { + assert !isSaved(); + int b = getKeyCount() - at; + K[] bKeys = splitKeys(at, b - 1); + PageReference[] aChildren = createRefStorage(at + 1); + PageReference[] bChildren = createRefStorage(b); + System.arraycopy(children, 0, aChildren, 0, at + 1); + System.arraycopy(children, at + 1, bChildren, 0, b); + children = aChildren; + + long t = 0; + for (PageReference x : aChildren) { + t += x.count; + } + totalCount = t; + t = 0; + for (PageReference x : bChildren) { + t += x.count; + } + Page newPage = createNode(map, bKeys, bChildren, t, 0); + if(isPersistent()) { + recalculateMemory(); + } + return newPage; + } + + @Override + public void expand(int keyCount, Object[] extraKeys, Object[] extraValues) { + throw new UnsupportedOperationException(); + } + + @Override + public long getTotalCount() { + assert !isComplete() || totalCount == calculateTotalCount() : + "Total count: " + totalCount + " != " + calculateTotalCount(); + return totalCount; + } + + private long calculateTotalCount() { + long check = 0; + int keyCount = getKeyCount(); + for (int i = 0; i <= keyCount; i++) { + check += children[i].count; + } + return check; + } + + void recalculateTotalCount() { + totalCount = calculateTotalCount(); + } + + @Override + long getCounts(int index) { + return children[index].count; + } + + @Override + public void setChild(int index, Page c) { + assert c != null; + PageReference child = children[index]; + if (c != child.getPage() || c.getPos() != child.getPos()) { + totalCount += c.getTotalCount() - child.count; + children = children.clone(); + children[index] = new PageReference<>(c); + } + } + + @Override + public V setValue(int index, V value) { + throw new UnsupportedOperationException(); + } + + @Override + public void insertLeaf(int index, K key, V value) { + throw new UnsupportedOperationException(); + } + + @Override + public void insertNode(int index, K key, Page childPage) { + int childCount = getRawChildPageCount(); + insertKey(index, key); + + PageReference[] newChildren = createRefStorage(childCount + 1); + DataUtils.copyWithGap(children, newChildren, childCount, index); + children = newChildren; + children[index] = new PageReference<>(childPage); + + totalCount += childPage.getTotalCount(); + if (isPersistent()) { + addMemory(MEMORY_POINTER + PAGE_MEMORY_CHILD); + } + } + + @Override + public void remove(int index) { + int childCount = getRawChildPageCount(); + super.remove(index); + if(isPersistent()) { + if (map.isMemoryEstimationAllowed()) { + addMemory(-getMemory() / childCount); + } else { + addMemory(-MEMORY_POINTER - PAGE_MEMORY_CHILD); + } + } + totalCount -= children[index].count; + PageReference[] newChildren = createRefStorage(childCount - 1); + DataUtils.copyExcept(children, newChildren, childCount, index); + children = newChildren; + } + + @Override + public int removeAllRecursive(long version) { + int unsavedMemory = removePage(version); + if (isPersistent()) { + for (int i = 0, size = map.getChildPageCount(this); i < size; i++) { + PageReference ref = children[i]; + Page page = ref.getPage(); + if (page != null) { + unsavedMemory += page.removeAllRecursive(version); + } else { + long pagePos = ref.getPos(); + assert DataUtils.isPageSaved(pagePos); + if (DataUtils.isLeafPosition(pagePos)) { + map.store.accountForRemovedPage(pagePos, version, map.isSingleWriter(), -1); + } else { + unsavedMemory += map.readPage(pagePos).removeAllRecursive(version); + } + } + } + } + return unsavedMemory; + } + + @Override + public CursorPos getPrependCursorPos(CursorPos cursorPos) { + Page childPage = getChildPage(0); + return childPage.getPrependCursorPos(new CursorPos<>(this, 0, cursorPos)); + } + + @Override + public CursorPos getAppendCursorPos(CursorPos cursorPos) { + int keyCount = getKeyCount(); + Page childPage = getChildPage(keyCount); + return childPage.getAppendCursorPos(new CursorPos<>(this, keyCount, cursorPos)); + } + + @Override + protected void readPayLoad(ByteBuffer buff) { + int keyCount = getKeyCount(); + children = createRefStorage(keyCount + 1); + long[] p = new long[keyCount + 1]; + for (int i = 0; i <= keyCount; i++) { + p[i] = buff.getLong(); + } + long total = 0; + for (int i = 0; i <= keyCount; i++) { + long s = DataUtils.readVarLong(buff); + long position = p[i]; + assert position == 0 ? s == 0 : s >= 0; + total += s; + children[i] = position == 0 ? + PageReference.empty() : + new PageReference<>(position, s); + } + totalCount = total; + } + + @Override + protected void writeValues(WriteBuffer buff) {} + + @Override + protected void writeChildren(WriteBuffer buff, boolean withCounts) { + int keyCount = getKeyCount(); + for (int i = 0; i <= keyCount; i++) { + buff.putLong(children[i].getPos()); + } + if(withCounts) { + for (int i = 0; i <= keyCount; i++) { + buff.putVarLong(children[i].count); + } + } + } + + @Override + void writeUnsavedRecursive(Chunk chunk, WriteBuffer buff, List toc) { + if (!isSaved()) { + int patch = write(chunk, buff, toc); + writeChildrenRecursive(chunk, buff, toc); + int old = buff.position(); + buff.position(patch); + writeChildren(buff, false); + buff.position(old); + } + } + + void writeChildrenRecursive(Chunk chunk, WriteBuffer buff, List toc) { + int len = getRawChildPageCount(); + for (int i = 0; i < len; i++) { + PageReference ref = children[i]; + Page p = ref.getPage(); + if (p != null) { + p.writeUnsavedRecursive(chunk, buff, toc); + ref.resetPos(); + } + } + } + + @Override + void releaseSavedPages() { + int len = getRawChildPageCount(); + for (int i = 0; i < len; i++) { + children[i].clearPageReference(); + } + } + + @Override + public int getRawChildPageCount() { + return getKeyCount() + 1; + } + + @Override + protected int calculateMemory() { + return super.calculateMemory() + PAGE_NODE_MEMORY + + getRawChildPageCount() * (MEMORY_POINTER + PAGE_MEMORY_CHILD); + } + + @Override + public void dump(StringBuilder buff) { + super.dump(buff); + int keyCount = getKeyCount(); + for (int i = 0; i <= keyCount; i++) { + if (i > 0) { + buff.append(" "); + } + buff.append("[").append(Long.toHexString(children[i].getPos())).append("]"); + if(i < keyCount) { + buff.append(" ").append(getKey(i)); + } + } + } + } + + + private static class IncompleteNonLeaf extends NonLeaf { + + private boolean complete; + + IncompleteNonLeaf(MVMap map, NonLeaf source) { + super(map, source, constructEmptyPageRefs(source.getRawChildPageCount()), source.getTotalCount()); + } + + private static PageReference[] constructEmptyPageRefs(int size) { + // replace child pages with empty pages + PageReference[] children = createRefStorage(size); + Arrays.fill(children, PageReference.empty()); + return children; + } + + @Override + void writeUnsavedRecursive(Chunk chunk, WriteBuffer buff, List toc) { + if (complete) { + super.writeUnsavedRecursive(chunk, buff, toc); + } else if (!isSaved()) { + writeChildrenRecursive(chunk, buff, toc); + } + } + + @Override + public boolean isComplete() { + return complete; + } + + @Override + public void setComplete() { + recalculateTotalCount(); + complete = true; + } + + @Override + public void dump(StringBuilder buff) { + super.dump(buff); + buff.append(", complete:").append(complete); + } + + } + + + + private static class Leaf extends Page { + /** + * The storage for values. + */ + private V[] values; + + Leaf(MVMap map) { + super(map); + } + + private Leaf(MVMap map, Leaf source) { + super(map, source); + this.values = source.values; + } + + Leaf(MVMap map, K[] keys, V[] values) { + super(map, keys); + this.values = values; + } + + @Override + public int getNodeType() { + return PAGE_TYPE_LEAF; + } + + @Override + public Page copy(MVMap map, boolean eraseChildrenRefs) { + return new Leaf<>(map, this); + } + + @Override + public Page getChildPage(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public long getChildPagePos(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public V getValue(int index) { + return values == null ? null : values[index]; + } + + @Override + public Page split(int at) { + assert !isSaved(); + int b = getKeyCount() - at; + K[] bKeys = splitKeys(at, b); + V[] bValues = createValueStorage(b); + if(values != null) { + V[] aValues = createValueStorage(at); + System.arraycopy(values, 0, aValues, 0, at); + System.arraycopy(values, at, bValues, 0, b); + values = aValues; + } + Page newPage = createLeaf(map, bKeys, bValues, 0); + if(isPersistent()) { + recalculateMemory(); + } + return newPage; + } + + @Override + public void expand(int extraKeyCount, K[] extraKeys, V[] extraValues) { + int keyCount = getKeyCount(); + expandKeys(extraKeyCount, extraKeys); + if(values != null) { + V[] newValues = createValueStorage(keyCount + extraKeyCount); + System.arraycopy(values, 0, newValues, 0, keyCount); + System.arraycopy(extraValues, 0, newValues, keyCount, extraKeyCount); + values = newValues; + } + if(isPersistent()) { + recalculateMemory(); + } + } + + @Override + public long getTotalCount() { + return getKeyCount(); + } + + @Override + long getCounts(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public void setChild(int index, Page c) { + throw new UnsupportedOperationException(); + } + + @Override + public V setValue(int index, V value) { + values = values.clone(); + V old = setValueInternal(index, value); + if(isPersistent()) { + if (!map.isMemoryEstimationAllowed()) { + addMemory(map.evaluateMemoryForValue(value) - + map.evaluateMemoryForValue(old)); + } + } + return old; + } + + private V setValueInternal(int index, V value) { + V old = values[index]; + values[index] = value; + return old; + } + + @Override + public void insertLeaf(int index, K key, V value) { + int keyCount = getKeyCount(); + insertKey(index, key); + + if(values != null) { + V[] newValues = createValueStorage(keyCount + 1); + DataUtils.copyWithGap(values, newValues, keyCount, index); + values = newValues; + setValueInternal(index, value); + if (isPersistent()) { + addMemory(MEMORY_POINTER + map.evaluateMemoryForValue(value)); + } + } + } + + @Override + public void insertNode(int index, K key, Page childPage) { + throw new UnsupportedOperationException(); + } + + @Override + public void remove(int index) { + int keyCount = getKeyCount(); + super.remove(index); + if (values != null) { + if(isPersistent()) { + if (map.isMemoryEstimationAllowed()) { + addMemory(-getMemory() / keyCount); + } else { + V old = getValue(index); + addMemory(-MEMORY_POINTER - map.evaluateMemoryForValue(old)); + } + } + V[] newValues = createValueStorage(keyCount - 1); + DataUtils.copyExcept(values, newValues, keyCount, index); + values = newValues; + } + } + + @Override + public int removeAllRecursive(long version) { + return removePage(version); + } + + @Override + public CursorPos getPrependCursorPos(CursorPos cursorPos) { + return new CursorPos<>(this, -1, cursorPos); + } + + @Override + public CursorPos getAppendCursorPos(CursorPos cursorPos) { + int keyCount = getKeyCount(); + return new CursorPos<>(this, ~keyCount, cursorPos); + } + + @Override + protected void readPayLoad(ByteBuffer buff) { + int keyCount = getKeyCount(); + values = createValueStorage(keyCount); + map.getValueType().read(buff, values, getKeyCount()); + } + + @Override + protected void writeValues(WriteBuffer buff) { + map.getValueType().write(buff, values, getKeyCount()); + } + + @Override + protected void writeChildren(WriteBuffer buff, boolean withCounts) {} + + @Override + void writeUnsavedRecursive(Chunk chunk, WriteBuffer buff, List toc) { + if (!isSaved()) { + write(chunk, buff, toc); + } + } + + @Override + void releaseSavedPages() {} + + @Override + public int getRawChildPageCount() { + return 0; + } + + @Override + protected int calculateMemory() { +//* + return super.calculateMemory() + PAGE_LEAF_MEMORY + + (values == null ? 0 : map.evaluateMemoryForValues(values, getKeyCount())); +/*/ + int keyCount = getKeyCount(); + int mem = super.calculateMemory() + PAGE_LEAF_MEMORY + keyCount * MEMORY_POINTER; + DataType valueType = map.getValueType(); + for (int i = 0; i < keyCount; i++) { + mem += getMemory(valueType, values[i]); + } + return mem; +//*/ + } + + @Override + public void dump(StringBuilder buff) { + super.dump(buff); + int keyCount = getKeyCount(); + for (int i = 0; i < keyCount; i++) { + if (i > 0) { + buff.append(" "); + } + buff.append(getKey(i)); + if (values != null) { + buff.append(':'); + buff.append(getValue(i)); + } + } + } + } +} diff --git a/h2/src/main/org/h2/mvstore/RootReference.java b/h2/src/main/org/h2/mvstore/RootReference.java new file mode 100644 index 0000000..dff7983 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/RootReference.java @@ -0,0 +1,256 @@ +/* + * 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.mvstore; + +/** + * Class RootReference is an immutable structure to represent state of the MVMap as a whole + * (not related to a particular B-Tree node). + * Single structure would allow for non-blocking atomic state change. + * The most important part of it is a reference to the root node. + * + * @author Andrei Tokar + */ +public final class RootReference { + + /** + * The root page. + */ + public final Page root; + /** + * The version used for writing. + */ + public final long version; + /** + * Counter of reentrant locks. + */ + private final byte holdCount; + /** + * Lock owner thread id. + */ + private final long ownerId; + /** + * Reference to the previous root in the chain. + * That is the last root of the previous version, which had any data changes. + * Versions without any data changes are dropped from the chain, as it built. + */ + volatile RootReference previous; + /** + * Counter for successful root updates. + */ + final long updateCounter; + /** + * Counter for attempted root updates. + */ + final long updateAttemptCounter; + /** + * Size of the occupied part of the append buffer. + */ + private final byte appendCounter; + + + // This one is used to set root initially and for r/o snapshots + RootReference(Page root, long version) { + this.root = root; + this.version = version; + this.previous = null; + this.updateCounter = 1; + this.updateAttemptCounter = 1; + this.holdCount = 0; + this.ownerId = 0; + this.appendCounter = 0; + } + + private RootReference(RootReference r, Page root, long updateAttemptCounter) { + this.root = root; + this.version = r.version; + this.previous = r.previous; + this.updateCounter = r.updateCounter + 1; + this.updateAttemptCounter = r.updateAttemptCounter + updateAttemptCounter; + this.holdCount = 0; + this.ownerId = 0; + this.appendCounter = r.appendCounter; + } + + // This one is used for locking + private RootReference(RootReference r, int attempt) { + this.root = r.root; + this.version = r.version; + this.previous = r.previous; + this.updateCounter = r.updateCounter + 1; + this.updateAttemptCounter = r.updateAttemptCounter + attempt; + assert r.holdCount == 0 || r.ownerId == Thread.currentThread().getId() // + : Thread.currentThread().getId() + " " + r; + this.holdCount = (byte)(r.holdCount + 1); + this.ownerId = Thread.currentThread().getId(); + this.appendCounter = r.appendCounter; + } + + // This one is used for unlocking + private RootReference(RootReference r, Page root, boolean keepLocked, int appendCounter) { + this.root = root; + this.version = r.version; + this.previous = r.previous; + this.updateCounter = r.updateCounter; + this.updateAttemptCounter = r.updateAttemptCounter; + assert r.holdCount > 0 && r.ownerId == Thread.currentThread().getId() // + : Thread.currentThread().getId() + " " + r; + this.holdCount = (byte)(r.holdCount - (keepLocked ? 0 : 1)); + this.ownerId = this.holdCount == 0 ? 0 : Thread.currentThread().getId(); + this.appendCounter = (byte) appendCounter; + } + + // This one is used for version change + private RootReference(RootReference r, long version, int attempt) { + RootReference previous = r; + RootReference tmp; + while ((tmp = previous.previous) != null && tmp.root == r.root) { + previous = tmp; + } + this.root = r.root; + this.version = version; + this.previous = previous; + this.updateCounter = r.updateCounter + 1; + this.updateAttemptCounter = r.updateAttemptCounter + attempt; + this.holdCount = r.holdCount == 0 ? 0 : (byte)(r.holdCount - 1); + this.ownerId = this.holdCount == 0 ? 0 : r.ownerId; + assert r.appendCounter == 0; + this.appendCounter = 0; + } + + /** + * Try to unlock. + * + * @param newRootPage the new root page + * @param attemptCounter the number of attempts so far + * @return the new, unlocked, root reference, or null if not successful + */ + RootReference updateRootPage(Page newRootPage, long attemptCounter) { + return isFree() ? tryUpdate(new RootReference<>(this, newRootPage, attemptCounter)) : null; + } + + /** + * Try to lock. + * + * @param attemptCounter the number of attempts so far + * @return the new, locked, root reference, or null if not successful + */ + RootReference tryLock(int attemptCounter) { + return canUpdate() ? tryUpdate(new RootReference<>(this, attemptCounter)) : null; + } + + /** + * Try to unlock, and if successful update the version + * + * @param version the version + * @param attempt the number of attempts so far + * @return the new, unlocked and updated, root reference, or null if not successful + */ + RootReference tryUnlockAndUpdateVersion(long version, int attempt) { + return canUpdate() ? tryUpdate(new RootReference<>(this, version, attempt)) : null; + } + + /** + * Update the page, possibly keeping it locked. + * + * @param page the page + * @param keepLocked whether to keep it locked + * @param appendCounter number of items in append buffer + * @return the new root reference, or null if not successful + */ + RootReference updatePageAndLockedStatus(Page page, boolean keepLocked, int appendCounter) { + return canUpdate() ? tryUpdate(new RootReference<>(this, page, keepLocked, appendCounter)) : null; + } + + /** + * Removed old versions that are not longer used. + * + * @param oldestVersionToKeep the oldest version that needs to be retained + */ + void removeUnusedOldVersions(long oldestVersionToKeep) { + // We need to keep at least one previous version (if any) here, + // because in order to retain whole history of some version + // we really need last root of the previous version. + // Root labeled with version "X" is the LAST known root for that version + // and therefore the FIRST known root for the version "X+1" + for(RootReference rootRef = this; rootRef != null; rootRef = rootRef.previous) { + if (rootRef.version < oldestVersionToKeep) { + RootReference previous; + assert (previous = rootRef.previous) == null || previous.getAppendCounter() == 0 // + : oldestVersionToKeep + " " + rootRef.previous; + rootRef.previous = null; + } + } + } + + boolean isLocked() { + return holdCount != 0; + } + + private boolean isFree() { + return holdCount == 0; + } + + + private boolean canUpdate() { + return isFree() || ownerId == Thread.currentThread().getId(); + } + + public boolean isLockedByCurrentThread() { + return holdCount != 0 && ownerId == Thread.currentThread().getId(); + } + + private RootReference tryUpdate(RootReference updatedRootReference) { + assert canUpdate(); + return root.map.compareAndSetRoot(this, updatedRootReference) ? updatedRootReference : null; + } + + long getVersion() { + RootReference prev = previous; + return prev == null || prev.root != root || + prev.appendCounter != appendCounter ? + version : prev.getVersion(); + } + + /** + * Does the root have changes since the specified version? + * + * @param version to check against + * @param persistent whether map is backed by persistent storage + * @return true if this root has unsaved changes + */ + boolean hasChangesSince(long version, boolean persistent) { + return persistent && (root.isSaved() ? getAppendCounter() > 0 : getTotalCount() > 0) + || getVersion() > version; + } + + int getAppendCounter() { + return appendCounter & 0xff; + } + + /** + * Whether flushing is needed. + * + * @return true if yes + */ + public boolean needFlush() { + return appendCounter != 0; + } + + public long getTotalCount() { + return root.getTotalCount() + getAppendCounter(); + } + + @Override + public String toString() { + return "RootReference(" + System.identityHashCode(root) + + ", v=" + version + + ", owner=" + ownerId + (ownerId == Thread.currentThread().getId() ? "(current)" : "") + + ", holdCnt=" + holdCount + + ", keys=" + root.getTotalCount() + + ", append=" + getAppendCounter() + + ")"; + } +} diff --git a/h2/src/main/org/h2/mvstore/StreamStore.java b/h2/src/main/org/h2/mvstore/StreamStore.java new file mode 100644 index 0000000..82a3944 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/StreamStore.java @@ -0,0 +1,583 @@ +/* + * 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.mvstore; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; + +/** + * A facility to store streams in a map. Streams are split into blocks, which + * are stored in a map. Very small streams are inlined in the stream id. + *

+ * The key of the map is a long (incremented for each stored block). The default + * initial value is 0. Before storing blocks into the map, the stream store + * checks if there is already a block with the next key, and if necessary + * searches the next free entry using a binary search (0 to Long.MAX_VALUE). + *

+ * Before storing + *

+ * The format of the binary id is: An empty id represents 0 bytes of data. + * In-place data is encoded as 0, the size (a variable size int), then the data. + * A stored block is encoded as 1, the length of the block (a variable size + * int), then the key (a variable size long). Multiple ids can be concatenated + * to concatenate the data. If the id is large, it is stored itself, which is + * encoded as 2, the total length (a variable size long), and the key of the + * block that contains the id (a variable size long). + */ +public class StreamStore { + + private final Map map; + private int minBlockSize = 256; + private int maxBlockSize = 256 * 1024; + private final AtomicLong nextKey = new AtomicLong(); + private final AtomicReference nextBuffer = + new AtomicReference<>(); + + /** + * Create a stream store instance. + * + * @param map the map to store blocks of data + */ + public StreamStore(Map map) { + this.map = map; + } + + public Map getMap() { + return map; + } + + public void setNextKey(long nextKey) { + this.nextKey.set(nextKey); + } + + public long getNextKey() { + return nextKey.get(); + } + + /** + * Set the minimum block size. The default is 256 bytes. + * + * @param minBlockSize the new value + */ + public void setMinBlockSize(int minBlockSize) { + this.minBlockSize = minBlockSize; + } + + public int getMinBlockSize() { + return minBlockSize; + } + + /** + * Set the maximum block size. The default is 256 KB. + * + * @param maxBlockSize the new value + */ + public void setMaxBlockSize(int maxBlockSize) { + this.maxBlockSize = maxBlockSize; + } + + public long getMaxBlockSize() { + return maxBlockSize; + } + + /** + * Store the stream, and return the id. The stream is not closed. + * + * @param in the stream + * @return the id (potentially an empty array) + * @throws IOException If an I/O error occurs + */ + @SuppressWarnings("resource") + public byte[] put(InputStream in) throws IOException { + ByteArrayOutputStream id = new ByteArrayOutputStream(); + int level = 0; + try { + while (!put(id, in, level)) { + if (id.size() > maxBlockSize / 2) { + id = putIndirectId(id); + level++; + } + } + } catch (IOException e) { + remove(id.toByteArray()); + throw e; + } + if (id.size() > minBlockSize * 2) { + id = putIndirectId(id); + } + return id.toByteArray(); + } + + private boolean put(ByteArrayOutputStream id, InputStream in, int level) + throws IOException { + if (level > 0) { + ByteArrayOutputStream id2 = new ByteArrayOutputStream(); + while (true) { + boolean eof = put(id2, in, level - 1); + if (id2.size() > maxBlockSize / 2) { + id2 = putIndirectId(id2); + id2.writeTo(id); + return eof; + } else if (eof) { + id2.writeTo(id); + return true; + } + } + } + byte[] readBuffer = nextBuffer.getAndSet(null); + if (readBuffer == null) { + readBuffer = new byte[maxBlockSize]; + } + byte[] buff = read(in, readBuffer); + if (buff != readBuffer) { + // re-use the buffer if the result was shorter + nextBuffer.set(readBuffer); + } + int len = buff.length; + if (len == 0) { + return true; + } + boolean eof = len < maxBlockSize; + if (len < minBlockSize) { + // in-place: 0, len (int), data + id.write(0); + DataUtils.writeVarInt(id, len); + id.write(buff); + } else { + // block: 1, len (int), blockId (long) + id.write(1); + DataUtils.writeVarInt(id, len); + DataUtils.writeVarLong(id, writeBlock(buff)); + } + return eof; + } + + private static byte[] read(InputStream in, byte[] target) + throws IOException { + int copied = 0; + int remaining = target.length; + while (remaining > 0) { + try { + int len = in.read(target, copied, remaining); + if (len < 0) { + return Arrays.copyOf(target, copied); + } + copied += len; + remaining -= len; + } catch (RuntimeException e) { + throw new IOException(e); + } + } + return target; + } + + private ByteArrayOutputStream putIndirectId(ByteArrayOutputStream id) + throws IOException { + byte[] data = id.toByteArray(); + id = new ByteArrayOutputStream(); + // indirect: 2, total len (long), blockId (long) + id.write(2); + DataUtils.writeVarLong(id, length(data)); + DataUtils.writeVarLong(id, writeBlock(data)); + return id; + } + + private long writeBlock(byte[] data) { + long key = getAndIncrementNextKey(); + map.put(key, data); + onStore(data.length); + return key; + } + + /** + * This method is called after a block of data is stored. Override this + * method to persist data if necessary. + * + * @param len the length of the stored block. + */ + @SuppressWarnings("unused") + protected void onStore(int len) { + // do nothing by default + } + + /** + * Generate a new key. + * + * @return the new key + */ + private long getAndIncrementNextKey() { + long key = nextKey.getAndIncrement(); + if (!map.containsKey(key)) { + return key; + } + // search the next free id using binary search + synchronized (this) { + long low = key, high = Long.MAX_VALUE; + while (low < high) { + long x = (low + high) >>> 1; + if (map.containsKey(x)) { + low = x + 1; + } else { + high = x; + } + } + key = low; + nextKey.set(key + 1); + return key; + } + } + + /** + * Get the key of the biggest block, of -1 for inline data. + * This method is used to garbage collect orphaned blocks. + * + * @param id the id + * @return the key, or -1 + */ + public long getMaxBlockKey(byte[] id) { + long maxKey = -1; + ByteBuffer idBuffer = ByteBuffer.wrap(id); + while (idBuffer.hasRemaining()) { + switch (idBuffer.get()) { + case 0: + // in-place: 0, len (int), data + int len = DataUtils.readVarInt(idBuffer); + idBuffer.position(idBuffer.position() + len); + break; + case 1: + // block: 1, len (int), blockId (long) + DataUtils.readVarInt(idBuffer); + long k = DataUtils.readVarLong(idBuffer); + maxKey = Math.max(maxKey, k); + break; + case 2: + // indirect: 2, total len (long), blockId (long) + DataUtils.readVarLong(idBuffer); + long k2 = DataUtils.readVarLong(idBuffer); + maxKey = k2; + byte[] r = map.get(k2); + // recurse + long m = getMaxBlockKey(r); + if (m >= 0) { + maxKey = Math.max(maxKey, m); + } + break; + default: + throw DataUtils.newIllegalArgumentException( + "Unsupported id {0}", Arrays.toString(id)); + } + } + return maxKey; + } + + /** + * Remove all stored blocks for the given id. + * + * @param id the id + */ + public void remove(byte[] id) { + ByteBuffer idBuffer = ByteBuffer.wrap(id); + while (idBuffer.hasRemaining()) { + switch (idBuffer.get()) { + case 0: + // in-place: 0, len (int), data + int len = DataUtils.readVarInt(idBuffer); + idBuffer.position(idBuffer.position() + len); + break; + case 1: + // block: 1, len (int), blockId (long) + DataUtils.readVarInt(idBuffer); + long k = DataUtils.readVarLong(idBuffer); + map.remove(k); + break; + case 2: + // indirect: 2, total len (long), blockId (long) + DataUtils.readVarLong(idBuffer); + long k2 = DataUtils.readVarLong(idBuffer); + // recurse + remove(map.get(k2)); + map.remove(k2); + break; + default: + throw DataUtils.newIllegalArgumentException( + "Unsupported id {0}", Arrays.toString(id)); + } + } + } + + /** + * Convert the id to a human readable string. + * + * @param id the stream id + * @return the string + */ + public static String toString(byte[] id) { + StringBuilder buff = new StringBuilder(); + ByteBuffer idBuffer = ByteBuffer.wrap(id); + long length = 0; + while (idBuffer.hasRemaining()) { + long block; + int len; + switch (idBuffer.get()) { + case 0: + // in-place: 0, len (int), data + len = DataUtils.readVarInt(idBuffer); + idBuffer.position(idBuffer.position() + len); + buff.append("data len=").append(len); + length += len; + break; + case 1: + // block: 1, len (int), blockId (long) + len = DataUtils.readVarInt(idBuffer); + length += len; + block = DataUtils.readVarLong(idBuffer); + buff.append("block ").append(block).append(" len=").append(len); + break; + case 2: + // indirect: 2, total len (long), blockId (long) + len = DataUtils.readVarInt(idBuffer); + length += DataUtils.readVarLong(idBuffer); + block = DataUtils.readVarLong(idBuffer); + buff.append("indirect block ").append(block).append(" len=").append(len); + break; + default: + buff.append("error"); + } + buff.append(", "); + } + buff.append("length=").append(length); + return buff.toString(); + } + + /** + * Calculate the number of data bytes for the given id. As the length is + * encoded in the id, this operation does not cause any reads in the map. + * + * @param id the id + * @return the length + */ + public long length(byte[] id) { + ByteBuffer idBuffer = ByteBuffer.wrap(id); + long length = 0; + while (idBuffer.hasRemaining()) { + switch (idBuffer.get()) { + case 0: + // in-place: 0, len (int), data + int len = DataUtils.readVarInt(idBuffer); + idBuffer.position(idBuffer.position() + len); + length += len; + break; + case 1: + // block: 1, len (int), blockId (long) + length += DataUtils.readVarInt(idBuffer); + DataUtils.readVarLong(idBuffer); + break; + case 2: + // indirect: 2, total len (long), blockId (long) + length += DataUtils.readVarLong(idBuffer); + DataUtils.readVarLong(idBuffer); + break; + default: + throw DataUtils.newIllegalArgumentException( + "Unsupported id {0}", Arrays.toString(id)); + } + } + return length; + } + + /** + * Check whether the id itself contains all the data. This operation does + * not cause any reads in the map. + * + * @param id the id + * @return if the id contains the data + */ + public boolean isInPlace(byte[] id) { + ByteBuffer idBuffer = ByteBuffer.wrap(id); + while (idBuffer.hasRemaining()) { + if (idBuffer.get() != 0) { + return false; + } + int len = DataUtils.readVarInt(idBuffer); + idBuffer.position(idBuffer.position() + len); + } + return true; + } + + /** + * Open an input stream to read data. + * + * @param id the id + * @return the stream + */ + public InputStream get(byte[] id) { + return new Stream(this, id); + } + + /** + * Get the block. + * + * @param key the key + * @return the block + */ + byte[] getBlock(long key) { + byte[] data = map.get(key); + if (data == null) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_BLOCK_NOT_FOUND, + "Block {0} not found", key); + } + return data; + } + + /** + * A stream backed by a map. + */ + static class Stream extends InputStream { + + private final StreamStore store; + private byte[] oneByteBuffer; + private ByteBuffer idBuffer; + private ByteArrayInputStream buffer; + private long skip; + private final long length; + private long pos; + + Stream(StreamStore store, byte[] id) { + this.store = store; + this.length = store.length(id); + this.idBuffer = ByteBuffer.wrap(id); + } + + @Override + public int read() throws IOException { + byte[] buffer = oneByteBuffer; + if (buffer == null) { + buffer = oneByteBuffer = new byte[1]; + } + int len = read(buffer, 0, 1); + return len == -1 ? -1 : (buffer[0] & 255); + } + + @Override + public long skip(long n) { + n = Math.min(length - pos, n); + if (n == 0) { + return 0; + } + if (buffer != null) { + long s = buffer.skip(n); + if (s > 0) { + n = s; + } else { + buffer = null; + skip += n; + } + } else { + skip += n; + } + pos += n; + return n; + } + + @Override + public void close() { + buffer = null; + idBuffer.position(idBuffer.limit()); + pos = length; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (len <= 0) { + return 0; + } + while (true) { + if (buffer == null) { + try { + buffer = nextBuffer(); + } catch (MVStoreException e) { + String msg = DataUtils.formatMessage( + DataUtils.ERROR_BLOCK_NOT_FOUND, + "Block not found in id {0}", + Arrays.toString(idBuffer.array())); + throw new IOException(msg, e); + } + if (buffer == null) { + return -1; + } + } + int result = buffer.read(b, off, len); + if (result > 0) { + pos += result; + return result; + } + buffer = null; + } + } + + private ByteArrayInputStream nextBuffer() { + while (idBuffer.hasRemaining()) { + switch (idBuffer.get()) { + case 0: { + int len = DataUtils.readVarInt(idBuffer); + if (skip >= len) { + skip -= len; + idBuffer.position(idBuffer.position() + len); + continue; + } + int p = (int) (idBuffer.position() + skip); + int l = (int) (len - skip); + idBuffer.position(p + l); + return new ByteArrayInputStream(idBuffer.array(), p, l); + } + case 1: { + int len = DataUtils.readVarInt(idBuffer); + long key = DataUtils.readVarLong(idBuffer); + if (skip >= len) { + skip -= len; + continue; + } + byte[] data = store.getBlock(key); + int s = (int) skip; + skip = 0; + return new ByteArrayInputStream(data, s, data.length - s); + } + case 2: { + long len = DataUtils.readVarLong(idBuffer); + long key = DataUtils.readVarLong(idBuffer); + if (skip >= len) { + skip -= len; + continue; + } + byte[] k = store.getBlock(key); + ByteBuffer newBuffer = ByteBuffer.allocate(k.length + + idBuffer.limit() - idBuffer.position()); + newBuffer.put(k); + newBuffer.put(idBuffer); + newBuffer.flip(); + idBuffer = newBuffer; + return nextBuffer(); + } + default: + throw DataUtils.newIllegalArgumentException( + "Unsupported id {0}", + Arrays.toString(idBuffer.array())); + } + } + return null; + } + + } + +} diff --git a/h2/src/main/org/h2/mvstore/WriteBuffer.java b/h2/src/main/org/h2/mvstore/WriteBuffer.java new file mode 100644 index 0000000..9dd2be2 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/WriteBuffer.java @@ -0,0 +1,331 @@ +/* + * 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.mvstore; + +import java.nio.ByteBuffer; + +/** + * An auto-resize buffer to write data into a ByteBuffer. + */ +public class WriteBuffer { + + /** + * The maximum size of the buffer in order to be re-used after a clear + * operation. + */ + private static final int MAX_REUSE_CAPACITY = 4 * 1024 * 1024; + + /** + * The minimum number of bytes to grow a buffer at a time. + */ + private static final int MIN_GROW = 1024 * 1024; + + /** + * The buffer that is used after a clear operation. + */ + private ByteBuffer reuse; + + /** + * The current buffer (may be replaced if it is too small). + */ + private ByteBuffer buff; + + public WriteBuffer(int initialSize) { + reuse = ByteBuffer.allocate(initialSize); + buff = reuse; + } + + public WriteBuffer() { + this(MIN_GROW); + } + + /** + * Write a variable size integer. + * + * @param x the value + * @return this + */ + public WriteBuffer putVarInt(int x) { + DataUtils.writeVarInt(ensureCapacity(5), x); + return this; + } + + /** + * Write a variable size long. + * + * @param x the value + * @return this + */ + public WriteBuffer putVarLong(long x) { + DataUtils.writeVarLong(ensureCapacity(10), x); + return this; + } + + /** + * Write the characters of a string in a format similar to UTF-8. + * + * @param s the string + * @param len the number of characters to write + * @return this + */ + public WriteBuffer putStringData(String s, int len) { + ByteBuffer b = ensureCapacity(3 * len); + DataUtils.writeStringData(b, s, len); + return this; + } + + /** + * Put a byte. + * + * @param x the value + * @return this + */ + public WriteBuffer put(byte x) { + ensureCapacity(1).put(x); + return this; + } + + /** + * Put a character. + * + * @param x the value + * @return this + */ + public WriteBuffer putChar(char x) { + ensureCapacity(2).putChar(x); + return this; + } + + /** + * Put a short. + * + * @param x the value + * @return this + */ + public WriteBuffer putShort(short x) { + ensureCapacity(2).putShort(x); + return this; + } + + /** + * Put an integer. + * + * @param x the value + * @return this + */ + public WriteBuffer putInt(int x) { + ensureCapacity(4).putInt(x); + return this; + } + + /** + * Put a long. + * + * @param x the value + * @return this + */ + public WriteBuffer putLong(long x) { + ensureCapacity(8).putLong(x); + return this; + } + + /** + * Put a float. + * + * @param x the value + * @return this + */ + public WriteBuffer putFloat(float x) { + ensureCapacity(4).putFloat(x); + return this; + } + + /** + * Put a double. + * + * @param x the value + * @return this + */ + public WriteBuffer putDouble(double x) { + ensureCapacity(8).putDouble(x); + return this; + } + + /** + * Put a byte array. + * + * @param bytes the value + * @return this + */ + public WriteBuffer put(byte[] bytes) { + ensureCapacity(bytes.length).put(bytes); + return this; + } + + /** + * Put a byte array. + * + * @param bytes the value + * @param offset the source offset + * @param length the number of bytes + * @return this + */ + public WriteBuffer put(byte[] bytes, int offset, int length) { + ensureCapacity(length).put(bytes, offset, length); + return this; + } + + /** + * Put the contents of a byte buffer. + * + * @param src the source buffer + * @return this + */ + public WriteBuffer put(ByteBuffer src) { + ensureCapacity(src.remaining()).put(src); + return this; + } + + /** + * Set the limit, possibly growing the buffer. + * + * @param newLimit the new limit + * @return this + */ + public WriteBuffer limit(int newLimit) { + ensureCapacity(newLimit - buff.position()).limit(newLimit); + return this; + } + + /** + * Get the capacity. + * + * @return the capacity + */ + public int capacity() { + return buff.capacity(); + } + + /** + * Set the position. + * + * @param newPosition the new position + * @return the new position + */ + public WriteBuffer position(int newPosition) { + buff.position(newPosition); + return this; + } + + /** + * Get the limit. + * + * @return the limit + */ + public int limit() { + return buff.limit(); + } + + /** + * Get the current position. + * + * @return the position + */ + public int position() { + return buff.position(); + } + + /** + * Copy the data into the destination array. + * + * @param dst the destination array + * @return this + */ + public WriteBuffer get(byte[] dst) { + buff.get(dst); + return this; + } + + /** + * Update an integer at the given index. + * + * @param index the index + * @param value the value + * @return this + */ + public WriteBuffer putInt(int index, int value) { + buff.putInt(index, value); + return this; + } + + /** + * Update a short at the given index. + * + * @param index the index + * @param value the value + * @return this + */ + public WriteBuffer putShort(int index, short value) { + buff.putShort(index, value); + return this; + } + + /** + * Clear the buffer after use. + * + * @return this + */ + public WriteBuffer clear() { + if (buff.limit() > MAX_REUSE_CAPACITY) { + buff = reuse; + } else if (buff != reuse) { + reuse = buff; + } + buff.clear(); + return this; + } + + /** + * Get the byte buffer. + * + * @return the byte buffer + */ + public ByteBuffer getBuffer() { + return buff; + } + + private ByteBuffer ensureCapacity(int len) { + if (buff.remaining() < len) { + grow(len); + } + return buff; + } + + private void grow(int additional) { + ByteBuffer temp = buff; + int needed = additional - temp.remaining(); + // grow at least MIN_GROW + long grow = Math.max(needed, MIN_GROW); + // grow at least 50% of the current size + grow = Math.max(temp.capacity() / 2, grow); + // the new capacity is at most Integer.MAX_VALUE + int newCapacity = (int) Math.min(Integer.MAX_VALUE, temp.capacity() + grow); + if (newCapacity < needed) { + throw new OutOfMemoryError("Capacity: " + newCapacity + " needed: " + needed); + } + try { + buff = ByteBuffer.allocate(newCapacity); + } catch (OutOfMemoryError e) { + throw new OutOfMemoryError("Capacity: " + newCapacity); + } + temp.flip(); + buff.put(temp); + if (newCapacity <= MAX_REUSE_CAPACITY) { + reuse = buff; + } + } + +} diff --git a/h2/src/main/org/h2/mvstore/cache/CacheLongKeyLIRS.java b/h2/src/main/org/h2/mvstore/cache/CacheLongKeyLIRS.java new file mode 100644 index 0000000..d75127e --- /dev/null +++ b/h2/src/main/org/h2/mvstore/cache/CacheLongKeyLIRS.java @@ -0,0 +1,1210 @@ +/* + * 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.mvstore.cache; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.h2.mvstore.DataUtils; + +/** + * A scan resistant cache that uses keys of type long. It is meant to cache + * objects that are relatively costly to acquire, for example file content. + *

+ * This implementation is multi-threading safe and supports concurrent access. + * Null keys or null values are not allowed. The map fill factor is at most 75%. + *

+ * Each entry is assigned a distinct memory size, and the cache will try to use + * at most the specified amount of memory. The memory unit is not relevant, + * however it is suggested to use bytes as the unit. + *

+ * This class implements an approximation of the LIRS replacement algorithm + * invented by Xiaodong Zhang and Song Jiang as described in + * https://web.cse.ohio-state.edu/~zhang.574/lirs-sigmetrics-02.html with a few + * smaller changes: An additional queue for non-resident entries is used, to + * prevent unbound memory usage. The maximum size of this queue is at most the + * size of the rest of the stack. About 6.25% of the mapped entries are cold. + *

+ * Internally, the cache is split into a number of segments, and each segment is + * an individual LIRS cache. + *

+ * Accessed entries are only moved to the top of the stack if at least a number + * of other entries have been moved to the front (8 per segment by default). + * Write access and moving entries to the top of the stack is synchronized per + * segment. + * + * @author Thomas Mueller + * @param the value type + */ +public class CacheLongKeyLIRS { + + /** + * The maximum memory this cache should use. + */ + private long maxMemory; + + private final Segment[] segments; + + private final int segmentCount; + private final int segmentShift; + private final int segmentMask; + private final int stackMoveDistance; + private final int nonResidentQueueSize; + private final int nonResidentQueueSizeHigh; + + /** + * Create a new cache with the given memory size. + * + * @param config the configuration + */ + @SuppressWarnings("unchecked") + public CacheLongKeyLIRS(Config config) { + setMaxMemory(config.maxMemory); + this.nonResidentQueueSize = config.nonResidentQueueSize; + this.nonResidentQueueSizeHigh = config.nonResidentQueueSizeHigh; + DataUtils.checkArgument( + Integer.bitCount(config.segmentCount) == 1, + "The segment count must be a power of 2, is {0}", config.segmentCount); + this.segmentCount = config.segmentCount; + this.segmentMask = segmentCount - 1; + this.stackMoveDistance = config.stackMoveDistance; + segments = new Segment[segmentCount]; + clear(); + // use the high bits for the segment + this.segmentShift = 32 - Integer.bitCount(segmentMask); + } + + /** + * Remove all entries. + */ + public void clear() { + long max = getMaxItemSize(); + for (int i = 0; i < segmentCount; i++) { + segments[i] = new Segment<>(max, stackMoveDistance, 8, nonResidentQueueSize, + nonResidentQueueSizeHigh); + } + } + + /** + * Determines max size of the data item size to fit into cache + * @return data items size limit + */ + public long getMaxItemSize() { + return Math.max(1, maxMemory / segmentCount); + } + + private Entry find(long key) { + int hash = getHash(key); + return getSegment(hash).find(key, hash); + } + + /** + * Check whether there is a resident entry for the given key. This + * method does not adjust the internal state of the cache. + * + * @param key the key (may not be null) + * @return true if there is a resident entry + */ + public boolean containsKey(long key) { + Entry e = find(key); + return e != null && e.value != null; + } + + /** + * Get the value for the given key if the entry is cached. This method does + * not modify the internal state. + * + * @param key the key (may not be null) + * @return the value, or null if there is no resident entry + */ + public V peek(long key) { + Entry e = find(key); + return e == null ? null : e.getValue(); + } + + /** + * Add an entry to the cache using the average memory size. + * + * @param key the key (may not be null) + * @param value the value (may not be null) + * @return the old value, or null if there was no resident entry + */ + public V put(long key, V value) { + return put(key, value, sizeOf(value)); + } + + /** + * Add an entry to the cache. The entry may or may not exist in the + * cache yet. This method will usually mark unknown entries as cold and + * known entries as hot. + * + * @param key the key (may not be null) + * @param value the value (may not be null) + * @param memory the memory used for the given entry + * @return the old value, or null if there was no resident entry + */ + public V put(long key, V value, int memory) { + if (value == null) { + throw DataUtils.newIllegalArgumentException( + "The value may not be null"); + } + int hash = getHash(key); + int segmentIndex = getSegmentIndex(hash); + Segment s = segments[segmentIndex]; + // check whether resize is required: synchronize on s, to avoid + // concurrent resizes (concurrent reads read + // from the old segment) + synchronized (s) { + s = resizeIfNeeded(s, segmentIndex); + return s.put(key, hash, value, memory); + } + } + + private Segment resizeIfNeeded(Segment s, int segmentIndex) { + int newLen = s.getNewMapLen(); + if (newLen == 0) { + return s; + } + // another thread might have resized + // (as we retrieved the segment before synchronizing on it) + Segment s2 = segments[segmentIndex]; + if (s == s2) { + // no other thread resized, so we do + s = new Segment<>(s, newLen); + segments[segmentIndex] = s; + } + return s; + } + + /** + * Get the size of the given value. The default implementation returns 1. + * + * @param value the value + * @return the size + */ + @SuppressWarnings("unused") + protected int sizeOf(V value) { + return 1; + } + + /** + * Remove an entry. Both resident and non-resident entries can be + * removed. + * + * @param key the key (may not be null) + * @return the old value, or null if there was no resident entry + */ + public V remove(long key) { + int hash = getHash(key); + int segmentIndex = getSegmentIndex(hash); + Segment s = segments[segmentIndex]; + // check whether resize is required: synchronize on s, to avoid + // concurrent resizes (concurrent reads read + // from the old segment) + synchronized (s) { + s = resizeIfNeeded(s, segmentIndex); + return s.remove(key, hash); + } + } + + /** + * Get the memory used for the given key. + * + * @param key the key (may not be null) + * @return the memory, or 0 if there is no resident entry + */ + public int getMemory(long key) { + Entry e = find(key); + return e == null ? 0 : e.getMemory(); + } + + /** + * Get the value for the given key if the entry is cached. This method + * adjusts the internal state of the cache sometimes, to ensure commonly + * used entries stay in the cache. + * + * @param key the key (may not be null) + * @return the value, or null if there is no resident entry + */ + public V get(long key) { + int hash = getHash(key); + Segment s = getSegment(hash); + Entry e = s.find(key, hash); + return s.get(e); + } + + private Segment getSegment(int hash) { + return segments[getSegmentIndex(hash)]; + } + + private int getSegmentIndex(int hash) { + return (hash >>> segmentShift) & segmentMask; + } + + /** + * Get the hash code for the given key. The hash code is + * further enhanced to spread the values more evenly. + * + * @param key the key + * @return the hash code + */ + static int getHash(long key) { + int hash = (int) ((key >>> 32) ^ key); + // a supplemental secondary hash function + // to protect against hash codes that don't differ much + hash = ((hash >>> 16) ^ hash) * 0x45d9f3b; + hash = ((hash >>> 16) ^ hash) * 0x45d9f3b; + hash = (hash >>> 16) ^ hash; + return hash; + } + + /** + * Get the currently used memory. + * + * @return the used memory + */ + public long getUsedMemory() { + long x = 0; + for (Segment s : segments) { + x += s.usedMemory; + } + return x; + } + + /** + * Set the maximum memory this cache should use. This will not + * immediately cause entries to get removed however; it will only change + * the limit. To resize the internal array, call the clear method. + * + * @param maxMemory the maximum size (1 or larger) in bytes + */ + public void setMaxMemory(long maxMemory) { + DataUtils.checkArgument( + maxMemory > 0, + "Max memory must be larger than 0, is {0}", maxMemory); + this.maxMemory = maxMemory; + if (segments != null) { + long max = 1 + maxMemory / segments.length; + for (Segment s : segments) { + s.setMaxMemory(max); + } + } + } + + /** + * Get the maximum memory to use. + * + * @return the maximum memory + */ + public long getMaxMemory() { + return maxMemory; + } + + /** + * Get the entry set for all resident entries. + * + * @return the entry set + */ + public synchronized Set> entrySet() { + return getMap().entrySet(); + } + + /** + * Get the set of keys for resident entries. + * + * @return the set of keys + */ + public Set keySet() { + HashSet set = new HashSet<>(); + for (Segment s : segments) { + set.addAll(s.keySet()); + } + return set; + } + + /** + * Get the number of non-resident entries in the cache. + * + * @return the number of non-resident entries + */ + public int sizeNonResident() { + int x = 0; + for (Segment s : segments) { + x += s.queue2Size; + } + return x; + } + + /** + * Get the length of the internal map array. + * + * @return the size of the array + */ + public int sizeMapArray() { + int x = 0; + for (Segment s : segments) { + x += s.entries.length; + } + return x; + } + + /** + * Get the number of hot entries in the cache. + * + * @return the number of hot entries + */ + public int sizeHot() { + int x = 0; + for (Segment s : segments) { + x += s.mapSize - s.queueSize - s.queue2Size; + } + return x; + } + + /** + * Get the number of cache hits. + * + * @return the cache hits + */ + public long getHits() { + long x = 0; + for (Segment s : segments) { + x += s.hits; + } + return x; + } + + /** + * Get the number of cache misses. + * + * @return the cache misses + */ + public long getMisses() { + int x = 0; + for (Segment s : segments) { + x += s.misses; + } + return x; + } + + /** + * Get the number of resident entries. + * + * @return the number of entries + */ + public int size() { + int x = 0; + for (Segment s : segments) { + x += s.mapSize - s.queue2Size; + } + return x; + } + + /** + * Get the list of keys. This method allows to read the internal state of + * the cache. + * + * @param cold if true, only keys for the cold entries are returned + * @param nonResident true for non-resident entries + * @return the key list + */ + public List keys(boolean cold, boolean nonResident) { + ArrayList keys = new ArrayList<>(); + for (Segment s : segments) { + keys.addAll(s.keys(cold, nonResident)); + } + return keys; + } + + /** + * Get the values for all resident entries. + * + * @return the entry set + */ + public List values() { + ArrayList list = new ArrayList<>(); + for (long k : keySet()) { + V value = peek(k); + if (value != null) { + list.add(value); + } + } + return list; + } + + /** + * Check whether the cache is empty. + * + * @return true if it is empty + */ + public boolean isEmpty() { + return size() == 0; + } + + /** + * Check whether the given value is stored. + * + * @param value the value + * @return true if it is stored + */ + public boolean containsValue(V value) { + return getMap().containsValue(value); + } + + /** + * Convert this cache to a map. + * + * @return the map + */ + public Map getMap() { + HashMap map = new HashMap<>(); + for (long k : keySet()) { + V x = peek(k); + if (x != null) { + map.put(k, x); + } + } + return map; + } + + /** + * Add all elements of the map to this cache. + * + * @param m the map + */ + public void putAll(Map m) { + for (Map.Entry e : m.entrySet()) { + // copy only non-null entries + put(e.getKey(), e.getValue()); + } + } + + /** + * Loop through segments, trimming the non resident queue. + */ + public void trimNonResidentQueue() { + for (Segment s : segments) { + synchronized (s) { + s.trimNonResidentQueue(); + } + } + } + + /** + * A cache segment + * + * @param the value type + */ + private static class Segment { + + /** + * The number of (hot, cold, and non-resident) entries in the map. + */ + int mapSize; + + /** + * The size of the LIRS queue for resident cold entries. + */ + int queueSize; + + /** + * The size of the LIRS queue for non-resident cold entries. + */ + int queue2Size; + + /** + * The number of cache hits. + */ + long hits; + + /** + * The number of cache misses. + */ + long misses; + + /** + * The map array. The size is always a power of 2. + */ + final Entry[] entries; + + /** + * The currently used memory. + */ + long usedMemory; + + /** + * How many other item are to be moved to the top of the stack before + * the current item is moved. + */ + private final int stackMoveDistance; + + /** + * The maximum memory this cache should use in bytes. + */ + private long maxMemory; + + /** + * The bit mask that is applied to the key hash code to get the index in + * the map array. The mask is the length of the array minus one. + */ + private final int mask; + + /** + * Low watermark for the number of entries in the non-resident queue, + * as a factor of the number of entries in the map. + */ + private final int nonResidentQueueSize; + + /** + * High watermark for the number of entries in the non-resident queue, + * as a factor of the number of entries in the map. + */ + private final int nonResidentQueueSizeHigh; + + /** + * The stack of recently referenced elements. This includes all hot + * entries, and the recently referenced cold entries. Resident cold + * entries that were not recently referenced, as well as non-resident + * cold entries, are not in the stack. + *

+ * There is always at least one entry: the head entry. + */ + private final Entry stack; + + /** + * The number of entries in the stack. + */ + private int stackSize; + + /** + * The queue of resident cold entries. + *

+ * There is always at least one entry: the head entry. + */ + private final Entry queue; + + /** + * The queue of non-resident cold entries. + *

+ * There is always at least one entry: the head entry. + */ + private final Entry queue2; + + /** + * The number of times any item was moved to the top of the stack. + */ + private int stackMoveCounter; + + /** + * Create a new cache segment. + * @param maxMemory the maximum memory to use + * @param stackMoveDistance the number of other entries to be moved to + * the top of the stack before moving an entry to the top + * @param len the number of hash table buckets (must be a power of 2) + * @param nonResidentQueueSize the non-resident queue size low watermark factor + * @param nonResidentQueueSizeHigh the non-resident queue size high watermark factor + */ + Segment(long maxMemory, int stackMoveDistance, int len, + int nonResidentQueueSize, int nonResidentQueueSizeHigh) { + setMaxMemory(maxMemory); + this.stackMoveDistance = stackMoveDistance; + this.nonResidentQueueSize = nonResidentQueueSize; + this.nonResidentQueueSizeHigh = nonResidentQueueSizeHigh; + + // the bit mask has all bits set + mask = len - 1; + + // initialize the stack and queue heads + stack = new Entry<>(); + stack.stackPrev = stack.stackNext = stack; + queue = new Entry<>(); + queue.queuePrev = queue.queueNext = queue; + queue2 = new Entry<>(); + queue2.queuePrev = queue2.queueNext = queue2; + + @SuppressWarnings("unchecked") + Entry[] e = new Entry[len]; + entries = e; + } + + /** + * Create a new cache segment from an existing one. + * The caller must synchronize on the old segment, to avoid + * concurrent modifications. + * + * @param old the old segment + * @param len the number of hash table buckets (must be a power of 2) + */ + Segment(Segment old, int len) { + this(old.maxMemory, old.stackMoveDistance, len, + old.nonResidentQueueSize, old.nonResidentQueueSizeHigh); + hits = old.hits; + misses = old.misses; + Entry s = old.stack.stackPrev; + while (s != old.stack) { + Entry e = new Entry<>(s); + addToMap(e); + addToStack(e); + s = s.stackPrev; + } + s = old.queue.queuePrev; + while (s != old.queue) { + Entry e = find(s.key, getHash(s.key)); + if (e == null) { + e = new Entry<>(s); + addToMap(e); + } + addToQueue(queue, e); + s = s.queuePrev; + } + s = old.queue2.queuePrev; + while (s != old.queue2) { + Entry e = find(s.key, getHash(s.key)); + if (e == null) { + e = new Entry<>(s); + addToMap(e); + } + addToQueue(queue2, e); + s = s.queuePrev; + } + } + + /** + * Calculate the new number of hash table buckets if the internal map + * should be re-sized. + * + * @return 0 if no resizing is needed, or the new length + */ + int getNewMapLen() { + int len = mask + 1; + if (len * 3 < mapSize * 4 && len < (1 << 28)) { + // more than 75% usage + return len * 2; + } else if (len > 32 && len / 8 > mapSize) { + // less than 12% usage + return len / 2; + } + return 0; + } + + private void addToMap(Entry e) { + int index = getHash(e.key) & mask; + e.mapNext = entries[index]; + entries[index] = e; + usedMemory += e.getMemory(); + mapSize++; + } + + /** + * Get the value from the given entry. + * This method adjusts the internal state of the cache sometimes, + * to ensure commonly used entries stay in the cache. + * + * @param e the entry + * @return the value, or null if there is no resident entry + */ + synchronized V get(Entry e) { + V value = e == null ? null : e.getValue(); + if (value == null) { + // the entry was not found + // or it was a non-resident entry + misses++; + } else { + access(e); + hits++; + } + return value; + } + + /** + * Access an item, moving the entry to the top of the stack or front of + * the queue if found. + * + * @param e entry to record access for + */ + private void access(Entry e) { + if (e.isHot()) { + if (e != stack.stackNext && e.stackNext != null) { + if (stackMoveCounter - e.topMove > stackMoveDistance) { + // move a hot entry to the top of the stack + // unless it is already there + boolean wasEnd = e == stack.stackPrev; + removeFromStack(e); + if (wasEnd) { + // if moving the last entry, the last entry + // could now be cold, which is not allowed + pruneStack(); + } + addToStack(e); + } + } + } else { + V v = e.getValue(); + if (v != null) { + removeFromQueue(e); + if (e.reference != null) { + e.value = v; + e.reference = null; + usedMemory += e.memory; + } + if (e.stackNext != null) { + // resident, or even non-resident (weak value reference), + // cold entries become hot if they are on the stack + removeFromStack(e); + // which means a hot entry needs to become cold + // (this entry is cold, that means there is at least one + // more entry in the stack, which must be hot) + convertOldestHotToCold(); + } else { + // cold entries that are not on the stack + // move to the front of the queue + addToQueue(queue, e); + } + // in any case, the cold entry is moved to the top of the stack + addToStack(e); + // but if newly promoted cold/non-resident is the only entry on a stack now + // that means last one is cold, need to prune + pruneStack(); + } + } + } + + /** + * Add an entry to the cache. The entry may or may not exist in the + * cache yet. This method will usually mark unknown entries as cold and + * known entries as hot. + * + * @param key the key (may not be null) + * @param hash the hash + * @param value the value (may not be null) + * @param memory the memory used for the given entry + * @return the old value, or null if there was no resident entry + */ + synchronized V put(long key, int hash, V value, int memory) { + Entry e = find(key, hash); + boolean existed = e != null; + V old = null; + if (existed) { + old = e.getValue(); + remove(key, hash); + } + if (memory > maxMemory) { + // the new entry is too big to fit + return old; + } + e = new Entry<>(key, value, memory); + int index = hash & mask; + e.mapNext = entries[index]; + entries[index] = e; + usedMemory += memory; + if (usedMemory > maxMemory) { + // old entries needs to be removed + evict(); + // if the cache is full, the new entry is + // cold if possible + if (stackSize > 0) { + // the new cold entry is at the top of the queue + addToQueue(queue, e); + } + } + mapSize++; + // added entries are always added to the stack + addToStack(e); + if (existed) { + // if it was there before (even non-resident), it becomes hot + access(e); + } + return old; + } + + /** + * Remove an entry. Both resident and non-resident entries can be + * removed. + * + * @param key the key (may not be null) + * @param hash the hash + * @return the old value, or null if there was no resident entry + */ + synchronized V remove(long key, int hash) { + int index = hash & mask; + Entry e = entries[index]; + if (e == null) { + return null; + } + if (e.key == key) { + entries[index] = e.mapNext; + } else { + Entry last; + do { + last = e; + e = e.mapNext; + if (e == null) { + return null; + } + } while (e.key != key); + last.mapNext = e.mapNext; + } + V old = e.getValue(); + mapSize--; + usedMemory -= e.getMemory(); + if (e.stackNext != null) { + removeFromStack(e); + } + if (e.isHot()) { + // when removing a hot entry, the newest cold entry gets hot, + // so the number of hot entries does not change + e = queue.queueNext; + if (e != queue) { + removeFromQueue(e); + if (e.stackNext == null) { + addToStackBottom(e); + } + } + pruneStack(); + } else { + removeFromQueue(e); + } + return old; + } + + /** + * Evict cold entries (resident and non-resident) until the memory limit + * is reached. The new entry is added as a cold entry, except if it is + * the only entry. + */ + private void evict() { + do { + evictBlock(); + } while (usedMemory > maxMemory); + } + + private void evictBlock() { + // ensure there are not too many hot entries: right shift of 5 is + // division by 32, that means if there are only 1/32 (3.125%) or + // less cold entries, a hot entry needs to become cold + while (queueSize <= ((mapSize - queue2Size) >>> 5) && stackSize > 0) { + convertOldestHotToCold(); + } + // the oldest resident cold entries become non-resident + while (usedMemory > maxMemory && queueSize > 0) { + Entry e = queue.queuePrev; + usedMemory -= e.memory; + removeFromQueue(e); + e.reference = new WeakReference<>(e.value); + e.value = null; + addToQueue(queue2, e); + // the size of the non-resident-cold entries needs to be limited + trimNonResidentQueue(); + } + } + + void trimNonResidentQueue() { + int residentCount = mapSize - queue2Size; + int maxQueue2SizeHigh = nonResidentQueueSizeHigh * residentCount; + int maxQueue2Size = nonResidentQueueSize * residentCount; + while (queue2Size > maxQueue2Size) { + Entry e = queue2.queuePrev; + if (queue2Size <= maxQueue2SizeHigh) { + WeakReference reference = e.reference; + if (reference != null && reference.get() != null) { + break; // stop trimming if entry holds a value + } + } + int hash = getHash(e.key); + remove(e.key, hash); + } + } + + private void convertOldestHotToCold() { + // the last entry of the stack is known to be hot + Entry last = stack.stackPrev; + if (last == stack) { + // never remove the stack head itself (this would mean the + // internal structure of the cache is corrupt) + throw new IllegalStateException(); + } + // remove from stack - which is done anyway in the stack pruning, + // but we can do it here as well + removeFromStack(last); + // adding an entry to the queue will make it cold + addToQueue(queue, last); + pruneStack(); + } + + /** + * Ensure the last entry of the stack is cold. + */ + private void pruneStack() { + while (true) { + Entry last = stack.stackPrev; + // must stop at a hot entry or the stack head, + // but the stack head itself is also hot, so we + // don't have to test it + if (last.isHot()) { + break; + } + // the cold entry is still in the queue + removeFromStack(last); + } + } + + /** + * Try to find an entry in the map. + * + * @param key the key + * @param hash the hash + * @return the entry (might be a non-resident) + */ + Entry find(long key, int hash) { + int index = hash & mask; + Entry e = entries[index]; + while (e != null && e.key != key) { + e = e.mapNext; + } + return e; + } + + private void addToStack(Entry e) { + e.stackPrev = stack; + e.stackNext = stack.stackNext; + e.stackNext.stackPrev = e; + stack.stackNext = e; + stackSize++; + e.topMove = stackMoveCounter++; + } + + private void addToStackBottom(Entry e) { + e.stackNext = stack; + e.stackPrev = stack.stackPrev; + e.stackPrev.stackNext = e; + stack.stackPrev = e; + stackSize++; + } + + /** + * Remove the entry from the stack. The head itself must not be removed. + * + * @param e the entry + */ + private void removeFromStack(Entry e) { + e.stackPrev.stackNext = e.stackNext; + e.stackNext.stackPrev = e.stackPrev; + e.stackPrev = e.stackNext = null; + stackSize--; + } + + private void addToQueue(Entry q, Entry e) { + e.queuePrev = q; + e.queueNext = q.queueNext; + e.queueNext.queuePrev = e; + q.queueNext = e; + if (e.value != null) { + queueSize++; + } else { + queue2Size++; + } + } + + private void removeFromQueue(Entry e) { + e.queuePrev.queueNext = e.queueNext; + e.queueNext.queuePrev = e.queuePrev; + e.queuePrev = e.queueNext = null; + if (e.value != null) { + queueSize--; + } else { + queue2Size--; + } + } + + /** + * Get the list of keys. This method allows to read the internal state + * of the cache. + * + * @param cold if true, only keys for the cold entries are returned + * @param nonResident true for non-resident entries + * @return the key list + */ + synchronized List keys(boolean cold, boolean nonResident) { + ArrayList keys = new ArrayList<>(); + if (cold) { + Entry start = nonResident ? queue2 : queue; + for (Entry e = start.queueNext; e != start; + e = e.queueNext) { + keys.add(e.key); + } + } else { + for (Entry e = stack.stackNext; e != stack; + e = e.stackNext) { + keys.add(e.key); + } + } + return keys; + } + + /** + * Get the set of keys for resident entries. + * + * @return the set of keys + */ + synchronized Set keySet() { + HashSet set = new HashSet<>(); + for (Entry e = stack.stackNext; e != stack; e = e.stackNext) { + set.add(e.key); + } + for (Entry e = queue.queueNext; e != queue; e = e.queueNext) { + set.add(e.key); + } + return set; + } + + /** + * Set the maximum memory this cache should use. This will not + * immediately cause entries to get removed however; it will only change + * the limit. To resize the internal array, call the clear method. + * + * @param maxMemory the maximum size (1 or larger) in bytes + */ + void setMaxMemory(long maxMemory) { + this.maxMemory = maxMemory; + } + + } + + /** + * A cache entry. Each entry is either hot (low inter-reference recency; + * LIR), cold (high inter-reference recency; HIR), or non-resident-cold. Hot + * entries are in the stack only. Cold entries are in the queue, and may be + * in the stack. Non-resident-cold entries have their value set to null and + * are in the stack and in the non-resident queue. + * + * @param the value type + */ + static class Entry { + + /** + * The key. + */ + final long key; + + /** + * The value. Set to null for non-resident-cold entries. + */ + V value; + + /** + * Weak reference to the value. Set to null for resident entries. + */ + WeakReference reference; + + /** + * The estimated memory used. + */ + final int memory; + + /** + * When the item was last moved to the top of the stack. + */ + int topMove; + + /** + * The next entry in the stack. + */ + Entry stackNext; + + /** + * The previous entry in the stack. + */ + Entry stackPrev; + + /** + * The next entry in the queue (either the resident queue or the + * non-resident queue). + */ + Entry queueNext; + + /** + * The previous entry in the queue. + */ + Entry queuePrev; + + /** + * The next entry in the map (the chained entry). + */ + Entry mapNext; + + + Entry() { + this(0L, null, 0); + } + + Entry(long key, V value, int memory) { + this.key = key; + this.memory = memory; + this.value = value; + } + + Entry(Entry old) { + this(old.key, old.value, old.memory); + this.reference = old.reference; + this.topMove = old.topMove; + } + + /** + * Whether this entry is hot. Cold entries are in one of the two queues. + * + * @return whether the entry is hot + */ + boolean isHot() { + return queueNext == null; + } + + V getValue() { + return value == null ? reference.get() : value; + } + + int getMemory() { + return value == null ? 0 : memory; + } + } + + /** + * The cache configuration. + */ + public static class Config { + + /** + * The maximum memory to use (1 or larger). + */ + public long maxMemory = 1; + + /** + * The number of cache segments (must be a power of 2). + */ + public int segmentCount = 16; + + /** + * How many other item are to be moved to the top of the stack before + * the current item is moved. + */ + public int stackMoveDistance = 32; + + /** + * Low water mark for the number of entries in the non-resident queue, + * as a factor of the number of all other entries in the map. + */ + public final int nonResidentQueueSize = 3; + + /** + * High watermark for the number of entries in the non-resident queue, + * as a factor of the number of all other entries in the map + */ + public final int nonResidentQueueSizeHigh = 12; + } +} diff --git a/h2/src/main/org/h2/mvstore/cache/FilePathCache.java b/h2/src/main/org/h2/mvstore/cache/FilePathCache.java new file mode 100644 index 0000000..fc04065 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/cache/FilePathCache.java @@ -0,0 +1,181 @@ +/* + * 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.mvstore.cache; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import org.h2.store.fs.FileBase; +import org.h2.store.fs.FilePath; +import org.h2.store.fs.FilePathWrapper; + +/** + * A file with a read cache. + */ +public class FilePathCache extends FilePathWrapper { + + /** + * The instance. + */ + public static final FilePathCache INSTANCE = new FilePathCache(); + + /** + * Register the file system. + */ + static { + FilePath.register(INSTANCE); + } + + public static FileChannel wrap(FileChannel f) { + return new FileCache(f); + } + + @Override + public FileChannel open(String mode) throws IOException { + return new FileCache(getBase().open(mode)); + } + + @Override + public String getScheme() { + return "cache"; + } + + /** + * A file with a read cache. + */ + public static class FileCache extends FileBase { + + private static final int CACHE_BLOCK_SIZE = 4 * 1024; + private final FileChannel base; + + private final CacheLongKeyLIRS cache; + + { + CacheLongKeyLIRS.Config cc = new CacheLongKeyLIRS.Config(); + // 1 MB cache size + cc.maxMemory = 1024 * 1024; + cache = new CacheLongKeyLIRS<>(cc); + } + + FileCache(FileChannel base) { + this.base = base; + } + + @Override + protected void implCloseChannel() throws IOException { + base.close(); + } + + @Override + public FileChannel position(long newPosition) throws IOException { + base.position(newPosition); + return this; + } + + @Override + public long position() throws IOException { + return base.position(); + } + + @Override + public int read(ByteBuffer dst) throws IOException { + return base.read(dst); + } + + @Override + public synchronized int read(ByteBuffer dst, long position) throws IOException { + long cachePos = getCachePos(position); + int off = (int) (position - cachePos); + int len = CACHE_BLOCK_SIZE - off; + len = Math.min(len, dst.remaining()); + ByteBuffer buff = cache.get(cachePos); + if (buff == null) { + buff = ByteBuffer.allocate(CACHE_BLOCK_SIZE); + long pos = cachePos; + while (true) { + int read = base.read(buff, pos); + if (read <= 0) { + break; + } + if (buff.remaining() == 0) { + break; + } + pos += read; + } + int read = buff.position(); + if (read == CACHE_BLOCK_SIZE) { + cache.put(cachePos, buff, CACHE_BLOCK_SIZE); + } else { + if (read <= 0) { + return -1; + } + len = Math.min(len, read - off); + } + } + dst.put(buff.array(), off, len); + return len == 0 ? -1 : len; + } + + private static long getCachePos(long pos) { + return (pos / CACHE_BLOCK_SIZE) * CACHE_BLOCK_SIZE; + } + + @Override + public long size() throws IOException { + return base.size(); + } + + @Override + public synchronized FileChannel truncate(long newSize) throws IOException { + cache.clear(); + base.truncate(newSize); + return this; + } + + @Override + public synchronized int write(ByteBuffer src, long position) throws IOException { + clearCache(src, position); + return base.write(src, position); + } + + @Override + public synchronized int write(ByteBuffer src) throws IOException { + clearCache(src, position()); + return base.write(src); + } + + private void clearCache(ByteBuffer src, long position) { + if (cache.size() > 0) { + int len = src.remaining(); + long p = getCachePos(position); + while (len > 0) { + cache.remove(p); + p += CACHE_BLOCK_SIZE; + len -= CACHE_BLOCK_SIZE; + } + } + } + + @Override + public void force(boolean metaData) throws IOException { + base.force(metaData); + } + + @Override + public FileLock tryLock(long position, long size, boolean shared) + throws IOException { + return base.tryLock(position, size, shared); + } + + @Override + public String toString() { + return "cache:" + base.toString(); + } + + } + +} diff --git a/h2/src/main/org/h2/mvstore/cache/package.html b/h2/src/main/org/h2/mvstore/cache/package.html new file mode 100644 index 0000000..0821fb4 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/cache/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Classes related to caching. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/mvstore/db/LobStorageMap.java b/h2/src/main/org/h2/mvstore/db/LobStorageMap.java new file mode 100644 index 0000000..948ec39 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/db/LobStorageMap.java @@ -0,0 +1,602 @@ +/* + * 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.mvstore.db; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicLong; +import org.h2.api.ErrorCode; +import org.h2.engine.Database; +import org.h2.message.DbException; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVStore; +import org.h2.mvstore.StreamStore; +import org.h2.mvstore.WriteBuffer; +import org.h2.mvstore.tx.TransactionStore; +import org.h2.mvstore.type.BasicDataType; +import org.h2.mvstore.type.ByteArrayDataType; +import org.h2.mvstore.type.LongDataType; +import org.h2.store.CountingReaderInputStream; +import org.h2.store.LobStorageFrontend; +import org.h2.store.LobStorageInterface; +import org.h2.store.RangeInputStream; +import org.h2.util.IOUtils; +import org.h2.util.StringUtils; +import org.h2.value.Value; +import org.h2.value.ValueBlob; +import org.h2.value.ValueClob; +import org.h2.value.ValueLob; +import org.h2.value.ValueNull; +import org.h2.value.lob.LobData; +import org.h2.value.lob.LobDataDatabase; + +/** + * This class stores LOB objects in the database, in maps. This is the back-end + * i.e. the server side of the LOB storage. + */ +public final class LobStorageMap implements LobStorageInterface, MVStore.Cleaner +{ + private static final boolean TRACE = false; + + private final Database database; + final MVStore mvStore; + private final AtomicLong nextLobId = new AtomicLong(0); + + /** + * The lob metadata map. It contains the mapping from the lob id + * (which is a long) to the blob metadata, including stream store id (which is a byte array). + */ + private final MVMap lobMap; + + /** + * The lob metadata map for temporary lobs. It contains the mapping from the lob id + * (which is a long) to the stream store id (which is a byte array). + * + * Key: lobId (long) + * Value: streamStoreId (byte[]) + */ + private final MVMap tempLobMap; + + /** + * The reference map. It is used to remove data from the stream store: if no + * more entries for the given streamStoreId exist, the data is removed from + * the stream store. + */ + private final MVMap refMap; + + private final StreamStore streamStore; + + private final Queue pendingLobRemovals = new ConcurrentLinkedQueue<>(); + + /** + * Open map used to store LOB metadata + * @param txStore containing map + * @return MVMap instance + */ + public static MVMap openLobMap(TransactionStore txStore) { + return txStore.openMap("lobMap", LongDataType.INSTANCE, LobStorageMap.BlobMeta.Type.INSTANCE); + } + + /** + * Open map used to store LOB data + * @param txStore containing map + * @return MVMap instance + */ + public static MVMap openLobDataMap(TransactionStore txStore) { + return txStore.openMap("lobData", LongDataType.INSTANCE, ByteArrayDataType.INSTANCE); + } + + public LobStorageMap(Database database) { + this.database = database; + Store s = database.getStore(); + TransactionStore txStore = s.getTransactionStore(); + mvStore = s.getMvStore(); + mvStore.setCleaner(this); + MVStore.TxCounter txCounter = mvStore.registerVersionUsage(); + try { + lobMap = openLobMap(txStore); + tempLobMap = txStore.openMap("tempLobMap", LongDataType.INSTANCE, ByteArrayDataType.INSTANCE); + refMap = txStore.openMap("lobRef", BlobReference.Type.INSTANCE, NullValueDataType.INSTANCE); + /* The stream store data map. + * + * Key: stream store block id (long). + * Value: data (byte[]). + */ + MVMap dataMap = openLobDataMap(txStore); + streamStore = new StreamStore(dataMap); + // garbage collection of the last blocks + if (!database.isReadOnly()) { + // don't re-use block ids, except at the very end + Long last = dataMap.lastKey(); + if (last != null) { + streamStore.setNextKey(last + 1); + } + // find the latest lob ID + Long id1 = lobMap.lastKey(); + Long id2 = tempLobMap.lastKey(); // just in case we had unclean shutdown + long next = 1; + if (id1 != null) { + next = id1 + 1; + } + if (id2 != null) { + next = Math.max(next, id2 + 1); + } + nextLobId.set( next ); + } + } finally { + mvStore.deregisterVersionUsage(txCounter); + } + } + + @Override + public ValueBlob createBlob(InputStream in, long maxLength) { + MVStore.TxCounter txCounter = mvStore.registerVersionUsage(); + try { + if (maxLength != -1 + && maxLength <= database.getMaxLengthInplaceLob()) { + byte[] small = new byte[(int) maxLength]; + int len = IOUtils.readFully(in, small, (int) maxLength); + if (len > maxLength) { + throw new IllegalStateException( + "len > blobLength, " + len + " > " + maxLength); + } + if (len < small.length) { + small = Arrays.copyOf(small, len); + } + return ValueBlob.createSmall(small); + } + if (maxLength != -1) { + in = new RangeInputStream(in, 0L, maxLength); + } + return createBlob(in); + } catch (IllegalStateException e) { + throw DbException.get(ErrorCode.OBJECT_CLOSED, e); + } catch (IOException e) { + throw DbException.convertIOException(e, null); + } finally { + mvStore.deregisterVersionUsage(txCounter); + } + } + + @Override + public ValueClob createClob(Reader reader, long maxLength) { + MVStore.TxCounter txCounter = mvStore.registerVersionUsage(); + try { + // we multiple by 3 here to get the worst-case size in bytes + if (maxLength != -1 + && maxLength * 3 <= database.getMaxLengthInplaceLob()) { + char[] small = new char[(int) maxLength]; + int len = IOUtils.readFully(reader, small, (int) maxLength); + if (len > maxLength) { + throw new IllegalStateException( + "len > blobLength, " + len + " > " + maxLength); + } + byte[] utf8 = new String(small, 0, len) + .getBytes(StandardCharsets.UTF_8); + if (utf8.length > database.getMaxLengthInplaceLob()) { + throw new IllegalStateException( + "len > maxinplace, " + utf8.length + " > " + + database.getMaxLengthInplaceLob()); + } + return ValueClob.createSmall(utf8, len); + } + if (maxLength < 0) { + maxLength = Long.MAX_VALUE; + } + CountingReaderInputStream in = new CountingReaderInputStream(reader, maxLength); + ValueBlob blob = createBlob(in); + LobData lobData = blob.getLobData(); + return new ValueClob(lobData, blob.octetLength(), in.getLength()); + } catch (IllegalStateException e) { + throw DbException.get(ErrorCode.OBJECT_CLOSED, e); + } catch (IOException e) { + throw DbException.convertIOException(e, null); + } finally { + mvStore.deregisterVersionUsage(txCounter); + } + } + + private ValueBlob createBlob(InputStream in) throws IOException { + byte[] streamStoreId; + try { + streamStoreId = streamStore.put(in); + } catch (Exception e) { + throw DataUtils.convertToIOException(e); + } + long lobId = generateLobId(); + long length = streamStore.length(streamStoreId); + final int tableId = LobStorageFrontend.TABLE_TEMP; + tempLobMap.put(lobId, streamStoreId); + BlobReference key = new BlobReference(streamStoreId, lobId); + refMap.put(key, ValueNull.INSTANCE); + ValueBlob lob = new ValueBlob(new LobDataDatabase(database, tableId, lobId), length); + if (TRACE) { + trace("create " + tableId + "/" + lobId); + } + return lob; + } + + private long generateLobId() { + return nextLobId.getAndIncrement(); + } + + @Override + public boolean isReadOnly() { + return database.isReadOnly(); + } + + @Override + public ValueLob copyLob(ValueLob old, int tableId) { + MVStore.TxCounter txCounter = mvStore.registerVersionUsage(); + try { + final LobDataDatabase lobData = (LobDataDatabase) old.getLobData(); + final int type = old.getValueType(); + final long oldLobId = lobData.getLobId(); + long octetLength = old.octetLength(); + // get source lob + final byte[] streamStoreId; + if (isTemporaryLob(lobData.getTableId())) { + streamStoreId = tempLobMap.get(oldLobId); + } else { + BlobMeta value = lobMap.get(oldLobId); + streamStoreId = value.streamStoreId; + } + // create destination lob + final long newLobId = generateLobId(); + if (isTemporaryLob(tableId)) { + tempLobMap.put(newLobId, streamStoreId); + } else { + BlobMeta value = new BlobMeta(streamStoreId, tableId, + type == Value.CLOB ? old.charLength() : octetLength, 0); + lobMap.put(newLobId, value); + } + BlobReference refMapKey = new BlobReference(streamStoreId, newLobId); + refMap.put(refMapKey, ValueNull.INSTANCE); + LobDataDatabase newLobData = new LobDataDatabase(database, tableId, newLobId); + ValueLob lob = type == Value.BLOB ? new ValueBlob(newLobData, octetLength) + : new ValueClob(newLobData, octetLength, old.charLength()); + if (TRACE) { + trace("copy " + lobData.getTableId() + "/" + lobData.getLobId() + + " > " + tableId + "/" + newLobId); + } + return lob; + } finally { + mvStore.deregisterVersionUsage(txCounter); + } + } + + @Override + public InputStream getInputStream(long lobId, long byteCount) + throws IOException { + MVStore.TxCounter txCounter = mvStore.registerVersionUsage(); + try { + byte[] streamStoreId = tempLobMap.get(lobId); + if (streamStoreId == null) { + BlobMeta value = lobMap.get(lobId); + streamStoreId = value.streamStoreId; + } + if (streamStoreId == null) { + throw DbException.get(ErrorCode.LOB_CLOSED_ON_TIMEOUT_1, "" + lobId); + } + InputStream inputStream = streamStore.get(streamStoreId); + return new LobInputStream(inputStream); + } finally { + mvStore.deregisterVersionUsage(txCounter); + } + } + + @Override + public InputStream getInputStream(long lobId, int tableId, long byteCount) + throws IOException { + MVStore.TxCounter txCounter = mvStore.registerVersionUsage(); + try { + byte[] streamStoreId; + if (isTemporaryLob(tableId)) { + streamStoreId = tempLobMap.get(lobId); + } else { + BlobMeta value = lobMap.get(lobId); + streamStoreId = value.streamStoreId; + } + if (streamStoreId == null) { + throw DbException.get(ErrorCode.LOB_CLOSED_ON_TIMEOUT_1, "" + lobId); + } + InputStream inputStream = streamStore.get(streamStoreId); + return new LobInputStream(inputStream); + } finally { + mvStore.deregisterVersionUsage(txCounter); + } + } + + private final class LobInputStream extends FilterInputStream { + + public LobInputStream(InputStream in) { + super(in); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + MVStore.TxCounter txCounter = mvStore.registerVersionUsage(); + try { + return super.read(b, off, len); + } finally { + mvStore.deregisterVersionUsage(txCounter); + } + } + + @Override + public int read() throws IOException { + MVStore.TxCounter txCounter = mvStore.registerVersionUsage(); + try { + return super.read(); + } finally { + mvStore.deregisterVersionUsage(txCounter); + } + } + } + + @Override + public void removeAllForTable(int tableId) { + if (mvStore.isClosed()) { + return; + } + MVStore.TxCounter txCounter = mvStore.registerVersionUsage(); + try { + if (isTemporaryLob(tableId)) { + final Iterator iter = tempLobMap.keyIterator(0L); + while (iter.hasNext()) { + long lobId = iter.next(); + doRemoveLob(tableId, lobId); + } + tempLobMap.clear(); + } else { + final ArrayList list = new ArrayList<>(); + // This might not be very efficient, but should only happen + // on DROP TABLE. + // To speed it up, we would need yet another map. + for (Entry e : lobMap.entrySet()) { + BlobMeta value = e.getValue(); + if (value.tableId == tableId) { + list.add(e.getKey()); + } + } + for (long lobId : list) { + doRemoveLob(tableId, lobId); + } + } + } finally { + mvStore.deregisterVersionUsage(txCounter); + } + } + + @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)); + } + + @Override + public boolean needCleanup() { + return !pendingLobRemovals.isEmpty(); + } + + @Override + public void cleanup(long oldestVersionToKeep) { + MVStore.TxCounter txCounter = mvStore.registerVersionUsage(); + try { + LobRemovalInfo lobRemovalInfo; + while ((lobRemovalInfo = pendingLobRemovals.poll()) != null + && lobRemovalInfo.version < oldestVersionToKeep) { + doRemoveLob(lobRemovalInfo.mapId, lobRemovalInfo.lobId); + } + if (lobRemovalInfo != null) { + pendingLobRemovals.offer(lobRemovalInfo); + } + } finally { + // we can not call deregisterVersionUsage() due to a possible infinite recursion + mvStore.decrementVersionUsageCounter(txCounter); + } + } + + public void doRemoveLob(int tableId, long lobId) { + if (TRACE) { + trace("remove " + tableId + "/" + lobId); + } + byte[] streamStoreId; + if (isTemporaryLob(tableId)) { + streamStoreId = tempLobMap.remove(lobId); + if (streamStoreId == null) { + // already removed + return; + } + } else { + BlobMeta value = lobMap.remove(lobId); + if (value == null) { + // already removed + return; + } + streamStoreId = value.streamStoreId; + } + BlobReference key = new BlobReference(streamStoreId, lobId); + Value existing = refMap.remove(key); + assert existing != null; + // check if there are more entries for this streamStoreId + key = new BlobReference(streamStoreId, 0L); + BlobReference value = refMap.ceilingKey(key); + boolean hasMoreEntries = false; + if (value != null) { + byte[] s2 = value.streamStoreId; + if (Arrays.equals(streamStoreId, s2)) { + if (TRACE) { + trace(" stream still needed in lob " + value.lobId); + } + hasMoreEntries = true; + } + } + if (!hasMoreEntries) { + if (TRACE) { + trace(" remove stream " + StringUtils.convertBytesToHex(streamStoreId)); + } + streamStore.remove(streamStoreId); + } + } + + private static boolean isTemporaryLob(int tableId) { + return tableId == LobStorageFrontend.TABLE_ID_SESSION_VARIABLE || tableId == LobStorageFrontend.TABLE_TEMP + || tableId == LobStorageFrontend.TABLE_RESULT; + } + + private static void trace(String op) { + System.out.println("[" + Thread.currentThread().getName() + "] LOB " + op); + } + + + public static final class BlobReference implements Comparable + { + public final byte[] streamStoreId; + public final long lobId; + + public BlobReference(byte[] streamStoreId, long lobId) { + this.streamStoreId = streamStoreId; + this.lobId = lobId; + } + + @Override + public int compareTo(BlobReference other) { + int res = Integer.compare(streamStoreId.length, other.streamStoreId.length); + if (res == 0) { + for (int i = 0; res == 0 && i < streamStoreId.length; i++) { + res = Byte.compare(streamStoreId[i], other.streamStoreId[i]); + } + if (res == 0) { + res = Long.compare(lobId, other.lobId); + } + } + return res; + } + + public static final class Type extends BasicDataType { + public static final Type INSTANCE = new Type(); + + private Type() {} + + @Override + public int getMemory(BlobReference blobReference) { + return blobReference.streamStoreId.length + 8; + } + + @Override + public int compare(BlobReference one, BlobReference two) { + return one == two ? 0 : one == null ? 1 : two == null ? -1 : one.compareTo(two); + } + + @Override + public void write(WriteBuffer buff, BlobReference blobReference) { + buff.putVarInt(blobReference.streamStoreId.length); + buff.put(blobReference.streamStoreId); + buff.putVarLong(blobReference.lobId); + } + + @Override + public BlobReference read(ByteBuffer buff) { + int len = DataUtils.readVarInt(buff); + byte[] streamStoreId = new byte[len]; + buff.get(streamStoreId); + long blobId = DataUtils.readVarLong(buff); + return new BlobReference(streamStoreId, blobId); + } + + @Override + public BlobReference[] createStorage(int size) { + return new BlobReference[size]; + } + } + } + + public static final class BlobMeta + { + /** + * Stream identifier. It is used as a key in LOB data map. + */ + public final byte[] streamStoreId; + final int tableId; + final long byteCount; + final long hash; + + public BlobMeta(byte[] streamStoreId, int tableId, long byteCount, long hash) { + this.streamStoreId = streamStoreId; + this.tableId = tableId; + this.byteCount = byteCount; + this.hash = hash; + } + + public static final class Type extends BasicDataType { + public static final Type INSTANCE = new Type(); + + private Type() { + } + + @Override + public int getMemory(BlobMeta blobMeta) { + return blobMeta.streamStoreId.length + 20; + } + + @Override + public void write(WriteBuffer buff, BlobMeta blobMeta) { + buff.putVarInt(blobMeta.streamStoreId.length); + buff.put(blobMeta.streamStoreId); + buff.putVarInt(blobMeta.tableId); + buff.putVarLong(blobMeta.byteCount); + buff.putLong(blobMeta.hash); + } + + @Override + public BlobMeta read(ByteBuffer buff) { + int len = DataUtils.readVarInt(buff); + byte[] streamStoreId = new byte[len]; + buff.get(streamStoreId); + int tableId = DataUtils.readVarInt(buff); + long byteCount = DataUtils.readVarLong(buff); + long hash = buff.getLong(); + return new BlobMeta(streamStoreId, tableId, byteCount, hash); + } + + @Override + public BlobMeta[] createStorage(int size) { + return new BlobMeta[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/MVDelegateIndex.java b/h2/src/main/org/h2/mvstore/db/MVDelegateIndex.java new file mode 100644 index 0000000..0cceba0 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/db/MVDelegateIndex.java @@ -0,0 +1,152 @@ +/* + * 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.mvstore.db; + +import java.util.List; + +import org.h2.command.query.AllColumnsForPlan; +import org.h2.engine.SessionLocal; +import org.h2.index.Cursor; +import org.h2.index.IndexType; +import org.h2.message.DbException; +import org.h2.mvstore.MVMap; +import org.h2.result.Row; +import org.h2.result.RowFactory; +import org.h2.result.SearchRow; +import org.h2.result.SortOrder; +import org.h2.table.Column; +import org.h2.table.IndexColumn; +import org.h2.table.TableFilter; +import org.h2.value.VersionedValue; + +/** + * An index that delegates indexing to another index. + */ +public class MVDelegateIndex extends MVIndex { + + private final MVPrimaryIndex mainIndex; + + public MVDelegateIndex(MVTable table, int id, String name, MVPrimaryIndex mainIndex, IndexType indexType) { + super(table, id, name, IndexColumn.wrap(new Column[] { table.getColumn(mainIndex.getMainIndexColumn()) }), + 1, indexType); + this.mainIndex = mainIndex; + if (id < 0) { + throw DbException.getInternalError(name); + } + } + + @Override + public RowFactory getRowFactory() { + return mainIndex.getRowFactory(); + } + + @Override + public void addRowsToBuffer(List rows, String bufferName) { + throw DbException.getInternalError(); + } + + @Override + public void addBufferedRows(List bufferNames) { + throw DbException.getInternalError(); + } + + @Override + public MVMap> getMVMap() { + return mainIndex.getMVMap(); + } + + @Override + public void add(SessionLocal session, Row row) { + // nothing to do + } + + @Override + public Row getRow(SessionLocal session, long key) { + return mainIndex.getRow(session, key); + } + + @Override + public boolean isRowIdIndex() { + return true; + } + + @Override + public boolean canGetFirstOrLast() { + return true; + } + + @Override + public void close(SessionLocal session) { + // nothing to do + } + + @Override + public Cursor find(SessionLocal session, SearchRow first, SearchRow last) { + return mainIndex.find(session, first, last); + } + + @Override + public Cursor findFirstOrLast(SessionLocal session, boolean first) { + return mainIndex.findFirstOrLast(session, first); + } + + @Override + public int getColumnIndex(Column col) { + if (col.getColumnId() == mainIndex.getMainIndexColumn()) { + return 0; + } + return -1; + } + + @Override + public boolean isFirstColumn(Column column) { + return getColumnIndex(column) == 0; + } + + @Override + public double getCost(SessionLocal session, int[] masks, + TableFilter[] filters, int filter, SortOrder sortOrder, + AllColumnsForPlan allColumnsSet) { + return 10 * getCostRangeIndex(masks, mainIndex.getRowCountApproximation(session), + filters, filter, sortOrder, true, allColumnsSet); + } + + @Override + public boolean needRebuild() { + return false; + } + + @Override + public void remove(SessionLocal session, Row row) { + // nothing to do + } + + @Override + public void update(SessionLocal session, Row oldRow, Row newRow) { + // nothing to do + } + + @Override + public void remove(SessionLocal session) { + mainIndex.setMainIndexColumn(SearchRow.ROWID_INDEX); + } + + @Override + public void truncate(SessionLocal session) { + // nothing to do + } + + @Override + public long getRowCount(SessionLocal session) { + return mainIndex.getRowCount(session); + } + + @Override + public long getRowCountApproximation(SessionLocal session) { + return mainIndex.getRowCountApproximation(session); + } + +} diff --git a/h2/src/main/org/h2/mvstore/db/MVInDoubtTransaction.java b/h2/src/main/org/h2/mvstore/db/MVInDoubtTransaction.java new file mode 100644 index 0000000..e8e9c01 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/db/MVInDoubtTransaction.java @@ -0,0 +1,47 @@ +/* + * 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.mvstore.db; + +import org.h2.mvstore.MVStore; +import org.h2.mvstore.tx.Transaction; +import org.h2.store.InDoubtTransaction; + +/** + * An in-doubt transaction. + */ +final class MVInDoubtTransaction implements InDoubtTransaction { + + private final MVStore store; + private final Transaction transaction; + private int state = InDoubtTransaction.IN_DOUBT; + + MVInDoubtTransaction(MVStore store, Transaction transaction) { + this.store = store; + this.transaction = transaction; + } + + @Override + public void setState(int state) { + if (state == InDoubtTransaction.COMMIT) { + transaction.commit(); + } else { + transaction.rollback(); + } + store.commit(); + this.state = state; + } + + @Override + public int getState() { + return state; + } + + @Override + public String getTransactionName() { + return transaction.getName(); + } + +} diff --git a/h2/src/main/org/h2/mvstore/db/MVIndex.java b/h2/src/main/org/h2/mvstore/db/MVIndex.java new file mode 100644 index 0000000..a831e6d --- /dev/null +++ b/h2/src/main/org/h2/mvstore/db/MVIndex.java @@ -0,0 +1,47 @@ +/* + * 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.mvstore.db; + +import java.util.List; + +import org.h2.index.Index; +import org.h2.index.IndexType; +import org.h2.mvstore.MVMap; +import org.h2.result.Row; +import org.h2.table.IndexColumn; +import org.h2.table.Table; +import org.h2.value.VersionedValue; + +/** + * An index that stores the data in an MVStore. + */ +public abstract class MVIndex extends Index { + + protected MVIndex(Table newTable, int id, String name, IndexColumn[] newIndexColumns, int uniqueColumnCount, + IndexType newIndexType) { + super(newTable, id, name, newIndexColumns, uniqueColumnCount, newIndexType); + } + + /** + * Add the rows to a temporary storage (not to the index yet). The rows are + * sorted by the index columns. This is to more quickly build the index. + * + * @param rows the rows + * @param bufferName the name of the temporary storage + */ + public abstract void addRowsToBuffer(List rows, String bufferName); + + /** + * Add all the index data from the buffers to the index. The index will + * typically use merge sort to add the data more quickly in sorted order. + * + * @param bufferNames the names of the temporary storage + */ + public abstract void addBufferedRows(List bufferNames); + + public abstract MVMap> getMVMap(); + +} diff --git a/h2/src/main/org/h2/mvstore/db/MVPlainTempResult.java b/h2/src/main/org/h2/mvstore/db/MVPlainTempResult.java new file mode 100644 index 0000000..e00e19e --- /dev/null +++ b/h2/src/main/org/h2/mvstore/db/MVPlainTempResult.java @@ -0,0 +1,124 @@ +/* + * 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.mvstore.db; + +import org.h2.engine.Database; +import org.h2.expression.Expression; +import org.h2.message.DbException; +import org.h2.mvstore.Cursor; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVMap.Builder; +import org.h2.mvstore.type.LongDataType; +import org.h2.result.ResultExternal; +import org.h2.result.RowFactory.DefaultRowFactory; +import org.h2.value.Value; +import org.h2.value.ValueRow; + +/** + * Plain temporary result. + */ +class MVPlainTempResult extends MVTempResult { + + /** + * Map with identities of rows as keys rows as values. + */ + private final MVMap map; + + /** + * Counter for the identities of rows. A separate counter is used instead of + * {@link #rowCount} because rows due to presence of {@link #removeRow(Value[])} + * method to ensure that each row will have an own identity. + */ + private long counter; + + /** + * Cursor for the {@link #next()} method. + */ + private Cursor cursor; + + /** + * Creates a shallow copy of the result. + * + * @param parent + * parent result + */ + private MVPlainTempResult(MVPlainTempResult parent) { + super(parent); + this.map = parent.map; + } + + /** + * Creates a new plain temporary result. This result does not sort its rows, + * but it can be used in index-sorted queries and it can preserve additional + * columns for WITH TIES processing. + * + * @param database + * database + * @param expressions + * column expressions + * @param visibleColumnCount + * count of visible columns + * @param resultColumnCount + * the number of columns including visible columns and additional + * virtual columns for ORDER BY clause + */ + MVPlainTempResult(Database database, Expression[] expressions, int visibleColumnCount, int resultColumnCount) { + super(database, expressions, visibleColumnCount, resultColumnCount); + ValueDataType valueType = new ValueDataType(database, new int[resultColumnCount]); + valueType.setRowFactory(DefaultRowFactory.INSTANCE.createRowFactory(database, database.getCompareMode(), + database, expressions, null, false)); + Builder builder = new MVMap.Builder().keyType(LongDataType.INSTANCE) + .valueType(valueType).singleWriter(); + map = store.openMap("tmp", builder); + } + + @Override + public int addRow(Value[] values) { + assert parent == null; + map.append(counter++, ValueRow.get(values)); + return ++rowCount; + } + + @Override + public boolean contains(Value[] values) { + throw DbException.getUnsupportedException("contains()"); + } + + @Override + public synchronized ResultExternal createShallowCopy() { + if (parent != null) { + return parent.createShallowCopy(); + } + if (closed) { + return null; + } + childCount++; + return new MVPlainTempResult(this); + } + + @Override + public Value[] next() { + if (cursor == null) { + cursor = map.cursor(null); + } + if (!cursor.hasNext()) { + return null; + } + cursor.next(); + return cursor.getValue().getList(); + } + + @Override + public int removeRow(Value[] values) { + throw DbException.getUnsupportedException("removeRow()"); + } + + @Override + public void reset() { + cursor = null; + } + +} diff --git a/h2/src/main/org/h2/mvstore/db/MVPrimaryIndex.java b/h2/src/main/org/h2/mvstore/db/MVPrimaryIndex.java new file mode 100644 index 0000000..bf1a576 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/db/MVPrimaryIndex.java @@ -0,0 +1,447 @@ +/* + * 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.mvstore.db; + +import java.util.List; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicLong; +import org.h2.api.ErrorCode; +import org.h2.command.query.AllColumnsForPlan; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.index.Cursor; +import org.h2.index.IndexType; +import org.h2.index.SingleRowCursor; +import org.h2.message.DbException; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVStoreException; +import org.h2.mvstore.tx.Transaction; +import org.h2.mvstore.tx.TransactionMap; +import org.h2.mvstore.tx.TransactionMap.TMIterator; +import org.h2.mvstore.type.LongDataType; +import org.h2.result.Row; +import org.h2.result.SearchRow; +import org.h2.result.SortOrder; +import org.h2.table.Column; +import org.h2.table.IndexColumn; +import org.h2.table.TableFilter; +import org.h2.value.Value; +import org.h2.value.ValueLob; +import org.h2.value.ValueNull; +import org.h2.value.VersionedValue; + +/** + * A table stored in a MVStore. + */ +public class MVPrimaryIndex extends MVIndex { + + private final MVTable mvTable; + private final String mapName; + private final TransactionMap dataMap; + private final AtomicLong lastKey = new AtomicLong(); + private int mainIndexColumn = SearchRow.ROWID_INDEX; + + public MVPrimaryIndex(Database db, MVTable table, int id, IndexColumn[] columns, IndexType indexType) { + super(table, id, table.getName() + "_DATA", columns, 0, indexType); + this.mvTable = table; + RowDataType valueType = table.getRowFactory().getRowDataType(); + mapName = "table." + getId(); + Transaction t = mvTable.getTransactionBegin(); + dataMap = t.openMap(mapName, LongDataType.INSTANCE, valueType); + dataMap.map.setVolatile(!table.isPersistData() || !indexType.isPersistent()); + if (!db.isStarting()) { + dataMap.clear(); + } + t.commit(); + Long k = dataMap.map.lastKey(); // include uncommitted keys as well + lastKey.set(k == null ? 0 : k); + } + + @Override + public String getCreateSQL() { + return null; + } + + @Override + public String getPlanSQL() { + return table.getSQL(new StringBuilder(), TRACE_SQL_FLAGS).append(".tableScan").toString(); + } + + public void setMainIndexColumn(int mainIndexColumn) { + this.mainIndexColumn = mainIndexColumn; + } + + public int getMainIndexColumn() { + return mainIndexColumn; + } + + @Override + public void close(SessionLocal session) { + // ok + } + + @Override + public void add(SessionLocal session, Row row) { + if (mainIndexColumn == SearchRow.ROWID_INDEX) { + if (row.getKey() == 0) { + row.setKey(lastKey.incrementAndGet()); + } + } else { + long c = row.getValue(mainIndexColumn).getLong(); + row.setKey(c); + } + + if (mvTable.getContainsLargeObject()) { + for (int i = 0, len = row.getColumnCount(); i < len; i++) { + Value v = row.getValue(i); + if (v instanceof ValueLob) { + ValueLob lob = ((ValueLob) v).copy(database, getId()); + session.removeAtCommitStop(lob); + if (v != lob) { + row.setValue(i, lob); + } + } + } + } + + TransactionMap map = getMap(session); + long rowKey = row.getKey(); + try { + Row old = (Row)map.putIfAbsent(rowKey, row); + if (old != null) { + int errorCode = ErrorCode.CONCURRENT_UPDATE_1; + if (map.getImmediate(rowKey) != null || map.getFromSnapshot(rowKey) != null) { + // committed + errorCode = ErrorCode.DUPLICATE_KEY_1; + } + DbException e = DbException.get(errorCode, + getDuplicatePrimaryKeyMessage(mainIndexColumn).append(' ').append(old).toString()); + e.setSource(this); + throw e; + } + } catch (MVStoreException e) { + throw mvTable.convertException(e); + } + // because it's possible to directly update the key using the _rowid_ + // syntax + long last; + while (rowKey > (last = lastKey.get())) { + if(lastKey.compareAndSet(last, rowKey)) break; + } + } + + @Override + public void remove(SessionLocal session, Row row) { + if (mvTable.getContainsLargeObject()) { + for (int i = 0, len = row.getColumnCount(); i < len; i++) { + Value v = row.getValue(i); + if (v instanceof ValueLob) { + session.removeAtCommit((ValueLob) v); + } + } + } + TransactionMap map = getMap(session); + try { + Row existing = (Row)map.remove(row.getKey()); + if (existing == null) { + StringBuilder builder = new StringBuilder(); + getSQL(builder, TRACE_SQL_FLAGS).append(": ").append(row.getKey()); + throw DbException.get(ErrorCode.ROW_NOT_FOUND_WHEN_DELETING_1, builder.toString()); + } + } catch (MVStoreException e) { + throw mvTable.convertException(e); + } + } + + @Override + public void update(SessionLocal session, Row oldRow, Row newRow) { + if (mainIndexColumn != SearchRow.ROWID_INDEX) { + long c = newRow.getValue(mainIndexColumn).getLong(); + newRow.setKey(c); + } + long key = oldRow.getKey(); + assert mainIndexColumn != SearchRow.ROWID_INDEX || key != 0; + assert key == newRow.getKey() : key + " != " + newRow.getKey(); + if (mvTable.getContainsLargeObject()) { + for (int i = 0, len = oldRow.getColumnCount(); i < len; i++) { + Value oldValue = oldRow.getValue(i); + Value newValue = newRow.getValue(i); + if (oldValue != newValue) { + if (oldValue instanceof ValueLob) { + session.removeAtCommit((ValueLob) oldValue); + } + if (newValue instanceof ValueLob) { + ValueLob lob = ((ValueLob) newValue).copy(database, getId()); + session.removeAtCommitStop(lob); + if (newValue != lob) { + newRow.setValue(i, lob); + } + } + } + } + } + + TransactionMap map = getMap(session); + try { + Row existing = (Row)map.put(key, newRow); + if (existing == null) { + StringBuilder builder = new StringBuilder(); + getSQL(builder, TRACE_SQL_FLAGS).append(": ").append(key); + throw DbException.get(ErrorCode.ROW_NOT_FOUND_WHEN_DELETING_1, builder.toString()); + } + } catch (MVStoreException e) { + throw mvTable.convertException(e); + } + + + // because it's possible to directly update the key using the _rowid_ + // syntax + if (newRow.getKey() > lastKey.get()) { + lastKey.set(newRow.getKey()); + } + } + + /** + * Lock a single row. + * + * @param session database session + * @param row to lock + * @return row object if it exists + */ + Row lockRow(SessionLocal session, Row row) { + TransactionMap map = getMap(session); + long key = row.getKey(); + return lockRow(map, key); + } + + private Row lockRow(TransactionMap map, long key) { + try { + return setRowKey((Row) map.lock(key), key); + } catch (MVStoreException ex) { + throw mvTable.convertException(ex); + } + } + + @Override + public Cursor find(SessionLocal session, SearchRow first, SearchRow last) { + long min = extractPKFromRow(first, Long.MIN_VALUE); + long max = extractPKFromRow(last, Long.MAX_VALUE); + return find(session, min, max); + } + + private long extractPKFromRow(SearchRow row, long defaultValue) { + long result; + if (row == null) { + result = defaultValue; + } else if (mainIndexColumn == SearchRow.ROWID_INDEX) { + result = row.getKey(); + } else { + Value v = row.getValue(mainIndexColumn); + if (v == null) { + result = row.getKey(); + } else if (v == ValueNull.INSTANCE) { + result = 0L; + } else { + result = v.getLong(); + } + } + return result; + } + + + @Override + public MVTable getTable() { + return mvTable; + } + + @Override + public Row getRow(SessionLocal session, long key) { + TransactionMap map = getMap(session); + Row row = (Row) map.getFromSnapshot(key); + if (row == null) { + throw DbException.get(ErrorCode.ROW_NOT_FOUND_IN_PRIMARY_INDEX, getTraceSQL(), String.valueOf(key)); + } + return setRowKey(row, key); + } + + @Override + public double getCost(SessionLocal session, int[] masks, + TableFilter[] filters, int filter, SortOrder sortOrder, + AllColumnsForPlan allColumnsSet) { + try { + return 10 * getCostRangeIndex(masks, dataMap.sizeAsLongMax(), + filters, filter, sortOrder, true, allColumnsSet); + } catch (MVStoreException e) { + throw DbException.get(ErrorCode.OBJECT_CLOSED, e); + } + } + + @Override + public int getColumnIndex(Column col) { + // can not use this index - use the delegate index instead + return SearchRow.ROWID_INDEX; + } + + @Override + public boolean isFirstColumn(Column column) { + return false; + } + + @Override + public void remove(SessionLocal session) { + TransactionMap map = getMap(session); + if (!map.isClosed()) { + Transaction t = session.getTransaction(); + t.removeMap(map); + } + } + + @Override + public void truncate(SessionLocal session) { + if (mvTable.getContainsLargeObject()) { + database.getLobStorage().removeAllForTable(table.getId()); + } + getMap(session).clear(); + } + + @Override + public boolean canGetFirstOrLast() { + return true; + } + + @Override + public Cursor findFirstOrLast(SessionLocal session, boolean first) { + TransactionMap map = getMap(session); + Entry entry = first ? map.firstEntry() : map.lastEntry(); + return new SingleRowCursor(entry != null ? setRowKey((Row) entry.getValue(), entry.getKey()) : null); + } + + @Override + public boolean needRebuild() { + return false; + } + + @Override + public long getRowCount(SessionLocal session) { + return getMap(session).sizeAsLong(); + } + + /** + * The maximum number of rows, including uncommitted rows of any session. + * + * @return the maximum number of rows + */ + public long getRowCountMax() { + return dataMap.sizeAsLongMax(); + } + + @Override + public long getRowCountApproximation(SessionLocal session) { + return getRowCountMax(); + } + + @Override + public long getDiskSpaceUsed() { + return dataMap.map.getRootPage().getDiskSpaceUsed(); + } + + public String getMapName() { + return mapName; + } + + @Override + public void addRowsToBuffer(List rows, String bufferName) { + throw new UnsupportedOperationException(); + } + + @Override + public void addBufferedRows(List bufferNames) { + throw new UnsupportedOperationException(); + } + + private Cursor find(SessionLocal session, Long first, Long last) { + TransactionMap map = getMap(session); + if (first != null && last != null && first.longValue() == last.longValue()) { + return new SingleRowCursor(setRowKey((Row) map.getFromSnapshot(first), first)); + } + return new MVStoreCursor(map.entryIterator(first, last)); + } + + @Override + public boolean isRowIdIndex() { + return true; + } + + /** + * Get the map to store the data. + * + * @param session the session + * @return the map + */ + TransactionMap getMap(SessionLocal session) { + if (session == null) { + return dataMap; + } + Transaction t = session.getTransaction(); + return dataMap.getInstance(t); + } + + @Override + public MVMap> getMVMap() { + return dataMap.map; + } + + private static Row setRowKey(Row row, long key) { + if (row != null && row.getKey() == 0) { + row.setKey(key); + } + return row; + } + + /** + * A cursor. + */ + static final class MVStoreCursor implements Cursor { + + private final TMIterator> it; + private Entry current; + private Row row; + + public MVStoreCursor(TMIterator> it) { + this.it = it; + } + + @Override + public Row get() { + if (row == null) { + if (current != null) { + row = (Row)current.getValue(); + if (row.getKey() == 0) { + row.setKey(current.getKey()); + } + } + } + return row; + } + + @Override + public SearchRow getSearchRow() { + return get(); + } + + @Override + public boolean next() { + current = it.fetchNext(); + row = null; + return current != null; + } + + @Override + public boolean previous() { + throw DbException.getUnsupportedException("previous"); + } + } +} diff --git a/h2/src/main/org/h2/mvstore/db/MVSecondaryIndex.java b/h2/src/main/org/h2/mvstore/db/MVSecondaryIndex.java new file mode 100644 index 0000000..0792c6a --- /dev/null +++ b/h2/src/main/org/h2/mvstore/db/MVSecondaryIndex.java @@ -0,0 +1,445 @@ +/* + * 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.mvstore.db; + +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.PriorityQueue; +import java.util.Queue; +import org.h2.api.ErrorCode; +import org.h2.command.query.AllColumnsForPlan; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.index.Cursor; +import org.h2.index.IndexType; +import org.h2.index.SingleRowCursor; +import org.h2.message.DbException; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVStore; +import org.h2.mvstore.MVStoreException; +import org.h2.mvstore.tx.Transaction; +import org.h2.mvstore.tx.TransactionMap; +import org.h2.mvstore.tx.TransactionMap.TMIterator; +import org.h2.mvstore.type.DataType; +import org.h2.result.Row; +import org.h2.result.RowFactory; +import org.h2.result.SearchRow; +import org.h2.result.SortOrder; +import org.h2.table.IndexColumn; +import org.h2.table.TableFilter; +import org.h2.value.Value; +import org.h2.value.ValueNull; +import org.h2.value.VersionedValue; + +/** + * An index stored in a MVStore. + */ +public final class MVSecondaryIndex extends MVIndex { + + /** + * The multi-value table. + */ + private final MVTable mvTable; + private final TransactionMap dataMap; + + public MVSecondaryIndex(Database db, MVTable table, int id, String indexName, + IndexColumn[] columns, int uniqueColumnCount, IndexType indexType) { + super(table, id, indexName, columns, uniqueColumnCount, indexType); + this.mvTable = table; + if (!database.isStarting()) { + checkIndexColumnTypes(columns); + } + String mapName = "index." + getId(); + RowDataType keyType = getRowFactory().getRowDataType(); + Transaction t = mvTable.getTransactionBegin(); + dataMap = t.openMap(mapName, keyType, NullValueDataType.INSTANCE); + dataMap.map.setVolatile(!table.isPersistData() || !indexType.isPersistent()); + if (!db.isStarting()) { + dataMap.clear(); + } + t.commit(); + if (!keyType.equals(dataMap.getKeyType())) { + throw DbException.getInternalError( + "Incompatible key type, expected " + keyType + " but got " + + dataMap.getKeyType() + " for index " + indexName); + } + } + + @Override + public void addRowsToBuffer(List rows, String bufferName) { + MVMap map = openMap(bufferName); + for (Row row : rows) { + SearchRow r = getRowFactory().createRow(); + r.copyFrom(row); + map.append(r, ValueNull.INSTANCE); + } + } + + private static final class Source { + + private final Iterator iterator; + + SearchRow currentRowData; + + public Source(Iterator iterator) { + assert iterator.hasNext(); + this.iterator = iterator; + this.currentRowData = iterator.next(); + } + + public boolean hasNext() { + boolean result = iterator.hasNext(); + if(result) { + currentRowData = iterator.next(); + } + return result; + } + + public SearchRow next() { + return currentRowData; + } + + static final class Comparator implements java.util.Comparator { + + private final DataType type; + + public Comparator(DataType type) { + this.type = type; + } + + @Override + public int compare(Source one, Source two) { + return type.compare(one.currentRowData, two.currentRowData); + } + } + } + + @Override + public void addBufferedRows(List bufferNames) { + int buffersCount = bufferNames.size(); + Queue queue = new PriorityQueue<>(buffersCount, + new Source.Comparator(getRowFactory().getRowDataType())); + for (String bufferName : bufferNames) { + Iterator iter = openMap(bufferName).keyIterator(null); + if (iter.hasNext()) { + queue.offer(new Source(iter)); + } + } + + try { + while (!queue.isEmpty()) { + Source s = queue.poll(); + SearchRow row = s.next(); + + if (uniqueColumnColumn > 0 && !mayHaveNullDuplicates(row)) { + checkUnique(false, dataMap, row, Long.MIN_VALUE); + } + + dataMap.putCommitted(row, ValueNull.INSTANCE); + + if (s.hasNext()) { + queue.offer(s); + } + } + } finally { + MVStore mvStore = database.getStore().getMvStore(); + for (String tempMapName : bufferNames) { + mvStore.removeMap(tempMapName); + } + } + } + + private MVMap openMap(String mapName) { + RowDataType keyType = getRowFactory().getRowDataType(); + MVMap.Builder builder = new MVMap.Builder() + .singleWriter() + .keyType(keyType) + .valueType(NullValueDataType.INSTANCE); + MVMap map = database.getStore().getMvStore() + .openMap(mapName, builder); + if (!keyType.equals(map.getKeyType())) { + throw DbException.getInternalError( + "Incompatible key type, expected " + keyType + " but got " + + map.getKeyType() + " for map " + mapName); + } + return map; + } + + @Override + public void close(SessionLocal session) { + // ok + } + + @Override + public void add(SessionLocal session, Row row) { + TransactionMap map = getMap(session); + SearchRow key = convertToKey(row, null); + boolean checkRequired = uniqueColumnColumn > 0 && !mayHaveNullDuplicates(row); + if (checkRequired) { + boolean repeatableRead = !session.getTransaction().allowNonRepeatableRead(); + checkUnique(repeatableRead, map, row, Long.MIN_VALUE); + } + + try { + map.put(key, ValueNull.INSTANCE); + } catch (MVStoreException e) { + throw mvTable.convertException(e); + } + + if (checkRequired) { + checkUnique(false, map, row, row.getKey()); + } + } + + private void checkUnique(boolean repeatableRead, TransactionMap map, SearchRow row, + long newKey) { + RowFactory uniqueRowFactory = getUniqueRowFactory(); + SearchRow from = uniqueRowFactory.createRow(); + from.copyFrom(row); + from.setKey(Long.MIN_VALUE); + SearchRow to = uniqueRowFactory.createRow(); + to.copyFrom(row); + to.setKey(Long.MAX_VALUE); + if (repeatableRead) { + // In order to guarantee repeatable reads, snapshot taken at the beginning of the statement or transaction + // need to be checked additionally, because existence of the key should be accounted for, + // even if since then, it was already deleted by another (possibly committed) transaction. + TMIterator it = map.keyIterator(from, to); + for (SearchRow k; (k = it.fetchNext()) != null;) { + if (newKey != k.getKey() && !map.isDeletedByCurrentTransaction(k)) { + throw getDuplicateKeyException(k.toString()); + } + } + } + TMIterator it = map.keyIteratorUncommitted(from, to); + for (SearchRow k; (k = it.fetchNext()) != null;) { + if (newKey != k.getKey()) { + if (map.getImmediate(k) != null) { + // committed + throw getDuplicateKeyException(k.toString()); + } + throw DbException.get(ErrorCode.CONCURRENT_UPDATE_1, table.getName()); + } + } + } + + @Override + public void remove(SessionLocal session, Row row) { + SearchRow searchRow = convertToKey(row, null); + TransactionMap map = getMap(session); + try { + if (map.remove(searchRow) == null) { + StringBuilder builder = new StringBuilder(); + getSQL(builder, TRACE_SQL_FLAGS).append(": ").append(row.getKey()); + throw DbException.get(ErrorCode.ROW_NOT_FOUND_WHEN_DELETING_1, builder.toString()); + } + } catch (MVStoreException e) { + throw mvTable.convertException(e); + } + } + + @Override + public void update(SessionLocal session, Row oldRow, Row newRow) { + SearchRow searchRowOld = convertToKey(oldRow, null); + SearchRow searchRowNew = convertToKey(newRow, null); + if (!rowsAreEqual(searchRowOld, searchRowNew)) { + super.update(session, oldRow, newRow); + } + } + + private boolean rowsAreEqual(SearchRow rowOne, SearchRow rowTwo) { + if (rowOne == rowTwo) { + return true; + } + for (int index : columnIds) { + Value v1 = rowOne.getValue(index); + Value v2 = rowTwo.getValue(index); + if (!Objects.equals(v1, v2)) { + return false; + } + } + return rowOne.getKey() == rowTwo.getKey(); + } + + @Override + public Cursor find(SessionLocal session, SearchRow first, SearchRow last) { + return find(session, first, false, last); + } + + private Cursor find(SessionLocal session, SearchRow first, boolean bigger, SearchRow last) { + SearchRow min = convertToKey(first, bigger); + SearchRow max = convertToKey(last, Boolean.TRUE); + return new MVStoreCursor(session, getMap(session).keyIterator(min, max), mvTable); + } + + private SearchRow convertToKey(SearchRow r, Boolean minMax) { + if (r == null) { + return null; + } + + SearchRow row = getRowFactory().createRow(); + row.copyFrom(r); + if (minMax != null) { + row.setKey(minMax ? Long.MAX_VALUE : Long.MIN_VALUE); + } + return row; + } + + @Override + public MVTable getTable() { + return mvTable; + } + + @Override + public double getCost(SessionLocal session, int[] masks, + TableFilter[] filters, int filter, SortOrder sortOrder, + AllColumnsForPlan allColumnsSet) { + try { + return 10 * getCostRangeIndex(masks, dataMap.sizeAsLongMax(), + filters, filter, sortOrder, false, allColumnsSet); + } catch (MVStoreException e) { + throw DbException.get(ErrorCode.OBJECT_CLOSED, e); + } + } + + @Override + public void remove(SessionLocal session) { + TransactionMap map = getMap(session); + if (!map.isClosed()) { + Transaction t = session.getTransaction(); + t.removeMap(map); + } + } + + @Override + public void truncate(SessionLocal session) { + TransactionMap map = getMap(session); + map.clear(); + } + + @Override + public boolean canGetFirstOrLast() { + return true; + } + + @Override + public Cursor findFirstOrLast(SessionLocal session, boolean first) { + TMIterator iter = getMap(session).keyIterator(null, !first); + for (SearchRow key; (key = iter.fetchNext()) != null;) { + if (key.getValue(columnIds[0]) != ValueNull.INSTANCE) { + return new SingleRowCursor(mvTable.getRow(session, key.getKey())); + } + } + return new SingleRowCursor(null); + } + + @Override + public boolean needRebuild() { + try { + return dataMap.sizeAsLongMax() == 0; + } catch (MVStoreException e) { + throw DbException.get(ErrorCode.OBJECT_CLOSED, e); + } + } + + @Override + public long getRowCount(SessionLocal session) { + TransactionMap map = getMap(session); + return map.sizeAsLong(); + } + + @Override + public long getRowCountApproximation(SessionLocal session) { + try { + return dataMap.sizeAsLongMax(); + } catch (MVStoreException e) { + throw DbException.get(ErrorCode.OBJECT_CLOSED, e); + } + } + + @Override + public long getDiskSpaceUsed() { + // TODO estimate disk space usage + return 0; + } + + @Override + public boolean canFindNext() { + return true; + } + + @Override + public Cursor findNext(SessionLocal session, SearchRow higherThan, SearchRow last) { + return find(session, higherThan, true, last); + } + + /** + * Get the map to store the data. + * + * @param session the session + * @return the map + */ + private TransactionMap getMap(SessionLocal session) { + if (session == null) { + return dataMap; + } + Transaction t = session.getTransaction(); + return dataMap.getInstance(t); + } + + @Override + public MVMap> getMVMap() { + return dataMap.map; + } + + /** + * A cursor. + */ + static final class MVStoreCursor implements Cursor { + + private final SessionLocal session; + private final TMIterator it; + private final MVTable mvTable; + private SearchRow current; + private Row row; + + MVStoreCursor(SessionLocal session, TMIterator it, MVTable mvTable) { + this.session = session; + this.it = it; + this.mvTable = mvTable; + } + + @Override + public Row get() { + if (row == null) { + SearchRow r = getSearchRow(); + if (r != null) { + row = mvTable.getRow(session, r.getKey()); + } + } + return row; + } + + @Override + public SearchRow getSearchRow() { + return current; + } + + @Override + public boolean next() { + current = it.fetchNext(); + row = null; + return current != null; + } + + @Override + public boolean previous() { + throw DbException.getUnsupportedException("previous"); + } + } + +} diff --git a/h2/src/main/org/h2/mvstore/db/MVSortedTempResult.java b/h2/src/main/org/h2/mvstore/db/MVSortedTempResult.java new file mode 100644 index 0000000..17579c9 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/db/MVSortedTempResult.java @@ -0,0 +1,389 @@ +/* + * 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.mvstore.db; + +import java.util.Arrays; +import java.util.BitSet; + +import org.h2.engine.Database; +import org.h2.expression.Expression; +import org.h2.message.DbException; +import org.h2.mvstore.Cursor; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVMap.Builder; +import org.h2.mvstore.type.DataType; +import org.h2.mvstore.type.LongDataType; +import org.h2.result.ResultExternal; +import org.h2.result.RowFactory.DefaultRowFactory; +import org.h2.result.SortOrder; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueNull; +import org.h2.value.ValueRow; + +/** + * Sorted temporary result. + * + *

+ * This result is used for distinct and/or sorted results. + *

+ */ +class MVSortedTempResult extends MVTempResult { + + /** + * Whether this result is a standard distinct result. + */ + private final boolean distinct; + + /** + * Distinct indexes for DISTINCT ON results. + */ + private final int[] distinctIndexes; + + /** + * Mapping of indexes of columns to its positions in the store, or {@code null} + * if columns are not reordered. + */ + private final int[] indexes; + + /** + * Map with rows as keys and counts of duplicate rows as values. If this map is + * distinct all values are 1. + */ + private final MVMap map; + + /** + * Optional index. This index is created only if result is distinct and + * {@code columnCount != distinctColumnCount} or if + * {@link #contains(Value[])} method is invoked. Only the root result should + * have an index if required. + */ + private MVMap index; + + /** + * Used for DISTINCT ON in presence of ORDER BY. + */ + private ValueDataType orderedDistinctOnType; + + /** + * Cursor for the {@link #next()} method. + */ + private Cursor cursor; + + /** + * Current value for the {@link #next()} method. Used in non-distinct results + * with duplicate rows. + */ + private Value[] current; + + /** + * Count of remaining duplicate rows for the {@link #next()} method. Used in + * non-distinct results. + */ + private long valueCount; + + /** + * Creates a shallow copy of the result. + * + * @param parent + * parent result + */ + private MVSortedTempResult(MVSortedTempResult parent) { + super(parent); + this.distinct = parent.distinct; + this.distinctIndexes = parent.distinctIndexes; + this.indexes = parent.indexes; + this.map = parent.map; + this.rowCount = parent.rowCount; + } + + /** + * Creates a new sorted temporary result. + * + * @param database + * database + * @param expressions + * column expressions + * @param distinct + * whether this result should be distinct + * @param distinctIndexes + * indexes of distinct columns for DISTINCT ON results + * @param visibleColumnCount + * count of visible columns + * @param resultColumnCount + * the number of columns including visible columns and additional + * virtual columns for ORDER BY and DISTINCT ON clauses + * @param sort + * sort order, or {@code null} if this result does not need any + * sorting + */ + MVSortedTempResult(Database database, Expression[] expressions, boolean distinct, int[] distinctIndexes, + int visibleColumnCount, int resultColumnCount, SortOrder sort) { + super(database, expressions, visibleColumnCount, resultColumnCount); + this.distinct = distinct; + this.distinctIndexes = distinctIndexes; + int[] sortTypes = new int[resultColumnCount]; + int[] indexes; + if (sort != null) { + /* + * If sorting is specified we need to reorder columns in requested order and set + * sort types (ASC, DESC etc) for them properly. + */ + indexes = new int[resultColumnCount]; + int[] colIndex = sort.getQueryColumnIndexes(); + int len = colIndex.length; + // This set is used to remember columns that are already included + BitSet used = new BitSet(); + for (int i = 0; i < len; i++) { + int idx = colIndex[i]; + assert !used.get(idx); + used.set(idx); + indexes[i] = idx; + sortTypes[i] = sort.getSortTypes()[i]; + } + /* + * Because this result may have more columns than specified in sorting we need + * to add all remaining columns to the mapping of columns. A default sorting + * order (ASC / 0) will be used for them. + */ + int idx = 0; + for (int i = len; i < resultColumnCount; i++) { + idx = used.nextClearBit(idx); + indexes[i] = idx; + idx++; + } + /* + * Sometimes columns may be not reordered. Because reordering of columns + * slightly slows down other methods we check whether columns are really + * reordered or have the same order. + */ + sameOrder: { + for (int i = 0; i < resultColumnCount; i++) { + if (indexes[i] != i) { + // Columns are reordered + break sameOrder; + } + } + /* + * Columns are not reordered, set this field to null to disable reordering in + * other methods. + */ + indexes = null; + } + } else { + // Columns are not reordered if sort order is not specified + indexes = null; + } + this.indexes = indexes; + ValueDataType keyType = new ValueDataType(database, SortOrder.addNullOrdering(database, sortTypes)); + if (indexes != null) { + int l = indexes.length; + TypeInfo[] types = new TypeInfo[l]; + for (int i = 0; i < l; i++) { + types[i] = expressions[indexes[i]].getType(); + } + keyType.setRowFactory(DefaultRowFactory.INSTANCE.createRowFactory(database, database.getCompareMode(), + database, types, null, false)); + } else { + keyType.setRowFactory(DefaultRowFactory.INSTANCE.createRowFactory(database, database.getCompareMode(), + database, expressions, null, false)); + } + Builder builder = new MVMap.Builder().keyType(keyType) + .valueType(LongDataType.INSTANCE); + map = store.openMap("tmp", builder); + if (distinct && resultColumnCount != visibleColumnCount || distinctIndexes != null) { + int count; + TypeInfo[] types; + if (distinctIndexes != null) { + count = distinctIndexes.length; + types = new TypeInfo[count]; + for (int i = 0; i < count; i++) { + types[i] = expressions[distinctIndexes[i]].getType(); + } + } else { + count = visibleColumnCount; + types = new TypeInfo[count]; + for (int i = 0; i < count; i++) { + types[i] = expressions[i].getType(); + } + } + ValueDataType distinctType = new ValueDataType(database, new int[count]); + distinctType.setRowFactory(DefaultRowFactory.INSTANCE.createRowFactory(database, database.getCompareMode(), + database, types, null, false)); + DataType distinctValueType; + if (distinctIndexes != null && sort != null) { + distinctValueType = orderedDistinctOnType = keyType; + } else { + distinctValueType = NullValueDataType.INSTANCE; + } + Builder indexBuilder = new MVMap.Builder().keyType(distinctType) + .valueType(distinctValueType); + index = store.openMap("idx", indexBuilder); + } + } + + @Override + public int addRow(Value[] values) { + assert parent == null; + ValueRow key = getKey(values); + if (distinct || distinctIndexes != null) { + if (distinctIndexes != null) { + int cnt = distinctIndexes.length; + Value[] newValues = new Value[cnt]; + for (int i = 0; i < cnt; i++) { + newValues[i] = values[distinctIndexes[i]]; + } + ValueRow distinctRow = ValueRow.get(newValues); + if (orderedDistinctOnType == null) { + if (index.putIfAbsent(distinctRow, ValueNull.INSTANCE) != null) { + return rowCount; + } + } else { + ValueRow previous = (ValueRow) index.get(distinctRow); + if (previous == null) { + index.put(distinctRow, key); + } else if (orderedDistinctOnType.compare(previous, key) > 0) { + map.remove(previous); + rowCount--; + index.put(distinctRow, key); + } else { + return rowCount; + } + } + } else if (visibleColumnCount != resultColumnCount) { + ValueRow distinctRow = ValueRow.get(Arrays.copyOf(values, visibleColumnCount)); + if (index.putIfAbsent(distinctRow, ValueNull.INSTANCE) != null) { + return rowCount; + } + } + // Add a row and increment the counter only if row does not exist + if (map.putIfAbsent(key, 1L) == null) { + rowCount++; + } + } else { + // Try to set counter to 1 first if such row does not exist yet + Long old = map.putIfAbsent(key, 1L); + if (old != null) { + // This rows is already in the map, increment its own counter + map.put(key, old + 1); + } + rowCount++; + } + return rowCount; + } + + @Override + public boolean contains(Value[] values) { + // Only parent result maintains the index + if (parent != null) { + return parent.contains(values); + } + assert distinct; + if (visibleColumnCount != resultColumnCount) { + return index.containsKey(ValueRow.get(values)); + } + return map.containsKey(getKey(values)); + } + + @Override + public synchronized ResultExternal createShallowCopy() { + if (parent != null) { + return parent.createShallowCopy(); + } + if (closed) { + return null; + } + childCount++; + return new MVSortedTempResult(this); + } + + /** + * Reorder values if required and convert them into {@link ValueRow}. + * + * @param values + * values + * @return ValueRow for maps + */ + private ValueRow getKey(Value[] values) { + if (indexes != null) { + Value[] r = new Value[indexes.length]; + for (int i = 0; i < indexes.length; i++) { + r[i] = values[indexes[i]]; + } + values = r; + } + return ValueRow.get(values); + } + + /** + * Reorder values back if required. + * + * @param key + * reordered values + * @return original values + */ + private Value[] getValue(Value[] key) { + if (indexes != null) { + Value[] r = new Value[indexes.length]; + for (int i = 0; i < indexes.length; i++) { + r[indexes[i]] = key[i]; + } + key = r; + } + return key; + } + + @Override + public Value[] next() { + if (cursor == null) { + cursor = map.cursor(null); + current = null; + valueCount = 0L; + } + // If we have multiple rows with the same values return them all + if (--valueCount > 0) { + /* + * Underflow in valueCount is hypothetically possible after a lot of invocations + * (not really possible in practice), but current will be null anyway. + */ + return current; + } + if (!cursor.hasNext()) { + // Set current to null to be sure + current = null; + return null; + } + // Read the next row + current = getValue(cursor.next().getList()); + /* + * If valueCount is greater than 1 that is possible for non-distinct results the + * following invocations of next() will use this.current and this.valueCount. + */ + valueCount = cursor.getValue(); + return current; + } + + @Override + public int removeRow(Value[] values) { + assert parent == null && distinct; + if (visibleColumnCount != resultColumnCount) { + throw DbException.getUnsupportedException("removeRow()"); + } + // If an entry was removed decrement the counter + if (map.remove(getKey(values)) != null) { + rowCount--; + } + return rowCount; + } + + @Override + public void reset() { + cursor = null; + current = null; + valueCount = 0L; + } + +} diff --git a/h2/src/main/org/h2/mvstore/db/MVSpatialIndex.java b/h2/src/main/org/h2/mvstore/db/MVSpatialIndex.java new file mode 100644 index 0000000..5d07ec7 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/db/MVSpatialIndex.java @@ -0,0 +1,550 @@ +/* + * 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.mvstore.db; + +import org.h2.mvstore.rtree.Spatial; +import static org.h2.util.geometry.GeometryUtils.MAX_X; +import static org.h2.util.geometry.GeometryUtils.MAX_Y; +import static org.h2.util.geometry.GeometryUtils.MIN_X; +import static org.h2.util.geometry.GeometryUtils.MIN_Y; + +import java.util.Iterator; +import java.util.List; +import org.h2.api.ErrorCode; +import org.h2.command.query.AllColumnsForPlan; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.index.Cursor; +import org.h2.index.IndexCondition; +import org.h2.index.IndexType; +import org.h2.index.SpatialIndex; +import org.h2.message.DbException; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVStoreException; +import org.h2.mvstore.Page; +import org.h2.mvstore.rtree.MVRTreeMap; +import org.h2.mvstore.rtree.MVRTreeMap.RTreeCursor; +import org.h2.mvstore.tx.Transaction; +import org.h2.mvstore.tx.TransactionMap; +import org.h2.mvstore.tx.VersionedValueType; +import org.h2.result.Row; +import org.h2.result.SearchRow; +import org.h2.result.SortOrder; +import org.h2.table.Column; +import org.h2.table.IndexColumn; +import org.h2.table.TableFilter; +import org.h2.value.Value; +import org.h2.value.ValueGeometry; +import org.h2.value.ValueNull; +import org.h2.value.VersionedValue; + +/** + * This is an index based on a MVRTreeMap. + * + * @author Thomas Mueller + * @author Noel Grandin + * @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888 + */ +public class MVSpatialIndex extends MVIndex implements SpatialIndex { + + /** + * The multi-value table. + */ + final MVTable mvTable; + + private final TransactionMap dataMap; + private final MVRTreeMap> spatialMap; + + /** + * Constructor. + * + * @param db the database + * @param table the table instance + * @param id the index id + * @param indexName the index name + * @param columns the indexed columns (only one geometry column allowed) + * @param uniqueColumnCount count of unique columns (0 or 1) + * @param indexType the index type (only spatial index) + */ + public MVSpatialIndex(Database db, MVTable table, int id, String indexName, IndexColumn[] columns, + int uniqueColumnCount, IndexType indexType) { + super(table, id, indexName, columns, uniqueColumnCount, indexType); + if (columns.length != 1) { + throw DbException.getUnsupportedException( + "Can only index one column"); + } + IndexColumn col = columns[0]; + if ((col.sortType & SortOrder.DESCENDING) != 0) { + throw DbException.getUnsupportedException( + "Cannot index in descending order"); + } + if ((col.sortType & SortOrder.NULLS_FIRST) != 0) { + throw DbException.getUnsupportedException( + "Nulls first is not supported"); + } + if ((col.sortType & SortOrder.NULLS_LAST) != 0) { + throw DbException.getUnsupportedException( + "Nulls last is not supported"); + } + if (col.column.getType().getValueType() != Value.GEOMETRY) { + throw DbException.getUnsupportedException( + "Spatial index on non-geometry column, " + + col.column.getCreateSQL()); + } + this.mvTable = table; + if (!database.isStarting()) { + checkIndexColumnTypes(columns); + } + String mapName = "index." + getId(); + VersionedValueType valueType = new VersionedValueType<>(NullValueDataType.INSTANCE); + MVRTreeMap.Builder> mapBuilder = + new MVRTreeMap.Builder>(). + valueType(valueType); + spatialMap = db.getStore().getMvStore().openMap(mapName, mapBuilder); + Transaction t = mvTable.getTransactionBegin(); + dataMap = t.openMapX(spatialMap); + dataMap.map.setVolatile(!table.isPersistData() || !indexType.isPersistent()); + t.commit(); + } + + @Override + public void addRowsToBuffer(List rows, String bufferName) { + throw DbException.getInternalError(); + } + + @Override + public void addBufferedRows(List bufferNames) { + throw DbException.getInternalError(); + } + + @Override + public void close(SessionLocal session) { + // ok + } + + @Override + public void add(SessionLocal session, Row row) { + TransactionMap map = getMap(session); + SpatialKey key = getKey(row); + + if (key.isNull()) { + return; + } + + if (uniqueColumnColumn > 0) { + // this will detect committed entries only + RTreeCursor> cursor = spatialMap.findContainedKeys(key); + Iterator it = new SpatialKeyIterator(map, cursor, false); + while (it.hasNext()) { + Spatial k = it.next(); + if (k.equalsIgnoringId(key)) { + throw getDuplicateKeyException(key.toString()); + } + } + } + try { + map.put(key, ValueNull.INSTANCE); + } catch (MVStoreException e) { + throw mvTable.convertException(e); + } + if (uniqueColumnColumn > 0) { + // check if there is another (uncommitted) entry + RTreeCursor> cursor = spatialMap.findContainedKeys(key); + Iterator it = new SpatialKeyIterator(map, cursor, true); + while (it.hasNext()) { + Spatial k = it.next(); + if (k.equalsIgnoringId(key)) { + if (map.isSameTransaction(k)) { + continue; + } + map.remove(key); + if (map.getImmediate(k) != null) { + // committed + throw getDuplicateKeyException(k.toString()); + } + throw DbException.get(ErrorCode.CONCURRENT_UPDATE_1, table.getName()); + } + } + } + } + + @Override + public void remove(SessionLocal session, Row row) { + SpatialKey key = getKey(row); + + if (key.isNull()) { + return; + } + + TransactionMap map = getMap(session); + try { + Value old = map.remove(key); + if (old == null) { + StringBuilder builder = new StringBuilder(); + getSQL(builder, TRACE_SQL_FLAGS).append(": ").append(row.getKey()); + throw DbException.get(ErrorCode.ROW_NOT_FOUND_WHEN_DELETING_1, builder.toString()); + } + } catch (MVStoreException e) { + throw mvTable.convertException(e); + } + } + + @Override + public Cursor find(SessionLocal session, SearchRow first, SearchRow last) { + Iterator cursor = spatialMap.keyIterator(null); + TransactionMap map = getMap(session); + Iterator it = new SpatialKeyIterator(map, cursor, false); + return new MVStoreCursor(session, it, mvTable); + } + + @Override + public Cursor findByGeometry(SessionLocal session, SearchRow first, SearchRow last, SearchRow intersection) { + if (intersection == null) { + return find(session, first, last); + } + Iterator cursor = + spatialMap.findIntersectingKeys(getKey(intersection)); + TransactionMap map = getMap(session); + Iterator it = new SpatialKeyIterator(map, cursor, false); + return new MVStoreCursor(session, it, mvTable); + } + + /** + * Returns the minimum bounding box that encloses all keys. + * + * @param session the session + * @return the minimum bounding box that encloses all keys, or null + */ + public Value getBounds(SessionLocal session) { + FindBoundsCursor cursor = new FindBoundsCursor(spatialMap.getRootPage(), new SpatialKey(0), session, + getMap(session), columnIds[0]); + while (cursor.hasNext()) { + cursor.next(); + } + return cursor.getBounds(); + } + + /** + * Returns the estimated minimum bounding box that encloses all keys. + * + * The returned value may be incorrect. + * + * @param session the session + * @return the estimated minimum bounding box that encloses all keys, or null + */ + public Value getEstimatedBounds(SessionLocal session) { + Page> p = spatialMap.getRootPage(); + int count = p.getKeyCount(); + if (count > 0) { + Spatial key = p.getKey(0); + float bminxf = key.min(0), bmaxxf = key.max(0), bminyf = key.min(1), bmaxyf = key.max(1); + for (int i = 1; i < count; i++) { + key = p.getKey(i); + float minxf = key.min(0), maxxf = key.max(0), minyf = key.min(1), maxyf = key.max(1); + if (minxf < bminxf) { + bminxf = minxf; + } + if (maxxf > bmaxxf) { + bmaxxf = maxxf; + } + if (minyf < bminyf) { + bminyf = minyf; + } + if (maxyf > bmaxyf) { + bmaxyf = maxyf; + } + } + return ValueGeometry.fromEnvelope(new double[] {bminxf, bmaxxf, bminyf, bmaxyf}); + } + return ValueNull.INSTANCE; + } + + private SpatialKey getKey(SearchRow row) { + Value v = row.getValue(columnIds[0]); + double[] env; + if (v == ValueNull.INSTANCE || (env = v.convertToGeometry(null).getEnvelopeNoCopy()) == null) { + return new SpatialKey(row.getKey()); + } + return new SpatialKey(row.getKey(), + (float) env[MIN_X], (float) env[MAX_X], + (float) env[MIN_Y], (float) env[MAX_Y]); + } + + @Override + public MVTable getTable() { + return mvTable; + } + + @Override + public double getCost(SessionLocal session, int[] masks, TableFilter[] filters, + int filter, SortOrder sortOrder, + AllColumnsForPlan allColumnsSet) { + return getCostRangeIndex(masks, columns); + } + + /** + * Compute spatial index cost + * @param masks Search mask + * @param columns Table columns + * @return Index cost hint + */ + public static long getCostRangeIndex(int[] masks, Column[] columns) { + // Never use spatial tree index without spatial filter + if (columns.length == 0) { + return Long.MAX_VALUE; + } + for (Column column : columns) { + int index = column.getColumnId(); + int mask = masks[index]; + if ((mask & IndexCondition.SPATIAL_INTERSECTS) != IndexCondition.SPATIAL_INTERSECTS) { + return Long.MAX_VALUE; + } + } + return 2; + } + + @Override + public void remove(SessionLocal session) { + TransactionMap map = getMap(session); + if (!map.isClosed()) { + Transaction t = session.getTransaction(); + t.removeMap(map); + } + } + + @Override + public void truncate(SessionLocal session) { + TransactionMap map = getMap(session); + map.clear(); + } + + @Override + public boolean needRebuild() { + try { + return dataMap.sizeAsLongMax() == 0; + } catch (MVStoreException e) { + throw DbException.get(ErrorCode.OBJECT_CLOSED, e); + } + } + + @Override + public long getRowCount(SessionLocal session) { + TransactionMap map = getMap(session); + return map.sizeAsLong(); + } + + @Override + public long getRowCountApproximation(SessionLocal session) { + try { + return dataMap.sizeAsLongMax(); + } catch (MVStoreException e) { + throw DbException.get(ErrorCode.OBJECT_CLOSED, e); + } + } + + @Override + public long getDiskSpaceUsed() { + // TODO estimate disk space usage + return 0; + } + + /** + * Get the map to store the data. + * + * @param session the session + * @return the map + */ + private TransactionMap getMap(SessionLocal session) { + if (session == null) { + return dataMap; + } + Transaction t = session.getTransaction(); + return dataMap.getInstance(t); + } + + @Override + public MVMap> getMVMap() { + return dataMap.map; + } + + + /** + * A cursor. + */ + private static class MVStoreCursor implements Cursor { + + private final SessionLocal session; + private final Iterator it; + private final MVTable mvTable; + private Spatial current; + private SearchRow searchRow; + private Row row; + + MVStoreCursor(SessionLocal session, Iterator it, MVTable mvTable) { + this.session = session; + this.it = it; + this.mvTable = mvTable; + } + + @Override + public Row get() { + if (row == null) { + SearchRow r = getSearchRow(); + if (r != null) { + row = mvTable.getRow(session, r.getKey()); + } + } + return row; + } + + @Override + public SearchRow getSearchRow() { + if (searchRow == null) { + if (current != null) { + searchRow = mvTable.getTemplateRow(); + searchRow.setKey(current.getId()); + } + } + return searchRow; + } + + @Override + public boolean next() { + current = it.hasNext() ? it.next() : null; + searchRow = null; + row = null; + return current != null; + } + + @Override + public boolean previous() { + throw DbException.getUnsupportedException("previous"); + } + + } + + private static class SpatialKeyIterator implements Iterator { + + private final TransactionMap map; + private final Iterator iterator; + private final boolean includeUncommitted; + private Spatial current; + + SpatialKeyIterator(TransactionMap map, + Iterator iterator, boolean includeUncommitted) { + this.map = map; + this.iterator = iterator; + this.includeUncommitted = includeUncommitted; + fetchNext(); + } + + private void fetchNext() { + while (iterator.hasNext()) { + current = iterator.next(); + if (includeUncommitted || map.containsKey(current)) { + return; + } + } + current = null; + } + + @Override + public boolean hasNext() { + return current != null; + } + + @Override + public Spatial next() { + Spatial result = current; + fetchNext(); + return result; + } + } + + /** + * A cursor for getBounds() method. + */ + private final class FindBoundsCursor extends RTreeCursor> { + + private final SessionLocal session; + + private final TransactionMap map; + + private final int columnId; + + private boolean hasBounds; + + private float bminxf, bmaxxf, bminyf, bmaxyf; + + private double bminxd, bmaxxd, bminyd, bmaxyd; + + FindBoundsCursor(Page> root, Spatial filter, SessionLocal session, + TransactionMap map, int columnId) { + super(root, filter); + this.session = session; + this.map = map; + this.columnId = columnId; + } + + @Override + protected boolean check(boolean leaf, Spatial key, Spatial test) { + float minxf = key.min(0), maxxf = key.max(0), minyf = key.min(1), maxyf = key.max(1); + if (leaf) { + if (hasBounds) { + if ((minxf <= bminxf || maxxf >= bmaxxf || minyf <= bminyf || maxyf >= bmaxyf) + && map.containsKey(key)) { + double[] env = ((ValueGeometry) mvTable.getRow(session, key.getId()).getValue(columnId)) + .getEnvelopeNoCopy(); + double minxd = env[MIN_X], maxxd = env[MAX_X], minyd = env[MIN_Y], maxyd = env[MAX_Y]; + if (minxd < bminxd) { + bminxf = minxf; + bminxd = minxd; + } + if (maxxd > bmaxxd) { + bmaxxf = maxxf; + bmaxxd = maxxd; + } + if (minyd < bminyd) { + bminyf = minyf; + bminyd = minyd; + } + if (maxyd > bmaxyd) { + bmaxyf = maxyf; + bmaxyd = maxyd; + } + } + } else if (map.containsKey(key)) { + hasBounds = true; + double[] env = ((ValueGeometry) mvTable.getRow(session, key.getId()).getValue(columnId)) + .getEnvelopeNoCopy(); + bminxf = minxf; + bminxd = env[MIN_X]; + bmaxxf = maxxf; + bmaxxd = env[MAX_X]; + bminyf = minyf; + bminyd = env[MIN_Y]; + bmaxyf = maxyf; + bmaxyd = env[MAX_Y]; + } + } else if (hasBounds) { + if (minxf <= bminxf || maxxf >= bmaxxf || minyf <= bminyf || maxyf >= bmaxyf) { + return true; + } + } else { + return true; + } + return false; + } + + Value getBounds() { + return hasBounds ? ValueGeometry.fromEnvelope(new double[] {bminxd, bmaxxd, bminyd, bmaxyd}) + : ValueNull.INSTANCE; + } + + } + +} + diff --git a/h2/src/main/org/h2/mvstore/db/MVTable.java b/h2/src/main/org/h2/mvstore/db/MVTable.java new file mode 100644 index 0000000..65c6118 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/db/MVTable.java @@ -0,0 +1,946 @@ +/* + * 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.mvstore.db; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import org.h2.api.DatabaseEventListener; +import org.h2.api.ErrorCode; +import org.h2.command.ddl.CreateTableData; +import org.h2.constraint.Constraint; +import org.h2.constraint.ConstraintReferential; +import org.h2.engine.Constants; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.engine.SysProperties; +import org.h2.index.Cursor; +import org.h2.index.Index; +import org.h2.index.IndexType; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.mode.DefaultNullOrdering; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.MVStoreException; +import org.h2.mvstore.tx.Transaction; +import org.h2.mvstore.tx.TransactionStore; +import org.h2.result.Row; +import org.h2.result.SearchRow; +import org.h2.result.SortOrder; +import org.h2.table.Column; +import org.h2.table.IndexColumn; +import org.h2.table.Table; +import org.h2.table.TableBase; +import org.h2.table.TableType; +import org.h2.util.DebuggingThreadLocal; +import org.h2.util.Utils; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; + +/** + * A table stored in a MVStore. + */ +public class MVTable extends TableBase { + /** + * The table name this thread is waiting to lock. + */ + public static final DebuggingThreadLocal WAITING_FOR_LOCK; + + /** + * The table names this thread has exclusively locked. + */ + public static final DebuggingThreadLocal> EXCLUSIVE_LOCKS; + + /** + * The tables names this thread has a shared lock on. + */ + public static final DebuggingThreadLocal> SHARED_LOCKS; + + /** + * The type of trace lock events + */ + private enum TraceLockEvent{ + + TRACE_LOCK_OK("ok"), + TRACE_LOCK_WAITING_FOR("waiting for"), + TRACE_LOCK_REQUESTING_FOR("requesting for"), + TRACE_LOCK_TIMEOUT_AFTER("timeout after "), + TRACE_LOCK_UNLOCK("unlock"), + TRACE_LOCK_ADDED_FOR("added for"), + TRACE_LOCK_ADD_UPGRADED_FOR("add (upgraded) for "); + + private final String eventText; + + TraceLockEvent(String eventText) { + this.eventText = eventText; + } + + public String getEventText() { + return eventText; + } + } + private static final String NO_EXTRA_INFO = ""; + + static { + if (SysProperties.THREAD_DEADLOCK_DETECTOR) { + WAITING_FOR_LOCK = new DebuggingThreadLocal<>(); + EXCLUSIVE_LOCKS = new DebuggingThreadLocal<>(); + SHARED_LOCKS = new DebuggingThreadLocal<>(); + } else { + WAITING_FOR_LOCK = null; + EXCLUSIVE_LOCKS = null; + SHARED_LOCKS = null; + } + } + + /** + * Whether the table contains a CLOB or BLOB. + */ + private final boolean containsLargeObject; + + /** + * The session (if any) that has exclusively locked this table. + */ + private volatile SessionLocal lockExclusiveSession; + + /** + * The set of sessions (if any) that have a shared lock on the table. Here + * we are using using a ConcurrentHashMap as a set, as there is no + * ConcurrentHashSet. + */ + private final ConcurrentHashMap lockSharedSessions = new ConcurrentHashMap<>(); + + private Column rowIdColumn; + + private final MVPrimaryIndex primaryIndex; + private final ArrayList indexes = Utils.newSmallArrayList(); + private final AtomicLong lastModificationId = new AtomicLong(); + + /** + * The queue of sessions waiting to lock the table. It is a FIFO queue to + * prevent starvation, since Java's synchronized locking is biased. + */ + private final ArrayDeque waitingSessions = new ArrayDeque<>(); + private final Trace traceLock; + private final AtomicInteger changesUntilAnalyze; + private int nextAnalyze; + + private final Store store; + private final TransactionStore transactionStore; + + public MVTable(CreateTableData data, Store store) { + super(data); + this.isHidden = data.isHidden; + boolean b = false; + for (Column col : getColumns()) { + if (DataType.isLargeObject(col.getType().getValueType())) { + b = true; + break; + } + } + containsLargeObject = b; + nextAnalyze = database.getSettings().analyzeAuto; + changesUntilAnalyze = nextAnalyze <= 0 ? null : new AtomicInteger(nextAnalyze); + this.store = store; + this.transactionStore = store.getTransactionStore(); + traceLock = database.getTrace(Trace.LOCK); + + primaryIndex = new MVPrimaryIndex(database, this, getId(), + IndexColumn.wrap(getColumns()), IndexType.createScan(true)); + indexes.add(primaryIndex); + } + + public String getMapName() { + return primaryIndex.getMapName(); + } + + @Override + public boolean lock(SessionLocal session, int lockType) { + if (database.getLockMode() == Constants.LOCK_MODE_OFF) { + session.registerTableAsUpdated(this); + return false; + } + if (lockType == Table.READ_LOCK && lockExclusiveSession == null) { + return false; + } + if (lockExclusiveSession == session) { + return true; + } + if (lockType != Table.EXCLUSIVE_LOCK && lockSharedSessions.containsKey(session)) { + return true; + } + synchronized (this) { + if (lockType != Table.EXCLUSIVE_LOCK && lockSharedSessions.containsKey(session)) { + return true; + } + session.setWaitForLock(this, Thread.currentThread()); + if (SysProperties.THREAD_DEADLOCK_DETECTOR) { + WAITING_FOR_LOCK.set(getName()); + } + waitingSessions.addLast(session); + try { + doLock1(session, lockType); + } finally { + session.setWaitForLock(null, null); + if (SysProperties.THREAD_DEADLOCK_DETECTOR) { + WAITING_FOR_LOCK.remove(); + } + waitingSessions.remove(session); + } + } + return false; + } + + private void doLock1(SessionLocal session, int lockType) { + traceLock(session, lockType, TraceLockEvent.TRACE_LOCK_REQUESTING_FOR, NO_EXTRA_INFO); + // don't get the current time unless necessary + long max = 0L; + boolean checkDeadlock = false; + while (true) { + // if I'm the next one in the queue + if (waitingSessions.getFirst() == session && lockExclusiveSession == null) { + if (doLock2(session, lockType)) { + return; + } + } + if (checkDeadlock) { + ArrayList sessions = checkDeadlock(session, null, null); + if (sessions != null) { + throw DbException.get(ErrorCode.DEADLOCK_1, + getDeadlockDetails(sessions, lockType)); + } + } else { + // check for deadlocks from now on + checkDeadlock = true; + } + long now = System.nanoTime(); + if (max == 0L) { + // try at least one more time + max = Utils.nanoTimePlusMillis(now, session.getLockTimeout()); + } else if (now - max >= 0L) { + traceLock(session, lockType, + TraceLockEvent.TRACE_LOCK_TIMEOUT_AFTER, Integer.toString(session.getLockTimeout())); + throw DbException.get(ErrorCode.LOCK_TIMEOUT_1, getName()); + } + try { + traceLock(session, lockType, TraceLockEvent.TRACE_LOCK_WAITING_FOR, NO_EXTRA_INFO); + // don't wait too long so that deadlocks are detected early + long sleep = Math.min(Constants.DEADLOCK_CHECK, (max - now) / 1_000_000L); + if (sleep == 0) { + sleep = 1; + } + wait(sleep); + } catch (InterruptedException e) { + // ignore + } + } + } + + private boolean doLock2(SessionLocal session, int lockType) { + switch (lockType) { + case Table.EXCLUSIVE_LOCK: + int size = lockSharedSessions.size(); + if (size == 0) { + traceLock(session, lockType, TraceLockEvent.TRACE_LOCK_ADDED_FOR, NO_EXTRA_INFO); + session.registerTableAsLocked(this); + } else if (size == 1 && lockSharedSessions.containsKey(session)) { + traceLock(session, lockType, TraceLockEvent.TRACE_LOCK_ADD_UPGRADED_FOR, NO_EXTRA_INFO); + } else { + return false; + } + lockExclusiveSession = session; + if (SysProperties.THREAD_DEADLOCK_DETECTOR) { + addLockToDebugList(EXCLUSIVE_LOCKS); + } + break; + case Table.WRITE_LOCK: + if (lockSharedSessions.putIfAbsent(session, session) == null) { + traceLock(session, lockType, TraceLockEvent.TRACE_LOCK_OK, NO_EXTRA_INFO); + session.registerTableAsLocked(this); + if (SysProperties.THREAD_DEADLOCK_DETECTOR) { + addLockToDebugList(SHARED_LOCKS); + } + } + } + return true; + } + + private void addLockToDebugList(DebuggingThreadLocal> locks) { + ArrayList list = locks.get(); + if (list == null) { + list = new ArrayList<>(); + locks.set(list); + } + list.add(getName()); + } + + private void traceLock(SessionLocal session, int lockType, TraceLockEvent eventEnum, String extraInfo) { + if (traceLock.isDebugEnabled()) { + traceLock.debug("{0} {1} {2} {3} {4}", session.getId(), + lockTypeToString(lockType), eventEnum.getEventText(), + getName(), extraInfo); + } + } + + @Override + public void unlock(SessionLocal s) { + if (database != null) { + int lockType; + if (lockExclusiveSession == s) { + lockType = Table.EXCLUSIVE_LOCK; + lockSharedSessions.remove(s); + lockExclusiveSession = null; + if (SysProperties.THREAD_DEADLOCK_DETECTOR) { + ArrayList exclusiveLocks = EXCLUSIVE_LOCKS.get(); + if (exclusiveLocks != null) { + exclusiveLocks.remove(getName()); + } + } + } else { + lockType = lockSharedSessions.remove(s) != null ? Table.WRITE_LOCK : Table.READ_LOCK; + if (SysProperties.THREAD_DEADLOCK_DETECTOR) { + ArrayList sharedLocks = SHARED_LOCKS.get(); + if (sharedLocks != null) { + sharedLocks.remove(getName()); + } + } + } + traceLock(s, lockType, TraceLockEvent.TRACE_LOCK_UNLOCK, NO_EXTRA_INFO); + if (lockType != Table.READ_LOCK && !waitingSessions.isEmpty()) { + synchronized (this) { + notifyAll(); + } + } + } + } + + @Override + public void close(SessionLocal session) { + // ignore + } + + @Override + public Row getRow(SessionLocal session, long key) { + return primaryIndex.getRow(session, key); + } + + @Override + public Index addIndex(SessionLocal session, String indexName, int indexId, IndexColumn[] cols, + int uniqueColumnCount, IndexType indexType, boolean create, String indexComment) { + cols = prepareColumns(database, cols, indexType); + boolean isSessionTemporary = isTemporary() && !isGlobalTemporary(); + if (!isSessionTemporary) { + database.lockMeta(session); + } + MVIndex index; + int mainIndexColumn = primaryIndex.getMainIndexColumn() != SearchRow.ROWID_INDEX + ? SearchRow.ROWID_INDEX : getMainIndexColumn(indexType, cols); + if (database.isStarting()) { + // if index does exists as a separate map it can't be a delegate + if (transactionStore.hasMap("index." + indexId)) { + // we can not reuse primary index + mainIndexColumn = SearchRow.ROWID_INDEX; + } + } else if (primaryIndex.getRowCountMax() != 0) { + mainIndexColumn = SearchRow.ROWID_INDEX; + } + + if (mainIndexColumn != SearchRow.ROWID_INDEX) { + primaryIndex.setMainIndexColumn(mainIndexColumn); + index = new MVDelegateIndex(this, indexId, indexName, primaryIndex, + indexType); + } else if (indexType.isSpatial()) { + index = new MVSpatialIndex(session.getDatabase(), this, indexId, + indexName, cols, uniqueColumnCount, indexType); + } else { + index = new MVSecondaryIndex(session.getDatabase(), this, indexId, + indexName, cols, uniqueColumnCount, indexType); + } + if (index.needRebuild()) { + rebuildIndex(session, index, indexName); + } + index.setTemporary(isTemporary()); + if (index.getCreateSQL() != null) { + index.setComment(indexComment); + if (isSessionTemporary) { + session.addLocalTempTableIndex(index); + } else { + database.addSchemaObject(session, index); + } + } + indexes.add(index); + setModified(); + return index; + } + + private void rebuildIndex(SessionLocal session, MVIndex index, String indexName) { + try { + if (!session.getDatabase().isPersistent() || index instanceof MVSpatialIndex) { + // in-memory + rebuildIndexBuffered(session, index); + } else { + rebuildIndexBlockMerge(session, index); + } + } catch (DbException e) { + getSchema().freeUniqueName(indexName); + try { + index.remove(session); + } catch (DbException e2) { + // this could happen, for example on failure in the storage + // but if that is not the case it means + // there is something wrong with the database + trace.error(e2, "could not remove index"); + throw e2; + } + throw e; + } + } + + private void rebuildIndexBlockMerge(SessionLocal session, MVIndex index) { + // Read entries in memory, sort them, write to a new map (in sorted + // order); repeat (using a new map for every block of 1 MB) until all + // record are read. Merge all maps to the target (using merge sort; + // duplicates are detected in the target). For randomly ordered data, + // this should use relatively few write operations. + // A possible optimization is: change the buffer size from "row count" + // to "amount of memory", and buffer index keys instead of rows. + Index scan = getScanIndex(session); + long remaining = scan.getRowCount(session); + long total = remaining; + Cursor cursor = scan.find(session, null, null); + long i = 0; + Store store = session.getDatabase().getStore(); + + int bufferSize = database.getMaxMemoryRows() / 2; + ArrayList buffer = new ArrayList<>(bufferSize); + String n = getName() + ':' + index.getName(); + ArrayList bufferNames = Utils.newSmallArrayList(); + while (cursor.next()) { + Row row = cursor.get(); + buffer.add(row); + database.setProgress(DatabaseEventListener.STATE_CREATE_INDEX, n, i++, total); + if (buffer.size() >= bufferSize) { + sortRows(buffer, index); + String mapName = store.nextTemporaryMapName(); + index.addRowsToBuffer(buffer, mapName); + bufferNames.add(mapName); + buffer.clear(); + } + remaining--; + } + sortRows(buffer, index); + if (!bufferNames.isEmpty()) { + String mapName = store.nextTemporaryMapName(); + index.addRowsToBuffer(buffer, mapName); + bufferNames.add(mapName); + buffer.clear(); + index.addBufferedRows(bufferNames); + } else { + addRowsToIndex(session, buffer, index); + } + if (remaining != 0) { + throw DbException.getInternalError("rowcount remaining=" + remaining + ' ' + getName()); + } + } + + private void rebuildIndexBuffered(SessionLocal session, Index index) { + Index scan = getScanIndex(session); + long remaining = scan.getRowCount(session); + long total = remaining; + Cursor cursor = scan.find(session, null, null); + long i = 0; + int bufferSize = (int) Math.min(total, database.getMaxMemoryRows()); + ArrayList buffer = new ArrayList<>(bufferSize); + String n = getName() + ':' + index.getName(); + while (cursor.next()) { + Row row = cursor.get(); + buffer.add(row); + database.setProgress(DatabaseEventListener.STATE_CREATE_INDEX, n, i++, total); + if (buffer.size() >= bufferSize) { + addRowsToIndex(session, buffer, index); + } + remaining--; + } + addRowsToIndex(session, buffer, index); + if (remaining != 0) { + throw DbException.getInternalError("rowcount remaining=" + remaining + ' ' + getName()); + } + } + + @Override + public void removeRow(SessionLocal session, Row row) { + syncLastModificationIdWithDatabase(); + Transaction t = session.getTransaction(); + long savepoint = t.setSavepoint(); + try { + for (int i = indexes.size() - 1; i >= 0; i--) { + Index index = indexes.get(i); + index.remove(session, row); + } + } catch (Throwable e) { + try { + t.rollbackToSavepoint(savepoint); + } catch (Throwable nested) { + e.addSuppressed(nested); + } + throw DbException.convert(e); + } + analyzeIfRequired(session); + } + + @Override + public long truncate(SessionLocal session) { + syncLastModificationIdWithDatabase(); + long result = getRowCountApproximation(session); + for (int i = indexes.size() - 1; i >= 0; i--) { + Index index = indexes.get(i); + index.truncate(session); + } + if (changesUntilAnalyze != null) { + changesUntilAnalyze.set(nextAnalyze); + } + return result; + } + + @Override + public void addRow(SessionLocal session, Row row) { + syncLastModificationIdWithDatabase(); + Transaction t = session.getTransaction(); + long savepoint = t.setSavepoint(); + try { + for (Index index : indexes) { + index.add(session, row); + } + } catch (Throwable e) { + try { + t.rollbackToSavepoint(savepoint); + } catch (Throwable nested) { + e.addSuppressed(nested); + } + throw DbException.convert(e); + } + analyzeIfRequired(session); + } + + @Override + public void updateRow(SessionLocal session, Row oldRow, Row newRow) { + newRow.setKey(oldRow.getKey()); + syncLastModificationIdWithDatabase(); + Transaction t = session.getTransaction(); + long savepoint = t.setSavepoint(); + try { + for (Index index : indexes) { + index.update(session, oldRow, newRow); + } + } catch (Throwable e) { + try { + t.rollbackToSavepoint(savepoint); + } catch (Throwable nested) { + e.addSuppressed(nested); + } + throw DbException.convert(e); + } + analyzeIfRequired(session); + } + + @Override + public Row lockRow(SessionLocal session, Row row) { + Row lockedRow = primaryIndex.lockRow(session, row); + if (lockedRow == null || !row.hasSharedData(lockedRow)) { + syncLastModificationIdWithDatabase(); + } + return lockedRow; + } + + private void analyzeIfRequired(SessionLocal session) { + if (changesUntilAnalyze != null) { + if (changesUntilAnalyze.decrementAndGet() == 0) { + if (nextAnalyze <= Integer.MAX_VALUE / 2) { + nextAnalyze *= 2; + } + changesUntilAnalyze.set(nextAnalyze); + session.markTableForAnalyze(this); + } + } + } + + @Override + public Index getScanIndex(SessionLocal session) { + return primaryIndex; + } + + @Override + public ArrayList getIndexes() { + return indexes; + } + + @Override + public long getMaxDataModificationId() { + return lastModificationId.get(); + } + + @Override + public void removeChildrenAndResources(SessionLocal session) { + if (containsLargeObject) { + // unfortunately, the data is gone on rollback + truncate(session); + database.getLobStorage().removeAllForTable(getId()); + database.lockMeta(session); + } + database.getStore().removeTable(this); + super.removeChildrenAndResources(session); + // remove scan index (at position 0 on the list) last + while (indexes.size() > 1) { + Index index = indexes.get(1); + index.remove(session); + if (index.getName() != null) { + database.removeSchemaObject(session, index); + } + // needed for session temporary indexes + indexes.remove(index); + } + primaryIndex.remove(session); + indexes.clear(); + close(session); + invalidate(); + } + + @Override + public long getRowCount(SessionLocal session) { + return primaryIndex.getRowCount(session); + } + + @Override + public long getRowCountApproximation(SessionLocal session) { + return primaryIndex.getRowCountApproximation(session); + } + + @Override + public long getDiskSpaceUsed() { + return primaryIndex.getDiskSpaceUsed(); + } + + /** + * Get a new transaction. + * + * @return the transaction + */ + Transaction getTransactionBegin() { + // TODO need to commit/rollback the transaction + return transactionStore.begin(); + } + + @Override + public boolean isRowLockable() { + return true; + } + + /** + * Mark the transaction as committed, so that the modification counter of + * the database is incremented. + */ + public void commit() { + if (database != null) { + syncLastModificationIdWithDatabase(); + } + } + + // Field lastModificationId can not be just a volatile, because window of opportunity + // between reading database's modification id and storing this value in the field + // could be exploited by another thread. + // Second thread may do the same with possibly bigger (already advanced) + // modification id, and when first thread finally updates the field, it will + // result in lastModificationId jumping back. + // This is, of course, unacceptable. + private void syncLastModificationIdWithDatabase() { + long nextModificationDataId = database.getNextModificationDataId(); + long currentId; + do { + currentId = lastModificationId.get(); + } while (nextModificationDataId > currentId && + !lastModificationId.compareAndSet(currentId, nextModificationDataId)); + } + + /** + * Convert the MVStoreException to a database exception. + * + * @param e the illegal state exception + * @return the database exception + */ + DbException convertException(MVStoreException e) { + int errorCode = e.getErrorCode(); + if (errorCode == DataUtils.ERROR_TRANSACTION_LOCKED) { + throw DbException.get(ErrorCode.CONCURRENT_UPDATE_1, + e, getName()); + } + if (errorCode == DataUtils.ERROR_TRANSACTIONS_DEADLOCK) { + throw DbException.get(ErrorCode.DEADLOCK_1, + e, getName()); + } + return store.convertMVStoreException(e); + } + + @Override + public int getMainIndexColumn() { + return primaryIndex.getMainIndexColumn(); + } + + + /** + * Appends the specified rows to the specified index. + * + * @param session + * the session + * @param list + * the rows, list is cleared on completion + * @param index + * the index to append to + */ + private static void addRowsToIndex(SessionLocal session, ArrayList list, Index index) { + sortRows(list, index); + for (Row row : list) { + index.add(session, row); + } + list.clear(); + } + + /** + * Formats details of a deadlock. + * + * @param sessions + * the list of sessions + * @param lockType + * the type of lock + * @return formatted details of a deadlock + */ + private static String getDeadlockDetails(ArrayList sessions, int lockType) { + // We add the thread details here to make it easier for customers to + // match up these error messages with their own logs. + StringBuilder builder = new StringBuilder(); + for (SessionLocal s : sessions) { + Table lock = s.getWaitForLock(); + Thread thread = s.getWaitForLockThread(); + builder.append("\nSession ").append(s).append(" on thread ").append(thread.getName()) + .append(" is waiting to lock ").append(lock.toString()) + .append(" (").append(lockTypeToString(lockType)).append(") while locking "); + boolean addComma = false; + for (Table t : s.getLocks()) { + if (addComma) { + builder.append(", "); + } + addComma = true; + builder.append(t.toString()); + if (t instanceof MVTable) { + if (((MVTable) t).lockExclusiveSession == s) { + builder.append(" (exclusive)"); + } else { + builder.append(" (shared)"); + } + } + } + builder.append('.'); + } + return builder.toString(); + } + + private static String lockTypeToString(int lockType) { + return lockType == Table.READ_LOCK ? "shared read" + : lockType == Table.WRITE_LOCK ? "shared write" : "exclusive"; + } + + /** + * Sorts the specified list of rows for a specified index. + * + * @param list + * the list of rows + * @param index + * the index to sort for + */ + private static void sortRows(ArrayList list, final Index index) { + list.sort(index::compareRows); + } + + @Override + public boolean canDrop() { + return true; + } + + @Override + public boolean canGetRowCount(SessionLocal session) { + return true; + } + + @Override + public boolean canTruncate() { + if (getCheckForeignKeyConstraints() && database.getReferentialIntegrity()) { + ArrayList constraints = getConstraints(); + if (constraints != null) { + for (Constraint c : constraints) { + if (c.getConstraintType() != Constraint.Type.REFERENTIAL) { + continue; + } + ConstraintReferential ref = (ConstraintReferential) c; + if (ref.getRefTable() == this) { + return false; + } + } + } + } + return true; + } + + @Override + public ArrayList checkDeadlock(SessionLocal session, SessionLocal clash, Set visited) { + // only one deadlock check at any given time + synchronized (getClass()) { + if (clash == null) { + // verification is started + clash = session; + visited = new HashSet<>(); + } else if (clash == session) { + // we found a cycle where this session is involved + return new ArrayList<>(0); + } else if (visited.contains(session)) { + // we have already checked this session. + // there is a cycle, but the sessions in the cycle need to + // find it out themselves + return null; + } + visited.add(session); + ArrayList error = null; + for (SessionLocal s : lockSharedSessions.keySet()) { + if (s == session) { + // it doesn't matter if we have locked the object already + continue; + } + Table t = s.getWaitForLock(); + if (t != null) { + error = t.checkDeadlock(s, clash, visited); + if (error != null) { + error.add(session); + break; + } + } + } + // take a local copy so we don't see inconsistent data, since we are + // not locked while checking the lockExclusiveSession value + SessionLocal copyOfLockExclusiveSession = lockExclusiveSession; + if (error == null && copyOfLockExclusiveSession != null) { + Table t = copyOfLockExclusiveSession.getWaitForLock(); + if (t != null) { + error = t.checkDeadlock(copyOfLockExclusiveSession, clash, visited); + if (error != null) { + error.add(session); + } + } + } + return error; + } + } + + @Override + public void checkSupportAlter() { + // ok + } + + public boolean getContainsLargeObject() { + return containsLargeObject; + } + + @Override + public Column getRowIdColumn() { + if (rowIdColumn == null) { + rowIdColumn = new Column(Column.ROWID, TypeInfo.TYPE_BIGINT, this, SearchRow.ROWID_INDEX); + rowIdColumn.setRowId(true); + rowIdColumn.setNullable(false); + } + return rowIdColumn; + } + + @Override + public TableType getTableType() { + return TableType.TABLE; + } + + @Override + public boolean isDeterministic() { + return true; + } + + @Override + public boolean isLockedExclusively() { + return lockExclusiveSession != null; + } + + @Override + public boolean isLockedExclusivelyBy(SessionLocal session) { + return lockExclusiveSession == session; + } + + @Override + protected void invalidate() { + super.invalidate(); + /* + * Query cache of a some sleeping session can have references to + * invalidated tables. When this table was dropped by another session, + * the field below still points to it and prevents its garbage + * collection, so this field needs to be cleared to prevent a memory + * leak. + */ + lockExclusiveSession = null; + } + + @Override + public String toString() { + return getTraceSQL(); + } + + /** + * Prepares columns of an index. + * + * @param database the database + * @param cols the index columns + * @param indexType the type of an index + * @return the prepared columns with flags set + */ + private static IndexColumn[] prepareColumns(Database database, IndexColumn[] cols, IndexType indexType) { + if (indexType.isPrimaryKey()) { + for (IndexColumn c : cols) { + Column column = c.column; + if (column.isNullable()) { + throw DbException.get(ErrorCode.COLUMN_MUST_NOT_BE_NULLABLE_1, column.getName()); + } + } + for (IndexColumn c : cols) { + c.column.setPrimaryKey(true); + } + } else if (!indexType.isSpatial()) { + int i = 0, l = cols.length; + while (i < l && (cols[i].sortType & (SortOrder.NULLS_FIRST | SortOrder.NULLS_LAST)) != 0) { + i++; + } + if (i != l) { + cols = cols.clone(); + DefaultNullOrdering defaultNullOrdering = database.getDefaultNullOrdering(); + for (; i < l; i++) { + IndexColumn oldColumn = cols[i]; + int sortTypeOld = oldColumn.sortType; + int sortTypeNew = defaultNullOrdering.addExplicitNullOrdering(sortTypeOld); + if (sortTypeNew != sortTypeOld) { + IndexColumn newColumn = new IndexColumn(oldColumn.columnName, sortTypeNew); + newColumn.column = oldColumn.column; + cols[i] = newColumn; + } + } + } + } + return cols; + } +} diff --git a/h2/src/main/org/h2/mvstore/db/MVTempResult.java b/h2/src/main/org/h2/mvstore/db/MVTempResult.java new file mode 100644 index 0000000..97779cb --- /dev/null +++ b/h2/src/main/org/h2/mvstore/db/MVTempResult.java @@ -0,0 +1,230 @@ +/* + * 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.mvstore.db; + +import java.io.IOException; +import java.lang.ref.Reference; +import java.util.Collection; + +import org.h2.engine.Constants; +import org.h2.engine.Database; +import org.h2.expression.Expression; +import org.h2.message.DbException; +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; +import org.h2.util.TempFileDeleter; +import org.h2.value.Value; + +/** + * Temporary result. + * + *

+ * A separate MVStore in a temporary file is used for each result. The file is + * removed when this result and all its copies are closed. + * {@link TempFileDeleter} is also used to delete this file if results are not + * closed properly. + *

+ */ +public abstract class MVTempResult implements ResultExternal { + + private static final class CloseImpl implements AutoCloseable { + /** + * MVStore. + */ + private final MVStore store; + + /** + * File name. + */ + private final String fileName; + + CloseImpl(MVStore store, String fileName) { + this.store = store; + this.fileName = fileName; + } + + @Override + public void close() throws Exception { + store.closeImmediately(); + FileUtils.tryDelete(fileName); + } + + } + + /** + * Creates MVStore-based temporary result. + * + * @param database + * database + * @param expressions + * expressions + * @param distinct + * is output distinct + * @param distinctIndexes + * indexes of distinct columns for DISTINCT ON results + * @param visibleColumnCount + * count of visible columns + * @param resultColumnCount + * the number of columns including visible columns and additional + * virtual columns for ORDER BY and DISTINCT ON clauses + * @param sort + * sort order, or {@code null} + * @return temporary result + */ + public static ResultExternal of(Database database, Expression[] expressions, boolean distinct, + int[] distinctIndexes, int visibleColumnCount, int resultColumnCount, SortOrder sort) { + return distinct || distinctIndexes != null || sort != null + ? new MVSortedTempResult(database, expressions, distinct, distinctIndexes, visibleColumnCount, + resultColumnCount, sort) + : new MVPlainTempResult(database, expressions, visibleColumnCount, resultColumnCount); + } + + private final Database database; + + /** + * MVStore. + */ + final MVStore store; + + /** + * Column expressions. + */ + final Expression[] expressions; + + /** + * Count of visible columns. + */ + final int visibleColumnCount; + + /** + * Total count of columns. + */ + final int resultColumnCount; + + /** + * Count of rows. Used only in a root results, copies always have 0 value. + */ + int rowCount; + + /** + * Parent store for copies. If {@code null} this result is a root result. + */ + final MVTempResult parent; + + /** + * Count of child results. + */ + int childCount; + + /** + * Whether this result is closed. + */ + boolean closed; + + /** + * Temporary file deleter. + */ + private final TempFileDeleter tempFileDeleter; + + /** + * Closeable to close the storage. + */ + private final CloseImpl closeable; + + /** + * Reference to the record in the temporary file deleter. + */ + private final Reference fileRef; + + /** + * Creates a shallow copy of the result. + * + * @param parent + * parent result + */ + MVTempResult(MVTempResult parent) { + this.parent = parent; + this.database = parent.database; + this.store = parent.store; + this.expressions = parent.expressions; + this.visibleColumnCount = parent.visibleColumnCount; + this.resultColumnCount = parent.resultColumnCount; + this.tempFileDeleter = null; + this.closeable = null; + this.fileRef = null; + } + + /** + * Creates a new temporary result. + * + * @param database + * database + * @param expressions + * column expressions + * @param visibleColumnCount + * count of visible columns + * @param resultColumnCount + * total count of columns + */ + MVTempResult(Database database, Expression[] expressions, int visibleColumnCount, int resultColumnCount) { + 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)); + } + store = builder.open(); + this.expressions = expressions; + this.visibleColumnCount = visibleColumnCount; + this.resultColumnCount = resultColumnCount; + tempFileDeleter = database.getTempFileDeleter(); + closeable = new CloseImpl(store, fileName); + fileRef = tempFileDeleter.addFile(closeable, this); + } catch (IOException e) { + throw DbException.convert(e); + } + parent = null; + } + + @Override + public int addRows(Collection rows) { + for (Value[] row : rows) { + addRow(row); + } + return rowCount; + } + + @Override + public synchronized void close() { + if (closed) { + return; + } + closed = true; + if (parent != null) { + parent.closeChild(); + } else { + if (childCount == 0) { + delete(); + } + } + } + + private synchronized void closeChild() { + if (--childCount == 0 && closed) { + delete(); + } + } + + private void delete() { + tempFileDeleter.deleteFile(fileRef, closeable); + } + +} diff --git a/h2/src/main/org/h2/mvstore/db/NullValueDataType.java b/h2/src/main/org/h2/mvstore/db/NullValueDataType.java new file mode 100644 index 0000000..c9b4ff3 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/db/NullValueDataType.java @@ -0,0 +1,73 @@ +/* + * 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.mvstore.db; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import org.h2.mvstore.WriteBuffer; +import org.h2.mvstore.type.DataType; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * Dummy data type used when no value is required. This data type doesn't use + * any disk space and always returns SQL NULL value. + */ +public final class NullValueDataType implements DataType { + + /** + * Dummy data type instance. + */ + public static final NullValueDataType INSTANCE = new NullValueDataType(); + + private NullValueDataType() { + } + + @Override + public int compare(Value a, Value b) { + return 0; + } + + @Override + public int binarySearch(Value key, Object storage, int size, int initialGuess) { + return 0; + } + + @Override + public int getMemory(Value obj) { + return 0; + } + + @Override + public boolean isMemoryEstimationAllowed() { + return true; + } + + @Override + public void write(WriteBuffer buff, Value obj) { + } + + @Override + public void write(WriteBuffer buff, Object storage, int len) { + } + + @Override + public Value read(ByteBuffer buff) { + return ValueNull.INSTANCE; + } + + @Override + public void read(ByteBuffer buff, Object storage, int len) { + Arrays.fill((Value[]) storage, 0, len, ValueNull.INSTANCE); + } + + @Override + public Value[] createStorage(int size) { + return new Value[size]; + } + +} diff --git a/h2/src/main/org/h2/mvstore/db/RowDataType.java b/h2/src/main/org/h2/mvstore/db/RowDataType.java new file mode 100644 index 0000000..3486203 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/db/RowDataType.java @@ -0,0 +1,262 @@ +/* + * 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.mvstore.db; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import org.h2.engine.CastDataProvider; +import org.h2.engine.Database; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.WriteBuffer; +import org.h2.mvstore.type.BasicDataType; +import org.h2.mvstore.type.MetaType; +import org.h2.mvstore.type.StatefulDataType; +import org.h2.result.RowFactory; +import org.h2.result.SearchRow; +import org.h2.store.DataHandler; +import org.h2.value.CompareMode; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * The data type for rows. + * + * @author Andrei Tokar + */ +public final class RowDataType extends BasicDataType implements StatefulDataType { + + private final ValueDataType valueDataType; + private final int[] sortTypes; + private final int[] indexes; + private final int columnCount; + private final boolean storeKeys; + + public RowDataType(CastDataProvider provider, CompareMode compareMode, DataHandler handler, int[] sortTypes, + int[] indexes, int columnCount, boolean storeKeys) { + this.valueDataType = new ValueDataType(provider, compareMode, handler, sortTypes); + this.sortTypes = sortTypes; + this.indexes = indexes; + this.columnCount = columnCount; + this.storeKeys = storeKeys; + assert indexes == null || sortTypes.length == indexes.length; + } + + public int[] getIndexes() { + return indexes; + } + + public RowFactory getRowFactory() { + return valueDataType.getRowFactory(); + } + + public void setRowFactory(RowFactory rowFactory) { + valueDataType.setRowFactory(rowFactory); + } + + public int getColumnCount() { + return columnCount; + } + + public boolean isStoreKeys() { + return storeKeys; + } + + @Override + public SearchRow[] createStorage(int capacity) { + return new SearchRow[capacity]; + } + + @Override + public int compare(SearchRow a, SearchRow b) { + if (a == b) { + return 0; + } + if (indexes == null) { + int len = a.getColumnCount(); + assert len == b.getColumnCount() : len + " != " + b.getColumnCount(); + for (int i = 0; i < len; i++) { + int comp = valueDataType.compareValues(a.getValue(i), b.getValue(i), sortTypes[i]); + if (comp != 0) { + return comp; + } + } + return 0; + } else { + return compareSearchRows(a, b); + } + } + + private int compareSearchRows(SearchRow a, SearchRow b) { + for (int i = 0; i < indexes.length; i++) { + int index = indexes[i]; + Value v1 = a.getValue(index); + Value v2 = b.getValue(index); + if (v1 == null || v2 == null) { + // can't compare further + break; + } + int comp = valueDataType.compareValues(v1, v2, sortTypes[i]); + if (comp != 0) { + return comp; + } + } + long aKey = a.getKey(); + long bKey = b.getKey(); + return aKey == SearchRow.MATCH_ALL_ROW_KEY || bKey == SearchRow.MATCH_ALL_ROW_KEY ? + 0 : Long.compare(aKey, bKey); + } + + @Override + public int binarySearch(SearchRow key, Object storage, int size, int initialGuess) { + return binarySearch(key, (SearchRow[])storage, size, initialGuess); + } + + public int binarySearch(SearchRow key, SearchRow[] keys, int size, int initialGuess) { + int low = 0; + int high = size - 1; + // the cached index minus one, so that + // for the first time (when cachedCompare is 0), + // the default value is used + int x = initialGuess - 1; + if (x < 0 || x > high) { + x = high >>> 1; + } + while (low <= high) { + int compare = compareSearchRows(key, keys[x]); + if (compare > 0) { + low = x + 1; + } else if (compare < 0) { + high = x - 1; + } else { + return x; + } + x = (low + high) >>> 1; + } + return -(low + 1); + } + + @Override + public int getMemory(SearchRow row) { + return row.getMemory(); + } + + @Override + public SearchRow read(ByteBuffer buff) { + RowFactory rowFactory = valueDataType.getRowFactory(); + SearchRow row = rowFactory.createRow(); + if (storeKeys) { + row.setKey(DataUtils.readVarLong(buff)); + } + TypeInfo[] columnTypes = rowFactory.getColumnTypes(); + if (indexes == null) { + int columnCount = row.getColumnCount(); + for (int i = 0; i < columnCount; i++) { + row.setValue(i, valueDataType.readValue(buff, columnTypes != null ? columnTypes[i] : null)); + } + } else { + for (int i : indexes) { + row.setValue(i, valueDataType.readValue(buff, columnTypes != null ? columnTypes[i] : null)); + } + } + return row; + } + + @Override + public void write(WriteBuffer buff, SearchRow row) { + if (storeKeys) { + buff.putVarLong(row.getKey()); + } + if (indexes == null) { + int columnCount = row.getColumnCount(); + for (int i = 0; i < columnCount; i++) { + valueDataType.write(buff, row.getValue(i)); + } + } else { + for (int i : indexes) { + valueDataType.write(buff, row.getValue(i)); + } + } + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } else if (obj == null || obj.getClass() != RowDataType.class) { + return false; + } + RowDataType other = (RowDataType) obj; + return columnCount == other.columnCount + && Arrays.equals(indexes, other.indexes) + && Arrays.equals(sortTypes, other.sortTypes) + && valueDataType.equals(other.valueDataType); + } + + @Override + public int hashCode() { + int res = super.hashCode(); + res = res * 31 + columnCount; + res = res * 31 + Arrays.hashCode(indexes); + res = res * 31 + Arrays.hashCode(sortTypes); + res = res * 31 + valueDataType.hashCode(); + return res; + } + + @Override + public void save(WriteBuffer buff, MetaType metaType) { + buff.putVarInt(columnCount); + writeIntArray(buff, sortTypes); + writeIntArray(buff, indexes); + buff.put(storeKeys ? (byte) 1 : (byte) 0); + } + + private static void writeIntArray(WriteBuffer buff, int[] array) { + if(array == null) { + buff.putVarInt(0); + } else { + buff.putVarInt(array.length + 1); + for (int i : array) { + buff.putVarInt(i); + } + } + } + + @Override + public Factory getFactory() { + return FACTORY; + } + + + + private static final Factory FACTORY = new Factory(); + + public static final class Factory implements StatefulDataType.Factory { + + @Override + public RowDataType create(ByteBuffer buff, MetaType metaDataType, Database database) { + int columnCount = DataUtils.readVarInt(buff); + int[] sortTypes = readIntArray(buff); + int[] indexes = readIntArray(buff); + boolean storeKeys = buff.get() != 0; + CompareMode compareMode = database == null ? CompareMode.getInstance(null, 0) : database.getCompareMode(); + RowFactory rowFactory = RowFactory.getDefaultRowFactory().createRowFactory(database, compareMode, database, + sortTypes, indexes, null, columnCount, storeKeys); + return rowFactory.getRowDataType(); + } + + private static int[] readIntArray(ByteBuffer buff) { + int len = DataUtils.readVarInt(buff) - 1; + if(len < 0) { + return null; + } + int[] res = new int[len]; + for (int i = 0; i < res.length; i++) { + res[i] = DataUtils.readVarInt(buff); + } + return res; + } + } +} diff --git a/h2/src/main/org/h2/mvstore/db/SpatialKey.java b/h2/src/main/org/h2/mvstore/db/SpatialKey.java new file mode 100644 index 0000000..2a9438e --- /dev/null +++ b/h2/src/main/org/h2/mvstore/db/SpatialKey.java @@ -0,0 +1,143 @@ +/* + * 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.mvstore.db; + +import java.util.Arrays; +import org.h2.engine.CastDataProvider; +import org.h2.mvstore.rtree.Spatial; +import org.h2.value.CompareMode; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * A unique spatial key. + */ +public class SpatialKey extends Value implements Spatial { + + private final long id; + private final float[] minMax; + + /** + * Create a new key. + * + * @param id the id + * @param minMax min x, max x, min y, max y, and so on + */ + public SpatialKey(long id, float... minMax) { + this.id = id; + this.minMax = minMax; + } + + public SpatialKey(long id, SpatialKey other) { + this.id = id; + this.minMax = other.minMax.clone(); + } + + @Override + public float min(int dim) { + return minMax[dim + dim]; + } + + @Override + public void setMin(int dim, float x) { + minMax[dim + dim] = x; + } + + @Override + public float max(int dim) { + return minMax[dim + dim + 1]; + } + + @Override + public void setMax(int dim, float x) { + minMax[dim + dim + 1] = x; + } + + @Override + public Spatial clone(long id) { + return new SpatialKey(id, this); + } + + @Override + public long getId() { + return id; + } + + @Override + public boolean isNull() { + return minMax.length == 0; + } + + @Override + public String toString() { + return getString(); + } + + @Override + public int hashCode() { + return (int) ((id >>> 32) ^ id); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (!(other instanceof SpatialKey)) { + return false; + } + SpatialKey o = (SpatialKey) other; + if (id != o.id) { + return false; + } + return equalsIgnoringId(o); + } + + @Override + public int compareTypeSafe(Value v, CompareMode mode, CastDataProvider provider) { + throw new UnsupportedOperationException(); +// return 0; + } + + /** + * Check whether two objects are equals, but do not compare the id fields. + * + * @param o the other key + * @return true if the contents are the same + */ + @Override + public boolean equalsIgnoringId(Spatial o) { + return Arrays.equals(minMax, ((SpatialKey)o).minMax); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + builder.append(id).append(": ("); + for (int i = 0; i < minMax.length; i += 2) { + if (i > 0) { + builder.append(", "); + } + builder.append(minMax[i]).append('/').append(minMax[i + 1]); + } + builder.append(")"); + return builder; + } + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_GEOMETRY; + } + + @Override + public int getValueType() { + return Value.GEOMETRY; + } + + @Override + public String getString() { + return getTraceSQL(); + } + +} diff --git a/h2/src/main/org/h2/mvstore/db/Store.java b/h2/src/main/org/h2/mvstore/db/Store.java new file mode 100644 index 0000000..4f051b2 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/db/Store.java @@ -0,0 +1,416 @@ +/* + * 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.mvstore.db; + +import java.io.InputStream; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.h2.api.ErrorCode; +import org.h2.command.ddl.CreateTableData; +import org.h2.engine.Constants; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.FileStore; +import org.h2.mvstore.MVStore; +import org.h2.mvstore.MVStoreException; +import org.h2.mvstore.MVStoreTool; +import org.h2.mvstore.tx.Transaction; +import org.h2.mvstore.tx.TransactionStore; +import org.h2.mvstore.type.MetaType; +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. + */ +public final class Store { + + /** + * Convert password from byte[] to char[]. + * + * @param key password as byte[] + * @return password as char[]. + */ + 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)); + } + return password; + } + + /** + * The map of open tables. + * Key: the map name, value: the table. + */ + private final ConcurrentHashMap tableMap = new ConcurrentHashMap<>(); + + /** + * The store. + */ + private final MVStore mvStore; + + /** + * The transaction store. + */ + private final TransactionStore transactionStore; + + private long statisticsStart; + + private int temporaryMapId; + + private final boolean encrypted; + + private final String fileName; + + /** + * Creates the store. + * + * @param db the database + */ + public Store(Database db) { + byte[] key = db.getFileEncryptionKey(); + String dbPath = db.getDatabasePath(); + MVStore.Builder builder = new MVStore.Builder(); + boolean encrypted = false; + if (dbPath != null) { + String fileName = dbPath + Constants.SUFFIX_MV_FILE; + MVStoreTool.compactCleanUp(fileName); + builder.fileName(fileName); + builder.pageSplitSize(db.getPageSize()); + if (db.isReadOnly()) { + builder.readOnly(); + } else { + // possibly create the directory + boolean exists = FileUtils.exists(fileName); + if (exists && !FileUtils.canWrite(fileName)) { + // read only + } else { + String dir = FileUtils.getParent(fileName); + FileUtils.createDirectories(dir); + } + int autoCompactFillRate = db.getSettings().autoCompactFillRate; + if (autoCompactFillRate <= 100) { + builder.autoCompactFillRate(autoCompactFillRate); + } + } + if (key != null) { + encrypted = true; + builder.encryptionKey(decodePassword(key)); + } + if (db.getSettings().compressData) { + builder.compress(); + // use a larger page split size to improve the compression ratio + builder.pageSplitSize(64 * 1024); + } + builder.backgroundExceptionHandler((t, e) -> db.setBackgroundException(DbException.convert(e))); + // always start without background thread first, and if necessary, + // it will be set up later, after db has been fully started, + // otherwise background thread would compete for store lock + // with maps opening procedure + builder.autoCommitDisabled(); + } + 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); + } + mvStore.setVersionsToKeep(0); + this.transactionStore = new TransactionStore(mvStore, + new MetaType<>(db, mvStore.backgroundExceptionHandler), new ValueDataType(db, null), + db.getLockTimeout()); + } catch (MVStoreException e) { + throw convertMVStoreException(e); + } + } + + /** + * Convert a MVStoreException to the similar exception used + * for the table/sql layers. + * + * @param e the illegal state exception + * @return the database exception + */ + DbException convertMVStoreException(MVStoreException e) { + switch (e.getErrorCode()) { + case DataUtils.ERROR_CLOSED: + throw DbException.get(ErrorCode.DATABASE_IS_CLOSED, e, fileName); + case DataUtils.ERROR_FILE_CORRUPT: + if (encrypted) { + throw DbException.get(ErrorCode.FILE_ENCRYPTION_ERROR_1, e, fileName); + } + throw DbException.get(ErrorCode.FILE_CORRUPTED_1, e, fileName); + case DataUtils.ERROR_FILE_LOCKED: + throw DbException.get(ErrorCode.DATABASE_ALREADY_OPEN_1, e, fileName); + case DataUtils.ERROR_READING_FAILED: + case DataUtils.ERROR_WRITING_FAILED: + throw DbException.get(ErrorCode.IO_EXCEPTION_1, e, fileName); + default: + throw DbException.get(ErrorCode.GENERAL_ERROR_1, e, e.getMessage()); + } + } + + /** + * 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; + } + + public TransactionStore getTransactionStore() { + return transactionStore; + } + + /** + * Get MVTable by table name. + * + * @param tableName table name + * @return MVTable + */ + public MVTable getTable(String tableName) { + return tableMap.get(tableName); + } + + /** + * Create a table. + * + * @param data CreateTableData + * @return table created + */ + public MVTable createTable(CreateTableData data) { + try { + MVTable table = new MVTable(data, this); + tableMap.put(table.getMapName(), table); + return table; + } catch (MVStoreException e) { + throw convertMVStoreException(e); + } + } + + /** + * Remove a table. + * + * @param table the table + */ + public void removeTable(MVTable table) { + try { + tableMap.remove(table.getMapName()); + } catch (MVStoreException e) { + throw convertMVStoreException(e); + } + } + + /** + * Store all pending changes. + */ + public void flush() { + FileStore s = mvStore.getFileStore(); + if (s == null || s.isReadOnly()) { + return; + } + if (!mvStore.compact(50, 4 * 1024 * 1024)) { + mvStore.commit(); + } + } + + /** + * Close the store, without persisting changes. + */ + public void closeImmediately() { + if (!mvStore.isClosed()) { + mvStore.closeImmediately(); + } + } + + /** + * Remove all temporary maps. + * + * @param objectIds the ids of the objects to keep + */ + public void removeTemporaryMaps(BitSet objectIds) { + for (String mapName : mvStore.getMapNames()) { + if (mapName.startsWith("temp.")) { + mvStore.removeMap(mapName); + } else if (mapName.startsWith("table.") || mapName.startsWith("index.")) { + int id = StringUtils.parseUInt31(mapName, mapName.indexOf('.') + 1, mapName.length()); + if (!objectIds.get(id)) { + mvStore.removeMap(mapName); + } + } + } + } + + /** + * Get the name of the next available temporary map. + * + * @return the map name + */ + public synchronized String nextTemporaryMapName() { + return "temp." + temporaryMapId++; + } + + /** + * Prepare a transaction. + * + * @param session the session + * @param transactionName the transaction name (may be null) + */ + public void prepareCommit(SessionLocal session, String transactionName) { + Transaction t = session.getTransaction(); + t.setName(transactionName); + t.prepare(); + mvStore.commit(); + } + + public ArrayList getInDoubtTransactions() { + List list = transactionStore.getOpenTransactions(); + ArrayList result = Utils.newSmallArrayList(); + for (Transaction t : list) { + if (t.getStatus() == Transaction.STATUS_PREPARED) { + result.add(new MVInDoubtTransaction(mvStore, t)); + } + } + return result; + } + + /** + * Set the maximum memory to be used by the cache. + * + * @param kb the maximum size in KB + */ + public void setCacheSize(int kb) { + mvStore.setCacheSize(Math.max(1, kb / 1024)); + } + + public InputStream getInputStream() { + FileChannel fc = mvStore.getFileStore().getEncryptedFile(); + if (fc == null) { + fc = mvStore.getFileStore().getFile(); + } + return new FileChannelInputStream(fc, false); + } + + /** + * Force the changes to disk. + */ + public void sync() { + flush(); + mvStore.sync(); + } + + /** + * Compact the database file, that is, compact blocks that have a low + * fill rate, and move chunks next to each other. This will typically + * shrink the database file. Changes are flushed to the file, and old + * chunks are overwritten. + * + * @param maxCompactTime the maximum time in milliseconds to compact + */ + @SuppressWarnings("unused") + public void compactFile(int maxCompactTime) { + mvStore.compactFile(maxCompactTime); + } + + /** + * Close the store. Pending changes are persisted. + * If time is allocated for housekeeping, chunks with a low + * fill rate are compacted, and some chunks are put next to each other. + * If time is unlimited then full compaction is performed, which uses + * different algorithm - opens alternative temp store and writes all live + * data there, then replaces this store with a new one. + * + * @param allowedCompactionTime time (in milliseconds) alloted for file + * compaction activity, 0 means no compaction, + * -1 means unlimited time (full compaction) + */ + public void close(int allowedCompactionTime) { + try { + FileStore fileStore = mvStore.getFileStore(); + if (!mvStore.isClosed() && fileStore != null) { + boolean compactFully = allowedCompactionTime == -1; + if (fileStore.isReadOnly()) { + compactFully = false; + } else { + transactionStore.close(); + } + if (compactFully) { + allowedCompactionTime = 0; + } + + 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); + } + } + } catch (MVStoreException e) { + int errorCode = e.getErrorCode(); + if (errorCode == DataUtils.ERROR_WRITING_FAILED) { + // disk full - ok + } else if (errorCode == DataUtils.ERROR_FILE_CORRUPT) { + // wrong encryption key - ok + } + mvStore.closeImmediately(); + throw DbException.get(ErrorCode.IO_EXCEPTION_1, e, "Closing"); + } + } + + /** + * Start collecting statistics. + */ + public void statisticsStart() { + FileStore fs = mvStore.getFileStore(); + statisticsStart = fs == null ? 0 : fs.getReadCount(); + } + + /** + * Stop collecting statistics. + * + * @return the statistics + */ + public Map statisticsEnd() { + HashMap map = new HashMap<>(); + FileStore fs = mvStore.getFileStore(); + int reads = fs == null ? 0 : (int) (fs.getReadCount() - statisticsStart); + map.put("reads", reads); + return map; + } + +} diff --git a/h2/src/main/org/h2/mvstore/db/ValueDataType.java b/h2/src/main/org/h2/mvstore/db/ValueDataType.java new file mode 100644 index 0000000..1d4b449 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/db/ValueDataType.java @@ -0,0 +1,897 @@ +/* + * 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.mvstore.db; + +import static org.h2.mvstore.DataUtils.readString; +import static org.h2.mvstore.DataUtils.readVarInt; +import static org.h2.mvstore.DataUtils.readVarLong; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Map.Entry; +import org.h2.api.ErrorCode; +import org.h2.api.IntervalQualifier; +import org.h2.engine.CastDataProvider; +import org.h2.engine.Database; +import org.h2.message.DbException; +import org.h2.mode.DefaultNullOrdering; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.WriteBuffer; +import org.h2.mvstore.type.BasicDataType; +import org.h2.mvstore.type.DataType; +import org.h2.mvstore.type.MetaType; +import org.h2.mvstore.type.StatefulDataType; +import org.h2.result.RowFactory; +import org.h2.result.SearchRow; +import org.h2.result.SortOrder; +import org.h2.store.DataHandler; +import org.h2.util.DateTimeUtils; +import org.h2.util.Utils; +import org.h2.value.CompareMode; +import org.h2.value.ExtTypeInfoEnum; +import org.h2.value.ExtTypeInfoRow; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueArray; +import org.h2.value.ValueBigint; +import org.h2.value.ValueBinary; +import org.h2.value.ValueBlob; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueChar; +import org.h2.value.ValueClob; +import org.h2.value.ValueCollectionBase; +import org.h2.value.ValueDate; +import org.h2.value.ValueDecfloat; +import org.h2.value.ValueDouble; +import org.h2.value.ValueGeometry; +import org.h2.value.ValueInteger; +import org.h2.value.ValueInterval; +import org.h2.value.ValueJavaObject; +import org.h2.value.ValueJson; +import org.h2.value.ValueNull; +import org.h2.value.ValueNumeric; +import org.h2.value.ValueReal; +import org.h2.value.ValueRow; +import org.h2.value.ValueSmallint; +import org.h2.value.ValueTime; +import org.h2.value.ValueTimeTimeZone; +import org.h2.value.ValueTimestamp; +import org.h2.value.ValueTimestampTimeZone; +import org.h2.value.ValueTinyint; +import org.h2.value.ValueUuid; +import org.h2.value.ValueVarbinary; +import org.h2.value.ValueVarchar; +import org.h2.value.ValueVarcharIgnoreCase; +import org.h2.value.lob.LobData; +import org.h2.value.lob.LobDataDatabase; +import org.h2.value.lob.LobDataInMemory; + +/** + * A row type. + */ +public final class ValueDataType extends BasicDataType implements StatefulDataType { + + private static final byte NULL = 0; + private static final byte TINYINT = 2; + private static final byte SMALLINT = 3; + private static final byte INTEGER = 4; + private static final byte BIGINT = 5; + private static final byte NUMERIC = 6; + private static final byte DOUBLE = 7; + private static final byte REAL = 8; + private static final byte TIME = 9; + private static final byte DATE = 10; + private static final byte TIMESTAMP = 11; + private static final byte VARBINARY = 12; + private static final byte VARCHAR = 13; + private static final byte VARCHAR_IGNORECASE = 14; + private static final byte BLOB = 15; + private static final byte CLOB = 16; + private static final byte ARRAY = 17; + private static final byte JAVA_OBJECT = 19; + private static final byte UUID = 20; + private static final byte CHAR = 21; + private static final byte GEOMETRY = 22; + private static final byte TIMESTAMP_TZ_OLD = 24; + private static final byte ENUM = 25; + private static final byte INTERVAL = 26; + private static final byte ROW = 27; + private static final byte INT_0_15 = 32; + private static final byte BIGINT_0_7 = 48; + private static final byte NUMERIC_0_1 = 56; + private static final byte NUMERIC_SMALL_0 = 58; + private static final byte NUMERIC_SMALL = 59; + private static final byte DOUBLE_0_1 = 60; + private static final byte REAL_0_1 = 62; + private static final byte BOOLEAN_FALSE = 64; + private static final byte BOOLEAN_TRUE = 65; + private static final byte INT_NEG = 66; + private static final byte BIGINT_NEG = 67; + private static final byte VARCHAR_0_31 = 68; + private static final int VARBINARY_0_31 = 100; + // 132 was used for SPATIAL_KEY_2D + // 133 was used for CUSTOM_DATA_TYPE + private static final int JSON = 134; + private static final int TIMESTAMP_TZ = 135; + private static final int TIME_TZ = 136; + private static final int BINARY = 137; + private static final int DECFLOAT = 138; + + final DataHandler handler; + final CastDataProvider provider; + final CompareMode compareMode; + final int[] sortTypes; + private RowFactory rowFactory; + + public ValueDataType() { + this(null, CompareMode.getInstance(null, 0), null, null); + } + + public ValueDataType(Database database, int[] sortTypes) { + this(database, database.getCompareMode(), database, sortTypes); + } + + public ValueDataType(CastDataProvider provider, CompareMode compareMode, DataHandler handler, int[] sortTypes) { + this.provider = provider; + this.compareMode = compareMode; + this.handler = handler; + this.sortTypes = sortTypes; + } + + public RowFactory getRowFactory() { + return rowFactory; + } + + public void setRowFactory(RowFactory rowFactory) { + this.rowFactory = rowFactory; + } + + @Override + public Value[] createStorage(int size) { + return new Value[size]; + } + + @Override + public int compare(Value a, Value b) { + if (a == b) { + return 0; + } + if (a instanceof SearchRow && b instanceof SearchRow) { + return compare((SearchRow)a, (SearchRow)b); + } else if (a instanceof ValueCollectionBase && b instanceof ValueCollectionBase) { + Value[] ax = ((ValueCollectionBase) a).getList(); + Value[] bx = ((ValueCollectionBase) b).getList(); + int al = ax.length; + int bl = bx.length; + int len = Math.min(al, bl); + for (int i = 0; i < len; i++) { + int sortType = sortTypes == null ? SortOrder.ASCENDING : sortTypes[i]; + Value one = ax[i]; + Value two = bx[i]; + if (one == null || two == null) { + return compareValues(ax[len - 1], bx[len - 1], SortOrder.ASCENDING); + } + + int comp = compareValues(one, two, sortType); + if (comp != 0) { + return comp; + } + } + if (len < al) { + return -1; + } else if (len < bl) { + return 1; + } + return 0; + } + return compareValues(a, b, SortOrder.ASCENDING); + } + + private int compare(SearchRow a, SearchRow b) { + if (a == b) { + return 0; + } + int[] indexes = rowFactory.getIndexes(); + if (indexes == null) { + int len = a.getColumnCount(); + assert len == b.getColumnCount() : len + " != " + b.getColumnCount(); + for (int i = 0; i < len; i++) { + int comp = compareValues(a.getValue(i), b.getValue(i), sortTypes[i]); + if (comp != 0) { + return comp; + } + } + return 0; + } else { + assert sortTypes.length == indexes.length; + for (int i = 0; i < indexes.length; i++) { + int index = indexes[i]; + Value v1 = a.getValue(index); + Value v2 = b.getValue(index); + if (v1 == null || v2 == null) { + // can't compare further + break; + } + int comp = compareValues(a.getValue(index), b.getValue(index), sortTypes[i]); + if (comp != 0) { + return comp; + } + } + long aKey = a.getKey(); + long bKey = b.getKey(); + return aKey == SearchRow.MATCH_ALL_ROW_KEY || bKey == SearchRow.MATCH_ALL_ROW_KEY ? + 0 : Long.compare(aKey, bKey); + } + } + + /** + * Compares the specified values. + * + * @param a the first value + * @param b the second value + * @param sortType the sorting type + * @return 0 if equal, -1 if first value is smaller for ascending or larger + * for descending sort type, 1 otherwise + */ + public int compareValues(Value a, Value b, int sortType) { + if (a == b) { + return 0; + } + boolean aNull = a == ValueNull.INSTANCE; + if (aNull || b == ValueNull.INSTANCE) { + /* + * Indexes with nullable values should have explicit null ordering, + * so default should not matter. + */ + return DefaultNullOrdering.LOW.compareNull(aNull, sortType); + } + + int comp = a.compareTo(b, provider, compareMode); + + if ((sortType & SortOrder.DESCENDING) != 0) { + comp = -comp; + } + return comp; + } + + @Override + public int getMemory(Value v) { + return v == null ? 0 : v.getMemory(); + } + + @Override + public Value read(ByteBuffer buff) { + return readValue(buff, null); + } + + @Override + public void write(WriteBuffer buff, Value v) { + if (v == ValueNull.INSTANCE) { + buff.put((byte) 0); + return; + } + int type = v.getValueType(); + switch (type) { + case Value.BOOLEAN: + buff.put(v.getBoolean() ? BOOLEAN_TRUE : BOOLEAN_FALSE); + break; + case Value.TINYINT: + buff.put(TINYINT).put(v.getByte()); + break; + case Value.SMALLINT: + buff.put(SMALLINT).putShort(v.getShort()); + break; + case Value.ENUM: + case Value.INTEGER: { + int x = v.getInt(); + if (x < 0) { + buff.put(INT_NEG).putVarInt(-x); + } else if (x < 16) { + buff.put((byte) (INT_0_15 + x)); + } else { + buff.put(type == Value.INTEGER ? INTEGER : ENUM).putVarInt(x); + } + break; + } + case Value.BIGINT: + writeLong(buff, v.getLong()); + break; + case Value.NUMERIC: { + BigDecimal x = v.getBigDecimal(); + if (BigDecimal.ZERO.equals(x)) { + buff.put(NUMERIC_0_1); + } else if (BigDecimal.ONE.equals(x)) { + buff.put((byte) (NUMERIC_0_1 + 1)); + } else { + int scale = x.scale(); + BigInteger b = x.unscaledValue(); + int bits = b.bitLength(); + if (bits <= 63) { + if (scale == 0) { + buff.put(NUMERIC_SMALL_0). + putVarLong(b.longValue()); + } else { + buff.put(NUMERIC_SMALL). + putVarInt(scale). + putVarLong(b.longValue()); + } + } else { + byte[] bytes = b.toByteArray(); + buff.put(NUMERIC). + putVarInt(scale). + putVarInt(bytes.length). + put(bytes); + } + } + break; + } + case Value.DECFLOAT: { + ValueDecfloat d = (ValueDecfloat) v; + buff.put((byte) DECFLOAT); + if (d.isFinite()) { + BigDecimal x = d.getBigDecimal(); + byte[] bytes = x.unscaledValue().toByteArray(); + buff.putVarInt(x.scale()). + putVarInt(bytes.length). + put(bytes); + } else { + int c; + if (d == ValueDecfloat.NEGATIVE_INFINITY) { + c = -3; + } else if (d == ValueDecfloat.POSITIVE_INFINITY) { + c = -2; + } else { + c = -1; + } + buff.putVarInt(0).putVarInt(c); + } + break; + } + case Value.TIME: + writeTimestampTime(buff.put(TIME), ((ValueTime) v).getNanos()); + break; + case Value.TIME_TZ: { + ValueTimeTimeZone t = (ValueTimeTimeZone) v; + long nanosOfDay = t.getNanos(); + buff.put((byte) TIME_TZ). + putVarInt((int) (nanosOfDay / DateTimeUtils.NANOS_PER_SECOND)). + putVarInt((int) (nanosOfDay % DateTimeUtils.NANOS_PER_SECOND)); + writeTimeZone(buff, t.getTimeZoneOffsetSeconds()); + break; + } + case Value.DATE: + buff.put(DATE).putVarLong(((ValueDate) v).getDateValue()); + break; + case Value.TIMESTAMP: { + ValueTimestamp ts = (ValueTimestamp) v; + buff.put(TIMESTAMP).putVarLong(ts.getDateValue()); + writeTimestampTime(buff, ts.getTimeNanos()); + break; + } + case Value.TIMESTAMP_TZ: { + ValueTimestampTimeZone ts = (ValueTimestampTimeZone) v; + buff.put((byte) TIMESTAMP_TZ).putVarLong(ts.getDateValue()); + writeTimestampTime(buff, ts.getTimeNanos()); + writeTimeZone(buff, ts.getTimeZoneOffsetSeconds()); + break; + } + case Value.JAVA_OBJECT: + writeBinary(JAVA_OBJECT, buff, v); + break; + case Value.VARBINARY: { + byte[] b = v.getBytesNoCopy(); + int len = b.length; + if (len < 32) { + buff.put((byte) (VARBINARY_0_31 + len)).put(b); + } else { + buff.put(VARBINARY).putVarInt(len).put(b); + } + break; + } + case Value.BINARY: + writeBinary((byte) BINARY, buff, v); + break; + case Value.UUID: { + ValueUuid uuid = (ValueUuid) v; + buff.put(UUID). + putLong(uuid.getHigh()). + putLong(uuid.getLow()); + break; + } + case Value.VARCHAR: { + String s = v.getString(); + int len = s.length(); + if (len < 32) { + buff.put((byte) (VARCHAR_0_31 + len)).putStringData(s, len); + } else { + writeString(buff.put(VARCHAR), s); + } + break; + } + case Value.VARCHAR_IGNORECASE: + writeString(buff.put(VARCHAR_IGNORECASE), v.getString()); + break; + case Value.CHAR: + writeString(buff.put(CHAR), v.getString()); + break; + case Value.DOUBLE: { + double x = v.getDouble(); + if (x == 1.0d) { + buff.put((byte) (DOUBLE_0_1 + 1)); + } else { + long d = Double.doubleToLongBits(x); + if (d == ValueDouble.ZERO_BITS) { + buff.put(DOUBLE_0_1); + } else { + buff.put(DOUBLE). + putVarLong(Long.reverse(d)); + } + } + break; + } + case Value.REAL: { + float x = v.getFloat(); + if (x == 1.0f) { + buff.put((byte) (REAL_0_1 + 1)); + } else { + int f = Float.floatToIntBits(x); + if (f == ValueReal.ZERO_BITS) { + buff.put(REAL_0_1); + } else { + buff.put(REAL). + putVarInt(Integer.reverse(f)); + } + } + break; + } + case Value.BLOB: { + buff.put(BLOB); + ValueBlob lob = (ValueBlob) v; + LobData lobData = lob.getLobData(); + if (lobData instanceof LobDataDatabase) { + LobDataDatabase lobDataDatabase = (LobDataDatabase) lobData; + buff.putVarInt(-3). + putVarInt(lobDataDatabase.getTableId()). + putVarLong(lobDataDatabase.getLobId()). + putVarLong(lob.octetLength()); + } else { + byte[] small = ((LobDataInMemory) lobData).getSmall(); + buff.putVarInt(small.length). + put(small); + } + break; + } + case Value.CLOB: { + buff.put(CLOB); + ValueClob lob = (ValueClob) v; + LobData lobData = lob.getLobData(); + if (lobData instanceof LobDataDatabase) { + LobDataDatabase lobDataDatabase = (LobDataDatabase) lobData; + buff.putVarInt(-3). + putVarInt(lobDataDatabase.getTableId()). + putVarLong(lobDataDatabase.getLobId()). + putVarLong(lob.octetLength()). + putVarLong(lob.charLength()); + } else { + byte[] small = ((LobDataInMemory) lobData).getSmall(); + buff.putVarInt(small.length). + put(small). + putVarLong(lob.charLength()); + } + break; + } + case Value.ARRAY: + case Value.ROW: { + Value[] list = ((ValueCollectionBase) v).getList(); + buff.put(type == Value.ARRAY ? ARRAY : ROW) + .putVarInt(list.length); + for (Value x : list) { + write(buff, x); + } + break; + } + case Value.GEOMETRY: + writeBinary(GEOMETRY, buff, v); + break; + case Value.INTERVAL_YEAR: + case Value.INTERVAL_MONTH: + case Value.INTERVAL_DAY: + case Value.INTERVAL_HOUR: + case Value.INTERVAL_MINUTE: { + ValueInterval interval = (ValueInterval) v; + int ordinal = type - Value.INTERVAL_YEAR; + if (interval.isNegative()) { + ordinal = ~ordinal; + } + buff.put(INTERVAL). + put((byte) ordinal). + putVarLong(interval.getLeading()); + break; + } + case Value.INTERVAL_SECOND: + case Value.INTERVAL_YEAR_TO_MONTH: + case Value.INTERVAL_DAY_TO_HOUR: + case Value.INTERVAL_DAY_TO_MINUTE: + case Value.INTERVAL_DAY_TO_SECOND: + case Value.INTERVAL_HOUR_TO_MINUTE: + case Value.INTERVAL_HOUR_TO_SECOND: + case Value.INTERVAL_MINUTE_TO_SECOND: { + ValueInterval interval = (ValueInterval) v; + int ordinal = type - Value.INTERVAL_YEAR; + if (interval.isNegative()) { + ordinal = ~ordinal; + } + buff.put(INTERVAL). + put((byte) ordinal). + putVarLong(interval.getLeading()). + putVarLong(interval.getRemaining()); + break; + } + case Value.JSON: + writeBinary((byte) JSON, buff, v); + break; + default: + throw DbException.getInternalError("type=" + v.getValueType()); + } + } + + private static void writeBinary(byte type, WriteBuffer buff, Value v) { + byte[] b = v.getBytesNoCopy(); + buff.put(type).putVarInt(b.length).put(b); + } + + /** + * Writes a long. + * + * @param buff the target buffer + * @param x the long value + */ + public static void writeLong(WriteBuffer buff, long x) { + if (x < 0) { + buff.put(BIGINT_NEG).putVarLong(-x); + } else if (x < 8) { + buff.put((byte) (BIGINT_0_7 + x)); + } else { + buff.put(BIGINT).putVarLong(x); + } + } + + private static void writeString(WriteBuffer buff, String s) { + int len = s.length(); + buff.putVarInt(len).putStringData(s, len); + } + + private static void writeTimestampTime(WriteBuffer buff, long nanos) { + long millis = nanos / 1_000_000L; + buff.putVarLong(millis).putVarInt((int) (nanos - millis * 1_000_000L)); + } + + private static void writeTimeZone(WriteBuffer buff, int timeZoneOffset) { + // Valid JSR-310 offsets are -64,800..64,800 + // Use 1 byte for common time zones (including +8:45 etc.) + if (timeZoneOffset % 900 == 0) { + // -72..72 + buff.put((byte) (timeZoneOffset / 900)); + } else if (timeZoneOffset > 0) { + buff.put(Byte.MAX_VALUE).putVarInt(timeZoneOffset); + } else { + buff.put(Byte.MIN_VALUE).putVarInt(-timeZoneOffset); + } + } + + /** + * Read a value. + * + * @param buff the source buffer + * @param columnType the data type of value, or {@code null} + * @return the value + */ + Value readValue(ByteBuffer buff, TypeInfo columnType) { + int type = buff.get() & 255; + switch (type) { + case NULL: + return ValueNull.INSTANCE; + case BOOLEAN_TRUE: + return ValueBoolean.TRUE; + case BOOLEAN_FALSE: + return ValueBoolean.FALSE; + case INT_NEG: + return ValueInteger.get(-readVarInt(buff)); + case INTEGER: + return ValueInteger.get(readVarInt(buff)); + case BIGINT_NEG: + return ValueBigint.get(-readVarLong(buff)); + case BIGINT: + return ValueBigint.get(readVarLong(buff)); + case TINYINT: + return ValueTinyint.get(buff.get()); + case SMALLINT: + return ValueSmallint.get(buff.getShort()); + case NUMERIC_0_1: + return ValueNumeric.ZERO; + case NUMERIC_0_1 + 1: + return ValueNumeric.ONE; + case NUMERIC_SMALL_0: + return ValueNumeric.get(BigDecimal.valueOf(readVarLong(buff))); + case NUMERIC_SMALL: { + int scale = readVarInt(buff); + return ValueNumeric.get(BigDecimal.valueOf(readVarLong(buff), scale)); + } + case NUMERIC: { + int scale = readVarInt(buff); + return ValueNumeric.get(new BigDecimal(new BigInteger(readVarBytes(buff)), scale)); + } + case DECFLOAT: { + int scale = readVarInt(buff), len = readVarInt(buff); + switch (len) { + case -3: + return ValueDecfloat.NEGATIVE_INFINITY; + case -2: + return ValueDecfloat.POSITIVE_INFINITY; + case -1: + return ValueDecfloat.NAN; + default: + byte[] b = Utils.newBytes(len); + buff.get(b, 0, len); + return ValueDecfloat.get(new BigDecimal(new BigInteger(b), scale)); + } + } + case DATE: + return ValueDate.fromDateValue(readVarLong(buff)); + case TIME: + return ValueTime.fromNanos(readTimestampTime(buff)); + case TIME_TZ: + return ValueTimeTimeZone.fromNanos(readVarInt(buff) * DateTimeUtils.NANOS_PER_SECOND + readVarInt(buff), + readTimeZone(buff)); + case TIMESTAMP: + return ValueTimestamp.fromDateValueAndNanos(readVarLong(buff), readTimestampTime(buff)); + case TIMESTAMP_TZ_OLD: + return ValueTimestampTimeZone.fromDateValueAndNanos(readVarLong(buff), readTimestampTime(buff), + readVarInt(buff) * 60); + case TIMESTAMP_TZ: + return ValueTimestampTimeZone.fromDateValueAndNanos(readVarLong(buff), readTimestampTime(buff), + readTimeZone(buff)); + case VARBINARY: + return ValueVarbinary.getNoCopy(readVarBytes(buff)); + case BINARY: + return ValueBinary.getNoCopy(readVarBytes(buff)); + case JAVA_OBJECT: + return ValueJavaObject.getNoCopy(readVarBytes(buff)); + case UUID: + return ValueUuid.get(buff.getLong(), buff.getLong()); + case VARCHAR: + return ValueVarchar.get(readString(buff)); + case VARCHAR_IGNORECASE: + return ValueVarcharIgnoreCase.get(readString(buff)); + case CHAR: + return ValueChar.get(readString(buff)); + case ENUM: { + int ordinal = readVarInt(buff); + if (columnType != null) { + return ((ExtTypeInfoEnum) columnType.getExtTypeInfo()).getValue(ordinal, provider); + } + return ValueInteger.get(ordinal); + } + case INTERVAL: { + int ordinal = buff.get(); + boolean negative = ordinal < 0; + if (negative) { + ordinal = ~ordinal; + } + return ValueInterval.from(IntervalQualifier.valueOf(ordinal), negative, readVarLong(buff), + ordinal < 5 ? 0 : readVarLong(buff)); + } + case REAL_0_1: + return ValueReal.ZERO; + case REAL_0_1 + 1: + return ValueReal.ONE; + case DOUBLE_0_1: + return ValueDouble.ZERO; + case DOUBLE_0_1 + 1: + return ValueDouble.ONE; + case DOUBLE: + return ValueDouble.get(Double.longBitsToDouble(Long.reverse(readVarLong(buff)))); + case REAL: + return ValueReal.get(Float.intBitsToFloat(Integer.reverse(readVarInt(buff)))); + case BLOB: { + int smallLen = readVarInt(buff); + if (smallLen >= 0) { + byte[] small = Utils.newBytes(smallLen); + buff.get(small, 0, smallLen); + return ValueBlob.createSmall(small); + } else if (smallLen == -3) { + return new ValueBlob(readLobDataDatabase(buff), readVarLong(buff)); + } else { + throw DbException.get(ErrorCode.FILE_CORRUPTED_1, "lob type: " + smallLen); + } + } + case CLOB: { + int smallLen = readVarInt(buff); + if (smallLen >= 0) { + byte[] small = Utils.newBytes(smallLen); + buff.get(small, 0, smallLen); + return ValueClob.createSmall(small, readVarLong(buff)); + } else if (smallLen == -3) { + return new ValueClob(readLobDataDatabase(buff), readVarLong(buff), readVarLong(buff)); + } else { + throw DbException.get(ErrorCode.FILE_CORRUPTED_1, "lob type: " + smallLen); + } + } + case ARRAY: { + if (columnType != null) { + TypeInfo elementType = (TypeInfo) columnType.getExtTypeInfo(); + return ValueArray.get(elementType, readArrayElements(buff, elementType), provider); + } + return ValueArray.get(readArrayElements(buff, null), provider); + } + case ROW: { + int len = readVarInt(buff); + Value[] list = new Value[len]; + if (columnType != null) { + ExtTypeInfoRow extTypeInfoRow = (ExtTypeInfoRow) columnType.getExtTypeInfo(); + Iterator> fields = extTypeInfoRow.getFields().iterator(); + for (int i = 0; i < len; i++) { + list[i] = readValue(buff, fields.next().getValue()); + } + return ValueRow.get(columnType, list); + } + TypeInfo[] columnTypes = rowFactory.getColumnTypes(); + for (int i = 0; i < len; i++) { + list[i] = readValue(buff, columnTypes[i]); + } + return ValueRow.get(list); + } + case GEOMETRY: + return ValueGeometry.get(readVarBytes(buff)); + case JSON: + return ValueJson.getInternal(readVarBytes(buff)); + default: + if (type >= INT_0_15 && type < INT_0_15 + 16) { + int i = type - INT_0_15; + if (columnType != null && columnType.getValueType() == Value.ENUM) { + return ((ExtTypeInfoEnum) columnType.getExtTypeInfo()).getValue(i, provider); + } + return ValueInteger.get(i); + } else if (type >= BIGINT_0_7 && type < BIGINT_0_7 + 8) { + return ValueBigint.get(type - BIGINT_0_7); + } else if (type >= VARBINARY_0_31 && type < VARBINARY_0_31 + 32) { + int len = type - VARBINARY_0_31; + byte[] b = Utils.newBytes(len); + buff.get(b, 0, len); + return ValueVarbinary.getNoCopy(b); + } else if (type >= VARCHAR_0_31 && type < VARCHAR_0_31 + 32) { + return ValueVarchar.get(readString(buff, type - VARCHAR_0_31)); + } + throw DbException.get(ErrorCode.FILE_CORRUPTED_1, "type: " + type); + } + } + + private LobDataDatabase readLobDataDatabase(ByteBuffer buff) { + int tableId = readVarInt(buff); + long lobId = readVarLong(buff); + LobDataDatabase lobData = new LobDataDatabase(handler, tableId, lobId); + return lobData; + } + + private Value[] readArrayElements(ByteBuffer buff, TypeInfo elementType) { + int len = readVarInt(buff); + Value[] list = new Value[len]; + for (int i = 0; i < len; i++) { + list[i] = readValue(buff, elementType); + } + return list; + } + + private static byte[] readVarBytes(ByteBuffer buff) { + int len = readVarInt(buff); + byte[] b = Utils.newBytes(len); + buff.get(b, 0, len); + return b; + } + + private static long readTimestampTime(ByteBuffer buff) { + return readVarLong(buff) * 1_000_000L + readVarInt(buff); + } + + private static int readTimeZone(ByteBuffer buff) { + byte b = buff.get(); + if (b == Byte.MAX_VALUE) { + return readVarInt(buff); + } else if (b == Byte.MIN_VALUE) { + return -readVarInt(buff); + } else { + return b * 900; + } + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } else if (!(obj instanceof ValueDataType)) { + return false; + } + ValueDataType v = (ValueDataType) obj; + if (!compareMode.equals(v.compareMode)) { + return false; + } + int[] indexes = rowFactory == null ? null : rowFactory.getIndexes(); + int[] indexes2 = v.rowFactory == null ? null : v.rowFactory.getIndexes(); + return Arrays.equals(sortTypes, v.sortTypes) + && Arrays.equals(indexes, indexes2); + } + + @Override + public int hashCode() { + int[] indexes = rowFactory == null ? null : rowFactory.getIndexes(); + return super.hashCode() ^ Arrays.hashCode(indexes) + ^ compareMode.hashCode() ^ Arrays.hashCode(sortTypes); + } + + @Override + public void save(WriteBuffer buff, MetaType metaType) { + writeIntArray(buff, sortTypes); + int columnCount = rowFactory == null ? 0 : rowFactory.getColumnCount(); + buff.putVarInt(columnCount); + int[] indexes = rowFactory == null ? null : rowFactory.getIndexes(); + writeIntArray(buff, indexes); + buff.put(rowFactory == null || rowFactory.getRowDataType().isStoreKeys() ? (byte) 1 : (byte) 0); + } + + private static void writeIntArray(WriteBuffer buff, int[] array) { + if(array == null) { + buff.putVarInt(0); + } else { + buff.putVarInt(array.length + 1); + for (int i : array) { + buff.putVarInt(i); + } + } + } + + @Override + public Factory getFactory() { + return FACTORY; + } + + private static final Factory FACTORY = new Factory(); + + public static final class Factory implements StatefulDataType.Factory { + + @Override + public DataType create(ByteBuffer buff, MetaType metaType, Database database) { + int[] sortTypes = readIntArray(buff); + int columnCount = DataUtils.readVarInt(buff); + int[] indexes = readIntArray(buff); + boolean storeKeys = buff.get() != 0; + CompareMode compareMode = database == null ? CompareMode.getInstance(null, 0) : database.getCompareMode(); + if (database == null) { + return new ValueDataType(); + } else if (sortTypes == null) { + return new ValueDataType(database, null); + } + RowFactory rowFactory = RowFactory.getDefaultRowFactory().createRowFactory(database, compareMode, database, + sortTypes, indexes, null, columnCount, storeKeys); + return rowFactory.getRowDataType(); + } + + private static int[] readIntArray(ByteBuffer buff) { + int len = DataUtils.readVarInt(buff) - 1; + if(len < 0) { + return null; + } + int[] res = new int[len]; + for (int i = 0; i < res.length; i++) { + res[i] = DataUtils.readVarInt(buff); + } + return res; + } + } + +} diff --git a/h2/src/main/org/h2/mvstore/db/package.html b/h2/src/main/org/h2/mvstore/db/package.html new file mode 100644 index 0000000..efa1e98 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/db/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Helper classes to use the MVStore in the H2 database. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/mvstore/package.html b/h2/src/main/org/h2/mvstore/package.html new file mode 100644 index 0000000..9ebeb43 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +A persistent storage for tree maps. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/mvstore/rtree/DefaultSpatial.java b/h2/src/main/org/h2/mvstore/rtree/DefaultSpatial.java new file mode 100644 index 0000000..e8b7a20 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/rtree/DefaultSpatial.java @@ -0,0 +1,75 @@ +/* + * 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.mvstore.rtree; + +import java.util.Arrays; + +/** + * Class BasicSpatialImpl. + * + * @author Andrei Tokar + */ +final class DefaultSpatial implements Spatial +{ + private final long id; + private final float[] minMax; + + /** + * Create a new key. + * + * @param id the id + * @param minMax min x, max x, min y, max y, and so on + */ + public DefaultSpatial(long id, float... minMax) { + this.id = id; + this.minMax = minMax; + } + + private DefaultSpatial(long id, DefaultSpatial other) { + this.id = id; + this.minMax = other.minMax.clone(); + } + + @Override + public float min(int dim) { + return minMax[dim + dim]; + } + + @Override + public void setMin(int dim, float x) { + minMax[dim + dim] = x; + } + + @Override + public float max(int dim) { + return minMax[dim + dim + 1]; + } + + @Override + public void setMax(int dim, float x) { + minMax[dim + dim + 1] = x; + } + + @Override + public Spatial clone(long id) { + return new DefaultSpatial(id, this); + } + + @Override + public long getId() { + return id; + } + + @Override + public boolean isNull() { + return minMax.length == 0; + } + + @Override + public boolean equalsIgnoringId(Spatial o) { + return Arrays.equals(minMax, ((DefaultSpatial)o).minMax); + } +} diff --git a/h2/src/main/org/h2/mvstore/rtree/MVRTreeMap.java b/h2/src/main/org/h2/mvstore/rtree/MVRTreeMap.java new file mode 100644 index 0000000..b856e72 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/rtree/MVRTreeMap.java @@ -0,0 +1,632 @@ +/* + * 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.mvstore.rtree; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +import org.h2.mvstore.CursorPos; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.Page; +import org.h2.mvstore.RootReference; +import org.h2.mvstore.type.DataType; + +/** + * An r-tree implementation. It supports both the linear and the quadratic split + * algorithm. + * + * @param the value class + */ +public final class MVRTreeMap extends MVMap { + + /** + * The spatial key type. + */ + private final SpatialDataType keyType; + + private boolean quadraticSplit; + + public MVRTreeMap(Map config, SpatialDataType keyType, DataType valueType) { + super(config, keyType, valueType); + this.keyType = keyType; + quadraticSplit = Boolean.parseBoolean(String.valueOf(config.get("quadraticSplit"))); + } + + private MVRTreeMap(MVRTreeMap source) { + super(source); + this.keyType = source.keyType; + this.quadraticSplit = source.quadraticSplit; + } + + @Override + public MVRTreeMap cloneIt() { + return new MVRTreeMap<>(this); + } + + /** + * Iterate over all keys that have an intersection with the given rectangle. + * + * @param x the rectangle + * @return the iterator + */ + public RTreeCursor findIntersectingKeys(Spatial x) { + return new IntersectsRTreeCursor<>(getRootPage(), x, keyType); + } + + /** + * Iterate over all keys that are fully contained within the given + * rectangle. + * + * @param x the rectangle + * @return the iterator + */ + public RTreeCursor findContainedKeys(Spatial x) { + return new ContainsRTreeCursor<>(getRootPage(), x, keyType); + } + + private boolean contains(Page p, int index, Object key) { + return keyType.contains(p.getKey(index), key); + } + + /** + * Get the object for the given key. An exact match is required. + * + * @param p the page + * @param key the key + * @return the value, or null if not found + */ + @Override + public V get(Page p, Spatial key) { + int keyCount = p.getKeyCount(); + if (!p.isLeaf()) { + for (int i = 0; i < keyCount; i++) { + if (contains(p, i, key)) { + V o = get(p.getChildPage(i), key); + if (o != null) { + return o; + } + } + } + } else { + for (int i = 0; i < keyCount; i++) { + if (keyType.equals(p.getKey(i), key)) { + return p.getValue(i); + } + } + } + return null; + } + + /** + * Remove a key-value pair, if the key exists. + * + * @param key the key (may not be null) + * @return the old value if the key existed, or null otherwise + */ + @Override + public V remove(Object key) { + return operate((Spatial) key, null, DecisionMaker.REMOVE); + } + + @Override + public V operate(Spatial key, V value, DecisionMaker decisionMaker) { + int attempt = 0; + final Collection> removedPages = isPersistent() ? new ArrayList<>() : null; + while(true) { + RootReference rootReference = flushAndGetRoot(); + if (attempt++ == 0 && !rootReference.isLockedByCurrentThread()) { + beforeWrite(); + } + Page p = rootReference.root; + if (removedPages != null && p.getTotalCount() > 0) { + removedPages.add(p); + } + p = p.copy(); + V result = operate(p, key, value, decisionMaker, removedPages); + if (!p.isLeaf() && p.getTotalCount() == 0) { + if (removedPages != null) { + removedPages.add(p); + } + p = createEmptyLeaf(); + } else if (p.getKeyCount() > store.getKeysPerPage() || p.getMemory() > store.getMaxPageSize() + && p.getKeyCount() > 3) { + // only possible if this is the root, else we would have + // split earlier (this requires pageSplitSize is fixed) + long totalCount = p.getTotalCount(); + Page split = split(p); + Spatial k1 = getBounds(p); + Spatial k2 = getBounds(split); + Spatial[] keys = p.createKeyStorage(2); + keys[0] = k1; + keys[1] = k2; + Page.PageReference[] children = Page.createRefStorage(3); + children[0] = new Page.PageReference<>(p); + children[1] = new Page.PageReference<>(split); + children[2] = Page.PageReference.empty(); + p = Page.createNode(this, keys, children, totalCount, 0); + if(isPersistent()) { + store.registerUnsavedMemory(p.getMemory()); + } + } + + if (removedPages == null) { + if (updateRoot(rootReference, p, attempt)) { + return result; + } + } else { + RootReference lockedRootReference = tryLock(rootReference, attempt); + if (lockedRootReference != null) { + try { + long version = lockedRootReference.version; + int unsavedMemory = 0; + for (Page page : removedPages) { + if (!page.isRemoved()) { + unsavedMemory += page.removePage(version); + } + } + if (isPersistent()) { + store.registerUnsavedMemory(unsavedMemory); + } + } finally { + unlockRoot(p); + } + return result; + } + removedPages.clear(); + } + decisionMaker.reset(); + } + } + + private V operate(Page p, Spatial key, V value, DecisionMaker decisionMaker, + Collection> removedPages) { + V result; + if (p.isLeaf()) { + int index = -1; + int keyCount = p.getKeyCount(); + for (int i = 0; i < keyCount; i++) { + if (keyType.equals(p.getKey(i), key)) { + index = i; + } + } + result = index < 0 ? null : p.getValue(index); + Decision decision = decisionMaker.decide(result, value); + switch (decision) { + case REPEAT: + case ABORT: + break; + case REMOVE: + if(index >= 0) { + p.remove(index); + } + break; + case PUT: + value = decisionMaker.selectValue(result, value); + if(index < 0) { + p.insertLeaf(p.getKeyCount(), key, value); + } else { + p.setKey(index, key); + p.setValue(index, value); + } + break; + } + return result; + } + + // p is an internal node + int index = -1; + for (int i = 0; i < p.getKeyCount(); i++) { + if (contains(p, i, key)) { + Page c = p.getChildPage(i); + if(get(c, key) != null) { + index = i; + break; + } + if(index < 0) { + index = i; + } + } + } + if (index < 0) { + // a new entry, we don't know where to add yet + float min = Float.MAX_VALUE; + for (int i = 0; i < p.getKeyCount(); i++) { + Object k = p.getKey(i); + float areaIncrease = keyType.getAreaIncrease(k, key); + if (areaIncrease < min) { + index = i; + min = areaIncrease; + } + } + } + Page c = p.getChildPage(index); + if (removedPages != null) { + removedPages.add(c); + } + c = c.copy(); + if (c.getKeyCount() > store.getKeysPerPage() || c.getMemory() > store.getMaxPageSize() + && c.getKeyCount() > 4) { + // split on the way down + Page split = split(c); + p.setKey(index, getBounds(c)); + p.setChild(index, c); + p.insertNode(index, getBounds(split), split); + // now we are not sure where to add + result = operate(p, key, value, decisionMaker, removedPages); + } else { + result = operate(c, key, value, decisionMaker, removedPages); + Spatial bounds = p.getKey(index); + if (!keyType.contains(bounds, key)) { + bounds = keyType.createBoundingBox(bounds); + keyType.increaseBounds(bounds, key); + p.setKey(index, bounds); + } + if (c.getTotalCount() > 0) { + p.setChild(index, c); + } else { + p.remove(index); + } + } + return result; + } + + private Spatial getBounds(Page x) { + Spatial bounds = keyType.createBoundingBox(x.getKey(0)); + int keyCount = x.getKeyCount(); + for (int i = 1; i < keyCount; i++) { + keyType.increaseBounds(bounds, x.getKey(i)); + } + return bounds; + } + + @Override + public V put(Spatial key, V value) { + return operate(key, value, DecisionMaker.PUT); + } + + /** + * Add a given key-value pair. The key should not exist (if it exists, the + * result is undefined). + * + * @param key the key + * @param value the value + */ + public void add(Spatial key, V value) { + operate(key, value, DecisionMaker.PUT); + } + + private Page split(Page p) { + return quadraticSplit ? + splitQuadratic(p) : + splitLinear(p); + } + + private Page splitLinear(Page p) { + int keyCount = p.getKeyCount(); + ArrayList keys = new ArrayList<>(keyCount); + for (int i = 0; i < keyCount; i++) { + keys.add(p.getKey(i)); + } + int[] extremes = keyType.getExtremes(keys); + if (extremes == null) { + return splitQuadratic(p); + } + Page splitA = newPage(p.isLeaf()); + Page splitB = newPage(p.isLeaf()); + move(p, splitA, extremes[0]); + if (extremes[1] > extremes[0]) { + extremes[1]--; + } + move(p, splitB, extremes[1]); + Object boundsA = keyType.createBoundingBox(splitA.getKey(0)); + Object boundsB = keyType.createBoundingBox(splitB.getKey(0)); + while (p.getKeyCount() > 0) { + Object o = p.getKey(0); + float a = keyType.getAreaIncrease(boundsA, o); + float b = keyType.getAreaIncrease(boundsB, o); + if (a < b) { + keyType.increaseBounds(boundsA, o); + move(p, splitA, 0); + } else { + keyType.increaseBounds(boundsB, o); + move(p, splitB, 0); + } + } + while (splitB.getKeyCount() > 0) { + move(splitB, p, 0); + } + return splitA; + } + + private Page splitQuadratic(Page p) { + Page splitA = newPage(p.isLeaf()); + Page splitB = newPage(p.isLeaf()); + float largest = Float.MIN_VALUE; + int ia = 0, ib = 0; + int keyCount = p.getKeyCount(); + for (int a = 0; a < keyCount; a++) { + Object objA = p.getKey(a); + for (int b = 0; b < keyCount; b++) { + if (a == b) { + continue; + } + Object objB = p.getKey(b); + float area = keyType.getCombinedArea(objA, objB); + if (area > largest) { + largest = area; + ia = a; + ib = b; + } + } + } + move(p, splitA, ia); + if (ia < ib) { + ib--; + } + move(p, splitB, ib); + Object boundsA = keyType.createBoundingBox(splitA.getKey(0)); + Object 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); + float incA = keyType.getAreaIncrease(boundsA, o); + float incB = keyType.getAreaIncrease(boundsB, o); + float d = Math.abs(incA - incB); + if (d > diff) { + diff = d; + bestA = incA; + bestB = incB; + best = i; + } + } + if (bestA < bestB) { + keyType.increaseBounds(boundsA, p.getKey(best)); + move(p, splitA, best); + } else { + keyType.increaseBounds(boundsB, p.getKey(best)); + move(p, splitB, best); + } + } + while (splitB.getKeyCount() > 0) { + move(splitB, p, 0); + } + return splitA; + } + + private Page newPage(boolean leaf) { + Page page = leaf ? createEmptyLeaf() : createEmptyNode(); + if(isPersistent()) { + store.registerUnsavedMemory(page.getMemory()); + } + return page; + } + + private static void move(Page source, Page target, int sourceIndex) { + Spatial k = source.getKey(sourceIndex); + if (source.isLeaf()) { + V v = source.getValue(sourceIndex); + target.insertLeaf(0, k, v); + } else { + Page c = source.getChildPage(sourceIndex); + target.insertNode(0, k, c); + } + source.remove(sourceIndex); + } + + /** + * Add all node keys (including internal bounds) to the given list. + * This is mainly used to visualize the internal splits. + * + * @param list the list + * @param p the root page + */ + public void addNodeKeys(ArrayList list, Page p) { + if (p != null && !p.isLeaf()) { + int keyCount = p.getKeyCount(); + for (int i = 0; i < keyCount; i++) { + list.add(p.getKey(i)); + addNodeKeys(list, p.getChildPage(i)); + } + } + } + + @SuppressWarnings("unused") + public boolean isQuadraticSplit() { + return quadraticSplit; + } + + public void setQuadraticSplit(boolean quadraticSplit) { + this.quadraticSplit = quadraticSplit; + } + + @Override + protected int getChildPageCount(Page p) { + return p.getRawChildPageCount() - 1; + } + + /** + * A cursor to iterate over a subset of the keys. + */ + public abstract static class RTreeCursor implements Iterator { + + private final Spatial filter; + private CursorPos pos; + private Spatial current; + private final Page root; + private boolean initialized; + + protected RTreeCursor(Page root, Spatial filter) { + this.root = root; + this.filter = filter; + } + + @Override + public boolean hasNext() { + if (!initialized) { + // init + pos = new CursorPos<>(root, 0, null); + fetchNext(); + initialized = true; + } + return current != null; + } + + /** + * Skip over that many entries. This method is relatively fast (for this + * map implementation) even if many entries need to be skipped. + * + * @param n the number of entries to skip + */ + public void skip(long n) { + while (hasNext() && n-- > 0) { + fetchNext(); + } + } + + @Override + public Spatial next() { + if (!hasNext()) { + return null; + } + Spatial c = current; + fetchNext(); + return c; + } + + /** + * Fetch the next entry if there is one. + */ + void fetchNext() { + while (pos != null) { + Page p = pos.page; + if (p.isLeaf()) { + while (pos.index < p.getKeyCount()) { + Spatial c = p.getKey(pos.index++); + if (filter == null || check(true, c, filter)) { + current = c; + return; + } + } + } else { + boolean found = false; + while (pos.index < p.getKeyCount()) { + int index = pos.index++; + Spatial c = p.getKey(index); + if (filter == null || check(false, c, filter)) { + Page child = pos.page.getChildPage(index); + pos = new CursorPos<>(child, 0, pos); + found = true; + break; + } + } + if (found) { + continue; + } + } + // parent + pos = pos.parent; + } + current = null; + } + + /** + * Check a given key. + * + * @param leaf if the key is from a leaf page + * @param key the stored key + * @param test the user-supplied test key + * @return true if there is a match + */ + protected abstract boolean check(boolean leaf, Spatial key, Spatial test); + } + + private static final class IntersectsRTreeCursor extends RTreeCursor { + private final SpatialDataType keyType; + + public IntersectsRTreeCursor(Page root, Spatial filter, SpatialDataType keyType) { + super(root, filter); + this.keyType = keyType; + } + + @Override + protected boolean check(boolean leaf, Spatial key, + Spatial test) { + return keyType.isOverlap(key, test); + } + } + + private static final class ContainsRTreeCursor extends RTreeCursor { + private final SpatialDataType keyType; + + public ContainsRTreeCursor(Page root, Spatial filter, SpatialDataType keyType) { + super(root, filter); + this.keyType = keyType; + } + + @Override + protected boolean check(boolean leaf, Spatial key, Spatial test) { + return leaf ? + keyType.isInside(key, test) : + keyType.isOverlap(key, test); + } + } + + @Override + public String getType() { + return "rtree"; + } + + /** + * A builder for this class. + * + * @param the value type + */ + public static class Builder extends MVMap.BasicBuilder, Spatial, V> { + + private int dimensions = 2; + + /** + * Create a new builder for maps with 2 dimensions. + */ + public Builder() { + setKeyType(new SpatialDataType(dimensions)); + } + + /** + * Set the dimensions. + * + * @param dimensions the dimensions to use + * @return this + */ + public Builder dimensions(int dimensions) { + this.dimensions = dimensions; + setKeyType(new SpatialDataType(dimensions)); + return this; + } + + /** + * Set the key data type. + * + * @param valueType the key type + * @return this + */ + @Override + public Builder valueType(DataType valueType) { + setValueType(valueType); + return this; + } + + @Override + public MVRTreeMap create(Map config) { + return new MVRTreeMap<>(config, (SpatialDataType)getKeyType(), getValueType()); + } + } +} diff --git a/h2/src/main/org/h2/mvstore/rtree/Spatial.java b/h2/src/main/org/h2/mvstore/rtree/Spatial.java new file mode 100644 index 0000000..1b9682d --- /dev/null +++ b/h2/src/main/org/h2/mvstore/rtree/Spatial.java @@ -0,0 +1,76 @@ +/* + * 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.mvstore.rtree; + +/** + * Interface Spatial represents boxes in 2+ dimensional space, + * where total ordering is not that straight-forward. + * They can be used as keys for MVRTree. + * + * @author Andrei Tokar + */ +public interface Spatial +{ + /** + * Get the minimum value for the given dimension. + * + * @param dim the dimension + * @return the value + */ + float min(int dim); + + /** + * Set the minimum value for the given dimension. + * + * @param dim the dimension + * @param x the value + */ + void setMin(int dim, float x); + + /** + * Get the maximum value for the given dimension. + * + * @param dim the dimension + * @return the value + */ + float max(int dim); + + /** + * Set the maximum value for the given dimension. + * + * @param dim the dimension + * @param x the value + */ + void setMax(int dim, float x); + + /** + * Creates a copy of this Spatial object with different id. + * + * @param id for the new Spatial object + * @return a clone + */ + Spatial clone(long id); + + /** + * Get id of this Spatial object + * @return id + */ + long getId(); + + /** + * Test whether this object has no value + * @return true if it is NULL, false otherwise + */ + boolean isNull(); + + /** + * Check whether two objects are equals, but do not compare the id fields. + * + * @param o the other key + * @return true if the contents are the same + */ + boolean equalsIgnoringId(Spatial o); +} diff --git a/h2/src/main/org/h2/mvstore/rtree/SpatialDataType.java b/h2/src/main/org/h2/mvstore/rtree/SpatialDataType.java new file mode 100644 index 0000000..6af8a58 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/rtree/SpatialDataType.java @@ -0,0 +1,385 @@ +/* + * 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.mvstore.rtree; + +import java.nio.ByteBuffer; +import java.util.ArrayList; + +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.WriteBuffer; +import org.h2.mvstore.type.BasicDataType; + +/** + * A spatial data type. This class supports up to 31 dimensions. Each dimension + * can have a minimum and a maximum value of type float. For each dimension, the + * maximum value is only stored when it is not the same as the minimum. + */ +public class SpatialDataType extends BasicDataType { + + private final int dimensions; + + public SpatialDataType(int dimensions) { + // Because of how we are storing the + // min-max-flag in the read/write method + // the number of dimensions must be < 32. + DataUtils.checkArgument( + dimensions >= 1 && dimensions < 32, + "Dimensions must be between 1 and 31, is {0}", dimensions); + this.dimensions = dimensions; + } + + /** + * Creates spatial object with specified parameters. + * + * @param id the ID + * @param minMax min x, max x, min y, max y, and so on + * @return the spatial object + */ + protected Spatial create(long id, float... minMax) { + return new DefaultSpatial(id, minMax); + } + + @Override + public Spatial[] createStorage(int size) { + return new Spatial[size]; + } + + @Override + public int compare(Spatial a, Spatial b) { + if (a == b) { + return 0; + } else if (a == null) { + return -1; + } else if (b == null) { + return 1; + } + long la = a.getId(); + long lb = b.getId(); + return Long.compare(la, lb); + } + + /** + * Check whether two spatial values are equal. + * + * @param a the first value + * @param b the second value + * @return true if they are equal + */ + public boolean equals(Object a, Object 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; + } + + @Override + public int getMemory(Spatial obj) { + return 40 + dimensions * 4; + } + + @Override + public void write(WriteBuffer buff, Spatial k) { + if (k.isNull()) { + buff.putVarInt(-1); + buff.putVarLong(k.getId()); + return; + } + int flags = 0; + for (int i = 0; i < dimensions; i++) { + if (k.min(i) == k.max(i)) { + flags |= 1 << i; + } + } + buff.putVarInt(flags); + for (int i = 0; i < dimensions; i++) { + buff.putFloat(k.min(i)); + if ((flags & (1 << i)) == 0) { + buff.putFloat(k.max(i)); + } + } + buff.putVarLong(k.getId()); + } + + @Override + public Spatial read(ByteBuffer buff) { + int flags = DataUtils.readVarInt(buff); + if (flags == -1) { + long id = DataUtils.readVarLong(buff); + return create(id); + } + float[] minMax = new float[dimensions * 2]; + for (int i = 0; i < dimensions; i++) { + float min = buff.getFloat(); + float max; + if ((flags & (1 << i)) != 0) { + max = min; + } else { + max = buff.getFloat(); + } + minMax[i + i] = min; + minMax[i + i + 1] = max; + } + long id = DataUtils.readVarLong(buff); + return create(id, minMax); + } + + /** + * Check whether the two objects overlap. + * + * @param a the first object + * @param b the second object + * @return true if they overlap + */ + public boolean isOverlap(Spatial a, Spatial b) { + if (a.isNull() || b.isNull()) { + return false; + } + for (int i = 0; i < dimensions; i++) { + if (a.max(i) < b.min(i) || a.min(i) > b.max(i)) { + return false; + } + } + return true; + } + + /** + * Increase the bounds in the given spatial object. + * + * @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()) { + return; + } + for (int i = 0; i < dimensions; i++) { + float v = a.min(i); + if (v < b.min(i)) { + b.setMin(i, v); + } + v = a.max(i); + if (v > b.max(i)) { + b.setMax(i, v); + } + } + } + + /** + * Get the area increase by extending a to contain b. + * + * @param objA the bounding box + * @param objB 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()) { + return 0; + } + float min = a.min(0); + float max = a.max(0); + float areaOld = max - min; + min = Math.min(min, b.min(0)); + max = Math.max(max, b.max(0)); + float areaNew = max - min; + for (int i = 1; i < dimensions; i++) { + min = a.min(i); + max = a.max(i); + areaOld *= max - min; + min = Math.min(min, b.min(i)); + max = Math.max(max, b.max(i)); + areaNew *= max - min; + } + return areaNew - areaOld; + } + + /** + * Get the combined area of both objects. + * + * @param objA the first object + * @param objB the second object + * @return the area + */ + float getCombinedArea(Object objA, Object objB) { + Spatial a = (Spatial) objA; + Spatial b = (Spatial) objB; + if (a.isNull()) { + return getArea(b); + } else if (b.isNull()) { + return getArea(a); + } + float area = 1; + for (int i = 0; i < dimensions; i++) { + float min = Math.min(a.min(i), b.min(i)); + float max = Math.max(a.max(i), b.max(i)); + area *= max - min; + } + return area; + } + + private float getArea(Spatial a) { + if (a.isNull()) { + return 0; + } + float area = 1; + for (int i = 0; i < dimensions; i++) { + area *= a.max(i) - a.min(i); + } + return area; + } + + /** + * Check whether a contains b. + * + * @param objA the bounding box + * @param objB 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()) { + return false; + } + for (int i = 0; i < dimensions; i++) { + if (a.min(i) > b.min(i) || a.max(i) < b.max(i)) { + return false; + } + } + return true; + } + + /** + * Check whether a is completely inside b and does not touch the + * given bound. + * + * @param objA the object to check + * @param objB 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()) { + return false; + } + for (int i = 0; i < dimensions; i++) { + if (a.min(i) <= b.min(i) || a.max(i) >= b.max(i)) { + return false; + } + } + return true; + } + + /** + * Create a bounding box starting with the given object. + * + * @param objA the object + * @return the bounding box + */ + Spatial createBoundingBox(Object objA) { + Spatial a = (Spatial) objA; + if (a.isNull()) { + return a; + } + return a.clone(0); + } + + /** + * Get the most extreme pair (elements that are as far apart as possible). + * This method is used to split a page (linear split). If no extreme objects + * could be found, this method returns null. + * + * @param list the objects + * @return the indexes of the extremes + */ + public int[] getExtremes(ArrayList list) { + list = getNotNull(list); + if (list.isEmpty()) { + return null; + } + Spatial bounds = createBoundingBox(list.get(0)); + Spatial boundsInner = createBoundingBox(bounds); + for (int i = 0; i < dimensions; i++) { + float t = boundsInner.min(i); + boundsInner.setMin(i, boundsInner.max(i)); + boundsInner.setMax(i, t); + } + for (Object o : list) { + increaseBounds(bounds, o); + increaseMaxInnerBounds(boundsInner, o); + } + double best = 0; + int bestDim = 0; + for (int i = 0; i < dimensions; i++) { + float inner = boundsInner.max(i) - boundsInner.min(i); + if (inner < 0) { + continue; + } + float outer = bounds.max(i) - bounds.min(i); + float d = inner / outer; + if (d > best) { + best = d; + bestDim = i; + } + } + if (best <= 0) { + return null; + } + float min = boundsInner.min(bestDim); + float max = boundsInner.max(bestDim); + int firstIndex = -1, lastIndex = -1; + for (int i = 0; i < list.size() && + (firstIndex < 0 || lastIndex < 0); i++) { + Spatial o = (Spatial) list.get(i); + if (firstIndex < 0 && o.max(bestDim) == min) { + firstIndex = i; + } else if (lastIndex < 0 && o.min(bestDim) == max) { + lastIndex = i; + } + } + return new int[] { firstIndex, lastIndex }; + } + + private static ArrayList getNotNull(ArrayList list) { + boolean foundNull = false; + for (Object o : list) { + Spatial a = (Spatial) o; + if (a.isNull()) { + foundNull = true; + break; + } + } + if (!foundNull) { + return list; + } + ArrayList result = new ArrayList<>(); + for (Object o : list) { + Spatial a = (Spatial) o; + if (!a.isNull()) { + result.add(a); + } + } + return result; + } + + private void increaseMaxInnerBounds(Object bounds, Object add) { + Spatial b = (Spatial) bounds; + Spatial a = (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))); + } + } + +} diff --git a/h2/src/main/org/h2/mvstore/rtree/package.html b/h2/src/main/org/h2/mvstore/rtree/package.html new file mode 100644 index 0000000..240224c --- /dev/null +++ b/h2/src/main/org/h2/mvstore/rtree/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +An R-tree implementation + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/mvstore/tx/CommitDecisionMaker.java b/h2/src/main/org/h2/mvstore/tx/CommitDecisionMaker.java new file mode 100644 index 0000000..f3867b3 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/tx/CommitDecisionMaker.java @@ -0,0 +1,65 @@ +/* + * 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.mvstore.tx; + +import org.h2.mvstore.MVMap; +import org.h2.value.VersionedValue; + +/** + * Class CommitDecisionMaker makes a decision during post-commit processing + * about how to transform uncommitted map entry into committed one, + * based on undo log information. + * + * @author Andrei Tokar + */ +final class CommitDecisionMaker extends MVMap.DecisionMaker> { + private long undoKey; + private MVMap.Decision decision; + + void setUndoKey(long undoKey) { + this.undoKey = undoKey; + reset(); + } + + @Override + public MVMap.Decision decide(VersionedValue existingValue, VersionedValue providedValue) { + assert decision == null; + if (existingValue == null || + // map entry was treated as already committed, and then + // it has been removed by another transaction (committed and closed by now) + existingValue.getOperationId() != undoKey) { + // this is not a final undo log entry for this key, + // or map entry was treated as already committed and then + // overwritten by another transaction + // see TxDecisionMaker.decide() + + decision = MVMap.Decision.ABORT; + } else /* this is final undo log entry for this key */ if (existingValue.getCurrentValue() == null) { + decision = MVMap.Decision.REMOVE; + } else { + decision = MVMap.Decision.PUT; + } + return decision; + } + + @SuppressWarnings("unchecked") + @Override + public > T selectValue(T existingValue, T providedValue) { + assert decision == MVMap.Decision.PUT; + assert existingValue != null; + return (T) VersionedValueCommitted.getInstance(existingValue.getCurrentValue()); + } + + @Override + public void reset() { + decision = null; + } + + @Override + public String toString() { + return "commit " + TransactionStore.getTransactionId(undoKey); + } +} diff --git a/h2/src/main/org/h2/mvstore/tx/Record.java b/h2/src/main/org/h2/mvstore/tx/Record.java new file mode 100644 index 0000000..4da15fd --- /dev/null +++ b/h2/src/main/org/h2/mvstore/tx/Record.java @@ -0,0 +1,118 @@ +/* + * 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.mvstore.tx; + +import java.nio.ByteBuffer; +import org.h2.engine.Constants; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.WriteBuffer; +import org.h2.mvstore.type.BasicDataType; +import org.h2.value.VersionedValue; + +/** + * Class Record is a value for undoLog. + * It contains information about a single change of some map. + * + * @author Andrei Tokar + */ +final class Record { + + // -1 is a bogus map id + static final Record COMMIT_MARKER = new Record<>(-1, null, null); + + /** + * Map id for this change is related to + */ + final int mapId; + + /** + * Key of the changed map entry key + */ + final K key; + + /** + * Value of the entry before change. + * It is null if entry did not exist before the change (addition). + */ + final VersionedValue oldValue; + + Record(int mapId, K key, VersionedValue oldValue) { + this.mapId = mapId; + this.key = key; + this.oldValue = oldValue; + } + + @Override + public String toString() { + return "mapId=" + mapId + ", key=" + key + ", value=" + oldValue; + } + + /** + * A data type for undo log values + */ + static final class Type extends BasicDataType> { + private final TransactionStore transactionStore; + + Type(TransactionStore transactionStore) { + this.transactionStore = transactionStore; + } + + @Override + public int getMemory(Record record) { + int result = Constants.MEMORY_OBJECT + 4 + 3 * Constants.MEMORY_POINTER; + if (record.mapId >= 0) { + MVMap> map = transactionStore.getMap(record.mapId); + result += map.getKeyType().getMemory(record.key) + + map.getValueType().getMemory(record.oldValue); + } + return result; + } + + @Override + public int compare(Record aObj, Record bObj) { + throw new UnsupportedOperationException(); + } + + @Override + public void write(WriteBuffer buff, Record record) { + buff.putVarInt(record.mapId); + if (record.mapId >= 0) { + MVMap> map = transactionStore.getMap(record.mapId); + map.getKeyType().write(buff, record.key); + VersionedValue oldValue = record.oldValue; + if (oldValue == null) { + buff.put((byte) 0); + } else { + buff.put((byte) 1); + map.getValueType().write(buff, oldValue); + } + } + } + + @SuppressWarnings("unchecked") + @Override + public Record read(ByteBuffer buff) { + int mapId = DataUtils.readVarInt(buff); + if (mapId < 0) { + return (Record)COMMIT_MARKER; + } + MVMap> map = transactionStore.getMap(mapId); + K key = map.getKeyType().read(buff); + VersionedValue oldValue = null; + if (buff.get() == 1) { + oldValue = map.getValueType().read(buff); + } + return new Record<>(mapId, key, oldValue); + } + + @SuppressWarnings("unchecked") + @Override + public Record[] createStorage(int size) { + return new Record[size]; + } + } +} diff --git a/h2/src/main/org/h2/mvstore/tx/RollbackDecisionMaker.java b/h2/src/main/org/h2/mvstore/tx/RollbackDecisionMaker.java new file mode 100644 index 0000000..923605e --- /dev/null +++ b/h2/src/main/org/h2/mvstore/tx/RollbackDecisionMaker.java @@ -0,0 +1,69 @@ +/* + * 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.mvstore.tx; + +import org.h2.mvstore.MVMap; +import org.h2.value.VersionedValue; + +/** + * Class RollbackDecisionMaker process undo log record during transaction rollback. + * + * @author Andrei Tokar + */ +final class RollbackDecisionMaker extends MVMap.DecisionMaker> { + private final TransactionStore store; + private final long transactionId; + private final long toLogId; + private final TransactionStore.RollbackListener listener; + private MVMap.Decision decision; + + RollbackDecisionMaker(TransactionStore store, long transactionId, long toLogId, + TransactionStore.RollbackListener listener) { + this.store = store; + this.transactionId = transactionId; + this.toLogId = toLogId; + this.listener = listener; + } + + @SuppressWarnings({"unchecked","rawtypes"}) + @Override + public MVMap.Decision decide(Record existingValue, Record providedValue) { + assert decision == null; + if (existingValue == null) { + // normally existingValue will always be there except of db initialization + // where some undo log entry was captured on disk but actual map entry was not + decision = MVMap.Decision.ABORT; + } else { + VersionedValue valueToRestore = existingValue.oldValue; + long operationId; + if (valueToRestore == null || + (operationId = valueToRestore.getOperationId()) == 0 || + TransactionStore.getTransactionId(operationId) == transactionId + && TransactionStore.getLogId(operationId) < toLogId) { + int mapId = existingValue.mapId; + MVMap> map = store.openMap(mapId); + if (map != null && !map.isClosed()) { + Object key = existingValue.key; + VersionedValue previousValue = map.operate(key, valueToRestore, + MVMap.DecisionMaker.DEFAULT); + listener.onRollback(map, key, previousValue, valueToRestore); + } + } + decision = MVMap.Decision.REMOVE; + } + return decision; + } + + @Override + public void reset() { + decision = null; + } + + @Override + public String toString() { + return "rollback-" + transactionId; + } +} diff --git a/h2/src/main/org/h2/mvstore/tx/Snapshot.java b/h2/src/main/org/h2/mvstore/tx/Snapshot.java new file mode 100644 index 0000000..224d1ce --- /dev/null +++ b/h2/src/main/org/h2/mvstore/tx/Snapshot.java @@ -0,0 +1,54 @@ +/* + * 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.mvstore.tx; + +import java.util.BitSet; + +import org.h2.mvstore.RootReference; + +/** + * Snapshot of the map root and committing transactions. + */ +final class Snapshot { + + /** + * The root reference. + */ + final RootReference root; + + /** + * The committing transactions (see also TransactionStore.committingTransactions). + */ + final BitSet committingTransactions; + + Snapshot(RootReference root, BitSet committingTransactions) { + this.root = root; + this.committingTransactions = committingTransactions; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + committingTransactions.hashCode(); + result = prime * result + root.hashCode(); + return result; + } + + @SuppressWarnings("unchecked") + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Snapshot)) { + return false; + } + Snapshot other = (Snapshot) obj; + return committingTransactions == other.committingTransactions && root == other.root; + } + +} diff --git a/h2/src/main/org/h2/mvstore/tx/Transaction.java b/h2/src/main/org/h2/mvstore/tx/Transaction.java new file mode 100644 index 0000000..d7d8516 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/tx/Transaction.java @@ -0,0 +1,807 @@ +/* + * 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.mvstore.tx; + +import java.util.BitSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; +import org.h2.engine.IsolationLevel; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVStore; +import org.h2.mvstore.RootReference; +import org.h2.mvstore.type.DataType; +import org.h2.value.VersionedValue; + +/** + * A transaction. + */ +public final class Transaction { + + /** + * The status of a closed transaction (committed or rolled back). + */ + public static final int STATUS_CLOSED = 0; + + /** + * The status of an open transaction. + */ + public static final int STATUS_OPEN = 1; + + /** + * The status of a prepared transaction. + */ + public static final int STATUS_PREPARED = 2; + + /** + * The status of a transaction that has been logically committed or rather + * marked as committed, because it might be still listed among prepared, + * if it was prepared for commit. Undo log entries might still exists for it + * and not all of it's changes within map's are re-written as committed yet. + * Nevertheless, those changes should be already viewed by other + * transactions as committed. + * This transaction's id can not be re-used until all of the above is completed + * and transaction is closed. + * A transactions can be observed in this state when the store was + * closed while the transaction was not closed yet. + * When opening a store, such transactions will automatically + * be processed and closed as committed. + */ + public static final int STATUS_COMMITTED = 3; + + /** + * The status of a transaction that currently in a process of rolling back + * to a savepoint. + */ + private static final int STATUS_ROLLING_BACK = 4; + + /** + * The status of a transaction that has been rolled back completely, + * but undo operations are not finished yet. + */ + private static final int STATUS_ROLLED_BACK = 5; + + private static final String[] STATUS_NAMES = { + "CLOSED", "OPEN", "PREPARED", "COMMITTED", "ROLLING_BACK", "ROLLED_BACK" + }; + /** + * How many bits of the "operation id" we store in the transaction belong to the + * log id (the rest belong to the transaction id). + */ + static final int LOG_ID_BITS = 40; + private static final int LOG_ID_BITS1 = LOG_ID_BITS + 1; + private static final long LOG_ID_LIMIT = 1L << LOG_ID_BITS; + private static final long LOG_ID_MASK = (1L << LOG_ID_BITS1) - 1; + private static final int STATUS_BITS = 4; + private static final int STATUS_MASK = (1 << STATUS_BITS) - 1; + + + /** + * The transaction store. + */ + final TransactionStore store; + + /** + * Listener for this transaction's rollback changes. + */ + final TransactionStore.RollbackListener listener; + + /** + * The transaction id. + * More appropriate name for this field would be "slotId" + */ + final int transactionId; + + /** + * This is really a transaction identity, because it's not re-used. + */ + final long sequenceNum; + + /* + * Transaction state is an atomic composite field: + * bit 45 : flag whether transaction had rollback(s) + * bits 44-41 : status + * bits 40 : overflow control bit, 1 indicates overflow + * bits 39-0 : log id of the last entry in the undo log map + */ + private final AtomicLong statusAndLogId; + + /** + * Reference to a counter for an earliest store version used by this transaction. + * Referenced version and all newer ones can not be discarded + * at least until this transaction ends. + */ + private MVStore.TxCounter txCounter; + + /** + * Transaction name. + */ + private String name; + + /** + * Indicates whether this transaction was stored in preparedTransactions map + */ + boolean wasStored; + + /** + * How long to wait for blocking transaction to commit or rollback. + */ + int timeoutMillis; + + /** + * Identification of the owner of this transaction, + * usually the owner is a database session. + */ + private final int ownerId; + + /** + * Blocking transaction, if any + */ + private volatile Transaction blockingTransaction; + + /** + * Map on which this transaction is blocked. + */ + private String blockingMapName; + + /** + * Key in blockingMap on which this transaction is blocked. + */ + private Object blockingKey; + + /** + * Whether other transaction(s) are waiting for this to close. + */ + private volatile boolean notificationRequested; + + /** + * RootReferences for undo log snapshots + */ + private RootReference>[] undoLogRootReferences; + + /** + * Map of transactional maps for this transaction + */ + private final Map> transactionMaps = new HashMap<>(); + + /** + * The current isolation level. + */ + final IsolationLevel isolationLevel; + + + Transaction(TransactionStore store, int transactionId, long sequenceNum, int status, + String name, long logId, int timeoutMillis, int ownerId, + IsolationLevel isolationLevel, TransactionStore.RollbackListener listener) { + this.store = store; + this.transactionId = transactionId; + this.sequenceNum = sequenceNum; + this.statusAndLogId = new AtomicLong(composeState(status, logId, false)); + this.name = name; + setTimeoutMillis(timeoutMillis); + this.ownerId = ownerId; + this.isolationLevel = isolationLevel; + this.listener = listener; + } + + public int getId() { + return transactionId; + } + + public long getSequenceNum() { + return sequenceNum; + } + + public int getStatus() { + return getStatus(statusAndLogId.get()); + } + + RootReference>[] getUndoLogRootReferences() { + return undoLogRootReferences; + } + + /** + * Changes transaction status to a specified value + * @param status to be set + * @return transaction state as it was before status change + */ + private long setStatus(int status) { + while (true) { + long currentState = statusAndLogId.get(); + long logId = getLogId(currentState); + int currentStatus = getStatus(currentState); + boolean valid; + switch (status) { + case STATUS_ROLLING_BACK: + valid = currentStatus == STATUS_OPEN; + break; + case STATUS_PREPARED: + valid = currentStatus == STATUS_OPEN; + break; + case STATUS_COMMITTED: + valid = currentStatus == STATUS_OPEN || + currentStatus == STATUS_PREPARED || + // this case is only possible if called + // from endLeftoverTransactions() + currentStatus == STATUS_COMMITTED; + break; + case STATUS_ROLLED_BACK: + valid = currentStatus == STATUS_OPEN || + currentStatus == STATUS_PREPARED || + currentStatus == STATUS_ROLLING_BACK; + break; + case STATUS_CLOSED: + valid = currentStatus == STATUS_COMMITTED || + currentStatus == STATUS_ROLLED_BACK; + break; + case STATUS_OPEN: + default: + valid = false; + break; + } + if (!valid) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_TRANSACTION_ILLEGAL_STATE, + "Transaction was illegally transitioned from {0} to {1}", + getStatusName(currentStatus), getStatusName(status)); + } + long newState = composeState(status, logId, hasRollback(currentState)); + if (statusAndLogId.compareAndSet(currentState, newState)) { + return currentState; + } + } + } + + /** + * Determine if any database changes were made as part of this transaction. + * + * @return true if there are changes to commit, false otherwise + */ + public boolean hasChanges() { + return hasChanges(statusAndLogId.get()); + } + + public void setName(String name) { + checkNotClosed(); + this.name = name; + store.storeTransaction(this); + } + + public String getName() { + return name; + } + + public int getBlockerId() { + Transaction blocker = this.blockingTransaction; + return blocker == null ? 0 : blocker.ownerId; + } + + /** + * Create a new savepoint. + * + * @return the savepoint id + */ + public long setSavepoint() { + return getLogId(); + } + + /** + * Returns whether statement dependencies are currently set. + * + * @return whether statement dependencies are currently set + */ + public boolean hasStatementDependencies() { + return !transactionMaps.isEmpty(); + } + + /** + * Returns the isolation level of this transaction. + * + * @return the isolation level of this transaction + */ + public IsolationLevel getIsolationLevel() { + return isolationLevel; + } + + boolean isReadCommitted() { + return isolationLevel == IsolationLevel.READ_COMMITTED; + } + + /** + * Whether this transaction has isolation level READ_COMMITTED or below. + * @return true if isolation level is READ_COMMITTED or READ_UNCOMMITTED + */ + public boolean allowNonRepeatableRead() { + return isolationLevel.allowNonRepeatableRead(); + } + + /** + * Mark an entry into a new SQL statement execution within this transaction. + * + * @param maps + * set of maps used by transaction or statement is about to be executed + */ + @SuppressWarnings({"unchecked","rawtypes"}) + public void markStatementStart(HashSet>> maps) { + markStatementEnd(); + if (txCounter == null && store.store.isVersioningRequired()) { + txCounter = store.store.registerVersionUsage(); + } + + if (maps != null && !maps.isEmpty()) { + // The purpose of the following loop is to get a coherent picture + // In order to get such a "snapshot", we wait for a moment of silence, + // when no new transaction were committed / closed. + BitSet committingTransactions; + do { + committingTransactions = store.committingTransactions.get(); + for (MVMap> map : maps) { + TransactionMap txMap = openMapX(map); + txMap.setStatementSnapshot(new Snapshot(map.flushAndGetRoot(), committingTransactions)); + } + if (isReadCommitted()) { + undoLogRootReferences = store.collectUndoLogRootReferences(); + } + } while (committingTransactions != store.committingTransactions.get()); + // Now we have a snapshot, where each map RootReference point to state of the map, + // undoLogRootReferences captures the state of undo logs + // and committingTransactions mask tells us which of seemingly uncommitted changes + // should be considered as committed. + // Subsequent processing uses this snapshot info only. + for (MVMap> map : maps) { + TransactionMap txMap = openMapX(map); + txMap.promoteSnapshot(); + } + } + } + + /** + * Mark an exit from SQL statement execution within this transaction. + */ + public void markStatementEnd() { + if (allowNonRepeatableRead()) { + releaseSnapshot(); + } + for (TransactionMap transactionMap : transactionMaps.values()) { + transactionMap.setStatementSnapshot(null); + } + } + + private void markTransactionEnd() { + if (!allowNonRepeatableRead()) { + releaseSnapshot(); + } + } + + private void releaseSnapshot() { + transactionMaps.clear(); + undoLogRootReferences = null; + MVStore.TxCounter counter = txCounter; + if (counter != null) { + txCounter = null; + store.store.deregisterVersionUsage(counter); + } + } + + /** + * Add a log entry. + * + * @param logRecord to append + * + * @return key for the newly added undo log entry + */ + long log(Record logRecord) { + long currentState = statusAndLogId.getAndIncrement(); + long logId = getLogId(currentState); + if (logId >= LOG_ID_LIMIT) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_TRANSACTION_TOO_BIG, + "Transaction {0} has too many changes", + transactionId); + } + int currentStatus = getStatus(currentState); + checkOpen(currentStatus); + long undoKey = store.addUndoLogRecord(transactionId, logId, logRecord); + return undoKey; + } + + /** + * Remove the last log entry. + */ + void logUndo() { + long currentState = statusAndLogId.decrementAndGet(); + long logId = getLogId(currentState); + if (logId >= LOG_ID_LIMIT) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_TRANSACTION_CORRUPT, + "Transaction {0} has internal error", + transactionId); + } + int currentStatus = getStatus(currentState); + checkOpen(currentStatus); + store.removeUndoLogRecord(transactionId); + } + + /** + * Open a data map. + * + * @param the key type + * @param the value type + * @param name the name of the map + * @return the transaction map + */ + public TransactionMap openMap(String name) { + return openMap(name, null, null); + } + + /** + * Open the map to store the data. + * + * @param the key type + * @param the value type + * @param name the name of the map + * @param keyType the key data type + * @param valueType the value data type + * @return the transaction map + */ + public TransactionMap openMap(String name, + DataType keyType, + DataType valueType) { + MVMap> map = store.openVersionedMap(name, keyType, valueType); + return openMapX(map); + } + + /** + * Open the transactional version of the given map. + * + * @param the key type + * @param the value type + * @param map the base map + * @return the transactional map + */ + @SuppressWarnings("unchecked") + public TransactionMap openMapX(MVMap> map) { + checkNotClosed(); + int id = map.getId(); + TransactionMap transactionMap = (TransactionMap)transactionMaps.get(id); + if (transactionMap == null) { + transactionMap = new TransactionMap<>(this, map); + transactionMaps.put(id, transactionMap); + } + return transactionMap; + } + + /** + * Prepare the transaction. Afterwards, the transaction can only be + * committed or completely rolled back. + */ + public void prepare() { + setStatus(STATUS_PREPARED); + store.storeTransaction(this); + } + + /** + * Commit the transaction. Afterwards, this transaction is closed. + */ + public void commit() { + assert store.openTransactions.get().get(transactionId); + markTransactionEnd(); + Throwable ex = null; + boolean hasChanges = false; + int previousStatus = STATUS_OPEN; + try { + long state = setStatus(STATUS_COMMITTED); + hasChanges = hasChanges(state); + previousStatus = getStatus(state); + if (hasChanges) { + store.commit(this, previousStatus == STATUS_COMMITTED); + } + } catch (Throwable e) { + ex = e; + throw e; + } finally { + if (isActive(previousStatus)) { + try { + store.endTransaction(this, hasChanges); + } catch (Throwable e) { + if (ex == null) { + throw e; + } else { + ex.addSuppressed(e); + } + } + } + } + } + + /** + * Roll back to the given savepoint. This is only allowed if the + * transaction is open. + * + * @param savepointId the savepoint id + */ + public void rollbackToSavepoint(long savepointId) { + long lastState = setStatus(STATUS_ROLLING_BACK); + long logId = getLogId(lastState); + boolean success; + try { + store.rollbackTo(this, logId, savepointId); + } finally { + notifyAllWaitingTransactions(); + long expectedState = composeState(STATUS_ROLLING_BACK, logId, hasRollback(lastState)); + long newState = composeState(STATUS_OPEN, savepointId, true); + do { + success = statusAndLogId.compareAndSet(expectedState, newState); + } while (!success && statusAndLogId.get() == expectedState); + } + // this is moved outside of finally block to avert masking original exception, if any + if (!success) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_TRANSACTION_ILLEGAL_STATE, + "Transaction {0} concurrently modified while rollback to savepoint was in progress", + transactionId); + } + } + + /** + * Roll the transaction back. Afterwards, this transaction is closed. + */ + public void rollback() { + markTransactionEnd(); + Throwable ex = null; + int status = STATUS_OPEN; + try { + long lastState = setStatus(STATUS_ROLLED_BACK); + status = getStatus(lastState); + long logId = getLogId(lastState); + if (logId > 0) { + store.rollbackTo(this, logId, 0); + } + } catch (Throwable e) { + status = getStatus(); + if (isActive(status)) { + ex = e; + throw e; + } + } finally { + try { + if (isActive(status)) { + store.endTransaction(this, true); + } + } catch (Throwable e) { + if (ex == null) { + throw e; + } else { + ex.addSuppressed(e); + } + } + } + } + + private static boolean isActive(int status) { + return status != STATUS_CLOSED + && status != STATUS_COMMITTED + && status != STATUS_ROLLED_BACK; + } + + /** + * Get the list of changes, starting with the latest change, up to the + * given savepoint (in reverse order than they occurred). The value of + * the change is the value before the change was applied. + * + * @param savepointId the savepoint id, 0 meaning the beginning of the + * transaction + * @return the changes + */ + public Iterator getChanges(long savepointId) { + return store.getChanges(this, getLogId(), savepointId); + } + + /** + * Sets the new lock timeout. + * + * @param timeoutMillis the new lock timeout in milliseconds + */ + public void setTimeoutMillis(int timeoutMillis) { + this.timeoutMillis = timeoutMillis > 0 ? timeoutMillis : store.timeoutMillis; + } + + private long getLogId() { + return getLogId(statusAndLogId.get()); + } + + /** + * Check whether this transaction is open. + */ + private void checkOpen(int status) { + if (status != STATUS_OPEN) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_TRANSACTION_ILLEGAL_STATE, + "Transaction {0} has status {1}, not OPEN", transactionId, getStatusName(status)); + } + } + + /** + * Check whether this transaction is open or prepared. + */ + private void checkNotClosed() { + if (getStatus() == STATUS_CLOSED) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_CLOSED, "Transaction {0} is closed", transactionId); + } + } + + /** + * Transition this transaction into a closed state. + */ + void closeIt() { + transactionMaps.clear(); + long lastState = setStatus(STATUS_CLOSED); + store.store.deregisterVersionUsage(txCounter); + if((hasChanges(lastState) || hasRollback(lastState))) { + notifyAllWaitingTransactions(); + } + } + + private void notifyAllWaitingTransactions() { + if (notificationRequested) { + synchronized (this) { + notifyAll(); + } + } + } + + /** + * Make this transaction to wait for the specified transaction to be closed, + * because both of them try to modify the same map entry. + * + * @param toWaitFor transaction to wait for + * @param mapName name of the map containing blocking entry + * @param key of the blocking entry + * @return true if other transaction was closed and this one can proceed, false if timed out + */ + public boolean waitFor(Transaction toWaitFor, String mapName, Object key) { + blockingTransaction = toWaitFor; + blockingMapName = mapName; + blockingKey = key; + if (isDeadlocked(toWaitFor)) { + tryThrowDeadLockException(false); + } + boolean result = toWaitFor.waitForThisToEnd(timeoutMillis, this); + blockingMapName = null; + blockingKey = null; + blockingTransaction = null; + return result; + } + + private boolean isDeadlocked(Transaction toWaitFor) { + // use transaction sequence No as a tie-breaker + // the youngest transaction should be selected as a victim + Transaction youngest = toWaitFor; + int backstop = store.getMaxTransactionId(); + for(Transaction tx = toWaitFor, nextTx; + (nextTx = tx.blockingTransaction) != null && tx.getStatus() == Transaction.STATUS_OPEN && backstop > 0; + tx = nextTx, --backstop) { + + if (nextTx.sequenceNum > youngest.sequenceNum) { + youngest = nextTx; + } + + if (nextTx == this) { + if (youngest == this) { + return true; + } + Transaction btx = youngest.blockingTransaction; + if (btx != null) { + youngest.setStatus(STATUS_ROLLING_BACK); + btx.notifyAllWaitingTransactions(); + return false; + } + } + } + return false; + } + + private void tryThrowDeadLockException(boolean throwIt) { + BitSet visited = new BitSet(); + StringBuilder details = new StringBuilder( + String.format("Transaction %d has been chosen as a deadlock victim. Details:%n", transactionId)); + for (Transaction tx = this, nextTx; + !visited.get(tx.transactionId) && (nextTx = tx.blockingTransaction) != null; tx = nextTx) { + visited.set(tx.transactionId); + details.append(String.format( + "Transaction %d attempts to update map <%s> entry with key <%s> modified by transaction %s%n", + tx.transactionId, tx.blockingMapName, tx.blockingKey, tx.blockingTransaction)); + if (nextTx == this) { + throwIt = true; + } + } + if (throwIt) { + throw DataUtils.newMVStoreException(DataUtils.ERROR_TRANSACTIONS_DEADLOCK, "{0}", details.toString()); + } + } + + private synchronized boolean waitForThisToEnd(int millis, Transaction waiter) { + long until = System.currentTimeMillis() + millis; + notificationRequested = true; + long state; + int status; + while((status = getStatus(state = statusAndLogId.get())) != STATUS_CLOSED + && status != STATUS_ROLLED_BACK && !hasRollback(state)) { + if (waiter.getStatus() != STATUS_OPEN) { + waiter.tryThrowDeadLockException(true); + } + long dur = until - System.currentTimeMillis(); + if(dur <= 0) { + return false; + } + try { + wait(dur); + } catch (InterruptedException ex) { + return false; + } + } + return true; + } + + /** + * Remove the map. + * + * @param the key type + * @param the value type + * @param map the map + */ + public void removeMap(TransactionMap map) { + store.removeMap(map); + } + + @Override + public String toString() { + return transactionId + "(" + sequenceNum + ") " + stateToString(); + } + + private String stateToString() { + return stateToString(statusAndLogId.get()); + } + + private static String stateToString(long state) { + return getStatusName(getStatus(state)) + (hasRollback(state) ? "<" : "") + " " + getLogId(state); + } + + + private static int getStatus(long state) { + return (int)(state >>> LOG_ID_BITS1) & STATUS_MASK; + } + + private static long getLogId(long state) { + return state & LOG_ID_MASK; + } + + private static boolean hasRollback(long state) { + return (state & (1L << (STATUS_BITS + LOG_ID_BITS1))) != 0; + } + + private static boolean hasChanges(long state) { + return getLogId(state) != 0; + } + + private static long composeState(int status, long logId, boolean hasRollback) { + assert logId < LOG_ID_LIMIT : logId; + assert (status & ~STATUS_MASK) == 0 : status; + + if (hasRollback) { + status |= 1 << STATUS_BITS; + } + return ((long)status << LOG_ID_BITS1) | logId; + } + + private static String getStatusName(int status) { + return status >= 0 && status < STATUS_NAMES.length ? STATUS_NAMES[status] : "UNKNOWN_STATUS_" + status; + } +} diff --git a/h2/src/main/org/h2/mvstore/tx/TransactionMap.java b/h2/src/main/org/h2/mvstore/tx/TransactionMap.java new file mode 100644 index 0000000..2c5d7f2 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/tx/TransactionMap.java @@ -0,0 +1,1127 @@ +/* + * 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.mvstore.tx; + +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.BitSet; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiFunction; + +import org.h2.engine.IsolationLevel; +import org.h2.mvstore.Cursor; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVStoreException; +import org.h2.mvstore.RootReference; +import org.h2.mvstore.type.DataType; +import org.h2.value.VersionedValue; + +/** + * A map that supports transactions. + * + *

+ * Methods of this class may be changed at any time without notice. If + * you use this class directly make sure that your application or library + * requires exactly the same version of MVStore or H2 jar as the version that + * you use during its development and build. + *

+ * + * @param the key type + * @param the value type + */ +public final class TransactionMap extends AbstractMap { + + /** + * The map used for writing (the latest version). + *

+ * Key: key the key of the data. + * Value: { transactionId, oldVersion, value } + */ + public final MVMap> map; + + /** + * The transaction which is used for this map. + */ + private final Transaction transaction; + + /** + * Snapshot of this map as of beginning of transaction or + * first usage within transaction or + * beginning of the statement, depending on isolation level + */ + private Snapshot> snapshot; + + /** + * Snapshot of this map as of beginning of beginning of the statement + */ + private Snapshot> statementSnapshot; + + /** + * Indicates whether underlying map was modified from within related transaction + */ + private boolean hasChanges; + + private final TxDecisionMaker txDecisionMaker; + private final TxDecisionMaker ifAbsentDecisionMaker; + private final TxDecisionMaker lockDecisionMaker; + + + TransactionMap(Transaction transaction, MVMap> map) { + this.transaction = transaction; + this.map = map; + this.txDecisionMaker = new TxDecisionMaker<>(map.getId(), transaction); + this.ifAbsentDecisionMaker = new TxDecisionMaker.PutIfAbsentDecisionMaker<>(map.getId(), + transaction, this::getFromSnapshot); + this.lockDecisionMaker = transaction.allowNonRepeatableRead() + ? new TxDecisionMaker.LockDecisionMaker<>(map.getId(), transaction) + : new TxDecisionMaker.RepeatableReadLockDecisionMaker<>(map.getId(), transaction, + map.getValueType(), this::getFromSnapshot); + + } + + /** + * Get a clone of this map for the given transaction. + * + * @param transaction the transaction + * @return the map + */ + public TransactionMap getInstance(Transaction transaction) { + return transaction.openMapX(map); + } + + /** + * Get the number of entries, as a integer. {@link Integer#MAX_VALUE} is + * returned if there are more than this entries. + * + * @return the number of entries, as an integer + * @see #sizeAsLong() + */ + @Override + public int size() { + long size = sizeAsLong(); + return size > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) size; + } + + /** + * Get the size of the raw map. This includes uncommitted entries, and + * transiently removed entries, so it is the maximum number of entries. + * + * @return the maximum size + */ + public long sizeAsLongMax() { + return map.sizeAsLong(); + } + + /** + * Get the size of the map as seen by this transaction. + * + * @return the size + */ + public long sizeAsLong() { + IsolationLevel isolationLevel = transaction.getIsolationLevel(); + if (!isolationLevel.allowNonRepeatableRead() && hasChanges) { + return sizeAsLongRepeatableReadWithChanges(); + } + // getting coherent picture of the map, committing transactions, and undo logs + // either from values stored in transaction (never loops in that case), + // or current values from the transaction store (loops until moment of silence) + Snapshot> snapshot; + RootReference>[] undoLogRootReferences; + do { + snapshot = getSnapshot(); + undoLogRootReferences = getTransaction().getUndoLogRootReferences(); + } while (!snapshot.equals(getSnapshot())); + + RootReference> mapRootReference = snapshot.root; + long size = mapRootReference.getTotalCount(); + long undoLogsTotalSize = undoLogRootReferences == null ? size + : TransactionStore.calculateUndoLogsTotalSize(undoLogRootReferences); + // if we are looking at the map without any uncommitted values + if (undoLogsTotalSize == 0) { + return size; + } + return adjustSize(undoLogRootReferences, mapRootReference, + isolationLevel == IsolationLevel.READ_UNCOMMITTED ? null : snapshot.committingTransactions, + size, undoLogsTotalSize); + } + + private long adjustSize(RootReference>[] undoLogRootReferences, + RootReference> mapRootReference, BitSet committingTransactions, long size, + long undoLogsTotalSize) { + // Entries describing removals from the map by this transaction and all transactions, + // which are committed but not closed yet, + // and entries about additions to the map by other uncommitted transactions were counted, + // but they should not contribute into total count. + if (2 * undoLogsTotalSize > size) { + // the undo log is larger than half of the map - scan the entries of the map directly + Cursor> cursor = map.cursor(mapRootReference, null, null, false); + while (cursor.hasNext()) { + cursor.next(); + VersionedValue currentValue = cursor.getValue(); + assert currentValue != null; + long operationId = currentValue.getOperationId(); + if (operationId != 0 && // skip committed entries + isIrrelevant(operationId, currentValue, committingTransactions)) { + --size; + } + } + } else { + assert undoLogRootReferences != null; + // The undo logs are much smaller than the map - scan all undo logs, + // and then lookup relevant map entry. + for (RootReference> undoLogRootReference : undoLogRootReferences) { + if (undoLogRootReference != null) { + Cursor> cursor = undoLogRootReference.root.map.cursor(undoLogRootReference, + null, null, false); + while (cursor.hasNext()) { + cursor.next(); + Record op = cursor.getValue(); + if (op.mapId == map.getId()) { + @SuppressWarnings("unchecked") + VersionedValue currentValue = map.get(mapRootReference.root, (K)op.key); + // If map entry is not there, then we never counted + // it, in the first place, so skip it. + // This is possible when undo entry exists because + // it belongs to a committed but not yet closed + // transaction, and it was later deleted by some + // other already committed and closed transaction. + if (currentValue != null) { + // only the last undo entry for any given map + // key should be considered + long operationId = cursor.getKey(); + assert operationId != 0; + if (currentValue.getOperationId() == operationId && + isIrrelevant(operationId, currentValue, committingTransactions)) { + --size; + } + } + } + } + } + } + } + return size; + } + + private boolean isIrrelevant(long operationId, VersionedValue currentValue, BitSet committingTransactions) { + Object v; + if (committingTransactions == null) { + v = currentValue.getCurrentValue(); + } else { + int txId = TransactionStore.getTransactionId(operationId); + v = txId == transaction.transactionId || committingTransactions.get(txId) + ? currentValue.getCurrentValue() : currentValue.getCommittedValue(); + } + return v == null; + } + + private long sizeAsLongRepeatableReadWithChanges() { + long count = 0L; + RepeatableIterator iterator = new RepeatableIterator<>(this, null, null, false, false); + while (iterator.fetchNext() != null) { + count++; + } + return count; + } + + /** + * Remove an entry. + *

+ * If the row is locked, this method will retry until the row could be + * updated or until a lock timeout. + * + * @param key the key + * @throws MVStoreException if a lock timeout occurs + * @throws ClassCastException if type of the specified key is not compatible with this map + */ + @SuppressWarnings("unchecked") + @Override + public V remove(Object key) { + return set((K)key, (V)null); + } + + /** + * Update the value for the given key. + *

+ * If the row is locked, this method will retry until the row could be + * updated or until a lock timeout. + * + * @param key the key + * @param value the new value (not null) + * @return the old value + * @throws MVStoreException if a lock timeout occurs + */ + @Override + public V put(K key, V value) { + DataUtils.checkArgument(value != null, "The value may not be null"); + return set(key, value); + } + + /** + * Put the value for the given key if entry for this key does not exist. + * It is atomic equivalent of the following expression: + * contains(key) ? get(k) : put(key, value); + * + * @param key the key + * @param value the new value (not null) + * @return the old value + */ + @Override + public V putIfAbsent(K key, V value) { + DataUtils.checkArgument(value != null, "The value may not be null"); + ifAbsentDecisionMaker.initialize(key, value); + V result = set(key, ifAbsentDecisionMaker); + if (ifAbsentDecisionMaker.getDecision() == MVMap.Decision.ABORT) { + result = ifAbsentDecisionMaker.getLastValue(); + } + return result; + } + + /** + * Appends entry to underlying map. This method may be used concurrently, + * but latest appended values are not guaranteed to be visible. + * @param key should be higher in map's order than any existing key + * @param value to be appended + */ + public void append(K key, V value) { + map.append(key, VersionedValueUncommitted.getInstance( + transaction.log(new Record<>(map.getId(), key, null)), value, null)); + hasChanges = true; + } + + /** + * Lock row for the given key. + *

+ * If the row is locked, this method will retry until the row could be + * updated or until a lock timeout. + * + * @param key the key + * @return the locked value + * @throws MVStoreException if a lock timeout occurs + */ + public V lock(K key) { + lockDecisionMaker.initialize(key, null); + return set(key, lockDecisionMaker); + } + + /** + * Update the value for the given key, without adding an undo log entry. + * + * @param key the key + * @param value the value + * @return the old value + */ + @SuppressWarnings("UnusedReturnValue") + public V putCommitted(K key, V value) { + DataUtils.checkArgument(value != null, "The value may not be null"); + VersionedValue newValue = VersionedValueCommitted.getInstance(value); + VersionedValue oldValue = map.put(key, newValue); + V result = oldValue == null ? null : oldValue.getCurrentValue(); + return result; + } + + private V set(K key, V value) { + txDecisionMaker.initialize(key, value); + return set(key, txDecisionMaker); + } + + private V set(Object key, TxDecisionMaker decisionMaker) { + Transaction blockingTransaction; + VersionedValue result; + String mapName = null; + do { + assert transaction.getBlockerId() == 0; + @SuppressWarnings("unchecked") + K k = (K) key; + // second parameter (value) is not really used, + // since TxDecisionMaker has it embedded + result = map.operate(k, null, decisionMaker); + + MVMap.Decision decision = decisionMaker.getDecision(); + assert decision != null; + assert decision != MVMap.Decision.REPEAT; + blockingTransaction = decisionMaker.getBlockingTransaction(); + if (decision != MVMap.Decision.ABORT || blockingTransaction == null) { + hasChanges |= decision != MVMap.Decision.ABORT; + V res = result == null ? null : result.getCurrentValue(); + return res; + } + decisionMaker.reset(); + if (mapName == null) { + mapName = map.getName(); + } + } while (transaction.waitFor(blockingTransaction, mapName, key)); + + throw DataUtils.newMVStoreException(DataUtils.ERROR_TRANSACTION_LOCKED, + "Map entry <{0}> with key <{1}> and value {2} is locked by tx {3} and can not be updated by tx {4}" + + " within allocated time interval {5} ms.", + mapName, key, result, blockingTransaction.transactionId, transaction.transactionId, + transaction.timeoutMillis); + } + + /** + * Try to remove the value for the given key. + *

+ * This will fail if the row is locked by another transaction (that + * means, if another open transaction changed the row). + * + * @param key the key + * @return whether the entry could be removed + */ + public boolean tryRemove(K key) { + return trySet(key, null); + } + + /** + * Try to update the value for the given key. + *

+ * This will fail if the row is locked by another transaction (that + * means, if another open transaction changed the row). + * + * @param key the key + * @param value the new value + * @return whether the entry could be updated + */ + public boolean tryPut(K key, V value) { + DataUtils.checkArgument(value != null, "The value may not be null"); + return trySet(key, value); + } + + /** + * Try to set or remove the value. When updating only unchanged entries, + * then the value is only changed if it was not changed after opening + * the map. + * + * @param key the key + * @param value the new value (null to remove the value) + * @return true if the value was set, false if there was a concurrent + * update + */ + public boolean trySet(K key, V value) { + try { + // TODO: effective transaction.timeoutMillis should be set to 0 here + // and restored before return + // TODO: eliminate exception usage as part of normal control flaw + set(key, value); + return true; + } catch (MVStoreException e) { + return false; + } + } + + /** + * Get the effective value for the given key. + * + * @param key the key + * @return the value or null + * @throws ClassCastException if type of the specified key is not compatible with this map + */ + @SuppressWarnings("unchecked") + @Override + public V get(Object key) { + return getImmediate((K)key); + } + + /** + * Get the value for the given key, or null if value does not exist in accordance with transactional rules. + * Value is taken from a snapshot, appropriate for an isolation level of the related transaction + * + * @param key the key + * @return the value, or null if not found + */ + public V getFromSnapshot(K key) { + switch (transaction.isolationLevel) { + case READ_UNCOMMITTED: { + Snapshot> snapshot = getStatementSnapshot(); + VersionedValue data = map.get(snapshot.root.root, key); + if (data != null) { + return data.getCurrentValue(); + } + return null; + } + case REPEATABLE_READ: + case SNAPSHOT: + case SERIALIZABLE: + if (transaction.hasChanges()) { + Snapshot> snapshot = getStatementSnapshot(); + VersionedValue data = map.get(snapshot.root.root, key); + if (data != null) { + long id = data.getOperationId(); + if (id != 0L && transaction.transactionId == TransactionStore.getTransactionId(id)) { + return data.getCurrentValue(); + } + } + } + //$FALL-THROUGH$ + case READ_COMMITTED: + default: + Snapshot> snapshot = getSnapshot(); + return getFromSnapshot(snapshot.root, snapshot.committingTransactions, key); + } + } + + private V getFromSnapshot(RootReference> rootRef, BitSet committingTransactions, K key) { + VersionedValue data = map.get(rootRef.root, key); + if (data == null) { + // doesn't exist + return null; + } + long id = data.getOperationId(); + if (id != 0) { + int tx = TransactionStore.getTransactionId(id); + if (tx != transaction.transactionId && !committingTransactions.get(tx)) { + // added/modified/removed by uncommitted transaction, change should not be visible + return data.getCommittedValue(); + } + } + // added/modified/removed by this transaction or another transaction which is committed by now + return data.getCurrentValue(); + } + + /** + * Get the value for the given key, or null if not found. + * Operation is performed on a snapshot of the map taken during this call. + * + * @param key the key + * @return the value, or null if not found + */ + public V getImmediate(K key) { + return useSnapshot((rootReference, committedTransactions) -> + getFromSnapshot(rootReference, committedTransactions, key)); + } + + Snapshot> getSnapshot() { + return snapshot == null ? createSnapshot() : snapshot; + } + + Snapshot> getStatementSnapshot() { + return statementSnapshot == null ? createSnapshot() : statementSnapshot; + } + + void setStatementSnapshot(Snapshot> snapshot) { + statementSnapshot = snapshot; + } + + void promoteSnapshot() { + if (snapshot == null) { + snapshot = statementSnapshot; + } + } + + /** + * Create a new snapshot for this map. + * + * @return the snapshot + */ + Snapshot> createSnapshot() { + return useSnapshot(Snapshot::new); + } + + /** + * Gets a coherent picture of committing transactions and root reference, + * passes it to the specified function, and returns its result. + * + * @param type of the result + * + * @param snapshotConsumer + * function to invoke on a snapshot + * @return function's result + */ + R useSnapshot(BiFunction>, BitSet, R> snapshotConsumer) { + // The purpose of the following loop is to get a coherent picture + // of a state of two independent volatile / atomic variables, + // which they had at some recent moment in time. + // In order to get such a "snapshot", we wait for a moment of silence, + // when neither of the variables concurrently changes it's value. + AtomicReference holder = transaction.store.committingTransactions; + BitSet committingTransactions = holder.get(); + while (true) { + BitSet prevCommittingTransactions = committingTransactions; + RootReference> root = map.getRoot(); + committingTransactions = holder.get(); + if (committingTransactions == prevCommittingTransactions) { + return snapshotConsumer.apply(root, committingTransactions); + } + } + } + + /** + * Whether the map contains the key. + * + * @param key the key + * @return true if the map contains an entry for this key + * @throws ClassCastException if type of the specified key is not compatible with this map + */ + @SuppressWarnings("unchecked") + @Override + public boolean containsKey(Object key) { + return getImmediate((K)key) != null; + } + + /** + * Check if the row was deleted by this transaction. + * + * @param key the key + * @return {@code true} if it was + */ + public boolean isDeletedByCurrentTransaction(K key) { + VersionedValue data = map.get(key); + if (data != null) { + long id = data.getOperationId(); + return id != 0 && TransactionStore.getTransactionId(id) == transaction.transactionId + && data.getCurrentValue() == null; + } + return false; + } + + /** + * Whether the entry for this key was added or removed from this + * session. + * + * @param key the key + * @return true if yes + */ + public boolean isSameTransaction(K key) { + VersionedValue data = map.get(key); + if (data == null) { + // doesn't exist or deleted by a committed transaction + return false; + } + int tx = TransactionStore.getTransactionId(data.getOperationId()); + return tx == transaction.transactionId; + } + + /** + * Check whether this map is closed. + * + * @return true if closed + */ + public boolean isClosed() { + return map.isClosed(); + } + + /** + * Clear the map. + */ + @Override + public void clear() { + // TODO truncate transactionally? + map.clear(); + hasChanges = true; + } + + @Override + public Set> entrySet() { + return new AbstractSet>() { + + @Override + public Iterator> iterator() { + return entryIterator(null, null); + } + + @Override + public int size() { + return TransactionMap.this.size(); + } + + @Override + public boolean contains(Object o) { + return TransactionMap.this.containsKey(o); + } + + }; + } + + /** + * Get the first entry. + * + * @return the first entry, or null if empty + */ + public Entry firstEntry() { + return this.>chooseIterator(null, null, false, true).fetchNext(); + } + + /** + * Get the first key. + * + * @return the first key, or null if empty + */ + public K firstKey() { + return this.chooseIterator(null, null, false, false).fetchNext(); + } + + /** + * Get the last entry. + * + * @return the last entry, or null if empty + */ + public Entry lastEntry() { + return this.>chooseIterator(null, null, true, true).fetchNext(); + } + + /** + * Get the last key. + * + * @return the last key, or null if empty + */ + public K lastKey() { + return this.chooseIterator(null, null, true, false).fetchNext(); + } + + /** + * Get the entry with smallest key that is larger than the given key, or null if no + * such key exists. + * + * @param key the key (may not be null) + * @return the result + */ + public Entry higherEntry(K key) { + return higherLowerEntry(key, false); + } + + /** + * Get the smallest key that is larger than the given key, or null if no + * such key exists. + * + * @param key the key (may not be null) + * @return the result + */ + public K higherKey(K key) { + return higherLowerKey(key, false); + } + + /** + * Get the entry with smallest key that is larger than or equal to this key, + * or null if no such key exists. + * + * @param key the key (may not be null) + * @return the result + */ + public Entry ceilingEntry(K key) { + return this.>chooseIterator(key, null, false, true).fetchNext(); + } + + /** + * Get the smallest key that is larger than or equal to this key, + * or null if no such key exists. + * + * @param key the key (may not be null) + * @return the result + */ + public K ceilingKey(K key) { + return this.chooseIterator(key, null, false, false).fetchNext(); + } + + /** + * Get the entry with largest key that is smaller than or equal to this key, + * or null if no such key exists. + * + * @param key the key (may not be null) + * @return the result + */ + public Entry floorEntry(K key) { + return this.>chooseIterator(key, null, true, true).fetchNext(); + } + + /** + * Get the largest key that is smaller than or equal to this key, + * or null if no such key exists. + * + * @param key the key (may not be null) + * @return the result + */ + public K floorKey(K key) { + return this.chooseIterator(key, null, true, false).fetchNext(); + } + + /** + * Get the entry with largest key that is smaller than the given key, or null if no + * such key exists. + * + * @param key the key (may not be null) + * @return the result + */ + public Entry lowerEntry(K key) { + return higherLowerEntry(key, true); + } + + /** + * Get the largest key that is smaller than the given key, or null if no + * such key exists. + * + * @param key the key (may not be null) + * @return the result + */ + public K lowerKey(K key) { + return higherLowerKey(key, true); + } + + private Entry higherLowerEntry(K key, boolean lower) { + TMIterator> it = chooseIterator(key, null, lower, true); + Entry result = it.fetchNext(); + if (result != null && map.getKeyType().compare(key, result.getKey()) == 0) { + result = it.fetchNext(); + } + return result; + } + + private K higherLowerKey(K key, boolean lower) { + TMIterator it = chooseIterator(key, null, lower, false); + K result = it.fetchNext(); + if (result != null && map.getKeyType().compare(key, result) == 0) { + result = it.fetchNext(); + } + return result; + } + + /** + * Iterate over keys. + * + * @param from the first key to return + * @return the iterator + */ + public Iterator keyIterator(K from) { + return chooseIterator(from, null, false, false); + } + + /** + * Iterate over keys in the specified order. + * + * @param from the first key to return + * @param reverse if true, iterate in reverse (descending) order + * @return the iterator + */ + public TMIterator keyIterator(K from, boolean reverse) { + return chooseIterator(from, null, reverse, false); + } + + /** + * Iterate over keys. + * + * @param from the first key to return + * @param to the last key to return or null if there is no limit + * @return the iterator + */ + public TMIterator keyIterator(K from, K to) { + return chooseIterator(from, to, false, false); + } + + /** + * Iterate over keys, including keys from uncommitted entries. + * + * @param from the first key to return + * @param to the last key to return or null if there is no limit + * @return the iterator + */ + public TMIterator keyIteratorUncommitted(K from, K to) { + return new ValidationIterator<>(this, from, to); + } + + /** + * Iterate over entries. + * + * @param from the first key to return + * @param to the last key to return + * @return the iterator + */ + public TMIterator> entryIterator(final K from, final K to) { + return chooseIterator(from, to, false, true); + } + + private TMIterator chooseIterator(K from, K to, boolean reverse, boolean forEntries) { + switch (transaction.isolationLevel) { + case READ_UNCOMMITTED: + return new UncommittedIterator<>(this, from, to, reverse, forEntries); + case REPEATABLE_READ: + case SNAPSHOT: + case SERIALIZABLE: + if (hasChanges) { + return new RepeatableIterator<>(this, from, to, reverse, forEntries); + } + //$FALL-THROUGH$ + case READ_COMMITTED: + default: + return new CommittedIterator<>(this, from, to, reverse, forEntries); + } + } + + public Transaction getTransaction() { + return transaction; + } + + public DataType getKeyType() { + return map.getKeyType(); + } + + /** + * The iterator for read uncommitted isolation level. This iterator is also + * used for unique indexes. + * + * @param + * the type of keys + * @param + * the type of elements + */ + private static class UncommittedIterator extends TMIterator { + UncommittedIterator(TransactionMap transactionMap, K from, K to, boolean reverse, boolean forEntries) { + super(transactionMap, from, to, transactionMap.createSnapshot(), reverse, forEntries); + } + + UncommittedIterator(TransactionMap transactionMap, K from, K to, Snapshot> snapshot, + boolean reverse, boolean forEntries) { + super(transactionMap, from, to, snapshot, reverse, forEntries); + } + + @Override + public final X fetchNext() { + while (cursor.hasNext()) { + K key = cursor.next(); + VersionedValue data = cursor.getValue(); + if (data != null) { + Object currentValue = data.getCurrentValue(); + if (currentValue != null || shouldIgnoreRemoval(data)) { + return toElement(key, currentValue); + } + } + } + return null; + } + + boolean shouldIgnoreRemoval(VersionedValue data) { + return false; + } + } + + + // This iterator should include all entries applicable for unique index validation, + // committed and otherwise, only excluding keys removed by the current transaction + // or by some other already committed (but not closed yet) transactions + private static final class ValidationIterator extends UncommittedIterator { + ValidationIterator(TransactionMap transactionMap, K from, K to) { + super(transactionMap, from, to, transactionMap.createSnapshot(), false, false); + } + + @Override + boolean shouldIgnoreRemoval(VersionedValue data) { + assert data.getCurrentValue() == null; + long id = data.getOperationId(); + if (id != 0) { + int tx = TransactionStore.getTransactionId(id); + return transactionId != tx && !committingTransactions.get(tx); + } + return false; + } + } + + /** + * The iterator for read committed isolation level. Can also be used on + * higher levels when the transaction doesn't have own changes. + * + * @param + * the type of keys + * @param + * the type of elements + */ + private static final class CommittedIterator extends TMIterator { + CommittedIterator(TransactionMap transactionMap, K from, K to, boolean reverse, boolean forEntries) { + super(transactionMap, from, to, transactionMap.getSnapshot(), reverse, forEntries); + } + + @Override + public X fetchNext() { + while (cursor.hasNext()) { + K key = cursor.next(); + VersionedValue data = cursor.getValue(); + // If value doesn't exist or it was deleted by a committed transaction, + // or if value is a committed one, just return it. + if (data != null) { + long id = data.getOperationId(); + if (id != 0) { + int tx = TransactionStore.getTransactionId(id); + if (tx != transactionId && !committingTransactions.get(tx)) { + // current value comes from another uncommitted transaction + // take committed value instead + Object committedValue = data.getCommittedValue(); + if (committedValue == null) { + continue; + } + return toElement(key, committedValue); + } + } + Object currentValue = data.getCurrentValue(); + if (currentValue != null) { + return toElement(key, currentValue); + } + } + } + return null; + } + } + + /** + * The iterator for repeatable read and serializable isolation levels. + * + * @param + * the type of keys + * @param + * the type of elements + */ + private static final class RepeatableIterator extends TMIterator { + private final DataType keyType; + + private K snapshotKey; + + private Object snapshotValue; + + private final Cursor> uncommittedCursor; + + private K uncommittedKey; + + private V uncommittedValue; + + RepeatableIterator(TransactionMap transactionMap, K from, K to, boolean reverse, boolean forEntries) { + super(transactionMap, from, to, transactionMap.getSnapshot(), reverse, forEntries); + keyType = transactionMap.map.getKeyType(); + Snapshot> snapshot = transactionMap.getStatementSnapshot(); + uncommittedCursor = transactionMap.map.cursor(snapshot.root, from, to, reverse); + } + + @Override + public X fetchNext() { + X next = null; + do { + if (snapshotKey == null) { + fetchSnapshot(); + } + if (uncommittedKey == null) { + fetchUncommitted(); + } + if (snapshotKey == null && uncommittedKey == null) { + break; + } + int cmp = snapshotKey == null ? 1 : + uncommittedKey == null ? -1 : + keyType.compare(snapshotKey, uncommittedKey); + if (cmp < 0) { + next = toElement(snapshotKey, snapshotValue); + snapshotKey = null; + break; + } + if (uncommittedValue != null) { + // This entry was added / updated by this transaction, use the new value + next = toElement(uncommittedKey, uncommittedValue); + } + if (cmp == 0) { // This entry was updated / deleted + snapshotKey = null; + } + uncommittedKey = null; + } while (next == null); + return next; + } + + private void fetchSnapshot() { + while (cursor.hasNext()) { + K key = cursor.next(); + VersionedValue data = cursor.getValue(); + // If value doesn't exist or it was deleted by a committed transaction, + // or if value is a committed one, just return it. + if (data != null) { + Object value = data.getCommittedValue(); + long id = data.getOperationId(); + if (id != 0) { + int tx = TransactionStore.getTransactionId(id); + if (tx == transactionId || committingTransactions.get(tx)) { + // value comes from this transaction or another committed transaction + // take current value instead instead of committed one + value = data.getCurrentValue(); + } + } + if (value != null) { + snapshotKey = key; + snapshotValue = value; + return; + } + } + } + } + + private void fetchUncommitted() { + while (uncommittedCursor.hasNext()) { + K key = uncommittedCursor.next(); + VersionedValue data = uncommittedCursor.getValue(); + if (data != null) { + long id = data.getOperationId(); + if (id != 0L && transactionId == TransactionStore.getTransactionId(id)) { + uncommittedKey = key; + uncommittedValue = data.getCurrentValue(); + return; + } + } + } + } + } + + public abstract static class TMIterator implements Iterator { + final int transactionId; + + final BitSet committingTransactions; + + protected final Cursor> cursor; + + private final boolean forEntries; + + X current; + + TMIterator(TransactionMap transactionMap, K from, K to, Snapshot> snapshot, + boolean reverse, boolean forEntries) { + Transaction transaction = transactionMap.getTransaction(); + this.transactionId = transaction.transactionId; + this.forEntries = forEntries; + this.cursor = transactionMap.map.cursor(snapshot.root, from, to, reverse); + this.committingTransactions = snapshot.committingTransactions; + } + + @SuppressWarnings("unchecked") + final X toElement(K key, Object value) { + return (X) (forEntries ? new AbstractMap.SimpleImmutableEntry<>(key, value) : key); + } + + /** + * Fetches a next entry. + * + * This method cannot be used together with {@link #hasNext()} and + * {@link #next()}. + * + * @return the next entry or {@code null} + */ + public abstract X fetchNext(); + + @Override + public final boolean hasNext() { + return current != null || (current = fetchNext()) != null; + } + + @Override + public final X next() { + X result = current; + if (result == null) { + if ((result = fetchNext()) == null) { + throw new NoSuchElementException(); + } + } else { + current = null; + } + return result; + } + + } + +} diff --git a/h2/src/main/org/h2/mvstore/tx/TransactionStore.java b/h2/src/main/org/h2/mvstore/tx/TransactionStore.java new file mode 100644 index 0000000..dcaf2c6 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/tx/TransactionStore.java @@ -0,0 +1,961 @@ +/* + * 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.mvstore.tx; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicReferenceArray; +import org.h2.engine.IsolationLevel; +import org.h2.mvstore.Cursor; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVStore; +import org.h2.mvstore.RootReference; +import org.h2.mvstore.rtree.MVRTreeMap; +import org.h2.mvstore.rtree.SpatialDataType; +import org.h2.mvstore.type.DataType; +import org.h2.mvstore.type.LongDataType; +import org.h2.mvstore.type.MetaType; +import org.h2.mvstore.type.ObjectDataType; +import org.h2.mvstore.type.StringDataType; +import org.h2.util.StringUtils; +import org.h2.value.VersionedValue; + +/** + * A store that supports concurrent MVCC read-committed transactions. + */ +public class TransactionStore { + + /** + * The store. + */ + final MVStore store; + + /** + * Default blocked transaction timeout + */ + final int timeoutMillis; + + /** + * The persisted map of prepared transactions. + * Key: transactionId, value: [ status, name ]. + */ + private final MVMap preparedTransactions; + + private final MVMap> typeRegistry; + + /** + * Undo logs. + *

+ * If the first entry for a transaction doesn't have a logId + * of 0, then the transaction is partially committed (which means rollback + * is not possible). Log entries are written before the data is changed + * (write-ahead). + *

+ * Key: opId, value: [ mapId, key, oldValue ]. + */ + @SuppressWarnings("unchecked") + final MVMap>[] undoLogs = new MVMap[MAX_OPEN_TRANSACTIONS]; + private final MVMap.Builder> undoLogBuilder; + + private final DataType dataType; + + /** + * This BitSet is used as vacancy indicator for transaction slots in transactions[]. + * It provides easy way to find first unoccupied slot, and also allows for copy-on-write + * non-blocking updates. + */ + final AtomicReference openTransactions = new AtomicReference<>(new VersionedBitSet()); + + /** + * This is intended to be the source of ultimate truth about transaction being committed. + * Once bit is set, corresponding transaction is logically committed, + * although it might be plenty of "uncommitted" entries in various maps + * and undo record are still around. + * Nevertheless, all of those should be considered by other transactions as committed. + */ + final AtomicReference committingTransactions = new AtomicReference<>(new BitSet()); + + private boolean init; + + /** + * Soft limit on the number of concurrently opened transactions. + * Not really needed but used by some test. + */ + private int maxTransactionId = MAX_OPEN_TRANSACTIONS; + + /** + * Array holding all open transaction objects. + * Position in array is "transaction id". + * VolatileReferenceArray would do the job here, but there is no such thing in Java yet + */ + private final AtomicReferenceArray transactions = + new AtomicReferenceArray<>(MAX_OPEN_TRANSACTIONS + 1); + + private static final String TYPE_REGISTRY_NAME = "_"; + + /** + * The prefix for undo log entries. + */ + public static final String UNDO_LOG_NAME_PREFIX = "undoLog"; + + // must come before open in lexicographical order + private static final char UNDO_LOG_COMMITTED = '-'; + + private static final char UNDO_LOG_OPEN = '.'; + + /** + * Hard limit on the number of concurrently opened transactions + */ + // TODO: introduce constructor parameter instead of a static field, driven by URL parameter + private static final int MAX_OPEN_TRANSACTIONS = 65535; + + /** + * Generate a string used to name undo log map for a specific transaction. + * This name will contain transaction id. + * + * @param transactionId of the corresponding transaction + * @return undo log name + */ + private static String getUndoLogName(int transactionId) { + return transactionId > 0 ? UNDO_LOG_NAME_PREFIX + UNDO_LOG_OPEN + transactionId + : UNDO_LOG_NAME_PREFIX + UNDO_LOG_OPEN; + } + + /** + * Create a new transaction store. + * + * @param store the store + */ + public TransactionStore(MVStore store) { + this(store, new ObjectDataType()); + } + + public TransactionStore(MVStore store, DataType dataType) { + this(store, new MetaType<>(null, store.backgroundExceptionHandler), dataType, 0); + } + + /** + * Create a new transaction store. + * @param store the store + * @param metaDataType the data type for type registry map values + * @param dataType default data type for map keys and values + * @param timeoutMillis lock acquisition timeout in milliseconds, 0 means no wait + */ + public TransactionStore(MVStore store, MetaType metaDataType, DataType dataType, int timeoutMillis) { + this.store = store; + this.dataType = dataType; + this.timeoutMillis = timeoutMillis; + this.typeRegistry = openTypeRegistry(store, metaDataType); + this.preparedTransactions = store.openMap("openTransactions", new MVMap.Builder<>()); + this.undoLogBuilder = createUndoLogBuilder(); + } + + @SuppressWarnings({"unchecked","rawtypes"}) + MVMap.Builder> createUndoLogBuilder() { + return new MVMap.Builder>() + .singleWriter() + .keyType(LongDataType.INSTANCE) + .valueType(new Record.Type(this)); + } + + private static MVMap> openTypeRegistry(MVStore store, MetaType metaDataType) { + MVMap.Builder> typeRegistryBuilder = + new MVMap.Builder>() + .keyType(StringDataType.INSTANCE) + .valueType(metaDataType); + return store.openMap(TYPE_REGISTRY_NAME, typeRegistryBuilder); + } + + /** + * Initialize the store without any RollbackListener. + * @see #init(RollbackListener) + */ + public void init() { + init(ROLLBACK_LISTENER_NONE); + } + + /** + * Initialize the store. This is needed before a transaction can be opened. + * If the transaction store is corrupt, this method can throw an exception, + * in which case the store can only be used for reading. + * + * @param listener to notify about transaction rollback + */ + public void init(RollbackListener listener) { + if (!init) { + for (String mapName : store.getMapNames()) { + if (mapName.startsWith(UNDO_LOG_NAME_PREFIX)) { + // Unexpectedly short name may be encountered upon upgrade from older version + // where undo log was persisted as a single map, remove it. + if (mapName.length() > UNDO_LOG_NAME_PREFIX.length()) { + // make a decision about tx status based on a log name + // to handle upgrade from a previous versions + boolean committed = mapName.charAt(UNDO_LOG_NAME_PREFIX.length()) == UNDO_LOG_COMMITTED; + if (store.hasData(mapName)) { + int transactionId = StringUtils.parseUInt31(mapName, UNDO_LOG_NAME_PREFIX.length() + 1, + mapName.length()); + VersionedBitSet openTxBitSet = openTransactions.get(); + if (!openTxBitSet.get(transactionId)) { + Object[] data = preparedTransactions.get(transactionId); + int status; + String name; + if (data == null) { + status = Transaction.STATUS_OPEN; + name = null; + } else { + status = (Integer) data[0]; + name = (String) data[1]; + } + MVMap> undoLog = store.openMap(mapName, undoLogBuilder); + undoLogs[transactionId] = undoLog; + Long lastUndoKey = undoLog.lastKey(); + assert lastUndoKey != null; + assert getTransactionId(lastUndoKey) == transactionId; + long logId = getLogId(lastUndoKey) + 1; + if (committed) { + // give it a proper name and used marker record instead + store.renameMap(undoLog, getUndoLogName(transactionId)); + markUndoLogAsCommitted(transactionId); + } else { + committed = logId > LOG_ID_MASK; + } + if (committed) { + status = Transaction.STATUS_COMMITTED; + lastUndoKey = undoLog.lowerKey(lastUndoKey); + assert lastUndoKey == null || getTransactionId(lastUndoKey) == transactionId; + logId = lastUndoKey == null ? 0 : getLogId(lastUndoKey) + 1; + } + registerTransaction(transactionId, status, name, logId, timeoutMillis, 0, + IsolationLevel.READ_COMMITTED, listener); + continue; + } + } + } + + if (!store.isReadOnly()) { + store.removeMap(mapName); + } + } + } + init = true; + } + } + + private void markUndoLogAsCommitted(int transactionId) { + addUndoLogRecord(transactionId, LOG_ID_MASK, Record.COMMIT_MARKER); + } + + /** + * Commit all transactions that are in the committed state, and + * rollback all open transactions. + */ + public void endLeftoverTransactions() { + List list = getOpenTransactions(); + for (Transaction t : list) { + int status = t.getStatus(); + if (status == Transaction.STATUS_COMMITTED) { + t.commit(); + } else if (status != Transaction.STATUS_PREPARED) { + t.rollback(); + } + } + } + + int getMaxTransactionId() { + return maxTransactionId; + } + + /** + * Set the maximum transaction id, after which ids are re-used. If the old + * transaction is still in use when re-using an old id, the new transaction + * fails. + * + * @param max the maximum id + */ + public void setMaxTransactionId(int max) { + DataUtils.checkArgument(max <= MAX_OPEN_TRANSACTIONS, + "Concurrent transactions limit is too high: {0}", max); + this.maxTransactionId = max; + } + + /** + * Check whether a given map exists. + * + * @param name the map name + * @return true if it exists + */ + public boolean hasMap(String name) { + return store.hasMap(name); + } + + private static final int LOG_ID_BITS = Transaction.LOG_ID_BITS; + private static final long LOG_ID_MASK = (1L << LOG_ID_BITS) - 1; + + /** + * Combine the transaction id and the log id to an operation id. + * + * @param transactionId the transaction id + * @param logId the log id + * @return the operation id + */ + static long getOperationId(int transactionId, long logId) { + DataUtils.checkArgument(transactionId >= 0 && transactionId < (1 << (64 - LOG_ID_BITS)), + "Transaction id out of range: {0}", transactionId); + DataUtils.checkArgument(logId >= 0 && logId <= LOG_ID_MASK, + "Transaction log id out of range: {0}", logId); + return ((long) transactionId << LOG_ID_BITS) | logId; + } + + /** + * Get the transaction id for the given operation id. + * + * @param operationId the operation id + * @return the transaction id + */ + static int getTransactionId(long operationId) { + return (int) (operationId >>> LOG_ID_BITS); + } + + /** + * Get the log id for the given operation id. + * + * @param operationId the operation id + * @return the log id + */ + static long getLogId(long operationId) { + return operationId & LOG_ID_MASK; + } + + /** + * Get the list of unclosed transactions that have pending writes. + * + * @return the list of transactions (sorted by id) + */ + public List getOpenTransactions() { + if(!init) { + init(); + } + ArrayList list = new ArrayList<>(); + int transactionId = 0; + BitSet bitSet = openTransactions.get(); + while((transactionId = bitSet.nextSetBit(transactionId + 1)) > 0) { + Transaction transaction = getTransaction(transactionId); + if(transaction != null) { + if(transaction.getStatus() != Transaction.STATUS_CLOSED) { + list.add(transaction); + } + } + } + return list; + } + + /** + * Close the transaction store. + */ + public synchronized void close() { + store.commit(); + } + + /** + * Begin a new transaction. + * + * @return the transaction + */ + public Transaction begin() { + return begin(ROLLBACK_LISTENER_NONE, timeoutMillis, 0, IsolationLevel.READ_COMMITTED); + } + + /** + * Begin a new transaction. + * @param listener to be notified in case of a rollback + * @param timeoutMillis to wait for a blocking transaction + * @param ownerId of the owner (Session?) to be reported by getBlockerId + * @param isolationLevel of new transaction + * @return the transaction + */ + public Transaction begin(RollbackListener listener, int timeoutMillis, int ownerId, + IsolationLevel isolationLevel) { + Transaction transaction = registerTransaction(0, Transaction.STATUS_OPEN, null, 0, + timeoutMillis, ownerId, isolationLevel, listener); + return transaction; + } + + private Transaction registerTransaction(int txId, int status, String name, long logId, + int timeoutMillis, int ownerId, + IsolationLevel isolationLevel, RollbackListener listener) { + int transactionId; + long sequenceNo; + boolean success; + do { + VersionedBitSet original = openTransactions.get(); + if (txId == 0) { + transactionId = original.nextClearBit(1); + } else { + transactionId = txId; + assert !original.get(transactionId); + } + if (transactionId > maxTransactionId) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_TOO_MANY_OPEN_TRANSACTIONS, + "There are {0} open transactions", + transactionId - 1); + } + VersionedBitSet clone = original.clone(); + clone.set(transactionId); + sequenceNo = clone.getVersion() + 1; + clone.setVersion(sequenceNo); + success = openTransactions.compareAndSet(original, clone); + } while(!success); + + Transaction transaction = new Transaction(this, transactionId, sequenceNo, status, name, logId, + timeoutMillis, ownerId, isolationLevel, listener); + + assert transactions.get(transactionId) == null; + transactions.set(transactionId, transaction); + + if (undoLogs[transactionId] == null) { + String undoName = getUndoLogName(transactionId); + MVMap> undoLog = store.openMap(undoName, undoLogBuilder); + undoLogs[transactionId] = undoLog; + } + return transaction; + } + + /** + * Store a transaction. + * + * @param t the transaction + */ + void storeTransaction(Transaction t) { + if (t.getStatus() == Transaction.STATUS_PREPARED || + t.getName() != null) { + Object[] v = { t.getStatus(), t.getName() }; + preparedTransactions.put(t.getId(), v); + t.wasStored = true; + } + } + + /** + * Add an undo log entry. + * + * @param transactionId id of the transaction + * @param logId sequential number of the log record within transaction + * @param record Record(mapId, key, previousValue) to add + * @return key for the added record + */ + long addUndoLogRecord(int transactionId, long logId, Record record) { + MVMap> undoLog = undoLogs[transactionId]; + long undoKey = getOperationId(transactionId, logId); + if (logId == 0 && !undoLog.isEmpty()) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_TOO_MANY_OPEN_TRANSACTIONS, + "An old transaction with the same id " + + "is still open: {0}", + transactionId); + } + undoLog.append(undoKey, record); + return undoKey; + } + + /** + * Remove an undo log entry. + * @param transactionId id of the transaction + */ + void removeUndoLogRecord(int transactionId) { + undoLogs[transactionId].trimLast(); + } + + /** + * Remove the given map. + * + * @param map the map + */ + void removeMap(TransactionMap map) { + store.removeMap(map.map); + } + + /** + * Commit a transaction. + * @param t transaction to commit + * @param recovery if called during initial transaction recovery procedure + * therefore undo log is stored under "committed" name already + */ + void commit(Transaction t, boolean recovery) { + if (!store.isClosed()) { + int transactionId = t.transactionId; + // First, mark log as "committed". + // It does not change the way this transaction is treated by others, + // but preserves fact of commit in case of abrupt termination. + MVMap> undoLog = undoLogs[transactionId]; + Cursor> cursor; + if(recovery) { + removeUndoLogRecord(transactionId); + cursor = undoLog.cursor(null); + } else { + cursor = undoLog.cursor(null); + markUndoLogAsCommitted(transactionId); + } + + // this is an atomic action that causes all changes + // made by this transaction, to be considered as "committed" + flipCommittingTransactionsBit(transactionId, true); + + CommitDecisionMaker commitDecisionMaker = new CommitDecisionMaker<>(); + try { + while (cursor.hasNext()) { + Long undoKey = cursor.next(); + Record op = cursor.getValue(); + int mapId = op.mapId; + MVMap> map = openMap(mapId); + if (map != null && !map.isClosed()) { // might be null if map was removed later + Object key = op.key; + commitDecisionMaker.setUndoKey(undoKey); + // second parameter (value) is not really + // used by CommitDecisionMaker + map.operate(key, null, commitDecisionMaker); + } + } + } finally { + try { + undoLog.clear(); + } finally { + flipCommittingTransactionsBit(transactionId, false); + } + } + } + } + + private void flipCommittingTransactionsBit(int transactionId, boolean flag) { + boolean success; + do { + BitSet original = committingTransactions.get(); + assert original.get(transactionId) != flag : flag ? "Double commit" : "Mysterious bit's disappearance"; + BitSet clone = (BitSet) original.clone(); + clone.set(transactionId, flag); + success = committingTransactions.compareAndSet(original, clone); + } while(!success); + } + + MVMap> openVersionedMap(String name, DataType keyType, DataType valueType) { + VersionedValueType vt = valueType == null ? null : new VersionedValueType<>(valueType); + return openMap(name, keyType, vt); + } + + /** + * Open the map with the given name. + * + * @param the key type + * @param the value type + * @param name the map name + * @param keyType the key type + * @param valueType the value type + * @return the map + */ + public MVMap openMap(String name, DataType keyType, DataType valueType) { + return store.openMap(name, new TxMapBuilder(typeRegistry, dataType) + .keyType(keyType).valueType(valueType)); + } + + /** + * Open the map with the given id. + * + * @param key type + * @param value type + * + * @param mapId the id + * @return the map + */ + MVMap> openMap(int mapId) { + MVMap> map = store.getMap(mapId); + if (map == null) { + String mapName = store.getMapName(mapId); + if (mapName == null) { + // the map was removed later on + return null; + } + MVMap.Builder> txMapBuilder = new TxMapBuilder<>(typeRegistry, dataType); + map = store.openMap(mapId, txMapBuilder); + } + return map; + } + + MVMap> getMap(int mapId) { + MVMap> map = store.getMap(mapId); + if (map == null && !init) { + map = openMap(mapId); + } + assert map != null : "map with id " + mapId + " is missing" + + (init ? "" : " during initialization"); + return map; + } + + /** + * End this transaction. Change status to CLOSED and vacate transaction slot. + * Will try to commit MVStore if autocommitDelay is 0 or if database is idle + * and amount of unsaved changes is sizable. + * + * @param t the transaction + * @param hasChanges true if transaction has done any updates + * (even if they are fully rolled back), + * false if it just performed a data access + */ + void endTransaction(Transaction t, boolean hasChanges) { + t.closeIt(); + int txId = t.transactionId; + transactions.set(txId, null); + + boolean success; + do { + VersionedBitSet original = openTransactions.get(); + assert original.get(txId); + VersionedBitSet clone = original.clone(); + clone.clear(txId); + success = openTransactions.compareAndSet(original, clone); + } while(!success); + + if (hasChanges) { + boolean wasStored = t.wasStored; + if (wasStored && !preparedTransactions.isClosed()) { + preparedTransactions.remove(txId); + } + + if (store.isVersioningRequired()) { + if (wasStored || store.getAutoCommitDelay() == 0) { + store.commit(); + } else { + if (isUndoEmpty()) { + // to avoid having to store the transaction log, + // if there is no open transaction, + // and if there have been many changes, store them now + int unsaved = store.getUnsavedMemory(); + int max = store.getAutoCommitMemory(); + // save at 3/4 capacity + if (unsaved * 4 > max * 3) { + store.tryCommit(); + } + } + } + } + } + } + + /** + * Get the root references (snapshots) for undo-log maps. + * Those snapshots can potentially be used to optimize TransactionMap.size(). + * + * @return the array of root references or null if snapshotting is not possible + */ + RootReference>[] collectUndoLogRootReferences() { + BitSet opentransactions = openTransactions.get(); + @SuppressWarnings("unchecked") + RootReference>[] undoLogRootReferences = new RootReference[opentransactions.length()]; + for (int i = opentransactions.nextSetBit(0); i >= 0; i = opentransactions.nextSetBit(i+1)) { + MVMap> undoLog = undoLogs[i]; + if (undoLog != null) { + RootReference> rootReference = undoLog.getRoot(); + if (rootReference.needFlush()) { + // abort attempt to collect snapshots for all undo logs + // because map's append buffer can't be flushed from a non-owning thread + return null; + } + undoLogRootReferences[i] = rootReference; + } + } + return undoLogRootReferences; + } + + /** + * Calculate the size for undo log entries. + * + * @param undoLogRootReferences the root references + * @return the number of key-value pairs + */ + static long calculateUndoLogsTotalSize(RootReference>[] undoLogRootReferences) { + long undoLogsTotalSize = 0; + for (RootReference> rootReference : undoLogRootReferences) { + if (rootReference != null) { + undoLogsTotalSize += rootReference.getTotalCount(); + } + } + return undoLogsTotalSize; + } + + private boolean isUndoEmpty() { + BitSet openTrans = openTransactions.get(); + for (int i = openTrans.nextSetBit(0); i >= 0; i = openTrans.nextSetBit(i + 1)) { + MVMap> undoLog = undoLogs[i]; + if (undoLog != null && !undoLog.isEmpty()) { + return false; + } + } + return true; + } + + /** + * Get Transaction object for a transaction id. + * + * @param transactionId id for an open transaction + * @return Transaction object. + */ + Transaction getTransaction(int transactionId) { + return transactions.get(transactionId); + } + + /** + * Rollback to an old savepoint. + * + * @param t the transaction + * @param maxLogId the last log id + * @param toLogId the log id to roll back to + */ + void rollbackTo(Transaction t, long maxLogId, long toLogId) { + int transactionId = t.getId(); + MVMap> undoLog = undoLogs[transactionId]; + RollbackDecisionMaker decisionMaker = new RollbackDecisionMaker(this, transactionId, toLogId, t.listener); + for (long logId = maxLogId - 1; logId >= toLogId; logId--) { + Long undoKey = getOperationId(transactionId, logId); + undoLog.operate(undoKey, null, decisionMaker); + decisionMaker.reset(); + } + } + + /** + * Get the changes of the given transaction, starting from the latest log id + * back to the given log id. + * + * @param t the transaction + * @param maxLogId the maximum log id + * @param toLogId the minimum log id + * @return the changes + */ + Iterator getChanges(final Transaction t, final long maxLogId, + final long toLogId) { + + final MVMap> undoLog = undoLogs[t.getId()]; + return new Iterator() { + + private long logId = maxLogId - 1; + private Change current; + + private void fetchNext() { + int transactionId = t.getId(); + while (logId >= toLogId) { + Long undoKey = getOperationId(transactionId, logId); + Record op = undoLog.get(undoKey); + logId--; + if (op == null) { + // partially rolled back: load previous + undoKey = undoLog.floorKey(undoKey); + if (undoKey == null || getTransactionId(undoKey) != transactionId) { + break; + } + logId = getLogId(undoKey); + continue; + } + int mapId = op.mapId; + MVMap> m = openMap(mapId); + if (m != null) { // could be null if map was removed later on + VersionedValue oldValue = op.oldValue; + current = new Change(m.getName(), op.key, + oldValue == null ? null : oldValue.getCurrentValue()); + return; + } + } + current = null; + } + + @Override + public boolean hasNext() { + if(current == null) { + fetchNext(); + } + return current != null; + } + + @Override + public Change next() { + if(!hasNext()) { + throw DataUtils.newUnsupportedOperationException("no data"); + } + Change result = current; + current = null; + return result; + } + + }; + } + + /** + * A change in a map. + */ + public static class Change { + + /** + * The name of the map where the change occurred. + */ + public final String mapName; + + /** + * The key. + */ + public final Object key; + + /** + * The value. + */ + public final Object value; + + public Change(String mapName, Object key, Object value) { + this.mapName = mapName; + this.key = key; + this.value = value; + } + } + + /** + * This listener can be registered with the transaction to be notified of + * every compensating change during transaction rollback. + * Normally this is not required, if no external resources were modified, + * because state of all transactional maps will be restored automatically. + * Only state of external resources, possibly modified by triggers + * need to be restored. + */ + public interface RollbackListener { + + /** + * Notified of a single map change (add/update/remove) + * @param map modified + * @param key of the modified entry + * @param existingValue value in the map (null if delete is rolled back) + * @param restoredValue value to be restored (null if add is rolled back) + */ + void onRollback(MVMap> map, Object key, + VersionedValue existingValue, VersionedValue restoredValue); + } + + private static final RollbackListener ROLLBACK_LISTENER_NONE = (map, key, existingValue, restoredValue) -> {}; + + private static final class TxMapBuilder extends MVMap.Builder { + + private final MVMap> typeRegistry; + private final DataType defaultDataType; + + TxMapBuilder(MVMap> typeRegistry, DataType defaultDataType) { + this.typeRegistry = typeRegistry; + this.defaultDataType = defaultDataType; + } + + private void registerDataType(DataType dataType) { + String key = getDataTypeRegistrationKey(dataType); + DataType registeredDataType = typeRegistry.putIfAbsent(key, dataType); + if(registeredDataType != null) { + // TODO: ensure type consistency + } + } + + static String getDataTypeRegistrationKey(DataType dataType) { + return Integer.toHexString(Objects.hashCode(dataType)); + } + + @SuppressWarnings("unchecked") + @Override + public MVMap create(MVStore store, Map config) { + DataType keyType = getKeyType(); + if (keyType == null) { + String keyTypeKey = (String) config.remove("key"); + if (keyTypeKey != null) { + keyType = (DataType)typeRegistry.get(keyTypeKey); + if (keyType == null) { + throw DataUtils.newMVStoreException(DataUtils.ERROR_UNKNOWN_DATA_TYPE, + "Data type with hash {0} can not be found", keyTypeKey); + } + setKeyType(keyType); + } + } else { + registerDataType(keyType); + } + + DataType valueType = getValueType(); + if (valueType == null) { + String valueTypeKey = (String) config.remove("val"); + if (valueTypeKey != null) { + valueType = (DataType)typeRegistry.get(valueTypeKey); + if (valueType == null) { + throw DataUtils.newMVStoreException(DataUtils.ERROR_UNKNOWN_DATA_TYPE, + "Data type with hash {0} can not be found", valueTypeKey); + } + setValueType(valueType); + } + } else { + registerDataType(valueType); + } + + if (getKeyType() == null) { + setKeyType(defaultDataType); + registerDataType(getKeyType()); + } + if (getValueType() == null) { + setValueType((DataType) new VersionedValueType(defaultDataType)); + registerDataType(getValueType()); + } + + config.put("store", store); + config.put("key", getKeyType()); + config.put("val", getValueType()); + return create(config); + } + + @Override + @SuppressWarnings("unchecked") + protected MVMap create(Map config) { + if ("rtree".equals(config.get("type"))) { + MVMap map = (MVMap) new MVRTreeMap<>(config, (SpatialDataType) getKeyType(), + getValueType()); + return map; + } + return new TMVMap<>(config, getKeyType(), getValueType()); + } + + private static final class TMVMap extends MVMap { + private final String type; + + TMVMap(Map config, DataType keyType, DataType valueType) { + super(config, keyType, valueType); + type = (String)config.get("type"); + } + + private TMVMap(MVMap source) { + super(source); + type = source.getType(); + } + + @Override + protected MVMap cloneIt() { + return new TMVMap<>(this); + } + + @Override + public String getType() { + return type; + } + + @Override + protected String asString(String name) { + StringBuilder buff = new StringBuilder(); + buff.append(super.asString(name)); + DataUtils.appendMap(buff, "key", getDataTypeRegistrationKey(getKeyType())); + DataUtils.appendMap(buff, "val", getDataTypeRegistrationKey(getValueType())); + return buff.toString(); + } + } + } +} diff --git a/h2/src/main/org/h2/mvstore/tx/TxDecisionMaker.java b/h2/src/main/org/h2/mvstore/tx/TxDecisionMaker.java new file mode 100644 index 0000000..2ab6535 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/tx/TxDecisionMaker.java @@ -0,0 +1,383 @@ +/* + * 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.mvstore.tx; + +import java.util.function.Function; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVMap.Decision; +import org.h2.mvstore.type.DataType; +import org.h2.value.VersionedValue; + +/** + * Class TxDecisionMaker is a base implementation of MVMap.DecisionMaker + * to be used for TransactionMap modification. + * + * @author Andrei Tokar + */ +class TxDecisionMaker extends MVMap.DecisionMaker> { + /** + * Map to decide upon + */ + private final int mapId; + + /** + * Key for the map entry to decide upon + */ + protected K key; + + /** + * Value for the map entry + */ + private V value; + + /** + * Transaction we are operating within + */ + private final Transaction transaction; + + /** + * Id for the undo log entry created for this modification + */ + private long undoKey; + + /** + * Id of the last operation, we decided to + * {@link org.h2.mvstore.MVMap.Decision#REPEAT}. + */ + private long lastOperationId; + + private Transaction blockingTransaction; + private MVMap.Decision decision; + private V lastValue; + + TxDecisionMaker(int mapId, Transaction transaction) { + this.mapId = mapId; + this.transaction = transaction; + } + + void initialize(K key, V value) { + this.key = key; + this.value = value; + decision = null; + reset(); + } + + @Override + public MVMap.Decision decide(VersionedValue existingValue, VersionedValue providedValue) { + assert decision == null; + long id; + int blockingId; + // if map does not have that entry yet + if (existingValue == null || + // or entry is a committed one + (id = existingValue.getOperationId()) == 0 || + // or it came from the same transaction + isThisTransaction(blockingId = TransactionStore.getTransactionId(id))) { + logAndDecideToPut(existingValue, existingValue == null ? null : existingValue.getCommittedValue()); + } else if (isCommitted(blockingId)) { + // Condition above means that entry belongs to a committing transaction. + // We assume that we are looking at the final value for this transaction, + // and if it's not the case, then it will fail later, + // because a tree root has definitely been changed. + V currentValue = existingValue.getCurrentValue(); + logAndDecideToPut(currentValue == null ? null : VersionedValueCommitted.getInstance(currentValue), + currentValue); + } else if (getBlockingTransaction() != null) { + // this entry comes from a different transaction, and this + // transaction is not committed yet + // should wait on blockingTransaction that was determined earlier + lastValue = existingValue.getCurrentValue(); + decision = MVMap.Decision.ABORT; + } else if (isRepeatedOperation(id)) { + // There is no transaction with that id, and we've tried it just + // before, but map root has not changed (which must be the case if + // we just missed a closed transaction), therefore we came back here + // again. + // Now we assume it's a leftover after unclean shutdown (map update + // was written but not undo log), and will effectively roll it back + // (just assume committed value and overwrite). + V committedValue = existingValue.getCommittedValue(); + logAndDecideToPut(committedValue == null ? null : VersionedValueCommitted.getInstance(committedValue), + committedValue); + } else { + // transaction has been committed/rolled back and is closed by now, so + // we can retry immediately and either that entry become committed + // or we'll hit case above + decision = MVMap.Decision.REPEAT; + } + return decision; + } + + @Override + public final void reset() { + if (decision != MVMap.Decision.REPEAT) { + lastOperationId = 0; + if (decision == MVMap.Decision.PUT) { + // positive decision has been made already and undo record created, + // but map was updated afterwards and undo record deletion required + transaction.logUndo(); + } + } + blockingTransaction = null; + decision = null; + lastValue = null; + } + + @SuppressWarnings("unchecked") + @Override + // always return value (ignores existingValue) + public > T selectValue(T existingValue, T providedValue) { + return (T) VersionedValueUncommitted.getInstance(undoKey, getNewValue(existingValue), lastValue); + } + + /** + * Get the new value. + * This implementation always return the current value (ignores the parameter). + * + * @param existingValue the parameter value + * @return the current value. + */ + V getNewValue(VersionedValue existingValue) { + return value; + } + + /** + * Create undo log entry and record for future references + * {@link org.h2.mvstore.MVMap.Decision#PUT} decision along with last known + * committed value + * + * @param valueToLog previous value to be logged + * @param lastValue last known committed value + * @return {@link org.h2.mvstore.MVMap.Decision#PUT} + */ + MVMap.Decision logAndDecideToPut(VersionedValue valueToLog, V lastValue) { + undoKey = transaction.log(new Record<>(mapId, key, valueToLog)); + this.lastValue = lastValue; + return setDecision(MVMap.Decision.PUT); + } + + final MVMap.Decision decideToAbort(V lastValue) { + this.lastValue = lastValue; + return setDecision(Decision.ABORT); + } + + final boolean allowNonRepeatableRead() { + return transaction.allowNonRepeatableRead(); + } + + final MVMap.Decision getDecision() { + return decision; + } + + final Transaction getBlockingTransaction() { + return blockingTransaction; + } + + final V getLastValue() { + return lastValue; + } + + /** + * Check whether specified transaction id belongs to "current" transaction + * (transaction we are acting within). + * + * @param transactionId to check + * @return true it it is "current" transaction's id, false otherwise + */ + final boolean isThisTransaction(int transactionId) { + return transactionId == transaction.transactionId; + } + + /** + * Determine whether specified id corresponds to a logically committed transaction. + * In case of pending transaction, reference to actual Transaction object (if any) + * is preserved for future use. + * + * @param transactionId to use + * @return true if transaction should be considered as committed, false otherwise + */ + final boolean isCommitted(int transactionId) { + Transaction blockingTx; + boolean result; + TransactionStore store = transaction.store; + do { + blockingTx = store.getTransaction(transactionId); + result = store.committingTransactions.get().get(transactionId); + } while (blockingTx != store.getTransaction(transactionId)); + + if (!result) { + blockingTransaction = blockingTx; + } + return result; + } + + /** + * Store operation id provided, but before that, compare it against last stored one. + * This is to prevent an infinite loop in case of uncommitted "leftover" entry + * (one without a corresponding undo log entry, most likely as a result of unclean shutdown). + * + * @param id + * for the operation we decided to + * {@link org.h2.mvstore.MVMap.Decision#REPEAT} + * @return true if the same as last operation id, false otherwise + */ + final boolean isRepeatedOperation(long id) { + if (id == lastOperationId) { + return true; + } + lastOperationId = id; + return false; + } + + /** + * Record for future references specified value as a decision that has been made. + * + * @param decision made + * @return argument provided + */ + final MVMap.Decision setDecision(MVMap.Decision decision) { + return this.decision = decision; + } + + @Override + public final String toString() { + return "txdm " + transaction.transactionId; + } + + + + public static final class PutIfAbsentDecisionMaker extends TxDecisionMaker { + private final Function oldValueSupplier; + + PutIfAbsentDecisionMaker(int mapId, Transaction transaction, Function oldValueSupplier) { + super(mapId, transaction); + this.oldValueSupplier = oldValueSupplier; + } + + @Override + public MVMap.Decision decide(VersionedValue existingValue, VersionedValue providedValue) { + assert getDecision() == null; + int blockingId; + // if map does not have that entry yet + if (existingValue == null) { + V snapshotValue = getValueInSnapshot(); + if (snapshotValue != null) { + // value exists in a snapshot but not in current map, therefore + // it was removed and committed by another transaction + return decideToAbort(snapshotValue); + } + return logAndDecideToPut(null, null); + } else { + long id = existingValue.getOperationId(); + if (id == 0 // entry is a committed one + // or it came from the same transaction + || isThisTransaction(blockingId = TransactionStore.getTransactionId(id))) { + if(existingValue.getCurrentValue() != null) { + return decideToAbort(existingValue.getCurrentValue()); + } + if (id == 0) { + V snapshotValue = getValueInSnapshot(); + if (snapshotValue != null) { + return decideToAbort(snapshotValue); + } + } + return logAndDecideToPut(existingValue, existingValue.getCommittedValue()); + } else if (isCommitted(blockingId)) { + // entry belongs to a committing transaction + // and therefore will be committed soon + if(existingValue.getCurrentValue() != null) { + return decideToAbort(existingValue.getCurrentValue()); + } + // even if that commit will result in entry removal + // current operation should fail within repeatable read transaction + // if initial snapshot carries some value + V snapshotValue = getValueInSnapshot(); + if (snapshotValue != null) { + return decideToAbort(snapshotValue); + } + return logAndDecideToPut(null, null); + } else if (getBlockingTransaction() != null) { + // this entry comes from a different transaction, and this + // transaction is not committed yet + // should wait on blockingTransaction that was determined + // earlier and then try again + return decideToAbort(existingValue.getCurrentValue()); + } else if (isRepeatedOperation(id)) { + // There is no transaction with that id, and we've tried it + // just before, but map root has not changed (which must be + // the case if we just missed a closed transaction), + // therefore we came back here again. + // Now we assume it's a leftover after unclean shutdown (map + // update was written but not undo log), and will + // effectively roll it back (just assume committed value and + // overwrite). + V committedValue = existingValue.getCommittedValue(); + if (committedValue != null) { + return decideToAbort(committedValue); + } + return logAndDecideToPut(null, null); + } else { + // transaction has been committed/rolled back and is closed + // by now, so we can retry immediately and either that entry + // become committed or we'll hit case above + return setDecision(MVMap.Decision.REPEAT); + } + } + } + + private V getValueInSnapshot() { + return allowNonRepeatableRead() ? null : oldValueSupplier.apply(key); + } + } + + + public static class LockDecisionMaker extends TxDecisionMaker { + + LockDecisionMaker(int mapId, Transaction transaction) { + super(mapId, transaction); + } + + @Override + public MVMap.Decision decide(VersionedValue existingValue, VersionedValue providedValue) { + MVMap.Decision decision = super.decide(existingValue, providedValue); + if (existingValue == null) { + assert decision == MVMap.Decision.PUT; + decision = setDecision(MVMap.Decision.REMOVE); + } + return decision; + } + + @Override + V getNewValue(VersionedValue existingValue) { + return existingValue == null ? null : existingValue.getCurrentValue(); + } + } + + public static final class RepeatableReadLockDecisionMaker extends LockDecisionMaker { + + private final DataType> valueType; + + private final Function snapshotValueSupplier; + + RepeatableReadLockDecisionMaker(int mapId, Transaction transaction, + DataType> valueType, Function snapshotValueSupplier) { + super(mapId, transaction); + this.valueType = valueType; + this.snapshotValueSupplier = snapshotValueSupplier; + } + + @Override + Decision logAndDecideToPut(VersionedValue valueToLog, V value) { + V snapshotValue = snapshotValueSupplier.apply(key); + if (snapshotValue != null && (valueToLog == null + || valueType.compare(VersionedValueCommitted.getInstance(snapshotValue), valueToLog) != 0)) { + throw DataUtils.newMVStoreException(DataUtils.ERROR_TRANSACTIONS_DEADLOCK, ""); + } + return super.logAndDecideToPut(valueToLog, value); + } + } +} diff --git a/h2/src/main/org/h2/mvstore/tx/VersionedBitSet.java b/h2/src/main/org/h2/mvstore/tx/VersionedBitSet.java new file mode 100644 index 0000000..e0d8351 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/tx/VersionedBitSet.java @@ -0,0 +1,33 @@ +/* + * 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.mvstore.tx; + +import java.util.BitSet; + +/** + * Class VersionedBitSet extends standard BitSet to add a version field. + * This will allow bit set and version to be changed atomically. + */ +final class VersionedBitSet extends BitSet { + private static final long serialVersionUID = 1L; + + private long version; + + public VersionedBitSet() {} + + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } + + @Override + public VersionedBitSet clone() { + return (VersionedBitSet)super.clone(); + } +} diff --git a/h2/src/main/org/h2/mvstore/tx/VersionedValueCommitted.java b/h2/src/main/org/h2/mvstore/tx/VersionedValueCommitted.java new file mode 100644 index 0000000..3d0df25 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/tx/VersionedValueCommitted.java @@ -0,0 +1,53 @@ +/* + * 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.mvstore.tx; + +import org.h2.value.VersionedValue; + +/** + * Class CommittedVersionedValue. + * + * @author Andrei Tokar + */ +class VersionedValueCommitted extends VersionedValue { + /** + * The current value. + */ + public final T value; + + VersionedValueCommitted(T value) { + this.value = value; + } + + /** + * Either cast to VersionedValue, or wrap in VersionedValueCommitted + * + * @param type of the value to get the VersionedValue for + * + * @param value the object to cast/wrap + * @return VersionedValue instance + */ + @SuppressWarnings("unchecked") + static VersionedValue getInstance(X value) { + assert value != null; + return value instanceof VersionedValue ? (VersionedValue)value : new VersionedValueCommitted<>(value); + } + + @Override + public T getCurrentValue() { + return value; + } + + @Override + public T getCommittedValue() { + return value; + } + + @Override + public String toString() { + return String.valueOf(value); + } +} diff --git a/h2/src/main/org/h2/mvstore/tx/VersionedValueType.java b/h2/src/main/org/h2/mvstore/tx/VersionedValueType.java new file mode 100644 index 0000000..a088b70 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/tx/VersionedValueType.java @@ -0,0 +1,164 @@ +/* + * 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.mvstore.tx; + +import java.nio.ByteBuffer; +import org.h2.engine.Constants; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.WriteBuffer; +import org.h2.mvstore.type.BasicDataType; +import org.h2.mvstore.type.DataType; +import org.h2.mvstore.type.MetaType; +import org.h2.mvstore.type.StatefulDataType; +import org.h2.value.VersionedValue; + +/** + * The value type for a versioned value. + */ +public class VersionedValueType extends BasicDataType> implements StatefulDataType { + + private final DataType valueType; + private final Factory factory = new Factory<>(); + + + public VersionedValueType(DataType valueType) { + this.valueType = valueType; + } + + @Override + @SuppressWarnings("unchecked") + public VersionedValue[] createStorage(int size) { + return new VersionedValue[size]; + } + + @Override + public int getMemory(VersionedValue v) { + if(v == null) return 0; + int res = Constants.MEMORY_OBJECT + 8 + 2 * Constants.MEMORY_POINTER + + getValMemory(v.getCurrentValue()); + if (v.getOperationId() != 0) { + res += getValMemory(v.getCommittedValue()); + } + return res; + } + + private int getValMemory(T obj) { + return obj == null ? 0 : valueType.getMemory(obj); + } + + @Override + public void read(ByteBuffer buff, Object storage, int len) { + if (buff.get() == 0) { + // fast path (no op ids or null entries) + for (int i = 0; i < len; i++) { + cast(storage)[i] = VersionedValueCommitted.getInstance(valueType.read(buff)); + } + } else { + // slow path (some entries may be null) + for (int i = 0; i < len; i++) { + cast(storage)[i] = read(buff); + } + } + } + + @Override + public VersionedValue read(ByteBuffer buff) { + long operationId = DataUtils.readVarLong(buff); + if (operationId == 0) { + return VersionedValueCommitted.getInstance(valueType.read(buff)); + } else { + byte flags = buff.get(); + T value = (flags & 1) != 0 ? valueType.read(buff) : null; + T committedValue = (flags & 2) != 0 ? valueType.read(buff) : null; + return VersionedValueUncommitted.getInstance(operationId, value, committedValue); + } + } + + @Override + public void write(WriteBuffer buff, Object storage, int len) { + boolean fastPath = true; + for (int i = 0; i < len; i++) { + VersionedValue v = cast(storage)[i]; + if (v.getOperationId() != 0 || v.getCurrentValue() == null) { + fastPath = false; + } + } + if (fastPath) { + buff.put((byte) 0); + for (int i = 0; i < len; i++) { + VersionedValue v = cast(storage)[i]; + valueType.write(buff, v.getCurrentValue()); + } + } else { + // slow path: + // store op ids, and some entries may be null + buff.put((byte) 1); + for (int i = 0; i < len; i++) { + write(buff, cast(storage)[i]); + } + } + } + + @Override + public void write(WriteBuffer buff, VersionedValue v) { + long operationId = v.getOperationId(); + buff.putVarLong(operationId); + if (operationId == 0) { + valueType.write(buff, v.getCurrentValue()); + } else { + T committedValue = v.getCommittedValue(); + int flags = (v.getCurrentValue() == null ? 0 : 1) | (committedValue == null ? 0 : 2); + buff.put((byte) flags); + if (v.getCurrentValue() != null) { + valueType.write(buff, v.getCurrentValue()); + } + if (committedValue != null) { + valueType.write(buff, committedValue); + } + } + } + + @Override + @SuppressWarnings("unchecked") + public boolean equals(Object obj) { + if (obj == this) { + return true; + } else if (!(obj instanceof VersionedValueType)) { + return false; + } + VersionedValueType other = (VersionedValueType) obj; + return valueType.equals(other.valueType); + } + + @Override + public int hashCode() { + return super.hashCode() ^ valueType.hashCode(); + } + + @Override + public void save(WriteBuffer buff, MetaType metaType) { + metaType.write(buff, valueType); + } + + @Override + public int compare(VersionedValue a, VersionedValue b) { + return valueType.compare(a.getCurrentValue(), b.getCurrentValue()); + } + + @Override + public Factory getFactory() { + return factory; + } + + public static final class Factory implements StatefulDataType.Factory { + @SuppressWarnings("unchecked") + @Override + public DataType create(ByteBuffer buff, MetaType metaType, D database) { + DataType> valueType = (DataType>)metaType.read(buff); + return new VersionedValueType,D>(valueType); + } + } +} diff --git a/h2/src/main/org/h2/mvstore/tx/VersionedValueUncommitted.java b/h2/src/main/org/h2/mvstore/tx/VersionedValueUncommitted.java new file mode 100644 index 0000000..dad0b33 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/tx/VersionedValueUncommitted.java @@ -0,0 +1,61 @@ +/* + * 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.mvstore.tx; + +import org.h2.value.VersionedValue; + +/** + * Class VersionedValueUncommitted. + * + * @author Andrei Tokar + */ +class VersionedValueUncommitted extends VersionedValueCommitted { + private final long operationId; + private final T committedValue; + + private VersionedValueUncommitted(long operationId, T value, T committedValue) { + super(value); + assert operationId != 0; + this.operationId = operationId; + this.committedValue = committedValue; + } + + /** + * Create new VersionedValueUncommitted. + * + * @param type of the value to get the VersionedValue for + * + * @param operationId combined log/transaction id + * @param value value before commit + * @param committedValue value after commit + * @return VersionedValue instance + */ + static VersionedValue getInstance(long operationId, X value, X committedValue) { + return new VersionedValueUncommitted<>(operationId, value, committedValue); + } + + @Override + public boolean isCommitted() { + return false; + } + + @Override + public long getOperationId() { + return operationId; + } + + @Override + public T getCommittedValue() { + return committedValue; + } + + @Override + public String toString() { + return super.toString() + + " " + TransactionStore.getTransactionId(operationId) + "/" + + TransactionStore.getLogId(operationId) + " " + committedValue; + } +} diff --git a/h2/src/main/org/h2/mvstore/tx/package.html b/h2/src/main/org/h2/mvstore/tx/package.html new file mode 100644 index 0000000..08b0f02 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/tx/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Helper classes to use the MVStore in a transactional manner. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/mvstore/type/BasicDataType.java b/h2/src/main/org/h2/mvstore/type/BasicDataType.java new file mode 100644 index 0000000..d9c79e6 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/type/BasicDataType.java @@ -0,0 +1,98 @@ +/* + * 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.mvstore.type; + +import java.nio.ByteBuffer; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.WriteBuffer; + +/** + * The base class for data type implementations. + * + * @author Andrei Tokar + */ +public abstract class BasicDataType implements DataType { + + @Override + public abstract int getMemory(T obj); + + @Override + public abstract void write(WriteBuffer buff, T obj); + + @Override + public abstract T read(ByteBuffer buff); + + @Override + public int compare(T a, T b) { + throw DataUtils.newUnsupportedOperationException("Can not compare"); + } + + @Override + public boolean isMemoryEstimationAllowed() { + return true; + } + + @Override + public int binarySearch(T key, Object storageObj, int size, int initialGuess) { + T[] storage = cast(storageObj); + int low = 0; + int high = size - 1; + // the cached index minus one, so that + // for the first time (when cachedCompare is 0), + // the default value is used + int x = initialGuess - 1; + if (x < 0 || x > high) { + x = high >>> 1; + } + while (low <= high) { + int compare = compare(key, storage[x]); + if (compare > 0) { + low = x + 1; + } else if (compare < 0) { + high = x - 1; + } else { + return x; + } + x = (low + high) >>> 1; + } + return ~low; + } + + @Override + public void write(WriteBuffer buff, Object storage, int len) { + for (int i = 0; i < len; i++) { + write(buff, cast(storage)[i]); + } + } + + @Override + public void read(ByteBuffer buff, Object storage, int len) { + for (int i = 0; i < len; i++) { + cast(storage)[i] = read(buff); + } + } + + @Override + public int hashCode() { + return getClass().getName().hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj != null && getClass().equals(obj.getClass()); + } + + /** + * Cast the storage object to an array of type T. + * + * @param storage the storage object + * @return the array + */ + @SuppressWarnings("unchecked") + protected final T[] cast(Object storage) { + return (T[])storage; + } +} diff --git a/h2/src/main/org/h2/mvstore/type/ByteArrayDataType.java b/h2/src/main/org/h2/mvstore/type/ByteArrayDataType.java new file mode 100644 index 0000000..9fb8546 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/type/ByteArrayDataType.java @@ -0,0 +1,46 @@ +/* + * 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.mvstore.type; + +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.WriteBuffer; +import java.nio.ByteBuffer; + +/** + * Class ByteArrayDataType. + * + * @author Andrei Tokar + */ +public final class ByteArrayDataType extends BasicDataType +{ + public static final ByteArrayDataType INSTANCE = new ByteArrayDataType(); + + private ByteArrayDataType() {} + + @Override + public int getMemory(byte[] data) { + return data.length; + } + + @Override + public void write(WriteBuffer buff, byte[] data) { + buff.putVarInt(data.length); + buff.put(data); + } + + @Override + public byte[] read(ByteBuffer buff) { + int size = DataUtils.readVarInt(buff); + byte[] data = new byte[size]; + buff.get(data); + return data; + } + + @Override + public byte[][] createStorage(int size) { + return new byte[size][]; + } +} diff --git a/h2/src/main/org/h2/mvstore/type/DataType.java b/h2/src/main/org/h2/mvstore/type/DataType.java new file mode 100644 index 0000000..4066cbc --- /dev/null +++ b/h2/src/main/org/h2/mvstore/type/DataType.java @@ -0,0 +1,95 @@ +/* + * 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.mvstore.type; + +import java.nio.ByteBuffer; +import java.util.Comparator; + +import org.h2.mvstore.WriteBuffer; + +/** + * A data type. + */ +public interface DataType extends Comparator { + + /** + * Compare two keys. + * + * @param a the first key + * @param b the second key + * @return -1 if the first key is smaller, 1 if larger, and 0 if equal + * @throws UnsupportedOperationException if the type is not orderable + */ + @Override + int compare(T a, T b); + + /** + * Perform binary search for the key within the storage + * @param key to search for + * @param storage to search within (an array of type T) + * @param size number of data items in the storage + * @param initialGuess for key position + * @return index of the key , if found, - index of the insertion point, if not + */ + int binarySearch(T key, Object storage, int size, int initialGuess); + + /** + * Calculates the amount of used memory in bytes. + * + * @param obj the object + * @return the used memory + */ + int getMemory(T obj); + + /** + * Whether memory estimation based on previously seen values is allowed/desirable + * @return true if memory estimation is allowed + */ + boolean isMemoryEstimationAllowed(); + + /** + * Write an object. + * + * @param buff the target buffer + * @param obj the value + */ + void write(WriteBuffer buff, T obj); + + /** + * Write a list of objects. + * + * @param buff the target buffer + * @param storage the objects + * @param len the number of objects to write + */ + void write(WriteBuffer buff, Object storage, int len); + + /** + * Read an object. + * + * @param buff the source buffer + * @return the object + */ + T read(ByteBuffer buff); + + /** + * Read a list of objects. + * + * @param buff the target buffer + * @param storage the objects + * @param len the number of objects to read + */ + void read(ByteBuffer buff, Object storage, int len); + + /** + * Create storage object of array type to hold values + * + * @param size number of values to hold + * @return storage object + */ + T[] createStorage(int size); +} + diff --git a/h2/src/main/org/h2/mvstore/type/LongDataType.java b/h2/src/main/org/h2/mvstore/type/LongDataType.java new file mode 100644 index 0000000..1fbca0e --- /dev/null +++ b/h2/src/main/org/h2/mvstore/type/LongDataType.java @@ -0,0 +1,83 @@ +/* + * 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.mvstore.type; + +import java.nio.ByteBuffer; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.WriteBuffer; + +/** + * Class LongDataType. + *
    + *
  • 8/21/17 6:52 PM initial creation + *
+ * + * @author Andrei Tokar + */ +public class LongDataType extends BasicDataType { + + public static final LongDataType INSTANCE = new LongDataType(); + + private static final Long[] EMPTY_LONG_ARR = new Long[0]; + + private LongDataType() {} + + @Override + public int getMemory(Long obj) { + return 8; + } + + @Override + public void write(WriteBuffer buff, Long data) { + buff.putVarLong(data); + } + + @Override + public Long read(ByteBuffer buff) { + return DataUtils.readVarLong(buff); + } + + @Override + public Long[] createStorage(int size) { + return size == 0 ? EMPTY_LONG_ARR : new Long[size]; + } + + @Override + public int compare(Long one, Long two) { + return Long.compare(one, two); + } + + @Override + public int binarySearch(Long keyObj, Object storageObj, int size, int initialGuess) { + long key = keyObj; + Long[] storage = cast(storageObj); + int low = 0; + int high = size - 1; + // the cached index minus one, so that + // for the first time (when cachedCompare is 0), + // the default value is used + int x = initialGuess - 1; + if (x < 0 || x > high) { + x = high >>> 1; + } + return binarySearch(key, storage, low, high, x); + } + + private static int binarySearch(long key, Long[] storage, int low, int high, int x) { + while (low <= high) { + long midVal = storage[x]; + if (key > midVal) { + low = x + 1; + } else if (key < midVal) { + high = x - 1; + } else { + return x; + } + x = (low + high) >>> 1; + } + return -(low + 1); + } +} diff --git a/h2/src/main/org/h2/mvstore/type/MetaType.java b/h2/src/main/org/h2/mvstore/type/MetaType.java new file mode 100644 index 0000000..d522ca1 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/type/MetaType.java @@ -0,0 +1,108 @@ +/* + * 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.mvstore.type; + +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +import org.h2.engine.Constants; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.WriteBuffer; + +/** + * Class DBMetaType is a type for values in the type registry map. + * + * @param type of opaque parameter passed as an operational context to Factory.create() + * + * @author Andrei Tokar + */ +public final class MetaType extends BasicDataType> { + + private final D database; + private final Thread.UncaughtExceptionHandler exceptionHandler; + private final Map cache = new HashMap<>(); + + public MetaType(D database, Thread.UncaughtExceptionHandler exceptionHandler) { + this.database = database; + this.exceptionHandler = exceptionHandler; + } + + @Override + public int compare(DataType a, DataType b) { + throw new UnsupportedOperationException(); + } + + @Override + public int getMemory(DataType obj) { + return Constants.MEMORY_OBJECT; + } + + @SuppressWarnings("unchecked") + @Override + public void write(WriteBuffer buff, DataType obj) { + Class clazz = obj.getClass(); + StatefulDataType statefulDataType = null; + if (obj instanceof StatefulDataType) { + statefulDataType = (StatefulDataType) obj; + StatefulDataType.Factory factory = statefulDataType.getFactory(); + if (factory != null) { + clazz = factory.getClass(); + } + } + String className = clazz.getName(); + int len = className.length(); + buff.putVarInt(len) + .putStringData(className, len); + if (statefulDataType != null) { + statefulDataType.save(buff, this); + } + } + + @SuppressWarnings("unchecked") + @Override + public DataType read(ByteBuffer buff) { + int len = DataUtils.readVarInt(buff); + String className = DataUtils.readString(buff, len); + try { + Object o = cache.get(className); + if (o != null) { + if (o instanceof StatefulDataType.Factory) { + return ((StatefulDataType.Factory) o).create(buff, this, database); + } + return (DataType) o; + } + Class clazz = Class.forName(className); + boolean singleton = false; + Object obj; + try { + obj = clazz.getDeclaredField("INSTANCE").get(null); + singleton = true; + } catch (ReflectiveOperationException | NullPointerException e) { + obj = clazz.getDeclaredConstructor().newInstance(); + } + if (obj instanceof StatefulDataType.Factory) { + StatefulDataType.Factory factory = (StatefulDataType.Factory) obj; + cache.put(className, factory); + return factory.create(buff, this, database); + } + if (singleton) { + cache.put(className, obj); + } + return (DataType) obj; + } catch (ReflectiveOperationException | SecurityException | IllegalArgumentException e) { + if (exceptionHandler != null) { + exceptionHandler.uncaughtException(Thread.currentThread(), e); + } + throw new RuntimeException(e); + } + } + + @Override + public DataType[] createStorage(int size) { + return new DataType[size]; + } +} diff --git a/h2/src/main/org/h2/mvstore/type/ObjectDataType.java b/h2/src/main/org/h2/mvstore/type/ObjectDataType.java new file mode 100644 index 0000000..eab21c0 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/type/ObjectDataType.java @@ -0,0 +1,1617 @@ +/* + * 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.mvstore.type; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.reflect.Array; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.UUID; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.WriteBuffer; +import org.h2.util.Utils; + +/** + * A data type implementation for the most common data types, including + * serializable objects. + */ +public class ObjectDataType extends BasicDataType { + + /** + * The type constants are also used as tag values. + */ + static final int TYPE_NULL = 0; + static final int TYPE_BOOLEAN = 1; + static final int TYPE_BYTE = 2; + static final int TYPE_SHORT = 3; + static final int TYPE_INT = 4; + static final int TYPE_LONG = 5; + static final int TYPE_BIG_INTEGER = 6; + static final int TYPE_FLOAT = 7; + static final int TYPE_DOUBLE = 8; + static final int TYPE_BIG_DECIMAL = 9; + static final int TYPE_CHAR = 10; + static final int TYPE_STRING = 11; + static final int TYPE_UUID = 12; + static final int TYPE_DATE = 13; + static final int TYPE_ARRAY = 14; + static final int TYPE_SERIALIZED_OBJECT = 19; + + /** + * For very common values (e.g. 0 and 1) we save space by encoding the value + * in the tag. e.g. TAG_BOOLEAN_TRUE and TAG_FLOAT_0. + */ + static final int TAG_BOOLEAN_TRUE = 32; + static final int TAG_INTEGER_NEGATIVE = 33; + static final int TAG_INTEGER_FIXED = 34; + static final int TAG_LONG_NEGATIVE = 35; + static final int TAG_LONG_FIXED = 36; + static final int TAG_BIG_INTEGER_0 = 37; + static final int TAG_BIG_INTEGER_1 = 38; + static final int TAG_BIG_INTEGER_SMALL = 39; + static final int TAG_FLOAT_0 = 40; + static final int TAG_FLOAT_1 = 41; + static final int TAG_FLOAT_FIXED = 42; + static final int TAG_DOUBLE_0 = 43; + static final int TAG_DOUBLE_1 = 44; + static final int TAG_DOUBLE_FIXED = 45; + static final int TAG_BIG_DECIMAL_0 = 46; + static final int TAG_BIG_DECIMAL_1 = 47; + static final int TAG_BIG_DECIMAL_SMALL = 48; + static final int TAG_BIG_DECIMAL_SMALL_SCALED = 49; + + /** + * For small-values/small-arrays, we encode the value/array-length in the + * tag. + */ + static final int TAG_INTEGER_0_15 = 64; + static final int TAG_LONG_0_7 = 80; + static final int TAG_STRING_0_15 = 88; + static final int TAG_BYTE_ARRAY_0_15 = 104; + + /** + * Constants for floating point synchronization. + */ + static final int FLOAT_ZERO_BITS = Float.floatToIntBits(0.0f); + static final int FLOAT_ONE_BITS = Float.floatToIntBits(1.0f); + static final long DOUBLE_ZERO_BITS = Double.doubleToLongBits(0.0d); + static final long DOUBLE_ONE_BITS = Double.doubleToLongBits(1.0d); + + static final Class[] COMMON_CLASSES = { boolean.class, byte.class, + short.class, char.class, int.class, long.class, float.class, + double.class, Object.class, Boolean.class, Byte.class, Short.class, + Character.class, Integer.class, Long.class, BigInteger.class, + Float.class, Double.class, BigDecimal.class, String.class, + UUID.class, Date.class }; + + private static class Holder { + private static final HashMap, Integer> COMMON_CLASSES_MAP = new HashMap<>(32); + + static { + for (int i = 0, size = COMMON_CLASSES.length; i < size; i++) { + COMMON_CLASSES_MAP.put(COMMON_CLASSES[i], i); + } + } + + /** + * Get the class id, or null if not found. + * + * @param clazz the class + * @return the class id or null + */ + static Integer getCommonClassId(Class clazz) { + return COMMON_CLASSES_MAP.get(clazz); + } + } + + @SuppressWarnings("unchecked") + private AutoDetectDataType last = selectDataType(TYPE_NULL); + + @Override + public Object[] createStorage(int size) { + return new Object[size]; + } + + @Override + public int compare(Object a, Object b) { + int typeId = getTypeId(a); + int typeDiff = typeId - getTypeId(b); + if (typeDiff == 0) { + return newType(typeId).compare(a, b); + } + return Integer.signum(typeDiff); + } + + @Override + public int getMemory(Object obj) { + return switchType(obj).getMemory(obj); + } + + @Override + public void write(WriteBuffer buff, Object obj) { + switchType(obj).write(buff, obj); + } + + @SuppressWarnings("unchecked") + private AutoDetectDataType newType(int typeId) { + if (typeId == last.typeId) { + return last; + } + return selectDataType(typeId); + } + + @SuppressWarnings("rawtypes") + private AutoDetectDataType selectDataType(int typeId) { + switch (typeId) { + case TYPE_NULL: + return NullType.INSTANCE; + case TYPE_BOOLEAN: + return BooleanType.INSTANCE; + case TYPE_BYTE: + return ByteType.INSTANCE; + case TYPE_SHORT: + return ShortType.INSTANCE; + case TYPE_CHAR: + return CharacterType.INSTANCE; + case TYPE_INT: + return IntegerType.INSTANCE; + case TYPE_LONG: + return LongType.INSTANCE; + case TYPE_FLOAT: + return FloatType.INSTANCE; + case TYPE_DOUBLE: + return DoubleType.INSTANCE; + case TYPE_BIG_INTEGER: + return BigIntegerType.INSTANCE; + case TYPE_BIG_DECIMAL: + return BigDecimalType.INSTANCE; + case TYPE_STRING: + return StringType.INSTANCE; + case TYPE_UUID: + return UUIDType.INSTANCE; + case TYPE_DATE: + return DateType.INSTANCE; + case TYPE_ARRAY: + return new ObjectArrayType(); + case TYPE_SERIALIZED_OBJECT: + return new SerializedObjectType(this); + default: + throw DataUtils.newMVStoreException(DataUtils.ERROR_INTERNAL, + "Unsupported type {0}", typeId); + } + } + + @Override + public Object read(ByteBuffer buff) { + int tag = buff.get(); + int typeId; + if (tag <= TYPE_SERIALIZED_OBJECT) { + typeId = tag; + } else { + switch (tag) { + case TAG_BOOLEAN_TRUE: + typeId = TYPE_BOOLEAN; + break; + case TAG_INTEGER_NEGATIVE: + case TAG_INTEGER_FIXED: + typeId = TYPE_INT; + break; + case TAG_LONG_NEGATIVE: + case TAG_LONG_FIXED: + typeId = TYPE_LONG; + break; + case TAG_BIG_INTEGER_0: + case TAG_BIG_INTEGER_1: + case TAG_BIG_INTEGER_SMALL: + typeId = TYPE_BIG_INTEGER; + break; + case TAG_FLOAT_0: + case TAG_FLOAT_1: + case TAG_FLOAT_FIXED: + typeId = TYPE_FLOAT; + break; + case TAG_DOUBLE_0: + case TAG_DOUBLE_1: + case TAG_DOUBLE_FIXED: + typeId = TYPE_DOUBLE; + break; + case TAG_BIG_DECIMAL_0: + case TAG_BIG_DECIMAL_1: + case TAG_BIG_DECIMAL_SMALL: + case TAG_BIG_DECIMAL_SMALL_SCALED: + typeId = TYPE_BIG_DECIMAL; + break; + default: + if (tag >= TAG_INTEGER_0_15 && tag <= TAG_INTEGER_0_15 + 15) { + typeId = TYPE_INT; + } else if (tag >= TAG_STRING_0_15 + && tag <= TAG_STRING_0_15 + 15) { + typeId = TYPE_STRING; + } else if (tag >= TAG_LONG_0_7 && tag <= TAG_LONG_0_7 + 7) { + typeId = TYPE_LONG; + } else if (tag >= TAG_BYTE_ARRAY_0_15 + && tag <= TAG_BYTE_ARRAY_0_15 + 15) { + typeId = TYPE_ARRAY; + } else { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_FILE_CORRUPT, "Unknown tag {0}", + tag); + } + } + } + AutoDetectDataType t = last; + if (typeId != t.typeId) { + last = t = newType(typeId); + } + return t.read(buff, tag); + } + + private static int getTypeId(Object obj) { + if (obj instanceof Integer) { + return TYPE_INT; + } else if (obj instanceof String) { + return TYPE_STRING; + } else if (obj instanceof Long) { + return TYPE_LONG; + } else if (obj instanceof Double) { + return TYPE_DOUBLE; + } else if (obj instanceof Float) { + return TYPE_FLOAT; + } else if (obj instanceof Boolean) { + return TYPE_BOOLEAN; + } else if (obj instanceof UUID) { + return TYPE_UUID; + } else if (obj instanceof Byte) { + return TYPE_BYTE; + } else if (obj instanceof Short) { + return TYPE_SHORT; + } else if (obj instanceof Character) { + return TYPE_CHAR; + } else if (obj == null) { + return TYPE_NULL; + } else if (isDate(obj)) { + return TYPE_DATE; + } else if (isBigInteger(obj)) { + return TYPE_BIG_INTEGER; + } else if (isBigDecimal(obj)) { + return TYPE_BIG_DECIMAL; + } else if (obj.getClass().isArray()) { + return TYPE_ARRAY; + } + return TYPE_SERIALIZED_OBJECT; + } + + /** + * Switch the last remembered type to match the type of the given object. + * + * @param obj the object + * @return the auto-detected type used + */ + AutoDetectDataType switchType(Object obj) { + int typeId = getTypeId(obj); + AutoDetectDataType l = last; + if (typeId != l.typeId) { + last = l = newType(typeId); + } + return l; + } + + /** + * Check whether this object is a BigInteger. + * + * @param obj the object + * @return true if yes + */ + static boolean isBigInteger(Object obj) { + return obj != null && obj.getClass() == BigInteger.class; + } + + /** + * Check whether this object is a BigDecimal. + * + * @param obj the object + * @return true if yes + */ + static boolean isBigDecimal(Object obj) { + return obj != null && obj.getClass() == BigDecimal.class; + } + + /** + * Check whether this object is a date. + * + * @param obj the object + * @return true if yes + */ + static boolean isDate(Object obj) { + return obj != null && obj.getClass() == Date.class; + } + + /** + * Check whether this object is an array. + * + * @param obj the object + * @return true if yes + */ + static boolean isArray(Object obj) { + return obj != null && obj.getClass().isArray(); + } + + /** + * Serialize the object to a byte array. + * + * @param obj the object to serialize + * @return the byte array + */ + public static byte[] serialize(Object obj) { + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream os = new ObjectOutputStream(out); + os.writeObject(obj); + return out.toByteArray(); + } catch (Throwable e) { + throw DataUtils.newIllegalArgumentException( + "Could not serialize {0}", obj, e); + } + } + + /** + * De-serialize the byte array to an object. + * + * @param data the byte array + * @return the object + */ + public static Object deserialize(byte[] data) { + try { + ByteArrayInputStream in = new ByteArrayInputStream(data); + ObjectInputStream is = new ObjectInputStream(in); + return is.readObject(); + } catch (Throwable e) { + throw DataUtils.newIllegalArgumentException( + "Could not deserialize {0}", Arrays.toString(data), e); + } + } + + /** + * Compare the contents of two byte arrays. If the content or length of the + * first array is smaller than the second array, -1 is returned. If the + * content or length of the second array is smaller than the first array, 1 + * is returned. If the contents and lengths are the same, 0 is returned. + *

+ * This method interprets bytes as unsigned. + * + * @param data1 the first byte array (must not be null) + * @param data2 the second byte array (must not be null) + * @return the result of the comparison (-1, 1 or 0) + */ + public static int compareNotNull(byte[] data1, byte[] data2) { + if (data1 == data2) { + return 0; + } + int len = Math.min(data1.length, data2.length); + for (int i = 0; i < len; i++) { + int b = data1[i] & 255; + int b2 = data2[i] & 255; + if (b != b2) { + return b > b2 ? 1 : -1; + } + } + return Integer.signum(data1.length - data2.length); + } + + /** + * The base class for auto-detect data types. + */ + abstract static class AutoDetectDataType extends BasicDataType { + + private final ObjectDataType base; + + /** + * The type id. + */ + final int typeId; + + AutoDetectDataType(int typeId) { + this.base = null; + this.typeId = typeId; + } + + AutoDetectDataType(ObjectDataType base, int typeId) { + this.base = base; + this.typeId = typeId; + } + + @Override + public int getMemory(T o) { + return getType(o).getMemory(o); + } + + @Override + public void write(WriteBuffer buff, T o) { + getType(o).write(buff, o); + } + + /** + * Get the type for the given object. + * + * @param o the object + * @return the type + */ + DataType getType(Object o) { + return base.switchType(o); + } + + /** + * Read an object from the buffer. + * + * @param buff the buffer + * @param tag the first byte of the object (usually the type) + * @return the read object + */ + abstract Object read(ByteBuffer buff, int tag); + + } + + /** + * The type for the null value + */ + static class NullType extends AutoDetectDataType { + + /** + * The only instance of this type. + */ + static final NullType INSTANCE = new NullType(); + + private NullType() { + super(TYPE_NULL); + } + + @Override + public Object[] createStorage(int size) { + return null; + } + + @Override + public int compare(Object aObj, Object bObj) { + return 0; + } + + @Override + public int getMemory(Object obj) { + return 0; + } + + @Override + public void write(WriteBuffer buff, Object obj) { + buff.put((byte) TYPE_NULL); + } + + @Override + public Object read(ByteBuffer buff) { + return null; + } + + @Override + public Object read(ByteBuffer buff, int tag) { + return null; + } + + } + + /** + * The type for boolean true and false. + */ + static class BooleanType extends AutoDetectDataType { + + /** + * The only instance of this type. + */ + static final BooleanType INSTANCE = new BooleanType(); + + private BooleanType() { + super(TYPE_BOOLEAN); + } + + @Override + public Boolean[] createStorage(int size) { + return new Boolean[size]; + } + + @Override + public int compare(Boolean a, Boolean b) { + return a.compareTo(b); + } + + @Override + public int getMemory(Boolean obj) { + return 0; + } + + @Override + public void write(WriteBuffer buff, Boolean obj) { + int tag = obj ? TAG_BOOLEAN_TRUE : TYPE_BOOLEAN; + buff.put((byte) tag); + } + + @Override + public Boolean read(ByteBuffer buff) { + return buff.get() == TAG_BOOLEAN_TRUE ? Boolean.TRUE : Boolean.FALSE; + } + + @Override + public Boolean read(ByteBuffer buff, int tag) { + return tag == TYPE_BOOLEAN ? Boolean.FALSE : Boolean.TRUE; + } + } + + /** + * The type for byte objects. + */ + static class ByteType extends AutoDetectDataType { + + /** + * The only instance of this type. + */ + static final ByteType INSTANCE = new ByteType(); + + private ByteType() { + super(TYPE_BYTE); + } + + @Override + public Byte[] createStorage(int size) { + return new Byte[size]; + } + + @Override + public int compare(Byte a, Byte b) { + return a.compareTo(b); + } + + @Override + public int getMemory(Byte obj) { + return 1; + } + + @Override + public void write(WriteBuffer buff, Byte obj) { + buff.put((byte) TYPE_BYTE); + buff.put(obj); + } + + @Override + public Byte read(ByteBuffer buff) { + return buff.get(); + } + + @Override + public Object read(ByteBuffer buff, int tag) { + return buff.get(); + } + + } + + /** + * The type for character objects. + */ + static class CharacterType extends AutoDetectDataType { + + /** + * The only instance of this type. + */ + static final CharacterType INSTANCE = new CharacterType(); + + private CharacterType() { + super(TYPE_CHAR); + } + + @Override + public Character[] createStorage(int size) { + return new Character[size]; + } + + @Override + public int compare(Character a, Character b) { + return a.compareTo(b); + } + + @Override + public int getMemory(Character obj) { + return 24; + } + + @Override + public void write(WriteBuffer buff, Character obj) { + buff.put((byte) TYPE_CHAR); + buff.putChar(obj); + } + + @Override + public Character read(ByteBuffer buff) { + return buff.getChar(); + } + + @Override + public Character read(ByteBuffer buff, int tag) { + return buff.getChar(); + } + } + + /** + * The type for short objects. + */ + static class ShortType extends AutoDetectDataType { + + /** + * The only instance of this type. + */ + static final ShortType INSTANCE = new ShortType(); + + private ShortType() { + super(TYPE_SHORT); + } + + @Override + public Short[] createStorage(int size) { + return new Short[size]; + } + + @Override + public int compare(Short a, Short b) { + return a.compareTo(b); + } + + @Override + public int getMemory(Short obj) { + return 24; + } + + @Override + public void write(WriteBuffer buff, Short obj) { + buff.put((byte) TYPE_SHORT); + buff.putShort(obj); + } + + @Override + public Short read(ByteBuffer buff) { + return read(buff, buff.get()); + } + + @Override + public Short read(ByteBuffer buff, int tag) { + return buff.getShort(); + } + } + + /** + * The type for integer objects. + */ + static class IntegerType extends AutoDetectDataType { + + /** + * The only instance of this type. + */ + static final IntegerType INSTANCE = new IntegerType(); + + private IntegerType() { + super(TYPE_INT); + } + + @Override + public Integer[] createStorage(int size) { + return new Integer[size]; + } + + @Override + public int compare(Integer a, Integer b) { + return a.compareTo(b); + } + + @Override + public int getMemory(Integer obj) { + return 24; + } + + @Override + public void write(WriteBuffer buff, Integer obj) { + int x = obj; + if (x < 0) { + // -Integer.MIN_VALUE is smaller than 0 + if (-x < 0 || -x > DataUtils.COMPRESSED_VAR_INT_MAX) { + buff.put((byte) TAG_INTEGER_FIXED).putInt(x); + } else { + buff.put((byte) TAG_INTEGER_NEGATIVE).putVarInt(-x); + } + } else if (x <= 15) { + buff.put((byte) (TAG_INTEGER_0_15 + x)); + } else if (x <= DataUtils.COMPRESSED_VAR_INT_MAX) { + buff.put((byte) TYPE_INT).putVarInt(x); + } else { + buff.put((byte) TAG_INTEGER_FIXED).putInt(x); + } + } + + @Override + public Integer read(ByteBuffer buff) { + return read(buff, buff.get()); + } + + @Override + public Integer read(ByteBuffer buff, int tag) { + switch (tag) { + case TYPE_INT: + return DataUtils.readVarInt(buff); + case TAG_INTEGER_NEGATIVE: + return -DataUtils.readVarInt(buff); + case TAG_INTEGER_FIXED: + return buff.getInt(); + } + return tag - TAG_INTEGER_0_15; + } + } + + /** + * The type for long objects. + */ + static class LongType extends AutoDetectDataType { + + /** + * The only instance of this type. + */ + static final LongType INSTANCE = new LongType(); + + private LongType() { + super(TYPE_LONG); + } + + @Override + public Long[] createStorage(int size) { + return new Long[size]; + } + + @Override + public int compare(Long a, Long b) { + return a.compareTo(b); + } + + @Override + public int getMemory(Long obj) { + return 30; + } + + @Override + public void write(WriteBuffer buff, Long obj) { + long x = obj; + if (x < 0) { + // -Long.MIN_VALUE is smaller than 0 + if (-x < 0 || -x > DataUtils.COMPRESSED_VAR_LONG_MAX) { + buff.put((byte) TAG_LONG_FIXED); + buff.putLong(x); + } else { + buff.put((byte) TAG_LONG_NEGATIVE); + buff.putVarLong(-x); + } + } else if (x <= 7) { + buff.put((byte) (TAG_LONG_0_7 + x)); + } else if (x <= DataUtils.COMPRESSED_VAR_LONG_MAX) { + buff.put((byte) TYPE_LONG); + buff.putVarLong(x); + } else { + buff.put((byte) TAG_LONG_FIXED); + buff.putLong(x); + } + } + + @Override + public Long read(ByteBuffer buff) { + return read(buff, buff.get()); + } + + @Override + public Long read(ByteBuffer buff, int tag) { + switch (tag) { + case TYPE_LONG: + return DataUtils.readVarLong(buff); + case TAG_LONG_NEGATIVE: + return -DataUtils.readVarLong(buff); + case TAG_LONG_FIXED: + return buff.getLong(); + } + return (long) (tag - TAG_LONG_0_7); + } + } + + /** + * The type for float objects. + */ + static class FloatType extends AutoDetectDataType { + + /** + * The only instance of this type. + */ + static final FloatType INSTANCE = new FloatType(); + + private FloatType() { + super(TYPE_FLOAT); + } + + @Override + public Float[] createStorage(int size) { + return new Float[size]; + } + + @Override + public int compare(Float a, Float b) { + return a.compareTo(b); + } + + @Override + public int getMemory(Float obj) { + return 24; + } + + @Override + public void write(WriteBuffer buff, Float obj) { + float x = obj; + int f = Float.floatToIntBits(x); + if (f == ObjectDataType.FLOAT_ZERO_BITS) { + buff.put((byte) TAG_FLOAT_0); + } else if (f == ObjectDataType.FLOAT_ONE_BITS) { + buff.put((byte) TAG_FLOAT_1); + } else { + int value = Integer.reverse(f); + if (value >= 0 && value <= DataUtils.COMPRESSED_VAR_INT_MAX) { + buff.put((byte) TYPE_FLOAT).putVarInt(value); + } else { + buff.put((byte) TAG_FLOAT_FIXED).putFloat(x); + } + } + } + + @Override + public Float read(ByteBuffer buff) { + return read(buff, buff.get()); + } + + @Override + public Float read(ByteBuffer buff, int tag) { + switch (tag) { + case TAG_FLOAT_0: + return 0f; + case TAG_FLOAT_1: + return 1f; + case TAG_FLOAT_FIXED: + return buff.getFloat(); + } + return Float.intBitsToFloat(Integer.reverse(DataUtils + .readVarInt(buff))); + } + + } + + /** + * The type for double objects. + */ + static class DoubleType extends AutoDetectDataType { + + /** + * The only instance of this type. + */ + static final DoubleType INSTANCE = new DoubleType(); + + private DoubleType() { + super(TYPE_DOUBLE); + } + + @Override + public Double[] createStorage(int size) { + return new Double[size]; + } + + @Override + public int compare(Double a, Double b) { + return a.compareTo(b); + } + + @Override + public int getMemory(Double obj) { + return 30; + } + + @Override + public void write(WriteBuffer buff, Double obj) { + double x = obj; + long d = Double.doubleToLongBits(x); + if (d == ObjectDataType.DOUBLE_ZERO_BITS) { + buff.put((byte) TAG_DOUBLE_0); + } else if (d == ObjectDataType.DOUBLE_ONE_BITS) { + buff.put((byte) TAG_DOUBLE_1); + } else { + long value = Long.reverse(d); + if (value >= 0 && value <= DataUtils.COMPRESSED_VAR_LONG_MAX) { + buff.put((byte) TYPE_DOUBLE); + buff.putVarLong(value); + } else { + buff.put((byte) TAG_DOUBLE_FIXED); + buff.putDouble(x); + } + } + } + + @Override + public Double read(ByteBuffer buff) { + return read(buff, buff.get()); + } + + @Override + public Double read(ByteBuffer buff, int tag) { + switch (tag) { + case TAG_DOUBLE_0: + return 0d; + case TAG_DOUBLE_1: + return 1d; + case TAG_DOUBLE_FIXED: + return buff.getDouble(); + } + return Double.longBitsToDouble(Long.reverse(DataUtils + .readVarLong(buff))); + } + } + + /** + * The type for BigInteger objects. + */ + static class BigIntegerType extends AutoDetectDataType { + + /** + * The only instance of this type. + */ + static final BigIntegerType INSTANCE = new BigIntegerType(); + + private BigIntegerType() { + super(TYPE_BIG_INTEGER); + } + + @Override + public BigInteger[] createStorage(int size) { + return new BigInteger[size]; + } + + @Override + public int compare(BigInteger a, BigInteger b) { + return a.compareTo(b); + } + + @Override + public int getMemory(BigInteger obj) { + return 100; + } + + @Override + public void write(WriteBuffer buff, BigInteger x) { + if (BigInteger.ZERO.equals(x)) { + buff.put((byte) TAG_BIG_INTEGER_0); + } else if (BigInteger.ONE.equals(x)) { + buff.put((byte) TAG_BIG_INTEGER_1); + } else { + int bits = x.bitLength(); + if (bits <= 63) { + buff.put((byte) TAG_BIG_INTEGER_SMALL).putVarLong( + x.longValue()); + } else { + byte[] bytes = x.toByteArray(); + buff.put((byte) TYPE_BIG_INTEGER).putVarInt(bytes.length) + .put(bytes); + } + } + } + + @Override + public BigInteger read(ByteBuffer buff) { + return read(buff, buff.get()); + } + + @Override + public BigInteger read(ByteBuffer buff, int tag) { + switch (tag) { + case TAG_BIG_INTEGER_0: + return BigInteger.ZERO; + case TAG_BIG_INTEGER_1: + return BigInteger.ONE; + case TAG_BIG_INTEGER_SMALL: + return BigInteger.valueOf(DataUtils.readVarLong(buff)); + } + int len = DataUtils.readVarInt(buff); + byte[] bytes = Utils.newBytes(len); + buff.get(bytes); + return new BigInteger(bytes); + } + } + + /** + * The type for BigDecimal objects. + */ + static class BigDecimalType extends AutoDetectDataType { + + /** + * The only instance of this type. + */ + static final BigDecimalType INSTANCE = new BigDecimalType(); + + private BigDecimalType() { + super(TYPE_BIG_DECIMAL); + } + + @Override + public BigDecimal[] createStorage(int size) { + return new BigDecimal[size]; + } + + @Override + public int compare(BigDecimal a, BigDecimal b) { + return a.compareTo(b); + } + + @Override + public int getMemory(BigDecimal obj) { + return 150; + } + + @Override + public void write(WriteBuffer buff, BigDecimal x) { + if (BigDecimal.ZERO.equals(x)) { + buff.put((byte) TAG_BIG_DECIMAL_0); + } else if (BigDecimal.ONE.equals(x)) { + buff.put((byte) TAG_BIG_DECIMAL_1); + } else { + int scale = x.scale(); + BigInteger b = x.unscaledValue(); + int bits = b.bitLength(); + if (bits < 64) { + if (scale == 0) { + buff.put((byte) TAG_BIG_DECIMAL_SMALL); + } else { + buff.put((byte) TAG_BIG_DECIMAL_SMALL_SCALED) + .putVarInt(scale); + } + buff.putVarLong(b.longValue()); + } else { + byte[] bytes = b.toByteArray(); + buff.put((byte) TYPE_BIG_DECIMAL).putVarInt(scale) + .putVarInt(bytes.length).put(bytes); + } + } + } + + @Override + public BigDecimal read(ByteBuffer buff) { + return read(buff, buff.get()); + } + + @Override + public BigDecimal read(ByteBuffer buff, int tag) { + switch (tag) { + case TAG_BIG_DECIMAL_0: + return BigDecimal.ZERO; + case TAG_BIG_DECIMAL_1: + return BigDecimal.ONE; + case TAG_BIG_DECIMAL_SMALL: + return BigDecimal.valueOf(DataUtils.readVarLong(buff)); + case TAG_BIG_DECIMAL_SMALL_SCALED: + int scale = DataUtils.readVarInt(buff); + return BigDecimal.valueOf(DataUtils.readVarLong(buff), scale); + } + int scale = DataUtils.readVarInt(buff); + int len = DataUtils.readVarInt(buff); + byte[] bytes = Utils.newBytes(len); + buff.get(bytes); + BigInteger b = new BigInteger(bytes); + return new BigDecimal(b, scale); + } + + } + + /** + * The type for string objects. + */ + static class StringType extends AutoDetectDataType { + + /** + * The only instance of this type. + */ + static final StringType INSTANCE = new StringType(); + + private StringType() { + super(TYPE_STRING); + } + + @Override + public String[] createStorage(int size) { + return new String[size]; + } + + @Override + public int getMemory(String obj) { + return 24 + 2 * obj.length(); + } + + @Override + public int compare(String aObj, String bObj) { + return aObj.compareTo(bObj); + } + + @Override + public void write(WriteBuffer buff, String s) { + int len = s.length(); + if (len <= 15) { + buff.put((byte) (TAG_STRING_0_15 + len)); + } else { + buff.put((byte) TYPE_STRING).putVarInt(len); + } + buff.putStringData(s, len); + } + + @Override + public String read(ByteBuffer buff) { + return read(buff, buff.get()); + } + + @Override + public String read(ByteBuffer buff, int tag) { + int len; + if (tag == TYPE_STRING) { + len = DataUtils.readVarInt(buff); + } else { + len = tag - TAG_STRING_0_15; + } + return DataUtils.readString(buff, len); + } + + } + + /** + * The type for UUID objects. + */ + static class UUIDType extends AutoDetectDataType { + + /** + * The only instance of this type. + */ + static final UUIDType INSTANCE = new UUIDType(); + + private UUIDType() { + super(TYPE_UUID); + } + + @Override + public UUID[] createStorage(int size) { + return new UUID[size]; + } + + @Override + public int getMemory(UUID obj) { + return 40; + } + + @Override + public int compare(UUID a, UUID b) { + return a.compareTo(b); + } + + @Override + public void write(WriteBuffer buff, UUID a) { + buff.put((byte) TYPE_UUID); + buff.putLong(a.getMostSignificantBits()); + buff.putLong(a.getLeastSignificantBits()); + } + + @Override + public UUID read(ByteBuffer buff) { + return read(buff, buff.get()); + } + + @Override + public UUID read(ByteBuffer buff, int tag) { + long a = buff.getLong(), b = buff.getLong(); + return new UUID(a, b); + } + + } + + /** + * The type for java.util.Date objects. + */ + static class DateType extends AutoDetectDataType { + + /** + * The only instance of this type. + */ + static final DateType INSTANCE = new DateType(); + + private DateType() { + super(TYPE_DATE); + } + + @Override + public Date[] createStorage(int size) { + return new Date[size]; + } + + @Override + public int getMemory(Date obj) { + return 40; + } + + @Override + public int compare(Date a, Date b) { + return a.compareTo(b); + } + + @Override + public void write(WriteBuffer buff, Date a) { + buff.put((byte) TYPE_DATE); + buff.putLong(a.getTime()); + } + + @Override + public Date read(ByteBuffer buff) { + return read(buff, buff.get()); + } + + @Override + public Date read(ByteBuffer buff, int tag) { + long a = buff.getLong(); + return new Date(a); + } + + } + + /** + * The type for object arrays. + */ + static class ObjectArrayType extends AutoDetectDataType { + private final ObjectDataType elementType = new ObjectDataType(); + + ObjectArrayType() { + super(TYPE_ARRAY); + } + + @Override + public Object[] createStorage(int size) { + return new Object[size]; + } + + @Override + public int getMemory(Object obj) { + if (!isArray(obj)) { + return super.getMemory(obj); + } + int size = 64; + Class type = obj.getClass().getComponentType(); + if (type.isPrimitive()) { + int len = Array.getLength(obj); + if (type == boolean.class || type == byte.class) { + size += len; + } else if (type == char.class || type == short.class) { + size += len * 2; + } else if (type == int.class || type == float.class) { + size += len * 4; + } else if (type == double.class || type == long.class) { + size += len * 8; + } + } else { + for (Object x : (Object[]) obj) { + if (x != null) { + size += elementType.getMemory(x); + } + } + } + // we say they are larger, because these objects + // use quite a lot of disk space + return size * 2; + } + + @Override + public int compare(Object aObj, Object bObj) { + if (!isArray(aObj) || !isArray(bObj)) { + return super.compare(aObj, bObj); + } + if (aObj == bObj) { + return 0; + } + Class type = aObj.getClass().getComponentType(); + Class bType = bObj.getClass().getComponentType(); + if (type != bType) { + Integer classA = Holder.getCommonClassId(type); + Integer classB = Holder.getCommonClassId(bType); + if (classA != null) { + if (classB != null) { + return classA.compareTo(classB); + } + return -1; + } else if (classB != null) { + return 1; + } + return type.getName().compareTo(bType.getName()); + } + int aLen = Array.getLength(aObj); + int bLen = Array.getLength(bObj); + int len = Math.min(aLen, bLen); + if (type.isPrimitive()) { + if (type == byte.class) { + byte[] a = (byte[]) aObj; + byte[] b = (byte[]) bObj; + return compareNotNull(a, b); + } + for (int i = 0; i < len; i++) { + int x; + if (type == boolean.class) { + 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]); + } else if (type == short.class) { + x = Integer.signum(((short[]) aObj)[i] - ((short[]) bObj)[i]); + } else if (type == int.class) { + int a = ((int[]) aObj)[i]; + int b = ((int[]) bObj)[i]; + x = Integer.compare(a, b); + } else if (type == float.class) { + x = Float.compare(((float[]) aObj)[i], + ((float[]) bObj)[i]); + } else if (type == double.class) { + x = Double.compare(((double[]) aObj)[i], + ((double[]) bObj)[i]); + } else { + long a = ((long[]) aObj)[i]; + long b = ((long[]) bObj)[i]; + x = Long.compare(a, b); + } + if (x != 0) { + return x; + } + } + } else { + Object[] a = (Object[]) aObj; + Object[] b = (Object[]) bObj; + for (int i = 0; i < len; i++) { + int comp = elementType.compare(a[i], b[i]); + if (comp != 0) { + return comp; + } + } + } + return Integer.compare(aLen, bLen); + } + + @Override + public void write(WriteBuffer buff, Object obj) { + if (!isArray(obj)) { + super.write(buff, obj); + return; + } + Class type = obj.getClass().getComponentType(); + Integer classId = Holder.getCommonClassId(type); + if (classId != null) { + if (type.isPrimitive()) { + if (type == byte.class) { + byte[] data = (byte[]) obj; + int len = data.length; + if (len <= 15) { + buff.put((byte) (TAG_BYTE_ARRAY_0_15 + len)); + } else { + buff.put((byte) TYPE_ARRAY) + .put((byte) classId.intValue()) + .putVarInt(len); + } + buff.put(data); + return; + } + int len = Array.getLength(obj); + buff.put((byte) TYPE_ARRAY).put((byte) classId.intValue()) + .putVarInt(len); + for (int i = 0; i < len; i++) { + if (type == boolean.class) { + buff.put((byte) (((boolean[]) obj)[i] ? 1 : 0)); + } else if (type == char.class) { + buff.putChar(((char[]) obj)[i]); + } else if (type == short.class) { + buff.putShort(((short[]) obj)[i]); + } else if (type == int.class) { + buff.putInt(((int[]) obj)[i]); + } else if (type == float.class) { + buff.putFloat(((float[]) obj)[i]); + } else if (type == double.class) { + buff.putDouble(((double[]) obj)[i]); + } else { + buff.putLong(((long[]) obj)[i]); + } + } + return; + } + buff.put((byte) TYPE_ARRAY).put((byte) classId.intValue()); + } else { + buff.put((byte) TYPE_ARRAY).put((byte) -1); + String c = type.getName(); + StringDataType.INSTANCE.write(buff, c); + } + Object[] array = (Object[]) obj; + int len = array.length; + buff.putVarInt(len); + for (Object x : array) { + elementType.write(buff, x); + } + } + + @Override + public Object read(ByteBuffer buff) { + return read(buff, buff.get()); + } + + @Override + public Object read(ByteBuffer buff, int tag) { + if (tag != TYPE_ARRAY) { + byte[] data; + int len = tag - TAG_BYTE_ARRAY_0_15; + data = Utils.newBytes(len); + buff.get(data); + return data; + } + int ct = buff.get(); + Class clazz; + Object obj; + if (ct == -1) { + String componentType = StringDataType.INSTANCE.read(buff); + try { + clazz = Class.forName(componentType); + } catch (Exception e) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_SERIALIZATION, + "Could not get class {0}", componentType, e); + } + } else { + clazz = COMMON_CLASSES[ct]; + } + int len = DataUtils.readVarInt(buff); + try { + obj = Array.newInstance(clazz, len); + } catch (Exception e) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_SERIALIZATION, + "Could not create array of type {0} length {1}", clazz, + len, e); + } + if (clazz.isPrimitive()) { + for (int i = 0; i < len; i++) { + if (clazz == boolean.class) { + ((boolean[]) obj)[i] = buff.get() == 1; + } else if (clazz == byte.class) { + ((byte[]) obj)[i] = buff.get(); + } else if (clazz == char.class) { + ((char[]) obj)[i] = buff.getChar(); + } else if (clazz == short.class) { + ((short[]) obj)[i] = buff.getShort(); + } else if (clazz == int.class) { + ((int[]) obj)[i] = buff.getInt(); + } else if (clazz == float.class) { + ((float[]) obj)[i] = buff.getFloat(); + } else if (clazz == double.class) { + ((double[]) obj)[i] = buff.getDouble(); + } else { + ((long[]) obj)[i] = buff.getLong(); + } + } + } else { + Object[] array = (Object[]) obj; + for (int i = 0; i < len; i++) { + array[i] = elementType.read(buff); + } + } + return obj; + } + + } + + /** + * The type for serialized objects. + */ + static class SerializedObjectType extends AutoDetectDataType { + + private int averageSize = 10_000; + + SerializedObjectType(ObjectDataType base) { + super(base, TYPE_SERIALIZED_OBJECT); + } + + @Override + public Object[] createStorage(int size) { + return new Object[size]; + } + + @SuppressWarnings("unchecked") + @Override + public int compare(Object aObj, Object bObj) { + if (aObj == bObj) { + return 0; + } + DataType ta = getType(aObj); + DataType tb = getType(bObj); + if (ta != this || tb != this) { + if (ta == tb) { + return ta.compare(aObj, bObj); + } + return super.compare(aObj, bObj); + } + // TODO ensure comparable type (both may be comparable but not + // with each other) + if (aObj instanceof Comparable) { + if (aObj.getClass().isAssignableFrom(bObj.getClass())) { + return ((Comparable) aObj).compareTo(bObj); + } + } + if (bObj instanceof Comparable) { + if (bObj.getClass().isAssignableFrom(aObj.getClass())) { + return -((Comparable) bObj).compareTo(aObj); + } + } + byte[] a = serialize(aObj); + byte[] b = serialize(bObj); + return compareNotNull(a, b); + } + + @Override + public int getMemory(Object obj) { + DataType t = getType(obj); + if (t == this) { + return averageSize; + } + return t.getMemory(obj); + } + + @Override + public void write(WriteBuffer buff, Object obj) { + DataType t = getType(obj); + if (t != this) { + t.write(buff, obj); + return; + } + byte[] data = serialize(obj); + // we say they are larger, because these objects + // use quite a lot of disk space + int size = data.length * 2; + // adjust the average size + // using an exponential moving average + averageSize = (int) ((size + 15L * averageSize) / 16); + buff.put((byte) TYPE_SERIALIZED_OBJECT).putVarInt(data.length) + .put(data); + } + + @Override + public Object read(ByteBuffer buff) { + return read(buff, buff.get()); + } + + @Override + public Object read(ByteBuffer buff, int tag) { + int len = DataUtils.readVarInt(buff); + byte[] data = Utils.newBytes(len); + int size = data.length * 2; + // adjust the average size + // using an exponential moving average + averageSize = (int) ((size + 15L * averageSize) / 16); + buff.get(data); + return deserialize(data); + } + + } + +} diff --git a/h2/src/main/org/h2/mvstore/type/StatefulDataType.java b/h2/src/main/org/h2/mvstore/type/StatefulDataType.java new file mode 100644 index 0000000..9a53c2c --- /dev/null +++ b/h2/src/main/org/h2/mvstore/type/StatefulDataType.java @@ -0,0 +1,47 @@ +/* + * 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.mvstore.type; + +import java.nio.ByteBuffer; + +import org.h2.mvstore.WriteBuffer; + +/** + * A data type that allows to save its state. + * + * @param type of opaque parameter passed as an operational context to Factory.create() + * + * @author Andrei Tokar + */ +public interface StatefulDataType { + + /** + * Save the state. + * + * @param buff the target buffer + * @param metaType the meta type + */ + void save(WriteBuffer buff, MetaType metaType); + + Factory getFactory(); + + /** + * A factory for data types. + * + * @param the database type + */ + interface Factory { + /** + * Reads the data type. + * + * @param buff the buffer the source buffer + * @param metaDataType the type + * @param database the database + * @return the data type + */ + DataType create(ByteBuffer buff, MetaType metaDataType, D database); + } +} diff --git a/h2/src/main/org/h2/mvstore/type/StringDataType.java b/h2/src/main/org/h2/mvstore/type/StringDataType.java new file mode 100644 index 0000000..63f907c --- /dev/null +++ b/h2/src/main/org/h2/mvstore/type/StringDataType.java @@ -0,0 +1,72 @@ +/* + * 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.mvstore.type; + +import java.nio.ByteBuffer; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.WriteBuffer; + +/** + * A string type. + */ +public class StringDataType extends BasicDataType { + + public static final StringDataType INSTANCE = new StringDataType(); + + private static final String[] EMPTY_STRING_ARR = new String[0]; + + @Override + public String[] createStorage(int size) { + return size == 0 ? EMPTY_STRING_ARR : new String[size]; + } + + @Override + public int compare(String a, String b) { + return a.compareTo(b); + } + + @Override + public int binarySearch(String key, Object storageObj, int size, int initialGuess) { + String[] storage = cast(storageObj); + int low = 0; + int high = size - 1; + // the cached index minus one, so that + // for the first time (when cachedCompare is 0), + // the default value is used + int x = initialGuess - 1; + if (x < 0 || x > high) { + x = high >>> 1; + } + while (low <= high) { + int compare = key.compareTo(storage[x]); + if (compare > 0) { + low = x + 1; + } else if (compare < 0) { + high = x - 1; + } else { + return x; + } + x = (low + high) >>> 1; + } + return -(low + 1); + } + @Override + public int getMemory(String obj) { + return 24 + 2 * obj.length(); + } + + @Override + public String read(ByteBuffer buff) { + return DataUtils.readString(buff); + } + + @Override + public void write(WriteBuffer buff, String s) { + int len = s.length(); + buff.putVarInt(len).putStringData(s, len); + } +} + diff --git a/h2/src/main/org/h2/mvstore/type/package.html b/h2/src/main/org/h2/mvstore/type/package.html new file mode 100644 index 0000000..110f3d7 --- /dev/null +++ b/h2/src/main/org/h2/mvstore/type/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Data types and serialization / deserialization + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/package.html b/h2/src/main/org/h2/package.html new file mode 100644 index 0000000..77e2084 --- /dev/null +++ b/h2/src/main/org/h2/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Implementation of the JDBC driver. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/res/_messages_cs.prop b/h2/src/main/org/h2/res/_messages_cs.prop new file mode 100644 index 0000000..f827d3d --- /dev/null +++ b/h2/src/main/org/h2/res/_messages_cs.prop @@ -0,0 +1,201 @@ +.translator=Hannibal (http://hannibal.cestiny.cz/) +02000=Žádná data nejsou k dispozici +07001=Neplatný počet parametrů pro {0}, očekávaný počet: {1} +08000=Chyba při otevírání databáze: {0} +21S02=Počet sloupců nesouhlasí +22001=Příliš dlouhá hodnota pro sloupec {0}: {1} +22003=Číselná hodnota je mimo rozsah: {0} +22004=#Numeric value out of range: {0} in column {1} +22007=Nelze zpracovat konstantu {0} {1} +22012=Dělení nulou: {0} +22013=#Invalid PRECEDING or FOLLOWING size in window function: {0} +22018=Chyba při převodu dat {0} +22025=Chyba v LIKE escapování: {0} +2202E=#Array element error: {0}, expected {1} +22030=#Value not permitted for column {0}: {1} +22031=#Value not a member of enumerators {0}: {1} +22032=#Empty enums are not allowed +22033=#Duplicate enumerators are not allowed for enum types: {0} +23502=Pro sloupec {0} není hodnota NULL povolena +23503=Nedodržení omezení referenční integrity: {0} +23505=Nedodržení unikátního indexu nebo primárního klíče: {0} +23506=Nedodržení omezení referenční integrity: {0} +23507=Nebyla nastavena žádná výchozí hodnota pro sloupec {0} +23513=Nedodržení omezení kontroly: {0} +23514=#Check constraint invalid: {0} +28000=Nesprávné uživatelské jméno nebo heslo +40001=Detekován deadlock. Probíhající transakce byla vrácena zpět. Podrobnosti: {0} +42000=Chyba syntaxe v SQL příkazu {0} +42001=Chyba syntaxe v SQL příkazu {0}; očekáváno {1} +42602=#Invalid name {0} +42622=#The name that starts with {0} is too long. The maximum length is {1} +42S01=Tabulka {0} již existuje +42S02=Tabulka {0} nenalezena +42S03=#Table {0} not found (candidates are: {1}) +42S04=#Table {0} not found (this database is empty) +42S11=Index {0} již existuje +42S12=Index {0} nenalezen +42S21=Duplicitní název sloupce {0} +42S22=Sloupec {0} nenalezen +42S31=#Identical expressions should be used; expected {0}, found {1} +54011=#Too many columns. The maximum count is {0} +57014=Příkaz byl zrušen nebo připojení vypršelo +90000=Funkce {0} musí vracet výsledek +90001=Metoda neumožňuje dotazování. Použijte execute nebo executeQuery namísto executeUpdate +90002=Metoda umožňuje pouze pro dotazování. Použijte execute nebo executeUpdate namísto executeQuery +90003=Hexadecimální řetězec s lichým počtem znaků: {0} +90005=#Invalid trigger flags: {0} +90004=Hexadecimální řetězec obsahuje neplatný znak: {0} +90006=#Sequence {0} has run out of numbers +90007=Tento objekt byl již uzavřen +90008=Neplatná hodnota {0} pro parametr {1} +90009=#Unable to create or alter sequence {0} because of invalid attributes (base value {1}, start value {2}, min value {3}, max value {4}, increment {5}, cache size {6}) +90010=#Invalid TO_CHAR format {0} +90011=#A file path that is implicitly relative to the current working directory is not allowed in the database URL {0}. Use an absolute path, ~/name, ./name, or the baseDir setting instead. +90012=Parametr {0} není nastaven +90013=Databáze {0} nenalezena +90014=Chyba zpracování {0} +90015=SUM nebo AVG pro nesprávný datový typ {0} +90016=Sloupec {0} musí být uveden v seznamu GROUP BY +90017=Pokus o nastavení druhého primárního klíče +90018=Připojení nebylo ukončeno aplikací a bude automaticky odstraněno +90019=Nelze smazat stávajícího uživatele +90020=Databáze je pravděpodobně používána: {0}. Možná řešení: uzavřete všechny ostatní připojení; použijte režim server +90021=#This combination of database settings is not supported: {0} +90022=Funkce {0} nenalezena +90023=Sloupec {0} nesmí mít možnou hodnotu NULL +90024=Chyba při přejmenování souboru {0} na {1} +90025=Nelze smazat soubor {0} +90026=Serializace selhala, příčina: {0} +90027=Deserializace selhala, příčina: {0} +90028=Výjimka vstupu a výstupu: {0} +90029=Nyní nelze řádek aktualizovat +90030=Při čtení záznamu zjištěno poškození souboru: {0}. Možná řešení: použijte nástroj pro zotavení +90031=Výjimka vstupu a výstupu: {0}; {1} +90032=Uživatel {0} nenalezena +90033=Uživatel {0} již existuje +90034=Chyba log souboru: {0}, příčina: {1} +90035=Sekvence {0} již existuje +90036=Sekvence {0} nenalezena +90037=Pohled {0} nenalezen +90038=Pohled {0} již existuje +90039=#This CLOB or BLOB reference timed out: {0} +90040=Pro tuto operaci je vyžadováno oprávnění Admin +90041=Trigger {0} již existuje +90042=Trigger {0} nenalezen +90043=Chyba při vytvoření nebo zavedení triggeru {0} objekt, třída {1}, příčina: {2}; pro podrobnosti prostudujte původní příčinu +90044=Chyba při provádění triggeru {0}, třída {1}, příčina: {2}; pro podrobnosti prostudujte původní příčinu +90045=Omezení {0} již existuje +90046=Chyba formátu URL; musí být {0}, je však {1} +90047=Verze nesouhlasí, verze ovladače je {0}, ale verze serveru je {1} +90048=Nepodporovaná verze souboru databáze nebo neplatná hlavička souboru {0} +90049=Chyba šifrování v souboru {0} +90050=Nesprávný formát hesla, musí být: heslo k souboru uživatelské heslo +90052=Vnořený dotaz není pouze jediný sloupec dotazu +90053=Skalární vnořený dotaz obsahuje více než jeden řádek +90054=Neplatné použití agregátní funkce {0} +90055=Nepodporované šifrování {0} +90056=#Function {0}: Invalid date format: {1} +90057=Omezení {0} nenalezeno +90058=Vkládání nebo vrácení změn není povoleno uvnitř triggeru +90059=Dvojsmyslný název sloupce {0} +90060=Nepodporovaný způsob zamykání souboru {0} +90061=Výjimka při otevírání portu {0} (port je již pravděpodobně používán), příčina: {1} +90062=Chyba při vytváření souboru {0} +90063=Uložený bod je neplatný: {0} +90064=Uložený bod není pojmenovaný +90065=Uložený bod je pojmenovaný +90066=Duplicitní vlastnost {0} +90067=Připojení bylo přerušeno: {0} +90068=Výraz pro seřazení {0} musí být v tomto případě uveden v seznamu výsledků +90069=Role {0} již existuje +90070=Role {0} nenalezena +90071=Uživatel nebo role {0} nenalezena +90072=Role a oprávnění nelze vzájemně míchat +90073=Shodně pojmenované Java metody musí mít odlišný počet parametrů: {0} a {1} +90074=Role {0} byla již udělena +90075=Sloupec je součástí indexu {0} +90076=Alias funkce {0} již existuje +90077=Alias funkce {0} nenalezen +90078=Schéma {0} již existuje +90079=Schéma {0} nenalezeno +90080=Název schématu musí být shodný +90081=Sloupec {0} obsahuje NULL hodnoty +90082=Sekvence {0} patří k tabulce +90083=Sloupec může být odkazem {0} +90084=Nelze odstranit poslední sloupec {0} +90085=Index {0} patří k omezení {1} +90086=Třída {0} nenalezena +90087=Metoda {0} nenalezena +90088=Neznámý režim {0} +90089=Porovnání nemůže být změněno, protože jde o datovou tabulku: {0} +90090=Schéma {0} nemůže být odstraněno +90091=Role {0} nemůže být odstraněna +90093=Chyba clusteru - databáze je spuštěna v samostatném režimu +90094=Chyba clusteru - databáze je spuštěna v cluster režimu, seznam serverů: {0} +90095=Chyba formátu řetězce: {0} +90096=Nedostatečná oprávnění k objektu {0} +90097=Databáze je pouze pro čtení +90098=Databáze byla ukončena +90099=Chyba nastavení posluchače událostí databáze {0}, příčina: {1} +90101=Špatný XID formát: {0} +90102=Nepodporovaná kompresní volba: {0} +90103=Nepodporovaný kompresní algoritmus: {0} +90104=Chyba komprese +90105=Výjimka při volání uživatelsky definované funkce: {0} +90106=Nelze vyprázdnit {0} +90107=Nelze odstranit {0}, protože {1} na něm závisí +90108=Nedostatek paměti. +90109=Pohled {0} je neplatný: {1} +90110=#Values of types {0} and {1} are not comparable +90111=Chyba přístupu propojené tabulky s SQL příkazem {0}, příčina: {1} +90112=Řádek nebyl nalezen při pokusu o smazání z indexu {0} +90113=Nepodporované nastavení připojení {0} +90114=Konstanta {0} již existuje +90115=Konstanta {0} nenalezena +90116=Definice tohoto druhu nejsou povoleny +90117=Vzdálené připojení není na tomto serveru povoleno, zkontrolujte volbu -tcpAllowOthers +90118=Nelze odstranit tabulku {0} +90119=Doména {0} již existuje +90120=Doména {0} nenalezen +90121=Databáze byla již ukončena (pro deaktivaci automatického ukončení při zastavení virtuálního stroje přidejte parametr ";DB_CLOSE_ON_EXIT=FALSE" do URL databáze) +90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause. +90123=Nelze vzájemně míchat indexované a neindexované parametry +90124=Soubor nenalezen: {0} +90125=Neplatná třída, očekáváno {0}, ale obdrženo {1} +90126=Databáze není perzistentní +90127=Vrácený výsledek nelze upravovat. Dotaz musí vybrat všechny sloupce, které jsou definovány jako unikátní klíč. Vybrána může být pouze jediná tabulka. +90128=Vrácený výsledek nelze procházet ani resetovat. Možná budete muset použít conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ...). +90129=Transakce {0} nenalezena +90130=Tato metoda není povolena pro připravený dotaz; místo toho použijte regulérní dotaz. +90131=Souběžná úprava v tabulce {0}: Jiná transakce upravila nebo smazala stejný řádek +90132=Agregátor {0} nenalezen +90133=Nelze změnit nastavení {0}, pokud je již databáze otevřena +90134=Přístup ke třídě {0} byl odepřen +90135=Databáze je spuštěna ve vyhrazeném režimu; nelze otevřít další spojení +90136=#Window not found: {0} +90137=Lze přiřadit pouze proměnné, nikoli: {0} +90138=Neplatný název databáze: {0} +90139=Nenalezena veřejná statická Java metoda: {0} +90140=Vrácený výsledek je pouze pro čtení. Možná budete muset použít conn.createStatement(..., ResultSet.CONCUR_UPDATABLE). +90141=#Serializer cannot be changed because there is a data table: {0} +90142=#Step size must not be zero +90143=#Row {1} not found in primary index {0} +90144=#Authenticator not enabled on database {0} +90145=#FOR UPDATE is not allowed in DISTINCT or grouped select +90146=#Database {0} not found, and IFEXISTS=true, so we can't auto-create it +90147=#Method {0} is not allowed when connection is in auto-commit mode +90148=#Current value of sequence {0} is not yet defined in this session +90149=#Database {0} not found, either pre-create it or allow remote database creation (not recommended in secure environments) +90150=#Precision ({0}) must be between {1} and {2} inclusive +90151=#Scale or fractional seconds precision ({0}) must be between {1} and {2} inclusive +90152=#Constraint {0} is used by constraint {1} +90153=#Column {0} references uncomparable column {1} +90154=#Generated column {0} cannot be assigned +90155=#Generated column {0} cannot be updatable by a referential constraint with {1} clause +90156=#Column alias is not specified for expression {0} +90157=#Column index {0} in GROUP BY clause is outside valid range 1 - {1} +HY000=Obecná chyba: {0} +HY004=Neznámý datový typ: {0} +HYC00=Vlastnost není podporována: {0} +HYT00=Timeout se pokouší uzamknout tabulku {0} diff --git a/h2/src/main/org/h2/res/_messages_de.prop b/h2/src/main/org/h2/res/_messages_de.prop new file mode 100644 index 0000000..f91951e --- /dev/null +++ b/h2/src/main/org/h2/res/_messages_de.prop @@ -0,0 +1,201 @@ +.translator=Thomas Mueller +02000=Keine Daten verfügbar +07001=Ungültige Anzahl Parameter für {0}, erwartet: {1} +08000=Fehler beim Öffnen der Datenbank: {0} +21S02=Anzahl der Felder stimmt nicht überein +22001=Wert zu groß / lang für Feld {0}: {1} +22003=Numerischer Wert außerhalb des Bereichs: {0} +22004=Numerischer Wert außerhalb des Bereichs: {0} in Feld {1} +22007=Kann {0} {1} nicht umwandeln +22012=Division durch 0: {0} +22013=Ungültige PRECEDING oder FOLLOWING Größe in Window-Funktion: {0} +22018=Datenumwandlungsfehler beim Umwandeln von {0} +22025=Fehler in LIKE ESCAPE: {0} +2202E=Fehlerhaftes Array-Element: {0}, erwartet: {1} +22030=Wert nicht erlaubt für Feld {0}: {1} +22031=Wert nicht Teil der Aufzählung {0}: {1} +22032=Leere Aufzählungen sind nicht erlaubt +22033=Doppelte Nennungen sind nicht erlaubt für Aufzählungstypen: {0} +23502=NULL nicht zulässig für Feld {0} +23503=Referentielle Integrität verletzt: {0} +23505=Eindeutiger Index oder Primärschlüssel verletzt: {0} +23506=Referentielle Integrität verletzt: {0} +23507=Kein Vorgabewert für Feld {0} +23513=Bedingung verletzt: {0} +23514=Ungültige Bedingung: {0} +28000=Falscher Benutzername oder Passwort +40001=Eine Verklemmung (Deadlock) ist aufgetreten. Die aktuelle Transaktion wurde rückgängig gemacht. Details: {0} +42000=Syntax Fehler in SQL Befehl {0} +42001=Syntax Fehler in SQL Befehl {0}; erwartet {1} +42602=Ungültiger Name {0} +42622=Der Name mit {0} beginnt ist zu lang. Die maximale Länge beträgt {1} +42S01=Tabelle {0} besteht bereits +42S02=Tabelle {0} nicht gefunden +42S03=Tabelle {0} nicht gefunden (mögliche Kandidaten: {1}) +42S04=Tabelle {0} nicht gefunden (diese Datenbank ist leer) +42S11=Index {0} besteht bereits +42S12=Index {0} nicht gefunden +42S21=Doppelter Feldname {0} +42S22=Feld {0} nicht gefunden +42S31=Es sollten identische Ausdrücke verwendet werden; erwartet {0}, tatsächlich {1} +54011=Zu viele Felder definiert. Maximale Anzahl von Felder: {0} +57014=Befehl wurde abgebrochen oder das Session-Timeout ist abgelaufen +90000=Funktion {0} muss Zeilen zurückgeben +90001=Methode nicht zulässig für eine Abfrage. Erlaubt sind execute oder executeQuery, nicht jedoch executeUpdate +90002=Methode nur zulässig für eine Abfrage. Erlaubt sind execute oder executeUpdate, nicht jedoch executeQuery +90003=Hexadezimal Zahl mit einer ungeraden Anzahl Zeichen: {0} +90004=Hexadezimal Zahl enthält unerlaubtes Zeichen: {0} +90005=Ungültige Triggeroptionen: {0} +90006=Die Sequenz {0} hat keine freien Nummern mehr +90007=Das Objekt wurde bereits geschlossen +90008=Unerlaubter Wert {0} für Parameter {1} +90009=Kann die Sequenz {0} nicht ändern aufgrund falscher Attribute (Basiswert {1}, Start-Wert {2}, Minimal-Wert {3}, Maximal-Wert {4}, Inkrement {5}, Cachegröße {6}) +90010=Ungültiges TO_CHAR Format {0} +90011=Ein implizit relativer Pfad zum Arbeitsverzeichnis ist nicht erlaubt in der Datenbank URL {0}. Bitte absolute Pfade, ~/name, ./name, oder baseDir verwenden. +90012=Parameter {0} wurde nicht gesetzt +90013=Datenbank {0} nicht gefunden +90014=Fehler beim Parsen von {0} +90015=SUM oder AVG auf falschem Datentyp für {0} +90016=Feld {0} muss in der GROUP BY Liste sein +90017=Versuche, einen zweiten Primärschlüssel zu definieren +90018=Die Datenbank-Verbindung wurde nicht explizit geschlossen (jetzt in der Müllabfuhr) +90019=Kann aktuellen Benutzer nicht löschen +90020=Datenbank wird wahrscheinlich bereits benutzt: {0}. Mögliche Lösungen: alle Verbindungen schliessen; Server Modus verwenden +90021=Diese Kombination von Einstellungen wird nicht unterstützt {0} +90022=Funktion {0} nicht gefunden +90023=Feld {0} darf nicht nullable sein +90024=Fehler beim Umbenennen der Datei {0} nach {1} +90025=Kann Datei {0} nicht löschen +90026=Serialisierung fehlgeschlagen, Grund: {0} +90027=De-Serialisierung fehlgeschlagen, Grund: {0} +90028=Eingabe/Ausgabe Fehler: {0} +90029=Im Moment nicht auf einer veränderbaren Zeile +90030=Datei fehlerhaft beim Lesen des Datensatzes: {0}. Mögliche Lösung: Recovery Werkzeug verwenden +90031=Eingabe/Ausgabe: {0}; {1} +90032=Benutzer {0} nicht gefunden +90033=Benutzer {0} besteht bereits +90034=Log Datei Fehler: {0}, Grund: {1} +90035=Sequenz {0} besteht bereits +90036=Sequenz {0} nicht gefunden +90037=View {0} nicht gefunden +90038=View {0} besteht bereits +90039=Diese CLOB oder BLOB Reference ist abgelaufen: {0} +90040=Für diese Operation werden Administrator-Rechte benötigt +90041=Trigger {0} besteht bereits +90042=Trigger {0} nicht gefunden +90043=Fehler beim Erzeugen des Triggers {0}, Klasse {1}, Grund: {1}; siehe Ursache für Details +90044=Fehler beim Ausführen des Triggers {0}, Klasse {1}, Grund: {1}; siehe Ursache für Details +90045=Bedingung {0} besteht bereits +90046=URL Format Fehler; erwartet {0}, erhalten {1} +90047=Falsche Version, Treiberversion ist {0}, Serverversion ist {1} +90048=Datenbank Datei Version wird nicht unterstützt oder ungültiger Dateikopf in Datei {0} +90049=Verschlüsselungsfehler in Datei {0} +90050=Falsches Passwortformat, benötigt wird: Datei-Passwort Benutzer-Passwort +90052=Unterabfrage gibt mehr als eine Feld zurück +90053=Skalar-Unterabfrage enthält mehr als eine Zeile +90054=Ungültige Verwendung der Aggregat Funktion {0} +90055=Chiffre nicht unterstützt: {0} +90056=Funktion {0}: Ungültiges Datums-Format: {1} +90057=Bedingung {0} nicht gefunden +90058=Innerhalb eines Triggers sind Commit und Rollback ist nicht erlaubt +90059=Mehrdeutiger Feldname {0} +90060=Ungültige Datei-Sperr-Methode {0} +90061=Fehler beim Öffnen von Port {0} (Port wird ev. bereits verwendet), Grund: {1} +90062=Fehler beim Erzeugen der Datei {0} +90063=Savepoint ist ungültig: {0} +90064=Savepoint hat keinen Namen +90065=Savepoint hat einen Namen +90066=Doppeltes Merkmahl {0} +90067=Verbindung ist unterbrochen: {0} +90068=Sortierausdruck {0} muss in diesem Fall im Resultat vorkommen +90069=Rolle {0} besteht bereits +90070=Rolle {0} nicht gefunden +90071=Benutzer or Rolle {0} nicht gefunden +90072=Rollen und Rechte können nicht gemischt werden +90073=Java Methoden müssen eine unterschiedliche Anzahl Parameter aufweisen: {0} und {1} +90074=Rolle {0} bereits zugewiesen +90075=Feld ist Teil eines Indexes {0} +90076=Funktions-Alias {0} besteht bereits +90077=Funktions-Alias {0} nicht gefunden +90078=Schema {0} besteht bereits +90079=Schema {0} nicht gefunden +90080=Schemanamen müssen übereinstimmen +90081=Feld {0} enthält NULL Werte +90082=Sequenz {0} gehört zu einer Tabelle +90083=Feld wird referenziert durch {0} +90084=Kann das letzte Feld nicht löschen {0} +90085=Index {0} gehört zur Bedingung {1} +90086=Klasse {0} nicht gefunden +90087=Methode {0} nicht gefunden +90088=Unbekannter Modus {0} +90089=Textvergleich-Modus kann nicht geändert werden wenn eine Daten-Tabelle existiert : {0} +90090=Schema {0} kann nicht gelöscht werden +90091=Rolle {0} kann nicht gelöscht werden +90093=Clustering Fehler - Datenbank läuft bereits im autonomen Modus +90094=Clustering Fehler - Datenbank läuft bereits im Cluster-Modus, Serverliste: {0} +90095=Textformat Fehler: {0} +90096=Nicht genug Rechte für Objekt {0} +90097=Die Datenbank ist schreibgeschützt +90098=Die Datenbank ist bereits geschlossen +90099=Fehler beim Setzen des Datenbank Ereignis Empfängers {0}, Grund: {1} +90101=Falsches XID Format: {0} +90102=Datenkompressions-Option nicht unterstützt: {0} +90103=Datenkompressions-Algorithmus nicht unterstützt: {0} +90104=Datenkompressions-Fehler +90105=Fehler beim Aufruf eine benutzerdefinierten Funktion: {0} +90106=Kann {0} nicht zurücksetzen per TRUNCATE +90107=Kann {0} nicht löschen weil {1} davon abhängt +90108=Nicht genug Hauptspeicher. +90109=View {0} ist ungültig: {1} +90110=Werte des Typs {0} und {1} sind nicht vergleichbar +90111=Fehler beim Zugriff auf eine verknüpfte Tabelle mit SQL Befehl {0}, Grund: {1} +90112=Zeile nicht gefunden beim Löschen von Index {0} +90113=Datenbank-Verbindungs Option {0} nicht unterstützt +90114=Konstante {0} besteht bereits +90115=Konstante {0} nicht gefunden +90116=Literal dieser Art nicht zugelassen +90117=Verbindungen von anderen Rechnern sind nicht freigegeben, siehe -tcpAllowOthers +90118=Kann Tabelle nicht löschen {0} +90119=Domäne {0} besteht bereits +90120=Domäne {0} nicht gefunden +90121=Die Datenbank wurde bereits geschlossen (um das automatische Schliessen beim Stopp der VM zu deaktivieren, die Datenbank URL mit ";DB_CLOSE_ON_EXIT=FALSE" ergänzen) +90122=Der WITH TIES Ausdruck ist ohne zugehörigem ORDER BY Ausdruck nicht erlaubt. +90123=Kann nicht indizierte und nicht indizierte Parameter mischen +90124=Datei nicht gefunden: {0} +90125=Ungültig Klasse, erwartet {0} erhalten {1} +90126=Datenbank ist nicht persistent +90127=Die Resultat-Zeilen können nicht verändert werden. Die Abfrage muss alle Felder eines eindeutigen Schlüssels enthalten, und nur eine Tabelle enthalten. +90128=Kann nicht an den Anfang der Resultat-Zeilen springen. Mögliche Lösung: conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ..). +90129=Transaktion {0} nicht gefunden +90130=Diese Methode ist nicht erlaubt für ein PreparedStatement; benützen Sie ein Statement. +90131=Gleichzeitige Änderung in Tabelle {0}: eine andere Transaktion hat den gleichen Datensatz geändert oder gelöscht +90132=Aggregat-Funktion {0} nicht gefunden +90133=Kann das Setting {0} nicht ändern wenn die Datenbank bereits geöffnet ist +90134=Der Zugriff auf die Klasse {0} ist nicht erlaubt +90135=Die Datenbank befindet sich im Exclusiv Modus; es können keine zusätzlichen Verbindungen geöffnet werden +90136=Bereich (Window) nicht gefunden: {0} +90137=Werte können nur einer Variablen zugewiesen werden, nicht an: {0} +90138=Ungültiger Datenbankname: {0} +90139=Die (public static) Java-Funktion wurde nicht gefunden: {0} +90140=Die Resultat-Zeilen können nicht verändert werden. Mögliche Lösung: conn.createStatement(.., ResultSet.CONCUR_UPDATABLE). +90141=Serialisierer kann nicht geändert werden wenn eine Daten-Tabelle existiert: {0} +90142=Schrittgröße darf nicht 0 sein +90143=Zeile {1} nicht gefunden im Primärschlüssel {0} +90144=Authenticator ist für die Datenbank {0} nicht aktiviert +90145=FOR UPDATE ist in einem DISTINCT oder gruppiertem Select nicht erlaubt +90146=Datenbank {0} nicht gefunden und IFEXISTS=true, daher können wir sie nicht automatisch anlegen +90147=Methode {0} ist nicht erlaubt, wenn sich die Verbindung im auto-commit Modus befindet +90148=Der aktuelle Wert der Sequenz {0} ist in dieser Session noch nicht definiert +90149=Datenbank {0} nicht gefunden. Entweder legen Sie sie an oder erlauben das Anlegen einer Datenbank aus der Ferne (nicht empfohlen in sicherheitsrelevanten Umgebungen) +90150=Genauigkeit ({0}) muss zwischen {1} und {2} inklusive liegen +90151=Genauigkeit von Skalierung oder anteiligen Sekunden ({0}) muss zwischen {1} und {2} inklusive liegen +90152=Referentielle Integrität {0} wird von referentieller Integrität {1} genutzt +90153=Spalte {0} bezieht sich auf nicht vergleichbare Spalte {1} +90154=Erzeugte Spalte {0} kann nicht zugewiesen werden +90155=Erzeugte Spalte {0} kann nicht durch eine referentielle Integrität mit dem Ausdruck {1} veränderbar sein +90156=Spalten-Alias ist nicht für den Audruck {0} angegeben +90157=#Column index {0} in GROUP BY clause is outside valid range 1 - {1} +HY000=Allgemeiner Fehler: {0} +HY004=Unbekannter Datentyp: {0} +HYC00=Dieses Feature wird nicht unterstützt: {0} +HYT00=Zeitüberschreitung beim Versuch die Tabelle {0} zu sperren diff --git a/h2/src/main/org/h2/res/_messages_en.prop b/h2/src/main/org/h2/res/_messages_en.prop new file mode 100644 index 0000000..85844f6 --- /dev/null +++ b/h2/src/main/org/h2/res/_messages_en.prop @@ -0,0 +1,201 @@ +.translator=Thomas Mueller +02000=No data is available +07001=Invalid parameter count for {0}, expected count: {1} +08000=Error opening database: {0} +21S02=Column count does not match +22001=Value too long for column {0}: {1} +22003=Numeric value out of range: {0} +22004=Numeric value out of range: {0} in column {1} +22007=Cannot parse {0} constant {1} +22012=Division by zero: {0} +22013=Invalid PRECEDING or FOLLOWING size in window function: {0} +22018=Data conversion error converting {0} +22025=Error in LIKE ESCAPE: {0} +2202E=Array element error: {0}, expected {1} +22030=Value not permitted for column {0}: {1} +22031=Value not a member of enumerators {0}: {1} +22032=Empty enums are not allowed +22033=Duplicate enumerators are not allowed for enum types: {0} +23502=NULL not allowed for column {0} +23503=Referential integrity constraint violation: {0} +23505=Unique index or primary key violation: {0} +23506=Referential integrity constraint violation: {0} +23507=No default value is set for column {0} +23513=Check constraint violation: {0} +23514=Check constraint invalid: {0} +28000=Wrong user name or password +40001=Deadlock detected. The current transaction was rolled back. Details: {0} +42000=Syntax error in SQL statement {0} +42001=Syntax error in SQL statement {0}; expected {1} +42602=Invalid name {0} +42622=The name that starts with {0} is too long. The maximum length is {1} +42S01=Table {0} already exists +42S02=Table {0} not found +42S03=Table {0} not found (candidates are: {1}) +42S04=Table {0} not found (this database is empty) +42S11=Index {0} already exists +42S12=Index {0} not found +42S21=Duplicate column name {0} +42S22=Column {0} not found +42S31=Identical expressions should be used; expected {0}, found {1} +54011=Too many columns. The maximum count is {0} +57014=Statement was canceled or the session timed out +90000=Function {0} must return a result set +90001=Method is not allowed for a query. Use execute or executeQuery instead of executeUpdate +90002=Method is only allowed for a query. Use execute or executeUpdate instead of executeQuery +90003=Hexadecimal string with odd number of characters: {0} +90004=Hexadecimal string contains non-hex character: {0} +90005=Invalid trigger flags: {0} +90006=Sequence {0} has run out of numbers +90007=The object is already closed +90008=Invalid value {0} for parameter {1} +90009=Unable to create or alter sequence {0} because of invalid attributes (base value {1}, start value {2}, min value {3}, max value {4}, increment {5}, cache size {6}) +90010=Invalid TO_CHAR format {0} +90011=A file path that is implicitly relative to the current working directory is not allowed in the database URL {0}. Use an absolute path, ~/name, ./name, or the baseDir setting instead. +90012=Parameter {0} is not set +90013=Database {0} not found +90014=Error parsing {0} +90015=SUM or AVG on wrong data type for {0} +90016=Column {0} must be in the GROUP BY list +90017=Attempt to define a second primary key +90018=The connection was not closed by the application and is garbage collected +90019=Cannot drop the current user +90020=Database may be already in use: {0}. Possible solutions: close all other connection(s); use the server mode +90021=This combination of database settings is not supported: {0} +90022=Function {0} not found +90023=Column {0} must not be nullable +90024=Error while renaming file {0} to {1} +90025=Cannot delete file {0} +90026=Serialization failed, cause: {0} +90027=Deserialization failed, cause: {0} +90028=IO Exception: {0} +90029=Currently not on an updatable row +90030=File corrupted while reading record: {0}. Possible solution: use the recovery tool +90031=IO Exception: {0}; {1} +90032=User {0} not found +90033=User {0} already exists +90034=Log file error: {0}, cause: {1} +90035=Sequence {0} already exists +90036=Sequence {0} not found +90037=View {0} not found +90038=View {0} already exists +90039=This CLOB or BLOB reference timed out: {0} +90040=Admin rights are required for this operation +90041=Trigger {0} already exists +90042=Trigger {0} not found +90043=Error creating or initializing trigger {0} object, class {1}, cause: {2}; see root cause for details +90044=Error executing trigger {0}, class {1}, cause : {2}; see root cause for details +90045=Constraint {0} already exists +90046=URL format error; must be {0} but is {1} +90047=Version mismatch, driver version is {0} but server version is {1} +90048=Unsupported database file version or invalid file header in file {0} +90049=Encryption error in file {0} +90050=Wrong password format, must be: file password user password +90052=Subquery is not a single column query +90053=Scalar subquery contains more than one row +90054=Invalid use of aggregate function {0} +90055=Unsupported cipher {0} +90056=Function {0}: Invalid date format: {1} +90057=Constraint {0} not found +90058=Commit or rollback is not allowed within a trigger +90059=Ambiguous column name {0} +90060=Unsupported file lock method {0} +90061=Exception opening port {0} (port may be in use), cause: {1} +90062=Error while creating file {0} +90063=Savepoint is invalid: {0} +90064=Savepoint is unnamed +90065=Savepoint is named +90066=Duplicate property {0} +90067=Connection is broken: {0} +90068=Order by expression {0} must be in the result list in this case +90069=Role {0} already exists +90070=Role {0} not found +90071=User or role {0} not found +90072=Roles and rights cannot be mixed +90073=Matching Java methods must have different parameter counts: {0} and {1} +90074=Role {0} already granted +90075=Column is part of the index {0} +90076=Function alias {0} already exists +90077=Function alias {0} not found +90078=Schema {0} already exists +90079=Schema {0} not found +90080=Schema name must match +90081=Column {0} contains null values +90082=Sequence {0} belongs to a table +90083=Column may be referenced by {0} +90084=Cannot drop last column {0} +90085=Index {0} belongs to constraint {1} +90086=Class {0} not found +90087=Method {0} not found +90088=Unknown mode {0} +90089=Collation cannot be changed because there is a data table: {0} +90090=Schema {0} cannot be dropped +90091=Role {0} cannot be dropped +90093=Clustering error - database currently runs in standalone mode +90094=Clustering error - database currently runs in cluster mode, server list: {0} +90095=String format error: {0} +90096=Not enough rights for object {0} +90097=The database is read only +90098=The database has been closed +90099=Error setting database event listener {0}, cause: {1} +90101=Wrong XID format: {0} +90102=Unsupported compression options: {0} +90103=Unsupported compression algorithm: {0} +90104=Compression error +90105=Exception calling user-defined function: {0} +90106=Cannot truncate {0} +90107=Cannot drop {0} because {1} depends on it +90108=Out of memory. +90109=View {0} is invalid: {1} +90110=Values of types {0} and {1} are not comparable +90111=Error accessing linked table with SQL statement {0}, cause: {1} +90112=Row not found when trying to delete from index {0} +90113=Unsupported connection setting {0} +90114=Constant {0} already exists +90115=Constant {0} not found +90116=Literals of this kind are not allowed +90117=Remote connections to this server are not allowed, see -tcpAllowOthers +90118=Cannot drop table {0} +90119=Domain {0} already exists +90120=Domain {0} not found +90121=Database is already closed (to disable automatic closing at VM shutdown, add ";DB_CLOSE_ON_EXIT=FALSE" to the db URL) +90122=The WITH TIES clause is not allowed without a corresponding ORDER BY clause. +90123=Cannot mix indexed and non-indexed parameters +90124=File not found: {0} +90125=Invalid class, expected {0} but got {1} +90126=Database is not persistent +90127=The result set is not updatable. The query must select all columns from a unique key. Only one table may be selected. +90128=The result set is not scrollable and can not be reset. You may need to use conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ..). +90129=Transaction {0} not found +90130=This method is not allowed for a prepared statement; use a regular statement instead. +90131=Concurrent update in table {0}: another transaction has updated or deleted the same row +90132=Aggregate {0} not found +90133=Cannot change the setting {0} when the database is already open +90134=Access to the class {0} is denied +90135=The database is open in exclusive mode; can not open additional connections +90136=Window not found: {0} +90137=Can only assign to a variable, not to: {0} +90138=Invalid database name: {0} +90139=The public static Java method was not found: {0} +90140=The result set is readonly. You may need to use conn.createStatement(.., ResultSet.CONCUR_UPDATABLE). +90141=Serializer cannot be changed because there is a data table: {0} +90142=Step size must not be zero +90143=Row {1} not found in primary index {0} +90144=Authenticator not enabled on database {0} +90145=FOR UPDATE is not allowed in DISTINCT or grouped select +90146=Database {0} not found, and IFEXISTS=true, so we can't auto-create it +90147=Method {0} is not allowed when connection is in auto-commit mode +90148=Current value of sequence {0} is not yet defined in this session +90149=Database {0} not found, either pre-create it or allow remote database creation (not recommended in secure environments) +90150=Precision ({0}) must be between {1} and {2} inclusive +90151=Scale or fractional seconds precision ({0}) must be between {1} and {2} inclusive +90152=Constraint {0} is used by constraint {1} +90153=Column {0} references uncomparable column {1} +90154=Generated column {0} cannot be assigned +90155=Generated column {0} cannot be updatable by a referential constraint with {1} clause +90156=Column alias is not specified for expression {0} +90157=Column index {0} in GROUP BY clause is outside valid range 1 - {1} +HY000=General error: {0} +HY004=Unknown data type: {0} +HYC00=Feature not supported: {0} +HYT00=Timeout trying to lock table {0} diff --git a/h2/src/main/org/h2/res/_messages_es.prop b/h2/src/main/org/h2/res/_messages_es.prop new file mode 100644 index 0000000..50089a4 --- /dev/null +++ b/h2/src/main/org/h2/res/_messages_es.prop @@ -0,0 +1,201 @@ +.translator=Dario V. Fassi +02000=No hay datos disponibles. +07001=Cantidad de parametros invalidos para {0}, cantidad esperada: {1} +08000=Error abriendo la base de datos: {0} +21S02=La cantidad de columnas no coincide +22001=Valor demasiado largo para la columna {0}: {1} +22003=Valor numerico fuera de rango: {0} +22004=#Numeric value out of range: {0} in column {1} +22007=Imposible interpretar la constante {0} {1} +22012=División por cero: {0} +22013=#Invalid PRECEDING or FOLLOWING size in window function: {0} +22018=Conversión de datos fallida, convirtiendo {0} +22025=Error en LIKE ESCAPE: {0} +2202E=#Array element error: {0}, expected {1} +22030=Valor no permitido para la columna {0}: {1} +22031=#Value not a member of enumerators {0}: {1} +22032=#Empty enums are not allowed +22033=#Duplicate enumerators are not allowed for enum types: {0} +23502=La columna {0} no permite valores nulos (NULL) +23503=Violación de una restricción de Integridad Referencial: {0} +23505=Violación de indice de Unicidad ó Clave primaria: {0} +23506=Violación de una restricción de Integridad Referencial: {0} +23507=No se fijado un valor por defecto para la columna {0} +23513=Violación de Check constraint: {0} +23514=#Check constraint invalid: {0} +28000=Nombre de usuario ó password incorrecto +40001=Deadlock - Punto muerto detectado. La transacción actual fue retrotraída (rollback). Detalles: {0} +42000=Error de Sintaxis en sentencia SQL {0} +42001=Error de Sintaxis en sentencia SQL {0}; se esperaba {1} +42602=#Invalid name {0} +42622=#The name that starts with {0} is too long. The maximum length is {1} +42S01=Tabla {0} ya existe +42S02=Tabla {0} no encontrada +42S03=#Table {0} not found (candidates are: {1}) +42S04=#Table {0} not found (this database is empty) +42S11=Indice {0} ya existe +42S12=Indice {0} no encontrado +42S21=Nombre de columna Duplicada {0} +42S22=Columna {0} no encontrada +42S31=#Identical expressions should be used; expected {0}, found {1} +54011=#Too many columns. The maximum count is {0} +57014=Ls sentencia fue cancelado ó la sesión expiró por tiempo vencido +90000=Función {0} debe devolver un set de resultados (ResultSet) +90001=Metodo no permitido en un query. Use execute ó executeQuery en lugar de executeUpdate +90002=Metodo permitido unicamente en un query. Use execute ó executeUpdate en lugar de executeQuery +90003=Cadena Hexadecimal con cantidad impar de caracteres: {0} +90004=Cadena Hexadecimal contiene caracteres invalidos: {0} +90005=#Invalid trigger flags: {0} +90006=#Sequence {0} has run out of numbers +90007=El objeto ya está cerrado +90008=Valor Invalido {0} para el parametro {1} +90009=#Unable to create or alter sequence {0} because of invalid attributes (base value {1}, start value {2}, min value {3}, max value {4}, increment {5}, cache size {6}) +90010=#Invalid TO_CHAR format {0} +90011=#A file path that is implicitly relative to the current working directory is not allowed in the database URL {0}. Use an absolute path, ~/name, ./name, or the baseDir setting instead. +90012=Parametro {0} no está fijado +90013=Database {0} no encontrada +90014=Error interpretando {0} +90015=SUM ó AVG sobre un tipo de datos invalidado para {0} +90016=La columna {0} debe estar incluida en la lista de GROUP BY +90017=Intento de definir una segunda clave primaria +90018=La conexión no fue cerrada por la aplicación y esta siendo limpiada (garbage collected) +90019=Imposible eliminar el usuario actual +90020=La base de datos puede que ya esté siendo utilizada: {0}. Soluciones Posibles: cierre todas las otras conexiones; use el modo server +90021=#This combination of database settings is not supported: {0} +90022=Función {0} no encontrada +90023=Columna {0} no puede ser nullable +90024=Error mientras se renombraba el archivo {0} a {1} +90025=No se pudo borrar el archivo {0} +90026=Serialización fallida, causa: {0} +90027=Deserialización fallida, causa: {0} +90028=IO Exception: {0} +90029=La fila actual no es actualizable +90030=Archivo corrupto mientras se leía el registro: {0}. Solución Posible: use la herramienta de recuperación (recovery tool) +90031=IO Exception: {0}; {1} +90032=Usuario {0} no encontrado +90033=Usuario {0} ya existe +90034=Log archivo error: {0}, causa: {1} +90035=Sequence {0} ya existe +90036=Sequence {0} no encontrado +90037=View {0} no encontrado +90038=View {0} ya existe +90039=#This CLOB or BLOB reference timed out: {0} +90040=Derechos de Admin son requeridos para esta operación +90041=Trigger {0} ya existe +90042=Trigger {0} no encontrado +90043=Error creando ó inicializando trigger {0} objeto, clase {1}, causa: {2}; vea la causa raiz para mas detalle +90044=Error ejecutando trigger {0}, clase {1}, causa : {2}; vea la causa raiz para mas detalle +90045=Constraint {0} ya existe +90046=Error de formato en URL; debe ser {0} pero es {1} +90047=Discordancia de Versión, la versión del driver es {0} y la del server es {1} +90048=Versión del archivo de base de datos no soportada ó encabezado de archivo invalido en archivo {0} +90049=Error de Encriptación en archivo {0} +90050=Formato de password erroneo, debe ser: archivo password Usuario password +90052=El Subquery no es un query escalar (debe devolver una sola columna) +90053=El Subquery escalar contiene mas de una fila +90054=Uso Invalido de la función de columna agregada {0} +90055=Cipher No soportado {0} +90056=Function {0}: Invalid date format: {1} +90057=Constraint {0} no encontrado +90058=Commit ó rollback no permitido dentro de un trigger +90059=Nombre de columna ambigua {0} +90060=Metodo de lockeo de archivo no soportado {0} +90061=Exception abriendo puerto {0} (el puerto puede estar en uso), causa: {1} +90062=Error creando archivo {0} +90063=Savepoint invalido: {0} +90064=Savepoint sin nombre +90065=Savepoint con nombre +90066=Propiedad Duplicada {0} +90067=Conexión rota: {0} +90068=Expresión Order by {0} debe estar en la lista de campos a devolver en este caso +90069=Role {0} ya existe +90070=Role {0} no encontrado +90071=Usuario ó role {0} no encontrado +90072=Roles y derechos no pueden ser mezclados +90073=Los metodos Java de Matching deben tener diferente cantidad de parametros: {0} y {1} +90074=Role {0} ya otorgado +90075=La columna es parte del indice {0} +90076=Función alias {0} ya existe +90077=Función alias {0} no encontrada +90078=Schema {0} ya existe +90079=Schema {0} no encontrado +90080=El nombre del Schema debe concordar +90081=Columna {0} contiene valores nulos +90082=Sequence {0} pertenece a una tabla +90083=La columna puede estar referenciada por {0} +90084=Imposible eliminar la ultima columna {0} +90085=Index {0} pertenece a un constraint {1} +90086=Class {0} no encontrada +90087=#Method {0} not found +90088=Modo desconocido {0} +90089=Collation no puede ser cambiado debido a que existe una tabla de datos: {0} +90090=Schema {0} no puede ser eliminado +90091=Role {0} no puede ser eliminado +90093=Clustering error - la base de datos se esta corriendo en modo standalone +90094=Clustering error - la base de datos esta corriendo en modo CLUSTER, lista de servidores: {0} +90095=String con error de formato: {0} +90096=No tiene los derechos necesarios para el objeto {0} +90097=La base de datos es de solo lectura (read only) +90098=La base de datos ha sido cerrada +90099=Error setting database event listener {0}, causa: {1} +90101=Formato erroneo de XID : {0} +90102=Opciones de compresión No soportadas: {0} +90103=Algoritmo de compresión No soportado: {0} +90104=Error de Compresión +90105=Exception llamando a una función definida por el usuario: {0} +90106=Imposible truncar {0} +90107=Imposible eliminar {0} debido a que {1} depende de él. +90108=Memoria Insuficiente - Out of memory. Tamaño: {0} +90109=La Vista {0} es invalida: {1} +90110=#Values of types {0} and {1} are not comparable +90111=Error accediendo Linked Table con sentencia SQL {0}, causa: {1} +90112=Fila no encontrada mientras se intentaba borrar del indice {0} +90113=Parametro de conexión No soportado {0} +90114=Constante {0} ya existe +90115=Constante {0} no encontrado +90116=Literales de este tipo no estan permitidos +90117=Este server no permite Conexiones Remotas, vea -tcpAllowOthers +90118=Imposible eliminar tabla {0} +90119=Dominio {0} ya existe +90120=Dominio {0} no encontrado +90121=La base de datos ya esta cerrada (para des-habilitar el cerrado automatico durante el shutdown de la VM, agregue ";DB_CLOSE_ON_EXIT=FALSE" a la URL de conexión) +90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause. +90123=No se puede mezclar parametros indexados y no-indexados +90124=Archivo no encontrado: {0} +90125=Clase Invalida, se esperaba {0} pero se obtuvo {1} +90126=La base de datos no es persistente +90127=El conjunto de resultados NO es actualizable. El query debe seleccionar todas la columnas desde una clave de unicidad. Solo una tabla puede ser seleccionada. +90128=El conjunto de resultados NO es scrollable y no puede ser reseteada. You may need to use conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ..). +90129=Transacción {0} no encontrada +90130=Este metodo no esta permitido para una sentencia preparada; en su lugar use una sentencia regular. +90131=Actualización concurrente sobre la tabla {0}: otra transacción ha actualizado ó borrado la misma fila +90132=Aggregate {0} no encontrado +90133=No puede cambiar el setting {0} cuando la base de datos esta abierta +90134=Acceso denegado a la clase {0} +90135=La base de datos esta abierta en modo EXCLUSIVO; no puede abrir conexiones adicionales +90136=#Window not found: {0} +90137=Solo puede asignarse a una variable, no a: {0} +90138=Nombre de base de datos Invalido: {0} +90139=El metodo Java (publico y estatico) : {0} no fue encontrado +90140=El conjunto de resultados es de solo lectura. Puede ser necesario usar conn.createStatement(.., ResultSet.CONCUR_UPDATABLE). +90141=#Serializer cannot be changed because there is a data table: {0} +90142=#Step size must not be zero +90143=#Row {1} not found in primary index {0} +90144=#Authenticator not enabled on database {0} +90145=#FOR UPDATE is not allowed in DISTINCT or grouped select +90146=#Database {0} not found, and IFEXISTS=true, so we can't auto-create it +90147=#Method {0} is not allowed when connection is in auto-commit mode +90148=#Current value of sequence {0} is not yet defined in this session +90149=#Database {0} not found, either pre-create it or allow remote database creation (not recommended in secure environments) +90150=#Precision ({0}) must be between {1} and {2} inclusive +90151=#Scale or fractional seconds precision ({0}) must be between {1} and {2} inclusive +90152=#Constraint {0} is used by constraint {1} +90153=#Column {0} references uncomparable column {1} +90154=#Generated column {0} cannot be assigned +90155=#Generated column {0} cannot be updatable by a referential constraint with {1} clause +90156=#Column alias is not specified for expression {0} +90157=#Column index {0} in GROUP BY clause is outside valid range 1 - {1} +HY000=Error General : {0} +HY004=Tipo de dato desconocido : {0} +HYC00=Caracteristica no soportada: {0} +HYT00=Tiempo vencido intentando trabar (lock) la tabla {0} diff --git a/h2/src/main/org/h2/res/_messages_fr.prop b/h2/src/main/org/h2/res/_messages_fr.prop new file mode 100644 index 0000000..69671ba --- /dev/null +++ b/h2/src/main/org/h2/res/_messages_fr.prop @@ -0,0 +1,201 @@ +.translator=Xavier Bouclet +02000=Aucune donnée disponible +07001=Nombre de paramètre invalide pour {0}, nombre de paramètre attendu: {1} +08000=Une erreur est survenue lors de l''ouverture de la base de données: {0} +21S02=Le nombre de colonnes ne correspond pas +22001=Valeur trop longue pour la colonne {0}: {1} +22003=Valeur numérique hors de portée: {0} +22004=#Numeric value out of range: {0} in column {1} +22007=Impossible d''analyser {0} constante {1} +22012=Division par zéro: {0} +22013=#Invalid PRECEDING or FOLLOWING size in window function: {0} +22018=Erreur lors de la conversion de données {0} +22025=Erreur dans LIKE ESCAPE: {0} +2202E=#Array element error: {0}, expected {1} +22030=Valeur non permise pour la colonne {0}: {1} +22031=La valeur n''est pas un membre de l''énumération {0}: {1} +22032=Les enums vides ne sont pas permis +22033=Les valeurs énumérées en double ne sont pas autorisées pour les types énumérés: {0} +23502=NULL non permis pour la colonne {0} +23503=Intégrité référentielle violation de contrainte: {0} +23505=Violation d''index unique ou clé primaire: {0} +23506=Intégrité référentielle violation de contrainte: {0} +23507=Pas de valeur par défaut initialisée pour la colonne {0} +23513=Vérifiez la violation de contrainte: {0} +23514=Vérifiez la contraite invalide: {0} +28000=Mauvais nom d''utilisateur ou mot de passe +40001=Deadlock détecté. La transaction courante a été annulée. Détails: {0} +42000=Erreur de syntaxe dans l''instruction SQL {0} +42001=Erreur de syntaxe dans l''instruction SQL {0}; attendu {1} +42602=#Invalid name {0} +42622=#The name that starts with {0} is too long. The maximum length is {1} +42S01=La table {0} existe déjà +42S02=Table {0} non trouvée +42S03=#Table {0} not found (candidates are: {1}) +42S04=#Table {0} not found (this database is empty) +42S11=L''index {0} existe déjà +42S12=Index {0} non trouvé +42S21=Duplication du nom de colonnes {0} +42S22=Colonne {0} non trouvée +42S31=#Identical expressions should be used; expected {0}, found {1} +54011=#Too many columns. The maximum count is {0} +57014=L''instruction a été annulée ou la session a expiré +90000=La fonction {0} doit retourner résultat +90001=Methode non autorisée pour une requête. Utilisez execute ou executeQuery à la place d''executeUpdate +90002=Methode est autorisée uniquement pour une requête. Utilisez execute ou executeUpdate à la place d''executeQuery +90003=Chaîne héxadecimale contenant un nombre impair de caractères: {0} +90004=Chaîne héxadecimale contenant un caractère non-héxa: {0} +90005=#Invalid trigger flags: {0} +90006=La séquence {0} a épuisé ses éléments +90007=L''objet est déjà fermé +90008=Valeur invalide {0} pour le paramètre {1} +90009=Impossible de créer ou modifier la séquence {0} car les attributs sont invalides (base value {1}, start value {2}, min value {3}, max value {4}, increment {5}, cache size {6}) +90010=Format invalide TO_CHAR {0} +90011=Un chemin de fichier implicitement relatif au répertoire de travail actuel n''est pas autorisé dans l''URL de la base de données {0}. Utilisez un chemin absolu, ~ /nom, ./nom ou le paramètre baseDir à la place. +90012=La paramètre {0} n''est pas initialisé +90013=Base de données {0} non trouvée +90014=Analyse d''erreur {0} +90015=SUM ou AVG sur le mauvais type de données pour {0} +90016=La colonne {0} doit être dans la liste du GROUP BY +90017=Tentative de définir une seconde clé primaire +90018=La connexion n''a pas été fermée et a été récupérée par le ramasse miette. +90019=Impossible de supprimer l''utilisateur actuel +90020=La base de données est peut-être en cours d''utilisation: {0}. Solutions posibles: fermer toutes les autres connexions; utilisez le mode serveur +90021=Cette combinaison de paramètres de base de données n''est pas supportée: {0} +90022=La fonction {0} n''a pas été trouvée +90023=La colonne {0} ne doit pas être nulle +90024=Erreur lors du renommage du fichier {0} vers {1} +90025=Impossible de supprimer le fichier {0} +90026=La sérialisation a échoué, cause: {0} +90027=La désérialisation a échoué, cause: {0} +90028=IO Exception: {0} +90029=Actuellement sur une ligne non actualisable +90030=Fichier corrompu lors de la lecture de l''enregistrement: {0}. Solution possible: utiliser l''outil de récupération +90031=IO Exception: {0}; {1} +90032=Utilisateur {0} non trouvé +90033=L''utilisateur {0} existe déjà +90034=Erreur du fichier journal: {0}, cause: {1} +90035=La séquence {0} existe déjà +90036=Séquence {0} non trouvée +90037=Vue {0} non trouvée +90038=La vue {0} existe déjà +90039=La référence CLOB ou BLOB a expiré: {0} +90040=Les droits admins sont requis pour cette opération +90041=Le trigger {0} existe déjà +90042=Trigger {0} non trouvé +90043=Erreur lors de la création ou l''initialisation du trigger {0} object, class {1}, cause: {2}; voir la racine de l''erreur pour les détails +90044=Erreur lors de l''exécution du trigger {0}, class {1}, cause : {2}; voir la racine de l''erreur pour les détails +90045=La contrainte {0} existe déjà +90046=Erreur dans le format de l''URL; doit être {0} mais est {1} +90047=Version non correspondante, la version du driver est {0}mais la version du serveur est {1} +90048=Version de fichier de base de données non supportée ou entête de ficher invalide dans le fichier {0} +90049=Erreur de cryptage dans le fichier {0} +90050=Mauvais format de mot de passe, doit être: mot de passe du fichier mot de passe de l''utilisateur +90052=La sous requête n''est pas une requête sur une seule colonne +90053=La sous-requête scalaire contient plus d''une rangée +90054=Utilisation invalide de la fonction agrégée {0} +90055=Chiffrement non pris en charge {0} +90056=Fonction {0}: Format de date invalide: {1} +90057=Contrainte {0} non trouvée +90058=Commit ou rollback n''est pas autorisé à l''intérieur d''un trigger +90059=Nom de colonne ambigu {0} +90060=Méthode de verrouillage de fichier non prise en charge {0} +90061=Exception à l''ouverture du port {0} (le port est peut-être en cours d''utilisation), cause: {1} +90062=Erreur lors de la création du fichier {0} +90063=Le point de sauvegarde est invalide: {0} +90064=Le point de sauvegarde est sans nom +90065=Le point de sauvegarde est nommé +90066=Propriété dupliquée {0} +90067=La connexion est cassée: {0} +90068=L''expression Order by {0} doit être dans ce cas dans la liste des résultats +90069=Le rôle {0} existe déjà +90070=Rôle {0} non trouvé +90071=Utilisateur ou rôle {0} non trouvé +90072=Les rôles et les droits ne peuvent être mélangés +90073=Les méthodes Java correspondantes doivent avoir un nombre de paramètres différents: {0} et {1} +90074=Le rôle {0} est déjà accordé +90075=La colonne fait partie de l''index {0} +90076=L''alias de fonction {0} existe déjà +90077=Alias de fonction {0} non trouvé +90078=Le schéma {0} existe déjà +90079=Schéma {0} non trouvé +90080=Le nom de schéma doit correspondre +90081=La colonne {0} contient des valeurs nulles +90082=La séquence {0} appartient une table +90083=La colonne doit être référencée par {0} +90084=Impossible de supprimer la dernière colonne {0} +90085=L''index {0} appartient à la contrainte {1} +90086=Classe {0} non trouvée +90087=Méthode {0} non trouvée +90088=Mode inconnu {0} +90089=La collation ne peut pas être changée parce qu''il y a des données dans la table: {0} +90090=Le schéma {0} ne peut pas être supprimé +90091=Le rôle {0} ne peut pas être supprimé +90093=Erreur de clustering - la base de données s''exécute actuellement en mode autonome +90094=Erreur de clustering - la base de données s''exécute actuellement en mode cluster, liste de serveurs: {0} +90095=Erreur de format de chaîne: {0} +90096=Pas assez de droit pour l''objet {0} +90097=La base de données est en lecture seule +90098=La base de données a été fermée +90099=Erreur lors du paramétrage de l''auditeur d''événements de la base de données {0}, cause: {1} +90101=Mauvais format XID: {0} +90102=Options de compression non supportées: {0} +90103=Algorithme de conpression non supporté: {0} +90104=Erreur de compression +90105=Exception lors de l''appel de la fonction définie par l''utilisateur: {0} +90106=Impossible de tronquer {0} +90107=Impossible de supprimer {0} car {1} dépend de lui +90108=Mémoire insuffisante. +90109=La vue {0} est invalide: {1} +90110=#Values of types {0} and {1} are not comparable +90111=Erreur lors de l''accès à la table liée à l''aide de l''instruction SQL {0}, cause: {1} +90112=Ligne non trouvée lors de la tentative de suppression à partir de l''index {0} +90113=Paramétrage de connexion non pris en charge {0} +90114=La constante {0} existe déjà +90115=Constante {0} non trouvée +90116=Les littérals de ce type ne sont pas permis +90117=Les connexions à distance à ce serveur ne sont pas autorisées, voir -tcpAllowOthers +90118=Impossible de supprimer la table {0} +90119=Le domaine {0} existe déjà +90120=Le domaine {0} non trouvé +90121=La base de données est déjà fermée (pour désactiver la fermeture automatique à l''arrêt de la VM, ajoutez "; DB_CLOSE_ON_EXIT = FALSE" à l''URL db) +90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause. +90123=Impossible de mélanger des paramètres indexés et non-indexés +90124=Fichier non trouvé: {0} +90125=Classe invalide, attendue {0} mais obtenue {1} +90126=La base de données n''est pas persistante +90127=L''ensemble des résultats ne peut pas être mis à jour. La requête doit sélectionner toutes les colonnes à partir d''une clé unique. Seule une table peut être sélectionnée. +90128=L''ensemble des résultats n''est pas scollable et ne peut pas être réinitialisé. Vous pouvez avoir besoin d''utiliser conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ..). +90129=Transaction {0} non trouvée +90130=Cette méthode n''est pas autorisée pour une instruction paramétrée; à la place utilisez une instruction régulière. +90131=Mise à jour concurrente dans la table {0}: une autre transaction à mis à jour ou supprimé la même ligne +90132=Aggregat {0} non trouvé +90133=Impossible de changer le paramétrage {0} lorsque la base de données est déjà ouverte +90134=L''accès à la classe {0} est interdit +90135=La base de données est ouverte en mode exclusif; impossible d''ouvrir des connexions additionnelles +90136=#Window not found: {0} +90137=Peut seulement être assigné à une variable, pas à: {0} +90138=Nom de la base de données invalide: {0} +90139=La méthode Java public static n''a pas été trouvée: {0} +90140=''ensemble des résultats est en lecture seule. Vous pouvez avoir besoin d''utiliser conn.createStatement(.., ResultSet.CONCUR_UPDATABLE). +90141=Le sérialiseur ne peut être changé parce que il y a des données dans la table: {0} +90142=La taille de l''étape ne doit pas être de 0 +90143=#Row {1} not found in primary index {0} +90144=#Authenticator not enabled on database {0} +90145=#FOR UPDATE is not allowed in DISTINCT or grouped select +90146=#Database {0} not found, and IFEXISTS=true, so we can't auto-create it +90147=#Method {0} is not allowed when connection is in auto-commit mode +90148=#Current value of sequence {0} is not yet defined in this session +90149=#Database {0} not found, either pre-create it or allow remote database creation (not recommended in secure environments) +90150=#Precision ({0}) must be between {1} and {2} inclusive +90151=#Scale or fractional seconds precision ({0}) must be between {1} and {2} inclusive +90152=#Constraint {0} is used by constraint {1} +90153=#Column {0} references uncomparable column {1} +90154=#Generated column {0} cannot be assigned +90155=#Generated column {0} cannot be updatable by a referential constraint with {1} clause +90156=#Column alias is not specified for expression {0} +90157=#Column index {0} in GROUP BY clause is outside valid range 1 - {1} +HY000=Erreur générale: {0} +HY004=Type de données inconnu: {0} +HYC00=Fonctionnalité non supportée: {0} +HYT00=Dépassement du temps lors du vérrouillage de la table {0} diff --git a/h2/src/main/org/h2/res/_messages_ja.prop b/h2/src/main/org/h2/res/_messages_ja.prop new file mode 100644 index 0000000..9eab01d --- /dev/null +++ b/h2/src/main/org/h2/res/_messages_ja.prop @@ -0,0 +1,201 @@ +.translator=IKEMOTO, Masahiro +02000=有効なデータがありません +07001={0} は無効なパラメータ番号です, 期待される番号: {1} +08000=データベースオープンエラー: {0} +21S02=列番号が一致しません +22001=列 {0} の値が長過ぎます: {1} +22003=範囲外の数値です: {0} +22004=#Numeric value out of range: {0} in column {1} +22007={0} 定数 {1} を解析できません +22012=ゼロで除算しました: {0} +22013=#Invalid PRECEDING or FOLLOWING size in window function: {0} +22018=データ変換中にエラーが発生しました {0} +22025=LIKE ESCAPE にエラーがあります: {0} +2202E=#Array element error: {0}, expected {1} +22030=#Value not permitted for column {0}: {1} +22031=#Value not a member of enumerators {0}: {1} +22032=#Empty enums are not allowed +22033=#Duplicate enumerators are not allowed for enum types: {0} +23502=列 {0} にはnull値が許されていません +23503=参照整合性制約違反: {0} +23505=ユニークインデックス、またはプライマリキー違反: {0} +23506=参照整合性制約違反: {0} +23507=列 {0} にデフォルト値が設定されていません +23513=制約違反を確認してください: {0} +23514=制約が無効です。確認してください: {0} +28000=ユーザ名またはパスワードが不正です +40001=デッドロックが検出されました。現在のトランザクションはロールバックされました。詳細: {0} +42000=SQLステートメントに文法エラーがあります {0} +42001=SQLステートメントに文法エラーがあります {0}; 期待されるステートメント {1} +42602=#Invalid name {0} +42622=#The name that starts with {0} is too long. The maximum length is {1} +42S01=テーブル {0} はすでに存在します +42S02=テーブル {0} が見つかりません +42S03=#Table {0} not found (candidates are: {1}) +42S04=#Table {0} not found (this database is empty) +42S11=インデックス {0} はすでに存在します +42S12=インデックス {0} が見つかりません +42S21=列名 {0} が重複しています +42S22=列 {0} が見つかりません +42S31=#Identical expressions should be used; expected {0}, found {1} +54011=#Too many columns. The maximum count is {0} +57014=ステートメントがキャンセルされたか、セッションがタイムアウトしました +90000=関数 {0} はリザルトセットを返さなければなりません +90001=メソッドはクエリをサポートしていません。executeUpdateのかわりに、excute、またはexecuteQueryを使用してください +90002=メソッドはクエリしかサポートしていません。executeQueryのかわりに、excecute、またはexecuteUpdateを使用してください +90003=文字数が奇数の16進文字列です: {0} +90004=16進文字列に不正な文字が含まれています: {0} +90005=#Invalid trigger flags: {0} +90006=シーケンス {0} を使い果たしました +90007=オブジェクトはすでに閉じられています +90008=パラメータ {1} に対する値 {0} が不正です +90009=#無効な属性により、シーケンス {0} の作成または変更ができません。(base value {1}, 開始値 {2}, 最小値 {3}, 最大値 {4}, 増分 {5}, cache size {6}) +90010=無効な TO_CHAR フォーマット {0} +90011=暗黙的なカレントディレクトリからの相対ファイルパスをデータベースURL({0})に指定することは許可されていません。代わりに絶対パスか相対パス( ~/name, ./name)あるいは baseDir を指定して下さい. +90012=パラメータ {0} がセットされていません +90013=データベース {0} が見つかりません +90014=解析エラー {0} +90015=SUM、AVGを不正なデータ型 {0} に使用しました +90016=列 {0} はリストによりグループ化されなければなりません +90017=複数のプライマリキーを定義しようとしました +90018=アプリケーションにより閉じられていない接続がガベージコレクトされました +90019=使用中のユーザをドロップすることはできません +90020=データベースが使用中です: {0}. 可能な解決策: 他の接続を全て閉じる; サーバモードを使う +90021=この組み合わせのデータベース設定はサポートされていません: {0} +90022=関数 {0} が見つかりません +90023=列 {0} にはnull値を許すべきてはありません +90024=ファイル名を {0} から {1} に変更中にエラーが発生しました +90025=ファイル {0} を削除できません +90026=直列化に失敗しました: {0} +90027=直列化復元に失敗しました: {0} +90028=入出力例外: {0} +90029=現在行は更新不可です +90030=レコード {0} を読み込み中にファイルの破損を検出しました。可能な解決策: リカバリツールを使用してください +90031=入出力例外: {0}; {1} +90032=ユーザ {0} が見つかりません +90033=ユーザ {0} はすでに存在します +90034=ログファイルエラー: {0} +90035=シーケンス {0} はすでに存在します +90036=シーケンス {0} が見つかりません +90037=ビュー {0} が見つかりません +90038=ビュー {0} はすでに存在します +90039=この CLOB または BLOB の参照がタイムアウトしました: {0} +90040=この操作には管理権限が必要です +90041=トリガ {0} はすでに存在します +90042=トリガ {0} が見つかりません +90043=トリガ {0} オブジェクト, クラス {1} を生成中にエラーが発生しました +90044=トリガ {0} オブジェクト, クラス {1} を実行中にエラーが発生しました +90045=制約 {0} はすでに存在します +90046=URLフォーマットエラー; {1} ではなく {0} でなければなりません +90047=バージョンが一致しません。ドライババージョンは {0} ですが、サーババージョンは {1} です +90048=ファイル {0} は、未サポートのバージョンか、不正なファイルヘッダを持つデータベースファイルです +90049=ファイル {0} の暗号化エラーです +90050=不正なパスワードフォーマットです。正しくは: ファイルパスワード <空白> ユーザパスワード +90052=サブクエリが単一列のクエリではありません +90053=数値サブクエリが複数の行を含んでいます +90054=集約関数 {0} の不正な使用 +90055={0} は未サポートの暗号です +90056=関数 {0}: 無効な日付フォーマット: {1} +90057=制約 {0} が見つかりません +90058=トリガ内でのコミット、ロールバックは許されていません +90059=列名 {0} があいまいです +90060={0} は未サポートのファイルロック方式です +90061=ポート {0} をオープン中に例外が発生しました (ポートが使用中の可能性があります) +90062=ファイル {0} を作成中にエラーが発生しました +90063=セーブポイントが不正です: {0} +90064=セーブポイントの名前を削除しました +90065=セーブポイントに名前を設定しました +90066=プロパティ {0} が重複しています +90067=接続が壊れています: {0} +90068=order by 対象の式 {0} は、結果リストに含まれる必要があります +90069=ロール {0} はすでに存在します +90070=ロール {0} が見つかりません +90071=ユーザ、またはロール {0} が見つかりません +90072=ロールと権限は混在できません +90073=適合するJavaメソッドは異なるパラメータ数である必要があります: {0}, {1} +90074=ロール {0} はすでに許可されています +90075=列はインデックス {0} の一部です +90076=関数の別名 {0} はすでに存在します +90077=関数の別名 {0} が見つかりません +90078=スキーマ {0} はすでに存在します +90079=スキーマ {0} が見つかりません +90080=スキーマ名が一致しません +90081=列 {0} がnull値を含んでいます +90082=シーケンス {0} はテーブルに属します +90083=列は {0} に参照されている可能性があります +90084=最後の列 {0} をドロップすることはできません +90085=インデックス {0} は制約に属しています {1} +90086=クラス {0} が見つかりません +90087=メソッド {0} が見つかりません +90088={0} は不明なモードです +90089=データテーブル {0} があるため、照合順序の変更はできません +90090=スキーマ {0} はドロップできません +90091=ロール {0} はドロップできません +90093=クラスタリングエラー - データベースはスタンドアロンモードで動作しています +90094=クラスタリングエラー - データベースはクラスターモードで動作しています, サーバリスト: {0} +90095=文字列フォーマットエラー: {0} +90096=オブジェクト {0} に対する十分な権限がありません +90097=データベースは読み込み専用です +90098=データベースはすでに閉じられています +90099=データベースイベントリスナの設定エラー {0} +90101=不正なXIDフォーマット: {0} +90102=未サポートの圧縮オプション: {0} +90103=未サポートの圧縮アルゴリズム: {0} +90104=圧縮エラー +90105=ユーザ定義関数を実行中に例外が発生しました: {0} +90106={0} を空にできません +90107={1} が依存しているため、{0} をドロップすることはできません +90108=メモリが不足しています +90109=ビュー {0} は無効です: {1} +90110=#Values of types {0} and {1} are not comparable +90111=SQLステートメント {0} による結合テーブルアクセスエラー +90112=インデックス {0} から削除を試みましたが、行が見つかりません +90113=未サポートの接続設定 {0} +90114=定数 {0} はすでに存在します +90115=定数 {0} が見つかりません +90116=この種類のリテラルは許されていません +90117=このサーバへのリモート接続は許されていません, -tcpAllowOthersを参照 +90118=テーブル {0} はドロップできません +90119=ドメイン {0} はすでに存在します +90120=ドメイン {0} が見つかりません +90121=データベースはすでに閉じられています (VM終了時の自動データベースクローズを無効にするためには、db URLに ";DB_CLOSE_ON_EXIT=FALSE" を追加してください) +90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause. +90123=インデックスの付いたパラメータと付いていないパラメータを混在させることはできません +90124=ファイルが見つかりません: {0} +90125=無効なクラス, {0} が期待されているにもかかわらず {1} を取得しました +90126=データベースは永続的ではありません +90127=リザルトセットが更新可能ではありません。クエリは単一のテーブルから、ユニークキーを全てselectしなければなりません +90128=リザルトセットがスクロール、リセット可能ではありません。conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ..) を使う必要があるかもしれません +90129=トランザクション {0} が見つかりません +90130=プリペアドステートメントにこのメソッドは許されていません; かわりに通常のステートメントを使用してください +90131=テーブル {0} に並行して更新が行われました: 別のトランザクションが、同じ行に更新か削除を行いました +90132=集約 {0} が見つかりません +90133=データベースオープン中には、設定 {0} を変更できません +90134=クラス {0} へのアクセスが拒否されました +90135=データベースは排他モードでオープンされています; 接続を追加することはできません +90136=#Window not found: {0} +90137=割り当ては変数にのみ可能です。{0} にはできません +90138=不正なデータベース名: {0} +90139=public staticであるJavaメソッドが見つかりません: {0} +90140=リザルトセットは読み込み専用です。conn.createStatement(.., ResultSet.CONCUR_UPDATABLE) を使う必要があるかもしれません +90141=データテーブル {0} があるため、シリアライザを変更することはできません +90142=ステップサイズに0は指定できません +90143=#Row {1} not found in primary index {0} +90144=#Authenticator not enabled on database {0} +90145=#FOR UPDATE is not allowed in DISTINCT or grouped select +90146=#Database {0} not found, and IFEXISTS=true, so we can't auto-create it +90147=#Method {0} is not allowed when connection is in auto-commit mode +90148=#Current value of sequence {0} is not yet defined in this session +90149=#Database {0} not found, either pre-create it or allow remote database creation (not recommended in secure environments) +90150=#Precision ({0}) must be between {1} and {2} inclusive +90151=#Scale or fractional seconds precision ({0}) must be between {1} and {2} inclusive +90152=#Constraint {0} is used by constraint {1} +90153=#Column {0} references uncomparable column {1} +90154=#Generated column {0} cannot be assigned +90155=#Generated column {0} cannot be updatable by a referential constraint with {1} clause +90156=#Column alias is not specified for expression {0} +90157=#Column index {0} in GROUP BY clause is outside valid range 1 - {1} +HY000=一般エラー: {0} +HY004=不明なデータ型: {0} +HYC00=機能はサポートされていません: {0} +HYT00=テーブル {0} のロック試行がタイムアウトしました diff --git a/h2/src/main/org/h2/res/_messages_pl.prop b/h2/src/main/org/h2/res/_messages_pl.prop new file mode 100644 index 0000000..44d4eeb --- /dev/null +++ b/h2/src/main/org/h2/res/_messages_pl.prop @@ -0,0 +1,201 @@ +.translator=Tomek; Wojciech S. Jurczyk +02000=Dane nie są dostępne +07001=Niewłaściwa liczba parametrów dla {0}, oczekiwano ilości: {1} +08000=Błąd otwarcia bazy danych: {0} +21S02=Niezgodna ilość kolumn +22001=Wartość za długa dla kolumny {0}: {1} +22003=Wartość numeryczna poza zakresem: {0} +22004=#Numeric value out of range: {0} in column {1} +22007=Nie można odczytać {0} jako {1} +22012=Dzielenie przez zero: {0} +22013=#Invalid PRECEDING or FOLLOWING size in window function: {0} +22018=Błąd konwersji danych {0} +22025=Błąd w LIKE ESCAPE: {0} +2202E=#Array element error: {0}, expected {1} +22030=#Value not permitted for column {0}: {1} +22031=#Value not a member of enumerators {0}: {1} +22032=#Empty enums are not allowed +22033=#Duplicate enumerators are not allowed for enum types: {0} +23502=Pole nie może być NULL{0} +23503=Naruszenie więzów integralności: {0} +23505=Naruszenie ograniczenia Klucza Głównego lub Indeksu Unikalnego: {0} +23506=Naruszenie więzów integralności: {0} +23507=Brak domyślnej wartości dla kolumny {0} +23513=Naruszenie ograniczenia CHECK {0} +23514=Nieprawidłowe ograniczenie CHECK: {0} +28000=Nieprawidłowy użytkownik/hasło +40001=Wykryto zakleszczenie. Bieżąca transakcja została wycofana. Szczegóły : {0} +42000=Błąd składniowy w wyrażeniu SQL {0} +42001=Błąd składniowy w wyrażeniu SQL {0}; oczekiwano {1} +42602=#Invalid name {0} +42622=#The name that starts with {0} is too long. The maximum length is {1} +42S01=Tabela {0} już istnieje +42S02=Tabela {0} nie istnieje +42S03=#Table {0} not found (candidates are: {1}) +42S04=#Table {0} not found (this database is empty) +42S11=Indeks {0} już istnieje +42S12=Indeks {0} nie istnieje +42S21=Zduplikowana nazwa kolumny {0} +42S22=Kolumna {0} nie istnieje +42S31=#Identical expressions should be used; expected {0}, found {1} +54011=#Too many columns. The maximum count is {0} +57014=Kwerenda została anulowana albo sesja wygasła +90000=Funkcja {0} musi zwrócić dane +90001=Metoda nie jest dozwolona w kwerendzie +90002=Metoda jest dozwolona tylko w kwerendzie +90003=Heksadecymalny string z nieparzystą liczbą znaków: {0} +90004=Heksadecymalny string zawiera niedozwolony znak: {0} +90005=#Invalid trigger flags: {0} +90006=Sekwencja {0} została wyczerpana +90007=Obiekt jest zamknięty +90008=Nieprawidłowa wartość {0} parametru {1} +90009=#Nie można utworzyć/zmienić sekwencji {0} ponieważ podane atrybuty są nieprawidłowe (base value {1}, wartość początkowa {2}, wartość minimalna {3}, wartość maksymalna {4}, przyrost {5}, cache size {6}) +90010=Nieprawidłowy format TO_CHAR {0} +90011=#A file path that is implicitly relative to the current working directory is not allowed in the database URL {0}. Use an absolute path, ~/name, ./name, or the baseDir setting instead. +90012=Parametr o numerze {0} nie jest ustalony +90013=Baza danych {0} nie znaleziona +90014=Błąd parsowania {0} +90015=Agregacja SUM lub AVG wywołana na złym typie danych {0} +90016=Kolumna {0} musi być na liście grupowania +90017=Próba zdefiniowania drugiego klucza głównego +90018=Połączenie zostało zamknięte przez aplikacje i zostało usunięte przez kolektor nieużytków +90019=Nie można skasować aktualnego użytkownika +90020=Baza danych może być już otwarta: {0} +90021=#This combination of database settings is not supported: {0} +90022=Funkcja {0} nie istnieje +90023=Kolumna {0} nie może zawierać wartości pustej +90024=Błąd w zmianie nazwy pliku {0} na {1} +90025=Nie można skasować pliku {0} +90026=Serializacja nie powiodła się: {0} +90027=Deserializacja nie powiodła się: {0} +90028=Błąd wejścia/wyjścia: {0} +90029=Aktualny rekord nie pozwala na aktualizacje +90030=Uszkodzenie pliku podczas wczytywania rekordu: {0} +90031=Połączenie nie zostało zamknięte +90032=Użytkownik {0} nie istnieje +90033=Użytkownik {0} już istnieje +90034=Błąd pliku dziennika: {0}, błąd: {1} +90035=Sekwencja {0} już istnieje +90036=Sekwencja {0} nie istnieje +90037=Widok {0} nie istnieje +90038=Widok {0} już istnieje +90039=#This CLOB or BLOB reference timed out: {0} +90040=Uprawnienia administratora są wymagane do wykonania tej operacji +90041=Wyzwalacz {0} już istnieje +90042=Wyzwalacz {0} nie istnieje +90043=Błąd tworzenia/inicjowania wyzwalacza {0} klasy {1}, błąd: {2}; szczegóły w źródle błędu +90044=Błąd uruchomienia wyzwalacza {0}, klasy {1}, błąd: {2}; szczegóły w źródle błędu +90045=Ograniczenie {0} już istnieje +90046=Błędny format URL; powinno być {0} a jest {1} +90047=Niezgodna wersja sterownika, aktualna wersja to {0} a wersja serwera to {1} +90048=Nieprawidłowa wersja pliku bazy danych lub nieprawidłowy nagłówek pliku {0} +90049=Błąd szyfrowania pliku {0} +90050=Zły format hasła, powinno być: plik hasło użytkownik hasło +90052=Podzapytanie nie jest zapytaniem opartym o jedna kolumnę +90053=Skalarna pod-kwerenda zawiera więcej niż jeden wiersz +90054=Nieprawidłowe użycie funkcji agregującej {0} +90055=Nieobsługiwany szyfr {0} +90056=Function {0}: Invalid date format: {1} +90057=Ograniczenie {0} nie istnieje +90058=Zatwierdzenie lub wycofanie transakcji nie jest dozwolone w wyzwalaczu +90059=Niejednoznaczna nazwa kolumny {0} +90060=Niewspierana metoda blokowania pliku {0} +90061=Błąd podczas otwierania portu {0} (może być w użyciu), błąd: {1} +90062=Błąd tworzenia pliku {0} +90063=Zakładką jest nieprawidłowa: {0} +90064=Zakładka jest bez nazwy +90065=Zakładka jest nazwana +90066=Zduplikowana właściwość {0} +90067=Połączenie uszkodzone: {0} +90068=Wyrażenie sortowania {0} musi być na liście wyboru w tym przypadku +90069=Rola {0} już istnieje +90070=Rola {0} nie istnieje +90071=Użytkownik lub rola {0} nie istnieje +90072=Role i prawa nie mogą być mieszane +90073=Metody Javy muszą mieć różną liczbę parametrów: {0} oraz {1} +90074=Rola {0} już przyznana +90075=Kolumna jest częścią indeksu {0} +90076=Alias funkcji {0} już istnieje +90077=Alias funkcji {0} nie istnieje +90078=Schemat{0} już istnieje +90079=Schemat {0} nie istnieje +90080=Nazwa schematu musi pasować +90081=Kolumna {0} zawiera wartości puste +90082=Sekwencja {0} należy do tabeli +90083=Do kolumny można odwołać się przez referencje {0} +90084=Nie można skasować ostatniej kolumny {0} +90085=Indeks {0} należy do ograniczenia {1} +90086=Klasa {0} nie istnieje +90087=#Method {0} not found +90088=Nieznany stan {0} +90089=Metoda porównywania językowego nie może być zmieniona z powodu istnienia danych w tabeli {0} +90090=Schemat {0} nie może zostać skasowany +90091=Rola {0} nie może zostać skasowana +90093=Błąd klastrowania - baza obecnie pracuje w trybie autonomicznym +90094=Błąd klastrowania - baza obecnie pracuje w trybie klastra, lista serwerów: {0} +90095=Błąd ciągu formatowania: {0} +90096=Brak wystarczających praw do obiektu {0} +90097=Baza danych jest w trybie tylko do odczytu +90098=Baza danych została zamknięta +90099=Błąd ustawiania detektora zdarzeń bazy danych {0}, błąd: {1} +90101=Zły format XID: {0} +90102=Nie wspierana opcja kompresji: {0} +90103=Nie wspierany algorytm kompresji: {0} +90104=Błąd kompresji +90105=Wyjątek wywołuje funkcję użytkownika: {0} +90106=Nie można obciąć {0} +90107=Nie można skasować {0} ponieważ zależy od {1} +90108=Brak pamięci. +90109=Widok {0} jest nieprawidłowy +90110=#Values of types {0} and {1} are not comparable +90111=Błąd dostępu do tabeli skrzyżowań przy pomocy zapytania SQL {0}, błąd: {1} +90112=Rekord nie znaleziony przy probie kasowania z indeksu {0} +90113=Nie wspierana opcja połączenia {0} +90114=Stała {0} już istnieje +90115=Stała {0} nie istnieje +90116=Literał tego typu nie jest dozwolony +90117=Zdalne połączenia do tego serwera nie są dozwolone, zobacz -tcpAllowOthers +90118=Nie można skasować tabeli {0} +90119=Domena {0} już istnieje +90120=Domena {0} nie istnieje +90121=Baza danych jest już zamknięta (aby zablokować samoczynne zamykanie podczas zamknięcia VM dodaj ";DB_CLOSE_ON_EXIT=FALSE" do URL bazy danych) +90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause. +90123=Nie można mieszać parametrów indeksowych z nieindeksowymi +90124=Plik nie istnieje: {0} +90125=Nieprawidłowa klasa, oczekiwano {0}, a jest {1} +90126=Baza danych nie jest trwała +90127=Wynik nie jest uaktualnialny. Kwerenda musi wybrać wszystkie kolumny z klucza unikalnego. Tylko jedna tabela może zostać wybrana. +90128=Wynik nie jest typu SCROLLABLE i nie może być zresetowany. Być może powinieneś użyć conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ..). +90129=Transakcja {0} nie znaleziona +90130=Ta metoda jest niedozwolona dla sparametryzowanych kwerend (prepared statement); użyj zwykłej kwerendy +90131=Jednoczesna zmiana w tabeli {0}: inna transakcja zaktualizowała lub usunęła ten sam wiersz +90132=Agregacja {0} nie znaleziona +90133=Nie można zmienić ustawienia {0} gdy baza danych jest otwarta +90134=Dostęp do klasy {0} jest zabroniony +90135=Baza danych jest otwarta w trybie wyłączności, nie można otworzyć dodatkowych połączeń +90136=#Window not found: {0} +90137=Można przypisywać tylko do zmiennych, nie do: {0} +90138=Nieprawidłowa nazwa bazy danych: {0} +90139=Publiczna, statyczna metoda Java nie znaleziona: {0} +90140=Wyniki są tylko do odczytu. Być może potrzebujesz użyć conn.createStatement(.., ResultSet.CONCUR_UPDATABLE). +90141=Serializator nie może być zmieniony ponieważ istnieje tabela z danymi: {0} +90142=#Step size must not be zero +90143=#Row {1} not found in primary index {0} +90144=#Authenticator not enabled on database {0} +90145=#FOR UPDATE is not allowed in DISTINCT or grouped select +90146=#Database {0} not found, and IFEXISTS=true, so we can't auto-create it +90147=#Method {0} is not allowed when connection is in auto-commit mode +90148=#Current value of sequence {0} is not yet defined in this session +90149=#Database {0} not found, either pre-create it or allow remote database creation (not recommended in secure environments) +90150=#Precision ({0}) must be between {1} and {2} inclusive +90151=#Scale or fractional seconds precision ({0}) must be between {1} and {2} inclusive +90152=#Constraint {0} is used by constraint {1} +90153=#Column {0} references uncomparable column {1} +90154=#Generated column {0} cannot be assigned +90155=#Generated column {0} cannot be updatable by a referential constraint with {1} clause +90156=#Column alias is not specified for expression {0} +90157=#Column index {0} in GROUP BY clause is outside valid range 1 - {1} +HY000=Błąd ogólny: {0} +HY004=Nieznany typ danych: {0} +HYC00=Cecha nie jest wspierana: {0} +HYT00=Czas oczekiwania na blokadę tabeli {0} skończył się diff --git a/h2/src/main/org/h2/res/_messages_pt_br.prop b/h2/src/main/org/h2/res/_messages_pt_br.prop new file mode 100644 index 0000000..e9383f5 --- /dev/null +++ b/h2/src/main/org/h2/res/_messages_pt_br.prop @@ -0,0 +1,201 @@ +.translator=Eduardo Fonseca Velasques +02000=Não há dados disponíveis +07001=Quantidade de parâmetros errados para {0}, experado: {1} +08000=Erro ao abrir a base de dados: {0} +21S02=A quantidade de colunas não corresponde +22001=Valor muito longo para a coluna {0}: {1} +22003=Valor númerico não esta dentro do limite: {0} +22004=#Numeric value out of range: {0} in column {1} +22007=Não é possível converter {1} para {0} +22012=Divisão por zero: {0} +22013=#Invalid PRECEDING or FOLLOWING size in window function: {0} +22018=Erro na conversão de dado, convertendo {0} +22025=Erro em LIKE ESCAPE: {0} +2202E=#Array element error: {0}, expected {1} +22030=#Value not permitted for column {0}: {1} +22031=#Value not a member of enumerators {0}: {1} +22032=#Empty enums are not allowed +22033=#Duplicate enumerators are not allowed for enum types: {0} +23502=NULL não é permitido para a coluna {0} +23503=Violação da integridade de restrição: {0} +23505=Violação de índice único ou de chave primária: {0} +23506=Violação da integridade de restrição: {0} +23507=Nenhum valor pré-definido foi especificado para a coluna {0} +23513=Violação da restrição: {0} +23514=#Check constraint invalid: {0} +28000=Autenticaçao inválida, verifique o usuário ou a senha +40001=#Deadlock detected. The current transaction was rolled back. Details: {0} +42000=Erro de sintax na declaração SQL {0} +42001=Erro de sintax na declaração SQL {0}; esperado {1} +42602=#Invalid name {0} +42622=#The name that starts with {0} is too long. The maximum length is {1} +42S01=Tabela {0} já existe +42S02=Tabela {0} não foi encontrada +42S03=#Table {0} not found (candidates are: {1}) +42S04=#Table {0} not found (this database is empty) +42S11=índice {0} já existe +42S12=índice {0} não foi encontrado +42S21=Nome duplicado da coluna {0} +42S22=Coluna {0} não foi encontrada +42S31=#Identical expressions should be used; expected {0}, found {1} +54011=#Too many columns. The maximum count is {0} +57014=#Statement was canceled or the session timed out +90000=Função {0} deve retornar algum resultado +90001=O método não esta hábilitado para consulta. Use o execute ou o executeQuery em vez de executeUpdate +90002=O método é apenas para consulta. Use o execute ou o executeUpdate em vez de executeQuery +90003=Sequência Hexadecimal com número ímpar de caracteres: {0} +90004=Sequência Hexadecimal contêm caracteres inválidos: {0} +90005=#Invalid trigger flags: {0} +90006=#Sequence {0} has run out of numbers +90007=O objeto está fechado +90008=Valor inválido {0} para o parâmetro {1} +90009=#Unable to create or alter sequence {0} because of invalid attributes (base value {1}, start value {2}, min value {3}, max value {4}, increment {5}, cache size {6}) +90010=#Invalid TO_CHAR format {0} +90011=#A file path that is implicitly relative to the current working directory is not allowed in the database URL {0}. Use an absolute path, ~/name, ./name, or the baseDir setting instead. +90012=Parâmetro {0} não esta definido +90013=Base de dados {0} não encontrada +90014=Erro na conversão {0} +90015=SUM ou AVG com tipo de dados errado para {0} +90016=Coluna {0} também deve estar no GROUP BY +90017=Tentativa para definir uma segunda chave primária +90018=A conecção foi fechada pela aplicação e retirada da memória +90019=Não pode remover o usuário corrente +90020=A base de dados talvez esteja em uso: {0}. Solução possível: fechar todas as outras conecções; use o modo servidor +90021=#This combination of database settings is not supported: {0} +90022=Função {0} não encontrada +90023=Coluna {0} não deve permitir valor nulo +90024=Erro ao renomear arquivo {0} para {1} +90025=Não pode apagar o arquivo {0} +90026=Serialização falhada, causa: {0} +90027=Deserialização falhada, causa: {0} +90028=Exceção de IO: {0} +90029=No momento não é possível fazer alterações nas linhas +90030=Arquivo corrompido durante a leitura: {0}. Possível solução: use o recovery tool +90031=Exceção de IO: {0}; {1} +90032=Usuário {0} não foi encontrado +90033=Usuário {0} já existe +90034=Erro no arquivo de Log: {0}, causa: {1} +90035=Sequência {0} já existe +90036=Sequência {0} não foi encontrada +90037=Vista {0} não foi encontrada +90038=Vista {0} já existe +90039=#This CLOB or BLOB reference timed out: {0} +90040=Direitos de permisões do Admin são necessários para está operação +90041=Trigger {0} já existe +90042=Trigger {0} não foi encontrada +90043=Erro na criação ou inicialização da trigger no objeto {0}, classe {1}, causa: {2}; para maior detalhe veja a raiz da causa +90044=Erro executando trigger {0}, classe {1}, causa : {2}; para maior detalhe veja a raiz da causa +90045=Restrição {0} já existe +90046=Erro no formato da URL; deve ser {0} mas está {1} +90047=Versões incompatíveis, versão do driver é {0} mas a versão do servidor é {1} +90048=Versão do arquivo de base de dados não é suportado, ou o cabeçalho do arquivo é inválido, no arquivo {0} +90049=Erro de encriptação no arquivo {0} +90050=Erro no formato da senha, deveria ser: arquivo de senha senha do usuário +90052=A Subquery não é de coluna única +90053=A Subquery contém mais de uma linha +90054=Uso inválido da função {0} agregada +90055=Cipher {0} não é suportado +90056=Function {0}: Invalid date format: {1} +90057=Restrição {0} não foi encontrada +90058=#Commit or rollback is not allowed within a trigger +90059=Nome da coluna {0} é ambíguo. +90060=Não suporta o método do arquivo de bloqueio {0} +90061=Exceção ao abrir no porto {0} (provavelmente está em uso), causa: {1} +90062=Erro na criação do arquivo {0} +90063=Savepoint é inválido: {0} +90064=Savepoint não está nomeado +90065=Savepoint está nomeado +90066=Propriedade {0} duplicada +90067=A conecção está quebrada: {0} +90068=Expressão order by {0} deve estar na lista neste caso +90069=Regra {0} já existe +90070=Regra {0} não foi encontrada +90071=Usuário ou regra {0} não foram encontrados +90072=Regras e permissões não podem ser combinados +90073=#Matching Java methods must have different parameter counts: {0} and {1} +90074=Regra {0} já foi concedida +90075=A coluna faz parte do índice {0} +90076=Nome alternativo da função {0} já existe +90077=Nome alternativo da função {0} não foi encontrado +90078=Esquema {0} já existe +90079=Esquema {0} não foi encontrado +90080=Nome do esquema deve ser válido +90081=Coluna {0} restringe valores nulos +90082=Sequência {0} pertence a uma tabela +90083=A coluna pode ser referênciada por {0} +90084=Não pode apagar a última coluna {0} +90085=índice {0} pertence a uma restrição {1} +90086=Classe {0} não foi encontrada +90087=#Method {0} not found +90088=Modo {0} desconhecido +90089=A coleção não pode ser alterada, porque existe uma tabela de dados: {0} +90090=Esquema {0} não pode ser apagado +90091=Regra {0} não pode ser apagada +90093=Erro de clusterização - base de dados rodando no modo standalone no momento +90094=Erro de clusterização - base de dados rodando no modo cluster, lista do servidor: {0} +90095=Erro no formato da string: {0} +90096=Permissões insuficientes para o objeto {0} +90097=Base de dados é somente leitura +90098=Base de dados foi fechada +90099=Erro na definição do evento listener {0} da base de dados, causa: {1} +90101=Formato XID incorreto: {0} +90102=Não é suportado as opções de compressão: {0} +90103=Não é suportado o algorítimo de compressão: {0} +90104=Erro na compressão +90105=Exceção na chamada da função definida pelo usuário: {0} +90106=Não pode fazer o truncate {0} +90107=Não pode apagar {0} por que depende de {1} +90108=#Out of memory. +90109=Vista {0} é inválida: {1} +90110=#Values of types {0} and {1} are not comparable +90111=Erro ao acessar a tabela lincada com a instrução SQL {0}, causa: {1} +90112=A linha não foi encontrada ao tentar eliminar apartir do índice {0} +90113=Não suporta a definição de conecção {0} +90114=Constante {0} já existe +90115=Constante {0} não foi encontrada +90116=Literais deste tipo não são permitidas +90117=Conecções remotas para este servidor não estão habilitadas, veja -tcpAllowOthers +90118=Não pode apagar a tabela {0} +90119=Domínio {0} já existe +90120=Domínio {0} não foram encontrados +90121=Base de dados já está fechada (para desabilitar o fechamento automático quando a VM terminar, addicione ";DB_CLOSE_ON_EXIT=FALSE" na url da base de dados) +90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause. +90123=Não pode combinar parâmetros de índices com não índices +90124=Arquivo não encontrado: {0} +90125=Classe inválida, experada {0} mas está {1} +90126=Base de dados não é persistente +90127=O resultado definido não é atualizável. A consulta deve selecionar todas as colunas de chave primária. Somente uma tabela pode ser selecionada. +90128=O resultado não é navegável e não pode ser resetado. Você pode usar conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ..). +90129=Transação {0} não encontrada +90130=Este método não é permitido para um statement preparado; Utilize um statement instanciado. +90131=Atualização concorrente na tabela {0}: outra transação atualizou ou deletou a mesma linha +90132=Agregação {0} não encontrada +90133=#Cannot change the setting {0} when the database is already open +90134=#Access to the class {0} is denied +90135=#The database is open in exclusive mode; can not open additional connections +90136=#Window not found: {0} +90137=#Can only assign to a variable, not to: {0} +90138=#Invalid database name: {0} +90139=#The public static Java method was not found: {0} +90140=#The result set is readonly. You may need to use conn.createStatement(.., ResultSet.CONCUR_UPDATABLE). +90141=#Serializer cannot be changed because there is a data table: {0} +90142=#Step size must not be zero +90143=#Row {1} not found in primary index {0} +90144=#Authenticator not enabled on database {0} +90145=#FOR UPDATE is not allowed in DISTINCT or grouped select +90146=#Database {0} not found, and IFEXISTS=true, so we can't auto-create it +90147=#Method {0} is not allowed when connection is in auto-commit mode +90148=#Current value of sequence {0} is not yet defined in this session +90149=#Database {0} not found, either pre-create it or allow remote database creation (not recommended in secure environments) +90150=#Precision ({0}) must be between {1} and {2} inclusive +90151=#Scale or fractional seconds precision ({0}) must be between {1} and {2} inclusive +90152=#Constraint {0} is used by constraint {1} +90153=#Column {0} references uncomparable column {1} +90154=#Generated column {0} cannot be assigned +90155=#Generated column {0} cannot be updatable by a referential constraint with {1} clause +90156=#Column alias is not specified for expression {0} +90157=#Column index {0} in GROUP BY clause is outside valid range 1 - {1} +HY000=Erro geral: {0} +HY004=Tipo de dados desconhecido: {0} +HYC00=Recurso não suportado: {0} +HYT00=Timeout ao tentar bloquear a tabela {0} diff --git a/h2/src/main/org/h2/res/_messages_ru.prop b/h2/src/main/org/h2/res/_messages_ru.prop new file mode 100644 index 0000000..c037c35 --- /dev/null +++ b/h2/src/main/org/h2/res/_messages_ru.prop @@ -0,0 +1,202 @@ +.translator=Sergi Vladykin; Evgenij Ryazanov +02000=Нет данных +07001=Неверное количество параметров для функции {0}, ожидаемое количество: {1} +08000=Ошибка при открытии базы данных: {0} +21S02=Неверное количество столбцов +22001=Значение слишком длинное для поля {0}: {1} +22003=Численное значение вне допустимого диапазона: {0} +22004=Численное значение вне допустимого диапазона: {0} в столбце {1} +22007=Невозможно преобразование строки {1} в тип {0} +22012=Деление на ноль: {0} +22013=Недопустимое значение PRECEDING или FOLLOWING в оконной функции: {0} +22018=Ошибка преобразования данных при конвертации {0} +22025=Ошибка в LIKE ESCAPE: {0} +2202E=Недопустимый элемент массива: {0}, ожидался {1} +22030=Недопустимое значение для столбца {0}: {1} +22031=Значение не указано в перечислимом типе {0}: {1} +22032=Пустые перечислимые типы не допускаются +22033=Повторяющиеся значения в перечислимом типе: {0} +23502=Значение NULL не разрешено для поля {0} +23503=Нарушение ссылочной целостности: {0} +23505=Нарушение уникального индекса или первичного ключа: {0} +23506=Нарушение ссылочной целостности: {0} +23507=Для поля {0} не установлено значение по умолчанию +23513=Нарушение ограничения: {0} +23514=Неправильное ограничение CHECK: {0} +28000=Неверное имя пользователя или пароль +40001=Обнаружена взаимная блокировка потоков. Текущая транзакция была откачена. Детали: {0} +42000=Синтаксическая ошибка в выражении SQL {0} +42001=Синтаксическая ошибка в выражении SQL {0}; ожидалось {1} +42602=Недопустимое имя {0} +42622=Имя, начинающееся с {0}, слишком длинное. Максимальная длина: {1} +42S01=Таблица {0} уже существует +42S02=Таблица {0} не найдена +42S03=#Table {0} not found (candidates are: {1}) +42S04=#Table {0} not found (this database is empty) +42S11=Индекс {0} уже существует +42S12=Индекс {0} не найден +42S21=Повтор имени столбца {0} +42S22=Столбец {0} не найден +42S31=Должны использоваться идентичные выражения; ожидалось {0}, получено {1} +54011=Слишком много столбцов. Масимальное количество {0} +57014=Запрос был отменен или закончилось время ожидания сессии +90000=Функция {0} должна возвращать набор записей +90001=Метод не разрешен для запросов. Используйте execute или executeQuery вместо executeUpdate +90002=Метод разрешен только для запросов. Используйте execute или executeUpdate вместо executeQuery +90003=Шестнадцатиричная строка содержит нечетное количество символов: {0} +90004=Шестнадцатиричная строка содержит нешестнадцатиричные символы: {0} +90005=Недопустимые флаги триггера: {0} +90006=Последовательность {0} вышла за границы (MINVALUE, MAXVALUE) +90007=Объект уже закрыт +90008=Недопустимое значение {0} для параметра {1} +90009=Невозможно создать или изменить последовательность {0} из-за неправильных атрибутов (базовое значение {1}, начальное значение {2}, минимальное значение {3}, максимальное значение {4}, приращение {5}, кэш {6}) +90010=Неправильный формат TO_CHAR {0} +90011=Путь неявно является относительным для текущего рабочего каталога и не допустим в URL базы данных {0}. Используйте абсолютный путь, ~/name, ./name, или настройку baseDir. +90012=Параметр {0} не установлен +90013=База данных {0} не найдена +90014=Ошибка при разборе {0} +90015=SUM или AVG на недопустимом типе данных {0} +90016=Столбец {0} должен быть в предложении GROUP BY +90017=Попытка создания второго первичного ключа +90018=Незакрытое приложением соединение уничтожено сборщиком мусора +90019=Невозможно удалить текущего пользователя +90020=База данных уже используется: {0}. Возможные решения: закрыть все другие соединения; использовать режим сервера +90021=Такое сочетание настроек базы данных не поддерживается: {0} +90022=Функция {0} не найдена +90023=Поле {0} не должно поддерживать значение NULL +90024=Ошибка при переименовании файла {0} в {1} +90025=Невозможно удалить файл {0} +90026=Ошибка сериализации, причина: {0} +90027=Ошибка десериализации, причина: {0} +90028=Ошибка ввода/вывода: {0} +90029=Запись не является обновляемой +90030=Файл поврежден при чтении строки: {0}. Возможные решения: используйте утилиту восстановления (recovery tool) +90031=Ошибка ввода/вывода: {0}; {1} +90032=Пользователь {0} не найден +90033=Пользователь {0} уже существует +90034=Ошибка записи в файл трассировки: {0}, причина: {1} +90035=Последовательность {0} уже существует +90036=Последовательность {0} не найдена +90037=Представление {0} не найдено +90038=Представление {0} уже существует +90039=Этот CLOB или BLOB объект закрыт по таймауту: {0} +90040=Для выполнения данной операции необходимы права администратора +90041=Триггер {0} уже существует +90042=Триггер {0} не найден +90043=Ошибка при создании или инициализации триггера {0}, класс {1}, причина: {2}; для изучения подробностей смотрите корневую причину +90044=Ошибка при выполнении триггера {0}, класс {1}, причина : {2}; для изучения подробностей смотрите корневую причину +90045=Ограничение {0} уже существует +90046=Ошибка формата URL; должно быть {0}, на текущий момент {1} +90047=Несовпадение версий: версия драйвера {0}, версия сервера {1} +90048=Неподдерживаемая версия файлов базы данных или некорректный заголовок в файле {0} +90049=Ошибка шифрования в файле {0} +90050=Некорректный формат пароля, должен быть: пароль файла <пробел> пароль пользователя +90052=Подзапрос выбирает более одного столбца +90053=Подзапрос выбирает более одной строки +90054=Некорректное использование агрегирующей функции {0} +90055=Метод шифрования {0} не поддерживается +90056=Функция {0}: Неверный формат даты: {1} +90057=Ограничение {0} не найдено +90058=Commit или rollback внутри триггера не допускается +90059=Неоднозначное имя столбца {0} +90060=Метод блокировки файлов {0} не поддерживается +90061=Ошибка при открытии порта {0} (порт может уже использоваться), причина: {1} +90062=Ошибка при создании файла {0} +90063=Savepoint не существует: {0} +90064=Savepoint не имеет имени +90065=Savepoint является именованным +90066=Повтор свойства соединения {0} +90067=Соединение разорвано: {0} +90068=Столбцы предложения ORDER BY {0} в данном случае должны содержаться в выбираемом списке +90069=Роль {0} уже существует +90070=Роль {0} не найдена +90071=Пользователь или роль {0} не найдены +90072=Недопустимо одновременно управлять правами и ролями пользователя +90073=Соответствующие методы Java должны иметь различное количество аргументов: {0} и {1} +90074=Роль {0} уже выдана +90075=Поле является частью индекса {0} +90076=Псевдоним функции {0} уже существует +90077=Псевдоним функции {0} не найден +90078=Схема {0} уже существует +90079=Схема {0} не найдена +90080=Схема должна совпадать с текущей +90081=Таблица содержит записи со значением NULL в поле {0} +90082=Последовательность {0} относится к таблице +90083=На поле может ссылаться {0} +90084=Невозможно удалить последнее поле {0} +90085=Индекс {0} относится к ограничению {1} +90086=Класс {0} не найден +90087=Метод {0} не найден +90088=Неизвестный режим {0} +90089=Невозможно изменить collation, пока есть данные в таблицах: {0} +90090=Схема {0} не может быть удалена +90091=Роль {0} не может быть удалена +90093=Ошибка кластеризации - база данных работает в одиночном режиме +90094=Ошибка кластеризации - база данных работает в кластерном режиме, список серверов: {0} +90095=Ошибка формата строкового литерала: {0} +90096=Недостаточно прав на объект {0} +90097=База данных доступна только на чтение +90098=База данных уже закрыта +90099=Ошибка при установке слушателя событий базы данных {0}, причина: {1} +90101=Некорректный XID формат: {0} +90102=Неподдерживаемые опции сжатия: {0} +90103=Неподдерживаемый алгоритм сжатия: {0} +90104=Ошибка сжатия +90105=Ошибка при вызове пользовательской функции: {0} +90106=Невозможно очистить {0} +90107=Невозможно удалить {0}, пока существует зависимый объект {1} +90108=Ошибка нехватки памяти +90109=Представление {0} содержит ошибки: {1} +90110=Значения типов данных {0} и {1} не сравнимы друг с другом +90110=Сравнение массива (ARRAY) со скалярным значением +90111=Ошибка при обращении к линкованной таблице SQL запросом {0}, причина: {1} +90112=Запись не найдена при удалении из индекса {0} +90113=Неподдерживаемая опция соединения {0} +90114=Константа {0} уже существует +90115=Константа {0} не найдена +90116=Вычисление литералов запрещено +90117=Удаленные соединения к данному серверу запрещены, см. -tcpAllowOthers +90118=Невозможно удалить таблицу {0} +90119=Домен {0} уже существует +90120=Домен {0} не найден +90121=База данных уже закрыта (чтобы отключить автоматическое закрытие базы данных при останове JVM, добавьте ";DB_CLOSE_ON_EXIT=FALSE" в URL) +90122=Ограничение WITH TIES использовано без соответствующего раздела ORDER BY. +90123=Одновременное использование индексированных и неиндексированных параметров в запросе не поддерживается +90124=Файл не найден: {0} +90125=Недопустимый класс, ожидался {0}, но получен {1} +90126=База данных не является персистентной +90127=Набор записей не является обновляемым. Запрос должен выбирать все поля уникального ключа. Только одна таблица может быть выбрана. +90128=Набор записей не является прокручиваемым. Возможно необходимо использовать conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ..). +90129=Транзакция {0} не найдена +90130=Данный метод не разрешен для PreparedStatement; используйте Statement. +90131=Конкурентное изменение таблицы {0}: другая транзакция обновила или удалила ту же строку +90132=Агрегирующая функция {0} не найдена +90133=Невозможно изменить опцию {0}, когда база данных уже открыта +90134=Доступ к классу {0} запрещен +90135=База данных открыта в эксклюзивном режиме, открыть дополнительные соединения невозможно +90136=Окно не найдено: {0} +90137=Присваивать значения возможно только переменным, но не: {0} +90138=Недопустимое имя базы данных: {0} +90139=public static Java метод не найден: {0} +90140=Набор записей не является обновляемым. Возможно необходимо использовать conn.createStatement(.., ResultSet.CONCUR_UPDATABLE). +90141=Serializer не может быть изменен, потому что есть таблица данных: {0} +90142=Размер шага не должен быть равен нулю +90143=Строка {1} не найдена в первичном индексе {0} +90144=Внешняя аутентификация не включена в базе данных {0} +90145=FOR UPDATE не допускается в запросе с DISTINCT или запросе с группировкой +90146=База данных {0} не найдена и её автоматическое создание запрещено флагом IFEXISTS=true +90147=Нельзя использовать метод {0} при включённом автовыполнении +90148=Текущее значение последовательности {0} ещё не определено в этой сессии +90149=База данных {0} не найдена, создайте её предварительно или разрешите удалённое создание баз данных (не рекомендуется в защищённых системах) +90150=Диапазон или точность ({0}) должны быть в пределах от {1} до {2} включительно +90151=Масштаб или точность долей секунды ({0}) должны быть в пределах {1} до {2} включительно +90152=Ограничение {0} испльзуется ограничением {1} +90153=Столбец {0} ссылается на столбец {1}, не имеющий допустимой операции сравнения +90154=Нельзя присвоить значение генерируемому столбцу {0} +90155=Генерируемый столбец {0} не может обновляться ссылочным ограничением с пунктом {1} +90156=Имя столбца не указано для выражения {0} +90157=#Column index {0} in GROUP BY clause is outside valid range 1 - {1} +HY000=Внутренняя ошибка: {0} +HY004=Неизвестный тип данных: {0} +HYC00=Данная функция не поддерживается: {0} +HYT00=Время ожидания блокировки таблицы {0} истекло diff --git a/h2/src/main/org/h2/res/_messages_sk.prop b/h2/src/main/org/h2/res/_messages_sk.prop new file mode 100644 index 0000000..b86a883 --- /dev/null +++ b/h2/src/main/org/h2/res/_messages_sk.prop @@ -0,0 +1,201 @@ +.translator=Ľubomír Grajciar +02000=Žiadné dáta nie sú dostupné +07001=Nesprávny počet parametrov pre {0}, očakávaný počet: {1} +08000=Chyba otvorenia databázy: {0} +21S02=Počet stĺpcov sa nezhoduje +22001=Hodnota je príliš dlhá pre stĺpec {0}: {1} +22003=Číselná hodnota mimo rozsah: {0} +22004=#Numeric value out of range: {0} in column {1} +22007=Nemožem rozobrať {0} konštantu {1} +22012=Delenie nulou: {0} +22013=#Invalid PRECEDING or FOLLOWING size in window function: {0} +22018=Chyba konverzie dát pre {0} +22025=Chyba v LIKE ESCAPE: {0} +2202E=#Array element error: {0}, expected {1} +22030=#Value not permitted for column {0}: {1} +22031=#Value not a member of enumerators {0}: {1} +22032=#Empty enums are not allowed +22033=#Duplicate enumerators are not allowed for enum types: {0} +23502=NULL nie je povolený pre stĺpec {0} +23503=Porušenie obmedzenia (constraint) referenčnej integrity: {0} +23505=Porušenie jedinečnosti (unique) indexu alebo primárneho kľúča: {0} +23506=Porušenie obmedzenia (constraint) referenčnej integrity: {0} +23507=Nie je nastavená vychodzia hodnota stĺpca {0} +23513=Skontrolujte porušenie obmedzenia (constraint): {0} +23514=#Check constraint invalid: {0} +28000=Nesprávne používateľské meno alebo heslo +40001=Mŕtvy bod (deadlock) detegovaný. Aktuálna transakcia bude odvolaná (rolled back). Podrobnosti: {0} +42000=Syntaktická chyba v SQL príkaze {0} +42001=Syntaktická chyba v SQL príkaze {0}; očakávané {1} +42602=#Invalid name {0} +42622=#The name that starts with {0} is too long. The maximum length is {1} +42S01=Tabuľka {0} už existuje +42S02=Tabuľka {0} nenájdená +42S03=#Table {0} not found (candidates are: {1}) +42S04=#Table {0} not found (this database is empty) +42S11=Index {0} už existuje +42S12=Index {0} nenájdený +42S21=Duplicitné meno stĺpca {0} +42S22=Stĺpec {0} nenájdený +42S31=#Identical expressions should be used; expected {0}, found {1} +54011=#Too many columns. The maximum count is {0} +57014=Príkaz bol zrušený alebo vypršal časový limit sedenia +90000=Funkcia {0} musí vracať výsledok (result set) +90001=Metóda nie je povolená pre dopyt (query). Použite execute alebo executeQuery namiesto executeUpdate +90002=Metóda je povolená iba pre dopyt (query). Použite execute alebo executeUpdate namiesto executeQuery +90003=Hexadecimálny reťazec s nepárnym počtom znakov: {0} +90004=Hexadecimálny reťazec obsahuje nepovolené znaky pre šestnáskovú sústavu: {0} +90005=#Invalid trigger flags: {0} +90006=#Sequence {0} has run out of numbers +90007=Objekt už je zatvorený +90008=Nesprávna hodnota {0} parametra {1} +90009=#Unable to create or alter sequence {0} because of invalid attributes (base value {1}, start value {2}, min value {3}, max value {4}, increment {5}, cache size {6}) +90010=#Invalid TO_CHAR format {0} +90011=#A file path that is implicitly relative to the current working directory is not allowed in the database URL {0}. Use an absolute path, ~/name, ./name, or the baseDir setting instead. +90012=Parameter {0} nie je nastavený +90013=Databáza {0} nenájdená +90014=Chyba rozobrania (parse) {0} +90015=SUM alebo AVG na nesprávnom dátovom type {0} +90016=Stĺpec {0} musí byť uvedený v GROUP BY zozname +90017=Pokus o definíciu druhého primárneho kľúča +90018=Spojenie neuzatvorené aplikáciou bolo zrušené +90019=Nemôžem zmazať aktuálneho používateľa +90020=Databáza sa už asi používa: {0}. Možné riešenia: zatvorte všetky dalšie spojenia; použite serverový mód +90021=#This combination of database settings is not supported: {0} +90022=Funkcia {0} nenájdená +90023=Stĺpec {0} nesmie umožniť vložiť NULL +90024=Chyba pri premenovaní súboru {0} na {1} +90025=Nemôžem zmazať súbor {0} +90026=Serializácia neúspešná, dôvod: {0} +90027=Deserializácia neúspešná, dôvod: {0} +90028=IO výnimka: {0} +90029=Momentálne nie ste na riadku umožňujúcom úpravu (update) +90030=Poškodenie súboru pri čítaní záznamu: {0}. Možné riešenie: použiť nástroj na opravu databázy +90031=IO výnimka: {0}; {1} +90032=Používateľ {0} nenájdený +90033=Používateľ {0} už existuje +90034=Chyba Log súboru: {0}, dôvod: {1} +90035=Sekvencia {0} už existuje +90036=Sekvencia {0} nenájdená +90037=Pohľad (view) {0} nenájdený +90038=Pohľad (view) {0} už existuje +90039=#This CLOB or BLOB reference timed out: {0} +90040=Administrátorské práva sú potrebné pre túto operáciu +90041=Spúštač (trigger) {0} už existuje +90042=Spúšťač (trigger) {0} nenájdený +90043=Chyba vytvorenia alebo spustenia spúšťača (trigger) {0} objekt, trieda {1}, dôvod: {2}; pre podrobnosti pozrite prvotný (root) dôvod +90044=Chyba vykonania spúšťača (trigger) {0}, trieda {1}, dôvod : {2}; pre podrobnosti pozrite prvotný (root) dôvod +90045=Obmedzenie (constraint) {0} už existuje +90046=Chyba formátu URL; musí byť {0}, ale je {1} +90047=Nezhoda verzií, verzia ovládača je {0}, ale verzia servera je {1} +90048=Nepodporovaná verzia databázového súboru alebo chybná hlavička súuboru {0} +90049=Chyba šifrovania súboru {0} +90050=Nesprávny formát hesiel, musí byť: súborové heslo používateľské heslo +90052=Vnorený dopyt (subquery) nie je dopyt na jeden stĺpec +90053=Skalárny vnorený dopyt (scalar subquery) obsahuje viac ako jeden riadok +90054=Nesprávne použitie agregačnej funkcie {0} +90055=Nepodporovaný typ šifry {0} +90056=#Function {0}: Invalid date format: {1} +90057=Obmedzenie (constraint) {0} nenájdený +90058=Commit alebo Rollback nie je povolené použiť v spúšťači (trigger) +90059=Nejednoznačné meno stĺpca {0} +90060=Nepodporovaná metóda zamknutia súboru {0} +90061=Vznikla výnimka pri otváraní portu {0} (port sa asi už používa), dôvod: {1} +90062=Chyba vytvorenia súboru {0} +90063=Bod návratu (savepoint) je nesprávny: {0} +90064=Bod návratu (savepoint) nie je pomenovaný +90065=Bod návratu (savepoint) je pomenovaný +90066=Duplicitná vlastnosť (property) {0} +90067=Spojenie je prerušené: {0} +90068=Order by expression {0} must be in the result list in this case +90069=Rola {0} už existuje +90070=Rola {0} nenájdená +90071=Používateľ alebo rola {0} nenajdená +90072=Role a práva sa nemôžu miešať +90073=Zhodné Java metódy musia mať rozdielny počet parametrov: {0} a {1} +90074=Rola {0} už je udelená (granted) +90075=Stĺpec je časťou indexu {0} +90076=Alias funkcie {0} už existuje +90077=Alias funkcie {0} nenájdený +90078=Schéma {0} už existuje +90079=Schéma {0} nenájdená +90080=Meno schémy sa musí zhodovať +90081=Stĺpec {0} obsahuje NULL hodnoty +90082=Sekvencia {0} patrí tabuľke +90083=Na stĺpec asi odkazuje {0} +90084=Nemôžem zmazať posledný stĺpec {0} +90085=Index {0} patrí obmedzeniu (constraint) {1} +90086=Trieda {0} nenájdená +90087=Metóda {0} nenájdená +90088=Neznámy mód {0} +90089=Kolácia (collation) nemôže byť zmenená pretože tu je dátová tabuľka: {0} +90090=Schéma {0} nemôže byť zmazaná +90091=Rola {0} nemôže byť zmazaná +90093=Chyba klustra - databáza je teraz v nezáviaslom (standalone) móde +90094=Chyba klustra - databáza je teraz v kluster móde, zoznam serverov: {0} +90095=Chyba formátu reťazca: {0} +90096=Nedostatočné práva na objekt {0} +90097=Databáza je iba na čítanie +90098=Databáza bola zatvorená +90099=Chyba nastavenia poslucháča udalostí (event listener) {0} pre databázu, dôvod: {1} +90101=Nesprávny XID formát: {0} +90102=Nepodporované prepínače kompresie: {0} +90103=Nepodporovaný algoritmus kompresie: {0} +90104=Chyba kompresie +90105=Výnimka pri volaní používatelsky definovanej funkcie +90106=Nemožem skrátiť {0} +90107=Nemôžem zmazať {0} lebo {1} zavisí na {0} +90108=Nedostatok pamäte. +90109=Pohľad (view) {0} je nesprávny: {1} +90110=#Values of types {0} and {1} are not comparable +90111=Chyba prístupu k linkovanej tabuľke SQL príkazom {0}, dôvod: {1} +90112=Riadok nenájdený pri pokuse o vymazanie cez index {0} +90113=Nepodporované nastavenie spojenia {0} +90114=Konštanta {0} už existuje +90115=Konštanta {0} nenájdená +90116=Písmená (literals) tohto druhu nie sú povolené +90117=Vzdialené pripojenia na tento server nie sú povolené, pozrite -tcpAllowOthers +90118=Nemôžem zmazať tabuľku {0} +90119=Doména {0} už existuje +90120=Doména {0} nenájdený +90121=Databáza už je zatvorená (na zamedzenie automatického zatvárania pri ukončení VM, pridajte ";DB_CLOSE_ON_EXIT=FALSE" do DB URL) +90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause. +90123=Nemožno miešať indexované a neindexované parametre +90124=Súbor nenájdený: {0} +90125=Nesprávna trieda {1}, očakávana je {0} +90126=Databáza nie je trvalá (persistent) +90127=Výsledok (result set) nie je upraviteľný. Dopyt musí vybrať všetky stĺpce zahrnuté v jedinečnom (unique) kĺúči. Iba jedna tabuľka môže byť vybraná. +90128=Výsledok (result set) nie je rolovateľný (scrollable) a nemôže byť resetnutý. Je potrebné použiť conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ..). +90129=Transakcia {0} nenájdená +90130=Táto metóda nie je povolená pre pripravený prikaz (prepared statement); použite namiesto neho normálny prikaz (regular statement). +90131=Viacnásobná úprava v tabuľke {0}: iná transakcia upravila alebo zmazala rovnaký riadok +90132=Agregácia {0} nenájdená +90133=Nemôžem zmeniť nastavenie {0} keď už je databáza otvorená +90134=Prístup k triede {0} odoprený +90135=Databáza je otvorená vo výhradnom (exclusive) móde; nemôžem na ňu otvoriť ďalšie pripojenia +90136=#Window not found: {0} +90137=Môžete priradiť len do premennej, nie do: {0} +90138=Nesprávne meno databázy: {0} +90139=Verejná statická Java metóda nebola nájdená: {0} +90140=Výsledok (result set) je iba na čítanie. Je potrebné použiť conn.createStatement(.., ResultSet.CONCUR_UPDATABLE). +90141=#Serializer cannot be changed because there is a data table: {0} +90142=#Step size must not be zero +90143=#Row {1} not found in primary index {0} +90144=#Authenticator not enabled on database {0} +90145=#FOR UPDATE is not allowed in DISTINCT or grouped select +90146=#Database {0} not found, and IFEXISTS=true, so we can't auto-create it +90147=#Method {0} is not allowed when connection is in auto-commit mode +90148=#Current value of sequence {0} is not yet defined in this session +90149=#Database {0} not found, either pre-create it or allow remote database creation (not recommended in secure environments) +90150=#Precision ({0}) must be between {1} and {2} inclusive +90151=#Scale or fractional seconds precision ({0}) must be between {1} and {2} inclusive +90152=#Constraint {0} is used by constraint {1} +90153=#Column {0} references uncomparable column {1} +90154=#Generated column {0} cannot be assigned +90155=#Generated column {0} cannot be updatable by a referential constraint with {1} clause +90156=#Column alias is not specified for expression {0} +90157=#Column index {0} in GROUP BY clause is outside valid range 1 - {1} +HY000=Všeobecná chyba: {0} +HY004=Neznámy dátový typ: {0} +HYC00=Vlastnosť nie je podporovaná: {0} +HYT00=Vypršal časový limit na zamknutie tabuľky {0} diff --git a/h2/src/main/org/h2/res/_messages_zh_cn.prop b/h2/src/main/org/h2/res/_messages_zh_cn.prop new file mode 100644 index 0000000..03d1079 --- /dev/null +++ b/h2/src/main/org/h2/res/_messages_zh_cn.prop @@ -0,0 +1,201 @@ +.translator=Huang JingCong,Terrence +02000=没有可用数据 +07001= {0}非法参数数量, 预期数量: {1} +08000=开启数据库错误: {0} +21S02=字段数目不匹配 +22001=字段 {0}数值太大: {1} +22003=数值超出范围: {0} +22004=#Numeric value out of range: {0} in column {1} +22007=不能解析字段 {0} 的数值 :{1} +22012=除数为零: {0} +22013=#Invalid PRECEDING or FOLLOWING size in window function: {0} +22018=转换数据{0}期间出现转换错误 +22025=LIKE ESCAPE(转义符)存在错误: {0} +2202E=#Array element error: {0}, expected {1} +22030=#Value not permitted for column {0}: {1} +22031=#Value not a member of enumerators {0}: {1} +22032=#Empty enums are not allowed +22033=#Duplicate enumerators are not allowed for enum types: {0} +23502=字段 {0} 不允许为NULL值 +23503=违反引用完整性约束: {0} +23505=违反唯一索引或逐渐约束: {0} +23506=违反引用完整性约束: {0} +23507=没有为字段{0}设置缺省值 +23513=违反检查约束: {0} +23514=#Check constraint invalid: {0} +28000=用户名或口令错误 +40001=检测到死锁.当前事务已回滚.详情: {0} +42000=SQL语法错误 {0} +42001=SQL语法错误 {0}; 预期: {1} +42602=#Invalid name {0} +42622=#The name that starts with {0} is too long. The maximum length is {1} +42S01= {0}表已存在 +42S02=找不到表 {0} +42S03=#Table {0} not found (candidates are: {1}) +42S04=#Table {0} not found (this database is empty) +42S11=索引 {0} 已存在 +42S12=找不到索引 {0} +42S21=重复的字段: {0} +42S22=找不到字段 {0} +42S31=#Identical expressions should be used; expected {0}, found {1} +54011=#Too many columns. The maximum count is {0} +57014=语句已取消执行或会话已过期 +90000={0} 函数必须返回一个结果集 +90001=不允许在查询内使用的方法,使用execute 或 executeQuery 代替 executeUpdate +90002=只允许在查询内使用的方法. 使用 execute 或 executeUpdate 代替 executeQuery +90003=十六进制字符串包含奇数个数字字符: {0} +90004=十六进制字符串包含非十六进制字符: {0} +90005=#Invalid trigger flags: {0} +90006=#Sequence {0} has run out of numbers +90007=对象已关闭 +90008=被发现非法的数值 {0} 在参数 {1} +90009=#Unable to create or alter sequence {0} because of invalid attributes (base value {1}, start value {2}, min value {3}, max value {4}, increment {5}, cache size {6}) +90010=#Invalid TO_CHAR format {0} +90011=#A file path that is implicitly relative to the current working directory is not allowed in the database URL {0}. Use an absolute path, ~/name, ./name, or the baseDir setting instead. +90012=参数 {0} 的值还没有设置 +90013=找不到数据库 {0} +90014=解析 {0} 时发生错误 +90015=错误的数据类型 {0}调用SUM 或者 AVG +90016=字段 {0} 必须出现在 GROUP BY 的列表内 +90017=尝试定义第二个主键 +90018=连接未被关闭但已被垃圾回收器回收 +90019=不能移除当前用户 +90020=数据可可能当前已正被使用: {0}. 解决方案有:关闭所有其他连接; 使用服务器模式(server mode) +90021=#This combination of database settings is not supported: {0} +90022=未找到函数 {0} +90023=字段 {0} 不能为空 +90024=将文件 {0} 命名为 {1} 发生错误 +90025=不能删除文件 {0} +90026=序列化(串行化)失败,原因: {0} +90027=反串行化失败, 原因: {0} +90028=IO 异常: {0} +90029=当前不是一个可更新的行 +90030=读取记录时文件错误: {0}. 解决方案: 使用恢复工具(recovery tool) +90031=IO 异常: {0}; {1} +90032=找不到用户 {0} +90033=用户{0}已存在 +90034=日志文件错误: {0}, 原因: {1} +90035=序列 {0} 已存在 +90036=找不到序列 {0} +90037=视图 {0} 已存在 +90038=找不到 {0} 视图 +90039=#This CLOB or BLOB reference timed out: {0} +90040=此操作需要管理员权限 +90041=触发器 {0}已存在 +90042=找不到触发器 {0} +90043=创建或初始化触发器{0}时发生错误 , 类型 {1}, 原因: {2}; 更多详情请查看根本原因 +90044=实行触发器 {0}时发生错误, 类型 {1}, 原因 : {2}; 更多详情请查看根本原因 +90045=约束 {0} 已存在 +90046=URL 格式错误; 必须为 {0} 但错误为 {1} +90047=版本不匹配, 驱动版本为 {0} 但服务器版本为 {1} +90048=不支持的数据库文件版本或无效的文件头 {0} +90049=文件加密错误 {0} +90050=错误的口令格式, 必须为: 文件 口令 <空格> 用户 口令 +90052=子查询非单一字段查询 +90053=标量子查询(Scalar subquery)包含多于一行结果 +90054=非法使用聚合函数 {0} +90055=不支持的加密算法 {0} +90056=#Function {0}: Invalid date format: {1} +90057=约束 {0} 找不到 +90058=提交或回滚不能办函触发器 +90059=不明确的字段名 {0} +90060=不支持的文件锁方法 {0} +90061=打开端口 {0} 异常(可能端口正被使用), 原因: {1} +90062=创建文件错误{0} +90063=非法记录点: {0} +90064=未命名的记录点 +90065=记录点已命名 +90066=重复的属性 {0} +90067=连接已断开: {0} +90068=Order by 表达式 {0} 必须在结果列表内 +90069=角色 {0} 已存在 +90070=找不到角色 {0} +90071=找不到用户或者角色{0} +90072=不能回合角色和权限 +90073=匹配的Java方法必须用不同的参数数目: {0} 和 {1} +90074=角色 {0}已授权 +90075=字段是 {0}索引的一部分 +90076=已存在 {0} +90077=找不到函数别名 {0} +90078=方案(Schema) {0} 已存在 +90079=找不到方案(Schema) {0} +90080=方案名(Schema) 必须匹配 +90081=字段 {0} 包含空值 +90082=序列 {0} 属于一个表 +90083=字段可能已被 {0} 引用 +90084=不能移除最后一个字段 {0} +90085=索引 {0} 属于一个约束 {1} +90086=找不到类 {0} +90087=找不到方法 {0} +90088=未知模式 {0} +90089=不能变更整理(Collation),因为存在一个数据表: {0} +90090=不能删除方案(schema) {0} +90091=不能删除角色 {0} +90093=集群错误 - 数据库现在运行在独立运行模式 +90094=集群错误 - 数据库现在运行在集群模式, 服务器列表: {0} +90095=字符串格式错误: {0} +90096=对象所需权限不够 {0} +90097=数据库为只读 +90098=数据库已关闭 +90099=设立数据库事件监听器错误 {0}, 原因: {1} +90101=错误的 XID 格式: {0} +90102=不支持的压缩选项: {0} +90103=不支持的压缩算法: {0} +90104=压缩错误 +90105=调用用户自定义函数时发生异常: {0} +90106=不能截断 {0} +90107=不能删除 {0} ,因为 {1} 依赖着它 +90108=内存不足. +90109=视图 {0} 无效: {1} +90110=#Values of types {0} and {1} are not comparable +90111=SQL语句访问表连接错误 {0}, 原因: {1} +90112=尝试从索引中删除 {0}的时候找不到行 +90113=不支持的连接设置 {0} +90114=常量 {0} 已存在 +90115=找不到常量{0} +90116=不允许此类型的字面值 +90117=不允许远程连接到本服务器, 参见 -tcpAllowOthers +90118=不能删除表 {0} +90119=域 {0} 已存在 +90120=找不到域 {0} +90121=数据库已关闭 (若需要禁用在虚拟机关闭的时候同时关闭数据库,请加上 ";DB_CLOSE_ON_EXIT=FALSE" 到数据库连接的 URL) +90122=#The WITH TIES clause is not allowed without a corresponding ORDER BY clause. +90123=不能混合已索引和未索引的参数 +90124=文件 找不到: {0} +90125=无效的类, 取代找到 {0} 但得到 {1} +90126=数据库不是持久化的 +90127=结果集不可更新. 查询必须选择唯一键的所有字段. 只能选择一个表. +90128=结果集不可滚动和重置. 你可以使用 conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ..). +90129= 找不到 事务{0} +90130=预编译语句不允许使用此方法; 使用常规语句代替. +90131=并发修改表 {0}: 另一个事务已经更新或删除相同的行 +90132=找不到聚合{0} +90133=数据库有已启动的时候不允许更改设置{0} +90134=访问 {0}类时被拒绝 +90135=数据库运行在独占模式(exclusive mode); 不能打开额外的连接 +90136=#Window not found: {0} +90137=只能赋值到一个变量,而不是: {0} +90138=无效数据库名称: {0} +90139=找不到公用Java静态方法: {0} +90140=结果集是只读的. 你可以使用 conn.createStatement(.., ResultSet.CONCUR_UPDATABLE). +90141=#Serializer cannot be changed because there is a data table: {0} +90142=#Step size must not be zero +90143=#Row {1} not found in primary index {0} +90144=#Authenticator not enabled on database {0} +90145=#FOR UPDATE is not allowed in DISTINCT or grouped select +90146=#Database {0} not found, and IFEXISTS=true, so we can't auto-create it +90147=#Method {0} is not allowed when connection is in auto-commit mode +90148=#Current value of sequence {0} is not yet defined in this session +90149=#Database {0} not found, either pre-create it or allow remote database creation (not recommended in secure environments) +90150=#Precision ({0}) must be between {1} and {2} inclusive +90151=#Scale or fractional seconds precision ({0}) must be between {1} and {2} inclusive +90152=#Constraint {0} is used by constraint {1} +90153=#Column {0} references uncomparable column {1} +90154=#Generated column {0} cannot be assigned +90155=#Generated column {0} cannot be updatable by a referential constraint with {1} clause +90156=#Column alias is not specified for expression {0} +90157=#Column index {0} in GROUP BY clause is outside valid range 1 - {1} +HY000=常规错误: {0} +HY004=位置数据类型: {0} +HYC00=不支持的特性: {0} +HYT00=尝试锁定表 {0} 的时候超时 diff --git a/h2/src/main/org/h2/res/h2-22-t.png b/h2/src/main/org/h2/res/h2-22-t.png new file mode 100644 index 0000000000000000000000000000000000000000..42cd1d5e24c4f6b5b8d194a3853dfeb82efde708 GIT binary patch literal 3870 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 literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/res/h2-22.png b/h2/src/main/org/h2/res/h2-22.png new file mode 100644 index 0000000000000000000000000000000000000000..95ba9c7be15bae06f2557ecb45b6d3d3dcc9f785 GIT binary patch literal 1239 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 literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/res/help.csv b/h2/src/main/org/h2/res/help.csv new file mode 100644 index 0000000..381726f --- /dev/null +++ b/h2/src/main/org/h2/res/help.csv @@ -0,0 +1,7495 @@ +# 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 + +"SECTION","TOPIC","SYNTAX","TEXT","EXAMPLE" + +"Commands (DML)","SELECT"," +SELECT [ DISTINCT @h2@ [ ON ( expression [,...] ) ] | ALL ] +selectExpression [,...] +[ FROM tableExpression [,...] ] +[ WHERE expression ] +[ GROUP BY groupingElement [,...] ] [ HAVING expression ] +[ WINDOW { { windowName AS windowSpecification } [,...] } ] +@h2@ [ QUALIFY expression ] +[ { UNION [ ALL ] | EXCEPT | INTERSECT } query ] +[ ORDER BY selectOrder [,...] ] +[ OFFSET expression { ROW | ROWS } ] +[ FETCH { FIRST | NEXT } [ expression [ PERCENT ] ] { ROW | ROWS } + { ONLY | WITH TIES } ] +@h2@ [ FOR UPDATE ] +"," +Selects data from a table or multiple tables. + +Command is executed in the following logical order: + +1. Data is taken from table value expressions that are specified in the FROM clause, joins are executed. +If FROM clause is not specified a single row is constructed. + +2. WHERE filters rows. Aggregate or window functions are not allowed in this clause. + +3. GROUP BY groups the result by the given expression(s). +If GROUP BY clause is not specified, but non-window aggregate functions are used or HAVING is specified +all rows are grouped together. + +4. Aggregate functions are evaluated. + +5. HAVING filters rows after grouping and evaluation of aggregate functions. +Non-window aggregate functions are allowed in this clause. + +6. Window functions are evaluated. + +7. QUALIFY filters rows after evaluation of window functions. +Aggregate and window functions are allowed in this clause. + +8. DISTINCT removes duplicates. +If DISTINCT ON is used only the specified expressions are checked for duplicates; +ORDER BY clause, if any, is used to determine preserved rows. +First row is each DISTINCT ON group is preserved. +In absence of ORDER BY preserved rows are not determined, database may choose any row from each DISTINCT ON group. + +9. UNION, EXCEPT, and INTERSECT combine the result of this query with the results of another query. +INTERSECT has higher precedence than UNION and EXCEPT. +Operators with equal precedence are evaluated from left to right. + +10. ORDER BY sorts the result by the given column(s) or expression(s). + +11. Number of rows in output can be limited with OFFSET and FETCH clauses. +OFFSET specifies how many rows to skip. +Please note that queries with high offset values can be slow. +FETCH FIRST/NEXT limits the number of rows returned by the query. +If PERCENT is specified number of rows is specified as a percent of the total number of rows +and should be an integer value between 0 and 100 inclusive. +WITH TIES can be used only together with ORDER BY and means that all additional rows that have the same sorting position +as the last row will be also returned. + +WINDOW clause specifies window definitions for window functions and window aggregate functions. +This clause can be used to reuse the same definition in multiple functions. + +If FOR UPDATE is specified, the tables or rows are locked for writing. +This clause is not allowed in DISTINCT queries and in queries with non-window aggregates, GROUP BY, or HAVING clauses. +Only the selected rows are locked as in an UPDATE statement. +Rows from the right side of a left join and from the left side of a right join, including nested joins, aren't locked. +Locking behavior for rows that were excluded from result using OFFSET / FETCH / LIMIT / TOP or QUALIFY is undefined, +to avoid possible locking of excessive rows try to filter out unneeded rows with the WHERE criteria when possible. +Rows are processed one by one. Each row is read, tested with WHERE criteria, locked, read again and re-tested, +because its value may be changed by concurrent transaction before lock acquisition. +Note that new uncommitted rows from other transactions are not visible unless read uncommitted isolation level is used +and therefore cannot be selected and locked. +Modified uncommitted rows from other transactions that satisfy the WHERE criteria cause this SELECT to wait for +commit or rollback of those transactions. +"," +SELECT * FROM TEST; +SELECT * FROM TEST ORDER BY NAME; +SELECT ID, COUNT(*) FROM TEST GROUP BY ID; +SELECT NAME, COUNT(*) FROM TEST GROUP BY NAME HAVING COUNT(*) > 2; +SELECT 'ID' COL, MAX(ID) AS MAX FROM TEST UNION SELECT 'NAME', MAX(NAME) FROM TEST; +SELECT * FROM TEST OFFSET 1000 ROWS FETCH FIRST 1000 ROWS ONLY; +SELECT A, B FROM TEST ORDER BY A FETCH FIRST 10 ROWS WITH TIES; +SELECT * FROM (SELECT ID, COUNT(*) FROM TEST + GROUP BY ID UNION SELECT NULL, COUNT(*) FROM TEST) + ORDER BY 1 NULLS LAST; +SELECT DISTINCT C1, C2 FROM TEST; +SELECT DISTINCT ON(C1) C1, C2 FROM TEST ORDER BY C1; +" + +"Commands (DML)","INSERT"," +INSERT INTO [schemaName.]tableName [ ( columnName [,...] ) ] +{ [ overrideClause ] { insertValues | @h2@ [ DIRECT ] query } } + | DEFAULT VALUES +"," +Inserts a new row / new rows into a table. + +When using DIRECT, then the results from the query are directly applied in the target table without any intermediate step. +"," +INSERT INTO TEST VALUES(1, 'Hello') +" + +"Commands (DML)","UPDATE"," +UPDATE [schemaName.]tableName [ [ AS ] newTableAlias ] SET setClauseList +[ WHERE expression ] @c@ [ ORDER BY sortSpecificationList ] +@h2@ FETCH { FIRST | NEXT } [ expression ] { ROW | ROWS } ONLY +"," +Updates data in a table. +ORDER BY is supported for MySQL compatibility, but it is ignored. +If FETCH is specified, at most the specified number of rows are updated (no limit if null or smaller than zero). +"," +UPDATE TEST SET NAME='Hi' WHERE ID=1; +UPDATE PERSON P SET NAME=(SELECT A.NAME FROM ADDRESS A WHERE A.ID=P.ID); +" + +"Commands (DML)","DELETE"," +DELETE FROM [schemaName.]tableName +[ WHERE expression ] +@h2@ FETCH { FIRST | NEXT } [ expression ] { ROW | ROWS } ONLY +"," +Deletes rows form a table. +If FETCH is specified, at most the specified number of rows are deleted (no limit if null or smaller than zero). +"," +DELETE FROM TEST WHERE ID=2 +" + +"Commands (DML)","BACKUP"," +@h2@ BACKUP TO fileNameString +"," +Backs up the database files to a .zip file. Objects are not locked, but +the backup is transactionally consistent because the transaction log is also copied. +Admin rights are required to execute this command. +"," +BACKUP TO 'backup.zip' +" + +"Commands (DML)","CALL"," +CALL expression +"," +Calculates a simple expression. This statement returns a result set with one row, +except if the called function returns a result set itself. +If the called function returns an array, then each element in this array is returned as a column. +"," +CALL 15*25 +" + +"Commands (DML)","EXECUTE IMMEDIATE"," +EXECUTE IMMEDIATE sqlString +"," +Dynamically prepares and executes the SQL command specified as a string. Query commands may not be used. +"," +EXECUTE IMMEDIATE 'ALTER TABLE TEST DROP CONSTRAINT ' || + QUOTE_IDENT((SELECT CONSTRAINT_NAME + FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS + WHERE TABLE_SCHEMA = 'PUBLIC' AND TABLE_NAME = 'TEST' + AND CONSTRAINT_TYPE = 'UNIQUE')); +" + +"Commands (DML)","EXPLAIN"," +@h2@ EXPLAIN { [ PLAN FOR ] | ANALYZE } +@h2@ { query | insert | update | delete | mergeInto | mergeUsing } +"," +Shows the execution plan for a statement. +When using EXPLAIN ANALYZE, the statement is actually executed, and the query plan +will include the actual row scan count for each table. +"," +EXPLAIN SELECT * FROM TEST WHERE ID=1 +" + +"Commands (DML)","MERGE INTO"," +@h2@ MERGE INTO [schemaName.]tableName [ ( columnName [,...] ) ] +@h2@ [ KEY ( columnName [,...] ) ] +@h2@ { insertValues | query } +"," +Updates existing rows, and insert rows that don't exist. If no key column is +specified, the primary key columns are used to find the row. If more than one +row per new row is affected, an exception is thrown. +"," +MERGE INTO TEST KEY(ID) VALUES(2, 'World') +" + +"Commands (DML)","MERGE USING"," +MERGE INTO [schemaName.]targetTableName [ [AS] targetAlias] +USING tableExpression +ON expression +mergeWhenClause [,...] +"," +Updates or deletes existing rows, and insert rows that don't exist. + +The ON clause specifies the matching column expression. + +Different rows from a source table may not match with the same target row +(this is not ensured by H2 if target table is an updatable view). +One source row may be matched with multiple target rows. + +If statement doesn't need a source table a DUAL table can be substituted. +"," +MERGE INTO TARGET_TABLE AS T USING SOURCE_TABLE AS S + ON T.ID = S.ID + WHEN MATCHED AND T.COL2 <> 'FINAL' THEN + UPDATE SET T.COL1 = S.COL1 + WHEN MATCHED AND T.COL2 = 'FINAL' THEN + DELETE + WHEN NOT MATCHED THEN + INSERT (ID, COL1, COL2) VALUES(S.ID, S.COL1, S.COL2); +MERGE INTO TARGET_TABLE AS T USING (SELECT * FROM SOURCE_TABLE) AS S + ON T.ID = S.ID + WHEN MATCHED AND T.COL2 <> 'FINAL' THEN + UPDATE SET T.COL1 = S.COL1 + WHEN MATCHED AND T.COL2 = 'FINAL' THEN + DELETE + WHEN NOT MATCHED THEN + INSERT VALUES (S.ID, S.COL1, S.COL2); +MERGE INTO TARGET T USING (VALUES (1, 4), (2, 15)) S(ID, V) + ON T.ID = S.ID + WHEN MATCHED THEN UPDATE SET V = S.V + WHEN NOT MATCHED THEN INSERT VALUES (S.ID, S.V); +MERGE INTO TARGET_TABLE USING DUAL ON ID = 1 + WHEN NOT MATCHED THEN INSERT VALUES (1, 'Test') + WHEN MATCHED THEN UPDATE SET NAME = 'Test'; +" + +"Commands (DML)","RUNSCRIPT"," +@h2@ RUNSCRIPT FROM fileNameString scriptCompressionEncryption +@h2@ [ CHARSET charsetString ] +@h2@ { [ QUIRKS_MODE ] [ VARIABLE_BINARY ] | FROM_1X } +"," +Runs a SQL script from a file. The script is a text file containing SQL +statements; each statement must end with ';'. This command can be used to +restore a database from a backup. The password must be in single quotes; it is +case sensitive and can contain spaces. + +Instead of a file name, a URL may be used. +To read a stream from the classpath, use the prefix 'classpath:'. +See the [Pluggable File System](https://h2database.com/html/advanced.html#file_system) section. + +The compression algorithm must match the one used when creating the script. +Instead of a file, a URL may be used. + +If ""QUIRKS_MODE"" is specified, the various compatibility quirks for scripts from older versions of H2 are enabled. +Use this clause when you import script that was generated by H2 1.4.200 or an older version into more recent version. + +If ""VARIABLE_BINARY"" is specified, the ""BINARY"" data type will be parsed as ""VARBINARY"". +Use this clause when you import script that was generated by H2 1.4.200 or an older version into more recent version. + +If ""FROM_1X"" is specified, quirks for scripts exported from H2 1.*.* are enabled. +Use this flag to populate a new database with the data exported from 1.*.* versions of H2. +This flag also enables ""QUIRKS_MODE"" and ""VARIABLE_BINARY"" implicitly. + +Admin rights are required to execute this command. +"," +RUNSCRIPT FROM 'backup.sql' +RUNSCRIPT FROM 'classpath:/com/acme/test.sql' +RUNSCRIPT FROM 'dump_from_1_4_200.sql' FROM_1X +" + +"Commands (DML)","SCRIPT"," +@h2@ SCRIPT { [ NODATA ] | [ SIMPLE ] [ COLUMNS ] } +@h2@ [ NOPASSWORDS ] @h2@ [ NOSETTINGS ] +@h2@ [ DROP ] @h2@ [ BLOCKSIZE blockSizeInt ] +@h2@ [ TO fileNameString scriptCompressionEncryption + [ CHARSET charsetString ] ] +@h2@ [ TABLE [schemaName.]tableName [, ...] ] +@h2@ [ SCHEMA schemaName [, ...] ] +"," +Creates a SQL script from the database. + +NODATA will not emit INSERT statements. +SIMPLE does not use multi-row insert statements. +COLUMNS includes column name lists into insert statements. +If the DROP option is specified, drop statements are created for tables, views, +and sequences. If the block size is set, CLOB and BLOB values larger than this +size are split into separate blocks. +BLOCKSIZE is used when writing out LOB data, and specifies the point at the +values transition from being inserted as inline values, to be inserted using +out-of-line commands. +NOSETTINGS turns off dumping the database settings (the SET XXX commands) + +If no 'TO fileName' clause is specified, the +script is returned as a result set. This command can be used to create a backup +of the database. For long term storage, it is more portable than copying the +database files. + +If a 'TO fileName' clause is specified, then the whole +script (including insert statements) is written to this file, and a result set +without the insert statements is returned. + +The password must be in single quotes; it is case sensitive and can contain spaces. + +This command locks objects while it is running. +Admin rights are required to execute this command. + +When using the TABLE or SCHEMA option, only the selected table(s) / schema(s) are included. +"," +SCRIPT NODATA +" + +"Commands (DML)","SHOW"," +@c@ SHOW { SCHEMAS | TABLES [ FROM schemaName ] | + COLUMNS FROM tableName [ FROM schemaName ] } +"," +Lists the schemas, tables, or the columns of a table. +"," +SHOW TABLES +" + +"Commands (DML)","Explicit table"," +TABLE [schemaName.]tableName +[ ORDER BY selectOrder [,...] ] +[ OFFSET expression { ROW | ROWS } ] +[ FETCH { FIRST | NEXT } [ expression [ PERCENT ] ] { ROW | ROWS } + { ONLY | WITH TIES } ] +"," +Selects data from a table. + +This command is an equivalent to SELECT * FROM tableName. +See [SELECT](https://h2database.com/html/commands.html#select) command for description of ORDER BY, OFFSET, and FETCH. +"," +TABLE TEST; +TABLE TEST ORDER BY ID FETCH FIRST ROW ONLY; +" + +"Commands (DML)","Table value"," +VALUES rowValueExpression [,...] +[ ORDER BY selectOrder [,...] ] +[ OFFSET expression { ROW | ROWS } ] +[ FETCH { FIRST | NEXT } [ expression [ PERCENT ] ] { ROW | ROWS } + { ONLY | WITH TIES } ] +"," +A list of rows that can be used like a table. +See See [SELECT](https://h2database.com/html/commands.html#select) command for description of ORDER BY, OFFSET, and FETCH. +The column list of the resulting table is C1, C2, and so on. +"," +VALUES (1, 'Hello'), (2, 'World'); +" + +"Commands (DML)","WITH"," +WITH [ RECURSIVE ] { name [( columnName [,...] )] AS ( query ) [,...] } +{ query | @h2@ { insert | update | delete | mergeInto | mergeUsing | createTable } } +"," +Can be used to create a recursive or non-recursive query (common table expression). +For recursive queries the first select has to be a UNION. +One or more common table entries can be referred to by name. +Column name declarations are now optional - the column names will be inferred from the named select queries. +The final action in a WITH statement can be a select, insert, update, merge, delete or create table. +"," +WITH RECURSIVE cte(n) AS ( + SELECT 1 + UNION ALL + SELECT n + 1 + FROM cte + WHERE n < 100 +) +SELECT sum(n) FROM cte; + +Example 2: +WITH cte1 AS ( + SELECT 1 AS FIRST_COLUMN +), cte2 AS ( + SELECT FIRST_COLUMN+1 AS FIRST_COLUMN FROM cte1 +) +SELECT sum(FIRST_COLUMN) FROM cte2; +" + +"Commands (DDL)","ALTER DOMAIN"," +ALTER DOMAIN @h2@ [ IF EXISTS ] [schemaName.]domainName +{ SET DEFAULT expression } + | { DROP DEFAULT } + | @h2@ { SET ON UPDATE expression } + | @h2@ { DROP ON UPDATE } +"," +Changes the default or on update expression of a domain. +Schema owner rights are required to execute this command. + +SET DEFAULT changes the default expression of a domain. + +DROP DEFAULT removes the default expression of a domain. +Old expression is copied into domains and columns that use this domain and don't have an own default expression. + +SET ON UPDATE changes the expression that is set on update if value for this domain is not specified in update +statement. + +DROP ON UPDATE removes the expression that is set on update of a column with this domain. +Old expression is copied into domains and columns that use this domain and don't have an own on update expression. + +This command commits an open transaction in this connection. +"," +ALTER DOMAIN D1 SET DEFAULT ''; +ALTER DOMAIN D1 DROP DEFAULT; +ALTER DOMAIN D1 SET ON UPDATE CURRENT_TIMESTAMP; +ALTER DOMAIN D1 DROP ON UPDATE; +" + +"Commands (DDL)","ALTER DOMAIN ADD CONSTRAINT"," +ALTER DOMAIN @h2@ [ IF EXISTS ] [schemaName.]domainName +ADD [ constraintNameDefinition ] +CHECK (condition) @h2@ [ CHECK | NOCHECK ] +"," +Adds a constraint to a domain. +Schema owner rights are required to execute this command. +This command commits an open transaction in this connection. +"," +ALTER DOMAIN D ADD CONSTRAINT D_POSITIVE CHECK (VALUE > 0) +" + +"Commands (DDL)","ALTER DOMAIN DROP CONSTRAINT"," +ALTER DOMAIN @h2@ [ IF EXISTS ] [schemaName.]domainName +DROP CONSTRAINT @h2@ [ IF EXISTS ] [schemaName.]constraintName +"," +Removes a constraint from a domain. +Schema owner rights are required to execute this command. +This command commits an open transaction in this connection. +"," +ALTER DOMAIN D DROP CONSTRAINT D_POSITIVE +" + +"Commands (DDL)","ALTER DOMAIN RENAME"," +@h2@ ALTER DOMAIN [ IF EXISTS ] [schemaName.]domainName RENAME TO newName +"," +Renames a domain. +Schema owner rights are required to execute this command. +This command commits an open transaction in this connection. +"," +ALTER DOMAIN TEST RENAME TO MY_TYPE +" + +"Commands (DDL)","ALTER DOMAIN RENAME CONSTRAINT"," +@h2@ ALTER DOMAIN [ IF EXISTS ] [schemaName.]domainName +@h2@ RENAME CONSTRAINT [schemaName.]oldConstraintName +@h2@ TO newConstraintName +"," +Renames a constraint. +This command commits an open transaction in this connection. +"," +ALTER DOMAIN D RENAME CONSTRAINT FOO TO BAR +" + +"Commands (DDL)","ALTER INDEX RENAME"," +@h2@ ALTER INDEX [ IF EXISTS ] [schemaName.]indexName RENAME TO newIndexName +"," +Renames an index. +This command commits an open transaction in this connection. +"," +ALTER INDEX IDXNAME RENAME TO IDX_TEST_NAME +" + +"Commands (DDL)","ALTER SCHEMA RENAME"," +@h2@ ALTER SCHEMA [ IF EXISTS ] schemaName RENAME TO newSchemaName +"," +Renames a schema. +Schema admin rights are required to execute this command. +This command commits an open transaction in this connection. +"," +ALTER SCHEMA TEST RENAME TO PRODUCTION +" + +"Commands (DDL)","ALTER SEQUENCE"," +ALTER SEQUENCE @h2@ [ IF EXISTS ] [schemaName.]sequenceName alterSequenceOption [...] +"," +Changes the parameters of a sequence. +Schema owner rights are required to execute this command. +This command does not commit the current transaction; however the new value is used by other +transactions immediately, and rolling back this command has no effect. +"," +ALTER SEQUENCE SEQ_ID RESTART WITH 1000 +" + +"Commands (DDL)","ALTER TABLE ADD"," +ALTER TABLE @h2@ [ IF EXISTS ] [schemaName.]tableName ADD [ COLUMN ] +{ @h2@ [ IF NOT EXISTS ] columnName columnDefinition @h2@ [ USING initialValueExpression ] + | @h2@ { ( { columnName columnDefinition | tableConstraintDefinition } [,...] ) } } +@h2@ [ { { BEFORE | AFTER } columnName } | FIRST ] +"," +Adds a new column to a table. +This command commits an open transaction in this connection. + +If USING is specified the provided expression is used to generate initial value of the new column for each row. +The expression may reference existing columns of the table. +Otherwise the DEFAULT expression is used, if any. +If neither USING nor DEFAULT are specified, the NULL is used. +"," +ALTER TABLE TEST ADD CREATEDATE TIMESTAMP +" + +"Commands (DDL)","ALTER TABLE ADD CONSTRAINT"," +ALTER TABLE @h2@ [ IF EXISTS ] tableName ADD tableConstraintDefinition @h2@ [ CHECK | NOCHECK ] +"," +Adds a constraint to a table. If NOCHECK is specified, existing rows are not +checked for consistency (the default is to check consistency for existing rows). +The required indexes are automatically created if they don't exist yet. +It is not possible to disable checking for unique constraints. +This command commits an open transaction in this connection. +"," +ALTER TABLE TEST ADD CONSTRAINT NAME_UNIQUE UNIQUE(NAME) +" + +"Commands (DDL)","ALTER TABLE RENAME CONSTRAINT"," +@h2@ ALTER TABLE [ IF EXISTS ] [schemaName.]tableName +@h2@ RENAME CONSTRAINT [schemaName.]oldConstraintName +@h2@ TO newConstraintName +"," +Renames a constraint. +This command commits an open transaction in this connection. +"," +ALTER TABLE TEST RENAME CONSTRAINT FOO TO BAR +" + +"Commands (DDL)","ALTER TABLE ALTER COLUMN"," +ALTER TABLE @h2@ [ IF EXISTS ] [schemaName.]tableName +ALTER COLUMN @h2@ [ IF EXISTS ] columnName +{ @h2@ { columnDefinition } + | @h2@ { RENAME TO name } + | SET GENERATED { ALWAYS | BY DEFAULT } [ alterIdentityColumnOption [...] ] + | alterIdentityColumnOption [...] + | DROP IDENTITY + | @h2@ { SELECTIVITY int } + | { SET DEFAULT expression } + | { DROP DEFAULT } + | DROP EXPRESSION + | @h2@ { SET ON UPDATE expression } + | @h2@ { DROP ON UPDATE } + | @h2@ { SET DEFAULT ON NULL } + | @h2@ { DROP DEFAULT ON NULL } + | { SET NOT NULL } + | { DROP NOT NULL } | @c@ { SET NULL } + | { SET DATA TYPE dataTypeOrDomain @h2@ [ USING newValueExpression ] } + | @h2@ { SET { VISIBLE | INVISIBLE } } } +"," +Changes the data type of a column, rename a column, +change the identity value, or change the selectivity. + +Changing the data type fails if the data can not be converted. + +SET GENERATED ALWAYS, SET GENERATED BY DEFAULT, or identity options convert the column into identity column +(if it wasn't an identity column) and set new values of specified options for its sequence. + +DROP IDENTITY removes identity status of a column. + +SELECTIVITY sets the selectivity (1-100) for a column. +Setting the selectivity to 0 means the default value. +Selectivity is used by the cost based optimizer to calculate the estimated cost of an index. +Selectivity 100 means values are unique, 10 means every distinct value appears 10 times on average. + +SET DEFAULT changes the default value of a column. +This command doesn't affect generated and identity columns. + +DROP DEFAULT removes the default value of a column. + +DROP EXPRESSION converts generated column into base column. + +SET ON UPDATE changes the value that is set on update if value for this column is not specified in update statement. +This command doesn't affect generated and identity columns. + +DROP ON UPDATE removes the value that is set on update of a column. + +SET DEFAULT ON NULL makes NULL value work as DEFAULT value is assignments to this column. + +DROP DEFAULT ON NULL makes NULL value work as NULL value in assignments to this column. + +SET NOT NULL sets a column to not allow NULL. Rows may not contain NULL in this column. + +DROP NOT NULL and SET NULL set a column to allow NULL. +The column may not be part of a primary key and may not be an identity column. + +SET DATA TYPE changes the data type of a column, for each row old value is converted to this data type +unless USING is specified with a custom expression. +USING expression may reference previous value of the modified column by its name and values of other columns. + +SET INVISIBLE makes the column hidden, i.e. it will not appear in SELECT * results. +SET VISIBLE has the reverse effect. + +This command commits an open transaction in this connection. +"," +ALTER TABLE TEST ALTER COLUMN NAME CLOB; +ALTER TABLE TEST ALTER COLUMN NAME RENAME TO TEXT; +ALTER TABLE TEST ALTER COLUMN ID RESTART WITH 10000; +ALTER TABLE TEST ALTER COLUMN NAME SELECTIVITY 100; +ALTER TABLE TEST ALTER COLUMN NAME SET DEFAULT ''; +ALTER TABLE TEST ALTER COLUMN NAME SET NOT NULL; +ALTER TABLE TEST ALTER COLUMN NAME SET NULL; +ALTER TABLE TEST ALTER COLUMN NAME SET VISIBLE; +ALTER TABLE TEST ALTER COLUMN NAME SET INVISIBLE; +" + +"Commands (DDL)","ALTER TABLE DROP COLUMN"," +ALTER TABLE @h2@ [ IF EXISTS ] [schemaName.]tableName +DROP [ COLUMN ] @h2@ [ IF EXISTS ] +@h2@ { ( columnName [,...] ) } | columnName @c@ [,...] +"," +Removes column(s) from a table. +This command commits an open transaction in this connection. +"," +ALTER TABLE TEST DROP COLUMN NAME +ALTER TABLE TEST DROP COLUMN (NAME1, NAME2) +" + +"Commands (DDL)","ALTER TABLE DROP CONSTRAINT"," +ALTER TABLE @h2@ [ IF EXISTS ] [schemaName.]tableName DROP +CONSTRAINT @h2@ [ IF EXISTS ] [schemaName.]constraintName [ RESTRICT | CASCADE ] | @c@ { PRIMARY KEY } +"," +Removes a constraint or a primary key from a table. +If CASCADE is specified, unique or primary key constraint is dropped together with all +referential constraints that reference the specified constraint. +This command commits an open transaction in this connection. +"," +ALTER TABLE TEST DROP CONSTRAINT UNIQUE_NAME RESTRICT +" + +"Commands (DDL)","ALTER TABLE SET"," +@h2@ ALTER TABLE [ IF EXISTS ] [schemaName.]tableName +SET REFERENTIAL_INTEGRITY +@h2@ { FALSE | TRUE } @h2@ [ CHECK | NOCHECK ] +"," +Disables or enables referential integrity checking for a table. This command can +be used inside a transaction. Enabling referential integrity does not check +existing data, except if CHECK is specified. Use SET REFERENTIAL_INTEGRITY to +disable it for all tables; the global flag and the flag for each table are +independent. + +This command commits an open transaction in this connection. +"," +ALTER TABLE TEST SET REFERENTIAL_INTEGRITY FALSE +" + +"Commands (DDL)","ALTER TABLE RENAME"," +@h2@ ALTER TABLE [ IF EXISTS ] [schemaName.]tableName RENAME TO newName +"," +Renames a table. +This command commits an open transaction in this connection. +"," +ALTER TABLE TEST RENAME TO MY_DATA +" + +"Commands (DDL)","ALTER USER ADMIN"," +@h2@ ALTER USER userName ADMIN { TRUE | FALSE } +"," +Switches the admin flag of a user on or off. + +Only unquoted or uppercase user names are allowed. +Admin rights are required to execute this command. +This command commits an open transaction in this connection. +"," +ALTER USER TOM ADMIN TRUE +" + +"Commands (DDL)","ALTER USER RENAME"," +@h2@ ALTER USER userName RENAME TO newUserName +"," +Renames a user. +After renaming a user, the password becomes invalid and needs to be changed as well. + +Only unquoted or uppercase user names are allowed. +Admin rights are required to execute this command. +This command commits an open transaction in this connection. +"," +ALTER USER TOM RENAME TO THOMAS +" + +"Commands (DDL)","ALTER USER SET PASSWORD"," +@h2@ ALTER USER userName SET { PASSWORD string | SALT bytes HASH bytes } +"," +Changes the password of a user. +Only unquoted or uppercase user names are allowed. +The password must be enclosed in single quotes. It is case sensitive +and can contain spaces. The salt and hash values are hex strings. + +Admin rights are required to execute this command. +This command commits an open transaction in this connection. +"," +ALTER USER SA SET PASSWORD 'rioyxlgt' +" + +"Commands (DDL)","ALTER VIEW RECOMPILE"," +@h2@ ALTER VIEW [ IF EXISTS ] [schemaName.]viewName RECOMPILE +"," +Recompiles a view after the underlying tables have been changed or created. +Schema owner rights are required to execute this command. +This command is used for views created using CREATE FORCE VIEW. +This command commits an open transaction in this connection. +"," +ALTER VIEW ADDRESS_VIEW RECOMPILE +" + +"Commands (DDL)","ALTER VIEW RENAME"," +@h2@ ALTER VIEW [ IF EXISTS ] [schemaName.]viewName RENAME TO newName +"," +Renames a view. +Schema owner rights are required to execute this command. +This command commits an open transaction in this connection. +"," +ALTER VIEW TEST RENAME TO MY_VIEW +" + +"Commands (DDL)","ANALYZE"," +@h2@ ANALYZE [ TABLE [schemaName.]tableName ] [ SAMPLE_SIZE rowCountInt ] +"," +Updates the selectivity statistics of tables. +If no table name is given, all tables are analyzed. +The selectivity is used by the +cost based optimizer to select the best index for a given query. If no sample +size is set, up to 10000 rows per table are read. The value 0 means all rows are +read. The selectivity can be set manually using ALTER TABLE ALTER COLUMN +SELECTIVITY. Manual values are overwritten by this statement. The selectivity is +available in the INFORMATION_SCHEMA.COLUMNS table. + +This command commits an open transaction in this connection. +"," +ANALYZE SAMPLE_SIZE 1000 +" + +"Commands (DDL)","COMMENT ON"," +@h2@ COMMENT ON +@h2@ { { COLUMN [schemaName.]tableName.columnName } + | { { TABLE | VIEW | CONSTANT | CONSTRAINT | ALIAS | INDEX | ROLE + | SCHEMA | SEQUENCE | TRIGGER | USER | DOMAIN } [schemaName.]objectName } } +@h2@ IS expression +"," +Sets the comment of a database object. Use NULL or empty string to remove the comment. + +Admin rights are required to execute this command if object is a USER or ROLE. +Schema owner rights are required to execute this command for all other types of objects. +This command commits an open transaction in this connection. +"," +COMMENT ON TABLE TEST IS 'Table used for testing' +" + +"Commands (DDL)","CREATE AGGREGATE"," +@h2@ CREATE AGGREGATE [ IF NOT EXISTS ] [schemaName.]aggregateName FOR classNameString +"," +Creates a new user-defined aggregate function. The method name must be the full +qualified class name. The class must implement the interface +""org.h2.api.Aggregate"" or ""org.h2.api.AggregateFunction"". + +Admin rights are required to execute this command. +This command commits an open transaction in this connection. +"," +CREATE AGGREGATE SIMPLE_MEDIAN FOR 'com.acme.db.Median' +" + +"Commands (DDL)","CREATE ALIAS"," +@h2@ CREATE ALIAS [ IF NOT EXISTS ] [schemaName.]functionAliasName +@h2@ [ DETERMINISTIC ] +@h2@ { FOR classAndMethodString | AS sourceCodeString } +"," +Creates a new function alias. If this is a ResultSet returning function, +by default the return value is cached in a local temporary file. + +DETERMINISTIC - Deterministic functions must always return the same value for the same parameters. + +The method name must be the full qualified class and method name, +and may optionally include the parameter classes as in +""java.lang.Integer.parseInt(java.lang.String, int)"". The class and the method +must both be public, and the method must be static. The class must be available +in the classpath of the database engine (when using the server mode, +it must be in the classpath of the server). + +When defining a function alias with source code, the Sun ""javac"" is compiler +is used if the file ""tools.jar"" is in the classpath. If not, ""javac"" is run as a separate process. +Only the source code is stored in the database; the class is compiled each time +the database is re-opened. Source code is usually passed +as dollar quoted text to avoid escaping problems. If import statements are used, +then the tag @CODE must be added before the method. + +If the method throws an SQLException, it is directly re-thrown to the calling application; +all other exceptions are first converted to a SQLException. + +If the first parameter of the Java function is a ""java.sql.Connection"", then a +connection to the database is provided. This connection must not be closed. +If the class contains multiple methods with the given name but different +parameter count, all methods are mapped. + +Admin rights are required to execute this command. +This command commits an open transaction in this connection. + +If you have the Groovy jar in your classpath, it is also possible to write methods using Groovy. +"," +CREATE ALIAS MY_SQRT FOR 'java.lang.Math.sqrt'; +CREATE ALIAS MY_ROUND FOR 'java.lang.Math.round(double)'; +CREATE ALIAS GET_SYSTEM_PROPERTY FOR 'java.lang.System.getProperty'; +CALL GET_SYSTEM_PROPERTY('java.class.path'); +CALL GET_SYSTEM_PROPERTY('com.acme.test', 'true'); +CREATE ALIAS REVERSE AS 'String reverse(String s) { return new StringBuilder(s).reverse().toString(); }'; +CALL REVERSE('Test'); +CREATE ALIAS tr AS '@groovy.transform.CompileStatic + static String tr(String str, String sourceSet, String replacementSet){ + return str.tr(sourceSet, replacementSet); + } +' +" + +"Commands (DDL)","CREATE CONSTANT"," +@h2@ CREATE CONSTANT [ IF NOT EXISTS ] [schemaName.]constantName +VALUE expression +"," +Creates a new constant. +Schema owner rights are required to execute this command. +This command commits an open transaction in this connection. +"," +CREATE CONSTANT ONE VALUE 1 +" + +"Commands (DDL)","CREATE DOMAIN"," +CREATE DOMAIN @h2@ [ IF NOT EXISTS ] [schemaName.]domainName +[ AS ] dataTypeOrDomain +[ DEFAULT expression ] +@h2@ [ ON UPDATE expression ] +@h2@ [ COMMENT expression ] +[ CHECK (condition) ] [...] +"," +Creates a new domain to define a set of permissible values. +Schema owner rights are required to execute this command. +Domains can be used as data types. +The domain constraints must evaluate to TRUE or to UNKNOWN. +In the conditions, the term VALUE refers to the value being tested. + +This command commits an open transaction in this connection. +"," +CREATE DOMAIN EMAIL AS VARCHAR(255) CHECK (POSITION('@', VALUE) > 1) +" + +"Commands (DDL)","CREATE INDEX"," +@h2@ CREATE [ UNIQUE | SPATIAL ] INDEX +@h2@ [ [ IF NOT EXISTS ] [schemaName.]indexName ] +@h2@ ON [schemaName.]tableName ( indexColumn [,...] ) +@h2@ [ INCLUDE ( indexColumn [,...] ) ] +"," +Creates a new index. +This command commits an open transaction in this connection. + +INCLUDE clause may only be specified for UNIQUE indexes. +With this clause additional columns are included into index, but aren't used in unique checks. + +Spatial indexes are supported only on GEOMETRY columns. +They may contain only one column and are used by the +[spatial overlapping operator](https://h2database.com/html/grammar.html#compare). +"," +CREATE INDEX IDXNAME ON TEST(NAME) +" + +"Commands (DDL)","CREATE LINKED TABLE"," +@h2@ CREATE [ FORCE ] [ [ GLOBAL | LOCAL ] TEMPORARY ] +@h2@ LINKED TABLE [ IF NOT EXISTS ] +@h2@ [schemaName.]tableName ( driverString, urlString, userString, passwordString, +@h2@ [ originalSchemaString, ] @h2@ originalTableString ) +@h2@ [ EMIT UPDATES | READONLY ] [ FETCH_SIZE sizeInt] [AUTOCOMMIT ON|OFF] +"," +Creates a table link to an external table. The driver name may be empty if the +driver is already loaded. If the schema name is not set, only one table with +that name may exist in the target database. + +FORCE - Create the LINKED TABLE even if the remote database/table does not exist. + +EMIT UPDATES - Usually, for update statements, the old rows are deleted first and then the new +rows are inserted. It is possible to emit update statements (except on +rollback), however in this case multi-row unique key updates may not always +work. Linked tables to the same database share one connection. + +READONLY - is set, the remote table may not be updated. This is enforced by H2. + +FETCH_SIZE - the number of rows fetched, a hint with non-negative number of rows to fetch from the external table +at once, may be ignored by the driver of external database. 0 is default and means no hint. +The value is passed to ""java.sql.Statement.setFetchSize()"" method. + +AUTOCOMMIT - is set to ON, the auto-commit mode is enable. OFF is disable. +The value is passed to ""java.sql.Connection.setAutoCommit()"" method. + +If the connection to the source database is lost, the connection is re-opened +(this is a workaround for MySQL that disconnects after 8 hours of inactivity by default). + +If a query is used instead of the original table name, the table is read only. +Queries must be enclosed in parenthesis: ""(SELECT * FROM ORDERS)"". + +To use JNDI to get the connection, the driver class must be a +javax.naming.Context (for example ""javax.naming.InitialContext""), and the URL must +be the resource name (for example ""java:comp/env/jdbc/Test""). + +Admin rights are required to execute this command. +This command commits an open transaction in this connection. +"," +CREATE LINKED TABLE LINK('org.h2.Driver', 'jdbc:h2:./test2', + 'sa', 'sa', 'TEST'); +CREATE LINKED TABLE LINK('', 'jdbc:h2:./test2', 'sa', 'sa', + '(SELECT * FROM TEST WHERE ID>0)'); +CREATE LINKED TABLE LINK('javax.naming.InitialContext', + 'java:comp/env/jdbc/Test', NULL, NULL, + '(SELECT * FROM TEST WHERE ID>0)'); +" + +"Commands (DDL)","CREATE ROLE"," +CREATE ROLE @h2@ [ IF NOT EXISTS ] newRoleName +"," +Creates a new role. +This command commits an open transaction in this connection. +"," +CREATE ROLE READONLY +" + +"Commands (DDL)","CREATE SCHEMA"," +CREATE SCHEMA @h2@ [ IF NOT EXISTS ] +{ name [ AUTHORIZATION ownerName ] | [ AUTHORIZATION ownerName ] } +@h2@ [ WITH tableEngineParamName [,...] ] +"," +Creates a new schema. +Schema admin rights are required to execute this command. + +If schema name is not specified, the owner name is used as a schema name. +If schema name is specified, but no owner is specified, the current user is used as an owner. + +Schema owners can create, rename, and drop objects in the schema. +Schema owners can drop the schema itself, but cannot rename it. +Some objects may still require admin rights for their creation, +see documentation of their CREATE statements for details. + +Optional table engine parameters are used when CREATE TABLE command +is run on this schema without having its engine params set. + +This command commits an open transaction in this connection. +"," +CREATE SCHEMA TEST_SCHEMA AUTHORIZATION SA +" + +"Commands (DDL)","CREATE SEQUENCE"," +CREATE SEQUENCE @h2@ [ IF NOT EXISTS ] [schemaName.]sequenceName +[ { AS dataType | sequenceOption } [...] ] +"," +Creates a new sequence. +Schema owner rights are required to execute this command. + +The data type of a sequence must be a numeric type, the default is BIGINT. +Sequence can produce only integer values. +For TINYINT the allowed values are between -128 and 127. +For SMALLINT the allowed values are between -32768 and 32767. +For INTEGER the allowed values are between -2147483648 and 2147483647. +For BIGINT the allowed values are between -9223372036854775808 and 9223372036854775807. +For NUMERIC and DECFLOAT the allowed values depend on precision, +but cannot exceed the range of BIGINT data type (from -9223372036854775808 to 9223372036854775807); +the scale of NUMERIC must be 0. +For REAL the allowed values are between -16777216 and 16777216. +For DOUBLE PRECISION the allowed values are between -9007199254740992 and 9007199254740992. + +Used values are never re-used, even when the transaction is rolled back. + +This command commits an open transaction in this connection. +"," +CREATE SEQUENCE SEQ_ID; +CREATE SEQUENCE SEQ2 AS INTEGER START WITH 10; +" + +"Commands (DDL)","CREATE TABLE"," +CREATE @h2@ [ CACHED | MEMORY ] [ @c@ { TEMP } | [ GLOBAL | LOCAL ] TEMPORARY ] +TABLE @h2@ [ IF NOT EXISTS ] [schemaName.]tableName +[ ( { columnName [columnDefinition] | tableConstraintDefinition } [,...] ) ] +@h2@ [ ENGINE tableEngineName ] +@h2@ [ WITH tableEngineParamName [,...] ] +@h2@ [ NOT PERSISTENT ] @h2@ [ TRANSACTIONAL ] +[ AS query [ WITH [ NO ] DATA ] ]"," +Creates a new table. + +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, +but the index data is kept in main memory, +that means memory tables should not get too large. + +Temporary tables are deleted when closing or opening a database. +Temporary tables can be global (accessible by all connections) +or local (only accessible by the current connection). +The default for temporary tables is global. +Indexes of temporary tables are kept fully in main memory, +unless the temporary table is created using CREATE CACHED TABLE. + +The ENGINE option is only required when custom table implementations are used. +The table engine class must implement the interface ""org.h2.api.TableEngine"". +Any table engine parameters are passed down in the tableEngineParams field of the CreateTableData object. + +Either ENGINE, or WITH (table engine params), or both may be specified. If ENGINE is not specified +in CREATE TABLE, then the engine specified by DEFAULT_TABLE_ENGINE option of database params is used. + +Tables with the NOT PERSISTENT modifier are kept fully in memory, and all +rows are lost when the database is closed. + +The column definitions are optional if a query is specified. +In that case the column list of the query is used. +If the query is specified its results are inserted into created table unless WITH NO DATA is specified. + +This command commits an open transaction, except when using +TRANSACTIONAL (only supported for temporary tables). +"," +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255)) +" + +"Commands (DDL)","CREATE TRIGGER"," +CREATE TRIGGER @h2@ [ IF NOT EXISTS ] [schemaName.]triggerName +{ BEFORE | AFTER | INSTEAD OF } +{ INSERT | UPDATE | DELETE | @h2@ { SELECT | ROLLBACK } } +@h2@ [,...] ON [schemaName.]tableName [ FOR EACH { ROW | STATEMENT } ] +@c@ [ QUEUE int ] @h2@ [ NOWAIT ] +@h2@ { CALL triggeredClassNameString | AS sourceCodeString } +"," +Creates a new trigger. +Admin rights are required to execute this command. + +The trigger class must be public and implement ""org.h2.api.Trigger"". +Inner classes are not supported. +The class must be available in the classpath of the database engine +(when using the server mode, it must be in the classpath of the server). + +The sourceCodeString must define a single method with no parameters that returns ""org.h2.api.Trigger"". +See CREATE ALIAS for requirements regarding the compilation. +Alternatively, javax.script.ScriptEngineManager can be used to create an instance of ""org.h2.api.Trigger"". +Currently javascript (included in every JRE) and ruby (with JRuby) are supported. +In that case the source must begin respectively with ""//javascript"" or ""#ruby"". + +BEFORE triggers are called after data conversion is made, default values are set, +null and length constraint checks have been made; +but before other constraints have been checked. +If there are multiple triggers, the order in which they are called is undefined. + +ROLLBACK can be specified in combination with INSERT, UPDATE, and DELETE. +Only row based AFTER trigger can be called on ROLLBACK. +Exceptions that occur within such triggers are ignored. +As the operations that occur within a trigger are part of the transaction, +ROLLBACK triggers are only required if an operation communicates outside of the database. + +INSTEAD OF triggers are implicitly row based and behave like BEFORE triggers. +Only the first such trigger is called. Such triggers on views are supported. +They can be used to make views updatable. +These triggers on INSERT and UPDATE must update the passed new row to values that were actually inserted +by the trigger; they are used for [FINAL TABLE](https://h2database.com/html/grammar.html#data_change_delta_table) +and for retrieval of generated keys. + +A BEFORE SELECT trigger is fired just before the database engine tries to read from the table. +The trigger can be used to update a table on demand. +The trigger is called with both 'old' and 'new' set to null. + +The MERGE statement will call both INSERT and UPDATE triggers. +Not supported are SELECT triggers with the option FOR EACH ROW, +and AFTER SELECT triggers. + +Committing or rolling back a transaction within a trigger is not allowed, except for SELECT triggers. + +By default a trigger is called once for each statement, without the old and new rows. +FOR EACH ROW triggers are called once for each inserted, updated, or deleted row. + +QUEUE is implemented for syntax compatibility with HSQL and has no effect. + +The trigger need to be created in the same schema as the table. +The schema name does not need to be specified when creating the trigger. + +This command commits an open transaction in this connection. +"," +CREATE TRIGGER TRIG_INS BEFORE INSERT ON TEST FOR EACH ROW CALL 'MyTrigger'; +CREATE TRIGGER TRIG_SRC BEFORE INSERT ON TEST AS + 'org.h2.api.Trigger create() { return new MyTrigger(""constructorParam""); }'; +CREATE TRIGGER TRIG_JS BEFORE INSERT ON TEST AS '//javascript +return new Packages.MyTrigger(""constructorParam"");'; +CREATE TRIGGER TRIG_RUBY BEFORE INSERT ON TEST AS '#ruby +Java::MyPackage::MyTrigger.new(""constructorParam"")'; +" +"Commands (DDL)","CREATE USER"," +@h2@ CREATE USER [ IF NOT EXISTS ] newUserName +@h2@ { PASSWORD string | SALT bytes HASH bytes } @h2@ [ ADMIN ] +"," +Creates a new user. For compatibility, only unquoted or uppercase user names are allowed. +The password must be in single quotes. It is case sensitive and can contain spaces. +The salt and hash values are hex strings. + +Admin rights are required to execute this command. +This command commits an open transaction in this connection. +"," +CREATE USER GUEST PASSWORD 'abc' +" + +"Commands (DDL)","CREATE VIEW"," +CREATE @h2@ [ OR REPLACE ] @h2@ [ FORCE ] +VIEW @h2@ [ IF NOT EXISTS ] [schemaName.]viewName +[ ( columnName [,...] ) ] AS query +"," +Creates a new view. If the force option is used, then the view is created even +if the underlying table(s) don't exist. +Schema owner rights are required to execute this command. + +If the OR REPLACE clause is used an existing view will be replaced, and any +dependent views will not need to be recreated. If dependent views will become +invalid as a result of the change an error will be generated, but this error +can be ignored if the FORCE clause is also used. + +Views are not updatable except when using 'instead of' triggers. + +This command commits an open transaction in this connection. +"," +CREATE VIEW TEST_VIEW AS SELECT * FROM TEST WHERE ID < 100 +" + +"Commands (DDL)","DROP AGGREGATE"," +@h2@ DROP AGGREGATE [ IF EXISTS ] aggregateName +"," +Drops an existing user-defined aggregate function. +Schema owner rights are required to execute this command. + +This command commits an open transaction in this connection. +"," +DROP AGGREGATE SIMPLE_MEDIAN +" + +"Commands (DDL)","DROP ALIAS"," +@h2@ DROP ALIAS [ IF EXISTS ] [schemaName.]aliasName +"," +Drops an existing function alias. +Schema owner rights are required to execute this command. + +This command commits an open transaction in this connection. +"," +DROP ALIAS MY_SQRT +" + +"Commands (DDL)","DROP ALL OBJECTS"," +@h2@ DROP ALL OBJECTS [ DELETE FILES ] +"," +Drops all existing views, tables, sequences, schemas, function aliases, roles, +user-defined aggregate functions, domains, and users (except the current user). +If DELETE FILES is specified, the database files will be removed when the last +user disconnects from the database. Warning: this command can not be rolled +back. + +Admin rights are required to execute this command. +"," +DROP ALL OBJECTS +" + +"Commands (DDL)","DROP CONSTANT"," +@h2@ DROP CONSTANT [ IF EXISTS ] [schemaName.]constantName +"," +Drops a constant. +Schema owner rights are required to execute this command. +This command commits an open transaction in this connection. +"," +DROP CONSTANT ONE +" + +"Commands (DDL)","DROP DOMAIN"," +DROP DOMAIN @h2@ [ IF EXISTS ] [schemaName.]domainName [ RESTRICT | CASCADE ] +"," +Drops a data type (domain). +Schema owner rights are required to execute this command. + +The command will fail if it is referenced by a column or another domain (the default). +Column descriptors are replaced with original definition of specified domain if the CASCADE clause is used. +Default and on update expressions are copied into domains and columns that use this domain and don't have own +expressions. Domain constraints are copied into domains that use this domain and to columns (as check constraints) that +use this domain. +This command commits an open transaction in this connection. +"," +DROP DOMAIN EMAIL +" + +"Commands (DDL)","DROP INDEX"," +@h2@ DROP INDEX [ IF EXISTS ] [schemaName.]indexName +"," +Drops an index. +This command commits an open transaction in this connection. +"," +DROP INDEX IF EXISTS IDXNAME +" + +"Commands (DDL)","DROP ROLE"," +DROP ROLE @h2@ [ IF EXISTS ] roleName +"," +Drops a role. +Admin rights are required to execute this command. +This command commits an open transaction in this connection. +"," +DROP ROLE READONLY +" + +"Commands (DDL)","DROP SCHEMA"," +DROP SCHEMA @h2@ [ IF EXISTS ] schemaName [ RESTRICT | CASCADE ] +"," +Drops a schema. +Schema owner rights are required to execute this command. +The command will fail if objects in this schema exist and the RESTRICT clause is used (the default). +All objects in this schema are dropped as well if the CASCADE clause is used. +This command commits an open transaction in this connection. +"," +DROP SCHEMA TEST_SCHEMA +" + +"Commands (DDL)","DROP SEQUENCE"," +DROP SEQUENCE @h2@ [ IF EXISTS ] [schemaName.]sequenceName +"," +Drops a sequence. +Schema owner rights are required to execute this command. +This command commits an open transaction in this connection. +"," +DROP SEQUENCE SEQ_ID +" + +"Commands (DDL)","DROP TABLE"," +DROP TABLE @h2@ [ IF EXISTS ] [schemaName.]tableName @h2@ [,...] +[ RESTRICT | CASCADE ] +"," +Drops an existing table, or a list of tables. +The command will fail if dependent objects exist and the RESTRICT clause is used (the default). +All dependent views and constraints are dropped as well if the CASCADE clause is used. +This command commits an open transaction in this connection. +"," +DROP TABLE TEST +" + +"Commands (DDL)","DROP TRIGGER"," +DROP TRIGGER @h2@ [ IF EXISTS ] [schemaName.]triggerName +"," +Drops an existing trigger. +This command commits an open transaction in this connection. +"," +DROP TRIGGER TRIG_INS +" + +"Commands (DDL)","DROP USER"," +@h2@ DROP USER [ IF EXISTS ] userName +"," +Drops a user. The current user cannot be dropped. +For compatibility, only unquoted or uppercase user names are allowed. + +Admin rights are required to execute this command. +This command commits an open transaction in this connection. +"," +DROP USER TOM +" + +"Commands (DDL)","DROP VIEW"," +DROP VIEW @h2@ [ IF EXISTS ] [schemaName.]viewName [ RESTRICT | CASCADE ] +"," +Drops an existing view. +Schema owner rights are required to execute this command. +All dependent views are dropped as well if the CASCADE clause is used (the default). +The command will fail if dependent views exist and the RESTRICT clause is used. +This command commits an open transaction in this connection. +"," +DROP VIEW TEST_VIEW +" + +"Commands (DDL)","TRUNCATE TABLE"," +TRUNCATE TABLE [schemaName.]tableName [ [ CONTINUE | RESTART ] IDENTITY ] +"," +Removes all rows from a table. +Unlike DELETE FROM without where clause, this command can not be rolled back. +This command is faster than DELETE without where clause. +Only regular data tables without foreign key constraints can be truncated +(except if referential integrity is disabled for this database or for this table). +Linked tables can't be truncated. +If RESTART IDENTITY is specified next values for identity columns are restarted. + +This command commits an open transaction in this connection. +"," +TRUNCATE TABLE TEST +" + +"Commands (Other)","CHECKPOINT"," +@h2@ CHECKPOINT +"," +Flushes the data to disk. + +Admin rights are required to execute this command. +"," +CHECKPOINT +" + +"Commands (Other)","CHECKPOINT SYNC"," +@h2@ CHECKPOINT SYNC +"," +Flushes the data to disk and forces all system buffers be written +to the underlying device. + +Admin rights are required to execute this command. +"," +CHECKPOINT SYNC +" + +"Commands (Other)","COMMIT"," +COMMIT [ WORK ] +"," +Commits a transaction. +"," +COMMIT +" + +"Commands (Other)","COMMIT TRANSACTION"," +@h2@ COMMIT TRANSACTION transactionName +"," +Sets the resolution of an in-doubt transaction to 'commit'. + +Admin rights are required to execute this command. +This command is part of the 2-phase-commit protocol. +"," +COMMIT TRANSACTION XID_TEST +" + +"Commands (Other)","GRANT RIGHT"," +GRANT { { SELECT | INSERT | UPDATE | DELETE } [,..] | ALL [ PRIVILEGES ] } ON +{ @h2@ { SCHEMA schemaName } | { [ TABLE ] [schemaName.]tableName @h2@ [,...] } } +TO { PUBLIC | userName | roleName } +"," +Grants rights for a table to a user or role. + +Schema owner rights are required to execute this command. +This command commits an open transaction in this connection. +"," +GRANT SELECT ON TEST TO READONLY +" + +"Commands (Other)","GRANT ALTER ANY SCHEMA"," +@h2@ GRANT ALTER ANY SCHEMA TO userName +"," +Grant schema admin rights to a user. + +Schema admin can create, rename, or drop schemas and also has schema owner rights in every schema. + +Admin rights are required to execute this command. +This command commits an open transaction in this connection. +"," +GRANT ALTER ANY SCHEMA TO Bob +" + +"Commands (Other)","GRANT ROLE"," +GRANT { roleName [,...] } TO { PUBLIC | userName | roleName } +"," +Grants a role to a user or role. + +Admin rights are required to execute this command. +This command commits an open transaction in this connection. +"," +GRANT READONLY TO PUBLIC +" + +"Commands (Other)","HELP"," +@h2@ HELP [ anything [...] ] +"," +Displays the help pages of SQL commands or keywords. +"," +HELP SELECT +" + +"Commands (Other)","PREPARE COMMIT"," +@h2@ PREPARE COMMIT newTransactionName +"," +Prepares committing a transaction. +This command is part of the 2-phase-commit protocol. +"," +PREPARE COMMIT XID_TEST +" + +"Commands (Other)","REVOKE RIGHT"," +REVOKE { { SELECT | INSERT | UPDATE | DELETE } [,..] | ALL [ PRIVILEGES ] } ON +{ @h2@ { SCHEMA schemaName } | { [ TABLE ] [schemaName.]tableName @h2@ [,...] } } +FROM { PUBLIC | userName | roleName } +"," +Removes rights for a table from a user or role. + +Schema owner rights are required to execute this command. +This command commits an open transaction in this connection. +"," +REVOKE SELECT ON TEST FROM READONLY +" + +"Commands (Other)","REVOKE ALTER ANY SCHEMA"," +@h2@ REVOKE ALTER ANY SCHEMA FROM userName +"," +Removes schema admin rights from a user. + +Admin rights are required to execute this command. +This command commits an open transaction in this connection. +"," +GRANT ALTER ANY SCHEMA TO Bob +" + +"Commands (Other)","REVOKE ROLE"," +REVOKE { roleName [,...] } FROM { PUBLIC | userName | roleName } +"," +Removes a role from a user or role. + +Admin rights are required to execute this command. +This command commits an open transaction in this connection. +"," +REVOKE READONLY FROM TOM +" + +"Commands (Other)","ROLLBACK"," +ROLLBACK [ WORK ] [ TO SAVEPOINT savepointName ] +"," +Rolls back a transaction. If a savepoint name is used, the transaction is only +rolled back to the specified savepoint. +"," +ROLLBACK +" + +"Commands (Other)","ROLLBACK TRANSACTION"," +@h2@ ROLLBACK TRANSACTION transactionName +"," +Sets the resolution of an in-doubt transaction to 'rollback'. + +Admin rights are required to execute this command. +This command is part of the 2-phase-commit protocol. +"," +ROLLBACK TRANSACTION XID_TEST +" + +"Commands (Other)","SAVEPOINT"," +SAVEPOINT savepointName +"," +Create a new savepoint. See also ROLLBACK. +Savepoints are only valid until the transaction is committed or rolled back. +"," +SAVEPOINT HALF_DONE +" + +"Commands (Other)","SET @"," +@h2@ SET @variableName [ = ] expression +"," +Updates a user-defined variable. +Variables are not persisted and session scoped, that means only visible from within the session in which they are defined. +This command does not commit a transaction, and rollback does not affect it. +"," +SET @TOTAL=0 +" + +"Commands (Other)","SET ALLOW_LITERALS"," +@h2@ SET ALLOW_LITERALS { NONE | ALL | NUMBERS } +"," +This setting can help solve the SQL injection problem. By default, text and +number literals are allowed in SQL statements. However, this enables SQL +injection if the application dynamically builds SQL statements. SQL injection is +not possible if user data is set using parameters ('?'). + +NONE means literals of any kind are not allowed, only parameters and constants +are allowed. NUMBERS mean only numerical and boolean literals are allowed. ALL +means all literals are allowed (default). + +See also CREATE CONSTANT. + +Admin rights are required to execute this command, as it affects all connections. +This command commits an open transaction in this connection. +This setting is persistent. +This setting can be appended to the database URL: ""jdbc:h2:./test;ALLOW_LITERALS=NONE"" +"," +SET ALLOW_LITERALS NONE +" + +"Commands (Other)","SET AUTOCOMMIT"," +@h2@ SET AUTOCOMMIT { TRUE | ON | FALSE | OFF } +"," +Switches auto commit on or off. +This setting can be appended to the database URL: ""jdbc:h2:./test;AUTOCOMMIT=OFF"" - +however this will not work as expected when using a connection pool +(the connection pool manager will re-enable autocommit when returning +the connection to the pool, so autocommit will only be disabled the first +time the connection is used. +"," +SET AUTOCOMMIT OFF +" + +"Commands (Other)","SET CACHE_SIZE"," +@h2@ SET CACHE_SIZE int +"," +Sets the size of the cache in KB (each KB being 1024 bytes) for the current database. +The default is 65536 per available GB of RAM, i.e. 64 MB per GB. +The value is rounded to the next higher power of two. +Depending on the virtual machine, the actual memory required may be higher. + +This setting is persistent and affects all connections as there is only one cache per database. +Using a very small value (specially 0) will reduce performance a lot. +This setting only affects the database engine (the server in a client/server environment; +in embedded mode, the database engine is in the same process as the application). +It has no effect for in-memory databases. + +Admin rights are required to execute this command, as it affects all connections. +This command commits an open transaction in this connection. +This setting is persistent. +This setting can be appended to the database URL: ""jdbc:h2:./test;CACHE_SIZE=8192"" +"," +SET CACHE_SIZE 8192 +" + +"Commands (Other)","SET CLUSTER"," +@h2@ SET CLUSTER serverListString +"," +This command should not be used directly by an application, the statement is +executed automatically by the system. The behavior may change in future +releases. Sets the cluster server list. An empty string switches off the cluster +mode. Switching on the cluster mode requires admin rights, but any user can +switch it off (this is automatically done when the client detects the other +server is not responding). + +This command is effective immediately, but does not commit an open transaction. +"," +SET CLUSTER '' +" + +"Commands (Other)","SET BUILTIN_ALIAS_OVERRIDE"," +@h2@ SET BUILTIN_ALIAS_OVERRIDE { TRUE | FALSE } +"," +Allows the overriding of the builtin system date/time functions +for unit testing purposes. + +Admin rights are required to execute this command. +This command commits an open transaction in this connection. +"," +SET BUILTIN_ALIAS_OVERRIDE TRUE +" + +"Commands (Other)","SET CATALOG"," +SET CATALOG { catalogString | @h2@ { catalogName } } +"," +This command has no effect if the specified name matches the name of the database, otherwise it throws an exception. + +This command does not commit a transaction. +"," +SET CATALOG 'DB' +SET CATALOG DB_NAME +" + +"Commands (Other)","SET COLLATION"," +@h2@ SET [ DATABASE ] COLLATION +@h2@ { OFF | collationName + [ STRENGTH { PRIMARY | SECONDARY | TERTIARY | IDENTICAL } ] } +"," +Sets the collation used for comparing strings. +This command can only be executed if there are no tables defined. +See ""java.text.Collator"" for details about the supported collations and the STRENGTH +(PRIMARY is usually case- and umlaut-insensitive; SECONDARY is case-insensitive but umlaut-sensitive; +TERTIARY is both case- and umlaut-sensitive; IDENTICAL is sensitive to all differences and only affects ordering). + +The ICU4J collator is used if it is in the classpath. +It is also used if the collation name starts with ICU4J_ +(in that case, the ICU4J must be in the classpath, otherwise an exception is thrown). +The default collator is used if the collation name starts with DEFAULT_ +(even if ICU4J is in the classpath). +The charset collator is used if the collation name starts with CHARSET_ (e.g. CHARSET_CP500). This collator sorts +strings according to the binary representation in the given charset. + +Admin rights are required to execute this command. +This command commits an open transaction in this connection. +This setting is persistent. +This setting can be appended to the database URL: ""jdbc:h2:./test;COLLATION='ENGLISH'"" +"," +SET COLLATION ENGLISH +SET COLLATION CHARSET_CP500 +" + +"Commands (Other)","SET DATABASE_EVENT_LISTENER"," +@h2@ SET DATABASE_EVENT_LISTENER classNameString +"," +Sets the event listener class. An empty string ('') means no listener should be +used. This setting is not persistent. + +Admin rights are required to execute this command, except if it is set when +opening the database (in this case it is reset just after opening the database). +This setting can be appended to the database URL: ""jdbc:h2:./test;DATABASE_EVENT_LISTENER='sample.MyListener'"" +"," +SET DATABASE_EVENT_LISTENER 'sample.MyListener' +" + +"Commands (Other)","SET DB_CLOSE_DELAY"," +@h2@ SET DB_CLOSE_DELAY int +"," +Sets the delay for closing a database if all connections are closed. +The value -1 means the database is never closed until the close delay is set to some other value or SHUTDOWN is called. +The value 0 means no delay (default; the database is closed if the last connection to it is closed). +Values 1 and larger mean the number of seconds the database is left open after closing the last connection. + +If the application exits normally or System.exit is called, the database is closed immediately, even if a delay is set. + +Admin rights are required to execute this command, as it affects all connections. +This command commits an open transaction in this connection. +This setting is persistent. +This setting can be appended to the database URL: ""jdbc:h2:./test;DB_CLOSE_DELAY=-1"" +"," +SET DB_CLOSE_DELAY -1 +" + +"Commands (Other)","SET DEFAULT_LOCK_TIMEOUT"," +@h2@ SET DEFAULT LOCK_TIMEOUT int +"," +Sets the default lock timeout (in milliseconds) in this database that is used +for the new sessions. The default value for this setting is 1000 (one second). + +Admin rights are required to execute this command, as it affects all connections. +This command commits an open transaction in this connection. +This setting is persistent. +"," +SET DEFAULT_LOCK_TIMEOUT 5000 +" + +"Commands (Other)","SET DEFAULT_NULL_ORDERING"," +@h2@ SET DEFAULT_NULL_ORDERING { LOW | HIGH | FIRST | LAST } +"," +Changes the default ordering of NULL values. +This setting affects new indexes without explicit NULLS FIRST or NULLS LAST columns, +and ordering clauses of other commands without explicit null ordering. +This setting doesn't affect ordering of NULL values inside ARRAY or ROW values +(""ARRAY[NULL]"" is always considered as smaller than ""ARRAY[1]"" during sorting). + +LOW is the default one, NULL values are considered as smaller than other values during sorting. + +With HIGH default ordering NULL values are considered as larger than other values during sorting. + +With FIRST default ordering NULL values are sorted before other values, +no matter if ascending or descending order is used. + +WITH LAST default ordering NULL values are sorted after other values, +no matter if ascending or descending order is used. + +This setting is not persistent, but indexes are persisted with explicit NULLS FIRST or NULLS LAST ordering +and aren't affected by changes in this setting. +Admin rights are required to execute this command, as it affects all connections. +This command commits an open transaction in this connection. +This setting can be appended to the database URL: ""jdbc:h2:./test;DEFAULT_NULL_ORDERING=HIGH"" +"," +SET DEFAULT_NULL_ORDERING HIGH +" + +"Commands (Other)","SET DEFAULT_TABLE_TYPE"," +@h2@ SET DEFAULT_TABLE_TYPE { MEMORY | CACHED } +"," +Sets the default table storage type that is used when creating new tables. +Memory tables are kept fully in the main memory (including indexes), however +the data is still stored in the database file. The size of memory tables is +limited by the memory. The default is CACHED. + +Admin rights are required to execute this command, as it affects all connections. +This command commits an open transaction in this connection. +This setting is persistent. +It has no effect for in-memory databases. +"," +SET DEFAULT_TABLE_TYPE MEMORY +" + +"Commands (Other)","SET EXCLUSIVE"," +@h2@ SET EXCLUSIVE { 0 | 1 | 2 } +"," +Switched the database to exclusive mode (1, 2) and back to normal mode (0). + +In exclusive mode, new connections are rejected, and operations by +other connections are paused until the exclusive mode is disabled. +When using the value 1, existing connections stay open. +When using the value 2, all existing connections are closed +(and current transactions are rolled back) except the connection +that executes SET EXCLUSIVE. +Only the connection that set the exclusive mode can disable it. +When the connection is closed, it is automatically disabled. + +Admin rights are required to execute this command. +This command commits an open transaction in this connection. +"," +SET EXCLUSIVE 1 +" + +"Commands (Other)","SET IGNORECASE"," +@h2@ SET IGNORECASE { TRUE | FALSE } +"," +If IGNORECASE is enabled, text columns in newly created tables will be +case-insensitive. Already existing tables are not affected. The effect of +case-insensitive columns is similar to using a collation with strength PRIMARY. +Case-insensitive columns are compared faster than when using a collation. +String literals and parameters are however still considered case sensitive even if this option is set. + +Admin rights are required to execute this command, as it affects all connections. +This command commits an open transaction in this connection. +This setting is persistent. +This setting can be appended to the database URL: ""jdbc:h2:./test;IGNORECASE=TRUE"" +"," +SET IGNORECASE TRUE +" + +"Commands (Other)","SET IGNORE_CATALOGS"," +@c@ SET IGNORE_CATALOGS { TRUE | FALSE } +"," +If IGNORE_CATALOGS is enabled, catalog names in front of schema names will be ignored. This can be used if +multiple catalogs used by the same connections must be simulated. Caveat: if both catalogs contain schemas of the +same name and if those schemas contain objects of the same name, this will lead to errors, when trying to manage, +access or change these objects. +This setting can be appended to the database URL: ""jdbc:h2:./test;IGNORE_CATALOGS=TRUE"" +"," +SET IGNORE_CATALOGS TRUE +" + +"Commands (Other)","SET JAVA_OBJECT_SERIALIZER"," +@h2@ SET JAVA_OBJECT_SERIALIZER { null | className } +"," +Sets the object used to serialize and deserialize java objects being stored in column of type OTHER. +The serializer class must be public and implement ""org.h2.api.JavaObjectSerializer"". +Inner classes are not supported. +The class must be available in the classpath of the database engine +(when using the server mode, it must be both in the classpath of the server and the client). +This command can only be executed if there are no tables defined. + +Admin rights are required to execute this command. +This command commits an open transaction in this connection. +This setting is persistent. +This setting can be appended to the database URL: ""jdbc:h2:./test;JAVA_OBJECT_SERIALIZER='com.acme.SerializerClassName'"" +"," +SET JAVA_OBJECT_SERIALIZER 'com.acme.SerializerClassName' +" + +"Commands (Other)","SET LAZY_QUERY_EXECUTION"," +@h2@ SET LAZY_QUERY_EXECUTION int +"," +Sets the lazy query execution mode. The values 0, 1 are supported. + +If true, then large results are retrieved in chunks. + +Note that not all queries support this feature, queries which do not are processed normally. + +This command does not commit a transaction, and rollback does not affect it. +This setting can be appended to the database URL: ""jdbc:h2:./test;LAZY_QUERY_EXECUTION=1"" +"," +SET LAZY_QUERY_EXECUTION 1 +" + +"Commands (Other)","SET LOCK_MODE"," +@h2@ SET LOCK_MODE int +"," +Sets the lock mode. The values 0, 1, 2, and 3 are supported. The default is 3. +This setting affects all connections. + +The value 0 means no locking (should only be used for testing). +Please note that using SET LOCK_MODE 0 while at the same time +using multiple connections may result in inconsistent transactions. + +The value 3 means row-level locking for write operations. + +The values 1 and 2 have the same effect as 3. + +Admin rights are required to execute this command, as it affects all connections. +This command commits an open transaction in this connection. +This setting is persistent. +This setting can be appended to the database URL: ""jdbc:h2:./test;LOCK_MODE=0"" +"," +SET LOCK_MODE 0 +" + +"Commands (Other)","SET LOCK_TIMEOUT"," +@h2@ SET LOCK_TIMEOUT int +"," +Sets the lock timeout (in milliseconds) for the current session. The default +value for this setting is 1000 (one second). + +This command does not commit a transaction, and rollback does not affect it. +This setting can be appended to the database URL: ""jdbc:h2:./test;LOCK_TIMEOUT=10000"" +"," +SET LOCK_TIMEOUT 1000 +" + +"Commands (Other)","SET MAX_LENGTH_INPLACE_LOB"," +@h2@ SET MAX_LENGTH_INPLACE_LOB int +"," +Sets the maximum size of an in-place LOB object. + +This is the maximum length of an LOB that is stored with the record itself, +and the default value is 256. + +Admin rights are required to execute this command, as it affects all connections. +This command commits an open transaction in this connection. +This setting is persistent. +"," +SET MAX_LENGTH_INPLACE_LOB 128 +" + +"Commands (Other)","SET MAX_LOG_SIZE"," +@h2@ SET MAX_LOG_SIZE int +"," +Sets the maximum size of the transaction log, in megabytes. +If the log is larger, and if there is no open transaction, the transaction log is truncated. +If there is an open transaction, the transaction log will continue to grow however. +The default max size is 16 MB. +This setting has no effect for in-memory databases. + +Admin rights are required to execute this command, as it affects all connections. +This command commits an open transaction in this connection. +This setting is persistent. +"," +SET MAX_LOG_SIZE 2 +" + +"Commands (Other)","SET MAX_MEMORY_ROWS"," +@h2@ SET MAX_MEMORY_ROWS int +"," +The maximum number of rows in a result set that are kept in-memory. If more rows +are read, then the rows are buffered to disk. +The default is 40000 per GB of available RAM. + +Admin rights are required to execute this command, as it affects all connections. +This command commits an open transaction in this connection. +This setting is persistent. +It has no effect for in-memory databases. +"," +SET MAX_MEMORY_ROWS 1000 +" + +"Commands (Other)","SET MAX_MEMORY_UNDO"," +@h2@ SET MAX_MEMORY_UNDO int +"," +The maximum number of undo records per a session that are kept in-memory. +If a transaction is larger, the records are buffered to disk. +The default value is 50000. +Changes to tables without a primary key can not be buffered to disk. +This setting is not supported when using multi-version concurrency. + +Admin rights are required to execute this command, as it affects all connections. +This command commits an open transaction in this connection. +This setting is persistent. +It has no effect for in-memory databases. +"," +SET MAX_MEMORY_UNDO 1000 +" + +"Commands (Other)","SET MAX_OPERATION_MEMORY"," +@h2@ SET MAX_OPERATION_MEMORY int +"," +Sets the maximum memory used for large operations (delete and insert), in bytes. +Operations that use more memory are buffered to disk, slowing down the +operation. The default max size is 100000. 0 means no limit. + +This setting is not persistent. +Admin rights are required to execute this command, as it affects all connections. +It has no effect for in-memory databases. +This setting can be appended to the database URL: ""jdbc:h2:./test;MAX_OPERATION_MEMORY=10000"" +"," +SET MAX_OPERATION_MEMORY 0 +" + +"Commands (Other)","SET MODE"," +@h2@ SET MODE { REGULAR | STRICT | LEGACY | DB2 | DERBY | HSQLDB | MSSQLSERVER | MYSQL | ORACLE | POSTGRESQL } +"," +Changes to another database compatibility mode. For details, see +[Compatibility Modes](https://h2database.com/html/features.html#compatibility_modes). + +This setting is not persistent. +Admin rights are required to execute this command, as it affects all connections. +This command commits an open transaction in this connection. +This setting can be appended to the database URL: ""jdbc:h2:./test;MODE=MYSQL"" +"," +SET MODE HSQLDB +" + +"Commands (Other)","SET NON_KEYWORDS"," +@h2@ SET NON_KEYWORDS [ name [,...] ] +"," +Converts the specified tokens from keywords to plain identifiers for the current session. +This setting may break some commands and should be used with caution and only when necessary. +Use [quoted identifiers](https://h2database.com/html/grammar.html#quoted_name) instead of this setting if possible. + +This command does not commit a transaction, and rollback does not affect it. +This setting can be appended to the database URL: ""jdbc:h2:./test;NON_KEYWORDS=KEY,VALUE"" +"," +SET NON_KEYWORDS KEY, VALUE +" + +"Commands (Other)","SET OPTIMIZE_REUSE_RESULTS"," +@h2@ SET OPTIMIZE_REUSE_RESULTS { 0 | 1 } +"," +Enabled (1) or disabled (0) the result reuse optimization. If enabled, +subqueries and views used as subqueries are only re-run if the data in one of +the tables was changed. This option is enabled by default. + +Admin rights are required to execute this command, as it affects all connections. +This command commits an open transaction in this connection. +This setting can be appended to the database URL: ""jdbc:h2:./test;OPTIMIZE_REUSE_RESULTS=0"" +"," +SET OPTIMIZE_REUSE_RESULTS 0 +" + +"Commands (Other)","SET PASSWORD"," +@h2@ SET PASSWORD string +"," +Changes the password of the current user. The password must be in single quotes. +It is case sensitive and can contain spaces. + +This command commits an open transaction in this connection. +"," +SET PASSWORD 'abcstzri!.5' +" + +"Commands (Other)","SET QUERY_STATISTICS"," +@h2@ SET QUERY_STATISTICS { TRUE | FALSE } +"," +Disabled or enables query statistics gathering for the whole database. +The statistics are reflected in the INFORMATION_SCHEMA.QUERY_STATISTICS meta-table. + +This setting is not persistent. +This command commits an open transaction in this connection. +Admin rights are required to execute this command, as it affects all connections. +"," +SET QUERY_STATISTICS FALSE +" + +"Commands (Other)","SET QUERY_STATISTICS_MAX_ENTRIES"," +@h2@ SET QUERY_STATISTICS int +"," +Set the maximum number of entries in query statistics meta-table. +Default value is 100. + +This setting is not persistent. +This command commits an open transaction in this connection. +Admin rights are required to execute this command, as it affects all connections. +"," +SET QUERY_STATISTICS_MAX_ENTRIES 500 +" + +"Commands (Other)","SET QUERY_TIMEOUT"," +@h2@ SET QUERY_TIMEOUT int +"," +Set the query timeout of the current session to the given value. The timeout is +in milliseconds. All kinds of statements will throw an exception if they take +longer than the given value. The default timeout is 0, meaning no timeout. + +This command does not commit a transaction, and rollback does not affect it. +"," +SET QUERY_TIMEOUT 10000 +" + +"Commands (Other)","SET REFERENTIAL_INTEGRITY"," +@h2@ SET REFERENTIAL_INTEGRITY { TRUE | FALSE } +"," +Disabled or enables referential integrity checking for the whole database. +Enabling it does not check existing data. Use ALTER TABLE SET to disable it only +for one table. + +This setting is not persistent. +This command commits an open transaction in this connection. +Admin rights are required to execute this command, as it affects all connections. +"," +SET REFERENTIAL_INTEGRITY FALSE +" + +"Commands (Other)","SET RETENTION_TIME"," +@h2@ SET RETENTION_TIME int +"," +How long to retain old, persisted data, in milliseconds. +The default is 45000 (45 seconds), 0 means overwrite data as early as possible. +It is assumed that a file system and hard disk will flush all write buffers within this time. +Using a lower value might be dangerous, unless the file system and hard disk flush the buffers earlier. +To manually flush the buffers, use CHECKPOINT SYNC, +however please note that according to various tests this does not always work as expected +depending on the operating system and hardware. + +Admin rights are required to execute this command, as it affects all connections. +This command commits an open transaction in this connection. +This setting is persistent. +This setting can be appended to the database URL: ""jdbc:h2:./test;RETENTION_TIME=0"" +"," +SET RETENTION_TIME 0 +" + +"Commands (Other)","SET SALT HASH"," +@h2@ SET SALT bytes HASH bytes +"," +Sets the password salt and hash for the current user. The password must be in +single quotes. It is case sensitive and can contain spaces. + +This command commits an open transaction in this connection. +"," +SET SALT '00' HASH '1122' +" + +"Commands (Other)","SET SCHEMA"," +SET SCHEMA { schemaString | @h2@ { schemaName } } +"," +Changes the default schema of the current connection. The default schema is used +in statements where no schema is set explicitly. The default schema for new +connections is PUBLIC. + +This command does not commit a transaction, and rollback does not affect it. +This setting can be appended to the database URL: ""jdbc:h2:./test;SCHEMA=ABC"" +"," +SET SCHEMA 'PUBLIC' +SET SCHEMA INFORMATION_SCHEMA +" + +"Commands (Other)","SET SCHEMA_SEARCH_PATH"," +@h2@ SET SCHEMA_SEARCH_PATH schemaName [,...] +"," +Changes the schema search path of the current connection. The default schema is +used in statements where no schema is set explicitly. The default schema for new +connections is PUBLIC. + +This command does not commit a transaction, and rollback does not affect it. +This setting can be appended to the database URL: ""jdbc:h2:./test;SCHEMA_SEARCH_PATH=ABC,DEF"" +"," +SET SCHEMA_SEARCH_PATH INFORMATION_SCHEMA, PUBLIC +" + +"Commands (Other)","SET SESSION CHARACTERISTICS"," +SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL +{ READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE } +"," +Changes the transaction isolation level of the current session. +The actual support of isolation levels depends on the database engine. + +This command commits an open transaction in this session. +"," +SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE +" + +"Commands (Other)","SET THROTTLE"," +@h2@ SET THROTTLE int +"," +Sets the throttle for the current connection. The value is the number of +milliseconds delay after each 50 ms. The default value is 0 (throttling +disabled). + +This command does not commit a transaction, and rollback does not affect it. +This setting can be appended to the database URL: ""jdbc:h2:./test;THROTTLE=50"" +"," +SET THROTTLE 200 +" + +"Commands (Other)","SET TIME ZONE"," +SET TIME ZONE { LOCAL | intervalHourToMinute | @h2@ { intervalHourToSecond | string } } +"," +Sets the current time zone for the session. + +This command does not commit a transaction, and rollback does not affect it. +This setting can be appended to the database URL: ""jdbc:h2:./test;TIME ZONE='1:00'"" + +Time zone offset used for [CURRENT_TIME](https://h2database.com/html/functions.html#current_time), +[CURRENT_TIMESTAMP](https://h2database.com/html/functions.html#current_timestamp), +[CURRENT_DATE](https://h2database.com/html/functions.html#current_date), +[LOCALTIME](https://h2database.com/html/functions.html#localtime), +and [LOCALTIMESTAMP](https://h2database.com/html/functions.html#localtimestamp) is adjusted, +so these functions will return new values based on the same UTC timestamp after execution of this command. +"," +SET TIME ZONE LOCAL +SET TIME ZONE '-5:00' +SET TIME ZONE INTERVAL '1:00' HOUR TO MINUTE +SET TIME ZONE 'Europe/London' +" + +"Commands (Other)","SET TRACE_LEVEL"," +@h2@ SET { TRACE_LEVEL_FILE | TRACE_LEVEL_SYSTEM_OUT } int +"," +Sets the trace level for file the file or system out stream. Levels are: 0=off, +1=error, 2=info, 3=debug. The default level is 1 for file and 0 for system out. +To use SLF4J, append "";TRACE_LEVEL_FILE=4"" to the database URL when opening the database. + +This setting is not persistent. +Admin rights are required to execute this command, as it affects all connections. +This command does not commit a transaction, and rollback does not affect it. +This setting can be appended to the database URL: ""jdbc:h2:./test;TRACE_LEVEL_SYSTEM_OUT=3"" +"," +SET TRACE_LEVEL_SYSTEM_OUT 3 +" + +"Commands (Other)","SET TRACE_MAX_FILE_SIZE"," +@h2@ SET TRACE_MAX_FILE_SIZE int +"," +Sets the maximum trace file size. If the file exceeds the limit, the file is +renamed to .old and a new file is created. If another .old file exists, it is +deleted. The default max size is 16 MB. + +This setting is persistent. +Admin rights are required to execute this command, as it affects all connections. +This command commits an open transaction in this connection. +This setting can be appended to the database URL: ""jdbc:h2:./test;TRACE_MAX_FILE_SIZE=3"" +"," +SET TRACE_MAX_FILE_SIZE 10 +" + +"Commands (Other)","SET TRUNCATE_LARGE_LENGTH"," +@h2@ SET TRUNCATE_LARGE_LENGTH { TRUE | FALSE } +"," +If ""TRUE"" is specified, the ""CHARACTER"", ""CHARACTER VARYING"", ""VARCHAR_IGNORECASE"", ""BINARY"", +"BINARY_VARYING", "JAVA_OBJECT"" and ""JSON"" data types with too large length will be treated as these data types with +maximum allowed length instead. +By default, or if ""FALSE"" is specified, such definitions throw an exception. +This setting can be used for compatibility with definitions from older versions of H2. + +This setting can be appended to the database URL: ""jdbc:h2:./test;TRUNCATE_LARGE_LENGTH=TRUE"" +"," +SET TRUNCATE_LARGE_LENGTH TRUE +" + +"Commands (Other)","SET VARIABLE_BINARY"," +@h2@ SET VARIABLE_BINARY { TRUE | FALSE } +"," +If ""TRUE"" is specified, the ""BINARY"" data type will be parsed as ""VARBINARY"" in the current session. +It can be used for compatibility with older versions of H2. + +This setting can be appended to the database URL: ""jdbc:h2:./test;VARIABLE_BINARY=TRUE"" +"," +SET VARIABLE_BINARY TRUE +" + +"Commands (Other)","SET WRITE_DELAY"," +@h2@ SET WRITE_DELAY int +"," +Set the maximum delay between a commit and flushing the log, in milliseconds. +This setting is persistent. The default is 500 ms. + +Admin rights are required to execute this command, as it affects all connections. +This command commits an open transaction in this connection. +This setting can be appended to the database URL: ""jdbc:h2:./test;WRITE_DELAY=0"" +"," +SET WRITE_DELAY 2000 +" + +"Commands (Other)","SHUTDOWN"," +@h2@ SHUTDOWN [ IMMEDIATELY | COMPACT | DEFRAG ] +"," +This statement closes all open connections to the database and closes the +database. This command is usually not required, as the database is +closed automatically when the last connection to it is closed. + +If no option is used, then the database is closed normally. +All connections are closed, open transactions are rolled back. + +SHUTDOWN COMPACT fully compacts the database (re-creating the database may further reduce the database size). +If the database is closed normally (using SHUTDOWN or by closing all connections), then the database is also compacted, +but only for at most the time defined by the database setting ""h2.maxCompactTime"" in milliseconds (see there). + +SHUTDOWN IMMEDIATELY closes the database files without any cleanup and without compacting. + +SHUTDOWN DEFRAG is currently equivalent to COMPACT. + +Admin rights are required to execute this command. +"," +SHUTDOWN COMPACT +" + +"Literals","Value"," +string | @h2@ { dollarQuotedString } | numeric | dateAndTime | boolean | bytes + | interval | array | @h2@ { geometry | json | uuid } | null +"," +A literal value of any data type, or null. +"," +10 +" + +"Literals","Approximate numeric"," +[ + | - ] { { number [ . number ] } | { . number } } +E [ + | - ] expNumber +"," +An approximate numeric value. +Approximate numeric values have [DECFLOAT](https://h2database.com/html/datatypes.html#decfloat_type) data type. +To define a [DOUBLE PRECISION](https://h2database.com/html/datatypes.html#double_precision_type) value, use +""CAST(X AS DOUBLE PRECISION)"". +To define a [REAL](https://h2database.com/html/datatypes.html#real_type) value, use ""CAST(X AS REAL)"". +There are some special REAL, DOUBLE PRECISION, and DECFLOAT values: +to represent positive infinity, use ""CAST('Infinity' AS dataType)""; +for negative infinity, use ""CAST('-Infinity' AS dataType)""; +for ""NaN"" (not a number), use ""CAST('NaN' AS dataType)"". +"," +-1.4e-10 +CAST(1e2 AS REAL) +CAST('NaN' AS DOUBLE PRECISION) +" + +"Literals","Array"," +ARRAY '[' [ expression [,...] ] ']' +"," +An array of values. +"," +ARRAY[1, 2] +ARRAY[1] +ARRAY[] +" + +"Literals","Boolean"," +TRUE | FALSE | UNKNOWN +"," +A boolean value. +UNKNOWN is a NULL value with the boolean data type. +"," +TRUE +" + +"Literals","Bytes"," +X'hex' [ 'hex' [...] ] +"," +A binary string value. The hex value is not case sensitive and may contain space characters as separators. +If there are more than one group of quoted hex values, groups must be separated with whitespace. +"," +X'' +X'01FF' +X'01 bc 2a' +X'01' '02' +" + +"Literals","Date"," +DATE '[-]yyyy-MM-dd' +"," +A date literal. +"," +DATE '2004-12-31' +" + +"Literals","Date and time"," +date | time | timeWithTimeZone | timestamp | timestampWithTimeZone +"," +A literal value of any date-time data type. +"," +TIMESTAMP '1999-01-31 10:00:00' +" + +"Literals","Dollar Quoted String"," +@h2@ $$anythingExceptTwoDollarSigns$$ +"," +A string starts and ends with two dollar signs. Two dollar signs are not allowed +within the text. A whitespace is required before the first set of dollar signs. +No escaping is required within the text. +"," +$$John's car$$ +" + +"Literals","Exact numeric"," +[ + | - ] { { number [ . number ] } | { . number } } +"," +An exact numeric value. +Exact numeric values with dot have [NUMERIC](https://h2database.com/html/datatypes.html#numeric_type) data type, values +without dot small enough to fit into [INTEGER](https://h2database.com/html/datatypes.html#integer_type) data type have +this type, larger values small enough to fit into [BIGINT](https://h2database.com/html/datatypes.html#bigint_type) data +type have this type, others also have NUMERIC data type. +"," +-1600.05 +" + +"Literals","Hex Number"," +@h2@ [ + | - ] @h2@ 0x { digit | a-f | A-F } [...] +"," +A number written in hexadecimal notation. +"," +0xff +" + +"Literals","Int"," +[ + | - ] number +"," +The maximum integer number is 2147483647, the minimum is -2147483648. +"," +10 +" + +"Literals","GEOMETRY"," +@h2@ GEOMETRY { bytes | string } +"," +A binary string or character string with GEOMETRY object. + +A binary string should contain Well-known Binary Representation (WKB) from OGC 06-103r4. +Dimension system marks may be specified either in both OGC WKB or in PostGIS EWKB formats. +Optional SRID from EWKB may be specified. +POINT EMPTY stored with NaN values as specified in OGC 12-128r15 is supported. + +A character string should contain Well-known Text Representation (WKT) from OGC 06-103r4 +with optional SRID from PostGIS EWKT extension. + +"," +GEOMETRY 'GEOMETRYCOLLECTION (POINT (1 2))' +GEOMETRY X'00000000013ff00000000000003ff0000000000000' +" + +"Literals","JSON"," +@h2@ JSON { bytes | string } +"," +A binary or character string with a RFC 8259-compliant JSON text and data format. +JSON text is parsed into internal representation. +Order of object members is preserved as is. +Duplicate object member names are allowed. +"," +JSON '{""id"":10,""name"":""What''s this?""}' +JSON '[1, ' '2]'; +JSON X'7472' '7565' +" + +"Literals","Long"," +[ + | - ] number +"," +Long numbers are between -9223372036854775808 and 9223372036854775807. +"," +100000 +" + +"Literals","Null"," +NULL +"," +NULL is a value without data type and means 'unknown value'. +"," +NULL +" + +"Literals","Number"," +digit [...] +"," +The maximum length of the number depends on the data type used. +"," +100 +" + +"Literals","Numeric"," +exactNumeric | approximateNumeric | int | long | @h2@ { hexNumber } +"," +The data type of a numeric literal is the one of numeric data types, such as NUMERIC, DECFLOAT, BIGINT, or INTEGER +depending on format and value. + +An explicit CAST can be used to change the data type. +"," +-1600.05 +CAST(0 AS DOUBLE PRECISION) +-1.4e-10 +" + +"Literals","String"," +[N]'anythingExceptSingleQuote' [...] + | U&{'anythingExceptSingleQuote' [...]} [ UESCAPE 'singleCharacter' ] +"," +A character string literal starts and ends with a single quote. +Two single quotes can be used to create a single quote inside a string. +Prefix ""N"" means a national character string literal; +H2 does not distinguish regular and national character string literals in any way, this prefix has no effect in H2. + +String literals staring with ""U&"" are Unicode character string literals. +All character string literals in H2 may have Unicode characters, +but Unicode character string literals may contain Unicode escape sequences ""\0000"" or ""\+000000"", +where \ is an escape character, ""0000"" and ""000000"" are Unicode character codes in hexadecimal notation. +Optional ""UESCAPE"" clause may be used to specify another escape character, +with exception for single quote, double quote, plus sign, and hexadecimal digits (0-9, a-f, and A-F). +By default the backslash is used. +Two escape characters can be used to include a single character inside a string. +Two single quotes can be used to create a single quote inside a string. +"," +'John''s car' +'A' 'B' 'C' +U&'W\00f6rter ' '\\ \+01f600 /' +U&'|00a1' UESCAPE '|' +" + +"Literals","UUID"," +@h2@ UUID '{ digit | a-f | A-F | - } [...]' +"," +A UUID literal. +Must contain 32 hexadecimal digits. Digits may be separated with - signs. +"," +UUID '12345678-1234-1234-1234-123456789ABC' +" + +"Literals","Time"," +TIME [ WITHOUT TIME ZONE ] 'hh:mm:ss[.nnnnnnnnn]' +"," +A time literal. A value is between 0:00:00 and 23:59:59.999999999 +and has nanosecond resolution. +"," +TIME '23:59:59' +" + +"Literals","Time with time zone"," +TIME WITH TIME ZONE 'hh:mm:ss[.nnnnnnnnn]{ @h2@ { Z } | { - | + } timeZoneOffsetString}' +"," +A time with time zone literal. A value is between 0:00:00 and 23:59:59.999999999 +and has nanosecond resolution. +"," +TIME WITH TIME ZONE '23:59:59+01' +TIME WITH TIME ZONE '10:15:30.334-03:30' +TIME WITH TIME ZONE '0:00:00Z' +" + +"Literals","Timestamp"," +TIMESTAMP [ WITHOUT TIME ZONE ] '[-]yyyy-MM-dd hh:mm:ss[.nnnnnnnnn]' +"," +A timestamp literal. +"," +TIMESTAMP '2005-12-31 23:59:59' +" + +"Literals","Timestamp with time zone"," +TIMESTAMP WITH TIME ZONE '[-]yyyy-MM-dd hh:mm:ss[.nnnnnnnnn] +[ @h2@ { Z } | { - | + } timeZoneOffsetString | @h2@ { timeZoneNameString } ]' +"," +A timestamp with time zone literal. +If name of time zone is specified it will be converted to time zone offset. +"," +TIMESTAMP WITH TIME ZONE '2005-12-31 23:59:59Z' +TIMESTAMP WITH TIME ZONE '2005-12-31 23:59:59-10:00' +TIMESTAMP WITH TIME ZONE '2005-12-31 23:59:59.123+05' +TIMESTAMP WITH TIME ZONE '2005-12-31 23:59:59.123456789 Europe/London' +" + +"Literals","Interval"," +intervalYear | intervalMonth | intervalDay | intervalHour | intervalMinute + | intervalSecond | intervalYearToMonth | intervalDayToHour + | intervalDayToMinute | intervalDayToSecond | intervalHourToMinute + | intervalHourToSecond | intervalMinuteToSecond +"," +An interval literal. +"," +INTERVAL '1-2' YEAR TO MONTH +" + +"Literals","INTERVAL 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 [ ( 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 [ ( 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 [ ( 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 [ ( 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 [ ( 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 [ ( 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 [ ( 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 [ ( 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 [ ( 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 [ ( 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 [ ( 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 [ ( 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 +" + +"Datetime fields","Datetime field"," +yearField | monthField | dayOfMonthField + | hourField | minuteField | secondField + | timezoneHourField | timezoneMinuteField + | @h2@ { timezoneSecondField + | millenniumField | centuryField | decadeField + | quarterField + | millisecondField | microsecondField | nanosecondField + | dayOfYearField + | isoDayOfWeekField | isoWeekField | isoWeekYearField + | dayOfWeekField | weekField | weekYearField + | epochField } +"," +Fields for EXTRACT, DATEADD, DATEDIFF, and DATE_TRUNC functions. +"," +YEAR +" + +"Datetime fields","Year field"," +YEAR | @c@ { YYYY | YY | SQL_TSI_YEAR } +"," +Year. +"," +YEAR +" + +"Datetime fields","Month field"," +MONTH | @c@ { MM | M | SQL_TSI_MONTH } +"," +Month (1-12). +"," +MONTH +" + +"Datetime fields","Day of month field"," +DAY | @c@ { DD | D | SQL_TSI_DAY } +"," +Day of month (1-31). +"," +DAY +" + +"Datetime fields","Hour field"," +HOUR | @c@ { HH | SQL_TSI_HOUR } +"," +Hour (0-23). +"," +HOUR +" + +"Datetime fields","Minute field"," +MINUTE | @c@ { MI | N | SQL_TSI_MINUTE } +"," +Minute (0-59). +"," +MINUTE +" + +"Datetime fields","Second field"," +SECOND | @c@ { SS | S | SQL_TSI_SECOND } +"," +Second (0-59). +"," +SECOND +" + +"Datetime fields","Timezone hour field"," +TIMEZONE_HOUR +"," +Timezone hour (from -18 to +18). +"," +TIMEZONE_HOUR +" + +"Datetime fields","Timezone minute field"," +TIMEZONE_MINUTE +"," +Timezone minute (from -59 to +59). +"," +TIMEZONE_MINUTE +" + +"Datetime fields","Timezone second field"," +@h2@ TIMEZONE_SECOND +"," +Timezone second (from -59 to +59). +Local mean time (LMT) used in the past may have offsets with seconds. +Standard time doesn't use such offsets. +"," +TIMEZONE_SECOND +" + +"Datetime fields","Millennium field"," +@h2@ MILLENNIUM +"," +Century, or one thousand years (2001-01-01 to 3000-12-31). +"," +MILLENNIUM +" + +"Datetime fields","Century field"," +@h2@ CENTURY +"," +Century, or one hundred years (2001-01-01 to 2100-12-31). +"," +CENTURY +" + +"Datetime fields","Decade field"," +@h2@ DECADE +"," +Decade, or ten years (2020-01-01 to 2029-12-31). +"," +DECADE +" + +"Datetime fields","Quarter field"," +@h2@ QUARTER +"," +Quarter (1-4). +"," +QUARTER +" + +"Datetime fields","Millisecond field"," +@h2@ { MILLISECOND } | @c@ { MS } +"," +Millisecond (0-999). +"," +MILLISECOND +" + +"Datetime fields","Microsecond field"," +@h2@ { MICROSECOND } | @c@ { MCS } +"," +Microsecond (0-999999). +"," +MICROSECOND +" + +"Datetime fields","Nanosecond field"," +@h2@ { NANOSECOND } | @c@ { NS } +"," +Nanosecond (0-999999999). +"," +NANOSECOND +" + +"Datetime fields","Day of year field"," +@h2@ { DAYOFYEAR | DAY_OF_YEAR } | @c@ { DOY | DY } +"," +Day of year (1-366). +"," +DAYOFYEAR +" + +"Datetime fields","ISO day of week field"," +@h2@ { ISO_DAY_OF_WEEK } | @c@ { ISODOW } +"," +ISO day of week (1-7). Monday is 1. +"," +ISO_DAY_OF_WEEK +" + +"Datetime fields","ISO week field"," +@h2@ ISO_WEEK +"," +ISO week of year (1-53). +ISO definition is used when first week of year should have at least four days +and week is started with Monday. +"," +ISO_WEEK +" + +"Datetime fields","ISO week year field"," +@h2@ { ISO_WEEK_YEAR } | @c@ { ISO_YEAR | ISOYEAR } +"," +Returns the ISO week-based year from a date/time value. +"," +ISO_WEEK_YEAR +" + +"Datetime fields","Day of week field"," +@h2@ { DAY_OF_WEEK | DAYOFWEEK } | @c@ { DOW } +"," +Day of week (1-7), locale-specific. +"," +DAY_OF_WEEK +" + +"Datetime fields","Week field"," +@h2@ { WEEK } | @c@ { WW | W | SQL_TSI_WEEK } +"," +Week of year (1-53) using local rules. +"," +WEEK +" + +"Datetime fields","Week year field"," +@h2@ { WEEK_YEAR } +"," +Returns the week-based year (locale-specific) from a date/time value. +"," +WEEK_YEAR +" + +"Datetime fields","Epoch field"," +@h2@ EPOCH +"," +For TIMESTAMP values number of seconds since 1970-01-01 00:00:00 in local time zone. +For TIMESTAMP WITH TIME ZONE values number of seconds since 1970-01-01 00:00:00 in UTC time zone. +For DATE values number of seconds since 1970-01-01. +For TIME values number of seconds since midnight. +"," +EPOCH +" + +"Other Grammar","Alias"," +name +"," +An alias is a name that is only valid in the context of the statement. +"," +A +" + +"Other Grammar","And Condition"," +condition [ { AND condition } [...] ] +"," +Value or condition. +"," +ID=1 AND NAME='Hi' +" + +"Other Grammar","Array element reference"," +array '[' indexInt ']' +"," +Returns array element at specified index or NULL if array is null or index is null. +"," +A[2] +" + +"Other Grammar","Field reference"," +(expression).fieldName +"," +Returns field value from the row value or NULL if row value is null. +Row value expression must be enclosed in parentheses. +"," +(R).COL1 +" + +"Other Grammar","Array value constructor by query"," +ARRAY (query) +"," +Collects values from the subquery into array. + +The subquery should have exactly one column. +Number of elements in the returned array is the number of rows in the subquery. +NULL values are included into array. +"," +ARRAY(SELECT * FROM SYSTEM_RANGE(1, 10)); +" + +"Other Grammar","Case expression"," +simpleCase | searchedCase +"," +Performs conditional evaluation of expressions. +"," +CASE A WHEN 'a' THEN 1 ELSE 2 END +CASE WHEN V > 10 THEN 1 WHEN V < 0 THEN 2 END +CASE WHEN A IS NULL THEN 'Null' ELSE 'Not null' END +" + +"Other Grammar","Simple case"," +CASE expression +{ WHEN { expression | conditionRightHandSide } [,...] THEN expression } [...] +[ ELSE expression ] END +"," +Returns then expression from the first when clause where one of its operands was was evaluated to ""TRUE"" +for the case expression. +If there are no such clauses, returns else expression or NULL if it is absent. + +Plain expressions are tested for equality with the case expression, ""NULL"" is not equal to ""NULL"". +Right sides of conditions are evaluated with the case expression on the left side. +"," +CASE CNT WHEN IS NULL THEN 'Null' WHEN 0 THEN 'No' WHEN 1 THEN 'One' WHEN 2, 3 THEN 'Few' ELSE 'Some' END +" + +"Other Grammar","Searched case"," +CASE { WHEN expression THEN expression } [...] +[ ELSE expression ] END +"," +Returns the first expression where the condition is true. If no else part is +specified, return NULL. +"," +CASE WHEN CNT<10 THEN 'Low' ELSE 'High' END +CASE WHEN A IS NULL THEN 'Null' ELSE 'Not null' END +" + +"Other Grammar","Cast specification"," +CAST(value AS dataTypeOrDomain) +"," +Converts a value to another data type. The following conversion rules are used: +When converting a number to a boolean, 0 is false and every other value is true. +When converting a boolean to a number, false is 0 and true is 1. +When converting a number to a number of another type, the value is checked for overflow. +When converting a string to binary, UTF-8 encoding is used. +Note that some data types may need explicitly specified precision to avoid overflow or rounding. +"," +CAST(NAME AS INT); +CAST(TIMESTAMP '2010-01-01 10:40:00.123456' AS TIME(6)) +" + +"Other Grammar","Cipher"," +@h2@ AES +"," +Only the algorithm AES (""AES-128"") is supported currently. +"," +AES +" + +"Other Grammar","Column Definition"," +dataTypeOrDomain @h2@ [ VISIBLE | INVISIBLE ] +[ { DEFAULT expression + | GENERATED ALWAYS AS (generatedColumnExpression) + | GENERATED {ALWAYS | BY DEFAULT} AS IDENTITY [(sequenceOption [...])]} ] +@h2@ [ ON UPDATE expression ] +@h2@ [ DEFAULT ON NULL ] +@h2@ [ SELECTIVITY selectivityInt ] @h2@ [ COMMENT expression ] +[ columnConstraintDefinition ] [...] +"," +The default expression is used if no explicit value was used when adding a row +and when DEFAULT value was specified in an update command. + +A column is either a generated column or a base column. +The generated column has a generated column expression. +The generated column expression is evaluated and assigned whenever the row changes. +This expression may reference base columns of the table, but may not reference other data. +The value of the generated column cannot be set explicitly. +Generated columns may not have DEFAULT or ON UPDATE expressions. + +On update column expression is used if row is updated, +at least one column has a new value that is different from its previous value +and value for this column is not set explicitly in update statement. + +Identity column is a column generated with a sequence. +The column declared as the identity column with IDENTITY data type or with IDENTITY () clause +is implicitly the primary key column of this table. +GENERATED ALWAYS AS IDENTITY, GENERATED BY DEFAULT AS IDENTITY, and AUTO_INCREMENT clauses +do not create the primary key constraint automatically. +GENERATED ALWAYS AS IDENTITY clause indicates that column can only be generated by the sequence, +its value cannot be set explicitly. +Identity column has implicit NOT NULL constraint. +Identity column may not have DEFAULT or ON UPDATE expressions. + +DEFAULT ON NULL makes NULL value work as DEFAULT value is assignments to this column. + +The invisible column will not be displayed as a result of SELECT * query. +Otherwise, it works as normal column. + +Column constraint definitions are not supported for ALTER statements. +"," +CREATE TABLE TEST(ID INT PRIMARY KEY, + NAME VARCHAR(255) DEFAULT '' NOT NULL); +CREATE TABLE TEST(ID BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + QUANTITY INT, PRICE NUMERIC(10, 2), + AMOUNT NUMERIC(20, 2) GENERATED ALWAYS AS (QUANTITY * PRICE)); +" + +"Other Grammar","Column Constraint Definition"," +[ constraintNameDefinition ] +NOT NULL | PRIMARY KEY | UNIQUE | referencesSpecification | CHECK (condition) +"," +NOT NULL disallows NULL value for a column. + +PRIMARY KEY and UNIQUE require unique values. +PRIMARY KEY also disallows NULL values and marks the column as a primary key. + +Referential constraint requires values that exist in other column (usually in another table). + +Check constraint require a specified condition to return TRUE or UNKNOWN (NULL). +It can reference columns of the table, and can reference objects that exist while the statement is executed. +Conditions are only checked when a row is added or modified in the table where the constraint exists. +"," +NOT NULL +PRIMARY KEY +UNIQUE +REFERENCES T2(ID) +CHECK (VALUE > 0) +" + +"Other Grammar","Comment"," +bracketedComment | -- anythingUntilEndOfLine | @c@ // anythingUntilEndOfLine +"," +Comments can be used anywhere in a command and are ignored by the database. +Line comments ""--"" and ""//"" end with a newline. +"," +-- comment +/* comment */ +" + +"Other Grammar","Bracketed comment"," +/* [ [ bracketedComment ] [ anythingUntilCommentStartOrEnd ] [...] ] */ +"," +Comments can be used anywhere in a command and are ignored by the database. +Bracketed comments ""/* */"" can be nested and can be multiple lines long. +"," +/* comment */ +/* comment /* nested comment */ comment */ +" + +"Other Grammar","Compare"," +<> | <= | >= | = | < | > | @c@ { != } | @h2@ && +"," +Comparison operator. The operator != is the same as <>. +The operator ""&&"" means overlapping; it can only be used with geometry types. +"," +<> +" + +"Other Grammar","Condition"," +operand [ conditionRightHandSide ] + | NOT condition + | EXISTS ( query ) + | UNIQUE ( query ) + | @h2@ INTERSECTS (operand, operand) +"," +Boolean value or condition. + +""NOT"" condition negates the result of subcondition and returns ""TRUE"", ""FALSE"", or ""UNKNOWN"" (""NULL""). + +""EXISTS"" predicate tests whether the result of the specified subquery is not empty and returns ""TRUE"" or ""FALSE"". + +""UNIQUE"" predicate tests absence of duplicate rows in the specified subquery and returns ""TRUE"" or ""FALSE"". +Rows with ""NULL"" value in any column are ignored. + +""INTERSECTS"" checks whether 2D bounding boxes of specified geometries intersect with each other +and returns ""TRUE"" or ""FALSE"". +"," +ID <> 2 +NOT(A OR B) +EXISTS (SELECT NULL FROM TEST T WHERE T.GROUP_ID = P.ID) +UNIQUE (SELECT A, B FROM TEST T WHERE T.CATEGORY = CAT) +INTERSECTS(GEOM1, GEOM2) +" + +"Other Grammar","Condition Right Hand Side"," +comparisonRightHandSide + | quantifiedComparisonRightHandSide + | nullPredicateRightHandSide + | distinctPredicateRightHandSide + | quantifiedDistinctPredicateRightHandSide + | booleanTestRightHandSide + | typePredicateRightHandSide + | jsonPredicateRightHandSide + | betweenPredicateRightHandSide + | inPredicateRightHandSide + | likePredicateRightHandSide + | regexpPredicateRightHandSide +"," +The right hand side of a condition. +"," +> 10 +IS NULL +IS NOT NULL +IS NOT DISTINCT FROM B +IS OF (DATE, TIMESTAMP, TIMESTAMP WITH TIME ZONE) +IS JSON OBJECT WITH UNIQUE KEYS +LIKE 'Jo%' +" + +"Other Grammar","Comparison Right Hand Side"," +compare operand +"," +Right side of comparison predicates. +"," +> 10 +" + +"Other Grammar","Quantified Comparison Right Hand Side"," +compare { ALL | ANY | SOME } ( query ) +"," +Right side of quantified comparison predicates. + +Quantified comparison predicate ALL returns TRUE if specified comparison operation between +left size of condition and each row from a subquery returns TRUE, including case when there are no rows. +ALL predicate returns FALSE if at least one such comparison returns FALSE. +Otherwise it returns UNKNOWN. + +Quantified comparison predicates ANY and SOME return TRUE if specified comparison operation between +left size of condition and at least one row from a subquery returns TRUE. +ANY and SOME predicates return FALSE if all such comparisons return FALSE. +Otherwise they return UNKNOWN. + +Note that these predicates have priority over ANY and SOME aggregate functions with subquery on the right side. +Use parentheses around aggregate function. +"," +< ALL(SELECT V FROM TEST) +" + +"Other Grammar","Null Predicate Right Hand Side"," +IS [ NOT ] NULL +"," +Right side of null predicate. + +Check whether the specified value(s) are NULL values. +To test multiple values a row value must be specified. +""IS NULL"" returns ""TRUE"" if and only if all values are ""NULL"" values; otherwise it returns ""FALSE"". +""IS NOT NULL"" returns ""TRUE"" if and only if all values are not ""NULL"" values; otherwise it returns ""FALSE"". +"," +IS NULL +" + +"Other Grammar","Distinct Predicate Right Hand Side"," +IS [ NOT ] [ DISTINCT FROM ] operand +"," +Right side of distinct predicate. + +Distinct predicate is null-safe, meaning NULL is considered the same as NULL, +and the condition never evaluates to UNKNOWN. +"," +IS NOT DISTINCT FROM OTHER +" + +"Other Grammar","Quantified Distinct Predicate Right Hand Side"," +@h2@ IS [ NOT ] [ DISTINCT FROM ] { ALL | ANY | SOME } ( query ) +"," +Right side of quantified distinct predicate. + +Quantified distinct predicate is null-safe, meaning NULL is considered the same as NULL, +and the condition never evaluates to UNKNOWN. + +Quantified distinct predicate ALL returns TRUE if specified distinct predicate between +left size of condition and each row from a subquery returns TRUE, including case when there are no rows. +Otherwise it returns FALSE. + +Quantified distinct predicates ANY and SOME return TRUE if specified distinct predicate between +left size of condition and at least one row from a subquery returns TRUE. +Otherwise they return FALSE. + +Note that these predicates have priority over ANY and SOME aggregate functions with subquery on the right side. +Use parentheses around aggregate function. +"," +IS DISTINCT FROM ALL(SELECT V FROM TEST) +" + +"Other Grammar","Boolean Test Right Hand Side"," +IS [ NOT ] { TRUE | FALSE | UNKNOWN } +"," +Right side of boolean test. + +Checks whether the specified value is (not) ""TRUE"", ""FALSE"", or ""UNKNOWN"" (""NULL"") +and return ""TRUE"" or ""FALSE"". +This test is null-safe. +"," +IS TRUE +" + +"Other Grammar","Type Predicate Right Hand Side"," +IS [ NOT ] OF (dataType [,...]) +"," +Right side of type predicate. + +Checks whether the data type of the specified operand is one of the specified data types. +Some data types have multiple names, these names are considered as equal here. +Domains and their base data types are currently not distinguished from each other. +Precision and scale are also ignored. +If operand is NULL, the result is UNKNOWN. +"," +IS OF (INTEGER, BIGINT) +" + +"Other Grammar","JSON Predicate Right Hand Side"," +IS [ NOT ] JSON [ VALUE | ARRAY | OBJECT | SCALAR ] + [ [ WITH | WITHOUT ] UNIQUE [ KEYS ] ] +"," +Right side of JSON predicate. + +Checks whether value of the specified string, binary data, or a JSON is a valid JSON. +If ""ARRAY"", ""OBJECT"", or ""SCALAR"" is specified, only JSON items of the specified type are considered as valid. +If ""WITH UNIQUE [ KEYS ]"" is specified only JSON with unique keys is considered as valid. +This predicate isn't null-safe, it returns UNKNOWN if operand is NULL. +"," +IS JSON OBJECT WITH UNIQUE KEYS +" + +"Other Grammar","Between Predicate Right Hand Side"," +[ NOT ] BETWEEN [ ASYMMETRIC | SYMMETRIC ] operand AND operand +"," +Right side of between predicate. + +Checks whether the value is within the range inclusive. +""V BETWEEN [ ASYMMETRIC ] A AND B"" is equivalent to ""A <= V AND V <= B"". +""V BETWEEN SYMMETRIC A AND B"" is equivalent to ""A <= V AND V <= B OR A >= V AND V >= B"". +"," +BETWEEN LOW AND HIGH +" + +"Other Grammar","In Predicate Right Hand Side"," +[ NOT ] IN ( { query | expression [,...] } ) +"," +Right side of in predicate. + +Checks presence of value in the specified list of values or in result of the specified query. + +Returns ""TRUE"" if row value on the left side is equal to one of values on the right side, +""FALSE"" if all comparison operations were evaluated to ""FALSE"" or right side has no values, +and ""UNKNOWN"" otherwise. + +This operation is logically equivalent to ""OR"" between comparison operations +comparing left side and each value from the right side. +"," +IN (A, B, C) +IN (SELECT V FROM TEST) +" + +"Other Grammar","Like Predicate Right Hand Side"," +[ NOT ] { LIKE | @h2@ { ILIKE } } operand [ ESCAPE string ] +"," +Right side of like predicate. + +The wildcards characters are ""_"" (any one character) and ""%"" (any characters). +The database uses an index when comparing with LIKE except if the operand starts with a wildcard. +To search for the characters ""%"" and ""_"", the characters need to be escaped. +The default escape character is "" \ "" (backslash). +To select no escape character, use ""ESCAPE ''"" (empty string). +At most one escape character is allowed. +Each character that follows the escape character in the pattern needs to match exactly. +Patterns that end with an escape character are invalid and the expression returns NULL. + +ILIKE does a case-insensitive compare. +"," +LIKE 'a%' +" + +"Other Grammar","Regexp Predicate Right Hand Side"," +@h2@ { [ NOT ] REGEXP operand } +"," +Right side of Regexp predicate. + +Regular expression matching is used. +See Java ""Matcher.find"" for details. +"," +REGEXP '[a-z]' +" + +"Other Grammar","Table Constraint Definition"," +[ constraintNameDefinition ] +{ PRIMARY KEY @h2@ [ HASH ] ( columnName [,...] ) } + | UNIQUE ( { columnName [,...] | VALUE } ) + | referentialConstraint + | CHECK (condition) +"," +Defines a constraint. + +PRIMARY KEY and UNIQUE require unique values. +PRIMARY KEY also disallows NULL values and marks the column as a primary key, a table can have only one primary key. +UNIQUE constraint supports NULL values and rows with NULL value in any column are considered as unique. +UNIQUE (VALUE) creates a unique constraint on entire row, excluding invisible columns; +but if new columns will be added to the table, they will not be included into this constraint. + +Referential constraint requires values that exist in other column(s) (usually in another table). + +Check constraint requires a specified condition to return TRUE or UNKNOWN (NULL). +It can reference columns of the table, and can reference objects that exist while the statement is executed. +Conditions are only checked when a row is added or modified in the table where the constraint exists. +"," +PRIMARY KEY(ID, NAME) +" + +"Other Grammar","Constraint Name Definition"," +CONSTRAINT @h2@ [ IF NOT EXISTS ] newConstraintName +"," +Defines a constraint name. +"," +CONSTRAINT CONST_ID +" + +"Other Grammar","Csv Options"," +@h2@ charsetString [, fieldSepString [, fieldDelimString [, escString [, nullString]]]] + | optionString +"," +Optional parameters for CSVREAD and CSVWRITE. +Instead of setting the options one by one, all options can be +combined into a space separated key-value pairs, as follows: +""STRINGDECODE('charset=UTF-8 escape=\"" fieldDelimiter=\"" fieldSeparator=, ' ||"" +""'lineComment=# lineSeparator=\n null= rowSeparator=')"". +The following options are supported: + +""caseSensitiveColumnNames"" (true or false; disabled by default), + +""charset"" (for example 'UTF-8'), + +""escape"" (the character that escapes the field delimiter), + +""fieldDelimiter"" (a double quote by default), + +""fieldSeparator"" (a comma by default), + +""lineComment"" (disabled by default), + +""lineSeparator"" (the line separator used for writing; ignored for reading), + +""null"", Support reading existing CSV files that contain explicit ""null"" delimiters. +Note that an empty, unquoted values are also treated as null. + +""preserveWhitespace"" (true or false; disabled by default), + +""writeColumnHeader"" (true or false; enabled by default). + +For a newline or other special character, use STRINGDECODE as in the example above. +A space needs to be escaped with a backslash (""'\ '""), and +a backslash needs to be escaped with another backslash (""'\\'""). +All other characters are not to be escaped, that means +newline and tab characters are written as such. +"," +CALL CSVWRITE('test2.csv', 'SELECT * FROM TEST', 'charset=UTF-8 fieldSeparator=|'); +" + +"Other Grammar","Data Change Delta Table"," +{ OLD | NEW | FINAL } TABLE +( { insert | update | delete | @h2@ { mergeInto } | mergeUsing } ) +"," +Executes the inner data change command and returns old, new, or final rows. + +""OLD"" is not allowed for ""INSERT"" command. It returns old rows. + +""NEW"" and ""FINAL"" are not allowed for ""DELETE"" command. + +""NEW"" returns new rows after evaluation of default expressions, but before execution of triggers. + +""FINAL"" returns new rows after execution of triggers. +"," +SELECT ID FROM FINAL TABLE (INSERT INTO TEST (A, B) VALUES (1, 2)) +" + +"Other Grammar","Data Type or Domain"," +dataType | [schemaName.]domainName +"," +A data type or domain name. +"," +INTEGER +MY_DOMAIN +" + +"Other Grammar","Data Type"," +predefinedType | arrayType | rowType +"," +A data type. +"," +INTEGER +" + +"Other Grammar","Predefined Type"," +characterType | characterVaryingType | characterLargeObjectType + | binaryType | binaryVaryingType | binaryLargeObjectType + | booleanType + | smallintType | integerType | bigintType + | numericType | realType | doublePrecisionType | decfloatType + | dateType | timeType | timeWithTimeZoneType + | timestampType | timestampWithTimeZoneType + | intervalType + | @h2@ { tinyintType | javaObjectType | enumType + | geometryType | jsonType | uuidType } +"," +A predefined data type. +"," +INTEGER +" + +"Other Grammar","Digit"," +0-9 +"," +A digit. +"," +0 +" + +"Other Grammar","Expression"," +andCondition [ { OR andCondition } [...] ] +"," +Value or condition. +"," +ID=1 OR NAME='Hi' +" + +"Other Grammar","Factor"," +term [ { { * | / | @c@ { % } } term } [...] ] +"," +A value or a numeric factor. +"," +ID * 10 +" + +"Other Grammar","Grouping element"," +expression | (expression [, ...]) | () +"," +A grouping element of GROUP BY clause. +"," +A +(B, C) +() +" + +"Other Grammar","Hex"," +[' ' [...]] { { digit | a-f | A-F } [' ' [...]] { digit | a-f | A-F } [' ' [...]] } [...] +"," +The hexadecimal representation of a number or of bytes with optional space characters. +Two hexadecimal digit characters are one byte. +"," +cafe +11 22 33 +a b c d +" + +"Other Grammar","Index Column"," +columnName [ ASC | DESC ] [ NULLS { FIRST | LAST } ] +"," +Indexes this column in ascending or descending order. Usually it is not required +to specify the order; however doing so will speed up large queries that order +the column in the same way. +"," +NAME +" + +"Other Grammar","Insert values"," +VALUES { DEFAULT|expression | [ROW] ({DEFAULT|expression} [,...]) }, [,...] +"," +Values for INSERT statement. +"," +VALUES (1, 'Test') +" + +"Other Grammar","Interval qualifier"," +YEAR [(precisionInt)] [ TO MONTH ] + | MONTH [(precisionInt)] + | DAY [(precisionInt)] [ TO { HOUR | MINUTE | SECOND [(scaleInt)] } ] + | HOUR [(precisionInt)] [ TO { MINUTE | SECOND [(scaleInt)] } ] + | MINUTE [(precisionInt)] [ TO SECOND [(scaleInt)] ] + | SECOND [(precisionInt [, scaleInt])] +"," +An interval qualifier. +"," +DAY TO SECOND +" + +"Other Grammar","Join specification"," +ON expression | USING (columnName [,...]) +"," +Specifies a join condition or column names. +"," +ON B.ID = A.PARENT_ID +USING (ID) +" + +"Other Grammar","Merge when clause"," +mergeWhenMatchedClause|mergeWhenNotMatchedClause +"," +WHEN MATCHED or WHEN NOT MATCHED clause for MERGE USING command. +"," +WHEN MATCHED THEN DELETE +" + +"Other Grammar","Merge when matched clause"," +WHEN MATCHED [ AND expression ] THEN +UPDATE SET setClauseList | DELETE +"," +WHEN MATCHED clause for MERGE USING command. +"," +WHEN MATCHED THEN UPDATE SET NAME = S.NAME +WHEN MATCHED THEN DELETE +" + +"Other Grammar","Merge when not matched clause"," +WHEN NOT MATCHED [ AND expression ] THEN INSERT +[ ( columnName [,...] ) ] +[ overrideClause ] +VALUES ({DEFAULT|expression} [,...]) +"," +WHEN NOT MATCHED clause for MERGE USING command. +"," +WHEN NOT MATCHED THEN INSERT (ID, NAME) VALUES (S.ID, S.NAME) +" + +"Other Grammar","Name"," +{ { A-Z|_ } [ { A-Z|_|0-9 } [...] ] } | quotedName +"," +With default settings unquoted names are converted to upper case. +The maximum name length is 256 characters. + +Identifiers in H2 are case sensitive by default. +Because unquoted names are converted to upper case, they can be written in any case anyway. +When both quoted and unquoted names are used for the same identifier the quoted names must be written in upper case. +Identifiers with lowercase characters can be written only as a quoted name, they aren't accessible with unquoted names. + +If DATABASE_TO_UPPER setting is set to FALSE the unquoted names aren't converted to upper case. + +If DATABASE_TO_LOWER setting is set to TRUE the unquoted names are converted to lower case instead. + +If CASE_INSENSITIVE_IDENTIFIERS setting is set to TRUE all identifiers are case insensitive. +"," +TEST +" + +"Other Grammar","Operand"," +summand [ { || summand } [...] ] +"," +Performs the concatenation of character string, binary string, or array values. +In the default mode, the result is NULL if either parameter is NULL. +In compatibility modes result of string concatenation with NULL parameter can be different. +"," +'Hi' || ' Eva' +X'AB' || X'CD' +ARRAY[1, 2] || 3 +1 || ARRAY[2, 3] +ARRAY[1, 2] || ARRAY[3, 4] +" + +"Other Grammar","Override clause"," +OVERRIDING { USER | SYSTEM } VALUE +"," +If OVERRIDING USER VALUE is specified, INSERT statement ignores the provided value for identity column +and generates a new one instead. + +If OVERRIDING SYSTEM VALUE is specified, INSERT statement assigns the provided value to identity column. + +If neither clauses are specified, INSERT statement assigns the provided value to +GENERATED BY DEFAULT AS IDENTITY column, +but throws an exception if value is specified for GENERATED ALWAYS AS IDENTITY column. +"," +OVERRIDING SYSTEM VALUE +OVERRIDING USER VALUE +" + +"Other Grammar","Query"," +select | explicitTable | tableValue +"," +A query, such as SELECT, explicit table, or table value. +"," +SELECT ID FROM TEST; +TABLE TEST; +VALUES (1, 2), (3, 4); +" + +"Other Grammar","Quoted Name"," +""anythingExceptDoubleQuote"" + | U&""anythingExceptDoubleQuote"" [ UESCAPE 'singleCharacter' ] +"," +Case of characters in quoted names is preserved as is. Such names can contain spaces. +The maximum name length is 256 characters. +Two double quotes can be used to create a single double quote inside an identifier. +With default settings identifiers in H2 are case sensitive. + +Identifiers staring with ""U&"" are Unicode identifiers. +All identifiers in H2 may have Unicode characters, +but Unicode identifiers may contain Unicode escape sequences ""\0000"" or ""\+000000"", +where \ is an escape character, ""0000"" and ""000000"" are Unicode character codes in hexadecimal notation. +Optional ""UESCAPE"" clause may be used to specify another escape character, +with exception for single quote, double quote, plus sign, and hexadecimal digits (0-9, a-f, and A-F). +By default the backslash is used. +Two escape characters can be used to include a single character inside an Unicode identifier. +Two double quotes can be used to create a single double quote inside an Unicode identifier. +"," +""FirstName"" +U&""\00d6ffnungszeit"" +U&""/00d6ffnungszeit"" UESCAPE '/' +" + +"Other Grammar","Referential Constraint"," +FOREIGN KEY ( columnName [,...] ) referencesSpecification +"," +Defines a referential constraint. +"," +FOREIGN KEY(ID) REFERENCES TEST(ID) +" + +"Other Grammar","References Specification"," +REFERENCES [ refTableName ] [ ( refColumnName [,...] ) ] +[ ON DELETE referentialAction ] [ ON UPDATE referentialAction ] +"," +Defines a referential specification of a referential constraint. +If the table name is not specified, then the same table is referenced. +RESTRICT is the default action. +If the referenced columns are not specified, then the primary key columns are used. +Referential constraint requires an existing unique or primary key constraint on referenced columns, +this constraint must include all referenced columns in any order and must not include any other columns. +Some tables may not be referenced, such as metadata tables. +"," +REFERENCES TEST(ID) +" + +"Other Grammar","Referential Action"," +CASCADE | RESTRICT | NO ACTION | SET { DEFAULT | NULL } +"," +The action CASCADE will cause conflicting rows in the referencing (child) table to be deleted or updated. +RESTRICT is the default action. +As this database does not support deferred checking, RESTRICT and NO ACTION will both throw an exception if the constraint is violated. +The action SET DEFAULT will set the column in the referencing (child) table to the default value, while SET NULL will set it to NULL. +"," +CASCADE +SET NULL +" + +"Other Grammar","Script Compression Encryption"," +@h2@ [ COMPRESSION { DEFLATE | LZF | ZIP | GZIP } ] +@h2@ [ CIPHER cipher PASSWORD string ] +"," +The compression and encryption algorithm to use for script files. +When using encryption, only DEFLATE and LZF are supported. +LZF is faster but uses more space. +"," +COMPRESSION LZF +" + +"Other Grammar","Select order"," +{ expression | @c@ { int } } [ ASC | DESC ] [ NULLS { FIRST | LAST } ] +"," +Sorts the result by the given column number, or by an expression. If the +expression is a single parameter, then the value is interpreted as a column +number. Negative column numbers reverse the sort order. +"," +NAME DESC NULLS LAST +" + +"Other Grammar","Row value expression"," +ROW (expression, [,...]) + | ( [ expression, expression [,...] ] ) + | expression +"," +A row value expression. +"," +ROW (1) +(1, 2) +1 +" + +"Other Grammar","Select Expression"," +wildcardExpression | expression [ [ AS ] columnAlias ] +"," +An expression in a SELECT statement. +"," +ID AS DOCUMENT_ID +" + +"Other Grammar","Sequence value expression"," +{ NEXT | @h2@ { CURRENT } } VALUE FOR [schemaName.]sequenceName +"," +The next or current value of a sequence. + +When the next value is requested the sequence is incremented and the current value of the sequence +and the last identity in the current session are updated with the generated value. +The next value of the sequence is generated only once for each processed row. +If this expression is used multiple times with the same sequence it returns the same value within a processed row. +Used values are never re-used, even when the transaction is rolled back. + +Current value may only be requested after generation of the sequence value in the current session. +It returns the latest generated value for the current session. + +If a single command contains next and current value expressions for the same sequence there is no guarantee that +the next value expression will be evaluated before the evaluation of current value expression. +"," +NEXT VALUE FOR SEQ1 +CURRENT VALUE FOR SCHEMA2.SEQ2 +" + +"Other Grammar","Sequence option"," +START WITH long + | @h2@ { RESTART WITH long } + | basicSequenceOption +"," +Option of a sequence. + +START WITH is used to set the initial value of the sequence. +If initial value is not defined, MINVALUE for incrementing sequences and MAXVALUE for decrementing sequences is used. + +RESTART is used to immediately restart the sequence with the specified value. +"," +START WITH 10000 +NO CACHE +" + +"Other Grammar","Alter sequence option"," +@h2@ { START WITH long } + | RESTART [ WITH long ] + | basicSequenceOption +"," +Option of a sequence. + +START WITH is used to change the initial value of the sequence. +It does not affect the current value of the sequence, +it only changes the preserved initial value that is used for simple RESTART without a value. + +RESTART is used to restart the sequence from its initial value or with the specified value. +"," +START WITH 10000 +NO CACHE +" + +"Other Grammar","Alter identity column option"," +@h2@ { START WITH long } + | RESTART [ WITH long ] + | SET basicSequenceOption +"," +Option of an identity column. + +START WITH is used to set or change the initial value of the sequence. +START WITH does not affect the current value of the sequence, +it only changes the preserved initial value that is used for simple RESTART without a value. + +RESTART is used to restart the sequence from its initial value or with the specified value. +"," +START WITH 10000 +SET NO CACHE +" + +"Other Grammar","Basic sequence option"," +INCREMENT BY long + | MINVALUE long | NO MINVALUE | @c@ { NOMINVALUE } + | MAXVALUE long | NO MAXVALUE | @c@ { NOMAXVALUE } + | CYCLE | NO CYCLE | @h2@ { EXHAUSTED } | @c@ { NOCYCLE } + | @h2@ { CACHE long } | @h2@ { NO CACHE } | @c@ { NOCACHE } +"," +Basic option of a sequence. + +INCREMENT BY specifies the step of the sequence, may be positive or negative, but may not be zero. +The default is 1. + +MINVALUE and MAXVALUE specify the bounds of the sequence. + +Sequences with CYCLE option start the generation again from +MINVALUE (incrementing sequences) or MAXVALUE (decrementing sequences) instead of exhausting with an error. +Sequences with EXHAUSTED option can't return values until they will be restarted. + +The CACHE option sets the number of pre-allocated numbers. +If the system crashes without closing the database, at most this many numbers are lost. +The default cache size is 32 if sequence has enough range of values. +NO CACHE option or the cache size 1 or lower disable the cache. +If CACHE option is specified, it cannot be larger than the total number of values +that sequence can produce within a cycle. +"," +MAXVALUE 100000 +CYCLE +NO CACHE +" + +"Other Grammar","Set clause list"," +{ { columnName = { DEFAULT | expression } } + | { ( columnName [,...] ) = { rowValueExpression | (query) } } } [,...] +"," +List of SET clauses. +"," +NAME = 'Test', PRICE = 2 +(A, B) = (1, 2) +(A, B) = (1, 2), C = 3 +(A, B) = (SELECT X, Y FROM OTHER T2 WHERE T1.ID = T2.ID) +" + +"Other Grammar","Sort specification"," +expression [ ASC | DESC ] [ NULLS { FIRST | LAST } ] +"," +Sorts the result by an expression. +"," +X ASC NULLS FIRST +" + +"Other Grammar","Sort specification list"," +sortSpecification [,...] +"," +Sorts the result by expressions. +"," +V +A, B DESC NULLS FIRST +" + +"Other Grammar","Summand"," +factor [ { { + | - } factor } [...] ] +"," +A value or a numeric sum. + +Please note the text concatenation operator is ""||"". +"," +ID + 20 +" + +"Other Grammar","Table Expression"," +{ [ schemaName. ] tableName + | ( query ) + | unnest + | table + | dataChangeDeltaTable } +[ [ AS ] newTableAlias [ ( columnName [,...] ) ] ] +@h2@ [ USE INDEX ([ indexName [,...] ]) ] +[ { { LEFT | RIGHT } [ OUTER ] | [ INNER ] | CROSS | NATURAL } + JOIN tableExpression [ joinSpecification ] ] +"," +Joins a table. The join specification is not supported for cross and natural joins. +A natural join is an inner join, where the condition is automatically on the +columns with the same name. +"," +TEST1 AS T1 LEFT JOIN TEST2 AS T2 ON T1.ID = T2.PARENT_ID +" + +"Other Grammar","Within group specification"," +WITHIN GROUP (ORDER BY sortSpecificationList) +"," +Group specification for ordered set functions. +"," +WITHIN GROUP (ORDER BY ID DESC) +" + +"Other Grammar","Wildcard expression"," +[[schemaName.]tableAlias.]* +@h2@ [EXCEPT ([[schemaName.]tableAlias.]columnName, [,...])] +"," +A wildcard expression in a SELECT statement. +A wildcard expression represents all visible columns. Some columns can be excluded with optional EXCEPT clause. +"," +* +* EXCEPT (DATA) +" + +"Other Grammar","Window name or specification"," +windowName | windowSpecification +"," +A window name or inline specification for a window function or aggregate. + +Window functions in H2 may require a lot of memory for large queries. +"," +W1 +(ORDER BY ID) +" + +"Other Grammar","Window specification"," +([existingWindowName] +[PARTITION BY expression [,...]] [ORDER BY sortSpecificationList] +[windowFrame]) +"," +A window specification for a window, window function or aggregate. + +If name of an existing window is specified its clauses are used by default. + +Optional window partition clause separates rows into independent partitions. +Each partition is processed separately. +If this clause is not present there is one implicit partition with all rows. + +Optional window order clause specifies order of rows in the partition. +If some rows have the same order position they are considered as a group of rows in optional window frame clause. + +Optional window frame clause specifies which rows are processed by a window function, +see its documentation for a more details. +"," +() +(W1 ORDER BY ID) +(PARTITION BY CATEGORY) +(PARTITION BY CATEGORY ORDER BY NAME, ID) +(ORDER BY Y RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE TIES) +" + +"Other Grammar","Window frame"," +ROWS|RANGE|GROUP +{windowFramePreceding|BETWEEN windowFrameBound AND windowFrameBound} +[EXCLUDE {CURRENT ROW|GROUP|TIES|NO OTHERS}] +"," +A window frame clause. +May be specified only for aggregates and FIRST_VALUE(), LAST_VALUE(), and NTH_VALUE() window functions. + +If this clause is not specified for an aggregate or window function that supports this clause +the default window frame depends on window order clause. +If window order clause is also not specified +the default window frame contains all the rows in the partition. +If window order clause is specified +the default window frame contains all preceding rows and all rows from the current group. + +Window frame unit determines how rows or groups of rows are selected and counted. +If ROWS is specified rows are not grouped in any way and relative numbers of rows are used in bounds. +If RANGE is specified rows are grouped according window order clause, +preceding and following values mean the difference between value in the current row and in the target rows, +and CURRENT ROW in bound specification means current group of rows. +If GROUPS is specified rows are grouped according window order clause, +preceding and following values means relative number of groups of rows, +and CURRENT ROW in bound specification means current group of rows. + +If only window frame preceding clause is specified it is treated as +BETWEEN windowFramePreceding AND CURRENT ROW. + +Optional window frame exclusion clause specifies rows that should be excluded from the frame. +EXCLUDE CURRENT ROW excludes only the current row regardless the window frame unit. +EXCLUDE GROUP excludes the whole current group of rows, including the current row. +EXCLUDE TIES excludes the current group of rows, but not the current row. +EXCLUDE NO OTHERS is default and it does not exclude anything. +"," +ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE TIES +" + +"Other Grammar","Window frame preceding"," +UNBOUNDED PRECEDING|value PRECEDING|CURRENT ROW +"," +A window frame preceding clause. +If value is specified it should not be negative. +"," +UNBOUNDED PRECEDING +1 PRECEDING +CURRENT ROW +" + +"Other Grammar","Window frame bound"," +UNBOUNDED PRECEDING|value PRECEDING|CURRENT ROW + |value FOLLOWING|UNBOUNDED FOLLOWING +"," +A window frame bound clause. +If value is specified it should not be negative. +"," +UNBOUNDED PRECEDING +UNBOUNDED FOLLOWING +1 FOLLOWING +CURRENT ROW +" + +"Other Grammar","Term"," +{ value + | column + | ?[ int ] + | sequenceValueExpression + | function + | { - | + } term + | ( expression ) + | arrayElementReference + | fieldReference + | query + | caseExpression + | castSpecification + | userDefinedFunctionName } +[ timeZone | intervalQualifier ] +"," +A value. Parameters can be indexed, for example ""?1"" meaning the first parameter. + +Interval qualifier may only be specified for a compatible value +or for a subtraction operation between two datetime values. +The subtraction operation ignores the leading field precision of the qualifier. +"," +'Hello' + +" + +"Other Grammar","Time zone"," +AT { TIME ZONE { intervalHourToMinute | intervalHourToSecond | @h2@ { string } } | LOCAL } +"," +A time zone. Converts the timestamp with or without time zone into timestamp with time zone at specified time zone. +If a day-time interval is specified as a time zone, +it may not have fractional seconds and must be between -18 to 18 hours inclusive. +"," +AT LOCAL +AT TIME ZONE '2' +AT TIME ZONE '-6:00' +AT TIME ZONE INTERVAL '10:00' HOUR TO MINUTE +AT TIME ZONE INTERVAL '10:00:00' HOUR TO SECOND +AT TIME ZONE 'UTC' +AT TIME ZONE 'Europe/London' +" + +"Other Grammar","Column"," +[[schemaName.]tableAlias.] { columnName | @h2@ { _ROWID_ } } +"," +A column name with optional table alias and schema. +_ROWID_ can be used to access unique row identifier. +"," +ID +" + +"Data Types","CHARACTER Type"," +{ CHARACTER | CHAR | NATIONAL { CHARACTER | CHAR } | NCHAR } +[ ( lengthInt [CHARACTERS|OCTETS] ) ] +"," +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. +If length is not specified, 1 character is used by default. + +The whole text is kept in memory when using this data type. +For variable-length strings use [CHARACTER VARYING](https://h2database.com/html/datatypes.html#character_varying_type) +data type instead. +For large text data [CHARACTER LARGE OBJECT](https://h2database.com/html/datatypes.html#character_large_object_type) +should be used; see there for details. + +Too short strings are right-padded with space characters. +Too long strings are truncated by CAST specification and rejected by column assignment. + +Two CHARACTER strings of different length are considered as equal if all additional characters in the longer string +are space characters. + +See also [string](https://h2database.com/html/grammar.html#string) literal grammar. +Mapped to ""java.lang.String"". +"," +CHARACTER +CHAR(10) +" + +"Data Types","CHARACTER VARYING Type"," +{ { 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 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. + +The whole text is loaded into memory when using this data type. +For large text data [CHARACTER LARGE OBJECT](https://h2database.com/html/datatypes.html#character_large_object_type) +should be used; see there for details. + +See also [string](https://h2database.com/html/grammar.html#string) literal grammar. +Mapped to ""java.lang.String"". +"," +CHARACTER VARYING(100) +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 } } +[ ( lengthLong [K|M|G|T|P] [CHARACTERS|OCTETS]) ] +"," +CHARACTER LARGE OBJECT is intended for very large Unicode character string values. +Unlike when using [CHARACTER VARYING](https://h2database.com/html/datatypes.html#character_varying_type), +large CHARACTER LARGE OBJECT values are not kept fully in-memory; instead, they are streamed. +CHARACTER LARGE OBJECT should be used for documents and texts with arbitrary size such as XML or +HTML documents, text files, or memo fields of unlimited size. +Use ""PreparedStatement.setCharacterStream"" to store values. +See also [Large Objects](https://h2database.com/html/advanced.html#large_objects) section. + +CHARACTER VARYING should be used for text with relatively short average size (for example +shorter than 200 characters). Short CHARACTER LARGE OBJECT values are stored inline, but there is +an overhead compared to CHARACTER VARYING. + +Length, if any, should be specified in characters, CHARACTERS and OCTETS units have no effect in H2. + +Mapped to ""java.sql.Clob"" (""java.io.Reader"" is also supported). +"," +CHARACTER LARGE OBJECT +CLOB(10K) +" + +"Data Types","VARCHAR_IGNORECASE Type"," +@h2@ VARCHAR_IGNORECASE +[ ( lengthInt [CHARACTERS|OCTETS] ) ] +"," +Same as VARCHAR, but not case sensitive when comparing. +Stored in mixed case. + +The allowed length is from 1 to 1048576 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. + +The whole text is loaded into memory when using this data type. +For large text data CLOB should be used; see there for details. + +See also [string](https://h2database.com/html/grammar.html#string) literal grammar. +Mapped to ""java.lang.String"". +"," +VARCHAR_IGNORECASE +" + +"Data Types","BINARY Type"," +BINARY [ ( lengthInt ) ] +"," +Represents a binary string (byte array) of fixed predefined length. + +The allowed length is from 1 to 1048576 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. +For variable-length binary strings use [BINARY VARYING](https://h2database.com/html/datatypes.html#binary_varying_type) +data type instead. +For large binary data [BINARY LARGE OBJECT](https://h2database.com/html/datatypes.html#binary_large_object_type) +should be used; see there for details. + +Too short binary string are right-padded with zero bytes. +Too long binary strings are truncated by CAST specification and rejected by column assignment. + +Binary strings of different length are considered as not equal to each other. + +See also [bytes](https://h2database.com/html/grammar.html#bytes) literal grammar. +Mapped to byte[]. +"," +BINARY +BINARY(1000) +" + +"Data Types","BINARY VARYING Type"," +{ BINARY VARYING | VARBINARY + | @c@ { LONGVARBINARY | RAW | BYTEA } } +[ ( lengthInt ) ] +"," +Represents a byte array. + +The allowed length is from 1 to 1048576 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. +For large binary data [BINARY LARGE OBJECT](https://h2database.com/html/datatypes.html#binary_large_object_type) +should be used; see there for details. + +See also [bytes](https://h2database.com/html/grammar.html#bytes) literal grammar. +Mapped to byte[]. +"," +BINARY VARYING(100) +VARBINARY(1000) +" + +"Data Types","BINARY LARGE OBJECT Type"," +{ BINARY LARGE OBJECT | BLOB + | @c@ { TINYBLOB | MEDIUMBLOB | LONGBLOB | IMAGE } } +[ ( lengthLong [K|M|G|T|P]) ] +"," +BINARY LARGE OBJECT is intended for very large binary values such as files or images. +Unlike when using [BINARY VARYING](https://h2database.com/html/datatypes.html#binary_varying_type), +large objects are not kept fully in-memory; instead, they are streamed. +Use ""PreparedStatement.setBinaryStream"" to store values. +See also [CHARACTER LARGE OBJECT](https://h2database.com/html/datatypes.html#character_large_object_type) +and [Large Objects](https://h2database.com/html/advanced.html#large_objects) section. + +Mapped to ""java.sql.Blob"" (""java.io.InputStream"" is also supported). +"," +BINARY LARGE OBJECT +BLOB(10K) +" + +"Data Types","BOOLEAN Type"," +BOOLEAN | @c@ { BIT | BOOL } +"," +Possible values: TRUE, FALSE, and UNKNOWN (NULL). + +See also [boolean](https://h2database.com/html/grammar.html#boolean) literal grammar. +Mapped to ""java.lang.Boolean"". +"," +BOOLEAN +" + +"Data Types","TINYINT Type"," +@h2@ TINYINT +"," +Possible values are: -128 to 127. + +See also [integer](https://h2database.com/html/grammar.html#int) literal grammar. + +In JDBC this data type is mapped to ""java.lang.Integer"". +""java.lang.Byte"" is also supported. + +In ""org.h2.api.Aggregate"", ""org.h2.api.AggregateFunction"", and ""org.h2.api.Trigger"" +this data type is mapped to ""java.lang.Byte"". + +"," +TINYINT +" + +"Data Types","SMALLINT Type"," +SMALLINT | @c@ { INT2 } +"," +Possible values: -32768 to 32767. + +See also [integer](https://h2database.com/html/grammar.html#int) literal grammar. + +In JDBC this data type is mapped to ""java.lang.Integer"". +""java.lang.Short"" is also supported. + +In ""org.h2.api.Aggregate"", ""org.h2.api.AggregateFunction"", and ""org.h2.api.Trigger"" +this data type is mapped to ""java.lang.Short"". +"," +SMALLINT +" + +"Data Types","INTEGER Type"," +INTEGER | INT | @c@ { MEDIUMINT | INT4 | SIGNED } +"," +Possible values: -2147483648 to 2147483647. + +See also [integer](https://h2database.com/html/grammar.html#int) literal grammar. +Mapped to ""java.lang.Integer"". +"," +INTEGER +INT +" + +"Data Types","BIGINT Type"," +BIGINT | @c@ INT8 +"," +Possible values: -9223372036854775808 to 9223372036854775807. + +See also [long](https://h2database.com/html/grammar.html#long) literal grammar. +Mapped to ""java.lang.Long"". +"," +BIGINT +" + +"Data Types","NUMERIC Type"," +{ NUMERIC | DECIMAL | DEC } [ ( precisionInt [ , scaleInt ] ) ] +"," +Data type with fixed decimal precision and scale. +This data type is recommended for storing currency values. + +If precision is specified, it must be from 1 to 100000. +If scale is specified, it must be from 0 to 100000, 0 is default. + +See also [numeric](https://h2database.com/html/grammar.html#numeric) literal grammar. +Mapped to ""java.math.BigDecimal"". +"," +NUMERIC(20, 2) +" + +"Data Types","REAL Type"," +REAL | FLOAT ( precisionInt ) | @c@ { FLOAT4 } +"," +A single precision floating point number. +Should not be used to represent currency values, because of rounding problems. +Precision value for FLOAT type name should be from 1 to 24. + +See also [numeric](https://h2database.com/html/grammar.html#numeric) literal grammar. +Mapped to ""java.lang.Float"". +"," +REAL +" + +"Data Types","DOUBLE PRECISION Type"," +DOUBLE PRECISION | FLOAT [ ( precisionInt ) ] | @c@ { DOUBLE | FLOAT8 } +"," +A double precision floating point number. +Should not be used to represent currency values, because of rounding problems. +If precision value is specified for FLOAT type name, it should be from 25 to 53. + +See also [numeric](https://h2database.com/html/grammar.html#numeric) literal grammar. +Mapped to ""java.lang.Double"". +"," +DOUBLE PRECISION +" + +"Data Types","DECFLOAT Type"," +DECFLOAT [ ( precisionInt ) ] +"," +Decimal floating point number. +This data type is not recommended to represent currency values, because of variable scale. + +If precision is specified, it must be from 1 to 100000. + +See also [numeric](https://h2database.com/html/grammar.html#numeric) literal grammar. +Mapped to ""java.math.BigDecimal"". +There are three special values: 'Infinity', '-Infinity', and 'NaN'. +These special values can't be read or set as ""BigDecimal"" values, +but they can be read or set using ""java.lang.String"", float, or double. +"," +DECFLOAT +DECFLOAT(20) +" + +"Data Types","DATE Type"," +DATE +"," +The date data type. The proleptic Gregorian calendar is used. + +See also [date](https://h2database.com/html/grammar.html#date) literal grammar. + +In JDBC this data type is mapped to ""java.sql.Date"", with the time set to ""00:00:00"" +(or to the next possible time if midnight doesn't exist for the given date and time zone due to a daylight saving change). +""java.time.LocalDate"" is also supported and recommended. + +In ""org.h2.api.Aggregate"", ""org.h2.api.AggregateFunction"", and ""org.h2.api.Trigger"" +this data type is mapped to ""java.time.LocalDate"". + +If your time zone had LMT (local mean time) in the past and you use such old dates +(depends on the time zone, usually 100 or more years ago), +don't use ""java.sql.Date"" to read and write them. + +If you deal with very old dates (before 1582-10-15) note that ""java.sql.Date"" uses a mixed Julian/Gregorian calendar, +""java.util.GregorianCalendar"" can be configured to proleptic Gregorian with +""setGregorianChange(new java.util.Date(Long.MIN_VALUE))"" and used to read or write fields of dates. +"," +DATE +" + +"Data Types","TIME Type"," +TIME [ ( precisionInt ) ] [ WITHOUT TIME ZONE ] +"," +The time data type. The format is hh:mm:ss[.nnnnnnnnn]. +If fractional seconds precision is specified it should be from 0 to 9, 0 is default. + +See also [time](https://h2database.com/html/grammar.html#time) literal grammar. + +In JDBC this data type is mapped to ""java.sql.Time"". +""java.time.LocalTime"" is also supported and recommended. + +In ""org.h2.api.Aggregate"", ""org.h2.api.AggregateFunction"", and ""org.h2.api.Trigger"" +this data type is mapped to ""java.time.LocalTime"". + +Use ""java.time.LocalTime"" or ""String"" instead of ""java.sql.Time"" when non-zero precision is needed. +Cast from higher fractional seconds precision to lower fractional seconds precision performs round half up; +if result of rounding is higher than maximum supported value 23:59:59.999999999 the value is rounded down instead. +The CAST operation to TIMESTAMP and TIMESTAMP WITH TIME ZONE data types uses the +[CURRENT_DATE](https://h2database.com/html/functions.html#current_date) for date fields. +"," +TIME +TIME(9) +" + +"Data Types","TIME WITH TIME ZONE Type"," +TIME [ ( precisionInt ) ] WITH TIME ZONE +"," +The time with time zone data type. +If fractional seconds precision is specified it should be from 0 to 9, 0 is default. + +See also [time with time zone](https://h2database.com/html/grammar.html#time_with_time_zone) literal grammar. +Mapped to ""java.time.OffsetTime"". +Cast from higher fractional seconds precision to lower fractional seconds precision performs round half up; +if result of rounding is higher than maximum supported value 23:59:59.999999999 the value is rounded down instead. +The CAST operation to TIMESTAMP and TIMESTAMP WITH TIME ZONE data types uses the +[CURRENT_DATE](https://h2database.com/html/functions.html#current_date) for date fields. +"," +TIME WITH TIME ZONE +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. +For absolute timestamps use the [TIMESTAMP WITH TIME ZONE](https://h2database.com/html/datatypes.html#timestamp_with_time_zone_type) data type instead. + +See also [timestamp](https://h2database.com/html/grammar.html#timestamp) literal grammar. + +In JDBC this data type is mapped to ""java.sql.Timestamp"" (""java.util.Date"" may be used too). +""java.time.LocalDateTime"" is also supported and recommended. + +In ""org.h2.api.Aggregate"", ""org.h2.api.AggregateFunction"", and ""org.h2.api.Trigger"" +this data type is mapped to ""java.time.LocalDateTime"". + +If your time zone had LMT (local mean time) in the past and you use such old dates +(depends on the time zone, usually 100 or more years ago), +don't use ""java.sql.Timestamp"" and ""java.util.Date"" to read and write them. + +If you deal with very old dates (before 1582-10-15) note that ""java.sql.Timestamp"" and ""java.util.Date"" +use a mixed Julian/Gregorian calendar, ""java.util.GregorianCalendar"" can be configured to proleptic Gregorian with +""setGregorianChange(new java.util.Date(Long.MIN_VALUE))"" and used to read or write fields of timestamps. + +Cast from higher fractional seconds precision to lower fractional seconds precision performs round half up. +"," +TIMESTAMP +TIMESTAMP(9) +" + +"Data Types","TIMESTAMP WITH TIME ZONE Type"," +TIMESTAMP [ ( precisionInt ) ] WITH TIME ZONE +"," +The timestamp with time zone data type. The proleptic Gregorian calendar is used. +If fractional seconds precision is specified it should be from 0 to 9, 6 is default. + +See also [timestamp with time zone](https://h2database.com/html/grammar.html#timestamp_with_time_zone) literal grammar. +Mapped to ""java.time.OffsetDateTime"". +""java.time.ZonedDateTime"" and ""java.time.Instant"" are also supported. + +Values of this data type are compared by UTC values. It means that ""2010-01-01 10:00:00+01"" is greater than ""2010-01-01 11:00:00+03"". + +Conversion to ""TIMESTAMP"" uses time zone offset to get UTC time and converts it to local time using the system time zone. +Conversion from ""TIMESTAMP"" does the same operations in reverse and sets time zone offset to offset of the system time zone. +Cast from higher fractional seconds precision to lower fractional seconds precision performs round half up. +"," +TIMESTAMP WITH TIME ZONE +TIMESTAMP(9) WITH TIME ZONE +" + +"Data Types","INTERVAL Type"," +intervalYearType | intervalMonthType | intervalDayType + | intervalHourType| intervalMinuteType | intervalSecondType + | intervalYearToMonthType | intervalDayToHourType + | intervalDayToMinuteType | intervalDayToSecondType + | intervalHourToMinuteType | intervalHourToSecondType + | intervalMinuteToSecondType +"," +Interval data type. +There are two classes of intervals. Year-month intervals can store years and months. +Day-time intervals can store days, hours, minutes, and seconds. +Year-month intervals are comparable only with another year-month intervals. +Day-time intervals are comparable only with another day-time intervals. + +Mapped to ""org.h2.api.Interval"". +"," +INTERVAL DAY TO SECOND +" + +"Data Types","JAVA_OBJECT Type"," +@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 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. +Deserialization is only done when ""getObject"" is called. +Java operations cannot be executed inside the database engine for security reasons. +Use ""PreparedStatement.setObject"" with ""Types.JAVA_OBJECT"" or ""H2Type.JAVA_OBJECT"" +as a third argument to store values. + +If Java method alias has ""Object"" parameter(s), values are deserialized during invocation of this method +on the server side. + +If a [linked table](https://h2database.com/html/advanced.html#linked_tables) has a column with ""Types.JAVA_OBJECT"" +JDBC data type and its database is not an another H2, Java objects need to be serialized and deserialized during +interaction between H2 and database that owns the table on the server side of H2. + +This data type needs special attention in secure environments. + +Mapped to ""java.lang.Object"" (or any subclass). +"," +JAVA_OBJECT +JAVA_OBJECT(10000) +" + +"Data Types","ENUM Type"," +@h2@ ENUM (string [, ... ]) +"," +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. +"," +ENUM('clubs', 'diamonds', 'hearts', 'spades') +" + +"Data Types","GEOMETRY Type"," +@h2@ GEOMETRY + [({ GEOMETRY | + { POINT + | LINESTRING + | POLYGON + | MULTIPOINT + | MULTILINESTRING + | MULTIPOLYGON + | GEOMETRYCOLLECTION } [Z|M|ZM]} + [, sridInt] )] +"," +A spatial geometry type. +If additional constraints are not specified this type accepts all supported types of geometries. +A constraint with required geometry type and dimension system can be set by specifying name of the type and +dimension system. A whitespace between them is optional. +2D dimension system does not have a name and assumed if only a geometry type name is specified. +POINT means 2D point, POINT Z or POINTZ means 3D point. +GEOMETRY constraint means no restrictions on type or dimension system of geometry. +A constraint with required spatial reference system identifier (SRID) can be set by specifying this identifier. + +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. +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). +SRID (spatial reference system identifier) is supported. + +Use a quoted string containing a WKT/EWKT formatted string or ""PreparedStatement.setObject()"" to store values, +and ""ResultSet.getObject(..)"" or ""ResultSet.getString(..)"" to retrieve the values. +"," +GEOMETRY +GEOMETRY(POINT) +GEOMETRY(POINT Z) +GEOMETRY(POINT Z, 4326) +GEOMETRY(GEOMETRY, 4326) +" + +"Data Types","JSON Type"," +@h2@ JSON [(lengthInt)] +"," +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 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 +(""INSERT INTO TEST(ID, DATA) VALUES (?, ? FORMAT JSON)""). +Without the data format VARCHAR values are converted to a JSON string values. + +Order of object members is preserved as is. +Duplicate object member names are allowed. +"," +JSON +" + +"Data Types","UUID Type"," +@h2@ UUID +"," +Universally unique identifier. This is a 128 bit value. +To store values, use ""PreparedStatement.setBytes"", +""setString"", or ""setObject(uuid)"" (where ""uuid"" is a ""java.util.UUID""). +""ResultSet.getObject"" will return a ""java.util.UUID"". + +Please note that using an index on randomly generated data will +result on poor performance once there are millions of rows in a table. +The reason is that the cache behavior is very bad with randomly distributed data. +This is a problem for any database system. + +For details, see the documentation of ""java.util.UUID"". +"," +UUID +" + +"Data Types","ARRAY Type"," +baseDataType ARRAY [ '[' maximumCardinalityInt ']' ] +"," +A data type for array of values. +Base data type specifies the data type of elements. +Array may have NULL elements. +Maximum cardinality, if any, specifies maximum allowed number of elements in the array. +The allowed cardinality is from 0 to 65536 elements. + +See also [array](https://h2database.com/html/grammar.html#array) literal grammar. +Mapped to ""java.lang.Object[]"" (arrays of any non-primitive type are also supported). + +Use ""PreparedStatement.setArray(..)"" or ""PreparedStatement.setObject(.., new Object[] {..})"" to store values, +and ""ResultSet.getObject(..)"" or ""ResultSet.getArray(..)"" to retrieve the values. +"," +BOOLEAN ARRAY +VARCHAR(100) ARRAY +INTEGER ARRAY[10] +" + +"Data Types","ROW Type"," +ROW (fieldName dataType [,...]) +"," +A row value data type. This data type should not be normally used as data type of a column. + +See also [row value expression](https://h2database.com/html/grammar.html#row_value_expression) grammar. +Mapped to ""java.sql.ResultSet"". +"," +ROW(A INT, B VARCHAR(10)) +" + +"Interval Data Types","INTERVAL YEAR Type"," +INTERVAL YEAR [ ( precisionInt ) ] +"," +Interval data type. +If precision is specified it should be from 1 to 18, 2 is default. + +See also [year interval](https://h2database.com/html/grammar.html#interval_year) literal grammar. +Mapped to ""org.h2.api.Interval"". +""java.time.Period"" is also supported. +"," +INTERVAL YEAR +" + +"Interval Data Types","INTERVAL MONTH Type"," +INTERVAL MONTH [ ( precisionInt ) ] +"," +Interval data type. +If precision is specified it should be from 1 to 18, 2 is default. + +See also [month interval](https://h2database.com/html/grammar.html#interval_month) literal grammar. +Mapped to ""org.h2.api.Interval"". +""java.time.Period"" is also supported. +"," +INTERVAL MONTH +" + +"Interval Data Types","INTERVAL DAY Type"," +INTERVAL DAY [ ( precisionInt ) ] +"," +Interval data type. +If precision is specified it should be from 1 to 18, 2 is default. + +See also [day interval](https://h2database.com/html/grammar.html#interval_day) literal grammar. +Mapped to ""org.h2.api.Interval"". +""java.time.Duration"" is also supported. +"," +INTERVAL DAY +" + +"Interval Data Types","INTERVAL HOUR Type"," +INTERVAL HOUR [ ( precisionInt ) ] +"," +Interval data type. +If precision is specified it should be from 1 to 18, 2 is default. + +See also [hour interval](https://h2database.com/html/grammar.html#interval_hour) literal grammar. +Mapped to ""org.h2.api.Interval"". +""java.time.Duration"" is also supported. +"," +INTERVAL HOUR +" + +"Interval Data Types","INTERVAL MINUTE Type"," +INTERVAL MINUTE [ ( precisionInt ) ] +"," +Interval data type. +If precision is specified it should be from 1 to 18, 2 is default. + +See also [minute interval](https://h2database.com/html/grammar.html#interval_minute) literal grammar. +Mapped to ""org.h2.api.Interval"". +""java.time.Duration"" is also supported. +"," +INTERVAL MINUTE +" + +"Interval Data Types","INTERVAL SECOND Type"," +INTERVAL SECOND [ ( precisionInt [, fractionalPrecisionInt ] ) ] +"," +Interval data type. +If precision is specified it should be from 1 to 18, 2 is default. +If fractional seconds precision is specified it should be from 0 to 9, 6 is default. + +See also [second interval](https://h2database.com/html/grammar.html#interval_second) literal grammar. +Mapped to ""org.h2.api.Interval"". +""java.time.Duration"" is also supported. +"," +INTERVAL SECOND +" + +"Interval Data Types","INTERVAL YEAR TO MONTH Type"," +INTERVAL YEAR [ ( precisionInt ) ] TO MONTH +"," +Interval data type. +If leading field precision is specified it should be from 1 to 18, 2 is default. + +See also [year to month interval](https://h2database.com/html/grammar.html#interval_year_to_month) literal grammar. +Mapped to ""org.h2.api.Interval"". +""java.time.Period"" is also supported. +"," +INTERVAL YEAR TO MONTH +" + +"Interval Data Types","INTERVAL DAY TO HOUR Type"," +INTERVAL DAY [ ( precisionInt ) ] TO HOUR +"," +Interval data type. +If leading field precision is specified it should be from 1 to 18, 2 is default. + +See also [day to hour interval](https://h2database.com/html/grammar.html#interval_day_to_hour) literal grammar. +Mapped to ""org.h2.api.Interval"". +""java.time.Duration"" is also supported. +"," +INTERVAL DAY TO HOUR +" + +"Interval Data Types","INTERVAL DAY TO MINUTE Type"," +INTERVAL DAY [ ( precisionInt ) ] TO MINUTE +"," +Interval data type. +If leading field precision is specified it should be from 1 to 18, 2 is default. + +See also [day to minute interval](https://h2database.com/html/grammar.html#interval_day_to_minute) literal grammar. +Mapped to ""org.h2.api.Interval"". +""java.time.Duration"" is also supported. +"," +INTERVAL DAY TO MINUTE +" + +"Interval Data Types","INTERVAL DAY TO SECOND Type"," +INTERVAL DAY [ ( precisionInt ) ] TO SECOND [ ( fractionalPrecisionInt ) ] +"," +Interval data type. +If leading field precision is specified it should be from 1 to 18, 2 is default. +If fractional seconds precision is specified it should be from 0 to 9, 6 is default. + +See also [day to second interval](https://h2database.com/html/grammar.html#interval_day_to_second) literal grammar. +Mapped to ""org.h2.api.Interval"". +""java.time.Duration"" is also supported. +"," +INTERVAL DAY TO SECOND +" + +"Interval Data Types","INTERVAL HOUR TO MINUTE Type"," +INTERVAL HOUR [ ( precisionInt ) ] TO MINUTE +"," +Interval data type. +If leading field precision is specified it should be from 1 to 18, 2 is default. + +See also [hour to minute interval](https://h2database.com/html/grammar.html#interval_hour_to_minute) literal grammar. +Mapped to ""org.h2.api.Interval"". +""java.time.Duration"" is also supported. +"," +INTERVAL HOUR TO MINUTE +" + +"Interval Data Types","INTERVAL HOUR TO SECOND Type"," +INTERVAL HOUR [ ( precisionInt ) ] TO SECOND [ ( fractionalPrecisionInt ) ] +"," +Interval data type. +If leading field precision is specified it should be from 1 to 18, 2 is default. +If fractional seconds precision is specified it should be from 0 to 9, 6 is default. + +See also [hour to second interval](https://h2database.com/html/grammar.html#interval_hour_to_second) literal grammar. +Mapped to ""org.h2.api.Interval"". +""java.time.Duration"" is also supported. +"," +INTERVAL HOUR TO SECOND +" + +"Interval Data Types","INTERVAL MINUTE TO SECOND Type"," +INTERVAL MINUTE [ ( precisionInt ) ] TO SECOND [ ( fractionalPrecisionInt ) ] +"," +Interval data type. +If leading field precision is specified it should be from 1 to 18, 2 is default. +If fractional seconds precision is specified it should be from 0 to 9, 6 is default. + +See also [minute to second interval](https://h2database.com/html/grammar.html#interval_minute_to_second) literal grammar. +Mapped to ""org.h2.api.Interval"". +""java.time.Duration"" is also supported. +"," +INTERVAL MINUTE TO SECOND +" + +"Functions (Numeric)","ABS"," +ABS( { numeric | interval } ) +"," +Returns the absolute value of a specified value. +The returned value is of the same data type as the parameter. + +Note that TINYINT, SMALLINT, INT, and BIGINT data types cannot represent absolute values +of their minimum negative values, because they have more negative values than positive. +For example, for INT data type allowed values are from -2147483648 to 2147483647. +ABS(-2147483648) should be 2147483648, but this value is not allowed for this data type. +It leads to an exception. +To avoid it cast argument of this function to a higher data type. +"," +ABS(I) +ABS(CAST(I AS BIGINT)) +" + +"Functions (Numeric)","ACOS"," +ACOS(numeric) +"," +Calculate the arc cosine. +See also Java ""Math.acos"". +This method returns a double. +"," +ACOS(D) +" + +"Functions (Numeric)","ASIN"," +ASIN(numeric) +"," +Calculate the arc sine. +See also Java ""Math.asin"". +This method returns a double. +"," +ASIN(D) +" + +"Functions (Numeric)","ATAN"," +ATAN(numeric) +"," +Calculate the arc tangent. +See also Java ""Math.atan"". +This method returns a double. +"," +ATAN(D) +" + +"Functions (Numeric)","COS"," +COS(numeric) +"," +Calculate the trigonometric cosine. +See also Java ""Math.cos"". +This method returns a double. +"," +COS(ANGLE) +" + +"Functions (Numeric)","COSH"," +COSH(numeric) +"," +Calculate the hyperbolic cosine. +See also Java ""Math.cosh"". +This method returns a double. +"," +COSH(X) +" + +"Functions (Numeric)","COT"," +@h2@ COT(numeric) +"," +Calculate the trigonometric cotangent (""1/TAN(ANGLE)""). +See also Java ""Math.*"" functions. +This method returns a double. +"," +COT(ANGLE) +" + +"Functions (Numeric)","SIN"," +SIN(numeric) +"," +Calculate the trigonometric sine. +See also Java ""Math.sin"". +This method returns a double. +"," +SIN(ANGLE) +" + +"Functions (Numeric)","SINH"," +SINH(numeric) +"," +Calculate the hyperbolic sine. +See also Java ""Math.sinh"". +This method returns a double. +"," +SINH(ANGLE) +" + +"Functions (Numeric)","TAN"," +TAN(numeric) +"," +Calculate the trigonometric tangent. +See also Java ""Math.tan"". +This method returns a double. +"," +TAN(ANGLE) +" + +"Functions (Numeric)","TANH"," +TANH(numeric) +"," +Calculate the hyperbolic tangent. +See also Java ""Math.tanh"". +This method returns a double. +"," +TANH(X) +" + +"Functions (Numeric)","ATAN2"," +@h2@ ATAN2(numeric, numeric) +"," +Calculate the angle when converting the rectangular coordinates to polar coordinates. +See also Java ""Math.atan2"". +This method returns a double. +"," +ATAN2(X, Y) +" + +"Functions (Numeric)","BITAND"," +@h2@ BITAND(expression, expression) +"," +The bitwise AND operation. +Arguments should have TINYINT, SMALLINT, INTEGER, BIGINT, BINARY, or BINARY VARYING data type. +This function returns result of the same data type. + +For aggregate function see [BIT_AND_AGG](https://h2database.com/html/functions-aggregate.html#bit_and_agg). +"," +BITAND(A, B) +" + +"Functions (Numeric)","BITOR"," +@h2@ BITOR(expression, expression) +"," +The bitwise OR operation. +Arguments should have TINYINT, SMALLINT, INTEGER, BIGINT, BINARY, or BINARY VARYING data type. +This function returns result of the same data type. + +For aggregate function see [BIT_OR_AGG](https://h2database.com/html/functions-aggregate.html#bit_or_agg). +"," +BITOR(A, B) +" + +"Functions (Numeric)","BITXOR"," +@h2@ BITXOR(expression, expression) +"," +Arguments should have TINYINT, SMALLINT, INTEGER, BIGINT, BINARY, or BINARY VARYING data type. +This function returns result of the same data type. + +For aggregate function see [BIT_XOR_AGG](https://h2database.com/html/functions-aggregate.html#bit_xor_agg). +"," +The bitwise XOR operation. +"," +BITXOR(A, B) +" + +"Functions (Numeric)","BITNOT"," +@h2@ BITNOT(expression) +"," +The bitwise NOT operation. +Argument should have TINYINT, SMALLINT, INTEGER, BIGINT, BINARY, or BINARY VARYING data type. +This function returns result of the same data type. +"," +BITNOT(A) +" + +"Functions (Numeric)","BITNAND"," +@h2@ BITNAND(expression, expression) +"," +The bitwise NAND operation equivalent to ""BITNOT(BITAND(expression, expression))"". +Arguments should have TINYINT, SMALLINT, INTEGER, BIGINT, BINARY, or BINARY VARYING data type. +This function returns result of the same data type. + +For aggregate function see [BIT_NAND_AGG](https://h2database.com/html/functions-aggregate.html#bit_nand_agg). +"," +BITNAND(A, B) +" + +"Functions (Numeric)","BITNOR"," +@h2@ BITNOR(expression, expression) +"," +The bitwise NOR operation equivalent to ""BITNOT(BITOR(expression, expression))"". +Arguments should have TINYINT, SMALLINT, INTEGER, BIGINT, BINARY, or BINARY VARYING data type. +This function returns result of the same data type. + +For aggregate function see [BIT_NOR_AGG](https://h2database.com/html/functions-aggregate.html#bit_nor_agg). +"," +BITNOR(A, B) +" + +"Functions (Numeric)","BITXNOR"," +@h2@ BITXNOR(expression, expression) +"," +The bitwise XNOR operation equivalent to ""BITNOT(BITXOR(expression, expression))"". +Arguments should have TINYINT, SMALLINT, INTEGER, BIGINT, BINARY, or BINARY VARYING data type. +This function returns result of the same data type. + +For aggregate function see [BIT_XNOR_AGG](https://h2database.com/html/functions-aggregate.html#bit_xnor_agg). +"," +BITXNOR(A, B) +" + +"Functions (Numeric)","BITGET"," +@h2@ BITGET(expression, long) +"," +Returns true if and only if the first argument has a bit set in the +position specified by the second parameter. +The first argument should have TINYINT, SMALLINT, INTEGER, BIGINT, BINARY, or BINARY VARYING data type. +This method returns a boolean. +The second argument is zero-indexed; the least significant bit has position 0. +"," +BITGET(A, 1) +" + +"Functions (Numeric)","BITCOUNT"," +@h2@ BITCOUNT(expression) +"," +Returns count of set bits in the specified value. +Value should have TINYINT, SMALLINT, INTEGER, BIGINT, BINARY, or BINARY VARYING data type. +This method returns a long. +"," +BITCOUNT(A) +" + +"Functions (Numeric)","LSHIFT"," +@h2@ LSHIFT(expression, long) +"," +The bitwise signed left shift operation. +Shifts the first argument by the number of bits given by the second argument. +Argument should have TINYINT, SMALLINT, INTEGER, BIGINT, BINARY, or BINARY VARYING data type. +This function returns result of the same data type. + +If number of bits is negative, a signed right shift is performed instead. +For numeric values a sign bit is used for left-padding (with negative offset). +If number of bits is equal to or larger than number of bits in value all bits are pushed out from the value. +For binary string arguments signed and unsigned shifts return the same results. +"," +LSHIFT(A, B) +" + +"Functions (Numeric)","RSHIFT"," +@h2@ RSHIFT(expression, long) +"," +The bitwise signed right shift operation. +Shifts the first argument by the number of bits given by the second argument. +Argument should have TINYINT, SMALLINT, INTEGER, BIGINT, BINARY, or BINARY VARYING data type. +This function returns result of the same data type. + +If number of bits is negative, a signed left shift is performed instead. +For numeric values a sign bit is used for left-padding (with positive offset). +If number of bits is equal to or larger than number of bits in value all bits are pushed out from the value. +For binary string arguments signed and unsigned shifts return the same results. +"," +RSHIFT(A, B) +" + +"Functions (Numeric)","ULSHIFT"," +@h2@ ULSHIFT(expression, long) +"," +The bitwise unsigned left shift operation. +Shifts the first argument by the number of bits given by the second argument. +Argument should have TINYINT, SMALLINT, INTEGER, BIGINT, BINARY, or BINARY VARYING data type. +This function returns result of the same data type. + +If number of bits is negative, an unsigned right shift is performed instead. +If number of bits is equal to or larger than number of bits in value all bits are pushed out from the value. +For binary string arguments signed and unsigned shifts return the same results. +"," +ULSHIFT(A, B) +" + +"Functions (Numeric)","URSHIFT"," +@h2@ URSHIFT(expression, long) +"," +The bitwise unsigned right shift operation. +Shifts the first argument by the number of bits given by the second argument. +Argument should have TINYINT, SMALLINT, INTEGER, BIGINT, BINARY, or BINARY VARYING data type. +This function returns result of the same data type. + +If number of bits is negative, an unsigned left shift is performed instead. +If number of bits is equal to or larger than number of bits in value all bits are pushed out from the value. +For binary string arguments signed and unsigned shifts return the same results. +"," +URSHIFT(A, B) +" + +"Functions (Numeric)","ROTATELEFT"," +@h2@ ROTATELEFT(expression, long) +"," +The bitwise left rotation operation. +Rotates the first argument by the number of bits given by the second argument. +Argument should have TINYINT, SMALLINT, INTEGER, BIGINT, BINARY, or BINARY VARYING data type. +This function returns result of the same data type. +"," +ROTATELEFT(A, B) +" + +"Functions (Numeric)","ROTATERIGHT"," +@h2@ ROTATERIGHT(expression, long) +"," +The bitwise right rotation operation. +Rotates the first argument by the number of bits given by the second argument. +Argument should have TINYINT, SMALLINT, INTEGER, BIGINT, BINARY, or BINARY VARYING data type. +This function returns result of the same data type. +"," +ROTATERIGHT(A, B) +" + +"Functions (Numeric)","MOD"," +MOD(dividendNumeric, divisorNumeric) +"," +The modulus expression. + +Result has the same type as divisor. +Result is NULL if either of arguments is NULL. +If divisor is 0, an exception is raised. +Result has the same sign as dividend or is equal to 0. + +Usually arguments should have scale 0, but it isn't required by H2. +"," +MOD(A, B) +" + +"Functions (Numeric)","CEIL"," +{ CEIL | CEILING } (numeric) +"," +Returns the smallest integer value that is greater than or equal to the argument. +This method returns value of the same type as argument, but with scale set to 0 and adjusted precision, if applicable. +"," +CEIL(A) +" + +"Functions (Numeric)","DEGREES"," +@h2@ DEGREES(numeric) +"," +See also Java ""Math.toDegrees"". +This method returns a double. +"," +DEGREES(A) +" + +"Functions (Numeric)","EXP"," +EXP(numeric) +"," +See also Java ""Math.exp"". +This method returns a double. +"," +EXP(A) +" + +"Functions (Numeric)","FLOOR"," +FLOOR(numeric) +"," +Returns the largest integer value that is less than or equal to the argument. +This method returns value of the same type as argument, but with scale set to 0 and adjusted precision, if applicable. +"," +FLOOR(A) +" + +"Functions (Numeric)","LN"," +LN(numeric) +"," +Calculates the natural (base e) logarithm as a double value. +Argument must be a positive numeric value. +"," +LN(A) +" + +"Functions (Numeric)","LOG"," +LOG({baseNumeric, numeric | @c@{numeric}}) +"," +Calculates the logarithm with specified base as a double value. +Argument and base must be positive numeric values. +Base cannot be equal to 1. + +The default base is e (natural logarithm), in the PostgreSQL mode the default base is base 10. +In MSSQLServer mode the optional base is specified after the argument. + +Single-argument variant of LOG function is deprecated, use [LN](https://h2database.com/html/functions.html#ln) +or [LOG10](https://h2database.com/html/functions.html#log10) instead. +"," +LOG(2, A) +" + +"Functions (Numeric)","LOG10"," +LOG10(numeric) +"," +Calculates the base 10 logarithm as a double value. +Argument must be a positive numeric value. +"," +LOG10(A) +" + +"Functions (Numeric)","ORA_HASH"," +@c@ ORA_HASH(expression [, bucketLong [, seedLong]]) +"," +Computes a hash value. +Optional bucket argument determines the maximum returned value. +This argument should be between 0 and 4294967295, default is 4294967295. +Optional seed argument is combined with the given expression to return the different values for the same expression. +This argument should be between 0 and 4294967295, default is 0. +This method returns a long value between 0 and the specified or default bucket value inclusive. +"," +ORA_HASH(A) +" + +"Functions (Numeric)","RADIANS"," +@h2@ RADIANS(numeric) +"," +See also Java ""Math.toRadians"". +This method returns a double. +"," +RADIANS(A) +" + +"Functions (Numeric)","SQRT"," +SQRT(numeric) +"," +See also Java ""Math.sqrt"". +This method returns a double. +"," +SQRT(A) +" + +"Functions (Numeric)","PI"," +@h2@ PI() +"," +See also Java ""Math.PI"". +This method returns a double. +"," +PI() +" + +"Functions (Numeric)","POWER"," +POWER(numeric, numeric) +"," +See also Java ""Math.pow"". +This method returns a double. +"," +POWER(A, B) +" + +"Functions (Numeric)","RAND"," +@h2@ { RAND | RANDOM } ( [ int ] ) +"," +Calling the function without parameter returns the next a pseudo random number. +Calling it with an parameter seeds the session's random number generator. +This method returns a double between 0 (including) and 1 (excluding). +"," +RAND() +" + +"Functions (Numeric)","RANDOM_UUID"," +@h2@ { RANDOM_UUID | UUID } () +"," +Returns a new UUID with 122 pseudo random bits. + +Please note that using an index on randomly generated data will +result on poor performance once there are millions of rows in a table. +The reason is that the cache behavior is very bad with randomly distributed data. +This is a problem for any database system. +"," +RANDOM_UUID() +" + +"Functions (Numeric)","ROUND"," +@h2@ ROUND(numeric [, digitsInt]) +"," +Rounds to a number of fractional digits. +This method returns value of the same type as argument, but with adjusted precision and scale, if applicable. +"," +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) +"," +Generates a number of cryptographically secure random numbers. +This method returns bytes. +"," +CALL SECURE_RAND(16) +" + +"Functions (Numeric)","SIGN"," +@h2@ SIGN( { numeric | interval } ) +"," +Returns -1 if the value is smaller than 0, 0 if zero or NaN, and otherwise 1. +"," +SIGN(N) +" + +"Functions (Numeric)","ENCRYPT"," +@h2@ ENCRYPT(algorithmString, keyBytes, dataBytes) +"," +Encrypts data using a key. +The supported algorithm is AES. +The block size is 16 bytes. +This method returns bytes. +"," +CALL ENCRYPT('AES', '00', STRINGTOUTF8('Test')) +" + +"Functions (Numeric)","DECRYPT"," +@h2@ DECRYPT(algorithmString, keyBytes, dataBytes) +"," +Decrypts data using a key. +The supported algorithm is AES. +The block size is 16 bytes. +This method returns bytes. +"," +CALL TRIM(CHAR(0) FROM UTF8TOSTRING( + DECRYPT('AES', '00', '3fabb4de8f1ee2e97d7793bab2db1116'))) +" + +"Functions (Numeric)","HASH"," +@h2@ HASH(algorithmString, expression [, iterationInt]) +"," +Calculate the hash value using an algorithm, and repeat this process for a number of iterations. + +This function supports MD5, SHA-1, SHA-224, SHA-256, SHA-384, SHA-512, SHA3-224, SHA3-256, SHA3-384, and SHA3-512 +algorithms. +SHA-224, SHA-384, and SHA-512 may be unavailable in some JREs. + +MD5 and SHA-1 algorithms should not be considered as secure. + +If this function is used to encrypt a password, a random salt should be concatenated with a password and this salt and +result of the function should be stored to prevent a rainbow table attack and number of iterations should be large +enough to slow down a dictionary or a brute force attack. + +This method returns bytes. +"," +CALL HASH('SHA-256', 'Text', 1000) +CALL HASH('SHA3-256', X'0102') +" + +"Functions (Numeric)","TRUNC"," +@h2@ { TRUNC | TRUNCATE } ( { {numeric [, digitsInt] } + | @c@ { timestamp | timestampWithTimeZone | date | timestampString } } ) +"," +When a numeric argument is specified, truncates it to a number of digits (to the next value closer to 0) +and returns value of the same type as argument, but with adjusted precision and scale, if applicable. + +This function with datetime or string argument is deprecated, use +[DATE_TRUNC](https://h2database.com/html/functions.html#date_trunc) instead. +When used with a timestamp, truncates the timestamp to a date (day) value +and returns a timestamp with or without time zone depending on type of the argument. +When used with a date, returns a timestamp at start of this date. +When used with a timestamp as string, truncates the timestamp to a date (day) value +and returns a timestamp without time zone. +"," +TRUNCATE(N, 2) +" + +"Functions (Numeric)","COMPRESS"," +@h2@ COMPRESS(dataBytes [, algorithmString]) +"," +Compresses the data using the specified compression algorithm. +Supported algorithms are: LZF (faster but lower compression; default), and DEFLATE (higher compression). +Compression does not always reduce size. Very small objects and objects with little redundancy may get larger. +This method returns bytes. +"," +COMPRESS(STRINGTOUTF8('Test')) +" + +"Functions (Numeric)","EXPAND"," +@h2@ EXPAND(bytes) +"," +Expands data that was compressed using the COMPRESS function. +This method returns bytes. +"," +UTF8TOSTRING(EXPAND(COMPRESS(STRINGTOUTF8('Test')))) +" + +"Functions (Numeric)","ZERO"," +@h2@ ZERO() +"," +Returns the value 0. This function can be used even if numeric literals are disabled. +"," +ZERO() +" + +"Functions (String)","ASCII"," +@h2@ ASCII(string) +"," +Returns the ASCII value of the first character in the string. +This method returns an int. +"," +ASCII('Hi') +" +"Functions (String)","BIT_LENGTH"," +@h2@ BIT_LENGTH(bytes) +"," +Returns the number of bits in a binary string. +This method returns a long. +"," +BIT_LENGTH(NAME) +" + +"Functions (String)","CHAR_LENGTH"," +{ CHAR_LENGTH | CHARACTER_LENGTH | @c@ { LENGTH } } ( string ) +"," +Returns the number of characters in a character string. +This method returns a long. +"," +CHAR_LENGTH(NAME) +" + +"Functions (String)","OCTET_LENGTH"," +OCTET_LENGTH(bytes) +"," +Returns the number of bytes in a binary string. +This method returns a long. +"," +OCTET_LENGTH(NAME) +" + +"Functions (String)","CHAR"," +@h2@ { CHAR | CHR } ( int ) +"," +Returns the character that represents the ASCII value. +This method returns a string. +"," +CHAR(65) +" + +"Functions (String)","CONCAT"," +@h2@ CONCAT(string, string [,...]) +"," +Combines strings. +Unlike with the operator ""||"", NULL parameters are ignored, +and do not cause the result to become NULL. +If all parameters are NULL the result is an empty string. +This method returns a string. +"," +CONCAT(NAME, '!') +" + +"Functions (String)","CONCAT_WS"," +@h2@ CONCAT_WS(separatorString, string, string [,...]) +"," +Combines strings with separator. +If separator is NULL it is treated like an empty string. +Other NULL parameters are ignored. +Remaining non-NULL parameters, if any, are concatenated with the specified separator. +If there are no remaining parameters the result is an empty string. +This method returns a string. +"," +CONCAT_WS(',', NAME, '!') +" + +"Functions (String)","DIFFERENCE"," +@h2@ DIFFERENCE(string, string) +"," +Returns the difference between the sounds of two strings. +The difference is calculated as a number of matched characters +in the same positions in SOUNDEX representations of arguments. +This method returns an int between 0 and 4 inclusive, or null if any of its parameters is null. +Note that value of 0 means that strings are not similar to each other. +Value of 4 means that strings are fully similar to each other (have the same SOUNDEX representation). +"," +DIFFERENCE(T1.NAME, T2.NAME) +" + +"Functions (String)","HEXTORAW"," +@h2@ HEXTORAW(string) +"," +Converts a hex representation of a string to a string. +4 hex characters per string character are used. +"," +HEXTORAW(DATA) +" + +"Functions (String)","RAWTOHEX"," +@h2@ RAWTOHEX({string|bytes}) +"," +Converts a string or bytes to the hex representation. +4 hex characters per string character are used. +This method returns a string. +"," +RAWTOHEX(DATA) +" + +"Functions (String)","INSERT Function"," +@h2@ INSERT(originalString, startInt, lengthInt, addString) +"," +Inserts a additional string into the original string at a specified start position. +The length specifies the number of characters that are removed at the start position in the original string. +This method returns a string. +"," +INSERT(NAME, 1, 1, ' ') +" + +"Functions (String)","LOWER"," +{ LOWER | @c@ { LCASE } } ( string ) +"," +Converts a string to lowercase. +"," +LOWER(NAME) +" + +"Functions (String)","UPPER"," +{ UPPER | @c@ { UCASE } } ( string ) +"," +Converts a string to uppercase. +"," +UPPER(NAME) +" + +"Functions (String)","LEFT"," +@h2@ LEFT(string, int) +"," +Returns the leftmost number of characters. +"," +LEFT(NAME, 3) +" + +"Functions (String)","RIGHT"," +@h2@ RIGHT(string, int) +"," +Returns the rightmost number of characters. +"," +RIGHT(NAME, 3) +" + +"Functions (String)","LOCATE"," +@h2@ { LOCATE(searchString, string [, startInt]) } + | @c@ { INSTR(string, searchString, [, startInt]) } + | @c@ { POSITION(searchString, string) } +"," +Returns the location of a search string in a string. +If a start position is used, the characters before it are ignored. +If position is negative, the rightmost location is returned. +0 is returned if the search string is not found. +Please note this function is case sensitive, even if the parameters are not. +"," +LOCATE('.', NAME) +" + +"Functions (String)","LPAD"," +@h2@ LPAD(string, int[, paddingString]) +"," +Left pad the string to the specified length. +If the length is shorter than the string, it will be truncated at the end. +If the padding string is not set, spaces will be used. +"," +LPAD(AMOUNT, 10, '*') +" + +"Functions (String)","RPAD"," +@h2@ RPAD(string, int[, paddingString]) +"," +Right pad the string to the specified length. +If the length is shorter than the string, it will be truncated. +If the padding string is not set, spaces will be used. +"," +RPAD(TEXT, 10, '-') +" + +"Functions (String)","LTRIM"," +@c@ LTRIM(string) +"," +Removes all leading spaces from a string. + +This function is deprecated, use [TRIM](https://h2database.com/html/functions.html#trim) instead of it. +"," +LTRIM(NAME) +" + +"Functions (String)","RTRIM"," +@c@ RTRIM(string) +"," +Removes all trailing spaces from a string. + +This function is deprecated, use [TRIM](https://h2database.com/html/functions.html#trim) instead of it. +"," +RTRIM(NAME) +" + +"Functions (String)","TRIM"," +TRIM ( [ [ LEADING | TRAILING | BOTH ] [ string ] FROM ] string ) +"," +Removes all leading spaces, trailing spaces, or spaces at both ends, from a string. +Other characters can be removed as well. +"," +TRIM(BOTH '_' FROM NAME) +" + +"Functions (String)","REGEXP_REPLACE"," +@h2@ REGEXP_REPLACE(inputString, regexString, replacementString [, flagsString]) +"," +Replaces each substring that matches a regular expression. +For details, see the Java ""String.replaceAll()"" method. +If any parameter is null (except optional flagsString parameter), the result is null. + +Flags values are limited to 'i', 'c', 'n', 'm'. Other symbols cause exception. +Multiple symbols could be used in one flagsString parameter (like 'im'). +Later flags override first ones, for example 'ic' is equivalent to case sensitive matching 'c'. + +'i' enables case insensitive matching (Pattern.CASE_INSENSITIVE) + +'c' disables case insensitive matching (Pattern.CASE_INSENSITIVE) + +'n' allows the period to match the newline character (Pattern.DOTALL) + +'m' enables multiline mode (Pattern.MULTILINE) + +"," +REGEXP_REPLACE('Hello World', ' +', ' ') +REGEXP_REPLACE('Hello WWWWorld', 'w+', 'W', 'i') +" + +"Functions (String)","REGEXP_LIKE"," +@h2@ REGEXP_LIKE(inputString, regexString [, flagsString]) +"," +Matches string to a regular expression. +For details, see the Java ""Matcher.find()"" method. +If any parameter is null (except optional flagsString parameter), the result is null. + +Flags values are limited to 'i', 'c', 'n', 'm'. Other symbols cause exception. +Multiple symbols could be used in one flagsString parameter (like 'im'). +Later flags override first ones, for example 'ic' is equivalent to case sensitive matching 'c'. + +'i' enables case insensitive matching (Pattern.CASE_INSENSITIVE) + +'c' disables case insensitive matching (Pattern.CASE_INSENSITIVE) + +'n' allows the period to match the newline character (Pattern.DOTALL) + +'m' enables multiline mode (Pattern.MULTILINE) + +"," +REGEXP_LIKE('Hello World', '[A-Z ]*', 'i') +" + +"Functions (String)","REGEXP_SUBSTR"," +@h2@ REGEXP_SUBSTR(inputString, regexString [, positionInt, occurrenceInt, flagsString, groupInt]) +"," +Matches string to a regular expression and returns the matched substring. +For details, see the java.util.regex.Pattern and related functionality. + +The parameter position specifies where in inputString the match should start. Occurrence indicates +which occurrence of pattern in inputString to search for. + +Flags values are limited to 'i', 'c', 'n', 'm'. Other symbols cause exception. +Multiple symbols could be used in one flagsString parameter (like 'im'). +Later flags override first ones, for example 'ic' is equivalent to case sensitive matching 'c'. + +'i' enables case insensitive matching (Pattern.CASE_INSENSITIVE) + +'c' disables case insensitive matching (Pattern.CASE_INSENSITIVE) + +'n' allows the period to match the newline character (Pattern.DOTALL) + +'m' enables multiline mode (Pattern.MULTILINE) + +If the pattern has groups, the group parameter can be used to specify which group to return. + +"," +REGEXP_SUBSTR('2020-10-01', '\d{4}') +REGEXP_SUBSTR('2020-10-01', '(\d{4})-(\d{2})-(\d{2})', 1, 1, NULL, 2) +" + +"Functions (String)","REPEAT"," +@h2@ REPEAT(string, int) +"," +Returns a string repeated some number of times. +"," +REPEAT(NAME || ' ', 10) +" + +"Functions (String)","REPLACE"," +@h2@ REPLACE(string, searchString [, replacementString]) +"," +Replaces all occurrences of a search string in a text with another string. +If no replacement is specified, the search string is removed from the original string. +If any parameter is null, the result is null. +"," +REPLACE(NAME, ' ') +" + +"Functions (String)","SOUNDEX"," +@h2@ SOUNDEX(string) +"," +Returns a four character code representing the sound of a string. +This method returns a string, or null if parameter is null. +See https://en.wikipedia.org/wiki/Soundex for more information. +"," +SOUNDEX(NAME) +" + +"Functions (String)","SPACE"," +@h2@ SPACE(int) +"," +Returns a string consisting of a number of spaces. +"," +SPACE(80) +" + +"Functions (String)","STRINGDECODE"," +@h2@ STRINGDECODE(string) +"," +Converts a encoded string using the Java string literal encoding format. +Special characters are \b, \t, \n, \f, \r, \"", \\, \, \u. +This method returns a string. +"," +CALL STRINGENCODE(STRINGDECODE('Lines 1\nLine 2')) +" + +"Functions (String)","STRINGENCODE"," +@h2@ STRINGENCODE(string) +"," +Encodes special characters in a string using the Java string literal encoding format. +Special characters are \b, \t, \n, \f, \r, \"", \\, \, \u. +This method returns a string. +"," +CALL STRINGENCODE(STRINGDECODE('Lines 1\nLine 2')) +" + +"Functions (String)","STRINGTOUTF8"," +@h2@ STRINGTOUTF8(string) +"," +Encodes a string to a byte array using the UTF8 encoding format. +This method returns bytes. +"," +CALL UTF8TOSTRING(STRINGTOUTF8('This is a test')) +" + +"Functions (String)","SUBSTRING"," +SUBSTRING ( {string|bytes} FROM startInt [ FOR lengthInt ] ) + | @c@ { { SUBSTRING | SUBSTR } ( {string|bytes}, startInt [, lengthInt ] ) } +"," +Returns a substring of a string starting at a position. +If the start index is negative, then the start index is relative to the end of the string. +The length is optional. +"," +CALL SUBSTRING('[Hello]' FROM 2 FOR 5); +CALL SUBSTRING('hour' FROM 2); +" + +"Functions (String)","UTF8TOSTRING"," +@h2@ UTF8TOSTRING(bytes) +"," +Decodes a byte array in the UTF8 format to a string. +"," +CALL UTF8TOSTRING(STRINGTOUTF8('This is a test')) +" + +"Functions (String)","QUOTE_IDENT"," +@h2@ QUOTE_IDENT(string) +"," +Quotes the specified identifier. +Identifier is surrounded by double quotes. +If identifier contains double quotes they are repeated twice. +"," +QUOTE_IDENT('Column 1') +" + +"Functions (String)","XMLATTR"," +@h2@ XMLATTR(nameString, valueString) +"," +Creates an XML attribute element of the form ""name=value"". +The value is encoded as XML text. +This method returns a string. +"," +CALL XMLNODE('a', XMLATTR('href', 'https://h2database.com')) +" + +"Functions (String)","XMLNODE"," +@h2@ XMLNODE(elementString [, attributesString [, contentString [, indentBoolean]]]) +"," +Create an XML node element. +An empty or null attribute string means no attributes are set. +An empty or null content string means the node is empty. +The content is indented by default if it contains a newline. +This method returns a string. +"," +CALL XMLNODE('a', XMLATTR('href', 'https://h2database.com'), 'H2') +" + +"Functions (String)","XMLCOMMENT"," +@h2@ XMLCOMMENT(commentString) +"," +Creates an XML comment. +Two dashes (""--"") are converted to ""- -"". +This method returns a string. +"," +CALL XMLCOMMENT('Test') +" + +"Functions (String)","XMLCDATA"," +@h2@ XMLCDATA(valueString) +"," +Creates an XML CDATA element. +If the value contains ""]]>"", an XML text element is created instead. +This method returns a string. +"," +CALL XMLCDATA('data') +" + +"Functions (String)","XMLSTARTDOC"," +@h2@ XMLSTARTDOC() +"," +Returns the XML declaration. +The result is always """". +"," +CALL XMLSTARTDOC() +" + +"Functions (String)","XMLTEXT"," +@h2@ XMLTEXT(valueString [, escapeNewlineBoolean]) +"," +Creates an XML text element. +If enabled, newline and linefeed is converted to an XML entity (&#). +This method returns a string. +"," +CALL XMLTEXT('test') +" + +"Functions (String)","TO_CHAR"," +@c@ TO_CHAR(value [, formatString[, nlsParamString]]) +"," +Oracle-compatible TO_CHAR function that can format a timestamp, a number, or text. +"," +CALL TO_CHAR(TIMESTAMP '2010-01-01 00:00:00', 'DD MON, YYYY') +" + +"Functions (String)","TRANSLATE"," +@c@ TRANSLATE(value, searchString, replacementString) +"," +Oracle-compatible TRANSLATE function that replaces a sequence of characters in a string with another set of characters. +"," +CALL TRANSLATE('Hello world', 'eo', 'EO') +" + +"Functions (Time and Date)","CURRENT_DATE"," +CURRENT_DATE +"," +Returns the current date. + +These functions return the same value within a transaction (default) +or within a command depending on database mode. + +[SET TIME ZONE](https://h2database.com/html/commands.html#set_time_zone) command reevaluates the value +for these functions using the same original UTC timestamp of transaction. +"," +CURRENT_DATE +" + +"Functions (Time and Date)","CURRENT_TIME"," +CURRENT_TIME [ (int) ] +"," +Returns the current time with time zone. +If fractional seconds precision is specified it should be from 0 to 9, 0 is default. +The specified value can be used only to limit precision of a result. +The actual maximum available precision depends on operating system and JVM and can be 3 (milliseconds) or higher. +Higher precision is not available before Java 9. + +This function returns the same value within a transaction (default) +or within a command depending on database mode. + +[SET TIME ZONE](https://h2database.com/html/commands.html#set_time_zone) command reevaluates the value +for this function using the same original UTC timestamp of transaction. +"," +CURRENT_TIME +CURRENT_TIME(9) +" + +"Functions (Time and Date)","CURRENT_TIMESTAMP"," +CURRENT_TIMESTAMP [ (int) ] +"," +Returns the current timestamp with time zone. +Time zone offset is set to a current time zone offset. +If fractional seconds precision is specified it should be from 0 to 9, 6 is default. +The specified value can be used only to limit precision of a result. +The actual maximum available precision depends on operating system and JVM and can be 3 (milliseconds) or higher. +Higher precision is not available before Java 9. + +This function returns the same value within a transaction (default) +or within a command depending on database mode. + +[SET TIME ZONE](https://h2database.com/html/commands.html#set_time_zone) command reevaluates the value +for this function using the same original UTC timestamp of transaction. +"," +CURRENT_TIMESTAMP +CURRENT_TIMESTAMP(9) +" + +"Functions (Time and Date)","LOCALTIME"," +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. +The specified value can be used only to limit precision of a result. +The actual maximum available precision depends on operating system and JVM and can be 3 (milliseconds) or higher. +Higher precision is not available before Java 9. + +These functions return the same value within a transaction (default) +or within a command depending on database mode. + +[SET TIME ZONE](https://h2database.com/html/commands.html#set_time_zone) command reevaluates the value +for these functions using the same original UTC timestamp of transaction. +"," +LOCALTIME +LOCALTIME(9) +" + +"Functions (Time and Date)","LOCALTIMESTAMP"," +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. +The specified value can be used only to limit precision of a result. +The actual maximum available precision depends on operating system and JVM and can be 3 (milliseconds) or higher. +Higher precision is not available before Java 9. + +The returned value has date and time without time zone information. +If time zone has DST transitions the returned values are ambiguous during transition from DST to normal time. +For absolute timestamps use the [CURRENT_TIMESTAMP](https://h2database.com/html/functions.html#current_timestamp) +function and [TIMESTAMP WITH TIME ZONE](https://h2database.com/html/datatypes.html#timestamp_with_time_zone_type) +data type. + +These functions return the same value within a transaction (default) +or within a command depending on database mode. + +[SET TIME ZONE](https://h2database.com/html/commands.html#set_time_zone) reevaluates the value +for these functions using the same original UTC timestamp of transaction. +"," +LOCALTIMESTAMP +LOCALTIMESTAMP(9) +" + +"Functions (Time and Date)","DATEADD"," +@h2@ { DATEADD| TIMESTAMPADD } @h2@ (datetimeField, addIntLong, dateAndTime) +"," +Adds units to a date-time value. The datetimeField indicates the unit. +Use negative values to subtract units. +addIntLong may be a long value when manipulating milliseconds, +microseconds, or nanoseconds otherwise its range is restricted to int. +This method returns a value with the same type as specified value if unit is compatible with this value. +If specified field is a HOUR, MINUTE, SECOND, MILLISECOND, etc and value is a DATE value DATEADD returns combined TIMESTAMP. +Fields DAY, MONTH, YEAR, WEEK, etc are not allowed for TIME values. +Fields TIMEZONE_HOUR, TIMEZONE_MINUTE, and TIMEZONE_SECOND are only allowed for TIMESTAMP WITH TIME ZONE values. +"," +DATEADD(MONTH, 1, DATE '2001-01-31') +" + +"Functions (Time and Date)","DATEDIFF"," +@h2@ { DATEDIFF | TIMESTAMPDIFF } @h2@ (datetimeField, aDateAndTime, bDateAndTime) +"," +Returns the number of crossed unit boundaries between two date/time values. +This method returns a long. +The datetimeField indicates the unit. +Only TIMEZONE_HOUR, TIMEZONE_MINUTE, and TIMEZONE_SECOND fields use the time zone offset component. +With all other fields if date/time values have time zone offset component it is ignored. +"," +DATEDIFF(YEAR, T1.CREATED, T2.CREATED) +" + +"Functions (Time and Date)","DATE_TRUNC"," +@h2@ DATE_TRUNC (datetimeField, dateAndTime) +"," +Truncates the specified date-time value to the specified field. +"," +DATE_TRUNC(DAY, TIMESTAMP '2010-01-03 10:40:00'); +" + +"Functions (Time and Date)","DAYNAME"," +@h2@ DAYNAME(dateAndTime) +"," +Returns the name of the day (in English). +"," +DAYNAME(CREATED) +" + +"Functions (Time and Date)","DAY_OF_MONTH"," +@c@ DAY_OF_MONTH({dateAndTime|interval}) +"," +Returns the day of the month (1-31). + +This function is deprecated, use [EXTRACT](https://h2database.com/html/functions.html#extract) instead of it. +"," +DAY_OF_MONTH(CREATED) +" + +"Functions (Time and Date)","DAY_OF_WEEK"," +@c@ DAY_OF_WEEK(dateAndTime) +"," +Returns the day of the week (1-7), locale-specific. + +This function is deprecated, use [EXTRACT](https://h2database.com/html/functions.html#extract) instead of it. +"," +DAY_OF_WEEK(CREATED) +" + +"Functions (Time and Date)","ISO_DAY_OF_WEEK"," +@c@ ISO_DAY_OF_WEEK(dateAndTime) +"," +Returns the ISO day of the week (1 means Monday). + +This function is deprecated, use [EXTRACT](https://h2database.com/html/functions.html#extract) instead of it. +"," +ISO_DAY_OF_WEEK(CREATED) +" + +"Functions (Time and Date)","DAY_OF_YEAR"," +@c@ DAY_OF_YEAR({dateAndTime|interval}) +"," +Returns the day of the year (1-366). + +This function is deprecated, use [EXTRACT](https://h2database.com/html/functions.html#extract) instead of it. +"," +DAY_OF_YEAR(CREATED) +" + +"Functions (Time and Date)","EXTRACT"," +EXTRACT ( datetimeField FROM { dateAndTime | interval }) +"," +Returns a value of the specific time unit from a date/time value. +This method returns a numeric value with EPOCH field and +an int for all other fields. +"," +EXTRACT(SECOND FROM CURRENT_TIMESTAMP) +" + +"Functions (Time and Date)","FORMATDATETIME"," +@h2@ FORMATDATETIME ( dateAndTime, formatString +[ , localeString [ , timeZoneString ] ] ) +"," +Formats a date, time or timestamp as a string. +The most important format characters are: +y year, M month, d day, H hour, m minute, s second. +For details of the format, see ""java.time.format.DateTimeFormatter"". + +If timeZoneString is specified, it is used in formatted string if formatString has time zone. +If TIMESTAMP WITH TIME ZONE is passed and timeZoneString is specified, +the timestamp is converted to the specified time zone and its UTC value is preserved. + +This method returns a string. +"," +CALL FORMATDATETIME(TIMESTAMP '2001-02-03 04:05:06', + 'EEE, d MMM yyyy HH:mm:ss z', 'en', 'GMT') +" + +"Functions (Time and Date)","HOUR"," +@c@ HOUR({dateAndTime|interval}) +"," +Returns the hour (0-23) from a date/time value. + +This function is deprecated, use [EXTRACT](https://h2database.com/html/functions.html#extract) instead of it. +"," +HOUR(CREATED) +" + +"Functions (Time and Date)","MINUTE"," +@c@ MINUTE({dateAndTime|interval}) +"," +Returns the minute (0-59) from a date/time value. + +This function is deprecated, use [EXTRACT](https://h2database.com/html/functions.html#extract) instead of it. +"," +MINUTE(CREATED) +" + +"Functions (Time and Date)","MONTH"," +@c@ MONTH({dateAndTime|interval}) +"," +Returns the month (1-12) from a date/time value. + +This function is deprecated, use [EXTRACT](https://h2database.com/html/functions.html#extract) instead of it. +"," +MONTH(CREATED) +" + +"Functions (Time and Date)","MONTHNAME"," +@h2@ MONTHNAME(dateAndTime) +"," +Returns the name of the month (in English). +"," +MONTHNAME(CREATED) +" + +"Functions (Time and Date)","PARSEDATETIME"," +@h2@ PARSEDATETIME(string, formatString +[, localeString [, timeZoneString]]) +"," +Parses a string and returns a TIMESTAMP WITH TIME ZONE value. +The most important format characters are: +y year, M month, d day, H hour, m minute, s second. +For details of the format, see ""java.time.format.DateTimeFormatter"". + +If timeZoneString is specified, it is used as default. +"," +CALL PARSEDATETIME('Sat, 3 Feb 2001 03:05:06 GMT', + 'EEE, d MMM yyyy HH:mm:ss z', 'en', 'GMT') +" + +"Functions (Time and Date)","QUARTER"," +@c@ QUARTER(dateAndTime) +"," +Returns the quarter (1-4) from a date/time value. + +This function is deprecated, use [EXTRACT](https://h2database.com/html/functions.html#extract) instead of it. +"," +QUARTER(CREATED) +" + +"Functions (Time and Date)","SECOND"," +@c@ SECOND(dateAndTime) +"," +Returns the second (0-59) from a date/time value. + +This function is deprecated, use [EXTRACT](https://h2database.com/html/functions.html#extract) instead of it. +"," +SECOND(CREATED|interval) +" + +"Functions (Time and Date)","WEEK"," +@c@ WEEK(dateAndTime) +"," +Returns the week (1-53) from a date/time value. + +This function is deprecated, use [EXTRACT](https://h2database.com/html/functions.html#extract) instead of it. + +This function uses the current system locale. +"," +WEEK(CREATED) +" + +"Functions (Time and Date)","ISO_WEEK"," +@c@ ISO_WEEK(dateAndTime) +"," +Returns the ISO week (1-53) from a date/time value. + +This function is deprecated, use [EXTRACT](https://h2database.com/html/functions.html#extract) instead of it. + +This function uses the ISO definition when +first week of year should have at least four days +and week is started with Monday. +"," +ISO_WEEK(CREATED) +" + +"Functions (Time and Date)","YEAR"," +@c@ YEAR({dateAndTime|interval}) +"," +Returns the year from a date/time value. + +This function is deprecated, use [EXTRACT](https://h2database.com/html/functions.html#extract) instead of it. +"," +YEAR(CREATED) +" + +"Functions (Time and Date)","ISO_YEAR"," +@c@ ISO_YEAR(dateAndTime) +"," +Returns the ISO week year from a date/time value. + +This function is deprecated, use [EXTRACT](https://h2database.com/html/functions.html#extract) instead of it. +"," +ISO_YEAR(CREATED) +" + +"Functions (System)","ABORT_SESSION"," +@h2@ ABORT_SESSION(sessionInt) +"," +Cancels the currently executing statement of another session. Closes the session and releases the allocated resources. +Returns true if the session was closed, false if no session with the given id was found. + +If a client was connected while its session was aborted it will see an error. + +Admin rights are required to execute this command. +"," +CALL ABORT_SESSION(3) +" + +"Functions (System)","ARRAY_GET"," +@c@ ARRAY_GET(arrayExpression, indexExpression) +"," +Returns element at the specified 1-based index from an array. + +This function is deprecated, use +[array element reference](https://www.h2database.com/html/grammar.html#array_element_reference) instead of it. + +Returns NULL if array or index is NULL. +"," +CALL ARRAY_GET(ARRAY['Hello', 'World'], 2) +" + +"Functions (System)","CARDINALITY"," +{ CARDINALITY | @c@ { ARRAY_LENGTH } } (arrayExpression) +"," +Returns the length of an array. +Returns NULL if the specified array is NULL. +"," +CALL CARDINALITY(ARRAY['Hello', 'World']) +" + +"Functions (System)","ARRAY_CONTAINS"," +@h2@ ARRAY_CONTAINS(arrayExpression, value) +"," +Returns a boolean TRUE if the array contains the value or FALSE if it does not contain it. +Returns NULL if the specified array is NULL. +"," +CALL ARRAY_CONTAINS(ARRAY['Hello', 'World'], 'Hello') +" + +"Functions (System)","ARRAY_CAT"," +@c@ ARRAY_CAT(arrayExpression, arrayExpression) +"," +Returns the concatenation of two arrays. + +This function is deprecated, use ""||"" instead of it. + +Returns NULL if any parameter is NULL. +"," +CALL ARRAY_CAT(ARRAY[1, 2], ARRAY[3, 4]) +" + +"Functions (System)","ARRAY_APPEND"," +@c@ ARRAY_APPEND(arrayExpression, value) +"," +Append an element to the end of an array. + +This function is deprecated, use ""||"" instead of it. + +Returns NULL if any parameter is NULL. +"," +CALL ARRAY_APPEND(ARRAY[1, 2], 3) +" + +"Functions (System)","ARRAY_MAX_CARDINALITY"," +ARRAY_MAX_CARDINALITY(arrayExpression) +"," +Returns the maximum allowed array cardinality (length) of the declared data type of argument. +"," +SELECT ARRAY_MAX_CARDINALITY(COL1) FROM TEST FETCH FIRST ROW ONLY; +" + +"Functions (System)","TRIM_ARRAY"," +TRIM_ARRAY(arrayExpression, int) +"," +Removes the specified number of elements from the end of the array. + +Returns NULL if second parameter is NULL or if first parameter is NULL and second parameter is not negative. +Throws exception if second parameter is negative or larger than number of elements in array. +Otherwise returns the truncated array. +"," +CALL TRIM_ARRAY(ARRAY[1, 2, 3, 4], 1) +" + +"Functions (System)","ARRAY_SLICE"," +@h2@ ARRAY_SLICE(arrayExpression, lowerBoundInt, upperBoundInt) +"," +Returns elements from the array as specified by the lower and upper bound parameters. +Both parameters are inclusive and the first element has index 1, i.e. ARRAY_SLICE(a, 2, 2) has only the second element. +Returns NULL if any parameter is NULL or if an index is out of bounds. +"," +CALL ARRAY_SLICE(ARRAY[1, 2, 3, 4], 1, 3) +" + +"Functions (System)","AUTOCOMMIT"," +@h2@ AUTOCOMMIT() +"," +Returns true if auto commit is switched on for this session. +"," +AUTOCOMMIT() +" + +"Functions (System)","CANCEL_SESSION"," +@h2@ CANCEL_SESSION(sessionInt) +"," +Cancels the currently executing statement of another session. +Returns true if the statement was canceled, false if the session is closed or no statement is currently executing. + +Admin rights are required to execute this command. +"," +CANCEL_SESSION(3) +" + +"Functions (System)","CASEWHEN Function"," +@c@ CASEWHEN(boolean, aValue, bValue) +"," +Returns 'aValue' if the boolean expression is true, otherwise 'bValue'. + +This function is deprecated, use [CASE](https://h2database.com/html/grammar.html#searched_case) instead of it. +"," +CASEWHEN(ID=1, 'A', 'B') +" + +"Functions (System)","COALESCE"," +{ COALESCE | @c@ { NVL } } (aValue, bValue [,...]) + | @c@ IFNULL(aValue, bValue) +"," +Returns the first value that is not null. +"," +COALESCE(A, B, C) +" + +"Functions (System)","CONVERT"," +@c@ CONVERT(value, dataTypeOrDomain) +"," +Converts a value to another data type. + +This function is deprecated, use [CAST](https://h2database.com/html/grammar.html#cast_specification) instead of it. +"," +CONVERT(NAME, INT) +" + +"Functions (System)","CURRVAL"," +@c@ CURRVAL( [ schemaNameString, ] sequenceString ) +"," +Returns the latest generated value of the sequence for the current session. +Current value may only be requested after generation of the sequence value in the current session. +This method exists only for compatibility, when it isn't required use +[CURRENT VALUE FOR sequenceName](https://h2database.com/html/grammar.html#sequence_value_expression) +instead. +If the schema name is not set, the current schema is used. +When sequence is not found, the uppercase name is also checked. +This method returns a long. +"," +CURRVAL('TEST_SEQ') +" + +"Functions (System)","CSVWRITE"," +@h2@ CSVWRITE ( fileNameString, queryString [, csvOptions [, lineSepString] ] ) +"," +Writes a CSV (comma separated values). The file is overwritten if it exists. +If only a file name is specified, it will be written to the current working directory. +For each parameter, NULL means the default value should be used. +The default charset is the default value for this system, and the default field separator is a comma. + +The values are converted to text using the default string representation; +if another conversion is required you need to change the select statement accordingly. +The parameter nullString is used when writing NULL (by default nothing is written +when NULL appears). The default line separator is the default value for this +system (system property ""line.separator""). + +The returned value is the number or rows written. +Admin rights are required to execute this command. +"," +CALL CSVWRITE('data/test.csv', 'SELECT * FROM TEST'); +CALL CSVWRITE('data/test2.csv', 'SELECT * FROM TEST', 'charset=UTF-8 fieldSeparator=|'); +-- Write a tab-separated file +CALL CSVWRITE('data/test.tsv', 'SELECT * FROM TEST', 'charset=UTF-8 fieldSeparator=' || CHAR(9)); +" + +"Functions (System)","CURRENT_SCHEMA"," +CURRENT_SCHEMA | @c@ SCHEMA() +"," +Returns the name of the default schema for this session. +"," +CALL CURRENT_SCHEMA +" + +"Functions (System)","CURRENT_CATALOG"," +CURRENT_CATALOG | @c@ DATABASE() +"," +Returns the name of the database. +"," +CALL CURRENT_CATALOG +" + +"Functions (System)","DATABASE_PATH"," +@h2@ DATABASE_PATH() +"," +Returns the directory of the database files and the database name, if it is file based. +Returns NULL otherwise. +"," +CALL DATABASE_PATH(); +" + +"Functions (System)","DATA_TYPE_SQL"," +@h2@ DATA_TYPE_SQL +@h2@ (objectSchemaString, objectNameString, objectTypeString, typeIdentifierString) +"," +Returns SQL representation of data type of the specified +constant, domain, table column, routine result or argument. + +For constants object type is 'CONSTANT' and type identifier is the value of +""INFORMATION_SCHEMA.CONSTANTS.DTD_IDENTIFIER"". + +For domains object type is 'DOMAIN' and type identifier is the value of +""INFORMATION_SCHEMA.DOMAINS.DTD_IDENTIFIER"". + +For columns object type is 'TABLE' and type identifier is the value of +""INFORMATION_SCHEMA.COLUMNS.DTD_IDENTIFIER"". + +For routines object name is the value of ""INFORMATION_SCHEMA.ROUTINES.SPECIFIC_NAME"", +object type is 'ROUTINE', and type identifier is the value of +""INFORMATION_SCHEMA.ROUTINES.DTD_IDENTIFIER"" for data type of the result and the value of +""INFORMATION_SCHEMA.PARAMETERS.DTD_IDENTIFIER"" for data types of arguments. +Aggregate functions aren't supported by this function, because their data type isn't statically known. + +This function returns NULL if any argument is NULL, object type is not valid, or object isn't found. +"," +DATA_TYPE_SQL('PUBLIC', 'C', 'CONSTANT', 'TYPE') +DATA_TYPE_SQL('PUBLIC', 'D', 'DOMAIN', 'TYPE') +DATA_TYPE_SQL('PUBLIC', 'T', 'TABLE', '1') +DATA_TYPE_SQL('PUBLIC', 'R_1', 'ROUTINE', 'RESULT') +DATA_TYPE_SQL('PUBLIC', 'R_1', 'ROUTINE', '1') +COALESCE( + QUOTE_IDENT(DOMAIN_SCHEMA) || '.' || QUOTE_IDENT(DOMAIN_NAME), + DATA_TYPE_SQL(TABLE_SCHEMA, TABLE_NAME, 'TABLE', DTD_IDENTIFIER)) +" + +"Functions (System)","DB_OBJECT_ID"," +@h2@ DB_OBJECT_ID({{'ROLE'|'SETTING'|'SCHEMA'|'USER'}, objectNameString + | {'CONSTANT'|'CONSTRAINT'|'DOMAIN'|'INDEX'|'ROUTINE'|'SEQUENCE' + |'SYNONYM'|'TABLE'|'TRIGGER'}, schemaNameString, objectNameString }) +"," +Returns internal identifier of the specified database object as integer value or NULL if object doesn't exist. + +Admin rights are required to execute this function. +"," +CALL DB_OBJECT_ID('ROLE', 'MANAGER'); +CALL DB_OBJECT_ID('TABLE', 'PUBLIC', 'MY_TABLE'); +" + +"Functions (System)","DB_OBJECT_SQL"," +@h2@ DB_OBJECT_SQL({{'ROLE'|'SETTING'|'SCHEMA'|'USER'}, objectNameString + | {'CONSTANT'|'CONSTRAINT'|'DOMAIN'|'INDEX'|'ROUTINE'|'SEQUENCE' + |'SYNONYM'|'TABLE'|'TRIGGER'}, schemaNameString, objectNameString }) +"," +Returns internal SQL definition of the specified database object or NULL if object doesn't exist +or it is a system object without SQL definition. + +This function should not be used to analyze structure of the object by machine code. +Internal SQL representation may contain undocumented non-standard clauses +and may be different in different versions of H2. +Objects are described in the ""INFORMATION_SCHEMA"" in machine-readable way. + +Admin rights are required to execute this function. +"," +CALL DB_OBJECT_SQL('ROLE', 'MANAGER'); +CALL DB_OBJECT_SQL('TABLE', 'PUBLIC', 'MY_TABLE'); +" + +"Functions (System)","DECODE"," +@c@ DECODE(value, whenValue, thenValue [,...]) +"," +Returns the first matching value. NULL is considered to match NULL. +If no match was found, then NULL or the last parameter (if the parameter count is even) is returned. +This function is provided for Oracle compatibility, +use [CASE](https://h2database.com/html/grammar.html#case_expression) instead of it. +"," +CALL DECODE(RAND()>0.5, 0, 'Red', 1, 'Black'); +" + +"Functions (System)","DISK_SPACE_USED"," +@h2@ DISK_SPACE_USED(tableNameString) +"," +Returns the approximate amount of space used by the table specified. +Does not currently take into account indexes or LOB's. +This function may be expensive since it has to load every page in the table. +"," +CALL DISK_SPACE_USED('my_table'); +" + +"Functions (System)","SIGNAL"," +@h2@ SIGNAL(sqlStateString, messageString) +"," +Throw an SQLException with the passed SQLState and reason. +"," +CALL SIGNAL('23505', 'Duplicate user ID: ' || user_id); +" + +"Functions (System)","ESTIMATED_ENVELOPE"," +@h2@ ESTIMATED_ENVELOPE(tableNameString, columnNameString) +"," +Returns the estimated minimum bounding box that encloses all specified GEOMETRY values. +Only 2D coordinate plane is supported. +NULL values are ignored. +Column must have a spatial index. +This function is fast, but estimation may include uncommitted data (including data from other transactions), +may return approximate bounds, or be different with actual value due to other reasons. +Use with caution. +If estimation is not available this function returns NULL. +For accurate and reliable result use ESTIMATE aggregate function instead. +"," +CALL ESTIMATED_ENVELOPE('MY_TABLE', 'GEOMETRY_COLUMN'); +" + +"Functions (System)","FILE_READ"," +@h2@ FILE_READ(fileNameString [,encodingString]) +"," +Returns the contents of a file. If only one parameter is supplied, the data are +returned as a BLOB. If two parameters are used, the data is returned as a CLOB +(text). The second parameter is the character set to use, NULL meaning the +default character set for this system. + +File names and URLs are supported. +To read a stream from the classpath, use the prefix ""classpath:"". + +Admin rights are required to execute this command. +"," +SELECT LENGTH(FILE_READ('~/.h2.server.properties')) LEN; +SELECT FILE_READ('http://localhost:8182/stylesheet.css', NULL) CSS; +" + +"Functions (System)","FILE_WRITE"," +@h2@ FILE_WRITE(blobValue, fileNameString) +"," +Write the supplied parameter into a file. Return the number of bytes written. + +Write access to folder, and admin rights are required to execute this command. +"," +SELECT FILE_WRITE('Hello world', '/tmp/hello.txt')) LEN; +" + +"Functions (System)","GREATEST"," +@h2@ GREATEST(aValue, bValue [,...]) +"," +Returns the largest value that is not NULL, or NULL if all values are NULL. +"," +CALL GREATEST(1, 2, 3); +" + +"Functions (System)","LEAST"," +@h2@ LEAST(aValue, bValue [,...]) +"," +Returns the smallest value that is not NULL, or NULL if all values are NULL. +"," +CALL LEAST(1, 2, 3); +" + +"Functions (System)","LOCK_MODE"," +@h2@ LOCK_MODE() +"," +Returns the current lock mode. See SET LOCK_MODE. +This method returns an int. +"," +CALL LOCK_MODE(); +" + +"Functions (System)","LOCK_TIMEOUT"," +@h2@ LOCK_TIMEOUT() +"," +Returns the lock timeout of the current session (in milliseconds). +"," +LOCK_TIMEOUT() +" + +"Functions (System)","MEMORY_FREE"," +@h2@ MEMORY_FREE() +"," +Returns the free memory in KB (where 1024 bytes is a KB). +This method returns a long. +The garbage is run before returning the value. +Admin rights are required to execute this command. +"," +MEMORY_FREE() +" + +"Functions (System)","MEMORY_USED"," +@h2@ MEMORY_USED() +"," +Returns the used memory in KB (where 1024 bytes is a KB). +This method returns a long. +The garbage is run before returning the value. +Admin rights are required to execute this command. +"," +MEMORY_USED() +" + +"Functions (System)","NEXTVAL"," +@c@ NEXTVAL ( [ schemaNameString, ] sequenceString ) +"," +Increments the sequence and returns its value. +The current value of the sequence and the last identity in the current session are updated with the generated value. +Used values are never re-used, even when the transaction is rolled back. +This method exists only for compatibility, it's recommended to use the standard +[NEXT VALUE FOR sequenceName](https://h2database.com/html/grammar.html#sequence_value_expression) +instead. +If the schema name is not set, the current schema is used. +When sequence is not found, the uppercase name is also checked. +This method returns a long. +"," +NEXTVAL('TEST_SEQ') +" + +"Functions (System)","NULLIF"," +NULLIF(aValue, bValue) +"," +Returns NULL if 'a' is equal to 'b', otherwise 'a'. +"," +NULLIF(A, B) +A / NULLIF(B, 0) +" + +"Functions (System)","NVL2"," +@c@ NVL2(testValue, aValue, bValue) +"," +If the test value is null, then 'b' is returned. Otherwise, 'a' is returned. +The data type of the returned value is the data type of 'a' if this is a text type. + +This function is provided for Oracle compatibility, +use [CASE](https://h2database.com/html/grammar.html#case_expression) +or [COALESCE](https://h2database.com/html/functions.html#coalesce) instead of it. +"," +NVL2(X, 'not null', 'null') +" + +"Functions (System)","READONLY"," +@h2@ READONLY() +"," +Returns true if the database is read-only. +"," +READONLY() +" + +"Functions (System)","ROWNUM"," +@h2@ ROWNUM() +"," +Returns the number of the current row. +This method returns a long value. +It is supported for SELECT statements, as well as for DELETE and UPDATE. +The first row has the row number 1, and is calculated before ordering and grouping the result set, +but after evaluating index conditions (even when the index conditions are specified in an outer query). +Use the [ROW_NUMBER() OVER ()](https://h2database.com/html/functions-window.html#row_number) +function to get row numbers after grouping or in specified order. +"," +SELECT ROWNUM(), * FROM TEST; +SELECT ROWNUM(), * FROM (SELECT * FROM TEST ORDER BY NAME); +SELECT ID FROM (SELECT T.*, ROWNUM AS R FROM TEST T) WHERE R BETWEEN 2 AND 3; +" + +"Functions (System)","SESSION_ID"," +@h2@ SESSION_ID() +"," +Returns the unique session id number for the current database connection. +This id stays the same while the connection is open. +This method returns an int. +The database engine may re-use a session id after the connection is closed. +"," +CALL SESSION_ID() +" + +"Functions (System)","SET"," +@h2@ SET(@variableName, value) +"," +Updates a variable with the given value. +The new value is returned. +When used in a query, the value is updated in the order the rows are read. +When used in a subquery, not all rows might be read depending on the query plan. +This can be used to implement running totals / cumulative sums. +"," +SELECT X, SET(@I, COALESCE(@I, 0)+X) RUNNING_TOTAL FROM SYSTEM_RANGE(1, 10) +" + +"Functions (System)","TRANSACTION_ID"," +@h2@ TRANSACTION_ID() +"," +Returns the current transaction id for this session. +This method returns NULL if there is no uncommitted change, or if the database is not persisted. +Otherwise a value of the following form is returned: +""logFileId-position-sessionId"". +This method returns a string. +The value is unique across database restarts (values are not re-used). +"," +CALL TRANSACTION_ID() +" + +"Functions (System)","TRUNCATE_VALUE"," +@h2@ TRUNCATE_VALUE(value, precisionInt, forceBoolean) +"," +Truncate a value to the required precision. +If force flag is set to ""FALSE"" fixed precision values are not truncated. +The method returns a value with the same data type as the first parameter. +"," +CALL TRUNCATE_VALUE(X, 10, TRUE); +" + +"Functions (System)","CURRENT_PATH"," +CURRENT_PATH +"," +Returns the comma-separated list of quoted schema names where user-defined functions are searched +when they are referenced without the schema name. +"," +CURRENT_PATH +" + +"Functions (System)","CURRENT_ROLE"," +CURRENT_ROLE +"," +Returns the name of the PUBLIC role. +"," +CURRENT_ROLE +" + +"Functions (System)","CURRENT_USER"," +CURRENT_USER | SESSION_USER | SYSTEM_USER | USER +"," +Returns the name of the current user of this session. +"," +CURRENT_USER +" + +"Functions (System)","H2VERSION"," +@h2@ H2VERSION() +"," +Returns the H2 version as a String. +"," +H2VERSION() +" + +"Functions (JSON)","JSON_OBJECT"," +JSON_OBJECT( +[{{[KEY] string VALUE expression} | {string : expression}} [,...] ] +[ { NULL | ABSENT } ON NULL ] +[ { WITH | WITHOUT } UNIQUE KEYS ] +) +"," +Returns a JSON object constructed from the specified properties. +If ABSENT ON NULL is specified properties with NULL value are not included in the object. +If WITH UNIQUE KEYS is specified the constructed object is checked for uniqueness of keys, +nested objects, if any, are checked too. +"," +JSON_OBJECT('id': 100, 'name': 'Joe', 'groups': '[2,5]' FORMAT JSON); +" + +"Functions (JSON)","JSON_ARRAY"," +JSON_ARRAY( +[expression [,...]]|{(query) [FORMAT JSON]} +[ { NULL | ABSENT } ON NULL ] +) +"," +Returns a JSON array constructed from the specified values or from the specified single-column subquery. +If NULL ON NULL is specified NULL values are included in the array. +"," +JSON_ARRAY(10, 15, 20); +JSON_ARRAY(JSON_DATA_A FORMAT JSON, JSON_DATA_B FORMAT JSON); +JSON_ARRAY((SELECT J FROM PROPS) FORMAT JSON); +" + +"Functions (Table)","CSVREAD"," +@h2@ CSVREAD(fileNameString [, columnsString [, csvOptions ] ] ) +"," +Returns the result set of reading the CSV (comma separated values) file. +For each parameter, NULL means the default value should be used. + +If the column names are specified (a list of column names separated with the +fieldSeparator), those are used, otherwise (or if they are set to NULL) the first line of +the file is interpreted as the column names. +In that case, column names that contain no special characters (only letters, '_', +and digits; similar to the rule for Java identifiers) are processed is the same way as unquoted identifiers +and therefore case of characters may be changed. +Other column names are processed as quoted identifiers and case of characters is preserved. +To preserve the case of column names unconditionally use +[caseSensitiveColumnNames](https://h2database.com/html/grammar.html#csv_options) option. + +The default charset is the default value for this system, and the default field separator +is a comma. Missing unquoted values as well as data that matches nullString is +parsed as NULL. All columns are of type VARCHAR. + +The BOM (the byte-order-mark) character 0xfeff at the beginning of the file is ignored. + +This function can be used like a table: ""SELECT * FROM CSVREAD(...)"". + +Instead of a file, a URL may be used, for example +""jar:file:///c:/temp/example.zip!/org/example/nested.csv"". +To read a stream from the classpath, use the prefix ""classpath:"". +To read from HTTP, use the prefix ""http:"" (as in a browser). + +For performance reason, CSVREAD should not be used inside a join. +Instead, import the data first (possibly into a temporary table) and then use the table. + +Admin rights are required to execute this command. +"," +SELECT * FROM CSVREAD('test.csv'); +-- Read a file containing the columns ID, NAME with +SELECT * FROM CSVREAD('test2.csv', 'ID|NAME', 'charset=UTF-8 fieldSeparator=|'); +SELECT * FROM CSVREAD('data/test.csv', null, 'rowSeparator=;'); +-- Read a tab-separated file +SELECT * FROM CSVREAD('data/test.tsv', null, 'rowSeparator=' || CHAR(9)); +SELECT ""Last Name"" FROM CSVREAD('address.csv'); +SELECT ""Last Name"" FROM CSVREAD('classpath:/org/acme/data/address.csv'); +" + +"Functions (Table)","LINK_SCHEMA"," +@h2@ LINK_SCHEMA (targetSchemaString, driverString, urlString, +@h2@ userString, passwordString, sourceSchemaString) +"," +Creates table links for all tables in a schema. +If tables with the same name already exist, they are dropped first. +The target schema is created automatically if it does not yet exist. +The driver name may be empty if the driver is already loaded. +The list of tables linked is returned in the form of a result set. +Admin rights are required to execute this command. +"," +SELECT * FROM LINK_SCHEMA('TEST2', '', 'jdbc:h2:./test2', 'sa', 'sa', 'PUBLIC'); +" + +"Functions (Table)","TABLE"," +@h2@ { TABLE | TABLE_DISTINCT } +@h2@ ( { name dataTypeOrDomain = {array|rowValueExpression} } [,...] ) +"," +Returns the result set. TABLE_DISTINCT removes duplicate rows. +"," +SELECT * FROM TABLE(V INT = ARRAY[1, 2]); +SELECT * FROM TABLE(ID INT=(1, 2), NAME VARCHAR=('Hello', 'World')); +" + +"Functions (Table)","UNNEST"," +UNNEST(array, [,...]) [WITH ORDINALITY] +"," +Returns the result set. +Number of columns is equal to number of arguments, +plus one additional column with row number if WITH ORDINALITY is specified. +Number of rows is equal to length of longest specified array. +If multiple arguments are specified and they have different length, cells with missing values will contain null values. +"," +SELECT * FROM UNNEST(ARRAY['a', 'b', 'c']); +" + +"Aggregate Functions (General)","AVG"," +AVG ( [ DISTINCT|ALL ] { numeric | interval } ) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +The average (mean) value. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. + +The data type of result is DOUBLE PRECISION for TINYINT, SMALLINT, INTEGER, and REAL arguments, +NUMERIC with additional 10 decimal digits of precision and scale for BIGINT and NUMERIC arguments; +DECFLOAT with additional 10 decimal digits of precision for DOUBLE PRECISION and DECFLOAT arguments; +INTERVAL with the same leading field precision, all additional smaller datetime units in interval qualifier, +and the maximum scale for INTERVAL arguments. +"," +AVG(X) +" + +"Aggregate Functions (General)","MAX"," +MAX(value) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +The highest value. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. +The returned value is of the same data type as the parameter. +"," +MAX(NAME) +" + +"Aggregate Functions (General)","MIN"," +MIN(value) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +The lowest value. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. +The returned value is of the same data type as the parameter. +"," +MIN(NAME) +" + +"Aggregate Functions (General)","SUM"," +SUM( [ DISTINCT|ALL ] { numeric | interval | @h2@ { boolean } } ) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +The sum of all values. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. + +The data type of result is BIGINT for BOOLEAN, TINYINT, SMALLINT, and INTEGER arguments; +NUMERIC with additional 10 decimal digits of precision for BIGINT and NUMERIC arguments; +DOUBLE PRECISION for REAL arguments, +DECFLOAT with additional 10 decimal digits of precision for DOUBLE PRECISION and DECFLOAT arguments; +INTERVAL with maximum precision and the same interval qualifier and scale for INTERVAL arguments. +"," +SUM(X) +" + +"Aggregate Functions (General)","EVERY"," +{EVERY| @c@ {BOOL_AND}}(boolean) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +Returns true if all expressions are true. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. +"," +EVERY(ID>10) +" + +"Aggregate Functions (General)","ANY"," +{ANY|SOME| @c@ {BOOL_OR}}(boolean) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +Returns true if any expression is true. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. + +Note that if ANY or SOME aggregate function is placed on the right side of comparison operation or distinct predicate +and argument of this function is a subquery additional parentheses around aggregate function are required, +otherwise it will be parsed as quantified predicate. +"," +ANY(NAME LIKE 'W%') +A = (ANY((SELECT B FROM T))) +" + +"Aggregate Functions (General)","COUNT"," +COUNT( { * | { [ DISTINCT|ALL ] expression } } ) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +The count of all row, or of the non-null values. +This method returns a long. +If no rows are selected, the result is 0. +Aggregates are only allowed in select statements. +"," +COUNT(*) +" + +"Aggregate Functions (General)","STDDEV_POP"," +STDDEV_POP( [ DISTINCT|ALL ] numeric ) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +The population standard deviation. +This method returns a double. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. +"," +STDDEV_POP(X) +" + +"Aggregate Functions (General)","STDDEV_SAMP"," +STDDEV_SAMP( [ DISTINCT|ALL ] numeric ) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +The sample standard deviation. +This method returns a double. +If less than two rows are selected, the result is NULL. +Aggregates are only allowed in select statements. +"," +STDDEV(X) +" + +"Aggregate Functions (General)","VAR_POP"," +VAR_POP( [ DISTINCT|ALL ] numeric ) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +The population variance (square of the population standard deviation). +This method returns a double. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. +"," +VAR_POP(X) +" + +"Aggregate Functions (General)","VAR_SAMP"," +VAR_SAMP( [ DISTINCT|ALL ] numeric ) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +The sample variance (square of the sample standard deviation). +This method returns a double. +If less than two rows are selected, the result is NULL. +Aggregates are only allowed in select statements. +"," +VAR_SAMP(X) +" + +"Aggregate Functions (General)","BIT_AND_AGG"," +{@h2@{BIT_AND_AGG}|@c@{BIT_AND}}@h2@(expression) +@h2@ [FILTER (WHERE expression)] @h2@ [OVER windowNameOrSpecification] +"," +The bitwise AND of all non-null values. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. + +For non-aggregate function see [BITAND](https://h2database.com/html/functions.html#bitand). +"," +BIT_AND_AGG(X) +" + +"Aggregate Functions (General)","BIT_OR_AGG"," +{@h2@{BIT_OR_AGG}|@c@{BIT_OR}}@h2@(expression) +@h2@ [FILTER (WHERE expression)] @h2@ [OVER windowNameOrSpecification] +"," +The bitwise OR of all non-null values. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. + +For non-aggregate function see [BITOR](https://h2database.com/html/functions.html#bitor). +"," +BIT_OR_AGG(X) +" + +"Aggregate Functions (General)","BIT_XOR_AGG"," +@h2@ BIT_XOR_AGG( [ DISTINCT|ALL ] expression) +@h2@ [FILTER (WHERE expression)] @h2@ [OVER windowNameOrSpecification] +"," +The bitwise XOR of all non-null values. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. + +For non-aggregate function see [BITXOR](https://h2database.com/html/functions.html#bitxor). +"," +BIT_XOR_AGG(X) +" + +"Aggregate Functions (General)","BIT_NAND_AGG"," +@h2@ BIT_NAND_AGG(expression) +@h2@ [FILTER (WHERE expression)] @h2@ [OVER windowNameOrSpecification] +"," +The bitwise NAND of all non-null values. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. + +For non-aggregate function see [BITNAND](https://h2database.com/html/functions.html#bitnand). +"," +BIT_NAND_AGG(X) +" + +"Aggregate Functions (General)","BIT_NOR_AGG"," +@h2@ BIT_NOR_AGG(expression) +@h2@ [FILTER (WHERE expression)] @h2@ [OVER windowNameOrSpecification] +"," +The bitwise NOR of all non-null values. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. + +For non-aggregate function see [BITNOR](https://h2database.com/html/functions.html#bitnor). +"," +BIT_NOR_AGG(X) +" + +"Aggregate Functions (General)","BIT_XNOR_AGG"," +@h2@ BIT_XNOR_AGG( [ DISTINCT|ALL ] expression) +@h2@ [FILTER (WHERE expression)] @h2@ [OVER windowNameOrSpecification] +"," +The bitwise XNOR of all non-null values. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. + +For non-aggregate function see [BITXNOR](https://h2database.com/html/functions.html#bitxnor). +"," +BIT_XNOR_AGG(X) +" + +"Aggregate Functions (General)","ENVELOPE"," +@h2@ ENVELOPE( value ) +@h2@ [FILTER (WHERE expression)] @h2@ [OVER windowNameOrSpecification] +"," +Returns the minimum bounding box that encloses all specified GEOMETRY values. +Only 2D coordinate plane is supported. +NULL values are ignored in the calculation. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. +"," +ENVELOPE(X) +" + +"Aggregate Functions (Binary Set)","COVAR_POP"," +COVAR_POP(dependentExpression, independentExpression) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +The population covariance. +This method returns a double. +Rows in which either argument is NULL are ignored in the calculation. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. +"," +COVAR_POP(Y, X) +" + +"Aggregate Functions (Binary Set)","COVAR_SAMP"," +COVAR_SAMP(dependentExpression, independentExpression) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +The sample covariance. +This method returns a double. +Rows in which either argument is NULL are ignored in the calculation. +If less than two rows are selected, the result is NULL. +Aggregates are only allowed in select statements. +"," +COVAR_SAMP(Y, X) +" + +"Aggregate Functions (Binary Set)","CORR"," +CORR(dependentExpression, independentExpression) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +Pearson's correlation coefficient. +This method returns a double. +Rows in which either argument is NULL are ignored in the calculation. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. +"," +CORR(Y, X) +" + +"Aggregate Functions (Binary Set)","REGR_SLOPE"," +REGR_SLOPE(dependentExpression, independentExpression) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +The slope of the line. +This method returns a double. +Rows in which either argument is NULL are ignored in the calculation. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. +"," +REGR_SLOPE(Y, X) +" + +"Aggregate Functions (Binary Set)","REGR_INTERCEPT"," +REGR_INTERCEPT(dependentExpression, independentExpression) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +The y-intercept of the regression line. +This method returns a double. +Rows in which either argument is NULL are ignored in the calculation. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. +"," +REGR_INTERCEPT(Y, X) +" + +"Aggregate Functions (Binary Set)","REGR_COUNT"," +REGR_COUNT(dependentExpression, independentExpression) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +Returns the number of rows in the group. +This method returns a long. +Rows in which either argument is NULL are ignored in the calculation. +If no rows are selected, the result is 0. +Aggregates are only allowed in select statements. +"," +REGR_COUNT(Y, X) +" + +"Aggregate Functions (Binary Set)","REGR_R2"," +REGR_R2(dependentExpression, independentExpression) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +The coefficient of determination. +This method returns a double. +Rows in which either argument is NULL are ignored in the calculation. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. +"," +REGR_R2(Y, X) +" + +"Aggregate Functions (Binary Set)","REGR_AVGX"," +REGR_AVGX(dependentExpression, independentExpression) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +The average (mean) value of dependent expression. +Rows in which either argument is NULL are ignored in the calculation. +If no rows are selected, the result is NULL. +For details about the data type see [AVG](https://h2database.com/html/functions-aggregate.html#avg). +Aggregates are only allowed in select statements. +"," +REGR_AVGX(Y, X) +" + +"Aggregate Functions (Binary Set)","REGR_AVGY"," +REGR_AVGY(dependentExpression, independentExpression) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +The average (mean) value of independent expression. +Rows in which either argument is NULL are ignored in the calculation. +If no rows are selected, the result is NULL. +For details about the data type see [AVG](https://h2database.com/html/functions-aggregate.html#avg). +Aggregates are only allowed in select statements. +"," +REGR_AVGY(Y, X) +" + +"Aggregate Functions (Binary Set)","REGR_SXX"," +REGR_SXX(dependentExpression, independentExpression) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +The the sum of squares of independent expression. +Rows in which either argument is NULL are ignored in the calculation. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. +"," +REGR_SXX(Y, X) +" + +"Aggregate Functions (Binary Set)","REGR_SYY"," +REGR_SYY(dependentExpression, independentExpression) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +The the sum of squares of dependent expression. +Rows in which either argument is NULL are ignored in the calculation. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. +"," +REGR_SYY(Y, X) +" + +"Aggregate Functions (Binary Set)","REGR_SXY"," +REGR_SXY(dependentExpression, independentExpression) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +The the sum of products independent expression times dependent expression. +Rows in which either argument is NULL are ignored in the calculation. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. +"," +REGR_SXY(Y, X) +" + +"Aggregate Functions (Ordered)","LISTAGG"," +LISTAGG ( [ DISTINCT|ALL ] string [, separatorString] +[ ON OVERFLOW { ERROR + | TRUNCATE [ filterString ] { WITH | WITHOUT } COUNT } ] ) +withinGroupSpecification +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +Concatenates strings with a separator. +The default separator is a ',' (without space). +This method returns a string. +NULL values are ignored in the calculation, COALESCE can be used to replace them. +If no rows are selected, the result is NULL. + +If ""ON OVERFLOW TRUNCATE"" is specified, values that don't fit into returned string are truncated +and replaced with filter string placeholder ('...' by default) and count of truncated elements in parentheses. +If ""WITHOUT COUNT"" is specified, count of truncated elements is not appended. + +Aggregates are only allowed in select statements. +"," +LISTAGG(NAME, ', ') WITHIN GROUP (ORDER BY ID) +LISTAGG(COALESCE(NAME, 'null'), ', ') WITHIN GROUP (ORDER BY ID) +LISTAGG(ID, ', ') WITHIN GROUP (ORDER BY ID) OVER (ORDER BY ID) +LISTAGG(ID, ';' ON OVERFLOW TRUNCATE 'etc' WITHOUT COUNT) WITHIN GROUP (ORDER BY ID) +" + +"Aggregate Functions (Ordered)","ARRAY_AGG"," +ARRAY_AGG ( @h2@ [ DISTINCT|ALL ] value +[ ORDER BY sortSpecificationList ] ) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +Aggregate the value into an array. +This method returns an array. +NULL values are included in the array, FILTER clause can be used to exclude them. +If no rows are selected, the result is NULL. +If ORDER BY is not specified order of values is not determined. +When this aggregate is used with OVER clause that contains ORDER BY subclause +it does not enforce exact order of values. +This aggregate needs additional own ORDER BY clause to make it deterministic. +Aggregates are only allowed in select statements. +"," +ARRAY_AGG(NAME ORDER BY ID) +ARRAY_AGG(NAME ORDER BY ID) FILTER (WHERE NAME IS NOT NULL) +ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY ID) +" + +"Aggregate Functions (Hypothetical Set)","RANK aggregate"," +RANK(value [,...]) +withinGroupSpecification +[FILTER (WHERE expression)] @h2@ [OVER windowNameOrSpecification] +"," +Returns the rank of the hypothetical row in specified collection of rows. +The rank of a row is the number of rows that precede this row plus 1. +If two or more rows have the same values in ORDER BY columns, these rows get the same rank from the first row with the same values. +It means that gaps in ranks are possible. + +See [RANK](https://h2database.com/html/functions-window.html#rank) for a window function with the same name. +"," +SELECT RANK(5) WITHIN GROUP (ORDER BY V) FROM TEST; +" + +"Aggregate Functions (Hypothetical Set)","DENSE_RANK aggregate"," +DENSE_RANK(value [,...]) +withinGroupSpecification +[FILTER (WHERE expression)] @h2@ [OVER windowNameOrSpecification] +"," +Returns the dense rank of the hypothetical row in specified collection of rows. +The rank of a row is the number of groups of rows with the same values in ORDER BY columns that precede group with this row plus 1. +If two or more rows have the same values in ORDER BY columns, these rows get the same rank. +Gaps in ranks are not possible. + +See [DENSE_RANK](https://h2database.com/html/functions-window.html#dense_rank) for a window function with the same name. +"," +SELECT DENSE_RANK(5) WITHIN GROUP (ORDER BY V) FROM TEST; +" + +"Aggregate Functions (Hypothetical Set)","PERCENT_RANK aggregate"," +PERCENT_RANK(value [,...]) +withinGroupSpecification +[FILTER (WHERE expression)] @h2@ [OVER windowNameOrSpecification] +"," +Returns the relative rank of the hypothetical row in specified collection of rows. +The relative rank is calculated as (RANK - 1) / (NR - 1), +where RANK is a rank of the row and NR is a total number of rows in the collection including hypothetical row. + +See [PERCENT_RANK](https://h2database.com/html/functions-window.html#percent_rank) for a window function with the same name. +"," +SELECT PERCENT_RANK(5) WITHIN GROUP (ORDER BY V) FROM TEST; +" + +"Aggregate Functions (Hypothetical Set)","CUME_DIST aggregate"," +CUME_DIST(value [,...]) +withinGroupSpecification +[FILTER (WHERE expression)] @h2@ [OVER windowNameOrSpecification] +"," +Returns the relative rank of the hypothetical row in specified collection of rows. +The relative rank is calculated as NP / NR +where NP is a number of rows that precede the current row or have the same values in ORDER BY columns +and NR is a total number of rows in the collection including hypothetical row. + +See [CUME_DIST](https://h2database.com/html/functions-window.html#cume_dist) for a window function with the same name. +"," +SELECT CUME_DIST(5) WITHIN GROUP (ORDER BY V) FROM TEST; +" + +"Aggregate Functions (Inverse Distribution)","PERCENTILE_CONT"," +PERCENTILE_CONT(numeric) WITHIN GROUP (ORDER BY sortSpecification) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +Return percentile of values from the group with interpolation. +Interpolation is only supported for numeric, date-time, and interval data types. +Argument must be between 0 and 1 inclusive. +Argument must be the same for all rows in the same group. +If argument is NULL, the result is NULL. +NULL values are ignored in the calculation. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. +"," +PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY V) +" + +"Aggregate Functions (Inverse Distribution)","PERCENTILE_DISC"," +PERCENTILE_DISC(numeric) WITHIN GROUP (ORDER BY sortSpecification) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +Return percentile of values from the group. +Interpolation is not performed. +Argument must be between 0 and 1 inclusive. +Argument must be the same for all rows in the same group. +If argument is NULL, the result is NULL. +NULL values are ignored in the calculation. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. +"," +PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY V) +" + +"Aggregate Functions (Inverse Distribution)","MEDIAN"," +@h2@ MEDIAN( [ DISTINCT|ALL ] value ) +@h2@ [FILTER (WHERE expression)] @h2@ [OVER windowNameOrSpecification] +"," +The value separating the higher half of a values from the lower half. +Returns the middle value or an interpolated value between two middle values if number of values is even. +Interpolation is only supported for numeric, date-time, and interval data types. +NULL values are ignored in the calculation. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. +"," +MEDIAN(X) +" + +"Aggregate Functions (Inverse Distribution)","MODE"," +@h2@ { MODE() WITHIN GROUP (ORDER BY sortSpecification) } + | @c@ { MODE( value [ ORDER BY sortSpecification ] ) } +@h2@ [FILTER (WHERE expression)] @h2@ [OVER windowNameOrSpecification] +"," +Returns the value that occurs with the greatest frequency. +If there are multiple values with the same frequency only one value will be returned. +In this situation value will be chosen based on optional ORDER BY clause +that should specify exactly the same expression as argument of this function. +Use ascending order to get smallest value or descending order to get largest value +from multiple values with the same frequency. +If this clause is not specified the exact chosen value is not determined in this situation. +NULL values are ignored in the calculation. +If no rows are selected, the result is NULL. +Aggregates are only allowed in select statements. +"," +MODE() WITHIN GROUP (ORDER BY X) +" + +"Aggregate Functions (JSON)","JSON_OBJECTAGG"," +JSON_OBJECTAGG( +{[KEY] string VALUE value} | {string : value} +[ { NULL | ABSENT } ON NULL ] +[ { WITH | WITHOUT } UNIQUE KEYS ] +) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +Aggregates the keys with values into a JSON object. +If ABSENT ON NULL is specified properties with NULL value are not included in the object. +If WITH UNIQUE KEYS is specified the constructed object is checked for uniqueness of keys, +nested objects, if any, are checked too. +If no values are selected, the result is SQL NULL value. +"," +JSON_OBJECTAGG(NAME: VAL); +JSON_OBJECTAGG(KEY NAME VALUE VAL); +" + +"Aggregate Functions (JSON)","JSON_ARRAYAGG"," +JSON_ARRAYAGG( @h2@ [ DISTINCT|ALL ] expression +[ ORDER BY sortSpecificationList ] +[ { NULL | ABSENT } ON NULL ] ) +[FILTER (WHERE expression)] [OVER windowNameOrSpecification] +"," +Aggregates the values into a JSON array. +If NULL ON NULL is specified NULL values are included in the array. +If no values are selected, the result is SQL NULL value. +"," +JSON_ARRAYAGG(NUMBER) +" + +"Window Functions (Row Number)","ROW_NUMBER"," +ROW_NUMBER() OVER windowNameOrSpecification +"," +Returns the number of the current row starting with 1. +Window frame clause is not allowed for this function. + +Window functions in H2 may require a lot of memory for large queries. +"," +SELECT ROW_NUMBER() OVER (), * FROM TEST; +SELECT ROW_NUMBER() OVER (ORDER BY ID), * FROM TEST; +SELECT ROW_NUMBER() OVER (PARTITION BY CATEGORY ORDER BY ID), * FROM TEST; +" + +"Window Functions (Rank)","RANK"," +RANK() OVER windowNameOrSpecification +"," +Returns the rank of the current row. +The rank of a row is the number of rows that precede this row plus 1. +If two or more rows have the same values in ORDER BY columns, these rows get the same rank from the first row with the same values. +It means that gaps in ranks are possible. +This function requires window order clause. +Window frame clause is not allowed for this function. + +Window functions in H2 may require a lot of memory for large queries. + +See [RANK aggregate](https://h2database.com/html/functions-aggregate.html#rank_aggregate) for a hypothetical set function with the same name. +"," +SELECT RANK() OVER (ORDER BY ID), * FROM TEST; +SELECT RANK() OVER (PARTITION BY CATEGORY ORDER BY ID), * FROM TEST; +" + +"Window Functions (Rank)","DENSE_RANK"," +DENSE_RANK() OVER windowNameOrSpecification +"," +Returns the dense rank of the current row. +The rank of a row is the number of groups of rows with the same values in ORDER BY columns that precede group with this row plus 1. +If two or more rows have the same values in ORDER BY columns, these rows get the same rank. +Gaps in ranks are not possible. +This function requires window order clause. +Window frame clause is not allowed for this function. + +Window functions in H2 may require a lot of memory for large queries. + +See [DENSE_RANK aggregate](https://h2database.com/html/functions-aggregate.html#dense_rank_aggregate) for a hypothetical set function with the same name. +"," +SELECT DENSE_RANK() OVER (ORDER BY ID), * FROM TEST; +SELECT DENSE_RANK() OVER (PARTITION BY CATEGORY ORDER BY ID), * FROM TEST; +" + +"Window Functions (Rank)","PERCENT_RANK"," +PERCENT_RANK() OVER windowNameOrSpecification +"," +Returns the relative rank of the current row. +The relative rank is calculated as (RANK - 1) / (NR - 1), +where RANK is a rank of the row and NR is a number of rows in window partition with this row. +Note that result is always 0 if window order clause is not specified. +Window frame clause is not allowed for this function. + +Window functions in H2 may require a lot of memory for large queries. + +See [PERCENT_RANK aggregate](https://h2database.com/html/functions-aggregate.html#percent_rank_aggregate) for a hypothetical set function with the same name. +"," +SELECT PERCENT_RANK() OVER (ORDER BY ID), * FROM TEST; +SELECT PERCENT_RANK() OVER (PARTITION BY CATEGORY ORDER BY ID), * FROM TEST; +" + +"Window Functions (Rank)","CUME_DIST"," +CUME_DIST() OVER windowNameOrSpecification +"," +Returns the relative rank of the current row. +The relative rank is calculated as NP / NR +where NP is a number of rows that precede the current row or have the same values in ORDER BY columns +and NR is a number of rows in window partition with this row. +Note that result is always 1 if window order clause is not specified. +Window frame clause is not allowed for this function. + +Window functions in H2 may require a lot of memory for large queries. + +See [CUME_DIST aggregate](https://h2database.com/html/functions-aggregate.html#cume_dist_aggregate) for a hypothetical set function with the same name. +"," +SELECT CUME_DIST() OVER (ORDER BY ID), * FROM TEST; +SELECT CUME_DIST() OVER (PARTITION BY CATEGORY ORDER BY ID), * FROM TEST; +" + +"Window Functions (Lead or Lag)","LEAD"," +LEAD(value [, offsetInt [, defaultValue]]) [{RESPECT|IGNORE} NULLS] +OVER windowNameOrSpecification +"," +Returns the value in a next row with specified offset relative to the current row. +Offset must be non-negative. +If IGNORE NULLS is specified rows with null values in selected expression are skipped. +If number of considered rows is less than specified relative number this function returns NULL +or the specified default value, if any. +If offset is 0 the value from the current row is returned unconditionally. +This function requires window order clause. +Window frame clause is not allowed for this function. + +Window functions in H2 may require a lot of memory for large queries. +"," +SELECT LEAD(X) OVER (ORDER BY ID), * FROM TEST; +SELECT LEAD(X, 2, 0) IGNORE NULLS OVER ( + PARTITION BY CATEGORY ORDER BY ID +), * FROM TEST; +" + +"Window Functions (Lead or Lag)","LAG"," +LAG(value [, offsetInt [, defaultValue]]) [{RESPECT|IGNORE} NULLS] +OVER windowNameOrSpecification +"," +Returns the value in a previous row with specified offset relative to the current row. +Offset must be non-negative. +If IGNORE NULLS is specified rows with null values in selected expression are skipped. +If number of considered rows is less than specified relative number this function returns NULL +or the specified default value, if any. +If offset is 0 the value from the current row is returned unconditionally. +This function requires window order clause. +Window frame clause is not allowed for this function. + +Window functions in H2 may require a lot of memory for large queries. +"," +SELECT LAG(X) OVER (ORDER BY ID), * FROM TEST; +SELECT LAG(X, 2, 0) IGNORE NULLS OVER ( + PARTITION BY CATEGORY ORDER BY ID +), * FROM TEST; +" + +"Window Functions (Nth Value)","FIRST_VALUE"," +FIRST_VALUE(value) [{RESPECT|IGNORE} NULLS] +OVER windowNameOrSpecification +"," +Returns the first value in a window. +If IGNORE NULLS is specified null values are skipped and the function returns first non-null value, if any. + +Window functions in H2 may require a lot of memory for large queries. +"," +SELECT FIRST_VALUE(X) OVER (ORDER BY ID), * FROM TEST; +SELECT FIRST_VALUE(X) IGNORE NULLS OVER (PARTITION BY CATEGORY ORDER BY ID), * FROM TEST; +" + +"Window Functions (Nth Value)","LAST_VALUE"," +LAST_VALUE(value) [{RESPECT|IGNORE} NULLS] +OVER windowNameOrSpecification +"," +Returns the last value in a window. +If IGNORE NULLS is specified null values are skipped and the function returns last non-null value before them, if any; +if there is no non-null value it returns NULL. +Note that the last value is actually a value in the current group of rows +if window order clause is specified and window frame clause is not specified. + +Window functions in H2 may require a lot of memory for large queries. +"," +SELECT LAST_VALUE(X) OVER (ORDER BY ID), * FROM TEST; +SELECT LAST_VALUE(X) IGNORE NULLS OVER ( + PARTITION BY CATEGORY ORDER BY ID + RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING +), * FROM TEST; +" + +"Window Functions (Nth Value)","NTH_VALUE"," +NTH_VALUE(value, nInt) [FROM {FIRST|LAST}] [{RESPECT|IGNORE} NULLS] +OVER windowNameOrSpecification +"," +Returns the value in a row with a specified relative number in a window. +Relative row number must be positive. +If FROM LAST is specified rows a counted backwards from the last row. +If IGNORE NULLS is specified rows with null values in selected expression are skipped. +If number of considered rows is less than specified relative number this function returns NULL. +Note that the last row is actually a last row in the current group of rows +if window order clause is specified and window frame clause is not specified. + +Window functions in H2 may require a lot of memory for large queries. +"," +SELECT NTH_VALUE(X) OVER (ORDER BY ID), * FROM TEST; +SELECT NTH_VALUE(X) IGNORE NULLS OVER ( + PARTITION BY CATEGORY ORDER BY ID + RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING +), * FROM TEST; +" + +"Window Functions (Other)","NTILE"," +NTILE(long) OVER windowNameOrSpecification +"," +Distributes the rows into a specified number of groups. +Number of groups should be a positive long value. +NTILE returns the 1-based number of the group to which the current row belongs. +First groups will have more rows if number of rows is not divisible by number of groups. +For example, if 5 rows are distributed into 2 groups this function returns 1 for the first 3 row and 2 for the last 2 rows. +This function requires window order clause. +Window frame clause is not allowed for this function. + +Window functions in H2 may require a lot of memory for large queries. +"," +SELECT NTILE(10) OVER (ORDER BY ID), * FROM TEST; +SELECT NTILE(5) OVER (PARTITION BY CATEGORY ORDER BY ID), * FROM TEST; +" + +"Window Functions (Other)","RATIO_TO_REPORT"," +@h2@ RATIO_TO_REPORT(value) +@h2@ OVER windowNameOrSpecification +"," +Returns the ratio of a value to the sum of all values. +If argument is NULL or sum of all values is 0, then the value of function is NULL. +Window ordering and window frame clauses are not allowed for this function. + +Window functions in H2 may require a lot of memory for large queries. +"," +SELECT X, RATIO_TO_REPORT(X) OVER (PARTITION BY CATEGORY), CATEGORY FROM TEST; +" + +"System Tables","Information Schema"," +INFORMATION_SCHEMA +"," +To get the list of system tables, execute the statement SELECT * FROM +INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'INFORMATION_SCHEMA' +"," +" + +"System Tables","Range Table"," +@h2@ SYSTEM_RANGE(start, end [, step]) +"," +Contains all values from start to end (this is a dynamic table). +"," +SYSTEM_RANGE(0, 100) +" diff --git a/h2/src/main/org/h2/res/javadoc.properties b/h2/src/main/org/h2/res/javadoc.properties new file mode 100644 index 0000000..fabf642 --- /dev/null +++ b/h2/src/main/org/h2/res/javadoc.properties @@ -0,0 +1,37 @@ +org.h2.jmx.DatabaseInfoMBean=Information and management operations for the given database. +org.h2.jmx.DatabaseInfoMBean.getCacheSize=The current cache size in KB. +org.h2.jmx.DatabaseInfoMBean.getCacheSizeMax=The maximum cache size in KB. +org.h2.jmx.DatabaseInfoMBean.getFileReadCount=The file read count since the database was opened. +org.h2.jmx.DatabaseInfoMBean.getFileSize=The database file size in KB. +org.h2.jmx.DatabaseInfoMBean.getFileWriteCount=The number of write operations since the database was opened. +org.h2.jmx.DatabaseInfoMBean.getMode=The database compatibility mode (REGULAR if no compatibility mode is\n used). +org.h2.jmx.DatabaseInfoMBean.getTraceLevel=The trace level (0 disabled, 1 error, 2 info, 3 debug). +org.h2.jmx.DatabaseInfoMBean.getVersion=The database version. +org.h2.jmx.DatabaseInfoMBean.isExclusive=Is the database open in exclusive mode? +org.h2.jmx.DatabaseInfoMBean.isReadOnly=Is the database read-only? +org.h2.jmx.DatabaseInfoMBean.listSessions=List sessions, including the queries that are in\n progress, and locked tables. +org.h2.jmx.DatabaseInfoMBean.listSettings=List the database settings. +org.h2.tools.Backup=Creates a backup of a database.\n\n This tool copies all database files. The database must be closed before using\n this tool. To create a backup while the database is in use, run the BACKUP\n SQL statement. In an emergency, for example if the application is not\n responding, creating a backup using the Backup tool is possible by using the\n quiet mode. However, if the database is changed while the backup is running\n in quiet mode, the backup could be corrupt. +org.h2.tools.Backup.main=Options are case sensitive.\nSupported options are\:[-help] or [-?]Print the list of options\n[-file ] The target file name (default\: backup.zip)\n[-dir ] The source directory (default\: .)\n[-db ] Source database; not required if there is only one\n[-quiet] Do not print progress information +org.h2.tools.ChangeFileEncryption=Allows changing the database file encryption password or algorithm.\n\n This tool can not be used to change a password of a user.\n The database must be closed before using this tool. +org.h2.tools.ChangeFileEncryption.main=Options are case sensitive.\nSupported options[-help] or [-?]Print the list of options\n[-cipher type] The encryption type (AES)\n[-dir ] The database directory (default\: .)\n[-db ] Database name (all databases if not set)\n[-decrypt ] The decryption password (if not set\: not yet encrypted)\n[-encrypt ] The encryption password (if not set\: do not encrypt)\n[-quiet] Do not print progress information +org.h2.tools.Console=Starts the H2 Console (web-) server, as well as the TCP and PG server. +org.h2.tools.Console.main=When running without options, -tcp, -web, -browser and -pg are started.\n\n Options are case sensitive.\nSupported options[-help] or [-?]Print the list of options\n[-url] Start a browser and connect to this URL\n[-driver] Used together with -url\: the driver\n[-user] Used together with -url\: the user name\n[-password] Used together with -url\: the password\n[-web] Start the web server with the H2 Console\n[-tool] Start the icon or window that allows to start a browser\n[-browser] Start a browser connecting to the web server\n[-tcp] Start the TCP server\n[-pg] Start the PG server\nFor each Server, additional options are available;\n for details, see the Server tool.\n If a service can not be started, the program\n terminates with an exit code of 1. +org.h2.tools.ConvertTraceFile=Converts a .trace.db file to a SQL script and Java source code.\n\n SQL statement statistics are listed as well. +org.h2.tools.ConvertTraceFile.main=Options are case sensitive.\nSupported options[-help] or [-?]Print the list of options\n[-traceFile ] The trace file name (default\: test.trace.db)\n[-script ] The script file name (default\: test.sql)\n[-javaClass ] The Java directory and class file name (default\: Test) +org.h2.tools.CreateCluster=Creates a cluster from a stand-alone database.\n\n Copies a database to another location if required. +org.h2.tools.CreateCluster.main=Options are case sensitive.\nSupported options[-help] or [-?]Print the list of options\n[-urlSource ""] The database URL of the source database (jdbc\:h2\:...)\n[-urlTarget ""] The database URL of the target database (jdbc\:h2\:...)\n[-user ] The user name (default\: sa)\n[-password ] The password\n[-serverList ] The comma separated list of host names or IP addresses +org.h2.tools.DeleteDbFiles=Deletes all files belonging to a database.\n\n The database must be closed before calling this tool. +org.h2.tools.DeleteDbFiles.main=Options are case sensitive.\nSupported options[-help] or [-?]Print the list of options\n[-dir ] The directory (default\: .)\n[-db ] The database name\n[-quiet] Do not print progress information +org.h2.tools.Recover=Helps recovering a corrupted database. +org.h2.tools.Recover.main=Options are case sensitive.\nSupported options[-help] or [-?]Print the list of options\n[-dir ] The directory (default\: .)\n[-db ] The database name (all databases if not set)\n[-trace] Print additional trace information\n[-transactionLog] Print the transaction log\nEncrypted databases need to be decrypted first. +org.h2.tools.Restore=Restores a H2 database by extracting the database files from a .zip file. +org.h2.tools.Restore.main=Options are case sensitive. Supported options\nSupported options[-help] or [-?]Print the list of options\n[-file ] The source file name (default\: backup.zip)\n[-dir ] The target directory (default\: .)\n[-db ] The target database name (as stored if not set)\n[-quiet] Do not print progress information +org.h2.tools.RunScript=Runs a SQL script against a database. +org.h2.tools.RunScript.main=Options are case sensitive.\nSupported options[-help] or [-?]Print the list of options\n[-url ""] The database URL (jdbc\:...)\n[-user ] The user name (default\: sa)\n[-password ] The password\n[-script ] The script file to run (default\: backup.sql)\n[-driver ] The JDBC driver class to use (not required in most cases)\n[-showResults] Show the statements and the results of queries\n[-checkResults] Check if the query results match the expected results\n[-continueOnError] Continue even if the script contains errors\n[-options ...] RUNSCRIPT options (embedded H2; -*Results not supported) +org.h2.tools.Script=Creates a SQL script file by extracting the schema and data of a database. +org.h2.tools.Script.main=Options are case sensitive.\nSupported options[-help] or [-?]Print the list of options\n[-url ""] The database URL (jdbc\:...)\n[-user ] The user name (default\: sa)\n[-password ] The password\n[-script ] The target script file name (default\: backup.sql)\n[-options ...] A list of options (only for embedded H2, see SCRIPT)\n[-quiet] Do not print progress information +org.h2.tools.Server=Starts the H2 Console (web-) server, TCP, and PG server. +org.h2.tools.Server.main=When running without options, -tcp, -web, -browser and -pg are started.\n\n Options are case sensitive.\nSupported options[-help] or [-?]Print the list of options\n[-web] Start the web server with the H2 Console\n[-webAllowOthers] Allow other computers to connect - see below\n[-webDaemon] Use a daemon thread\n[-webPort ] The port (default\: 8082)\n[-webSSL] Use encrypted (HTTPS) connections\n[-webAdminPassword] Password of DB Console administrator\n[-browser] Start a browser connecting to the web server\n[-tcp] Start the TCP server\n[-tcpAllowOthers] Allow other computers to connect - see below\n[-tcpDaemon] Use a daemon thread\n[-tcpPort ] The port (default\: 9092)\n[-tcpSSL] Use encrypted (SSL) connections\n[-tcpPassword ] The password for shutting down a TCP server\n[-tcpShutdown ""] Stop the TCP server; example\: tcp\://localhost\n[-tcpShutdownForce] Do not wait until all connections are closed\n[-pg] Start the PG server\n[-pgAllowOthers] Allow other computers to connect - see below\n[-pgDaemon] Use a daemon thread\n[-pgPort ] The port (default\: 5435)\n[-properties ""] Server properties (default\: ~, disable\: null)\n[-baseDir ] The base directory for H2 databases (all servers)\n[-ifExists] Only existing databases may be opened (all servers)\n[-ifNotExists] Databases are created when accessed\n[-trace] Print additional trace information (all servers)\n[-key ] Allows to map a database name to another (all servers)\nThe options -xAllowOthers are potentially risky.\n\n For details, see Advanced Topics / Protection against Remote Access. +org.h2.tools.Shell=Interactive command line tool to access a database using JDBC. +org.h2.tools.Shell.main=Options are case sensitive.\nSupported options[-help] or [-?]Print the list of options\n[-url ""] The database URL (jdbc\:h2\:...)\n[-user ] The user name\n[-password ] The password\n[-driver ] The JDBC driver class to use (not required in most cases)\n[-sql ""] Execute the SQL statements and exit\n[-properties ""] Load the server properties from this directory\nIf special characters don't work as expected, you may need to use\n -Dfile.encoding\=UTF-8 (Mac OS X) or CP850 (Windows). diff --git a/h2/src/main/org/h2/result/DefaultRow.java b/h2/src/main/org/h2/result/DefaultRow.java new file mode 100644 index 0000000..a9fe6c4 --- /dev/null +++ b/h2/src/main/org/h2/result/DefaultRow.java @@ -0,0 +1,116 @@ +/* + * 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.result; + +import org.h2.engine.Constants; +import org.h2.value.Value; +import org.h2.value.ValueBigint; + +/** + * The default implementation of a row in a table. + */ +public class DefaultRow extends Row { + + /** + * The constant that means "memory usage is unknown and needs to be calculated first". + */ + public static final int MEMORY_CALCULATE = -1; + + /** + * The values of the row (one entry per column). + */ + protected final Value[] data; + + private int memory; + + DefaultRow(int columnCount) { + this.data = new Value[columnCount]; + this.memory = MEMORY_CALCULATE; + } + + public DefaultRow(Value[] data) { + this.data = data; + this.memory = MEMORY_CALCULATE; + } + + public DefaultRow(Value[] data, int memory) { + this.data = data; + this.memory = memory; + } + + @Override + public Value getValue(int i) { + return i == ROWID_INDEX ? ValueBigint.get(key) : data[i]; + } + + @Override + public void setValue(int i, Value v) { + if (i == ROWID_INDEX) { + key = v.getLong(); + } else { + data[i] = v; + } + } + + @Override + public int getColumnCount() { + return data.length; + } + + @Override + public int getMemory() { + if (memory != MEMORY_CALCULATE) { + return memory; + } + return memory = calculateMemory(); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("( /* key:").append(key).append(" */ "); + for (int i = 0, length = data.length; i < length; i++) { + if (i > 0) { + builder.append(", "); + } + Value v = data[i]; + builder.append(v == null ? "null" : v.getTraceSQL()); + } + return builder.append(')').toString(); + } + + /** + * Calculate the estimated memory used for this row, in bytes. + * + * @return the memory + */ + protected int calculateMemory() { + int m = Constants.MEMORY_ROW + Constants.MEMORY_ARRAY + data.length * Constants.MEMORY_POINTER; + for (Value v : data) { + if (v != null) { + m += v.getMemory(); + } + } + return m; + } + + @Override + public Value[] getValueList() { + return data; + } + + @Override + public boolean hasSharedData(Row other) { + return other instanceof DefaultRow && data == ((DefaultRow) other).data; + } + + @Override + public void copyFrom(SearchRow source) { + setKey(source.getKey()); + for (int i = 0; i < getColumnCount(); i++) { + setValue(i, source.getValue(i)); + } + } +} diff --git a/h2/src/main/org/h2/result/FetchedResult.java b/h2/src/main/org/h2/result/FetchedResult.java new file mode 100644 index 0000000..6882ede --- /dev/null +++ b/h2/src/main/org/h2/result/FetchedResult.java @@ -0,0 +1,69 @@ +/* + * 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.result; + +import org.h2.engine.Session; +import org.h2.value.Value; + +/** + * Abstract fetched result. + */ +public abstract class FetchedResult implements ResultInterface { + + long rowId = -1; + + Value[] currentRow; + + Value[] nextRow; + + boolean afterLast; + + FetchedResult() { + } + + @Override + public final Value[] currentRow() { + return currentRow; + } + + @Override + public final boolean next() { + if (hasNext()) { + rowId++; + currentRow = nextRow; + nextRow = null; + return true; + } + if (!afterLast) { + rowId++; + currentRow = null; + afterLast = true; + } + return false; + } + + @Override + public final boolean isAfterLast() { + return afterLast; + } + + @Override + public final long getRowId() { + return rowId; + } + + @Override + public final boolean needToClose() { + return true; + } + + @Override + public final ResultInterface createShallowCopy(Session targetSession) { + // The operation is not supported on fetched result. + return null; + } + +} diff --git a/h2/src/main/org/h2/result/LazyResult.java b/h2/src/main/org/h2/result/LazyResult.java new file mode 100644 index 0000000..66c6187 --- /dev/null +++ b/h2/src/main/org/h2/result/LazyResult.java @@ -0,0 +1,160 @@ +/* + * 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.result; + +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.message.DbException; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * Lazy execution support for queries. + * + * @author Sergi Vladykin + */ +public abstract class LazyResult extends FetchedResult { + + private final SessionLocal session; + private final Expression[] expressions; + private boolean closed; + private long limit; + + public LazyResult(SessionLocal session, Expression[] expressions) { + this.session = session; + this.expressions = expressions; + } + + public void setLimit(long limit) { + this.limit = limit; + } + + @Override + public boolean isLazy() { + return true; + } + + @Override + public void reset() { + if (closed) { + throw DbException.getInternalError(); + } + rowId = -1L; + afterLast = false; + currentRow = null; + nextRow = null; + } + + /** + * Go to the next row and skip it. + * + * @return true if a row exists + */ + public boolean skip() { + if (closed || afterLast) { + return false; + } + currentRow = null; + if (nextRow != null) { + nextRow = null; + return true; + } + if (skipNextRow()) { + return true; + } + afterLast = true; + return false; + } + + @Override + public boolean hasNext() { + if (closed || afterLast) { + return false; + } + if (nextRow == null && (limit <= 0 || rowId + 1 < limit)) { + nextRow = fetchNextRow(); + } + return nextRow != null; + } + + /** + * Fetch next row or null if none available. + * + * @return next row or null + */ + protected abstract Value[] fetchNextRow(); + + /** + * Skip next row. + * + * @return true if next row was available + */ + protected boolean skipNextRow() { + return fetchNextRow() != null; + } + + @Override + public long getRowCount() { + throw DbException.getUnsupportedException("Row count is unknown for lazy result."); + } + + @Override + public boolean isClosed() { + return closed; + } + + @Override + public void close() { + closed = true; + } + + @Override + public String getAlias(int i) { + return expressions[i].getAlias(session, i); + } + + @Override + public String getSchemaName(int i) { + return expressions[i].getSchemaName(); + } + + @Override + public String getTableName(int i) { + return expressions[i].getTableName(); + } + + @Override + public String getColumnName(int i) { + return expressions[i].getColumnName(session, i); + } + + @Override + public TypeInfo getColumnType(int i) { + return expressions[i].getType(); + } + + @Override + public boolean isIdentity(int i) { + return expressions[i].isIdentity(); + } + + @Override + public int getNullable(int i) { + return expressions[i].getNullable(); + } + + @Override + public void setFetchSize(int fetchSize) { + // ignore + } + + @Override + public int getFetchSize() { + // We always fetch rows one by one. + return 1; + } + +} diff --git a/h2/src/main/org/h2/result/LocalResult.java b/h2/src/main/org/h2/result/LocalResult.java new file mode 100644 index 0000000..fa630ed --- /dev/null +++ b/h2/src/main/org/h2/result/LocalResult.java @@ -0,0 +1,696 @@ +/* + * 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.result; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.TreeMap; + +import org.h2.engine.Database; +import org.h2.engine.Session; +import org.h2.engine.SessionLocal; +import org.h2.engine.SysProperties; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.message.DbException; +import org.h2.mvstore.db.MVTempResult; +import org.h2.table.Column; +import org.h2.table.Table; +import org.h2.util.Utils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBigint; +import org.h2.value.ValueLob; +import org.h2.value.ValueRow; + +/** + * A local result set contains all row data of a result set. + * This is the object generated by engine, + * and it is also used directly by the ResultSet class in the embedded mode. + * If the result does not fit in memory, it is written to a temporary file. + */ +public class LocalResult implements ResultInterface, ResultTarget { + + /** + * Constructs a new local result object for the specified table. + * + * @param session + * the session + * @param table + * the table + * @return the local result + */ + public static LocalResult forTable(SessionLocal session, Table table) { + Column[] columns = table.getColumns(); + int degree = columns.length; + Expression[] expressions = new Expression[degree + 1]; + Database database = session.getDatabase(); + for (int i = 0; i < degree; i++) { + expressions[i] = new ExpressionColumn(database, columns[i]); + } + Column rowIdColumn = table.getRowIdColumn(); + expressions[degree] = rowIdColumn != null ? new ExpressionColumn(database, rowIdColumn) + : new ExpressionColumn(database, null, table.getName()); + return new LocalResult(session, expressions, degree, degree + 1); + } + + private int maxMemoryRows; + private final SessionLocal session; + private int visibleColumnCount; + private int resultColumnCount; + private Expression[] expressions; + private boolean forDataChangeDeltaTable; + private long rowId, rowCount; + private ArrayList rows; + private SortOrder sort; + // HashSet cannot be used here, because we need to compare values of + // different type or scale properly. + private TreeMap distinctRows; + private Value[] currentRow; + private long offset; + private long limit = -1; + private boolean fetchPercent; + private SortOrder withTiesSortOrder; + private boolean limitsWereApplied; + private ResultExternal external; + private boolean distinct; + private int[] distinctIndexes; + private boolean closed; + private boolean containsLobs; + private Boolean containsNull; + + /** + * Construct a local result object. + */ + public LocalResult() { + this(null); + } + + private LocalResult(SessionLocal session) { + this.session = session; + } + + /** + * Construct a local result object. + * + * @param session + * the session + * @param expressions + * the expression array + * @param visibleColumnCount + * the number of visible columns + * @param resultColumnCount + * the number of columns including visible columns and additional + * virtual columns for ORDER BY and DISTINCT ON clauses + */ + public LocalResult(SessionLocal session, Expression[] expressions, int visibleColumnCount, int resultColumnCount) { + this.session = session; + if (session == null) { + this.maxMemoryRows = Integer.MAX_VALUE; + } else { + Database db = session.getDatabase(); + if (db.isPersistent() && !db.isReadOnly()) { + this.maxMemoryRows = session.getDatabase().getMaxMemoryRows(); + } else { + this.maxMemoryRows = Integer.MAX_VALUE; + } + } + rows = Utils.newSmallArrayList(); + this.visibleColumnCount = visibleColumnCount; + this.resultColumnCount = resultColumnCount; + rowId = -1; + this.expressions = expressions; + } + + @Override + public boolean isLazy() { + return false; + } + + /** + * Redefine count of maximum rows holds in memory for the result. + * + * @param maxValue Maximum rows count in memory. + * + * @see SysProperties#MAX_MEMORY_ROWS + */ + public void setMaxMemoryRows(int maxValue) { + this.maxMemoryRows = maxValue; + } + + /** + * Sets value collection mode for data change delta tables. + */ + public void setForDataChangeDeltaTable() { + forDataChangeDeltaTable = true; + } + + /** + * Create a shallow copy of the result set. The data and a temporary table + * (if there is any) is not copied. + * + * @param targetSession the session of the copy + * @return the copy if possible, or null if copying is not possible + */ + @Override + public LocalResult createShallowCopy(Session targetSession) { + if (external == null && (rows == null || rows.size() < rowCount)) { + return null; + } + if (containsLobs) { + return null; + } + ResultExternal e2 = null; + if (external != null) { + e2 = external.createShallowCopy(); + if (e2 == null) { + return null; + } + } + LocalResult copy = new LocalResult((SessionLocal) targetSession); + copy.maxMemoryRows = this.maxMemoryRows; + copy.visibleColumnCount = this.visibleColumnCount; + copy.resultColumnCount = this.resultColumnCount; + copy.expressions = this.expressions; + copy.rowId = -1; + copy.rowCount = this.rowCount; + copy.rows = this.rows; + copy.sort = this.sort; + copy.distinctRows = this.distinctRows; + copy.distinct = distinct; + copy.distinctIndexes = distinctIndexes; + copy.currentRow = null; + copy.offset = 0; + copy.limit = -1; + copy.external = e2; + copy.containsNull = containsNull; + return copy; + } + + /** + * Sets sort order to be used by this result. When rows are presorted by the + * query this method should not be used. + * + * @param sort the sort order + */ + public void setSortOrder(SortOrder sort) { + this.sort = sort; + } + + /** + * Remove duplicate rows. + */ + public void setDistinct() { + assert distinctIndexes == null; + distinct = true; + distinctRows = new TreeMap<>(session.getDatabase().getCompareMode()); + } + + /** + * Remove rows with duplicates in columns with specified indexes. + * + * @param distinctIndexes distinct indexes + */ + public void setDistinct(int[] distinctIndexes) { + assert !distinct; + this.distinctIndexes = distinctIndexes; + distinctRows = new TreeMap<>(session.getDatabase().getCompareMode()); + } + + /** + * @return whether this result is a distinct result + */ + private boolean isAnyDistinct() { + return distinct || distinctIndexes != null; + } + + /** + * Check if this result set contains the given row. + * + * @param values the row + * @return true if the row exists + */ + public boolean containsDistinct(Value[] values) { + assert values.length == visibleColumnCount; + if (external != null) { + return external.contains(values); + } + if (distinctRows == null) { + distinctRows = new TreeMap<>(session.getDatabase().getCompareMode()); + for (Value[] row : rows) { + ValueRow array = getDistinctRow(row); + distinctRows.put(array, array.getList()); + } + } + ValueRow array = ValueRow.get(values); + return distinctRows.get(array) != null; + } + + /** + * Check if this result set contains a NULL value. This method may reset + * this result. + * + * @return true if there is a NULL value + */ + public boolean containsNull() { + Boolean r = containsNull; + if (r == null) { + r = false; + reset(); + loop: while (next()) { + Value[] row = currentRow; + for (int i = 0; i < visibleColumnCount; i++) { + if (row[i].containsNull()) { + r = true; + break loop; + } + } + } + reset(); + containsNull = r; + } + return r; + } + + /** + * Remove the row from the result set if it exists. + * + * @param values the row + */ + public void removeDistinct(Value[] values) { + if (!distinct) { + throw DbException.getInternalError(); + } + assert values.length == visibleColumnCount; + if (distinctRows != null) { + distinctRows.remove(ValueRow.get(values)); + rowCount = distinctRows.size(); + } else { + rowCount = external.removeRow(values); + } + } + + @Override + public void reset() { + rowId = -1; + currentRow = null; + if (external != null) { + external.reset(); + } + } + + /** + * Retrieve the current row + * @return row + */ + public Row currentRowForTable() { + int degree = visibleColumnCount; + Value[] currentRow = this.currentRow; + Row row = session.getDatabase().getRowFactory() + .createRow(Arrays.copyOf(currentRow, degree), SearchRow.MEMORY_CALCULATE); + row.setKey(currentRow[degree].getLong()); + return row; + } + + @Override + public Value[] currentRow() { + return currentRow; + } + + @Override + public boolean next() { + if (!closed && rowId < rowCount) { + rowId++; + if (rowId < rowCount) { + if (external != null) { + currentRow = external.next(); + } else { + currentRow = rows.get((int) rowId); + } + return true; + } + currentRow = null; + } + return false; + } + + @Override + public long getRowId() { + return rowId; + } + + @Override + public boolean isAfterLast() { + return rowId >= rowCount; + } + + private void cloneLobs(Value[] values) { + for (int i = 0; i < values.length; i++) { + Value v = values[i]; + if (v instanceof ValueLob) { + if (forDataChangeDeltaTable) { + containsLobs = true; + } else { + ValueLob v2 = ((ValueLob) v).copyToResult(); + if (v2 != v) { + containsLobs = true; + values[i] = session.addTemporaryLob(v2); + } + } + } + } + } + + private ValueRow getDistinctRow(Value[] values) { + if (distinctIndexes != null) { + int cnt = distinctIndexes.length; + Value[] newValues = new Value[cnt]; + for (int i = 0; i < cnt; i++) { + newValues[i] = values[distinctIndexes[i]]; + } + values = newValues; + } else if (values.length > visibleColumnCount) { + values = Arrays.copyOf(values, visibleColumnCount); + } + return ValueRow.get(values); + } + + private void createExternalResult() { + external = MVTempResult.of(session.getDatabase(), expressions, distinct, distinctIndexes, visibleColumnCount, + resultColumnCount, sort); + } + + /** + * Add a row for a table. + * + * @param row the row to add + */ + public void addRowForTable(Row row) { + int degree = visibleColumnCount; + Value[] values = new Value[degree + 1]; + for (int i = 0; i < degree; i++) { + values[i] = row.getValue(i); + } + values[degree] = ValueBigint.get(row.getKey()); + addRowInternal(values); + } + + /** + * Add a row to this object. + * + * @param values the row to add + */ + @Override + public void addRow(Value... values) { + assert values.length == resultColumnCount; + cloneLobs(values); + addRowInternal(values); + } + + private void addRowInternal(Value... values) { + if (isAnyDistinct()) { + if (distinctRows != null) { + ValueRow distinctRow = getDistinctRow(values); + Value[] previous = distinctRows.get(distinctRow); + if (previous == null || sort != null && sort.compare(previous, values) > 0) { + distinctRows.put(distinctRow, values); + } + rowCount = distinctRows.size(); + if (rowCount > maxMemoryRows) { + createExternalResult(); + rowCount = external.addRows(distinctRows.values()); + distinctRows = null; + } + } else { + rowCount = external.addRow(values); + } + } else { + rows.add(values); + rowCount++; + if (rows.size() > maxMemoryRows) { + addRowsToDisk(); + } + } + } + + private void addRowsToDisk() { + if (external == null) { + createExternalResult(); + } + rowCount = external.addRows(rows); + rows.clear(); + } + + @Override + public int getVisibleColumnCount() { + return visibleColumnCount; + } + + /** + * This method is called after all rows have been added. + */ + public void done() { + if (external != null) { + addRowsToDisk(); + } else { + if (isAnyDistinct()) { + rows = new ArrayList<>(distinctRows.values()); + } + if (sort != null && limit != 0 && !limitsWereApplied) { + boolean withLimit = limit > 0 && withTiesSortOrder == null; + if (offset > 0 || withLimit) { + int endExclusive = rows.size(); + if (offset < endExclusive) { + int fromInclusive = (int) offset; + if (withLimit && limit < endExclusive - fromInclusive) { + endExclusive = fromInclusive + (int) limit; + } + sort.sort(rows, fromInclusive, endExclusive); + } + } else { + sort.sort(rows); + } + } + } + applyOffsetAndLimit(); + reset(); + } + + private void applyOffsetAndLimit() { + if (limitsWereApplied) { + return; + } + long offset = Math.max(this.offset, 0); + long limit = this.limit; + if (offset == 0 && limit < 0 && !fetchPercent || rowCount == 0) { + return; + } + if (fetchPercent) { + if (limit < 0 || limit > 100) { + throw DbException.getInvalidValueException("FETCH PERCENT", limit); + } + // Oracle rounds percent up, do the same for now + limit = (limit * rowCount + 99) / 100; + } + boolean clearAll = offset >= rowCount || limit == 0; + if (!clearAll) { + long remaining = rowCount - offset; + limit = limit < 0 ? remaining : Math.min(remaining, limit); + if (offset == 0 && remaining <= limit) { + return; + } + } else { + limit = 0; + } + distinctRows = null; + rowCount = limit; + if (external == null) { + if (clearAll) { + rows.clear(); + return; + } + int to = (int) (offset + limit); + if (withTiesSortOrder != null) { + Value[] expected = rows.get(to - 1); + while (to < rows.size() && withTiesSortOrder.compare(expected, rows.get(to)) == 0) { + to++; + rowCount++; + } + } + if (offset != 0 || to != rows.size()) { + // avoid copying the whole array for each row + rows = new ArrayList<>(rows.subList((int) offset, to)); + } + } else { + if (clearAll) { + external.close(); + external = null; + return; + } + trimExternal(offset, limit); + } + } + + private void trimExternal(long offset, long limit) { + ResultExternal temp = external; + external = null; + temp.reset(); + while (--offset >= 0) { + temp.next(); + } + Value[] row = null; + while (--limit >= 0) { + row = temp.next(); + rows.add(row); + if (rows.size() > maxMemoryRows) { + addRowsToDisk(); + } + } + if (withTiesSortOrder != null && row != null) { + Value[] expected = row; + while ((row = temp.next()) != null && withTiesSortOrder.compare(expected, row) == 0) { + rows.add(row); + rowCount++; + if (rows.size() > maxMemoryRows) { + addRowsToDisk(); + } + } + } + if (external != null) { + addRowsToDisk(); + } + temp.close(); + } + + @Override + public long getRowCount() { + return rowCount; + } + + @Override + public void limitsWereApplied() { + this.limitsWereApplied = true; + } + + @Override + public boolean hasNext() { + return !closed && rowId < rowCount - 1; + } + + /** + * Set the number of rows that this result will return at the maximum. + * + * @param limit the limit (-1 means no limit, 0 means no rows) + */ + public void setLimit(long limit) { + this.limit = limit; + } + + /** + * @param fetchPercent whether limit expression specifies percentage of rows + */ + public void setFetchPercent(boolean fetchPercent) { + this.fetchPercent = fetchPercent; + } + + /** + * Enables inclusion of tied rows to result and sets the sort order for tied + * rows. The specified sort order must be the same as sort order if sort + * order was set. Passed value will be used if sort order was not set that + * is possible when rows are presorted. + * + * @param withTiesSortOrder the sort order for tied rows + */ + public void setWithTies(SortOrder withTiesSortOrder) { + assert sort == null || sort == withTiesSortOrder; + this.withTiesSortOrder = withTiesSortOrder; + } + + @Override + public boolean needToClose() { + return external != null; + } + + @Override + public void close() { + if (external != null) { + external.close(); + external = null; + closed = true; + } + } + + @Override + public String getAlias(int i) { + return expressions[i].getAlias(session, i); + } + + @Override + public String getTableName(int i) { + return expressions[i].getTableName(); + } + + @Override + public String getSchemaName(int i) { + return expressions[i].getSchemaName(); + } + + @Override + public String getColumnName(int i) { + return expressions[i].getColumnName(session, i); + } + + @Override + public TypeInfo getColumnType(int i) { + return expressions[i].getType(); + } + + @Override + public int getNullable(int i) { + return expressions[i].getNullable(); + } + + @Override + public boolean isIdentity(int i) { + return expressions[i].isIdentity(); + } + + /** + * Set the offset of the first row to return. + * + * @param offset the offset + */ + public void setOffset(long offset) { + this.offset = offset; + } + + @Override + public String toString() { + return super.toString() + " columns: " + visibleColumnCount + + " rows: " + rowCount + " pos: " + rowId; + } + + /** + * Check if this result set is closed. + * + * @return true if it is + */ + @Override + public boolean isClosed() { + return closed; + } + + @Override + public int getFetchSize() { + return 0; + } + + @Override + public void setFetchSize(int fetchSize) { + // ignore + } + +} diff --git a/h2/src/main/org/h2/result/MergedResult.java b/h2/src/main/org/h2/result/MergedResult.java new file mode 100644 index 0000000..5754582 --- /dev/null +++ b/h2/src/main/org/h2/result/MergedResult.java @@ -0,0 +1,88 @@ +/* + * 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.result; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.h2.util.Utils; +import org.h2.value.Value; + +/** + * Merged result. Used to combine several results into one. Merged result will + * contain rows from all appended results. Results are not required to have the + * same lists of columns, but required to have compatible column definitions, + * for example, if one result has a {@link java.sql.Types#VARCHAR} column + * {@code NAME} then another results that have {@code NAME} column should also + * define it with the same type. + */ +public final class MergedResult { + private final ArrayList> data = Utils.newSmallArrayList(); + + private final ArrayList columns = Utils.newSmallArrayList(); + + /** + * Appends a result. + * + * @param result + * result to append + */ + public void add(ResultInterface result) { + int count = result.getVisibleColumnCount(); + if (count == 0) { + return; + } + SimpleResult.Column[] cols = new SimpleResult.Column[count]; + for (int i = 0; i < count; i++) { + SimpleResult.Column c = new SimpleResult.Column(result.getAlias(i), result.getColumnName(i), + result.getColumnType(i)); + cols[i] = c; + if (!columns.contains(c)) { + columns.add(c); + } + } + while (result.next()) { + if (count == 1) { + data.add(Collections.singletonMap(cols[0], result.currentRow()[0])); + } else { + HashMap map = new HashMap<>(); + for (int i = 0; i < count; i++) { + SimpleResult.Column ci = cols[i]; + map.put(ci, result.currentRow()[i]); + } + data.add(map); + } + } + } + + /** + * Returns merged results. + * + * @return result with rows from all appended result sets + */ + public SimpleResult getResult() { + SimpleResult result = new SimpleResult(); + for (SimpleResult.Column c : columns) { + result.addColumn(c); + } + for (Map map : data) { + Value[] row = new Value[columns.size()]; + for (Map.Entry entry : map.entrySet()) { + row[columns.indexOf(entry.getKey())] = entry.getValue(); + } + result.addRow(row); + } + return result; + } + + @Override + public String toString() { + return columns + ": " + data.size(); + } + +} diff --git a/h2/src/main/org/h2/result/ResultColumn.java b/h2/src/main/org/h2/result/ResultColumn.java new file mode 100644 index 0000000..f8cc1a5 --- /dev/null +++ b/h2/src/main/org/h2/result/ResultColumn.java @@ -0,0 +1,95 @@ +/* + * 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.result; + +import java.io.IOException; + +import org.h2.engine.Constants; +import org.h2.value.Transfer; +import org.h2.value.TypeInfo; + +/** + * A result set column of a remote result. + */ +public class ResultColumn { + + /** + * The column alias. + */ + final String alias; + + /** + * The schema name or null. + */ + final String schemaName; + + /** + * The table name or null. + */ + final String tableName; + + /** + * The column name or null. + */ + final String columnName; + + /** + * The column type. + */ + final TypeInfo columnType; + + /** + * True if this is an identity column. + */ + final boolean identity; + + /** + * True if this column is nullable. + */ + final int nullable; + + /** + * Read an object from the given transfer object. + * + * @param in the object from where to read the data + */ + ResultColumn(Transfer in) throws IOException { + alias = in.readString(); + schemaName = in.readString(); + tableName = in.readString(); + columnName = in.readString(); + columnType = in.readTypeInfo(); + if (in.getVersion() < Constants.TCP_PROTOCOL_VERSION_20) { + in.readInt(); + } + identity = in.readBoolean(); + nullable = in.readInt(); + } + + /** + * Write a result column to the given output. + * + * @param out the object to where to write the data + * @param result the result + * @param i the column index + * @throws IOException on failure + */ + public static void writeColumn(Transfer out, ResultInterface result, int i) + throws IOException { + out.writeString(result.getAlias(i)); + out.writeString(result.getSchemaName(i)); + out.writeString(result.getTableName(i)); + out.writeString(result.getColumnName(i)); + TypeInfo type = result.getColumnType(i); + out.writeTypeInfo(type); + if (out.getVersion() < Constants.TCP_PROTOCOL_VERSION_20) { + out.writeInt(type.getDisplaySize()); + } + out.writeBoolean(result.isIdentity(i)); + out.writeInt(result.getNullable(i)); + } + +} diff --git a/h2/src/main/org/h2/result/ResultExternal.java b/h2/src/main/org/h2/result/ResultExternal.java new file mode 100644 index 0000000..c61b5a1 --- /dev/null +++ b/h2/src/main/org/h2/result/ResultExternal.java @@ -0,0 +1,74 @@ +/* + * 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.result; + +import java.util.Collection; +import org.h2.value.Value; + +/** + * This interface is used to extend the LocalResult class, if data does not fit + * in memory. + */ +public interface ResultExternal { + + /** + * Reset the current position of this object. + */ + void reset(); + + /** + * Get the next row from the result. + * + * @return the next row or null + */ + Value[] next(); + + /** + * Add a row to this object. + * + * @param values the row to add + * @return the new number of rows in this object + */ + int addRow(Value[] values); + + /** + * Add a number of rows to the result. + * + * @param rows the list of rows to add + * @return the new number of rows in this object + */ + int addRows(Collection rows); + + /** + * Close this object and delete the temporary file. + */ + void close(); + + /** + * Remove the row with the given values from this object if such a row + * exists. + * + * @param values the row + * @return the new row count + */ + int removeRow(Value[] values); + + /** + * Check if the given row exists in this object. + * + * @param values the row + * @return true if it exists + */ + boolean contains(Value[] values); + + /** + * Create a shallow copy of this object if possible. + * + * @return the shallow copy, or null + */ + ResultExternal createShallowCopy(); + +} diff --git a/h2/src/main/org/h2/result/ResultInterface.java b/h2/src/main/org/h2/result/ResultInterface.java new file mode 100644 index 0000000..c9ac258 --- /dev/null +++ b/h2/src/main/org/h2/result/ResultInterface.java @@ -0,0 +1,182 @@ +/* + * 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.result; + +import org.h2.engine.Session; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * The result interface is used by the LocalResult and ResultRemote class. + * A result may contain rows, or just an update count. + */ +public interface ResultInterface extends AutoCloseable { + + /** + * Go to the beginning of the result, that means + * before the first row. + */ + void reset(); + + /** + * Get the current row. + * + * @return the row + */ + Value[] currentRow(); + + /** + * Go to the next row. + * + * @return true if a row exists + */ + boolean next(); + + /** + * Get the current row id, starting with 0. + * -1 is returned when next() was not called yet. + * + * @return the row id + */ + long getRowId(); + + /** + * Check if the current position is after last row. + * + * @return true if after last + */ + boolean isAfterLast(); + + /** + * Get the number of visible columns. + * More columns may exist internally for sorting or grouping. + * + * @return the number of columns + */ + int getVisibleColumnCount(); + + /** + * Get the number of rows in this object. + * + * @return the number of rows + */ + long getRowCount(); + + /** + * Check if this result has more rows to fetch. + * + * @return true if it has + */ + boolean hasNext(); + + /** + * Check if this result set should be closed, for example because it is + * buffered using a temporary file. + * + * @return true if close should be called. + */ + boolean needToClose(); + + /** + * Close the result and delete any temporary files + */ + @Override + void close(); + + /** + * Get the column alias name for the column. + * + * @param i the column number (starting with 0) + * @return the alias name + */ + String getAlias(int i); + + /** + * Get the schema name for the column, if one exists. + * + * @param i the column number (starting with 0) + * @return the schema name or null + */ + String getSchemaName(int i); + + /** + * Get the table name for the column, if one exists. + * + * @param i the column number (starting with 0) + * @return the table name or null + */ + String getTableName(int i); + + /** + * Get the column name. + * + * @param i the column number (starting with 0) + * @return the column name + */ + String getColumnName(int i); + + /** + * Get the column data type. + * + * @param i the column number (starting with 0) + * @return the column data type + */ + TypeInfo getColumnType(int i); + + /** + * Check if this is an identity column. + * + * @param i the column number (starting with 0) + * @return true for identity columns + */ + boolean isIdentity(int i); + + /** + * Check if this column is nullable. + * + * @param i the column number (starting with 0) + * @return Column.NULLABLE_* + */ + int getNullable(int i); + + /** + * Set the fetch size for this result set. + * + * @param fetchSize the new fetch size + */ + void setFetchSize(int fetchSize); + + /** + * Get the current fetch size for this result set. + * + * @return the fetch size + */ + int getFetchSize(); + + /** + * Check if this a lazy execution result. + * + * @return true if it is a lazy result + */ + boolean isLazy(); + + /** + * Check if this result set is closed. + * + * @return true if it is + */ + boolean isClosed(); + + /** + * Create a shallow copy of the result set. The data and a temporary table + * (if there is any) is not copied. + * + * @param targetSession the session of the copy + * @return the copy if possible, or null if copying is not possible + */ + ResultInterface createShallowCopy(Session targetSession); + +} diff --git a/h2/src/main/org/h2/result/ResultRemote.java b/h2/src/main/org/h2/result/ResultRemote.java new file mode 100644 index 0000000..e3e5a53 --- /dev/null +++ b/h2/src/main/org/h2/result/ResultRemote.java @@ -0,0 +1,274 @@ +/* + * 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.result; + +import java.io.IOException; +import java.util.ArrayList; + +import org.h2.api.ErrorCode; +import org.h2.engine.SessionRemote; +import org.h2.engine.SysProperties; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.value.Transfer; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * The client side part of a result set that is kept on the server. + * In many cases, the complete data is kept on the client side, + * but for large results only a subset is in-memory. + */ +public final class ResultRemote extends FetchedResult { + + private int fetchSize; + private SessionRemote session; + private Transfer transfer; + private int id; + private final ResultColumn[] columns; + private long rowCount; + private long rowOffset; + private ArrayList result; + private final Trace trace; + + public ResultRemote(SessionRemote session, Transfer transfer, int id, + int columnCount, int fetchSize) throws IOException { + this.session = session; + trace = session.getTrace(); + this.transfer = transfer; + this.id = id; + this.columns = new ResultColumn[columnCount]; + rowCount = transfer.readRowCount(); + for (int i = 0; i < columnCount; i++) { + columns[i] = new ResultColumn(transfer); + } + rowId = -1; + this.fetchSize = fetchSize; + if (rowCount >= 0) { + fetchSize = (int) Math.min(rowCount, fetchSize); + result = new ArrayList<>(fetchSize); + } else { + result = new ArrayList<>(); + } + synchronized (session) { + try { + if (fetchRows(fetchSize)) { + rowCount = result.size(); + } + } catch (IOException e) { + throw DbException.convertIOException(e, null); + } + } + } + + @Override + public boolean isLazy() { + return rowCount < 0L; + } + + @Override + public String getAlias(int i) { + return columns[i].alias; + } + + @Override + public String getSchemaName(int i) { + return columns[i].schemaName; + } + + @Override + public String getTableName(int i) { + return columns[i].tableName; + } + + @Override + public String getColumnName(int i) { + return columns[i].columnName; + } + + @Override + public TypeInfo getColumnType(int i) { + return columns[i].columnType; + } + + @Override + public boolean isIdentity(int i) { + return columns[i].identity; + } + + @Override + public int getNullable(int i) { + return columns[i].nullable; + } + + @Override + public void reset() { + if (rowCount < 0L || rowOffset > 0L) { + throw DbException.get(ErrorCode.RESULT_SET_NOT_SCROLLABLE); + } + rowId = -1; + currentRow = null; + nextRow = null; + afterLast = false; + if (session == null) { + return; + } + synchronized (session) { + session.checkClosed(); + try { + session.traceOperation("RESULT_RESET", id); + transfer.writeInt(SessionRemote.RESULT_RESET).writeInt(id).flush(); + } catch (IOException e) { + throw DbException.convertIOException(e, null); + } + } + } + + @Override + public int getVisibleColumnCount() { + return columns.length; + } + + @Override + public long getRowCount() { + if (rowCount < 0L) { + throw DbException.getUnsupportedException("Row count is unknown for lazy result."); + } + return rowCount; + } + + @Override + public boolean hasNext() { + if (afterLast) { + return false; + } + if (nextRow == null) { + if (rowCount < 0L || rowId < rowCount - 1) { + long nextRowId = rowId + 1; + if (session != null) { + remapIfOld(); + if (nextRowId - rowOffset >= result.size()) { + fetchAdditionalRows(); + } + } + int index = (int) (nextRowId - rowOffset); + nextRow = index < result.size() ? result.get(index) : null; + } + } + return nextRow != null; + } + + private void sendClose() { + if (session == null) { + return; + } + // TODO result sets: no reset possible for larger remote result sets + try { + synchronized (session) { + session.traceOperation("RESULT_CLOSE", id); + transfer.writeInt(SessionRemote.RESULT_CLOSE).writeInt(id); + } + } catch (IOException e) { + trace.error(e, "close"); + } finally { + transfer = null; + session = null; + } + } + + @Override + public void close() { + result = null; + sendClose(); + } + + private void remapIfOld() { + try { + if (id <= session.getCurrentId() - SysProperties.SERVER_CACHED_OBJECTS / 2) { + // object is too old - we need to map it to a new id + int newId = session.getNextId(); + session.traceOperation("CHANGE_ID", id); + transfer.writeInt(SessionRemote.CHANGE_ID).writeInt(id).writeInt(newId); + id = newId; + // TODO remote result set: very old result sets may be + // already removed on the server (theoretically) - how to + // solve this? + } + } catch (IOException e) { + throw DbException.convertIOException(e, null); + } + } + + private void fetchAdditionalRows() { + synchronized (session) { + session.checkClosed(); + try { + rowOffset += result.size(); + result.clear(); + int fetch = fetchSize; + if (rowCount >= 0) { + fetch = (int) Math.min(fetch, rowCount - rowOffset); + } else if (fetch == Integer.MAX_VALUE) { + fetch = SysProperties.SERVER_RESULT_SET_FETCH_SIZE; + } + session.traceOperation("RESULT_FETCH_ROWS", id); + transfer.writeInt(SessionRemote.RESULT_FETCH_ROWS).writeInt(id).writeInt(fetch); + session.done(transfer); + fetchRows(fetch); + } catch (IOException e) { + throw DbException.convertIOException(e, null); + } + } + } + + private boolean fetchRows(int fetch) throws IOException { + int len = columns.length; + for (int r = 0; r < fetch; r++) { + switch (transfer.readByte()) { + case 1: { + Value[] values = new Value[len]; + for (int i = 0; i < len; i++) { + values[i] = transfer.readValue(columns[i].columnType); + } + result.add(values); + break; + } + case 0: + sendClose(); + return true; + case -1: + throw SessionRemote.readException(transfer); + default: + throw DbException.getInternalError(); + } + } + if (rowCount >= 0L && rowOffset + result.size() >= rowCount) { + sendClose(); + } + return false; + } + + @Override + public String toString() { + return "columns: " + columns.length + (rowCount < 0L ? " lazy" : " rows: " + rowCount) + " pos: " + rowId; + } + + @Override + public int getFetchSize() { + return fetchSize; + } + + @Override + public void setFetchSize(int fetchSize) { + this.fetchSize = fetchSize; + } + + @Override + public boolean isClosed() { + return result == null; + } + +} diff --git a/h2/src/main/org/h2/result/ResultTarget.java b/h2/src/main/org/h2/result/ResultTarget.java new file mode 100644 index 0000000..cca53de --- /dev/null +++ b/h2/src/main/org/h2/result/ResultTarget.java @@ -0,0 +1,36 @@ +/* + * 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.result; + +import org.h2.value.Value; + +/** + * A object where rows are written to. + */ +public interface ResultTarget { + + /** + * Add the row to the result set. + * + * @param values the values + */ + void addRow(Value... values); + + /** + * Get the number of rows. + * + * @return the number of rows + */ + long getRowCount(); + + /** + * A hint that sorting, offset and limit may be ignored by this result + * because they were applied during the query. This is useful for WITH TIES + * clause because result may contain tied rows above limit. + */ + void limitsWereApplied(); + +} diff --git a/h2/src/main/org/h2/result/ResultWithGeneratedKeys.java b/h2/src/main/org/h2/result/ResultWithGeneratedKeys.java new file mode 100644 index 0000000..62a8427 --- /dev/null +++ b/h2/src/main/org/h2/result/ResultWithGeneratedKeys.java @@ -0,0 +1,72 @@ +/* + * 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.result; + +/** + * Result of update command with optional generated keys. + */ +public class ResultWithGeneratedKeys { + /** + * Result of update command with generated keys; + */ + public static final class WithKeys extends ResultWithGeneratedKeys { + private final ResultInterface generatedKeys; + + /** + * Creates a result with update count and generated keys. + * + * @param updateCount + * update count + * @param generatedKeys + * generated keys + */ + public WithKeys(long updateCount, ResultInterface generatedKeys) { + super(updateCount); + this.generatedKeys = generatedKeys; + } + + @Override + public ResultInterface getGeneratedKeys() { + return generatedKeys; + } + } + + /** + * Returns a result with only update count. + * + * @param updateCount + * update count + * @return the result. + */ + public static ResultWithGeneratedKeys of(long updateCount) { + return new ResultWithGeneratedKeys(updateCount); + } + + private final long updateCount; + + ResultWithGeneratedKeys(long updateCount) { + this.updateCount = updateCount; + } + + /** + * Returns generated keys, or {@code null}. + * + * @return generated keys, or {@code null} + */ + public ResultInterface getGeneratedKeys() { + return null; + } + + /** + * Returns update count. + * + * @return update count + */ + public long getUpdateCount() { + return updateCount; + } + +} diff --git a/h2/src/main/org/h2/result/ResultWithPaddedStrings.java b/h2/src/main/org/h2/result/ResultWithPaddedStrings.java new file mode 100644 index 0000000..d195f91 --- /dev/null +++ b/h2/src/main/org/h2/result/ResultWithPaddedStrings.java @@ -0,0 +1,193 @@ +/* + * 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.result; + +import java.util.Arrays; +import org.h2.engine.Session; +import org.h2.util.MathUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueVarchar; + +/** + * Result with padded fixed length strings. + */ +public class ResultWithPaddedStrings implements ResultInterface { + + private final ResultInterface source; + + /** + * Returns wrapped result if necessary, or original result if it does not + * contain visible CHAR columns. + * + * @param source + * source result + * @return wrapped result or original result + */ + public static ResultInterface get(ResultInterface source) { + int count = source.getVisibleColumnCount(); + for (int i = 0; i < count; i++) { + if (source.getColumnType(i).getValueType() == Value.CHAR) { + return new ResultWithPaddedStrings(source); + } + } + return source; + } + + /** + * Creates new instance of result. + * + * @param source + * the source result + */ + private ResultWithPaddedStrings(ResultInterface source) { + this.source = source; + } + + @Override + public void reset() { + source.reset(); + } + + @Override + public Value[] currentRow() { + int count = source.getVisibleColumnCount(); + Value[] row = Arrays.copyOf(source.currentRow(), count); + for (int i = 0; i < count; i++) { + TypeInfo type = source.getColumnType(i); + if (type.getValueType() == Value.CHAR) { + long precision = type.getPrecision(); + if (precision == Integer.MAX_VALUE) { + // CHAR is CHAR(1) + precision = 1; + } + String s = row[i].getString(); + if (s != null && s.length() < precision) { + /* + * Use ValueString to avoid truncation of spaces. There is + * no difference between ValueStringFixed and ValueString + * for JDBC layer anyway. + */ + row[i] = ValueVarchar.get(rightPadWithSpaces(s, MathUtils.convertLongToInt(precision))); + } + } + } + return row; + } + + private static String rightPadWithSpaces(String s, int length) { + int used = s.length(); + if (length <= used) { + return s; + } + char[] res = new char[length]; + s.getChars(0, used, res, 0); + Arrays.fill(res, used, length, ' '); + return new String(res); + } + + @Override + public boolean next() { + return source.next(); + } + + @Override + public long getRowId() { + return source.getRowId(); + } + + @Override + public boolean isAfterLast() { + return source.isAfterLast(); + } + + @Override + public int getVisibleColumnCount() { + return source.getVisibleColumnCount(); + } + + @Override + public long getRowCount() { + return source.getRowCount(); + } + + @Override + public boolean hasNext() { + return source.hasNext(); + } + + @Override + public boolean needToClose() { + return source.needToClose(); + } + + @Override + public void close() { + source.close(); + } + + @Override + public String getAlias(int i) { + return source.getAlias(i); + } + + @Override + public String getSchemaName(int i) { + return source.getSchemaName(i); + } + + @Override + public String getTableName(int i) { + return source.getTableName(i); + } + + @Override + public String getColumnName(int i) { + return source.getColumnName(i); + } + + @Override + public TypeInfo getColumnType(int i) { + return source.getColumnType(i); + } + + @Override + public boolean isIdentity(int i) { + return source.isIdentity(i); + } + + @Override + public int getNullable(int i) { + return source.getNullable(i); + } + + @Override + public void setFetchSize(int fetchSize) { + source.setFetchSize(fetchSize); + } + + @Override + public int getFetchSize() { + return source.getFetchSize(); + } + + @Override + public boolean isLazy() { + return source.isLazy(); + } + + @Override + public boolean isClosed() { + return source.isClosed(); + } + + @Override + public ResultInterface createShallowCopy(Session targetSession) { + ResultInterface copy = source.createShallowCopy(targetSession); + return copy != null ? new ResultWithPaddedStrings(copy) : null; + } + +} diff --git a/h2/src/main/org/h2/result/Row.java b/h2/src/main/org/h2/result/Row.java new file mode 100644 index 0000000..29dbc80 --- /dev/null +++ b/h2/src/main/org/h2/result/Row.java @@ -0,0 +1,77 @@ +/* + * 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.result; + +import java.util.Arrays; + +import org.h2.value.Value; + +/** + * Represents a row in a table. + */ +public abstract class Row extends SearchRow { + + /** + * Creates a new row. + * + * @param data values of columns, or null + * @param memory used memory + * @return the allocated row + */ + public static Row get(Value[] data, int memory) { + return new DefaultRow(data, memory); + } + + /** + * Creates a new row with the specified key. + * + * @param data values of columns, or null + * @param memory used memory + * @param key the key + * @return the allocated row + */ + public static Row get(Value[] data, int memory, long key) { + Row r = new DefaultRow(data, memory); + r.setKey(key); + return r; + } + + /** + * Get values. + * + * @return values + */ + public abstract Value[] getValueList(); + + /** + * Check whether values of this row are equal to values of other row. + * + * @param other + * the other row + * @return {@code true} if values are equal, + * {@code false} otherwise + */ + public boolean hasSameValues(Row other) { + return Arrays.equals(getValueList(), other.getValueList()); + } + + /** + * Check whether this row and the specified row share the same underlying + * data with values. This method must return {@code false} when values are + * not equal and may return either {@code true} or {@code false} when they + * are equal. This method may be used only for optimizations and should not + * perform any slow checks, such as equality checks for all pairs of values. + * + * @param other + * the other row + * @return {@code true} if rows share the same underlying data, + * {@code false} otherwise or when unknown + */ + public boolean hasSharedData(Row other) { + return false; + } + +} diff --git a/h2/src/main/org/h2/result/RowFactory.java b/h2/src/main/org/h2/result/RowFactory.java new file mode 100644 index 0000000..0a257fd --- /dev/null +++ b/h2/src/main/org/h2/result/RowFactory.java @@ -0,0 +1,207 @@ +/* + * 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.result; + +import org.h2.engine.CastDataProvider; +import org.h2.mvstore.db.RowDataType; +import org.h2.store.DataHandler; +import org.h2.table.IndexColumn; +import org.h2.value.CompareMode; +import org.h2.value.TypeInfo; +import org.h2.value.Typed; +import org.h2.value.Value; + +/** + * Creates rows. + * + * @author Sergi Vladykin + * @author Andrei Tokar + */ +public abstract class RowFactory { + + private static final class Holder { + static final RowFactory EFFECTIVE = DefaultRowFactory.INSTANCE; + } + + public static DefaultRowFactory getDefaultRowFactory() { + return DefaultRowFactory.INSTANCE; + } + + public static RowFactory getRowFactory() { + return Holder.EFFECTIVE; + } + + /** + * Create a new row factory. + * + * @param provider the cast provider + * @param compareMode the compare mode + * @param handler the data handler + * @param columns the list of columns + * @param indexColumns the list of index columns + * @param storeKeys whether row keys are stored + * @return the (possibly new) row factory + */ + public RowFactory createRowFactory(CastDataProvider provider, CompareMode compareMode, DataHandler handler, + Typed[] columns, IndexColumn[] indexColumns, boolean storeKeys) { + return this; + } + + /** + * Create a new row. + * + * @param data the values + * @param memory the estimated memory usage in bytes + * @return the created row + */ + public abstract Row createRow(Value[] data, int memory); + + /** + * Create new row. + * + * @return the created row + */ + public abstract SearchRow createRow(); + + public abstract RowDataType getRowDataType(); + + public abstract int[] getIndexes(); + + public abstract TypeInfo[] getColumnTypes(); + + public abstract int getColumnCount(); + + public abstract boolean getStoreKeys(); + + + /** + * Default implementation of row factory. + */ + public static final class DefaultRowFactory extends RowFactory { + private final RowDataType dataType; + private final int columnCount; + private final int[] indexes; + private TypeInfo[] columnTypes; + private final int[] map; + + public static final DefaultRowFactory INSTANCE = new DefaultRowFactory(); + + DefaultRowFactory() { + this(new RowDataType(null, CompareMode.getInstance(null, 0), null, null, null, 0, true), 0, null, null); + } + + private DefaultRowFactory(RowDataType dataType, int columnCount, int[] indexes, TypeInfo[] columnTypes) { + this.dataType = dataType; + this.columnCount = columnCount; + this.indexes = indexes; + if (indexes == null) { + map = null; + } else { + map = new int[columnCount]; + for (int i = 0, l = indexes.length; i < l;) { + map[indexes[i]] = ++i; + } + } + this.columnTypes = columnTypes; + } + + @Override + public RowFactory createRowFactory(CastDataProvider provider, CompareMode compareMode, DataHandler handler, + Typed[] columns, IndexColumn[] indexColumns, boolean storeKeys) { + int[] indexes = null; + int[] sortTypes = null; + TypeInfo[] columnTypes = null; + int columnCount = 0; + if (columns != null) { + columnCount = columns.length; + if (indexColumns == null) { + sortTypes = new int[columnCount]; + for (int i = 0; i < columnCount; i++) { + sortTypes[i] = SortOrder.ASCENDING; + } + } else { + int len = indexColumns.length; + indexes = new int[len]; + sortTypes = new int[len]; + for (int i = 0; i < len; i++) { + IndexColumn indexColumn = indexColumns[i]; + indexes[i] = indexColumn.column.getColumnId(); + sortTypes[i] = indexColumn.sortType; + } + } + columnTypes = new TypeInfo[columnCount]; + for (int i = 0; i < columnCount; i++) { + columnTypes[i] = columns[i].getType(); + } + } + return createRowFactory(provider, compareMode, handler, sortTypes, indexes, columnTypes, columnCount, + storeKeys); + } + + /** + * Create a new row factory. + * + * @param provider the cast provider + * @param compareMode the compare mode + * @param handler the data handler + * @param sortTypes the sort types + * @param indexes the list of indexed columns + * @param columnTypes the list of column data type information + * @param columnCount the number of columns + * @param storeKeys whether row keys are stored + * @return the (possibly new) row factory + */ + public RowFactory createRowFactory(CastDataProvider provider, CompareMode compareMode, DataHandler handler, + int[] sortTypes, int[] indexes, TypeInfo[] columnTypes, int columnCount, boolean storeKeys) { + RowDataType rowDataType = new RowDataType(provider, compareMode, handler, sortTypes, indexes, columnCount, + storeKeys); + RowFactory rowFactory = new DefaultRowFactory(rowDataType, columnCount, indexes, columnTypes); + rowDataType.setRowFactory(rowFactory); + return rowFactory; + } + + @Override + public Row createRow(Value[] data, int memory) { + return new DefaultRow(data, memory); + } + + @Override + public SearchRow createRow() { + if (indexes == null) { + return new DefaultRow(columnCount); + } else if (indexes.length == 1) { + return new SimpleRowValue(columnCount, indexes[0]); + } else { + return new Sparse(columnCount, indexes.length, map); + } + } + + @Override + public RowDataType getRowDataType() { + return dataType; + } + + @Override + public int[] getIndexes() { + return indexes; + } + + @Override + public TypeInfo[] getColumnTypes() { + return columnTypes; + } + + @Override + public int getColumnCount() { + return columnCount; + } + + @Override + public boolean getStoreKeys() { + return dataType.isStoreKeys(); + } + } +} diff --git a/h2/src/main/org/h2/result/SearchRow.java b/h2/src/main/org/h2/result/SearchRow.java new file mode 100644 index 0000000..80babce --- /dev/null +++ b/h2/src/main/org/h2/result/SearchRow.java @@ -0,0 +1,146 @@ +/* + * 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.result; + +import org.h2.engine.CastDataProvider; +import org.h2.value.CompareMode; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * The base class for rows stored in a table, and for partial rows stored in the + * index. + */ +public abstract class SearchRow extends Value { + + /** + * Index of a virtual "_ROWID_" column within a row or a table + */ + public static final int ROWID_INDEX = -1; + + /** + * If the key is this value, then the key is considered equal to all other + * keys, when comparing. + */ + public static long MATCH_ALL_ROW_KEY = Long.MIN_VALUE + 1; + + /** + * The constant that means "memory usage is unknown and needs to be calculated first". + */ + public static final int MEMORY_CALCULATE = -1; + + /** + * The row key. + */ + protected long key; + + /** + * Get the column count. + * + * @return the column count + */ + public abstract int getColumnCount(); + + /** + * Determine if specified column contains NULL + * @param index column index + * @return true if NULL + */ + public boolean isNull(int index) { + return getValue(index) == ValueNull.INSTANCE; + } + + /** + * Get the value for the column + * + * @param index the column number (starting with 0) + * @return the value + */ + public abstract Value getValue(int index); + + /** + * Set the value for given column + * + * @param index the column number (starting with 0) + * @param v the new value + */ + public abstract void setValue(int index, Value v); + + /** + * Set the unique key of the row. + * + * @param key the key + */ + public void setKey(long key) { + this.key = key; + } + + /** + * Get the unique key of the row. + * + * @return the key + */ + public long getKey() { + return key; + } + + /** + * Get the estimated memory used for this row, in bytes. + * + * @return the memory + */ + @Override + public abstract int getMemory(); + + /** + * Copy all relevant values from the source to this row. + * @param source source of column values + */ + public abstract void copyFrom(SearchRow source); + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_ROW_EMPTY; + } + + @Override + public int getValueType() { + return Value.ROW; + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + builder.append("ROW ("); + for (int index = 0, count = getColumnCount(); index < count; index++) { + if (index != 0) { + builder.append(", "); + } + getValue(index).getSQL(builder, sqlFlags); + } + return builder.append(')'); + } + + @Override + public String getString() { + return getTraceSQL(); + } + + @Override + public int hashCode() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean equals(Object other) { + throw new UnsupportedOperationException(); + } + + @Override + public int compareTypeSafe(Value v, CompareMode mode, CastDataProvider provider) { + throw new UnsupportedOperationException(); + } +} diff --git a/h2/src/main/org/h2/result/SimpleResult.java b/h2/src/main/org/h2/result/SimpleResult.java new file mode 100644 index 0000000..c47a315 --- /dev/null +++ b/h2/src/main/org/h2/result/SimpleResult.java @@ -0,0 +1,302 @@ +/* + * 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.result; + +import java.sql.ResultSetMetaData; +import java.util.ArrayList; +import java.util.Comparator; + +import org.h2.engine.Session; +import org.h2.util.Utils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * Simple in-memory result. + */ +public class SimpleResult implements ResultInterface, ResultTarget { + + /** + * Column info for the simple result. + */ + static final class Column { + /** Column alias. */ + final String alias; + + /** Column name. */ + final String columnName; + + /** Column type. */ + final TypeInfo columnType; + + Column(String alias, String columnName, TypeInfo columnType) { + if (alias == null || columnName == null) { + throw new NullPointerException(); + } + this.alias = alias; + this.columnName = columnName; + this.columnType = columnType; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + alias.hashCode(); + result = prime * result + columnName.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + Column other = (Column) obj; + return alias.equals(other.alias) && columnName.equals(other.columnName); + } + + @Override + public String toString() { + if (alias.equals(columnName)) { + return columnName; + } + return columnName + ' ' + alias; + } + + } + + private final ArrayList columns; + + private final ArrayList rows; + + private final String schemaName, tableName; + + private int rowId; + + /** + * Creates new instance of simple result. + */ + public SimpleResult() { + this("", ""); + } + + /** + * Creates new instance of simple result. + * + * @param schemaName + * the name of the schema + * @param tableName + * the name of the table + */ + public SimpleResult(String schemaName, String tableName) { + this.columns = Utils.newSmallArrayList(); + this.rows = new ArrayList<>(); + this.schemaName = schemaName; + this.tableName = tableName; + this.rowId = -1; + } + + private SimpleResult(ArrayList columns, ArrayList rows, String schemaName, String tableName) { + this.columns = columns; + this.rows = rows; + this.schemaName = schemaName; + this.tableName = tableName; + this.rowId = -1; + } + + /** + * Add column to the result. + * + * @param alias + * Column's alias. + * @param columnName + * Column's name. + * @param columnType + * Column's value type. + * @param columnPrecision + * Column's precision. + * @param columnScale + * Column's scale. + */ + public void addColumn(String alias, String columnName, int columnType, long columnPrecision, int columnScale) { + addColumn(alias, columnName, TypeInfo.getTypeInfo(columnType, columnPrecision, columnScale, null)); + } + + /** + * Add column to the result. + * + * @param columnName + * Column's name. + * @param columnType + * Column's type. + */ + public void addColumn(String columnName, TypeInfo columnType) { + addColumn(new Column(columnName, columnName, columnType)); + } + + /** + * Add column to the result. + * + * @param alias + * Column's alias. + * @param columnName + * Column's name. + * @param columnType + * Column's type. + */ + public void addColumn(String alias, String columnName, TypeInfo columnType) { + addColumn(new Column(alias, columnName, columnType)); + } + + /** + * Add column to the result. + * + * @param column + * Column info. + */ + void addColumn(Column column) { + assert rows.isEmpty(); + columns.add(column); + } + + @Override + public void addRow(Value... values) { + assert values.length == columns.size(); + rows.add(values); + } + + @Override + public void reset() { + rowId = -1; + } + + @Override + public Value[] currentRow() { + return rows.get(rowId); + } + + @Override + public boolean next() { + int count = rows.size(); + if (rowId < count) { + return ++rowId < count; + } + return false; + } + + @Override + public long getRowId() { + return rowId; + } + + @Override + public boolean isAfterLast() { + return rowId >= rows.size(); + } + + @Override + public int getVisibleColumnCount() { + return columns.size(); + } + + @Override + public long getRowCount() { + return rows.size(); + } + + @Override + public boolean hasNext() { + return rowId < rows.size() - 1; + } + + @Override + public boolean needToClose() { + return false; + } + + @Override + public void close() { + // Do nothing for now + } + + @Override + public String getAlias(int i) { + return columns.get(i).alias; + } + + @Override + public String getSchemaName(int i) { + return schemaName; + } + + @Override + public String getTableName(int i) { + return tableName; + } + + @Override + public String getColumnName(int i) { + return columns.get(i).columnName; + } + + @Override + public TypeInfo getColumnType(int i) { + return columns.get(i).columnType; + } + + @Override + public boolean isIdentity(int i) { + return false; + } + + @Override + public int getNullable(int i) { + return ResultSetMetaData.columnNullableUnknown; + } + + @Override + public void setFetchSize(int fetchSize) { + // Ignored + } + + @Override + public int getFetchSize() { + return 1; + } + + @Override + public boolean isLazy() { + return false; + } + + @Override + public boolean isClosed() { + return false; + } + + @Override + public SimpleResult createShallowCopy(Session targetSession) { + return new SimpleResult(columns, rows, schemaName, tableName); + } + + @Override + public void limitsWereApplied() { + // Nothing to do + } + + /** + * Sort rows in the list. + * + * @param comparator + * the comparator + */ + public void sortRows(Comparator comparator) { + rows.sort(comparator); + } + +} diff --git a/h2/src/main/org/h2/result/SimpleRowValue.java b/h2/src/main/org/h2/result/SimpleRowValue.java new file mode 100644 index 0000000..84181cd --- /dev/null +++ b/h2/src/main/org/h2/result/SimpleRowValue.java @@ -0,0 +1,74 @@ +/* + * 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.result; + +import org.h2.engine.Constants; +import org.h2.value.Value; +import org.h2.value.ValueBigint; +import org.h2.value.ValueNull; + +/** + * A simple row that contains data for only one column. + */ +public class SimpleRowValue extends SearchRow { + + private int index; + private final int virtualColumnCount; + private Value data; + + public SimpleRowValue(int columnCount) { + this.virtualColumnCount = columnCount; + } + + public SimpleRowValue(int columnCount, int index) { + this.virtualColumnCount = columnCount; + this.index = index; + } + + @Override + public int getColumnCount() { + return virtualColumnCount; + } + + @Override + public Value getValue(int idx) { + if (idx == ROWID_INDEX) { + return ValueBigint.get(getKey()); + } + return idx == index ? data : null; + } + + @Override + public void setValue(int idx, Value v) { + if (idx == ROWID_INDEX) { + setKey(v.getLong()); + } + index = idx; + data = v; + } + + @Override + public String toString() { + return "( /* " + key + " */ " + (data == null ? + "null" : data.getTraceSQL()) + " )"; + } + + @Override + public int getMemory() { + return Constants.MEMORY_ROW + (data == null ? 0 : data.getMemory()); + } + + @Override + public boolean isNull(int index) { + return index != this.index || data == null || data == ValueNull.INSTANCE; + } + + @Override + public void copyFrom(SearchRow source) { + setKey(source.getKey()); + setValue(index, source.getValue(index)); + } +} diff --git a/h2/src/main/org/h2/result/SortOrder.java b/h2/src/main/org/h2/result/SortOrder.java new file mode 100644 index 0000000..65b9782 --- /dev/null +++ b/h2/src/main/org/h2/result/SortOrder.java @@ -0,0 +1,296 @@ +/* + * 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.result; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +import org.h2.command.query.QueryOrderBy; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.mode.DefaultNullOrdering; +import org.h2.table.Column; +import org.h2.table.TableFilter; +import org.h2.util.Utils; +import org.h2.value.Value; +import org.h2.value.ValueNull; +import org.h2.value.ValueRow; + +/** + * A sort order represents an ORDER BY clause in a query. + */ +public final class SortOrder implements Comparator { + + /** + * This bit mask means the values should be sorted in ascending order. + */ + public static final int ASCENDING = 0; + + /** + * This bit mask means the values should be sorted in descending order. + */ + public static final int DESCENDING = 1; + + /** + * This bit mask means NULLs should be sorted before other data, no matter + * if ascending or descending order is used. + */ + public static final int NULLS_FIRST = 2; + + /** + * This bit mask means NULLs should be sorted after other data, no matter + * if ascending or descending order is used. + */ + public static final int NULLS_LAST = 4; + + private final SessionLocal session; + + /** + * The column indexes of the order by expressions within the query. + */ + private final int[] queryColumnIndexes; + + /** + * The sort type bit mask (DESCENDING, NULLS_FIRST, NULLS_LAST). + */ + private final int[] sortTypes; + + /** + * The order list. + */ + private final ArrayList orderList; + + /** + * Construct a new sort order object with default sort directions. + * + * @param session the session + * @param queryColumnIndexes the column index list + */ + public SortOrder(SessionLocal session, int[] queryColumnIndexes) { + this (session, queryColumnIndexes, new int[queryColumnIndexes.length], null); + } + + /** + * Construct a new sort order object. + * + * @param session the session + * @param queryColumnIndexes the column index list + * @param sortType the sort order bit masks + * @param orderList the original query order list (if this is a query) + */ + public SortOrder(SessionLocal session, int[] queryColumnIndexes, int[] sortType, + ArrayList orderList) { + this.session = session; + this.queryColumnIndexes = queryColumnIndexes; + this.sortTypes = sortType; + this.orderList = orderList; + } + + /** + * Create the SQL snippet that describes this sort order. + * This is the SQL snippet that usually appears after the ORDER BY clause. + * + * @param builder string builder to append to + * @param list the expression list + * @param visible the number of columns in the select list + * @param sqlFlags formatting flags + * @return the specified string builder + */ + public StringBuilder getSQL(StringBuilder builder, Expression[] list, int visible, int sqlFlags) { + int i = 0; + for (int idx : queryColumnIndexes) { + if (i > 0) { + builder.append(", "); + } + if (idx < visible) { + builder.append(idx + 1); + } else { + list[idx].getUnenclosedSQL(builder, sqlFlags); + } + typeToString(builder, sortTypes[i++]); + } + return builder; + } + + /** + * Appends type information (DESC, NULLS FIRST, NULLS LAST) to the specified statement builder. + * @param builder string builder + * @param type sort type + */ + public static void typeToString(StringBuilder builder, int type) { + if ((type & DESCENDING) != 0) { + builder.append(" DESC"); + } + if ((type & NULLS_FIRST) != 0) { + builder.append(" NULLS FIRST"); + } else if ((type & NULLS_LAST) != 0) { + builder.append(" NULLS LAST"); + } + } + + /** + * Compare two expression lists. + * + * @param a the first expression list + * @param b the second expression list + * @return the result of the comparison + */ + @Override + public int compare(Value[] a, Value[] b) { + for (int i = 0, len = queryColumnIndexes.length; i < len; i++) { + int idx = queryColumnIndexes[i]; + int type = sortTypes[i]; + Value ao = a[idx]; + Value bo = b[idx]; + boolean aNull = ao == ValueNull.INSTANCE, bNull = bo == ValueNull.INSTANCE; + if (aNull || bNull) { + if (aNull == bNull) { + continue; + } + return session.getDatabase().getDefaultNullOrdering().compareNull(aNull, type); + } + int comp = session.compare(ao, bo); + if (comp != 0) { + return (type & DESCENDING) == 0 ? comp : -comp; + } + } + return 0; + } + + /** + * Sort a list of rows. + * + * @param rows the list of rows + */ + public void sort(ArrayList rows) { + rows.sort(this); + } + + /** + * Sort a list of rows using offset and limit. + * + * @param rows the list of rows + * @param fromInclusive the start index, inclusive + * @param toExclusive the end index, exclusive + */ + public void sort(ArrayList rows, int fromInclusive, int toExclusive) { + if (toExclusive == 1 && fromInclusive == 0) { + rows.set(0, Collections.min(rows, this)); + return; + } + Value[][] arr = rows.toArray(new Value[0][]); + Utils.sortTopN(arr, fromInclusive, toExclusive, this); + for (int i = fromInclusive; i < toExclusive; i++) { + rows.set(i, arr[i]); + } + } + + /** + * Get the column index list. This is the column indexes of the order by + * expressions within the query. + *

+ * For the query "select name, id from test order by id, name" this is {1, + * 0} as the first order by expression (the column "id") is the second + * column of the query, and the second order by expression ("name") is the + * first column of the query. + * + * @return the list + */ + public int[] getQueryColumnIndexes() { + return queryColumnIndexes; + } + + /** + * Get the column for the given table filter, if the sort column is for this + * filter. + * + * @param index the column index (0, 1,..) + * @param filter the table filter + * @return the column, or null + */ + public Column getColumn(int index, TableFilter filter) { + if (orderList == null) { + return null; + } + QueryOrderBy order = orderList.get(index); + Expression expr = order.expression; + if (expr == null) { + return null; + } + expr = expr.getNonAliasExpression(); + if (expr.isConstant()) { + return null; + } + if (!(expr instanceof ExpressionColumn)) { + return null; + } + ExpressionColumn exprCol = (ExpressionColumn) expr; + if (exprCol.getTableFilter() != filter) { + return null; + } + return exprCol.getColumn(); + } + + /** + * Get the sort order bit masks. + * + * @return the list + */ + public int[] getSortTypes() { + return sortTypes; + } + + /** + * Returns the original query order list. + * + * @return the original query order list + */ + public ArrayList getOrderList() { + return orderList; + } + + /** + * Returns sort order bit masks with {@link SortOrder#NULLS_FIRST} or + * {@link SortOrder#NULLS_LAST} explicitly set. + * + * @return bit masks with either {@link SortOrder#NULLS_FIRST} or {@link SortOrder#NULLS_LAST} + * explicitly set. + */ + public int[] getSortTypesWithNullOrdering() { + return addNullOrdering(session.getDatabase(), sortTypes.clone()); + } + + /** + * Add explicit {@link SortOrder#NULLS_FIRST} or {@link SortOrder#NULLS_LAST} where they + * aren't already specified. + * + * @param database + * the database + * @param sortTypes + * bit masks + * @return the specified array with possibly modified bit masks + */ + public static int[] addNullOrdering(Database database, int[] sortTypes) { + DefaultNullOrdering defaultNullOrdering = database.getDefaultNullOrdering(); + for (int i = 0, length = sortTypes.length; i < length; i++) { + sortTypes[i] = defaultNullOrdering.addExplicitNullOrdering(sortTypes[i]); + } + return sortTypes; + } + + /** + * Returns comparator for row values. + * + * @return comparator for row values. + */ + public Comparator getRowValueComparator() { + return (o1, o2) -> compare(((ValueRow) o1).getList(), ((ValueRow) o2).getList()); + } + +} diff --git a/h2/src/main/org/h2/result/Sparse.java b/h2/src/main/org/h2/result/Sparse.java new file mode 100644 index 0000000..828cd05 --- /dev/null +++ b/h2/src/main/org/h2/result/Sparse.java @@ -0,0 +1,64 @@ +/* + * 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.result; + +import org.h2.value.Value; +import org.h2.value.ValueBigint; + +/** + * Class Sparse. + *

    + *
  • 11/16/19 7:35 PM initial creation + *
+ * + * @author Andrei Tokar + */ +public final class Sparse extends DefaultRow { + private final int columnCount; + private final int[] map; + + Sparse(int columnCount, int capacity, int[] map) { + super(new Value[capacity]); + this.columnCount = columnCount; + this.map = map; + } + + @Override + public int getColumnCount() { + return columnCount; + } + + @Override + public Value getValue(int i) { + if (i == ROWID_INDEX) { + return ValueBigint.get(getKey()); + } + int index = map[i]; + return index > 0 ? super.getValue(index - 1) : null; + } + + @Override + public void setValue(int i, Value v) { + if (i == ROWID_INDEX) { + setKey(v.getLong()); + } + int index = map[i]; + if (index > 0) { + super.setValue(index - 1, v); + } + } + + @Override + public void copyFrom(SearchRow source) { + setKey(source.getKey()); + for (int i = 0; i < map.length; i++) { + int index = map[i]; + if (index > 0) { + super.setValue(index - 1, source.getValue(i)); + } + } + } +} diff --git a/h2/src/main/org/h2/result/UpdatableRow.java b/h2/src/main/org/h2/result/UpdatableRow.java new file mode 100644 index 0000000..fb3e707 --- /dev/null +++ b/h2/src/main/org/h2/result/UpdatableRow.java @@ -0,0 +1,340 @@ +/* + * 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.result; + +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; + +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.engine.Session; +import org.h2.engine.SessionRemote; +import org.h2.jdbc.JdbcConnection; +import org.h2.jdbc.JdbcResultSet; +import org.h2.message.DbException; +import org.h2.util.JdbcUtils; +import org.h2.util.StringUtils; +import org.h2.util.Utils; +import org.h2.value.Value; +import org.h2.value.ValueNull; +import org.h2.value.ValueToObjectConverter; + +/** + * This class is used for updatable result sets. An updatable row provides + * functions to update the current row in a result set. + */ +public class UpdatableRow { + + private final JdbcConnection conn; + private final ResultInterface result; + private final int columnCount; + private String schemaName; + private String tableName; + private ArrayList key; + private boolean isUpdatable; + + /** + * Construct a new object that is linked to the result set. The constructor + * reads the database meta data to find out if the result set is updatable. + * + * @param conn the database connection + * @param result the result + * @throws SQLException on failure + */ + public UpdatableRow(JdbcConnection conn, ResultInterface result) + throws SQLException { + this.conn = conn; + this.result = result; + columnCount = result.getVisibleColumnCount(); + if (columnCount == 0) { + return; + } + for (int i = 0; i < columnCount; i++) { + String t = result.getTableName(i); + String s = result.getSchemaName(i); + if (t == null || s == null) { + return; + } + if (tableName == null) { + tableName = t; + } else if (!tableName.equals(t)) { + return; + } + if (schemaName == null) { + schemaName = s; + } else if (!schemaName.equals(s)) { + return; + } + } + String type = "BASE TABLE"; + Session session = conn.getSession(); + if (session instanceof SessionRemote + && ((SessionRemote) session).getClientVersion() <= Constants.TCP_PROTOCOL_VERSION_19) { + type = "TABLE"; + } + final DatabaseMetaData meta = conn.getMetaData(); + ResultSet rs = meta.getTables(null, + StringUtils.escapeMetaDataPattern(schemaName), + StringUtils.escapeMetaDataPattern(tableName), + new String[] { type }); + if (!rs.next()) { + return; + } + String table = rs.getString("TABLE_NAME"); + // if the table name in the database meta data is lower case, + // but the table in the result set meta data is not, then the column + // in the database meta data is also lower case + boolean toUpper = !table.equals(tableName) && table.equalsIgnoreCase(tableName); + key = Utils.newSmallArrayList(); + rs = meta.getPrimaryKeys(null, + StringUtils.escapeMetaDataPattern(schemaName), + tableName); + while (rs.next()) { + String c = rs.getString("COLUMN_NAME"); + key.add(toUpper ? StringUtils.toUpperEnglish(c) : c); + } + if (isIndexUsable(key)) { + isUpdatable = true; + return; + } + key.clear(); + rs = meta.getIndexInfo(null, + StringUtils.escapeMetaDataPattern(schemaName), + tableName, true, true); + while (rs.next()) { + int pos = rs.getShort("ORDINAL_POSITION"); + if (pos == 1) { + // check the last key if there was any + if (isIndexUsable(key)) { + isUpdatable = true; + return; + } + key.clear(); + } + String c = rs.getString("COLUMN_NAME"); + key.add(toUpper ? StringUtils.toUpperEnglish(c) : c); + } + if (isIndexUsable(key)) { + isUpdatable = true; + return; + } + key = null; + } + + private boolean isIndexUsable(ArrayList indexColumns) { + if (indexColumns.isEmpty()) { + return false; + } + for (String c : indexColumns) { + if (findColumnIndex(c) < 0) { + return false; + } + } + return true; + } + + /** + * Check if this result set is updatable. + * + * @return true if it is + */ + public boolean isUpdatable() { + return isUpdatable; + } + + private int findColumnIndex(String columnName) { + for (int i = 0; i < columnCount; i++) { + String col = result.getColumnName(i); + if (col.equals(columnName)) { + return i; + } + } + return -1; + } + + private int getColumnIndex(String columnName) { + int index = findColumnIndex(columnName); + if (index < 0) { + throw DbException.get(ErrorCode.COLUMN_NOT_FOUND_1, columnName); + } + return index; + } + + private void appendColumnList(StringBuilder builder, boolean set) { + for (int i = 0; i < columnCount; i++) { + if (i > 0) { + builder.append(','); + } + String col = result.getColumnName(i); + StringUtils.quoteIdentifier(builder, col); + if (set) { + builder.append("=? "); + } + } + } + + private void appendKeyCondition(StringBuilder builder) { + builder.append(" WHERE "); + for (int i = 0; i < key.size(); i++) { + if (i > 0) { + builder.append(" AND "); + } + StringUtils.quoteIdentifier(builder, key.get(i)).append("=?"); + } + } + + private void setKey(PreparedStatement prep, int start, Value[] current) throws SQLException { + for (int i = 0, size = key.size(); i < size; i++) { + String col = key.get(i); + int idx = getColumnIndex(col); + Value v = current[idx]; + if (v == null || v == ValueNull.INSTANCE) { + // rows with a unique key containing NULL are not supported, + // as multiple such rows could exist + throw DbException.get(ErrorCode.NO_DATA_AVAILABLE); + } + JdbcUtils.set(prep, start + i, v, conn); + } + } + +// public boolean isRowDeleted(Value[] row) throws SQLException { +// StringBuilder buff = new StringBuilder(); +// buff.append("SELECT COUNT(*) FROM "). +// append(StringUtils.quoteIdentifier(tableName)); +// appendKeyCondition(buff); +// PreparedStatement prep = conn.prepareStatement(buff.toString()); +// setKey(prep, 1, row); +// ResultSet rs = prep.executeQuery(); +// rs.next(); +// return rs.getInt(1) == 0; +// } + + private void appendTableName(StringBuilder builder) { + if (schemaName != null && schemaName.length() > 0) { + StringUtils.quoteIdentifier(builder, schemaName).append('.'); + } + StringUtils.quoteIdentifier(builder, tableName); + } + + /** + * Re-reads a row from the database and updates the values in the array. + * + * @param row the values that contain the key + * @return the row + * @throws SQLException on failure + */ + public Value[] readRow(Value[] row) throws SQLException { + StringBuilder builder = new StringBuilder("SELECT "); + appendColumnList(builder, false); + builder.append(" FROM "); + appendTableName(builder); + appendKeyCondition(builder); + PreparedStatement prep = conn.prepareStatement(builder.toString()); + setKey(prep, 1, row); + JdbcResultSet rs = (JdbcResultSet) prep.executeQuery(); + if (!rs.next()) { + throw DbException.get(ErrorCode.NO_DATA_AVAILABLE); + } + Value[] newRow = new Value[columnCount]; + for (int i = 0; i < columnCount; i++) { + newRow[i] = ValueToObjectConverter.readValue(conn.getSession(), rs, i + 1); + } + return newRow; + } + + /** + * Delete the given row in the database. + * + * @param current the row + * @throws SQLException if this row has already been deleted + */ + public void deleteRow(Value[] current) throws SQLException { + StringBuilder builder = new StringBuilder("DELETE FROM "); + appendTableName(builder); + appendKeyCondition(builder); + PreparedStatement prep = conn.prepareStatement(builder.toString()); + setKey(prep, 1, current); + int count = prep.executeUpdate(); + if (count != 1) { + // the row has already been deleted + throw DbException.get(ErrorCode.NO_DATA_AVAILABLE); + } + } + + /** + * Update a row in the database. + * + * @param current the old row + * @param updateRow the new row + * @throws SQLException if the row has been deleted + */ + public void updateRow(Value[] current, Value[] updateRow) throws SQLException { + StringBuilder builder = new StringBuilder("UPDATE "); + appendTableName(builder); + builder.append(" SET "); + appendColumnList(builder, true); + // TODO updatable result set: we could add all current values to the + // where clause + // - like this optimistic ('no') locking is possible + appendKeyCondition(builder); + PreparedStatement prep = conn.prepareStatement(builder.toString()); + int j = 1; + for (int i = 0; i < columnCount; i++) { + Value v = updateRow[i]; + if (v == null) { + v = current[i]; + } + JdbcUtils.set(prep, j++, v, conn); + } + setKey(prep, j, current); + int count = prep.executeUpdate(); + if (count != 1) { + // the row has been deleted + throw DbException.get(ErrorCode.NO_DATA_AVAILABLE); + } + } + + /** + * Insert a new row into the database. + * + * @param row the new row + * @throws SQLException if the row could not be inserted + */ + public void insertRow(Value[] row) throws SQLException { + StringBuilder builder = new StringBuilder("INSERT INTO "); + appendTableName(builder); + builder.append('('); + appendColumnList(builder, false); + builder.append(")VALUES("); + for (int i = 0; i < columnCount; i++) { + if (i > 0) { + builder.append(','); + } + Value v = row[i]; + if (v == null) { + builder.append("DEFAULT"); + } else { + builder.append('?'); + } + } + builder.append(')'); + PreparedStatement prep = conn.prepareStatement(builder.toString()); + for (int i = 0, j = 0; i < columnCount; i++) { + Value v = row[i]; + if (v != null) { + JdbcUtils.set(prep, j++ + 1, v, conn); + } + } + int count = prep.executeUpdate(); + if (count != 1) { + throw DbException.get(ErrorCode.NO_DATA_AVAILABLE); + } + } + +} diff --git a/h2/src/main/org/h2/result/package.html b/h2/src/main/org/h2/result/package.html new file mode 100644 index 0000000..0629958 --- /dev/null +++ b/h2/src/main/org/h2/result/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Implementation of row and internal result sets. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/schema/Constant.java b/h2/src/main/org/h2/schema/Constant.java new file mode 100644 index 0000000..bcf523a --- /dev/null +++ b/h2/src/main/org/h2/schema/Constant.java @@ -0,0 +1,61 @@ +/* + * 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.schema; + +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; + +/** + * A user-defined constant as created by the SQL statement + * CREATE CONSTANT + */ +public final class Constant extends SchemaObject { + + private Value value; + private ValueExpression expression; + + 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 "); + getSQL(builder, DEFAULT_SQL_FLAGS).append(" VALUE "); + return value.getSQL(builder, DEFAULT_SQL_FLAGS).toString(); + } + + @Override + public int getType() { + return DbObject.CONSTANT; + } + + @Override + public void removeChildrenAndResources(SessionLocal session) { + database.removeMeta(session, getId()); + invalidate(); + } + + public void setValue(Value value) { + this.value = value; + expression = ValueExpression.get(value); + } + + public ValueExpression getValue() { + return expression; + } + +} diff --git a/h2/src/main/org/h2/schema/Domain.java b/h2/src/main/org/h2/schema/Domain.java new file mode 100644 index 0000000..1003a21 --- /dev/null +++ b/h2/src/main/org/h2/schema/Domain.java @@ -0,0 +1,224 @@ +/* + * 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.schema; + +import java.util.ArrayList; +import org.h2.constraint.Constraint; +import org.h2.constraint.ConstraintDomain; +import org.h2.engine.DbObject; +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; + +/** + * Represents a domain. + */ +public final class Domain extends SchemaObject implements ColumnTemplate { + + private TypeInfo type; + + /** + * Parent domain. + */ + private Domain domain; + + private Expression defaultExpression; + + private Expression onUpdateExpression; + + private ArrayList constraints; + + 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 "); + return getSQL(builder, DEFAULT_SQL_FLAGS).toString(); + } + + @Override + public String getCreateSQL() { + StringBuilder builder = getSQL(new StringBuilder("CREATE DOMAIN "), DEFAULT_SQL_FLAGS).append(" AS "); + if (domain != null) { + domain.getSQL(builder, DEFAULT_SQL_FLAGS); + } else { + type.getSQL(builder, DEFAULT_SQL_FLAGS); + } + if (defaultExpression != null) { + defaultExpression.getUnenclosedSQL(builder.append(" DEFAULT "), DEFAULT_SQL_FLAGS); + } + if (onUpdateExpression != null) { + onUpdateExpression.getUnenclosedSQL(builder.append(" ON UPDATE "), DEFAULT_SQL_FLAGS); + } + return builder.toString(); + } + + public void setDataType(TypeInfo type) { + this.type = type; + } + + public TypeInfo getDataType() { + return type; + } + + @Override + public void setDomain(Domain domain) { + this.domain = domain; + } + + @Override + public Domain getDomain() { + return domain; + } + + @Override + public void setDefaultExpression(SessionLocal session, Expression defaultExpression) { + // also to test that no column names are used + if (defaultExpression != null) { + defaultExpression = defaultExpression.optimize(session); + if (defaultExpression.isConstant()) { + defaultExpression = ValueExpression.get(defaultExpression.getValue(session)); + } + } + this.defaultExpression = defaultExpression; + } + + @Override + public Expression getDefaultExpression() { + return defaultExpression; + } + + @Override + public Expression getEffectiveDefaultExpression() { + return defaultExpression != null ? defaultExpression + : domain != null ? domain.getEffectiveDefaultExpression() : null; + } + + @Override + public String getDefaultSQL() { + return defaultExpression == null ? null + : defaultExpression.getUnenclosedSQL(new StringBuilder(), DEFAULT_SQL_FLAGS).toString(); + } + + @Override + public void setOnUpdateExpression(SessionLocal session, Expression onUpdateExpression) { + // also to test that no column names are used + if (onUpdateExpression != null) { + onUpdateExpression = onUpdateExpression.optimize(session); + if (onUpdateExpression.isConstant()) { + onUpdateExpression = ValueExpression.get(onUpdateExpression.getValue(session)); + } + } + this.onUpdateExpression = onUpdateExpression; + } + + @Override + public Expression getOnUpdateExpression() { + return onUpdateExpression; + } + + @Override + public Expression getEffectiveOnUpdateExpression() { + return onUpdateExpression != null ? onUpdateExpression + : domain != null ? domain.getEffectiveOnUpdateExpression() : null; + } + + @Override + public String getOnUpdateSQL() { + return onUpdateExpression == null ? null + : onUpdateExpression.getUnenclosedSQL(new StringBuilder(), DEFAULT_SQL_FLAGS).toString(); + } + + @Override + public void prepareExpressions(SessionLocal session) { + if (defaultExpression != null) { + defaultExpression = defaultExpression.optimize(session); + } + if (onUpdateExpression != null) { + onUpdateExpression = onUpdateExpression.optimize(session); + } + if (domain != null) { + domain.prepareExpressions(session); + } + } + + /** + * Add a constraint to the domain. + * + * @param constraint the constraint to add + */ + public void addConstraint(ConstraintDomain constraint) { + if (constraints == null) { + constraints = Utils.newSmallArrayList(); + } + if (!constraints.contains(constraint)) { + constraints.add(constraint); + } + } + + public ArrayList getConstraints() { + return constraints; + } + + /** + * Remove the given constraint from the list. + * + * @param constraint the constraint to remove + */ + public void removeConstraint(Constraint constraint) { + if (constraints != null) { + constraints.remove(constraint); + } + } + + @Override + public int getType() { + return DbObject.DOMAIN; + } + + @Override + public void removeChildrenAndResources(SessionLocal session) { + if (constraints != null && !constraints.isEmpty()) { + for (ConstraintDomain constraint : constraints.toArray(new ConstraintDomain[0])) { + database.removeSchemaObject(session, constraint); + } + constraints = null; + } + database.removeMeta(session, getId()); + } + + /** + * Check the specified value. + * + * @param session the session + * @param value the value + */ + public void checkConstraints(SessionLocal session, Value value) { + if (constraints != null) { + for (ConstraintDomain constraint : constraints) { + constraint.check(session, value); + } + } + if (domain != null) { + domain.checkConstraints(session, value); + } + } + +} diff --git a/h2/src/main/org/h2/schema/FunctionAlias.java b/h2/src/main/org/h2/schema/FunctionAlias.java new file mode 100644 index 0000000..47caf1e --- /dev/null +++ b/h2/src/main/org/h2/schema/FunctionAlias.java @@ -0,0 +1,561 @@ +/* + * 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.schema; + +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; + +import org.h2.Driver; +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.engine.DbObject; +import org.h2.engine.SessionLocal; +import org.h2.expression.Alias; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.jdbc.JdbcConnection; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.result.LocalResult; +import org.h2.result.ResultInterface; +import org.h2.table.Column; +import org.h2.util.JdbcUtils; +import org.h2.util.SourceCompiler; +import org.h2.util.StringUtils; +import org.h2.util.Utils; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueNull; +import org.h2.value.ValueToObjectConverter; +import org.h2.value.ValueToObjectConverter2; + +/** + * Represents a user-defined function, or alias. + * + * @author Thomas Mueller + * @author Gary Tong + */ +public final class FunctionAlias extends UserDefinedFunction { + + private String methodName; + private String source; + private JavaMethod[] javaMethods; + private boolean deterministic; + + private FunctionAlias(Schema schema, int id, String name) { + super(schema, id, name, Trace.FUNCTION); + } + + /** + * Create a new alias based on a method name. + * + * @param schema the schema + * @param id the id + * @param name the name + * @param javaClassMethod the class and method name + * @param force create the object even if the class or method does not exist + * @return the database object + */ + public static FunctionAlias newInstance( + Schema schema, int id, String name, String javaClassMethod, + boolean force) { + FunctionAlias alias = new FunctionAlias(schema, id, name); + int paren = javaClassMethod.indexOf('('); + int lastDot = javaClassMethod.lastIndexOf('.', paren < 0 ? + javaClassMethod.length() : paren); + if (lastDot < 0) { + throw DbException.get(ErrorCode.SYNTAX_ERROR_1, javaClassMethod); + } + alias.className = javaClassMethod.substring(0, lastDot); + alias.methodName = javaClassMethod.substring(lastDot + 1); + alias.init(force); + return alias; + } + + /** + * Create a new alias based on source code. + * + * @param schema the schema + * @param id the id + * @param name the name + * @param source the source code + * @param force create the object even if the class or method does not exist + * @return the database object + */ + public static FunctionAlias newInstanceFromSource( + Schema schema, int id, String name, String source, boolean force) { + FunctionAlias alias = new FunctionAlias(schema, id, name); + alias.source = source; + alias.init(force); + return alias; + } + + private void init(boolean force) { + try { + // at least try to compile the class, otherwise the data type is not + // initialized if it could be + load(); + } catch (DbException e) { + if (!force) { + throw e; + } + } + } + + private synchronized void load() { + if (javaMethods != null) { + return; + } + if (source != null) { + loadFromSource(); + } else { + loadClass(); + } + } + + private void loadFromSource() { + SourceCompiler compiler = database.getCompiler(); + synchronized (compiler) { + String fullClassName = Constants.USER_PACKAGE + "." + getName(); + compiler.setSource(fullClassName, source); + try { + Method m = compiler.getMethod(fullClassName); + JavaMethod method = new JavaMethod(m, 0); + javaMethods = new JavaMethod[] { + method + }; + } catch (DbException e) { + throw e; + } catch (Exception e) { + throw DbException.get(ErrorCode.SYNTAX_ERROR_1, e, source); + } + } + } + + private void loadClass() { + Class javaClass = JdbcUtils.loadUserClass(className); + Method[] methods = javaClass.getMethods(); + ArrayList list = new ArrayList<>(1); + for (int i = 0, len = methods.length; i < len; i++) { + Method m = methods[i]; + if (!Modifier.isStatic(m.getModifiers())) { + continue; + } + if (m.getName().equals(methodName) || + getMethodSignature(m).equals(methodName)) { + JavaMethod javaMethod = new JavaMethod(m, i); + for (JavaMethod old : list) { + if (old.getParameterCount() == javaMethod.getParameterCount()) { + throw DbException.get(ErrorCode. + METHODS_MUST_HAVE_DIFFERENT_PARAMETER_COUNTS_2, + old.toString(), javaMethod.toString()); + } + } + list.add(javaMethod); + } + } + if (list.isEmpty()) { + throw DbException.get( + ErrorCode.PUBLIC_STATIC_JAVA_METHOD_NOT_FOUND_1, + methodName + " (" + className + ")"); + } + javaMethods = list.toArray(new JavaMethod[0]); + // Sort elements. Methods with a variable number of arguments must be at + // the end. Reason: there could be one method without parameters and one + // with a variable number. The one without parameters needs to be used + // if no parameters are given. + Arrays.sort(javaMethods); + } + + private static String getMethodSignature(Method m) { + StringBuilder buff = new StringBuilder(m.getName()); + buff.append('('); + Class[] parameterTypes = m.getParameterTypes(); + for (int i = 0, length = parameterTypes.length; i < length; i++) { + if (i > 0) { + // do not use a space here, because spaces are removed + // in CreateFunctionAlias.setJavaClassMethod() + buff.append(','); + } + Class p = parameterTypes[i]; + if (p.isArray()) { + buff.append(p.getComponentType().getName()).append("[]"); + } else { + buff.append(p.getName()); + } + } + return buff.append(')').toString(); + } + + @Override + public String getDropSQL() { + return getSQL(new StringBuilder("DROP ALIAS IF EXISTS "), DEFAULT_SQL_FLAGS).toString(); + } + + @Override + public String getCreateSQL() { + StringBuilder builder = new StringBuilder("CREATE FORCE ALIAS "); + getSQL(builder, DEFAULT_SQL_FLAGS); + if (deterministic) { + builder.append(" DETERMINISTIC"); + } + if (source != null) { + StringUtils.quoteStringSQL(builder.append(" AS "), source); + } else { + StringUtils.quoteStringSQL(builder.append(" FOR "), className + '.' + methodName); + } + return builder.toString(); + } + + @Override + public int getType() { + return DbObject.FUNCTION_ALIAS; + } + + @Override + public synchronized void removeChildrenAndResources(SessionLocal session) { + database.removeMeta(session, getId()); + className = null; + methodName = null; + javaMethods = null; + invalidate(); + } + + /** + * Find the Java method that matches the arguments. + * + * @param args the argument list + * @return the Java method + * @throws DbException if no matching method could be found + */ + public JavaMethod findJavaMethod(Expression[] args) { + load(); + int parameterCount = args.length; + for (JavaMethod m : javaMethods) { + int count = m.getParameterCount(); + if (count == parameterCount || (m.isVarArgs() && + count <= parameterCount + 1)) { + return m; + } + } + throw DbException.get(ErrorCode.METHOD_NOT_FOUND_1, getName() + " (" + + className + ", parameter count: " + parameterCount + ")"); + } + + public String getJavaMethodName() { + return this.methodName; + } + + /** + * Get the Java methods mapped by this function. + * + * @return the Java methods. + */ + public JavaMethod[] getJavaMethods() { + load(); + return javaMethods; + } + + public void setDeterministic(boolean deterministic) { + this.deterministic = deterministic; + } + + public boolean isDeterministic() { + return deterministic; + } + + public String getSource() { + return source; + } + + /** + * There may be multiple Java methods that match a function name. + * Each method must have a different number of parameters however. + * This helper class represents one such method. + */ + public static class JavaMethod implements Comparable { + private final int id; + private final Method method; + private final TypeInfo dataType; + private boolean hasConnectionParam; + private boolean varArgs; + private Class varArgClass; + private int paramCount; + + JavaMethod(Method method, int id) { + this.method = method; + this.id = id; + Class[] paramClasses = method.getParameterTypes(); + paramCount = paramClasses.length; + if (paramCount > 0) { + Class paramClass = paramClasses[0]; + if (Connection.class.isAssignableFrom(paramClass)) { + hasConnectionParam = true; + paramCount--; + } + } + if (paramCount > 0) { + Class lastArg = paramClasses[paramClasses.length - 1]; + if (lastArg.isArray() && method.isVarArgs()) { + varArgs = true; + varArgClass = lastArg.getComponentType(); + } + } + Class returnClass = method.getReturnType(); + dataType = ResultSet.class.isAssignableFrom(returnClass) ? null + : ValueToObjectConverter2.classToType(returnClass); + } + + @Override + public String toString() { + return method.toString(); + } + + /** + * Check if this function requires a database connection. + * + * @return if the function requires a connection + */ + public boolean hasConnectionParam() { + return this.hasConnectionParam; + } + + /** + * Call the user-defined function and return the value. + * + * @param session the session + * @param args the argument list + * @param columnList true if the function should only return the column + * list + * @return the value + */ + public Value getValue(SessionLocal session, Expression[] args, boolean columnList) { + Object returnValue = execute(session, args, columnList); + if (Value.class.isAssignableFrom(method.getReturnType())) { + return (Value) returnValue; + } + return ValueToObjectConverter.objectToValue(session, returnValue, dataType.getValueType()) + .convertTo(dataType, session); + } + + /** + * Call the table user-defined function and return the value. + * + * @param session the session + * @param args the argument list + * @param columnList true if the function should only return the column + * list + * @return the value + */ + public ResultInterface getTableValue(SessionLocal session, Expression[] args, boolean columnList) { + Object o = execute(session, args, columnList); + if (o == null) { + throw DbException.get(ErrorCode.FUNCTION_MUST_RETURN_RESULT_SET_1, method.getName()); + } + if (ResultInterface.class.isAssignableFrom(method.getReturnType())) { + return (ResultInterface) o; + } + return resultSetToResult(session, (ResultSet) o, columnList ? 0 : Integer.MAX_VALUE); + } + + /** + * Create a result for the given result set. + * + * @param session the session + * @param rs 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 { + ResultSetMetaData meta = rs.getMetaData(); + int columnCount = meta.getColumnCount(); + Expression[] columns = new Expression[columnCount]; + for (int i = 0; i < columnCount; i++) { + String alias = meta.getColumnLabel(i + 1); + String name = meta.getColumnName(i + 1); + String columnTypeName = meta.getColumnTypeName(i + 1); + int columnType = DataType.convertSQLTypeToValueType(meta.getColumnType(i + 1), columnTypeName); + int precision = meta.getPrecision(i + 1); + int scale = meta.getScale(i + 1); + TypeInfo typeInfo; + if (columnType == Value.ARRAY && columnTypeName.endsWith(" ARRAY")) { + typeInfo = TypeInfo + .getTypeInfo(Value.ARRAY, -1L, 0, + TypeInfo.getTypeInfo(DataType.getTypeByName( + columnTypeName.substring(0, columnTypeName.length() - 6), + session.getMode()).type)); + } else { + typeInfo = TypeInfo.getTypeInfo(columnType, precision, scale, null); + } + Expression e = new ExpressionColumn(session.getDatabase(), new Column(name, typeInfo)); + if (!alias.equals(name)) { + e = new Alias(e, alias, false); + } + columns[i] = e; + } + LocalResult result = new LocalResult(session, columns, columnCount, columnCount); + for (int i = 0; i < maxrows && rs.next(); i++) { + Value[] list = new Value[columnCount]; + for (int j = 0; j < columnCount; j++) { + list[j] = ValueToObjectConverter.objectToValue(session, rs.getObject(j + 1), + columns[j].getType().getValueType()); + } + result.addRow(list); + } + result.done(); + return result; + } catch (SQLException e) { + throw DbException.convert(e); + } + } + + private Object execute(SessionLocal session, Expression[] args, boolean columnList) { + Class[] paramClasses = method.getParameterTypes(); + Object[] params = new Object[paramClasses.length]; + int p = 0; + JdbcConnection conn = session.createConnection(columnList); + if (hasConnectionParam && params.length > 0) { + params[p++] = conn; + } + + // allocate array for varArgs parameters + Object varArg = null; + if (varArgs) { + int len = args.length - params.length + 1 + + (hasConnectionParam ? 1 : 0); + varArg = Array.newInstance(varArgClass, len); + params[params.length - 1] = varArg; + } + + for (int a = 0, len = args.length; a < len; a++, p++) { + boolean currentIsVarArg = varArgs && + p >= paramClasses.length - 1; + Class paramClass; + if (currentIsVarArg) { + paramClass = varArgClass; + } else { + paramClass = paramClasses[p]; + } + Value v = args[a].getValue(session); + Object o; + if (Value.class.isAssignableFrom(paramClass)) { + o = v; + } else { + boolean primitive = paramClass.isPrimitive(); + if (v == ValueNull.INSTANCE) { + if (primitive) { + if (columnList) { + // If the column list is requested, the parameters + // may be null. Need to set to default value, + // otherwise the function can't be called at all. + o = DataType.getDefaultForPrimitiveType(paramClass); + } else { + // NULL for a java primitive: return NULL + return null; + } + } else { + o = null; + } + } else { + o = ValueToObjectConverter.valueToObject( + (Class) (primitive ? Utils.getNonPrimitiveClass(paramClass) : paramClass), v, conn); + } + } + if (currentIsVarArg) { + Array.set(varArg, p - params.length + 1, o); + } else { + params[p] = o; + } + } + boolean old = session.getAutoCommit(); + Value identity = session.getLastIdentity(); + boolean defaultConnection = session.getDatabase(). + getSettings().defaultConnection; + try { + session.setAutoCommit(false); + Object returnValue; + try { + if (defaultConnection) { + Driver.setDefaultConnection(session.createConnection(columnList)); + } + returnValue = method.invoke(null, params); + if (returnValue == null) { + return null; + } + } catch (InvocationTargetException e) { + StringBuilder builder = new StringBuilder(method.getName()).append('('); + for (int i = 0, length = params.length; i < length; i++) { + if (i > 0) { + builder.append(", "); + } + builder.append(params[i]); + } + builder.append(')'); + throw DbException.convertInvocation(e, builder.toString()); + } catch (Exception e) { + throw DbException.convert(e); + } + return returnValue; + } finally { + session.setLastIdentity(identity); + session.setAutoCommit(old); + if (defaultConnection) { + Driver.setDefaultConnection(null); + } + } + } + + public Class[] getColumnClasses() { + return method.getParameterTypes(); + } + + /** + * Returns data type information for regular functions or {@code null} + * for table value functions. + * + * @return data type information for regular functions or {@code null} + * for table value functions + */ + public TypeInfo getDataType() { + return dataType; + } + + public int getParameterCount() { + return paramCount; + } + + public boolean isVarArgs() { + return varArgs; + } + + @Override + public int compareTo(JavaMethod m) { + if (varArgs != m.varArgs) { + return varArgs ? 1 : -1; + } + if (paramCount != m.paramCount) { + return paramCount - m.paramCount; + } + if (hasConnectionParam != m.hasConnectionParam) { + return hasConnectionParam ? 1 : -1; + } + return id - m.id; + } + + } + +} diff --git a/h2/src/main/org/h2/schema/InformationSchema.java b/h2/src/main/org/h2/schema/InformationSchema.java new file mode 100644 index 0000000..a958166 --- /dev/null +++ b/h2/src/main/org/h2/schema/InformationSchema.java @@ -0,0 +1,77 @@ +/* + * 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.schema; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.h2.engine.Constants; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.engine.User; +import org.h2.table.InformationSchemaTable; +import org.h2.table.InformationSchemaTableLegacy; +import org.h2.table.Table; + +/** + * Information schema. + */ +public final class InformationSchema extends MetaSchema { + + private volatile HashMap newTables; + + private volatile HashMap oldTables; + + /** + * Creates new instance of information schema. + * + * @param database + * the database + * @param owner + * the owner of the schema (system user) + */ + public InformationSchema(Database database, User owner) { + super(database, Constants.INFORMATION_SCHEMA_ID, database.sysIdentifier("INFORMATION_SCHEMA"), owner); + } + + @Override + protected Map getMap(SessionLocal session) { + if (session == null) { + return Collections.emptyMap(); + } + boolean old = session.isOldInformationSchema(); + HashMap map = old ? oldTables : newTables; + if (map == null) { + map = fillMap(old); + } + return map; + } + + private synchronized HashMap fillMap(boolean old) { + HashMap map = old ? oldTables : newTables; + if (map == null) { + map = database.newStringMap(64); + if (old) { + for (int type = 0; type < InformationSchemaTableLegacy.META_TABLE_TYPE_COUNT; type++) { + InformationSchemaTableLegacy table = new InformationSchemaTableLegacy(this, + Constants.INFORMATION_SCHEMA_ID - type, type); + map.put(table.getName(), table); + } + oldTables = map; + } else { + for (int type = 0; type < InformationSchemaTable.META_TABLE_TYPE_COUNT; type++) { + InformationSchemaTable table = new InformationSchemaTable(this, + Constants.INFORMATION_SCHEMA_ID - type, type); + map.put(table.getName(), table); + } + newTables = map; + } + } + return map; + } + +} diff --git a/h2/src/main/org/h2/schema/MetaSchema.java b/h2/src/main/org/h2/schema/MetaSchema.java new file mode 100644 index 0000000..867421d --- /dev/null +++ b/h2/src/main/org/h2/schema/MetaSchema.java @@ -0,0 +1,97 @@ +/* + * 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.schema; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.engine.User; +import org.h2.table.Table; + +/** + * Meta data schema. + */ +public abstract class MetaSchema extends Schema { + + /** + * Creates a new instance of meta data schema. + * + * @param database + * the database + * @param id + * the object id + * @param schemaName + * the schema name + * @param owner + * the owner of the schema + */ + public MetaSchema(Database database, int id, String schemaName, User owner) { + super(database, id, schemaName, owner, true); + } + + @Override + public Table findTableOrView(SessionLocal session, String name) { + Map map = getMap(session); + Table table = map.get(name); + if (table != null) { + return table; + } + return super.findTableOrView(session, name); + } + + @Override + public Collection getAllTablesAndViews(SessionLocal session) { + Collection
userTables = super.getAllTablesAndViews(session); + if (session == null) { + return userTables; + } + Collection
systemTables = getMap(session).values(); + if (userTables.isEmpty()) { + return systemTables; + } + ArrayList
list = new ArrayList<>(systemTables.size() + userTables.size()); + list.addAll(systemTables); + list.addAll(userTables); + return list; + } + + @Override + public Table getTableOrView(SessionLocal session, String name) { + Map map = getMap(session); + Table table = map.get(name); + if (table != null) { + return table; + } + return super.getTableOrView(session, name); + } + + @Override + public Table getTableOrViewByName(SessionLocal session, String name) { + Map map = getMap(session); + Table table = map.get(name); + if (table != null) { + return table; + } + return super.getTableOrViewByName(session, name); + } + + /** + * Returns map of tables in this schema. + * + * @param session the session + * @return map of tables in this schema + */ + protected abstract Map getMap(SessionLocal session); + + @Override + public boolean isEmpty() { + return false; + } + +} diff --git a/h2/src/main/org/h2/schema/Schema.java b/h2/src/main/org/h2/schema/Schema.java new file mode 100644 index 0000000..9002a5c --- /dev/null +++ b/h2/src/main/org/h2/schema/Schema.java @@ -0,0 +1,831 @@ +/* + * 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.schema; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.h2.api.ErrorCode; +import org.h2.command.ddl.CreateSynonymData; +import org.h2.command.ddl.CreateTableData; +import org.h2.constraint.Constraint; +import org.h2.engine.Database; +import org.h2.engine.DbObject; +import org.h2.engine.DbSettings; +import org.h2.engine.Right; +import org.h2.engine.RightOwner; +import org.h2.engine.SessionLocal; +import org.h2.engine.SysProperties; +import org.h2.index.Index; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.table.MetaTable; +import org.h2.table.Table; +import org.h2.table.TableLink; +import org.h2.table.TableSynonym; +import org.h2.util.Utils; + +/** + * A schema as created by the SQL statement + * CREATE SCHEMA + */ +public class Schema extends DbObject { + + private RightOwner owner; + private final boolean system; + private ArrayList tableEngineParams; + + private final ConcurrentHashMap tablesAndViews; + private final ConcurrentHashMap domains; + private final ConcurrentHashMap synonyms; + private final ConcurrentHashMap indexes; + private final ConcurrentHashMap sequences; + private final ConcurrentHashMap triggers; + private final ConcurrentHashMap constraints; + private final ConcurrentHashMap constants; + private final ConcurrentHashMap functionsAndAggregates; + + /** + * The set of returned unique names that are not yet stored. It is used to + * avoid returning the same unique name twice when multiple threads + * concurrently create objects. + */ + private final HashSet temporaryUniqueNames = new HashSet<>(); + + /** + * Create a new schema object. + * + * @param database the database + * @param id the object id + * @param schemaName the schema name + * @param owner the owner of the schema + * @param system if this is a system schema (such a schema can not be + * dropped) + */ + public Schema(Database database, int id, String schemaName, RightOwner owner, boolean system) { + super(database, id, schemaName, Trace.SCHEMA); + tablesAndViews = database.newConcurrentStringMap(); + domains = database.newConcurrentStringMap(); + synonyms = database.newConcurrentStringMap(); + indexes = database.newConcurrentStringMap(); + sequences = database.newConcurrentStringMap(); + triggers = database.newConcurrentStringMap(); + constraints = database.newConcurrentStringMap(); + constants = database.newConcurrentStringMap(); + functionsAndAggregates = database.newConcurrentStringMap(); + this.owner = owner; + this.system = system; + } + + /** + * Check if this schema can be dropped. System schemas can not be dropped. + * + * @return true if it can be dropped + */ + public boolean canDrop() { + return !system; + } + + @Override + public String getCreateSQLForCopy(Table table, String quotedName) { + throw DbException.getInternalError(toString()); + } + + @Override + public String getCreateSQL() { + if (system) { + return null; + } + StringBuilder builder = new StringBuilder("CREATE SCHEMA IF NOT EXISTS "); + getSQL(builder, DEFAULT_SQL_FLAGS).append(" AUTHORIZATION "); + owner.getSQL(builder, DEFAULT_SQL_FLAGS); + return builder.toString(); + } + + @Override + public int getType() { + return DbObject.SCHEMA; + } + + /** + * Return whether is this schema is empty (does not contain any objects). + * + * @return {@code true} if this schema is empty, {@code false} otherwise + */ + public boolean isEmpty() { + return tablesAndViews.isEmpty() && domains.isEmpty() && synonyms.isEmpty() && indexes.isEmpty() + && sequences.isEmpty() && triggers.isEmpty() && constraints.isEmpty() && constants.isEmpty() + && functionsAndAggregates.isEmpty(); + } + + @Override + public ArrayList getChildren() { + ArrayList children = Utils.newSmallArrayList(); + ArrayList rights = database.getAllRights(); + for (Right right : rights) { + if (right.getGrantedObject() == this) { + children.add(right); + } + } + return children; + } + + @Override + public void removeChildrenAndResources(SessionLocal session) { + removeChildrenFromMap(session, triggers); + removeChildrenFromMap(session, constraints); + // There can be dependencies between tables e.g. using computed columns, + // so we might need to loop over them multiple times. + boolean modified = true; + while (!tablesAndViews.isEmpty()) { + boolean newModified = false; + for (Table obj : tablesAndViews.values()) { + if (obj.getName() != null) { + // Database.removeSchemaObject() removes the object from + // the map too, but it is safe for ConcurrentHashMap. + Table dependentTable = database.getDependentTable(obj, obj); + if (dependentTable == null) { + database.removeSchemaObject(session, obj); + newModified = true; + } else if (dependentTable.getSchema() != this) { + throw DbException.get(ErrorCode.CANNOT_DROP_2, // + obj.getTraceSQL(), dependentTable.getTraceSQL()); + } else if (!modified) { + dependentTable.removeColumnExpressionsDependencies(session); + dependentTable.setModified(); + database.updateMeta(session, dependentTable); + } + } + } + modified = newModified; + } + removeChildrenFromMap(session, domains); + removeChildrenFromMap(session, indexes); + removeChildrenFromMap(session, sequences); + removeChildrenFromMap(session, constants); + removeChildrenFromMap(session, functionsAndAggregates); + for (Right right : database.getAllRights()) { + if (right.getGrantedObject() == this) { + database.removeDatabaseObject(session, right); + } + } + database.removeMeta(session, getId()); + owner = null; + invalidate(); + } + + private void removeChildrenFromMap(SessionLocal session, ConcurrentHashMap map) { + if (!map.isEmpty()) { + for (SchemaObject obj : map.values()) { + /* + * Referential constraints are dropped when unique or PK + * constraint is dropped, but iterator may return already + * removed objects in some cases. + */ + if (obj.isValid()) { + // Database.removeSchemaObject() removes the object from + // the map too, but it is safe for ConcurrentHashMap. + database.removeSchemaObject(session, obj); + } + } + } + } + + /** + * Get the owner of this schema. + * + * @return the owner + */ + public RightOwner getOwner() { + return owner; + } + + /** + * Get table engine params of this schema. + * + * @return default table engine params + */ + public ArrayList getTableEngineParams() { + return tableEngineParams; + } + + /** + * Set table engine params of this schema. + * @param tableEngineParams default table engine params + */ + public void setTableEngineParams(ArrayList tableEngineParams) { + this.tableEngineParams = tableEngineParams; + } + + @SuppressWarnings("unchecked") + private Map getMap(int type) { + Map result; + switch (type) { + case DbObject.TABLE_OR_VIEW: + result = tablesAndViews; + break; + case DbObject.DOMAIN: + result = domains; + break; + case DbObject.SYNONYM: + result = synonyms; + break; + case DbObject.SEQUENCE: + result = sequences; + break; + case DbObject.INDEX: + result = indexes; + break; + case DbObject.TRIGGER: + result = triggers; + break; + case DbObject.CONSTRAINT: + result = constraints; + break; + case DbObject.CONSTANT: + result = constants; + break; + case DbObject.FUNCTION_ALIAS: + case DbObject.AGGREGATE: + result = functionsAndAggregates; + break; + default: + throw DbException.getInternalError("type=" + type); + } + return (Map) result; + } + + /** + * Add an object to this schema. + * This method must not be called within CreateSchemaObject; + * use Database.addSchemaObject() instead + * + * @param obj the object to add + */ + public void add(SchemaObject obj) { + if (obj.getSchema() != this) { + throw DbException.getInternalError("wrong schema"); + } + String name = obj.getName(); + Map map = getMap(obj.getType()); + if (map.putIfAbsent(name, obj) != null) { + throw DbException.getInternalError("object already exists: " + name); + } + freeUniqueName(name); + } + + /** + * Rename an object. + * + * @param obj the object to rename + * @param newName the new name + */ + public void rename(SchemaObject obj, String newName) { + int type = obj.getType(); + Map map = getMap(type); + if (SysProperties.CHECK) { + if (!map.containsKey(obj.getName()) && !(obj instanceof MetaTable)) { + throw DbException.getInternalError("not found: " + obj.getName()); + } + if (obj.getName().equals(newName) || map.containsKey(newName)) { + throw DbException.getInternalError("object already exists: " + newName); + } + } + obj.checkRename(); + map.remove(obj.getName()); + freeUniqueName(obj.getName()); + obj.rename(newName); + map.put(newName, obj); + freeUniqueName(newName); + } + + /** + * Try to find a table or view with this name. This method returns null if + * no object with this name exists. Local temporary tables are also + * returned. Synonyms are not returned or resolved. + * + * @param session the session + * @param name the object name + * @return the object or null + */ + public Table findTableOrView(SessionLocal session, String name) { + Table table = tablesAndViews.get(name); + if (table == null && session != null) { + table = session.findLocalTempTable(name); + } + return table; + } + + /** + * Try to find a table or view with this name. This method returns null if + * no object with this name exists. Local temporary tables are also + * returned. If a synonym with this name exists, the backing table of the + * synonym is returned + * + * @param session the session + * @param name the object name + * @return the object or null + */ + public Table resolveTableOrView(SessionLocal session, String name) { + Table table = findTableOrView(session, name); + if (table == null) { + TableSynonym synonym = synonyms.get(name); + if (synonym != null) { + return synonym.getSynonymFor(); + } + } + return table; + } + + /** + * Try to find a synonym with this name. This method returns null if + * no object with this name exists. + * + * @param name the object name + * @return the object or null + */ + public TableSynonym getSynonym(String name) { + return synonyms.get(name); + } + + /** + * Get the domain if it exists, or null if not. + * + * @param name the name of the domain + * @return the domain or null + */ + public Domain findDomain(String name) { + return domains.get(name); + } + + /** + * Try to find an index with this name. This method returns null if + * no object with this name exists. + * + * @param session the session + * @param name the object name + * @return the object or null + */ + public Index findIndex(SessionLocal session, String name) { + Index index = indexes.get(name); + if (index == null) { + index = session.findLocalTempTableIndex(name); + } + return index; + } + + /** + * Try to find a trigger with this name. This method returns null if + * no object with this name exists. + * + * @param name the object name + * @return the object or null + */ + public TriggerObject findTrigger(String name) { + return triggers.get(name); + } + + /** + * Try to find a sequence with this name. This method returns null if + * no object with this name exists. + * + * @param sequenceName the object name + * @return the object or null + */ + public Sequence findSequence(String sequenceName) { + return sequences.get(sequenceName); + } + + /** + * Try to find a constraint with this name. This method returns null if no + * object with this name exists. + * + * @param session the session + * @param name the object name + * @return the object or null + */ + public Constraint findConstraint(SessionLocal session, String name) { + Constraint constraint = constraints.get(name); + if (constraint == null) { + constraint = session.findLocalTempTableConstraint(name); + } + return constraint; + } + + /** + * Try to find a user defined constant with this name. This method returns + * null if no object with this name exists. + * + * @param constantName the object name + * @return the object or null + */ + public Constant findConstant(String constantName) { + return constants.get(constantName); + } + + /** + * Try to find a user defined function with this name. This method returns + * null if no object with this name exists. + * + * @param functionAlias the object name + * @return the object or null + */ + public FunctionAlias findFunction(String functionAlias) { + UserDefinedFunction userDefinedFunction = findFunctionOrAggregate(functionAlias); + return userDefinedFunction instanceof FunctionAlias ? (FunctionAlias) userDefinedFunction : null; + } + + /** + * Get the user defined aggregate function if it exists. This method returns + * null if no object with this name exists. + * + * @param name the name of the user defined aggregate function + * @return the aggregate function or null + */ + public UserAggregate findAggregate(String name) { + UserDefinedFunction userDefinedFunction = findFunctionOrAggregate(name); + return userDefinedFunction instanceof UserAggregate ? (UserAggregate) userDefinedFunction : null; + } + + /** + * Try to find a user defined function or aggregate function with the + * specified name. This method returns null if no object with this name + * exists. + * + * @param name + * the object name + * @return the object or null + */ + public UserDefinedFunction findFunctionOrAggregate(String name) { + return functionsAndAggregates.get(name); + } + + /** + * Reserve a unique object name. + * + * @param name the object name + */ + public void reserveUniqueName(String name) { + if (name != null) { + synchronized (temporaryUniqueNames) { + temporaryUniqueNames.add(name); + } + } + } + + /** + * Release a unique object name. + * + * @param name the object name + */ + public void freeUniqueName(String name) { + if (name != null) { + synchronized (temporaryUniqueNames) { + temporaryUniqueNames.remove(name); + } + } + } + + private String getUniqueName(DbObject obj, Map map, String prefix) { + StringBuilder nameBuilder = new StringBuilder(prefix); + String hash = Integer.toHexString(obj.getName().hashCode()); + synchronized (temporaryUniqueNames) { + for (int i = 0, len = hash.length(); i < len; i++) { + char c = hash.charAt(i); + String name = nameBuilder.append(c >= 'a' ? (char) (c - 0x20) : c).toString(); + if (!map.containsKey(name) && temporaryUniqueNames.add(name)) { + return name; + } + } + int nameLength = nameBuilder.append('_').length(); + for (int i = 0;; i++) { + String name = nameBuilder.append(i).toString(); + if (!map.containsKey(name) && temporaryUniqueNames.add(name)) { + return name; + } + nameBuilder.setLength(nameLength); + } + } + } + + /** + * Create a unique constraint name. + * + * @param session the session + * @param table the constraint table + * @return the unique name + */ + public String getUniqueConstraintName(SessionLocal session, Table table) { + Map tableConstraints; + if (table.isTemporary() && !table.isGlobalTemporary()) { + tableConstraints = session.getLocalTempTableConstraints(); + } else { + tableConstraints = constraints; + } + return getUniqueName(table, tableConstraints, "CONSTRAINT_"); + } + + /** + * Create a unique constraint name. + * + * @param session the session + * @param domain the constraint domain + * @return the unique name + */ + public String getUniqueDomainConstraintName(SessionLocal session, Domain domain) { + return getUniqueName(domain, constraints, "CONSTRAINT_"); + } + + /** + * Create a unique index name. + * + * @param session the session + * @param table the indexed table + * @param prefix the index name prefix + * @return the unique name + */ + public String getUniqueIndexName(SessionLocal session, Table table, String prefix) { + Map tableIndexes; + if (table.isTemporary() && !table.isGlobalTemporary()) { + tableIndexes = session.getLocalTempTableIndexes(); + } else { + tableIndexes = indexes; + } + return getUniqueName(table, tableIndexes, prefix); + } + + /** + * Get the table or view with the given name. + * Local temporary tables are also returned. + * + * @param session the session + * @param name the table or view name + * @return the table or view + * @throws DbException if no such object exists + */ + public Table getTableOrView(SessionLocal session, String name) { + Table table = tablesAndViews.get(name); + if (table == null) { + if (session != null) { + table = session.findLocalTempTable(name); + } + if (table == null) { + throw DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, name); + } + } + return table; + } + + /** + * Get the domain with the given name. + * + * @param name the domain name + * @return the domain + * @throws DbException if no such object exists + */ + public Domain getDomain(String name) { + Domain domain = domains.get(name); + if (domain == null) { + throw DbException.get(ErrorCode.DOMAIN_NOT_FOUND_1, name); + } + return domain; + } + + /** + * Get the index with the given name. + * + * @param name the index name + * @return the index + * @throws DbException if no such object exists + */ + public Index getIndex(String name) { + Index index = indexes.get(name); + if (index == null) { + throw DbException.get(ErrorCode.INDEX_NOT_FOUND_1, name); + } + return index; + } + + /** + * Get the constraint with the given name. + * + * @param name the constraint name + * @return the constraint + * @throws DbException if no such object exists + */ + public Constraint getConstraint(String name) { + Constraint constraint = constraints.get(name); + if (constraint == null) { + throw DbException.get(ErrorCode.CONSTRAINT_NOT_FOUND_1, name); + } + return constraint; + } + + /** + * Get the user defined constant with the given name. + * + * @param constantName the constant name + * @return the constant + * @throws DbException if no such object exists + */ + public Constant getConstant(String constantName) { + Constant constant = constants.get(constantName); + if (constant == null) { + throw DbException.get(ErrorCode.CONSTANT_NOT_FOUND_1, constantName); + } + return constant; + } + + /** + * Get the sequence with the given name. + * + * @param sequenceName the sequence name + * @return the sequence + * @throws DbException if no such object exists + */ + public Sequence getSequence(String sequenceName) { + Sequence sequence = sequences.get(sequenceName); + if (sequence == null) { + throw DbException.get(ErrorCode.SEQUENCE_NOT_FOUND_1, sequenceName); + } + return sequence; + } + + /** + * Get all objects. + * + * @param addTo + * list to add objects to, or {@code null} to allocate a new + * list + * @return the specified list with added objects, or a new (possibly empty) list + * with all objects + */ + public ArrayList getAll(ArrayList addTo) { + if (addTo == null) { + addTo = Utils.newSmallArrayList(); + } + addTo.addAll(tablesAndViews.values()); + addTo.addAll(domains.values()); + addTo.addAll(synonyms.values()); + addTo.addAll(sequences.values()); + addTo.addAll(indexes.values()); + addTo.addAll(triggers.values()); + addTo.addAll(constraints.values()); + addTo.addAll(constants.values()); + addTo.addAll(functionsAndAggregates.values()); + return addTo; + } + + /** + * Get all objects of the given type. + * + * @param type + * the object type + * @param addTo + * list to add objects to + */ + public void getAll(int type, ArrayList addTo) { + addTo.addAll(getMap(type).values()); + } + + public Collection getAllDomains() { + return domains.values(); + } + + public Collection getAllConstraints() { + return constraints.values(); + } + + public Collection getAllConstants() { + return constants.values(); + } + + public Collection getAllSequences() { + return sequences.values(); + } + + public Collection getAllTriggers() { + return triggers.values(); + } + + /** + * Get all tables and views. + * + * @param session the session, {@code null} to exclude meta tables + * @return a (possible empty) list of all objects + */ + public Collection
getAllTablesAndViews(SessionLocal session) { + return tablesAndViews.values(); + } + + public Collection getAllIndexes() { + return indexes.values(); + } + + public Collection getAllSynonyms() { + return synonyms.values(); + } + + public Collection getAllFunctionsAndAggregates() { + return functionsAndAggregates.values(); + } + + /** + * Get the table with the given name, if any. + * + * @param session the session + * @param name the table name + * @return the table or null if not found + */ + public Table getTableOrViewByName(SessionLocal session, String name) { + return tablesAndViews.get(name); + } + + /** + * Remove an object from this schema. + * + * @param obj the object to remove + */ + public void remove(SchemaObject obj) { + String objName = obj.getName(); + Map map = getMap(obj.getType()); + if (map.remove(objName) == null) { + throw DbException.getInternalError("not found: " + objName); + } + freeUniqueName(objName); + } + + /** + * Add a table to the schema. + * + * @param data the create table information + * @return the created {@link Table} object + */ + public Table createTable(CreateTableData data) { + synchronized (database) { + if (!data.temporary || data.globalTemporary) { + database.lockMeta(data.session); + } + data.schema = this; + String tableEngine = data.tableEngine; + if (tableEngine == null) { + DbSettings s = database.getSettings(); + tableEngine = s.defaultTableEngine; + if (tableEngine == null) { + return database.getStore().createTable(data); + } + data.tableEngine = tableEngine; + } + if (data.tableEngineParams == null) { + data.tableEngineParams = this.tableEngineParams; + } + return database.getTableEngine(tableEngine).createTable(data); + } + } + + /** + * Add a table synonym to the schema. + * + * @param data the create synonym information + * @return the created {@link TableSynonym} object + */ + public TableSynonym createSynonym(CreateSynonymData data) { + synchronized (database) { + database.lockMeta(data.session); + data.schema = this; + return new TableSynonym(data); + } + } + + /** + * Add a linked table to the schema. + * + * @param id the object id + * @param tableName the table name of the alias + * @param driver the driver class name + * @param url the database URL + * @param user the user name + * @param password the password + * @param originalSchema the schema name of the target table + * @param originalTable the table name of the target table + * @param emitUpdates if updates should be emitted instead of delete/insert + * @param force create the object even if the database can not be accessed + * @return the {@link TableLink} object + */ + public TableLink createTableLink(int id, String tableName, String driver, + String url, String user, String password, String originalSchema, + String originalTable, boolean emitUpdates, boolean force) { + synchronized (database) { + return new TableLink(this, id, tableName, + driver, url, user, password, + originalSchema, originalTable, emitUpdates, force); + } + } + +} diff --git a/h2/src/main/org/h2/schema/SchemaObject.java b/h2/src/main/org/h2/schema/SchemaObject.java new file mode 100644 index 0000000..f777d03 --- /dev/null +++ b/h2/src/main/org/h2/schema/SchemaObject.java @@ -0,0 +1,60 @@ +/* + * 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.schema; + +import org.h2.engine.DbObject; + +/** + * Any database object that is stored in a schema. + */ +public abstract class SchemaObject extends DbObject { + + private final Schema schema; + + /** + * Initialize some attributes of this object. + * + * @param newSchema the schema + * @param id the object id + * @param name the name + * @param traceModuleId the trace module id + */ + protected SchemaObject(Schema newSchema, int id, String name, int traceModuleId) { + super(newSchema.getDatabase(), id, name, traceModuleId); + this.schema = newSchema; + } + + /** + * Get the schema in which this object is defined + * + * @return the schema + */ + public final Schema getSchema() { + return schema; + } + + @Override + public String getSQL(int sqlFlags) { + return getSQL(new StringBuilder(), sqlFlags).toString(); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + schema.getSQL(builder, sqlFlags).append('.'); + return super.getSQL(builder, sqlFlags); + } + + /** + * Check whether this is a hidden object that doesn't appear in the meta + * data and in the script, and is not dropped on DROP ALL OBJECTS. + * + * @return true if it is hidden + */ + public boolean isHidden() { + return false; + } + +} diff --git a/h2/src/main/org/h2/schema/Sequence.java b/h2/src/main/org/h2/schema/Sequence.java new file mode 100644 index 0000000..f21b918 --- /dev/null +++ b/h2/src/main/org/h2/schema/Sequence.java @@ -0,0 +1,585 @@ +/* + * 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.schema; + +import org.h2.api.ErrorCode; +import org.h2.command.ddl.SequenceOptions; +import org.h2.engine.DbObject; +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; + +/** + * A sequence is created using the statement + * CREATE SEQUENCE + */ +public final class Sequence extends SchemaObject { + + /** + * CYCLE clause and sequence state. + */ + public enum Cycle { + + /** + * Sequence is cycled. + */ + CYCLE, + + /** + * Sequence is not cycled and isn't exhausted yet. + */ + NO_CYCLE, + + /** + * Sequence is not cycled and was already exhausted. + */ + EXHAUSTED; + + /** + * Return whether sequence is cycled. + * + * @return {@code true} if sequence is cycled, {@code false} if sequence + * is not cycled + */ + public boolean isCycle() { + return this == CYCLE; + } + + } + + /** + * The default cache size for sequences. + */ + public static final int DEFAULT_CACHE_SIZE = 32; + + private long baseValue; + private long margin; + + private TypeInfo dataType; + + private long increment; + private long cacheSize; + private long startValue; + private long minValue; + private long maxValue; + private Cycle cycle; + private boolean belongsToTable; + private boolean writeWithMargin; + + /** + * Creates a new sequence. + * + * @param session + * the session + * @param schema + * the schema + * @param id + * the object id + * @param name + * the sequence name + * @param options + * the sequence options + * @param belongsToTable + * whether this sequence belongs to a table (for generated + * columns) + */ + public Sequence(SessionLocal session, Schema schema, int id, String name, SequenceOptions options, + boolean belongsToTable) { + super(schema, id, name, Trace.SEQUENCE); + dataType = options.getDataType(); + if (dataType == null) { + options.setDataType(dataType = session.getMode().decimalSequences ? TypeInfo.TYPE_NUMERIC_BIGINT + : TypeInfo.TYPE_BIGINT); + } + long bounds[] = options.getBounds(); + Long t = options.getIncrement(session); + long increment = t != null ? t : 1; + Long start = options.getStartValue(session); + Long min = options.getMinValue(null, session); + Long max = options.getMaxValue(null, session); + long minValue = min != null ? min : getDefaultMinValue(start, increment, bounds); + long maxValue = max != null ? max : getDefaultMaxValue(start, increment, bounds); + long startValue = start != null ? start : increment >= 0 ? minValue : maxValue; + Long restart = options.getRestartValue(session, startValue); + long baseValue = restart != null ? restart : startValue; + t = options.getCacheSize(session); + long cacheSize; + boolean mayAdjustCacheSize; + if (t != null) { + cacheSize = t; + mayAdjustCacheSize = false; + } else { + cacheSize = DEFAULT_CACHE_SIZE; + mayAdjustCacheSize = true; + } + cacheSize = checkOptions(baseValue, startValue, minValue, maxValue, increment, cacheSize, mayAdjustCacheSize); + Cycle cycle = options.getCycle(); + if (cycle == null) { + cycle = Cycle.NO_CYCLE; + } else if (cycle == Cycle.EXHAUSTED) { + baseValue = startValue; + } + this.margin = this.baseValue = baseValue; + this.increment = increment; + this.cacheSize = cacheSize; + this.startValue = startValue; + this.minValue = minValue; + this.maxValue = maxValue; + this.cycle = cycle; + this.belongsToTable = belongsToTable; + } + + /** + * Allows the base value, start value, min value, max value, increment and + * cache size to be updated atomically, including atomic validation. Useful + * because setting these attributes one after the other could otherwise + * result in an invalid sequence state (e.g. min value > max value, start + * value < min value, etc). + * @param baseValue + * the base value ({@code null} if restart is not requested) + * @param startValue + * the new start value ({@code null} if no change) + * @param minValue + * the new min value ({@code null} if no change) + * @param maxValue + * the new max value ({@code null} if no change) + * @param increment + * the new increment ({@code null} if no change) + * @param cycle + * the new cycle value, or {@code null} if no change + * @param cacheSize + * the new cache size ({@code null} if no change) + */ + public synchronized void modify(Long baseValue, Long startValue, Long minValue, Long maxValue, Long increment, + Cycle cycle, Long cacheSize) { + long baseValueAsLong = baseValue != null ? baseValue : this.baseValue; + long startValueAsLong = startValue != null ? startValue : this.startValue; + long minValueAsLong = minValue != null ? minValue : this.minValue; + long maxValueAsLong = maxValue != null ? maxValue : this.maxValue; + long incrementAsLong = increment != null ? increment : this.increment; + long cacheSizeAsLong; + boolean mayAdjustCacheSize; + if (cacheSize != null) { + cacheSizeAsLong = cacheSize; + mayAdjustCacheSize = false; + } else { + cacheSizeAsLong = this.cacheSize; + mayAdjustCacheSize = true; + } + cacheSizeAsLong = checkOptions(baseValueAsLong, startValueAsLong, minValueAsLong, maxValueAsLong, + incrementAsLong, cacheSizeAsLong, mayAdjustCacheSize); + if (cycle == null) { + cycle = this.cycle; + if (cycle == Cycle.EXHAUSTED && baseValue != null) { + cycle = Cycle.NO_CYCLE; + } + } else if (cycle == Cycle.EXHAUSTED) { + baseValueAsLong = startValueAsLong; + } + this.margin = this.baseValue = baseValueAsLong; + this.startValue = startValueAsLong; + this.minValue = minValueAsLong; + this.maxValue = maxValueAsLong; + this.increment = incrementAsLong; + this.cacheSize = cacheSizeAsLong; + this.cycle = cycle; + } + + /** + * Validates the specified prospective base value, start value, min value, + * max value, increment, and cache size relative to each other, since each + * of their respective validities are contingent on the values of the other + * parameters. + * + * @param baseValue + * the prospective base value + * @param startValue + * the prospective start value + * @param minValue + * the prospective min value + * @param maxValue + * the prospective max value + * @param increment + * the prospective increment + * @param cacheSize + * the prospective cache size + * @param mayAdjustCacheSize + * whether cache size may be adjusted, cache size 0 is adjusted + * unconditionally to 1 + * @return the prospective or adjusted cache size + */ + private long checkOptions(long baseValue, long startValue, long minValue, long maxValue, long increment, + long cacheSize, boolean mayAdjustCacheSize) { + if (minValue <= baseValue && baseValue <= maxValue // + && minValue <= startValue && startValue <= maxValue // + && minValue < maxValue && increment != 0L) { + long range = maxValue - minValue; + if (Long.compareUnsigned(Math.abs(increment), range) <= 0 && cacheSize >= 0L) { + if (cacheSize <= 1L) { + return 1L; + } + long maxCacheSize = getMaxCacheSize(range, increment); + if (cacheSize <= maxCacheSize) { + return cacheSize; + } + if (mayAdjustCacheSize) { + return maxCacheSize; + } + } + } + throw DbException.get(ErrorCode.SEQUENCE_ATTRIBUTES_INVALID_7, getName(), Long.toString(baseValue), + Long.toString(startValue), Long.toString(minValue), Long.toString(maxValue), Long.toString(increment), + Long.toString(cacheSize)); + } + + private static long getMaxCacheSize(long range, long increment) { + if (increment > 0L) { + if (range < 0) { + range = Long.MAX_VALUE; + } else { + range += increment; + if (range < 0) { + range = Long.MAX_VALUE; + } + } + } else { + range = -range; + if (range > 0) { + range = Long.MIN_VALUE; + } else { + range += increment; + if (range >= 0) { + range = Long.MIN_VALUE; + } + } + } + return range / increment; + } + + /** + * Calculates default min value. + * + * @param startValue the start value of the sequence. + * @param increment the increment of the sequence value. + * @param bounds min and max bounds of data type of the sequence + * @return min value. + */ + public static long getDefaultMinValue(Long startValue, long increment, long[] bounds) { + long v = increment >= 0 ? 1 : bounds[0]; + if (startValue != null && increment >= 0 && startValue < v) { + v = startValue; + } + return v; + } + + /** + * Calculates default max value. + * + * @param startValue the start value of the sequence. + * @param increment the increment of the sequence value. + * @param bounds min and max bounds of data type of the sequence + * @return min value. + */ + public static long getDefaultMaxValue(Long startValue, long increment, long[] bounds) { + long v = increment >= 0 ? bounds[1] : -1; + if (startValue != null && increment < 0 && startValue > v) { + v = startValue; + } + return v; + } + + public boolean getBelongsToTable() { + return belongsToTable; + } + + public TypeInfo getDataType() { + return dataType; + } + + public int getEffectivePrecision() { + TypeInfo dataType = this.dataType; + switch (dataType.getValueType()) { + case Value.NUMERIC: { + int p = (int) dataType.getPrecision(); + int s = dataType.getScale(); + if (p - s > ValueBigint.DECIMAL_PRECISION) { + return ValueBigint.DECIMAL_PRECISION + s; + } + return p; + } + case Value.DECFLOAT: + return Math.min((int) dataType.getPrecision(), ValueBigint.DECIMAL_PRECISION); + default: + return (int) dataType.getPrecision(); + } + } + + public long getIncrement() { + return increment; + } + + public long getStartValue() { + return startValue; + } + + public long getMinValue() { + return minValue; + } + + public long getMaxValue() { + return maxValue; + } + + public Cycle getCycle() { + return cycle; + } + + @Override + public String getDropSQL() { + if (getBelongsToTable()) { + return null; + } + StringBuilder builder = new StringBuilder("DROP SEQUENCE IF EXISTS "); + 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); + if (dataType.getValueType() != Value.BIGINT) { + dataType.getSQL(builder.append(" AS "), DEFAULT_SQL_FLAGS); + } + builder.append(' '); + synchronized (this) { + getSequenceOptionsSQL(builder, writeWithMargin ? margin : baseValue); + } + if (belongsToTable) { + builder.append(" BELONGS_TO_TABLE"); + } + return builder.toString(); + } + + /** + * Append the options part of the SQL statement to create the sequence. + * + * @param builder the builder + * @return the builder + */ + public synchronized StringBuilder getSequenceOptionsSQL(StringBuilder builder) { + return getSequenceOptionsSQL(builder, baseValue); + } + + private StringBuilder getSequenceOptionsSQL(StringBuilder builder, long value) { + builder.append("START WITH ").append(startValue); + if (value != startValue && cycle != Cycle.EXHAUSTED) { + builder.append(" RESTART WITH ").append(value); + } + if (increment != 1) { + builder.append(" INCREMENT BY ").append(increment); + } + long[] bounds = SequenceOptions.getBounds(dataType); + if (minValue != getDefaultMinValue(value, increment, bounds)) { + builder.append(" MINVALUE ").append(minValue); + } + if (maxValue != getDefaultMaxValue(value, increment, bounds)) { + builder.append(" MAXVALUE ").append(maxValue); + } + if (cycle == Cycle.CYCLE) { + builder.append(" CYCLE"); + } else if (cycle == Cycle.EXHAUSTED) { + builder.append(" EXHAUSTED"); + } + if (cacheSize != DEFAULT_CACHE_SIZE) { + if (cacheSize == 1) { + builder.append(" NO CACHE"); + } else if (cacheSize > DEFAULT_CACHE_SIZE // + || cacheSize != getMaxCacheSize(maxValue - minValue, increment)) { + builder.append(" CACHE ").append(cacheSize); + } + } + return builder; + } + + /** + * Get the next value for this sequence. Should not be called directly, use + * {@link SessionLocal#getNextValueFor(Sequence, org.h2.command.Prepared)} instead. + * + * @param session the session + * @return the next value + */ + public Value getNext(SessionLocal session) { + long result; + boolean needsFlush; + synchronized (this) { + if (cycle == Cycle.EXHAUSTED) { + throw DbException.get(ErrorCode.SEQUENCE_EXHAUSTED, getName()); + } + result = baseValue; + long newBase = result + increment; + needsFlush = increment > 0 ? increment(result, newBase) : decrement(result, newBase); + } + if (needsFlush) { + flush(session); + } + return ValueBigint.get(result).castTo(dataType, session); + } + + private boolean increment(long oldBase, long newBase) { + boolean needsFlush = false; + /* + * If old base is not negative and new base is negative there is an + * overflow. + */ + if (newBase > maxValue || (~oldBase & newBase) < 0) { + newBase = minValue; + needsFlush = true; + if (cycle == Cycle.CYCLE) { + margin = newBase + increment * (cacheSize - 1); + } else { + margin = newBase; + cycle = Cycle.EXHAUSTED; + } + } else if (newBase > margin) { + long newMargin = newBase + increment * (cacheSize - 1); + if (newMargin > maxValue || (~newBase & newMargin) < 0) { + /* + * Don't cache values near the end of the sequence for + * simplicity. + */ + newMargin = newBase; + } + margin = newMargin; + needsFlush = true; + } + baseValue = newBase; + return needsFlush; + } + + private boolean decrement(long oldBase, long newBase) { + boolean needsFlush = false; + /* + * If old base is negative and new base is not negative there is an + * overflow. + */ + if (newBase < minValue || (oldBase & ~newBase) < 0) { + newBase = maxValue; + needsFlush = true; + if (cycle == Cycle.CYCLE) { + margin = newBase + increment * (cacheSize - 1); + } else { + margin = newBase; + cycle = Cycle.EXHAUSTED; + } + } else if (newBase < margin) { + long newMargin = newBase + increment * (cacheSize - 1); + if (newMargin < minValue || (newBase & ~newMargin) < 0) { + /* + * Don't cache values near the end of the sequence for + * simplicity. + */ + newMargin = newBase; + } + margin = newMargin; + needsFlush = true; + } + baseValue = newBase; + return needsFlush; + } + + /** + * Flush the current value to disk. + */ + public void flushWithoutMargin() { + if (margin != baseValue) { + margin = baseValue; + flush(null); + } + } + + /** + * Flush the current value, including the margin, to disk. + * + * @param session the session + */ + public void flush(SessionLocal session) { + if (isTemporary()) { + return; + } + if (session == null || !database.isSysTableLockedBy(session)) { + // This session may not lock the sys table (except if it has already + // locked it) because it must be committed immediately, otherwise + // other threads can not access the sys table. + SessionLocal sysSession = database.getSystemSession(); + synchronized (sysSession) { + flushInternal(sysSession); + sysSession.commit(false); + } + } else { + synchronized (session) { + flushInternal(session); + } + } + } + + private void flushInternal(SessionLocal session) { + final boolean metaWasLocked = database.lockMeta(session); + // just for this case, use the value with the margin + try { + writeWithMargin = true; + database.updateMeta(session, this); + } finally { + writeWithMargin = false; + if (!metaWasLocked) { + database.unlockMeta(session); + } + } + } + + /** + * Flush the current value to disk and close this object. + */ + public void close() { + flushWithoutMargin(); + } + + @Override + public int getType() { + return DbObject.SEQUENCE; + } + + @Override + public void removeChildrenAndResources(SessionLocal session) { + database.removeMeta(session, getId()); + invalidate(); + } + + public synchronized long getBaseValue() { + // Use synchronized because baseValue is not volatile + return baseValue; + } + + public synchronized long getCurrentValue() { + return baseValue - increment; + } + + public void setBelongsToTable(boolean b) { + this.belongsToTable = b; + } + + public long getCacheSize() { + return cacheSize; + } + +} diff --git a/h2/src/main/org/h2/schema/TriggerObject.java b/h2/src/main/org/h2/schema/TriggerObject.java new file mode 100644 index 0000000..fbf2b46 --- /dev/null +++ b/h2/src/main/org/h2/schema/TriggerObject.java @@ -0,0 +1,542 @@ +/* + * 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.schema; + +import java.lang.reflect.Method; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Arrays; + +import org.h2.api.ErrorCode; +import org.h2.api.Trigger; +import org.h2.engine.Constants; +import org.h2.engine.DbObject; +import org.h2.engine.SessionLocal; +import org.h2.jdbc.JdbcConnection; +import org.h2.jdbc.JdbcResultSet; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.result.Row; +import org.h2.result.SimpleResult; +import org.h2.table.Column; +import org.h2.table.Table; +import org.h2.tools.TriggerAdapter; +import org.h2.util.JdbcUtils; +import org.h2.util.SourceCompiler; +import org.h2.util.StringUtils; +import org.h2.value.Value; +import org.h2.value.ValueToObjectConverter; + +/** + *A trigger is created using the statement + * CREATE TRIGGER + */ +public final class TriggerObject extends SchemaObject { + + /** + * The default queue size. + */ + public static final int DEFAULT_QUEUE_SIZE = 1024; + + private boolean insteadOf; + private boolean before; + private int typeMask; + private boolean rowBased; + private boolean onRollback; + // TODO trigger: support queue and noWait = false as well + private int queueSize = DEFAULT_QUEUE_SIZE; + private boolean noWait; + private Table table; + private String triggerClassName; + private String triggerSource; + private Trigger triggerCallback; + + public TriggerObject(Schema schema, int id, String name, Table table) { + super(schema, id, name, Trace.TRIGGER); + this.table = table; + setTemporary(table.isTemporary()); + } + + public void setBefore(boolean before) { + this.before = before; + } + + public boolean isInsteadOf() { + return insteadOf; + } + + public void setInsteadOf(boolean insteadOf) { + this.insteadOf = insteadOf; + } + + private synchronized void load() { + if (triggerCallback != null) { + return; + } + try { + SessionLocal sysSession = database.getSystemSession(); + Connection c2 = sysSession.createConnection(false); + Object obj; + if (triggerClassName != null) { + obj = JdbcUtils.loadUserClass(triggerClassName).getDeclaredConstructor().newInstance(); + } else { + obj = loadFromSource(); + } + triggerCallback = (Trigger) obj; + triggerCallback.init(c2, getSchema().getName(), getName(), + table.getName(), before, typeMask); + } catch (Throwable e) { + // try again later + triggerCallback = null; + throw DbException.get(ErrorCode.ERROR_CREATING_TRIGGER_OBJECT_3, e, getName(), + triggerClassName != null ? triggerClassName : "..source..", e.toString()); + } + } + + private Trigger loadFromSource() { + SourceCompiler compiler = database.getCompiler(); + synchronized (compiler) { + String fullClassName = Constants.USER_PACKAGE + ".trigger." + getName(); + compiler.setSource(fullClassName, triggerSource); + try { + if (SourceCompiler.isJavaxScriptSource(triggerSource)) { + return (Trigger) compiler.getCompiledScript(fullClassName).eval(); + } else { + final Method m = compiler.getMethod(fullClassName); + if (m.getParameterTypes().length > 0) { + throw new IllegalStateException("No parameters are allowed for a trigger"); + } + return (Trigger) m.invoke(null); + } + } catch (DbException e) { + throw e; + } catch (Exception e) { + throw DbException.get(ErrorCode.SYNTAX_ERROR_1, e, triggerSource); + } + } + } + + /** + * Set the trigger class name and load the class if possible. + * + * @param triggerClassName the name of the trigger class + * @param force whether exceptions (due to missing class or access rights) + * should be ignored + */ + public void setTriggerClassName(String triggerClassName, boolean force) { + this.setTriggerAction(triggerClassName, null, force); + } + + /** + * Set the trigger source code and compile it if possible. + * + * @param source the source code of a method returning a {@link Trigger} + * @param force whether exceptions (due to syntax error) + * should be ignored + */ + public void setTriggerSource(String source, boolean force) { + this.setTriggerAction(null, source, force); + } + + private void setTriggerAction(String triggerClassName, String source, boolean force) { + this.triggerClassName = triggerClassName; + this.triggerSource = source; + try { + load(); + } catch (DbException e) { + if (!force) { + throw e; + } + } + } + + /** + * Call the trigger class if required. This method does nothing if the + * trigger is not defined for the given action. This method is called before + * or after any rows have been processed, once for each statement. + * + * @param session the session + * @param type the trigger type + * @param beforeAction if this method is called before applying the changes + */ + public void fire(SessionLocal session, int type, boolean beforeAction) { + if (rowBased || before != beforeAction || (typeMask & type) == 0) { + return; + } + load(); + Connection c2 = session.createConnection(false); + boolean old = false; + if (type != Trigger.SELECT) { + old = session.setCommitOrRollbackDisabled(true); + } + Value identity = session.getLastIdentity(); + try { + if (triggerCallback instanceof TriggerAdapter) { + ((TriggerAdapter) triggerCallback).fire(c2, (ResultSet) null, (ResultSet) null); + } else { + triggerCallback.fire(c2, null, null); + } + } catch (Throwable e) { + throw getErrorExecutingTrigger(e); + } finally { + session.setLastIdentity(identity); + if (type != Trigger.SELECT) { + session.setCommitOrRollbackDisabled(old); + } + } + } + + private static Object[] convertToObjectList(Row row, JdbcConnection conn) { + if (row == null) { + return null; + } + int len = row.getColumnCount(); + Object[] list = new Object[len]; + for (int i = 0; i < len; i++) { + list[i] = ValueToObjectConverter.valueToDefaultObject(row.getValue(i), conn, false); + } + return list; + } + + /** + * Call the fire method of the user-defined trigger class if required. This + * method does nothing if the trigger is not defined for the given action. + * This method is called before or after a row is processed, possibly many + * times for each statement. + * + * @param session the session + * @param table the table + * @param oldRow the old row + * @param newRow the new row + * @param beforeAction true if this method is called before the operation is + * applied + * @param rollback when the operation occurred within a rollback + * @return true if no further action is required (for 'instead of' triggers) + */ + public boolean fireRow(SessionLocal session, Table table, Row oldRow, Row newRow, + boolean beforeAction, boolean rollback) { + if (!rowBased || before != beforeAction) { + return false; + } + if (rollback && !onRollback) { + return false; + } + load(); + boolean fire = false; + if ((typeMask & Trigger.INSERT) != 0) { + if (oldRow == null && newRow != null) { + fire = true; + } + } + if ((typeMask & Trigger.UPDATE) != 0) { + if (oldRow != null && newRow != null) { + fire = true; + } + } + if ((typeMask & Trigger.DELETE) != 0) { + if (oldRow != null && newRow == null) { + fire = true; + } + } + if (!fire) { + return false; + } + JdbcConnection c2 = session.createConnection(false); + boolean old = session.getAutoCommit(); + boolean oldDisabled = session.setCommitOrRollbackDisabled(true); + Value identity = session.getLastIdentity(); + try { + session.setAutoCommit(false); + if (triggerCallback instanceof TriggerAdapter) { + JdbcResultSet oldResultSet = oldRow != null ? createResultSet(c2, table, oldRow, false) : null; + JdbcResultSet newResultSet = newRow != null ? createResultSet(c2, table, newRow, before) : null; + try { + ((TriggerAdapter) triggerCallback).fire(c2, oldResultSet, newResultSet); + } catch (Throwable e) { + throw getErrorExecutingTrigger(e); + } + if (newResultSet != null) { + Value[] updatedList = newResultSet.getUpdateRow(); + if (updatedList != null) { + boolean modified = false; + for (int i = 0, l = updatedList.length; i < l; i++) { + Value v = updatedList[i]; + if (v != null) { + modified = true; + newRow.setValue(i, v); + } + } + if (modified) { + table.convertUpdateRow(session, newRow, true); + } + } + } + } else { + Object[] oldList = convertToObjectList(oldRow, c2); + Object[] newList = convertToObjectList(newRow, c2); + Object[] newListBackup = before && newList != null ? Arrays.copyOf(newList, newList.length) : null; + try { + triggerCallback.fire(c2, oldList, newList); + } catch (Throwable e) { + throw getErrorExecutingTrigger(e); + } + if (newListBackup != null) { + boolean modified = false; + for (int i = 0; i < newList.length; i++) { + Object o = newList[i]; + if (o != newListBackup[i]) { + modified = true; + newRow.setValue(i, ValueToObjectConverter.objectToValue(session, o, Value.UNKNOWN)); + } + } + if (modified) { + table.convertUpdateRow(session, newRow, true); + } + } + } + } catch (Exception e) { + if (onRollback) { + // ignore + } else { + throw DbException.convert(e); + } + } finally { + session.setLastIdentity(identity); + session.setCommitOrRollbackDisabled(oldDisabled); + session.setAutoCommit(old); + } + return insteadOf; + } + + private static JdbcResultSet createResultSet(JdbcConnection conn, Table table, Row row, boolean updatable) + throws SQLException { + SimpleResult result = new SimpleResult(table.getSchema().getName(), table.getName()); + for (Column c : table.getColumns()) { + result.addColumn(c.getName(), c.getType()); + } + /* + * Old implementation works with and without next() invocation, so add + * the row twice for compatibility. + */ + result.addRow(row.getValueList()); + result.addRow(row.getValueList()); + JdbcResultSet resultSet = new JdbcResultSet(conn, null, null, result, -1, false, false, updatable); + resultSet.next(); + return resultSet; + } + + private DbException getErrorExecutingTrigger(Throwable e) { + if (e instanceof DbException) { + return (DbException) e; + } + if (e instanceof SQLException) { + return DbException.convert(e); + } + return DbException.get(ErrorCode.ERROR_EXECUTING_TRIGGER_3, e, getName(), + triggerClassName != null ? triggerClassName : "..source..", e.toString()); + } + + /** + * Returns the trigger type. + * + * @return the trigger type + */ + public int getTypeMask() { + return typeMask; + } + + /** + * Set the trigger type. + * + * @param typeMask the type + */ + public void setTypeMask(int typeMask) { + this.typeMask = typeMask; + } + + public void setRowBased(boolean rowBased) { + this.rowBased = rowBased; + } + + public boolean isRowBased() { + return rowBased; + } + + public void setQueueSize(int size) { + this.queueSize = size; + } + + public int getQueueSize() { + return queueSize; + } + + public void setNoWait(boolean noWait) { + this.noWait = noWait; + } + + public boolean isNoWait() { + return noWait; + } + + public void setOnRollback(boolean onRollback) { + this.onRollback = onRollback; + } + + public boolean isOnRollback() { + return onRollback; + } + + @Override + public String getCreateSQLForCopy(Table targetTable, String quotedName) { + StringBuilder builder = new StringBuilder("CREATE FORCE TRIGGER "); + builder.append(quotedName); + if (insteadOf) { + builder.append(" INSTEAD OF "); + } else if (before) { + builder.append(" BEFORE "); + } else { + builder.append(" AFTER "); + } + getTypeNameList(builder).append(" ON "); + targetTable.getSQL(builder, DEFAULT_SQL_FLAGS); + if (rowBased) { + builder.append(" FOR EACH ROW"); + } + if (noWait) { + builder.append(" NOWAIT"); + } else { + builder.append(" QUEUE ").append(queueSize); + } + if (triggerClassName != null) { + StringUtils.quoteStringSQL(builder.append(" CALL "), triggerClassName); + } else { + StringUtils.quoteStringSQL(builder.append(" AS "), triggerSource); + } + return builder.toString(); + } + + /** + * Append the trigger types to the given string builder. + * + * @param builder the builder + * @return the passed string builder + */ + public StringBuilder getTypeNameList(StringBuilder builder) { + boolean f = false; + if ((typeMask & Trigger.INSERT) != 0) { + f = true; + builder.append("INSERT"); + } + if ((typeMask & Trigger.UPDATE) != 0) { + if (f) { + builder.append(", "); + } + f = true; + builder.append("UPDATE"); + } + if ((typeMask & Trigger.DELETE) != 0) { + if (f) { + builder.append(", "); + } + f = true; + builder.append("DELETE"); + } + if ((typeMask & Trigger.SELECT) != 0) { + if (f) { + builder.append(", "); + } + f = true; + builder.append("SELECT"); + } + if (onRollback) { + if (f) { + builder.append(", "); + } + builder.append("ROLLBACK"); + } + return builder; + } + + @Override + public String getCreateSQL() { + return getCreateSQLForCopy(table, getSQL(DEFAULT_SQL_FLAGS)); + } + + @Override + public int getType() { + return DbObject.TRIGGER; + } + + @Override + public void removeChildrenAndResources(SessionLocal session) { + table.removeTrigger(this); + database.removeMeta(session, getId()); + if (triggerCallback != null) { + try { + triggerCallback.remove(); + } catch (SQLException e) { + throw DbException.convert(e); + } + } + table = null; + triggerClassName = null; + triggerSource = null; + triggerCallback = null; + invalidate(); + } + + /** + * Get the table of this trigger. + * + * @return the table + */ + public Table getTable() { + return table; + } + + /** + * Check if this is a before trigger. + * + * @return true if it is + */ + public boolean isBefore() { + return before; + } + + /** + * Get the trigger class name. + * + * @return the class name + */ + public String getTriggerClassName() { + return triggerClassName; + } + + public String getTriggerSource() { + return triggerSource; + } + + /** + * Close the trigger. + * @throws SQLException on failure + */ + public void close() throws SQLException { + if (triggerCallback != null) { + triggerCallback.close(); + } + } + + /** + * Check whether this is a select trigger. + * + * @return true if it is + */ + public boolean isSelectTrigger() { + return (typeMask & Trigger.SELECT) != 0; + } + +} diff --git a/h2/src/main/org/h2/schema/UserAggregate.java b/h2/src/main/org/h2/schema/UserAggregate.java new file mode 100644 index 0000000..45ee8b4 --- /dev/null +++ b/h2/src/main/org/h2/schema/UserAggregate.java @@ -0,0 +1,119 @@ +/* + * 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.schema; + +import java.sql.Connection; +import java.sql.SQLException; + +import org.h2.api.Aggregate; +import org.h2.api.AggregateFunction; +import org.h2.engine.DbObject; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.util.JdbcUtils; +import org.h2.util.StringUtils; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; + +/** + * Represents a user-defined aggregate function. + */ +public final class UserAggregate extends UserDefinedFunction { + + private Class javaClass; + + public UserAggregate(Schema schema, int id, String name, String className, + boolean force) { + super(schema, id, name, Trace.FUNCTION); + this.className = className; + if (!force) { + getInstance(); + } + } + + public Aggregate getInstance() { + if (javaClass == null) { + javaClass = JdbcUtils.loadUserClass(className); + } + Object obj; + try { + obj = javaClass.getDeclaredConstructor().newInstance(); + Aggregate agg; + if (obj instanceof Aggregate) { + agg = (Aggregate) obj; + } else { + agg = new AggregateWrapper((AggregateFunction) obj); + } + return agg; + } catch (Exception e) { + throw DbException.convert(e); + } + } + + @Override + public String getDropSQL() { + StringBuilder builder = new StringBuilder("DROP AGGREGATE IF EXISTS "); + return getSQL(builder, DEFAULT_SQL_FLAGS).toString(); + } + + @Override + public String getCreateSQL() { + StringBuilder builder = new StringBuilder("CREATE FORCE AGGREGATE "); + getSQL(builder, DEFAULT_SQL_FLAGS).append(" FOR "); + return StringUtils.quoteStringSQL(builder, className).toString(); + } + + @Override + public int getType() { + return DbObject.AGGREGATE; + } + + @Override + public synchronized void removeChildrenAndResources(SessionLocal session) { + database.removeMeta(session, getId()); + className = null; + javaClass = null; + invalidate(); + } + + /** + * Wrap {@link AggregateFunction} in order to behave as + * {@link org.h2.api.Aggregate} + **/ + private static class AggregateWrapper implements Aggregate { + private final AggregateFunction aggregateFunction; + + AggregateWrapper(AggregateFunction aggregateFunction) { + this.aggregateFunction = aggregateFunction; + } + + @Override + public void init(Connection conn) throws SQLException { + aggregateFunction.init(conn); + } + + @Override + public int getInternalType(int[] inputTypes) throws SQLException { + int[] sqlTypes = new int[inputTypes.length]; + for (int i = 0; i < inputTypes.length; i++) { + sqlTypes[i] = DataType.convertTypeToSQLType(TypeInfo.getTypeInfo(inputTypes[i])); + } + return DataType.convertSQLTypeToValueType(aggregateFunction.getType(sqlTypes)); + } + + @Override + public void add(Object value) throws SQLException { + aggregateFunction.add(value); + } + + @Override + public Object getResult() throws SQLException { + return aggregateFunction.getResult(); + } + } + +} diff --git a/h2/src/main/org/h2/schema/UserDefinedFunction.java b/h2/src/main/org/h2/schema/UserDefinedFunction.java new file mode 100644 index 0000000..7a3c6c8 --- /dev/null +++ b/h2/src/main/org/h2/schema/UserDefinedFunction.java @@ -0,0 +1,36 @@ +/* + * 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.schema; + +import org.h2.message.DbException; +import org.h2.table.Table; + +/** + * User-defined Java function or aggregate function. + */ +public abstract class UserDefinedFunction extends SchemaObject { + + String className; + + UserDefinedFunction(Schema newSchema, int id, String name, int traceModuleId) { + 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"); + } + + public final String getJavaClassName() { + return className; + } + +} diff --git a/h2/src/main/org/h2/schema/package.html b/h2/src/main/org/h2/schema/package.html new file mode 100644 index 0000000..815a65a --- /dev/null +++ b/h2/src/main/org/h2/schema/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Schema implementation and objects that are stored in a schema (for example, sequences and constants). + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/security/AES.java b/h2/src/main/org/h2/security/AES.java new file mode 100644 index 0000000..2644770 --- /dev/null +++ b/h2/src/main/org/h2/security/AES.java @@ -0,0 +1,327 @@ +/* + * 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.security; + +import org.h2.util.Bits; + +/** + * An implementation of the AES block cipher algorithm, + * also known as Rijndael. Only AES-128 is supported by this class. + */ +public class AES implements BlockCipher { + + private static final int[] RCON = new int[10]; + private static final int[] FS = new int[256]; + private static final int[] FT0 = new int[256]; + private static final int[] FT1 = new int[256]; + private static final int[] FT2 = new int[256]; + private static final int[] FT3 = new int[256]; + private static final int[] RS = new int[256]; + private static final int[] RT0 = new int[256]; + private static final int[] RT1 = new int[256]; + private static final int[] RT2 = new int[256]; + private static final int[] RT3 = new int[256]; + private final int[] encKey = new int[44]; + private final int[] decKey = new int[44]; + + private static int rot8(int x) { + return (x >>> 8) | (x << 24); + } + + private static int xtime(int x) { + return ((x << 1) ^ (((x & 0x80) != 0) ? 0x1b : 0)) & 255; + } + + private static int mul(int[] pow, int[] log, int x, int y) { + return (x != 0 && y != 0) ? pow[(log[x] + log[y]) % 255] : 0; + } + + static { + int[] pow = new int[256]; + int[] log = new int[256]; + for (int i = 0, x = 1; i < 256; i++, x ^= xtime(x)) { + pow[i] = x; + log[x] = i; + } + for (int i = 0, x = 1; i < 10; i++, x = xtime(x)) { + RCON[i] = x << 24; + } + FS[0x00] = 0x63; + RS[0x63] = 0x00; + for (int i = 1; i < 256; i++) { + int x = pow[255 - log[i]], y = x; + y = ((y << 1) | (y >> 7)) & 255; + x ^= y; + y = ((y << 1) | (y >> 7)) & 255; + x ^= y; + y = ((y << 1) | (y >> 7)) & 255; + x ^= y; + y = ((y << 1) | (y >> 7)) & 255; + x ^= y ^ 0x63; + FS[i] = x & 255; + RS[x] = i & 255; + } + for (int i = 0; i < 256; i++) { + int x = FS[i], y = xtime(x); + FT0[i] = (x ^ y) ^ (x << 8) ^ (x << 16) ^ (y << 24); + FT1[i] = rot8(FT0[i]); + FT2[i] = rot8(FT1[i]); + FT3[i] = rot8(FT2[i]); + y = RS[i]; + RT0[i] = mul(pow, log, 0x0b, y) ^ (mul(pow, log, 0x0d, y) << 8) + ^ (mul(pow, log, 0x09, y) << 16) ^ (mul(pow, log, 0x0e, y) << 24); + RT1[i] = rot8(RT0[i]); + RT2[i] = rot8(RT1[i]); + RT3[i] = rot8(RT2[i]); + } + } + + private static int getDec(int t) { + return RT0[FS[(t >> 24) & 255]] ^ RT1[FS[(t >> 16) & 255]] + ^ RT2[FS[(t >> 8) & 255]] ^ RT3[FS[t & 255]]; + } + + @Override + public void setKey(byte[] key) { + for (int i = 0, j = 0; i < 4; i++) { + encKey[i] = decKey[i] = ((key[j++] & 255) << 24) + | ((key[j++] & 255) << 16) | ((key[j++] & 255) << 8) + | (key[j++] & 255); + } + int e = 0; + for (int i = 0; i < 10; i++, e += 4) { + 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] >> 24) & 255]; + encKey[e + 5] = encKey[e + 1] ^ encKey[e + 4]; + encKey[e + 6] = encKey[e + 2] ^ encKey[e + 5]; + encKey[e + 7] = encKey[e + 3] ^ encKey[e + 6]; + } + int d = 0; + decKey[d++] = encKey[e++]; + decKey[d++] = encKey[e++]; + decKey[d++] = encKey[e++]; + decKey[d++] = encKey[e++]; + for (int i = 1; i < 10; i++) { + e -= 8; + decKey[d++] = getDec(encKey[e++]); + decKey[d++] = getDec(encKey[e++]); + decKey[d++] = getDec(encKey[e++]); + decKey[d++] = getDec(encKey[e++]); + } + e -= 8; + decKey[d++] = encKey[e++]; + decKey[d++] = encKey[e++]; + decKey[d++] = encKey[e++]; + decKey[d] = encKey[e]; + } + + @Override + public void encrypt(byte[] bytes, int off, int len) { + for (int i = off; i < off + len; i += 16) { + encryptBlock(bytes, bytes, i); + } + } + + @Override + public void decrypt(byte[] bytes, int off, int len) { + for (int i = off; i < off + len; i += 16) { + decryptBlock(bytes, bytes, i); + } + } + + private void encryptBlock(byte[] in, byte[] out, int off) { + int[] k = encKey; + int x0 = Bits.readInt(in, off) ^ k[0]; + int x1 = Bits.readInt(in, off + 4) ^ k[1]; + int x2 = Bits.readInt(in, off + 8) ^ k[2]; + int x3 = Bits.readInt(in, off + 12) ^ k[3]; + int y0 = FT0[(x0 >> 24) & 255] ^ FT1[(x1 >> 16) & 255] + ^ FT2[(x2 >> 8) & 255] ^ FT3[x3 & 255] ^ k[4]; + int y1 = FT0[(x1 >> 24) & 255] ^ FT1[(x2 >> 16) & 255] + ^ FT2[(x3 >> 8) & 255] ^ FT3[x0 & 255] ^ k[5]; + int y2 = FT0[(x2 >> 24) & 255] ^ FT1[(x3 >> 16) & 255] + ^ FT2[(x0 >> 8) & 255] ^ FT3[x1 & 255] ^ k[6]; + int y3 = FT0[(x3 >> 24) & 255] ^ FT1[(x0 >> 16) & 255] + ^ FT2[(x1 >> 8) & 255] ^ FT3[x2 & 255] ^ k[7]; + x0 = FT0[(y0 >> 24) & 255] ^ FT1[(y1 >> 16) & 255] + ^ FT2[(y2 >> 8) & 255] ^ FT3[y3 & 255] ^ k[8]; + x1 = FT0[(y1 >> 24) & 255] ^ FT1[(y2 >> 16) & 255] + ^ FT2[(y3 >> 8) & 255] ^ FT3[y0 & 255] ^ k[9]; + x2 = FT0[(y2 >> 24) & 255] ^ FT1[(y3 >> 16) & 255] + ^ FT2[(y0 >> 8) & 255] ^ FT3[y1 & 255] ^ k[10]; + x3 = FT0[(y3 >> 24) & 255] ^ FT1[(y0 >> 16) & 255] + ^ FT2[(y1 >> 8) & 255] ^ FT3[y2 & 255] ^ k[11]; + y0 = FT0[(x0 >> 24) & 255] ^ FT1[(x1 >> 16) & 255] + ^ FT2[(x2 >> 8) & 255] ^ FT3[x3 & 255] ^ k[12]; + y1 = FT0[(x1 >> 24) & 255] ^ FT1[(x2 >> 16) & 255] + ^ FT2[(x3 >> 8) & 255] ^ FT3[x0 & 255] ^ k[13]; + y2 = FT0[(x2 >> 24) & 255] ^ FT1[(x3 >> 16) & 255] + ^ FT2[(x0 >> 8) & 255] ^ FT3[x1 & 255] ^ k[14]; + y3 = FT0[(x3 >> 24) & 255] ^ FT1[(x0 >> 16) & 255] + ^ FT2[(x1 >> 8) & 255] ^ FT3[x2 & 255] ^ k[15]; + x0 = FT0[(y0 >> 24) & 255] ^ FT1[(y1 >> 16) & 255] + ^ FT2[(y2 >> 8) & 255] ^ FT3[y3 & 255] ^ k[16]; + x1 = FT0[(y1 >> 24) & 255] ^ FT1[(y2 >> 16) & 255] + ^ FT2[(y3 >> 8) & 255] ^ FT3[y0 & 255] ^ k[17]; + x2 = FT0[(y2 >> 24) & 255] ^ FT1[(y3 >> 16) & 255] + ^ FT2[(y0 >> 8) & 255] ^ FT3[y1 & 255] ^ k[18]; + x3 = FT0[(y3 >> 24) & 255] ^ FT1[(y0 >> 16) & 255] + ^ FT2[(y1 >> 8) & 255] ^ FT3[y2 & 255] ^ k[19]; + y0 = FT0[(x0 >> 24) & 255] ^ FT1[(x1 >> 16) & 255] + ^ FT2[(x2 >> 8) & 255] ^ FT3[x3 & 255] ^ k[20]; + y1 = FT0[(x1 >> 24) & 255] ^ FT1[(x2 >> 16) & 255] + ^ FT2[(x3 >> 8) & 255] ^ FT3[x0 & 255] ^ k[21]; + y2 = FT0[(x2 >> 24) & 255] ^ FT1[(x3 >> 16) & 255] + ^ FT2[(x0 >> 8) & 255] ^ FT3[x1 & 255] ^ k[22]; + y3 = FT0[(x3 >> 24) & 255] ^ FT1[(x0 >> 16) & 255] + ^ FT2[(x1 >> 8) & 255] ^ FT3[x2 & 255] ^ k[23]; + x0 = FT0[(y0 >> 24) & 255] ^ FT1[(y1 >> 16) & 255] + ^ FT2[(y2 >> 8) & 255] ^ FT3[y3 & 255] ^ k[24]; + x1 = FT0[(y1 >> 24) & 255] ^ FT1[(y2 >> 16) & 255] + ^ FT2[(y3 >> 8) & 255] ^ FT3[y0 & 255] ^ k[25]; + x2 = FT0[(y2 >> 24) & 255] ^ FT1[(y3 >> 16) & 255] + ^ FT2[(y0 >> 8) & 255] ^ FT3[y1 & 255] ^ k[26]; + x3 = FT0[(y3 >> 24) & 255] ^ FT1[(y0 >> 16) & 255] + ^ FT2[(y1 >> 8) & 255] ^ FT3[y2 & 255] ^ k[27]; + y0 = FT0[(x0 >> 24) & 255] ^ FT1[(x1 >> 16) & 255] + ^ FT2[(x2 >> 8) & 255] ^ FT3[x3 & 255] ^ k[28]; + y1 = FT0[(x1 >> 24) & 255] ^ FT1[(x2 >> 16) & 255] + ^ FT2[(x3 >> 8) & 255] ^ FT3[x0 & 255] ^ k[29]; + y2 = FT0[(x2 >> 24) & 255] ^ FT1[(x3 >> 16) & 255] + ^ FT2[(x0 >> 8) & 255] ^ FT3[x1 & 255] ^ k[30]; + y3 = FT0[(x3 >> 24) & 255] ^ FT1[(x0 >> 16) & 255] + ^ FT2[(x1 >> 8) & 255] ^ FT3[x2 & 255] ^ k[31]; + x0 = FT0[(y0 >> 24) & 255] ^ FT1[(y1 >> 16) & 255] + ^ FT2[(y2 >> 8) & 255] ^ FT3[y3 & 255] ^ k[32]; + x1 = FT0[(y1 >> 24) & 255] ^ FT1[(y2 >> 16) & 255] + ^ FT2[(y3 >> 8) & 255] ^ FT3[y0 & 255] ^ k[33]; + x2 = FT0[(y2 >> 24) & 255] ^ FT1[(y3 >> 16) & 255] + ^ FT2[(y0 >> 8) & 255] ^ FT3[y1 & 255] ^ k[34]; + x3 = FT0[(y3 >> 24) & 255] ^ FT1[(y0 >> 16) & 255] + ^ FT2[(y1 >> 8) & 255] ^ FT3[y2 & 255] ^ k[35]; + y0 = FT0[(x0 >> 24) & 255] ^ FT1[(x1 >> 16) & 255] + ^ FT2[(x2 >> 8) & 255] ^ FT3[x3 & 255] ^ k[36]; + y1 = FT0[(x1 >> 24) & 255] ^ FT1[(x2 >> 16) & 255] + ^ FT2[(x3 >> 8) & 255] ^ FT3[x0 & 255] ^ k[37]; + y2 = FT0[(x2 >> 24) & 255] ^ FT1[(x3 >> 16) & 255] + ^ FT2[(x0 >> 8) & 255] ^ FT3[x1 & 255] ^ k[38]; + y3 = FT0[(x3 >> 24) & 255] ^ FT1[(x0 >> 16) & 255] + ^ FT2[(x1 >> 8) & 255] ^ FT3[x2 & 255] ^ k[39]; + x0 = ((FS[(y0 >> 24) & 255] << 24) | (FS[(y1 >> 16) & 255] << 16) + | (FS[(y2 >> 8) & 255] << 8) | FS[y3 & 255]) ^ k[40]; + x1 = ((FS[(y1 >> 24) & 255] << 24) | (FS[(y2 >> 16) & 255] << 16) + | (FS[(y3 >> 8) & 255] << 8) | FS[y0 & 255]) ^ k[41]; + x2 = ((FS[(y2 >> 24) & 255] << 24) | (FS[(y3 >> 16) & 255] << 16) + | (FS[(y0 >> 8) & 255] << 8) | FS[y1 & 255]) ^ k[42]; + x3 = ((FS[(y3 >> 24) & 255] << 24) | (FS[(y0 >> 16) & 255] << 16) + | (FS[(y1 >> 8) & 255] << 8) | FS[y2 & 255]) ^ k[43]; + Bits.writeInt(out, off, x0); + Bits.writeInt(out, off + 4, x1); + Bits.writeInt(out, off + 8, x2); + Bits.writeInt(out, off + 12, x3); + } + + private void decryptBlock(byte[] in, byte[] out, int off) { + int[] k = decKey; + int x0 = Bits.readInt(in, off) ^ k[0]; + int x1 = Bits.readInt(in, off + 4) ^ k[1]; + int x2 = Bits.readInt(in, off + 8) ^ k[2]; + int x3 = Bits.readInt(in, off + 12) ^ k[3]; + int y0 = RT0[(x0 >> 24) & 255] ^ RT1[(x3 >> 16) & 255] + ^ RT2[(x2 >> 8) & 255] ^ RT3[x1 & 255] ^ k[4]; + int y1 = RT0[(x1 >> 24) & 255] ^ RT1[(x0 >> 16) & 255] + ^ RT2[(x3 >> 8) & 255] ^ RT3[x2 & 255] ^ k[5]; + int y2 = RT0[(x2 >> 24) & 255] ^ RT1[(x1 >> 16) & 255] + ^ RT2[(x0 >> 8) & 255] ^ RT3[x3 & 255] ^ k[6]; + int y3 = RT0[(x3 >> 24) & 255] ^ RT1[(x2 >> 16) & 255] + ^ RT2[(x1 >> 8) & 255] ^ RT3[x0 & 255] ^ k[7]; + x0 = RT0[(y0 >> 24) & 255] ^ RT1[(y3 >> 16) & 255] + ^ RT2[(y2 >> 8) & 255] ^ RT3[y1 & 255] ^ k[8]; + x1 = RT0[(y1 >> 24) & 255] ^ RT1[(y0 >> 16) & 255] + ^ RT2[(y3 >> 8) & 255] ^ RT3[y2 & 255] ^ k[9]; + x2 = RT0[(y2 >> 24) & 255] ^ RT1[(y1 >> 16) & 255] + ^ RT2[(y0 >> 8) & 255] ^ RT3[y3 & 255] ^ k[10]; + x3 = RT0[(y3 >> 24) & 255] ^ RT1[(y2 >> 16) & 255] + ^ RT2[(y1 >> 8) & 255] ^ RT3[y0 & 255] ^ k[11]; + y0 = RT0[(x0 >> 24) & 255] ^ RT1[(x3 >> 16) & 255] + ^ RT2[(x2 >> 8) & 255] ^ RT3[x1 & 255] ^ k[12]; + y1 = RT0[(x1 >> 24) & 255] ^ RT1[(x0 >> 16) & 255] + ^ RT2[(x3 >> 8) & 255] ^ RT3[x2 & 255] ^ k[13]; + y2 = RT0[(x2 >> 24) & 255] ^ RT1[(x1 >> 16) & 255] + ^ RT2[(x0 >> 8) & 255] ^ RT3[x3 & 255] ^ k[14]; + y3 = RT0[(x3 >> 24) & 255] ^ RT1[(x2 >> 16) & 255] + ^ RT2[(x1 >> 8) & 255] ^ RT3[x0 & 255] ^ k[15]; + x0 = RT0[(y0 >> 24) & 255] ^ RT1[(y3 >> 16) & 255] + ^ RT2[(y2 >> 8) & 255] ^ RT3[y1 & 255] ^ k[16]; + x1 = RT0[(y1 >> 24) & 255] ^ RT1[(y0 >> 16) & 255] + ^ RT2[(y3 >> 8) & 255] ^ RT3[y2 & 255] ^ k[17]; + x2 = RT0[(y2 >> 24) & 255] ^ RT1[(y1 >> 16) & 255] + ^ RT2[(y0 >> 8) & 255] ^ RT3[y3 & 255] ^ k[18]; + x3 = RT0[(y3 >> 24) & 255] ^ RT1[(y2 >> 16) & 255] + ^ RT2[(y1 >> 8) & 255] ^ RT3[y0 & 255] ^ k[19]; + y0 = RT0[(x0 >> 24) & 255] ^ RT1[(x3 >> 16) & 255] + ^ RT2[(x2 >> 8) & 255] ^ RT3[x1 & 255] ^ k[20]; + y1 = RT0[(x1 >> 24) & 255] ^ RT1[(x0 >> 16) & 255] + ^ RT2[(x3 >> 8) & 255] ^ RT3[x2 & 255] ^ k[21]; + y2 = RT0[(x2 >> 24) & 255] ^ RT1[(x1 >> 16) & 255] + ^ RT2[(x0 >> 8) & 255] ^ RT3[x3 & 255] ^ k[22]; + y3 = RT0[(x3 >> 24) & 255] ^ RT1[(x2 >> 16) & 255] + ^ RT2[(x1 >> 8) & 255] ^ RT3[x0 & 255] ^ k[23]; + x0 = RT0[(y0 >> 24) & 255] ^ RT1[(y3 >> 16) & 255] + ^ RT2[(y2 >> 8) & 255] ^ RT3[y1 & 255] ^ k[24]; + x1 = RT0[(y1 >> 24) & 255] ^ RT1[(y0 >> 16) & 255] + ^ RT2[(y3 >> 8) & 255] ^ RT3[y2 & 255] ^ k[25]; + x2 = RT0[(y2 >> 24) & 255] ^ RT1[(y1 >> 16) & 255] + ^ RT2[(y0 >> 8) & 255] ^ RT3[y3 & 255] ^ k[26]; + x3 = RT0[(y3 >> 24) & 255] ^ RT1[(y2 >> 16) & 255] + ^ RT2[(y1 >> 8) & 255] ^ RT3[y0 & 255] ^ k[27]; + y0 = RT0[(x0 >> 24) & 255] ^ RT1[(x3 >> 16) & 255] + ^ RT2[(x2 >> 8) & 255] ^ RT3[x1 & 255] ^ k[28]; + y1 = RT0[(x1 >> 24) & 255] ^ RT1[(x0 >> 16) & 255] + ^ RT2[(x3 >> 8) & 255] ^ RT3[x2 & 255] ^ k[29]; + y2 = RT0[(x2 >> 24) & 255] ^ RT1[(x1 >> 16) & 255] + ^ RT2[(x0 >> 8) & 255] ^ RT3[x3 & 255] ^ k[30]; + y3 = RT0[(x3 >> 24) & 255] ^ RT1[(x2 >> 16) & 255] + ^ RT2[(x1 >> 8) & 255] ^ RT3[x0 & 255] ^ k[31]; + x0 = RT0[(y0 >> 24) & 255] ^ RT1[(y3 >> 16) & 255] + ^ RT2[(y2 >> 8) & 255] ^ RT3[y1 & 255] ^ k[32]; + x1 = RT0[(y1 >> 24) & 255] ^ RT1[(y0 >> 16) & 255] + ^ RT2[(y3 >> 8) & 255] ^ RT3[y2 & 255] ^ k[33]; + x2 = RT0[(y2 >> 24) & 255] ^ RT1[(y1 >> 16) & 255] + ^ RT2[(y0 >> 8) & 255] ^ RT3[y3 & 255] ^ k[34]; + x3 = RT0[(y3 >> 24) & 255] ^ RT1[(y2 >> 16) & 255] + ^ RT2[(y1 >> 8) & 255] ^ RT3[y0 & 255] ^ k[35]; + y0 = RT0[(x0 >> 24) & 255] ^ RT1[(x3 >> 16) & 255] + ^ RT2[(x2 >> 8) & 255] ^ RT3[x1 & 255] ^ k[36]; + y1 = RT0[(x1 >> 24) & 255] ^ RT1[(x0 >> 16) & 255] + ^ RT2[(x3 >> 8) & 255] ^ RT3[x2 & 255] ^ k[37]; + y2 = RT0[(x2 >> 24) & 255] ^ RT1[(x1 >> 16) & 255] + ^ RT2[(x0 >> 8) & 255] ^ RT3[x3 & 255] ^ k[38]; + y3 = RT0[(x3 >> 24) & 255] ^ RT1[(x2 >> 16) & 255] + ^ RT2[(x1 >> 8) & 255] ^ RT3[x0 & 255] ^ k[39]; + x0 = ((RS[(y0 >> 24) & 255] << 24) | (RS[(y3 >> 16) & 255] << 16) + | (RS[(y2 >> 8) & 255] << 8) | RS[y1 & 255]) ^ k[40]; + x1 = ((RS[(y1 >> 24) & 255] << 24) | (RS[(y0 >> 16) & 255] << 16) + | (RS[(y3 >> 8) & 255] << 8) | RS[y2 & 255]) ^ k[41]; + x2 = ((RS[(y2 >> 24) & 255] << 24) | (RS[(y1 >> 16) & 255] << 16) + | (RS[(y0 >> 8) & 255] << 8) | RS[y3 & 255]) ^ k[42]; + x3 = ((RS[(y3 >> 24) & 255] << 24) | (RS[(y2 >> 16) & 255] << 16) + | (RS[(y1 >> 8) & 255] << 8) | RS[y0 & 255]) ^ k[43]; + Bits.writeInt(out, off, x0); + Bits.writeInt(out, off + 4, x1); + Bits.writeInt(out, off + 8, x2); + Bits.writeInt(out, off + 12, x3); + } + + @Override + public int getKeyLength() { + return 16; + } + +} diff --git a/h2/src/main/org/h2/security/BlockCipher.java b/h2/src/main/org/h2/security/BlockCipher.java new file mode 100644 index 0000000..6e4cca4 --- /dev/null +++ b/h2/src/main/org/h2/security/BlockCipher.java @@ -0,0 +1,53 @@ +/* + * 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.security; + +/** + * A block cipher is a data encryption algorithm that operates on blocks. + */ +public interface BlockCipher { + + /** + * Blocks sizes are always multiples of this number. + */ + int ALIGN = 16; + + /** + * Set the encryption key used for encrypting and decrypting. + * The key needs to be 16 bytes long. + * + * @param key the key + */ + void setKey(byte[] key); + + /** + * Encrypt a number of bytes. This is done in-place, that + * means the bytes are overwritten. + * + * @param bytes the byte array + * @param off the start index + * @param len the number of bytes to encrypt + */ + void encrypt(byte[] bytes, int off, int len); + + /** + * Decrypt a number of bytes. This is done in-place, that + * means the bytes are overwritten. + * + * @param bytes the byte array + * @param off the start index + * @param len the number of bytes to decrypt + */ + void decrypt(byte[] bytes, int off, int len); + + /** + * Get the length of the key in bytes. + * + * @return the length of the key + */ + int getKeyLength(); + +} diff --git a/h2/src/main/org/h2/security/CipherFactory.java b/h2/src/main/org/h2/security/CipherFactory.java new file mode 100644 index 0000000..8096238 --- /dev/null +++ b/h2/src/main/org/h2/security/CipherFactory.java @@ -0,0 +1,410 @@ +/* + * 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.security; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.Security; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; + +import javax.net.ServerSocketFactory; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +import org.h2.api.ErrorCode; +import org.h2.engine.SysProperties; +import org.h2.message.DbException; +import org.h2.mvstore.DataUtils; +import org.h2.store.fs.FileUtils; +import org.h2.util.IOUtils; +import org.h2.util.StringUtils; + +/** + * A factory to create new block cipher objects. + */ +public class CipherFactory { + + /** + * The default password to use for the .h2.keystore file + */ + public static final String KEYSTORE_PASSWORD = + "h2pass"; + + /** + * The security property which can prevent anonymous TLS connections. + * Introduced into Java 6, 7, 8 in updates from July 2015. + */ + public static final String LEGACY_ALGORITHMS_SECURITY_KEY = + "jdk.tls.legacyAlgorithms"; + + /** + * The value of {@value #LEGACY_ALGORITHMS_SECURITY_KEY} security + * property at the time of class initialization. + * Null if it is not set. + */ + public static final String DEFAULT_LEGACY_ALGORITHMS = getLegacyAlgorithmsSilently(); + + private static final String KEYSTORE = + "~/.h2.keystore"; + private static final String KEYSTORE_KEY = + "javax.net.ssl.keyStore"; + private static final String KEYSTORE_PASSWORD_KEY = + "javax.net.ssl.keyStorePassword"; + + + private CipherFactory() { + // utility class + } + + /** + * Get a new block cipher object for the given algorithm. + * + * @param algorithm the algorithm + * @return a new cipher object + */ + public static BlockCipher getBlockCipher(String algorithm) { + if ("XTEA".equalsIgnoreCase(algorithm)) { + return new XTEA(); + } else if ("AES".equalsIgnoreCase(algorithm)) { + return new AES(); + } else if ("FOG".equalsIgnoreCase(algorithm)) { + return new Fog(); + } + throw DbException.get(ErrorCode.UNSUPPORTED_CIPHER, algorithm); + } + + /** + * Create a secure client socket that is connected to the given address and + * port. + * + * @param address the address to connect to + * @param port the port + * @return the socket + * @throws IOException on failure + */ + public static Socket createSocket(InetAddress address, int port) + throws IOException { + setKeystore(); + SSLSocketFactory f = (SSLSocketFactory) SSLSocketFactory.getDefault(); + SSLSocket secureSocket = (SSLSocket) f.createSocket(); + secureSocket.connect(new InetSocketAddress(address, port), + SysProperties.SOCKET_CONNECT_TIMEOUT); + secureSocket.setEnabledProtocols( + disableSSL(secureSocket.getEnabledProtocols())); + if (SysProperties.ENABLE_ANONYMOUS_TLS) { + String[] list = enableAnonymous( + secureSocket.getEnabledCipherSuites(), + secureSocket.getSupportedCipherSuites()); + secureSocket.setEnabledCipherSuites(list); + } + return secureSocket; + } + +/** + * Create a secure server socket. If a bind address is specified, the + * socket is only bound to this address. + * If h2.enableAnonymousTLS is true, an attempt is made to modify + * the security property jdk.tls.legacyAlgorithms (in newer JVMs) to allow + * anonymous TLS. This system change is effectively permanent for the + * lifetime of the JVM. + * @see #removeAnonFromLegacyAlgorithms() + * + * @param port the port to listen on + * @param bindAddress the address to bind to, or null to bind to all + * addresses + * @return the server socket + * @throws IOException on failure + */ + public static ServerSocket createServerSocket(int port, + InetAddress bindAddress) throws IOException { + if (SysProperties.ENABLE_ANONYMOUS_TLS) { + removeAnonFromLegacyAlgorithms(); + } + setKeystore(); + ServerSocketFactory f = SSLServerSocketFactory.getDefault(); + SSLServerSocket secureSocket; + if (bindAddress == null) { + secureSocket = (SSLServerSocket) f.createServerSocket(port); + } else { + secureSocket = (SSLServerSocket) f.createServerSocket(port, 0, bindAddress); + } + secureSocket.setEnabledProtocols( + disableSSL(secureSocket.getEnabledProtocols())); + if (SysProperties.ENABLE_ANONYMOUS_TLS) { + String[] list = enableAnonymous( + secureSocket.getEnabledCipherSuites(), + secureSocket.getSupportedCipherSuites()); + secureSocket.setEnabledCipherSuites(list); + } + return secureSocket; + } + + /** + * Removes DH_anon and ECDH_anon from a comma separated list of ciphers. + * Only the first occurrence is removed. + * If there is nothing to remove, returns the reference to the argument. + * @param list a list of names separated by commas (and spaces) + * @return a new string without DH_anon and ECDH_anon items, + * or the original if none were found + */ + public static String removeDhAnonFromCommaSeparatedList(String list) { + if (list == null) { + return list; + } + List algorithms = new LinkedList<>(Arrays.asList(list.split("\\s*,\\s*"))); + boolean dhAnonRemoved = algorithms.remove("DH_anon"); + boolean ecdhAnonRemoved = algorithms.remove("ECDH_anon"); + if (dhAnonRemoved || ecdhAnonRemoved) { + String string = Arrays.toString(algorithms.toArray(new String[algorithms.size()])); + return (!algorithms.isEmpty()) ? string.substring(1, string.length() - 1): ""; + } + return list; + } + + /** + * Attempts to weaken the security properties to allow anonymous TLS. + * New JREs would not choose an anonymous cipher suite in a TLS handshake + * if server-side security property + * {@value #LEGACY_ALGORITHMS_SECURITY_KEY} + * were not modified from the default value. + *

+ * NOTE: In current (as of 2016) default implementations of JSSE which use + * this security property, the value is permanently cached inside the + * ServerHandshake class upon its first use. + * Therefore the modification accomplished by this method has to be done + * before the first use of a server SSL socket. + * Later changes to this property will not have any effect on server socket + * behavior. + */ + public static synchronized void removeAnonFromLegacyAlgorithms() { + String legacyOriginal = getLegacyAlgorithmsSilently(); + if (legacyOriginal == null) { + return; + } + String legacyNew = removeDhAnonFromCommaSeparatedList(legacyOriginal); + if (!legacyOriginal.equals(legacyNew)) { + setLegacyAlgorithmsSilently(legacyNew); + } + } + + /** + * Attempts to resets the security property to the default value. + * The default value of {@value #LEGACY_ALGORITHMS_SECURITY_KEY} was + * obtained at time of class initialization. + *

+ * NOTE: Resetting the property might not have any effect on server + * socket behavior. + * @see #removeAnonFromLegacyAlgorithms() + */ + public static synchronized void resetDefaultLegacyAlgorithms() { + setLegacyAlgorithmsSilently(DEFAULT_LEGACY_ALGORITHMS); + } + + /** + * Returns the security property {@value #LEGACY_ALGORITHMS_SECURITY_KEY}. + * Ignores security exceptions. + * + * @return the value of the security property, or null if not set + * or not accessible + */ + public static String getLegacyAlgorithmsSilently() { + String defaultLegacyAlgorithms = null; + try { + defaultLegacyAlgorithms = Security.getProperty(LEGACY_ALGORITHMS_SECURITY_KEY); + } catch (SecurityException e) { + // ignore + } + return defaultLegacyAlgorithms; + } + + private static void setLegacyAlgorithmsSilently(String legacyAlgorithms) { + if (legacyAlgorithms == null) { + return; + } + try { + Security.setProperty(LEGACY_ALGORITHMS_SECURITY_KEY, legacyAlgorithms); + } catch (SecurityException e) { + // ignore + } + } + + private static byte[] getKeyStoreBytes(KeyStore store, String password) + throws IOException { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + try { + store.store(bout, password.toCharArray()); + } catch (Exception e) { + throw DataUtils.convertToIOException(e); + } + return bout.toByteArray(); + } + + /** + * Get the keystore object using the given password. + * + * @param password the keystore password + * @return the keystore + * @throws IOException on failure + */ + public static KeyStore getKeyStore(String password) throws IOException { + try { + // The following source code can be re-generated + // if you have a keystore file. + // This code is (hopefully) more Java version independent + // than using keystores directly. See also: + // https://bugs.openjdk.java.net/browse/JDK-4887561 + // (1.4.2 cannot read keystore written with 1.4.1) + // --- generated code start --- + + KeyStore store = KeyStore.getInstance(KeyStore.getDefaultType()); + + store.load(null, password.toCharArray()); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + store.load(null, password.toCharArray()); + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec( + StringUtils.convertHexToBytes( + "30820277020100300d06092a864886f70d010101" + + "0500048202613082025d02010002818100dc0a13" + + "c602b7141110eade2f051b54777b060d0f74e6a1" + + "10f9cce81159f271ebc88d8e8aa1f743b505fc2e" + + "7dfe38d33b8d3f64d1b363d1af4d877833897954" + + "cbaec2fa384c22a415498cf306bb07ac09b76b00" + + "1cd68bf77ea0a628f5101959cf2993a9c23dbee7" + + "9b19305977f8715ae78d023471194cc900b231ee" + + "cb0aaea98d02030100010281810099aa4ff4d0a0" + + "9a5af0bd953cb10c4d08c3d98df565664ac5582e" + + "494314d5c3c92dddedd5d316a32a206be4ec0846" + + "16fe57be15e27cad111aa3c21fa79e32258c6ca8" + + "430afc69eddd52d3b751b37da6b6860910b94653" + + "192c0db1d02abcfd6ce14c01f238eec7c20bd3bb" + + "750940004bacba2880349a9494d10e139ecb2355" + + "d101024100ffdc3defd9c05a2d377ef6019fa62b" + + "3fbd5b0020a04cc8533bca730e1f6fcf5dfceea1" + + "b044fbe17d9eababfbc7d955edad6bc60f9be826" + + "ad2c22ba77d19a9f65024100dc28d43fdbbc9385" + + "2cc3567093157702bc16f156f709fb7db0d9eec0" + + "28f41fd0edcd17224c866e66be1744141fb724a1" + + "0fd741c8a96afdd9141b36d67fff6309024077b1" + + "cddbde0f69604bdcfe33263fb36ddf24aa3b9922" + + "327915b890f8a36648295d0139ecdf68c245652c" + + "4489c6257b58744fbdd961834a4cab201801a3b1" + + "e52d024100b17142e8991d1b350a0802624759d4" + + "8ae2b8071a158ff91fabeb6a8f7c328e762143dc" + + "726b8529f42b1fab6220d1c676fdc27ba5d44e84" + + "7c72c52064afd351a902407c6e23fe35bcfcd1a6" + + "62aa82a2aa725fcece311644d5b6e3894853fd4c" + + "e9fe78218c957b1ff03fc9e5ef8ffeb6bd58235f" + + "6a215c97d354fdace7e781e4a63e8b")); + PrivateKey privateKey = keyFactory.generatePrivate(keySpec); + Certificate[] certs = { CertificateFactory + .getInstance("X.509") + .generateCertificate( + new ByteArrayInputStream( + StringUtils.convertHexToBytes( + "3082018b3081f502044295ce6b300d06092a8648" + + "86f70d0101040500300d310b3009060355040313" + + "024832301e170d3035303532363133323630335a" + + "170d3337303933303036353734375a300d310b30" + + "0906035504031302483230819f300d06092a8648" + + "86f70d010101050003818d0030818902818100dc" + + "0a13c602b7141110eade2f051b54777b060d0f74" + + "e6a110f9cce81159f271ebc88d8e8aa1f743b505" + + "fc2e7dfe38d33b8d3f64d1b363d1af4d87783389" + + "7954cbaec2fa384c22a415498cf306bb07ac09b7" + + "6b001cd68bf77ea0a628f5101959cf2993a9c23d" + + "bee79b19305977f8715ae78d023471194cc900b2" + + "31eecb0aaea98d0203010001300d06092a864886" + + "f70d01010405000381810083f4401a279453701b" + + "ef9a7681a5b8b24f153f7d18c7c892133d97bd5f" + + "13736be7505290a445a7d5ceb75522403e509751" + + "5cd966ded6351ff60d5193de34cd36e5cb04d380" + + "398e66286f99923fd92296645fd4ada45844d194" + + "dfd815e6cd57f385c117be982809028bba1116c8" + + "5740b3d27a55b1a0948bf291ddba44bed337b9"))), }; + store.setKeyEntry("h2", privateKey, password.toCharArray(), certs); + // --- generated code end --- + return store; + } catch (Exception e) { + throw DataUtils.convertToIOException(e); + } + } + + private static void setKeystore() throws IOException { + Properties p = System.getProperties(); + if (p.getProperty(KEYSTORE_KEY) == null) { + String fileName = KEYSTORE; + byte[] data = getKeyStoreBytes(getKeyStore( + KEYSTORE_PASSWORD), KEYSTORE_PASSWORD); + boolean needWrite = true; + if (FileUtils.exists(fileName) && FileUtils.size(fileName) == data.length) { + // don't need to overwrite the file if it did not change + InputStream fin = FileUtils.newInputStream(fileName); + byte[] now = IOUtils.readBytesAndClose(fin, 0); + if (now != null && Arrays.equals(data, now)) { + needWrite = false; + } + } + if (needWrite) { + try { + OutputStream out = FileUtils.newOutputStream(fileName, false); + out.write(data); + out.close(); + } catch (Exception e) { + throw DataUtils.convertToIOException(e); + } + } + String absolutePath = FileUtils.toRealPath(fileName); + System.setProperty(KEYSTORE_KEY, absolutePath); + } + if (p.getProperty(KEYSTORE_PASSWORD_KEY) == null) { + System.setProperty(KEYSTORE_PASSWORD_KEY, KEYSTORE_PASSWORD); + } + } + + private static String[] enableAnonymous(String[] enabled, String[] supported) { + LinkedHashSet set = new LinkedHashSet<>(); + for (String x : supported) { + if (!x.startsWith("SSL") && x.contains("_anon_") && + (x.contains("_AES_") || x.contains("_3DES_")) && x.contains("_SHA")) { + set.add(x); + } + } + Collections.addAll(set, enabled); + return set.toArray(new String[0]); + } + + private static String[] disableSSL(String[] enabled) { + HashSet set = new HashSet<>(); + for (String x : enabled) { + if (!x.startsWith("SSL")) { + set.add(x); + } + } + return set.toArray(new String[0]); + } + +} diff --git a/h2/src/main/org/h2/security/Fog.java b/h2/src/main/org/h2/security/Fog.java new file mode 100644 index 0000000..ab5d61f --- /dev/null +++ b/h2/src/main/org/h2/security/Fog.java @@ -0,0 +1,75 @@ +/* + * 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.security; + +import org.h2.util.Bits; + +/** + * A pseudo-encryption algorithm that makes the data appear to be + * encrypted. This algorithm is cryptographically extremely weak, and should + * only be used to hide data from reading the plain text using a text editor. + */ +public class Fog implements BlockCipher { + + private int key; + + @Override + public void encrypt(byte[] bytes, int off, int len) { + for (int i = off; i < off + len; i += 16) { + encryptBlock(bytes, bytes, i); + } + } + + @Override + public void decrypt(byte[] bytes, int off, int len) { + for (int i = off; i < off + len; i += 16) { + decryptBlock(bytes, bytes, i); + } + } + + private void encryptBlock(byte[] in, byte[] out, int off) { + int x0 = Bits.readInt(in, off); + int x1 = Bits.readInt(in, off + 4); + int x2 = Bits.readInt(in, off + 8); + int x3 = Bits.readInt(in, off + 12); + int k = key; + x0 = Integer.rotateLeft(x0 ^ k, x1); + x2 = Integer.rotateLeft(x2 ^ k, x1); + x1 = Integer.rotateLeft(x1 ^ k, x0); + x3 = Integer.rotateLeft(x3 ^ k, x0); + Bits.writeInt(out, off, x0); + Bits.writeInt(out, off + 4, x1); + Bits.writeInt(out, off + 8, x2); + Bits.writeInt(out, off + 12, x3); + } + + private void decryptBlock(byte[] in, byte[] out, int off) { + int x0 = Bits.readInt(in, off); + int x1 = Bits.readInt(in, off + 4); + int x2 = Bits.readInt(in, off + 8); + int x3 = Bits.readInt(in, off + 12); + int k = key; + x1 = Integer.rotateRight(x1, x0) ^ k; + x3 = Integer.rotateRight(x3, x0) ^ k; + x0 = Integer.rotateRight(x0, x1) ^ k; + x2 = Integer.rotateRight(x2, x1) ^ k; + Bits.writeInt(out, off, x0); + Bits.writeInt(out, off + 4, x1); + Bits.writeInt(out, off + 8, x2); + Bits.writeInt(out, off + 12, x3); + } + + @Override + public int getKeyLength() { + return 16; + } + + @Override + public void setKey(byte[] key) { + this.key = (int) Bits.readLong(key, 0); + } + +} diff --git a/h2/src/main/org/h2/security/SHA256.java b/h2/src/main/org/h2/security/SHA256.java new file mode 100644 index 0000000..1b37289 --- /dev/null +++ b/h2/src/main/org/h2/security/SHA256.java @@ -0,0 +1,156 @@ +/* + * 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.security; + +import java.security.GeneralSecurityException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import org.h2.util.Bits; + +/** + * This class implements the cryptographic hash function SHA-256. + */ +public class SHA256 { + + private SHA256() { + } + + /** + * Calculate the hash code by using the given salt. The salt is appended + * after the data before the hash code is calculated. After generating the + * hash code, the data and all internal buffers are filled with zeros to + * avoid keeping insecure data in memory longer than required (and possibly + * swapped to disk). + * + * @param data the data to hash + * @param salt the salt to use + * @return the hash code + */ + public static byte[] getHashWithSalt(byte[] data, byte[] salt) { + byte[] buff = new byte[data.length + salt.length]; + System.arraycopy(data, 0, buff, 0, data.length); + System.arraycopy(salt, 0, buff, data.length, salt.length); + return getHash(buff, true); + } + + /** + * Calculate the hash of a password by prepending the user name and a '@' + * character. Both the user name and the password are encoded to a byte + * array using UTF-16. After generating the hash code, the password array + * and all internal buffers are filled with zeros to avoid keeping the plain + * text password in memory longer than required (and possibly swapped to + * disk). + * + * @param userName the user name + * @param password the password + * @return the hash code + */ + public static byte[] getKeyPasswordHash(String userName, char[] password) { + String user = userName + "@"; + byte[] buff = new byte[2 * (user.length() + password.length)]; + int n = 0; + for (int i = 0, length = user.length(); i < length; i++) { + char c = user.charAt(i); + buff[n++] = (byte) (c >> 8); + buff[n++] = (byte) c; + } + for (char c : password) { + buff[n++] = (byte) (c >> 8); + buff[n++] = (byte) c; + } + Arrays.fill(password, (char) 0); + return getHash(buff, true); + } + + /** + * Calculate the hash-based message authentication code. + * + * @param key the key + * @param message the message + * @return the hash + */ + public static byte[] getHMAC(byte[] key, byte[] message) { + return initMac(key).doFinal(message); + } + + private static Mac initMac(byte[] key) { + // Java forbids empty keys + if (key.length == 0) { + key = new byte[1]; + } + try { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(key, "HmacSHA256")); + return mac; + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } + } + + /** + * Calculate the hash using the password-based key derivation function 2. + * + * @param password the password + * @param salt the salt + * @param iterations the number of iterations + * @param resultLen the number of bytes in the result + * @return the result + */ + public static byte[] getPBKDF2(byte[] password, byte[] salt, + int iterations, int resultLen) { + byte[] result = new byte[resultLen]; + Mac mac = initMac(password); + int len = 64 + Math.max(32, salt.length + 4); + byte[] message = new byte[len]; + byte[] macRes = null; + for (int k = 1, offset = 0; offset < resultLen; k++, offset += 32) { + for (int i = 0; i < iterations; i++) { + if (i == 0) { + System.arraycopy(salt, 0, message, 0, salt.length); + Bits.writeInt(message, salt.length, k); + len = salt.length + 4; + } else { + System.arraycopy(macRes, 0, message, 0, 32); + len = 32; + } + mac.update(message, 0, len); + macRes = mac.doFinal(); + for (int j = 0; j < 32 && j + offset < resultLen; j++) { + result[j + offset] ^= macRes[j]; + } + } + } + Arrays.fill(password, (byte) 0); + return result; + } + + /** + * Calculate the hash code for the given data. + * + * @param data the data to hash + * @param nullData if the data should be filled with zeros after calculating + * the hash code + * @return the hash code + */ + public static byte[] getHash(byte[] data, boolean nullData) { + byte[] result; + try { + result = MessageDigest.getInstance("SHA-256").digest(data); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + if (nullData) { + Arrays.fill(data, (byte) 0); + } + return result; + } + +} diff --git a/h2/src/main/org/h2/security/SHA3.java b/h2/src/main/org/h2/security/SHA3.java new file mode 100644 index 0000000..cc22b7b --- /dev/null +++ b/h2/src/main/org/h2/security/SHA3.java @@ -0,0 +1,289 @@ +/* + * 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.security; + +import java.security.MessageDigest; +import java.util.Arrays; + +import org.h2.util.Bits; + +/** + * SHA-3 message digest family. + */ +public final class SHA3 extends MessageDigest { + + private static final long[] ROUND_CONSTANTS; + + static { + long[] rc = new long[24]; + byte l = 1; + for (int i = 0; i < 24; i++) { + rc[i] = 0; + for (int j = 0; j < 7; j++) { + byte t = l; + l = (byte) (t < 0 ? t << 1 ^ 0x71 : t << 1); + if ((t & 1) != 0) { + rc[i] ^= 1L << (1 << j) - 1; + } + } + } + ROUND_CONSTANTS = rc; + } + + /** + * Returns a new instance of SHA3-224 message digest. + * + * @return SHA3-224 message digest + */ + public static SHA3 getSha3_224() { + return new SHA3("SHA3-224", 28); + } + + /** + * Returns a new instance of SHA3-256 message digest. + * + * @return SHA3-256 message digest + */ + public static SHA3 getSha3_256() { + return new SHA3("SHA3-256", 32); + } + + /** + * Returns a new instance of SHA3-384 message digest. + * + * @return SHA3-384 message digest + */ + public static SHA3 getSha3_384() { + return new SHA3("SHA3-384", 48); + } + + /** + * Returns a new instance of SHA3-512 message digest. + * + * @return SHA3-512 message digest + */ + public static SHA3 getSha3_512() { + return new SHA3("SHA3-512", 64); + } + + private final int digestLength; + + private final int rate; + + private long state00, state01, state02, state03, state04, state05, state06, state07, state08, state09, // + state10, state11, state12, state13, state14, state15, state16, state17, state18, state19, // + state20, state21, state22, state23, state24; + + private final byte[] buf; + + private int bufcnt; + + private SHA3(String algorithm, int digestLength) { + super(algorithm); + this.digestLength = digestLength; + buf = new byte[this.rate = 200 - digestLength * 2]; + } + + @Override + protected byte[] engineDigest() { + buf[bufcnt] = 0b110; + Arrays.fill(buf, bufcnt + 1, rate, (byte) 0); + buf[rate - 1] |= 0x80; + absorbQueue(); + byte[] r = new byte[digestLength]; + switch (digestLength) { + case 64: + Bits.writeLongLE(r, 56, state07); + Bits.writeLongLE(r, 48, state06); + //$FALL-THROUGH$ + case 48: + Bits.writeLongLE(r, 40, state05); + Bits.writeLongLE(r, 32, state04); + //$FALL-THROUGH$ + case 32: + Bits.writeLongLE(r, 24, state03); + break; + case 28: + Bits.writeIntLE(r, 24, (int) state03); + } + Bits.writeLongLE(r, 16, state02); + Bits.writeLongLE(r, 8, state01); + Bits.writeLongLE(r, 0, state00); + engineReset(); + return r; + } + + @Override + protected int engineGetDigestLength() { + return digestLength; + } + + @Override + protected void engineReset() { + state24 = state23 = state22 = state21 = state20 // + = state19 = state18 = state17 = state16 = state15 // + = state14 = state13 = state12 = state11 = state10 // + = state09 = state08 = state07 = state06 = state05 // + = state04 = state03 = state02 = state01 = state00 = 0L; + Arrays.fill(buf, (byte) 0); + bufcnt = 0; + } + + @Override + protected void engineUpdate(byte input) { + buf[bufcnt++] = input; + if (bufcnt == rate) { + absorbQueue(); + } + } + + @Override + protected void engineUpdate(byte[] input, int offset, int len) { + while (len > 0) { + if (bufcnt == 0 && len >= rate) { + do { + absorb(input, offset); + offset += rate; + len -= rate; + } while (len >= rate); + } else { + int partialBlock = Math.min(len, rate - bufcnt); + System.arraycopy(input, offset, buf, bufcnt, partialBlock); + bufcnt += partialBlock; + offset += partialBlock; + len -= partialBlock; + if (bufcnt == rate) { + absorbQueue(); + } + } + } + } + + private void absorbQueue() { + absorb(buf, 0); + bufcnt = 0; + } + + private void absorb(byte[] data, int offset) { + /* + * There is no need to copy 25 state* fields into local variables, + * because so large number of local variables only hurts performance. + */ + switch (digestLength) { + case 28: + state17 ^= Bits.readLongLE(data, offset + 136); + //$FALL-THROUGH$ + case 32: + state13 ^= Bits.readLongLE(data, offset + 104); + state14 ^= Bits.readLongLE(data, offset + 112); + state15 ^= Bits.readLongLE(data, offset + 120); + state16 ^= Bits.readLongLE(data, offset + 128); + //$FALL-THROUGH$ + case 48: + state09 ^= Bits.readLongLE(data, offset + 72); + state10 ^= Bits.readLongLE(data, offset + 80); + state11 ^= Bits.readLongLE(data, offset + 88); + state12 ^= Bits.readLongLE(data, offset + 96); + } + state00 ^= Bits.readLongLE(data, offset); + state01 ^= Bits.readLongLE(data, offset + 8); + state02 ^= Bits.readLongLE(data, offset + 16); + state03 ^= Bits.readLongLE(data, offset + 24); + state04 ^= Bits.readLongLE(data, offset + 32); + state05 ^= Bits.readLongLE(data, offset + 40); + state06 ^= Bits.readLongLE(data, offset + 48); + state07 ^= Bits.readLongLE(data, offset + 56); + state08 ^= Bits.readLongLE(data, offset + 64); + for (int i = 0; i < 24; i++) { + long c0 = state00 ^ state05 ^ state10 ^ state15 ^ state20; + long c1 = state01 ^ state06 ^ state11 ^ state16 ^ state21; + long c2 = state02 ^ state07 ^ state12 ^ state17 ^ state22; + long c3 = state03 ^ state08 ^ state13 ^ state18 ^ state23; + long c4 = state04 ^ state09 ^ state14 ^ state19 ^ state24; + long dX = (c1 << 1 | c1 >>> 63) ^ c4; + state00 ^= dX; + state05 ^= dX; + state10 ^= dX; + state15 ^= dX; + state20 ^= dX; + dX = (c2 << 1 | c2 >>> 63) ^ c0; + state01 ^= dX; + state06 ^= dX; + state11 ^= dX; + state16 ^= dX; + state21 ^= dX; + dX = (c3 << 1 | c3 >>> 63) ^ c1; + state02 ^= dX; + state07 ^= dX; + state12 ^= dX; + state17 ^= dX; + state22 ^= dX; + dX = (c4 << 1 | c4 >>> 63) ^ c2; + state03 ^= dX; + state08 ^= dX; + state13 ^= dX; + state18 ^= dX; + state23 ^= dX; + dX = (c0 << 1 | c0 >>> 63) ^ c3; + state04 ^= dX; + state09 ^= dX; + state14 ^= dX; + state19 ^= dX; + state24 ^= dX; + long s00 = state00; + long s01 = state06 << 44 | state06 >>> 20; + long s02 = state12 << 43 | state12 >>> 21; + long s03 = state18 << 21 | state18 >>> 43; + long s04 = state24 << 14 | state24 >>> 50; + long s05 = state03 << 28 | state03 >>> 36; + long s06 = state09 << 20 | state09 >>> 44; + long s07 = state10 << 3 | state10 >>> 61; + long s08 = state16 << 45 | state16 >>> 19; + long s09 = state22 << 61 | state22 >>> 3; + long s10 = state01 << 1 | state01 >>> 63; + long s11 = state07 << 6 | state07 >>> 58; + long s12 = state13 << 25 | state13 >>> 39; + long s13 = state19 << 8 | state19 >>> 56; + long s14 = state20 << 18 | state20 >>> 46; + long s15 = state04 << 27 | state04 >>> 37; + long s16 = state05 << 36 | state05 >>> 28; + long s17 = state11 << 10 | state11 >>> 54; + long s18 = state17 << 15 | state17 >>> 49; + long s19 = state23 << 56 | state23 >>> 8; + long s20 = state02 << 62 | state02 >>> 2; + long s21 = state08 << 55 | state08 >>> 9; + long s22 = state14 << 39 | state14 >>> 25; + long s23 = state15 << 41 | state15 >>> 23; + long s24 = state21 << 2 | state21 >>> 62; + state00 = s00 ^ ~s01 & s02 ^ ROUND_CONSTANTS[i]; + state01 = s01 ^ ~s02 & s03; + state02 = s02 ^ ~s03 & s04; + state03 = s03 ^ ~s04 & s00; + state04 = s04 ^ ~s00 & s01; + state05 = s05 ^ ~s06 & s07; + state06 = s06 ^ ~s07 & s08; + state07 = s07 ^ ~s08 & s09; + state08 = s08 ^ ~s09 & s05; + state09 = s09 ^ ~s05 & s06; + state10 = s10 ^ ~s11 & s12; + state11 = s11 ^ ~s12 & s13; + state12 = s12 ^ ~s13 & s14; + state13 = s13 ^ ~s14 & s10; + state14 = s14 ^ ~s10 & s11; + state15 = s15 ^ ~s16 & s17; + state16 = s16 ^ ~s17 & s18; + state17 = s17 ^ ~s18 & s19; + state18 = s18 ^ ~s19 & s15; + state19 = s19 ^ ~s15 & s16; + state20 = s20 ^ ~s21 & s22; + state21 = s21 ^ ~s22 & s23; + state22 = s22 ^ ~s23 & s24; + state23 = s23 ^ ~s24 & s20; + state24 = s24 ^ ~s20 & s21; + } + } + +} diff --git a/h2/src/main/org/h2/security/SecureFileStore.java b/h2/src/main/org/h2/security/SecureFileStore.java new file mode 100644 index 0000000..2e70aaa --- /dev/null +++ b/h2/src/main/org/h2/security/SecureFileStore.java @@ -0,0 +1,114 @@ +/* + * 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.security; + +import org.h2.engine.Constants; +import org.h2.store.DataHandler; +import org.h2.store.FileStore; +import org.h2.util.Bits; +import org.h2.util.MathUtils; + +/** + * A file store that encrypts all data before writing, and decrypts all data + * after reading. Areas that were never written to (for example after calling + * setLength to enlarge the file) are not encrypted (contains 0 bytes). + */ +public class SecureFileStore extends FileStore { + + private byte[] key; + private final BlockCipher cipher; + private final BlockCipher cipherForInitVector; + private byte[] buffer = new byte[4]; + private long pos; + private final byte[] bufferForInitVector; + private final int keyIterations; + + public SecureFileStore(DataHandler handler, String name, String mode, + String cipher, byte[] key, int keyIterations) { + super(handler, name, mode); + this.key = key; + this.cipher = CipherFactory.getBlockCipher(cipher); + this.cipherForInitVector = CipherFactory.getBlockCipher(cipher); + this.keyIterations = keyIterations; + bufferForInitVector = new byte[Constants.FILE_BLOCK_SIZE]; + } + + @Override + protected byte[] generateSalt() { + return MathUtils.secureRandomBytes(Constants.FILE_BLOCK_SIZE); + } + + @Override + protected void initKey(byte[] salt) { + key = SHA256.getHashWithSalt(key, salt); + for (int i = 0; i < keyIterations; i++) { + key = SHA256.getHash(key, true); + } + cipher.setKey(key); + key = SHA256.getHash(key, true); + cipherForInitVector.setKey(key); + } + + @Override + protected void writeDirect(byte[] b, int off, int len) { + super.write(b, off, len); + pos += len; + } + + @Override + public void write(byte[] b, int off, int len) { + if (buffer.length < b.length) { + buffer = new byte[len]; + } + System.arraycopy(b, off, buffer, 0, len); + xorInitVector(buffer, 0, len, pos); + cipher.encrypt(buffer, 0, len); + super.write(buffer, 0, len); + pos += len; + } + + @Override + public void readFullyDirect(byte[] b, int off, int len) { + super.readFully(b, off, len); + pos += len; + } + + @Override + public void readFully(byte[] b, int off, int len) { + super.readFully(b, off, len); + for (int i = 0; i < len; i++) { + if (b[i] != 0) { + cipher.decrypt(b, off, len); + xorInitVector(b, off, len, pos); + break; + } + } + pos += len; + } + + @Override + public void seek(long x) { + this.pos = x; + super.seek(x); + } + + private void xorInitVector(byte[] b, int off, int len, long p) { + byte[] iv = bufferForInitVector; + while (len > 0) { + for (int i = 0; i < Constants.FILE_BLOCK_SIZE; i += 8) { + Bits.writeLong(iv, i, (p + i) >>> 3); + } + cipherForInitVector.encrypt(iv, 0, Constants.FILE_BLOCK_SIZE); + for (int i = 0; i < Constants.FILE_BLOCK_SIZE; i++) { + b[off + i] ^= iv[i]; + } + p += Constants.FILE_BLOCK_SIZE; + off += Constants.FILE_BLOCK_SIZE; + len -= Constants.FILE_BLOCK_SIZE; + } + } + +} diff --git a/h2/src/main/org/h2/security/XTEA.java b/h2/src/main/org/h2/security/XTEA.java new file mode 100644 index 0000000..01f2192 --- /dev/null +++ b/h2/src/main/org/h2/security/XTEA.java @@ -0,0 +1,150 @@ +/* + * 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.security; + +import org.h2.message.DbException; +import org.h2.util.Bits; + +/** + * An implementation of the XTEA block cipher algorithm. + *

+ * This implementation uses 32 rounds. + * The best attack reported as of 2009 is 36 rounds (Wikipedia). + */ +public class XTEA implements BlockCipher { + + private static final int DELTA = 0x9E3779B9; + private int k0, k1, k2, k3, k4, k5, k6, k7; + private int k8, k9, k10, k11, k12, k13, k14, k15; + private int k16, k17, k18, k19, k20, k21, k22, k23; + private int k24, k25, k26, k27, k28, k29, k30, k31; + + @Override + public void setKey(byte[] b) { + int[] key = new int[4]; + for (int i = 0; i < 16; i += 4) { + key[i / 4] = Bits.readInt(b, i); + } + int[] r = new int[32]; + for (int i = 0, sum = 0; i < 32;) { + r[i++] = sum + key[sum & 3]; + sum += DELTA; + r[i++] = sum + key[ (sum >>> 11) & 3]; + } + k0 = r[0]; k1 = r[1]; k2 = r[2]; k3 = r[3]; + k4 = r[4]; k5 = r[5]; k6 = r[6]; k7 = r[7]; + k8 = r[8]; k9 = r[9]; k10 = r[10]; k11 = r[11]; + k12 = r[12]; k13 = r[13]; k14 = r[14]; k15 = r[15]; + k16 = r[16]; k17 = r[17]; k18 = r[18]; k19 = r[19]; + k20 = r[20]; k21 = r[21]; k22 = r[22]; k23 = r[23]; + k24 = r[24]; k25 = r[25]; k26 = r[26]; k27 = r[27]; + k28 = r[28]; k29 = r[29]; k30 = r[30]; k31 = r[31]; + } + + @Override + public void encrypt(byte[] bytes, int off, int len) { + if (len % ALIGN != 0) { + throw DbException.getInternalError("unaligned len " + len); + } + for (int i = off; i < off + len; i += 8) { + encryptBlock(bytes, bytes, i); + } + } + + @Override + public void decrypt(byte[] bytes, int off, int len) { + if (len % ALIGN != 0) { + throw DbException.getInternalError("unaligned len " + len); + } + for (int i = off; i < off + len; i += 8) { + decryptBlock(bytes, bytes, i); + } + } + + private void encryptBlock(byte[] in, byte[] out, int off) { + int y = Bits.readInt(in, off); + int z = Bits.readInt(in, off + 4); + y += (((z << 4) ^ (z >>> 5)) + z) ^ k0; + z += (((y >>> 5) ^ (y << 4)) + y) ^ k1; + y += (((z << 4) ^ (z >>> 5)) + z) ^ k2; + z += (((y >>> 5) ^ (y << 4)) + y) ^ k3; + y += (((z << 4) ^ (z >>> 5)) + z) ^ k4; + z += (((y >>> 5) ^ (y << 4)) + y) ^ k5; + y += (((z << 4) ^ (z >>> 5)) + z) ^ k6; + z += (((y >>> 5) ^ (y << 4)) + y) ^ k7; + y += (((z << 4) ^ (z >>> 5)) + z) ^ k8; + z += (((y >>> 5) ^ (y << 4)) + y) ^ k9; + y += (((z << 4) ^ (z >>> 5)) + z) ^ k10; + z += (((y >>> 5) ^ (y << 4)) + y) ^ k11; + y += (((z << 4) ^ (z >>> 5)) + z) ^ k12; + z += (((y >>> 5) ^ (y << 4)) + y) ^ k13; + y += (((z << 4) ^ (z >>> 5)) + z) ^ k14; + z += (((y >>> 5) ^ (y << 4)) + y) ^ k15; + y += (((z << 4) ^ (z >>> 5)) + z) ^ k16; + z += (((y >>> 5) ^ (y << 4)) + y) ^ k17; + y += (((z << 4) ^ (z >>> 5)) + z) ^ k18; + z += (((y >>> 5) ^ (y << 4)) + y) ^ k19; + y += (((z << 4) ^ (z >>> 5)) + z) ^ k20; + z += (((y >>> 5) ^ (y << 4)) + y) ^ k21; + y += (((z << 4) ^ (z >>> 5)) + z) ^ k22; + z += (((y >>> 5) ^ (y << 4)) + y) ^ k23; + y += (((z << 4) ^ (z >>> 5)) + z) ^ k24; + z += (((y >>> 5) ^ (y << 4)) + y) ^ k25; + y += (((z << 4) ^ (z >>> 5)) + z) ^ k26; + z += (((y >>> 5) ^ (y << 4)) + y) ^ k27; + y += (((z << 4) ^ (z >>> 5)) + z) ^ k28; + z += (((y >>> 5) ^ (y << 4)) + y) ^ k29; + y += (((z << 4) ^ (z >>> 5)) + z) ^ k30; + z += (((y >>> 5) ^ (y << 4)) + y) ^ k31; + Bits.writeInt(out, off, y); + Bits.writeInt(out, off + 4, z); + } + + private void decryptBlock(byte[] in, byte[] out, int off) { + int y = Bits.readInt(in, off); + int z = Bits.readInt(in, off + 4); + z -= (((y >>> 5) ^ (y << 4)) + y) ^ k31; + y -= (((z << 4) ^ (z >>> 5)) + z) ^ k30; + z -= (((y >>> 5) ^ (y << 4)) + y) ^ k29; + y -= (((z << 4) ^ (z >>> 5)) + z) ^ k28; + z -= (((y >>> 5) ^ (y << 4)) + y) ^ k27; + y -= (((z << 4) ^ (z >>> 5)) + z) ^ k26; + z -= (((y >>> 5) ^ (y << 4)) + y) ^ k25; + y -= (((z << 4) ^ (z >>> 5)) + z) ^ k24; + z -= (((y >>> 5) ^ (y << 4)) + y) ^ k23; + y -= (((z << 4) ^ (z >>> 5)) + z) ^ k22; + z -= (((y >>> 5) ^ (y << 4)) + y) ^ k21; + y -= (((z << 4) ^ (z >>> 5)) + z) ^ k20; + z -= (((y >>> 5) ^ (y << 4)) + y) ^ k19; + y -= (((z << 4) ^ (z >>> 5)) + z) ^ k18; + z -= (((y >>> 5) ^ (y << 4)) + y) ^ k17; + y -= (((z << 4) ^ (z >>> 5)) + z) ^ k16; + z -= (((y >>> 5) ^ (y << 4)) + y) ^ k15; + y -= (((z << 4) ^ (z >>> 5)) + z) ^ k14; + z -= (((y >>> 5) ^ (y << 4)) + y) ^ k13; + y -= (((z << 4) ^ (z >>> 5)) + z) ^ k12; + z -= (((y >>> 5) ^ (y << 4)) + y) ^ k11; + y -= (((z << 4) ^ (z >>> 5)) + z) ^ k10; + z -= (((y >>> 5) ^ (y << 4)) + y) ^ k9; + y -= (((z << 4) ^ (z >>> 5)) + z) ^ k8; + z -= (((y >>> 5) ^ (y << 4)) + y) ^ k7; + y -= (((z << 4) ^ (z >>> 5)) + z) ^ k6; + z -= (((y >>> 5) ^ (y << 4)) + y) ^ k5; + y -= (((z << 4) ^ (z >>> 5)) + z) ^ k4; + z -= (((y >>> 5) ^ (y << 4)) + y) ^ k3; + y -= (((z << 4) ^ (z >>> 5)) + z) ^ k2; + z -= (((y >>> 5) ^ (y << 4)) + y) ^ k1; + y -= (((z << 4) ^ (z >>> 5)) + z) ^ k0; + Bits.writeInt(out, off, y); + Bits.writeInt(out, off + 4, z); + } + + @Override + public int getKeyLength() { + return 16; + } + +} diff --git a/h2/src/main/org/h2/security/auth/AuthConfigException.java b/h2/src/main/org/h2/security/auth/AuthConfigException.java new file mode 100644 index 0000000..6135f6d --- /dev/null +++ b/h2/src/main/org/h2/security/auth/AuthConfigException.java @@ -0,0 +1,30 @@ +/* + * 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: Alessandro Ventura + */ +package org.h2.security.auth; + +/** + * Exception thrown when an issue occurs during the authentication configuration + * + */ +public class AuthConfigException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public AuthConfigException() { + super(); + } + + public AuthConfigException(String message) { + super(message); + } + + public AuthConfigException(Throwable cause) { + super(cause); + } + + public AuthConfigException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/h2/src/main/org/h2/security/auth/AuthenticationException.java b/h2/src/main/org/h2/security/auth/AuthenticationException.java new file mode 100644 index 0000000..df054b2 --- /dev/null +++ b/h2/src/main/org/h2/security/auth/AuthenticationException.java @@ -0,0 +1,30 @@ +/* + * 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: Alessandro Ventura + */ +package org.h2.security.auth; + +/** + * Exception thrown in case of errors during authentication + */ +public class AuthenticationException extends Exception { + private static final long serialVersionUID = 1L; + + public AuthenticationException() { + super(); + } + + public AuthenticationException(String message) { + super(message); + } + + public AuthenticationException(Throwable cause) { + super(cause); + } + + public AuthenticationException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/h2/src/main/org/h2/security/auth/AuthenticationInfo.java b/h2/src/main/org/h2/security/auth/AuthenticationInfo.java new file mode 100644 index 0000000..ab9ecfd --- /dev/null +++ b/h2/src/main/org/h2/security/auth/AuthenticationInfo.java @@ -0,0 +1,89 @@ +/* + * 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: Alessandro Ventura + */ +package org.h2.security.auth; + +import org.h2.engine.ConnectionInfo; +import org.h2.util.StringUtils; + +/** + * Input data for authenticators; it wraps ConnectionInfo + */ +public class AuthenticationInfo { + + private ConnectionInfo connectionInfo; + + private String password; + + private String realm; + + /** + * Can be used by authenticator to hold information. + */ + Object nestedIdentity; + + public AuthenticationInfo(ConnectionInfo connectionInfo) { + this.connectionInfo = connectionInfo; + this.realm = connectionInfo.getProperty("AUTHREALM", null); + if (this.realm != null) { + this.realm = StringUtils.toUpperEnglish(this.realm); + } + this.password = connectionInfo.getProperty("AUTHZPWD", null); + } + + public String getUserName() { + return connectionInfo.getUserName(); + } + + public String getRealm() { + return realm; + } + + public String getPassword() { + return password; + } + + public ConnectionInfo getConnectionInfo() { + return connectionInfo; + } + + public String getFullyQualifiedName() { + if (realm == null) { + return connectionInfo.getUserName(); + } else { + return connectionInfo.getUserName() + "@" + realm; + } + } + + /** + * Gets nested identity object that can be used by authenticator to hold information. + * + * @return nested identity object. + */ + public Object getNestedIdentity() { + return nestedIdentity; + } + + /** + * Method used by authenticators to hold information about authenticated + * user + * + * @param nestedIdentity + * = nested identity object + */ + public void setNestedIdentity(Object nestedIdentity) { + this.nestedIdentity = nestedIdentity; + } + + /** + * Clean authentication data. + */ + public void clean() { + this.password = null; + this.nestedIdentity = null; + connectionInfo.cleanAuthenticationInfo(); + } + +} diff --git a/h2/src/main/org/h2/security/auth/Authenticator.java b/h2/src/main/org/h2/security/auth/Authenticator.java new file mode 100644 index 0000000..c5ea0b1 --- /dev/null +++ b/h2/src/main/org/h2/security/auth/Authenticator.java @@ -0,0 +1,34 @@ +/* + * 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: Alessandro Ventura + */ +package org.h2.security.auth; + +import org.h2.engine.Database; +import org.h2.engine.User; + +/** + * Low level interface to implement full authentication process. + */ +public interface Authenticator { + + /** + * Perform user authentication. + * + * @param authenticationInfo authentication info. + * @param database target database instance. + * @return valid database user or null if user doesn't exists in the + * database + * @throws AuthenticationException on failure + */ + User authenticate(AuthenticationInfo authenticationInfo, Database database) throws AuthenticationException; + + /** + * Initialize the authenticator. This method is invoked by databases when + * the authenticator is set when the authenticator is set. + * + * @param database target database + */ + void init(Database database) throws AuthConfigException; +} diff --git a/h2/src/main/org/h2/security/auth/AuthenticatorFactory.java b/h2/src/main/org/h2/security/auth/AuthenticatorFactory.java new file mode 100644 index 0000000..c099ac5 --- /dev/null +++ b/h2/src/main/org/h2/security/auth/AuthenticatorFactory.java @@ -0,0 +1,20 @@ +/* + * 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: Alessandro Ventura + */ +package org.h2.security.auth; + +/** + * Authenticator factory + */ +public class AuthenticatorFactory { + + /** + * Factory method. + * @return authenticator instance. + */ + public static Authenticator createAuthenticator() { + return DefaultAuthenticator.getInstance(); + } +} diff --git a/h2/src/main/org/h2/security/auth/ConfigProperties.java b/h2/src/main/org/h2/security/auth/ConfigProperties.java new file mode 100644 index 0000000..0dc19bf --- /dev/null +++ b/h2/src/main/org/h2/security/auth/ConfigProperties.java @@ -0,0 +1,115 @@ +/* + * 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: Alessandro Ventura + */ +package org.h2.security.auth; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; + +import org.h2.util.Utils; + +/** + * wrapper for configuration properties + */ +public class ConfigProperties { + + private HashMap properties; + + public ConfigProperties() { + properties = new HashMap<>(); + } + + public ConfigProperties(PropertyConfig... configProperties) { + this(configProperties == null ? null : Arrays.asList(configProperties)); + } + + public ConfigProperties(Collection configProperties) { + properties = new HashMap<>(); + if (configProperties != null) { + for (PropertyConfig currentProperty : configProperties) { + if (properties.putIfAbsent(currentProperty.getName(), currentProperty.getValue()) != null) { + throw new AuthConfigException("duplicate property " + currentProperty.getName()); + } + } + } + } + + /** + * Returns the string value of specified property. + * + * @param name property name. + * @param defaultValue default value. + * @return the string property value or {@code defaultValue} if the property is missing. + */ + public String getStringValue(String name, String defaultValue) { + String result = properties.get(name); + if (result == null) { + return defaultValue; + } + return result; + } + + /** + * Returns the string value of specified property. + * + * @param name property name. + * @return the string property value. + * @throws AuthConfigException if the property is missing. + */ + public String getStringValue(String name) { + String result = properties.get(name); + if (result == null) { + throw new AuthConfigException("missing config property " + name); + } + return result; + } + + /** + * Returns the integer value of specified property. + * + * @param name property name. + * @param defaultValue default value. + * @return the integer property value or {@code defaultValue} if the property is missing. + */ + public int getIntValue(String name, int defaultValue) { + String result = properties.get(name); + if (result == null) { + return defaultValue; + } + return Integer.parseInt(result); + } + + /** + * Returns the integer value of specified property. + * + * @param name property name. + * @return the integer property value. + * @throws AuthConfigException if the property is missing. + */ + public int getIntValue(String name) { + String result = properties.get(name); + if (result == null) { + throw new AuthConfigException("missing config property " + name); + } + return Integer.parseInt(result); + } + + /** + * Returns the boolean value of specified property. + * + * @param name property name. + * @param defaultValue default value. + * @return the boolean property value or {@code defaultValue} if the property is missing. + */ + public boolean getBooleanValue(String name, boolean defaultValue) { + String result = properties.get(name); + if (result == null) { + return defaultValue; + } + return Utils.parseBoolean(result, defaultValue, true); + } + +} diff --git a/h2/src/main/org/h2/security/auth/Configurable.java b/h2/src/main/org/h2/security/auth/Configurable.java new file mode 100644 index 0000000..56191e1 --- /dev/null +++ b/h2/src/main/org/h2/security/auth/Configurable.java @@ -0,0 +1,17 @@ +/* + * 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: Alessandro Ventura + */ +package org.h2.security.auth; + +/** + * describe how to perform objects runtime configuration + */ +public interface Configurable { + /** + * configure the component + * @param configProperties = configuration properties + */ + void configure(ConfigProperties configProperties); +} diff --git a/h2/src/main/org/h2/security/auth/DefaultAuthenticator.java b/h2/src/main/org/h2/security/auth/DefaultAuthenticator.java new file mode 100644 index 0000000..052270e --- /dev/null +++ b/h2/src/main/org/h2/security/auth/DefaultAuthenticator.java @@ -0,0 +1,364 @@ +/* + * 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: Alessandro Ventura + */ +package org.h2.security.auth; + +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.xml.parsers.ParserConfigurationException; +import org.h2.api.CredentialsValidator; +import org.h2.api.UserToRolesMapper; +import org.h2.engine.Database; +import org.h2.engine.Right; +import org.h2.engine.Role; +import org.h2.engine.SysProperties; +import org.h2.engine.User; +import org.h2.engine.UserBuilder; +import org.h2.message.Trace; +import org.h2.security.auth.impl.AssignRealmNameRole; +import org.h2.security.auth.impl.JaasCredentialsValidator; +import org.h2.util.StringUtils; +import org.xml.sax.SAXException; + +/** + * Default authenticator implementation. + *

+ * When client connectionInfo contains property AUTHREALM={realName} credentials + * (typically user id and password) are validated by + * {@link org.h2.api.CredentialsValidator} configured for that realm. + *

+ *

+ * When client connectionInfo doesn't contains AUTHREALM property credentials + * are validated internally on the database + *

+ *

+ * Rights assignment can be managed through {@link org.h2.api.UserToRolesMapper} + *

+ *

+ * Default configuration has a realm H2 that validate credentials through JAAS + * api (appName=h2). To customize configuration set h2.authConfigFile system + * property to refer a valid h2auth.xml config file + *

+ */ +public class DefaultAuthenticator implements Authenticator { + + public static final String DEFAULT_REALMNAME = "H2"; + + private Map realms = new HashMap<>(); + private List userToRolesMappers = new ArrayList<>(); + private boolean allowUserRegistration; + private boolean persistUsers; + private boolean createMissingRoles; + private boolean skipDefaultInitialization; + private boolean initialized; + private static DefaultAuthenticator instance; + + protected static final DefaultAuthenticator getInstance() { + if (instance == null) { + instance = new DefaultAuthenticator(); + } + return instance; + } + + /** + * Create the Authenticator with default configurations + */ + public DefaultAuthenticator() { + } + + /** + * Create authenticator and optionally skip the default configuration. This + * option is useful when the authenticator is configured at code level + * + * @param skipDefaultInitialization + * if true default initialization is skipped + */ + public DefaultAuthenticator(boolean skipDefaultInitialization) { + this.skipDefaultInitialization = skipDefaultInitialization; + } + + /** + * If set save users externals defined during the authentication. + * + * @return {@code true} if user will be persisted, + * otherwise returns {@code false} + */ + public boolean isPersistUsers() { + return persistUsers; + } + + /** + * If set to {@code true} saves users externals defined during the authentication. + * + * @param persistUsers {@code true} if user will be persisted, + * otherwise {@code false}. + */ + public void setPersistUsers(boolean persistUsers) { + this.persistUsers = persistUsers; + } + + /** + * If set create external users in the database if not present. + * + * @return {@code true} if creation external user is allowed, + * otherwise returns {@code false} + */ + public boolean isAllowUserRegistration() { + return allowUserRegistration; + } + + /** + * If set to{@code true} creates external users in the database if not present. + * + * @param allowUserRegistration {@code true} if creation external user is allowed, + * otherwise returns {@code false} + */ + public void setAllowUserRegistration(boolean allowUserRegistration) { + this.allowUserRegistration = allowUserRegistration; + } + + /** + * When set create roles not found in the database. If not set roles not + * found in the database are silently skipped. + * + * @return {@code true} if not found roles will be created, + * {@code false} roles are silently skipped. + */ + public boolean isCreateMissingRoles() { + return createMissingRoles; + } + + /** + * Sets the flag that define behavior in case external roles not found in the database. + * + * + * @param createMissingRoles when is {@code true} not found roles are created, + * when is {@code false} roles are silently skipped. + */ + public void setCreateMissingRoles(boolean createMissingRoles) { + this.createMissingRoles = createMissingRoles; + } + + /** + * Add an authentication realm. Realms are case insensitive + * + * @param name + * realm name + * @param credentialsValidator + * credentials validator for realm + */ + public void addRealm(String name, CredentialsValidator credentialsValidator) { + realms.put(StringUtils.toUpperEnglish(name), credentialsValidator); + } + + /** + * UserToRoleMappers assign roles to authenticated users + * + * @return current UserToRoleMappers active + */ + public List getUserToRolesMappers() { + return userToRolesMappers; + } + + public void setUserToRolesMappers(UserToRolesMapper... userToRolesMappers) { + List userToRolesMappersList = new ArrayList<>(); + for (UserToRolesMapper current : userToRolesMappers) { + userToRolesMappersList.add(current); + } + this.userToRolesMappers = userToRolesMappersList; + } + + /** + * Initializes the authenticator. + * + * this method is skipped if skipDefaultInitialization is set Order of + * initialization is + *
    + *
  1. Check h2.authConfigFile system property.
  2. + *
  3. Use the default configuration hard coded
  4. + *
+ * + * @param database where authenticator is initialized + */ + @Override + public void init(Database database) throws AuthConfigException { + if (skipDefaultInitialization) { + return; + } + if (initialized) { + return; + } + synchronized (this) { + if (initialized) { + return; + } + Trace trace = database.getTrace(Trace.DATABASE); + URL h2AuthenticatorConfigurationUrl = null; + try { + String configFile = SysProperties.AUTH_CONFIG_FILE; + if (configFile != null) { + if (trace.isDebugEnabled()) { + trace.debug("DefaultAuthenticator.config: configuration read from system property" + + " h2auth.configurationfile={0}", configFile); + } + h2AuthenticatorConfigurationUrl = new URL(configFile); + } + if (h2AuthenticatorConfigurationUrl == null) { + if (trace.isDebugEnabled()) { + trace.debug("DefaultAuthenticator.config: default configuration"); + } + defaultConfiguration(); + } else { + configureFromUrl(h2AuthenticatorConfigurationUrl); + } + } catch (Exception e) { + trace.error(e, "DefaultAuthenticator.config: an error occurred during configuration from {0} ", + h2AuthenticatorConfigurationUrl); + throw new AuthConfigException( + "Failed to configure authentication from " + h2AuthenticatorConfigurationUrl, e); + } + initialized = true; + } + } + + private void defaultConfiguration() { + createMissingRoles = false; + allowUserRegistration = true; + realms = new HashMap<>(); + CredentialsValidator jaasCredentialsValidator = new JaasCredentialsValidator(); + jaasCredentialsValidator.configure(new ConfigProperties()); + realms.put(DEFAULT_REALMNAME, jaasCredentialsValidator); + UserToRolesMapper assignRealmNameRole = new AssignRealmNameRole(); + assignRealmNameRole.configure(new ConfigProperties()); + userToRolesMappers.add(assignRealmNameRole); + } + + /** + * Configure the authenticator from a configuration file + * + * @param configUrl URL of configuration file + * @throws AuthenticationException on failure + * @throws SAXException on failure + * @throws IOException on failure + * @throws ParserConfigurationException on failure + */ + public void configureFromUrl(URL configUrl) throws AuthenticationException, + SAXException, IOException, ParserConfigurationException { + H2AuthConfig config = H2AuthConfigXml.parseFrom(configUrl); + configureFrom(config); + } + + private void configureFrom(H2AuthConfig config) throws AuthenticationException { + allowUserRegistration = config.isAllowUserRegistration(); + createMissingRoles = config.isCreateMissingRoles(); + HashMap newRealms = new HashMap<>(); + for (RealmConfig currentRealmConfig : config.getRealms()) { + String currentRealmName = currentRealmConfig.getName(); + if (currentRealmName == null) { + throw new AuthenticationException("Missing realm name"); + } + currentRealmName = currentRealmName.toUpperCase(); + CredentialsValidator currentValidator = null; + try { + currentValidator = (CredentialsValidator) Class.forName(currentRealmConfig.getValidatorClass()) + .getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new AuthenticationException("invalid validator class fo realm " + currentRealmName, e); + } + currentValidator.configure(new ConfigProperties(currentRealmConfig.getProperties())); + if (newRealms.putIfAbsent(currentRealmConfig.getName().toUpperCase(), currentValidator) != null) { + throw new AuthenticationException("Duplicate realm " + currentRealmConfig.getName()); + } + } + this.realms = newRealms; + List newUserToRolesMapper = new ArrayList<>(); + for (UserToRolesMapperConfig currentUserToRolesMapperConfig : config.getUserToRolesMappers()) { + UserToRolesMapper currentUserToRolesMapper = null; + try { + currentUserToRolesMapper = (UserToRolesMapper) Class + .forName(currentUserToRolesMapperConfig.getClassName()).getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new AuthenticationException("Invalid class in UserToRolesMapperConfig", e); + } + currentUserToRolesMapper.configure(new ConfigProperties(currentUserToRolesMapperConfig.getProperties())); + newUserToRolesMapper.add(currentUserToRolesMapper); + } + this.userToRolesMappers = newUserToRolesMapper; + } + + private boolean updateRoles(AuthenticationInfo authenticationInfo, User user, Database database) + throws AuthenticationException { + boolean updatedDb = false; + Set roles = new HashSet<>(); + for (UserToRolesMapper currentUserToRolesMapper : userToRolesMappers) { + Collection currentRoles = currentUserToRolesMapper.mapUserToRoles(authenticationInfo); + if (currentRoles != null && !currentRoles.isEmpty()) { + roles.addAll(currentRoles); + } + } + for (String currentRoleName : roles) { + if (currentRoleName == null || currentRoleName.isEmpty()) { + continue; + } + Role currentRole = database.findRole(currentRoleName); + if (currentRole == null && isCreateMissingRoles()) { + synchronized (database.getSystemSession()) { + currentRole = new Role(database, database.allocateObjectId(), currentRoleName, false); + database.addDatabaseObject(database.getSystemSession(), currentRole); + database.getSystemSession().commit(false); + updatedDb = true; + } + } + if (currentRole == null) { + continue; + } + if (user.getRightForRole(currentRole) == null) { + // NON PERSISTENT + Right currentRight = new Right(database, -1, user, currentRole); + currentRight.setTemporary(true); + user.grantRole(currentRole, currentRight); + } + } + return updatedDb; + } + + @Override + public final User authenticate(AuthenticationInfo authenticationInfo, Database database) + throws AuthenticationException { + String userName = authenticationInfo.getFullyQualifiedName(); + User user = database.findUser(userName); + if (user == null && !isAllowUserRegistration()) { + throw new AuthenticationException("User " + userName + " not found in db"); + } + CredentialsValidator validator = realms.get(authenticationInfo.getRealm()); + if (validator == null) { + throw new AuthenticationException("realm " + authenticationInfo.getRealm() + " not configured"); + } + try { + if (!validator.validateCredentials(authenticationInfo)) { + return null; + } + } catch (Exception e) { + throw new AuthenticationException(e); + } + if (user == null) { + synchronized (database.getSystemSession()) { + user = UserBuilder.buildUser(authenticationInfo, database, isPersistUsers()); + database.addDatabaseObject(database.getSystemSession(), user); + database.getSystemSession().commit(false); + } + } + user.revokeTemporaryRightsOnRoles(); + updateRoles(authenticationInfo, user, database); + return user; + } +} diff --git a/h2/src/main/org/h2/security/auth/H2AuthConfig.java b/h2/src/main/org/h2/security/auth/H2AuthConfig.java new file mode 100644 index 0000000..9fe1688 --- /dev/null +++ b/h2/src/main/org/h2/security/auth/H2AuthConfig.java @@ -0,0 +1,98 @@ +/* + * 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: Alessandro Ventura + */ +package org.h2.security.auth; + +import java.util.ArrayList; +import java.util.List; + +/** + * Describe configuration of H2 DefaultAuthenticator. + */ +public class H2AuthConfig { + + private boolean allowUserRegistration=true; + private boolean createMissingRoles=true; + private List realms; + private List userToRolesMappers; + + /** + * Allow user registration flag. If set to {@code true} + * creates external users in the database if not present. + * + * @return {@code true} in case user registration is allowed, + * otherwise returns {@code false}. + */ + public boolean isAllowUserRegistration() { + return allowUserRegistration; + } + + /** + * @param allowUserRegistration Allow user registration flag. + */ + public void setAllowUserRegistration(boolean allowUserRegistration) { + this.allowUserRegistration = allowUserRegistration; + } + + /** + * When set create roles not found in the database. If not set roles not + * found in the database are silently skipped. + * @return {@code true} if the flag is set, otherwise returns {@code false}. + */ + public boolean isCreateMissingRoles() { + return createMissingRoles; + } + + /** + * When set create roles not found in the database. If not set roles not + * found in the database are silently skipped + * @param createMissingRoles missing roles flag. + */ + public void setCreateMissingRoles(boolean createMissingRoles) { + this.createMissingRoles = createMissingRoles; + } + + /** + * Gets configuration of authentication realms. + * + * @return configuration of authentication realms. + */ + public List getRealms() { + if (realms == null) { + realms = new ArrayList<>(); + } + return realms; + } + + /** + * Sets configuration of authentication realms. + * + * @param realms configuration of authentication realms. + */ + public void setRealms(List realms) { + this.realms = realms; + } + + /** + * Gets configuration of the mappers external users to database roles. + * + * @return configuration of the mappers external users to database roles. + */ + public List getUserToRolesMappers() { + if (userToRolesMappers == null) { + userToRolesMappers = new ArrayList<>(); + } + return userToRolesMappers; + } + + /** + * Sets configuration of the mappers external users to database roles. + * + * @param userToRolesMappers configuration of the mappers external users to database roles. + */ + public void setUserToRolesMappers(List userToRolesMappers) { + this.userToRolesMappers = userToRolesMappers; + } +} diff --git a/h2/src/main/org/h2/security/auth/H2AuthConfigXml.java b/h2/src/main/org/h2/security/auth/H2AuthConfigXml.java new file mode 100644 index 0000000..fb1eb16 --- /dev/null +++ b/h2/src/main/org/h2/security/auth/H2AuthConfigXml.java @@ -0,0 +1,144 @@ +/* + * 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: Alessandro Ventura + */ +package org.h2.security.auth; + +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; + +/** + * Parser of external authentication XML configuration file + */ +public class H2AuthConfigXml extends DefaultHandler{ + + private H2AuthConfig result; + private HasConfigProperties lastConfigProperties; + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + switch (qName) { + case "h2Auth": + result = new H2AuthConfig(); + result.setAllowUserRegistration("true".equals( + getAttributeValueOr("allowUserRegistration",attributes,"false"))); + result.setCreateMissingRoles("true".equals( + getAttributeValueOr("createMissingRoles",attributes, "true"))); + break; + case "realm": + RealmConfig realmConfig = new RealmConfig(); + realmConfig.setName(getMandatoryAttributeValue("name", attributes)); + realmConfig.setValidatorClass(getMandatoryAttributeValue("validatorClass", attributes)); + result.getRealms().add(realmConfig); + lastConfigProperties=realmConfig; + break; + case "userToRolesMapper": + UserToRolesMapperConfig userToRolesMapperConfig = new UserToRolesMapperConfig(); + userToRolesMapperConfig.setClassName(getMandatoryAttributeValue("className", attributes)); + result.getUserToRolesMappers().add(userToRolesMapperConfig); + lastConfigProperties=userToRolesMapperConfig; + break; + case "property": + if (lastConfigProperties==null) { + throw new SAXException("property element in the wrong place"); + } + lastConfigProperties.getProperties().add(new PropertyConfig( + getMandatoryAttributeValue("name", attributes), + getMandatoryAttributeValue("value", attributes))); + break; + default: + throw new SAXException("unexpected element "+qName); + } + + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + if (lastConfigProperties!=null && qName.equals("property")==false) { + lastConfigProperties=null; + } + } + + @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("")) { + throw new SAXException("missing attribute "+attributeName); + } + return attributeValue; + + } + + private static String getAttributeValueOr(String attributeName, Attributes attributes, String defaultValue) { + String attributeValue=attributes.getValue(attributeName); + if (attributeValue==null || attributeValue.trim().equals("")) { + return defaultValue; + } + return attributeValue; + } + + /** + * Returns parsed authenticator configuration. + * + * @return Authenticator configuration. + */ + public H2AuthConfig getResult() { + return result; + } + + /** + * Parse the xml. + * + * @param url the source of the xml configuration. + * @return Authenticator configuration. + * @throws ParserConfigurationException if a parser cannot be created. + * @throws SAXException for SAX errors. + * @throws IOException If an I/O error occurs + */ + public static H2AuthConfig parseFrom(URL url) + throws SAXException, IOException, ParserConfigurationException { + try (InputStream inputStream = url.openStream()) { + return parseFrom(inputStream); + } + } + + /** + * Parse the xml. + * + * @param inputStream the source of the xml configuration. + * @return Authenticator configuration. + * @throws ParserConfigurationException if a parser cannot be created. + * @throws SAXException for SAX errors. + * @throws IOException If an I/O error occurs + */ + public static H2AuthConfig parseFrom(InputStream inputStream) + throws SAXException, IOException, ParserConfigurationException { + 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/HasConfigProperties.java b/h2/src/main/org/h2/security/auth/HasConfigProperties.java new file mode 100644 index 0000000..93856bf --- /dev/null +++ b/h2/src/main/org/h2/security/auth/HasConfigProperties.java @@ -0,0 +1,15 @@ +/* + * 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: Alessandro Ventura + */ +package org.h2.security.auth; + +import java.util.List; + +/** + * Interface for objects with configuration properties. + */ +public interface HasConfigProperties { + List getProperties(); +} diff --git a/h2/src/main/org/h2/security/auth/PropertyConfig.java b/h2/src/main/org/h2/security/auth/PropertyConfig.java new file mode 100644 index 0000000..2f049cf --- /dev/null +++ b/h2/src/main/org/h2/security/auth/PropertyConfig.java @@ -0,0 +1,40 @@ +/* + * 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: Alessandro Ventura + */ +package org.h2.security.auth; + +/** + * Configuration property + */ +public class PropertyConfig { + + private String name; + + private String value; + + public PropertyConfig() { + } + + public PropertyConfig(String name, String value) { + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/h2/src/main/org/h2/security/auth/RealmConfig.java b/h2/src/main/org/h2/security/auth/RealmConfig.java new file mode 100644 index 0000000..f020fca --- /dev/null +++ b/h2/src/main/org/h2/security/auth/RealmConfig.java @@ -0,0 +1,64 @@ +/* + * 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: Alessandro Ventura + */ +package org.h2.security.auth; + +import java.util.ArrayList; +import java.util.List; + +/** + * Configuration for authentication realm. + */ +public class RealmConfig implements HasConfigProperties { + + private String name; + private String validatorClass; + private List properties; + + /** + * Gets realm's name. + * + * @return realm's name. + */ + public String getName() { + return name; + } + + /** + * Sets realm's name. + * + * @param name realm's name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Gets validator class name. + * + * @return validator class name. + */ + public String getValidatorClass() { + return validatorClass; + } + + /** + * Sets validator class name. + * + * @param validatorClass validator class name. + */ + public void setValidatorClass(String validatorClass) { + this.validatorClass = validatorClass; + } + + @Override + public List getProperties() { + if (properties == null) { + properties = new ArrayList<>(); + } + return properties; + } + +} diff --git a/h2/src/main/org/h2/security/auth/UserToRolesMapperConfig.java b/h2/src/main/org/h2/security/auth/UserToRolesMapperConfig.java new file mode 100644 index 0000000..16df852 --- /dev/null +++ b/h2/src/main/org/h2/security/auth/UserToRolesMapperConfig.java @@ -0,0 +1,46 @@ +/* + * 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: Alessandro Ventura + */ +package org.h2.security.auth; + +import java.util.ArrayList; +import java.util.List; + +/** + * Configuration for class that maps users to their roles. + * + * @see org.h2.api.UserToRolesMapper + */ +public class UserToRolesMapperConfig implements HasConfigProperties { + + private String className; + private List properties; + + /** + * @return Mapper class name. + */ + public String getClassName() { + return className; + } + + /** + * @param className mapper class name. + */ + public void setClassName(String className) { + this.className = className; + } + + /** + * @return Mapper properties. + */ + @Override + public List getProperties() { + if (properties == null) { + properties = new ArrayList<>(); + } + return properties; + } + +} diff --git a/h2/src/main/org/h2/security/auth/impl/AssignRealmNameRole.java b/h2/src/main/org/h2/security/auth/impl/AssignRealmNameRole.java new file mode 100644 index 0000000..825ce39 --- /dev/null +++ b/h2/src/main/org/h2/security/auth/impl/AssignRealmNameRole.java @@ -0,0 +1,48 @@ +/* + * 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: Alessandro Ventura + */ +package org.h2.security.auth.impl; + +import java.util.Arrays; +import java.util.Collection; + +import org.h2.api.UserToRolesMapper; +import org.h2.security.auth.AuthenticationException; +import org.h2.security.auth.AuthenticationInfo; +import org.h2.security.auth.ConfigProperties; + +/** + * Assign to user a role based on realm name + * + * *

+ * Configuration parameters: + *

+ *
    + *
  • roleNameFormat, optional by default is @{realm}
  • + *
+ */ +public class AssignRealmNameRole implements UserToRolesMapper{ + + private String roleNameFormat; + + public AssignRealmNameRole() { + this("@%s"); + } + + public AssignRealmNameRole(String roleNameFormat) { + this.roleNameFormat = roleNameFormat; + } + + @Override + public void configure(ConfigProperties configProperties) { + roleNameFormat=configProperties.getStringValue("roleNameFormat",roleNameFormat); + } + + @Override + public Collection mapUserToRoles(AuthenticationInfo authenticationInfo) throws AuthenticationException { + return Arrays.asList(String.format(roleNameFormat, authenticationInfo.getRealm())); + } + +} diff --git a/h2/src/main/org/h2/security/auth/impl/JaasCredentialsValidator.java b/h2/src/main/org/h2/security/auth/impl/JaasCredentialsValidator.java new file mode 100644 index 0000000..9b43a30 --- /dev/null +++ b/h2/src/main/org/h2/security/auth/impl/JaasCredentialsValidator.java @@ -0,0 +1,85 @@ +/* + * 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: Alessandro Ventura + */ +package org.h2.security.auth.impl; + +import java.io.IOException; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginContext; + +import org.h2.api.CredentialsValidator; +import org.h2.security.auth.AuthenticationInfo; +import org.h2.security.auth.ConfigProperties; + +/** + * Validate credentials by using standard Java Authentication and Authorization Service + * + *

+ * Configuration parameters: + *

+ *
    + *
  • appName inside the JAAS configuration (by default h2)
  • + *
+ * + */ +public class JaasCredentialsValidator implements CredentialsValidator { + + public static final String DEFAULT_APPNAME="h2"; + + private String appName; + + public JaasCredentialsValidator() { + this(DEFAULT_APPNAME); + } + + /** + * Create the validator with the given name of JAAS configuration + * @param appName = name of JAAS configuration + */ + public JaasCredentialsValidator(String appName) { + this.appName=appName; + } + + @Override + public void configure(ConfigProperties configProperties) { + appName=configProperties.getStringValue("appName",appName); + } + + static class AuthenticationInfoCallbackHandler implements CallbackHandler { + + AuthenticationInfo authenticationInfo; + + AuthenticationInfoCallbackHandler(AuthenticationInfo authenticationInfo) { + this.authenticationInfo = authenticationInfo; + } + + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (int i = 0; i < callbacks.length; i++) { + if (callbacks[i] instanceof NameCallback) { + ((NameCallback) callbacks[i]).setName(authenticationInfo.getUserName()); + } else if (callbacks[i] instanceof PasswordCallback) { + ((PasswordCallback) callbacks[i]).setPassword(authenticationInfo.getPassword().toCharArray()); + } + } + } + + } + + @Override + public boolean validateCredentials(AuthenticationInfo authenticationInfo) throws Exception { + LoginContext loginContext = new LoginContext(appName, + new AuthenticationInfoCallbackHandler(authenticationInfo)); + loginContext.login(); + authenticationInfo.setNestedIdentity(loginContext.getSubject()); + return true; + } + +} diff --git a/h2/src/main/org/h2/security/auth/impl/LdapCredentialsValidator.java b/h2/src/main/org/h2/security/auth/impl/LdapCredentialsValidator.java new file mode 100644 index 0000000..e1e85c8 --- /dev/null +++ b/h2/src/main/org/h2/security/auth/impl/LdapCredentialsValidator.java @@ -0,0 +1,73 @@ +/* + * 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: Alessandro Ventura + */ +package org.h2.security.auth.impl; + +import java.util.Hashtable; + +import javax.naming.Context; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; + +import org.h2.api.CredentialsValidator; +import org.h2.security.auth.AuthenticationInfo; +import org.h2.security.auth.ConfigProperties; + +/** + * Validate credentials by performing an LDAP bind + *

+ * Configuration parameters: + *

+ *
    + *
  • bindDnPattern bind dn pattern with %u instead of username + * (example: uid=%u,ou=users,dc=example,dc=com)
  • + *
  • host ldap server
  • + *
  • port of ldap service; optional, by default 389 for insecure, 636 for secure
  • + *
  • secure, optional by default is true (use SSL)
  • + *
+ */ +public class LdapCredentialsValidator implements CredentialsValidator { + + private String bindDnPattern; + private String host; + private int port; + private boolean secure; + private String url; + + @Override + public void configure(ConfigProperties configProperties) { + bindDnPattern = configProperties.getStringValue("bindDnPattern"); + host = configProperties.getStringValue("host"); + secure = configProperties.getBooleanValue("secure", true); + port = configProperties.getIntValue("port", secure ? 636 : 389); + url = "ldap" + (secure ? "s" : "") + "://" + host + ":" + port; + } + + @Override + public boolean validateCredentials(AuthenticationInfo authenticationInfo) throws Exception { + DirContext dirContext = null; + try { + String dn=bindDnPattern.replace("%u", authenticationInfo.getUserName()); + Hashtable env = new Hashtable<>(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.PROVIDER_URL, url); + env.put(Context.SECURITY_AUTHENTICATION, "simple"); + env.put(Context.SECURITY_PRINCIPAL, dn); + env.put(Context.SECURITY_CREDENTIALS, authenticationInfo.getPassword()); + if (secure) { + env.put(Context.SECURITY_PROTOCOL,"ssl"); + } + dirContext = new InitialDirContext(env); + authenticationInfo.setNestedIdentity(dn); + return true; + } finally { + if (dirContext != null) { + dirContext.close(); + } + } + + } + +} diff --git a/h2/src/main/org/h2/security/auth/impl/StaticRolesMapper.java b/h2/src/main/org/h2/security/auth/impl/StaticRolesMapper.java new file mode 100644 index 0000000..adbed39 --- /dev/null +++ b/h2/src/main/org/h2/security/auth/impl/StaticRolesMapper.java @@ -0,0 +1,51 @@ +/* + * 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: Alessandro Ventura + */ +package org.h2.security.auth.impl; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; + +import org.h2.api.UserToRolesMapper; +import org.h2.security.auth.AuthenticationException; +import org.h2.security.auth.AuthenticationInfo; +import org.h2.security.auth.ConfigProperties; + +/** + * Assign static roles to authenticated users + *

+ * Configuration parameters: + *

+ *
    + *
  • roles role list separated by comma
  • + *
+ * + */ +public class StaticRolesMapper implements UserToRolesMapper { + + private Collection roles; + + public StaticRolesMapper() { + } + + public StaticRolesMapper(String... roles) { + this.roles=Arrays.asList(roles); + } + + @Override + public void configure(ConfigProperties configProperties) { + String rolesString=configProperties.getStringValue("roles", ""); + if (rolesString!=null) { + roles = new HashSet<>(Arrays.asList(rolesString.split(","))); + } + } + + @Override + public Collection mapUserToRoles(AuthenticationInfo authenticationInfo) throws AuthenticationException { + return roles; + } + +} diff --git a/h2/src/main/org/h2/security/auth/impl/StaticUserCredentialsValidator.java b/h2/src/main/org/h2/security/auth/impl/StaticUserCredentialsValidator.java new file mode 100644 index 0000000..6296328 --- /dev/null +++ b/h2/src/main/org/h2/security/auth/impl/StaticUserCredentialsValidator.java @@ -0,0 +1,74 @@ +/* + * 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: Alessandro Ventura + */ +package org.h2.security.auth.impl; + +import java.nio.charset.StandardCharsets; +import java.util.regex.Pattern; + +import org.h2.api.CredentialsValidator; +import org.h2.security.SHA256; +import org.h2.security.auth.AuthenticationException; +import org.h2.security.auth.AuthenticationInfo; +import org.h2.security.auth.ConfigProperties; +import org.h2.util.MathUtils; +import org.h2.util.StringUtils; +import org.h2.util.Utils; + +/** + * This credentials validator matches the user and password with the configured + * Usage should be limited to test purposes + * + */ +public class StaticUserCredentialsValidator implements CredentialsValidator { + + private Pattern userNamePattern; + private String password; + private byte[] salt; + private byte[] hashWithSalt; + + public StaticUserCredentialsValidator() { + } + + public StaticUserCredentialsValidator(String userNamePattern,String password) { + if (userNamePattern!=null) { + this.userNamePattern=Pattern.compile(userNamePattern.toUpperCase()); + } + salt=MathUtils.secureRandomBytes(256); + hashWithSalt=SHA256.getHashWithSalt(password.getBytes(StandardCharsets.UTF_8), salt); + } + + @Override + public boolean validateCredentials(AuthenticationInfo authenticationInfo) throws AuthenticationException { + if (userNamePattern!=null) { + if (!userNamePattern.matcher(authenticationInfo.getUserName()).matches()) { + return false; + } + } + if (password!=null) { + return password.equals(authenticationInfo.getPassword()); + } + return Utils.compareSecure(hashWithSalt, + SHA256.getHashWithSalt(authenticationInfo.getPassword().getBytes(StandardCharsets.UTF_8), salt)); + } + + @Override + public void configure(ConfigProperties configProperties) { + String userNamePatternString=configProperties.getStringValue("userNamePattern",null); + if (userNamePatternString!=null) { + userNamePattern = Pattern.compile(userNamePatternString); + } + password=configProperties.getStringValue("password",password); + String saltString =configProperties.getStringValue("salt",null); + if (saltString!=null) { + salt=StringUtils.convertHexToBytes(saltString); + } + String hashString=configProperties.getStringValue("hash", null); + if (hashString!=null) { + hashWithSalt = SHA256.getHashWithSalt(StringUtils.convertHexToBytes(hashString), salt); + } + } + +} diff --git a/h2/src/main/org/h2/security/auth/impl/package.html b/h2/src/main/org/h2/security/auth/impl/package.html new file mode 100644 index 0000000..429db14 --- /dev/null +++ b/h2/src/main/org/h2/security/auth/impl/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Authentication classes. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/security/auth/package.html b/h2/src/main/org/h2/security/auth/package.html new file mode 100644 index 0000000..429db14 --- /dev/null +++ b/h2/src/main/org/h2/security/auth/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Authentication classes. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/security/package.html b/h2/src/main/org/h2/security/package.html new file mode 100644 index 0000000..44e27d7 --- /dev/null +++ b/h2/src/main/org/h2/security/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Security classes, such as encryption and cryptographically secure hash algorithms. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/server/Service.java b/h2/src/main/org/h2/server/Service.java new file mode 100644 index 0000000..dfcd8b0 --- /dev/null +++ b/h2/src/main/org/h2/server/Service.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.server; + +import java.sql.SQLException; + +/** + * Classes implementing this interface usually provide a + * TCP/IP listener such as an FTP server. + * The can be started and stopped, and may or may not + * allow remote connections. + */ +public interface Service { + + /** + * Initialize the service from command line options. + * + * @param args the command line options + * @throws Exception on failure + */ + void init(String... args) throws Exception; + + /** + * Get the URL of this service in a human readable form + * + * @return the url + */ + String getURL(); + + /** + * Start the service. This usually means create the server socket. + * This method must not block. + * @throws SQLException on failure + */ + void start() throws SQLException; + + /** + * Listen for incoming connections. + * This method blocks. + */ + void listen(); + + /** + * Stop the service. + */ + void stop(); + + /** + * Check if the service is running. + * + * @param traceError if errors should be written + * @return if the server is running + */ + boolean isRunning(boolean traceError); + + /** + * Check if remote connections are allowed. + * + * @return true if remote connections are allowed + */ + boolean getAllowOthers(); + + /** + * Get the human readable name of the service. + * + * @return the name + */ + String getName(); + + /** + * Get the human readable short name of the service. + * + * @return the type + */ + String getType(); + + /** + * Gets the port this service is listening on. + * + * @return the port + */ + int getPort(); + + /** + * Check if a daemon thread should be used. + * + * @return true if a daemon thread should be used + */ + boolean isDaemon(); + +} diff --git a/h2/src/main/org/h2/server/ShutdownHandler.java b/h2/src/main/org/h2/server/ShutdownHandler.java new file mode 100644 index 0000000..49b24d3 --- /dev/null +++ b/h2/src/main/org/h2/server/ShutdownHandler.java @@ -0,0 +1,17 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.server; + +/** + * A shutdown handler is a listener for shutdown events. + */ +public interface ShutdownHandler { + + /** + * Tell the listener to shut down. + */ + void shutdown(); +} diff --git a/h2/src/main/org/h2/server/TcpServer.java b/h2/src/main/org/h2/server/TcpServer.java new file mode 100644 index 0000000..fe90ba4 --- /dev/null +++ b/h2/src/main/org/h2/server/TcpServer.java @@ -0,0 +1,516 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.server; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.jdbc.JdbcConnection; +import org.h2.message.DbException; +import org.h2.util.MathUtils; +import org.h2.util.NetUtils; +import org.h2.util.StringUtils; +import org.h2.util.Tool; +import org.h2.util.Utils10; + +/** + * The TCP server implements the native H2 database server protocol. + * It supports multiple client connections to multiple databases + * (many to many). The same database may be opened by multiple clients. + * Also supported is the mixed mode: opening databases in embedded mode, + * and at the same time start a TCP server to allow clients to connect to + * the same database over the network. + */ +public class TcpServer implements Service { + + private static final int SHUTDOWN_NORMAL = 0; + private static final int SHUTDOWN_FORCE = 1; + + /** + * The name of the in-memory management database used by the TCP server + * to keep the active sessions. + */ + private static final String MANAGEMENT_DB_PREFIX = "management_db_"; + + private static final ConcurrentHashMap SERVERS = new ConcurrentHashMap<>(); + + private int port; + private boolean portIsSet; + private boolean trace; + private boolean ssl; + private boolean stop; + private ShutdownHandler shutdownHandler; + private ServerSocket serverSocket; + private final Set running = + Collections.synchronizedSet(new HashSet()); + private String baseDir; + private boolean allowOthers; + private boolean isDaemon; + private boolean ifExists = true; + private JdbcConnection managementDb; + private PreparedStatement managementDbAdd; + private PreparedStatement managementDbRemove; + private String managementPassword = ""; + private Thread listenerThread; + private int nextThreadId; + private String key, keyDatabase; + + /** + * Get the database name of the management database. + * The management database contains a table with active sessions (SESSIONS). + * + * @param port the TCP server port + * @return the database name (usually starting with mem:) + */ + public static String getManagementDbName(int port) { + return "mem:" + MANAGEMENT_DB_PREFIX + port; + } + + private void initManagementDb() throws SQLException { + if (managementPassword.isEmpty()) { + managementPassword = StringUtils.convertBytesToHex(MathUtils.secureRandomBytes(32)); + } + // avoid using the driver manager + JdbcConnection conn = new JdbcConnection("jdbc:h2:" + getManagementDbName(port), null, "", managementPassword, + false); + managementDb = conn; + + try (Statement stat = conn.createStatement()) { + stat.execute("CREATE ALIAS IF NOT EXISTS STOP_SERVER FOR '" + TcpServer.class.getName() + ".stopServer'"); + stat.execute("CREATE TABLE IF NOT EXISTS SESSIONS" + + "(ID INT PRIMARY KEY, URL VARCHAR, `USER` VARCHAR, " + + "CONNECTED TIMESTAMP(9) WITH TIME ZONE)"); + managementDbAdd = conn.prepareStatement( + "INSERT INTO SESSIONS VALUES(?, ?, ?, CURRENT_TIMESTAMP(9))"); + managementDbRemove = conn.prepareStatement( + "DELETE FROM SESSIONS WHERE ID=?"); + } + SERVERS.put(port, this); + } + + /** + * Shut down this server. + */ + void shutdown() { + if (shutdownHandler != null) { + shutdownHandler.shutdown(); + } + } + + public void setShutdownHandler(ShutdownHandler shutdownHandler) { + this.shutdownHandler = shutdownHandler; + } + + /** + * Add a connection to the management database. + * + * @param id the connection id + * @param url the database URL + * @param user the user name + */ + synchronized void addConnection(int id, String url, String user) { + try { + managementDbAdd.setInt(1, id); + managementDbAdd.setString(2, url); + managementDbAdd.setString(3, user); + managementDbAdd.execute(); + } catch (SQLException e) { + DbException.traceThrowable(e); + } + } + + /** + * Remove a connection from the management database. + * + * @param id the connection id + */ + synchronized void removeConnection(int id) { + try { + managementDbRemove.setInt(1, id); + managementDbRemove.execute(); + } catch (SQLException e) { + DbException.traceThrowable(e); + } + } + + private synchronized void stopManagementDb() { + if (managementDb != null) { + try { + managementDb.close(); + } catch (SQLException e) { + DbException.traceThrowable(e); + } + managementDb = null; + } + } + + @Override + public void init(String... args) { + port = Constants.DEFAULT_TCP_PORT; + for (int i = 0; args != null && i < args.length; i++) { + String a = args[i]; + if (Tool.isOption(a, "-trace")) { + trace = true; + } else if (Tool.isOption(a, "-tcpSSL")) { + ssl = true; + } else if (Tool.isOption(a, "-tcpPort")) { + port = Integer.decode(args[++i]); + portIsSet = true; + } else if (Tool.isOption(a, "-tcpPassword")) { + managementPassword = args[++i]; + } else if (Tool.isOption(a, "-baseDir")) { + baseDir = args[++i]; + } else if (Tool.isOption(a, "-key")) { + key = args[++i]; + keyDatabase = args[++i]; + } else if (Tool.isOption(a, "-tcpAllowOthers")) { + allowOthers = true; + } else if (Tool.isOption(a, "-tcpDaemon")) { + isDaemon = true; + } else if (Tool.isOption(a, "-ifExists")) { + ifExists = true; + } else if (Tool.isOption(a, "-ifNotExists")) { + ifExists = false; + } + } + } + + @Override + public String getURL() { + return (ssl ? "ssl" : "tcp") + "://" + NetUtils.getLocalAddress() + ":" + port; + } + + @Override + public int getPort() { + return port; + } + + /** + * Returns whether a secure protocol is used. + * + * @return {@code true} if SSL socket is used, {@code false} if plain socket + * is used + */ + public boolean getSSL() { + return ssl; + } + + /** + * Check if this socket may connect to this server. Remote connections are + * not allowed if the flag allowOthers is set. + * + * @param socket the socket + * @return true if this client may connect + */ + boolean allow(Socket socket) { + if (allowOthers) { + return true; + } + try { + return NetUtils.isLocalAddress(socket); + } catch (UnknownHostException e) { + traceError(e); + return false; + } + } + + @Override + public synchronized void start() throws SQLException { + stop = false; + try { + serverSocket = NetUtils.createServerSocket(port, ssl); + } catch (DbException e) { + if (!portIsSet) { + serverSocket = NetUtils.createServerSocket(0, ssl); + } else { + throw e; + } + } + port = serverSocket.getLocalPort(); + initManagementDb(); + } + + @Override + public void listen() { + listenerThread = Thread.currentThread(); + String threadName = listenerThread.getName(); + try { + while (!stop) { + Socket s = serverSocket.accept(); + Utils10.setTcpQuickack(s, true); + int id = nextThreadId++; + TcpServerThread c = new TcpServerThread(s, this, id); + running.add(c); + Thread thread = new Thread(c, threadName + " thread-" + id); + thread.setDaemon(isDaemon); + c.setThread(thread); + thread.start(); + } + serverSocket = NetUtils.closeSilently(serverSocket); + } catch (Exception e) { + if (!stop) { + DbException.traceThrowable(e); + } + } + stopManagementDb(); + } + + @Override + public synchronized boolean isRunning(boolean traceError) { + if (serverSocket == null) { + return false; + } + try { + Socket s = NetUtils.createLoopbackSocket(port, ssl); + s.close(); + return true; + } catch (Exception e) { + if (traceError) { + traceError(e); + } + return false; + } + } + + @Override + public void stop() { + // TODO server: share code between web and tcp servers + // need to remove the server first, otherwise the connection is broken + // while the server is still registered in this map + SERVERS.remove(port); + if (!stop) { + stopManagementDb(); + stop = true; + if (serverSocket != null) { + try { + serverSocket.close(); + } catch (IOException e) { + DbException.traceThrowable(e); + } catch (NullPointerException e) { + // ignore + } + serverSocket = null; + } + if (listenerThread != null) { + try { + listenerThread.join(1000); + } catch (InterruptedException e) { + DbException.traceThrowable(e); + } + } + } + // TODO server: using a boolean 'now' argument? a timeout? + for (TcpServerThread c : new ArrayList<>(running)) { + if (c != null) { + c.close(); + try { + c.getThread().join(100); + } catch (Exception e) { + DbException.traceThrowable(e); + } + } + } + } + + /** + * Stop a running server. This method is called via reflection from the + * STOP_SERVER function. + * + * @param port the port where the server runs, or 0 for all running servers + * @param password the password (or null) + * @param shutdownMode the shutdown mode, SHUTDOWN_NORMAL or SHUTDOWN_FORCE. + */ + public static void stopServer(int port, String password, int shutdownMode) { + if (port == 0) { + for (int p : SERVERS.keySet().toArray(new Integer[0])) { + if (p != 0) { + stopServer(p, password, shutdownMode); + } + } + return; + } + TcpServer server = SERVERS.get(port); + if (server == null) { + return; + } + if (!server.managementPassword.equals(password)) { + return; + } + if (shutdownMode == SHUTDOWN_NORMAL) { + server.stopManagementDb(); + server.stop = true; + try { + Socket s = NetUtils.createLoopbackSocket(port, false); + s.close(); + } catch (Exception e) { + // try to connect - so that accept returns + } + } else if (shutdownMode == SHUTDOWN_FORCE) { + server.stop(); + } + server.shutdown(); + } + + /** + * Remove a thread from the list. + * + * @param t the thread to remove + */ + void remove(TcpServerThread t) { + running.remove(t); + } + + /** + * Get the configured base directory. + * + * @return the base directory + */ + String getBaseDir() { + return baseDir; + } + + /** + * Print a message if the trace flag is enabled. + * + * @param s the message + */ + void trace(String s) { + if (trace) { + System.out.println(s); + } + } + /** + * Print a stack trace if the trace flag is enabled. + * + * @param e the exception + */ + void traceError(Throwable e) { + if (trace) { + e.printStackTrace(); + } + } + + @Override + public boolean getAllowOthers() { + return allowOthers; + } + + @Override + public String getType() { + return "TCP"; + } + + @Override + public String getName() { + return "H2 TCP Server"; + } + + boolean getIfExists() { + return ifExists; + } + + /** + * Stop the TCP server with the given URL. + * + * @param url the database URL + * @param password the password + * @param force if the server should be stopped immediately + * @param all whether all TCP servers that are running in the JVM should be + * stopped + * @throws SQLException on failure + */ + public static synchronized void shutdown(String url, String password, + boolean force, boolean all) throws SQLException { + try { + int port = Constants.DEFAULT_TCP_PORT; + int idx = url.lastIndexOf(':'); + if (idx >= 0) { + String p = url.substring(idx + 1); + if (StringUtils.isNumber(p)) { + port = Integer.decode(p); + } + } + String db = getManagementDbName(port); + for (int i = 0; i < 2; i++) { + try (JdbcConnection conn = new JdbcConnection("jdbc:h2:" + url + '/' + db, null, "", password, true)) { + PreparedStatement prep = conn.prepareStatement("CALL STOP_SERVER(?, ?, ?)"); + prep.setInt(1, all ? 0 : port); + prep.setString(2, password); + prep.setInt(3, force ? SHUTDOWN_FORCE : SHUTDOWN_NORMAL); + try { + prep.execute(); + } catch (SQLException e) { + if (force) { + // ignore + } else { + if (e.getErrorCode() != ErrorCode.CONNECTION_BROKEN_1) { + throw e; + } + } + } + break; + } catch (SQLException e) { + if (i == 1) { + throw e; + } + } + } + } catch (Exception e) { + throw DbException.toSQLException(e); + } + } + + /** + * Cancel a running statement. + * + * @param sessionId the session id + * @param statementId the statement id + */ + void cancelStatement(String sessionId, int statementId) { + for (TcpServerThread c : new ArrayList<>(running)) { + if (c != null) { + c.cancelStatement(sessionId, statementId); + } + } + } + + /** + * If no key is set, return the original database name. If a key is set, + * check if the key matches. If yes, return the correct database name. If + * not, throw an exception. + * + * @param db the key to test (or database name if no key is used) + * @return the database name + * @throws DbException if a key is set but doesn't match + */ + public String checkKeyAndGetDatabaseName(String db) { + if (key == null) { + return db; + } + if (key.equals(db)) { + return keyDatabase; + } + throw DbException.get(ErrorCode.WRONG_USER_OR_PASSWORD); + } + + @Override + public boolean isDaemon() { + return isDaemon; + } + +} diff --git a/h2/src/main/org/h2/server/TcpServerThread.java b/h2/src/main/org/h2/server/TcpServerThread.java new file mode 100644 index 0000000..82c210f --- /dev/null +++ b/h2/src/main/org/h2/server/TcpServerThread.java @@ -0,0 +1,675 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.server; + +import java.io.ByteArrayInputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.Socket; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Objects; + +import org.h2.api.ErrorCode; +import org.h2.command.Command; +import org.h2.engine.ConnectionInfo; +import org.h2.engine.Constants; +import org.h2.engine.Engine; +import org.h2.engine.GeneratedKeysMode; +import org.h2.engine.Session; +import org.h2.engine.SessionLocal; +import org.h2.engine.SessionRemote; +import org.h2.engine.SysProperties; +import org.h2.expression.Parameter; +import org.h2.expression.ParameterInterface; +import org.h2.expression.ParameterRemote; +import org.h2.jdbc.JdbcException; +import org.h2.jdbc.meta.DatabaseMetaServer; +import org.h2.message.DbException; +import org.h2.result.ResultColumn; +import org.h2.result.ResultInterface; +import org.h2.result.ResultWithGeneratedKeys; +import org.h2.store.LobStorageInterface; +import org.h2.util.IOUtils; +import org.h2.util.NetUtils; +import org.h2.util.NetworkConnectionInfo; +import org.h2.util.SmallLRUCache; +import org.h2.util.SmallMap; +import org.h2.util.TimeZoneProvider; +import org.h2.value.Transfer; +import org.h2.value.Value; +import org.h2.value.ValueLob; + +/** + * One server thread is opened per client connection. + */ +public class TcpServerThread implements Runnable { + + protected final Transfer transfer; + private final TcpServer server; + private SessionLocal session; + private boolean stop; + private Thread thread; + private Command commit; + private final SmallMap cache = + new SmallMap(SysProperties.SERVER_CACHED_OBJECTS); + private final SmallLRUCache lobs = + SmallLRUCache.newInstance(Math.max( + SysProperties.SERVER_CACHED_OBJECTS, + SysProperties.SERVER_RESULT_SET_FETCH_SIZE * 5)); + private final int threadId; + private int clientVersion; + private String sessionId; + private long lastRemoteSettingsId; + + TcpServerThread(Socket socket, TcpServer server, int id) { + this.server = server; + this.threadId = id; + transfer = new Transfer(null, socket); + } + + private void trace(String s) { + server.trace(this + " " + s); + } + + @Override + public void run() { + try { + transfer.init(); + trace("Connect"); + // TODO server: should support a list of allowed databases + // and a list of allowed clients + try { + Socket socket = transfer.getSocket(); + if (socket == null) { + // the transfer is already closed, prevent NPE in TcpServer#allow(Socket) + return; + } + if (!server.allow(transfer.getSocket())) { + throw DbException.get(ErrorCode.REMOTE_CONNECTION_NOT_ALLOWED); + } + int minClientVersion = transfer.readInt(); + if (minClientVersion < 6) { + throw DbException.get(ErrorCode.DRIVER_VERSION_ERROR_2, + Integer.toString(minClientVersion), "" + Constants.TCP_PROTOCOL_VERSION_MIN_SUPPORTED); + } + int maxClientVersion = transfer.readInt(); + if (maxClientVersion < Constants.TCP_PROTOCOL_VERSION_MIN_SUPPORTED) { + throw DbException.get(ErrorCode.DRIVER_VERSION_ERROR_2, + Integer.toString(maxClientVersion), "" + Constants.TCP_PROTOCOL_VERSION_MIN_SUPPORTED); + } else if (minClientVersion > Constants.TCP_PROTOCOL_VERSION_MAX_SUPPORTED) { + throw DbException.get(ErrorCode.DRIVER_VERSION_ERROR_2, + Integer.toString(minClientVersion), "" + Constants.TCP_PROTOCOL_VERSION_MAX_SUPPORTED); + } + if (maxClientVersion >= Constants.TCP_PROTOCOL_VERSION_MAX_SUPPORTED) { + clientVersion = Constants.TCP_PROTOCOL_VERSION_MAX_SUPPORTED; + } else { + clientVersion = maxClientVersion; + } + transfer.setVersion(clientVersion); + String db = transfer.readString(); + String originalURL = transfer.readString(); + if (db == null && originalURL == null) { + String targetSessionId = transfer.readString(); + int command = transfer.readInt(); + stop = true; + if (command == SessionRemote.SESSION_CANCEL_STATEMENT) { + // cancel a running statement + int statementId = transfer.readInt(); + server.cancelStatement(targetSessionId, statementId); + } else if (command == SessionRemote.SESSION_CHECK_KEY) { + // check if this is the correct server + db = server.checkKeyAndGetDatabaseName(targetSessionId); + if (!targetSessionId.equals(db)) { + transfer.writeInt(SessionRemote.STATUS_OK); + } else { + transfer.writeInt(SessionRemote.STATUS_ERROR); + } + } + } + String baseDir = server.getBaseDir(); + if (baseDir == null) { + baseDir = SysProperties.getBaseDir(); + } + db = server.checkKeyAndGetDatabaseName(db); + ConnectionInfo ci = new ConnectionInfo(db); + ci.setOriginalURL(originalURL); + ci.setUserName(transfer.readString()); + ci.setUserPasswordHash(transfer.readBytes()); + ci.setFilePasswordHash(transfer.readBytes()); + int len = transfer.readInt(); + for (int i = 0; i < len; i++) { + ci.setProperty(transfer.readString(), transfer.readString()); + } + // override client's requested properties with server settings + if (baseDir != null) { + ci.setBaseDir(baseDir); + } + if (server.getIfExists()) { + ci.setProperty("FORBID_CREATION", "TRUE"); + } + transfer.writeInt(SessionRemote.STATUS_OK); + transfer.writeInt(clientVersion); + transfer.flush(); + if (ci.getFilePasswordHash() != null) { + ci.setFileEncryptionKey(transfer.readBytes()); + } + ci.setNetworkConnectionInfo(new NetworkConnectionInfo( + NetUtils.ipToShortForm(new StringBuilder(server.getSSL() ? "ssl://" : "tcp://"), + socket.getLocalAddress().getAddress(), true) // + .append(':').append(socket.getLocalPort()).toString(), // + socket.getInetAddress().getAddress(), socket.getPort(), + new StringBuilder().append('P').append(clientVersion).toString())); + if (clientVersion < Constants.TCP_PROTOCOL_VERSION_20) { + // For DatabaseMetaData + ci.setProperty("OLD_INFORMATION_SCHEMA", "TRUE"); + // For H2 Console + ci.setProperty("NON_KEYWORDS", "VALUE"); + } + session = Engine.createSession(ci); + transfer.setSession(session); + server.addConnection(threadId, originalURL, ci.getUserName()); + trace("Connected"); + lastRemoteSettingsId = session.getDatabase().getRemoteSettingsId(); + } catch (OutOfMemoryError e) { + // catch this separately otherwise such errors will never hit the console + server.traceError(e); + sendError(e, true); + stop = true; + } catch (Throwable e) { + sendError(e,true); + stop = true; + } + while (!stop) { + try { + process(); + } catch (Throwable e) { + sendError(e, true); + } + } + trace("Disconnect"); + } catch (Throwable e) { + server.traceError(e); + } finally { + close(); + } + } + + private void closeSession() { + if (session != null) { + RuntimeException closeError = null; + try { + session.close(); + server.removeConnection(threadId); + } catch (RuntimeException e) { + closeError = e; + server.traceError(e); + } catch (Exception e) { + server.traceError(e); + } finally { + session = null; + } + if (closeError != null) { + throw closeError; + } + } + } + + /** + * Close a connection. + */ + void close() { + try { + stop = true; + closeSession(); + } catch (Exception e) { + server.traceError(e); + } finally { + transfer.close(); + trace("Close"); + server.remove(this); + } + } + + private void sendError(Throwable t, boolean withStatus) { + try { + SQLException e = DbException.convert(t).getSQLException(); + StringWriter writer = new StringWriter(); + e.printStackTrace(new PrintWriter(writer)); + String trace = writer.toString(); + String message; + String sql; + if (e instanceof JdbcException) { + JdbcException j = (JdbcException) e; + message = j.getOriginalMessage(); + sql = j.getSQL(); + } else { + message = e.getMessage(); + sql = null; + } + if (withStatus) { + transfer.writeInt(SessionRemote.STATUS_ERROR); + } + transfer. + writeString(e.getSQLState()).writeString(message). + writeString(sql).writeInt(e.getErrorCode()).writeString(trace).flush(); + } catch (Exception e2) { + if (!transfer.isClosed()) { + server.traceError(e2); + } + // if writing the error does not work, close the connection + stop = true; + } + } + + private void setParameters(Command command) throws IOException { + int len = transfer.readInt(); + ArrayList params = command.getParameters(); + for (int i = 0; i < len; i++) { + Parameter p = (Parameter) params.get(i); + p.setValue(transfer.readValue(null)); + } + } + + private void process() throws IOException { + int operation = transfer.readInt(); + switch (operation) { + case SessionRemote.SESSION_PREPARE: + case SessionRemote.SESSION_PREPARE_READ_PARAMS2: { + int id = transfer.readInt(); + String sql = transfer.readString(); + int old = session.getModificationId(); + Command command = session.prepareLocal(sql); + boolean readonly = command.isReadOnly(); + cache.addObject(id, command); + boolean isQuery = command.isQuery(); + + transfer.writeInt(getState(old)).writeBoolean(isQuery). + writeBoolean(readonly); + + if (operation != SessionRemote.SESSION_PREPARE) { + transfer.writeInt(command.getCommandType()); + } + + ArrayList params = command.getParameters(); + + transfer.writeInt(params.size()); + + if (operation != SessionRemote.SESSION_PREPARE) { + for (ParameterInterface p : params) { + ParameterRemote.writeMetaData(transfer, p); + } + } + transfer.flush(); + break; + } + case SessionRemote.SESSION_CLOSE: { + stop = true; + closeSession(); + transfer.writeInt(SessionRemote.STATUS_OK).flush(); + close(); + break; + } + case SessionRemote.COMMAND_COMMIT: { + if (commit == null) { + commit = session.prepareLocal("COMMIT"); + } + int old = session.getModificationId(); + commit.executeUpdate(null); + transfer.writeInt(getState(old)).flush(); + break; + } + case SessionRemote.COMMAND_GET_META_DATA: { + int id = transfer.readInt(); + int objectId = transfer.readInt(); + Command command = (Command) cache.getObject(id, false); + ResultInterface result = command.getMetaData(); + cache.addObject(objectId, result); + int columnCount = result.getVisibleColumnCount(); + transfer.writeInt(SessionRemote.STATUS_OK). + writeInt(columnCount).writeRowCount(0L); + for (int i = 0; i < columnCount; i++) { + ResultColumn.writeColumn(transfer, result, i); + } + transfer.flush(); + break; + } + case SessionRemote.COMMAND_EXECUTE_QUERY: { + int id = transfer.readInt(); + int objectId = transfer.readInt(); + long maxRows = transfer.readRowCount(); + int fetchSize = transfer.readInt(); + Command command = (Command) cache.getObject(id, false); + setParameters(command); + int old = session.getModificationId(); + ResultInterface result; + synchronized (session) { + result = command.executeQuery(maxRows, false); + } + cache.addObject(objectId, result); + int columnCount = result.getVisibleColumnCount(); + int state = getState(old); + transfer.writeInt(state).writeInt(columnCount); + long rowCount = result.isLazy() ? -1L : result.getRowCount(); + transfer.writeRowCount(rowCount); + for (int i = 0; i < columnCount; i++) { + ResultColumn.writeColumn(transfer, result, i); + } + sendRows(result, rowCount >= 0L ? Math.min(rowCount, fetchSize) : fetchSize); + transfer.flush(); + break; + } + case SessionRemote.COMMAND_EXECUTE_UPDATE: { + int id = transfer.readInt(); + Command command = (Command) cache.getObject(id, false); + setParameters(command); + boolean writeGeneratedKeys = true; + Object generatedKeysRequest; + int mode = transfer.readInt(); + switch (mode) { + case GeneratedKeysMode.NONE: + generatedKeysRequest = false; + writeGeneratedKeys = false; + break; + case GeneratedKeysMode.AUTO: + generatedKeysRequest = true; + break; + case GeneratedKeysMode.COLUMN_NUMBERS: { + int len = transfer.readInt(); + int[] keys = new int[len]; + for (int i = 0; i < len; i++) { + keys[i] = transfer.readInt(); + } + generatedKeysRequest = keys; + break; + } + case GeneratedKeysMode.COLUMN_NAMES: { + int len = transfer.readInt(); + String[] keys = new String[len]; + for (int i = 0; i < len; i++) { + keys[i] = transfer.readString(); + } + generatedKeysRequest = keys; + break; + } + default: + throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, + "Unsupported generated keys' mode " + mode); + } + int old = session.getModificationId(); + ResultWithGeneratedKeys result; + synchronized (session) { + result = command.executeUpdate(generatedKeysRequest); + } + int status; + if (session.isClosed()) { + status = SessionRemote.STATUS_CLOSED; + stop = true; + } else { + status = getState(old); + } + transfer.writeInt(status); + transfer.writeRowCount(result.getUpdateCount()); + transfer.writeBoolean(session.getAutoCommit()); + if (writeGeneratedKeys) { + ResultInterface generatedKeys = result.getGeneratedKeys(); + int columnCount = generatedKeys.getVisibleColumnCount(); + transfer.writeInt(columnCount); + long rowCount = generatedKeys.getRowCount(); + transfer.writeRowCount(rowCount); + for (int i = 0; i < columnCount; i++) { + ResultColumn.writeColumn(transfer, generatedKeys, i); + } + sendRows(generatedKeys, rowCount); + generatedKeys.close(); + } + transfer.flush(); + break; + } + case SessionRemote.COMMAND_CLOSE: { + int id = transfer.readInt(); + Command command = (Command) cache.getObject(id, true); + if (command != null) { + command.close(); + cache.freeObject(id); + } + break; + } + case SessionRemote.RESULT_FETCH_ROWS: { + int id = transfer.readInt(); + int count = transfer.readInt(); + ResultInterface result = (ResultInterface) cache.getObject(id, false); + transfer.writeInt(SessionRemote.STATUS_OK); + sendRows(result, count); + transfer.flush(); + break; + } + case SessionRemote.RESULT_RESET: { + int id = transfer.readInt(); + ResultInterface result = (ResultInterface) cache.getObject(id, false); + result.reset(); + break; + } + case SessionRemote.RESULT_CLOSE: { + int id = transfer.readInt(); + ResultInterface result = (ResultInterface) cache.getObject(id, true); + if (result != null) { + result.close(); + cache.freeObject(id); + } + break; + } + case SessionRemote.CHANGE_ID: { + int oldId = transfer.readInt(); + int newId = transfer.readInt(); + Object obj = cache.getObject(oldId, false); + cache.freeObject(oldId); + cache.addObject(newId, obj); + break; + } + case SessionRemote.SESSION_SET_ID: { + sessionId = transfer.readString(); + if (clientVersion >= Constants.TCP_PROTOCOL_VERSION_20) { + session.setTimeZone(TimeZoneProvider.ofId(transfer.readString())); + } + transfer.writeInt(SessionRemote.STATUS_OK) + .writeBoolean(session.getAutoCommit()) + .flush(); + break; + } + case SessionRemote.SESSION_SET_AUTOCOMMIT: { + boolean autoCommit = transfer.readBoolean(); + session.setAutoCommit(autoCommit); + transfer.writeInt(SessionRemote.STATUS_OK).flush(); + break; + } + case SessionRemote.SESSION_HAS_PENDING_TRANSACTION: { + transfer.writeInt(SessionRemote.STATUS_OK). + writeInt(session.hasPendingTransaction() ? 1 : 0).flush(); + break; + } + case SessionRemote.LOB_READ: { + long lobId = transfer.readLong(); + byte[] hmac = transfer.readBytes(); + long offset = transfer.readLong(); + int length = transfer.readInt(); + transfer.verifyLobMac(hmac, lobId); + CachedInputStream in = lobs.get(lobId); + if (in == null || in.getPos() != offset) { + LobStorageInterface lobStorage = session.getDataHandler().getLobStorage(); + // only the lob id is used + InputStream lobIn = lobStorage.getInputStream(lobId, -1); + in = new CachedInputStream(lobIn); + lobs.put(lobId, in); + lobIn.skip(offset); + } + // limit the buffer size + length = Math.min(16 * Constants.IO_BUFFER_SIZE, length); + byte[] buff = new byte[length]; + length = IOUtils.readFully(in, buff, length); + transfer.writeInt(SessionRemote.STATUS_OK); + transfer.writeInt(length); + transfer.writeBytes(buff, 0, length); + transfer.flush(); + break; + } + case SessionRemote.GET_JDBC_META: { + int code = transfer.readInt(); + int length = transfer.readInt(); + Value[] args = new Value[length]; + for (int i = 0; i < length; i++) { + args[i] = transfer.readValue(null); + } + int old = session.getModificationId(); + ResultInterface result; + synchronized (session) { + result = DatabaseMetaServer.process(session, code, args); + } + int columnCount = result.getVisibleColumnCount(); + int state = getState(old); + transfer.writeInt(state).writeInt(columnCount); + long rowCount = result.getRowCount(); + transfer.writeRowCount(rowCount); + for (int i = 0; i < columnCount; i++) { + ResultColumn.writeColumn(transfer, result, i); + } + sendRows(result, rowCount); + transfer.flush(); + break; + } + default: + trace("Unknown operation: " + operation); + close(); + } + } + + private int getState(int oldModificationId) { + if (session == null) { + return SessionRemote.STATUS_CLOSED; + } + if (session.getModificationId() == oldModificationId) { + long remoteSettingsId = session.getDatabase().getRemoteSettingsId(); + if (lastRemoteSettingsId == remoteSettingsId) { + return SessionRemote.STATUS_OK; + } + lastRemoteSettingsId = remoteSettingsId; + } + return SessionRemote.STATUS_OK_STATE_CHANGED; + } + + private void sendRows(ResultInterface result, long count) throws IOException { + int columnCount = result.getVisibleColumnCount(); + boolean lazy = result.isLazy(); + Session oldSession = lazy ? session.setThreadLocalSession() : null; + try { + while (count-- > 0L) { + boolean hasNext; + try { + hasNext = result.next(); + } catch (Exception e) { + transfer.writeByte((byte) -1); + sendError(e, false); + break; + } + if (hasNext) { + transfer.writeByte((byte) 1); + Value[] values = result.currentRow(); + for (int i = 0; i < columnCount; i++) { + Value v = values[i]; + if (lazy && v instanceof ValueLob) { + ValueLob v2 = ((ValueLob) v).copyToResult(); + if (v2 != v) { + v = session.addTemporaryLob(v2); + } + } + transfer.writeValue(v); + } + } else { + transfer.writeByte((byte) 0); + break; + } + } + } finally { + if (lazy) { + session.resetThreadLocalSession(oldSession); + } + } + } + + void setThread(Thread thread) { + this.thread = thread; + } + + Thread getThread() { + return thread; + } + + /** + * Cancel a running statement. + * + * @param targetSessionId the session id + * @param statementId the statement to cancel + */ + void cancelStatement(String targetSessionId, int statementId) { + if (Objects.equals(targetSessionId, this.sessionId)) { + Command cmd = (Command) cache.getObject(statementId, false); + cmd.cancel(); + } + } + + /** + * An input stream with a position. + */ + static class CachedInputStream extends FilterInputStream { + + private static final ByteArrayInputStream DUMMY = + new ByteArrayInputStream(new byte[0]); + private long pos; + + CachedInputStream(InputStream in) { + super(in == null ? DUMMY : in); + if (in == null) { + pos = -1; + } + } + + @Override + public int read(byte[] buff, int off, int len) throws IOException { + len = super.read(buff, off, len); + if (len > 0) { + pos += len; + } + return len; + } + + @Override + public int read() throws IOException { + int x = in.read(); + if (x >= 0) { + pos++; + } + return x; + } + + @Override + public long skip(long n) throws IOException { + n = super.skip(n); + if (n > 0) { + pos += n; + } + return n; + } + + public long getPos() { + return pos; + } + + } + +} diff --git a/h2/src/main/org/h2/server/package.html b/h2/src/main/org/h2/server/package.html new file mode 100644 index 0000000..05dde64 --- /dev/null +++ b/h2/src/main/org/h2/server/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +A small FTP server. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/server/pg/PgServer.java b/h2/src/main/org/h2/server/pg/PgServer.java new file mode 100644 index 0000000..94a59dd --- /dev/null +++ b/h2/src/main/org/h2/server/pg/PgServer.java @@ -0,0 +1,485 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.server.pg; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import org.h2.api.ErrorCode; +import org.h2.message.DbException; +import org.h2.server.Service; +import org.h2.util.NetUtils; +import org.h2.util.Tool; +import org.h2.util.Utils10; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * This class implements a subset of the PostgreSQL protocol as described here: + * https://www.postgresql.org/docs/devel/protocol.html + * The PostgreSQL catalog is described here: + * https://www.postgresql.org/docs/7.4/catalogs.html + * + * @author Thomas Mueller + * @author Sergi Vladykin 2009-07-03 (convertType) + */ +public class PgServer implements Service { + + /** + * The default port to use for the PG server. + * This value is also in the documentation and in the Server javadoc. + */ + public static final int DEFAULT_PORT = 5435; + + /** + * The VARCHAR type. + */ + public static final int PG_TYPE_VARCHAR = 1043; + + public static final int PG_TYPE_BOOL = 16; + public static final int PG_TYPE_BYTEA = 17; + public static final int PG_TYPE_BPCHAR = 1042; + public static final int PG_TYPE_INT8 = 20; + public static final int PG_TYPE_INT2 = 21; + public static final int PG_TYPE_INT4 = 23; + public static final int PG_TYPE_TEXT = 25; + public static final int PG_TYPE_FLOAT4 = 700; + public static final int PG_TYPE_FLOAT8 = 701; + public static final int PG_TYPE_UNKNOWN = 705; + public static final int PG_TYPE_INT2_ARRAY = 1005; + public static final int PG_TYPE_INT4_ARRAY = 1007; + public static final int PG_TYPE_VARCHAR_ARRAY = 1015; + public static final int PG_TYPE_DATE = 1082; + public static final int PG_TYPE_TIME = 1083; + public static final int PG_TYPE_TIMETZ = 1266; + public static final int PG_TYPE_TIMESTAMP = 1114; + public static final int PG_TYPE_TIMESTAMPTZ = 1184; + public static final int PG_TYPE_NUMERIC = 1700; + + private final HashSet typeSet = new HashSet<>(); + + private int port = PgServer.DEFAULT_PORT; + private boolean portIsSet; + private boolean stop; + private boolean trace; + private ServerSocket serverSocket; + private final Set running = Collections. + synchronizedSet(new HashSet()); + private final AtomicInteger pid = new AtomicInteger(); + private String baseDir; + private boolean allowOthers; + private boolean isDaemon; + private boolean ifExists = true; + private String key, keyDatabase; + + @Override + public void init(String... args) { + port = DEFAULT_PORT; + for (int i = 0; args != null && i < args.length; i++) { + String a = args[i]; + if (Tool.isOption(a, "-trace")) { + trace = true; + } else if (Tool.isOption(a, "-pgPort")) { + port = Integer.decode(args[++i]); + portIsSet = true; + } else if (Tool.isOption(a, "-baseDir")) { + baseDir = args[++i]; + } else if (Tool.isOption(a, "-pgAllowOthers")) { + allowOthers = true; + } else if (Tool.isOption(a, "-pgDaemon")) { + isDaemon = true; + } else if (Tool.isOption(a, "-ifExists")) { + ifExists = true; + } else if (Tool.isOption(a, "-ifNotExists")) { + ifExists = false; + } else if (Tool.isOption(a, "-key")) { + key = args[++i]; + keyDatabase = args[++i]; + } + } + // int testing; + // trace = true; + } + + boolean getTrace() { + return trace; + } + + /** + * Print a message if the trace flag is enabled. + * + * @param s the message + */ + void trace(String s) { + if (trace) { + System.out.println(s); + } + } + + /** + * Remove a thread from the list. + * + * @param t the thread to remove + */ + synchronized void remove(PgServerThread t) { + running.remove(t); + } + + /** + * Print the stack trace if the trace flag is enabled. + * + * @param e the exception + */ + void traceError(Exception e) { + if (trace) { + e.printStackTrace(); + } + } + + @Override + public String getURL() { + return "pg://" + NetUtils.getLocalAddress() + ":" + port; + } + + @Override + public int getPort() { + return port; + } + + private boolean allow(Socket socket) { + if (allowOthers) { + return true; + } + try { + return NetUtils.isLocalAddress(socket); + } catch (UnknownHostException e) { + traceError(e); + return false; + } + } + + @Override + public void start() { + stop = false; + try { + serverSocket = NetUtils.createServerSocket(port, false); + } catch (DbException e) { + if (!portIsSet) { + serverSocket = NetUtils.createServerSocket(0, false); + } else { + throw e; + } + } + port = serverSocket.getLocalPort(); + } + + @Override + public void listen() { + String threadName = Thread.currentThread().getName(); + try { + while (!stop) { + Socket s = serverSocket.accept(); + if (!allow(s)) { + trace("Connection not allowed"); + s.close(); + } else { + Utils10.setTcpQuickack(s, true); + PgServerThread c = new PgServerThread(s, this); + running.add(c); + int id = pid.incrementAndGet(); + c.setProcessId(id); + Thread thread = new Thread(c, threadName + " thread-" + id); + thread.setDaemon(isDaemon); + c.setThread(thread); + thread.start(); + } + } + } catch (Exception e) { + if (!stop) { + e.printStackTrace(); + } + } + } + + @Override + public void stop() { + // TODO server: combine with tcp server + if (!stop) { + stop = true; + if (serverSocket != null) { + try { + serverSocket.close(); + } catch (IOException e) { + // TODO log exception + e.printStackTrace(); + } + serverSocket = null; + } + } + // TODO server: using a boolean 'now' argument? a timeout? + for (PgServerThread c : new ArrayList<>(running)) { + c.close(); + try { + Thread t = c.getThread(); + if (t != null) { + t.join(100); + } + } catch (Exception e) { + // TODO log exception + e.printStackTrace(); + } + } + } + + @Override + public boolean isRunning(boolean traceError) { + if (serverSocket == null) { + return false; + } + try { + Socket s = NetUtils.createLoopbackSocket(serverSocket.getLocalPort(), false); + s.close(); + return true; + } catch (Exception e) { + if (traceError) { + traceError(e); + } + return false; + } + } + + /** + * Get the thread with the given process id. + * + * @param processId the process id + * @return the thread + */ + PgServerThread getThread(int processId) { + for (PgServerThread c : new ArrayList<>(running)) { + if (c.getProcessId() == processId) { + return c; + } + } + return null; + } + + String getBaseDir() { + return baseDir; + } + + @Override + public boolean getAllowOthers() { + return allowOthers; + } + + @Override + public String getType() { + return "PG"; + } + + @Override + public String getName() { + return "H2 PG Server"; + } + + boolean getIfExists() { + return ifExists; + } + + /** + * Returns the name of the given type. + * + * @param pgType the PostgreSQL type oid + * @return the name of the given type + */ + public static String formatType(int pgType) { + int valueType; + switch (pgType) { + case 0: + return "-"; + case PG_TYPE_BOOL: + valueType = Value.BOOLEAN; + break; + case PG_TYPE_BYTEA: + valueType = Value.VARBINARY; + break; + case 18: + return "char"; + case 19: + return "name"; + case PG_TYPE_INT8: + valueType = Value.BIGINT; + break; + case PG_TYPE_INT2: + valueType = Value.SMALLINT; + break; + case 22: + return "int2vector"; + case PG_TYPE_INT4: + valueType = Value.INTEGER; + break; + case 24: + return "regproc"; + case PG_TYPE_TEXT: + valueType = Value.CLOB; + break; + case PG_TYPE_FLOAT4: + valueType = Value.REAL; + break; + case PG_TYPE_FLOAT8: + valueType = Value.DOUBLE; + break; + case PG_TYPE_INT2_ARRAY: + return "smallint[]"; + case PG_TYPE_INT4_ARRAY: + return "integer[]"; + case PG_TYPE_VARCHAR_ARRAY: + return "character varying[]"; + case PG_TYPE_BPCHAR: + valueType = Value.CHAR; + break; + case PG_TYPE_VARCHAR: + valueType = Value.VARCHAR; + break; + case PG_TYPE_DATE: + valueType = Value.DATE; + break; + case PG_TYPE_TIME: + valueType = Value.TIME; + break; + case PG_TYPE_TIMETZ: + valueType = Value.TIME_TZ; + break; + case PG_TYPE_TIMESTAMP: + valueType = Value.TIMESTAMP; + break; + case PG_TYPE_TIMESTAMPTZ: + valueType = Value.TIMESTAMP_TZ; + break; + case PG_TYPE_NUMERIC: + valueType = Value.NUMERIC; + break; + case 2205: + return "regclass"; + default: + return "???"; + } + return Value.getTypeName(valueType); + } + + /** + * Convert the SQL type to a PostgreSQL type + * + * @param type the SQL type + * @return the PostgreSQL type + */ + public static int convertType(TypeInfo type) { + switch (type.getValueType()) { + case Value.BOOLEAN: + return PG_TYPE_BOOL; + case Value.VARCHAR: + return PG_TYPE_VARCHAR; + case Value.NULL: + case Value.CLOB: + return PG_TYPE_TEXT; + case Value.CHAR: + return PG_TYPE_BPCHAR; + case Value.SMALLINT: + return PG_TYPE_INT2; + case Value.INTEGER: + return PG_TYPE_INT4; + case Value.BIGINT: + return PG_TYPE_INT8; + case Value.NUMERIC: + case Value.DECFLOAT: + return PG_TYPE_NUMERIC; + case Value.REAL: + return PG_TYPE_FLOAT4; + case Value.DOUBLE: + return PG_TYPE_FLOAT8; + case Value.TIME: + return PG_TYPE_TIME; + case Value.TIME_TZ: + return PG_TYPE_TIMETZ; + case Value.DATE: + return PG_TYPE_DATE; + case Value.TIMESTAMP: + return PG_TYPE_TIMESTAMP; + case Value.TIMESTAMP_TZ: + return PG_TYPE_TIMESTAMPTZ; + case Value.BINARY: + case Value.VARBINARY: + return PG_TYPE_BYTEA; + case Value.ARRAY: { + type = (TypeInfo) type.getExtTypeInfo(); + switch (type.getValueType()) { + case Value.SMALLINT: + return PG_TYPE_INT2_ARRAY; + case Value.INTEGER: + return PG_TYPE_INT4_ARRAY; + case Value.VARCHAR: + return PG_TYPE_VARCHAR_ARRAY; + default: + return PG_TYPE_VARCHAR_ARRAY; + } + } + default: + return PG_TYPE_UNKNOWN; + } + } + + /** + * Get the type hash set. + * + * @return the type set + */ + HashSet getTypeSet() { + return typeSet; + } + + /** + * Check whether a data type is supported. + * A warning is logged if not. + * + * @param type the type + */ + void checkType(int type) { + if (!typeSet.contains(type)) { + trace("Unsupported type: " + type); + } + } + + /** + * If no key is set, return the original database name. If a key is set, + * check if the key matches. If yes, return the correct database name. If + * not, throw an exception. + * + * @param db the key to test (or database name if no key is used) + * @return the database name + * @throws DbException if a key is set but doesn't match + */ + public String checkKeyAndGetDatabaseName(String db) { + if (key == null) { + return db; + } + if (key.equals(db)) { + return keyDatabase; + } + throw DbException.get(ErrorCode.WRONG_USER_OR_PASSWORD); + } + + @Override + public boolean isDaemon() { + return isDaemon; + } + +} diff --git a/h2/src/main/org/h2/server/pg/PgServerThread.java b/h2/src/main/org/h2/server/pg/PgServerThread.java new file mode 100644 index 0000000..acff786 --- /dev/null +++ b/h2/src/main/org/h2/server/pg/PgServerThread.java @@ -0,0 +1,1386 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.server.pg; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.StringReader; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.Socket; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Properties; +import java.util.regex.Pattern; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandInterface; +import org.h2.engine.ConnectionInfo; +import org.h2.engine.Constants; +import org.h2.engine.Database; +import org.h2.engine.Engine; +import org.h2.engine.SessionLocal; +import org.h2.engine.SysProperties; +import org.h2.expression.ParameterInterface; +import org.h2.message.DbException; +import org.h2.result.ResultInterface; +import org.h2.schema.Schema; +import org.h2.table.Column; +import org.h2.table.Table; +import org.h2.util.DateTimeUtils; +import org.h2.util.MathUtils; +import org.h2.util.NetUtils; +import org.h2.util.NetworkConnectionInfo; +import org.h2.util.ScriptReader; +import org.h2.util.StringUtils; +import org.h2.util.TimeZoneProvider; +import org.h2.util.Utils; +import org.h2.util.Utils10; +import org.h2.value.CaseInsensitiveMap; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +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; +import org.h2.value.ValueTimeTimeZone; +import org.h2.value.ValueTimestamp; +import org.h2.value.ValueTimestampTimeZone; +import org.h2.value.ValueVarbinary; +import org.h2.value.ValueVarchar; + +/** + * One server thread is opened for each client. + */ +public final class PgServerThread implements Runnable { + + private static final boolean INTEGER_DATE_TYPES = false; + + private static final Pattern SHOULD_QUOTE = Pattern.compile(".*[\",\\\\{}].*"); + + private static String pgTimeZone(String value) { + if (value.startsWith("GMT+")) { + return convertTimeZone(value, "GMT-"); + } else if (value.startsWith("GMT-")) { + return convertTimeZone(value, "GMT+"); + } else if (value.startsWith("UTC+")) { + return convertTimeZone(value, "UTC-"); + } else if (value.startsWith("UTC-")) { + return convertTimeZone(value, "UTC+"); + } else { + return value; + } + } + + private static String convertTimeZone(String value, String prefix) { + int length = value.length(); + return new StringBuilder(length).append(prefix).append(value, 4, length).toString(); + } + + private final PgServer server; + private Socket socket; + private SessionLocal session; + private boolean stop; + private DataInputStream dataInRaw; + private DataInputStream dataIn; + private OutputStream out; + private int messageType; + private ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); + private DataOutputStream dataOut; + private Thread thread; + private boolean initDone; + private String userName; + private String databaseName; + private int processId; + private final int secret; + private CommandInterface activeRequest; + private String clientEncoding = SysProperties.PG_DEFAULT_CLIENT_ENCODING; + private String dateStyle = "ISO, MDY"; + private TimeZoneProvider timeZone = DateTimeUtils.getTimeZone(); + private final HashMap prepared = + new CaseInsensitiveMap<>(); + private final HashMap portals = + new CaseInsensitiveMap<>(); + + PgServerThread(Socket socket, PgServer server) { + this.server = server; + this.socket = socket; + this.secret = (int) MathUtils.secureRandomLong(); + } + + @Override + public void run() { + try { + server.trace("Connect"); + InputStream ins = socket.getInputStream(); + out = socket.getOutputStream(); + dataInRaw = new DataInputStream(ins); + while (!stop) { + process(); + out.flush(); + } + } catch (EOFException e) { + // more or less normal disconnect + } catch (Exception e) { + server.traceError(e); + } finally { + server.trace("Disconnect"); + close(); + } + } + + private String readString() throws IOException { + ByteArrayOutputStream buff = new ByteArrayOutputStream(); + while (true) { + int x = dataIn.read(); + if (x <= 0) { + break; + } + buff.write(x); + } + return Utils10.byteArrayOutputStreamToString(buff, getEncoding()); + } + + private int readInt() throws IOException { + return dataIn.readInt(); + } + + private short readShort() throws IOException { + return dataIn.readShort(); + } + + private byte readByte() throws IOException { + return dataIn.readByte(); + } + + private void readFully(byte[] buff) throws IOException { + dataIn.readFully(buff); + } + + private void process() throws IOException { + int x; + if (initDone) { + x = dataInRaw.read(); + if (x < 0) { + stop = true; + return; + } + } else { + x = 0; + } + int len = dataInRaw.readInt(); + len -= 4; + byte[] data = Utils.newBytes(len); + dataInRaw.readFully(data, 0, len); + dataIn = new DataInputStream(new ByteArrayInputStream(data, 0, len)); + switch (x) { + case 0: + server.trace("Init"); + int version = readInt(); + if (version == 80877102) { + server.trace("CancelRequest"); + int pid = readInt(); + int key = readInt(); + PgServerThread c = server.getThread(pid); + if (c != null && key == c.secret) { + c.cancelRequest(); + } else { + // According to the PostgreSQL documentation, when canceling + // a request, if an invalid secret is provided then no + // exception should be sent back to the client. + server.trace("Invalid CancelRequest: pid=" + pid + ", key=" + key); + } + close(); + } else if (version == 80877103) { + server.trace("SSLRequest"); + out.write('N'); + } else { + server.trace("StartupMessage"); + server.trace(" version " + version + + " (" + (version >> 16) + "." + (version & 0xff) + ")"); + while (true) { + String param = readString(); + if (param.isEmpty()) { + break; + } + String value = readString(); + switch (param) { + case "user": + this.userName = value; + break; + case "database": + this.databaseName = server.checkKeyAndGetDatabaseName(value); + break; + case "client_encoding": + // node-postgres will send "'utf-8'" + int length = value.length(); + if (length >= 2 && value.charAt(0) == '\'' + && value.charAt(length - 1) == '\'') { + value = value.substring(1, length - 1); + } + // UTF8 + clientEncoding = value; + break; + case "DateStyle": + if (value.indexOf(',') < 0) { + value += ", MDY"; + } + dateStyle = value; + break; + case "TimeZone": + try { + timeZone = TimeZoneProvider.ofId(pgTimeZone(value)); + } catch (Exception e) { + server.trace("Unknown TimeZone: " + value); + } + break; + } + // extra_float_digits 2 + // geqo on (Genetic Query Optimization) + server.trace(" param " + param + "=" + value); + } + sendAuthenticationCleartextPassword(); + initDone = true; + } + break; + case 'p': { + server.trace("PasswordMessage"); + String password = readString(); + try { + Properties info = new Properties(); + info.put("MODE", "PostgreSQL"); + info.put("DATABASE_TO_LOWER", "TRUE"); + info.put("DEFAULT_NULL_ORDERING", "HIGH"); + String url = "jdbc:h2:" + databaseName; + ConnectionInfo ci = new ConnectionInfo(url, info, userName, password); + String baseDir = server.getBaseDir(); + if (baseDir == null) { + baseDir = SysProperties.getBaseDir(); + } + if (baseDir != null) { + ci.setBaseDir(baseDir); + } + if (server.getIfExists()) { + ci.setProperty("FORBID_CREATION", "TRUE"); + } + ci.setNetworkConnectionInfo(new NetworkConnectionInfo( // + NetUtils.ipToShortForm(new StringBuilder("pg://"), // + socket.getLocalAddress().getAddress(), true) // + .append(':').append(socket.getLocalPort()).toString(), // + socket.getInetAddress().getAddress(), socket.getPort(), null)); + session = Engine.createSession(ci); + initDb(); + sendAuthenticationOk(); + } catch (Exception e) { + e.printStackTrace(); + stop = true; + } + break; + } + case 'P': { + server.trace("Parse"); + Prepared p = new Prepared(); + p.name = readString(); + p.sql = getSQL(readString()); + int paramTypesCount = readShort(); + int[] paramTypes = null; + if (paramTypesCount > 0) { + paramTypes = new int[paramTypesCount]; + for (int i = 0; i < paramTypesCount; i++) { + paramTypes[i] = readInt(); + } + } + try { + p.prep = session.prepareLocal(p.sql); + ArrayList parameters = p.prep.getParameters(); + int count = parameters.size(); + p.paramType = new int[count]; + for (int i = 0; i < count; i++) { + int type; + if (i < paramTypesCount && paramTypes[i] != 0) { + type = paramTypes[i]; + server.checkType(type); + } else { + type = PgServer.convertType(parameters.get(i).getType()); + } + p.paramType[i] = type; + } + prepared.put(p.name, p); + sendParseComplete(); + } catch (Exception e) { + sendErrorResponse(e); + } + break; + } + case 'B': { + server.trace("Bind"); + Portal portal = new Portal(); + portal.name = readString(); + String prepName = readString(); + Prepared prep = prepared.get(prepName); + if (prep == null) { + sendErrorResponse("Prepared not found"); + break; + } + portal.prep = prep; + portals.put(portal.name, portal); + int formatCodeCount = readShort(); + int[] formatCodes = new int[formatCodeCount]; + for (int i = 0; i < formatCodeCount; i++) { + formatCodes[i] = readShort(); + } + int paramCount = readShort(); + try { + ArrayList parameters = prep.prep.getParameters(); + for (int i = 0; i < paramCount; i++) { + setParameter(parameters, prep.paramType[i], i, formatCodes); + } + } catch (Exception e) { + sendErrorResponse(e); + break; + } + int resultCodeCount = readShort(); + portal.resultColumnFormat = new int[resultCodeCount]; + for (int i = 0; i < resultCodeCount; i++) { + portal.resultColumnFormat[i] = readShort(); + } + sendBindComplete(); + break; + } + case 'C': { + char type = (char) readByte(); + String name = readString(); + server.trace("Close"); + if (type == 'S') { + Prepared p = prepared.remove(name); + if (p != null) { + p.close(); + } + } else if (type == 'P') { + Portal p = portals.remove(name); + if (p != null) { + p.prep.closeResult(); + } + } else { + server.trace("expected S or P, got " + type); + sendErrorResponse("expected S or P"); + break; + } + sendCloseComplete(); + break; + } + case 'D': { + char type = (char) readByte(); + String name = readString(); + server.trace("Describe"); + if (type == 'S') { + Prepared p = prepared.get(name); + if (p == null) { + sendErrorResponse("Prepared not found: " + name); + } else { + try { + sendParameterDescription(p.prep.getParameters(), p.paramType); + sendRowDescription(p.prep.getMetaData(), null); + } catch (Exception e) { + sendErrorResponse(e); + } + } + } else if (type == 'P') { + Portal p = portals.get(name); + if (p == null) { + sendErrorResponse("Portal not found: " + name); + } else { + CommandInterface prep = p.prep.prep; + try { + sendRowDescription(prep.getMetaData(), p.resultColumnFormat); + } catch (Exception e) { + sendErrorResponse(e); + } + } + } else { + server.trace("expected S or P, got " + type); + sendErrorResponse("expected S or P"); + } + break; + } + case 'E': { + String name = readString(); + server.trace("Execute"); + Portal p = portals.get(name); + if (p == null) { + sendErrorResponse("Portal not found: " + name); + break; + } + int maxRows = readInt(); + Prepared prepared = p.prep; + CommandInterface prep = prepared.prep; + server.trace(prepared.sql); + try { + setActiveRequest(prep); + if (prep.isQuery()) { + executeQuery(prepared, prep, p.resultColumnFormat, maxRows); + } else { + sendCommandComplete(prep, prep.executeUpdate(null).getUpdateCount()); + } + } catch (Exception e) { + sendErrorOrCancelResponse(e); + } finally { + setActiveRequest(null); + } + break; + } + case 'S': { + server.trace("Sync"); + sendReadyForQuery(); + break; + } + case 'Q': { + server.trace("Query"); + String query = readString(); + @SuppressWarnings("resource") + ScriptReader reader = new ScriptReader(new StringReader(query)); + while (true) { + String s = reader.readStatement(); + if (s == null) { + break; + } + s = getSQL(s); + try (CommandInterface command = session.prepareLocal(s)) { + setActiveRequest(command); + if (command.isQuery()) { + try (ResultInterface result = command.executeQuery(0, false)) { + sendRowDescription(result, null); + while (result.next()) { + sendDataRow(result, null); + } + sendCommandComplete(command, 0); + } + } else { + sendCommandComplete(command, command.executeUpdate(null).getUpdateCount()); + } + } catch (Exception e) { + sendErrorOrCancelResponse(e); + break; + } finally { + setActiveRequest(null); + } + } + sendReadyForQuery(); + break; + } + case 'X': { + server.trace("Terminate"); + close(); + break; + } + default: + server.trace("Unsupported: " + x + " (" + (char) x + ")"); + break; + } + } + + private void executeQuery(Prepared prepared, CommandInterface prep, int[] resultColumnFormat, int maxRows) + throws Exception { + ResultInterface result = prepared.result; + if (result == null) { + result = prep.executeQuery(0L, false); + } + try { + // the meta-data is sent in the prior 'Describe' + if (maxRows == 0) { + while (result.next()) { + sendDataRow(result, resultColumnFormat); + } + } else { + for (; maxRows > 0 && result.next(); maxRows--) { + sendDataRow(result, resultColumnFormat); + } + if (result.hasNext()) { + prepared.result = result; + sendCommandSuspended(); + return; + } + } + prepared.closeResult(); + sendCommandComplete(prep, 0); + } catch (Exception e) { + prepared.closeResult(); + throw e; + } + } + + private String getSQL(String s) { + String lower = StringUtils.toLowerEnglish(s); + if (lower.startsWith("show max_identifier_length")) { + s = "CALL 63"; + } else if (lower.startsWith("set client_encoding to")) { + s = "set DATESTYLE ISO"; + } + // s = StringUtils.replaceAll(s, "i.indkey[ia.attnum-1]", "0"); + if (server.getTrace()) { + server.trace(s + ";"); + } + return s; + } + + private void sendCommandComplete(CommandInterface command, long updateCount) throws IOException { + startMessage('C'); + switch (command.getCommandType()) { + case CommandInterface.INSERT: + writeStringPart("INSERT 0 "); + writeString(Long.toString(updateCount)); + break; + case CommandInterface.UPDATE: + writeStringPart("UPDATE "); + writeString(Long.toString(updateCount)); + break; + case CommandInterface.DELETE: + writeStringPart("DELETE "); + writeString(Long.toString(updateCount)); + break; + case CommandInterface.SELECT: + case CommandInterface.CALL: + writeString("SELECT"); + break; + case CommandInterface.BEGIN: + writeString("BEGIN"); + break; + default: + server.trace("check CommandComplete tag for command " + command); + writeStringPart("UPDATE "); + writeString(Long.toString(updateCount)); + } + sendMessage(); + } + + private void sendCommandSuspended() throws IOException { + startMessage('s'); + sendMessage(); + } + + private void sendDataRow(ResultInterface result, int[] formatCodes) throws IOException { + int columns = result.getVisibleColumnCount(); + startMessage('D'); + writeShort(columns); + Value[] row = result.currentRow(); + for (int i = 0; i < columns; i++) { + int pgType = PgServer.convertType(result.getColumnType(i)); + boolean text = formatAsText(pgType, formatCodes, i); + writeDataColumn(row[i], pgType, text); + } + sendMessage(); + } + + private static long toPostgreDays(long dateValue) { + return DateTimeUtils.absoluteDayFromDateValue(dateValue) - 10_957; + } + + private void writeDataColumn(Value v, int pgType, boolean text) throws IOException { + if (v == ValueNull.INSTANCE) { + writeInt(-1); + return; + } + if (text) { + // plain text + switch (pgType) { + case PgServer.PG_TYPE_BOOL: + writeInt(1); + dataOut.writeByte(v.getBoolean() ? 't' : 'f'); + break; + case PgServer.PG_TYPE_BYTEA: { + byte[] bytes = v.getBytesNoCopy(); + int length = bytes.length; + int cnt = length; + for (int i = 0; i < length; i++) { + byte b = bytes[i]; + if (b < 32 || b > 126) { + cnt += 3; + } else if (b == 92) { + cnt++; + } + } + byte[] data = new byte[cnt]; + for (int i = 0, j = 0; i < length; i++) { + byte b = bytes[i]; + if (b < 32 || b > 126) { + data[j++] = '\\'; + data[j++] = (byte) (((b >>> 6) & 3) + '0'); + data[j++] = (byte) (((b >>> 3) & 7) + '0'); + data[j++] = (byte) ((b & 7) + '0'); + } else if (b == 92) { + data[j++] = '\\'; + data[j++] = '\\'; + } else { + data[j++] = b; + } + } + writeInt(data.length); + write(data); + break; + } + case PgServer.PG_TYPE_INT2_ARRAY: + case PgServer.PG_TYPE_INT4_ARRAY: + case PgServer.PG_TYPE_VARCHAR_ARRAY: + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write('{'); + Value[] values = ((ValueArray) v).getList(); + Charset encoding = getEncoding(); + for (int i = 0; i < values.length; i++) { + if (i > 0) { + baos.write(','); + } + String s = values[i].getString(); + if (SHOULD_QUOTE.matcher(s).matches()) { + List ss = new ArrayList<>(); + for (String s0 : s.split("\\\\")) { + ss.add(s0.replace("\"", "\\\"")); + } + s = "\"" + String.join("\\\\", ss) + "\""; + } + baos.write(s.getBytes(encoding)); + } + baos.write('}'); + writeInt(baos.size()); + write(baos); + break; + default: + byte[] data = v.getString().getBytes(getEncoding()); + writeInt(data.length); + write(data); + } + } else { + // binary + switch (pgType) { + case PgServer.PG_TYPE_BOOL: + writeInt(1); + dataOut.writeByte(v.getBoolean() ? 1 : 0); + break; + case PgServer.PG_TYPE_INT2: + writeInt(2); + writeShort(v.getShort()); + break; + case PgServer.PG_TYPE_INT4: + writeInt(4); + writeInt(v.getInt()); + break; + case PgServer.PG_TYPE_INT8: + writeInt(8); + dataOut.writeLong(v.getLong()); + break; + case PgServer.PG_TYPE_FLOAT4: + writeInt(4); + dataOut.writeFloat(v.getFloat()); + break; + case PgServer.PG_TYPE_FLOAT8: + writeInt(8); + dataOut.writeDouble(v.getDouble()); + break; + case PgServer.PG_TYPE_NUMERIC: + writeNumericBinary(v.getBigDecimal()); + break; + case PgServer.PG_TYPE_BYTEA: { + byte[] data = v.getBytesNoCopy(); + writeInt(data.length); + write(data); + break; + } + case PgServer.PG_TYPE_DATE: + writeInt(4); + writeInt((int) toPostgreDays(((ValueDate) v).getDateValue())); + break; + case PgServer.PG_TYPE_TIME: + writeTimeBinary(((ValueTime) v).getNanos(), 8); + break; + case PgServer.PG_TYPE_TIMETZ: { + ValueTimeTimeZone t = (ValueTimeTimeZone) v; + long m = t.getNanos(); + writeTimeBinary(m, 12); + dataOut.writeInt(-t.getTimeZoneOffsetSeconds()); + break; + } + case PgServer.PG_TYPE_TIMESTAMP: { + ValueTimestamp t = (ValueTimestamp) v; + long m = toPostgreDays(t.getDateValue()) * 86_400; + long nanos = t.getTimeNanos(); + writeTimestampBinary(m, nanos); + break; + } + case PgServer.PG_TYPE_TIMESTAMPTZ: { + ValueTimestampTimeZone t = (ValueTimestampTimeZone) v; + long m = toPostgreDays(t.getDateValue()) * 86_400; + long nanos = t.getTimeNanos() - t.getTimeZoneOffsetSeconds() * 1_000_000_000L; + if (nanos < 0L) { + m--; + nanos += DateTimeUtils.NANOS_PER_DAY; + } + writeTimestampBinary(m, nanos); + break; + } + default: throw new IllegalStateException("output binary format is undefined"); + } + } + } + + 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)); + unscaled[0] = bi[0]; + return bi[1].intValue(); + } + + // https://www.npgsql.org/dev/types.html + // https://github.com/npgsql/npgsql/blob/8a479081f707784b5040747b23102c3d6371b9d3/ + // src/Npgsql/TypeHandlers/NumericHandlers/NumericHandler.cs#L166 + private void writeNumericBinary(BigDecimal value) throws IOException { + int weight = 0; + List groups = new ArrayList<>(); + int scale = value.scale(); + int signum = value.signum(); + if (signum != 0) { + BigInteger[] unscaled = {null}; + if (scale < 0) { + unscaled[0] = value.setScale(0).unscaledValue(); + scale = 0; + } else { + unscaled[0] = value.unscaledValue(); + } + if (signum < 0) { + unscaled[0] = unscaled[0].negate(); + } + weight = -scale / MAX_GROUP_SCALE - 1; + int remainder = 0; + int scaleChunk = scale % MAX_GROUP_SCALE; + if (scaleChunk > 0) { + remainder = divide(unscaled, POWERS10[scaleChunk]) * POWERS10[MAX_GROUP_SCALE - scaleChunk]; + if (remainder != 0) { + weight--; + } + } + if (remainder == 0) { + while ((remainder = divide(unscaled, MAX_GROUP_SIZE)) == 0) { + weight++; + } + } + groups.add(remainder); + while (unscaled[0].signum() != 0) { + groups.add(divide(unscaled, MAX_GROUP_SIZE)); + } + } + int groupCount = groups.size(); + if (groupCount + weight > Short.MAX_VALUE || scale > Short.MAX_VALUE) { + throw DbException.get(ErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_1, value.toString()); + } + writeInt(8 + groupCount * 2); + writeShort(groupCount); + writeShort(groupCount + weight); + writeShort(signum < 0 ? NUMERIC_NEGATIVE : NUMERIC_POSITIVE); + writeShort(scale); + for (int i = groupCount - 1; i >= 0; i--) { + writeShort(groups.get(i)); + } + } + + private void writeTimeBinary(long m, int numBytes) throws IOException { + writeInt(numBytes); + if (INTEGER_DATE_TYPES) { + // long format + m /= 1_000; + } else { + // double format + m = Double.doubleToLongBits(m * 0.000_000_001); + } + dataOut.writeLong(m); + } + + private void writeTimestampBinary(long m, long nanos) throws IOException { + writeInt(8); + if (INTEGER_DATE_TYPES) { + // long format + m = m * 1_000_000 + nanos / 1_000; + } else { + // double format + m = Double.doubleToLongBits(m + nanos * 0.000_000_001); + } + dataOut.writeLong(m); + } + + private Charset getEncoding() { + if ("UNICODE".equals(clientEncoding)) { + return StandardCharsets.UTF_8; + } + return Charset.forName(clientEncoding); + } + + private void setParameter(ArrayList parameters, int pgType, int i, int[] formatCodes) + throws IOException { + boolean text = true; + if (formatCodes.length == 1) { + text = formatCodes[0] == 0; + } else if (i < formatCodes.length) { + text = formatCodes[i] == 0; + } + int paramLen = readInt(); + Value value; + if (paramLen == -1) { + value = ValueNull.INSTANCE; + } else if (text) { + // plain text + byte[] data = Utils.newBytes(paramLen); + readFully(data); + String str = new String(data, getEncoding()); + switch (pgType) { + case PgServer.PG_TYPE_DATE: { + // Strip timezone offset + int idx = str.indexOf(' '); + if (idx > 0) { + str = str.substring(0, idx); + } + break; + } + case PgServer.PG_TYPE_TIME: { + // Strip timezone offset + int idx = str.indexOf('+'); + if (idx <= 0) { + idx = str.indexOf('-'); + } + if (idx > 0) { + str = str.substring(0, idx); + } + break; + } + } + value = ValueVarchar.get(str, session); + } else { + // binary + switch (pgType) { + case PgServer.PG_TYPE_INT2: + checkParamLength(2, paramLen); + value = ValueSmallint.get(readShort()); + break; + case PgServer.PG_TYPE_INT4: + checkParamLength(4, paramLen); + value = ValueInteger.get(readInt()); + break; + case PgServer.PG_TYPE_INT8: + checkParamLength(8, paramLen); + value = ValueBigint.get(dataIn.readLong()); + break; + case PgServer.PG_TYPE_FLOAT4: + checkParamLength(4, paramLen); + value = ValueReal.get(dataIn.readFloat()); + break; + case PgServer.PG_TYPE_FLOAT8: + checkParamLength(8, paramLen); + value = ValueDouble.get(dataIn.readDouble()); + break; + 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[] d = Utils.newBytes(paramLen); + readFully(d); + value = ValueVarchar.get(new String(d, getEncoding()), session); + } + } + parameters.get(i).setValue(value, true); + } + + private static void checkParamLength(int expected, int got) { + if (expected != got) { + throw DbException.getInvalidValueException("paramLen", 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(); + } else { + sendErrorResponse(e); + } + } + + private void sendErrorResponse(Exception re) throws IOException { + SQLException e = DbException.toSQLException(re); + server.traceError(e); + startMessage('E'); + write('S'); + writeString("ERROR"); + write('C'); + writeString(e.getSQLState()); + write('M'); + writeString(e.getMessage()); + write('D'); + writeString(e.toString()); + write(0); + sendMessage(); + } + + private void sendCancelQueryResponse() throws IOException { + server.trace("CancelSuccessResponse"); + startMessage('E'); + write('S'); + writeString("ERROR"); + write('C'); + writeString("57014"); + write('M'); + writeString("canceling statement due to user request"); + write(0); + sendMessage(); + } + + private void sendParameterDescription(ArrayList parameters, int[] paramTypes) + throws Exception { + int count = parameters.size(); + startMessage('t'); + writeShort(count); + for (int i = 0; i < count; i++) { + int type; + if (paramTypes != null && paramTypes[i] != 0) { + type = paramTypes[i]; + } else { + type = PgServer.PG_TYPE_VARCHAR; + } + server.checkType(type); + writeInt(type); + } + sendMessage(); + } + + private void sendNoData() throws IOException { + startMessage('n'); + sendMessage(); + } + + private void sendRowDescription(ResultInterface result, int[] formatCodes) throws IOException { + if (result == null) { + sendNoData(); + } else { + int columns = result.getVisibleColumnCount(); + int[] oids = new int[columns]; + int[] attnums = new int[columns]; + int[] types = new int[columns]; + int[] precision = new int[columns]; + String[] names = new String[columns]; + Database database = session.getDatabase(); + for (int i = 0; i < columns; i++) { + String name = result.getColumnName(i); + Schema schema = database.findSchema(result.getSchemaName(i)); + if (schema != null) { + Table table = schema.findTableOrView(session, result.getTableName(i)); + if (table != null) { + oids[i] = table.getId(); + Column column = table.findColumn(name); + if (column != null) { + attnums[i] = column.getColumnId() + 1; + } + } + } + names[i] = name; + TypeInfo type = result.getColumnType(i); + int pgType = PgServer.convertType(type); + // the ODBC client needs the column pg_catalog.pg_index + // to be of type 'int2vector' + // if (name.equalsIgnoreCase("indkey") && + // "pg_index".equalsIgnoreCase( + // meta.getTableName(i + 1))) { + // type = PgServer.PG_TYPE_INT2VECTOR; + // } + precision[i] = type.getDisplaySize(); + if (type.getValueType() != Value.NULL) { + server.checkType(pgType); + } + types[i] = pgType; + } + startMessage('T'); + writeShort(columns); + for (int i = 0; i < columns; i++) { + writeString(StringUtils.toLowerEnglish(names[i])); + // object ID + writeInt(oids[i]); + // attribute number of the column + writeShort(attnums[i]); + // data type + writeInt(types[i]); + // pg_type.typlen + writeShort(getTypeSize(types[i], precision[i])); + // pg_attribute.atttypmod + writeInt(-1); + // the format type: text = 0, binary = 1 + writeShort(formatAsText(types[i], formatCodes, i) ? 0 : 1); + } + sendMessage(); + } + } + + /** + * Check whether the given type should be formatted as text. + * + * @param pgType data type + * @param formatCodes format codes, or {@code null} + * @param column 0-based column number + * @return true for text + */ + private static boolean formatAsText(int pgType, int[] formatCodes, int column) { + boolean text = true; + if (formatCodes != null && formatCodes.length > 0) { + if (formatCodes.length == 1) { + text = formatCodes[0] == 0; + } else if (column < formatCodes.length) { + text = formatCodes[column] == 0; + } + } + return text; + } + + private static int getTypeSize(int pgType, int precision) { + switch (pgType) { + case PgServer.PG_TYPE_BOOL: + return 1; + case PgServer.PG_TYPE_VARCHAR: + return Math.max(255, precision + 10); + default: + return precision + 4; + } + } + + private void sendErrorResponse(String message) throws IOException { + server.trace("Exception: " + message); + startMessage('E'); + write('S'); + writeString("ERROR"); + write('C'); + // PROTOCOL VIOLATION + writeString("08P01"); + write('M'); + writeString(message); + sendMessage(); + } + + private void sendParseComplete() throws IOException { + startMessage('1'); + sendMessage(); + } + + private void sendBindComplete() throws IOException { + startMessage('2'); + sendMessage(); + } + + private void sendCloseComplete() throws IOException { + startMessage('3'); + sendMessage(); + } + + private void initDb() { + session.setTimeZone(timeZone); + try (CommandInterface command = session.prepareLocal("set search_path = public, pg_catalog")) { + command.executeUpdate(null); + } + HashSet typeSet = server.getTypeSet(); + if (typeSet.isEmpty()) { + try (CommandInterface command = session.prepareLocal("select oid from pg_catalog.pg_type"); + ResultInterface result = command.executeQuery(0, false)) { + while (result.next()) { + typeSet.add(result.currentRow()[0].getInt()); + } + } + } + } + + /** + * Close this connection. + */ + void close() { + for (Prepared prep : prepared.values()) { + prep.close(); + } + try { + stop = true; + try { + session.close(); + } catch (Exception e) { + // Ignore + } + if (socket != null) { + socket.close(); + } + server.trace("Close"); + } catch (Exception e) { + server.traceError(e); + } + session = null; + socket = null; + server.remove(this); + } + + private void sendAuthenticationCleartextPassword() throws IOException { + startMessage('R'); + writeInt(3); + sendMessage(); + } + + private void sendAuthenticationOk() throws IOException { + startMessage('R'); + writeInt(0); + sendMessage(); + sendParameterStatus("client_encoding", clientEncoding); + sendParameterStatus("DateStyle", dateStyle); + sendParameterStatus("is_superuser", "off"); + sendParameterStatus("server_encoding", "SQL_ASCII"); + sendParameterStatus("server_version", Constants.PG_VERSION); + sendParameterStatus("session_authorization", userName); + sendParameterStatus("standard_conforming_strings", "off"); + sendParameterStatus("TimeZone", pgTimeZone(timeZone.getId())); + // Don't inline, see https://bugs.eclipse.org/bugs/show_bug.cgi?id=569498 + String value = INTEGER_DATE_TYPES ? "on" : "off"; + sendParameterStatus("integer_datetimes", value); + sendBackendKeyData(); + sendReadyForQuery(); + } + + private void sendReadyForQuery() throws IOException { + startMessage('Z'); + write((byte) (session.getAutoCommit() ? /* idle */ 'I' : /* in a transaction block */ 'T')); + sendMessage(); + } + + private void sendBackendKeyData() throws IOException { + startMessage('K'); + writeInt(processId); + writeInt(secret); + sendMessage(); + } + + private void writeString(String s) throws IOException { + writeStringPart(s); + write(0); + } + + private void writeStringPart(String s) throws IOException { + write(s.getBytes(getEncoding())); + } + + private void writeInt(int i) throws IOException { + dataOut.writeInt(i); + } + + private void writeShort(int i) throws IOException { + dataOut.writeShort(i); + } + + private void write(byte[] data) throws IOException { + dataOut.write(data); + } + + private void write(ByteArrayOutputStream baos) throws IOException { + baos.writeTo(dataOut); + } + + private void write(int b) throws IOException { + dataOut.write(b); + } + + private void startMessage(int newMessageType) { + this.messageType = newMessageType; + if (outBuffer.size() <= 65_536) { + outBuffer.reset(); + } else { + outBuffer = new ByteArrayOutputStream(); + } + dataOut = new DataOutputStream(outBuffer); + } + + private void sendMessage() throws IOException { + dataOut.flush(); + dataOut = new DataOutputStream(out); + write(messageType); + writeInt(outBuffer.size() + 4); + write(outBuffer); + dataOut.flush(); + } + + private void sendParameterStatus(String param, String value) + throws IOException { + startMessage('S'); + writeString(param); + writeString(value); + sendMessage(); + } + + void setThread(Thread thread) { + this.thread = thread; + } + + Thread getThread() { + return thread; + } + + void setProcessId(int id) { + this.processId = id; + } + + int getProcessId() { + return this.processId; + } + + private synchronized void setActiveRequest(CommandInterface statement) { + activeRequest = statement; + } + + /** + * Kill a currently running query on this thread. + */ + private synchronized void cancelRequest() { + if (activeRequest != null) { + activeRequest.cancel(); + activeRequest = null; + } + } + + /** + * Represents a PostgreSQL Prepared object. + */ + static class Prepared { + + /** + * The object name. + */ + String name; + + /** + * The SQL statement. + */ + String sql; + + /** + * The prepared statement. + */ + CommandInterface prep; + + /** + * The current result (for suspended portal). + */ + ResultInterface result; + + /** + * The list of parameter types (if set). + */ + int[] paramType; + + /** + * Closes prepared statement and result, if any. + */ + void close() { + try { + closeResult(); + prep.close(); + } catch (Exception e) { + // Ignore + } + } + + /** + * Closes the result, if any. + */ + void closeResult() { + ResultInterface result = this.result; + if (result != null) { + this.result = null; + result.close(); + } + } + } + + /** + * Represents a PostgreSQL Portal object. + */ + static class Portal { + + /** + * The portal name. + */ + String name; + + /** + * The format used in the result set columns (if set). + */ + int[] resultColumnFormat; + + /** + * The prepared object. + */ + Prepared prep; + } +} diff --git a/h2/src/main/org/h2/server/pg/package.html b/h2/src/main/org/h2/server/pg/package.html new file mode 100644 index 0000000..0a3346d --- /dev/null +++ b/h2/src/main/org/h2/server/pg/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +PostgreSQL server implementation of this database. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/server/web/ConnectionInfo.java b/h2/src/main/org/h2/server/web/ConnectionInfo.java new file mode 100644 index 0000000..2b6fcdb --- /dev/null +++ b/h2/src/main/org/h2/server/web/ConnectionInfo.java @@ -0,0 +1,66 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.server.web; + +import org.h2.util.StringUtils; + +/** + * The connection info object is a wrapper for database connection information + * such as the database URL, user name and password. + * This class is used by the H2 Console. + */ +public class ConnectionInfo implements Comparable { + /** + * The driver class name. + */ + public String driver; + + /** + * The database URL. + */ + public String url; + + /** + * The user name. + */ + public String user; + + /** + * The connection display name. + */ + String name; + + /** + * The last time this connection was used. + */ + int lastAccess; + + ConnectionInfo() { + // nothing to do + } + + public ConnectionInfo(String data) { + String[] array = StringUtils.arraySplit(data, '|', false); + name = get(array, 0); + driver = get(array, 1); + url = get(array, 2); + user = get(array, 3); + } + + private static String get(String[] array, int i) { + return array != null && array.length > i ? array[i] : ""; + } + + String getString() { + return StringUtils.arrayCombine(new String[] { name, driver, url, user }, '|'); + } + + @Override + public int compareTo(ConnectionInfo o) { + return Integer.compare(o.lastAccess, lastAccess); + } + +} diff --git a/h2/src/main/org/h2/server/web/DbStarter.java b/h2/src/main/org/h2/server/web/DbStarter.java new file mode 100644 index 0000000..3cbb465 --- /dev/null +++ b/h2/src/main/org/h2/server/web/DbStarter.java @@ -0,0 +1,93 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.server.web; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.Statement; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +import org.h2.tools.Server; +import org.h2.util.StringUtils; + +/** + * This class can be used to start the H2 TCP server (or other H2 servers, for + * example the PG server) inside a web application container such as Tomcat or + * Jetty. It can also open a database connection. + */ +public class DbStarter implements ServletContextListener { + + private Connection conn; + private Server server; + + @Override + public void contextInitialized(ServletContextEvent servletContextEvent) { + try { + org.h2.Driver.load(); + + // This will get the setting from a context-param in web.xml if + // defined: + ServletContext servletContext = servletContextEvent.getServletContext(); + String url = getParameter(servletContext, "db.url", "jdbc:h2:~/test"); + String user = getParameter(servletContext, "db.user", "sa"); + String password = getParameter(servletContext, "db.password", "sa"); + + // Start the server if configured to do so + String serverParams = getParameter(servletContext, "db.tcpServer", null); + if (serverParams != null) { + String[] params = StringUtils.arraySplit(serverParams, ' ', true); + server = Server.createTcpServer(params); + server.start(); + } + + // To access the database in server mode, use the database URL: + // jdbc:h2:tcp://localhost/~/test + conn = DriverManager.getConnection(url, user, password); + servletContext.setAttribute("connection", conn); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static String getParameter(ServletContext servletContext, + String key, String defaultValue) { + String value = servletContext.getInitParameter(key); + return value == null ? defaultValue : value; + } + + /** + * Get the connection. + * + * @return the connection + */ + public Connection getConnection() { + return conn; + } + + @Override + public void contextDestroyed(ServletContextEvent servletContextEvent) { + try { + Statement stat = conn.createStatement(); + stat.execute("SHUTDOWN"); + stat.close(); + } catch (Exception e) { + e.printStackTrace(); + } + try { + conn.close(); + } catch (Exception e) { + e.printStackTrace(); + } + if (server != null) { + server.stop(); + server = null; + } + } + +} diff --git a/h2/src/main/org/h2/server/web/JakartaDbStarter.java b/h2/src/main/org/h2/server/web/JakartaDbStarter.java new file mode 100644 index 0000000..1547672 --- /dev/null +++ b/h2/src/main/org/h2/server/web/JakartaDbStarter.java @@ -0,0 +1,93 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.server.web; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.Statement; + +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletContextEvent; +import jakarta.servlet.ServletContextListener; + +import org.h2.tools.Server; +import org.h2.util.StringUtils; + +/** + * This class can be used to start the H2 TCP server (or other H2 servers, for + * example the PG server) inside a Jakarta web application container such as + * Tomcat or Jetty. It can also open a database connection. + */ +public class JakartaDbStarter implements ServletContextListener { + + private Connection conn; + private Server server; + + @Override + public void contextInitialized(ServletContextEvent servletContextEvent) { + try { + org.h2.Driver.load(); + + // This will get the setting from a context-param in web.xml if + // defined: + ServletContext servletContext = servletContextEvent.getServletContext(); + String url = getParameter(servletContext, "db.url", "jdbc:h2:~/test"); + String user = getParameter(servletContext, "db.user", "sa"); + String password = getParameter(servletContext, "db.password", "sa"); + + // Start the server if configured to do so + String serverParams = getParameter(servletContext, "db.tcpServer", null); + if (serverParams != null) { + String[] params = StringUtils.arraySplit(serverParams, ' ', true); + server = Server.createTcpServer(params); + server.start(); + } + + // To access the database in server mode, use the database URL: + // jdbc:h2:tcp://localhost/~/test + conn = DriverManager.getConnection(url, user, password); + servletContext.setAttribute("connection", conn); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static String getParameter(ServletContext servletContext, + String key, String defaultValue) { + String value = servletContext.getInitParameter(key); + return value == null ? defaultValue : value; + } + + /** + * Get the connection. + * + * @return the connection + */ + public Connection getConnection() { + return conn; + } + + @Override + public void contextDestroyed(ServletContextEvent servletContextEvent) { + try { + Statement stat = conn.createStatement(); + stat.execute("SHUTDOWN"); + stat.close(); + } catch (Exception e) { + e.printStackTrace(); + } + try { + conn.close(); + } catch (Exception e) { + e.printStackTrace(); + } + if (server != null) { + server.stop(); + server = null; + } + } + +} diff --git a/h2/src/main/org/h2/server/web/JakartaWebServlet.java b/h2/src/main/org/h2/server/web/JakartaWebServlet.java new file mode 100644 index 0000000..260266e --- /dev/null +++ b/h2/src/main/org/h2/server/web/JakartaWebServlet.java @@ -0,0 +1,169 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.server.web; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.Properties; + +import jakarta.servlet.ServletConfig; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.h2.util.NetworkConnectionInfo; + +/** + * This servlet lets the H2 Console be used in a Jakarta servlet container + * such as Tomcat or Jetty. + */ +public class JakartaWebServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + private transient WebServer server; + + @Override + public void init() { + ServletConfig config = getServletConfig(); + Enumeration en = config.getInitParameterNames(); + ArrayList list = new ArrayList<>(); + while (en.hasMoreElements()) { + String name = en.nextElement().toString(); + String value = config.getInitParameter(name); + if (!name.startsWith("-")) { + name = "-" + name; + } + list.add(name); + if (value.length() > 0) { + list.add(value); + } + } + String[] args = list.toArray(new String[0]); + server = new WebServer(); + server.setAllowChunked(false); + server.init(args); + } + + @Override + public void destroy() { + server.stop(); + } + + private boolean allow(HttpServletRequest req) { + if (server.getAllowOthers()) { + return true; + } + String addr = req.getRemoteAddr(); + try { + InetAddress address = InetAddress.getByName(addr); + return address.isLoopbackAddress(); + } catch (UnknownHostException | NoClassDefFoundError e) { + // Google App Engine does not allow java.net.InetAddress + return false; + } + + } + + private String getAllowedFile(HttpServletRequest req, String requestedFile) { + if (!allow(req)) { + return "notAllowed.jsp"; + } + if (requestedFile.length() == 0) { + return "index.do"; + } + return requestedFile; + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws IOException { + req.setCharacterEncoding("utf-8"); + String file = req.getPathInfo(); + if (file == null) { + resp.sendRedirect(req.getRequestURI() + "/"); + return; + } else if (file.startsWith("/")) { + file = file.substring(1); + } + file = getAllowedFile(req, file); + + // extract the request attributes + Properties attributes = new Properties(); + Enumeration en = req.getAttributeNames(); + while (en.hasMoreElements()) { + String name = en.nextElement().toString(); + String value = req.getAttribute(name).toString(); + attributes.put(name, value); + } + en = req.getParameterNames(); + while (en.hasMoreElements()) { + String name = en.nextElement().toString(); + String value = req.getParameter(name); + attributes.put(name, value); + } + + WebSession session = null; + String sessionId = attributes.getProperty("jsessionid"); + if (sessionId != null) { + session = server.getSession(sessionId); + } + WebApp app = new WebApp(server); + app.setSession(session, attributes); + String ifModifiedSince = req.getHeader("if-modified-since"); + + String scheme = req.getScheme(); + StringBuilder builder = new StringBuilder(scheme).append("://").append(req.getServerName()); + int serverPort = req.getServerPort(); + if (!(serverPort == 80 && scheme.equals("http") || serverPort == 443 && scheme.equals("https"))) { + builder.append(':').append(serverPort); + } + String path = builder.append(req.getContextPath()).toString(); + file = app.processRequest(file, new NetworkConnectionInfo(path, req.getRemoteAddr(), req.getRemotePort())); + session = app.getSession(); + + String mimeType = app.getMimeType(); + boolean cache = app.getCache(); + + if (cache && server.getStartDateTime().equals(ifModifiedSince)) { + resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + return; + } + byte[] bytes = server.getFile(file); + if (bytes == null) { + resp.sendError(HttpServletResponse.SC_NOT_FOUND); + bytes = ("File not found: " + file).getBytes(StandardCharsets.UTF_8); + } else { + if (session != null && file.endsWith(".jsp")) { + String page = new String(bytes, StandardCharsets.UTF_8); + page = PageParser.parse(page, session.map); + bytes = page.getBytes(StandardCharsets.UTF_8); + } + resp.setContentType(mimeType); + if (!cache) { + resp.setHeader("Cache-Control", "no-cache"); + } else { + resp.setHeader("Cache-Control", "max-age=10"); + resp.setHeader("Last-Modified", server.getStartDateTime()); + } + } + if (bytes != null) { + ServletOutputStream out = resp.getOutputStream(); + out.write(bytes); + } + } + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) + throws IOException { + doGet(req, resp); + } + +} diff --git a/h2/src/main/org/h2/server/web/PageParser.java b/h2/src/main/org/h2/server/web/PageParser.java new file mode 100644 index 0000000..78f8036 --- /dev/null +++ b/h2/src/main/org/h2/server/web/PageParser.java @@ -0,0 +1,349 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.server.web; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.h2.util.StringUtils; + +/** + * A page parser can parse an HTML page and replace the tags there. + * This class is used by the H2 Console. + */ +public class PageParser { + private static final int TAB_WIDTH = 4; + + private final String page; + private int pos; + private final Map settings; + private final int len; + private StringBuilder result; + + private PageParser(String page, Map settings, int pos) { + this.page = page; + this.pos = pos; + this.len = page.length(); + this.settings = settings; + result = new StringBuilder(len); + } + + /** + * Replace the tags in the HTML page with the given settings. + * + * @param page the HTML page + * @param settings the settings + * @return the converted page + */ + public static String parse(String page, Map settings) { + PageParser block = new PageParser(page, settings, 0); + return block.replaceTags(); + } + + private void setError(int i) { + String s = page.substring(0, i) + "####BUG####" + page.substring(i); + s = PageParser.escapeHtml(s); + result = new StringBuilder(); + result.append(s); + } + + private String parseBlockUntil(String end) throws ParseException { + PageParser block = new PageParser(page, settings, pos); + block.parseAll(); + if (!block.readIf(end)) { + throw new ParseException(page, block.pos); + } + pos = block.pos; + return block.result.toString(); + } + + private String replaceTags() { + try { + parseAll(); + if (pos != len) { + setError(pos); + } + } catch (ParseException e) { + setError(pos); + } + return result.toString(); + } + + @SuppressWarnings("unchecked") + private void parseAll() throws ParseException { + StringBuilder buff = result; + String p = page; + int i = pos; + for (; i < len; i++) { + char c = p.charAt(i); + switch (c) { + case '<': { + if (p.charAt(i + 3) == ':' && p.charAt(i + 1) == '/') { + // end tag + pos = i; + return; + } else if (p.charAt(i + 2) == ':') { + pos = i; + if (readIf(""); + int start = pos; + List list = (List) get(items); + if (list == null) { + result.append("?items?"); + list = new ArrayList<>(); + } + if (list.isEmpty()) { + parseBlockUntil(""); + } + for (Object o : list) { + settings.put(var, o); + pos = start; + String block = parseBlockUntil(""); + result.append(block); + } + } else if (readIf(""); + String block = parseBlockUntil(""); + pos--; + if (value.equals(val)) { + result.append(block); + } + } else { + setError(i); + return; + } + i = pos; + } else { + buff.append(c); + } + break; + } + case '$': + if (p.length() > i + 1 && p.charAt(i + 1) == '{') { + i += 2; + int j = p.indexOf('}', i); + if (j < 0) { + setError(i); + return; + } + String item = StringUtils.trimSubstring(p, i, j); + i = j; + String s = (String) get(item); + replaceTags(s); + } else { + buff.append(c); + } + break; + default: + buff.append(c); + break; + } + } + pos = i; + } + + @SuppressWarnings("unchecked") + private Object get(String item) { + int dot = item.indexOf('.'); + if (dot >= 0) { + String sub = item.substring(dot + 1); + item = item.substring(0, dot); + HashMap map = (HashMap) settings.get(item); + if (map == null) { + return "?" + item + "?"; + } + return map.get(sub); + } + return settings.get(item); + } + + private void replaceTags(String s) { + if (s != null) { + result.append(PageParser.parse(s, settings)); + } + } + + private String readParam(String name) throws ParseException { + read(name); + read("="); + read("\""); + int start = pos; + while (page.charAt(pos) != '"') { + pos++; + } + int end = pos; + read("\""); + String s = page.substring(start, end); + return PageParser.parse(s, settings); + } + + private void skipSpaces() { + while (page.charAt(pos) == ' ') { + pos++; + } + } + + private void read(String s) throws ParseException { + if (!readIf(s)) { + throw new ParseException(s, pos); + } + } + + private boolean readIf(String s) { + skipSpaces(); + if (page.regionMatches(pos, s, 0, s.length())) { + pos += s.length(); + skipSpaces(); + return true; + } + return false; + } + + /** + * Convert data to HTML, but don't convert newlines and multiple spaces. + * + * @param s the data + * @return the escaped html text + */ + static String escapeHtmlData(String s) { + return escapeHtml(s, false); + } + + /** + * Convert data to HTML, including newlines and multiple spaces. + * + * @param s the data + * @return the escaped html text + */ + public static String escapeHtml(String s) { + return escapeHtml(s, true); + } + + private static String escapeHtml(String s, boolean convertBreakAndSpace) { + if (s == null) { + return null; + } + int length = s.length(); + if (convertBreakAndSpace) { + if (length == 0) { + return " "; + } + } + StringBuilder builder = new StringBuilder(length); + boolean convertSpace = true; + for (int i = 0; i < length;) { + int cp = s.codePointAt(i); + if (cp == ' ' || cp == '\t') { + // convert tabs into spaces + for (int j = 0; j < (cp == ' ' ? 1 : TAB_WIDTH); j++) { + if (convertSpace && convertBreakAndSpace) { + builder.append(" "); + } else { + builder.append(' '); + convertSpace = true; + } + } + } else { + convertSpace = false; + switch (cp) { + case '$': + // so that ${ } in the text is interpreted correctly + builder.append("$"); + break; + case '<': + builder.append("<"); + break; + case '>': + builder.append(">"); + break; + case '&': + builder.append("&"); + break; + case '"': + builder.append("""); + break; + case '\'': + builder.append("'"); + break; + case '\n': + if (convertBreakAndSpace) { + builder.append("
"); + convertSpace = true; + } else { + builder.append(cp); + } + break; + default: + if (cp >= 128) { + builder.append("&#").append(cp).append(';'); + } else { + builder.append((char) cp); + } + } + } + i += Character.charCount(cp); + } + return builder.toString(); + } + + /** + * Escape text as a the javascript string. + * + * @param s the text + * @return the javascript string + */ + static String escapeJavaScript(String s) { + if (s == null) { + return null; + } + int length = s.length(); + if (length == 0) { + return ""; + } + StringBuilder buff = new StringBuilder(length); + for (int i = 0; i < length; i++) { + char c = s.charAt(i); + switch (c) { + case '"': + buff.append("\\\""); + break; + case '\'': + buff.append("\\'"); + break; + case '\\': + buff.append("\\\\"); + break; + case '\n': + buff.append("\\n"); + break; + case '\r': + buff.append("\\r"); + break; + case '\t': + buff.append("\\t"); + break; + default: + buff.append(c); + break; + } + } + return buff.toString(); + } +} diff --git a/h2/src/main/org/h2/server/web/WebApp.java b/h2/src/main/org/h2/server/web/WebApp.java new file mode 100644 index 0000000..9454036 --- /dev/null +++ b/h2/src/main/org/h2/server/web/WebApp.java @@ -0,0 +1,1849 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.server.web; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringReader; +import java.io.StringWriter; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.Random; + +import org.h2.api.ErrorCode; +import org.h2.bnf.Bnf; +import org.h2.bnf.context.DbColumn; +import org.h2.bnf.context.DbContents; +import org.h2.bnf.context.DbSchema; +import org.h2.bnf.context.DbTableOrView; +import org.h2.command.Parser; +import org.h2.engine.Constants; +import org.h2.engine.SysProperties; +import org.h2.jdbc.JdbcException; +import org.h2.message.DbException; +import org.h2.security.SHA256; +import org.h2.tools.Backup; +import org.h2.tools.ChangeFileEncryption; +import org.h2.tools.ConvertTraceFile; +import org.h2.tools.CreateCluster; +import org.h2.tools.DeleteDbFiles; +import org.h2.tools.Recover; +import org.h2.tools.Restore; +import org.h2.tools.RunScript; +import org.h2.tools.Script; +import org.h2.tools.SimpleResultSet; +import org.h2.util.JdbcUtils; +import org.h2.util.NetUtils; +import org.h2.util.NetworkConnectionInfo; +import org.h2.util.Profiler; +import org.h2.util.ScriptReader; +import org.h2.util.SortedProperties; +import org.h2.util.StringUtils; +import org.h2.util.Tool; +import org.h2.util.Utils; +import org.h2.util.Utils10; +import org.h2.value.DataType; + +/** + * For each connection to a session, an object of this class is created. + * This class is used by the H2 Console. + */ +public class WebApp { + + private static final Comparator SYSTEM_SCHEMA_COMPARATOR = Comparator + .comparing(DbTableOrView::getName, String.CASE_INSENSITIVE_ORDER); + + /** + * The web server. + */ + protected final WebServer server; + + /** + * The session. + */ + protected WebSession session; + + /** + * The session attributes + */ + protected Properties attributes; + + /** + * The mime type of the current response. + */ + protected String mimeType; + + /** + * Whether the response can be cached. + */ + protected boolean cache; + + /** + * Whether to close the connection. + */ + protected boolean stop; + + /** + * The language in the HTTP header. + */ + protected String headerLanguage; + + private Profiler profiler; + + WebApp(WebServer server) { + this.server = server; + } + + /** + * Set the web session and attributes. + * + * @param session the session + * @param attributes the attributes + */ + void setSession(WebSession session, Properties attributes) { + this.session = session; + this.attributes = attributes; + } + + /** + * Process an HTTP request. + * + * @param file the file that was requested + * @param networkConnectionInfo the network connection information + * @return the name of the file to return to the client + */ + String processRequest(String file, NetworkConnectionInfo networkConnectionInfo) { + int index = file.lastIndexOf('.'); + String suffix; + if (index >= 0) { + suffix = file.substring(index + 1); + } else { + suffix = ""; + } + if ("ico".equals(suffix)) { + mimeType = "image/x-icon"; + cache = true; + } else if ("gif".equals(suffix)) { + mimeType = "image/gif"; + cache = true; + } else if ("css".equals(suffix)) { + cache = true; + mimeType = "text/css"; + } else if ("html".equals(suffix) || + "do".equals(suffix) || + "jsp".equals(suffix)) { + cache = false; + mimeType = "text/html"; + if (session == null) { + session = server.createNewSession( + NetUtils.ipToShortForm(null, networkConnectionInfo.getClientAddr(), false).toString()); + if (!"notAllowed.jsp".equals(file)) { + file = "index.do"; + } + } + } else if ("js".equals(suffix)) { + cache = true; + mimeType = "text/javascript"; + } else { + cache = true; + mimeType = "application/octet-stream"; + } + trace("mimeType=" + mimeType); + trace(file); + if (file.endsWith(".do")) { + file = process(file, networkConnectionInfo); + } else if (file.endsWith(".jsp")) { + switch (file) { + case "admin.jsp": + case "tools.jsp": + if (!checkAdmin(file)) { + file = process("adminLogin.do", networkConnectionInfo); + } + } + } + return file; + } + + private static String getComboBox(String[] elements, String selected) { + StringBuilder buff = new StringBuilder(); + for (String value : elements) { + buff.append(""); + } + return buff.toString(); + } + + private static String getComboBox(String[][] elements, String selected) { + StringBuilder buff = new StringBuilder(); + for (String[] n : elements) { + buff.append(""); + } + return buff.toString(); + } + + private String process(String file, NetworkConnectionInfo networkConnectionInfo) { + trace("process " + file); + while (file.endsWith(".do")) { + switch (file) { + case "login.do": + file = login(networkConnectionInfo); + break; + case "index.do": + file = index(); + break; + case "logout.do": + file = logout(); + break; + case "settingRemove.do": + file = settingRemove(); + break; + case "settingSave.do": + file = settingSave(); + break; + case "test.do": + file = test(networkConnectionInfo); + break; + case "query.do": + file = query(); + break; + case "tables.do": + file = tables(); + break; + case "editResult.do": + file = editResult(); + break; + case "getHistory.do": + file = getHistory(); + break; + case "admin.do": + file = checkAdmin(file) ? admin() : "adminLogin.do"; + break; + case "adminSave.do": + file = checkAdmin(file) ? adminSave() : "adminLogin.do"; + break; + case "adminStartTranslate.do": + file = checkAdmin(file) ? adminStartTranslate() : "adminLogin.do"; + break; + case "adminShutdown.do": + file = checkAdmin(file) ? adminShutdown() : "adminLogin.do"; + break; + case "autoCompleteList.do": + file = autoCompleteList(); + break; + case "tools.do": + file = checkAdmin(file) ? tools() : "adminLogin.do"; + break; + case "adminLogin.do": + file = adminLogin(); + break; + default: + file = "error.jsp"; + break; + } + } + trace("return " + file); + return file; + } + + private boolean checkAdmin(String file) { + Boolean b = (Boolean) session.get("admin"); + if (b != null && b) { + return true; + } + String key = server.getKey(); + if (key != null && key.equals(session.get("key"))) { + return true; + } + session.put("adminBack", file); + return false; + } + + private String adminLogin() { + String password = attributes.getProperty("password"); + if (password == null || password.isEmpty() || !server.checkAdminPassword(password)) { + return "adminLogin.jsp"; + } + String back = (String) session.remove("adminBack"); + session.put("admin", true); + return back != null ? back : "admin.do"; + } + + private String autoCompleteList() { + String query = (String) attributes.get("query"); + boolean lowercase = false; + String tQuery = query.trim(); + if (!tQuery.isEmpty() && Character.isLowerCase(tQuery.charAt(0))) { + lowercase = true; + } + try { + String sql = query; + if (sql.endsWith(";")) { + sql += " "; + } + ScriptReader reader = new ScriptReader(new StringReader(sql)); + reader.setSkipRemarks(true); + String lastSql = ""; + while (true) { + String n = reader.readStatement(); + if (n == null) { + break; + } + lastSql = n; + } + String result = ""; + if (reader.isInsideRemark()) { + if (reader.isBlockRemark()) { + result = "1#(End Remark)# */\n" + result; + } else { + result = "1#(Newline)#\n" + result; + } + } else { + sql = lastSql; + while (sql.length() > 0 && sql.charAt(0) <= ' ') { + sql = sql.substring(1); + } + String tSql = sql.trim(); + if (!tSql.isEmpty() && Character.isLowerCase(tSql.charAt(0))) { + lowercase = true; + } + Bnf bnf = session.getBnf(); + if (bnf == null) { + return "autoCompleteList.jsp"; + } + HashMap map = bnf.getNextTokenList(sql); + String space = ""; + if (sql.length() > 0) { + char last = sql.charAt(sql.length() - 1); + if (!Character.isWhitespace(last) && (last != '.' && + last >= ' ' && last != '\'' && last != '"')) { + space = " "; + } + } + ArrayList list = new ArrayList<>(map.size()); + for (Map.Entry entry : map.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + String type = String.valueOf(key.charAt(0)); + if (Integer.parseInt(type) > 2) { + continue; + } + key = key.substring(2); + if (Character.isLetter(key.charAt(0)) && lowercase) { + key = StringUtils.toLowerEnglish(key); + value = StringUtils.toLowerEnglish(value); + } + if (key.equals(value) && !".".equals(value)) { + value = space + value; + } + key = StringUtils.urlEncode(key); + key = key.replace('+', ' '); + value = StringUtils.urlEncode(value); + value = value.replace('+', ' '); + list.add(type + "#" + key + "#" + value); + } + Collections.sort(list); + if (query.endsWith("\n") || tQuery.endsWith(";")) { + list.add(0, "1#(Newline)#\n"); + } + result = String.join("|", list); + } + session.put("autoCompleteList", result); + } catch (Throwable e) { + server.traceError(e); + } + return "autoCompleteList.jsp"; + } + + private String admin() { + session.put("port", Integer.toString(server.getPort())); + session.put("allowOthers", Boolean.toString(server.getAllowOthers())); + session.put("webExternalNames", server.getExternalNames()); + session.put("ssl", String.valueOf(server.getSSL())); + session.put("sessions", server.getSessions()); + return "admin.jsp"; + } + + private String adminSave() { + try { + Properties prop = new SortedProperties(); + int port = Integer.decode((String) attributes.get("port")); + prop.setProperty("webPort", Integer.toString(port)); + server.setPort(port); + boolean allowOthers = Utils.parseBoolean((String) attributes.get("allowOthers"), false, false); + prop.setProperty("webAllowOthers", String.valueOf(allowOthers)); + server.setAllowOthers(allowOthers); + String externalNames = (String) attributes.get("webExternalNames"); + prop.setProperty("webExternalNames", externalNames); + server.setExternalNames(externalNames); + boolean ssl = Utils.parseBoolean((String) attributes.get("ssl"), false, false); + prop.setProperty("webSSL", String.valueOf(ssl)); + server.setSSL(ssl); + byte[] adminPassword = server.getAdminPassword(); + if (adminPassword != null) { + prop.setProperty("webAdminPassword", StringUtils.convertBytesToHex(adminPassword)); + } + server.saveProperties(prop); + } catch (Exception e) { + trace(e.toString()); + } + return admin(); + } + + private String tools() { + try { + String toolName = (String) attributes.get("tool"); + session.put("tool", toolName); + String args = (String) attributes.get("args"); + String[] argList = StringUtils.arraySplit(args, ',', false); + Tool tool = null; + if ("Backup".equals(toolName)) { + tool = new Backup(); + } else if ("Restore".equals(toolName)) { + tool = new Restore(); + } else if ("Recover".equals(toolName)) { + tool = new Recover(); + } else if ("DeleteDbFiles".equals(toolName)) { + tool = new DeleteDbFiles(); + } else if ("ChangeFileEncryption".equals(toolName)) { + tool = new ChangeFileEncryption(); + } else if ("Script".equals(toolName)) { + tool = new Script(); + } else if ("RunScript".equals(toolName)) { + tool = new RunScript(); + } else if ("ConvertTraceFile".equals(toolName)) { + tool = new ConvertTraceFile(); + } else if ("CreateCluster".equals(toolName)) { + tool = new CreateCluster(); + } else { + throw DbException.getInternalError(toolName); + } + ByteArrayOutputStream outBuff = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(outBuff, false, "UTF-8"); + tool.setOut(out); + try { + tool.runTool(argList); + out.flush(); + String o = Utils10.byteArrayOutputStreamToString(outBuff, StandardCharsets.UTF_8); + String result = PageParser.escapeHtml(o); + session.put("toolResult", result); + } catch (Exception e) { + session.put("toolResult", getStackTrace(0, e, true)); + } + } catch (Exception e) { + server.traceError(e); + } + return "tools.jsp"; + } + + private String adminStartTranslate() { + Map p = Map.class.cast(session.map.get("text")); + @SuppressWarnings("unchecked") + Map p2 = (Map) p; + String file = server.startTranslate(p2); + session.put("translationFile", file); + return "helpTranslate.jsp"; + } + + /** + * Stop the application and the server. + * + * @return the page to display + */ + protected String adminShutdown() { + server.shutdown(); + return "admin.jsp"; + } + + private String index() { + String[][] languageArray = WebServer.LANGUAGES; + String language = (String) attributes.get("language"); + Locale locale = session.locale; + if (language != null) { + if (locale == null || !StringUtils.toLowerEnglish( + locale.getLanguage()).equals(language)) { + locale = new Locale(language, ""); + server.readTranslations(session, locale.getLanguage()); + session.put("language", language); + session.locale = locale; + } + } else { + language = (String) session.get("language"); + } + if (language == null) { + // if the language is not yet known + // use the last header + language = headerLanguage; + } + session.put("languageCombo", getComboBox(languageArray, language)); + String[] settingNames = server.getSettingNames(); + String setting = attributes.getProperty("setting"); + if (setting == null && settingNames.length > 0) { + setting = settingNames[0]; + } + String combobox = getComboBox(settingNames, setting); + session.put("settingsList", combobox); + ConnectionInfo info = server.getSetting(setting); + if (info == null) { + info = new ConnectionInfo(); + } + session.put("setting", PageParser.escapeHtmlData(setting)); + session.put("name", PageParser.escapeHtmlData(setting)); + session.put("driver", PageParser.escapeHtmlData(info.driver)); + session.put("url", PageParser.escapeHtmlData(info.url)); + session.put("user", PageParser.escapeHtmlData(info.user)); + return "index.jsp"; + } + + private String getHistory() { + int id = Integer.parseInt(attributes.getProperty("id")); + String sql = session.getCommand(id); + session.put("query", PageParser.escapeHtmlData(sql)); + return "query.jsp"; + } + + private static int addColumns(boolean mainSchema, DbTableOrView table, StringBuilder builder, int treeIndex, + boolean showColumnTypes, StringBuilder columnsBuilder) { + DbColumn[] columns = table.getColumns(); + for (int i = 0; columns != null && i < columns.length; i++) { + DbColumn column = columns[i]; + if (columnsBuilder.length() > 0) { + columnsBuilder.append(' '); + } + columnsBuilder.append(column.getName()); + String col = escapeIdentifier(column.getName()); + String level = mainSchema ? ", 1, 1" : ", 2, 2"; + builder.append("setNode(").append(treeIndex).append(level) + .append(", 'column', '") + .append(PageParser.escapeJavaScript(column.getName())) + .append("', 'javascript:ins(\\'").append(col).append("\\')');\n"); + treeIndex++; + if (mainSchema && showColumnTypes) { + builder.append("setNode(").append(treeIndex) + .append(", 2, 2, 'type', '") + .append(PageParser.escapeJavaScript(column.getDataType())) + .append("', null);\n"); + treeIndex++; + } + } + return treeIndex; + } + + private static String escapeIdentifier(String name) { + return StringUtils.urlEncode( + PageParser.escapeJavaScript(name)).replace('+', ' '); + } + + /** + * This class represents index information for the GUI. + */ + static class IndexInfo { + + /** + * The index name. + */ + String name; + + /** + * The index type name. + */ + String type; + + /** + * The indexed columns. + */ + String columns; + } + + private static int addIndexes(boolean mainSchema, DatabaseMetaData meta, + String table, String schema, StringBuilder buff, int treeIndex) + throws SQLException { + ResultSet rs; + try { + rs = meta.getIndexInfo(null, schema, table, false, true); + } catch (SQLException e) { + // SQLite + return treeIndex; + } + HashMap indexMap = new HashMap<>(); + while (rs.next()) { + String name = rs.getString("INDEX_NAME"); + IndexInfo info = indexMap.get(name); + if (info == null) { + int t = rs.getInt("TYPE"); + String type; + if (t == DatabaseMetaData.tableIndexClustered) { + type = ""; + } else if (t == DatabaseMetaData.tableIndexHashed) { + type = " (${text.tree.hashed})"; + } else if (t == DatabaseMetaData.tableIndexOther) { + type = ""; + } else { + type = null; + } + if (name != null && type != null) { + info = new IndexInfo(); + info.name = name; + type = (rs.getBoolean("NON_UNIQUE") ? + "${text.tree.nonUnique}" : "${text.tree.unique}") + type; + info.type = type; + info.columns = rs.getString("COLUMN_NAME"); + indexMap.put(name, info); + } + } else { + info.columns += ", " + rs.getString("COLUMN_NAME"); + } + } + rs.close(); + if (indexMap.size() > 0) { + String level = mainSchema ? ", 1, 1" : ", 2, 1"; + String levelIndex = mainSchema ? ", 2, 1" : ", 3, 1"; + String levelColumnType = mainSchema ? ", 3, 2" : ", 4, 2"; + buff.append("setNode(").append(treeIndex).append(level) + .append(", 'index_az', '${text.tree.indexes}', null);\n"); + treeIndex++; + for (IndexInfo info : indexMap.values()) { + buff.append("setNode(").append(treeIndex).append(levelIndex) + .append(", 'index', '") + .append(PageParser.escapeJavaScript(info.name)) + .append("', null);\n"); + treeIndex++; + buff.append("setNode(").append(treeIndex).append(levelColumnType) + .append(", 'type', '").append(info.type).append("', null);\n"); + treeIndex++; + buff.append("setNode(").append(treeIndex).append(levelColumnType) + .append(", 'type', '") + .append(PageParser.escapeJavaScript(info.columns)) + .append("', null);\n"); + treeIndex++; + } + } + return treeIndex; + } + + private int addTablesAndViews(DbSchema schema, boolean mainSchema, StringBuilder builder, int treeIndex) + throws SQLException { + if (schema == null) { + return treeIndex; + } + Connection conn = session.getConnection(); + DatabaseMetaData meta = session.getMetaData(); + int level = mainSchema ? 0 : 1; + boolean showColumns = mainSchema || !schema.isSystem; + String indentation = ", " + level + ", " + (showColumns ? "1" : "2") + ", "; + String indentNode = ", " + (level + 1) + ", 2, "; + DbTableOrView[] tables = schema.getTables(); + if (tables == null) { + return treeIndex; + } + DbContents contents = schema.getContents(); + boolean isOracle = contents.isOracle(); + boolean notManyTables = tables.length < SysProperties.CONSOLE_MAX_TABLES_LIST_INDEXES; + try (PreparedStatement prep = showColumns ? prepareViewDefinitionQuery(conn, contents) : null) { + if (prep != null) { + prep.setString(1, schema.name); + } + if (schema.isSystem) { + Arrays.sort(tables, SYSTEM_SCHEMA_COMPARATOR); + for (DbTableOrView table : tables) { + treeIndex = addTableOrView(schema, mainSchema, builder, treeIndex, meta, false, indentation, + isOracle, notManyTables, table, table.isView(), prep, indentNode); + } + } else { + for (DbTableOrView table : tables) { + if (table.isView()) { + continue; + } + treeIndex = addTableOrView(schema, mainSchema, builder, treeIndex, meta, showColumns, indentation, + isOracle, notManyTables, table, false, null, indentNode); + } + for (DbTableOrView table : tables) { + if (!table.isView()) { + continue; + } + treeIndex = addTableOrView(schema, mainSchema, builder, treeIndex, meta, showColumns, indentation, + isOracle, notManyTables, table, true, prep, indentNode); + } + } + } + return treeIndex; + } + + private static PreparedStatement prepareViewDefinitionQuery(Connection conn, DbContents contents) { + if (contents.mayHaveStandardViews()) { + try { + return conn.prepareStatement("SELECT VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS" + + " WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?"); + } catch (SQLException e) { + contents.setMayHaveStandardViews(false); + } + } + return null; + } + + private static int addTableOrView(DbSchema schema, boolean mainSchema, StringBuilder builder, int treeIndex, + DatabaseMetaData meta, boolean showColumns, String indentation, boolean isOracle, boolean notManyTables, + DbTableOrView table, boolean isView, PreparedStatement prep, String indentNode) throws SQLException { + int tableId = treeIndex; + String tab = table.getQuotedName(); + if (!mainSchema) { + tab = schema.quotedName + '.' + tab; + } + tab = escapeIdentifier(tab); + builder.append("setNode(").append(treeIndex).append(indentation) + .append(" '").append(isView ? "view" : "table").append("', '") + .append(PageParser.escapeJavaScript(table.getName())) + .append("', 'javascript:ins(\\'").append(tab).append("\\',true)');\n"); + treeIndex++; + if (showColumns) { + StringBuilder columnsBuilder = new StringBuilder(); + treeIndex = addColumns(mainSchema, table, builder, treeIndex, notManyTables, columnsBuilder); + if (isView) { + if (prep != null) { + prep.setString(2, table.getName()); + try (ResultSet rs = prep.executeQuery()) { + if (rs.next()) { + String sql = rs.getString(1); + if (sql != null) { + builder.append("setNode(").append(treeIndex).append(indentNode).append(" 'type', '") + .append(PageParser.escapeJavaScript(sql)).append("', null);\n"); + treeIndex++; + } + } + } + } + } else if (!isOracle && notManyTables) { + treeIndex = addIndexes(mainSchema, meta, table.getName(), schema.name, builder, treeIndex); + } + builder.append("addTable('") + .append(PageParser.escapeJavaScript(table.getName())).append("', '") + .append(PageParser.escapeJavaScript(columnsBuilder.toString())).append("', ") + .append(tableId).append(");\n"); + } + return treeIndex; + } + + private String tables() { + DbContents contents = session.getContents(); + boolean isH2 = false; + try { + String url = (String) session.get("url"); + Connection conn = session.getConnection(); + contents.readContents(url, conn); + session.loadBnf(); + isH2 = contents.isH2(); + + StringBuilder buff = new StringBuilder() + .append("setNode(0, 0, 0, 'database', '") + .append(PageParser.escapeJavaScript(url)) + .append("', null);\n"); + int treeIndex = 1; + + DbSchema defaultSchema = contents.getDefaultSchema(); + treeIndex = addTablesAndViews(defaultSchema, true, buff, treeIndex); + DbSchema[] schemas = contents.getSchemas(); + for (DbSchema schema : schemas) { + if (schema == defaultSchema || schema == null) { + continue; + } + buff.append("setNode(").append(treeIndex).append(", 0, 1, 'folder', '") + .append(PageParser.escapeJavaScript(schema.name)) + .append("', null);\n"); + treeIndex++; + treeIndex = addTablesAndViews(schema, false, buff, treeIndex); + } + if (isH2) { + try (Statement stat = conn.createStatement()) { + ResultSet rs; + try { + rs = stat.executeQuery("SELECT SEQUENCE_NAME, BASE_VALUE, INCREMENT FROM " + + "INFORMATION_SCHEMA.SEQUENCES ORDER BY SEQUENCE_NAME"); + } catch (SQLException e) { + rs = stat.executeQuery("SELECT SEQUENCE_NAME, CURRENT_VALUE, INCREMENT FROM " + + "INFORMATION_SCHEMA.SEQUENCES ORDER BY SEQUENCE_NAME"); + } + for (int i = 0; rs.next(); i++) { + if (i == 0) { + buff.append("setNode(").append(treeIndex) + .append(", 0, 1, 'sequences', '${text.tree.sequences}', null);\n"); + treeIndex++; + } + String name = rs.getString(1); + String currentBase = rs.getString(2); + String increment = rs.getString(3); + buff.append("setNode(").append(treeIndex) + .append(", 1, 1, 'sequence', '") + .append(PageParser.escapeJavaScript(name)) + .append("', null);\n"); + treeIndex++; + buff.append("setNode(").append(treeIndex) + .append(", 2, 2, 'type', '${text.tree.current}: ") + .append(PageParser.escapeJavaScript(currentBase)) + .append("', null);\n"); + treeIndex++; + if (!"1".equals(increment)) { + buff.append("setNode(").append(treeIndex) + .append(", 2, 2, 'type', '${text.tree.increment}: ") + .append(PageParser.escapeJavaScript(increment)) + .append("', null);\n"); + treeIndex++; + } + } + rs.close(); + try { + rs = stat.executeQuery( + "SELECT USER_NAME, IS_ADMIN FROM INFORMATION_SCHEMA.USERS ORDER BY USER_NAME"); + } catch (SQLException e) { + rs = stat.executeQuery("SELECT NAME, ADMIN FROM INFORMATION_SCHEMA.USERS ORDER BY NAME"); + } + for (int i = 0; rs.next(); i++) { + if (i == 0) { + buff.append("setNode(").append(treeIndex) + .append(", 0, 1, 'users', '${text.tree.users}', null);\n"); + treeIndex++; + } + String name = rs.getString(1); + String admin = rs.getString(2); + buff.append("setNode(").append(treeIndex) + .append(", 1, 1, 'user', '") + .append(PageParser.escapeJavaScript(name)) + .append("', null);\n"); + treeIndex++; + if (admin.equalsIgnoreCase("TRUE")) { + buff.append("setNode(").append(treeIndex) + .append(", 2, 2, 'type', '${text.tree.admin}', null);\n"); + treeIndex++; + } + } + rs.close(); + } + } + DatabaseMetaData meta = session.getMetaData(); + String version = meta.getDatabaseProductName() + " " + + meta.getDatabaseProductVersion(); + buff.append("setNode(").append(treeIndex) + .append(", 0, 0, 'info', '") + .append(PageParser.escapeJavaScript(version)) + .append("', null);\n") + .append("refreshQueryTables();"); + session.put("tree", buff.toString()); + } catch (Exception e) { + session.put("tree", ""); + session.put("error", getStackTrace(0, e, isH2)); + } + return "tables.jsp"; + } + + private String getStackTrace(int id, Throwable e, boolean isH2) { + try { + StringWriter writer = new StringWriter(); + e.printStackTrace(new PrintWriter(writer)); + String stackTrace = writer.toString(); + stackTrace = PageParser.escapeHtml(stackTrace); + if (isH2) { + stackTrace = linkToSource(stackTrace); + } + stackTrace = StringUtils.replaceAll(stackTrace, "\t", + "    "); + String message = PageParser.escapeHtml(e.getMessage()); + String error = "" + message + + ""; + if (e instanceof SQLException) { + SQLException se = (SQLException) e; + error += " " + se.getSQLState() + "/" + se.getErrorCode(); + if (isH2) { + int code = se.getErrorCode(); + error += " (${text.a.help})"; + } + } + error += "
" + stackTrace + "
"; + error = formatAsError(error); + return error; + } catch (OutOfMemoryError e2) { + server.traceError(e); + return e.toString(); + } + } + + private static String linkToSource(String s) { + try { + StringBuilder result = new StringBuilder(s.length()); + int idx = s.indexOf("
"); + result.append(s, 0, idx); + while (true) { + int start = s.indexOf("org.h2.", idx); + if (start < 0) { + result.append(s.substring(idx)); + break; + } + result.append(s, idx, start); + int end = s.indexOf(')', start); + if (end < 0) { + result.append(s.substring(idx)); + break; + } + String element = s.substring(start, end); + int open = element.lastIndexOf('('); + int dotMethod = element.lastIndexOf('.', open - 1); + int dotClass = element.lastIndexOf('.', dotMethod - 1); + String packageName = element.substring(0, dotClass); + int colon = element.lastIndexOf(':'); + String file = element.substring(open + 1, colon); + String lineNumber = element.substring(colon + 1, element.length()); + String fullFileName = packageName.replace('.', '/') + "/" + file; + result.append(""); + result.append(element); + result.append(""); + idx = end; + } + return result.toString(); + } catch (Throwable t) { + return s; + } + } + + private static String formatAsError(String s) { + return "
" + s + "
"; + } + + private String test(NetworkConnectionInfo networkConnectionInfo) { + String driver = attributes.getProperty("driver", ""); + String url = attributes.getProperty("url", ""); + String user = attributes.getProperty("user", ""); + String password = attributes.getProperty("password", ""); + session.put("driver", driver); + session.put("url", url); + session.put("user", user); + boolean isH2 = url.startsWith("jdbc:h2:"); + try { + long start = System.currentTimeMillis(); + String profOpen = "", profClose = ""; + Profiler prof = new Profiler(); + prof.startCollecting(); + Connection conn; + try { + conn = server.getConnection(driver, url, user, password, null, networkConnectionInfo); + } finally { + prof.stopCollecting(); + profOpen = prof.getTop(3); + } + prof = new Profiler(); + prof.startCollecting(); + try { + JdbcUtils.closeSilently(conn); + } finally { + prof.stopCollecting(); + profClose = prof.getTop(3); + } + long time = System.currentTimeMillis() - start; + String success; + if (time > 1000) { + success = "" + + "${text.login.testSuccessful}" + + "
" + + PageParser.escapeHtml(profOpen) + + "
" + + PageParser.escapeHtml(profClose) + + "
"; + } else { + success = "
${text.login.testSuccessful}
"; + } + session.put("error", success); + // session.put("error", "${text.login.testSuccessful}"); + return "login.jsp"; + } catch (Exception e) { + session.put("error", getLoginError(e, isH2)); + return "login.jsp"; + } + } + + /** + * Get the formatted login error message. + * + * @param e the exception + * @param isH2 if the current database is a H2 database + * @return the formatted error message + */ + private String getLoginError(Exception e, boolean isH2) { + if (e instanceof JdbcException && ((JdbcException) e).getErrorCode() == ErrorCode.CLASS_NOT_FOUND_1) { + return "${text.login.driverNotFound}
" + getStackTrace(0, e, isH2); + } + return getStackTrace(0, e, isH2); + } + + private String login(NetworkConnectionInfo networkConnectionInfo) { + String driver = attributes.getProperty("driver", ""); + String url = attributes.getProperty("url", ""); + String user = attributes.getProperty("user", ""); + String password = attributes.getProperty("password", ""); + session.put("autoCommit", "checked"); + session.put("autoComplete", "1"); + session.put("maxrows", "1000"); + boolean isH2 = url.startsWith("jdbc:h2:"); + try { + Connection conn = server.getConnection(driver, url, user, password, (String) session.get("key"), + networkConnectionInfo); + session.setConnection(conn); + session.put("url", url); + session.put("user", user); + session.remove("error"); + settingSave(); + return "frame.jsp"; + } catch (Exception e) { + session.put("error", getLoginError(e, isH2)); + return "login.jsp"; + } + } + + private String logout() { + try { + Connection conn = session.getConnection(); + session.setConnection(null); + session.remove("conn"); + session.remove("result"); + session.remove("tables"); + session.remove("user"); + session.remove("tool"); + if (conn != null) { + if (session.getShutdownServerOnDisconnect()) { + server.shutdown(); + } else { + conn.close(); + } + } + } catch (Exception e) { + trace(e.toString()); + } + session.remove("admin"); + return "index.do"; + } + + private String query() { + String sql = attributes.getProperty("sql").trim(); + try { + ScriptReader r = new ScriptReader(new StringReader(sql)); + final ArrayList list = new ArrayList<>(); + while (true) { + String s = r.readStatement(); + if (s == null) { + break; + } + list.add(s); + } + final Connection conn = session.getConnection(); + if (SysProperties.CONSOLE_STREAM && server.getAllowChunked()) { + String page = new String(server.getFile("result.jsp"), StandardCharsets.UTF_8); + int idx = page.indexOf("${result}"); + // the first element of the list is the header, the last the + // footer + list.add(0, page.substring(0, idx)); + list.add(page.substring(idx + "${result}".length())); + session.put("chunks", new Iterator() { + private int i; + @Override + public boolean hasNext() { + return i < list.size(); + } + @Override + public String next() { + String s = list.get(i++); + if (i == 1 || i == list.size()) { + return s; + } + StringBuilder b = new StringBuilder(); + query(conn, s, i - 1, list.size() - 2, b); + return b.toString(); + } + }); + return "result.jsp"; + } + String result; + StringBuilder buff = new StringBuilder(); + for (int i = 0; i < list.size(); i++) { + String s = list.get(i); + query(conn, s, i, list.size(), buff); + } + result = buff.toString(); + session.put("result", result); + } catch (Throwable e) { + session.put("result", getStackTrace(0, e, session.getContents().isH2())); + } + return "result.jsp"; + } + + /** + * Execute a query and append the result to the buffer. + * + * @param conn the connection + * @param s the statement + * @param i the index + * @param size the number of statements + * @param buff the target buffer + */ + void query(Connection conn, String s, int i, int size, StringBuilder buff) { + if (!(s.startsWith("@") && s.endsWith("."))) { + buff.append(PageParser.escapeHtml(s + ";")).append("
"); + } + boolean forceEdit = s.startsWith("@edit"); + buff.append(getResult(conn, i + 1, s, size == 1, forceEdit)). + append("
"); + } + + private String editResult() { + ResultSet rs = session.result; + int row = Integer.parseInt(attributes.getProperty("row")); + int op = Integer.parseInt(attributes.getProperty("op")); + String result = "", error = ""; + try { + if (op == 1) { + boolean insert = row < 0; + if (insert) { + rs.moveToInsertRow(); + } else { + rs.absolute(row); + } + for (int i = 0; i < rs.getMetaData().getColumnCount(); i++) { + String x = attributes.getProperty("r" + row + "c" + (i + 1)); + unescapeData(x, rs, i + 1); + } + if (insert) { + rs.insertRow(); + } else { + rs.updateRow(); + } + } else if (op == 2) { + rs.absolute(row); + rs.deleteRow(); + } else if (op == 3) { + // cancel + } + } catch (Throwable e) { + result = "
" + getStackTrace(0, e, session.getContents().isH2()); + error = formatAsError(e.getMessage()); + } + String sql = "@edit " + (String) session.get("resultSetSQL"); + Connection conn = session.getConnection(); + result = error + getResult(conn, -1, sql, true, true) + result; + session.put("result", result); + return "result.jsp"; + } + + private int getMaxrows() { + String r = (String) session.get("maxrows"); + return r == null ? 0 : Integer.parseInt(r); + } + + private String getResult(Connection conn, int id, String sql, + boolean allowEdit, boolean forceEdit) { + try { + sql = sql.trim(); + StringBuilder buff = new StringBuilder(); + String sqlUpper = StringUtils.toUpperEnglish(sql); + if (sqlUpper.contains("CREATE") || + sqlUpper.contains("DROP") || + sqlUpper.contains("ALTER") || + sqlUpper.contains("RUNSCRIPT")) { + String sessionId = attributes.getProperty("jsessionid"); + buff.append(""); + } + Statement stat; + DbContents contents = session.getContents(); + if (forceEdit || (allowEdit && contents.isH2())) { + stat = conn.createStatement( + ResultSet.TYPE_SCROLL_INSENSITIVE, + ResultSet.CONCUR_UPDATABLE); + } else { + stat = conn.createStatement(); + } + ResultSet rs; + long time = System.currentTimeMillis(); + boolean metadata = false; + Object generatedKeys = null; + boolean edit = false; + boolean list = false; + if (JdbcUtils.isBuiltIn(sql, "@autocommit_true")) { + conn.setAutoCommit(true); + return "${text.result.autoCommitOn}"; + } else if (JdbcUtils.isBuiltIn(sql, "@autocommit_false")) { + conn.setAutoCommit(false); + return "${text.result.autoCommitOff}"; + } else if (JdbcUtils.isBuiltIn(sql, "@cancel")) { + stat = session.executingStatement; + if (stat != null) { + stat.cancel(); + buff.append("${text.result.statementWasCanceled}"); + } else { + buff.append("${text.result.noRunningStatement}"); + } + return buff.toString(); + } else if (JdbcUtils.isBuiltIn(sql, "@edit")) { + edit = true; + sql = StringUtils.trimSubstring(sql, "@edit".length()); + session.put("resultSetSQL", sql); + } + if (JdbcUtils.isBuiltIn(sql, "@list")) { + list = true; + sql = StringUtils.trimSubstring(sql, "@list".length()); + } + if (JdbcUtils.isBuiltIn(sql, "@meta")) { + metadata = true; + sql = StringUtils.trimSubstring(sql, "@meta".length()); + } + if (JdbcUtils.isBuiltIn(sql, "@generated")) { + generatedKeys = true; + int offset = "@generated".length(); + int length = sql.length(); + for (; offset < length; offset++) { + char c = sql.charAt(offset); + if (c == '(') { + Parser p = new Parser(); + generatedKeys = p.parseColumnList(sql, offset); + offset = p.getLastParseIndex(); + break; + } + if (!Character.isWhitespace(c)) { + break; + } + } + sql = StringUtils.trimSubstring(sql, offset); + } else if (JdbcUtils.isBuiltIn(sql, "@history")) { + buff.append(getCommandHistoryString()); + return buff.toString(); + } else if (JdbcUtils.isBuiltIn(sql, "@loop")) { + sql = StringUtils.trimSubstring(sql, "@loop".length()); + int idx = sql.indexOf(' '); + int count = Integer.decode(sql.substring(0, idx)); + sql = StringUtils.trimSubstring(sql, idx); + return executeLoop(conn, count, sql); + } else if (JdbcUtils.isBuiltIn(sql, "@maxrows")) { + int maxrows = (int) Double.parseDouble(StringUtils.trimSubstring(sql, "@maxrows".length())); + session.put("maxrows", Integer.toString(maxrows)); + return "${text.result.maxrowsSet}"; + } else if (JdbcUtils.isBuiltIn(sql, "@parameter_meta")) { + sql = StringUtils.trimSubstring(sql, "@parameter_meta".length()); + PreparedStatement prep = conn.prepareStatement(sql); + buff.append(getParameterResultSet(prep.getParameterMetaData())); + return buff.toString(); + } else if (JdbcUtils.isBuiltIn(sql, "@password_hash")) { + sql = StringUtils.trimSubstring(sql, "@password_hash".length()); + String[] p = JdbcUtils.split(sql); + return StringUtils.convertBytesToHex( + SHA256.getKeyPasswordHash(p[0], p[1].toCharArray())); + } else if (JdbcUtils.isBuiltIn(sql, "@prof_start")) { + if (profiler != null) { + profiler.stopCollecting(); + } + profiler = new Profiler(); + profiler.startCollecting(); + return "Ok"; + } else if (JdbcUtils.isBuiltIn(sql, "@sleep")) { + String s = StringUtils.trimSubstring(sql, "@sleep".length()); + int sleep = 1; + if (s.length() > 0) { + sleep = Integer.parseInt(s); + } + Thread.sleep(sleep * 1000); + return "Ok"; + } else if (JdbcUtils.isBuiltIn(sql, "@transaction_isolation")) { + String s = StringUtils.trimSubstring(sql, "@transaction_isolation".length()); + if (s.length() > 0) { + int level = Integer.parseInt(s); + conn.setTransactionIsolation(level); + } + buff.append("Transaction Isolation: ") + .append(conn.getTransactionIsolation()) + .append("
"); + buff.append(Connection.TRANSACTION_READ_UNCOMMITTED) + .append(": read_uncommitted
"); + buff.append(Connection.TRANSACTION_READ_COMMITTED) + .append(": read_committed
"); + buff.append(Connection.TRANSACTION_REPEATABLE_READ) + .append(": repeatable_read
"); + buff.append(Constants.TRANSACTION_SNAPSHOT) + .append(": snapshot
"); + buff.append(Connection.TRANSACTION_SERIALIZABLE) + .append(": serializable"); + } + if (sql.startsWith("@")) { + rs = JdbcUtils.getMetaResultSet(conn, sql); + if (rs == null && JdbcUtils.isBuiltIn(sql, "@prof_stop")) { + if (profiler != null) { + profiler.stopCollecting(); + SimpleResultSet simple = new SimpleResultSet(); + simple.addColumn("Top Stack Trace(s)", Types.VARCHAR, 0, 0); + simple.addRow(profiler.getTop(3)); + rs = simple; + profiler = null; + } + } + if (rs == null) { + buff.append("?: ").append(sql); + return buff.toString(); + } + } else { + int maxrows = getMaxrows(); + stat.setMaxRows(maxrows); + session.executingStatement = stat; + boolean isResultSet; + if (generatedKeys == null) { + isResultSet = stat.execute(sql); + } else if (generatedKeys instanceof Boolean) { + isResultSet = stat.execute(sql, + ((Boolean) generatedKeys) ? Statement.RETURN_GENERATED_KEYS : Statement.NO_GENERATED_KEYS); + } else if (generatedKeys instanceof String[]) { + isResultSet = stat.execute(sql, (String[]) generatedKeys); + } else { + isResultSet = stat.execute(sql, (int[]) generatedKeys); + } + session.addCommand(sql); + if (generatedKeys != null) { + rs = null; + rs = stat.getGeneratedKeys(); + } else { + if (!isResultSet) { + long updateCount; + try { + updateCount = stat.getLargeUpdateCount(); + } catch (UnsupportedOperationException e) { + updateCount = stat.getUpdateCount(); + } + buff.append("${text.result.updateCount}: ").append(updateCount); + time = System.currentTimeMillis() - time; + buff.append("
(").append(time).append(" ms)"); + stat.close(); + return buff.toString(); + } + rs = stat.getResultSet(); + } + } + time = System.currentTimeMillis() - time; + buff.append(getResultSet(sql, rs, metadata, list, edit, time, allowEdit)); + // SQLWarning warning = stat.getWarnings(); + // if (warning != null) { + // buff.append("
Warning:
"). + // append(getStackTrace(id, warning)); + // } + if (!edit) { + stat.close(); + } + return buff.toString(); + } catch (Throwable e) { + // throwable: including OutOfMemoryError and so on + return getStackTrace(id, e, session.getContents().isH2()); + } finally { + session.executingStatement = null; + } + } + + private String executeLoop(Connection conn, int count, String sql) + throws SQLException { + ArrayList params = new ArrayList<>(); + int idx = 0; + while (!stop) { + idx = sql.indexOf('?', idx); + if (idx < 0) { + break; + } + if (JdbcUtils.isBuiltIn(sql.substring(idx), "?/*rnd*/")) { + params.add(1); + sql = sql.substring(0, idx) + "?" + sql.substring(idx + "/*rnd*/".length() + 1); + } else { + params.add(0); + } + idx++; + } + boolean prepared; + Random random = new Random(1); + long time = System.currentTimeMillis(); + if (JdbcUtils.isBuiltIn(sql, "@statement")) { + sql = StringUtils.trimSubstring(sql, "@statement".length()); + prepared = false; + Statement stat = conn.createStatement(); + for (int i = 0; !stop && i < count; i++) { + String s = sql; + for (Integer type : params) { + idx = s.indexOf('?'); + if (type == 1) { + s = s.substring(0, idx) + random.nextInt(count) + s.substring(idx + 1); + } else { + s = s.substring(0, idx) + i + s.substring(idx + 1); + } + } + if (stat.execute(s)) { + ResultSet rs = stat.getResultSet(); + while (!stop && rs.next()) { + // maybe get the data as well + } + rs.close(); + } + } + } else { + prepared = true; + PreparedStatement prep = conn.prepareStatement(sql); + for (int i = 0; !stop && i < count; i++) { + for (int j = 0; j < params.size(); j++) { + Integer type = params.get(j); + if (type == 1) { + prep.setInt(j + 1, random.nextInt(count)); + } else { + prep.setInt(j + 1, i); + } + } + if (session.getContents().isSQLite()) { + // SQLite currently throws an exception on prep.execute() + prep.executeUpdate(); + } else { + if (prep.execute()) { + ResultSet rs = prep.getResultSet(); + while (!stop && rs.next()) { + // maybe get the data as well + } + rs.close(); + } + } + } + } + time = System.currentTimeMillis() - time; + StringBuilder builder = new StringBuilder().append(time).append(" ms: ").append(count).append(" * ") + .append(prepared ? "(Prepared) " : "(Statement) ").append('('); + for (int i = 0, size = params.size(); i < size; i++) { + if (i > 0) { + builder.append(", "); + } + builder.append(params.get(i) == 0 ? "i" : "rnd"); + } + return builder.append(") ").append(sql).toString(); + } + + private String getCommandHistoryString() { + StringBuilder buff = new StringBuilder(); + ArrayList history = session.getCommandHistory(); + buff.append("
" + + ""); + for (int i = history.size() - 1; i >= 0; i--) { + String sql = history.get(i); + buff.append(""); + } + buff.append("
Command
"). + append("\"${text.resultEdit.edit}\""). + append(""). + append(PageParser.escapeHtml(sql)). + append("
"); + return buff.toString(); + } + + private static String getParameterResultSet(ParameterMetaData meta) + throws SQLException { + StringBuilder buff = new StringBuilder(); + if (meta == null) { + return "No parameter meta data"; + } + buff.append(""). + append(""). + append(""); + for (int i = 0; i < meta.getParameterCount(); i++) { + buff.append(""); + } + buff.append("
classNamemodetypetypeNameprecisionscale
"). + append(meta.getParameterClassName(i + 1)). + append(""). + append(meta.getParameterMode(i + 1)). + append(""). + append(meta.getParameterType(i + 1)). + append(""). + append(meta.getParameterTypeName(i + 1)). + append(""). + append(meta.getPrecision(i + 1)). + append(""). + append(meta.getScale(i + 1)). + append("
"); + return buff.toString(); + } + + private String getResultSet(String sql, ResultSet rs, boolean metadata, + boolean list, boolean edit, long time, boolean allowEdit) + throws SQLException { + int maxrows = getMaxrows(); + time = System.currentTimeMillis() - time; + StringBuilder buff = new StringBuilder(); + if (edit) { + buff.append("
" + + "" + + "" + + ""); + } else { + buff.append("
"); + } + if (metadata) { + SimpleResultSet r = new SimpleResultSet(); + r.addColumn("#", Types.INTEGER, 0, 0); + r.addColumn("label", Types.VARCHAR, 0, 0); + r.addColumn("catalog", Types.VARCHAR, 0, 0); + r.addColumn("schema", Types.VARCHAR, 0, 0); + r.addColumn("table", Types.VARCHAR, 0, 0); + r.addColumn("column", Types.VARCHAR, 0, 0); + r.addColumn("type", Types.INTEGER, 0, 0); + r.addColumn("typeName", Types.VARCHAR, 0, 0); + r.addColumn("class", Types.VARCHAR, 0, 0); + r.addColumn("precision", Types.INTEGER, 0, 0); + r.addColumn("scale", Types.INTEGER, 0, 0); + r.addColumn("displaySize", Types.INTEGER, 0, 0); + r.addColumn("autoIncrement", Types.BOOLEAN, 0, 0); + r.addColumn("caseSensitive", Types.BOOLEAN, 0, 0); + r.addColumn("currency", Types.BOOLEAN, 0, 0); + r.addColumn("nullable", Types.INTEGER, 0, 0); + r.addColumn("readOnly", Types.BOOLEAN, 0, 0); + r.addColumn("searchable", Types.BOOLEAN, 0, 0); + r.addColumn("signed", Types.BOOLEAN, 0, 0); + r.addColumn("writable", Types.BOOLEAN, 0, 0); + r.addColumn("definitelyWritable", Types.BOOLEAN, 0, 0); + ResultSetMetaData m = rs.getMetaData(); + for (int i = 1; i <= m.getColumnCount(); i++) { + r.addRow(i, + m.getColumnLabel(i), + m.getCatalogName(i), + m.getSchemaName(i), + m.getTableName(i), + m.getColumnName(i), + m.getColumnType(i), + m.getColumnTypeName(i), + m.getColumnClassName(i), + m.getPrecision(i), + m.getScale(i), + m.getColumnDisplaySize(i), + m.isAutoIncrement(i), + m.isCaseSensitive(i), + m.isCurrency(i), + m.isNullable(i), + m.isReadOnly(i), + m.isSearchable(i), + m.isSigned(i), + m.isWritable(i), + m.isDefinitelyWritable(i)); + } + rs = r; + } + ResultSetMetaData meta = rs.getMetaData(); + int columns = meta.getColumnCount(); + int rows = 0; + if (list) { + buff.append(""); + while (rs.next()) { + if (maxrows > 0 && rows >= maxrows) { + break; + } + rows++; + buff.append(""); + for (int i = 0; i < columns; i++) { + buff.append(""); + } + } + } else { + buff.append(""); + if (edit) { + buff.append(""); + } + for (int i = 0; i < columns; i++) { + buff.append(""); + } + buff.append(""); + while (rs.next()) { + if (maxrows > 0 && rows >= maxrows) { + break; + } + rows++; + buff.append(""); + if (edit) { + buff.append(""); + } + for (int i = 0; i < columns; i++) { + buff.append(""); + } + buff.append(""); + } + } + boolean isUpdatable = false; + try { + if (!session.getContents().isDB2()) { + isUpdatable = rs.getConcurrency() == ResultSet.CONCUR_UPDATABLE + && rs.getType() != ResultSet.TYPE_FORWARD_ONLY; + } + } catch (NullPointerException e) { + // ignore + // workaround for a JDBC-ODBC bridge problem + } + if (edit) { + ResultSet old = session.result; + if (old != null) { + old.close(); + } + session.result = rs; + } else { + rs.close(); + } + if (edit) { + buff.append(""); + for (int i = 0; i < columns; i++) { + buff.append(""); + } + buff.append(""); + } + buff.append("
ColumnData
Row #"). + append(rows).append("
"). + append(PageParser.escapeHtml(meta.getColumnLabel(i + 1))). + append(""). + append(escapeData(rs, i + 1)). + append("
${text.resultEdit.action}"). + append(PageParser.escapeHtml(meta.getColumnLabel(i + 1))). + append("
"). + append("\"${text.resultEdit.edit}\""). + append("\"${text.resultEdit.delete}\""). + append(""). + append(escapeData(rs, i + 1)). + append("
"). + append("\"${text.resultEdit.add}\""). + append("
"); + if (edit) { + buff.append("
"); + } + if (rows == 0) { + buff.append("(${text.result.noRows}"); + } else if (rows == 1) { + buff.append("(${text.result.1row}"); + } else { + buff.append('(').append(rows).append(" ${text.result.rows}"); + } + buff.append(", "); + time = System.currentTimeMillis() - time; + buff.append(time).append(" ms)"); + if (!edit && isUpdatable && allowEdit) { + buff.append("

" + + "
" + + "" + + "
"); + } + return buff.toString(); + } + + /** + * Save the current connection settings to the properties file. + * + * @return the file to open afterwards + */ + private String settingSave() { + ConnectionInfo info = new ConnectionInfo(); + info.name = attributes.getProperty("name", ""); + info.driver = attributes.getProperty("driver", ""); + info.url = attributes.getProperty("url", ""); + info.user = attributes.getProperty("user", ""); + server.updateSetting(info); + attributes.put("setting", info.name); + server.saveProperties(null); + return "index.do"; + } + + private static String escapeData(ResultSet rs, int columnIndex) throws SQLException { + if (DataType.isBinaryColumn(rs.getMetaData(), columnIndex)) { + byte[] d = rs.getBytes(columnIndex); + if (d == null) { + return "null"; + } else if (d.length > 50_000) { + return "
=+
" + StringUtils.convertBytesToHex(d, 3) + "... (" + + d.length + " ${text.result.bytes})"; + } + return StringUtils.convertBytesToHex(d); + } + String d = rs.getString(columnIndex); + if (d == null) { + return "null"; + } else if (d.length() > 100_000) { + return "
=+
" + PageParser.escapeHtml(d.substring(0, 100)) + "... (" + + d.length() + " ${text.result.characters})"; + } else if (d.equals("null") || d.startsWith("= ") || d.startsWith("=+")) { + return "
=
" + PageParser.escapeHtml(d); + } else if (d.equals("")) { + // PageParser.escapeHtml replaces "" with a non-breaking space + return ""; + } + return PageParser.escapeHtml(d); + } + + private void unescapeData(String x, ResultSet rs, int columnIndex) + throws SQLException { + if (x.equals("null")) { + rs.updateNull(columnIndex); + return; + } else if (x.startsWith("=+")) { + // don't update + return; + } else if (x.equals("=*")) { + // set an appropriate default value + int type = rs.getMetaData().getColumnType(columnIndex); + switch (type) { + case Types.TIME: + rs.updateString(columnIndex, "12:00:00"); + break; + case Types.TIMESTAMP: + case Types.DATE: + rs.updateString(columnIndex, "2001-01-01"); + break; + default: + rs.updateString(columnIndex, "1"); + break; + } + return; + } else if (x.startsWith("= ")) { + x = x.substring(2); + } + ResultSetMetaData meta = rs.getMetaData(); + if (DataType.isBinaryColumn(meta, columnIndex)) { + rs.updateBytes(columnIndex, StringUtils.convertHexToBytes(x)); + return; + } + int type = meta.getColumnType(columnIndex); + if (session.getContents().isH2()) { + rs.updateString(columnIndex, x); + return; + } + switch (type) { + case Types.BIGINT: + rs.updateLong(columnIndex, Long.decode(x)); + break; + case Types.DECIMAL: + rs.updateBigDecimal(columnIndex, new BigDecimal(x)); + break; + case Types.DOUBLE: + case Types.FLOAT: + rs.updateDouble(columnIndex, Double.parseDouble(x)); + break; + case Types.REAL: + rs.updateFloat(columnIndex, Float.parseFloat(x)); + break; + case Types.INTEGER: + rs.updateInt(columnIndex, Integer.decode(x)); + break; + case Types.TINYINT: + rs.updateShort(columnIndex, Short.decode(x)); + break; + default: + rs.updateString(columnIndex, x); + } + } + + private String settingRemove() { + String setting = attributes.getProperty("name", ""); + server.removeSetting(setting); + ArrayList settings = server.getSettings(); + if (!settings.isEmpty()) { + attributes.put("setting", settings.get(0)); + } + server.saveProperties(null); + return "index.do"; + } + + /** + * Get the current mime type. + * + * @return the mime type + */ + String getMimeType() { + return mimeType; + } + + boolean getCache() { + return cache; + } + + WebSession getSession() { + return session; + } + + private void trace(String s) { + server.trace(s); + } + +} diff --git a/h2/src/main/org/h2/server/web/WebServer.java b/h2/src/main/org/h2/server/web/WebServer.java new file mode 100644 index 0000000..7043417 --- /dev/null +++ b/h2/src/main/org/h2/server/web/WebServer.java @@ -0,0 +1,958 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.server.web; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.Connection; +import java.sql.SQLException; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; + +import org.h2.engine.Constants; +import org.h2.engine.SysProperties; +import org.h2.message.DbException; +import org.h2.security.SHA256; +import org.h2.server.Service; +import org.h2.server.ShutdownHandler; +import org.h2.store.fs.FileUtils; +import org.h2.util.JdbcUtils; +import org.h2.util.MathUtils; +import org.h2.util.NetUtils; +import org.h2.util.NetworkConnectionInfo; +import org.h2.util.SortedProperties; +import org.h2.util.StringUtils; +import org.h2.util.Tool; +import org.h2.util.Utils; + +/** + * The web server is a simple standalone HTTP server that implements the H2 + * Console application. It is not optimized for performance. + */ +public class WebServer implements Service { + + static final String[][] LANGUAGES = { + { "cs", "\u010ce\u0161tina" }, + { "de", "Deutsch" }, + { "en", "English" }, + { "es", "Espa\u00f1ol" }, + { "fr", "Fran\u00e7ais" }, + { "hi", "Hindi \u0939\u093f\u0902\u0926\u0940" }, + { "hu", "Magyar"}, + { "ko", "\ud55c\uad6d\uc5b4"}, + { "in", "Indonesia"}, + { "it", "Italiano"}, + { "ja", "\u65e5\u672c\u8a9e"}, + { "nl", "Nederlands"}, + { "pl", "Polski"}, + { "pt_BR", "Portugu\u00eas (Brasil)"}, + { "pt_PT", "Portugu\u00eas (Europeu)"}, + { "ru", "\u0440\u0443\u0441\u0441\u043a\u0438\u0439"}, + { "sk", "Slovensky"}, + { "tr", "T\u00fcrk\u00e7e"}, + { "uk", "\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430"}, + { "zh_CN", "\u4e2d\u6587 (\u7b80\u4f53)"}, + { "zh_TW", "\u4e2d\u6587 (\u7e41\u9ad4)"}, + }; + + private static final String COMMAND_HISTORY = "commandHistory"; + + private static final String DEFAULT_LANGUAGE = "en"; + + private static final String[] GENERIC = { + "Generic JNDI Data Source|javax.naming.InitialContext|" + + "java:comp/env/jdbc/Test|sa", + "Generic Teradata|com.teradata.jdbc.TeraDriver|" + + "jdbc:teradata://whomooz/|", + "Generic Snowflake|com.snowflake.client.jdbc.SnowflakeDriver|" + + "jdbc:snowflake://accountName.snowflakecomputing.com|", + "Generic Redshift|com.amazon.redshift.jdbc42.Driver|" + + "jdbc:redshift://endpoint:5439/database|", + "Generic Impala|org.cloudera.impala.jdbc41.Driver|" + + "jdbc:impala://clustername:21050/default|", + "Generic Hive 2|org.apache.hive.jdbc.HiveDriver|" + + "jdbc:hive2://clustername:10000/default|", + "Generic Hive|org.apache.hadoop.hive.jdbc.HiveDriver|" + + "jdbc:hive://clustername:10000/default|", + "Generic Azure SQL|com.microsoft.sqlserver.jdbc.SQLServerDriver|" + + "jdbc:sqlserver://name.database.windows.net:1433|", + "Generic Firebird Server|org.firebirdsql.jdbc.FBDriver|" + + "jdbc:firebirdsql:localhost:c:/temp/firebird/test|sysdba", + "Generic SQLite|org.sqlite.JDBC|" + + "jdbc:sqlite:test|sa", + "Generic DB2|com.ibm.db2.jcc.DB2Driver|" + + "jdbc:db2://localhost/test|" , + "Generic Oracle|oracle.jdbc.driver.OracleDriver|" + + "jdbc:oracle:thin:@localhost:1521:XE|sa" , + "Generic MS SQL Server 2000|com.microsoft.jdbc.sqlserver.SQLServerDriver|" + + "jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=sqlexpress|sa", + "Generic MS SQL Server 2005|com.microsoft.sqlserver.jdbc.SQLServerDriver|" + + "jdbc:sqlserver://localhost;DatabaseName=test|sa", + "Generic PostgreSQL|org.postgresql.Driver|" + + "jdbc:postgresql:test|" , + "Generic MySQL|com.mysql.cj.jdbc.Driver|" + + "jdbc:mysql://localhost:3306/test|" , + "Generic MariaDB|org.mariadb.jdbc.Driver|" + + "jdbc:mariadb://localhost:3306/test|" , + "Generic HSQLDB|org.hsqldb.jdbcDriver|" + + "jdbc:hsqldb:test;hsqldb.default_table_type=cached|sa" , + "Generic Derby (Server)|org.apache.derby.client.ClientAutoloadedDriver|" + + "jdbc:derby://localhost:1527/test;create=true|sa", + "Generic Derby (Embedded)|org.apache.derby.iapi.jdbc.AutoloadedDriver|" + + "jdbc:derby:test;create=true|sa", + "Generic H2 (Server)|org.h2.Driver|" + + "jdbc:h2:tcp://localhost/~/test|sa", + // this will be listed on top for new installations + "Generic H2 (Embedded)|org.h2.Driver|" + + "jdbc:h2:~/test|sa", + }; + + private static int ticker; + + /** + * The session timeout (the default is 30 minutes). + */ + private static final long SESSION_TIMEOUT = SysProperties.CONSOLE_TIMEOUT; + +// public static void main(String... args) throws IOException { +// String s = IOUtils.readStringAndClose(new java.io.FileReader( +// // "src/main/org/h2/server/web/res/_text_cs.prop"), -1); +// "src/main/org/h2/res/_messages_cs.prop"), -1); +// System.out.println(StringUtils.javaEncode("...")); +// String[] list = Locale.getISOLanguages(); +// for (int i = 0; i < list.length; i++) { +// System.out.print(list[i] + " "); +// } +// System.out.println(); +// String l = "de"; +// String lang = new java.util.Locale(l). +// getDisplayLanguage(new java.util.Locale(l)); +// System.out.println(new java.util.Locale(l).getDisplayLanguage()); +// System.out.println(lang); +// java.util.Locale.CHINESE.getDisplayLanguage(java.util.Locale.CHINESE); +// for (int i = 0; i < lang.length(); i++) { +// System.out.println(Integer.toHexString(lang.charAt(i)) + " "); +// } +// } + + // private URLClassLoader urlClassLoader; + private int port; + private boolean allowOthers; + private String externalNames; + private boolean isDaemon; + private final Set running = + Collections.synchronizedSet(new HashSet()); + private boolean ssl; + private byte[] adminPassword; + private final HashMap connInfoMap = new HashMap<>(); + + private long lastTimeoutCheck; + private final HashMap sessions = new HashMap<>(); + private final HashSet languages = new HashSet<>(); + private String startDateTime; + private ServerSocket serverSocket; + private String host; + private String url; + private ShutdownHandler shutdownHandler; + private Thread listenerThread; + private boolean ifExists = true; + private String key; + private boolean allowSecureCreation; + private boolean trace; + private TranslateThread translateThread; + private boolean allowChunked = true; + private String serverPropertiesDir = Constants.SERVER_PROPERTIES_DIR; + // null means the history is not allowed to be stored + private String commandHistoryString; + + /** + * Read the given file from the file system or from the resources. + * + * @param file the file name + * @return the data + * @throws IOException on failure + */ + byte[] getFile(String file) throws IOException { + trace("getFile <" + file + ">"); + byte[] data = Utils.getResource("/org/h2/server/web/res/" + file); + if (data == null) { + trace(" null"); + } else { + trace(" size=" + data.length); + } + return data; + } + + /** + * Remove this web thread from the set of running threads. + * + * @param t the thread to remove + */ + synchronized void remove(WebThread t) { + running.remove(t); + } + + private static String generateSessionId() { + byte[] buff = MathUtils.secureRandomBytes(16); + return StringUtils.convertBytesToHex(buff); + } + + /** + * Get the web session object for the given session id. + * + * @param sessionId the session id + * @return the web session or null + */ + WebSession getSession(String sessionId) { + long now = System.currentTimeMillis(); + if (lastTimeoutCheck + SESSION_TIMEOUT < now) { + for (String id : new ArrayList<>(sessions.keySet())) { + WebSession session = sessions.get(id); + if (session.lastAccess + SESSION_TIMEOUT < now) { + trace("timeout for " + id); + sessions.remove(id); + } + } + lastTimeoutCheck = now; + } + WebSession session = sessions.get(sessionId); + if (session != null) { + session.lastAccess = System.currentTimeMillis(); + } + return session; + } + + /** + * Create a new web session id and object. + * + * @param hostAddr the host address + * @return the web session object + */ + WebSession createNewSession(String hostAddr) { + String newId; + do { + newId = generateSessionId(); + } while (sessions.get(newId) != null); + WebSession session = new WebSession(this); + session.lastAccess = System.currentTimeMillis(); + session.put("sessionId", newId); + session.put("ip", hostAddr); + session.put("language", DEFAULT_LANGUAGE); + session.put("frame-border", "0"); + session.put("frameset-border", "4"); + sessions.put(newId, session); + // always read the english translation, + // so that untranslated text appears at least in english + readTranslations(session, DEFAULT_LANGUAGE); + return getSession(newId); + } + + String getStartDateTime() { + if (startDateTime == null) { + startDateTime = DateTimeFormatter.ofPattern("EEE, d MMM yyyy HH:mm:ss z", Locale.ENGLISH) + .format(ZonedDateTime.now(ZoneId.of("UTC"))); + } + return startDateTime; + } + + /** + * Returns the key for privileged connections. + * + * @return key key, or null + */ + String getKey() { + return key; + } + + /** + * Sets the key for privileged connections. + * + * @param key key, or null + */ + public void setKey(String key) { + if (!allowOthers) { + this.key = key; + } + } + + /** + * @param allowSecureCreation + * whether creation of databases using the key should be allowed + */ + public void setAllowSecureCreation(boolean allowSecureCreation) { + if (!allowOthers) { + this.allowSecureCreation = allowSecureCreation; + } + } + + @Override + public void init(String... args) { + // set the serverPropertiesDir, because it's used in loadProperties() + for (int i = 0; args != null && i < args.length; i++) { + if ("-properties".equals(args[i])) { + serverPropertiesDir = args[++i]; + } + } + Properties prop = loadProperties(); + port = SortedProperties.getIntProperty(prop, + "webPort", Constants.DEFAULT_HTTP_PORT); + ssl = SortedProperties.getBooleanProperty(prop, + "webSSL", false); + allowOthers = SortedProperties.getBooleanProperty(prop, + "webAllowOthers", false); + setExternalNames(SortedProperties.getStringProperty(prop, "webExternalNames", null)); + setAdminPassword(SortedProperties.getStringProperty(prop, "webAdminPassword", null)); + commandHistoryString = prop.getProperty(COMMAND_HISTORY); + for (int i = 0; args != null && i < args.length; i++) { + String a = args[i]; + if (Tool.isOption(a, "-webPort")) { + port = Integer.decode(args[++i]); + } else if (Tool.isOption(a, "-webSSL")) { + ssl = true; + } else if (Tool.isOption(a, "-webAllowOthers")) { + allowOthers = true; + } else if (Tool.isOption(a, "-webExternalNames")) { + setExternalNames(args[++i]); + } else if (Tool.isOption(a, "-webDaemon")) { + isDaemon = true; + } else if (Tool.isOption(a, "-baseDir")) { + String baseDir = args[++i]; + SysProperties.setBaseDir(baseDir); + } else if (Tool.isOption(a, "-ifExists")) { + ifExists = true; + } else if (Tool.isOption(a, "-ifNotExists")) { + ifExists = false; + } else if (Tool.isOption(a, "-webAdminPassword")) { + setAdminPassword(args[++i]); + } else if (Tool.isOption(a, "-properties")) { + // already set + i++; + } else if (Tool.isOption(a, "-trace")) { + trace = true; + } + } +// if (driverList != null) { +// try { +// String[] drivers = +// StringUtils.arraySplit(driverList, ',', false); +// URL[] urls = new URL[drivers.length]; +// for(int i=0; i entry : text.entrySet()) { + String value = (String) entry.getValue(); + if (value.startsWith("#")) { + entry.setValue(value.substring(1)); + } + } + } catch (IOException e) { + DbException.traceThrowable(e); + } + session.put("text", new HashMap<>(text)); + } + + ArrayList> getSessions() { + ArrayList> list = new ArrayList<>(sessions.size()); + for (WebSession s : sessions.values()) { + list.add(s.getInfo()); + } + return list; + } + + @Override + public String getType() { + return "Web Console"; + } + + @Override + public String getName() { + return "H2 Console Server"; + } + + void setAllowOthers(boolean b) { + if (b) { + key = null; + } + allowOthers = b; + } + + @Override + public boolean getAllowOthers() { + return allowOthers; + } + + void setExternalNames(String externalNames) { + this.externalNames = externalNames != null ? StringUtils.toLowerEnglish(externalNames) : null; + } + + String getExternalNames() { + return externalNames; + } + + void setSSL(boolean b) { + ssl = b; + } + + void setPort(int port) { + this.port = port; + } + + boolean getSSL() { + return ssl; + } + + @Override + public int getPort() { + return port; + } + + public boolean isCommandHistoryAllowed() { + return commandHistoryString != null; + } + + public void setCommandHistoryAllowed(boolean allowed) { + if (allowed) { + if (commandHistoryString == null) { + commandHistoryString = ""; + } + } else { + commandHistoryString = null; + } + } + + public ArrayList getCommandHistoryList() { + ArrayList result = new ArrayList<>(); + if (commandHistoryString == null) { + return result; + } + + // Split the commandHistoryString on non-escaped semicolons + // and unescape it. + StringBuilder sb = new StringBuilder(); + for (int end = 0;; end++) { + if (end == commandHistoryString.length() || + commandHistoryString.charAt(end) == ';') { + if (sb.length() > 0) { + result.add(sb.toString()); + sb.delete(0, sb.length()); + } + if (end == commandHistoryString.length()) { + break; + } + } else if (commandHistoryString.charAt(end) == '\\' && + end < commandHistoryString.length() - 1) { + sb.append(commandHistoryString.charAt(++end)); + } else { + sb.append(commandHistoryString.charAt(end)); + } + } + return result; + } + + /** + * Save the command history to the properties file. + * + * @param commandHistory the history + */ + public void saveCommandHistoryList(ArrayList commandHistory) { + StringBuilder sb = new StringBuilder(); + for (String s : commandHistory) { + if (sb.length() > 0) { + sb.append(';'); + } + sb.append(s.replace("\\", "\\\\").replace(";", "\\;")); + } + commandHistoryString = sb.toString(); + saveProperties(null); + } + + /** + * Get the connection information for this setting. + * + * @param name the setting name + * @return the connection information + */ + ConnectionInfo getSetting(String name) { + return connInfoMap.get(name); + } + + /** + * Update a connection information setting. + * + * @param info the connection information + */ + void updateSetting(ConnectionInfo info) { + connInfoMap.put(info.name, info); + info.lastAccess = ticker++; + } + + /** + * Remove a connection information setting from the list + * + * @param name the setting to remove + */ + void removeSetting(String name) { + connInfoMap.remove(name); + } + + private Properties loadProperties() { + try { + if ("null".equals(serverPropertiesDir)) { + return new Properties(); + } + return SortedProperties.loadProperties( + serverPropertiesDir + "/" + Constants.SERVER_PROPERTIES_NAME); + } catch (Exception e) { + DbException.traceThrowable(e); + return new Properties(); + } + } + + /** + * Get the list of connection information setting names. + * + * @return the connection info names + */ + String[] getSettingNames() { + ArrayList list = getSettings(); + String[] names = new String[list.size()]; + for (int i = 0; i < list.size(); i++) { + names[i] = list.get(i).name; + } + return names; + } + + /** + * Get the list of connection info objects. + * + * @return the list + */ + synchronized ArrayList getSettings() { + ArrayList settings = new ArrayList<>(); + if (connInfoMap.size() == 0) { + Properties prop = loadProperties(); + if (prop.size() == 0) { + for (String gen : GENERIC) { + ConnectionInfo info = new ConnectionInfo(gen); + settings.add(info); + updateSetting(info); + } + } else { + for (int i = 0;; i++) { + String data = prop.getProperty(Integer.toString(i)); + if (data == null) { + break; + } + ConnectionInfo info = new ConnectionInfo(data); + settings.add(info); + updateSetting(info); + } + } + } else { + settings.addAll(connInfoMap.values()); + } + Collections.sort(settings); + return settings; + } + + /** + * Save the settings to the properties file. + * + * @param prop null or the properties webPort, webAllowOthers, and webSSL + */ + synchronized void saveProperties(Properties prop) { + try { + if (prop == null) { + Properties old = loadProperties(); + prop = new SortedProperties(); + prop.setProperty("webPort", + Integer.toString(SortedProperties.getIntProperty(old, "webPort", port))); + prop.setProperty("webAllowOthers", + Boolean.toString(SortedProperties.getBooleanProperty(old, "webAllowOthers", allowOthers))); + if (externalNames != null) { + prop.setProperty("webExternalNames", externalNames); + } + prop.setProperty("webSSL", + Boolean.toString(SortedProperties.getBooleanProperty(old, "webSSL", ssl))); + if (adminPassword != null) { + prop.setProperty("webAdminPassword", StringUtils.convertBytesToHex(adminPassword)); + } + if (commandHistoryString != null) { + prop.setProperty(COMMAND_HISTORY, commandHistoryString); + } + } + ArrayList settings = getSettings(); + int len = settings.size(); + for (int i = 0; i < len; i++) { + ConnectionInfo info = settings.get(i); + if (info != null) { + prop.setProperty(Integer.toString(len - i - 1), info.getString()); + } + } + if (!"null".equals(serverPropertiesDir)) { + OutputStream out = FileUtils.newOutputStream( + serverPropertiesDir + "/" + Constants.SERVER_PROPERTIES_NAME, false); + prop.store(out, "H2 Server Properties"); + out.close(); + } + } catch (Exception e) { + DbException.traceThrowable(e); + } + } + + /** + * Open a database connection. + * + * @param driver the driver class name + * @param databaseUrl the database URL + * @param user the user name + * @param password the password + * @param userKey the key of privileged user + * @param networkConnectionInfo the network connection information + * @return the database connection + * @throws SQLException on failure + */ + Connection getConnection(String driver, String databaseUrl, String user, + String password, String userKey, NetworkConnectionInfo networkConnectionInfo) throws SQLException { + driver = driver.trim(); + databaseUrl = databaseUrl.trim(); + // do not trim the password, otherwise an + // encrypted H2 database with empty user password doesn't work + return JdbcUtils.getConnection(driver, databaseUrl, user.trim(), password, networkConnectionInfo, + ifExists && (!allowSecureCreation || key == null || !key.equals(userKey))); + } + + /** + * Shut down the web server. + */ + void shutdown() { + if (shutdownHandler != null) { + shutdownHandler.shutdown(); + } + } + + public void setShutdownHandler(ShutdownHandler shutdownHandler) { + this.shutdownHandler = shutdownHandler; + } + + /** + * Create a session with a given connection. + * + * @param conn the connection + * @return the URL of the web site to access this connection + * @throws SQLException on failure + */ + public String addSession(Connection conn) throws SQLException { + WebSession session = createNewSession("local"); + session.setShutdownServerOnDisconnect(); + session.setConnection(conn); + session.put("url", conn.getMetaData().getURL()); + String s = (String) session.get("sessionId"); + return url + "/frame.jsp?jsessionid=" + s; + } + + /** + * The translate thread reads and writes the file translation.properties + * once a second. + */ + private class TranslateThread extends Thread { + + private final Path file = Paths.get("translation.properties"); + private final Map translation; + private volatile boolean stopNow; + + TranslateThread(Map translation) { + this.translation = translation; + } + + public String getFileName() { + return file.toAbsolutePath().toString(); + } + + public void stopNow() { + this.stopNow = true; + try { + join(); + } catch (InterruptedException e) { + // ignore + } + } + + @Override + public void run() { + while (!stopNow) { + try { + SortedProperties sp = new SortedProperties(); + if (Files.exists(file)) { + InputStream in = Files.newInputStream(file); + sp.load(in); + translation.putAll(sp); + } else { + OutputStream out = Files.newOutputStream(file); + sp.putAll(translation); + sp.store(out, "Translation"); + } + Thread.sleep(1000); + } catch (Exception e) { + traceError(e); + } + } + } + + } + + /** + * Start the translation thread that reads the file once a second. + * + * @param translation the translation map + * @return the name of the file to translate + */ + String startTranslate(Map translation) { + if (translateThread != null) { + translateThread.stopNow(); + } + translateThread = new TranslateThread(translation); + translateThread.setDaemon(true); + translateThread.start(); + return translateThread.getFileName(); + } + + @Override + public boolean isDaemon() { + return isDaemon; + } + + void setAllowChunked(boolean allowChunked) { + this.allowChunked = allowChunked; + } + + boolean getAllowChunked() { + return allowChunked; + } + + byte[] getAdminPassword() { + return adminPassword; + } + + void setAdminPassword(String password) { + if (password == null || password.isEmpty()) { + adminPassword = null; + return; + } + if (password.length() == 128) { + try { + adminPassword = StringUtils.convertHexToBytes(password); + return; + } catch (Exception ex) {} + } + byte[] salt = MathUtils.secureRandomBytes(32); + byte[] hash = SHA256.getHashWithSalt(password.getBytes(StandardCharsets.UTF_8), salt); + byte[] total = Arrays.copyOf(salt, 64); + System.arraycopy(hash, 0, total, 32, 32); + adminPassword = total; + } + + /** + * Check the admin password. + * + * @param password the password to test + * @return true if admin password not configure, or admin password correct + */ + boolean checkAdminPassword(String password) { + if (adminPassword == null) { + return false; + } + byte[] salt = Arrays.copyOf(adminPassword, 32); + byte[] hash = new byte[32]; + System.arraycopy(adminPassword, 32, hash, 0, 32); + return Utils.compareSecure(hash, SHA256.getHashWithSalt(password.getBytes(StandardCharsets.UTF_8), salt)); + } + +} diff --git a/h2/src/main/org/h2/server/web/WebServlet.java b/h2/src/main/org/h2/server/web/WebServlet.java new file mode 100644 index 0000000..752cf6b --- /dev/null +++ b/h2/src/main/org/h2/server/web/WebServlet.java @@ -0,0 +1,169 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.server.web; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.Properties; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.h2.util.NetworkConnectionInfo; + +/** + * This servlet lets the H2 Console be used in a standard servlet container + * such as Tomcat or Jetty. + */ +public class WebServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + private transient WebServer server; + + @Override + public void init() { + ServletConfig config = getServletConfig(); + Enumeration en = config.getInitParameterNames(); + ArrayList list = new ArrayList<>(); + while (en.hasMoreElements()) { + String name = en.nextElement().toString(); + String value = config.getInitParameter(name); + if (!name.startsWith("-")) { + name = "-" + name; + } + list.add(name); + if (value.length() > 0) { + list.add(value); + } + } + String[] args = list.toArray(new String[0]); + server = new WebServer(); + server.setAllowChunked(false); + server.init(args); + } + + @Override + public void destroy() { + server.stop(); + } + + private boolean allow(HttpServletRequest req) { + if (server.getAllowOthers()) { + return true; + } + String addr = req.getRemoteAddr(); + try { + InetAddress address = InetAddress.getByName(addr); + return address.isLoopbackAddress(); + } catch (UnknownHostException | NoClassDefFoundError e) { + // Google App Engine does not allow java.net.InetAddress + return false; + } + + } + + private String getAllowedFile(HttpServletRequest req, String requestedFile) { + if (!allow(req)) { + return "notAllowed.jsp"; + } + if (requestedFile.length() == 0) { + return "index.do"; + } + return requestedFile; + } + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws IOException { + req.setCharacterEncoding("utf-8"); + String file = req.getPathInfo(); + if (file == null) { + resp.sendRedirect(req.getRequestURI() + "/"); + return; + } else if (file.startsWith("/")) { + file = file.substring(1); + } + file = getAllowedFile(req, file); + + // extract the request attributes + Properties attributes = new Properties(); + Enumeration en = req.getAttributeNames(); + while (en.hasMoreElements()) { + String name = en.nextElement().toString(); + String value = req.getAttribute(name).toString(); + attributes.put(name, value); + } + en = req.getParameterNames(); + while (en.hasMoreElements()) { + String name = en.nextElement().toString(); + String value = req.getParameter(name); + attributes.put(name, value); + } + + WebSession session = null; + String sessionId = attributes.getProperty("jsessionid"); + if (sessionId != null) { + session = server.getSession(sessionId); + } + WebApp app = new WebApp(server); + app.setSession(session, attributes); + String ifModifiedSince = req.getHeader("if-modified-since"); + + String scheme = req.getScheme(); + StringBuilder builder = new StringBuilder(scheme).append("://").append(req.getServerName()); + int serverPort = req.getServerPort(); + if (!(serverPort == 80 && scheme.equals("http") || serverPort == 443 && scheme.equals("https"))) { + builder.append(':').append(serverPort); + } + String path = builder.append(req.getContextPath()).toString(); + file = app.processRequest(file, new NetworkConnectionInfo(path, req.getRemoteAddr(), req.getRemotePort())); + session = app.getSession(); + + String mimeType = app.getMimeType(); + boolean cache = app.getCache(); + + if (cache && server.getStartDateTime().equals(ifModifiedSince)) { + resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + return; + } + byte[] bytes = server.getFile(file); + if (bytes == null) { + resp.sendError(HttpServletResponse.SC_NOT_FOUND); + bytes = ("File not found: " + file).getBytes(StandardCharsets.UTF_8); + } else { + if (session != null && file.endsWith(".jsp")) { + String page = new String(bytes, StandardCharsets.UTF_8); + page = PageParser.parse(page, session.map); + bytes = page.getBytes(StandardCharsets.UTF_8); + } + resp.setContentType(mimeType); + if (!cache) { + resp.setHeader("Cache-Control", "no-cache"); + } else { + resp.setHeader("Cache-Control", "max-age=10"); + resp.setHeader("Last-Modified", server.getStartDateTime()); + } + } + if (bytes != null) { + ServletOutputStream out = resp.getOutputStream(); + out.write(bytes); + } + } + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse resp) + throws IOException { + doGet(req, resp); + } + +} diff --git a/h2/src/main/org/h2/server/web/WebSession.java b/h2/src/main/org/h2/server/web/WebSession.java new file mode 100644 index 0000000..bda717d --- /dev/null +++ b/h2/src/main/org/h2/server/web/WebSession.java @@ -0,0 +1,275 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.server.web; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Locale; + +import org.h2.bnf.Bnf; +import org.h2.bnf.context.DbContents; +import org.h2.bnf.context.DbContextRule; +import org.h2.message.DbException; + +/** + * The web session keeps all data of a user session. + * This class is used by the H2 Console. + */ +class WebSession { + + private static final int MAX_HISTORY = 1000; + + /** + * The last time this client sent a request. + */ + long lastAccess; + + /** + * The session attribute map. + */ + final HashMap map = new HashMap<>(); + + /** + * The current locale. + */ + Locale locale; + + /** + * The currently executing statement. + */ + Statement executingStatement; + + /** + * The current updatable result set. + */ + ResultSet result; + + private final WebServer server; + + private final ArrayList commandHistory; + + private Connection conn; + private DatabaseMetaData meta; + private DbContents contents = new DbContents(); + private Bnf bnf; + private boolean shutdownServerOnDisconnect; + + WebSession(WebServer server) { + this.server = server; + // This must be stored in the session rather than in the server. + // Otherwise, one client could allow + // saving history for others (insecure). + this.commandHistory = server.getCommandHistoryList(); + } + + /** + * Put an attribute value in the map. + * + * @param key the key + * @param value the new value + */ + void put(String key, Object value) { + map.put(key, value); + } + + /** + * Get the value for the given key. + * + * @param key the key + * @return the value + */ + Object get(String key) { + if ("sessions".equals(key)) { + return server.getSessions(); + } + return map.get(key); + } + + /** + * Remove a session attribute from the map. + * + * @param key the key + * @return value that was associated with the key, or null + */ + Object remove(String key) { + return map.remove(key); + } + + /** + * Get the BNF object. + * + * @return the BNF object + */ + Bnf getBnf() { + return bnf; + } + + /** + * Load the SQL grammar BNF. + */ + void loadBnf() { + try { + Bnf newBnf = Bnf.getInstance(null); + DbContextRule columnRule = + new DbContextRule(contents, DbContextRule.COLUMN); + DbContextRule newAliasRule = + new DbContextRule(contents, DbContextRule.NEW_TABLE_ALIAS); + DbContextRule aliasRule = + new DbContextRule(contents, DbContextRule.TABLE_ALIAS); + DbContextRule tableRule = + new DbContextRule(contents, DbContextRule.TABLE); + DbContextRule schemaRule = + new DbContextRule(contents, DbContextRule.SCHEMA); + DbContextRule columnAliasRule = + new DbContextRule(contents, DbContextRule.COLUMN_ALIAS); + DbContextRule procedure = + new DbContextRule(contents, DbContextRule.PROCEDURE); + newBnf.updateTopic("procedure", procedure); + newBnf.updateTopic("column_name", columnRule); + newBnf.updateTopic("new_table_alias", newAliasRule); + newBnf.updateTopic("table_alias", aliasRule); + newBnf.updateTopic("column_alias", columnAliasRule); + newBnf.updateTopic("table_name", tableRule); + newBnf.updateTopic("schema_name", schemaRule); + newBnf.linkStatements(); + bnf = newBnf; + } catch (Exception e) { + // ok we don't have the bnf + server.traceError(e); + } + } + + /** + * Get the SQL statement from history. + * + * @param id the history id + * @return the SQL statement + */ + String getCommand(int id) { + return commandHistory.get(id); + } + + /** + * Add a SQL statement to the history. + * + * @param sql the SQL statement + */ + void addCommand(String sql) { + if (sql == null) { + return; + } + sql = sql.trim(); + if (sql.isEmpty()) { + return; + } + if (commandHistory.size() > MAX_HISTORY) { + commandHistory.remove(0); + } + int idx = commandHistory.indexOf(sql); + if (idx >= 0) { + commandHistory.remove(idx); + } + commandHistory.add(sql); + if (server.isCommandHistoryAllowed()) { + server.saveCommandHistoryList(commandHistory); + } + } + + /** + * Get the list of SQL statements in the history. + * + * @return the commands + */ + ArrayList getCommandHistory() { + return commandHistory; + } + + /** + * Update session meta data information and get the information in a map. + * + * @return a map containing the session meta data + */ + HashMap getInfo() { + HashMap m = new HashMap<>(); + m.putAll(map); + m.put("lastAccess", new Timestamp(lastAccess).toString()); + try { + m.put("url", conn == null ? + "${text.admin.notConnected}" : conn.getMetaData().getURL()); + m.put("user", conn == null ? + "-" : conn.getMetaData().getUserName()); + m.put("lastQuery", commandHistory.isEmpty() ? + "" : commandHistory.get(0)); + m.put("executing", executingStatement == null ? + "${text.admin.no}" : "${text.admin.yes}"); + } catch (SQLException e) { + DbException.traceThrowable(e); + } + return m; + } + + void setConnection(Connection conn) throws SQLException { + this.conn = conn; + if (conn == null) { + meta = null; + } else { + meta = conn.getMetaData(); + } + contents = new DbContents(); + } + + DatabaseMetaData getMetaData() { + return meta; + } + + Connection getConnection() { + return conn; + } + + DbContents getContents() { + return contents; + } + + /** + * Shutdown the server when disconnecting. + */ + void setShutdownServerOnDisconnect() { + this.shutdownServerOnDisconnect = true; + } + + boolean getShutdownServerOnDisconnect() { + return shutdownServerOnDisconnect; + } + + /** + * Close the connection and stop the statement if one is currently + * executing. + */ + void close() { + if (executingStatement != null) { + try { + executingStatement.cancel(); + } catch (Exception e) { + // ignore + } + } + if (conn != null) { + try { + conn.close(); + } catch (Exception e) { + // ignore + } + } + + } + +} diff --git a/h2/src/main/org/h2/server/web/WebThread.java b/h2/src/main/org/h2/server/web/WebThread.java new file mode 100644 index 0000000..2c6a7fd --- /dev/null +++ b/h2/src/main/org/h2/server/web/WebThread.java @@ -0,0 +1,429 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.server.web; + +import java.io.BufferedInputStream; +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; +import java.nio.charset.StandardCharsets; +import java.util.Iterator; +import java.util.Locale; +import java.util.Properties; +import java.util.StringTokenizer; + +import org.h2.engine.SysProperties; +import org.h2.message.DbException; +import org.h2.util.IOUtils; +import org.h2.util.NetUtils; +import org.h2.util.NetworkConnectionInfo; +import org.h2.util.StringUtils; +import org.h2.util.Utils; + +/** + * For each connection to a session, an object of this class is created. + * This class is used by the H2 Console. + */ +class WebThread extends WebApp implements Runnable { + + private static final byte[] RN = { '\r', '\n' }; + + private static final byte[] RNRN = { '\r', '\n', '\r', '\n' }; + + protected OutputStream output; + protected final Socket socket; + private final Thread thread; + private InputStream input; + private String host; + private int dataLength; + private String ifModifiedSince; + + WebThread(Socket socket, WebServer server) { + super(server); + this.socket = socket; + thread = new Thread(this, "H2 Console thread"); + } + + /** + * Start the thread. + */ + void start() { + thread.start(); + } + + /** + * Wait until the thread is stopped. + * + * @param millis the maximum number of milliseconds to wait + * @throws InterruptedException if interrupted + */ + void join(int millis) throws InterruptedException { + thread.join(millis); + } + + /** + * Close the connection now. + */ + void stopNow() { + this.stop = true; + try { + socket.close(); + } catch (IOException e) { + // ignore + } + } + + private String getAllowedFile(String requestedFile) { + if (!allow()) { + return "notAllowed.jsp"; + } + if (requestedFile.length() == 0) { + return "index.do"; + } + if (requestedFile.charAt(0) == '?') { + return "index.do" + requestedFile; + } + return requestedFile; + } + + @Override + public void run() { + try { + input = new BufferedInputStream(socket.getInputStream()); + output = new BufferedOutputStream(socket.getOutputStream()); + while (!stop) { + if (!process()) { + break; + } + } + } catch (Exception e) { + DbException.traceThrowable(e); + } + IOUtils.closeSilently(output); + IOUtils.closeSilently(input); + try { + socket.close(); + } catch (IOException e) { + // ignore + } finally { + server.remove(this); + } + } + + @SuppressWarnings("unchecked") + private boolean process() throws IOException { + String head = readHeaderLine(); + boolean get = head.startsWith("GET "); + if ((!get && !head.startsWith("POST ")) || !head.endsWith(" HTTP/1.1")) { + writeSimple("HTTP/1.1 400 Bad Request", "Bad request"); + return false; + } + String file = StringUtils.trimSubstring(head, get ? 4 : 5, head.length() - 9); + if (file.isEmpty() || file.charAt(0) != '/') { + writeSimple("HTTP/1.1 400 Bad Request", "Bad request"); + return false; + } + attributes = new Properties(); + boolean keepAlive = parseHeader(); + if (!checkHost(host)) { + return false; + } + file = file.substring(1); + trace(head + ": " + file); + file = getAllowedFile(file); + int paramIndex = file.indexOf('?'); + session = null; + String key = null; + if (paramIndex >= 0) { + String attrib = file.substring(paramIndex + 1); + parseAttributes(attrib); + String sessionId = attributes.getProperty("jsessionid"); + key = attributes.getProperty("key"); + file = file.substring(0, paramIndex); + session = server.getSession(sessionId); + } + parseBodyAttributes(); + file = processRequest(file, + new NetworkConnectionInfo( + NetUtils.ipToShortForm(new StringBuilder(server.getSSL() ? "https://" : "http://"), + socket.getLocalAddress().getAddress(), true) // + .append(':').append(socket.getLocalPort()).toString(), // + socket.getInetAddress().getAddress(), socket.getPort(), null)); + if (file.length() == 0) { + // asynchronous request + return true; + } + String message; + if (cache && ifModifiedSince != null && ifModifiedSince.equals(server.getStartDateTime())) { + writeSimple("HTTP/1.1 304 Not Modified", (byte[]) null); + return keepAlive; + } + byte[] bytes = server.getFile(file); + if (bytes == null) { + writeSimple("HTTP/1.1 404 Not Found", "File not found: " + file); + return keepAlive; + } + if (session != null && file.endsWith(".jsp")) { + if (key != null) { + session.put("key", key); + } + String page = new String(bytes, StandardCharsets.UTF_8); + if (SysProperties.CONSOLE_STREAM) { + Iterator it = (Iterator) session.map.remove("chunks"); + if (it != null) { + message = "HTTP/1.1 200 OK\r\n"; + message += "Content-Type: " + mimeType + "\r\n"; + message += "Cache-Control: no-cache\r\n"; + message += "Transfer-Encoding: chunked\r\n"; + message += "\r\n"; + trace(message); + output.write(message.getBytes(StandardCharsets.ISO_8859_1)); + while (it.hasNext()) { + String s = it.next(); + s = PageParser.parse(s, session.map); + bytes = s.getBytes(StandardCharsets.UTF_8); + if (bytes.length == 0) { + continue; + } + output.write(Integer.toHexString(bytes.length).getBytes(StandardCharsets.ISO_8859_1)); + output.write(RN); + output.write(bytes); + output.write(RN); + output.flush(); + } + output.write('0'); + output.write(RNRN); + output.flush(); + return keepAlive; + } + } + page = PageParser.parse(page, session.map); + bytes = page.getBytes(StandardCharsets.UTF_8); + } + message = "HTTP/1.1 200 OK\r\n"; + message += "Content-Type: " + mimeType + "\r\n"; + if (!cache) { + message += "Cache-Control: no-cache\r\n"; + } else { + message += "Cache-Control: max-age=10\r\n"; + message += "Last-Modified: " + server.getStartDateTime() + "\r\n"; + } + message += "Content-Length: " + bytes.length + "\r\n"; + message += "\r\n"; + trace(message); + output.write(message.getBytes(StandardCharsets.ISO_8859_1)); + output.write(bytes); + output.flush(); + return keepAlive; + } + + private void writeSimple(String status, String text) throws IOException { + writeSimple(status, text != null ? text.getBytes(StandardCharsets.UTF_8) : null); + } + + private void writeSimple(String status, byte[] bytes) throws IOException { + trace(status); + output.write(status.getBytes(StandardCharsets.ISO_8859_1)); + if (bytes != null) { + output.write(RN); + String contentLength = "Content-Length: " + bytes.length; + trace(contentLength); + output.write(contentLength.getBytes(StandardCharsets.ISO_8859_1)); + output.write(RNRN); + output.write(bytes); + } else { + output.write(RNRN); + } + output.flush(); + } + + private boolean checkHost(String host) throws IOException { + if (host == null) { + writeSimple("HTTP/1.1 400 Bad Request", "Bad request"); + return false; + } + int index = host.indexOf(':'); + if (index >= 0) { + host = host.substring(0, index); + } + if (host.isEmpty()) { + return false; + } + host = StringUtils.toLowerEnglish(host); + if (host.equals(server.getHost()) || host.equals("localhost") || host.equals("127.0.0.1")) { + return true; + } + String externalNames = server.getExternalNames(); + if (externalNames != null && !externalNames.isEmpty()) { + for (String s : externalNames.split(",")) { + if (host.equals(s.trim())) { + return true; + } + } + } + writeSimple("HTTP/1.1 404 Not Found", "Host " + host + " not found"); + return false; + } + + private String readHeaderLine() throws IOException { + StringBuilder buff = new StringBuilder(); + while (true) { + int c = input.read(); + if (c == -1) { + throw new IOException("Unexpected EOF"); + } else if (c == '\r') { + if (input.read() == '\n') { + return buff.length() > 0 ? buff.toString() : null; + } + } else if (c == '\n') { + return buff.length() > 0 ? buff.toString() : null; + } else { + buff.append((char) c); + } + } + } + + private void parseBodyAttributes() throws IOException { + if (dataLength > 0) { + byte[] bytes = Utils.newBytes(dataLength); + for (int pos = 0; pos < dataLength;) { + pos += input.read(bytes, pos, dataLength - pos); + } + String s = new String(bytes, StandardCharsets.UTF_8); + parseAttributes(s); + } + } + + private void parseAttributes(String s) { + trace("data=" + s); + while (s != null) { + int idx = s.indexOf('='); + if (idx >= 0) { + String property = s.substring(0, idx); + s = s.substring(idx + 1); + idx = s.indexOf('&'); + String value; + if (idx >= 0) { + value = s.substring(0, idx); + s = s.substring(idx + 1); + } else { + value = s; + } + String attr = StringUtils.urlDecode(value); + attributes.put(property, attr); + } else { + break; + } + } + trace(attributes.toString()); + } + + private boolean parseHeader() throws IOException { + boolean keepAlive = false; + trace("parseHeader"); + int len = 0; + host = null; + ifModifiedSince = null; + boolean multipart = false; + for (String line; (line = readHeaderLine()) != null;) { + trace(" " + line); + String lower = StringUtils.toLowerEnglish(line); + if (lower.startsWith("host")) { + host = getHeaderLineValue(line); + } else if (lower.startsWith("if-modified-since")) { + ifModifiedSince = getHeaderLineValue(line); + } else if (lower.startsWith("connection")) { + String conn = getHeaderLineValue(line); + if ("keep-alive".equals(conn)) { + keepAlive = true; + } + } else if (lower.startsWith("content-type")) { + String type = getHeaderLineValue(line); + if (type.startsWith("multipart/form-data")) { + multipart = true; + } + } else if (lower.startsWith("content-length")) { + len = Integer.parseInt(getHeaderLineValue(line)); + trace("len=" + len); + } else if (lower.startsWith("user-agent")) { + boolean isWebKit = lower.contains("webkit/"); + if (isWebKit && session != null) { + // workaround for what seems to be a WebKit bug: + // https://bugs.chromium.org/p/chromium/issues/detail?id=6402 + session.put("frame-border", "1"); + session.put("frameset-border", "2"); + } + } else if (lower.startsWith("accept-language")) { + Locale locale = session == null ? null : session.locale; + if (locale == null) { + String languages = getHeaderLineValue(line); + StringTokenizer tokenizer = new StringTokenizer(languages, ",;"); + while (tokenizer.hasMoreTokens()) { + String token = tokenizer.nextToken(); + if (!token.startsWith("q=")) { + if (server.supportsLanguage(token)) { + int dash = token.indexOf('-'); + if (dash >= 0) { + String language = token.substring(0, dash); + String country = token.substring(dash + 1); + locale = new Locale(language, country); + } else { + locale = new Locale(token, ""); + } + headerLanguage = locale.getLanguage(); + if (session != null) { + session.locale = locale; + session.put("language", headerLanguage); + server.readTranslations(session, headerLanguage); + } + break; + } + } + } + } + } else if (StringUtils.isWhitespaceOrEmpty(line)) { + break; + } + } + dataLength = 0; + if (multipart) { + // not supported + } else if (len > 0) { + dataLength = len; + } + return keepAlive; + } + + private static String getHeaderLineValue(String line) { + return StringUtils.trimSubstring(line, line.indexOf(':') + 1); + } + + @Override + protected String adminShutdown() { + stopNow(); + return super.adminShutdown(); + } + + private boolean allow() { + if (server.getAllowOthers()) { + return true; + } + try { + return NetUtils.isLocalAddress(socket); + } catch (UnknownHostException e) { + server.traceError(e); + return false; + } + } + + private void trace(String s) { + server.trace(s); + } +} diff --git a/h2/src/main/org/h2/server/web/package.html b/h2/src/main/org/h2/server/web/package.html new file mode 100644 index 0000000..4eab3b2 --- /dev/null +++ b/h2/src/main/org/h2/server/web/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +The H2 Console tool. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/server/web/res/_text_cs.prop b/h2/src/main/org/h2/server/web/res/_text_cs.prop new file mode 100644 index 0000000..4e08223 --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/_text_cs.prop @@ -0,0 +1,164 @@ +.translator=Hannibal (http://hannibal.cestiny.cz/) +a.help=Nápověda +a.language=Čeština +a.lynxNotSupported=Bohužel, Lynx není zatím podporován +a.password=Heslo +a.remoteConnectionsDisabled=Bohužel, vzdálené připojení ('webAllowOthers') je na tomto serveru zakázáno. +a.title=H2 konzole +a.tools=Nástroje +a.user=Uživatelské jméno +admin.executing=Probíhá zpracování +admin.ip=IP +admin.lastAccess=Poslední přístup +admin.lastQuery=Poslední dotaz +admin.no=ne +admin.notConnected=nepřipojeno +admin.url=URL +admin.yes=ano +adminAllow=Povolení klienti +adminConnection=Zabezpečení připojení +adminHttp=Použít nešifrované HTTP připojení +adminHttps=Použít šifrované SSL (HTTPS) připojení +adminLocal=Povolit pouze lokální připojení +adminLogin=Správa přístupu +adminLoginCancel=Zrušit +adminLoginOk=OK +adminLogout=Odhlásit +adminOthers=Povolit připojení z jiných počítačů +adminWebExternalNames=#External names or addresses of this server (comma-separated) +adminPort=Číslo portu +adminPortWeb=Číslo portu webového serveru +adminRestart=Změny se projeví po restartu serveru. +adminSave=Uložit +adminSessions=Aktivní přístupy +adminShutdown=Zastavit +adminTitle=H2 konzole - Předvolby +adminTranslateHelp=Přeložit nebo zlepšit překlad H2 konzole. +adminTranslateStart=Přeložit +helpAction=Akce +helpAddAnotherRow=Přidat další řádek +helpAddDrivers=Přidání ovladačů k databázi +helpAddDriversText=Dodatečné ovladače k databázi lze zaregistrovat přidáním cesty k Jar souboru s ovladačem do proměnné prostředí H2DRIVERS nebo CLASSPATH. Příklad (Windows): k přidání knihovny s ovladačem k databázi C:/Programs/hsqldb/lib/hsqldb.jar, nastavíme hodnotu proměnné prostředí H2DRIVERS na C:/Programs/hsqldb/lib/hsqldb.jar. +helpAddRow=Přidat nový řádek +helpCommandHistory=Zobrazí historii příkazů +helpCreateTable=Vytvořit novou tabulku +helpDeleteRow=Smazat řádek +helpDisconnect=Odpojení od databáze +helpDisplayThis=Zobrazí tuto stránku s nápovědou +helpDropTable=Smazat tabulku, pokud existuje +helpExecuteCurrent=Provede daný SQL příkaz +helpExecuteSelected=#Executes the SQL statement defined by the text selection +helpIcon=Ikona +helpImportantCommands=Důležité příkazy +helpOperations=Operace +helpQuery=Dotaz na tabulku +helpSampleSQL=Ukázka SQL skriptu +helpStatements=SQL příkazy +helpUpdate=Změnit hodnotu v řádku +helpWithColumnsIdName=obsahující sloupce ID a NAME +key.alt=#Alt +key.ctrl=#Ctrl +key.enter=#Enter +key.shift=#Shift +key.space=#Space +login.connect=Připojit +login.driverClass=Třída ovladače +login.driverNotFound=Ovladač k databázi nebyl nalezen
Postup pro přidání ovladače naleznete v sekci Nápověda +login.goAdmin=Předvolby +login.jdbcUrl=JDBC URL +login.language=Jazyk +login.login=Přihlášení +login.remove=Odstranit +login.save=Uložit +login.savedSetting=Uložená nastavení +login.settingName=Název nastavení +login.testConnection=Vyzkoušet připojení +login.testSuccessful=Připojení úspěšné +login.welcome=H2 konzole +result.1row=1 řádek +result.autoCommitOff=Automatické vkládání je nyní VYPNUTÉ +result.autoCommitOn=Automatické vkládání je nyní ZAPNUTÉ +result.bytes=bytů +result.characters=znaků +result.maxrowsSet=Nastaven maximální počet řádků +result.noRows=žádné řádky +result.noRunningStatement=V tuto chvíli se neprovádí žádný příkaz +result.rows=řádků +result.statementWasCanceled=Příkaz byl zrušen +result.updateCount=Počet změn +resultEdit.action=Akce +resultEdit.add=Přidat +resultEdit.cancel=Zrušit +resultEdit.delete=Smazat +resultEdit.edit=Upravit +resultEdit.editResult=Upravit +resultEdit.save=Uložit +toolbar.all=Vše +toolbar.autoCommit=Automatické vkládání +toolbar.autoComplete=Automatické dokončování +toolbar.autoComplete.full=Úplné +toolbar.autoComplete.normal=Normální +toolbar.autoComplete.off=Vypnuto +toolbar.autoSelect=#Auto select +toolbar.autoSelect.off=Vypnuto +toolbar.autoSelect.on=#On +toolbar.cancelStatement=Zrušit prováděný příkaz +toolbar.clear=Vyčistit +toolbar.commit=Vložit +toolbar.disconnect=Odpojit +toolbar.history=Historie příkazů +toolbar.maxRows=Maximum řádků +toolbar.refresh=Aktualizovat +toolbar.rollback=Vrátit změny +toolbar.run=Provést příkaz +toolbar.runSelected=#Run Selected +toolbar.sqlStatement=SQL příkaz +tools.backup=Zálohovat +tools.backup.help=Provede zálohu databáze. +tools.changeFileEncryption=Změnit šifrování souboru +tools.changeFileEncryption.help=Umožňuje změnu šifrovacího hesla a algoritmu souboru databáze. +tools.cipher=Šifrování (AES nebo XTEA) +tools.commandLine=Příkazová řádka +tools.convertTraceFile=Převést transakční soubor +tools.convertTraceFile.help=Převede soubor .trace.db na Java aplikaci a SQL skript. +tools.createCluster=Vytvořit cluster +tools.createCluster.help=Vytvoří cluster ze samostatné databáze. +tools.databaseName=Název databáze +tools.decryptionPassword=Heslo k dešifrování +tools.deleteDbFiles=Smazat databázové soubory +tools.deleteDbFiles.help=Vymaže všechny soubory, které patří databázi. +tools.directory=Adresář +tools.encryptionPassword=Heslo k šifrování +tools.javaDirectoryClassName=Java adresář a název třídy +tools.recover=Zotavení +tools.recover.help=Pomůže zotavit poškozenou databázi. +tools.restore=Obnovit +tools.restore.help=Obnoví zálohu databáze. +tools.result=Výsledek +tools.run=Spustit +tools.runScript=Spustit skript +tools.runScript.help=Spustí SQL skript. +tools.script=Skript +tools.script.help=Umožní převést databázi na SQL skript pro účely zálohy či přesunu. +tools.scriptFileName=Název souboru skriptu +tools.serverList=Seznam serverů +tools.sourceDatabaseName=Název zdrojové databáze +tools.sourceDatabaseURL=URL zdrojové databáze +tools.sourceDirectory=Zdrojový adresář +tools.sourceFileName=Název zdrojového souboru +tools.sourceScriptFileName=Název zdrojového souboru skriptu +tools.targetDatabaseName=Název cílové databáze +tools.targetDatabaseURL=URL cílové databáze +tools.targetDirectory=Cílový adresář +tools.targetFileName=Název cílového souboru +tools.targetScriptFileName=Název cílového souboru skriptu +tools.traceFileName=Název transakčního souboru +tree.admin=Admin +tree.current=Současná hodnota +tree.hashed=Hešováno +tree.increment=Přírůstek +tree.indexes=Indexy +tree.nonUnique=Neunikátní +tree.sequences=Sekvence +tree.unique=Unikátní +tree.users=Uživatelé diff --git a/h2/src/main/org/h2/server/web/res/_text_de.prop b/h2/src/main/org/h2/server/web/res/_text_de.prop new file mode 100644 index 0000000..846bcbd --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/_text_de.prop @@ -0,0 +1,164 @@ +.translator=Thomas Mueller +a.help=Hilfe +a.language=Deutsch +a.lynxNotSupported=Dieser Browser unterstützt keine Frames. Frames (und Javascript) werden benötigt. +a.password=Passwort +a.remoteConnectionsDisabled=Verbindungen von anderen Rechnern sind nicht freigegeben ('webAllowOthers'). +a.title=H2 Console +a.tools=Tools +a.user=Benutzername +admin.executing=Aktive Ausführung +admin.ip=IP +admin.lastAccess=Letzter Zugriff +admin.lastQuery=Letzter Befehl +admin.no=Nein +admin.notConnected=nicht verbunden +admin.url=URL +admin.yes=Ja +adminAllow=Zugelassene Verbindungen +adminConnection=Verbindungssicherheit +adminHttp=Unverschlüsselte HTTP Verbindungen verwenden +adminHttps=Verschlüsselte HTTPS Verbindungen verwenden +adminLocal=Nur lokale Verbindungen erlauben +adminLogin=Administration Login +adminLoginCancel=Abbrechen +adminLoginOk=OK +adminLogout=Beenden +adminOthers=Verbindungen von anderen Computern erlauben +adminWebExternalNames=#External names or addresses of this server (comma-separated) +adminPort=Admin Port +adminPortWeb=Web-Server Port +adminRestart=Änderungen werden nach einem Neustart des Servers aktiv. +adminSave=Speichern +adminSessions=Aktive Verbindungen +adminShutdown=Herunterfahren +adminTitle=H2 Console Optionen +adminTranslateHelp=Die H2 Console übersetzen oder die Übersetzung verbessern. +adminTranslateStart=Übersetzen +helpAction=Aktion +helpAddAnotherRow=Fügt einen weiteren Datensatz hinzu +helpAddDrivers=Datenbank Treiber hinzufügen +helpAddDriversText=Es ist möglich, zusätzliche Datenbank-Treiber zu laden, indem die Pfade der Treiber-Dateien in den Umgebungsvariablen H2DRIVERS oder CLASSPATH eingetragen werden. Beispiel (Windows): Um den Datenbank-Treiber mit dem Jar-File C:/Programs/hsqldb/lib/hsqldb.jar hinzuzufügen, setzen Sie die Umgebungvariable H2DRIVERS auf C:/Programs/hsqldb/lib/hsqldb.jar. +helpAddRow=Fügt einen Datensatz hinzu +helpCommandHistory=Zeigt die Befehls-Chronik +helpCreateTable=Erzeugt eine neue Tabelle +helpDeleteRow=Entfernt einen Datensatz +helpDisconnect=Trennt die Verbindung zur Datenbank +helpDisplayThis=Zeigt diese Hilfe Seite +helpDropTable=Löscht die Tabelle falls es sie gibt +helpExecuteCurrent=Führt den aktuellen SQL Befehl aus +helpExecuteSelected=Führt den SQL Befehl des ausgwählten Textes aus +helpIcon=Schaltfläche +helpImportantCommands=Wichtige Befehle +helpOperations=Operationen +helpQuery=Fragt die Tabelle ab +helpSampleSQL=Beispiel SQL Skript +helpStatements=SQL Befehle +helpUpdate=Ändert Daten in einer Zeile +helpWithColumnsIdName=mit zwei Spalten +key.alt=Alt +key.ctrl=Strg +key.enter=Enter +key.shift=Umsch +key.space=Leer +login.connect=Verbinden +login.driverClass=Datenbank-Treiber Klasse +login.driverNotFound=Datenbank-Treiber nicht gefunden
Für Informationen zum Hinzufügen von Treibern siehe Hilfe +login.goAdmin=Optionen +login.jdbcUrl=JDBC URL +login.language=Sprache +login.login=Login +login.remove=Entfernen +login.save=Speichern +login.savedSetting=Gespeicherte Einstellung +login.settingName=Einstellungs-Name +login.testConnection=Verbindung testen +login.testSuccessful=Test erfolgreich +login.welcome=H2 Console +result.1row=1 Datensatz +result.autoCommitOff=Auto-Commit ist jetzt ausgeschaltet +result.autoCommitOn=Auto-Commit ist jetzt eingeschaltet +result.bytes=Bytes +result.characters=Characters +result.maxrowsSet=Maximale Anzahl Zeilen ist jetzt gesetzt +result.noRows=keine Datensätze +result.noRunningStatement=Im Moment wird kein Befehl ausgeführt +result.rows=Datensätze +result.statementWasCanceled=Der Befehl wurde abgebrochen +result.updateCount=Änderungen +resultEdit.action=Aktion +resultEdit.add=Hinzufügen +resultEdit.cancel=Abbrechen +resultEdit.delete=Löschen +resultEdit.edit=Bearbeiten +resultEdit.editResult=Bearbeiten +resultEdit.save=Speichern +toolbar.all=Alle +toolbar.autoCommit=Auto-Commit +toolbar.autoComplete=Auto-Complete +toolbar.autoComplete.full=Alles +toolbar.autoComplete.normal=Normal +toolbar.autoComplete.off=Aus +toolbar.autoSelect=Automatische Auswahl +toolbar.autoSelect.off=Aus +toolbar.autoSelect.on=An +toolbar.cancelStatement=Laufenden Befehl abbrechen +toolbar.clear=Leeren +toolbar.commit=Commit (Abschliessen/Speichern) +toolbar.disconnect=Verbindung trennen +toolbar.history=Befehls-Chronik +toolbar.maxRows=Maximale Anzahl Zeilen +toolbar.refresh=Aktualisieren +toolbar.rollback=Rollback (Rückgängig) +toolbar.run=Ausführen +toolbar.runSelected=Ausgewähltes Ausführen +toolbar.sqlStatement=SQL Befehl +tools.backup=Backup +tools.backup.help=Erzeugt eine Sicherheitskopie einer Datenbank. +tools.changeFileEncryption=ChangeFileEncryption +tools.changeFileEncryption.help=Erlaubt, Datei Verschlüsselungs-Passwort und -Algorithmus einer Datenbank zu ändern. +tools.cipher=Verschlüsselung (AES oder XTEA) +tools.commandLine=Kommandozeile +tools.convertTraceFile=ConvertTraceFile +tools.convertTraceFile.help=Konvertiert eine .trace.db Datei in eine Java Applikation und ein SQL Skript. +tools.createCluster=CreateCluster +tools.createCluster.help=Generiert ein Cluster aus einer autonomen Datenbank. +tools.databaseName=Datenbankname +tools.decryptionPassword=Entschlüsselungs-Passwort +tools.deleteDbFiles=DeleteDbFiles +tools.deleteDbFiles.help=Löscht alle Dateien die zu einer Datenbank gehören. +tools.directory=Verzeichnis +tools.encryptionPassword=Verschlüsselungs-Passwort +tools.javaDirectoryClassName=Java Verzeichnis- und Klassen-Name +tools.recover=Recover +tools.recover.help=Hilft bei der Reparatur einer beschädigten Datenbank. +tools.restore=Restore +tools.restore.help=Stellt eine Datenbank aus einem Backup her. +tools.result=Ergebnis +tools.run=Start +tools.runScript=RunScript +tools.runScript.help=Führt ein SQL Skript aus. +tools.script=Script +tools.script.help=Generiert ein SQL Skript einer Datenbank für Backup- und Migrationszwecke. +tools.scriptFileName=Skript Dateiname +tools.serverList=Server Liste +tools.sourceDatabaseName=Quell-Datenbankname +tools.sourceDatabaseURL=Quell-Datenbank URL +tools.sourceDirectory=Quell-Verzeichnis +tools.sourceFileName=Quell-Dateiname +tools.sourceScriptFileName=Dateiname des Skripts (Quelle) +tools.targetDatabaseName=Ziel-Datenbankname +tools.targetDatabaseURL=Ziel-Datenbank URL +tools.targetDirectory=Ziel-Verzeichnis +tools.targetFileName=Ziel-Dateiname +tools.targetScriptFileName=Dateiname des Skripts (Ziel) +tools.traceFileName=Name der Trace Datei +tree.admin=Administrator +tree.current=Aktueller Wert +tree.hashed=Hash-basiert +tree.increment=Inkrement +tree.indexes=Indexe +tree.nonUnique=nicht eindeutig +tree.sequences=Sequenzen +tree.unique=eindeutig +tree.users=Benutzer diff --git a/h2/src/main/org/h2/server/web/res/_text_en.prop b/h2/src/main/org/h2/server/web/res/_text_en.prop new file mode 100644 index 0000000..b6f0fb8 --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/_text_en.prop @@ -0,0 +1,164 @@ +.translator=Thomas Mueller +a.help=Help +a.language=English +a.lynxNotSupported=Sorry, Lynx is not supported yet +a.password=Password +a.remoteConnectionsDisabled=Sorry, remote connections ('webAllowOthers') are disabled on this server. +a.title=H2 Console +a.tools=Tools +a.user=User Name +admin.executing=Executing +admin.ip=IP +admin.lastAccess=Last Access +admin.lastQuery=Last Query +admin.no=no +admin.notConnected=not connected +admin.url=URL +admin.yes=yes +adminAllow=Allowed clients +adminConnection=Connection security +adminHttp=Use unencrypted HTTP connections +adminHttps=Use encrypted SSL (HTTPS) connections +adminLocal=Only allow local connections +adminLogin=Administration Login +adminLoginCancel=Cancel +adminLoginOk=OK +adminLogout=Logout +adminOthers=Allow connections from other computers +adminWebExternalNames=External names or addresses of this server (comma-separated) +adminPort=Port number +adminPortWeb=Web server port number +adminRestart=Changes take effect after restarting the server. +adminSave=Save +adminSessions=Active Sessions +adminShutdown=Shutdown +adminTitle=H2 Console Preferences +adminTranslateHelp=Translate or improve the translation of the H2 Console. +adminTranslateStart=Translate +helpAction=Action +helpAddAnotherRow=Add another row +helpAddDrivers=Adding Database Drivers +helpAddDriversText=Additional database drivers can be registered by adding the Jar file location of the driver to the environment variables H2DRIVERS or CLASSPATH. Example (Windows): to add the database driver library C:/Programs/hsqldb/lib/hsqldb.jar, set the environment variable H2DRIVERS to C:/Programs/hsqldb/lib/hsqldb.jar. +helpAddRow=Add a new row +helpCommandHistory=Shows the Command History +helpCreateTable=Create a new table +helpDeleteRow=Remove a row +helpDisconnect=Disconnects from the database +helpDisplayThis=Displays this Help Page +helpDropTable=Delete the table if it exists +helpExecuteCurrent=Executes the current SQL statement +helpExecuteSelected=Executes the SQL statement defined by the text selection +helpIcon=Icon +helpImportantCommands=Important Commands +helpOperations=Operations +helpQuery=Query the table +helpSampleSQL=Sample SQL Script +helpStatements=SQL statements +helpUpdate=Change data in a row +helpWithColumnsIdName=with ID and NAME columns +key.alt=Alt +key.ctrl=Ctrl +key.enter=Enter +key.shift=Shift +key.space=Space +login.connect=Connect +login.driverClass=Driver Class +login.driverNotFound=Database driver not found
See in the Help for how to add drivers +login.goAdmin=Preferences +login.jdbcUrl=JDBC URL +login.language=Language +login.login=Login +login.remove=Remove +login.save=Save +login.savedSetting=Saved Settings +login.settingName=Setting Name +login.testConnection=Test Connection +login.testSuccessful=Test successful +login.welcome=H2 Console +result.1row=1 row +result.autoCommitOff=Auto commit is now OFF +result.autoCommitOn=Auto commit is now ON +result.bytes=bytes +result.characters=characters +result.maxrowsSet=Max rowcount is set +result.noRows=no rows +result.noRunningStatement=There is currently no running statement +result.rows=rows +result.statementWasCanceled=The statement was canceled +result.updateCount=Update count +resultEdit.action=Action +resultEdit.add=Add +resultEdit.cancel=Cancel +resultEdit.delete=Delete +resultEdit.edit=Edit +resultEdit.editResult=Edit +resultEdit.save=Save +toolbar.all=All +toolbar.autoCommit=Auto commit +toolbar.autoComplete=Auto complete +toolbar.autoComplete.full=Full +toolbar.autoComplete.normal=Normal +toolbar.autoComplete.off=Off +toolbar.autoSelect=Auto select +toolbar.autoSelect.off=Off +toolbar.autoSelect.on=On +toolbar.cancelStatement=Cancel the current statement +toolbar.clear=Clear +toolbar.commit=Commit +toolbar.disconnect=Disconnect +toolbar.history=Command history +toolbar.maxRows=Max rows +toolbar.refresh=Refresh +toolbar.rollback=Rollback +toolbar.run=Run +toolbar.runSelected=Run Selected +toolbar.sqlStatement=SQL statement +tools.backup=Backup +tools.backup.help=Creates a backup of a database. +tools.changeFileEncryption=ChangeFileEncryption +tools.changeFileEncryption.help=Allows changing the database file encryption password and algorithm. +tools.cipher=Cipher (AES or XTEA) +tools.commandLine=Command line +tools.convertTraceFile=ConvertTraceFile +tools.convertTraceFile.help=Converts a .trace.db file to a Java application and SQL script. +tools.createCluster=CreateCluster +tools.createCluster.help=Creates a cluster from a standalone database. +tools.databaseName=Database name +tools.decryptionPassword=Decryption password +tools.deleteDbFiles=DeleteDbFiles +tools.deleteDbFiles.help=Deletes all files belonging to a database. +tools.directory=Directory +tools.encryptionPassword=Encryption password +tools.javaDirectoryClassName=Java directory and class name +tools.recover=Recover +tools.recover.help=Helps recovering a corrupted database. +tools.restore=Restore +tools.restore.help=Restores a database backup. +tools.result=Result +tools.run=Run +tools.runScript=RunScript +tools.runScript.help=Runs a SQL script. +tools.script=Script +tools.script.help=Allows to convert a database to a SQL script for backup or migration. +tools.scriptFileName=Script file name +tools.serverList=Server list +tools.sourceDatabaseName=Source database name +tools.sourceDatabaseURL=Source database URL +tools.sourceDirectory=Source directory +tools.sourceFileName=Source file name +tools.sourceScriptFileName=Source script file name +tools.targetDatabaseName=Target database name +tools.targetDatabaseURL=Target database URL +tools.targetDirectory=Target directory +tools.targetFileName=Target file name +tools.targetScriptFileName=Target script file name +tools.traceFileName=Trace file name +tree.admin=Admin +tree.current=Current value +tree.hashed=Hashed +tree.increment=Increment +tree.indexes=Indexes +tree.nonUnique=Non unique +tree.sequences=Sequences +tree.unique=Unique +tree.users=Users diff --git a/h2/src/main/org/h2/server/web/res/_text_es.prop b/h2/src/main/org/h2/server/web/res/_text_es.prop new file mode 100644 index 0000000..8e41b66 --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/_text_es.prop @@ -0,0 +1,164 @@ +.translator=Miguel Angel +a.help=Ayuda +a.language=Español +a.lynxNotSupported=Lo sentimos, Lynx no está soportado todavía +a.password=Contraseña +a.remoteConnectionsDisabled=Conexiones remotas desactivadas +a.title=H2 Console +a.tools=#Tools +a.user=Nombre de usuario +admin.executing=Ejecutando +admin.ip=IP +admin.lastAccess=Último acceso +admin.lastQuery=Última consulta +admin.no=#no +admin.notConnected=#not connected +admin.url=URL +admin.yes=#yes +adminAllow=Clientes permitidos +adminConnection=Conexión segura +adminHttp=Usar conexiones HTTP desencriptadas +adminHttps=Usar conexiones HTTPS (SSL) encriptadas +adminLocal=Permitir únicamente conexiones locales +adminLogin=Registrar administración +adminLoginCancel=Cancelar +adminLoginOk=Aceptar +adminLogout=Desconectar +adminOthers=Permitir conexiones desde otros ordenadores +adminWebExternalNames=#External names or addresses of this server (comma-separated) +adminPort=Puerto +adminPortWeb=Puerto del servidor Web +adminRestart=Los cambios tendrán efecto al reiniciar el servidor. +adminSave=Guardar +adminSessions=Sesiones activas +adminShutdown=Parar servidor +adminTitle=Preferencias de H2 Consola +adminTranslateHelp=#Translate or improve the translation of the H2 Console. +adminTranslateStart=#Translate +helpAction=Action +helpAddAnotherRow=Añadir otra fila +helpAddDrivers=Añadiendo drivers de base de datos +helpAddDriversText=Se pueden registrar otros drivers añadiendo el archivo Jar del driver a la variable de entorno H2DRIVERS o CLASSPATH. Por ejemplo (Windows): Para añadir la librería del driver de base de datos C:/Programs/hsqldb/lib/hsqldb.jar, hay que establecer la variable de entorno H2DRIVERS a C:/Programs/hsqldb/lib/hsqldb.jar. +helpAddRow=Añadir una fila nueva +helpCommandHistory=Ver histórico de comandos +helpCreateTable=Crear una tabla nueva +helpDeleteRow=Borrar una fila +helpDisconnect=Desconectar de la base de datos. +helpDisplayThis=Visualizar esta página de ayuda. +helpDropTable=Borrar la tabla si existe +helpExecuteCurrent=Ejecuta la actual sentencia SQL +helpExecuteSelected=#Executes the SQL statement defined by the text selection +helpIcon=Icon +helpImportantCommands=Comandos importantes +helpOperations=Operaciones +helpQuery=Consulta la tabla +helpSampleSQL=Ejemplo SQL Script +helpStatements=Sentencias SQL +helpUpdate=Modificar datos en una fila +helpWithColumnsIdName=con las columnas ID y NAME +key.alt=#Alt +key.ctrl=#Ctrl +key.enter=#Enter +key.shift=#Shift +key.space=#Space +login.connect=Conectar +login.driverClass=Controlador +login.driverNotFound=Controlador no encontrado +login.goAdmin=Preferencias +login.jdbcUrl=URL JDBC +login.language=Idioma +login.login=Registrar +login.remove=Eliminar +login.save=Guardar +login.savedSetting=Configuraciones guardadas +login.settingName=Nombre de la configuración +login.testConnection=Probar la conexión +login.testSuccessful=Prueba correcta +login.welcome=H2 Consola +result.1row=1 fila +result.autoCommitOff=El auto commit no está activo +result.autoCommitOn=El auto commit está activo +result.bytes=#bytes +result.characters=#characters +result.maxrowsSet=Número máximo de filas modificado +result.noRows=No se han recuperado filas +result.noRunningStatement=No hay una instrucción ejecutándose +result.rows=filas +result.statementWasCanceled=La instrucción fue cancelada +result.updateCount=Modificaciones +resultEdit.action=Action +resultEdit.add=Añadir +resultEdit.cancel=Cancelar +resultEdit.delete=Eliminar +resultEdit.edit=Editar +resultEdit.editResult=Editar +resultEdit.save=Guardar +toolbar.all=Todos +toolbar.autoCommit=Auto commit +toolbar.autoComplete=Auto completado +toolbar.autoComplete.full=Completo +toolbar.autoComplete.normal=Normal +toolbar.autoComplete.off=Desactivado +toolbar.autoSelect=#Auto select +toolbar.autoSelect.off=Desactivado +toolbar.autoSelect.on=#On +toolbar.cancelStatement=Cancelar la instrucción actual +toolbar.clear=Eliminar +toolbar.commit=Commit +toolbar.disconnect=Desconectar +toolbar.history=Histórico comandos +toolbar.maxRows=Número máximo de filas +toolbar.refresh=Actualizar +toolbar.rollback=Rollback +toolbar.run=Ejecutar +toolbar.runSelected=#Run Selected +toolbar.sqlStatement=Instrucción SQL +tools.backup=#Backup +tools.backup.help=#Creates a backup of a database. +tools.changeFileEncryption=#ChangeFileEncryption +tools.changeFileEncryption.help=#Allows changing the database file encryption password and algorithm. +tools.cipher=#Cipher (AES or XTEA) +tools.commandLine=#Command line +tools.convertTraceFile=#ConvertTraceFile +tools.convertTraceFile.help=#Converts a .trace.db file to a Java application and SQL script. +tools.createCluster=#CreateCluster +tools.createCluster.help=#Creates a cluster from a standalone database. +tools.databaseName=#Database name +tools.decryptionPassword=#Decryption password +tools.deleteDbFiles=#DeleteDbFiles +tools.deleteDbFiles.help=#Deletes all files belonging to a database. +tools.directory=#Directory +tools.encryptionPassword=#Encryption password +tools.javaDirectoryClassName=#Java directory and class name +tools.recover=#Recover +tools.recover.help=#Helps recovering a corrupted database. +tools.restore=#Restore +tools.restore.help=#Restores a database backup. +tools.result=#Result +tools.run=Ejecutar +tools.runScript=#RunScript +tools.runScript.help=#Runs a SQL script. +tools.script=#Script +tools.script.help=#Allows to convert a database to a SQL script for backup or migration. +tools.scriptFileName=#Script file name +tools.serverList=#Server list +tools.sourceDatabaseName=#Source database name +tools.sourceDatabaseURL=#Source database URL +tools.sourceDirectory=#Source directory +tools.sourceFileName=#Source file name +tools.sourceScriptFileName=#Source script file name +tools.targetDatabaseName=#Target database name +tools.targetDatabaseURL=#Target database URL +tools.targetDirectory=#Target directory +tools.targetFileName=#Target file name +tools.targetScriptFileName=#Target script file name +tools.traceFileName=#Trace file name +tree.admin=Administrador +tree.current=Valor actual +tree.hashed=Hash +tree.increment=Incremento +tree.indexes=Índices +tree.nonUnique=No único +tree.sequences=Secuencias +tree.unique=Único +tree.users=Usuarios diff --git a/h2/src/main/org/h2/server/web/res/_text_fr.prop b/h2/src/main/org/h2/server/web/res/_text_fr.prop new file mode 100644 index 0000000..792f72e --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/_text_fr.prop @@ -0,0 +1,164 @@ +.translator=Olivier Parent +a.help=Aide +a.language=Français +a.lynxNotSupported=Désolé, Lynx n'est pas encore supporté +a.password=Mot de passe +a.remoteConnectionsDisabled=Désolé, la gestion des connexions provenant de machines distantes est désactivée sur ce serveur ('webAllowOthers'). +a.title=Console H2 +a.tools=Outils +a.user=Nom d'utilisateur +admin.executing=Exécution en cours +admin.ip=Adresse IP +admin.lastAccess=Dernier accès +admin.lastQuery=Dernière requête +admin.no=Non +admin.notConnected=#not connected +admin.url=URL +admin.yes=Oui +adminAllow=Clients autorisés +adminConnection=Sécurité des connexions +adminHttp=Utiliser des connexions HTTP non sécurisées +adminHttps=Utiliser des connexions sécurisées +adminLocal=Autoriser uniquement les connexions locales +adminLogin=Login administrateur +adminLoginCancel=Annuler +adminLoginOk=OK +adminLogout=Déconnexion +adminOthers=Autoriser les connexions d'ordinateurs distants +adminWebExternalNames=#External names or addresses of this server (comma-separated) +adminPort=Numéro de port +adminPortWeb=Numéro de port du serveur Web +adminRestart=Modifications effectuées après redémarrage du serveur. +adminSave=Enregistrer +adminSessions=Sessions actives +adminShutdown=Arrêt +adminTitle=Console H2 de paramétrage des options +adminTranslateHelp=#Translate or improve the translation of the H2 Console. +adminTranslateStart=#Translate +helpAction=Action +helpAddAnotherRow=Ajouter un autre enregistrement +helpAddDrivers=Ajouter des pilotes de base de données +helpAddDriversText=Des pilotes additionels peuvent être configurés en déclarant l'emplacement du fichier Jar contenant ces pilotes dans les variables d'environnement H2DRIVERS ou CLASSPATH. Exemple (Windows): Pour ajouter la bibliothèque C:/Programs/hsqldb/lib/hsqldb.jar, définir la valeur de la variable d'environnement H2DRIVERS en C:/Programs/hsqldb/lib/hsqldb.jar. +helpAddRow=Ajouter un nouvel enregistrement +helpCommandHistory=Affiche l'historique des commandes +helpCreateTable=Créer une nouvelle table +helpDeleteRow=Effacer un enregistrement +helpDisconnect=Déconnexion de la base de données +helpDisplayThis=Affiche cette page d'aide +helpDropTable=Effacer une table si elle existe +helpExecuteCurrent=Exécute la commande courante +helpExecuteSelected=#Executes the SQL statement defined by the text selection +helpIcon=Icone +helpImportantCommands=Commandes principales +helpOperations=Opérations +helpQuery=Requêter une table +helpSampleSQL=Exemple de script SQL +helpStatements=Instructions SQL +helpUpdate=Modifier un enregistrement +helpWithColumnsIdName=avec les colonnes ID et NAME +key.alt=#Alt +key.ctrl=#Ctrl +key.enter=#Enter +key.shift=#Shift +key.space=#Space +login.connect=Connecter +login.driverClass=Pilote JDBC +login.driverNotFound=Pilote non trouvé.
Veuillez consulter dans l'aide la procédure d'ajout de pilotes. +login.goAdmin=Options +login.jdbcUrl=URL JDBC +login.language=Langue +login.login=Connexion +login.remove=Supprimer +login.save=Enregistrer +login.savedSetting=Configuration enregistrée +login.settingName=Nom de configuration +login.testConnection=Test de connexion +login.testSuccessful=Succès +login.welcome=Console H2 +result.1row=1 enregistrement +result.autoCommitOff=Validation automatique non activée +result.autoCommitOn=Validation automatique activée +result.bytes=#bytes +result.characters=#characters +result.maxrowsSet=Nombre max d'enregistrements défini +result.noRows=Aucun enregistrement +result.noRunningStatement=Pas d'instruction en cours +result.rows=enregistrements +result.statementWasCanceled=L'instruction a été annulée +result.updateCount=Nombre de modifications +resultEdit.action=Action +resultEdit.add=Ajouter +resultEdit.cancel=Annuler +resultEdit.delete=Supprimer +resultEdit.edit=Editer +resultEdit.editResult=Editer +resultEdit.save=Enregistrer +toolbar.all=Tous +toolbar.autoCommit=Validation automatique (auto commit) +toolbar.autoComplete=Complètement automatique +toolbar.autoComplete.full=Exhaustif +toolbar.autoComplete.normal=Normal +toolbar.autoComplete.off=Désactivé +toolbar.autoSelect=#Auto select +toolbar.autoSelect.off=Désactivé +toolbar.autoSelect.on=#On +toolbar.cancelStatement=Annuler l'instruction en cours +toolbar.clear=Effacer +toolbar.commit=Valider +toolbar.disconnect=Déconnecter +toolbar.history=Historique des instructions +toolbar.maxRows=Max lignes +toolbar.refresh=Rafraîchir +toolbar.rollback=Revenir en arrière +toolbar.run=Exécuter +toolbar.runSelected=#Run Selected +toolbar.sqlStatement=Instruction SQL +tools.backup=Sauvegarde +tools.backup.help=Crée une sauvegarde de base de données. +tools.changeFileEncryption=Modifier le chiffrement +tools.changeFileEncryption.help=Permet la modification du mot de passe et de l'algorithme de chiffrement des fichiers de base de données. +tools.cipher=Chiffrement (AES ou XTEA) +tools.commandLine=Ligne de commande +tools.convertTraceFile=Convertir le fichier trace +tools.convertTraceFile.help=Convertit un fichier .trace.db en une application Java ou un script SQL. +tools.createCluster=Créer une grappe de serveurs +tools.createCluster.help=Crée une grappe de serveurs depuis une base de données autonome. +tools.databaseName=Nom de la base de données +tools.decryptionPassword=Mot de passe de déchiffrement +tools.deleteDbFiles=Supprimer les fichiers +tools.deleteDbFiles.help=Supprime tous les fichiers appartenant à une base de données +tools.directory=Répertoire +tools.encryptionPassword=Mot de passe de chiffrement +tools.javaDirectoryClassName=Répertoire Java et nom de classe +tools.recover=Réparation +tools.recover.help=Aide la réparation d'une base de données corrompue +tools.restore=Restauration +tools.restore.help=Restaure une sauvegarde de base de données. +tools.result=Résultat +tools.run=Exécuter +tools.runScript=Exécuter le script +tools.runScript.help=Exécute un script SQL. +tools.script=Script +tools.script.help=Convertit une base de données en script SQL pour une sauvegarde ou une migration. +tools.scriptFileName=Nom du fichier de script +tools.serverList=Liste des serveurs +tools.sourceDatabaseName=Base de données source +tools.sourceDatabaseURL=URL base de données source +tools.sourceDirectory=Répertoire source +tools.sourceFileName=Nom de fichier source +tools.sourceScriptFileName=Nom du fichier script source +tools.targetDatabaseName=Base de données cible +tools.targetDatabaseURL=URL base de données cible +tools.targetDirectory=Répertoire cible +tools.targetFileName=Nom de fichier cible +tools.targetScriptFileName=Nom de fichier script cible +tools.traceFileName=Nom du fichier de trace +tree.admin=Administrateur +tree.current=Valeur actuelle +tree.hashed=Haché (hashed) +tree.increment=Incrément +tree.indexes=Index +tree.nonUnique=Non unique +tree.sequences=Séquences +tree.unique=Unique +tree.users=Utilisateurs diff --git a/h2/src/main/org/h2/server/web/res/_text_hi.prop b/h2/src/main/org/h2/server/web/res/_text_hi.prop new file mode 100644 index 0000000..a7d8a05 --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/_text_hi.prop @@ -0,0 +1,164 @@ +.translator=vikash verma +a.help=सहायता +a.language=Hindi(हिंदी) +a.lynxNotSupported=क्षमा करें, लिंक्स(Lynx) अभी तक समर्थित नहीं है +a.password=पासवर्ड +a.remoteConnectionsDisabled=क्षमा करें, इस सर्वर पर दूरस्थ कनेक्शन ('webAllowOthers') अक्षम हैं। +a.title=एच 2 कंसोल +a.tools=उपकरण +a.user=प्रयोक्ता नाम +admin.executing=निष्पादित +admin.ip=आईपी (IP) +admin.lastAccess=अंतिम पहुंच +admin.lastQuery=अंतिम प्रश्न(query) +admin.no=नहीं +admin.notConnected=जुड़े नहीं हैं +admin.url=यूआरएल (URL) +admin.yes=हाँ +adminAllow=ग्राहकों को अनुमति है +adminConnection=कनेक्शन सुरक्षा +adminHttp=अनएन्क्रिप्टेड HTTP कनेक्शन का उपयोग करें +adminHttps=एन्क्रिप्टेड एसएसएल (HTTPS) कनेक्शन का उपयोग करें +adminLocal=केवल स्थानीय कनेक्शन की अनुमति दें +adminLogin=प्रशासन लॉगिन करें +adminLoginCancel=रद्द करना +adminLoginOk=ठीक +adminLogout=लोग आउट +adminOthers=अन्य कंप्यूटर से कनेक्शन की अनुमति दें +adminWebExternalNames=#External names or addresses of this server (comma-separated) +adminPort=पोर्ट नंबर +adminPortWeb=वेब सर्वर पोर्ट नंबर +adminRestart=सर्वर को पुनरारंभ करने के बाद परिवर्तन प्रभावी होते हैं। +adminSave=रक्षित करें +adminSessions=सक्रिय सत्र +adminShutdown=बंद करना +adminTitle=एच 2 कंसोल प्राथमिकताएं +adminTranslateHelp=अनुवाद या H2 कंसोल के अनुवाद में सुधार। +adminTranslateStart=अनुवाद करना +helpAction=कर्म +helpAddAnotherRow=एक और पंक्ति जोड़ें +helpAddDrivers=डेटाबेस ड्राइवर्स जोड़ना +helpAddDriversText=अतिरिक्त डेटाबेस ड्राइवरों को पर्यावरण चर (environment variables) H2DRIVERS या CLASSPATH में ड्राइवर के जार फ़ाइल स्थान को जोड़कर पंजीकृत किया जा सकता है। उदाहरण (विंडोज़) : डेटाबेस ड्राइवर लाइब्रेरी को जोड़ने के लिए C : / Programs / hsqldb / lib / hsqldb.jar, C_: / प्रोग्राम / hsqldb (lib / hsqldb.jar) पर्यावरण चर H2DRIVERS सेट करें। +helpAddRow=एक नई पंक्ति जोड़ें +helpCommandHistory=कमांड इतिहास दिखाता है +helpCreateTable=एक नई तालिका बनाएँ +helpDeleteRow=एक पंक्ति निकालें +helpDisconnect=डेटाबेस से डिस्कनेक्ट करता है +helpDisplayThis=यह सहायता पृष्ठ प्रदर्शित करें +helpDropTable=यदि मौजूद है तो तालिका हटाएं +helpExecuteCurrent=वर्तमान SQL कथन निष्पादित करता है +helpExecuteSelected=पाठ चयन द्वारा परिभाषित SQL कथन निष्पादित करता है +helpIcon=चिह्न +helpImportantCommands=महत्वपूर्ण आदेश +helpOperations=संचालन +helpQuery=तालिका को क्वेरी करें +helpSampleSQL=नमूना एसक्यूएल स्क्रिप्ट +helpStatements=एसक्यूएल बयान +helpUpdate=एक पंक्ति में डेटा बदलें +helpWithColumnsIdName=आईडी और NAME कॉलम के साथ +key.alt=Alt +key.ctrl=Ctrl +key.enter=Enter +key.shift=Shift +key.space=Space +login.connect=जुडिये +login.driverClass=चालक वर्ग (Driver Class) +login.driverNotFound=डेटाबेस ड्राइवर नहीं मिला
ड्राइवरों को जोड़ने के लिए सहायता में देखें +login.goAdmin=पसंद +login.jdbcUrl=JDBC URL +login.language=भाषा +login.login=लॉग इन करें +login.remove=हटाये +login.save=रक्षित करें +login.savedSetting=सहेजे गए सेटिंग्स +login.settingName=सेटिंग्स का नाम +login.testConnection=परीक्षण कनेक्शन +login.testSuccessful=सफल परीक्षण +login.welcome=एच 2 कंसोल +result.1row=1 पंक्ति +result.autoCommitOff=ऑटो कमिट बंद +result.autoCommitOn=ऑटो कमिट चालू +result.bytes=बाइट्स +result.characters=वर्ण +result.maxrowsSet=अधिकतम पंक्ति संख्या सेट है +result.noRows=कोई पंक्तियाँ नहीं +result.noRunningStatement=वर्तमान में कोई स्टेटमेंट नहीं चल रहा है +result.rows=पंक्तियां +result.statementWasCanceled=बयान रद्द कर दिया गया +result.updateCount=अद्यतन गणना +resultEdit.action=कर्म +resultEdit.add=जोड़ना +resultEdit.cancel=रद्द करना +resultEdit.delete=हटाये +resultEdit.edit=संपादित करें +resultEdit.editResult=संपादित करें +resultEdit.save=रक्षित करें +toolbar.all=सब +toolbar.autoCommit=ऑटो कमिट +toolbar.autoComplete=ऑटो पूर्ण +toolbar.autoComplete.full=पूर्ण +toolbar.autoComplete.normal=सामान्य +toolbar.autoComplete.off=बंद +toolbar.autoSelect=स्वतः चयन +toolbar.autoSelect.off=बंद +toolbar.autoSelect.on=पर +toolbar.cancelStatement=वर्तमान कथन को रद्द करें +toolbar.clear=स्पष्ट +toolbar.commit=कमिट +toolbar.disconnect=डिस्कनेक्ट +toolbar.history=कमान का इतिहास +toolbar.maxRows=अधिकतम पंक्तियाँ +toolbar.refresh=ताज़ा करना +toolbar.rollback=रोलबैक +toolbar.run=रन +toolbar.runSelected=चयनित चलाएं +toolbar.sqlStatement=एसक्यूएल बयान +tools.backup=बैकअप +tools.backup.help=एक डेटाबेस का बैकअप बनाता है। +tools.changeFileEncryption=ChangeFileEncryption +tools.changeFileEncryption.help=डेटाबेस फ़ाइल एन्क्रिप्शन पासवर्ड और एल्गोरिथ्म को बदलने देता है। +tools.cipher=सिफर (एईएस या एक्सटीईए) +tools.commandLine=कमांड लाइन +tools.convertTraceFile=ConvertTraceFile +tools.convertTraceFile.help=एक जावा अनुप्रयोग और SQL स्क्रिप्ट के लिए एक .trace.db फ़ाइल में कनवर्ट करता है। +tools.createCluster=CreateCluster +tools.createCluster.help=एक स्टैंडअलोन डेटाबेस से एक क्लस्टर बनाता है। +tools.databaseName=डेटाबेस नाम +tools.decryptionPassword=डिक्रिप्शन पासवर्ड +tools.deleteDbFiles=DeleteDbFiles +tools.deleteDbFiles.help=डेटाबेस से संबंधित सभी फ़ाइलों को हटाता है। +tools.directory=निर्देशिका +tools.encryptionPassword=एन्क्रिप्शन पासवर्ड +tools.javaDirectoryClassName=जावा निर्देशिका और वर्ग का नाम +tools.recover=वसूली +tools.recover.help=एक दूषित डेटाबेस को पुनर्प्राप्त करने में मदद करता है। +tools.restore=पुनर्स्थापित +tools.restore.help=डेटाबेस बैकअप पुनर्स्थापित करता है। +tools.result=परिणाम +tools.run=रन +tools.runScript=RunScript +tools.runScript.help=SQL स्क्रिप्ट चलाता है। +tools.script=लिपि +tools.script.help=बैकअप या माइग्रेशन के लिए डेटाबेस को SQL स्क्रिप्ट में बदलने की अनुमति देता है। +tools.scriptFileName=स्क्रिप्ट फ़ाइल नाम +tools.serverList=सर्वर सूची +tools.sourceDatabaseName=स्रोत डेटाबेस का नाम +tools.sourceDatabaseURL=स्रोत डेटाबेस URL +tools.sourceDirectory=स्रोत निर्देशिका +tools.sourceFileName=स्रोत फ़ाइल नाम +tools.sourceScriptFileName=स्रोत स्क्रिप्ट फ़ाइल नाम +tools.targetDatabaseName=लक्ष्य डेटाबेस नाम +tools.targetDatabaseURL=लक्ष्य डेटाबेस URL +tools.targetDirectory=लक्ष्य निर्देशिका +tools.targetFileName=लक्ष्य फ़ाइल नाम +tools.targetScriptFileName=लक्ष्य स्क्रिप्ट फ़ाइल नाम +tools.traceFileName=ट्रेस फ़ाइल नाम +tree.admin=व्यवस्थापक +tree.current=वर्तमान मूल्य +tree.hashed=टुकड़ों में बांटा(Hashed) +tree.increment=वृद्धि +tree.indexes=इंडेक्स +tree.nonUnique=गैर अद्वितीय +tree.sequences=दृश्यों +tree.unique=अद्वितीय +tree.users=उपयोगकर्ता diff --git a/h2/src/main/org/h2/server/web/res/_text_hu.prop b/h2/src/main/org/h2/server/web/res/_text_hu.prop new file mode 100644 index 0000000..1406ed0 --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/_text_hu.prop @@ -0,0 +1,164 @@ +.translator=Andras Hideg +a.help=Súgó +a.language=Magyar +a.lynxNotSupported=A Lynx böngésző egyelőre nem támogatott +a.password=Jelszó +a.remoteConnectionsDisabled=Ezen a kiszolgálón távoli kapcsolatok ('webAllowOthers') nem engedélyezettek. +a.title=H2 konzol +a.tools=#Tools +a.user=Felhasználónév +admin.executing=Utasítás végrehajtása +admin.ip=IP +admin.lastAccess=Legutóbbi hozzáférés +admin.lastQuery=Legutóbbi lekérdezés +admin.no=#no +admin.notConnected=#not connected +admin.url=URL +admin.yes=#yes +adminAllow=Engedélyezett ügyfelek +adminConnection=Kapcsolatbiztonság +adminHttp=Titkosítatlan HTTP kapcsolatok használata +adminHttps=Titkosított SSL (HTTPS) kapcsolatok használata +adminLocal=Csak helyi kapcsolatok engedélyezése +adminLogin=Adminisztrációs bejelentkezés +adminLoginCancel=Mégse +adminLoginOk=OK +adminLogout=Kilépés +adminOthers=Más számítógépekről kezdeményezett kapcsolatok engedélyezése +adminWebExternalNames=#External names or addresses of this server (comma-separated) +adminPort=#Port number +adminPortWeb=Webkiszolgáló portszáma +adminRestart=A változtatások a kiszolgáló újraindítása után lépnek érvénybe +adminSave=Mentés +adminSessions=Aktív munkamenetek +adminShutdown=Leállítás +adminTitle=H2 Konzol tulajdonságai +adminTranslateHelp=#Translate or improve the translation of the H2 Console. +adminTranslateStart=#Translate +helpAction=Parancs +helpAddAnotherRow=Rekord hozzáadása +helpAddDrivers=Adatbázis-illesztőprogramok hozzáadása +helpAddDriversText=További adatbázis-illesztőprogramok regisztrálásakor a H2DRIVERS vagy CLASSPATH környezeti változókhoz kell adni a .jar illesztőprogram-fájlok elérési útvonalait. Például (Windows esetén) a C:/Programs/hsqldb/lib/hsqldb.jar illesztőprogram regisztrálásához a H2DRIVERS környezeti változónak az alábbi értékét kell megadni: C:/Programs/hsqldb/lib/hsqldb.jar +helpAddRow=Rekord hozzáadása +helpCommandHistory=Korábbi utasítások megjelenítése +helpCreateTable=Új tábla létrehozása +helpDeleteRow=rekord törlése +helpDisconnect=Kapcsolat megszakítása az adatbázissal +helpDisplayThis=Súgó megjelenítése +helpDropTable=Tábla törlése, ha az létezik +helpExecuteCurrent=Aktuális SQL utasítás végrehajtása +helpExecuteSelected=#Executes the SQL statement defined by the text selection +helpIcon=Ikon +helpImportantCommands=Fontos utasítások +helpOperations=Műveletek +helpQuery=Tábla lekérdezése +helpSampleSQL=Minta SQL szkript +helpStatements=SQL utasítások +helpUpdate=Adatok módosítása egy rekordon belül +helpWithColumnsIdName=ID és NAME oszlopokkal +key.alt=#Alt +key.ctrl=#Ctrl +key.enter=#Enter +key.shift=#Shift +key.space=#Space +login.connect=Csatlakozás +login.driverClass=Illesztőprogram osztály +login.driverNotFound=Adatbázis-illesztőprogram nem található
Illesztőprogramok hozzáadásáról a Súgó ad felvilágosítást. +login.goAdmin=Tulajdonságok +login.jdbcUrl=JDBC URL +login.language=Nyelv +login.login=Belépés +login.remove=Eltávolítás +login.save=Mentés +login.savedSetting=Mentett beállítások +login.settingName=Beállítás neve +login.testConnection=Kapcsolat tesztelése +login.testSuccessful=Kapcsolat tesztelése sikeres volt +login.welcome=H2 konzol +result.1row=1 rekord +result.autoCommitOff=Automatikus jóváhagyás kikapcsolva +result.autoCommitOn=Automatikus jóváhagyás bekapcsolva +result.bytes=#bytes +result.characters=#characters +result.maxrowsSet=Rekordok maximális száma beállítva +result.noRows=nincs rekord +result.noRunningStatement=Utasítás jelenleg nincs folyamatban +result.rows=rekordok +result.statementWasCanceled=Az utasítás végrehajtása megszakadt +result.updateCount=Frissítés +resultEdit.action=Parancs +resultEdit.add=Hozzáadás +resultEdit.cancel=Mégse +resultEdit.delete=Törlés +resultEdit.edit=Szerkesztés +resultEdit.editResult=Szerkesztés +resultEdit.save=Mentés +toolbar.all=Mind +toolbar.autoCommit=Automatikus jóváhagyás +toolbar.autoComplete=Automatikus kiegészítés +toolbar.autoComplete.full=Teljes +toolbar.autoComplete.normal=Normál +toolbar.autoComplete.off=Kikapcsolva +toolbar.autoSelect=#Auto select +toolbar.autoSelect.off=Kikapcsolva +toolbar.autoSelect.on=#On +toolbar.cancelStatement=Aktuális utasítás végrehajtásának megszakítása +toolbar.clear=Törlés +toolbar.commit=Jóváhagyás +toolbar.disconnect=Kapcsolat bontása +toolbar.history=Korábbi utasítások +toolbar.maxRows=Rekordok maximális száma +toolbar.refresh=Frissítés +toolbar.rollback=Visszagörgetés +toolbar.run=Végrehajtás +toolbar.runSelected=#Run Selected +toolbar.sqlStatement=SQL utasítás +tools.backup=#Backup +tools.backup.help=#Creates a backup of a database. +tools.changeFileEncryption=#ChangeFileEncryption +tools.changeFileEncryption.help=#Allows changing the database file encryption password and algorithm. +tools.cipher=#Cipher (AES or XTEA) +tools.commandLine=#Command line +tools.convertTraceFile=#ConvertTraceFile +tools.convertTraceFile.help=#Converts a .trace.db file to a Java application and SQL script. +tools.createCluster=#CreateCluster +tools.createCluster.help=#Creates a cluster from a standalone database. +tools.databaseName=#Database name +tools.decryptionPassword=#Decryption password +tools.deleteDbFiles=#DeleteDbFiles +tools.deleteDbFiles.help=#Deletes all files belonging to a database. +tools.directory=#Directory +tools.encryptionPassword=#Encryption password +tools.javaDirectoryClassName=#Java directory and class name +tools.recover=#Recover +tools.recover.help=#Helps recovering a corrupted database. +tools.restore=#Restore +tools.restore.help=#Restores a database backup. +tools.result=#Result +tools.run=Végrehajtás +tools.runScript=#RunScript +tools.runScript.help=#Runs a SQL script. +tools.script=#Script +tools.script.help=#Allows to convert a database to a SQL script for backup or migration. +tools.scriptFileName=#Script file name +tools.serverList=#Server list +tools.sourceDatabaseName=#Source database name +tools.sourceDatabaseURL=#Source database URL +tools.sourceDirectory=#Source directory +tools.sourceFileName=#Source file name +tools.sourceScriptFileName=#Source script file name +tools.targetDatabaseName=#Target database name +tools.targetDatabaseURL=#Target database URL +tools.targetDirectory=#Target directory +tools.targetFileName=#Target file name +tools.targetScriptFileName=#Target script file name +tools.traceFileName=#Trace file name +tree.admin=Adminisztrátor +tree.current=Aktuális érték +tree.hashed=Hash-értékkel ellátott +tree.increment=Inkrementált +tree.indexes=Indexek +tree.nonUnique=Nem egyedi +tree.sequences=Szekvencia +tree.unique=Egyedi +tree.users=Felhasználók diff --git a/h2/src/main/org/h2/server/web/res/_text_in.prop b/h2/src/main/org/h2/server/web/res/_text_in.prop new file mode 100644 index 0000000..e954ac7 --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/_text_in.prop @@ -0,0 +1,164 @@ +.translator=Joko Yuliantoro +a.help=Bantuan +a.language=Indonesia +a.lynxNotSupported=Maaf, Lynx belum didukung. Gunakan browser yang mendukung Javascript (dan Frame). +a.password=Kata kunci +a.remoteConnectionsDisabled=Maaf, fungsi koneksi jarak jauh ('webAllowOthers') dimatikan pada server ini. +a.title=Konsol H2 +a.tools=#Tools +a.user=Nama pengguna +admin.executing=Sedang eksekusi +admin.ip=IP +admin.lastAccess=Akses Terakhir +admin.lastQuery=Kueri Terakhir +admin.no=#no +admin.notConnected=#not connected +admin.url=URL +admin.yes=#yes +adminAllow=Klien terijin +adminConnection=Keamanan koneksi +adminHttp=Gunakan koneksi HTTP tidak terenkripsi +adminHttps=Gunakan koneksi SSL terenkripsi (HTTPS) +adminLocal=Hanya ijinkan koneksi lokal +adminLogin=Login Pengelola +adminLoginCancel=Batal +adminLoginOk=OK +adminLogout=Keluar +adminOthers=Ijinkan koneksi dari komputer lain +adminWebExternalNames=#External names or addresses of this server (comma-separated) +adminPort=Nomor port +adminPortWeb=Nomor port web server +adminRestart=Perubahan akan efektif setelah server di-restart. +adminSave=Simpan +adminSessions=Sesi aktif +adminShutdown=Matikan +adminTitle=Pilihan di Konsol H2 +adminTranslateHelp=#Translate or improve the translation of the H2 Console. +adminTranslateStart=#Translate +helpAction=Aksi +helpAddAnotherRow=Menambah sebuah baris +helpAddDrivers=Menambah pengendali basis data +helpAddDriversText=Pengendali basis data tambahan dapat didaftarkan dengan cara menambah lokasi file Jar dari si pengendali ke variabel lingkungan H2DRIVERS atau CLASSPATH. Contoh (Windows): Untuk menambah librari pengendali basis data C:/Programs/hsqldb/lib/hsqldb.jar, atur variabel lingkungan H2DRIVERS menjadi C:/Programs/hsqldb/lib/hsqldb.jar. +helpAddRow=Tambah sebuah baris baru +helpCommandHistory=Tampilkan sejarah perintah +helpCreateTable=Ciptakan sebuah tabel +helpDeleteRow=Buang sebuah baris +helpDisconnect=Putuskan koneksi dari basis data +helpDisplayThis=Tampilkan laman bantuan ini +helpDropTable=Hapus tabel jika sudah ada +helpExecuteCurrent=Jalankan pernyataan SQL terkini +helpExecuteSelected=#Executes the SQL statement defined by the text selection +helpIcon=Ikon +helpImportantCommands=Perintah penting +helpOperations=Operasi +helpQuery=Kueri ke tabel +helpSampleSQL=Contoh skrip SQL +helpStatements=Pernyataan SQL +helpUpdate=Rubah data dalam sebuah baris +helpWithColumnsIdName=dengan kolom ID dan NAME +key.alt=#Alt +key.ctrl=#Ctrl +key.enter=#Enter +key.shift=#Shift +key.space=#Space +login.connect=Hubungkan +login.driverClass=Kelas Pengendali +login.driverNotFound=Pengendali basis data tidak ditemukan
Pelajari bagian Bantuan untuk mengetahui bagaimana cara menambah pengendali basis data +login.goAdmin=Pilihan +login.jdbcUrl=JDBC URL +login.language=Bahasa +login.login=Login +login.remove=Buang +login.save=Simpan +login.savedSetting=Pengaturan tersimpan +login.settingName=Nama pengaturan +login.testConnection=Tes koneksi +login.testSuccessful=Tes berhasil +login.welcome=Konsol H2 +result.1row=1 baris +result.autoCommitOff=Autocommit sekarang OFF +result.autoCommitOn=Autocommit sekarang ON +result.bytes=#bytes +result.characters=#characters +result.maxrowsSet=Hitungan baris maksimum terpasang +result.noRows=Tidak ada hasil +result.noRunningStatement=Saat ini tidak ada pernyataan yang beroperasi +result.rows=baris +result.statementWasCanceled=Pernyataan tersebut dibatalkan +result.updateCount=Perbarui Hitungan +resultEdit.action=Aksi +resultEdit.add=Tambah +resultEdit.cancel=Batal +resultEdit.delete=Hapus +resultEdit.edit=Ubah +resultEdit.editResult=Ubah +resultEdit.save=Simpan +toolbar.all=Semua +toolbar.autoCommit=Autocommit +toolbar.autoComplete=Auto-Complete +toolbar.autoComplete.full=Full +toolbar.autoComplete.normal=Normal +toolbar.autoComplete.off=Off +toolbar.autoSelect=#Auto select +toolbar.autoSelect.off=Off +toolbar.autoSelect.on=#On +toolbar.cancelStatement=Batalkan pernyataan terkini +toolbar.clear=Bersihkan +toolbar.commit=Laksanakan +toolbar.disconnect=Putuskan koneksi +toolbar.history=Sejarah perintah +toolbar.maxRows=Baris maksimum +toolbar.refresh=Segarkan +toolbar.rollback=Gulung mundur +toolbar.run=Jalankan +toolbar.runSelected=#Run Selected +toolbar.sqlStatement=Pernyataan SQL +tools.backup=#Backup +tools.backup.help=#Creates a backup of a database. +tools.changeFileEncryption=#ChangeFileEncryption +tools.changeFileEncryption.help=#Allows changing the database file encryption password and algorithm. +tools.cipher=#Cipher (AES or XTEA) +tools.commandLine=#Command line +tools.convertTraceFile=#ConvertTraceFile +tools.convertTraceFile.help=#Converts a .trace.db file to a Java application and SQL script. +tools.createCluster=#CreateCluster +tools.createCluster.help=#Creates a cluster from a standalone database. +tools.databaseName=#Database name +tools.decryptionPassword=#Decryption password +tools.deleteDbFiles=#DeleteDbFiles +tools.deleteDbFiles.help=#Deletes all files belonging to a database. +tools.directory=#Directory +tools.encryptionPassword=#Encryption password +tools.javaDirectoryClassName=#Java directory and class name +tools.recover=#Recover +tools.recover.help=#Helps recovering a corrupted database. +tools.restore=#Restore +tools.restore.help=#Restores a database backup. +tools.result=#Result +tools.run=Jalankan +tools.runScript=#RunScript +tools.runScript.help=#Runs a SQL script. +tools.script=#Script +tools.script.help=#Allows to convert a database to a SQL script for backup or migration. +tools.scriptFileName=#Script file name +tools.serverList=#Server list +tools.sourceDatabaseName=#Source database name +tools.sourceDatabaseURL=#Source database URL +tools.sourceDirectory=#Source directory +tools.sourceFileName=#Source file name +tools.sourceScriptFileName=#Source script file name +tools.targetDatabaseName=#Target database name +tools.targetDatabaseURL=#Target database URL +tools.targetDirectory=#Target directory +tools.targetFileName=#Target file name +tools.targetScriptFileName=#Target script file name +tools.traceFileName=#Trace file name +tree.admin=Admin +tree.current=Nilai terkini +tree.hashed=Hashed +tree.increment=Penambahan +tree.indexes=Indeks +tree.nonUnique=Tidak-Unik +tree.sequences=Urut-urutan +tree.unique=Unik +tree.users=Para pengguna diff --git a/h2/src/main/org/h2/server/web/res/_text_it.prop b/h2/src/main/org/h2/server/web/res/_text_it.prop new file mode 100644 index 0000000..73fa39f --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/_text_it.prop @@ -0,0 +1,164 @@ +.translator=PierPaolo Ucchino +a.help=Aiuto +a.language=Italiano +a.lynxNotSupported=Spiacente, Lynx non è al momento supportato +a.password=Password +a.remoteConnectionsDisabled=Spiacente, le connessioni remote ('webAllowOthers') sono disabilitate su questo server. +a.title=H2 Pannello di controllo +a.tools=Accessori +a.user=Nome utente +admin.executing=Esecuzione in corso +admin.ip=IP +admin.lastAccess=Ultimo accesso +admin.lastQuery=Ultima query +admin.no=#no +admin.notConnected=#not connected +admin.url=URL +admin.yes=#yes +adminAllow=Client abilitati +adminConnection=Sicurezza della connessione +adminHttp=Usa connessioni HTTP non criptate +adminHttps=Usa connessioni criptate con SSL (HTTPS) +adminLocal=Abilita solo connessioni locali +adminLogin=Accesso amministratore +adminLoginCancel=Annulla +adminLoginOk=OK +adminLogout=Disconnessione +adminOthers=Abilita connessioni da altri computers +adminWebExternalNames=#External names or addresses of this server (comma-separated) +adminPort=Numero di porta +adminPortWeb=Numero di porta del server Web +adminRestart=Le modifiche saranno effettive dopo il riavvio del server. +adminSave=Salva +adminSessions=Connessioni attive +adminShutdown=Shutdown +adminTitle=Pannello di controllo preferenze H2 +adminTranslateHelp=Traduci o modifica la traduzione della console di H2. +adminTranslateStart=Traduci +helpAction=Azione +helpAddAnotherRow=Aggiunge un'altra riga +helpAddDrivers=Aggiunta di altri driver per l'accesso al database +helpAddDriversText=I drivers per il database possono essere inseriti aggiungendo la posizione del file Jar del driver stesso alle variabili di ambiente H2DRIVERS o CLASSPATH. Esempio (Windows): Per aggiungere alla libreria il drivers per il database C:/Programs/hsqldb/lib/hsqldb.jar, basta modificare la variabile di ambiente H2DRIVERS in C:/Programs/hsqldb/lib/hsqldb.jar. +helpAddRow=Aggiunge una nuova riga +helpCommandHistory=Mostra l'elenco dei comandi eseguiti +helpCreateTable=Crea una nuova tabella +helpDeleteRow=Rimuove una riga +helpDisconnect=Disconnette dal database +helpDisplayThis=Mostra questa pagina di aiuto +helpDropTable=Cancella la tabella se esiste +helpExecuteCurrent=Esegue il corrente comando SQL +helpExecuteSelected=#Executes the SQL statement defined by the text selection +helpIcon=Icona +helpImportantCommands=Comandi importanti +helpOperations=Operazioni +helpQuery=Seleziona tutto dalla tabella +helpSampleSQL=Programma SQL di esempio +helpStatements=Comandi SQL +helpUpdate=Cambia dei dati in una riga +helpWithColumnsIdName=tramite ID e NOME colonne +key.alt=#Alt +key.ctrl=#Ctrl +key.enter=Invio +key.shift=#Shift +key.space=#Space +login.connect=Connetti +login.driverClass=Classe driver +login.driverNotFound=Il driver del database non è stato trovato
Guardare in Aiuto su come aggiungere dei drivers +login.goAdmin=Preferenze +login.jdbcUrl=JDBC URL +login.language=Lingua +login.login=Accesso +login.remove=Rimuovi +login.save=Salva +login.savedSetting=Configurazioni salvate +login.settingName=Nome configurazione +login.testConnection=Prova la connessione +login.testSuccessful=Connessione riuscita +login.welcome=Pannello di controllo H2 +result.1row=1 riga +result.autoCommitOff=Auto inserimento adesso e' disattivo +result.autoCommitOn=Auto inserimento adesso e' attivo +result.bytes=#bytes +result.characters=#characters +result.maxrowsSet=Il numero massimo di righe e' stato impostato +result.noRows=nessuna riga +result.noRunningStatement=C'e' un comando in corso di esecuzione +result.rows=righe +result.statementWasCanceled=Il comando e' stato annullato +result.updateCount=Aggiorna il risultato +resultEdit.action=Azione +resultEdit.add=Aggiungi +resultEdit.cancel=Annulla +resultEdit.delete=Cancella +resultEdit.edit=Modifica +resultEdit.editResult=Modifica +resultEdit.save=Salva +toolbar.all=Tutto +toolbar.autoCommit=Auto inserimento +toolbar.autoComplete=Auto completamento +toolbar.autoComplete.full=Pieno +toolbar.autoComplete.normal=Normale +toolbar.autoComplete.off=Disattivo +toolbar.autoSelect=#Auto select +toolbar.autoSelect.off=Disattivo +toolbar.autoSelect.on=#On +toolbar.cancelStatement=Annulla il seguente comando +toolbar.clear=Annulla +toolbar.commit=Esegui comando +toolbar.disconnect=Disconnetti +toolbar.history=Comandi eseguiti +toolbar.maxRows=Massimo numero righe +toolbar.refresh=Aggiorna +toolbar.rollback=Annulla comando +toolbar.run=Esegui +toolbar.runSelected=#Run Selected +toolbar.sqlStatement=Comando SQL +tools.backup=Backup +tools.backup.help=Crea un backup del database. +tools.changeFileEncryption=Cambia cifratura del file +tools.changeFileEncryption.help=Permette il cambiamento della password e dell'algoritmo di cifratura del database. +tools.cipher=Cifratura (AES or XTEA) +tools.commandLine=Linea di comando +tools.convertTraceFile=Converti il file di tracciamento +tools.convertTraceFile.help=Converte un file .trace.db in una applicazione java e uno script SQL. +tools.createCluster=Crea un cluster +tools.createCluster.help=Crea un cluster da un database standalone. +tools.databaseName=Nome del database +tools.decryptionPassword=Password per la decifratura +tools.deleteDbFiles=Cancella files del database +tools.deleteDbFiles.help=Cancella tutti i files appartenenti a un database. +tools.directory=Directory +tools.encryptionPassword=Password di cifratura +tools.javaDirectoryClassName=Directory java e nome della classe +tools.recover=Recupera +tools.recover.help=Aiuta a recuperare un database corrotto. +tools.restore=Ripristina +tools.restore.help=Ripristina un backup di un database. +tools.result=Risultato +tools.run=Esegui +tools.runScript=Esegui script +tools.runScript.help=Esegui script SQL. +tools.script=Script +tools.script.help=Permette la conversione di un database in uno script SQL per un backup o una migrazione. +tools.scriptFileName=Nome del file di script +tools.serverList=Lista dei servers +tools.sourceDatabaseName=Nome del database sorgente +tools.sourceDatabaseURL=URL del database sorgente +tools.sourceDirectory=Directory sorgente +tools.sourceFileName=Nome del file sorgente +tools.sourceScriptFileName=Nome del file script sorgente +tools.targetDatabaseName=Nome del database destinazione +tools.targetDatabaseURL=URL del database destinazione +tools.targetDirectory=Directory destinazione +tools.targetFileName=Nome del file destinazione +tools.targetScriptFileName=Nome del file di script destinazione +tools.traceFileName=Nome del file di traccia +tree.admin=Amministratore +tree.current=Valore corrente +tree.hashed=Valore hash +tree.increment=Incremento +tree.indexes=Indici +tree.nonUnique=Non unico +tree.sequences=Sequenze +tree.unique=Unico +tree.users=Utenti diff --git a/h2/src/main/org/h2/server/web/res/_text_ja.prop b/h2/src/main/org/h2/server/web/res/_text_ja.prop new file mode 100644 index 0000000..f998bfd --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/_text_ja.prop @@ -0,0 +1,164 @@ +.translator=IKEMOTO, Masahiro +a.help=ヘルプ +a.language=日本語 +a.lynxNotSupported=Lynxにはまだ対応していません +a.password=パスワード +a.remoteConnectionsDisabled=このサーバでは、リモート接続('webAllowOthers')が有効になっていません。 +a.title=H2コンソール +a.tools=ツール +a.user=ユーザ名 +admin.executing=実行中 +admin.ip=IP +admin.lastAccess=最終アクセス +admin.lastQuery=最終問い合せ +admin.no=#no +admin.notConnected=#not connected +admin.url=URL +admin.yes=#yes +adminAllow=クライアント接続許可 +adminConnection=接続セキュリティ +adminHttp=暗号化されない HTTP 接続を使用 +adminHttps=暗号化された SSL (HTTPS) 接続を使用 +adminLocal=ローカル接続のみ許可 +adminLogin=管理者ログイン +adminLoginCancel=キャンセル +adminLoginOk=OK +adminLogout=ログアウト +adminOthers=他のコンピュータからの接続を許可 +adminWebExternalNames=#External names or addresses of this server (comma-separated) +adminPort=ポート番号 +adminPortWeb=Webサーバポート番号 +adminRestart=変更はサーバの再起動後に有効になります。 +adminSave=保存 +adminSessions=アクティブセッション +adminShutdown=シャットダウン +adminTitle=H2コンソール設定 +adminTranslateHelp=H2コンソールの日本語訳を更新します。 +adminTranslateStart=翻訳 +helpAction=アクション +helpAddAnotherRow=別の行を追加 +helpAddDrivers=データベースドライバの追加 +helpAddDriversText=追加ドライバは、Jarファイルの場所を環境変数 H2DRIVERS、または CLASSPATH に追加することで登録できます。例 (Windows): データベースドライバライブラリ C:/Programs/hsqldb/lib/hsqldb.jar を追加するには、環境変数 H2DRIVERS に、C:/Programs/hsqldb/lib/hsqldb.jar を設定します。 +helpAddRow=新しい行を追加 +helpCommandHistory=コマンド履歴を表示 +helpCreateTable=ID、および NAME 列を持つ +helpDeleteRow=行を削除 +helpDisconnect=データベース接続を切断 +helpDisplayThis=このページを表示 +helpDropTable=存在するならテーブルを削除 +helpExecuteCurrent=現在のSQLステートメントを実行 +helpExecuteSelected=#Executes the SQL statement defined by the text selection +helpIcon=アイコン +helpImportantCommands=重要なコマンド +helpOperations=オペレーション +helpQuery=テーブル問い合わせ +helpSampleSQL=SQLステートメントのサンプル +helpStatements=SQLステートメント +helpUpdate=行データの変更 +helpWithColumnsIdName=新しいテーブルを作成 +key.alt=#Alt +key.ctrl=#Ctrl +key.enter=#Enter +key.shift=#Shift +key.space=#Space +login.connect=接続 +login.driverClass=ドライバクラス +login.driverNotFound=データベースドライバが見付かりません
ヘルプでドライバの追加方法を確認してください +login.goAdmin=設定 +login.jdbcUrl=JDBC URL +login.language=言語 +login.login=ログイン +login.remove=削除 +login.save=保存 +login.savedSetting=保存済設定 +login.settingName=設定名 +login.testConnection=接続テスト +login.testSuccessful=テストは成功しました +login.welcome=H2コンソール +result.1row=1 行 +result.autoCommitOff=オートコミットが無効になりました +result.autoCommitOn=オートコミットが有効になりました +result.bytes=バイト +result.characters=文字 +result.maxrowsSet=最大行数が設定されました +result.noRows=該当行無し +result.noRunningStatement=現在実行中のステートメントはありません +result.rows=行 +result.statementWasCanceled=ステートメントがキャンセルされました +result.updateCount=更新数 +resultEdit.action=アクション +resultEdit.add=追加 +resultEdit.cancel=キャンセル +resultEdit.delete=削除 +resultEdit.edit=編集 +resultEdit.editResult=編集 +resultEdit.save=保存 +toolbar.all=全て +toolbar.autoCommit=オートコミット +toolbar.autoComplete=オートコンプリート +toolbar.autoComplete.full=フル +toolbar.autoComplete.normal=ノーマル +toolbar.autoComplete.off=オフ +toolbar.autoSelect=#Auto select +toolbar.autoSelect.off=オフ +toolbar.autoSelect.on=#On +toolbar.cancelStatement=現在のステートメントをキャンセル +toolbar.clear=クリア +toolbar.commit=コミット +toolbar.disconnect=切断 +toolbar.history=コマンド履歴 +toolbar.maxRows=最大行数 +toolbar.refresh=リフレッシュ +toolbar.rollback=ロールバック +toolbar.run=実行 +toolbar.runSelected=#Run Selected +toolbar.sqlStatement=SQLステートメント +tools.backup=バックアップ +tools.backup.help=データベースのバックアップを作成します。 +tools.changeFileEncryption=ファイル暗号化設定の変更 +tools.changeFileEncryption.help=データベースファイルの暗号化方式とパスワードを変更します。 +tools.cipher=暗号化方式 (AES or XTEA) +tools.commandLine=コマンドライン +tools.convertTraceFile=トレースファイルの変換 +tools.convertTraceFile.help=.trace.dbファイルを、JavaアプリケーションとSQLスクリプトに変換します。 +tools.createCluster=クラスタ作成 +tools.createCluster.help=スタンドアロンデータベースからクラスタを作成します。 +tools.databaseName=データベース名 +tools.decryptionPassword=復号化パスワード +tools.deleteDbFiles=データベースファイルの削除 +tools.deleteDbFiles.help=データベースに関連する全てのファイルを削除します。 +tools.directory=ディレクトリ +tools.encryptionPassword=暗号化パスワード +tools.javaDirectoryClassName=Javaディレクトリとクラス名 +tools.recover=修復 +tools.recover.help=破損したデータベースの修復を支援します。 +tools.restore=リストア +tools.restore.help=データベースをバックアップからリストアします。 +tools.result=結果 +tools.run=実行 +tools.runScript=スクリプトの実行 +tools.runScript.help=SQLスクリプトを実行します。 +tools.script=スクリプト +tools.script.help=バックアップ、または移動のために、データベースをSQLスクリプトに変換します。 +tools.scriptFileName=スクリプトファイル名 +tools.serverList=サーバリスト +tools.sourceDatabaseName=ソースデータベース名 +tools.sourceDatabaseURL=ソースデータベースURL +tools.sourceDirectory=ソースディレクトリ +tools.sourceFileName=ソースファイル名 +tools.sourceScriptFileName=ソーススクリプトファイル名 +tools.targetDatabaseName=ターゲットデータベース名 +tools.targetDatabaseURL=ターゲットデータベースURL +tools.targetDirectory=ターゲットディレクトリ +tools.targetFileName=ターゲットファイル名 +tools.targetScriptFileName=ターゲットスクリプトファイル名 +tools.traceFileName=トレースファイル名 +tree.admin=管理者 +tree.current=現在値 +tree.hashed=ハッシュ +tree.increment=インクリメント +tree.indexes=インデックス +tree.nonUnique=Non-Unique +tree.sequences=シーケンス +tree.unique=Unique +tree.users=ユーザ diff --git a/h2/src/main/org/h2/server/web/res/_text_ko.prop b/h2/src/main/org/h2/server/web/res/_text_ko.prop new file mode 100644 index 0000000..cfa58eb --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/_text_ko.prop @@ -0,0 +1,164 @@ +.translator=Dylan +a.help=도움말 +a.language=한국어 +a.lynxNotSupported=죄송하지만 Lynx는 아직 지원되지 않습니다 +a.password=비밀번호 +a.remoteConnectionsDisabled=죄송하지만 이 서버는 원격 연결('webAllowOthers')이 사용 중지된 상태입니다. +a.title=H2 콘솔 +a.tools=도구 +a.user=사용자명 +admin.executing=실행 여부 +admin.ip=IP +admin.lastAccess=최종 액세스 +admin.lastQuery=최종 질의 +admin.no=아니요 +admin.notConnected=연결되지 않았음 +admin.url=URL +admin.yes=예 +adminAllow=클라이언트 허가 +adminConnection=연결 보안 +adminHttp=암호화되지 않은 HTTP 연결 사용 +adminHttps=암호화된 SSL(HTTPS) 연결 사용 +adminLocal=로컬 연결만 허가 +adminLogin=관리자 로그인 +adminLoginCancel=취소 +adminLoginOk=확인 +adminLogout=로그아웃 +adminOthers=다른 컴퓨터에서의 연결 허가 +adminWebExternalNames=#External names or addresses of this server (comma-separated) +adminPort=포트 번호 +adminPortWeb=웹 서버 포트 번호 +adminRestart=변경 사항은 서버 재시작 후 반영됩니다. +adminSave=저장 +adminSessions=활동중인 세션 +adminShutdown=종료 +adminTitle=H2 콘솔 설정 +adminTranslateHelp=H2 콘솔을 번역하거나 번역을 수정할 수 있습니다. +adminTranslateStart=번역 +helpAction=작업 +helpAddAnotherRow=행 추가 +helpAddDrivers=데이터베이스 드라이버 추가 +helpAddDriversText=데이터베이스 드라이버를 추가로 등록하려면 H2DRIVERS나 CLASSPATH 환경 변수에 드라이버의 Jar 파일 위치를 추가하면 됩니다. 예 (Windows): 데이터베이스 드라이버 라이브러리로 C:/Programs/hsqldb/lib/hsqldb.jar를 추가하려면 H2DRIVERS 환경 변수를 C:/Programs/hsqldb/lib/hsqldb.jar로 설정합니다. +helpAddRow=행 추가 +helpCommandHistory=명령 이력 보기 +helpCreateTable=새 테이블 만들기 +helpDeleteRow=행 삭제 +helpDisconnect=데이터베이스 연결 끊기 +helpDisplayThis=이 도움말 페이지 보기 +helpDropTable=테이블이 존재하는 경우 삭제하기 +helpExecuteCurrent=현재의 SQL 문 실행 +helpExecuteSelected=#Executes the SQL statement defined by the text selection +helpIcon=아이콘 +helpImportantCommands=중요 명령 +helpOperations=작동 +helpQuery=테이블 질의 +helpSampleSQL=샘플 SQL 스크립트 +helpStatements=SQL 문 +helpUpdate=행 데이터 변경 +helpWithColumnsIdName=컬럼은 ID와 NAME +key.alt=#Alt +key.ctrl=#Ctrl +key.enter=엔터 +key.shift=#Shift +key.space=#Space +login.connect=연결 +login.driverClass=드라이버 클래스 +login.driverNotFound=데이터베이스 드라이버를 찾지 못함
도움말에서 드라이버 추가 방법 참고 +login.goAdmin=설정 +login.jdbcUrl=JDBC URL +login.language=언어 +login.login=로그인 +login.remove=삭제 +login.save=저장 +login.savedSetting=저장한 설정 +login.settingName=설정 이름 +login.testConnection=연결 시험 +login.testSuccessful=시험 성공 +login.welcome=H2 콘설 +result.1row=1 row +result.autoCommitOff=자동 커밋이 중지됨 +result.autoCommitOn=자동 커밋이 작동됨 +result.bytes=바이트 +result.characters=자 +result.maxrowsSet=최대 행 개수 설정 +result.noRows=행 없음 +result.noRunningStatement=현재 실행중인 문 없음 +result.rows=행 +result.statementWasCanceled=문이 취소됨 +result.updateCount=갱신된 개수 +resultEdit.action=작업 +resultEdit.add=추가 +resultEdit.cancel=취소 +resultEdit.delete=삭제 +resultEdit.edit=편집 +resultEdit.editResult=편집 +resultEdit.save=저장 +toolbar.all=전체 +toolbar.autoCommit=자동 커밋 +toolbar.autoComplete=자동 완성 +toolbar.autoComplete.full=전체 +toolbar.autoComplete.normal=보통 +toolbar.autoComplete.off=안함 +toolbar.autoSelect=#Auto select +toolbar.autoSelect.off=안함 +toolbar.autoSelect.on=#On +toolbar.cancelStatement=현재 문 취소 +toolbar.clear=지우기 +toolbar.commit=커밋 +toolbar.disconnect=연결 끊기 +toolbar.history=명령 이력 +toolbar.maxRows=최대 행 수 +toolbar.refresh=새로 고침 +toolbar.rollback=롤백 +toolbar.run=실행 +toolbar.runSelected=#Run Selected +toolbar.sqlStatement=SQL 문 +tools.backup=백업 +tools.backup.help=데이터베이스 백업 만들기. +tools.changeFileEncryption=파일암호화변경 +tools.changeFileEncryption.help=데이터베이스 파일 암호화 비밀번호 및 앨거리듬을 변경할 수 있습니다. +tools.cipher=Cipher (AES 또는 XTEA) +tools.commandLine=명령줄 +tools.convertTraceFile=트레이스파일변환 +tools.convertTraceFile.help=.trace.db 파일을 Java 애플리케이션 및 SQL 스크립트로 변환합니다. +tools.createCluster=클러스터생성 +tools.createCluster.help=독립형 데이터베이스에서 클러스터를 만듧니다. +tools.databaseName=데이터베이스 이름 +tools.decryptionPassword=복호화 비밀번호 +tools.deleteDbFiles=Db파일삭제 +tools.deleteDbFiles.help=데이터베이스에 속한 모든 파일을 삭제합니다. +tools.directory=디렉터리 +tools.encryptionPassword=암호화 비밀번호 +tools.javaDirectoryClassName=Java 디렉터리 및 클래스 이름 +tools.recover=복구 +tools.recover.help=손상된 데이터베이스를 복구하도록 합니다. +tools.restore=복원 +tools.restore.help=데이터베이스 백업을 복원합니다. +tools.result=결과 +tools.run=실행 +tools.runScript=스크립트실행 +tools.runScript.help=SQL 스크립트를 실행합니다. +tools.script=스크립트 +tools.script.help=백업 또는 이관을 위해 데이터베이스를 SQL 스크립트로 변환할 수 있습니다. +tools.scriptFileName=스크립트 파일명 +tools.serverList=서버 목록 +tools.sourceDatabaseName=원본 데이터베이스 이름 +tools.sourceDatabaseURL=원본 데이터베이스 URL +tools.sourceDirectory=원본 디렉터리 +tools.sourceFileName=원본 파일명 +tools.sourceScriptFileName=원본 스크립트 파일명 +tools.targetDatabaseName=대상 데이터베이스 이름 +tools.targetDatabaseURL=대상 데이터베이스 URL +tools.targetDirectory=대상 디렉터리 +tools.targetFileName=대상 파일명 +tools.targetScriptFileName=대상 스크립트 파일명 +tools.traceFileName=트레이스 파일명 +tree.admin=Admin +tree.current=현재 값 +tree.hashed=Hashed +tree.increment=증가 +tree.indexes=인덱스 +tree.nonUnique=유니크아님 +tree.sequences=시퀀스 +tree.unique=유니크 +tree.users=사용자 diff --git a/h2/src/main/org/h2/server/web/res/_text_nl.prop b/h2/src/main/org/h2/server/web/res/_text_nl.prop new file mode 100644 index 0000000..5c04618 --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/_text_nl.prop @@ -0,0 +1,164 @@ +.translator=Remco Schoen +a.help=Help +a.language=Nederlands +a.lynxNotSupported=Sorry, Lynx wordt nog niet ondersteund +a.password=Wachtwoord +a.remoteConnectionsDisabled=Sorry, externe verbindingen ('webAllowOthers') zijn niet toegelaten voor deze server. +a.title=H2 Console +a.tools=#Tools +a.user=Gebruikersnaam +admin.executing=Uitvoeren +admin.ip=IP +admin.lastAccess=Laatste toegang +admin.lastQuery=Laatste query +admin.no=#no +admin.notConnected=#not connected +admin.url=URL +admin.yes=#yes +adminAllow=Toegestane clients +adminConnection=Beveiliging verbinding +adminHttp=Gebruik onversleutelde HTTP verbindingen +adminHttps=Gebruik versleutelde SSL (HTTPS) verbindingen +adminLocal=Sta alleen lokale verbindingen toe +adminLogin=Inloggen instellingen +adminLoginCancel=Annuleren +adminLoginOk=OK +adminLogout=Uitloggen +adminOthers=Sta verbindingen vanaf andere computers toe +adminWebExternalNames=#External names or addresses of this server (comma-separated) +adminPort=Poortnummer +adminPortWeb=Webserver poortnummer +adminRestart=Wijzigingen worden doorgevoerd na herstarten server +adminSave=Opslaan +adminSessions=Actieve sessies +adminShutdown=Server stoppen +adminTitle=H2 Console instellingen +adminTranslateHelp=#Translate or improve the translation of the H2 Console. +adminTranslateStart=#Translate +helpAction=Gebeurtenis +helpAddAnotherRow=Voeg nogmaals een nieuwe regel toe +helpAddDrivers=Toevoegen drivers voor een database +helpAddDriversText=Extra drivers voor een database kunnen worden geregistreerd door het toevoegen van het Jar-bestand van de driver aan de omgevingsvariabelen H2DRIVERS of CLASSPATH. Voorbeeld (Windows): om de driver bibliotheek C:/Programs/hsqldb/lib/hsqldb.jar toe te voegen, moet de omgevingsvariabele H2DRIVERS op C:/Programs/hsqldb/lib/hsqldb.jar gezet worden. +helpAddRow=Voeg een nieuwe regel toe +helpCommandHistory=Toont de geschiedenis van commando's +helpCreateTable=Maak een nieuwe table aan +helpDeleteRow=Verwijder een regel +helpDisconnect=Verbreekt de verbinding met de database +helpDisplayThis=Toont deze helppagina +helpDropTable=Verwijdert de tabel als deze bestaat +helpExecuteCurrent=Voert het huidige SQL-statement uit +helpExecuteSelected=#Executes the SQL statement defined by the text selection +helpIcon=Icoon +helpImportantCommands=Belangrijke commando's +helpOperations=Acties +helpQuery=Vraag gegevens op uit de tabel +helpSampleSQL=Voorbeeld SQL-script +helpStatements=SQL-statements +helpUpdate=Verander de gegevens in een rij +helpWithColumnsIdName=met de kolommen ID en NAME +key.alt=#Alt +key.ctrl=#Ctrl +key.enter=#Enter +key.shift=#Shift +key.space=#Space +login.connect=Verbind +login.driverClass=Driver klasse +login.driverNotFound=Driver voor database niet gevonden
Kijk in de help hoe je drivers kunt toevoegen +login.goAdmin=Instellingen +login.jdbcUrl=JDBC URL +login.language=Taal +login.login=Inloggen +login.remove=Verwijderen +login.save=Opslaan +login.savedSetting=Opgeslagen instellingen +login.settingName=Instellingsnaam +login.testConnection=Test verbinding +login.testSuccessful=Test succesvol +login.welcome=H2 Console +result.1row=1 rij +result.autoCommitOff=Auto commit is nu UIT +result.autoCommitOn=Auto commit is nu AAN +result.bytes=#bytes +result.characters=#characters +result.maxrowsSet=Het maximum aantal rijen is ingesteld +result.noRows=geen rijen +result.noRunningStatement=Er wordt momenteel geen statement uitgevoerd +result.rows=rijen +result.statementWasCanceled=Het statement is geannuleerd +result.updateCount=Aantal wijzigingen +resultEdit.action=Gebeurtenis +resultEdit.add=Toevoegen +resultEdit.cancel=Annuleren +resultEdit.delete=Verwijderen +resultEdit.edit=Wijzigen +resultEdit.editResult=Wijzigen +resultEdit.save=Opslaan +toolbar.all=Alle +toolbar.autoCommit=Auto commit +toolbar.autoComplete=Auto aanvullen +toolbar.autoComplete.full=Volledig +toolbar.autoComplete.normal=Normaal +toolbar.autoComplete.off=Uit +toolbar.autoSelect=#Auto select +toolbar.autoSelect.off=Uit +toolbar.autoSelect.on=#On +toolbar.cancelStatement=Annuleer het huidige statement +toolbar.clear=Wissen +toolbar.commit=Commit +toolbar.disconnect=Verbinding verbreken +toolbar.history=Geschiedenis commando's +toolbar.maxRows=Max rijen +toolbar.refresh=Verversen +toolbar.rollback=Rollback +toolbar.run=Uitvoeren +toolbar.runSelected=#Run Selected +toolbar.sqlStatement=SQL statement +tools.backup=#Backup +tools.backup.help=#Creates a backup of a database. +tools.changeFileEncryption=#ChangeFileEncryption +tools.changeFileEncryption.help=#Allows changing the database file encryption password and algorithm. +tools.cipher=#Cipher (AES or XTEA) +tools.commandLine=#Command line +tools.convertTraceFile=#ConvertTraceFile +tools.convertTraceFile.help=#Converts a .trace.db file to a Java application and SQL script. +tools.createCluster=#CreateCluster +tools.createCluster.help=#Creates a cluster from a standalone database. +tools.databaseName=#Database name +tools.decryptionPassword=#Decryption password +tools.deleteDbFiles=#DeleteDbFiles +tools.deleteDbFiles.help=#Deletes all files belonging to a database. +tools.directory=#Directory +tools.encryptionPassword=#Encryption password +tools.javaDirectoryClassName=#Java directory and class name +tools.recover=#Recover +tools.recover.help=#Helps recovering a corrupted database. +tools.restore=#Restore +tools.restore.help=#Restores a database backup. +tools.result=#Result +tools.run=Uitvoeren +tools.runScript=#RunScript +tools.runScript.help=#Runs a SQL script. +tools.script=#Script +tools.script.help=#Allows to convert a database to a SQL script for backup or migration. +tools.scriptFileName=#Script file name +tools.serverList=#Server list +tools.sourceDatabaseName=#Source database name +tools.sourceDatabaseURL=#Source database URL +tools.sourceDirectory=#Source directory +tools.sourceFileName=#Source file name +tools.sourceScriptFileName=#Source script file name +tools.targetDatabaseName=#Target database name +tools.targetDatabaseURL=#Target database URL +tools.targetDirectory=#Target directory +tools.targetFileName=#Target file name +tools.targetScriptFileName=#Target script file name +tools.traceFileName=#Trace file name +tree.admin=Beheerder +tree.current=Huidige waarde +tree.hashed=Hashed +tree.increment=Ophogen +tree.indexes=Indices +tree.nonUnique=Niet uniek +tree.sequences=Sequenties +tree.unique=Uniek +tree.users=Gebruikers diff --git a/h2/src/main/org/h2/server/web/res/_text_pl.prop b/h2/src/main/org/h2/server/web/res/_text_pl.prop new file mode 100644 index 0000000..b13069b --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/_text_pl.prop @@ -0,0 +1,164 @@ +.translator=Tomek +a.help=Pomoc +a.language=Polski +a.lynxNotSupported=Przeglądarka Lynx nie jest wpierana jeszcze +a.password=Hasło +a.remoteConnectionsDisabled=Połączenia zdalne ('webAllowOthers') są wyłączone na tym serwerze. +a.title=Konsola H2 +a.tools=Narzędzia +a.user=Użytkownik +admin.executing=Wykonywanie +admin.ip=IP +admin.lastAccess=Ostatnie logowanie +admin.lastQuery=Ostatnie zapytanie +admin.no=no +admin.notConnected=not connected +admin.url=URL +admin.yes=yes +adminAllow=Zaufani klienci +adminConnection=Bezpieczeństwo połączeń +adminHttp=Używaj szyfrowanych połączeń HTTP +adminHttps=Używaj szyfrowanych połączeń SSL (HTTPS) +adminLocal=Tylko lokalne połączenia +adminLogin=Logowanie administracyjne +adminLoginCancel=Anuluj +adminLoginOk=OK +adminLogout=Wyloguj +adminOthers=Pozwalaj na połączenia zdalne +adminWebExternalNames=#External names or addresses of this server (comma-separated) +adminPort=Numer portu +adminPortWeb=Numer portu serwera Web +adminRestart=Zmiany będą widoczne po zrestartowaniu serwera. +adminSave=Zapisz +adminSessions=Aktywne sesje +adminShutdown=Wyłącz +adminTitle=Ustawienia konsoli H2 +adminTranslateHelp=Przetłumacz lub popraw tłumaczenie konsoli H2. +adminTranslateStart=Tłumacz +helpAction=Akcja +helpAddAnotherRow=Dodaj kolejny rekord +helpAddDrivers=Dodatkowe sterowniki baz danych +helpAddDriversText=Dodatkowe sterowniki bazy danych mogą być rejestrowane przez dodanie plików Jar do zmiennych środowiskowych H2DRIVERS lub CLASSPATH. Przykładowo (Windows): Aby dodać sterownik z pliku C:/Programs/hsqldb/lib/hsqldb.jar, ustaw zmienną środowiskową H2DRIVERS na C:/Programs/hsqldb/lib/hsqldb.jar. +helpAddRow=Dodaj nowy rekord +helpCommandHistory=Pokazuje historię komend +helpCreateTable=Tworzy nową tabele +helpDeleteRow=Usuń rekord +helpDisconnect=Wyloguj się z bazy danych +helpDisplayThis=Wyświetla tą strone pomocy +helpDropTable=Skasuj tabele jeżeli istnieje +helpExecuteCurrent=Wykonuje bieżące zapytanie SQL +helpExecuteSelected=#Executes the SQL statement defined by the text selection +helpIcon=Ikona +helpImportantCommands=Ważne komendy +helpOperations=Operacje +helpQuery=Wykonaj zapytanie wybierajace +helpSampleSQL=Prosty skrypt SQL +helpStatements=Wyrażenia SQL +helpUpdate=Zmień dane w rekordzie +helpWithColumnsIdName=z kolumnami ID i NAME +key.alt=#Alt +key.ctrl=#Ctrl +key.enter=#Enter +key.shift=#Shift +key.space=#Space +login.connect=Połącz +login.driverClass=Klasa sterownika +login.driverNotFound=Sterownik nie istnieje
Zobacz w dokumentacji opis dodawania sterowników +login.goAdmin=Opcje +login.jdbcUrl=JDBC URL +login.language=Język +login.login=Użytkownik +login.remove=Usuń +login.save=Zapisz +login.savedSetting=Zapisz opcje +login.settingName=Nazwa opcji +login.testConnection=Testuj połaczenie +login.testSuccessful=Test zakończony sukcesem +login.welcome=Konsola H2 +result.1row=1 rekord +result.autoCommitOff=Automatyczne zatwierdzanie jest teraz WYŁĄCZONE +result.autoCommitOn=Automatyczne zatwierdzanie jest teraz WŁACZONE +result.bytes=bytes +result.characters=characters +result.maxrowsSet=Maksymalna ilość rekordów +result.noRows=brak danych +result.noRunningStatement=Obecnie nie jest wykonywane żedne zapytanie +result.rows=rekordy +result.statementWasCanceled=Zapytanie zostało anulowane +result.updateCount=Ilość aktualizacji +resultEdit.action=Akcja +resultEdit.add=Dodaj +resultEdit.cancel=Anuluj +resultEdit.delete=Skasuj +resultEdit.edit=Edytuj +resultEdit.editResult=Edytuj +resultEdit.save=Zapisz +toolbar.all=Wszystko +toolbar.autoCommit=Automatyczne zatwierdzanie +toolbar.autoComplete=Automatyczne uzupełnianie +toolbar.autoComplete.full=Pełny +toolbar.autoComplete.normal=Normalny +toolbar.autoComplete.off=Wyłączony +toolbar.autoSelect=#Auto select +toolbar.autoSelect.off=Wyłączony +toolbar.autoSelect.on=#On +toolbar.cancelStatement=Anuluj bieżące zapytanie +toolbar.clear=Wyczyść +toolbar.commit=Zatwierdź +toolbar.disconnect=Rozłącz +toolbar.history=Historia komend +toolbar.maxRows=Maksymalna ilość rekordów +toolbar.refresh=Odśwież +toolbar.rollback=Cofnij zmiany +toolbar.run=Wykonaj +toolbar.runSelected=#Run Selected +toolbar.sqlStatement=Zapytanie SQL +tools.backup=Backup +tools.backup.help=Twozy kopie bezpieczenstwa bazy danych. +tools.changeFileEncryption=Zmien kodowanie pliku +tools.changeFileEncryption.help=Zezwalaj a zmiane hasła i algorytmu szyforowania. +tools.cipher=Cipher (AES lub XTEA) +tools.commandLine=Linia komend +tools.convertTraceFile=Konwertuj plik śladu +tools.convertTraceFile.help=Konwertuje plik .trace.db na aplikacje Java i skrypt SQL. +tools.createCluster=Utwórz Klaster +tools.createCluster.help=Tworzy klaster z samodzielnej bazy danych. +tools.databaseName=Naza bazy danych +tools.decryptionPassword=Hasło deszyfrowania +tools.deleteDbFiles=Skasuj pliki bazy danych +tools.deleteDbFiles.help=Skasuj wszystkie pliki bazy danych. +tools.directory=Katalog +tools.encryptionPassword=hasło szyfrowania +tools.javaDirectoryClassName=Katalog Javy i nazwa klasy +tools.recover=Odzyskaj +tools.recover.help=Pomaga odzyskać dane z uszkodzonej bazy. +tools.restore=Przywroć +tools.restore.help=Przywraca baze z archiwum. +tools.result=Wynik +tools.run=Uruchom +tools.runScript=Wykonaj Skrypt +tools.runScript.help=Uruchamia skrytp SQL. +tools.script=Skrypt +tools.script.help=Pozwala na konwersje bazy danych do skryptu SQL dla potrzeb kopii bezpieczenstwa lub migracji bazy. +tools.scriptFileName=Nazwa pliku skryptu +tools.serverList=Lista serwerów +tools.sourceDatabaseName=Nazwa źrodłowej bazy danych +tools.sourceDatabaseURL=URL źrodłowej bazy danych +tools.sourceDirectory=Źrodłowy katalog +tools.sourceFileName=Nazwa pliku źrodłowego +tools.sourceScriptFileName=Nazwa pliku źrodłowego skryptu +tools.targetDatabaseName=Nazwa docelowej bazy danych +tools.targetDatabaseURL=URL docelowej bazy danych +tools.targetDirectory=Docelowy katalog +tools.targetFileName=Nazwa pliku docelowego +tools.targetScriptFileName=Nazwa pliku docelowego skryptu +tools.traceFileName=Nazwa pliku śladu +tree.admin=Administrator +tree.current=Bieżąca wartość +tree.hashed=Haszowany +tree.increment=Zwiekszenie +tree.indexes=Indeksy +tree.nonUnique=Nieunikalny +tree.sequences=Sekwencje +tree.unique=Unikalny +tree.users=Użytkownicy diff --git a/h2/src/main/org/h2/server/web/res/_text_pt_br.prop b/h2/src/main/org/h2/server/web/res/_text_pt_br.prop new file mode 100644 index 0000000..56516c9 --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/_text_pt_br.prop @@ -0,0 +1,164 @@ +.translator=Eduardo Fonseca Velasques +a.help=Ajuda +a.language=Português (Brasil) +a.lynxNotSupported=Lynx não é suportado +a.password=Senha +a.remoteConnectionsDisabled=Conexões remotas ('webAllowOthers') estão desativadas neste servidor. +a.title=H2 Terminal +a.tools=#Tools +a.user=Usuário +admin.executing=Executando +admin.ip=IP +admin.lastAccess=Último Acesso +admin.lastQuery=Última Query +admin.no=#no +admin.notConnected=#not connected +admin.url=URL +admin.yes=#yes +adminAllow=Clientes com acesso +adminConnection=Nível de segurança da conexão +adminHttp=Usar conexão do tipo HTTP não segura (os dados não são encriptados) +adminHttps=Usar conexão do tipo HTTPS segura por SSL (os dados são encriptados) +adminLocal=Permitir apenas conexões do computador local +adminLogin=Entrar como administrador +adminLoginCancel=Cancelar +adminLoginOk=Confirmar +adminLogout=Sair +adminOthers=Permitir conexões de outros computadores na rede +adminWebExternalNames=#External names or addresses of this server (comma-separated) +adminPort=Número da porta +adminPortWeb=Número da porta do servidor +adminRestart=As alterações serão aplicadas depois de reiniciar o servidor. +adminSave=Salvar +adminSessions=Sessões ativas +adminShutdown=Terminar +adminTitle=Configuração do H2 Terminal +adminTranslateHelp=#Translate or improve the translation of the H2 Console. +adminTranslateStart=#Translate +helpAction=Ação +helpAddAnotherRow=Adicionar outra linha +helpAddDrivers=Adicionar drivers de Base de Dados +helpAddDriversText=É possível registrar outros drivers, adicionando o arquivo JAR respectivo na variável de ambiente H2DRIVERS ou CLASSPATH. Exemplo (Windows): Para adicionar o driver que está em C:/Programs/hsqldb/lib/hsqldb.jar altere o valor da variável de ambiente H2DRIVERS para C:/Programs/hsqldb/lib/hsqldb.jar. +helpAddRow=Adicionar uma linha nova +helpCommandHistory=Mostrar o histórico de comandos +helpCreateTable=Criar uma tabela nova +helpDeleteRow=Apagar uma linha +helpDisconnect=Fechar conexão à Base de Dados +helpDisplayThis=Mostrar está página de ajuda +helpDropTable=Apagar a tabela, caso ela exista +helpExecuteCurrent=Executar o comando SQL corrente +helpExecuteSelected=#Executes the SQL statement defined by the text selection +helpIcon=Símbolo +helpImportantCommands=Comandos importantes +helpOperations=Operações= +helpQuery=Pesquisar uma tabela +helpSampleSQL=Scripts de exemplo +helpStatements=Comandos SQL +helpUpdate=Alterar os dados de uma linha +helpWithColumnsIdName=com as colunas ID e NAME +key.alt=#Alt +key.ctrl=#Ctrl +key.enter=#Enter +key.shift=#Shift +key.space=#Space +login.connect=Conectar +login.driverClass=Classe com o driver +login.driverNotFound=O driver não foi encontrado
Ver na seção de ajuda, como adicionar drivers +login.goAdmin=Preferências +login.jdbcUrl=JDBC URL +login.language=Língua +login.login=Login +login.remove=Remover +login.save=Gravar +login.savedSetting=Configuração ativa +login.settingName=Nome da configuração +login.testConnection=Testar conexão +login.testSuccessful=Teste bem sucedido +login.welcome=Consola H2 +result.1row=Uma linha +result.autoCommitOff=Auto commit agora está desligado +result.autoCommitOn=Auto commit agora está ligado +result.bytes=#bytes +result.characters=#characters +result.maxrowsSet=Número máximo de linhas foi alterado +result.noRows=sem linhas +result.noRunningStatement=Actualmente não existe nenhum comando em execução +result.rows=linhas +result.statementWasCanceled=O comando foi cancelado +result.updateCount=Número de registros alterados +resultEdit.action=Ação +resultEdit.add=Adicionar +resultEdit.cancel=Cancelar +resultEdit.delete=Apagar +resultEdit.edit=Alterar +resultEdit.editResult=Alterar +resultEdit.save=Salvar +toolbar.all=Todos +toolbar.autoCommit=Auto commit +toolbar.autoComplete=Auto complete +toolbar.autoComplete.full=Total +toolbar.autoComplete.normal=Normal +toolbar.autoComplete.off=Desligado +toolbar.autoSelect=Auto seleção +toolbar.autoSelect.off=Desligado +toolbar.autoSelect.on=Ligado +toolbar.cancelStatement=Cancelar o comando que está em execução +toolbar.clear=Limpar +toolbar.commit=Commit +toolbar.disconnect=Desligar +toolbar.history=Histórico de comandos executados +toolbar.maxRows=Número máximo de linhas +toolbar.refresh=Atualizar +toolbar.rollback=Rollback +toolbar.run=Executar comando +toolbar.runSelected=Executar selecionado +toolbar.sqlStatement=Comando SQL +tools.backup=#Backup +tools.backup.help=Cria um backup de um banco de dados. +tools.changeFileEncryption=#ChangeFileEncryption +tools.changeFileEncryption.help=#Allows changing the database file encryption password and algorithm. +tools.cipher=#Cipher (AES or XTEA) +tools.commandLine=#Command line +tools.convertTraceFile=#ConvertTraceFile +tools.convertTraceFile.help=#Converts a .trace.db file to a Java application and SQL script. +tools.createCluster=#CreateCluster +tools.createCluster.help=#Creates a cluster from a standalone database. +tools.databaseName=#Database name +tools.decryptionPassword=#Decryption password +tools.deleteDbFiles=#DeleteDbFiles +tools.deleteDbFiles.help=#Deletes all files belonging to a database. +tools.directory=#Directory +tools.encryptionPassword=#Encryption password +tools.javaDirectoryClassName=#Java directory and class name +tools.recover=#Recover +tools.recover.help=#Helps recovering a corrupted database. +tools.restore=#Restore +tools.restore.help=Restaura um backup de banco de dados. +tools.result=#Result +tools.run=Executar comando +tools.runScript=#RunScript +tools.runScript.help=#Runs a SQL script. +tools.script=#Script +tools.script.help=#Allows to convert a database to a SQL script for backup or migration. +tools.scriptFileName=#Script file name +tools.serverList=#Server list +tools.sourceDatabaseName=#Source database name +tools.sourceDatabaseURL=#Source database URL +tools.sourceDirectory=#Source directory +tools.sourceFileName=#Source file name +tools.sourceScriptFileName=#Source script file name +tools.targetDatabaseName=#Target database name +tools.targetDatabaseURL=#Target database URL +tools.targetDirectory=#Target directory +tools.targetFileName=Nome do arquivo de destino +tools.targetScriptFileName=Nome do arquivo de script de destino +tools.traceFileName=#Trace file name +tree.admin=Administrador +tree.current=Valor corrente +tree.hashed=Hashed +tree.increment=Incrementar +tree.indexes=Índices +tree.nonUnique=Não único +tree.sequences=Sequências +tree.unique=Único +tree.users=Usuários diff --git a/h2/src/main/org/h2/server/web/res/_text_pt_pt.prop b/h2/src/main/org/h2/server/web/res/_text_pt_pt.prop new file mode 100644 index 0000000..3323f3b --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/_text_pt_pt.prop @@ -0,0 +1,164 @@ +.translator=Antonio Casqueiro +a.help=Ajuda +a.language=Português (Europeu) +a.lynxNotSupported=Lynx ainda não é suportado +a.password=Senha +a.remoteConnectionsDisabled=Conexões remotas ('webAllowOthers') encontram-se desactivadas neste servidor. +a.title=H2 Consola +a.tools=#Tools +a.user=Nome Utilizador +admin.executing=A executar +admin.ip=IP +admin.lastAccess=Último Acesso +admin.lastQuery=Última Query +admin.no=#no +admin.notConnected=#not connected +admin.url=URL +admin.yes=#yes +adminAllow=Clientes com acesso +adminConnection=Nível de segurança da conexão +adminHttp=Usar conexões HTTP não seguras (os dados não são cifrados) +adminHttps=Usar conexões SSL (HTTPS) seguras (os dados são cifrados) +adminLocal=Apenas permitir conexões a partir do computador local +adminLogin=Entrar como administrador +adminLoginCancel=Cancelar +adminLoginOk=Confirmar +adminLogout=Sair +adminOthers=Permitir conexões a partir de outro computador na rede +adminWebExternalNames=#External names or addresses of this server (comma-separated) +adminPort=Número do porto +adminPortWeb=Número do porto do servidor +adminRestart=As alterações apenas serão aplicadas após reiniciar o servidor. +adminSave=Gravar +adminSessions=Sessões activas +adminShutdown=Encerrar +adminTitle=Preferencias da Consola H2 +adminTranslateHelp=#Translate or improve the translation of the H2 Console. +adminTranslateStart=#Translate +helpAction=Acção +helpAddAnotherRow=Adicionar outra linha +helpAddDrivers=Adicionar drivers de Base de Dados +helpAddDriversText=É possível registar outros drivers, adicionando o ficheiro JAR respectivo, à variável de ambiente H2DRIVERS ou CLASSPATH. Exemplo (Windows): Para adicionar o driver que se encontra em C:/Programs/hsqldb/lib/hsqldb.jar, alterar o valor da variável de ambiente H2DRIVERS para C:/Programs/hsqldb/lib/hsqldb.jar. +helpAddRow=Adicionar uma linha nova +helpCommandHistory=Mostrar o histórico de comandos +helpCreateTable=Criar uma tabela nova +helpDeleteRow=Apagar uma linha +helpDisconnect=Fechar conexão à Base de Dados +helpDisplayThis=Mostrar esta página de ajuda +helpDropTable=Apagar a tabela, caso ela exista +helpExecuteCurrent=Executar o comando SQL corrente +helpExecuteSelected=#Executes the SQL statement defined by the text selection +helpIcon=Simbolo +helpImportantCommands=Comandos importantes +helpOperations=Operações= +helpQuery=Pesquisar uma tabela +helpSampleSQL=Scripts de exemplo +helpStatements=Comandos SQL +helpUpdate=Modificar os dados de uma linha +helpWithColumnsIdName=com as colunas ID e NAME +key.alt=#Alt +key.ctrl=#Ctrl +key.enter=#Enter +key.shift=#Shift +key.space=#Space +login.connect=Estabelecer conexão +login.driverClass=Classe com o driver +login.driverNotFound=O driver não foi encontrado
Ver na secção de ajuda, como adicionar drivers +login.goAdmin=Preferencias +login.jdbcUrl=JDBC URL +login.language=Lingua +login.login=Login +login.remove=Remover +login.save=Gravar +login.savedSetting=Configuração activa +login.settingName=Nome da configuração +login.testConnection=Testar conexão +login.testSuccessful=Teste bem sucedido +login.welcome=Consola H2 +result.1row=1 linha +result.autoCommitOff=O auto commit passou a estar desligado +result.autoCommitOn=O auto commit passou a estar ligado +result.bytes=#bytes +result.characters=#characters +result.maxrowsSet=O número máximo de linhas foi alterado +result.noRows=sem linhas +result.noRunningStatement=Actualmente não existe nenhum comando em execução +result.rows=linhas +result.statementWasCanceled=O comando foi cancelado +result.updateCount=Número de registos actualizados +resultEdit.action=Acção +resultEdit.add=Adicionar +resultEdit.cancel=Cancelar +resultEdit.delete=Apagar +resultEdit.edit=Alterar +resultEdit.editResult=Alterar +resultEdit.save=Gravar +toolbar.all=Todas +toolbar.autoCommit=Auto commit +toolbar.autoComplete=Auto complete +toolbar.autoComplete.full=Total +toolbar.autoComplete.normal=Normal +toolbar.autoComplete.off=Desligado +toolbar.autoSelect=#Auto select +toolbar.autoSelect.off=Desligado +toolbar.autoSelect.on=#On +toolbar.cancelStatement=Cancelar o comando que se encontra em execução +toolbar.clear=Limpar +toolbar.commit=Commit +toolbar.disconnect=Desligar +toolbar.history=Histórico de comandos executados +toolbar.maxRows=Número máximo de linhas +toolbar.refresh=Actualizar +toolbar.rollback=Rollback +toolbar.run=Executar comando +toolbar.runSelected=#Run Selected +toolbar.sqlStatement=Comando SQL +tools.backup=#Backup +tools.backup.help=#Creates a backup of a database. +tools.changeFileEncryption=#ChangeFileEncryption +tools.changeFileEncryption.help=#Allows changing the database file encryption password and algorithm. +tools.cipher=#Cipher (AES or XTEA) +tools.commandLine=#Command line +tools.convertTraceFile=#ConvertTraceFile +tools.convertTraceFile.help=#Converts a .trace.db file to a Java application and SQL script. +tools.createCluster=#CreateCluster +tools.createCluster.help=#Creates a cluster from a standalone database. +tools.databaseName=#Database name +tools.decryptionPassword=#Decryption password +tools.deleteDbFiles=#DeleteDbFiles +tools.deleteDbFiles.help=#Deletes all files belonging to a database. +tools.directory=#Directory +tools.encryptionPassword=#Encryption password +tools.javaDirectoryClassName=#Java directory and class name +tools.recover=#Recover +tools.recover.help=#Helps recovering a corrupted database. +tools.restore=#Restore +tools.restore.help=#Restores a database backup. +tools.result=#Result +tools.run=Executar comando +tools.runScript=#RunScript +tools.runScript.help=#Runs a SQL script. +tools.script=#Script +tools.script.help=#Allows to convert a database to a SQL script for backup or migration. +tools.scriptFileName=#Script file name +tools.serverList=#Server list +tools.sourceDatabaseName=#Source database name +tools.sourceDatabaseURL=#Source database URL +tools.sourceDirectory=#Source directory +tools.sourceFileName=#Source file name +tools.sourceScriptFileName=#Source script file name +tools.targetDatabaseName=#Target database name +tools.targetDatabaseURL=#Target database URL +tools.targetDirectory=#Target directory +tools.targetFileName=#Target file name +tools.targetScriptFileName=#Target script file name +tools.traceFileName=#Trace file name +tree.admin=Administrador +tree.current=Valor corrente +tree.hashed=Hashed +tree.increment=Incrementar +tree.indexes=Índices +tree.nonUnique=Não único +tree.sequences=Sequências +tree.unique=Único +tree.users=Utilizadoreses diff --git a/h2/src/main/org/h2/server/web/res/_text_ru.prop b/h2/src/main/org/h2/server/web/res/_text_ru.prop new file mode 100644 index 0000000..4f23c8a --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/_text_ru.prop @@ -0,0 +1,164 @@ +.translator=Vlad Alexahin +a.help=Помощь +a.language=Русский +a.lynxNotSupported=Извините, Lynx пока не поддерживается +a.password=Пароль +a.remoteConnectionsDisabled=Извините, удалённые подключения ('webAllowOthers') запрещены на этом сервере. +a.title=H2 Console +a.tools=Инструменты +a.user=Имя пользователя +admin.executing=Выполняется +admin.ip=IP +admin.lastAccess=Последний доступ +admin.lastQuery=Последний запрос +admin.no=нет +admin.notConnected=нет соединения +admin.url=URL +admin.yes=да +adminAllow=Разрешённые клиенты +adminConnection=Безопасность подключения +adminHttp=Используйте незашифрованные HTTP-соединения +adminHttps=Используйте SSL (HTTPS) соединения +adminLocal=Разрешены только локальные подключения +adminLogin=Административный вход +adminLoginCancel=Отменить +adminLoginOk=OK +adminLogout=Выход +adminOthers=Разрешить удаленные подключения +adminWebExternalNames=Внешние имена или адреса этого сервера (через запятую) +adminPort=Номер порта +adminPortWeb=Порт web-сервера +adminRestart=Изменения вступят в силу после перезагрузки сервера. +adminSave=Сохранить +adminSessions=Активные сессии +adminShutdown=Выключить +adminTitle=Настройки консоли H2 +adminTranslateHelp=Перевести или улучшить перевод консоли H2 +adminTranslateStart=Перевести +helpAction=Действие +helpAddAnotherRow=Добавить строку +helpAddDrivers=Добавляем драйвер базы данных +helpAddDriversText=Дополнительные драйверы базы данных могут быть зарегистрированы добавлением соответствующих Jar-файлов в переменную среды H2DRIVERS или в CLASSPATH. Пример (Windows): Чтобы добавить библиотеку драйвера базы данных C:/Programs/hsqldb/lib/hsqldb.jar, установите в переменную среды H2DRIVERS значение C:/Programs/hsqldb/lib/hsqldb.jar. +helpAddRow=Добавить новую строку +helpCommandHistory=Показывает историю выполненных команд +helpCreateTable=Создать новую таблицу +helpDeleteRow=Удалить строку +helpDisconnect=Отключиться от базы данных +helpDisplayThis=Показывает это окно помощи +helpDropTable=Удаляет таблицу, если она уже существует +helpExecuteCurrent=Выполнить текущий SQL-запрос +helpExecuteSelected=Выполнить SQL-запрос, выделенный в тексте +helpIcon=Иконка +helpImportantCommands=Важные команды +helpOperations=Операции +helpQuery=Запрос к таблице +helpSampleSQL=Примеры скриптов SQL +helpStatements=SQL-запрос +helpUpdate=Изменить данные в строке +helpWithColumnsIdName=с колонками ID и NAME +key.alt=#Alt +key.ctrl=#Ctrl +key.enter=#Enter +key.shift=#Shift +key.space=#Space +login.connect=Соединиться +login.driverClass=Класс драйвера +login.driverNotFound=Драйвер базы данных не найден
Посмотрите в Помощи, как добавить драйвер базы данных +login.goAdmin=Настройки +login.jdbcUrl=JDBC URL +login.language=Язык +login.login=Логин +login.remove=Удалить +login.save=Сохранить +login.savedSetting=Сохранить настройки +login.settingName=Имя настройки +login.testConnection=Тестовое соединение +login.testSuccessful=Тест прошел успешно +login.welcome=H2 Console +result.1row=1 строка +result.autoCommitOff=Авто-выполнение сейчас ВЫКЛЮЧЕНО +result.autoCommitOn=Авто-выполнение сейчас ВКЛЮЧЕНО +result.bytes=байт +result.characters=символов +result.maxrowsSet=Установлено максимальное количество строк +result.noRows=нет строк +result.noRunningStatement=Сейчас нет выполняемых запросов +result.rows=строки +result.statementWasCanceled=Запрос был отменен +result.updateCount=Обновить количество +resultEdit.action=Действие +resultEdit.add=Добавить +resultEdit.cancel=Отменить +resultEdit.delete=Удалить +resultEdit.edit=Править +resultEdit.editResult=Править +resultEdit.save=Сохранить +toolbar.all=Все +toolbar.autoCommit=Авто-выполнение +toolbar.autoComplete=Авто-завершение +toolbar.autoComplete.full=Все +toolbar.autoComplete.normal=Нормальные +toolbar.autoComplete.off=Выключено +toolbar.autoSelect=Автовыбор +toolbar.autoSelect.off=Выключено +toolbar.autoSelect.on=Включено +toolbar.cancelStatement=Отменить текущий запрос +toolbar.clear=Очистить +toolbar.commit=Зафиксировать транзакцию +toolbar.disconnect=Отсоединиться +toolbar.history=История команд +toolbar.maxRows=Максимальное количество строк +toolbar.refresh=Обновить +toolbar.rollback=Откатить транзакцию +toolbar.run=Выполнить +toolbar.runSelected=Выполнить выделенное +toolbar.sqlStatement=SQL-запрос +tools.backup=Резервное копирование +tools.backup.help=Создает резервную копию базы данных. +tools.changeFileEncryption=Изменить шифрование файла +tools.changeFileEncryption.help=Позволяет изменить алгоритм шифрования файлов базы данных и пароль. +tools.cipher=Алгоритм (AES или XTEA) +tools.commandLine=Командная строка +tools.convertTraceFile=Преобразовать trace-файл +tools.convertTraceFile.help=Преобразует .trace.db файл в приложение Java и скрипт SQL. +tools.createCluster=Создать кластер +tools.createCluster.help=Создание кластера из автономной базы данных. +tools.databaseName=Имя базы данных +tools.decryptionPassword=Пароль дешифровки +tools.deleteDbFiles=Удалить файлы БД +tools.deleteDbFiles.help=Удаляет все файлы, относящиеся к базе данных. +tools.directory=Каталог +tools.encryptionPassword=Пароль шифровки +tools.javaDirectoryClassName=Каталог и имя класса +tools.recover=Восстановление +tools.recover.help=Восстановление поврежденной базы данных. +tools.restore=Восстановить +tools.restore.help=Восстановление из резервной копии базы данных. +tools.result=Результат +tools.run=Выполнить +tools.runScript=Выполнить скрипт +tools.runScript.help=Выполнение скрипта SQL. +tools.script=Скрипт +tools.script.help=Позволяет преобразовать базу данных в скрипт SQL для резервного копирования или миграции. +tools.scriptFileName=Имя файла скрипта SQL +tools.serverList=Список серверов +tools.sourceDatabaseName=Имя базы данных источника +tools.sourceDatabaseURL=URL базы данных источника +tools.sourceDirectory=Каталог источника +tools.sourceFileName=Имя файла источника +tools.sourceScriptFileName=Имя файла скрипта источника +tools.targetDatabaseName=Имя базы данных назначения +tools.targetDatabaseURL=URL базы данных назначения +tools.targetDirectory=Каталог назначения +tools.targetFileName=Имя файла назначения +tools.targetScriptFileName=Имя файла скрипта назначения +tools.traceFileName=Имя trace-файла +tree.admin=Администратор +tree.current=Текущее значение +tree.hashed=Hashed +tree.increment=Приращение +tree.indexes=Индексы +tree.nonUnique=Неуникальное +tree.sequences=Последовательности +tree.unique=Уникальное +tree.users=Пользователи diff --git a/h2/src/main/org/h2/server/web/res/_text_sk.prop b/h2/src/main/org/h2/server/web/res/_text_sk.prop new file mode 100644 index 0000000..a4f11db --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/_text_sk.prop @@ -0,0 +1,164 @@ +.translator=Ľubomír Grajciar +a.help=Pomocník +a.language=Slovensky +a.lynxNotSupported=Prepáčte, Lynx zatiaľ nie je podporovaný +a.password=Heslo +a.remoteConnectionsDisabled=Prepáčte, vzdialené pripojenia ('webAllowOthers') sú pre tento server zakázané. +a.title=H2 konzola +a.tools=Nástroje +a.user=Meno používateľa +admin.executing=Vykonávanie +admin.ip=IP +admin.lastAccess=Posledný prístup +admin.lastQuery=Posledný príkaz +admin.no=#no +admin.notConnected=#not connected +admin.url=URL +admin.yes=#yes +adminAllow=Povolenia klientov +adminConnection=Bezpečnosť pripojenia +adminHttp=Použiť nešifrované HTTP pripojenia +adminHttps=Použiť šifrované SSL (HTTPS) pripojenia +adminLocal=Povoliť iba lokálne pripojenia +adminLogin=Prihlásenie Správcu +adminLoginCancel=Zrušiť +adminLoginOk=OK +adminLogout=Odhlásiť +adminOthers=Povoliť pripojenia z iných počítačov +adminWebExternalNames=#External names or addresses of this server (comma-separated) +adminPort=Číslo portu +adminPortWeb=Číslo portu Web servera +adminRestart=Zmeny sa vykonajú po reštarte servera +adminSave=Uložiť +adminSessions=Aktívne pripojenia +adminShutdown=Ukončiť H2 server +adminTitle=Nastavenia H2 konzoly +adminTranslateHelp=Preložiť alebo zlepšiť preklad H2 konzoly. +adminTranslateStart=Preložiť +helpAction=Akcia +helpAddAnotherRow=Pridať ďalší riadok +helpAddDrivers=Pridanie databázových ovládačov +helpAddDriversText=Ďalšie databázové ovládače môžu byť zaregistrované pridaním mena jar súboru ovládača aj s cestou do premennej prostredia H2DRIVERS alebo CLASSPATH. Napríklad (Windows): na pridanie knižnice databázového ovládača C:/Programs/hsqldb/lib/hsqldb.jar, nastavte premennú prostredia H2DRIVERS na C:/Programs/hsqldb/lib/hsqldb.jar. +helpAddRow=Pridať nový riadok +helpCommandHistory=Ukáž históriu príkazov +helpCreateTable=Vytvoriť novú tabuľku +helpDeleteRow=Odstrániť riadok +helpDisconnect=Odpojiť sa od databázy +helpDisplayThis=Zobraziť túto stránku pomocníka +helpDropTable=Zmazať tabuľku pokiaľ existuje +helpExecuteCurrent=Vykonať aktuálny SQL príkaz +helpExecuteSelected=#Executes the SQL statement defined by the text selection +helpIcon=Ikona +helpImportantCommands=Dôležité príkazy +helpOperations=Operácie +helpQuery=Vypýtať dáta z tabuľky +helpSampleSQL=Príklad SQL skriptu +helpStatements=SQL príkazy +helpUpdate=Zmeniť dáta v riadku +helpWithColumnsIdName=so stĺpcami ID a NAME +key.alt=#Alt +key.ctrl=#Ctrl +key.enter=#Enter +key.shift=#Shift +key.space=#Space +login.connect=Pripojiť +login.driverClass=Trieda ovládača +login.driverNotFound=Databázový ovládač nebol nájdený
Použite pomocníka na zistenie, ako pridať databázový ovládač +login.goAdmin=Nastavenia +login.jdbcUrl=JDBC URL +login.language=Jazyk +login.login=Prihlásenie +login.remove=Odstrániť +login.save=Uložiť +login.savedSetting=Uložené nastavenia +login.settingName=Meno nastavenia +login.testConnection=Test pripojenia +login.testSuccessful=Test úspešný +login.welcome=H2 konzola +result.1row=1 riadok +result.autoCommitOff=Automatický commit je teraz VYPNUTÝ +result.autoCommitOn=Automatický commit je teraz ZAPNUTÝ +result.bytes=bajtov +result.characters=znakov +result.maxrowsSet=Maximalny počet riadkov v skupine +result.noRows=žiadne riadky +result.noRunningStatement=Momentálne sa nevykonáva žiadny príkaz +result.rows=riadky/ov +result.statementWasCanceled=Príkaz bol zrušený +result.updateCount=Počet aktualizácii +resultEdit.action=Akcia +resultEdit.add=Pridať +resultEdit.cancel=Zrušiť +resultEdit.delete=Zmazať +resultEdit.edit=Upraviť +resultEdit.editResult=Upraviť +resultEdit.save=Uložiť +toolbar.all=Všetko +toolbar.autoCommit=Auto commit +toolbar.autoComplete=Auto dokončovanie +toolbar.autoComplete.full=Plné +toolbar.autoComplete.normal=Normálne +toolbar.autoComplete.off=Vypnuté +toolbar.autoSelect=#Auto select +toolbar.autoSelect.off=Vypnuté +toolbar.autoSelect.on=#On +toolbar.cancelStatement=Zrušiť aktuálny príkaz +toolbar.clear=Vyčistiť +toolbar.commit=Commit (schváliť) +toolbar.disconnect=Odpojiť +toolbar.history=História príkazov +toolbar.maxRows=Max. riadkov +toolbar.refresh=Obnoviť +toolbar.rollback=Rollback (odvolať) +toolbar.run=Spustiť +toolbar.runSelected=#Run Selected +toolbar.sqlStatement=SQL príkaz +tools.backup=Zálohovať +tools.backup.help=Vytvorenie zálohy databázy +tools.changeFileEncryption=ZmeniťŠifrovanieSúboru +tools.changeFileEncryption.help=Umožní zmeniť heslo a algoritmus šifrovania databázového súboru. +tools.cipher=Šifra (AES alebo XTEA) +tools.commandLine=Príkazový riadok +tools.convertTraceFile=PrekonvertovaťTraceSúbor +tools.convertTraceFile.help=Prekonvertuje .trace.db súbor na Java program a SQL skript. +tools.createCluster=VytvoriťKluster +tools.createCluster.help=Vytvoriť kluster zo samostatnej databázy. +tools.databaseName=Meno databázy +tools.decryptionPassword=Heslo na dešifrovanie +tools.deleteDbFiles=ZmazaťDbSúbory +tools.deleteDbFiles.help=Vymaže všetky súbory databázy +tools.directory=Priečinok +tools.encryptionPassword=Heslo na šifrovanie +tools.javaDirectoryClassName=Java priečinok a meno triedy +tools.recover=Opraviť +tools.recover.help=Umožní opraviť poškodenú databázu. +tools.restore=Obnoviť +tools.restore.help=Obnoviť databázu zo zálohy. +tools.result=Výsledok +tools.run=Spustiť +tools.runScript=SpustiťSkript +tools.runScript.help=Spustí SQL skript. +tools.script=Skript +tools.script.help=Umožní vytvoriť z databázy SQL skript na zálohu alebo prenesenie databázy. +tools.scriptFileName=Meno súboru skriptu +tools.serverList=Zoznam serverov +tools.sourceDatabaseName=Meno zdrojovej databázy +tools.sourceDatabaseURL=URL zdrojovej databázy +tools.sourceDirectory=Zdrojový priečinok +tools.sourceFileName=Meno zdrojového súboru +tools.sourceScriptFileName=Meno súboru zdrojového skriptu +tools.targetDatabaseName=Meno cieľovej databázy +tools.targetDatabaseURL=URL cieľovej databázy +tools.targetDirectory=Cieľový priečinok +tools.targetFileName=Meno cieľového súboru +tools.targetScriptFileName=Meno súboru cieľového skriptu +tools.traceFileName=Meno trace súboru +tree.admin=Admin +tree.current=Aktuálna hodnota +tree.hashed=Hashed (s kontrolným súčtom) +tree.increment=Inkrement +tree.indexes=Indexy +tree.nonUnique=Non unique (nie je jedinečný) +tree.sequences=Sekvencie +tree.unique=Unique (jedinečný) +tree.users=Používatelia diff --git a/h2/src/main/org/h2/server/web/res/_text_tr.prop b/h2/src/main/org/h2/server/web/res/_text_tr.prop new file mode 100644 index 0000000..80aed9f --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/_text_tr.prop @@ -0,0 +1,164 @@ +.translator=Rıdvan Ağar +a.help=Yardım +a.language=Türkçe +a.lynxNotSupported=Web tarayıcınız HTML Frames'i desteklemiyor. Frames (ve Javascript) desteği gerekli. +a.password=Şifre +a.remoteConnectionsDisabled=Başka bilgisayarlardan, veri tabanına bağlanma izni henüz ayarlanmamış ('webAllowOthers'). +a.title=H2 Konsolu +a.tools=Araçlar +a.user=Kullanıcı adı +admin.executing=Aktif +admin.ip=IP +admin.lastAccess=Son bağlantı +admin.lastQuery=Son komut +admin.no=#no +admin.notConnected=#not connected +admin.url=URL +admin.yes=#yes +adminAllow=İzin verilen bağlantılar +adminConnection=Bağlantı güvenliği +adminHttp=Şifrelenmemiş HTTP bağlantıları +adminHttps=Şifrelenmiş HTTP bağlantıları +adminLocal=Sadece yerel bağlantılara izin ver +adminLogin=Yönetim girişi +adminLoginCancel=İptal et +adminLoginOk=Tamam +adminLogout=Bitir +adminOthers=Başka bilgisayarlardan, veri tabanına bağlanma izni ver +adminWebExternalNames=#External names or addresses of this server (comma-separated) +adminPort=Port +adminPortWeb=Web-Server Port +adminRestart=Değişiklikler veri tabanı hizmetçisinin yeniden başlatılmasıyla etkinlik kazanacak. +adminSave=Kaydet +adminSessions=Aktif bağlantılar +adminShutdown=Kapat +adminTitle=H2 Konsol ayarları +adminTranslateHelp=H2 Kullanıcı arayüzünü (H2 Konsol) dilinize çevirin yada çeviriyi düzeltin. +adminTranslateStart=Çeviri +helpAction=Aksiyon +helpAddAnotherRow=Yeni bir satır ekle +helpAddDrivers=Veritabanı sürücüsü ekle +helpAddDriversText=Yeni veri tabanı sürücüleri eklemek için, sürücü dosyalarının yerini H2DRIVERS yada CLASSPATH çevre değişkenlerine ekleyebilirsiniz. Örnek (Windows): Sürücü dosyası C:/Programs/hsqldb/lib/hsqldb.jar ise H2DRIVERS değişkenini C:/Programs/hsqldb/lib/hsqldb.jar olarak girin. +helpAddRow=Veri tabanına yeni bir satır ekler +helpCommandHistory=Komut tarihçesini gösterir +helpCreateTable=Veri tabanına yeni bir tabela ekler +helpDeleteRow=Tabeladan satırı siler +helpDisconnect=Veri tabanı bağlantısını keser +helpDisplayThis=Bu yardım sayfasını gösterir +helpDropTable=Var ise, istenen tabelayı siler +helpExecuteCurrent=Girilen SQL komutunu icra eder +helpExecuteSelected=#Executes the SQL statement defined by the text selection +helpIcon=Şalter +helpImportantCommands=Önemli komutlar +helpOperations=İşlemler +helpQuery=Tabela içeriğini gösterir +helpSampleSQL=Örnek SQL +helpStatements=SQL komutları +helpUpdate=Bir tabeladaki belli bir satır içeriğini değiştirir +helpWithColumnsIdName=Colon isimleriyle birlikte +key.alt=#Alt +key.ctrl=#Ctrl +key.enter=#Enter +key.shift=#Shift +key.space=#Space +login.connect=Bağlan +login.driverClass=Veri tabanı sürücü sınıfı +login.driverNotFound=İstenilen veri tabanı sürücüsü bulunamadı
Sürücü ekleme konusunda bilgi için Yardım'a başvurunuz +login.goAdmin=Seçenekler +login.jdbcUrl=JDBC URL +login.language=Dil +login.login=Giriş +login.remove=Sil +login.save=Kaydet +login.savedSetting=Kayıtlı ayarlar +login.settingName=Ayar adı +login.testConnection=Bağlantıyı test et +login.testSuccessful=Test başarılı +login.welcome=H2 Konsolu +result.1row=1 dizi +result.autoCommitOff=Auto-Commit kapatıldı +result.autoCommitOn=Auto-Commit açıldı +result.bytes=#bytes +result.characters=#characters +result.maxrowsSet=Maximum dizi sayısı ayarı yapıldı +result.noRows=Hiç bir bilgi yok +result.noRunningStatement=Şu an bir komut icra ediliyor +result.rows=Dizi +result.statementWasCanceled=Komut iptal edildi +result.updateCount=Güncelleşterilen dizi sayısı +resultEdit.action=Aksiyon +resultEdit.add=Ekle +resultEdit.cancel=İptal +resultEdit.delete=Sil +resultEdit.edit=Değiştir +resultEdit.editResult=Değiştir +resultEdit.save=Kaydet +toolbar.all=Hepsi +toolbar.autoCommit=Auto-Commit +toolbar.autoComplete=Auto-Complete +toolbar.autoComplete.full=Hepsi +toolbar.autoComplete.normal=Normal +toolbar.autoComplete.off=Kapalı +toolbar.autoSelect=#Auto select +toolbar.autoSelect.off=Kapalı +toolbar.autoSelect.on=#On +toolbar.cancelStatement=Yürütülen işlemi iptal et +toolbar.clear=Temizle +toolbar.commit=Degişiklikleri kaydet +toolbar.disconnect=Bağlantıyı kes +toolbar.history=Verilmiş olan komutlar +toolbar.maxRows=Maximum dizi sayısı +toolbar.refresh=Güncelleştir +toolbar.rollback=Değişiklikleri geri al +toolbar.run=İşlemi yürüt +toolbar.runSelected=#Run Selected +toolbar.sqlStatement=SQL komutu +tools.backup=Yedekle +tools.backup.help=Bir veritabanının yedeklemesini yapar. +tools.changeFileEncryption=DosyaKodla +tools.changeFileEncryption.help=Veritabanının dosya kodlama şifresi ve türünü belirler. +tools.cipher=Şifreleme türü (AES yada XTEA) +tools.commandLine=Komut +tools.convertTraceFile=TraceDosyasiDönüştür +tools.convertTraceFile.help=Verilen bir trace.db dosyasını Java uygulamasına ve SQL-Betiğe çevirir. +tools.createCluster=KümeYarat +tools.createCluster.help=Bağımsız bir veritabanından bir küme (Cluster) yaratır. +tools.databaseName=Veritabanının adı +tools.decryptionPassword=Kod çözme şifresi +tools.deleteDbFiles=VeritabanıDosyalarınıSil +tools.deleteDbFiles.help=Bir veritabanına ait bütün dosyaları siler. +tools.directory=Dizelge +tools.encryptionPassword=Kodlama şifresi +tools.javaDirectoryClassName=Java dizelge ve sınıf adı +tools.recover=Kurtar +tools.recover.help=Bozuk bir veritabanının kurtarılmasına yardımcı olur. +tools.restore=YenidenY???#252kle +tools.restore.help=Bir veritabanının yedeklemesini yeniden yükler. +tools.result=Sonuç +tools.run=İşlemi yürüt +tools.runScript=BetikÇalıştır +tools.runScript.help=Bir betik dosyası çalıştırır. +tools.script=Betik +tools.script.help=Bir veritabanının yedekleme yada taşıma amaçlı SQL-Betiğe çevrilmesini sağlar +tools.scriptFileName=Betik dosya adı +tools.serverList=Hizmetçi listesi +tools.sourceDatabaseName=Kaynak veritabanının adı +tools.sourceDatabaseURL=Kaynak veritabanının URL'u +tools.sourceDirectory=Kaynak dizelge +tools.sourceFileName=Kaynak dosya adı +tools.sourceScriptFileName=Kaynak betik dosya adı +tools.targetDatabaseName=Hedef veritabanının adı +tools.targetDatabaseURL=Hedef veritabanının URL'u +tools.targetDirectory=Hedef dizelge +tools.targetFileName=Hedef dosya adı +tools.targetScriptFileName=Hedef betik dosya adı +tools.traceFileName=Trace dosya adı +tree.admin=Yönetici +tree.current=Güncel değer +tree.hashed=Hash tabanlı +tree.increment=Artır +tree.indexes=Indexler +tree.nonUnique=eşsiz değil +tree.sequences=Dizinler +tree.unique=Eşsiz +tree.users=Kullanıcı diff --git a/h2/src/main/org/h2/server/web/res/_text_uk.prop b/h2/src/main/org/h2/server/web/res/_text_uk.prop new file mode 100644 index 0000000..3c71e5d --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/_text_uk.prop @@ -0,0 +1,164 @@ +.translator=Igor Dobrovolskyi +a.help=Допомога +a.language=Українська +a.lynxNotSupported=Вибачте, але Lynx не підтримується +a.password=Пароль +a.remoteConnectionsDisabled=Вибачте, віддалені підключення ('webAllowOthers') на цьому сервері заборонені. +a.title=Консоль H2 +a.tools=#Tools +a.user=Iм'я користувача +admin.executing=Виконується +admin.ip=IP +admin.lastAccess=Останній доступ +admin.lastQuery=Останній запит +admin.no=#no +admin.notConnected=#not connected +admin.url=URL +admin.yes=#yes +adminAllow=Дозволені клієнти +adminConnection=Безпека під'єднання +adminHttp=Використовуйте незашифровані HTTP під'єднання +adminHttps=Використовуйте зашифровані SSL (HTTPS) під'єднання +adminLocal=Дозволено лише локальні під'єднання +adminLogin=Адміністративний логін +adminLoginCancel=Відмінити +adminLoginOk=OK +adminLogout=Завершення сеансу +adminOthers=Дозволити під'єднання з інших копм'ютерів +adminWebExternalNames=#External names or addresses of this server (comma-separated) +adminPort=Номер порта +adminPortWeb=Номер порта веб сервера +adminRestart=Зміни вступлять в силу після перезавантаження сервера. +adminSave=Зберегти +adminSessions=Активні сесії +adminShutdown=Виключити +adminTitle=Настройки консолі H2 +adminTranslateHelp=#Translate or improve the translation of the H2 Console. +adminTranslateStart=#Translate +helpAction=Дія +helpAddAnotherRow=Додати новий рядок +helpAddDrivers=Додати драйвер бази даних +helpAddDriversText=Нові драйвери баз даних можуть бути зареєстровані додаванням шляху до Jar-файлу з драйвером до змінної оточення H2DRIVERS або CLASSPATH. Наприклад (Windows): Щоб додати драйвер бази даних C:/Programs/hsqldb/lib/hsqldb.jar, встановіть змінну оточення H2DRIVERS рівною C:/Programs/hsqldb/lib/hsqldb.jar. +helpAddRow=Додати новий рядок +helpCommandHistory=Показує історію команд +helpCreateTable=Створити нову таблицю +helpDeleteRow=Видалити рядок +helpDisconnect=Від'єднує від бази даних +helpDisplayThis=Показує цю сторінку допомоги +helpDropTable=Видалити таблицю, якщо вона існує +helpExecuteCurrent=Виконує поточний SQL запит +helpExecuteSelected=#Executes the SQL statement defined by the text selection +helpIcon=Iконка +helpImportantCommands=Важливі команди +helpOperations=Операції +helpQuery=Запит до таблиці +helpSampleSQL=Приклад SQL запиту +helpStatements=SQL запити +helpUpdate=Змінити дані в рядку +helpWithColumnsIdName=з колонками ID і NAME +key.alt=#Alt +key.ctrl=#Ctrl +key.enter=#Enter +key.shift=#Shift +key.space=#Space +login.connect=Під'єднатись +login.driverClass=Driver Class +login.driverNotFound=Дравер бази даних не знайдено
Подивіться в допомозі як додати нові драйвери +login.goAdmin=Настройки +login.jdbcUrl=JDBC URL +login.language=Мова +login.login=Логін +login.remove=Видалити +login.save=Зберегти +login.savedSetting=Збережені налаштування +login.settingName=Iм'я налаштування +login.testConnection=Тестове під'єднання +login.testSuccessful=Тест пройдено успішно +login.welcome=Консоль H2 +result.1row=1 рядок +result.autoCommitOff=Автозбереження виключене +result.autoCommitOn=Автозбереження включене +result.bytes=#bytes +result.characters=#characters +result.maxrowsSet=Встановлено максимальну кількість рядків +result.noRows=немає рядків +result.noRunningStatement=В даний момент не виконується жоден запит +result.rows=рядків +result.statementWasCanceled=Запит було відмінено +result.updateCount=Кількість змінених +resultEdit.action=Дія +resultEdit.add=Додати +resultEdit.cancel=Відмінити +resultEdit.delete=Видалити +resultEdit.edit=Редагувати +resultEdit.editResult=Редагувати +resultEdit.save=Зберегти +toolbar.all=Всі +toolbar.autoCommit=Автозбереження +toolbar.autoComplete=Авто доповнення +toolbar.autoComplete.full=Повне +toolbar.autoComplete.normal=Нормальне +toolbar.autoComplete.off=Виключене +toolbar.autoSelect=#Auto select +toolbar.autoSelect.off=Виключене +toolbar.autoSelect.on=#On +toolbar.cancelStatement=Відмінити поточний запит +toolbar.clear=Очистити +toolbar.commit=Підтвердити зміни +toolbar.disconnect=Від'єднатись +toolbar.history=Iсторія команд +toolbar.maxRows=Максимальна кількість рядків +toolbar.refresh=Оновити +toolbar.rollback=Вернути назад +toolbar.run=Виконати +toolbar.runSelected=#Run Selected +toolbar.sqlStatement=SQL запит +tools.backup=#Backup +tools.backup.help=#Creates a backup of a database. +tools.changeFileEncryption=#ChangeFileEncryption +tools.changeFileEncryption.help=#Allows changing the database file encryption password and algorithm. +tools.cipher=#Cipher (AES or XTEA) +tools.commandLine=#Command line +tools.convertTraceFile=#ConvertTraceFile +tools.convertTraceFile.help=#Converts a .trace.db file to a Java application and SQL script. +tools.createCluster=#CreateCluster +tools.createCluster.help=#Creates a cluster from a standalone database. +tools.databaseName=#Database name +tools.decryptionPassword=#Decryption password +tools.deleteDbFiles=#DeleteDbFiles +tools.deleteDbFiles.help=#Deletes all files belonging to a database. +tools.directory=#Directory +tools.encryptionPassword=#Encryption password +tools.javaDirectoryClassName=#Java directory and class name +tools.recover=#Recover +tools.recover.help=#Helps recovering a corrupted database. +tools.restore=#Restore +tools.restore.help=#Restores a database backup. +tools.result=#Result +tools.run=Виконати +tools.runScript=#RunScript +tools.runScript.help=#Runs a SQL script. +tools.script=#Script +tools.script.help=#Allows to convert a database to a SQL script for backup or migration. +tools.scriptFileName=#Script file name +tools.serverList=#Server list +tools.sourceDatabaseName=#Source database name +tools.sourceDatabaseURL=#Source database URL +tools.sourceDirectory=#Source directory +tools.sourceFileName=#Source file name +tools.sourceScriptFileName=#Source script file name +tools.targetDatabaseName=#Target database name +tools.targetDatabaseURL=#Target database URL +tools.targetDirectory=#Target directory +tools.targetFileName=#Target file name +tools.targetScriptFileName=#Target script file name +tools.traceFileName=#Trace file name +tree.admin=Адмін +tree.current=Поточне значення +tree.hashed=Хешований +tree.increment=Збільшити +tree.indexes=Iндекси +tree.nonUnique=Неунікальне +tree.sequences=Послідовності +tree.unique=Унікальне +tree.users=Користувачі diff --git a/h2/src/main/org/h2/server/web/res/_text_zh_cn.prop b/h2/src/main/org/h2/server/web/res/_text_zh_cn.prop new file mode 100644 index 0000000..5dabdcd --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/_text_zh_cn.prop @@ -0,0 +1,164 @@ +.translator=#Thomas Mueller +a.help=帮助 +a.language=中文 (简体) +a.lynxNotSupported=抱歉, 目前还不支持Lynx +a.password=密码 +a.remoteConnectionsDisabled=抱歉, 服务器上的远程计算机连接被禁用. +a.title=H2 控制台 +a.tools=工具 +a.user=用户名 +admin.executing=执行中 +admin.ip=IP地址 +admin.lastAccess=最近访问 +admin.lastQuery=最近查询 +admin.no=否 +admin.notConnected=未连接 +admin.url=URL +admin.yes=是 +adminAllow=允许客户端连接 +adminConnection=连接安全 +adminHttp=使用非加密的 HTTP 连接 +adminHttps=使用加密的 SSL (HTTPS) 连接 +adminLocal=只允许本地连接 +adminLogin=管理员登录 +adminLoginCancel=取消 +adminLoginOk=确认 +adminLogout=注销 +adminOthers=允许来自其他远程计算机的连接 +adminWebExternalNames=#External names or addresses of this server (comma-separated) +adminPort=端口号 +adminPortWeb=Web 服务器端口号 +adminRestart=更新配置将在重启服务器后生效. +adminSave=保存 +adminSessions=活动的会话 +adminShutdown=关闭 +adminTitle=H2 控制台配置 +adminTranslateHelp=创建或改进H2控制台的翻译 +adminTranslateStart=翻译 +helpAction=活动 +helpAddAnotherRow=增加另一行 +helpAddDrivers=增加数据库驱动 +helpAddDriversText=可以通过添加系统环境变量H2DRIVERS 或者 CLASSPATH 来增加数据库驱动注册。例如(Windows):要增加数据库驱动C:/Programs/hsqldb/lib/hsqldb.jar,可以增加系统环境变量H2DRIVERS并设置到C:/Programs/hsqldb/lib/hsqldb.jar。 +helpAddRow=增加新的一行 +helpCommandHistory=显示历史SQL命令 +helpCreateTable=创建一个新表 +helpDeleteRow=删除一行 +helpDisconnect=断开数据库连接 +helpDisplayThis=显示帮助页 +helpDropTable=如果表存在删除它 +helpExecuteCurrent=执行当前SQL语句 +helpExecuteSelected=#Executes the SQL statement defined by the text selection +helpIcon=图标 +helpImportantCommands=重要的命令 +helpOperations=操作 +helpQuery=查询表 +helpSampleSQL=样例SQL脚本 +helpStatements=SQL 语句 +helpUpdate=改变一行数据 +helpWithColumnsIdName=用ID和NAME列 +key.alt=#Alt +key.ctrl=#Ctrl +key.enter=#Enter +key.shift=#Shift +key.space=#Space +login.connect=连接 +login.driverClass=驱动类 +login.driverNotFound=找不到数据库驱动
请参考帮助去添加数据库驱动 +login.goAdmin=配置 +login.jdbcUrl=JDBC URL +login.language=语言 +login.login=登录 +login.remove=删除 +login.save=保存 +login.savedSetting=保存的连接设置 +login.settingName=连接设置名称 +login.testConnection=测试连接 +login.testSuccessful=测试成功 +login.welcome=H2 控制台 +result.1row=1 行 +result.autoCommitOff=自动提交已关闭 +result.autoCommitOn=自动提交已打开 +result.bytes=字节 +result.characters=字符 +result.maxrowsSet=最大返回行数被设置 +result.noRows=无返回行 +result.noRunningStatement=当前没有正在执行的SQL语句 +result.rows=行 +result.statementWasCanceled=SQL 语句被取消 +result.updateCount=更新行数 +resultEdit.action=活动 +resultEdit.add=增加 +resultEdit.cancel=取消 +resultEdit.delete=删除 +resultEdit.edit=编辑 +resultEdit.editResult=编辑结果集 +resultEdit.save=保存 +toolbar.all=全部 +toolbar.autoCommit=自动提交 +toolbar.autoComplete=自动完成 +toolbar.autoComplete.full=完全 +toolbar.autoComplete.normal=正常 +toolbar.autoComplete.off=关闭 +toolbar.autoSelect=#Auto select +toolbar.autoSelect.off=关闭 +toolbar.autoSelect.on=#On +toolbar.cancelStatement=取消当前的执行语句 +toolbar.clear=清除 +toolbar.commit=提交 +toolbar.disconnect=断开连接 +toolbar.history=历史SQL命令 +toolbar.maxRows=最大行数 +toolbar.refresh=刷新 +toolbar.rollback=回滚 +toolbar.run=执行 +toolbar.runSelected=#Run Selected +toolbar.sqlStatement=SQL 语句 +tools.backup=备份 +tools.backup.help=创建一个数据库备份 +tools.changeFileEncryption=更改文件加密方式 +tools.changeFileEncryption.help=允许更改数据库文件加密方式和算法 +tools.cipher=加密方式 (AES 或 XTEA) +tools.commandLine=命令行 +tools.convertTraceFile=转换跟踪文件 +tools.convertTraceFile.help=转换 .trace.db 文件为Java程序和SQL脚本. +tools.createCluster=创建集群 +tools.createCluster.help=从单一数据库创建集群. +tools.databaseName=数据库名称 +tools.decryptionPassword=密码明文 +tools.deleteDbFiles=删除数据库文件 +tools.deleteDbFiles.help=删除数据库的所有文件 +tools.directory=目录 +tools.encryptionPassword=密码密文 +tools.javaDirectoryClassName=Java目录和类名 +tools.recover=恢复 +tools.recover.help=帮助恢复一个已崩溃的数据库 +tools.restore=还原 +tools.restore.help=还原数据库备份 +tools.result=结果 +tools.run=运行 +tools.runScript=运行脚本 +tools.runScript.help=运行SQL脚本 +tools.script=脚本 +tools.script.help=允许为备份或迁移而 +tools.scriptFileName=脚本文件名 +tools.serverList=服务器列表 +tools.sourceDatabaseName=源数据库名 +tools.sourceDatabaseURL=源数据库 URL +tools.sourceDirectory=源目录 +tools.sourceFileName=源文件名 +tools.sourceScriptFileName=源脚本文件名 +tools.targetDatabaseName=目标据库名 +tools.targetDatabaseURL=目标数据库 URL +tools.targetDirectory=目标目录 +tools.targetFileName=目标文件名 +tools.targetScriptFileName=目标脚本文件名 +tools.traceFileName=跟踪文件名 +tree.admin=管理 +tree.current=当前值 +tree.hashed=杂乱的 +tree.increment=增加 +tree.indexes=索引 +tree.nonUnique=不唯一 +tree.sequences=序列 +tree.unique=唯一 +tree.users=用户 diff --git a/h2/src/main/org/h2/server/web/res/_text_zh_tw.prop b/h2/src/main/org/h2/server/web/res/_text_zh_tw.prop new file mode 100644 index 0000000..6e726c8 --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/_text_zh_tw.prop @@ -0,0 +1,164 @@ +.translator=derek chao (Dept of Geog., CCU, Taiwan), 2008.04.20 +a.help=輔助說明 +a.language=中文 (繁體) +a.lynxNotSupported=抱歉, 目前還不支援Lynx瀏覽器 +a.password=密碼 +a.remoteConnectionsDisabled=抱歉, 本伺服器禁用遠端連接 ('webAllowOthers'). +a.title=H2 控制台 +a.tools=#Tools +a.user=使用者名稱 +admin.executing=執行 +admin.ip=IP位址 +admin.lastAccess=上次存取 +admin.lastQuery=上次查詢 +admin.no=#no +admin.notConnected=#not connected +admin.url=URL +admin.yes=#yes +adminAllow=允許連接的客戶端 +adminConnection=連接之安全性 +adminHttp=使用非加密的 HTTP 連接 +adminHttps=使用加密的 SSL (HTTPS) 連接 +adminLocal=只允許本地連接 +adminLogin=管理員登入 +adminLoginCancel=取消 +adminLoginOk=確定 +adminLogout=登出 +adminOthers=允許來自其他電腦的連接 +adminWebExternalNames=#External names or addresses of this server (comma-separated) +adminPort=通訊埠 +adminPortWeb=Web 伺服器的通訊埠 +adminRestart=伺服器重新啟動後修改才會生效. +adminSave=儲存 +adminSessions=作用中的進程 (Active Sessions) +adminShutdown=關閉 +adminTitle=H2 控制台個人喜好設定 +adminTranslateHelp=#Translate or improve the translation of the H2 Console. +adminTranslateStart=#Translate +helpAction=動作 +helpAddAnotherRow=增加另一資料列 (row) +helpAddDrivers=增加資料庫驅動程式 +helpAddDriversText=可以透過添加系統環境變量H2DRIVERS 或 CLASSPATH 指向Jar檔案的位置,來註冊資料庫的驅動程式。例如(在Windows系統內):添加系統環境變數H2DRIVERS並指向C:/Programs/hsqldb/lib/hsqldb.jar,即可將C:/Programs/hsqldb/lib/hsqldb.jar資料庫驅動程式庫加入。 +helpAddRow=增加新的資料列 (row) +helpCommandHistory=顯示命令史 +helpCreateTable=創建新資料表 +helpDeleteRow=刪除資料列 (row) +helpDisconnect=中斷資料庫連接 +helpDisplayThis=顯示輔助說明頁 +helpDropTable=刪除存在的資料表 +helpExecuteCurrent=執行目前的SQL述句 +helpExecuteSelected=#Executes the SQL statement defined by the text selection +helpIcon=圖示 +helpImportantCommands=重要命令 +helpOperations=操作 +helpQuery=資料表查詢 +helpSampleSQL=範例SQL腳本 +helpStatements=SQL述句 +helpUpdate=變更資料列 (row) 中的資料 +helpWithColumnsIdName=用ID和NAME欄位 +key.alt=#Alt +key.ctrl=#Ctrl +key.enter=#Enter +key.shift=#Shift +key.space=#Space +login.connect=連接 +login.driverClass=驅動程式類別 (Driver Class) +login.driverNotFound=沒有找到資料庫驅動程式
請參考輔助說明來添加驅動程式 +login.goAdmin=個人喜好設定 +login.jdbcUrl=JDBC URL +login.language=語言 +login.login=登入 +login.remove=刪除 +login.save=儲存 +login.savedSetting=儲存的設定值 +login.settingName=設定的名稱 +login.testConnection=測試連接 +login.testSuccessful=測試成功 +login.welcome=H2 控制台 +result.1row=1列資料列 (row) +result.autoCommitOff=自動提交現在為關閉狀態 +result.autoCommitOn=自動提交現在為開啟狀態 +result.bytes=#bytes +result.characters=#characters +result.maxrowsSet=最大資料列 (rowcount) 設定完成 +result.noRows=無資料列 (rows) +result.noRunningStatement=目前沒有正在執行的SQL述句 +result.rows=資料列 (rows) +result.statementWasCanceled=SQL述句已取消 +result.updateCount=更新計數 +resultEdit.action=動作 +resultEdit.add=增加 +resultEdit.cancel=取消 +resultEdit.delete=刪除 +resultEdit.edit=編輯 +resultEdit.editResult=編輯 +resultEdit.save=儲存 +toolbar.all=全部 +toolbar.autoCommit=自動提交 +toolbar.autoComplete=自動完成 (complete) +toolbar.autoComplete.full=完整 +toolbar.autoComplete.normal=標準 +toolbar.autoComplete.off=關閉 +toolbar.autoSelect=#Auto select +toolbar.autoSelect.off=關閉 +toolbar.autoSelect.on=#On +toolbar.cancelStatement=取消目前的SQL述句 +toolbar.clear=清除 +toolbar.commit=提交 +toolbar.disconnect=中斷連接 +toolbar.history=命令史 +toolbar.maxRows=最大資料列 (rows) +toolbar.refresh=更新 +toolbar.rollback=退返 (rollback) +toolbar.run=執行 +toolbar.runSelected=#Run Selected +toolbar.sqlStatement=SQL 述句 +tools.backup=備份 +tools.backup.help=建立資料庫的備份 +tools.changeFileEncryption=#ChangeFileEncryption +tools.changeFileEncryption.help=#Allows changing the database file encryption password and algorithm. +tools.cipher=加密 (AES 或 XTEA) +tools.commandLine=命令列 +tools.convertTraceFile=轉換Trace檔案 +tools.convertTraceFile.help=將.trace.db檔案轉換成Java應用程式與SQL腳本 (script). +tools.createCluster=建立叢集 (Cluster) +tools.createCluster.help=自獨立的資料庫建立叢集 (Cluster) +tools.databaseName=資料庫名稱 +tools.decryptionPassword=明文密碼 +tools.deleteDbFiles=刪除資料庫檔案 +tools.deleteDbFiles.help=刪除某一資料庫的所有相關檔案 +tools.directory=目錄 +tools.encryptionPassword=密文密碼 +tools.javaDirectoryClassName=Java目錄 (directory) 與類別 (class) 名稱 +tools.recover=修復 +tools.recover.help=協助修復損壞的資料庫 +tools.restore=回存 +tools.restore.help=回存資料庫的備份 +tools.result=結果 +tools.run=執行 +tools.runScript=執行腳本 (Script) +tools.runScript.help=執行SQL腳本 (script) +tools.script=腳本 (Script) +tools.script.help=允許自資料庫轉換出為備份或搬遷用的SQL腳本(script) +tools.scriptFileName=腳本 (Script) 檔案名稱 +tools.serverList=伺服器清單 +tools.sourceDatabaseName=來源 (source) 資料庫名稱 +tools.sourceDatabaseURL=來源 (source) 資料庫URL +tools.sourceDirectory=來源目錄 (source directory) +tools.sourceFileName=來源 (source) 檔案名稱 +tools.sourceScriptFileName=來源腳本 (script) 檔案名稱 +tools.targetDatabaseName=目的 (target) 資料庫名稱 +tools.targetDatabaseURL=目的 (target) 資料庫URL +tools.targetDirectory=目的目錄 (target directory) +tools.targetFileName=目的 (target) 檔案名稱 +tools.targetScriptFileName=目的腳本 (script) 檔案名稱 +tools.traceFileName=Trace 檔案名稱 +tree.admin=管理 +tree.current=目前的數值 +tree.hashed=使用雜湊法 (hashed) +tree.increment=遞增 +tree.indexes=索引 +tree.nonUnique=非唯一 +tree.sequences=序列 +tree.unique=唯一 +tree.users=使用者 diff --git a/h2/src/main/org/h2/server/web/res/admin.jsp b/h2/src/main/org/h2/server/web/res/admin.jsp new file mode 100644 index 0000000..f9b3ae2 --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/admin.jsp @@ -0,0 +1,129 @@ + + + + + + ${text.a.title} + + + +

+ ${text.adminTitle} +

+

+ ${text.adminLogout} +

+
+
+

+ ${text.adminAllow} +

+

+ + + + + + + ${text.adminLocal}
+ + + + + + + + ${text.adminOthers}
+

+

+ ${text.adminWebExternalNames}:
+ +

+

+ ${text.adminConnection} +

+

+ + + + + + + ${text.adminHttp}
+ + + + + + + + ${text.adminHttps}
+

+

+ ${text.adminPort} +

+

+ ${text.adminPortWeb}: +

+
+

+ +

+

+ ${text.adminRestart} +

+
+
+

+

+ +
+

+

+ ${text.adminTranslateHelp} +

+
+

+ ${text.adminSessions} +

+ + + + + + + + + + + + + + + + + + + +
${text.admin.ip}${text.admin.url}${text.a.user}${text.admin.executing}${text.admin.lastAccess}${text.admin.lastQuery}
+ ${item.ip} + + ${item.url} + + ${item.user} + + ${item.executing} + + ${item.lastAccess} + + ${item.lastQuery} +
+
+
+ +
+ \ No newline at end of file diff --git a/h2/src/main/org/h2/server/web/res/adminLogin.jsp b/h2/src/main/org/h2/server/web/res/adminLogin.jsp new file mode 100644 index 0000000..4f13e87 --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/adminLogin.jsp @@ -0,0 +1,44 @@ + + + + + ${text.a.title} + + + +
+ + + + + + + + + + + + + + + +
+

${error}

+
+ + + \ No newline at end of file diff --git a/h2/src/main/org/h2/server/web/res/autoCompleteList.jsp b/h2/src/main/org/h2/server/web/res/autoCompleteList.jsp new file mode 100644 index 0000000..8d68480 --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/autoCompleteList.jsp @@ -0,0 +1 @@ +${autoCompleteList} \ No newline at end of file diff --git a/h2/src/main/org/h2/server/web/res/background.gif b/h2/src/main/org/h2/server/web/res/background.gif new file mode 100644 index 0000000000000000000000000000000000000000..f8832e6b1a0a2e3cff7448dbbdc57a8c037c4310 GIT binary patch literal 169 zcmZ?wbhEHbWMq(LXc1>H32Be%I1$%%D!%7zV&D0si5F8QUrC*MEq(fpjG4Ey=G@Jm zdoO4H!@Px$^Orm=So*wZ`OD&!uS-_HEnWM*Y~6?Q^`9y>e*TXeDEQcw7=?JQnHrJhll?@eN76E5^{mU=09edsp-T literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/error.jsp b/h2/src/main/org/h2/server/web/res/error.jsp new file mode 100644 index 0000000..f0f26fe --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/error.jsp @@ -0,0 +1,16 @@ + + + + + ${text.a.title} + + + +

+ ${error} +

+ \ No newline at end of file diff --git a/h2/src/main/org/h2/server/web/res/favicon.ico b/h2/src/main/org/h2/server/web/res/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..fd5e73a416cf2cde3a3e5d9e530e53c3e74742af GIT binary patch literal 4286 zcmcgwU2IfE6rO@~@65JbQG-8;l#uv`L}Pe|jiq;PA@~3(_zMJ+4SVlh2(gXPM7ty! zjnVo9{y_?8p{C38cGsYeUfyHtl;?MUd$g#Fi@@;{1KEQYmbQLrnNd6Mp zjwF8;-*3-_0-5z^^Q*AISrz)uN3eSZ^cnDX6uVnjmwH&MxfXsLhg{8tBA>ry+A!zE zZk7q9coX)A;&6U*`&~9x+BeRBD}4R!-}@J5z&nvf z6f@gaDBB)|?oXj73bt&vZ}`0iYcGOs#=+;83Z7Uy+gg(E>&)Iw*p1wadiHid#oDp2 zFJ}1Q@!t{ao_cGDAIF={+!uk?3pQr&A>TC>yTub)W{S$B z(>nGT`JzPeHu+fpvSs7CDL3bV=k!NX>@j(f$BS4$ZQ8)u>@#&}9-q6%FWYx*F8E2V zD4g>E?O$VH&wEY$*dH&`rR6lj&x6SES4UdFJLpIv$ zv8>kD<2>*c_I;{mr?dS|*s^ldd4c5E`*y_bnASDNdEq0TmaVg}cQY@gI7)J=tM zZ|ds#0^?K9sNv(jpN{b*9}YIOKB6(raiQ=7pKpP!8#C}F|2)ATvwMm5AqD@>xEE1B zvIzBm#Izrx=77Eg@B9pYat49#>`L&*HJ&}D;MdMC&E_0eBICGG=ORp&*GpX4WG z;|W!@%pUPBe$CK>zPMoP(D#11^bW!zOjAzxm-?{DYsMVdhXN_yux(R6Mt%-Op2*v> zh5E*s??kSBPw`K)nU~^T#O4z2LuZk2hk2&f%X}@Pqc9Y-GLGp|$bb4n2Ohed{%Pr2ByW`40T18r@efW?J7Isg?k@ zH(o3GyZWtX>NGmv+m(3$|Nr|0wnmX7NAb>nuHa`LGJ6)ysfSPix<7>5i$6$@QwGq*Qsa`Wxq<1>gVx literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/frame.jsp b/h2/src/main/org/h2/server/web/res/frame.jsp new file mode 100644 index 0000000..224b6a3 --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/frame.jsp @@ -0,0 +1,28 @@ + + + + + + ${text.a.title} + + + + + + + + + + + + + +<body> + ${text.a.lynxNotSupported} +</body> + + diff --git a/h2/src/main/org/h2/server/web/res/header.jsp b/h2/src/main/org/h2/server/web/res/header.jsp new file mode 100644 index 0000000..5edb398 --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/header.jsp @@ -0,0 +1,163 @@ + + + + + +${text.a.title} + + + +
+ + + + + + + + + + + + + + + +
+ + ${text.toolbar.disconnect} + + + + ${text.toolbar.refresh} + + + + + + ${text.toolbar.autoCommit}  + + + ${text.toolbar.rollback} + + + ${text.toolbar.commit} + + + +  ${text.toolbar.maxRows}:  + +   + + + ${text.toolbar.run} + + + + ${text.toolbar.runSelected} + + + + ${text.toolbar.cancelStatement} + + + + ${text.toolbar.history} + + + + ${text.toolbar.autoComplete}  +   + + ${text.toolbar.autoSelect}  + + + + ${text.a.help} + +
+
+ + + diff --git a/h2/src/main/org/h2/server/web/res/help.jsp b/h2/src/main/org/h2/server/web/res/help.jsp new file mode 100644 index 0000000..c5d9421 --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/help.jsp @@ -0,0 +1,99 @@ + + + + + + ${text.a.title} + + + + + + +
+ +

${text.helpImportantCommands}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
${text.a.help}${text.helpDisplayThis}
${text.toolbar.history}${text.helpCommandHistory}
${text.toolbar.run}${text.key.ctrl}+${text.key.enter}${text.helpExecuteCurrent}
${text.toolbar.runSelected}${text.key.shift}+${text.key.enter}${text.helpExecuteSelected}
${text.key.ctrl}+${text.key.space}${text.toolbar.autoComplete}
${text.toolbar.disconnect}${text.helpDisconnect}
+ +

${text.helpSampleSQL}

+ + + +
+ ${text.helpDropTable}
+ ${text.helpCreateTable}
+   ${text.helpWithColumnsIdName}
+ ${text.helpAddRow}
+ ${text.helpAddAnotherRow}
+ ${text.helpQuery}
+ ${text.helpUpdate}
+ ${text.helpDeleteRow} +
+ DROP TABLE IF EXISTS TEST;
+ CREATE TABLE TEST(ID INT PRIMARY KEY,
+    NAME VARCHAR(255));
+ INSERT INTO TEST VALUES(1, 'Hello');
+ INSERT INTO TEST VALUES(2, 'World');
+ SELECT * FROM TEST ORDER BY ID;
+ UPDATE TEST SET NAME='Hi' WHERE ID=1;
+ DELETE FROM TEST WHERE ID=2; +
+ ${text.a.help} + + HELP ... +
+ +

${text.helpAddDrivers}

+

+${text.helpAddDriversText} +

+ +
+ +
+ + diff --git a/h2/src/main/org/h2/server/web/res/helpTranslate.jsp b/h2/src/main/org/h2/server/web/res/helpTranslate.jsp new file mode 100644 index 0000000..2df2f6b --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/helpTranslate.jsp @@ -0,0 +1,43 @@ + + + + + + ${text.a.title} + + + + +
+ +

Translate

+

+You can now translate the file ${translationFile} with your favorite editor. +

+

+To view the changes in context, save the file and refresh the browser. +The H2 Console reads the file every second. +

+

+When done, please send the file to the H2 support. +Please send the file as an attachment (to avoid line breaks). +

+

+To translate from scratch: +

+
  • Stop the H2 Console +
  • Rename or delete the translation file +
  • Start the H2 Console +
  • Select the source language of your choice +
  • Go to 'Preferences' and click 'Translation' +
+ +${text.adminLogout} + +
+ + \ No newline at end of file diff --git a/h2/src/main/org/h2/server/web/res/ico_add.gif b/h2/src/main/org/h2/server/web/res/ico_add.gif new file mode 100644 index 0000000000000000000000000000000000000000..252d7ebcb8c74d6e5de66ef0eb8856622a0e9d89 GIT binary patch literal 318 zcmZ?wbhEHb6krfwxXQp_S{7na6>d=(Ze15;Qy1mX8t2lT^ zHOHgY9FJamJZkN+sI|wVk6ueSb}ixP)r4bLfn?%^+sU^crQUj&`rv8W|Ns9PC;*B- zSr{1@v>9|jW`O*}z!rUAYJrE2RKG`JYNRKhwpQrnor{(Ph`sVEnyR2Kc;c~(o$WdP zMb0Wl6K^dQ-0hgs;HDp=`YXq7eRk`yBilLl`~C3yXx4)#-w1yD ze-TnjCABw^MJQ5HidKw^8Ol_avX$fB3RS8~)vEDmhdR}zZnc?eLX(=(G_(zDXs{Tj zd%VYy5f#x9Hl3V339-w5YKd zY@oqncr3^{QJI}N*2h}1s;tf$%Y_BSuI$bp)I*BioXY7OHlgFqgIB-4-+bO2K0evJyM3;EN9UJUAAg*E gIe7B+=helB-(T-OI{$Y1=l0I!!#CHT?r-(%A330GQUCw| literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/ico_remove.gif b/h2/src/main/org/h2/server/web/res/ico_remove.gif new file mode 100644 index 0000000000000000000000000000000000000000..64b438488bcb51f639e52a3c685f55b740883016 GIT binary patch literal 350 zcmZ?wbhEHb6krfwxXQqAFDUd$XvEX-s3+l3*MdThhK7Bvto~e8{RxC>?gfP%@e4Za z7j!fz^mBg6Cm<>)Iqd2GAv*PabjlI$z{B2wpAs@ZBxHU{%e_=seQ(b4%iWD9GU85T z#vRX$JCT!crab5N()q_zVvom19gmMb79V{tFY9Y-)92QvhpEX&qauz*MLs-q=>Px! z3}gYtpDc_F47v7O@vFB`qe}(^LZldT0EV8>Vg_9 zLJJQmK3X=#VuqVqOJj;^TG)i+hd&0SPGDu0Xl7#d(B)+m7ZYRT^^oKinmn1O-c?d$ wGK(mSkg(`@Cf|62_ON9lcoOJ}bmOHOv5R7r!u*hlEj`sZrbR8M20XzVCnE(I) literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/ico_remove_ok.gif b/h2/src/main/org/h2/server/web/res/ico_remove_ok.gif new file mode 100644 index 0000000000000000000000000000000000000000..facfd1395af87918567e33a19f2ab3b5ed25cfc6 GIT binary patch literal 107 zcmZ?wbh9u|6krfwc+AfL1Px%^0Hzv%yao_BaNxjyp!k1=|6mG816e?z0~7@66kuRr qQTTAubM;<}*R#I)%w=!8l`(Nr<)l})wqEDx+WdV%*}7^$25SJ6t}1r` literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/ico_search.gif b/h2/src/main/org/h2/server/web/res/ico_search.gif new file mode 100644 index 0000000000000000000000000000000000000000..a4548c5dc8cc3c2d5c6ca78224a3f1b7f2cb4b2a GIT binary patch literal 545 zcmZ?wbhEHb6krfwc*ejmbK1(|a*HZOnv%%c4}CT?2OdFI5XV@I~%zk6ZLqJoXf zOSi7By>M#nwl($d-@TqQW7?dK`bvL?uELCt!pvFi4d1_f?kLP^FUaaFDQGV)Y|6{~ z_wV2D-@m_n`SR)0rx!0?JbU)+|Ns9Cg9#}9WMO1r2w>0wISLdf4D59c0Zq*H;}#d=k(J~Zwd)IDXXE1JU}bQzzs1DD%+%!U;CQRe V)xqgqtDA%S=N1poA8m~c)&T$D+}QvC literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/ico_undo.gif b/h2/src/main/org/h2/server/web/res/ico_undo.gif new file mode 100644 index 0000000000000000000000000000000000000000..d77f94fc0532c437989c2d2188c298a54df35510 GIT binary patch literal 851 zcmb`Gv5J&&48-TaViAiIu~A{gMi9F+C-$dTSlHVf2PwrutSnZ~Lcv1Ibk_)$;uEZh zg_!rTjKBFmgMsDSY?8^$55JFQJ$Uks;J5!5 zA*EDOdlOlNA{C`*#kiQEOl2usIqt1crK(h|8jp6UQ(fv-o2e!=sVPlE+rWkfi($IQ zdmI^25gjpJOO_c~ksUc^L9sHbqB?3!ir&uXitgxvBcm}HQ!yRGR#*cDX;?yw8jHaO z8Z3s#f}9hT*_mT~tR<_;>a4L`SWxWB?(9K5r0C75oX$ZWen69H>_9&xPzYGrO*6Ss+uj}3}t`pkkbrtt6qwV_g&ZEl?A7Lpp z(jx7fEFK14(7X;@3G>jzs{4MM{a?`LNZZY=$KNh~oNun(xTK5QKhLk+y!`R~r#rvi mUESV${o?8A@rUc%S1-SQK0A4P_sR2TzrTO^^XA|{5B~v7bZ#5~ literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/ico_write.gif b/h2/src/main/org/h2/server/web/res/ico_write.gif new file mode 100644 index 0000000000000000000000000000000000000000..feb8e94a74f14633174999f46b00c5fea61f11ce GIT binary patch literal 210 zcmZ?wbhEHb6krfwIKsd%uV?<8?j^0QTNqTcpPZj^a8=#!J?VcR?f-po_Dp~2nL$dk zql{)J+RjLEo}Cr&9}E~s0g69a7#V<8>3}qW>||i|S5WC&^1~)I%ZztbpMSDM&+22x z8g1h{UcG=1Rx&Cu+mXtNaIGZp&`jn$x-?zjIGf@BaPu_NDy?$|oMG znY6EV(!S~`2O4J{X_$GsbHR!E^XE@oc6!qCGyCV)PhEL-#_IDk*POpUKYq^I^Rw4o zSi1Gf^6gjm@85ra|NLW@UfjRG|H0D_|7imhf3h%gG1xQcurRPN2mm1iTf%|)1s*zL zww*o)GJMpgoly*W*~vFI;I&D>-$=0pZo`i&9mItNIX*}5sLprrSg~Nm%`Qa;CzqIa v0vuuN_1OaKVQj4u0&HO{-MwMV6Z-;rS!Jd91Z2YO1(}$5q~-@YGFSruU--N) literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/icon_disconnect.gif b/h2/src/main/org/h2/server/web/res/icon_disconnect.gif new file mode 100644 index 0000000000000000000000000000000000000000..2ece8d838f3690c815304b157186965a626ed4d8 GIT binary patch literal 114 zcmV-&0FD1gNk%w1VHf}y0Kx|V$SE*@A0vQ0LGW8=@jp}cl8*I!eEKJ3KV~`FfuUMGw84| zurLS!Ap_f_1M>?!bi_m_b2SIf)abEy30CA#&~9GTxFDiqpHf3pfP|J-tZVW~kHxK% zPr83ea8dKVsIKP{?-i?!+T2Cnp}y$HONsF3r;+t2jfcUqEh#>{R;F$S9MhlmXHWP?H2AG9B|#$ Hk--`O!Ti|P literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/icon_history.gif b/h2/src/main/org/h2/server/web/res/icon_history.gif new file mode 100644 index 0000000000000000000000000000000000000000..745fdf2c099da3263208aead3b0cb83310c2dc68 GIT binary patch literal 216 zcmZ?wbhEHblwgoxXpv=DUMI7%QFeWY!un2yO}$E+dzH3LQr3K{yz*T{$yd~VvuLhVE_V P(_fygZER9eWUvMRZg+0o literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/icon_line.gif b/h2/src/main/org/h2/server/web/res/icon_line.gif new file mode 100644 index 0000000000000000000000000000000000000000..98c2c3832c01fdd3f8660c3f6abf64bd573af098 GIT binary patch literal 818 zcmb`Fy^U0H48(^6q01ITaw#dg%n68`k1|LcQ^JZaoKebNK7CLF5@PuIUjvp_Z~bhK zXU6aI*H7<1e0!+}{kn*_p5OI6u4LDZ+wB(KM>!9k{3Cex|DvRpM)GbVt5UUUR90n+ zUFuek+UjiYl%_SKu_im}l3O0x@-kIQX=M~cdw~}kEQaaM?%C+7sd80zEm?Q8=EQWnJFAbiWHUYP z$;yQVMNix9pdM26cBbPF^6&$iOk)T7A%Q|z0|seW(h3h2gAFuTS?9J)PIxA3B>GrO zW{-of$b|*Pi6{7o(-;6R+A$inScxCdWEwl*gkTiH8ZbyR2`zZA7;K=kC)#exG2{2kc^@9E`0-)G?+;sk{CM&2@B9DM28usf7#SGs8FW|}SQrF=kby1b!2ALa z9jQ)%6BjuYHK#kXcV4NmSn{^)3+D|^4a8F`E^T`M5Gg#WrFA-q4 z@p!JifU>`PlcJ}xLW`?_jADPkgrGOKnA~(ZF|Gg(c2+47mH=l4k=n)8%iSFrtO48` Bz4ZV9 literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/icon_rollback.gif b/h2/src/main/org/h2/server/web/res/icon_rollback.gif new file mode 100644 index 0000000000000000000000000000000000000000..bcc2a4c6b96d966e94dca9c735c81988ee2982b3 GIT binary patch literal 331 zcmZ?wbhEHblwgoxSgOXbFh?||d`(K-ri{jI*)3Z))Jf&E?#S=lv%N*8sCR#9|AF#} zhiWG6tDUs3ddh*unMWFCp6*<5V&byXla`;Ey7KId)#qofIX`FZ`Pu6(EZur#`Sz~$N zz-Qf6T{aP*iM?lCLeYNChZ zME|t4Kq*J#ax2FU508od{@|VOltj+J(UOww!N5}U3iQCI3ZqHwGu%qFO+M$;f|H%c4 zKUo+V80;BzSQuCs1b~o%ZN`E51s*zLj_png0TvBC`*rdDU@_KcoWf`3q_^0;t%1>os zaAFjhKV4UDzJTzgKpsKC>2j;Yg_!FUID{m75zfIv1rTmZYXAlxLP?D7bt21~4f8WMSlDkY~^VSr2ju1M7kZ>U}Ah^D!iMzSau#MDHawE9v=2IZT>ktyu3V|%p27i_U_w%V4vDvy(6l}bWRwYK66${ Hk--`Ox59YK literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/icon_stop.gif b/h2/src/main/org/h2/server/web/res/icon_stop.gif new file mode 100644 index 0000000000000000000000000000000000000000..ae44bf3b6399edf65ca93e4e6dd6c8e13481f64b GIT binary patch literal 215 zcmZ?wbhEHblwgoxXc1y~VPNsX!r_I3#|w{u4;cjq1QcE<7@W|sxR6kABEaE9gvXB! zD^56A?69zS5fJd9qT+|z#z|{!@|JAAOM66tRWB7`%*IJWn{T| zz1|f#Q>P?W-RQ}iJ_7k1surXny~a3v%^{@InSWxgcnu3Ep9Ijwg$}H + + + + ${text.a.title} + + + + + +

Welcome to H2

+

No Javascript

+If you are not automatically redirected to the login page, then +Javascript is currently disabled or your browser does not support Javascript. +For this application to work, Javascript is essential. +Please enable Javascript now, or use another web browser that supports it. + + diff --git a/h2/src/main/org/h2/server/web/res/login.jsp b/h2/src/main/org/h2/server/web/res/login.jsp new file mode 100644 index 0000000..ab9483f --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/login.jsp @@ -0,0 +1,131 @@ + + + + + + ${text.a.title} + + + + +
+

+    ${text.login.goAdmin} + +    ${text.a.tools} +    ${text.a.help} +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

${error}

+
+ \ No newline at end of file diff --git a/h2/src/main/org/h2/server/web/res/notAllowed.jsp b/h2/src/main/org/h2/server/web/res/notAllowed.jsp new file mode 100644 index 0000000..bb4b34f --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/notAllowed.jsp @@ -0,0 +1,16 @@ + + + + + ${text.a.title} + + +

${text.a.title}

+

+ ${text.a.remoteConnectionsDisabled} +

+ \ No newline at end of file diff --git a/h2/src/main/org/h2/server/web/res/query.jsp b/h2/src/main/org/h2/server/web/res/query.jsp new file mode 100644 index 0000000..a177c03 --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/query.jsp @@ -0,0 +1,530 @@ + + + + + + ${text.a.title} + + + + +
+ + + + + + ${text.toolbar.sqlStatement}: + +
+ +
+ +
+
+ +
+ + \ No newline at end of file diff --git a/h2/src/main/org/h2/server/web/res/result.jsp b/h2/src/main/org/h2/server/web/res/result.jsp new file mode 100644 index 0000000..72a4ace --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/result.jsp @@ -0,0 +1,22 @@ + + + + + + ${text.a.title} + + + + + +
+${result} +
+ +
+ + diff --git a/h2/src/main/org/h2/server/web/res/stylesheet.css b/h2/src/main/org/h2/server/web/res/stylesheet.css new file mode 100644 index 0000000..8d217a0 --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/stylesheet.css @@ -0,0 +1,338 @@ +/* + * 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 + */ + +td, input, select, textarea, body, code, pre { + font: 12px/1.4 Arial, sans-serif; +} + +h1, h2, h3, h4, h5 { + font: 12px/1.4 Arial, sans-serif; + font-weight: bold; +} + +a { + text-decoration: none; + color: #0000ff; +} + +a:hover { + text-decoration: underline; +} + +body { + margin: 4px; +} + +code { + background-color: #ece9d8; + padding: 0px 2px; +} + +h1 { + background-color: #0000bb; + padding: 2px 4px 2px 4px; + color: #fff; + font-size: 22px; + line-height: normal; +} + +h2 { + font-size: 19px; +} + +h3 { + font-size: 16px; +} + +li { + margin-top: 6px; +} + +ol { + list-style-type: upper-roman; + list-style-position: outside; +} + +table { + background-color: #ffffff; + border-collapse: collapse; + border: 1px solid #aca899; +} + +td { + background-color: #ffffff; + padding: 2px; + text-align: left; + vertical-align:top; + border: 1px solid #aca899; +} + +textarea { + width: 100%; + overflow: auto; +} + +th { + font-weight: normal; + text-align: left; + background-color: #ece9d8; + padding: 2px; + border: 1px solid #aca899; +} + +ul { + list-style-type: disc; + list-style-position: outside; + padding-left: 20px; +} + +.result { + background-color: #f4f0e0; + margin: 10px; +} + +table.resultSet { + white-space: pre; +} + +.toolbar { + background-color: #ece9d8; +} + +table.toolbar { + border-collapse: collapse; + border: 0px; + padding: 0px 0px; +} + +th.toolbar { + border: 0px; +} + +tr.toolbar { + border: 0px; +} + +td.toolbar { + vertical-align: middle; + border: 0px; + padding: 0px 0px; +} + +table.nav { + border: 0px; +} + +tr.nav { + border: 0px; +} + +td.nav { + border: 0px; +} + +table.login { + background-color: #ece9d8; + border:1px solid #aca899; +} + +tr.login { + border: 0px; +} + +th.login { + color: #ffffff; + text-align: left; + border: 0px; + background-color: #ece9d8; + padding: 4px 10px; + background-image: url(background.gif); +} + +td.login { + background-color: #ece9d8; + padding: 5px 10px; + text-align: left; + border: 0px; +} + +.iconLine { + border-width:0px; + border-style:solid; +} + +.icon { + border-top-color:#ece9d8; + border-left-color:#ece9d8; + border-right-color:#ece9d8; + border-bottom-color:#ece9d8; + border-width:1px; + border-style:solid; +} + +.icon_hover { + border-color:#aca899; + border-radius: 2px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-width:1px; + border-style:solid; +} + +table.empty { + background-color: #ffffff; + border: 0px; +} + +td.empty { + background-color: #ffffff; + border: 0px; + padding: 5px 10px; + text-align: left; +} + +.error { + color: #771111; +} + +div.error { + background-color: #eecccc; + border-color: #ddbbbb; +} + +div.success { + color: #226622; + background-color: #cceecc; + border-color: #bbddbb; +} + +div.error, div.success { + border-radius: 4px; + padding: 10px; + border-width: 1px; + border-style: solid; +} + +input.button { + padding: 3px; + background-color: #ece9d8; + border-color: #aca899; + border-radius: 2px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-width: 1px; + border-style: solid; +} + +input.button:hover { + border-color: #5e5c55; +} + +input.button:active { + position:relative; + top:1px; +} + +.tree { + border: 0px; + vertical-align: middle; + white-space: nowrap; +} + +.tree img { + height: 18px; + width: 18px; + border: 0px; + vertical-align: middle; +} + +.tree a { + border: 0px; + text-decoration: none; + vertical-align: middle; + white-space: nowrap; + color: #000000; +} + +.tree a:hover { + color: #345373; +} + +table.content { + width: 100%; + height: 100%; + border: 0px; +} + +tr.content { + border:0px; + border-left:1px solid #aca899; +} + +td.content { + border:0px; + border-left:1px solid #aca899; +} + +.contentDiv { + margin:10px; +} + +tr.contentResult { + border:0px; + border-top:1px solid #aca899; + border-left:1px solid #aca899; +} + +td.contentResult { + border:0px; + border-top:1px solid #aca899; + border-left:1px solid #aca899; +} + +table.autoComp { + background-color: #e0ecff; + border: 1px solid #7f9db9; + cursor: pointer; + position: absolute; + top: 1px; + left: 1px; + z-index:0; + padding: 0px; + margin: 0px; + border-spacing:2px; +} + +td.autoComp0 { + border-spacing: 0px; + padding: 1px 8px; + background-color: #cce0ff; + border: 0px; +} + +td.autoComp1 { + border-spacing: 0px; + padding: 1px 8px; + background-color: #e7f0ff; + border: 0px; +} + +td.autoComp2 { + border-spacing: 0px; + padding: 1px 8px; + background-color: #ffffff; + border: 0px; +} + +td.autoCompHide { + padding: 2px; + display: none; +} + +table.tool, table.tool tr, table.tool tr td { + padding: 0px; + border: 0px; +} diff --git a/h2/src/main/org/h2/server/web/res/table.js b/h2/src/main/org/h2/server/web/res/table.js new file mode 100644 index 0000000..841b3da --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/table.js @@ -0,0 +1,265 @@ +/* + * 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 + */ + +addEvent(window, "load", initSort); + +function addEvent(elm, evType, fn, useCapture) { + // addEvent and removeEvent + // cross-browser event handling for IE5+, NS6 and Mozilla + // By Scott Andrew + if (elm.addEventListener){ + elm.addEventListener(evType, fn, useCapture); + return true; + } else if (elm.attachEvent){ + var r = elm.attachEvent("on"+evType, fn); + return r; + } else { + alert("Handler could not be added"); + } +} + +function initSort() { + if (document.getElementById('editing') != undefined) { + // don't allow sorting while editing + return; + } + var tables = document.getElementsByTagName("table"); + for (var i=0; i 0) { + var header = table.rows[0]; + for(var j=0;j  '; + } + } + } +} + +function editRow(row, session, write, undo) { + var table = document.getElementById('editTable'); + var y = row < 0 ? table.rows.length - 1 : row; + var i; + for(i=1; i'; + var undo = ''+undo+''; + cell.innerHTML = edit + undo; + } else { + cell.innerHTML = ''; + } + } + var cells = table.rows[y].cells; + for (i=1; i/g, '>'); + var size; + var newHTML; + if (text.indexOf('\n') >= 0) { + size = 40; + newHTML = ''; + } else { + size = text.length+5; + newHTML = ''; + } + newHTML = newHTML.replace('$rowName', 'r' + row + 'c' + i); + newHTML = newHTML.replace('$row', row); + newHTML = newHTML.replace('$t', text); + newHTML = newHTML.replace('$size', size); + cell.innerHTML = newHTML; + } +} + +function deleteRow(row, session, write, undo) { + var table = document.getElementById('editTable'); + var y = row < 0 ? table.rows.length - 1 : row; + var i; + for(i=1; i'; + var undo = ''+undo+''; + cell.innerHTML = edit + undo; + } else { + cell.innerHTML = ''; + } + } + var cells = table.rows[y].cells; + for (i=1; i + + + + + ${text.a.title} + + + + + + + +
+ +
+ +

+ ${error} +

+ + + diff --git a/h2/src/main/org/h2/server/web/res/tools.jsp b/h2/src/main/org/h2/server/web/res/tools.jsp new file mode 100644 index 0000000..110378c --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/tools.jsp @@ -0,0 +1,241 @@ + + + + + + ${text.a.tools} + + + + +
+ +

${text.a.tools}

+

+${text.adminLogout} +

+
+

+${text.tools.backup}   +${text.tools.restore}   +${text.tools.recover}   +${text.tools.deleteDbFiles}   +${text.tools.changeFileEncryption} +

+${text.tools.script}   +${text.tools.runScript}   +${text.tools.convertTraceFile}   +${text.tools.createCluster} +

+
+ + + + + + + + + + + + + + +
+ + + diff --git a/h2/src/main/org/h2/server/web/res/tree.js b/h2/src/main/org/h2/server/web/res/tree.js new file mode 100644 index 0000000..e4de5f3 --- /dev/null +++ b/h2/src/main/org/h2/server/web/res/tree.js @@ -0,0 +1,124 @@ +/* + * 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 + */ + +var nodeList = new Array(); +var icons = new Array(); +var tables = new Array(); +var tablesByName = new Object(); + +function Table(name, columns, i) { + this.name = name; + this.columns = columns; + this.id = i; +} + +function addTable(name, columns, i) { + var t = new Table(name, columns, i); + tables[tables.length] = t; + tablesByName[name] = t; +} + +function ins(s, isTable) { + if (parent.h2query) { + if (parent.h2query.insertText) { + parent.h2query.insertText(s, isTable); + } + } +} + +function refreshQueryTables() { + if (parent.h2query) { + if (parent.h2query.refreshTables) { + parent.h2query.refreshTables(); + } + } +} + +function goToTable(s) { + var t = tablesByName[s]; + if (t) { + hitOpen(t.id); + return true; + } + return false; +} + +function loadIcons() { + icons[0] = new Image(); + icons[0].src = "tree_minus.gif"; + icons[1] = new Image(); + icons[1].src = "tree_plus.gif"; +} + +function Node(level, type, icon, text, link) { + this.level = level; + this.type = type; + this.icon = icon; + this.text = text; + this.link = link; +} + +function setNode(id, level, type, icon, text, link) { + nodeList[id] = new Node(level, type, icon, text, link); +} + +function writeDiv(i, level, dist) { + if (dist>0) { + document.write("
"); + } else { + while (dist++<0) { + document.write("
"); + } + } +} + +function writeTree() { + loadIcons(); + var last=nodeList[0]; + for (var i=0; i0) { + document.write(""); + } + if (node.type==1) { + if (i < nodeList.length-1 && nodeList[i+1].level > node.level) { + document.write(""); + } else { + document.write(""); + } + } + document.write(" "); + if (node.link==null) { + document.write(node.text); + } else { + document.write(""+node.text+""); + } + document.write("
"); + } + writeDiv(0, 0, -last.type); +} + +function hit(i) { + var theDiv = document.getElementById("div"+i); + var theJoin = document.getElementById("join"+i); + if (theDiv.style.display == 'none') { + theJoin.src = icons[0].src; + theDiv.style.display = ''; + } else { + theJoin.src = icons[1].src; + theDiv.style.display = 'none'; + } +} + +function hitOpen(i) { + var theDiv = document.getElementById("div"+i); + var theJoin = document.getElementById("join"+i); + theJoin.src = icons[0].src; + theDiv.style.display = ''; +} \ No newline at end of file diff --git a/h2/src/main/org/h2/server/web/res/tree_column.gif b/h2/src/main/org/h2/server/web/res/tree_column.gif new file mode 100644 index 0000000000000000000000000000000000000000..e6aeb357361190bc673b92c0c7f1aefeb6056804 GIT binary patch literal 317 zcmZ?wbhEHb6k-r!SZc}8*t=xKh671O{Rss<85L8yrmfy{NYquZUcHsQZLl+L5ymsX5t;1(-R&*|??pfS0VOjI!mCch^v`<^zJ!{>P z4F{HOI<#fqnG+Z97BS&av~iHtadMe)ow93pN}%f9pRsp!iSJ zxhOTUBsE2$JhLQ2AtWPJ!QIn0fI;ym3nK%AJ%bJ-10#a~5Hhei9++R?p(E9QLSD&m zMZ%;*w{t|C9H%lIe_Rs!bl<9VFB-U=gYV0{$ujcbS*ES&yK7(4)*G)^d1$ZWXlx8` LX>D_HWUvMRSH`W} literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/tree_database.gif b/h2/src/main/org/h2/server/web/res/tree_database.gif new file mode 100644 index 0000000000000000000000000000000000000000..c0cb0dacb59b2885ebc6f9345f8c47a287c80e78 GIT binary patch literal 545 zcmZ?wbhEHb6k-r!IOfjq^F!j#52=5C%=zOq_^@kI-zfbD?KCS!VgyZ{TP6rm+pV<&{Y@zM(MfQ)D zi5!|~dUT%kv4!@ZHp(BGYII<#$&uMskLC#P?bSat)%^ZU(H)K2yV`7awAyTIvE9{X zyS>F`TeI!fM%zOThKCvrpH{N%uQl3NWBjy|eM7F=<}~&HLkB4SWMO1rsAte&WME_v z073@#x`z6u=9bnr1p^&J6LW>OCMjik7EV!FeW`w36}c(X^j-U{j3pTbrL`TM`|T9@ z=S^STX1k7sLu9&Bzl|avE4%Q{e#^bAY(j_HER^_I*yr2#TdVRhbBRrNxTMC*#4UdF kl9>bpkBqj9TffHRr_Wsk`s)?+v^0%O)j$1iZe*|q0M1BSPyhe` literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/tree_empty.gif b/h2/src/main/org/h2/server/web/res/tree_empty.gif new file mode 100644 index 0000000000000000000000000000000000000000..b5cf52378fa5f361ff532bd66a89fb405f23f815 GIT binary patch literal 62 zcmZ?wbhEHb6k-r!n8*ME|NsAQXlMYEia%Kx85o!ubU>mYc?Kr_IsGe7zvW*%XUnbb L&G+VrGgt!vQpOa` literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/tree_folder.gif b/h2/src/main/org/h2/server/web/res/tree_folder.gif new file mode 100644 index 0000000000000000000000000000000000000000..eb129763dcea0dc1916b7e934fdce2fd8770c380 GIT binary patch literal 372 zcmZ?wbhEHb6k-r!xXQrr`}gnn@893v9&~1=$&)8f4jw$XxRCo$zt;Bc+vnx5?P^z= zlf`m!n&I9q)y<94Gt(JwZ!g^1B)cGw zMSgXq@QJAg2YNM*9XqzJMr>9F(~2^IHB}=2ujK##G5PiD*Z=>|`G33g|C9Rv?>o0` z+x8y>W-^ck6o0ZXGBD^d=zw&C{KUXE(P4UlhmKVLi6xgzC4402GB{W!L~2f9POOq~ zU`a4ovhaqB%+vz~Nx8dkFFrrvqSej!Kd!MF9@+A*p|PG#+gVIkwX>^RSE$vAT~}-J zl&QL${d#h`nsevP*X5qctE;hW`3hadnOwRm>o;uF<*#vKOczy ZVbwl)>NG2h3j?FtrOQ`>yuBP5tO0@?i&Ovr literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/tree_index.gif b/h2/src/main/org/h2/server/web/res/tree_index.gif new file mode 100644 index 0000000000000000000000000000000000000000..a84151102a129a661579e4270421e5d2c5e2c04b GIT binary patch literal 152 zcmZ?wbhEHb6k-r!c+A7Fq<&3)SaBhKt! z`5y@={$ycfVBlxaVPs%r5CB337S|6ae~JW|_^Vo76EPBCVG8OBl)0}NxmKIi$o2B4 wNjm-K=5snd5|o+8!0Di0dB}FoInOVeOMj?cU%kZQVUg-BRsERRE({FT03-E90RR91 literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/tree_index_az.gif b/h2/src/main/org/h2/server/web/res/tree_index_az.gif new file mode 100644 index 0000000000000000000000000000000000000000..e9567554696090ca499aac835993134d6a743768 GIT binary patch literal 157 zcmZ?wbhEHb6k-r!c+Abv=Qd?o<*NL$mekrQF|AAe`uB!(Z}yFfm2Rtco!n8ea?$_) z|C?pofg}Vd{$ycfVBlxaVPs%r5CB337OxK{Jy+LqEnJc}(_?#Vn`xBlw4C&*>w>)Z za;W*Q+_Z_a>Te5ovd0u2y@mtI2N*n9#8oeAt_ah;eA2PtfSKwk_v=gv4RiOiPn2L} Gum%97@j)K| literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/tree_info.gif b/h2/src/main/org/h2/server/web/res/tree_info.gif new file mode 100644 index 0000000000000000000000000000000000000000..a9a38f4600fd8e184999dc3282bacb1174f84a6d GIT binary patch literal 267 zcmZ?wbhEHb6k-r!Xpv(0_wWD5PhUTM{>tRm^Y`C>j*uDLvGaLi=JO>j37xQi+l42; z{{H{+`~UYp|KEQ4`t8sEH=n-#{QLjUzyH&=9sT;}|Bt`_zyJOJ```a>fBzr9a^XJ) zQ2fcl$iN`apu@<($RGfO46Gdw)caB*(>RTIJSNS&Z=%vApmEY*s_-HK2^J>i0EMze zXCAb3@)@!meel&&Ma7qAo94s_*L|+RKla4*EMim<66BuU-I_e1E#%#dW=7$i&M)Nc zczMMnSXsryS~5a8n|UR)MI`unLGYlFIxARNO@hDtWaBUIiPTOMfV7_}lvsu=hh(Z;&NfL@qDJM29Jw4q( Jn2m+O8UP#&8=e3F literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/tree_page.gif b/h2/src/main/org/h2/server/web/res/tree_page.gif new file mode 100644 index 0000000000000000000000000000000000000000..42d7318c5d928a2033cb42c72598a131ea6de64d GIT binary patch literal 582 zcmZ?wbhEHb6k-r!c*el+`}gnn@8AFU{r~N^{|65qeDdVU-@kuvy#9aS{{Mgf|9}4Z z|M0{AU%q_(`SWMv)Wg{&OV2(3AD1{GbotxKt1eYFZ#Zz^|ILSQTV|i?T5|Ba{La?4kwX3c6}c&WZ`PygnpJ?rkDyZry# zum9<}^Zxw(U)#BD%KlHsjvf2=@89j`-!lsq`N~Rn2+cXw3F)`cNXo|{M zwn^yh*lA;L&u+56RaJ{?r=8t#bK_Gj;^%BG95Yu?zS_*U>z2K_g$4ipCbgRvpFHC> zdg-Vu`PTd?H;48ok#7ReIGiR-xW0J7!Dc}Y2`z_&_QUOboFWD+3JrbT68dFMM=k~^ HFjxZso}Jb2 literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/tree_plus.gif b/h2/src/main/org/h2/server/web/res/tree_plus.gif new file mode 100644 index 0000000000000000000000000000000000000000..f258ce211a0a19c2ecbcb11170b9a8b35ae2436c GIT binary patch literal 870 zcmZ?wbhEHb6k-r!_|DAG)YR0_(7-SXMnhmkhJfNv7DfgJW(FOQ??HKjfy0@BokPZB z!-9j&9Ku>LGYlFIxARNO@hAj7*xx83m?l#Y_`t1Ma?3%r?0r0t}2Alb$KAIjyzjjq3RlT~!8a0A5WaY5)KL literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/tree_sequences.gif b/h2/src/main/org/h2/server/web/res/tree_sequences.gif new file mode 100644 index 0000000000000000000000000000000000000000..e72a59619e628182742f522e29eae0b7ac9bbbc1 GIT binary patch literal 114 zcmZ?wbhEHb6k-r!IK;*fQ?+W%?kgr~ldQ`Yb?&?EJoVszFi`x-!pOkD&Y;7{z{nr~ zgbd8)6}#^IGuY%LuDgxTh>0Wmduw}w-m->ylZsPkd}@NYquZUcHsQZLl+L5ymsX5t;1(-R&*|??pfS0VOjI!mCch^v`<^zJ!{>P z4F{HOI<#fqnG+Z97BS&av~iHtadMe)ow93pN}%f9pRsp!k!8 zk%7UUL5GonkwE|m8Q4k=%rEfJk?MDINm5uKASTMaxa1&D{^mdphOC=5g4eiAT^MBg z-4r)pEKrzxIbiDQ-LHP^%F?rXP@q(ARBux4q*)343l#+;e@#!P}9u4*Vw$DE?$&WMGhI&|zd?WDo#C23DsB>U}Ah z^D<^J>8xftrNG10z~nzc$MeGtwF3--no4X3gd7|)3{+SgC#mRItYdw`DQ@7vw@=g5 SZ1vu#q^VVB^CmknSOWm7GiEjb literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/tree_types.gif b/h2/src/main/org/h2/server/web/res/tree_types.gif new file mode 100644 index 0000000000000000000000000000000000000000..8e55d90b0e5316719620b16f695629939e5eb8b8 GIT binary patch literal 364 zcmZ?wbhEHb6k-r!SgOXbZ~MNs%GJdwODeLL^)#&w^O%|$v#`2gLu&N=y0SG(=ACgU zS>alyXdyNuDP_;d1|QtocIZcXFGQK zx=#;qpC0G|MAHKO=ZE^w3$0pPP_+h#3Rj-!`A=1#_>+Z^fx(_ZhmnDiK>!FD*g6l) zFYwUe)0gyqX2c@E#?tU1Ni$N{=YYdOSr0?yNgXDK{&2LHSbf->b1(mNe!zr%E5R-T+ljue_7{>xtp$SJo0$c(Z|!5 z?OV6+`s!Vm*6g~ta{IYW2d=N%b7{lg%j@@CTD$A~hP{^>rfib~Dq@|drDtVi0uuWC*F?U@( nfk$0RjcIDVoF(IqdU*?$z5G%KcPPfN?$r=f+k0+8BZD;n)j}J1 literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/tree_users.gif b/h2/src/main/org/h2/server/web/res/tree_users.gif new file mode 100644 index 0000000000000000000000000000000000000000..3c3d84fb59c097ec0a5874a99cc107fcbe449551 GIT binary patch literal 601 zcmZ?wbhEHb6k-r!IF`-T+ljue_7{>xtp$SJo0$c(Z|!5 z?OV6+`s!Vm*6g~ta{IYW2d=N%b7{lg%j@@CTD$A~hP{^>rfidD?ZnH@f3shNa0@97~Xncao@H$V=bW!%f-^qhizKEE!YZ$XZTGVrMdzFA&g}ynwme+2xpw!DolV lhgegxWF{ppY@I0(!!@O`k!J2 literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/server/web/res/tree_view.gif b/h2/src/main/org/h2/server/web/res/tree_view.gif new file mode 100644 index 0000000000000000000000000000000000000000..0ef44930a4df5199809da93a865978cbd0a137f4 GIT binary patch literal 157 zcmZ?wbhEHb6k-r!c+A1Da?`QyX{$qX+Kv43p1gQ-_4EJNzyAOEj}s{VWMO1r;AhZb zWME_v073>9uMa0ZSMRlmR$*}vQEK8hs$q&`Y8IHX++qTU_Og2`ZW~z7vA?h2AT7ev pCj5Fv2rp0j;WI2#tvK0dbDo~e(YZA~XVK!9CNH+!5@KYq1^}j#OKboD literal 0 HcmV?d00001 diff --git a/h2/src/main/org/h2/store/CountingReaderInputStream.java b/h2/src/main/org/h2/store/CountingReaderInputStream.java new file mode 100644 index 0000000..23f4e66 --- /dev/null +++ b/h2/src/main/org/h2/store/CountingReaderInputStream.java @@ -0,0 +1,112 @@ +/* + * 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.store; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.StandardCharsets; + +import org.h2.engine.Constants; + +/** + * An input stream that reads the data from a reader and limits the number of + * bytes that can be read. + */ +public class CountingReaderInputStream extends InputStream { + + private final Reader reader; + + private final CharBuffer charBuffer = + CharBuffer.allocate(Constants.IO_BUFFER_SIZE); + + private final CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder(). + onMalformedInput(CodingErrorAction.REPLACE). + onUnmappableCharacter(CodingErrorAction.REPLACE); + + private ByteBuffer byteBuffer = ByteBuffer.allocate(0); + private long length; + private long remaining; + + public CountingReaderInputStream(Reader reader, long maxLength) { + this.reader = reader; + this.remaining = maxLength; + } + + @Override + public int read(byte[] buff, int offset, int len) throws IOException { + if (!fetch()) { + return -1; + } + len = Math.min(len, byteBuffer.remaining()); + byteBuffer.get(buff, offset, len); + return len; + } + + @Override + public int read() throws IOException { + if (!fetch()) { + return -1; + } + return byteBuffer.get() & 255; + } + + private boolean fetch() throws IOException { + if (byteBuffer != null && byteBuffer.remaining() == 0) { + fillBuffer(); + } + return byteBuffer != null; + } + + private void fillBuffer() throws IOException { + int len = (int) Math.min(charBuffer.capacity() - charBuffer.position(), + remaining); + if (len > 0) { + len = reader.read(charBuffer.array(), charBuffer.position(), len); + } + if (len > 0) { + remaining -= len; + } else { + len = 0; + remaining = 0; + } + length += len; + charBuffer.limit(charBuffer.position() + len); + charBuffer.rewind(); + byteBuffer = ByteBuffer.allocate(Constants.IO_BUFFER_SIZE); + boolean end = remaining == 0; + encoder.encode(charBuffer, byteBuffer, end); + if (end && byteBuffer.position() == 0) { + // EOF + byteBuffer = null; + return; + } + byteBuffer.flip(); + charBuffer.compact(); + charBuffer.flip(); + charBuffer.position(charBuffer.limit()); + } + + /** + * The number of characters read so far (but there might still be some bytes + * in the buffer). + * + * @return the number of characters + */ + public long getLength() { + return length; + } + + @Override + public void close() throws IOException { + reader.close(); + } + +} \ No newline at end of file diff --git a/h2/src/main/org/h2/store/Data.java b/h2/src/main/org/h2/store/Data.java new file mode 100644 index 0000000..67316a0 --- /dev/null +++ b/h2/src/main/org/h2/store/Data.java @@ -0,0 +1,218 @@ +/* + * 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 + * + * The variable size number format code is a port from SQLite, + * but stored in reverse order (least significant bits in the first byte). + */ +package org.h2.store; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Reader; + +import org.h2.engine.Constants; +import org.h2.util.Bits; +import org.h2.util.MathUtils; +import org.h2.util.Utils; + +/** + * This class represents a byte buffer that contains persistent data of a page. + * + * @author Thomas Mueller + * @author Noel Grandin + * @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888 + */ +public class Data { + + /** + * The data itself. + */ + private byte[] data; + + /** + * The current write or read position. + */ + private int pos; + + private Data(byte[] data) { + this.data = data; + } + + /** + * Write an integer at the current position. + * The current position is incremented. + * + * @param x the value + */ + public void writeInt(int x) { + Bits.writeInt(data, pos, x); + pos += 4; + } + + /** + * Read an integer at the current position. + * The current position is incremented. + * + * @return the value + */ + public int readInt() { + int x = Bits.readInt(data, pos); + pos += 4; + return x; + } + + private void writeStringWithoutLength(char[] chars, int len) { + int p = pos; + byte[] buff = data; + for (int i = 0; i < len; i++) { + int c = chars[i]; + if (c < 0x80) { + buff[p++] = (byte) c; + } else if (c >= 0x800) { + buff[p++] = (byte) (0xe0 | (c >> 12)); + buff[p++] = (byte) ((c >> 6) & 0x3f); + buff[p++] = (byte) (c & 0x3f); + } else { + buff[p++] = (byte) (0xc0 | (c >> 6)); + buff[p++] = (byte) (c & 0x3f); + } + } + pos = p; + } + + /** + * Create a new buffer. + * + * @param capacity the initial capacity of the buffer + * @return the buffer + */ + public static Data create(int capacity) { + return new Data(new byte[capacity]); + } + + /** + * Get the current write position of this buffer, which is the current + * length. + * + * @return the length + */ + public int length() { + return pos; + } + + /** + * Get the byte array used for this page. + * + * @return the byte array + */ + public byte[] getBytes() { + return data; + } + + /** + * Set the position to 0. + */ + public void reset() { + pos = 0; + } + + /** + * Append a number of bytes to this buffer. + * + * @param buff the data + * @param off the offset in the data + * @param len the length in bytes + */ + public void write(byte[] buff, int off, int len) { + System.arraycopy(buff, off, data, pos, len); + pos += len; + } + + /** + * Copy a number of bytes to the given buffer from the current position. The + * current position is incremented accordingly. + * + * @param buff the output buffer + * @param off the offset in the output buffer + * @param len the number of bytes to copy + */ + public void read(byte[] buff, int off, int len) { + System.arraycopy(data, pos, buff, off, len); + pos += len; + } + + /** + * Set the current read / write position. + * + * @param pos the new position + */ + public void setPos(int pos) { + this.pos = pos; + } + + /** + * Read one single byte. + * + * @return the value + */ + public byte readByte() { + return data[pos++]; + } + + /** + * Check if there is still enough capacity in the buffer. + * This method extends the buffer if required. + * + * @param plus the number of additional bytes required + */ + public void checkCapacity(int plus) { + if (pos + plus >= data.length) { + // a separate method to simplify inlining + expand(plus); + } + } + + private void expand(int plus) { + // must copy everything, because pos could be 0 and data may be + // still required + data = Utils.copyBytes(data, (data.length + plus) * 2); + } + + /** + * Fill up the buffer with empty space and an (initially empty) checksum + * until the size is a multiple of Constants.FILE_BLOCK_SIZE. + */ + public void fillAligned() { + // 0..6 > 8, 7..14 > 16, 15..22 > 24, ... + int len = MathUtils.roundUpInt(pos + 2, Constants.FILE_BLOCK_SIZE); + pos = len; + if (data.length < len) { + checkCapacity(len - data.length); + } + } + + /** + * Copy a String from a reader to an output stream. + * + * @param source the reader + * @param target the output stream + * @throws IOException on failure + */ + public static void copyString(Reader source, OutputStream target) + throws IOException { + char[] buff = new char[Constants.IO_BUFFER_SIZE]; + Data d = new Data(new byte[3 * Constants.IO_BUFFER_SIZE]); + while (true) { + int l = source.read(buff); + if (l < 0) { + break; + } + d.writeStringWithoutLength(buff, l); + target.write(d.data, 0, d.pos); + d.reset(); + } + } + +} diff --git a/h2/src/main/org/h2/store/DataHandler.java b/h2/src/main/org/h2/store/DataHandler.java new file mode 100644 index 0000000..6c115d4 --- /dev/null +++ b/h2/src/main/org/h2/store/DataHandler.java @@ -0,0 +1,105 @@ +/* + * 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.store; + +import org.h2.message.DbException; +import org.h2.util.SmallLRUCache; +import org.h2.util.TempFileDeleter; +import org.h2.value.CompareMode; + +/** + * A data handler contains a number of callback methods, mostly related to CLOB + * and BLOB handling. The most important implementing class is a database. + */ +public interface DataHandler { + + /** + * Get the database path. + * + * @return the database path + */ + String getDatabasePath(); + + /** + * Open a file at the given location. + * + * @param name the file name + * @param mode the mode + * @param mustExist whether the file must already exist + * @return the file + */ + FileStore openFile(String name, String mode, boolean mustExist); + + /** + * Check if the simulated power failure occurred. + * This call will decrement the countdown. + * + * @throws DbException if the simulated power failure occurred + */ + void checkPowerOff() throws DbException; + + /** + * Check if writing is allowed. + * + * @throws DbException if it is not allowed + */ + void checkWritingAllowed() throws DbException; + + /** + * Get the maximum length of a in-place large object + * + * @return the maximum size + */ + int getMaxLengthInplaceLob(); + + /** + * Get the temp file deleter mechanism. + * + * @return the temp file deleter + */ + TempFileDeleter getTempFileDeleter(); + + /** + * Get the synchronization object for lob operations. + * + * @return the synchronization object + */ + Object getLobSyncObject(); + + /** + * Get the lob file list cache if it is used. + * + * @return the cache or null + */ + SmallLRUCache getLobFileListCache(); + + /** + * Get the lob storage mechanism to use. + * + * @return the lob storage mechanism + */ + LobStorageInterface getLobStorage(); + + /** + * Read from a lob. + * + * @param lobId the lob id + * @param hmac the message authentication code + * @param offset the offset within the lob + * @param buff the target buffer + * @param off the offset within the target buffer + * @param length the number of bytes to read + * @return the number of bytes read + */ + int readLob(long lobId, byte[] hmac, long offset, byte[] buff, int off, int length); + + /** + * Return compare mode. + * + * @return Compare mode. + */ + CompareMode getCompareMode(); +} diff --git a/h2/src/main/org/h2/store/DataReader.java b/h2/src/main/org/h2/store/DataReader.java new file mode 100644 index 0000000..8c552f0 --- /dev/null +++ b/h2/src/main/org/h2/store/DataReader.java @@ -0,0 +1,132 @@ +/* + * 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.store; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; + +/** + * This class is backed by an input stream and supports reading values and + * variable size data. + */ +public class DataReader extends Reader { + + private final InputStream in; + + /** + * Create a new data reader. + * + * @param in the input stream + */ + public DataReader(InputStream in) { + this.in = in; + } + + /** + * Read a byte. + * + * @return the byte + * @throws IOException on failure + */ + public byte readByte() throws IOException { + int x = in.read(); + if (x < 0) { + throw new FastEOFException(); + } + return (byte) x; + } + + /** + * Read a variable size integer. + * + * @return the value + * @throws IOException on failure + */ + public int readVarInt() throws IOException { + int b = readByte(); + if (b >= 0) { + return b; + } + int x = b & 0x7f; + b = readByte(); + if (b >= 0) { + return x | (b << 7); + } + x |= (b & 0x7f) << 7; + b = readByte(); + if (b >= 0) { + return x | (b << 14); + } + x |= (b & 0x7f) << 14; + b = readByte(); + if (b >= 0) { + return x | b << 21; + } + return x | ((b & 0x7f) << 21) | (readByte() << 28); + } + + /** + * Read one character from the input stream. + * + * @return the character + */ + private char readChar() throws IOException { + int x = readByte() & 0xff; + if (x < 0x80) { + return (char) x; + } else if (x >= 0xe0) { + return (char) (((x & 0xf) << 12) + + ((readByte() & 0x3f) << 6) + + (readByte() & 0x3f)); + } else { + return (char) (((x & 0x1f) << 6) + + (readByte() & 0x3f)); + } + } + + @Override + public void close() throws IOException { + // ignore + } + + @Override + public int read(char[] buff, int off, int len) throws IOException { + if (len == 0) { + return 0; + } + int i = 0; + try { + for (; i < len; i++) { + buff[off + i] = readChar(); + } + return len; + } catch (EOFException e) { + if (i == 0) { + return -1; + } + return i; + } + } + + /** + * Constructing such an EOF exception is fast, because the stack trace is + * not filled in. If used in a static context, this will also avoid + * classloader memory leaks. + */ + static class FastEOFException extends EOFException { + + private static final long serialVersionUID = 1L; + + @Override + public synchronized Throwable fillInStackTrace() { + return null; + } + + } + +} diff --git a/h2/src/main/org/h2/store/FileLister.java b/h2/src/main/org/h2/store/FileLister.java new file mode 100644 index 0000000..2fc6f5a --- /dev/null +++ b/h2/src/main/org/h2/store/FileLister.java @@ -0,0 +1,113 @@ +/* + * 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.store; + +import java.nio.channels.FileChannel; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.message.DbException; +import org.h2.message.TraceSystem; +import org.h2.store.fs.FilePath; +import org.h2.store.fs.FileUtils; + +/** + * Utility class to list the files of a database. + */ +public class FileLister { + + private FileLister() { + // utility class + } + + /** + * Try to lock the database, and then unlock it. If this worked, the + * .lock.db file will be removed. + * + * @param files the database files to check + * @param message the text to include in the error message + * @throws SQLException if it failed + */ + public static void tryUnlockDatabase(List files, String message) + throws SQLException { + for (String fileName : files) { + if (fileName.endsWith(Constants.SUFFIX_LOCK_FILE)) { + FileLock lock = new FileLock(new TraceSystem(null), fileName, + Constants.LOCK_SLEEP); + try { + lock.lock(FileLockMethod.FILE); + lock.unlock(); + } catch (DbException e) { + throw DbException.getJdbcSQLException( + ErrorCode.CANNOT_CHANGE_SETTING_WHEN_OPEN_1, + message); + } + } else if (fileName.endsWith(Constants.SUFFIX_MV_FILE)) { + try (FileChannel f = FilePath.get(fileName).open("r")) { + java.nio.channels.FileLock lock = f.tryLock(0, Long.MAX_VALUE, true); + lock.release(); + } catch (Exception e) { + throw DbException.getJdbcSQLException( + ErrorCode.CANNOT_CHANGE_SETTING_WHEN_OPEN_1, e, + message); + } + } + } + } + + /** + * Normalize the directory name. + * + * @param dir the directory (null for the current directory) + * @return the normalized directory name + */ + public static String getDir(String dir) { + if (dir == null || dir.equals("")) { + return "."; + } + return FileUtils.toRealPath(dir); + } + + /** + * Get the list of database files. + * + * @param dir the directory (must be normalized) + * @param db the database name (null for all databases) + * @param all if true, files such as the lock, trace, and lob + * files are included. If false, only data, index, log, + * and lob files are returned + * @return the list of files + */ + 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)) { + boolean ok = false; + if (f.endsWith(Constants.SUFFIX_MV_FILE)) { + ok = true; + } else if (all) { + if (f.endsWith(Constants.SUFFIX_LOCK_FILE)) { + ok = true; + } else if (f.endsWith(Constants.SUFFIX_TEMP_FILE)) { + ok = true; + } else if (f.endsWith(Constants.SUFFIX_TRACE_FILE)) { + ok = true; + } + } + if (ok) { + if (db == null || f.startsWith(start)) { + files.add(f); + } + } + } + return files; + } + +} diff --git a/h2/src/main/org/h2/store/FileLock.java b/h2/src/main/org/h2/store/FileLock.java new file mode 100644 index 0000000..fbe3539 --- /dev/null +++ b/h2/src/main/org/h2/store/FileLock.java @@ -0,0 +1,513 @@ +/* + * 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.store; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.BindException; +import java.net.ConnectException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.Paths; +import java.util.Properties; +import org.h2.Driver; +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.engine.SessionRemote; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.message.TraceSystem; +import org.h2.store.fs.FileUtils; +import org.h2.util.MathUtils; +import org.h2.util.NetUtils; +import org.h2.util.SortedProperties; +import org.h2.util.StringUtils; +import org.h2.value.Transfer; + +/** + * The file lock is used to lock a database so that only one process can write + * to it. It uses a cooperative locking protocol. Usually a .lock.db file is + * used, but locking by creating a socket is supported as well. + */ +public class FileLock implements Runnable { + + private static final String MAGIC = "FileLock"; + private static final String FILE = "file"; + private static final String SOCKET = "socket"; + private static final int RANDOM_BYTES = 16; + private static final int SLEEP_GAP = 25; + private static final int TIME_GRANULARITY = 2000; + + /** + * The lock file name. + */ + private volatile String fileName; + + /** + * The server socket (only used when using the SOCKET mode). + */ + private volatile ServerSocket serverSocket; + + /** + * Whether the file is locked. + */ + private volatile boolean locked; + + /** + * The number of milliseconds to sleep after checking a file. + */ + private final int sleep; + + /** + * The trace object. + */ + private final Trace trace; + + /** + * The last time the lock file was written. + */ + private long lastWrite; + + private String method; + private Properties properties; + private String uniqueId; + private Thread watchdog; + + /** + * Create a new file locking object. + * + * @param traceSystem the trace system to use + * @param fileName the file name + * @param sleep the number of milliseconds to sleep + */ + public FileLock(TraceSystem traceSystem, String fileName, int sleep) { + this.trace = traceSystem == null ? + null : traceSystem.getTrace(Trace.FILE_LOCK); + this.fileName = fileName; + this.sleep = sleep; + } + + /** + * Lock the file if possible. A file may only be locked once. + * + * @param fileLockMethod the file locking method to use + * @throws DbException if locking was not successful + */ + public synchronized void lock(FileLockMethod fileLockMethod) { + checkServer(); + if (locked) { + throw DbException.getInternalError("already locked"); + } + switch (fileLockMethod) { + case FILE: + lockFile(); + break; + case SOCKET: + lockSocket(); + break; + case FS: + case NO: + break; + } + locked = true; + } + + /** + * Unlock the file. The watchdog thread is stopped. This method does nothing + * if the file is already unlocked. + */ + public synchronized void unlock() { + if (!locked) { + return; + } + locked = false; + try { + if (watchdog != null) { + watchdog.interrupt(); + } + } catch (Exception e) { + trace.debug(e, "unlock"); + } + try { + if (fileName != null) { + if (load().equals(properties)) { + FileUtils.delete(fileName); + } + } + if (serverSocket != null) { + serverSocket.close(); + } + } catch (Exception e) { + trace.debug(e, "unlock"); + } finally { + fileName = null; + serverSocket = null; + } + try { + if (watchdog != null) { + watchdog.join(); + } + } catch (Exception e) { + trace.debug(e, "unlock"); + } finally { + watchdog = null; + } + } + + /** + * Add or change a setting to the properties. This call does not save the + * file. + * + * @param key the key + * @param value the value + */ + public void setProperty(String key, String value) { + if (value == null) { + properties.remove(key); + } else { + properties.put(key, value); + } + } + + /** + * Save the lock file. + * + * @return the saved properties + */ + public Properties save() { + try { + try (OutputStream out = FileUtils.newOutputStream(fileName, false)) { + properties.store(out, MAGIC); + } + lastWrite = aggressiveLastModified(fileName); + if (trace.isDebugEnabled()) { + trace.debug("save " + properties); + } + return properties; + } catch (IOException e) { + throw getExceptionFatal("Could not save properties " + fileName, e); + } + } + + /** + * Aggressively read last modified time, to work-around remote filesystems. + * + * @param fileName file name to check + * @return last modified date/time in milliseconds UTC + */ + private static long aggressiveLastModified(String fileName) { + /* + * Some remote filesystem, e.g. SMB on Windows, can cache metadata for + * 5-10 seconds. To work around that, do a one-byte read from the + * underlying file, which has the effect of invalidating the metadata + * cache. + */ + try { + try (FileChannel f = FileChannel.open(Paths.get(fileName), FileUtils.RWS, FileUtils.NO_ATTRIBUTES);) { + ByteBuffer b = ByteBuffer.wrap(new byte[1]); + f.read(b); + } + } catch (IOException ignoreEx) {} + return FileUtils.lastModified(fileName); + } + + private void checkServer() { + Properties prop = load(); + String server = prop.getProperty("server"); + if (server == null) { + return; + } + boolean running = false; + String id = prop.getProperty("id"); + try { + Socket socket = NetUtils.createSocket(server, + Constants.DEFAULT_TCP_PORT, false); + Transfer transfer = new Transfer(null, socket); + transfer.init(); + transfer.writeInt(Constants.TCP_PROTOCOL_VERSION_MIN_SUPPORTED); + transfer.writeInt(Constants.TCP_PROTOCOL_VERSION_MAX_SUPPORTED); + transfer.writeString(null); + transfer.writeString(null); + transfer.writeString(id); + transfer.writeInt(SessionRemote.SESSION_CHECK_KEY); + transfer.flush(); + int state = transfer.readInt(); + if (state == SessionRemote.STATUS_OK) { + running = true; + } + transfer.close(); + socket.close(); + } catch (IOException e) { + return; + } + if (running) { + DbException e = DbException.get( + ErrorCode.DATABASE_ALREADY_OPEN_1, "Server is running"); + throw e.addSQL(server + "/" + id); + } + } + + /** + * Load the properties file. + * + * @return the properties + */ + public Properties load() { + IOException lastException = null; + for (int i = 0; i < 5; i++) { + try { + Properties p2 = SortedProperties.loadProperties(fileName); + if (trace.isDebugEnabled()) { + trace.debug("load " + p2); + } + return p2; + } catch (IOException e) { + lastException = e; + } + } + throw getExceptionFatal( + "Could not load properties " + fileName, lastException); + } + + private void waitUntilOld() { + for (int i = 0; i < 2 * TIME_GRANULARITY / SLEEP_GAP; i++) { + long last = aggressiveLastModified(fileName); + long dist = System.currentTimeMillis() - last; + if (dist < -TIME_GRANULARITY) { + // lock file modified in the future - + // wait for a bit longer than usual + try { + Thread.sleep(2 * (long) sleep); + } catch (Exception e) { + trace.debug(e, "sleep"); + } + return; + } else if (dist > TIME_GRANULARITY) { + return; + } + try { + Thread.sleep(SLEEP_GAP); + } catch (Exception e) { + trace.debug(e, "sleep"); + } + } + throw getExceptionFatal("Lock file recently modified", null); + } + + private void setUniqueId() { + byte[] bytes = MathUtils.secureRandomBytes(RANDOM_BYTES); + String random = StringUtils.convertBytesToHex(bytes); + uniqueId = Long.toHexString(System.currentTimeMillis()) + random; + properties.setProperty("id", uniqueId); + } + + private void lockFile() { + method = FILE; + properties = new SortedProperties(); + properties.setProperty("method", String.valueOf(method)); + setUniqueId(); + FileUtils.createDirectories(FileUtils.getParent(fileName)); + if (!FileUtils.createFile(fileName)) { + waitUntilOld(); + String m2 = load().getProperty("method", FILE); + if (!m2.equals(FILE)) { + throw getExceptionFatal("Unsupported lock method " + m2, null); + } + save(); + sleep(2 * sleep); + if (!load().equals(properties)) { + throw getExceptionAlreadyInUse("Locked by another process: " + fileName); + } + FileUtils.delete(fileName); + if (!FileUtils.createFile(fileName)) { + throw getExceptionFatal("Another process was faster", null); + } + } + save(); + sleep(SLEEP_GAP); + if (!load().equals(properties)) { + fileName = null; + throw getExceptionFatal("Concurrent update", null); + } + locked = true; + watchdog = new Thread(this, "H2 File Lock Watchdog " + fileName); + Driver.setThreadContextClassLoader(watchdog); + watchdog.setDaemon(true); + watchdog.setPriority(Thread.MAX_PRIORITY - 1); + watchdog.start(); + } + + private void lockSocket() { + method = SOCKET; + properties = new SortedProperties(); + properties.setProperty("method", String.valueOf(method)); + setUniqueId(); + // if this returns 127.0.0.1, + // the computer is probably not networked + String ipAddress = NetUtils.getLocalAddress(); + FileUtils.createDirectories(FileUtils.getParent(fileName)); + if (!FileUtils.createFile(fileName)) { + waitUntilOld(); + long read = aggressiveLastModified(fileName); + Properties p2 = load(); + String m2 = p2.getProperty("method", SOCKET); + if (m2.equals(FILE)) { + lockFile(); + return; + } else if (!m2.equals(SOCKET)) { + throw getExceptionFatal("Unsupported lock method " + m2, null); + } + String ip = p2.getProperty("ipAddress", ipAddress); + if (!ipAddress.equals(ip)) { + throw getExceptionAlreadyInUse("Locked by another computer: " + ip); + } + String port = p2.getProperty("port", "0"); + int portId = Integer.parseInt(port); + InetAddress address; + try { + address = InetAddress.getByName(ip); + } catch (UnknownHostException e) { + throw getExceptionFatal("Unknown host " + ip, e); + } + for (int i = 0; i < 3; i++) { + try { + Socket s = new Socket(address, portId); + s.close(); + throw getExceptionAlreadyInUse("Locked by another process"); + } catch (BindException e) { + throw getExceptionFatal("Bind Exception", null); + } catch (ConnectException e) { + trace.debug(e, "socket not connected to port " + port); + } catch (IOException e) { + throw getExceptionFatal("IOException", null); + } + } + if (read != aggressiveLastModified(fileName)) { + throw getExceptionFatal("Concurrent update", null); + } + FileUtils.delete(fileName); + if (!FileUtils.createFile(fileName)) { + throw getExceptionFatal("Another process was faster", null); + } + } + try { + // 0 to use any free port + serverSocket = NetUtils.createServerSocket(0, false); + int port = serverSocket.getLocalPort(); + properties.setProperty("ipAddress", ipAddress); + properties.setProperty("port", Integer.toString(port)); + } catch (Exception e) { + trace.debug(e, "lock"); + serverSocket = null; + lockFile(); + return; + } + save(); + locked = true; + watchdog = new Thread(this, + "H2 File Lock Watchdog (Socket) " + fileName); + watchdog.setDaemon(true); + watchdog.start(); + } + + private static void sleep(int time) { + try { + Thread.sleep(time); + } catch (InterruptedException e) { + throw getExceptionFatal("Sleep interrupted", e); + } + } + + private static DbException getExceptionFatal(String reason, Throwable t) { + return DbException.get( + ErrorCode.ERROR_OPENING_DATABASE_1, t, reason); + } + + private DbException getExceptionAlreadyInUse(String reason) { + DbException e = DbException.get( + ErrorCode.DATABASE_ALREADY_OPEN_1, reason); + if (fileName != null) { + try { + Properties prop = load(); + String server = prop.getProperty("server"); + if (server != null) { + String serverId = server + "/" + prop.getProperty("id"); + e = e.addSQL(serverId); + } + } catch (DbException e2) { + // ignore + } + } + return e; + } + + /** + * Get the file locking method type given a method name. + * + * @param method the method name + * @return the method type + * @throws DbException if the method name is unknown + */ + public static FileLockMethod getFileLockMethod(String method) { + if (method == null || method.equalsIgnoreCase("FILE")) { + return FileLockMethod.FILE; + } else if (method.equalsIgnoreCase("NO")) { + return FileLockMethod.NO; + } else if (method.equalsIgnoreCase("SOCKET")) { + return FileLockMethod.SOCKET; + } else if (method.equalsIgnoreCase("FS")) { + return FileLockMethod.FS; + } else { + throw DbException.get(ErrorCode.UNSUPPORTED_LOCK_METHOD_1, method); + } + } + + public String getUniqueId() { + return uniqueId; + } + + @Override + public void run() { + try { + while (locked && fileName != null) { + // trace.debug("watchdog check"); + try { + if (!FileUtils.exists(fileName) || + aggressiveLastModified(fileName) != lastWrite) { + save(); + } + Thread.sleep(sleep); + } catch (OutOfMemoryError | NullPointerException | InterruptedException e) { + // ignore + } catch (Exception e) { + trace.debug(e, "watchdog"); + } + } + while (true) { + // take a copy so we don't get an NPE between checking it and using it + ServerSocket local = serverSocket; + if (local == null) { + break; + } + try { + trace.debug("watchdog accept"); + Socket s = local.accept(); + s.close(); + } catch (Exception e) { + trace.debug(e, "watchdog"); + } + } + } catch (Exception e) { + trace.debug(e, "watchdog"); + } + trace.debug("watchdog end"); + } + +} diff --git a/h2/src/main/org/h2/store/FileLockMethod.java b/h2/src/main/org/h2/store/FileLockMethod.java new file mode 100644 index 0000000..c225f4a --- /dev/null +++ b/h2/src/main/org/h2/store/FileLockMethod.java @@ -0,0 +1,29 @@ +/* + * 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.store; + +public enum FileLockMethod { + /** + * This locking method means no locking is used at all. + */ + NO, + + /** + * This locking method means the cooperative file locking protocol should be + * used. + */ + FILE, + + /** + * This locking method means a socket is created on the given machine. + */ + SOCKET, + + /** + * Use the file system to lock the file; don't use a separate lock file. + */ + FS +} diff --git a/h2/src/main/org/h2/store/FileStore.java b/h2/src/main/org/h2/store/FileStore.java new file mode 100644 index 0000000..adfd343 --- /dev/null +++ b/h2/src/main/org/h2/store/FileStore.java @@ -0,0 +1,512 @@ +/* + * 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.store; + +import java.io.IOException; +import java.lang.ref.Reference; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.engine.SysProperties; +import org.h2.message.DbException; +import org.h2.security.SecureFileStore; +import org.h2.store.fs.FileUtils; + +/** + * This class is an abstraction of a random access file. + * Each file contains a magic header, and reading / writing is done in blocks. + * See also {@link SecureFileStore} + */ +public class FileStore { + + /** + * The size of the file header in bytes. + */ + public static final int HEADER_LENGTH = 3 * Constants.FILE_BLOCK_SIZE; + + /** + * The magic file header. + */ + private static final String HEADER = + "-- H2 0.5/B -- ".substring(0, Constants.FILE_BLOCK_SIZE - 1) + "\n"; + + private static final boolean ASSERT; + + static { + boolean a = false; + // Intentional side-effect + assert a = true; + ASSERT = a; + } + + /** + * The file name. + */ + protected String name; + + /** + * The callback object is responsible to check access rights, and free up + * disk space if required. + */ + private final DataHandler handler; + + private FileChannel file; + private long filePos; + private long fileLength; + private Reference autoDeleteReference; + private boolean checkedWriting = true; + private final String mode; + private java.nio.channels.FileLock lock; + + /** + * Create a new file using the given settings. + * + * @param handler the callback object + * @param name the file name + * @param mode the access mode ("r", "rw", "rws", "rwd") + */ + protected FileStore(DataHandler handler, String name, String mode) { + this.handler = handler; + this.name = name; + try { + boolean exists = FileUtils.exists(name); + if (exists && !FileUtils.canWrite(name)) { + mode = "r"; + } else { + FileUtils.createDirectories(FileUtils.getParent(name)); + } + file = FileUtils.open(name, mode); + if (exists) { + fileLength = file.size(); + } + } catch (IOException e) { + throw DbException.convertIOException( + e, "name: " + name + " mode: " + mode); + } + this.mode = mode; + } + + /** + * Open a non encrypted file store with the given settings. + * + * @param handler the data handler + * @param name the file name + * @param mode the access mode (r, rw, rws, rwd) + * @return the created object + */ + public static FileStore open(DataHandler handler, String name, String mode) { + return open(handler, name, mode, null, null, 0); + } + + /** + * Open an encrypted file store with the given settings. + * + * @param handler the data handler + * @param name the file name + * @param mode the access mode (r, rw, rws, rwd) + * @param cipher the name of the cipher algorithm + * @param key the encryption key + * @return the created object + */ + public static FileStore open(DataHandler handler, String name, String mode, + String cipher, byte[] key) { + return open(handler, name, mode, cipher, key, + Constants.ENCRYPTION_KEY_HASH_ITERATIONS); + } + + /** + * Open an encrypted file store with the given settings. + * + * @param handler the data handler + * @param name the file name + * @param mode the access mode (r, rw, rws, rwd) + * @param cipher the name of the cipher algorithm + * @param key the encryption key + * @param keyIterations the number of iterations the key should be hashed + * @return the created object + */ + public static FileStore open(DataHandler handler, String name, String mode, + String cipher, byte[] key, int keyIterations) { + FileStore store; + if (cipher == null) { + store = new FileStore(handler, name, mode); + } else { + store = new SecureFileStore(handler, name, mode, + cipher, key, keyIterations); + } + return store; + } + + /** + * Generate the random salt bytes if required. + * + * @return the random salt or the magic + */ + protected byte[] generateSalt() { + return HEADER.getBytes(StandardCharsets.UTF_8); + } + + /** + * Initialize the key using the given salt. + * + * @param salt the salt + */ + @SuppressWarnings("unused") + protected void initKey(byte[] salt) { + // do nothing + } + + public void setCheckedWriting(boolean value) { + this.checkedWriting = value; + } + + private void checkWritingAllowed() { + if (handler != null && checkedWriting) { + handler.checkWritingAllowed(); + } + } + + private void checkPowerOff() { + if (handler != null) { + handler.checkPowerOff(); + } + } + + /** + * Initialize the file. This method will write or check the file header if + * required. + */ + public void init() { + int len = Constants.FILE_BLOCK_SIZE; + byte[] salt; + byte[] magic = HEADER.getBytes(StandardCharsets.UTF_8); + if (length() < HEADER_LENGTH) { + // write unencrypted + checkedWriting = false; + writeDirect(magic, 0, len); + salt = generateSalt(); + writeDirect(salt, 0, len); + initKey(salt); + // write (maybe) encrypted + write(magic, 0, len); + checkedWriting = true; + } else { + // read unencrypted + seek(0); + byte[] buff = new byte[len]; + readFullyDirect(buff, 0, len); + if (!Arrays.equals(buff, magic)) { + throw DbException.get(ErrorCode.FILE_VERSION_ERROR_1, name); + } + salt = new byte[len]; + readFullyDirect(salt, 0, len); + initKey(salt); + // read (maybe) encrypted + readFully(buff, 0, Constants.FILE_BLOCK_SIZE); + if (!Arrays.equals(buff, magic)) { + throw DbException.get(ErrorCode.FILE_ENCRYPTION_ERROR_1, name); + } + } + } + + /** + * Close the file. + */ + public void close() { + if (file != null) { + try { + trace("close", name, file); + file.close(); + } catch (IOException e) { + throw DbException.convertIOException(e, name); + } finally { + file = null; + } + } + } + + /** + * Close the file without throwing any exceptions. Exceptions are simply + * ignored. + */ + public void closeSilently() { + try { + close(); + } catch (Exception e) { + // ignore + } + } + + /** + * Close the file (ignoring exceptions) and delete the file. + */ + public void closeAndDeleteSilently() { + if (file != null) { + closeSilently(); + handler.getTempFileDeleter().deleteFile(autoDeleteReference, name); + name = null; + } + } + + /** + * Read a number of bytes without decrypting. + * + * @param b the target buffer + * @param off the offset + * @param len the number of bytes to read + */ + public void readFullyDirect(byte[] b, int off, int len) { + readFully(b, off, len); + } + + /** + * Read a number of bytes. + * + * @param b the target buffer + * @param off the offset + * @param len the number of bytes to read + */ + public void readFully(byte[] b, int off, int len) { + if (len < 0 || len % Constants.FILE_BLOCK_SIZE != 0) { + throw DbException.getInternalError("unaligned read " + name + " len " + len); + } + checkPowerOff(); + try { + FileUtils.readFully(file, ByteBuffer.wrap(b, off, len)); + } catch (IOException e) { + throw DbException.convertIOException(e, name); + } + filePos += len; + } + + /** + * Go to the specified file location. + * + * @param pos the location + */ + public void seek(long pos) { + if (pos % Constants.FILE_BLOCK_SIZE != 0) { + throw DbException.getInternalError("unaligned seek " + name + " pos " + pos); + } + try { + if (pos != filePos) { + file.position(pos); + filePos = pos; + } + } catch (IOException e) { + throw DbException.convertIOException(e, name); + } + } + + /** + * Write a number of bytes without encrypting. + * + * @param b the source buffer + * @param off the offset + * @param len the number of bytes to write + */ + protected void writeDirect(byte[] b, int off, int len) { + write(b, off, len); + } + + /** + * Write a number of bytes. + * + * @param b the source buffer + * @param off the offset + * @param len the number of bytes to write + */ + public void write(byte[] b, int off, int len) { + if (len < 0 || len % Constants.FILE_BLOCK_SIZE != 0) { + throw DbException.getInternalError("unaligned write " + name + " len " + len); + } + checkWritingAllowed(); + checkPowerOff(); + try { + FileUtils.writeFully(file, ByteBuffer.wrap(b, off, len)); + } catch (IOException e) { + closeFileSilently(); + throw DbException.convertIOException(e, name); + } + filePos += len; + fileLength = Math.max(filePos, fileLength); + } + + /** + * Set the length of the file. This will expand or shrink the file. + * + * @param newLength the new file size + */ + public void setLength(long newLength) { + if (newLength % Constants.FILE_BLOCK_SIZE != 0) { + throw DbException.getInternalError("unaligned setLength " + name + " pos " + newLength); + } + checkPowerOff(); + checkWritingAllowed(); + try { + if (newLength > fileLength) { + long pos = filePos; + file.position(newLength - 1); + FileUtils.writeFully(file, ByteBuffer.wrap(new byte[1])); + file.position(pos); + } else { + file.truncate(newLength); + } + fileLength = newLength; + } catch (IOException e) { + closeFileSilently(); + throw DbException.convertIOException(e, name); + } + } + + /** + * Get the file size in bytes. + * + * @return the file size + */ + public long length() { + long len = fileLength; + if (ASSERT) { + try { + len = file.size(); + if (len != fileLength) { + throw DbException.getInternalError("file " + name + " length " + len + " expected " + fileLength); + } + if (len % Constants.FILE_BLOCK_SIZE != 0) { + long newLength = len + Constants.FILE_BLOCK_SIZE - + (len % Constants.FILE_BLOCK_SIZE); + file.truncate(newLength); + fileLength = newLength; + throw DbException.getInternalError("unaligned file length " + name + " len " + len); + } + } catch (IOException e) { + throw DbException.convertIOException(e, name); + } + } + return len; + } + + /** + * Get the current location of the file pointer. + * + * @return the location + */ + public long getFilePointer() { + if (ASSERT) { + try { + if (file.position() != filePos) { + throw DbException.getInternalError(file.position() + " " + filePos); + } + } catch (IOException e) { + throw DbException.convertIOException(e, name); + } + } + return filePos; + } + + /** + * Call fsync. Depending on the operating system and hardware, this may or + * may not in fact write the changes. + */ + public void sync() { + try { + file.force(true); + } catch (IOException e) { + closeFileSilently(); + throw DbException.convertIOException(e, name); + } + } + + /** + * Automatically delete the file once it is no longer in use. + */ + public void autoDelete() { + if (autoDeleteReference == null) { + autoDeleteReference = handler.getTempFileDeleter().addFile(name, this); + } + } + + /** + * No longer automatically delete the file once it is no longer in use. + */ + public void stopAutoDelete() { + handler.getTempFileDeleter().stopAutoDelete(autoDeleteReference, name); + autoDeleteReference = null; + } + + /** + * Close the file. The file may later be re-opened using openFile. + * @throws IOException on failure + */ + public void closeFile() throws IOException { + file.close(); + file = null; + } + + /** + * Just close the file, without setting the reference to null. This method + * is called when writing failed. The reference is not set to null so that + * there are no NullPointerExceptions later on. + */ + private void closeFileSilently() { + try { + file.close(); + } catch (IOException e) { + // ignore + } + } + + /** + * Re-open the file. The file pointer will be reset to the previous + * location. + * @throws IOException on failure + */ + public void openFile() throws IOException { + if (file == null) { + file = FileUtils.open(name, mode); + file.position(filePos); + } + } + + private static void trace(String method, String fileName, Object o) { + if (SysProperties.TRACE_IO) { + System.out.println("FileStore." + method + " " + fileName + " " + o); + } + } + + /** + * Try to lock the file. + * + * @return true if successful + */ + public synchronized boolean tryLock() { + try { + lock = file.tryLock(); + return lock != null; + } catch (Exception e) { + // ignore OverlappingFileLockException + return false; + } + } + + /** + * Release the file lock. + */ + public synchronized void releaseLock() { + if (file != null && lock != null) { + try { + lock.release(); + } catch (Exception e) { + // ignore + } + lock = null; + } + } + +} diff --git a/h2/src/main/org/h2/store/FileStoreInputStream.java b/h2/src/main/org/h2/store/FileStoreInputStream.java new file mode 100644 index 0000000..87b9fdb --- /dev/null +++ b/h2/src/main/org/h2/store/FileStoreInputStream.java @@ -0,0 +1,159 @@ +/* + * 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.store; + +import java.io.IOException; +import java.io.InputStream; +import org.h2.engine.Constants; +import org.h2.message.DbException; +import org.h2.tools.CompressTool; +import org.h2.util.Utils; + +/** + * An input stream that is backed by a file store. + */ +public class FileStoreInputStream extends InputStream { + + private FileStore store; + private final Data page; + private int remainingInBuffer; + private final CompressTool compress; + private boolean endOfFile; + private final boolean alwaysClose; + + public FileStoreInputStream(FileStore store, boolean compression, boolean alwaysClose) { + this.store = store; + this.alwaysClose = alwaysClose; + if (compression) { + compress = CompressTool.getInstance(); + } else { + compress = null; + } + page = Data.create(Constants.FILE_BLOCK_SIZE); + try { + if (store.length() <= FileStore.HEADER_LENGTH) { + close(); + } else { + fillBuffer(); + } + } catch (IOException e) { + throw DbException.convertIOException(e, store.name); + } + } + + @Override + public int available() { + return remainingInBuffer <= 0 ? 0 : remainingInBuffer; + } + + @Override + public int read(byte[] buff) throws IOException { + return read(buff, 0, buff.length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (len == 0) { + return 0; + } + int read = 0; + while (len > 0) { + int r = readBlock(b, off, len); + if (r < 0) { + break; + } + read += r; + off += r; + len -= r; + } + return read == 0 ? -1 : read; + } + + private int readBlock(byte[] buff, int off, int len) throws IOException { + fillBuffer(); + if (endOfFile) { + return -1; + } + int l = Math.min(remainingInBuffer, len); + page.read(buff, off, l); + remainingInBuffer -= l; + return l; + } + + private void fillBuffer() throws IOException { + if (remainingInBuffer > 0 || endOfFile) { + return; + } + page.reset(); + store.openFile(); + if (store.length() == store.getFilePointer()) { + close(); + return; + } + store.readFully(page.getBytes(), 0, Constants.FILE_BLOCK_SIZE); + page.reset(); + remainingInBuffer = page.readInt(); + if (remainingInBuffer < 0) { + close(); + return; + } + page.checkCapacity(remainingInBuffer); + // get the length to read + if (compress != null) { + page.checkCapacity(Integer.BYTES); + page.readInt(); + } + page.setPos(page.length() + remainingInBuffer); + page.fillAligned(); + int len = page.length() - Constants.FILE_BLOCK_SIZE; + page.reset(); + page.readInt(); + store.readFully(page.getBytes(), Constants.FILE_BLOCK_SIZE, len); + page.reset(); + page.readInt(); + if (compress != null) { + int uncompressed = page.readInt(); + byte[] buff = Utils.newBytes(remainingInBuffer); + page.read(buff, 0, remainingInBuffer); + page.reset(); + page.checkCapacity(uncompressed); + CompressTool.expand(buff, page.getBytes(), 0); + remainingInBuffer = uncompressed; + } + if (alwaysClose) { + store.closeFile(); + } + } + + @Override + public void close() { + if (store != null) { + try { + store.close(); + endOfFile = true; + } finally { + store = null; + } + } + } + + @Override + protected void finalize() { + close(); + } + + @Override + public int read() throws IOException { + fillBuffer(); + if (endOfFile) { + return -1; + } + int i = page.readByte() & 0xff; + remainingInBuffer--; + return i; + } + +} diff --git a/h2/src/main/org/h2/store/FileStoreOutputStream.java b/h2/src/main/org/h2/store/FileStoreOutputStream.java new file mode 100644 index 0000000..a414443 --- /dev/null +++ b/h2/src/main/org/h2/store/FileStoreOutputStream.java @@ -0,0 +1,84 @@ +/* + * 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.store; + +import java.io.OutputStream; +import java.util.Arrays; + +import org.h2.engine.Constants; +import org.h2.tools.CompressTool; + +/** + * An output stream that is backed by a file store. + */ +public class FileStoreOutputStream extends OutputStream { + private FileStore store; + private final Data page; + private final String compressionAlgorithm; + private final CompressTool compress; + private final byte[] buffer = { 0 }; + + public FileStoreOutputStream(FileStore store, String compressionAlgorithm) { + this.store = store; + if (compressionAlgorithm != null) { + this.compress = CompressTool.getInstance(); + this.compressionAlgorithm = compressionAlgorithm; + } else { + this.compress = null; + this.compressionAlgorithm = null; + } + page = Data.create(Constants.FILE_BLOCK_SIZE); + } + + @Override + public void write(int b) { + buffer[0] = (byte) b; + write(buffer); + } + + @Override + public void write(byte[] buff) { + write(buff, 0, buff.length); + } + + @Override + public void write(byte[] buff, int off, int len) { + if (len > 0) { + page.reset(); + if (compress != null) { + if (off != 0 || len != buff.length) { + buff = Arrays.copyOfRange(buff, off, off + len); + off = 0; + } + int uncompressed = len; + buff = compress.compress(buff, compressionAlgorithm); + len = buff.length; + page.checkCapacity(2 * Integer.BYTES + len); + page.writeInt(len); + page.writeInt(uncompressed); + page.write(buff, off, len); + } else { + page.checkCapacity(Integer.BYTES + len); + page.writeInt(len); + page.write(buff, off, len); + } + page.fillAligned(); + store.write(page.getBytes(), 0, page.length()); + } + } + + @Override + public void close() { + if (store != null) { + try { + store.close(); + } finally { + store = null; + } + } + } + +} diff --git a/h2/src/main/org/h2/store/InDoubtTransaction.java b/h2/src/main/org/h2/store/InDoubtTransaction.java new file mode 100644 index 0000000..33a1292 --- /dev/null +++ b/h2/src/main/org/h2/store/InDoubtTransaction.java @@ -0,0 +1,71 @@ +/* + * 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.store; + +import org.h2.message.DbException; + +/** + * Represents an in-doubt transaction (a transaction in the prepare phase). + */ +public interface InDoubtTransaction { + + /** + * The transaction state meaning this transaction is not committed yet, but + * also not rolled back (in-doubt). + */ + int IN_DOUBT = 0; + + /** + * The transaction state meaning this transaction is committed. + */ + int COMMIT = 1; + + /** + * The transaction state meaning this transaction is rolled back. + */ + int ROLLBACK = 2; + + /** + * Change the state of this transaction. + * This will also update the transaction log. + * + * @param state the new state + */ + void setState(int state); + + /** + * Get the state of this transaction. + * + * @return the transaction state + */ + int getState(); + + /** + * Get the state of this transaction as a text. + * + * @return the transaction state text + */ + default String getStateDescription() { + int state = getState(); + switch (state) { + case 0: + return "IN_DOUBT"; + case 1: + return "COMMIT"; + case 2: + return "ROLLBACK"; + default: + throw DbException.getInternalError("state=" + state); + } + } + + /** + * Get the name of the transaction. + * + * @return the transaction name + */ + String getTransactionName(); +} diff --git a/h2/src/main/org/h2/store/LobStorageFrontend.java b/h2/src/main/org/h2/store/LobStorageFrontend.java new file mode 100644 index 0000000..5c57ace --- /dev/null +++ b/h2/src/main/org/h2/store/LobStorageFrontend.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.store; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import org.h2.engine.SessionRemote; +import org.h2.value.ValueBlob; +import org.h2.value.ValueClob; +import org.h2.value.ValueLob; + +/** + * This factory creates in-memory objects and temporary files. It is used on the + * client side. + */ +public class LobStorageFrontend implements LobStorageInterface { + + /** + * The table id for session variables (LOBs not assigned to a table). + */ + public static final int TABLE_ID_SESSION_VARIABLE = -1; + + /** + * The table id for temporary objects (not assigned to any object). + */ + public static final int TABLE_TEMP = -2; + + /** + * The table id for result sets. + */ + public static final int TABLE_RESULT = -3; + + private final SessionRemote sessionRemote; + + public LobStorageFrontend(SessionRemote handler) { + this.sessionRemote = handler; + } + + @Override + public void removeLob(ValueLob lob) { + // not stored in the database + } + + @Override + public InputStream getInputStream(long lobId, + long byteCount) throws IOException { + // this method is only implemented on the server side of a TCP connection + throw new IllegalStateException(); + } + + @Override + public InputStream getInputStream(long lobId, int tableId, long byteCount) throws IOException { + // this method is only implemented on the server side of a TCP + // connection + throw new IllegalStateException(); + } + + @Override + public boolean isReadOnly() { + return false; + } + + @Override + public ValueLob copyLob(ValueLob old, int tableId) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeAllForTable(int tableId) { + throw new UnsupportedOperationException(); + } + + @Override + public ValueBlob createBlob(InputStream in, long maxLength) { + // need to use a temp file, because the input stream could come from + // the same database, which would create a weird situation (trying + // to read a block while writing something) + return ValueBlob.createTempBlob(in, maxLength, sessionRemote); + } + + /** + * Create a CLOB object. + * + * @param reader the reader + * @param maxLength the maximum length (-1 if not known) + * @return the LOB + */ + @Override + public ValueClob createClob(Reader reader, long maxLength) { + // need to use a temp file, because the input stream could come from + // the same database, which would create a weird situation (trying + // to read a block while writing something) + return ValueClob.createTempClob(reader, maxLength, sessionRemote); + } +} diff --git a/h2/src/main/org/h2/store/LobStorageInterface.java b/h2/src/main/org/h2/store/LobStorageInterface.java new file mode 100644 index 0000000..b750c5a --- /dev/null +++ b/h2/src/main/org/h2/store/LobStorageInterface.java @@ -0,0 +1,89 @@ +/* + * 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.store; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; + +import org.h2.value.ValueBlob; +import org.h2.value.ValueClob; +import org.h2.value.ValueLob; + +/** + * A mechanism to store and retrieve lob data. + */ +public interface LobStorageInterface { + + /** + * Create a CLOB object. + * + * @param reader the reader + * @param maxLength the maximum length (-1 if not known) + * @return the LOB + */ + ValueClob createClob(Reader reader, long maxLength); + + /** + * Create a BLOB object. + * + * @param in the input stream + * @param maxLength the maximum length (-1 if not known) + * @return the LOB + */ + ValueBlob createBlob(InputStream in, long maxLength); + + /** + * Copy a lob. + * + * @param old the old lob + * @param tableId the new table id + * @return the new lob + */ + ValueLob copyLob(ValueLob old, int tableId); + + /** + * Get the input stream for the given lob, only called on server side of a TCP connection. + * + * @param lobId the lob id + * @param byteCount the number of bytes to read, or -1 if not known + * @return the stream + * @throws IOException on failure + */ + InputStream getInputStream(long lobId, long byteCount) throws IOException; + + /** + * Get the input stream for the given lob + * + * @param lobId the lob id + * @param tableId the able id + * @param byteCount the number of bytes to read, or -1 if not known + * @return the stream + * @throws IOException on failure + */ + InputStream getInputStream(long lobId, int tableId, long byteCount) throws IOException; + + /** + * Delete a LOB (from the database, if it is stored there). + * + * @param lob the lob + */ + void removeLob(ValueLob lob); + + /** + * Remove all LOBs for this table. + * + * @param tableId the table id + */ + void removeAllForTable(int tableId); + + /** + * Whether the storage is read-only + * + * @return true if yes + */ + boolean isReadOnly(); +} diff --git a/h2/src/main/org/h2/store/LobStorageRemoteInputStream.java b/h2/src/main/org/h2/store/LobStorageRemoteInputStream.java new file mode 100644 index 0000000..06e1d86 --- /dev/null +++ b/h2/src/main/org/h2/store/LobStorageRemoteInputStream.java @@ -0,0 +1,70 @@ +/* + * 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.store; + +import java.io.IOException; +import java.io.InputStream; +import org.h2.engine.SessionRemote; +import org.h2.message.DbException; +import org.h2.mvstore.DataUtils; + +/** + * An input stream used by the client side of a tcp connection to fetch LOB data + * on demand from the server. + */ +public class LobStorageRemoteInputStream extends InputStream { + + private final SessionRemote sessionRemote; + + /** + * The lob id. + */ + private final long lobId; + + private final byte[] hmac; + + /** + * The position. + */ + private long pos; + + public LobStorageRemoteInputStream(SessionRemote handler, long lobId, byte[] hmac) { + this.sessionRemote = handler; + this.lobId = lobId; + this.hmac = hmac; + } + + @Override + public int read() throws IOException { + byte[] buff = new byte[1]; + int len = read(buff, 0, 1); + return len < 0 ? len : (buff[0] & 255); + } + + @Override + public int read(byte[] buff) throws IOException { + return read(buff, 0, buff.length); + } + + @Override + public int read(byte[] buff, int off, int length) throws IOException { + assert(length >= 0); + if (length == 0) { + return 0; + } + try { + length = sessionRemote.readLob(lobId, hmac, pos, buff, off, length); + } catch (DbException e) { + throw DataUtils.convertToIOException(e); + } + if (length == 0) { + return -1; + } + pos += length; + return length; + } + +} diff --git a/h2/src/main/org/h2/store/RangeInputStream.java b/h2/src/main/org/h2/store/RangeInputStream.java new file mode 100644 index 0000000..d4401d4 --- /dev/null +++ b/h2/src/main/org/h2/store/RangeInputStream.java @@ -0,0 +1,102 @@ +/* + * 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.store; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.h2.util.IOUtils; + +/** + * Input stream that reads only a specified range from the source stream. + */ +public final class RangeInputStream extends FilterInputStream { + private long limit; + + /** + * Creates new instance of range input stream. + * + * @param in + * source stream + * @param offset + * offset of the range + * @param limit + * length of the range + * @throws IOException + * on I/O exception during seeking to the specified offset + */ + public RangeInputStream(InputStream in, long offset, long limit) throws IOException { + super(in); + this.limit = limit; + IOUtils.skipFully(in, offset); + } + + @Override + public int read() throws IOException { + if (limit <= 0) { + return -1; + } + int b = in.read(); + if (b >= 0) { + limit--; + } + return b; + } + + @Override + public int read(byte b[], int off, int len) throws IOException { + if (limit <= 0) { + return -1; + } + if (len > limit) { + len = (int) limit; + } + int cnt = in.read(b, off, len); + if (cnt > 0) { + limit -= cnt; + } + return cnt; + } + + @Override + public long skip(long n) throws IOException { + if (n > limit) { + n = (int) limit; + } + n = in.skip(n); + limit -= n; + return n; + } + + @Override + public int available() throws IOException { + int cnt = in.available(); + if (cnt > limit) { + return (int) limit; + } + return cnt; + } + + @Override + public void close() throws IOException { + in.close(); + } + + @Override + public void mark(int readlimit) { + } + + @Override + public synchronized void reset() throws IOException { + throw new IOException("mark/reset not supported"); + } + + @Override + public boolean markSupported() { + return false; + } +} diff --git a/h2/src/main/org/h2/store/RangeReader.java b/h2/src/main/org/h2/store/RangeReader.java new file mode 100644 index 0000000..d0a6e0d --- /dev/null +++ b/h2/src/main/org/h2/store/RangeReader.java @@ -0,0 +1,103 @@ +/* + * 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.store; + +import java.io.IOException; +import java.io.Reader; + +import org.h2.util.IOUtils; + +/** + * Reader that reads only a specified range from the source reader. + */ +public final class RangeReader extends Reader { + private final Reader r; + + private long limit; + + /** + * Creates new instance of range reader. + * + * @param r + * source reader + * @param offset + * offset of the range + * @param limit + * length of the range + * @throws IOException + * on I/O exception during seeking to the specified offset + */ + public RangeReader(Reader r, long offset, long limit) throws IOException { + this.r = r; + this.limit = limit; + IOUtils.skipFully(r, offset); + } + + @Override + public int read() throws IOException { + if (limit <= 0) { + return -1; + } + int c = r.read(); + if (c >= 0) { + limit--; + } + return c; + } + + @Override + public int read(char cbuf[], int off, int len) throws IOException { + if (limit <= 0) { + return -1; + } + if (len > limit) { + len = (int) limit; + } + int cnt = r.read(cbuf, off, len); + if (cnt > 0) { + limit -= cnt; + } + return cnt; + } + + @Override + public long skip(long n) throws IOException { + if (n > limit) { + n = (int) limit; + } + n = r.skip(n); + limit -= n; + return n; + } + + @Override + public boolean ready() throws IOException { + if (limit > 0) { + return r.ready(); + } + return false; + } + + @Override + public boolean markSupported() { + return false; + } + + @Override + public void mark(int readAheadLimit) throws IOException { + throw new IOException("mark() not supported"); + } + + @Override + public void reset() throws IOException { + throw new IOException("reset() not supported"); + } + + @Override + public void close() throws IOException { + r.close(); + } +} diff --git a/h2/src/main/org/h2/store/RecoverTester.java b/h2/src/main/org/h2/store/RecoverTester.java new file mode 100644 index 0000000..3c4c94e --- /dev/null +++ b/h2/src/main/org/h2/store/RecoverTester.java @@ -0,0 +1,176 @@ +/* + * 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.store; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.sql.SQLException; +import java.util.HashSet; + +import org.h2.api.ErrorCode; +import org.h2.engine.ConnectionInfo; +import org.h2.engine.Constants; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.store.fs.FileUtils; +import org.h2.store.fs.Recorder; +import org.h2.store.fs.rec.FilePathRec; +import org.h2.tools.Recover; +import org.h2.util.IOUtils; +import org.h2.util.StringUtils; +import org.h2.util.Utils; + +/** + * A tool that simulates a crash while writing to the database, and then + * verifies the database doesn't get corrupt. + */ +public class RecoverTester implements Recorder { + + private static final RecoverTester instance = new RecoverTester(); + + private String testDatabase = "memFS:reopen"; + private int writeCount = Utils.getProperty("h2.recoverTestOffset", 0); + private int testEvery = Utils.getProperty("h2.recoverTest", 64); + private final long maxFileSize = Utils.getProperty( + "h2.recoverTestMaxFileSize", Integer.MAX_VALUE) * 1024L * 1024; + private int verifyCount; + private final HashSet knownErrors = new HashSet<>(); + private volatile boolean testing; + + /** + * Initialize the recover test. + * + * @param recoverTest the value of the recover test parameter + */ + public static synchronized void init(String recoverTest) { + if (StringUtils.isNumber(recoverTest)) { + instance.setTestEvery(Integer.parseInt(recoverTest)); + } + FilePathRec.setRecorder(instance); + } + + @Override + public void log(int op, String fileName, byte[] data, long x) { + if (op != Recorder.WRITE && op != Recorder.TRUNCATE) { + return; + } + if (!fileName.endsWith(Constants.SUFFIX_MV_FILE)) { + return; + } + writeCount++; + if ((writeCount % testEvery) != 0) { + return; + } + if (FileUtils.size(fileName) > maxFileSize) { + // System.out.println(fileName + " " + IOUtils.length(fileName)); + return; + } + if (testing) { + // avoid deadlocks + return; + } + testing = true; + PrintWriter out = null; + try { + out = new PrintWriter( + new OutputStreamWriter( + FileUtils.newOutputStream(fileName + ".log", true))); + testDatabase(fileName, out); + } catch (IOException e) { + throw DbException.convertIOException(e, null); + } finally { + IOUtils.closeSilently(out); + testing = false; + } + } + + private synchronized void testDatabase(String fileName, PrintWriter out) { + out.println("+ write #" + writeCount + " verify #" + verifyCount); + try { + IOUtils.copyFiles(fileName, testDatabase + Constants.SUFFIX_MV_FILE); + verifyCount++; + // avoid using the Engine class to avoid deadlocks + ConnectionInfo ci = new ConnectionInfo("jdbc:h2:" + testDatabase + + ";FILE_LOCK=NO;TRACE_LEVEL_FILE=0", null, "", ""); + Database database = new Database(ci, null); + // close the database + SessionLocal sysSession = database.getSystemSession(); + sysSession.prepare("script to '" + testDatabase + ".sql'").query(0); + sysSession.prepare("shutdown immediately").update(); + database.removeSession(null); + // everything OK - return + return; + } catch (DbException e) { + SQLException e2 = DbException.toSQLException(e); + int errorCode = e2.getErrorCode(); + if (errorCode == ErrorCode.WRONG_USER_OR_PASSWORD) { + return; + } else if (errorCode == ErrorCode.FILE_ENCRYPTION_ERROR_1) { + return; + } + e.printStackTrace(System.out); + } catch (Exception e) { + // failed + int errorCode = 0; + if (e instanceof SQLException) { + errorCode = ((SQLException) e).getErrorCode(); + } + if (errorCode == ErrorCode.WRONG_USER_OR_PASSWORD) { + return; + } else if (errorCode == ErrorCode.FILE_ENCRYPTION_ERROR_1) { + return; + } + e.printStackTrace(System.out); + } + out.println("begin ------------------------------ " + writeCount); + try { + Recover.execute(fileName.substring(0, fileName.lastIndexOf('/')), null); + } catch (SQLException e) { + // ignore + } + testDatabase += "X"; + try { + IOUtils.copyFiles(fileName, testDatabase + Constants.SUFFIX_MV_FILE); + // avoid using the Engine class to avoid deadlocks + ConnectionInfo ci = new ConnectionInfo("jdbc:h2:" + + testDatabase + ";FILE_LOCK=NO", null, null, null); + Database database = new Database(ci, null); + // close the database + database.removeSession(null); + } catch (Exception e) { + int errorCode = 0; + if (e instanceof DbException) { + e = ((DbException) e).getSQLException(); + errorCode = ((SQLException) e).getErrorCode(); + } + if (errorCode == ErrorCode.WRONG_USER_OR_PASSWORD) { + return; + } else if (errorCode == ErrorCode.FILE_ENCRYPTION_ERROR_1) { + return; + } + StringBuilder buff = new StringBuilder(); + StackTraceElement[] list = e.getStackTrace(); + for (int i = 0; i < 10 && i < list.length; i++) { + buff.append(list[i].toString()).append('\n'); + } + String s = buff.toString(); + if (!knownErrors.contains(s)) { + out.println(writeCount + " code: " + errorCode + " " + e.toString()); + e.printStackTrace(System.out); + knownErrors.add(s); + } else { + out.println(writeCount + " code: " + errorCode); + } + } + } + + public void setTestEvery(int testEvery) { + this.testEvery = testEvery; + } + +} \ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/FakeFileChannel.java b/h2/src/main/org/h2/store/fs/FakeFileChannel.java new file mode 100644 index 0000000..62793ce --- /dev/null +++ b/h2/src/main/org/h2/store/fs/FakeFileChannel.java @@ -0,0 +1,112 @@ +/* + * 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.store.fs; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; + +/** + * Fake file channel to use by in-memory and ZIP file systems. + */ +public class FakeFileChannel extends FileChannel { + + /** + * No need to allocate these, they have no state + */ + public static final FakeFileChannel INSTANCE = new FakeFileChannel(); + + private FakeFileChannel() {} + + @Override + protected void implCloseChannel() throws IOException { + throw new IOException(); + } + + @Override + public FileLock lock(long position, long size, boolean shared) throws IOException { + throw new IOException(); + } + + @Override + public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException { + throw new IOException(); + } + + @Override + public long position() throws IOException { + throw new IOException(); + } + + @Override + public FileChannel position(long newPosition) throws IOException { + throw new IOException(); + } + + @Override + public int read(ByteBuffer dst) throws IOException { + throw new IOException(); + } + + @Override + public int read(ByteBuffer dst, long position) throws IOException { + throw new IOException(); + } + + @Override + public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { + throw new IOException(); + } + + @Override + public long size() throws IOException { + throw new IOException(); + } + + @Override + public long transferFrom(ReadableByteChannel src, long position, long count) throws IOException { + throw new IOException(); + } + + @Override + public long transferTo(long position, long count, WritableByteChannel target) throws IOException { + throw new IOException(); + } + + @Override + public FileChannel truncate(long size) throws IOException { + throw new IOException(); + } + + @Override + public FileLock tryLock(long position, long size, boolean shared) throws IOException { + throw new IOException(); + } + + @Override + public int write(ByteBuffer src) throws IOException { + throw new IOException(); + } + + @Override + public int write(ByteBuffer src, long position) throws IOException { + throw new IOException(); + } + + @Override + public long write(ByteBuffer[] srcs, int offset, int len) throws IOException { + throw new IOException(); + } + + @Override + public void force(boolean metaData) throws IOException { + throw new IOException(); + } +} \ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/FileBase.java b/h2/src/main/org/h2/store/fs/FileBase.java new file mode 100644 index 0000000..b8bf353 --- /dev/null +++ b/h2/src/main/org/h2/store/fs/FileBase.java @@ -0,0 +1,93 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.store.fs; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; + +/** + * The base class for file implementations. + */ +public abstract class FileBase extends FileChannel { + + @Override + public synchronized int read(ByteBuffer dst, long position) + throws IOException { + long oldPos = position(); + position(position); + int len = read(dst); + position(oldPos); + return len; + } + + @Override + public synchronized int write(ByteBuffer src, long position) + throws IOException { + long oldPos = position(); + position(position); + int len = write(src); + position(oldPos); + return len; + } + + @Override + public void force(boolean metaData) throws IOException { + // ignore + } + + @Override + protected void implCloseChannel() throws IOException { + // ignore + } + + @Override + public FileLock lock(long position, long size, boolean shared) + throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public MappedByteBuffer map(MapMode mode, long position, long size) + throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public long read(ByteBuffer[] dsts, int offset, int length) + throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public long transferFrom(ReadableByteChannel src, long position, long count) + throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public long transferTo(long position, long count, WritableByteChannel target) + throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public FileLock tryLock(long position, long size, boolean shared) + throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public long write(ByteBuffer[] srcs, int offset, int length) + throws IOException { + throw new UnsupportedOperationException(); + } + +} diff --git a/h2/src/main/org/h2/store/fs/FileBaseDefault.java b/h2/src/main/org/h2/store/fs/FileBaseDefault.java new file mode 100644 index 0000000..38a0bde --- /dev/null +++ b/h2/src/main/org/h2/store/fs/FileBaseDefault.java @@ -0,0 +1,68 @@ +/* + * 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.store.fs; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +/** + * Default implementation of the slow operations that need synchronization because they + * involve the file position. + */ +public abstract class FileBaseDefault extends FileBase { + + private long position = 0; + + @Override + public final synchronized long position() throws IOException { + return position; + } + + @Override + public final synchronized FileChannel position(long newPosition) throws IOException { + if (newPosition < 0) { + throw new IllegalArgumentException(); + } + position = newPosition; + return this; + } + + @Override + public final synchronized int read(ByteBuffer dst) throws IOException { + int read = read(dst, position); + if (read > 0) { + position += read; + } + return read; + } + + @Override + public final synchronized int write(ByteBuffer src) throws IOException { + int written = write(src, position); + if (written > 0) { + position += written; + } + return written; + } + + @Override + public final synchronized FileChannel truncate(long newLength) throws IOException { + implTruncate(newLength); + if (newLength < position) { + position = newLength; + } + return this; + } + + /** + * The truncate implementation. + * + * @param size the new size + * @throws IOException on failure + */ + protected abstract void implTruncate(long size) throws IOException; +} diff --git a/h2/src/main/org/h2/store/fs/FileChannelInputStream.java b/h2/src/main/org/h2/store/fs/FileChannelInputStream.java new file mode 100644 index 0000000..5677a38 --- /dev/null +++ b/h2/src/main/org/h2/store/fs/FileChannelInputStream.java @@ -0,0 +1,71 @@ +/* + * 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.store.fs; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +/** + * Allows to read from a file channel like an input stream. + */ +public class FileChannelInputStream extends InputStream { + + private final FileChannel channel; + private final boolean closeChannel; + + private ByteBuffer buffer; + private long pos; + + /** + * Create a new file object input stream from the file channel. + * + * @param channel the file channel + * @param closeChannel whether closing the stream should close the channel + */ + public FileChannelInputStream(FileChannel channel, boolean closeChannel) { + this.channel = channel; + this.closeChannel = closeChannel; + } + + @Override + public int read() throws IOException { + if (buffer == null) { + buffer = ByteBuffer.allocate(1); + } + buffer.rewind(); + int len = channel.read(buffer, pos++); + if (len < 0) { + return -1; + } + return buffer.get(0) & 0xff; + } + + @Override + public int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + ByteBuffer buff = ByteBuffer.wrap(b, off, len); + int read = channel.read(buff, pos); + if (read == -1) { + return -1; + } + pos += read; + return read; + } + + @Override + public void close() throws IOException { + if (closeChannel) { + channel.close(); + } + } + +} diff --git a/h2/src/main/org/h2/store/fs/FilePath.java b/h2/src/main/org/h2/store/fs/FilePath.java new file mode 100644 index 0000000..a3409cd --- /dev/null +++ b/h2/src/main/org/h2/store/fs/FilePath.java @@ -0,0 +1,357 @@ +/* + * 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.store.fs; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import org.h2.store.fs.disk.FilePathDisk; +import org.h2.util.MathUtils; + +/** + * A path to a file. It similar to the Java 7 java.nio.file.Path, + * but simpler, and works with older versions of Java. It also implements the + * relevant methods found in java.nio.file.FileSystem and + * FileSystems + */ +public abstract class FilePath { + + private static final FilePath defaultProvider; + + private static final ConcurrentHashMap providers; + + /** + * The prefix for temporary files. + */ + private static String tempRandom; + private static long tempSequence; + + /** + * The complete path (which may be absolute or relative, depending on the + * file system). + */ + public String name; + + static { + FilePath def = null; + ConcurrentHashMap map = new ConcurrentHashMap<>(); + for (String c : new String[] { + "org.h2.store.fs.disk.FilePathDisk", + "org.h2.store.fs.mem.FilePathMem", + "org.h2.store.fs.mem.FilePathMemLZF", + "org.h2.store.fs.niomem.FilePathNioMem", + "org.h2.store.fs.niomem.FilePathNioMemLZF", + "org.h2.store.fs.split.FilePathSplit", + "org.h2.store.fs.niomapped.FilePathNioMapped", + "org.h2.store.fs.async.FilePathAsync", + "org.h2.store.fs.zip.FilePathZip", + "org.h2.store.fs.retry.FilePathRetryOnInterrupt" + }) { + try { + FilePath p = (FilePath) Class.forName(c).getDeclaredConstructor().newInstance(); + map.put(p.getScheme(), p); + if (p.getClass() == FilePathDisk.class) { + map.put("nio", p); + } + if (def == null) { + def = p; + } + } catch (Exception e) { + // ignore - the files may be excluded in purpose + } + } + defaultProvider = def; + providers = map; + } + + /** + * Get the file path object for the given path. + * Windows-style '\' is replaced with '/'. + * + * @param path the path + * @return the file path object + */ + public static FilePath get(String path) { + path = path.replace('\\', '/'); + int index = path.indexOf(':'); + if (index < 2) { + // use the default provider if no prefix or + // only a single character (drive name) + return defaultProvider.getPath(path); + } + String scheme = path.substring(0, index); + FilePath p = providers.get(scheme); + if (p == null) { + // provider not found - use the default + p = defaultProvider; + } + return p.getPath(path); + } + + /** + * Register a file provider. + * + * @param provider the file provider + */ + public static void register(FilePath provider) { + providers.put(provider.getScheme(), provider); + } + + /** + * Unregister a file provider. + * + * @param provider the file provider + */ + public static void unregister(FilePath provider) { + providers.remove(provider.getScheme()); + } + + /** + * Get the size of a file in bytes + * + * @return the size in bytes + */ + public abstract long size(); + + /** + * Rename a file if this is allowed. + * + * @param newName the new fully qualified file name + * @param atomicReplace whether the move should be atomic, and the target + * file should be replaced if it exists and replacing is possible + */ + public abstract void moveTo(FilePath newName, boolean atomicReplace); + + /** + * Create a new file. + * + * @return true if creating was successful + */ + public abstract boolean createFile(); + + /** + * Checks if a file exists. + * + * @return true if it exists + */ + public abstract boolean exists(); + + /** + * Delete a file or directory if it exists. + * Directories may only be deleted if they are empty. + */ + public abstract void delete(); + + /** + * List the files and directories in the given directory. + * + * @return the list of fully qualified file names + */ + public abstract List newDirectoryStream(); + + /** + * Normalize a file name. + * + * @return the normalized file name + */ + public abstract FilePath toRealPath(); + + /** + * Get the parent directory of a file or directory. + * + * @return the parent directory name + */ + public abstract FilePath getParent(); + + /** + * Check if it is a file or a directory. + * + * @return true if it is a directory + */ + 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. + * + * @return if the file name is absolute + */ + public abstract boolean isAbsolute(); + + /** + * Get the last modified date of a file + * + * @return the last modified date + */ + public abstract long lastModified(); + + /** + * Check if the file is writable. + * + * @return if the file is writable + */ + public abstract boolean canWrite(); + + /** + * Create a directory (all required parent directories already exist). + */ + public abstract void createDirectory(); + + /** + * Get the file or directory name (the last element of the path). + * + * @return the last element of the path + */ + public String getName() { + int idx = Math.max(name.indexOf(':'), name.lastIndexOf('/')); + return idx < 0 ? name : name.substring(idx + 1); + } + + /** + * Create an output stream to write into the file. + * + * @param append if true, the file will grow, if false, the file will be + * truncated first + * @return the output stream + * @throws IOException If an I/O error occurs + */ + public OutputStream newOutputStream(boolean append) throws IOException { + return newFileChannelOutputStream(open("rw"), append); + } + + /** + * Create a new output stream from the channel. + * + * @param channel the file channel + * @param append true for append mode, false for truncate and overwrite + * @return the output stream + * @throws IOException on I/O exception + */ + public static final OutputStream newFileChannelOutputStream(FileChannel channel, boolean append) + throws IOException { + if (append) { + channel.position(channel.size()); + } else { + channel.position(0); + channel.truncate(0); + } + return Channels.newOutputStream(channel); + } + + /** + * Open a random access file object. + * + * @param mode the access mode. Supported are r, rw, rws, rwd + * @return the file object + * @throws IOException If an I/O error occurs + */ + public abstract FileChannel open(String mode) throws IOException; + + /** + * Create an input stream to read from the file. + * + * @return the input stream + * @throws IOException If an I/O error occurs + */ + public InputStream newInputStream() throws IOException { + return Channels.newInputStream(open("r")); + } + + /** + * Disable the ability to write. + * + * @return true if the call was successful + */ + public abstract boolean setReadOnly(); + + /** + * Create a new temporary file. + * + * @param suffix the suffix + * @param inTempDir if the file should be stored in the temporary directory + * @return the name of the created file + * @throws IOException on failure + */ + @SuppressWarnings("unused") + public FilePath createTempFile(String suffix, boolean inTempDir) throws IOException { + while (true) { + FilePath p = getPath(name + getNextTempFileNamePart(false) + suffix); + if (p.exists() || !p.createFile()) { + // in theory, the random number could collide + getNextTempFileNamePart(true); + continue; + } + p.open("rw").close(); + return p; + } + } + + /** + * Get the next temporary file name part (the part in the middle). + * + * @param newRandom if the random part of the filename should change + * @return the file name part + */ + private static synchronized String getNextTempFileNamePart( + boolean newRandom) { + if (newRandom || tempRandom == null) { + tempRandom = MathUtils.randomInt(Integer.MAX_VALUE) + "."; + } + return tempRandom + tempSequence++; + } + + /** + * Get the string representation. The returned string can be used to + * construct a new object. + * + * @return the path as a string + */ + @Override + public String toString() { + return name; + } + + /** + * Get the scheme (prefix) for this file provider. + * This is similar to + * java.nio.file.spi.FileSystemProvider.getScheme. + * + * @return the scheme + */ + public abstract String getScheme(); + + /** + * Convert a file to a path. This is similar to + * java.nio.file.spi.FileSystemProvider.getPath, but may + * return an object even if the scheme doesn't match in case of the + * default file provider. + * + * @param path the path + * @return the file path object + */ + public abstract FilePath getPath(String path); + + /** + * Get the unwrapped file name (without wrapper prefixes if wrapping / + * delegating file systems are used). + * + * @return the unwrapped path + */ + public FilePath unwrap() { + return this; + } + +} diff --git a/h2/src/main/org/h2/store/fs/FilePathWrapper.java b/h2/src/main/org/h2/store/fs/FilePathWrapper.java new file mode 100644 index 0000000..da29e92 --- /dev/null +++ b/h2/src/main/org/h2/store/fs/FilePathWrapper.java @@ -0,0 +1,170 @@ +/* + * 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.store.fs; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.channels.FileChannel; +import java.util.List; + +/** + * The base class for wrapping / delegating file systems such as + * the split file system. + */ +public abstract class FilePathWrapper extends FilePath { + + private FilePath base; + + @Override + public FilePathWrapper getPath(String path) { + return create(path, unwrap(path)); + } + + /** + * Create a wrapped path instance for the given base path. + * + * @param base the base path + * @return the wrapped path + */ + public FilePathWrapper wrap(FilePath base) { + return base == null ? null : create(getPrefix() + base.name, base); + } + + @Override + public FilePath unwrap() { + return unwrap(name); + } + + private FilePathWrapper create(String path, FilePath base) { + try { + FilePathWrapper p = getClass().getDeclaredConstructor().newInstance(); + p.name = path; + p.base = base; + return p; + } catch (Exception e) { + throw new IllegalArgumentException("Path: " + path, e); + } + } + + protected String getPrefix() { + return getScheme() + ":"; + } + + /** + * Get the base path for the given wrapped path. + * + * @param path the path including the scheme prefix + * @return the base file path + */ + protected FilePath unwrap(String path) { + return FilePath.get(path.substring(getScheme().length() + 1)); + } + + protected FilePath getBase() { + return base; + } + + @Override + public boolean canWrite() { + return base.canWrite(); + } + + @Override + public void createDirectory() { + base.createDirectory(); + } + + @Override + public boolean createFile() { + return base.createFile(); + } + + @Override + public void delete() { + base.delete(); + } + + @Override + public boolean exists() { + return base.exists(); + } + + @Override + public FilePath getParent() { + return wrap(base.getParent()); + } + + @Override + public boolean isAbsolute() { + return base.isAbsolute(); + } + + @Override + public boolean isDirectory() { + return base.isDirectory(); + } + + @Override + public boolean isRegularFile() { + return base.isRegularFile(); + } + + @Override + public long lastModified() { + return base.lastModified(); + } + + @Override + public FilePath toRealPath() { + return wrap(base.toRealPath()); + } + + @Override + public List newDirectoryStream() { + List list = base.newDirectoryStream(); + for (int i = 0, len = list.size(); i < len; i++) { + list.set(i, wrap(list.get(i))); + } + return list; + } + + @Override + public void moveTo(FilePath newName, boolean atomicReplace) { + base.moveTo(((FilePathWrapper) newName).base, atomicReplace); + } + + @Override + public InputStream newInputStream() throws IOException { + return base.newInputStream(); + } + + @Override + public OutputStream newOutputStream(boolean append) throws IOException { + return base.newOutputStream(append); + } + + @Override + public FileChannel open(String mode) throws IOException { + return base.open(mode); + } + + @Override + public boolean setReadOnly() { + return base.setReadOnly(); + } + + @Override + public long size() { + return base.size(); + } + + @Override + public FilePath createTempFile(String suffix, boolean inTempDir) throws IOException { + return wrap(base.createTempFile(suffix, inTempDir)); + } + +} diff --git a/h2/src/main/org/h2/store/fs/FileUtils.java b/h2/src/main/org/h2/store/fs/FileUtils.java new file mode 100644 index 0000000..32fcdfe --- /dev/null +++ b/h2/src/main/org/h2/store/fs/FileUtils.java @@ -0,0 +1,476 @@ +/* + * 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.store.fs; + +import java.io.BufferedReader; +import java.io.EOFException; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.Charset; +import java.nio.file.OpenOption; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.FileAttribute; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; + +import org.h2.engine.Constants; + +/** + * This utility class contains utility functions that use the file system + * abstraction. + */ +public class FileUtils { + + /** + * {@link StandardOpenOption#READ}. + */ + public static final Set R = Collections.singleton(StandardOpenOption.READ); + + /** + * {@link StandardOpenOption#READ}, {@link StandardOpenOption#WRITE}, and + * {@link StandardOpenOption#CREATE}. + */ + public static final Set RW = Collections + .unmodifiableSet(EnumSet.of(StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE)); + + /** + * {@link StandardOpenOption#READ}, {@link StandardOpenOption#WRITE}, + * {@link StandardOpenOption#CREATE}, and {@link StandardOpenOption#SYNC}. + */ + public static final Set RWS = Collections.unmodifiableSet(EnumSet.of(StandardOpenOption.READ, + StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.SYNC)); + + /** + * {@link StandardOpenOption#READ}, {@link StandardOpenOption#WRITE}, + * {@link StandardOpenOption#CREATE}, and {@link StandardOpenOption#DSYNC}. + */ + public static final Set RWD = Collections.unmodifiableSet(EnumSet.of(StandardOpenOption.READ, + StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.DSYNC)); + + /** + * No file attributes. + */ + public static final FileAttribute[] NO_ATTRIBUTES = new FileAttribute[0]; + + /** + * Checks if a file exists. + * This method is similar to Java 7 java.nio.file.Path.exists. + * + * @param fileName the file name + * @return true if it exists + */ + public static boolean exists(String fileName) { + return FilePath.get(fileName).exists(); + } + + /** + * Create a directory (all required parent directories must already exist). + * This method is similar to Java 7 + * java.nio.file.Path.createDirectory. + * + * @param directoryName the directory name + */ + public static void createDirectory(String directoryName) { + FilePath.get(directoryName).createDirectory(); + } + + /** + * Create a new file. This method is similar to Java 7 + * java.nio.file.Path.createFile, but returns false instead of + * throwing a exception if the file already existed. + * + * @param fileName the file name + * @return true if creating was successful + */ + public static boolean createFile(String fileName) { + return FilePath.get(fileName).createFile(); + } + + /** + * Delete a file or directory if it exists. + * Directories may only be deleted if they are empty. + * This method is similar to Java 7 + * java.nio.file.Path.deleteIfExists. + * + * @param path the file or directory name + */ + public static void delete(String path) { + FilePath.get(path).delete(); + } + + /** + * Get the canonical file or directory name. This method is similar to Java + * 7 java.nio.file.Path.toRealPath. + * + * @param fileName the file name + * @return the normalized file name + */ + public static String toRealPath(String fileName) { + return FilePath.get(fileName).toRealPath().toString(); + } + + /** + * Get the parent directory of a file or directory. This method returns null + * if there is no parent. This method is similar to Java 7 + * java.nio.file.Path.getParent. + * + * @param fileName the file or directory name + * @return the parent directory name + */ + public static String getParent(String fileName) { + FilePath p = FilePath.get(fileName).getParent(); + return p == null ? null : p.toString(); + } + + /** + * Check if the file name includes a path. This method is similar to Java 7 + * java.nio.file.Path.isAbsolute. + * + * @param fileName the file name + * @return if the file name is absolute + */ + public static boolean isAbsolute(String fileName) { + return FilePath.get(fileName).isAbsolute() + // Allows Windows to recognize "/path" as absolute. + // Makes the same configuration work on all platforms. + || fileName.startsWith(File.separator) + // Just in case of non-normalized path on Windows + || fileName.startsWith("/"); + } + + /** + * Rename a file if this is allowed. This method is similar to Java 7 + * java.nio.file.Files.move. + * + * @param source the old fully qualified file name + * @param target the new fully qualified file name + */ + public static void move(String source, String target) { + FilePath.get(source).moveTo(FilePath.get(target), false); + } + + /** + * Rename a file if this is allowed, and try to atomically replace an + * existing file. This method is similar to Java 7 + * java.nio.file.Files.move. + * + * @param source the old fully qualified file name + * @param target the new fully qualified file name + */ + public static void moveAtomicReplace(String source, String target) { + FilePath.get(source).moveTo(FilePath.get(target), true); + } + + /** + * Get the file or directory name (the last element of the path). + * This method is similar to Java 7 java.nio.file.Path.getName. + * + * @param path the directory and file name + * @return just the file name + */ + public static String getName(String path) { + return FilePath.get(path).getName(); + } + + /** + * List the files and directories in the given directory. + * This method is similar to Java 7 + * java.nio.file.Path.newDirectoryStream. + * + * @param path the directory + * @return the list of fully qualified file names + */ + public static List newDirectoryStream(String path) { + List list = FilePath.get(path).newDirectoryStream(); + int len = list.size(); + List result = new ArrayList<>(len); + for (FilePath filePath : list) { + result.add(filePath.toString()); + } + return result; + } + + /** + * Get the last modified date of a file. + * This method is similar to Java 7 + * java.nio.file.attribute.Attributes. + * readBasicFileAttributes(file).lastModified().toMillis() + * + * @param fileName the file name + * @return the last modified date + */ + public static long lastModified(String fileName) { + return FilePath.get(fileName).lastModified(); + } + + /** + * Get the size of a file in bytes + * This method is similar to Java 7 + * java.nio.file.attribute.Attributes. + * readBasicFileAttributes(file).size() + * + * @param fileName the file name + * @return the size in bytes + */ + public static long size(String fileName) { + return FilePath.get(fileName).size(); + } + + /** + * Check if it is a file or a directory. + * java.nio.file.attribute.Attributes. + * readBasicFileAttributes(file).isDirectory() + * + * @param fileName the file or directory name + * @return true if it is a directory + */ + 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 + * java.nio.channels.FileChannel.open. + * + * @param fileName the file name + * @param mode the access mode. Supported are r, rw, rws, rwd + * @return the file object + * @throws IOException on failure + */ + public static FileChannel open(String fileName, String mode) + throws IOException { + return FilePath.get(fileName).open(mode); + } + + /** + * Create an input stream to read from the file. + * This method is similar to Java 7 + * java.nio.file.Files.newInputStream(). + * + * @param fileName the file name + * @return the input stream + * @throws IOException on failure + */ + public static InputStream newInputStream(String fileName) throws IOException { + return FilePath.get(fileName).newInputStream(); + } + + /** + * Create a buffered reader to read from the file. + * This method is similar to + * java.nio.file.Files.newBufferedReader(). + * + * @param fileName the file name + * @param charset the charset + * @return the buffered reader + * @throws IOException on failure + */ + public static BufferedReader newBufferedReader(String fileName, Charset charset) throws IOException { + return new BufferedReader(new InputStreamReader(newInputStream(fileName), charset), Constants.IO_BUFFER_SIZE); + } + + /** + * Create an output stream to write into the file. + * This method is similar to + * java.nio.file.Files.newOutputStream(). + * + * @param fileName the file name + * @param append if true, the file will grow, if false, the file will be + * truncated first + * @return the output stream + * @throws IOException on failure + */ + public static OutputStream newOutputStream(String fileName, boolean append) throws IOException { + return FilePath.get(fileName).newOutputStream(append); + } + + /** + * Check if the file is writable. + * This method is similar to Java 7 + * java.nio.file.Path.checkAccess(AccessMode.WRITE) + * + * @param fileName the file name + * @return if the file is writable + */ + public static boolean canWrite(String fileName) { + return FilePath.get(fileName).canWrite(); + } + + // special methods ======================================= + + /** + * Disable the ability to write. The file can still be deleted afterwards. + * + * @param fileName the file name + * @return true if the call was successful + */ + public static boolean setReadOnly(String fileName) { + return FilePath.get(fileName).setReadOnly(); + } + + /** + * Get the unwrapped file name (without wrapper prefixes if wrapping / + * delegating file systems are used). + * + * @param fileName the file name + * @return the unwrapped + */ + public static String unwrap(String fileName) { + return FilePath.get(fileName).unwrap().toString(); + } + + // utility methods ======================================= + + /** + * Delete a directory or file and all subdirectories and files. + * + * @param path the path + * @param tryOnly whether errors should be ignored + */ + public static void deleteRecursive(String path, boolean tryOnly) { + if (exists(path)) { + if (isDirectory(path)) { + for (String s : newDirectoryStream(path)) { + deleteRecursive(s, tryOnly); + } + } + if (tryOnly) { + tryDelete(path); + } else { + delete(path); + } + } + } + + /** + * Create the directory and all required parent directories. + * + * @param dir the directory name + */ + public static void createDirectories(String dir) { + if (dir != null) { + if (exists(dir)) { + if (!isDirectory(dir)) { + // this will fail + createDirectory(dir); + } + } else { + String parent = getParent(dir); + createDirectories(parent); + createDirectory(dir); + } + } + } + + /** + * Try to delete a file or directory (ignoring errors). + * + * @param path the file or directory name + * @return true if it worked + */ + public static boolean tryDelete(String path) { + try { + FilePath.get(path).delete(); + return true; + } catch (Exception e) { + return false; + } + } + + /** + * Create a new temporary file. + * + * @param prefix the prefix of the file name (including directory name if + * required) + * @param suffix the suffix + * @param inTempDir if the file should be stored in the temporary directory + * @return the name of the created file + * @throws IOException on failure + */ + public static String createTempFile(String prefix, String suffix, + boolean inTempDir) throws IOException { + return FilePath.get(prefix).createTempFile(suffix, inTempDir).toString(); + } + + /** + * Fully read from the file. This will read all remaining bytes, + * or throw an EOFException if not successful. + * + * @param channel the file channel + * @param dst the byte buffer + * @throws IOException on failure + */ + public static void readFully(FileChannel channel, ByteBuffer dst) + throws IOException { + do { + int r = channel.read(dst); + if (r < 0) { + throw new EOFException(); + } + } while (dst.remaining() > 0); + } + + /** + * Fully write to the file. This will write all remaining bytes. + * + * @param channel the file channel + * @param src the byte buffer + * @throws IOException on failure + */ + public static void writeFully(FileChannel channel, ByteBuffer src) + throws IOException { + do { + channel.write(src); + } while (src.remaining() > 0); + } + + /** + * Convert the string representation to a set. + * + * @param mode the mode as a string + * @return the set + */ + public static Set modeToOptions(String mode) { + Set options; + switch (mode) { + case "r": + options = R; + break; + case "rw": + options = RW; + break; + case "rws": + options = RWS; + break; + case "rwd": + options = RWD; + break; + default: + throw new IllegalArgumentException(mode); + } + return options; + } + +} diff --git a/h2/src/main/org/h2/store/fs/Recorder.java b/h2/src/main/org/h2/store/fs/Recorder.java new file mode 100644 index 0000000..829be53 --- /dev/null +++ b/h2/src/main/org/h2/store/fs/Recorder.java @@ -0,0 +1,59 @@ +/* + * 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.store.fs; + +/** + * A recorder for the recording file system. + */ +public interface Recorder { + + /** + * Create a new file. + */ + int CREATE_NEW_FILE = 2; + + /** + * Create a temporary file. + */ + int CREATE_TEMP_FILE = 3; + + /** + * Delete a file. + */ + int DELETE = 4; + + /** + * Open a file output stream. + */ + int OPEN_OUTPUT_STREAM = 5; + + /** + * Rename a file. The file name contains the source and the target file + * separated with a colon. + */ + int RENAME = 6; + + /** + * Truncate the file. + */ + int TRUNCATE = 7; + + /** + * Write to the file. + */ + int WRITE = 8; + + /** + * Record the method. + * + * @param op the operation + * @param fileName the file name or file name list + * @param data the data or null + * @param x the value or 0 + */ + void log(int op, String fileName, byte[] data, long x); + +} diff --git a/h2/src/main/org/h2/store/fs/async/FileAsync.java b/h2/src/main/org/h2/store/fs/async/FileAsync.java new file mode 100644 index 0000000..427d415 --- /dev/null +++ b/h2/src/main/org/h2/store/fs/async/FileAsync.java @@ -0,0 +1,89 @@ +/* + * 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.store.fs.async; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousFileChannel; +import java.nio.channels.FileLock; +import java.nio.file.Paths; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import org.h2.store.fs.FileBaseDefault; +import org.h2.store.fs.FileUtils; + +/** + * File which uses NIO2 AsynchronousFileChannel. + */ +class FileAsync extends FileBaseDefault { + + private final String name; + private final AsynchronousFileChannel channel; + + private static T complete(Future future) throws IOException { + boolean interrupted = false; + for (;;) { + try { + T result = future.get(); + if (interrupted) { + Thread.currentThread().interrupt(); + } + return result; + } catch (InterruptedException e) { + interrupted = true; + } catch (ExecutionException e) { + throw new IOException(e.getCause()); + } + } + } + + FileAsync(String fileName, String mode) throws IOException { + this.name = fileName; + channel = AsynchronousFileChannel.open(Paths.get(fileName), FileUtils.modeToOptions(mode), null, + FileUtils.NO_ATTRIBUTES); + } + + @Override + public void implCloseChannel() throws IOException { + channel.close(); + } + + @Override + public long size() throws IOException { + return channel.size(); + } + + @Override + public int read(ByteBuffer dst, long position) throws IOException { + return complete(channel.read(dst, position)); + } + + @Override + public int write(ByteBuffer src, long position) throws IOException { + return complete(channel.write(src, position)); + } + + @Override + protected void implTruncate(long newLength) throws IOException { + channel.truncate(newLength); + } + + @Override + public void force(boolean metaData) throws IOException { + channel.force(metaData); + } + + @Override + public FileLock tryLock(long position, long size, boolean shared) throws IOException { + return channel.tryLock(position, size, shared); + } + + @Override + public String toString() { + return "async:" + name; + } + +} \ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/async/FilePathAsync.java b/h2/src/main/org/h2/store/fs/async/FilePathAsync.java new file mode 100644 index 0000000..b853fe8 --- /dev/null +++ b/h2/src/main/org/h2/store/fs/async/FilePathAsync.java @@ -0,0 +1,28 @@ +/* + * 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.store.fs.async; + +import java.io.IOException; +import java.nio.channels.FileChannel; +import org.h2.store.fs.FilePathWrapper; + +/** + * This file system stores files on disk and uses + * java.nio.channels.AsynchronousFileChannel to access the files. + */ +public class FilePathAsync extends FilePathWrapper { + + @Override + public FileChannel open(String mode) throws IOException { + return new FileAsync(name.substring(getScheme().length() + 1), mode); + } + + @Override + public String getScheme() { + return "async"; + } + +} diff --git a/h2/src/main/org/h2/store/fs/async/package.html b/h2/src/main/org/h2/store/fs/async/package.html new file mode 100644 index 0000000..b4736bf --- /dev/null +++ b/h2/src/main/org/h2/store/fs/async/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +This file system stores files on disk and uses java.nio.channels.AsynchronousFileChannel to access the files. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/disk/FilePathDisk.java b/h2/src/main/org/h2/store/fs/disk/FilePathDisk.java new file mode 100644 index 0000000..9c30c40 --- /dev/null +++ b/h2/src/main/org/h2/store/fs/disk/FilePathDisk.java @@ -0,0 +1,450 @@ +/* + * 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.store.fs.disk; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.nio.channels.FileChannel; +import java.nio.file.AtomicMoveNotSupportedException; +import java.nio.file.CopyOption; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.FileStore; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.DosFileAttributeView; +import java.nio.file.attribute.PosixFileAttributeView; +import java.nio.file.attribute.PosixFilePermission; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.stream.Stream; + +import org.h2.api.ErrorCode; +import org.h2.engine.SysProperties; +import org.h2.message.DbException; +import org.h2.store.fs.FilePath; +import org.h2.store.fs.FileUtils; +import org.h2.util.IOUtils; + +/** + * This file system stores files on disk. + * This is the most common file system. + */ +public class FilePathDisk extends FilePath { + + private static final String CLASSPATH_PREFIX = "classpath:"; + + @Override + public FilePathDisk getPath(String path) { + FilePathDisk p = new FilePathDisk(); + p.name = translateFileName(path); + return p; + } + + @Override + public long size() { + if (name.startsWith(CLASSPATH_PREFIX)) { + try { + String fileName = name.substring(CLASSPATH_PREFIX.length()); + // Force absolute resolution in Class.getResource + if (!fileName.startsWith("/")) { + fileName = "/" + fileName; + } + URL resource = this.getClass().getResource(fileName); + if (resource != null) { + return Files.size(Paths.get(resource.toURI())); + } else { + return 0; + } + } catch (Exception e) { + return 0; + } + } + try { + return Files.size(Paths.get(name)); + } catch (IOException e) { + return 0L; + } + } + + /** + * Translate the file name to the native format. This will replace '\' with + * '/' and expand the home directory ('~'). + * + * @param fileName the file name + * @return the native file name + */ + protected static String translateFileName(String fileName) { + fileName = fileName.replace('\\', '/'); + if (fileName.startsWith("file:")) { + fileName = fileName.substring(5); + } else if (fileName.startsWith("nio:")) { + fileName = fileName.substring(4); + } + return expandUserHomeDirectory(fileName); + } + + /** + * Expand '~' to the user home directory. It is only be expanded if the '~' + * stands alone, or is followed by '/' or '\'. + * + * @param fileName the file name + * @return the native file name + */ + public static String expandUserHomeDirectory(String fileName) { + if (fileName.startsWith("~") && (fileName.length() == 1 || + fileName.startsWith("~/"))) { + String userDir = SysProperties.USER_HOME; + fileName = userDir + fileName.substring(1); + } + return fileName; + } + + @Override + public void moveTo(FilePath newName, boolean atomicReplace) { + Path oldFile = Paths.get(name); + Path newFile = Paths.get(newName.name); + if (!Files.exists(oldFile)) { + throw DbException.get(ErrorCode.FILE_RENAME_FAILED_2, name + " (not found)", newName.name); + } + if (atomicReplace) { + try { + Files.move(oldFile, newFile, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + return; + } catch (AtomicMoveNotSupportedException ex) { + // Ignore + } catch (IOException ex) { + throw DbException.get(ErrorCode.FILE_RENAME_FAILED_2, ex, name, newName.name); + } + } + CopyOption[] copyOptions = atomicReplace ? new CopyOption[] { StandardCopyOption.REPLACE_EXISTING } + : new CopyOption[0]; + IOException cause; + try { + Files.move(oldFile, newFile, copyOptions); + } catch (FileAlreadyExistsException ex) { + throw DbException.get(ErrorCode.FILE_RENAME_FAILED_2, name, newName + " (exists)"); + } catch (IOException ex) { + cause = ex; + for (int i = 0; i < SysProperties.MAX_FILE_RETRY; i++) { + IOUtils.trace("rename", name + " >" + newName, null); + try { + Files.move(oldFile, newFile, copyOptions); + return; + } catch (FileAlreadyExistsException ex2) { + throw DbException.get(ErrorCode.FILE_RENAME_FAILED_2, name, newName + " (exists)"); + } catch (IOException ex2) { + cause = ex; + } + wait(i); + } + throw DbException.get(ErrorCode.FILE_RENAME_FAILED_2, cause, name, newName.name); + } + } + + private static void wait(int i) { + if (i == 8) { + System.gc(); + } + try { + // sleep at most 256 ms + long sleep = Math.min(256, i * i); + Thread.sleep(sleep); + } catch (InterruptedException e) { + // ignore + } + } + + @Override + public boolean createFile() { + Path file = Paths.get(name); + for (int i = 0; i < SysProperties.MAX_FILE_RETRY; i++) { + try { + Files.createFile(file); + return true; + } catch (FileAlreadyExistsException e) { + return false; + } catch (IOException e) { + // 'access denied' is really a concurrent access problem + wait(i); + } + } + return false; + } + + @Override + public boolean exists() { + return Files.exists(Paths.get(name)); + } + + @Override + public void delete() { + Path file = Paths.get(name); + IOException cause = null; + for (int i = 0; i < SysProperties.MAX_FILE_RETRY; i++) { + IOUtils.trace("delete", name, null); + try { + Files.deleteIfExists(file); + return; + } catch (DirectoryNotEmptyException e) { + throw DbException.get(ErrorCode.FILE_DELETE_FAILED_1, e, name); + } catch (IOException e) { + cause = e; + } + wait(i); + } + throw DbException.get(ErrorCode.FILE_DELETE_FAILED_1, cause, name); + } + + @Override + public List newDirectoryStream() { + try (Stream files = Files.list(Paths.get(name).toRealPath())) { + return files.collect(ArrayList::new, (t, u) -> t.add(getPath(u.toString())), ArrayList::addAll); + } catch (NoSuchFileException e) { + return Collections.emptyList(); + } catch (IOException e) { + throw DbException.convertIOException(e, name); + } + } + + @Override + public boolean canWrite() { + try { + return Files.isWritable(Paths.get(name)); + } catch (Exception e) { + // Catch security exceptions + return false; + } + } + + @Override + public boolean setReadOnly() { + Path f = Paths.get(name); + try { + FileStore fileStore = Files.getFileStore(f); + /* + * Need to check PosixFileAttributeView first because + * DosFileAttributeView is also supported by recent Java versions on + * non-Windows file systems, but it doesn't affect real access + * permissions. + */ + if (fileStore.supportsFileAttributeView(PosixFileAttributeView.class)) { + HashSet permissions = new HashSet<>(); + for (PosixFilePermission p : Files.getPosixFilePermissions(f)) { + switch (p) { + case OWNER_WRITE: + case GROUP_WRITE: + case OTHERS_WRITE: + break; + default: + permissions.add(p); + } + } + Files.setPosixFilePermissions(f, permissions); + } else if (fileStore.supportsFileAttributeView(DosFileAttributeView.class)) { + Files.setAttribute(f, "dos:readonly", true); + } else { + return false; + } + return true; + } catch (IOException e) { + return false; + } + } + + @Override + public FilePathDisk toRealPath() { + Path path = Paths.get(name); + try { + return getPath(path.toRealPath().toString()); + } catch (IOException e) { + /* + * File does not exist or isn't accessible, try to get the real path + * of parent directory. + */ + return getPath(toRealPath(path.toAbsolutePath().normalize()).toString()); + } + } + + private static Path toRealPath(Path path) { + Path parent = path.getParent(); + if (parent == null) { + return path; + } + try { + parent = parent.toRealPath(); + } catch (IOException e) { + parent = toRealPath(parent); + } + return parent.resolve(path.getFileName()); + } + + @Override + public FilePath getParent() { + Path p = Paths.get(name).getParent(); + return p == null ? null : getPath(p.toString()); + } + + @Override + 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(); + } + + @Override + public long lastModified() { + try { + return Files.getLastModifiedTime(Paths.get(name)).toMillis(); + } catch (IOException e) { + return 0L; + } + } + + @Override + public void createDirectory() { + Path dir = Paths.get(name); + try { + Files.createDirectory(dir); + } catch (FileAlreadyExistsException e) { + throw DbException.get(ErrorCode.FILE_CREATION_FAILED_1, name + " (a file with this name already exists)"); + } catch (IOException e) { + IOException cause = e; + for (int i = 0; i < SysProperties.MAX_FILE_RETRY; i++) { + if (Files.isDirectory(dir)) { + return; + } + try { + Files.createDirectory(dir); + } catch (FileAlreadyExistsException ex) { + throw DbException.get(ErrorCode.FILE_CREATION_FAILED_1, + name + " (a file with this name already exists)"); + } catch (IOException ex) { + cause = ex; + } + wait(i); + } + throw DbException.get(ErrorCode.FILE_CREATION_FAILED_1, cause, name); + } + } + + @Override + public OutputStream newOutputStream(boolean append) throws IOException { + Path file = Paths.get(name); + OpenOption[] options = append // + ? new OpenOption[] { StandardOpenOption.CREATE, StandardOpenOption.APPEND } + : new OpenOption[0]; + try { + Path parent = file.getParent(); + if (parent != null) { + Files.createDirectories(parent); + } + OutputStream out = Files.newOutputStream(file, options); + IOUtils.trace("openFileOutputStream", name, out); + return out; + } catch (IOException e) { + freeMemoryAndFinalize(); + return Files.newOutputStream(file, options); + } + } + + @Override + public InputStream newInputStream() throws IOException { + if (name.matches("[a-zA-Z]{2,19}:.*")) { + // if the ':' is in position 1, a windows file access is assumed: + // C:.. or D:, and if the ':' is not at the beginning, assume its a + // file name with a colon + if (name.startsWith(CLASSPATH_PREFIX)) { + String fileName = name.substring(CLASSPATH_PREFIX.length()); + // Force absolute resolution in Class.getResourceAsStream + if (!fileName.startsWith("/")) { + fileName = "/" + fileName; + } + InputStream in = getClass().getResourceAsStream(fileName); + if (in == null) { + // ClassLoader.getResourceAsStream doesn't need leading "/" + in = Thread.currentThread().getContextClassLoader(). + getResourceAsStream(fileName.substring(1)); + } + if (in == null) { + throw new FileNotFoundException("resource " + fileName); + } + return in; + } + // otherwise a URL is assumed + URL url = new URL(name); + return url.openStream(); + } + InputStream in = Files.newInputStream(Paths.get(name)); + IOUtils.trace("openFileInputStream", name, in); + return in; + } + + /** + * Call the garbage collection and run finalization. This close all files + * that were not closed, and are no longer referenced. + */ + static void freeMemoryAndFinalize() { + IOUtils.trace("freeMemoryAndFinalize", null, null); + Runtime rt = Runtime.getRuntime(); + long mem = rt.freeMemory(); + for (int i = 0; i < 16; i++) { + rt.gc(); + long now = rt.freeMemory(); + rt.runFinalization(); + if (now == mem) { + break; + } + mem = now; + } + } + + @Override + public FileChannel open(String mode) throws IOException { + FileChannel f = FileChannel.open(Paths.get(name), FileUtils.modeToOptions(mode), FileUtils.NO_ATTRIBUTES); + IOUtils.trace("open", name, f); + return f; + } + + @Override + public String getScheme() { + return "file"; + } + + @Override + public FilePath createTempFile(String suffix, boolean inTempDir) throws IOException { + Path file = Paths.get(name + '.').toAbsolutePath(); + String prefix = file.getFileName().toString(); + if (inTempDir) { + Files.createDirectories(Paths.get(System.getProperty("java.io.tmpdir", "."))); + file = Files.createTempFile(prefix, suffix); + } else { + Path dir = file.getParent(); + Files.createDirectories(dir); + file = Files.createTempFile(dir, prefix, suffix); + } + return get(file.toString()); + } + +} diff --git a/h2/src/main/org/h2/store/fs/disk/package.html b/h2/src/main/org/h2/store/fs/disk/package.html new file mode 100644 index 0000000..7156f31 --- /dev/null +++ b/h2/src/main/org/h2/store/fs/disk/package.html @@ -0,0 +1,16 @@ + + + + +Javadoc package documentation +

+ +This file system stores files on disk. +
+This is the most common file system. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/encrypt/FileEncrypt.java b/h2/src/main/org/h2/store/fs/encrypt/FileEncrypt.java new file mode 100644 index 0000000..38bc227 --- /dev/null +++ b/h2/src/main/org/h2/store/fs/encrypt/FileEncrypt.java @@ -0,0 +1,261 @@ +/* + * 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.store.fs.encrypt; + +import java.io.EOFException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import org.h2.security.AES; +import org.h2.security.SHA256; +import org.h2.store.fs.FileBaseDefault; +import org.h2.util.MathUtils; + +/** + * An encrypted file with a read cache. + */ +public class FileEncrypt extends FileBaseDefault { + + /** + * The block size. + */ + public static final int BLOCK_SIZE = 4096; + + /** + * The block size bit mask. + */ + static final int BLOCK_SIZE_MASK = BLOCK_SIZE - 1; + + /** + * The length of the file header. Using a smaller header is possible, + * but would mean reads and writes are not aligned to the block size. + */ + static final int HEADER_LENGTH = BLOCK_SIZE; + + private static final byte[] HEADER = "H2encrypt\n".getBytes(StandardCharsets.ISO_8859_1); + private static final int SALT_POS = HEADER.length; + + /** + * The length of the salt, in bytes. + */ + private static final int SALT_LENGTH = 8; + + /** + * The number of iterations. It is relatively low; a higher value would + * slow down opening files on Android too much. + */ + private static final int HASH_ITERATIONS = 10; + + private final FileChannel base; + + /** + * The current file size, from a user perspective. + */ + private volatile long size; + + private final String name; + + private volatile XTS xts; + + private byte[] encryptionKey; + + 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 + // chance to lock the file first + this.name = name; + this.base = base; + this.encryptionKey = encryptionKey; + } + + private XTS init() throws IOException { + // Keep this method small to allow inlining + XTS xts = this.xts; + if (xts == null) { + xts = createXTS(); + } + return xts; + } + + private synchronized XTS createXTS() throws IOException { + XTS xts = this.xts; + 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; + } else { + salt = new byte[SALT_LENGTH]; + readFully(base, SALT_POS, ByteBuffer.wrap(salt)); + if ((size & BLOCK_SIZE_MASK) != 0) { + size -= BLOCK_SIZE; + } + } + AES cipher = new AES(); + cipher.setKey(SHA256.getPBKDF2(encryptionKey, salt, HASH_ITERATIONS, 16)); + encryptionKey = null; + return this.xts = new XTS(cipher); + } + + @Override + protected void implCloseChannel() throws IOException { + base.close(); + } + + @Override + public int read(ByteBuffer dst, long position) throws IOException { + int len = dst.remaining(); + if (len == 0) { + return 0; + } + XTS xts = init(); + len = (int) Math.min(len, size - position); + if (position >= size) { + return -1; + } else if (position < 0) { + throw new IllegalArgumentException("pos: " + position); + } + if ((position & BLOCK_SIZE_MASK) != 0 || (len & BLOCK_SIZE_MASK) != 0) { + // either the position or the len is unaligned: + // read aligned, and then truncate + long p = position / BLOCK_SIZE * BLOCK_SIZE; + int offset = (int) (position - p); + int l = (len + offset + BLOCK_SIZE - 1) / BLOCK_SIZE * BLOCK_SIZE; + ByteBuffer temp = ByteBuffer.allocate(l); + readInternal(temp, p, l, xts); + temp.flip().limit(offset + len).position(offset); + dst.put(temp); + return len; + } + readInternal(dst, position, len, xts); + return len; + } + + private void readInternal(ByteBuffer dst, long position, int len, XTS xts) throws IOException { + int x = dst.position(); + readFully(base, position + HEADER_LENGTH, dst); + long block = position / BLOCK_SIZE; + while (len > 0) { + xts.decrypt(block++, BLOCK_SIZE, dst.array(), dst.arrayOffset() + x); + x += BLOCK_SIZE; + len -= BLOCK_SIZE; + } + } + + private static void readFully(FileChannel file, long pos, ByteBuffer dst) throws IOException { + do { + int len = file.read(dst, pos); + if (len < 0) { + throw new EOFException(); + } + pos += len; + } while (dst.remaining() > 0); + } + + @Override + public int write(ByteBuffer src, long position) throws IOException { + XTS xts = init(); + int len = src.remaining(); + if ((position & BLOCK_SIZE_MASK) != 0 || (len & BLOCK_SIZE_MASK) != 0) { + // either the position or the len is unaligned: + // read aligned, and then truncate + long p = position / BLOCK_SIZE * BLOCK_SIZE; + int offset = (int) (position - p); + int l = (len + offset + BLOCK_SIZE - 1) / BLOCK_SIZE * BLOCK_SIZE; + ByteBuffer temp = ByteBuffer.allocate(l); + int available = (int) (size - p + BLOCK_SIZE - 1) / BLOCK_SIZE * BLOCK_SIZE; + int readLen = Math.min(l, available); + if (readLen > 0) { + readInternal(temp, p, readLen, xts); + temp.rewind(); + } + temp.limit(offset + len).position(offset); + temp.put(src).limit(l).rewind(); + writeInternal(temp, p, l, xts); + long p2 = position + len; + size = Math.max(size, p2); + int plus = (int) (size & BLOCK_SIZE_MASK); + if (plus > 0) { + temp = ByteBuffer.allocate(plus); + writeFully(base, p + HEADER_LENGTH + l, temp); + } + return len; + } + writeInternal(src, position, len, xts); + long p2 = position + len; + size = Math.max(size, p2); + return len; + } + + private void writeInternal(ByteBuffer src, long position, int len, XTS xts) throws IOException { + ByteBuffer crypt = ByteBuffer.allocate(len).put(src); + crypt.flip(); + long block = position / BLOCK_SIZE; + int x = 0, l = len; + while (l > 0) { + xts.encrypt(block++, BLOCK_SIZE, crypt.array(), crypt.arrayOffset() + x); + x += BLOCK_SIZE; + l -= BLOCK_SIZE; + } + writeFully(base, position + HEADER_LENGTH, crypt); + } + + private static void writeFully(FileChannel file, long pos, ByteBuffer src) throws IOException { + do { + pos += file.write(src, pos); + } while (src.remaining() > 0); + } + + @Override + public long size() throws IOException { + init(); + return size; + } + + @Override + protected void implTruncate(long newSize) throws IOException { + init(); + if (newSize > size) { + return; + } + if (newSize < 0) { + throw new IllegalArgumentException("newSize: " + newSize); + } + int offset = (int) (newSize & BLOCK_SIZE_MASK); + if (offset > 0) { + base.truncate(newSize + HEADER_LENGTH + BLOCK_SIZE); + } else { + base.truncate(newSize + HEADER_LENGTH); + } + this.size = newSize; + } + + @Override + public void force(boolean metaData) throws IOException { + base.force(metaData); + } + + @Override + public FileLock tryLock(long position, long size, boolean shared) throws IOException { + return base.tryLock(position, size, shared); + } + + @Override + public String toString() { + return name; + } + +} diff --git a/h2/src/main/org/h2/store/fs/encrypt/FilePathEncrypt.java b/h2/src/main/org/h2/store/fs/encrypt/FilePathEncrypt.java new file mode 100644 index 0000000..40dffc5 --- /dev/null +++ b/h2/src/main/org/h2/store/fs/encrypt/FilePathEncrypt.java @@ -0,0 +1,118 @@ +/* + * 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.store.fs.encrypt; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; +import org.h2.store.fs.FilePath; +import org.h2.store.fs.FilePathWrapper; +import org.h2.store.fs.FileUtils; + +/** + * An encrypted file. + */ +public class FilePathEncrypt extends FilePathWrapper { + + private static final String SCHEME = "encrypt"; + + /** + * Register this file system. + */ + public static void register() { + FilePath.register(new FilePathEncrypt()); + } + + @Override + public FileChannel open(String mode) throws IOException { + String[] parsed = parse(name); + FileChannel file = FileUtils.open(parsed[1], mode); + byte[] passwordBytes = parsed[0].getBytes(StandardCharsets.UTF_8); + return new FileEncrypt(name, passwordBytes, file); + } + + @Override + public String getScheme() { + return SCHEME; + } + + @Override + protected String getPrefix() { + String[] parsed = parse(name); + return getScheme() + ":" + parsed[0] + ":"; + } + + @Override + public FilePath unwrap(String fileName) { + return FilePath.get(parse(fileName)[1]); + } + + @Override + public long size() { + long size = getBase().size() - FileEncrypt.HEADER_LENGTH; + size = Math.max(0, size); + if ((size & FileEncrypt.BLOCK_SIZE_MASK) != 0) { + size -= FileEncrypt.BLOCK_SIZE; + } + return size; + } + + @Override + public OutputStream newOutputStream(boolean append) throws IOException { + return newFileChannelOutputStream(open("rw"), append); + } + + @Override + public InputStream newInputStream() throws IOException { + return Channels.newInputStream(open("r")); + } + + /** + * Split the file name into algorithm, password, and base file name. + * + * @param fileName the file name + * @return an array with algorithm, password, and base file name + */ + private String[] parse(String fileName) { + if (!fileName.startsWith(getScheme())) { + throw new IllegalArgumentException(fileName + + " doesn't start with " + getScheme()); + } + fileName = fileName.substring(getScheme().length() + 1); + int idx = fileName.indexOf(':'); + String password; + if (idx < 0) { + throw new IllegalArgumentException(fileName + + " doesn't contain encryption algorithm and password"); + } + password = fileName.substring(0, idx); + fileName = fileName.substring(idx + 1); + return new String[] { password, fileName }; + } + + /** + * Convert a char array to a byte array, in UTF-16 format. The char array is + * not cleared after use (this must be done by the caller). + * + * @param passwordChars the password characters + * @return the byte array + */ + public static byte[] getPasswordBytes(char[] passwordChars) { + // using UTF-16 + int len = passwordChars.length; + byte[] password = new byte[len * 2]; + for (int i = 0; i < len; i++) { + char c = passwordChars[i]; + password[i + i] = (byte) (c >>> 8); + password[i + i + 1] = (byte) c; + } + return password; + } + +} diff --git a/h2/src/main/org/h2/store/fs/encrypt/XTS.java b/h2/src/main/org/h2/store/fs/encrypt/XTS.java new file mode 100644 index 0000000..570ec3f --- /dev/null +++ b/h2/src/main/org/h2/store/fs/encrypt/XTS.java @@ -0,0 +1,129 @@ +/* + * 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.store.fs.encrypt; + +import org.h2.security.BlockCipher; + +/** + * An XTS implementation as described in + * IEEE P1619 (Standard Architecture for Encrypted Shared Storage Media). + * See also + * http://axelkenzo.ru/downloads/1619-2007-NIST-Submission.pdf + */ +class XTS { + + /** + * Galois field feedback. + */ + private static final int GF_128_FEEDBACK = 0x87; + + /** + * The AES encryption block size. + */ + private static final int CIPHER_BLOCK_SIZE = 16; + + private final BlockCipher cipher; + + XTS(BlockCipher cipher) { + this.cipher = cipher; + } + + /** + * Encrypt the data. + * + * @param id the (sector) id + * @param len the number of bytes + * @param data the data + * @param offset the offset within the data + */ + void encrypt(long id, int len, byte[] data, int offset) { + byte[] tweak = initTweak(id); + int i = 0; + for (; i + CIPHER_BLOCK_SIZE <= len; i += CIPHER_BLOCK_SIZE) { + if (i > 0) { + updateTweak(tweak); + } + xorTweak(data, i + offset, tweak); + cipher.encrypt(data, i + offset, CIPHER_BLOCK_SIZE); + xorTweak(data, i + offset, tweak); + } + if (i < len) { + updateTweak(tweak); + swap(data, i + offset, i - CIPHER_BLOCK_SIZE + offset, len - i); + xorTweak(data, i - CIPHER_BLOCK_SIZE + offset, tweak); + cipher.encrypt(data, i - CIPHER_BLOCK_SIZE + offset, CIPHER_BLOCK_SIZE); + xorTweak(data, i - CIPHER_BLOCK_SIZE + offset, tweak); + } + } + + /** + * Decrypt the data. + * + * @param id the (sector) id + * @param len the number of bytes + * @param data the data + * @param offset the offset within the data + */ + void decrypt(long id, int len, byte[] data, int offset) { + byte[] tweak = initTweak(id), tweakEnd = tweak; + int i = 0; + for (; i + CIPHER_BLOCK_SIZE <= len; i += CIPHER_BLOCK_SIZE) { + if (i > 0) { + updateTweak(tweak); + if (i + CIPHER_BLOCK_SIZE + CIPHER_BLOCK_SIZE > len && + i + CIPHER_BLOCK_SIZE < len) { + tweakEnd = tweak.clone(); + updateTweak(tweak); + } + } + xorTweak(data, i + offset, tweak); + cipher.decrypt(data, i + offset, CIPHER_BLOCK_SIZE); + xorTweak(data, i + offset, tweak); + } + if (i < len) { + swap(data, i, i - CIPHER_BLOCK_SIZE + offset, len - i + offset); + xorTweak(data, i - CIPHER_BLOCK_SIZE + offset, tweakEnd); + cipher.decrypt(data, i - CIPHER_BLOCK_SIZE + offset, CIPHER_BLOCK_SIZE); + xorTweak(data, i - CIPHER_BLOCK_SIZE + offset, tweakEnd); + } + } + + private byte[] initTweak(long id) { + byte[] tweak = new byte[CIPHER_BLOCK_SIZE]; + for (int j = 0; j < CIPHER_BLOCK_SIZE; j++, id >>>= 8) { + tweak[j] = (byte) (id & 0xff); + } + cipher.encrypt(tweak, 0, CIPHER_BLOCK_SIZE); + return tweak; + } + + private static void xorTweak(byte[] data, int pos, byte[] tweak) { + for (int i = 0; i < CIPHER_BLOCK_SIZE; i++) { + data[pos + i] ^= tweak[i]; + } + } + + private static void updateTweak(byte[] tweak) { + byte ci = 0, co = 0; + for (int i = 0; i < CIPHER_BLOCK_SIZE; i++) { + co = (byte) ((tweak[i] >> 7) & 1); + tweak[i] = (byte) (((tweak[i] << 1) + ci) & 255); + ci = co; + } + if (co != 0) { + tweak[0] ^= GF_128_FEEDBACK; + } + } + + private static void swap(byte[] data, int source, int target, int len) { + for (int i = 0; i < len; i++) { + byte temp = data[source + i]; + data[source + i] = data[target + i]; + data[target + i] = temp; + } + } + +} \ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/encrypt/package.html b/h2/src/main/org/h2/store/fs/encrypt/package.html new file mode 100644 index 0000000..84d70fc --- /dev/null +++ b/h2/src/main/org/h2/store/fs/encrypt/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +An encrypted file system abstraction. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/mem/FileMem.java b/h2/src/main/org/h2/store/fs/mem/FileMem.java new file mode 100644 index 0000000..ecf21ae --- /dev/null +++ b/h2/src/main/org/h2/store/fs/mem/FileMem.java @@ -0,0 +1,137 @@ +/* + * 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.store.fs.mem; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.FileLock; +import java.nio.channels.NonWritableChannelException; +import org.h2.store.fs.FakeFileChannel; +import org.h2.store.fs.FileBaseDefault; + +/** + * This class represents an in-memory file. + */ +class FileMem extends FileBaseDefault { + + /** + * The file data. + */ + final FileMemData data; + + private final boolean readOnly; + private volatile boolean closed; + + FileMem(FileMemData data, boolean readOnly) { + this.data = data; + this.readOnly = readOnly; + } + + @Override + public long size() { + return data.length(); + } + + @Override + protected void implTruncate(long newLength) throws IOException { + // compatibility with JDK FileChannel#truncate + if (readOnly) { + throw new NonWritableChannelException(); + } + if (closed) { + throw new ClosedChannelException(); + } + if (newLength < size()) { + data.touch(readOnly); + data.truncate(newLength); + } + } + + @Override + public int write(ByteBuffer src, long position) throws IOException { + if (closed) { + throw new ClosedChannelException(); + } + if (readOnly) { + throw new NonWritableChannelException(); + } + int len = src.remaining(); + if (len == 0) { + return 0; + } + data.touch(readOnly); + data.readWrite(position, src.array(), + src.arrayOffset() + src.position(), len, true); + src.position(src.position() + len); + return len; + } + + @Override + public int read(ByteBuffer dst, long position) throws IOException { + if (closed) { + throw new ClosedChannelException(); + } + int len = dst.remaining(); + if (len == 0) { + return 0; + } + long newPos = data.readWrite(position, dst.array(), + dst.arrayOffset() + dst.position(), len, false); + len = (int) (newPos - position); + if (len <= 0) { + return -1; + } + dst.position(dst.position() + len); + return len; + } + + @Override + public void implCloseChannel() throws IOException { + closed = true; + } + + @Override + public void force(boolean metaData) throws IOException { + // do nothing + } + + @Override + public FileLock tryLock(long position, long size, + boolean shared) throws IOException { + if (closed) { + throw new ClosedChannelException(); + } + if (shared) { + if (!data.lockShared()) { + return null; + } + } else { + if (!data.lockExclusive()) { + return null; + } + } + + return new FileLock(FakeFileChannel.INSTANCE, position, size, shared) { + + @Override + public boolean isValid() { + return true; + } + + @Override + public void release() throws IOException { + data.unlock(); + } + }; + } + + @Override + public String toString() { + return closed ? "" : data.getName(); + } + +} \ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/mem/FileMemData.java b/h2/src/main/org/h2/store/fs/mem/FileMemData.java new file mode 100644 index 0000000..3d15676 --- /dev/null +++ b/h2/src/main/org/h2/store/fs/mem/FileMemData.java @@ -0,0 +1,385 @@ +/* + * 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.store.fs.mem; + +import java.io.IOException; +import java.nio.channels.NonWritableChannelException; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import org.h2.compress.CompressLZF; +import org.h2.util.MathUtils; + +/** + * This class contains the data of an in-memory random access file. + * Data compression using the LZF algorithm is supported as well. + */ +class FileMemData { + + private static final int CACHE_SIZE = 8; + private static final int BLOCK_SIZE_SHIFT = 10; + private static final int BLOCK_SIZE = 1 << BLOCK_SIZE_SHIFT; + private static final int BLOCK_SIZE_MASK = BLOCK_SIZE - 1; + private static final CompressLZF LZF = new CompressLZF(); + private static final byte[] BUFFER = new byte[BLOCK_SIZE * 2]; + private static final byte[] COMPRESSED_EMPTY_BLOCK; + + private static final Cache COMPRESS_LATER = + new Cache<>(CACHE_SIZE); + + private String name; + private final int id; + private final boolean compress; + private volatile long length; + private AtomicReference[] data; + private long lastModified; + private boolean isReadOnly; + private boolean isLockedExclusive; + private int sharedLockCount; + + static { + byte[] n = new byte[BLOCK_SIZE]; + int len = LZF.compress(n, 0, BLOCK_SIZE, BUFFER, 0); + COMPRESSED_EMPTY_BLOCK = Arrays.copyOf(BUFFER, len); + } + + @SuppressWarnings("unchecked") + FileMemData(String name, boolean compress) { + this.name = name; + this.id = name.hashCode(); + this.compress = compress; + this.data = new AtomicReference[0]; + lastModified = System.currentTimeMillis(); + } + + /** + * Get the page if it exists. + * + * @param page the page id + * @return the byte array, or null + */ + private byte[] getPage(int page) { + AtomicReference[] b = data; + if (page >= b.length) { + return null; + } + return b[page].get(); + } + + /** + * Set the page data. + * + * @param page the page id + * @param oldData the old data + * @param newData the new data + * @param force whether the data should be overwritten even if the old data + * doesn't match + */ + private void setPage(int page, byte[] oldData, byte[] newData, boolean force) { + AtomicReference[] b = data; + if (page >= b.length) { + return; + } + if (force) { + b[page].set(newData); + } else { + b[page].compareAndSet(oldData, newData); + } + } + + int getId() { + return id; + } + + /** + * Lock the file in exclusive mode if possible. + * + * @return if locking was successful + */ + synchronized boolean lockExclusive() { + if (sharedLockCount > 0 || isLockedExclusive) { + return false; + } + isLockedExclusive = true; + return true; + } + + /** + * Lock the file in shared mode if possible. + * + * @return if locking was successful + */ + synchronized boolean lockShared() { + if (isLockedExclusive) { + return false; + } + sharedLockCount++; + return true; + } + + /** + * Unlock the file. + */ + synchronized void unlock() throws IOException { + if (isLockedExclusive) { + isLockedExclusive = false; + } else if (sharedLockCount > 0) { + sharedLockCount--; + } else { + throw new IOException("not locked"); + } + } + + /** + * This small cache compresses the data if an element leaves the cache. + */ + static class Cache extends LinkedHashMap { + + private static final long serialVersionUID = 1L; + private final int size; + + Cache(int size) { + super(size, (float) 0.75, true); + this.size = size; + } + + @Override + public synchronized V put(K key, V value) { + return super.put(key, value); + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + if (size() < size) { + return false; + } + CompressItem c = (CompressItem) eldest.getKey(); + c.file.compress(c.page); + return true; + } + } + + /** + * Points to a block of bytes that needs to be compressed. + */ + static class CompressItem { + + /** + * The file. + */ + FileMemData file; + + /** + * The page to compress. + */ + int page; + + @Override + public int hashCode() { + return page ^ file.getId(); + } + + @Override + public boolean equals(Object o) { + if (o instanceof CompressItem) { + CompressItem c = (CompressItem) o; + return c.page == page && c.file == file; + } + return false; + } + + } + + private void compressLater(int page) { + CompressItem c = new CompressItem(); + c.file = this; + c.page = page; + synchronized (LZF) { + COMPRESS_LATER.put(c, c); + } + } + + private byte[] expand(int page) { + byte[] d = getPage(page); + if (d.length == BLOCK_SIZE) { + return d; + } + byte[] out = new byte[BLOCK_SIZE]; + if (d != COMPRESSED_EMPTY_BLOCK) { + synchronized (LZF) { + LZF.expand(d, 0, d.length, out, 0, BLOCK_SIZE); + } + } + setPage(page, d, out, false); + return out; + } + + /** + * Compress the data in a byte array. + * + * @param page which page to compress + */ + void compress(int page) { + byte[] old = getPage(page); + if (old == null || old.length != BLOCK_SIZE) { + // not yet initialized or already compressed + return; + } + synchronized (LZF) { + int len = LZF.compress(old, 0, BLOCK_SIZE, BUFFER, 0); + if (len <= BLOCK_SIZE) { + byte[] d = Arrays.copyOf(BUFFER, len); + // maybe data was changed in the meantime + setPage(page, old, d, false); + } + } + } + + /** + * Update the last modified time. + * + * @param openReadOnly if the file was opened in read-only mode + */ + void touch(boolean openReadOnly) { + if (isReadOnly || openReadOnly) { + throw new NonWritableChannelException(); + } + lastModified = System.currentTimeMillis(); + } + + /** + * Get the file length. + * + * @return the length + */ + long length() { + return length; + } + + /** + * Truncate the file. + * + * @param newLength the new length + */ + void truncate(long newLength) { + changeLength(newLength); + long end = MathUtils.roundUpLong(newLength, BLOCK_SIZE); + if (end != newLength) { + int lastPage = (int) (newLength >>> BLOCK_SIZE_SHIFT); + byte[] d = expand(lastPage); + byte[] d2 = Arrays.copyOf(d, d.length); + for (int i = (int) (newLength & BLOCK_SIZE_MASK); i < BLOCK_SIZE; i++) { + d2[i] = 0; + } + setPage(lastPage, d, d2, true); + if (compress) { + compressLater(lastPage); + } + } + } + + private void changeLength(long len) { + length = len; + len = MathUtils.roundUpLong(len, BLOCK_SIZE); + int blocks = (int) (len >>> BLOCK_SIZE_SHIFT); + if (blocks != data.length) { + AtomicReference[] n = Arrays.copyOf(data, blocks); + for (int i = data.length; i < blocks; i++) { + n[i] = new AtomicReference<>(COMPRESSED_EMPTY_BLOCK); + } + data = n; + } + } + + /** + * Read or write. + * + * @param pos the position + * @param b the byte array + * @param off the offset within the byte array + * @param len the number of bytes + * @param write true for writing + * @return the new position + */ + long readWrite(long pos, byte[] b, int off, int len, boolean write) { + long end = pos + len; + if (end > length) { + if (write) { + changeLength(end); + } else { + len = (int) (length - pos); + } + } + while (len > 0) { + int l = (int) Math.min(len, BLOCK_SIZE - (pos & BLOCK_SIZE_MASK)); + int page = (int) (pos >>> BLOCK_SIZE_SHIFT); + byte[] block = expand(page); + int blockOffset = (int) (pos & BLOCK_SIZE_MASK); + if (write) { + byte[] p2 = Arrays.copyOf(block, block.length); + System.arraycopy(b, off, p2, blockOffset, l); + setPage(page, block, p2, true); + } else { + System.arraycopy(block, blockOffset, b, off, l); + } + if (compress) { + compressLater(page); + } + off += l; + pos += l; + len -= l; + } + return pos; + } + + /** + * Set the file name. + * + * @param name the name + */ + void setName(String name) { + this.name = name; + } + + /** + * Get the file name + * + * @return the name + */ + String getName() { + return name; + } + + /** + * Get the last modified time. + * + * @return the time + */ + long getLastModified() { + return lastModified; + } + + /** + * Check whether writing is allowed. + * + * @return true if it is + */ + boolean canWrite() { + return !isReadOnly; + } + + /** + * Set the read-only flag. + * + * @return true + */ + boolean setReadOnly() { + isReadOnly = true; + return true; + } + +} \ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/mem/FilePathMem.java b/h2/src/main/org/h2/store/fs/mem/FilePathMem.java new file mode 100644 index 0000000..37290bf --- /dev/null +++ b/h2/src/main/org/h2/store/fs/mem/FilePathMem.java @@ -0,0 +1,224 @@ +/* + * 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.store.fs.mem; + +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; +import org.h2.api.ErrorCode; +import org.h2.message.DbException; +import org.h2.store.fs.FilePath; + +/** + * This file system keeps files fully in memory. There is an option to compress + * file blocks to save memory. + */ +public class FilePathMem extends FilePath { + + private static final TreeMap MEMORY_FILES = + new TreeMap<>(); + private static final FileMemData DIRECTORY = new FileMemData("", false); + + @Override + public FilePathMem getPath(String path) { + FilePathMem p = new FilePathMem(); + p.name = getCanonicalPath(path); + return p; + } + + @Override + public long size() { + return getMemoryFile().length(); + } + + @Override + public void moveTo(FilePath newName, boolean atomicReplace) { + synchronized (MEMORY_FILES) { + if (!atomicReplace && !newName.name.equals(name) && + MEMORY_FILES.containsKey(newName.name)) { + throw DbException.get(ErrorCode.FILE_RENAME_FAILED_2, name, newName + " (exists)"); + } + FileMemData f = getMemoryFile(); + f.setName(newName.name); + MEMORY_FILES.remove(name); + MEMORY_FILES.put(newName.name, f); + } + } + + @Override + public boolean createFile() { + synchronized (MEMORY_FILES) { + if (exists()) { + return false; + } + getMemoryFile(); + } + return true; + } + + @Override + public boolean exists() { + if (isRoot()) { + return true; + } + synchronized (MEMORY_FILES) { + return MEMORY_FILES.get(name) != null; + } + } + + @Override + public void delete() { + if (isRoot()) { + return; + } + synchronized (MEMORY_FILES) { + FileMemData old = MEMORY_FILES.remove(name); + if (old != null) { + old.truncate(0); + } + } + } + + @Override + public List newDirectoryStream() { + ArrayList list = new ArrayList<>(); + synchronized (MEMORY_FILES) { + for (String n : MEMORY_FILES.tailMap(name).keySet()) { + if (n.startsWith(name)) { + if (!n.equals(name) && n.indexOf('/', name.length() + 1) < 0) { + list.add(getPath(n)); + } + } else { + break; + } + } + return list; + } + } + + @Override + public boolean setReadOnly() { + return getMemoryFile().setReadOnly(); + } + + @Override + public boolean canWrite() { + return getMemoryFile().canWrite(); + } + + @Override + public FilePathMem getParent() { + int idx = name.lastIndexOf('/'); + return idx < 0 ? null : getPath(name.substring(0, idx)); + } + + @Override + public boolean isDirectory() { + if (isRoot()) { + return true; + } + synchronized (MEMORY_FILES) { + FileMemData d = MEMORY_FILES.get(name); + return d == DIRECTORY; + } + } + + @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 + return true; + } + + @Override + public FilePathMem toRealPath() { + return this; + } + + @Override + public long lastModified() { + return getMemoryFile().getLastModified(); + } + + @Override + public void createDirectory() { + if (exists()) { + throw DbException.get(ErrorCode.FILE_CREATION_FAILED_1, + name + " (a file with this name already exists)"); + } + synchronized (MEMORY_FILES) { + MEMORY_FILES.put(name, DIRECTORY); + } + } + + @Override + public FileChannel open(String mode) { + FileMemData obj = getMemoryFile(); + return new FileMem(obj, "r".equals(mode)); + } + + private FileMemData getMemoryFile() { + synchronized (MEMORY_FILES) { + FileMemData m = MEMORY_FILES.get(name); + if (m == DIRECTORY) { + throw DbException.get(ErrorCode.FILE_CREATION_FAILED_1, + name + " (a directory with this name already exists)"); + } + if (m == null) { + m = new FileMemData(name, compressed()); + MEMORY_FILES.put(name, m); + } + return m; + } + } + + private boolean isRoot() { + return name.equals(getScheme() + ":"); + } + + /** + * Get the canonical path for this file name. + * + * @param fileName the file name + * @return the canonical path + */ + protected static String getCanonicalPath(String fileName) { + fileName = fileName.replace('\\', '/'); + int idx = fileName.indexOf(':') + 1; + if (fileName.length() > idx && fileName.charAt(idx) != '/') { + fileName = fileName.substring(0, idx) + "/" + fileName.substring(idx); + } + return fileName; + } + + @Override + public String getScheme() { + return "memFS"; + } + + /** + * Whether the file should be compressed. + * + * @return if it should be compressed. + */ + boolean compressed() { + return false; + } + +} + + diff --git a/h2/src/main/org/h2/store/fs/mem/FilePathMemLZF.java b/h2/src/main/org/h2/store/fs/mem/FilePathMemLZF.java new file mode 100644 index 0000000..19c7aba --- /dev/null +++ b/h2/src/main/org/h2/store/fs/mem/FilePathMemLZF.java @@ -0,0 +1,30 @@ +/* + * 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.store.fs.mem; + +/** + * A memory file system that compresses blocks to conserve memory. + */ +public class FilePathMemLZF extends FilePathMem { + + @Override + public FilePathMem getPath(String path) { + FilePathMemLZF p = new FilePathMemLZF(); + p.name = getCanonicalPath(path); + return p; + } + + @Override + boolean compressed() { + return true; + } + + @Override + public String getScheme() { + return "memLZF"; + } + +} \ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/mem/package.html b/h2/src/main/org/h2/store/fs/mem/package.html new file mode 100644 index 0000000..3858793 --- /dev/null +++ b/h2/src/main/org/h2/store/fs/mem/package.html @@ -0,0 +1,15 @@ + + + + +Javadoc package documentation +

+ +This file system keeps files fully in memory. +There is an option to compress file blocks to save memory. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/niomapped/FileNioMapped.java b/h2/src/main/org/h2/store/fs/niomapped/FileNioMapped.java new file mode 100644 index 0000000..2ea73dd --- /dev/null +++ b/h2/src/main/org/h2/store/fs/niomapped/FileNioMapped.java @@ -0,0 +1,209 @@ +/* + * 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.store.fs.niomapped; + +import java.io.EOFException; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.NonWritableChannelException; +import java.nio.file.Paths; +import org.h2.engine.SysProperties; +import org.h2.store.fs.FileBaseDefault; +import org.h2.store.fs.FileUtils; +import org.h2.util.MemoryUnmapper; + +/** + * Uses memory mapped files. + * The file size is limited to 2 GB. + */ +class FileNioMapped extends FileBaseDefault { + + private static final int GC_TIMEOUT_MS = 10_000; + private final String name; + private final MapMode mode; + private FileChannel channel; + private MappedByteBuffer mapped; + private long fileLength; + + FileNioMapped(String fileName, String mode) throws IOException { + if ("r".equals(mode)) { + this.mode = MapMode.READ_ONLY; + } else { + this.mode = MapMode.READ_WRITE; + } + this.name = fileName; + channel = FileChannel.open(Paths.get(fileName), FileUtils.modeToOptions(mode), FileUtils.NO_ATTRIBUTES); + reMap(); + } + + private void unMap() throws IOException { + if (mapped == null) { + return; + } + // first write all data + mapped.force(); + + // need to dispose old direct buffer, see bug + // https://bugs.openjdk.java.net/browse/JDK-4724038 + + if (SysProperties.NIO_CLEANER_HACK) { + if (MemoryUnmapper.unmap(mapped)) { + mapped = null; + return; + } + } + WeakReference bufferWeakRef = new WeakReference<>(mapped); + mapped = null; + long stopAt = System.nanoTime() + GC_TIMEOUT_MS * 1_000_000L; + while (bufferWeakRef.get() != null) { + if (System.nanoTime() - stopAt > 0L) { + throw new IOException("Timeout (" + GC_TIMEOUT_MS + " ms) reached while trying to GC mapped buffer"); + } + System.gc(); + Thread.yield(); + } + } + + /** + * Re-map byte buffer into memory, called when file size has changed or file + * was created. + */ + private void reMap() throws IOException { + if (mapped != null) { + unMap(); + } + fileLength = channel.size(); + checkFileSizeLimit(fileLength); + // maps new MappedByteBuffer; the old one is disposed during GC + mapped = channel.map(mode, 0, fileLength); + int limit = mapped.limit(); + int capacity = mapped.capacity(); + if (limit < fileLength || capacity < fileLength) { + throw new IOException("Unable to map: length=" + limit + + " capacity=" + capacity + " length=" + fileLength); + } + if (SysProperties.NIO_LOAD_MAPPED) { + mapped.load(); + } + } + + private static void checkFileSizeLimit(long length) throws IOException { + if (length > Integer.MAX_VALUE) { + throw new IOException( + "File over 2GB is not supported yet when using this file system"); + } + } + + @Override + public void implCloseChannel() throws IOException { + if (channel != null) { + unMap(); + channel.close(); + channel = null; + } + } + + @Override + public String toString() { + return "nioMapped:" + name; + } + + @Override + public synchronized long size() throws IOException { + return fileLength; + } + + @Override + public synchronized int read(ByteBuffer dst, long pos) throws IOException { + checkFileSizeLimit(pos); + try { + int len = dst.remaining(); + if (len == 0) { + return 0; + } + len = (int) Math.min(len, fileLength - pos); + if (len <= 0) { + return -1; + } + mapped.position((int)pos); + mapped.get(dst.array(), dst.arrayOffset() + dst.position(), len); + dst.position(dst.position() + len); + pos += len; + return len; + } catch (IllegalArgumentException | BufferUnderflowException e) { + EOFException e2 = new EOFException("EOF"); + e2.initCause(e); + throw e2; + } + } + + @Override + protected void implTruncate(long newLength) throws IOException { + // compatibility with JDK FileChannel#truncate + if (mode == MapMode.READ_ONLY) { + throw new NonWritableChannelException(); + } + if (newLength < size()) { + setFileLength(newLength); + } + } + + public synchronized void setFileLength(long newLength) throws IOException { + if (mode == MapMode.READ_ONLY) { + throw new NonWritableChannelException(); + } + checkFileSizeLimit(newLength); + unMap(); + for (int i = 0;; i++) { + try { + long length = channel.size(); + if (length >= newLength) { + channel.truncate(newLength); + } else { + channel.write(ByteBuffer.wrap(new byte[1]), newLength - 1); + } + break; + } catch (IOException e) { + if (i > 16 || !e.toString().contains("user-mapped section open")) { + throw e; + } + } + System.gc(); + } + reMap(); + } + + @Override + public void force(boolean metaData) throws IOException { + mapped.force(); + channel.force(metaData); + } + + @Override + public synchronized int write(ByteBuffer src, long position) throws IOException { + checkFileSizeLimit(position); + int len = src.remaining(); + // check if need to expand file + if (mapped.capacity() < position + len) { + setFileLength(position + len); + } + mapped.position((int)position); + mapped.put(src); + return len; + } + + @Override + public synchronized FileLock tryLock(long position, long size, + boolean shared) throws IOException { + return channel.tryLock(position, size, shared); + } + +} \ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/niomapped/FilePathNioMapped.java b/h2/src/main/org/h2/store/fs/niomapped/FilePathNioMapped.java new file mode 100644 index 0000000..2479478 --- /dev/null +++ b/h2/src/main/org/h2/store/fs/niomapped/FilePathNioMapped.java @@ -0,0 +1,28 @@ +/* + * 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.store.fs.niomapped; + +import java.io.IOException; +import java.nio.channels.FileChannel; +import org.h2.store.fs.FilePathWrapper; + +/** + * This file system stores files on disk and uses java.nio to access the files. + * This class used memory mapped files. + */ +public class FilePathNioMapped extends FilePathWrapper { + + @Override + public FileChannel open(String mode) throws IOException { + return new FileNioMapped(name.substring(getScheme().length() + 1), mode); + } + + @Override + public String getScheme() { + return "nioMapped"; + } + +} diff --git a/h2/src/main/org/h2/store/fs/niomapped/package.html b/h2/src/main/org/h2/store/fs/niomapped/package.html new file mode 100644 index 0000000..ef22adf --- /dev/null +++ b/h2/src/main/org/h2/store/fs/niomapped/package.html @@ -0,0 +1,15 @@ + + + + +Javadoc package documentation +

+ +This file system stores files on disk and uses java.nio to access the files. +This class used memory mapped files. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/niomem/FileNioMem.java b/h2/src/main/org/h2/store/fs/niomem/FileNioMem.java new file mode 100644 index 0000000..5bc4ad2 --- /dev/null +++ b/h2/src/main/org/h2/store/fs/niomem/FileNioMem.java @@ -0,0 +1,131 @@ +/* + * 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.store.fs.niomem; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.FileLock; +import java.nio.channels.NonWritableChannelException; +import org.h2.store.fs.FakeFileChannel; +import org.h2.store.fs.FileBaseDefault; + +/** + * This class represents an in-memory file. + */ +class FileNioMem extends FileBaseDefault { + + /** + * The file data. + */ + final FileNioMemData data; + + private final boolean readOnly; + private volatile boolean closed; + + FileNioMem(FileNioMemData data, boolean readOnly) { + this.data = data; + this.readOnly = readOnly; + } + + @Override + public long size() { + return data.length(); + } + + @Override + protected void implTruncate(long newLength) throws IOException { + // compatibility with JDK FileChannel#truncate + if (readOnly) { + throw new NonWritableChannelException(); + } + if (closed) { + throw new ClosedChannelException(); + } + if (newLength < size()) { + data.touch(readOnly); + data.truncate(newLength); + } + } + + @Override + public int write(ByteBuffer src, long position) throws IOException { + if (closed) { + throw new ClosedChannelException(); + } + data.touch(readOnly); + // offset is 0 because we start writing from src.position() + long newPosition = data.readWrite(position, src, 0, src.remaining(), true); + int len = (int)(newPosition - position); + src.position(src.position() + len); + return len; + } + + @Override + public int read(ByteBuffer dst, long position) throws IOException { + if (closed) { + throw new ClosedChannelException(); + } + int len = dst.remaining(); + if (len == 0) { + return 0; + } + long newPos; + newPos = data.readWrite(position, dst, dst.position(), len, false); + len = (int) (newPos - position); + if (len <= 0) { + return -1; + } + dst.position(dst.position() + len); + return len; + } + + @Override + public void implCloseChannel() throws IOException { + closed = true; + } + + @Override + public void force(boolean metaData) throws IOException { + // do nothing + } + + @Override + public FileLock tryLock(long position, long size, + boolean shared) throws IOException { + if (closed) { + throw new ClosedChannelException(); + } + if (shared) { + if (!data.lockShared()) { + return null; + } + } else { + if (!data.lockExclusive()) { + return null; + } + } + + return new FileLock(FakeFileChannel.INSTANCE, position, size, shared) { + + @Override + public boolean isValid() { + return true; + } + + @Override + public void release() throws IOException { + data.unlock(); + } + }; + } + + @Override + public String toString() { + return closed ? "" : data.getName(); + } + +} \ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/niomem/FileNioMemData.java b/h2/src/main/org/h2/store/fs/niomem/FileNioMemData.java new file mode 100644 index 0000000..e98f7d8 --- /dev/null +++ b/h2/src/main/org/h2/store/fs/niomem/FileNioMemData.java @@ -0,0 +1,394 @@ +/* + * 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.store.fs.niomem; + +import java.nio.ByteBuffer; +import java.nio.channels.NonWritableChannelException; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import org.h2.compress.CompressLZF; +import org.h2.util.MathUtils; + +/** + * This class contains the data of an in-memory random access file. + * Data compression using the LZF algorithm is supported as well. + */ +class FileNioMemData { + + private static final int CACHE_MIN_SIZE = 8; + private static final int BLOCK_SIZE_SHIFT = 16; + + private static final int BLOCK_SIZE = 1 << BLOCK_SIZE_SHIFT; + private static final int BLOCK_SIZE_MASK = BLOCK_SIZE - 1; + private static final ByteBuffer COMPRESSED_EMPTY_BLOCK; + + private static final ThreadLocal LZF_THREAD_LOCAL = ThreadLocal.withInitial(CompressLZF::new); + + /** the output buffer when compressing */ + private static final ThreadLocal COMPRESS_OUT_BUF_THREAD_LOCAL = ThreadLocal + .withInitial(() -> new byte[BLOCK_SIZE * 2]); + + /** + * The hash code of the name. + */ + final int nameHashCode; + + private final CompressLaterCache compressLaterCache = + new CompressLaterCache<>(CACHE_MIN_SIZE); + + private String name; + private final boolean compress; + private final float compressLaterCachePercent; + private volatile long length; + private AtomicReference[] buffers; + private long lastModified; + private boolean isReadOnly; + private boolean isLockedExclusive; + private int sharedLockCount; + private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); + + static { + final byte[] n = new byte[BLOCK_SIZE]; + final byte[] output = new byte[BLOCK_SIZE * 2]; + int len = new CompressLZF().compress(n, 0, BLOCK_SIZE, output, 0); + COMPRESSED_EMPTY_BLOCK = ByteBuffer.allocateDirect(len); + COMPRESSED_EMPTY_BLOCK.put(output, 0, len); + } + + @SuppressWarnings("unchecked") + FileNioMemData(String name, boolean compress, float compressLaterCachePercent) { + this.name = name; + this.nameHashCode = name.hashCode(); + this.compress = compress; + this.compressLaterCachePercent = compressLaterCachePercent; + buffers = new AtomicReference[0]; + lastModified = System.currentTimeMillis(); + } + + /** + * Lock the file in exclusive mode if possible. + * + * @return if locking was successful + */ + synchronized boolean lockExclusive() { + if (sharedLockCount > 0 || isLockedExclusive) { + return false; + } + isLockedExclusive = true; + return true; + } + + /** + * Lock the file in shared mode if possible. + * + * @return if locking was successful + */ + synchronized boolean lockShared() { + if (isLockedExclusive) { + return false; + } + sharedLockCount++; + return true; + } + + /** + * Unlock the file. + */ + synchronized void unlock() { + if (isLockedExclusive) { + isLockedExclusive = false; + } else { + sharedLockCount = Math.max(0, sharedLockCount - 1); + } + } + + /** + * This small cache compresses the data if an element leaves the cache. + */ + static class CompressLaterCache extends LinkedHashMap { + + private static final long serialVersionUID = 1L; + private int size; + + CompressLaterCache(int size) { + super(size, (float) 0.75, true); + this.size = size; + } + + @Override + public synchronized V put(K key, V value) { + return super.put(key, value); + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + if (size() < size) { + return false; + } + CompressItem c = (CompressItem) eldest.getKey(); + c.data.compressPage(c.page); + return true; + } + + public void setCacheSize(int size) { + this.size = size; + } + } + + /** + * Represents a compressed item. + */ + static class CompressItem { + + /** + * The file data. + */ + public final FileNioMemData data; + + /** + * The page to compress. + */ + public final int page; + + public CompressItem(FileNioMemData data, int page) { + this.data = data; + this.page = page; + } + + @Override + public int hashCode() { + return page ^ data.nameHashCode; + } + + @Override + public boolean equals(Object o) { + if (o instanceof CompressItem) { + CompressItem c = (CompressItem) o; + return c.data == data && c.page == page; + } + return false; + } + + } + + private void addToCompressLaterCache(int page) { + CompressItem c = new CompressItem(this, page); + compressLaterCache.put(c, c); + } + + private ByteBuffer expandPage(int page) { + final ByteBuffer d = buffers[page].get(); + if (d.capacity() == BLOCK_SIZE) { + // already expanded, or not compressed + return d; + } + synchronized (d) { + if (d.capacity() == BLOCK_SIZE) { + return d; + } + ByteBuffer out = ByteBuffer.allocateDirect(BLOCK_SIZE); + if (d != COMPRESSED_EMPTY_BLOCK) { + d.position(0); + CompressLZF.expand(d, out); + } + buffers[page].compareAndSet(d, out); + return out; + } + } + + /** + * Compress the data in a byte array. + * + * @param page which page to compress + */ + void compressPage(int page) { + final ByteBuffer d = buffers[page].get(); + synchronized (d) { + if (d.capacity() != BLOCK_SIZE) { + // already compressed + return; + } + final byte[] compressOutputBuffer = COMPRESS_OUT_BUF_THREAD_LOCAL.get(); + int len = LZF_THREAD_LOCAL.get().compress(d, 0, compressOutputBuffer, 0); + ByteBuffer out = ByteBuffer.allocateDirect(len); + out.put(compressOutputBuffer, 0, len); + buffers[page].compareAndSet(d, out); + } + } + + /** + * Update the last modified time. + * + * @param openReadOnly if the file was opened in read-only mode + */ + void touch(boolean openReadOnly) { + if (isReadOnly || openReadOnly) { + throw new NonWritableChannelException(); + } + lastModified = System.currentTimeMillis(); + } + + /** + * Get the file length. + * + * @return the length + */ + long length() { + return length; + } + + /** + * Truncate the file. + * + * @param newLength the new length + */ + void truncate(long newLength) { + rwLock.writeLock().lock(); + try { + changeLength(newLength); + long end = MathUtils.roundUpLong(newLength, BLOCK_SIZE); + if (end != newLength) { + int lastPage = (int) (newLength >>> BLOCK_SIZE_SHIFT); + ByteBuffer d = expandPage(lastPage); + for (int i = (int) (newLength & BLOCK_SIZE_MASK); i < BLOCK_SIZE; i++) { + d.put(i, (byte) 0); + } + if (compress) { + addToCompressLaterCache(lastPage); + } + } + } finally { + rwLock.writeLock().unlock(); + } + } + + @SuppressWarnings("unchecked") + private void changeLength(long len) { + length = len; + len = MathUtils.roundUpLong(len, BLOCK_SIZE); + int blocks = (int) (len >>> BLOCK_SIZE_SHIFT); + if (blocks != buffers.length) { + final AtomicReference[] newBuffers = new AtomicReference[blocks]; + System.arraycopy(buffers, 0, newBuffers, 0, + Math.min(buffers.length, newBuffers.length)); + for (int i = buffers.length; i < blocks; i++) { + newBuffers[i] = new AtomicReference<>(COMPRESSED_EMPTY_BLOCK); + } + buffers = newBuffers; + } + compressLaterCache.setCacheSize(Math.max(CACHE_MIN_SIZE, (int) (blocks * + compressLaterCachePercent / 100))); + } + + /** + * Read or write. + * + * @param pos the position + * @param b the byte array + * @param off the offset within the byte array + * @param len the number of bytes + * @param write true for writing + * @return the new position + */ + long readWrite(long pos, ByteBuffer b, int off, int len, boolean write) { + final java.util.concurrent.locks.Lock lock = write ? rwLock.writeLock() + : rwLock.readLock(); + lock.lock(); + try { + + long end = pos + len; + if (end > length) { + if (write) { + changeLength(end); + } else { + len = (int) (length - pos); + } + } + while (len > 0) { + final int l = (int) Math.min(len, BLOCK_SIZE - (pos & BLOCK_SIZE_MASK)); + final int page = (int) (pos >>> BLOCK_SIZE_SHIFT); + final ByteBuffer block = expandPage(page); + int blockOffset = (int) (pos & BLOCK_SIZE_MASK); + if (write) { + final ByteBuffer srcTmp = b.slice(); + final ByteBuffer dstTmp = block.duplicate(); + srcTmp.position(off); + srcTmp.limit(off + l); + dstTmp.position(blockOffset); + dstTmp.put(srcTmp); + } else { + // duplicate, so this can be done concurrently + final ByteBuffer tmp = block.duplicate(); + tmp.position(blockOffset); + tmp.limit(l + blockOffset); + int oldPosition = b.position(); + b.position(off); + b.put(tmp); + // restore old position + b.position(oldPosition); + } + if (compress) { + addToCompressLaterCache(page); + } + off += l; + pos += l; + len -= l; + } + return pos; + } finally { + lock.unlock(); + } + } + + /** + * Set the file name. + * + * @param name the name + */ + void setName(String name) { + this.name = name; + } + + /** + * Get the file name + * + * @return the name + */ + String getName() { + return name; + } + + /** + * Get the last modified time. + * + * @return the time + */ + long getLastModified() { + return lastModified; + } + + /** + * Check whether writing is allowed. + * + * @return true if it is + */ + boolean canWrite() { + return !isReadOnly; + } + + /** + * Set the read-only flag. + * + * @return true + */ + boolean setReadOnly() { + isReadOnly = true; + return true; + } + +} \ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/niomem/FilePathNioMem.java b/h2/src/main/org/h2/store/fs/niomem/FilePathNioMem.java new file mode 100644 index 0000000..c9cb0cb --- /dev/null +++ b/h2/src/main/org/h2/store/fs/niomem/FilePathNioMem.java @@ -0,0 +1,220 @@ +/* + * 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.store.fs.niomem; + +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; +import org.h2.api.ErrorCode; +import org.h2.message.DbException; +import org.h2.store.fs.FilePath; + +/** + * This file system keeps files fully in off-java-heap memory. There is an option to compress + * file blocks to save memory. + */ +public class FilePathNioMem extends FilePath { + + private static final TreeMap MEMORY_FILES = + new TreeMap<>(); + + /** + * The percentage of uncompressed (cached) entries. + */ + float compressLaterCachePercent = 1; + + @Override + public FilePathNioMem getPath(String path) { + FilePathNioMem p = new FilePathNioMem(); + p.name = getCanonicalPath(path); + return p; + } + + @Override + public long size() { + return getMemoryFile().length(); + } + + @Override + public void moveTo(FilePath newName, boolean atomicReplace) { + synchronized (MEMORY_FILES) { + if (!atomicReplace && !name.equals(newName.name) && + MEMORY_FILES.containsKey(newName.name)) { + throw DbException.get(ErrorCode.FILE_RENAME_FAILED_2, name, newName + " (exists)"); + } + FileNioMemData f = getMemoryFile(); + f.setName(newName.name); + MEMORY_FILES.remove(name); + MEMORY_FILES.put(newName.name, f); + } + } + + @Override + public boolean createFile() { + synchronized (MEMORY_FILES) { + if (exists()) { + return false; + } + getMemoryFile(); + } + return true; + } + + @Override + public boolean exists() { + if (isRoot()) { + return true; + } + synchronized (MEMORY_FILES) { + return MEMORY_FILES.get(name) != null; + } + } + + @Override + public void delete() { + if (isRoot()) { + return; + } + synchronized (MEMORY_FILES) { + MEMORY_FILES.remove(name); + } + } + + @Override + public List newDirectoryStream() { + ArrayList list = new ArrayList<>(); + synchronized (MEMORY_FILES) { + for (String n : MEMORY_FILES.tailMap(name).keySet()) { + if (n.startsWith(name)) { + list.add(getPath(n)); + } else { + break; + } + } + return list; + } + } + + @Override + public boolean setReadOnly() { + return getMemoryFile().setReadOnly(); + } + + @Override + public boolean canWrite() { + return getMemoryFile().canWrite(); + } + + @Override + public FilePathNioMem getParent() { + int idx = name.lastIndexOf('/'); + return idx < 0 ? null : getPath(name.substring(0, idx)); + } + + @Override + public boolean isDirectory() { + if (isRoot()) { + return true; + } + // TODO in memory file system currently + // does not really support directories + synchronized (MEMORY_FILES) { + return MEMORY_FILES.get(name) == null; + } + } + + @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 + return true; + } + + @Override + public FilePathNioMem toRealPath() { + return this; + } + + @Override + public long lastModified() { + return getMemoryFile().getLastModified(); + } + + @Override + public void createDirectory() { + if (exists() && isDirectory()) { + throw DbException.get(ErrorCode.FILE_CREATION_FAILED_1, + name + " (a file with this name already exists)"); + } + // TODO directories are not really supported + } + + @Override + public FileChannel open(String mode) { + FileNioMemData obj = getMemoryFile(); + return new FileNioMem(obj, "r".equals(mode)); + } + + private FileNioMemData getMemoryFile() { + synchronized (MEMORY_FILES) { + FileNioMemData m = MEMORY_FILES.get(name); + if (m == null) { + m = new FileNioMemData(name, compressed(), compressLaterCachePercent); + MEMORY_FILES.put(name, m); + } + return m; + } + } + + protected boolean isRoot() { + return name.equals(getScheme() + ":"); + } + + /** + * Get the canonical path of a file (with backslashes replaced with forward + * slashes). + * + * @param fileName the file name + * @return the canonical path + */ + protected static String getCanonicalPath(String fileName) { + fileName = fileName.replace('\\', '/'); + int idx = fileName.lastIndexOf(':') + 1; + if (fileName.length() > idx && fileName.charAt(idx) != '/') { + fileName = fileName.substring(0, idx) + "/" + fileName.substring(idx); + } + return fileName; + } + + @Override + public String getScheme() { + return "nioMemFS"; + } + + /** + * Whether the file should be compressed. + * + * @return true if it should be compressed. + */ + boolean compressed() { + return false; + } + +} + + diff --git a/h2/src/main/org/h2/store/fs/niomem/FilePathNioMemLZF.java b/h2/src/main/org/h2/store/fs/niomem/FilePathNioMemLZF.java new file mode 100644 index 0000000..7ef048f --- /dev/null +++ b/h2/src/main/org/h2/store/fs/niomem/FilePathNioMemLZF.java @@ -0,0 +1,44 @@ +/* + * 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.store.fs.niomem; + +/** + * A memory file system that compresses blocks to conserve memory. + */ +public class FilePathNioMemLZF extends FilePathNioMem { + + @Override + boolean compressed() { + return true; + } + + @Override + public FilePathNioMem getPath(String path) { + if (!path.startsWith(getScheme())) { + throw new IllegalArgumentException(path + + " doesn't start with " + getScheme()); + } + int idx1 = path.indexOf(':'); + int idx2 = path.lastIndexOf(':'); + final FilePathNioMemLZF p = new FilePathNioMemLZF(); + if (idx1 != -1 && idx1 != idx2) { + p.compressLaterCachePercent = Float.parseFloat(path.substring(idx1 + 1, idx2)); + } + p.name = getCanonicalPath(path); + return p; + } + + @Override + protected boolean isRoot() { + return name.lastIndexOf(':') == name.length() - 1; + } + + @Override + public String getScheme() { + return "nioMemLZF"; + } + +} \ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/niomem/package.html b/h2/src/main/org/h2/store/fs/niomem/package.html new file mode 100644 index 0000000..6197af1 --- /dev/null +++ b/h2/src/main/org/h2/store/fs/niomem/package.html @@ -0,0 +1,15 @@ + + + + +Javadoc package documentation +

+ +This file system keeps files fully in off-java-heap memory. +There is an option to compress file blocks to save memory. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/package.html b/h2/src/main/org/h2/store/fs/package.html new file mode 100644 index 0000000..1797c0e --- /dev/null +++ b/h2/src/main/org/h2/store/fs/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +A file system abstraction. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/rec/FilePathRec.java b/h2/src/main/org/h2/store/fs/rec/FilePathRec.java new file mode 100644 index 0000000..14c63bb --- /dev/null +++ b/h2/src/main/org/h2/store/fs/rec/FilePathRec.java @@ -0,0 +1,119 @@ +/* + * 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.store.fs.rec; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.channels.FileChannel; +import org.h2.store.fs.FilePath; +import org.h2.store.fs.FilePathWrapper; +import org.h2.store.fs.Recorder; + +/** + * A file system that records all write operations and can re-play them. + */ +public class FilePathRec extends FilePathWrapper { + + private static final FilePathRec INSTANCE = new FilePathRec(); + + private static Recorder recorder; + + private boolean trace; + + /** + * Register the file system. + */ + public static void register() { + FilePath.register(INSTANCE); + } + + /** + * Set the recorder class. + * + * @param recorder the recorder + */ + public static void setRecorder(Recorder recorder) { + FilePathRec.recorder = recorder; + } + + @Override + public boolean createFile() { + log(Recorder.CREATE_NEW_FILE, name); + return super.createFile(); + } + + @Override + public FilePath createTempFile(String suffix, boolean inTempDir) throws IOException { + log(Recorder.CREATE_TEMP_FILE, unwrap(name) + ":" + suffix + ":" + inTempDir); + return super.createTempFile(suffix, inTempDir); + } + + @Override + public void delete() { + log(Recorder.DELETE, name); + super.delete(); + } + + @Override + public FileChannel open(String mode) throws IOException { + return new FileRec(this, super.open(mode), name); + } + + @Override + public OutputStream newOutputStream(boolean append) throws IOException { + log(Recorder.OPEN_OUTPUT_STREAM, name); + return super.newOutputStream(append); + } + + @Override + public void moveTo(FilePath newPath, boolean atomicReplace) { + log(Recorder.RENAME, unwrap(name) + ":" + unwrap(newPath.name)); + super.moveTo(newPath, atomicReplace); + } + + public boolean isTrace() { + return trace; + } + + public void setTrace(boolean trace) { + this.trace = trace; + } + + /** + * Log the operation. + * + * @param op the operation + * @param fileName the file name(s) + */ + void log(int op, String fileName) { + log(op, fileName, null, 0); + } + + /** + * Log the operation. + * + * @param op the operation + * @param fileName the file name + * @param data the data or null + * @param x the value or 0 + */ + void log(int op, String fileName, byte[] data, long x) { + if (recorder != null) { + recorder.log(op, fileName, data, x); + } + } + + /** + * Get the prefix for this file system. + * + * @return the prefix + */ + @Override + public String getScheme() { + return "rec"; + } + +} diff --git a/h2/src/main/org/h2/store/fs/rec/FileRec.java b/h2/src/main/org/h2/store/fs/rec/FileRec.java new file mode 100644 index 0000000..3bee02e --- /dev/null +++ b/h2/src/main/org/h2/store/fs/rec/FileRec.java @@ -0,0 +1,111 @@ +/* + * 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.store.fs.rec; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.util.Arrays; +import org.h2.store.fs.FileBase; +import org.h2.store.fs.Recorder; + +/** + * A file object that records all write operations and can re-play them. + */ +class FileRec extends FileBase { + + private final FilePathRec rec; + private final FileChannel channel; + private final String name; + + FileRec(FilePathRec rec, FileChannel file, String fileName) { + this.rec = rec; + this.channel = file; + this.name = fileName; + } + + @Override + public void implCloseChannel() throws IOException { + channel.close(); + } + + @Override + public long position() throws IOException { + return channel.position(); + } + + @Override + public long size() throws IOException { + return channel.size(); + } + + @Override + public int read(ByteBuffer dst) throws IOException { + return channel.read(dst); + } + + @Override + public int read(ByteBuffer dst, long position) throws IOException { + return channel.read(dst, position); + } + + @Override + public FileChannel position(long pos) throws IOException { + channel.position(pos); + return this; + } + + @Override + public FileChannel truncate(long newLength) throws IOException { + rec.log(Recorder.TRUNCATE, name, null, newLength); + channel.truncate(newLength); + return this; + } + + @Override + public void force(boolean metaData) throws IOException { + channel.force(metaData); + } + + @Override + public int write(ByteBuffer src) throws IOException { + byte[] buff = src.array(); + int len = src.remaining(); + if (src.position() != 0 || len != buff.length) { + int offset = src.arrayOffset() + src.position(); + buff = Arrays.copyOfRange(buff, offset, offset + len); + } + int result = channel.write(src); + rec.log(Recorder.WRITE, name, buff, channel.position()); + return result; + } + + @Override + public int write(ByteBuffer src, long position) throws IOException { + byte[] buff = src.array(); + int len = src.remaining(); + if (src.position() != 0 || len != buff.length) { + int offset = src.arrayOffset() + src.position(); + buff = Arrays.copyOfRange(buff, offset, offset + len); + } + int result = channel.write(src, position); + rec.log(Recorder.WRITE, name, buff, position); + return result; + } + + @Override + public synchronized FileLock tryLock(long position, long size, + boolean shared) throws IOException { + return channel.tryLock(position, size, shared); + } + + @Override + public String toString() { + return name; + } + +} \ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/rec/package.html b/h2/src/main/org/h2/store/fs/rec/package.html new file mode 100644 index 0000000..23ddc8d --- /dev/null +++ b/h2/src/main/org/h2/store/fs/rec/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +A file system that records all write operations and can re-play them. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/retry/FilePathRetryOnInterrupt.java b/h2/src/main/org/h2/store/fs/retry/FilePathRetryOnInterrupt.java new file mode 100644 index 0000000..1279be1 --- /dev/null +++ b/h2/src/main/org/h2/store/fs/retry/FilePathRetryOnInterrupt.java @@ -0,0 +1,35 @@ +/* + * 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.store.fs.retry; + +import java.io.IOException; +import java.nio.channels.FileChannel; +import org.h2.store.fs.FilePathWrapper; + +/** + * A file system that re-opens and re-tries the operation if the file was + * closed, because a thread was interrupted. This will clear the interrupt flag. + * It is mainly useful for applications that call Thread.interrupt by mistake. + */ +public class FilePathRetryOnInterrupt extends FilePathWrapper { + + /** + * The prefix. + */ + static final String SCHEME = "retry"; + + @Override + public FileChannel open(String mode) throws IOException { + return new FileRetryOnInterrupt(name.substring(getScheme().length() + 1), mode); + } + + @Override + public String getScheme() { + return SCHEME; + } + +} + diff --git a/h2/src/main/org/h2/store/fs/retry/FileRetryOnInterrupt.java b/h2/src/main/org/h2/store/fs/retry/FileRetryOnInterrupt.java new file mode 100644 index 0000000..e3508b2 --- /dev/null +++ b/h2/src/main/org/h2/store/fs/retry/FileRetryOnInterrupt.java @@ -0,0 +1,234 @@ +/* + * 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.store.fs.retry; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedByInterruptException; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import org.h2.store.fs.FileBase; +import org.h2.store.fs.FileUtils; + +/** + * A file object that re-opens and re-tries the operation if the file was + * closed. + */ +class FileRetryOnInterrupt extends FileBase { + + private final String fileName; + private final String mode; + private FileChannel channel; + private FileLockRetry lock; + + FileRetryOnInterrupt(String fileName, String mode) throws IOException { + this.fileName = fileName; + this.mode = mode; + open(); + } + + private void open() throws IOException { + channel = FileUtils.open(fileName, mode); + } + + private void reopen(int i, IOException e) throws IOException { + if (i > 20) { + throw e; + } + if (!(e instanceof ClosedByInterruptException) && + !(e instanceof ClosedChannelException)) { + throw e; + } + // clear the interrupt flag, to avoid re-opening many times + Thread.interrupted(); + FileChannel before = channel; + // ensure we don't re-open concurrently; + // sometimes we don't re-open, which is fine, + // as this method is called in a loop + synchronized (this) { + if (before == channel) { + open(); + reLock(); + } + } + } + + private void reLock() throws IOException { + if (lock == null) { + return; + } + try { + lock.base.release(); + } catch (IOException e) { + // ignore + } + FileLock l2 = channel.tryLock(lock.position(), lock.size(), lock.isShared()); + if (l2 == null) { + throw new IOException("Re-locking failed"); + } + lock.base = l2; + } + + @Override + public void implCloseChannel() throws IOException { + try { + channel.close(); + } catch (IOException e) { + // ignore + } + } + + @Override + public long position() throws IOException { + for (int i = 0;; i++) { + try { + return channel.position(); + } catch (IOException e) { + reopen(i, e); + } + } + } + + @Override + public long size() throws IOException { + for (int i = 0;; i++) { + try { + return channel.size(); + } catch (IOException e) { + reopen(i, e); + } + } + } + + @Override + public int read(ByteBuffer dst) throws IOException { + long pos = position(); + for (int i = 0;; i++) { + try { + return channel.read(dst); + } catch (IOException e) { + reopen(i, e); + position(pos); + } + } + } + + @Override + public int read(ByteBuffer dst, long position) throws IOException { + for (int i = 0;; i++) { + try { + return channel.read(dst, position); + } catch (IOException e) { + reopen(i, e); + } + } + } + + @Override + public FileChannel position(long pos) throws IOException { + for (int i = 0;; i++) { + try { + channel.position(pos); + return this; + } catch (IOException e) { + reopen(i, e); + } + } + } + + @Override + public FileChannel truncate(long newLength) throws IOException { + for (int i = 0;; i++) { + try { + channel.truncate(newLength); + return this; + } catch (IOException e) { + reopen(i, e); + } + } + } + + @Override + public void force(boolean metaData) throws IOException { + for (int i = 0;; i++) { + try { + channel.force(metaData); + return; + } catch (IOException e) { + reopen(i, e); + } + } + } + + @Override + public int write(ByteBuffer src) throws IOException { + long pos = position(); + for (int i = 0;; i++) { + try { + return channel.write(src); + } catch (IOException e) { + reopen(i, e); + position(pos); + } + } + } + + @Override + public int write(ByteBuffer src, long position) throws IOException { + for (int i = 0;; i++) { + try { + return channel.write(src, position); + } catch (IOException e) { + reopen(i, e); + } + } + } + + @Override + public synchronized FileLock tryLock(long position, long size, + boolean shared) throws IOException { + FileLock l = channel.tryLock(position, size, shared); + if (l == null) { + return null; + } + lock = new FileLockRetry(l, this); + return lock; + } + + /** + * A wrapped file lock. + */ + static class FileLockRetry extends FileLock { + + /** + * The base lock. + */ + FileLock base; + + protected FileLockRetry(FileLock base, FileChannel channel) { + super(channel, base.position(), base.size(), base.isShared()); + this.base = base; + } + + @Override + public boolean isValid() { + return base.isValid(); + } + + @Override + public void release() throws IOException { + base.release(); + } + + } + + @Override + public String toString() { + return FilePathRetryOnInterrupt.SCHEME + ":" + fileName; + } + +} \ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/retry/package.html b/h2/src/main/org/h2/store/fs/retry/package.html new file mode 100644 index 0000000..6908e6a --- /dev/null +++ b/h2/src/main/org/h2/store/fs/retry/package.html @@ -0,0 +1,16 @@ + + + + +Javadoc package documentation +

+ +A file system that re-opens and re-tries the operation if the file was closed, because a thread was interrupted. +This will clear the interrupt flag. +It is mainly useful for applications that call Thread.interrupt by mistake. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/split/FilePathSplit.java b/h2/src/main/org/h2/store/fs/split/FilePathSplit.java new file mode 100644 index 0000000..7f3abb4 --- /dev/null +++ b/h2/src/main/org/h2/store/fs/split/FilePathSplit.java @@ -0,0 +1,242 @@ +/* + * 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.store.fs.split; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.SequenceInputStream; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.List; + +import org.h2.engine.SysProperties; +import org.h2.message.DbException; +import org.h2.store.fs.FilePath; +import org.h2.store.fs.FilePathWrapper; + +/** + * A file system that may split files into multiple smaller files. + * (required for a FAT32 because it only support files up to 2 GB). + */ +public class FilePathSplit extends FilePathWrapper { + + private static final String PART_SUFFIX = ".part"; + + @Override + protected String getPrefix() { + return getScheme() + ":" + parse(name)[0] + ":"; + } + + @Override + public FilePath unwrap(String fileName) { + return FilePath.get(parse(fileName)[1]); + } + + @Override + public boolean setReadOnly() { + boolean result = false; + for (int i = 0;; i++) { + FilePath f = getBase(i); + if (f.exists()) { + result = f.setReadOnly(); + } else { + break; + } + } + return result; + } + + @Override + public void delete() { + for (int i = 0;; i++) { + FilePath f = getBase(i); + if (f.exists()) { + f.delete(); + } else { + break; + } + } + } + + @Override + public long lastModified() { + long lastModified = 0; + for (int i = 0;; i++) { + FilePath f = getBase(i); + if (f.exists()) { + long l = f.lastModified(); + lastModified = Math.max(lastModified, l); + } else { + break; + } + } + return lastModified; + } + + @Override + public long size() { + long length = 0; + for (int i = 0;; i++) { + FilePath f = getBase(i); + if (f.exists()) { + length += f.size(); + } else { + break; + } + } + return length; + } + + @Override + public ArrayList newDirectoryStream() { + List list = getBase().newDirectoryStream(); + ArrayList newList = new ArrayList<>(); + for (FilePath f : list) { + if (!f.getName().endsWith(PART_SUFFIX)) { + newList.add(wrap(f)); + } + } + return newList; + } + + @Override + public InputStream newInputStream() throws IOException { + InputStream input = getBase().newInputStream(); + for (int i = 1;; i++) { + FilePath f = getBase(i); + if (f.exists()) { + InputStream i2 = f.newInputStream(); + input = new SequenceInputStream(input, i2); + } else { + break; + } + } + return input; + } + + @Override + public FileChannel open(String mode) throws IOException { + ArrayList list = new ArrayList<>(); + list.add(getBase().open(mode)); + for (int i = 1;; i++) { + FilePath f = getBase(i); + if (f.exists()) { + list.add(f.open(mode)); + } else { + break; + } + } + FileChannel[] array = list.toArray(new FileChannel[0]); + long maxLength = array[0].size(); + long length = maxLength; + if (array.length == 1) { + long defaultMaxLength = getDefaultMaxLength(); + if (maxLength < defaultMaxLength) { + maxLength = defaultMaxLength; + } + } else { + if (maxLength == 0) { + closeAndThrow(0, array, array[0], maxLength); + } + for (int i = 1; i < array.length - 1; i++) { + FileChannel c = array[i]; + long l = c.size(); + length += l; + if (l != maxLength) { + closeAndThrow(i, array, c, maxLength); + } + } + FileChannel c = array[array.length - 1]; + long l = c.size(); + length += l; + if (l > maxLength) { + closeAndThrow(array.length - 1, array, c, maxLength); + } + } + return new FileSplit(this, mode, array, length, maxLength); + } + + private long getDefaultMaxLength() { + return 1L << Integer.decode(parse(name)[0]); + } + + private void closeAndThrow(int id, FileChannel[] array, FileChannel o, + long maxLength) throws IOException { + String message = "Expected file length: " + maxLength + " got: " + + o.size() + " for " + getName(id); + for (FileChannel f : array) { + f.close(); + } + throw new IOException(message); + } + + @Override + public OutputStream newOutputStream(boolean append) throws IOException { + return newFileChannelOutputStream(open("rw"), append); + } + + @Override + public void moveTo(FilePath path, boolean atomicReplace) { + FilePathSplit newName = (FilePathSplit) path; + for (int i = 0;; i++) { + FilePath o = getBase(i); + if (o.exists()) { + o.moveTo(newName.getBase(i), atomicReplace); + } else if (newName.getBase(i).exists()) { + newName.getBase(i).delete(); + } else { + break; + } + } + } + + /** + * Split the file name into size and base file name. + * + * @param fileName the file name + * @return an array with size and file name + */ + private String[] parse(String fileName) { + if (!fileName.startsWith(getScheme())) { + throw DbException.getInternalError(fileName + " doesn't start with " + getScheme()); + } + fileName = fileName.substring(getScheme().length() + 1); + String size; + if (fileName.length() > 0 && Character.isDigit(fileName.charAt(0))) { + int idx = fileName.indexOf(':'); + size = fileName.substring(0, idx); + try { + fileName = fileName.substring(idx + 1); + } catch (NumberFormatException e) { + // ignore + } + } else { + size = Long.toString(SysProperties.SPLIT_FILE_SIZE_SHIFT); + } + return new String[] { size, fileName }; + } + + /** + * Get the file name of a part file. + * + * @param id the part id + * @return the file name including the part id + */ + FilePath getBase(int id) { + return FilePath.get(getName(id)); + } + + private String getName(int id) { + return id > 0 ? getBase().name + "." + id + PART_SUFFIX : getBase().name; + } + + @Override + public String getScheme() { + return "split"; + } + +} diff --git a/h2/src/main/org/h2/store/fs/split/FileSplit.java b/h2/src/main/org/h2/store/fs/split/FileSplit.java new file mode 100644 index 0000000..2cb8e21 --- /dev/null +++ b/h2/src/main/org/h2/store/fs/split/FileSplit.java @@ -0,0 +1,156 @@ +/* + * 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.store.fs.split; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import org.h2.message.DbException; +import org.h2.mvstore.DataUtils; +import org.h2.store.fs.FileBaseDefault; +import org.h2.store.fs.FilePath; + +/** + * A file that may be split into multiple smaller files. + */ +class FileSplit extends FileBaseDefault { + + private final FilePathSplit filePath; + private final String mode; + private final long maxLength; + private FileChannel[] list; + private volatile long length; + + FileSplit(FilePathSplit file, String mode, FileChannel[] list, long length, + long maxLength) { + this.filePath = file; + this.mode = mode; + this.list = list; + this.length = length; + this.maxLength = maxLength; + } + + @Override + public synchronized void implCloseChannel() throws IOException { + for (FileChannel c : list) { + c.close(); + } + } + + @Override + public long size() { + return length; + } + + @Override + public synchronized int read(ByteBuffer dst, long position) + throws IOException { + int len = dst.remaining(); + if (len == 0) { + return 0; + } + len = (int) Math.min(len, length - position); + if (len <= 0) { + return -1; + } + long offset = position % maxLength; + len = (int) Math.min(len, maxLength - offset); + FileChannel channel = getFileChannel(position); + return channel.read(dst, offset); + } + + private FileChannel getFileChannel(long position) throws IOException { + int id = (int) (position / maxLength); + while (id >= list.length) { + int i = list.length; + FileChannel[] newList = new FileChannel[i + 1]; + System.arraycopy(list, 0, newList, 0, i); + FilePath f = filePath.getBase(i); + newList[i] = f.open(mode); + list = newList; + } + return list[id]; + } + + @Override + protected void implTruncate(long newLength) throws IOException { + if (newLength >= length) { + return; + } + int newFileCount = 1 + (int) (newLength / maxLength); + if (newFileCount < list.length) { + // delete some of the files + FileChannel[] newList = new FileChannel[newFileCount]; + // delete backwards, so that truncating is somewhat transactional + for (int i = list.length - 1; i >= newFileCount; i--) { + // verify the file is writable + list[i].truncate(0); + list[i].close(); + try { + filePath.getBase(i).delete(); + } catch (DbException e) { + throw DataUtils.convertToIOException(e); + } + } + System.arraycopy(list, 0, newList, 0, newList.length); + list = newList; + } + long size = newLength - maxLength * (newFileCount - 1); + list[list.length - 1].truncate(size); + this.length = newLength; + } + + @Override + public synchronized void force(boolean metaData) throws IOException { + for (FileChannel c : list) { + c.force(metaData); + } + } + + @Override + public synchronized int write(ByteBuffer src, long position) throws IOException { + if (position >= length && position > maxLength) { + // may need to extend and create files + long oldFilePointer = position; + long x = length - (length % maxLength) + maxLength; + for (; x < position; x += maxLength) { + if (x > length) { + // expand the file size + position(x - 1); + write(ByteBuffer.wrap(new byte[1])); + } + position = oldFilePointer; + } + } + long offset = position % maxLength; + int len = src.remaining(); + FileChannel channel = getFileChannel(position); + int l = (int) Math.min(len, maxLength - offset); + if (l == len) { + l = channel.write(src, offset); + } else { + int oldLimit = src.limit(); + src.limit(src.position() + l); + l = channel.write(src, offset); + src.limit(oldLimit); + } + length = Math.max(length, position + l); + return l; + } + + @Override + public synchronized FileLock tryLock(long position, long size, + boolean shared) throws IOException { + return list[0].tryLock(position, size, shared); + } + + @Override + public String toString() { + return filePath.toString(); + } + +} \ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/split/package.html b/h2/src/main/org/h2/store/fs/split/package.html new file mode 100644 index 0000000..ef8d718 --- /dev/null +++ b/h2/src/main/org/h2/store/fs/split/package.html @@ -0,0 +1,15 @@ + + + + +Javadoc package documentation +

+ +A file system that may split files into multiple smaller files +(required for a FAT32 because it only support files up to 2 GB). + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/zip/FilePathZip.java b/h2/src/main/org/h2/store/fs/zip/FilePathZip.java new file mode 100644 index 0000000..5cacabd --- /dev/null +++ b/h2/src/main/org/h2/store/fs/zip/FilePathZip.java @@ -0,0 +1,251 @@ +/* + * 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.store.fs.zip; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.h2.message.DbException; +import org.h2.store.fs.FilePath; +import org.h2.store.fs.disk.FilePathDisk; + +/** + * This is a read-only file system that allows + * to access databases stored in a .zip or .jar file. + */ +public class FilePathZip extends FilePath { + + @Override + public FilePathZip getPath(String path) { + FilePathZip p = new FilePathZip(); + p.name = path; + return p; + } + + @Override + public void createDirectory() { + // ignore + } + + @Override + public boolean createFile() { + throw DbException.getUnsupportedException("write"); + } + + @Override + public void delete() { + throw DbException.getUnsupportedException("write"); + } + + @Override + public boolean exists() { + try { + String entryName = getEntryName(); + if (entryName.isEmpty()) { + return true; + } + try (ZipFile file = openZipFile()) { + return file.getEntry(entryName) != null; + } + } catch (IOException e) { + return false; + } + } + + @Override + public long lastModified() { + return 0; + } + + @Override + public FilePath getParent() { + int idx = name.lastIndexOf('/'); + return idx < 0 ? null : getPath(name.substring(0, idx)); + } + + @Override + public boolean isAbsolute() { + String fileName = translateFileName(name); + return FilePath.get(fileName).isAbsolute(); + } + + @Override + public FilePath unwrap() { + return FilePath.get(name.substring(getScheme().length() + 1)); + } + + @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 directory; + } + try (ZipFile file = openZipFile()) { + Enumeration en = file.entries(); + while (en.hasMoreElements()) { + ZipEntry entry = en.nextElement(); + String n = entry.getName(); + if (n.equals(entryName)) { + return entry.isDirectory() == directory; + } else if (n.startsWith(entryName)) { + if (n.length() == entryName.length() + 1) { + if (n.equals(entryName + "/")) { + return directory; + } + } + } + } + } + return false; + } catch (IOException e) { + return false; + } + } + + @Override + public boolean canWrite() { + return false; + } + + @Override + public boolean setReadOnly() { + return true; + } + + @Override + public long size() { + try { + try (ZipFile file = openZipFile()) { + ZipEntry entry = file.getEntry(getEntryName()); + return entry == null ? 0 : entry.getSize(); + } + } catch (IOException e) { + return 0; + } + } + + @Override + public ArrayList newDirectoryStream() { + String path = name; + ArrayList list = new ArrayList<>(); + try { + if (path.indexOf('!') < 0) { + path += "!"; + } + if (!path.endsWith("/")) { + path += "/"; + } + try (ZipFile file = openZipFile()) { + String dirName = getEntryName(); + String prefix = path.substring(0, path.length() - dirName.length()); + Enumeration en = file.entries(); + while (en.hasMoreElements()) { + ZipEntry entry = en.nextElement(); + String name = entry.getName(); + if (!name.startsWith(dirName)) { + continue; + } + if (name.length() <= dirName.length()) { + continue; + } + int idx = name.indexOf('/', dirName.length()); + if (idx < 0 || idx >= name.length() - 1) { + list.add(getPath(prefix + name)); + } + } + } + return list; + } catch (IOException e) { + throw DbException.convertIOException(e, "listFiles " + path); + } + } + + @Override + public FileChannel open(String mode) throws IOException { + ZipFile file = openZipFile(); + ZipEntry entry = file.getEntry(getEntryName()); + if (entry == null) { + file.close(); + throw new FileNotFoundException(name); + } + return new FileZip(file, entry); + } + + @Override + public OutputStream newOutputStream(boolean append) throws IOException { + throw new IOException("write"); + } + + @Override + public void moveTo(FilePath newName, boolean atomicReplace) { + throw DbException.getUnsupportedException("write"); + } + + private static String translateFileName(String fileName) { + if (fileName.startsWith("zip:")) { + fileName = fileName.substring("zip:".length()); + } + int idx = fileName.indexOf('!'); + if (idx >= 0) { + fileName = fileName.substring(0, idx); + } + return FilePathDisk.expandUserHomeDirectory(fileName); + } + + @Override + public FilePath toRealPath() { + return this; + } + + private String getEntryName() { + int idx = name.indexOf('!'); + String fileName; + if (idx <= 0) { + fileName = ""; + } else { + fileName = name.substring(idx + 1); + } + fileName = fileName.replace('\\', '/'); + if (fileName.startsWith("/")) { + fileName = fileName.substring(1); + } + return fileName; + } + + private ZipFile openZipFile() throws IOException { + String fileName = translateFileName(name); + return new ZipFile(fileName); + } + + @Override + public FilePath createTempFile(String suffix, boolean inTempDir) throws IOException { + if (!inTempDir) { + throw new IOException("File system is read-only"); + } + return new FilePathDisk().getPath(name).createTempFile(suffix, true); + } + + @Override + public String getScheme() { + return "zip"; + } + +} diff --git a/h2/src/main/org/h2/store/fs/zip/FileZip.java b/h2/src/main/org/h2/store/fs/zip/FileZip.java new file mode 100644 index 0000000..1488cfc --- /dev/null +++ b/h2/src/main/org/h2/store/fs/zip/FileZip.java @@ -0,0 +1,147 @@ +/* + * 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.store.fs.zip; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.NonWritableChannelException; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.h2.store.fs.FakeFileChannel; +import org.h2.store.fs.FileBase; +import org.h2.util.IOUtils; + +/** + * The file is read from a stream. When reading from start to end, the same + * input stream is re-used, however when reading from end to start, a new input + * stream is opened for each request. + */ +class FileZip extends FileBase { + + private static final byte[] SKIP_BUFFER = new byte[1024]; + + private final ZipFile file; + private final ZipEntry entry; + private long pos; + private InputStream in; + private long inPos; + private final long length; + private boolean skipUsingRead; + + FileZip(ZipFile file, ZipEntry entry) { + this.file = file; + this.entry = entry; + length = entry.getSize(); + } + + @Override + public long position() { + return pos; + } + + @Override + public long size() { + return length; + } + + @Override + public int read(ByteBuffer dst) throws IOException { + seek(); + int len = in.read(dst.array(), dst.arrayOffset() + dst.position(), + dst.remaining()); + if (len > 0) { + dst.position(dst.position() + len); + pos += len; + inPos += len; + } + return len; + } + + private void seek() throws IOException { + if (inPos > pos) { + if (in != null) { + in.close(); + } + in = null; + } + if (in == null) { + in = file.getInputStream(entry); + inPos = 0; + } + if (inPos < pos) { + long skip = pos - inPos; + if (!skipUsingRead) { + try { + IOUtils.skipFully(in, skip); + } catch (NullPointerException e) { + // workaround for Android + skipUsingRead = true; + } + } + if (skipUsingRead) { + while (skip > 0) { + int s = (int) Math.min(SKIP_BUFFER.length, skip); + s = in.read(SKIP_BUFFER, 0, s); + skip -= s; + } + } + inPos = pos; + } + } + + @Override + public FileChannel position(long newPos) { + this.pos = newPos; + return this; + } + + @Override + public FileChannel truncate(long newLength) throws IOException { + throw new IOException("File is read-only"); + } + + @Override + public void force(boolean metaData) throws IOException { + // nothing to do + } + + @Override + public int write(ByteBuffer src) throws IOException { + throw new NonWritableChannelException(); + } + + @Override + public synchronized FileLock tryLock(long position, long size, + boolean shared) throws IOException { + if (shared) { + return new FileLock(FakeFileChannel.INSTANCE, position, size, shared) { + + @Override + public boolean isValid() { + return true; + } + + @Override + public void release() throws IOException { + // ignore + }}; + } + return null; + } + + @Override + protected void implCloseChannel() throws IOException { + if (in != null) { + in.close(); + in = null; + } + file.close(); + } + +} \ No newline at end of file diff --git a/h2/src/main/org/h2/store/fs/zip/package.html b/h2/src/main/org/h2/store/fs/zip/package.html new file mode 100644 index 0000000..d314bc5 --- /dev/null +++ b/h2/src/main/org/h2/store/fs/zip/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +A zip-file base file system abstraction. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/store/package.html b/h2/src/main/org/h2/store/package.html new file mode 100644 index 0000000..157780f --- /dev/null +++ b/h2/src/main/org/h2/store/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Storage abstractions, such as a file with a cache, or a class to convert values to a byte array and vice versa. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/table/Column.java b/h2/src/main/org/h2/table/Column.java new file mode 100644 index 0000000..1dd1d56 --- /dev/null +++ b/h2/src/main/org/h2/table/Column.java @@ -0,0 +1,819 @@ +/* + * 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.sql.ResultSetMetaData; +import java.util.Objects; + +import org.h2.api.ErrorCode; +import org.h2.command.Parser; +import org.h2.command.ddl.SequenceOptions; +import org.h2.engine.CastDataProvider; +import org.h2.engine.Constants; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.ValueExpression; +import org.h2.message.DbException; +import org.h2.result.Row; +import org.h2.schema.Domain; +import org.h2.schema.Schema; +import org.h2.schema.Sequence; +import org.h2.util.HasSQL; +import org.h2.util.ParserUtil; +import org.h2.util.StringUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Typed; +import org.h2.value.Value; +import org.h2.value.ValueNull; +import org.h2.value.ValueUuid; + +/** + * This class represents a column in a table. + */ +public final class Column implements HasSQL, Typed, ColumnTemplate { + + /** + * The name of the rowid pseudo column. + */ + public static final String ROWID = "_ROWID_"; + + /** + * This column is not nullable. + */ + public static final int NOT_NULLABLE = + ResultSetMetaData.columnNoNulls; + + /** + * This column is nullable. + */ + public static final int NULLABLE = + ResultSetMetaData.columnNullable; + + /** + * It is not know whether this column is nullable. + */ + public static final int NULLABLE_UNKNOWN = + ResultSetMetaData.columnNullableUnknown; + + private TypeInfo type; + private Table table; + private String name; + private int columnId; + private boolean nullable = true; + private Expression defaultExpression; + private Expression onUpdateExpression; + private SequenceOptions identityOptions; + private boolean defaultOnNull; + private Sequence sequence; + private boolean isGeneratedAlways; + private GeneratedColumnResolver generatedTableFilter; + private int selectivity; + private String comment; + private boolean primaryKey; + private boolean visible = true; + private boolean rowId; + private Domain domain; + + /** + * Appends the specified columns to the specified builder. + * + * @param builder + * string builder + * @param columns + * columns + * @param sqlFlags + * formatting flags + * @return the specified string builder + */ + public static StringBuilder writeColumns(StringBuilder builder, Column[] columns, int sqlFlags) { + for (int i = 0, l = columns.length; i < l; i++) { + if (i > 0) { + builder.append(", "); + } + columns[i].getSQL(builder, sqlFlags); + } + return builder; + } + + /** + * Appends the specified columns to the specified builder. + * + * @param builder + * string builder + * @param columns + * columns + * @param separator + * separator + * @param suffix + * additional SQL to append after each column + * @param sqlFlags + * formatting flags + * @return the specified string builder + */ + public static StringBuilder writeColumns(StringBuilder builder, Column[] columns, String separator, + String suffix, int sqlFlags) { + for (int i = 0, l = columns.length; i < l; i++) { + if (i > 0) { + builder.append(separator); + } + columns[i].getSQL(builder, sqlFlags).append(suffix); + } + return builder; + } + + public Column(String name, TypeInfo type) { + this.name = name; + this.type = type; + } + + public Column(String name, TypeInfo type, Table table, int columnId) { + this.name = name; + this.type = type; + this.table = table; + this.columnId = columnId; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } else if (!(o instanceof Column)) { + return false; + } + Column other = (Column) o; + if (table == null || other.table == null || + name == null || other.name == null) { + return false; + } + if (table != other.table) { + return false; + } + return name.equals(other.name); + } + + @Override + public int hashCode() { + if (table == null || name == null) { + return 0; + } + return table.getId() ^ name.hashCode(); + } + + public Column getClone() { + Column newColumn = new Column(name, type); + newColumn.copy(this); + return newColumn; + } + + /** + * Convert a value to this column's type without precision and scale checks. + * + * @param provider the cast information provider + * @param v the value + * @return the value + */ + public Value convert(CastDataProvider provider, Value v) { + try { + return v.convertTo(type, provider, this); + } catch (DbException e) { + if (e.getErrorCode() == ErrorCode.DATA_CONVERSION_ERROR_1) { + e = getDataConversionError(v, e); + } + throw e; + } + } + + /** + * Returns whether this column is an identity column. + * + * @return whether this column is an identity column + */ + public boolean isIdentity() { + return sequence != null || identityOptions != null; + } + + /** + * Returns whether this column is a generated column. + * + * @return whether this column is a generated column + */ + public boolean isGenerated() { + return isGeneratedAlways && defaultExpression != null; + } + + /** + * Returns whether this column is a generated column or always generated + * identity column. + * + * @return whether this column is a generated column or always generated + * identity column + */ + public boolean isGeneratedAlways() { + return isGeneratedAlways; + } + + /** + * Set the default value in the form of a generated expression of other + * columns. + * + * @param expression the computed expression + */ + public void setGeneratedExpression(Expression expression) { + this.isGeneratedAlways = true; + this.defaultExpression = expression; + } + + /** + * Set the table and column id. + * + * @param table the table + * @param columnId the column index + */ + public void setTable(Table table, int columnId) { + this.table = table; + this.columnId = columnId; + } + + public Table getTable() { + return table; + } + + @Override + public void setDefaultExpression(SessionLocal session, Expression defaultExpression) { + // also to test that no column names are used + if (defaultExpression != null) { + defaultExpression = defaultExpression.optimize(session); + if (defaultExpression.isConstant()) { + defaultExpression = ValueExpression.get( + defaultExpression.getValue(session)); + } + } + this.defaultExpression = defaultExpression; + this.isGeneratedAlways = false; + } + + @Override + public void setOnUpdateExpression(SessionLocal session, Expression onUpdateExpression) { + // also to test that no column names are used + if (onUpdateExpression != null) { + onUpdateExpression = onUpdateExpression.optimize(session); + if (onUpdateExpression.isConstant()) { + onUpdateExpression = ValueExpression.get(onUpdateExpression.getValue(session)); + } + } + this.onUpdateExpression = onUpdateExpression; + } + + public int getColumnId() { + return columnId; + } + + @Override + public String getSQL(int sqlFlags) { + return rowId ? name : Parser.quoteIdentifier(name, sqlFlags); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return rowId ? builder.append(name) : ParserUtil.quoteIdentifier(builder, name, sqlFlags); + } + + /** + * Appends the table name and column name to the specified builder. + * + * @param builder the string builder + * @param sqlFlags formatting flags + * @return the specified string builder + */ + public StringBuilder getSQLWithTable(StringBuilder builder, int sqlFlags) { + return getSQL(table.getSQL(builder, sqlFlags).append('.'), sqlFlags); + } + + public String getName() { + return name; + } + + @Override + public TypeInfo getType() { + return type; + } + + public void setNullable(boolean b) { + nullable = b; + } + + public boolean getVisible() { + return visible; + } + + public void setVisible(boolean b) { + visible = b; + } + + @Override + public Domain getDomain() { + return domain; + } + + @Override + public void setDomain(Domain domain) { + this.domain = domain; + } + + /** + * Returns whether this column is a row identity column. + * + * @return true for _ROWID_ column, false otherwise + */ + public boolean isRowId() { + return rowId; + } + + /** + * Set row identity flag. + * + * @param rowId true _ROWID_ column, false otherwise + */ + public void setRowId(boolean rowId) { + this.rowId = rowId; + } + + /** + * Validate the value, convert it if required, and update the sequence value + * if required. If the value is null, the default value (NULL if no default + * is set) is returned. Domain constraints are validated as well. + * + * @param session the session + * @param value the value or null + * @param row the row + * @return the new or converted value + */ + Value validateConvertUpdateSequence(SessionLocal session, Value value, Row row) { + check: { + if (value == null) { + if (sequence != null) { + value = session.getNextValueFor(sequence, null); + break check; + } + value = getDefaultOrGenerated(session, row); + } + if (value == ValueNull.INSTANCE && !nullable) { + throw DbException.get(ErrorCode.NULL_NOT_ALLOWED, name); + } + } + try { + value = value.convertForAssignTo(type, session, name); + } catch (DbException e) { + if (e.getErrorCode() == ErrorCode.DATA_CONVERSION_ERROR_1) { + e = getDataConversionError(value, e); + } + throw e; + } + if (domain != null) { + domain.checkConstraints(session, value); + } + if (sequence != null && session.getMode().updateSequenceOnManualIdentityInsertion) { + updateSequenceIfRequired(session, value.getLong()); + } + return value; + } + + private Value getDefaultOrGenerated(SessionLocal session, Row row) { + Value value; + Expression localDefaultExpression = getEffectiveDefaultExpression(); + if (localDefaultExpression == null) { + value = ValueNull.INSTANCE; + } else { + if (isGeneratedAlways) { + synchronized (this) { + generatedTableFilter.set(row); + try { + value = localDefaultExpression.getValue(session); + } finally { + generatedTableFilter.set(null); + } + } + } else { + value = localDefaultExpression.getValue(session); + } + } + return value; + } + + private DbException getDataConversionError(Value value, DbException cause) { + StringBuilder builder = new StringBuilder().append(value.getTraceSQL()).append(" ("); + if (table != null) { + builder.append(table.getName()).append(": "); + } + builder.append(getCreateSQL()).append(')'); + return DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, cause, builder.toString()); + } + + private void updateSequenceIfRequired(SessionLocal session, long value) { + if (sequence.getCycle() == Sequence.Cycle.EXHAUSTED) { + return; + } + long current = sequence.getCurrentValue(); + long inc = sequence.getIncrement(); + if (inc > 0) { + if (value < current) { + return; + } + } else if (value > current) { + return; + } + try { + sequence.modify(value + inc, null, null, null, null, null, null); + } catch (DbException ex) { + if (ex.getErrorCode() == ErrorCode.SEQUENCE_ATTRIBUTES_INVALID_7) { + return; + } + throw ex; + } + sequence.flush(session); + } + + /** + * Initialize the sequence for this column. + * + * @param session the session + * @param schema the schema where the sequence should be generated + * @param id the object id + * @param temporary true if the sequence is temporary and does not need to + * be stored + */ + public void initializeSequence(SessionLocal session, Schema schema, int id, boolean temporary) { + if (identityOptions == null) { + throw DbException.getInternalError(); + } + String sequenceName; + do { + sequenceName = "SYSTEM_SEQUENCE_" + + StringUtils.toUpperEnglish(ValueUuid.getNewRandom().getString().replace('-', '_')); + } while (schema.findSequence(sequenceName) != null); + identityOptions.setDataType(type); + Sequence seq = new Sequence(session, schema, id, sequenceName, identityOptions, true); + seq.setTemporary(temporary); + session.getDatabase().addSchemaObject(session, seq); + // This method also ensures NOT NULL + setSequence(seq, isGeneratedAlways); + } + + @Override + public void prepareExpressions(SessionLocal session) { + if (defaultExpression != null) { + if (isGeneratedAlways) { + generatedTableFilter = new GeneratedColumnResolver(table); + defaultExpression.mapColumns(generatedTableFilter, 0, Expression.MAP_INITIAL); + } + defaultExpression = defaultExpression.optimize(session); + } + if (onUpdateExpression != null) { + onUpdateExpression = onUpdateExpression.optimize(session); + } + if (domain != null) { + domain.prepareExpressions(session); + } + } + + public String getCreateSQLWithoutName() { + return getCreateSQL(new StringBuilder(), false); + } + + public String getCreateSQL() { + return getCreateSQL(false); + } + + /** + * Get this columns part of CREATE TABLE SQL statement. + * + * @param forMeta whether this is for the metadata table + * @return the SQL statement + */ + public String getCreateSQL(boolean forMeta) { + StringBuilder builder = new StringBuilder(); + if (name != null) { + ParserUtil.quoteIdentifier(builder, name, DEFAULT_SQL_FLAGS).append(' '); + } + return getCreateSQL(builder, forMeta); + } + + private String getCreateSQL(StringBuilder builder, boolean forMeta) { + if (domain != null) { + domain.getSQL(builder, DEFAULT_SQL_FLAGS); + } else { + type.getSQL(builder, DEFAULT_SQL_FLAGS); + } + if (!visible) { + builder.append(" INVISIBLE "); + } + if (sequence != null) { + builder.append(" GENERATED ").append(isGeneratedAlways ? "ALWAYS" : "BY DEFAULT").append(" AS IDENTITY"); + if (!forMeta) { + sequence.getSequenceOptionsSQL(builder.append('(')).append(')'); + } + } else if (defaultExpression != null) { + if (isGeneratedAlways) { + defaultExpression.getEnclosedSQL(builder.append(" GENERATED ALWAYS AS "), DEFAULT_SQL_FLAGS); + } else { + defaultExpression.getUnenclosedSQL(builder.append(" DEFAULT "), DEFAULT_SQL_FLAGS); + } + } + if (onUpdateExpression != null) { + onUpdateExpression.getUnenclosedSQL(builder.append(" ON UPDATE "), DEFAULT_SQL_FLAGS); + } + if (defaultOnNull) { + builder.append(" DEFAULT ON NULL"); + } + if (forMeta && sequence != null) { + sequence.getSQL(builder.append(" SEQUENCE "), DEFAULT_SQL_FLAGS); + } + if (selectivity != 0) { + builder.append(" SELECTIVITY ").append(selectivity); + } + if (comment != null) { + StringUtils.quoteStringSQL(builder.append(" COMMENT "), comment); + } + if (!nullable) { + builder.append(" NOT NULL"); + } + return builder.toString(); + } + + public boolean isNullable() { + return nullable; + } + + @Override + public Expression getDefaultExpression() { + return defaultExpression; + } + + @Override + public Expression getEffectiveDefaultExpression() { + /* + * Identity columns may not have a default expression and may not use an + * expression from domain. + * + * Generated columns always have an own expression. + */ + if (sequence != null) { + return null; + } + return defaultExpression != null ? defaultExpression + : domain != null ? domain.getEffectiveDefaultExpression() : null; + } + + @Override + public Expression getOnUpdateExpression() { + return onUpdateExpression; + } + + @Override + public Expression getEffectiveOnUpdateExpression() { + /* + * Identity and generated columns may not have an on update expression + * and may not use an expression from domain. + */ + if (sequence != null || isGeneratedAlways) { + return null; + } + return onUpdateExpression != null ? onUpdateExpression + : domain != null ? domain.getEffectiveOnUpdateExpression() : null; + } + + /** + * Whether the column has any identity options. + * + * @return true if yes + */ + public boolean hasIdentityOptions() { + return identityOptions != null; + } + + /** + * Set the identity options of this column. + * + * @param identityOptions + * identity column options + * @param generatedAlways + * whether value should be always generated + */ + public void setIdentityOptions(SequenceOptions identityOptions, boolean generatedAlways) { + this.identityOptions = identityOptions; + this.isGeneratedAlways = generatedAlways; + removeNonIdentityProperties(); + } + + private void removeNonIdentityProperties() { + nullable = false; + onUpdateExpression = defaultExpression = null; + } + + /** + * Returns identity column options, or {@code null} if sequence was already + * created or this column is not an identity column. + * + * @return identity column options, or {@code null} + */ + public SequenceOptions getIdentityOptions() { + return identityOptions; + } + + public void setDefaultOnNull(boolean defaultOnNull) { + this.defaultOnNull = defaultOnNull; + } + + public boolean isDefaultOnNull() { + return defaultOnNull; + } + + /** + * Rename the column. This method will only set the column name to the new + * value. + * + * @param newName the new column name + */ + public void rename(String newName) { + this.name = newName; + } + + /** + * Set the sequence to generate the value. + * + * @param sequence the sequence + * @param generatedAlways whether the value of the sequence is always used + */ + public void setSequence(Sequence sequence, boolean generatedAlways) { + this.sequence = sequence; + this.isGeneratedAlways = generatedAlways; + this.identityOptions = null; + if (sequence != null) { + removeNonIdentityProperties(); + if (sequence.getDatabase().getMode().identityColumnsHaveDefaultOnNull) { + defaultOnNull = true; + } + } + } + + public Sequence getSequence() { + return sequence; + } + + /** + * Get the selectivity of the column. Selectivity 100 means values are + * unique, 10 means every distinct value appears 10 times on average. + * + * @return the selectivity + */ + public int getSelectivity() { + return selectivity == 0 ? Constants.SELECTIVITY_DEFAULT : selectivity; + } + + /** + * Set the new selectivity of a column. + * + * @param selectivity the new value + */ + public void setSelectivity(int selectivity) { + selectivity = selectivity < 0 ? 0 : (selectivity > 100 ? 100 : selectivity); + this.selectivity = selectivity; + } + + @Override + public String getDefaultSQL() { + return defaultExpression == null ? null + : defaultExpression.getUnenclosedSQL(new StringBuilder(), DEFAULT_SQL_FLAGS).toString(); + } + + @Override + public String getOnUpdateSQL() { + return onUpdateExpression == null ? null + : onUpdateExpression.getUnenclosedSQL(new StringBuilder(), DEFAULT_SQL_FLAGS).toString(); + } + + public void setComment(String comment) { + this.comment = comment != null && !comment.isEmpty() ? comment : null; + } + + public String getComment() { + return comment; + } + + public void setPrimaryKey(boolean primaryKey) { + this.primaryKey = primaryKey; + } + + /** + * Visit the default expression, the check constraint, and the sequence (if + * any). + * + * @param visitor the visitor + * @return true if every visited expression returned true, or if there are + * no expressions + */ + boolean isEverything(ExpressionVisitor visitor) { + if (visitor.getType() == ExpressionVisitor.GET_DEPENDENCIES) { + if (sequence != null) { + visitor.getDependencies().add(sequence); + } + } + Expression e = getEffectiveDefaultExpression(); + if (e != null && !e.isEverything(visitor)) { + return false; + } + e = getEffectiveOnUpdateExpression(); + if (e != null && !e.isEverything(visitor)) { + return false; + } + return true; + } + + public boolean isPrimaryKey() { + return primaryKey; + } + + @Override + public String toString() { + return name; + } + + /** + * Check whether the new column is of the same type and not more restricted + * than this column. + * + * @param newColumn the new (target) column + * @return true if the new column is compatible + */ + public boolean isWideningConversion(Column newColumn) { + TypeInfo newType = newColumn.type; + int valueType = type.getValueType(); + if (valueType != newType.getValueType()) { + return false; + } + long precision = type.getPrecision(); + long newPrecision = newType.getPrecision(); + if (precision > newPrecision + || precision < newPrecision && (valueType == Value.CHAR || valueType == Value.BINARY)) { + return false; + } + if (type.getScale() != newType.getScale()) { + return false; + } + if (!Objects.equals(type.getExtTypeInfo(), newType.getExtTypeInfo())) { + return false; + } + if (nullable && !newColumn.nullable) { + return false; + } + if (primaryKey != newColumn.primaryKey) { + return false; + } + if (identityOptions != null || newColumn.identityOptions != null) { + return false; + } + if (domain != newColumn.domain) { + return false; + } + if (defaultExpression != null || newColumn.defaultExpression != null) { + return false; + } + if (isGeneratedAlways || newColumn.isGeneratedAlways) { + return false; + } + if (onUpdateExpression != null || newColumn.onUpdateExpression != null) { + return false; + } + return true; + } + + /** + * Copy the data of the source column into the current column. + * + * @param source the source column + */ + public void copy(Column source) { + name = source.name; + type = source.type; + domain = source.domain; + // table is not set + // columnId is not set + nullable = source.nullable; + defaultExpression = source.defaultExpression; + onUpdateExpression = source.onUpdateExpression; + // identityOptions field is not set + defaultOnNull = source.defaultOnNull; + sequence = source.sequence; + comment = source.comment; + generatedTableFilter = source.generatedTableFilter; + isGeneratedAlways = source.isGeneratedAlways; + selectivity = source.selectivity; + primaryKey = source.primaryKey; + visible = source.visible; + } + +} diff --git a/h2/src/main/org/h2/table/ColumnResolver.java b/h2/src/main/org/h2/table/ColumnResolver.java new file mode 100644 index 0000000..6942d21 --- /dev/null +++ b/h2/src/main/org/h2/table/ColumnResolver.java @@ -0,0 +1,129 @@ +/* + * 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 org.h2.command.query.Select; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.value.Value; + +/** + * A column resolver is list of column (for example, a table) that can map a + * column name to an actual column. + */ +public interface ColumnResolver { + + /** + * Get the table alias. + * + * @return the table alias + */ + default String getTableAlias() { + return null; + } + + /** + * Get the column list. + * + * @return the column list + */ + Column[] getColumns(); + + /** + * Get the column with the specified name. + * + * @param name + * the column name, must be a derived name if this column + * resolver has a derived column list + * @return the column with the specified name, or {@code null} + */ + Column findColumn(String name); + + /** + * Get the name of the specified column. + * + * @param column column + * @return column name + */ + default String getColumnName(Column column) { + return column.getName(); + } + + /** + * Returns whether this column resolver has a derived column list. + * + * @return {@code true} if this column resolver has a derived column list, + * {@code false} otherwise + */ + default boolean hasDerivedColumnList() { + return false; + } + + /** + * Get the list of system columns, if any. + * + * @return the system columns or null + */ + default Column[] getSystemColumns() { + return null; + } + + /** + * Get the row id pseudo column, if there is one. + * + * @return the row id column or null + */ + default Column getRowIdColumn() { + return null; + } + + /** + * Get the schema name or null. + * + * @return the schema name or null + */ + default String getSchemaName() { + return null; + } + + /** + * Get the value for the given column. + * + * @param column the column + * @return the value + */ + Value getValue(Column column); + + /** + * Get the table filter. + * + * @return the table filter + */ + default TableFilter getTableFilter() { + return null; + } + + /** + * Get the select statement. + * + * @return the select statement + */ + default Select getSelect() { + return null; + } + + /** + * Get the expression that represents this column. + * + * @param expressionColumn the expression column + * @param column the column + * @return the optimized expression + */ + default Expression optimize(ExpressionColumn expressionColumn, Column column) { + return expressionColumn; + } + +} diff --git a/h2/src/main/org/h2/table/ColumnTemplate.java b/h2/src/main/org/h2/table/ColumnTemplate.java new file mode 100644 index 0000000..44459ca --- /dev/null +++ b/h2/src/main/org/h2/table/ColumnTemplate.java @@ -0,0 +1,61 @@ +/* + * 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 org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.schema.Domain; + +/** + * Column or domain. + */ +public interface ColumnTemplate { + + Domain getDomain(); + + void setDomain(Domain domain); + + /** + * Set the default expression. + * + * @param session + * the session + * @param defaultExpression + * the default expression + */ + void setDefaultExpression(SessionLocal session, Expression defaultExpression); + + Expression getDefaultExpression(); + + Expression getEffectiveDefaultExpression(); + + String getDefaultSQL(); + + /** + * Set the on update expression. + * + * @param session + * the session + * @param onUpdateExpression + * the on update expression + */ + void setOnUpdateExpression(SessionLocal session, Expression onUpdateExpression); + + Expression getOnUpdateExpression(); + + Expression getEffectiveOnUpdateExpression(); + + String getOnUpdateSQL(); + + /** + * Prepare all expressions of this column or domain. + * + * @param session + * the session + */ + void prepareExpressions(SessionLocal session); + +} diff --git a/h2/src/main/org/h2/table/DataChangeDeltaTable.java b/h2/src/main/org/h2/table/DataChangeDeltaTable.java new file mode 100644 index 0000000..e9046a3 --- /dev/null +++ b/h2/src/main/org/h2/table/DataChangeDeltaTable.java @@ -0,0 +1,134 @@ +/* + * 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 org.h2.command.dml.DataChangeStatement; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionColumn; +import org.h2.result.LocalResult; +import org.h2.result.ResultInterface; +import org.h2.result.ResultTarget; +import org.h2.result.Row; +import org.h2.schema.Schema; + +/** + * A data change delta table. + */ +public class DataChangeDeltaTable extends VirtualConstructedTable { + + /** + * Result option. + */ + public enum ResultOption { + + /** + * OLD row. + */ + OLD, + + /** + * NEW row with evaluated default expressions, but before triggers. + */ + NEW, + + /** + * FINAL rows after triggers. + */ + FINAL; + + } + + /** + * Collects final row for INSERT operations. + * + * @param session + * the session + * @param table + * the table + * @param deltaChangeCollector + * target result + * @param deltaChangeCollectionMode + * collection mode + * @param newRow + * the inserted row + */ + public static void collectInsertedFinalRow(SessionLocal session, Table table, ResultTarget deltaChangeCollector, + ResultOption deltaChangeCollectionMode, Row newRow) { + if (session.getMode().takeInsertedIdentity) { + Column column = table.getIdentityColumn(); + if (column != null) { + session.setLastIdentity(newRow.getValue(column.getColumnId())); + } + } + if (deltaChangeCollectionMode == ResultOption.FINAL) { + deltaChangeCollector.addRow(newRow.getValueList()); + } + } + + private final DataChangeStatement statement; + + private final ResultOption resultOption; + + private final Expression[] expressions; + + public DataChangeDeltaTable(Schema schema, SessionLocal session, DataChangeStatement statement, + ResultOption resultOption) { + super(schema, 0, statement.getStatementName()); + this.statement = statement; + this.resultOption = resultOption; + Table table = statement.getTable(); + Column[] tableColumns = table.getColumns(); + int columnCount = tableColumns.length; + Column[] c = new Column[columnCount]; + for (int i = 0; i < columnCount; i++) { + c[i] = tableColumns[i].getClone(); + } + setColumns(c); + Expression[] expressions = new Expression[columnCount]; + String tableName = getName(); + for (int i = 0; i < columnCount; i++) { + expressions[i] = new ExpressionColumn(database, null, tableName, c[i].getName()); + } + this.expressions = expressions; + } + + @Override + public boolean canGetRowCount(SessionLocal session) { + return false; + } + + @Override + public long getRowCount(SessionLocal session) { + return Long.MAX_VALUE; + } + + @Override + public long getRowCountApproximation(SessionLocal session) { + return Long.MAX_VALUE; + } + + @Override + public ResultInterface getResult(SessionLocal session) { + statement.prepare(); + int columnCount = expressions.length; + LocalResult result = new LocalResult(session, expressions, columnCount, columnCount); + result.setForDataChangeDeltaTable(); + statement.update(result, resultOption); + return result; + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return builder.append(resultOption.name()).append(" TABLE (").append(statement.getSQL()).append(')'); + } + + @Override + public boolean isDeterministic() { + return false; + } + +} 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 0000000..2f8cfc5 --- /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/DualTable.java b/h2/src/main/org/h2/table/DualTable.java new file mode 100644 index 0000000..5f9b5ed --- /dev/null +++ b/h2/src/main/org/h2/table/DualTable.java @@ -0,0 +1,74 @@ +/* + * 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 org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.index.DualIndex; +import org.h2.index.Index; + +/** + * The DUAL table for selects without a FROM clause. + */ +public class DualTable extends VirtualTable { + + /** + * The name of the range table. + */ + public static final String NAME = "DUAL"; + + /** + * Create a new range with the given start and end expressions. + * + * @param database + * the database + */ + public DualTable(Database database) { + super(database.getMainSchema(), 0, NAME); + setColumns(new Column[0]); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return builder.append(NAME); + } + + @Override + public boolean canGetRowCount(SessionLocal session) { + return true; + } + + @Override + public long getRowCount(SessionLocal session) { + return 1L; + } + + @Override + public TableType getTableType() { + return TableType.SYSTEM_TABLE; + } + + @Override + public Index getScanIndex(SessionLocal session) { + return new DualIndex(this); + } + + @Override + public long getMaxDataModificationId() { + return 0L; + } + + @Override + public long getRowCountApproximation(SessionLocal session) { + return 1L; + } + + @Override + public boolean isDeterministic() { + return true; + } + +} diff --git a/h2/src/main/org/h2/table/FunctionTable.java b/h2/src/main/org/h2/table/FunctionTable.java new file mode 100644 index 0000000..61ea951 --- /dev/null +++ b/h2/src/main/org/h2/table/FunctionTable.java @@ -0,0 +1,69 @@ +/* + * 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 org.h2.engine.SessionLocal; +import org.h2.expression.function.table.TableFunction; +import org.h2.result.ResultInterface; +import org.h2.schema.Schema; + +/** + * A table backed by a system or user-defined function that returns a result + * set. + */ +public class FunctionTable extends VirtualConstructedTable { + + private final TableFunction function; + + public FunctionTable(Schema schema, SessionLocal session, TableFunction function) { + super(schema, 0, function.getName()); + this.function = function; + function.optimize(session); + ResultInterface result = function.getValueTemplate(session); + int columnCount = result.getVisibleColumnCount(); + Column[] cols = new Column[columnCount]; + for (int i = 0; i < columnCount; i++) { + cols[i] = new Column(result.getColumnName(i), result.getColumnType(i)); + } + setColumns(cols); + } + + @Override + public boolean canGetRowCount(SessionLocal session) { + return false; + } + + @Override + public long getRowCount(SessionLocal session) { + return Long.MAX_VALUE; + } + + @Override + public long getRowCountApproximation(SessionLocal session) { + return Long.MAX_VALUE; + } + + @Override + public ResultInterface getResult(SessionLocal session) { + return function.getValue(session); + } + + @Override + public String getSQL(int sqlFlags) { + return function.getSQL(sqlFlags); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return builder.append(function.getSQL(sqlFlags)); + } + + @Override + public boolean isDeterministic() { + return function.isDeterministic(); + } + +} diff --git a/h2/src/main/org/h2/table/GeneratedColumnResolver.java b/h2/src/main/org/h2/table/GeneratedColumnResolver.java new file mode 100644 index 0000000..a7883de --- /dev/null +++ b/h2/src/main/org/h2/table/GeneratedColumnResolver.java @@ -0,0 +1,101 @@ +/* + * 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.HashMap; + +import org.h2.result.Row; +import org.h2.value.Value; +import org.h2.value.ValueBigint; + +/** + * Column resolver for generated columns. + */ +class GeneratedColumnResolver implements ColumnResolver { + + private final Table table; + + private Column[] columns; + + private HashMap columnMap; + + private Row current; + + /** + * Column resolver for generated columns. + * + * @param table + * the table + */ + GeneratedColumnResolver(Table table) { + this.table = table; + } + + /** + * Set the current row. + * + * @param current + * the current row + */ + void set(Row current) { + this.current = current; + } + + @Override + public Column[] getColumns() { + Column[] columns = this.columns; + if (columns == null) { + this.columns = columns = createColumns(); + } + return columns; + } + + private Column[] createColumns() { + Column[] allColumns = table.getColumns(); + int totalCount = allColumns.length, baseCount = totalCount; + for (int i = 0; i < totalCount; i++) { + if (allColumns[i].isGenerated()) { + baseCount--; + } + } + Column[] baseColumns = new Column[baseCount]; + for (int i = 0, j = 0; i < totalCount; i++) { + Column c = allColumns[i]; + if (!c.isGenerated()) { + baseColumns[j++] = c; + } + } + return baseColumns; + } + + @Override + public Column findColumn(String name) { + HashMap columnMap = this.columnMap; + if (columnMap == null) { + columnMap = table.getDatabase().newStringMap(); + for (Column c : getColumns()) { + columnMap.put(c.getName(), c); + } + this.columnMap = columnMap; + } + return columnMap.get(name); + } + + @Override + public Value getValue(Column column) { + int columnId = column.getColumnId(); + if (columnId == -1) { + return ValueBigint.get(current.getKey()); + } + return current.getValue(columnId); + } + + @Override + public Column getRowIdColumn() { + return table.getRowIdColumn(); + } + +} diff --git a/h2/src/main/org/h2/table/IndexColumn.java b/h2/src/main/org/h2/table/IndexColumn.java new file mode 100644 index 0000000..16cfbf8 --- /dev/null +++ b/h2/src/main/org/h2/table/IndexColumn.java @@ -0,0 +1,192 @@ +/* + * 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 org.h2.result.SortOrder; +import org.h2.util.HasSQL; +import org.h2.util.ParserUtil; + +/** + * This represents a column item of an index. This is required because some + * indexes support descending sorted columns. + */ +public class IndexColumn { + + /** + * Do not append ordering. + */ + public static final int SQL_NO_ORDER = 0x8000_0000; + + /** + * The column name. + */ + public final String columnName; + + /** + * The column, or null if not set. + */ + public Column column; + + /** + * The sort type. Ascending (the default) and descending are supported; + * nulls can be sorted first or last. + */ + public int sortType = SortOrder.ASCENDING; + + /** + * Appends the specified columns to the specified builder. + * + * @param builder + * string builder + * @param columns + * index columns + * @param sqlFlags + * formatting flags + * @return the specified string builder + */ + public static StringBuilder writeColumns(StringBuilder builder, IndexColumn[] columns, int sqlFlags) { + return writeColumns(builder, columns, 0, columns.length, sqlFlags); + } + + /** + * Appends the specified columns to the specified builder. + * + * @param builder + * string builder + * @param startOffset + * start offset, inclusive + * @param endOffset + * end offset, exclusive + * @param columns + * index columns + * @param sqlFlags + * formatting flags + * @return the specified string builder + */ + public static StringBuilder writeColumns(StringBuilder builder, IndexColumn[] columns, int startOffset, + int endOffset, int sqlFlags) { + for (int i = startOffset; i < endOffset; i++) { + if (i > startOffset) { + builder.append(", "); + } + columns[i].getSQL(builder, sqlFlags); + } + return builder; + } + + /** + * Appends the specified columns to the specified builder. + * + * @param builder + * string builder + * @param columns + * index columns + * @param separator + * separator + * @param suffix + * additional SQL to append after each column + * @param sqlFlags + * formatting flags + * @return the specified string builder + */ + public static StringBuilder writeColumns(StringBuilder builder, IndexColumn[] columns, String separator, + String suffix, int sqlFlags) { + for (int i = 0, l = columns.length; i < l; i++) { + if (i > 0) { + builder.append(separator); + } + columns[i].getSQL(builder, sqlFlags).append(suffix); + } + return builder; + } + + /** + * Creates a new instance with the specified name. + * + * @param columnName + * the column name + */ + public IndexColumn(String columnName) { + this.columnName = columnName; + } + + /** + * Creates a new instance with the specified name. + * + * @param columnName + * the column name + * @param sortType + * the sort type + */ + public IndexColumn(String columnName, int sortType) { + this.columnName = columnName; + this.sortType = sortType; + } + + /** + * Creates a new instance with the specified column. + * + * @param column + * the column + */ + public IndexColumn(Column column) { + columnName = null; + this.column = column; + } + + /** + * Appends the SQL snippet for this index column to the specified string builder. + * + * @param builder + * string builder + * @param sqlFlags + * formatting flags + * @return the specified string builder + */ + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + if (column != null) { + column.getSQL(builder, sqlFlags); + } else { + ParserUtil.quoteIdentifier(builder, columnName, sqlFlags); + } + if ((sqlFlags & SQL_NO_ORDER) == 0) { + SortOrder.typeToString(builder, sortType); + } + return builder; + } + + /** + * Create an array of index columns from a list of columns. The default sort + * type is used. + * + * @param columns the column list + * @return the index column array + */ + public static IndexColumn[] wrap(Column[] columns) { + IndexColumn[] list = new IndexColumn[columns.length]; + for (int i = 0; i < list.length; i++) { + list[i] = new IndexColumn(columns[i]); + } + return list; + } + + /** + * Map the columns using the column names and the specified table. + * + * @param indexColumns the column list with column names set + * @param table the table from where to map the column names to columns + */ + public static void mapColumns(IndexColumn[] indexColumns, Table table) { + for (IndexColumn col : indexColumns) { + col.column = table.getColumn(col.columnName); + } + } + + @Override + public String toString() { + return getSQL(new StringBuilder("IndexColumn "), HasSQL.TRACE_SQL_FLAGS).toString(); + } +} diff --git a/h2/src/main/org/h2/table/IndexHints.java b/h2/src/main/org/h2/table/IndexHints.java new file mode 100644 index 0000000..30a3e1b --- /dev/null +++ b/h2/src/main/org/h2/table/IndexHints.java @@ -0,0 +1,57 @@ +/* + * 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.LinkedHashSet; +import java.util.Set; + +import org.h2.index.Index; + +/** + * Contains the hints for which index to use for a specific table. Currently + * allows a list of "use indexes" to be specified. + *

+ * Use the factory method IndexHints.createUseIndexHints(listOfIndexes) to limit + * the query planner to only use specific indexes when determining which index + * to use for a table + **/ +public final class IndexHints { + + private final LinkedHashSet allowedIndexes; + + private IndexHints(LinkedHashSet allowedIndexes) { + this.allowedIndexes = allowedIndexes; + } + + /** + * Create an index hint object. + * + * @param allowedIndexes the set of allowed indexes + * @return the hint object + */ + public static IndexHints createUseIndexHints(LinkedHashSet allowedIndexes) { + return new IndexHints(allowedIndexes); + } + + public Set getAllowedIndexes() { + return allowedIndexes; + } + + @Override + public String toString() { + return "IndexHints{allowedIndexes=" + allowedIndexes + '}'; + } + + /** + * Allow an index to be used. + * + * @param index the index + * @return whether it was already allowed + */ + public boolean allowIndex(Index index) { + return allowedIndexes.contains(index.getName()); + } +} diff --git a/h2/src/main/org/h2/table/InformationSchemaTable.java b/h2/src/main/org/h2/table/InformationSchemaTable.java new file mode 100644 index 0000000..c7ac425 --- /dev/null +++ b/h2/src/main/org/h2/table/InformationSchemaTable.java @@ -0,0 +1,3481 @@ +/* + * 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.io.IOException; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; + +import org.h2.api.IntervalQualifier; +import org.h2.api.Trigger; +import org.h2.command.Command; +import org.h2.command.Parser; +import org.h2.constraint.Constraint; +import org.h2.constraint.Constraint.Type; +import org.h2.constraint.ConstraintCheck; +import org.h2.constraint.ConstraintDomain; +import org.h2.constraint.ConstraintReferential; +import org.h2.constraint.ConstraintUnique; +import org.h2.engine.Constants; +import org.h2.engine.DbObject; +import org.h2.engine.QueryStatisticsData; +import org.h2.engine.Right; +import org.h2.engine.RightOwner; +import org.h2.engine.Role; +import org.h2.engine.SessionLocal; +import org.h2.engine.SessionLocal.State; +import org.h2.engine.Setting; +import org.h2.engine.User; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.ValueExpression; +import org.h2.index.Index; +import org.h2.index.MetaIndex; +import org.h2.message.DbException; +import org.h2.mvstore.FileStore; +import org.h2.mvstore.MVStore; +import org.h2.mvstore.db.Store; +import org.h2.result.Row; +import org.h2.result.SearchRow; +import org.h2.result.SortOrder; +import org.h2.schema.Constant; +import org.h2.schema.Domain; +import org.h2.schema.FunctionAlias; +import org.h2.schema.Schema; +import org.h2.schema.Sequence; +import org.h2.schema.TriggerObject; +import org.h2.schema.UserDefinedFunction; +import org.h2.schema.FunctionAlias.JavaMethod; +import org.h2.store.InDoubtTransaction; +import org.h2.util.DateTimeUtils; +import org.h2.util.MathUtils; +import org.h2.util.NetworkConnectionInfo; +import org.h2.util.StringUtils; +import org.h2.util.TimeZoneProvider; +import org.h2.util.Utils; +import org.h2.util.geometry.EWKTUtils; +import org.h2.value.CompareMode; +import org.h2.value.DataType; +import org.h2.value.ExtTypeInfoEnum; +import org.h2.value.ExtTypeInfoGeometry; +import org.h2.value.ExtTypeInfoRow; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBigint; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueDouble; +import org.h2.value.ValueInteger; +import org.h2.value.ValueNull; +import org.h2.value.ValueToObjectConverter2; +import org.h2.value.ValueVarchar; + +/** + * This class is responsible to build the INFORMATION_SCHEMA tables. + */ +public final class InformationSchemaTable extends MetaTable { + + private static final String CHARACTER_SET_NAME = "Unicode"; + + // Standard table + + private static final int INFORMATION_SCHEMA_CATALOG_NAME = 0; + + // Standard views + + private static final int CHECK_CONSTRAINTS = INFORMATION_SCHEMA_CATALOG_NAME + 1; + + private static final int COLLATIONS = CHECK_CONSTRAINTS + 1; + + private static final int COLUMNS = COLLATIONS + 1; + + private static final int COLUMN_PRIVILEGES = COLUMNS + 1; + + private static final int CONSTRAINT_COLUMN_USAGE = COLUMN_PRIVILEGES + 1; + + private static final int DOMAINS = CONSTRAINT_COLUMN_USAGE + 1; + + private static final int DOMAIN_CONSTRAINTS = DOMAINS + 1; + + private static final int ELEMENT_TYPES = DOMAIN_CONSTRAINTS + 1; + + private static final int FIELDS = ELEMENT_TYPES + 1; + + private static final int KEY_COLUMN_USAGE = FIELDS + 1; + + private static final int PARAMETERS = KEY_COLUMN_USAGE + 1; + + private static final int REFERENTIAL_CONSTRAINTS = PARAMETERS + 1; + + private static final int ROUTINES = REFERENTIAL_CONSTRAINTS + 1; + + private static final int SCHEMATA = ROUTINES + 1; + + private static final int SEQUENCES = SCHEMATA + 1; + + private static final int TABLES = SEQUENCES + 1; + + private static final int TABLE_CONSTRAINTS = TABLES + 1; + + private static final int TABLE_PRIVILEGES = TABLE_CONSTRAINTS + 1; + + private static final int TRIGGERS = TABLE_PRIVILEGES + 1; + + private static final int VIEWS = TRIGGERS + 1; + + // Extensions + + private static final int CONSTANTS = VIEWS + 1; + + private static final int ENUM_VALUES = CONSTANTS + 1; + + private static final int INDEXES = ENUM_VALUES + 1; + + private static final int INDEX_COLUMNS = INDEXES + 1; + + private static final int IN_DOUBT = INDEX_COLUMNS + 1; + + private static final int LOCKS = IN_DOUBT + 1; + + private static final int QUERY_STATISTICS = LOCKS + 1; + + private static final int RIGHTS = QUERY_STATISTICS + 1; + + private static final int ROLES = RIGHTS + 1; + + private static final int SESSIONS = ROLES + 1; + + private static final int SESSION_STATE = SESSIONS + 1; + + private static final int SETTINGS = SESSION_STATE + 1; + + private static final int SYNONYMS = SETTINGS + 1; + + private static final int USERS = SYNONYMS + 1; + + /** + * The number of meta table types. Supported meta table types are + * {@code 0..META_TABLE_TYPE_COUNT - 1}. + */ + public static final int META_TABLE_TYPE_COUNT = USERS + 1; + + private final boolean isView; + + /** + * Create a new metadata table. + * + * @param schema the schema + * @param id the object id + * @param type the meta table type + */ + public InformationSchemaTable(Schema schema, int id, int type) { + super(schema, id, type); + Column[] cols; + String indexColumnName = null; + boolean isView = true; + switch (type) { + // Standard table + case INFORMATION_SCHEMA_CATALOG_NAME: + setMetaTableName("INFORMATION_SCHEMA_CATALOG_NAME"); + isView = false; + cols = new Column[] { + column("CATALOG_NAME"), // + }; + break; + // Standard views + case CHECK_CONSTRAINTS: + setMetaTableName("CHECK_CONSTRAINTS"); + cols = new Column[] { + column("CONSTRAINT_CATALOG"), // + column("CONSTRAINT_SCHEMA"), // + column("CONSTRAINT_NAME"), // + column("CHECK_CLAUSE"), // + }; + indexColumnName = "CONSTRAINT_NAME"; + break; + case COLLATIONS: + setMetaTableName("COLLATIONS"); + cols = new Column[] { + column("COLLATION_CATALOG"), // + column("COLLATION_SCHEMA"), // + column("COLLATION_NAME"), // + column("PAD_ATTRIBUTE"), // + // extensions + column("LANGUAGE_TAG"), // + }; + break; + case COLUMNS: + setMetaTableName("COLUMNS"); + cols = new Column[] { + column("TABLE_CATALOG"), // + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("COLUMN_NAME"), // + column("ORDINAL_POSITION", TypeInfo.TYPE_INTEGER), // + column("COLUMN_DEFAULT"), // + column("IS_NULLABLE"), // + column("DATA_TYPE"), // + column("CHARACTER_MAXIMUM_LENGTH", TypeInfo.TYPE_BIGINT), // + column("CHARACTER_OCTET_LENGTH", TypeInfo.TYPE_BIGINT), // + column("NUMERIC_PRECISION", TypeInfo.TYPE_INTEGER), // + column("NUMERIC_PRECISION_RADIX", TypeInfo.TYPE_INTEGER), // + column("NUMERIC_SCALE", TypeInfo.TYPE_INTEGER), // + column("DATETIME_PRECISION", TypeInfo.TYPE_INTEGER), // + column("INTERVAL_TYPE"), // + column("INTERVAL_PRECISION", TypeInfo.TYPE_INTEGER), // + column("CHARACTER_SET_CATALOG"), // + column("CHARACTER_SET_SCHEMA"), // + column("CHARACTER_SET_NAME"), // + column("COLLATION_CATALOG"), // + column("COLLATION_SCHEMA"), // + column("COLLATION_NAME"), // + column("DOMAIN_CATALOG"), // + column("DOMAIN_SCHEMA"), // + column("DOMAIN_NAME"), // + column("MAXIMUM_CARDINALITY", TypeInfo.TYPE_INTEGER), // + column("DTD_IDENTIFIER"), // + column("IS_IDENTITY"), // + column("IDENTITY_GENERATION"), // + column("IDENTITY_START", TypeInfo.TYPE_BIGINT), // + column("IDENTITY_INCREMENT", TypeInfo.TYPE_BIGINT), // + column("IDENTITY_MAXIMUM", TypeInfo.TYPE_BIGINT), // + column("IDENTITY_MINIMUM", TypeInfo.TYPE_BIGINT), // + column("IDENTITY_CYCLE"), // + column("IS_GENERATED"), // + column("GENERATION_EXPRESSION"), // + column("DECLARED_DATA_TYPE"), // + column("DECLARED_NUMERIC_PRECISION", TypeInfo.TYPE_INTEGER), // + column("DECLARED_NUMERIC_SCALE", TypeInfo.TYPE_INTEGER), // + // extensions + column("GEOMETRY_TYPE"), // + column("GEOMETRY_SRID", TypeInfo.TYPE_INTEGER), // + column("IDENTITY_BASE", TypeInfo.TYPE_BIGINT), // + column("IDENTITY_CACHE", TypeInfo.TYPE_BIGINT), // + column("COLUMN_ON_UPDATE"), // + column("IS_VISIBLE", TypeInfo.TYPE_BOOLEAN), // + column("DEFAULT_ON_NULL", TypeInfo.TYPE_BOOLEAN), // + column("SELECTIVITY", TypeInfo.TYPE_INTEGER), // + column("REMARKS"), // + }; + indexColumnName = "TABLE_NAME"; + break; + case COLUMN_PRIVILEGES: + setMetaTableName("COLUMN_PRIVILEGES"); + cols = new Column[] { + column("GRANTOR"), // + column("GRANTEE"), // + column("TABLE_CATALOG"), // + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("COLUMN_NAME"), // + column("PRIVILEGE_TYPE"), // + column("IS_GRANTABLE"), // + }; + indexColumnName = "TABLE_NAME"; + break; + case CONSTRAINT_COLUMN_USAGE: + setMetaTableName("CONSTRAINT_COLUMN_USAGE"); + cols = new Column[] { + column("TABLE_CATALOG"), // + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("COLUMN_NAME"), // + column("CONSTRAINT_CATALOG"), // + column("CONSTRAINT_SCHEMA"), // + column("CONSTRAINT_NAME"), // + }; + indexColumnName = "TABLE_NAME"; + break; + case DOMAINS: + setMetaTableName("DOMAINS"); + cols = new Column[] { + column("DOMAIN_CATALOG"), // + column("DOMAIN_SCHEMA"), // + column("DOMAIN_NAME"), // + column("DATA_TYPE"), // + column("CHARACTER_MAXIMUM_LENGTH", TypeInfo.TYPE_BIGINT), // + column("CHARACTER_OCTET_LENGTH", TypeInfo.TYPE_BIGINT), // + column("CHARACTER_SET_CATALOG"), // + column("CHARACTER_SET_SCHEMA"), // + column("CHARACTER_SET_NAME"), // + column("COLLATION_CATALOG"), // + column("COLLATION_SCHEMA"), // + column("COLLATION_NAME"), // + column("NUMERIC_PRECISION", TypeInfo.TYPE_INTEGER), // + column("NUMERIC_PRECISION_RADIX", TypeInfo.TYPE_INTEGER), // + column("NUMERIC_SCALE", TypeInfo.TYPE_INTEGER), // + column("DATETIME_PRECISION", TypeInfo.TYPE_INTEGER), // + column("INTERVAL_TYPE"), // + column("INTERVAL_PRECISION", TypeInfo.TYPE_INTEGER), // + column("DOMAIN_DEFAULT"), // + column("MAXIMUM_CARDINALITY", TypeInfo.TYPE_INTEGER), // + column("DTD_IDENTIFIER"), // + column("DECLARED_DATA_TYPE"), // + column("DECLARED_NUMERIC_PRECISION", TypeInfo.TYPE_INTEGER), // + column("DECLARED_NUMERIC_SCALE", TypeInfo.TYPE_INTEGER), // + // extensions + column("GEOMETRY_TYPE"), // + column("GEOMETRY_SRID", TypeInfo.TYPE_INTEGER), // + column("DOMAIN_ON_UPDATE"), // + column("PARENT_DOMAIN_CATALOG"), // + column("PARENT_DOMAIN_SCHEMA"), // + column("PARENT_DOMAIN_NAME"), // + column("REMARKS"), // + }; + indexColumnName = "DOMAIN_NAME"; + break; + case DOMAIN_CONSTRAINTS: + setMetaTableName("DOMAIN_CONSTRAINTS"); + cols = new Column[] { + column("CONSTRAINT_CATALOG"), // + column("CONSTRAINT_SCHEMA"), // + column("CONSTRAINT_NAME"), // + column("DOMAIN_CATALOG"), // + column("DOMAIN_SCHEMA"), // + column("DOMAIN_NAME"), // + column("IS_DEFERRABLE"), // + column("INITIALLY_DEFERRED"), // + // extensions + column("REMARKS"), // + }; + indexColumnName = "DOMAIN_NAME"; + break; + case ELEMENT_TYPES: + setMetaTableName("ELEMENT_TYPES"); + cols = new Column[] { + column("OBJECT_CATALOG"), // + column("OBJECT_SCHEMA"), // + column("OBJECT_NAME"), // + column("OBJECT_TYPE"), // + column("COLLECTION_TYPE_IDENTIFIER"), // + column("DATA_TYPE"), // + column("CHARACTER_MAXIMUM_LENGTH", TypeInfo.TYPE_BIGINT), // + column("CHARACTER_OCTET_LENGTH", TypeInfo.TYPE_BIGINT), // + column("CHARACTER_SET_CATALOG"), // + column("CHARACTER_SET_SCHEMA"), // + column("CHARACTER_SET_NAME"), // + column("COLLATION_CATALOG"), // + column("COLLATION_SCHEMA"), // + column("COLLATION_NAME"), // + column("NUMERIC_PRECISION", TypeInfo.TYPE_INTEGER), // + column("NUMERIC_PRECISION_RADIX", TypeInfo.TYPE_INTEGER), // + column("NUMERIC_SCALE", TypeInfo.TYPE_INTEGER), // + column("DATETIME_PRECISION", TypeInfo.TYPE_INTEGER), // + column("INTERVAL_TYPE"), // + column("INTERVAL_PRECISION", TypeInfo.TYPE_INTEGER), // + column("MAXIMUM_CARDINALITY", TypeInfo.TYPE_INTEGER), // + column("DTD_IDENTIFIER"), // + column("DECLARED_DATA_TYPE"), // + column("DECLARED_NUMERIC_PRECISION", TypeInfo.TYPE_INTEGER), // + column("DECLARED_NUMERIC_SCALE", TypeInfo.TYPE_INTEGER), // + // extensions + column("GEOMETRY_TYPE"), // + column("GEOMETRY_SRID", TypeInfo.TYPE_INTEGER), // + }; + break; + case FIELDS: + setMetaTableName("FIELDS"); + cols = new Column[] { + column("OBJECT_CATALOG"), // + column("OBJECT_SCHEMA"), // + column("OBJECT_NAME"), // + column("OBJECT_TYPE"), // + column("ROW_IDENTIFIER"), // + column("FIELD_NAME"), // + column("ORDINAL_POSITION", TypeInfo.TYPE_INTEGER), // + column("DATA_TYPE"), // + column("CHARACTER_MAXIMUM_LENGTH", TypeInfo.TYPE_BIGINT), // + column("CHARACTER_OCTET_LENGTH", TypeInfo.TYPE_BIGINT), // + column("CHARACTER_SET_CATALOG"), // + column("CHARACTER_SET_SCHEMA"), // + column("CHARACTER_SET_NAME"), // + column("COLLATION_CATALOG"), // + column("COLLATION_SCHEMA"), // + column("COLLATION_NAME"), // + column("NUMERIC_PRECISION", TypeInfo.TYPE_INTEGER), // + column("NUMERIC_PRECISION_RADIX", TypeInfo.TYPE_INTEGER), // + column("NUMERIC_SCALE", TypeInfo.TYPE_INTEGER), // + column("DATETIME_PRECISION", TypeInfo.TYPE_INTEGER), // + column("INTERVAL_TYPE"), // + column("INTERVAL_PRECISION", TypeInfo.TYPE_INTEGER), // + column("MAXIMUM_CARDINALITY", TypeInfo.TYPE_INTEGER), // + column("DTD_IDENTIFIER"), // + column("DECLARED_DATA_TYPE"), // + column("DECLARED_NUMERIC_PRECISION", TypeInfo.TYPE_INTEGER), // + column("DECLARED_NUMERIC_SCALE", TypeInfo.TYPE_INTEGER), // + // extensions + column("GEOMETRY_TYPE"), // + column("GEOMETRY_SRID", TypeInfo.TYPE_INTEGER), // + }; + break; + case KEY_COLUMN_USAGE: + setMetaTableName("KEY_COLUMN_USAGE"); + cols = new Column[] { + column("CONSTRAINT_CATALOG"), // + column("CONSTRAINT_SCHEMA"), // + column("CONSTRAINT_NAME"), // + column("TABLE_CATALOG"), // + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("COLUMN_NAME"), // + column("ORDINAL_POSITION", TypeInfo.TYPE_INTEGER), // + column("POSITION_IN_UNIQUE_CONSTRAINT", TypeInfo.TYPE_INTEGER), // + }; + indexColumnName = "TABLE_NAME"; + break; + case PARAMETERS: + setMetaTableName("PARAMETERS"); + cols = new Column[] { + column("SPECIFIC_CATALOG"), // + column("SPECIFIC_SCHEMA"), // + column("SPECIFIC_NAME"), // + column("ORDINAL_POSITION", TypeInfo.TYPE_INTEGER), // + column("PARAMETER_MODE"), // + column("IS_RESULT"), // + column("AS_LOCATOR"), // + column("PARAMETER_NAME"), // + column("DATA_TYPE"), // + column("CHARACTER_MAXIMUM_LENGTH", TypeInfo.TYPE_BIGINT), // + column("CHARACTER_OCTET_LENGTH", TypeInfo.TYPE_BIGINT), // + column("CHARACTER_SET_CATALOG"), // + column("CHARACTER_SET_SCHEMA"), // + column("CHARACTER_SET_NAME"), // + column("COLLATION_CATALOG"), // + column("COLLATION_SCHEMA"), // + column("COLLATION_NAME"), // + column("NUMERIC_PRECISION", TypeInfo.TYPE_INTEGER), // + column("NUMERIC_PRECISION_RADIX", TypeInfo.TYPE_INTEGER), // + column("NUMERIC_SCALE", TypeInfo.TYPE_INTEGER), // + column("DATETIME_PRECISION", TypeInfo.TYPE_INTEGER), // + column("INTERVAL_TYPE"), // + column("INTERVAL_PRECISION", TypeInfo.TYPE_INTEGER), // + column("MAXIMUM_CARDINALITY", TypeInfo.TYPE_INTEGER), // + column("DTD_IDENTIFIER"), // + column("DECLARED_DATA_TYPE"), // + column("DECLARED_NUMERIC_PRECISION", TypeInfo.TYPE_INTEGER), // + column("DECLARED_NUMERIC_SCALE", TypeInfo.TYPE_INTEGER), // + column("PARAMETER_DEFAULT"), // + // extensions + column("GEOMETRY_TYPE"), // + column("GEOMETRY_SRID", TypeInfo.TYPE_INTEGER), // + }; + break; + case REFERENTIAL_CONSTRAINTS: + setMetaTableName("REFERENTIAL_CONSTRAINTS"); + cols = new Column[] { + column("CONSTRAINT_CATALOG"), // + column("CONSTRAINT_SCHEMA"), // + column("CONSTRAINT_NAME"), // + column("UNIQUE_CONSTRAINT_CATALOG"), // + column("UNIQUE_CONSTRAINT_SCHEMA"), // + column("UNIQUE_CONSTRAINT_NAME"), // + column("MATCH_OPTION"), // + column("UPDATE_RULE"), // + column("DELETE_RULE"), // + }; + indexColumnName = "CONSTRAINT_NAME"; + break; + case ROUTINES: + setMetaTableName("ROUTINES"); + cols = new Column[] { + column("SPECIFIC_CATALOG"), // + column("SPECIFIC_SCHEMA"), // + column("SPECIFIC_NAME"), // + column("ROUTINE_CATALOG"), // + column("ROUTINE_SCHEMA"), // + column("ROUTINE_NAME"), // + column("ROUTINE_TYPE"), // + column("DATA_TYPE"), // + column("CHARACTER_MAXIMUM_LENGTH", TypeInfo.TYPE_BIGINT), // + column("CHARACTER_OCTET_LENGTH", TypeInfo.TYPE_BIGINT), // + column("CHARACTER_SET_CATALOG"), // + column("CHARACTER_SET_SCHEMA"), // + column("CHARACTER_SET_NAME"), // + column("COLLATION_CATALOG"), // + column("COLLATION_SCHEMA"), // + column("COLLATION_NAME"), // + column("NUMERIC_PRECISION", TypeInfo.TYPE_INTEGER), // + column("NUMERIC_PRECISION_RADIX", TypeInfo.TYPE_INTEGER), // + column("NUMERIC_SCALE", TypeInfo.TYPE_INTEGER), // + column("DATETIME_PRECISION", TypeInfo.TYPE_INTEGER), // + column("INTERVAL_TYPE"), // + column("INTERVAL_PRECISION", TypeInfo.TYPE_INTEGER), // + column("MAXIMUM_CARDINALITY", TypeInfo.TYPE_INTEGER), // + column("DTD_IDENTIFIER"), // + column("ROUTINE_BODY"), // + column("ROUTINE_DEFINITION"), // + column("EXTERNAL_NAME"), // + column("EXTERNAL_LANGUAGE"), // + column("PARAMETER_STYLE"), // + column("IS_DETERMINISTIC"), // + column("DECLARED_DATA_TYPE"), // + column("DECLARED_NUMERIC_PRECISION", TypeInfo.TYPE_INTEGER), // + column("DECLARED_NUMERIC_SCALE", TypeInfo.TYPE_INTEGER), // + // extensions + column("GEOMETRY_TYPE"), // + column("GEOMETRY_SRID", TypeInfo.TYPE_INTEGER), // + column("REMARKS"), // + }; + break; + case SCHEMATA: + setMetaTableName("SCHEMATA"); + cols = new Column[] { + column("CATALOG_NAME"), // + column("SCHEMA_NAME"), // + column("SCHEMA_OWNER"), // + column("DEFAULT_CHARACTER_SET_CATALOG"), // + column("DEFAULT_CHARACTER_SET_SCHEMA"), // + column("DEFAULT_CHARACTER_SET_NAME"), // + column("SQL_PATH"), // + // extensions + column("DEFAULT_COLLATION_NAME"), // // MySQL + column("REMARKS"), // + }; + break; + case SEQUENCES: + setMetaTableName("SEQUENCES"); + cols = new Column[] { + column("SEQUENCE_CATALOG"), // + column("SEQUENCE_SCHEMA"), // + column("SEQUENCE_NAME"), // + column("DATA_TYPE"), // + column("NUMERIC_PRECISION", TypeInfo.TYPE_INTEGER), // + column("NUMERIC_PRECISION_RADIX", TypeInfo.TYPE_INTEGER), // + column("NUMERIC_SCALE", TypeInfo.TYPE_INTEGER), // + column("START_VALUE", TypeInfo.TYPE_BIGINT), // + column("MINIMUM_VALUE", TypeInfo.TYPE_BIGINT), // + column("MAXIMUM_VALUE", TypeInfo.TYPE_BIGINT), // + column("INCREMENT", TypeInfo.TYPE_BIGINT), // + column("CYCLE_OPTION"), // + column("DECLARED_DATA_TYPE"), // + column("DECLARED_NUMERIC_PRECISION", TypeInfo.TYPE_INTEGER), // + column("DECLARED_NUMERIC_SCALE", TypeInfo.TYPE_INTEGER), // + // extensions + column("BASE_VALUE", TypeInfo.TYPE_BIGINT), // + column("CACHE", TypeInfo.TYPE_BIGINT), // + column("REMARKS"), // + }; + indexColumnName = "SEQUENCE_NAME"; + break; + case TABLES: + setMetaTableName("TABLES"); + cols = new Column[] { + column("TABLE_CATALOG"), // + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("TABLE_TYPE"), // + column("IS_INSERTABLE_INTO"), // + column("COMMIT_ACTION"), // + // extensions + column("STORAGE_TYPE"), // + column("REMARKS"), // + column("LAST_MODIFICATION", TypeInfo.TYPE_BIGINT), // + column("TABLE_CLASS"), // + column("ROW_COUNT_ESTIMATE", TypeInfo.TYPE_BIGINT), // + }; + indexColumnName = "TABLE_NAME"; + break; + case TABLE_CONSTRAINTS: + setMetaTableName("TABLE_CONSTRAINTS"); + cols = new Column[] { + column("CONSTRAINT_CATALOG"), // + column("CONSTRAINT_SCHEMA"), // + column("CONSTRAINT_NAME"), // + column("CONSTRAINT_TYPE"), // + column("TABLE_CATALOG"), // + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("IS_DEFERRABLE"), // + column("INITIALLY_DEFERRED"), // + column("ENFORCED"), // + // extensions + column("INDEX_CATALOG"), // + column("INDEX_SCHEMA"), // + column("INDEX_NAME"), // + column("REMARKS"), // + }; + indexColumnName = "TABLE_NAME"; + break; + case TABLE_PRIVILEGES: + setMetaTableName("TABLE_PRIVILEGES"); + cols = new Column[] { + column("GRANTOR"), // + column("GRANTEE"), // + column("TABLE_CATALOG"), // + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("PRIVILEGE_TYPE"), // + column("IS_GRANTABLE"), // + column("WITH_HIERARCHY"), // + }; + indexColumnName = "TABLE_NAME"; + break; + case TRIGGERS: + setMetaTableName("TRIGGERS"); + cols = new Column[] { + column("TRIGGER_CATALOG"), // + column("TRIGGER_SCHEMA"), // + column("TRIGGER_NAME"), // + column("EVENT_MANIPULATION"), // + column("EVENT_OBJECT_CATALOG"), // + column("EVENT_OBJECT_SCHEMA"), // + column("EVENT_OBJECT_TABLE"), // + column("ACTION_ORIENTATION"), // + column("ACTION_TIMING"), // + // extensions + column("IS_ROLLBACK", TypeInfo.TYPE_BOOLEAN), // + column("JAVA_CLASS"), // + column("QUEUE_SIZE", TypeInfo.TYPE_INTEGER), // + column("NO_WAIT", TypeInfo.TYPE_BOOLEAN), // + column("REMARKS"), // + }; + indexColumnName = "EVENT_OBJECT_TABLE"; + break; + case VIEWS: + setMetaTableName("VIEWS"); + cols = new Column[] { + column("TABLE_CATALOG"), // + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("VIEW_DEFINITION"), // + column("CHECK_OPTION"), // + column("IS_UPDATABLE"), // + column("INSERTABLE_INTO"), // + column("IS_TRIGGER_UPDATABLE"), // + column("IS_TRIGGER_DELETABLE"), // + column("IS_TRIGGER_INSERTABLE_INTO"), // + // extensions + column("STATUS"), // + column("REMARKS"), // + }; + indexColumnName = "TABLE_NAME"; + break; + // Extensions + case CONSTANTS: + setMetaTableName("CONSTANTS"); + isView = false; + cols = new Column[] { + column("CONSTANT_CATALOG"), // + column("CONSTANT_SCHEMA"), // + column("CONSTANT_NAME"), // + column("VALUE_DEFINITION"), // + column("DATA_TYPE"), // + column("CHARACTER_MAXIMUM_LENGTH", TypeInfo.TYPE_BIGINT), // + column("CHARACTER_OCTET_LENGTH", TypeInfo.TYPE_BIGINT), // + column("CHARACTER_SET_CATALOG"), // + column("CHARACTER_SET_SCHEMA"), // + column("CHARACTER_SET_NAME"), // + column("COLLATION_CATALOG"), // + column("COLLATION_SCHEMA"), // + column("COLLATION_NAME"), // + column("NUMERIC_PRECISION", TypeInfo.TYPE_INTEGER), // + column("NUMERIC_PRECISION_RADIX", TypeInfo.TYPE_INTEGER), // + column("NUMERIC_SCALE", TypeInfo.TYPE_INTEGER), // + column("DATETIME_PRECISION", TypeInfo.TYPE_INTEGER), // + column("INTERVAL_TYPE"), // + column("INTERVAL_PRECISION", TypeInfo.TYPE_INTEGER), // + column("MAXIMUM_CARDINALITY", TypeInfo.TYPE_INTEGER), // + column("DTD_IDENTIFIER"), // + column("DECLARED_DATA_TYPE"), // + column("DECLARED_NUMERIC_PRECISION", TypeInfo.TYPE_INTEGER), // + column("DECLARED_NUMERIC_SCALE", TypeInfo.TYPE_INTEGER), // + column("GEOMETRY_TYPE"), // + column("GEOMETRY_SRID", TypeInfo.TYPE_INTEGER), // + column("REMARKS"), // + }; + indexColumnName = "CONSTANT_NAME"; + break; + case ENUM_VALUES: + setMetaTableName("ENUM_VALUES"); + isView = false; + cols = new Column[] { + column("OBJECT_CATALOG"), // + column("OBJECT_SCHEMA"), // + column("OBJECT_NAME"), // + column("OBJECT_TYPE"), // + column("ENUM_IDENTIFIER"), // + column("VALUE_NAME"), // + column("VALUE_ORDINAL"), // + }; + break; + case INDEXES: + setMetaTableName("INDEXES"); + isView = false; + cols = new Column[] { + column("INDEX_CATALOG"), // + column("INDEX_SCHEMA"), // + column("INDEX_NAME"), // + column("TABLE_CATALOG"), // + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("INDEX_TYPE_NAME"), // + column("IS_GENERATED", TypeInfo.TYPE_BOOLEAN), // + column("REMARKS"), // + column("INDEX_CLASS"), // + }; + indexColumnName = "TABLE_NAME"; + break; + case INDEX_COLUMNS: + setMetaTableName("INDEX_COLUMNS"); + isView = false; + cols = new Column[] { + column("INDEX_CATALOG"), // + column("INDEX_SCHEMA"), // + column("INDEX_NAME"), // + column("TABLE_CATALOG"), // + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("COLUMN_NAME"), // + column("ORDINAL_POSITION", TypeInfo.TYPE_INTEGER), // + column("ORDERING_SPECIFICATION"), // + column("NULL_ORDERING"), // + column("IS_UNIQUE", TypeInfo.TYPE_BOOLEAN), // + }; + indexColumnName = "TABLE_NAME"; + break; + case IN_DOUBT: + setMetaTableName("IN_DOUBT"); + isView = false; + cols = new Column[] { + column("TRANSACTION_NAME"), // + column("TRANSACTION_STATE"), // + }; + break; + case LOCKS: + setMetaTableName("LOCKS"); + isView = false; + cols = new Column[] { + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("SESSION_ID", TypeInfo.TYPE_INTEGER), // + column("LOCK_TYPE"), // + }; + break; + case QUERY_STATISTICS: + setMetaTableName("QUERY_STATISTICS"); + isView = false; + cols = new Column[] { + column("SQL_STATEMENT"), // + column("EXECUTION_COUNT", TypeInfo.TYPE_INTEGER), // + column("MIN_EXECUTION_TIME", TypeInfo.TYPE_DOUBLE), // + column("MAX_EXECUTION_TIME", TypeInfo.TYPE_DOUBLE), // + column("CUMULATIVE_EXECUTION_TIME", TypeInfo.TYPE_DOUBLE), // + column("AVERAGE_EXECUTION_TIME", TypeInfo.TYPE_DOUBLE), // + column("STD_DEV_EXECUTION_TIME", TypeInfo.TYPE_DOUBLE), // + column("MIN_ROW_COUNT", TypeInfo.TYPE_BIGINT), // + column("MAX_ROW_COUNT", TypeInfo.TYPE_BIGINT), // + column("CUMULATIVE_ROW_COUNT", TypeInfo.TYPE_BIGINT), // + column("AVERAGE_ROW_COUNT", TypeInfo.TYPE_DOUBLE), // + column("STD_DEV_ROW_COUNT", TypeInfo.TYPE_DOUBLE), // + }; + break; + case RIGHTS: + setMetaTableName("RIGHTS"); + isView = false; + cols = new Column[] { + column("GRANTEE"), // + column("GRANTEETYPE"), // + column("GRANTEDROLE"), // + column("RIGHTS"), // + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + }; + indexColumnName = "TABLE_NAME"; + break; + case ROLES: + setMetaTableName("ROLES"); + isView = false; + cols = new Column[] { + column("ROLE_NAME"), // + column("REMARKS"), // + }; + break; + case SESSIONS: + setMetaTableName("SESSIONS"); + isView = false; + cols = new Column[] { + column("SESSION_ID", TypeInfo.TYPE_INTEGER), // + column("USER_NAME"), // + column("SERVER"), // + column("CLIENT_ADDR"), // + column("CLIENT_INFO"), // + column("SESSION_START", TypeInfo.TYPE_TIMESTAMP_TZ), // + column("ISOLATION_LEVEL"), // + column("EXECUTING_STATEMENT"), // + column("EXECUTING_STATEMENT_START", TypeInfo.TYPE_TIMESTAMP_TZ), // + column("CONTAINS_UNCOMMITTED", TypeInfo.TYPE_BOOLEAN), // + column("SESSION_STATE"), // + column("BLOCKER_ID", TypeInfo.TYPE_INTEGER), // + column("SLEEP_SINCE", TypeInfo.TYPE_TIMESTAMP_TZ), // + }; + break; + case SESSION_STATE: + setMetaTableName("SESSION_STATE"); + isView = false; + cols = new Column[] { + column("STATE_KEY"), // + column("STATE_COMMAND"), // + }; + break; + case SETTINGS: + setMetaTableName("SETTINGS"); + isView = false; + cols = new Column[] { + column("SETTING_NAME"), // + column("SETTING_VALUE"), // + }; + break; + case SYNONYMS: + setMetaTableName("SYNONYMS"); + isView = false; + cols = new Column[] { + column("SYNONYM_CATALOG"), // + column("SYNONYM_SCHEMA"), // + column("SYNONYM_NAME"), // + column("SYNONYM_FOR"), // + column("SYNONYM_FOR_SCHEMA"), // + column("TYPE_NAME"), // + column("STATUS"), // + column("REMARKS"), // + }; + indexColumnName = "SYNONYM_NAME"; + break; + case USERS: + setMetaTableName("USERS"); + isView = false; + cols = new Column[] { + column("USER_NAME"), // + column("IS_ADMIN", TypeInfo.TYPE_BOOLEAN), + column("REMARKS"), // + }; + break; + default: + throw DbException.getInternalError("type=" + type); + } + setColumns(cols); + + if (indexColumnName == null) { + indexColumn = -1; + metaIndex = null; + } else { + indexColumn = getColumn(database.sysIdentifier(indexColumnName)).getColumnId(); + IndexColumn[] indexCols = IndexColumn.wrap(new Column[] { cols[indexColumn] }); + metaIndex = new MetaIndex(this, indexCols, false); + } + this.isView = isView; + } + + @Override + public ArrayList generateRows(SessionLocal session, SearchRow first, SearchRow last) { + Value indexFrom = null, indexTo = null; + if (indexColumn >= 0) { + if (first != null) { + indexFrom = first.getValue(indexColumn); + } + if (last != null) { + indexTo = last.getValue(indexColumn); + } + } + ArrayList rows = Utils.newSmallArrayList(); + String catalog = database.getShortName(); + switch (type) { + // Standard table + case INFORMATION_SCHEMA_CATALOG_NAME: + informationSchemaCatalogName(session, rows, catalog); + break; + // Standard views + case CHECK_CONSTRAINTS: + checkConstraints(session, indexFrom, indexTo, rows, catalog); + break; + case COLLATIONS: + collations(session, rows, catalog); + break; + case COLUMNS: + columns(session, indexFrom, indexTo, rows, catalog); + break; + case COLUMN_PRIVILEGES: + columnPrivileges(session, indexFrom, indexTo, rows, catalog); + break; + case CONSTRAINT_COLUMN_USAGE: + constraintColumnUsage(session, indexFrom, indexTo, rows, catalog); + break; + case DOMAINS: + domains(session, indexFrom, indexTo, rows, catalog); + break; + case DOMAIN_CONSTRAINTS: + domainConstraints(session, indexFrom, indexTo, rows, catalog); + break; + case ELEMENT_TYPES: + elementTypesFields(session, rows, catalog, ELEMENT_TYPES); + break; + case FIELDS: + elementTypesFields(session, rows, catalog, FIELDS); + break; + case KEY_COLUMN_USAGE: + keyColumnUsage(session, indexFrom, indexTo, rows, catalog); + break; + case PARAMETERS: + parameters(session, rows, catalog); + break; + case REFERENTIAL_CONSTRAINTS: + referentialConstraints(session, indexFrom, indexTo, rows, catalog); + break; + case ROUTINES: + routines(session, rows, catalog); + break; + case SCHEMATA: + schemata(session, rows, catalog); + break; + case SEQUENCES: + sequences(session, indexFrom, indexTo, rows, catalog); + break; + case TABLES: + tables(session, indexFrom, indexTo, rows, catalog); + break; + case TABLE_CONSTRAINTS: + tableConstraints(session, indexFrom, indexTo, rows, catalog); + break; + case TABLE_PRIVILEGES: + tablePrivileges(session, indexFrom, indexTo, rows, catalog); + break; + case TRIGGERS: + triggers(session, indexFrom, indexTo, rows, catalog); + break; + case VIEWS: + views(session, indexFrom, indexTo, rows, catalog); + break; + // Extensions + case CONSTANTS: + constants(session, indexFrom, indexTo, rows, catalog); + break; + case ENUM_VALUES: + elementTypesFields(session, rows, catalog, ENUM_VALUES); + break; + case INDEXES: + indexes(session, indexFrom, indexTo, rows, catalog, false); + break; + case INDEX_COLUMNS: + indexes(session, indexFrom, indexTo, rows, catalog, true); + break; + case IN_DOUBT: + inDoubt(session, rows); + break; + case LOCKS: + locks(session, rows); + break; + case QUERY_STATISTICS: + queryStatistics(session, rows); + break; + case RIGHTS: + rights(session, indexFrom, indexTo, rows); + break; + case ROLES: + roles(session, rows); + break; + case SESSIONS: + sessions(session, rows); + break; + case SESSION_STATE: + sessionState(session, rows); + break; + case SETTINGS: + settings(session, rows); + break; + case SYNONYMS: + synonyms(session, rows, catalog); + break; + case USERS: + users(session, rows); + break; + default: + throw DbException.getInternalError("type=" + type); + } + return rows; + } + + private void informationSchemaCatalogName(SessionLocal session, ArrayList rows, String catalog) { + add(session, rows, + // CATALOG_NAME + catalog); + } + + private void checkConstraints(SessionLocal session, Value indexFrom, Value indexTo, ArrayList rows, + String catalog) { + for (Schema schema : database.getAllSchemas()) { + for (Constraint constraint : schema.getAllConstraints()) { + Type constraintType = constraint.getConstraintType(); + if (constraintType == Constraint.Type.CHECK) { + ConstraintCheck check = (ConstraintCheck) constraint; + Table table = check.getTable(); + if (hideTable(table, session)) { + continue; + } + } else if (constraintType != Constraint.Type.DOMAIN) { + continue; + } + String constraintName = constraint.getName(); + if (!checkIndex(session, constraintName, indexFrom, indexTo)) { + continue; + } + checkConstraints(session, rows, catalog, constraint, constraintName); + } + } + } + + private void checkConstraints(SessionLocal session, ArrayList rows, String catalog, Constraint constraint, + String constraintName) { + add(session, rows, + // CONSTRAINT_CATALOG + catalog, + // CONSTRAINT_SCHEMA + constraint.getSchema().getName(), + // CONSTRAINT_NAME + constraintName, + // CHECK_CLAUSE + constraint.getExpression().getSQL(DEFAULT_SQL_FLAGS, Expression.WITHOUT_PARENTHESES) + ); + } + + private void collations(SessionLocal session, ArrayList rows, String catalog) { + String mainSchemaName = database.getMainSchema().getName(); + collations(session, rows, catalog, mainSchemaName, "OFF", null); + for (Locale l : CompareMode.getCollationLocales(false)) { + collations(session, rows, catalog, mainSchemaName, CompareMode.getName(l), l.toLanguageTag()); + } + } + + private void collations(SessionLocal session, ArrayList rows, String catalog, String mainSchemaName, + String name, String languageTag) { + if ("und".equals(languageTag)) { + languageTag = null; + } + add(session, rows, + // COLLATION_CATALOG + catalog, + // COLLATION_SCHEMA + mainSchemaName, + // COLLATION_NAME + name, + // PAD_ATTRIBUTE + "NO PAD", + // extensions + // LANGUAGE_TAG + languageTag + ); + } + + private void columns(SessionLocal session, Value indexFrom, Value indexTo, ArrayList rows, String catalog) { + String mainSchemaName = database.getMainSchema().getName(); + String collation = database.getCompareMode().getName(); + if (indexFrom != null && indexFrom.equals(indexTo)) { + String tableName = indexFrom.getString(); + if (tableName == null) { + return; + } + for (Schema schema : database.getAllSchemas()) { + Table table = schema.getTableOrViewByName(session, tableName); + if (table != null) { + columns(session, rows, catalog, mainSchemaName, collation, table, table.getName()); + } + } + Table table = session.findLocalTempTable(tableName); + if (table != null) { + columns(session, rows, catalog, mainSchemaName, collation, table, table.getName()); + } + } else { + for (Schema schema : database.getAllSchemas()) { + for (Table table : schema.getAllTablesAndViews(session)) { + String tableName = table.getName(); + if (checkIndex(session, tableName, indexFrom, indexTo)) { + columns(session, rows, catalog, mainSchemaName, collation, table, tableName); + } + } + } + for (Table table : session.getLocalTempTables()) { + String tableName = table.getName(); + if (checkIndex(session, tableName, indexFrom, indexTo)) { + columns(session, rows, catalog, mainSchemaName, collation, table, tableName); + } + } + } + } + + private void columns(SessionLocal session, ArrayList rows, String catalog, String mainSchemaName, + String collation, Table table, String tableName) { + if (hideTable(table, session)) { + return; + } + Column[] cols = table.getColumns(); + for (int i = 0, l = cols.length; i < l;) { + columns(session, rows, catalog, mainSchemaName, collation, table, tableName, cols[i], ++i); + } + } + + private void columns(SessionLocal session, ArrayList rows, String catalog, String mainSchemaName, + String collation, Table table, String tableName, Column c, int ordinalPosition) { + TypeInfo typeInfo = c.getType(); + DataTypeInformation dt = DataTypeInformation.valueOf(typeInfo); + String characterSetCatalog, characterSetSchema, characterSetName, collationName; + if (dt.hasCharsetAndCollation) { + characterSetCatalog = catalog; + characterSetSchema = mainSchemaName; + characterSetName = CHARACTER_SET_NAME; + collationName = collation; + } else { + characterSetCatalog = characterSetSchema = characterSetName = collationName = null; + } + Domain domain = c.getDomain(); + String domainCatalog = null, domainSchema = null, domainName = null; + if (domain != null) { + domainCatalog = catalog; + domainSchema = domain.getSchema().getName(); + domainName = domain.getName(); + } + String columnDefault, isGenerated, generationExpression; + String isIdentity, identityGeneration, identityCycle; + Value identityStart, identityIncrement, identityMaximum, identityMinimum, identityBase, identityCache; + Sequence sequence = c.getSequence(); + if (sequence != null) { + columnDefault = null; + isGenerated = "NEVER"; + generationExpression = null; + isIdentity = "YES"; + identityGeneration = c.isGeneratedAlways() ? "ALWAYS" : "BY DEFAULT"; + identityStart = ValueBigint.get(sequence.getStartValue()); + identityIncrement = ValueBigint.get(sequence.getIncrement()); + identityMaximum = ValueBigint.get(sequence.getMaxValue()); + identityMinimum = ValueBigint.get(sequence.getMinValue()); + Sequence.Cycle cycle = sequence.getCycle(); + identityCycle = cycle.isCycle() ? "YES" : "NO"; + identityBase = cycle != Sequence.Cycle.EXHAUSTED ? ValueBigint.get(sequence.getBaseValue()) : null; + identityCache = ValueBigint.get(sequence.getCacheSize()); + } else { + if (c.isGenerated()) { + columnDefault = null; + isGenerated = "ALWAYS"; + generationExpression = c.getDefaultSQL(); + } else { + columnDefault = c.getDefaultSQL(); + isGenerated = "NEVER"; + generationExpression = null; + } + isIdentity = "NO"; + identityGeneration = identityCycle = null; + identityStart = identityIncrement = identityMaximum = identityMinimum = identityBase = identityCache + = null; + } + add(session, rows, + // TABLE_CATALOG + catalog, + // TABLE_SCHEMA + table.getSchema().getName(), + // TABLE_NAME + tableName, + // COLUMN_NAME + c.getName(), + // ORDINAL_POSITION + ValueInteger.get(ordinalPosition), + // COLUMN_DEFAULT + columnDefault, + // IS_NULLABLE + c.isNullable() ? "YES" : "NO", + // DATA_TYPE + identifier(dt.dataType), + // CHARACTER_MAXIMUM_LENGTH + dt.characterPrecision, + // CHARACTER_OCTET_LENGTH + dt.characterPrecision, + // NUMERIC_PRECISION + dt.numericPrecision, + // NUMERIC_PRECISION_RADIX + dt.numericPrecisionRadix, + // NUMERIC_SCALE + dt.numericScale, + // DATETIME_PRECISION + dt.datetimePrecision, + // INTERVAL_TYPE + dt.intervalType, + // INTERVAL_PRECISION + dt.intervalPrecision, + // CHARACTER_SET_CATALOG + characterSetCatalog, + // CHARACTER_SET_SCHEMA + characterSetSchema, + // CHARACTER_SET_NAME + characterSetName, + // COLLATION_CATALOG + characterSetCatalog, + // COLLATION_SCHEMA + characterSetSchema, + // COLLATION_NAME + collationName, + // DOMAIN_CATALOG + domainCatalog, + // DOMAIN_SCHEMA + domainSchema, + // DOMAIN_NAME + domainName, + // MAXIMUM_CARDINALITY + dt.maximumCardinality, + // DTD_IDENTIFIER + Integer.toString(ordinalPosition), + // IS_IDENTITY + isIdentity, + // IDENTITY_GENERATION + identityGeneration, + // IDENTITY_START + identityStart, + // IDENTITY_INCREMENT + identityIncrement, + // IDENTITY_MAXIMUM + identityMaximum, + // IDENTITY_MINIMUM + identityMinimum, + // IDENTITY_CYCLE + identityCycle, + // IS_GENERATED + isGenerated, + // GENERATION_EXPRESSION + generationExpression, + // DECLARED_DATA_TYPE + dt.declaredDataType, + // DECLARED_NUMERIC_PRECISION + dt.declaredNumericPrecision, + // DECLARED_NUMERIC_SCALE + dt.declaredNumericScale, + // extensions + // GEOMETRY_TYPE + dt.geometryType, + // GEOMETRY_SRID + dt.geometrySrid, + // IDENTITY_BASE + identityBase, + // IDENTITY_CACHE + identityCache, + // COLUMN_ON_UPDATE + c.getOnUpdateSQL(), + // IS_VISIBLE + ValueBoolean.get(c.getVisible()), + // DEFAULT_ON_NULL + ValueBoolean.get(c.isDefaultOnNull()), + // SELECTIVITY + ValueInteger.get(c.getSelectivity()), + // REMARKS + c.getComment() + ); + } + + private void columnPrivileges(SessionLocal session, Value indexFrom, Value indexTo, ArrayList rows, + String catalog) { + for (Right r : database.getAllRights()) { + DbObject object = r.getGrantedObject(); + if (!(object instanceof Table)) { + continue; + } + Table table = (Table) object; + if (hideTable(table, session)) { + continue; + } + String tableName = table.getName(); + if (!checkIndex(session, tableName, indexFrom, indexTo)) { + continue; + } + DbObject grantee = r.getGrantee(); + int mask = r.getRightMask(); + for (Column column : table.getColumns()) { + addPrivileges(session, rows, grantee, catalog, table, column.getName(), mask); + } + } + } + + private void constraintColumnUsage(SessionLocal session, Value indexFrom, Value indexTo, ArrayList rows, + String catalog) { + for (Schema schema : database.getAllSchemas()) { + for (Constraint constraint : schema.getAllConstraints()) { + constraintColumnUsage(session, indexFrom, indexTo, rows, catalog, constraint); + } + } + } + + private void constraintColumnUsage(SessionLocal session, Value indexFrom, Value indexTo, ArrayList rows, + String catalog, Constraint constraint) { + switch (constraint.getConstraintType()) { + case CHECK: + case DOMAIN: { + HashSet columns = new HashSet<>(); + constraint.getExpression().isEverything(ExpressionVisitor.getColumnsVisitor(columns, null)); + for (Column column : columns) { + Table table = column.getTable(); + if (checkIndex(session, table.getName(), indexFrom, indexTo) && !hideTable(table, session)) { + addConstraintColumnUsage(session, rows, catalog, constraint, column); + } + } + break; + } + case REFERENTIAL: { + Table table = constraint.getRefTable(); + if (checkIndex(session, table.getName(), indexFrom, indexTo) && !hideTable(table, session)) { + for (Column column : constraint.getReferencedColumns(table)) { + addConstraintColumnUsage(session, rows, catalog, constraint, column); + } + } + } + //$FALL-THROUGH$ + case PRIMARY_KEY: + case UNIQUE: { + Table table = constraint.getTable(); + if (checkIndex(session, table.getName(), indexFrom, indexTo) && !hideTable(table, session)) { + for (Column column : constraint.getReferencedColumns(table)) { + addConstraintColumnUsage(session, rows, catalog, constraint, column); + } + } + } + } + } + + private void domains(SessionLocal session, Value indexFrom, Value indexTo, ArrayList rows, String catalog) { + String mainSchemaName = database.getMainSchema().getName(); + String collation = database.getCompareMode().getName(); + for (Schema schema : database.getAllSchemas()) { + for (Domain domain : schema.getAllDomains()) { + String domainName = domain.getName(); + if (!checkIndex(session, domainName, indexFrom, indexTo)) { + continue; + } + domains(session, rows, catalog, mainSchemaName, collation, domain, domainName); + } + } + } + + private void domains(SessionLocal session, ArrayList rows, String catalog, String mainSchemaName, + String collation, Domain domain, String domainName) { + Domain parentDomain = domain.getDomain(); + TypeInfo typeInfo = domain.getDataType(); + DataTypeInformation dt = DataTypeInformation.valueOf(typeInfo); + String characterSetCatalog, characterSetSchema, characterSetName, collationName; + if (dt.hasCharsetAndCollation) { + characterSetCatalog = catalog; + characterSetSchema = mainSchemaName; + characterSetName = CHARACTER_SET_NAME; + collationName = collation; + } else { + characterSetCatalog = characterSetSchema = characterSetName = collationName = null; + } + add(session, rows, + // DOMAIN_CATALOG + catalog, + // DOMAIN_SCHEMA + domain.getSchema().getName(), + // DOMAIN_NAME + domainName, + // DATA_TYPE + dt.dataType, + // CHARACTER_MAXIMUM_LENGTH + dt.characterPrecision, + // CHARACTER_OCTET_LENGTH + dt.characterPrecision, + // CHARACTER_SET_CATALOG + characterSetCatalog, + // CHARACTER_SET_SCHEMA + characterSetSchema, + // CHARACTER_SET_NAME + characterSetName, + // COLLATION_CATALOG + characterSetCatalog, + // COLLATION_SCHEMA + characterSetSchema, + // COLLATION_NAME + collationName, + // NUMERIC_PRECISION + dt.numericPrecision, + // NUMERIC_PRECISION_RADIX + dt.numericPrecisionRadix, + // NUMERIC_SCALE + dt.numericScale, + // DATETIME_PRECISION + dt.datetimePrecision, + // INTERVAL_TYPE + dt.intervalType, + // INTERVAL_PRECISION + dt.intervalPrecision, + // DOMAIN_DEFAULT + domain.getDefaultSQL(), + // MAXIMUM_CARDINALITY + dt.maximumCardinality, + // DTD_IDENTIFIER + "TYPE", + // DECLARED_DATA_TYPE + dt.declaredDataType, + // DECLARED_NUMERIC_PRECISION INT + dt.declaredNumericPrecision, + // DECLARED_NUMERIC_SCALE INT + dt.declaredNumericScale, + // extensions + // GEOMETRY_TYPE + dt.geometryType, + // GEOMETRY_SRID INT + dt.geometrySrid, + // DOMAIN_ON_UPDATE + domain.getOnUpdateSQL(), + // PARENT_DOMAIN_CATALOG + parentDomain != null ? catalog : null, + // PARENT_DOMAIN_SCHEMA + parentDomain != null ? parentDomain.getSchema().getName() : null, + // PARENT_DOMAIN_NAME + parentDomain != null ? parentDomain.getName() : null, + // REMARKS + domain.getComment() + ); + } + + private void domainConstraints(SessionLocal session, Value indexFrom, Value indexTo, ArrayList rows, + String catalog) { + for (Schema schema : database.getAllSchemas()) { + for (Constraint constraint : schema.getAllConstraints()) { + if (constraint.getConstraintType() != Constraint.Type.DOMAIN) { + continue; + } + ConstraintDomain domainConstraint = (ConstraintDomain) constraint; + Domain domain = domainConstraint.getDomain(); + String domainName = domain.getName(); + if (!checkIndex(session, domainName, indexFrom, indexTo)) { + continue; + } + domainConstraints(session, rows, catalog, domainConstraint, domain, domainName); + } + } + } + + private void domainConstraints(SessionLocal session, ArrayList rows, String catalog, + ConstraintDomain constraint, Domain domain, String domainName) { + add(session, rows, + // CONSTRAINT_CATALOG + catalog, + // CONSTRAINT_SCHEMA + constraint.getSchema().getName(), + // CONSTRAINT_NAME + constraint.getName(), + // DOMAIN_CATALOG + catalog, + // DOMAIN_SCHEMA + domain.getSchema().getName(), + // DOMAIN_NAME + domainName, + // IS_DEFERRABLE + "NO", + // INITIALLY_DEFERRED + "NO", + // extensions + // REMARKS + constraint.getComment() + ); + } + + private void elementTypesFields(SessionLocal session, ArrayList rows, String catalog, int type) { + String mainSchemaName = database.getMainSchema().getName(); + String collation = database.getCompareMode().getName(); + for (Schema schema : database.getAllSchemas()) { + String schemaName = schema.getName(); + for (Table table : schema.getAllTablesAndViews(session)) { + elementTypesFieldsForTable(session, rows, catalog, type, mainSchemaName, collation, schemaName, + table); + } + for (Domain domain : schema.getAllDomains()) { + elementTypesFieldsRow(session, rows, catalog, type, mainSchemaName, collation, schemaName, + domain.getName(), "DOMAIN", "TYPE", domain.getDataType()); + } + for (UserDefinedFunction userDefinedFunction : schema.getAllFunctionsAndAggregates()) { + if (userDefinedFunction instanceof FunctionAlias) { + String name = userDefinedFunction.getName(); + JavaMethod[] methods; + try { + methods = ((FunctionAlias) userDefinedFunction).getJavaMethods(); + } catch (DbException e) { + continue; + } + for (int i = 0; i < methods.length; i++) { + FunctionAlias.JavaMethod method = methods[i]; + TypeInfo typeInfo = method.getDataType(); + String specificName = name + '_' + (i + 1); + if (typeInfo != null && typeInfo.getValueType() != Value.NULL) { + elementTypesFieldsRow(session, rows, catalog, type, mainSchemaName, collation, schemaName, + specificName, "ROUTINE", "RESULT", typeInfo); + } + Class[] columnList = method.getColumnClasses(); + for (int o = 1, p = method.hasConnectionParam() ? 1 + : 0, n = columnList.length; p < n; o++, p++) { + elementTypesFieldsRow(session, rows, catalog, type, mainSchemaName, collation, schemaName, + specificName, "ROUTINE", Integer.toString(o), + ValueToObjectConverter2.classToType(columnList[p])); + } + } + } + } + for (Constant constant : schema.getAllConstants()) { + elementTypesFieldsRow(session, rows, catalog, type, mainSchemaName, collation, schemaName, + constant.getName(), "CONSTANT", "TYPE", constant.getValue().getType()); + } + } + for (Table table : session.getLocalTempTables()) { + elementTypesFieldsForTable(session, rows, catalog, type, mainSchemaName, collation, + table.getSchema().getName(), + table); + } + } + + private void elementTypesFieldsForTable(SessionLocal session, ArrayList rows, String catalog, int type, + String mainSchemaName, String collation, String schemaName, Table table) { + if (hideTable(table, session)) { + return; + } + String tableName = table.getName(); + Column[] cols = table.getColumns(); + for (int i = 0; i < cols.length; i++) { + elementTypesFieldsRow(session, rows, catalog, type, mainSchemaName, collation, schemaName, + tableName, "TABLE", Integer.toString(i + 1), cols[i].getType()); + } + } + + private void elementTypesFieldsRow(SessionLocal session, ArrayList rows, String catalog, int type, + String mainSchemaName, String collation, String objectSchema, String objectName, String objectType, + String identifier, TypeInfo typeInfo) { + switch (typeInfo.getValueType()) { + case Value.ENUM: + if (type == ENUM_VALUES) { + enumValues(session, rows, catalog, objectSchema, objectName, objectType, identifier, typeInfo); + } + break; + case Value.ARRAY: { + typeInfo = (TypeInfo) typeInfo.getExtTypeInfo(); + String dtdIdentifier = identifier + '_'; + if (type == ELEMENT_TYPES) { + elementTypes(session, rows, catalog, mainSchemaName, collation, objectSchema, objectName, + objectType, identifier, dtdIdentifier, typeInfo); + } + elementTypesFieldsRow(session, rows, catalog, type, mainSchemaName, collation, objectSchema, + objectName, objectType, dtdIdentifier, typeInfo); + break; + } + case Value.ROW: { + ExtTypeInfoRow ext = (ExtTypeInfoRow) typeInfo.getExtTypeInfo(); + int ordinalPosition = 0; + for (Map.Entry entry : ext.getFields()) { + typeInfo = entry.getValue(); + String fieldName = entry.getKey(); + String dtdIdentifier = identifier + '_' + ++ordinalPosition; + if (type == FIELDS) { + fields(session, rows, catalog, mainSchemaName, collation, objectSchema, objectName, + objectType, identifier, fieldName, ordinalPosition, dtdIdentifier, typeInfo); + } + elementTypesFieldsRow(session, rows, catalog, type, mainSchemaName, collation, objectSchema, + objectName, objectType, dtdIdentifier, typeInfo); + } + } + } + } + + private void elementTypes(SessionLocal session, ArrayList rows, String catalog, String mainSchemaName, + String collation, String objectSchema, String objectName, String objectType, String collectionIdentifier, + String dtdIdentifier, TypeInfo typeInfo) { + DataTypeInformation dt = DataTypeInformation.valueOf(typeInfo); + String characterSetCatalog, characterSetSchema, characterSetName, collationName; + if (dt.hasCharsetAndCollation) { + characterSetCatalog = catalog; + characterSetSchema = mainSchemaName; + characterSetName = CHARACTER_SET_NAME; + collationName = collation; + } else { + characterSetCatalog = characterSetSchema = characterSetName = collationName = null; + } + add(session, rows, + // OBJECT_CATALOG + catalog, + // OBJECT_SCHEMA + objectSchema, + // OBJECT_NAME + objectName, + // OBJECT_TYPE + objectType, + // COLLECTION_TYPE_IDENTIFIER + collectionIdentifier, + // DATA_TYPE + dt.dataType, + // CHARACTER_MAXIMUM_LENGTH + dt.characterPrecision, + // CHARACTER_OCTET_LENGTH + dt.characterPrecision, + // CHARACTER_SET_CATALOG + characterSetCatalog, + // CHARACTER_SET_SCHEMA + characterSetSchema, + // CHARACTER_SET_NAME + characterSetName, + // COLLATION_CATALOG + characterSetCatalog, + // COLLATION_SCHEMA + characterSetSchema, + // COLLATION_NAME + collationName, + // NUMERIC_PRECISION + dt.numericPrecision, + // NUMERIC_PRECISION_RADIX + dt.numericPrecisionRadix, + // NUMERIC_SCALE + dt.numericScale, + // DATETIME_PRECISION + dt.datetimePrecision, + // INTERVAL_TYPE + dt.intervalType, + // INTERVAL_PRECISION + dt.intervalPrecision, + // MAXIMUM_CARDINALITY + dt.maximumCardinality, + // DTD_IDENTIFIER + dtdIdentifier, + // DECLARED_DATA_TYPE + dt.declaredDataType, + // DECLARED_NUMERIC_PRECISION INT + dt.declaredNumericPrecision, + // DECLARED_NUMERIC_SCALE INT + dt.declaredNumericScale, + // extensions + // GEOMETRY_TYPE + dt.geometryType, + // GEOMETRY_SRID INT + dt.geometrySrid + ); + } + + private void fields(SessionLocal session, ArrayList rows, String catalog, String mainSchemaName, + String collation, String objectSchema, String objectName, String objectType, String rowIdentifier, + String fieldName, int ordinalPosition, String dtdIdentifier, TypeInfo typeInfo) { + DataTypeInformation dt = DataTypeInformation.valueOf(typeInfo); + String characterSetCatalog, characterSetSchema, characterSetName, collationName; + if (dt.hasCharsetAndCollation) { + characterSetCatalog = catalog; + characterSetSchema = mainSchemaName; + characterSetName = CHARACTER_SET_NAME; + collationName = collation; + } else { + characterSetCatalog = characterSetSchema = characterSetName = collationName = null; + } + add(session, rows, + // OBJECT_CATALOG + catalog, + // OBJECT_SCHEMA + objectSchema, + // OBJECT_NAME + objectName, + // OBJECT_TYPE + objectType, + // ROW_IDENTIFIER + rowIdentifier, + // FIELD_NAME + fieldName, + // ORDINAL_POSITION + ValueInteger.get(ordinalPosition), + // DATA_TYPE + dt.dataType, + // CHARACTER_MAXIMUM_LENGTH + dt.characterPrecision, + // CHARACTER_OCTET_LENGTH + dt.characterPrecision, + // CHARACTER_SET_CATALOG + characterSetCatalog, + // CHARACTER_SET_SCHEMA + characterSetSchema, + // CHARACTER_SET_NAME + characterSetName, + // COLLATION_CATALOG + characterSetCatalog, + // COLLATION_SCHEMA + characterSetSchema, + // COLLATION_NAME + collationName, + // NUMERIC_PRECISION + dt.numericPrecision, + // NUMERIC_PRECISION_RADIX + dt.numericPrecisionRadix, + // NUMERIC_SCALE + dt.numericScale, + // DATETIME_PRECISION + dt.datetimePrecision, + // INTERVAL_TYPE + dt.intervalType, + // INTERVAL_PRECISION + dt.intervalPrecision, + // MAXIMUM_CARDINALITY + dt.maximumCardinality, + // DTD_IDENTIFIER + dtdIdentifier, + // DECLARED_DATA_TYPE + dt.declaredDataType, + // DECLARED_NUMERIC_PRECISION INT + dt.declaredNumericPrecision, + // DECLARED_NUMERIC_SCALE INT + dt.declaredNumericScale, + // extensions + // GEOMETRY_TYPE + dt.geometryType, + // GEOMETRY_SRID INT + dt.geometrySrid + ); + } + + private void keyColumnUsage(SessionLocal session, Value indexFrom, Value indexTo, ArrayList rows, + String catalog) { + for (Schema schema : database.getAllSchemas()) { + for (Constraint constraint : schema.getAllConstraints()) { + Constraint.Type constraintType = constraint.getConstraintType(); + IndexColumn[] indexColumns = null; + if (constraintType == Constraint.Type.UNIQUE || constraintType == Constraint.Type.PRIMARY_KEY) { + indexColumns = ((ConstraintUnique) constraint).getColumns(); + } else if (constraintType == Constraint.Type.REFERENTIAL) { + indexColumns = ((ConstraintReferential) constraint).getColumns(); + } + if (indexColumns == null) { + continue; + } + Table table = constraint.getTable(); + if (hideTable(table, session)) { + continue; + } + String tableName = table.getName(); + if (!checkIndex(session, tableName, indexFrom, indexTo)) { + continue; + } + keyColumnUsage(session, rows, catalog, constraint, constraintType, indexColumns, table, tableName); + } + } + } + + private void keyColumnUsage(SessionLocal session, ArrayList rows, String catalog, Constraint constraint, + Constraint.Type constraintType, IndexColumn[] indexColumns, Table table, String tableName) { + ConstraintUnique referenced; + if (constraintType == Constraint.Type.REFERENTIAL) { + referenced = ((ConstraintReferential) constraint).getReferencedConstraint(); + } else { + referenced = null; + } + for (int i = 0; i < indexColumns.length; i++) { + IndexColumn indexColumn = indexColumns[i]; + ValueInteger ordinalPosition = ValueInteger.get(i + 1); + ValueInteger positionInUniqueConstraint = null; + if (referenced != null) { + Column c = ((ConstraintReferential) constraint).getRefColumns()[i].column; + IndexColumn[] refColumns = referenced.getColumns(); + for (int j = 0; j < refColumns.length; j++) { + if (refColumns[j].column.equals(c)) { + positionInUniqueConstraint = ValueInteger.get(j + 1); + break; + } + } + } + add(session, rows, + // CONSTRAINT_CATALOG + catalog, + // CONSTRAINT_SCHEMA + constraint.getSchema().getName(), + // CONSTRAINT_NAME + constraint.getName(), + // TABLE_CATALOG + catalog, + // TABLE_SCHEMA + table.getSchema().getName(), + // TABLE_NAME + tableName, + // COLUMN_NAME + indexColumn.columnName, + // ORDINAL_POSITION + ordinalPosition, + // POSITION_IN_UNIQUE_CONSTRAINT + positionInUniqueConstraint + ); + } + } + + private void parameters(SessionLocal session, ArrayList rows, String catalog) { + String mainSchemaName = database.getMainSchema().getName(); + String collation = database.getCompareMode().getName(); + for (Schema schema : database.getAllSchemas()) { + for (UserDefinedFunction userDefinedFunction : schema.getAllFunctionsAndAggregates()) { + if (userDefinedFunction instanceof FunctionAlias) { + JavaMethod[] methods; + try { + methods = ((FunctionAlias) userDefinedFunction).getJavaMethods(); + } catch (DbException e) { + continue; + } + for (int i = 0; i < methods.length; i++) { + FunctionAlias.JavaMethod method = methods[i]; + Class[] columnList = method.getColumnClasses(); + for (int o = 1, p = method.hasConnectionParam() ? 1 + : 0, n = columnList.length; p < n; o++, p++) { + parameters(session, rows, catalog, mainSchemaName, collation, schema.getName(), + userDefinedFunction.getName() + '_' + (i + 1), + ValueToObjectConverter2.classToType(columnList[p]), o); + } + } + } + } + } + } + + private void parameters(SessionLocal session, ArrayList rows, String catalog, String mainSchemaName, + String collation, String schema, String specificName, TypeInfo typeInfo, int pos) { + DataTypeInformation dt = DataTypeInformation.valueOf(typeInfo); + String characterSetCatalog, characterSetSchema, characterSetName, collationName; + if (dt.hasCharsetAndCollation) { + characterSetCatalog = catalog; + characterSetSchema = mainSchemaName; + characterSetName = CHARACTER_SET_NAME; + collationName = collation; + } else { + characterSetCatalog = characterSetSchema = characterSetName = collationName = null; + } + add(session, rows, + // SPECIFIC_CATALOG + catalog, + // SPECIFIC_SCHEMA + schema, + // SPECIFIC_NAME + specificName, + // ORDINAL_POSITION + ValueInteger.get(pos), + // PARAMETER_MODE + "IN", + // IS_RESULT + "NO", + // AS_LOCATOR + DataType.isLargeObject(typeInfo.getValueType()) ? "YES" : "NO", + // PARAMETER_NAME + "P" + pos, + // DATA_TYPE + identifier(dt.dataType), + // CHARACTER_MAXIMUM_LENGTH + dt.characterPrecision, + // CHARACTER_OCTET_LENGTH + dt.characterPrecision, + // CHARACTER_SET_CATALOG + characterSetCatalog, + // CHARACTER_SET_SCHEMA + characterSetSchema, + // CHARACTER_SET_NAME + characterSetName, + // COLLATION_CATALOG + characterSetCatalog, + // COLLATION_SCHEMA + characterSetSchema, + // COLLATION_NAME + collationName, + // NUMERIC_PRECISION + dt.numericPrecision, + // NUMERIC_PRECISION_RADIX + dt.numericPrecisionRadix, + // NUMERIC_SCALE + dt.numericScale, + // DATETIME_PRECISION + dt.datetimePrecision, + // INTERVAL_TYPE + dt.intervalType, + // INTERVAL_PRECISION + dt.intervalPrecision, + // MAXIMUM_CARDINALITY + dt.maximumCardinality, + // DTD_IDENTIFIER + Integer.toString(pos), + // DECLARED_DATA_TYPE + dt.declaredDataType, + // DECLARED_NUMERIC_PRECISION INT + dt.declaredNumericPrecision, + // DECLARED_NUMERIC_SCALE INT + dt.declaredNumericScale, + // PARAMETER_DEFAULT + null, + // extensions + // GEOMETRY_TYPE + dt.geometryType, + // GEOMETRY_SRID INT + dt.geometrySrid + ); + } + + private void referentialConstraints(SessionLocal session, Value indexFrom, Value indexTo, ArrayList rows, + String catalog) { + for (Schema schema : database.getAllSchemas()) { + for (Constraint constraint : schema.getAllConstraints()) { + if (constraint.getConstraintType() != Constraint.Type.REFERENTIAL) { + continue; + } + if (hideTable(constraint.getTable(), session)) { + continue; + } + String constraintName = constraint.getName(); + if (!checkIndex(session, constraintName, indexFrom, indexTo)) { + continue; + } + referentialConstraints(session, rows, catalog, (ConstraintReferential) constraint, constraintName); + } + } + } + + private void referentialConstraints(SessionLocal session, ArrayList rows, String catalog, + ConstraintReferential constraint, String constraintName) { + ConstraintUnique unique = constraint.getReferencedConstraint(); + add(session, rows, + // CONSTRAINT_CATALOG + catalog, + // CONSTRAINT_SCHEMA + constraint.getSchema().getName(), + // CONSTRAINT_NAME + constraintName, + // UNIQUE_CONSTRAINT_CATALOG + catalog, + // UNIQUE_CONSTRAINT_SCHEMA + unique.getSchema().getName(), + // UNIQUE_CONSTRAINT_NAME + unique.getName(), + // MATCH_OPTION + "NONE", + // UPDATE_RULE + constraint.getUpdateAction().getSqlName(), + // DELETE_RULE + constraint.getDeleteAction().getSqlName() + ); + } + + private void routines(SessionLocal session, ArrayList rows, String catalog) { + boolean admin = session.getUser().isAdmin(); + String mainSchemaName = database.getMainSchema().getName(); + String collation = database.getCompareMode().getName(); + for (Schema schema : database.getAllSchemas()) { + String schemaName = schema.getName(); + for (UserDefinedFunction userDefinedFunction : schema.getAllFunctionsAndAggregates()) { + String name = userDefinedFunction.getName(); + if (userDefinedFunction instanceof FunctionAlias) { + FunctionAlias alias = (FunctionAlias) userDefinedFunction; + JavaMethod[] methods; + try { + methods = alias.getJavaMethods(); + } catch (DbException e) { + continue; + } + for (int i = 0; i < methods.length; i++) { + FunctionAlias.JavaMethod method = methods[i]; + TypeInfo typeInfo = method.getDataType(); + String routineType; + if (typeInfo != null && typeInfo.getValueType() == Value.NULL) { + routineType = "PROCEDURE"; + typeInfo = null; + } else { + routineType = "FUNCTION"; + } + String javaClassName = alias.getJavaClassName(); + routines(session, rows, catalog, mainSchemaName, collation, schemaName, name, + name + '_' + (i + 1), routineType, admin ? alias.getSource() : null, + javaClassName != null ? javaClassName + '.' + alias.getJavaMethodName() : null, + typeInfo, alias.isDeterministic(), alias.getComment()); + } + } else { + routines(session, rows, catalog, mainSchemaName, collation, schemaName, name, name, "AGGREGATE", + null, userDefinedFunction.getJavaClassName(), TypeInfo.TYPE_NULL, false, + userDefinedFunction.getComment()); + } + } + } + } + + private void routines(SessionLocal session, ArrayList rows, String catalog, String mainSchemaName, // + String collation, String schema, String name, String specificName, String routineType, String definition, + String externalName, TypeInfo typeInfo, boolean deterministic, String remarks) { + DataTypeInformation dt = typeInfo != null ? DataTypeInformation.valueOf(typeInfo) : DataTypeInformation.NULL; + String characterSetCatalog, characterSetSchema, characterSetName, collationName; + if (dt.hasCharsetAndCollation) { + characterSetCatalog = catalog; + characterSetSchema = mainSchemaName; + characterSetName = CHARACTER_SET_NAME; + collationName = collation; + } else { + characterSetCatalog = characterSetSchema = characterSetName = collationName = null; + } + add(session, rows, + // SPECIFIC_CATALOG + catalog, + // SPECIFIC_SCHEMA + schema, + // SPECIFIC_NAME + specificName, + // ROUTINE_CATALOG + catalog, + // ROUTINE_SCHEMA + schema, + // ROUTINE_NAME + name, + // ROUTINE_TYPE + routineType, + // DATA_TYPE + identifier(dt.dataType), + // CHARACTER_MAXIMUM_LENGTH + dt.characterPrecision, + // CHARACTER_OCTET_LENGTH + dt.characterPrecision, + // CHARACTER_SET_CATALOG + characterSetCatalog, + // CHARACTER_SET_SCHEMA + characterSetSchema, + // CHARACTER_SET_NAME + characterSetName, + // COLLATION_CATALOG + characterSetCatalog, + // COLLATION_SCHEMA + characterSetSchema, + // COLLATION_NAME + collationName, + // NUMERIC_PRECISION + dt.numericPrecision, + // NUMERIC_PRECISION_RADIX + dt.numericPrecisionRadix, + // NUMERIC_SCALE + dt.numericScale, + // DATETIME_PRECISION + dt.datetimePrecision, + // INTERVAL_TYPE + dt.intervalType, + // INTERVAL_PRECISION + dt.intervalPrecision, + // MAXIMUM_CARDINALITY + dt.maximumCardinality, + // DTD_IDENTIFIER + "RESULT", + // ROUTINE_BODY + "EXTERNAL", + // ROUTINE_DEFINITION + definition, + // EXTERNAL_NAME + externalName, + // EXTERNAL_LANGUAGE + "JAVA", + // PARAMETER_STYLE + "GENERAL", + // IS_DETERMINISTIC + deterministic ? "YES" : "NO", + // DECLARED_DATA_TYPE + dt.declaredDataType, + // DECLARED_NUMERIC_PRECISION INT + dt.declaredNumericPrecision, + // DECLARED_NUMERIC_SCALE INT + dt.declaredNumericScale, + // extensions + // GEOMETRY_TYPE + dt.geometryType, + // GEOMETRY_SRID INT + dt.geometrySrid, + // REMARKS + remarks + ); + } + + private void schemata(SessionLocal session, ArrayList rows, String catalog) { + String mainSchemaName = database.getMainSchema().getName(); + String collation = database.getCompareMode().getName(); + for (Schema schema : database.getAllSchemas()) { + add(session, rows, + // CATALOG_NAME + catalog, + // SCHEMA_NAME + schema.getName(), + // SCHEMA_OWNER + identifier(schema.getOwner().getName()), + // DEFAULT_CHARACTER_SET_CATALOG + catalog, + // DEFAULT_CHARACTER_SET_SCHEMA + mainSchemaName, + // DEFAULT_CHARACTER_SET_NAME + CHARACTER_SET_NAME, + // SQL_PATH + null, + // extensions + // DEFAULT_COLLATION_NAME + collation, + // REMARKS + schema.getComment() + ); + } + } + + private void sequences(SessionLocal session, Value indexFrom, Value indexTo, ArrayList rows, String catalog) { + for (Schema schema : database.getAllSchemas()) { + for (Sequence sequence : schema.getAllSequences()) { + if (sequence.getBelongsToTable()) { + continue; + } + String sequenceName = sequence.getName(); + if (!checkIndex(session, sequenceName, indexFrom, indexTo)) { + continue; + } + sequences(session, rows, catalog, sequence, sequenceName); + } + } + } + + private void sequences(SessionLocal session, ArrayList rows, String catalog, Sequence sequence, + String sequenceName) { + DataTypeInformation dt = DataTypeInformation.valueOf(sequence.getDataType()); + Sequence.Cycle cycle = sequence.getCycle(); + add(session, rows, + // SEQUENCE_CATALOG + catalog, + // SEQUENCE_SCHEMA + sequence.getSchema().getName(), + // SEQUENCE_NAME + sequenceName, + // DATA_TYPE + dt.dataType, + // NUMERIC_PRECISION + ValueInteger.get(sequence.getEffectivePrecision()), + // NUMERIC_PRECISION_RADIX + dt.numericPrecisionRadix, + // NUMERIC_SCALE + dt.numericScale, + // START_VALUE + ValueBigint.get(sequence.getStartValue()), + // MINIMUM_VALUE + ValueBigint.get(sequence.getMinValue()), + // MAXIMUM_VALUE + ValueBigint.get(sequence.getMaxValue()), + // INCREMENT + ValueBigint.get(sequence.getIncrement()), + // CYCLE_OPTION + cycle.isCycle() ? "YES" : "NO", + // DECLARED_DATA_TYPE + dt.declaredDataType, + // DECLARED_NUMERIC_PRECISION + dt.declaredNumericPrecision, + // DECLARED_NUMERIC_SCALE + dt.declaredNumericScale, + // extensions + // BASE_VALUE + cycle != Sequence.Cycle.EXHAUSTED ? ValueBigint.get(sequence.getBaseValue()) : null, + // CACHE + ValueBigint.get(sequence.getCacheSize()), + // REMARKS + sequence.getComment() + ); + } + + private void tables(SessionLocal session, Value indexFrom, Value indexTo, ArrayList rows, String catalog) { + for (Schema schema : database.getAllSchemas()) { + for (Table table : schema.getAllTablesAndViews(session)) { + String tableName = table.getName(); + if (checkIndex(session, tableName, indexFrom, indexTo)) { + tables(session, rows, catalog, table, tableName); + } + } + } + for (Table table : session.getLocalTempTables()) { + String tableName = table.getName(); + if (checkIndex(session, tableName, indexFrom, indexTo)) { + tables(session, rows, catalog, table, tableName); + } + } + } + + private void tables(SessionLocal session, ArrayList rows, String catalog, Table table, + String tableName) { + if (hideTable(table, session)) { + return; + } + String commitAction, storageType; + if (table.isTemporary()) { + commitAction = table.getOnCommitTruncate() ? "DELETE" : table.getOnCommitDrop() ? "DROP" : "PRESERVE"; + storageType = table.isGlobalTemporary() ? "GLOBAL TEMPORARY" : "LOCAL TEMPORARY"; + } else { + commitAction = null; + switch (table.getTableType()) { + case TABLE_LINK: + storageType = "TABLE LINK"; + break; + case EXTERNAL_TABLE_ENGINE: + storageType = "EXTERNAL"; + break; + default: + storageType = table.isPersistIndexes() ? "CACHED" : "MEMORY"; + break; + } + } + long lastModification = table.getMaxDataModificationId(); + add(session, rows, + // TABLE_CATALOG + catalog, + // TABLE_SCHEMA + table.getSchema().getName(), + // TABLE_NAME + tableName, + // TABLE_TYPE + table.getSQLTableType(), + // IS_INSERTABLE_INTO" + table.isInsertable() ? "YES" : "NO", + // COMMIT_ACTION + commitAction, + // extensions + // STORAGE_TYPE + storageType, + // REMARKS + table.getComment(), + // LAST_MODIFICATION + lastModification != Long.MAX_VALUE ? ValueBigint.get(lastModification) : null, + // TABLE_CLASS + table.getClass().getName(), + // ROW_COUNT_ESTIMATE + ValueBigint.get(table.getRowCountApproximation(session)) + ); + } + + private void tableConstraints(SessionLocal session, Value indexFrom, Value indexTo, ArrayList rows, + String catalog) { + for (Schema schema : database.getAllSchemas()) { + for (Constraint constraint : schema.getAllConstraints()) { + Constraint.Type constraintType = constraint.getConstraintType(); + if (constraintType == Constraint.Type.DOMAIN) { + continue; + } + Table table = constraint.getTable(); + if (hideTable(table, session)) { + continue; + } + String tableName = table.getName(); + if (!checkIndex(session, tableName, indexFrom, indexTo)) { + continue; + } + tableConstraints(session, rows, catalog, constraint, constraintType, table, tableName); + } + } + } + + private void tableConstraints(SessionLocal session, ArrayList rows, String catalog, Constraint constraint, + Constraint.Type constraintType, Table table, String tableName) { + Index index = constraint.getIndex(); + boolean enforced; + if (constraintType != Constraint.Type.REFERENTIAL) { + enforced = true; + } else { + enforced = database.getReferentialIntegrity() && table.getCheckForeignKeyConstraints() + && ((ConstraintReferential) constraint).getRefTable().getCheckForeignKeyConstraints(); + } + add(session, rows, + // CONSTRAINT_CATALOG + catalog, + // CONSTRAINT_SCHEMA + constraint.getSchema().getName(), + // CONSTRAINT_NAME + constraint.getName(), + // CONSTRAINT_TYPE + constraintType.getSqlName(), + // TABLE_CATALOG + catalog, + // TABLE_SCHEMA + table.getSchema().getName(), + // TABLE_NAME + tableName, + // IS_DEFERRABLE + "NO", + // INITIALLY_DEFERRED + "NO", + // ENFORCED + enforced ? "YES" : "NO", + // extensions + // INDEX_CATALOG + index != null ? catalog : null, + // INDEX_SCHEMA + index != null ? index.getSchema().getName() : null, + // INDEX_NAME + index != null ? index.getName() : null, + // REMARKS + constraint.getComment() + ); + } + + private void tablePrivileges(SessionLocal session, Value indexFrom, Value indexTo, ArrayList rows, // + String catalog) { + for (Right r : database.getAllRights()) { + DbObject object = r.getGrantedObject(); + if (!(object instanceof Table)) { + continue; + } + Table table = (Table) object; + if (hideTable(table, session)) { + continue; + } + String tableName = table.getName(); + if (!checkIndex(session, tableName, indexFrom, indexTo)) { + continue; + } + addPrivileges(session, rows, r.getGrantee(), catalog, table, null, r.getRightMask()); + } + } + + private void triggers(SessionLocal session, Value indexFrom, Value indexTo, ArrayList rows, String catalog) { + for (Schema schema : database.getAllSchemas()) { + for (TriggerObject trigger : schema.getAllTriggers()) { + Table table = trigger.getTable(); + String tableName = table.getName(); + if (!checkIndex(session, tableName, indexFrom, indexTo)) { + continue; + } + int typeMask = trigger.getTypeMask(); + if ((typeMask & Trigger.INSERT) != 0) { + triggers(session, rows, catalog, trigger, "INSERT", table, tableName); + } + if ((typeMask & Trigger.UPDATE) != 0) { + triggers(session, rows, catalog, trigger, "UPDATE", table, tableName); + } + if ((typeMask & Trigger.DELETE) != 0) { + triggers(session, rows, catalog, trigger, "DELETE", table, tableName); + } + if ((typeMask & Trigger.SELECT) != 0) { + triggers(session, rows, catalog, trigger, "SELECT", table, tableName); + } + } + } + } + + private void triggers(SessionLocal session, ArrayList rows, String catalog, TriggerObject trigger, + String eventManipulation, Table table, String tableName) { + add(session, rows, + // TRIGGER_CATALOG + catalog, + // TRIGGER_SCHEMA + trigger.getSchema().getName(), + // TRIGGER_NAME + trigger.getName(), + // EVENT_MANIPULATION + eventManipulation, + // EVENT_OBJECT_CATALOG + catalog, + // EVENT_OBJECT_SCHEMA + table.getSchema().getName(), + // EVENT_OBJECT_TABLE + tableName, + // ACTION_ORIENTATION + trigger.isRowBased() ? "ROW" : "STATEMENT", + // ACTION_TIMING + trigger.isInsteadOf() ? "INSTEAD OF" : trigger.isBefore() ? "BEFORE" : "AFTER", + // extensions + // IS_ROLLBACK + ValueBoolean.get(trigger.isOnRollback()), + // JAVA_CLASS + trigger.getTriggerClassName(), + // QUEUE_SIZE + ValueInteger.get(trigger.getQueueSize()), + // NO_WAIT + ValueBoolean.get(trigger.isNoWait()), + // REMARKS + trigger.getComment() + ); + } + + private void views(SessionLocal session, Value indexFrom, Value indexTo, ArrayList rows, String catalog) { + for (Schema schema : database.getAllSchemas()) { + for (Table table : schema.getAllTablesAndViews(session)) { + if (table.isView()) { + String tableName = table.getName(); + if (checkIndex(session, tableName, indexFrom, indexTo)) { + views(session, rows, catalog, table, tableName); + } + } + } + } + for (Table table : session.getLocalTempTables()) { + if (table.isView()) { + String tableName = table.getName(); + if (checkIndex(session, tableName, indexFrom, indexTo)) { + views(session, rows, catalog, table, tableName); + } + } + } + } + + private void views(SessionLocal session, ArrayList rows, String catalog, Table table, String tableName) { + String viewDefinition, status = "VALID"; + if (table instanceof TableView) { + TableView view = (TableView) table; + viewDefinition = view.getQuerySQL(); + if (view.isInvalid()) { + status = "INVALID"; + } + } else { + viewDefinition = null; + } + int mask = 0; + ArrayList triggers = table.getTriggers(); + if (triggers != null) { + for (TriggerObject trigger : triggers) { + if (trigger.isInsteadOf()) { + mask |= trigger.getTypeMask(); + } + } + } + add(session, rows, + // TABLE_CATALOG + catalog, + // TABLE_SCHEMA + table.getSchema().getName(), + // TABLE_NAME + tableName, + // VIEW_DEFINITION + viewDefinition, + // CHECK_OPTION + "NONE", + // IS_UPDATABLE + "NO", + // INSERTABLE_INTO + "NO", + // IS_TRIGGER_UPDATABLE + (mask & Trigger.UPDATE) != 0 ? "YES" : "NO", + // IS_TRIGGER_DELETABLE + (mask & Trigger.DELETE) != 0 ? "YES" : "NO", + // IS_TRIGGER_INSERTABLE_INTO + (mask & Trigger.INSERT) != 0 ? "YES" : "NO", + // extensions + // STATUS + status, + // REMARKS + table.getComment() + ); + } + + private void constants(SessionLocal session, Value indexFrom, Value indexTo, ArrayList rows, String catalog) { + String mainSchemaName = database.getMainSchema().getName(); + String collation = database.getCompareMode().getName(); + for (Schema schema : database.getAllSchemas()) { + for (Constant constant : schema.getAllConstants()) { + String constantName = constant.getName(); + if (!checkIndex(session, constantName, indexFrom, indexTo)) { + continue; + } + constants(session, rows, catalog, mainSchemaName, collation, constant, constantName); + } + } + } + + private void constants(SessionLocal session, ArrayList rows, String catalog, String mainSchemaName, + String collation, Constant constant, String constantName) { + ValueExpression expr = constant.getValue(); + TypeInfo typeInfo = expr.getType(); + DataTypeInformation dt = DataTypeInformation.valueOf(typeInfo); + String characterSetCatalog, characterSetSchema, characterSetName, collationName; + if (dt.hasCharsetAndCollation) { + characterSetCatalog = catalog; + characterSetSchema = mainSchemaName; + characterSetName = CHARACTER_SET_NAME; + collationName = collation; + } else { + characterSetCatalog = characterSetSchema = characterSetName = collationName = null; + } + add(session, rows, + // CONSTANT_CATALOG + catalog, + // CONSTANT_SCHEMA + constant.getSchema().getName(), + // CONSTANT_NAME + constantName, + // VALUE_DEFINITION + expr.getSQL(DEFAULT_SQL_FLAGS), + // DATA_TYPE + dt.dataType, + // CHARACTER_MAXIMUM_LENGTH + dt.characterPrecision, + // CHARACTER_OCTET_LENGTH + dt.characterPrecision, + // CHARACTER_SET_CATALOG + characterSetCatalog, + // CHARACTER_SET_SCHEMA + characterSetSchema, + // CHARACTER_SET_NAME + characterSetName, + // COLLATION_CATALOG + characterSetCatalog, + // COLLATION_SCHEMA + characterSetSchema, + // COLLATION_NAME + collationName, + // NUMERIC_PRECISION + dt.numericPrecision, + // NUMERIC_PRECISION_RADIX + dt.numericPrecisionRadix, + // NUMERIC_SCALE + dt.numericScale, + // DATETIME_PRECISION + dt.datetimePrecision, + // INTERVAL_TYPE + dt.intervalType, + // INTERVAL_PRECISION + dt.intervalPrecision, + // MAXIMUM_CARDINALITY + dt.maximumCardinality, + // DTD_IDENTIFIER + "TYPE", + // DECLARED_DATA_TYPE + dt.declaredDataType, + // DECLARED_NUMERIC_PRECISION INT + dt.declaredNumericPrecision, + // DECLARED_NUMERIC_SCALE INT + dt.declaredNumericScale, + // GEOMETRY_TYPE + dt.geometryType, + // GEOMETRY_SRID INT + dt.geometrySrid, + // REMARKS + constant.getComment() + ); + } + + private void enumValues(SessionLocal session, ArrayList rows, String catalog, String objectSchema, + String objectName, String objectType, String enumIdentifier, TypeInfo typeInfo) { + ExtTypeInfoEnum ext = (ExtTypeInfoEnum) typeInfo.getExtTypeInfo(); + if (ext == null) { + return; + } + for (int i = 0, ordinal = session.zeroBasedEnums() ? 0 : 1, l = ext.getCount(); i < l; i++, ordinal++) { + add(session, rows, + // OBJECT_CATALOG + catalog, + // OBJECT_SCHEMA + objectSchema, + // OBJECT_NAME + objectName, + // OBJECT_TYPE + objectType, + // ENUM_IDENTIFIER + enumIdentifier, + // VALUE_NAME + ext.getEnumerator(i), + // VALUE_ORDINAL + ValueInteger.get(ordinal) + ); + } + } + + private void indexes(SessionLocal session, Value indexFrom, Value indexTo, ArrayList rows, String catalog, + boolean columns) { + if (indexFrom != null && indexFrom.equals(indexTo)) { + String tableName = indexFrom.getString(); + if (tableName == null) { + return; + } + for (Schema schema : database.getAllSchemas()) { + Table table = schema.getTableOrViewByName(session, tableName); + if (table != null) { + indexes(session, rows, catalog, columns, table, table.getName()); + } + } + Table table = session.findLocalTempTable(tableName); + if (table != null) { + indexes(session, rows, catalog, columns, table, table.getName()); + } + } else { + for (Schema schema : database.getAllSchemas()) { + for (Table table : schema.getAllTablesAndViews(session)) { + String tableName = table.getName(); + if (checkIndex(session, tableName, indexFrom, indexTo)) { + indexes(session, rows, catalog, columns, table, tableName); + } + } + } + for (Table table : session.getLocalTempTables()) { + String tableName = table.getName(); + if (checkIndex(session, tableName, indexFrom, indexTo)) { + indexes(session, rows, catalog, columns, table, tableName); + } + } + } + } + + private void indexes(SessionLocal session, ArrayList rows, String catalog, boolean columns, Table table, + String tableName) { + if (hideTable(table, session)) { + return; + } + ArrayList indexes = table.getIndexes(); + if (indexes == null) { + return; + } + for (Index index : indexes) { + if (index.getCreateSQL() == null) { + continue; + } + if (columns) { + indexColumns(session, rows, catalog, table, tableName, index); + } else { + indexes(session, rows, catalog, table, tableName, index); + } + } + } + + private void indexes(SessionLocal session, ArrayList rows, String catalog, Table table, String tableName, + Index index) { + add(session, rows, + // INDEX_CATALOG + catalog, + // INDEX_SCHEMA + index.getSchema().getName(), + // INDEX_NAME + index.getName(), + // TABLE_CATALOG + catalog, + // TABLE_SCHEMA + table.getSchema().getName(), + // TABLE_NAME + tableName, + // INDEX_TYPE_NAME + index.getIndexType().getSQL(), + // IS_GENERATED + ValueBoolean.get(index.getIndexType().getBelongsToConstraint()), + // REMARKS + index.getComment(), + // INDEX_CLASS + index.getClass().getName() + ); + } + + private void indexColumns(SessionLocal session, ArrayList rows, String catalog, Table table, + String tableName, Index index) { + IndexColumn[] cols = index.getIndexColumns(); + int uniqueColumnCount = index.getUniqueColumnCount(); + for (int i = 0, l = cols.length; i < l;) { + IndexColumn idxCol = cols[i]; + int sortType = idxCol.sortType; + add(session, rows, + // INDEX_CATALOG + catalog, + // INDEX_SCHEMA + index.getSchema().getName(), + // INDEX_NAME + index.getName(), + // TABLE_CATALOG + catalog, + // TABLE_SCHEMA + table.getSchema().getName(), + // TABLE_NAME + tableName, + // COLUMN_NAME + idxCol.column.getName(), + // ORDINAL_POSITION + ValueInteger.get(++i), + // ORDERING_SPECIFICATION + (sortType & SortOrder.DESCENDING) == 0 ? "ASC" : "DESC", + // NULL_ORDERING + (sortType & SortOrder.NULLS_FIRST) != 0 ? "FIRST" + : (sortType & SortOrder.NULLS_LAST) != 0 ? "LAST" : null, + // IS_UNIQUE + ValueBoolean.get(i <= uniqueColumnCount) + ); + } + } + + private void inDoubt(SessionLocal session, ArrayList rows) { + if (session.getUser().isAdmin()) { + ArrayList prepared = database.getInDoubtTransactions(); + if (prepared != null) { + for (InDoubtTransaction prep : prepared) { + add(session, rows, + // TRANSACTION_NAME + prep.getTransactionName(), + // TRANSACTION_STATE + prep.getStateDescription() + ); + } + } + } + } + + private void locks(SessionLocal session, ArrayList rows) { + if (session.getUser().isAdmin()) { + for (SessionLocal s : database.getSessions(false)) { + locks(session, rows, s); + } + } else { + locks(session, rows, session); + } + } + + private void locks(SessionLocal session, ArrayList rows, SessionLocal sessionWithLocks) { + for (Table table : sessionWithLocks.getLocks()) { + add(session, rows, + // TABLE_SCHEMA + table.getSchema().getName(), + // TABLE_NAME + table.getName(), + // SESSION_ID + ValueInteger.get(sessionWithLocks.getId()), + // LOCK_TYPE + table.isLockedExclusivelyBy(sessionWithLocks) ? "WRITE" : "READ" + ); + } + } + + private void queryStatistics(SessionLocal session, ArrayList rows) { + QueryStatisticsData control = database.getQueryStatisticsData(); + if (control != null) { + for (QueryStatisticsData.QueryEntry entry : control.getQueries()) { + add(session, rows, + // SQL_STATEMENT + entry.sqlStatement, + // EXECUTION_COUNT + ValueInteger.get(entry.count), + // MIN_EXECUTION_TIME + ValueDouble.get(entry.executionTimeMinNanos / 1_000_000d), + // MAX_EXECUTION_TIME + ValueDouble.get(entry.executionTimeMaxNanos / 1_000_000d), + // CUMULATIVE_EXECUTION_TIME + ValueDouble.get(entry.executionTimeCumulativeNanos / 1_000_000d), + // AVERAGE_EXECUTION_TIME + ValueDouble.get(entry.executionTimeMeanNanos / 1_000_000d), + // STD_DEV_EXECUTION_TIME + ValueDouble.get(entry.getExecutionTimeStandardDeviation() / 1_000_000d), + // MIN_ROW_COUNT + ValueBigint.get(entry.rowCountMin), + // MAX_ROW_COUNT + ValueBigint.get(entry.rowCountMax), + // CUMULATIVE_ROW_COUNT + ValueBigint.get(entry.rowCountCumulative), + // AVERAGE_ROW_COUNT + ValueDouble.get(entry.rowCountMean), + // STD_DEV_ROW_COUNT + ValueDouble.get(entry.getRowCountStandardDeviation()) + ); + } + } + } + + private void rights(SessionLocal session, Value indexFrom, Value indexTo, ArrayList rows) { + if (!session.getUser().isAdmin()) { + return; + } + for (Right r : database.getAllRights()) { + Role role = r.getGrantedRole(); + DbObject grantee = r.getGrantee(); + String rightType = grantee.getType() == DbObject.USER ? "USER" : "ROLE"; + if (role == null) { + DbObject object = r.getGrantedObject(); + Schema schema = null; + Table table = null; + if (object != null) { + if (object instanceof Schema) { + schema = (Schema) object; + } else if (object instanceof Table) { + table = (Table) object; + schema = table.getSchema(); + } + } + String tableName = (table != null) ? table.getName() : ""; + String schemaName = (schema != null) ? schema.getName() : ""; + if (!checkIndex(session, tableName, indexFrom, indexTo)) { + continue; + } + add(session, rows, + // GRANTEE + identifier(grantee.getName()), + // GRANTEETYPE + rightType, + // GRANTEDROLE + null, + // RIGHTS + r.getRights(), + // TABLE_SCHEMA + schemaName, + // TABLE_NAME + tableName + ); + } else { + add(session, rows, + // GRANTEE + identifier(grantee.getName()), + // GRANTEETYPE + rightType, + // GRANTEDROLE + identifier(role.getName()), + // RIGHTS + null, + // TABLE_SCHEMA + null, + // TABLE_NAME + null + ); + } + } + } + + private void roles(SessionLocal session, ArrayList rows) { + boolean admin = session.getUser().isAdmin(); + for (RightOwner rightOwner : database.getAllUsersAndRoles()) { + if (rightOwner instanceof Role) { + Role r = (Role) rightOwner; + if (admin || session.getUser().isRoleGranted(r)) { + add(session, rows, + // ROLE_NAME + identifier(r.getName()), + // REMARKS + r.getComment() + ); + } + } + } + } + + private void sessions(SessionLocal session, ArrayList rows) { + if (session.getUser().isAdmin()) { + for (SessionLocal s : database.getSessions(false)) { + sessions(session, rows, s); + } + } else { + sessions(session, rows, session); + } + } + + private void sessions(SessionLocal session, ArrayList rows, SessionLocal s) { + NetworkConnectionInfo networkConnectionInfo = s.getNetworkConnectionInfo(); + Command command = s.getCurrentCommand(); + int blockingSessionId = s.getBlockingSessionId(); + add(session, rows, + // SESSION_ID + ValueInteger.get(s.getId()), + // USER_NAME + s.getUser().getName(), + // SERVER + networkConnectionInfo == null ? null : networkConnectionInfo.getServer(), + // CLIENT_ADDR + networkConnectionInfo == null ? null : networkConnectionInfo.getClient(), + // CLIENT_INFO + networkConnectionInfo == null ? null : networkConnectionInfo.getClientInfo(), + // SESSION_START + s.getSessionStart(), + // ISOLATION_LEVEL + session.getIsolationLevel().getSQL(), + // EXECUTING_STATEMENT + command == null ? null : command.toString(), + // EXECUTING_STATEMENT_START + command == null ? null : s.getCommandStartOrEnd(), + // CONTAINS_UNCOMMITTED + ValueBoolean.get(s.hasPendingTransaction()), + // SESSION_STATE + String.valueOf(s.getState()), + // BLOCKER_ID + blockingSessionId == 0 ? null : ValueInteger.get(blockingSessionId), + // SLEEP_SINCE + s.getState() == State.SLEEP ? s.getCommandStartOrEnd() : null + ); + } + + private void sessionState(SessionLocal session, ArrayList rows) { + for (String name : session.getVariableNames()) { + Value v = session.getVariable(name); + StringBuilder builder = new StringBuilder().append("SET @").append(name).append(' '); + v.getSQL(builder, DEFAULT_SQL_FLAGS); + add(session, rows, + // STATE_KEY + "@" + name, + // STATE_COMMAND + builder.toString() + ); + } + for (Table table : session.getLocalTempTables()) { + add(session, rows, + // STATE_KEY + "TABLE " + table.getName(), + // STATE_COMMAND + table.getCreateSQL() + ); + } + String[] path = session.getSchemaSearchPath(); + if (path != null && path.length > 0) { + StringBuilder builder = new StringBuilder("SET SCHEMA_SEARCH_PATH "); + for (int i = 0, l = path.length; i < l; i++) { + if (i > 0) { + builder.append(", "); + } + StringUtils.quoteIdentifier(builder, path[i]); + } + add(session, rows, + // STATE_KEY + "SCHEMA_SEARCH_PATH", + // STATE_COMMAND + builder.toString() + ); + } + String schema = session.getCurrentSchemaName(); + if (schema != null) { + add(session, rows, + // STATE_KEY + "SCHEMA", + // STATE_COMMAND + StringUtils.quoteIdentifier(new StringBuilder("SET SCHEMA "), schema).toString() + ); + } + TimeZoneProvider currentTimeZone = session.currentTimeZone(); + if (!currentTimeZone.equals(DateTimeUtils.getTimeZone())) { + add(session, rows, + // STATE_KEY + "TIME ZONE", + // STATE_COMMAND + StringUtils.quoteStringSQL(new StringBuilder("SET TIME ZONE "), currentTimeZone.getId()) + .toString() + ); + } + } + + private void settings(SessionLocal session, ArrayList rows) { + for (Setting s : database.getAllSettings()) { + String value = s.getStringValue(); + if (value == null) { + value = Integer.toString(s.getIntValue()); + } + add(session, rows, identifier(s.getName()), value); + } + add(session, rows, "info.BUILD_ID", "" + Constants.BUILD_ID); + add(session, rows, "info.VERSION_MAJOR", "" + Constants.VERSION_MAJOR); + add(session, rows, "info.VERSION_MINOR", "" + Constants.VERSION_MINOR); + add(session, rows, "info.VERSION", Constants.FULL_VERSION); + if (session.getUser().isAdmin()) { + String[] settings = { + "java.runtime.version", "java.vm.name", + "java.vendor", "os.name", "os.arch", "os.version", + "sun.os.patch.level", "file.separator", + "path.separator", "line.separator", "user.country", + "user.language", "user.variant", "file.encoding" }; + for (String s : settings) { + add(session, rows, "property." + s, Utils.getProperty(s, "")); + } + } + add(session, rows, "DEFAULT_NULL_ORDERING", database.getDefaultNullOrdering().name()); + add(session, rows, "EXCLUSIVE", database.getExclusiveSession() == null ? "FALSE" : "TRUE"); + add(session, rows, "MODE", database.getMode().getName()); + add(session, rows, "QUERY_TIMEOUT", Integer.toString(session.getQueryTimeout())); + add(session, rows, "TIME ZONE", session.currentTimeZone().getId()); + add(session, rows, "TRUNCATE_LARGE_LENGTH", session.isTruncateLargeLength() ? "TRUE" : "FALSE"); + add(session, rows, "VARIABLE_BINARY", session.isVariableBinary() ? "TRUE" : "FALSE"); + add(session, rows, "OLD_INFORMATION_SCHEMA", session.isOldInformationSchema() ? "TRUE" : "FALSE"); + BitSet nonKeywords = session.getNonKeywords(); + if (nonKeywords != null) { + add(session, rows, "NON_KEYWORDS", Parser.formatNonKeywords(nonKeywords)); + } + add(session, rows, "RETENTION_TIME", Integer.toString(database.getRetentionTime())); + // database settings + for (Map.Entry entry : database.getSettings().getSortedSettings()) { + add(session, rows, entry.getKey(), entry.getValue()); + } + Store store = database.getStore(); + MVStore mvStore = store.getMvStore(); + FileStore fs = mvStore.getFileStore(); + if (fs != null) { + add(session, rows, + "info.FILE_WRITE", Long.toString(fs.getWriteCount())); + add(session, rows, + "info.FILE_WRITE_BYTES", Long.toString(fs.getWriteBytes())); + add(session, rows, + "info.FILE_READ", Long.toString(fs.getReadCount())); + add(session, rows, + "info.FILE_READ_BYTES", Long.toString(fs.getReadBytes())); + add(session, rows, + "info.UPDATE_FAILURE_PERCENT", + String.format(Locale.ENGLISH, "%.2f%%", 100 * mvStore.getUpdateFailureRatio())); + add(session, rows, + "info.FILL_RATE", Integer.toString(mvStore.getFillRate())); + add(session, rows, + "info.CHUNKS_FILL_RATE", Integer.toString(mvStore.getChunksFillRate())); + add(session, rows, + "info.CHUNKS_FILL_RATE_RW", Integer.toString(mvStore.getRewritableChunksFillRate())); + try { + add(session, rows, + "info.FILE_SIZE", Long.toString(fs.getFile().size())); + } catch (IOException ignore) {/**/} + add(session, rows, + "info.CHUNK_COUNT", Long.toString(mvStore.getChunkCount())); + add(session, rows, + "info.PAGE_COUNT", Long.toString(mvStore.getPageCount())); + add(session, rows, + "info.PAGE_COUNT_LIVE", Long.toString(mvStore.getLivePageCount())); + add(session, rows, + "info.PAGE_SIZE", Integer.toString(mvStore.getPageSplitSize())); + add(session, rows, + "info.CACHE_MAX_SIZE", Integer.toString(mvStore.getCacheSize())); + add(session, rows, + "info.CACHE_SIZE", Integer.toString(mvStore.getCacheSizeUsed())); + add(session, rows, + "info.CACHE_HIT_RATIO", Integer.toString(mvStore.getCacheHitRatio())); + add(session, rows, "info.TOC_CACHE_HIT_RATIO", + Integer.toString(mvStore.getTocCacheHitRatio())); + add(session, rows, + "info.LEAF_RATIO", Integer.toString(mvStore.getLeafRatio())); + } + } + + private void synonyms(SessionLocal session, ArrayList rows, String catalog) { + for (TableSynonym synonym : database.getAllSynonyms()) { + add(session, rows, + // SYNONYM_CATALOG + catalog, + // SYNONYM_SCHEMA + synonym.getSchema().getName(), + // SYNONYM_NAME + synonym.getName(), + // SYNONYM_FOR + synonym.getSynonymForName(), + // SYNONYM_FOR_SCHEMA + synonym.getSynonymForSchema().getName(), + // TYPE NAME + "SYNONYM", + // STATUS + "VALID", + // REMARKS + synonym.getComment() + ); + } + } + + private void users(SessionLocal session, ArrayList rows) { + User currentUser = session.getUser(); + if (currentUser.isAdmin()) { + for (RightOwner rightOwner : database.getAllUsersAndRoles()) { + if (rightOwner instanceof User) { + users(session, rows, (User) rightOwner); + } + } + } else { + users(session, rows, currentUser); + } + } + + private void users(SessionLocal session, ArrayList rows, User user) { + add(session, rows, + // USER_NAME + identifier(user.getName()), + // IS_ADMIN + ValueBoolean.get(user.isAdmin()), + // REMARKS + user.getComment() + ); + } + + private void addConstraintColumnUsage(SessionLocal session, ArrayList rows, String catalog, + Constraint constraint, Column column) { + Table table = column.getTable(); + add(session, rows, + // TABLE_CATALOG + catalog, + // TABLE_SCHEMA + table.getSchema().getName(), + // TABLE_NAME + table.getName(), + // COLUMN_NAME + column.getName(), + // CONSTRAINT_CATALOG + catalog, + // CONSTRAINT_SCHEMA + constraint.getSchema().getName(), + // CONSTRAINT_NAME + constraint.getName() + ); + } + + private void addPrivileges(SessionLocal session, ArrayList rows, DbObject grantee, String catalog, // + Table table, String column, int rightMask) { + if ((rightMask & Right.SELECT) != 0) { + addPrivilege(session, rows, grantee, catalog, table, column, "SELECT"); + } + if ((rightMask & Right.INSERT) != 0) { + addPrivilege(session, rows, grantee, catalog, table, column, "INSERT"); + } + if ((rightMask & Right.UPDATE) != 0) { + addPrivilege(session, rows, grantee, catalog, table, column, "UPDATE"); + } + if ((rightMask & Right.DELETE) != 0) { + addPrivilege(session, rows, grantee, catalog, table, column, "DELETE"); + } + } + + private void addPrivilege(SessionLocal session, ArrayList rows, DbObject grantee, String catalog, Table table, + String column, String right) { + String isGrantable = "NO"; + if (grantee.getType() == DbObject.USER) { + User user = (User) grantee; + if (user.isAdmin()) { + // the right is grantable if the grantee is an admin + isGrantable = "YES"; + } + } + if (column == null) { + add(session, rows, + // GRANTOR + null, + // GRANTEE + identifier(grantee.getName()), + // TABLE_CATALOG + catalog, + // TABLE_SCHEMA + table.getSchema().getName(), + // TABLE_NAME + table.getName(), + // PRIVILEGE_TYPE + right, + // IS_GRANTABLE + isGrantable, + // WITH_HIERARCHY + "NO" + ); + } else { + add(session, rows, + // GRANTOR + null, + // GRANTEE + identifier(grantee.getName()), + // TABLE_CATALOG + catalog, + // TABLE_SCHEMA + table.getSchema().getName(), + // TABLE_NAME + table.getName(), + // COLUMN_NAME + column, + // PRIVILEGE_TYPE + right, + // IS_GRANTABLE + isGrantable + ); + } + } + + @Override + public long getMaxDataModificationId() { + switch (type) { + case SETTINGS: + case SEQUENCES: + case IN_DOUBT: + case SESSIONS: + case LOCKS: + case SESSION_STATE: + return Long.MAX_VALUE; + } + return database.getModificationDataId(); + } + + @Override + public boolean isView() { + return isView; + } + + @Override + public long getRowCount(SessionLocal session) { + return getRowCount(session, false); + } + + @Override + public long getRowCountApproximation(SessionLocal session) { + return getRowCount(session, true); + } + + private long getRowCount(SessionLocal session, boolean approximation) { + switch (type) { + case INFORMATION_SCHEMA_CATALOG_NAME: + return 1L; + case COLLATIONS: { + Locale[] locales = CompareMode.getCollationLocales(approximation); + if (locales != null) { + return locales.length + 1; + } + break; + } + case SCHEMATA: + return session.getDatabase().getAllSchemas().size(); + case IN_DOUBT: + if (session.getUser().isAdmin()) { + ArrayList inDoubt = session.getDatabase().getInDoubtTransactions(); + if (inDoubt != null) { + return inDoubt.size(); + } + } + return 0L; + case ROLES: + if (session.getUser().isAdmin()) { + long count = 0L; + for (RightOwner rightOwner : session.getDatabase().getAllUsersAndRoles()) { + if (rightOwner instanceof Role) { + count++; + } + } + return count; + } + break; + case SESSIONS: + if (session.getUser().isAdmin()) { + return session.getDatabase().getSessionCount(); + } else { + return 1L; + } + case USERS: + if (session.getUser().isAdmin()) { + long count = 0L; + for (RightOwner rightOwner : session.getDatabase().getAllUsersAndRoles()) { + if (rightOwner instanceof User) { + count++; + } + } + return count; + } else { + return 1L; + } + } + if (approximation) { + return ROW_COUNT_APPROXIMATION; + } + throw DbException.getInternalError(toString()); + } + + @Override + public boolean canGetRowCount(SessionLocal session) { + switch (type) { + case INFORMATION_SCHEMA_CATALOG_NAME: + case COLLATIONS: + case SCHEMATA: + case IN_DOUBT: + case SESSIONS: + case USERS: + return true; + case ROLES: + if (session.getUser().isAdmin()) { + return true; + } + break; + } + return false; + } + + /** + * Data type information. + */ + static final class DataTypeInformation { + + static final DataTypeInformation NULL = new DataTypeInformation(null, null, null, null, null, null, null, null, + null, false, null, null, null, null, null); + + /** + * DATA_TYPE. + */ + final String dataType; + + /** + * CHARACTER_MAXIMUM_LENGTH and CHARACTER_OCTET_LENGTH. + */ + final Value characterPrecision; + + /** + * NUMERIC_PRECISION. + */ + final Value numericPrecision; + + /** + * NUMERIC_PRECISION_RADIX. + */ + final Value numericPrecisionRadix; + + /** + * NUMERIC_SCALE. + */ + final Value numericScale; + + /** + * DATETIME_PRECISION. + */ + final Value datetimePrecision; + + /** + * INTERVAL_PRECISION. + */ + final Value intervalPrecision; + + /** + * INTERVAL_TYPE. + */ + final Value intervalType; + + /** + * MAXIMUM_CARDINALITY. + */ + final Value maximumCardinality; + + final boolean hasCharsetAndCollation; + + /** + * DECLARED_DATA_TYPE. + */ + final String declaredDataType; + + /** + * DECLARED_NUMERIC_PRECISION. + */ + final Value declaredNumericPrecision; + + /** + * DECLARED_NUMERIC_SCALE. + */ + final Value declaredNumericScale; + + /** + * GEOMETRY_TYPE. + */ + final String geometryType; + + /** + * GEOMETRY_SRID. + */ + final Value geometrySrid; + + static DataTypeInformation valueOf(TypeInfo typeInfo) { + int type = typeInfo.getValueType(); + String dataType = Value.getTypeName(type); + ValueBigint characterPrecision = null; + ValueInteger numericPrecision = null, numericScale = null, numericPrecisionRadix = null, + datetimePrecision = null, intervalPrecision = null, maximumCardinality = null; + String intervalType = null; + boolean hasCharsetAndCollation = false; + String declaredDataType = null; + ValueInteger declaredNumericPrecision = null, declaredNumericScale = null; + String geometryType = null; + ValueInteger geometrySrid = null; + switch (type) { + case Value.CHAR: + case Value.VARCHAR: + case Value.CLOB: + case Value.VARCHAR_IGNORECASE: + hasCharsetAndCollation = true; + //$FALL-THROUGH$ + case Value.BINARY: + case Value.VARBINARY: + case Value.BLOB: + case Value.JAVA_OBJECT: + case Value.JSON: + characterPrecision = ValueBigint.get(typeInfo.getPrecision()); + break; + case Value.TINYINT: + case Value.SMALLINT: + case Value.INTEGER: + case Value.BIGINT: + numericPrecision = ValueInteger.get(MathUtils.convertLongToInt(typeInfo.getPrecision())); + numericScale = ValueInteger.get(0); + numericPrecisionRadix = ValueInteger.get(2); + declaredDataType = dataType; + break; + case Value.NUMERIC: { + numericPrecision = ValueInteger.get(MathUtils.convertLongToInt(typeInfo.getPrecision())); + numericScale = ValueInteger.get(typeInfo.getScale()); + numericPrecisionRadix = ValueInteger.get(10); + declaredDataType = typeInfo.getExtTypeInfo() != null ? "DECIMAL" : "NUMERIC"; + if (typeInfo.getDeclaredPrecision() >= 0L) { + declaredNumericPrecision = numericPrecision; + } + if (typeInfo.getDeclaredScale() >= 0) { + declaredNumericScale = numericScale; + } + break; + } + case Value.REAL: + case Value.DOUBLE: { + numericPrecision = ValueInteger.get(MathUtils.convertLongToInt(typeInfo.getPrecision())); + numericPrecisionRadix = ValueInteger.get(2); + long declaredPrecision = typeInfo.getDeclaredPrecision(); + if (declaredPrecision >= 0) { + declaredDataType = "FLOAT"; + if (declaredPrecision > 0) { + declaredNumericPrecision = ValueInteger.get((int) declaredPrecision); + } + } else { + declaredDataType = dataType; + } + break; + } + case Value.DECFLOAT: + numericPrecision = ValueInteger.get(MathUtils.convertLongToInt(typeInfo.getPrecision())); + numericPrecisionRadix = ValueInteger.get(10); + declaredDataType = dataType; + if (typeInfo.getDeclaredPrecision() >= 0L) { + declaredNumericPrecision = numericPrecision; + } + break; + case Value.INTERVAL_YEAR: + case Value.INTERVAL_MONTH: + case Value.INTERVAL_DAY: + case Value.INTERVAL_HOUR: + case Value.INTERVAL_MINUTE: + case Value.INTERVAL_SECOND: + case Value.INTERVAL_YEAR_TO_MONTH: + case Value.INTERVAL_DAY_TO_HOUR: + case Value.INTERVAL_DAY_TO_MINUTE: + case Value.INTERVAL_DAY_TO_SECOND: + case Value.INTERVAL_HOUR_TO_MINUTE: + case Value.INTERVAL_HOUR_TO_SECOND: + case Value.INTERVAL_MINUTE_TO_SECOND: + intervalType = IntervalQualifier.valueOf(type - Value.INTERVAL_YEAR).toString(); + dataType = "INTERVAL"; + intervalPrecision = ValueInteger.get(MathUtils.convertLongToInt(typeInfo.getPrecision())); + //$FALL-THROUGH$ + case Value.DATE: + case Value.TIME: + case Value.TIME_TZ: + case Value.TIMESTAMP: + case Value.TIMESTAMP_TZ: + datetimePrecision = ValueInteger.get(typeInfo.getScale()); + break; + case Value.GEOMETRY: { + ExtTypeInfoGeometry extTypeInfo = (ExtTypeInfoGeometry) typeInfo.getExtTypeInfo(); + if (extTypeInfo != null) { + int typeCode = extTypeInfo.getType(); + if (typeCode != 0) { + geometryType = EWKTUtils.formatGeometryTypeAndDimensionSystem(new StringBuilder(), typeCode) + .toString(); + } + Integer srid = extTypeInfo.getSrid(); + if (srid != null) { + geometrySrid = ValueInteger.get(srid); + } + } + break; + } + case Value.ARRAY: + maximumCardinality = ValueInteger.get(MathUtils.convertLongToInt(typeInfo.getPrecision())); + } + return new DataTypeInformation(dataType, characterPrecision, numericPrecision, numericPrecisionRadix, + numericScale, datetimePrecision, intervalPrecision, + intervalType != null ? ValueVarchar.get(intervalType) : ValueNull.INSTANCE, maximumCardinality, + hasCharsetAndCollation, declaredDataType, declaredNumericPrecision, declaredNumericScale, + geometryType, geometrySrid); + } + + private DataTypeInformation(String dataType, Value characterPrecision, Value numericPrecision, + Value numericPrecisionRadix, Value numericScale, Value datetimePrecision, Value intervalPrecision, + Value intervalType, Value maximumCardinality, boolean hasCharsetAndCollation, String declaredDataType, + Value declaredNumericPrecision, Value declaredNumericScale, String geometryType, Value geometrySrid) { + this.dataType = dataType; + this.characterPrecision = characterPrecision; + this.numericPrecision = numericPrecision; + this.numericPrecisionRadix = numericPrecisionRadix; + this.numericScale = numericScale; + this.datetimePrecision = datetimePrecision; + this.intervalPrecision = intervalPrecision; + this.intervalType = intervalType; + this.maximumCardinality = maximumCardinality; + this.hasCharsetAndCollation = hasCharsetAndCollation; + this.declaredDataType = declaredDataType; + this.declaredNumericPrecision = declaredNumericPrecision; + this.declaredNumericScale = declaredNumericScale; + this.geometryType = geometryType; + this.geometrySrid = geometrySrid; + } + + } + +} diff --git a/h2/src/main/org/h2/table/InformationSchemaTableLegacy.java b/h2/src/main/org/h2/table/InformationSchemaTableLegacy.java new file mode 100644 index 0000000..59695ef --- /dev/null +++ b/h2/src/main/org/h2/table/InformationSchemaTableLegacy.java @@ -0,0 +1,2519 @@ +/* + * 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.io.ByteArrayInputStream; +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; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; + +import org.h2.command.Command; +import org.h2.command.Parser; +import org.h2.command.dml.Help; +import org.h2.constraint.Constraint; +import org.h2.constraint.Constraint.Type; +import org.h2.constraint.ConstraintActionType; +import org.h2.constraint.ConstraintCheck; +import org.h2.constraint.ConstraintDomain; +import org.h2.constraint.ConstraintReferential; +import org.h2.constraint.ConstraintUnique; +import org.h2.engine.Constants; +import org.h2.engine.DbObject; +import org.h2.engine.QueryStatisticsData; +import org.h2.engine.Right; +import org.h2.engine.RightOwner; +import org.h2.engine.Role; +import org.h2.engine.SessionLocal; +import org.h2.engine.SessionLocal.State; +import org.h2.engine.Setting; +import org.h2.engine.User; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.ValueExpression; +import org.h2.index.Index; +import org.h2.index.MetaIndex; +import org.h2.message.DbException; +import org.h2.mvstore.FileStore; +import org.h2.mvstore.MVStore; +import org.h2.mvstore.db.Store; +import org.h2.result.Row; +import org.h2.result.SearchRow; +import org.h2.result.SortOrder; +import org.h2.schema.Constant; +import org.h2.schema.Domain; +import org.h2.schema.FunctionAlias; +import org.h2.schema.FunctionAlias.JavaMethod; +import org.h2.schema.Schema; +import org.h2.schema.SchemaObject; +import org.h2.schema.Sequence; +import org.h2.schema.TriggerObject; +import org.h2.schema.UserDefinedFunction; +import org.h2.store.InDoubtTransaction; +import org.h2.tools.Csv; +import org.h2.util.DateTimeUtils; +import org.h2.util.HasSQL; +import org.h2.util.MathUtils; +import org.h2.util.NetworkConnectionInfo; +import org.h2.util.StringUtils; +import org.h2.util.TimeZoneProvider; +import org.h2.util.Utils; +import org.h2.value.CompareMode; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBigint; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueDouble; +import org.h2.value.ValueInteger; +import org.h2.value.ValueSmallint; +import org.h2.value.ValueToObjectConverter2; + +/** + * This class is responsible to build the legacy variant of INFORMATION_SCHEMA + * tables. + */ +public final class InformationSchemaTableLegacy extends MetaTable { + + private static final String CHARACTER_SET_NAME = "Unicode"; + + private static final int TABLES = 0; + private static final int COLUMNS = TABLES + 1; + private static final int INDEXES = COLUMNS + 1; + private static final int TABLE_TYPES = INDEXES + 1; + private static final int TYPE_INFO = TABLE_TYPES + 1; + private static final int CATALOGS = TYPE_INFO + 1; + private static final int SETTINGS = CATALOGS + 1; + private static final int HELP = SETTINGS + 1; + private static final int SEQUENCES = HELP + 1; + private static final int USERS = SEQUENCES + 1; + private static final int ROLES = USERS + 1; + private static final int RIGHTS = ROLES + 1; + private static final int FUNCTION_ALIASES = RIGHTS + 1; + private static final int SCHEMATA = FUNCTION_ALIASES + 1; + private static final int TABLE_PRIVILEGES = SCHEMATA + 1; + private static final int COLUMN_PRIVILEGES = TABLE_PRIVILEGES + 1; + private static final int COLLATIONS = COLUMN_PRIVILEGES + 1; + private static final int VIEWS = COLLATIONS + 1; + private static final int IN_DOUBT = VIEWS + 1; + private static final int CROSS_REFERENCES = IN_DOUBT + 1; + private static final int FUNCTION_COLUMNS = CROSS_REFERENCES + 1; + private static final int CONSTRAINTS = FUNCTION_COLUMNS + 1; + private static final int CONSTANTS = CONSTRAINTS + 1; + private static final int DOMAINS = CONSTANTS + 1; + private static final int TRIGGERS = DOMAINS + 1; + private static final int SESSIONS = TRIGGERS + 1; + private static final int LOCKS = SESSIONS + 1; + private static final int SESSION_STATE = LOCKS + 1; + private static final int QUERY_STATISTICS = SESSION_STATE + 1; + private static final int SYNONYMS = QUERY_STATISTICS + 1; + private static final int TABLE_CONSTRAINTS = SYNONYMS + 1; + private static final int DOMAIN_CONSTRAINTS = TABLE_CONSTRAINTS + 1; + private static final int KEY_COLUMN_USAGE = DOMAIN_CONSTRAINTS + 1; + private static final int REFERENTIAL_CONSTRAINTS = KEY_COLUMN_USAGE + 1; + private static final int CHECK_CONSTRAINTS = REFERENTIAL_CONSTRAINTS + 1; + private static final int CONSTRAINT_COLUMN_USAGE = CHECK_CONSTRAINTS + 1; + + /** + * The number of meta table types. Supported meta table types are + * {@code 0..META_TABLE_TYPE_COUNT - 1}. + */ + public static final int META_TABLE_TYPE_COUNT = CONSTRAINT_COLUMN_USAGE + 1; + + /** + * Create a new metadata table. + * + * @param schema the schema + * @param id the object id + * @param type the meta table type + */ + public InformationSchemaTableLegacy(Schema schema, int id, int type) { + super(schema, id, type); + Column[] cols; + String indexColumnName = null; + switch (type) { + case TABLES: + setMetaTableName("TABLES"); + cols = new Column[] { + column("TABLE_CATALOG"), // + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("TABLE_TYPE"), // + // extensions + column("STORAGE_TYPE"), // + column("SQL"), // + column("REMARKS"), // + column("LAST_MODIFICATION", TypeInfo.TYPE_BIGINT), // + column("ID", TypeInfo.TYPE_INTEGER), // + column("TYPE_NAME"), // + column("TABLE_CLASS"), // + column("ROW_COUNT_ESTIMATE", TypeInfo.TYPE_BIGINT), // + }; + indexColumnName = "TABLE_NAME"; + break; + case COLUMNS: + setMetaTableName("COLUMNS"); + cols = new Column[] { + column("TABLE_CATALOG"), // + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("COLUMN_NAME"), // + column("ORDINAL_POSITION", TypeInfo.TYPE_INTEGER), // + column("COLUMN_DEFAULT"), // + column("IS_NULLABLE"), // + column("DATA_TYPE", TypeInfo.TYPE_INTEGER), // + column("CHARACTER_MAXIMUM_LENGTH", TypeInfo.TYPE_INTEGER), // + column("CHARACTER_OCTET_LENGTH", TypeInfo.TYPE_INTEGER), // + column("NUMERIC_PRECISION", TypeInfo.TYPE_INTEGER), // + column("NUMERIC_PRECISION_RADIX", TypeInfo.TYPE_INTEGER), // + column("NUMERIC_SCALE", TypeInfo.TYPE_INTEGER), // + column("DATETIME_PRECISION", TypeInfo.TYPE_INTEGER), // + column("INTERVAL_TYPE"), // + column("INTERVAL_PRECISION", TypeInfo.TYPE_INTEGER), // + column("CHARACTER_SET_NAME"), // + column("COLLATION_NAME"), // + column("DOMAIN_CATALOG"), // + column("DOMAIN_SCHEMA"), // + column("DOMAIN_NAME"), // + column("IS_GENERATED"), // + column("GENERATION_EXPRESSION"), // + // extensions + column("TYPE_NAME"), // + column("NULLABLE", TypeInfo.TYPE_INTEGER), // + column("IS_COMPUTED", TypeInfo.TYPE_BOOLEAN), // + column("SELECTIVITY", TypeInfo.TYPE_INTEGER), // + column("SEQUENCE_NAME"), // + column("REMARKS"), // + column("SOURCE_DATA_TYPE", TypeInfo.TYPE_SMALLINT), // + column("COLUMN_TYPE"), // + column("COLUMN_ON_UPDATE"), // + column("IS_VISIBLE"), // + // compatibility + column("CHECK_CONSTRAINT"), // + }; + indexColumnName = "TABLE_NAME"; + break; + case INDEXES: + setMetaTableName("INDEXES"); + cols = new Column[] { + column("TABLE_CATALOG"), // + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("NON_UNIQUE", TypeInfo.TYPE_BOOLEAN), // + column("INDEX_NAME"), // + column("ORDINAL_POSITION", TypeInfo.TYPE_SMALLINT), // + column("COLUMN_NAME"), // + column("CARDINALITY", TypeInfo.TYPE_INTEGER), // + column("PRIMARY_KEY", TypeInfo.TYPE_BOOLEAN), // + column("INDEX_TYPE_NAME"), // + column("IS_GENERATED", TypeInfo.TYPE_BOOLEAN), // + column("INDEX_TYPE", TypeInfo.TYPE_SMALLINT), // + column("ASC_OR_DESC"), // + column("PAGES", TypeInfo.TYPE_INTEGER), // + column("FILTER_CONDITION"), // + column("REMARKS"), // + column("SQL"), // + column("ID", TypeInfo.TYPE_INTEGER), // + column("SORT_TYPE", TypeInfo.TYPE_INTEGER), // + column("CONSTRAINT_NAME"), // + column("INDEX_CLASS"), // + }; + indexColumnName = "TABLE_NAME"; + break; + case TABLE_TYPES: + setMetaTableName("TABLE_TYPES"); + cols = new Column[] { + column("TYPE"), // + }; + break; + case TYPE_INFO: + setMetaTableName("TYPE_INFO"); + cols = new Column[] { + column("TYPE_NAME"), // + column("DATA_TYPE", TypeInfo.TYPE_INTEGER), // + column("PRECISION", TypeInfo.TYPE_INTEGER), // + column("PREFIX"), // + column("SUFFIX"), // + column("PARAMS"), // + column("AUTO_INCREMENT", TypeInfo.TYPE_BOOLEAN), // + column("MINIMUM_SCALE", TypeInfo.TYPE_SMALLINT), // + column("MAXIMUM_SCALE", TypeInfo.TYPE_SMALLINT), // + column("RADIX", TypeInfo.TYPE_INTEGER), // + column("POS", TypeInfo.TYPE_INTEGER), // + column("CASE_SENSITIVE", TypeInfo.TYPE_BOOLEAN), // + column("NULLABLE", TypeInfo.TYPE_SMALLINT), // + column("SEARCHABLE", TypeInfo.TYPE_SMALLINT), // + }; + break; + case CATALOGS: + setMetaTableName("CATALOGS"); + cols = new Column[] { + column("CATALOG_NAME"), // + }; + break; + case SETTINGS: + setMetaTableName("SETTINGS"); + cols = new Column[] { + column("NAME"), // + column("VALUE"), // + }; + break; + case HELP: + setMetaTableName("HELP"); + cols = new Column[] { + column("ID", TypeInfo.TYPE_INTEGER), // + column("SECTION"), // + column("TOPIC"), // + column("SYNTAX"), // + column("TEXT"), // + }; + break; + case SEQUENCES: + setMetaTableName("SEQUENCES"); + cols = new Column[] { + column("SEQUENCE_CATALOG"), // + column("SEQUENCE_SCHEMA"), // + column("SEQUENCE_NAME"), // + column("DATA_TYPE"), // + column("NUMERIC_PRECISION", TypeInfo.TYPE_INTEGER), // + column("NUMERIC_PRECISION_RADIX", TypeInfo.TYPE_INTEGER), // + column("NUMERIC_SCALE", TypeInfo.TYPE_INTEGER), // + column("START_VALUE", TypeInfo.TYPE_BIGINT), // + column("MINIMUM_VALUE", TypeInfo.TYPE_BIGINT), // + column("MAXIMUM_VALUE", TypeInfo.TYPE_BIGINT), // + column("INCREMENT", TypeInfo.TYPE_BIGINT), // + column("CYCLE_OPTION"), // + column("DECLARED_DATA_TYPE"), // + column("DECLARED_NUMERIC_PRECISION", TypeInfo.TYPE_INTEGER), // + column("DECLARED_NUMERIC_SCALE", TypeInfo.TYPE_INTEGER), // + column("CURRENT_VALUE", TypeInfo.TYPE_BIGINT), // + column("IS_GENERATED", TypeInfo.TYPE_BOOLEAN), // + column("REMARKS"), // + column("CACHE", TypeInfo.TYPE_BIGINT), // + column("ID", TypeInfo.TYPE_INTEGER), // + // compatibility + column("MIN_VALUE", TypeInfo.TYPE_BIGINT), // + column("MAX_VALUE", TypeInfo.TYPE_BIGINT), // + column("IS_CYCLE", TypeInfo.TYPE_BOOLEAN), // + }; + break; + case USERS: + setMetaTableName("USERS"); + cols = new Column[] { + column("NAME"), // + column("ADMIN"), // + column("REMARKS"), // + column("ID", TypeInfo.TYPE_INTEGER), // + }; + break; + case ROLES: + setMetaTableName("ROLES"); + cols = new Column[] { + column("NAME"), // + column("REMARKS"), // + column("ID", TypeInfo.TYPE_INTEGER), // + }; + break; + case RIGHTS: + setMetaTableName("RIGHTS"); + cols = new Column[] { + column("GRANTEE"), // + column("GRANTEETYPE"), // + column("GRANTEDROLE"), // + column("RIGHTS"), // + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("ID", TypeInfo.TYPE_INTEGER), // + }; + indexColumnName = "TABLE_NAME"; + break; + case FUNCTION_ALIASES: + setMetaTableName("FUNCTION_ALIASES"); + cols = new Column[] { + column("ALIAS_CATALOG"), // + column("ALIAS_SCHEMA"), // + column("ALIAS_NAME"), // + column("JAVA_CLASS"), // + column("JAVA_METHOD"), // + column("DATA_TYPE", TypeInfo.TYPE_INTEGER), // + column("TYPE_NAME"), // + column("COLUMN_COUNT", TypeInfo.TYPE_INTEGER), // + column("RETURNS_RESULT", TypeInfo.TYPE_SMALLINT), // + column("REMARKS"), // + column("ID", TypeInfo.TYPE_INTEGER), // + column("SOURCE"), // + }; + break; + case FUNCTION_COLUMNS: + setMetaTableName("FUNCTION_COLUMNS"); + cols = new Column[] { + column("ALIAS_CATALOG"), // + column("ALIAS_SCHEMA"), // + column("ALIAS_NAME"), // + column("JAVA_CLASS"), // + column("JAVA_METHOD"), // + column("COLUMN_COUNT", TypeInfo.TYPE_INTEGER), // + column("POS", TypeInfo.TYPE_INTEGER), // + column("COLUMN_NAME"), // + column("DATA_TYPE", TypeInfo.TYPE_INTEGER), // + column("TYPE_NAME"), // + column("PRECISION", TypeInfo.TYPE_INTEGER), // + column("SCALE", TypeInfo.TYPE_SMALLINT), // + column("RADIX", TypeInfo.TYPE_SMALLINT), // + column("NULLABLE", TypeInfo.TYPE_SMALLINT), // + column("COLUMN_TYPE", TypeInfo.TYPE_SMALLINT), // + column("REMARKS"), // + column("COLUMN_DEFAULT"), // + }; + break; + case SCHEMATA: + setMetaTableName("SCHEMATA"); + cols = new Column[] { + column("CATALOG_NAME"), // + column("SCHEMA_NAME"), // + column("SCHEMA_OWNER"), // + column("DEFAULT_CHARACTER_SET_NAME"), // + column("DEFAULT_COLLATION_NAME"), // + column("IS_DEFAULT", TypeInfo.TYPE_BOOLEAN), // + column("REMARKS"), // + column("ID", TypeInfo.TYPE_INTEGER), // + }; + break; + case TABLE_PRIVILEGES: + setMetaTableName("TABLE_PRIVILEGES"); + cols = new Column[] { + column("GRANTOR"), // + column("GRANTEE"), // + column("TABLE_CATALOG"), // + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("PRIVILEGE_TYPE"), // + column("IS_GRANTABLE"), // + }; + indexColumnName = "TABLE_NAME"; + break; + case COLUMN_PRIVILEGES: + setMetaTableName("COLUMN_PRIVILEGES"); + cols = new Column[] { + column("GRANTOR"), // + column("GRANTEE"), // + column("TABLE_CATALOG"), // + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("COLUMN_NAME"), // + column("PRIVILEGE_TYPE"), // + column("IS_GRANTABLE"), // + }; + indexColumnName = "TABLE_NAME"; + break; + case COLLATIONS: + setMetaTableName("COLLATIONS"); + cols = new Column[] { + column("NAME"), // + column("KEY"), // + }; + break; + case VIEWS: + setMetaTableName("VIEWS"); + cols = new Column[] { + column("TABLE_CATALOG"), // + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("VIEW_DEFINITION"), // + column("CHECK_OPTION"), // + column("IS_UPDATABLE"), // + column("STATUS"), // + column("REMARKS"), // + column("ID", TypeInfo.TYPE_INTEGER), // + }; + indexColumnName = "TABLE_NAME"; + break; + case IN_DOUBT: + setMetaTableName("IN_DOUBT"); + cols = new Column[] { + column("TRANSACTION"), // + column("STATE"), // + }; + break; + case CROSS_REFERENCES: + setMetaTableName("CROSS_REFERENCES"); + cols = new Column[] { + column("PKTABLE_CATALOG"), // + column("PKTABLE_SCHEMA"), // + column("PKTABLE_NAME"), // + column("PKCOLUMN_NAME"), // + column("FKTABLE_CATALOG"), // + column("FKTABLE_SCHEMA"), // + column("FKTABLE_NAME"), // + column("FKCOLUMN_NAME"), // + column("ORDINAL_POSITION", TypeInfo.TYPE_SMALLINT), // + column("UPDATE_RULE", TypeInfo.TYPE_SMALLINT), // + column("DELETE_RULE", TypeInfo.TYPE_SMALLINT), // + column("FK_NAME"), // + column("PK_NAME"), // + column("DEFERRABILITY", TypeInfo.TYPE_SMALLINT), // + }; + indexColumnName = "PKTABLE_NAME"; + break; + case CONSTRAINTS: + setMetaTableName("CONSTRAINTS"); + cols = new Column[] { + column("CONSTRAINT_CATALOG"), // + column("CONSTRAINT_SCHEMA"), // + column("CONSTRAINT_NAME"), // + column("CONSTRAINT_TYPE"), // + column("TABLE_CATALOG"), // + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("UNIQUE_INDEX_NAME"), // + column("CHECK_EXPRESSION"), // + column("COLUMN_LIST"), // + column("REMARKS"), // + column("SQL"), // + column("ID", TypeInfo.TYPE_INTEGER), // + }; + indexColumnName = "TABLE_NAME"; + break; + case CONSTANTS: + setMetaTableName("CONSTANTS"); + cols = new Column[] { + column("CONSTANT_CATALOG"), // + column("CONSTANT_SCHEMA"), // + column("CONSTANT_NAME"), // + column("DATA_TYPE", TypeInfo.TYPE_INTEGER), // + column("REMARKS"), // + column("SQL"), // + column("ID", TypeInfo.TYPE_INTEGER), // + }; + break; + case DOMAINS: + setMetaTableName("DOMAINS"); + cols = new Column[] { + column("DOMAIN_CATALOG"), // + column("DOMAIN_SCHEMA"), // + column("DOMAIN_NAME"), // + column("DOMAIN_DEFAULT"), // + column("DOMAIN_ON_UPDATE"), // + column("DATA_TYPE", TypeInfo.TYPE_INTEGER), // + column("PRECISION", TypeInfo.TYPE_INTEGER), // + column("SCALE", TypeInfo.TYPE_INTEGER), // + column("TYPE_NAME"), // + column("PARENT_DOMAIN_CATALOG"), // + column("PARENT_DOMAIN_SCHEMA"), // + column("PARENT_DOMAIN_NAME"), // + column("SELECTIVITY", TypeInfo.TYPE_INTEGER), // + column("REMARKS"), // + column("SQL"), // + column("ID", TypeInfo.TYPE_INTEGER), // + // compatibility + column("COLUMN_DEFAULT"), // + column("IS_NULLABLE"), // + column("CHECK_CONSTRAINT"), // + }; + break; + case TRIGGERS: + setMetaTableName("TRIGGERS"); + cols = new Column[] { + column("TRIGGER_CATALOG"), // + column("TRIGGER_SCHEMA"), // + column("TRIGGER_NAME"), // + column("TRIGGER_TYPE"), // + column("TABLE_CATALOG"), // + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("BEFORE", TypeInfo.TYPE_BOOLEAN), // + column("JAVA_CLASS"), // + column("QUEUE_SIZE", TypeInfo.TYPE_INTEGER), // + column("NO_WAIT", TypeInfo.TYPE_BOOLEAN), // + column("REMARKS"), // + column("SQL"), // + column("ID", TypeInfo.TYPE_INTEGER), // + }; + break; + case SESSIONS: { + setMetaTableName("SESSIONS"); + cols = new Column[] { + column("ID", TypeInfo.TYPE_INTEGER), // + column("USER_NAME"), // + column("SERVER"), // + column("CLIENT_ADDR"), // + column("CLIENT_INFO"), // + column("SESSION_START", TypeInfo.TYPE_TIMESTAMP_TZ), // + column("ISOLATION_LEVEL"), // + column("STATEMENT"), // + column("STATEMENT_START", TypeInfo.TYPE_TIMESTAMP_TZ), // + column("CONTAINS_UNCOMMITTED", TypeInfo.TYPE_BOOLEAN), // + column("STATE"), // + column("BLOCKER_ID", TypeInfo.TYPE_INTEGER), // + column("SLEEP_SINCE", TypeInfo.TYPE_TIMESTAMP_TZ), // + }; + break; + } + case LOCKS: { + setMetaTableName("LOCKS"); + cols = new Column[] { + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("SESSION_ID", TypeInfo.TYPE_INTEGER), // + column("LOCK_TYPE"), // + }; + break; + } + case SESSION_STATE: { + setMetaTableName("SESSION_STATE"); + cols = new Column[] { + column("KEY"), // + column("SQL"), // + }; + break; + } + case QUERY_STATISTICS: { + setMetaTableName("QUERY_STATISTICS"); + cols = new Column[] { + column("SQL_STATEMENT"), // + column("EXECUTION_COUNT", TypeInfo.TYPE_INTEGER), // + column("MIN_EXECUTION_TIME", TypeInfo.TYPE_DOUBLE), // + column("MAX_EXECUTION_TIME", TypeInfo.TYPE_DOUBLE), // + column("CUMULATIVE_EXECUTION_TIME", TypeInfo.TYPE_DOUBLE), // + column("AVERAGE_EXECUTION_TIME", TypeInfo.TYPE_DOUBLE), // + column("STD_DEV_EXECUTION_TIME", TypeInfo.TYPE_DOUBLE), // + column("MIN_ROW_COUNT", TypeInfo.TYPE_BIGINT), // + column("MAX_ROW_COUNT", TypeInfo.TYPE_BIGINT), // + column("CUMULATIVE_ROW_COUNT", TypeInfo.TYPE_BIGINT), // + column("AVERAGE_ROW_COUNT", TypeInfo.TYPE_DOUBLE), // + column("STD_DEV_ROW_COUNT", TypeInfo.TYPE_DOUBLE), // + }; + break; + } + case SYNONYMS: { + setMetaTableName("SYNONYMS"); + cols = new Column[] { + column("SYNONYM_CATALOG"), // + column("SYNONYM_SCHEMA"), // + column("SYNONYM_NAME"), // + column("SYNONYM_FOR"), // + column("SYNONYM_FOR_SCHEMA"), // + column("TYPE_NAME"), // + column("STATUS"), // + column("REMARKS"), // + column("ID", TypeInfo.TYPE_INTEGER), // + }; + indexColumnName = "SYNONYM_NAME"; + break; + } + case TABLE_CONSTRAINTS: { + setMetaTableName("TABLE_CONSTRAINTS"); + cols = new Column[] { + column("CONSTRAINT_CATALOG"), // + column("CONSTRAINT_SCHEMA"), // + column("CONSTRAINT_NAME"), // + column("CONSTRAINT_TYPE"), // + column("TABLE_CATALOG"), // + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("IS_DEFERRABLE"), // + column("INITIALLY_DEFERRED"), // + column("REMARKS"), // + column("SQL"), // + column("ID", TypeInfo.TYPE_INTEGER), // + }; + indexColumnName = "TABLE_NAME"; + break; + } + case DOMAIN_CONSTRAINTS: { + setMetaTableName("DOMAIN_CONSTRAINTS"); + cols = new Column[] { + column("CONSTRAINT_CATALOG"), // + column("CONSTRAINT_SCHEMA"), // + column("CONSTRAINT_NAME"), // + column("DOMAIN_CATALOG"), // + column("DOMAIN_SCHEMA"), // + column("DOMAIN_NAME"), // + column("IS_DEFERRABLE"), // + column("INITIALLY_DEFERRED"), // + column("REMARKS"), // + column("SQL"), // + column("ID", TypeInfo.TYPE_INTEGER), // + }; + break; + } + case KEY_COLUMN_USAGE: { + setMetaTableName("KEY_COLUMN_USAGE"); + cols = new Column[] { + column("CONSTRAINT_CATALOG"), // + column("CONSTRAINT_SCHEMA"), // + column("CONSTRAINT_NAME"), // + column("TABLE_CATALOG"), // + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("COLUMN_NAME"), // + column("ORDINAL_POSITION", TypeInfo.TYPE_INTEGER), // + column("POSITION_IN_UNIQUE_CONSTRAINT", TypeInfo.TYPE_INTEGER), // + column("INDEX_CATALOG"), // + column("INDEX_SCHEMA"), // + column("INDEX_NAME"), // + }; + indexColumnName = "TABLE_NAME"; + break; + } + case REFERENTIAL_CONSTRAINTS: { + setMetaTableName("REFERENTIAL_CONSTRAINTS"); + cols = new Column[] { + column("CONSTRAINT_CATALOG"), // + column("CONSTRAINT_SCHEMA"), // + column("CONSTRAINT_NAME"), // + column("UNIQUE_CONSTRAINT_CATALOG"), // + column("UNIQUE_CONSTRAINT_SCHEMA"), // + column("UNIQUE_CONSTRAINT_NAME"), // + column("MATCH_OPTION"), // + column("UPDATE_RULE"), // + column("DELETE_RULE"), // + }; + break; + } + case CHECK_CONSTRAINTS: { + setMetaTableName("CHECK_CONSTRAINTS"); + cols = new Column[] { + column("CONSTRAINT_CATALOG"), // + column("CONSTRAINT_SCHEMA"), // + column("CONSTRAINT_NAME"), // + column("CHECK_CLAUSE"), // + }; + break; + } + case CONSTRAINT_COLUMN_USAGE: { + setMetaTableName("CONSTRAINT_COLUMN_USAGE"); + cols = new Column[] { + column("TABLE_CATALOG"), // + column("TABLE_SCHEMA"), // + column("TABLE_NAME"), // + column("COLUMN_NAME"), // + column("CONSTRAINT_CATALOG"), // + column("CONSTRAINT_SCHEMA"), // + column("CONSTRAINT_NAME"), // + }; + indexColumnName = "TABLE_NAME"; + break; + } + default: + throw DbException.getInternalError("type=" + type); + } + setColumns(cols); + + if (indexColumnName == null) { + indexColumn = -1; + metaIndex = null; + } else { + indexColumn = getColumn(database.sysIdentifier(indexColumnName)).getColumnId(); + IndexColumn[] indexCols = IndexColumn.wrap( + new Column[] { cols[indexColumn] }); + metaIndex = new MetaIndex(this, indexCols, false); + } + } + + private static String replaceNullWithEmpty(String s) { + return s == null ? "" : s; + } + + @Override + public ArrayList generateRows(SessionLocal session, SearchRow first, SearchRow last) { + Value indexFrom = null, indexTo = null; + + if (indexColumn >= 0) { + if (first != null) { + indexFrom = first.getValue(indexColumn); + } + if (last != null) { + indexTo = last.getValue(indexColumn); + } + } + + ArrayList rows = Utils.newSmallArrayList(); + String catalog = database.getShortName(); + boolean admin = session.getUser().isAdmin(); + switch (type) { + case TABLES: { + for (Table table : getAllTables(session)) { + String tableName = table.getName(); + if (!checkIndex(session, tableName, indexFrom, indexTo)) { + continue; + } + if (hideTable(table, session)) { + continue; + } + String storageType; + if (table.isTemporary()) { + if (table.isGlobalTemporary()) { + storageType = "GLOBAL TEMPORARY"; + } else { + storageType = "LOCAL TEMPORARY"; + } + } else { + storageType = table.isPersistIndexes() ? + "CACHED" : "MEMORY"; + } + String sql = table.getCreateSQL(); + if (!admin) { + if (sql != null && sql.contains(DbException.HIDE_SQL)) { + // hide the password of linked tables + sql = "-"; + } + } + add(session, + rows, + // TABLE_CATALOG + catalog, + // TABLE_SCHEMA + table.getSchema().getName(), + // TABLE_NAME + tableName, + // TABLE_TYPE + table.getTableType().toString(), + // STORAGE_TYPE + storageType, + // SQL + sql, + // REMARKS + replaceNullWithEmpty(table.getComment()), + // LAST_MODIFICATION + ValueBigint.get(table.getMaxDataModificationId()), + // ID + ValueInteger.get(table.getId()), + // TYPE_NAME + null, + // TABLE_CLASS + table.getClass().getName(), + // ROW_COUNT_ESTIMATE + ValueBigint.get(table.getRowCountApproximation(session)) + ); + } + break; + } + case COLUMNS: { + // reduce the number of tables to scan - makes some metadata queries + // 10x faster + final ArrayList tablesToList; + if (indexFrom != null && indexFrom.equals(indexTo)) { + String tableName = indexFrom.getString(); + if (tableName == null) { + break; + } + tablesToList = getTablesByName(session, tableName); + } else { + tablesToList = getAllTables(session); + } + for (Table table : tablesToList) { + String tableName = table.getName(); + if (!checkIndex(session, tableName, indexFrom, indexTo)) { + continue; + } + if (hideTable(table, session)) { + continue; + } + Column[] cols = table.getColumns(); + String collation = database.getCompareMode().getName(); + for (int j = 0; j < cols.length; j++) { + Column c = cols[j]; + Domain domain = c.getDomain(); + TypeInfo typeInfo = c.getType(); + ValueInteger precision = ValueInteger.get(MathUtils.convertLongToInt(typeInfo.getPrecision())); + ValueInteger scale = ValueInteger.get(typeInfo.getScale()); + Sequence sequence = c.getSequence(); + boolean hasDateTimePrecision; + int type = typeInfo.getValueType(); + switch (type) { + case Value.TIME: + case Value.TIME_TZ: + case Value.DATE: + case Value.TIMESTAMP: + case Value.TIMESTAMP_TZ: + case Value.INTERVAL_SECOND: + case Value.INTERVAL_DAY_TO_SECOND: + case Value.INTERVAL_HOUR_TO_SECOND: + case Value.INTERVAL_MINUTE_TO_SECOND: + hasDateTimePrecision = true; + break; + default: + hasDateTimePrecision = false; + } + boolean isGenerated = c.isGenerated(); + boolean isInterval = DataType.isIntervalType(type); + String createSQLWithoutName = c.getCreateSQLWithoutName(); + add(session, + rows, + // TABLE_CATALOG + catalog, + // TABLE_SCHEMA + table.getSchema().getName(), + // TABLE_NAME + tableName, + // COLUMN_NAME + c.getName(), + // ORDINAL_POSITION + ValueInteger.get(j + 1), + // COLUMN_DEFAULT + isGenerated ? null : c.getDefaultSQL(), + // IS_NULLABLE + c.isNullable() ? "YES" : "NO", + // DATA_TYPE + ValueInteger.get(DataType.convertTypeToSQLType(typeInfo)), + // CHARACTER_MAXIMUM_LENGTH + precision, + // CHARACTER_OCTET_LENGTH + precision, + // NUMERIC_PRECISION + precision, + // NUMERIC_PRECISION_RADIX + ValueInteger.get(10), + // NUMERIC_SCALE + scale, + // DATETIME_PRECISION + hasDateTimePrecision ? scale : null, + // INTERVAL_TYPE + isInterval ? createSQLWithoutName.substring(9) : null, + // INTERVAL_PRECISION + isInterval ? precision : null, + // CHARACTER_SET_NAME + CHARACTER_SET_NAME, + // COLLATION_NAME + collation, + // DOMAIN_CATALOG + domain != null ? catalog : null, + // DOMAIN_SCHEMA + domain != null ? domain.getSchema().getName() : null, + // DOMAIN_NAME + domain != null ? domain.getName() : null, + // IS_GENERATED + isGenerated ? "ALWAYS" : "NEVER", + // GENERATION_EXPRESSION + isGenerated ? c.getDefaultSQL() : null, + // TYPE_NAME + identifier(isInterval ? "INTERVAL" : typeInfo.getDeclaredTypeName()), + // NULLABLE + ValueInteger.get(c.isNullable() + ? DatabaseMetaData.columnNullable : DatabaseMetaData.columnNoNulls), + // IS_COMPUTED + ValueBoolean.get(isGenerated), + // SELECTIVITY + ValueInteger.get(c.getSelectivity()), + // SEQUENCE_NAME + sequence == null ? null : sequence.getName(), + // REMARKS + replaceNullWithEmpty(c.getComment()), + // SOURCE_DATA_TYPE + // SMALLINT + null, + // COLUMN_TYPE + createSQLWithoutName, + // COLUMN_ON_UPDATE + c.getOnUpdateSQL(), + // IS_VISIBLE + ValueBoolean.get(c.getVisible()), + // CHECK_CONSTRAINT + null + ); + } + } + break; + } + case INDEXES: { + // reduce the number of tables to scan - makes some metadata queries + // 10x faster + final ArrayList
tablesToList; + if (indexFrom != null && indexFrom.equals(indexTo)) { + String tableName = indexFrom.getString(); + if (tableName == null) { + break; + } + tablesToList = getTablesByName(session, tableName); + } else { + tablesToList = getAllTables(session); + } + for (Table table : tablesToList) { + String tableName = table.getName(); + if (!checkIndex(session, tableName, indexFrom, indexTo)) { + continue; + } + if (hideTable(table, session)) { + continue; + } + ArrayList indexes = table.getIndexes(); + ArrayList constraints = table.getConstraints(); + for (int j = 0; indexes != null && j < indexes.size(); j++) { + Index index = indexes.get(j); + if (index.getCreateSQL() == null) { + continue; + } + String constraintName = null; + for (int k = 0; constraints != null && k < constraints.size(); k++) { + Constraint constraint = constraints.get(k); + if (constraint.usesIndex(index)) { + if (index.getIndexType().isPrimaryKey()) { + if (constraint.getConstraintType() == Constraint.Type.PRIMARY_KEY) { + constraintName = constraint.getName(); + } + } else { + constraintName = constraint.getName(); + } + } + } + IndexColumn[] cols = index.getIndexColumns(); + int uniqueColumnCount = index.getUniqueColumnCount(); + String indexClass = index.getClass().getName(); + for (int k = 0; k < cols.length; k++) { + IndexColumn idxCol = cols[k]; + Column column = idxCol.column; + add(session, + rows, + // TABLE_CATALOG + catalog, + // TABLE_SCHEMA + table.getSchema().getName(), + // TABLE_NAME + tableName, + // NON_UNIQUE + ValueBoolean.get(k >= uniqueColumnCount), + // INDEX_NAME + index.getName(), + // ORDINAL_POSITION + ValueSmallint.get((short) (k + 1)), + // COLUMN_NAME + column.getName(), + // CARDINALITY + ValueInteger.get(0), + // PRIMARY_KEY + ValueBoolean.get(index.getIndexType().isPrimaryKey()), + // INDEX_TYPE_NAME + index.getIndexType().getSQL(), + // IS_GENERATED + ValueBoolean.get(index.getIndexType().getBelongsToConstraint()), + // INDEX_TYPE + ValueSmallint.get(DatabaseMetaData.tableIndexOther), + // ASC_OR_DESC + (idxCol.sortType & SortOrder.DESCENDING) != 0 ? "D" : "A", + // PAGES + ValueInteger.get(0), + // FILTER_CONDITION + "", + // REMARKS + replaceNullWithEmpty(index.getComment()), + // SQL + index.getCreateSQL(), + // ID + ValueInteger.get(index.getId()), + // SORT_TYPE + ValueInteger.get(idxCol.sortType), + // CONSTRAINT_NAME + constraintName, + // INDEX_CLASS + indexClass + ); + } + } + } + break; + } + case TABLE_TYPES: { + add(session, rows, TableType.TABLE.toString()); + add(session, rows, TableType.TABLE_LINK.toString()); + add(session, rows, TableType.SYSTEM_TABLE.toString()); + add(session, rows, TableType.VIEW.toString()); + add(session, rows, TableType.EXTERNAL_TABLE_ENGINE.toString()); + break; + } + case TYPE_INFO: { + for (int i = 1, l = Value.TYPE_COUNT; i < l; i++) { + DataType t = DataType.getDataType(i); + add(session, + rows, + // TYPE_NAME + Value.getTypeName(t.type), + // DATA_TYPE + ValueInteger.get(t.sqlType), + // PRECISION + ValueInteger.get(MathUtils.convertLongToInt(t.maxPrecision)), + // PREFIX + t.prefix, + // SUFFIX + t.suffix, + // PARAMS + t.params, + // AUTO_INCREMENT + ValueBoolean.FALSE, + // MINIMUM_SCALE + ValueSmallint.get(MathUtils.convertIntToShort(t.minScale)), + // MAXIMUM_SCALE + ValueSmallint.get(MathUtils.convertIntToShort(t.maxScale)), + // RADIX + DataType.isNumericType(i) ? ValueInteger.get(10) : null, + // POS + ValueInteger.get(t.type), + // CASE_SENSITIVE + ValueBoolean.get(t.caseSensitive), + // NULLABLE + ValueSmallint.get((short) DatabaseMetaData.typeNullable), + // SEARCHABLE + ValueSmallint.get((short) DatabaseMetaData.typeSearchable) + ); + } + break; + } + case CATALOGS: { + add(session, rows, catalog); + break; + } + case SETTINGS: { + for (Setting s : database.getAllSettings()) { + String value = s.getStringValue(); + if (value == null) { + value = Integer.toString(s.getIntValue()); + } + add(session, + rows, + identifier(s.getName()), value + ); + } + add(session, rows, "info.BUILD_ID", "" + Constants.BUILD_ID); + add(session, rows, "info.VERSION_MAJOR", "" + Constants.VERSION_MAJOR); + add(session, rows, "info.VERSION_MINOR", "" + Constants.VERSION_MINOR); + add(session, rows, "info.VERSION", Constants.FULL_VERSION); + if (admin) { + String[] settings = { + "java.runtime.version", "java.vm.name", + "java.vendor", "os.name", "os.arch", "os.version", + "sun.os.patch.level", "file.separator", + "path.separator", "line.separator", "user.country", + "user.language", "user.variant", "file.encoding" }; + for (String s : settings) { + add(session, rows, "property." + s, Utils.getProperty(s, "")); + } + } + add(session, rows, "DEFAULT_NULL_ORDERING", database.getDefaultNullOrdering().name()); + add(session, rows, "EXCLUSIVE", database.getExclusiveSession() == null ? + "FALSE" : "TRUE"); + add(session, rows, "MODE", database.getMode().getName()); + add(session, rows, "QUERY_TIMEOUT", Integer.toString(session.getQueryTimeout())); + add(session, rows, "TIME ZONE", session.currentTimeZone().getId()); + add(session, rows, "TRUNCATE_LARGE_LENGTH", session.isTruncateLargeLength() ? "TRUE" : "FALSE"); + add(session, rows, "VARIABLE_BINARY", session.isVariableBinary() ? "TRUE" : "FALSE"); + add(session, rows, "OLD_INFORMATION_SCHEMA", session.isOldInformationSchema() ? "TRUE" : "FALSE"); + BitSet nonKeywords = session.getNonKeywords(); + if (nonKeywords != null) { + add(session, rows, "NON_KEYWORDS", Parser.formatNonKeywords(nonKeywords)); + } + add(session, rows, "RETENTION_TIME", Integer.toString(database.getRetentionTime())); + // database settings + for (Map.Entry entry : database.getSettings().getSortedSettings()) { + add(session, rows, entry.getKey(), entry.getValue()); + } + Store store = database.getStore(); + MVStore mvStore = store.getMvStore(); + FileStore fs = mvStore.getFileStore(); + if (fs != null) { + add(session, rows, + "info.FILE_WRITE", Long.toString(fs.getWriteCount())); + add(session, rows, + "info.FILE_WRITE_BYTES", Long.toString(fs.getWriteBytes())); + add(session, rows, + "info.FILE_READ", Long.toString(fs.getReadCount())); + add(session, rows, + "info.FILE_READ_BYTES", Long.toString(fs.getReadBytes())); + add(session, rows, + "info.UPDATE_FAILURE_PERCENT", + String.format(Locale.ENGLISH, "%.2f%%", 100 * mvStore.getUpdateFailureRatio())); + add(session, rows, + "info.FILL_RATE", Integer.toString(mvStore.getFillRate())); + add(session, rows, + "info.CHUNKS_FILL_RATE", Integer.toString(mvStore.getChunksFillRate())); + add(session, rows, + "info.CHUNKS_FILL_RATE_RW", Integer.toString(mvStore.getRewritableChunksFillRate())); + try { + add(session, rows, + "info.FILE_SIZE", Long.toString(fs.getFile().size())); + } catch (IOException ignore) {/**/} + add(session, rows, + "info.CHUNK_COUNT", Long.toString(mvStore.getChunkCount())); + add(session, rows, + "info.PAGE_COUNT", Long.toString(mvStore.getPageCount())); + add(session, rows, + "info.PAGE_COUNT_LIVE", Long.toString(mvStore.getLivePageCount())); + add(session, rows, + "info.PAGE_SIZE", Integer.toString(mvStore.getPageSplitSize())); + add(session, rows, + "info.CACHE_MAX_SIZE", Integer.toString(mvStore.getCacheSize())); + add(session, rows, + "info.CACHE_SIZE", Integer.toString(mvStore.getCacheSizeUsed())); + add(session, rows, + "info.CACHE_HIT_RATIO", Integer.toString(mvStore.getCacheHitRatio())); + add(session, rows, "info.TOC_CACHE_HIT_RATIO", + Integer.toString(mvStore.getTocCacheHitRatio())); + add(session, rows, + "info.LEAF_RATIO", Integer.toString(mvStore.getLeafRatio())); + } + break; + } + case HELP: { + String resource = "/org/h2/res/help.csv"; + try { + final byte[] data = Utils.getResource(resource); + 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); + final int columnCount = rs.getMetaData().getColumnCount() - 1; + final String[] values = new String[5]; + for (int i = 0; rs.next(); i++) { + for (int j = 0; j < columnCount; j++) { + String s = rs.getString(1 + j); + switch (j) { + case 2: // SYNTAX column + // Strip out the special annotations we use to help build + // the railroad/BNF diagrams + s = Help.stripAnnotationsFromSyntax(s); + break; + case 3: // TEXT column + s = Help.processHelpText(s); + } + values[j] = s.trim(); + } + add(session, + rows, + // ID + ValueInteger.get(i), + // SECTION + values[0], + // TOPIC + values[1], + // SYNTAX + values[2], + // TEXT + values[3] + ); + } + } catch (Exception e) { + throw DbException.convert(e); + } + break; + } + case SEQUENCES: { + for (SchemaObject obj : getAllSchemaObjects(DbObject.SEQUENCE)) { + Sequence s = (Sequence) obj; + TypeInfo dataType = s.getDataType(); + String dataTypeName = Value.getTypeName(dataType.getValueType()); + ValueInteger declaredScale = ValueInteger.get(dataType.getScale()); + add(session, + rows, + // SEQUENCE_CATALOG + catalog, + // SEQUENCE_SCHEMA + s.getSchema().getName(), + // SEQUENCE_NAME + s.getName(), + // DATA_TYPE + dataTypeName, + // NUMERIC_PRECISION + ValueInteger.get(s.getEffectivePrecision()), + // NUMERIC_PRECISION_RADIX + ValueInteger.get(10), + // NUMERIC_SCALE + declaredScale, + // START_VALUE + ValueBigint.get(s.getStartValue()), + // MINIMUM_VALUE + ValueBigint.get(s.getMinValue()), + // MAXIMUM_VALUE + ValueBigint.get(s.getMaxValue()), + // INCREMENT + ValueBigint.get(s.getIncrement()), + // CYCLE_OPTION + s.getCycle().isCycle() ? "YES" : "NO", + // DECLARED_DATA_TYPE + dataTypeName, + // DECLARED_NUMERIC_PRECISION + ValueInteger.get((int) dataType.getPrecision()), + // DECLARED_NUMERIC_SCALE + declaredScale, + // CURRENT_VALUE + ValueBigint.get(s.getCurrentValue()), + // IS_GENERATED + ValueBoolean.get(s.getBelongsToTable()), + // REMARKS + replaceNullWithEmpty(s.getComment()), + // CACHE + ValueBigint.get(s.getCacheSize()), + // ID + ValueInteger.get(s.getId()), + // MIN_VALUE + ValueBigint.get(s.getMinValue()), + // MAX_VALUE + ValueBigint.get(s.getMaxValue()), + // IS_CYCLE + ValueBoolean.get(s.getCycle().isCycle()) + ); + } + break; + } + case USERS: { + for (RightOwner rightOwner : database.getAllUsersAndRoles()) { + if (rightOwner instanceof User) { + User u = (User) rightOwner; + if (admin || session.getUser() == u) { + add(session, + rows, + // NAME + identifier(u.getName()), + // ADMIN + String.valueOf(u.isAdmin()), + // REMARKS + replaceNullWithEmpty(u.getComment()), + // ID + ValueInteger.get(u.getId()) + ); + } + } + } + break; + } + case ROLES: { + for (RightOwner rightOwner : database.getAllUsersAndRoles()) { + if (rightOwner instanceof Role) { + Role r = (Role) rightOwner; + if (admin || session.getUser().isRoleGranted(r)) { + add(session, + rows, + // NAME + identifier(r.getName()), + // REMARKS + replaceNullWithEmpty(r.getComment()), + // ID + ValueInteger.get(r.getId()) + ); + } + } + } + break; + } + case RIGHTS: { + if (admin) { + for (Right r : database.getAllRights()) { + Role role = r.getGrantedRole(); + DbObject grantee = r.getGrantee(); + String rightType = grantee.getType() == DbObject.USER ? "USER" : "ROLE"; + if (role == null) { + DbObject object = r.getGrantedObject(); + Schema schema = null; + Table table = null; + if (object != null) { + if (object instanceof Schema) { + schema = (Schema) object; + } else if (object instanceof Table) { + table = (Table) object; + schema = table.getSchema(); + } + } + String tableName = (table != null) ? table.getName() : ""; + String schemaName = (schema != null) ? schema.getName() : ""; + if (!checkIndex(session, tableName, indexFrom, indexTo)) { + continue; + } + add(session, + rows, + // GRANTEE + identifier(grantee.getName()), + // GRANTEETYPE + rightType, + // GRANTEDROLE + "", + // RIGHTS + r.getRights(), + // TABLE_SCHEMA + schemaName, + // TABLE_NAME + tableName, + // ID + ValueInteger.get(r.getId()) + ); + } else { + add(session, + rows, + // GRANTEE + identifier(grantee.getName()), + // GRANTEETYPE + rightType, + // GRANTEDROLE + identifier(role.getName()), + // RIGHTS + "", + // TABLE_SCHEMA + "", + // TABLE_NAME + "", + // ID + ValueInteger.get(r.getId()) + ); + } + } + } + break; + } + case FUNCTION_ALIASES: + for (Schema schema : database.getAllSchemas()) { + for (UserDefinedFunction userDefinedFunction : schema.getAllFunctionsAndAggregates()) { + if (userDefinedFunction instanceof FunctionAlias) { + FunctionAlias alias = (FunctionAlias) userDefinedFunction; + JavaMethod[] methods; + try { + methods = alias.getJavaMethods(); + } catch (DbException e) { + continue; + } + for (FunctionAlias.JavaMethod method : methods) { + TypeInfo typeInfo = method.getDataType(); + if (typeInfo == null) { + typeInfo = TypeInfo.TYPE_NULL; + } + add(session, + rows, + // ALIAS_CATALOG + catalog, + // ALIAS_SCHEMA + alias.getSchema().getName(), + // ALIAS_NAME + alias.getName(), + // JAVA_CLASS + alias.getJavaClassName(), + // JAVA_METHOD + alias.getJavaMethodName(), + // DATA_TYPE + ValueInteger.get(DataType.convertTypeToSQLType(typeInfo)), + // TYPE_NAME + typeInfo.getDeclaredTypeName(), + // COLUMN_COUNT + ValueInteger.get(method.getParameterCount()), + // RETURNS_RESULT + ValueSmallint.get(typeInfo.getValueType() == Value.NULL + ? (short) DatabaseMetaData.procedureNoResult + : (short) DatabaseMetaData.procedureReturnsResult), + // REMARKS + replaceNullWithEmpty(alias.getComment()), + // ID + ValueInteger.get(alias.getId()), + // SOURCE + alias.getSource() + // when adding more columns, see also below + ); + } + } else { + add(session, + rows, + // ALIAS_CATALOG + catalog, + // ALIAS_SCHEMA + database.getMainSchema().getName(), + // ALIAS_NAME + userDefinedFunction.getName(), + // JAVA_CLASS + userDefinedFunction.getJavaClassName(), + // JAVA_METHOD + "", + // DATA_TYPE + ValueInteger.get(Types.NULL), + // TYPE_NAME + "NULL", + // COLUMN_COUNT + ValueInteger.get(1), + // RETURNS_RESULT + ValueSmallint.get((short) DatabaseMetaData.procedureReturnsResult), + // REMARKS + replaceNullWithEmpty(userDefinedFunction.getComment()), + // ID + ValueInteger.get(userDefinedFunction.getId()), + // SOURCE + "" + // when adding more columns, see also below + ); + } + } + } + break; + case FUNCTION_COLUMNS: + for (Schema schema : database.getAllSchemas()) { + for (UserDefinedFunction userDefinedFunction : schema.getAllFunctionsAndAggregates()) { + if (userDefinedFunction instanceof FunctionAlias) { + FunctionAlias alias = (FunctionAlias) userDefinedFunction; + JavaMethod[] methods; + try { + methods = alias.getJavaMethods(); + } catch (DbException e) { + continue; + } + for (FunctionAlias.JavaMethod method : methods) { + // Add return column index 0 + TypeInfo typeInfo = method.getDataType(); + if (typeInfo != null && typeInfo.getValueType() != Value.NULL) { + DataType dt = DataType.getDataType(typeInfo.getValueType()); + add(session, + rows, + // ALIAS_CATALOG + catalog, + // ALIAS_SCHEMA + alias.getSchema().getName(), + // ALIAS_NAME + alias.getName(), + // JAVA_CLASS + alias.getJavaClassName(), + // JAVA_METHOD + alias.getJavaMethodName(), + // COLUMN_COUNT + ValueInteger.get(method.getParameterCount()), + // POS + ValueInteger.get(0), + // COLUMN_NAME + "P0", + // DATA_TYPE + ValueInteger.get(DataType.convertTypeToSQLType(typeInfo)), + // TYPE_NAME + typeInfo.getDeclaredTypeName(), + // PRECISION + ValueInteger.get(MathUtils.convertLongToInt(dt.defaultPrecision)), + // SCALE + ValueSmallint.get(MathUtils.convertIntToShort(dt.defaultScale)), + // RADIX + ValueSmallint.get((short) 10), + // NULLABLE + ValueSmallint.get((short) DatabaseMetaData.columnNullableUnknown), + // COLUMN_TYPE + ValueSmallint.get((short) DatabaseMetaData.procedureColumnReturn), + // REMARKS + "", + // COLUMN_DEFAULT + null + ); + } + Class[] columnList = method.getColumnClasses(); + for (int k = 0; k < columnList.length; k++) { + if (method.hasConnectionParam() && k == 0) { + continue; + } + Class clazz = columnList[k]; + TypeInfo columnTypeInfo = ValueToObjectConverter2.classToType(clazz); + DataType dt = DataType.getDataType(columnTypeInfo.getValueType()); + add(session, + rows, + // ALIAS_CATALOG + catalog, + // ALIAS_SCHEMA + alias.getSchema().getName(), + // ALIAS_NAME + alias.getName(), + // JAVA_CLASS + alias.getJavaClassName(), + // JAVA_METHOD + alias.getJavaMethodName(), + // COLUMN_COUNT + ValueInteger.get(method.getParameterCount()), + // POS + ValueInteger.get(k + (method.hasConnectionParam() ? 0 : 1)), + // COLUMN_NAME + "P" + (k + 1), + // DATA_TYPE + ValueInteger.get(DataType.convertTypeToSQLType(columnTypeInfo)), + // TYPE_NAME + columnTypeInfo.getDeclaredTypeName(), + // PRECISION + ValueInteger.get(MathUtils.convertLongToInt(dt.defaultPrecision)), + // SCALE + ValueSmallint.get(MathUtils.convertIntToShort(dt.defaultScale)), + // RADIX + ValueSmallint.get((short) 10), + // NULLABLE + ValueSmallint.get(clazz.isPrimitive() + ? (short) DatabaseMetaData.columnNoNulls + : (short) DatabaseMetaData.columnNullable), + // COLUMN_TYPE + ValueSmallint.get((short) DatabaseMetaData.procedureColumnIn), + // REMARKS + "", + // COLUMN_DEFAULT + null + ); + } + } + } + } + } + break; + case SCHEMATA: { + String collation = database.getCompareMode().getName(); + for (Schema schema : database.getAllSchemas()) { + add(session, + rows, + // CATALOG_NAME + catalog, + // SCHEMA_NAME + schema.getName(), + // SCHEMA_OWNER + identifier(schema.getOwner().getName()), + // DEFAULT_CHARACTER_SET_NAME + CHARACTER_SET_NAME, + // DEFAULT_COLLATION_NAME + collation, + // IS_DEFAULT + ValueBoolean.get(schema.getId() == Constants.MAIN_SCHEMA_ID), + // REMARKS + replaceNullWithEmpty(schema.getComment()), + // ID + ValueInteger.get(schema.getId()) + ); + } + break; + } + case TABLE_PRIVILEGES: { + for (Right r : database.getAllRights()) { + DbObject object = r.getGrantedObject(); + if (!(object instanceof Table)) { + continue; + } + Table table = (Table) object; + if (hideTable(table, session)) { + continue; + } + String tableName = table.getName(); + if (!checkIndex(session, tableName, indexFrom, indexTo)) { + continue; + } + addPrivileges(session, rows, r.getGrantee(), catalog, table, null, r.getRightMask()); + } + break; + } + case COLUMN_PRIVILEGES: { + for (Right r : database.getAllRights()) { + DbObject object = r.getGrantedObject(); + if (!(object instanceof Table)) { + continue; + } + Table table = (Table) object; + if (hideTable(table, session)) { + continue; + } + String tableName = table.getName(); + if (!checkIndex(session, tableName, indexFrom, indexTo)) { + continue; + } + DbObject grantee = r.getGrantee(); + int mask = r.getRightMask(); + for (Column column : table.getColumns()) { + addPrivileges(session, rows, grantee, catalog, table, column.getName(), mask); + } + } + break; + } + case COLLATIONS: { + for (Locale l : CompareMode.getCollationLocales(false)) { + add(session, + rows, + // NAME + CompareMode.getName(l), // KEY + l.toString() + ); + } + break; + } + case VIEWS: { + for (Table table : getAllTables(session)) { + if (table.getTableType() != TableType.VIEW) { + continue; + } + String tableName = table.getName(); + if (!checkIndex(session, tableName, indexFrom, indexTo)) { + continue; + } + TableView view = (TableView) table; + add(session, + rows, + // TABLE_CATALOG + catalog, + // TABLE_SCHEMA + table.getSchema().getName(), + // TABLE_NAME + tableName, + // VIEW_DEFINITION + table.getCreateSQL(), + // CHECK_OPTION + "NONE", + // IS_UPDATABLE + "NO", + // STATUS + view.isInvalid() ? "INVALID" : "VALID", + // REMARKS + replaceNullWithEmpty(view.getComment()), + // ID + ValueInteger.get(view.getId()) + ); + } + break; + } + case IN_DOUBT: { + ArrayList prepared = database.getInDoubtTransactions(); + if (prepared != null && admin) { + for (InDoubtTransaction prep : prepared) { + add(session, + rows, + // TRANSACTION + prep.getTransactionName(), // STATE + prep.getStateDescription() + ); + } + } + break; + } + case CROSS_REFERENCES: { + for (SchemaObject obj : getAllSchemaObjects( + DbObject.CONSTRAINT)) { + Constraint constraint = (Constraint) obj; + if (constraint.getConstraintType() != Constraint.Type.REFERENTIAL) { + continue; + } + ConstraintReferential ref = (ConstraintReferential) constraint; + IndexColumn[] cols = ref.getColumns(); + IndexColumn[] refCols = ref.getRefColumns(); + Table tab = ref.getTable(); + Table refTab = ref.getRefTable(); + String tableName = refTab.getName(); + if (!checkIndex(session, tableName, indexFrom, indexTo)) { + continue; + } + ValueSmallint update = ValueSmallint.get(getRefAction(ref.getUpdateAction())); + ValueSmallint delete = ValueSmallint.get(getRefAction(ref.getDeleteAction())); + for (int j = 0; j < cols.length; j++) { + add(session, + rows, + // PKTABLE_CATALOG + catalog, + // PKTABLE_SCHEMA + refTab.getSchema().getName(), + // PKTABLE_NAME + refTab.getName(), + // PKCOLUMN_NAME + refCols[j].column.getName(), + // FKTABLE_CATALOG + catalog, + // FKTABLE_SCHEMA + tab.getSchema().getName(), + // FKTABLE_NAME + tab.getName(), + // FKCOLUMN_NAME + cols[j].column.getName(), + // ORDINAL_POSITION + ValueSmallint.get((short) (j + 1)), + // UPDATE_RULE + update, + // DELETE_RULE + delete, + // FK_NAME + ref.getName(), + // PK_NAME + ref.getReferencedConstraint().getName(), + // DEFERRABILITY + ValueSmallint.get((short) DatabaseMetaData.importedKeyNotDeferrable) + ); + } + } + break; + } + case CONSTRAINTS: { + for (SchemaObject obj : getAllSchemaObjects( + DbObject.CONSTRAINT)) { + Constraint constraint = (Constraint) obj; + Constraint.Type constraintType = constraint.getConstraintType(); + String checkExpression = null; + IndexColumn[] indexColumns = null; + Table table = constraint.getTable(); + if (hideTable(table, session)) { + continue; + } + Index index = constraint.getIndex(); + String uniqueIndexName = null; + if (index != null) { + uniqueIndexName = index.getName(); + } + String tableName = table.getName(); + if (!checkIndex(session, tableName, indexFrom, indexTo)) { + continue; + } + if (constraintType == Constraint.Type.CHECK) { + checkExpression = ((ConstraintCheck) constraint).getExpression().getSQL(HasSQL.DEFAULT_SQL_FLAGS); + } else if (constraintType == Constraint.Type.UNIQUE || + constraintType == Constraint.Type.PRIMARY_KEY) { + indexColumns = ((ConstraintUnique) constraint).getColumns(); + } else if (constraintType == Constraint.Type.REFERENTIAL) { + indexColumns = ((ConstraintReferential) constraint).getColumns(); + } + String columnList = null; + if (indexColumns != null) { + StringBuilder builder = new StringBuilder(); + for (int i = 0, length = indexColumns.length; i < length; i++) { + if (i > 0) { + builder.append(','); + } + builder.append(indexColumns[i].column.getName()); + } + columnList = builder.toString(); + } + add(session, + rows, + // CONSTRAINT_CATALOG + catalog, + // CONSTRAINT_SCHEMA + constraint.getSchema().getName(), + // CONSTRAINT_NAME + constraint.getName(), + // CONSTRAINT_TYPE + constraintType == Constraint.Type.PRIMARY_KEY ? + constraintType.getSqlName() : constraintType.name(), + // TABLE_CATALOG + catalog, + // TABLE_SCHEMA + table.getSchema().getName(), + // TABLE_NAME + tableName, + // UNIQUE_INDEX_NAME + uniqueIndexName, + // CHECK_EXPRESSION + checkExpression, + // COLUMN_LIST + columnList, + // REMARKS + replaceNullWithEmpty(constraint.getComment()), + // SQL + constraint.getCreateSQL(), + // ID + ValueInteger.get(constraint.getId()) + ); + } + break; + } + case CONSTANTS: { + for (SchemaObject obj : getAllSchemaObjects( + DbObject.CONSTANT)) { + Constant constant = (Constant) obj; + ValueExpression expr = constant.getValue(); + add(session, + rows, + // CONSTANT_CATALOG + catalog, + // CONSTANT_SCHEMA + constant.getSchema().getName(), + // CONSTANT_NAME + constant.getName(), + // DATA_TYPE + ValueInteger.get(DataType.convertTypeToSQLType(expr.getType())), + // REMARKS + replaceNullWithEmpty(constant.getComment()), + // SQL + expr.getSQL(DEFAULT_SQL_FLAGS), + // ID + ValueInteger.get(constant.getId()) + ); + } + break; + } + case DOMAINS: { + for (SchemaObject obj : getAllSchemaObjects(DbObject.DOMAIN)) { + Domain domain = (Domain) obj; + Domain parentDomain = domain.getDomain(); + TypeInfo typeInfo = domain.getDataType(); + add(session, + rows, + // DOMAIN_CATALOG + catalog, + // DOMAIN_SCHEMA + domain.getSchema().getName(), + // DOMAIN_NAME + domain.getName(), + // DOMAIN_DEFAULT + domain.getDefaultSQL(), + // DOMAIN_ON_UPDATE + domain.getOnUpdateSQL(), + // DATA_TYPE + ValueInteger.get(DataType.convertTypeToSQLType(typeInfo)), + // PRECISION + ValueInteger.get(MathUtils.convertLongToInt(typeInfo.getPrecision())), + // SCALE + ValueInteger.get(typeInfo.getScale()), + // TYPE_NAME + typeInfo.getDeclaredTypeName(), + // PARENT_DOMAIN_CATALOG + parentDomain != null ? catalog : null, + // PARENT_DOMAIN_SCHEMA + parentDomain != null ? parentDomain.getSchema().getName() : null, + // PARENT_DOMAIN_NAME + parentDomain != null ? parentDomain.getName() : null, + // SELECTIVITY INT + ValueInteger.get(Constants.SELECTIVITY_DEFAULT), + // REMARKS + replaceNullWithEmpty(domain.getComment()), + // SQL + domain.getCreateSQL(), + // ID + ValueInteger.get(domain.getId()), + // COLUMN_DEFAULT + domain.getDefaultSQL(), + // IS_NULLABLE + "YES", + // CHECK_CONSTRAINT + null + ); + } + break; + } + case TRIGGERS: { + for (SchemaObject obj : getAllSchemaObjects( + DbObject.TRIGGER)) { + TriggerObject trigger = (TriggerObject) obj; + Table table = trigger.getTable(); + add(session, + rows, + // TRIGGER_CATALOG + catalog, + // TRIGGER_SCHEMA + trigger.getSchema().getName(), + // TRIGGER_NAME + trigger.getName(), + // TRIGGER_TYPE + trigger.getTypeNameList(new StringBuilder()).toString(), + // TABLE_CATALOG + catalog, + // TABLE_SCHEMA + table.getSchema().getName(), + // TABLE_NAME + table.getName(), + // BEFORE + ValueBoolean.get(trigger.isBefore()), + // JAVA_CLASS + trigger.getTriggerClassName(), + // QUEUE_SIZE + ValueInteger.get(trigger.getQueueSize()), + // NO_WAIT + ValueBoolean.get(trigger.isNoWait()), + // REMARKS + replaceNullWithEmpty(trigger.getComment()), + // SQL + trigger.getCreateSQL(), + // ID + ValueInteger.get(trigger.getId()) + ); + } + break; + } + case SESSIONS: { + for (SessionLocal s : database.getSessions(false)) { + if (admin || s == session) { + NetworkConnectionInfo networkConnectionInfo = s.getNetworkConnectionInfo(); + Command command = s.getCurrentCommand(); + int blockingSessionId = s.getBlockingSessionId(); + add(session, + rows, + // ID + ValueInteger.get(s.getId()), + // USER_NAME + s.getUser().getName(), + // SERVER + networkConnectionInfo == null ? null : networkConnectionInfo.getServer(), + // CLIENT_ADDR + networkConnectionInfo == null ? null : networkConnectionInfo.getClient(), + // CLIENT_INFO + networkConnectionInfo == null ? null : networkConnectionInfo.getClientInfo(), + // SESSION_START + s.getSessionStart(), + // ISOLATION_LEVEL + session.getIsolationLevel().getSQL(), + // STATEMENT + command == null ? null : command.toString(), + // STATEMENT_START + command == null ? null : s.getCommandStartOrEnd(), + // CONTAINS_UNCOMMITTED + ValueBoolean.get(s.hasPendingTransaction()), + // STATE + String.valueOf(s.getState()), + // BLOCKER_ID + blockingSessionId == 0 ? null : ValueInteger.get(blockingSessionId), + // SLEEP_SINCE + s.getState() == State.SLEEP ? s.getCommandStartOrEnd() : null + ); + } + } + break; + } + case LOCKS: { + for (SessionLocal s : database.getSessions(false)) { + if (admin || s == session) { + for (Table table : s.getLocks()) { + add(session, + rows, + // TABLE_SCHEMA + table.getSchema().getName(), + // TABLE_NAME + table.getName(), + // SESSION_ID + ValueInteger.get(s.getId()), + // LOCK_TYPE + table.isLockedExclusivelyBy(s) ? "WRITE" : "READ" + ); + } + } + } + break; + } + case SESSION_STATE: { + for (String name : session.getVariableNames()) { + Value v = session.getVariable(name); + StringBuilder builder = new StringBuilder().append("SET @").append(name).append(' '); + v.getSQL(builder, DEFAULT_SQL_FLAGS); + add(session, + rows, + // KEY + "@" + name, + // SQL + builder.toString() + ); + } + for (Table table : session.getLocalTempTables()) { + add(session, + rows, + // KEY + "TABLE " + table.getName(), + // SQL + table.getCreateSQL() + ); + } + String[] path = session.getSchemaSearchPath(); + if (path != null && path.length > 0) { + StringBuilder builder = new StringBuilder("SET SCHEMA_SEARCH_PATH "); + for (int i = 0, l = path.length; i < l; i++) { + if (i > 0) { + builder.append(", "); + } + StringUtils.quoteIdentifier(builder, path[i]); + } + add(session, + rows, + // KEY + "SCHEMA_SEARCH_PATH", + // SQL + builder.toString() + ); + } + String schema = session.getCurrentSchemaName(); + if (schema != null) { + add(session, + rows, + // KEY + "SCHEMA", + // SQL + StringUtils.quoteIdentifier(new StringBuilder("SET SCHEMA "), schema).toString() + ); + } + TimeZoneProvider currentTimeZone = session.currentTimeZone(); + if (!currentTimeZone.equals(DateTimeUtils.getTimeZone())) { + add(session, + rows, + // KEY + "TIME ZONE", + // SQL + StringUtils.quoteStringSQL(new StringBuilder("SET TIME ZONE "), currentTimeZone.getId()) + .toString() + ); + } + break; + } + case QUERY_STATISTICS: { + QueryStatisticsData control = database.getQueryStatisticsData(); + if (control != null) { + for (QueryStatisticsData.QueryEntry entry : control.getQueries()) { + add(session, + rows, + // SQL_STATEMENT + entry.sqlStatement, + // EXECUTION_COUNT + ValueInteger.get(entry.count), + // MIN_EXECUTION_TIME + ValueDouble.get(entry.executionTimeMinNanos / 1_000_000d), + // MAX_EXECUTION_TIME + ValueDouble.get(entry.executionTimeMaxNanos / 1_000_000d), + // CUMULATIVE_EXECUTION_TIME + ValueDouble.get(entry.executionTimeCumulativeNanos / 1_000_000d), + // AVERAGE_EXECUTION_TIME + ValueDouble.get(entry.executionTimeMeanNanos / 1_000_000d), + // STD_DEV_EXECUTION_TIME + ValueDouble.get(entry.getExecutionTimeStandardDeviation() / 1_000_000d), + // MIN_ROW_COUNT + ValueBigint.get(entry.rowCountMin), + // MAX_ROW_COUNT + ValueBigint.get(entry.rowCountMax), + // CUMULATIVE_ROW_COUNT + ValueBigint.get(entry.rowCountCumulative), + // AVERAGE_ROW_COUNT + ValueDouble.get(entry.rowCountMean), + // STD_DEV_ROW_COUNT + ValueDouble.get(entry.getRowCountStandardDeviation()) + ); + } + } + break; + } + case SYNONYMS: { + for (TableSynonym synonym : database.getAllSynonyms()) { + add(session, + rows, + // SYNONYM_CATALOG + catalog, + // SYNONYM_SCHEMA + synonym.getSchema().getName(), + // SYNONYM_NAME + synonym.getName(), + // SYNONYM_FOR + synonym.getSynonymForName(), + // SYNONYM_FOR_SCHEMA + synonym.getSynonymForSchema().getName(), + // TYPE NAME + "SYNONYM", + // STATUS + "VALID", + // REMARKS + replaceNullWithEmpty(synonym.getComment()), + // ID + ValueInteger.get(synonym.getId()) + ); + } + break; + } + case TABLE_CONSTRAINTS: { + for (SchemaObject obj : getAllSchemaObjects(DbObject.CONSTRAINT)) { + Constraint constraint = (Constraint) obj; + Constraint.Type constraintType = constraint.getConstraintType(); + if (constraintType == Constraint.Type.DOMAIN) { + continue; + } + Table table = constraint.getTable(); + if (hideTable(table, session)) { + continue; + } + String tableName = table.getName(); + if (!checkIndex(session, tableName, indexFrom, indexTo)) { + continue; + } + add(session, + rows, + // CONSTRAINT_CATALOG + catalog, + // CONSTRAINT_SCHEMA + constraint.getSchema().getName(), + // CONSTRAINT_NAME + constraint.getName(), + // CONSTRAINT_TYPE + constraintType.getSqlName(), + // TABLE_CATALOG + catalog, + // TABLE_SCHEMA + table.getSchema().getName(), + // TABLE_NAME + tableName, + // IS_DEFERRABLE + "NO", + // INITIALLY_DEFERRED + "NO", + // REMARKS + replaceNullWithEmpty(constraint.getComment()), + // SQL + constraint.getCreateSQL(), + // ID + ValueInteger.get(constraint.getId()) + ); + } + break; + } + case DOMAIN_CONSTRAINTS: { + for (SchemaObject obj : getAllSchemaObjects(DbObject.CONSTRAINT)) { + if (((Constraint) obj).getConstraintType() != Constraint.Type.DOMAIN) { + continue; + } + ConstraintDomain constraint = (ConstraintDomain) obj; + Domain domain = constraint.getDomain(); + add(session, + rows, + // CONSTRAINT_CATALOG + catalog, + // CONSTRAINT_SCHEMA + constraint.getSchema().getName(), + // CONSTRAINT_NAME + constraint.getName(), + // DOMAIN_CATALOG + catalog, + // DOMAIN_SCHEMA + domain.getSchema().getName(), + // DOMAIN_NAME + domain.getName(), + // IS_DEFERRABLE + "NO", + // INITIALLY_DEFERRED + "NO", + // REMARKS + replaceNullWithEmpty(constraint.getComment()), + // SQL + constraint.getCreateSQL(), + // ID + ValueInteger.get(constraint.getId()) + ); + } + break; + } + case KEY_COLUMN_USAGE: { + for (SchemaObject obj : getAllSchemaObjects(DbObject.CONSTRAINT)) { + Constraint constraint = (Constraint) obj; + Constraint.Type constraintType = constraint.getConstraintType(); + IndexColumn[] indexColumns = null; + if (constraintType == Constraint.Type.UNIQUE || constraintType == Constraint.Type.PRIMARY_KEY) { + indexColumns = ((ConstraintUnique) constraint).getColumns(); + } else if (constraintType == Constraint.Type.REFERENTIAL) { + indexColumns = ((ConstraintReferential) constraint).getColumns(); + } + if (indexColumns == null) { + continue; + } + Table table = constraint.getTable(); + if (hideTable(table, session)) { + continue; + } + String tableName = table.getName(); + if (!checkIndex(session, tableName, indexFrom, indexTo)) { + continue; + } + ConstraintUnique referenced; + if (constraintType == Constraint.Type.REFERENTIAL) { + referenced = ((ConstraintReferential) constraint).getReferencedConstraint(); + } else { + referenced = null; + } + Index index = constraint.getIndex(); + for (int i = 0; i < indexColumns.length; i++) { + IndexColumn indexColumn = indexColumns[i]; + ValueInteger ordinalPosition = ValueInteger.get(i + 1); + ValueInteger positionInUniqueConstraint = null; + if (referenced != null) { + Column c = ((ConstraintReferential) constraint).getRefColumns()[i].column; + IndexColumn[] refColumns = referenced.getColumns(); + for (int j = 0; j < refColumns.length; j++) { + if (refColumns[j].column.equals(c)) { + positionInUniqueConstraint = ValueInteger.get(j + 1); + break; + } + } + } + add(session, + rows, + // CONSTRAINT_CATALOG + catalog, + // CONSTRAINT_SCHEMA + constraint.getSchema().getName(), + // CONSTRAINT_NAME + constraint.getName(), + // TABLE_CATALOG + catalog, + // TABLE_SCHEMA + table.getSchema().getName(), + // TABLE_NAME + tableName, + // COLUMN_NAME + indexColumn.columnName, + // ORDINAL_POSITION + ordinalPosition, + // POSITION_IN_UNIQUE_CONSTRAINT + positionInUniqueConstraint, + // INDEX_CATALOG + index != null ? catalog : null, + // INDEX_SCHEMA + index != null ? index.getSchema().getName() : null, + // INDEX_NAME + index != null ? index.getName() : null + ); + } + } + break; + } + case REFERENTIAL_CONSTRAINTS: { + for (SchemaObject obj : getAllSchemaObjects(DbObject.CONSTRAINT)) { + if (((Constraint) obj).getConstraintType() != Constraint.Type.REFERENTIAL) { + continue; + } + ConstraintReferential constraint = (ConstraintReferential) obj; + Table table = constraint.getTable(); + if (hideTable(table, session)) { + continue; + } + ConstraintUnique unique = constraint.getReferencedConstraint(); + add(session, + rows, + // CONSTRAINT_CATALOG + catalog, + // CONSTRAINT_SCHEMA + constraint.getSchema().getName(), + // CONSTRAINT_NAME + constraint.getName(), + // UNIQUE_CONSTRAINT_CATALOG + catalog, + // UNIQUE_CONSTRAINT_SCHEMA + unique.getSchema().getName(), + // UNIQUE_CONSTRAINT_NAME + unique.getName(), + // MATCH_OPTION + "NONE", + // UPDATE_RULE + constraint.getUpdateAction().getSqlName(), + // DELETE_RULE + constraint.getDeleteAction().getSqlName() + ); + } + break; + } + case CHECK_CONSTRAINTS: { + for (SchemaObject obj : getAllSchemaObjects(DbObject.CONSTRAINT)) { + Constraint constraint = (Constraint) obj; + Type constraintType = constraint.getConstraintType(); + if (constraintType == Constraint.Type.CHECK) { + ConstraintCheck check = (ConstraintCheck) obj; + Table table = check.getTable(); + if (hideTable(table, session)) { + continue; + } + } else if (constraintType != Constraint.Type.DOMAIN) { + continue; + } + add(session, + rows, + // CONSTRAINT_CATALOG + catalog, + // CONSTRAINT_SCHEMA + obj.getSchema().getName(), + // CONSTRAINT_NAME + obj.getName(), + // CHECK_CLAUSE + constraint.getExpression().getSQL(DEFAULT_SQL_FLAGS, Expression.WITHOUT_PARENTHESES) + ); + } + break; + } + case CONSTRAINT_COLUMN_USAGE: { + for (SchemaObject obj : getAllSchemaObjects(DbObject.CONSTRAINT)) { + Constraint constraint = (Constraint) obj; + switch (constraint.getConstraintType()) { + case CHECK: + case DOMAIN: { + HashSet columns = new HashSet<>(); + constraint.getExpression().isEverything(ExpressionVisitor.getColumnsVisitor(columns, null)); + for (Column column: columns) { + Table table = column.getTable(); + if (checkIndex(session, table.getName(), indexFrom, indexTo) && !hideTable(table, session)) { + addConstraintColumnUsage(session, rows, catalog, constraint, column); + } + } + break; + } + case REFERENTIAL: { + Table table = constraint.getRefTable(); + if (checkIndex(session, table.getName(), indexFrom, indexTo) && !hideTable(table, session)) { + for (Column column : constraint.getReferencedColumns(table)) { + addConstraintColumnUsage(session, rows, catalog, constraint, column); + } + } + } + //$FALL-THROUGH$ + case PRIMARY_KEY: + case UNIQUE: { + Table table = constraint.getTable(); + if (checkIndex(session, table.getName(), indexFrom, indexTo) && !hideTable(table, session)) { + for (Column column : constraint.getReferencedColumns(table)) { + addConstraintColumnUsage(session, rows, catalog, constraint, column); + } + } + } + } + } + break; + } + default: + throw DbException.getInternalError("type=" + type); + } + return rows; + } + + private static short getRefAction(ConstraintActionType action) { + switch (action) { + case CASCADE: + return DatabaseMetaData.importedKeyCascade; + case RESTRICT: + return DatabaseMetaData.importedKeyRestrict; + case SET_DEFAULT: + return DatabaseMetaData.importedKeySetDefault; + case SET_NULL: + return DatabaseMetaData.importedKeySetNull; + default: + throw DbException.getInternalError("action="+action); + } + } + + private void addConstraintColumnUsage(SessionLocal session, ArrayList rows, String catalog, + Constraint constraint, Column column) { + Table table = column.getTable(); + add(session, + rows, + // TABLE_CATALOG + catalog, + // TABLE_SCHEMA + table.getSchema().getName(), + // TABLE_NAME + table.getName(), + // COLUMN_NAME + column.getName(), + // CONSTRAINT_CATALOG + catalog, + // CONSTRAINT_SCHEMA + constraint.getSchema().getName(), + // CONSTRAINT_NAME + constraint.getName() + ); + } + + private void addPrivileges(SessionLocal session, ArrayList rows, DbObject grantee, + String catalog, Table table, String column, int rightMask) { + if ((rightMask & Right.SELECT) != 0) { + addPrivilege(session, rows, grantee, catalog, table, column, "SELECT"); + } + if ((rightMask & Right.INSERT) != 0) { + addPrivilege(session, rows, grantee, catalog, table, column, "INSERT"); + } + if ((rightMask & Right.UPDATE) != 0) { + addPrivilege(session, rows, grantee, catalog, table, column, "UPDATE"); + } + if ((rightMask & Right.DELETE) != 0) { + addPrivilege(session, rows, grantee, catalog, table, column, "DELETE"); + } + } + + private void addPrivilege(SessionLocal session, ArrayList rows, DbObject grantee, + String catalog, Table table, String column, String right) { + String isGrantable = "NO"; + if (grantee.getType() == DbObject.USER) { + User user = (User) grantee; + if (user.isAdmin()) { + // the right is grantable if the grantee is an admin + isGrantable = "YES"; + } + } + if (column == null) { + add(session, + rows, + // GRANTOR + null, + // GRANTEE + identifier(grantee.getName()), + // TABLE_CATALOG + catalog, + // TABLE_SCHEMA + table.getSchema().getName(), + // TABLE_NAME + table.getName(), + // PRIVILEGE_TYPE + right, + // IS_GRANTABLE + isGrantable + ); + } else { + add(session, + rows, + // GRANTOR + null, + // GRANTEE + identifier(grantee.getName()), + // TABLE_CATALOG + catalog, + // TABLE_SCHEMA + table.getSchema().getName(), + // TABLE_NAME + table.getName(), + // COLUMN_NAME + column, + // PRIVILEGE_TYPE + right, + // IS_GRANTABLE + isGrantable + ); + } + } + + private ArrayList getAllSchemaObjects(int type) { + ArrayList list = new ArrayList<>(); + for (Schema schema : database.getAllSchemas()) { + schema.getAll(type, list); + } + return list; + } + + /** + * Get all tables of this database, including local temporary tables for the + * session. + * + * @param session the session + * @return the array of tables + */ + private ArrayList
getAllTables(SessionLocal session) { + ArrayList
tables = new ArrayList<>(); + for (Schema schema : database.getAllSchemas()) { + tables.addAll(schema.getAllTablesAndViews(session)); + } + tables.addAll(session.getLocalTempTables()); + return tables; + } + + private ArrayList
getTablesByName(SessionLocal session, String tableName) { + // we expect that at most one table matches, at least in most cases + ArrayList
tables = new ArrayList<>(1); + for (Schema schema : database.getAllSchemas()) { + Table table = schema.getTableOrViewByName(session, tableName); + if (table != null) { + tables.add(table); + } + } + Table table = session.findLocalTempTable(tableName); + if (table != null) { + tables.add(table); + } + return tables; + } + + @Override + public long getMaxDataModificationId() { + switch (type) { + case SETTINGS: + case SEQUENCES: + case IN_DOUBT: + case SESSIONS: + case LOCKS: + case SESSION_STATE: + return Long.MAX_VALUE; + } + return database.getModificationDataId(); + } + +} diff --git a/h2/src/main/org/h2/table/MetaTable.java b/h2/src/main/org/h2/table/MetaTable.java new file mode 100644 index 0000000..19f10fd --- /dev/null +++ b/h2/src/main/org/h2/table/MetaTable.java @@ -0,0 +1,277 @@ +/* + * 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.engine.SessionLocal; +import org.h2.index.Index; +import org.h2.index.IndexType; +import org.h2.index.MetaIndex; +import org.h2.message.DbException; +import org.h2.result.Row; +import org.h2.result.SearchRow; +import org.h2.schema.Schema; +import org.h2.util.StringUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueNull; +import org.h2.value.ValueVarchar; +import org.h2.value.ValueVarcharIgnoreCase; + +/** + * This class is responsible to build the database meta data pseudo tables. + */ +public abstract class MetaTable extends Table { + + /** + * The approximate number of rows of a meta table. + */ + public static final long ROW_COUNT_APPROXIMATION = 1000; + + /** + * The table type. + */ + protected final int type; + + /** + * The indexed column. + */ + protected int indexColumn; + + /** + * The index for this table. + */ + protected MetaIndex metaIndex; + + /** + * Create a new metadata table. + * + * @param schema the schema + * @param id the object id + * @param type the meta table type + */ + protected MetaTable(Schema schema, int id, int type) { + // tableName will be set later + super(schema, id, null, true, true); + this.type = type; + } + + protected final void setMetaTableName(String upperName) { + setObjectName(database.sysIdentifier(upperName)); + } + + /** + * Creates a column with the specified name and character string data type. + * + * @param name + * the uppercase column name + * @return the column + */ + final Column column(String name) { + return new Column(database.sysIdentifier(name), + database.getSettings().caseInsensitiveIdentifiers ? TypeInfo.TYPE_VARCHAR_IGNORECASE + : TypeInfo.TYPE_VARCHAR); + } + + /** + * Creates a column with the specified name and data type. + * + * @param name + * the uppercase column name + * @param type + * the data type + * @return the column + */ + protected final Column column(String name, TypeInfo type) { + return new Column(database.sysIdentifier(name), type); + } + + @Override + public final String getCreateSQL() { + return null; + } + + @Override + public final Index addIndex(SessionLocal session, String indexName, int indexId, IndexColumn[] cols, + int uniqueColumnCount, IndexType indexType, boolean create, String indexComment) { + throw DbException.getUnsupportedException("META"); + } + + /** + * If needed, convert the identifier to lower case. + * + * @param s the identifier to convert + * @return the converted identifier + */ + protected final String identifier(String s) { + if (database.getSettings().databaseToLower) { + s = s == null ? null : StringUtils.toLowerEnglish(s); + } + return s; + } + + /** + * Checks index conditions. + * + * @param session the session + * @param value the value + * @param indexFrom the lower bound of value, or {@code null} + * @param indexTo the higher bound of value, or {@code null} + * @return whether row should be included into result + */ + protected final boolean checkIndex(SessionLocal session, String value, Value indexFrom, Value indexTo) { + if (value == null || (indexFrom == null && indexTo == null)) { + return true; + } + Value v; + if (database.getSettings().caseInsensitiveIdentifiers) { + v = ValueVarcharIgnoreCase.get(value); + } else { + v = ValueVarchar.get(value); + } + if (indexFrom != null && session.compare(v, indexFrom) < 0) { + return false; + } + if (indexTo != null && session.compare(v, indexTo) > 0) { + return false; + } + return true; + } + + /** + * Check whether to hide the table. Tables are never hidden in the system + * session. + * + * @param table the table + * @param session the session + * @return whether the table is hidden + */ + protected final boolean hideTable(Table table, SessionLocal session) { + return table.isHidden() && session != database.getSystemSession(); + } + + /** + * Generate the data for the given metadata table using the given first and + * last row filters. + * + * @param session the session + * @param first the first row to return + * @param last the last row to return + * @return the generated rows + */ + public abstract ArrayList generateRows(SessionLocal session, SearchRow first, SearchRow last); + + @Override + public boolean isInsertable() { + return false; + } + + @Override + public final void removeRow(SessionLocal session, Row row) { + throw DbException.getUnsupportedException("META"); + } + + @Override + public final void addRow(SessionLocal session, Row row) { + throw DbException.getUnsupportedException("META"); + } + + @Override + public final void removeChildrenAndResources(SessionLocal session) { + throw DbException.getUnsupportedException("META"); + } + + @Override + public final void close(SessionLocal session) { + // nothing to do + } + + /** + * Add a row to a list. + * + * @param session the session + * @param rows the original row list + * @param stringsOrValues the values, or strings + */ + protected final void add(SessionLocal session, ArrayList rows, Object... stringsOrValues) { + Value[] values = new Value[stringsOrValues.length]; + for (int i = 0; i < stringsOrValues.length; i++) { + Object s = stringsOrValues[i]; + Value v = s == null ? ValueNull.INSTANCE : s instanceof String ? ValueVarchar.get((String) s) : (Value) s; + values[i] = columns[i].convert(session, v); + } + rows.add(Row.get(values, 1, rows.size())); + } + + @Override + public final void checkRename() { + throw DbException.getUnsupportedException("META"); + } + + @Override + public final void checkSupportAlter() { + throw DbException.getUnsupportedException("META"); + } + + @Override + public final long truncate(SessionLocal session) { + throw DbException.getUnsupportedException("META"); + } + + @Override + public long getRowCount(SessionLocal session) { + throw DbException.getInternalError(toString()); + } + + @Override + public boolean canGetRowCount(SessionLocal session) { + return false; + } + + @Override + public final boolean canDrop() { + return false; + } + + @Override + public final TableType getTableType() { + return TableType.SYSTEM_TABLE; + } + + @Override + public final Index getScanIndex(SessionLocal session) { + return new MetaIndex(this, IndexColumn.wrap(columns), true); + } + + @Override + public final ArrayList getIndexes() { + ArrayList list = new ArrayList<>(2); + if (metaIndex == null) { + return list; + } + list.add(new MetaIndex(this, IndexColumn.wrap(columns), true)); + // TODO re-use the index + list.add(metaIndex); + return list; + } + + @Override + public long getRowCountApproximation(SessionLocal session) { + return ROW_COUNT_APPROXIMATION; + } + + @Override + public final boolean isDeterministic() { + return true; + } + + @Override + public final boolean canReference() { + return false; + } + +} diff --git a/h2/src/main/org/h2/table/Plan.java b/h2/src/main/org/h2/table/Plan.java new file mode 100644 index 0000000..635aa2a --- /dev/null +++ b/h2/src/main/org/h2/table/Plan.java @@ -0,0 +1,149 @@ +/* + * 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.HashMap; + +import org.h2.command.query.AllColumnsForPlan; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.message.Trace; + +/** + * A possible query execution plan. The time required to execute a query depends + * on the order the tables are accessed. + */ +public class Plan { + + private final TableFilter[] filters; + private final HashMap planItems = new HashMap<>(); + private final Expression[] allConditions; + private final TableFilter[] allFilters; + + /** + * Create a query plan with the given order. + * + * @param filters the tables of the query + * @param count the number of table items + * @param condition the condition in the WHERE clause + */ + public Plan(TableFilter[] filters, int count, Expression condition) { + this.filters = new TableFilter[count]; + System.arraycopy(filters, 0, this.filters, 0, count); + final ArrayList allCond = new ArrayList<>(); + final ArrayList all = new ArrayList<>(); + if (condition != null) { + allCond.add(condition); + } + for (int i = 0; i < count; i++) { + TableFilter f = filters[i]; + f.visit(f1 -> { + all.add(f1); + if (f1.getJoinCondition() != null) { + allCond.add(f1.getJoinCondition()); + } + }); + } + allConditions = allCond.toArray(new Expression[0]); + allFilters = all.toArray(new TableFilter[0]); + } + + /** + * Get the plan item for the given table. + * + * @param filter the table + * @return the plan item + */ + public PlanItem getItem(TableFilter filter) { + return planItems.get(filter); + } + + /** + * The the list of tables. + * + * @return the list of tables + */ + public TableFilter[] getFilters() { + return filters; + } + + /** + * Remove all index conditions that can not be used. + */ + public void removeUnusableIndexConditions() { + for (int i = 0; i < allFilters.length; i++) { + TableFilter f = allFilters[i]; + setEvaluatable(f, true); + if (i < allFilters.length - 1) { + // the last table doesn't need the optimization, + // otherwise the expression is calculated twice unnecessarily + // (not that bad but not optimal) + f.optimizeFullCondition(); + } + f.removeUnusableIndexConditions(); + } + for (TableFilter f : allFilters) { + setEvaluatable(f, false); + } + } + + /** + * Calculate the cost of this query plan. + * + * @param session the session + * @param allColumnsSet calculates all columns on-demand + * @return the cost + */ + public double calculateCost(SessionLocal session, AllColumnsForPlan allColumnsSet) { + Trace t = session.getTrace(); + if (t.isDebugEnabled()) { + t.debug("Plan : calculate cost for plan {0}", Arrays.toString(allFilters)); + } + double cost = 1; + boolean invalidPlan = false; + for (int i = 0; i < allFilters.length; i++) { + TableFilter tableFilter = allFilters[i]; + if (t.isDebugEnabled()) { + t.debug("Plan : for table filter {0}", tableFilter); + } + PlanItem item = tableFilter.getBestPlanItem(session, allFilters, i, allColumnsSet); + planItems.put(tableFilter, item); + if (t.isDebugEnabled()) { + t.debug("Plan : best plan item cost {0} index {1}", + item.cost, item.getIndex().getPlanSQL()); + } + cost += cost * item.cost; + setEvaluatable(tableFilter, true); + Expression on = tableFilter.getJoinCondition(); + if (on != null) { + if (!on.isEverything(ExpressionVisitor.EVALUATABLE_VISITOR)) { + invalidPlan = true; + break; + } + } + } + if (invalidPlan) { + cost = Double.POSITIVE_INFINITY; + } + if (t.isDebugEnabled()) { + session.getTrace().debug("Plan : plan cost {0}", cost); + } + for (TableFilter f : allFilters) { + setEvaluatable(f, false); + } + return cost; + } + + private void setEvaluatable(TableFilter filter, boolean b) { + filter.setEvaluatable(filter, b); + for (Expression e : allConditions) { + e.setEvaluatable(filter, b); + } + } +} diff --git a/h2/src/main/org/h2/table/PlanItem.java b/h2/src/main/org/h2/table/PlanItem.java new file mode 100644 index 0000000..5d834ee --- /dev/null +++ b/h2/src/main/org/h2/table/PlanItem.java @@ -0,0 +1,58 @@ +/* + * 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 org.h2.index.Index; + +/** + * The plan item describes the index to be used, and the estimated cost when + * using it. + */ +public class PlanItem { + + /** + * The cost. + */ + double cost; + + private int[] masks; + private Index index; + private PlanItem joinPlan; + private PlanItem nestedJoinPlan; + + void setMasks(int[] masks) { + this.masks = masks; + } + + int[] getMasks() { + return masks; + } + + void setIndex(Index index) { + this.index = index; + } + + public Index getIndex() { + return index; + } + + PlanItem getJoinPlan() { + return joinPlan; + } + + PlanItem getNestedJoinPlan() { + return nestedJoinPlan; + } + + void setJoinPlan(PlanItem joinPlan) { + this.joinPlan = joinPlan; + } + + void setNestedJoinPlan(PlanItem nestedJoinPlan) { + this.nestedJoinPlan = nestedJoinPlan; + } + +} 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 0000000..b7514e6 --- /dev/null +++ b/h2/src/main/org/h2/table/QueryExpressionTable.java @@ -0,0 +1,319 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.table; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import org.h2.command.query.AllColumnsForPlan; +import org.h2.command.query.Query; +import org.h2.engine.DbObject; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.Parameter; +import org.h2.index.Index; +import org.h2.index.IndexType; +import org.h2.index.QueryExpressionIndex; +import org.h2.message.DbException; +import org.h2.result.Row; +import org.h2.result.SortOrder; +import org.h2.schema.Schema; +import org.h2.util.StringUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * A derived table or view. + */ +public abstract class QueryExpressionTable extends Table { + + /** + * The key of the index cache for views. + */ + static final class CacheKey { + + private final int[] masks; + + private final QueryExpressionTable queryExpressionTable; + + CacheKey(int[] masks, QueryExpressionTable queryExpressionTable) { + this.masks = masks; + this.queryExpressionTable = queryExpressionTable; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(masks); + result = prime * result + queryExpressionTable.hashCode(); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + CacheKey other = (CacheKey) obj; + if (queryExpressionTable != other.queryExpressionTable) { + return false; + } + return Arrays.equals(masks, other.masks); + } + } + + private static final long ROW_COUNT_APPROXIMATION = 100; + + /** + * Creates a list of column templates from a query (usually from WITH query, + * but could be any query) + * + * @param cols + * - an optional list of column names (can be specified by WITH + * clause overriding usual select names) + * @param theQuery + * - the query object we want the column list for + * @param querySQLOutput + * - array of length 1 to receive extra 'output' field in + * addition to return value - containing the SQL query of the + * Query object + * @return a list of column object returned by withQuery + */ + public static List createQueryColumnTemplateList(String[] cols, Query theQuery, String[] querySQLOutput) { + ArrayList columnTemplateList = new ArrayList<>(); + theQuery.prepare(); + // String array of length 1 is to receive extra 'output' field in + // addition to + // return value + querySQLOutput[0] = StringUtils.cache(theQuery.getPlanSQL(ADD_PLAN_INFORMATION)); + SessionLocal session = theQuery.getSession(); + ArrayList withExpressions = theQuery.getExpressions(); + for (int i = 0; i < withExpressions.size(); ++i) { + Expression columnExp = withExpressions.get(i); + // use the passed in column name if supplied, otherwise use alias + // (if found) otherwise use column name derived from column + // expression + String columnName = cols != null && cols.length > i ? cols[i] : columnExp.getColumnNameForView(session, i); + columnTemplateList.add(new Column(columnName, columnExp.getType())); + } + return columnTemplateList; + } + + static int getMaxParameterIndex(ArrayList parameters) { + int result = -1; + for (Parameter p : parameters) { + if (p != null) { + result = Math.max(result, p.getIndex()); + } + } + return result; + } + + Query viewQuery; + + QueryExpressionIndex index; + + ArrayList
tables; + + private long lastModificationCheck; + + private long maxDataModificationId; + + QueryExpressionTable(Schema schema, int id, String name) { + super(schema, id, name, false, true); + } + + Column[] initColumns(SessionLocal session, Column[] columnTemplates, Query query, boolean isDerivedTable) { + ArrayList expressions = query.getExpressions(); + final int count = query.getColumnCount(); + ArrayList list = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + Expression expr = expressions.get(i); + String name = null; + TypeInfo type = TypeInfo.TYPE_UNKNOWN; + if (columnTemplates != null && columnTemplates.length > i) { + name = columnTemplates[i].getName(); + type = columnTemplates[i].getType(); + } + if (name == null) { + name = isDerivedTable ? expr.getAlias(session, i) : expr.getColumnNameForView(session, i); + } + if (type.getValueType() == Value.UNKNOWN) { + type = expr.getType(); + } + list.add(new Column(name, type, this, i)); + } + return list.toArray(new Column[0]); + } + + public final Query getQuery() { + return viewQuery; + } + + public abstract Query getTopQuery(); + + @Override + public final void close(SessionLocal session) { + // nothing to do + } + + @Override + public final Index addIndex(SessionLocal session, String indexName, int indexId, IndexColumn[] cols, + int uniqueColumnCount, IndexType indexType, boolean create, String indexComment) { + throw DbException.getUnsupportedException(getClass().getSimpleName() + ".addIndex"); + } + + @Override + public final boolean isView() { + return true; + } + + @Override + public final PlanItem getBestPlanItem(SessionLocal session, int[] masks, TableFilter[] filters, int filter, + SortOrder sortOrder, AllColumnsForPlan allColumnsSet) { + final CacheKey cacheKey = new CacheKey(masks, this); + Map indexCache = session.getViewIndexCache(getTableType() == null); + QueryExpressionIndex i = indexCache.get(cacheKey); + if (i == null || i.isExpired()) { + i = new QueryExpressionIndex(this, index, session, masks, filters, filter, sortOrder); + indexCache.put(cacheKey, i); + } + PlanItem item = new PlanItem(); + item.cost = i.getCost(session, masks, filters, filter, sortOrder, allColumnsSet); + item.setIndex(i); + return item; + } + + @Override + public boolean isQueryComparable() { + for (Table t : tables) { + if (!t.isQueryComparable()) { + return false; + } + } + return true; + } + + @Override + public final boolean isInsertable() { + return false; + } + + @Override + public final void removeRow(SessionLocal session, Row row) { + throw DbException.getUnsupportedException(getClass().getSimpleName() + ".removeRow"); + } + + @Override + public final void addRow(SessionLocal session, Row row) { + throw DbException.getUnsupportedException(getClass().getSimpleName() + ".addRow"); + } + + @Override + public final void checkSupportAlter() { + throw DbException.getUnsupportedException(getClass().getSimpleName() + ".checkSupportAlter"); + } + + @Override + public final long truncate(SessionLocal session) { + throw DbException.getUnsupportedException(getClass().getSimpleName() + ".truncate"); + } + + @Override + public final long getRowCount(SessionLocal session) { + throw DbException.getInternalError(toString()); + } + + @Override + public final boolean canGetRowCount(SessionLocal session) { + // TODO could get the row count, but not that easy + return false; + } + + @Override + public final long getRowCountApproximation(SessionLocal session) { + return ROW_COUNT_APPROXIMATION; + } + + /** + * Get the index of the first parameter. + * + * @param additionalParameters + * additional parameters + * @return the index of the first parameter + */ + public final int getParameterOffset(ArrayList additionalParameters) { + Query topQuery = getTopQuery(); + int result = topQuery == null ? -1 : getMaxParameterIndex(topQuery.getParameters()); + if (additionalParameters != null) { + result = Math.max(result, getMaxParameterIndex(additionalParameters)); + } + return result + 1; + } + + @Override + public final boolean canReference() { + return false; + } + + @Override + public final ArrayList getIndexes() { + return null; + } + + @Override + public long getMaxDataModificationId() { + // if nothing was modified in the database since the last check, and the + // last is known, then we don't need to check again + // this speeds up nested views + long dbMod = database.getModificationDataId(); + if (dbMod > lastModificationCheck && maxDataModificationId <= dbMod) { + maxDataModificationId = viewQuery.getMaxDataModificationId(); + lastModificationCheck = dbMod; + } + return maxDataModificationId; + } + + @Override + public final Index getScanIndex(SessionLocal session) { + return getBestPlanItem(session, null, null, -1, null, null).getIndex(); + } + + @Override + public Index getScanIndex(SessionLocal session, int[] masks, TableFilter[] filters, int filter, // + SortOrder sortOrder, AllColumnsForPlan allColumnsSet) { + return getBestPlanItem(session, masks, filters, filter, sortOrder, allColumnsSet).getIndex(); + } + + @Override + public boolean isDeterministic() { + return viewQuery.isEverything(ExpressionVisitor.DETERMINISTIC_VISITOR); + } + + @Override + public final void addDependencies(HashSet dependencies) { + super.addDependencies(dependencies); + if (tables != null) { + for (Table t : tables) { + if (TableType.VIEW != t.getTableType()) { + t.addDependencies(dependencies); + } + } + } + } + +} diff --git a/h2/src/main/org/h2/table/RangeTable.java b/h2/src/main/org/h2/table/RangeTable.java new file mode 100644 index 0000000..774e429 --- /dev/null +++ b/h2/src/main/org/h2/table/RangeTable.java @@ -0,0 +1,175 @@ +/* + * 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.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.index.Index; +import org.h2.index.RangeIndex; +import org.h2.message.DbException; +import org.h2.schema.Schema; +import org.h2.value.TypeInfo; + +/** + * The table SYSTEM_RANGE is a virtual table that generates incrementing numbers + * with a given start end point. + */ +public class RangeTable extends VirtualTable { + + /** + * The name of the range table. + */ + public static final String NAME = "SYSTEM_RANGE"; + + /** + * The PostgreSQL alias for the range table. + */ + public static final String ALIAS = "GENERATE_SERIES"; + + private Expression min, max, step; + private boolean optimized; + + private final RangeIndex index; + + /** + * Create a new range with the given start and end expressions. + * + * @param schema the schema (always the main schema) + * @param min the start expression + * @param max the end expression + */ + public RangeTable(Schema schema, Expression min, Expression max) { + super(schema, 0, NAME); + this.min = min; + this.max = max; + Column[] columns = new Column[] { new Column("X", TypeInfo.TYPE_BIGINT) }; + setColumns(columns); + index = new RangeIndex(this, IndexColumn.wrap(columns)); + } + + public RangeTable(Schema schema, Expression min, Expression max, Expression step) { + this(schema, min, max); + this.step = step; + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + builder.append(NAME).append('('); + min.getUnenclosedSQL(builder, sqlFlags).append(", "); + max.getUnenclosedSQL(builder, sqlFlags); + if (step != null) { + step.getUnenclosedSQL(builder.append(", "), sqlFlags); + } + return builder.append(')'); + } + + @Override + public boolean canGetRowCount(SessionLocal session) { + return true; + } + + @Override + public long getRowCount(SessionLocal session) { + long step = getStep(session); + if (step == 0L) { + throw DbException.get(ErrorCode.STEP_SIZE_MUST_NOT_BE_ZERO); + } + long delta = getMax(session) - getMin(session); + if (step > 0) { + if (delta < 0) { + return 0; + } + } else if (delta > 0) { + return 0; + } + return delta / step + 1; + } + + @Override + public TableType getTableType() { + return TableType.SYSTEM_TABLE; + } + + @Override + public Index getScanIndex(SessionLocal session) { + return index; + } + + @Override + public ArrayList getIndexes() { + ArrayList list = new ArrayList<>(2); + // Scan index (ignored by MIN/MAX optimization) + list.add(index); + // Normal index + list.add(index); + return list; + } + + /** + * Calculate and get the start value of this range. + * + * @param session the session + * @return the start value + */ + public long getMin(SessionLocal session) { + optimize(session); + return min.getValue(session).getLong(); + } + + /** + * Calculate and get the end value of this range. + * + * @param session the session + * @return the end value + */ + public long getMax(SessionLocal session) { + optimize(session); + return max.getValue(session).getLong(); + } + + /** + * Get the increment. + * + * @param session the session + * @return the increment (1 by default) + */ + public long getStep(SessionLocal session) { + optimize(session); + if (step == null) { + return 1; + } + return step.getValue(session).getLong(); + } + + private void optimize(SessionLocal s) { + if (!optimized) { + min = min.optimize(s); + max = max.optimize(s); + if (step != null) { + step = step.optimize(s); + } + optimized = true; + } + } + + @Override + public long getMaxDataModificationId() { + return 0; + } + + @Override + public long getRowCountApproximation(SessionLocal session) { + return 100; + } + + @Override + public boolean isDeterministic() { + return true; + } + +} diff --git a/h2/src/main/org/h2/table/Table.java b/h2/src/main/org/h2/table/Table.java new file mode 100644 index 0000000..d194c64 --- /dev/null +++ b/h2/src/main/org/h2/table/Table.java @@ -0,0 +1,1441 @@ +/* + * 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.HashMap; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.h2.api.ErrorCode; +import org.h2.command.Prepared; +import org.h2.command.query.AllColumnsForPlan; +import org.h2.constraint.Constraint; +import org.h2.constraint.Constraint.Type; +import org.h2.engine.CastDataProvider; +import org.h2.engine.Constants; +import org.h2.engine.DbObject; +import org.h2.engine.Right; +import org.h2.engine.SessionLocal; +import org.h2.expression.ExpressionVisitor; +import org.h2.index.Index; +import org.h2.index.IndexType; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.result.DefaultRow; +import org.h2.result.LocalResult; +import org.h2.result.Row; +import org.h2.result.RowFactory; +import org.h2.result.SearchRow; +import org.h2.result.SimpleRowValue; +import org.h2.result.SortOrder; +import org.h2.schema.Schema; +import org.h2.schema.SchemaObject; +import org.h2.schema.Sequence; +import org.h2.schema.TriggerObject; +import org.h2.util.Utils; +import org.h2.value.CompareMode; +import org.h2.value.Value; +import org.h2.value.ValueNull; + +/** + * This is the base class for most tables. + * A table contains a list of columns and a list of rows. + */ +public abstract class Table extends SchemaObject { + + /** + * The table type that means this table is a regular persistent table. + */ + public static final int TYPE_CACHED = 0; + + /** + * The table type that means this table is a regular persistent table. + */ + public static final int TYPE_MEMORY = 1; + + /** + * Read lock. + */ + public static final int READ_LOCK = 0; + + /** + * Write lock. + */ + public static final int WRITE_LOCK = 1; + + /** + * Exclusive lock. + */ + public static final int EXCLUSIVE_LOCK = 2; + + /** + * The columns of this table. + */ + protected Column[] columns; + + /** + * The compare mode used for this table. + */ + protected CompareMode compareMode; + + /** + * Protected tables are not listed in the meta data and are excluded when + * using the SCRIPT command. + */ + protected boolean isHidden; + + private final HashMap columnMap; + private final boolean persistIndexes; + private final boolean persistData; + private ArrayList triggers; + private ArrayList constraints; + private ArrayList sequences; + /** + * views that depend on this table + */ + private final CopyOnWriteArrayList dependentViews = new CopyOnWriteArrayList<>(); + private ArrayList synonyms; + /** Is foreign key constraint checking enabled for this table. */ + private boolean checkForeignKeyConstraints = true; + private boolean onCommitDrop, onCommitTruncate; + private volatile Row nullRow; + private RowFactory rowFactory = RowFactory.getRowFactory(); + private boolean tableExpression; + + protected Table(Schema schema, int id, String name, boolean persistIndexes, boolean persistData) { + super(schema, id, name, Trace.TABLE); + columnMap = schema.getDatabase().newStringMap(); + this.persistIndexes = persistIndexes; + this.persistData = persistData; + compareMode = schema.getDatabase().getCompareMode(); + } + + @Override + public void rename(String newName) { + super.rename(newName); + if (constraints != null) { + for (Constraint constraint : constraints) { + constraint.rebuild(); + } + } + } + + public boolean isView() { + return false; + } + + /** + * Lock the table for the given session. + * This method waits until the lock is granted. + * + * @param session the session + * @param lockType the type of lock + * @return true if the table was already exclusively locked by this session. + * @throws DbException if a lock timeout occurred + */ + public boolean lock(SessionLocal session, int lockType) { + return false; + } + + /** + * Close the table object and flush changes. + * + * @param session the session + */ + public abstract void close(SessionLocal session); + + /** + * Release the lock for this session. + * + * @param s the session + */ + public void unlock(SessionLocal s) { + } + + /** + * Create an index for this table + * + * @param session the session + * @param indexName the name of the index + * @param indexId the id + * @param cols the index columns + * @param uniqueColumnCount the count of unique columns + * @param indexType the index type + * @param create whether this is a new index + * @param indexComment the comment + * @return the index + */ + public abstract Index addIndex(SessionLocal session, String indexName, int indexId, IndexColumn[] cols, + int uniqueColumnCount, IndexType indexType, boolean create, String indexComment); + + /** + * Get the given row. + * + * @param session the session + * @param key the primary key + * @return the row + */ + @SuppressWarnings("unused") + public Row getRow(SessionLocal session, long key) { + return null; + } + + /** + * Returns whether this table is insertable. + * + * @return whether this table is insertable + */ + public boolean isInsertable() { + return true; + } + + /** + * Remove a row from the table and all indexes. + * + * @param session the session + * @param row the row + */ + public abstract void removeRow(SessionLocal session, Row row); + + /** + * Locks row, preventing any updated to it, except from the session specified. + * + * @param session the session + * @param row to lock + * @return locked row, or null if row does not exist anymore + */ + public Row lockRow(SessionLocal session, Row row) { + throw DbException.getUnsupportedException("lockRow()"); + } + + /** + * Remove all rows from the table and indexes. + * + * @param session the session + * @return number of removed rows, possibly including uncommitted rows + */ + public abstract long truncate(SessionLocal session); + + /** + * Add a row to the table and all indexes. + * + * @param session the session + * @param row the row + * @throws DbException if a constraint was violated + */ + public abstract void addRow(SessionLocal session, Row row); + + /** + * Update a row to the table and all indexes. + * + * @param session the session + * @param oldRow the row to update + * @param newRow the row with updated values (_rowid_ suppose to be the same) + * @throws DbException if a constraint was violated + */ + public void updateRow(SessionLocal session, Row oldRow, Row newRow) { + newRow.setKey(oldRow.getKey()); + removeRow(session, oldRow); + addRow(session, newRow); + } + + /** + * Check if this table supports ALTER TABLE. + * + * @throws DbException if it is not supported + */ + public abstract void checkSupportAlter(); + + /** + * Get the table type name + * + * @return the table type name + */ + public abstract TableType getTableType(); + + /** + * Return SQL table type for INFORMATION_SCHEMA. + * + * @return SQL table type for INFORMATION_SCHEMA + */ + public String getSQLTableType() { + if (isView()) { + return "VIEW"; + } + if (isTemporary()) { + return isGlobalTemporary() ? "GLOBAL TEMPORARY" : "LOCAL TEMPORARY"; + } + return "BASE TABLE"; + } + + /** + * Get the scan index to iterate through all rows. + * + * @param session the session + * @return the index + */ + public abstract Index getScanIndex(SessionLocal session); + + /** + * Get the scan index for this table. + * + * @param session the session + * @param masks the search mask + * @param filters the table filters + * @param filter the filter index + * @param sortOrder the sort order + * @param allColumnsSet all columns + * @return the scan index + */ + @SuppressWarnings("unused") + public Index getScanIndex(SessionLocal session, int[] masks, + TableFilter[] filters, int filter, SortOrder sortOrder, + AllColumnsForPlan allColumnsSet) { + return getScanIndex(session); + } + + /** + * Get all indexes for this table. + * + * @return the list of indexes + */ + public abstract ArrayList getIndexes(); + + /** + * Get an index by name. + * + * @param indexName the index name to search for + * @return the found index + */ + public Index getIndex(String indexName) { + ArrayList indexes = getIndexes(); + if (indexes != null) { + for (Index index : indexes) { + if (index.getName().equals(indexName)) { + return index; + } + } + } + throw DbException.get(ErrorCode.INDEX_NOT_FOUND_1, indexName); + } + + /** + * Check if this table is locked exclusively. + * + * @return true if it is. + */ + public boolean isLockedExclusively() { + return false; + } + + /** + * Get the last data modification id. + * + * @return the modification id + */ + public abstract long getMaxDataModificationId(); + + /** + * Check if the table is deterministic. + * + * @return true if it is + */ + public abstract boolean isDeterministic(); + + /** + * Check if the row count can be retrieved quickly. + * + * @param session the session + * @return true if it can + */ + public abstract boolean canGetRowCount(SessionLocal session); + + /** + * Check if this table can be referenced. + * + * @return true if it can + */ + public boolean canReference() { + return true; + } + + /** + * Check if this table can be dropped. + * + * @return true if it can + */ + public abstract boolean canDrop(); + + /** + * Get the row count for this table. + * + * @param session the session + * @return the row count + */ + public abstract long getRowCount(SessionLocal session); + + /** + * Get the approximated row count for this table. + * + * @param session the session + * @return the approximated row count + */ + public abstract long getRowCountApproximation(SessionLocal session); + + public long getDiskSpaceUsed() { + return 0L; + } + + /** + * Get the row id column if this table has one. + * + * @return the row id column, or null + */ + 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() + * pseudo-column prevents this. + * + * @return true if the table contains no query-comparable column + */ + public boolean isQueryComparable() { + return true; + } + + /** + * Add all objects that this table depends on to the hash set. + * + * @param dependencies the current set of dependencies + */ + public void addDependencies(HashSet dependencies) { + if (dependencies.contains(this)) { + // avoid endless recursion + return; + } + if (sequences != null) { + dependencies.addAll(sequences); + } + ExpressionVisitor visitor = ExpressionVisitor.getDependenciesVisitor( + dependencies); + for (Column col : columns) { + col.isEverything(visitor); + } + if (constraints != null) { + for (Constraint c : constraints) { + c.isEverything(visitor); + } + } + dependencies.add(this); + } + + @Override + public ArrayList getChildren() { + ArrayList children = Utils.newSmallArrayList(); + ArrayList indexes = getIndexes(); + if (indexes != null) { + children.addAll(indexes); + } + if (constraints != null) { + children.addAll(constraints); + } + if (triggers != null) { + children.addAll(triggers); + } + if (sequences != null) { + children.addAll(sequences); + } + children.addAll(dependentViews); + if (synonyms != null) { + children.addAll(synonyms); + } + ArrayList rights = database.getAllRights(); + for (Right right : rights) { + if (right.getGrantedObject() == this) { + children.add(right); + } + } + return children; + } + + protected void setColumns(Column[] columns) { + if (columns.length > Constants.MAX_COLUMNS) { + throw DbException.get(ErrorCode.TOO_MANY_COLUMNS_1, "" + Constants.MAX_COLUMNS); + } + this.columns = columns; + if (columnMap.size() > 0) { + columnMap.clear(); + } + for (int i = 0; i < columns.length; i++) { + Column col = columns[i]; + int dataType = col.getType().getValueType(); + if (dataType == Value.UNKNOWN) { + throw DbException.get(ErrorCode.UNKNOWN_DATA_TYPE_1, col.getTraceSQL()); + } + col.setTable(this, i); + String columnName = col.getName(); + if (columnMap.putIfAbsent(columnName, col) != null) { + throw DbException.get(ErrorCode.DUPLICATE_COLUMN_NAME_1, columnName); + } + } + rowFactory = database.getRowFactory().createRowFactory(database, database.getCompareMode(), database, columns, + null, false); + } + + /** + * Rename a column of this table. + * + * @param column the column to rename + * @param newName the new column name + */ + public void renameColumn(Column column, String newName) { + for (Column c : columns) { + if (c == column) { + continue; + } + if (c.getName().equals(newName)) { + throw DbException.get( + ErrorCode.DUPLICATE_COLUMN_NAME_1, newName); + } + } + columnMap.remove(column.getName()); + column.rename(newName); + columnMap.put(newName, column); + } + + /** + * Check if the table is exclusively locked by this session. + * + * @param session the session + * @return true if it is + */ + @SuppressWarnings("unused") + public boolean isLockedExclusivelyBy(SessionLocal session) { + return false; + } + + /** + * Update a list of rows in this table. + * + * @param prepared the prepared statement + * @param session the session + * @param rows a list of row pairs of the form old row, new row, old row, + * new row,... + */ + public void updateRows(Prepared prepared, SessionLocal session, LocalResult rows) { + // in case we need to undo the update + SessionLocal.Savepoint rollback = session.setSavepoint(); + // remove the old rows + int rowScanCount = 0; + while (rows.next()) { + if ((++rowScanCount & 127) == 0) { + prepared.checkCanceled(); + } + Row o = rows.currentRowForTable(); + rows.next(); + try { + removeRow(session, o); + } catch (DbException e) { + if (e.getErrorCode() == ErrorCode.CONCURRENT_UPDATE_1 + || e.getErrorCode() == ErrorCode.ROW_NOT_FOUND_WHEN_DELETING_1) { + session.rollbackTo(rollback); + } + throw e; + } + } + // add the new rows + rows.reset(); + while (rows.next()) { + if ((++rowScanCount & 127) == 0) { + prepared.checkCanceled(); + } + rows.next(); + Row n = rows.currentRowForTable(); + try { + addRow(session, n); + } catch (DbException e) { + if (e.getErrorCode() == ErrorCode.CONCURRENT_UPDATE_1) { + session.rollbackTo(rollback); + } + throw e; + } + } + } + + public CopyOnWriteArrayList getDependentViews() { + return dependentViews; + } + + @Override + public void removeChildrenAndResources(SessionLocal session) { + while (!dependentViews.isEmpty()) { + TableView view = dependentViews.get(0); + dependentViews.remove(0); + database.removeSchemaObject(session, view); + } + while (synonyms != null && !synonyms.isEmpty()) { + TableSynonym synonym = synonyms.remove(0); + database.removeSchemaObject(session, synonym); + } + while (triggers != null && !triggers.isEmpty()) { + TriggerObject trigger = triggers.remove(0); + database.removeSchemaObject(session, trigger); + } + while (constraints != null && !constraints.isEmpty()) { + Constraint constraint = constraints.remove(0); + database.removeSchemaObject(session, constraint); + } + for (Right right : database.getAllRights()) { + if (right.getGrantedObject() == this) { + database.removeDatabaseObject(session, right); + } + } + database.removeMeta(session, getId()); + // must delete sequences later (in case there is a power failure + // before removing the table object) + while (sequences != null && !sequences.isEmpty()) { + Sequence sequence = sequences.remove(0); + // only remove if no other table depends on this sequence + // this is possible when calling ALTER TABLE ALTER COLUMN + if (database.getDependentTable(sequence, this) == null) { + database.removeSchemaObject(session, sequence); + } + } + } + + /** + * Check that these columns are not referenced by a multi-column constraint + * or multi-column index. If it is, an exception is thrown. Single-column + * references and indexes are dropped. + * + * @param session the session + * @param columnsToDrop the columns to drop + * @throws DbException if the column is referenced by multi-column + * constraints or indexes + */ + public void dropMultipleColumnsConstraintsAndIndexes(SessionLocal session, + ArrayList columnsToDrop) { + HashSet constraintsToDrop = new HashSet<>(); + if (constraints != null) { + for (Column col : columnsToDrop) { + for (Constraint constraint : constraints) { + HashSet columns = constraint.getReferencedColumns(this); + if (!columns.contains(col)) { + continue; + } + if (columns.size() == 1) { + constraintsToDrop.add(constraint); + } else { + throw DbException.get(ErrorCode.COLUMN_IS_REFERENCED_1, constraint.getTraceSQL()); + } + } + } + } + HashSet indexesToDrop = new HashSet<>(); + ArrayList indexes = getIndexes(); + if (indexes != null) { + for (Column col : columnsToDrop) { + for (Index index : indexes) { + if (index.getCreateSQL() == null) { + continue; + } + if (index.getColumnIndex(col) < 0) { + continue; + } + if (index.getColumns().length == 1) { + indexesToDrop.add(index); + } else { + throw DbException.get(ErrorCode.COLUMN_IS_REFERENCED_1, index.getTraceSQL()); + } + } + } + } + for (Constraint c : constraintsToDrop) { + if (c.isValid()) { + session.getDatabase().removeSchemaObject(session, c); + } + } + for (Index i : indexesToDrop) { + // the index may already have been dropped when dropping the + // constraint + if (getIndexes().contains(i)) { + session.getDatabase().removeSchemaObject(session, i); + } + } + } + + public RowFactory getRowFactory() { + return rowFactory; + } + + /** + * Create a new row for this table. + * + * @param data the values + * @param memory the estimated memory usage in bytes + * @return the created row + */ + public Row createRow(Value[] data, int memory) { + return rowFactory.createRow(data, memory); + } + + public Row getTemplateRow() { + return createRow(new Value[getColumns().length], DefaultRow.MEMORY_CALCULATE); + } + + /** + * Get a new simple row object. + * + * @param singleColumn if only one value need to be stored + * @return the simple row object + */ + public SearchRow getTemplateSimpleRow(boolean singleColumn) { + if (singleColumn) { + return new SimpleRowValue(columns.length); + } + return new DefaultRow(new Value[columns.length]); + } + + public Row getNullRow() { + Row row = nullRow; + if (row == null) { + // Here can be concurrently produced more than one row, but it must + // be ok. + Value[] values = new Value[columns.length]; + Arrays.fill(values, ValueNull.INSTANCE); + nullRow = row = createRow(values, 1); + } + return row; + } + + public Column[] getColumns() { + return columns; + } + + @Override + public int getType() { + return DbObject.TABLE_OR_VIEW; + } + + /** + * Get the column at the given index. + * + * @param index the column index (0, 1,...) + * @return the column + */ + public Column getColumn(int index) { + return columns[index]; + } + + /** + * Get the column with the given name. + * + * @param columnName the column name + * @return the column + * @throws DbException if the column was not found + */ + public Column getColumn(String columnName) { + Column column = columnMap.get(columnName); + if (column == null) { + throw DbException.get(ErrorCode.COLUMN_NOT_FOUND_1, columnName); + } + return column; + } + + /** + * 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 + * @return the column + * @throws DbException if the column was not found + */ + public Column getColumn(String columnName, boolean ifExists) { + Column column = columnMap.get(columnName); + if (column == null && !ifExists) { + throw DbException.get(ErrorCode.COLUMN_NOT_FOUND_1, columnName); + } + return column; + } + + /** + * Get the column with the given name if it exists. + * + * @param columnName the column name, or {@code null} + * @return the column + */ + public Column findColumn(String columnName) { + return columnMap.get(columnName); + } + + /** + * Does the column with the given name exist? + * + * @param columnName the column name + * @return true if the column exists + */ + public boolean doesColumnExist(String columnName) { + return columnMap.containsKey(columnName); + } + + /** + * Returns first identity column, or {@code null}. + * + * @return first identity column, or {@code null} + */ + public Column getIdentityColumn() { + for (Column column : columns) { + if (column.isIdentity()) { + return column; + } + } + return null; + } + + /** + * Get the best plan for the given search mask. + * + * @param session the session + * @param masks per-column comparison bit masks, null means 'always false', + * see constants in IndexCondition + * @param filters all joined table filters + * @param filter the current table filter index + * @param sortOrder the sort order + * @param allColumnsSet the set of all columns + * @return the plan item + */ + public PlanItem getBestPlanItem(SessionLocal session, int[] masks, + TableFilter[] filters, int filter, SortOrder sortOrder, + AllColumnsForPlan allColumnsSet) { + PlanItem item = new PlanItem(); + item.setIndex(getScanIndex(session)); + item.cost = item.getIndex().getCost(session, null, filters, filter, null, allColumnsSet); + Trace t = session.getTrace(); + if (t.isDebugEnabled()) { + t.debug("Table : potential plan item cost {0} index {1}", + item.cost, item.getIndex().getPlanSQL()); + } + ArrayList indexes = getIndexes(); + IndexHints indexHints = getIndexHints(filters, filter); + + if (indexes != null && masks != null) { + for (int i = 1, size = indexes.size(); i < size; i++) { + Index index = indexes.get(i); + + if (isIndexExcludedByHints(indexHints, index)) { + continue; + } + + double cost = index.getCost(session, masks, filters, filter, + sortOrder, allColumnsSet); + if (t.isDebugEnabled()) { + t.debug("Table : potential plan item cost {0} index {1}", + cost, index.getPlanSQL()); + } + if (cost < item.cost) { + item.cost = cost; + item.setIndex(index); + } + } + } + return item; + } + + private static boolean isIndexExcludedByHints(IndexHints indexHints, Index index) { + return indexHints != null && !indexHints.allowIndex(index); + } + + private static IndexHints getIndexHints(TableFilter[] filters, int filter) { + return filters == null ? null : filters[filter].getIndexHints(); + } + + /** + * Get the primary key index if there is one, or null if there is none. + * + * @return the primary key index or null + */ + public Index findPrimaryKey() { + ArrayList indexes = getIndexes(); + if (indexes != null) { + for (Index idx : indexes) { + if (idx.getIndexType().isPrimaryKey()) { + return idx; + } + } + } + return null; + } + + public Index getPrimaryKey() { + Index index = findPrimaryKey(); + if (index != null) { + return index; + } + throw DbException.get(ErrorCode.INDEX_NOT_FOUND_1, + Constants.PREFIX_PRIMARY_KEY); + } + + /** + * Prepares the specified row for INSERT operation. + * + * Identity, default, and generated values are evaluated, all values are + * converted to target data types and validated. Base value of identity + * column is updated when required by compatibility mode. + * + * @param session the session + * @param overridingSystem + * {@link Boolean#TRUE} for {@code OVERRIDING SYSTEM VALUES}, + * {@link Boolean#FALSE} for {@code OVERRIDING USER VALUES}, + * {@code null} if override clause is not specified + * @param row the row + */ + public void convertInsertRow(SessionLocal session, Row row, Boolean overridingSystem) { + int length = columns.length, generated = 0; + for (int i = 0; i < length; i++) { + Value value = row.getValue(i); + Column column = columns[i]; + if (value == ValueNull.INSTANCE && column.isDefaultOnNull()) { + value = null; + } + if (column.isIdentity()) { + if (overridingSystem != null) { + if (!overridingSystem) { + value = null; + } + } else if (value != null && column.isGeneratedAlways()) { + throw DbException.get(ErrorCode.GENERATED_COLUMN_CANNOT_BE_ASSIGNED_1, + column.getSQLWithTable(new StringBuilder(), TRACE_SQL_FLAGS).toString()); + } + } else if (column.isGeneratedAlways()) { + if (value != null) { + throw DbException.get(ErrorCode.GENERATED_COLUMN_CANNOT_BE_ASSIGNED_1, + column.getSQLWithTable(new StringBuilder(), TRACE_SQL_FLAGS).toString()); + } + generated++; + continue; + } + Value v2 = column.validateConvertUpdateSequence(session, value, row); + if (v2 != value) { + row.setValue(i, v2); + } + } + if (generated > 0) { + for (int i = 0; i < length; i++) { + Value value = row.getValue(i); + if (value == null) { + row.setValue(i, columns[i].validateConvertUpdateSequence(session, null, row)); + } + } + } + } + + /** + * Prepares the specified row for UPDATE operation. + * + * Default and generated values are evaluated, all values are converted to + * target data types and validated. Base value of identity column is updated + * when required by compatibility mode. + * + * @param session the session + * @param row the row + * @param fromTrigger {@code true} if row was modified by INSERT or UPDATE trigger + */ + public void convertUpdateRow(SessionLocal session, Row row, boolean fromTrigger) { + int length = columns.length, generated = 0; + for (int i = 0; i < length; i++) { + Value value = row.getValue(i); + Column column = columns[i]; + if (column.isGenerated()) { + if (value != null) { + if (!fromTrigger) { + throw DbException.get(ErrorCode.GENERATED_COLUMN_CANNOT_BE_ASSIGNED_1, + column.getSQLWithTable(new StringBuilder(), TRACE_SQL_FLAGS).toString()); + } + row.setValue(i, null); + } + generated++; + continue; + } + Value v2 = column.validateConvertUpdateSequence(session, value, row); + if (v2 != value) { + row.setValue(i, v2); + } + } + if (generated > 0) { + for (int i = 0; i < length; i++) { + Value value = row.getValue(i); + if (value == null) { + row.setValue(i, columns[i].validateConvertUpdateSequence(session, null, row)); + } + } + } + } + + private static void remove(ArrayList list, DbObject obj) { + if (list != null) { + list.remove(obj); + } + } + + /** + * Remove the given index from the list. + * + * @param index the index to remove + */ + public void removeIndex(Index index) { + ArrayList indexes = getIndexes(); + if (indexes != null) { + remove(indexes, index); + if (index.getIndexType().isPrimaryKey()) { + for (Column col : index.getColumns()) { + col.setPrimaryKey(false); + } + } + } + } + + /** + * Remove the given view from the dependent views list. + * + * @param view the view to remove + */ + public void removeDependentView(TableView view) { + dependentViews.remove(view); + } + + /** + * Remove the given view from the list. + * + * @param synonym the synonym to remove + */ + public void removeSynonym(TableSynonym synonym) { + remove(synonyms, synonym); + } + + /** + * Remove the given constraint from the list. + * + * @param constraint the constraint to remove + */ + public void removeConstraint(Constraint constraint) { + remove(constraints, constraint); + } + + /** + * Remove a sequence from the table. Sequences are used as identity columns. + * + * @param sequence the sequence to remove + */ + public final void removeSequence(Sequence sequence) { + remove(sequences, sequence); + } + + /** + * Remove the given trigger from the list. + * + * @param trigger the trigger to remove + */ + public void removeTrigger(TriggerObject trigger) { + remove(triggers, trigger); + } + + /** + * Add a view to this table. + * + * @param view the view to add + */ + public void addDependentView(TableView view) { + dependentViews.add(view); + } + + /** + * Add a synonym to this table. + * + * @param synonym the synonym to add + */ + public void addSynonym(TableSynonym synonym) { + synonyms = add(synonyms, synonym); + } + + /** + * Add a constraint to the table. + * + * @param constraint the constraint to add + */ + public void addConstraint(Constraint constraint) { + if (constraints == null || !constraints.contains(constraint)) { + constraints = add(constraints, constraint); + } + } + + public ArrayList getConstraints() { + return constraints; + } + + /** + * Add a sequence to this table. + * + * @param sequence the sequence to add + */ + public void addSequence(Sequence sequence) { + sequences = add(sequences, sequence); + } + + /** + * Add a trigger to this table. + * + * @param trigger the trigger to add + */ + public void addTrigger(TriggerObject trigger) { + triggers = add(triggers, trigger); + } + + private static ArrayList add(ArrayList list, T obj) { + if (list == null) { + list = Utils.newSmallArrayList(); + } + // self constraints are two entries in the list + list.add(obj); + return list; + } + + /** + * Fire the triggers for this table. + * + * @param session the session + * @param type the trigger type + * @param beforeAction whether 'before' triggers should be called + */ + public void fire(SessionLocal session, int type, boolean beforeAction) { + if (triggers != null) { + for (TriggerObject trigger : triggers) { + trigger.fire(session, type, beforeAction); + } + } + } + + /** + * Check whether this table has a select trigger. + * + * @return true if it has + */ + public boolean hasSelectTrigger() { + if (triggers != null) { + for (TriggerObject trigger : triggers) { + if (trigger.isSelectTrigger()) { + return true; + } + } + } + return false; + } + + /** + * Check if row based triggers or constraints are defined. + * In this case the fire after and before row methods need to be called. + * + * @return if there are any triggers or rows defined + */ + public boolean fireRow() { + return (constraints != null && !constraints.isEmpty()) || + (triggers != null && !triggers.isEmpty()); + } + + /** + * Fire all triggers that need to be called before a row is updated. + * + * @param session the session + * @param oldRow the old data or null for an insert + * @param newRow the new data or null for a delete + * @return true if no further action is required (for 'instead of' triggers) + */ + public boolean fireBeforeRow(SessionLocal session, Row oldRow, Row newRow) { + boolean done = fireRow(session, oldRow, newRow, true, false); + fireConstraints(session, oldRow, newRow, true); + return done; + } + + private void fireConstraints(SessionLocal session, Row oldRow, Row newRow, + boolean before) { + if (constraints != null) { + for (Constraint constraint : constraints) { + if (constraint.isBefore() == before) { + constraint.checkRow(session, this, oldRow, newRow); + } + } + } + } + + /** + * Fire all triggers that need to be called after a row is updated. + * + * @param session the session + * @param oldRow the old data or null for an insert + * @param newRow the new data or null for a delete + * @param rollback when the operation occurred within a rollback + */ + public void fireAfterRow(SessionLocal session, Row oldRow, Row newRow, + boolean rollback) { + fireRow(session, oldRow, newRow, false, rollback); + if (!rollback) { + fireConstraints(session, oldRow, newRow, false); + } + } + + private boolean fireRow(SessionLocal session, Row oldRow, Row newRow, + boolean beforeAction, boolean rollback) { + if (triggers != null) { + for (TriggerObject trigger : triggers) { + boolean done = trigger.fireRow(session, this, oldRow, newRow, beforeAction, rollback); + if (done) { + return true; + } + } + } + return false; + } + + public boolean isGlobalTemporary() { + return false; + } + + /** + * Check if this table can be truncated. + * + * @return true if it can + */ + public boolean canTruncate() { + return false; + } + + /** + * Enable or disable foreign key constraint checking for this table. + * + * @param session the session + * @param enabled true if checking should be enabled + * @param checkExisting true if existing rows must be checked during this + * call + */ + public void setCheckForeignKeyConstraints(SessionLocal session, boolean enabled, boolean checkExisting) { + if (enabled && checkExisting) { + if (constraints != null) { + for (Constraint c : constraints) { + if (c.getConstraintType() == Type.REFERENTIAL) { + c.checkExistingData(session); + } + } + } + } + checkForeignKeyConstraints = enabled; + } + + /** + * @return is foreign key constraint checking enabled for this table. + */ + public boolean getCheckForeignKeyConstraints() { + return checkForeignKeyConstraints; + } + + /** + * Get the index that has the given column as the first element. + * This method returns null if no matching index is found. + * + * @param column the column + * @param needGetFirstOrLast if the returned index must be able + * to do {@link Index#canGetFirstOrLast()} + * @param needFindNext if the returned index must be able to do + * {@link Index#findNext(SessionLocal, SearchRow, SearchRow)} + * @return the index or null + */ + public Index getIndexForColumn(Column column, + boolean needGetFirstOrLast, boolean needFindNext) { + ArrayList indexes = getIndexes(); + Index result = null; + if (indexes != null) { + for (int i = 1, size = indexes.size(); i < size; i++) { + Index index = indexes.get(i); + if (needGetFirstOrLast && !index.canGetFirstOrLast()) { + continue; + } + if (needFindNext && !index.canFindNext()) { + continue; + } + // choose the minimal covering index with the needed first + // column to work consistently with execution plan from + // Optimizer + if (index.isFirstColumn(column) && (result == null || + result.getColumns().length > index.getColumns().length)) { + result = index; + } + } + } + return result; + } + + public boolean getOnCommitDrop() { + return onCommitDrop; + } + + public void setOnCommitDrop(boolean onCommitDrop) { + this.onCommitDrop = onCommitDrop; + } + + public boolean getOnCommitTruncate() { + return onCommitTruncate; + } + + public void setOnCommitTruncate(boolean onCommitTruncate) { + this.onCommitTruncate = onCommitTruncate; + } + + /** + * If the index is still required by a constraint, transfer the ownership to + * it. Otherwise, the index is removed. + * + * @param session the session + * @param index the index that is no longer required + */ + public void removeIndexOrTransferOwnership(SessionLocal session, Index index) { + boolean stillNeeded = false; + if (constraints != null) { + for (Constraint cons : constraints) { + if (cons.usesIndex(index)) { + cons.setIndexOwner(index); + database.updateMeta(session, cons); + stillNeeded = true; + } + } + } + if (!stillNeeded) { + database.removeSchemaObject(session, index); + } + } + + /** + * Removes dependencies of column expressions, used for tables with circular + * dependencies. + * + * @param session the session + */ + public void removeColumnExpressionsDependencies(SessionLocal session) { + for (Column column : columns) { + column.setDefaultExpression(session, null); + column.setOnUpdateExpression(session, null); + } + } + + /** + * Check if a deadlock occurred. This method is called recursively. There is + * a circle if the session to be tested has already being visited. If this + * session is part of the circle (if it is the clash session), the method + * must return an empty object array. Once a deadlock has been detected, the + * methods must add the session to the list. If this session is not part of + * the circle, or if no deadlock is detected, this method returns null. + * + * @param session the session to be tested for + * @param clash set with sessions already visited, and null when starting + * verification + * @param visited set with sessions already visited, and null when starting + * verification + * @return an object array with the sessions involved in the deadlock, or + * null + */ + @SuppressWarnings("unused") + public ArrayList checkDeadlock(SessionLocal session, SessionLocal clash, + Set visited) { + return null; + } + + public boolean isPersistIndexes() { + return persistIndexes; + } + + public boolean isPersistData() { + return persistData; + } + + /** + * Compare two values with the current comparison mode. The values may be of + * different type. + * + * @param provider the cast information provider + * @param a the first value + * @param b the second value + * @return 0 if both values are equal, -1 if the first value is smaller, and + * 1 otherwise + */ + public int compareValues(CastDataProvider provider, Value a, Value b) { + return a.compareTo(b, provider, compareMode); + } + + public CompareMode getCompareMode() { + return compareMode; + } + + /** + * Tests if the table can be written. Usually, this depends on the + * database.checkWritingAllowed method, but some tables (eg. TableLink) + * overwrite this default behaviour. + */ + public void checkWritingAllowed() { + database.checkWritingAllowed(); + } + + @Override + public boolean isHidden() { + return isHidden; + } + + public void setHidden(boolean hidden) { + this.isHidden = hidden; + } + + /** + * Views, function tables, links, etc. do not support locks + * @return true if table supports row-level locks + */ + public boolean isRowLockable() { + return false; + } + + public void setTableExpression(boolean tableExpression) { + this.tableExpression = tableExpression; + } + + public boolean isTableExpression() { + return tableExpression; + } + + /** + * Return list of triggers. + * + * @return list of triggers + */ + public ArrayList getTriggers() { + return triggers; + } + + /** + * Returns ID of main index column, or {@link SearchRow#ROWID_INDEX}. + * + * @return ID of main index column, or {@link SearchRow#ROWID_INDEX} + */ + public int getMainIndexColumn() { + return SearchRow.ROWID_INDEX; + } + +} diff --git a/h2/src/main/org/h2/table/TableBase.java b/h2/src/main/org/h2/table/TableBase.java new file mode 100644 index 0000000..a2858ba --- /dev/null +++ b/h2/src/main/org/h2/table/TableBase.java @@ -0,0 +1,159 @@ +/* + * 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.Collections; +import java.util.List; +import org.h2.command.ddl.CreateTableData; +import org.h2.engine.Database; +import org.h2.index.IndexType; +import org.h2.result.SearchRow; +import org.h2.result.SortOrder; +import org.h2.util.StringUtils; +import org.h2.value.Value; + +/** + * The base class of a regular table, or a user defined table. + * + * @author Thomas Mueller + * @author Sergi Vladykin + */ +public abstract class TableBase extends Table { + + /** + * The table engine used (null for regular tables). + */ + private final String tableEngine; + /** Provided table parameters */ + private final List tableEngineParams; + + private final boolean globalTemporary; + + /** + * Returns main index column if index is an primary key index and has only + * one column with _ROWID_ compatible data type. + * + * @param indexType type of an index + * @param cols columns of the index + * @return main index column or {@link SearchRow#ROWID_INDEX} + */ + public static int getMainIndexColumn(IndexType indexType, IndexColumn[] cols) { + if (!indexType.isPrimaryKey() || cols.length != 1) { + return SearchRow.ROWID_INDEX; + } + IndexColumn first = cols[0]; + if ((first.sortType & SortOrder.DESCENDING) != 0) { + return SearchRow.ROWID_INDEX; + } + switch (first.column.getType().getValueType()) { + case Value.TINYINT: + case Value.SMALLINT: + case Value.INTEGER: + case Value.BIGINT: + return first.column.getColumnId(); + default: + return SearchRow.ROWID_INDEX; + } + } + + public TableBase(CreateTableData data) { + super(data.schema, data.id, data.tableName, + data.persistIndexes, data.persistData); + this.tableEngine = data.tableEngine; + this.globalTemporary = data.globalTemporary; + if (data.tableEngineParams != null) { + this.tableEngineParams = data.tableEngineParams; + } else { + this.tableEngineParams = Collections.emptyList(); + } + setTemporary(data.temporary); + setColumns(data.columns.toArray(new Column[0])); + } + + @Override + public String getDropSQL() { + StringBuilder builder = new StringBuilder("DROP TABLE IF EXISTS "); + getSQL(builder, DEFAULT_SQL_FLAGS).append(" CASCADE"); + return builder.toString(); + } + + @Override + public String getCreateSQLForMeta() { + return getCreateSQL(true); + } + + @Override + public String getCreateSQL() { + return getCreateSQL(false); + } + + private String getCreateSQL(boolean forMeta) { + Database db = getDatabase(); + if (db == null) { + // closed + return null; + } + StringBuilder buff = new StringBuilder("CREATE "); + if (isTemporary()) { + if (isGlobalTemporary()) { + buff.append("GLOBAL "); + } else { + buff.append("LOCAL "); + } + buff.append("TEMPORARY "); + } else if (isPersistIndexes()) { + buff.append("CACHED "); + } else { + buff.append("MEMORY "); + } + buff.append("TABLE "); + if (isHidden) { + buff.append("IF NOT EXISTS "); + } + getSQL(buff, DEFAULT_SQL_FLAGS); + if (comment != null) { + buff.append(" COMMENT "); + StringUtils.quoteStringSQL(buff, comment); + } + buff.append("(\n "); + for (int i = 0, l = columns.length; i < l; i++) { + if (i > 0) { + buff.append(",\n "); + } + buff.append(columns[i].getCreateSQL(forMeta)); + } + buff.append("\n)"); + if (tableEngine != null) { + String d = db.getSettings().defaultTableEngine; + if (d == null || !tableEngine.endsWith(d)) { + buff.append("\nENGINE "); + StringUtils.quoteIdentifier(buff, tableEngine); + } + } + if (!tableEngineParams.isEmpty()) { + buff.append("\nWITH "); + for (int i = 0, l = tableEngineParams.size(); i < l; i++) { + if (i > 0) { + buff.append(", "); + } + StringUtils.quoteIdentifier(buff, tableEngineParams.get(i)); + } + } + if (!isPersistIndexes() && !isPersistData()) { + buff.append("\nNOT PERSISTENT"); + } + if (isHidden) { + buff.append("\nHIDDEN"); + } + return buff.toString(); + } + + @Override + public boolean isGlobalTemporary() { + return globalTemporary; + } + +} diff --git a/h2/src/main/org/h2/table/TableFilter.java b/h2/src/main/org/h2/table/TableFilter.java new file mode 100644 index 0000000..cd0a952 --- /dev/null +++ b/h2/src/main/org/h2/table/TableFilter.java @@ -0,0 +1,1250 @@ +/* + * 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.Comparator; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map.Entry; + +import org.h2.api.ErrorCode; +import org.h2.command.query.AllColumnsForPlan; +import org.h2.command.query.Select; +import org.h2.engine.Database; +import org.h2.engine.Right; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.expression.condition.Comparison; +import org.h2.expression.condition.ConditionAndOr; +import org.h2.index.Index; +import org.h2.index.IndexCondition; +import org.h2.index.IndexCursor; +import org.h2.message.DbException; +import org.h2.result.Row; +import org.h2.result.SearchRow; +import org.h2.result.SortOrder; +import org.h2.util.HasSQL; +import org.h2.util.ParserUtil; +import org.h2.util.StringUtils; +import org.h2.util.Utils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueBigint; +import org.h2.value.ValueInteger; +import org.h2.value.ValueNull; +import org.h2.value.ValueSmallint; +import org.h2.value.ValueTinyint; + +/** + * A table filter represents a table that is used in a query. There is one such + * object whenever a table (or view) is used in a query. For example the + * following query has 2 table filters: SELECT * FROM TEST T1, TEST T2. + */ +public class TableFilter implements ColumnResolver { + + private static final int BEFORE_FIRST = 0, FOUND = 1, AFTER_LAST = 2, NULL_ROW = 3; + + /** + * Comparator that uses order in FROM clause as a sort key. + */ + public static final Comparator ORDER_IN_FROM_COMPARATOR = + Comparator.comparing(TableFilter::getOrderInFrom); + + /** + * A visitor that sets joinOuterIndirect to true. + */ + private static final TableFilterVisitor JOI_VISITOR = f -> f.joinOuterIndirect = true; + + /** + * Whether this is a direct or indirect (nested) outer join + */ + protected boolean joinOuterIndirect; + + private SessionLocal session; + + private final Table table; + private final Select select; + private String alias; + private Index index; + private final IndexHints indexHints; + private int[] masks; + private int scanCount; + private boolean evaluatable; + + /** + * Indicates that this filter is used in the plan. + */ + private boolean used; + + /** + * The filter used to walk through the index. + */ + private final IndexCursor cursor; + + /** + * The index conditions used for direct index lookup (start or end). + */ + private final ArrayList indexConditions = Utils.newSmallArrayList(); + + /** + * Additional conditions that can't be used for index lookup, but for row + * filter for this table (ID=ID, NAME LIKE '%X%') + */ + private Expression filterCondition; + + /** + * The complete join condition. + */ + private Expression joinCondition; + + private SearchRow currentSearchRow; + private Row current; + private int state; + + /** + * The joined table (if there is one). + */ + private TableFilter join; + + /** + * Whether this is an outer join. + */ + private boolean joinOuter; + + /** + * The nested joined table (if there is one). + */ + private TableFilter nestedJoin; + + /** + * Map of common join columns, used for NATURAL joins and USING clause of + * other joins. This map preserves original order of the columns. + */ + private LinkedHashMap commonJoinColumns; + + private TableFilter commonJoinColumnsFilter; + private ArrayList commonJoinColumnsToExclude; + private boolean foundOne; + private Expression fullCondition; + private final int hashCode; + private final int orderInFrom; + + /** + * Map of derived column names. This map preserves original order of the + * columns. + */ + private LinkedHashMap derivedColumnMap; + + /** + * Create a new table filter object. + * + * @param session the session + * @param table the table from where to read data + * @param alias the alias name + * @param rightsChecked true if rights are already checked + * @param select the select statement + * @param orderInFrom original order number (index) of this table filter in + * @param indexHints the index hints to be used by the query planner + */ + public TableFilter(SessionLocal session, Table table, String alias, + boolean rightsChecked, Select select, int orderInFrom, IndexHints indexHints) { + this.session = session; + this.table = table; + this.alias = alias; + this.select = select; + this.cursor = new IndexCursor(); + if (!rightsChecked) { + session.getUser().checkTableRight(table, Right.SELECT); + } + hashCode = session.nextObjectId(); + this.orderInFrom = orderInFrom; + this.indexHints = indexHints; + } + + /** + * Get the order number (index) of this table filter in the "from" clause of + * the query. + * + * @return the index (0, 1, 2,...) + */ + public int getOrderInFrom() { + return orderInFrom; + } + + public IndexCursor getIndexCursor() { + return cursor; + } + + @Override + public Select getSelect() { + return select; + } + + public Table getTable() { + return table; + } + + /** + * Lock the table. This will also lock joined tables. + * + * @param s the session + */ + public void lock(SessionLocal s) { + table.lock(s, Table.READ_LOCK); + if (join != null) { + join.lock(s); + } + } + + /** + * Get the best plan item (index, cost) to use for the current join + * order. + * + * @param s the session + * @param filters all joined table filters + * @param filter the current table filter index + * @param allColumnsSet the set of all columns + * @return the best plan item + */ + public PlanItem getBestPlanItem(SessionLocal s, TableFilter[] filters, int filter, + AllColumnsForPlan allColumnsSet) { + PlanItem item1 = null; + SortOrder sortOrder = null; + if (select != null) { + sortOrder = select.getSortOrder(); + } + if (indexConditions.isEmpty()) { + item1 = new PlanItem(); + item1.setIndex(table.getScanIndex(s, null, filters, filter, + sortOrder, allColumnsSet)); + item1.cost = item1.getIndex().getCost(s, null, filters, filter, + sortOrder, allColumnsSet); + } + int len = table.getColumns().length; + int[] masks = new int[len]; + for (IndexCondition condition : indexConditions) { + if (condition.isEvaluatable()) { + if (condition.isAlwaysFalse()) { + masks = null; + break; + } + int id = condition.getColumn().getColumnId(); + if (id >= 0) { + masks[id] |= condition.getMask(indexConditions); + } + } + } + PlanItem item = table.getBestPlanItem(s, masks, filters, filter, sortOrder, allColumnsSet); + item.setMasks(masks); + // The more index conditions, the earlier the table. + // This is to ensure joins without indexes run quickly: + // x (x.a=10); y (x.b=y.b) - see issue 113 + item.cost -= item.cost * indexConditions.size() / 100 / (filter + 1); + + if (item1 != null && item1.cost < item.cost) { + item = item1; + } + + if (nestedJoin != null) { + setEvaluatable(true); + item.setNestedJoinPlan(nestedJoin.getBestPlanItem(s, filters, filter, allColumnsSet)); + // TODO optimizer: calculate cost of a join: should use separate + // expected row number and lookup cost + item.cost += item.cost * item.getNestedJoinPlan().cost; + } + if (join != null) { + setEvaluatable(true); + do { + filter++; + } while (filters[filter] != join); + item.setJoinPlan(join.getBestPlanItem(s, filters, filter, allColumnsSet)); + // TODO optimizer: calculate cost of a join: should use separate + // expected row number and lookup cost + item.cost += item.cost * item.getJoinPlan().cost; + } + return item; + } + + /** + * Set what plan item (index, cost, masks) to use. + * + * @param item the plan item + */ + public void setPlanItem(PlanItem item) { + if (item == null) { + // invalid plan, most likely because a column wasn't found + // this will result in an exception later on + return; + } + setIndex(item.getIndex()); + masks = item.getMasks(); + if (nestedJoin != null) { + if (item.getNestedJoinPlan() != null) { + nestedJoin.setPlanItem(item.getNestedJoinPlan()); + } else { + nestedJoin.setScanIndexes(); + } + } + if (join != null) { + if (item.getJoinPlan() != null) { + join.setPlanItem(item.getJoinPlan()); + } else { + join.setScanIndexes(); + } + } + } + + /** + * Set all missing indexes to scan indexes recursively. + */ + private void setScanIndexes() { + if (index == null) { + setIndex(table.getScanIndex(session)); + } + if (join != null) { + join.setScanIndexes(); + } + if (nestedJoin != null) { + nestedJoin.setScanIndexes(); + } + } + + /** + * Prepare reading rows. This method will remove all index conditions that + * can not be used, and optimize the conditions. + */ + public void prepare() { + // forget all unused index conditions + // the indexConditions list may be modified here + for (int i = 0; i < indexConditions.size(); i++) { + IndexCondition condition = indexConditions.get(i); + if (!condition.isAlwaysFalse()) { + Column col = condition.getColumn(); + if (col.getColumnId() >= 0) { + if (index.getColumnIndex(col) < 0) { + indexConditions.remove(i); + i--; + } + } + } + } + if (nestedJoin != null) { + if (nestedJoin == this) { + throw DbException.getInternalError("self join"); + } + nestedJoin.prepare(); + } + if (join != null) { + if (join == this) { + throw DbException.getInternalError("self join"); + } + join.prepare(); + } + if (filterCondition != null) { + filterCondition = filterCondition.optimizeCondition(session); + } + if (joinCondition != null) { + joinCondition = joinCondition.optimizeCondition(session); + } + } + + /** + * Start the query. This will reset the scan counts. + * + * @param s the session + */ + public void startQuery(SessionLocal s) { + this.session = s; + scanCount = 0; + if (nestedJoin != null) { + nestedJoin.startQuery(s); + } + if (join != null) { + join.startQuery(s); + } + } + + /** + * Reset to the current position. + */ + public void reset() { + if (nestedJoin != null) { + nestedJoin.reset(); + } + if (join != null) { + join.reset(); + } + state = BEFORE_FIRST; + foundOne = false; + } + + /** + * Check if there are more rows to read. + * + * @return true if there are + */ + public boolean next() { + if (state == AFTER_LAST) { + return false; + } else if (state == BEFORE_FIRST) { + cursor.find(session, indexConditions); + if (!cursor.isAlwaysFalse()) { + if (nestedJoin != null) { + nestedJoin.reset(); + } + if (join != null) { + join.reset(); + } + } + } else { + // state == FOUND || NULL_ROW + // the last row was ok - try next row of the join + if (join != null && join.next()) { + return true; + } + } + while (true) { + // go to the next row + if (state == NULL_ROW) { + break; + } + if (cursor.isAlwaysFalse()) { + state = AFTER_LAST; + } else if (nestedJoin != null) { + if (state == BEFORE_FIRST) { + state = FOUND; + } + } else { + if ((++scanCount & 4095) == 0) { + checkTimeout(); + } + if (cursor.next()) { + currentSearchRow = cursor.getSearchRow(); + current = null; + state = FOUND; + } else { + state = AFTER_LAST; + } + } + if (nestedJoin != null && state == FOUND) { + if (!nestedJoin.next()) { + state = AFTER_LAST; + if (joinOuter && !foundOne) { + // possibly null row + } else { + continue; + } + } + } + // if no more rows found, try the null row (for outer joins only) + if (state == AFTER_LAST) { + if (joinOuter && !foundOne) { + setNullRow(); + } else { + break; + } + } + if (!isOk(filterCondition)) { + continue; + } + boolean joinConditionOk = isOk(joinCondition); + if (state == FOUND) { + if (joinConditionOk) { + foundOne = true; + } else { + continue; + } + } + if (join != null) { + join.reset(); + if (!join.next()) { + continue; + } + } + // check if it's ok + if (state == NULL_ROW || joinConditionOk) { + return true; + } + } + state = AFTER_LAST; + return false; + } + + public boolean isNullRow() { + return state == NULL_ROW; + } + + /** + * Set the state of this and all nested tables to the NULL row. + */ + protected void setNullRow() { + state = NULL_ROW; + current = table.getNullRow(); + currentSearchRow = current; + if (nestedJoin != null) { + nestedJoin.visit(TableFilter::setNullRow); + } + } + + private void checkTimeout() { + session.checkCanceled(); + } + + /** + * Whether the current value of the condition is true, or there is no + * condition. + * + * @param condition the condition (null for no condition) + * @return true if yes + */ + boolean isOk(Expression condition) { + return condition == null || condition.getBooleanValue(session); + } + + /** + * Get the current row. + * + * @return the current row, or null + */ + public Row get() { + if (current == null && currentSearchRow != null) { + current = cursor.get(); + } + return current; + } + + /** + * Set the current row. + * + * @param current the current row + */ + public void set(Row current) { + this.current = current; + this.currentSearchRow = current; + } + + /** + * Get the table alias name. If no alias is specified, the table name is + * returned. + * + * @return the alias name + */ + @Override + public String getTableAlias() { + if (alias != null) { + return alias; + } + return table.getName(); + } + + /** + * Add an index condition. + * + * @param condition the index condition + */ + public void addIndexCondition(IndexCondition condition) { + indexConditions.add(condition); + } + + /** + * Add a filter condition. + * + * @param condition the condition + * @param isJoin if this is in fact a join condition + */ + public void addFilterCondition(Expression condition, boolean isJoin) { + if (isJoin) { + if (joinCondition == null) { + joinCondition = condition; + } else { + joinCondition = new ConditionAndOr(ConditionAndOr.AND, + joinCondition, condition); + } + } else { + if (filterCondition == null) { + filterCondition = condition; + } else { + filterCondition = new ConditionAndOr(ConditionAndOr.AND, + filterCondition, condition); + } + } + } + + /** + * Add a joined table. + * + * @param filter the joined table filter + * @param outer if this is an outer join + * @param on the join condition + */ + public void addJoin(TableFilter filter, boolean outer, Expression on) { + if (on != null) { + on.mapColumns(this, 0, Expression.MAP_INITIAL); + TableFilterVisitor visitor = new MapColumnsVisitor(on); + visit(visitor); + filter.visit(visitor); + } + if (join == null) { + join = filter; + filter.joinOuter = outer; + if (outer) { + filter.visit(JOI_VISITOR); + } + if (on != null) { + filter.mapAndAddFilter(on); + } + } else { + join.addJoin(filter, outer, on); + } + } + + /** + * Set a nested joined table. + * + * @param filter the joined table filter + */ + public void setNestedJoin(TableFilter filter) { + nestedJoin = filter; + } + + /** + * Map the columns and add the join condition. + * + * @param on the condition + */ + public void mapAndAddFilter(Expression on) { + on.mapColumns(this, 0, Expression.MAP_INITIAL); + addFilterCondition(on, true); + if (nestedJoin != null) { + on.mapColumns(nestedJoin, 0, Expression.MAP_INITIAL); + } + if (join != null) { + join.mapAndAddFilter(on); + } + } + + /** + * Create the index conditions for this filter if needed. + */ + public void createIndexConditions() { + if (joinCondition != null) { + joinCondition = joinCondition.optimizeCondition(session); + if (joinCondition != null) { + joinCondition.createIndexConditions(session, this); + if (nestedJoin != null) { + joinCondition.createIndexConditions(session, nestedJoin); + } + } + } + if (join != null) { + join.createIndexConditions(); + } + if (nestedJoin != null) { + nestedJoin.createIndexConditions(); + } + } + + public TableFilter getJoin() { + return join; + } + + /** + * Whether this is an outer joined table. + * + * @return true if it is + */ + public boolean isJoinOuter() { + return joinOuter; + } + + /** + * Whether this is indirectly an outer joined table (nested within an inner + * join). + * + * @return true if it is + */ + public boolean isJoinOuterIndirect() { + return joinOuterIndirect; + } + + /** + * Get the query execution plan text to use for this table filter and append + * it to the specified builder. + * + * @param builder string builder to append to + * @param isJoin if this is a joined table + * @param sqlFlags formatting flags + * @return the specified builder + */ + public StringBuilder getPlanSQL(StringBuilder builder, boolean isJoin, int sqlFlags) { + if (isJoin) { + if (joinOuter) { + builder.append("LEFT OUTER JOIN "); + } else { + builder.append("INNER JOIN "); + } + } + if (nestedJoin != null) { + StringBuilder buffNested = new StringBuilder(); + TableFilter n = nestedJoin; + do { + n.getPlanSQL(buffNested, n != nestedJoin, sqlFlags).append('\n'); + n = n.getJoin(); + } while (n != null); + String nested = buffNested.toString(); + boolean enclose = !nested.startsWith("("); + if (enclose) { + builder.append("(\n"); + } + StringUtils.indent(builder, nested, 4, false); + if (enclose) { + builder.append(')'); + } + if (isJoin) { + builder.append(" ON "); + if (joinCondition == null) { + // need to have a ON expression, + // otherwise the nesting is unclear + builder.append("1=1"); + } else { + joinCondition.getUnenclosedSQL(builder, sqlFlags); + } + } + return builder; + } + if (table instanceof TableView && ((TableView) table).isRecursive()) { + table.getSchema().getSQL(builder, sqlFlags).append('.'); + ParserUtil.quoteIdentifier(builder, table.getName(), sqlFlags); + } else { + table.getSQL(builder, sqlFlags); + } + if (table instanceof TableView && ((TableView) table).isInvalid()) { + throw DbException.get(ErrorCode.VIEW_IS_INVALID_2, table.getName(), "not compiled"); + } + if (alias != null) { + builder.append(' '); + ParserUtil.quoteIdentifier(builder, alias, sqlFlags); + if (derivedColumnMap != null) { + builder.append('('); + boolean f = false; + for (String name : derivedColumnMap.values()) { + if (f) { + builder.append(", "); + } + f = true; + ParserUtil.quoteIdentifier(builder, name, sqlFlags); + } + builder.append(')'); + } + } + if (indexHints != null) { + builder.append(" USE INDEX ("); + boolean first = true; + for (String index : indexHints.getAllowedIndexes()) { + if (!first) { + builder.append(", "); + } else { + first = false; + } + ParserUtil.quoteIdentifier(builder, index, sqlFlags); + } + builder.append(")"); + } + if (index != null && (sqlFlags & HasSQL.ADD_PLAN_INFORMATION) != 0) { + builder.append('\n'); + StringBuilder planBuilder = new StringBuilder().append("/* ").append(index.getPlanSQL()); + if (!indexConditions.isEmpty()) { + planBuilder.append(": "); + for (int i = 0, size = indexConditions.size(); i < size; i++) { + if (i > 0) { + planBuilder.append("\n AND "); + } + planBuilder.append(indexConditions.get(i).getSQL( + HasSQL.TRACE_SQL_FLAGS | HasSQL.ADD_PLAN_INFORMATION)); + } + } + if (planBuilder.indexOf("\n", 3) >= 0) { + planBuilder.append('\n'); + } + StringUtils.indent(builder, planBuilder.append(" */").toString(), 4, false); + } + if (isJoin) { + builder.append("\n ON "); + if (joinCondition == null) { + // need to have a ON expression, otherwise the nesting is + // unclear + builder.append("1=1"); + } else { + joinCondition.getUnenclosedSQL(builder, sqlFlags); + } + } + if ((sqlFlags & HasSQL.ADD_PLAN_INFORMATION) != 0) { + if (filterCondition != null) { + builder.append('\n'); + String condition = filterCondition.getSQL(HasSQL.TRACE_SQL_FLAGS | HasSQL.ADD_PLAN_INFORMATION, + Expression.WITHOUT_PARENTHESES); + condition = "/* WHERE " + condition + "\n*/"; + StringUtils.indent(builder, condition, 4, false); + } + if (scanCount > 0) { + builder.append("\n /* scanCount: ").append(scanCount).append(" */"); + } + } + return builder; + } + + /** + * Remove all index conditions that are not used by the current index. + */ + void removeUnusableIndexConditions() { + // the indexConditions list may be modified here + for (int i = 0; i < indexConditions.size(); i++) { + IndexCondition cond = indexConditions.get(i); + if (cond.getMask(indexConditions) == 0 || !cond.isEvaluatable()) { + indexConditions.remove(i--); + } + } + } + + public int[] getMasks() { + return masks; + } + + public ArrayList getIndexConditions() { + return indexConditions; + } + + public Index getIndex() { + return index; + } + + public void setIndex(Index index) { + this.index = index; + cursor.setIndex(index); + } + + public void setUsed(boolean used) { + this.used = used; + } + + public boolean isUsed() { + return used; + } + + /** + * Remove the joined table + */ + public void removeJoin() { + this.join = null; + } + + public Expression getJoinCondition() { + return joinCondition; + } + + /** + * Remove the join condition. + */ + public void removeJoinCondition() { + this.joinCondition = null; + } + + public Expression getFilterCondition() { + return filterCondition; + } + + /** + * Remove the filter condition. + */ + public void removeFilterCondition() { + this.filterCondition = null; + } + + public void setFullCondition(Expression condition) { + this.fullCondition = condition; + if (join != null) { + join.setFullCondition(condition); + } + } + + /** + * Optimize the full condition. This will add the full condition to the + * filter condition. + */ + void optimizeFullCondition() { + if (!joinOuter && fullCondition != null) { + fullCondition.addFilterConditions(this); + if (nestedJoin != null) { + nestedJoin.optimizeFullCondition(); + } + if (join != null) { + join.optimizeFullCondition(); + } + } + } + + /** + * Update the filter and join conditions of this and all joined tables with + * the information that the given table filter and all nested filter can now + * return rows or not. + * + * @param filter the table filter + * @param b the new flag + */ + public void setEvaluatable(TableFilter filter, boolean b) { + filter.setEvaluatable(b); + if (filterCondition != null) { + filterCondition.setEvaluatable(filter, b); + } + if (joinCondition != null) { + joinCondition.setEvaluatable(filter, b); + } + if (nestedJoin != null) { + // don't enable / disable the nested join filters + // if enabling a filter in a joined filter + if (this == filter) { + nestedJoin.setEvaluatable(nestedJoin, b); + } + } + if (join != null) { + join.setEvaluatable(filter, b); + } + } + + public void setEvaluatable(boolean evaluatable) { + this.evaluatable = evaluatable; + } + + @Override + public String getSchemaName() { + if (alias == null && !(table instanceof VirtualTable)) { + return table.getSchema().getName(); + } + return null; + } + + @Override + public Column[] getColumns() { + return table.getColumns(); + } + + @Override + public Column findColumn(String name) { + HashMap map = derivedColumnMap; + if (map != null) { + Database db = session.getDatabase(); + for (Entry entry : derivedColumnMap.entrySet()) { + if (db.equalsIdentifiers(entry.getValue(), name)) { + return entry.getKey(); + } + } + return null; + } + return table.findColumn(name); + } + + @Override + public String getColumnName(Column column) { + HashMap map = derivedColumnMap; + return map != null ? map.get(column) : column.getName(); + } + + @Override + public boolean hasDerivedColumnList() { + return derivedColumnMap != null; + } + + /** + * 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 + * @return the column + * @throws DbException + * if the column was not found and {@code ifExists} is + * {@code false} + */ + public Column getColumn(String columnName, boolean ifExists) { + HashMap map = derivedColumnMap; + if (map != null) { + Database database = session.getDatabase(); + for (Entry entry : map.entrySet()) { + if (database.equalsIdentifiers(columnName, entry.getValue())) { + return entry.getKey(); + } + } + if (ifExists) { + return null; + } else { + throw DbException.get(ErrorCode.COLUMN_NOT_FOUND_1, columnName); + } + } + return table.getColumn(columnName, ifExists); + } + + /** + * Get the system columns that this table understands. This is used for + * compatibility with other databases. The columns are only returned if the + * current mode supports system columns. + * + * @return the system columns + */ + @Override + public Column[] getSystemColumns() { + if (!session.getDatabase().getMode().systemColumns) { + return null; + } + Column[] sys = { // + new Column("oid", TypeInfo.TYPE_INTEGER, table, 0), // + new Column("ctid", TypeInfo.TYPE_VARCHAR, table, 0) // + }; + return sys; + } + + @Override + public Column getRowIdColumn() { + return table.getRowIdColumn(); + } + + @Override + public Value getValue(Column column) { + if (currentSearchRow == null) { + return null; + } + int columnId = column.getColumnId(); + if (columnId == -1) { + return ValueBigint.get(currentSearchRow.getKey()); + } + if (current == null) { + Value v = currentSearchRow.getValue(columnId); + if (v != null) { + return v; + } + if (columnId == column.getTable().getMainIndexColumn()) { + return getDelegatedValue(column); + } + current = cursor.get(); + if (current == null) { + return ValueNull.INSTANCE; + } + } + return current.getValue(columnId); + } + + private Value getDelegatedValue(Column column) { + long key = currentSearchRow.getKey(); + switch (column.getType().getValueType()) { + case Value.TINYINT: + return ValueTinyint.get((byte) key); + case Value.SMALLINT: + return ValueSmallint.get((short) key); + case Value.INTEGER: + return ValueInteger.get((int) key); + case Value.BIGINT: + return ValueBigint.get(key); + default: + throw DbException.getInternalError(); + } + } + + @Override + public TableFilter getTableFilter() { + return this; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + /** + * Set derived column list. + * + * @param derivedColumnNames names of derived columns + */ + public void setDerivedColumns(ArrayList derivedColumnNames) { + Column[] columns = getColumns(); + int count = columns.length; + if (count != derivedColumnNames.size()) { + throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); + } + LinkedHashMap map = new LinkedHashMap<>(); + for (int i = 0; i < count; i++) { + String alias = derivedColumnNames.get(i); + for (int j = 0; j < i; j++) { + if (alias.equals(derivedColumnNames.get(j))) { + throw DbException.get(ErrorCode.DUPLICATE_COLUMN_NAME_1, alias); + } + } + map.put(columns[i], alias); + } + this.derivedColumnMap = map; + } + + @Override + public String toString() { + return alias != null ? alias : table.toString(); + } + + /** + * Add a column to the common join column list for a left table filter. + * + * @param leftColumn + * the column on the left side + * @param replacementColumn + * the column to use instead, may be the same as column on the + * left side + * @param replacementFilter + * the table filter for replacement columns + */ + public void addCommonJoinColumns(Column leftColumn, Column replacementColumn, TableFilter replacementFilter) { + if (commonJoinColumns == null) { + commonJoinColumns = new LinkedHashMap<>(); + commonJoinColumnsFilter = replacementFilter; + } else { + assert commonJoinColumnsFilter == replacementFilter; + } + commonJoinColumns.put(leftColumn, replacementColumn); + } + + /** + * Add an excluded column to the common join column list. + * + * @param columnToExclude + * the column to exclude + */ + public void addCommonJoinColumnToExclude(Column columnToExclude) { + if (commonJoinColumnsToExclude == null) { + commonJoinColumnsToExclude = Utils.newSmallArrayList(); + } + commonJoinColumnsToExclude.add(columnToExclude); + } + + /** + * Returns common join columns map. + * + * @return common join columns map, or {@code null} + */ + public LinkedHashMap getCommonJoinColumns() { + return commonJoinColumns; + } + + /** + * Returns common join columns table filter. + * + * @return common join columns table filter, or {@code null} + */ + public TableFilter getCommonJoinColumnsFilter() { + return commonJoinColumnsFilter; + } + + /** + * Check if the given column is an excluded common join column. + * + * @param c + * the column to check + * @return true if this is an excluded common join column + */ + public boolean isCommonJoinColumnToExclude(Column c) { + return commonJoinColumnsToExclude != null && commonJoinColumnsToExclude.contains(c); + } + + @Override + public int hashCode() { + return hashCode; + } + + /** + * Are there any index conditions that involve IN(...). + * + * @return whether there are IN(...) comparisons + */ + public boolean hasInComparisons() { + for (IndexCondition cond : indexConditions) { + int compareType = cond.getCompareType(); + if (compareType == Comparison.IN_QUERY || compareType == Comparison.IN_LIST) { + return true; + } + } + return false; + } + + public TableFilter getNestedJoin() { + return nestedJoin; + } + + /** + * Visit this and all joined or nested table filters. + * + * @param visitor the visitor + */ + public void visit(TableFilterVisitor visitor) { + TableFilter f = this; + do { + visitor.accept(f); + TableFilter n = f.nestedJoin; + if (n != null) { + n.visit(visitor); + } + f = f.join; + } while (f != null); + } + + public boolean isEvaluatable() { + return evaluatable; + } + + public SessionLocal getSession() { + return session; + } + + public IndexHints getIndexHints() { + return indexHints; + } + + /** + * Returns whether this is a table filter with implicit DUAL table for a + * SELECT without a FROM clause. + * + * @return whether this is a table filter with implicit DUAL table + */ + public boolean isNoFromClauseFilter() { + return table instanceof DualTable && join == null && nestedJoin == null + && joinCondition == null && filterCondition == null; + } + + /** + * A visitor for table filters. + */ + public interface TableFilterVisitor { + + /** + * This method is called for each nested or joined table filter. + * + * @param f the filter + */ + void accept(TableFilter f); + } + + /** + * A visitor that maps columns. + */ + private static final class MapColumnsVisitor implements TableFilterVisitor { + private final Expression on; + + MapColumnsVisitor(Expression on) { + this.on = on; + } + + @Override + public void accept(TableFilter f) { + on.mapColumns(f, 0, Expression.MAP_INITIAL); + } + } + +} diff --git a/h2/src/main/org/h2/table/TableLink.java b/h2/src/main/org/h2/table/TableLink.java new file mode 100644 index 0000000..ca34042 --- /dev/null +++ b/h2/src/main/org/h2/table/TableLink.java @@ -0,0 +1,740 @@ +/* + * 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.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; + +import org.h2.api.ErrorCode; +import org.h2.command.Prepared; +import org.h2.engine.SessionLocal; +import org.h2.index.Index; +import org.h2.index.IndexType; +import org.h2.index.LinkedIndex; +import org.h2.jdbc.JdbcConnection; +import org.h2.jdbc.JdbcResultSet; +import org.h2.message.DbException; +import org.h2.result.LocalResult; +import org.h2.result.ResultInterface; +import org.h2.result.Row; +import org.h2.schema.Schema; +import org.h2.util.JdbcUtils; +import org.h2.util.StringUtils; +import org.h2.util.Utils; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueDate; +import org.h2.value.ValueTime; +import org.h2.value.ValueTimestamp; + +/** + * A linked table contains connection information for a table accessible by + * JDBC. The table may be stored in a different database. + */ +public class TableLink extends Table { + + private static final int MAX_RETRY = 2; + + private static final long ROW_COUNT_APPROXIMATION = 100_000; + + private final String originalSchema; + private String driver, url, user, password, originalTable, qualifiedTableName; + private TableLinkConnection conn; + private HashMap preparedMap = new HashMap<>(); + private final ArrayList indexes = Utils.newSmallArrayList(); + private final boolean emitUpdates; + private LinkedIndex linkedIndex; + private DbException connectException; + private boolean storesLowerCase; + private boolean storesMixedCase; + private boolean storesMixedCaseQuoted; + private boolean supportsMixedCaseIdentifiers; + private boolean globalTemporary; + private boolean readOnly; + private final boolean targetsMySql; + private int fetchSize = 0; + private boolean autocommit =true; + + public TableLink(Schema schema, int id, String name, String driver, + String url, String user, String password, String originalSchema, + String originalTable, boolean emitUpdates, boolean force) { + super(schema, id, name, false, true); + this.driver = driver; + this.url = url; + this.user = user; + this.password = password; + this.originalSchema = originalSchema; + this.originalTable = originalTable; + this.emitUpdates = emitUpdates; + this.targetsMySql = isMySqlUrl(this.url); + try { + connect(); + } catch (DbException e) { + if (!force) { + throw e; + } + Column[] cols = { }; + setColumns(cols); + linkedIndex = new LinkedIndex(this, id, IndexColumn.wrap(cols), 0, IndexType.createNonUnique(false)); + indexes.add(linkedIndex); + } + } + + private void connect() { + connectException = null; + for (int retry = 0;; retry++) { + try { + conn = database.getLinkConnection(driver, url, user, password); + conn.setAutoCommit(autocommit); + synchronized (conn) { + try { + readMetaData(); + return; + } catch (Exception e) { + // could be SQLException or RuntimeException + conn.close(true); + conn = null; + throw DbException.convert(e); + } + } + } catch (DbException e) { + if (retry >= MAX_RETRY) { + connectException = e; + throw e; + } + } + } + } + + private void readMetaData() throws SQLException { + DatabaseMetaData meta = conn.getConnection().getMetaData(); + storesLowerCase = meta.storesLowerCaseIdentifiers(); + storesMixedCase = meta.storesMixedCaseIdentifiers(); + storesMixedCaseQuoted = meta.storesMixedCaseQuotedIdentifiers(); + supportsMixedCaseIdentifiers = meta.supportsMixedCaseIdentifiers(); + ArrayList columnList = Utils.newSmallArrayList(); + HashMap columnMap = new HashMap<>(); + String schema = null; + boolean isQuery = originalTable.startsWith("("); + if (!isQuery) { + try (ResultSet rs = meta.getTables(null, originalSchema, originalTable, null)) { + if (rs.next() && rs.next()) { + throw DbException.get(ErrorCode.SCHEMA_NAME_MUST_MATCH, originalTable); + } + } + try (ResultSet rs = meta.getColumns(null, originalSchema, originalTable, null)) { + int i = 0; + String catalog = null; + while (rs.next()) { + String thisCatalog = rs.getString("TABLE_CAT"); + if (catalog == null) { + catalog = thisCatalog; + } + String thisSchema = rs.getString("TABLE_SCHEM"); + if (schema == null) { + schema = thisSchema; + } + if (!Objects.equals(catalog, thisCatalog) || + !Objects.equals(schema, thisSchema)) { + // if the table exists in multiple schemas or tables, + // use the alternative solution + columnMap.clear(); + columnList.clear(); + break; + } + String n = rs.getString("COLUMN_NAME"); + n = convertColumnName(n); + int sqlType = rs.getInt("DATA_TYPE"); + String sqlTypeName = rs.getString("TYPE_NAME"); + long precision = rs.getInt("COLUMN_SIZE"); + precision = convertPrecision(sqlType, precision); + int scale = rs.getInt("DECIMAL_DIGITS"); + scale = convertScale(sqlType, scale); + int type = DataType.convertSQLTypeToValueType(sqlType, sqlTypeName); + Column col = new Column(n, TypeInfo.getTypeInfo(type, precision, scale, null), this, i++); + columnList.add(col); + columnMap.put(n, col); + } + } + } + if (originalTable.indexOf('.') < 0 && !StringUtils.isNullOrEmpty(schema)) { + qualifiedTableName = schema + '.' + originalTable; + } else { + qualifiedTableName = originalTable; + } + // check if the table is accessible + + try (Statement stat = conn.getConnection().createStatement(); + ResultSet rs = stat.executeQuery("SELECT * FROM " + qualifiedTableName + " T WHERE 1=0")) { + if (rs instanceof JdbcResultSet) { + ResultInterface result = ((JdbcResultSet) rs).getResult(); + columnList.clear(); + columnMap.clear(); + for (int i = 0, l = result.getVisibleColumnCount(); i < l;) { + String n = result.getColumnName(i); + Column col = new Column(n, result.getColumnType(i), this, ++i); + columnList.add(col); + columnMap.put(n, col); + } + } else if (columnList.isEmpty()) { + // alternative solution + ResultSetMetaData rsMeta = rs.getMetaData(); + for (int i = 0, l = rsMeta.getColumnCount(); i < l;) { + String n = rsMeta.getColumnName(i + 1); + n = convertColumnName(n); + int sqlType = rsMeta.getColumnType(i + 1); + long precision = rsMeta.getPrecision(i + 1); + precision = convertPrecision(sqlType, precision); + int scale = rsMeta.getScale(i + 1); + scale = convertScale(sqlType, scale); + int type = DataType.getValueTypeFromResultSet(rsMeta, i + 1); + Column col = new Column(n, TypeInfo.getTypeInfo(type, precision, scale, null), this, i++); + columnList.add(col); + columnMap.put(n, col); + } + } + } catch (Exception e) { + throw DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, e, + originalTable + '(' + e + ')'); + } + Column[] cols = columnList.toArray(new Column[0]); + setColumns(cols); + int id = getId(); + linkedIndex = new LinkedIndex(this, id, IndexColumn.wrap(cols), 0, IndexType.createNonUnique(false)); + indexes.add(linkedIndex); + if (!isQuery) { + readIndexes(meta, columnMap); + } + } + + private void readIndexes(DatabaseMetaData meta, HashMap columnMap) { + String pkName = null; + try (ResultSet rs = meta.getPrimaryKeys(null, originalSchema, originalTable)) { + if (rs.next()) { + pkName = readPrimaryKey(rs, columnMap); + } + } catch (Exception e) { + // Some ODBC bridge drivers don't support it: + // some combinations of "DataDirect SequeLink(R) for JDBC" + // https://www.progress.com/odbc/sequelink + } + try (ResultSet rs = meta.getIndexInfo(null, originalSchema, originalTable, false, true)) { + readIndexes(rs, columnMap, pkName); + } catch (Exception e) { + // Oracle throws an exception if the table is not found or is a + // SYNONYM + } + } + + private String readPrimaryKey(ResultSet rs, HashMap columnMap) throws SQLException { + String pkName = null; + // the problem is, the rows are not sorted by KEY_SEQ + ArrayList list = Utils.newSmallArrayList(); + do { + int idx = rs.getInt("KEY_SEQ"); + if (StringUtils.isNullOrEmpty(pkName)) { + pkName = rs.getString("PK_NAME"); + } + while (list.size() < idx) { + list.add(null); + } + String col = rs.getString("COLUMN_NAME"); + col = convertColumnName(col); + Column column = columnMap.get(col); + if (idx == 0) { + // workaround for a bug in the SQLite JDBC driver + list.add(column); + } else { + list.set(idx - 1, column); + } + } while (rs.next()); + addIndex(list, list.size(), IndexType.createPrimaryKey(false, false)); + return pkName; + } + + private void readIndexes(ResultSet rs, HashMap columnMap, String pkName) throws SQLException { + String indexName = null; + ArrayList list = Utils.newSmallArrayList(); + int uniqueColumnCount = 0; + IndexType indexType = null; + while (rs.next()) { + if (rs.getShort("TYPE") == DatabaseMetaData.tableIndexStatistic) { + // ignore index statistics + continue; + } + String newIndex = rs.getString("INDEX_NAME"); + if (pkName != null && pkName.equals(newIndex)) { + continue; + } + if (indexName != null && !indexName.equals(newIndex)) { + addIndex(list, uniqueColumnCount, indexType); + uniqueColumnCount = 0; + indexName = null; + } + if (indexName == null) { + indexName = newIndex; + list.clear(); + } + if (!rs.getBoolean("NON_UNIQUE")) { + uniqueColumnCount++; + } + indexType = uniqueColumnCount > 0 ? IndexType.createUnique(false, false) : + IndexType.createNonUnique(false); + String col = rs.getString("COLUMN_NAME"); + col = convertColumnName(col); + Column column = columnMap.get(col); + list.add(column); + } + if (indexName != null) { + addIndex(list, uniqueColumnCount, indexType); + } + } + + private static long convertPrecision(int sqlType, long precision) { + // workaround for an Oracle problem: + // for DATE columns, the reported precision is 7 + // for DECIMAL columns, the reported precision is 0 + switch (sqlType) { + case Types.DECIMAL: + case Types.NUMERIC: + if (precision == 0) { + precision = 65535; + } + break; + case Types.DATE: + precision = Math.max(ValueDate.PRECISION, precision); + break; + case Types.TIMESTAMP: + precision = Math.max(ValueTimestamp.MAXIMUM_PRECISION, precision); + break; + case Types.TIME: + precision = Math.max(ValueTime.MAXIMUM_PRECISION, precision); + break; + } + return precision; + } + + private static int convertScale(int sqlType, int scale) { + // workaround for an Oracle problem: + // for DECIMAL columns, the reported precision is -127 + switch (sqlType) { + case Types.DECIMAL: + case Types.NUMERIC: + if (scale < 0) { + scale = 32767; + } + break; + } + return scale; + } + + private String convertColumnName(String columnName) { + if(targetsMySql) { + // MySQL column names are not case-sensitive on any platform + columnName = StringUtils.toUpperEnglish(columnName); + } else if ((storesMixedCase || storesLowerCase) && + columnName.equals(StringUtils.toLowerEnglish(columnName))) { + columnName = StringUtils.toUpperEnglish(columnName); + } else if (storesMixedCase && !supportsMixedCaseIdentifiers) { + // TeraData + columnName = StringUtils.toUpperEnglish(columnName); + } else if (storesMixedCase && storesMixedCaseQuoted) { + // MS SQL Server (identifiers are case insensitive even if quoted) + columnName = StringUtils.toUpperEnglish(columnName); + } + return columnName; + } + + private void addIndex(List list, int uniqueColumnCount, IndexType indexType) { + // bind the index to the leading recognized columns in the index + // (null columns might come from a function-based index) + int firstNull = list.indexOf(null); + if (firstNull == 0) { + trace.info("Omitting linked index - no recognized columns."); + return; + } else if (firstNull > 0) { + trace.info("Unrecognized columns in linked index. " + + "Registering the index against the leading {0} " + + "recognized columns of {1} total columns.", firstNull, list.size()); + list = list.subList(0, firstNull); + } + Column[] cols = list.toArray(new Column[0]); + Index index = new LinkedIndex(this, 0, IndexColumn.wrap(cols), uniqueColumnCount, indexType); + indexes.add(index); + } + + @Override + public String getDropSQL() { + StringBuilder builder = new StringBuilder("DROP TABLE IF EXISTS "); + return getSQL(builder, DEFAULT_SQL_FLAGS).toString(); + } + + @Override + public String getCreateSQL() { + StringBuilder buff = new StringBuilder("CREATE FORCE "); + if (isTemporary()) { + if (globalTemporary) { + buff.append("GLOBAL "); + } else { + buff.append("LOCAL "); + } + buff.append("TEMPORARY "); + } + buff.append("LINKED TABLE "); + getSQL(buff, DEFAULT_SQL_FLAGS); + if (comment != null) { + buff.append(" COMMENT "); + StringUtils.quoteStringSQL(buff, comment); + } + buff.append('('); + StringUtils.quoteStringSQL(buff, driver).append(", "); + StringUtils.quoteStringSQL(buff, url).append(", "); + StringUtils.quoteStringSQL(buff, user).append(", "); + StringUtils.quoteStringSQL(buff, password).append(", "); + StringUtils.quoteStringSQL(buff, originalTable).append(')'); + if (emitUpdates) { + buff.append(" EMIT UPDATES"); + } + if (readOnly) { + buff.append(" READONLY"); + } + if (fetchSize != 0) { + buff.append(" FETCH_SIZE ").append(fetchSize); + } + if(!autocommit) { + buff.append(" AUTOCOMMIT OFF"); + } + buff.append(" /*").append(DbException.HIDE_SQL).append("*/"); + return buff.toString(); + } + + @Override + public Index addIndex(SessionLocal session, String indexName, int indexId, IndexColumn[] cols, + int uniqueColumnCount, IndexType indexType, boolean create, String indexComment) { + throw DbException.getUnsupportedException("LINK"); + } + + @Override + public Index getScanIndex(SessionLocal session) { + return linkedIndex; + } + + @Override + public boolean isInsertable() { + return !readOnly; + } + + private void checkReadOnly() { + if (readOnly) { + throw DbException.get(ErrorCode.DATABASE_IS_READ_ONLY); + } + } + + @Override + public void removeRow(SessionLocal session, Row row) { + checkReadOnly(); + getScanIndex(session).remove(session, row); + } + + @Override + public void addRow(SessionLocal session, Row row) { + checkReadOnly(); + getScanIndex(session).add(session, row); + } + + @Override + public void close(SessionLocal session) { + if (conn != null) { + try { + conn.close(false); + } finally { + conn = null; + } + } + } + + @Override + public synchronized long getRowCount(SessionLocal session) { + //The foo alias is used to support the PostgreSQL syntax + String sql = "SELECT COUNT(*) FROM " + qualifiedTableName + " as foo"; + try { + PreparedStatement prep = execute(sql, null, false, session); + ResultSet rs = prep.getResultSet(); + rs.next(); + long count = rs.getLong(1); + rs.close(); + reusePreparedStatement(prep, sql); + return count; + } catch (Exception e) { + throw wrapException(sql, e); + } + } + + /** + * Wrap a SQL exception that occurred while accessing a linked table. + * + * @param sql the SQL statement + * @param ex the exception from the remote database + * @return the wrapped exception + */ + public static DbException wrapException(String sql, Exception ex) { + SQLException e = DbException.toSQLException(ex); + return DbException.get(ErrorCode.ERROR_ACCESSING_LINKED_TABLE_2, + e, sql, e.toString()); + } + + public String getQualifiedTable() { + return qualifiedTableName; + } + + /** + * Execute a SQL statement using the given parameters. Prepared + * statements are kept in a hash map to avoid re-creating them. + * + * @param sql the SQL statement + * @param params the parameters or null + * @param reusePrepared if the prepared statement can be re-used immediately + * @param session the session + * @return the prepared statement, or null if it is re-used + */ + public PreparedStatement execute(String sql, ArrayList params, boolean reusePrepared, // + SessionLocal session) { + if (conn == null) { + throw connectException; + } + for (int retry = 0;; retry++) { + try { + synchronized (conn) { + PreparedStatement prep = preparedMap.remove(sql); + if (prep == null) { + prep = conn.getConnection().prepareStatement(sql); + if (fetchSize != 0) { + prep.setFetchSize(fetchSize); + } + } + if (trace.isDebugEnabled()) { + StringBuilder builder = new StringBuilder(getName()).append(":\n").append(sql); + if (params != null && !params.isEmpty()) { + builder.append(" {"); + for (int i = 0, l = params.size(); i < l;) { + Value v = params.get(i); + if (i > 0) { + builder.append(", "); + } + builder.append(++i).append(": "); + v.getSQL(builder, DEFAULT_SQL_FLAGS); + } + builder.append('}'); + } + builder.append(';'); + trace.debug(builder.toString()); + } + if (params != null) { + JdbcConnection ownConnection = session.createConnection(false); + for (int i = 0, size = params.size(); i < size; i++) { + Value v = params.get(i); + JdbcUtils.set(prep, i + 1, v, ownConnection); + } + } + prep.execute(); + if (reusePrepared) { + reusePreparedStatement(prep, sql); + return null; + } + return prep; + } + } catch (SQLException e) { + if (retry >= MAX_RETRY) { + throw DbException.convert(e); + } + conn.close(true); + connect(); + } + } + } + + @Override + public void checkSupportAlter() { + throw DbException.getUnsupportedException("LINK"); + } + + @Override + public long truncate(SessionLocal session) { + throw DbException.getUnsupportedException("LINK"); + } + + @Override + public boolean canGetRowCount(SessionLocal session) { + return true; + } + + @Override + public boolean canDrop() { + return true; + } + + @Override + public TableType getTableType() { + return TableType.TABLE_LINK; + } + + @Override + public void removeChildrenAndResources(SessionLocal session) { + super.removeChildrenAndResources(session); + close(session); + database.removeMeta(session, getId()); + driver = null; + url = user = password = originalTable = null; + preparedMap = null; + invalidate(); + } + + public boolean isOracle() { + return url.startsWith("jdbc:oracle:"); + } + + private static boolean isMySqlUrl(String url) { + return url.startsWith("jdbc:mysql:") + || url.startsWith("jdbc:mariadb:"); + } + + @Override + public ArrayList getIndexes() { + return indexes; + } + + @Override + public long getMaxDataModificationId() { + // data may have been modified externally + return Long.MAX_VALUE; + } + + @Override + public void updateRows(Prepared prepared, SessionLocal session, LocalResult rows) { + checkReadOnly(); + if (emitUpdates) { + while (rows.next()) { + prepared.checkCanceled(); + Row oldRow = rows.currentRowForTable(); + rows.next(); + Row newRow = rows.currentRowForTable(); + linkedIndex.update(oldRow, newRow, session); + } + } else { + super.updateRows(prepared, session, rows); + } + } + + public void setGlobalTemporary(boolean globalTemporary) { + this.globalTemporary = globalTemporary; + } + + public void setReadOnly(boolean readOnly) { + this.readOnly = readOnly; + } + + @Override + public long getRowCountApproximation(SessionLocal session) { + return ROW_COUNT_APPROXIMATION; + } + + /** + * Add this prepared statement to the list of cached statements. + * + * @param prep the prepared statement + * @param sql the SQL statement + */ + public void reusePreparedStatement(PreparedStatement prep, String sql) { + synchronized (conn) { + preparedMap.put(sql, prep); + } + } + + @Override + public boolean isDeterministic() { + return false; + } + + /** + * Linked tables don't know if they are readonly. This overwrites + * the default handling. + */ + @Override + public void checkWritingAllowed() { + // only the target database can verify this + } + + @Override + public void convertInsertRow(SessionLocal session, Row row, Boolean overridingSystem) { + convertRow(session, row); + } + + @Override + public void convertUpdateRow(SessionLocal session, Row row, boolean fromTrigger) { + convertRow(session, row); + } + + private void convertRow(SessionLocal session, Row row) { + for (int i = 0; i < columns.length; i++) { + Value value = row.getValue(i); + if (value != null) { + // null means use the default value + Column column = columns[i]; + Value v2 = column.validateConvertUpdateSequence(session, value, row); + if (v2 != value) { + row.setValue(i, v2); + } + } + } + } + + /** + * Specify the number of rows fetched by the linked table command + * + * @param fetchSize to set + */ + public void setFetchSize(int fetchSize) { + this.fetchSize = fetchSize; + } + + /** + * Specify if the autocommit mode is activated or not + * + * @param mode to set + */ + public void setAutoCommit(boolean mode) { + this.autocommit= mode; + } + + /** + * The autocommit mode + * @return true if autocommit is on + */ + public boolean getAutocommit(){ + return autocommit; + } + + /** + * The number of rows to fetch + * default is 0 + * + * @return number of rows to fetch + */ + public int getFetchSize() { + return fetchSize; + } + +} diff --git a/h2/src/main/org/h2/table/TableLinkConnection.java b/h2/src/main/org/h2/table/TableLinkConnection.java new file mode 100644 index 0000000..2286e7d --- /dev/null +++ b/h2/src/main/org/h2/table/TableLinkConnection.java @@ -0,0 +1,163 @@ +/* + * 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.sql.Connection; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Objects; +import org.h2.message.DbException; +import org.h2.util.JdbcUtils; + +/** + * A connection for a linked table. The same connection may be used for multiple + * tables, that means a connection may be shared. + */ +public class TableLinkConnection { + + /** + * The map where the link is kept. + */ + private final HashMap map; + + /** + * The connection information. + */ + private final String driver, url, user, password; + + /** + * The database connection. + */ + private Connection conn; + + /** + * How many times the connection is used. + */ + private int useCounter; + private boolean autocommit =true; + + private TableLinkConnection( + HashMap map, + String driver, String url, String user, String password) { + this.map = map; + this.driver = driver; + this.url = url; + this.user = user; + this.password = password; + } + + /** + * Open a new connection. + * + * @param map the map where the connection should be stored + * (if shared connections are enabled). + * @param driver the JDBC driver class name + * @param url the database URL + * @param user the user name + * @param password the password + * @param shareLinkedConnections if connections should be shared + * @return a connection + */ + public static TableLinkConnection open( + HashMap map, + String driver, String url, String user, String password, + boolean shareLinkedConnections) { + TableLinkConnection t = new TableLinkConnection(map, driver, url, + user, password); + if (!shareLinkedConnections) { + t.open(); + return t; + } + synchronized (map) { + TableLinkConnection result = map.get(t); + if (result == null) { + t.open(); + // put the connection in the map after is has been opened, + // when we know it works + map.put(t, t); + result = t; + } + result.useCounter++; + return result; + } + } + + private void open() { + try { + conn = JdbcUtils.getConnection(driver, url, user, password); + } catch (SQLException e) { + throw DbException.convert(e); + } + } + + @Override + public int hashCode() { + return Objects.hashCode(driver) + ^ Objects.hashCode(url) + ^ Objects.hashCode(user) + ^ Objects.hashCode(password); + } + + @Override + public boolean equals(Object o) { + if (o instanceof TableLinkConnection) { + TableLinkConnection other = (TableLinkConnection) o; + return Objects.equals(driver, other.driver) + && Objects.equals(url, other.url) + && Objects.equals(user, other.user) + && Objects.equals(password, other.password); + } + return false; + } + + /** + * Get the connection. + * This method and methods on the statement must be + * synchronized on this object. + * + * @return the connection + */ + Connection getConnection() { + return conn; + } + + /** + * Closes the connection if this is the last link to it. + * + * @param force if the connection needs to be closed even if it is still + * used elsewhere (for example, because the connection is broken) + */ + void close(boolean force) { + boolean actuallyClose = false; + synchronized (map) { + if (--useCounter <= 0 || force) { + actuallyClose = true; + map.remove(this); + } + } + if (actuallyClose) { + JdbcUtils.closeSilently(conn); + } + } + + /** + * Specify if the autocommit mode is activated or not + * + * @param mode to set + */ + public void setAutoCommit(boolean mode) { + this.autocommit= mode; + } + + /** + * The autocommit mode + * @return true if autocommit is on + */ + public boolean getAutocommit(){ + return autocommit; + } + +} diff --git a/h2/src/main/org/h2/table/TableSynonym.java b/h2/src/main/org/h2/table/TableSynonym.java new file mode 100644 index 0000000..cf35d03 --- /dev/null +++ b/h2/src/main/org/h2/table/TableSynonym.java @@ -0,0 +1,120 @@ +/* + * 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 org.h2.command.ddl.CreateSynonymData; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.message.Trace; +import org.h2.schema.Schema; +import org.h2.schema.SchemaObject; +import org.h2.util.ParserUtil; + +/** + * Synonym for an existing table or view. All DML requests are forwarded to the backing table. + * Adding indices to a synonym or altering the table is not supported. + */ +public class TableSynonym extends SchemaObject { + + private CreateSynonymData data; + + /** + * The table the synonym is created for. + */ + private Table synonymFor; + + public TableSynonym(CreateSynonymData data) { + super(data.schema, data.id, data.synonymName, Trace.TABLE); + this.data = data; + } + + /** + * @return the table this is a synonym for + */ + public Table getSynonymFor() { + return synonymFor; + } + + /** + * Set (update) the data. + * + * @param data the new data + */ + public void updateData(CreateSynonymData data) { + this.data = data; + } + + @Override + public int getType() { + return SYNONYM; + } + + @Override + public String getCreateSQLForCopy(Table table, String quotedName) { + return synonymFor.getCreateSQLForCopy(table, quotedName); + } + + @Override + public void rename(String newName) { throw DbException.getUnsupportedException("SYNONYM"); } + + @Override + public void removeChildrenAndResources(SessionLocal session) { + synonymFor.removeSynonym(this); + database.removeMeta(session, getId()); + } + + @Override + public String getCreateSQL() { + StringBuilder builder = new StringBuilder("CREATE SYNONYM "); + getSQL(builder, DEFAULT_SQL_FLAGS).append(" FOR "); + ParserUtil.quoteIdentifier(builder, data.synonymForSchema.getName(), DEFAULT_SQL_FLAGS).append('.'); + ParserUtil.quoteIdentifier(builder, data.synonymFor, DEFAULT_SQL_FLAGS); + return builder.toString(); + } + + @Override + public String getDropSQL() { + return getSQL(new StringBuilder("DROP SYNONYM "), DEFAULT_SQL_FLAGS).toString(); + } + + @Override + public void checkRename() { + throw DbException.getUnsupportedException("SYNONYM"); + } + + /** + * @return the table this synonym is for + */ + public String getSynonymForName() { + return data.synonymFor; + } + + /** + * @return the schema this synonym is for + */ + public Schema getSynonymForSchema() { + return data.synonymForSchema; + } + + /** + * @return true if this synonym currently points to a real table + */ + public boolean isInvalid() { + return synonymFor.isValid(); + } + + /** + * Update the table that this is a synonym for, to know about this synonym. + */ + public void updateSynonymFor() { + if (synonymFor != null) { + synonymFor.removeSynonym(this); + } + synonymFor = data.synonymForSchema.getTableOrView(data.session, data.synonymFor); + synonymFor.addSynonym(this); + } + +} diff --git a/h2/src/main/org/h2/table/TableType.java b/h2/src/main/org/h2/table/TableType.java new file mode 100644 index 0000000..0e406bd --- /dev/null +++ b/h2/src/main/org/h2/table/TableType.java @@ -0,0 +1,51 @@ +/* + * 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; + +/** + * The table types. + */ +public enum TableType { + + /** + * The table type name for linked tables. + */ + TABLE_LINK, + + /** + * The table type name for system tables. (aka. MetaTable) + */ + SYSTEM_TABLE, + + /** + * The table type name for regular data tables. + */ + TABLE, + + /** + * The table type name for views. + */ + VIEW, + + /** + * The table type name for external table engines. + */ + EXTERNAL_TABLE_ENGINE; + + @Override + public String toString() { + if (this == EXTERNAL_TABLE_ENGINE) { + return "EXTERNAL"; + } else if (this == SYSTEM_TABLE) { + return "SYSTEM TABLE"; + } else if (this == TABLE_LINK) { + return "TABLE LINK"; + } else { + return super.toString(); + } + } + +} diff --git a/h2/src/main/org/h2/table/TableValueConstructorTable.java b/h2/src/main/org/h2/table/TableValueConstructorTable.java new file mode 100644 index 0000000..c532e44 --- /dev/null +++ b/h2/src/main/org/h2/table/TableValueConstructorTable.java @@ -0,0 +1,70 @@ +/* + * 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.command.query.TableValueConstructor; +import org.h2.engine.SessionLocal; +import org.h2.expression.Expression; +import org.h2.result.ResultInterface; +import org.h2.result.SimpleResult; +import org.h2.schema.Schema; + +/** + * A table for table value constructor. + */ +public class TableValueConstructorTable extends VirtualConstructedTable { + + private final ArrayList> rows; + + public TableValueConstructorTable(Schema schema, SessionLocal session, Column[] columns, + ArrayList> rows) { + super(schema, 0, "VALUES"); + setColumns(columns); + this.rows = rows; + } + + @Override + public boolean canGetRowCount(SessionLocal session) { + return true; + } + + @Override + public long getRowCount(SessionLocal session) { + return rows.size(); + } + + @Override + public long getRowCountApproximation(SessionLocal session) { + return rows.size(); + } + + @Override + public ResultInterface getResult(SessionLocal session) { + SimpleResult simple = new SimpleResult(); + int columnCount = columns.length; + for (int i = 0; i < columnCount; i++) { + Column column = columns[i]; + simple.addColumn(column.getName(), column.getType()); + } + TableValueConstructor.getVisibleResult(session, simple, columns, rows); + return simple; + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + builder.append('('); + TableValueConstructor.getValuesSQL(builder, sqlFlags, rows); + return builder.append(')'); + } + + @Override + public boolean isDeterministic() { + return true; + } + +} diff --git a/h2/src/main/org/h2/table/TableView.java b/h2/src/main/org/h2/table/TableView.java new file mode 100644 index 0000000..e8850a6 --- /dev/null +++ b/h2/src/main/org/h2/table/TableView.java @@ -0,0 +1,517 @@ +/* + * 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.List; + +import org.h2.api.ErrorCode; +import org.h2.command.Prepared; +import org.h2.command.ddl.CreateTableData; +import org.h2.command.query.AllColumnsForPlan; +import org.h2.command.query.Query; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.expression.Parameter; +import org.h2.index.Index; +import org.h2.index.QueryExpressionIndex; +import org.h2.message.DbException; +import org.h2.result.ResultInterface; +import org.h2.result.SortOrder; +import org.h2.schema.Schema; +import org.h2.util.StringUtils; +import org.h2.util.Utils; + +/** + * 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 final class TableView extends QueryExpressionTable { + + private String querySQL; + private Column[] columnTemplates; + private boolean allowRecursive; + private DbException createException; + private ResultInterface recursiveResult; + private boolean isRecursiveQueryDetected; + private boolean isTableExpression; + + 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); + setTemporary(isTemporary); + init(querySQL, params, columnTemplates, session, allowRecursive, literalsChecked, isTableExpression); + } + + /** + * Try to replace the SQL statement of the view and re-compile this and all + * dependent views. + * + * @param querySQL the SQL statement + * @param newColumnTemplates the columns + * @param session the session + * @param recursive whether this is a recursive view + * @param force if errors should be ignored + * @param literalsChecked if literals have been checked + */ + public void replace(String querySQL, Column[] newColumnTemplates, SessionLocal session, + boolean recursive, boolean force, boolean literalsChecked) { + String oldQuerySQL = this.querySQL; + Column[] oldColumnTemplates = this.columnTemplates; + boolean oldRecursive = this.allowRecursive; + init(querySQL, null, newColumnTemplates, session, recursive, literalsChecked, isTableExpression); + DbException e = recompile(session, force, true); + if (e != null) { + init(oldQuerySQL, null, oldColumnTemplates, session, oldRecursive, + literalsChecked, isTableExpression); + recompile(session, true, false); + throw e; + } + } + + private synchronized void init(String querySQL, ArrayList params, + Column[] columnTemplates, SessionLocal session, boolean allowRecursive, boolean literalsChecked, + boolean isTableExpression) { + this.querySQL = querySQL; + this.columnTemplates = columnTemplates; + this.allowRecursive = allowRecursive; + this.isRecursiveQueryDetected = false; + this.isTableExpression = isTableExpression; + index = new QueryExpressionIndex(this, querySQL, params, allowRecursive); + initColumnsAndTables(session, literalsChecked); + } + + private Query compileViewQuery(SessionLocal session, String sql, boolean literalsChecked) { + Prepared p; + session.setParsingCreateView(true); + try { + p = session.prepare(sql, false, literalsChecked); + } finally { + session.setParsingCreateView(false); + } + if (!(p instanceof Query)) { + throw DbException.getSyntaxError(sql, 0); + } + Query q = (Query) p; + // only potentially recursive cte queries need to be non-lazy + if (isTableExpression && allowRecursive) { + q.setNeverLazy(true); + } + return q; + } + + /** + * Re-compile the view query and all views that depend on this object. + * + * @param session the session + * @param force if exceptions should be ignored + * @param clearIndexCache if we need to clear view index cache + * @return the exception if re-compiling this or any dependent view failed + * (only when force is disabled) + */ + public synchronized DbException recompile(SessionLocal session, boolean force, + boolean clearIndexCache) { + try { + compileViewQuery(session, querySQL, false); + } catch (DbException e) { + if (!force) { + return e; + } + } + ArrayList dependentViews = new ArrayList<>(getDependentViews()); + initColumnsAndTables(session, false); + for (TableView v : dependentViews) { + DbException e = v.recompile(session, force, false); + if (e != null && !force) { + return e; + } + } + if (clearIndexCache) { + clearIndexCaches(database); + } + return force ? null : createException; + } + + private void initColumnsAndTables(SessionLocal session, boolean literalsChecked) { + Column[] cols; + removeCurrentViewFromOtherTables(); + setTableExpression(isTableExpression); + try { + Query compiledQuery = compileViewQuery(session, querySQL, literalsChecked); + this.querySQL = compiledQuery.getPlanSQL(DEFAULT_SQL_FLAGS); + tables = new ArrayList<>(compiledQuery.getTables()); + cols = initColumns(session, columnTemplates, compiledQuery, false); + createException = null; + viewQuery = compiledQuery; + } catch (DbException e) { + if (e.getErrorCode() == ErrorCode.COLUMN_ALIAS_IS_NOT_SPECIFIED_1) { + throw e; + } + e.addSQL(getCreateSQL()); + createException = e; + // If it can't be compiled, then it's a 'zero column table' + // this avoids problems when creating the view when opening the + // database. + // If it can not be compiled - it could also be a recursive common + // table expression query. + if (isRecursiveQueryExceptionDetected(createException)) { + this.isRecursiveQueryDetected = true; + } + tables = Utils.newSmallArrayList(); + cols = new Column[0]; + if (allowRecursive && columnTemplates != null) { + cols = new Column[columnTemplates.length]; + for (int i = 0; i < columnTemplates.length; i++) { + cols[i] = columnTemplates[i].getClone(); + } + index.setRecursive(true); + createException = null; + } + } + setColumns(cols); + if (getId() != 0) { + addDependentViewToTables(); + } + } + + /** + * Check if this view is currently invalid. + * + * @return true if it is + */ + public boolean isInvalid() { + return createException != null; + } + + @Override + public Query getTopQuery() { + return null; + } + + @Override + public String getDropSQL() { + return getSQL(new StringBuilder("DROP VIEW IF EXISTS "), DEFAULT_SQL_FLAGS).append(" CASCADE").toString(); + } + + @Override + public String getCreateSQLForCopy(Table table, String quotedName) { + return getCreateSQL(false, true, quotedName); + } + + + @Override + public String getCreateSQL() { + return getCreateSQL(false, true); + } + + /** + * Generate "CREATE" SQL statement for the view. + * + * @param orReplace if true, then include the OR REPLACE clause + * @param force if true, then include the FORCE clause + * @return the SQL statement + */ + public String getCreateSQL(boolean orReplace, boolean force) { + return getCreateSQL(orReplace, force, getSQL(DEFAULT_SQL_FLAGS)); + } + + private String getCreateSQL(boolean orReplace, boolean force, String quotedName) { + StringBuilder builder = new StringBuilder("CREATE "); + if (orReplace) { + builder.append("OR REPLACE "); + } + if (force) { + builder.append("FORCE "); + } + builder.append("VIEW "); + if (isTableExpression) { + builder.append("TABLE_EXPRESSION "); + } + builder.append(quotedName); + if (comment != null) { + builder.append(" COMMENT "); + StringUtils.quoteStringSQL(builder, comment); + } + if (columns != null && columns.length > 0) { + builder.append('('); + Column.writeColumns(builder, columns, DEFAULT_SQL_FLAGS); + builder.append(')'); + } else if (columnTemplates != null) { + builder.append('('); + Column.writeColumns(builder, columnTemplates, DEFAULT_SQL_FLAGS); + builder.append(')'); + } + return builder.append(" AS\n").append(querySQL).toString(); + } + + @Override + public boolean canDrop() { + return true; + } + + @Override + public TableType getTableType() { + return TableType.VIEW; + } + + @Override + public void removeChildrenAndResources(SessionLocal session) { + removeCurrentViewFromOtherTables(); + super.removeChildrenAndResources(session); + database.removeMeta(session, getId()); + querySQL = null; + index = null; + clearIndexCaches(database); + invalidate(); + } + + /** + * Clear the cached indexes for all sessions. + * + * @param database the database + */ + public static void clearIndexCaches(Database database) { + for (SessionLocal s : database.getSessions(true)) { + s.clearViewIndexCache(); + } + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + if (isTemporary() && querySQL != null) { + builder.append("(\n"); + return StringUtils.indent(builder, querySQL, 4, true).append(')'); + } + return super.getSQL(builder, sqlFlags); + } + + public String getQuerySQL() { + return querySQL; + } + + @Override + public Index getScanIndex(SessionLocal session, int[] masks, + TableFilter[] filters, int filter, SortOrder sortOrder, + AllColumnsForPlan allColumnsSet) { + if (createException != null) { + String msg = createException.getMessage(); + throw DbException.get(ErrorCode.VIEW_IS_INVALID_2, createException, getTraceSQL(), msg); + } + return super.getScanIndex(session, masks, filters, filter, sortOrder, allColumnsSet); + } + + @Override + public long getMaxDataModificationId() { + if (createException != null || viewQuery == null) { + return Long.MAX_VALUE; + } + return super.getMaxDataModificationId(); + } + + private void removeCurrentViewFromOtherTables() { + if (tables != null) { + for (Table t : tables) { + t.removeDependentView(this); + } + tables.clear(); + } + } + + private void addDependentViewToTables() { + for (Table t : tables) { + t.addDependentView(this); + } + } + + public boolean isRecursive() { + return allowRecursive; + } + + @Override + public boolean isDeterministic() { + if (allowRecursive || viewQuery == null) { + return false; + } + return super.isDeterministic(); + } + + public void setRecursiveResult(ResultInterface value) { + if (recursiveResult != null) { + recursiveResult.close(); + } + this.recursiveResult = value; + } + + public ResultInterface getRecursiveResult() { + return recursiveResult; + } + + /** + * Was query recursion detected during compiling. + * + * @return true if yes + */ + public boolean isRecursiveQueryDetected() { + return isRecursiveQueryDetected; + } + + /** + * Does exception indicate query recursion? + */ + private boolean isRecursiveQueryExceptionDetected(DbException exception) { + if (exception == null) { + return false; + } + int errorCode = exception.getErrorCode(); + if (errorCode != ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1 && + errorCode != ErrorCode.TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1 && + errorCode != ErrorCode.TABLE_OR_VIEW_NOT_FOUND_WITH_CANDIDATES_2 + ) { + return false; + } + return exception.getMessage().contains("\"" + this.getName() + "\""); + } + + public List
getTables() { + return tables; + } + + /** + * Create a view. + * + * @param schema the schema + * @param id the view id + * @param name the view name + * @param querySQL the query + * @param parameters the parameters + * @param columnTemplates the columns + * @param session the session + * @param literalsChecked whether literals in the query are checked + * @param isTableExpression if this is a table expression + * @param isTemporary whether the view is persisted + * @param db the database + * @return the view + */ + public static TableView createTableViewMaybeRecursive(Schema schema, int id, String name, String querySQL, + ArrayList parameters, Column[] columnTemplates, SessionLocal session, + boolean literalsChecked, boolean isTableExpression, boolean isTemporary, Database db) { + + + Table recursiveTable = createShadowTableForRecursiveTableExpression(isTemporary, session, name, + schema, Arrays.asList(columnTemplates), db); + + List columnTemplateList; + String[] querySQLOutput = new String[1]; + ArrayList columnNames = new ArrayList<>(); + for (Column columnTemplate: columnTemplates) { + columnNames.add(columnTemplate.getName()); + } + + try { + Prepared withQuery = session.prepare(querySQL, false, false); + if (!isTemporary) { + withQuery.setSession(session); + } + columnTemplateList = createQueryColumnTemplateList(columnNames.toArray(new String[1]), + (Query) withQuery, querySQLOutput); + + } finally { + destroyShadowTableForRecursiveExpression(isTemporary, session, recursiveTable); + } + + // build with recursion turned on + TableView view = new TableView(schema, id, name, querySQL, + parameters, columnTemplateList.toArray(columnTemplates), session, + true/* try recursive */, literalsChecked, isTableExpression, isTemporary); + + // is recursion really detected ? if not - recreate it without recursion flag + // and no recursive index + if (!view.isRecursiveQueryDetected()) { + if (!isTemporary) { + db.addSchemaObject(session, view); + view.lock(session, Table.EXCLUSIVE_LOCK); + session.getDatabase().removeSchemaObject(session, view); + + // during database startup - this method does not normally get called - and it + // needs to be to correctly un-register the table which the table expression + // uses... + view.removeChildrenAndResources(session); + } else { + session.removeLocalTempTable(view); + } + view = new TableView(schema, id, name, querySQL, parameters, + columnTemplates, session, + false/* detected not recursive */, literalsChecked, isTableExpression, isTemporary); + } + + return view; + } + + /** + * Create a table for a recursive query. + * + * @param isTemporary whether the table is persisted + * @param targetSession the session + * @param cteViewName the name + * @param schema the schema + * @param columns the columns + * @param db the database + * @return the table + */ + public static Table createShadowTableForRecursiveTableExpression(boolean isTemporary, SessionLocal targetSession, + String cteViewName, Schema schema, List columns, Database db) { + + // create table data object + CreateTableData recursiveTableData = new CreateTableData(); + recursiveTableData.id = db.allocateObjectId(); + recursiveTableData.columns = new ArrayList<>(columns); + recursiveTableData.tableName = cteViewName; + recursiveTableData.temporary = isTemporary; + recursiveTableData.persistData = true; + recursiveTableData.persistIndexes = !isTemporary; + recursiveTableData.session = targetSession; + + // this gets a meta table lock that is not released + Table recursiveTable = schema.createTable(recursiveTableData); + + if (!isTemporary) { + // this unlock is to prevent lock leak from schema.createTable() + db.unlockMeta(targetSession); + synchronized (targetSession) { + db.addSchemaObject(targetSession, recursiveTable); + } + } else { + targetSession.addLocalTempTable(recursiveTable); + } + return recursiveTable; + } + + /** + * Remove a table for a recursive query. + * + * @param isTemporary whether the table is persisted + * @param targetSession the session + * @param recursiveTable the table + */ + public static void destroyShadowTableForRecursiveExpression(boolean isTemporary, SessionLocal targetSession, + Table recursiveTable) { + if (recursiveTable != null) { + if (!isTemporary) { + recursiveTable.lock(targetSession, Table.EXCLUSIVE_LOCK); + targetSession.getDatabase().removeSchemaObject(targetSession, recursiveTable); + + } else { + targetSession.removeLocalTempTable(recursiveTable); + } + + // both removeSchemaObject and removeLocalTempTable hold meta locks - release them here + targetSession.getDatabase().unlockMeta(targetSession); + } + } +} diff --git a/h2/src/main/org/h2/table/VirtualConstructedTable.java b/h2/src/main/org/h2/table/VirtualConstructedTable.java new file mode 100644 index 0000000..77f6ec2 --- /dev/null +++ b/h2/src/main/org/h2/table/VirtualConstructedTable.java @@ -0,0 +1,44 @@ +/* + * 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 org.h2.engine.SessionLocal; +import org.h2.index.Index; +import org.h2.index.VirtualConstructedTableIndex; +import org.h2.result.ResultInterface; +import org.h2.schema.Schema; + +/** + * A base class for virtual tables that construct all their content at once. + */ +public abstract class VirtualConstructedTable extends VirtualTable { + + protected VirtualConstructedTable(Schema schema, int id, String name) { + super(schema, id, name); + } + + /** + * Read the rows from the table. + * + * @param session + * the session + * @return the result + */ + public abstract ResultInterface getResult(SessionLocal session); + + @Override + public Index getScanIndex(SessionLocal session) { + return new VirtualConstructedTableIndex(this, IndexColumn.wrap(columns)); + } + + @Override + public long getMaxDataModificationId() { + // TODO optimization: virtual table currently doesn't know the + // last modified date + return Long.MAX_VALUE; + } + +} diff --git a/h2/src/main/org/h2/table/VirtualTable.java b/h2/src/main/org/h2/table/VirtualTable.java new file mode 100644 index 0000000..a0dead3 --- /dev/null +++ b/h2/src/main/org/h2/table/VirtualTable.java @@ -0,0 +1,93 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.table; + +import java.util.ArrayList; + +import org.h2.engine.SessionLocal; +import org.h2.index.Index; +import org.h2.index.IndexType; +import org.h2.message.DbException; +import org.h2.result.Row; +import org.h2.schema.Schema; + +/** + * A base class for virtual tables. + */ +public abstract class VirtualTable extends Table { + + protected VirtualTable(Schema schema, int id, String name) { + super(schema, id, name, false, true); + } + + @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("Virtual table"); + } + + @Override + public boolean isInsertable() { + return false; + } + + @Override + public void removeRow(SessionLocal session, Row row) { + throw DbException.getUnsupportedException("Virtual table"); + + } + + @Override + public long truncate(SessionLocal session) { + throw DbException.getUnsupportedException("Virtual table"); + } + + @Override + public void addRow(SessionLocal session, Row row) { + throw DbException.getUnsupportedException("Virtual table"); + } + + @Override + public void checkSupportAlter() { + throw DbException.getUnsupportedException("Virtual table"); + } + + @Override + public TableType getTableType() { + return null; + } + + @Override + public ArrayList getIndexes() { + return null; + } + + @Override + public boolean canReference() { + return false; + } + + @Override + public boolean canDrop() { + throw DbException.getInternalError(toString()); + } + + @Override + public String getCreateSQL() { + return null; + } + + @Override + public void checkRename() { + throw DbException.getUnsupportedException("Virtual table"); + } + +} diff --git a/h2/src/main/org/h2/table/package.html b/h2/src/main/org/h2/table/package.html new file mode 100644 index 0000000..5ae6ac1 --- /dev/null +++ b/h2/src/main/org/h2/table/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Classes related to a table and table meta data. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/tools/Backup.java b/h2/src/main/org/h2/tools/Backup.java new file mode 100644 index 0000000..afb464b --- /dev/null +++ b/h2/src/main/org/h2/tools/Backup.java @@ -0,0 +1,178 @@ +/* + * 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.tools; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.sql.SQLException; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; +import org.h2.command.dml.BackupCommand; +import org.h2.engine.Constants; +import org.h2.message.DbException; +import org.h2.store.FileLister; +import org.h2.store.fs.FileUtils; +import org.h2.util.IOUtils; +import org.h2.util.Tool; + +/** + * Creates a backup of a database. + * + * This tool copies all database files. The database must be closed before using + * this tool. To create a backup while the database is in use, run the BACKUP + * SQL statement. In an emergency, for example if the application is not + * responding, creating a backup using the Backup tool is possible by using the + * quiet mode. However, if the database is changed while the backup is running + * in quiet mode, the backup could be corrupt. + */ +public class Backup extends Tool { + + /** + * Options are case sensitive. + *
+ * + * + * + * + * + * + * + * + * + * + * + *
Supported options are:
[-help] or [-?]Print the list of options
[-file <filename>]The target file name (default: backup.zip)
[-dir <dir>]The source directory (default: .)
[-db <database>]Source database; not required if there is only one
[-quiet]Do not print progress information
+ * + * @param args the command line arguments + * @throws SQLException on failure + */ + public static void main(String... args) throws SQLException { + new Backup().runTool(args); + } + + @Override + public void runTool(String... args) throws SQLException { + String zipFileName = "backup.zip"; + String dir = "."; + String db = null; + boolean quiet = false; + for (int i = 0; args != null && i < args.length; i++) { + String arg = args[i]; + if (arg.equals("-dir")) { + dir = args[++i]; + } else if (arg.equals("-db")) { + db = args[++i]; + } else if (arg.equals("-quiet")) { + quiet = true; + } else if (arg.equals("-file")) { + zipFileName = args[++i]; + } else if (arg.equals("-help") || arg.equals("-?")) { + showUsage(); + return; + } else { + showUsageAndThrowUnsupportedOption(arg); + } + } + try { + process(zipFileName, dir, db, quiet); + } catch (Exception e) { + throw DbException.toSQLException(e); + } + } + + /** + * Backs up database files. + * + * @param zipFileName the name of the target backup file (including path) + * @param directory the source directory name + * @param db the source database name (null if there is only one database, + * and empty string to backup all files in this directory) + * @param quiet don't print progress information + * @throws SQLException on failure + */ + public static void execute(String zipFileName, String directory, String db, + boolean quiet) throws SQLException { + try { + new Backup().process(zipFileName, directory, db, quiet); + } catch (Exception e) { + throw DbException.toSQLException(e); + } + } + + private void process(String zipFileName, String directory, String db, + boolean quiet) throws SQLException { + List list; + boolean allFiles = db != null && db.isEmpty(); + if (allFiles) { + list = FileUtils.newDirectoryStream(directory); + } else { + list = FileLister.getDatabaseFiles(directory, db, true); + } + if (list.isEmpty()) { + if (!quiet) { + printNoDatabaseFilesFound(directory, db); + } + return; + } + if (!quiet) { + FileLister.tryUnlockDatabase(list, "backup"); + } + zipFileName = FileUtils.toRealPath(zipFileName); + FileUtils.delete(zipFileName); + OutputStream fileOut = null; + try { + fileOut = FileUtils.newOutputStream(zipFileName, false); + try (ZipOutputStream zipOut = new ZipOutputStream(fileOut)) { + String base = ""; + for (String fileName : list) { + if (allFiles || + fileName.endsWith(Constants.SUFFIX_MV_FILE)) { + base = FileUtils.getParent(fileName); + break; + } + } + for (String fileName : list) { + String f = FileUtils.toRealPath(fileName); + if (!f.startsWith(base)) { + throw DbException.getInternalError(f + " does not start with " + base); + } + if (f.endsWith(zipFileName)) { + continue; + } + if (FileUtils.isDirectory(fileName)) { + continue; + } + f = f.substring(base.length()); + f = BackupCommand.correctFileName(f); + ZipEntry entry = new ZipEntry(f); + zipOut.putNextEntry(entry); + InputStream in = null; + try { + in = FileUtils.newInputStream(fileName); + IOUtils.copyAndCloseInput(in, zipOut); + } catch (FileNotFoundException e) { + // the file could have been deleted in the meantime + // ignore this (in this case an empty file is created) + } finally { + IOUtils.closeSilently(in); + } + zipOut.closeEntry(); + if (!quiet) { + out.println("Processed: " + fileName); + } + } + } + } catch (IOException e) { + throw DbException.convertIOException(e, zipFileName); + } finally { + IOUtils.closeSilently(fileOut); + } + } + +} diff --git a/h2/src/main/org/h2/tools/ChangeFileEncryption.java b/h2/src/main/org/h2/tools/ChangeFileEncryption.java new file mode 100644 index 0000000..e1f600f --- /dev/null +++ b/h2/src/main/org/h2/tools/ChangeFileEncryption.java @@ -0,0 +1,244 @@ +/* + * 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.tools; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; +import org.h2.engine.Constants; +import org.h2.message.DbException; +import org.h2.mvstore.MVStore; +import org.h2.store.FileLister; +import org.h2.store.fs.FilePath; +import org.h2.store.fs.FileUtils; +import org.h2.store.fs.encrypt.FileEncrypt; +import org.h2.store.fs.encrypt.FilePathEncrypt; +import org.h2.util.Tool; + +/** + * Allows changing the database file encryption password or algorithm. + * + * This tool can not be used to change a password of a user. + * The database must be closed before using this tool. + */ +public class ChangeFileEncryption extends Tool { + + private String directory; + private String cipherType; + private byte[] decryptKey; + private byte[] encryptKey; + + /** + * Options are case sensitive. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Supported options
[-help] or [-?]Print the list of options
[-cipher type]The encryption type (AES)
[-dir <dir>]The database directory (default: .)
[-db <database>]Database name (all databases if not set)
[-decrypt <pwd>]The decryption password (if not set: not yet encrypted)
[-encrypt <pwd>]The encryption password (if not set: do not encrypt)
[-quiet]Do not print progress information
+ * + * @param args the command line arguments + */ + public static void main(String... args) { + try { + new ChangeFileEncryption().runTool(args); + } catch (SQLException ex) { + ex.printStackTrace(System.err); + System.exit(1); + } + } + + @Override + public void runTool(String... args) throws SQLException { + String dir = "."; + String cipher = null; + char[] decryptPassword = null; + char[] encryptPassword = null; + String db = null; + boolean quiet = false; + for (int i = 0; args != null && i < args.length; i++) { + String arg = args[i]; + if (arg.equals("-dir")) { + dir = args[++i]; + } else if (arg.equals("-cipher")) { + cipher = args[++i]; + } else if (arg.equals("-db")) { + db = args[++i]; + } else if (arg.equals("-decrypt")) { + decryptPassword = args[++i].toCharArray(); + } else if (arg.equals("-encrypt")) { + encryptPassword = args[++i].toCharArray(); + } else if (arg.equals("-quiet")) { + quiet = true; + } else if (arg.equals("-help") || arg.equals("-?")) { + showUsage(); + return; + } else { + showUsageAndThrowUnsupportedOption(arg); + } + } + if ((encryptPassword == null && decryptPassword == null) || cipher == null) { + showUsage(); + throw new SQLException( + "Encryption or decryption password not set, or cipher not set"); + } + try { + process(dir, db, cipher, decryptPassword, encryptPassword, quiet); + } catch (Exception e) { + throw DbException.toSQLException(e); + } + } + + /** + * Changes the password for a database. The passwords must be supplied as + * char arrays and are cleaned in this method. The database must be closed + * before calling this method. + * + * @param dir the directory (. for the current directory) + * @param db the database name (null for all databases) + * @param cipher the cipher (AES) + * @param decryptPassword the decryption password as a char array + * @param encryptPassword the encryption password as a char array + * @param quiet don't print progress information + * @throws SQLException on failure + */ + public static void execute(String dir, String db, String cipher, + char[] decryptPassword, char[] encryptPassword, boolean quiet) + throws SQLException { + try { + new ChangeFileEncryption().process(dir, db, cipher, + decryptPassword, encryptPassword, quiet); + } catch (Exception e) { + throw DbException.toSQLException(e); + } + } + + private void process(String dir, String db, String cipher, + char[] decryptPassword, char[] encryptPassword, boolean quiet) + throws SQLException { + dir = FileLister.getDir(dir); + ChangeFileEncryption change = new ChangeFileEncryption(); + if (encryptPassword != null) { + for (char c : encryptPassword) { + if (c == ' ') { + throw new SQLException("The file password may not contain spaces"); + } + } + change.encryptKey = FilePathEncrypt.getPasswordBytes(encryptPassword); + } + if (decryptPassword != null) { + change.decryptKey = FilePathEncrypt.getPasswordBytes(decryptPassword); + } + change.out = out; + change.directory = dir; + change.cipherType = cipher; + + ArrayList files = FileLister.getDatabaseFiles(dir, db, true); + FileLister.tryUnlockDatabase(files, "encryption"); + files = FileLister.getDatabaseFiles(dir, db, false); + if (files.isEmpty() && !quiet) { + printNoDatabaseFilesFound(dir, db); + } + // first, test only if the file can be renamed + // (to find errors with locked files early) + for (String fileName : files) { + String temp = dir + "/temp.db"; + FileUtils.delete(temp); + FileUtils.move(fileName, temp); + FileUtils.move(temp, fileName); + } + // if this worked, the operation will (hopefully) be successful + // TODO changeFileEncryption: this is a workaround! + // make the operation atomic (all files or none) + for (String fileName : files) { + // don't process a lob directory, just the files in the directory. + if (!FileUtils.isDirectory(fileName)) { + change.process(fileName, quiet, decryptPassword); + } + } + } + + private void process(String fileName, boolean quiet, char[] decryptPassword) throws SQLException { + if (fileName.endsWith(Constants.SUFFIX_MV_FILE)) { + try { + copyMvStore(fileName, quiet, decryptPassword); + } catch (IOException e) { + throw DbException.convertIOException(e, + "Error encrypting / decrypting file " + fileName); + } + return; + } + } + + private void copyMvStore(String fileName, boolean quiet, char[] decryptPassword) throws IOException, SQLException { + if (FileUtils.isDirectory(fileName)) { + return; + } + // check that we have the right encryption key + try { + final MVStore source = new MVStore.Builder(). + fileName(fileName). + readOnly(). + encryptionKey(decryptPassword). + open(); + source.close(); + } catch (IllegalStateException ex) { + throw new SQLException("error decrypting file " + fileName, ex); + } + + String temp = directory + "/temp.db"; + try (FileChannel fileIn = getFileChannel(fileName, "r", decryptKey)){ + try (InputStream inStream = Channels.newInputStream(fileIn)) { + FileUtils.delete(temp); + try (OutputStream outStream = Channels.newOutputStream(getFileChannel(temp, "rw", encryptKey))) { + final byte[] buffer = new byte[4 * 1024]; + long remaining = fileIn.size(); + long total = remaining; + long time = System.nanoTime(); + while (remaining > 0) { + if (!quiet && System.nanoTime() - time > TimeUnit.SECONDS.toNanos(1)) { + out.println(fileName + ": " + (100 - 100 * remaining / total) + "%"); + time = System.nanoTime(); + } + int len = (int) Math.min(buffer.length, remaining); + len = inStream.read(buffer, 0, len); + outStream.write(buffer, 0, len); + remaining -= len; + } + } + } + } + FileUtils.delete(fileName); + FileUtils.move(temp, fileName); + } + + private static FileChannel getFileChannel(String fileName, String r, + byte[] decryptKey) throws IOException { + FileChannel fileIn = FilePath.get(fileName).open(r); + if (decryptKey != null) { + fileIn = new FileEncrypt(fileName, decryptKey, + fileIn); + } + return fileIn; + } + +} diff --git a/h2/src/main/org/h2/tools/CompressTool.java b/h2/src/main/org/h2/tools/CompressTool.java new file mode 100644 index 0000000..7fa7d50 --- /dev/null +++ b/h2/src/main/org/h2/tools/CompressTool.java @@ -0,0 +1,349 @@ +/* + * 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.tools; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; +import java.util.zip.InflaterInputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import org.h2.api.ErrorCode; +import org.h2.compress.CompressDeflate; +import org.h2.compress.CompressLZF; +import org.h2.compress.CompressNo; +import org.h2.compress.Compressor; +import org.h2.compress.LZFInputStream; +import org.h2.compress.LZFOutputStream; +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; + +/** + * A tool to losslessly compress data, and expand the compressed data again. + */ +public class CompressTool { + + private static final int MAX_BUFFER_SIZE = 3 * Constants.IO_BUFFER_SIZE_COMPRESS; + + private byte[] buffer; + + private CompressTool() { + // don't allow construction + } + + private byte[] getBuffer(int min) { + if (min > MAX_BUFFER_SIZE) { + return Utils.newBytes(min); + } + if (buffer == null || buffer.length < min) { + buffer = Utils.newBytes(min); + } + return buffer; + } + + /** + * Get a new instance. Each instance uses a separate buffer, so multiple + * instances can be used concurrently. However each instance alone is not + * multithreading safe. + * + * @return a new instance + */ + public static CompressTool getInstance() { + return new CompressTool(); + } + + /** + * Compressed the data using the specified algorithm. If no algorithm is + * supplied, LZF is used + * + * @param in the byte array with the original data + * @param algorithm the algorithm (LZF, DEFLATE) + * @return the compressed data + */ + public byte[] compress(byte[] in, String algorithm) { + int len = in.length; + if (in.length < 5) { + algorithm = "NO"; + } + Compressor compress = getCompressor(algorithm); + byte[] buff = getBuffer((len < 100 ? len + 100 : len) * 2); + int newLen = compress(in, in.length, compress, buff); + return Utils.copyBytes(buff, newLen); + } + + private static int compress(byte[] in, int len, Compressor compress, + byte[] out) { + out[0] = (byte) compress.getAlgorithm(); + int start = 1 + writeVariableInt(out, 1, len); + int newLen = compress.compress(in, 0, len, out, start); + if (newLen > len + start || newLen <= 0) { + out[0] = Compressor.NO; + System.arraycopy(in, 0, out, start, len); + newLen = len + start; + } + return newLen; + } + + /** + * Expands the compressed data. + * + * @param in the byte array with the compressed data + * @return the uncompressed data + */ + public byte[] expand(byte[] in) { + if (in.length == 0) { + throw DbException.get(ErrorCode.COMPRESSION_ERROR); + } + int algorithm = in[0]; + Compressor compress = getCompressor(algorithm); + try { + int len = readVariableInt(in, 1); + int start = 1 + getVariableIntLength(len); + byte[] buff = Utils.newBytes(len); + compress.expand(in, start, in.length - start, buff, 0, len); + return buff; + } catch (Exception e) { + throw DbException.get(ErrorCode.COMPRESSION_ERROR, e); + } + } + + /** + * INTERNAL + * @param in compressed data + * @param out uncompressed result + * @param outPos the offset at the output array + */ + public static void expand(byte[] in, byte[] out, int outPos) { + int algorithm = in[0]; + Compressor compress = getCompressor(algorithm); + try { + int len = readVariableInt(in, 1); + int start = 1 + getVariableIntLength(len); + compress.expand(in, start, in.length - start, out, outPos, len); + } catch (Exception e) { + throw DbException.get(ErrorCode.COMPRESSION_ERROR, e); + } + } + + /** + * Read a variable size integer using Rice coding. + * + * @param buff the buffer + * @param pos the position + * @return the integer + */ + public static int readVariableInt(byte[] buff, int pos) { + int x = buff[pos++] & 0xff; + if (x < 0x80) { + return x; + } + if (x < 0xc0) { + return ((x & 0x3f) << 8) + (buff[pos] & 0xff); + } + if (x < 0xe0) { + return ((x & 0x1f) << 16) + + ((buff[pos++] & 0xff) << 8) + + (buff[pos] & 0xff); + } + if (x < 0xf0) { + return ((x & 0xf) << 24) + + ((buff[pos++] & 0xff) << 16) + + ((buff[pos++] & 0xff) << 8) + + (buff[pos] & 0xff); + } + return Bits.readInt(buff, pos); + } + + /** + * Write a variable size integer using Rice coding. + * Negative values need 5 bytes. + * + * @param buff the buffer + * @param pos the position + * @param x the value + * @return the number of bytes written (0-5) + */ + public static int writeVariableInt(byte[] buff, int pos, int x) { + if (x < 0) { + buff[pos++] = (byte) 0xf0; + Bits.writeInt(buff, pos, x); + return 5; + } else if (x < 0x80) { + buff[pos] = (byte) x; + return 1; + } else if (x < 0x4000) { + buff[pos++] = (byte) (0x80 | (x >> 8)); + buff[pos] = (byte) x; + return 2; + } else if (x < 0x20_0000) { + buff[pos++] = (byte) (0xc0 | (x >> 16)); + buff[pos++] = (byte) (x >> 8); + buff[pos] = (byte) x; + return 3; + } else if (x < 0x1000_0000) { + Bits.writeInt(buff, pos, x | 0xe000_0000); + return 4; + } else { + buff[pos++] = (byte) 0xf0; + Bits.writeInt(buff, pos, x); + return 5; + } + } + + /** + * Get a variable size integer length using Rice coding. + * Negative values need 5 bytes. + * + * @param x the value + * @return the number of bytes needed (0-5) + */ + public static int getVariableIntLength(int x) { + if (x < 0) { + return 5; + } else if (x < 0x80) { + return 1; + } else if (x < 0x4000) { + return 2; + } else if (x < 0x20_0000) { + return 3; + } else if (x < 0x1000_0000) { + return 4; + } else { + return 5; + } + } + + private static Compressor getCompressor(String algorithm) { + if (algorithm == null) { + algorithm = "LZF"; + } + int idx = algorithm.indexOf(' '); + String options = null; + if (idx > 0) { + options = algorithm.substring(idx + 1); + algorithm = algorithm.substring(0, idx); + } + int a = getCompressAlgorithm(algorithm); + Compressor compress = getCompressor(a); + compress.setOptions(options); + return compress; + } + + /** + * INTERNAL + * @param algorithm to translate into index + * @return index of the specified algorithm + */ + private static int getCompressAlgorithm(String algorithm) { + algorithm = StringUtils.toUpperEnglish(algorithm); + if ("NO".equals(algorithm)) { + return Compressor.NO; + } else if ("LZF".equals(algorithm)) { + return Compressor.LZF; + } else if ("DEFLATE".equals(algorithm)) { + return Compressor.DEFLATE; + } else { + throw DbException.get( + ErrorCode.UNSUPPORTED_COMPRESSION_ALGORITHM_1, + algorithm); + } + } + + private static Compressor getCompressor(int algorithm) { + switch (algorithm) { + case Compressor.NO: + return new CompressNo(); + case Compressor.LZF: + return new CompressLZF(); + case Compressor.DEFLATE: + return new CompressDeflate(); + default: + throw DbException.get( + ErrorCode.UNSUPPORTED_COMPRESSION_ALGORITHM_1, + Integer.toString(algorithm)); + } + } + + /** + * INTERNAL + * @param out stream + * @param compressionAlgorithm to be used + * @param entryName in a zip file + * @return compressed stream + */ + public static OutputStream wrapOutputStream(OutputStream out, + String compressionAlgorithm, String entryName) { + try { + if ("GZIP".equals(compressionAlgorithm)) { + out = new GZIPOutputStream(out); + } else if ("ZIP".equals(compressionAlgorithm)) { + ZipOutputStream z = new ZipOutputStream(out); + z.putNextEntry(new ZipEntry(entryName)); + out = z; + } else if ("DEFLATE".equals(compressionAlgorithm)) { + out = new DeflaterOutputStream(out); + } else if ("LZF".equals(compressionAlgorithm)) { + out = new LZFOutputStream(out); + } else if (compressionAlgorithm != null) { + throw DbException.get( + ErrorCode.UNSUPPORTED_COMPRESSION_ALGORITHM_1, + compressionAlgorithm); + } + return out; + } catch (IOException e) { + throw DbException.convertIOException(e, null); + } + } + + /** + * INTERNAL + * @param in stream + * @param compressionAlgorithm to be used + * @param entryName in a zip file + * @return in stream or null if there is no such entry + */ + public static InputStream wrapInputStream(InputStream in, + String compressionAlgorithm, String entryName) { + try { + if ("GZIP".equals(compressionAlgorithm)) { + in = new GZIPInputStream(in); + } else if ("ZIP".equals(compressionAlgorithm)) { + ZipInputStream z = new ZipInputStream(in); + while (true) { + ZipEntry entry = z.getNextEntry(); + if (entry == null) { + return null; + } + if (entryName.equals(entry.getName())) { + break; + } + } + in = z; + } else if ("DEFLATE".equals(compressionAlgorithm)) { + in = new InflaterInputStream(in); + } else if ("LZF".equals(compressionAlgorithm)) { + in = new LZFInputStream(in); + } else if (compressionAlgorithm != null) { + throw DbException.get( + ErrorCode.UNSUPPORTED_COMPRESSION_ALGORITHM_1, + compressionAlgorithm); + } + return in; + } catch (IOException e) { + throw DbException.convertIOException(e, null); + } + } + +} + diff --git a/h2/src/main/org/h2/tools/Console.java b/h2/src/main/org/h2/tools/Console.java new file mode 100644 index 0000000..4262430 --- /dev/null +++ b/h2/src/main/org/h2/tools/Console.java @@ -0,0 +1,315 @@ +/* + * 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.tools; + +import java.sql.Connection; +import java.sql.SQLException; +import org.h2.server.ShutdownHandler; +import org.h2.util.JdbcUtils; +import org.h2.util.MathUtils; +import org.h2.util.StringUtils; +import org.h2.util.Tool; +import org.h2.util.Utils; + +/** + * Starts the H2 Console (web-) server, as well as the TCP and PG server. + * + * @author Thomas Mueller, Ridvan Agar + */ +public class Console extends Tool implements ShutdownHandler { + + Server web; + + private Server tcp, pg; + + boolean isWindows; + + /** + * When running without options, -tcp, -web, -browser and -pg are started. + * + * Options are case sensitive. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Supported options
[-help] or [-?]Print the list of options
[-url]Start a browser and connect to this URL
[-driver]Used together with -url: the driver
[-user]Used together with -url: the user name
[-password]Used together with -url: the password
[-web]Start the web server with the H2 Console
[-tool]Start the icon or window that allows to start a browser
[-browser]Start a browser connecting to the web server
[-tcp]Start the TCP server
[-pg]Start the PG server
+ * For each Server, additional options are available; + * for details, see the Server tool. + * If a service can not be started, the program + * terminates with an exit code of 1. + * + * @param args the command line arguments + * @throws SQLException on failure + */ + public static void main(String... args) throws SQLException { + Console console; + try { + console = (Console) Utils.newInstance("org.h2.tools.GUIConsole"); + } catch (Exception | NoClassDefFoundError e) { + console = new Console(); + } + console.runTool(args); + } + + /** + * This tool starts the H2 Console (web-) server, as well as the TCP and PG + * server. A system tray icon is created, for platforms that + * support it. Otherwise, a small window opens. + * + * @param args the command line arguments + */ + @Override + public void runTool(String... args) throws SQLException { + isWindows = Utils.getProperty("os.name", "").startsWith("Windows"); + boolean tcpStart = false, pgStart = false, webStart = false, toolStart = false; + boolean browserStart = false; + boolean startDefaultServers = true; + boolean printStatus = args != null && args.length > 0; + String driver = null, url = null, user = null, password = null; + boolean tcpShutdown = false, tcpShutdownForce = false; + String tcpPassword = ""; + String tcpShutdownServer = ""; + boolean ifExists = false, webAllowOthers = false; + for (int i = 0; args != null && i < args.length; i++) { + String arg = args[i]; + if (arg == null) { + } else if ("-?".equals(arg) || "-help".equals(arg)) { + showUsage(); + return; + } else if ("-url".equals(arg)) { + startDefaultServers = false; + url = args[++i]; + } else if ("-driver".equals(arg)) { + driver = args[++i]; + } else if ("-user".equals(arg)) { + user = args[++i]; + } else if ("-password".equals(arg)) { + password = args[++i]; + } else if (arg.startsWith("-web")) { + if ("-web".equals(arg)) { + startDefaultServers = false; + webStart = true; + } else if ("-webAllowOthers".equals(arg)) { + // no parameters + webAllowOthers = true; + } else if ("-webExternalNames".equals(arg)) { + i++; + } else if ("-webDaemon".equals(arg)) { + // no parameters + } else if ("-webSSL".equals(arg)) { + // no parameters + } else if ("-webPort".equals(arg)) { + i++; + } else { + showUsageAndThrowUnsupportedOption(arg); + } + } else if ("-tool".equals(arg)) { + startDefaultServers = false; + webStart = true; + toolStart = true; + } else if ("-browser".equals(arg)) { + startDefaultServers = false; + webStart = true; + browserStart = true; + } else if (arg.startsWith("-tcp")) { + if ("-tcp".equals(arg)) { + startDefaultServers = false; + tcpStart = true; + } else if ("-tcpAllowOthers".equals(arg)) { + // no parameters + } else if ("-tcpDaemon".equals(arg)) { + // no parameters + } else if ("-tcpSSL".equals(arg)) { + // no parameters + } else if ("-tcpPort".equals(arg)) { + i++; + } else if ("-tcpPassword".equals(arg)) { + tcpPassword = args[++i]; + } else if ("-tcpShutdown".equals(arg)) { + startDefaultServers = false; + tcpShutdown = true; + tcpShutdownServer = args[++i]; + } else if ("-tcpShutdownForce".equals(arg)) { + tcpShutdownForce = true; + } else { + showUsageAndThrowUnsupportedOption(arg); + } + } else if (arg.startsWith("-pg")) { + if ("-pg".equals(arg)) { + startDefaultServers = false; + pgStart = true; + } else if ("-pgAllowOthers".equals(arg)) { + // no parameters + } else if ("-pgDaemon".equals(arg)) { + // no parameters + } else if ("-pgPort".equals(arg)) { + i++; + } else { + showUsageAndThrowUnsupportedOption(arg); + } + } else if ("-properties".equals(arg)) { + i++; + } else if ("-trace".equals(arg)) { + // no parameters + } else if ("-ifExists".equals(arg)) { + // no parameters + ifExists = true; + } else if ("-baseDir".equals(arg)) { + i++; + } else { + showUsageAndThrowUnsupportedOption(arg); + } + } + if (startDefaultServers) { + webStart = true; + toolStart = true; + browserStart = true; + tcpStart = true; + pgStart = true; + } + if (tcpShutdown) { + out.println("Shutting down TCP Server at " + tcpShutdownServer); + Server.shutdownTcpServer(tcpShutdownServer, + tcpPassword, tcpShutdownForce, false); + } + SQLException startException = null; + boolean webRunning = false; + + if (url != null) { + Connection conn = JdbcUtils.getConnection(driver, url, user, password); + Server.startWebServer(conn); + } + + if (webStart) { + try { + String webKey = webAllowOthers ? null + : StringUtils.convertBytesToHex(MathUtils.secureRandomBytes(32)); + web = Server.createWebServer(args, webKey, !ifExists); + web.setShutdownHandler(this); + web.start(); + if (printStatus) { + out.println(web.getStatus()); + } + webRunning = true; + } catch (SQLException e) { + printProblem(e, web); + startException = e; + } + } + + if (toolStart && webRunning){ + show(); + } + + // start browser in any case (even if the server is already running) + // because some people don't look at the output, + // but are wondering why nothing happens + if (browserStart && web != null) { + openBrowser(web.getURL()); + } + + if (tcpStart) { + try { + tcp = Server.createTcpServer(args); + tcp.start(); + if (printStatus) { + out.println(tcp.getStatus()); + } + tcp.setShutdownHandler(this); + } catch (SQLException e) { + printProblem(e, tcp); + if (startException == null) { + startException = e; + } + } + } + if (pgStart) { + try { + pg = Server.createPgServer(args); + pg.start(); + if (printStatus) { + out.println(pg.getStatus()); + } + } catch (SQLException e) { + printProblem(e, pg); + if (startException == null) { + startException = e; + } + } + } + if (startException != null) { + shutdown(); + throw startException; + } + } + + /** + * Overridden by GUIConsole to show a window + */ + void show() { + } + + private void printProblem(Exception e, Server server) { + if (server == null) { + e.printStackTrace(); + } else { + out.println(server.getStatus()); + out.println("Root cause: " + e.getMessage()); + } + } + + /** + * INTERNAL. + * Stop all servers that were started using the console. + */ + @Override + public void shutdown() { + if (web != null && web.isRunning(false)) { + web.stop(); + web = null; + } + if (tcp != null && tcp.isRunning(false)) { + tcp.stop(); + tcp = null; + } + if (pg != null && pg.isRunning(false)) { + pg.stop(); + pg = null; + } + } + + /** + * Open a new browser tab or window with the given URL. + * + * @param url the URL to open + */ + void openBrowser(String url) { + try { + Server.openBrowser(url); + } catch (Exception e) { + out.println(e.getMessage()); + } + } + +} diff --git a/h2/src/main/org/h2/tools/ConvertTraceFile.java b/h2/src/main/org/h2/tools/ConvertTraceFile.java new file mode 100644 index 0000000..c9de53f --- /dev/null +++ b/h2/src/main/org/h2/tools/ConvertTraceFile.java @@ -0,0 +1,226 @@ +/* + * 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.tools; +import java.io.IOException; +import java.io.LineNumberReader; +import java.io.PrintWriter; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.StringTokenizer; +import org.h2.message.DbException; +import org.h2.store.fs.FileUtils; +import org.h2.util.IOUtils; +import org.h2.util.StringUtils; +import org.h2.util.Tool; + +/** + * Converts a .trace.db file to a SQL script and Java source code. + * + * SQL statement statistics are listed as well. + */ +public class ConvertTraceFile extends Tool { + + private final HashMap stats = new HashMap<>(); + private long timeTotal; + + /** + * This class holds statistics about a SQL statement. + */ + static class Stat implements Comparable { + String sql; + int executeCount; + long time; + long resultCount; + + @Override + public int compareTo(Stat other) { + if (other == this) { + return 0; + } + int c = Long.compare(other.time, time); + if (c == 0) { + c = Integer.compare(other.executeCount, executeCount); + if (c == 0) { + c = sql.compareTo(other.sql); + } + } + return c; + } + } + + /** + * Options are case sensitive. + * + * + * + * + * + * + * + * + * + * + *
Supported options
[-help] or [-?]Print the list of options
[-traceFile <file>]The trace file name (default: test.trace.db)
[-script <file>]The script file name (default: test.sql)
[-javaClass <file>]The Java directory and class file name (default: Test)
+ * + * @param args the command line arguments + * @throws SQLException on failure + */ + public static void main(String... args) throws SQLException { + new ConvertTraceFile().runTool(args); + } + + @Override + public void runTool(String... args) throws SQLException { + String traceFile = "test.trace.db"; + String javaClass = "Test"; + String script = "test.sql"; + for (int i = 0; args != null && i < args.length; i++) { + String arg = args[i]; + if (arg.equals("-traceFile")) { + traceFile = args[++i]; + } else if (arg.equals("-javaClass")) { + javaClass = args[++i]; + } else if (arg.equals("-script")) { + script = args[++i]; + } else if (arg.equals("-help") || arg.equals("-?")) { + showUsage(); + return; + } else { + showUsageAndThrowUnsupportedOption(arg); + } + } + try { + convertFile(traceFile, javaClass, script); + } catch (IOException e) { + throw DbException.convertIOException(e, traceFile); + } + } + + /** + * Converts a trace file to a Java class file and a script file. + */ + private void convertFile(String traceFileName, String javaClassName, + String script) throws IOException { + LineNumberReader reader = new LineNumberReader( + IOUtils.getReader( + FileUtils.newInputStream(traceFileName))); + PrintWriter javaWriter = new PrintWriter( + IOUtils.getBufferedWriter( + FileUtils.newOutputStream(javaClassName + ".java", false))); + PrintWriter scriptWriter = new PrintWriter( + IOUtils.getBufferedWriter( + FileUtils.newOutputStream(script, false))); + javaWriter.println("import java.io.*;"); + javaWriter.println("import java.sql.*;"); + javaWriter.println("import java.math.*;"); + javaWriter.println("import java.util.Calendar;"); + String cn = javaClassName.replace('\\', '/'); + int idx = cn.lastIndexOf('/'); + if (idx > 0) { + cn = cn.substring(idx + 1); + } + javaWriter.println("public class " + cn + " {"); + javaWriter.println(" public static void main(String... args) " + + "throws Exception {"); + javaWriter.println(" Class.forName(\"org.h2.Driver\");"); + while (true) { + String line = reader.readLine(); + if (line == null) { + break; + } + if (line.startsWith("/**/")) { + line = " " + line.substring(4); + javaWriter.println(line); + } else if (line.startsWith("/*SQL")) { + int end = line.indexOf("*/"); + String sql = line.substring(end + "*/".length()); + sql = StringUtils.javaDecode(sql); + line = line.substring("/*SQL".length(), end); + if (line.length() > 0) { + String statement = sql; + int count = 0; + long time = 0; + line = line.trim(); + if (line.length() > 0) { + StringTokenizer tk = new StringTokenizer(line, " :"); + while (tk.hasMoreElements()) { + String token = tk.nextToken(); + if ("l".equals(token)) { + int len = Integer.parseInt(tk.nextToken()); + statement = sql.substring(0, len) + ";"; + } else if ("#".equals(token)) { + count = Integer.parseInt(tk.nextToken()); + } else if ("t".equals(token)) { + time = Long.parseLong(tk.nextToken()); + } + } + } + addToStats(statement, count, time); + } + scriptWriter.println(sql); + } + } + javaWriter.println(" }"); + javaWriter.println('}'); + reader.close(); + javaWriter.close(); + if (stats.size() > 0) { + scriptWriter.println("-----------------------------------------"); + scriptWriter.println("-- SQL Statement Statistics"); + scriptWriter.println("-- time: total time in milliseconds (accumulated)"); + scriptWriter.println("-- count: how many times the statement ran"); + scriptWriter.println("-- result: total update count or row count"); + scriptWriter.println("-----------------------------------------"); + scriptWriter.println("-- self accu time count result sql"); + int accumTime = 0; + ArrayList list = new ArrayList<>(stats.values()); + Collections.sort(list); + if (timeTotal == 0) { + timeTotal = 1; + } + for (Stat stat : list) { + accumTime += stat.time; + StringBuilder buff = new StringBuilder(100); + buff.append("-- "). + append(padNumberLeft(100 * stat.time / timeTotal, 3)). + append("% "). + append(padNumberLeft(100 * accumTime / timeTotal, 3)). + append('%'). + append(padNumberLeft(stat.time, 8)). + append(padNumberLeft(stat.executeCount, 8)). + append(padNumberLeft(stat.resultCount, 8)). + append(' '). + append(removeNewlines(stat.sql)); + scriptWriter.println(buff.toString()); + } + } + scriptWriter.close(); + } + + private static String removeNewlines(String s) { + return s == null ? s : s.replace('\r', ' ').replace('\n', ' '); + } + + private static String padNumberLeft(long number, int digits) { + return StringUtils.pad(Long.toString(number), digits, " ", false); + } + + private void addToStats(String sql, int resultCount, long time) { + Stat stat = stats.get(sql); + if (stat == null) { + stat = new Stat(); + stat.sql = sql; + stats.put(sql, stat); + } + stat.executeCount++; + stat.resultCount += resultCount; + stat.time += time; + timeTotal += time; + } + +} diff --git a/h2/src/main/org/h2/tools/CreateCluster.java b/h2/src/main/org/h2/tools/CreateCluster.java new file mode 100644 index 0000000..04508c7 --- /dev/null +++ b/h2/src/main/org/h2/tools/CreateCluster.java @@ -0,0 +1,181 @@ +/* + * 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.tools; + +import java.io.IOException; +import java.io.PipedReader; +import java.io.PipedWriter; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import org.h2.jdbc.JdbcConnection; +import org.h2.util.Tool; + +/** + * Creates a cluster from a stand-alone database. + * + * Copies a database to another location if required. + */ +public class CreateCluster extends Tool { + + /** + * Options are case sensitive. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Supported options
[-help] or [-?]Print the list of options
[-urlSource "<url>"]The database URL of the source database (jdbc:h2:...)
[-urlTarget "<url>"]The database URL of the target database (jdbc:h2:...)
[-user <user>]The user name (default: sa)
[-password <pwd>]The password
[-serverList <list>]The comma separated list of host names or IP addresses
+ * + * @param args the command line arguments + * @throws SQLException on failure + */ + public static void main(String... args) throws SQLException { + new CreateCluster().runTool(args); + } + + @Override + public void runTool(String... args) throws SQLException { + String urlSource = null; + String urlTarget = null; + String user = ""; + String password = ""; + String serverList = null; + for (int i = 0; args != null && i < args.length; i++) { + String arg = args[i]; + if (arg.equals("-urlSource")) { + urlSource = args[++i]; + } else if (arg.equals("-urlTarget")) { + urlTarget = args[++i]; + } else if (arg.equals("-user")) { + user = args[++i]; + } else if (arg.equals("-password")) { + password = args[++i]; + } else if (arg.equals("-serverList")) { + serverList = args[++i]; + } else if (arg.equals("-help") || arg.equals("-?")) { + showUsage(); + return; + } else { + showUsageAndThrowUnsupportedOption(arg); + } + } + if (urlSource == null || urlTarget == null || serverList == null) { + showUsage(); + throw new SQLException("Source URL, target URL, or server list not set"); + } + process(urlSource, urlTarget, user, password, serverList); + } + + /** + * Creates a cluster. + * + * @param urlSource the database URL of the original database + * @param urlTarget the database URL of the copy + * @param user the user name + * @param password the password + * @param serverList the server list + * @throws SQLException on failure + */ + public void execute(String urlSource, String urlTarget, + String user, String password, String serverList) throws SQLException { + process(urlSource, urlTarget, user, password, serverList); + } + + private static void process(String urlSource, String urlTarget, + String user, String password, String serverList) throws SQLException { + // use cluster='' so connecting is possible + // even if the cluster is enabled + try (JdbcConnection connSource = new JdbcConnection(urlSource + ";CLUSTER=''", null, user, password, false); + Statement statSource = connSource.createStatement()) { + // enable the exclusive mode and close other connections, + // so that data can't change while restoring the second database + statSource.execute("SET EXCLUSIVE 2"); + try { + performTransfer(statSource, urlTarget, user, password, serverList); + } finally { + // switch back to the regular mode + statSource.execute("SET EXCLUSIVE FALSE"); + } + } + } + + private static void performTransfer(Statement statSource, String urlTarget, String user, String password, + String serverList) throws SQLException { + + // Delete the target database first. + try (JdbcConnection connTarget = new JdbcConnection(urlTarget + ";CLUSTER=''", null, user, password, false); + Statement statTarget = connTarget.createStatement()) { + statTarget.execute("DROP ALL OBJECTS DELETE FILES"); + } + + try (PipedReader pipeReader = new PipedReader()) { + Future threadFuture = startWriter(pipeReader, statSource); + + // Read data from pipe reader, restore on target. + try (JdbcConnection connTarget = new JdbcConnection(urlTarget, null, user, password, false); + Statement statTarget = connTarget.createStatement()) { + RunScript.execute(connTarget, pipeReader); + + // Check if the writer encountered any exception + try { + threadFuture.get(); + } catch (ExecutionException ex) { + throw new SQLException(ex.getCause()); + } catch (InterruptedException ex) { + throw new SQLException(ex); + } + + // set the cluster to the serverList on both databases + statSource.executeUpdate("SET CLUSTER '" + serverList + "'"); + statTarget.executeUpdate("SET CLUSTER '" + serverList + "'"); + } + } catch (IOException ex) { + throw new SQLException(ex); + } + } + + private static Future startWriter(final PipedReader pipeReader, + final Statement statSource) throws IOException { + final ExecutorService thread = Executors.newFixedThreadPool(1); + final PipedWriter pipeWriter = new PipedWriter(pipeReader); + // Since exceptions cannot be thrown across thread boundaries, return + // the task's future so we can check manually + Future threadFuture = thread.submit(() -> { + // If the creation of the piped writer fails, the reader will + // throw an IOException as soon as read() is called: IOException + // - if the pipe is broken, unconnected, closed, or an I/O error + // occurs. The reader's IOException will then trigger the + // finally{} that releases exclusive mode on the source DB. + try (PipedWriter writer = pipeWriter; + final ResultSet rs = statSource.executeQuery("SCRIPT")) { + while (rs.next()) { + writer.write(rs.getString(1) + "\n"); + } + } catch (SQLException | IOException ex) { + throw new IllegalStateException("Producing script from the source DB is failing.", ex); + } + }); + + thread.shutdown(); + + return threadFuture; + } + +} diff --git a/h2/src/main/org/h2/tools/Csv.java b/h2/src/main/org/h2/tools/Csv.java new file mode 100644 index 0000000..60b9c37 --- /dev/null +++ b/h2/src/main/org/h2/tools/Csv.java @@ -0,0 +1,856 @@ +/* + * 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.tools; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.util.ArrayList; +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.message.DbException; +import org.h2.mvstore.DataUtils; +import org.h2.store.fs.FileUtils; +import org.h2.util.IOUtils; +import org.h2.util.JdbcUtils; +import org.h2.util.StringUtils; +import org.h2.util.Utils; + +/** + * A facility to read from and write to CSV (comma separated values) files. When + * reading, the BOM (the byte-order-mark) character 0xfeff at the beginning of + * the file is ignored. + * + * @author Thomas Mueller, Sylvain Cuaz + */ +public class Csv implements SimpleRowSource { + + private String[] columnNames; + + private String characterSet; + private char escapeCharacter = '\"'; + private char fieldDelimiter = '\"'; + private char fieldSeparatorRead = ','; + private String fieldSeparatorWrite = ","; + private boolean caseSensitiveColumnNames; + private boolean preserveWhitespace; + private boolean writeColumnHeader = true; + private char lineComment; + private String lineSeparator = System.lineSeparator(); + private String nullString = ""; + + private String fileName; + private BufferedReader input; + private char[] inputBuffer; + private int inputBufferPos; + private int inputBufferStart = -1; + private int inputBufferEnd; + private Writer output; + private boolean endOfLine, endOfFile; + + private int writeResultSet(ResultSet rs) throws SQLException { + try { + int rows = 0; + ResultSetMetaData meta = rs.getMetaData(); + int columnCount = meta.getColumnCount(); + String[] row = new String[columnCount]; + for (int i = 0; i < columnCount; i++) { + row[i] = meta.getColumnLabel(i + 1); + } + if (writeColumnHeader) { + writeRow(row); + } + while (rs.next()) { + for (int i = 0; i < columnCount; i++) { + row[i] = rs.getString(i + 1); + } + writeRow(row); + rows++; + } + output.close(); + return rows; + } catch (IOException e) { + throw DbException.convertIOException(e, null); + } finally { + close(); + JdbcUtils.closeSilently(rs); + } + } + + /** + * Writes the result set to a file in the CSV format. + * + * @param writer the writer + * @param rs the result set + * @return the number of rows written + * @throws SQLException on failure + */ + public int write(Writer writer, ResultSet rs) throws SQLException { + this.output = writer; + return writeResultSet(rs); + } + + /** + * Writes the result set to a file in the CSV format. The result set is read + * using the following loop: + * + *

+     * while (rs.next()) {
+     *     writeRow(row);
+     * }
+     * 
+ * + * @param outputFileName the name of the csv file + * @param rs the result set - the result set must be positioned before the + * first row. + * @param charset the charset or null to use the system default charset + * @return the number of rows written + * @throws SQLException on failure + */ + public int write(String outputFileName, ResultSet rs, String charset) + throws SQLException { + init(outputFileName, charset); + try { + initWrite(); + return writeResultSet(rs); + } catch (IOException e) { + throw convertException("IOException writing " + outputFileName, e); + } + } + + /** + * Writes the result set of a query to a file in the CSV format. + * + * @param conn the connection + * @param outputFileName the file name + * @param sql the query + * @param charset the charset or null to use the system default charset + * (see system property file.encoding) + * @return the number of rows written + * @throws SQLException on failure + */ + public int write(Connection conn, String outputFileName, String sql, + String charset) throws SQLException { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery(sql); + int rows = write(outputFileName, rs, charset); + stat.close(); + return rows; + } + + /** + * Reads from the CSV file and returns a result set. The rows in the result + * set are created on demand, that means the file is kept open until all + * rows are read or the result set is closed. + * + * If the columns are read from the CSV file, then the following rules are + * used: columns names that start with a letter or '_', and only + * contain letters, '_', and digits, are considered case insensitive + * and are converted to uppercase. Other column names are considered + * case sensitive (that means they need to be quoted when accessed). + * + * @param inputFileName the file name + * @param colNames or null if the column names should be read from the CSV + * file + * @param charset the charset or null to use the system default charset + * @return the result set + * @throws SQLException on failure + */ + public ResultSet read(String inputFileName, String[] colNames, + String charset) throws SQLException { + init(inputFileName, charset); + try { + return readResultSet(colNames); + } catch (IOException e) { + throw convertException("IOException reading " + inputFileName, e); + } + } + + /** + * Reads CSV data from a reader and returns a result set. The rows in the + * result set are created on demand, that means the reader is kept open + * until all rows are read or the result set is closed. + * + * @param reader the reader + * @param colNames or null if the column names should be read from the CSV + * file + * @return the result set + * @throws IOException on failure + */ + public ResultSet read(Reader reader, String[] colNames) throws IOException { + init(null, null); + this.input = reader instanceof BufferedReader ? (BufferedReader) reader + : new BufferedReader(reader, Constants.IO_BUFFER_SIZE); + return readResultSet(colNames); + } + + private ResultSet readResultSet(String[] colNames) throws IOException { + this.columnNames = colNames; + initRead(); + SimpleResultSet result = new SimpleResultSet(this); + makeColumnNamesUnique(); + for (String columnName : columnNames) { + result.addColumn(columnName, Types.VARCHAR, Integer.MAX_VALUE, 0); + } + return result; + } + + private void makeColumnNamesUnique() { + for (int i = 0; i < columnNames.length; i++) { + StringBuilder buff = new StringBuilder(); + String n = columnNames[i]; + if (n == null || n.isEmpty()) { + buff.append('C').append(i + 1); + } else { + buff.append(n); + } + for (int j = 0; j < i; j++) { + String y = columnNames[j]; + if (buff.toString().equals(y)) { + buff.append('1'); + j = -1; + } + } + columnNames[i] = buff.toString(); + } + } + + private void init(String newFileName, String charset) { + this.fileName = newFileName; + this.characterSet = charset; + } + + private void initWrite() throws IOException { + if (output == null) { + try { + OutputStream out = FileUtils.newOutputStream(fileName, false); + out = new BufferedOutputStream(out, Constants.IO_BUFFER_SIZE); + output = new BufferedWriter(characterSet != null ? + new OutputStreamWriter(out, characterSet) : new OutputStreamWriter(out)); + } catch (Exception e) { + close(); + throw DataUtils.convertToIOException(e); + } + } + } + + private void writeRow(String[] values) throws IOException { + for (int i = 0; i < values.length; i++) { + if (i > 0) { + if (fieldSeparatorWrite != null) { + output.write(fieldSeparatorWrite); + } + } + String s = values[i]; + if (s != null) { + if (escapeCharacter != 0) { + if (fieldDelimiter != 0) { + output.write(fieldDelimiter); + } + output.write(escape(s)); + if (fieldDelimiter != 0) { + output.write(fieldDelimiter); + } + } else { + output.write(s); + } + } else if (nullString != null && nullString.length() > 0) { + output.write(nullString); + } + } + output.write(lineSeparator); + } + + private String escape(String data) { + if (data.indexOf(fieldDelimiter) < 0) { + if (escapeCharacter == fieldDelimiter || data.indexOf(escapeCharacter) < 0) { + return data; + } + } + int length = data.length(); + StringBuilder buff = new StringBuilder(length); + for (int i = 0; i < length; i++) { + char ch = data.charAt(i); + if (ch == fieldDelimiter || ch == escapeCharacter) { + buff.append(escapeCharacter); + } + buff.append(ch); + } + return buff.toString(); + } + + private void initRead() throws IOException { + if (input == null) { + try { + input = FileUtils.newBufferedReader(fileName, + characterSet != null ? Charset.forName(characterSet) : StandardCharsets.UTF_8); + } catch (IOException e) { + close(); + throw e; + } + } + input.mark(1); + int bom = input.read(); + if (bom != 0xfeff) { + // Microsoft Excel compatibility + // ignore pseudo-BOM + input.reset(); + } + inputBuffer = new char[Constants.IO_BUFFER_SIZE * 2]; + if (columnNames == null) { + readHeader(); + } + } + + private void readHeader() throws IOException { + ArrayList list = new ArrayList<>(); + while (true) { + String v = readValue(); + if (v == null) { + if (endOfLine) { + if (endOfFile || !list.isEmpty()) { + break; + } + } else { + v = "COLUMN" + list.size(); + list.add(v); + } + } else { + if (v.isEmpty()) { + v = "COLUMN" + list.size(); + } else if (!caseSensitiveColumnNames && isSimpleColumnName(v)) { + v = StringUtils.toUpperEnglish(v); + } + list.add(v); + if (endOfLine) { + break; + } + } + } + columnNames = list.toArray(new String[0]); + } + + private static boolean isSimpleColumnName(String columnName) { + for (int i = 0, length = columnName.length(); i < length; i++) { + char ch = columnName.charAt(i); + if (i == 0) { + if (ch != '_' && !Character.isLetter(ch)) { + return false; + } + } else { + if (ch != '_' && !Character.isLetterOrDigit(ch)) { + return false; + } + } + } + return columnName.length() != 0; + } + + private void pushBack() { + inputBufferPos--; + } + + private int readChar() throws IOException { + if (inputBufferPos >= inputBufferEnd) { + return readBuffer(); + } + return inputBuffer[inputBufferPos++]; + } + + private int readBuffer() throws IOException { + if (endOfFile) { + return -1; + } + int keep; + if (inputBufferStart >= 0) { + keep = inputBufferPos - inputBufferStart; + if (keep > 0) { + char[] src = inputBuffer; + if (keep + Constants.IO_BUFFER_SIZE > src.length) { + inputBuffer = new char[src.length * 2]; + } + System.arraycopy(src, inputBufferStart, inputBuffer, 0, keep); + } + inputBufferStart = 0; + } else { + keep = 0; + } + inputBufferPos = keep; + int len = input.read(inputBuffer, keep, Constants.IO_BUFFER_SIZE); + if (len == -1) { + // ensure bufferPos > bufferEnd + // even after pushBack + inputBufferEnd = -1024; + endOfFile = true; + // ensure the right number of characters are read + // in case the input buffer is still used + inputBufferPos++; + return -1; + } + inputBufferEnd = keep + len; + return inputBuffer[inputBufferPos++]; + } + + private String readValue() throws IOException { + endOfLine = false; + inputBufferStart = inputBufferPos; + while (true) { + int ch = readChar(); + if (ch == fieldDelimiter) { + // delimited value + boolean containsEscape = false; + inputBufferStart = inputBufferPos; + int sep; + while (true) { + ch = readChar(); + if (ch == fieldDelimiter) { + ch = readChar(); + if (ch != fieldDelimiter) { + sep = 2; + break; + } + containsEscape = true; + } else if (ch == escapeCharacter) { + ch = readChar(); + if (ch < 0) { + sep = 1; + break; + } + containsEscape = true; + } else if (ch < 0) { + sep = 1; + break; + } + } + String s = new String(inputBuffer, + inputBufferStart, inputBufferPos - inputBufferStart - sep); + if (containsEscape) { + s = unEscape(s); + } + inputBufferStart = -1; + while (true) { + if (ch == fieldSeparatorRead) { + break; + } else if (ch == '\n' || ch < 0 || ch == '\r') { + endOfLine = true; + break; + } else if (ch == ' ' || ch == '\t') { + // ignore + } else { + pushBack(); + break; + } + ch = readChar(); + } + return s; + } else if (ch == '\n' || ch < 0 || ch == '\r') { + endOfLine = true; + return null; + } else if (ch == fieldSeparatorRead) { + // null + return null; + } else if (ch <= ' ') { + // ignore spaces + } else if (lineComment != 0 && ch == lineComment) { + // comment until end of line + inputBufferStart = -1; + do { + ch = readChar(); + } while (ch != '\n' && ch >= 0 && ch != '\r'); + endOfLine = true; + return null; + } else { + // un-delimited value + while (true) { + ch = readChar(); + if (ch == fieldSeparatorRead) { + break; + } else if (ch == '\n' || ch < 0 || ch == '\r') { + endOfLine = true; + break; + } + } + String s = new String(inputBuffer, + inputBufferStart, inputBufferPos - inputBufferStart - 1); + if (!preserveWhitespace) { + s = s.trim(); + } + inputBufferStart = -1; + // check un-delimited value for nullString + return readNull(s); + } + } + } + + private String readNull(String s) { + return s.equals(nullString) ? null : s; + } + + private String unEscape(String s) { + StringBuilder buff = new StringBuilder(s.length()); + int start = 0; + char[] chars = null; + while (true) { + int idx = s.indexOf(escapeCharacter, start); + if (idx < 0) { + idx = s.indexOf(fieldDelimiter, start); + if (idx < 0) { + break; + } + } + if (chars == null) { + chars = s.toCharArray(); + } + buff.append(chars, start, idx - start); + if (idx == s.length() - 1) { + start = s.length(); + break; + } + buff.append(chars[idx + 1]); + start = idx + 2; + } + buff.append(s, start, s.length()); + return buff.toString(); + } + + /** + * INTERNAL + */ + @Override + public Object[] readRow() throws SQLException { + if (input == null) { + return null; + } + String[] row = new String[columnNames.length]; + try { + int i = 0; + while (true) { + String v = readValue(); + if (v == null) { + if (endOfLine) { + if (i == 0) { + if (endOfFile) { + return null; + } + // empty line + continue; + } + break; + } + } + if (i < row.length) { + row[i++] = v; + } + if (endOfLine) { + break; + } + } + } catch (IOException e) { + throw convertException("IOException reading from " + fileName, e); + } + return row; + } + + private static SQLException convertException(String message, Exception e) { + return DbException.getJdbcSQLException(ErrorCode.IO_EXCEPTION_1, e, message); + } + + /** + * INTERNAL + */ + @Override + public void close() { + IOUtils.closeSilently(input); + input = null; + IOUtils.closeSilently(output); + output = null; + } + + /** + * INTERNAL + */ + @Override + public void reset() throws SQLException { + throw new SQLException("Method is not supported", "CSV"); + } + + /** + * Override the field separator for writing. The default is ",". + * + * @param fieldSeparatorWrite the field separator + */ + public void setFieldSeparatorWrite(String fieldSeparatorWrite) { + this.fieldSeparatorWrite = fieldSeparatorWrite; + } + + /** + * Get the current field separator for writing. + * + * @return the field separator + */ + public String getFieldSeparatorWrite() { + return fieldSeparatorWrite; + } + + /** + * Override the case sensitive column names setting. The default is false. + * If enabled, the case of all column names is always preserved. + * + * @param caseSensitiveColumnNames whether column names are case sensitive + */ + public void setCaseSensitiveColumnNames(boolean caseSensitiveColumnNames) { + this.caseSensitiveColumnNames = caseSensitiveColumnNames; + } + + /** + * Get the current case sensitive column names setting. + * + * @return whether column names are case sensitive + */ + public boolean getCaseSensitiveColumnNames() { + return caseSensitiveColumnNames; + } + + /** + * Override the field separator for reading. The default is ','. + * + * @param fieldSeparatorRead the field separator + */ + public void setFieldSeparatorRead(char fieldSeparatorRead) { + this.fieldSeparatorRead = fieldSeparatorRead; + } + + /** + * Get the current field separator for reading. + * + * @return the field separator + */ + public char getFieldSeparatorRead() { + return fieldSeparatorRead; + } + + /** + * Set the line comment character. The default is character code 0 (line + * comments are disabled). + * + * @param lineCommentCharacter the line comment character + */ + public void setLineCommentCharacter(char lineCommentCharacter) { + this.lineComment = lineCommentCharacter; + } + + /** + * Get the line comment character. + * + * @return the line comment character, or 0 if disabled + */ + public char getLineCommentCharacter() { + return lineComment; + } + + /** + * Set the field delimiter. The default is " (a double quote). + * The value 0 means no field delimiter is used. + * + * @param fieldDelimiter the field delimiter + */ + public void setFieldDelimiter(char fieldDelimiter) { + this.fieldDelimiter = fieldDelimiter; + } + + /** + * Get the current field delimiter. + * + * @return the field delimiter + */ + public char getFieldDelimiter() { + return fieldDelimiter; + } + + /** + * Set the escape character. The escape character is used to escape the + * field delimiter. This is needed if the data contains the field delimiter. + * The default escape character is " (a double quote), which is the same as + * the field delimiter. If the field delimiter and the escape character are + * both " (double quote), and the data contains a double quote, then an + * additional double quote is added. Example: + *
+     * Data: He said "Hello".
+     * Escape character: "
+     * Field delimiter: "
+     * CSV file: "He said ""Hello""."
+     * 
+ * If the field delimiter is a double quote and the escape character is a + * backslash, then escaping is done similar to Java (however, only the field + * delimiter is escaped). Example: + *
+     * Data: He said "Hello".
+     * Escape character: \
+     * Field delimiter: "
+     * CSV file: "He said \"Hello\"."
+     * 
+ * The value 0 means no escape character is used. + * + * @param escapeCharacter the escape character + */ + public void setEscapeCharacter(char escapeCharacter) { + this.escapeCharacter = escapeCharacter; + } + + /** + * Get the current escape character. + * + * @return the escape character + */ + public char getEscapeCharacter() { + return escapeCharacter; + } + + /** + * Set the line separator used for writing. This is usually a line feed (\n + * or \r\n depending on the system settings). The line separator is written + * after each row (including the last row), so this option can include an + * end-of-row marker if needed. + * + * @param lineSeparator the line separator + */ + public void setLineSeparator(String lineSeparator) { + this.lineSeparator = lineSeparator; + } + + /** + * Get the line separator used for writing. + * + * @return the line separator + */ + public String getLineSeparator() { + return lineSeparator; + } + + /** + * Set the value that represents NULL. It is only used for non-delimited + * values. + * + * @param nullString the null + */ + public void setNullString(String nullString) { + this.nullString = nullString; + } + + /** + * Get the current null string. + * + * @return the null string. + */ + public String getNullString() { + return nullString; + } + + /** + * Enable or disable preserving whitespace in unquoted text. + * + * @param value the new value for the setting + */ + public void setPreserveWhitespace(boolean value) { + this.preserveWhitespace = value; + } + + /** + * Whether whitespace in unquoted text is preserved. + * + * @return the current value for the setting + */ + public boolean getPreserveWhitespace() { + return preserveWhitespace; + } + + /** + * Enable or disable writing the column header. + * + * @param value the new value for the setting + */ + public void setWriteColumnHeader(boolean value) { + this.writeColumnHeader = value; + } + + /** + * Whether the column header is written. + * + * @return the current value for the setting + */ + public boolean getWriteColumnHeader() { + return writeColumnHeader; + } + + /** + * INTERNAL. + * Parse and set the CSV options. + * + * @param options the options + * @return the character set + */ + public String setOptions(String options) { + String charset = null; + String[] keyValuePairs = StringUtils.arraySplit(options, ' ', false); + for (String pair : keyValuePairs) { + if (pair.isEmpty()) { + continue; + } + int index = pair.indexOf('='); + String key = StringUtils.trim(pair.substring(0, index), true, true, " "); + String value = pair.substring(index + 1); + char ch = value.isEmpty() ? 0 : value.charAt(0); + if (isParam(key, "escape", "esc", "escapeCharacter")) { + setEscapeCharacter(ch); + } else if (isParam(key, "fieldDelimiter", "fieldDelim")) { + setFieldDelimiter(ch); + } else if (isParam(key, "fieldSeparator", "fieldSep")) { + setFieldSeparatorRead(ch); + setFieldSeparatorWrite(value); + } else if (isParam(key, "lineComment", "lineCommentCharacter")) { + setLineCommentCharacter(ch); + } else if (isParam(key, "lineSeparator", "lineSep")) { + setLineSeparator(value); + } else if (isParam(key, "null", "nullString")) { + setNullString(value); + } else if (isParam(key, "charset", "characterSet")) { + charset = value; + } else if (isParam(key, "preserveWhitespace")) { + setPreserveWhitespace(Utils.parseBoolean(value, false, false)); + } else if (isParam(key, "writeColumnHeader")) { + setWriteColumnHeader(Utils.parseBoolean(value, true, false)); + } else if (isParam(key, "caseSensitiveColumnNames")) { + setCaseSensitiveColumnNames(Utils.parseBoolean(value, false, false)); + } else { + throw DbException.getUnsupportedException(key); + } + } + return charset; + } + + private static boolean isParam(String key, String... values) { + for (String v : values) { + if (key.equalsIgnoreCase(v)) { + return true; + } + } + return false; + } + +} diff --git a/h2/src/main/org/h2/tools/DeleteDbFiles.java b/h2/src/main/org/h2/tools/DeleteDbFiles.java new file mode 100644 index 0000000..45fe453 --- /dev/null +++ b/h2/src/main/org/h2/tools/DeleteDbFiles.java @@ -0,0 +1,110 @@ +/* + * 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.tools; + +import java.sql.SQLException; +import java.util.ArrayList; + +import org.h2.engine.Constants; +import org.h2.store.FileLister; +import org.h2.store.fs.FileUtils; +import org.h2.util.Tool; + +/** + * Deletes all files belonging to a database. + * + * The database must be closed before calling this tool. + */ +public class DeleteDbFiles extends Tool { + + /** + * Options are case sensitive. + * + * + * + * + * + * + * + * + * + * + *
Supported options
[-help] or [-?]Print the list of options
[-dir <dir>]The directory (default: .)
[-db <database>]The database name
[-quiet]Do not print progress information
+ * + * @param args the command line arguments + * @throws SQLException on failure + */ + public static void main(String... args) throws SQLException { + new DeleteDbFiles().runTool(args); + } + + @Override + public void runTool(String... args) throws SQLException { + String dir = "."; + String db = null; + boolean quiet = false; + for (int i = 0; args != null && i < args.length; i++) { + String arg = args[i]; + if (arg.equals("-dir")) { + dir = args[++i]; + } else if (arg.equals("-db")) { + db = args[++i]; + } else if (arg.equals("-quiet")) { + quiet = true; + } else if (arg.equals("-help") || arg.equals("-?")) { + showUsage(); + return; + } else { + showUsageAndThrowUnsupportedOption(arg); + } + } + process(dir, db, quiet); + } + + /** + * Deletes the database files. + * + * @param dir the directory + * @param db the database name (null for all databases) + * @param quiet don't print progress information + */ + public static void execute(String dir, String db, boolean quiet) { + new DeleteDbFiles().process(dir, db, quiet); + } + + /** + * Deletes the database files. + * + * @param dir the directory + * @param db the database name (null for all databases) + * @param quiet don't print progress information + */ + private void process(String dir, String db, boolean quiet) { + ArrayList files = FileLister.getDatabaseFiles(dir, db, true); + if (files.isEmpty() && !quiet) { + printNoDatabaseFilesFound(dir, db); + } + for (String fileName : files) { + process(fileName, quiet); + if (!quiet) { + out.println("Processed: " + fileName); + } + } + } + + private static void process(String fileName, boolean quiet) { + if (FileUtils.isDirectory(fileName)) { + // only delete empty directories + FileUtils.tryDelete(fileName); + } else if (quiet || fileName.endsWith(Constants.SUFFIX_TEMP_FILE) || + fileName.endsWith(Constants.SUFFIX_TRACE_FILE)) { + FileUtils.tryDelete(fileName); + } else { + FileUtils.delete(fileName); + } + } + +} diff --git a/h2/src/main/org/h2/tools/GUIConsole.java b/h2/src/main/org/h2/tools/GUIConsole.java new file mode 100644 index 0000000..c2b9d1e --- /dev/null +++ b/h2/src/main/org/h2/tools/GUIConsole.java @@ -0,0 +1,611 @@ +/* + * 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.tools; + +import java.awt.Button; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Frame; +import java.awt.GraphicsEnvironment; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Image; +import java.awt.Insets; +import java.awt.Label; +import java.awt.MenuItem; +import java.awt.Panel; +import java.awt.PopupMenu; +import java.awt.SystemColor; +import java.awt.TextArea; +import java.awt.TextField; +import java.awt.Toolkit; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.WindowEvent; +import java.awt.event.WindowListener; +import java.io.IOException; +import java.util.Locale; + +import org.h2.jdbc.JdbcConnection; +import org.h2.util.Utils; + +/** + * Console for environments with AWT support. + */ +public class GUIConsole extends Console implements ActionListener, MouseListener, WindowListener { + private long lastOpenNs; + + private boolean trayIconUsed; + private Font font; + + private Frame statusFrame; + private TextField urlText; + private Button startBrowser; + + private Frame createFrame; + private TextField pathField, userField, passwordField, passwordConfirmationField; + private Button createButton; + private TextArea errorArea; + + private Object tray; + private Object trayIcon; + + @Override + void show() { + if (!GraphicsEnvironment.isHeadless()) { + loadFont(); + try { + if (!createTrayIcon()) { + showStatusWindow(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private static Image loadImage(String name) { + try { + byte[] imageData = Utils.getResource(name); + if (imageData == null) { + return null; + } + return Toolkit.getDefaultToolkit().createImage(imageData); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + @Override + public void shutdown() { + super.shutdown(); + if (statusFrame != null) { + statusFrame.dispose(); + statusFrame = null; + } + if (trayIconUsed) { + try { + // tray.remove(trayIcon); + Utils.callMethod(tray, "remove", trayIcon); + } catch (Exception e) { + // ignore + } finally { + trayIcon = null; + tray = null; + trayIconUsed = false; + } + System.gc(); + // Mac OS X: Console tool process did not stop on exit + String os = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH); + if (os.contains("mac")) { + for (Thread t : Thread.getAllStackTraces().keySet()) { + if (t.getName().startsWith("AWT-")) { + t.interrupt(); + } + } + } + Thread.currentThread().interrupt(); + // throw new ThreadDeath(); + } + } + + private void loadFont() { + if (isWindows) { + font = new Font("Dialog", Font.PLAIN, 11); + } else { + font = new Font("Dialog", Font.PLAIN, 12); + } + } + + private boolean createTrayIcon() { + try { + // SystemTray.isSupported(); + boolean supported = (Boolean) Utils.callStaticMethod( + "java.awt.SystemTray.isSupported"); + if (!supported) { + return false; + } + PopupMenu menuConsole = new PopupMenu(); + MenuItem itemConsole = new MenuItem("H2 Console"); + itemConsole.setActionCommand("console"); + itemConsole.addActionListener(this); + itemConsole.setFont(font); + menuConsole.add(itemConsole); + MenuItem itemCreate = new MenuItem("Create a new database..."); + itemCreate.setActionCommand("showCreate"); + itemCreate.addActionListener(this); + itemCreate.setFont(font); + menuConsole.add(itemCreate); + MenuItem itemStatus = new MenuItem("Status"); + itemStatus.setActionCommand("status"); + itemStatus.addActionListener(this); + itemStatus.setFont(font); + menuConsole.add(itemStatus); + MenuItem itemExit = new MenuItem("Exit"); + itemExit.setFont(font); + itemExit.setActionCommand("exit"); + itemExit.addActionListener(this); + menuConsole.add(itemExit); + + // tray = SystemTray.getSystemTray(); + tray = Utils.callStaticMethod("java.awt.SystemTray.getSystemTray"); + + // Dimension d = tray.getTrayIconSize(); + Dimension d = (Dimension) Utils.callMethod(tray, "getTrayIconSize"); + String iconFile; + if (d.width >= 24 && d.height >= 24) { + iconFile = "/org/h2/res/h2-24.png"; + } else if (d.width >= 22 && d.height >= 22) { + // for Mac OS X 10.8.1 with retina display: + // the reported resolution is 22 x 22, but the image + // is scaled and the real resolution is 44 x 44 + iconFile = "/org/h2/res/h2-64-t.png"; + // iconFile = "/org/h2/res/h2-22-t.png"; + } else { + iconFile = "/org/h2/res/h2.png"; + } + Image icon = loadImage(iconFile); + + // trayIcon = new TrayIcon(image, "H2 Database Engine", + // menuConsole); + trayIcon = Utils.newInstance("java.awt.TrayIcon", + icon, "H2 Database Engine", menuConsole); + + // trayIcon.addMouseListener(this); + Utils.callMethod(trayIcon, "addMouseListener", this); + + // tray.add(trayIcon); + Utils.callMethod(tray, "add", trayIcon); + + this.trayIconUsed = true; + + return true; + } catch (Exception e) { + return false; + } + } + + private void showStatusWindow() { + if (statusFrame != null) { + return; + } + statusFrame = new Frame("H2 Console"); + statusFrame.addWindowListener(this); + Image image = loadImage("/org/h2/res/h2.png"); + if (image != null) { + statusFrame.setIconImage(image); + } + statusFrame.setResizable(false); + statusFrame.setBackground(SystemColor.control); + + GridBagLayout layout = new GridBagLayout(); + statusFrame.setLayout(layout); + + // the main panel keeps everything together + Panel mainPanel = new Panel(layout); + + GridBagConstraints constraintsPanel = new GridBagConstraints(); + constraintsPanel.gridx = 0; + constraintsPanel.weightx = 1.0D; + constraintsPanel.weighty = 1.0D; + constraintsPanel.fill = GridBagConstraints.BOTH; + constraintsPanel.insets = new Insets(0, 10, 0, 10); + constraintsPanel.gridy = 0; + + GridBagConstraints constraintsButton = new GridBagConstraints(); + constraintsButton.gridx = 0; + constraintsButton.gridwidth = 2; + constraintsButton.insets = new Insets(10, 0, 0, 0); + constraintsButton.gridy = 1; + constraintsButton.anchor = GridBagConstraints.EAST; + + GridBagConstraints constraintsTextField = new GridBagConstraints(); + constraintsTextField.fill = GridBagConstraints.HORIZONTAL; + constraintsTextField.gridy = 0; + constraintsTextField.weightx = 1.0; + constraintsTextField.insets = new Insets(0, 5, 0, 0); + constraintsTextField.gridx = 1; + + GridBagConstraints constraintsLabel = new GridBagConstraints(); + constraintsLabel.gridx = 0; + constraintsLabel.gridy = 0; + + Label label = new Label("H2 Console URL:", Label.LEFT); + label.setFont(font); + mainPanel.add(label, constraintsLabel); + + urlText = new TextField(); + urlText.setEditable(false); + urlText.setFont(font); + urlText.setText(web.getURL()); + if (isWindows) { + urlText.setFocusable(false); + } + mainPanel.add(urlText, constraintsTextField); + + startBrowser = new Button("Start Browser"); + startBrowser.setFocusable(false); + startBrowser.setActionCommand("console"); + startBrowser.addActionListener(this); + startBrowser.setFont(font); + mainPanel.add(startBrowser, constraintsButton); + statusFrame.add(mainPanel, constraintsPanel); + + int width = 300, height = 120; + statusFrame.setSize(width, height); + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + statusFrame.setLocation((screenSize.width - width) / 2, + (screenSize.height - height) / 2); + try { + statusFrame.setVisible(true); + } catch (Throwable t) { + // ignore + // some systems don't support this method, for example IKVM + // however it still works + } + try { + // ensure this window is in front of the browser + statusFrame.setAlwaysOnTop(true); + statusFrame.setAlwaysOnTop(false); + } catch (Throwable t) { + // ignore + } + } + + private void startBrowser() { + if (web != null) { + String url = web.getURL(); + if (urlText != null) { + urlText.setText(url); + } + long now = Utils.currentNanoTime(); + if (lastOpenNs == 0 || now - lastOpenNs > 100_000_000L) { + lastOpenNs = now; + openBrowser(url); + } + } + } + + private void showCreateDatabase() { + if (createFrame != null) { + return; + } + createFrame = new Frame("H2 Console"); + createFrame.addWindowListener(this); + Image image = loadImage("/org/h2/res/h2.png"); + if (image != null) { + createFrame.setIconImage(image); + } + createFrame.setResizable(false); + createFrame.setBackground(SystemColor.control); + + GridBagLayout layout = new GridBagLayout(); + createFrame.setLayout(layout); + + // the main panel keeps everything together + Panel mainPanel = new Panel(layout); + + GridBagConstraints constraints; + + constraints = new GridBagConstraints(); + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.insets = new Insets(10, 0, 0, 0); + constraints.gridx = 0; + constraints.gridy = 0; + Label urlLabel = new Label("Database path:", Label.LEFT); + urlLabel.setFont(font); + mainPanel.add(urlLabel, constraints); + + constraints = new GridBagConstraints(); + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.gridy = 0; + constraints.weightx = 1d; + constraints.insets = new Insets(10, 5, 0, 0); + constraints.gridx = 1; + pathField = new TextField(); + pathField.setFont(font); + pathField.setText("./test"); + mainPanel.add(pathField, constraints); + + constraints = new GridBagConstraints(); + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.gridx = 0; + constraints.gridy = 1; + Label userLabel = new Label("Username:", Label.LEFT); + userLabel.setFont(font); + mainPanel.add(userLabel, constraints); + + constraints = new GridBagConstraints(); + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.gridy = 1; + constraints.weightx = 1d; + constraints.insets = new Insets(0, 5, 0, 0); + constraints.gridx = 1; + userField = new TextField(); + userField.setFont(font); + userField.setText("sa"); + mainPanel.add(userField, constraints); + + constraints = new GridBagConstraints(); + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.gridx = 0; + constraints.gridy = 2; + Label passwordLabel = new Label("Password:", Label.LEFT); + passwordLabel.setFont(font); + mainPanel.add(passwordLabel, constraints); + + constraints = new GridBagConstraints(); + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.gridy = 2; + constraints.weightx = 1d; + constraints.insets = new Insets(0, 5, 0, 0); + constraints.gridx = 1; + passwordField = new TextField(); + passwordField.setFont(font); + passwordField.setEchoChar('*'); + mainPanel.add(passwordField, constraints); + + constraints = new GridBagConstraints(); + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.gridx = 0; + constraints.gridy = 3; + Label passwordConfirmationLabel = new Label("Password confirmation:", Label.LEFT); + passwordConfirmationLabel.setFont(font); + mainPanel.add(passwordConfirmationLabel, constraints); + + constraints = new GridBagConstraints(); + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.gridy = 3; + constraints.weightx = 1d; + constraints.insets = new Insets(0, 5, 0, 0); + constraints.gridx = 1; + passwordConfirmationField = new TextField(); + passwordConfirmationField.setFont(font); + passwordConfirmationField.setEchoChar('*'); + mainPanel.add(passwordConfirmationField, constraints); + + constraints = new GridBagConstraints(); + constraints.gridx = 0; + constraints.gridwidth = 2; + constraints.insets = new Insets(10, 0, 0, 0); + constraints.gridy = 4; + constraints.anchor = GridBagConstraints.EAST; + createButton = new Button("Create"); + createButton.setFocusable(false); + createButton.setActionCommand("create"); + createButton.addActionListener(this); + createButton.setFont(font); + mainPanel.add(createButton, constraints); + + constraints = new GridBagConstraints(); + constraints.fill = GridBagConstraints.HORIZONTAL; + constraints.gridy = 5; + constraints.weightx = 1d; + constraints.insets = new Insets(10, 0, 0, 0); + constraints.gridx = 0; + constraints.gridwidth = 2; + errorArea = new TextArea(); + errorArea.setFont(font); + errorArea.setEditable(false); + mainPanel.add(errorArea, constraints); + + constraints = new GridBagConstraints(); + constraints.gridx = 0; + constraints.weightx = 1d; + constraints.weighty = 1d; + constraints.fill = GridBagConstraints.BOTH; + constraints.insets = new Insets(0, 10, 0, 10); + constraints.gridy = 0; + createFrame.add(mainPanel, constraints); + + int width = 400, height = 400; + createFrame.setSize(width, height); + createFrame.pack(); + createFrame.setLocationRelativeTo(null); + try { + createFrame.setVisible(true); + } catch (Throwable t) { + // ignore + // some systems don't support this method, for example IKVM + // however it still works + } + try { + // ensure this window is in front of the browser + createFrame.setAlwaysOnTop(true); + createFrame.setAlwaysOnTop(false); + } catch (Throwable t) { + // ignore + } + } + + private void createDatabase() { + if (web == null || createFrame == null) { + return; + } + String path = pathField.getText(), user = userField.getText(), password = passwordField.getText(), + passwordConfirmation = passwordConfirmationField.getText(); + if (!password.equals(passwordConfirmation)) { + errorArea.setForeground(Color.RED); + errorArea.setText("Passwords don't match"); + return; + } + if (password.isEmpty()) { + errorArea.setForeground(Color.RED); + errorArea.setText("Specify a password"); + return; + } + String url = "jdbc:h2:" + path; + try { + new JdbcConnection(url, null, user, password, false).close(); + errorArea.setForeground(new Color(0, 0x99, 0)); + errorArea.setText("Database was created successfully.\n\n" + + "JDBC URL for H2 Console:\n" + + url); + } catch (Exception ex) { + errorArea.setForeground(Color.RED); + errorArea.setText(ex.getMessage()); + } + } + + /** + * INTERNAL + */ + @Override + public void actionPerformed(ActionEvent e) { + String command = e.getActionCommand(); + if ("exit".equals(command)) { + shutdown(); + } else if ("console".equals(command)) { + startBrowser(); + } else if ("showCreate".equals(command)) { + showCreateDatabase(); + } else if ("status".equals(command)) { + showStatusWindow(); + } else { + // for some reason, IKVM ignores setActionCommand + if (startBrowser == e.getSource()) { + startBrowser(); + } else if (createButton == e.getSource()) { + createDatabase(); + } + } + } + + /** + * INTERNAL + */ + @Override + public void mouseClicked(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + startBrowser(); + } + } + + /** + * INTERNAL + */ + @Override + public void mouseEntered(MouseEvent e) { + // nothing to do + } + + /** + * INTERNAL + */ + @Override + public void mouseExited(MouseEvent e) { + // nothing to do + } + + /** + * INTERNAL + */ + @Override + public void mousePressed(MouseEvent e) { + // nothing to do + } + + /** + * INTERNAL + */ + @Override + public void mouseReleased(MouseEvent e) { + // nothing to do + } + + /** + * INTERNAL + */ + @Override + public void windowClosing(WindowEvent e) { + if (trayIconUsed) { + Window window = e.getWindow(); + if (window == statusFrame) { + statusFrame.dispose(); + statusFrame = null; + } else if (window == createFrame) { + createFrame.dispose(); + createFrame = null; + } + } else { + shutdown(); + } + } + + /** + * INTERNAL + */ + @Override + public void windowActivated(WindowEvent e) { + // nothing to do + } + + /** + * INTERNAL + */ + @Override + public void windowClosed(WindowEvent e) { + // nothing to do + } + + /** + * INTERNAL + */ + @Override + public void windowDeactivated(WindowEvent e) { + // nothing to do + } + + /** + * INTERNAL + */ + @Override + public void windowDeiconified(WindowEvent e) { + // nothing to do + } + + /** + * INTERNAL + */ + @Override + public void windowIconified(WindowEvent e) { + // nothing to do + } + + /** + * INTERNAL + */ + @Override + public void windowOpened(WindowEvent e) { + // nothing to do + } + +} diff --git a/h2/src/main/org/h2/tools/MultiDimension.java b/h2/src/main/org/h2/tools/MultiDimension.java new file mode 100644 index 0000000..591348f --- /dev/null +++ b/h2/src/main/org/h2/tools/MultiDimension.java @@ -0,0 +1,336 @@ +/* + * 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.tools; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Comparator; +import org.h2.util.StringUtils; + +/** + * A tool to help an application execute multi-dimensional range queries. + * The algorithm used is database independent, the only requirement + * is that the engine supports a range index (for example b-tree). + */ +public class MultiDimension implements Comparator { + + private static final MultiDimension INSTANCE = new MultiDimension(); + + protected MultiDimension() { + // don't allow construction by normal code + // but allow tests + } + + /** + * Get the singleton. + * + * @return the singleton + */ + public static MultiDimension getInstance() { + return INSTANCE; + } + + /** + * Normalize a value so that it is between the minimum and maximum for the + * given number of dimensions. + * + * @param dimensions the number of dimensions + * @param value the value (must be in the range min..max) + * @param min the minimum value + * @param max the maximum value (must be larger than min) + * @return the normalized value in the range 0..getMaxValue(dimensions) + */ + public int normalize(int dimensions, double value, double min, double max) { + if (value < min || value > max) { + throw new IllegalArgumentException(min + "<" + value + "<" + max); + } + double x = (value - min) / (max - min); + return (int) (x * getMaxValue(dimensions)); + } + + /** + * Get the maximum value for the given dimension count. For two dimensions, + * each value must contain at most 32 bit, for 3: 21 bit, 4: 16 bit, 5: 12 + * bit, 6: 10 bit, 7: 9 bit, 8: 8 bit. + * + * @param dimensions the number of dimensions + * @return the maximum value + */ + public int getMaxValue(int dimensions) { + if (dimensions < 2 || dimensions > 32) { + throw new IllegalArgumentException(Integer.toString(dimensions)); + } + int bitsPerValue = getBitsPerValue(dimensions); + return (int) ((1L << bitsPerValue) - 1); + } + + private static int getBitsPerValue(int dimensions) { + return Math.min(31, 64 / dimensions); + } + + /** + * Convert the multi-dimensional value into a one-dimensional (scalar) + * value. This is done by interleaving the bits of the values. Each values + * must be between 0 (including) and the maximum value for the given number + * of dimensions (getMaxValue, excluding). To normalize values to this + * range, use the normalize function. + * + * @param values the multi-dimensional value + * @return the scalar value + */ + public long interleave(int... values) { + int dimensions = values.length; + long max = getMaxValue(dimensions); + int bitsPerValue = getBitsPerValue(dimensions); + long x = 0; + for (int i = 0; i < dimensions; i++) { + long k = values[i]; + if (k < 0 || k > max) { + throw new IllegalArgumentException(0 + "<" + k + "<" + max); + } + for (int b = 0; b < bitsPerValue; b++) { + x |= (k & (1L << b)) << (i + (dimensions - 1) * b); + } + } + return x; + } + + /** + * Convert the two-dimensional value into a one-dimensional (scalar) value. + * This is done by interleaving the bits of the values. + * Each values must be between 0 (including) and the maximum value + * for the given number of dimensions (getMaxValue, excluding). + * To normalize values to this range, use the normalize function. + * + * @param x the value of the first dimension, normalized + * @param y the value of the second dimension, normalized + * @return the scalar value + */ + public long interleave(int x, int y) { + if (x < 0) { + throw new IllegalArgumentException(0 + "<" + x); + } + if (y < 0) { + throw new IllegalArgumentException(0 + "<" + y); + } + long z = 0; + for (int i = 0; i < 32; i++) { + z |= (x & (1L << i)) << i; + z |= (y & (1L << i)) << (i + 1); + } + return z; + } + + /** + * Gets one of the original multi-dimensional values from a scalar value. + * + * @param dimensions the number of dimensions + * @param scalar the scalar value + * @param dim the dimension of the returned value (starting from 0) + * @return the value + */ + public int deinterleave(int dimensions, long scalar, int dim) { + int bitsPerValue = getBitsPerValue(dimensions); + int value = 0; + for (int i = 0; i < bitsPerValue; i++) { + value |= (scalar >> (dim + (dimensions - 1) * i)) & (1L << i); + } + return value; + } + + /** + * Generates an optimized multi-dimensional range query. The query contains + * parameters. It can only be used with the H2 database. + * + * @param table the table name + * @param columns the list of columns + * @param scalarColumn the column name of the computed scalar column + * @return the query + */ + public String generatePreparedQuery(String table, String scalarColumn, + String[] columns) { + StringBuilder buff = new StringBuilder("SELECT D.* FROM "); + StringUtils.quoteIdentifier(buff, table). + append(" D, TABLE(_FROM_ BIGINT=?, _TO_ BIGINT=?) WHERE "); + StringUtils.quoteIdentifier(buff, scalarColumn). + append(" BETWEEN _FROM_ AND _TO_"); + for (String col : columns) { + buff.append(" AND "); + StringUtils.quoteIdentifier(buff, col). + append("+1 BETWEEN ?+1 AND ?+1"); + } + return buff.toString(); + } + + /** + * Executes a prepared query that was generated using generatePreparedQuery. + * + * @param prep the prepared statement + * @param min the lower values + * @param max the upper values + * @return the result set + * @throws SQLException on failure + */ + public ResultSet getResult(PreparedStatement prep, int[] min, int[] max) + throws SQLException { + long[][] ranges = getMortonRanges(min, max); + int len = ranges.length; + Long[] from = new Long[len]; + Long[] to = new Long[len]; + for (int i = 0; i < len; i++) { + from[i] = ranges[i][0]; + to[i] = ranges[i][1]; + } + prep.setObject(1, from); + prep.setObject(2, to); + len = min.length; + for (int i = 0, idx = 3; i < len; i++) { + prep.setInt(idx++, min[i]); + prep.setInt(idx++, max[i]); + } + return prep.executeQuery(); + } + + /** + * Gets a list of ranges to be searched for a multi-dimensional range query + * where min <= value <= max. In most cases, the ranges will be larger + * than required in order to combine smaller ranges into one. Usually, about + * double as many points will be included in the resulting range. + * + * @param min the minimum value + * @param max the maximum value + * @return the list of ranges (low, high pairs) + */ + private long[][] getMortonRanges(int[] min, int[] max) { + int len = min.length; + if (max.length != len) { + throw new IllegalArgumentException(len + "=" + max.length); + } + for (int i = 0; i < len; i++) { + if (min[i] > max[i]) { + int temp = min[i]; + min[i] = max[i]; + max[i] = temp; + } + } + int total = getSize(min, max, len); + ArrayList list = new ArrayList<>(); + addMortonRanges(list, min, max, len, 0); + combineEntries(list, total); + return list.toArray(new long[0][]); + } + + private static int getSize(int[] min, int[] max, int len) { + int size = 1; + for (int i = 0; i < len; i++) { + int diff = max[i] - min[i]; + size *= diff + 1; + } + return size; + } + + /** + * Combine entries if the size of the list is too large. + * + * @param list list of pairs(low, high) + * @param total product of the gap lengths + */ + private void combineEntries(ArrayList list, int total) { + list.sort(this); + for (int minGap = 10; minGap < total; minGap += minGap / 2) { + for (int i = 0; i < list.size() - 1; i++) { + long[] current = list.get(i); + long[] next = list.get(i + 1); + if (current[1] + minGap >= next[0]) { + current[1] = next[1]; + list.remove(i + 1); + i--; + } + } + int searched = 0; + for (long[] range : list) { + searched += range[1] - range[0] + 1; + } + if (searched > 2 * total || list.size() < 100) { + break; + } + } + } + + @Override + public int compare(long[] a, long[] b) { + return a[0] > b[0] ? 1 : -1; + } + + private void addMortonRanges(ArrayList list, int[] min, int[] max, + int len, int level) { + if (level > 100) { + throw new IllegalArgumentException(Integer.toString(level)); + } + int largest = 0, largestDiff = 0; + long size = 1; + for (int i = 0; i < len; i++) { + int diff = max[i] - min[i]; + if (diff < 0) { + throw new IllegalArgumentException(Integer.toString(diff)); + } + size *= diff + 1; + if (size < 0) { + throw new IllegalArgumentException(Long.toString(size)); + } + if (diff > largestDiff) { + largestDiff = diff; + largest = i; + } + } + long low = interleave(min), high = interleave(max); + if (high < low) { + throw new IllegalArgumentException(high + "<" + low); + } + long range = high - low + 1; + if (range == size) { + long[] item = { low, high }; + list.add(item); + } else { + int middle = findMiddle(min[largest], max[largest]); + int temp = max[largest]; + max[largest] = middle; + addMortonRanges(list, min, max, len, level + 1); + max[largest] = temp; + temp = min[largest]; + min[largest] = middle + 1; + addMortonRanges(list, min, max, len, level + 1); + min[largest] = temp; + } + } + + private static int roundUp(int x, int blockSizePowerOf2) { + return (x + blockSizePowerOf2 - 1) & -blockSizePowerOf2; + } + + private static int findMiddle(int a, int b) { + int diff = b - a - 1; + if (diff == 0) { + return a; + } + if (diff == 1) { + return a + 1; + } + int scale = 0; + while ((1 << scale) < diff) { + scale++; + } + scale--; + int m = roundUp(a + 2, 1 << scale) - 1; + if (m <= a || m >= b) { + throw new IllegalArgumentException(a + "<" + m + "<" + b); + } + return m; + } + +} diff --git a/h2/src/main/org/h2/tools/Recover.java b/h2/src/main/org/h2/tools/Recover.java new file mode 100644 index 0000000..ee267c1 --- /dev/null +++ b/h2/src/main/org/h2/tools/Recover.java @@ -0,0 +1,754 @@ +/* + * 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.tools; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.SequenceInputStream; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import org.h2.engine.Constants; +import org.h2.engine.DbObject; +import org.h2.engine.MetaRecord; +import org.h2.message.DbException; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVStore; +import org.h2.mvstore.MVStoreTool; +import org.h2.mvstore.StreamStore; +import org.h2.mvstore.db.LobStorageMap; +import org.h2.mvstore.db.ValueDataType; +import org.h2.mvstore.tx.TransactionMap; +import org.h2.mvstore.tx.TransactionStore; +import org.h2.mvstore.type.DataType; +import org.h2.mvstore.type.MetaType; +import org.h2.mvstore.type.StringDataType; +import org.h2.result.Row; +import org.h2.store.DataHandler; +import org.h2.store.FileLister; +import org.h2.store.FileStore; +import org.h2.store.LobStorageFrontend; +import org.h2.store.LobStorageInterface; +import org.h2.store.fs.FileUtils; +import org.h2.util.HasSQL; +import org.h2.util.IOUtils; +import org.h2.util.SmallLRUCache; +import org.h2.util.StringUtils; +import org.h2.util.TempFileDeleter; +import org.h2.util.Tool; +import org.h2.value.CompareMode; +import org.h2.value.Value; +import org.h2.value.ValueCollectionBase; +import org.h2.value.ValueLob; +import org.h2.value.lob.LobData; +import org.h2.value.lob.LobDataDatabase; + +/** + * Helps recovering a corrupted database. + */ +public class Recover extends Tool implements DataHandler { + + private String databaseName; + private int storageId; + private String storageName; + private int recordLength; + private int valueId; + private boolean trace; + private ArrayList schema; + private HashSet objectIdSet; + private HashMap tableMap; + private HashMap columnTypeMap; + private boolean lobMaps; + + /** + * Options are case sensitive. + * + * + * + * + * + * + * + * + * + * + * + * + *
Supported options
[-help] or [-?]Print the list of options
[-dir <dir>]The directory (default: .)
[-db <database>]The database name (all databases if not set)
[-trace]Print additional trace information
[-transactionLog]Print the transaction log
+ * Encrypted databases need to be decrypted first. + * + * @param args the command line arguments + * @throws SQLException on failure + */ + public static void main(String... args) throws SQLException { + new Recover().runTool(args); + } + + /** + * Dumps the contents of a database file to a human readable text file. This + * text file can be used to recover most of the data. This tool does not + * open the database and can be used even if the database files are + * corrupted. A database can get corrupted if there is a bug in the database + * engine or file system software, or if an application writes into the + * database file that doesn't understand the file format, or if there is + * a hardware problem. + * + * @param args the command line arguments + * @throws SQLException on failure + */ + @Override + public void runTool(String... args) throws SQLException { + String dir = "."; + String db = null; + for (int i = 0; args != null && i < args.length; i++) { + String arg = args[i]; + if ("-dir".equals(arg)) { + dir = args[++i]; + } else if ("-db".equals(arg)) { + db = args[++i]; + } else if ("-trace".equals(arg)) { + trace = true; + } else if (arg.equals("-help") || arg.equals("-?")) { + showUsage(); + return; + } else { + showUsageAndThrowUnsupportedOption(arg); + } + } + process(dir, db); + } + + /** + * INTERNAL + * @param conn to use + * @param lobId id of the LOB stream + * @param precision not used + * @return InputStream to read LOB content from + * @throws SQLException on failure + */ + public static InputStream readBlobMap(Connection conn, long lobId, + long precision) throws SQLException { + final PreparedStatement prep = conn.prepareStatement( + "SELECT DATA FROM INFORMATION_SCHEMA.LOB_BLOCKS " + + "WHERE LOB_ID = ? AND SEQ = ? AND ? > 0"); + prep.setLong(1, lobId); + // precision is currently not really used, + // it is just to improve readability of the script + prep.setLong(3, precision); + return new SequenceInputStream( + new Enumeration() { + + private int seq; + private byte[] data = fetch(); + + private byte[] fetch() { + try { + prep.setInt(2, seq++); + ResultSet rs = prep.executeQuery(); + if (rs.next()) { + return rs.getBytes(1); + } + return null; + } catch (SQLException e) { + throw DbException.convert(e); + } + } + + @Override + public boolean hasMoreElements() { + return data != null; + } + + @Override + public InputStream nextElement() { + ByteArrayInputStream in = new ByteArrayInputStream(data); + data = fetch(); + return in; + } + } + ); + } + + /** + * INTERNAL + * @param conn to use + * @param lobId id of the LOB stream + * @param precision not used + * @return Reader to read LOB content from + * @throws SQLException on failure + */ + public static Reader readClobMap(Connection conn, long lobId, long precision) + throws Exception { + InputStream in = readBlobMap(conn, lobId, precision); + return new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); + } + + private void trace(String message) { + if (trace) { + out.println(message); + } + } + + private void traceError(String message, Throwable t) { + out.println(message + ": " + t.toString()); + if (trace) { + t.printStackTrace(out); + } + } + + /** + * Dumps the contents of a database to a SQL script file. + * + * @param dir the directory + * @param db the database name (null for all databases) + * @throws SQLException on failure + */ + public static void execute(String dir, String db) throws SQLException { + try { + new Recover().process(dir, db); + } catch (DbException e) { + throw DbException.toSQLException(e); + } + } + + private void process(String dir, String db) { + ArrayList list = FileLister.getDatabaseFiles(dir, db, true); + if (list.isEmpty()) { + printNoDatabaseFilesFound(dir, db); + } + for (String fileName : list) { + if (fileName.endsWith(Constants.SUFFIX_MV_FILE)) { + String f = fileName.substring(0, fileName.length() - + Constants.SUFFIX_MV_FILE.length()); + try (PrintWriter writer = getWriter(fileName, ".txt")) { + MVStoreTool.dump(fileName, writer, true); + MVStoreTool.info(fileName, writer); + } + try (PrintWriter writer = getWriter(f + ".h2.db", ".sql")) { + dumpMVStoreFile(writer, fileName); + } + } + } + } + + private PrintWriter getWriter(String fileName, String suffix) { + fileName = fileName.substring(0, fileName.length() - 3); + String outputFile = fileName + suffix; + trace("Created file: " + outputFile); + try { + return new PrintWriter(IOUtils.getBufferedWriter( + FileUtils.newOutputStream(outputFile, false))); + } catch (IOException e) { + throw DbException.convertIOException(e, null); + } + } + + private void getSQL(StringBuilder builder, String column, Value v) { + if (v instanceof ValueLob) { + ValueLob lob = (ValueLob) v; + LobData lobData = lob.getLobData(); + if (lobData instanceof LobDataDatabase) { + LobDataDatabase lobDataDatabase = (LobDataDatabase) lobData; + int type = v.getValueType(); + long id = lobDataDatabase.getLobId(); + long precision; + String columnType; + if (type == Value.BLOB) { + precision = lob.octetLength(); + columnType = "BLOB"; + builder.append("READ_BLOB"); + } else { + precision = lob.charLength(); + columnType = "CLOB"; + builder.append("READ_CLOB"); + } + if (lobMaps) { + builder.append("_MAP"); + } else { + builder.append("_DB"); + } + columnTypeMap.put(column, columnType); + builder.append('(').append(id).append(", ").append(precision).append(')'); + return; + } + } + v.getSQL(builder, HasSQL.NO_CASTS); + } + + private void setDatabaseName(String name) { + databaseName = name; + } + + private void dumpMVStoreFile(PrintWriter writer, String fileName) { + writer.println("-- MVStore"); + String className = getClass().getName(); + writer.println("CREATE ALIAS IF NOT EXISTS READ_BLOB_MAP FOR '" + className + ".readBlobMap';"); + writer.println("CREATE ALIAS IF NOT EXISTS READ_CLOB_MAP FOR '" + className + ".readClobMap';"); + resetSchema(); + setDatabaseName(fileName.substring(0, fileName.length() - + Constants.SUFFIX_MV_FILE.length())); + try (MVStore mv = new MVStore.Builder(). + fileName(fileName).recoveryMode().readOnly().open()) { + dumpLobMaps(writer, mv); + writer.println("-- Layout"); + dumpLayout(writer, mv); + writer.println("-- Meta"); + dumpMeta(writer, mv); + writer.println("-- Types"); + dumpTypes(writer, mv); + writer.println("-- Tables"); + TransactionStore store = new TransactionStore(mv, new ValueDataType()); + try { + store.init(); + } catch (Throwable e) { + writeError(writer, e); + } + + // extract the metadata so we can dump the settings + for (String mapName : mv.getMapNames()) { + if (!mapName.startsWith("table.")) { + continue; + } + String tableId = mapName.substring("table.".length()); + if (Integer.parseInt(tableId) == 0) { + TransactionMap dataMap = store.begin().openMap(mapName); + Iterator dataIt = dataMap.keyIterator(null); + while (dataIt.hasNext()) { + Long rowId = dataIt.next(); + Row row = dataMap.get(rowId); + try { + writeMetaRow(row); + } catch (Throwable t) { + writeError(writer, t); + } + } + } + } + // Have to do these before the tables because settings like COLLATION may affect + // some of them, and we can't change settings after we have created user tables + writeSchemaSET(writer); + writer.println("---- Table Data ----"); + for (String mapName : mv.getMapNames()) { + if (!mapName.startsWith("table.")) { + continue; + } + String tableId = mapName.substring("table.".length()); + if (Integer.parseInt(tableId) == 0) { + continue; + } + TransactionMap dataMap = store.begin().openMap(mapName); + Iterator dataIt = dataMap.keyIterator(null); + boolean init = false; + while (dataIt.hasNext()) { + Object rowId = dataIt.next(); + Object value = dataMap.get(rowId); + Value[] values; + if (value instanceof Row) { + values = ((Row) value).getValueList(); + recordLength = values.length; + } else { + values = ((ValueCollectionBase) value).getList(); + recordLength = values.length - 1; + } + if (!init) { + setStorage(Integer.parseInt(tableId)); + // init the column types + StringBuilder builder = new StringBuilder(); + for (valueId = 0; valueId < recordLength; valueId++) { + String columnName = storageName + "." + valueId; + builder.setLength(0); + getSQL(builder, columnName, values[valueId]); + } + createTemporaryTable(writer); + init = true; + } + StringBuilder buff = new StringBuilder(); + buff.append("INSERT INTO O_").append(tableId) + .append(" VALUES("); + for (valueId = 0; valueId < recordLength; valueId++) { + if (valueId > 0) { + buff.append(", "); + } + String columnName = storageName + "." + valueId; + getSQL(buff, columnName, values[valueId]); + } + buff.append(");"); + writer.println(buff.toString()); + } + } + writeSchema(writer); + writer.println("DROP ALIAS READ_BLOB_MAP;"); + writer.println("DROP ALIAS READ_CLOB_MAP;"); + writer.println("DROP TABLE IF EXISTS INFORMATION_SCHEMA.LOB_BLOCKS;"); + } catch (Throwable e) { + writeError(writer, e); + } + } + + private static void dumpLayout(PrintWriter writer, MVStore mv) { + MVMap layout = mv.getLayoutMap(); + for (Entry e : layout.entrySet()) { + writer.println("-- " + e.getKey() + " = " + e.getValue()); + } + } + + private static void dumpMeta(PrintWriter writer, MVStore mv) { + MVMap meta = mv.getMetaMap(); + for (Entry e : meta.entrySet()) { + writer.println("-- " + e.getKey() + " = " + e.getValue()); + } + } + + private static void dumpTypes(PrintWriter writer, MVStore mv) { + MVMap.Builder> builder = new MVMap.Builder>() + .keyType(StringDataType.INSTANCE) + .valueType(new MetaType<>(null, null)); + MVMap> map = mv.openMap("_", builder); + for (Entry e : map.entrySet()) { + writer.println("-- " + e.getKey() + " = " + e.getValue()); + } + } + + private void dumpLobMaps(PrintWriter writer, MVStore mv) { + lobMaps = mv.hasMap("lobData"); + if (!lobMaps) { + return; + } + TransactionStore txStore = new TransactionStore(mv); + MVMap lobData = LobStorageMap.openLobDataMap(txStore); + StreamStore streamStore = new StreamStore(lobData); + MVMap lobMap = LobStorageMap.openLobMap(txStore); + writer.println("-- LOB"); + writer.println("CREATE TABLE IF NOT EXISTS " + + "INFORMATION_SCHEMA.LOB_BLOCKS(" + + "LOB_ID BIGINT, SEQ INT, DATA VARBINARY, " + + "PRIMARY KEY(LOB_ID, SEQ));"); + boolean hasErrors = false; + for (Entry e : lobMap.entrySet()) { + long lobId = e.getKey(); + LobStorageMap.BlobMeta value = e.getValue(); + byte[] streamStoreId = value.streamStoreId; + InputStream in = streamStore.get(streamStoreId); + int len = 8 * 1024; + byte[] block = new byte[len]; + try { + for (int seq = 0;; seq++) { + int l = IOUtils.readFully(in, block, block.length); + if (l > 0) { + writer.print("INSERT INTO INFORMATION_SCHEMA.LOB_BLOCKS " + + "VALUES(" + lobId + ", " + seq + ", X'"); + writer.print(StringUtils.convertBytesToHex(block, l)); + writer.println("');"); + } + if (l != len) { + break; + } + } + } catch (IOException ex) { + writeError(writer, ex); + hasErrors = true; + } + } + writer.println("-- lobMap.size: " + lobMap.sizeAsLong()); + writer.println("-- lobData.size: " + lobData.sizeAsLong()); + + if (hasErrors) { + writer.println("-- lobMap"); + for (Long k : lobMap.keyList()) { + LobStorageMap.BlobMeta value = lobMap.get(k); + byte[] streamStoreId = value.streamStoreId; + writer.println("-- " + k + " " + StreamStore.toString(streamStoreId)); + } + writer.println("-- lobData"); + for (Long k : lobData.keyList()) { + writer.println("-- " + k + " len " + lobData.get(k).length); + } + } + } + + private String setStorage(int storageId) { + this.storageId = storageId; + this.storageName = "O_" + Integer.toString(storageId).replace('-', 'M'); + return storageName; + } + + private void writeMetaRow(Row r) { + MetaRecord meta = new MetaRecord(r); + int objectType = meta.getObjectType(); + if (objectType == DbObject.INDEX && meta.getSQL().startsWith("CREATE PRIMARY KEY ")) { + return; + } + schema.add(meta); + if (objectType == DbObject.TABLE_OR_VIEW) { + tableMap.put(meta.getId(), extractTableOrViewName(meta.getSQL())); + } + } + + private void resetSchema() { + schema = new ArrayList<>(); + objectIdSet = new HashSet<>(); + tableMap = new HashMap<>(); + columnTypeMap = new HashMap<>(); + } + + private void writeSchemaSET(PrintWriter writer) { + writer.println("---- Schema SET ----"); + for (MetaRecord m : schema) { + if (m.getObjectType() == DbObject.SETTING) { + String sql = m.getSQL(); + writer.println(sql + ";"); + } + } + } + + private void writeSchema(PrintWriter writer) { + writer.println("---- Schema ----"); + Collections.sort(schema); + for (MetaRecord m : schema) { + if (m.getObjectType() != DbObject.SETTING + && !isSchemaObjectTypeDelayed(m)) { + // create, but not referential integrity constraints and so on + // because they could fail on duplicate keys + String sql = m.getSQL(); + writer.println(sql + ";"); + } + } + // first, copy the lob storage (if there is any) + // must occur before copying data, + // otherwise the lob storage may be overwritten + boolean deleteLobs = false; + for (Map.Entry entry : tableMap.entrySet()) { + Integer objectId = entry.getKey(); + String name = entry.getValue(); + if (objectIdSet.contains(objectId)) { + if (isLobTable(name)) { + setStorage(objectId); + writer.println("DELETE FROM " + name + ";"); + writer.println("INSERT INTO " + name + " SELECT * FROM " + storageName + ";"); + if (name.equals("INFORMATION_SCHEMA.LOBS") + || name.equalsIgnoreCase("\"INFORMATION_SCHEMA\".\"LOBS\"")) { + writer.println("UPDATE " + name + " SET `TABLE` = " + + LobStorageFrontend.TABLE_TEMP + ";"); + deleteLobs = true; + } + } + } + } + for (Map.Entry entry : tableMap.entrySet()) { + Integer objectId = entry.getKey(); + String name = entry.getValue(); + if (objectIdSet.contains(objectId)) { + setStorage(objectId); + if (isLobTable(name)) { + continue; + } + writer.println("INSERT INTO " + name + " SELECT * FROM " + storageName + ";"); + } + } + for (Integer objectId : objectIdSet) { + setStorage(objectId); + writer.println("DROP TABLE " + storageName + ";"); + } + if (deleteLobs) { + writer.println("DELETE FROM INFORMATION_SCHEMA.LOBS WHERE `TABLE` = " + + LobStorageFrontend.TABLE_TEMP + ";"); + } + ArrayList referentialConstraints = new ArrayList<>(); + for (MetaRecord m : schema) { + if (isSchemaObjectTypeDelayed(m)) { + String sql = m.getSQL(); + // TODO parse SQL properly + if (m.getObjectType() == DbObject.CONSTRAINT && sql.endsWith("NOCHECK") + && sql.contains(" FOREIGN KEY") && sql.contains("REFERENCES ")) { + referentialConstraints.add(sql); + } else { + writer.println(sql + ';'); + } + } + } + for (String sql : referentialConstraints) { + writer.println(sql + ';'); + } + } + + private static boolean isLobTable(String name) { + return name.startsWith("INFORMATION_SCHEMA.LOB") || name.startsWith("\"INFORMATION_SCHEMA\".\"LOB") + || name.startsWith("\"information_schema\".\"lob"); + } + + private static boolean isSchemaObjectTypeDelayed(MetaRecord m) { + switch (m.getObjectType()) { + case DbObject.INDEX: + case DbObject.CONSTRAINT: + case DbObject.TRIGGER: + return true; + } + return false; + } + + private void createTemporaryTable(PrintWriter writer) { + if (!objectIdSet.contains(storageId)) { + objectIdSet.add(storageId); + writer.write("CREATE TABLE "); + writer.write(storageName); + writer.write('('); + for (int i = 0; i < recordLength; i++) { + if (i > 0) { + writer.print(", "); + } + writer.write('C'); + writer.print(i); + writer.write(' '); + String columnType = columnTypeMap.get(storageName + "." + i); + writer.write(columnType == null ? "VARCHAR" : columnType); + } + writer.println(");"); + writer.flush(); + } + } + + private static String extractTableOrViewName(String sql) { + int indexTable = sql.indexOf(" TABLE "); + int indexView = sql.indexOf(" VIEW "); + if (indexTable > 0 && indexView > 0) { + if (indexTable < indexView) { + indexView = -1; + } else { + indexTable = -1; + } + } + if (indexView > 0) { + sql = sql.substring(indexView + " VIEW ".length()); + } else if (indexTable > 0) { + sql = sql.substring(indexTable + " TABLE ".length()); + } else { + return "UNKNOWN"; + } + if (sql.startsWith("IF NOT EXISTS ")) { + sql = sql.substring("IF NOT EXISTS ".length()); + } + boolean ignore = false; + // sql is modified in the loop + for (int i = 0; i < sql.length(); i++) { + char ch = sql.charAt(i); + if (ch == '\"') { + ignore = !ignore; + } else if (!ignore && (ch <= ' ' || ch == '(')) { + sql = sql.substring(0, i); + return sql; + } + } + return "UNKNOWN"; + } + + + private void writeError(PrintWriter writer, Throwable e) { + if (writer != null) { + writer.println("// error: " + e); + } + traceError("Error", e); + } + + /** + * INTERNAL + */ + @Override + public String getDatabasePath() { + return databaseName; + } + + /** + * INTERNAL + */ + @Override + public FileStore openFile(String name, String mode, boolean mustExist) { + return FileStore.open(this, name, "rw"); + } + + /** + * INTERNAL + */ + @Override + public void checkPowerOff() { + // nothing to do + } + + /** + * INTERNAL + */ + @Override + public void checkWritingAllowed() { + // nothing to do + } + + /** + * INTERNAL + */ + @Override + public int getMaxLengthInplaceLob() { + throw DbException.getInternalError(); + } + + /** + * INTERNAL + */ + @Override + public Object getLobSyncObject() { + return this; + } + + /** + * INTERNAL + */ + @Override + public SmallLRUCache getLobFileListCache() { + return null; + } + + /** + * INTERNAL + */ + @Override + public TempFileDeleter getTempFileDeleter() { + return TempFileDeleter.getInstance(); + } + + /** + * INTERNAL + */ + @Override + public LobStorageInterface getLobStorage() { + return null; + } + + /** + * INTERNAL + */ + @Override + public int readLob(long lobId, byte[] hmac, long offset, byte[] buff, int off, int length) { + throw DbException.getInternalError(); + } + + @Override + public CompareMode getCompareMode() { + return CompareMode.getInstance(null, 0); + } +} diff --git a/h2/src/main/org/h2/tools/Restore.java b/h2/src/main/org/h2/tools/Restore.java new file mode 100644 index 0000000..426abca --- /dev/null +++ b/h2/src/main/org/h2/tools/Restore.java @@ -0,0 +1,194 @@ +/* + * 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.tools; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.sql.SQLException; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import org.h2.engine.Constants; +import org.h2.message.DbException; +import org.h2.store.fs.FileUtils; +import org.h2.util.IOUtils; +import org.h2.util.Tool; + +/** + * Restores a H2 database by extracting the database files from a .zip file. + */ +public class Restore extends Tool { + + /** + * Options are case sensitive. Supported options + * + * + * + * + * + * + * + * + * + * + * + * + *
Supported options
[-help] or [-?]Print the list of options
[-file <filename>]The source file name (default: backup.zip)
[-dir <dir>]The target directory (default: .)
[-db <database>]The target database name (as stored if not set)
[-quiet]Do not print progress information
+ * + * @param args the command line arguments + * @throws SQLException on failure + */ + public static void main(String... args) throws SQLException { + new Restore().runTool(args); + } + + @Override + public void runTool(String... args) throws SQLException { + String zipFileName = "backup.zip"; + String dir = "."; + String db = null; + for (int i = 0; args != null && i < args.length; i++) { + String arg = args[i]; + if (arg.equals("-dir")) { + dir = args[++i]; + } else if (arg.equals("-file")) { + zipFileName = args[++i]; + } else if (arg.equals("-db")) { + db = args[++i]; + } else if (arg.equals("-quiet")) { + // ignore + } else if (arg.equals("-help") || arg.equals("-?")) { + showUsage(); + return; + } else { + showUsageAndThrowUnsupportedOption(arg); + } + } + execute(zipFileName, dir, db); + } + + private static String getOriginalDbName(String fileName, String db) + throws IOException { + + try (InputStream in = FileUtils.newInputStream(fileName)) { + ZipInputStream zipIn = new ZipInputStream(in); + String originalDbName = null; + boolean multiple = false; + while (true) { + ZipEntry entry = zipIn.getNextEntry(); + if (entry == null) { + break; + } + String entryName = entry.getName(); + zipIn.closeEntry(); + String name = getDatabaseNameFromFileName(entryName); + if (name != null) { + if (db.equals(name)) { + originalDbName = name; + // we found the correct database + break; + } else if (originalDbName == null) { + originalDbName = name; + // we found a database, but maybe another one + } else { + // we have found multiple databases, but not the correct + // one + multiple = true; + } + } + } + zipIn.close(); + if (multiple && !db.equals(originalDbName)) { + throw new IOException("Multiple databases found, but not " + db); + } + return originalDbName; + } + } + + /** + * Extract the name of the database from a given file name. + * Only files ending with .h2.db are considered, all others return null. + * + * @param fileName the file name (without directory) + * @return the database name or null + */ + private static String getDatabaseNameFromFileName(String fileName) { + if (fileName.endsWith(Constants.SUFFIX_MV_FILE)) { + return fileName.substring(0, + fileName.length() - Constants.SUFFIX_MV_FILE.length()); + } + return null; + } + + /** + * Restores database files. + * + * @param zipFileName the name of the backup file + * @param directory the directory name + * @param db the database name (null for all databases) + * @throws DbException if there is an IOException + */ + public static void execute(String zipFileName, String directory, String db) { + InputStream in = null; + try { + if (!FileUtils.exists(zipFileName)) { + throw new IOException("File not found: " + zipFileName); + } + String originalDbName = null; + int originalDbLen = 0; + if (db != null) { + originalDbName = getOriginalDbName(zipFileName, db); + if (originalDbName == null) { + throw new IOException("No database named " + db + " found"); + } + if (originalDbName.startsWith(File.separator)) { + originalDbName = originalDbName.substring(1); + } + originalDbLen = originalDbName.length(); + } + in = FileUtils.newInputStream(zipFileName); + try (ZipInputStream zipIn = new ZipInputStream(in)) { + while (true) { + ZipEntry entry = zipIn.getNextEntry(); + if (entry == null) { + break; + } + String fileName = entry.getName(); + // restoring windows backups on linux and vice versa + fileName = IOUtils.nameSeparatorsToNative(fileName); + if (fileName.startsWith(File.separator)) { + fileName = fileName.substring(1); + } + boolean copy = false; + if (db == null) { + copy = true; + } else if (fileName.startsWith(originalDbName + ".")) { + fileName = db + fileName.substring(originalDbLen); + copy = true; + } + if (copy) { + OutputStream o = null; + try { + o = FileUtils.newOutputStream(directory + File.separatorChar + fileName, false); + IOUtils.copy(zipIn, o); + o.close(); + } finally { + IOUtils.closeSilently(o); + } + } + zipIn.closeEntry(); + } + zipIn.closeEntry(); + } + } catch (IOException e) { + throw DbException.convertIOException(e, zipFileName); + } finally { + IOUtils.closeSilently(in); + } + } + +} diff --git a/h2/src/main/org/h2/tools/RunScript.java b/h2/src/main/org/h2/tools/RunScript.java new file mode 100644 index 0000000..fc0efba --- /dev/null +++ b/h2/src/main/org/h2/tools/RunScript.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.tools; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.Reader; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.TimeUnit; + +import org.h2.message.DbException; +import org.h2.store.fs.FileUtils; +import org.h2.util.IOUtils; +import org.h2.util.JdbcUtils; +import org.h2.util.ScriptReader; +import org.h2.util.StringUtils; +import org.h2.util.Tool; + +/** + * Runs a SQL script against a database. + */ +public class RunScript extends Tool { + + private boolean showResults; + private boolean checkResults; + + /** + * Options are case sensitive. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Supported options
[-help] or [-?]Print the list of options
[-url "<url>"]The database URL (jdbc:...)
[-user <user>]The user name (default: sa)
[-password <pwd>]The password
[-script <file>]The script file to run (default: backup.sql)
[-driver <class>]The JDBC driver class to use (not required in most cases)
[-showResults]Show the statements and the results of queries
[-checkResults]Check if the query results match the expected results
[-continueOnError]Continue even if the script contains errors
[-options ...]RUNSCRIPT options (embedded H2; -*Results not supported)
+ * + * @param args the command line arguments + * @throws SQLException on failure + */ + public static void main(String... args) throws SQLException { + new RunScript().runTool(args); + } + + /** + * Executes the contents of a SQL script file against a database. + * This tool is usually used to create a database from script. + * It can also be used to analyze performance problems by running + * the tool using Java profiler settings such as: + *
+     * java -Xrunhprof:cpu=samples,depth=16 ...
+     * 
+ * To include local files when using remote databases, use the special + * syntax: + *
+     * @INCLUDE fileName
+     * 
+ * This syntax is only supported by this tool. Embedded RUNSCRIPT SQL + * statements will be executed by the database. + * + * @param args the command line arguments + */ + @Override + public void runTool(String... args) throws SQLException { + String url = null; + String user = ""; + String password = ""; + String script = "backup.sql"; + String options = null; + boolean continueOnError = false; + boolean showTime = false; + for (int i = 0; args != null && i < args.length; i++) { + String arg = args[i]; + if (arg.equals("-url")) { + url = args[++i]; + } else if (arg.equals("-user")) { + user = args[++i]; + } else if (arg.equals("-password")) { + password = args[++i]; + } else if (arg.equals("-continueOnError")) { + continueOnError = true; + } else if (arg.equals("-checkResults")) { + checkResults = true; + } else if (arg.equals("-showResults")) { + showResults = true; + } else if (arg.equals("-script")) { + script = args[++i]; + } else if (arg.equals("-time")) { + showTime = true; + } else if (arg.equals("-driver")) { + String driver = args[++i]; + JdbcUtils.loadUserClass(driver); + } else if (arg.equals("-options")) { + StringBuilder buff = new StringBuilder(); + i++; + for (; i < args.length; i++) { + buff.append(' ').append(args[i]); + } + options = buff.toString(); + } else if (arg.equals("-help") || arg.equals("-?")) { + showUsage(); + return; + } else { + showUsageAndThrowUnsupportedOption(arg); + } + } + if (url == null) { + showUsage(); + throw new SQLException("URL not set"); + } + long time = System.nanoTime(); + if (options != null) { + processRunscript(url, user, password, script, options); + } else { + process(url, user, password, script, null, continueOnError); + } + if (showTime) { + time = System.nanoTime() - time; + out.println("Done in " + TimeUnit.NANOSECONDS.toMillis(time) + " ms"); + } + } + + /** + * Executes the SQL commands read from the reader against a database. + * + * @param conn the connection to a database + * @param reader the reader + * @return the last result set + * @throws SQLException on failure + */ + public static ResultSet execute(Connection conn, Reader reader) + throws SQLException { + // can not close the statement because we return a result set from it + Statement stat = conn.createStatement(); + ResultSet rs = null; + ScriptReader r = new ScriptReader(reader); + while (true) { + String sql = r.readStatement(); + if (sql == null) { + break; + } + if (StringUtils.isWhitespaceOrEmpty(sql)) { + continue; + } + boolean resultSet = stat.execute(sql); + if (resultSet) { + if (rs != null) { + rs.close(); + rs = null; + } + rs = stat.getResultSet(); + } + } + return rs; + } + + private void process(Connection conn, String fileName, + boolean continueOnError, Charset charset) throws SQLException, + IOException { + BufferedReader reader = FileUtils.newBufferedReader(fileName, charset); + try { + process(conn, continueOnError, FileUtils.getParent(fileName), reader, charset); + } finally { + IOUtils.closeSilently(reader); + } + } + + private void process(Connection conn, boolean continueOnError, String path, + Reader reader, Charset charset) throws SQLException, IOException { + Statement stat = conn.createStatement(); + ScriptReader r = new ScriptReader(reader); + while (true) { + String sql = r.readStatement(); + if (sql == null) { + break; + } + String trim = sql.trim(); + if (trim.isEmpty()) { + continue; + } + if (trim.startsWith("@") && StringUtils.toUpperEnglish(trim). + startsWith("@INCLUDE")) { + sql = StringUtils.trimSubstring(sql, "@INCLUDE".length()); + if (!FileUtils.isAbsolute(sql)) { + sql = path + File.separatorChar + sql; + } + process(conn, sql, continueOnError, charset); + } else { + try { + if (showResults && !trim.startsWith("-->")) { + out.print(sql + ";"); + } + if (showResults || checkResults) { + boolean query = stat.execute(sql); + if (query) { + ResultSet rs = stat.getResultSet(); + int columns = rs.getMetaData().getColumnCount(); + StringBuilder buff = new StringBuilder(); + while (rs.next()) { + buff.append("\n-->"); + for (int i = 0; i < columns; i++) { + String s = rs.getString(i + 1); + if (s != null) { + s = StringUtils.replaceAll(s, "\r\n", "\n"); + s = StringUtils.replaceAll(s, "\n", "\n--> "); + s = StringUtils.replaceAll(s, "\r", "\r--> "); + } + buff.append(' ').append(s); + } + } + buff.append("\n;"); + String result = buff.toString(); + if (showResults) { + out.print(result); + } + if (checkResults) { + String expected = r.readStatement() + ";"; + expected = StringUtils.replaceAll(expected, "\r\n", "\n"); + expected = StringUtils.replaceAll(expected, "\r", "\n"); + if (!expected.equals(result)) { + expected = StringUtils.replaceAll(expected, " ", "+"); + result = StringUtils.replaceAll(result, " ", "+"); + throw new SQLException( + "Unexpected output for:\n" + sql.trim() + + "\nGot:\n" + result + "\nExpected:\n" + expected); + } + } + + } + } else { + stat.execute(sql); + } + } catch (Exception e) { + if (continueOnError) { + e.printStackTrace(out); + } else { + throw DbException.toSQLException(e); + } + } + } + } + } + + private static void processRunscript(String url, String user, String password, String fileName, String options) + throws SQLException { + try (Connection conn = JdbcUtils.getConnection(null, url, user, password); + Statement stat = conn.createStatement()) { + String sql = "RUNSCRIPT FROM '" + fileName + "' " + options; + stat.execute(sql); + } + } + + /** + * Executes the SQL commands in a script file against a database. + * + * @param url the database URL + * @param user the user name + * @param password the password + * @param fileName the script file + * @param charset the character set or null for UTF-8 + * @param continueOnError if execution should be continued if an error + * occurs + * @throws SQLException on failure + */ + public static void execute(String url, String user, String password, + String fileName, Charset charset, boolean continueOnError) + throws SQLException { + new RunScript().process(url, user, password, fileName, charset, + continueOnError); + } + + /** + * Executes the SQL commands in a script file against a database. + * + * @param url the database URL + * @param user the user name + * @param password the password + * @param fileName the script file + * @param charset the character set or null for UTF-8 + * @param continueOnError if execution should be continued if an error + * occurs + */ + void process(String url, String user, String password, String fileName, Charset charset, boolean continueOnError) + throws SQLException { + if (charset == null) { + charset = StandardCharsets.UTF_8; + } + try (Connection conn = JdbcUtils.getConnection(null, url, user, password)) { + process(conn, fileName, continueOnError, charset); + } catch (IOException e) { + throw DbException.convertIOException(e, fileName); + } + } + +} diff --git a/h2/src/main/org/h2/tools/Script.java b/h2/src/main/org/h2/tools/Script.java new file mode 100644 index 0000000..8a2f5da --- /dev/null +++ b/h2/src/main/org/h2/tools/Script.java @@ -0,0 +1,139 @@ +/* + * 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.tools; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import org.h2.util.JdbcUtils; +import org.h2.util.StringUtils; +import org.h2.util.Tool; + +/** + * Creates a SQL script file by extracting the schema and data of a database. + */ +public class Script extends Tool { + + /** + * Options are case sensitive. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Supported options
[-help] or [-?]Print the list of options
[-url "<url>"]The database URL (jdbc:...)
[-user <user>]The user name (default: sa)
[-password <pwd>]The password
[-script <file>]The target script file name (default: backup.sql)
[-options ...]A list of options (only for embedded H2, see SCRIPT)
[-quiet]Do not print progress information
+ * + * @param args the command line arguments + * @throws SQLException on failure + */ + public static void main(String... args) throws SQLException { + new Script().runTool(args); + } + + @Override + public void runTool(String... args) throws SQLException { + String url = null; + String user = ""; + String password = ""; + String file = "backup.sql"; + String options1 = ""; + String options2 = ""; + for (int i = 0; args != null && i < args.length; i++) { + String arg = args[i]; + if (arg.equals("-url")) { + url = args[++i]; + } else if (arg.equals("-user")) { + user = args[++i]; + } else if (arg.equals("-password")) { + password = args[++i]; + } else if (arg.equals("-script")) { + file = args[++i]; + } else if (arg.equals("-options")) { + StringBuilder buff1 = new StringBuilder(); + StringBuilder buff2 = new StringBuilder(); + i++; + for (; i < args.length; i++) { + String a = args[i]; + String upper = StringUtils.toUpperEnglish(a); + if ("SIMPLE".equals(upper) || upper.startsWith("NO") || "DROP".equals(upper)) { + buff1.append(' '); + buff1.append(args[i]); + } else if ("BLOCKSIZE".equals(upper)) { + buff1.append(' '); + buff1.append(args[i]); + i++; + buff1.append(' '); + buff1.append(args[i]); + } else { + buff2.append(' '); + buff2.append(args[i]); + } + } + options1 = buff1.toString(); + options2 = buff2.toString(); + } else if (arg.equals("-help") || arg.equals("-?")) { + showUsage(); + return; + } else { + showUsageAndThrowUnsupportedOption(arg); + } + } + if (url == null) { + showUsage(); + throw new SQLException("URL not set"); + } + process(url, user, password, file, options1, options2); + } + + /** + * Backs up a database to a stream. + * + * @param url the database URL + * @param user the user name + * @param password the password + * @param fileName the target file name + * @param options1 the options before the file name (may be an empty string) + * @param options2 the options after the file name (may be an empty string) + * @throws SQLException on failure + */ + public static void process(String url, String user, String password, String fileName, String options1, + String options2) throws SQLException { + try (Connection conn = JdbcUtils.getConnection(null, url, user, password)) { + process(conn, fileName, options1, options2); + } + } + + /** + * Backs up a database to a stream. The stream is not closed. + * The connection is not closed. + * + * @param conn the connection + * @param fileName the target file name + * @param options1 the options before the file name + * @param options2 the options after the file name + * @throws SQLException on failure + */ + public static void process(Connection conn, + String fileName, String options1, String options2) throws SQLException { + + try (Statement stat = conn.createStatement()) { + String sql = "SCRIPT " + options1 + " TO '" + fileName + "' " + options2; + stat.execute(sql); + } + } + +} diff --git a/h2/src/main/org/h2/tools/Server.java b/h2/src/main/org/h2/tools/Server.java new file mode 100644 index 0000000..c195601 --- /dev/null +++ b/h2/src/main/org/h2/tools/Server.java @@ -0,0 +1,792 @@ +/* + * 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.tools; + +import java.net.URI; +import java.sql.Connection; +import java.sql.SQLException; + +import org.h2.api.ErrorCode; +import org.h2.engine.SysProperties; +import org.h2.message.DbException; +import org.h2.server.Service; +import org.h2.server.ShutdownHandler; +import org.h2.server.TcpServer; +import org.h2.server.pg.PgServer; +import org.h2.server.web.WebServer; +import org.h2.util.StringUtils; +import org.h2.util.Tool; +import org.h2.util.Utils; + +/** + * Starts the H2 Console (web-) server, TCP, and PG server. + */ +public class Server extends Tool implements Runnable, ShutdownHandler { + + private final Service service; + private Server web, tcp, pg; + private ShutdownHandler shutdownHandler; + private boolean started; + + public Server() { + // nothing to do + this.service = null; + } + + /** + * Create a new server for the given service. + * + * @param service the service + * @param args the command line arguments + * @throws SQLException on failure + */ + public Server(Service service, String... args) throws SQLException { + verifyArgs(args); + this.service = service; + try { + service.init(args); + } catch (Exception e) { + throw DbException.toSQLException(e); + } + } + + /** + * When running without options, -tcp, -web, -browser and -pg are started. + * + * Options are case sensitive. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Supported options
[-help] or [-?]Print the list of options
[-web]Start the web server with the H2 Console
[-webAllowOthers]Allow other computers to connect - see below
[-webExternalNames <names>]The comma-separated list of external names and IP addresses of this server, + * used together with -webAllowOthers
[-webDaemon]Use a daemon thread
[-webPort <port>]The port (default: 8082)
[-webSSL]Use encrypted (HTTPS) connections
[-webAdminPassword]Password of DB Console administrator
[-browser]Start a browser connecting to the web server
[-tcp]Start the TCP server
[-tcpAllowOthers]Allow other computers to connect - see below
[-tcpDaemon]Use a daemon thread
[-tcpPort <port>]The port (default: 9092)
[-tcpSSL]Use encrypted (SSL) connections
[-tcpPassword <pwd>]The password for shutting down a TCP server
[-tcpShutdown "<url>"]Stop the TCP server; example: tcp://localhost
[-tcpShutdownForce]Do not wait until all connections are closed
[-pg]Start the PG server
[-pgAllowOthers]Allow other computers to connect - see below
[-pgDaemon]Use a daemon thread
[-pgPort <port>]The port (default: 5435)
[-properties "<dir>"]Server properties (default: ~, disable: null)
[-baseDir <dir>]The base directory for H2 databases (all servers)
[-ifExists]Only existing databases may be opened (all servers)
[-ifNotExists]Databases are created when accessed
[-trace]Print additional trace information (all servers)
[-key <from> <to>]Allows to map a database name to another (all servers)
+ * The options -xAllowOthers are potentially risky. + * + * For details, see Advanced Topics / Protection against Remote Access. + * + * @param args the command line arguments + * @throws SQLException on failure + */ + public static void main(String... args) throws SQLException { + new Server().runTool(args); + } + + private void verifyArgs(String... args) throws SQLException { + for (int i = 0; args != null && i < args.length; i++) { + String arg = args[i]; + if (arg == null) { + } else if ("-?".equals(arg) || "-help".equals(arg)) { + // ok + } else if (arg.startsWith("-web")) { + if ("-web".equals(arg)) { + // ok + } else if ("-webAllowOthers".equals(arg)) { + // no parameters + } else if ("-webExternalNames".equals(arg)) { + i++; + } else if ("-webDaemon".equals(arg)) { + // no parameters + } else if ("-webSSL".equals(arg)) { + // no parameters + } else if ("-webPort".equals(arg)) { + i++; + } else if ("-webAdminPassword".equals(arg)) { + i++; + } else { + throwUnsupportedOption(arg); + } + } else if ("-browser".equals(arg)) { + // ok + } else if (arg.startsWith("-tcp")) { + if ("-tcp".equals(arg)) { + // ok + } else if ("-tcpAllowOthers".equals(arg)) { + // no parameters + } else if ("-tcpDaemon".equals(arg)) { + // no parameters + } else if ("-tcpSSL".equals(arg)) { + // no parameters + } else if ("-tcpPort".equals(arg)) { + i++; + } else if ("-tcpPassword".equals(arg)) { + i++; + } else if ("-tcpShutdown".equals(arg)) { + i++; + } else if ("-tcpShutdownForce".equals(arg)) { + // ok + } else { + throwUnsupportedOption(arg); + } + } else if (arg.startsWith("-pg")) { + if ("-pg".equals(arg)) { + // ok + } else if ("-pgAllowOthers".equals(arg)) { + // no parameters + } else if ("-pgDaemon".equals(arg)) { + // no parameters + } else if ("-pgPort".equals(arg)) { + i++; + } else { + throwUnsupportedOption(arg); + } + } else if (arg.startsWith("-ftp")) { + if ("-ftpPort".equals(arg)) { + i++; + } else if ("-ftpDir".equals(arg)) { + i++; + } else if ("-ftpRead".equals(arg)) { + i++; + } else if ("-ftpWrite".equals(arg)) { + i++; + } else if ("-ftpWritePassword".equals(arg)) { + i++; + } else if ("-ftpTask".equals(arg)) { + // no parameters + } else { + throwUnsupportedOption(arg); + } + } else if ("-properties".equals(arg)) { + i++; + } else if ("-trace".equals(arg)) { + // no parameters + } else if ("-ifExists".equals(arg)) { + // no parameters + } else if ("-ifNotExists".equals(arg)) { + // no parameters + } else if ("-baseDir".equals(arg)) { + i++; + } else if ("-key".equals(arg)) { + i += 2; + } else if ("-tool".equals(arg)) { + // no parameters + } else { + throwUnsupportedOption(arg); + } + } + } + + @Override + public void runTool(String... args) throws SQLException { + boolean tcpStart = false, pgStart = false, webStart = false; + boolean browserStart = false; + boolean tcpShutdown = false, tcpShutdownForce = false; + String tcpPassword = ""; + String tcpShutdownServer = ""; + boolean startDefaultServers = true; + for (int i = 0; args != null && i < args.length; i++) { + String arg = args[i]; + if (arg == null) { + } else if ("-?".equals(arg) || "-help".equals(arg)) { + showUsage(); + return; + } else if (arg.startsWith("-web")) { + if ("-web".equals(arg)) { + startDefaultServers = false; + webStart = true; + } else if ("-webAllowOthers".equals(arg)) { + // no parameters + } else if ("-webDaemon".equals(arg)) { + // no parameters + } else if ("-webSSL".equals(arg)) { + // no parameters + } else if ("-webPort".equals(arg)) { + i++; + } else if ("-webAdminPassword".equals(arg)) { + i++; + } else { + showUsageAndThrowUnsupportedOption(arg); + } + } else if ("-browser".equals(arg)) { + startDefaultServers = false; + browserStart = true; + } else if (arg.startsWith("-tcp")) { + if ("-tcp".equals(arg)) { + startDefaultServers = false; + tcpStart = true; + } else if ("-tcpAllowOthers".equals(arg)) { + // no parameters + } else if ("-tcpDaemon".equals(arg)) { + // no parameters + } else if ("-tcpSSL".equals(arg)) { + // no parameters + } else if ("-tcpPort".equals(arg)) { + i++; + } else if ("-tcpPassword".equals(arg)) { + tcpPassword = args[++i]; + } else if ("-tcpShutdown".equals(arg)) { + startDefaultServers = false; + tcpShutdown = true; + tcpShutdownServer = args[++i]; + } else if ("-tcpShutdownForce".equals(arg)) { + tcpShutdownForce = true; + } else { + showUsageAndThrowUnsupportedOption(arg); + } + } else if (arg.startsWith("-pg")) { + if ("-pg".equals(arg)) { + startDefaultServers = false; + pgStart = true; + } else if ("-pgAllowOthers".equals(arg)) { + // no parameters + } else if ("-pgDaemon".equals(arg)) { + // no parameters + } else if ("-pgPort".equals(arg)) { + i++; + } else { + showUsageAndThrowUnsupportedOption(arg); + } + } else if ("-properties".equals(arg)) { + i++; + } else if ("-trace".equals(arg)) { + // no parameters + } else if ("-ifExists".equals(arg)) { + // no parameters + } else if ("-ifNotExists".equals(arg)) { + // no parameters + } else if ("-baseDir".equals(arg)) { + i++; + } else if ("-key".equals(arg)) { + i += 2; + } else { + showUsageAndThrowUnsupportedOption(arg); + } + } + verifyArgs(args); + if (startDefaultServers) { + tcpStart = true; + pgStart = true; + webStart = true; + browserStart = true; + } + // TODO server: maybe use one single properties file? + if (tcpShutdown) { + out.println("Shutting down TCP Server at " + tcpShutdownServer); + shutdownTcpServer(tcpShutdownServer, tcpPassword, + tcpShutdownForce, false); + } + try { + if (tcpStart) { + tcp = createTcpServer(args); + tcp.start(); + out.println(tcp.getStatus()); + tcp.setShutdownHandler(this); + } + if (pgStart) { + pg = createPgServer(args); + pg.start(); + out.println(pg.getStatus()); + } + if (webStart) { + web = createWebServer(args); + web.setShutdownHandler(this); + SQLException result = null; + try { + web.start(); + } catch (Exception e) { + result = DbException.toSQLException(e); + } + out.println(web.getStatus()); + // start browser in any case (even if the server is already + // running) because some people don't look at the output, but + // are wondering why nothing happens + if (browserStart) { + try { + openBrowser(web.getURL()); + } catch (Exception e) { + out.println(e.getMessage()); + } + } + if (result != null) { + throw result; + } + } else if (browserStart) { + out.println("The browser can only start if a web server is started (-web)"); + } + } catch (SQLException e) { + stopAll(); + throw e; + } + } + + /** + * Shutdown one or all TCP server. If force is set to false, the server will + * not allow new connections, but not kill existing connections, instead it + * will stop if the last connection is closed. If force is set to true, + * existing connections are killed. After calling the method with + * force=false, it is not possible to call it again with force=true because + * new connections are not allowed. Example: + * + *
+     * Server.shutdownTcpServer("tcp://localhost:9094",
+     *         password, true, false);
+     * 
+ * + * @param url example: tcp://localhost:9094 + * @param password the password to use ("" for no password) + * @param force the shutdown (don't wait) + * @param all whether all TCP servers that are running in the JVM should be + * stopped + * @throws SQLException on failure + */ + public static void shutdownTcpServer(String url, String password, + boolean force, boolean all) throws SQLException { + TcpServer.shutdown(url, password, force, all); + } + + /** + * Get the status of this server. + * + * @return the status + */ + public String getStatus() { + StringBuilder buff = new StringBuilder(); + if (!started) { + buff.append("Not started"); + } else if (isRunning(false)) { + buff.append(service.getType()). + append(" server running at "). + append(service.getURL()). + append(" ("); + if (service.getAllowOthers()) { + buff.append("others can connect"); + } else { + buff.append("only local connections"); + } + buff.append(')'); + } else { + buff.append("The "). + append(service.getType()). + append(" server could not be started. " + + "Possible cause: another server is already running at "). + append(service.getURL()); + } + return buff.toString(); + } + + /** + * Create a new web server, but does not start it yet. Example: + * + *
+     * Server server = Server.createWebServer("-trace").start();
+     * 
+ * Supported options are: + * -webPort, -webSSL, -webAllowOthers, -webDaemon, + * -trace, -ifExists, -ifNotExists, -baseDir, -properties. + * See the main method for details. + * + * @param args the argument list + * @return the server + * @throws SQLException on failure + */ + public static Server createWebServer(String... args) throws SQLException { + return createWebServer(args, null, false); + } + + /** + * Create a new web server, but does not start it yet. + * + * @param args + * the argument list + * @param key + * key, or null + * @param allowSecureCreation + * whether creation of databases using the key should be allowed + * @return the server + */ + static Server createWebServer(String[] args, String key, boolean allowSecureCreation) throws SQLException { + WebServer service = new WebServer(); + service.setKey(key); + service.setAllowSecureCreation(allowSecureCreation); + Server server = new Server(service, args); + service.setShutdownHandler(server); + return server; + } + + /** + * Create a new TCP server, but does not start it yet. Example: + * + *
+     * Server server = Server.createTcpServer(
+     *     "-tcpPort", "9123", "-tcpAllowOthers").start();
+     * 
+ * Supported options are: + * -tcpPort, -tcpSSL, -tcpPassword, -tcpAllowOthers, -tcpDaemon, + * -trace, -ifExists, -ifNotExists, -baseDir, -key. + * See the main method for details. + *

+ * If no port is specified, the default port is used if possible, + * and if this port is already used, a random port is used. + * Use getPort() or getURL() after starting to retrieve the port. + *

+ * + * @param args the argument list + * @return the server + * @throws SQLException on failure + */ + public static Server createTcpServer(String... args) throws SQLException { + TcpServer service = new TcpServer(); + Server server = new Server(service, args); + service.setShutdownHandler(server); + return server; + } + + /** + * Create a new PG server, but does not start it yet. + * Example: + *
+     * Server server =
+     *     Server.createPgServer("-pgAllowOthers").start();
+     * 
+ * Supported options are: + * -pgPort, -pgAllowOthers, -pgDaemon, + * -trace, -ifExists, -ifNotExists, -baseDir, -key. + * See the main method for details. + *

+ * If no port is specified, the default port is used if possible, + * and if this port is already used, a random port is used. + * Use getPort() or getURL() after starting to retrieve the port. + *

+ * + * @param args the argument list + * @return the server + * @throws SQLException on failure + */ + public static Server createPgServer(String... args) throws SQLException { + return new Server(new PgServer(), args); + } + + /** + * Tries to start the server. + * @return the server if successful + * @throws SQLException if the server could not be started + */ + public Server start() throws SQLException { + try { + started = true; + service.start(); + String url = service.getURL(); + int idx = url.indexOf('?'); + if (idx >= 0) { + url = url.substring(0, idx); + } + String name = service.getName() + " (" + url + ')'; + Thread t = new Thread(this, name); + t.setDaemon(service.isDaemon()); + t.start(); + for (int i = 1; i < 64; i += i) { + wait(i); + if (isRunning(false)) { + return this; + } + } + if (isRunning(true)) { + return this; + } + throw DbException.get(ErrorCode.EXCEPTION_OPENING_PORT_2, + name, "timeout; " + + "please check your network configuration, specially the file /etc/hosts"); + } catch (DbException e) { + throw DbException.toSQLException(e); + } + } + + private static void wait(int i) { + try { + // sleep at most 4096 ms + long sleep = (long) i * (long) i; + Thread.sleep(sleep); + } catch (InterruptedException e) { + // ignore + } + } + + private void stopAll() { + Server s = web; + if (s != null && s.isRunning(false)) { + s.stop(); + web = null; + } + s = tcp; + if (s != null && s.isRunning(false)) { + s.stop(); + tcp = null; + } + s = pg; + if (s != null && s.isRunning(false)) { + s.stop(); + pg = null; + } + } + + /** + * Checks if the server is running. + * + * @param traceError if errors should be written + * @return if the server is running + */ + public boolean isRunning(boolean traceError) { + return service.isRunning(traceError); + } + + /** + * Stops the server. + */ + public void stop() { + started = false; + if (service != null) { + service.stop(); + } + } + + /** + * Gets the URL of this server. + * + * @return the url + */ + public String getURL() { + return service.getURL(); + } + + /** + * Gets the port this server is listening on. + * + * @return the port + */ + public int getPort() { + return service.getPort(); + } + + /** + * INTERNAL + */ + @Override + public void run() { + try { + service.listen(); + } catch (Exception e) { + DbException.traceThrowable(e); + } + } + + /** + * INTERNAL + * @param shutdownHandler to set + */ + public void setShutdownHandler(ShutdownHandler shutdownHandler) { + this.shutdownHandler = shutdownHandler; + } + + /** + * INTERNAL + */ + @Override + public void shutdown() { + if (shutdownHandler != null) { + shutdownHandler.shutdown(); + } else { + stopAll(); + } + } + + /** + * Get the service attached to this server. + * + * @return the service + */ + public Service getService() { + return service; + } + + /** + * Open a new browser tab or window with the given URL. + * + * @param url the URL to open + * @throws Exception on failure + */ + public static void openBrowser(String url) throws Exception { + try { + String osName = StringUtils.toLowerEnglish( + Utils.getProperty("os.name", "linux")); + Runtime rt = Runtime.getRuntime(); + String browser = Utils.getProperty(SysProperties.H2_BROWSER, null); + if (browser == null) { + // under Linux, this will point to the default system browser + try { + browser = System.getenv("BROWSER"); + } catch (SecurityException se) { + // ignore + } + } + if (browser != null) { + if (browser.startsWith("call:")) { + browser = browser.substring("call:".length()); + Utils.callStaticMethod(browser, url); + } else if (browser.contains("%url")) { + String[] args = StringUtils.arraySplit(browser, ',', false); + for (int i = 0; i < args.length; i++) { + args[i] = StringUtils.replaceAll(args[i], "%url", url); + } + rt.exec(args); + } else if (osName.contains("windows")) { + rt.exec(new String[] { "cmd.exe", "/C", browser, url }); + } else { + rt.exec(new String[] { browser, url }); + } + return; + } + try { + Class desktopClass = Class.forName("java.awt.Desktop"); + // Desktop.isDesktopSupported() + Boolean supported = (Boolean) desktopClass. + getMethod("isDesktopSupported"). + invoke(null, new Object[0]); + URI uri = new URI(url); + if (supported) { + // Desktop.getDesktop(); + Object desktop = desktopClass.getMethod("getDesktop"). + invoke(null); + // desktop.browse(uri); + desktopClass.getMethod("browse", URI.class). + invoke(desktop, uri); + return; + } + } catch (Exception e) { + // ignore + } + if (osName.contains("windows")) { + rt.exec(new String[] { "rundll32", "url.dll,FileProtocolHandler", url }); + } else if (osName.contains("mac") || osName.contains("darwin")) { + // Mac OS: to open a page with Safari, use "open -a Safari" + Runtime.getRuntime().exec(new String[] { "open", url }); + } else { + String[] browsers = { "xdg-open", "chromium", "google-chrome", + "firefox", "mozilla-firefox", "mozilla", "konqueror", + "netscape", "opera", "midori" }; + boolean ok = false; + for (String b : browsers) { + try { + rt.exec(new String[] { b, url }); + ok = true; + break; + } catch (Exception e) { + // ignore and try the next + } + } + if (!ok) { + // No success in detection. + throw new Exception( + "Browser detection failed, and java property 'h2.browser' " + + "and environment variable BROWSER are not set to a browser executable."); + } + } + } catch (Exception e) { + throw new Exception( + "Failed to start a browser to open the URL " + + url + ": " + e.getMessage()); + } + } + + /** + * Start a web server and a browser that uses the given connection. The + * current transaction is preserved. This is specially useful to manually + * inspect the database when debugging. This method return as soon as the + * user has disconnected. + * + * @param conn the database connection (the database must be open) + * @throws SQLException on failure + */ + public static void startWebServer(Connection conn) throws SQLException { + startWebServer(conn, false); + } + + /** + * Start a web server and a browser that uses the given connection. The + * current transaction is preserved. This is specially useful to manually + * inspect the database when debugging. This method return as soon as the + * user has disconnected. + * + * @param conn the database connection (the database must be open) + * @param ignoreProperties if {@code true} properties from + * {@code .h2.server.properties} will be ignored + * @throws SQLException on failure + */ + public static void startWebServer(Connection conn, boolean ignoreProperties) throws SQLException { + WebServer webServer = new WebServer(); + String[] args; + if (ignoreProperties) { + args = new String[] { "-webPort", "0", "-properties", "null"}; + } else { + args = new String[] { "-webPort", "0" }; + } + Server web = new Server(webServer, args); + web.start(); + Server server = new Server(); + server.web = web; + webServer.setShutdownHandler(server); + String url = webServer.addSession(conn); + try { + Server.openBrowser(url); + while (!webServer.isStopped()) { + Thread.sleep(1000); + } + } catch (Exception e) { + // ignore + } + } + +} diff --git a/h2/src/main/org/h2/tools/Shell.java b/h2/src/main/org/h2/tools/Shell.java new file mode 100644 index 0000000..a85b84e --- /dev/null +++ b/h2/src/main/org/h2/tools/Shell.java @@ -0,0 +1,633 @@ +/* + * 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.tools; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.StringReader; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Properties; +import java.util.concurrent.TimeUnit; + +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.server.web.ConnectionInfo; +import org.h2.util.JdbcUtils; +import org.h2.util.ScriptReader; +import org.h2.util.SortedProperties; +import org.h2.util.StringUtils; +import org.h2.util.Tool; +import org.h2.util.Utils; + +/** + * Interactive command line tool to access a database using JDBC. + */ +public class Shell extends Tool implements Runnable { + + private static final int MAX_ROW_BUFFER = 5000; + private static final int HISTORY_COUNT = 20; + // Windows: '\u00b3'; + private static final char BOX_VERTICAL = '|'; + + private PrintStream err = System.err; + private InputStream in = System.in; + private BufferedReader reader; + private Connection conn; + private Statement stat; + private boolean listMode; + private int maxColumnSize = 100; + private final ArrayList history = new ArrayList<>(); + private boolean stopHide; + private String serverPropertiesDir = Constants.SERVER_PROPERTIES_DIR; + + /** + * Options are case sensitive. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Supported options
[-help] or [-?]Print the list of options
[-url "<url>"]The database URL (jdbc:h2:...)
[-user <user>]The user name
[-password <pwd>]The password
[-driver <class>]The JDBC driver class to use (not required in most cases)
[-sql "<statements>"]Execute the SQL statements and exit
[-properties "<dir>"]Load the server properties from this directory
+ * If special characters don't work as expected, you may need to use + * -Dfile.encoding=UTF-8 (Mac OS X) or CP850 (Windows). + * + * @param args the command line arguments + * @throws SQLException on failure + */ + public static void main(String... args) throws SQLException { + new Shell().runTool(args); + } + + /** + * Sets the standard error stream. + * + * @param err the new standard error stream + */ + public void setErr(PrintStream err) { + this.err = err; + } + + /** + * Redirects the standard input. By default, System.in is used. + * + * @param in the input stream to use + */ + public void setIn(InputStream in) { + this.in = in; + } + + /** + * Redirects the standard input. By default, System.in is used. + * + * @param reader the input stream reader to use + */ + public void setInReader(BufferedReader reader) { + this.reader = reader; + } + + /** + * Run the shell tool with the given command line settings. + * + * @param args the command line settings + */ + @Override + public void runTool(String... args) throws SQLException { + String driver = null; + String url = null; + String user = ""; + String password = ""; + String sql = null; + for (int i = 0; args != null && i < args.length; i++) { + String arg = args[i]; + if (arg.equals("-url")) { + url = args[++i]; + } else if (arg.equals("-user")) { + user = args[++i]; + } else if (arg.equals("-password")) { + password = args[++i]; + } else if (arg.equals("-driver")) { + driver = args[++i]; + } else if (arg.equals("-sql")) { + sql = args[++i]; + } else if (arg.equals("-properties")) { + serverPropertiesDir = args[++i]; + } else if (arg.equals("-help") || arg.equals("-?")) { + showUsage(); + return; + } else if (arg.equals("-list")) { + listMode = true; + } else { + showUsageAndThrowUnsupportedOption(arg); + } + } + if (url != null) { + conn = JdbcUtils.getConnection(driver, url, user, password); + stat = conn.createStatement(); + } + if (sql == null) { + promptLoop(); + } else { + ScriptReader r = new ScriptReader(new StringReader(sql)); + while (true) { + String s = r.readStatement(); + if (s == null) { + break; + } + execute(s); + } + if (conn != null) { + conn.close(); + } + } + } + + /** + * Run the shell tool with the given connection and command line settings. + * The connection will be closed when the shell exits. + * This is primary used to integrate the Shell into another application. + *

+ * Note: using the "-url" option in {@code args} doesn't make much sense + * since it will override the {@code conn} parameter. + *

+ * + * @param conn the connection + * @param args the command line settings + * @throws SQLException on failure + */ + public void runTool(Connection conn, String... args) throws SQLException { + this.conn = conn; + this.stat = conn.createStatement(); + runTool(args); + } + + private void showHelp() { + println("Commands are case insensitive; SQL statements end with ';'"); + println("help or ? Display this help"); + println("list Toggle result list / stack trace mode"); + println("maxwidth Set maximum column width (default is 100)"); + println("autocommit Enable or disable autocommit"); + println("history Show the last 20 statements"); + println("quit or exit Close the connection and exit"); + println(""); + } + + private void promptLoop() { + println(""); + println("Welcome to H2 Shell " + Constants.FULL_VERSION); + println("Exit with Ctrl+C"); + if (conn != null) { + showHelp(); + } + String statement = null; + if (reader == null) { + reader = new BufferedReader(new InputStreamReader(in)); + } + while (true) { + try { + if (conn == null) { + connect(); + showHelp(); + } + if (statement == null) { + print("sql> "); + } else { + print("...> "); + } + String line = readLine(); + if (line == null) { + break; + } + String trimmed = line.trim(); + if (trimmed.isEmpty()) { + continue; + } + boolean end = trimmed.endsWith(";"); + if (end) { + line = line.substring(0, line.lastIndexOf(';')); + trimmed = trimmed.substring(0, trimmed.length() - 1); + } + String lower = StringUtils.toLowerEnglish(trimmed); + if ("exit".equals(lower) || "quit".equals(lower)) { + break; + } else if ("help".equals(lower) || "?".equals(lower)) { + showHelp(); + } else if ("list".equals(lower)) { + listMode = !listMode; + println("Result list mode is now " + (listMode ? "on" : "off")); + } else if ("history".equals(lower)) { + for (int i = 0, size = history.size(); i < size; i++) { + String s = history.get(i); + s = s.replace('\n', ' ').replace('\r', ' '); + println("#" + (1 + i) + ": " + s); + } + if (!history.isEmpty()) { + println("To re-run a statement, type the number and press and enter"); + } else { + println("No history"); + } + } else if (lower.startsWith("autocommit")) { + lower = StringUtils.trimSubstring(lower, "autocommit".length()); + if ("true".equals(lower)) { + conn.setAutoCommit(true); + } else if ("false".equals(lower)) { + conn.setAutoCommit(false); + } else { + println("Usage: autocommit [true|false]"); + } + println("Autocommit is now " + conn.getAutoCommit()); + } else if (lower.startsWith("maxwidth")) { + lower = StringUtils.trimSubstring(lower, "maxwidth".length()); + try { + maxColumnSize = Integer.parseInt(lower); + } catch (NumberFormatException e) { + println("Usage: maxwidth "); + } + println("Maximum column width is now " + maxColumnSize); + } else { + boolean addToHistory = true; + if (statement == null) { + if (StringUtils.isNumber(line)) { + int pos = Integer.parseInt(line); + if (pos == 0 || pos > history.size()) { + println("Not found"); + } else { + statement = history.get(pos - 1); + addToHistory = false; + println(statement); + end = true; + } + } else { + statement = line; + } + } else { + statement += "\n" + line; + } + if (end) { + if (addToHistory) { + history.add(0, statement); + if (history.size() > HISTORY_COUNT) { + history.remove(HISTORY_COUNT); + } + } + execute(statement); + statement = null; + } + } + } catch (SQLException e) { + println("SQL Exception: " + e.getMessage()); + statement = null; + } catch (IOException e) { + println(e.getMessage()); + break; + } catch (Exception e) { + println("Exception: " + e.toString()); + e.printStackTrace(err); + break; + } + } + if (conn != null) { + try { + conn.close(); + println("Connection closed"); + } catch (SQLException e) { + println("SQL Exception: " + e.getMessage()); + e.printStackTrace(err); + } + } + } + + private void connect() throws IOException, SQLException { + String url = "jdbc:h2:~/test"; + String user = ""; + String driver = null; + try { + Properties prop; + if ("null".equals(serverPropertiesDir)) { + prop = new Properties(); + } else { + prop = SortedProperties.loadProperties( + serverPropertiesDir + "/" + Constants.SERVER_PROPERTIES_NAME); + } + String data = null; + boolean found = false; + for (int i = 0;; i++) { + String d = prop.getProperty(Integer.toString(i)); + if (d == null) { + break; + } + found = true; + data = d; + } + if (found) { + ConnectionInfo info = new ConnectionInfo(data); + url = info.url; + user = info.user; + driver = info.driver; + } + } catch (IOException e) { + // ignore + } + println("[Enter] " + url); + print("URL "); + url = readLine(url).trim(); + if (driver == null) { + driver = JdbcUtils.getDriver(url); + } + if (driver != null) { + println("[Enter] " + driver); + } + print("Driver "); + driver = readLine(driver).trim(); + println("[Enter] " + user); + print("User "); + user = readLine(user); + conn = url.startsWith(Constants.START_URL) ? connectH2(driver, url, user) + : JdbcUtils.getConnection(driver, url, user, readPassword()); + stat = conn.createStatement(); + println("Connected"); + } + + private Connection connectH2(String driver, String url, String user) throws IOException, SQLException { + for (;;) { + String password = readPassword(); + try { + return JdbcUtils.getConnection(driver, url + ";IFEXISTS=TRUE", user, password); + } catch (SQLException ex) { + if (ex.getErrorCode() == ErrorCode.DATABASE_NOT_FOUND_WITH_IF_EXISTS_1) { + println("Type the same password again to confirm database creation."); + String password2 = readPassword(); + if (password.equals(password2)) { + return JdbcUtils.getConnection(driver, url, user, password); + } else { + println("Passwords don't match. Try again."); + } + } else { + throw ex; + } + } + } + } + + /** + * Print the string without newline, and flush. + * + * @param s the string to print + */ + protected void print(String s) { + out.print(s); + out.flush(); + } + + private void println(String s) { + out.println(s); + out.flush(); + } + + private String readPassword() throws IOException { + try { + Object console = Utils.callStaticMethod("java.lang.System.console"); + print("Password "); + char[] password = (char[]) Utils.callMethod(console, "readPassword"); + return password == null ? null : new String(password); + } catch (Exception e) { + // ignore, use the default solution + } + Thread passwordHider = new Thread(this, "Password hider"); + stopHide = false; + passwordHider.start(); + print("Password > "); + String p = readLine(); + stopHide = true; + try { + passwordHider.join(); + } catch (InterruptedException e) { + // ignore + } + print("\b\b"); + return p; + } + + /** + * INTERNAL. + * Hides the password by repeatedly printing + * backspace, backspace, >, <. + */ + @Override + public void run() { + while (!stopHide) { + print("\b\b><"); + try { + Thread.sleep(10); + } catch (InterruptedException e) { + // ignore + } + } + } + + + private String readLine(String defaultValue) throws IOException { + String s = readLine(); + return s.isEmpty() ? defaultValue : s; + } + + private String readLine() throws IOException { + String line = reader.readLine(); + if (line == null) { + throw new IOException("Aborted"); + } + return line; + } + + private void execute(String sql) { + if (StringUtils.isWhitespaceOrEmpty(sql)) { + return; + } + long time = System.nanoTime(); + try { + ResultSet rs = null; + try { + if (sql.startsWith("@")) { + rs = JdbcUtils.getMetaResultSet(conn, sql); + printResult(rs, listMode); + } else if (stat.execute(sql)) { + rs = stat.getResultSet(); + int rowCount = printResult(rs, listMode); + time = System.nanoTime() - time; + println("(" + rowCount + (rowCount == 1 ? + " row, " : " rows, ") + TimeUnit.NANOSECONDS.toMillis(time) + " ms)"); + } else { + long updateCount; + try { + updateCount = stat.getLargeUpdateCount(); + } catch (UnsupportedOperationException e) { + updateCount = stat.getUpdateCount(); + } + time = System.nanoTime() - time; + println("(Update count: " + updateCount + ", " + + TimeUnit.NANOSECONDS.toMillis(time) + " ms)"); + } + } finally { + JdbcUtils.closeSilently(rs); + } + } catch (SQLException e) { + println("Error: " + e.toString()); + if (listMode) { + e.printStackTrace(err); + } + } + } + + private int printResult(ResultSet rs, boolean asList) throws SQLException { + if (asList) { + return printResultAsList(rs); + } + return printResultAsTable(rs); + } + + private int printResultAsTable(ResultSet rs) throws SQLException { + ResultSetMetaData meta = rs.getMetaData(); + int len = meta.getColumnCount(); + boolean truncated = false; + ArrayList rows = new ArrayList<>(); + // buffer the header + String[] columns = new String[len]; + for (int i = 0; i < len; i++) { + String s = meta.getColumnLabel(i + 1); + columns[i] = s == null ? "" : s; + } + rows.add(columns); + int rowCount = 0; + while (rs.next()) { + rowCount++; + truncated |= loadRow(rs, len, rows); + if (rowCount > MAX_ROW_BUFFER) { + printRows(rows, len); + rows.clear(); + } + } + printRows(rows, len); + rows.clear(); + if (truncated) { + println("(data is partially truncated)"); + } + return rowCount; + } + + private boolean loadRow(ResultSet rs, int len, ArrayList rows) + throws SQLException { + boolean truncated = false; + String[] row = new String[len]; + for (int i = 0; i < len; i++) { + String s = rs.getString(i + 1); + if (s == null) { + s = "null"; + } + // only truncate if more than one column + if (len > 1 && s.length() > maxColumnSize) { + s = s.substring(0, maxColumnSize); + truncated = true; + } + row[i] = s; + } + rows.add(row); + return truncated; + } + + private int[] printRows(ArrayList rows, int len) { + int[] columnSizes = new int[len]; + for (int i = 0; i < len; i++) { + int max = 0; + for (String[] row : rows) { + max = Math.max(max, row[i].length()); + } + if (len > 1) { + max = Math.min(maxColumnSize, max); + } + columnSizes[i] = max; + } + for (String[] row : rows) { + StringBuilder buff = new StringBuilder(); + for (int i = 0; i < len; i++) { + if (i > 0) { + buff.append(' ').append(BOX_VERTICAL).append(' '); + } + String s = row[i]; + buff.append(s); + if (i < len - 1) { + for (int j = s.length(); j < columnSizes[i]; j++) { + buff.append(' '); + } + } + } + println(buff.toString()); + } + return columnSizes; + } + + private int printResultAsList(ResultSet rs) throws SQLException { + ResultSetMetaData meta = rs.getMetaData(); + int longestLabel = 0; + int len = meta.getColumnCount(); + String[] columns = new String[len]; + for (int i = 0; i < len; i++) { + String s = meta.getColumnLabel(i + 1); + columns[i] = s; + longestLabel = Math.max(longestLabel, s.length()); + } + StringBuilder buff = new StringBuilder(); + int rowCount = 0; + while (rs.next()) { + rowCount++; + buff.setLength(0); + if (rowCount > 1) { + println(""); + } + for (int i = 0; i < len; i++) { + if (i > 0) { + buff.append('\n'); + } + String label = columns[i]; + buff.append(label); + for (int j = label.length(); j < longestLabel; j++) { + buff.append(' '); + } + buff.append(": ").append(rs.getString(i + 1)); + } + println(buff.toString()); + } + if (rowCount == 0) { + for (int i = 0; i < len; i++) { + if (i > 0) { + buff.append('\n'); + } + String label = columns[i]; + buff.append(label); + } + println(buff.toString()); + } + return rowCount; + } + +} diff --git a/h2/src/main/org/h2/tools/SimpleResultSet.java b/h2/src/main/org/h2/tools/SimpleResultSet.java new file mode 100644 index 0000000..35d2964 --- /dev/null +++ b/h2/src/main/org/h2/tools/SimpleResultSet.java @@ -0,0 +1,2479 @@ +/* + * 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.tools; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.NClob; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Map; +import java.util.UUID; +import org.h2.api.ErrorCode; +import org.h2.message.DbException; +import org.h2.util.Bits; +import org.h2.util.JdbcUtils; +import org.h2.util.MathUtils; +import org.h2.util.SimpleColumnInfo; +import org.h2.util.Utils; +import org.h2.value.DataType; +import org.h2.value.Value; +import org.h2.value.ValueToObjectConverter; + +/** + * This class is a simple result set and meta data implementation. + * It can be used in Java functions that return a result set. + * Only the most basic methods are implemented, the others throw an exception. + * This implementation is standalone, and only relies on standard classes. + * It can be extended easily if required. + * + * An application can create a result set using the following code: + * + *
+ * SimpleResultSet rs = new SimpleResultSet();
+ * rs.addColumn("ID", Types.INTEGER, 10, 0);
+ * rs.addColumn("NAME", Types.VARCHAR, 255, 0);
+ * rs.addRow(0, "Hello" });
+ * rs.addRow(1, "World" });
+ * 
+ * + */ +public class SimpleResultSet implements ResultSet, ResultSetMetaData { + + private ArrayList rows; + private Object[] currentRow; + private int rowId = -1; + private boolean wasNull; + private SimpleRowSource source; + private ArrayList columns = Utils.newSmallArrayList(); + private boolean autoClose = true; + + /** + * This constructor is used if the result set is later populated with + * addRow. + */ + public SimpleResultSet() { + rows = Utils.newSmallArrayList(); + } + + /** + * This constructor is used if the result set should retrieve the rows using + * the specified row source object. + * + * @param source the row source + */ + public SimpleResultSet(SimpleRowSource source) { + this.source = source; + } + + /** + * Adds a column to the result set. + * All columns must be added before adding rows. + * This method uses the default SQL type names. + * + * @param name null is replaced with C1, C2,... + * @param sqlType the value returned in getColumnType(..) + * @param precision the precision + * @param scale the scale + */ + public void addColumn(String name, int sqlType, int precision, int scale) { + int valueType = DataType.convertSQLTypeToValueType(sqlType); + addColumn(name, sqlType, Value.getTypeName(valueType), precision, scale); + } + + /** + * Adds a column to the result set. + * All columns must be added before adding rows. + * + * @param name null is replaced with C1, C2,... + * @param sqlType the value returned in getColumnType(..) + * @param sqlTypeName the type name return in getColumnTypeName(..) + * @param precision the precision + * @param scale the scale + */ + public void addColumn(String name, int sqlType, String sqlTypeName, + int precision, int scale) { + if (rows != null && !rows.isEmpty()) { + throw new IllegalStateException( + "Cannot add a column after adding rows"); + } + if (name == null) { + name = "C" + (columns.size() + 1); + } + columns.add(new SimpleColumnInfo(name, sqlType, sqlTypeName, precision, scale)); + } + + /** + * Add a new row to the result set. + * Do not use this method when using a RowSource. + * + * @param row the row as an array of objects + */ + public void addRow(Object... row) { + if (rows == null) { + throw new IllegalStateException( + "Cannot add a row when using RowSource"); + } + rows.add(row); + } + + /** + * Returns ResultSet.CONCUR_READ_ONLY. + * + * @return CONCUR_READ_ONLY + */ + @Override + public int getConcurrency() { + return ResultSet.CONCUR_READ_ONLY; + } + + /** + * Returns ResultSet.FETCH_FORWARD. + * + * @return FETCH_FORWARD + */ + @Override + public int getFetchDirection() { + return ResultSet.FETCH_FORWARD; + } + + /** + * Returns 0. + * + * @return 0 + */ + @Override + public int getFetchSize() { + return 0; + } + + /** + * Returns the row number (1, 2,...) or 0 for no row. + * + * @return 0 + */ + @Override + public int getRow() { + return currentRow == null ? 0 : rowId + 1; + } + + /** + * Returns the result set type. This is ResultSet.TYPE_FORWARD_ONLY for + * auto-close result sets, and ResultSet.TYPE_SCROLL_INSENSITIVE for others. + * + * @return TYPE_FORWARD_ONLY or TYPE_SCROLL_INSENSITIVE + */ + @Override + public int getType() { + if (autoClose) { + return ResultSet.TYPE_FORWARD_ONLY; + } + return ResultSet.TYPE_SCROLL_INSENSITIVE; + } + + /** + * Closes the result set and releases the resources. + */ + @Override + public void close() { + currentRow = null; + rows = null; + columns = null; + rowId = -1; + if (source != null) { + source.close(); + source = null; + } + } + + /** + * Moves the cursor to the next row of the result set. + * + * @return true if successful, false if there are no more rows + */ + @Override + public boolean next() throws SQLException { + if (source != null) { + rowId++; + currentRow = source.readRow(); + if (currentRow != null) { + return true; + } + } else if (rows != null && rowId < rows.size()) { + rowId++; + if (rowId < rows.size()) { + currentRow = rows.get(rowId); + return true; + } + currentRow = null; + } + if (autoClose) { + close(); + } + return false; + } + + /** + * Moves the current position to before the first row, that means the result + * set is reset. + */ + @Override + public void beforeFirst() throws SQLException { + if (autoClose) { + throw DbException.getJdbcSQLException(ErrorCode.RESULT_SET_NOT_SCROLLABLE); + } + rowId = -1; + if (source != null) { + source.reset(); + } + } + + /** + * Returns whether the last column accessed was null. + * + * @return true if the last column accessed was null + */ + @Override + public boolean wasNull() { + return wasNull; + } + + /** + * Searches for a specific column in the result set. A case-insensitive + * search is made. + * + * @param columnLabel the column label + * @return the column index (1,2,...) + * @throws SQLException if the column is not found or if the result set is + * closed + */ + @Override + public int findColumn(String columnLabel) throws SQLException { + if (columnLabel != null && columns != null) { + for (int i = 0, size = columns.size(); i < size; i++) { + if (columnLabel.equalsIgnoreCase(getColumn(i).name)) { + return i + 1; + } + } + } + throw DbException.getJdbcSQLException(ErrorCode.COLUMN_NOT_FOUND_1, columnLabel); + } + + /** + * Returns a reference to itself. + * + * @return this + */ + @Override + public ResultSetMetaData getMetaData() { + return this; + } + + /** + * Returns null. + * + * @return null + */ + @Override + public SQLWarning getWarnings() { + return null; + } + + /** + * Returns null. + * + * @return null + */ + @Override + public Statement getStatement() { + return null; + } + + /** + * INTERNAL + */ + @Override + public void clearWarnings() { + // nothing to do + } + + // ---- get --------------------------------------------- + + /** + * Returns the value as a java.sql.Array. + * + * @param columnIndex (1,2,...) + * @return the value + */ + @Override + public Array getArray(int columnIndex) throws SQLException { + Object[] o = (Object[]) get(columnIndex); + return o == null ? null : new SimpleArray(o); + } + + /** + * Returns the value as a java.sql.Array. + * + * @param columnLabel the column label + * @return the value + */ + @Override + public Array getArray(String columnLabel) throws SQLException { + return getArray(findColumn(columnLabel)); + } + + /** + * INTERNAL + */ + @Override + public InputStream getAsciiStream(int columnIndex) throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public InputStream getAsciiStream(String columnLabel) throws SQLException { + throw getUnsupportedException(); + } + + /** + * Returns the value as a java.math.BigDecimal. + * + * @param columnIndex (1,2,...) + * @return the value + */ + @Override + public BigDecimal getBigDecimal(int columnIndex) throws SQLException { + Object o = get(columnIndex); + if (o != null && !(o instanceof BigDecimal)) { + o = new BigDecimal(o.toString()); + } + return (BigDecimal) o; + } + + /** + * Returns the value as a java.math.BigDecimal. + * + * @param columnLabel the column label + * @return the value + */ + @Override + public BigDecimal getBigDecimal(String columnLabel) throws SQLException { + return getBigDecimal(findColumn(columnLabel)); + } + + /** + * @deprecated INTERNAL + */ + @Deprecated + @Override + public BigDecimal getBigDecimal(int columnIndex, int scale) + throws SQLException { + throw getUnsupportedException(); + } + + /** + * @deprecated INTERNAL + */ + @Deprecated + @Override + public BigDecimal getBigDecimal(String columnLabel, int scale) + throws SQLException { + throw getUnsupportedException(); + } + + /** + * Returns the value as a java.io.InputStream. + * + * @param columnIndex (1,2,...) + * @return the value + */ + @Override + public InputStream getBinaryStream(int columnIndex) throws SQLException { + return asInputStream(get(columnIndex)); + } + + private static InputStream asInputStream(Object o) throws SQLException { + if (o == null) { + return null; + } else if (o instanceof Blob) { + return ((Blob) o).getBinaryStream(); + } + return (InputStream) o; + } + + /** + * Returns the value as a java.io.InputStream. + * + * @param columnLabel the column label + * @return the value + */ + @Override + public InputStream getBinaryStream(String columnLabel) throws SQLException { + return getBinaryStream(findColumn(columnLabel)); + } + + /** + * Returns the value as a java.sql.Blob. + * This is only supported if the + * result set was created using a Blob object. + * + * @param columnIndex (1,2,...) + * @return the value + */ + @Override + public Blob getBlob(int columnIndex) throws SQLException { + return (Blob) get(columnIndex); + } + + /** + * Returns the value as a java.sql.Blob. + * This is only supported if the + * result set was created using a Blob object. + * + * @param columnLabel the column label + * @return the value + */ + @Override + public Blob getBlob(String columnLabel) throws SQLException { + return getBlob(findColumn(columnLabel)); + } + + /** + * Returns the value as a boolean. + * + * @param columnIndex (1,2,...) + * @return the value + */ + @Override + public boolean getBoolean(int columnIndex) throws SQLException { + Object o = get(columnIndex); + if (o == null) { + return false; + } + if (o instanceof Boolean) { + return (Boolean) o; + } + if (o instanceof Number) { + Number n = (Number) o; + if (n instanceof Double || n instanceof Float) { + return n.doubleValue() != 0; + } + if (n instanceof BigDecimal) { + return ((BigDecimal) n).signum() != 0; + } + if (n instanceof BigInteger) { + return ((BigInteger) n).signum() != 0; + } + return n.longValue() != 0; + } + return Utils.parseBoolean(o.toString(), false, true); + } + + /** + * Returns the value as a boolean. + * + * @param columnLabel the column label + * @return the value + */ + @Override + public boolean getBoolean(String columnLabel) throws SQLException { + return getBoolean(findColumn(columnLabel)); + } + + /** + * Returns the value as a byte. + * + * @param columnIndex (1,2,...) + * @return the value + */ + @Override + public byte getByte(int columnIndex) throws SQLException { + Object o = get(columnIndex); + if (o != null && !(o instanceof Number)) { + o = Byte.decode(o.toString()); + } + return o == null ? 0 : ((Number) o).byteValue(); + } + + /** + * Returns the value as a byte. + * + * @param columnLabel the column label + * @return the value + */ + @Override + public byte getByte(String columnLabel) throws SQLException { + return getByte(findColumn(columnLabel)); + } + + /** + * Returns the value as a byte array. + * + * @param columnIndex (1,2,...) + * @return the value + */ + @Override + public byte[] getBytes(int columnIndex) throws SQLException { + Object o = get(columnIndex); + if (o == null || o instanceof byte[]) { + return (byte[]) o; + } + if (o instanceof UUID) { + return Bits.uuidToBytes((UUID) o); + } + return JdbcUtils.serialize(o, null); + } + + /** + * Returns the value as a byte array. + * + * @param columnLabel the column label + * @return the value + */ + @Override + public byte[] getBytes(String columnLabel) throws SQLException { + return getBytes(findColumn(columnLabel)); + } + + /** + * Returns the value as a java.io.Reader. + * This is only supported if the + * result set was created using a Clob or Reader object. + * + * @param columnIndex (1,2,...) + * @return the value + */ + @Override + public Reader getCharacterStream(int columnIndex) throws SQLException { + return asReader(get(columnIndex)); + } + + private static Reader asReader(Object o) throws SQLException { + if (o == null) { + return null; + } else if (o instanceof Clob) { + return ((Clob) o).getCharacterStream(); + } + return (Reader) o; + } + + /** + * Returns the value as a java.io.Reader. + * This is only supported if the + * result set was created using a Clob or Reader object. + * + * @param columnLabel the column label + * @return the value + */ + @Override + public Reader getCharacterStream(String columnLabel) throws SQLException { + return getCharacterStream(findColumn(columnLabel)); + } + + /** + * Returns the value as a java.sql.Clob. + * This is only supported if the + * result set was created using a Clob object. + * + * @param columnIndex (1,2,...) + * @return the value + */ + @Override + public Clob getClob(int columnIndex) throws SQLException { + return (Clob) get(columnIndex); + } + + /** + * Returns the value as a java.sql.Clob. + * This is only supported if the + * result set was created using a Clob object. + * + * @param columnLabel the column label + * @return the value + */ + @Override + public Clob getClob(String columnLabel) throws SQLException { + return getClob(findColumn(columnLabel)); + } + + /** + * Returns the value as an java.sql.Date. + * + * @param columnIndex (1,2,...) + * @return the value + */ + @Override + public Date getDate(int columnIndex) throws SQLException { + return (Date) get(columnIndex); + } + + /** + * Returns the value as a java.sql.Date. + * + * @param columnLabel the column label + * @return the value + */ + @Override + public Date getDate(String columnLabel) throws SQLException { + return getDate(findColumn(columnLabel)); + } + + /** + * INTERNAL + */ + @Override + public Date getDate(int columnIndex, Calendar cal) throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public Date getDate(String columnLabel, Calendar cal) throws SQLException { + throw getUnsupportedException(); + } + + /** + * Returns the value as an double. + * + * @param columnIndex (1,2,...) + * @return the value + */ + @Override + public double getDouble(int columnIndex) throws SQLException { + Object o = get(columnIndex); + if (o != null && !(o instanceof Number)) { + return Double.parseDouble(o.toString()); + } + return o == null ? 0 : ((Number) o).doubleValue(); + } + + /** + * Returns the value as a double. + * + * @param columnLabel the column label + * @return the value + */ + @Override + public double getDouble(String columnLabel) throws SQLException { + return getDouble(findColumn(columnLabel)); + } + + /** + * Returns the value as a float. + * + * @param columnIndex (1,2,...) + * @return the value + */ + @Override + public float getFloat(int columnIndex) throws SQLException { + Object o = get(columnIndex); + if (o != null && !(o instanceof Number)) { + return Float.parseFloat(o.toString()); + } + return o == null ? 0 : ((Number) o).floatValue(); + } + + /** + * Returns the value as a float. + * + * @param columnLabel the column label + * @return the value + */ + @Override + public float getFloat(String columnLabel) throws SQLException { + return getFloat(findColumn(columnLabel)); + } + + /** + * Returns the value as an int. + * + * @param columnIndex (1,2,...) + * @return the value + */ + @Override + public int getInt(int columnIndex) throws SQLException { + Object o = get(columnIndex); + if (o != null && !(o instanceof Number)) { + o = Integer.decode(o.toString()); + } + return o == null ? 0 : ((Number) o).intValue(); + } + + /** + * Returns the value as an int. + * + * @param columnLabel the column label + * @return the value + */ + @Override + public int getInt(String columnLabel) throws SQLException { + return getInt(findColumn(columnLabel)); + } + + /** + * Returns the value as a long. + * + * @param columnIndex (1,2,...) + * @return the value + */ + @Override + public long getLong(int columnIndex) throws SQLException { + Object o = get(columnIndex); + if (o != null && !(o instanceof Number)) { + o = Long.decode(o.toString()); + } + return o == null ? 0 : ((Number) o).longValue(); + } + + /** + * Returns the value as a long. + * + * @param columnLabel the column label + * @return the value + */ + @Override + public long getLong(String columnLabel) throws SQLException { + return getLong(findColumn(columnLabel)); + } + + /** + * INTERNAL + */ + @Override + public Reader getNCharacterStream(int columnIndex) throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public Reader getNCharacterStream(String columnLabel) throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public NClob getNClob(int columnIndex) throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public NClob getNClob(String columnLabel) throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public String getNString(int columnIndex) throws SQLException { + return getString(columnIndex); + } + + /** + * INTERNAL + */ + @Override + public String getNString(String columnLabel) throws SQLException { + return getString(columnLabel); + } + + /** + * Returns the value as an Object. + * + * @param columnIndex (1,2,...) + * @return the value + */ + @Override + public Object getObject(int columnIndex) throws SQLException { + return get(columnIndex); + } + + /** + * Returns the value as an Object. + * + * @param columnLabel the column label + * @return the value + */ + @Override + public Object getObject(String columnLabel) throws SQLException { + return getObject(findColumn(columnLabel)); + } + + /** + * Returns the value as an Object of the specified type. + * + * @param columnIndex the column index (1, 2, ...) + * @param type the class of the returned value + * @return the value + */ + @SuppressWarnings("unchecked") + @Override + public T getObject(int columnIndex, Class type) throws SQLException { + if (get(columnIndex) == null) { + return null; + } + if (type == BigDecimal.class) { + return (T) getBigDecimal(columnIndex); + } else if (type == BigInteger.class) { + return (T) getBigDecimal(columnIndex).toBigInteger(); + } else if (type == String.class) { + return (T) getString(columnIndex); + } else if (type == Boolean.class) { + return (T) (Boolean) getBoolean(columnIndex); + } else if (type == Byte.class) { + return (T) (Byte) getByte(columnIndex); + } else if (type == Short.class) { + return (T) (Short) getShort(columnIndex); + } else if (type == Integer.class) { + return (T) (Integer) getInt(columnIndex); + } else if (type == Long.class) { + return (T) (Long) getLong(columnIndex); + } else if (type == Float.class) { + return (T) (Float) getFloat(columnIndex); + } else if (type == Double.class) { + return (T) (Double) getDouble(columnIndex); + } else if (type == Date.class) { + return (T) getDate(columnIndex); + } else if (type == Time.class) { + return (T) getTime(columnIndex); + } else if (type == Timestamp.class) { + return (T) getTimestamp(columnIndex); + } else if (type == UUID.class) { + return (T) getObject(columnIndex); + } else if (type == byte[].class) { + return (T) getBytes(columnIndex); + } else if (type == java.sql.Array.class) { + return (T) getArray(columnIndex); + } else if (type == Blob.class) { + return (T) getBlob(columnIndex); + } else if (type == Clob.class) { + return (T) getClob(columnIndex); + } else { + throw getUnsupportedException(); + } + } + + /** + * Returns the value as an Object of the specified type. + * + * @param columnName the column name + * @param type the class of the returned value + * @return the value + */ + @Override + public T getObject(String columnName, Class type) throws SQLException { + return getObject(findColumn(columnName), type); + } + + /** + * INTERNAL + */ + @Override + public Object getObject(int columnIndex, Map> map) + throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public Object getObject(String columnLabel, Map> map) + throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public Ref getRef(int columnIndex) throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public Ref getRef(String columnLabel) throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public RowId getRowId(int columnIndex) throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public RowId getRowId(String columnLabel) throws SQLException { + throw getUnsupportedException(); + } + + /** + * Returns the value as a short. + * + * @param columnIndex (1,2,...) + * @return the value + */ + @Override + public short getShort(int columnIndex) throws SQLException { + Object o = get(columnIndex); + if (o != null && !(o instanceof Number)) { + o = Short.decode(o.toString()); + } + return o == null ? 0 : ((Number) o).shortValue(); + } + + /** + * Returns the value as a short. + * + * @param columnLabel the column label + * @return the value + */ + @Override + public short getShort(String columnLabel) throws SQLException { + return getShort(findColumn(columnLabel)); + } + + /** + * INTERNAL + */ + @Override + public SQLXML getSQLXML(int columnIndex) throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public SQLXML getSQLXML(String columnLabel) throws SQLException { + throw getUnsupportedException(); + } + + /** + * Returns the value as a String. + * + * @param columnIndex (1,2,...) + * @return the value + */ + @Override + public String getString(int columnIndex) throws SQLException { + Object o = get(columnIndex); + if (o == null) { + return null; + } + switch (columns.get(columnIndex - 1).type) { + case Types.CLOB: + Clob c = (Clob) o; + return c.getSubString(1, MathUtils.convertLongToInt(c.length())); + } + return o.toString(); + } + + /** + * Returns the value as a String. + * + * @param columnLabel the column label + * @return the value + */ + @Override + public String getString(String columnLabel) throws SQLException { + return getString(findColumn(columnLabel)); + } + + /** + * Returns the value as an java.sql.Time. + * + * @param columnIndex (1,2,...) + * @return the value + */ + @Override + public Time getTime(int columnIndex) throws SQLException { + return (Time) get(columnIndex); + } + + /** + * Returns the value as a java.sql.Time. + * + * @param columnLabel the column label + * @return the value + */ + @Override + public Time getTime(String columnLabel) throws SQLException { + return getTime(findColumn(columnLabel)); + } + + /** + * INTERNAL + */ + @Override + public Time getTime(int columnIndex, Calendar cal) throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public Time getTime(String columnLabel, Calendar cal) throws SQLException { + throw getUnsupportedException(); + } + + /** + * Returns the value as an java.sql.Timestamp. + * + * @param columnIndex (1,2,...) + * @return the value + */ + @Override + public Timestamp getTimestamp(int columnIndex) throws SQLException { + return (Timestamp) get(columnIndex); + } + + /** + * Returns the value as a java.sql.Timestamp. + * + * @param columnLabel the column label + * @return the value + */ + @Override + public Timestamp getTimestamp(String columnLabel) throws SQLException { + return getTimestamp(findColumn(columnLabel)); + } + + /** + * INTERNAL + */ + @Override + public Timestamp getTimestamp(int columnIndex, Calendar cal) + throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public Timestamp getTimestamp(String columnLabel, Calendar cal) + throws SQLException { + throw getUnsupportedException(); + } + + /** + * @deprecated INTERNAL + */ + @Deprecated + @Override + public InputStream getUnicodeStream(int columnIndex) throws SQLException { + throw getUnsupportedException(); + } + + /** + * @deprecated INTERNAL + */ + @Deprecated + @Override + public InputStream getUnicodeStream(String columnLabel) throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public URL getURL(int columnIndex) throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public URL getURL(String columnLabel) throws SQLException { + throw getUnsupportedException(); + } + + // ---- update --------------------------------------------- + + /** + * INTERNAL + */ + @Override + public void updateArray(int columnIndex, Array x) throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateArray(String columnLabel, Array x) throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateAsciiStream(int columnIndex, InputStream x) + throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateAsciiStream(String columnLabel, InputStream x) + throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateAsciiStream(int columnIndex, InputStream x, int length) + throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateAsciiStream(String columnLabel, InputStream x, int length) + throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateAsciiStream(int columnIndex, InputStream x, long length) + throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateAsciiStream(String columnLabel, InputStream x, long length) + throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateBigDecimal(int columnIndex, BigDecimal x) + throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateBigDecimal(String columnLabel, BigDecimal x) + throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateBinaryStream(int columnIndex, InputStream x) + throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateBinaryStream(String columnLabel, InputStream x) + throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateBinaryStream(int columnIndex, InputStream x, int length) + throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateBinaryStream(String columnLabel, InputStream x, int length) + throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateBinaryStream(int columnIndex, InputStream x, long length) + throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateBinaryStream(String columnLabel, InputStream x, long length) + throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateBlob(int columnIndex, Blob x) throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateBlob(String columnLabel, Blob x) throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateBlob(int columnIndex, InputStream x) throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateBlob(String columnLabel, InputStream x) + throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateBlob(int columnIndex, InputStream x, long length) + throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateBlob(String columnLabel, InputStream x, long length) + throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateBoolean(int columnIndex, boolean x) throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateBoolean(String columnLabel, boolean x) + throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateByte(int columnIndex, byte x) throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateByte(String columnLabel, byte x) throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateBytes(int columnIndex, byte[] x) throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateBytes(String columnLabel, byte[] x) throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateCharacterStream(int columnIndex, Reader x) + throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateCharacterStream(String columnLabel, Reader x) + throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateCharacterStream(int columnIndex, Reader x, int length) + throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateCharacterStream(String columnLabel, Reader x, int length) + throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateCharacterStream(int columnIndex, Reader x, long length) + throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateCharacterStream(String columnLabel, Reader x, long length) + throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateClob(int columnIndex, Clob x) throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateClob(String columnLabel, Clob x) throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateClob(int columnIndex, Reader x) throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateClob(String columnLabel, Reader x) throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateClob(int columnIndex, Reader x, long length) + throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateClob(String columnLabel, Reader x, long length) + throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateDate(int columnIndex, Date x) throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateDate(String columnLabel, Date x) throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateDouble(int columnIndex, double x) throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateDouble(String columnLabel, double x) throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateFloat(int columnIndex, float x) throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateFloat(String columnLabel, float x) throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateInt(int columnIndex, int x) throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateInt(String columnLabel, int x) throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateLong(int columnIndex, long x) throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateLong(String columnLabel, long x) throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateNCharacterStream(int columnIndex, Reader x) + throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateNCharacterStream(String columnLabel, Reader x) + throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateNCharacterStream(int columnIndex, Reader x, long length) + throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateNCharacterStream(String columnLabel, Reader x, long length) + throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateNClob(int columnIndex, NClob x) throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateNClob(String columnLabel, NClob x) throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateNClob(int columnIndex, Reader x) throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateNClob(String columnLabel, Reader x) throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateNClob(int columnIndex, Reader x, long length) + throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateNClob(String columnLabel, Reader x, long length) + throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateNString(int columnIndex, String x) throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateNString(String columnLabel, String x) throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateNull(int columnIndex) throws SQLException { + update(columnIndex, null); + } + + /** + * INTERNAL + */ + @Override + public void updateNull(String columnLabel) throws SQLException { + update(columnLabel, null); + } + + /** + * INTERNAL + */ + @Override + public void updateObject(int columnIndex, Object x) throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateObject(String columnLabel, Object x) throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateObject(int columnIndex, Object x, int scale) + throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateObject(String columnLabel, Object x, int scale) + throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateRef(int columnIndex, Ref x) throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateRef(String columnLabel, Ref x) throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateRowId(int columnIndex, RowId x) throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateRowId(String columnLabel, RowId x) throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateShort(int columnIndex, short x) throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateShort(String columnLabel, short x) throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateSQLXML(int columnIndex, SQLXML x) throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateSQLXML(String columnLabel, SQLXML x) throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateString(int columnIndex, String x) throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateString(String columnLabel, String x) throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateTime(int columnIndex, Time x) throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateTime(String columnLabel, Time x) throws SQLException { + update(columnLabel, x); + } + + /** + * INTERNAL + */ + @Override + public void updateTimestamp(int columnIndex, Timestamp x) + throws SQLException { + update(columnIndex, x); + } + + /** + * INTERNAL + */ + @Override + public void updateTimestamp(String columnLabel, Timestamp x) + throws SQLException { + update(columnLabel, x); + } + + // ---- result set meta data --------------------------------------------- + + /** + * Returns the column count. + * + * @return the column count + */ + @Override + public int getColumnCount() { + return columns.size(); + } + + /** + * Returns 15. + * + * @param columnIndex (1,2,...) + * @return 15 + */ + @Override + public int getColumnDisplaySize(int columnIndex) { + return 15; + } + + /** + * Returns the SQL type. + * + * @param columnIndex (1,2,...) + * @return the SQL type + */ + @Override + public int getColumnType(int columnIndex) throws SQLException { + return getColumn(columnIndex - 1).type; + } + + /** + * Returns the precision. + * + * @param columnIndex (1,2,...) + * @return the precision + */ + @Override + public int getPrecision(int columnIndex) throws SQLException { + return getColumn(columnIndex - 1).precision; + } + + /** + * Returns the scale. + * + * @param columnIndex (1,2,...) + * @return the scale + */ + @Override + public int getScale(int columnIndex) throws SQLException { + return getColumn(columnIndex - 1).scale; + } + + /** + * Returns ResultSetMetaData.columnNullableUnknown. + * + * @param columnIndex (1,2,...) + * @return columnNullableUnknown + */ + @Override + public int isNullable(int columnIndex) { + return ResultSetMetaData.columnNullableUnknown; + } + + /** + * Returns false. + * + * @param columnIndex (1,2,...) + * @return false + */ + @Override + public boolean isAutoIncrement(int columnIndex) { + return false; + } + + /** + * Returns true. + * + * @param columnIndex (1,2,...) + * @return true + */ + @Override + public boolean isCaseSensitive(int columnIndex) { + return true; + } + + /** + * Returns false. + * + * @param columnIndex (1,2,...) + * @return false + */ + @Override + public boolean isCurrency(int columnIndex) { + return false; + } + + /** + * Returns false. + * + * @param columnIndex (1,2,...) + * @return false + */ + @Override + public boolean isDefinitelyWritable(int columnIndex) { + return false; + } + + /** + * Returns true. + * + * @param columnIndex (1,2,...) + * @return true + */ + @Override + public boolean isReadOnly(int columnIndex) { + return true; + } + + /** + * Returns true. + * + * @param columnIndex (1,2,...) + * @return true + */ + @Override + public boolean isSearchable(int columnIndex) { + return true; + } + + /** + * Returns true. + * + * @param columnIndex (1,2,...) + * @return true + */ + @Override + public boolean isSigned(int columnIndex) { + return true; + } + + /** + * Returns false. + * + * @param columnIndex (1,2,...) + * @return false + */ + @Override + public boolean isWritable(int columnIndex) { + return false; + } + + /** + * Returns empty string. + * + * @param columnIndex (1,2,...) + * @return empty string + */ + @Override + public String getCatalogName(int columnIndex) { + return ""; + } + + /** + * Returns the Java class name if this column. + * + * @param columnIndex (1,2,...) + * @return the class name + */ + @Override + public String getColumnClassName(int columnIndex) throws SQLException { + int type = DataType.getValueTypeFromResultSet(this, columnIndex); + return ValueToObjectConverter.getDefaultClass(type, true).getName(); + } + + /** + * Returns the column label. + * + * @param columnIndex (1,2,...) + * @return the column label + */ + @Override + public String getColumnLabel(int columnIndex) throws SQLException { + return getColumn(columnIndex - 1).name; + } + + /** + * Returns the column name. + * + * @param columnIndex (1,2,...) + * @return the column name + */ + @Override + public String getColumnName(int columnIndex) throws SQLException { + return getColumnLabel(columnIndex); + } + + /** + * Returns the data type name of a column. + * + * @param columnIndex (1,2,...) + * @return the type name + */ + @Override + public String getColumnTypeName(int columnIndex) throws SQLException { + return getColumn(columnIndex - 1).typeName; + } + + /** + * Returns empty string. + * + * @param columnIndex (1,2,...) + * @return empty string + */ + @Override + public String getSchemaName(int columnIndex) { + return ""; + } + + /** + * Returns empty string. + * + * @param columnIndex (1,2,...) + * @return empty string + */ + @Override + public String getTableName(int columnIndex) { + return ""; + } + + // ---- unsupported / result set ----------------------------------- + + /** + * INTERNAL + */ + @Override + public void afterLast() throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public void cancelRowUpdates() throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public void deleteRow() throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public void insertRow() throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public void moveToCurrentRow() throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public void moveToInsertRow() throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public void refreshRow() throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public void updateRow() throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public boolean first() throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public boolean isAfterLast() throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public boolean isBeforeFirst() throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public boolean isFirst() throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public boolean isLast() throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public boolean last() throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public boolean previous() throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public boolean rowDeleted() throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public boolean rowInserted() throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public boolean rowUpdated() throws SQLException { + return true; + } + + /** + * INTERNAL + */ + @Override + public void setFetchDirection(int direction) throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public void setFetchSize(int rows) throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public boolean absolute(int row) throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public boolean relative(int offset) throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public String getCursorName() throws SQLException { + throw getUnsupportedException(); + } + + // --- private ----------------------------- + + private void update(int columnIndex, Object obj) throws SQLException { + checkClosed(); + checkColumnIndex(columnIndex); + this.currentRow[columnIndex - 1] = obj; + } + + private void update(String columnLabel, Object obj) throws SQLException { + this.currentRow[findColumn(columnLabel) - 1] = obj; + } + + /** + * INTERNAL + */ + static SQLException getUnsupportedException() { + return DbException.getJdbcSQLException(ErrorCode.FEATURE_NOT_SUPPORTED_1); + } + + private void checkClosed() throws SQLException { + if (columns == null) { + throw DbException.getJdbcSQLException(ErrorCode.OBJECT_CLOSED); + } + } + + private void checkColumnIndex(int columnIndex) throws SQLException { + if (columnIndex < 1 || columnIndex > columns.size()) { + throw DbException.getInvalidValueException( + "columnIndex", columnIndex).getSQLException(); + } + } + + private Object get(int columnIndex) throws SQLException { + if (currentRow == null) { + throw DbException.getJdbcSQLException(ErrorCode.NO_DATA_AVAILABLE); + } + checkColumnIndex(columnIndex); + columnIndex--; + Object o = columnIndex < currentRow.length ? + currentRow[columnIndex] : null; + wasNull = o == null; + return o; + } + + private SimpleColumnInfo getColumn(int i) throws SQLException { + checkColumnIndex(i + 1); + return columns.get(i); + } + + /** + * Returns the current result set holdability. + * + * @return the holdability + */ + @Override + public int getHoldability() { + return ResultSet.HOLD_CURSORS_OVER_COMMIT; + } + + /** + * Returns whether this result set has been closed. + * + * @return true if the result set was closed + */ + @Override + public boolean isClosed() { + return rows == null && source == null; + } + + /** + * Return an object of this class if possible. + * + * @param iface the class + * @return this + */ + @Override + @SuppressWarnings("unchecked") + public T unwrap(Class iface) throws SQLException { + try { + if (isWrapperFor(iface)) { + return (T) this; + } + throw DbException.getInvalidValueException("iface", iface); + } catch (Exception e) { + throw DbException.toSQLException(e); + } + } + + /** + * Checks if unwrap can return an object of this class. + * + * @param iface the class + * @return whether or not the interface is assignable from this class + */ + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return iface != null && iface.isAssignableFrom(getClass()); + } + + /** + * Set the auto-close behavior. If enabled (the default), the result set is + * closed after reading the last row. + * + * @param autoClose the new value + */ + public void setAutoClose(boolean autoClose) { + this.autoClose = autoClose; + } + + /** + * Get the current auto-close behavior. + * + * @return the auto-close value + */ + public boolean getAutoClose() { + return autoClose; + } + + /** + * A simple array implementation, + * backed by an object array + */ + public static class SimpleArray implements Array { + + private final Object[] value; + + SimpleArray(Object[] value) { + this.value = value; + } + + /** + * Get the object array. + * + * @return the object array + */ + @Override + public Object getArray() { + return value; + } + + /** + * INTERNAL + */ + @Override + public Object getArray(Map> map) throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public Object getArray(long index, int count) throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public Object getArray(long index, int count, Map> map) + throws SQLException { + throw getUnsupportedException(); + } + + /** + * Get the base type of this array. + * + * @return Types.NULL + */ + @Override + public int getBaseType() { + return Types.NULL; + } + + /** + * Get the base type name of this array. + * + * @return "NULL" + */ + @Override + public String getBaseTypeName() { + return "NULL"; + } + + /** + * INTERNAL + */ + @Override + public ResultSet getResultSet() throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public ResultSet getResultSet(Map> map) + throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public ResultSet getResultSet(long index, int count) + throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public ResultSet getResultSet(long index, int count, + Map> map) throws SQLException { + throw getUnsupportedException(); + } + + /** + * INTERNAL + */ + @Override + public void free() { + // nothing to do + } + + } + +} diff --git a/h2/src/main/org/h2/tools/SimpleRowSource.java b/h2/src/main/org/h2/tools/SimpleRowSource.java new file mode 100644 index 0000000..c1a38f4 --- /dev/null +++ b/h2/src/main/org/h2/tools/SimpleRowSource.java @@ -0,0 +1,35 @@ +/* + * 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.tools; + +import java.sql.SQLException; + +/** + * This interface is for classes that create rows on demand. + * It is used together with SimpleResultSet to create a dynamic result set. + */ +public interface SimpleRowSource { + + /** + * Get the next row. Must return null if no more rows are available. + * + * @return the row or null + * @throws SQLException on failure + */ + Object[] readRow() throws SQLException; + + /** + * Close the row source. + */ + void close(); + + /** + * Reset the position (before the first row). + * + * @throws SQLException if this operation is not supported + */ + void reset() throws SQLException; +} diff --git a/h2/src/main/org/h2/tools/TriggerAdapter.java b/h2/src/main/org/h2/tools/TriggerAdapter.java new file mode 100644 index 0000000..06b25ac --- /dev/null +++ b/h2/src/main/org/h2/tools/TriggerAdapter.java @@ -0,0 +1,97 @@ +/* + * 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.tools; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import org.h2.api.Trigger; +import org.h2.message.DbException; + +/** + * An adapter for the trigger interface that allows to use the ResultSet + * interface instead of a row array. + */ +public abstract class TriggerAdapter implements Trigger { + + /** + * The schema name. + */ + protected String schemaName; + + /** + * The name of the trigger. + */ + protected String triggerName; + + /** + * The name of the table. + */ + protected String tableName; + + /** + * Whether the fire method is called before or after the operation is + * performed. + */ + protected boolean before; + + /** + * The trigger type: INSERT, UPDATE, DELETE, SELECT, or a combination (a bit + * field). + */ + protected int type; + + /** + * This method is called by the database engine once when initializing the + * trigger. It is called when the trigger is created, as well as when the + * database is opened. The default implementation initialized the result + * sets. + * + * @param conn a connection to the database + * @param schemaName the name of the schema + * @param triggerName the name of the trigger used in the CREATE TRIGGER + * statement + * @param tableName the name of the table + * @param before whether the fire method is called before or after the + * operation is performed + * @param type the operation type: INSERT, UPDATE, DELETE, SELECT, or a + * combination (this parameter is a bit field) + */ + @Override + public void init(Connection conn, String schemaName, + String triggerName, String tableName, + boolean before, int type) throws SQLException { + this.schemaName = schemaName; + this.triggerName = triggerName; + this.tableName = tableName; + this.before = before; + this.type = type; + } + + @Override + public final void fire(Connection conn, Object[] oldRow, Object[] newRow) throws SQLException { + throw DbException.getInternalError(); + } + + /** + * This method is called for each triggered action by the default + * fire(Connection conn, Object[] oldRow, Object[] newRow) method. + *

+ * For "before" triggers, the new values of the new row may be changed + * using the ResultSet.updateX methods. + *

+ * + * @param conn a connection to the database + * @param oldRow the old row, or null if no old row is available (for + * INSERT) + * @param newRow the new row, or null if no new row is available (for + * DELETE) + * @throws SQLException if the operation must be undone + */ + public abstract void fire(Connection conn, ResultSet oldRow, + ResultSet newRow) throws SQLException; + +} diff --git a/h2/src/main/org/h2/tools/Upgrade.java b/h2/src/main/org/h2/tools/Upgrade.java new file mode 100644 index 0000000..ca77508 --- /dev/null +++ b/h2/src/main/org/h2/tools/Upgrade.java @@ -0,0 +1,384 @@ +/* + * 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.tools; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.sql.Connection; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Properties; +import java.util.UUID; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import org.h2.engine.ConnectionInfo; +import org.h2.engine.Constants; +import org.h2.jdbc.JdbcConnection; +import org.h2.util.IOUtils; +import org.h2.util.StringUtils; + +/** + * Upgrade utility. + */ +public final class Upgrade { + + private static final String[] CHECKSUMS = { + /* 1.2.120 */ "6fca37906aa3916ba609f47258c4abb4c749cd51aa28718a2339d9aa234a480c", + /* 1.2.121 */ "3233d38ee11e15243f66c98ad388da9f12cf038a203cf507415081e3329ac4f4", + /* 1.2.122 */ "7451e9f234f32fd9f07e4e5e682c0595806a803de656228a43887a525019ea74", + /* 1.2.123 */ "5a4dfaf211d32860623fdc5627f12a9cf8446b9cfabc742e7c0bad26835a8bb1", + /* 1.2.124 */ "f75efcaf9ccb91d94de920322c32328435e9705c19cc06b510c5f09c0a6245bf", + /* 1.2.125 */ "0ca368055dd72d539084c916642147780c944b90d98d2306da86814b174d1145", + /* 1.2.126 */ "4d9143f5b80f8878ca56edc383ae6d0a183a3b5879e83228dbacbe288007455c", + /* 1.2.127 */ "3df7aedd564cf61a464f4e95ec364eb7bb2b51d36863ed54edeb6ff2fed7b376", + /* 1.2.128 */ "7e8af7b5eca6334013fc024dab02e173a017b2d1c22c8481ed64a6af873d0819", + /* 1.2.129 */ "9a705009830ae80a368b1b66c8ba63071845fe25d8f6b0964aa14a3f31b46bdd", + /* 1.2.130 */ "8810d72867508b033a68830024e7fe7dd5a99e6f5bbb38c5a933aeb23badff00", + /* 1.2.131 */ "c8debc05829db1db2e6b6507a3f0561e1f72bd966d36f322bdf294baca29ed22", + /* 1.2.132 */ "75819d4adbf76d66af904e76b52b57afe26e9bc0e15aceed4e3c72cd7586b0d3", + /* 1.2.133 */ "c9ea3e95e77ae560322bca37d51601ae4b1d07ae90988af1e9fe1ceda80cd9ce", + /* 1.2.134 */ "1f4753d8d862d7d22d234625f617d3d7e91b73799c89b8a6036895f944a863eb", + /* 1.2.135 */ "eed53fcd3cf6e1159c90e57ef2b4cbd1fa3aff7a936988bb018af6fc17a2b6d9", + /* 1.2.136 */ "d3101d540ed004493952732d28bdf90a7968990bab7a2e04d16805469aa4eedd", + /* 1.2.137 */ "035dd78af874ada48339b90e8e4f1ffba0f32bb0fa37dec37ed523afa96a9c32", + /* 1.2.138 */ "1d03156b22b40812e39cca4d250eededfed4db8476bfbae78d60a24975cbe6d8", + /* 1.2.139 */ "8102cc96257d71caeff04f02c97020ae39268a32c1f0aa8fcdfda4e948ce48c8", + /* 1.2.140 */ "134ceafcae6ca661d8acd64c8e67d30f6ead609065dba9f6d3a0cde0d7bef6e3", + /* 1.2.141 */ "e453faccaaf7d8fe4eb8be744549c4a2395c7b3dcfcbc19173588c3756baff1e", + /* 1.2.142 */ "5973b4b467f1e0a69cf8c7b02d03d9dcadb4171d8a9635c85442a5829200e76f", + /* 1.2.143 */ "711cc225d8fe5325458c3947dda2093ef3a1cd4923e916082b27e87e41ca6735", + /* 1.2.144 */ "682f6997495a8389f4881b93cb8224685b9c6cbed487bcb445712402e52a4b80", + /* 1.2.145 */ "1407913cc6ba2f8c2928e8ad544c232273365d6eb66fdf84ec4213abf71449d5", + /* 1.3.146 */ "7756a89f10d5d5df23936bbb613de8b481e32d1099e5228968046fee62fee882", + /* 1.2.147 */ "2649d19db9eebbddc826029d236886dfece9404cd108ca590e82d3fd7d888278", + /* 1.3.148 */ "66f9389748f176c11c66c201a3737ebad0b1f4ace37cc2cd3da8962c92c72128", + /* 1.3.149 */ "7c3e3b93ffaf617393126870be7f8e1708bbe8e05b931c51c638a8cb03f79a36", + /* 1.3.150 */ "1d6dc1095d3d4b105a99034ab61ab5943c4dbb31551e7b244b403cb3c324964f", + /* 1.3.151 */ "8eabfde7cf64cedb7c25dc25ee7fe75a633c5cbeb18a1060da2045293fd53b14", + /* 1.3.152 */ "a9840c6024f8570ad3aa4d54388b4dd605640cb5ab163c444a123f7d4739aa09", + /* 1.3.153 */ "33d80491417eb117a0d64442dc3e60b78cf014ad099bb36a55d3835bb69e6248", + /* 1.3.154 */ "f153d03466acc00b66e699213fe092277e457502b5caf48c417ed3745f50eaac", + /* 1.3.155 */ "244b29d22939b43ecdcd3b0bfd279899df18e3af20a50241278b5b27bcf1a902", + /* 1.3.156 */ "070f9e4898044880e01232b269fea5285dbf7b814b7092701e755aa7d6941832", + /* 1.3.157 */ "4666d8f01c661054b973bc0f01f8b20f298d8e134e6fd26d78c74d43eeffd54e", + /* 1.3.158 */ "b0d95f18474beea619fcfba83f033e5702483457e0f0a1d1ffb4b757c5182582", + /* 1.3.159 */ "17aa5ced25f13f9adc2820e0ccc3010e3ce55944d10c9e2c0c631b77674d039b", + /* 1.3.160 */ "7fe66e211202733c52f02a328b55b30975287d9c509751bf87507e6227c6a2a7", + /* 1.3.161 */ "42e2ebbb7bdf29dd2de4ab16fc8fb511af6337d223afd66a5ee5fe183de05d57", + /* 1.3.162 */ "89e362f9525adf36d58487ff756ee93254bf92595a7098258a4c030e08e0742e", + /* 1.3.163 */ "1d1be843af365e8881e22732c8640e2b04c2821a0d7aa61d4152ac3f991bb735", + /* 1.3.164 */ "dbc88bb8cd8177b5f13b655d6afb525637129369422f0b7be0fe187950ea5132", + /* 1.3.165 */ "03f60ca37c0124fd2b9b177726396a51853ed0cade444e1674a090b73d341b08", + /* 1.3.166 */ "35103656071f1ffd1078b1a8c8028c9577297f31c5f8c7dcc845c7b4b6392619", + /* 1.3.167 */ "fa97521a2e72174485a96276bcf6f573d5e44ca6aba2f62de87b33b5bb0d4b91", + /* 1.3.168 */ "46d7ff55ccd910def16f9afd21d983f2eb2f9a6850fb501916f6673caebc2694", + /* 1.3.169 */ "0d99d51b8d7b8e94732d048438b9f555e031ecd52225613d7bea45290571886d", + /* 1.3.170 */ "0aca5eea86e8619e91ad61b82b77fb9d0e51e939c5603ab8da41be32c6f25664", + /* 1.3.171 */ "144d4ddb5d9f610b8b26809f1c65f442864cc55136325d3f02d7a93fb878a1db", + /* 1.3.172 */ "6ca30e38ccaa0c6f4264ef013327ef9ba5303f4be3d8fdbce0c3ae6451178c1e", + /* 1.3.173 */ "43908ee9db698cb335e2b85375d68a9d03d818869a0542b85d8d4e416619795b", + /* 1.3.174 */ "990b94cdfc89987281af4168fc2f6c9067be96a8533f5a6eb0f33da4d30d3e4b", + /* 1.3.175 */ "cc329a8742fb6e7168b00ebd0015816ff0d2462409add7c9d223826486de4691", + /* 1.3.176 */ "6ae3cc11a8bbaa5bd1d8494e62bccea4d354eaf042da468eac3bc5009fd33b67", + /* 1.4.177 */ "f281673f3248a4b5cb03fdc0cc39b944fe978366be959d0e8106fcc3197f4705", + /* 1.4.178 */ "da08fef0b2bc0ff8876f895e17605daf514405a064e3c2c11d2275a19d301be6", + /* 1.4.179 */ "2b76304ce4256ee9fd61156f9b6ef82c049ffdc8dc89af07fcf59e9532c7e7cd", + /* 1.4.180 */ "16428fd1e6a3e5baa8067c1c2e777e1e99af68c6ef3ff7fbbf1938937a048a82", + /* 1.4.181 */ "44673ff2834428fdb7f11dac3b9d679fb3039ea32194a69452971fdd7150a08b", + /* 1.4.182 */ "1025d0d70a4e899c41bc8fd7370cd3768826e78da91b66fd9357e44d03d79d30", + /* 1.4.183 */ "b3ff2ebe161976124965a9a841877ec4f6e913dbadcc31af27f1b99f6abd57e9", + /* 1.4.184 */ "9e47e14d5b4b9ead127b15a33b107ff06f0a7dd3f98b5d6c149e6ccae05dc0a2", + /* 1.4.185 */ "c4ac74be5971445e270bbd4344be58d9a06dc927223614217e5a87257a7edc03", + /* 1.4.186 */ "e3b7a39a2b45b61fa1521ef33b3ba676a5a9e1a397bc3ef4fb678d861a1b0ae4", + /* 1.4.187 */ "6204d0c206443681911fb9e04a3af5198b253b5627d16d8d8d79180d13319212", + /* 1.4.188 */ "11d6bff477f7ca392288f5f6d42ee61d0ccb63a34c99ba2d91710b2409673897", + /* 1.4.189 */ "c8dac03b66c8011cca4e44dcc7a8b1c8f8df769927c7672be1704e76f9ee7926", + /* 1.4.190 */ "23ba495a07bbbb3bd6c3084d10a96dad7a23741b8b6d64b213459a784195a98c", + /* 1.4.191 */ "e21ea665b74ec0115344b5afda5ec70ea27b528c3f103524e74c9854b1c4a284", + /* 1.4.192 */ "225b22e9857235c46c93861410b60b8c81c10dc8985f4faf188985ba5445126c", + /* 1.4.193 */ "b1cf34c64871014aa73580281cc464dfa72450d8860cc0752fc175e87edd6544", + /* 1.4.194 */ "b5b0c1836cead6831a50bd3e1b6c16fe6e583d4d2b7c4f41b4f838745c27cd01", + /* 1.4.195 */ "b99ea1f785c62b2a021664e72de696f8ea896f0da392a1c7baa3d4d47020b126", + /* 1.4.196 */ "0a05f4a0d5b85840148aadce63a423b5d3c36ef44756389b4faad08d2733faf5", + /* 1.4.197 */ "37f5216e14af2772930dff9b8734353f0a80e89ba3f33e065441de6537c5e842", + /* 1.4.198 */ "32dd6b149cb722aa4c2dd4d40a74a9cd41e32ac59a4e755a66e5753660d61d46", + /* 1.4.199 */ "3125a16743bc6b4cfbb61abba783203f1fb68230aa0fdc97898f796f99a5d42e", + /* 1.4.200 */ "3ad9ac4b6aae9cd9d3ac1c447465e1ed06019b851b893dd6a8d76ddb6d85bca6", + // + }; + + private static final String REPOSITORY = "https://repo1.maven.org/maven2"; + + /** + * Performs database upgrade from an older version of H2. + * + * @param url + * the JDBC connection URL + * @param info + * the connection properties ("user", "password", etc). + * @param version + * the old version of H2 + * @return {@code true} on success, {@code false} if URL is a remote or + * in-memory URL + * @throws Exception + * on failure + */ + public static boolean upgrade(String url, Properties info, int version) throws Exception { + Properties oldInfo = new Properties(); + oldInfo.putAll(info); + Object password = info.get("password"); + if (password instanceof char[]) { + oldInfo.put("password", ((char[]) password).clone()); + } + ConnectionInfo ci = new ConnectionInfo(url, info, null, null); + if (!ci.isPersistent() || ci.isRemote()) { + return false; + } + String name = ci.getName(); + String script = name + ".script.sql"; + StringBuilder oldUrl = new StringBuilder("jdbc:h2:").append(name).append(";ACCESS_MODE_DATA=r"); + copyProperty(ci, oldUrl, "FILE_LOCK"); + copyProperty(ci, oldUrl, "MV_STORE"); + String cipher = copyProperty(ci, oldUrl, "CIPHER"); + String scriptCommandSuffix = cipher == null ? "" : " CIPHER AES PASSWORD '" + UUID.randomUUID() + "' --hide--"; + java.sql.Driver driver = loadH2(version); + try (Connection conn = driver.connect(oldUrl.toString(), oldInfo)) { + conn.createStatement().execute(StringUtils.quoteStringSQL(new StringBuilder("SCRIPT TO "), script) + .append(scriptCommandSuffix).toString()); + } finally { + unloadH2(driver); + } + rename(name, false); + try (JdbcConnection conn = new JdbcConnection(url, info, null, null, false)) { + StringBuilder builder = StringUtils.quoteStringSQL(new StringBuilder("RUNSCRIPT FROM "), script) + .append(scriptCommandSuffix); + if (version <= 200) { + builder.append(" FROM_1X"); + } + conn.createStatement().execute(builder.toString()); + } catch (Throwable t) { + rename(name, true); + throw t; + } finally { + Files.deleteIfExists(Paths.get(script)); + } + return true; + } + + private static void rename(String name, boolean back) throws IOException { + rename(name, Constants.SUFFIX_MV_FILE, back); + rename(name, ".lobs.db", back); + } + + private static void rename(String name, String suffix, boolean back) throws IOException { + String source = name + suffix; + String target = source + ".bak"; + if (back) { + String t = source; + source = target; + target = t; + } + Path p = Paths.get(source); + if (Files.exists(p)) { + Files.move(p, Paths.get(target), StandardCopyOption.ATOMIC_MOVE); + } + } + + private static String copyProperty(ConnectionInfo ci, StringBuilder oldUrl, String name) { + try { + String value = ci.getProperty(name, null); + if (value != null) { + oldUrl.append(';').append(name).append('=').append(value); + } + return value; + } catch (Exception e) { + return null; + } + } + + /** + * Loads the specified version of H2 in a separate class loader. + * + * @param version + * the version to load + * @return the driver of the specified version + * @throws IOException + * on I/O exception + * @throws ReflectiveOperationException + * on exception during initialization of the driver + */ + public static java.sql.Driver loadH2(int version) throws IOException, ReflectiveOperationException { + String prefix; + if (version >= 201) { + if ((version & 1) != 0 || version > Constants.BUILD_ID) { + throw new IllegalArgumentException("version=" + version); + } + prefix = "2.0."; + } else if (version >= 177) { + prefix = "1.4."; + } else if (version >= 146 && version != 147) { + prefix = "1.3."; + } else if (version >= 120) { + prefix = "1.2."; + } else { + throw new IllegalArgumentException("version=" + version); + } + String fullVersion = prefix + version; + byte[] data = downloadUsingMaven("com.h2database", "h2", fullVersion, CHECKSUMS[version - 120]); + ZipInputStream is = new ZipInputStream(new ByteArrayInputStream(data)); + HashMap map = new HashMap<>(version >= 198 ? 2048 : 1024); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for (ZipEntry ze; (ze = is.getNextEntry()) != null;) { + if (ze.isDirectory()) { + continue; + } + IOUtils.copy(is, baos); + map.put(ze.getName(), baos.toByteArray()); + baos.reset(); + } + ClassLoader cl = new ClassLoader(null) { + @Override + protected Class findClass(String name) throws ClassNotFoundException { + String resourceName = name.replace('.', '/') + ".class"; + byte[] b = map.get(resourceName); + if (b == null) { + return ClassLoader.getSystemClassLoader().loadClass(name); + } + return defineClass(name, b, 0, b.length); + } + + @Override + public InputStream getResourceAsStream(String name) { + byte[] b = map.get(name); + return b != null ? new ByteArrayInputStream(b) : null; + } + }; + return (java.sql.Driver) cl.loadClass("org.h2.Driver").getDeclaredMethod("load").invoke(null); + } + + /** + * Unloads the specified driver of H2. + * + * @param driver + * the driver to unload + * @throws ReflectiveOperationException + * on exception + */ + public static void unloadH2(java.sql.Driver driver) throws ReflectiveOperationException { + driver.getClass().getDeclaredMethod("unload").invoke(null); + } + + private static byte[] downloadUsingMaven(String group, String artifact, String version, String sha256Checksum) + throws IOException { + String repoFile = group.replace('.', '/') + '/' + artifact + '/' + version + '/' + artifact + '-' + version + + ".jar"; + Path localMavenDir = Paths.get(System.getProperty("user.home") + "/.m2/repository"); + if (Files.isDirectory(localMavenDir)) { + Path f = localMavenDir.resolve(repoFile); + if (!Files.exists(f)) { + try { + ArrayList args = new ArrayList<>(); + if (System.getProperty("os.name").toLowerCase().contains("windows")) { + args.add("cmd"); + args.add("/C"); + } + args.add("mvn"); + args.add("org.apache.maven.plugins:maven-dependency-plugin:2.1:get"); + args.add("-D" + "repoUrl=" + REPOSITORY); + args.add("-D" + "artifact=" + group + ':' + artifact + ':' + version); + exec(args); + } catch (RuntimeException e) { + System.out.println("Could not download using Maven: " + e.toString()); + } + } + if (Files.exists(f)) { + return check(Files.readAllBytes(f), sha256Checksum, f.toAbsolutePath().toString()); + } + } + return download(REPOSITORY + '/' + repoFile, sha256Checksum); + } + + private static int exec(ArrayList args) { + try { + ProcessBuilder pb = new ProcessBuilder(); + pb.command(args.toArray(new String[0])); + pb.inheritIO(); + Process p = pb.start(); + p.waitFor(); + return p.exitValue(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static byte[] download(String fileURL, String sha256Checksum) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + System.out.println("Downloading " + fileURL); + URL url = new URL(fileURL); + InputStream in = new BufferedInputStream(url.openStream()); + long last = System.nanoTime(); + int len = 0; + while (true) { + long now = System.nanoTime(); + if (now - last > 1_000_000_000L) { + System.out.println("Downloaded " + len + " bytes"); + last = now; + } + int x = in.read(); + len++; + if (x < 0) { + break; + } + baos.write(x); + } + in.close(); + } catch (IOException e) { + throw new RuntimeException("Error downloading " + fileURL, e); + } + return check(baos.toByteArray(), sha256Checksum, null); + } + + private static byte[] check(byte[] data, String sha256Checksum, String checksummedFile) { + String got = getSHA256(data); + if (sha256Checksum == null) { + System.out.println('"' + got + '"'); + } else { + if (!got.equals(sha256Checksum)) { + StringBuilder builder = new StringBuilder().append("SHA-256 checksum mismatch; got: ").append(got) + .append(" expected: ").append(sha256Checksum); + if (checksummedFile != null) { + builder.append(" for file ").append(checksummedFile); + } + throw new RuntimeException(builder.toString()); + } + } + return data; + } + + private static String getSHA256(byte[] data) { + try { + return StringUtils.convertBytesToHex(MessageDigest.getInstance("SHA-256").digest(data)); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + private Upgrade() { + } + +} diff --git a/h2/src/main/org/h2/tools/package.html b/h2/src/main/org/h2/tools/package.html new file mode 100644 index 0000000..806c13e --- /dev/null +++ b/h2/src/main/org/h2/tools/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Various tools. Most tools are command line driven, but not all (for example the CSV tool). + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/util/AbbaDetector.java b/h2/src/main/org/h2/util/AbbaDetector.java new file mode 100644 index 0000000..b4c41bc --- /dev/null +++ b/h2/src/main/org/h2/util/AbbaDetector.java @@ -0,0 +1,124 @@ +/* + * 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.util; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; + +/** + * Utility to detect AB-BA deadlocks. + */ +public class AbbaDetector { + + private static final boolean TRACE = false; + + private static final ThreadLocal> STACK = ThreadLocal.withInitial(ArrayDeque::new); + + /** + * Map of (object A) -> ( + * map of (object locked before object A) -> + * (stack trace where locked) ) + */ + private static final Map> LOCK_ORDERING = + new WeakHashMap<>(); + + private static final Set KNOWN_DEADLOCKS = new HashSet<>(); + + /** + * This method is called just before or just after an object is + * synchronized. + * + * @param o the object, or null for the current class + * @return the object that was passed + */ + public static Object begin(Object o) { + if (o == null) { + o = new SecurityManager() { + Class clazz = getClassContext()[2]; + }.clazz; + } + Deque stack = STACK.get(); + if (!stack.isEmpty()) { + // Ignore locks which are locked multiple times in succession - + // Java locks are recursive + if (stack.contains(o)) { + // already synchronized on this + return o; + } + while (!stack.isEmpty()) { + Object last = stack.peek(); + if (Thread.holdsLock(last)) { + break; + } + stack.pop(); + } + } + if (TRACE) { + String thread = "[thread " + Thread.currentThread().getId() + "]"; + String indent = new String(new char[stack.size() * 2]).replace((char) 0, ' '); + System.out.println(thread + " " + indent + + "sync " + getObjectName(o)); + } + if (!stack.isEmpty()) { + markHigher(o, stack); + } + stack.push(o); + return o; + } + + private static Object getTest(Object o) { + // return o.getClass(); + return o; + } + + private static String getObjectName(Object o) { + return o.getClass().getSimpleName() + "@" + System.identityHashCode(o); + } + + private static synchronized void markHigher(Object o, Deque older) { + Object test = getTest(o); + Map map = LOCK_ORDERING.get(test); + if (map == null) { + map = new WeakHashMap<>(); + LOCK_ORDERING.put(test, map); + } + Exception oldException = null; + for (Object old : older) { + Object oldTest = getTest(old); + if (oldTest == test) { + continue; + } + Map oldMap = LOCK_ORDERING.get(oldTest); + if (oldMap != null) { + Exception e = oldMap.get(test); + if (e != null) { + String deadlockType = test.getClass() + " " + oldTest.getClass(); + if (!KNOWN_DEADLOCKS.contains(deadlockType)) { + String message = getObjectName(test) + + " synchronized after \n " + getObjectName(oldTest) + + ", but in the past before"; + RuntimeException ex = new RuntimeException(message); + ex.initCause(e); + ex.printStackTrace(System.out); + // throw ex; + KNOWN_DEADLOCKS.add(deadlockType); + } + } + } + if (!map.containsKey(oldTest)) { + if (oldException == null) { + oldException = new Exception("Before"); + } + map.put(oldTest, oldException); + } + } + } + +} diff --git a/h2/src/main/org/h2/util/AbbaLockingDetector.java b/h2/src/main/org/h2/util/AbbaLockingDetector.java new file mode 100644 index 0000000..62b67f4 --- /dev/null +++ b/h2/src/main/org/h2/util/AbbaLockingDetector.java @@ -0,0 +1,255 @@ +/* + * 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.util; + +import java.lang.management.ManagementFactory; +import java.lang.management.MonitorInfo; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; + +/** + * Utility to detect AB-BA deadlocks. + */ +public class AbbaLockingDetector implements Runnable { + + private final int tickIntervalMs = 2; + private volatile boolean stop; + + private final ThreadMXBean threadMXBean = + ManagementFactory.getThreadMXBean(); + private Thread thread; + + /** + * Map of (object A) -> ( map of (object locked before object A) -> + * (stack trace where locked) ) + */ + private final Map> lockOrdering = + new WeakHashMap<>(); + private final Set knownDeadlocks = new HashSet<>(); + + /** + * Start collecting locking data. + * + * @return this + */ + public AbbaLockingDetector startCollecting() { + thread = new Thread(this, "AbbaLockingDetector"); + thread.setDaemon(true); + thread.start(); + return this; + } + + /** + * Reset the state. + */ + public synchronized void reset() { + lockOrdering.clear(); + knownDeadlocks.clear(); + } + + /** + * Stop collecting. + * + * @return this + */ + public AbbaLockingDetector stopCollecting() { + stop = true; + if (thread != null) { + try { + thread.join(); + } catch (InterruptedException e) { + // ignore + } + thread = null; + } + return this; + } + + @Override + public void run() { + while (!stop) { + try { + tick(); + } catch (Throwable t) { + break; + } + } + } + + private void tick() { + if (tickIntervalMs > 0) { + try { + Thread.sleep(tickIntervalMs); + } catch (InterruptedException ex) { + // ignore + } + } + + ThreadInfo[] list = threadMXBean.dumpAllThreads( + // lockedMonitors + true, + // lockedSynchronizers + false); + processThreadList(list); + } + + private void processThreadList(ThreadInfo[] threadInfoList) { + final List lockOrder = new ArrayList<>(); + for (ThreadInfo threadInfo : threadInfoList) { + lockOrder.clear(); + generateOrdering(lockOrder, threadInfo); + if (lockOrder.size() > 1) { + markHigher(lockOrder, threadInfo); + } + } + } + + /** + * We cannot simply call getLockedMonitors because it is not guaranteed to + * return the locks in the correct order. + */ + private static void generateOrdering(List lockOrder, ThreadInfo info) { + final MonitorInfo[] lockedMonitors = info.getLockedMonitors(); + Arrays.sort(lockedMonitors, (a, b) -> b.getLockedStackDepth() - a.getLockedStackDepth()); + for (MonitorInfo mi : lockedMonitors) { + String lockName = getObjectName(mi); + if (lockName.equals("sun.misc.Launcher$AppClassLoader")) { + // ignore, it shows up everywhere + continue; + } + // Ignore locks which are locked multiple times in + // succession - Java locks are recursive. + if (!lockOrder.contains(lockName)) { + lockOrder.add(lockName); + } + } + } + + private synchronized void markHigher(List lockOrder, + ThreadInfo threadInfo) { + String topLock = lockOrder.get(lockOrder.size() - 1); + Map map = lockOrdering.get(topLock); + if (map == null) { + map = new WeakHashMap<>(); + lockOrdering.put(topLock, map); + } + String oldException = null; + for (int i = 0; i < lockOrder.size() - 1; i++) { + String olderLock = lockOrder.get(i); + Map oldMap = lockOrdering.get(olderLock); + boolean foundDeadLock = false; + if (oldMap != null) { + String e = oldMap.get(topLock); + if (e != null) { + foundDeadLock = true; + String deadlockType = topLock + " " + olderLock; + if (!knownDeadlocks.contains(deadlockType)) { + System.out.println(topLock + " synchronized after \n " + olderLock + + ", but in the past before\n" + "AFTER\n" + + getStackTraceForThread(threadInfo) + + "BEFORE\n" + e); + knownDeadlocks.add(deadlockType); + } + } + } + if (!foundDeadLock && !map.containsKey(olderLock)) { + if (oldException == null) { + oldException = getStackTraceForThread(threadInfo); + } + map.put(olderLock, oldException); + } + } + } + + /** + * Dump data in the same format as {@link ThreadInfo#toString()}, but with + * some modifications (no stack frame limit, and removal of uninteresting + * stack frames) + */ + private static String getStackTraceForThread(ThreadInfo info) { + StringBuilder sb = new StringBuilder().append('"') + .append(info.getThreadName()).append("\"" + " Id=") + .append(info.getThreadId()).append(' ').append(info.getThreadState()); + if (info.getLockName() != null) { + sb.append(" on ").append(info.getLockName()); + } + if (info.getLockOwnerName() != null) { + sb.append(" owned by \"").append(info.getLockOwnerName()) + .append("\" Id=").append(info.getLockOwnerId()); + } + if (info.isSuspended()) { + sb.append(" (suspended)"); + } + if (info.isInNative()) { + sb.append(" (in native)"); + } + sb.append('\n'); + final StackTraceElement[] stackTrace = info.getStackTrace(); + final MonitorInfo[] lockedMonitors = info.getLockedMonitors(); + boolean startDumping = false; + for (int i = 0; i < stackTrace.length; i++) { + StackTraceElement e = stackTrace[i]; + if (startDumping) { + dumpStackTraceElement(info, sb, i, e); + } + + for (MonitorInfo mi : lockedMonitors) { + if (mi.getLockedStackDepth() == i) { + // Only start dumping the stack from the first time we lock + // something. + // Removes a lot of unnecessary noise from the output. + if (!startDumping) { + dumpStackTraceElement(info, sb, i, e); + startDumping = true; + } + sb.append("\t- locked ").append(mi); + sb.append('\n'); + } + } + } + return sb.toString(); + } + + private static void dumpStackTraceElement(ThreadInfo info, + StringBuilder sb, int i, StackTraceElement e) { + sb.append('\t').append("at ").append(e) + .append('\n'); + if (i == 0 && info.getLockInfo() != null) { + Thread.State ts = info.getThreadState(); + switch (ts) { + case BLOCKED: + sb.append("\t- blocked on ") + .append(info.getLockInfo()) + .append('\n'); + break; + case WAITING: + sb.append("\t- waiting on ") + .append(info.getLockInfo()) + .append('\n'); + break; + case TIMED_WAITING: + sb.append("\t- waiting on ") + .append(info.getLockInfo()) + .append('\n'); + break; + default: + } + } + } + + private static String getObjectName(MonitorInfo info) { + return info.getClassName() + "@" + + Integer.toHexString(info.getIdentityHashCode()); + } + +} diff --git a/h2/src/main/org/h2/util/Bits.java b/h2/src/main/org/h2/util/Bits.java new file mode 100644 index 0000000..f910c5a --- /dev/null +++ b/h2/src/main/org/h2/util/Bits.java @@ -0,0 +1,325 @@ +/* + * 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.util; + +import java.util.UUID; + +/** + * Manipulations with bytes and arrays. This class can be overridden in + * multi-release JAR with more efficient implementation for a newer versions of + * Java. + */ +public final class Bits { + + /* + * Signatures of methods should match with + * h2/src/java9/src/org/h2/util/Bits.java and precompiled + * h2/src/java9/precompiled/org/h2/util/Bits.class. + */ + + /** + * Compare the contents of two char arrays. If the content or length of the + * first array is smaller than the second array, -1 is returned. If the content + * or length of the second array is smaller than the first array, 1 is returned. + * If the contents and lengths are the same, 0 is returned. + * + * @param data1 + * the first char array (must not be null) + * @param data2 + * the second char array (must not be null) + * @return the result of the comparison (-1, 1 or 0) + */ + public static int compareNotNull(char[] data1, char[] data2) { + if (data1 == data2) { + return 0; + } + int len = Math.min(data1.length, data2.length); + for (int i = 0; i < len; i++) { + char b = data1[i]; + char b2 = data2[i]; + if (b != b2) { + return b > b2 ? 1 : -1; + } + } + return Integer.signum(data1.length - data2.length); + } + + /** + * Compare the contents of two byte arrays. If the content or length of the + * first array is smaller than the second array, -1 is returned. If the content + * or length of the second array is smaller than the first array, 1 is returned. + * If the contents and lengths are the same, 0 is returned. + * + *

+ * This method interprets bytes as signed. + *

+ * + * @param data1 + * the first byte array (must not be null) + * @param data2 + * the second byte array (must not be null) + * @return the result of the comparison (-1, 1 or 0) + */ + public static int compareNotNullSigned(byte[] data1, byte[] data2) { + if (data1 == data2) { + return 0; + } + int len = Math.min(data1.length, data2.length); + for (int i = 0; i < len; i++) { + byte b = data1[i]; + byte b2 = data2[i]; + if (b != b2) { + return b > b2 ? 1 : -1; + } + } + return Integer.signum(data1.length - data2.length); + } + + /** + * Compare the contents of two byte arrays. If the content or length of the + * first array is smaller than the second array, -1 is returned. If the content + * or length of the second array is smaller than the first array, 1 is returned. + * If the contents and lengths are the same, 0 is returned. + * + *

+ * This method interprets bytes as unsigned. + *

+ * + * @param data1 + * the first byte array (must not be null) + * @param data2 + * the second byte array (must not be null) + * @return the result of the comparison (-1, 1 or 0) + */ + public static int compareNotNullUnsigned(byte[] data1, byte[] data2) { + if (data1 == data2) { + return 0; + } + int len = Math.min(data1.length, data2.length); + for (int i = 0; i < len; i++) { + int b = data1[i] & 0xff; + int b2 = data2[i] & 0xff; + if (b != b2) { + return b > b2 ? 1 : -1; + } + } + return Integer.signum(data1.length - data2.length); + } + + /** + * Reads a int value from the byte array at the given position in big-endian + * order. + * + * @param buff + * the byte array + * @param pos + * the position + * @return the value + */ + public static int readInt(byte[] buff, int pos) { + return (buff[pos++] << 24) + ((buff[pos++] & 0xff) << 16) + ((buff[pos++] & 0xff) << 8) + (buff[pos] & 0xff); + } + + /** + * Reads a int value from the byte array at the given position in + * little-endian order. + * + * @param buff + * the byte array + * @param pos + * the position + * @return the value + */ + public static int readIntLE(byte[] buff, int pos) { + return (buff[pos++] & 0xff) + ((buff[pos++] & 0xff) << 8) + ((buff[pos++] & 0xff) << 16) + (buff[pos] << 24); + } + + /** + * Reads a long value from the byte array at the given position in + * big-endian order. + * + * @param buff + * the byte array + * @param pos + * the position + * @return the value + */ + public static long readLong(byte[] buff, int pos) { + return (((long) readInt(buff, pos)) << 32) + (readInt(buff, pos + 4) & 0xffff_ffffL); + } + + /** + * Reads a long value from the byte array at the given position in + * little-endian order. + * + * @param buff + * the byte array + * @param pos + * the position + * @return the value + */ + public static long readLongLE(byte[] buff, int pos) { + return (readIntLE(buff, pos) & 0xffff_ffffL) + (((long) readIntLE(buff, pos + 4)) << 32); + } + + /** + * Reads a double value from the byte array at the given position in + * big-endian order. + * + * @param buff + * the byte array + * @param pos + * the position + * @return the value + */ + public static double readDouble(byte[] buff, int pos) { + return Double.longBitsToDouble(readLong(buff, pos)); + } + + /** + * Reads a double value from the byte array at the given position in + * little-endian order. + * + * @param buff + * the byte array + * @param pos + * the position + * @return the value + */ + public static double readDoubleLE(byte[] buff, int pos) { + return Double.longBitsToDouble(readLongLE(buff, pos)); + } + + /** + * Converts UUID value to byte array in big-endian order. + * + * @param msb + * most significant part of UUID + * @param lsb + * least significant part of UUID + * @return byte array representation + */ + public static byte[] uuidToBytes(long msb, long lsb) { + byte[] buff = new byte[16]; + for (int i = 0; i < 8; i++) { + buff[i] = (byte) ((msb >> (8 * (7 - i))) & 0xff); + buff[8 + i] = (byte) ((lsb >> (8 * (7 - i))) & 0xff); + } + return buff; + } + + /** + * Converts UUID value to byte array in big-endian order. + * + * @param uuid + * UUID value + * @return byte array representation + */ + public static byte[] uuidToBytes(UUID uuid) { + return uuidToBytes(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits()); + } + + /** + * Writes a int value to the byte array at the given position in big-endian + * order. + * + * @param buff + * the byte array + * @param pos + * the position + * @param x + * the value to write + */ + public static void writeInt(byte[] buff, int pos, int x) { + buff[pos++] = (byte) (x >> 24); + buff[pos++] = (byte) (x >> 16); + buff[pos++] = (byte) (x >> 8); + buff[pos] = (byte) x; + } + + /** + * Writes a int value to the byte array at the given position in + * little-endian order. + * + * @param buff + * the byte array + * @param pos + * the position + * @param x + * the value to write + */ + public static void writeIntLE(byte[] buff, int pos, int x) { + buff[pos++] = (byte) x; + buff[pos++] = (byte) (x >> 8); + buff[pos++] = (byte) (x >> 16); + buff[pos] = (byte) (x >> 24); + } + + /** + * Writes a long value to the byte array at the given position in big-endian + * order. + * + * @param buff + * the byte array + * @param pos + * the position + * @param x + * the value to write + */ + public static void writeLong(byte[] buff, int pos, long x) { + writeInt(buff, pos, (int) (x >> 32)); + writeInt(buff, pos + 4, (int) x); + } + + /** + * Writes a long value to the byte array at the given position in + * little-endian order. + * + * @param buff + * the byte array + * @param pos + * the position + * @param x + * the value to write + */ + public static void writeLongLE(byte[] buff, int pos, long x) { + writeIntLE(buff, pos, (int) x); + writeIntLE(buff, pos + 4, (int) (x >> 32)); + } + + /** + * Writes a double value to the byte array at the given position in + * big-endian order. + * + * @param buff + * the byte array + * @param pos + * the position + * @param x + * the value to write + */ + public static void writeDouble(byte[] buff, int pos, double x) { + writeLong(buff, pos, Double.doubleToRawLongBits(x)); + } + + /** + * Writes a double value to the byte array at the given position in + * little-endian order. + * + * @param buff + * the byte array + * @param pos + * the position + * @param x + * the value to write + */ + public static void writeDoubleLE(byte[] buff, int pos, double x) { + writeLongLE(buff, pos, Double.doubleToRawLongBits(x)); + } + + private Bits() { + } +} diff --git a/h2/src/main/org/h2/util/ByteStack.java b/h2/src/main/org/h2/util/ByteStack.java new file mode 100644 index 0000000..f1764ad --- /dev/null +++ b/h2/src/main/org/h2/util/ByteStack.java @@ -0,0 +1,122 @@ +/* + * 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.util; + +import java.util.Arrays; +import java.util.NoSuchElementException; + +/** + * The stack of byte values. This class is not synchronized and should not be + * used by multiple threads concurrently. + */ +public final class ByteStack { + + private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + + private int size; + + private byte[] array; + + /** + * Creates a new empty instance. + */ + public ByteStack() { + array = Utils.EMPTY_BYTES; + } + + /** + * Pushes an item onto the top of this stack. + * + * @param item + * the item to push + */ + public void push(byte item) { + int index = size; + int oldLength = array.length; + if (index >= oldLength) { + grow(oldLength); + } + array[index] = item; + size = index + 1; + } + + /** + * Removes the item at the top of this stack and returns that item. + * + * @return the item at the top of this stack + * @throws NoSuchElementException + * if stack is empty + */ + public byte pop() { + int index = size - 1; + if (index < 0) { + throw new NoSuchElementException(); + } + size = index; + return array[index]; + } + + /** + * Removes the item at the top of this stack and returns that item. + * + * @param defaultValue + * value to return if stack is empty + * @return the item at the top of this stack, or default value + */ + public int poll(int defaultValue) { + int index = size - 1; + if (index < 0) { + return defaultValue; + } + size = index; + return array[index]; + } + + /** + * Looks at the item at the top of this stack without removing it. + * + * @param defaultValue + * value to return if stack is empty + * @return the item at the top of this stack, or default value + */ + public int peek(int defaultValue) { + int index = size - 1; + if (index < 0) { + return defaultValue; + } + return array[index]; + } + + /** + * Returns {@code true} if this stack is empty. + * + * @return {@code true} if this stack is empty + */ + public boolean isEmpty() { + return size == 0; + } + + /** + * Returns the number of items in this stack. + * + * @return the number of items in this stack + */ + public int size() { + return size; + } + + private void grow(int length) { + if (length == 0) { + length = 0x10; + } else if (length >= MAX_ARRAY_SIZE) { + throw new OutOfMemoryError(); + } else if ((length <<= 1) < 0) { + length = MAX_ARRAY_SIZE; + } + array = Arrays.copyOf(array, length); + } + +} diff --git a/h2/src/main/org/h2/util/Cache.java b/h2/src/main/org/h2/util/Cache.java new file mode 100644 index 0000000..9ea4857 --- /dev/null +++ b/h2/src/main/org/h2/util/Cache.java @@ -0,0 +1,92 @@ +/* + * 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.util; + +import java.util.ArrayList; + +/** + * The cache keeps frequently used objects in the main memory. + */ +public interface Cache { + + /** + * Get all objects in the cache that have been changed. + * + * @return the list of objects + */ + ArrayList getAllChanged(); + + /** + * Clear the cache. + */ + void clear(); + + /** + * Get an element in the cache if it is available. + * This will move the item to the front of the list. + * + * @param pos the unique key of the element + * @return the element or null + */ + CacheObject get(int pos); + + /** + * Add an element to the cache. Other items may fall out of the cache + * because of this. It is not allowed to add the same record twice. + * + * @param r the object + */ + void put(CacheObject r); + + /** + * Update an element in the cache. + * This will move the item to the front of the list. + * + * @param pos the unique key of the element + * @param record the element + * @return the element + */ + CacheObject update(int pos, CacheObject record); + + /** + * Remove an object from the cache. + * + * @param pos the unique key of the element + * @return true if the key was in the cache + */ + boolean remove(int pos); + + /** + * Get an element from the cache if it is available. + * This will not move the item to the front of the list. + * + * @param pos the unique key of the element + * @return the element or null + */ + CacheObject find(int pos); + + /** + * Set the maximum memory to be used by this cache. + * + * @param size the maximum size in KB + */ + void setMaxMemory(int size); + + /** + * Get the maximum memory to be used. + * + * @return the maximum size in KB + */ + int getMaxMemory(); + + /** + * Get the used size in KB. + * + * @return the current size in KB + */ + int getMemory(); + +} diff --git a/h2/src/main/org/h2/util/CacheHead.java b/h2/src/main/org/h2/util/CacheHead.java new file mode 100644 index 0000000..d18bb13 --- /dev/null +++ b/h2/src/main/org/h2/util/CacheHead.java @@ -0,0 +1,23 @@ +/* + * 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.util; + +/** + * The head element of the linked list. + */ +public class CacheHead extends CacheObject { + + @Override + public boolean canRemove() { + return false; + } + + @Override + public int getMemory() { + return 0; + } + +} diff --git a/h2/src/main/org/h2/util/CacheLRU.java b/h2/src/main/org/h2/util/CacheLRU.java new file mode 100644 index 0000000..7dbd581 --- /dev/null +++ b/h2/src/main/org/h2/util/CacheLRU.java @@ -0,0 +1,381 @@ +/* + * 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.util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Map; +import org.h2.engine.Constants; +import org.h2.engine.SysProperties; +import org.h2.message.DbException; + +/** + * A cache implementation based on the last recently used (LRU) algorithm. + */ +public class CacheLRU implements Cache { + + static final String TYPE_NAME = "LRU"; + + private final CacheWriter writer; + + /** + * Use First-In-First-Out (don't move recently used items to the front of + * the queue). + */ + private final boolean fifo; + + private final CacheObject head = new CacheHead(); + private final int mask; + private CacheObject[] values; + private int recordCount; + + /** + * The number of cache buckets. + */ + private final int len; + + /** + * The maximum memory, in words (4 bytes each). + */ + private long maxMemory; + + /** + * The current memory used in this cache, in words (4 bytes each). + */ + private long memory; + + CacheLRU(CacheWriter writer, int maxMemoryKb, boolean fifo) { + this.writer = writer; + this.fifo = fifo; + this.setMaxMemory(maxMemoryKb); + try { + // Since setMaxMemory() ensures that maxMemory is >=0, + // we don't have to worry about an underflow. + long tmpLen = maxMemory / 64; + if (tmpLen > Integer.MAX_VALUE) { + throw new IllegalArgumentException(); + } + this.len = MathUtils.nextPowerOf2((int) tmpLen); + } catch (IllegalArgumentException e) { + throw new IllegalStateException("This much cache memory is not supported: " + maxMemoryKb + "kb", e); + } + this.mask = len - 1; + clear(); + } + + /** + * Create a cache of the given type and size. + * + * @param writer the cache writer + * @param cacheType the cache type + * @param cacheSize the size + * @return the cache object + */ + public static Cache getCache(CacheWriter writer, String cacheType, + int cacheSize) { + Map secondLevel = null; + if (cacheType.startsWith("SOFT_")) { + secondLevel = new SoftValuesHashMap<>(); + cacheType = cacheType.substring("SOFT_".length()); + } + Cache cache; + if (CacheLRU.TYPE_NAME.equals(cacheType)) { + cache = new CacheLRU(writer, cacheSize, false); + } else if (CacheTQ.TYPE_NAME.equals(cacheType)) { + cache = new CacheTQ(writer, cacheSize); + } else { + throw DbException.getInvalidValueException("CACHE_TYPE", cacheType); + } + if (secondLevel != null) { + cache = new CacheSecondLevel(cache, secondLevel); + } + return cache; + } + + @Override + public void clear() { + head.cacheNext = head.cachePrevious = head; + // first set to null - avoiding out of memory + values = null; + values = new CacheObject[len]; + recordCount = 0; + memory = len * (long)Constants.MEMORY_POINTER; + } + + @Override + public void put(CacheObject rec) { + if (SysProperties.CHECK) { + int pos = rec.getPos(); + CacheObject old = find(pos); + if (old != null) { + throw DbException.getInternalError("try to add a record twice at pos " + pos); + } + } + int index = rec.getPos() & mask; + rec.cacheChained = values[index]; + values[index] = rec; + recordCount++; + memory += rec.getMemory(); + addToFront(rec); + removeOldIfRequired(); + } + + @Override + public CacheObject update(int pos, CacheObject rec) { + CacheObject old = find(pos); + if (old == null) { + put(rec); + } else { + if (old != rec) { + throw DbException.getInternalError("old!=record pos:" + pos + " old:" + old + " new:" + rec); + } + if (!fifo) { + removeFromLinkedList(rec); + addToFront(rec); + } + } + return old; + } + + private void removeOldIfRequired() { + // a small method, to allow inlining + if (memory >= maxMemory) { + removeOld(); + } + } + + private void removeOld() { + int i = 0; + ArrayList changed = new ArrayList<>(); + long mem = memory; + int rc = recordCount; + boolean flushed = false; + CacheObject next = head.cacheNext; + while (true) { + if (rc <= Constants.CACHE_MIN_RECORDS) { + break; + } + if (changed.isEmpty()) { + if (mem <= maxMemory) { + break; + } + } else { + if (mem * 4 <= maxMemory * 3) { + break; + } + } + CacheObject check = next; + next = check.cacheNext; + i++; + if (i >= recordCount) { + if (!flushed) { + writer.flushLog(); + flushed = true; + i = 0; + } else { + // can't remove any record, because the records can not be + // removed hopefully this does not happen frequently, but it + // can happen + writer.getTrace() + .info("cannot remove records, cache size too small? records:" + + recordCount + " memory:" + memory); + break; + } + } + if (check == head) { + throw DbException.getInternalError("try to remove head"); + } + // we are not allowed to remove it if the log is not yet written + // (because we need to log before writing the data) + // also, can't write it if the record is pinned + if (!check.canRemove()) { + removeFromLinkedList(check); + addToFront(check); + continue; + } + rc--; + mem -= check.getMemory(); + if (check.isChanged()) { + changed.add(check); + } else { + remove(check.getPos()); + } + } + if (!changed.isEmpty()) { + if (!flushed) { + writer.flushLog(); + } + Collections.sort(changed); + long max = maxMemory; + int size = changed.size(); + try { + // temporary disable size checking, + // to avoid stack overflow + maxMemory = Long.MAX_VALUE; + for (i = 0; i < size; i++) { + CacheObject rec = changed.get(i); + writer.writeBack(rec); + } + } finally { + maxMemory = max; + } + for (i = 0; i < size; i++) { + CacheObject rec = changed.get(i); + remove(rec.getPos()); + if (rec.cacheNext != null) { + throw DbException.getInternalError(); + } + } + } + } + + private void addToFront(CacheObject rec) { + if (rec == head) { + throw DbException.getInternalError("try to move head"); + } + rec.cacheNext = head; + rec.cachePrevious = head.cachePrevious; + rec.cachePrevious.cacheNext = rec; + head.cachePrevious = rec; + } + + private void removeFromLinkedList(CacheObject rec) { + if (rec == head) { + throw DbException.getInternalError("try to remove head"); + } + rec.cachePrevious.cacheNext = rec.cacheNext; + rec.cacheNext.cachePrevious = rec.cachePrevious; + // TODO cache: mystery: why is this required? needs more memory if we + // don't do this + rec.cacheNext = null; + rec.cachePrevious = null; + } + + @Override + public boolean remove(int pos) { + int index = pos & mask; + CacheObject rec = values[index]; + if (rec == null) { + return false; + } + if (rec.getPos() == pos) { + values[index] = rec.cacheChained; + } else { + CacheObject last; + do { + last = rec; + rec = rec.cacheChained; + if (rec == null) { + return false; + } + } while (rec.getPos() != pos); + last.cacheChained = rec.cacheChained; + } + recordCount--; + memory -= rec.getMemory(); + removeFromLinkedList(rec); + if (SysProperties.CHECK) { + rec.cacheChained = null; + CacheObject o = find(pos); + if (o != null) { + throw DbException.getInternalError("not removed: " + o); + } + } + return true; + } + + @Override + public CacheObject find(int pos) { + CacheObject rec = values[pos & mask]; + while (rec != null && rec.getPos() != pos) { + rec = rec.cacheChained; + } + return rec; + } + + @Override + public CacheObject get(int pos) { + CacheObject rec = find(pos); + if (rec != null) { + if (!fifo) { + removeFromLinkedList(rec); + addToFront(rec); + } + } + return rec; + } + + // private void testConsistency() { + // int s = size; + // HashSet set = new HashSet(); + // for(int i=0; i { + + /** + * The previous element in the LRU linked list. If the previous element is + * the head, then this element is the most recently used object. + */ + public CacheObject cachePrevious; + + /** + * The next element in the LRU linked list. If the next element is the head, + * then this element is the least recently used object. + */ + public CacheObject cacheNext; + + /** + * The next element in the hash chain. + */ + public CacheObject cacheChained; + + private int pos; + private boolean changed; + + /** + * Check if the object can be removed from the cache. + * For example pinned objects can not be removed. + * + * @return true if it can be removed + */ + public abstract boolean canRemove(); + + /** + * Get the estimated used memory. + * + * @return number of words (one word is 4 bytes) + */ + public abstract int getMemory(); + + public void setPos(int pos) { + if (cachePrevious != null || cacheNext != null || cacheChained != null) { + throw DbException.getInternalError("setPos too late"); + } + this.pos = pos; + } + + public int getPos() { + return pos; + } + + /** + * Check if this cache object has been changed and thus needs to be written + * back to the storage. + * + * @return if it has been changed + */ + public boolean isChanged() { + return changed; + } + + public void setChanged(boolean b) { + changed = b; + } + + @Override + public int compareTo(CacheObject other) { + return Integer.compare(getPos(), other.getPos()); + } + + public boolean isStream() { + return false; + } + +} diff --git a/h2/src/main/org/h2/util/CacheSecondLevel.java b/h2/src/main/org/h2/util/CacheSecondLevel.java new file mode 100644 index 0000000..7d0469d --- /dev/null +++ b/h2/src/main/org/h2/util/CacheSecondLevel.java @@ -0,0 +1,89 @@ +/* + * 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: Jan Kotek + */ +package org.h2.util; + +import java.util.ArrayList; +import java.util.Map; + +/** + * Cache which wraps another cache (proxy pattern) and adds caching using map. + * This is useful for WeakReference, SoftReference or hard reference cache. + */ +class CacheSecondLevel implements Cache { + + private final Cache baseCache; + private final Map map; + + CacheSecondLevel(Cache cache, Map map) { + this.baseCache = cache; + this.map = map; + } + + @Override + public void clear() { + map.clear(); + baseCache.clear(); + } + + @Override + public CacheObject find(int pos) { + CacheObject ret = baseCache.find(pos); + if (ret == null) { + ret = map.get(pos); + } + return ret; + } + + @Override + public CacheObject get(int pos) { + CacheObject ret = baseCache.get(pos); + if (ret == null) { + ret = map.get(pos); + } + return ret; + } + + @Override + public ArrayList getAllChanged() { + return baseCache.getAllChanged(); + } + + @Override + public int getMaxMemory() { + return baseCache.getMaxMemory(); + } + + @Override + public int getMemory() { + return baseCache.getMemory(); + } + + @Override + public void put(CacheObject r) { + baseCache.put(r); + map.put(r.getPos(), r); + } + + @Override + public boolean remove(int pos) { + boolean result = baseCache.remove(pos); + result |= map.remove(pos) != null; + return result; + } + + @Override + public void setMaxMemory(int size) { + baseCache.setMaxMemory(size); + } + + @Override + public CacheObject update(int pos, CacheObject record) { + CacheObject oldRec = baseCache.update(pos, record); + map.put(pos, record); + return oldRec; + } + +} diff --git a/h2/src/main/org/h2/util/CacheTQ.java b/h2/src/main/org/h2/util/CacheTQ.java new file mode 100644 index 0000000..b05c574 --- /dev/null +++ b/h2/src/main/org/h2/util/CacheTQ.java @@ -0,0 +1,133 @@ +/* + * 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.util; + +import java.util.ArrayList; + +/** + * An alternative cache implementation. This implementation uses two caches: a + * LRU cache and a FIFO cache. Entries are first kept in the FIFO cache, and if + * referenced again then marked in a hash set. If referenced again, they are + * moved to the LRU cache. Stream pages are never added to the LRU cache. It is + * supposed to be more or less scan resistant, and it doesn't cache large rows + * in the LRU cache. + */ +public class CacheTQ implements Cache { + + static final String TYPE_NAME = "TQ"; + + private final Cache lru; + private final Cache fifo; + private final SmallLRUCache recentlyUsed = + SmallLRUCache.newInstance(1024); + private int lastUsed = -1; + + private int maxMemory; + + CacheTQ(CacheWriter writer, int maxMemoryKb) { + this.maxMemory = maxMemoryKb; + lru = new CacheLRU(writer, (int) (maxMemoryKb * 0.8), false); + fifo = new CacheLRU(writer, (int) (maxMemoryKb * 0.2), true); + setMaxMemory(4 * maxMemoryKb); + } + + @Override + public void clear() { + lru.clear(); + fifo.clear(); + recentlyUsed.clear(); + lastUsed = -1; + } + + @Override + public CacheObject find(int pos) { + CacheObject r = lru.find(pos); + if (r == null) { + r = fifo.find(pos); + } + return r; + } + + @Override + public CacheObject get(int pos) { + CacheObject r = lru.find(pos); + if (r != null) { + return r; + } + r = fifo.find(pos); + if (r != null && !r.isStream()) { + if (recentlyUsed.get(pos) != null) { + if (lastUsed != pos) { + fifo.remove(pos); + lru.put(r); + } + } else { + recentlyUsed.put(pos, this); + } + lastUsed = pos; + } + return r; + } + + @Override + public ArrayList getAllChanged() { + ArrayList lruChanged = lru.getAllChanged(); + ArrayList fifoChanged = fifo.getAllChanged(); + ArrayList changed = new ArrayList<>(lruChanged.size() + fifoChanged.size()); + changed.addAll(lruChanged); + changed.addAll(fifoChanged); + return changed; + } + + @Override + public int getMaxMemory() { + return maxMemory; + } + + @Override + public int getMemory() { + return lru.getMemory() + fifo.getMemory(); + } + + @Override + public void put(CacheObject r) { + if (r.isStream()) { + fifo.put(r); + } else if (recentlyUsed.get(r.getPos()) != null) { + lru.put(r); + } else { + fifo.put(r); + lastUsed = r.getPos(); + } + } + + @Override + public boolean remove(int pos) { + boolean result = lru.remove(pos); + if (!result) { + result = fifo.remove(pos); + } + recentlyUsed.remove(pos); + return result; + } + + @Override + public void setMaxMemory(int maxMemoryKb) { + this.maxMemory = maxMemoryKb; + lru.setMaxMemory((int) (maxMemoryKb * 0.8)); + fifo.setMaxMemory((int) (maxMemoryKb * 0.2)); + recentlyUsed.setMaxSize(4 * maxMemoryKb); + } + + @Override + public CacheObject update(int pos, CacheObject record) { + if (lru.find(pos) != null) { + return lru.update(pos, record); + } + return fifo.update(pos, record); + } + +} diff --git a/h2/src/main/org/h2/util/CacheWriter.java b/h2/src/main/org/h2/util/CacheWriter.java new file mode 100644 index 0000000..4277471 --- /dev/null +++ b/h2/src/main/org/h2/util/CacheWriter.java @@ -0,0 +1,38 @@ +/* + * 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.util; + +import org.h2.message.Trace; + +/** + * The cache writer is called by the cache to persist changed data that needs to + * be removed from the cache. + */ +public interface CacheWriter { + + /** + * Persist a record. + * + * @param entry the cache entry + */ + void writeBack(CacheObject entry); + + /** + * Flush the transaction log, so that entries can be removed from the cache. + * This is only required if the cache is full and contains data that is not + * yet written to the log. It is required to write the log entries to the + * log first, because the log is 'write ahead'. + */ + void flushLog(); + + /** + * Get the trace writer. + * + * @return the trace writer + */ + Trace getTrace(); + +} diff --git a/h2/src/main/org/h2/util/CloseWatcher.java b/h2/src/main/org/h2/util/CloseWatcher.java new file mode 100644 index 0000000..3a5911f --- /dev/null +++ b/h2/src/main/org/h2/util/CloseWatcher.java @@ -0,0 +1,117 @@ +/* + * 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 + * Iso8601: + * Initial Developer: Robert Rathsack (firstName dot lastName at gmx dot de) + */ +package org.h2.util; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * A phantom reference to watch for unclosed objects. + */ +public class CloseWatcher extends PhantomReference { + + /** + * The queue (might be set to null at any time). + */ + private static final ReferenceQueue queue = new ReferenceQueue<>(); + + /** + * The reference set. Must keep it, otherwise the references are garbage + * collected first and thus never enqueued. + */ + private static final Set refs = Collections.synchronizedSet(new HashSet<>()); + + /** + * The stack trace of when the object was created. It is converted to a + * string early on to avoid classloader problems (a classloader can't be + * garbage collected if there is a static reference to one of its classes). + */ + private String openStackTrace; + + /** + * The closeable object. + */ + private AutoCloseable closeable; + + public CloseWatcher(Object referent, ReferenceQueue q, + AutoCloseable closeable) { + super(referent, q); + this.closeable = closeable; + } + + /** + * Check for an collected object. + * + * @return the first watcher + */ + public static CloseWatcher pollUnclosed() { + while (true) { + CloseWatcher cw = (CloseWatcher) queue.poll(); + if (cw == null) { + return null; + } + if (refs != null) { + refs.remove(cw); + } + if (cw.closeable != null) { + return cw; + } + } + } + + /** + * Register an object. Before calling this method, pollUnclosed() should be + * called in a loop to remove old references. + * + * @param o the object + * @param closeable the object to close + * @param stackTrace whether the stack trace should be registered (this is + * relatively slow) + * @return the close watcher + */ + public static CloseWatcher register(Object o, AutoCloseable closeable, boolean stackTrace) { + CloseWatcher cw = new CloseWatcher(o, queue, closeable); + if (stackTrace) { + Exception e = new Exception("Open Stack Trace"); + StringWriter s = new StringWriter(); + e.printStackTrace(new PrintWriter(s)); + cw.openStackTrace = s.toString(); + } + refs.add(cw); + return cw; + } + + /** + * Unregister an object, so it is no longer tracked. + * + * @param w the reference + */ + public static void unregister(CloseWatcher w) { + w.closeable = null; + refs.remove(w); + } + + /** + * Get the open stack trace or null if none. + * + * @return the open stack trace + */ + public String getOpenStackTrace() { + return openStackTrace; + } + + public AutoCloseable getCloseable() { + return closeable; + } + +} diff --git a/h2/src/main/org/h2/util/DateTimeUtils.java b/h2/src/main/org/h2/util/DateTimeUtils.java new file mode 100644 index 0000000..1ee7c91 --- /dev/null +++ b/h2/src/main/org/h2/util/DateTimeUtils.java @@ -0,0 +1,1151 @@ +/* + * 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 + * Iso8601: Initial Developer: Robert Rathsack (firstName dot lastName at gmx + * dot de) + */ +package org.h2.util; + +import java.time.Instant; + +import org.h2.api.ErrorCode; +import org.h2.engine.CastDataProvider; +import org.h2.message.DbException; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueDate; +import org.h2.value.ValueTime; +import org.h2.value.ValueTimeTimeZone; +import org.h2.value.ValueTimestamp; +import org.h2.value.ValueTimestampTimeZone; + +/** + * This utility class contains time conversion functions. + *

+ * Date value: a bit field with bits for the year, month, and day. Absolute day: + * the day number (0 means 1970-01-01). + */ +public class DateTimeUtils { + + /** + * The number of milliseconds per day. + */ + public static final long MILLIS_PER_DAY = 24 * 60 * 60 * 1000L; + + /** + * The number of seconds per day. + */ + public static final long SECONDS_PER_DAY = 24 * 60 * 60; + + /** + * The number of nanoseconds per second. + */ + public static final long NANOS_PER_SECOND = 1_000_000_000; + + /** + * The number of nanoseconds per minute. + */ + public static final long NANOS_PER_MINUTE = 60 * NANOS_PER_SECOND; + + /** + * The number of nanoseconds per hour. + */ + public static final long NANOS_PER_HOUR = 60 * NANOS_PER_MINUTE; + + /** + * The number of nanoseconds per day. + */ + public static final long NANOS_PER_DAY = MILLIS_PER_DAY * 1_000_000; + + /** + * The offset of year bits in date values. + */ + public static final int SHIFT_YEAR = 9; + + /** + * The offset of month bits in date values. + */ + public static final int SHIFT_MONTH = 5; + + /** + * Date value for 1970-01-01. + */ + public static final int EPOCH_DATE_VALUE = (1970 << SHIFT_YEAR) + (1 << SHIFT_MONTH) + 1; + + /** + * Minimum possible date value. + */ + public static final long MIN_DATE_VALUE = (-1_000_000_000L << SHIFT_YEAR) + (1 << SHIFT_MONTH) + 1; + + /** + * Maximum possible date value. + */ + public static final long MAX_DATE_VALUE = (1_000_000_000L << SHIFT_YEAR) + (12 << SHIFT_MONTH) + 31; + + private static final int[] NORMAL_DAYS_PER_MONTH = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + + /** + * Multipliers for {@link #convertScale(long, int, long)} and + * {@link #appendNanos(StringBuilder, int)}. + */ + private static final int[] FRACTIONAL_SECONDS_TABLE = { 1_000_000_000, 100_000_000, + 10_000_000, 1_000_000, 100_000, 10_000, 1_000, 100, 10, 1 }; + + private static volatile TimeZoneProvider LOCAL; + + private DateTimeUtils() { + // utility class + } + + /** + * Reset the cached calendar for default timezone, for example after + * changing the default timezone. + */ + public static void resetCalendar() { + LOCAL = null; + } + + /** + * Get the time zone provider for the default time zone. + * + * @return the time zone provider for the default time zone + */ + public static TimeZoneProvider getTimeZone() { + TimeZoneProvider local = LOCAL; + if (local == null) { + LOCAL = local = TimeZoneProvider.getDefault(); + } + return local; + } + + /** + * Returns current timestamp. + * + * @param timeZone + * the time zone + * @return current timestamp + */ + public static ValueTimestampTimeZone currentTimestamp(TimeZoneProvider timeZone) { + return currentTimestamp(timeZone, Instant.now()); + } + + /** + * Returns current timestamp using the specified instant for its value. + * + * @param timeZone + * the time zone + * @param now + * timestamp source, must be greater than or equal to + * 1970-01-01T00:00:00Z + * @return current timestamp + */ + public static ValueTimestampTimeZone currentTimestamp(TimeZoneProvider timeZone, Instant now) { + /* + * This code intentionally does not support properly dates before UNIX + * epoch because such support is not required for current dates. + */ + long second = now.getEpochSecond(); + int offset = timeZone.getTimeZoneOffsetUTC(second); + second += offset; + return ValueTimestampTimeZone.fromDateValueAndNanos(dateValueFromAbsoluteDay(second / SECONDS_PER_DAY), + second % SECONDS_PER_DAY * 1_000_000_000 + now.getNano(), offset); + } + + /** + * Parse a date string. The format is: [+|-]year-month-day + * or [+|-]yyyyMMdd. + * + * @param s the string to parse + * @param start the parse index start + * @param end the parse index end + * @return the date value + * @throws IllegalArgumentException if there is a problem + */ + public static long parseDateValue(String s, int start, int end) { + if (s.charAt(start) == '+') { + // +year + start++; + } + // start at position 1 to support "-year" + int yEnd = s.indexOf('-', start + 1); + int mStart, mEnd, dStart; + if (yEnd > 0) { + // Standard [+|-]year-month-day format + mStart = yEnd + 1; + mEnd = s.indexOf('-', mStart); + if (mEnd <= mStart) { + throw new IllegalArgumentException(s); + } + dStart = mEnd + 1; + } else { + // Additional [+|-]yyyyMMdd format for compatibility + mEnd = dStart = end - 2; + yEnd = mStart = mEnd - 2; + // Accept only 3 or more digits in year for now + if (yEnd < start + 3) { + throw new IllegalArgumentException(s); + } + } + int year = Integer.parseInt(s.substring(start, yEnd)); + int month = StringUtils.parseUInt31(s, mStart, mEnd); + int day = StringUtils.parseUInt31(s, dStart, end); + if (!isValidDate(year, month, day)) { + throw new IllegalArgumentException(year + "-" + month + "-" + day); + } + return dateValue(year, month, day); + } + + /** + * Parse a time string. The format is: hour:minute[:second[.nanos]], + * hhmm[ss[.nanos]], or hour.minute.second[.nanos]. + * + * @param s the string to parse + * @param start the parse index start + * @param end the parse index end + * @return the time in nanoseconds + * @throws IllegalArgumentException if there is a problem + */ + public static long parseTimeNanos(String s, int start, int end) { + int hour, minute, second, nanos; + int hEnd = s.indexOf(':', start); + int mStart, mEnd, sStart, sEnd; + if (hEnd > 0) { + mStart = hEnd + 1; + mEnd = s.indexOf(':', mStart); + if (mEnd >= mStart) { + // Standard hour:minute:second[.nanos] format + sStart = mEnd + 1; + sEnd = s.indexOf('.', sStart); + } else { + // Additional hour:minute format for compatibility + mEnd = end; + sStart = sEnd = -1; + } + } else { + int t = s.indexOf('.', start); + if (t < 0) { + // Additional hhmm[ss] format for compatibility + hEnd = mStart = start + 2; + mEnd = mStart + 2; + int len = end - start; + if (len == 6) { + sStart = mEnd; + sEnd = -1; + } else if (len == 4) { + sStart = sEnd = -1; + } else { + throw new IllegalArgumentException(s); + } + } else if (t >= start + 6) { + // Additional hhmmss.nanos format for compatibility + if (t - start != 6) { + throw new IllegalArgumentException(s); + } + hEnd = mStart = start + 2; + mEnd = sStart = mStart + 2; + sEnd = t; + } else { + // Additional hour.minute.second[.nanos] IBM DB2 time format + hEnd = t; + mStart = hEnd + 1; + mEnd = s.indexOf('.', mStart); + if (mEnd <= mStart) { + throw new IllegalArgumentException(s); + } + sStart = mEnd + 1; + sEnd = s.indexOf('.', sStart); + } + } + hour = StringUtils.parseUInt31(s, start, hEnd); + if (hour >= 24) { + throw new IllegalArgumentException(s); + } + minute = StringUtils.parseUInt31(s, mStart, mEnd); + if (sStart > 0) { + if (sEnd < 0) { + second = StringUtils.parseUInt31(s, sStart, end); + nanos = 0; + } else { + second = StringUtils.parseUInt31(s, sStart, sEnd); + nanos = parseNanos(s, sEnd + 1, end); + } + } else { + second = nanos = 0; + } + if (minute >= 60 || second >= 60) { + throw new IllegalArgumentException(s); + } + return ((((hour * 60L) + minute) * 60) + second) * NANOS_PER_SECOND + nanos; + } + + /** + * Parse nanoseconds. + * + * @param s String to parse. + * @param start Begin position at the string to read. + * @param end End position at the string to read. + * @return Parsed nanoseconds. + */ + static int parseNanos(String s, int start, int end) { + if (start >= end) { + throw new IllegalArgumentException(s); + } + int nanos = 0, mul = 100_000_000; + do { + char c = s.charAt(start); + if (c < '0' || c > '9') { + throw new IllegalArgumentException(s); + } + nanos += mul * (c - '0'); + // mul can become 0, but continue loop anyway to ensure that all + // remaining digits are valid + mul /= 10; + } while (++start < end); + return nanos; + } + + /** + * Parses timestamp value from the specified string. + * + * @param s + * string to parse + * @param provider + * the cast information provider, may be {@code null} for + * Standard-compliant literals + * @param withTimeZone + * if {@code true} return {@link ValueTimestampTimeZone} instead of + * {@link ValueTimestamp} + * @return parsed timestamp + */ + public static Value parseTimestamp(String s, CastDataProvider provider, boolean withTimeZone) { + int dateEnd = s.indexOf(' '); + if (dateEnd < 0) { + // ISO 8601 compatibility + dateEnd = s.indexOf('T'); + if (dateEnd < 0 && provider != null && provider.getMode().allowDB2TimestampFormat) { + // DB2 also allows dash between date and time + dateEnd = s.indexOf('-', s.indexOf('-', s.indexOf('-') + 1) + 1); + } + } + int timeStart; + if (dateEnd < 0) { + dateEnd = s.length(); + timeStart = -1; + } else { + timeStart = dateEnd + 1; + } + long dateValue = parseDateValue(s, 0, dateEnd); + long nanos; + TimeZoneProvider tz = null; + if (timeStart < 0) { + nanos = 0; + } else { + dateEnd++; + int timeEnd; + if (s.endsWith("Z")) { + tz = TimeZoneProvider.UTC; + timeEnd = s.length() - 1; + } else { + int timeZoneStart = s.indexOf('+', dateEnd); + if (timeZoneStart < 0) { + timeZoneStart = s.indexOf('-', dateEnd); + } + if (timeZoneStart >= 0) { + // Allow [timeZoneName] part after time zone offset + int offsetEnd = s.indexOf('[', timeZoneStart + 1); + if (offsetEnd < 0) { + offsetEnd = s.length(); + } + tz = TimeZoneProvider.ofId(s.substring(timeZoneStart, offsetEnd)); + if (s.charAt(timeZoneStart - 1) == ' ') { + timeZoneStart--; + } + timeEnd = timeZoneStart; + } else { + timeZoneStart = s.indexOf(' ', dateEnd); + if (timeZoneStart > 0) { + tz = TimeZoneProvider.ofId(s.substring(timeZoneStart + 1)); + timeEnd = timeZoneStart; + } else { + timeEnd = s.length(); + } + } + } + nanos = parseTimeNanos(s, dateEnd, timeEnd); + } + if (withTimeZone) { + int tzSeconds; + if (tz == null) { + tz = provider != null ? provider.currentTimeZone() : DateTimeUtils.getTimeZone(); + } + if (tz != TimeZoneProvider.UTC) { + tzSeconds = tz.getTimeZoneOffsetUTC(tz.getEpochSecondsFromLocal(dateValue, nanos)); + } else { + tzSeconds = 0; + } + return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, nanos, tzSeconds); + } else if (tz != null) { + long seconds = tz.getEpochSecondsFromLocal(dateValue, nanos); + seconds += (provider != null ? provider.currentTimeZone() : DateTimeUtils.getTimeZone()) + .getTimeZoneOffsetUTC(seconds); + dateValue = dateValueFromLocalSeconds(seconds); + nanos = nanos % 1_000_000_000 + nanosFromLocalSeconds(seconds); + } + return ValueTimestamp.fromDateValueAndNanos(dateValue, nanos); + } + + /** + * Parses TIME WITH TIME ZONE value from the specified string. + * + * @param s + * string to parse + * @param provider + * the cast information provider, or {@code null} + * @return parsed time with time zone + */ + public static ValueTimeTimeZone parseTimeWithTimeZone(String s, CastDataProvider provider) { + int timeEnd; + TimeZoneProvider tz; + if (s.endsWith("Z")) { + tz = TimeZoneProvider.UTC; + timeEnd = s.length() - 1; + } else { + int timeZoneStart = s.indexOf('+', 1); + if (timeZoneStart < 0) { + timeZoneStart = s.indexOf('-', 1); + } + if (timeZoneStart >= 0) { + tz = TimeZoneProvider.ofId(s.substring(timeZoneStart)); + if (s.charAt(timeZoneStart - 1) == ' ') { + timeZoneStart--; + } + timeEnd = timeZoneStart; + } else { + timeZoneStart = s.indexOf(' ', 1); + if (timeZoneStart > 0) { + tz = TimeZoneProvider.ofId(s.substring(timeZoneStart + 1)); + timeEnd = timeZoneStart; + } else { + throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2, "TIME WITH TIME ZONE", s); + } + } + if (!tz.hasFixedOffset()) { + throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2, "TIME WITH TIME ZONE", s); + } + } + return ValueTimeTimeZone.fromNanos(parseTimeNanos(s, 0, timeEnd), tz.getTimeZoneOffsetUTC(0L)); + } + + /** + * Calculates the seconds since epoch for the specified date value, + * nanoseconds since midnight, and time zone offset. + * @param dateValue + * date value + * @param timeNanos + * nanoseconds since midnight + * @param offsetSeconds + * time zone offset in seconds + * @return seconds since epoch in UTC + */ + public static long getEpochSeconds(long dateValue, long timeNanos, int offsetSeconds) { + return absoluteDayFromDateValue(dateValue) * SECONDS_PER_DAY + timeNanos / NANOS_PER_SECOND - offsetSeconds; + } + + /** + * Extracts date value and nanos of day from the specified value. + * + * @param value + * value to extract fields from + * @param provider + * the cast information provider + * @return array with date value and nanos of day + */ + public static long[] dateAndTimeFromValue(Value value, CastDataProvider provider) { + long dateValue = EPOCH_DATE_VALUE; + long timeNanos = 0; + if (value instanceof ValueTimestamp) { + ValueTimestamp v = (ValueTimestamp) value; + dateValue = v.getDateValue(); + timeNanos = v.getTimeNanos(); + } else if (value instanceof ValueDate) { + dateValue = ((ValueDate) value).getDateValue(); + } else if (value instanceof ValueTime) { + timeNanos = ((ValueTime) value).getNanos(); + } else if (value instanceof ValueTimestampTimeZone) { + ValueTimestampTimeZone v = (ValueTimestampTimeZone) value; + dateValue = v.getDateValue(); + timeNanos = v.getTimeNanos(); + } else if (value instanceof ValueTimeTimeZone) { + timeNanos = ((ValueTimeTimeZone) value).getNanos(); + } else { + ValueTimestamp v = (ValueTimestamp) value.convertTo(TypeInfo.TYPE_TIMESTAMP, provider); + dateValue = v.getDateValue(); + timeNanos = v.getTimeNanos(); + } + return new long[] {dateValue, timeNanos}; + } + + /** + * Creates a new date-time value with the same type as original value. If + * original value is a ValueTimestampTimeZone or ValueTimeTimeZone, returned + * value will have the same time zone offset as original value. + * + * @param original + * original value + * @param dateValue + * date value for the returned value + * @param timeNanos + * nanos of day for the returned value + * @return new value with specified date value and nanos of day + */ + public static Value dateTimeToValue(Value original, long dateValue, long timeNanos) { + switch (original.getValueType()) { + case Value.DATE: + return ValueDate.fromDateValue(dateValue); + case Value.TIME: + return ValueTime.fromNanos(timeNanos); + case Value.TIME_TZ: + return ValueTimeTimeZone.fromNanos(timeNanos, ((ValueTimeTimeZone) original).getTimeZoneOffsetSeconds()); + case Value.TIMESTAMP: + default: + return ValueTimestamp.fromDateValueAndNanos(dateValue, timeNanos); + case Value.TIMESTAMP_TZ: + return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, timeNanos, + ((ValueTimestampTimeZone) original).getTimeZoneOffsetSeconds()); + } + } + + /** + * Returns day of week. + * + * @param dateValue + * the date value + * @param firstDayOfWeek + * first day of week, Monday as 1, Sunday as 7 or 0 + * @return day of week + * @see #getIsoDayOfWeek(long) + */ + public static int getDayOfWeek(long dateValue, int firstDayOfWeek) { + return getDayOfWeekFromAbsolute(absoluteDayFromDateValue(dateValue), firstDayOfWeek); + } + + /** + * Get the day of the week from the absolute day value. + * + * @param absoluteValue the absolute day + * @param firstDayOfWeek the first day of the week + * @return the day of week + */ + public static int getDayOfWeekFromAbsolute(long absoluteValue, int firstDayOfWeek) { + return absoluteValue >= 0 ? (int) ((absoluteValue - firstDayOfWeek + 11) % 7) + 1 + : (int) ((absoluteValue - firstDayOfWeek - 2) % 7) + 7; + } + + /** + * Returns number of day in year. + * + * @param dateValue + * the date value + * @return number of day in year + */ + public static int getDayOfYear(long dateValue) { + int m = monthFromDateValue(dateValue); + int a = (367 * m - 362) / 12 + dayFromDateValue(dateValue); + if (m > 2) { + a--; + long y = yearFromDateValue(dateValue); + if ((y & 3) != 0 || (y % 100 == 0 && y % 400 != 0)) { + a--; + } + } + return a; + } + + /** + * Returns ISO day of week. + * + * @param dateValue + * the date value + * @return ISO day of week, Monday as 1 to Sunday as 7 + * @see #getSundayDayOfWeek(long) + */ + public static int getIsoDayOfWeek(long dateValue) { + return getDayOfWeek(dateValue, 1); + } + + /** + * Returns ISO number of week in year. + * + * @param dateValue + * the date value + * @return number of week in year + * @see #getIsoWeekYear(long) + * @see #getWeekOfYear(long, int, int) + */ + public static int getIsoWeekOfYear(long dateValue) { + return getWeekOfYear(dateValue, 1, 4); + } + + /** + * Returns ISO week year. + * + * @param dateValue + * the date value + * @return ISO week year + * @see #getIsoWeekOfYear(long) + * @see #getWeekYear(long, int, int) + */ + public static int getIsoWeekYear(long dateValue) { + return getWeekYear(dateValue, 1, 4); + } + + /** + * Returns day of week with Sunday as 1. + * + * @param dateValue + * the date value + * @return day of week, Sunday as 1 to Monday as 7 + * @see #getIsoDayOfWeek(long) + */ + public static int getSundayDayOfWeek(long dateValue) { + return getDayOfWeek(dateValue, 0); + } + + /** + * Returns number of week in year. + * + * @param dateValue + * the date value + * @param firstDayOfWeek + * first day of week, Monday as 1, Sunday as 7 or 0 + * @param minimalDaysInFirstWeek + * minimal days in first week of year + * @return number of week in year + * @see #getIsoWeekOfYear(long) + */ + public static int getWeekOfYear(long dateValue, int firstDayOfWeek, int minimalDaysInFirstWeek) { + long abs = absoluteDayFromDateValue(dateValue); + int year = yearFromDateValue(dateValue); + long base = getWeekYearAbsoluteStart(year, firstDayOfWeek, minimalDaysInFirstWeek); + if (abs - base < 0) { + base = getWeekYearAbsoluteStart(year - 1, firstDayOfWeek, minimalDaysInFirstWeek); + } else if (monthFromDateValue(dateValue) == 12 && 24 + minimalDaysInFirstWeek < dayFromDateValue(dateValue)) { + if (abs >= getWeekYearAbsoluteStart(year + 1, firstDayOfWeek, minimalDaysInFirstWeek)) { + return 1; + } + } + return (int) ((abs - base) / 7) + 1; + } + + /** + * Get absolute day of the first day in the week year. + * + * @param weekYear + * the week year + * @param firstDayOfWeek + * first day of week, Monday as 1, Sunday as 7 or 0 + * @param minimalDaysInFirstWeek + * minimal days in first week of year + * @return absolute day of the first day in the week year + */ + public static long getWeekYearAbsoluteStart(int weekYear, int firstDayOfWeek, int minimalDaysInFirstWeek) { + long first = absoluteDayFromYear(weekYear); + int daysInFirstWeek = 8 - getDayOfWeekFromAbsolute(first, firstDayOfWeek); + long base = first + daysInFirstWeek; + if (daysInFirstWeek >= minimalDaysInFirstWeek) { + base -= 7; + } + return base; + } + + /** + * Returns week year. + * + * @param dateValue + * the date value + * @param firstDayOfWeek + * first day of week, Monday as 1, Sunday as 7 or 0 + * @param minimalDaysInFirstWeek + * minimal days in first week of year + * @return week year + * @see #getIsoWeekYear(long) + */ + public static int getWeekYear(long dateValue, int firstDayOfWeek, int minimalDaysInFirstWeek) { + long abs = absoluteDayFromDateValue(dateValue); + int year = yearFromDateValue(dateValue); + long base = getWeekYearAbsoluteStart(year, firstDayOfWeek, minimalDaysInFirstWeek); + if (abs < base) { + return year - 1; + } else if (monthFromDateValue(dateValue) == 12 && 24 + minimalDaysInFirstWeek < dayFromDateValue(dateValue)) { + if (abs >= getWeekYearAbsoluteStart(year + 1, firstDayOfWeek, minimalDaysInFirstWeek)) { + return year + 1; + } + } + return year; + } + + /** + * Returns number of days in month. + * + * @param year the year + * @param month the month + * @return number of days in the specified month + */ + public static int getDaysInMonth(int year, int month) { + if (month != 2) { + return NORMAL_DAYS_PER_MONTH[month]; + } + return (year & 3) == 0 && (year % 100 != 0 || year % 400 == 0) ? 29 : 28; + } + + /** + * Verify if the specified date is valid. + * + * @param year the year + * @param month the month (January is 1) + * @param day the day (1 is the first of the month) + * @return true if it is valid + */ + public static boolean isValidDate(int year, int month, int day) { + return month >= 1 && month <= 12 && day >= 1 && day <= getDaysInMonth(year, month); + } + + /** + * Get the year from a date value. + * + * @param x the date value + * @return the year + */ + public static int yearFromDateValue(long x) { + return (int) (x >>> SHIFT_YEAR); + } + + /** + * Get the month from a date value. + * + * @param x the date value + * @return the month (1..12) + */ + public static int monthFromDateValue(long x) { + return (int) (x >>> SHIFT_MONTH) & 15; + } + + /** + * Get the day of month from a date value. + * + * @param x the date value + * @return the day (1..31) + */ + public static int dayFromDateValue(long x) { + return (int) (x & 31); + } + + /** + * Get the date value from a given date. + * + * @param year the year + * @param month the month (1..12) + * @param day the day (1..31) + * @return the date value + */ + public static long dateValue(long year, int month, int day) { + return (year << SHIFT_YEAR) | (month << SHIFT_MONTH) | day; + } + + /** + * Get the date value from a given denormalized date with possible out of range + * values of month and/or day. Used after addition or subtraction month or years + * to (from) it to get a valid date. + * + * @param year + * the year + * @param month + * the month, if out of range month and year will be normalized + * @param day + * the day of the month, if out of range it will be saturated + * @return the date value + */ + public static long dateValueFromDenormalizedDate(long year, long month, int day) { + long mm1 = month - 1; + long yd = mm1 / 12; + if (mm1 < 0 && yd * 12 != mm1) { + yd--; + } + int y = (int) (year + yd); + int m = (int) (month - yd * 12); + if (day < 1) { + day = 1; + } else { + int max = getDaysInMonth(y, m); + if (day > max) { + day = max; + } + } + return dateValue(y, m, day); + } + + /** + * Convert a local seconds to an encoded date. + * + * @param localSeconds the seconds since 1970-01-01 + * @return the date value + */ + public static long dateValueFromLocalSeconds(long localSeconds) { + long absoluteDay = localSeconds / SECONDS_PER_DAY; + // Round toward negative infinity + if (localSeconds < 0 && (absoluteDay * SECONDS_PER_DAY != localSeconds)) { + absoluteDay--; + } + return dateValueFromAbsoluteDay(absoluteDay); + } + + /** + * Convert a time in seconds in local time to the nanoseconds since midnight. + * + * @param localSeconds the seconds since 1970-01-01 + * @return the nanoseconds + */ + public static long nanosFromLocalSeconds(long localSeconds) { + localSeconds %= SECONDS_PER_DAY; + if (localSeconds < 0) { + localSeconds += SECONDS_PER_DAY; + } + return localSeconds * NANOS_PER_SECOND; + } + + /** + * Calculate the normalized nanos of day. + * + * @param nanos the nanoseconds (may be negative or larger than one day) + * @return the nanos of day within a day + */ + public static long normalizeNanosOfDay(long nanos) { + nanos %= NANOS_PER_DAY; + if (nanos < 0) { + nanos += NANOS_PER_DAY; + } + return nanos; + } + + /** + * Calculate the absolute day for a January, 1 of the specified year. + * + * @param year + * the year + * @return the absolute day + */ + public static long absoluteDayFromYear(long year) { + long a = 365 * year - 719_528; + if (year >= 0) { + a += (year + 3) / 4 - (year + 99) / 100 + (year + 399) / 400; + } else { + a -= year / -4 - year / -100 + year / -400; + } + return a; + } + + /** + * Calculate the absolute day from an encoded date value. + * + * @param dateValue the date value + * @return the absolute day + */ + public static long absoluteDayFromDateValue(long dateValue) { + return absoluteDay(yearFromDateValue(dateValue), monthFromDateValue(dateValue), dayFromDateValue(dateValue)); + } + + /** + * Calculate the absolute day. + * + * @param y year + * @param m month + * @param d day + * @return the absolute day + */ + static long absoluteDay(long y, int m, int d) { + long a = absoluteDayFromYear(y) + (367 * m - 362) / 12 + d - 1; + if (m > 2) { + a--; + if ((y & 3) != 0 || (y % 100 == 0 && y % 400 != 0)) { + a--; + } + } + return a; + } + + /** + * Calculate the encoded date value from an absolute day. + * + * @param absoluteDay the absolute day + * @return the date value + */ + public static long dateValueFromAbsoluteDay(long absoluteDay) { + long d = absoluteDay + 719_468; + long a = 0; + if (d < 0) { + a = (d + 1) / 146_097 - 1; + d -= a * 146_097; + a *= 400; + } + long y = (400 * d + 591) / 146_097; + int day = (int) (d - (365 * y + y / 4 - y / 100 + y / 400)); + if (day < 0) { + y--; + day = (int) (d - (365 * y + y / 4 - y / 100 + y / 400)); + } + y += a; + int m = (day * 5 + 2) / 153; + day -= (m * 306 + 5) / 10 - 1; + if (m >= 10) { + y++; + m -= 12; + } + return dateValue(y, m + 3, day); + } + + /** + * Return the next date value. + * + * @param dateValue + * the date value + * @return the next date value + */ + public static long incrementDateValue(long dateValue) { + int day = dayFromDateValue(dateValue); + if (day < 28) { + return dateValue + 1; + } + int year = yearFromDateValue(dateValue); + int month = monthFromDateValue(dateValue); + if (day < getDaysInMonth(year, month)) { + return dateValue + 1; + } + if (month < 12) { + month++; + } else { + month = 1; + year++; + } + return dateValue(year, month, 1); + } + + /** + * Return the previous date value. + * + * @param dateValue + * the date value + * @return the previous date value + */ + public static long decrementDateValue(long dateValue) { + if (dayFromDateValue(dateValue) > 1) { + return dateValue - 1; + } + int year = yearFromDateValue(dateValue); + int month = monthFromDateValue(dateValue); + if (month > 1) { + month--; + } else { + month = 12; + year--; + } + return dateValue(year, month, getDaysInMonth(year, month)); + } + + /** + * Append a date to the string builder. + * + * @param builder the target string builder + * @param dateValue the date value + * @return the specified string builder + */ + public static StringBuilder appendDate(StringBuilder builder, long dateValue) { + int y = yearFromDateValue(dateValue); + if (y < 1_000 && y > -1_000) { + if (y < 0) { + builder.append('-'); + y = -y; + } + StringUtils.appendZeroPadded(builder, 4, y); + } else { + builder.append(y); + } + StringUtils.appendTwoDigits(builder.append('-'), monthFromDateValue(dateValue)).append('-'); + return StringUtils.appendTwoDigits(builder, dayFromDateValue(dateValue)); + } + + /** + * Append a time to the string builder. + * + * @param builder the target string builder + * @param nanos the time in nanoseconds + * @return the specified string builder + */ + public static StringBuilder appendTime(StringBuilder builder, long nanos) { + if (nanos < 0) { + builder.append('-'); + nanos = -nanos; + } + /* + * nanos now either in range from 0 to Long.MAX_VALUE or equals to + * Long.MIN_VALUE. We need to divide nanos by 1,000,000,000 with + * unsigned division to get correct result. The simplest way to do this + * with such constraints is to divide -nanos by -1,000,000,000. + */ + long s = -nanos / -1_000_000_000; + nanos -= s * 1_000_000_000; + int m = (int) (s / 60); + s -= m * 60; + int h = m / 60; + m -= h * 60; + StringUtils.appendTwoDigits(builder, h).append(':'); + StringUtils.appendTwoDigits(builder, m).append(':'); + StringUtils.appendTwoDigits(builder, (int) s); + return appendNanos(builder, (int) nanos); + } + + /** + * Append nanoseconds of time, if any. + * + * @param builder string builder to append to + * @param nanos nanoseconds of second + * @return the specified string builder + */ + static StringBuilder appendNanos(StringBuilder builder, int nanos) { + if (nanos > 0) { + builder.append('.'); + for (int i = 1; nanos < FRACTIONAL_SECONDS_TABLE[i]; i++) { + builder.append('0'); + } + if (nanos % 1_000 == 0) { + nanos /= 1_000; + if (nanos % 1_000 == 0) { + nanos /= 1_000; + } + } + if (nanos % 10 == 0) { + nanos /= 10; + if (nanos % 10 == 0) { + nanos /= 10; + } + } + builder.append(nanos); + } + return builder; + } + + /** + * Append a time zone to the string builder. + * + * @param builder the target string builder + * @param tz the time zone offset in seconds + * @return the specified string builder + */ + public static StringBuilder appendTimeZone(StringBuilder builder, int tz) { + if (tz < 0) { + builder.append('-'); + tz = -tz; + } else { + builder.append('+'); + } + int rem = tz / 3_600; + StringUtils.appendTwoDigits(builder, rem); + tz -= rem * 3_600; + if (tz != 0) { + rem = tz / 60; + StringUtils.appendTwoDigits(builder.append(':'), rem); + tz -= rem * 60; + if (tz != 0) { + StringUtils.appendTwoDigits(builder.append(':'), tz); + } + } + return builder; + } + + /** + * Generates time zone name for the specified offset in seconds. + * + * @param offsetSeconds + * time zone offset in seconds + * @return time zone name + */ + public static String timeZoneNameFromOffsetSeconds(int offsetSeconds) { + if (offsetSeconds == 0) { + return "UTC"; + } + StringBuilder b = new StringBuilder(12); + b.append("GMT"); + if (offsetSeconds < 0) { + b.append('-'); + offsetSeconds = -offsetSeconds; + } else { + b.append('+'); + } + StringUtils.appendTwoDigits(b, offsetSeconds / 3_600).append(':'); + offsetSeconds %= 3_600; + StringUtils.appendTwoDigits(b, offsetSeconds / 60); + offsetSeconds %= 60; + if (offsetSeconds != 0) { + b.append(':'); + StringUtils.appendTwoDigits(b, offsetSeconds); + } + return b.toString(); + } + + + /** + * Converts scale of nanoseconds. + * + * @param nanosOfDay nanoseconds of day + * @param scale fractional seconds precision + * @param range the allowed range of values (0..range-1) + * @return scaled value + */ + public static long convertScale(long nanosOfDay, int scale, long range) { + if (scale >= 9) { + return nanosOfDay; + } + int m = FRACTIONAL_SECONDS_TABLE[scale]; + long mod = nanosOfDay % m; + if (mod >= m >>> 1) { + nanosOfDay += m; + } + long r = nanosOfDay - mod; + if (r >= range) { + r = range - m; + } + return r; + } + + /** + * Moves timestamp with time zone to a new time zone. + * + * @param dateValue the date value + * @param timeNanos the nanoseconds since midnight + * @param oldOffset old offset + * @param newOffset new offset + * @return timestamp with time zone with new offset + */ + public static ValueTimestampTimeZone timestampTimeZoneAtOffset(long dateValue, long timeNanos, int oldOffset, + int newOffset) { + timeNanos += (newOffset - oldOffset) * DateTimeUtils.NANOS_PER_SECOND; + // Value can be 18+18 hours before or after the limit + if (timeNanos < 0) { + timeNanos += DateTimeUtils.NANOS_PER_DAY; + dateValue = DateTimeUtils.decrementDateValue(dateValue); + if (timeNanos < 0) { + timeNanos += DateTimeUtils.NANOS_PER_DAY; + dateValue = DateTimeUtils.decrementDateValue(dateValue); + } + } else if (timeNanos >= DateTimeUtils.NANOS_PER_DAY) { + timeNanos -= DateTimeUtils.NANOS_PER_DAY; + dateValue = DateTimeUtils.incrementDateValue(dateValue); + if (timeNanos >= DateTimeUtils.NANOS_PER_DAY) { + timeNanos -= DateTimeUtils.NANOS_PER_DAY; + dateValue = DateTimeUtils.incrementDateValue(dateValue); + } + } + return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, timeNanos, newOffset); + } + +} diff --git a/h2/src/main/org/h2/util/DbDriverActivator.java b/h2/src/main/org/h2/util/DbDriverActivator.java new file mode 100644 index 0000000..cf388f6 --- /dev/null +++ b/h2/src/main/org/h2/util/DbDriverActivator.java @@ -0,0 +1,52 @@ +/* + * 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.util; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +/** + * The driver activator loads the H2 driver when starting the bundle. The driver + * is unloaded when stopping the bundle. + */ +public class DbDriverActivator implements BundleActivator { + + private static final String DATASOURCE_FACTORY_CLASS = + "org.osgi.service.jdbc.DataSourceFactory"; + + /** + * Start the bundle. If the 'org.osgi.service.jdbc.DataSourceFactory' class + * is available in the class path, this will load the database driver and + * register the DataSourceFactory service. + * + * @param bundleContext the bundle context + */ + @Override + public void start(BundleContext bundleContext) { + org.h2.Driver driver = org.h2.Driver.load(); + try { + JdbcUtils.loadUserClass(DATASOURCE_FACTORY_CLASS); + } catch (Exception e) { + // class not found - don't register + return; + } + // but don't ignore exceptions in this call + OsgiDataSourceFactory.registerService(bundleContext, driver); + } + + /** + * Stop the bundle. This will unload the database driver. The + * DataSourceFactory service is implicitly un-registered by the OSGi + * framework. + * + * @param bundleContext the bundle context + */ + @Override + public void stop(BundleContext bundleContext) { + org.h2.Driver.unload(); + } + +} diff --git a/h2/src/main/org/h2/util/DebuggingThreadLocal.java b/h2/src/main/org/h2/util/DebuggingThreadLocal.java new file mode 100644 index 0000000..9413de4 --- /dev/null +++ b/h2/src/main/org/h2/util/DebuggingThreadLocal.java @@ -0,0 +1,45 @@ +/* + * 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.util; + +import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Similar to ThreadLocal, except that it allows its data to be read from other + * threads - useful for debugging info. + * + * @param the type + */ +public class DebuggingThreadLocal { + + private final ConcurrentHashMap map = new ConcurrentHashMap<>(); + + public void set(T value) { + map.put(Thread.currentThread().getId(), value); + } + + /** + * Remove the value for the current thread. + */ + public void remove() { + map.remove(Thread.currentThread().getId()); + } + + public T get() { + return map.get(Thread.currentThread().getId()); + } + + /** + * Get a snapshot of the data of all threads. + * + * @return a HashMap containing a mapping from thread-id to value + */ + public HashMap getSnapshotOfAllThreads() { + return new HashMap<>(map); + } + +} diff --git a/h2/src/main/org/h2/util/HasSQL.java b/h2/src/main/org/h2/util/HasSQL.java new file mode 100644 index 0000000..a57716c --- /dev/null +++ b/h2/src/main/org/h2/util/HasSQL.java @@ -0,0 +1,76 @@ +/* + * 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.util; + +/** + * An object that has an SQL representation. + */ +public interface HasSQL { + + /** + * Quote identifiers only when it is strictly required (different case or + * identifier is also a keyword). + */ + int QUOTE_ONLY_WHEN_REQUIRED = 1; + + /** + * Replace long LOB values with some generated values. + */ + int REPLACE_LOBS_FOR_TRACE = 2; + + /** + * Don't add casts around literals. + */ + int NO_CASTS = 4; + + /** + * Add execution plan information. + */ + int ADD_PLAN_INFORMATION = 8; + + /** + * Default flags. + */ + int DEFAULT_SQL_FLAGS = 0; + + /** + * Combined flags for trace. + */ + int TRACE_SQL_FLAGS = QUOTE_ONLY_WHEN_REQUIRED | REPLACE_LOBS_FOR_TRACE; + + /** + * Get a medium size SQL expression for debugging or tracing. + * + * @return the SQL expression + */ + default String getTraceSQL() { + return getSQL(TRACE_SQL_FLAGS); + } + + /** + * Get the SQL statement of this expression. This may not always be the + * original SQL statement, specially after optimization. + * + * @param sqlFlags + * formatting flags + * @return the SQL statement + */ + default String getSQL(int sqlFlags) { + return getSQL(new StringBuilder(), sqlFlags).toString(); + } + + /** + * Appends the SQL statement of this object to the specified builder. + * + * @param builder + * string builder + * @param sqlFlags + * formatting flags + * @return the specified string builder + */ + StringBuilder getSQL(StringBuilder builder, int sqlFlags); + +} diff --git a/h2/src/main/org/h2/util/IOUtils.java b/h2/src/main/org/h2/util/IOUtils.java new file mode 100644 index 0000000..8a131a3 --- /dev/null +++ b/h2/src/main/org/h2/util/IOUtils.java @@ -0,0 +1,423 @@ +/* + * 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.util; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; + +import org.h2.engine.Constants; +import org.h2.engine.SysProperties; +import org.h2.mvstore.DataUtils; +import org.h2.store.fs.FileUtils; + +/** + * This utility class contains input/output functions. + */ +public class IOUtils { + + private IOUtils() { + // utility class + } + + /** + * Close an AutoCloseable without throwing an exception. + * + * @param out the AutoCloseable or null + */ + public static void closeSilently(AutoCloseable out) { + if (out != null) { + try { + trace("closeSilently", null, out); + out.close(); + } catch (Exception e) { + // ignore + } + } + } + + /** + * Skip a number of bytes in an input stream. + * + * @param in the input stream + * @param skip the number of bytes to skip + * @throws EOFException if the end of file has been reached before all bytes + * could be skipped + * @throws IOException if an IO exception occurred while skipping + */ + public static void skipFully(InputStream in, long skip) throws IOException { + try { + while (skip > 0) { + long skipped = in.skip(skip); + if (skipped <= 0) { + throw new EOFException(); + } + skip -= skipped; + } + } catch (Exception e) { + throw DataUtils.convertToIOException(e); + } + } + + /** + * Skip a number of characters in a reader. + * + * @param reader the reader + * @param skip the number of characters to skip + * @throws EOFException if the end of file has been reached before all + * characters could be skipped + * @throws IOException if an IO exception occurred while skipping + */ + public static void skipFully(Reader reader, long skip) throws IOException { + try { + while (skip > 0) { + long skipped = reader.skip(skip); + if (skipped <= 0) { + throw new EOFException(); + } + skip -= skipped; + } + } catch (Exception e) { + throw DataUtils.convertToIOException(e); + } + } + + /** + * Copy all data from the input stream to the output stream and close both + * streams. Exceptions while closing are ignored. + * + * @param in the input stream + * @param out the output stream + * @return the number of bytes copied + * @throws IOException on failure + */ + public static long copyAndClose(InputStream in, OutputStream out) + throws IOException { + try { + long len = copyAndCloseInput(in, out); + out.close(); + return len; + } catch (Exception e) { + throw DataUtils.convertToIOException(e); + } finally { + closeSilently(out); + } + } + + /** + * Copy all data from the input stream to the output stream and close the + * input stream. Exceptions while closing are ignored. + * + * @param in the input stream + * @param out the output stream (null if writing is not required) + * @return the number of bytes copied + * @throws IOException on failure + */ + public static long copyAndCloseInput(InputStream in, OutputStream out) + throws IOException { + try { + return copy(in, out); + } catch (Exception e) { + throw DataUtils.convertToIOException(e); + } finally { + closeSilently(in); + } + } + + /** + * Copy all data from the input stream to the output stream. Both streams + * are kept open. + * + * @param in the input stream + * @param out the output stream (null if writing is not required) + * @return the number of bytes copied + * @throws IOException on failure + */ + public static long copy(InputStream in, OutputStream out) + throws IOException { + return copy(in, out, Long.MAX_VALUE); + } + + /** + * Copy all data from the input stream to the output stream. Both streams + * are kept open. + * + * @param in the input stream + * @param out the output stream (null if writing is not required) + * @param length the maximum number of bytes to copy + * @return the number of bytes copied + * @throws IOException on failure + */ + public static long copy(InputStream in, OutputStream out, long length) + throws IOException { + try { + long copied = 0; + int len = (int) Math.min(length, Constants.IO_BUFFER_SIZE); + byte[] buffer = new byte[len]; + while (length > 0) { + len = in.read(buffer, 0, len); + if (len < 0) { + break; + } + if (out != null) { + out.write(buffer, 0, len); + } + copied += len; + length -= len; + len = (int) Math.min(length, Constants.IO_BUFFER_SIZE); + } + return copied; + } catch (Exception e) { + throw DataUtils.convertToIOException(e); + } + } + + /** + * Copy all data from the reader to the writer and close the reader. + * Exceptions while closing are ignored. + * + * @param in the reader + * @param out the writer (null if writing is not required) + * @param length the maximum number of bytes to copy + * @return the number of characters copied + * @throws IOException on failure + */ + public static long copyAndCloseInput(Reader in, Writer out, long length) + throws IOException { + try { + long copied = 0; + int len = (int) Math.min(length, Constants.IO_BUFFER_SIZE); + char[] buffer = new char[len]; + while (length > 0) { + len = in.read(buffer, 0, len); + if (len < 0) { + break; + } + if (out != null) { + out.write(buffer, 0, len); + } + copied += len; + length -= len; + len = (int) Math.min(length, Constants.IO_BUFFER_SIZE); + } + return copied; + } catch (Exception e) { + throw DataUtils.convertToIOException(e); + } finally { + in.close(); + } + } + + /** + * Read a number of bytes from an input stream and close the stream. + * + * @param in the input stream + * @param length the maximum number of bytes to read, or -1 to read until + * the end of file + * @return the bytes read + * @throws IOException on failure + */ + public static byte[] readBytesAndClose(InputStream in, int length) + throws IOException { + try { + if (length <= 0) { + length = Integer.MAX_VALUE; + } + int block = Math.min(Constants.IO_BUFFER_SIZE, length); + ByteArrayOutputStream out = new ByteArrayOutputStream(block); + copy(in, out, length); + return out.toByteArray(); + } catch (Exception e) { + throw DataUtils.convertToIOException(e); + } finally { + in.close(); + } + } + + /** + * Read a number of characters from a reader and close it. + * + * @param in the reader + * @param length the maximum number of characters to read, or -1 to read + * until the end of file + * @return the string read + * @throws IOException on failure + */ + public static String readStringAndClose(Reader in, int length) + throws IOException { + try { + if (length <= 0) { + length = Integer.MAX_VALUE; + } + int block = Math.min(Constants.IO_BUFFER_SIZE, length); + StringWriter out = new StringWriter(block); + copyAndCloseInput(in, out, length); + return out.toString(); + } finally { + in.close(); + } + } + + /** + * Try to read the given number of bytes to the buffer. This method reads + * until the maximum number of bytes have been read or until the end of + * file. + * + * @param in the input stream + * @param buffer the output buffer + * @param max the number of bytes to read at most + * @return the number of bytes read, 0 meaning EOF + * @throws IOException on failure + */ + public static int readFully(InputStream in, byte[] buffer, int max) + throws IOException { + try { + int result = 0, len = Math.min(max, buffer.length); + while (len > 0) { + int l = in.read(buffer, result, len); + if (l < 0) { + break; + } + result += l; + len -= l; + } + return result; + } catch (Exception e) { + throw DataUtils.convertToIOException(e); + } + } + + /** + * Try to read the given number of characters to the buffer. This method + * reads until the maximum number of characters have been read or until the + * end of file. + * + * @param in the reader + * @param buffer the output buffer + * @param max the number of characters to read at most + * @return the number of characters read, 0 meaning EOF + * @throws IOException on failure + */ + public static int readFully(Reader in, char[] buffer, int max) + throws IOException { + try { + int result = 0, len = Math.min(max, buffer.length); + while (len > 0) { + int l = in.read(buffer, result, len); + if (l < 0) { + break; + } + result += l; + len -= l; + } + return result; + } catch (Exception e) { + throw DataUtils.convertToIOException(e); + } + } + + /** + * Create a reader to read from an input stream using the UTF-8 format. If + * the input stream is null, this method returns null. The InputStreamReader + * that is used here is not exact, that means it may read some additional + * bytes when buffering. + * + * @param in the input stream or null + * @return the reader + */ + public static Reader getReader(InputStream in) { + // InputStreamReader may read some more bytes + return in == null ? null : new BufferedReader( + new InputStreamReader(in, StandardCharsets.UTF_8)); + } + + /** + * Create a buffered writer to write to an output stream using the UTF-8 + * format. If the output stream is null, this method returns null. + * + * @param out the output stream or null + * @return the writer + */ + public static Writer getBufferedWriter(OutputStream out) { + return out == null ? null : new BufferedWriter( + new OutputStreamWriter(out, StandardCharsets.UTF_8)); + } + + /** + * Wrap an input stream in a reader. The bytes are converted to characters + * using the US-ASCII character set. + * + * @param in the input stream + * @return the reader + */ + public static Reader getAsciiReader(InputStream in) { + return in == null ? null : new InputStreamReader(in, StandardCharsets.US_ASCII); + } + + /** + * Trace input or output operations if enabled. + * + * @param method the method from where this method was called + * @param fileName the file name + * @param o the object to append to the message + */ + public static void trace(String method, String fileName, Object o) { + if (SysProperties.TRACE_IO) { + System.out.println("IOUtils." + method + ' ' + fileName + ' ' + o); + } + } + + /** + * Create an input stream to read from a string. The string is converted to + * a byte array using UTF-8 encoding. + * If the string is null, this method returns null. + * + * @param s the string + * @return the input stream + */ + public static InputStream getInputStreamFromString(String s) { + if (s == null) { + return null; + } + return new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8)); + } + + /** + * Copy a file from one directory to another, or to another file. + * + * @param original the original file name + * @param copy the file name of the copy + * @throws IOException on failure + */ + public static void copyFiles(String original, String copy) throws IOException { + InputStream in = FileUtils.newInputStream(original); + OutputStream out = FileUtils.newOutputStream(copy, false); + copyAndClose(in, out); + } + + /** + * Converts / and \ name separators in path to native separators. + * + * @param path path to convert + * @return path with converted separators + */ + public static String nameSeparatorsToNative(String path) { + return File.separatorChar == '/' ? path.replace('\\', '/') : path.replace('/', '\\'); + } + +} diff --git a/h2/src/main/org/h2/util/IntArray.java b/h2/src/main/org/h2/util/IntArray.java new file mode 100644 index 0000000..50dce12 --- /dev/null +++ b/h2/src/main/org/h2/util/IntArray.java @@ -0,0 +1,170 @@ +/* + * 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.util; + +import java.util.Arrays; + +/** + * An array with integer element. + */ +public class IntArray { + + private int[] data; + private int size; + private int hash; + + /** + * Create an int array with the default initial capacity. + */ + public IntArray() { + this(10); + } + + /** + * Create an int array with specified initial capacity. + * + * @param capacity the initial capacity + */ + public IntArray(int capacity) { + data = new int[capacity]; + } + + /** + * Create an int array with the given values and size. + * + * @param data the int array + */ + public IntArray(int[] data) { + this.data = data; + size = data.length; + } + + /** + * Append a value. + * + * @param value the value to append + */ + public void add(int value) { + if (size >= data.length) { + ensureCapacity(size + size); + } + data[size++] = value; + } + + /** + * Get the value at the given index. + * + * @param index the index + * @return the value + */ + public int get(int index) { + if (index >= size) { + throw new ArrayIndexOutOfBoundsException("i=" + index + " size=" + size); + } + return data[index]; + } + + /** + * Remove the value at the given index. + * + * @param index the index + */ + public void remove(int index) { + if (index >= size) { + throw new ArrayIndexOutOfBoundsException("i=" + index + " size=" + size); + } + System.arraycopy(data, index + 1, data, index, size - index - 1); + size--; + } + + /** + * Ensure the underlying array is large enough for the given number of + * entries. + * + * @param minCapacity the minimum capacity + */ + public void ensureCapacity(int minCapacity) { + minCapacity = Math.max(4, minCapacity); + if (minCapacity >= data.length) { + data = Arrays.copyOf(data, minCapacity); + } + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof IntArray)) { + return false; + } + IntArray other = (IntArray) obj; + if (hashCode() != other.hashCode() || size != other.size) { + return false; + } + for (int i = 0; i < size; i++) { + if (data[i] != other.data[i]) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + if (hash != 0) { + return hash; + } + int h = size + 1; + for (int i = 0; i < size; i++) { + h = h * 31 + data[i]; + } + hash = h; + return h; + } + + /** + * Get the size of the list. + * + * @return the size + */ + public int size() { + return size; + } + + /** + * Convert this list to an array. The target array must be big enough. + * + * @param array the target array + */ + public void toArray(int[] array) { + System.arraycopy(data, 0, array, 0, size); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder("{"); + for (int i = 0; i < size; i++) { + if (i > 0) { + builder.append(", "); + } + builder.append(data[i]); + } + return builder.append('}').toString(); + } + + /** + * Remove a number of elements. + * + * @param fromIndex the index of the first item to remove + * @param toIndex upper bound (exclusive) + */ + public void removeRange(int fromIndex, int toIndex) { + if (fromIndex > toIndex || toIndex > size) { + throw new ArrayIndexOutOfBoundsException("from=" + fromIndex + " to=" + toIndex + " size=" + size); + } + System.arraycopy(data, toIndex, data, fromIndex, size - toIndex); + size -= toIndex - fromIndex; + } + +} diff --git a/h2/src/main/org/h2/util/IntervalUtils.java b/h2/src/main/org/h2/util/IntervalUtils.java new file mode 100644 index 0000000..1761f91 --- /dev/null +++ b/h2/src/main/org/h2/util/IntervalUtils.java @@ -0,0 +1,856 @@ +/* + * 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.util; + +import static org.h2.util.DateTimeUtils.NANOS_PER_DAY; +import static org.h2.util.DateTimeUtils.NANOS_PER_HOUR; +import static org.h2.util.DateTimeUtils.NANOS_PER_MINUTE; +import static org.h2.util.DateTimeUtils.NANOS_PER_SECOND; + +import java.math.BigInteger; + +import org.h2.api.ErrorCode; +import org.h2.api.IntervalQualifier; +import org.h2.message.DbException; +import org.h2.value.ValueInterval; + +/** + * This utility class contains interval conversion functions. + */ +public class IntervalUtils { + + private static final BigInteger NANOS_PER_SECOND_BI = BigInteger.valueOf(NANOS_PER_SECOND); + + private static final BigInteger NANOS_PER_MINUTE_BI = BigInteger.valueOf(NANOS_PER_MINUTE); + + private static final BigInteger NANOS_PER_HOUR_BI = BigInteger.valueOf(NANOS_PER_HOUR); + + /** + * The number of nanoseconds per day as BigInteger. + */ + public static final BigInteger NANOS_PER_DAY_BI = BigInteger.valueOf(NANOS_PER_DAY); + + private static final BigInteger MONTHS_PER_YEAR_BI = BigInteger.valueOf(12); + + private static final BigInteger HOURS_PER_DAY_BI = BigInteger.valueOf(24); + + private static final BigInteger MINUTES_PER_DAY_BI = BigInteger.valueOf(24 * 60); + + private static final BigInteger MINUTES_PER_HOUR_BI = BigInteger.valueOf(60); + + private static final BigInteger LEADING_MIN = BigInteger.valueOf(-999_999_999_999_999_999L); + + private static final BigInteger LEADING_MAX = BigInteger.valueOf(999_999_999_999_999_999L); + + private IntervalUtils() { + // utility class + } + + /** + * Parses the specified string as {@code INTERVAL} value. + * + * @param qualifier + * the default qualifier to use if string does not have one + * @param s + * the string with type information to parse + * @return the interval value. Type of value can be different from the + * specified qualifier. + */ + public static ValueInterval parseFormattedInterval(IntervalQualifier qualifier, String s) { + int i = 0; + i = skipWS(s, i); + if (!s.regionMatches(true, i, "INTERVAL", 0, 8)) { + return parseInterval(qualifier, false, s); + } + i = skipWS(s, i + 8); + boolean negative = false; + char ch = s.charAt(i); + if (ch == '-') { + negative = true; + i = skipWS(s, i + 1); + ch = s.charAt(i); + } else if (ch == '+') { + i = skipWS(s, i + 1); + ch = s.charAt(i); + } + if (ch != '\'') { + throw new IllegalArgumentException(s); + } + int start = ++i; + int l = s.length(); + for (;;) { + if (i == l) { + throw new IllegalArgumentException(s); + } + if (s.charAt(i) == '\'') { + break; + } + i++; + } + String v = s.substring(start, i); + i = skipWS(s, i + 1); + if (s.regionMatches(true, i, "YEAR", 0, 4)) { + i += 4; + int j = skipWSEnd(s, i); + if (j == l) { + return parseInterval(IntervalQualifier.YEAR, negative, v); + } + if (j > i && s.regionMatches(true, j, "TO", 0, 2)) { + j += 2; + i = skipWS(s, j); + if (i > j && s.regionMatches(true, i, "MONTH", 0, 5)) { + if (skipWSEnd(s, i + 5) == l) { + return parseInterval(IntervalQualifier.YEAR_TO_MONTH, negative, v); + } + } + } + } else if (s.regionMatches(true, i, "MONTH", 0, 5)) { + if (skipWSEnd(s, i + 5) == l) { + return parseInterval(IntervalQualifier.MONTH, negative, v); + } + } + if (s.regionMatches(true, i, "DAY", 0, 3)) { + i += 3; + int j = skipWSEnd(s, i); + if (j == l) { + return parseInterval(IntervalQualifier.DAY, negative, v); + } + if (j > i && s.regionMatches(true, j, "TO", 0, 2)) { + j += 2; + i = skipWS(s, j); + if (i > j) { + if (s.regionMatches(true, i, "HOUR", 0, 4)) { + if (skipWSEnd(s, i + 4) == l) { + return parseInterval(IntervalQualifier.DAY_TO_HOUR, negative, v); + } + } else if (s.regionMatches(true, i, "MINUTE", 0, 6)) { + if (skipWSEnd(s, i + 6) == l) { + return parseInterval(IntervalQualifier.DAY_TO_MINUTE, negative, v); + } + } else if (s.regionMatches(true, i, "SECOND", 0, 6)) { + if (skipWSEnd(s, i + 6) == l) { + return parseInterval(IntervalQualifier.DAY_TO_SECOND, negative, v); + } + } + } + } + } + if (s.regionMatches(true, i, "HOUR", 0, 4)) { + i += 4; + int j = skipWSEnd(s, i); + if (j == l) { + return parseInterval(IntervalQualifier.HOUR, negative, v); + } + if (j > i && s.regionMatches(true, j, "TO", 0, 2)) { + j += 2; + i = skipWS(s, j); + if (i > j) { + if (s.regionMatches(true, i, "MINUTE", 0, 6)) { + if (skipWSEnd(s, i + 6) == l) { + return parseInterval(IntervalQualifier.HOUR_TO_MINUTE, negative, v); + } + } else if (s.regionMatches(true, i, "SECOND", 0, 6)) { + if (skipWSEnd(s, i + 6) == l) { + return parseInterval(IntervalQualifier.HOUR_TO_SECOND, negative, v); + } + } + } + } + } + if (s.regionMatches(true, i, "MINUTE", 0, 6)) { + i += 6; + int j = skipWSEnd(s, i); + if (j == l) { + return parseInterval(IntervalQualifier.MINUTE, negative, v); + } + if (j > i && s.regionMatches(true, j, "TO", 0, 2)) { + j += 2; + i = skipWS(s, j); + if (i > j && s.regionMatches(true, i, "SECOND", 0, 6)) { + if (skipWSEnd(s, i + 6) == l) { + return parseInterval(IntervalQualifier.MINUTE_TO_SECOND, negative, v); + } + } + } + } + if (s.regionMatches(true, i, "SECOND", 0, 6)) { + if (skipWSEnd(s, i + 6) == l) { + return parseInterval(IntervalQualifier.SECOND, negative, v); + } + } + throw new IllegalArgumentException(s); + } + + private static int skipWS(String s, int i) { + for (int l = s.length();; i++) { + if (i == l) { + throw new IllegalArgumentException(s); + } + if (!Character.isWhitespace(s.charAt(i))) { + return i; + } + } + } + + private static int skipWSEnd(String s, int i) { + for (int l = s.length();; i++) { + if (i == l) { + return i; + } + if (!Character.isWhitespace(s.charAt(i))) { + return i; + } + } + } + + /** + * Parses the specified string as {@code INTERVAL} value. + * + * @param qualifier + * the qualifier of interval + * @param negative + * whether the interval is negative + * @param s + * the string to parse + * @return the interval value + */ + public static ValueInterval parseInterval(IntervalQualifier qualifier, boolean negative, String s) { + long leading, remaining; + switch (qualifier) { + case YEAR: + case MONTH: + case DAY: + case HOUR: + case MINUTE: + leading = parseIntervalLeading(s, 0, s.length(), negative); + remaining = 0; + break; + case SECOND: { + int dot = s.indexOf('.'); + if (dot < 0) { + leading = parseIntervalLeading(s, 0, s.length(), negative); + remaining = 0; + } else { + leading = parseIntervalLeading(s, 0, dot, negative); + remaining = DateTimeUtils.parseNanos(s, dot + 1, s.length()); + } + break; + } + case YEAR_TO_MONTH: + return parseInterval2(qualifier, s, '-', 11, negative); + case DAY_TO_HOUR: + return parseInterval2(qualifier, s, ' ', 23, negative); + case DAY_TO_MINUTE: { + int space = s.indexOf(' '); + if (space < 0) { + leading = parseIntervalLeading(s, 0, s.length(), negative); + remaining = 0; + } else { + leading = parseIntervalLeading(s, 0, space, negative); + int colon = s.indexOf(':', space + 1); + if (colon < 0) { + remaining = parseIntervalRemaining(s, space + 1, s.length(), 23) * 60; + } else { + remaining = parseIntervalRemaining(s, space + 1, colon, 23) * 60 + + parseIntervalRemaining(s, colon + 1, s.length(), 59); + } + } + break; + } + case DAY_TO_SECOND: { + int space = s.indexOf(' '); + if (space < 0) { + leading = parseIntervalLeading(s, 0, s.length(), negative); + remaining = 0; + } else { + leading = parseIntervalLeading(s, 0, space, negative); + int colon = s.indexOf(':', space + 1); + if (colon < 0) { + remaining = parseIntervalRemaining(s, space + 1, s.length(), 23) * NANOS_PER_HOUR; + } else { + int colon2 = s.indexOf(':', colon + 1); + if (colon2 < 0) { + remaining = parseIntervalRemaining(s, space + 1, colon, 23) * NANOS_PER_HOUR + + parseIntervalRemaining(s, colon + 1, s.length(), 59) * NANOS_PER_MINUTE; + } else { + remaining = parseIntervalRemaining(s, space + 1, colon, 23) * NANOS_PER_HOUR + + parseIntervalRemaining(s, colon + 1, colon2, 59) * NANOS_PER_MINUTE + + parseIntervalRemainingSeconds(s, colon2 + 1); + } + } + } + break; + } + case HOUR_TO_MINUTE: + return parseInterval2(qualifier, s, ':', 59, negative); + case HOUR_TO_SECOND: { + int colon = s.indexOf(':'); + if (colon < 0) { + leading = parseIntervalLeading(s, 0, s.length(), negative); + remaining = 0; + } else { + leading = parseIntervalLeading(s, 0, colon, negative); + int colon2 = s.indexOf(':', colon + 1); + if (colon2 < 0) { + remaining = parseIntervalRemaining(s, colon + 1, s.length(), 59) * NANOS_PER_MINUTE; + } else { + remaining = parseIntervalRemaining(s, colon + 1, colon2, 59) * NANOS_PER_MINUTE + + parseIntervalRemainingSeconds(s, colon2 + 1); + } + } + break; + } + case MINUTE_TO_SECOND: { + int dash = s.indexOf(':'); + if (dash < 0) { + leading = parseIntervalLeading(s, 0, s.length(), negative); + remaining = 0; + } else { + leading = parseIntervalLeading(s, 0, dash, negative); + remaining = parseIntervalRemainingSeconds(s, dash + 1); + } + break; + } + default: + throw new IllegalArgumentException(); + } + negative = leading < 0; + if (negative) { + if (leading != Long.MIN_VALUE) { + leading = -leading; + } else { + leading = 0; + } + } + return ValueInterval.from(qualifier, negative, leading, remaining); + } + + private static ValueInterval parseInterval2(IntervalQualifier qualifier, String s, + char ch, int max, boolean negative) { + long leading; + long remaining; + int dash = s.indexOf(ch, 1); + if (dash < 0) { + leading = parseIntervalLeading(s, 0, s.length(), negative); + remaining = 0; + } else { + leading = parseIntervalLeading(s, 0, dash, negative); + remaining = parseIntervalRemaining(s, dash + 1, s.length(), max); + } + negative = leading < 0; + if (negative) { + if (leading != Long.MIN_VALUE) { + leading = -leading; + } else { + leading = 0; + } + } + return ValueInterval.from(qualifier, negative, leading, remaining); + } + + private static long parseIntervalLeading(String s, int start, int end, boolean negative) { + long leading = Long.parseLong(s.substring(start, end)); + if (leading == 0) { + return negative ^ s.charAt(start) == '-' ? Long.MIN_VALUE : 0; + } + return negative ? -leading : leading; + } + + private static long parseIntervalRemaining(String s, int start, int end, int max) { + int v = StringUtils.parseUInt31(s, start, end); + if (v > max) { + throw new IllegalArgumentException(s); + } + return v; + } + + private static long parseIntervalRemainingSeconds(String s, int start) { + int seconds, nanos; + int dot = s.indexOf('.', start + 1); + if (dot < 0) { + seconds = StringUtils.parseUInt31(s, start, s.length()); + nanos = 0; + } else { + seconds = StringUtils.parseUInt31(s, start, dot); + nanos = DateTimeUtils.parseNanos(s, dot + 1, s.length()); + } + if (seconds > 59) { + throw new IllegalArgumentException(s); + } + return seconds * NANOS_PER_SECOND + nanos; + } + + /** + * Formats interval as a string and appends it to a specified string + * builder. + * + * @param buff + * string builder to append to + * @param qualifier + * qualifier of the interval + * @param negative + * whether interval is negative + * @param leading + * the value of leading field + * @param remaining + * the value of all remaining fields + * @return the specified string builder + */ + public static StringBuilder appendInterval(StringBuilder buff, IntervalQualifier qualifier, boolean negative, + long leading, long remaining) { + buff.append("INTERVAL '"); + if (negative) { + buff.append('-'); + } + switch (qualifier) { + case YEAR: + case MONTH: + case DAY: + case HOUR: + case MINUTE: + buff.append(leading); + break; + case SECOND: + DateTimeUtils.appendNanos(buff.append(leading), (int) remaining); + break; + case YEAR_TO_MONTH: + buff.append(leading).append('-').append(remaining); + break; + case DAY_TO_HOUR: + buff.append(leading).append(' '); + StringUtils.appendTwoDigits(buff, (int) remaining); + break; + case DAY_TO_MINUTE: { + buff.append(leading).append(' '); + int r = (int) remaining; + StringUtils.appendTwoDigits(buff, r / 60).append(':'); + StringUtils.appendTwoDigits(buff, r % 60); + break; + } + case DAY_TO_SECOND: { + long nanos = remaining % NANOS_PER_MINUTE; + int r = (int) (remaining / NANOS_PER_MINUTE); + buff.append(leading).append(' '); + StringUtils.appendTwoDigits(buff, r / 60).append(':'); + StringUtils.appendTwoDigits(buff, r % 60).append(':'); + StringUtils.appendTwoDigits(buff, (int) (nanos / NANOS_PER_SECOND)); + DateTimeUtils.appendNanos(buff, (int) (nanos % NANOS_PER_SECOND)); + break; + } + case HOUR_TO_MINUTE: + buff.append(leading).append(':'); + StringUtils.appendTwoDigits(buff, (int) remaining); + break; + case HOUR_TO_SECOND: { + buff.append(leading).append(':'); + StringUtils.appendTwoDigits(buff, (int) (remaining / NANOS_PER_MINUTE)).append(':'); + long s = remaining % NANOS_PER_MINUTE; + StringUtils.appendTwoDigits(buff, (int) (s / NANOS_PER_SECOND)); + DateTimeUtils.appendNanos(buff, (int) (s % NANOS_PER_SECOND)); + break; + } + case MINUTE_TO_SECOND: + buff.append(leading).append(':'); + StringUtils.appendTwoDigits(buff, (int) (remaining / NANOS_PER_SECOND)); + DateTimeUtils.appendNanos(buff, (int) (remaining % NANOS_PER_SECOND)); + break; + } + return buff.append("' ").append(qualifier); + } + + /** + * Converts interval value to an absolute value. + * + * @param interval + * the interval value + * @return absolute value in months for year-month intervals, in nanoseconds + * for day-time intervals + */ + public static BigInteger intervalToAbsolute(ValueInterval interval) { + BigInteger r; + switch (interval.getQualifier()) { + case YEAR: + r = BigInteger.valueOf(interval.getLeading()).multiply(MONTHS_PER_YEAR_BI); + break; + case MONTH: + r = BigInteger.valueOf(interval.getLeading()); + break; + case DAY: + r = BigInteger.valueOf(interval.getLeading()).multiply(NANOS_PER_DAY_BI); + break; + case HOUR: + r = BigInteger.valueOf(interval.getLeading()).multiply(NANOS_PER_HOUR_BI); + break; + case MINUTE: + r = BigInteger.valueOf(interval.getLeading()).multiply(NANOS_PER_MINUTE_BI); + break; + case SECOND: + r = intervalToAbsolute(interval, NANOS_PER_SECOND_BI); + break; + case YEAR_TO_MONTH: + r = intervalToAbsolute(interval, MONTHS_PER_YEAR_BI); + break; + case DAY_TO_HOUR: + r = intervalToAbsolute(interval, HOURS_PER_DAY_BI, NANOS_PER_HOUR_BI); + break; + case DAY_TO_MINUTE: + r = intervalToAbsolute(interval, MINUTES_PER_DAY_BI, NANOS_PER_MINUTE_BI); + break; + case DAY_TO_SECOND: + r = intervalToAbsolute(interval, NANOS_PER_DAY_BI); + break; + case HOUR_TO_MINUTE: + r = intervalToAbsolute(interval, MINUTES_PER_HOUR_BI, NANOS_PER_MINUTE_BI); + break; + case HOUR_TO_SECOND: + r = intervalToAbsolute(interval, NANOS_PER_HOUR_BI); + break; + case MINUTE_TO_SECOND: + r = intervalToAbsolute(interval, NANOS_PER_MINUTE_BI); + break; + default: + throw new IllegalArgumentException(); + } + return interval.isNegative() ? r.negate() : r; + } + + private static BigInteger intervalToAbsolute(ValueInterval interval, BigInteger multiplier, + BigInteger totalMultiplier) { + return intervalToAbsolute(interval, multiplier).multiply(totalMultiplier); + } + + private static BigInteger intervalToAbsolute(ValueInterval interval, BigInteger multiplier) { + return BigInteger.valueOf(interval.getLeading()).multiply(multiplier) + .add(BigInteger.valueOf(interval.getRemaining())); + } + + /** + * Converts absolute value to an interval value. + * + * @param qualifier + * the qualifier of interval + * @param absolute + * absolute value in months for year-month intervals, in + * nanoseconds for day-time intervals + * @return the interval value + */ + public static ValueInterval intervalFromAbsolute(IntervalQualifier qualifier, BigInteger absolute) { + switch (qualifier) { + case YEAR: + return ValueInterval.from(qualifier, absolute.signum() < 0, + leadingExact(absolute.divide(MONTHS_PER_YEAR_BI)), 0); + case MONTH: + return ValueInterval.from(qualifier, absolute.signum() < 0, leadingExact(absolute), 0); + case DAY: + return ValueInterval.from(qualifier, absolute.signum() < 0, + leadingExact(absolute.divide(NANOS_PER_DAY_BI)), 0); + case HOUR: + return ValueInterval.from(qualifier, absolute.signum() < 0, + leadingExact(absolute.divide(NANOS_PER_HOUR_BI)), 0); + case MINUTE: + return ValueInterval.from(qualifier, absolute.signum() < 0, + leadingExact(absolute.divide(NANOS_PER_MINUTE_BI)), 0); + case SECOND: + return intervalFromAbsolute(qualifier, absolute, NANOS_PER_SECOND_BI); + case YEAR_TO_MONTH: + return intervalFromAbsolute(qualifier, absolute, MONTHS_PER_YEAR_BI); + case DAY_TO_HOUR: + return intervalFromAbsolute(qualifier, absolute.divide(NANOS_PER_HOUR_BI), HOURS_PER_DAY_BI); + case DAY_TO_MINUTE: + return intervalFromAbsolute(qualifier, absolute.divide(NANOS_PER_MINUTE_BI), MINUTES_PER_DAY_BI); + case DAY_TO_SECOND: + return intervalFromAbsolute(qualifier, absolute, NANOS_PER_DAY_BI); + case HOUR_TO_MINUTE: + return intervalFromAbsolute(qualifier, absolute.divide(NANOS_PER_MINUTE_BI), MINUTES_PER_HOUR_BI); + case HOUR_TO_SECOND: + return intervalFromAbsolute(qualifier, absolute, NANOS_PER_HOUR_BI); + case MINUTE_TO_SECOND: + return intervalFromAbsolute(qualifier, absolute, NANOS_PER_MINUTE_BI); + default: + throw new IllegalArgumentException(); + } + } + + private static ValueInterval intervalFromAbsolute(IntervalQualifier qualifier, BigInteger absolute, + BigInteger divisor) { + BigInteger[] dr = absolute.divideAndRemainder(divisor); + return ValueInterval.from(qualifier, absolute.signum() < 0, leadingExact(dr[0]), Math.abs(dr[1].longValue())); + } + + private static long leadingExact(BigInteger absolute) { + if (absolute.compareTo(LEADING_MAX) > 0 || absolute.compareTo(LEADING_MIN) < 0) { + throw DbException.get(ErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_1, absolute.toString()); + } + return Math.abs(absolute.longValue()); + } + + /** + * Ensures that all fields in interval are valid. + * + * @param qualifier + * qualifier + * @param negative + * whether interval is negative + * @param leading + * value of leading field + * @param remaining + * values of all remaining fields + * @return fixed value of negative field + */ + public static boolean validateInterval(IntervalQualifier qualifier, boolean negative, long leading, + long remaining) { + if (qualifier == null) { + throw new NullPointerException(); + } + if (leading == 0L && remaining == 0L) { + return false; + } + // Upper bound for remaining value (exclusive) + long bound; + switch (qualifier) { + case YEAR: + case MONTH: + case DAY: + case HOUR: + case MINUTE: + bound = 1; + break; + case SECOND: + bound = NANOS_PER_SECOND; + break; + case YEAR_TO_MONTH: + bound = 12; + break; + case DAY_TO_HOUR: + bound = 24; + break; + case DAY_TO_MINUTE: + bound = 24 * 60; + break; + case DAY_TO_SECOND: + bound = NANOS_PER_DAY; + break; + case HOUR_TO_MINUTE: + bound = 60; + break; + case HOUR_TO_SECOND: + bound = NANOS_PER_HOUR; + break; + case MINUTE_TO_SECOND: + bound = NANOS_PER_MINUTE; + break; + default: + throw DbException.getInvalidValueException("interval", qualifier); + } + if (leading < 0L || leading >= 1_000_000_000_000_000_000L) { + throw DbException.getInvalidValueException("interval", Long.toString(leading)); + } + if (remaining < 0L || remaining >= bound) { + throw DbException.getInvalidValueException("interval", Long.toString(remaining)); + } + return negative; + } + + /** + * Returns years value of interval, if any. + * + * @param qualifier + * qualifier + * @param negative + * whether interval is negative + * @param leading + * value of leading field + * @param remaining + * values of all remaining fields + * @return years, or 0 + */ + public static long yearsFromInterval(IntervalQualifier qualifier, boolean negative, long leading, long remaining) { + if (qualifier == IntervalQualifier.YEAR || qualifier == IntervalQualifier.YEAR_TO_MONTH) { + long v = leading; + if (negative) { + v = -v; + } + return v; + } else { + return 0; + } + } + + /** + * Returns months value of interval, if any. + * + * @param qualifier + * qualifier + * @param negative + * whether interval is negative + * @param leading + * value of leading field + * @param remaining + * values of all remaining fields + * @return months, or 0 + */ + public static long monthsFromInterval(IntervalQualifier qualifier, boolean negative, long leading, // + long remaining) { + long v; + if (qualifier == IntervalQualifier.MONTH) { + v = leading; + } else if (qualifier == IntervalQualifier.YEAR_TO_MONTH) { + v = remaining; + } else { + return 0; + } + if (negative) { + v = -v; + } + return v; + } + + /** + * Returns days value of interval, if any. + * + * @param qualifier + * qualifier + * @param negative + * whether interval is negative + * @param leading + * value of leading field + * @param remaining + * values of all remaining fields + * @return days, or 0 + */ + public static long daysFromInterval(IntervalQualifier qualifier, boolean negative, long leading, long remaining) { + switch (qualifier) { + case DAY: + case DAY_TO_HOUR: + case DAY_TO_MINUTE: + case DAY_TO_SECOND: + long v = leading; + if (negative) { + v = -v; + } + return v; + default: + return 0; + } + } + + /** + * Returns hours value of interval, if any. + * + * @param qualifier + * qualifier + * @param negative + * whether interval is negative + * @param leading + * value of leading field + * @param remaining + * values of all remaining fields + * @return hours, or 0 + */ + public static long hoursFromInterval(IntervalQualifier qualifier, boolean negative, long leading, long remaining) { + long v; + switch (qualifier) { + case HOUR: + case HOUR_TO_MINUTE: + case HOUR_TO_SECOND: + v = leading; + break; + case DAY_TO_HOUR: + v = remaining; + break; + case DAY_TO_MINUTE: + v = remaining / 60; + break; + case DAY_TO_SECOND: + v = remaining / NANOS_PER_HOUR; + break; + default: + return 0; + } + if (negative) { + v = -v; + } + return v; + } + + /** + * Returns minutes value of interval, if any. + * + * @param qualifier + * qualifier + * @param negative + * whether interval is negative + * @param leading + * value of leading field + * @param remaining + * values of all remaining fields + * @return minutes, or 0 + */ + public static long minutesFromInterval(IntervalQualifier qualifier, boolean negative, long leading, + long remaining) { + long v; + switch (qualifier) { + case MINUTE: + case MINUTE_TO_SECOND: + v = leading; + break; + case DAY_TO_MINUTE: + v = remaining % 60; + break; + case DAY_TO_SECOND: + v = remaining / NANOS_PER_MINUTE % 60; + break; + case HOUR_TO_MINUTE: + v = remaining; + break; + case HOUR_TO_SECOND: + v = remaining / NANOS_PER_MINUTE; + break; + default: + return 0; + } + if (negative) { + v = -v; + } + return v; + } + + /** + * Returns nanoseconds value of interval, if any. + * + * @param qualifier + * qualifier + * @param negative + * whether interval is negative + * @param leading + * value of leading field + * @param remaining + * values of all remaining fields + * @return nanoseconds, or 0 + */ + public static long nanosFromInterval(IntervalQualifier qualifier, boolean negative, long leading, long remaining) { + long v; + switch (qualifier) { + case SECOND: + v = leading * NANOS_PER_SECOND + remaining; + break; + case DAY_TO_SECOND: + case HOUR_TO_SECOND: + v = remaining % NANOS_PER_MINUTE; + break; + case MINUTE_TO_SECOND: + v = remaining; + break; + default: + return 0; + } + if (negative) { + v = -v; + } + return v; + } + +} diff --git a/h2/src/main/org/h2/util/JSR310Utils.java b/h2/src/main/org/h2/util/JSR310Utils.java new file mode 100644 index 0000000..c53bce3 --- /dev/null +++ b/h2/src/main/org/h2/util/JSR310Utils.java @@ -0,0 +1,424 @@ +/* + * 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.util; + +import static org.h2.util.DateTimeUtils.NANOS_PER_SECOND; +import static org.h2.util.DateTimeUtils.SECONDS_PER_DAY; +import static org.h2.util.DateTimeUtils.SHIFT_MONTH; +import static org.h2.util.DateTimeUtils.SHIFT_YEAR; +import static org.h2.util.DateTimeUtils.absoluteDayFromDateValue; +import static org.h2.util.DateTimeUtils.dateValue; +import static org.h2.util.DateTimeUtils.dateValueFromAbsoluteDay; +import static org.h2.util.DateTimeUtils.dayFromDateValue; +import static org.h2.util.DateTimeUtils.monthFromDateValue; +import static org.h2.util.DateTimeUtils.yearFromDateValue; + +import java.math.BigInteger; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.Period; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; + +import org.h2.api.ErrorCode; +import org.h2.api.IntervalQualifier; +import org.h2.engine.CastDataProvider; +import org.h2.message.DbException; +import org.h2.value.DataType; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueDate; +import org.h2.value.ValueInterval; +import org.h2.value.ValueTime; +import org.h2.value.ValueTimeTimeZone; +import org.h2.value.ValueTimestamp; +import org.h2.value.ValueTimestampTimeZone; + +/** + * This utility class provides access to JSR 310 classes. + */ +public class JSR310Utils { + + private static final long MIN_DATE_VALUE = (-999_999_999L << SHIFT_YEAR) + + (1 << SHIFT_MONTH) + 1; + + private static final long MAX_DATE_VALUE = (999_999_999L << SHIFT_YEAR) + + (12 << SHIFT_MONTH) + 31; + + private static final long MIN_INSTANT_SECOND = -31_557_014_167_219_200L; + + private static final long MAX_INSTANT_SECOND = 31_556_889_864_403_199L; + + private JSR310Utils() { + // utility class + } + + /** + * Converts a value to a LocalDate. + * + * This method should only be called from Java 8 or later version. + * + * @param value + * the value to convert + * @param provider + * the cast information provider + * @return the LocalDate + */ + public static LocalDate valueToLocalDate(Value value, CastDataProvider provider) { + long dateValue = value.convertToDate(provider).getDateValue(); + if (dateValue > MAX_DATE_VALUE) { + return LocalDate.MAX; + } else if (dateValue < MIN_DATE_VALUE) { + return LocalDate.MIN; + } + return LocalDate.of(yearFromDateValue(dateValue), monthFromDateValue(dateValue), + dayFromDateValue(dateValue)); + } + + /** + * Converts a value to a LocalTime. + * + * This method should only be called from Java 8 or later version. + * + * @param value + * the value to convert + * @param provider + * the cast information provider + * @return the LocalTime + */ + public static LocalTime valueToLocalTime(Value value, CastDataProvider provider) { + return LocalTime.ofNanoOfDay(((ValueTime) value.convertTo(TypeInfo.TYPE_TIME, provider)).getNanos()); + } + + /** + * Converts a value to a LocalDateTime. + * + * This method should only be called from Java 8 or later version. + * + * @param value + * the value to convert + * @param provider + * the cast information provider + * @return the LocalDateTime + */ + public static LocalDateTime valueToLocalDateTime(Value value, CastDataProvider provider) { + ValueTimestamp valueTimestamp = (ValueTimestamp) value.convertTo(TypeInfo.TYPE_TIMESTAMP, provider); + return localDateTimeFromDateNanos(valueTimestamp.getDateValue(), valueTimestamp.getTimeNanos()); + } + + /** + * Converts a value to a Instant. + * + * This method should only be called from Java 8 or later version. + * + * @param value + * the value to convert + * @param provider + * the cast information provider + * @return the Instant + */ + public static Instant valueToInstant(Value value, CastDataProvider provider) { + ValueTimestampTimeZone valueTimestampTimeZone = (ValueTimestampTimeZone) value + .convertTo(TypeInfo.TYPE_TIMESTAMP_TZ, provider); + long timeNanos = valueTimestampTimeZone.getTimeNanos(); + long epochSecond = absoluteDayFromDateValue(valueTimestampTimeZone.getDateValue()) + * SECONDS_PER_DAY // + + timeNanos / NANOS_PER_SECOND // + - valueTimestampTimeZone.getTimeZoneOffsetSeconds(); + if (epochSecond > MAX_INSTANT_SECOND) { + return Instant.MAX; + } else if (epochSecond < MIN_INSTANT_SECOND) { + return Instant.MIN; + } + return Instant.ofEpochSecond(epochSecond, timeNanos % NANOS_PER_SECOND); + } + + /** + * Converts a value to a OffsetDateTime. + * + * This method should only be called from Java 8 or later version. + * + * @param value + * the value to convert + * @param provider + * the cast information provider + * @return the OffsetDateTime + */ + public static OffsetDateTime valueToOffsetDateTime(Value value, CastDataProvider provider) { + ValueTimestampTimeZone v = (ValueTimestampTimeZone) value.convertTo(TypeInfo.TYPE_TIMESTAMP_TZ, provider); + return OffsetDateTime.of(localDateTimeFromDateNanos(v.getDateValue(), v.getTimeNanos()), + ZoneOffset.ofTotalSeconds(v.getTimeZoneOffsetSeconds())); + } + + /** + * Converts a value to a ZonedDateTime. + * + * This method should only be called from Java 8 or later version. + * + * @param value + * the value to convert + * @param provider + * the cast information provider + * @return the ZonedDateTime + */ + public static ZonedDateTime valueToZonedDateTime(Value value, CastDataProvider provider) { + ValueTimestampTimeZone v = (ValueTimestampTimeZone) value.convertTo(TypeInfo.TYPE_TIMESTAMP_TZ, provider); + return ZonedDateTime.of(localDateTimeFromDateNanos(v.getDateValue(), v.getTimeNanos()), + ZoneOffset.ofTotalSeconds(v.getTimeZoneOffsetSeconds())); + } + + /** + * Converts a value to a OffsetTime. + * + * This method should only be called from Java 8 or later version. + * + * @param value + * the value to convert + * @param provider + * the cast information provider + * @return the OffsetTime + */ + public static OffsetTime valueToOffsetTime(Value value, CastDataProvider provider) { + ValueTimeTimeZone valueTimeTimeZone = (ValueTimeTimeZone) value.convertTo(TypeInfo.TYPE_TIME_TZ, provider); + return OffsetTime.of(LocalTime.ofNanoOfDay(valueTimeTimeZone.getNanos()), + ZoneOffset.ofTotalSeconds(valueTimeTimeZone.getTimeZoneOffsetSeconds())); + } + + /** + * Converts a value to a Period. + * + * This method should only be called from Java 8 or later version. + * + * @param value + * the value to convert + * @return the Period + */ + public static Period valueToPeriod(Value value) { + if (!(value instanceof ValueInterval)) { + value = value.convertTo(TypeInfo.TYPE_INTERVAL_YEAR_TO_MONTH); + } + if (!DataType.isYearMonthIntervalType(value.getValueType())) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, (Throwable) null, value.getString()); + } + ValueInterval v = (ValueInterval) value; + IntervalQualifier qualifier = v.getQualifier(); + boolean negative = v.isNegative(); + long leading = v.getLeading(); + long remaining = v.getRemaining(); + int y = Value.convertToInt(IntervalUtils.yearsFromInterval(qualifier, negative, leading, remaining), null); + int m = Value.convertToInt(IntervalUtils.monthsFromInterval(qualifier, negative, leading, remaining), null); + return Period.of(y, m, 0); + } + + /** + * Converts a value to a Duration. + * + * This method should only be called from Java 8 or later version. + * + * @param value + * the value to convert + * @return the Duration + */ + public static Duration valueToDuration(Value value) { + if (!(value instanceof ValueInterval)) { + value = value.convertTo(TypeInfo.TYPE_INTERVAL_DAY_TO_SECOND); + } + if (DataType.isYearMonthIntervalType(value.getValueType())) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, (Throwable) null, value.getString()); + } + BigInteger[] dr = IntervalUtils.intervalToAbsolute((ValueInterval) value) + .divideAndRemainder(BigInteger.valueOf(1_000_000_000)); + return Duration.ofSeconds(dr[0].longValue(), dr[1].longValue()); + } + + /** + * Converts a LocalDate to a Value. + * + * @param localDate + * the LocalDate to convert, not {@code null} + * @return the value + */ + public static ValueDate localDateToValue(LocalDate localDate) { + return ValueDate.fromDateValue( + dateValue(localDate.getYear(), localDate.getMonthValue(), localDate.getDayOfMonth())); + } + + /** + * Converts a LocalTime to a Value. + * + * @param localTime + * the LocalTime to convert, not {@code null} + * @return the value + */ + public static ValueTime localTimeToValue(LocalTime localTime) { + return ValueTime.fromNanos(localTime.toNanoOfDay()); + } + + /** + * Converts a LocalDateTime to a Value. + * + * @param localDateTime + * the LocalDateTime to convert, not {@code null} + * @return the value + */ + public static ValueTimestamp localDateTimeToValue(LocalDateTime localDateTime) { + LocalDate localDate = localDateTime.toLocalDate(); + return ValueTimestamp.fromDateValueAndNanos( + dateValue(localDate.getYear(), localDate.getMonthValue(), localDate.getDayOfMonth()), + localDateTime.toLocalTime().toNanoOfDay()); + } + + /** + * Converts a Instant to a Value. + * + * @param instant + * the Instant to convert, not {@code null} + * @return the value + */ + public static ValueTimestampTimeZone instantToValue(Instant instant) { + long epochSecond = instant.getEpochSecond(); + int nano = instant.getNano(); + long absoluteDay = epochSecond / 86_400; + // Round toward negative infinity + if (epochSecond < 0 && (absoluteDay * 86_400 != epochSecond)) { + absoluteDay--; + } + long timeNanos = (epochSecond - absoluteDay * 86_400) * 1_000_000_000 + nano; + return ValueTimestampTimeZone.fromDateValueAndNanos(dateValueFromAbsoluteDay(absoluteDay), + timeNanos, 0); + } + + /** + * Converts a OffsetDateTime to a Value. + * + * @param offsetDateTime + * the OffsetDateTime to convert, not {@code null} + * @return the value + */ + public static ValueTimestampTimeZone offsetDateTimeToValue(OffsetDateTime offsetDateTime) { + LocalDateTime localDateTime = offsetDateTime.toLocalDateTime(); + LocalDate localDate = localDateTime.toLocalDate(); + return ValueTimestampTimeZone.fromDateValueAndNanos( + dateValue(localDate.getYear(), localDate.getMonthValue(), localDate.getDayOfMonth()), + localDateTime.toLocalTime().toNanoOfDay(), // + offsetDateTime.getOffset().getTotalSeconds()); + } + + /** + * Converts a ZonedDateTime to a Value. + * + * @param zonedDateTime + * the ZonedDateTime to convert, not {@code null} + * @return the value + */ + public static ValueTimestampTimeZone zonedDateTimeToValue(ZonedDateTime zonedDateTime) { + LocalDateTime localDateTime = zonedDateTime.toLocalDateTime(); + LocalDate localDate = localDateTime.toLocalDate(); + return ValueTimestampTimeZone.fromDateValueAndNanos( + dateValue(localDate.getYear(), localDate.getMonthValue(), localDate.getDayOfMonth()), + localDateTime.toLocalTime().toNanoOfDay(), // + zonedDateTime.getOffset().getTotalSeconds()); + } + + /** + * Converts a OffsetTime to a Value. + * + * @param offsetTime + * the OffsetTime to convert, not {@code null} + * @return the value + */ + public static ValueTimeTimeZone offsetTimeToValue(OffsetTime offsetTime) { + return ValueTimeTimeZone.fromNanos(offsetTime.toLocalTime().toNanoOfDay(), + offsetTime.getOffset().getTotalSeconds()); + } + + private static LocalDateTime localDateTimeFromDateNanos(long dateValue, long timeNanos) { + if (dateValue > MAX_DATE_VALUE) { + return LocalDateTime.MAX; + } else if (dateValue < MIN_DATE_VALUE) { + return LocalDateTime.MIN; + } + return LocalDateTime.of(LocalDate.of(yearFromDateValue(dateValue), + monthFromDateValue(dateValue), dayFromDateValue(dateValue)), + LocalTime.ofNanoOfDay(timeNanos)); + } + + /** + * Converts a Period to a Value. + * + * @param period + * the Period to convert, not {@code null} + * @return the value + */ + public static ValueInterval periodToValue(Period period) { + int days = period.getDays(); + if (days != 0) { + throw DbException.getInvalidValueException("Period.days", days); + } + int years = period.getYears(); + int months = period.getMonths(); + IntervalQualifier qualifier; + boolean negative = false; + long leading = 0L, remaining = 0L; + if (years == 0) { + if (months == 0L) { + // Use generic qualifier + qualifier = IntervalQualifier.YEAR_TO_MONTH; + } else { + qualifier = IntervalQualifier.MONTH; + leading = months; + if (leading < 0) { + leading = -leading; + negative = true; + } + } + } else { + if (months == 0L) { + qualifier = IntervalQualifier.YEAR; + leading = years; + if (leading < 0) { + leading = -leading; + negative = true; + } + } else { + qualifier = IntervalQualifier.YEAR_TO_MONTH; + leading = years * 12 + months; + if (leading < 0) { + leading = -leading; + negative = true; + } + remaining = leading % 12; + leading /= 12; + } + } + return ValueInterval.from(qualifier, negative, leading, remaining); + } + + /** + * Converts a Duration to a Value. + * + * @param duration + * the Duration to convert, not {@code null} + * @return the value + */ + public static ValueInterval durationToValue(Duration duration) { + long seconds = duration.getSeconds(); + int nano = duration.getNano(); + boolean negative = seconds < 0; + seconds = Math.abs(seconds); + if (negative && nano != 0) { + nano = 1_000_000_000 - nano; + seconds--; + } + return ValueInterval.from(IntervalQualifier.SECOND, negative, seconds, nano); + } + +} diff --git a/h2/src/main/org/h2/util/JdbcUtils.java b/h2/src/main/org/h2/util/JdbcUtils.java new file mode 100644 index 0000000..03a126c --- /dev/null +++ b/h2/src/main/org/h2/util/JdbcUtils.java @@ -0,0 +1,777 @@ +/* + * 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.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Properties; + +import javax.naming.Context; +import javax.sql.DataSource; +import org.h2.api.ErrorCode; +import org.h2.api.JavaObjectSerializer; +import org.h2.engine.Constants; +import org.h2.engine.SysProperties; +import org.h2.jdbc.JdbcConnection; +import org.h2.jdbc.JdbcPreparedStatement; +import org.h2.message.DbException; +import org.h2.tools.SimpleResultSet; +import org.h2.util.Utils.ClassFactory; +import org.h2.value.Value; +import org.h2.value.ValueLob; +import org.h2.value.ValueToObjectConverter; +import org.h2.value.ValueUuid; + +/** + * This is a utility class with JDBC helper functions. + */ +public class JdbcUtils { + + /** + * The serializer to use. + */ + public static JavaObjectSerializer serializer; + + private static final String[] DRIVERS = { + "h2:", "org.h2.Driver", + "Cache:", "com.intersys.jdbc.CacheDriver", + "daffodilDB://", "in.co.daffodil.db.rmi.RmiDaffodilDBDriver", + "daffodil", "in.co.daffodil.db.jdbc.DaffodilDBDriver", + "db2:", "com.ibm.db2.jcc.DB2Driver", + "derby:net:", "org.apache.derby.client.ClientAutoloadedDriver", + "derby://", "org.apache.derby.client.ClientAutoloadedDriver", + "derby:", "org.apache.derby.iapi.jdbc.AutoloadedDriver", + "FrontBase:", "com.frontbase.jdbc.FBJDriver", + "firebirdsql:", "org.firebirdsql.jdbc.FBDriver", + "hsqldb:", "org.hsqldb.jdbcDriver", + "informix-sqli:", "com.informix.jdbc.IfxDriver", + "jtds:", "net.sourceforge.jtds.jdbc.Driver", + "microsoft:", "com.microsoft.jdbc.sqlserver.SQLServerDriver", + "mimer:", "com.mimer.jdbc.Driver", + "mysql:", "com.mysql.cj.jdbc.Driver", + "mariadb:", "org.mariadb.jdbc.Driver", + "odbc:", "sun.jdbc.odbc.JdbcOdbcDriver", + "oracle:", "oracle.jdbc.driver.OracleDriver", + "pervasive:", "com.pervasive.jdbc.v2.Driver", + "pointbase:micro:", "com.pointbase.me.jdbc.jdbcDriver", + "pointbase:", "com.pointbase.jdbc.jdbcUniversalDriver", + "postgresql:", "org.postgresql.Driver", + "sybase:", "com.sybase.jdbc3.jdbc.SybDriver", + "sqlserver:", "com.microsoft.sqlserver.jdbc.SQLServerDriver", + "teradata:", "com.ncr.teradata.TeraDriver", + }; + + private static final byte[] UUID_PREFIX = + "\254\355\0\5sr\0\16java.util.UUID\274\231\3\367\230m\205/\2\0\2J\0\14leastSigBitsJ\0\13mostSigBitsxp" + .getBytes(StandardCharsets.ISO_8859_1); + + private static boolean allowAllClasses; + private static HashSet allowedClassNames; + + /** + * In order to manage more than one class loader + */ + private static final ArrayList userClassFactories = new ArrayList<>(); + + private static String[] allowedClassNamePrefixes; + + private JdbcUtils() { + // utility class + } + + /** + * Add a class factory in order to manage more than one class loader. + * + * @param classFactory An object that implements ClassFactory + */ + public static void addClassFactory(ClassFactory classFactory) { + userClassFactories.add(classFactory); + } + + /** + * Remove a class factory + * + * @param classFactory Already inserted class factory instance + */ + public static void removeClassFactory(ClassFactory classFactory) { + userClassFactories.remove(classFactory); + } + + static { + String clazz = SysProperties.JAVA_OBJECT_SERIALIZER; + if (clazz != null) { + try { + serializer = (JavaObjectSerializer) loadUserClass(clazz).getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw DbException.convert(e); + } + } + } + + /** + * Load a class, but check if it is allowed to load this class first. To + * perform access rights checking, the system property h2.allowedClasses + * needs to be set to a list of class file name prefixes. + * + * @param generic return type + * @param className the name of the class + * @return the class object + */ + @SuppressWarnings("unchecked") + public static Class loadUserClass(String className) { + if (allowedClassNames == null) { + // initialize the static fields + String s = SysProperties.ALLOWED_CLASSES; + ArrayList prefixes = new ArrayList<>(); + boolean allowAll = false; + HashSet classNames = new HashSet<>(); + for (String p : StringUtils.arraySplit(s, ',', true)) { + if (p.equals("*")) { + allowAll = true; + } else if (p.endsWith("*")) { + prefixes.add(p.substring(0, p.length() - 1)); + } else { + classNames.add(p); + } + } + allowedClassNamePrefixes = prefixes.toArray(new String[0]); + allowAllClasses = allowAll; + allowedClassNames = classNames; + } + if (!allowAllClasses && !allowedClassNames.contains(className)) { + boolean allowed = false; + for (String s : allowedClassNamePrefixes) { + if (className.startsWith(s)) { + allowed = true; + break; + } + } + if (!allowed) { + throw DbException.get( + ErrorCode.ACCESS_DENIED_TO_CLASS_1, className); + } + } + // Use provided class factory first. + for (ClassFactory classFactory : userClassFactories) { + if (classFactory.match(className)) { + try { + Class userClass = classFactory.loadClass(className); + if (userClass != null) { + return (Class) userClass; + } + } catch (Exception e) { + throw DbException.get( + ErrorCode.CLASS_NOT_FOUND_1, e, className); + } + } + } + // Use local ClassLoader + try { + return (Class) Class.forName(className); + } catch (ClassNotFoundException e) { + try { + return (Class) Class.forName( + className, true, + Thread.currentThread().getContextClassLoader()); + } catch (Exception e2) { + throw DbException.get( + ErrorCode.CLASS_NOT_FOUND_1, e, className); + } + } catch (NoClassDefFoundError e) { + throw DbException.get( + ErrorCode.CLASS_NOT_FOUND_1, e, className); + } catch (Error e) { + // UnsupportedClassVersionError + throw DbException.get( + ErrorCode.GENERAL_ERROR_1, e, className); + } + } + + /** + * Close a statement without throwing an exception. + * + * @param stat the statement or null + */ + public static void closeSilently(Statement stat) { + if (stat != null) { + try { + stat.close(); + } catch (SQLException e) { + // ignore + } + } + } + + /** + * Close a connection without throwing an exception. + * + * @param conn the connection or null + */ + public static void closeSilently(Connection conn) { + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + // ignore + } + } + } + + /** + * Close a result set without throwing an exception. + * + * @param rs the result set or null + */ + public static void closeSilently(ResultSet rs) { + if (rs != null) { + try { + rs.close(); + } catch (SQLException e) { + // ignore + } + } + } + + /** + * Open a new database connection with the given settings. + * + * @param driver the driver class name + * @param url the database URL + * @param user the user name + * @param password the password + * @return the database connection + * @throws SQLException on failure + */ + public static Connection getConnection(String driver, String url, + String user, String password) throws SQLException { + return getConnection(driver, url, user, password, null, false); + } + + /** + * Open a new database connection with the given settings. + * + * @param driver the driver class name + * @param url the database URL + * @param user the user name or {@code null} + * @param password the password or {@code null} + * @param networkConnectionInfo the network connection information, or {@code null} + * @param forbidCreation whether database creation is forbidden + * @return the database connection + * @throws SQLException on failure + */ + public static Connection getConnection(String driver, String url, String user, String password, + NetworkConnectionInfo networkConnectionInfo, boolean forbidCreation) throws SQLException { + if (url.startsWith(Constants.START_URL)) { + JdbcConnection connection = new JdbcConnection(url, null, user, password, forbidCreation); + if (networkConnectionInfo != null) { + connection.getSession().setNetworkConnectionInfo(networkConnectionInfo); + } + return connection; + } + if (StringUtils.isNullOrEmpty(driver)) { + JdbcUtils.load(url); + } else { + Class d = loadUserClass(driver); + try { + if (java.sql.Driver.class.isAssignableFrom(d)) { + Driver driverInstance = (Driver) d.getDeclaredConstructor().newInstance(); + Properties prop = new Properties(); + if (user != null) { + prop.setProperty("user", user); + } + if (password != null) { + prop.setProperty("password", password); + } + /* + * fix issue #695 with drivers with the same jdbc + * subprotocol in classpath of jdbc drivers (as example + * redshift and postgresql drivers) + */ + Connection connection = driverInstance.connect(url, prop); + if (connection != null) { + return connection; + } + throw new SQLException("Driver " + driver + " is not suitable for " + url, "08001"); + } else if (javax.naming.Context.class.isAssignableFrom(d)) { + if (!url.startsWith("java:")) { + throw new SQLException("Only java scheme is supported for JNDI lookups", "08001"); + } + // JNDI context + Context context = (Context) d.getDeclaredConstructor().newInstance(); + DataSource ds = (DataSource) context.lookup(url); + if (StringUtils.isNullOrEmpty(user) && StringUtils.isNullOrEmpty(password)) { + return ds.getConnection(); + } + return ds.getConnection(user, password); + } + } catch (Exception e) { + throw DbException.toSQLException(e); + } + // don't know, but maybe it loaded a JDBC Driver + } + return DriverManager.getConnection(url, user, password); + } + + /** + * Get the driver class name for the given URL, or null if the URL is + * unknown. + * + * @param url the database URL + * @return the driver class name + */ + public static String getDriver(String url) { + if (url.startsWith("jdbc:")) { + url = url.substring("jdbc:".length()); + for (int i = 0; i < DRIVERS.length; i += 2) { + String prefix = DRIVERS[i]; + if (url.startsWith(prefix)) { + return DRIVERS[i + 1]; + } + } + } + return null; + } + + /** + * Load the driver class for the given URL, if the database URL is known. + * + * @param url the database URL + */ + public static void load(String url) { + String driver = getDriver(url); + if (driver != null) { + loadUserClass(driver); + } + } + + /** + * Serialize the object to a byte array, using the serializer specified by + * the connection info if set, or the default serializer. + * + * @param obj the object to serialize + * @param javaObjectSerializer the object serializer (may be null) + * @return the byte array + */ + public static byte[] serialize(Object obj, JavaObjectSerializer javaObjectSerializer) { + try { + if (javaObjectSerializer != null) { + return javaObjectSerializer.serialize(obj); + } + if (serializer != null) { + return serializer.serialize(obj); + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ObjectOutputStream os = new ObjectOutputStream(out); + os.writeObject(obj); + return out.toByteArray(); + } catch (Throwable e) { + throw DbException.get(ErrorCode.SERIALIZATION_FAILED_1, e, e.toString()); + } + } + + /** + * De-serialize the byte array to an object, eventually using the serializer + * specified by the connection info. + * + * @param data the byte array + * @param javaObjectSerializer the object serializer (may be null) + * @return the object + * @throws DbException if serialization fails + */ + public static Object deserialize(byte[] data, JavaObjectSerializer javaObjectSerializer) { + try { + if (javaObjectSerializer != null) { + return javaObjectSerializer.deserialize(data); + } + if (serializer != null) { + return serializer.deserialize(data); + } + ByteArrayInputStream in = new ByteArrayInputStream(data); + ObjectInputStream is; + if (SysProperties.USE_THREAD_CONTEXT_CLASS_LOADER) { + final ClassLoader loader = Thread.currentThread().getContextClassLoader(); + is = new ObjectInputStream(in) { + @Override + protected Class resolveClass(ObjectStreamClass desc) + throws IOException, ClassNotFoundException { + try { + return Class.forName(desc.getName(), true, loader); + } catch (ClassNotFoundException e) { + return super.resolveClass(desc); + } + } + }; + } else { + is = new ObjectInputStream(in); + } + return is.readObject(); + } catch (Throwable e) { + throw DbException.get(ErrorCode.DESERIALIZATION_FAILED_1, e, e.toString()); + } + } + + /** + * De-serialize the byte array to a UUID object. This method is called on + * the server side where regular de-serialization of user-supplied Java + * objects may create a security hole if object was maliciously crafted. + * Unlike {@link #deserialize(byte[], JavaObjectSerializer)}, this method + * does not try to de-serialize instances of other classes. + * + * @param data the byte array + * @return the UUID object + * @throws DbException if serialization fails + */ + public static ValueUuid deserializeUuid(byte[] data) { + uuid: if (data.length == 80) { + for (int i = 0; i < 64; i++) { + if (data[i] != UUID_PREFIX[i]) { + break uuid; + } + } + return ValueUuid.get(Bits.readLong(data, 72), Bits.readLong(data, 64)); + } + throw DbException.get(ErrorCode.DESERIALIZATION_FAILED_1, "Is not a UUID"); + } + + /** + * Set a value as a parameter in a prepared statement. + * + * @param prep the prepared statement + * @param parameterIndex the parameter index + * @param value the value + * @param conn the own connection + * @throws SQLException on failure + */ + public static void set(PreparedStatement prep, int parameterIndex, Value value, JdbcConnection conn) + throws SQLException { + if (prep instanceof JdbcPreparedStatement) { + if (value instanceof ValueLob) { + setLob(prep, parameterIndex, (ValueLob) value); + } else { + prep.setObject(parameterIndex, value); + } + } else { + setOther(prep, parameterIndex, value, conn); + } + } + + private static void setOther(PreparedStatement prep, int parameterIndex, Value value, JdbcConnection conn) + throws SQLException { + int valueType = value.getValueType(); + switch (valueType) { + case Value.NULL: + prep.setNull(parameterIndex, Types.NULL); + break; + case Value.BOOLEAN: + prep.setBoolean(parameterIndex, value.getBoolean()); + break; + case Value.TINYINT: + prep.setByte(parameterIndex, value.getByte()); + break; + case Value.SMALLINT: + prep.setShort(parameterIndex, value.getShort()); + break; + case Value.INTEGER: + prep.setInt(parameterIndex, value.getInt()); + break; + case Value.BIGINT: + prep.setLong(parameterIndex, value.getLong()); + break; + case Value.NUMERIC: + case Value.DECFLOAT: + prep.setBigDecimal(parameterIndex, value.getBigDecimal()); + break; + case Value.DOUBLE: + prep.setDouble(parameterIndex, value.getDouble()); + break; + case Value.REAL: + prep.setFloat(parameterIndex, value.getFloat()); + break; + case Value.TIME: + try { + prep.setObject(parameterIndex, JSR310Utils.valueToLocalTime(value, null), Types.TIME); + } catch (SQLException ignore) { + prep.setTime(parameterIndex, LegacyDateTimeUtils.toTime(null, null, value)); + } + break; + case Value.DATE: + try { + prep.setObject(parameterIndex, JSR310Utils.valueToLocalDate(value, null), Types.DATE); + } catch (SQLException ignore) { + prep.setDate(parameterIndex, LegacyDateTimeUtils.toDate(null, null, value)); + } + break; + case Value.TIMESTAMP: + try { + prep.setObject(parameterIndex, JSR310Utils.valueToLocalDateTime(value, null), Types.TIMESTAMP); + } catch (SQLException ignore) { + prep.setTimestamp(parameterIndex, LegacyDateTimeUtils.toTimestamp(null, null, value)); + } + break; + case Value.VARBINARY: + case Value.BINARY: + case Value.GEOMETRY: + case Value.JSON: + prep.setBytes(parameterIndex, value.getBytesNoCopy()); + break; + case Value.VARCHAR: + case Value.VARCHAR_IGNORECASE: + case Value.ENUM: + case Value.INTERVAL_YEAR: + case Value.INTERVAL_MONTH: + case Value.INTERVAL_DAY: + case Value.INTERVAL_HOUR: + case Value.INTERVAL_MINUTE: + case Value.INTERVAL_SECOND: + case Value.INTERVAL_YEAR_TO_MONTH: + case Value.INTERVAL_DAY_TO_HOUR: + case Value.INTERVAL_DAY_TO_MINUTE: + case Value.INTERVAL_DAY_TO_SECOND: + case Value.INTERVAL_HOUR_TO_MINUTE: + case Value.INTERVAL_HOUR_TO_SECOND: + case Value.INTERVAL_MINUTE_TO_SECOND: + prep.setString(parameterIndex, value.getString()); + break; + case Value.BLOB: + case Value.CLOB: + setLob(prep, parameterIndex, (ValueLob) value); + break; + case Value.ARRAY: + prep.setArray(parameterIndex, prep.getConnection().createArrayOf("NULL", + (Object[]) ValueToObjectConverter.valueToDefaultObject(value, conn, true))); + break; + case Value.JAVA_OBJECT: + prep.setObject(parameterIndex, + JdbcUtils.deserialize(value.getBytesNoCopy(), conn.getJavaObjectSerializer()), + Types.JAVA_OBJECT); + break; + case Value.UUID: + prep.setBytes(parameterIndex, value.getBytes()); + break; + case Value.CHAR: + try { + prep.setObject(parameterIndex, value.getString(), Types.CHAR); + } catch (SQLException ignore) { + prep.setString(parameterIndex, value.getString()); + } + break; + case Value.TIMESTAMP_TZ: + try { + prep.setObject(parameterIndex, JSR310Utils.valueToOffsetDateTime(value, null), + Types.TIMESTAMP_WITH_TIMEZONE); + return; + } catch (SQLException ignore) { + prep.setString(parameterIndex, value.getString()); + } + break; + case Value.TIME_TZ: + try { + prep.setObject(parameterIndex, JSR310Utils.valueToOffsetTime(value, null), Types.TIME_WITH_TIMEZONE); + return; + } catch (SQLException ignore) { + prep.setString(parameterIndex, value.getString()); + } + break; + default: + throw DbException.getUnsupportedException(Value.getTypeName(valueType)); + } + } + + private static void setLob(PreparedStatement prep, int parameterIndex, ValueLob value) throws SQLException { + if (value.getValueType() == Value.BLOB) { + long p = value.octetLength(); + prep.setBinaryStream(parameterIndex, value.getInputStream(), p > Integer.MAX_VALUE ? -1 : (int) p); + } else { + long p = value.charLength(); + prep.setCharacterStream(parameterIndex, value.getReader(), p > Integer.MAX_VALUE ? -1 : (int) p); + } + } + + /** + * Get metadata from the database. + * + * @param conn the connection + * @param sql the SQL statement + * @return the metadata + * @throws SQLException on failure + */ + public static ResultSet getMetaResultSet(Connection conn, String sql) + throws SQLException { + DatabaseMetaData meta = conn.getMetaData(); + if (isBuiltIn(sql, "@best_row_identifier")) { + String[] p = split(sql); + int scale = p[4] == null ? 0 : Integer.parseInt(p[4]); + boolean nullable = Boolean.parseBoolean(p[5]); + return meta.getBestRowIdentifier(p[1], p[2], p[3], scale, nullable); + } else if (isBuiltIn(sql, "@catalogs")) { + return meta.getCatalogs(); + } else if (isBuiltIn(sql, "@columns")) { + String[] p = split(sql); + return meta.getColumns(p[1], p[2], p[3], p[4]); + } else if (isBuiltIn(sql, "@column_privileges")) { + String[] p = split(sql); + return meta.getColumnPrivileges(p[1], p[2], p[3], p[4]); + } else if (isBuiltIn(sql, "@cross_references")) { + String[] p = split(sql); + return meta.getCrossReference(p[1], p[2], p[3], p[4], p[5], p[6]); + } else if (isBuiltIn(sql, "@exported_keys")) { + String[] p = split(sql); + return meta.getExportedKeys(p[1], p[2], p[3]); + } else if (isBuiltIn(sql, "@imported_keys")) { + String[] p = split(sql); + return meta.getImportedKeys(p[1], p[2], p[3]); + } else if (isBuiltIn(sql, "@index_info")) { + String[] p = split(sql); + boolean unique = Boolean.parseBoolean(p[4]); + boolean approx = Boolean.parseBoolean(p[5]); + return meta.getIndexInfo(p[1], p[2], p[3], unique, approx); + } else if (isBuiltIn(sql, "@primary_keys")) { + String[] p = split(sql); + return meta.getPrimaryKeys(p[1], p[2], p[3]); + } else if (isBuiltIn(sql, "@procedures")) { + String[] p = split(sql); + return meta.getProcedures(p[1], p[2], p[3]); + } else if (isBuiltIn(sql, "@procedure_columns")) { + String[] p = split(sql); + return meta.getProcedureColumns(p[1], p[2], p[3], p[4]); + } else if (isBuiltIn(sql, "@schemas")) { + return meta.getSchemas(); + } else if (isBuiltIn(sql, "@tables")) { + String[] p = split(sql); + String[] types = p[4] == null ? null : StringUtils.arraySplit(p[4], ',', false); + return meta.getTables(p[1], p[2], p[3], types); + } else if (isBuiltIn(sql, "@table_privileges")) { + String[] p = split(sql); + return meta.getTablePrivileges(p[1], p[2], p[3]); + } else if (isBuiltIn(sql, "@table_types")) { + return meta.getTableTypes(); + } else if (isBuiltIn(sql, "@type_info")) { + return meta.getTypeInfo(); + } else if (isBuiltIn(sql, "@udts")) { + String[] p = split(sql); + int[] types; + if (p[4] == null) { + types = null; + } else { + String[] t = StringUtils.arraySplit(p[4], ',', false); + types = new int[t.length]; + for (int i = 0; i < t.length; i++) { + types[i] = Integer.parseInt(t[i]); + } + } + return meta.getUDTs(p[1], p[2], p[3], types); + } else if (isBuiltIn(sql, "@version_columns")) { + String[] p = split(sql); + return meta.getVersionColumns(p[1], p[2], p[3]); + } else if (isBuiltIn(sql, "@memory")) { + SimpleResultSet rs = new SimpleResultSet(); + rs.addColumn("Type", Types.VARCHAR, 0, 0); + rs.addColumn("KB", Types.VARCHAR, 0, 0); + rs.addRow("Used Memory", Long.toString(Utils.getMemoryUsed())); + rs.addRow("Free Memory", Long.toString(Utils.getMemoryFree())); + return rs; + } else if (isBuiltIn(sql, "@info")) { + SimpleResultSet rs = new SimpleResultSet(); + rs.addColumn("KEY", Types.VARCHAR, 0, 0); + rs.addColumn("VALUE", Types.VARCHAR, 0, 0); + rs.addRow("conn.getCatalog", conn.getCatalog()); + rs.addRow("conn.getAutoCommit", Boolean.toString(conn.getAutoCommit())); + rs.addRow("conn.getTransactionIsolation", Integer.toString(conn.getTransactionIsolation())); + rs.addRow("conn.getWarnings", String.valueOf(conn.getWarnings())); + String map; + try { + map = String.valueOf(conn.getTypeMap()); + } catch (SQLException e) { + map = e.toString(); + } + rs.addRow("conn.getTypeMap", map); + rs.addRow("conn.isReadOnly", Boolean.toString(conn.isReadOnly())); + rs.addRow("conn.getHoldability", Integer.toString(conn.getHoldability())); + addDatabaseMetaData(rs, meta); + return rs; + } else if (isBuiltIn(sql, "@attributes")) { + String[] p = split(sql); + return meta.getAttributes(p[1], p[2], p[3], p[4]); + } else if (isBuiltIn(sql, "@super_tables")) { + String[] p = split(sql); + return meta.getSuperTables(p[1], p[2], p[3]); + } else if (isBuiltIn(sql, "@super_types")) { + String[] p = split(sql); + return meta.getSuperTypes(p[1], p[2], p[3]); + } else if (isBuiltIn(sql, "@pseudo_columns")) { + String[] p = split(sql); + return meta.getPseudoColumns(p[1], p[2], p[3], p[4]); + } + return null; + } + + private static void addDatabaseMetaData(SimpleResultSet rs, + DatabaseMetaData meta) { + Method[] methods = DatabaseMetaData.class.getDeclaredMethods(); + Arrays.sort(methods, Comparator.comparing(Method::toString)); + for (Method m : methods) { + if (m.getParameterTypes().length == 0) { + try { + Object o = m.invoke(meta); + rs.addRow("meta." + m.getName(), String.valueOf(o)); + } catch (InvocationTargetException e) { + rs.addRow("meta." + m.getName(), e.getTargetException().toString()); + } catch (Exception e) { + rs.addRow("meta." + m.getName(), e.toString()); + } + } + } + } + + /** + * Check is the SQL string starts with a prefix (case insensitive). + * + * @param sql the SQL statement + * @param builtIn the prefix + * @return true if yes + */ + public static boolean isBuiltIn(String sql, String builtIn) { + return sql.regionMatches(true, 0, builtIn, 0, builtIn.length()); + } + + /** + * Split the string using the space separator into at least 10 entries. + * + * @param s the string + * @return the array + */ + public static String[] split(String s) { + String[] t = StringUtils.arraySplit(s, ' ', true); + String[] list = new String[Math.max(10, t.length)]; + System.arraycopy(t, 0, list, 0, t.length); + for (int i = 0; i < list.length; i++) { + if ("null".equals(list[i])) { + list[i] = null; + } + } + return list; + } +} diff --git a/h2/src/main/org/h2/util/LegacyDateTimeUtils.java b/h2/src/main/org/h2/util/LegacyDateTimeUtils.java new file mode 100644 index 0000000..254c7ff --- /dev/null +++ b/h2/src/main/org/h2/util/LegacyDateTimeUtils.java @@ -0,0 +1,328 @@ +/* + * 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.util; + +import static org.h2.util.DateTimeUtils.MILLIS_PER_DAY; +import static org.h2.util.DateTimeUtils.NANOS_PER_SECOND; + +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import org.h2.engine.CastDataProvider; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueDate; +import org.h2.value.ValueNull; +import org.h2.value.ValueTime; +import org.h2.value.ValueTimestamp; +import org.h2.value.ValueTimestampTimeZone; + +/** + * Date and time utilities for {@link Date}, {@link Time}, and {@link Timestamp} + * classes. + */ +public final class LegacyDateTimeUtils { + + /** + * Gregorian change date for a {@link java.util.GregorianCalendar} that + * represents a proleptic Gregorian calendar. + */ + public static final Date PROLEPTIC_GREGORIAN_CHANGE = new Date(Long.MIN_VALUE); + + /** + * UTC time zone. + */ + public static final TimeZone UTC = TimeZone.getTimeZone("UTC"); + + private LegacyDateTimeUtils() { + } + + /** + * Get or create a date value for the given date. + * + * @param provider + * the cast information provider + * @param timeZone + * time zone, or {@code null} for default + * @param date + * the date + * @return the value + */ + public static ValueDate fromDate(CastDataProvider provider, TimeZone timeZone, Date date) { + long ms = date.getTime(); + return ValueDate.fromDateValue(dateValueFromLocalMillis( + ms + (timeZone == null ? getTimeZoneOffsetMillis(provider, ms) : timeZone.getOffset(ms)))); + } + + /** + * Get or create a time value for the given time. + * + * @param provider + * the cast information provider + * @param timeZone + * time zone, or {@code null} for default + * @param time + * the time + * @return the value + */ + public static ValueTime fromTime(CastDataProvider provider, TimeZone timeZone, Time time) { + long ms = time.getTime(); + return ValueTime.fromNanos(nanosFromLocalMillis( + ms + (timeZone == null ? getTimeZoneOffsetMillis(provider, ms) : timeZone.getOffset(ms)))); + } + + /** + * Get or create a timestamp value for the given timestamp. + * + * @param provider + * the cast information provider + * @param timeZone + * time zone, or {@code null} for default + * @param timestamp + * the timestamp + * @return the value + */ + public static ValueTimestamp fromTimestamp(CastDataProvider provider, TimeZone timeZone, Timestamp timestamp) { + long ms = timestamp.getTime(); + return timestampFromLocalMillis( + ms + (timeZone == null ? getTimeZoneOffsetMillis(provider, ms) : timeZone.getOffset(ms)), + timestamp.getNanos() % 1_000_000); + } + + /** + * Get or create a timestamp value for the given date/time in millis. + * + * @param provider + * the cast information provider + * @param ms + * the milliseconds + * @param nanos + * the nanoseconds + * @return the value + */ + public static ValueTimestamp fromTimestamp(CastDataProvider provider, long ms, int nanos) { + return timestampFromLocalMillis(ms + getTimeZoneOffsetMillis(provider, ms), nanos); + } + + private static ValueTimestamp timestampFromLocalMillis(long ms, int nanos) { + long dateValue = dateValueFromLocalMillis(ms); + long timeNanos = nanos + nanosFromLocalMillis(ms); + return ValueTimestamp.fromDateValueAndNanos(dateValue, timeNanos); + } + + /** + * Convert a local datetime in millis to an encoded date. + * + * @param ms + * the milliseconds + * @return the date value + */ + public static long dateValueFromLocalMillis(long ms) { + long absoluteDay = ms / MILLIS_PER_DAY; + // Round toward negative infinity + if (ms < 0 && (absoluteDay * MILLIS_PER_DAY != ms)) { + absoluteDay--; + } + return DateTimeUtils.dateValueFromAbsoluteDay(absoluteDay); + } + + /** + * Convert a time in milliseconds in local time to the nanoseconds since + * midnight. + * + * @param ms + * the milliseconds + * @return the nanoseconds + */ + public static long nanosFromLocalMillis(long ms) { + ms %= MILLIS_PER_DAY; + if (ms < 0) { + ms += MILLIS_PER_DAY; + } + return ms * 1_000_000; + } + + /** + * Get the date value converted to the specified time zone. + * + * @param provider the cast information provider + * @param timeZone the target time zone + * @param value the value to convert + * @return the date + */ + public static Date toDate(CastDataProvider provider, TimeZone timeZone, Value value) { + return value != ValueNull.INSTANCE + ? new Date(getMillis(provider, timeZone, value.convertToDate(provider).getDateValue(), 0)) : null; + } + + /** + * Get the time value converted to the specified time zone. + * + * @param provider the cast information provider + * @param timeZone the target time zone + * @param value the value to convert + * @return the time + */ + public static Time toTime(CastDataProvider provider, TimeZone timeZone, Value value) { + switch (value.getValueType()) { + case Value.NULL: + return null; + default: + value = value.convertTo(TypeInfo.TYPE_TIME, provider); + //$FALL-THROUGH$ + case Value.TIME: + return new Time( + getMillis(provider, timeZone, DateTimeUtils.EPOCH_DATE_VALUE, ((ValueTime) value).getNanos())); + } + } + + /** + * Get the timestamp value converted to the specified time zone. + * + * @param provider the cast information provider + * @param timeZone the target time zone + * @param value the value to convert + * @return the timestamp + */ + public static Timestamp toTimestamp(CastDataProvider provider, TimeZone timeZone, Value value) { + switch (value.getValueType()) { + case Value.NULL: + return null; + default: + value = value.convertTo(TypeInfo.TYPE_TIMESTAMP, provider); + //$FALL-THROUGH$ + case Value.TIMESTAMP: { + ValueTimestamp v = (ValueTimestamp) value; + long timeNanos = v.getTimeNanos(); + Timestamp ts = new Timestamp(getMillis(provider, timeZone, v.getDateValue(), timeNanos)); + ts.setNanos((int) (timeNanos % NANOS_PER_SECOND)); + return ts; + } + case Value.TIMESTAMP_TZ: { + ValueTimestampTimeZone v = (ValueTimestampTimeZone) value; + long timeNanos = v.getTimeNanos(); + Timestamp ts = new Timestamp(DateTimeUtils.absoluteDayFromDateValue(v.getDateValue()) * MILLIS_PER_DAY + + timeNanos / 1_000_000 - v.getTimeZoneOffsetSeconds() * 1_000); + ts.setNanos((int) (timeNanos % NANOS_PER_SECOND)); + return ts; + } + } + } + + /** + * Calculate the milliseconds since 1970-01-01 (UTC) for the given date and + * time (in the specified timezone). + * + * @param provider the cast information provider + * @param tz the timezone of the parameters, or null for the default + * timezone + * @param dateValue date value + * @param timeNanos nanoseconds since midnight + * @return the number of milliseconds (UTC) + */ + public static long getMillis(CastDataProvider provider, TimeZone tz, long dateValue, long timeNanos) { + return (tz == null ? provider != null ? provider.currentTimeZone() : DateTimeUtils.getTimeZone() + : TimeZoneProvider.ofId(tz.getID())).getEpochSecondsFromLocal(dateValue, timeNanos) * 1_000 + + timeNanos / 1_000_000 % 1_000; + } + + /** + * Returns local time zone offset for a specified timestamp. + * + * @param provider the cast information provider + * @param ms milliseconds since Epoch in UTC + * @return local time zone offset + */ + public static int getTimeZoneOffsetMillis(CastDataProvider provider, long ms) { + long seconds = ms / 1_000; + // Round toward negative infinity + if (ms < 0 && (seconds * 1_000 != ms)) { + seconds--; + } + return (provider != null ? provider.currentTimeZone() : DateTimeUtils.getTimeZone()) + .getTimeZoneOffsetUTC(seconds) * 1_000; + } + + /** + * Convert a legacy Java object to a value. + * + * @param session + * the session + * @param x + * the value + * @return the value, or {@code null} if not supported + */ + public static Value legacyObjectToValue(CastDataProvider session, Object x) { + if (x instanceof Date) { + return fromDate(session, null, (Date) x); + } else if (x instanceof Time) { + return fromTime(session, null, (Time) x); + } else if (x instanceof Timestamp) { + return fromTimestamp(session, null, (Timestamp) x); + } else if (x instanceof java.util.Date) { + return fromTimestamp(session, ((java.util.Date) x).getTime(), 0); + } else if (x instanceof Calendar) { + Calendar gc = (Calendar) x; + long ms = gc.getTimeInMillis(); + return timestampFromLocalMillis(ms + gc.getTimeZone().getOffset(ms), 0); + } else { + return null; + } + } + + /** + * Converts the specified value to an object of the specified legacy type. + * + * @param the type + * @param type the class + * @param value the value + * @param provider the cast information provider + * @return an instance of the specified class, or {@code null} if not supported + */ + @SuppressWarnings("unchecked") + public static T valueToLegacyType(Class type, Value value, CastDataProvider provider) { + if (type == Date.class) { + return (T) toDate(provider, null, value); + } else if (type == Time.class) { + return (T) toTime(provider, null, value); + } else if (type == Timestamp.class) { + return (T) toTimestamp(provider, null, value); + } else if (type == java.util.Date.class) { + return (T) new java.util.Date(toTimestamp(provider, null, value).getTime()); + } else if (type == Calendar.class) { + GregorianCalendar calendar = new GregorianCalendar(); + calendar.setGregorianChange(PROLEPTIC_GREGORIAN_CHANGE); + calendar.setTime(toTimestamp(provider, calendar.getTimeZone(), value)); + return (T) calendar; + } else { + return null; + } + } + + /** + * Get the type information for the given legacy Java class. + * + * @param clazz + * the Java class + * @return the value type, or {@code null} if not supported + */ + public static TypeInfo legacyClassToType(Class clazz) { + if (Date.class.isAssignableFrom(clazz)) { + return TypeInfo.TYPE_DATE; + } else if (Time.class.isAssignableFrom(clazz)) { + return TypeInfo.TYPE_TIME; + } else if (java.util.Date.class.isAssignableFrom(clazz) || Calendar.class.isAssignableFrom(clazz)) { + return TypeInfo.TYPE_TIMESTAMP; + } else{ + return null; + } + } + +} diff --git a/h2/src/main/org/h2/util/MathUtils.java b/h2/src/main/org/h2/util/MathUtils.java new file mode 100644 index 0000000..3edb1de --- /dev/null +++ b/h2/src/main/org/h2/util/MathUtils.java @@ -0,0 +1,320 @@ +/* + * 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.util; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.util.concurrent.ThreadLocalRandom; + +/** + * This is a utility class with mathematical helper functions. + */ +public class MathUtils { + + /** + * The secure random object. + */ + static SecureRandom secureRandom; + + /** + * True if the secure random object is seeded. + */ + static volatile boolean seeded; + + private MathUtils() { + // utility class + } + + + /** + * Round the value up to the next block size. The block size must be a power + * of two. As an example, using the block size of 8, the following rounding + * operations are done: 0 stays 0; values 1..8 results in 8, 9..16 results + * in 16, and so on. + * + * @param x the value to be rounded + * @param blockSizePowerOf2 the block size + * @return the rounded value + */ + public static int roundUpInt(int x, int blockSizePowerOf2) { + return (x + blockSizePowerOf2 - 1) & -blockSizePowerOf2; + } + + /** + * Round the value up to the next block size. The block size must be a power + * of two. As an example, using the block size of 8, the following rounding + * operations are done: 0 stays 0; values 1..8 results in 8, 9..16 results + * in 16, and so on. + * + * @param x the value to be rounded + * @param blockSizePowerOf2 the block size + * @return the rounded value + */ + public static long roundUpLong(long x, long blockSizePowerOf2) { + return (x + blockSizePowerOf2 - 1) & -blockSizePowerOf2; + } + + private static synchronized SecureRandom getSecureRandom() { + if (secureRandom != null) { + return secureRandom; + } + // Workaround for SecureRandom problem as described in + // https://bugs.openjdk.java.net/browse/JDK-6202721 + // Can not do that in a static initializer block, because + // threads are not started until after the initializer block exits + try { + secureRandom = SecureRandom.getInstance("SHA1PRNG"); + // On some systems, secureRandom.generateSeed() is very slow. + // In this case it is initialized using our own seed implementation + // and afterwards (in the thread) using the regular algorithm. + Runnable runnable = () -> { + try { + SecureRandom sr = SecureRandom.getInstance("SHA1PRNG"); + byte[] seed = sr.generateSeed(20); + synchronized (secureRandom) { + secureRandom.setSeed(seed); + seeded = true; + } + } catch (Exception e) { + // NoSuchAlgorithmException + warn("SecureRandom", e); + } + }; + + try { + Thread t = new Thread(runnable, "Generate Seed"); + // let the process terminate even if generating the seed is + // really slow + t.setDaemon(true); + t.start(); + Thread.yield(); + try { + // normally, generateSeed takes less than 200 ms + t.join(400); + } catch (InterruptedException e) { + warn("InterruptedException", e); + } + if (!seeded) { + byte[] seed = generateAlternativeSeed(); + // this never reduces randomness + synchronized (secureRandom) { + secureRandom.setSeed(seed); + } + } + } catch (SecurityException e) { + // workaround for the Google App Engine: don't use a thread + runnable.run(); + generateAlternativeSeed(); + } + + } catch (Exception e) { + // NoSuchAlgorithmException + warn("SecureRandom", e); + secureRandom = new SecureRandom(); + } + return secureRandom; + } + + /** + * Generate a seed value, using as much unpredictable data as possible. + * + * @return the seed + */ + public static byte[] generateAlternativeSeed() { + try { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(bout); + + // milliseconds and nanoseconds + out.writeLong(System.currentTimeMillis()); + out.writeLong(System.nanoTime()); + + // memory + out.writeInt(new Object().hashCode()); + Runtime runtime = Runtime.getRuntime(); + out.writeLong(runtime.freeMemory()); + out.writeLong(runtime.maxMemory()); + out.writeLong(runtime.totalMemory()); + + // environment + try { + String s = System.getProperties().toString(); + // can't use writeUTF, as the string + // might be larger than 64 KB + out.writeInt(s.length()); + out.write(s.getBytes(StandardCharsets.UTF_8)); + } catch (Exception e) { + warn("generateAlternativeSeed", e); + } + + // host name and ip addresses (if any) + try { + // workaround for the Google App Engine: don't use InetAddress + Class inetAddressClass = Class.forName( + "java.net.InetAddress"); + Object localHost = inetAddressClass.getMethod( + "getLocalHost").invoke(null); + String hostName = inetAddressClass.getMethod( + "getHostName").invoke(localHost).toString(); + out.writeUTF(hostName); + Object[] list = (Object[]) inetAddressClass.getMethod( + "getAllByName", String.class).invoke(null, hostName); + Method getAddress = inetAddressClass.getMethod( + "getAddress"); + for (Object o : list) { + out.write((byte[]) getAddress.invoke(o)); + } + } catch (Throwable e) { + // on some system, InetAddress is not supported + // on some system, InetAddress.getLocalHost() doesn't work + // for some reason (incorrect configuration) + } + + // timing (a second thread is already running usually) + for (int j = 0; j < 16; j++) { + int i = 0; + long end = System.currentTimeMillis(); + while (end == System.currentTimeMillis()) { + i++; + } + out.writeInt(i); + } + + out.close(); + return bout.toByteArray(); + } catch (IOException e) { + warn("generateAlternativeSeed", e); + return new byte[1]; + } + } + + /** + * Print a message to system output if there was a problem initializing the + * random number generator. + * + * @param s the message to print + * @param t the stack trace + */ + static void warn(String s, Throwable t) { + // not a fatal problem, but maybe reduced security + System.out.println("Warning: " + s); + if (t != null) { + t.printStackTrace(); + } + } + + /** + * Get the value that is equal to or higher than this value, and that is a + * power of two. + * + * @param x the original value + * @return the next power of two value + * @throws IllegalArgumentException if x < 0 or x > 0x40000000 + */ + public static int nextPowerOf2(int x) throws IllegalArgumentException { + if (x + Integer.MIN_VALUE > (0x4000_0000 + Integer.MIN_VALUE)) { + throw new IllegalArgumentException("Argument out of range" + + " [0x0-0x40000000]. Argument was: " + x); + } + return x <= 1 ? 1 : (-1 >>> Integer.numberOfLeadingZeros(x - 1)) + 1; + } + + /** + * Convert a long value to an int value. Values larger than the biggest int + * value are converted to the biggest int value, and values smaller than the + * smallest int value are converted to the smallest int value. + * + * @param l the value to convert + * @return the converted int value + */ + public static int convertLongToInt(long l) { + if (l <= Integer.MIN_VALUE) { + return Integer.MIN_VALUE; + } else if (l >= Integer.MAX_VALUE) { + return Integer.MAX_VALUE; + } else { + return (int) l; + } + } + + /** + * Convert an int value to a short value. Values larger than the biggest + * short value are converted to the biggest short value, and values smaller + * than the smallest short value are converted to the smallest short value. + * + * @param i the value to convert + * @return the converted short value + */ + public static short convertIntToShort(int i) { + if (i <= Short.MIN_VALUE) { + return Short.MIN_VALUE; + } else if (i >= Short.MAX_VALUE) { + return Short.MAX_VALUE; + } else { + return (short) i; + } + } + + /** + * Get a cryptographically secure pseudo random long value. + * + * @return the random long value + */ + public static long secureRandomLong() { + return getSecureRandom().nextLong(); + } + + /** + * Get a number of pseudo random bytes. + * + * @param bytes the target array + */ + public static void randomBytes(byte[] bytes) { + ThreadLocalRandom.current().nextBytes(bytes); + } + + /** + * Get a number of cryptographically secure pseudo random bytes. + * + * @param len the number of bytes + * @return the random bytes + */ + public static byte[] secureRandomBytes(int len) { + if (len <= 0) { + len = 1; + } + byte[] buff = new byte[len]; + getSecureRandom().nextBytes(buff); + return buff; + } + + /** + * Get a pseudo random int value between 0 (including and the given value + * (excluding). The value is not cryptographically secure. + * + * @param lowerThan the value returned will be lower than this value + * @return the random long value + */ + public static int randomInt(int lowerThan) { + return ThreadLocalRandom.current().nextInt(lowerThan); + } + + /** + * Get a cryptographically secure pseudo random int value between 0 + * (including and the given value (excluding). + * + * @param lowerThan the value returned will be lower than this value + * @return the random long value + */ + public static int secureRandomInt(int lowerThan) { + return getSecureRandom().nextInt(lowerThan); + } + +} diff --git a/h2/src/main/org/h2/util/MemoryEstimator.java b/h2/src/main/org/h2/util/MemoryEstimator.java new file mode 100644 index 0000000..ed662a6 --- /dev/null +++ b/h2/src/main/org/h2/util/MemoryEstimator.java @@ -0,0 +1,195 @@ +/* + * 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.util; + +import static org.h2.engine.Constants.MEMORY_POINTER; + +import java.util.concurrent.atomic.AtomicLong; + +import org.h2.mvstore.type.DataType; + +/** + * Class MemoryEstimator. + * + * Calculation of the amount of memory occupied by keys, values and pages of the MVTable + * may become expensive operation for complex data types like Row. + * On the other hand, result of the calculation is used by page cache to limit it's size + * and determine when eviction is needed. Another usage is to trigger auto commit, + * based on amount of unsaved changes. In both cases reasonable (lets say ~30%) approximation + * would be good enough and will do the job. + * This class replaces exact calculation with an estimate based on + * a sliding window average of last 256 values. + * If estimation gets close to the exact value, then next N calculations are skipped + * and replaced with the estimate, where N depends on the estimation error. + * + * @author Andrei Tokar + */ +public final class MemoryEstimator { + + // Structure of statsData long value: + // 0 - 7 skip counter (how many more requests will skip calculation and use an estimate instead) + // 8 - 23 total number of skips between last 256 calculations + // (used for sampling percentage calculation only) + // 24 bit is 0 when window is not completely filled yet, 1 once it become full + // 25 - 31 unused + // 32 - 63 sliding window sum of estimated values + + private static final int SKIP_SUM_SHIFT = 8; + private static final int COUNTER_MASK = (1 << SKIP_SUM_SHIFT) - 1; + private static final int SKIP_SUM_MASK = 0xFFFF; + private static final int INIT_BIT_SHIFT = 24; + private static final int INIT_BIT = 1 << INIT_BIT_SHIFT; + private static final int WINDOW_SHIFT = 8; + private static final int MAGNITUDE_LIMIT = WINDOW_SHIFT - 1; + private static final int WINDOW_SIZE = 1 << WINDOW_SHIFT; + private static final int WINDOW_HALF_SIZE = WINDOW_SIZE >> 1; + private static final int SUM_SHIFT = 32; + + private MemoryEstimator() {} + + /** + * Estimates memory size of the data based on previous values. + * @param stats AtomicLong holding statistical data about the estimated sequence + * @param dataType used for calculation of the next sequence value, if necessary + * @param data which size is to be calculated as the next sequence value, if necessary + * @param type of the data + * @return next estimated or calculated value of the sequence + */ + public static int estimateMemory(AtomicLong stats, DataType dataType, T data) { + long statsData = stats.get(); + int counter = getCounter(statsData); + int skipSum = getSkipSum(statsData); + long initialized = statsData & INIT_BIT; + long sum = statsData >>> SUM_SHIFT; + int mem = 0; + int cnt = 0; + if (initialized == 0 || counter-- == 0) { + cnt = 1; + mem = data == null ? 0 : dataType.getMemory(data); + long delta = ((long) mem << WINDOW_SHIFT) - sum; + if (initialized == 0) { + if (++counter == WINDOW_SIZE) { + initialized = INIT_BIT; + } + sum = (sum * counter + delta + (counter >> 1)) / counter; + } else { + long absDelta = delta >= 0 ? delta : -delta; + int magnitude = calculateMagnitude(sum, absDelta); + sum += ((delta >> (MAGNITUDE_LIMIT - magnitude)) + 1) >> 1; + counter = ((1 << magnitude) - 1) & COUNTER_MASK; + + delta = (counter << WINDOW_SHIFT) - skipSum; + skipSum += (delta + WINDOW_HALF_SIZE) >> WINDOW_SHIFT; + } + } + long updatedStatsData = updateStatsData(stats, statsData, counter, skipSum, initialized, sum, cnt, mem); + return getAverage(updatedStatsData); + } + + /** + * Estimates memory size of the data set based on previous values. + * @param stats AtomicLong holding statistical data about the estimated sequence + * @param dataType used for calculation of the next sequence value, if necessary + * @param storage of the data set, which size is to be calculated + * @param count number of data items in the storage + * @param type of the data in the storage + * @return next estimated or calculated size of the storage + */ + public static int estimateMemory(AtomicLong stats, DataType dataType, T[] storage, int count) { + long statsData = stats.get(); + int counter = getCounter(statsData); + int skipSum = getSkipSum(statsData); + long initialized = statsData & INIT_BIT; + long sum = statsData >>> SUM_SHIFT; + int index = 0; + int memSum = 0; + if (initialized != 0 && counter >= count) { + counter -= count; + } else { + int cnt = count; + while (cnt-- > 0) { + T data = storage[index++]; + int mem = data == null ? 0 : dataType.getMemory(data); + memSum += mem; + long delta = ((long) mem << WINDOW_SHIFT) - sum; + if (initialized == 0) { + if (++counter == WINDOW_SIZE) { + initialized = INIT_BIT; + } + sum = (sum * counter + delta + (counter >> 1)) / counter; + } else { + cnt -= counter; + long absDelta = delta >= 0 ? delta : -delta; + int magnitude = calculateMagnitude(sum, absDelta); + sum += ((delta >> (MAGNITUDE_LIMIT - magnitude)) + 1) >> 1; + counter += ((1 << magnitude) - 1) & COUNTER_MASK; + + delta = ((long) counter << WINDOW_SHIFT) - skipSum; + skipSum += (delta + WINDOW_HALF_SIZE) >> WINDOW_SHIFT; + } + } + } + long updatedStatsData = updateStatsData(stats, statsData, counter, skipSum, initialized, sum, index, memSum); + return (getAverage(updatedStatsData) + MEMORY_POINTER) * count; + } + + /** + * Calculates percentage of how many times actual calculation happened (vs. estimation) + * @param stats AtomicLong holding statistical data about the estimated sequence + * @return sampling percentage in range 0 - 100 + */ + public static int samplingPct(AtomicLong stats) { + long statsData = stats.get(); + int count = (statsData & INIT_BIT) == 0 ? getCounter(statsData) : WINDOW_SIZE; + int total = getSkipSum(statsData) + count; + return (count * 100 + (total >> 1)) / total; + } + + private static int calculateMagnitude(long sum, long absDelta) { + int magnitude = 0; + while (absDelta < sum && magnitude < MAGNITUDE_LIMIT) { + ++magnitude; + absDelta <<= 1; + } + return magnitude; + } + + private static long updateStatsData(AtomicLong stats, long statsData, + int counter, int skipSum, long initialized, long sum, + int itemsCount, int itemsMem) { + return updateStatsData(stats, statsData, + constructStatsData(sum, initialized, skipSum, counter), itemsCount, itemsMem); + } + + private static long constructStatsData(long sum, long initialized, int skipSum, int counter) { + return (sum << SUM_SHIFT) | initialized | ((long) skipSum << SKIP_SUM_SHIFT) | counter; + } + + private static long updateStatsData(AtomicLong stats, long statsData, long updatedStatsData, + int itemsCount, int itemsMem) { + while (!stats.compareAndSet(statsData, updatedStatsData)) { + statsData = stats.get(); + long sum = statsData >>> SUM_SHIFT; + if (itemsCount > 0) { + sum += itemsMem - ((sum * itemsCount + WINDOW_HALF_SIZE) >> WINDOW_SHIFT); + } + updatedStatsData = (sum << SUM_SHIFT) | (statsData & (INIT_BIT | SKIP_SUM_MASK | COUNTER_MASK)); + } + return updatedStatsData; + } + + private static int getCounter(long statsData) { + return (int)(statsData & COUNTER_MASK); + } + + private static int getSkipSum(long statsData) { + return (int)((statsData >> SKIP_SUM_SHIFT) & SKIP_SUM_MASK); + } + + private static int getAverage(long updatedStatsData) { + return (int)(updatedStatsData >>> (SUM_SHIFT + WINDOW_SHIFT)); + } +} diff --git a/h2/src/main/org/h2/util/MemoryUnmapper.java b/h2/src/main/org/h2/util/MemoryUnmapper.java new file mode 100644 index 0000000..4994263 --- /dev/null +++ b/h2/src/main/org/h2/util/MemoryUnmapper.java @@ -0,0 +1,92 @@ +/* + * 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.util; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.nio.ByteBuffer; + +import org.h2.engine.SysProperties; + +/** + * Unsafe memory unmapper. + * + * @see SysProperties#NIO_CLEANER_HACK + */ +public final class MemoryUnmapper { + + private static final boolean ENABLED; + + private static final Object UNSAFE; + + private static final Method INVOKE_CLEANER; + + static { + boolean enabled = SysProperties.NIO_CLEANER_HACK; + Object unsafe = null; + Method invokeCleaner = null; + if (enabled) { + try { + Class clazz = Class.forName("sun.misc.Unsafe"); + Field field = clazz.getDeclaredField("theUnsafe"); + field.setAccessible(true); + unsafe = field.get(null); + // This method exists only on Java 9 and later versions + invokeCleaner = clazz.getMethod("invokeCleaner", ByteBuffer.class); + } catch (ReflectiveOperationException e) { + // Java 8 + unsafe = null; + // invokeCleaner can be only null here + } catch (Throwable e) { + // Should be a SecurityException, but catch everything to be + // safe + enabled = false; + unsafe = null; + // invokeCleaner can be only null here + } + } + ENABLED = enabled; + UNSAFE = unsafe; + INVOKE_CLEANER = invokeCleaner; + } + + /** + * Tries to unmap memory for the specified byte buffer using Java internals + * in unsafe way if {@link SysProperties#NIO_CLEANER_HACK} is enabled and + * access is not denied by a security manager. + * + * @param buffer + * mapped byte buffer + * @return whether operation was successful + */ + public static boolean unmap(ByteBuffer buffer) { + if (!ENABLED) { + return false; + } + try { + if (INVOKE_CLEANER != null) { + // Java 9 or later + INVOKE_CLEANER.invoke(UNSAFE, buffer); + return true; + } + // Java 8 + Method cleanerMethod = buffer.getClass().getMethod("cleaner"); + cleanerMethod.setAccessible(true); + Object cleaner = cleanerMethod.invoke(buffer); + if (cleaner != null) { + Method clearMethod = cleaner.getClass().getMethod("clean"); + clearMethod.invoke(cleaner); + } + return true; + } catch (Throwable e) { + return false; + } + } + + private MemoryUnmapper() { + } + +} diff --git a/h2/src/main/org/h2/util/NetUtils.java b/h2/src/main/org/h2/util/NetUtils.java new file mode 100644 index 0000000..972ff6f --- /dev/null +++ b/h2/src/main/org/h2/util/NetUtils.java @@ -0,0 +1,399 @@ +/* + * 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.util; + +import java.io.IOException; +import java.net.BindException; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; + +import org.h2.api.ErrorCode; +import org.h2.engine.SysProperties; +import org.h2.message.DbException; +import org.h2.security.CipherFactory; + +/** + * This utility class contains socket helper functions. + */ +public class NetUtils { + + private static final int CACHE_MILLIS = 1000; + private static InetAddress cachedBindAddress; + private static String cachedLocalAddress; + private static long cachedLocalAddressTime; + + private NetUtils() { + // utility class + } + + /** + * Create a loopback socket (a socket that is connected to localhost) on + * this port. + * + * @param port the port + * @param ssl if SSL should be used + * @return the socket + * @throws IOException on failure + */ + public static Socket createLoopbackSocket(int port, boolean ssl) + throws IOException { + String local = getLocalAddress(); + try { + return createSocket(local, port, ssl); + } catch (IOException e) { + try { + return createSocket("localhost", port, ssl); + } catch (IOException e2) { + // throw the original exception + throw e; + } + } + } + + /** + * Create a client socket that is connected to the given address and port. + * + * @param server to connect to (including an optional port) + * @param defaultPort the default port (if not specified in the server + * address) + * @param ssl if SSL should be used + * @return the socket + * @throws IOException on failure + */ + public static Socket createSocket(String server, int defaultPort, boolean ssl) throws IOException { + return createSocket(server, defaultPort, ssl, 0); + } + + /** + * Create a client socket that is connected to the given address and port. + * + * @param server to connect to (including an optional port) + * @param defaultPort the default port (if not specified in the server + * address) + * @param ssl if SSL should be used + * @param networkTimeout socket so timeout + * @return the socket + * @throws IOException on failure + */ + public static Socket createSocket(String server, int defaultPort, + boolean ssl, int networkTimeout) throws IOException { + int port = defaultPort; + // IPv6: RFC 2732 format is '[a:b:c:d:e:f:g:h]' or + // '[a:b:c:d:e:f:g:h]:port' + // RFC 2396 format is 'a.b.c.d' or 'a.b.c.d:port' or 'hostname' or + // 'hostname:port' + int startIndex = server.startsWith("[") ? server.indexOf(']') : 0; + int idx = server.indexOf(':', startIndex); + if (idx >= 0) { + port = Integer.decode(server.substring(idx + 1)); + server = server.substring(0, idx); + } + InetAddress address = InetAddress.getByName(server); + return createSocket(address, port, ssl, networkTimeout); + } + + /** + * Create a client socket that is connected to the given address and port. + * + * @param address the address to connect to + * @param port the port + * @param ssl if SSL should be used + * @return the socket + * @throws IOException on failure + */ + public static Socket createSocket(InetAddress address, int port, boolean ssl) + throws IOException { + return createSocket(address, port, ssl, 0); + } + /** + * Create a client socket that is connected to the given address and port. + * + * @param address the address to connect to + * @param port the port + * @param ssl if SSL should be used + * @param networkTimeout socket so timeout + * @return the socket + * @throws IOException on failure + */ + public static Socket createSocket(InetAddress address, int port, boolean ssl, int networkTimeout) + throws IOException { + long start = System.nanoTime(); + for (int i = 0;; i++) { + try { + if (ssl) { + return CipherFactory.createSocket(address, port); + } + Socket socket = new Socket(); + socket.setSoTimeout(networkTimeout); + socket.connect(new InetSocketAddress(address, port), + SysProperties.SOCKET_CONNECT_TIMEOUT); + return socket; + } catch (IOException e) { + if (System.nanoTime() - start >= SysProperties.SOCKET_CONNECT_TIMEOUT * 1_000_000L) { + // either it was a connect timeout, + // or list of different exceptions + throw e; + } + if (i >= SysProperties.SOCKET_CONNECT_RETRY) { + throw e; + } + // wait a bit and retry + try { + // sleep at most 256 ms + long sleep = Math.min(256, i * i); + Thread.sleep(sleep); + } catch (InterruptedException e2) { + // ignore + } + } + } + } + + /** + * Create a server socket. The system property h2.bindAddress is used if + * set. If SSL is used and h2.enableAnonymousTLS is true, an attempt is + * made to modify the security property jdk.tls.legacyAlgorithms + * (in newer JVMs) to allow anonymous TLS. + *

+ * This system change is effectively permanent for the lifetime of the JVM. + * @see CipherFactory#removeAnonFromLegacyAlgorithms() + * + * @param port the port to listen on + * @param ssl if SSL should be used + * @return the server socket + */ + public static ServerSocket createServerSocket(int port, boolean ssl) { + try { + return createServerSocketTry(port, ssl); + } catch (Exception e) { + // try again + return createServerSocketTry(port, ssl); + } + } + + /** + * Get the bind address if the system property h2.bindAddress is set, or + * null if not. + * + * @return the bind address + */ + private static InetAddress getBindAddress() throws UnknownHostException { + String host = SysProperties.BIND_ADDRESS; + if (host == null || host.isEmpty()) { + return null; + } + synchronized (NetUtils.class) { + if (cachedBindAddress == null) { + cachedBindAddress = InetAddress.getByName(host); + } + } + return cachedBindAddress; + } + + private static ServerSocket createServerSocketTry(int port, boolean ssl) { + try { + InetAddress bindAddress = getBindAddress(); + if (ssl) { + return CipherFactory.createServerSocket(port, bindAddress); + } + if (bindAddress == null) { + return new ServerSocket(port); + } + return new ServerSocket(port, 0, bindAddress); + } catch (BindException be) { + throw DbException.get(ErrorCode.EXCEPTION_OPENING_PORT_2, + be, Integer.toString(port), be.toString()); + } catch (IOException e) { + throw DbException.convertIOException(e, "port: " + port + " ssl: " + ssl); + } + } + + /** + * Check if a socket is connected to a local address. + * + * @param socket the socket + * @return true if it is + * @throws UnknownHostException on failure + */ + public static boolean isLocalAddress(Socket socket) + throws UnknownHostException { + InetAddress test = socket.getInetAddress(); + if (test.isLoopbackAddress()) { + return true; + } + InetAddress localhost = InetAddress.getLocalHost(); + // localhost.getCanonicalHostName() is very slow + String host = localhost.getHostAddress(); + for (InetAddress addr : InetAddress.getAllByName(host)) { + if (test.equals(addr)) { + return true; + } + } + return false; + } + + /** + * Close a server socket and ignore any exceptions. + * + * @param socket the socket + * @return null + */ + public static ServerSocket closeSilently(ServerSocket socket) { + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + // ignore + } + } + return null; + } + + /** + * Get the local host address as a string. + * For performance, the result is cached for one second. + * + * @return the local host address + */ + public static synchronized String getLocalAddress() { + long now = System.nanoTime(); + if (cachedLocalAddress != null && now - cachedLocalAddressTime < CACHE_MILLIS * 1_000_000L) { + return cachedLocalAddress; + } + InetAddress bind = null; + boolean useLocalhost = false; + try { + bind = getBindAddress(); + if (bind == null) { + useLocalhost = true; + } + } catch (UnknownHostException e) { + // ignore + } + if (useLocalhost) { + try { + bind = InetAddress.getLocalHost(); + } catch (UnknownHostException e) { + throw DbException.convert(e); + } + } + String address; + if (bind == null) { + address = "localhost"; + } else { + address = bind.getHostAddress(); + if (bind instanceof Inet6Address) { + if (address.indexOf('%') >= 0) { + address = "localhost"; + } else if (address.indexOf(':') >= 0 && !address.startsWith("[")) { + // adds'[' and ']' if required for + // Inet6Address that contain a ':'. + address = "[" + address + "]"; + } + } + } + if (address.equals("127.0.0.1")) { + address = "localhost"; + } + cachedLocalAddress = address; + cachedLocalAddressTime = now; + return address; + } + + /** + * Get the host name of a local address, if available. + * + * @param localAddress the local address + * @return the host name, or another text if not available + */ + public static String getHostName(String localAddress) { + try { + InetAddress addr = InetAddress.getByName(localAddress); + return addr.getHostName(); + } catch (Exception e) { + return "unknown"; + } + } + + /** + * Appends short representation of the specified IP address to the string + * builder. + * + * @param builder + * string builder to append to, or {@code null} + * @param address + * IP address + * @param addBrackets + * if ({@code true}, add brackets around IPv6 addresses + * @return the specified or the new string builder with short representation + * of specified address + */ + public static StringBuilder ipToShortForm(StringBuilder builder, byte[] address, boolean addBrackets) { + switch (address.length) { + case 4: + if (builder == null) { + builder = new StringBuilder(15); + } + builder // + .append(address[0] & 0xff).append('.') // + .append(address[1] & 0xff).append('.') // + .append(address[2] & 0xff).append('.') // + .append(address[3] & 0xff); + break; + case 16: + short[] a = new short[8]; + int maxStart = 0, maxLen = 0, currentLen = 0; + for (int i = 0, offset = 0; i < 8; i++) { + if ((a[i] = (short) ((address[offset++] & 0xff) << 8 | address[offset++] & 0xff)) == 0) { + currentLen++; + if (currentLen > maxLen) { + maxLen = currentLen; + maxStart = i - currentLen + 1; + } + } else { + currentLen = 0; + } + } + if (builder == null) { + builder = new StringBuilder(addBrackets ? 41 : 39); + } + if (addBrackets) { + builder.append('['); + } + int start; + if (maxLen > 1) { + for (int i = 0; i < maxStart; i++) { + builder.append(Integer.toHexString(a[i] & 0xffff)).append(':'); + } + if (maxStart == 0) { + builder.append(':'); + } + builder.append(':'); + start = maxStart + maxLen; + } else { + start = 0; + } + for (int i = start; i < 8; i++) { + builder.append(Integer.toHexString(a[i] & 0xffff)); + if (i < 7) { + builder.append(':'); + } + } + if (addBrackets) { + builder.append(']'); + } + break; + default: + StringUtils.convertBytesToHex(builder, address); + } + return builder; + } + +} diff --git a/h2/src/main/org/h2/util/NetworkConnectionInfo.java b/h2/src/main/org/h2/util/NetworkConnectionInfo.java new file mode 100644 index 0000000..d562dc7 --- /dev/null +++ b/h2/src/main/org/h2/util/NetworkConnectionInfo.java @@ -0,0 +1,104 @@ +/* + * 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.util; + +import java.net.InetAddress; +import java.net.UnknownHostException; + +/** + * Network connection information. + */ +public final class NetworkConnectionInfo { + + private final String server; + + private final byte[] clientAddr; + + private final int clientPort; + + private final String clientInfo; + + /** + * Creates new instance of network connection information. + * + * @param server + * the protocol and port of the server + * @param clientAddr + * the client address + * @param clientPort + * the client port + * @throws UnknownHostException + * if clientAddr cannot be resolved + */ + public NetworkConnectionInfo(String server, String clientAddr, int clientPort) throws UnknownHostException { + this(server, InetAddress.getByName(clientAddr).getAddress(), clientPort, null); + } + + /** + * Creates new instance of network connection information. + * + * @param server + * the protocol and port of the server + * @param clientAddr + * the client address + * @param clientPort + * the client port + * @param clientInfo + * additional client information, or {@code null} + */ + public NetworkConnectionInfo(String server, byte[] clientAddr, int clientPort, String clientInfo) { + this.server = server; + this.clientAddr = clientAddr; + this.clientPort = clientPort; + this.clientInfo = clientInfo; + } + + /** + * Returns the protocol and port of the server. + * + * @return the protocol and port of the server + */ + public String getServer() { + return server; + } + + /** + * Returns the client address. + * + * @return the client address + */ + public byte[] getClientAddr() { + return clientAddr; + } + + /** + * Returns the client port. + * + * @return the client port + */ + public int getClientPort() { + return clientPort; + } + + /** + * Returns additional client information, or {@code null}. + * + * @return additional client information, or {@code null} + */ + public String getClientInfo() { + return clientInfo; + } + + /** + * Returns the client address and port. + * + * @return the client address and port + */ + public String getClient() { + return NetUtils.ipToShortForm(new StringBuilder(), clientAddr, true).append(':').append(clientPort).toString(); + } + +} diff --git a/h2/src/main/org/h2/util/OsgiDataSourceFactory.java b/h2/src/main/org/h2/util/OsgiDataSourceFactory.java new file mode 100644 index 0000000..002f605 --- /dev/null +++ b/h2/src/main/org/h2/util/OsgiDataSourceFactory.java @@ -0,0 +1,306 @@ +/* + * 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.util; + +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.Hashtable; +import java.util.Properties; +import javax.sql.ConnectionPoolDataSource; +import javax.sql.DataSource; +import javax.sql.XADataSource; +import org.h2.engine.Constants; +import org.h2.jdbcx.JdbcDataSource; +import org.osgi.framework.BundleContext; +import org.osgi.service.jdbc.DataSourceFactory; + +/** + * This class implements the OSGi DataSourceFactory interface for the H2 JDBC + * driver. The following standard configuration properties are supported: + * {@link #JDBC_USER}, {@link #JDBC_PASSWORD}, {@link #JDBC_DESCRIPTION}, + * {@link #JDBC_DATASOURCE_NAME}, {@link #JDBC_NETWORK_PROTOCOL}, + * {@link #JDBC_URL}, {@link #JDBC_SERVER_NAME}, {@link #JDBC_PORT_NUMBER}. The + * following standard configuration properties are not supported: + * {@link #JDBC_ROLE_NAME}, {@link #JDBC_DATABASE_NAME}, + * {@link #JDBC_INITIAL_POOL_SIZE}, {@link #JDBC_MAX_POOL_SIZE}, + * {@link #JDBC_MIN_POOL_SIZE}, {@link #JDBC_MAX_IDLE_TIME}, + * {@link #JDBC_MAX_STATEMENTS}, {@link #JDBC_PROPERTY_CYCLE}. Any other + * property will be treated as a H2 specific option. If the {@link #JDBC_URL} + * property is passed to any of the DataSource factories, the following + * properties will be ignored: {@link #JDBC_DATASOURCE_NAME}, + * {@link #JDBC_NETWORK_PROTOCOL}, {@link #JDBC_SERVER_NAME}, + * {@link #JDBC_PORT_NUMBER}. + * + * @author Per Otterstrom + */ +public class OsgiDataSourceFactory implements DataSourceFactory { + private final org.h2.Driver driver; + + public OsgiDataSourceFactory(org.h2.Driver driver) { + this.driver = driver; + } + + /** + * Creates a basic data source. + * + * @param properties the properties for the data source. + * @throws SQLException if unsupported properties are supplied, or if data + * source can not be created. + * @return a new data source. + */ + @Override + public DataSource createDataSource(Properties properties) + throws SQLException { + // Make copy of properties + Properties propertiesCopy = new Properties(); + if (properties != null) { + propertiesCopy.putAll(properties); + } + + // Verify that no unsupported standard options are used + rejectUnsupportedOptions(propertiesCopy); + + // Standard pool properties in OSGi not applicable here + rejectPoolingOptions(propertiesCopy); + + JdbcDataSource dataSource = new JdbcDataSource(); + + setupH2DataSource(dataSource, propertiesCopy); + + return dataSource; + } + + /** + * Creates a pooled data source. + * + * @param properties the properties for the data source. + * @throws SQLException if unsupported properties are supplied, or if data + * source can not be created. + * @return a new data source. + */ + @Override + public ConnectionPoolDataSource createConnectionPoolDataSource( + Properties properties) throws SQLException { + // Make copy of properties + Properties propertiesCopy = new Properties(); + if (properties != null) { + propertiesCopy.putAll(properties); + } + + // Verify that no unsupported standard options are used + rejectUnsupportedOptions(propertiesCopy); + + // The integrated connection pool is H2 is not configurable + rejectPoolingOptions(propertiesCopy); + + JdbcDataSource dataSource = new JdbcDataSource(); + + setupH2DataSource(dataSource, propertiesCopy); + + return dataSource; + } + + /** + * Creates a pooled XA data source. + * + * @param properties the properties for the data source. + * @throws SQLException if unsupported properties are supplied, or if data + * source can not be created. + * @return a new data source. + */ + @Override + public XADataSource createXADataSource(Properties properties) + throws SQLException { + // Make copy of properties + Properties propertiesCopy = new Properties(); + if (properties != null) { + propertiesCopy.putAll(properties); + } + + // Verify that no unsupported standard options are used + rejectUnsupportedOptions(propertiesCopy); + + // The integrated connection pool is H2 is not configurable + rejectPoolingOptions(propertiesCopy); + + JdbcDataSource dataSource = new JdbcDataSource(); + + setupH2DataSource(dataSource, propertiesCopy); + + return dataSource; + } + + /** + * Returns a driver. The H2 driver does not support any properties. + * + * @param properties must be null or empty list. + * @throws SQLException if any property is supplied. + * @return a driver. + */ + @Override + public java.sql.Driver createDriver(Properties properties) + throws SQLException { + if (properties != null && !properties.isEmpty()) { + // No properties supported + throw new SQLException(); + } + return driver; + } + + /** + * Checker method that will throw if any unsupported standard OSGi options + * is present. + * + * @param p the properties to check + * @throws SQLFeatureNotSupportedException if unsupported properties are + * present + */ + private static void rejectUnsupportedOptions(Properties p) + throws SQLFeatureNotSupportedException { + // Unsupported standard properties in OSGi + if (p.containsKey(DataSourceFactory.JDBC_ROLE_NAME)) { + throw new SQLFeatureNotSupportedException("The " + + DataSourceFactory.JDBC_ROLE_NAME + + " property is not supported by H2"); + } + if (p.containsKey(DataSourceFactory.JDBC_DATASOURCE_NAME)) { + throw new SQLFeatureNotSupportedException("The " + + DataSourceFactory.JDBC_DATASOURCE_NAME + + " property is not supported by H2"); + } + } + + /** + * Applies common OSGi properties to a H2 data source. Non standard + * properties will be applied as H2 options. + * + * @param dataSource the data source to configure + * @param p the properties to apply to the data source + */ + private static void setupH2DataSource(JdbcDataSource dataSource, + Properties p) { + // Setting user and password + if (p.containsKey(DataSourceFactory.JDBC_USER)) { + dataSource.setUser((String) p.remove(DataSourceFactory.JDBC_USER)); + } + if (p.containsKey(DataSourceFactory.JDBC_PASSWORD)) { + dataSource.setPassword((String) p + .remove(DataSourceFactory.JDBC_PASSWORD)); + } + + // Setting description + if (p.containsKey(DataSourceFactory.JDBC_DESCRIPTION)) { + dataSource.setDescription((String) p + .remove(DataSourceFactory.JDBC_DESCRIPTION)); + } + + // Setting URL + StringBuilder connectionUrl = new StringBuilder(); + if (p.containsKey(DataSourceFactory.JDBC_URL)) { + // Use URL if specified + connectionUrl.append(p.remove(DataSourceFactory.JDBC_URL)); + // Remove individual properties + p.remove(DataSourceFactory.JDBC_NETWORK_PROTOCOL); + p.remove(DataSourceFactory.JDBC_SERVER_NAME); + p.remove(DataSourceFactory.JDBC_PORT_NUMBER); + p.remove(DataSourceFactory.JDBC_DATABASE_NAME); + } else { + // Creating URL from individual properties + connectionUrl.append(Constants.START_URL); + + // Set network protocol (tcp/ssl) or DB type (mem/file) + String protocol = ""; + if (p.containsKey(DataSourceFactory.JDBC_NETWORK_PROTOCOL)) { + protocol = (String) p.remove(DataSourceFactory.JDBC_NETWORK_PROTOCOL); + connectionUrl.append(protocol).append(":"); + } + + // Host name and/or port + if (p.containsKey(DataSourceFactory.JDBC_SERVER_NAME)) { + connectionUrl.append("//").append( + p.remove(DataSourceFactory.JDBC_SERVER_NAME)); + + if (p.containsKey(DataSourceFactory.JDBC_PORT_NUMBER)) { + connectionUrl.append(":").append( + p.remove(DataSourceFactory.JDBC_PORT_NUMBER)); + } + + connectionUrl.append("/"); + } else if (p.containsKey( + DataSourceFactory.JDBC_PORT_NUMBER)) { + // Assume local host if only port was set + connectionUrl + .append("//localhost:") + .append(p.remove(DataSourceFactory.JDBC_PORT_NUMBER)) + .append("/"); + } else if (protocol.equals("tcp") || protocol.equals("ssl")) { + // Assume local host if network protocol is set, but no host or + // port is set + connectionUrl.append("//localhost/"); + } + + // DB path and name + if (p.containsKey(DataSourceFactory.JDBC_DATABASE_NAME)) { + connectionUrl.append( + p.remove(DataSourceFactory.JDBC_DATABASE_NAME)); + } + } + + // Add remaining properties as options + for (Object option : p.keySet()) { + connectionUrl.append(";").append(option).append("=") + .append(p.get(option)); + } + + if (connectionUrl.length() > Constants.START_URL.length()) { + dataSource.setURL(connectionUrl.toString()); + } + } + + /** + * Checker method that will throw if any pooling related standard OSGi + * options are present. + * + * @param p the properties to check + * @throws SQLFeatureNotSupportedException if unsupported properties are + * present + */ + private static void rejectPoolingOptions(Properties p) + throws SQLFeatureNotSupportedException { + if (p.containsKey(DataSourceFactory.JDBC_INITIAL_POOL_SIZE) || + p.containsKey(DataSourceFactory.JDBC_MAX_IDLE_TIME) || + p.containsKey(DataSourceFactory.JDBC_MAX_POOL_SIZE) || + p.containsKey(DataSourceFactory.JDBC_MAX_STATEMENTS) || + p.containsKey(DataSourceFactory.JDBC_MIN_POOL_SIZE) || + p.containsKey(DataSourceFactory.JDBC_PROPERTY_CYCLE)) { + throw new SQLFeatureNotSupportedException( + "Pooling properties are not supported by H2"); + } + } + + /** + * Register the H2 JDBC driver service. + * + * @param bundleContext the bundle context + * @param driver the driver + */ + static void registerService(BundleContext bundleContext, + org.h2.Driver driver) { + Hashtable properties = new Hashtable<>(); + properties.put( + DataSourceFactory.OSGI_JDBC_DRIVER_CLASS, + org.h2.Driver.class.getName()); + properties.put( + DataSourceFactory.OSGI_JDBC_DRIVER_NAME, + "H2 JDBC Driver"); + properties.put( + DataSourceFactory.OSGI_JDBC_DRIVER_VERSION, + Constants.FULL_VERSION); + bundleContext.registerService( + DataSourceFactory.class.getName(), + new OsgiDataSourceFactory(driver), properties); + } +} diff --git a/h2/src/main/org/h2/util/ParserUtil.java b/h2/src/main/org/h2/util/ParserUtil.java new file mode 100644 index 0000000..95498a4 --- /dev/null +++ b/h2/src/main/org/h2/util/ParserUtil.java @@ -0,0 +1,695 @@ +/* + * 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.util; + +import java.util.HashMap; + +public class ParserUtil { + + /** + * A keyword. + */ + public static final int KEYWORD = 1; + + /** + * An identifier (table name, column name,...). + */ + public static final int IDENTIFIER = 2; + + // Constants below must be sorted + + /** + * The token "ALL". + */ + public static final int ALL = IDENTIFIER + 1; + + /** + * The token "AND". + */ + public static final int AND = ALL + 1; + + /** + * The token "ANY". + */ + public static final int ANY = AND + 1; + + /** + * The token "ARRAY". + */ + public static final int ARRAY = ANY + 1; + + /** + * The token "AS". + */ + public static final int AS = ARRAY + 1; + + /** + * The token "ASYMMETRIC". + */ + public static final int ASYMMETRIC = AS + 1; + + /** + * The token "AUTHORIZATION". + */ + public static final int AUTHORIZATION = ASYMMETRIC + 1; + + /** + * The token "BETWEEN". + */ + public static final int BETWEEN = AUTHORIZATION + 1; + + /** + * The token "CASE". + */ + public static final int CASE = BETWEEN + 1; + + /** + * The token "CAST". + */ + public static final int CAST = CASE + 1; + + /** + * The token "CHECK". + */ + public static final int CHECK = CAST + 1; + + /** + * The token "CONSTRAINT". + */ + public static final int CONSTRAINT = CHECK + 1; + + /** + * The token "CROSS". + */ + public static final int CROSS = CONSTRAINT + 1; + + /** + * The token "CURRENT_CATALOG". + */ + public static final int CURRENT_CATALOG = CROSS + 1; + + /** + * The token "CURRENT_DATE". + */ + public static final int CURRENT_DATE = CURRENT_CATALOG + 1; + + /** + * The token "CURRENT_PATH". + */ + public static final int CURRENT_PATH = CURRENT_DATE + 1; + + /** + * The token "CURRENT_ROLE". + */ + public static final int CURRENT_ROLE = CURRENT_PATH + 1; + + /** + * The token "CURRENT_SCHEMA". + */ + public static final int CURRENT_SCHEMA = CURRENT_ROLE + 1; + + /** + * The token "CURRENT_TIME". + */ + public static final int CURRENT_TIME = CURRENT_SCHEMA + 1; + + /** + * The token "CURRENT_TIMESTAMP". + */ + public static final int CURRENT_TIMESTAMP = CURRENT_TIME + 1; + + /** + * The token "CURRENT_USER". + */ + public static final int CURRENT_USER = CURRENT_TIMESTAMP + 1; + + /** + * The token "DAY". + */ + public static final int DAY = CURRENT_USER + 1; + + /** + * The token "DEFAULT". + */ + public static final int DEFAULT = DAY + 1; + + /** + * The token "DISTINCT". + */ + public static final int DISTINCT = DEFAULT + 1; + + /** + * The token "ELSE". + */ + public static final int ELSE = DISTINCT + 1; + + /** + * The token "END". + */ + public static final int END = ELSE + 1; + + /** + * The token "EXCEPT". + */ + public static final int EXCEPT = END + 1; + + /** + * The token "EXISTS". + */ + public static final int EXISTS = EXCEPT + 1; + + /** + * The token "FALSE". + */ + public static final int FALSE = EXISTS + 1; + + /** + * The token "FETCH". + */ + public static final int FETCH = FALSE + 1; + + /** + * The token "FOR". + */ + public static final int FOR = FETCH + 1; + + /** + * The token "FOREIGN". + */ + public static final int FOREIGN = FOR + 1; + + /** + * The token "FROM". + */ + public static final int FROM = FOREIGN + 1; + + /** + * The token "FULL". + */ + public static final int FULL = FROM + 1; + + /** + * The token "GROUP". + */ + public static final int GROUP = FULL + 1; + + /** + * The token "HAVING". + */ + public static final int HAVING = GROUP + 1; + + /** + * The token "HOUR". + */ + public static final int HOUR = HAVING + 1; + + /** + * The token "IF". + */ + public static final int IF = HOUR + 1; + + /** + * The token "IN". + */ + public static final int IN = IF + 1; + + /** + * The token "INNER". + */ + public static final int INNER = IN + 1; + + /** + * The token "INTERSECT". + */ + public static final int INTERSECT = INNER + 1; + + /** + * The token "INTERVAL". + */ + public static final int INTERVAL = INTERSECT + 1; + + /** + * The token "IS". + */ + public static final int IS = INTERVAL + 1; + + /** + * The token "JOIN". + */ + public static final int JOIN = IS + 1; + + /** + * The token "KEY". + */ + public static final int KEY = JOIN + 1; + + /** + * The token "LEFT". + */ + public static final int LEFT = KEY + 1; + + /** + * The token "LIKE". + */ + public static final int LIKE = LEFT + 1; + + /** + * The token "LIMIT". + */ + public static final int LIMIT = LIKE + 1; + + /** + * The token "LOCALTIME". + */ + public static final int LOCALTIME = LIMIT + 1; + + /** + * The token "LOCALTIMESTAMP". + */ + public static final int LOCALTIMESTAMP = LOCALTIME + 1; + + /** + * The token "MINUS". + */ + public static final int MINUS = LOCALTIMESTAMP + 1; + + /** + * The token "MINUTE". + */ + public static final int MINUTE = MINUS + 1; + + /** + * The token "MONTH". + */ + public static final int MONTH = MINUTE + 1; + + /** + * The token "NATURAL". + */ + public static final int NATURAL = MONTH + 1; + + /** + * The token "NOT". + */ + public static final int NOT = NATURAL + 1; + + /** + * The token "NULL". + */ + public static final int NULL = NOT + 1; + + /** + * The token "OFFSET". + */ + public static final int OFFSET = NULL + 1; + + /** + * The token "ON". + */ + public static final int ON = OFFSET + 1; + + /** + * The token "OR". + */ + public static final int OR = ON + 1; + + /** + * The token "ORDER". + */ + public static final int ORDER = OR + 1; + + /** + * The token "PRIMARY". + */ + public static final int PRIMARY = ORDER + 1; + + /** + * The token "QUALIFY". + */ + public static final int QUALIFY = PRIMARY + 1; + + /** + * The token "RIGHT". + */ + public static final int RIGHT = QUALIFY + 1; + + /** + * The token "ROW". + */ + public static final int ROW = RIGHT + 1; + + /** + * The token "ROWNUM". + */ + public static final int ROWNUM = ROW + 1; + + /** + * The token "SECOND". + */ + public static final int SECOND = ROWNUM + 1; + + /** + * The token "SELECT". + */ + public static final int SELECT = SECOND + 1; + + /** + * The token "SESSION_USER". + */ + public static final int SESSION_USER = SELECT + 1; + + /** + * The token "SET". + */ + public static final int SET = SESSION_USER + 1; + + /** + * The token "SOME". + */ + public static final int SOME = SET + 1; + + /** + * The token "SYMMETRIC". + */ + public static final int SYMMETRIC = SOME + 1; + + /** + * The token "SYSTEM_USER". + */ + public static final int SYSTEM_USER = SYMMETRIC + 1; + + /** + * The token "TABLE". + */ + public static final int TABLE = SYSTEM_USER + 1; + + /** + * The token "TO". + */ + public static final int TO = TABLE + 1; + + /** + * The token "TRUE". + */ + public static final int TRUE = TO + 1; + + /** + * The token "UESCAPE". + */ + public static final int UESCAPE = TRUE + 1; + + /** + * The token "UNION". + */ + public static final int UNION = UESCAPE + 1; + + /** + * The token "UNIQUE". + */ + public static final int UNIQUE = UNION + 1; + + /** + * The token "UNKNOWN". + */ + public static final int UNKNOWN = UNIQUE + 1; + + /** + * The token "USER". + */ + public static final int USER = UNKNOWN + 1; + + /** + * The token "USING". + */ + public static final int USING = USER + 1; + + /** + * The token "VALUE". + */ + public static final int VALUE = USING + 1; + + /** + * The token "VALUES". + */ + public static final int VALUES = VALUE + 1; + + /** + * The token "WHEN". + */ + public static final int WHEN = VALUES + 1; + + /** + * The token "WHERE". + */ + public static final int WHERE = WHEN + 1; + + /** + * The token "WINDOW". + */ + public static final int WINDOW = WHERE + 1; + + /** + * The token "WITH". + */ + public static final int WITH = WINDOW + 1; + + /** + * The token "YEAR". + */ + public static final int YEAR = WITH + 1; + + /** + * The token "_ROWID_". + */ + public static final int _ROWID_ = YEAR + 1; + + // Constants above must be sorted + + /** + * The ordinal number of the first keyword. + */ + public static final int FIRST_KEYWORD = IDENTIFIER + 1; + + /** + * The ordinal number of the last keyword. + */ + public static final int LAST_KEYWORD = _ROWID_; + + private static final HashMap KEYWORDS; + + static { + HashMap map = new HashMap<>(256); + map.put("ALL", ALL); + map.put("AND", AND); + map.put("ANY", ANY); + map.put("ARRAY", ARRAY); + map.put("AS", AS); + map.put("ASYMMETRIC", ASYMMETRIC); + map.put("AUTHORIZATION", AUTHORIZATION); + map.put("BETWEEN", BETWEEN); + map.put("CASE", CASE); + map.put("CAST", CAST); + map.put("CHECK", CHECK); + map.put("CONSTRAINT", CONSTRAINT); + map.put("CROSS", CROSS); + map.put("CURRENT_CATALOG", CURRENT_CATALOG); + map.put("CURRENT_DATE", CURRENT_DATE); + map.put("CURRENT_PATH", CURRENT_PATH); + map.put("CURRENT_ROLE", CURRENT_ROLE); + map.put("CURRENT_SCHEMA", CURRENT_SCHEMA); + map.put("CURRENT_TIME", CURRENT_TIME); + map.put("CURRENT_TIMESTAMP", CURRENT_TIMESTAMP); + map.put("CURRENT_USER", CURRENT_USER); + map.put("DAY", DAY); + map.put("DEFAULT", DEFAULT); + map.put("DISTINCT", DISTINCT); + map.put("ELSE", ELSE); + map.put("END", END); + map.put("EXCEPT", EXCEPT); + map.put("EXISTS", EXISTS); + map.put("FALSE", FALSE); + map.put("FETCH", FETCH); + map.put("FOR", FOR); + map.put("FOREIGN", FOREIGN); + map.put("FROM", FROM); + map.put("FULL", FULL); + map.put("GROUP", GROUP); + map.put("HAVING", HAVING); + map.put("HOUR", HOUR); + map.put("IF", IF); + map.put("IN", IN); + map.put("INNER", INNER); + map.put("INTERSECT", INTERSECT); + map.put("INTERVAL", INTERVAL); + map.put("IS", IS); + map.put("JOIN", JOIN); + map.put("KEY", KEY); + map.put("LEFT", LEFT); + map.put("LIKE", LIKE); + map.put("LIMIT", LIMIT); + map.put("LOCALTIME", LOCALTIME); + map.put("LOCALTIMESTAMP", LOCALTIMESTAMP); + map.put("MINUS", MINUS); + map.put("MINUTE", MINUTE); + map.put("MONTH", MONTH); + map.put("NATURAL", NATURAL); + map.put("NOT", NOT); + map.put("NULL", NULL); + map.put("OFFSET", OFFSET); + map.put("ON", ON); + map.put("OR", OR); + map.put("ORDER", ORDER); + map.put("PRIMARY", PRIMARY); + map.put("QUALIFY", QUALIFY); + map.put("RIGHT", RIGHT); + map.put("ROW", ROW); + map.put("ROWNUM", ROWNUM); + map.put("SECOND", SECOND); + map.put("SELECT", SELECT); + map.put("SESSION_USER", SESSION_USER); + map.put("SET", SET); + map.put("SOME", SOME); + map.put("SYMMETRIC", SYMMETRIC); + map.put("SYSTEM_USER", SYSTEM_USER); + map.put("TABLE", TABLE); + map.put("TO", TO); + map.put("TRUE", TRUE); + map.put("UESCAPE", UESCAPE); + map.put("UNION", UNION); + map.put("UNIQUE", UNIQUE); + map.put("UNKNOWN", UNKNOWN); + map.put("USER", USER); + map.put("USING", USING); + map.put("VALUE", VALUE); + map.put("VALUES", VALUES); + map.put("WHEN", WHEN); + map.put("WHERE", WHERE); + map.put("WINDOW", WINDOW); + map.put("WITH", WITH); + map.put("YEAR", YEAR); + map.put("_ROWID_", _ROWID_); + // Additional keywords + map.put("BOTH", KEYWORD); + map.put("FILTER", KEYWORD); + map.put("GROUPS", KEYWORD); + map.put("ILIKE", KEYWORD); + map.put("LEADING", KEYWORD); + map.put("OVER", KEYWORD); + map.put("PARTITION", KEYWORD); + map.put("RANGE", KEYWORD); + map.put("REGEXP", KEYWORD); + map.put("ROWS", KEYWORD); + map.put("TOP", KEYWORD); + map.put("TRAILING", KEYWORD); + KEYWORDS = map; + } + + private ParserUtil() { + // utility class + } + + /** + * Add double quotes around an identifier if required and appends it to the + * specified string builder. + * + * @param builder string builder to append to + * @param s the identifier + * @param sqlFlags formatting flags + * @return the specified builder + */ + public static StringBuilder quoteIdentifier(StringBuilder builder, String s, int sqlFlags) { + if (s == null) { + return builder.append("\"\""); + } + if ((sqlFlags & HasSQL.QUOTE_ONLY_WHEN_REQUIRED) != 0 && isSimpleIdentifier(s, false, false)) { + return builder.append(s); + } + return StringUtils.quoteIdentifier(builder, s); + } + + /** + * Checks if this string is a SQL keyword. + * + * @param s the token to check + * @param ignoreCase true if case should be ignored, false if only upper case + * tokens are detected as keywords + * @return true if it is a keyword + */ + public static boolean isKeyword(String s, boolean ignoreCase) { + return getTokenType(s, ignoreCase, false) != IDENTIFIER; + } + + /** + * Is this a simple identifier (in the JDBC specification sense). + * + * @param s identifier to check + * @param databaseToUpper whether unquoted identifiers are converted to upper case + * @param databaseToLower whether unquoted identifiers are converted to lower case + * @return is specified identifier may be used without quotes + * @throws NullPointerException if s is {@code null} + */ + public static boolean isSimpleIdentifier(String s, boolean databaseToUpper, boolean databaseToLower) { + if (databaseToUpper && databaseToLower) { + throw new IllegalArgumentException("databaseToUpper && databaseToLower"); + } + int length = s.length(); + if (length == 0 || !checkLetter(databaseToUpper, databaseToLower, s.charAt(0))) { + return false; + } + for (int i = 1; i < length; i++) { + char c = s.charAt(i); + if (c != '_' && (c < '0' || c > '9') && !checkLetter(databaseToUpper, databaseToLower, c)) { + return false; + } + } + return getTokenType(s, !databaseToUpper, true) == IDENTIFIER; + } + + private static boolean checkLetter(boolean databaseToUpper, boolean databaseToLower, char c) { + if (databaseToUpper) { + if (c < 'A' || c > 'Z') { + return false; + } + } else if (databaseToLower) { + if (c < 'a' || c > 'z') { + return false; + } + } else { + if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z')) { + return false; + } + } + return true; + } + + /** + * Get the token type. + * + * @param s the string with token + * @param ignoreCase true if case should be ignored, false if only upper case + * tokens are detected as keywords + * @param additionalKeywords + * whether context-sensitive keywords are returned as + * {@link #KEYWORD} + * @return the token type + */ + public static int getTokenType(String s, boolean ignoreCase, boolean additionalKeywords) { + int length = s.length(); + if (length <= 1 || length > 17) { + return IDENTIFIER; + } + if (ignoreCase) { + s = StringUtils.toUpperEnglish(s); + } + Integer type = KEYWORDS.get(s); + if (type == null) { + return IDENTIFIER; + } + int t = type; + return t == KEYWORD && !additionalKeywords ? IDENTIFIER : t; + } + +} diff --git a/h2/src/main/org/h2/util/Permutations.java b/h2/src/main/org/h2/util/Permutations.java new file mode 100644 index 0000000..08a3bf6 --- /dev/null +++ b/h2/src/main/org/h2/util/Permutations.java @@ -0,0 +1,173 @@ +/* + * 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 + * + * According to a mail from Alan Tucker to Chris H Miller from IBM, + * the algorithm is in the public domain: + * + * Date: 2010-07-15 15:57 + * Subject: Re: Applied Combinatorics Code + * + * Chris, + * The combinatorics algorithms in my textbook are all not under patent + * or copyright. They are as much in the public domain as the solution to any + * common question in an undergraduate mathematics course, e.g., in my + * combinatorics course, the solution to the problem of how many arrangements + * there are of the letters in the word MATHEMATICS. I appreciate your due + * diligence. + * -Alan + */ +package org.h2.util; + +import org.h2.message.DbException; + +/** + * A class to iterate over all permutations of an array. + * The algorithm is from Applied Combinatorics, by Alan Tucker as implemented in + * http://www.koders.com/java/fidD3445CD11B1DC687F6B8911075E7F01E23171553.aspx + * + * @param the element type + */ +public class Permutations { + + private final T[] in; + private final T[] out; + private final int n, m; + private final int[] index; + private boolean hasNext = true; + + private Permutations(T[] in, T[] out, int m) { + this.n = in.length; + this.m = m; + if (n < m || m < 0) { + throw DbException.getInternalError("n < m or m < 0"); + } + this.in = in; + this.out = out; + index = new int[n]; + for (int i = 0; i < n; i++) { + index[i] = i; + } + + // The elements from m to n are always kept ascending right to left. + // This keeps the dip in the interesting region. + reverseAfter(m - 1); + } + + /** + * Create a new permutations object. + * + * @param the type + * @param in the source array + * @param out the target array + * @return the generated permutations object + */ + public static Permutations create(T[] in, T[] out) { + return new Permutations<>(in, out, in.length); + } + + /** + * Create a new permutations object. + * + * @param the type + * @param in the source array + * @param out the target array + * @param m the number of output elements to generate + * @return the generated permutations object + */ + public static Permutations create(T[] in, T[] out, int m) { + return new Permutations<>(in, out, m); + } + + /** + * Move the index forward a notch. The algorithm first finds the rightmost + * index that is less than its neighbor to the right. This is the dip point. + * The algorithm next finds the least element to the right of the dip that + * is greater than the dip. That element is switched with the dip. Finally, + * the list of elements to the right of the dip is reversed. + * For example, in a permutation of 5 items, the index may be {1, 2, 4, 3, + * 0}. The dip is 2 the rightmost element less than its neighbor on its + * right. The least element to the right of 2 that is greater than 2 is 3. + * These elements are swapped, yielding {1, 3, 4, 2, 0}, and the list right + * of the dip point is reversed, yielding {1, 3, 0, 2, 4}. + */ + private void moveIndex() { + // find the index of the first element that dips + int i = rightmostDip(); + if (i < 0) { + hasNext = false; + return; + } + + // find the least greater element to the right of the dip + int leastToRightIndex = i + 1; + for (int j = i + 2; j < n; j++) { + if (index[j] < index[leastToRightIndex] && index[j] > index[i]) { + leastToRightIndex = j; + } + } + + // switch dip element with least greater element to its right + int t = index[i]; + index[i] = index[leastToRightIndex]; + index[leastToRightIndex] = t; + + if (m - 1 > i) { + // reverse the elements to the right of the dip + reverseAfter(i); + + // reverse the elements to the right of m - 1 + reverseAfter(m - 1); + } + } + + /** + * Get the index of the first element from the right that is less + * than its neighbor on the right. + * + * @return the index or -1 if non is found + */ + private int rightmostDip() { + for (int i = n - 2; i >= 0; i--) { + if (index[i] < index[i + 1]) { + return i; + } + } + return -1; + } + + /** + * Reverse the elements to the right of the specified index. + * + * @param i the index + */ + private void reverseAfter(int i) { + int start = i + 1; + int end = n - 1; + while (start < end) { + int t = index[start]; + index[start] = index[end]; + index[end] = t; + start++; + end--; + } + } + + /** + * Go to the next lineup, and if available, fill the target array. + * + * @return if a new lineup is available + */ + public boolean next() { + if (!hasNext) { + return false; + } + for (int i = 0; i < m; i++) { + out[i] = in[index[i]]; + } + moveIndex(); + return true; + } + +} diff --git a/h2/src/main/org/h2/util/Profiler.java b/h2/src/main/org/h2/util/Profiler.java new file mode 100644 index 0000000..4bcc28f --- /dev/null +++ b/h2/src/main/org/h2/util/Profiler.java @@ -0,0 +1,516 @@ +/* + * 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.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.LineNumberReader; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringReader; +import java.lang.instrument.Instrumentation; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * A simple CPU profiling tool similar to java -Xrunhprof. It can be used + * in-process (to profile the current application) or as a standalone program + * (to profile a different process, or files containing full thread dumps). + */ +public class Profiler implements Runnable { + + private static Instrumentation instrumentation; + private static final String LINE_SEPARATOR = + System.getProperty("line.separator", "\n"); + private static final int MAX_ELEMENTS = 1000; + + public int interval = 2; + public int depth = 48; + public boolean paused; + public boolean sumClasses; + public boolean sumMethods; + + private int pid; + + private final String[] ignoreLines = ( + "java," + + "sun," + + "com.sun.," + + "com.google.common.," + + "com.mongodb.," + + "org.bson.," + ).split(","); + private final String[] ignorePackages = ( + "java," + + "sun," + + "com.sun.," + + "com.google.common.," + + "com.mongodb.," + + "org.bson" + ).split(","); + private final String[] ignoreThreads = ( + "java.lang.Object.wait," + + "java.lang.Thread.dumpThreads," + + "java.lang.Thread.getThreads," + + "java.lang.Thread.sleep," + + "java.lang.UNIXProcess.waitForProcessExit," + + "java.net.PlainDatagramSocketImpl.receive0," + + "java.net.PlainSocketImpl.accept," + + "java.net.PlainSocketImpl.socketAccept," + + "java.net.SocketInputStream.socketRead," + + "java.net.SocketOutputStream.socketWrite," + + "org.eclipse.jetty.io.nio.SelectorManager$SelectSet.doSelect," + + "sun.awt.windows.WToolkit.eventLoop," + + "sun.misc.Unsafe.park," + + "sun.nio.ch.EPollArrayWrapper.epollWait," + + "sun.nio.ch.KQueueArrayWrapper.kevent0," + + "sun.nio.ch.ServerSocketChannelImpl.accept," + + "dalvik.system.VMStack.getThreadStackTrace," + + "dalvik.system.NativeStart.run" + ).split(","); + + private volatile boolean stop; + private final HashMap counts = + new HashMap<>(); + + /** + * The summary (usually one entry per package, unless sumClasses is enabled, + * in which case it's one entry per class). + */ + private final HashMap summary = + new HashMap<>(); + private int minCount = 1; + private int total; + private Thread thread; + private long start; + private long time; + private int threadDumps; + + /** + * This method is called when the agent is installed. + * + * @param agentArgs the agent arguments + * @param inst the instrumentation object + */ + public static void premain(@SuppressWarnings("unused") String agentArgs, Instrumentation inst) { + instrumentation = inst; + } + + /** + * Get the instrumentation object if started as an agent. + * + * @return the instrumentation, or null + */ + public static Instrumentation getInstrumentation() { + return instrumentation; + } + + /** + * Run the command line version of the profiler. The JDK (jps and jstack) + * need to be in the path. + * + * @param args the process id of the process - if not set the java processes + * are listed + */ + public static void main(String... args) { + new Profiler().run(args); + } + + private void run(String... args) { + if (args.length == 0) { + System.out.println("Show profiling data"); + System.out.println("Usage: java " + getClass().getName() + + " | "); + System.out.println("Processes:"); + String processes = exec("jps", "-l"); + System.out.println(processes); + return; + } + start = System.nanoTime(); + if (args[0].matches("\\d+")) { + pid = Integer.parseInt(args[0]); + long last = 0; + while (true) { + tick(); + long t = System.nanoTime(); + if (t - last > TimeUnit.SECONDS.toNanos(5)) { + time = System.nanoTime() - start; + System.out.println(getTopTraces(3)); + last = t; + } + } + } + try { + for (String arg : args) { + if (arg.startsWith("-")) { + if ("-classes".equals(arg)) { + sumClasses = true; + } else if ("-methods".equals(arg)) { + sumMethods = true; + } else if ("-packages".equals(arg)) { + sumClasses = false; + sumMethods = false; + } else { + throw new IllegalArgumentException(arg); + } + continue; + } + Path file = Paths.get(arg); + try (Reader reader = Files.newBufferedReader(file)) { + LineNumberReader r = new LineNumberReader(reader); + for (String line; (line = r.readLine()) != null;) { + if (line.startsWith("Full thread dump")) { + threadDumps++; + } + } + } + try (Reader reader = Files.newBufferedReader(file)) { + processList(readStackTrace(new LineNumberReader(reader))); + } + } + System.out.println(getTopTraces(5)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static List getRunnableStackTraces() { + ArrayList list = new ArrayList<>(); + Map map = Thread.getAllStackTraces(); + for (Map.Entry entry : map.entrySet()) { + Thread t = entry.getKey(); + if (t.getState() != Thread.State.RUNNABLE) { + continue; + } + StackTraceElement[] dump = entry.getValue(); + if (dump == null || dump.length == 0) { + continue; + } + list.add(dump); + } + return list; + } + + private static List readRunnableStackTraces(int pid) { + try { + String jstack = exec("jstack", Integer.toString(pid)); + LineNumberReader r = new LineNumberReader( + new StringReader(jstack)); + return readStackTrace(r); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static List readStackTrace(LineNumberReader r) + throws IOException { + ArrayList list = new ArrayList<>(); + while (true) { + String line = r.readLine(); + if (line == null) { + break; + } + if (!line.startsWith("\"")) { + // not a thread + continue; + } + line = r.readLine(); + if (line == null) { + break; + } + line = line.trim(); + if (!line.startsWith("java.lang.Thread.State: RUNNABLE")) { + continue; + } + ArrayList stack = new ArrayList<>(); + while (true) { + line = r.readLine(); + if (line == null) { + break; + } + line = line.trim(); + if (line.startsWith("- ")) { + continue; + } + if (!line.startsWith("at ")) { + break; + } + line = StringUtils.trimSubstring(line, 3); + stack.add(line); + } + if (!stack.isEmpty()) { + String[] s = stack.toArray(new String[0]); + list.add(s); + } + } + return list; + } + + private static String exec(String... args) { + ByteArrayOutputStream err = new ByteArrayOutputStream(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + Process p = Runtime.getRuntime().exec(args); + copyInThread(p.getInputStream(), out); + copyInThread(p.getErrorStream(), err); + p.waitFor(); + String e = Utils10.byteArrayOutputStreamToString(err, StandardCharsets.UTF_8); + if (e.length() > 0) { + throw new RuntimeException(e); + } + return Utils10.byteArrayOutputStreamToString(out, StandardCharsets.UTF_8); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void copyInThread(final InputStream in, + final OutputStream out) { + new Thread("Profiler stream copy") { + @Override + public void run() { + byte[] buffer = new byte[4096]; + try { + while (true) { + int len = in.read(buffer, 0, buffer.length); + if (len < 0) { + break; + } + out.write(buffer, 0, len); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }.start(); + } + + /** + * Start collecting profiling data. + * + * @return this + */ + public Profiler startCollecting() { + thread = new Thread(this, "Profiler"); + thread.setDaemon(true); + thread.start(); + return this; + } + + /** + * Stop collecting. + * + * @return this + */ + public Profiler stopCollecting() { + stop = true; + if (thread != null) { + try { + thread.join(); + } catch (InterruptedException e) { + // ignore + } + thread = null; + } + return this; + } + + @Override + public void run() { + start = System.nanoTime(); + while (!stop) { + try { + tick(); + } catch (Throwable t) { + break; + } + } + time = System.nanoTime() - start; + } + + private void tick() { + if (interval > 0) { + if (paused) { + return; + } + try { + Thread.sleep(interval, 0); + } catch (Exception e) { + // ignore + } + } + + List list; + if (pid != 0) { + list = readRunnableStackTraces(pid); + } else { + list = getRunnableStackTraces(); + } + threadDumps++; + processList(list); + } + + private void processList(List list) { + for (Object[] dump : list) { + if (startsWithAny(dump[0].toString(), ignoreThreads)) { + continue; + } + StringBuilder buff = new StringBuilder(); + // simple recursive calls are ignored + String last = null; + boolean packageCounts = false; + for (int j = 0, i = 0; i < dump.length && j < depth; i++) { + String el = dump[i].toString(); + if (!el.equals(last) && !startsWithAny(el, ignoreLines)) { + last = el; + buff.append("at ").append(el).append(LINE_SEPARATOR); + if (!packageCounts && !startsWithAny(el, ignorePackages)) { + packageCounts = true; + int index = 0; + for (; index < el.length(); index++) { + char c = el.charAt(index); + if (c == '(' || Character.isUpperCase(c)) { + break; + } + } + if (index > 0 && el.charAt(index - 1) == '.') { + index--; + } + if (sumClasses) { + int m = el.indexOf('.', index + 1); + index = m >= 0 ? m : index; + } + if (sumMethods) { + int m = el.indexOf('(', index + 1); + index = m >= 0 ? m : index; + } + String groupName = el.substring(0, index); + increment(summary, groupName, 0); + } + j++; + } + } + if (buff.length() > 0) { + minCount = increment(counts, buff.toString().trim(), minCount); + total++; + } + } + } + + private static boolean startsWithAny(String s, String[] prefixes) { + for (String p : prefixes) { + if (p.length() > 0 && s.startsWith(p)) { + return true; + } + } + return false; + } + + private static int increment(HashMap map, String trace, + int minCount) { + Integer oldCount = map.get(trace); + if (oldCount == null) { + map.put(trace, 1); + } else { + map.put(trace, oldCount + 1); + } + while (map.size() > MAX_ELEMENTS) { + for (Iterator> ei = + map.entrySet().iterator(); ei.hasNext();) { + Map.Entry e = ei.next(); + if (e.getValue() <= minCount) { + ei.remove(); + } + } + if (map.size() > MAX_ELEMENTS) { + minCount++; + } + } + return minCount; + } + + /** + * Get the top stack traces. + * + * @param count the maximum number of stack traces + * @return the stack traces. + */ + public String getTop(int count) { + stopCollecting(); + return getTopTraces(count); + } + + private String getTopTraces(int count) { + StringBuilder buff = new StringBuilder(); + buff.append("Profiler: top ").append(count).append(" stack trace(s) of "); + if (time > 0) { + buff.append(" of ").append(TimeUnit.NANOSECONDS.toMillis(time)).append(" ms"); + } + if (threadDumps > 0) { + buff.append(" of ").append(threadDumps).append(" thread dumps"); + } + buff.append(":").append(LINE_SEPARATOR); + if (counts.size() == 0) { + buff.append("(none)").append(LINE_SEPARATOR); + } + HashMap copy = new HashMap<>(counts); + appendTop(buff, copy, count, total, false); + buff.append("summary:").append(LINE_SEPARATOR); + copy = new HashMap<>(summary); + appendTop(buff, copy, count, total, true); + buff.append('.'); + return buff.toString(); + } + + private static void appendTop(StringBuilder buff, + HashMap map, int count, int total, boolean table) { + for (int x = 0, min = 0;;) { + int highest = 0; + Map.Entry best = null; + for (Map.Entry el : map.entrySet()) { + if (el.getValue() > highest) { + best = el; + highest = el.getValue(); + } + } + if (best == null) { + break; + } + map.remove(best.getKey()); + if (++x >= count) { + if (best.getValue() < min) { + break; + } + min = best.getValue(); + } + int c = best.getValue(); + int percent = 100 * c / Math.max(total, 1); + if (table) { + if (percent > 1) { + buff.append(percent). + append("%: ").append(best.getKey()). + append(LINE_SEPARATOR); + } + } else { + buff.append(c).append('/').append(total).append(" ("). + append(percent). + append("%):").append(LINE_SEPARATOR). + append(best.getKey()). + append(LINE_SEPARATOR); + } + } + } + +} diff --git a/h2/src/main/org/h2/util/ScriptReader.java b/h2/src/main/org/h2/util/ScriptReader.java new file mode 100644 index 0000000..8a930ca --- /dev/null +++ b/h2/src/main/org/h2/util/ScriptReader.java @@ -0,0 +1,336 @@ +/* + * 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.util; + +import java.io.Closeable; +import java.io.IOException; +import java.io.Reader; +import java.util.Arrays; +import org.h2.engine.Constants; +import org.h2.message.DbException; + +/** + * This class can split SQL scripts to single SQL statements. + * Each SQL statement ends with the character ';', however it is ignored + * in comments and quotes. + */ +public class ScriptReader implements Closeable { + + private final Reader reader; + private char[] buffer; + + /** + * The position in the buffer of the next char to be read + */ + private int bufferPos; + + /** + * The position in the buffer of the statement start + */ + private int bufferStart = -1; + + /** + * The position in the buffer of the last available char + */ + private int bufferEnd; + + /** + * True if we have read past the end of file + */ + private boolean endOfFile; + + /** + * True if we are inside a comment + */ + private boolean insideRemark; + + /** + * Only valid if insideRemark is true. True if we are inside a block + * comment, false if we are inside a line comment + */ + private boolean blockRemark; + + /** + * True if comments should be skipped completely by this reader. + */ + private boolean skipRemarks; + + /** + * The position in buffer of start of comment + */ + private int remarkStart; + + /** + * Create a new SQL script reader from the given reader + * + * @param reader the reader + */ + public ScriptReader(Reader reader) { + this.reader = reader; + buffer = new char[Constants.IO_BUFFER_SIZE * 2]; + } + + /** + * Close the underlying reader. + */ + @Override + public void close() { + try { + reader.close(); + } catch (IOException e) { + throw DbException.convertIOException(e, null); + } + } + + /** + * Read a statement from the reader. This method returns null if the end has + * been reached. + * + * @return the SQL statement or null + */ + public String readStatement() { + if (endOfFile) { + return null; + } + try { + return readStatementLoop(); + } catch (IOException e) { + throw DbException.convertIOException(e, null); + } + } + + private String readStatementLoop() throws IOException { + bufferStart = bufferPos; + int c = read(); + while (true) { + if (c < 0) { + endOfFile = true; + if (bufferPos - 1 == bufferStart) { + return null; + } + break; + } else if (c == ';') { + break; + } + switch (c) { + case '$': { + c = read(); + if (c == '$' && (bufferPos - bufferStart < 3 || buffer[bufferPos - 3] <= ' ')) { + // dollar quoted string + while (true) { + c = read(); + if (c < 0) { + break; + } + if (c == '$') { + c = read(); + if (c < 0) { + break; + } + if (c == '$') { + break; + } + } + } + c = read(); + } + break; + } + case '\'': + while (true) { + c = read(); + if (c < 0) { + break; + } + if (c == '\'') { + break; + } + } + c = read(); + break; + case '"': + while (true) { + c = read(); + if (c < 0) { + break; + } + if (c == '\"') { + break; + } + } + c = read(); + break; + case '/': { + c = read(); + if (c == '*') { + // block comment + startRemark(true); + int level = 1; + while (true) { + c = read(); + if (c < 0) { + break; + } + if (c == '*') { + c = read(); + if (c < 0) { + clearRemark(); + break; + } + if (c == '/') { + if (--level == 0) { + endRemark(); + break; + } + } + } else if (c == '/') { + c = read(); + if (c < 0) { + clearRemark(); + break; + } + if (c == '*') { + level++; + } + } + } + c = read(); + } else if (c == '/') { + // single line comment + startRemark(false); + while (true) { + c = read(); + if (c < 0) { + clearRemark(); + break; + } + if (c == '\r' || c == '\n') { + endRemark(); + break; + } + } + c = read(); + } + break; + } + case '-': { + c = read(); + if (c == '-') { + // single line comment + startRemark(false); + while (true) { + c = read(); + if (c < 0) { + clearRemark(); + break; + } + if (c == '\r' || c == '\n') { + endRemark(); + break; + } + } + c = read(); + } + break; + } + default: { + c = read(); + } + } + } + return new String(buffer, bufferStart, bufferPos - 1 - bufferStart); + } + + private void startRemark(boolean block) { + blockRemark = block; + remarkStart = bufferPos - 2; + insideRemark = true; + } + + private void endRemark() { + clearRemark(); + insideRemark = false; + } + + private void clearRemark() { + if (skipRemarks) { + Arrays.fill(buffer, remarkStart, bufferPos, ' '); + } + } + + private int read() throws IOException { + if (bufferPos >= bufferEnd) { + return readBuffer(); + } + return buffer[bufferPos++]; + } + + private int readBuffer() throws IOException { + if (endOfFile) { + return -1; + } + int keep = bufferPos - bufferStart; + if (keep > 0) { + char[] src = buffer; + if (keep + Constants.IO_BUFFER_SIZE > src.length) { + // protect against NegativeArraySizeException + if (src.length >= Integer.MAX_VALUE / 2) { + throw new IOException("Error in parsing script, " + + "statement size exceeds 1G, " + + "first 80 characters of statement looks like: " + + new String(buffer, bufferStart, 80)); + } + buffer = new char[src.length * 2]; + } + System.arraycopy(src, bufferStart, buffer, 0, keep); + } + remarkStart -= bufferStart; + bufferStart = 0; + bufferPos = keep; + int len = reader.read(buffer, keep, Constants.IO_BUFFER_SIZE); + if (len == -1) { + // ensure bufferPos > bufferEnd + bufferEnd = -1024; + endOfFile = true; + // ensure the right number of characters are read + // in case the input buffer is still used + bufferPos++; + return -1; + } + bufferEnd = keep + len; + return buffer[bufferPos++]; + } + + /** + * Check if this is the last statement, and if the single line or block + * comment is not finished yet. + * + * @return true if the current position is inside a remark + */ + public boolean isInsideRemark() { + return insideRemark; + } + + /** + * If currently inside a remark, this method tells if it is a block comment + * (true) or single line comment (false) + * + * @return true if inside a block comment + */ + public boolean isBlockRemark() { + return blockRemark; + } + + /** + * If comments should be skipped completely by this reader. + * + * @param skipRemarks true if comments should be skipped + */ + public void setSkipRemarks(boolean skipRemarks) { + this.skipRemarks = skipRemarks; + } + +} diff --git a/h2/src/main/org/h2/util/SimpleColumnInfo.java b/h2/src/main/org/h2/util/SimpleColumnInfo.java new file mode 100644 index 0000000..4e0672e --- /dev/null +++ b/h2/src/main/org/h2/util/SimpleColumnInfo.java @@ -0,0 +1,80 @@ +/* + * 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.util; + +/** + * Metadata of a column. + * + *

+ * Notice: {@linkplain #equals(Object)} and {@linkplain #hashCode()} use only + * {@linkplain #name} field. + *

+ */ +public final class SimpleColumnInfo { + /** + * Name of the column. + */ + public final String name; + + /** + * Type of the column, see {@link java.sql.Types}. + */ + public final int type; + + /** + * Type name of the column. + */ + public final String typeName; + + /** + * Precision of the column + */ + public final int precision; + + /** + * Scale of the column. + */ + public final int scale; + + /** + * Creates metadata. + * + * @param name + * name of the column + * @param type + * type of the column, see {@link java.sql.Types} + * @param typeName + * type name of the column + * @param precision + * precision of the column + * @param scale + * scale of the column + */ + public SimpleColumnInfo(String name, int type, String typeName, int precision, int scale) { + this.name = name; + this.type = type; + this.typeName = typeName; + this.precision = precision; + this.scale = scale; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + SimpleColumnInfo other = (SimpleColumnInfo) obj; + return name.equals(other.name); + } + + @Override + public int hashCode() { + return name.hashCode(); + } +} diff --git a/h2/src/main/org/h2/util/SmallLRUCache.java b/h2/src/main/org/h2/util/SmallLRUCache.java new file mode 100644 index 0000000..7b9d67f --- /dev/null +++ b/h2/src/main/org/h2/util/SmallLRUCache.java @@ -0,0 +1,48 @@ +/* + * 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.util; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * This class implements a small LRU object cache. + * + * @param the key + * @param the value + */ +public class SmallLRUCache extends LinkedHashMap { + + private static final long serialVersionUID = 1L; + private int size; + + private SmallLRUCache(int size) { + super(size, (float) 0.75, true); + this.size = size; + } + + /** + * Create a new object with all elements of the given collection. + * + * @param the key type + * @param the value type + * @param size the number of elements + * @return the object + */ + public static SmallLRUCache newInstance(int size) { + return new SmallLRUCache<>(size); + } + + public void setMaxSize(int size) { + this.size = size; + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > size; + } + +} diff --git a/h2/src/main/org/h2/util/SmallMap.java b/h2/src/main/org/h2/util/SmallMap.java new file mode 100644 index 0000000..3dc55a0 --- /dev/null +++ b/h2/src/main/org/h2/util/SmallMap.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.util; + +import java.util.HashMap; +import java.util.Iterator; + +import org.h2.api.ErrorCode; +import org.h2.message.DbException; + +/** + * A simple hash table with an optimization for the last recently used object. + */ +public class SmallMap { + + private final HashMap map = new HashMap<>(); + private Object cache; + private int cacheId; + private int lastId; + private final int maxElements; + + /** + * Create a map with the given maximum number of entries. + * + * @param maxElements the maximum number of entries + */ + public SmallMap(int maxElements) { + this.maxElements = maxElements; + } + + /** + * Add an object to the map. If the size of the map is larger than twice the + * maximum size, objects with a low id are removed. + * + * @param id the object id + * @param o the object + * @return the id + */ + public int addObject(int id, Object o) { + if (map.size() > maxElements * 2) { + Iterator it = map.keySet().iterator(); + while (it.hasNext()) { + Integer k = it.next(); + if (k + maxElements < lastId) { + it.remove(); + } + } + } + if (id > lastId) { + lastId = id; + } + map.put(id, o); + cacheId = id; + cache = o; + return id; + } + + /** + * Remove an object from the map. + * + * @param id the id of the object to remove + */ + public void freeObject(int id) { + if (cacheId == id) { + cacheId = -1; + cache = null; + } + map.remove(id); + } + + /** + * Get an object from the map if it is stored. + * + * @param id the id of the object + * @param ifAvailable only return it if available, otherwise return null + * @return the object or null + * @throws DbException if isAvailable is false and the object has not been + * found + */ + public Object getObject(int id, boolean ifAvailable) { + if (id == cacheId) { + return cache; + } + Object obj = map.get(id); + if (obj == null && !ifAvailable) { + throw DbException.get(ErrorCode.OBJECT_CLOSED); + } + return obj; + } + +} diff --git a/h2/src/main/org/h2/util/SoftValuesHashMap.java b/h2/src/main/org/h2/util/SoftValuesHashMap.java new file mode 100644 index 0000000..ddade87 --- /dev/null +++ b/h2/src/main/org/h2/util/SoftValuesHashMap.java @@ -0,0 +1,108 @@ +/* + * 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.util; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Map which stores items using SoftReference. Items can be garbage collected + * and removed. It is not a general purpose cache, as it doesn't implement some + * methods, and others not according to the map definition, to improve speed. + * + * @param the key type + * @param the value type + */ +public class SoftValuesHashMap extends AbstractMap { + + private final Map> map; + private final ReferenceQueue queue = new ReferenceQueue<>(); + + public SoftValuesHashMap() { + map = new HashMap<>(); + } + + @SuppressWarnings("unchecked") + private void processQueue() { + while (true) { + Reference o = queue.poll(); + if (o == null) { + return; + } + SoftValue k = (SoftValue) o; + Object key = k.key; + map.remove(key); + } + } + + @Override + public V get(Object key) { + processQueue(); + SoftReference o = map.get(key); + if (o == null) { + return null; + } + return o.get(); + } + + /** + * Store the object. The return value of this method is null or a + * SoftReference. + * + * @param key the key + * @param value the value + * @return null or the old object. + */ + @Override + public V put(K key, V value) { + processQueue(); + SoftValue old = map.put(key, new SoftValue<>(value, queue, key)); + return old == null ? null : old.get(); + } + + /** + * Remove an object. + * + * @param key the key + * @return null or the old object + */ + @Override + public V remove(Object key) { + processQueue(); + SoftReference ref = map.remove(key); + return ref == null ? null : ref.get(); + } + + @Override + public void clear() { + processQueue(); + map.clear(); + } + + @Override + public Set> entrySet() { + throw new UnsupportedOperationException(); + } + + /** + * A soft reference that has a hard reference to the key. + */ + private static class SoftValue extends SoftReference { + final Object key; + + public SoftValue(T ref, ReferenceQueue q, Object key) { + super(ref, q); + this.key = key; + } + + } + +} diff --git a/h2/src/main/org/h2/util/SortedProperties.java b/h2/src/main/org/h2/util/SortedProperties.java new file mode 100644 index 0000000..f7dfd76 --- /dev/null +++ b/h2/src/main/org/h2/util/SortedProperties.java @@ -0,0 +1,172 @@ +/* + * 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.util; + +import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.OutputStreamWriter; +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 org.h2.store.fs.FileUtils; + +/** + * Sorted properties file. + * This implementation requires that store() internally calls keys(). + */ +public class SortedProperties extends Properties { + + private static final long serialVersionUID = 1L; + + @Override + public synchronized Enumeration keys() { + ArrayList v = new ArrayList<>(); + for (Object o : keySet()) { + v.add(o.toString()); + } + v.sort(null); + return Collections.enumeration(v); + } + + /** + * Get a boolean property value from a properties object. + * + * @param prop the properties object + * @param key the key + * @param def the default value + * @return the value if set, or the default value if not + */ + public static boolean getBooleanProperty(Properties prop, String key, + boolean def) { + try { + return Utils.parseBoolean(prop.getProperty(key, null), def, true); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + return def; + } + } + + /** + * Get an int property value from a properties object. + * + * @param prop the properties object + * @param key the key + * @param def the default value + * @return the value if set, or the default value if not + */ + public static int getIntProperty(Properties prop, String key, int def) { + String value = prop.getProperty(key, Integer.toString(def)); + try { + return Integer.decode(value); + } catch (Exception e) { + e.printStackTrace(); + return def; + } + } + + /** + * Get a string property value from a properties object. + * + * @param prop the properties object + * @param key the key + * @param def the default value + * @return the value if set, or the default value if not + */ + public static String getStringProperty(Properties prop, String key, String def) { + return prop.getProperty(key, def); + } + + /** + * Load a properties object from a file. + * + * @param fileName the name of the properties file + * @return the properties object + * @throws IOException on failure + */ + public static synchronized SortedProperties loadProperties(String fileName) + throws IOException { + SortedProperties prop = new SortedProperties(); + if (FileUtils.exists(fileName)) { + try (InputStream in = FileUtils.newInputStream(fileName)) { + prop.load(new InputStreamReader(in, StandardCharsets.ISO_8859_1)); + } + } + return prop; + } + + /** + * Store a properties file. The header and the date is not written. + * + * @param fileName the target file name + * @throws IOException on failure + */ + public synchronized void store(String fileName) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + store(out, null); + ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); + InputStreamReader reader = new InputStreamReader(in, StandardCharsets.ISO_8859_1); + LineNumberReader r = new LineNumberReader(reader); + Writer w; + try { + w = new OutputStreamWriter(FileUtils.newOutputStream(fileName, false), StandardCharsets.ISO_8859_1); + } catch (Exception e) { + throw new IOException(e.toString(), e); + } + try (PrintWriter writer = new PrintWriter(new BufferedWriter(w))) { + while (true) { + String line = r.readLine(); + if (line == null) { + break; + } + if (!line.startsWith("#")) { + writer.print(line + "\n"); + } + } + } + } + + /** + * Convert the map to a list of line in the form key=value. + * + * @return the lines + */ + public synchronized String toLines() { + StringBuilder buff = new StringBuilder(); + for (Entry e : new TreeMap<>(this).entrySet()) { + buff.append(e.getKey()).append('=').append(e.getValue()).append('\n'); + } + return buff.toString(); + } + + /** + * Convert a String to a map. + * + * @param s the string + * @return the map + */ + public static SortedProperties fromLines(String s) { + SortedProperties p = new SortedProperties(); + for (String line : StringUtils.arraySplit(s, '\n', true)) { + int idx = line.indexOf('='); + if (idx > 0) { + p.put(line.substring(0, idx), line.substring(idx + 1)); + } + } + return p; + } + +} diff --git a/h2/src/main/org/h2/util/SourceCompiler.java b/h2/src/main/org/h2/util/SourceCompiler.java new file mode 100644 index 0000000..38fed8f --- /dev/null +++ b/h2/src/main/org/h2/util/SourceCompiler.java @@ -0,0 +1,602 @@ +/* + * 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.util; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.StringReader; +import java.io.StringWriter; +import java.lang.reflect.Array; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.SecureClassLoader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import javax.script.Compilable; +import javax.script.CompiledScript; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import javax.tools.FileObject; +import javax.tools.ForwardingJavaFileManager; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.JavaFileObject.Kind; +import javax.tools.SimpleJavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; + +import org.h2.api.ErrorCode; +import org.h2.engine.SysProperties; +import org.h2.message.DbException; + +/** + * This class allows to convert source code to a class. It uses one class loader + * per class. + */ +public class SourceCompiler { + + /** + * The "com.sun.tools.javac.Main" (if available). + */ + static final JavaCompiler JAVA_COMPILER; + + private static final Class JAVAC_SUN; + + private static final String COMPILE_DIR = + Utils.getProperty("java.io.tmpdir", "."); + + /** + * The class name to source code map. + */ + final HashMap sources = new HashMap<>(); + + /** + * The class name to byte code map. + */ + final HashMap> compiled = new HashMap<>(); + + /** + * The class name to compiled scripts map. + */ + final Map compiledScripts = new HashMap<>(); + + /** + * Whether to use the ToolProvider.getSystemJavaCompiler(). + */ + boolean useJavaSystemCompiler = SysProperties.JAVA_SYSTEM_COMPILER; + + static { + JavaCompiler c; + try { + c = ToolProvider.getSystemJavaCompiler(); + } catch (Exception e) { + // ignore + c = null; + } + JAVA_COMPILER = c; + Class clazz; + try { + clazz = Class.forName("com.sun.tools.javac.Main"); + } catch (Exception e) { + clazz = null; + } + JAVAC_SUN = clazz; + } + + /** + * Set the source code for the specified class. + * This will reset all compiled classes. + * + * @param className the class name + * @param source the source code + */ + public void setSource(String className, String source) { + sources.put(className, source); + compiled.clear(); + } + + /** + * Enable or disable the usage of the Java system compiler. + * + * @param enabled true to enable + */ + public void setJavaSystemCompiler(boolean enabled) { + this.useJavaSystemCompiler = enabled; + } + + /** + * Get the class object for the given name. + * + * @param packageAndClassName the class name + * @return the class + * @throws ClassNotFoundException on failure + */ + public Class getClass(String packageAndClassName) + throws ClassNotFoundException { + + Class compiledClass = compiled.get(packageAndClassName); + if (compiledClass != null) { + return compiledClass; + } + String source = sources.get(packageAndClassName); + if (isGroovySource(source)) { + Class clazz = GroovyCompiler.parseClass(source, packageAndClassName); + compiled.put(packageAndClassName, clazz); + return clazz; + } + + ClassLoader classLoader = new ClassLoader(getClass().getClassLoader()) { + + @Override + public Class findClass(String name) throws ClassNotFoundException { + Class classInstance = compiled.get(name); + if (classInstance == null) { + String source = sources.get(name); + String packageName = null; + int idx = name.lastIndexOf('.'); + String className; + if (idx >= 0) { + packageName = name.substring(0, idx); + className = name.substring(idx + 1); + } else { + className = name; + } + String s = getCompleteSourceCode(packageName, className, source); + if (JAVA_COMPILER != null && useJavaSystemCompiler) { + classInstance = javaxToolsJavac(packageName, className, s); + } else { + byte[] data = javacCompile(packageName, className, s); + if (data == null) { + classInstance = findSystemClass(name); + } else { + classInstance = defineClass(name, data, 0, data.length); + } + } + compiled.put(name, classInstance); + } + return classInstance; + } + }; + return classLoader.loadClass(packageAndClassName); + } + + private static boolean isGroovySource(String source) { + return source.startsWith("//groovy") || source.startsWith("@groovy"); + } + + private static boolean isJavascriptSource(String source) { + return source.startsWith("//javascript"); + } + + private static boolean isRubySource(String source) { + return source.startsWith("#ruby"); + } + + /** + * Whether the passed source can be compiled using {@link javax.script.ScriptEngineManager}. + * + * @param source the source to test. + * @return true if {@link #getCompiledScript(String)} can be called. + */ + public static boolean isJavaxScriptSource(String source) { + return isJavascriptSource(source) || isRubySource(source); + } + + /** + * Get the compiled script. + * + * @param packageAndClassName the package and class name + * @return the compiled script + * @throws ScriptException on failure + */ + public CompiledScript getCompiledScript(String packageAndClassName) throws ScriptException { + CompiledScript compiledScript = compiledScripts.get(packageAndClassName); + if (compiledScript == null) { + String source = sources.get(packageAndClassName); + final String lang; + if (isJavascriptSource(source)) { + lang = "javascript"; + } else if (isRubySource(source)) { + lang = "ruby"; + } else { + throw new IllegalStateException("Unknown language for " + source); + } + + final Compilable jsEngine = (Compilable) new ScriptEngineManager().getEngineByName(lang); + compiledScript = jsEngine.compile(source); + compiledScripts.put(packageAndClassName, compiledScript); + } + return compiledScript; + } + + /** + * Get the first public static method of the given class. + * + * @param className the class name + * @return the method name + * @throws ClassNotFoundException on failure + */ + public Method getMethod(String className) throws ClassNotFoundException { + Class clazz = getClass(className); + Method[] methods = clazz.getDeclaredMethods(); + for (Method m : methods) { + int modifiers = m.getModifiers(); + if (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers)) { + String name = m.getName(); + if (!name.startsWith("_") && !m.getName().equals("main")) { + return m; + } + } + } + return null; + } + + /** + * Compile the given class. This method tries to use the class + * "com.sun.tools.javac.Main" if available. If not, it tries to run "javac" + * in a separate process. + * + * @param packageName the package name + * @param className the class name + * @param source the source code + * @return the class file + */ + byte[] javacCompile(String packageName, String className, String source) { + Path dir = Paths.get(COMPILE_DIR); + if (packageName != null) { + dir = dir.resolve(packageName.replace('.', '/')); + try { + Files.createDirectories(dir); + } catch (Exception e) { + throw DbException.convert(e); + } + } + Path javaFile = dir.resolve(className + ".java"); + Path classFile = dir.resolve(className + ".class"); + try { + Files.write(javaFile, source.getBytes(StandardCharsets.UTF_8)); + Files.deleteIfExists(classFile); + if (JAVAC_SUN != null) { + javacSun(javaFile); + } else { + javacProcess(javaFile); + } + return Files.readAllBytes(classFile); + } catch (Exception e) { + throw DbException.convert(e); + } finally { + try { + Files.deleteIfExists(javaFile); + } catch (IOException e) { + } + try { + Files.deleteIfExists(classFile); + } catch (IOException e) { + } + } + } + + /** + * Get the complete source code (including package name, imports, and so + * on). + * + * @param packageName the package name + * @param className the class name + * @param source the (possibly shortened) source code + * @return the full source code + */ + static String getCompleteSourceCode(String packageName, String className, + String source) { + if (source.startsWith("package ")) { + return source; + } + StringBuilder buff = new StringBuilder(); + if (packageName != null) { + buff.append("package ").append(packageName).append(";\n"); + } + int endImport = source.indexOf("@CODE"); + String importCode = + "import java.util.*;\n" + + "import java.math.*;\n" + + "import java.sql.*;\n"; + if (endImport >= 0) { + importCode = source.substring(0, endImport); + source = source.substring("@CODE".length() + endImport); + } + buff.append(importCode); + buff.append("public class ").append(className).append( + " {\n" + + " public static ").append(source).append("\n" + + "}\n"); + return buff.toString(); + } + + /** + * Compile using the standard java compiler. + * + * @param packageName the package name + * @param className the class name + * @param source the source code + * @return the class + */ + Class javaxToolsJavac(String packageName, String className, String source) { + String fullClassName = packageName + "." + className; + StringWriter writer = new StringWriter(); + try (JavaFileManager fileManager = new + ClassFileManager(JAVA_COMPILER + .getStandardFileManager(null, null, null))) { + ArrayList compilationUnits = new ArrayList<>(); + compilationUnits.add(new StringJavaFileObject(fullClassName, source)); + // cannot concurrently compile + final boolean ok; + synchronized (JAVA_COMPILER) { + ok = JAVA_COMPILER.getTask(writer, fileManager, null, null, + null, compilationUnits).call(); + } + String output = writer.toString(); + handleSyntaxError(output, (ok? 0: 1)); + return fileManager.getClassLoader(null).loadClass(fullClassName); + } catch (ClassNotFoundException | IOException e) { + throw DbException.convert(e); + } + } + + private static void javacProcess(Path javaFile) { + exec("javac", + "-sourcepath", COMPILE_DIR, + "-d", COMPILE_DIR, + "-encoding", "UTF-8", + javaFile.toAbsolutePath().toString()); + } + + private static int exec(String... args) { + ByteArrayOutputStream buff = new ByteArrayOutputStream(); + try { + ProcessBuilder builder = new ProcessBuilder(); + // The javac executable allows some of it's flags + // to be smuggled in via environment variables. + // But if it sees those flags, it will write out a message + // to stderr, which messes up our parsing of the output. + builder.environment().remove("JAVA_TOOL_OPTIONS"); + builder.command(args); + + Process p = builder.start(); + copyInThread(p.getInputStream(), buff); + copyInThread(p.getErrorStream(), buff); + p.waitFor(); + String output = Utils10.byteArrayOutputStreamToString(buff, StandardCharsets.UTF_8); + handleSyntaxError(output, p.exitValue()); + return p.exitValue(); + } catch (Exception e) { + throw DbException.convert(e); + } + } + + private static void copyInThread(final InputStream in, final OutputStream out) { + new Task() { + @Override + public void call() throws IOException { + IOUtils.copy(in, out); + } + }.execute(); + } + + private static synchronized void javacSun(Path javaFile) { + PrintStream old = System.err; + ByteArrayOutputStream buff = new ByteArrayOutputStream(); + try { + System.setErr(new PrintStream(buff, false, "UTF-8")); + Method compile; + compile = JAVAC_SUN.getMethod("compile", String[].class); + Object javac = JAVAC_SUN.getDeclaredConstructor().newInstance(); + // Bugfix: Here we should check exit status value instead of parsing javac output text. + // Because of the output text is different in different locale environment. + // @since 2018-07-20 little-pan + final Integer status = (Integer)compile.invoke(javac, (Object) new String[] { + "-sourcepath", COMPILE_DIR, + // "-Xlint:unchecked", + "-d", COMPILE_DIR, + "-encoding", "UTF-8", + javaFile.toAbsolutePath().toString() }); + String output = Utils10.byteArrayOutputStreamToString(buff, StandardCharsets.UTF_8); + handleSyntaxError(output, status); + } catch (Exception e) { + throw DbException.convert(e); + } finally { + System.setErr(old); + } + } + + private static void handleSyntaxError(String output, int exitStatus) { + if(0 == exitStatus){ + return; + } + boolean syntaxError = false; + final BufferedReader reader = new BufferedReader(new StringReader(output)); + try { + for (String line; (line = reader.readLine()) != null;) { + if (line.endsWith("warning") || line.endsWith("warnings")) { + // ignore summary line + } else if (line.startsWith("Note:") + || line.startsWith("warning:")) { + // just a warning (e.g. unchecked or unsafe operations) + } else { + syntaxError = true; + break; + } + } + } catch (IOException ignored) { + // exception ignored + } + + if (syntaxError) { + output = StringUtils.replaceAll(output, COMPILE_DIR, ""); + throw DbException.get(ErrorCode.SYNTAX_ERROR_1, output); + } + } + + + /** + * Access the Groovy compiler using reflection, so that we do not gain a + * compile-time dependency unnecessarily. + */ + private static final class GroovyCompiler { + + private static final Object LOADER; + private static final Throwable INIT_FAIL_EXCEPTION; + + static { + Object loader = null; + Throwable initFailException = null; + try { + // Create an instance of ImportCustomizer + Class importCustomizerClass = Class.forName( + "org.codehaus.groovy.control.customizers.ImportCustomizer"); + Object importCustomizer = Utils.newInstance( + "org.codehaus.groovy.control.customizers.ImportCustomizer"); + // Call the method ImportCustomizer.addImports(String[]) + String[] importsArray = { + "java.sql.Connection", + "java.sql.Types", + "java.sql.ResultSet", + "groovy.sql.Sql", + "org.h2.tools.SimpleResultSet" + }; + Utils.callMethod(importCustomizer, "addImports", new Object[] { importsArray }); + + // Call the method + // CompilerConfiguration.addCompilationCustomizers( + // ImportCustomizer...) + Object importCustomizerArray = Array.newInstance(importCustomizerClass, 1); + Array.set(importCustomizerArray, 0, importCustomizer); + Object configuration = Utils.newInstance( + "org.codehaus.groovy.control.CompilerConfiguration"); + Utils.callMethod(configuration, + "addCompilationCustomizers", importCustomizerArray); + + ClassLoader parent = GroovyCompiler.class.getClassLoader(); + loader = Utils.newInstance( + "groovy.lang.GroovyClassLoader", parent, configuration); + } catch (Exception ex) { + initFailException = ex; + } + LOADER = loader; + INIT_FAIL_EXCEPTION = initFailException; + } + + public static Class parseClass(String source, + String packageAndClassName) { + if (LOADER == null) { + throw new RuntimeException( + "Compile fail: no Groovy jar in the classpath", INIT_FAIL_EXCEPTION); + } + try { + Object codeSource = Utils.newInstance("groovy.lang.GroovyCodeSource", + source, packageAndClassName + ".groovy", "UTF-8"); + Utils.callMethod(codeSource, "setCachable", false); + return (Class) Utils.callMethod( + LOADER, "parseClass", codeSource); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + /** + * An in-memory java source file object. + */ + static class StringJavaFileObject extends SimpleJavaFileObject { + + private final String sourceCode; + + public StringJavaFileObject(String className, String sourceCode) { + super(URI.create("string:///" + className.replace('.', '/') + + Kind.SOURCE.extension), Kind.SOURCE); + this.sourceCode = sourceCode; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return sourceCode; + } + + } + + /** + * An in-memory java class object. + */ + static class JavaClassObject extends SimpleJavaFileObject { + + private final ByteArrayOutputStream out = new ByteArrayOutputStream(); + + public JavaClassObject(String name, Kind kind) { + super(URI.create("string:///" + name.replace('.', '/') + + kind.extension), kind); + } + + public byte[] getBytes() { + return out.toByteArray(); + } + + @Override + public OutputStream openOutputStream() throws IOException { + return out; + } + } + + /** + * An in-memory class file manager. + */ + static class ClassFileManager extends + ForwardingJavaFileManager { + + /** + * We use map because there can be nested, anonymous etc classes. + */ + Map classObjectsByName = new HashMap<>(); + + private SecureClassLoader classLoader = new SecureClassLoader() { + + @Override + protected Class findClass(String name) + throws ClassNotFoundException { + byte[] bytes = classObjectsByName.get(name).getBytes(); + return super.defineClass(name, bytes, 0, + bytes.length); + } + }; + + public ClassFileManager(StandardJavaFileManager standardManager) { + super(standardManager); + } + + @Override + public ClassLoader getClassLoader(Location location) { + return this.classLoader; + } + + @Override + public JavaFileObject getJavaFileForOutput(Location location, + String className, Kind kind, FileObject sibling) throws IOException { + JavaClassObject classObject = new JavaClassObject(className, kind); + classObjectsByName.put(className, classObject); + return classObject; + } + } + +} diff --git a/h2/src/main/org/h2/util/StringUtils.java b/h2/src/main/org/h2/util/StringUtils.java new file mode 100644 index 0000000..3faef45 --- /dev/null +++ b/h2/src/main/org/h2/util/StringUtils.java @@ -0,0 +1,1307 @@ +/* + * 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.util; + +import java.io.ByteArrayOutputStream; +import java.lang.ref.SoftReference; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Locale; +import java.util.concurrent.TimeUnit; + +import org.h2.api.ErrorCode; +import org.h2.engine.SysProperties; +import org.h2.message.DbException; + +/** + * A few String utility functions. + */ +public class StringUtils { + + private static SoftReference softCache; + private static long softCacheCreatedNs; + + private static final char[] HEX = "0123456789abcdef".toCharArray(); + private static final int[] HEX_DECODE = new int['f' + 1]; + + // memory used by this cache: + // 4 * 1024 * 2 (strings per pair) * 64 * 2 (bytes per char) = 0.5 MB + private static final int TO_UPPER_CACHE_LENGTH = 2 * 1024; + private static final int TO_UPPER_CACHE_MAX_ENTRY_LENGTH = 64; + private static final String[][] TO_UPPER_CACHE = new String[TO_UPPER_CACHE_LENGTH][]; + + static { + for (int i = 0; i < HEX_DECODE.length; i++) { + HEX_DECODE[i] = -1; + } + for (int i = 0; i <= 9; i++) { + HEX_DECODE[i + '0'] = i; + } + for (int i = 0; i <= 5; i++) { + HEX_DECODE[i + 'a'] = HEX_DECODE[i + 'A'] = i + 10; + } + } + + private StringUtils() { + // utility class + } + + private static String[] getCache() { + String[] cache; + if (softCache != null) { + cache = softCache.get(); + if (cache != null) { + return cache; + } + } + // create a new cache at most every 5 seconds + // so that out of memory exceptions are not delayed + long time = System.nanoTime(); + if (softCacheCreatedNs != 0 && time - softCacheCreatedNs < TimeUnit.SECONDS.toNanos(5)) { + return null; + } + try { + cache = new String[SysProperties.OBJECT_CACHE_SIZE]; + softCache = new SoftReference<>(cache); + return cache; + } finally { + softCacheCreatedNs = System.nanoTime(); + } + } + + /** + * Convert a string to uppercase using the English locale. + * + * @param s the test to convert + * @return the uppercase text + */ + public static String toUpperEnglish(String s) { + if (s.length() > TO_UPPER_CACHE_MAX_ENTRY_LENGTH) { + return s.toUpperCase(Locale.ENGLISH); + } + int index = s.hashCode() & (TO_UPPER_CACHE_LENGTH - 1); + String[] e = TO_UPPER_CACHE[index]; + if (e != null) { + if (e[0].equals(s)) { + return e[1]; + } + } + String s2 = s.toUpperCase(Locale.ENGLISH); + e = new String[] { s, s2 }; + TO_UPPER_CACHE[index] = e; + return s2; + } + + /** + * Convert a string to lowercase using the English locale. + * + * @param s the text to convert + * @return the lowercase text + */ + public static String toLowerEnglish(String s) { + return s.toLowerCase(Locale.ENGLISH); + } + + /** + * Convert a string to a SQL literal. Null is converted to NULL. The text is + * enclosed in single quotes. If there are any special characters, the + * method STRINGDECODE is used. + * + * @param s the text to convert. + * @return the SQL literal + */ + public static String quoteStringSQL(String s) { + if (s == null) { + return "NULL"; + } + return quoteStringSQL(new StringBuilder(s.length() + 2), s).toString(); + } + + /** + * Convert a string to a SQL character string literal. Null is converted to + * NULL. If there are any special characters, the Unicode character string + * literal is used. + * + * @param builder + * string builder to append result to + * @param s the text to convert + * @return the specified string builder + */ + public static StringBuilder quoteStringSQL(StringBuilder builder, String s) { + if (s == null) { + return builder.append("NULL"); + } + return quoteIdentifierOrLiteral(builder, s, '\''); + } + + /** + * Decodes a Unicode SQL string. + * + * @param s + * the string to decode + * @param uencode + * the code point of UENCODE character, or '\\' + * @return the decoded string + * @throws DbException + * on format exception + */ + public static String decodeUnicodeStringSQL(String s, int uencode) { + int l = s.length(); + StringBuilder builder = new StringBuilder(l); + for (int i = 0; i < l;) { + int cp = s.codePointAt(i); + i += Character.charCount(cp); + if (cp == uencode) { + if (i >= l) { + throw getFormatException(s, i); + } + cp = s.codePointAt(i); + if (cp == uencode) { + i += Character.charCount(cp); + } else { + if (i + 4 > l) { + throw getFormatException(s, i); + } + char ch = s.charAt(i); + try { + if (ch == '+') { + if (i + 7 > l) { + throw getFormatException(s, i); + } + cp = Integer.parseUnsignedInt(s.substring(i + 1, i += 7), 16); + } else { + cp = Integer.parseUnsignedInt(s.substring(i, i += 4), 16); + } + } catch (NumberFormatException e) { + throw getFormatException(s, i); + } + } + } + builder.appendCodePoint(cp); + } + return builder.toString(); + } + + /** + * Convert a string to a Java literal using the correct escape sequences. + * The literal is not enclosed in double quotes. The result can be used in + * properties files or in Java source code. + * + * @param s the text to convert + * @return the Java representation + */ + public static String javaEncode(String s) { + StringBuilder buff = new StringBuilder(s.length()); + javaEncode(s, buff, false); + return buff.toString(); + } + + /** + * Convert a string to a Java literal using the correct escape sequences. + * The literal is not enclosed in double quotes. The result can be used in + * properties files or in Java source code. + * + * @param s the text to convert + * @param buff the Java representation to return + * @param forSQL true if we embedding this inside a STRINGDECODE SQL command + */ + public static void javaEncode(String s, StringBuilder buff, boolean forSQL) { + int length = s.length(); + for (int i = 0; i < length; i++) { + char c = s.charAt(i); + switch (c) { +// case '\b': +// // BS backspace +// // not supported in properties files +// buff.append("\\b"); +// break; + 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 '\'': + // quote: + if (forSQL) { + buff.append('\''); + } + buff.append('\''); + break; + case '\\': + // backslash + buff.append("\\\\"); + break; + default: + if (c >= ' ' && (c < 0x80)) { + buff.append(c); + // not supported in properties files + // } else if (c < 0xff) { + // buff.append("\\"); + // // make sure it's three characters (0x200 is octal 1000) + // buff.append(Integer.toOctalString(0x200 | c).substring(1)); + } else { + buff.append("\\u") + .append(HEX[c >>> 12]) + .append(HEX[c >>> 8 & 0xf]) + .append(HEX[c >>> 4 & 0xf]) + .append(HEX[c & 0xf]); + } + } + } + } + + /** + * Add an asterisk ('[*]') at the given position. This format is used to + * show where parsing failed in a statement. + * + * @param s the text + * @param index the position + * @return the text with asterisk + */ + public static String addAsterisk(String s, int index) { + if (s != null) { + int len = s.length(); + index = Math.min(index, len); + s = new StringBuilder(len + 3).append(s, 0, index).append("[*]").append(s, index, len).toString(); + } + return s; + } + + private static DbException getFormatException(String s, int i) { + return DbException.get(ErrorCode.STRING_FORMAT_ERROR_1, addAsterisk(s, i)); + } + + /** + * Decode a text that is encoded as a Java string literal. The Java + * properties file format and Java source code format is supported. + * + * @param s the encoded string + * @return the string + */ + public static String javaDecode(String s) { + int length = s.length(); + StringBuilder buff = new StringBuilder(length); + for (int i = 0; i < length; i++) { + char c = s.charAt(i); + if (c == '\\') { + if (i + 1 >= s.length()) { + throw getFormatException(s, i); + } + 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 '#': + // for properties files + buff.append('#'); + break; + case '=': + // for properties files + buff.append('='); + break; + case ':': + // for properties files + buff.append(':'); + break; + case '"': + buff.append('"'); + break; + case '\\': + buff.append('\\'); + break; + case 'u': { + if (i + 4 >= length) { + throw getFormatException(s, i); + } + 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' && i + 2 < length) { + 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(); + } + + /** + * Convert a string to the Java literal and enclose it with double quotes. + * Null will result in "null" (without double quotes). + * + * @param s the text to convert + * @return the Java representation + */ + public static String quoteJavaString(String s) { + if (s == null) { + return "null"; + } + StringBuilder builder = new StringBuilder(s.length() + 2).append('"'); + javaEncode(s, builder, false); + return builder.append('"').toString(); + } + + /** + * Convert a string array to the Java source code that represents this + * array. Null will be converted to 'null'. + * + * @param array the string array + * @return the Java source code (including new String[]{}) + */ + public static String quoteJavaStringArray(String[] array) { + if (array == null) { + return "null"; + } + StringBuilder buff = new StringBuilder("new String[]{"); + for (int i = 0; i < array.length; i++) { + if (i > 0) { + buff.append(", "); + } + buff.append(quoteJavaString(array[i])); + } + return buff.append('}').toString(); + } + + /** + * Convert an int array to the Java source code that represents this array. + * Null will be converted to 'null'. + * + * @param array the int array + * @return the Java source code (including new int[]{}) + */ + public static String quoteJavaIntArray(int[] array) { + if (array == null) { + return "null"; + } + StringBuilder builder = new StringBuilder("new int[]{"); + for (int i = 0; i < array.length; i++) { + if (i > 0) { + builder.append(", "); + } + builder.append(array[i]); + } + return builder.append('}').toString(); + } + + /** + * Encode the string as a URL. + * + * @param s the string to encode + * @return the encoded string + */ + public static String urlEncode(String s) { + try { + return URLEncoder.encode(s, "UTF-8"); + } catch (Exception e) { + // UnsupportedEncodingException + throw DbException.convert(e); + } + } + + /** + * Decode the URL to a string. + * + * @param encoded the encoded URL + * @return the decoded string + */ + public static String urlDecode(String encoded) { + int length = encoded.length(); + byte[] buff = new byte[length]; + int j = 0; + for (int i = 0; i < length; i++) { + char ch = encoded.charAt(i); + if (ch == '+') { + buff[j++] = ' '; + } else if (ch == '%') { + buff[j++] = (byte) Integer.parseInt(encoded.substring(i + 1, i + 3), 16); + i += 2; + } else if (ch <= 127 && ch >= ' ') { + buff[j++] = (byte) ch; + } else { + throw new IllegalArgumentException("Unexpected char " + (int) ch + " decoding " + encoded); + } + } + return new String(buff, 0, j, StandardCharsets.UTF_8); + } + + /** + * Split a string into an array of strings using the given separator. A null + * string will result in a null array, and an empty string in a zero element + * array. + * + * @param s the string to split + * @param separatorChar the separator character + * @param trim whether each element should be trimmed + * @return the array list + */ + public static String[] arraySplit(String s, char separatorChar, boolean trim) { + if (s == null) { + return null; + } + int length = s.length(); + if (length == 0) { + return new String[0]; + } + ArrayList list = Utils.newSmallArrayList(); + StringBuilder buff = new StringBuilder(length); + for (int i = 0; i < length; i++) { + char c = s.charAt(i); + if (c == separatorChar) { + String e = buff.toString(); + list.add(trim ? e.trim() : e); + buff.setLength(0); + } else if (c == '\\' && i < length - 1) { + buff.append(s.charAt(++i)); + } else { + buff.append(c); + } + } + String e = buff.toString(); + list.add(trim ? e.trim() : e); + return list.toArray(new String[0]); + } + + /** + * Combine an array of strings to one array using the given separator + * character. A backslash and the separator character and escaped using a + * backslash. + * + * @param list the string array + * @param separatorChar the separator character + * @return the combined string + */ + public static String arrayCombine(String[] list, char separatorChar) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < list.length; i++) { + if (i > 0) { + builder.append(separatorChar); + } + String s = list[i]; + if (s == null) { + continue; + } + for (int j = 0, length = s.length(); j < length; j++) { + char c = s.charAt(j); + if (c == '\\' || c == separatorChar) { + builder.append('\\'); + } + builder.append(c); + } + } + return builder.toString(); + } + + /** + * Creates an XML attribute of the form name="value". + * A single space is prepended to the name, + * so that multiple attributes can be concatenated. + * @param name the attribute name + * @param value the attribute value + * @return the attribute + */ + public static String xmlAttr(String name, String value) { + return " " + name + "=\"" + xmlText(value) + "\""; + } + + /** + * Create an XML node with optional attributes and content. + * The data is indented with 4 spaces if it contains a newline character. + * + * @param name the element name + * @param attributes the attributes (may be null) + * @param content the content (may be null) + * @return the node + */ + public static String xmlNode(String name, String attributes, String content) { + return xmlNode(name, attributes, content, true); + } + + /** + * Create an XML node with optional attributes and content. The data is + * indented with 4 spaces if it contains a newline character and the indent + * parameter is set to true. + * + * @param name the element name + * @param attributes the attributes (may be null) + * @param content the content (may be null) + * @param indent whether to indent the content if it contains a newline + * @return the node + */ + public static String xmlNode(String name, String attributes, + String content, boolean indent) { + StringBuilder builder = new StringBuilder(); + builder.append('<').append(name); + if (attributes != null) { + builder.append(attributes); + } + if (content == null) { + builder.append("/>\n"); + return builder.toString(); + } + builder.append('>'); + if (indent && content.indexOf('\n') >= 0) { + builder.append('\n'); + indent(builder, content, 4, true); + } else { + builder.append(content); + } + builder.append("\n"); + return builder.toString(); + } + + /** + * Indents a string with spaces and appends it to a specified builder. + * + * @param builder string builder to append to + * @param s the string + * @param spaces the number of spaces + * @param newline append a newline if there is none + * @return the specified string builder + */ + public static StringBuilder indent(StringBuilder builder, String s, int spaces, boolean newline) { + for (int i = 0, length = s.length(); i < length;) { + for (int j = 0; j < spaces; j++) { + builder.append(' '); + } + int n = s.indexOf('\n', i); + n = n < 0 ? length : n + 1; + builder.append(s, i, n); + i = n; + } + if (newline && !s.endsWith("\n")) { + builder.append('\n'); + } + return builder; + } + + /** + * Escapes a comment. + * If the data contains '--', it is converted to '- -'. + * The data is indented with 4 spaces if it contains a newline character. + * + * @param data the comment text + * @return <!-- data --> + */ + public static String xmlComment(String data) { + int idx = 0; + while (true) { + idx = data.indexOf("--", idx); + if (idx < 0) { + break; + } + data = data.substring(0, idx + 1) + " " + data.substring(idx + 1); + } + // must have a space at the beginning and at the end, + // otherwise the data must not contain '-' as the first/last character + if (data.indexOf('\n') >= 0) { + StringBuilder builder = new StringBuilder(data.length() + 18).append("\n").toString(); + } + return "\n"; + } + + /** + * Converts the data to a CDATA element. + * If the data contains ']]>', it is escaped as a text element. + * + * @param data the text data + * @return <![CDATA[data]]> + */ + public static String xmlCData(String data) { + if (data.contains("]]>")) { + return xmlText(data); + } + boolean newline = data.endsWith("\n"); + data = ""; + return newline ? data + "\n" : data; + } + + /** + * Returns <?xml version="1.0"?> + * @return <?xml version="1.0"?> + */ + public static String xmlStartDoc() { + return "\n"; + } + + /** + * Escapes an XML text element. + * + * @param text the text data + * @return the escaped text + */ + public static String xmlText(String text) { + return xmlText(text, false); + } + + /** + * Escapes an XML text element. + * + * @param text the text data + * @param escapeNewline whether to escape newlines + * @return the escaped text + */ + public static String xmlText(String text, boolean escapeNewline) { + int length = text.length(); + StringBuilder buff = new StringBuilder(length); + for (int i = 0; i < length; i++) { + char ch = text.charAt(i); + switch (ch) { + case '<': + buff.append("<"); + break; + case '>': + buff.append(">"); + break; + case '&': + buff.append("&"); + break; + case '\'': + // ' is not valid in HTML + buff.append("'"); + break; + case '\"': + buff.append("""); + break; + case '\r': + case '\n': + if (escapeNewline) { + buff.append("&#x"). + append(Integer.toHexString(ch)). + append(';'); + } else { + buff.append(ch); + } + break; + case '\t': + buff.append(ch); + break; + default: + if (ch < ' ' || ch > 127) { + buff.append("&#x"). + append(Integer.toHexString(ch)). + append(';'); + } else { + buff.append(ch); + } + } + } + return buff.toString(); + } + + /** + * Replace all occurrences of the before string with the after string. Unlike + * {@link String#replaceAll(String, String)} this method reads {@code before} + * and {@code after} arguments as plain strings and if {@code before} argument + * is an empty string this method returns original string {@code s}. + * + * @param s the string + * @param before the old text + * @param after the new text + * @return the string with the before string replaced + */ + public static String replaceAll(String s, String before, String after) { + int next = s.indexOf(before); + if (next < 0 || before.isEmpty()) { + return s; + } + StringBuilder buff = new StringBuilder( + s.length() - before.length() + after.length()); + int index = 0; + while (true) { + buff.append(s, index, next).append(after); + index = next + before.length(); + next = s.indexOf(before, index); + if (next < 0) { + buff.append(s, index, s.length()); + break; + } + } + return buff.toString(); + } + + /** + * Enclose a string with double quotes. A double quote inside the string is + * escaped using a double quote. + * + * @param s the text + * @return the double quoted text + */ + public static String quoteIdentifier(String s) { + return quoteIdentifierOrLiteral(new StringBuilder(s.length() + 2), s, '"').toString(); + } + + /** + * Enclose a string with double quotes and append it to the specified + * string builder. A double quote inside the string is escaped using a + * double quote. + * + * @param builder string builder to append to + * @param s the text + * @return the specified builder + */ + public static StringBuilder quoteIdentifier(StringBuilder builder, String s) { + return quoteIdentifierOrLiteral(builder, s, '"'); + } + + private static StringBuilder quoteIdentifierOrLiteral(StringBuilder builder, String s, char q) { + int builderLength = builder.length(); + builder.append(q); + for (int i = 0, l = s.length(); i < l;) { + int cp = s.codePointAt(i); + i += Character.charCount(cp); + if (cp < ' ' || cp > 127) { + // need to start from the beginning + builder.setLength(builderLength); + builder.append("U&").append(q); + for (i = 0; i < l;) { + cp = s.codePointAt(i); + i += Character.charCount(cp); + if (cp >= ' ' && cp < 127) { + char ch = (char) cp; + if (ch == q || ch == '\\') { + builder.append(ch); + } + builder.append(ch); + } else if (cp <= 0xffff) { + appendHex(builder.append('\\'), cp, 2); + } else { + appendHex(builder.append("\\+"), cp, 3); + } + } + break; + } + if (cp == q) { + builder.append(q); + } + builder.append((char) cp); + } + return builder.append(q); + } + + /** + * Check if a String is null or empty (the length is null). + * + * @param s the string to check + * @return true if it is null or empty + */ + public static boolean isNullOrEmpty(String s) { + return s == null || s.isEmpty(); + } + + /** + * Pad a string. This method is used for the SQL function RPAD and LPAD. + * + * @param string the original string + * @param n the target length + * @param padding the padding string + * @param right true if the padding should be appended at the end + * @return the padded string + */ + public static String pad(String string, int n, String padding, boolean right) { + if (n < 0) { + n = 0; + } + if (n < string.length()) { + return string.substring(0, n); + } else if (n == string.length()) { + return string; + } + char paddingChar; + if (padding == null || padding.isEmpty()) { + paddingChar = ' '; + } else { + paddingChar = padding.charAt(0); + } + StringBuilder buff = new StringBuilder(n); + n -= string.length(); + if (right) { + buff.append(string); + } + for (int i = 0; i < n; i++) { + buff.append(paddingChar); + } + if (!right) { + buff.append(string); + } + return buff.toString(); + } + + /** + * Create a new char array and copy all the data. If the size of the byte + * array is zero, the same array is returned. + * + * @param chars the char array (may be null) + * @return a new char array + */ + public static char[] cloneCharArray(char[] chars) { + if (chars == null) { + return null; + } + int len = chars.length; + if (len == 0) { + return chars; + } + return Arrays.copyOf(chars, len); + } + + /** + * Trim a character from a string. + * + * @param s the string + * @param leading if leading characters should be removed + * @param trailing if trailing characters should be removed + * @param sp what to remove (only the first character is used) + * or null for a space + * @return the trimmed string + */ + public static String trim(String s, boolean leading, boolean trailing, + String sp) { + char space = sp == null || sp.isEmpty() ? ' ' : sp.charAt(0); + int begin = 0, end = s.length(); + if (leading) { + while (begin < end && s.charAt(begin) == space) { + begin++; + } + } + if (trailing) { + while (end > begin && s.charAt(end - 1) == space) { + end--; + } + } + // substring() returns self if start == 0 && end == length() + return s.substring(begin, end); + } + + /** + * Trim a whitespace from a substring. Equivalent of + * {@code substring(beginIndex).trim()}. + * + * @param s the string + * @param beginIndex start index of substring (inclusive) + * @return trimmed substring + */ + public static String trimSubstring(String s, int beginIndex) { + return trimSubstring(s, beginIndex, s.length()); + } + + /** + * Trim a whitespace from a substring. Equivalent of + * {@code substring(beginIndex, endIndex).trim()}. + * + * @param s the string + * @param beginIndex start index of substring (inclusive) + * @param endIndex end index of substring (exclusive) + * @return trimmed substring + */ + public static String trimSubstring(String s, int beginIndex, int endIndex) { + while (beginIndex < endIndex && s.charAt(beginIndex) <= ' ') { + beginIndex++; + } + while (beginIndex < endIndex && s.charAt(endIndex - 1) <= ' ') { + endIndex--; + } + return s.substring(beginIndex, endIndex); + } + + /** + * Trim a whitespace from a substring and append it to a specified string + * builder. Equivalent of + * {@code builder.append(substring(beginIndex, endIndex).trim())}. + * + * @param builder string builder to append to + * @param s the string + * @param beginIndex start index of substring (inclusive) + * @param endIndex end index of substring (exclusive) + * @return the specified builder + */ + public static StringBuilder trimSubstring(StringBuilder builder, String s, int beginIndex, int endIndex) { + while (beginIndex < endIndex && s.charAt(beginIndex) <= ' ') { + beginIndex++; + } + while (beginIndex < endIndex && s.charAt(endIndex - 1) <= ' ') { + endIndex--; + } + return builder.append(s, beginIndex, endIndex); + } + + /** + * Truncates the specified string to the specified length. This method, + * unlike {@link String#substring(int, int)}, doesn't break Unicode code + * points. If the specified length in characters breaks a valid pair of + * surrogates, the whole pair is not included into result. + * + * @param s + * the string to truncate + * @param maximumLength + * the maximum length in characters + * @return the specified string if it isn't longer than the specified + * maximum length, and the truncated string otherwise + */ + public static String truncateString(String s, int maximumLength) { + if (s.length() > maximumLength) { + s = maximumLength > 0 ? s.substring(0, + Character.isSurrogatePair(s.charAt(maximumLength - 1), s.charAt(maximumLength)) ? maximumLength - 1 + : maximumLength) + : ""; + } + return s; + } + + /** + * Get the string from the cache if possible. If the string has not been + * found, it is added to the cache. If there is such a string in the cache, + * that one is returned. + * + * @param s the original string + * @return a string with the same content, if possible from the cache + */ + public static String cache(String s) { + if (!SysProperties.OBJECT_CACHE) { + return s; + } + if (s == null) { + return s; + } else if (s.isEmpty()) { + return ""; + } + String[] cache = getCache(); + if (cache != null) { + int hash = s.hashCode(); + int index = hash & (SysProperties.OBJECT_CACHE_SIZE - 1); + String cached = cache[index]; + if (s.equals(cached)) { + return cached; + } + cache[index] = s; + } + return s; + } + + /** + * Clear the cache. This method is used for testing. + */ + public static void clearCache() { + softCache = null; + } + + /** + * Parses an unsigned 31-bit integer. Neither - nor + signs are allowed. + * + * @param s string to parse + * @param start the beginning index, inclusive + * @param end the ending index, exclusive + * @return the unsigned {@code int} not greater than {@link Integer#MAX_VALUE}. + */ + public static int parseUInt31(String s, int start, int end) { + if (end > s.length() || start < 0 || start > end) { + throw new IndexOutOfBoundsException(); + } + if (start == end) { + throw new NumberFormatException(""); + } + int result = 0; + for (int i = start; i < end; i++) { + char ch = s.charAt(i); + // Ensure that character is valid and that multiplication by 10 will + // be performed without overflow + if (ch < '0' || ch > '9' || result > 214_748_364) { + throw new NumberFormatException(s.substring(start, end)); + } + result = result * 10 + ch - '0'; + if (result < 0) { + // Overflow + throw new NumberFormatException(s.substring(start, end)); + } + } + return result; + } + + /** + * Convert a hex encoded string to a byte array. + * + * @param s the hex encoded string + * @return the byte array + */ + public static byte[] convertHexToBytes(String s) { + int len = s.length(); + if (len % 2 != 0) { + throw DbException.get(ErrorCode.HEX_STRING_ODD_1, s); + } + len /= 2; + byte[] buff = new byte[len]; + int mask = 0; + int[] hex = HEX_DECODE; + try { + for (int i = 0; i < len; i++) { + int d = hex[s.charAt(i + i)] << 4 | hex[s.charAt(i + i + 1)]; + mask |= d; + buff[i] = (byte) d; + } + } catch (ArrayIndexOutOfBoundsException e) { + throw DbException.get(ErrorCode.HEX_STRING_WRONG_1, s); + } + if ((mask & ~255) != 0) { + throw DbException.get(ErrorCode.HEX_STRING_WRONG_1, s); + } + return buff; + } + + /** + * Parses a hex encoded string with possible space separators and appends + * the decoded binary string to the specified output stream. + * + * @param baos the output stream, or {@code null} + * @param s the hex encoded string + * @param start the start index + * @param end the end index, exclusive + * @return the specified output stream or a new output stream + */ + public static ByteArrayOutputStream convertHexWithSpacesToBytes(ByteArrayOutputStream baos, String s, int start, + int end) { + if (baos == null) { + baos = new ByteArrayOutputStream((end - start) >>> 1); + } + int mask = 0; + int[] hex = HEX_DECODE; + try { + loop: for (int i = start;;) { + char c1, c2; + do { + if (i >= end) { + break loop; + } + c1 = s.charAt(i++); + } while (c1 == ' '); + do { + if (i >= end) { + if (((mask | hex[c1]) & ~255) != 0) { + throw getHexStringException(ErrorCode.HEX_STRING_WRONG_1, s, start, end); + } + throw getHexStringException(ErrorCode.HEX_STRING_ODD_1, s, start, end); + } + c2 = s.charAt(i++); + } while (c2 == ' '); + int d = hex[c1] << 4 | hex[c2]; + mask |= d; + baos.write(d); + } + } catch (ArrayIndexOutOfBoundsException e) { + throw getHexStringException(ErrorCode.HEX_STRING_WRONG_1, s, start, end); + } + if ((mask & ~255) != 0) { + throw getHexStringException(ErrorCode.HEX_STRING_WRONG_1, s, start, end); + } + return baos; + } + + private static DbException getHexStringException(int code, String s, int start, int end) { + return DbException.get(code, s.substring(start, end)); + } + + /** + * Convert a byte array to a hex encoded string. + * + * @param value the byte array + * @return the hex encoded string + */ + public static String convertBytesToHex(byte[] value) { + return convertBytesToHex(value, value.length); + } + + /** + * Convert a byte array to a hex encoded string. + * + * @param value the byte array + * @param len the number of bytes to encode + * @return the hex encoded string + */ + public static String convertBytesToHex(byte[] value, int len) { + byte[] bytes = new byte[len * 2]; + char[] hex = HEX; + for (int i = 0, j = 0; i < len; i++) { + int c = value[i] & 0xff; + bytes[j++] = (byte) hex[c >> 4]; + bytes[j++] = (byte) hex[c & 0xf]; + } + return new String(bytes, StandardCharsets.ISO_8859_1); + } + + /** + * Convert a byte array to a hex encoded string and appends it to a specified string builder. + * + * @param builder string builder to append to + * @param value the byte array + * @return the hex encoded string + */ + public static StringBuilder convertBytesToHex(StringBuilder builder, byte[] value) { + return convertBytesToHex(builder, value, value.length); + } + + /** + * Convert a byte array to a hex encoded string and appends it to a specified string builder. + * + * @param builder string builder to append to + * @param value the byte array + * @param len the number of bytes to encode + * @return the hex encoded string + */ + public static StringBuilder convertBytesToHex(StringBuilder builder, byte[] value, int len) { + char[] hex = HEX; + for (int i = 0; i < len; i++) { + int c = value[i] & 0xff; + builder.append(hex[c >>> 4]).append(hex[c & 0xf]); + } + return builder; + } + + /** + * Appends specified number of trailing bytes from unsigned long value to a + * specified string builder. + * + * @param builder + * string builder to append to + * @param x + * value to append + * @param bytes + * number of bytes to append + * @return the specified string builder + */ + public static StringBuilder appendHex(StringBuilder builder, long x, int bytes) { + char[] hex = HEX; + for (int i = bytes * 8; i > 0;) { + builder.append(hex[(int) (x >> (i -= 4)) & 0xf]).append(hex[(int) (x >> (i -= 4)) & 0xf]); + } + return builder; + } + + /** + * Check if this string is a decimal number. + * + * @param s the string + * @return true if it is + */ + public static boolean isNumber(String s) { + int l = s.length(); + if (l == 0) { + return false; + } + for (int i = 0; i < l; i++) { + if (!Character.isDigit(s.charAt(i))) { + return false; + } + } + return true; + } + + /** + * Check if the specified string is empty or contains only whitespace. + * + * @param s + * the string + * @return whether the specified string is empty or contains only whitespace + */ + public static boolean isWhitespaceOrEmpty(String s) { + for (int i = 0, l = s.length(); i < l; i++) { + if (s.charAt(i) > ' ') { + return false; + } + } + return true; + } + + /** + * Append a zero-padded number from 00 to 99 to a string builder. + * + * @param builder the string builder + * @param positiveValue the number to append + * @return the specified string builder + */ + public static StringBuilder appendTwoDigits(StringBuilder builder, int positiveValue) { + if (positiveValue < 10) { + builder.append('0'); + } + return builder.append(positiveValue); + } + + /** + * Append a zero-padded number to a string builder. + * + * @param builder the string builder + * @param length the number of characters to append + * @param positiveValue the number to append + * @return the specified string builder + */ + public static StringBuilder appendZeroPadded(StringBuilder builder, int length, long positiveValue) { + String s = Long.toString(positiveValue); + length -= s.length(); + for (; length > 0; length--) { + builder.append('0'); + } + return builder.append(s); + } + + /** + * Escape table or schema patterns used for DatabaseMetaData functions. + * + * @param pattern the pattern + * @return the escaped pattern + */ + public static String escapeMetaDataPattern(String pattern) { + if (pattern == null || pattern.isEmpty()) { + return pattern; + } + return replaceAll(pattern, "\\", "\\\\"); + } + +} diff --git a/h2/src/main/org/h2/util/Task.java b/h2/src/main/org/h2/util/Task.java new file mode 100644 index 0000000..b238ee1 --- /dev/null +++ b/h2/src/main/org/h2/util/Task.java @@ -0,0 +1,126 @@ +/* + * 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.util; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * A method call that is executed in a separate thread. If the method throws an + * exception, it is wrapped in a RuntimeException. + */ +public abstract class Task implements Runnable { + + private static final AtomicInteger counter = new AtomicInteger(); + + /** + * A flag indicating the get() method has been called. + */ + public volatile boolean stop; + + /** + * The result, if any. + */ + private volatile Object result; + + private volatile boolean finished; + + private Thread thread; + + private volatile Exception ex; + + /** + * The method to be implemented. + * + * @throws Exception any exception is wrapped in a RuntimeException + */ + public abstract void call() throws Exception; + + @Override + public void run() { + try { + call(); + } catch (Exception e) { + this.ex = e; + } + finished = true; + } + + /** + * Start the thread. + * + * @return this + */ + public Task execute() { + return execute(getClass().getName() + ":" + counter.getAndIncrement()); + } + + /** + * Start the thread. + * + * @param threadName the name of the thread + * @return this + */ + public Task execute(String threadName) { + thread = new Thread(this, threadName); + thread.setDaemon(true); + thread.start(); + return this; + } + + /** + * Calling this method will set the stop flag and wait until the thread is + * stopped. + * + * @return the result, or null + * @throws RuntimeException if an exception in the method call occurs + */ + public Object get() { + Exception e = getException(); + if (e != null) { + throw new RuntimeException(e); + } + return result; + } + + /** + * Whether the call method has returned (with or without exception). + * + * @return true if yes + */ + public boolean isFinished() { + return finished; + } + + /** + * Get the exception that was thrown in the call (if any). + * + * @return the exception or null + */ + public Exception getException() { + join(); + if (ex != null) { + return ex; + } + return null; + } + + /** + * Stop the thread and wait until it is no longer running. Exceptions are + * ignored. + */ + public void join() { + stop = true; + if (thread == null) { + throw new IllegalStateException("Thread not started"); + } + try { + thread.join(); + } catch (InterruptedException e) { + // ignore + } + } + +} diff --git a/h2/src/main/org/h2/util/TempFileDeleter.java b/h2/src/main/org/h2/util/TempFileDeleter.java new file mode 100644 index 0000000..1afe2da --- /dev/null +++ b/h2/src/main/org/h2/util/TempFileDeleter.java @@ -0,0 +1,138 @@ +/* + * 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.util; + +import java.lang.ref.PhantomReference; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.util.ArrayList; +import java.util.HashMap; + +import org.h2.engine.SysProperties; +import org.h2.message.DbException; +import org.h2.store.fs.FileUtils; + +/** + * This class deletes temporary files when they are not used any longer. + */ +public class TempFileDeleter { + + private final ReferenceQueue queue = new ReferenceQueue<>(); + private final HashMap, Object> refMap = new HashMap<>(); + + private TempFileDeleter() { + // utility class + } + + public static TempFileDeleter getInstance() { + return new TempFileDeleter(); + } + + /** + * Add a file or a closeable to the list of temporary objects to delete. The + * file is deleted once the file object is garbage collected. + * + * @param resource the file name or the closeable + * @param monitor the object to monitor + * @return the reference that can be used to stop deleting the file or closing the closeable + */ + public synchronized Reference addFile(Object resource, Object monitor) { + if (!(resource instanceof String) && !(resource instanceof AutoCloseable)) { + throw DbException.getUnsupportedException("Unsupported resource " + resource); + } + IOUtils.trace("TempFileDeleter.addFile", + resource instanceof String ? (String) resource : "-", monitor); + PhantomReference ref = new PhantomReference<>(monitor, queue); + refMap.put(ref, resource); + deleteUnused(); + return ref; + } + + /** + * Delete the given file or close the closeable now. This will remove the + * reference from the list. + * + * @param ref the reference as returned by addFile + * @param resource the file name or closeable + */ + public synchronized void deleteFile(Reference ref, Object resource) { + if (ref != null) { + Object f2 = refMap.remove(ref); + if (f2 != null) { + if (SysProperties.CHECK) { + if (resource != null && !f2.equals(resource)) { + throw DbException.getInternalError("f2:" + f2 + " f:" + resource); + } + } + resource = f2; + } + } + if (resource instanceof String) { + String fileName = (String) resource; + if (FileUtils.exists(fileName)) { + try { + IOUtils.trace("TempFileDeleter.deleteFile", fileName, null); + FileUtils.tryDelete(fileName); + } catch (Exception e) { + // TODO log such errors? + } + } + } else if (resource instanceof AutoCloseable) { + AutoCloseable closeable = (AutoCloseable) resource; + try { + IOUtils.trace("TempFileDeleter.deleteCloseable", "-", null); + closeable.close(); + } catch (Exception e) { + // TODO log such errors? + } + } + } + + /** + * Delete all registered temp resources. + */ + public void deleteAll() { + for (Object resource : new ArrayList<>(refMap.values())) { + deleteFile(null, resource); + } + deleteUnused(); + } + + /** + * Delete all unused resources now. + */ + public void deleteUnused() { + while (queue != null) { + Reference ref = queue.poll(); + if (ref == null) { + break; + } + deleteFile(ref, null); + } + } + + /** + * This method is called if a file should no longer be deleted or a resource + * should no longer be closed if the object is garbage collected. + * + * @param ref the reference as returned by addFile + * @param resource file name or closeable + */ + public void stopAutoDelete(Reference ref, Object resource) { + IOUtils.trace("TempFileDeleter.stopAutoDelete", + resource instanceof String ? (String) resource : "-", ref); + if (ref != null) { + Object f2 = refMap.remove(ref); + if (SysProperties.CHECK) { + if (f2 == null || !f2.equals(resource)) { + throw DbException.getInternalError("f2:" + f2 + ' ' + (f2 == null ? "" : f2) + " f:" + resource); + } + } + } + deleteUnused(); + } + +} diff --git a/h2/src/main/org/h2/util/ThreadDeadlockDetector.java b/h2/src/main/org/h2/util/ThreadDeadlockDetector.java new file mode 100644 index 0000000..8acdb9c --- /dev/null +++ b/h2/src/main/org/h2/util/ThreadDeadlockDetector.java @@ -0,0 +1,197 @@ +/* + * 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.util; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.management.LockInfo; +import java.lang.management.ManagementFactory; +import java.lang.management.MonitorInfo; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Timer; +import java.util.TimerTask; +import org.h2.engine.SysProperties; +import org.h2.mvstore.db.MVTable; + +/** + * Detects deadlocks between threads. Prints out data in the same format as the + * CTRL-BREAK handler, but includes information about table locks. + */ +public class ThreadDeadlockDetector { + + private static final String INDENT = " "; + + private static ThreadDeadlockDetector detector; + + private final ThreadMXBean threadBean; + + private ThreadDeadlockDetector() { + this.threadBean = ManagementFactory.getThreadMXBean(); + + // a daemon thread + // delay: 10 ms + // period: 10000 ms (100 seconds) + Timer threadCheck = new Timer("ThreadDeadlockDetector", true); + threadCheck.schedule(new TimerTask() { + @Override + public void run() { + checkForDeadlocks(); + } + }, 10, 10_000); + } + + /** + * Initialize the detector. + */ + public static synchronized void init() { + if (detector == null) { + detector = new ThreadDeadlockDetector(); + } + } + + /** + * Checks if any threads are deadlocked. If any, print the thread dump + * information. + */ + void checkForDeadlocks() { + long[] deadlockedThreadIds = threadBean.findDeadlockedThreads(); + if (deadlockedThreadIds == null) { + return; + } + dumpThreadsAndLocks("ThreadDeadlockDetector - deadlock found :", + threadBean, deadlockedThreadIds, System.out); + } + + /** + * Dump all deadlocks (if any). + * + * @param msg the message + */ + public static void dumpAllThreadsAndLocks(String msg) { + dumpAllThreadsAndLocks(msg, System.out); + } + + /** + * Dump all deadlocks (if any). + * + * @param msg the message + * @param out the output + */ + public static void dumpAllThreadsAndLocks(String msg, PrintStream out) { + final ThreadMXBean threadBean = ManagementFactory.getThreadMXBean(); + final long[] allThreadIds = threadBean.getAllThreadIds(); + dumpThreadsAndLocks(msg, threadBean, allThreadIds, out); + } + + private static void dumpThreadsAndLocks(String msg, ThreadMXBean threadBean, + long[] threadIds, PrintStream out) { + final StringWriter stringWriter = new StringWriter(); + final PrintWriter print = new PrintWriter(stringWriter); + + print.println(msg); + + final HashMap tableWaitingForLockMap; + final HashMap> tableExclusiveLocksMap; + final HashMap> tableSharedLocksMap; + if (SysProperties.THREAD_DEADLOCK_DETECTOR) { + tableWaitingForLockMap = MVTable.WAITING_FOR_LOCK + .getSnapshotOfAllThreads(); + tableExclusiveLocksMap = MVTable.EXCLUSIVE_LOCKS + .getSnapshotOfAllThreads(); + tableSharedLocksMap = MVTable.SHARED_LOCKS + .getSnapshotOfAllThreads(); + } else { + tableWaitingForLockMap = new HashMap<>(); + tableExclusiveLocksMap = new HashMap<>(); + tableSharedLocksMap = new HashMap<>(); + } + + final ThreadInfo[] infos = threadBean.getThreadInfo(threadIds, true, + true); + for (ThreadInfo ti : infos) { + printThreadInfo(print, ti); + printLockInfo(print, ti.getLockedSynchronizers(), + tableWaitingForLockMap.get(ti.getThreadId()), + tableExclusiveLocksMap.get(ti.getThreadId()), + tableSharedLocksMap.get(ti.getThreadId())); + } + + print.flush(); + // Dump it to system.out in one block, so it doesn't get mixed up with + // other stuff when we're using a logging subsystem. + out.println(stringWriter.getBuffer()); + out.flush(); + } + + private static void printThreadInfo(PrintWriter print, ThreadInfo ti) { + // print thread information + printThread(print, ti); + + // print stack trace with locks + StackTraceElement[] stackTrace = ti.getStackTrace(); + MonitorInfo[] monitors = ti.getLockedMonitors(); + for (int i = 0; i < stackTrace.length; i++) { + StackTraceElement e = stackTrace[i]; + print.println(INDENT + "at " + e.toString()); + for (MonitorInfo mi : monitors) { + if (mi.getLockedStackDepth() == i) { + print.println(INDENT + " - locked " + mi); + } + } + } + print.println(); + } + + private static void printThread(PrintWriter print, ThreadInfo ti) { + print.print("\"" + ti.getThreadName() + "\"" + " Id=" + + ti.getThreadId() + " in " + ti.getThreadState()); + if (ti.getLockName() != null) { + print.append(" on lock=").append(ti.getLockName()); + } + if (ti.isSuspended()) { + print.append(" (suspended)"); + } + if (ti.isInNative()) { + print.append(" (running in native)"); + } + print.println(); + if (ti.getLockOwnerName() != null) { + print.println(INDENT + " owned by " + ti.getLockOwnerName() + " Id=" + + ti.getLockOwnerId()); + } + } + + private static void printLockInfo(PrintWriter print, LockInfo[] locks, + String tableWaitingForLock, + ArrayList tableExclusiveLocks, + ArrayList tableSharedLocksMap) { + print.println(INDENT + "Locked synchronizers: count = " + locks.length); + for (LockInfo li : locks) { + print.println(INDENT + " - " + li); + } + if (tableWaitingForLock != null) { + print.println(INDENT + "Waiting for table: " + tableWaitingForLock); + } + if (tableExclusiveLocks != null) { + print.println(INDENT + "Exclusive table locks: count = " + tableExclusiveLocks.size()); + for (String name : tableExclusiveLocks) { + print.println(INDENT + " - " + name); + } + } + if (tableSharedLocksMap != null) { + print.println(INDENT + "Shared table locks: count = " + tableSharedLocksMap.size()); + for (String name : tableSharedLocksMap) { + print.println(INDENT + " - " + name); + } + } + print.println(); + } + +} diff --git a/h2/src/main/org/h2/util/TimeZoneProvider.java b/h2/src/main/org/h2/util/TimeZoneProvider.java new file mode 100644 index 0000000..f5b7bc2 --- /dev/null +++ b/h2/src/main/org/h2/util/TimeZoneProvider.java @@ -0,0 +1,441 @@ +/* + * 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.util; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.zone.ZoneRules; +import java.util.Locale; + +/** + * Provides access to time zone API. + */ +public abstract class TimeZoneProvider { + + /** + * The UTC time zone provider. + */ + public static final TimeZoneProvider UTC = new Simple((short) 0); + + /** + * A small cache for timezone providers. + */ + public static TimeZoneProvider[] CACHE; + + /** + * The number of cache elements (needs to be a power of 2). + */ + private static final int CACHE_SIZE = 32; + + /** + * Returns the time zone provider with the specified offset. + * + * @param offset + * UTC offset in seconds + * @return the time zone provider with the specified offset + */ + public static TimeZoneProvider ofOffset(int offset) { + if (offset == 0) { + return UTC; + } + if (offset < (-18 * 60 * 60) || offset > (18 * 60 * 60)) { + throw new IllegalArgumentException("Time zone offset " + offset + " seconds is out of range"); + } + return new Simple(offset); + } + + /** + * Returns the time zone provider with the specified name. + * + * @param id + * the ID of the time zone + * @return the time zone provider with the specified name + * @throws RuntimeException + * if time zone with specified ID isn't known + */ + public static TimeZoneProvider ofId(String id) throws RuntimeException { + int length = id.length(); + if (length == 1 && id.charAt(0) == 'Z') { + return UTC; + } + int index = 0; + if (id.startsWith("GMT") || id.startsWith("UTC")) { + if (length == 3) { + return UTC; + } + index = 3; + } + if (length > index) { + boolean negative = false; + char c = id.charAt(index); + if (length > index + 1) { + if (c == '+') { + c = id.charAt(++index); + } else if (c == '-') { + negative = true; + c = id.charAt(++index); + } + } + if (index != 3 && c >= '0' && c <= '9') { + int hour = c - '0'; + if (++index < length) { + c = id.charAt(index); + if (c >= '0' && c <= '9') { + hour = hour * 10 + c - '0'; + index++; + } + } + if (index == length) { + int offset = hour * 3_600; + return ofOffset(negative ? -offset : offset); + } + if (id.charAt(index) == ':') { + if (++index < length) { + c = id.charAt(index); + if (c >= '0' && c <= '9') { + int minute = c - '0'; + if (++index < length) { + c = id.charAt(index); + if (c >= '0' && c <= '9') { + minute = minute * 10 + c - '0'; + index++; + } + } + if (index == length) { + int offset = (hour * 60 + minute) * 60; + return ofOffset(negative ? -offset : offset); + } + if (id.charAt(index) == ':') { + if (++index < length) { + c = id.charAt(index); + if (c >= '0' && c <= '9') { + int second = c - '0'; + if (++index < length) { + c = id.charAt(index); + if (c >= '0' && c <= '9') { + second = second * 10 + c - '0'; + index++; + } + } + if (index == length) { + int offset = (hour * 60 + minute) * 60 + second; + return ofOffset(negative ? -offset : offset); + } + } + } + } + } + } + } + } + if (index > 0) { + throw new IllegalArgumentException(id); + } + } + int hash = id.hashCode() & (CACHE_SIZE - 1); + TimeZoneProvider[] cache = CACHE; + if (cache != null) { + TimeZoneProvider provider = cache[hash]; + if (provider != null && provider.getId().equals(id)) { + return provider; + } + } + TimeZoneProvider provider = new WithTimeZone(ZoneId.of(id, ZoneId.SHORT_IDS)); + if (cache == null) { + CACHE = cache = new TimeZoneProvider[CACHE_SIZE]; + } + cache[hash] = provider; + return provider; + } + + /** + * Returns the time zone provider for the system default time zone. + * + * @return the time zone provider for the system default time zone + */ + public static TimeZoneProvider getDefault() { + ZoneId zoneId = ZoneId.systemDefault(); + ZoneOffset offset; + if (zoneId instanceof ZoneOffset) { + offset = (ZoneOffset) zoneId; + } else { + ZoneRules rules = zoneId.getRules(); + if (!rules.isFixedOffset()) { + return new WithTimeZone(zoneId); + } + offset = rules.getOffset(Instant.EPOCH); + } + return ofOffset(offset.getTotalSeconds()); + } + + /** + * Calculates the time zone offset in seconds for the specified EPOCH + * seconds. + * + * @param epochSeconds + * seconds since EPOCH + * @return time zone offset in minutes + */ + public abstract int getTimeZoneOffsetUTC(long epochSeconds); + + /** + * Calculates the time zone offset in seconds for the specified date value + * and nanoseconds since midnight in local time. + * + * @param dateValue + * date value + * @param timeNanos + * nanoseconds since midnight + * @return time zone offset in minutes + */ + public abstract int getTimeZoneOffsetLocal(long dateValue, long timeNanos); + + /** + * Calculates the epoch seconds from local date and time. + * + * @param dateValue + * date value + * @param timeNanos + * nanoseconds since midnight + * @return the epoch seconds value + */ + public abstract long getEpochSecondsFromLocal(long dateValue, long timeNanos); + + /** + * Returns the ID of the time zone. + * + * @return the ID of the time zone + */ + public abstract String getId(); + + /** + * Get the standard time name or daylight saving time name of the time zone. + * + * @param epochSeconds + * seconds since EPOCH + * @return the standard time name or daylight saving time name of the time + * zone + */ + public abstract String getShortId(long epochSeconds); + + /** + * Returns whether this is a simple time zone provider with a fixed offset + * from UTC. + * + * @return whether this is a simple time zone provider with a fixed offset + * from UTC + */ + public boolean hasFixedOffset() { + return false; + } + + /** + * Time zone provider with offset. + */ + private static final class Simple extends TimeZoneProvider { + + private final int offset; + + private volatile String id; + + Simple(int offset) { + this.offset = offset; + } + + @Override + public int hashCode() { + return offset + 129607; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != Simple.class) { + return false; + } + return offset == ((Simple) obj).offset; + } + + @Override + public int getTimeZoneOffsetUTC(long epochSeconds) { + return offset; + } + + @Override + public int getTimeZoneOffsetLocal(long dateValue, long timeNanos) { + return offset; + } + + @Override + public long getEpochSecondsFromLocal(long dateValue, long timeNanos) { + return DateTimeUtils.getEpochSeconds(dateValue, timeNanos, offset); + } + + @Override + public String getId() { + String id = this.id; + if (id == null) { + this.id = id = DateTimeUtils.timeZoneNameFromOffsetSeconds(offset); + } + return id; + } + + @Override + public String getShortId(long epochSeconds) { + return getId(); + } + + @Override + public boolean hasFixedOffset() { + return true; + } + + @Override + public String toString() { + return "TimeZoneProvider " + getId(); + } + + } + + /** + * Time zone provider with time zone. + */ + static final class WithTimeZone extends TimeZoneProvider { + + /** + * Number of seconds in 400 years. + */ + static final long SECONDS_PER_PERIOD = 146_097L * 60 * 60 * 24; + + /** + * Number of seconds per year. + */ + static final long SECONDS_PER_YEAR = SECONDS_PER_PERIOD / 400; + + private static volatile DateTimeFormatter TIME_ZONE_FORMATTER; + + private final ZoneId zoneId; + + WithTimeZone(ZoneId timeZone) { + this.zoneId = timeZone; + } + + @Override + public int hashCode() { + return zoneId.hashCode() + 951689; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != WithTimeZone.class) { + return false; + } + return zoneId.equals(((WithTimeZone) obj).zoneId); + } + + @Override + public int getTimeZoneOffsetUTC(long epochSeconds) { + /* + * Construct an Instant with EPOCH seconds within the range + * -31,557,014,135,532,000..31,556,889,832,715,999 + * (-999999999-01-01T00:00-18:00.. + * +999999999-12-31T23:59:59.999999999+18:00). Too large and too + * small EPOCH seconds are replaced with EPOCH seconds within the + * range using the 400 years period of the Gregorian calendar. + * + * H2 has slightly wider range of EPOCH seconds than Instant, and + * ZoneRules.getOffset(Instant) does not support all Instant values + * in all time zones. + */ + if (epochSeconds > 31_556_889_832_715_999L) { + epochSeconds -= SECONDS_PER_PERIOD; + } else if (epochSeconds < -31_557_014_135_532_000L) { + epochSeconds += SECONDS_PER_PERIOD; + } + return zoneId.getRules().getOffset(Instant.ofEpochSecond(epochSeconds)).getTotalSeconds(); + } + + @Override + public int getTimeZoneOffsetLocal(long dateValue, long timeNanos) { + int second = (int) (timeNanos / DateTimeUtils.NANOS_PER_SECOND); + int minute = second / 60; + second -= minute * 60; + int hour = minute / 60; + minute -= hour * 60; + return ZonedDateTime.of(LocalDateTime.of(yearForCalendar(DateTimeUtils.yearFromDateValue(dateValue)), + DateTimeUtils.monthFromDateValue(dateValue), DateTimeUtils.dayFromDateValue(dateValue), hour, + minute, second), zoneId).getOffset().getTotalSeconds(); + } + + @Override + public long getEpochSecondsFromLocal(long dateValue, long timeNanos) { + int second = (int) (timeNanos / DateTimeUtils.NANOS_PER_SECOND); + int minute = second / 60; + second -= minute * 60; + int hour = minute / 60; + minute -= hour * 60; + int year = DateTimeUtils.yearFromDateValue(dateValue); + int yearForCalendar = yearForCalendar(year); + long epoch = ZonedDateTime + .of(LocalDateTime.of(yearForCalendar, DateTimeUtils.monthFromDateValue(dateValue), + DateTimeUtils.dayFromDateValue(dateValue), hour, minute, second), zoneId) + .toOffsetDateTime().toEpochSecond(); + return epoch + (year - yearForCalendar) * SECONDS_PER_YEAR; + } + + @Override + public String getId() { + return zoneId.getId(); + } + + @Override + public String getShortId(long epochSeconds) { + DateTimeFormatter timeZoneFormatter = TIME_ZONE_FORMATTER; + if (timeZoneFormatter == null) { + TIME_ZONE_FORMATTER = timeZoneFormatter = DateTimeFormatter.ofPattern("z", Locale.ENGLISH); + } + return ZonedDateTime.ofInstant(Instant.ofEpochSecond(epochSeconds), zoneId).format(timeZoneFormatter); + } + + /** + * Returns a year within the range -999,999,999..999,999,999 for the + * given year. Too large and too small years are replaced with years + * within the range using the 400 years period of the Gregorian + * calendar. + * + * Because we need them only to calculate a time zone offset, it's safe + * to normalize them to such range. + * + * @param year + * the year + * @return the specified year or the replacement year within the range + */ + private static int yearForCalendar(int year) { + if (year > 999_999_999) { + year -= 400; + } else if (year < -999_999_999) { + year += 400; + } + return year; + } + + @Override + public String toString() { + return "TimeZoneProvider " + zoneId.getId(); + } + + } + +} diff --git a/h2/src/main/org/h2/util/Tool.java b/h2/src/main/org/h2/util/Tool.java new file mode 100644 index 0000000..1ad65d9 --- /dev/null +++ b/h2/src/main/org/h2/util/Tool.java @@ -0,0 +1,140 @@ +/* + * 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.util; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.sql.SQLException; +import java.util.Properties; +import org.h2.api.ErrorCode; +import org.h2.message.DbException; +import org.h2.store.FileLister; +import org.h2.store.fs.FileUtils; + +/** + * Command line tools implement the tool interface so that they can be used in + * the H2 Console. + */ +public abstract class Tool { + + /** + * The output stream where this tool writes to. + */ + protected PrintStream out = System.out; + + private Properties resources; + + /** + * Sets the standard output stream. + * + * @param out the new standard output stream + */ + public void setOut(PrintStream out) { + this.out = out; + } + + /** + * Run the tool with the given output stream and arguments. + * + * @param args the argument list + * @throws SQLException on failure + */ + public abstract void runTool(String... args) throws SQLException; + + /** + * Throw a SQLException saying this command line option is not supported. + * + * @param option the unsupported option + * @return this method never returns normally + * @throws SQLException on failure + */ + protected SQLException showUsageAndThrowUnsupportedOption(String option) + throws SQLException { + showUsage(); + throw throwUnsupportedOption(option); + } + + /** + * Throw a SQLException saying this command line option is not supported. + * + * @param option the unsupported option + * @return this method never returns normally + * @throws SQLException on failure + */ + protected SQLException throwUnsupportedOption(String option) + throws SQLException { + throw DbException.getJdbcSQLException( + ErrorCode.FEATURE_NOT_SUPPORTED_1, option); + } + + /** + * Print to the output stream that no database files have been found. + * + * @param dir the directory or null + * @param db the database name or null + */ + protected void printNoDatabaseFilesFound(String dir, String db) { + StringBuilder buff; + dir = FileLister.getDir(dir); + if (!FileUtils.isDirectory(dir)) { + buff = new StringBuilder("Directory not found: "); + buff.append(dir); + } else { + buff = new StringBuilder("No database files have been found"); + buff.append(" in directory ").append(dir); + if (db != null) { + buff.append(" for the database ").append(db); + } + } + out.println(buff.toString()); + } + + /** + * Print the usage of the tool. This method reads the description from the + * resource file. + */ + protected void showUsage() { + if (resources == null) { + resources = new Properties(); + String resourceName = "/org/h2/res/javadoc.properties"; + try { + byte[] buff = Utils.getResource(resourceName); + if (buff != null) { + resources.load(new ByteArrayInputStream(buff)); + } + } catch (IOException e) { + out.println("Cannot load " + resourceName); + } + } + String className = getClass().getName(); + out.println(resources.get(className)); + out.println("Usage: java "+getClass().getName() + " "); + out.println(resources.get(className + ".main")); + out.println("See also https://h2database.com/javadoc/" + + className.replace('.', '/') + ".html"); + } + + /** + * Check if the argument matches the option. + * If the argument starts with this option, but doesn't match, + * then an exception is thrown. + * + * @param arg the argument + * @param option the command line option + * @return true if it matches + */ + public static boolean isOption(String arg, String option) { + if (arg.equals(option)) { + return true; + } else if (arg.startsWith(option)) { + throw DbException.getUnsupportedException( + "expected: " + option + " got: " + arg); + } + return false; + } + +} diff --git a/h2/src/main/org/h2/util/Utils.java b/h2/src/main/org/h2/util/Utils.java new file mode 100644 index 0000000..4594146 --- /dev/null +++ b/h2/src/main/org/h2/util/Utils.java @@ -0,0 +1,775 @@ +/* + * 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.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; +import java.lang.management.OperatingSystemMXBean; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * This utility class contains miscellaneous functions. + */ +public class Utils { + + /** + * An 0-size byte array. + */ + public static final byte[] EMPTY_BYTES = {}; + + /** + * An 0-size int array. + */ + public static final int[] EMPTY_INT_ARRAY = {}; + + private static final HashMap RESOURCES = new HashMap<>(); + + private Utils() { + // utility class + } + + /** + * Calculate the index of the first occurrence of the pattern in the byte + * array, starting with the given index. This methods returns -1 if the + * pattern has not been found, and the start position if the pattern is + * empty. + * + * @param bytes the byte array + * @param pattern the pattern + * @param start the start index from where to search + * @return the index + */ + public static int indexOf(byte[] bytes, byte[] pattern, int start) { + if (pattern.length == 0) { + return start; + } + if (start > bytes.length) { + return -1; + } + int last = bytes.length - pattern.length + 1; + int patternLen = pattern.length; + next: + for (; start < last; start++) { + for (int i = 0; i < patternLen; i++) { + if (bytes[start + i] != pattern[i]) { + continue next; + } + } + return start; + } + return -1; + } + + /** + * Calculate the hash code of the given byte array. + * + * @param value the byte array + * @return the hash code + */ + public static int getByteArrayHash(byte[] value) { + int len = value.length; + int h = len; + if (len < 50) { + for (int i = 0; i < len; i++) { + h = 31 * h + value[i]; + } + } else { + int step = len / 16; + for (int i = 0; i < 4; i++) { + h = 31 * h + value[i]; + h = 31 * h + value[--len]; + } + for (int i = 4 + step; i < len; i += step) { + h = 31 * h + value[i]; + } + } + return h; + } + + /** + * Compare two byte arrays. This method will always loop over all bytes and + * doesn't use conditional operations in the loop to make sure an attacker + * can not use a timing attack when trying out passwords. + * + * @param test the first array + * @param good the second array + * @return true if both byte arrays contain the same bytes + */ + public static boolean compareSecure(byte[] test, byte[] good) { + if ((test == null) || (good == null)) { + return (test == null) && (good == null); + } + int len = test.length; + if (len != good.length) { + return false; + } + if (len == 0) { + return true; + } + // don't use conditional operations inside the loop + int bits = 0; + for (int i = 0; i < len; i++) { + // this will never reset any bits + bits |= test[i] ^ good[i]; + } + return bits == 0; + } + + /** + * Copy the contents of the source array to the target array. If the size if + * the target array is too small, a larger array is created. + * + * @param source the source array + * @param target the target array + * @return the target array or a new one if the target array was too small + */ + public static byte[] copy(byte[] source, byte[] target) { + int len = source.length; + if (len > target.length) { + target = new byte[len]; + } + System.arraycopy(source, 0, target, 0, len); + return target; + } + + /** + * Create an array of bytes with the given size. If this is not possible + * because not enough memory is available, an OutOfMemoryError with the + * requested size in the message is thrown. + *

+ * This method should be used if the size of the array is user defined, or + * stored in a file, so wrong size data can be distinguished from regular + * out-of-memory. + *

+ * + * @param len the number of bytes requested + * @return the byte array + * @throws OutOfMemoryError if the allocation was too large + */ + public static byte[] newBytes(int len) { + if (len == 0) { + return EMPTY_BYTES; + } + try { + return new byte[len]; + } catch (OutOfMemoryError e) { + Error e2 = new OutOfMemoryError("Requested memory: " + len); + e2.initCause(e); + throw e2; + } + } + + /** + * Creates a copy of array of bytes with the new size. If this is not possible + * because not enough memory is available, an OutOfMemoryError with the + * requested size in the message is thrown. + *

+ * This method should be used if the size of the array is user defined, or + * stored in a file, so wrong size data can be distinguished from regular + * out-of-memory. + *

+ * + * @param bytes source array + * @param len the number of bytes in the new array + * @return the byte array + * @throws OutOfMemoryError if the allocation was too large + * @see Arrays#copyOf(byte[], int) + */ + public static byte[] copyBytes(byte[] bytes, int len) { + if (len == 0) { + return EMPTY_BYTES; + } + try { + return Arrays.copyOf(bytes, len); + } catch (OutOfMemoryError e) { + Error e2 = new OutOfMemoryError("Requested memory: " + len); + e2.initCause(e); + throw e2; + } + } + + /** + * Create a new byte array and copy all the data. If the size of the byte + * array is zero, the same array is returned. + * + * @param b the byte array (may not be null) + * @return a new byte array + */ + public static byte[] cloneByteArray(byte[] b) { + if (b == null) { + return null; + } + int len = b.length; + if (len == 0) { + return EMPTY_BYTES; + } + return Arrays.copyOf(b, len); + } + + /** + * Get the used memory in KB. + * This method possibly calls System.gc(). + * + * @return the used memory + */ + public static long getMemoryUsed() { + collectGarbage(); + Runtime rt = Runtime.getRuntime(); + return rt.totalMemory() - rt.freeMemory() >> 10; + } + + /** + * Get the free memory in KB. + * This method possibly calls System.gc(). + * + * @return the free memory + */ + public static long getMemoryFree() { + collectGarbage(); + return Runtime.getRuntime().freeMemory() >> 10; + } + + /** + * Get the maximum memory in KB. + * + * @return the maximum memory + */ + public static long getMemoryMax() { + return Runtime.getRuntime().maxMemory() >> 10; + } + + public static long getGarbageCollectionTime() { + long totalGCTime = 0; + for (GarbageCollectorMXBean gcMXBean : ManagementFactory.getGarbageCollectorMXBeans()) { + long collectionTime = gcMXBean.getCollectionTime(); + if(collectionTime > 0) { + totalGCTime += collectionTime; + } + } + return totalGCTime; + } + + public static long getGarbageCollectionCount() { + long totalGCCount = 0; + int poolCount = 0; + for (GarbageCollectorMXBean gcMXBean : ManagementFactory.getGarbageCollectorMXBeans()) { + long collectionCount = gcMXBean.getCollectionTime(); + if(collectionCount > 0) { + totalGCCount += collectionCount; + poolCount += gcMXBean.getMemoryPoolNames().length; + } + } + poolCount = Math.max(poolCount, 1); + return (totalGCCount + (poolCount >> 1)) / poolCount; + } + + /** + * Run Java memory garbage collection. + */ + public static synchronized void collectGarbage() { + Runtime runtime = Runtime.getRuntime(); + long garbageCollectionCount = getGarbageCollectionCount(); + while (garbageCollectionCount == getGarbageCollectionCount()) { + runtime.gc(); + Thread.yield(); + } + } + + /** + * Create a new ArrayList with an initial capacity of 4. + * + * @param the type + * @return the object + */ + public static ArrayList newSmallArrayList() { + return new ArrayList<>(4); + } + + /** + * Find the top limit values using given comparator and place them as in a + * full array sort, in descending order. + * + * @param the type of elements + * @param array the array. + * @param fromInclusive the start index, inclusive + * @param toExclusive the end index, exclusive + * @param comp the comparator. + */ + public static void sortTopN(X[] array, int fromInclusive, int toExclusive, Comparator comp) { + int highInclusive = array.length - 1; + if (highInclusive > 0 && toExclusive > fromInclusive) { + partialQuickSort(array, 0, highInclusive, comp, fromInclusive, toExclusive - 1); + Arrays.sort(array, fromInclusive, toExclusive, comp); + } + } + + /** + * Partial quick sort. + * + *

+ * Works with elements from {@code low} to {@code high} indexes, inclusive. + *

+ *

+ * Moves smallest elements to {@code low..start-1} positions and largest + * elements to {@code end+1..high} positions. Middle elements are placed + * into {@code start..end} positions. All these regions aren't fully sorted. + *

+ * + * @param the type of elements + * @param array the array to sort + * @param low the lower index with data, inclusive + * @param high the higher index with data, inclusive, {@code high > low} + * @param comp the comparator + * @param start the start index of requested region, inclusive + * @param end the end index of requested region, inclusive, {@code end >= start} + */ + private static void partialQuickSort(X[] array, int low, int high, + Comparator comp, int start, int end) { + if (low >= start && high <= end) { + // Don't sort blocks entirely contained in the middle region + return; + } + int i = low, j = high; + // use a random pivot to protect against + // the worst case order + int p = low + MathUtils.randomInt(high - low); + X pivot = array[p]; + int m = (low + high) >>> 1; + X temp = array[m]; + array[m] = pivot; + array[p] = temp; + while (i <= j) { + while (comp.compare(array[i], pivot) < 0) { + i++; + } + while (comp.compare(array[j], pivot) > 0) { + j--; + } + if (i <= j) { + temp = array[i]; + array[i++] = array[j]; + array[j--] = temp; + } + } + if (low < j && /* Intersection with middle region */ start <= j) { + partialQuickSort(array, low, j, comp, start, end); + } + if (i < high && /* Intersection with middle region */ i <= end) { + partialQuickSort(array, i, high, comp, start, end); + } + } + + /** + * Get a resource from the resource map. + * + * @param name the name of the resource + * @return the resource data + * @throws IOException on failure + */ + public static byte[] getResource(String name) throws IOException { + byte[] data = RESOURCES.get(name); + if (data == null) { + data = loadResource(name); + if (data != null) { + RESOURCES.put(name, data); + } + } + return data; + } + + private static byte[] loadResource(String name) throws IOException { + InputStream in = Utils.class.getResourceAsStream("data.zip"); + if (in == null) { + in = Utils.class.getResourceAsStream(name); + if (in == null) { + return null; + } + return IOUtils.readBytesAndClose(in, 0); + } + + try (ZipInputStream zipIn = new ZipInputStream(in)) { + while (true) { + ZipEntry entry = zipIn.getNextEntry(); + if (entry == null) { + break; + } + String entryName = entry.getName(); + if (!entryName.startsWith("/")) { + entryName = "/" + entryName; + } + if (entryName.equals(name)) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + IOUtils.copy(zipIn, out); + zipIn.closeEntry(); + return out.toByteArray(); + } + zipIn.closeEntry(); + } + } catch (IOException e) { + // if this happens we have a real problem + e.printStackTrace(); + } + return null; + } + + /** + * Calls a static method via reflection. This will try to use the method + * where the most parameter classes match exactly (this algorithm is simpler + * than the one in the Java specification, but works well for most cases). + * + * @param classAndMethod a string with the entire class and method name, eg. + * "java.lang.System.gc" + * @param params the method parameters + * @return the return value from this call + * @throws Exception on failure + */ + public static Object callStaticMethod(String classAndMethod, + Object... params) throws Exception { + int lastDot = classAndMethod.lastIndexOf('.'); + String className = classAndMethod.substring(0, lastDot); + String methodName = classAndMethod.substring(lastDot + 1); + return callMethod(null, Class.forName(className), methodName, params); + } + + /** + * Calls an instance method via reflection. This will try to use the method + * where the most parameter classes match exactly (this algorithm is simpler + * than the one in the Java specification, but works well for most cases). + * + * @param instance the instance on which the call is done + * @param methodName a string with the method name + * @param params the method parameters + * @return the return value from this call + * @throws Exception on failure + */ + public static Object callMethod( + Object instance, + String methodName, + Object... params) throws Exception { + return callMethod(instance, instance.getClass(), methodName, params); + } + + private static Object callMethod( + Object instance, Class clazz, + String methodName, + Object... params) throws Exception { + Method best = null; + int bestMatch = 0; + boolean isStatic = instance == null; + for (Method m : clazz.getMethods()) { + if (Modifier.isStatic(m.getModifiers()) == isStatic && + m.getName().equals(methodName)) { + int p = match(m.getParameterTypes(), params); + if (p > bestMatch) { + bestMatch = p; + best = m; + } + } + } + if (best == null) { + throw new NoSuchMethodException(methodName); + } + return best.invoke(instance, params); + } + + /** + * Creates a new instance. This will try to use the constructor where the + * most parameter classes match exactly (this algorithm is simpler than the + * one in the Java specification, but works well for most cases). + * + * @param className a string with the entire class, eg. "java.lang.Integer" + * @param params the constructor parameters + * @return the newly created object + * @throws Exception on failure + */ + public static Object newInstance(String className, Object... params) + throws Exception { + Constructor best = null; + int bestMatch = 0; + for (Constructor c : Class.forName(className).getConstructors()) { + int p = match(c.getParameterTypes(), params); + if (p > bestMatch) { + bestMatch = p; + best = c; + } + } + if (best == null) { + throw new NoSuchMethodException(className); + } + return best.newInstance(params); + } + + private static int match(Class[] params, Object[] values) { + int len = params.length; + if (len == values.length) { + int points = 1; + for (int i = 0; i < len; i++) { + Class pc = getNonPrimitiveClass(params[i]); + Object v = values[i]; + Class vc = v == null ? null : v.getClass(); + if (pc == vc) { + points++; + } else if (vc == null) { + // can't verify + } else if (!pc.isAssignableFrom(vc)) { + return 0; + } + } + return points; + } + return 0; + } + + /** + * Convert primitive class names to java.lang.* class names. + * + * @param clazz the class (for example: int) + * @return the non-primitive class (for example: java.lang.Integer) + */ + public static Class getNonPrimitiveClass(Class clazz) { + if (!clazz.isPrimitive()) { + return clazz; + } else if (clazz == boolean.class) { + return Boolean.class; + } else if (clazz == byte.class) { + return Byte.class; + } else if (clazz == char.class) { + return Character.class; + } else if (clazz == double.class) { + return Double.class; + } else if (clazz == float.class) { + return Float.class; + } else if (clazz == int.class) { + return Integer.class; + } else if (clazz == long.class) { + return Long.class; + } else if (clazz == short.class) { + return Short.class; + } else if (clazz == void.class) { + return Void.class; + } + return clazz; + } + + /** + * Parses the specified string to boolean value. + * + * @param value + * string to parse + * @param defaultValue + * value to return if value is null or on parsing error + * @param throwException + * throw exception on parsing error or return default value instead + * @return parsed or default value + * @throws IllegalArgumentException + * on parsing error if {@code throwException} is true + */ + public static boolean parseBoolean(String value, boolean defaultValue, boolean throwException) { + if (value == null) { + return defaultValue; + } + switch (value.length()) { + case 1: + if (value.equals("1") || value.equalsIgnoreCase("t") || value.equalsIgnoreCase("y")) { + return true; + } + if (value.equals("0") || value.equalsIgnoreCase("f") || value.equalsIgnoreCase("n")) { + return false; + } + break; + case 2: + if (value.equalsIgnoreCase("no")) { + return false; + } + break; + case 3: + if (value.equalsIgnoreCase("yes")) { + return true; + } + break; + case 4: + if (value.equalsIgnoreCase("true")) { + return true; + } + break; + case 5: + if (value.equalsIgnoreCase("false")) { + return false; + } + } + if (throwException) { + throw new IllegalArgumentException(value); + } + return defaultValue; + } + + /** + * Get the system property. If the system property is not set, or if a + * security exception occurs, the default value is returned. + * + * @param key the key + * @param defaultValue the default value + * @return the value + */ + public static String getProperty(String key, String defaultValue) { + try { + return System.getProperty(key, defaultValue); + } catch (SecurityException se) { + return defaultValue; + } + } + + /** + * Get the system property. If the system property is not set, or if a + * security exception occurs, the default value is returned. + * + * @param key the key + * @param defaultValue the default value + * @return the value + */ + public static int getProperty(String key, int defaultValue) { + String s = getProperty(key, null); + if (s != null) { + try { + return Integer.decode(s); + } catch (NumberFormatException e) { + // ignore + } + } + return defaultValue; + } + + /** + * Get the system property. If the system property is not set, or if a + * security exception occurs, the default value is returned. + * + * @param key the key + * @param defaultValue the default value + * @return the value + */ + public static boolean getProperty(String key, boolean defaultValue) { + return parseBoolean(getProperty(key, null), defaultValue, false); + } + + /** + * Scale the value with the available memory. If 1 GB of RAM is available, + * the value is returned, if 2 GB are available, then twice the value, and + * so on. + * + * @param value the value to scale + * @return the scaled value + */ + public static int scaleForAvailableMemory(int value) { + long maxMemory = Runtime.getRuntime().maxMemory(); + if (maxMemory != Long.MAX_VALUE) { + // we are limited by an -XmX parameter + return (int) (value * maxMemory / (1024 * 1024 * 1024)); + } + try { + OperatingSystemMXBean mxBean = ManagementFactory + .getOperatingSystemMXBean(); + // this method is only available on the class + // com.sun.management.OperatingSystemMXBean, which mxBean + // is an instance of under the Oracle JDK, but it is not present on + // Android and other JDK's + Method method = Class.forName( + "com.sun.management.OperatingSystemMXBean"). + getMethod("getTotalPhysicalMemorySize"); + long physicalMemorySize = ((Number) method.invoke(mxBean)).longValue(); + return (int) (value * physicalMemorySize / (1024 * 1024 * 1024)); + } catch (Exception e) { + // ignore + } catch (Error error) { + // ignore + } + return value; + } + + /** + * Returns the current value of the high-resolution time source. + * + * @return time in nanoseconds, never equal to 0 + * @see System#nanoTime() + */ + public static long currentNanoTime() { + long time = System.nanoTime(); + if (time == 0L) { + time = 1L; + } + return time; + } + + /** + * Returns the current value of the high-resolution time source plus the + * specified offset. + * + * @param ms + * additional offset in milliseconds + * @return time in nanoseconds, never equal to 0 + * @see System#nanoTime() + */ + public static long currentNanoTimePlusMillis(int ms) { + return nanoTimePlusMillis(System.nanoTime(), ms); + } + + /** + * Returns the current value of the high-resolution time source plus the + * specified offset. + * + * @param nanoTime + * time in nanoseconds + * @param ms + * additional offset in milliseconds + * @return time in nanoseconds, never equal to 0 + * @see System#nanoTime() + */ + public static long nanoTimePlusMillis(long nanoTime, int ms) { + long time = nanoTime + ms * 1_000_000L; + if (time == 0L) { + time = 1L; + } + return time; + } + + /** + * 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 + * in order to provide a class from another bundle ClassLoader. + */ + public interface ClassFactory { + + /** + * Check whether the factory can return the named class. + * + * @param name the binary name of the class + * @return true if this factory can return a valid class for the + * provided class name + */ + boolean match(String name); + + /** + * Load the class. + * + * @param name the binary name of the class + * @return the class object + * @throws ClassNotFoundException If the class is not handle by this + * factory + */ + Class loadClass(String name) + throws ClassNotFoundException; + } +} diff --git a/h2/src/main/org/h2/util/Utils10.java b/h2/src/main/org/h2/util/Utils10.java new file mode 100644 index 0000000..a77dbac --- /dev/null +++ b/h2/src/main/org/h2/util/Utils10.java @@ -0,0 +1,78 @@ +/* + * 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.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.Socket; +import java.nio.charset.Charset; + +/** + * Utilities with specialized implementations for Java 10 and later versions. + * + * This class contains basic implementations for Java 8 and 9 and it is + * overridden in multi-release JARs. + */ +public final class Utils10 { + + /* + * Signatures of methods should match with + * h2/src/java10/src/org/h2/util/Utils10.java and precompiled + * h2/src/java10/precompiled/org/h2/util/Utils10.class. + */ + + /** + * Converts the buffer's contents into a string by decoding the bytes using + * the specified {@link java.nio.charset.Charset charset}. + * + * @param baos + * the buffer to decode + * @param charset + * the charset to use + * @return the decoded string + */ + public static String byteArrayOutputStreamToString(ByteArrayOutputStream baos, Charset charset) { + try { + return baos.toString(charset.name()); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + /** + * Returns the value of TCP_QUICKACK option. + * + * @param socket + * the socket + * @return the current value of TCP_QUICKACK option + * @throws IOException + * on I/O exception + * @throws UnsupportedOperationException + * if TCP_QUICKACK is not supported + */ + public static boolean getTcpQuickack(Socket socket) throws IOException { + throw new UnsupportedOperationException(); + } + + /** + * Sets the value of TCP_QUICKACK option. + * + * @param socket + * the socket + * @param value + * the value to set + * @return whether operation was successful + */ + public static boolean setTcpQuickack(Socket socket, boolean value) { + // The default implementation does nothing + return false; + } + + private Utils10() { + } + +} diff --git a/h2/src/main/org/h2/util/geometry/EWKBUtils.java b/h2/src/main/org/h2/util/geometry/EWKBUtils.java new file mode 100644 index 0000000..8a598dc --- /dev/null +++ b/h2/src/main/org/h2/util/geometry/EWKBUtils.java @@ -0,0 +1,563 @@ +/* + * 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.util.geometry; + +import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYM; +import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYZ; +import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYZM; +import static org.h2.util.geometry.GeometryUtils.GEOMETRY_COLLECTION; +import static org.h2.util.geometry.GeometryUtils.LINE_STRING; +import static org.h2.util.geometry.GeometryUtils.MAX_X; +import static org.h2.util.geometry.GeometryUtils.MAX_Y; +import static org.h2.util.geometry.GeometryUtils.MIN_X; +import static org.h2.util.geometry.GeometryUtils.MIN_Y; +import static org.h2.util.geometry.GeometryUtils.MULTI_LINE_STRING; +import static org.h2.util.geometry.GeometryUtils.MULTI_POINT; +import static org.h2.util.geometry.GeometryUtils.MULTI_POLYGON; +import static org.h2.util.geometry.GeometryUtils.POINT; +import static org.h2.util.geometry.GeometryUtils.POLYGON; +import static org.h2.util.geometry.GeometryUtils.checkFinite; +import static org.h2.util.geometry.GeometryUtils.toCanonicalDouble; + +import java.io.ByteArrayOutputStream; + +import org.h2.util.Bits; +import org.h2.util.StringUtils; +import org.h2.util.geometry.GeometryUtils.Target; + +/** + * EWKB format support for GEOMETRY data type. + * + *

+ * This class provides limited support of EWKB. EWKB is based on Well-known + * Binary Representation (WKB) from OGC 06-103r4 and includes additional PostGIS + * extensions. This class can read dimension system marks in both OGC WKB and + * EWKB formats, but always writes them in EWKB format. SRID support from EWKB + * is implemented. As an addition POINT EMPTY is stored with NaN values as + * specified in OGC 12-128r15. + *

+ */ +public final class EWKBUtils { + + /** + * Converter output target that writes a EWKB. + */ + public static final class EWKBTarget extends Target { + + private final ByteArrayOutputStream output; + + private final int dimensionSystem; + + private final byte[] buf = new byte[8]; + + private int type; + + private int srid; + + /** + * Creates a new EWKB output target. + * + * @param output + * output stream + * @param dimensionSystem + * dimension system to use + */ + public EWKBTarget(ByteArrayOutputStream output, int dimensionSystem) { + this.output = output; + this.dimensionSystem = dimensionSystem; + } + + @Override + protected void init(int srid) { + this.srid = srid; + } + + @Override + protected void startPoint() { + writeHeader(POINT); + } + + @Override + protected void startLineString(int numPoints) { + writeHeader(LINE_STRING); + writeInt(numPoints); + } + + @Override + protected void startPolygon(int numInner, int numPoints) { + writeHeader(POLYGON); + if (numInner == 0 && numPoints == 0) { + /* + * Representation of POLYGON EMPTY is not defined is + * specification. We store it as a polygon with 0 rings, as + * PostGIS does. + */ + writeInt(0); + } else { + writeInt(numInner + 1); + writeInt(numPoints); + } + } + + @Override + protected void startPolygonInner(int numInner) { + writeInt(numInner); + } + + @Override + protected void startCollection(int type, int numItems) { + writeHeader(type); + writeInt(numItems); + } + + private void writeHeader(int type) { + this.type = type; + switch (dimensionSystem) { + case DIMENSION_SYSTEM_XYZ: + type |= EWKB_Z; + break; + case DIMENSION_SYSTEM_XYZM: + type |= EWKB_Z; + //$FALL-THROUGH$ + case DIMENSION_SYSTEM_XYM: + type |= EWKB_M; + } + if (srid != 0) { + type |= EWKB_SRID; + } + output.write(0); + writeInt(type); + if (srid != 0) { + writeInt(srid); + // Never write SRID in inner objects + srid = 0; + } + } + + @Override + protected Target startCollectionItem(int index, int total) { + return this; + } + + @Override + protected void addCoordinate(double x, double y, double z, double m, int index, int total) { + boolean check = type != POINT || !Double.isNaN(x) || !Double.isNaN(y) || !Double.isNaN(z) + || !Double.isNaN(m); + if (check) { + checkFinite(x); + checkFinite(y); + } + writeDouble(x); + writeDouble(y); + if ((dimensionSystem & DIMENSION_SYSTEM_XYZ) != 0) { + writeDouble(check ? checkFinite(z) : z); + } else if (check && !Double.isNaN(z)) { + throw new IllegalArgumentException(); + } + if ((dimensionSystem & DIMENSION_SYSTEM_XYM) != 0) { + writeDouble(check ? checkFinite(m) : m); + } else if (check && !Double.isNaN(m)) { + throw new IllegalArgumentException(); + } + } + + private void writeInt(int v) { + Bits.writeInt(buf, 0, v); + output.write(buf, 0, 4); + } + + private void writeDouble(double v) { + v = toCanonicalDouble(v); + Bits.writeDouble(buf, 0, v); + output.write(buf, 0, 8); + } + + } + + /** + * Helper source object for EWKB reading. + */ + private static final class EWKBSource { + private final byte[] ewkb; + + private int offset; + + /** + * Whether current byte order is big-endian. + */ + boolean bigEndian; + + /** + * Creates new instance of EWKB source. + * + * @param ewkb + * EWKB + */ + EWKBSource(byte[] ewkb) { + this.ewkb = ewkb; + } + + /** + * Reads one byte. + * + * @return next byte + */ + byte readByte() { + return ewkb[offset++]; + } + + /** + * Reads a 32-bit integer using current byte order. + * + * @return next 32-bit integer + */ + int readInt() { + int result = bigEndian ? Bits.readInt(ewkb, offset) : Bits.readIntLE(ewkb, offset); + offset += 4; + return result; + } + + /** + * Reads a 64-bit floating point using current byte order. + * + * @return next 64-bit floating point + */ + double readCoordinate() { + double v = bigEndian ? Bits.readDouble(ewkb, offset) : Bits.readDoubleLE(ewkb, offset); + offset += 8; + return toCanonicalDouble(v); + } + + @Override + public String toString() { + String s = StringUtils.convertBytesToHex(ewkb); + int idx = offset * 2; + return new StringBuilder(s.length() + 3).append(s, 0, idx).append("<*>").append(s, idx, s.length()) + .toString(); + } + + } + + /** + * Geometry type mask that indicates presence of dimension Z. + */ + public static final int EWKB_Z = 0x8000_0000; + + /** + * Geometry type mask that indicates presence of dimension M. + */ + public static final int EWKB_M = 0x4000_0000; + + /** + * Geometry type mask that indicates presence of SRID. + */ + public static final int EWKB_SRID = 0x2000_0000; + + /** + * Converts any supported EWKB to EWKB representation that is used by this + * class. Reduces dimension system to minimal possible and uses EWKB flags + * for dimension system indication. May also perform other changes. + * + * @param ewkb + * source EWKB + * @return canonical EWKB, may be the same as the source + */ + public static byte[] ewkb2ewkb(byte[] ewkb) { + return ewkb2ewkb(ewkb, getDimensionSystem(ewkb)); + } + + /** + * Converts any supported EWKB to EWKB representation that is used by this + * class. Reduces dimension system to minimal possible and uses EWKB flags + * for dimension system indication. May also perform other changes. + * + * @param ewkb + * source EWKB + * @param dimensionSystem + * dimension system + * @return canonical EWKB, may be the same as the source + */ + public static byte[] ewkb2ewkb(byte[] ewkb, int dimensionSystem) { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + EWKBTarget target = new EWKBTarget(output, dimensionSystem); + parseEWKB(ewkb, target); + return output.toByteArray(); + } + + /** + * Parses a EWKB. + * + * @param ewkb + * EWKB representation + * @param target + * output target + */ + public static void parseEWKB(byte[] ewkb, Target target) { + try { + parseEWKB(new EWKBSource(ewkb), target, 0); + } catch (ArrayIndexOutOfBoundsException e) { + throw new IllegalArgumentException(); + } + } + + /** + * Converts geometry type with flags to a dimension system. + * + * @param type + * geometry type with flags + * @return dimension system + */ + public static int type2dimensionSystem(int type) { + // PostGIS extensions + boolean useZ = (type & EWKB_Z) != 0; + boolean useM = (type & EWKB_M) != 0; + // OGC 06-103r4 + type &= 0xffff; + switch (type / 1_000) { + case DIMENSION_SYSTEM_XYZ: + useZ = true; + break; + case DIMENSION_SYSTEM_XYZM: + useZ = true; + //$FALL-THROUGH$ + case DIMENSION_SYSTEM_XYM: + useM = true; + } + return (useZ ? DIMENSION_SYSTEM_XYZ : 0) | (useM ? DIMENSION_SYSTEM_XYM : 0); + } + + /** + * Parses a EWKB. + * + * @param source + * EWKB source + * @param target + * output target + * @param parentType + * type of parent geometry collection, or 0 for the root geometry + */ + private static void parseEWKB(EWKBSource source, Target target, int parentType) { + // Read byte order of a next geometry + switch (source.readByte()) { + case 0: + source.bigEndian = true; + break; + case 1: + source.bigEndian = false; + break; + default: + throw new IllegalArgumentException(); + } + // Type contains type of a geometry and additional flags + int type = source.readInt(); + // PostGIS extensions + boolean useZ = (type & EWKB_Z) != 0; + boolean useM = (type & EWKB_M) != 0; + int srid = (type & EWKB_SRID) != 0 ? source.readInt() : 0; + // Use only top-level SRID + if (parentType == 0) { + target.init(srid); + } + // OGC 06-103r4 + type &= 0xffff; + switch (type / 1_000) { + case DIMENSION_SYSTEM_XYZ: + useZ = true; + break; + case DIMENSION_SYSTEM_XYZM: + useZ = true; + //$FALL-THROUGH$ + case DIMENSION_SYSTEM_XYM: + useM = true; + } + target.dimensionSystem((useZ ? DIMENSION_SYSTEM_XYZ : 0) | (useM ? DIMENSION_SYSTEM_XYM : 0)); + type %= 1_000; + switch (type) { + case POINT: + if (parentType != 0 && parentType != MULTI_POINT && parentType != GEOMETRY_COLLECTION) { + throw new IllegalArgumentException(); + } + target.startPoint(); + addCoordinate(source, target, useZ, useM, 0, 1); + break; + case LINE_STRING: { + if (parentType != 0 && parentType != MULTI_LINE_STRING && parentType != GEOMETRY_COLLECTION) { + throw new IllegalArgumentException(); + } + int numPoints = source.readInt(); + if (numPoints < 0 || numPoints == 1) { + throw new IllegalArgumentException(); + } + target.startLineString(numPoints); + for (int i = 0; i < numPoints; i++) { + addCoordinate(source, target, useZ, useM, i, numPoints); + } + break; + } + case POLYGON: { + if (parentType != 0 && parentType != MULTI_POLYGON && parentType != GEOMETRY_COLLECTION) { + throw new IllegalArgumentException(); + } + int numRings = source.readInt(); + if (numRings == 0) { + target.startPolygon(0, 0); + break; + } else if (numRings < 0) { + throw new IllegalArgumentException(); + } + numRings--; + int size = source.readInt(); + // Size may be 0 (EMPTY) or 4+ + if (size < 0 || size >= 1 && size <= 3) { + throw new IllegalArgumentException(); + } + if (size == 0 && numRings > 0) { + throw new IllegalArgumentException(); + } + target.startPolygon(numRings, size); + if (size > 0) { + addRing(source, target, useZ, useM, size); + for (int i = 0; i < numRings; i++) { + size = source.readInt(); + // Size may be 0 (EMPTY) or 4+ + if (size < 0 || size >= 1 && size <= 3) { + throw new IllegalArgumentException(); + } + target.startPolygonInner(size); + addRing(source, target, useZ, useM, size); + } + target.endNonEmptyPolygon(); + } + break; + } + case MULTI_POINT: + case MULTI_LINE_STRING: + case MULTI_POLYGON: + case GEOMETRY_COLLECTION: { + if (parentType != 0 && parentType != GEOMETRY_COLLECTION) { + throw new IllegalArgumentException(); + } + int numItems = source.readInt(); + if (numItems < 0) { + throw new IllegalArgumentException(); + } + target.startCollection(type, numItems); + for (int i = 0; i < numItems; i++) { + Target innerTarget = target.startCollectionItem(i, numItems); + parseEWKB(source, innerTarget, type); + target.endCollectionItem(innerTarget, type, i, numItems); + } + break; + } + default: + throw new IllegalArgumentException(); + } + target.endObject(type); + } + + private static void addRing(EWKBSource source, Target target, boolean useZ, boolean useM, int size) { + // 0 or 4+ are valid + if (size >= 4) { + double startX = source.readCoordinate(), startY = source.readCoordinate(); + target.addCoordinate(startX, startY, // + useZ ? source.readCoordinate() : Double.NaN, useM ? source.readCoordinate() : Double.NaN, // + 0, size); + for (int i = 1; i < size - 1; i++) { + addCoordinate(source, target, useZ, useM, i, size); + } + double endX = source.readCoordinate(), endY = source.readCoordinate(); + /* + * TODO OGC 06-103r4 determines points as equal if they have the + * same X and Y coordinates. Should we check Z and M here too? + */ + if (startX != endX || startY != endY) { + throw new IllegalArgumentException(); + } + target.addCoordinate(endX, endY, // + useZ ? source.readCoordinate() : Double.NaN, useM ? source.readCoordinate() : Double.NaN, // + size - 1, size); + } + } + + private static void addCoordinate(EWKBSource source, Target target, boolean useZ, boolean useM, int index, + int total) { + target.addCoordinate(source.readCoordinate(), source.readCoordinate(), + useZ ? source.readCoordinate() : Double.NaN, useM ? source.readCoordinate() : Double.NaN, // + index, total); + } + + /** + * Reads the dimension system from EWKB. + * + * @param ewkb + * EWKB + * @return the dimension system + */ + public static int getDimensionSystem(byte[] ewkb) { + EWKBSource source = new EWKBSource(ewkb); + // Read byte order of a next geometry + switch (source.readByte()) { + case 0: + source.bigEndian = true; + break; + case 1: + source.bigEndian = false; + break; + default: + throw new IllegalArgumentException(); + } + return type2dimensionSystem(source.readInt()); + } + + /** + * Converts an envelope to a WKB. + * + * @param envelope + * envelope, or null + * @return WKB, or null + */ + public static byte[] envelope2wkb(double[] envelope) { + if (envelope == null) { + return null; + } + byte[] result; + double minX = envelope[MIN_X], maxX = envelope[MAX_X], minY = envelope[MIN_Y], maxY = envelope[MAX_Y]; + if (minX == maxX && minY == maxY) { + result = new byte[21]; + result[4] = POINT; + Bits.writeDouble(result, 5, minX); + Bits.writeDouble(result, 13, minY); + } else if (minX == maxX || minY == maxY) { + result = new byte[41]; + result[4] = LINE_STRING; + result[8] = 2; + Bits.writeDouble(result, 9, minX); + Bits.writeDouble(result, 17, minY); + Bits.writeDouble(result, 25, maxX); + Bits.writeDouble(result, 33, maxY); + } else { + result = new byte[93]; + result[4] = POLYGON; + result[8] = 1; + result[12] = 5; + Bits.writeDouble(result, 13, minX); + Bits.writeDouble(result, 21, minY); + Bits.writeDouble(result, 29, minX); + Bits.writeDouble(result, 37, maxY); + Bits.writeDouble(result, 45, maxX); + Bits.writeDouble(result, 53, maxY); + Bits.writeDouble(result, 61, maxX); + Bits.writeDouble(result, 69, minY); + Bits.writeDouble(result, 77, minX); + Bits.writeDouble(result, 85, minY); + } + return result; + } + + private EWKBUtils() { + } + +} diff --git a/h2/src/main/org/h2/util/geometry/EWKTUtils.java b/h2/src/main/org/h2/util/geometry/EWKTUtils.java new file mode 100644 index 0000000..ccf245d --- /dev/null +++ b/h2/src/main/org/h2/util/geometry/EWKTUtils.java @@ -0,0 +1,888 @@ +/* + * 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.util.geometry; + +import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XY; +import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYM; +import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYZ; +import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYZM; +import static org.h2.util.geometry.GeometryUtils.GEOMETRY_COLLECTION; +import static org.h2.util.geometry.GeometryUtils.LINE_STRING; +import static org.h2.util.geometry.GeometryUtils.M; +import static org.h2.util.geometry.GeometryUtils.MULTI_LINE_STRING; +import static org.h2.util.geometry.GeometryUtils.MULTI_POINT; +import static org.h2.util.geometry.GeometryUtils.MULTI_POLYGON; +import static org.h2.util.geometry.GeometryUtils.POINT; +import static org.h2.util.geometry.GeometryUtils.POLYGON; +import static org.h2.util.geometry.GeometryUtils.X; +import static org.h2.util.geometry.GeometryUtils.Y; +import static org.h2.util.geometry.GeometryUtils.Z; + +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; + +import org.h2.util.StringUtils; +import org.h2.util.geometry.EWKBUtils.EWKBTarget; +import org.h2.util.geometry.GeometryUtils.Target; + +/** + * EWKT format support for GEOMETRY data type. + * + *

+ * This class provides limited support of EWKT. EWKT is based on Well-known Text + * Representation (WKT) from OGC 06-103r4 and includes additional PostGIS + * extensions. SRID support from EWKT is implemented. + *

+ */ +public final class EWKTUtils { + + /** + * 0-based type names of geometries, subtract 1 from type code to get index + * in this array. + */ + static final String[] TYPES = { // + "POINT", // + "LINESTRING", // + "POLYGON", // + "MULTIPOINT", // + "MULTILINESTRING", // + "MULTIPOLYGON", // + "GEOMETRYCOLLECTION", // + }; + + /** + * Names of dimension systems. + */ + private static final String[] DIMENSION_SYSTEMS = { // + "XY", // + "Z", // + "M", // + "ZM", // + }; + + /** + * Converter output target that writes a EWKT. + */ + public static final class EWKTTarget extends Target { + + private final StringBuilder output; + + private final int dimensionSystem; + + private int type; + + private boolean inMulti; + + /** + * Creates a new EWKT output target. + * + * @param output + * output stream + * @param dimensionSystem + * dimension system to use + */ + public EWKTTarget(StringBuilder output, int dimensionSystem) { + this.output = output; + this.dimensionSystem = dimensionSystem; + } + + @Override + protected void init(int srid) { + if (srid != 0) { + output.append("SRID=").append(srid).append(';'); + } + } + + @Override + protected void startPoint() { + writeHeader(POINT); + } + + @Override + protected void startLineString(int numPoints) { + writeHeader(LINE_STRING); + if (numPoints == 0) { + output.append("EMPTY"); + } + } + + @Override + protected void startPolygon(int numInner, int numPoints) { + writeHeader(POLYGON); + if (numPoints == 0) { + output.append("EMPTY"); + } else { + output.append('('); + } + } + + @Override + protected void startPolygonInner(int numInner) { + output.append(numInner > 0 ? ", " : ", EMPTY"); + } + + @Override + protected void endNonEmptyPolygon() { + output.append(')'); + } + + @Override + protected void startCollection(int type, int numItems) { + writeHeader(type); + if (numItems == 0) { + output.append("EMPTY"); + } + if (type != GEOMETRY_COLLECTION) { + inMulti = true; + } + } + + private void writeHeader(int type) { + this.type = type; + if (inMulti) { + return; + } + output.append(TYPES[type - 1]); + switch (dimensionSystem) { + case DIMENSION_SYSTEM_XYZ: + output.append(" Z"); + break; + case DIMENSION_SYSTEM_XYM: + output.append(" M"); + break; + case DIMENSION_SYSTEM_XYZM: + output.append(" ZM"); + } + output.append(' '); + } + + @Override + protected Target startCollectionItem(int index, int total) { + if (index == 0) { + output.append('('); + } else { + output.append(", "); + } + return this; + } + + @Override + protected void endCollectionItem(Target target, int type, int index, int total) { + if (index + 1 == total) { + output.append(')'); + } + } + + @Override + protected void endObject(int type) { + switch (type) { + case MULTI_POINT: + case MULTI_LINE_STRING: + case MULTI_POLYGON: + inMulti = false; + } + } + + @Override + protected void addCoordinate(double x, double y, double z, double m, int index, int total) { + if (type == POINT && Double.isNaN(x) && Double.isNaN(y) && Double.isNaN(z) && Double.isNaN(m)) { + output.append("EMPTY"); + return; + } + if (index == 0) { + output.append('('); + } else { + output.append(", "); + } + writeDouble(x); + output.append(' '); + writeDouble(y); + if ((dimensionSystem & DIMENSION_SYSTEM_XYZ) != 0) { + output.append(' '); + writeDouble(z); + } + if ((dimensionSystem & DIMENSION_SYSTEM_XYM) != 0) { + output.append(' '); + writeDouble(m); + } + if (index + 1 == total) { + output.append(')'); + } + } + + private void writeDouble(double v) { + String s = Double.toString(GeometryUtils.checkFinite(v)); + if (s.endsWith(".0")) { + output.append(s, 0, s.length() - 2); + } else { + int idx = s.indexOf(".0E"); + if (idx < 0) { + output.append(s); + } else { + output.append(s, 0, idx).append(s, idx + 2, s.length()); + } + } + } + + } + + /** + * Helper source object for EWKT reading. + */ + private static final class EWKTSource { + private final String ewkt; + + private int offset; + + EWKTSource(String ewkt) { + this.ewkt = ewkt; + } + + int readSRID() { + skipWS(); + int srid; + if (ewkt.regionMatches(true, offset, "SRID=", 0, 5)) { + offset += 5; + int idx = ewkt.indexOf(';', 5); + if (idx < 0) { + throw new IllegalArgumentException(); + } + int end = idx; + while (ewkt.charAt(end - 1) <= ' ') { + end--; + } + srid = Integer.parseInt(StringUtils.trimSubstring(ewkt, offset, end)); + offset = idx + 1; + } else { + srid = 0; + } + return srid; + } + + void read(char symbol) { + skipWS(); + int len = ewkt.length(); + if (offset >= len) { + throw new IllegalArgumentException(); + } + if (ewkt.charAt(offset) != symbol) { + throw new IllegalArgumentException(); + } + offset++; + } + + int readType() { + skipWS(); + int len = ewkt.length(); + if (offset >= len) { + throw new IllegalArgumentException(); + } + int result = 0; + char ch = ewkt.charAt(offset); + switch (ch) { + case 'P': + case 'p': + result = match("POINT", POINT); + if (result == 0) { + result = match("POLYGON", POLYGON); + } + break; + case 'L': + case 'l': + result = match("LINESTRING", LINE_STRING); + break; + case 'M': + case 'm': + if (match("MULTI", 1) != 0) { + result = match("POINT", MULTI_POINT); + if (result == 0) { + result = match("POLYGON", MULTI_POLYGON); + if (result == 0) { + result = match("LINESTRING", MULTI_LINE_STRING); + } + } + } + break; + case 'G': + case 'g': + result = match("GEOMETRYCOLLECTION", GEOMETRY_COLLECTION); + break; + } + if (result == 0) { + throw new IllegalArgumentException(); + } + return result; + } + + int readDimensionSystem() { + int o = offset; + skipWS(); + int len = ewkt.length(); + if (offset >= len) { + throw new IllegalArgumentException(); + } + int result; + char ch = ewkt.charAt(offset); + switch (ch) { + case 'M': + case 'm': + result = DIMENSION_SYSTEM_XYM; + offset++; + break; + case 'Z': + case 'z': + offset++; + if (offset >= len) { + result = DIMENSION_SYSTEM_XYZ; + } else { + ch = ewkt.charAt(offset); + if (ch == 'M' || ch == 'm') { + offset++; + result = DIMENSION_SYSTEM_XYZM; + } else { + result = DIMENSION_SYSTEM_XYZ; + } + } + break; + default: + result = DIMENSION_SYSTEM_XY; + if (o != offset) { + // Token is already terminated by a whitespace + return result; + } + } + checkStringEnd(len); + return result; + } + + boolean readEmpty() { + skipWS(); + int len = ewkt.length(); + if (offset >= len) { + throw new IllegalArgumentException(); + } + if (ewkt.charAt(offset) == '(') { + offset++; + return false; + } + if (match("EMPTY", 1) != 0) { + checkStringEnd(len); + return true; + } + throw new IllegalArgumentException(); + } + + private int match(String token, int code) { + int l = token.length(); + if (offset <= ewkt.length() - l && ewkt.regionMatches(true, offset, token, 0, l)) { + offset += l; + } else { + code = 0; + } + return code; + } + + private void checkStringEnd(int len) { + if (offset < len) { + char ch = ewkt.charAt(offset); + if (ch > ' ' && ch != '(' && ch != ')' && ch != ',') { + throw new IllegalArgumentException(); + } + } + } + + public boolean hasCoordinate() { + skipWS(); + if (offset >= ewkt.length()) { + return false; + } + return isNumberStart(ewkt.charAt(offset)); + } + + public double readCoordinate() { + skipWS(); + int len = ewkt.length(); + if (offset >= len) { + throw new IllegalArgumentException(); + } + char ch = ewkt.charAt(offset); + if (!isNumberStart(ch)) { + throw new IllegalArgumentException(); + } + int start = offset++; + while (offset < len && isNumberPart(ch = ewkt.charAt(offset))) { + offset++; + } + if (offset < len) { + if (ch > ' ' && ch != ')' && ch != ',') { + throw new IllegalArgumentException(); + } + } + Double d = Double.parseDouble(ewkt.substring(start, offset)); + return d == 0 ? 0 : d; + } + + private static boolean isNumberStart(char ch) { + if (ch >= '0' && ch <= '9') { + return true; + } + switch (ch) { + case '+': + case '-': + case '.': + return true; + default: + return false; + } + } + + private static boolean isNumberPart(char ch) { + if (ch >= '0' && ch <= '9') { + return true; + } + switch (ch) { + case '+': + case '-': + case '.': + case 'E': + case 'e': + return true; + default: + return false; + } + } + + public boolean hasMoreCoordinates() { + skipWS(); + if (offset >= ewkt.length()) { + throw new IllegalArgumentException(); + } + switch (ewkt.charAt(offset)) { + case ',': + offset++; + return true; + case ')': + offset++; + return false; + default: + throw new IllegalArgumentException(); + } + } + + boolean hasData() { + skipWS(); + return offset < ewkt.length(); + } + + int getItemCount() { + int result = 1; + int offset = this.offset, level = 0, len = ewkt.length(); + while (offset < len) { + switch (ewkt.charAt(offset++)) { + case ',': + if (level == 0) { + result++; + } + break; + case '(': + level++; + break; + case ')': + if (--level < 0) { + return result; + } + } + } + throw new IllegalArgumentException(); + } + + private void skipWS() { + for (int len = ewkt.length(); offset < len && ewkt.charAt(offset) <= ' '; offset++) { + } + } + + @Override + public String toString() { + return new StringBuilder(ewkt.length() + 3).append(ewkt, 0, offset).append("<*>") + .append(ewkt, offset, ewkt.length()).toString(); + } + + } + + /** + * Converts EWKB to EWKT. + * + * @param ewkb + * source EWKB + * @return EWKT representation + */ + public static String ewkb2ewkt(byte[] ewkb) { + return ewkb2ewkt(ewkb, EWKBUtils.getDimensionSystem(ewkb)); + } + + /** + * Converts EWKB to EWKT. + * + * @param ewkb + * source EWKB + * @param dimensionSystem + * dimension system + * @return EWKT representation + */ + public static String ewkb2ewkt(byte[] ewkb, int dimensionSystem) { + StringBuilder output = new StringBuilder(); + EWKBUtils.parseEWKB(ewkb, new EWKTTarget(output, dimensionSystem)); + return output.toString(); + } + + /** + * Converts EWKT to EWKB. + * + * @param ewkt + * source EWKT + * @return EWKB representation + */ + public static byte[] ewkt2ewkb(String ewkt) { + return ewkt2ewkb(ewkt, getDimensionSystem(ewkt)); + } + + /** + * Converts EWKT to EWKB. + * + * @param ewkt + * source EWKT + * @param dimensionSystem + * dimension system + * @return EWKB representation + */ + public static byte[] ewkt2ewkb(String ewkt, int dimensionSystem) { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + EWKBTarget target = new EWKBTarget(output, dimensionSystem); + parseEWKT(ewkt, target); + return output.toByteArray(); + } + + /** + * Parses a EWKT. + * + * @param ewkt + * source EWKT + * @param target + * output target + */ + public static void parseEWKT(String ewkt, Target target) { + parseEWKT(new EWKTSource(ewkt), target, 0, 0); + } + + /** + * Parses geometry type and dimension system from the given string. + * + * @param s + * string to parse + * @return geometry type and dimension system in OGC geometry code format + * (type + dimensionSystem * 1000) + * @throws IllegalArgumentException + * if input is not valid + */ + public static int parseGeometryType(String s) { + EWKTSource source = new EWKTSource(s); + int type = source.readType(); + int dimensionSystem = 0; + if (source.hasData()) { + dimensionSystem = source.readDimensionSystem(); + if (source.hasData()) { + throw new IllegalArgumentException(); + } + } + return dimensionSystem * 1_000 + type; + } + + /** + * Parses a dimension system from the given string. + * + * @param s + * string to parse + * @return dimension system, one of XYZ, XYM, or XYZM + * @see GeometryUtils#DIMENSION_SYSTEM_XYZ + * @see GeometryUtils#DIMENSION_SYSTEM_XYM + * @see GeometryUtils#DIMENSION_SYSTEM_XYZM + * @throws IllegalArgumentException + * if input is not valid + */ + public static int parseDimensionSystem(String s) { + EWKTSource source = new EWKTSource(s); + int dimensionSystem = source.readDimensionSystem(); + if (source.hasData() || dimensionSystem == DIMENSION_SYSTEM_XY) { + throw new IllegalArgumentException(); + } + return dimensionSystem; + } + + /** + * Formats type and dimension system as a string. + * + * @param builder + * string builder + * @param type + * OGC geometry code format (type + dimensionSystem * 1000) + * @return the specified string builder + * @throws IllegalArgumentException + * if type is not valid + */ + public static StringBuilder formatGeometryTypeAndDimensionSystem(StringBuilder builder, int type) { + int t = type % 1_000, d = type / 1_000; + if (t < POINT || t > GEOMETRY_COLLECTION || d < DIMENSION_SYSTEM_XY || d > DIMENSION_SYSTEM_XYZM) { + throw new IllegalArgumentException(); + } + builder.append(TYPES[t - 1]); + if (d != DIMENSION_SYSTEM_XY) { + builder.append(' ').append(DIMENSION_SYSTEMS[d]); + } + return builder; + } + + /** + * Parses a EWKB. + * + * @param source + * EWKT source + * @param target + * output target + * @param parentType + * type of parent geometry collection, or 0 for the root geometry + * @param dimensionSystem + * dimension system of parent geometry + */ + private static void parseEWKT(EWKTSource source, Target target, int parentType, int dimensionSystem) { + if (parentType == 0) { + target.init(source.readSRID()); + } + int type; + switch (parentType) { + default: { + type = source.readType(); + dimensionSystem = source.readDimensionSystem(); + break; + } + case MULTI_POINT: + type = POINT; + break; + case MULTI_LINE_STRING: + type = LINE_STRING; + break; + case MULTI_POLYGON: + type = POLYGON; + break; + } + target.dimensionSystem(dimensionSystem); + switch (type) { + case POINT: { + if (parentType != 0 && parentType != MULTI_POINT && parentType != GEOMETRY_COLLECTION) { + throw new IllegalArgumentException(); + } + boolean empty = source.readEmpty(); + target.startPoint(); + if (empty) { + target.addCoordinate(Double.NaN, Double.NaN, Double.NaN, Double.NaN, 0, 1); + } else { + addCoordinate(source, target, dimensionSystem, 0, 1); + source.read(')'); + } + break; + } + case LINE_STRING: { + if (parentType != 0 && parentType != MULTI_LINE_STRING && parentType != GEOMETRY_COLLECTION) { + throw new IllegalArgumentException(); + } + boolean empty = source.readEmpty(); + if (empty) { + target.startLineString(0); + } else { + ArrayList coordinates = new ArrayList<>(); + do { + coordinates.add(readCoordinate(source, dimensionSystem)); + } while (source.hasMoreCoordinates()); + int numPoints = coordinates.size(); + if (numPoints < 0 || numPoints == 1) { + throw new IllegalArgumentException(); + } + target.startLineString(numPoints); + for (int i = 0; i < numPoints; i++) { + double[] c = coordinates.get(i); + target.addCoordinate(c[X], c[Y], c[Z], c[M], i, numPoints); + } + } + break; + } + case POLYGON: { + if (parentType != 0 && parentType != MULTI_POLYGON && parentType != GEOMETRY_COLLECTION) { + throw new IllegalArgumentException(); + } + boolean empty = source.readEmpty(); + if (empty) { + target.startPolygon(0, 0); + } else { + ArrayList outer = readRing(source, dimensionSystem); + ArrayList> inner = new ArrayList<>(); + while (source.hasMoreCoordinates()) { + inner.add(readRing(source, dimensionSystem)); + } + int numInner = inner.size(); + int size = outer.size(); + // Size may be 0 (EMPTY) or 4+ + if (size >= 1 && size <= 3) { + throw new IllegalArgumentException(); + } + if (size == 0 && numInner > 0) { + throw new IllegalArgumentException(); + } + target.startPolygon(numInner, size); + if (size > 0) { + addRing(outer, target); + for (int i = 0; i < numInner; i++) { + ArrayList ring = inner.get(i); + size = ring.size(); + // Size may be 0 (EMPTY) or 4+ + if (size >= 1 && size <= 3) { + throw new IllegalArgumentException(); + } + target.startPolygonInner(size); + addRing(ring, target); + } + target.endNonEmptyPolygon(); + } + } + break; + } + case MULTI_POINT: + case MULTI_LINE_STRING: + case MULTI_POLYGON: + parseCollection(source, target, type, parentType, dimensionSystem); + break; + case GEOMETRY_COLLECTION: + parseCollection(source, target, GEOMETRY_COLLECTION, parentType, 0); + break; + default: + throw new IllegalArgumentException(); + } + target.endObject(type); + if (parentType == 0 && source.hasData()) { + throw new IllegalArgumentException(); + } + } + + private static void parseCollection(EWKTSource source, Target target, int type, int parentType, + int dimensionSystem) { + if (parentType != 0 && parentType != GEOMETRY_COLLECTION) { + throw new IllegalArgumentException(); + } + if (source.readEmpty()) { + target.startCollection(type, 0); + } else { + if (type == MULTI_POINT && source.hasCoordinate()) { + parseMultiPointAlternative(source, target, dimensionSystem); + } else { + int numItems = source.getItemCount(); + target.startCollection(type, numItems); + for (int i = 0; i < numItems; i++) { + if (i > 0) { + source.read(','); + } + Target innerTarget = target.startCollectionItem(i, numItems); + parseEWKT(source, innerTarget, type, dimensionSystem); + target.endCollectionItem(innerTarget, type, i, numItems); + } + source.read(')'); + } + } + } + + private static void parseMultiPointAlternative(EWKTSource source, Target target, int dimensionSystem) { + // Special case for MULTIPOINT (1 2, 3 4) + ArrayList points = new ArrayList<>(); + do { + points.add(readCoordinate(source, dimensionSystem)); + } while (source.hasMoreCoordinates()); + int numItems = points.size(); + target.startCollection(MULTI_POINT, numItems); + for (int i = 0; i < points.size(); i++) { + Target innerTarget = target.startCollectionItem(i, numItems); + target.startPoint(); + double[] c = points.get(i); + target.addCoordinate(c[X], c[Y], c[Z], c[M], 0, 1); + target.endCollectionItem(innerTarget, MULTI_POINT, i, numItems); + } + } + + private static ArrayList readRing(EWKTSource source, int dimensionSystem) { + if (source.readEmpty()) { + return new ArrayList<>(0); + } + ArrayList result = new ArrayList<>(); + double[] c = readCoordinate(source, dimensionSystem); + double startX = c[X], startY = c[Y]; + result.add(c); + while (source.hasMoreCoordinates()) { + result.add(readCoordinate(source, dimensionSystem)); + } + int size = result.size(); + if (size < 4) { + throw new IllegalArgumentException(); + } + c = result.get(size - 1); + double endX = c[X], endY = c[Y]; + /* + * TODO OGC 06-103r4 determines points as equal if they have the same X + * and Y coordinates. Should we check Z and M here too? + */ + if (startX != endX || startY != endY) { + throw new IllegalArgumentException(); + } + return result; + } + + private static void addRing(ArrayList ring, Target target) { + for (int i = 0, size = ring.size(); i < size; i++) { + double[] coordinates = ring.get(i); + target.addCoordinate(coordinates[X], coordinates[Y], coordinates[Z], coordinates[M], i, size); + } + } + + private static void addCoordinate(EWKTSource source, Target target, int dimensionSystem, int index, int total) { + double x = source.readCoordinate(); + double y = source.readCoordinate(); + double z = (dimensionSystem & DIMENSION_SYSTEM_XYZ) != 0 ? source.readCoordinate() : Double.NaN; + double m = (dimensionSystem & DIMENSION_SYSTEM_XYM) != 0 ? source.readCoordinate() : Double.NaN; + target.addCoordinate(x, y, z, m, index, total); + } + + private static double[] readCoordinate(EWKTSource source, int dimensionSystem) { + double x = source.readCoordinate(); + double y = source.readCoordinate(); + double z = (dimensionSystem & DIMENSION_SYSTEM_XYZ) != 0 ? source.readCoordinate() : Double.NaN; + double m = (dimensionSystem & DIMENSION_SYSTEM_XYM) != 0 ? source.readCoordinate() : Double.NaN; + return new double[] { x, y, z, m }; + } + + /** + * Reads the dimension system from EWKT. + * + * @param ewkt + * EWKT source + * @return the dimension system + */ + public static int getDimensionSystem(String ewkt) { + EWKTSource source = new EWKTSource(ewkt); + source.readSRID(); + source.readType(); + return source.readDimensionSystem(); + } + + private EWKTUtils() { + } + +} diff --git a/h2/src/main/org/h2/util/geometry/GeoJsonUtils.java b/h2/src/main/org/h2/util/geometry/GeoJsonUtils.java new file mode 100644 index 0000000..1ba11df --- /dev/null +++ b/h2/src/main/org/h2/util/geometry/GeoJsonUtils.java @@ -0,0 +1,455 @@ +/* + * 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.util.geometry; + +import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYM; +import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYZ; +import static org.h2.util.geometry.GeometryUtils.GEOMETRY_COLLECTION; +import static org.h2.util.geometry.GeometryUtils.LINE_STRING; +import static org.h2.util.geometry.GeometryUtils.M; +import static org.h2.util.geometry.GeometryUtils.MULTI_LINE_STRING; +import static org.h2.util.geometry.GeometryUtils.MULTI_POINT; +import static org.h2.util.geometry.GeometryUtils.MULTI_POLYGON; +import static org.h2.util.geometry.GeometryUtils.POINT; +import static org.h2.util.geometry.GeometryUtils.POLYGON; +import static org.h2.util.geometry.GeometryUtils.X; +import static org.h2.util.geometry.GeometryUtils.Y; +import static org.h2.util.geometry.GeometryUtils.Z; + +import java.io.ByteArrayOutputStream; +import java.math.BigDecimal; + +import org.h2.api.ErrorCode; +import org.h2.message.DbException; +import org.h2.util.geometry.EWKBUtils.EWKBTarget; +import org.h2.util.geometry.GeometryUtils.DimensionSystemTarget; +import org.h2.util.geometry.GeometryUtils.Target; +import org.h2.util.json.JSONArray; +import org.h2.util.json.JSONByteArrayTarget; +import org.h2.util.json.JSONBytesSource; +import org.h2.util.json.JSONNull; +import org.h2.util.json.JSONNumber; +import org.h2.util.json.JSONObject; +import org.h2.util.json.JSONString; +import org.h2.util.json.JSONValue; +import org.h2.util.json.JSONValueTarget; + +/** + * GeoJson format support for GEOMETRY data type. + */ +public final class GeoJsonUtils { + + /** + * 0-based type names of geometries, subtract 1 from type code to get index + * in this array. + */ + static final String[] TYPES = { // + "Point", // + "LineString", // + "Polygon", // + "MultiPoint", // + "MultiLineString", // + "MultiPolygon", // + "GeometryCollection", // + }; + + /** + * Converter output target that writes a GeoJson. + */ + public static final class GeoJsonTarget extends Target { + + private final JSONByteArrayTarget output; + + private final int dimensionSystem; + + private int type; + + private boolean inMulti, inMultiLine, wasEmpty; + + /** + * Creates new GeoJson output target. + * + * @param output + * output JSON target + * @param dimensionSystem + * dimension system to use + */ + public GeoJsonTarget(JSONByteArrayTarget output, int dimensionSystem) { + if (dimensionSystem == DIMENSION_SYSTEM_XYM) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, + "M (XYM) dimension system is not supported in GeoJson"); + } + this.output = output; + this.dimensionSystem = dimensionSystem; + } + + @Override + protected void startPoint() { + type = POINT; + wasEmpty = false; + } + + @Override + protected void startLineString(int numPoints) { + writeHeader(LINE_STRING); + if (numPoints == 0) { + output.endArray(); + } + } + + @Override + protected void startPolygon(int numInner, int numPoints) { + writeHeader(POLYGON); + if (numPoints == 0) { + output.endArray(); + } else { + output.startArray(); + } + } + + @Override + protected void startPolygonInner(int numInner) { + output.startArray(); + if (numInner == 0) { + output.endArray(); + } + } + + @Override + protected void endNonEmptyPolygon() { + output.endArray(); + } + + @Override + protected void startCollection(int type, int numItems) { + writeHeader(type); + if (type != GEOMETRY_COLLECTION) { + inMulti = true; + if (type == MULTI_LINE_STRING || type == MULTI_POLYGON) { + inMultiLine = true; + } + } + } + + @Override + protected Target startCollectionItem(int index, int total) { + if (inMultiLine) { + output.startArray(); + } + return this; + } + + @Override + protected void endObject(int type) { + switch (type) { + case MULTI_POINT: + case MULTI_LINE_STRING: + case MULTI_POLYGON: + inMultiLine = inMulti = false; + //$FALL-THROUGH$ + case GEOMETRY_COLLECTION: + output.endArray(); + } + if (!inMulti && !wasEmpty) { + output.endObject(); + } + } + + private void writeHeader(int type) { + this.type = type; + wasEmpty = false; + if (!inMulti) { + writeStartObject(type); + } + } + + @Override + protected void addCoordinate(double x, double y, double z, double m, int index, int total) { + if (type == POINT) { + if (Double.isNaN(x) && Double.isNaN(y) && Double.isNaN(z) && Double.isNaN(m)) { + wasEmpty = true; + output.valueNull(); + return; + } + if (!inMulti) { + writeStartObject(POINT); + } + } + output.startArray(); + writeDouble(x); + writeDouble(y); + if ((dimensionSystem & DIMENSION_SYSTEM_XYZ) != 0) { + writeDouble(z); + } + if ((dimensionSystem & DIMENSION_SYSTEM_XYM) != 0) { + writeDouble(m); + } + output.endArray(); + if (type != POINT && index + 1 == total) { + output.endArray(); + } + } + + private void writeStartObject(int type) { + output.startObject(); + output.member("type"); + output.valueString(TYPES[type - 1]); + output.member(type != GEOMETRY_COLLECTION ? "coordinates" : "geometries"); + if (type != POINT) { + output.startArray(); + } + } + + private void writeDouble(double v) { + output.valueNumber(BigDecimal.valueOf(GeometryUtils.checkFinite(v)).stripTrailingZeros()); + } + + } + + /** + * Converts EWKB with known dimension system to GeoJson. + * + * @param ewkb + * geometry object in EWKB format + * @param dimensionSystem + * dimension system of the specified object, may be the same or + * smaller than its real dimension system. M dimension system is + * not supported. + * @return GeoJson representation of the specified geometry + * @throws DbException + * on unsupported dimension system + */ + public static byte[] ewkbToGeoJson(byte[] ewkb, int dimensionSystem) { + JSONByteArrayTarget output = new JSONByteArrayTarget(); + GeoJsonTarget target = new GeoJsonTarget(output, dimensionSystem); + EWKBUtils.parseEWKB(ewkb, target); + return output.getResult(); + } + + /** + * Converts EWKB with known dimension system to GeoJson. + * + * @param json + * geometry object in GeoJson format + * @param srid + * the SRID of geometry + * @return GeoJson representation of the specified geometry + * @throws DbException + * on unsupported dimension system + */ + public static byte[] geoJsonToEwkb(byte[] json, int srid) { + JSONValue v = JSONBytesSource.parse(json, new JSONValueTarget()); + DimensionSystemTarget dst = new DimensionSystemTarget(); + parse(v, dst); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + EWKBTarget target = new EWKBTarget(baos, dst.getDimensionSystem()); + target.init(srid); + parse(v, target); + return baos.toByteArray(); + } + + private static void parse(JSONValue v, GeometryUtils.Target target) { + if (v instanceof JSONNull) { + target.startPoint(); + target.addCoordinate(Double.NaN, Double.NaN, Double.NaN, Double.NaN, 0, 1); + target.endObject(POINT); + } else if (v instanceof JSONObject) { + JSONObject o = (JSONObject) v; + JSONValue t = o.getFirst("type"); + if (!(t instanceof JSONString)) { + throw new IllegalArgumentException(); + } + switch (((JSONString) t).getString()) { + case "Point": + parse(o, target, POINT); + break; + case "LineString": + parse(o, target, LINE_STRING); + break; + case "Polygon": + parse(o, target, POLYGON); + break; + case "MultiPoint": + parse(o, target, MULTI_POINT); + break; + case "MultiLineString": + parse(o, target, MULTI_LINE_STRING); + break; + case "MultiPolygon": + parse(o, target, MULTI_POLYGON); + break; + case "GeometryCollection": + parseGeometryCollection(o, target); + break; + default: + throw new IllegalArgumentException(); + } + } else { + throw new IllegalArgumentException(); + } + } + + private static void parse(JSONObject o, Target target, int type) { + JSONValue t = o.getFirst("coordinates"); + if (!(t instanceof JSONArray)) { + throw new IllegalArgumentException(); + } + JSONArray a = (JSONArray) t; + switch (type) { + case POINT: + target.startPoint(); + parseCoordinate(a, target, 0, 1); + target.endObject(POINT); + break; + case LINE_STRING: { + parseLineString(a, target); + break; + } + case POLYGON: { + parsePolygon(a, target); + break; + } + case MULTI_POINT: { + JSONValue[] points = a.getArray(); + int numPoints = points.length; + target.startCollection(MULTI_POINT, numPoints); + for (int i = 0; i < numPoints; i++) { + target.startPoint(); + parseCoordinate(points[i], target, 0, 1); + target.endObject(POINT); + target.endCollectionItem(target, MULTI_POINT, i, numPoints); + } + target.endObject(MULTI_POINT); + break; + } + case MULTI_LINE_STRING: { + JSONValue[] strings = a.getArray(); + int numStrings = strings.length; + target.startCollection(MULTI_LINE_STRING, numStrings); + for (int i = 0; i < numStrings; i++) { + JSONValue string = strings[i]; + if (!(string instanceof JSONArray)) { + throw new IllegalArgumentException(); + } + parseLineString((JSONArray) string, target); + target.endCollectionItem(target, MULTI_LINE_STRING, i, numStrings); + } + target.endObject(MULTI_LINE_STRING); + break; + } + case MULTI_POLYGON: { + JSONValue[] polygons = a.getArray(); + int numPolygons = polygons.length; + target.startCollection(MULTI_POLYGON, numPolygons); + for (int i = 0; i < numPolygons; i++) { + JSONValue string = polygons[i]; + if (!(string instanceof JSONArray)) { + throw new IllegalArgumentException(); + } + parsePolygon((JSONArray) string, target); + target.endCollectionItem(target, MULTI_POLYGON, i, numPolygons); + } + target.endObject(MULTI_POLYGON); + break; + } + default: + throw new IllegalArgumentException(); + } + } + + private static void parseGeometryCollection(JSONObject o, Target target) { + JSONValue t = o.getFirst("geometries"); + if (!(t instanceof JSONArray)) { + throw new IllegalArgumentException(); + } + JSONArray a = (JSONArray) t; + JSONValue[] geometries = a.getArray(); + int numGeometries = geometries.length; + target.startCollection(GEOMETRY_COLLECTION, numGeometries); + for (int i = 0; i < numGeometries; i++) { + JSONValue geometry = geometries[i]; + parse(geometry, target); + target.endCollectionItem(target, GEOMETRY_COLLECTION, i, numGeometries); + } + target.endObject(GEOMETRY_COLLECTION); + } + + private static void parseLineString(JSONArray a, Target target) { + JSONValue[] points = a.getArray(); + int numPoints = points.length; + target.startLineString(numPoints); + for (int i = 0; i < numPoints; i++) { + parseCoordinate(points[i], target, i, numPoints); + } + target.endObject(LINE_STRING); + } + + private static void parsePolygon(JSONArray a, Target target) { + JSONValue[] rings = a.getArray(); + int numRings = rings.length; + if (numRings == 0) { + target.startPolygon(0, 0); + } else { + JSONValue ring = rings[0]; + if (!(ring instanceof JSONArray)) { + throw new IllegalArgumentException(); + } + JSONValue[] points = ((JSONArray) ring).getArray(); + target.startPolygon(numRings - 1, points.length); + parseRing(points, target); + for (int i = 1; i < numRings; i++) { + ring = rings[i]; + if (!(ring instanceof JSONArray)) { + throw new IllegalArgumentException(); + } + points = ((JSONArray) ring).getArray(); + target.startPolygonInner(points.length); + parseRing(points, target); + } + target.endNonEmptyPolygon(); + } + target.endObject(POLYGON); + } + + private static void parseRing(JSONValue[] points, Target target) { + int numPoints = points.length; + for (int i = 0; i < numPoints; i++) { + parseCoordinate(points[i], target, i, numPoints); + } + } + + private static void parseCoordinate(JSONValue v, Target target, int index, int total) { + if (v instanceof JSONNull) { + target.addCoordinate(Double.NaN, Double.NaN, Double.NaN, Double.NaN, 0, 1); + return; + } + if (!(v instanceof JSONArray)) { + throw new IllegalArgumentException(); + } + JSONValue[] values = ((JSONArray) v).getArray(); + int length = values.length; + if (length < 2) { + throw new IllegalArgumentException(); + } + target.addCoordinate(readCoordinate(values, X), readCoordinate(values, Y), readCoordinate(values, Z), + readCoordinate(values, M), index, total); + } + + private static double readCoordinate(JSONValue[] values, int index) { + if (index >= values.length) { + return Double.NaN; + } + JSONValue v = values[index]; + if (!(v instanceof JSONNumber)) { + throw new IllegalArgumentException(); + } + return ((JSONNumber) v).getBigDecimal().doubleValue(); + } + + private GeoJsonUtils() { + } + +} diff --git a/h2/src/main/org/h2/util/geometry/GeometryUtils.java b/h2/src/main/org/h2/util/geometry/GeometryUtils.java new file mode 100644 index 0000000..ef4bf8e --- /dev/null +++ b/h2/src/main/org/h2/util/geometry/GeometryUtils.java @@ -0,0 +1,480 @@ +/* + * 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.util.geometry; + +/** + * Utilities for GEOMETRY data type. + */ +public final class GeometryUtils { + + /** + * Converter output target. + */ + public abstract static class Target { + + public Target() { + } + + /** + * Initializes top-level target. + * + * @param srid + * SRID + */ + protected void init(int srid) { + } + + /** + * Invoked to add dimension system requirement. + * + * @param dimensionSystem + * dimension system + */ + protected void dimensionSystem(int dimensionSystem) { + } + + /** + * Invoked before writing a POINT. + */ + protected void startPoint() { + } + + /** + * Invoked before writing a LINESTRING. + * + * @param numPoints + * number of points in line string + */ + protected void startLineString(int numPoints) { + } + + /** + * Invoked before writing a POLYGON. If polygon is empty, both + * parameters are 0. + * + * @param numInner + * number of inner polygons + * @param numPoints + * number of points in outer polygon + */ + protected void startPolygon(int numInner, int numPoints) { + } + + /** + * Invoked before writing an inner polygon in POLYGON. + * + * @param numInner + * number of points in inner polygon + */ + protected void startPolygonInner(int numInner) { + } + + /** + * Invoked after writing of non-empty POLYGON. + */ + protected void endNonEmptyPolygon() { + } + + /** + * Invoked before writing of a collection. + * + * @param type + * type of collection, one of + * {@link GeometryUtils#MULTI_POINT}, + * {@link GeometryUtils#MULTI_LINE_STRING}, + * {@link GeometryUtils#MULTI_POLYGON}, + * {@link GeometryUtils#GEOMETRY_COLLECTION} + * @param numItems + * number of items in this collection + */ + protected void startCollection(int type, int numItems) { + } + + /** + * Invoked before writing of a collection item. + * + * @param index + * 0-based index of this item in the collection + * @param total + * total number of items in the collection + * @return output target that should be used for processing of this + * collection item. May return this target or an custom + * sub-target. + */ + protected Target startCollectionItem(int index, int total) { + return this; + } + + /** + * Invoked after writing of a collection item. This method is invoked on + * the same target that was used for + * {@link #startCollectionItem(int, int)}. + * + * @param target + * the result of {@link #startCollectionItem(int, int)} + * @param type + * type of collection + * @param index + * 0-based index of this item in the collection + * @param total + * total number of items in the collection + */ + protected void endCollectionItem(Target target, int type, int index, int total) { + } + + /** + * Invoked after writing of the object. + * + * @param type + * type of the object + */ + protected void endObject(int type) { + } + + /** + * Invoked to add a coordinate to a geometry. + * + * @param x + * X coordinate + * @param y + * Y coordinate + * @param z + * Z coordinate (NaN if not used) + * @param m + * M coordinate (NaN if not used) + * @param index + * 0-based index of coordinate in the current sequence + * @param total + * total number of coordinates in the current sequence + */ + protected abstract void addCoordinate(double x, double y, double z, double m, int index, int total); + + } + + /** + * Converter output target that calculates an envelope. + */ + public static final class EnvelopeTarget extends Target { + + /** + * Enables or disables the envelope calculation. Inner rings of polygons + * are not counted. + */ + private boolean enabled; + + /** + * Whether envelope was set. + */ + private boolean set; + + private double minX, maxX, minY, maxY; + + /** + * Creates a new envelope calculation target. + */ + public EnvelopeTarget() { + } + + @Override + protected void startPoint() { + enabled = true; + } + + @Override + protected void startLineString(int numPoints) { + enabled = true; + } + + @Override + protected void startPolygon(int numInner, int numPoints) { + enabled = true; + } + + @Override + protected void startPolygonInner(int numInner) { + enabled = false; + } + + @Override + protected void addCoordinate(double x, double y, double z, double m, int index, int total) { + // POINT EMPTY has NaNs + if (enabled && !Double.isNaN(x) && !Double.isNaN(y)) { + if (!set) { + minX = maxX = x; + minY = maxY = y; + set = true; + } else { + if (minX > x) { + minX = x; + } + if (maxX < x) { + maxX = x; + } + if (minY > y) { + minY = y; + } + if (maxY < y) { + maxY = y; + } + } + } + } + + /** + * Returns the envelope. + * + * @return the envelope, or null + */ + public double[] getEnvelope() { + return set ? new double[] { minX, maxX, minY, maxY } : null; + } + + } + + /** + * Converter output target that determines minimal dimension system for a + * geometry. + */ + public static final class DimensionSystemTarget extends Target { + + private boolean hasZ; + + private boolean hasM; + + /** + * Creates a new dimension system determination target. + */ + public DimensionSystemTarget() { + } + + @Override + protected void dimensionSystem(int dimensionSystem) { + if ((dimensionSystem & DIMENSION_SYSTEM_XYZ) != 0) { + hasZ = true; + } + if ((dimensionSystem & DIMENSION_SYSTEM_XYM) != 0) { + hasM = true; + } + } + + @Override + protected void addCoordinate(double x, double y, double z, double m, int index, int total) { + if (!hasZ && !Double.isNaN(z)) { + hasZ = true; + } + if (!hasM && !Double.isNaN(m)) { + hasM = true; + } + } + + /** + * Returns the minimal dimension system. + * + * @return the minimal dimension system + */ + public int getDimensionSystem() { + return (hasZ ? DIMENSION_SYSTEM_XYZ : 0) | (hasM ? DIMENSION_SYSTEM_XYM : 0); + } + + } + + /** + * POINT geometry type. + */ + public static final int POINT = 1; + + /** + * LINESTRING geometry type. + */ + public static final int LINE_STRING = 2; + + /** + * POLYGON geometry type. + */ + public static final int POLYGON = 3; + + /** + * MULTIPOINT geometry type. + */ + public static final int MULTI_POINT = 4; + + /** + * MULTILINESTRING geometry type. + */ + public static final int MULTI_LINE_STRING = 5; + + /** + * MULTIPOLYGON geometry type. + */ + public static final int MULTI_POLYGON = 6; + + /** + * GEOMETRYCOLLECTION geometry type. + */ + public static final int GEOMETRY_COLLECTION = 7; + + /** + * Number of X coordinate. + */ + public static final int X = 0; + + /** + * Number of Y coordinate. + */ + public static final int Y = 1; + + /** + * Number of Z coordinate. + */ + public static final int Z = 2; + + /** + * Number of M coordinate. + */ + public static final int M = 3; + + /** + * Code of 2D (XY) dimension system. + */ + public static final int DIMENSION_SYSTEM_XY = 0; + + /** + * Code of Z (XYZ) dimension system. Can also be used in bit masks to + * determine presence of dimension Z. + */ + public static final int DIMENSION_SYSTEM_XYZ = 1; + + /** + * Code of M (XYM) dimension system. Can also be used in bit masks to + * determine presence of dimension M. + */ + public static final int DIMENSION_SYSTEM_XYM = 2; + + /** + * Code of ZM (XYZM) dimension system. Can be also combined from + * {@link #DIMENSION_SYSTEM_XYZ} and {@link #DIMENSION_SYSTEM_XYM} using + * bitwise OR. + */ + public static final int DIMENSION_SYSTEM_XYZM = 3; + + /** + * Minimum X coordinate index. + */ + public static final int MIN_X = 0; + + /** + * Maximum X coordinate index. + */ + public static final int MAX_X = 1; + + /** + * Minimum Y coordinate index. + */ + public static final int MIN_Y = 2; + + /** + * Maximum Y coordinate index. + */ + public static final int MAX_Y = 3; + + /** + * Calculates an envelope of a specified geometry. + * + * @param ewkb + * EWKB of a geometry + * @return envelope, or null + */ + public static double[] getEnvelope(byte[] ewkb) { + EnvelopeTarget target = new EnvelopeTarget(); + EWKBUtils.parseEWKB(ewkb, target); + return target.getEnvelope(); + } + + /** + * Checks whether two envelopes intersect with each other. + * + * @param envelope1 + * first envelope, or null + * @param envelope2 + * second envelope, or null + * @return whether the specified envelopes intersects + */ + public static boolean intersects(double[] envelope1, double[] envelope2) { + return envelope1 != null && envelope2 != null // + && envelope1[MAX_X] >= envelope2[MIN_X] // + && envelope1[MIN_X] <= envelope2[MAX_X] // + && envelope1[MAX_Y] >= envelope2[MIN_Y] // + && envelope1[MIN_Y] <= envelope2[MAX_Y]; + } + + /** + * Returns union of two envelopes. This method does not modify the specified + * envelopes, but may return one of them as a result. + * + * @param envelope1 + * first envelope, or null + * @param envelope2 + * second envelope, or null + * @return union of two envelopes + */ + public static double[] union(double[] envelope1, double[] envelope2) { + if (envelope1 == null) { + return envelope2; + } else if (envelope2 == null) { + return envelope1; + } + double minX1 = envelope1[MIN_X], maxX1 = envelope1[MAX_X], minY1 = envelope1[MIN_Y], maxY1 = envelope1[MAX_Y]; + double minX2 = envelope2[MIN_X], maxX2 = envelope2[MAX_X], minY2 = envelope2[MIN_Y], maxY2 = envelope2[MAX_Y]; + boolean modified = false; + if (minX1 > minX2) { + minX1 = minX2; + modified = true; + } + if (maxX1 < maxX2) { + maxX1 = maxX2; + modified = true; + } + if (minY1 > minY2) { + minY1 = minY2; + modified = true; + } + if (maxY1 < maxY2) { + maxY1 = maxY2; + modified = true; + } + return modified ? new double[] { minX1, maxX1, minY1, maxY1 } : envelope1; + } + + /** + * Normalizes all NaNs into single type on NaN and negative zero to positive + * zero. + * + * @param d + * double value + * @return normalized value + */ + static double toCanonicalDouble(double d) { + return Double.isNaN(d) ? Double.NaN : d == 0d ? 0d : d; + } + + /** + * Throw exception if param is not finite value (ie. NaN/inf/etc) + * + * @param d + * a double value + * @return the same double value + */ + static double checkFinite(double d) { + if (!Double.isFinite(d)) { + throw new IllegalArgumentException(); + } + return d; + } + + private GeometryUtils() { + } + +} diff --git a/h2/src/main/org/h2/util/geometry/JTSUtils.java b/h2/src/main/org/h2/util/geometry/JTSUtils.java new file mode 100644 index 0000000..40d1dc3 --- /dev/null +++ b/h2/src/main/org/h2/util/geometry/JTSUtils.java @@ -0,0 +1,488 @@ +/* + * 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.util.geometry; + +import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XY; +import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYM; +import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYZ; +import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYZM; +import static org.h2.util.geometry.GeometryUtils.GEOMETRY_COLLECTION; +import static org.h2.util.geometry.GeometryUtils.LINE_STRING; +import static org.h2.util.geometry.GeometryUtils.M; +import static org.h2.util.geometry.GeometryUtils.MULTI_LINE_STRING; +import static org.h2.util.geometry.GeometryUtils.MULTI_POINT; +import static org.h2.util.geometry.GeometryUtils.MULTI_POLYGON; +import static org.h2.util.geometry.GeometryUtils.POINT; +import static org.h2.util.geometry.GeometryUtils.POLYGON; +import static org.h2.util.geometry.GeometryUtils.X; +import static org.h2.util.geometry.GeometryUtils.Y; +import static org.h2.util.geometry.GeometryUtils.Z; +import static org.h2.util.geometry.GeometryUtils.checkFinite; +import static org.h2.util.geometry.GeometryUtils.toCanonicalDouble; + +import java.io.ByteArrayOutputStream; + +import org.h2.message.DbException; +import org.h2.util.geometry.EWKBUtils.EWKBTarget; +import org.h2.util.geometry.GeometryUtils.Target; +import org.locationtech.jts.geom.CoordinateSequence; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryCollection; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.LineString; +import org.locationtech.jts.geom.LinearRing; +import org.locationtech.jts.geom.MultiLineString; +import org.locationtech.jts.geom.MultiPoint; +import org.locationtech.jts.geom.MultiPolygon; +import org.locationtech.jts.geom.Point; +import org.locationtech.jts.geom.Polygon; +import org.locationtech.jts.geom.PrecisionModel; +import org.locationtech.jts.geom.impl.CoordinateArraySequenceFactory; +import org.locationtech.jts.geom.impl.PackedCoordinateSequenceFactory; + +/** + * Utilities for Geometry data type from JTS library. + */ +public final class JTSUtils { + + /** + * Converter output target that creates a JTS Geometry. + */ + public static final class GeometryTarget extends Target { + + private final int dimensionSystem; + + private GeometryFactory factory; + + private int type; + + private CoordinateSequence coordinates; + + private CoordinateSequence[] innerCoordinates; + + private int innerOffset; + + private Geometry[] subgeometries; + + /** + * Creates a new instance of JTS Geometry target. + * + * @param dimensionSystem + * dimension system to use + */ + public GeometryTarget(int dimensionSystem) { + this.dimensionSystem = dimensionSystem; + } + + private GeometryTarget(int dimensionSystem, GeometryFactory factory) { + this.dimensionSystem = dimensionSystem; + this.factory = factory; + } + + @Override + protected void init(int srid) { + factory = new GeometryFactory(new PrecisionModel(), srid, + (dimensionSystem & DIMENSION_SYSTEM_XYM) != 0 ? PackedCoordinateSequenceFactory.DOUBLE_FACTORY + : CoordinateArraySequenceFactory.instance()); + } + + @Override + protected void startPoint() { + type = POINT; + initCoordinates(1); + innerOffset = -1; + } + + @Override + protected void startLineString(int numPoints) { + type = LINE_STRING; + initCoordinates(numPoints); + innerOffset = -1; + } + + @Override + protected void startPolygon(int numInner, int numPoints) { + type = POLYGON; + initCoordinates(numPoints); + innerCoordinates = new CoordinateSequence[numInner]; + innerOffset = -1; + } + + @Override + protected void startPolygonInner(int numInner) { + innerCoordinates[++innerOffset] = createCoordinates(numInner); + } + + @Override + protected void startCollection(int type, int numItems) { + this.type = type; + switch (type) { + case MULTI_POINT: + subgeometries = new Point[numItems]; + break; + case MULTI_LINE_STRING: + subgeometries = new LineString[numItems]; + break; + case MULTI_POLYGON: + subgeometries = new Polygon[numItems]; + break; + case GEOMETRY_COLLECTION: + subgeometries = new Geometry[numItems]; + break; + default: + throw new IllegalArgumentException(); + } + } + + @Override + protected Target startCollectionItem(int index, int total) { + return new GeometryTarget(dimensionSystem, factory); + } + + @Override + protected void endCollectionItem(Target target, int type, int index, int total) { + subgeometries[index] = ((GeometryTarget) target).getGeometry(); + } + + private void initCoordinates(int numPoints) { + coordinates = createCoordinates(numPoints); + } + + private CoordinateSequence createCoordinates(int numPoints) { + int d, m; + switch (dimensionSystem) { + case DIMENSION_SYSTEM_XY: + d = 2; + m = 0; + break; + case DIMENSION_SYSTEM_XYZ: + d = 3; + m = 0; + break; + case DIMENSION_SYSTEM_XYM: + d = 3; + m = 1; + break; + case DIMENSION_SYSTEM_XYZM: + d = 4; + m = 1; + break; + default: + throw DbException.getInternalError(); + } + return factory.getCoordinateSequenceFactory().create(numPoints, d, m); + } + + @Override + protected void addCoordinate(double x, double y, double z, double m, int index, int total) { + if (type == POINT && Double.isNaN(x) && Double.isNaN(y) && Double.isNaN(z) && Double.isNaN(m)) { + this.coordinates = createCoordinates(0); + return; + } + CoordinateSequence coordinates = innerOffset < 0 ? this.coordinates : innerCoordinates[innerOffset]; + coordinates.setOrdinate(index, X, checkFinite(x)); + coordinates.setOrdinate(index, Y, checkFinite(y)); + switch (dimensionSystem) { + case DIMENSION_SYSTEM_XYZM: + coordinates.setOrdinate(index, M, checkFinite(m)); + //$FALL-THROUGH$ + case DIMENSION_SYSTEM_XYZ: + coordinates.setOrdinate(index, Z, checkFinite(z)); + break; + case DIMENSION_SYSTEM_XYM: + coordinates.setOrdinate(index, 2, checkFinite(m)); + } + } + + Geometry getGeometry() { + switch (type) { + case POINT: + return new Point(coordinates, factory); + case LINE_STRING: + return new LineString(coordinates, factory); + case POLYGON: { + LinearRing shell = new LinearRing(coordinates, factory); + int innerCount = innerCoordinates.length; + LinearRing[] holes = new LinearRing[innerCount]; + for (int i = 0; i < innerCount; i++) { + holes[i] = new LinearRing(innerCoordinates[i], factory); + } + return new Polygon(shell, holes, factory); + } + case MULTI_POINT: + return new MultiPoint((Point[]) subgeometries, factory); + case MULTI_LINE_STRING: + return new MultiLineString((LineString[]) subgeometries, factory); + case MULTI_POLYGON: + return new MultiPolygon((Polygon[]) subgeometries, factory); + case GEOMETRY_COLLECTION: + return new GeometryCollection(subgeometries, factory); + default: + throw new IllegalStateException(); + } + } + + } + + /** + * Converts EWKB to a JTS geometry object. + * + * @param ewkb + * source EWKB + * @return JTS geometry object + */ + public static Geometry ewkb2geometry(byte[] ewkb) { + return ewkb2geometry(ewkb, EWKBUtils.getDimensionSystem(ewkb)); + } + + /** + * Converts EWKB to a JTS geometry object. + * + * @param ewkb + * source EWKB + * @param dimensionSystem + * dimension system + * @return JTS geometry object + */ + public static Geometry ewkb2geometry(byte[] ewkb, int dimensionSystem) { + GeometryTarget target = new GeometryTarget(dimensionSystem); + EWKBUtils.parseEWKB(ewkb, target); + return target.getGeometry(); + } + + /** + * Converts Geometry to EWKB. + * + * @param geometry + * source geometry + * @return EWKB representation + */ + public static byte[] geometry2ewkb(Geometry geometry) { + return geometry2ewkb(geometry, getDimensionSystem(geometry)); + } + + /** + * Converts Geometry to EWKB. + * + * @param geometry + * source geometry + * @param dimensionSystem + * dimension system + * @return EWKB representation + */ + public static byte[] geometry2ewkb(Geometry geometry, int dimensionSystem) { + // Write an EWKB + ByteArrayOutputStream output = new ByteArrayOutputStream(); + EWKBTarget target = new EWKBTarget(output, dimensionSystem); + parseGeometry(geometry, target); + return output.toByteArray(); + } + + /** + * Parses a JTS Geometry object. + * + * @param geometry + * geometry to parse + * @param target + * output target + */ + public static void parseGeometry(Geometry geometry, Target target) { + parseGeometry(geometry, target, 0); + } + + /** + * Parses a JTS Geometry object. + * + * @param geometry + * geometry to parse + * @param target + * output target + * @param parentType + * type of parent geometry collection, or 0 for the root geometry + */ + private static void parseGeometry(Geometry geometry, Target target, int parentType) { + if (parentType == 0) { + target.init(geometry.getSRID()); + } + if (geometry instanceof Point) { + if (parentType != 0 && parentType != MULTI_POINT && parentType != GEOMETRY_COLLECTION) { + throw new IllegalArgumentException(); + } + target.startPoint(); + Point p = (Point) geometry; + if (p.isEmpty()) { + target.addCoordinate(Double.NaN, Double.NaN, Double.NaN, Double.NaN, 0, 1); + } else { + addCoordinate(p.getCoordinateSequence(), target, 0, 1); + } + target.endObject(POINT); + } else if (geometry instanceof LineString) { + if (parentType != 0 && parentType != MULTI_LINE_STRING && parentType != GEOMETRY_COLLECTION) { + throw new IllegalArgumentException(); + } + LineString ls = (LineString) geometry; + CoordinateSequence cs = ls.getCoordinateSequence(); + int numPoints = cs.size(); + if (numPoints == 1) { + throw new IllegalArgumentException(); + } + target.startLineString(numPoints); + for (int i = 0; i < numPoints; i++) { + addCoordinate(cs, target, i, numPoints); + } + target.endObject(LINE_STRING); + } else if (geometry instanceof Polygon) { + if (parentType != 0 && parentType != MULTI_POLYGON && parentType != GEOMETRY_COLLECTION) { + throw new IllegalArgumentException(); + } + Polygon p = (Polygon) geometry; + int numInner = p.getNumInteriorRing(); + CoordinateSequence cs = p.getExteriorRing().getCoordinateSequence(); + int size = cs.size(); + // Size may be 0 (EMPTY) or 4+ + if (size >= 1 && size <= 3) { + throw new IllegalArgumentException(); + } + if (size == 0 && numInner > 0) { + throw new IllegalArgumentException(); + } + target.startPolygon(numInner, size); + if (size > 0) { + addRing(cs, target, size); + for (int i = 0; i < numInner; i++) { + cs = p.getInteriorRingN(i).getCoordinateSequence(); + size = cs.size(); + // Size may be 0 (EMPTY) or 4+ + if (size >= 1 && size <= 3) { + throw new IllegalArgumentException(); + } + target.startPolygonInner(size); + addRing(cs, target, size); + } + target.endNonEmptyPolygon(); + } + target.endObject(POLYGON); + } else if (geometry instanceof GeometryCollection) { + if (parentType != 0 && parentType != GEOMETRY_COLLECTION) { + throw new IllegalArgumentException(); + } + GeometryCollection gc = (GeometryCollection) geometry; + int type; + if (gc instanceof MultiPoint) { + type = MULTI_POINT; + } else if (gc instanceof MultiLineString) { + type = MULTI_LINE_STRING; + } else if (gc instanceof MultiPolygon) { + type = MULTI_POLYGON; + } else { + type = GEOMETRY_COLLECTION; + } + int numItems = gc.getNumGeometries(); + target.startCollection(type, numItems); + for (int i = 0; i < numItems; i++) { + Target innerTarget = target.startCollectionItem(i, numItems); + parseGeometry(gc.getGeometryN(i), innerTarget, type); + target.endCollectionItem(innerTarget, type, i, numItems); + } + target.endObject(type); + } else { + throw new IllegalArgumentException(); + } + } + + private static void addRing(CoordinateSequence sequence, Target target, int size) { + // 0 or 4+ are valid + if (size >= 4) { + double startX = toCanonicalDouble(sequence.getX(0)), startY = toCanonicalDouble(sequence.getY(0)); + addCoordinate(sequence, target, 0, size, startX, startY); + for (int i = 1; i < size - 1; i++) { + addCoordinate(sequence, target, i, size); + } + double endX = toCanonicalDouble(sequence.getX(size - 1)), // + endY = toCanonicalDouble(sequence.getY(size - 1)); + /* + * TODO OGC 06-103r4 determines points as equal if they have the + * same X and Y coordinates. Should we check Z and M here too? + */ + if (startX != endX || startY != endY) { + throw new IllegalArgumentException(); + } + addCoordinate(sequence, target, size - 1, size, endX, endY); + } + } + + private static void addCoordinate(CoordinateSequence sequence, Target target, int index, int total) { + addCoordinate(sequence, target, index, total, toCanonicalDouble(sequence.getX(index)), + toCanonicalDouble(sequence.getY(index))); + } + + private static void addCoordinate(CoordinateSequence sequence, Target target, int index, int total, double x, + double y) { + double z = toCanonicalDouble(sequence.getZ(index)); + double m = toCanonicalDouble(sequence.getM(index)); + target.addCoordinate(x, y, z, m, index, total); + } + + /** + * Determines a dimension system of a JTS Geometry object. + * + * @param geometry + * geometry to parse + * @return the dimension system + */ + public static int getDimensionSystem(Geometry geometry) { + int d = getDimensionSystem1(geometry); + return d >= 0 ? d : 0; + } + + private static int getDimensionSystem1(Geometry geometry) { + int d; + if (geometry instanceof Point) { + d = getDimensionSystemFromSequence(((Point) geometry).getCoordinateSequence()); + } else if (geometry instanceof LineString) { + d = getDimensionSystemFromSequence(((LineString) geometry).getCoordinateSequence()); + } else if (geometry instanceof Polygon) { + d = getDimensionSystemFromSequence(((Polygon) geometry).getExteriorRing().getCoordinateSequence()); + } else if (geometry instanceof GeometryCollection) { + d = -1; + GeometryCollection gc = (GeometryCollection) geometry; + for (int i = 0, l = gc.getNumGeometries(); i < l; i++) { + d = getDimensionSystem1(gc.getGeometryN(i)); + if (d >= 0) { + break; + } + } + } else { + throw new IllegalArgumentException(); + } + return d; + } + + private static int getDimensionSystemFromSequence(CoordinateSequence sequence) { + int size = sequence.size(); + if (size > 0) { + for (int i = 0; i < size; i++) { + int d = getDimensionSystemFromCoordinate(sequence, i); + if (d >= 0) { + return d; + } + } + } + return (sequence.hasZ() ? DIMENSION_SYSTEM_XYZ : 0) | (sequence.hasM() ? DIMENSION_SYSTEM_XYM : 0); + } + + private static int getDimensionSystemFromCoordinate(CoordinateSequence sequence, int index) { + if (Double.isNaN(sequence.getX(index))) { + return -1; + } + return (!Double.isNaN(sequence.getZ(index)) ? DIMENSION_SYSTEM_XYZ : 0) + | (!Double.isNaN(sequence.getM(index)) ? DIMENSION_SYSTEM_XYM : 0); + } + + private JTSUtils() { + } + +} diff --git a/h2/src/main/org/h2/util/geometry/package.html b/h2/src/main/org/h2/util/geometry/package.html new file mode 100644 index 0000000..b6d0df0 --- /dev/null +++ b/h2/src/main/org/h2/util/geometry/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Internal utility classes for GEOMETRY data type. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/util/json/JSONArray.java b/h2/src/main/org/h2/util/json/JSONArray.java new file mode 100644 index 0000000..69e3564 --- /dev/null +++ b/h2/src/main/org/h2/util/json/JSONArray.java @@ -0,0 +1,47 @@ +/* + * 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.util.json; + +import java.util.ArrayList; + +/** + * JSON array. + */ +public class JSONArray extends JSONValue { + + private final ArrayList elements = new ArrayList<>(); + + JSONArray() { + } + + /** + * Add a value to the array. + * + * @param value the value to add + */ + void addElement(JSONValue value) { + elements.add(value); + } + + @Override + public void addTo(JSONTarget target) { + target.startArray(); + for (JSONValue element : elements) { + element.addTo(target); + } + target.endArray(); + } + + /** + * Returns the value. + * + * @return the value + */ + public JSONValue[] getArray() { + return elements.toArray(new JSONValue[0]); + } + +} diff --git a/h2/src/main/org/h2/util/json/JSONBoolean.java b/h2/src/main/org/h2/util/json/JSONBoolean.java new file mode 100644 index 0000000..dd00c07 --- /dev/null +++ b/h2/src/main/org/h2/util/json/JSONBoolean.java @@ -0,0 +1,47 @@ +/* + * 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.util.json; + +/** + * JSON boolean. + */ +public class JSONBoolean extends JSONValue { + + /** + * {@code false} value. + */ + public static final JSONBoolean FALSE = new JSONBoolean(false); + + /** + * {@code true} value. + */ + public static final JSONBoolean TRUE = new JSONBoolean(true); + + private final boolean value; + + private JSONBoolean(boolean value) { + this.value = value; + } + + @Override + public void addTo(JSONTarget target) { + if (value) { + target.valueTrue(); + } else { + target.valueFalse(); + } + } + + /** + * Returns the value. + * + * @return the value + */ + public boolean getBoolean() { + return value; + } + +} diff --git a/h2/src/main/org/h2/util/json/JSONByteArrayTarget.java b/h2/src/main/org/h2/util/json/JSONByteArrayTarget.java new file mode 100644 index 0000000..9082b8a --- /dev/null +++ b/h2/src/main/org/h2/util/json/JSONByteArrayTarget.java @@ -0,0 +1,246 @@ +/* + * 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.util.json; + +import static org.h2.util.json.JSONStringTarget.ARRAY; +import static org.h2.util.json.JSONStringTarget.HEX; +import static org.h2.util.json.JSONStringTarget.OBJECT; + +import java.io.ByteArrayOutputStream; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; + +import org.h2.util.ByteStack; + +/** + * JSON byte array target. + */ +public final class JSONByteArrayTarget extends JSONTarget { + + private static final byte[] NULL_BYTES = "null".getBytes(StandardCharsets.ISO_8859_1); + + private static final byte[] FALSE_BYTES = "false".getBytes(StandardCharsets.ISO_8859_1); + + private static final byte[] TRUE_BYTES = "true".getBytes(StandardCharsets.ISO_8859_1); + + private static final byte[] U00_BYTES = "\\u00".getBytes(StandardCharsets.ISO_8859_1); + + /** + * Encodes a JSON string and appends it to the specified output stream. + * + * @param baos + * the output stream to append to + * @param s + * the string to encode + * @return the specified output stream + */ + public static ByteArrayOutputStream encodeString(ByteArrayOutputStream baos, String s) { + baos.write('"'); + for (int i = 0, length = s.length(); i < length; i++) { + char c = s.charAt(i); + switch (c) { + case '\b': + baos.write('\\'); + baos.write('b'); + break; + case '\t': + baos.write('\\'); + baos.write('t'); + break; + case '\f': + baos.write('\\'); + baos.write('f'); + break; + case '\n': + baos.write('\\'); + baos.write('n'); + break; + case '\r': + baos.write('\\'); + baos.write('r'); + break; + case '"': + baos.write('\\'); + baos.write('"'); + break; + case '\\': + baos.write('\\'); + baos.write('\\'); + break; + default: + if (c >= ' ') { + if (c < 0x80) { + baos.write(c); + } else if (c < 0x800) { + baos.write(0xc0 | c >> 6); + baos.write(0x80 | c & 0x3f); + } else if (!Character.isSurrogate(c)) { + baos.write(0xe0 | c >> 12); + baos.write(0x80 | c >> 6 & 0x3f); + baos.write(0x80 | c & 0x3f); + } else { + char c2; + if (!Character.isHighSurrogate(c) || ++i >= length + || !Character.isLowSurrogate(c2 = s.charAt(i))) { + throw new IllegalArgumentException(); + } + int uc = Character.toCodePoint(c, c2); + baos.write(0xf0 | uc >> 18); + baos.write(0x80 | uc >> 12 & 0x3f); + baos.write(0x80 | uc >> 6 & 0x3f); + baos.write(0x80 | uc & 0x3f); + } + } else { + baos.write(U00_BYTES, 0, 4); + baos.write(HEX[c >>> 4 & 0xf]); + baos.write(HEX[c & 0xf]); + } + } + } + baos.write('"'); + return baos; + } + + private final ByteArrayOutputStream baos; + + private final ByteStack stack; + + private boolean needSeparator; + + private boolean afterName; + + /** + * Creates new instance of JSON byte array target. + */ + public JSONByteArrayTarget() { + baos = new ByteArrayOutputStream(); + stack = new ByteStack(); + } + + @Override + public void startObject() { + beforeValue(); + afterName = false; + stack.push(OBJECT); + baos.write('{'); + } + + @Override + public void endObject() { + if (afterName || stack.poll(-1) != OBJECT) { + throw new IllegalStateException(); + } + baos.write('}'); + afterValue(); + } + + @Override + public void startArray() { + beforeValue(); + afterName = false; + stack.push(ARRAY); + baos.write('['); + } + + @Override + public void endArray() { + if (stack.poll(-1) != ARRAY) { + throw new IllegalStateException(); + } + baos.write(']'); + afterValue(); + } + + @Override + public void member(String name) { + if (afterName || stack.peek(-1) != OBJECT) { + throw new IllegalStateException(); + } + afterName = true; + beforeValue(); + encodeString(baos, name).write(':'); + } + + @Override + public void valueNull() { + beforeValue(); + baos.write(NULL_BYTES, 0, 4); + afterValue(); + } + + @Override + public void valueFalse() { + beforeValue(); + baos.write(FALSE_BYTES, 0, 5); + afterValue(); + } + + @Override + public void valueTrue() { + beforeValue(); + baos.write(TRUE_BYTES, 0, 4); + afterValue(); + } + + @Override + public void valueNumber(BigDecimal number) { + beforeValue(); + String s = number.toString(); + int index = s.indexOf('E'); + byte[] b = s.getBytes(StandardCharsets.ISO_8859_1); + if (index >= 0 && s.charAt(++index) == '+') { + baos.write(b, 0, index); + baos.write(b, index + 1, b.length - index - 1); + } else { + baos.write(b, 0, b.length); + } + afterValue(); + } + + @Override + public void valueString(String string) { + beforeValue(); + encodeString(baos, string); + afterValue(); + } + + private void beforeValue() { + if (!afterName && stack.peek(-1) == OBJECT) { + throw new IllegalStateException(); + } + if (needSeparator) { + if (stack.isEmpty()) { + throw new IllegalStateException(); + } + needSeparator = false; + baos.write(','); + } + } + + private void afterValue() { + needSeparator = true; + afterName = false; + } + + @Override + public boolean isPropertyExpected() { + return !afterName && stack.peek(-1) == OBJECT; + } + + @Override + public boolean isValueSeparatorExpected() { + return needSeparator; + } + + @Override + public byte[] getResult() { + if (!stack.isEmpty() || baos.size() == 0) { + throw new IllegalStateException(); + } + return baos.toByteArray(); + } + +} diff --git a/h2/src/main/org/h2/util/json/JSONBytesSource.java b/h2/src/main/org/h2/util/json/JSONBytesSource.java new file mode 100644 index 0000000..2f9c75b --- /dev/null +++ b/h2/src/main/org/h2/util/json/JSONBytesSource.java @@ -0,0 +1,258 @@ +/* + * 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.util.json; + +import java.math.BigDecimal; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +/** + * JSON byte array source. + */ +public final class JSONBytesSource extends JSONTextSource { + + /** + * Parses source bytes to a specified target. + * + * @param bytes + * source + * @param target + * target + * @param + * the type of the result + * @return the result of the target + */ + public static R parse(byte[] bytes, JSONTarget target) { + int length = bytes.length; + Charset charset = null; + if (length >= 4) { + byte b0 = bytes[0]; + byte b1 = bytes[1]; + byte b2 = bytes[2]; + byte b3 = bytes[3]; + switch (b0) { + case -2: + if (b1 == -1) { + charset = StandardCharsets.UTF_16BE; + } + break; + case -1: + if (b1 == -2) { + if (b2 == 0 && b3 == 0) { + charset = Charset.forName("UTF-32LE"); + } else { + charset = StandardCharsets.UTF_16LE; + } + } + break; + case 0: + if (b1 != 0) { + charset = StandardCharsets.UTF_16BE; + } else if (b2 == 0 && b3 != 0 || b2 == -2 && b3 == -1) { + charset = Charset.forName("UTF-32BE"); + } + break; + default: + if (b1 == 0) { + if (b2 == 0 && b3 == 0) { + charset = Charset.forName("UTF-32LE"); + } else { + charset = StandardCharsets.UTF_16LE; + } + } + break; + } + } else if (length >= 2) { + byte b0 = bytes[0]; + byte b1 = bytes[1]; + if (b0 != 0) { + if (b1 == 0) { + charset = StandardCharsets.UTF_16LE; + } + } else if (b1 != 0) { + charset = StandardCharsets.UTF_16BE; + } + } + (charset == null ? new JSONBytesSource(bytes, target) + : new JSONStringSource(new String(bytes, charset), target)).parse(); + return target.getResult(); + } + + /** + * Converts bytes into normalized JSON representation. + * + * @param bytes + * source representation + * @return normalized representation + */ + public static byte[] normalize(byte[] bytes) { + return parse(bytes, new JSONByteArrayTarget()); + } + + private final byte[] bytes; + + private final int length; + + private int index; + + JSONBytesSource(byte[] bytes, JSONTarget target) { + super(target); + this.bytes = bytes; + this.length = bytes.length; + // Ignore BOM + if (nextChar() != '\uFEFF') { + index = 0; + } + } + + @Override + int nextCharAfterWhitespace() { + int index = this.index; + while (index < length) { + byte ch = bytes[index++]; + switch (ch) { + case '\t': + case '\n': + case '\r': + case ' ': + break; + default: + if (ch < 0) { + throw new IllegalArgumentException(); + } + this.index = index; + return ch; + } + } + return -1; + } + + @Override + void readKeyword1(String keyword) { + int l = keyword.length() - 1; + if (index + l > length) { + throw new IllegalArgumentException(); + } + for (int i = index, j = 1; j <= l; i++, j++) { + if (bytes[i] != keyword.charAt(j)) { + throw new IllegalArgumentException(); + } + } + index += l; + } + + @Override + void parseNumber(boolean positive) { + int index = this.index; + int start = index - 1; + index = skipInt(index, positive); + l: if (index < length) { + byte ch = bytes[index]; + if (ch == '.') { + index = skipInt(index + 1, false); + if (index >= length) { + break l; + } + ch = bytes[index]; + } + if (ch == 'E' || ch == 'e') { + if (++index >= length) { + throw new IllegalArgumentException(); + } + ch = bytes[index]; + if (ch == '+' || ch == '-') { + index++; + } + index = skipInt(index, false); + } + } + target.valueNumber(new BigDecimal(new String(bytes, start, index - start, StandardCharsets.ISO_8859_1))); + this.index = index; + } + + private int skipInt(int index, boolean hasInt) { + while (index < length) { + byte ch = bytes[index]; + if (ch >= '0' && ch <= '9') { + hasInt = true; + index++; + } else { + break; + } + } + if (!hasInt) { + throw new IllegalArgumentException(); + } + return index; + } + + @Override + int nextChar() { + if (index >= length) { + throw new IllegalArgumentException(); + } + int b1 = bytes[index++] & 0xff; + if (b1 >= 0x80) { + if (b1 >= 0xe0) { + if (b1 >= 0xf0) { + if (index + 2 >= length) { + throw new IllegalArgumentException(); + } + int b2 = bytes[index++] & 0xff; + int b3 = bytes[index++] & 0xff; + int b4 = bytes[index++] & 0xff; + b1 = ((b1 & 0xf) << 18) + ((b2 & 0x3f) << 12) + ((b3 & 0x3f) << 6) + (b4 & 0x3f); + if (b1 < 0x10000 || b1 > Character.MAX_CODE_POINT || (b2 & 0xc0) != 0x80 || (b3 & 0xc0) != 0x80 + || (b4 & 0xc0) != 0x80) { + throw new IllegalArgumentException(); + } + } else { + if (index + 1 >= length) { + throw new IllegalArgumentException(); + } + int b2 = bytes[index++] & 0xff; + int b3 = bytes[index++] & 0xff; + b1 = ((b1 & 0xf) << 12) + ((b2 & 0x3f) << 6) + (b3 & 0x3f); + if (b1 < 0x800 || (b2 & 0xc0) != 0x80 || (b3 & 0xc0) != 0x80) { + throw new IllegalArgumentException(); + } + } + } else { + if (index >= length) { + throw new IllegalArgumentException(); + } + int b2 = bytes[index++] & 0xff; + b1 = ((b1 & 0x1f) << 6) + (b2 & 0x3f); + if (b1 < 0x80 || (b2 & 0xc0) != 0x80) { + throw new IllegalArgumentException(); + } + } + } + return b1; + } + + @Override + char readHex() { + if (index + 3 >= length) { + throw new IllegalArgumentException(); + } + int ch; + try { + ch = Integer.parseInt(new String(bytes, index, 4, StandardCharsets.ISO_8859_1), 16); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(); + } + index += 4; + return (char) ch; + } + + @Override + public String toString() { + return new String(bytes, 0, index, StandardCharsets.UTF_8) + "[*]" + + new String(bytes, index, length, StandardCharsets.UTF_8); + } + +} diff --git a/h2/src/main/org/h2/util/json/JSONItemType.java b/h2/src/main/org/h2/util/json/JSONItemType.java new file mode 100644 index 0000000..696e67c --- /dev/null +++ b/h2/src/main/org/h2/util/json/JSONItemType.java @@ -0,0 +1,48 @@ +/* + * 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.util.json; + +/** + * JSON item type. + */ +public enum JSONItemType { + + /** + * Either {@link #ARRAY}, {@link #OBJECT}, or {@link #SCALAR}. + */ + VALUE, + + /** + * JSON array. + */ + ARRAY, + + /** + * JSON object. + */ + OBJECT, + + /** + * JSON scalar value: string, number, {@code true}, {@code false}, or + * {@code null}. + */ + SCALAR; + + /** + * Checks whether this item type includes the specified item type. + * + * @param type + * item type to check + * @return whether this item type includes the specified item type + */ + public boolean includes(JSONItemType type) { + if (type == null) { + throw new NullPointerException(); + } + return this == VALUE || this == type; + } + +} diff --git a/h2/src/main/org/h2/util/json/JSONNull.java b/h2/src/main/org/h2/util/json/JSONNull.java new file mode 100644 index 0000000..d5ea3ac --- /dev/null +++ b/h2/src/main/org/h2/util/json/JSONNull.java @@ -0,0 +1,26 @@ +/* + * 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.util.json; + +/** + * JSON null. + */ +public class JSONNull extends JSONValue { + + /** + * {@code null} value. + */ + public static final JSONNull NULL = new JSONNull(); + + private JSONNull() { + } + + @Override + public void addTo(JSONTarget target) { + target.valueNull(); + } + +} diff --git a/h2/src/main/org/h2/util/json/JSONNumber.java b/h2/src/main/org/h2/util/json/JSONNumber.java new file mode 100644 index 0000000..de998d6 --- /dev/null +++ b/h2/src/main/org/h2/util/json/JSONNumber.java @@ -0,0 +1,35 @@ +/* + * 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.util.json; + +import java.math.BigDecimal; + +/** + * JSON number. + */ +public class JSONNumber extends JSONValue { + + private final BigDecimal value; + + JSONNumber(BigDecimal value) { + this.value = value; + } + + @Override + public void addTo(JSONTarget target) { + target.valueNumber(value); + } + + /** + * Returns the value. + * + * @return the value + */ + public BigDecimal getBigDecimal() { + return value; + } + +} diff --git a/h2/src/main/org/h2/util/json/JSONObject.java b/h2/src/main/org/h2/util/json/JSONObject.java new file mode 100644 index 0000000..2f3565d --- /dev/null +++ b/h2/src/main/org/h2/util/json/JSONObject.java @@ -0,0 +1,69 @@ +/* + * 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.util.json; + +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.ArrayList; +import java.util.Map.Entry; + +/** + * JSON object. + */ +public class JSONObject extends JSONValue { + + private final ArrayList> members = new ArrayList<>(); + + JSONObject() { + } + + /** + * Add a key-value pair. + * + * @param name the key + * @param value the value + */ + void addMember(String name, JSONValue value) { + members.add(new SimpleImmutableEntry<>(name, value)); + } + + @Override + public void addTo(JSONTarget target) { + target.startObject(); + for (SimpleImmutableEntry member : members) { + target.member(member.getKey()); + member.getValue().addTo(target); + } + target.endObject(); + } + + /** + * Returns the value. + * + * @return the value + */ + @SuppressWarnings("unchecked") + public Entry[] getMembers() { + return members.toArray(new Entry[0]); + } + + /** + * Returns value of the first member with the specified name. + * + * @param name + * name of the member + * @return value of the first member with the specified name, or + * {@code null} + */ + public JSONValue getFirst(String name) { + for (SimpleImmutableEntry entry : members) { + if (name.equals(entry.getKey())) { + return entry.getValue(); + } + } + return null; + } + +} diff --git a/h2/src/main/org/h2/util/json/JSONString.java b/h2/src/main/org/h2/util/json/JSONString.java new file mode 100644 index 0000000..98659d1 --- /dev/null +++ b/h2/src/main/org/h2/util/json/JSONString.java @@ -0,0 +1,33 @@ +/* + * 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.util.json; + +/** + * JSON string. + */ +public class JSONString extends JSONValue { + + private final String value; + + JSONString(String value) { + this.value = value; + } + + @Override + public void addTo(JSONTarget target) { + target.valueString(value); + } + + /** + * Returns the value. + * + * @return the value + */ + public String getString() { + return value; + } + +} diff --git a/h2/src/main/org/h2/util/json/JSONStringSource.java b/h2/src/main/org/h2/util/json/JSONStringSource.java new file mode 100644 index 0000000..b6ff80e --- /dev/null +++ b/h2/src/main/org/h2/util/json/JSONStringSource.java @@ -0,0 +1,161 @@ +/* + * 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.util.json; + +import java.math.BigDecimal; + +import org.h2.util.StringUtils; + +/** + * JSON string source. + */ +public final class JSONStringSource extends JSONTextSource { + + /** + * Parses source string to a specified target. + * + * @param string + * source + * @param target + * target + * @param + * the type of the result + * @return the result of the target + */ + public static R parse(String string, JSONTarget target) { + new JSONStringSource(string, target).parse(); + return target.getResult(); + } + + /** + * Normalizes textual JSON representation. + * + * @param string + * source representation + * @return normalized representation + */ + public static byte[] normalize(String string) { + return parse(string, new JSONByteArrayTarget()); + } + + private final String string; + + private final int length; + + private int index; + + JSONStringSource(String string, JSONTarget target) { + super(target); + this.string = string; + this.length = string.length(); + if (length == 0) { + throw new IllegalArgumentException(); + } + // Ignore BOM + if (string.charAt(index) == '\uFEFF') { + index++; + } + } + + @Override + int nextCharAfterWhitespace() { + int index = this.index; + while (index < length) { + char ch = string.charAt(index++); + switch (ch) { + case '\t': + case '\n': + case '\r': + case ' ': + break; + default: + this.index = index; + return ch; + } + } + return -1; + } + + @Override + void readKeyword1(String keyword) { + int l = keyword.length() - 1; + if (!string.regionMatches(index, keyword, 1, l)) { + throw new IllegalArgumentException(); + } + index += l; + } + + @Override + void parseNumber(boolean positive) { + int index = this.index; + int start = index - 1; + index = skipInt(index, positive); + l: if (index < length) { + char ch = string.charAt(index); + if (ch == '.') { + index = skipInt(index + 1, false); + if (index >= length) { + break l; + } + ch = string.charAt(index); + } + if (ch == 'E' || ch == 'e') { + if (++index >= length) { + throw new IllegalArgumentException(); + } + ch = string.charAt(index); + if (ch == '+' || ch == '-') { + index++; + } + index = skipInt(index, false); + } + } + target.valueNumber(new BigDecimal(string.substring(start, index))); + this.index = index; + } + + private int skipInt(int index, boolean hasInt) { + while (index < length) { + char ch = string.charAt(index); + if (ch >= '0' && ch <= '9') { + hasInt = true; + index++; + } else { + break; + } + } + if (!hasInt) { + throw new IllegalArgumentException(); + } + return index; + } + + @Override + int nextChar() { + if (index >= length) { + throw new IllegalArgumentException(); + } + return string.charAt(index++); + } + + @Override + char readHex() { + if (index + 3 >= length) { + throw new IllegalArgumentException(); + } + try { + return (char) Integer.parseInt(string.substring(index, index += 4), 16); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(); + } + } + + @Override + public String toString() { + return StringUtils.addAsterisk(string, index); + } + +} diff --git a/h2/src/main/org/h2/util/json/JSONStringTarget.java b/h2/src/main/org/h2/util/json/JSONStringTarget.java new file mode 100644 index 0000000..5646dcb --- /dev/null +++ b/h2/src/main/org/h2/util/json/JSONStringTarget.java @@ -0,0 +1,247 @@ +/* + * 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.util.json; + +import java.math.BigDecimal; + +import org.h2.util.ByteStack; + +/** + * JSON String target. + */ +public final class JSONStringTarget extends JSONTarget { + + /** + * The hex characters. + */ + static final char[] HEX = "0123456789abcdef".toCharArray(); + + /** + * A JSON object. + */ + static final byte OBJECT = 1; + + /** + * A JSON array. + */ + static final byte ARRAY = 2; + + /** + * Encodes a JSON string and appends it to the specified string builder. + * + * @param builder + * the string builder to append to + * @param s + * the string to encode + * @param asciiPrintableOnly + * whether all non-printable, non-ASCII characters, and {@code '} + * (single quote) characters should be escaped + * @return the specified string builder + */ + public static StringBuilder encodeString(StringBuilder builder, String s, boolean asciiPrintableOnly) { + builder.append('"'); + for (int i = 0, length = s.length(); i < length; i++) { + char c = s.charAt(i); + switch (c) { + case '\b': + builder.append("\\b"); + break; + case '\t': + builder.append("\\t"); + break; + case '\f': + builder.append("\\f"); + break; + case '\n': + builder.append("\\n"); + break; + case '\r': + builder.append("\\r"); + break; + case '"': + builder.append("\\\""); + break; + case '\'': + if (asciiPrintableOnly) { + builder.append("\\u0027"); + } else { + builder.append('\''); + } + break; + case '\\': + builder.append("\\\\"); + break; + default: + if (c < ' ') { + builder.append("\\u00") // + .append(HEX[c >>> 4 & 0xf]) // + .append(HEX[c & 0xf]); + } else if (!asciiPrintableOnly || c <= 0x7f) { + builder.append(c); + } else { + builder.append("\\u") // + .append(HEX[c >>> 12 & 0xf]) // + .append(HEX[c >>> 8 & 0xf]) // + .append(HEX[c >>> 4 & 0xf]) // + .append(HEX[c & 0xf]); + } + } + } + return builder.append('"'); + } + + private final StringBuilder builder; + + private final ByteStack stack; + + private final boolean asciiPrintableOnly; + + private boolean needSeparator; + + private boolean afterName; + + /** + * Creates new instance of JSON String target. + */ + public JSONStringTarget() { + this(false); + } + + /** + * Creates new instance of JSON String target. + * + * @param asciiPrintableOnly + * whether all non-printable, non-ASCII characters, and {@code '} + * (single quote) characters should be escaped + */ + public JSONStringTarget(boolean asciiPrintableOnly) { + builder = new StringBuilder(); + stack = new ByteStack(); + this.asciiPrintableOnly = asciiPrintableOnly; + } + + @Override + public void startObject() { + beforeValue(); + afterName = false; + stack.push(OBJECT); + builder.append('{'); + } + + @Override + public void endObject() { + if (afterName || stack.poll(-1) != OBJECT) { + throw new IllegalStateException(); + } + builder.append('}'); + afterValue(); + } + + @Override + public void startArray() { + beforeValue(); + afterName = false; + stack.push(ARRAY); + builder.append('['); + } + + @Override + public void endArray() { + if (stack.poll(-1) != ARRAY) { + throw new IllegalStateException(); + } + builder.append(']'); + afterValue(); + } + + @Override + public void member(String name) { + if (afterName || stack.peek(-1) != OBJECT) { + throw new IllegalStateException(); + } + afterName = true; + beforeValue(); + encodeString(builder, name, asciiPrintableOnly).append(':'); + } + + @Override + public void valueNull() { + beforeValue(); + builder.append("null"); + afterValue(); + } + + @Override + public void valueFalse() { + beforeValue(); + builder.append("false"); + afterValue(); + } + + @Override + public void valueTrue() { + beforeValue(); + builder.append("true"); + afterValue(); + } + + @Override + public void valueNumber(BigDecimal number) { + beforeValue(); + String s = number.toString(); + int index = s.indexOf('E'); + if (index >= 0 && s.charAt(++index) == '+') { + builder.append(s, 0, index).append(s, index + 1, s.length()); + } else { + builder.append(s); + } + afterValue(); + } + + @Override + public void valueString(String string) { + beforeValue(); + encodeString(builder, string, asciiPrintableOnly); + afterValue(); + } + + private void beforeValue() { + if (!afterName && stack.peek(-1) == OBJECT) { + throw new IllegalStateException(); + } + if (needSeparator) { + if (stack.isEmpty()) { + throw new IllegalStateException(); + } + needSeparator = false; + builder.append(','); + } + } + + private void afterValue() { + needSeparator = true; + afterName = false; + } + + @Override + public boolean isPropertyExpected() { + return !afterName && stack.peek(-1) == OBJECT; + } + + @Override + public boolean isValueSeparatorExpected() { + return needSeparator; + } + + @Override + public String getResult() { + if (!stack.isEmpty() || builder.length() == 0) { + throw new IllegalStateException(); + } + return builder.toString(); + } + +} diff --git a/h2/src/main/org/h2/util/json/JSONTarget.java b/h2/src/main/org/h2/util/json/JSONTarget.java new file mode 100644 index 0000000..921857f --- /dev/null +++ b/h2/src/main/org/h2/util/json/JSONTarget.java @@ -0,0 +1,105 @@ +/* + * 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.util.json; + +import java.math.BigDecimal; + +/** + * Abstract JSON output target. + * + * @param + * the type of the result + */ +public abstract class JSONTarget { + + /** + * Start of an object. + */ + public abstract void startObject(); + + /** + * End of the current object. + */ + public abstract void endObject(); + + /** + * Start of an array. + */ + public abstract void startArray(); + + /** + * End of the current array. + */ + public abstract void endArray(); + + /** + * Name of a member. + * + * @param name + * the name + */ + public abstract void member(String name); + + /** + * Parse "null". + * + * {@code null} value. + */ + public abstract void valueNull(); + + /** + * Parse "false". + * + * {@code false} value. + */ + public abstract void valueFalse(); + + /** + * Parse "true". + * + * {@code true} value. + */ + public abstract void valueTrue(); + + /** + * A number value. + * + * @param number + * the number + */ + public abstract void valueNumber(BigDecimal number); + + /** + * A string value. + * + * @param string + * the string + */ + public abstract void valueString(String string); + + /** + * Returns whether member's name or the end of the current object is + * expected. + * + * @return {@code true} if it is, {@code false} otherwise + */ + public abstract boolean isPropertyExpected(); + + /** + * Returns whether value separator expected before the next member or value. + * + * @return {@code true} if it is, {@code false} otherwise + */ + public abstract boolean isValueSeparatorExpected(); + + /** + * Returns the result. + * + * @return the result + */ + public abstract R getResult(); + +} diff --git a/h2/src/main/org/h2/util/json/JSONTextSource.java b/h2/src/main/org/h2/util/json/JSONTextSource.java new file mode 100644 index 0000000..e504514 --- /dev/null +++ b/h2/src/main/org/h2/util/json/JSONTextSource.java @@ -0,0 +1,216 @@ +/* + * 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.util.json; + +/** + * JSON text source. + */ +public abstract class JSONTextSource { + + /** + * The output. + */ + final JSONTarget target; + + private final StringBuilder builder; + + JSONTextSource(JSONTarget target) { + this.target = target; + builder = new StringBuilder(); + } + + /** + * Parse the text and write it to the output. + */ + final void parse() { + boolean comma = false; + for (int ch; (ch = nextCharAfterWhitespace()) >= 0;) { + if (ch == '}' || ch == ']') { + if (comma) { + throw new IllegalArgumentException(); + } + if (ch == '}') { + target.endObject(); + } else { + target.endArray(); + } + continue; + } + if (ch == ',') { + if (comma || !target.isValueSeparatorExpected()) { + throw new IllegalArgumentException(); + } + comma = true; + continue; + } + if (comma != target.isValueSeparatorExpected()) { + throw new IllegalArgumentException(); + } + comma = false; + switch (ch) { + case 'f': + readKeyword1("false"); + target.valueFalse(); + break; + case 'n': + readKeyword1("null"); + target.valueNull(); + break; + case 't': + readKeyword1("true"); + target.valueTrue(); + break; + case '{': + target.startObject(); + break; + case '[': + target.startArray(); + break; + case '"': { + String s = readString(); + if (target.isPropertyExpected()) { + if (nextCharAfterWhitespace() != ':') { + throw new IllegalArgumentException(); + } + target.member(s); + } else { + target.valueString(s); + } + break; + } + case '-': + parseNumber(false); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + parseNumber(true); + break; + default: + throw new IllegalArgumentException(); + } + } + } + + /** + * Skip all whitespace characters, and get the next character. + * + * @return the character code + */ + abstract int nextCharAfterWhitespace(); + + /** + * Read the specified keyword, or (it there is no match), throw an + * IllegalArgumentException. + * + * @param keyword the expected keyword + */ + abstract void readKeyword1(String keyword); + + /** + * Parse a number. + * + * @param positive whether it needs to be positive + */ + abstract void parseNumber(boolean positive); + + /** + * Read the next character. + * + * @return the character code + */ + abstract int nextChar(); + + /** + * Read 4 hex characters (0-9, a-f, A-F), and return the Unicode character. + * + * @return the character + */ + abstract char readHex(); + + private String readString() { + builder.setLength(0); + boolean inSurrogate = false; + for (;;) { + int ch = nextChar(); + switch (ch) { + case '"': + if (inSurrogate) { + throw new IllegalArgumentException(); + } + return builder.toString(); + case '\\': + ch = nextChar(); + switch (ch) { + case '"': + case '/': + case '\\': + appendNonSurrogate((char) ch, inSurrogate); + break; + case 'b': + appendNonSurrogate('\b', inSurrogate); + break; + case 'f': + appendNonSurrogate('\f', inSurrogate); + break; + case 'n': + appendNonSurrogate('\n', inSurrogate); + break; + case 'r': + appendNonSurrogate('\r', inSurrogate); + break; + case 't': + appendNonSurrogate('\t', inSurrogate); + break; + case 'u': + inSurrogate = appendChar(readHex(), inSurrogate); + break; + default: + throw new IllegalArgumentException(); + } + break; + default: + if (Character.isBmpCodePoint(ch)) { + inSurrogate = appendChar((char) ch, inSurrogate); + } else { + if (inSurrogate) { + throw new IllegalArgumentException(); + } + builder.appendCodePoint(ch); + inSurrogate = false; + } + } + } + } + + private void appendNonSurrogate(char ch, boolean inSurrogate) { + if (inSurrogate) { + throw new IllegalArgumentException(); + } + builder.append(ch); + } + + private boolean appendChar(char ch, boolean inSurrogate) { + if (inSurrogate != Character.isLowSurrogate(ch)) { + throw new IllegalArgumentException(); + } + if (inSurrogate) { + inSurrogate = false; + } else if (Character.isHighSurrogate(ch)) { + inSurrogate = true; + } + builder.append(ch); + return inSurrogate; + } + +} diff --git a/h2/src/main/org/h2/util/json/JSONValidationTarget.java b/h2/src/main/org/h2/util/json/JSONValidationTarget.java new file mode 100644 index 0000000..04b880a --- /dev/null +++ b/h2/src/main/org/h2/util/json/JSONValidationTarget.java @@ -0,0 +1,20 @@ +/* + * 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.util.json; + +/** + * JSON validation target. + */ +public abstract class JSONValidationTarget extends JSONTarget { + + /** + * @return JSON item type of the top-level item, may not return + * {@link JSONItemType#VALUE} + */ + @Override + public abstract JSONItemType getResult(); + +} diff --git a/h2/src/main/org/h2/util/json/JSONValidationTargetWithUniqueKeys.java b/h2/src/main/org/h2/util/json/JSONValidationTargetWithUniqueKeys.java new file mode 100644 index 0000000..1f5b9ad --- /dev/null +++ b/h2/src/main/org/h2/util/json/JSONValidationTargetWithUniqueKeys.java @@ -0,0 +1,157 @@ +/* + * 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.util.json; + +import java.math.BigDecimal; +import java.util.ArrayDeque; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; + +/** + * JSON validation target with unique keys. + */ +public final class JSONValidationTargetWithUniqueKeys extends JSONValidationTarget { + + private final ArrayDeque stack; + + private final ArrayDeque names; + + private boolean needSeparator; + + private String memberName; + + private JSONItemType type; + + /** + * Creates new instance of JSON validation target with unique keys. + */ + public JSONValidationTargetWithUniqueKeys() { + stack = new ArrayDeque<>(); + names = new ArrayDeque<>(); + } + + @Override + public void startObject() { + beforeValue(); + names.push(memberName != null ? memberName : ""); + memberName = null; + stack.push(new HashSet<>()); + } + + @Override + public void endObject() { + if (memberName != null) { + throw new IllegalStateException(); + } + if (!(stack.poll() instanceof HashSet)) { + throw new IllegalStateException(); + } + memberName = names.pop(); + afterValue(JSONItemType.OBJECT); + } + + @Override + public void startArray() { + beforeValue(); + names.push(memberName != null ? memberName : ""); + memberName = null; + stack.push(Collections.emptyList()); + } + + @Override + public void endArray() { + if (!(stack.poll() instanceof List)) { + throw new IllegalStateException(); + } + memberName = names.pop(); + afterValue(JSONItemType.ARRAY); + } + + @Override + public void member(String name) { + if (memberName != null || !(stack.peek() instanceof HashSet)) { + throw new IllegalStateException(); + } + memberName = name; + beforeValue(); + } + + @Override + public void valueNull() { + beforeValue(); + afterValue(JSONItemType.SCALAR); + } + + @Override + public void valueFalse() { + beforeValue(); + afterValue(JSONItemType.SCALAR); + } + + @Override + public void valueTrue() { + beforeValue(); + afterValue(JSONItemType.SCALAR); + } + + @Override + public void valueNumber(BigDecimal number) { + beforeValue(); + afterValue(JSONItemType.SCALAR); + } + + @Override + public void valueString(String string) { + beforeValue(); + afterValue(JSONItemType.SCALAR); + } + + private void beforeValue() { + if (memberName == null && stack.peek() instanceof HashSet) { + throw new IllegalStateException(); + } + if (needSeparator) { + if (stack.isEmpty()) { + throw new IllegalStateException(); + } + needSeparator = false; + } + } + + @SuppressWarnings("unchecked") + private void afterValue(JSONItemType type) { + Object parent = stack.peek(); + if (parent == null) { + this.type = type; + } else if (parent instanceof HashSet) { + if (!((HashSet) parent).add(memberName)) { + throw new IllegalStateException(); + } + } + needSeparator = true; + memberName = null; + } + + @Override + public boolean isPropertyExpected() { + return memberName == null && stack.peek() instanceof HashSet; + } + + @Override + public boolean isValueSeparatorExpected() { + return needSeparator; + } + + @Override + public JSONItemType getResult() { + if (!stack.isEmpty() || type == null) { + throw new IllegalStateException(); + } + return type; + } + +} diff --git a/h2/src/main/org/h2/util/json/JSONValidationTargetWithoutUniqueKeys.java b/h2/src/main/org/h2/util/json/JSONValidationTargetWithoutUniqueKeys.java new file mode 100644 index 0000000..85d03a8 --- /dev/null +++ b/h2/src/main/org/h2/util/json/JSONValidationTargetWithoutUniqueKeys.java @@ -0,0 +1,143 @@ +/* + * 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.util.json; + +import java.math.BigDecimal; + +import org.h2.util.ByteStack; + +/** + * JSON validation target without unique keys. + */ +public final class JSONValidationTargetWithoutUniqueKeys extends JSONValidationTarget { + + private static final byte OBJECT = 1; + + private static final byte ARRAY = 2; + + private JSONItemType type; + + private final ByteStack stack; + + private boolean needSeparator; + + private boolean afterName; + + /** + * Creates new instance of JSON validation target without unique keys. + */ + public JSONValidationTargetWithoutUniqueKeys() { + stack = new ByteStack(); + } + + @Override + public void startObject() { + beforeValue(); + afterName = false; + stack.push(OBJECT); + } + + @Override + public void endObject() { + if (afterName || stack.poll(-1) != OBJECT) { + throw new IllegalStateException(); + } + afterValue(JSONItemType.OBJECT); + } + + @Override + public void startArray() { + beforeValue(); + afterName = false; + stack.push(ARRAY); + } + + @Override + public void endArray() { + if (stack.poll(-1) != ARRAY) { + throw new IllegalStateException(); + } + afterValue(JSONItemType.ARRAY); + } + + @Override + public void member(String name) { + if (afterName || stack.peek(-1) != OBJECT) { + throw new IllegalStateException(); + } + afterName = true; + beforeValue(); + } + + @Override + public void valueNull() { + beforeValue(); + afterValue(JSONItemType.SCALAR); + } + + @Override + public void valueFalse() { + beforeValue(); + afterValue(JSONItemType.SCALAR); + } + + @Override + public void valueTrue() { + beforeValue(); + afterValue(JSONItemType.SCALAR); + } + + @Override + public void valueNumber(BigDecimal number) { + beforeValue(); + afterValue(JSONItemType.SCALAR); + } + + @Override + public void valueString(String string) { + beforeValue(); + afterValue(JSONItemType.SCALAR); + } + + private void beforeValue() { + if (!afterName && stack.peek(-1) == OBJECT) { + throw new IllegalStateException(); + } + if (needSeparator) { + if (stack.isEmpty()) { + throw new IllegalStateException(); + } + needSeparator = false; + } + } + + private void afterValue(JSONItemType type) { + needSeparator = true; + afterName = false; + if (stack.isEmpty()) { + this.type = type; + } + } + + @Override + public boolean isPropertyExpected() { + return !afterName && stack.peek(-1) == OBJECT; + } + + @Override + public boolean isValueSeparatorExpected() { + return needSeparator; + } + + @Override + public JSONItemType getResult() { + if (!stack.isEmpty() || type == null) { + throw new IllegalStateException(); + } + return type; + } + +} diff --git a/h2/src/main/org/h2/util/json/JSONValue.java b/h2/src/main/org/h2/util/json/JSONValue.java new file mode 100644 index 0000000..89bfbf4 --- /dev/null +++ b/h2/src/main/org/h2/util/json/JSONValue.java @@ -0,0 +1,31 @@ +/* + * 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.util.json; + +/** + * JSON value. + */ +public abstract class JSONValue { + + JSONValue() { + } + + /** + * Appends this value to the specified target. + * + * @param target + * the target + */ + public abstract void addTo(JSONTarget target); + + @Override + public final String toString() { + JSONStringTarget target = new JSONStringTarget(); + addTo(target); + return target.getResult(); + } + +} diff --git a/h2/src/main/org/h2/util/json/JSONValueTarget.java b/h2/src/main/org/h2/util/json/JSONValueTarget.java new file mode 100644 index 0000000..2df6962 --- /dev/null +++ b/h2/src/main/org/h2/util/json/JSONValueTarget.java @@ -0,0 +1,155 @@ +/* + * 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.util.json; + +import java.math.BigDecimal; +import java.util.ArrayDeque; + +/** + * JSON value target. + */ +public final class JSONValueTarget extends JSONTarget { + + private final ArrayDeque stack; + + private final ArrayDeque names; + + private boolean needSeparator; + + private String memberName; + + private JSONValue result; + + /** + * Creates new instance of JSON value target. + */ + public JSONValueTarget() { + stack = new ArrayDeque<>(); + names = new ArrayDeque<>(); + } + + @Override + public void startObject() { + beforeValue(); + names.push(memberName != null ? memberName : ""); + memberName = null; + stack.push(new JSONObject()); + } + + @Override + public void endObject() { + if (memberName != null) { + throw new IllegalStateException(); + } + JSONValue value = stack.poll(); + if (!(value instanceof JSONObject)) { + throw new IllegalStateException(); + } + memberName = names.pop(); + afterValue(value); + } + + @Override + public void startArray() { + beforeValue(); + names.push(memberName != null ? memberName : ""); + memberName = null; + stack.push(new JSONArray()); + } + + @Override + public void endArray() { + JSONValue value = stack.poll(); + if (!(value instanceof JSONArray)) { + throw new IllegalStateException(); + } + memberName = names.pop(); + afterValue(value); + } + + @Override + public void member(String name) { + if (memberName != null || !(stack.peek() instanceof JSONObject)) { + throw new IllegalStateException(); + } + memberName = name; + beforeValue(); + } + + @Override + public void valueNull() { + beforeValue(); + afterValue(JSONNull.NULL); + } + + @Override + public void valueFalse() { + beforeValue(); + afterValue(JSONBoolean.FALSE); + } + + @Override + public void valueTrue() { + beforeValue(); + afterValue(JSONBoolean.TRUE); + } + + @Override + public void valueNumber(BigDecimal number) { + beforeValue(); + afterValue(new JSONNumber(number)); + } + + @Override + public void valueString(String string) { + beforeValue(); + afterValue(new JSONString(string)); + } + + private void beforeValue() { + if (memberName == null && stack.peek() instanceof JSONObject) { + throw new IllegalStateException(); + } + if (needSeparator) { + if (stack.isEmpty()) { + throw new IllegalStateException(); + } + needSeparator = false; + } + } + + private void afterValue(JSONValue value) { + JSONValue parent = stack.peek(); + if (parent == null) { + result = value; + } else if (parent instanceof JSONObject) { + ((JSONObject) parent).addMember(memberName, value); + } else { + ((JSONArray) parent).addElement(value); + } + needSeparator = true; + memberName = null; + } + + @Override + public boolean isPropertyExpected() { + return memberName == null && stack.peek() instanceof JSONObject; + } + + @Override + public boolean isValueSeparatorExpected() { + return needSeparator; + } + + @Override + public JSONValue getResult() { + if (!stack.isEmpty() || result == null) { + throw new IllegalStateException(); + } + return result; + } + +} diff --git a/h2/src/main/org/h2/util/json/JsonConstructorUtils.java b/h2/src/main/org/h2/util/json/JsonConstructorUtils.java new file mode 100644 index 0000000..b05f813 --- /dev/null +++ b/h2/src/main/org/h2/util/json/JsonConstructorUtils.java @@ -0,0 +1,105 @@ +/* + * 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.util.json; + +import java.io.ByteArrayOutputStream; + +import org.h2.message.DbException; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueJson; +import org.h2.value.ValueNull; + +/** + * Utilities for JSON constructors. + */ +public final class JsonConstructorUtils { + + /** + * The ABSENT ON NULL flag. + */ + public static final int JSON_ABSENT_ON_NULL = 1; + + /** + * The WITH UNIQUE KEYS flag. + */ + public static final int JSON_WITH_UNIQUE_KEYS = 2; + + private JsonConstructorUtils() { + } + + /** + * Appends a value to a JSON object in the specified string builder. + * + * @param baos + * the output stream to append to + * @param key + * the name of the property + * @param value + * the value of the property + */ + public static void jsonObjectAppend(ByteArrayOutputStream baos, String key, Value value) { + if (baos.size() > 1) { + baos.write(','); + } + JSONByteArrayTarget.encodeString(baos, key).write(':'); + byte[] b = value.convertTo(TypeInfo.TYPE_JSON).getBytesNoCopy(); + baos.write(b, 0, b.length); + } + + /** + * Appends trailing closing brace to the specified string builder with a + * JSON object, validates it, and converts to a JSON value. + * + * @param baos + * the output stream with the object + * @param flags + * the flags ({@link #JSON_WITH_UNIQUE_KEYS}) + * @return the JSON value + * @throws DbException + * if {@link #JSON_WITH_UNIQUE_KEYS} is specified and keys are + * not unique + */ + public static Value jsonObjectFinish(ByteArrayOutputStream baos, int flags) { + baos.write('}'); + byte[] result = baos.toByteArray(); + if ((flags & JSON_WITH_UNIQUE_KEYS) != 0) { + try { + JSONBytesSource.parse(result, new JSONValidationTargetWithUniqueKeys()); + } catch (RuntimeException ex) { + String s = JSONBytesSource.parse(result, new JSONStringTarget()); + throw DbException.getInvalidValueException("JSON WITH UNIQUE KEYS", + s.length() < 128 ? result : s.substring(0, 128) + "..."); + } + } + return ValueJson.getInternal(result); + } + + /** + * Appends a value to a JSON array in the specified output stream. + * + * @param baos + * the output stream to append to + * @param value + * the value + * @param flags + * the flags ({@link #JSON_ABSENT_ON_NULL}) + */ + public static void jsonArrayAppend(ByteArrayOutputStream baos, Value value, int flags) { + if (value == ValueNull.INSTANCE) { + if ((flags & JSON_ABSENT_ON_NULL) != 0) { + return; + } + value = ValueJson.NULL; + } + if (baos.size() > 1) { + baos.write(','); + } + byte[] b = value.convertTo(TypeInfo.TYPE_JSON).getBytesNoCopy(); + baos.write(b, 0, b.length); + } + +} diff --git a/h2/src/main/org/h2/util/json/package.html b/h2/src/main/org/h2/util/json/package.html new file mode 100644 index 0000000..c34f97e --- /dev/null +++ b/h2/src/main/org/h2/util/json/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Internal utility classes for JSON data type. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/util/package.html b/h2/src/main/org/h2/util/package.html new file mode 100644 index 0000000..fc268b5 --- /dev/null +++ b/h2/src/main/org/h2/util/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Internal utility classes. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/value/CaseInsensitiveConcurrentMap.java b/h2/src/main/org/h2/value/CaseInsensitiveConcurrentMap.java new file mode 100644 index 0000000..838366e --- /dev/null +++ b/h2/src/main/org/h2/value/CaseInsensitiveConcurrentMap.java @@ -0,0 +1,46 @@ +/* + * 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.value; + +import java.util.concurrent.ConcurrentHashMap; + +import org.h2.util.StringUtils; + +/** + * A concurrent hash map with case-insensitive string keys. + * + * @param the value type + */ +public class CaseInsensitiveConcurrentMap extends ConcurrentHashMap { + + private static final long serialVersionUID = 1L; + + @Override + public V get(Object key) { + return super.get(StringUtils.toUpperEnglish((String) key)); + } + + @Override + public V put(String key, V value) { + return super.put(StringUtils.toUpperEnglish(key), value); + } + + @Override + public V putIfAbsent(String key, V value) { + return super.putIfAbsent(StringUtils.toUpperEnglish(key), value); + } + + @Override + public boolean containsKey(Object key) { + return super.containsKey(StringUtils.toUpperEnglish((String) key)); + } + + @Override + public V remove(Object key) { + return super.remove(StringUtils.toUpperEnglish((String) key)); + } + +} diff --git a/h2/src/main/org/h2/value/CaseInsensitiveMap.java b/h2/src/main/org/h2/value/CaseInsensitiveMap.java new file mode 100644 index 0000000..230ac7d --- /dev/null +++ b/h2/src/main/org/h2/value/CaseInsensitiveMap.java @@ -0,0 +1,61 @@ +/* + * 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.value; + +import java.util.HashMap; +import org.h2.util.StringUtils; + +/** + * A hash map with a case-insensitive string key. + * + * @param the value type + */ +public class CaseInsensitiveMap extends HashMap { + + private static final long serialVersionUID = 1L; + + /** + * Creates new instance of case-insensitive map. + */ + public CaseInsensitiveMap() { + } + + /** + * Creates new instance of case-insensitive map with specified initial + * capacity. + * + * @param initialCapacity the initial capacity + */ + public CaseInsensitiveMap(int initialCapacity) { + super(initialCapacity); + } + + @Override + public V get(Object key) { + return super.get(StringUtils.toUpperEnglish((String) key)); + } + + @Override + public V put(String key, V value) { + return super.put(StringUtils.toUpperEnglish(key), value); + } + + @Override + public V putIfAbsent(String key, V value) { + return super.putIfAbsent(StringUtils.toUpperEnglish(key), value); + } + + @Override + public boolean containsKey(Object key) { + return super.containsKey(StringUtils.toUpperEnglish((String) key)); + } + + @Override + public V remove(Object key) { + return super.remove(StringUtils.toUpperEnglish((String) key)); + } + +} diff --git a/h2/src/main/org/h2/value/CharsetCollator.java b/h2/src/main/org/h2/value/CharsetCollator.java new file mode 100644 index 0000000..a824924 --- /dev/null +++ b/h2/src/main/org/h2/value/CharsetCollator.java @@ -0,0 +1,85 @@ +/* + * 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.value; + +import java.nio.charset.Charset; +import java.text.CollationKey; +import java.text.Collator; +import java.util.Comparator; +import java.util.Locale; + +import org.h2.util.Bits; + +/** + * The charset collator sorts strings according to the order in the given charset. + */ +public class CharsetCollator extends Collator { + + /** + * The comparator used to compare byte arrays. + */ + static final Comparator COMPARATOR = Bits::compareNotNullSigned; + + private final Charset charset; + + public CharsetCollator(Charset charset) { + this.charset = charset; + } + + public Charset getCharset() { + return charset; + } + + @Override + public int compare(String source, String target) { + return COMPARATOR.compare(toBytes(source), toBytes(target)); + } + + /** + * Convert the source to bytes, using the character set. + * + * @param source the source + * @return the bytes + */ + byte[] toBytes(String source) { + if (getStrength() <= Collator.SECONDARY) { + // TODO perform case-insensitive comparison properly + source = source.toUpperCase(Locale.ROOT); + } + return source.getBytes(charset); + } + + @Override + public CollationKey getCollationKey(String source) { + return new CharsetCollationKey(source); + } + + @Override + public int hashCode() { + return 255; + } + + private class CharsetCollationKey extends CollationKey { + + private final byte[] bytes; + + CharsetCollationKey(String source) { + super(source); + bytes = toBytes(source); + } + + @Override + public int compareTo(CollationKey target) { + return COMPARATOR.compare(bytes, target.toByteArray()); + } + + @Override + public byte[] toByteArray() { + return bytes; + } + + } +} diff --git a/h2/src/main/org/h2/value/CompareMode.java b/h2/src/main/org/h2/value/CompareMode.java new file mode 100644 index 0000000..aeea652 --- /dev/null +++ b/h2/src/main/org/h2/value/CompareMode.java @@ -0,0 +1,283 @@ +/* + * 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.value; + +import java.nio.charset.Charset; +import java.text.Collator; +import java.util.Comparator; +import java.util.Locale; +import java.util.Objects; + +import org.h2.util.StringUtils; + +/** + * Instances of this class can compare strings. Case sensitive and case + * insensitive comparison is supported, and comparison using a collator. + */ +public class CompareMode implements Comparator { + + /** + * This constant means there is no collator set, and the default string + * comparison is to be used. + */ + public static final String OFF = "OFF"; + + /** + * This constant means the default collator should be used, even if ICU4J is + * in the classpath. + */ + public static final String DEFAULT = "DEFAULT_"; + + /** + * This constant means ICU4J should be used (this will fail if it is not in + * the classpath). + */ + public static final String ICU4J = "ICU4J_"; + + /** + * This constant means the charset specified should be used. + * This will fail if the specified charset does not exist. + */ + public static final String CHARSET = "CHARSET_"; + + private static Locale[] LOCALES; + + private static volatile CompareMode lastUsed; + + private static final boolean CAN_USE_ICU4J; + + static { + boolean b = false; + try { + Class.forName("com.ibm.icu.text.Collator"); + b = true; + } catch (Exception e) { + // ignore + } + CAN_USE_ICU4J = b; + } + + private final String name; + private final int strength; + + protected CompareMode(String name, int strength) { + this.name = name; + this.strength = strength; + } + + /** + * Create a new compare mode with the given collator and strength. If + * required, a new CompareMode is created, or if possible the last one is + * returned. A cache is used to speed up comparison when using a collator; + * CollationKey objects are cached. + * + * @param name the collation name or null + * @param strength the collation strength + * @return the compare mode + */ + public static CompareMode getInstance(String name, int strength) { + CompareMode last = lastUsed; + if (last != null && Objects.equals(last.name, name) && last.strength == strength) { + return last; + } + if (name == null || name.equals(OFF)) { + last = new CompareMode(name, strength); + } else { + boolean useICU4J; + if (name.startsWith(ICU4J)) { + useICU4J = true; + name = name.substring(ICU4J.length()); + } else if (name.startsWith(DEFAULT)) { + useICU4J = false; + name = name.substring(DEFAULT.length()); + } else if (name.startsWith(CHARSET)) { + useICU4J = false; + } else { + useICU4J = CAN_USE_ICU4J; + } + if (useICU4J) { + last = new CompareModeIcu4J(name, strength); + } else { + last = new CompareModeDefault(name, strength); + } + } + lastUsed = last; + return last; + } + + /** + * Returns available locales for collations. + * + * @param onlyIfInitialized + * if {@code true}, returns {@code null} when locales are not yet + * initialized + * @return available locales for collations. + */ + public static Locale[] getCollationLocales(boolean onlyIfInitialized) { + Locale[] locales = LOCALES; + if (locales == null && !onlyIfInitialized) { + LOCALES = locales = Collator.getAvailableLocales(); + } + return locales; + } + + /** + * Compare two characters in a string. + * + * @param a the first string + * @param ai the character index in the first string + * @param b the second string + * @param bi the character index in the second string + * @param ignoreCase true if a case-insensitive comparison should be made + * @return true if the characters are equals + */ + public boolean equalsChars(String a, int ai, String b, int bi, boolean ignoreCase) { + char ca = a.charAt(ai); + char cb = b.charAt(bi); + if (ca == cb) { + return true; + } + if (ignoreCase) { + if (Character.toUpperCase(ca) == Character.toUpperCase(cb) + || Character.toLowerCase(ca) == Character.toLowerCase(cb)) { + return true; + } + } + return false; + } + + /** + * Compare two strings. + * + * @param a the first string + * @param b the second string + * @param ignoreCase true if a case-insensitive comparison should be made + * @return -1 if the first string is 'smaller', 1 if the second string is + * smaller, and 0 if they are equal + */ + public int compareString(String a, String b, boolean ignoreCase) { + if (ignoreCase) { + return a.compareToIgnoreCase(b); + } + return a.compareTo(b); + } + + /** + * Get the collation name. + * + * @param l the locale + * @return the name of the collation + */ + public static String getName(Locale l) { + Locale english = Locale.ENGLISH; + String name = l.getDisplayLanguage(english) + ' ' + + l.getDisplayCountry(english) + ' ' + l.getVariant(); + name = StringUtils.toUpperEnglish(name.trim().replace(' ', '_')); + return name; + } + + /** + * Compare name of the locale with the given name. The case of the name + * is ignored. + * + * @param locale the locale + * @param name the name + * @return true if they match + */ + static boolean compareLocaleNames(Locale locale, String name) { + return name.equalsIgnoreCase(locale.toString()) || name.equalsIgnoreCase(locale.toLanguageTag()) || + name.equalsIgnoreCase(getName(locale)); + } + + /** + * Get the collator object for the given language name or language / country + * combination. + * + * @param name the language name + * @return the collator + */ + public static Collator getCollator(String name) { + Collator result = null; + if (name.startsWith(ICU4J)) { + name = name.substring(ICU4J.length()); + } else if (name.startsWith(DEFAULT)) { + name = name.substring(DEFAULT.length()); + } else if (name.startsWith(CHARSET)) { + return new CharsetCollator(Charset.forName(name.substring(CHARSET.length()))); + } + int length = name.length(); + if (length == 2) { + Locale locale = new Locale(StringUtils.toLowerEnglish(name), ""); + if (compareLocaleNames(locale, name)) { + result = Collator.getInstance(locale); + } + } else if (length == 5) { + // LL_CC (language_country) + int idx = name.indexOf('_'); + if (idx >= 0) { + String language = StringUtils.toLowerEnglish(name.substring(0, idx)); + String country = name.substring(idx + 1); + Locale locale = new Locale(language, country); + if (compareLocaleNames(locale, name)) { + result = Collator.getInstance(locale); + } + } + } else if (name.indexOf('-') > 0) { + Locale locale = Locale.forLanguageTag(name); + if (!locale.getLanguage().isEmpty()) { + return Collator.getInstance(locale); + } + } + if (result == null) { + for (Locale locale : getCollationLocales(false)) { + if (compareLocaleNames(locale, name)) { + result = Collator.getInstance(locale); + break; + } + } + } + return result; + } + + public String getName() { + return name == null ? OFF : name; + } + + public int getStrength() { + return strength; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } else if (!(obj instanceof CompareMode)) { + return false; + } + CompareMode o = (CompareMode) obj; + if (!getName().equals(o.getName())) { + return false; + } + if (strength != o.strength) { + return false; + } + return true; + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + getName().hashCode(); + result = 31 * result + strength; + return result; + } + + @Override + public int compare(Value o1, Value o2) { + return o1.compareTo(o2, null, this); + } + +} diff --git a/h2/src/main/org/h2/value/CompareModeDefault.java b/h2/src/main/org/h2/value/CompareModeDefault.java new file mode 100644 index 0000000..fe4ac13 --- /dev/null +++ b/h2/src/main/org/h2/value/CompareModeDefault.java @@ -0,0 +1,78 @@ +/* + * 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.value; + +import java.text.CollationKey; +import java.text.Collator; + +import org.h2.engine.SysProperties; +import org.h2.message.DbException; +import org.h2.util.SmallLRUCache; + +/** + * The default implementation of CompareMode. It uses java.text.Collator. + */ +public class CompareModeDefault extends CompareMode { + + private final Collator collator; + private final SmallLRUCache collationKeys; + + private volatile CompareModeDefault caseInsensitive; + + protected CompareModeDefault(String name, int strength) { + super(name, strength); + collator = CompareMode.getCollator(name); + if (collator == null) { + throw DbException.getInternalError(name); + } + collator.setStrength(strength); + int cacheSize = SysProperties.COLLATOR_CACHE_SIZE; + if (cacheSize != 0) { + collationKeys = SmallLRUCache.newInstance(cacheSize); + } else { + collationKeys = null; + } + } + + @Override + public int compareString(String a, String b, boolean ignoreCase) { + if (ignoreCase && getStrength() > Collator.SECONDARY) { + CompareModeDefault i = caseInsensitive; + if (i == null) { + caseInsensitive = i = new CompareModeDefault(getName(), Collator.SECONDARY); + } + return i.compareString(a, b, false); + } + int comp; + if (collationKeys != null) { + CollationKey aKey = getKey(a); + CollationKey bKey = getKey(b); + comp = aKey.compareTo(bKey); + } else { + comp = collator.compare(a, b); + } + return comp; + } + + @Override + public boolean equalsChars(String a, int ai, String b, int bi, + boolean ignoreCase) { + return compareString(a.substring(ai, ai + 1), b.substring(bi, bi + 1), + ignoreCase) == 0; + } + + private CollationKey getKey(String a) { + synchronized (collationKeys) { + CollationKey key = collationKeys.get(a); + if (key == null) { + key = collator.getCollationKey(a); + collationKeys.put(a, key); + } + return key; + } + } + +} diff --git a/h2/src/main/org/h2/value/CompareModeIcu4J.java b/h2/src/main/org/h2/value/CompareModeIcu4J.java new file mode 100644 index 0000000..19312f8 --- /dev/null +++ b/h2/src/main/org/h2/value/CompareModeIcu4J.java @@ -0,0 +1,95 @@ +/* + * 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.value; + +import java.lang.reflect.Method; +import java.text.Collator; +import java.util.Comparator; +import java.util.Locale; + +import org.h2.message.DbException; +import org.h2.util.JdbcUtils; +import org.h2.util.StringUtils; + +/** + * An implementation of CompareMode that uses the ICU4J Collator. + */ +public class CompareModeIcu4J extends CompareMode { + + private final Comparator collator; + + private volatile CompareModeIcu4J caseInsensitive; + + protected CompareModeIcu4J(String name, int strength) { + super(name, strength); + collator = getIcu4jCollator(name, strength); + } + + @Override + public int compareString(String a, String b, boolean ignoreCase) { + if (ignoreCase && getStrength() > Collator.SECONDARY) { + CompareModeIcu4J i = caseInsensitive; + if (i == null) { + caseInsensitive = i = new CompareModeIcu4J(getName(), Collator.SECONDARY); + } + return i.compareString(a, b, false); + } + return collator.compare(a, b); + } + + @Override + public boolean equalsChars(String a, int ai, String b, int bi, + boolean ignoreCase) { + return compareString(a.substring(ai, ai + 1), b.substring(bi, bi + 1), + ignoreCase) == 0; + } + + @SuppressWarnings("unchecked") + private static Comparator getIcu4jCollator(String name, int strength) { + try { + Comparator result = null; + Class collatorClass = JdbcUtils.loadUserClass( + "com.ibm.icu.text.Collator"); + Method getInstanceMethod = collatorClass.getMethod( + "getInstance", Locale.class); + int length = name.length(); + if (length == 2) { + Locale locale = new Locale(StringUtils.toLowerEnglish(name), ""); + if (compareLocaleNames(locale, name)) { + result = (Comparator) getInstanceMethod.invoke(null, locale); + } + } else if (length == 5) { + // LL_CC (language_country) + int idx = name.indexOf('_'); + if (idx >= 0) { + String language = StringUtils.toLowerEnglish(name.substring(0, idx)); + String country = name.substring(idx + 1); + Locale locale = new Locale(language, country); + if (compareLocaleNames(locale, name)) { + result = (Comparator) getInstanceMethod.invoke(null, locale); + } + } + } + if (result == null) { + for (Locale locale : (Locale[]) collatorClass.getMethod( + "getAvailableLocales").invoke(null)) { + if (compareLocaleNames(locale, name)) { + result = (Comparator) getInstanceMethod.invoke(null, locale); + break; + } + } + } + if (result == null) { + throw DbException.getInvalidValueException("collator", name); + } + collatorClass.getMethod("setStrength", int.class).invoke(result, strength); + return result; + } catch (Exception e) { + throw DbException.convert(e); + } + } + +} diff --git a/h2/src/main/org/h2/value/DataType.java b/h2/src/main/org/h2/value/DataType.java new file mode 100644 index 0000000..29ec4fc --- /dev/null +++ b/h2/src/main/org/h2/value/DataType.java @@ -0,0 +1,826 @@ +/* + * 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.value; + +import java.sql.JDBCType; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.SQLType; +import java.sql.Types; +import java.util.HashMap; +import java.util.Map; + +import org.h2.api.ErrorCode; +import org.h2.api.H2Type; +import org.h2.api.IntervalQualifier; +import org.h2.engine.Constants; +import org.h2.engine.Mode; +import org.h2.message.DbException; +import org.h2.util.StringUtils; + +/** + * This class contains meta data information about data types, + * and can convert between Java objects and Values. + */ +public class DataType { + + /** + * The map of types. + */ + private static final HashMap TYPES_BY_NAME = new HashMap<>(128); + + /** + * Mapping from Value type numbers to DataType. + */ + static final DataType[] TYPES_BY_VALUE_TYPE = new DataType[Value.TYPE_COUNT]; + + /** + * The value type of this data type. + */ + public int type; + + /** + * The SQL type. + */ + public int sqlType; + + /** + * The minimum supported precision. + */ + public long minPrecision; + + /** + * The maximum supported precision. + */ + public long maxPrecision; + + /** + * The lowest possible scale. + */ + public int minScale; + + /** + * The highest possible scale. + */ + public int maxScale; + + /** + * The prefix required for the SQL literal representation. + */ + public String prefix; + + /** + * The suffix required for the SQL literal representation. + */ + public String suffix; + + /** + * The list of parameters used in the column definition. + */ + public String params; + + /** + * If this data type is case sensitive. + */ + public boolean caseSensitive; + + /** + * If the precision parameter is supported. + */ + public boolean supportsPrecision; + + /** + * If the scale parameter is supported. + */ + public boolean supportsScale; + + /** + * The default precision. + */ + public long defaultPrecision; + + /** + * The default scale. + */ + public int defaultScale; + + /** + * If precision and scale have non-standard default values. + */ + public boolean specialPrecisionScale; + + static { + DataType dataType = new DataType(); + dataType.defaultPrecision = dataType.maxPrecision = dataType.minPrecision = ValueNull.PRECISION; + add(Value.NULL, Types.NULL, dataType, "NULL"); + add(Value.CHAR, Types.CHAR, createString(true, true), + "CHARACTER", "CHAR", "NCHAR", "NATIONAL CHARACTER", "NATIONAL CHAR"); + add(Value.VARCHAR, Types.VARCHAR, createString(true, false), + "CHARACTER VARYING", "VARCHAR", "CHAR VARYING", + "NCHAR VARYING", "NATIONAL CHARACTER VARYING", "NATIONAL CHAR VARYING", + "VARCHAR2", "NVARCHAR", "NVARCHAR2", + "VARCHAR_CASESENSITIVE", "TID", + "LONGVARCHAR", "LONGNVARCHAR"); + 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"); + 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), + "BINARY VARYING", "VARBINARY", "RAW", "BYTEA", "LONG RAW", "LONGVARBINARY"); + add(Value.BLOB, Types.BLOB, createLob(false), + "BINARY LARGE OBJECT", "BLOB", "TINYBLOB", "MEDIUMBLOB", "LONGBLOB", "IMAGE"); + add(Value.BOOLEAN, Types.BOOLEAN, createNumeric(ValueBoolean.PRECISION, 0), "BOOLEAN", "BIT", "BOOL"); + add(Value.TINYINT, Types.TINYINT, createNumeric(ValueTinyint.PRECISION, 0), "TINYINT"); + add(Value.SMALLINT, Types.SMALLINT, createNumeric(ValueSmallint.PRECISION, 0), "SMALLINT", "INT2"); + add(Value.INTEGER, Types.INTEGER, createNumeric(ValueInteger.PRECISION, 0), + "INTEGER", "INT", "MEDIUMINT", "INT4", "SIGNED" + ); + add(Value.BIGINT, Types.BIGINT, createNumeric(ValueBigint.PRECISION, 0), + "BIGINT", "INT8", "LONG"); + dataType = new DataType(); + dataType.minPrecision = 1; + dataType.defaultPrecision = dataType.maxPrecision = Constants.MAX_NUMERIC_PRECISION; + dataType.defaultScale = ValueNumeric.DEFAULT_SCALE; + dataType.maxScale = ValueNumeric.MAXIMUM_SCALE; + dataType.minScale = 0; + dataType.params = "PRECISION,SCALE"; + dataType.supportsPrecision = true; + dataType.supportsScale = true; + add(Value.NUMERIC, Types.NUMERIC, dataType, "NUMERIC", "DECIMAL", "DEC"); + add(Value.REAL, Types.REAL, createNumeric(ValueReal.PRECISION, 0), "REAL", "FLOAT4"); + add(Value.DOUBLE, Types.DOUBLE, createNumeric(ValueDouble.PRECISION, 0), + "DOUBLE PRECISION", "DOUBLE", "FLOAT8"); + add(Value.DOUBLE, Types.FLOAT, createNumeric(ValueDouble.PRECISION, 0), "FLOAT"); + dataType = new DataType(); + dataType.minPrecision = 1; + dataType.defaultPrecision = dataType.maxPrecision = Constants.MAX_NUMERIC_PRECISION; + dataType.params = "PRECISION"; + dataType.supportsPrecision = true; + add(Value.DECFLOAT, Types.NUMERIC, dataType, "DECFLOAT"); + add(Value.DATE, Types.DATE, createDate(ValueDate.PRECISION, ValueDate.PRECISION, "DATE", false, 0, 0), "DATE"); + add(Value.TIME, Types.TIME, + createDate(ValueTime.MAXIMUM_PRECISION, ValueTime.DEFAULT_PRECISION, + "TIME", true, ValueTime.DEFAULT_SCALE, ValueTime.MAXIMUM_SCALE), + "TIME", "TIME WITHOUT TIME ZONE"); + add(Value.TIME_TZ, Types.TIME_WITH_TIMEZONE, + createDate(ValueTimeTimeZone.MAXIMUM_PRECISION, ValueTimeTimeZone.DEFAULT_PRECISION, + "TIME WITH TIME ZONE", true, ValueTime.DEFAULT_SCALE, ValueTime.MAXIMUM_SCALE), + "TIME WITH TIME ZONE"); + add(Value.TIMESTAMP, Types.TIMESTAMP, + createDate(ValueTimestamp.MAXIMUM_PRECISION, ValueTimestamp.DEFAULT_PRECISION, + "TIMESTAMP", true, ValueTimestamp.DEFAULT_SCALE, ValueTimestamp.MAXIMUM_SCALE), + "TIMESTAMP", "TIMESTAMP WITHOUT TIME ZONE", "DATETIME", "DATETIME2", "SMALLDATETIME"); + add(Value.TIMESTAMP_TZ, Types.TIMESTAMP_WITH_TIMEZONE, + createDate(ValueTimestampTimeZone.MAXIMUM_PRECISION, ValueTimestampTimeZone.DEFAULT_PRECISION, + "TIMESTAMP WITH TIME ZONE", true, ValueTimestamp.DEFAULT_SCALE, ValueTimestamp.MAXIMUM_SCALE), + "TIMESTAMP WITH TIME ZONE"); + for (int i = Value.INTERVAL_YEAR; i <= Value.INTERVAL_MINUTE_TO_SECOND; i++) { + addInterval(i); + } + add(Value.JAVA_OBJECT, Types.JAVA_OBJECT, createBinary(false), "JAVA_OBJECT", "OBJECT", "OTHER"); + dataType = createString(false, false); + dataType.supportsPrecision = false; + dataType.params = "ELEMENT [,...]"; + add(Value.ENUM, Types.OTHER, dataType, "ENUM"); + add(Value.GEOMETRY, Types.OTHER, createGeometry(), "GEOMETRY"); + add(Value.JSON, Types.OTHER, createString(true, false, "JSON '", "'"), "JSON"); + dataType = new DataType(); + dataType.prefix = dataType.suffix = "'"; + dataType.defaultPrecision = dataType.maxPrecision = dataType.minPrecision = ValueUuid.PRECISION; + add(Value.UUID, Types.BINARY, dataType, "UUID"); + dataType = new DataType(); + dataType.prefix = "ARRAY["; + dataType.suffix = "]"; + dataType.params = "CARDINALITY"; + dataType.supportsPrecision = true; + dataType.defaultPrecision = dataType.maxPrecision = Constants.MAX_ARRAY_CARDINALITY; + add(Value.ARRAY, Types.ARRAY, dataType, "ARRAY"); + dataType = new DataType(); + dataType.prefix = "ROW("; + dataType.suffix = ")"; + dataType.params = "NAME DATA_TYPE [,...]"; + add(Value.ROW, Types.OTHER, dataType, "ROW"); + } + + private static void addInterval(int type) { + IntervalQualifier qualifier = IntervalQualifier.valueOf(type - Value.INTERVAL_YEAR); + String name = qualifier.toString(); + DataType dataType = new DataType(); + dataType.prefix = "INTERVAL '"; + dataType.suffix = "' " + name; + dataType.supportsPrecision = true; + dataType.defaultPrecision = ValueInterval.DEFAULT_PRECISION; + dataType.minPrecision = 1; + dataType.maxPrecision = ValueInterval.MAXIMUM_PRECISION; + if (qualifier.hasSeconds()) { + dataType.supportsScale = true; + dataType.defaultScale = ValueInterval.DEFAULT_SCALE; + dataType.maxScale = ValueInterval.MAXIMUM_SCALE; + dataType.params = "PRECISION,SCALE"; + } else { + dataType.params = "PRECISION"; + } + add(type, Types.OTHER, dataType, ("INTERVAL " + name).intern()); + } + + private static void add(int type, int sqlType, DataType dataType, String... names) { + dataType.type = type; + dataType.sqlType = sqlType; + if (TYPES_BY_VALUE_TYPE[type] == null) { + TYPES_BY_VALUE_TYPE[type] = dataType; + } + for (String name : names) { + TYPES_BY_NAME.put(name, dataType); + } + } + + /** + * Create a numeric data type without parameters. + * + * @param precision precision + * @param scale scale + * @return data type + */ + public static DataType createNumeric(int precision, int scale) { + DataType dataType = new DataType(); + dataType.defaultPrecision = dataType.maxPrecision = dataType.minPrecision = precision; + dataType.defaultScale = dataType.maxScale = dataType.minScale = scale; + return dataType; + } + + /** + * Create a date-time data type. + * + * @param maxPrecision maximum supported precision + * @param precision default precision + * @param prefix the prefix for SQL literal representation + * @param supportsScale whether the scale parameter is supported + * @param scale default scale + * @param maxScale highest possible scale + * @return data type + */ + public static DataType createDate(int maxPrecision, int precision, String prefix, + boolean supportsScale, int scale, int maxScale) { + DataType dataType = new DataType(); + dataType.prefix = prefix + " '"; + dataType.suffix = "'"; + dataType.maxPrecision = maxPrecision; + dataType.defaultPrecision = dataType.minPrecision = precision; + if (supportsScale) { + dataType.params = "SCALE"; + dataType.supportsScale = true; + dataType.maxScale = maxScale; + dataType.defaultScale = scale; + } + return dataType; + } + + private static DataType createString(boolean caseSensitive, boolean fixedLength) { + return createString(caseSensitive, fixedLength, "'", "'"); + } + + private static DataType createBinary(boolean fixedLength) { + return createString(false, fixedLength, "X'", "'"); + } + + private static DataType createString(boolean caseSensitive, boolean fixedLength, String prefix, String suffix) { + DataType dataType = new DataType(); + dataType.prefix = prefix; + dataType.suffix = suffix; + dataType.params = "LENGTH"; + dataType.caseSensitive = caseSensitive; + dataType.supportsPrecision = true; + dataType.minPrecision = 1; + dataType.maxPrecision = Constants.MAX_STRING_LENGTH; + dataType.defaultPrecision = fixedLength ? 1 : Constants.MAX_STRING_LENGTH; + return dataType; + } + + private static DataType createLob(boolean clob) { + DataType t = clob ? createString(true, false) : createBinary(false); + t.maxPrecision = Long.MAX_VALUE; + t.defaultPrecision = Long.MAX_VALUE; + return t; + } + + private static DataType createGeometry() { + DataType dataType = new DataType(); + dataType.prefix = "'"; + dataType.suffix = "'"; + dataType.params = "TYPE,SRID"; + dataType.maxPrecision = Long.MAX_VALUE; + dataType.defaultPrecision = Long.MAX_VALUE; + return dataType; + } + + /** + * Get the data type object for the given value type. + * + * @param type the value type + * @return the data type object + */ + public static DataType getDataType(int type) { + if (type == Value.UNKNOWN) { + throw DbException.get(ErrorCode.UNKNOWN_DATA_TYPE_1, "?"); + } + if (type >= Value.NULL && type < Value.TYPE_COUNT) { + return TYPES_BY_VALUE_TYPE[type]; + } + return TYPES_BY_VALUE_TYPE[Value.NULL]; + } + + /** + * Convert a value type to a SQL type. + * + * @param type the type + * @return the SQL type + */ + public static int convertTypeToSQLType(TypeInfo type) { + int valueType = type.getValueType(); + switch (valueType) { + case Value.NUMERIC: + return type.getExtTypeInfo() != null ? Types.DECIMAL : Types.NUMERIC; + case Value.REAL: + case Value.DOUBLE: + if (type.getDeclaredPrecision() >= 0) { + return Types.FLOAT; + } + break; + } + return getDataType(valueType).sqlType; + } + + /** + * Convert a SQL type to a value type using SQL type name, in order to + * manage SQL type extension mechanism. + * + * @param sqlType the SQL type + * @param sqlTypeName the SQL type name + * @return the value type + */ + public static int convertSQLTypeToValueType(int sqlType, String sqlTypeName) { + switch (sqlType) { + case Types.BINARY: + if (sqlTypeName.equalsIgnoreCase("UUID")) { + return Value.UUID; + } + break; + case Types.OTHER: { + DataType type = TYPES_BY_NAME.get(StringUtils.toUpperEnglish(sqlTypeName)); + if (type != null) { + return type.type; + } + } + } + return convertSQLTypeToValueType(sqlType); + } + + /** + * Get the SQL type from the result set meta data for the given column. This + * method uses the SQL type and type name. + * + * @param meta the meta data + * @param columnIndex the column index (1, 2,...) + * @return the value type + * @throws SQLException on failure + */ + public static int getValueTypeFromResultSet(ResultSetMetaData meta, + int columnIndex) throws SQLException { + return convertSQLTypeToValueType( + meta.getColumnType(columnIndex), + meta.getColumnTypeName(columnIndex)); + } + + /** + * Check whether the specified column needs the binary representation. + * + * @param meta + * metadata + * @param column + * column index + * @return {@code true} if column needs the binary representation, + * {@code false} otherwise + * @throws SQLException + * on SQL exception + */ + public static boolean isBinaryColumn(ResultSetMetaData meta, int column) throws SQLException { + switch (meta.getColumnType(column)) { + case Types.BINARY: + if (meta.getColumnTypeName(column).equals("UUID")) { + break; + } + //$FALL-THROUGH$ + case Types.LONGVARBINARY: + case Types.VARBINARY: + case Types.JAVA_OBJECT: + case Types.BLOB: + return true; + } + return false; + } + + /** + * Convert a SQL type to a value type. + * + * @param sqlType the SQL type + * @return the value type + */ + public static int convertSQLTypeToValueType(SQLType sqlType) { + if (sqlType instanceof H2Type) { + return sqlType.getVendorTypeNumber(); + } else if (sqlType instanceof JDBCType) { + return convertSQLTypeToValueType(sqlType.getVendorTypeNumber()); + } else { + throw DbException.get(ErrorCode.UNKNOWN_DATA_TYPE_1, sqlType == null ? "" + : unknownSqlTypeToString(new StringBuilder(), sqlType).toString()); + } + } + + /** + * Convert a SQL type to a value type. + * + * @param sqlType the SQL type + * @return the value type + */ + public static int convertSQLTypeToValueType(int sqlType) { + switch (sqlType) { + case Types.CHAR: + case Types.NCHAR: + return Value.CHAR; + case Types.VARCHAR: + case Types.LONGVARCHAR: + case Types.NVARCHAR: + case Types.LONGNVARCHAR: + return Value.VARCHAR; + case Types.NUMERIC: + case Types.DECIMAL: + return Value.NUMERIC; + case Types.BIT: + case Types.BOOLEAN: + return Value.BOOLEAN; + case Types.INTEGER: + return Value.INTEGER; + case Types.SMALLINT: + return Value.SMALLINT; + case Types.TINYINT: + return Value.TINYINT; + case Types.BIGINT: + return Value.BIGINT; + case Types.REAL: + return Value.REAL; + case Types.DOUBLE: + case Types.FLOAT: + return Value.DOUBLE; + case Types.BINARY: + return Value.BINARY; + case Types.VARBINARY: + case Types.LONGVARBINARY: + return Value.VARBINARY; + case Types.OTHER: + return Value.UNKNOWN; + case Types.JAVA_OBJECT: + return Value.JAVA_OBJECT; + case Types.DATE: + return Value.DATE; + case Types.TIME: + return Value.TIME; + case Types.TIMESTAMP: + return Value.TIMESTAMP; + case Types.TIME_WITH_TIMEZONE: + return Value.TIME_TZ; + case Types.TIMESTAMP_WITH_TIMEZONE: + return Value.TIMESTAMP_TZ; + case Types.BLOB: + return Value.BLOB; + case Types.CLOB: + case Types.NCLOB: + return Value.CLOB; + case Types.NULL: + return Value.NULL; + case Types.ARRAY: + return Value.ARRAY; + default: + throw DbException.get( + ErrorCode.UNKNOWN_DATA_TYPE_1, Integer.toString(sqlType)); + } + } + + /** + * Convert a SQL type to a debug string. + * + * @param sqlType the SQL type + * @return the textual representation + */ + public static String sqlTypeToString(SQLType sqlType) { + if (sqlType == null) { + return "null"; + } + if (sqlType instanceof JDBCType) { + return "JDBCType." + sqlType.getName(); + } + if (sqlType instanceof H2Type) { + return sqlType.toString(); + } + return unknownSqlTypeToString(new StringBuilder("/* "), sqlType).append(" */ null").toString(); + } + + private static StringBuilder unknownSqlTypeToString(StringBuilder builder, SQLType sqlType) { + return builder.append(StringUtils.quoteJavaString(sqlType.getVendor())).append('/') + .append(StringUtils.quoteJavaString(sqlType.getName())).append(" [") + .append(sqlType.getVendorTypeNumber()).append(']'); + } + + /** + * Get a data type object from a type name. + * + * @param s the type name + * @param mode database mode + * @return the data type object + */ + public static DataType getTypeByName(String s, Mode mode) { + DataType result = mode.typeByNameMap.get(s); + if (result == null) { + result = TYPES_BY_NAME.get(s); + } + return result; + } + + /** + * Returns whether columns with the specified data type may have an index. + * + * @param type the data type + * @return whether an index is allowed + */ + public static boolean isIndexable(TypeInfo type) { + switch(type.getValueType()) { + case Value.UNKNOWN: + case Value.NULL: + case Value.BLOB: + case Value.CLOB: + return false; + case Value.ARRAY: + return isIndexable((TypeInfo) type.getExtTypeInfo()); + case Value.ROW: { + ExtTypeInfoRow ext = (ExtTypeInfoRow) type.getExtTypeInfo(); + for (Map.Entry entry : ext.getFields()) { + if (!isIndexable(entry.getValue())) { + return false; + } + } + } + //$FALL-THROUGH$ + default: + return true; + } + } + + /** + * Returns whether values of the specified data types have + * session-independent compare results. + * + * @param type1 + * the first data type + * @param type2 + * the second data type + * @return are values have session-independent compare results + */ + public static boolean areStableComparable(TypeInfo type1, TypeInfo type2) { + int t1 = type1.getValueType(); + int t2 = type2.getValueType(); + switch (t1) { + case Value.UNKNOWN: + case Value.NULL: + case Value.BLOB: + case Value.CLOB: + case Value.ROW: + return false; + case Value.DATE: + case Value.TIMESTAMP: + // DATE is equal to TIMESTAMP at midnight + return t2 == Value.DATE || t2 == Value.TIMESTAMP; + case Value.TIME: + case Value.TIME_TZ: + case Value.TIMESTAMP_TZ: + // Conversions depend on current timestamp and time zone + return t1 == t2; + case Value.ARRAY: + if (t2 == Value.ARRAY) { + return areStableComparable((TypeInfo) type1.getExtTypeInfo(), (TypeInfo) type2.getExtTypeInfo()); + } + return false; + default: + switch (t2) { + case Value.UNKNOWN: + case Value.NULL: + case Value.BLOB: + case Value.CLOB: + case Value.ROW: + return false; + default: + return true; + } + } + } + + /** + * Check if the given value type is a date-time type (TIME, DATE, TIMESTAMP, + * TIMESTAMP_TZ). + * + * @param type the value type + * @return true if the value type is a date-time type + */ + public static boolean isDateTimeType(int type) { + return type >= Value.DATE && type <= Value.TIMESTAMP_TZ; + } + + /** + * Check if the given value type is an interval type. + * + * @param type the value type + * @return true if the value type is an interval type + */ + public static boolean isIntervalType(int type) { + return type >= Value.INTERVAL_YEAR && type <= Value.INTERVAL_MINUTE_TO_SECOND; + } + + /** + * Check if the given value type is a year-month interval type. + * + * @param type the value type + * @return true if the value type is a year-month interval type + */ + public static boolean isYearMonthIntervalType(int type) { + return type == Value.INTERVAL_YEAR || type == Value.INTERVAL_MONTH || type == Value.INTERVAL_YEAR_TO_MONTH; + } + + /** + * Check if the given value type is a large object (BLOB or CLOB). + * + * @param type the value type + * @return true if the value type is a lob type + */ + public static boolean isLargeObject(int type) { + return type == Value.BLOB || type == Value.CLOB; + } + + /** + * Check if the given value type is a numeric type. + * + * @param type the value type + * @return true if the value type is a numeric type + */ + public static boolean isNumericType(int type) { + return type >= Value.TINYINT && type <= Value.DECFLOAT; + } + + /** + * Check if the given value type is a binary string type. + * + * @param type the value type + * @return true if the value type is a binary string type + */ + public static boolean isBinaryStringType(int type) { + return type >= Value.BINARY && type <= Value.BLOB; + } + + /** + * Check if the given value type is a character string type. + * + * @param type the value type + * @return true if the value type is a character string type + */ + public static boolean isCharacterStringType(int type) { + return type >= Value.CHAR && type <= Value.VARCHAR_IGNORECASE; + } + + /** + * Check if the given value type is a String (VARCHAR,...). + * + * @param type the value type + * @return true if the value type is a String type + */ + public static boolean isStringType(int type) { + return type == Value.VARCHAR || type == Value.CHAR || type == Value.VARCHAR_IGNORECASE; + } + + /** + * Check if the given value type is a binary string type or a compatible + * special data type such as Java object, UUID, geometry object, or JSON. + * + * @param type + * the value type + * @return true if the value type is a binary string type or a compatible + * special data type + */ + public static boolean isBinaryStringOrSpecialBinaryType(int type) { + switch (type) { + case Value.VARBINARY: + case Value.BINARY: + case Value.BLOB: + case Value.JAVA_OBJECT: + case Value.UUID: + case Value.GEOMETRY: + case Value.JSON: + return true; + default: + return false; + } + } + + /** + * Check if the given type has total ordering. + * + * @param type the value type + * @return true if the value type has total ordering + */ + public static boolean hasTotalOrdering(int type) { + switch (type) { + case Value.BOOLEAN: + case Value.TINYINT: + case Value.SMALLINT: + case Value.INTEGER: + case Value.BIGINT: + // Negative zeroes and NaNs are normalized + case Value.DOUBLE: + case Value.REAL: + case Value.TIME: + case Value.DATE: + case Value.TIMESTAMP: + case Value.VARBINARY: + // Serialized data is compared + case Value.JAVA_OBJECT: + case Value.UUID: + // EWKB is used + case Value.GEOMETRY: + case Value.ENUM: + case Value.INTERVAL_YEAR: + case Value.INTERVAL_MONTH: + case Value.INTERVAL_DAY: + case Value.INTERVAL_HOUR: + case Value.INTERVAL_MINUTE: + case Value.INTERVAL_SECOND: + case Value.INTERVAL_YEAR_TO_MONTH: + case Value.INTERVAL_DAY_TO_HOUR: + case Value.INTERVAL_DAY_TO_MINUTE: + case Value.INTERVAL_DAY_TO_SECOND: + case Value.INTERVAL_HOUR_TO_MINUTE: + case Value.INTERVAL_HOUR_TO_SECOND: + case Value.INTERVAL_MINUTE_TO_SECOND: + case Value.BINARY: + return true; + default: + return false; + } + } + + /** + * Performs saturated addition of precision values. + * + * @param p1 + * the first summand + * @param p2 + * the second summand + * @return the sum of summands, or {@link Long#MAX_VALUE} if either argument + * is negative or sum is out of range + */ + public static long addPrecision(long p1, long p2) { + long sum = p1 + p2; + if ((p1 | p2 | sum) < 0) { + return Long.MAX_VALUE; + } + return sum; + } + + /** + * Get the default value in the form of a Java object for the given Java + * class. + * + * @param clazz the Java class + * @return the default object + */ + public static Object getDefaultForPrimitiveType(Class clazz) { + if (clazz == Boolean.TYPE) { + return Boolean.FALSE; + } else if (clazz == Byte.TYPE) { + return (byte) 0; + } else if (clazz == Character.TYPE) { + return (char) 0; + } else if (clazz == Short.TYPE) { + return (short) 0; + } else if (clazz == Integer.TYPE) { + return 0; + } else if (clazz == Long.TYPE) { + return 0L; + } else if (clazz == Float.TYPE) { + return (float) 0; + } else if (clazz == Double.TYPE) { + return (double) 0; + } + throw DbException.getInternalError("primitive=" + clazz.toString()); + } + +} diff --git a/h2/src/main/org/h2/value/ExtTypeInfo.java b/h2/src/main/org/h2/value/ExtTypeInfo.java new file mode 100644 index 0000000..98c5446 --- /dev/null +++ b/h2/src/main/org/h2/value/ExtTypeInfo.java @@ -0,0 +1,20 @@ +/* + * 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.value; + +import org.h2.util.HasSQL; + +/** + * Extended parameters of a data type. + */ +public abstract class ExtTypeInfo implements HasSQL { + + @Override + public String toString() { + return getSQL(QUOTE_ONLY_WHEN_REQUIRED); + } + +} diff --git a/h2/src/main/org/h2/value/ExtTypeInfoEnum.java b/h2/src/main/org/h2/value/ExtTypeInfoEnum.java new file mode 100644 index 0000000..3c3651f --- /dev/null +++ b/h2/src/main/org/h2/value/ExtTypeInfoEnum.java @@ -0,0 +1,214 @@ +/* + * 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.value; + +import java.util.Arrays; +import java.util.Locale; + +import org.h2.api.ErrorCode; +import org.h2.engine.CastDataProvider; +import org.h2.engine.Constants; +import org.h2.message.DbException; + +/** + * Extended parameters of the ENUM data type. + */ +public final class ExtTypeInfoEnum extends ExtTypeInfo { + + private final String[] enumerators, cleaned; + + private TypeInfo type; + + /** + * Returns enumerators for the two specified values for a binary operation. + * + * @param left + * left (first) operand + * @param right + * right (second) operand + * @return enumerators from the left or the right value, or an empty array + * if both values do not have enumerators + */ + public static ExtTypeInfoEnum getEnumeratorsForBinaryOperation(Value left, Value right) { + if (left.getValueType() == Value.ENUM) { + return ((ValueEnum) left).getEnumerators(); + } else if (right.getValueType() == Value.ENUM) { + return ((ValueEnum) right).getEnumerators(); + } else { + throw DbException.get(ErrorCode.UNKNOWN_DATA_TYPE_1, + "type1=" + left.getValueType() + ", type2=" + right.getValueType()); + } + } + + private static String sanitize(String label) { + if (label == null) { + return null; + } + int length = label.length(); + if (length > Constants.MAX_STRING_LENGTH) { + throw DbException.getValueTooLongException("ENUM", label, length); + } + return label.trim().toUpperCase(Locale.ENGLISH); + } + + private static StringBuilder toSQL(StringBuilder builder, String[] enumerators) { + builder.append('('); + for (int i = 0; i < enumerators.length; i++) { + if (i != 0) { + builder.append(", "); + } + builder.append('\''); + String s = enumerators[i]; + for (int j = 0, length = s.length(); j < length; j++) { + char c = s.charAt(j); + if (c == '\'') { + builder.append('\''); + } + builder.append(c); + } + builder.append('\''); + } + return builder.append(')'); + } + + /** + * Creates new instance of extended parameters of the ENUM data type. + * + * @param enumerators + * the enumerators. May not be modified by caller or this class. + */ + public ExtTypeInfoEnum(String[] enumerators) { + int length; + if (enumerators == null || (length = enumerators.length) == 0) { + throw DbException.get(ErrorCode.ENUM_EMPTY); + } + if (length > Constants.MAX_ARRAY_CARDINALITY) { + throw DbException.getValueTooLongException("ENUM", "(" + length + " elements)", length); + } + final String[] cleaned = new String[length]; + for (int i = 0; i < length; i++) { + String l = sanitize(enumerators[i]); + if (l == null || l.isEmpty()) { + throw DbException.get(ErrorCode.ENUM_EMPTY); + } + for (int j = 0; j < i; j++) { + if (l.equals(cleaned[j])) { + throw DbException.get(ErrorCode.ENUM_DUPLICATE, // + toSQL(new StringBuilder(), enumerators).toString()); + } + } + cleaned[i] = l; + } + this.enumerators = enumerators; + this.cleaned = Arrays.equals(cleaned, enumerators) ? enumerators : cleaned; + } + + TypeInfo getType() { + TypeInfo type = this.type; + if (type == null) { + int p = 0; + for (String s : enumerators) { + int l = s.length(); + if (l > p) { + p = l; + } + } + this.type = type = new TypeInfo(Value.ENUM, p, 0, this); + } + return type; + } + + /** + * Get count of elements in enumeration. + * + * @return count of elements in enumeration + */ + public int getCount() { + return enumerators.length; + } + + /** + * Returns an enumerator with specified 0-based ordinal value. + * + * @param ordinal + * ordinal value of an enumerator + * @return the enumerator with specified ordinal value + */ + public String getEnumerator(int ordinal) { + return enumerators[ordinal]; + } + + /** + * Get ValueEnum instance for an ordinal. + * @param ordinal ordinal value of an enum + * @param provider the cast information provider + * @return ValueEnum instance + */ + public ValueEnum getValue(int ordinal, CastDataProvider provider) { + String label; + if (provider == null || !provider.zeroBasedEnums()) { + if (ordinal < 1 || ordinal > enumerators.length) { + throw DbException.get(ErrorCode.ENUM_VALUE_NOT_PERMITTED, getTraceSQL(), Integer.toString(ordinal)); + } + label = enumerators[ordinal - 1]; + } else { + if (ordinal < 0 || ordinal >= enumerators.length) { + throw DbException.get(ErrorCode.ENUM_VALUE_NOT_PERMITTED, getTraceSQL(), Integer.toString(ordinal)); + } + label = enumerators[ordinal]; + } + return new ValueEnum(this, label, ordinal); + } + + /** + * Get ValueEnum instance for a label string. + * @param label label string + * @param provider the cast information provider + * @return ValueEnum instance + */ + public ValueEnum getValue(String label, CastDataProvider provider) { + ValueEnum value = getValueOrNull(label, provider); + if (value == null) { + throw DbException.get(ErrorCode.ENUM_VALUE_NOT_PERMITTED, toString(), label); + } + return value; + } + + private ValueEnum getValueOrNull(String label, CastDataProvider provider) { + String l = sanitize(label); + if (l != null) { + for (int i = 0, ordinal = provider == null || !provider.zeroBasedEnums() ? 1 + : 0; i < cleaned.length; i++, ordinal++) { + if (l.equals(cleaned[i])) { + return new ValueEnum(this, enumerators[i], ordinal); + } + } + } + return null; + } + + @Override + public int hashCode() { + return Arrays.hashCode(enumerators) + 203_117; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != ExtTypeInfoEnum.class) { + return false; + } + return Arrays.equals(enumerators, ((ExtTypeInfoEnum) obj).enumerators); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return toSQL(builder, enumerators); + } + +} diff --git a/h2/src/main/org/h2/value/ExtTypeInfoGeometry.java b/h2/src/main/org/h2/value/ExtTypeInfoGeometry.java new file mode 100644 index 0000000..6f5b086 --- /dev/null +++ b/h2/src/main/org/h2/value/ExtTypeInfoGeometry.java @@ -0,0 +1,92 @@ +/* + * 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.value; + +import java.util.Objects; + +import org.h2.util.geometry.EWKTUtils; + +/** + * Extended parameters of the GEOMETRY data type. + */ +public final class ExtTypeInfoGeometry extends ExtTypeInfo { + + private final int type; + + private final Integer srid; + + static StringBuilder toSQL(StringBuilder builder, int type, Integer srid) { + if (type == 0 && srid == null) { + return builder; + } + builder.append('('); + if (type == 0) { + builder.append("GEOMETRY"); + } else { + EWKTUtils.formatGeometryTypeAndDimensionSystem(builder, type); + } + if (srid != null) { + builder.append(", ").append((int) srid); + } + return builder.append(')'); + } + + /** + * Creates new instance of extended parameters of the GEOMETRY data type. + * + * @param type + * the type and dimension system of geometries, or 0 if not + * constrained + * @param srid + * the SRID of geometries, or {@code null} if not constrained + */ + public ExtTypeInfoGeometry(int type, Integer srid) { + this.type = type; + this.srid = srid; + } + + @Override + public int hashCode() { + return 31 * ((srid == null) ? 0 : srid.hashCode()) + type; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != ExtTypeInfoGeometry.class) { + return false; + } + ExtTypeInfoGeometry other = (ExtTypeInfoGeometry) obj; + return type == other.type && Objects.equals(srid, other.srid); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return toSQL(builder, type, srid); + } + + /** + * Returns the type and dimension system of geometries. + * + * @return the type and dimension system of geometries, or 0 if not + * constrained + */ + public int getType() { + return type; + } + + /** + * Returns the SRID of geometries. + * + * @return the SRID of geometries, or {@code null} if not constrained + */ + public Integer getSrid() { + return srid; + } + +} diff --git a/h2/src/main/org/h2/value/ExtTypeInfoNumeric.java b/h2/src/main/org/h2/value/ExtTypeInfoNumeric.java new file mode 100644 index 0000000..dafc52b --- /dev/null +++ b/h2/src/main/org/h2/value/ExtTypeInfoNumeric.java @@ -0,0 +1,26 @@ +/* + * 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.value; + +/** + * Extended parameters of the NUMERIC data type. + */ +public final class ExtTypeInfoNumeric extends ExtTypeInfo { + + /** + * DECIMAL data type. + */ + public static final ExtTypeInfoNumeric DECIMAL = new ExtTypeInfoNumeric(); + + private ExtTypeInfoNumeric() { + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return builder.append("DECIMAL"); + } + +} diff --git a/h2/src/main/org/h2/value/ExtTypeInfoRow.java b/h2/src/main/org/h2/value/ExtTypeInfoRow.java new file mode 100644 index 0000000..2fd2864 --- /dev/null +++ b/h2/src/main/org/h2/value/ExtTypeInfoRow.java @@ -0,0 +1,130 @@ +/* + * 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.value; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.message.DbException; +import org.h2.util.ParserUtil; + +/** + * Extended parameters of the ROW data type. + */ +public final class ExtTypeInfoRow extends ExtTypeInfo { + + private final LinkedHashMap fields; + + private int hash; + + /** + * Creates new instance of extended parameters of ROW data type. + * + * @param fields + * fields + */ + public ExtTypeInfoRow(Typed[] fields) { + this(fields, fields.length); + } + + /** + * Creates new instance of extended parameters of ROW data type. + * + * @param fields + * fields + * @param degree + * number of fields to use + */ + public ExtTypeInfoRow(Typed[] fields, int degree) { + if (degree > Constants.MAX_COLUMNS) { + throw DbException.get(ErrorCode.TOO_MANY_COLUMNS_1, "" + Constants.MAX_COLUMNS); + } + LinkedHashMap map = new LinkedHashMap<>((int) Math.ceil(degree / .75)); + for (int i = 0; i < degree;) { + TypeInfo t = fields[i].getType(); + map.put("C" + ++i, t); + } + this.fields = map; + } + + /** + * Creates new instance of extended parameters of ROW data type. + * + * @param fields + * fields + */ + public ExtTypeInfoRow(LinkedHashMap fields) { + if (fields.size() > Constants.MAX_COLUMNS) { + throw DbException.get(ErrorCode.TOO_MANY_COLUMNS_1, "" + Constants.MAX_COLUMNS); + } + this.fields = fields; + } + + /** + * Returns fields. + * + * @return fields + */ + public Set> getFields() { + return fields.entrySet(); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + builder.append('('); + boolean f = false; + for (Map.Entry field : fields.entrySet()) { + if (f) { + builder.append(", "); + } + f = true; + ParserUtil.quoteIdentifier(builder, field.getKey(), sqlFlags).append(' '); + field.getValue().getSQL(builder, sqlFlags); + } + return builder.append(')'); + } + + @Override + public int hashCode() { + int h = hash; + if (h != 0) { + return h; + } + h = 67_378_403; + for (Map.Entry entry : fields.entrySet()) { + h = (h * 31 + entry.getKey().hashCode()) * 37 + entry.getValue().hashCode(); + } + return hash = h; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj.getClass() != ExtTypeInfoRow.class) { + return false; + } + LinkedHashMap fields2 = ((ExtTypeInfoRow) obj).fields; + int degree = fields.size(); + if (degree != fields2.size()) { + return false; + } + for (Iterator> i1 = fields.entrySet().iterator(), i2 = fields2.entrySet() + .iterator(); i1.hasNext();) { + Map.Entry e1 = i1.next(), e2 = i2.next(); + if (!e1.getKey().equals(e2.getKey()) || !e1.getValue().equals(e2.getValue())) { + return false; + } + } + return true; + } + +} diff --git a/h2/src/main/org/h2/value/Transfer.java b/h2/src/main/org/h2/value/Transfer.java new file mode 100644 index 0000000..62496b0 --- /dev/null +++ b/h2/src/main/org/h2/value/Transfer.java @@ -0,0 +1,1316 @@ +/* + * 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.value; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.InetAddress; +import java.net.Socket; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +import org.h2.api.ErrorCode; +import org.h2.api.IntervalQualifier; +import org.h2.engine.Constants; +import org.h2.engine.Session; +import org.h2.message.DbException; +import org.h2.security.SHA256; +import org.h2.store.Data; +import org.h2.store.DataReader; +import org.h2.util.Bits; +import org.h2.util.DateTimeUtils; +import org.h2.util.IOUtils; +import org.h2.util.MathUtils; +import org.h2.util.NetUtils; +import org.h2.util.StringUtils; +import org.h2.util.Utils; +import org.h2.value.lob.LobData; +import org.h2.value.lob.LobDataDatabase; +import org.h2.value.lob.LobDataFetchOnDemand; + +/** + * The transfer class is used to send and receive Value objects. + * It is used on both the client side, and on the server side. + */ +public final class Transfer { + + private static final int BUFFER_SIZE = 64 * 1024; + private static final int LOB_MAGIC = 0x1234; + private static final int LOB_MAC_SALT_LENGTH = 16; + + private static final int NULL = 0; + private static final int BOOLEAN = 1; + private static final int TINYINT = 2; + private static final int SMALLINT = 3; + private static final int INTEGER = 4; + private static final int BIGINT = 5; + private static final int NUMERIC = 6; + private static final int DOUBLE = 7; + private static final int REAL = 8; + private static final int TIME = 9; + private static final int DATE = 10; + private static final int TIMESTAMP = 11; + private static final int VARBINARY = 12; + private static final int VARCHAR = 13; + private static final int VARCHAR_IGNORECASE = 14; + private static final int BLOB = 15; + private static final int CLOB = 16; + private static final int ARRAY = 17; + private static final int JAVA_OBJECT = 19; + private static final int UUID = 20; + private static final int CHAR = 21; + private static final int GEOMETRY = 22; + // 1.4.192 + private static final int TIMESTAMP_TZ = 24; + // 1.4.195 + private static final int ENUM = 25; + // 1.4.198 + private static final int INTERVAL = 26; + private static final int ROW = 27; + // 1.4.200 + private static final int JSON = 28; + private static final int TIME_TZ = 29; + // 2.0.202 + private static final int BINARY = 30; + private static final int DECFLOAT = 31; + + private static final int[] VALUE_TO_TI = new int[Value.TYPE_COUNT + 1]; + private static final int[] TI_TO_VALUE = new int[45]; + + static { + addType(-1, Value.UNKNOWN); + addType(NULL, Value.NULL); + addType(BOOLEAN, Value.BOOLEAN); + addType(TINYINT, Value.TINYINT); + addType(SMALLINT, Value.SMALLINT); + addType(INTEGER, Value.INTEGER); + addType(BIGINT, Value.BIGINT); + addType(NUMERIC, Value.NUMERIC); + addType(DOUBLE, Value.DOUBLE); + addType(REAL, Value.REAL); + addType(TIME, Value.TIME); + addType(DATE, Value.DATE); + addType(TIMESTAMP, Value.TIMESTAMP); + addType(VARBINARY, Value.VARBINARY); + addType(VARCHAR, Value.VARCHAR); + addType(VARCHAR_IGNORECASE, Value.VARCHAR_IGNORECASE); + addType(BLOB, Value.BLOB); + addType(CLOB, Value.CLOB); + addType(ARRAY, Value.ARRAY); + addType(JAVA_OBJECT, Value.JAVA_OBJECT); + addType(UUID, Value.UUID); + addType(CHAR, Value.CHAR); + addType(GEOMETRY, Value.GEOMETRY); + addType(TIMESTAMP_TZ, Value.TIMESTAMP_TZ); + addType(ENUM, Value.ENUM); + addType(26, Value.INTERVAL_YEAR); + addType(27, Value.INTERVAL_MONTH); + addType(28, Value.INTERVAL_DAY); + addType(29, Value.INTERVAL_HOUR); + addType(30, Value.INTERVAL_MINUTE); + addType(31, Value.INTERVAL_SECOND); + addType(32, Value.INTERVAL_YEAR_TO_MONTH); + addType(33, Value.INTERVAL_DAY_TO_HOUR); + addType(34, Value.INTERVAL_DAY_TO_MINUTE); + addType(35, Value.INTERVAL_DAY_TO_SECOND); + addType(36, Value.INTERVAL_HOUR_TO_MINUTE); + addType(37, Value.INTERVAL_HOUR_TO_SECOND); + addType(38, Value.INTERVAL_MINUTE_TO_SECOND); + addType(39, Value.ROW); + addType(40, Value.JSON); + addType(41, Value.TIME_TZ); + addType(42, Value.BINARY); + addType(43, Value.DECFLOAT); + } + + private static void addType(int typeInformationType, int valueType) { + VALUE_TO_TI[valueType + 1] = typeInformationType; + TI_TO_VALUE[typeInformationType + 1] = valueType; + } + + private Socket socket; + private DataInputStream in; + private DataOutputStream out; + private Session session; + private boolean ssl; + private int version; + private byte[] lobMacSalt; + + /** + * Create a new transfer object for the specified session. + * + * @param session the session + * @param s the socket + */ + public Transfer(Session session, Socket s) { + this.session = session; + this.socket = s; + } + + /** + * Initialize the transfer object. This method will try to open an input and + * output stream. + * @throws IOException on failure + */ + public synchronized void init() throws IOException { + if (socket != null) { + in = new DataInputStream( + new BufferedInputStream( + socket.getInputStream(), Transfer.BUFFER_SIZE)); + out = new DataOutputStream( + new BufferedOutputStream( + socket.getOutputStream(), Transfer.BUFFER_SIZE)); + } + } + + /** + * Write pending changes. + * @throws IOException on failure + */ + public void flush() throws IOException { + out.flush(); + } + + /** + * Write a boolean. + * + * @param x the value + * @return itself + * @throws IOException on failure + */ + public Transfer writeBoolean(boolean x) throws IOException { + out.writeByte((byte) (x ? 1 : 0)); + return this; + } + + /** + * Read a boolean. + * + * @return the value + * @throws IOException on failure + */ + public boolean readBoolean() throws IOException { + return in.readByte() != 0; + } + + /** + * Write a byte. + * + * @param x the value + * @return itself + * @throws IOException on failure + */ + public Transfer writeByte(byte x) throws IOException { + out.writeByte(x); + return this; + } + + /** + * Read a byte. + * + * @return the value + * @throws IOException on failure + */ + public byte readByte() throws IOException { + return in.readByte(); + } + + /** + * Write a short. + * + * @param x the value + * @return itself + * @throws IOException on failure + */ + private Transfer writeShort(short x) throws IOException { + out.writeShort(x); + return this; + } + + /** + * Read a short. + * + * @return the value + * @throws IOException on failure + */ + private short readShort() throws IOException { + return in.readShort(); + } + + /** + * Write an int. + * + * @param x the value + * @return itself + * @throws IOException on failure + */ + public Transfer writeInt(int x) throws IOException { + out.writeInt(x); + return this; + } + + /** + * Read an int. + * + * @return the value + * @throws IOException on failure + */ + public int readInt() throws IOException { + return in.readInt(); + } + + /** + * Write a long. + * + * @param x the value + * @return itself + * @throws IOException on failure + */ + public Transfer writeLong(long x) throws IOException { + out.writeLong(x); + return this; + } + + /** + * Read a long. + * + * @return the value + * @throws IOException on failure + */ + public long readLong() throws IOException { + return in.readLong(); + } + + /** + * Write a double. + * + * @param i the value + * @return itself + * @throws IOException on failure + */ + private Transfer writeDouble(double i) throws IOException { + out.writeDouble(i); + return this; + } + + /** + * Write a float. + * + * @param i the value + * @return itself + */ + private Transfer writeFloat(float i) throws IOException { + out.writeFloat(i); + return this; + } + + /** + * Read a double. + * + * @return the value + * @throws IOException on failure + */ + private double readDouble() throws IOException { + return in.readDouble(); + } + + /** + * Read a float. + * + * @return the value + * @throws IOException on failure + */ + private float readFloat() throws IOException { + return in.readFloat(); + } + + /** + * Write a string. The maximum string length is Integer.MAX_VALUE. + * + * @param s the value + * @return itself + * @throws IOException on failure + */ + public Transfer writeString(String s) throws IOException { + if (s == null) { + out.writeInt(-1); + } else { + out.writeInt(s.length()); + out.writeChars(s); + } + return this; + } + + /** + * Read a string. + * + * @return the value + * @throws IOException on failure + */ + public String readString() throws IOException { + int len = in.readInt(); + if (len == -1) { + return null; + } + StringBuilder buff = new StringBuilder(len); + for (int i = 0; i < len; i++) { + buff.append(in.readChar()); + } + String s = buff.toString(); + s = StringUtils.cache(s); + return s; + } + + /** + * Write a byte array. + * + * @param data the value + * @return itself + * @throws IOException on failure + */ + public Transfer writeBytes(byte[] data) throws IOException { + if (data == null) { + writeInt(-1); + } else { + writeInt(data.length); + out.write(data); + } + return this; + } + + /** + * Write a number of bytes. + * + * @param buff the value + * @param off the offset + * @param len the length + * @return itself + * @throws IOException on failure + */ + public Transfer writeBytes(byte[] buff, int off, int len) throws IOException { + out.write(buff, off, len); + return this; + } + + /** + * Read a byte array. + * + * @return the value + * @throws IOException on failure + */ + public byte[] readBytes() throws IOException { + int len = readInt(); + if (len == -1) { + return null; + } + byte[] b = Utils.newBytes(len); + in.readFully(b); + return b; + } + + /** + * Read a number of bytes. + * + * @param buff the target buffer + * @param off the offset + * @param len the number of bytes to read + * @throws IOException on failure + */ + public void readBytes(byte[] buff, int off, int len) throws IOException { + in.readFully(buff, off, len); + } + + /** + * Close the transfer object and the socket. + */ + public synchronized void close() { + if (socket != null) { + try { + if (out != null) { + out.flush(); + } + socket.close(); + } catch (IOException e) { + DbException.traceThrowable(e); + } finally { + socket = null; + } + } + } + + /** + * Write value type, precision, and scale. + * + * @param type data type information + * @return itself + * @throws IOException on failure + */ + public Transfer writeTypeInfo(TypeInfo type) throws IOException { + if (version >= Constants.TCP_PROTOCOL_VERSION_20) { + writeTypeInfo20(type); + } else { + writeTypeInfo19(type); + } + return this; + } + + private void writeTypeInfo20(TypeInfo type) throws IOException { + int valueType = type.getValueType(); + writeInt(VALUE_TO_TI[valueType + 1]); + switch (valueType) { + case Value.UNKNOWN: + case Value.NULL: + case Value.BOOLEAN: + case Value.TINYINT: + case Value.SMALLINT: + case Value.INTEGER: + case Value.BIGINT: + case Value.DATE: + case Value.UUID: + break; + case Value.CHAR: + case Value.VARCHAR: + case Value.VARCHAR_IGNORECASE: + case Value.BINARY: + case Value.VARBINARY: + case Value.DECFLOAT: + case Value.JAVA_OBJECT: + case Value.JSON: + writeInt((int) type.getDeclaredPrecision()); + break; + case Value.CLOB: + case Value.BLOB: + writeLong(type.getDeclaredPrecision()); + break; + case Value.NUMERIC: + writeInt((int) type.getDeclaredPrecision()); + writeInt(type.getDeclaredScale()); + writeBoolean(type.getExtTypeInfo() != null); + break; + case Value.REAL: + case Value.DOUBLE: + case Value.INTERVAL_YEAR: + case Value.INTERVAL_MONTH: + case Value.INTERVAL_DAY: + case Value.INTERVAL_HOUR: + case Value.INTERVAL_MINUTE: + case Value.INTERVAL_YEAR_TO_MONTH: + case Value.INTERVAL_DAY_TO_HOUR: + case Value.INTERVAL_DAY_TO_MINUTE: + case Value.INTERVAL_HOUR_TO_MINUTE: + writeBytePrecisionWithDefault(type.getDeclaredPrecision()); + break; + case Value.TIME: + case Value.TIME_TZ: + case Value.TIMESTAMP: + case Value.TIMESTAMP_TZ: + writeByteScaleWithDefault(type.getDeclaredScale()); + break; + case Value.INTERVAL_SECOND: + case Value.INTERVAL_DAY_TO_SECOND: + case Value.INTERVAL_HOUR_TO_SECOND: + case Value.INTERVAL_MINUTE_TO_SECOND: + writeBytePrecisionWithDefault(type.getDeclaredPrecision()); + writeByteScaleWithDefault(type.getDeclaredScale()); + break; + case Value.ENUM: + writeTypeInfoEnum(type); + break; + case Value.GEOMETRY: + writeTypeInfoGeometry(type); + break; + case Value.ARRAY: + writeInt((int) type.getDeclaredPrecision()); + writeTypeInfo((TypeInfo) type.getExtTypeInfo()); + break; + case Value.ROW: + writeTypeInfoRow(type); + break; + default: + throw DbException.getUnsupportedException("value type " + valueType); + } + } + + private void writeBytePrecisionWithDefault(long precision) throws IOException { + writeByte(precision >= 0 ? (byte) precision : -1); + } + + private void writeByteScaleWithDefault(int scale) throws IOException { + writeByte(scale >= 0 ? (byte) scale : -1); + } + + private void writeTypeInfoEnum(TypeInfo type) throws IOException { + ExtTypeInfoEnum ext = (ExtTypeInfoEnum) type.getExtTypeInfo(); + if (ext != null) { + int c = ext.getCount(); + writeInt(c); + for (int i = 0; i < c; i++) { + writeString(ext.getEnumerator(i)); + } + } else { + writeInt(0); + } + } + + private void writeTypeInfoGeometry(TypeInfo type) throws IOException { + ExtTypeInfoGeometry ext = (ExtTypeInfoGeometry) type.getExtTypeInfo(); + if (ext == null) { + writeByte((byte) 0); + } else { + int t = ext.getType(); + Integer srid = ext.getSrid(); + if (t == 0) { + if (srid == null) { + writeByte((byte) 0); + } else { + writeByte((byte) 2); + writeInt(srid); + } + } else { + if (srid == null) { + writeByte((byte) 1); + writeShort((short) t); + } else { + writeByte((byte) 3); + writeShort((short) t); + writeInt(srid); + } + } + } + } + + private void writeTypeInfoRow(TypeInfo type) throws IOException { + Set> fields = ((ExtTypeInfoRow) type.getExtTypeInfo()).getFields(); + writeInt(fields.size()); + for (Map.Entry field : fields) { + writeString(field.getKey()).writeTypeInfo(field.getValue()); + } + } + + private void writeTypeInfo19(TypeInfo type) throws IOException { + int valueType = type.getValueType(); + switch (valueType) { + case Value.BINARY: + valueType = Value.VARBINARY; + break; + case Value.DECFLOAT: + valueType = Value.NUMERIC; + break; + } + writeInt(VALUE_TO_TI[valueType + 1]).writeLong(type.getPrecision()).writeInt(type.getScale()); + } + + /** + * Read a type information. + * + * @return the type information + * @throws IOException on failure + */ + public TypeInfo readTypeInfo() throws IOException { + if (version >= Constants.TCP_PROTOCOL_VERSION_20) { + return readTypeInfo20(); + } else { + return readTypeInfo19(); + } + } + + private TypeInfo readTypeInfo20() throws IOException { + int valueType = TI_TO_VALUE[readInt() + 1]; + long precision = -1L; + int scale = -1; + ExtTypeInfo ext = null; + switch (valueType) { + case Value.UNKNOWN: + case Value.NULL: + case Value.BOOLEAN: + case Value.TINYINT: + case Value.SMALLINT: + case Value.INTEGER: + case Value.BIGINT: + case Value.DATE: + case Value.UUID: + break; + case Value.CHAR: + case Value.VARCHAR: + case Value.VARCHAR_IGNORECASE: + case Value.BINARY: + case Value.VARBINARY: + case Value.DECFLOAT: + case Value.JAVA_OBJECT: + case Value.JSON: + precision = readInt(); + break; + case Value.CLOB: + case Value.BLOB: + precision = readLong(); + break; + case Value.NUMERIC: + precision = readInt(); + scale = readInt(); + if (readBoolean()) { + ext = ExtTypeInfoNumeric.DECIMAL; + } + break; + case Value.REAL: + case Value.DOUBLE: + case Value.INTERVAL_YEAR: + case Value.INTERVAL_MONTH: + case Value.INTERVAL_DAY: + case Value.INTERVAL_HOUR: + case Value.INTERVAL_MINUTE: + case Value.INTERVAL_YEAR_TO_MONTH: + case Value.INTERVAL_DAY_TO_HOUR: + case Value.INTERVAL_DAY_TO_MINUTE: + case Value.INTERVAL_HOUR_TO_MINUTE: + precision = readByte(); + break; + case Value.TIME: + case Value.TIME_TZ: + case Value.TIMESTAMP: + case Value.TIMESTAMP_TZ: + scale = readByte(); + break; + case Value.INTERVAL_SECOND: + case Value.INTERVAL_DAY_TO_SECOND: + case Value.INTERVAL_HOUR_TO_SECOND: + case Value.INTERVAL_MINUTE_TO_SECOND: + precision = readByte(); + scale = readByte(); + break; + case Value.ENUM: + ext = readTypeInfoEnum(); + break; + case Value.GEOMETRY: + ext = readTypeInfoGeometry(); + break; + case Value.ARRAY: + precision = readInt(); + ext = readTypeInfo(); + break; + case Value.ROW: + ext = readTypeInfoRow(); + break; + default: + throw DbException.getUnsupportedException("value type " + valueType); + } + return TypeInfo.getTypeInfo(valueType, precision, scale, ext); + } + + private ExtTypeInfo readTypeInfoEnum() throws IOException { + ExtTypeInfo ext; + int c = readInt(); + if (c > 0) { + String[] enumerators = new String[c]; + for (int i = 0; i < c; i++) { + enumerators[i] = readString(); + } + ext = new ExtTypeInfoEnum(enumerators); + } else { + ext = null; + } + return ext; + } + + private ExtTypeInfo readTypeInfoGeometry() throws IOException { + ExtTypeInfo ext; + int e = readByte(); + switch (e) { + case 0: + ext = null; + break; + case 1: + ext = new ExtTypeInfoGeometry(readShort(), null); + break; + case 2: + ext = new ExtTypeInfoGeometry(0, readInt()); + break; + case 3: + ext = new ExtTypeInfoGeometry(readShort(), readInt()); + break; + default: + throw DbException.getUnsupportedException("GEOMETRY type encoding " + e); + } + return ext; + } + + private ExtTypeInfo readTypeInfoRow() throws IOException { + LinkedHashMap fields = new LinkedHashMap<>(); + for (int i = 0, l = readInt(); i < l; i++) { + String name = readString(); + if (fields.putIfAbsent(name, readTypeInfo()) != null) { + throw DbException.get(ErrorCode.DUPLICATE_COLUMN_NAME_1, name); + } + } + return new ExtTypeInfoRow(fields); + } + + private TypeInfo readTypeInfo19() throws IOException { + return TypeInfo.getTypeInfo(TI_TO_VALUE[readInt() + 1], readLong(), readInt(), null); + } + + /** + * Write a value. + * + * @param v the value + * @throws IOException on failure + */ + public void writeValue(Value v) throws IOException { + int type = v.getValueType(); + switch (type) { + case Value.NULL: + writeInt(NULL); + break; + case Value.BINARY: + if (version >= Constants.TCP_PROTOCOL_VERSION_20) { + writeInt(BINARY); + writeBytes(v.getBytesNoCopy()); + break; + } + //$FALL-THROUGH$ + case Value.VARBINARY: + writeInt(VARBINARY); + writeBytes(v.getBytesNoCopy()); + break; + case Value.JAVA_OBJECT: + writeInt(JAVA_OBJECT); + writeBytes(v.getBytesNoCopy()); + break; + case Value.UUID: { + writeInt(UUID); + ValueUuid uuid = (ValueUuid) v; + writeLong(uuid.getHigh()); + writeLong(uuid.getLow()); + break; + } + case Value.BOOLEAN: + writeInt(BOOLEAN); + writeBoolean(v.getBoolean()); + break; + case Value.TINYINT: + writeInt(TINYINT); + writeByte(v.getByte()); + break; + case Value.TIME: + writeInt(TIME); + writeLong(((ValueTime) v).getNanos()); + break; + case Value.TIME_TZ: { + ValueTimeTimeZone t = (ValueTimeTimeZone) v; + if (version >= Constants.TCP_PROTOCOL_VERSION_19) { + writeInt(TIME_TZ); + writeLong(t.getNanos()); + writeInt(t.getTimeZoneOffsetSeconds()); + } else { + writeInt(TIME); + /* + * Don't call SessionRemote.currentTimestamp(), it may require + * own remote call and old server will not return custom time + * zone anyway. + */ + ValueTimestampTimeZone current = session.isRemote() + ? DateTimeUtils.currentTimestamp(DateTimeUtils.getTimeZone()) : session.currentTimestamp(); + writeLong(DateTimeUtils.normalizeNanosOfDay(t.getNanos() + + (t.getTimeZoneOffsetSeconds() - current.getTimeZoneOffsetSeconds()) + * DateTimeUtils.NANOS_PER_DAY)); + } + break; + } + case Value.DATE: + writeInt(DATE); + writeLong(((ValueDate) v).getDateValue()); + break; + case Value.TIMESTAMP: { + writeInt(TIMESTAMP); + ValueTimestamp ts = (ValueTimestamp) v; + writeLong(ts.getDateValue()); + writeLong(ts.getTimeNanos()); + break; + } + case Value.TIMESTAMP_TZ: { + writeInt(TIMESTAMP_TZ); + ValueTimestampTimeZone ts = (ValueTimestampTimeZone) v; + writeLong(ts.getDateValue()); + writeLong(ts.getTimeNanos()); + int timeZoneOffset = ts.getTimeZoneOffsetSeconds(); + writeInt(version >= Constants.TCP_PROTOCOL_VERSION_19 // + ? timeZoneOffset : timeZoneOffset / 60); + break; + } + case Value.DECFLOAT: + if (version >= Constants.TCP_PROTOCOL_VERSION_20) { + writeInt(DECFLOAT); + writeString(v.getString()); + break; + } + //$FALL-THROUGH$ + case Value.NUMERIC: + writeInt(NUMERIC); + writeString(v.getString()); + break; + case Value.DOUBLE: + writeInt(DOUBLE); + writeDouble(v.getDouble()); + break; + case Value.REAL: + writeInt(REAL); + writeFloat(v.getFloat()); + break; + case Value.INTEGER: + writeInt(INTEGER); + writeInt(v.getInt()); + break; + case Value.BIGINT: + writeInt(BIGINT); + writeLong(v.getLong()); + break; + case Value.SMALLINT: + writeInt(SMALLINT); + if (version >= Constants.TCP_PROTOCOL_VERSION_20) { + writeShort(v.getShort()); + } else { + writeInt(v.getShort()); + } + break; + case Value.VARCHAR: + writeInt(VARCHAR); + writeString(v.getString()); + break; + case Value.VARCHAR_IGNORECASE: + writeInt(VARCHAR_IGNORECASE); + writeString(v.getString()); + break; + case Value.CHAR: + writeInt(CHAR); + writeString(v.getString()); + break; + case Value.BLOB: { + writeInt(BLOB); + ValueBlob lob = (ValueBlob) v; + LobData lobData = lob.getLobData(); + long length = lob.octetLength(); + if (lobData instanceof LobDataDatabase) { + LobDataDatabase lobDataDatabase = (LobDataDatabase) lobData; + writeLong(-1); + writeInt(lobDataDatabase.getTableId()); + writeLong(lobDataDatabase.getLobId()); + writeBytes(calculateLobMac(lobDataDatabase.getLobId())); + writeLong(length); + break; + } + if (length < 0) { + throw DbException.get( + ErrorCode.CONNECTION_BROKEN_1, "length=" + length); + } + writeLong(length); + long written = IOUtils.copyAndCloseInput(lob.getInputStream(), out); + if (written != length) { + throw DbException.get( + ErrorCode.CONNECTION_BROKEN_1, "length:" + length + " written:" + written); + } + writeInt(LOB_MAGIC); + break; + } + case Value.CLOB: { + writeInt(CLOB); + ValueClob lob = (ValueClob) v; + LobData lobData = lob.getLobData(); + long charLength = lob.charLength(); + if (lobData instanceof LobDataDatabase) { + LobDataDatabase lobDataDatabase = (LobDataDatabase) lobData; + writeLong(-1); + writeInt(lobDataDatabase.getTableId()); + writeLong(lobDataDatabase.getLobId()); + writeBytes(calculateLobMac(lobDataDatabase.getLobId())); + if (version >= Constants.TCP_PROTOCOL_VERSION_20) { + writeLong(lob.octetLength()); + } + writeLong(charLength); + break; + } + if (charLength < 0) { + throw DbException.get( + ErrorCode.CONNECTION_BROKEN_1, "length=" + charLength); + } + writeLong(charLength); + Reader reader = lob.getReader(); + Data.copyString(reader, out); + writeInt(LOB_MAGIC); + break; + } + case Value.ARRAY: { + writeInt(ARRAY); + ValueArray va = (ValueArray) v; + Value[] list = va.getList(); + int len = list.length; + writeInt(len); + for (Value value : list) { + writeValue(value); + } + break; + } + case Value.ROW: { + writeInt(version >= Constants.TCP_PROTOCOL_VERSION_18 ? ROW : ARRAY); + ValueRow va = (ValueRow) v; + Value[] list = va.getList(); + int len = list.length; + writeInt(len); + for (Value value : list) { + writeValue(value); + } + break; + } + case Value.ENUM: { + writeInt(ENUM); + writeInt(v.getInt()); + if (version < Constants.TCP_PROTOCOL_VERSION_20) { + writeString(v.getString()); + } + break; + } + case Value.GEOMETRY: + writeInt(GEOMETRY); + writeBytes(v.getBytesNoCopy()); + break; + case Value.INTERVAL_YEAR: + case Value.INTERVAL_MONTH: + case Value.INTERVAL_DAY: + case Value.INTERVAL_HOUR: + case Value.INTERVAL_MINUTE: + if (version >= Constants.TCP_PROTOCOL_VERSION_18) { + ValueInterval interval = (ValueInterval) v; + int ordinal = type - Value.INTERVAL_YEAR; + if (interval.isNegative()) { + ordinal = ~ordinal; + } + writeInt(INTERVAL); + writeByte((byte) ordinal); + writeLong(interval.getLeading()); + } else { + writeInt(VARCHAR); + writeString(v.getString()); + } + break; + case Value.INTERVAL_SECOND: + case Value.INTERVAL_YEAR_TO_MONTH: + case Value.INTERVAL_DAY_TO_HOUR: + case Value.INTERVAL_DAY_TO_MINUTE: + case Value.INTERVAL_DAY_TO_SECOND: + case Value.INTERVAL_HOUR_TO_MINUTE: + case Value.INTERVAL_HOUR_TO_SECOND: + case Value.INTERVAL_MINUTE_TO_SECOND: + if (version >= Constants.TCP_PROTOCOL_VERSION_18) { + ValueInterval interval = (ValueInterval) v; + int ordinal = type - Value.INTERVAL_YEAR; + if (interval.isNegative()) { + ordinal = ~ordinal; + } + writeInt(INTERVAL); + writeByte((byte) ordinal); + writeLong(interval.getLeading()); + writeLong(interval.getRemaining()); + } else { + writeInt(VARCHAR); + writeString(v.getString()); + } + break; + case Value.JSON: { + writeInt(JSON); + writeBytes(v.getBytesNoCopy()); + break; + } + default: + throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "type=" + type); + } + } + + /** + * Read a value. + * + * @param columnType the data type of value, or {@code null} + * @return the value + * @throws IOException on failure + */ + public Value readValue(TypeInfo columnType) throws IOException { + int type = readInt(); + switch (type) { + case NULL: + return ValueNull.INSTANCE; + case VARBINARY: + return ValueVarbinary.getNoCopy(readBytes()); + case BINARY: + return ValueBinary.getNoCopy(readBytes()); + case UUID: + return ValueUuid.get(readLong(), readLong()); + case JAVA_OBJECT: + return ValueJavaObject.getNoCopy(readBytes()); + case BOOLEAN: + return ValueBoolean.get(readBoolean()); + case TINYINT: + return ValueTinyint.get(readByte()); + case DATE: + return ValueDate.fromDateValue(readLong()); + case TIME: + return ValueTime.fromNanos(readLong()); + case TIME_TZ: + return ValueTimeTimeZone.fromNanos(readLong(), readInt()); + case TIMESTAMP: + return ValueTimestamp.fromDateValueAndNanos(readLong(), readLong()); + case TIMESTAMP_TZ: { + long dateValue = readLong(), timeNanos = readLong(); + int timeZoneOffset = readInt(); + return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, timeNanos, + version >= Constants.TCP_PROTOCOL_VERSION_19 ? timeZoneOffset : timeZoneOffset * 60); + } + case NUMERIC: + return ValueNumeric.get(new BigDecimal(readString())); + case DOUBLE: + return ValueDouble.get(readDouble()); + case REAL: + return ValueReal.get(readFloat()); + case ENUM: { + int ordinal = readInt(); + if (version >= Constants.TCP_PROTOCOL_VERSION_20) { + return ((ExtTypeInfoEnum) columnType.getExtTypeInfo()).getValue(ordinal, session); + } + return ValueEnumBase.get(readString(), ordinal); + } + case INTEGER: + return ValueInteger.get(readInt()); + case BIGINT: + return ValueBigint.get(readLong()); + case SMALLINT: + if (version >= Constants.TCP_PROTOCOL_VERSION_20) { + return ValueSmallint.get(readShort()); + } else { + return ValueSmallint.get((short) readInt()); + } + case VARCHAR: + return ValueVarchar.get(readString()); + case VARCHAR_IGNORECASE: + return ValueVarcharIgnoreCase.get(readString()); + case CHAR: + return ValueChar.get(readString()); + case BLOB: { + long length = readLong(); + if (length == -1) { + // fetch-on-demand LOB + int tableId = readInt(); + long id = readLong(); + byte[] hmac = readBytes(); + long precision = readLong(); + return new ValueBlob(new LobDataFetchOnDemand(session.getDataHandler(), tableId, id, hmac), precision); + } + Value v = session.getDataHandler().getLobStorage().createBlob(in, length); + int magic = readInt(); + if (magic != LOB_MAGIC) { + throw DbException.get( + ErrorCode.CONNECTION_BROKEN_1, "magic=" + magic); + } + return v; + } + case CLOB: { + long charLength = readLong(); + if (charLength == -1) { + // fetch-on-demand LOB + int tableId = readInt(); + long id = readLong(); + byte[] hmac = readBytes(); + long octetLength = version >= Constants.TCP_PROTOCOL_VERSION_20 ? readLong() : -1L; + charLength = readLong(); + return new ValueClob(new LobDataFetchOnDemand(session.getDataHandler(), tableId, id, hmac), + octetLength, charLength); + } + if (charLength < 0) { + throw DbException.get( + ErrorCode.CONNECTION_BROKEN_1, "length="+ charLength); + } + Value v = session.getDataHandler().getLobStorage(). + createClob(new DataReader(in), charLength); + int magic = readInt(); + if (magic != LOB_MAGIC) { + throw DbException.get( + ErrorCode.CONNECTION_BROKEN_1, "magic=" + magic); + } + return v; + } + case ARRAY: { + int len = readInt(); + if (len < 0) { + // Unlikely, but possible with H2 1.4.200 and older versions + len = ~len; + readString(); + } + if (columnType != null) { + TypeInfo elementType = (TypeInfo) columnType.getExtTypeInfo(); + return ValueArray.get(elementType, readArrayElements(len, elementType), session); + } + return ValueArray.get(readArrayElements(len, null), session); + } + case ROW: { + int len = readInt(); + Value[] list = new Value[len]; + if (columnType != null) { + ExtTypeInfoRow extTypeInfoRow = (ExtTypeInfoRow) columnType.getExtTypeInfo(); + Iterator> fields = extTypeInfoRow.getFields().iterator(); + for (int i = 0; i < len; i++) { + list[i] = readValue(fields.next().getValue()); + } + return ValueRow.get(columnType, list); + } + for (int i = 0; i < len; i++) { + list[i] = readValue(null); + } + return ValueRow.get(list); + } + case GEOMETRY: + return ValueGeometry.get(readBytes()); + case INTERVAL: { + int ordinal = readByte(); + boolean negative = ordinal < 0; + if (negative) { + ordinal = ~ordinal; + } + return ValueInterval.from(IntervalQualifier.valueOf(ordinal), negative, readLong(), + ordinal < 5 ? 0 : readLong()); + } + case JSON: + // Do not trust the value + return ValueJson.fromJson(readBytes()); + case DECFLOAT: { + String s = readString(); + switch (s) { + case "-Infinity": + return ValueDecfloat.NEGATIVE_INFINITY; + case "Infinity": + return ValueDecfloat.POSITIVE_INFINITY; + case "NaN": + return ValueDecfloat.NAN; + default: + return ValueDecfloat.get(new BigDecimal(s)); + } + } + default: + throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "type=" + type); + } + } + + private Value[] readArrayElements(int len, TypeInfo elementType) throws IOException { + Value[] list = new Value[len]; + for (int i = 0; i < len; i++) { + list[i] = readValue(elementType); + } + return list; + } + + /** + * Read a row count. + * + * @return the row count + * @throws IOException on failure + */ + public long readRowCount() throws IOException { + return version >= Constants.TCP_PROTOCOL_VERSION_20 ? readLong() : readInt(); + } + + /** + * Write a row count. + * + * @param rowCount the row count + * @return itself + * @throws IOException on failure + */ + public Transfer writeRowCount(long rowCount) throws IOException { + return version >= Constants.TCP_PROTOCOL_VERSION_20 ? writeLong(rowCount) + : writeInt(rowCount < Integer.MAX_VALUE ? (int) rowCount : Integer.MAX_VALUE); + } + + /** + * Get the socket. + * + * @return the socket + */ + public Socket getSocket() { + return socket; + } + + /** + * Set the session. + * + * @param session the session + */ + public void setSession(Session session) { + this.session = session; + } + + /** + * Enable or disable SSL. + * + * @param ssl the new value + */ + public void setSSL(boolean ssl) { + this.ssl = ssl; + } + + /** + * Open a new connection to the same address and port as this one. + * + * @return the new transfer object + * @throws IOException on failure + */ + public Transfer openNewConnection() throws IOException { + InetAddress address = socket.getInetAddress(); + int port = socket.getPort(); + Socket s2 = NetUtils.createSocket(address, port, ssl); + Transfer trans = new Transfer(null, s2); + trans.setSSL(ssl); + return trans; + } + + public void setVersion(int version) { + this.version = version; + } + + public int getVersion() { + return version; + } + + public synchronized boolean isClosed() { + return socket == null || socket.isClosed(); + } + + /** + * Verify the HMAC. + * + * @param hmac the message authentication code + * @param lobId the lobId + * @throws DbException if the HMAC does not match + */ + public void verifyLobMac(byte[] hmac, long lobId) { + byte[] result = calculateLobMac(lobId); + if (!Utils.compareSecure(hmac, result)) { + throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, + "Invalid lob hmac; possibly the connection was re-opened internally"); + } + } + + private byte[] calculateLobMac(long lobId) { + if (lobMacSalt == null) { + lobMacSalt = MathUtils.secureRandomBytes(LOB_MAC_SALT_LENGTH); + } + byte[] data = new byte[8]; + Bits.writeLong(data, 0, lobId); + return SHA256.getHashWithSalt(data, lobMacSalt); + } + +} diff --git a/h2/src/main/org/h2/value/TypeInfo.java b/h2/src/main/org/h2/value/TypeInfo.java new file mode 100644 index 0000000..4411d97 --- /dev/null +++ b/h2/src/main/org/h2/value/TypeInfo.java @@ -0,0 +1,1536 @@ +/* + * 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.value; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; + +import org.h2.api.ErrorCode; +import org.h2.api.IntervalQualifier; +import org.h2.engine.Constants; +import org.h2.message.DbException; + +/** + * Data type with parameters. + */ +public class TypeInfo extends ExtTypeInfo implements Typed { + + /** + * UNKNOWN type with parameters. + */ + public static final TypeInfo TYPE_UNKNOWN; + + /** + * NULL type with parameters. + */ + public static final TypeInfo TYPE_NULL; + + /** + * CHAR type with default parameters. + */ + public static final TypeInfo TYPE_CHAR; + + /** + * CHARACTER VARYING type with maximum parameters. + */ + public static final TypeInfo TYPE_VARCHAR; + + /** + * VARCHAR_IGNORECASE type with maximum parameters. + */ + public static final TypeInfo TYPE_VARCHAR_IGNORECASE; + + /** + * CHARACTER LARGE OBJECT type with maximum parameters. + */ + public static final TypeInfo TYPE_CLOB; + + /** + * BINARY type with default parameters. + */ + public static final TypeInfo TYPE_BINARY; + + /** + * BINARY VARYING type with maximum parameters. + */ + public static final TypeInfo TYPE_VARBINARY; + + /** + * BINARY LARGE OBJECT type with maximum parameters. + */ + public static final TypeInfo TYPE_BLOB; + + /** + * BOOLEAN type with parameters. + */ + public static final TypeInfo TYPE_BOOLEAN; + + /** + * TINYINT type with parameters. + */ + public static final TypeInfo TYPE_TINYINT; + + /** + * SMALLINT type with parameters. + */ + public static final TypeInfo TYPE_SMALLINT; + + /** + * INTEGER type with parameters. + */ + public static final TypeInfo TYPE_INTEGER; + + /** + * BIGINT type with parameters. + */ + public static final TypeInfo TYPE_BIGINT; + + /** + * NUMERIC type with maximum precision and scale 0. + */ + public static final TypeInfo TYPE_NUMERIC_SCALE_0; + + /** + * NUMERIC type with parameters enough to hold a BIGINT value. + */ + public static final TypeInfo TYPE_NUMERIC_BIGINT; + + /** + * NUMERIC type that can hold values with floating point. + */ + public static final TypeInfo TYPE_NUMERIC_FLOATING_POINT; + + /** + * REAL type with parameters. + */ + public static final TypeInfo TYPE_REAL; + + /** + * DOUBLE PRECISION type with parameters. + */ + public static final TypeInfo TYPE_DOUBLE; + + /** + * DECFLOAT type with maximum parameters. + */ + public static final TypeInfo TYPE_DECFLOAT; + + /** + * DECFLOAT type with parameters enough to hold a BIGINT value. + */ + public static final TypeInfo TYPE_DECFLOAT_BIGINT; + + /** + * DATE type with parameters. + */ + public static final TypeInfo TYPE_DATE; + + /** + * TIME type with maximum parameters. + */ + public static final TypeInfo TYPE_TIME; + + /** + * TIME WITH TIME ZONE type with maximum parameters. + */ + public static final TypeInfo TYPE_TIME_TZ; + + /** + * TIMESTAMP type with maximum parameters. + */ + public static final TypeInfo TYPE_TIMESTAMP; + + /** + * TIMESTAMP WITH TIME ZONE type with maximum parameters. + */ + public static final TypeInfo TYPE_TIMESTAMP_TZ; + + /** + * INTERVAL DAY type with maximum parameters. + */ + public static final TypeInfo TYPE_INTERVAL_DAY; + + /** + * INTERVAL YEAR TO MONTH type with maximum parameters. + */ + public static final TypeInfo TYPE_INTERVAL_YEAR_TO_MONTH; + + /** + * INTERVAL DAY TO SECOND type with maximum parameters. + */ + public static final TypeInfo TYPE_INTERVAL_DAY_TO_SECOND; + + /** + * INTERVAL HOUR TO SECOND type with maximum parameters. + */ + public static final TypeInfo TYPE_INTERVAL_HOUR_TO_SECOND; + + /** + * JAVA_OBJECT type with maximum parameters. + */ + public static final TypeInfo TYPE_JAVA_OBJECT; + + /** + * ENUM type with undefined parameters. + */ + public static final TypeInfo TYPE_ENUM_UNDEFINED; + + /** + * GEOMETRY type with default parameters. + */ + public static final TypeInfo TYPE_GEOMETRY; + + /** + * JSON type. + */ + public static final TypeInfo TYPE_JSON; + + /** + * UUID type with parameters. + */ + public static final TypeInfo TYPE_UUID; + + /** + * ARRAY type with unknown parameters. + */ + public static final TypeInfo TYPE_ARRAY_UNKNOWN; + + /** + * ROW (row value) type without fields. + */ + public static final TypeInfo TYPE_ROW_EMPTY; + + private static final TypeInfo[] TYPE_INFOS_BY_VALUE_TYPE; + + private final int valueType; + + private final long precision; + + private final int scale; + + private final ExtTypeInfo extTypeInfo; + + static { + TypeInfo[] infos = new TypeInfo[Value.TYPE_COUNT]; + TYPE_UNKNOWN = new TypeInfo(Value.UNKNOWN); + // NULL + infos[Value.NULL] = TYPE_NULL = new TypeInfo(Value.NULL); + // CHARACTER + infos[Value.CHAR] = TYPE_CHAR = new TypeInfo(Value.CHAR, -1L); + infos[Value.VARCHAR] = TYPE_VARCHAR = new TypeInfo(Value.VARCHAR); + infos[Value.CLOB] = TYPE_CLOB = new TypeInfo(Value.CLOB); + infos[Value.VARCHAR_IGNORECASE] = TYPE_VARCHAR_IGNORECASE = new TypeInfo(Value.VARCHAR_IGNORECASE); + // BINARY + infos[Value.BINARY] = TYPE_BINARY = new TypeInfo(Value.BINARY, -1L); + infos[Value.VARBINARY] = TYPE_VARBINARY = new TypeInfo(Value.VARBINARY); + infos[Value.BLOB] = TYPE_BLOB = new TypeInfo(Value.BLOB); + // BOOLEAN + infos[Value.BOOLEAN] = TYPE_BOOLEAN = new TypeInfo(Value.BOOLEAN); + // NUMERIC + infos[Value.TINYINT] = TYPE_TINYINT = new TypeInfo(Value.TINYINT); + infos[Value.SMALLINT] = TYPE_SMALLINT = new TypeInfo(Value.SMALLINT); + infos[Value.INTEGER] = TYPE_INTEGER = new TypeInfo(Value.INTEGER); + infos[Value.BIGINT] = TYPE_BIGINT = new TypeInfo(Value.BIGINT); + TYPE_NUMERIC_SCALE_0 = new TypeInfo(Value.NUMERIC, Constants.MAX_NUMERIC_PRECISION, 0, null); + TYPE_NUMERIC_BIGINT = new TypeInfo(Value.NUMERIC, ValueBigint.DECIMAL_PRECISION, 0, null); + infos[Value.NUMERIC] = TYPE_NUMERIC_FLOATING_POINT = new TypeInfo(Value.NUMERIC, + Constants.MAX_NUMERIC_PRECISION, Constants.MAX_NUMERIC_PRECISION / 2, null); + infos[Value.REAL] = TYPE_REAL = new TypeInfo(Value.REAL); + infos[Value.DOUBLE] = TYPE_DOUBLE = new TypeInfo(Value.DOUBLE); + infos[Value.DECFLOAT] = TYPE_DECFLOAT = new TypeInfo(Value.DECFLOAT); + TYPE_DECFLOAT_BIGINT = new TypeInfo(Value.DECFLOAT, (long) ValueBigint.DECIMAL_PRECISION); + // DATETIME + infos[Value.DATE] = TYPE_DATE = new TypeInfo(Value.DATE); + infos[Value.TIME] = TYPE_TIME = new TypeInfo(Value.TIME, ValueTime.MAXIMUM_SCALE); + infos[Value.TIME_TZ] = TYPE_TIME_TZ = new TypeInfo(Value.TIME_TZ, ValueTime.MAXIMUM_SCALE); + infos[Value.TIMESTAMP] = TYPE_TIMESTAMP = new TypeInfo(Value.TIMESTAMP, ValueTimestamp.MAXIMUM_SCALE); + infos[Value.TIMESTAMP_TZ] = TYPE_TIMESTAMP_TZ = new TypeInfo(Value.TIMESTAMP_TZ, ValueTimestamp.MAXIMUM_SCALE); + // INTERVAL + for (int i = Value.INTERVAL_YEAR; i <= Value.INTERVAL_MINUTE_TO_SECOND; i++) { + infos[i] = new TypeInfo(i, ValueInterval.MAXIMUM_PRECISION, + IntervalQualifier.valueOf(i - Value.INTERVAL_YEAR).hasSeconds() ? ValueInterval.MAXIMUM_SCALE : -1, + null); + } + TYPE_INTERVAL_DAY = infos[Value.INTERVAL_DAY]; + TYPE_INTERVAL_YEAR_TO_MONTH = infos[Value.INTERVAL_YEAR_TO_MONTH]; + TYPE_INTERVAL_DAY_TO_SECOND = infos[Value.INTERVAL_DAY_TO_SECOND]; + TYPE_INTERVAL_HOUR_TO_SECOND = infos[Value.INTERVAL_HOUR_TO_SECOND]; + // OTHER + infos[Value.JAVA_OBJECT] = TYPE_JAVA_OBJECT = new TypeInfo(Value.JAVA_OBJECT); + infos[Value.ENUM] = TYPE_ENUM_UNDEFINED = new TypeInfo(Value.ENUM); + infos[Value.GEOMETRY] = TYPE_GEOMETRY = new TypeInfo(Value.GEOMETRY); + infos[Value.JSON] = TYPE_JSON = new TypeInfo(Value.JSON); + infos[Value.UUID] = TYPE_UUID = new TypeInfo(Value.UUID); + // COLLECTION + infos[Value.ARRAY] = TYPE_ARRAY_UNKNOWN = new TypeInfo(Value.ARRAY); + infos[Value.ROW] = TYPE_ROW_EMPTY = new TypeInfo(Value.ROW, -1L, -1, // + new ExtTypeInfoRow(new LinkedHashMap<>())); + TYPE_INFOS_BY_VALUE_TYPE = infos; + } + + /** + * Get the data type with parameters object for the given value type and + * maximum parameters. + * + * @param type + * the value type + * @return the data type with parameters object + */ + public static TypeInfo getTypeInfo(int type) { + if (type == Value.UNKNOWN) { + throw DbException.get(ErrorCode.UNKNOWN_DATA_TYPE_1, "?"); + } + if (type >= Value.NULL && type < Value.TYPE_COUNT) { + TypeInfo t = TYPE_INFOS_BY_VALUE_TYPE[type]; + if (t != null) { + return t; + } + } + return TYPE_NULL; + } + + /** + * Get the data type with parameters object for the given value type and the + * specified parameters. + * + * @param type + * the value type + * @param precision + * the precision or {@code -1L} for default + * @param scale + * the scale or {@code -1} for default + * @param extTypeInfo + * the extended type information or null + * @return the data type with parameters object + */ + public static TypeInfo getTypeInfo(int type, long precision, int scale, ExtTypeInfo extTypeInfo) { + switch (type) { + case Value.NULL: + case Value.BOOLEAN: + case Value.TINYINT: + case Value.SMALLINT: + case Value.INTEGER: + case Value.BIGINT: + case Value.DATE: + case Value.UUID: + return TYPE_INFOS_BY_VALUE_TYPE[type]; + case Value.UNKNOWN: + return TYPE_UNKNOWN; + case Value.CHAR: + if (precision < 1) { + return TYPE_CHAR; + } + if (precision > Constants.MAX_STRING_LENGTH) { + precision = Constants.MAX_STRING_LENGTH; + } + return new TypeInfo(Value.CHAR, precision); + case Value.VARCHAR: + if (precision < 1 || precision >= Constants.MAX_STRING_LENGTH) { + if (precision != 0) { + return TYPE_VARCHAR; + } + precision = 1; + } + return new TypeInfo(Value.VARCHAR, precision); + case Value.CLOB: + if (precision < 1) { + return TYPE_CLOB; + } + return new TypeInfo(Value.CLOB, precision); + case Value.VARCHAR_IGNORECASE: + if (precision < 1 || precision >= Constants.MAX_STRING_LENGTH) { + if (precision != 0) { + return TYPE_VARCHAR_IGNORECASE; + } + precision = 1; + } + return new TypeInfo(Value.VARCHAR_IGNORECASE, precision); + case Value.BINARY: + if (precision < 1) { + return TYPE_BINARY; + } + if (precision > Constants.MAX_STRING_LENGTH) { + precision = Constants.MAX_STRING_LENGTH; + } + return new TypeInfo(Value.BINARY, precision); + case Value.VARBINARY: + if (precision < 1 || precision >= Constants.MAX_STRING_LENGTH) { + if (precision != 0) { + return TYPE_VARBINARY; + } + precision = 1; + } + return new TypeInfo(Value.VARBINARY, precision); + case Value.BLOB: + if (precision < 1) { + return TYPE_BLOB; + } + return new TypeInfo(Value.BLOB, precision); + case Value.NUMERIC: + if (precision < 1) { + precision = -1L; + } else if (precision > Constants.MAX_NUMERIC_PRECISION) { + precision = Constants.MAX_NUMERIC_PRECISION; + } + if (scale < 0) { + scale = -1; + } else if (scale > ValueNumeric.MAXIMUM_SCALE) { + scale = ValueNumeric.MAXIMUM_SCALE; + } + return new TypeInfo(Value.NUMERIC, precision, scale, + extTypeInfo instanceof ExtTypeInfoNumeric ? extTypeInfo : null); + case Value.REAL: + if (precision >= 1 && precision <= 24) { + return new TypeInfo(Value.REAL, precision, -1, extTypeInfo); + } + return TYPE_REAL; + case Value.DOUBLE: + if (precision == 0 || precision >= 25 && precision <= 53) { + return new TypeInfo(Value.DOUBLE, precision, -1, extTypeInfo); + } + return TYPE_DOUBLE; + case Value.DECFLOAT: + if (precision < 1) { + precision = -1L; + } else if (precision >= Constants.MAX_NUMERIC_PRECISION) { + return TYPE_DECFLOAT; + } + return new TypeInfo(Value.DECFLOAT, precision, -1, null); + case Value.TIME: + if (scale < 0) { + scale = -1; + } else if (scale >= ValueTime.MAXIMUM_SCALE) { + return TYPE_TIME; + } + return new TypeInfo(Value.TIME, scale); + case Value.TIME_TZ: + if (scale < 0) { + scale = -1; + } else if (scale >= ValueTime.MAXIMUM_SCALE) { + return TYPE_TIME_TZ; + } + return new TypeInfo(Value.TIME_TZ, scale); + case Value.TIMESTAMP: + if (scale < 0) { + scale = -1; + } else if (scale >= ValueTimestamp.MAXIMUM_SCALE) { + return TYPE_TIMESTAMP; + } + return new TypeInfo(Value.TIMESTAMP, scale); + case Value.TIMESTAMP_TZ: + if (scale < 0) { + scale = -1; + } else if (scale >= ValueTimestamp.MAXIMUM_SCALE) { + return TYPE_TIMESTAMP_TZ; + } + return new TypeInfo(Value.TIMESTAMP_TZ, scale); + case Value.INTERVAL_YEAR: + case Value.INTERVAL_MONTH: + case Value.INTERVAL_DAY: + case Value.INTERVAL_HOUR: + case Value.INTERVAL_MINUTE: + case Value.INTERVAL_YEAR_TO_MONTH: + case Value.INTERVAL_DAY_TO_HOUR: + case Value.INTERVAL_DAY_TO_MINUTE: + case Value.INTERVAL_HOUR_TO_MINUTE: + if (precision < 1) { + precision = -1L; + } else if (precision > ValueInterval.MAXIMUM_PRECISION) { + precision = ValueInterval.MAXIMUM_PRECISION; + } + return new TypeInfo(type, precision); + case Value.INTERVAL_SECOND: + case Value.INTERVAL_DAY_TO_SECOND: + case Value.INTERVAL_HOUR_TO_SECOND: + case Value.INTERVAL_MINUTE_TO_SECOND: + if (precision < 1) { + precision = -1L; + } else if (precision > ValueInterval.MAXIMUM_PRECISION) { + precision = ValueInterval.MAXIMUM_PRECISION; + } + if (scale < 0) { + scale = -1; + } else if (scale > ValueInterval.MAXIMUM_SCALE) { + scale = ValueInterval.MAXIMUM_SCALE; + } + return new TypeInfo(type, precision, scale, null); + case Value.JAVA_OBJECT: + if (precision < 1) { + return TYPE_JAVA_OBJECT; + } else if (precision > Constants.MAX_STRING_LENGTH) { + precision = Constants.MAX_STRING_LENGTH; + } + return new TypeInfo(Value.JAVA_OBJECT, precision); + case Value.ENUM: + if (extTypeInfo instanceof ExtTypeInfoEnum) { + return ((ExtTypeInfoEnum) extTypeInfo).getType(); + } else { + return TYPE_ENUM_UNDEFINED; + } + case Value.GEOMETRY: + if (extTypeInfo instanceof ExtTypeInfoGeometry) { + return new TypeInfo(Value.GEOMETRY, -1L, -1, extTypeInfo); + } else { + return TYPE_GEOMETRY; + } + case Value.JSON: + if (precision < 1) { + return TYPE_JSON; + } else if (precision > Constants.MAX_STRING_LENGTH) { + precision = Constants.MAX_STRING_LENGTH; + } + return new TypeInfo(Value.JSON, precision); + case Value.ARRAY: + if (!(extTypeInfo instanceof TypeInfo)) { + throw new IllegalArgumentException(); + } + if (precision < 0 || precision >= Constants.MAX_ARRAY_CARDINALITY) { + precision = -1L; + } + return new TypeInfo(Value.ARRAY, precision, -1, extTypeInfo); + case Value.ROW: + if (!(extTypeInfo instanceof ExtTypeInfoRow)) { + throw new IllegalArgumentException(); + } + return new TypeInfo(Value.ROW, -1L, -1, extTypeInfo); + } + return TYPE_NULL; + } + + /** + * Get the higher data type of all values. + * + * @param values + * the values + * @return the higher data type + */ + public static TypeInfo getHigherType(Typed[] values) { + int cardinality = values.length; + TypeInfo type; + if (cardinality == 0) { + type = TypeInfo.TYPE_NULL; + } else { + type = values[0].getType(); + boolean hasUnknown = false, hasNull = false; + switch (type.getValueType()) { + case Value.UNKNOWN: + hasUnknown = true; + break; + case Value.NULL: + hasNull = true; + } + for (int i = 1; i < cardinality; i++) { + TypeInfo t = values[i].getType(); + switch (t.getValueType()) { + case Value.UNKNOWN: + hasUnknown = true; + break; + case Value.NULL: + hasNull = true; + break; + default: + type = getHigherType(type, t); + } + } + if (type.getValueType() <= Value.NULL && hasUnknown) { + throw DbException.get(ErrorCode.UNKNOWN_DATA_TYPE_1, hasNull ? "NULL, ?" : "?"); + } + } + return type; + } + + /** + * Get the higher data type of two data types. If values need to be + * converted to match the other operands data type, the value with the lower + * order is converted to the value with the higher order. + * + * @param type1 + * the first data type + * @param type2 + * the second data type + * @return the higher data type of the two + */ + public static TypeInfo getHigherType(TypeInfo type1, TypeInfo type2) { + int t1 = type1.getValueType(), t2 = type2.getValueType(), dataType; + if (t1 == t2) { + if (t1 == Value.UNKNOWN) { + throw DbException.get(ErrorCode.UNKNOWN_DATA_TYPE_1, "?, ?"); + } + dataType = t1; + } else { + if (t1 < t2) { + int t = t1; + t1 = t2; + t2 = t; + TypeInfo type = type1; + type1 = type2; + type2 = type; + } + if (t1 == Value.UNKNOWN) { + if (t2 == Value.NULL) { + throw DbException.get(ErrorCode.UNKNOWN_DATA_TYPE_1, "?, NULL"); + } + return type2; + } else if (t2 == Value.UNKNOWN) { + if (t1 == Value.NULL) { + throw DbException.get(ErrorCode.UNKNOWN_DATA_TYPE_1, "NULL, ?"); + } + return type1; + } + if (t2 == Value.NULL) { + return type1; + } + dataType = Value.getHigherOrderKnown(t1, t2); + } + long precision; + switch (dataType) { + case Value.NUMERIC: { + type1 = type1.toNumericType(); + type2 = type2.toNumericType(); + long precision1 = type1.getPrecision(), precision2 = type2.getPrecision(); + int scale1 = type1.getScale(), scale2 = type2.getScale(), scale; + if (scale1 < scale2) { + precision1 += scale2 - scale1; + scale = scale2; + } else { + precision2 += scale1 - scale2; + scale = scale1; + } + return TypeInfo.getTypeInfo(Value.NUMERIC, Math.max(precision1, precision2), scale, null); + } + case Value.REAL: + 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: + return getHigherRow(type1, type2); + default: + precision = Math.max(type1.getPrecision(), type2.getPrecision()); + } + ExtTypeInfo ext1 = type1.extTypeInfo; + return TypeInfo.getTypeInfo(dataType, // + precision, // + Math.max(type1.getScale(), type2.getScale()), // + dataType == t1 && ext1 != null ? ext1 : dataType == t2 ? type2.extTypeInfo : null); + } + + private static TypeInfo getHigherGeometry(TypeInfo type1, TypeInfo type2) { + ExtTypeInfo ext1 = type1.getExtTypeInfo(), ext2 = type2.getExtTypeInfo(); + if (ext1 instanceof ExtTypeInfoGeometry) { + if (ext2 instanceof ExtTypeInfoGeometry) { + ExtTypeInfoGeometry g1 = (ExtTypeInfoGeometry) ext1, g2 = (ExtTypeInfoGeometry) ext2; + Integer srid = g1.getSrid(); + if (!Objects.equals(srid, g2.getSrid())) { + throw DbException.get(ErrorCode.TYPES_ARE_NOT_COMPARABLE_2, type1.getTraceSQL(), + type2.getTraceSQL()); + } + if (g1.getType() == g2.getType()) { + return type1; + } + return srid == null ? TypeInfo.TYPE_GEOMETRY + : TypeInfo.getTypeInfo(Value.GEOMETRY, -1, -1, new ExtTypeInfoGeometry(0, srid)); + } else { + return getHigherGeometry(type1, ext1, type2); + } + } else if (ext2 instanceof ExtTypeInfoGeometry) { + return getHigherGeometry(type2, ext2, type1); + } else { + return TypeInfo.TYPE_GEOMETRY; + } + } + + private static TypeInfo getHigherGeometry(TypeInfo geometryType, ExtTypeInfo geometryExt, TypeInfo otherType) { + if (otherType.getValueType() != Value.GEOMETRY) { + return geometryType; + } + ExtTypeInfoGeometry g = (ExtTypeInfoGeometry) geometryExt; + if (g.getType() == 0) { + return geometryType; + } + Integer srid = g.getSrid(); + return srid == null ? TypeInfo.TYPE_GEOMETRY + : TypeInfo.getTypeInfo(Value.GEOMETRY, -1, -1, new ExtTypeInfoGeometry(0, srid)); + } + + private static int dimensions(TypeInfo type) { + int result; + for (result = 0; type.getValueType() == Value.ARRAY; result++) { + type = (TypeInfo) type.extTypeInfo; + } + return result; + } + + private static TypeInfo getHigherArray(TypeInfo type1, TypeInfo type2, int d1, int d2) { + long precision; + if (d1 > d2) { + d1--; + precision = Math.max(type1.getPrecision(), 1L); + type1 = (TypeInfo) type1.extTypeInfo; + } else if (d1 < d2) { + d2--; + precision = Math.max(1L, type2.getPrecision()); + type2 = (TypeInfo) type2.extTypeInfo; + } else if (d1 > 0) { + d1--; + d2--; + precision = Math.max(type1.getPrecision(), type2.getPrecision()); + type1 = (TypeInfo) type1.extTypeInfo; + type2 = (TypeInfo) type2.extTypeInfo; + } else { + return getHigherType(type1, type2); + } + return TypeInfo.getTypeInfo(Value.ARRAY, precision, 0, getHigherArray(type1, type2, d1, d2)); + } + + private static TypeInfo getHigherRow(TypeInfo type1, TypeInfo type2) { + if (type1.getValueType() != Value.ROW) { + type1 = typeToRow(type1); + } + if (type2.getValueType() != Value.ROW) { + type2 = typeToRow(type2); + } + ExtTypeInfoRow ext1 = (ExtTypeInfoRow) type1.getExtTypeInfo(), ext2 = (ExtTypeInfoRow) type2.getExtTypeInfo(); + if (ext1.equals(ext2)) { + return type1; + } + Set> m1 = ext1.getFields(), m2 = ext2.getFields(); + int degree = m1.size(); + if (m2.size() != degree) { + throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); + } + LinkedHashMap m = new LinkedHashMap<>((int) Math.ceil(degree / .75)); + for (Iterator> i1 = m1.iterator(), i2 = m2.iterator(); i1.hasNext();) { + Map.Entry e1 = i1.next(); + m.put(e1.getKey(), getHigherType(e1.getValue(), i2.next().getValue())); + } + return TypeInfo.getTypeInfo(Value.ROW, 0, 0, new ExtTypeInfoRow(m)); + } + + private static TypeInfo typeToRow(TypeInfo type) { + LinkedHashMap map = new LinkedHashMap<>(2); + map.put("C1", type); + return TypeInfo.getTypeInfo(Value.ROW, 0, 0, new ExtTypeInfoRow(map)); + } + + /** + * Determines whether two specified types are the same data types without + * taking precision or scale into account. + * + * @param t1 + * first data type + * @param t2 + * second data type + * @return whether types are the same + */ + public static boolean areSameTypes(TypeInfo t1, TypeInfo t2) { + for (;;) { + int valueType = t1.getValueType(); + if (valueType != t2.getValueType()) { + return false; + } + ExtTypeInfo ext1 = t1.getExtTypeInfo(), ext2 = t2.getExtTypeInfo(); + if (valueType != Value.ARRAY) { + return Objects.equals(ext1, ext2); + } + t1 = (TypeInfo) ext1; + t2 = (TypeInfo) ext2; + } + } + + /** + * Checks whether two specified types are comparable and throws an exception + * otherwise. + * + * @param t1 + * first data type + * @param t2 + * second data type + * @throws DbException + * if types aren't comparable + */ + public static void checkComparable(TypeInfo t1, TypeInfo t2) { + if (!areComparable(t1, t2)) { + throw DbException.get(ErrorCode.TYPES_ARE_NOT_COMPARABLE_2, t1.getTraceSQL(), t2.getTraceSQL()); + } + } + + /** + * Determines whether two specified types are comparable. + * + * @param t1 + * first data type + * @param t2 + * second data type + * @return whether types are comparable + */ + private static boolean areComparable(TypeInfo t1, TypeInfo t2) { + int vt1 = (t1 = t1.unwrapRow()).getValueType(), vt2 = (t2 = t2.unwrapRow()).getValueType(); + if (vt1 > vt2) { + int vt = vt1; + vt1 = vt2; + vt2 = vt; + TypeInfo t = t1; + t1 = t2; + t2 = t; + } + if (vt1 <= Value.NULL) { + return true; + } + if (vt1 == vt2) { + switch (vt1) { + case Value.ARRAY: + return areComparable((TypeInfo) t1.getExtTypeInfo(), (TypeInfo) t2.getExtTypeInfo()); + case Value.ROW: { + Set> f1 = ((ExtTypeInfoRow) t1.getExtTypeInfo()).getFields(); + Set> f2 = ((ExtTypeInfoRow) t2.getExtTypeInfo()).getFields(); + int degree = f1.size(); + if (f2.size() != degree) { + return false; + } + Iterator> i1 = f1.iterator(), i2 = f2.iterator(); + while (i1.hasNext()) { + if (!areComparable(i1.next().getValue(), i2.next().getValue())) { + return false; + } + } + } + //$FALL-THROUGH$ + default: + return true; + } + } + byte g1 = Value.GROUPS[vt1], g2 = Value.GROUPS[vt2]; + if (g1 == g2) { + switch (g1) { + default: + return true; + case Value.GROUP_DATETIME: + return vt1 != Value.DATE || vt2 != Value.TIME && vt2 != Value.TIME_TZ; + case Value.GROUP_OTHER: + case Value.GROUP_COLLECTION: + return false; + } + } + switch (g1) { + case Value.GROUP_CHARACTER_STRING: + switch (g2) { + case Value.GROUP_NUMERIC: + case Value.GROUP_DATETIME: + case Value.GROUP_INTERVAL_YM: + case Value.GROUP_INTERVAL_DT: + return true; + case Value.GROUP_OTHER: + switch (vt2) { + case Value.ENUM: + case Value.GEOMETRY: + case Value.JSON: + case Value.UUID: + return true; + default: + return false; + } + default: + return false; + } + case Value.GROUP_BINARY_STRING: + switch (vt2) { + case Value.JAVA_OBJECT: + case Value.GEOMETRY: + case Value.JSON: + case Value.UUID: + return true; + default: + return false; + } + } + return false; + } + + /** + * Determines whether two specified types have the same ordering rules. + * + * @param t1 + * first data type + * @param t2 + * second data type + * @return whether types are comparable + */ + public static boolean haveSameOrdering(TypeInfo t1, TypeInfo t2) { + int vt1 = (t1 = t1.unwrapRow()).getValueType(), vt2 = (t2 = t2.unwrapRow()).getValueType(); + if (vt1 > vt2) { + int vt = vt1; + vt1 = vt2; + vt2 = vt; + TypeInfo t = t1; + t1 = t2; + t2 = t; + } + if (vt1 <= Value.NULL) { + return true; + } + if (vt1 == vt2) { + switch (vt1) { + case Value.ARRAY: + return haveSameOrdering((TypeInfo) t1.getExtTypeInfo(), (TypeInfo) t2.getExtTypeInfo()); + case Value.ROW: { + Set> f1 = ((ExtTypeInfoRow) t1.getExtTypeInfo()).getFields(); + Set> f2 = ((ExtTypeInfoRow) t2.getExtTypeInfo()).getFields(); + int degree = f1.size(); + if (f2.size() != degree) { + return false; + } + Iterator> i1 = f1.iterator(), i2 = f2.iterator(); + while (i1.hasNext()) { + if (!haveSameOrdering(i1.next().getValue(), i2.next().getValue())) { + return false; + } + } + } + //$FALL-THROUGH$ + default: + return true; + } + } + byte g1 = Value.GROUPS[vt1], g2 = Value.GROUPS[vt2]; + if (g1 == g2) { + switch (g1) { + default: + return true; + case Value.GROUP_CHARACTER_STRING: + return (vt1 == Value.VARCHAR_IGNORECASE) == (vt2 == Value.VARCHAR_IGNORECASE); + case Value.GROUP_DATETIME: + switch (vt1) { + case Value.DATE: + return vt2 == Value.TIMESTAMP || vt2 == Value.TIMESTAMP_TZ; + case Value.TIME: + case Value.TIME_TZ: + return vt2 == Value.TIME || vt2 == Value.TIME_TZ; + default: // TIMESTAMP TIMESTAMP_TZ + return true; + } + case Value.GROUP_OTHER: + case Value.GROUP_COLLECTION: + return false; + } + } + if (g1 == Value.GROUP_BINARY_STRING) { + switch (vt2) { + case Value.JAVA_OBJECT: + case Value.GEOMETRY: + case Value.JSON: + case Value.UUID: + return true; + default: + return false; + } + } + return false; + } + + private TypeInfo(int valueType) { + this.valueType = valueType; + precision = -1L; + scale = -1; + extTypeInfo = null; + } + + private TypeInfo(int valueType, long precision) { + this.valueType = valueType; + this.precision = precision; + scale = -1; + extTypeInfo = null; + } + + private TypeInfo(int valueType, int scale) { + this.valueType = valueType; + precision = -1L; + this.scale = scale; + extTypeInfo = null; + } + + /** + * Creates new instance of data type with parameters. + * + * @param valueType + * the value type + * @param precision + * the precision + * @param scale + * the scale + * @param extTypeInfo + * the extended type information, or null + */ + public TypeInfo(int valueType, long precision, int scale, ExtTypeInfo extTypeInfo) { + this.valueType = valueType; + this.precision = precision; + this.scale = scale; + this.extTypeInfo = extTypeInfo; + } + + /** + * Returns this type information. + * + * @return this + */ + @Override + public TypeInfo getType() { + return this; + } + + /** + * Returns the value type. + * + * @return the value type + */ + public int getValueType() { + return valueType; + } + + /** + * Returns the precision. + * + * @return the precision + */ + public long getPrecision() { + switch (valueType) { + case Value.UNKNOWN: + return -1L; + case Value.NULL: + return ValueNull.PRECISION; + case Value.CHAR: + case Value.BINARY: + return precision >= 0L ? precision : 1L; + case Value.VARCHAR: + case Value.VARCHAR_IGNORECASE: + case Value.VARBINARY: + case Value.JAVA_OBJECT: + case Value.ENUM: + case Value.GEOMETRY: + case Value.JSON: + return precision >= 0L ? precision : Constants.MAX_STRING_LENGTH; + case Value.CLOB: + case Value.BLOB: + return precision >= 0L ? precision : Long.MAX_VALUE; + case Value.BOOLEAN: + return ValueBoolean.PRECISION; + case Value.TINYINT: + return ValueTinyint.PRECISION; + case Value.SMALLINT: + return ValueSmallint.PRECISION; + case Value.INTEGER: + return ValueInteger.PRECISION; + case Value.BIGINT: + return ValueBigint.PRECISION; + case Value.NUMERIC: + return precision >= 0L ? precision : Constants.MAX_NUMERIC_PRECISION; + case Value.REAL: + return ValueReal.PRECISION; + case Value.DOUBLE: + return ValueDouble.PRECISION; + case Value.DECFLOAT: + return precision >= 0L ? precision : Constants.MAX_NUMERIC_PRECISION; + case Value.DATE: + return ValueDate.PRECISION; + case Value.TIME: { + int s = scale >= 0 ? scale : ValueTime.DEFAULT_SCALE; + return s == 0 ? 8 : 9 + s; + } + case Value.TIME_TZ: { + int s = scale >= 0 ? scale : ValueTime.DEFAULT_SCALE; + return s == 0 ? 14 : 15 + s; + } + case Value.TIMESTAMP: { + int s = scale >= 0 ? scale : ValueTimestamp.DEFAULT_SCALE; + return s == 0 ? 19 : 20 + s; + } + case Value.TIMESTAMP_TZ: { + int s = scale >= 0 ? scale : ValueTimestamp.DEFAULT_SCALE; + return s == 0 ? 25 : 26 + s; + } + case Value.INTERVAL_YEAR: + case Value.INTERVAL_MONTH: + case Value.INTERVAL_DAY: + case Value.INTERVAL_HOUR: + case Value.INTERVAL_MINUTE: + case Value.INTERVAL_SECOND: + case Value.INTERVAL_YEAR_TO_MONTH: + case Value.INTERVAL_DAY_TO_HOUR: + case Value.INTERVAL_DAY_TO_MINUTE: + case Value.INTERVAL_DAY_TO_SECOND: + case Value.INTERVAL_HOUR_TO_MINUTE: + case Value.INTERVAL_HOUR_TO_SECOND: + case Value.INTERVAL_MINUTE_TO_SECOND: + return precision >= 0L ? precision : ValueInterval.DEFAULT_PRECISION; + case Value.ROW: + return Integer.MAX_VALUE; + case Value.UUID: + return ValueUuid.PRECISION; + case Value.ARRAY: + return precision >= 0L ? precision : Constants.MAX_ARRAY_CARDINALITY; + default: + return precision; + } + } + + /** + * Returns the precision, or {@code -1L} if not specified in data type + * definition. + * + * @return the precision, or {@code -1L} if not specified in data type + * definition + */ + public long getDeclaredPrecision() { + return precision; + } + + /** + * Returns the scale. + * + * @return the scale + */ + public int getScale() { + switch (valueType) { + case Value.UNKNOWN: + return -1; + case Value.NULL: + case Value.CHAR: + case Value.VARCHAR: + case Value.CLOB: + case Value.VARCHAR_IGNORECASE: + case Value.BINARY: + case Value.VARBINARY: + case Value.BLOB: + case Value.BOOLEAN: + case Value.TINYINT: + case Value.SMALLINT: + case Value.INTEGER: + case Value.BIGINT: + case Value.REAL: + case Value.DOUBLE: + case Value.DECFLOAT: + case Value.DATE: + case Value.INTERVAL_YEAR: + case Value.INTERVAL_MONTH: + case Value.INTERVAL_DAY: + case Value.INTERVAL_HOUR: + case Value.INTERVAL_MINUTE: + case Value.INTERVAL_YEAR_TO_MONTH: + case Value.INTERVAL_DAY_TO_HOUR: + case Value.INTERVAL_DAY_TO_MINUTE: + case Value.INTERVAL_HOUR_TO_MINUTE: + case Value.JAVA_OBJECT: + case Value.ENUM: + case Value.GEOMETRY: + case Value.JSON: + case Value.UUID: + case Value.ARRAY: + case Value.ROW: + return 0; + case Value.NUMERIC: + return scale >= 0 ? scale : 0; + case Value.TIME: + case Value.TIME_TZ: + return scale >= 0 ? scale : ValueTime.DEFAULT_SCALE; + case Value.TIMESTAMP: + case Value.TIMESTAMP_TZ: + return scale >= 0 ? scale : ValueTimestamp.DEFAULT_SCALE; + case Value.INTERVAL_SECOND: + case Value.INTERVAL_DAY_TO_SECOND: + case Value.INTERVAL_HOUR_TO_SECOND: + case Value.INTERVAL_MINUTE_TO_SECOND: + return scale >= 0 ? scale : ValueInterval.DEFAULT_SCALE; + default: + return scale; + } + } + + /** + * Returns the scale, or {@code -1} if not specified in data type + * definition. + * + * @return the scale, or {@code -1} if not specified in data type definition + */ + public int getDeclaredScale() { + return scale; + } + + /** + * Returns the display size in characters. + * + * @return the display size + */ + public int getDisplaySize() { + switch (valueType) { + case Value.UNKNOWN: + default: + return -1; + case Value.NULL: + return ValueNull.DISPLAY_SIZE; + case Value.CHAR: + return precision >= 0 ? (int) precision : 1; + case Value.VARCHAR: + case Value.VARCHAR_IGNORECASE: + case Value.JSON: + return precision >= 0 ? (int) precision : Constants.MAX_STRING_LENGTH; + case Value.CLOB: + return precision >= 0 && precision <= Integer.MAX_VALUE ? (int) precision : Integer.MAX_VALUE; + case Value.BINARY: + return precision >= 0 ? (int) precision * 2 : 2; + case Value.VARBINARY: + case Value.JAVA_OBJECT: + return precision >= 0 ? (int) precision * 2 : Constants.MAX_STRING_LENGTH * 2; + case Value.BLOB: + return precision >= 0 && precision <= Integer.MAX_VALUE / 2 ? (int) precision * 2 : Integer.MAX_VALUE; + case Value.BOOLEAN: + return ValueBoolean.DISPLAY_SIZE; + case Value.TINYINT: + return ValueTinyint.DISPLAY_SIZE; + case Value.SMALLINT: + return ValueSmallint.DISPLAY_SIZE; + case Value.INTEGER: + return ValueInteger.DISPLAY_SIZE; + case Value.BIGINT: + return ValueBigint.DISPLAY_SIZE; + case Value.NUMERIC: + return precision >= 0 ? (int) precision + 2 : Constants.MAX_NUMERIC_PRECISION + 2; + case Value.REAL: + return ValueReal.DISPLAY_SIZE; + case Value.DOUBLE: + return ValueDouble.DISPLAY_SIZE; + case Value.DECFLOAT: + return precision >= 0 ? (int) precision + 12 : Constants.MAX_NUMERIC_PRECISION + 12; + case Value.DATE: + return ValueDate.PRECISION; + case Value.TIME: { + int s = scale >= 0 ? scale : ValueTime.DEFAULT_SCALE; + return s == 0 ? 8 : 9 + s; + } + case Value.TIME_TZ: { + int s = scale >= 0 ? scale : ValueTime.DEFAULT_SCALE; + return s == 0 ? 14 : 15 + s; + } + case Value.TIMESTAMP: { + int s = scale >= 0 ? scale : ValueTimestamp.DEFAULT_SCALE; + return s == 0 ? 19 : 20 + s; + } + case Value.TIMESTAMP_TZ: { + int s = scale >= 0 ? scale : ValueTimestamp.DEFAULT_SCALE; + return s == 0 ? 25 : 26 + s; + } + case Value.INTERVAL_YEAR: + case Value.INTERVAL_MONTH: + case Value.INTERVAL_DAY: + case Value.INTERVAL_HOUR: + case Value.INTERVAL_MINUTE: + case Value.INTERVAL_SECOND: + case Value.INTERVAL_YEAR_TO_MONTH: + case Value.INTERVAL_DAY_TO_HOUR: + case Value.INTERVAL_DAY_TO_MINUTE: + case Value.INTERVAL_DAY_TO_SECOND: + case Value.INTERVAL_HOUR_TO_MINUTE: + case Value.INTERVAL_HOUR_TO_SECOND: + case Value.INTERVAL_MINUTE_TO_SECOND: + return ValueInterval.getDisplaySize(valueType, + precision >= 0 ? (int) precision : ValueInterval.DEFAULT_PRECISION, + scale >= 0 ? scale : ValueInterval.DEFAULT_SCALE); + case Value.GEOMETRY: + case Value.ARRAY: + case Value.ROW: + return Integer.MAX_VALUE; + case Value.ENUM: + return extTypeInfo != null ? (int) precision : Constants.MAX_STRING_LENGTH; + case Value.UUID: + return ValueUuid.DISPLAY_SIZE; + } + } + + /** + * Returns the extended type information, or null. + * + * @return the extended type information, or null + */ + public ExtTypeInfo getExtTypeInfo() { + return extTypeInfo; + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + switch (valueType) { + case Value.CHAR: + case Value.VARCHAR: + case Value.CLOB: + case Value.VARCHAR_IGNORECASE: + case Value.BINARY: + case Value.VARBINARY: + case Value.BLOB: + case Value.JAVA_OBJECT: + case Value.JSON: + builder.append(Value.getTypeName(valueType)); + if (precision >= 0L) { + builder.append('(').append(precision).append(')'); + } + break; + case Value.NUMERIC: { + if (extTypeInfo != null) { + extTypeInfo.getSQL(builder, sqlFlags); + } else { + builder.append("NUMERIC"); + } + boolean withPrecision = precision >= 0; + boolean withScale = scale >= 0; + if (withPrecision || withScale) { + builder.append('(').append(withPrecision ? precision : Constants.MAX_NUMERIC_PRECISION); + if (withScale) { + builder.append(", ").append(scale); + } + builder.append(')'); + } + break; + } + case Value.REAL: + case Value.DOUBLE: + if (precision < 0) { + builder.append(Value.getTypeName(valueType)); + } else { + builder.append("FLOAT"); + if (precision > 0) { + builder.append('(').append(precision).append(')'); + } + } + break; + case Value.DECFLOAT: + builder.append("DECFLOAT"); + if (precision >= 0) { + builder.append('(').append(precision).append(')'); + } + break; + case Value.TIME: + case Value.TIME_TZ: + builder.append("TIME"); + if (scale >= 0) { + builder.append('(').append(scale).append(')'); + } + if (valueType == Value.TIME_TZ) { + builder.append(" WITH TIME ZONE"); + } + break; + case Value.TIMESTAMP: + case Value.TIMESTAMP_TZ: + builder.append("TIMESTAMP"); + if (scale >= 0) { + builder.append('(').append(scale).append(')'); + } + if (valueType == Value.TIMESTAMP_TZ) { + builder.append(" WITH TIME ZONE"); + } + break; + case Value.INTERVAL_YEAR: + case Value.INTERVAL_MONTH: + case Value.INTERVAL_DAY: + case Value.INTERVAL_HOUR: + case Value.INTERVAL_MINUTE: + case Value.INTERVAL_SECOND: + case Value.INTERVAL_YEAR_TO_MONTH: + case Value.INTERVAL_DAY_TO_HOUR: + case Value.INTERVAL_DAY_TO_MINUTE: + case Value.INTERVAL_DAY_TO_SECOND: + case Value.INTERVAL_HOUR_TO_MINUTE: + case Value.INTERVAL_HOUR_TO_SECOND: + case Value.INTERVAL_MINUTE_TO_SECOND: + IntervalQualifier.valueOf(valueType - Value.INTERVAL_YEAR).getTypeName(builder, (int) precision, scale, + false); + break; + case Value.ENUM: + extTypeInfo.getSQL(builder.append("ENUM"), sqlFlags); + break; + case Value.GEOMETRY: + builder.append("GEOMETRY"); + if (extTypeInfo != null) { + extTypeInfo.getSQL(builder, sqlFlags); + } + break; + case Value.ARRAY: + if (extTypeInfo != null) { + extTypeInfo.getSQL(builder, sqlFlags).append(' '); + } + builder.append("ARRAY"); + if (precision >= 0L) { + builder.append('[').append(precision).append(']'); + } + break; + case Value.ROW: + builder.append("ROW"); + if (extTypeInfo != null) { + extTypeInfo.getSQL(builder, sqlFlags); + } + break; + default: + builder.append(Value.getTypeName(valueType)); + } + return builder; + } + + @Override + public int hashCode() { + int result = 1; + result = 31 * result + valueType; + result = 31 * result + (int) (precision ^ (precision >>> 32)); + result = 31 * result + scale; + result = 31 * result + ((extTypeInfo == null) ? 0 : extTypeInfo.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || obj.getClass() != TypeInfo.class) { + return false; + } + TypeInfo other = (TypeInfo) obj; + return valueType == other.valueType && precision == other.precision && scale == other.scale + && Objects.equals(extTypeInfo, other.extTypeInfo); + } + + /** + * Convert this type information to compatible NUMERIC type information. + * + * @return NUMERIC type information + */ + public TypeInfo toNumericType() { + switch (valueType) { + case Value.BOOLEAN: + case Value.TINYINT: + case Value.SMALLINT: + case Value.INTEGER: + return getTypeInfo(Value.NUMERIC, getDecimalPrecision(), 0, null); + case Value.BIGINT: + return TYPE_NUMERIC_BIGINT; + case Value.NUMERIC: + return this; + case Value.REAL: + // Smallest REAL value is 1.4E-45 with precision 2 and scale 46 + // Largest REAL value is 3.4028235E+38 with precision 8 and scale + // -31 + return getTypeInfo(Value.NUMERIC, 85, 46, null); + case Value.DOUBLE: + // Smallest DOUBLE value is 4.9E-324 with precision 2 and scale 325 + // Largest DOUBLE value is 1.7976931348623157E+308 with precision 17 + // and scale -292 + return getTypeInfo(Value.NUMERIC, 634, 325, null); + default: + return TYPE_NUMERIC_FLOATING_POINT; + } + } + + /** + * Convert this type information to compatible DECFLOAT type information. + * + * @return DECFLOAT type information + */ + public TypeInfo toDecfloatType() { + switch (valueType) { + case Value.BOOLEAN: + case Value.TINYINT: + case Value.SMALLINT: + case Value.INTEGER: + return getTypeInfo(Value.DECFLOAT, getDecimalPrecision(), 0, null); + case Value.BIGINT: + return TYPE_DECFLOAT_BIGINT; + case Value.NUMERIC: + return getTypeInfo(Value.DECFLOAT, getPrecision(), 0, null); + case Value.REAL: + return getTypeInfo(Value.DECFLOAT, ValueReal.DECIMAL_PRECISION, 0, null); + case Value.DOUBLE: + return getTypeInfo(Value.DECFLOAT, ValueReal.DECIMAL_PRECISION, 0, null); + case Value.DECFLOAT: + return this; + default: + return TYPE_DECFLOAT; + } + } + + /** + * Returns unwrapped data type if this data type is a row type with degree 1 + * or this type otherwise. + * + * @return unwrapped data type if this data type is a row type with degree 1 + * or this type otherwise + */ + public TypeInfo unwrapRow() { + if (valueType == Value.ROW) { + Set> fields = ((ExtTypeInfoRow) extTypeInfo).getFields(); + if (fields.size() == 1) { + return fields.iterator().next().getValue().unwrapRow(); + } + } + return this; + } + + /** + * Returns approximate precision in decimal digits for binary numeric data + * types and precision for all other types. + * + * @return precision in decimal digits + */ + public long getDecimalPrecision() { + switch (valueType) { + case Value.TINYINT: + return ValueTinyint.DECIMAL_PRECISION; + case Value.SMALLINT: + return ValueSmallint.DECIMAL_PRECISION; + case Value.INTEGER: + return ValueInteger.DECIMAL_PRECISION; + case Value.BIGINT: + return ValueBigint.DECIMAL_PRECISION; + case Value.REAL: + return ValueReal.DECIMAL_PRECISION; + case Value.DOUBLE: + return ValueDouble.DECIMAL_PRECISION; + default: + return precision; + } + } + + /** + * Returns the declared name of this data type with precision, scale, + * length, cardinality etc. parameters removed, excluding parameters of ENUM + * data type, GEOMETRY data type, ARRAY elements, and ROW fields. + * + * @return the declared name + */ + public String getDeclaredTypeName() { + switch (valueType) { + case Value.NUMERIC: + return extTypeInfo != null ? "DECIMAL" : "NUMERIC"; + case Value.REAL: + case Value.DOUBLE: + if (extTypeInfo != null) { + return "FLOAT"; + } + break; + case Value.ENUM: + case Value.GEOMETRY: + case Value.ROW: + return getSQL(DEFAULT_SQL_FLAGS); + case Value.ARRAY: + TypeInfo typeInfo = (TypeInfo) extTypeInfo; + // Use full type names with parameters for elements + return typeInfo.getSQL(new StringBuilder(), DEFAULT_SQL_FLAGS).append(" ARRAY").toString(); + } + return Value.getTypeName(valueType); + } + +} diff --git a/h2/src/main/org/h2/value/Typed.java b/h2/src/main/org/h2/value/Typed.java new file mode 100644 index 0000000..8ec898e --- /dev/null +++ b/h2/src/main/org/h2/value/Typed.java @@ -0,0 +1,20 @@ +/* + * 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.value; + +/** + * An object with data type. + */ +public interface Typed { + + /** + * Returns the data type. + * + * @return the data type + */ + TypeInfo getType(); + +} diff --git a/h2/src/main/org/h2/value/Value.java b/h2/src/main/org/h2/value/Value.java new file mode 100644 index 0000000..1ed983f --- /dev/null +++ b/h2/src/main/org/h2/value/Value.java @@ -0,0 +1,2782 @@ +/* + * 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.value; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.lang.ref.SoftReference; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.h2.api.ErrorCode; +import org.h2.api.IntervalQualifier; +import org.h2.engine.CastDataProvider; +import org.h2.engine.Mode.CharPadding; +import org.h2.engine.SysProperties; +import org.h2.message.DbException; +import org.h2.store.DataHandler; +import org.h2.util.Bits; +import org.h2.util.DateTimeUtils; +import org.h2.util.HasSQL; +import org.h2.util.IntervalUtils; +import org.h2.util.JdbcUtils; +import org.h2.util.MathUtils; +import org.h2.util.StringUtils; +import org.h2.util.geometry.GeoJsonUtils; +import org.h2.util.json.JsonConstructorUtils; +import org.h2.value.lob.LobData; +import org.h2.value.lob.LobDataDatabase; +import org.h2.value.lob.LobDataInMemory; + +/** + * This is the base class for all value classes. + * It provides conversion and comparison methods. + * + * @author Thomas Mueller + * @author Noel Grandin + * @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888 + */ +public abstract class Value extends VersionedValue implements HasSQL, Typed { + + /** + * The data type is unknown at this time. + */ + public static final int UNKNOWN = -1; + + /** + * The value type for NULL. + */ + public static final int NULL = UNKNOWN + 1; + + /** + * The value type for CHARACTER values. + */ + public static final int CHAR = NULL + 1; + + /** + * The value type for CHARACTER VARYING values. + */ + public static final int VARCHAR = CHAR + 1; + + /** + * The value type for CHARACTER LARGE OBJECT values. + */ + public static final int CLOB = VARCHAR + 1; + + /** + * The value type for VARCHAR_IGNORECASE values. + */ + public static final int VARCHAR_IGNORECASE = CLOB + 1; + + /** + * The value type for BINARY values. + */ + public static final int BINARY = VARCHAR_IGNORECASE + 1; + + /** + * The value type for BINARY VARYING values. + */ + public static final int VARBINARY = BINARY + 1; + + /** + * The value type for BINARY LARGE OBJECT values. + */ + public static final int BLOB = VARBINARY + 1; + + /** + * The value type for BOOLEAN values. + */ + public static final int BOOLEAN = BLOB + 1; + + /** + * The value type for TINYINT values. + */ + public static final int TINYINT = BOOLEAN + 1; + + /** + * The value type for SMALLINT values. + */ + public static final int SMALLINT = TINYINT + 1; + + /** + * The value type for INTEGER values. + */ + public static final int INTEGER = SMALLINT + 1; + + /** + * The value type for BIGINT values. + */ + public static final int BIGINT = INTEGER + 1; + + /** + * The value type for NUMERIC values. + */ + public static final int NUMERIC = BIGINT + 1; + + /** + * The value type for REAL values. + */ + public static final int REAL = NUMERIC + 1; + + /** + * The value type for DOUBLE PRECISION values. + */ + public static final int DOUBLE = REAL + 1; + + /** + * The value type for DECFLOAT values. + */ + public static final int DECFLOAT = DOUBLE + 1; + + /** + * The value type for DATE values. + */ + public static final int DATE = DECFLOAT + 1; + + /** + * The value type for TIME values. + */ + public static final int TIME = DATE + 1; + + /** + * The value type for TIME WITH TIME ZONE values. + */ + public static final int TIME_TZ = TIME + 1; + + /** + * The value type for TIMESTAMP values. + */ + public static final int TIMESTAMP = TIME_TZ + 1; + + /** + * The value type for TIMESTAMP WITH TIME ZONE values. + */ + public static final int TIMESTAMP_TZ = TIMESTAMP + 1; + + /** + * The value type for {@code INTERVAL YEAR} values. + */ + public static final int INTERVAL_YEAR = TIMESTAMP_TZ + 1; + + /** + * The value type for {@code INTERVAL MONTH} values. + */ + public static final int INTERVAL_MONTH = INTERVAL_YEAR + 1; + + /** + * The value type for {@code INTERVAL DAY} values. + */ + public static final int INTERVAL_DAY = INTERVAL_MONTH + 1; + + /** + * The value type for {@code INTERVAL HOUR} values. + */ + public static final int INTERVAL_HOUR = INTERVAL_DAY + 1; + + /** + * The value type for {@code INTERVAL MINUTE} values. + */ + public static final int INTERVAL_MINUTE = INTERVAL_HOUR + 1; + + /** + * The value type for {@code INTERVAL SECOND} values. + */ + public static final int INTERVAL_SECOND = INTERVAL_MINUTE + 1; + + /** + * The value type for {@code INTERVAL YEAR TO MONTH} values. + */ + public static final int INTERVAL_YEAR_TO_MONTH = INTERVAL_SECOND + 1; + + /** + * The value type for {@code INTERVAL DAY TO HOUR} values. + */ + public static final int INTERVAL_DAY_TO_HOUR = INTERVAL_YEAR_TO_MONTH + 1; + + /** + * The value type for {@code INTERVAL DAY TO MINUTE} values. + */ + public static final int INTERVAL_DAY_TO_MINUTE = INTERVAL_DAY_TO_HOUR + 1; + + /** + * The value type for {@code INTERVAL DAY TO SECOND} values. + */ + public static final int INTERVAL_DAY_TO_SECOND = INTERVAL_DAY_TO_MINUTE + 1; + + /** + * The value type for {@code INTERVAL HOUR TO MINUTE} values. + */ + public static final int INTERVAL_HOUR_TO_MINUTE = INTERVAL_DAY_TO_SECOND + 1; + + /** + * The value type for {@code INTERVAL HOUR TO SECOND} values. + */ + public static final int INTERVAL_HOUR_TO_SECOND = INTERVAL_HOUR_TO_MINUTE + 1; + + /** + * The value type for {@code INTERVAL MINUTE TO SECOND} values. + */ + public static final int INTERVAL_MINUTE_TO_SECOND = INTERVAL_HOUR_TO_SECOND + 1; + + /** + * The value type for JAVA_OBJECT values. + */ + public static final int JAVA_OBJECT = INTERVAL_MINUTE_TO_SECOND + 1; + + /** + * The value type for ENUM values. + */ + public static final int ENUM = JAVA_OBJECT + 1; + + /** + * The value type for string values with a fixed size. + */ + public static final int GEOMETRY = ENUM + 1; + + /** + * The value type for JSON values. + */ + public static final int JSON = GEOMETRY + 1; + + /** + * The value type for UUID values. + */ + public static final int UUID = JSON + 1; + + /** + * The value type for ARRAY values. + */ + public static final int ARRAY = UUID + 1; + + /** + * The value type for ROW values. + */ + public static final int ROW = ARRAY + 1; + + /** + * The number of value types. + */ + public static final int TYPE_COUNT = ROW + 1; + + /** + * Group for untyped NULL data type. + */ + static final int GROUP_NULL = 0; + + /** + * Group for character string data types. + */ + static final int GROUP_CHARACTER_STRING = GROUP_NULL + 1; + + /** + * Group for binary string data types. + */ + static final int GROUP_BINARY_STRING = GROUP_CHARACTER_STRING + 1; + + /** + * Group for BINARY data type. + */ + static final int GROUP_BOOLEAN = GROUP_BINARY_STRING + 1; + + /** + * Group for numeric data types. + */ + static final int GROUP_NUMERIC = GROUP_BOOLEAN + 1; + + /** + * Group for datetime data types. + */ + static final int GROUP_DATETIME = GROUP_NUMERIC + 1; + + /** + * Group for year-month interval data types. + */ + static final int GROUP_INTERVAL_YM = GROUP_DATETIME + 1; + + /** + * Group for day-time interval data types. + */ + static final int GROUP_INTERVAL_DT = GROUP_INTERVAL_YM + 1; + + /** + * Group for other data types (JAVA_OBJECT, UUID, GEOMETRY, ENUM, JSON). + */ + static final int GROUP_OTHER = GROUP_INTERVAL_DT + 1; + + /** + * Group for collection data types (ARRAY, ROW). + */ + static final int GROUP_COLLECTION = GROUP_OTHER + 1; + + static final byte GROUPS[] = { + // NULL + GROUP_NULL, + // CHAR, VARCHAR, CLOB, VARCHAR_IGNORECASE + GROUP_CHARACTER_STRING, GROUP_CHARACTER_STRING, GROUP_CHARACTER_STRING, GROUP_CHARACTER_STRING, + // BINARY, VARBINARY, BLOB + GROUP_BINARY_STRING, GROUP_BINARY_STRING, GROUP_BINARY_STRING, + // BOOLEAN + GROUP_BOOLEAN, + // TINYINT, SMALLINT, INTEGER, BIGINT, NUMERIC, REAL, DOUBLE, DECFLOAT + GROUP_NUMERIC, GROUP_NUMERIC, GROUP_NUMERIC, GROUP_NUMERIC, GROUP_NUMERIC, GROUP_NUMERIC, GROUP_NUMERIC, + GROUP_NUMERIC, + // DATE, TIME, TIME_TZ, TIMESTAMP, TIMESTAMP_TZ + GROUP_DATETIME, GROUP_DATETIME, GROUP_DATETIME, GROUP_DATETIME, GROUP_DATETIME, + // INTERVAL_YEAR, INTERVAL_MONTH + GROUP_INTERVAL_YM, GROUP_INTERVAL_YM, + // INTERVAL_DAY, INTERVAL_HOUR, INTERVAL_MINUTE, INTERVAL_SECOND + GROUP_INTERVAL_DT, GROUP_INTERVAL_DT, GROUP_INTERVAL_DT, GROUP_INTERVAL_DT, + // INTERVAL_YEAR_TO_MONTH + GROUP_INTERVAL_YM, + // INTERVAL_DAY_TO_HOUR, INTERVAL_DAY_TO_MINUTE, + // INTERVAL_DAY_TO_SECOND, INTERVAL_HOUR_TO_MINUTE, + // INTERVAL_HOUR_TO_SECOND, INTERVAL_MINUTE_TO_SECOND + GROUP_INTERVAL_DT, GROUP_INTERVAL_DT, GROUP_INTERVAL_DT, GROUP_INTERVAL_DT, GROUP_INTERVAL_DT, + GROUP_INTERVAL_DT, + // JAVA_OBJECT, ENUM, GEOMETRY, JSON, UUID + GROUP_OTHER, GROUP_OTHER, GROUP_OTHER, GROUP_OTHER, GROUP_OTHER, + // ARRAY, ROW + GROUP_COLLECTION, GROUP_COLLECTION, + // + }; + + private static final String NAMES[] = { + "UNKNOWN", + "NULL", // + "CHARACTER", "CHARACTER VARYING", "CHARACTER LARGE OBJECT", "VARCHAR_IGNORECASE", // + "BINARY", "BINARY VARYING", "BINARY LARGE OBJECT", // + "BOOLEAN", // + "TINYINT", "SMALLINT", "INTEGER", "BIGINT", // + "NUMERIC", "REAL", "DOUBLE PRECISION", "DECFLOAT", // + "DATE", "TIME", "TIME WITH TIME ZONE", "TIMESTAMP", "TIMESTAMP WITH TIME ZONE", // + "INTERVAL YEAR", "INTERVAL MONTH", // + "INTERVAL DAY", "INTERVAL HOUR", "INTERVAL MINUTE", "INTERVAL SECOND", // + "INTERVAL YEAR TO MONTH", // + "INTERVAL DAY TO HOUR", "INTERVAL DAY TO MINUTE", "INTERVAL DAY TO SECOND", // + "INTERVAL HOUR TO MINUTE", "INTERVAL HOUR TO SECOND", "INTERVAL MINUTE TO SECOND", // + "JAVA_OBJECT", "ENUM", "GEOMETRY", "JSON", "UUID", // + "ARRAY", "ROW", // + }; + + /** + * Empty array of values. + */ + public static final Value[] EMPTY_VALUES = new Value[0]; + + private static SoftReference softCache; + + static final BigDecimal MAX_LONG_DECIMAL = BigDecimal.valueOf(Long.MAX_VALUE); + + /** + * The smallest Long value, as a BigDecimal. + */ + public static final BigDecimal MIN_LONG_DECIMAL = BigDecimal.valueOf(Long.MIN_VALUE); + + /** + * Convert a value to the specified type without taking scale and precision + * into account. + */ + static final int CONVERT_TO = 0; + + /** + * Cast a value to the specified type. The scale is set if applicable. The + * value is truncated to a required precision. + */ + static final int CAST_TO = 1; + + /** + * Cast a value to the specified type for assignment. The scale is set if + * applicable. If precision is too large an exception is thrown. + */ + static final int ASSIGN_TO = 2; + + /** + * Returns name of the specified data type. + * + * @param valueType + * the value type + * @return the name + */ + public static String getTypeName(int valueType) { + return NAMES[valueType + 1]; + } + + /** + * Check the range of the parameters. + * + * @param zeroBasedOffset the offset (0 meaning no offset) + * @param length the length of the target + * @param dataSize the length of the source + */ + static void rangeCheck(long zeroBasedOffset, long length, long dataSize) { + if ((zeroBasedOffset | length) < 0 || length > dataSize - zeroBasedOffset) { + if (zeroBasedOffset < 0 || zeroBasedOffset > dataSize) { + throw DbException.getInvalidValueException("offset", zeroBasedOffset + 1); + } + throw DbException.getInvalidValueException("length", length); + } + } + + @Override + public abstract TypeInfo getType(); + + /** + * Get the value type. + * + * @return the value type + */ + public abstract int getValueType(); + + /** + * Get the memory used by this object. + * + * @return the memory used in bytes + */ + public int getMemory() { + /* + * Java 11 with -XX:-UseCompressedOops for all values up to ValueLong + * and ValueDouble. + */ + return 24; + } + + @Override + public abstract int hashCode(); + + /** + * Check if the two values have the same hash code. No data conversion is + * made; this method returns false if the other object is not of the same + * class. For some values, compareTo may return 0 even if equals return + * false. Example: ValueDecimal 0.0 and 0.00. + * + * @param other the other value + * @return true if they are equal + */ + @Override + public abstract boolean equals(Object other); + + /** + * Get the higher value order type of two value types. If values need to be + * converted to match the other operands value type, the value with the + * lower order is converted to the value with the higher order. + * + * @param t1 the first value type + * @param t2 the second value type + * @return the higher value type of the two + */ + public static int getHigherOrder(int t1, int t2) { + if (t1 == t2) { + if (t1 == UNKNOWN) { + throw DbException.get(ErrorCode.UNKNOWN_DATA_TYPE_1, "?, ?"); + } + return t1; + } + if (t1 < t2) { + int t = t1; + t1 = t2; + t2 = t; + } + if (t1 == UNKNOWN) { + if (t2 == NULL) { + throw DbException.get(ErrorCode.UNKNOWN_DATA_TYPE_1, "?, NULL"); + } + return t2; + } else if (t2 == UNKNOWN) { + if (t1 == NULL) { + throw DbException.get(ErrorCode.UNKNOWN_DATA_TYPE_1, "NULL, ?"); + } + return t1; + } + if (t2 == NULL) { + return t1; + } + return getHigherOrderKnown(t1, t2); + } + + static int getHigherOrderKnown(int t1, int t2) { + int g1 = GROUPS[t1], g2 = GROUPS[t2]; + switch (g1) { + case GROUP_BOOLEAN: + if (g2 == GROUP_BINARY_STRING) { + throw getDataTypeCombinationException(BOOLEAN, t2); + } + break; + case GROUP_NUMERIC: + return getHigherNumeric(t1, t2, g2); + case GROUP_DATETIME: + return getHigherDateTime(t1, t2, g2); + case GROUP_INTERVAL_YM: + return getHigherIntervalYearMonth(t1, t2, g2); + case GROUP_INTERVAL_DT: + return getHigherIntervalDayTime(t1, t2, g2); + case GROUP_OTHER: + return getHigherOther(t1, t2, g2); + } + return t1; + } + + private static int getHigherNumeric(int t1, int t2, int g2) { + if (g2 == GROUP_NUMERIC) { + switch (t1) { + case REAL: + switch (t2) { + case INTEGER: + return DOUBLE; + case BIGINT: + case NUMERIC: + return DECFLOAT; + } + break; + case DOUBLE: + switch (t2) { + case BIGINT: + case NUMERIC: + return DECFLOAT; + } + break; + } + } else if (g2 == GROUP_BINARY_STRING) { + throw getDataTypeCombinationException(t1, t2); + } + return t1; + } + + private static int getHigherDateTime(int t1, int t2, int g2) { + if (g2 == GROUP_CHARACTER_STRING) { + return t1; + } + if (g2 != GROUP_DATETIME) { + throw getDataTypeCombinationException(t1, t2); + } + switch (t1) { + case TIME: + if (t2 == DATE) { + return TIMESTAMP; + } + break; + case TIME_TZ: + if (t2 == DATE) { + return TIMESTAMP_TZ; + } + break; + case TIMESTAMP: + if (t2 == TIME_TZ) { + return TIMESTAMP_TZ; + } + } + return t1; + } + + private static int getHigherIntervalYearMonth(int t1, int t2, int g2) { + switch (g2) { + case GROUP_INTERVAL_YM: + if (t1 == INTERVAL_MONTH && t2 == INTERVAL_YEAR) { + return INTERVAL_YEAR_TO_MONTH; + } + //$FALL-THROUGH$ + case GROUP_CHARACTER_STRING: + case GROUP_NUMERIC: + return t1; + default: + throw getDataTypeCombinationException(t1, t2); + } + } + + private static int getHigherIntervalDayTime(int t1, int t2, int g2) { + switch (g2) { + case GROUP_INTERVAL_DT: + break; + case GROUP_CHARACTER_STRING: + case GROUP_NUMERIC: + return t1; + default: + throw getDataTypeCombinationException(t1, t2); + } + switch (t1) { + case INTERVAL_HOUR: + return INTERVAL_DAY_TO_HOUR; + case INTERVAL_MINUTE: + if (t2 == INTERVAL_DAY) { + return INTERVAL_DAY_TO_MINUTE; + } + return INTERVAL_HOUR_TO_MINUTE; + case INTERVAL_SECOND: + if (t2 == INTERVAL_DAY) { + return INTERVAL_DAY_TO_SECOND; + } + if (t2 == INTERVAL_HOUR) { + return INTERVAL_HOUR_TO_SECOND; + } + return INTERVAL_MINUTE_TO_SECOND; + case INTERVAL_DAY_TO_HOUR: + if (t2 == INTERVAL_MINUTE) { + return INTERVAL_DAY_TO_MINUTE; + } + if (t2 == INTERVAL_SECOND) { + return INTERVAL_DAY_TO_SECOND; + } + break; + case INTERVAL_DAY_TO_MINUTE: + if (t2 == INTERVAL_SECOND) { + return INTERVAL_DAY_TO_SECOND; + } + break; + case INTERVAL_HOUR_TO_MINUTE: + switch (t2) { + case INTERVAL_DAY: + case INTERVAL_DAY_TO_HOUR: + case INTERVAL_DAY_TO_MINUTE: + return INTERVAL_DAY_TO_MINUTE; + case INTERVAL_SECOND: + return INTERVAL_HOUR_TO_SECOND; + case INTERVAL_DAY_TO_SECOND: + return INTERVAL_DAY_TO_SECOND; + } + break; + case INTERVAL_HOUR_TO_SECOND: + switch (t2) { + case INTERVAL_DAY: + case INTERVAL_DAY_TO_HOUR: + case INTERVAL_DAY_TO_MINUTE: + case INTERVAL_DAY_TO_SECOND: + return INTERVAL_DAY_TO_SECOND; + } + break; + case INTERVAL_MINUTE_TO_SECOND: + switch (t2) { + case INTERVAL_DAY: + case INTERVAL_DAY_TO_HOUR: + case INTERVAL_DAY_TO_MINUTE: + case INTERVAL_DAY_TO_SECOND: + return INTERVAL_DAY_TO_SECOND; + case INTERVAL_HOUR: + case INTERVAL_HOUR_TO_MINUTE: + case INTERVAL_HOUR_TO_SECOND: + return INTERVAL_HOUR_TO_SECOND; + } + } + return t1; + } + + private static int getHigherOther(int t1, int t2, int g2) { + switch (t1) { + case JAVA_OBJECT: + if (g2 != GROUP_BINARY_STRING) { + throw getDataTypeCombinationException(t1, t2); + } + break; + case ENUM: + if (g2 != GROUP_CHARACTER_STRING && (g2 != GROUP_NUMERIC || t2 > INTEGER)) { + throw getDataTypeCombinationException(t1, t2); + } + break; + case GEOMETRY: + if (g2 != GROUP_CHARACTER_STRING && g2 != GROUP_BINARY_STRING) { + throw getDataTypeCombinationException(t1, t2); + } + break; + case JSON: + switch (g2) { + case GROUP_DATETIME: + case GROUP_INTERVAL_YM: + case GROUP_INTERVAL_DT: + case GROUP_OTHER: + throw getDataTypeCombinationException(t1, t2); + } + break; + case UUID: + switch (g2) { + case GROUP_CHARACTER_STRING: + case GROUP_BINARY_STRING: + break; + case GROUP_OTHER: + if (t2 == JAVA_OBJECT) { + break; + } + //$FALL-THROUGH$ + default: + throw getDataTypeCombinationException(t1, t2); + } + } + return t1; + } + + private static DbException getDataTypeCombinationException(int t1, int t2) { + return DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, getTypeName(t1) + ", " + getTypeName(t2)); + } + + /** + * Check if a value is in the cache that is equal to this value. If yes, + * this value should be used to save memory. If the value is not in the + * cache yet, it is added. + * + * @param v the value to look for + * @return the value in the cache or the value passed + */ + static Value cache(Value v) { + if (SysProperties.OBJECT_CACHE) { + int hash = v.hashCode(); + Value[] cache; + if (softCache == null || (cache = softCache.get()) == null) { + cache = new Value[SysProperties.OBJECT_CACHE_SIZE]; + softCache = new SoftReference<>(cache); + } + int index = hash & (SysProperties.OBJECT_CACHE_SIZE - 1); + Value cached = cache[index]; + if (cached != null) { + if (cached.getValueType() == v.getValueType() && v.equals(cached)) { + // cacheHit++; + return cached; + } + } + // cacheMiss++; + // cache[cacheCleaner] = null; + // cacheCleaner = (cacheCleaner + 1) & + // (Constants.OBJECT_CACHE_SIZE - 1); + cache[index] = v; + } + return v; + } + + /** + * Clear the value cache. Used for testing. + */ + public static void clearCache() { + softCache = null; + } + + /** + * Get the value as a string. + * + * @return the string + */ + public abstract String getString(); + + public Reader getReader() { + return new StringReader(getString()); + } + + /** + * Get the reader + * + * @param oneBasedOffset the offset (1 means no offset) + * @param length the requested length + * @return the new reader + */ + public Reader getReader(long oneBasedOffset, long length) { + String string = getString(); + long zeroBasedOffset = oneBasedOffset - 1; + rangeCheck(zeroBasedOffset, length, string.length()); + int offset = (int) zeroBasedOffset; + return new StringReader(string.substring(offset, offset + (int) length)); + } + + public byte[] getBytes() { + throw getDataConversionError(VARBINARY); + } + + public byte[] getBytesNoCopy() { + return getBytes(); + } + + public InputStream getInputStream() { + return new ByteArrayInputStream(getBytesNoCopy()); + } + + /** + * Get the input stream + * + * @param oneBasedOffset the offset (1 means no offset) + * @param length the requested length + * @return the new input stream + */ + public InputStream getInputStream(long oneBasedOffset, long length) { + byte[] bytes = getBytesNoCopy(); + long zeroBasedOffset = oneBasedOffset - 1; + rangeCheck(zeroBasedOffset, length, bytes.length); + return new ByteArrayInputStream(bytes, (int) zeroBasedOffset, (int) length); + } + + /** + * Returns this value as a Java {@code boolean} value. + * + * @throws DbException + * if this value is {@code NULL} or cannot be casted to + * {@code BOOLEAN} + * @return value + * @see #isTrue() + * @see #isFalse() + */ + public boolean getBoolean() { + return convertToBoolean().getBoolean(); + } + + /** + * Returns this value as a Java {@code byte} value. + * + * @throws DbException + * if this value is {@code NULL} or cannot be casted to + * {@code TINYINT} + * @return value + */ + public byte getByte() { + return convertToTinyint(null).getByte(); + } + + /** + * Returns this value as a Java {@code short} value. + * + * @throws DbException + * if this value is {@code NULL} or cannot be casted to + * {@code SMALLINT} + * @return value + */ + public short getShort() { + return convertToSmallint(null).getShort(); + } + + /** + * Returns this value as a Java {@code int} value. + * + * @throws DbException + * if this value is {@code NULL} or cannot be casted to + * {@code INTEGER} + * @return value + */ + public int getInt() { + return convertToInt(null).getInt(); + } + + /** + * Returns this value as a Java {@code long} value. + * + * @throws DbException + * if this value is {@code NULL} or cannot be casted to + * {@code BIGINT} + * @return value + */ + public long getLong() { + return convertToBigint(null).getLong(); + } + + public BigDecimal getBigDecimal() { + throw getDataConversionError(NUMERIC); + } + + /** + * Returns this value as a Java {@code float} value. + * + * @throws DbException + * if this value is {@code NULL} or cannot be casted to + * {@code REAL} + * @return value + */ + public float getFloat() { + throw getDataConversionError(REAL); + } + + /** + * Returns this value as a Java {@code double} value. + * + * @throws DbException + * if this value is {@code NULL} or cannot be casted to + * {@code DOUBLE PRECISION} + * @return value + */ + public double getDouble() { + throw getDataConversionError(DOUBLE); + } + + /** + * Add a value and return the result. + * + * @param v the value to add + * @return the result + */ + public Value add(@SuppressWarnings("unused") Value v) { + throw getUnsupportedExceptionForOperation("+"); + } + + public int getSignum() { + throw getUnsupportedExceptionForOperation("SIGNUM"); + } + + /** + * Return -value if this value support arithmetic operations. + * + * @return the negative + */ + public Value negate() { + throw getUnsupportedExceptionForOperation("NEG"); + } + + /** + * Subtract a value and return the result. + * + * @param v the value to subtract + * @return the result + */ + public Value subtract(@SuppressWarnings("unused") Value v) { + throw getUnsupportedExceptionForOperation("-"); + } + + /** + * Divide by a value and return the result. + * + * @param v the divisor + * @param quotientType the type of quotient (used only to read precision and scale + * when applicable) + * @return the result + */ + public Value divide(@SuppressWarnings("unused") Value v, TypeInfo quotientType) { + throw getUnsupportedExceptionForOperation("/"); + } + + /** + * Multiply with a value and return the result. + * + * @param v the value to multiply with + * @return the result + */ + public Value multiply(@SuppressWarnings("unused") Value v) { + throw getUnsupportedExceptionForOperation("*"); + } + + /** + * Take the modulus with a value and return the result. + * + * @param v the value to take the modulus with + * @return the result + */ + public Value modulus(@SuppressWarnings("unused") Value v) { + throw getUnsupportedExceptionForOperation("%"); + } + + /** + * Convert a value to the specified type without taking scale and precision + * into account. + * + * @param targetType the type of the returned value + * @return the converted value + */ + public final Value convertTo(int targetType) { + return convertTo(targetType, null); + } + + /** + * Convert a value to the specified type without taking scale and precision + * into account. + * + * @param targetType the type of the returned value + * @return the converted value + */ + public final Value convertTo(TypeInfo targetType) { + return convertTo(targetType, null, CONVERT_TO, null); + } + + /** + * Convert a value to the specified type without taking scale and precision + * into account. + * + * @param targetType the type of the returned value + * @param provider the cast information provider + * @return the converted value + */ + public final Value convertTo(int targetType, CastDataProvider provider) { + switch (targetType) { + case ARRAY: + return convertToAnyArray(provider); + case ROW: + return convertToAnyRow(); + default: + return convertTo(TypeInfo.getTypeInfo(targetType), provider, CONVERT_TO, null); + } + } + + /** + * Convert a value to the specified type without taking scale and precision + * into account. + * + * @param targetType + * the type of the returned value + * @param provider + * the cast information provider + * @return the converted value + */ + public final Value convertTo(TypeInfo targetType, CastDataProvider provider) { + return convertTo(targetType, provider, CONVERT_TO, null); + } + + /** + * Convert a value to the specified type without taking scale and precision + * into account. + * + * @param targetType + * the type of the returned value + * @param provider + * the cast information provider + * @param column + * the column, used to improve the error message if conversion + * fails + * @return the converted value + */ + public final Value convertTo(TypeInfo targetType, CastDataProvider provider, Object column) { + return convertTo(targetType, provider, CONVERT_TO, column); + } + + /** + * Convert this value to any ARRAY data type. + * + * @param provider + * the cast information provider + * @return a row value + */ + public final ValueArray convertToAnyArray(CastDataProvider provider) { + if (getValueType() == Value.ARRAY) { + return (ValueArray) this; + } + return ValueArray.get(this.getType(), new Value[] { this }, provider); + } + + /** + * Convert this value to any ROW data type. + * + * @return a row value + */ + public final ValueRow convertToAnyRow() { + if (getValueType() == Value.ROW) { + return (ValueRow) this; + } + return ValueRow.get(new Value[] { this }); + } + + /** + * Cast a value to the specified type. The scale is set if applicable. The + * value is truncated to the required precision. + * + * @param targetType + * the type of the returned value + * @param provider + * the cast information provider + * @return the converted value + */ + public final Value castTo(TypeInfo targetType, CastDataProvider provider) { + return convertTo(targetType, provider, CAST_TO, null); + } + + /** + * Cast a value to the specified type for assignment. The scale is set if + * applicable. If precision is too large an exception is thrown. + * + * @param targetType + * the type of the returned value + * @param provider + * the cast information provider + * @param column + * the column, used to improve the error message if conversion + * fails + * @return the converted value + */ + public final Value convertForAssignTo(TypeInfo targetType, CastDataProvider provider, Object column) { + return convertTo(targetType, provider, ASSIGN_TO, column); + } + + /** + * Convert a value to the specified type. + * + * @param targetType the type of the returned value + * @param provider the cast information provider + * @param conversionMode conversion mode + * @param column the column (if any), used to improve the error message if conversion fails + * @return the converted value + */ + private Value convertTo(TypeInfo targetType, CastDataProvider provider, int conversionMode, Object column) { + int valueType = getValueType(), targetValueType; + if (valueType == NULL + || valueType == (targetValueType = targetType.getValueType()) && conversionMode == CONVERT_TO + && targetType.getExtTypeInfo() == null && valueType != CHAR) { + return this; + } + switch (targetValueType) { + case NULL: + return ValueNull.INSTANCE; + case CHAR: + return convertToChar(targetType, provider, conversionMode, column); + case VARCHAR: + return convertToVarchar(targetType, provider, conversionMode, column); + case CLOB: + return convertToClob(targetType, conversionMode, column); + case VARCHAR_IGNORECASE: + return convertToVarcharIgnoreCase(targetType, conversionMode, column); + case BINARY: + return convertToBinary(targetType, conversionMode, column); + case VARBINARY: + return convertToVarbinary(targetType, conversionMode, column); + case BLOB: + return convertToBlob(targetType, conversionMode, column); + case BOOLEAN: + return convertToBoolean(); + case TINYINT: + return convertToTinyint(column); + case SMALLINT: + return convertToSmallint(column); + case INTEGER: + return convertToInt(column); + case BIGINT: + return convertToBigint(column); + case NUMERIC: + return convertToNumeric(targetType, provider, conversionMode, column); + case REAL: + return convertToReal(); + case DOUBLE: + return convertToDouble(); + case DECFLOAT: + return convertToDecfloat(targetType, conversionMode); + case DATE: + return convertToDate(provider); + case TIME: + return convertToTime(targetType, provider, conversionMode); + case TIME_TZ: + return convertToTimeTimeZone(targetType, provider, conversionMode); + case TIMESTAMP: + return convertToTimestamp(targetType, provider, conversionMode); + case TIMESTAMP_TZ: + return convertToTimestampTimeZone(targetType, provider, conversionMode); + case INTERVAL_YEAR: + case INTERVAL_MONTH: + case INTERVAL_YEAR_TO_MONTH: + return convertToIntervalYearMonth(targetType, conversionMode, column); + case INTERVAL_DAY: + case INTERVAL_HOUR: + case INTERVAL_MINUTE: + case INTERVAL_SECOND: + case INTERVAL_DAY_TO_HOUR: + case INTERVAL_DAY_TO_MINUTE: + case INTERVAL_DAY_TO_SECOND: + case INTERVAL_HOUR_TO_MINUTE: + case INTERVAL_HOUR_TO_SECOND: + case INTERVAL_MINUTE_TO_SECOND: + return convertToIntervalDayTime(targetType, conversionMode, column); + case JAVA_OBJECT: + return convertToJavaObject(targetType, conversionMode, column); + case ENUM: + return convertToEnum((ExtTypeInfoEnum) targetType.getExtTypeInfo(), provider); + case GEOMETRY: + return convertToGeometry((ExtTypeInfoGeometry) targetType.getExtTypeInfo()); + case JSON: + return convertToJson(targetType, conversionMode, column); + case UUID: + return convertToUuid(); + case ARRAY: + return convertToArray(targetType, provider, conversionMode, column); + case ROW: + return convertToRow(targetType, provider, conversionMode, column); + default: + throw getDataConversionError(targetValueType); + } + } + + /** + * Converts this value to a CHAR value. May not be called on a NULL value. + * + * @return a CHAR value. + */ + public ValueChar convertToChar() { + return convertToChar(TypeInfo.getTypeInfo(CHAR), null, CONVERT_TO, null); + } + + private ValueChar convertToChar(TypeInfo targetType, CastDataProvider provider, int conversionMode, // + Object column) { + int valueType = getValueType(); + switch (valueType) { + case BLOB: + case JAVA_OBJECT: + throw getDataConversionError(targetType.getValueType()); + } + String s = getString(); + int length = s.length(), newLength = length; + if (conversionMode == CONVERT_TO) { + while (newLength > 0 && s.charAt(newLength - 1) == ' ') { + newLength--; + } + } else { + int p = MathUtils.convertLongToInt(targetType.getPrecision()); + if (provider == null || provider.getMode().charPadding == CharPadding.ALWAYS) { + if (newLength != p) { + if (newLength < p) { + return ValueChar.get(StringUtils.pad(s, p, null, true)); + } else if (conversionMode == CAST_TO) { + newLength = p; + } else { + do { + if (s.charAt(--newLength) != ' ') { + throw getValueTooLongException(targetType, column); + } + } while (newLength > p); + } + } + } else { + if (conversionMode == CAST_TO && newLength > p) { + newLength = p; + } + while (newLength > 0 && s.charAt(newLength - 1) == ' ') { + newLength--; + } + if (conversionMode == ASSIGN_TO && newLength > p) { + throw getValueTooLongException(targetType, column); + } + } + } + if (length != newLength) { + s = s.substring(0, newLength); + } else if (valueType == CHAR) { + return (ValueChar) this; + } + return ValueChar.get(s); + } + + private Value convertToVarchar(TypeInfo targetType, CastDataProvider provider, int conversionMode, Object column) { + int valueType = getValueType(); + switch (valueType) { + case BLOB: + case JAVA_OBJECT: + throw getDataConversionError(targetType.getValueType()); + } + if (conversionMode != CONVERT_TO) { + String s = getString(); + int p = MathUtils.convertLongToInt(targetType.getPrecision()); + if (s.length() > p) { + if (conversionMode != CAST_TO) { + throw getValueTooLongException(targetType, column); + } + return ValueVarchar.get(s.substring(0, p), provider); + } + } + return valueType == Value.VARCHAR ? this : ValueVarchar.get(getString(), provider); + } + + private ValueClob convertToClob(TypeInfo targetType, int conversionMode, Object column) { + ValueClob v; + switch (getValueType()) { + case CLOB: + v = (ValueClob) this; + break; + case JAVA_OBJECT: + throw getDataConversionError(targetType.getValueType()); + case BLOB: { + LobData data = ((ValueBlob) this).lobData; + // Try to reuse the array, if possible + if (data instanceof LobDataInMemory) { + byte[] small = ((LobDataInMemory) data).getSmall(); + byte[] bytes = new String(small, StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8); + if (Arrays.equals(bytes, small)) { + bytes = small; + } + v = ValueClob.createSmall(bytes); + break; + } else if (data instanceof LobDataDatabase) { + v = data.getDataHandler().getLobStorage().createClob(getReader(), -1); + break; + } + } + //$FALL-THROUGH$ + default: + v = ValueClob.createSmall(getString()); + } + if (conversionMode != CONVERT_TO) { + if (conversionMode == CAST_TO) { + v = v.convertPrecision(targetType.getPrecision()); + } else if (v.charLength() > targetType.getPrecision()) { + throw v.getValueTooLongException(targetType, column); + } + } + return v; + } + + private Value convertToVarcharIgnoreCase(TypeInfo targetType, int conversionMode, Object column) { + int valueType = getValueType(); + switch (valueType) { + case BLOB: + case JAVA_OBJECT: + throw getDataConversionError(targetType.getValueType()); + } + if (conversionMode != CONVERT_TO) { + String s = getString(); + int p = MathUtils.convertLongToInt(targetType.getPrecision()); + if (s.length() > p) { + if (conversionMode != CAST_TO) { + throw getValueTooLongException(targetType, column); + } + return ValueVarcharIgnoreCase.get(s.substring(0, p)); + } + } + return valueType == Value.VARCHAR_IGNORECASE ? this : ValueVarcharIgnoreCase.get(getString()); + } + + private ValueBinary convertToBinary(TypeInfo targetType, int conversionMode, Object column) { + ValueBinary v; + if (getValueType() == BINARY) { + v = (ValueBinary) this; + } else { + try { + v = ValueBinary.getNoCopy(getBytesNoCopy()); + } catch (DbException e) { + if (e.getErrorCode() == ErrorCode.DATA_CONVERSION_ERROR_1) { + throw getDataConversionError(BINARY); + } + throw e; + } + } + if (conversionMode != CONVERT_TO) { + byte[] value = v.getBytesNoCopy(); + int length = value.length; + int p = MathUtils.convertLongToInt(targetType.getPrecision()); + if (length != p) { + if (conversionMode == ASSIGN_TO && length > p) { + throw v.getValueTooLongException(targetType, column); + } + v = ValueBinary.getNoCopy(Arrays.copyOf(value, p)); + } + } + return v; + } + + private ValueVarbinary convertToVarbinary(TypeInfo targetType, int conversionMode, Object column) { + ValueVarbinary v; + if (getValueType() == VARBINARY) { + v = (ValueVarbinary) this; + } else { + v = ValueVarbinary.getNoCopy(getBytesNoCopy()); + } + if (conversionMode != CONVERT_TO) { + byte[] value = v.getBytesNoCopy(); + int length = value.length; + int p = MathUtils.convertLongToInt(targetType.getPrecision()); + if (conversionMode == CAST_TO) { + if (length > p) { + v = ValueVarbinary.getNoCopy(Arrays.copyOf(value, p)); + } + } else if (length > p) { + throw v.getValueTooLongException(targetType, column); + } + } + return v; + } + + private ValueBlob convertToBlob(TypeInfo targetType, int conversionMode, Object column) { + ValueBlob v; + switch (getValueType()) { + case BLOB: + v = (ValueBlob) this; + break; + case CLOB: + DataHandler handler = ((ValueLob) this).lobData.getDataHandler(); + if (handler != null) { + v = handler.getLobStorage().createBlob(getInputStream(), -1); + break; + } + //$FALL-THROUGH$ + default: + try { + v = ValueBlob.createSmall(getBytesNoCopy()); + } catch (DbException e) { + if (e.getErrorCode() == ErrorCode.DATA_CONVERSION_ERROR_1) { + throw getDataConversionError(BLOB); + } + throw e; + } + break; + } + if (conversionMode != CONVERT_TO) { + if (conversionMode == CAST_TO) { + v = v.convertPrecision(targetType.getPrecision()); + } else if (v.octetLength() > targetType.getPrecision()) { + throw v.getValueTooLongException(targetType, column); + } + } + return v; + } + + /** + * Converts this value to a BOOLEAN value. May not be called on a NULL + * value. + * + * @return the BOOLEAN value + */ + public final ValueBoolean convertToBoolean() { + switch (getValueType()) { + case BOOLEAN: + return (ValueBoolean) this; + case CHAR: + case VARCHAR: + case VARCHAR_IGNORECASE: + return ValueBoolean.get(getBoolean()); + case TINYINT: + case SMALLINT: + case INTEGER: + case BIGINT: + case NUMERIC: + case DOUBLE: + case REAL: + case DECFLOAT: + return ValueBoolean.get(getSignum() != 0); + default: + throw getDataConversionError(BOOLEAN); + case NULL: + throw DbException.getInternalError(); + } + } + + /** + * Converts this value to a TINYINT value. May not be called on a NULL + * value. + * + * @param column + * the column, used for to improve the error message if + * conversion fails + * @return the TINYINT value + */ + public final ValueTinyint convertToTinyint(Object column) { + switch (getValueType()) { + case TINYINT: + return (ValueTinyint) this; + case CHAR: + case VARCHAR: + case VARCHAR_IGNORECASE: + case BOOLEAN: + return ValueTinyint.get(getByte()); + case SMALLINT: + case ENUM: + case INTEGER: + return ValueTinyint.get(convertToByte(getInt(), column)); + case BIGINT: + case INTERVAL_YEAR: + case INTERVAL_MONTH: + case INTERVAL_DAY: + case INTERVAL_HOUR: + case INTERVAL_MINUTE: + case INTERVAL_SECOND: + case INTERVAL_YEAR_TO_MONTH: + case INTERVAL_DAY_TO_HOUR: + case INTERVAL_DAY_TO_MINUTE: + case INTERVAL_DAY_TO_SECOND: + case INTERVAL_HOUR_TO_MINUTE: + case INTERVAL_HOUR_TO_SECOND: + case INTERVAL_MINUTE_TO_SECOND: + return ValueTinyint.get(convertToByte(getLong(), column)); + case NUMERIC: + case DECFLOAT: + return ValueTinyint.get(convertToByte(convertToLong(getBigDecimal(), column), column)); + case REAL: + case DOUBLE: + return ValueTinyint.get(convertToByte(convertToLong(getDouble(), column), column)); + case BINARY: + case VARBINARY: { + byte[] bytes = getBytesNoCopy(); + if (bytes.length == 1) { + return ValueTinyint.get(bytes[0]); + } + } + //$FALL-THROUGH$ + default: + throw getDataConversionError(TINYINT); + case NULL: + throw DbException.getInternalError(); + } + } + + /** + * Converts this value to a SMALLINT value. May not be called on a NULL value. + * + * @param column + * the column, used for to improve the error message if + * conversion fails + * @return the SMALLINT value + */ + public final ValueSmallint convertToSmallint(Object column) { + switch (getValueType()) { + case SMALLINT: + return (ValueSmallint) this; + case CHAR: + case VARCHAR: + case VARCHAR_IGNORECASE: + case BOOLEAN: + case TINYINT: + return ValueSmallint.get(getShort()); + case ENUM: + case INTEGER: + return ValueSmallint.get(convertToShort(getInt(), column)); + case BIGINT: + case INTERVAL_YEAR: + case INTERVAL_MONTH: + case INTERVAL_DAY: + case INTERVAL_HOUR: + case INTERVAL_MINUTE: + case INTERVAL_SECOND: + case INTERVAL_YEAR_TO_MONTH: + case INTERVAL_DAY_TO_HOUR: + case INTERVAL_DAY_TO_MINUTE: + case INTERVAL_DAY_TO_SECOND: + case INTERVAL_HOUR_TO_MINUTE: + case INTERVAL_HOUR_TO_SECOND: + case INTERVAL_MINUTE_TO_SECOND: + return ValueSmallint.get(convertToShort(getLong(), column)); + case NUMERIC: + case DECFLOAT: + return ValueSmallint.get(convertToShort(convertToLong(getBigDecimal(), column), column)); + case REAL: + case DOUBLE: + return ValueSmallint.get(convertToShort(convertToLong(getDouble(), column), column)); + case BINARY: + case VARBINARY: { + byte[] bytes = getBytesNoCopy(); + if (bytes.length == 2) { + return ValueSmallint.get((short) ((bytes[0] << 8) + (bytes[1] & 0xff))); + } + } + //$FALL-THROUGH$ + default: + throw getDataConversionError(SMALLINT); + case NULL: + throw DbException.getInternalError(); + } + } + + /** + * Converts this value to a INT value. May not be called on a NULL value. + * + * @param column + * the column, used for to improve the error message if + * conversion fails + * @return the INT value + */ + public final ValueInteger convertToInt(Object column) { + switch (getValueType()) { + case INTEGER: + return (ValueInteger) this; + case CHAR: + case VARCHAR: + case VARCHAR_IGNORECASE: + case BOOLEAN: + case TINYINT: + case ENUM: + case SMALLINT: + return ValueInteger.get(getInt()); + case BIGINT: + case INTERVAL_YEAR: + case INTERVAL_MONTH: + case INTERVAL_DAY: + case INTERVAL_HOUR: + case INTERVAL_MINUTE: + case INTERVAL_SECOND: + case INTERVAL_YEAR_TO_MONTH: + case INTERVAL_DAY_TO_HOUR: + case INTERVAL_DAY_TO_MINUTE: + case INTERVAL_DAY_TO_SECOND: + case INTERVAL_HOUR_TO_MINUTE: + case INTERVAL_HOUR_TO_SECOND: + case INTERVAL_MINUTE_TO_SECOND: + return ValueInteger.get(convertToInt(getLong(), column)); + case NUMERIC: + case DECFLOAT: + return ValueInteger.get(convertToInt(convertToLong(getBigDecimal(), column), column)); + case REAL: + case DOUBLE: + return ValueInteger.get(convertToInt(convertToLong(getDouble(), column), column)); + case BINARY: + case VARBINARY: { + byte[] bytes = getBytesNoCopy(); + if (bytes.length == 4) { + return ValueInteger.get(Bits.readInt(bytes, 0)); + } + } + //$FALL-THROUGH$ + default: + throw getDataConversionError(INTEGER); + case NULL: + throw DbException.getInternalError(); + } + } + + /** + * Converts this value to a BIGINT value. May not be called on a NULL value. + * + * @param column + * the column, used for to improve the error message if + * conversion fails + * @return the BIGINT value + */ + public final ValueBigint convertToBigint(Object column) { + switch (getValueType()) { + case BIGINT: + return (ValueBigint) this; + case CHAR: + case VARCHAR: + case VARCHAR_IGNORECASE: + case BOOLEAN: + case TINYINT: + case SMALLINT: + case INTEGER: + case INTERVAL_YEAR: + case INTERVAL_MONTH: + case INTERVAL_DAY: + case INTERVAL_HOUR: + case INTERVAL_MINUTE: + case INTERVAL_SECOND: + case INTERVAL_YEAR_TO_MONTH: + case INTERVAL_DAY_TO_HOUR: + case INTERVAL_DAY_TO_MINUTE: + case INTERVAL_DAY_TO_SECOND: + case INTERVAL_HOUR_TO_MINUTE: + case INTERVAL_HOUR_TO_SECOND: + case INTERVAL_MINUTE_TO_SECOND: + case ENUM: + return ValueBigint.get(getLong()); + case NUMERIC: + case DECFLOAT: + return ValueBigint.get(convertToLong(getBigDecimal(), column)); + case REAL: + case DOUBLE: + return ValueBigint.get(convertToLong(getDouble(), column)); + case BINARY: + case VARBINARY: { + byte[] bytes = getBytesNoCopy(); + if (bytes.length == 8) { + return ValueBigint.get(Bits.readLong(bytes, 0)); + } + } + //$FALL-THROUGH$ + default: + throw getDataConversionError(BIGINT); + case NULL: + throw DbException.getInternalError(); + } + } + + private ValueNumeric convertToNumeric(TypeInfo targetType, CastDataProvider provider, int conversionMode, + Object column) { + ValueNumeric v; + switch (getValueType()) { + case NUMERIC: + v = (ValueNumeric) this; + break; + case BOOLEAN: + v = getBoolean() ? ValueNumeric.ONE : ValueNumeric.ZERO; + break; + default: { + BigDecimal value = getBigDecimal(); + int targetScale = targetType.getScale(); + int scale = value.scale(); + if (scale < 0 || scale > ValueNumeric.MAXIMUM_SCALE || conversionMode != CONVERT_TO && scale != targetScale + && (scale >= targetScale || !provider.getMode().convertOnlyToSmallerScale)) { + value = ValueNumeric.setScale(value, targetScale); + } + if (conversionMode != CONVERT_TO + && value.precision() > targetType.getPrecision() - targetScale + value.scale()) { + throw getValueTooLongException(targetType, column); + } + return ValueNumeric.get(value); + } + case NULL: + throw DbException.getInternalError(); + } + if (conversionMode != CONVERT_TO) { + int targetScale = targetType.getScale(); + BigDecimal value = v.getBigDecimal(); + int scale = value.scale(); + if (scale != targetScale && (scale >= targetScale || !provider.getMode().convertOnlyToSmallerScale)) { + v = ValueNumeric.get(ValueNumeric.setScale(value, targetScale)); + } + BigDecimal bd = v.getBigDecimal(); + if (bd.precision() > targetType.getPrecision() - targetScale + bd.scale()) { + throw v.getValueTooLongException(targetType, column); + } + } + return v; + } + + /** + * Converts this value to a REAL value. May not be called on a NULL value. + * + * @return the REAL value + */ + public final ValueReal convertToReal() { + switch (getValueType()) { + case REAL: + return (ValueReal) this; + case BOOLEAN: + return getBoolean() ? ValueReal.ONE : ValueReal.ZERO; + default: + return ValueReal.get(getFloat()); + case NULL: + throw DbException.getInternalError(); + } + } + + /** + * Converts this value to a DOUBLE value. May not be called on a NULL value. + * + * @return the DOUBLE value + */ + public final ValueDouble convertToDouble() { + switch (getValueType()) { + case DOUBLE: + return (ValueDouble) this; + case BOOLEAN: + return getBoolean() ? ValueDouble.ONE : ValueDouble.ZERO; + default: + return ValueDouble.get(getDouble()); + case NULL: + throw DbException.getInternalError(); + } + } + + private ValueDecfloat convertToDecfloat(TypeInfo targetType, int conversionMode) { + ValueDecfloat v; + switch (getValueType()) { + case DECFLOAT: + v = (ValueDecfloat) this; + if (v.value == null) { + return v; + } + break; + case CHAR: + case VARCHAR: + case VARCHAR_IGNORECASE: { + String s = getString().trim(); + try { + v = ValueDecfloat.get(new BigDecimal(s)); + } catch (NumberFormatException e) { + switch (s) { + case "-Infinity": + return ValueDecfloat.NEGATIVE_INFINITY; + case "Infinity": + case "+Infinity": + return ValueDecfloat.POSITIVE_INFINITY; + case "NaN": + case "-NaN": + case "+NaN": + return ValueDecfloat.NAN; + default: + throw getDataConversionError(DECFLOAT); + } + } + break; + } + case BOOLEAN: + v = getBoolean() ? ValueDecfloat.ONE : ValueDecfloat.ZERO; + break; + case REAL: { + float value = getFloat(); + if (Float.isFinite(value)) { + v = ValueDecfloat.get(new BigDecimal(Float.toString(value))); + } else if (value == Float.POSITIVE_INFINITY) { + return ValueDecfloat.POSITIVE_INFINITY; + } else if (value == Float.NEGATIVE_INFINITY) { + return ValueDecfloat.NEGATIVE_INFINITY; + } else { + return ValueDecfloat.NAN; + } + break; + } + case DOUBLE: { + double value = getDouble(); + if (Double.isFinite(value)) { + v = ValueDecfloat.get(new BigDecimal(Double.toString(value))); + } else if (value == Double.POSITIVE_INFINITY) { + return ValueDecfloat.POSITIVE_INFINITY; + } else if (value == Double.NEGATIVE_INFINITY) { + return ValueDecfloat.NEGATIVE_INFINITY; + } else { + return ValueDecfloat.NAN; + } + break; + } + default: + try { + v = ValueDecfloat.get(getBigDecimal()); + } catch (DbException e) { + if (e.getErrorCode() == ErrorCode.DATA_CONVERSION_ERROR_1) { + throw getDataConversionError(DECFLOAT); + } + throw e; + } + break; + case NULL: + throw DbException.getInternalError(); + } + if (conversionMode != CONVERT_TO) { + BigDecimal bd = v.value; + int precision = bd.precision(), targetPrecision = (int) targetType.getPrecision(); + if (precision > targetPrecision) { + v = ValueDecfloat.get(bd.setScale(bd.scale() - precision + targetPrecision, RoundingMode.HALF_UP)); + } + } + return v; + } + + /** + * Converts this value to a DATE value. May not be called on a NULL value. + * + * @param provider + * the cast information provider + * @return the DATE value + */ + public final ValueDate convertToDate(CastDataProvider provider) { + switch (getValueType()) { + case DATE: + return (ValueDate) this; + case TIMESTAMP: + return ValueDate.fromDateValue(((ValueTimestamp) this).getDateValue()); + case TIMESTAMP_TZ: { + ValueTimestampTimeZone ts = (ValueTimestampTimeZone) this; + long timeNanos = ts.getTimeNanos(); + long epochSeconds = DateTimeUtils.getEpochSeconds(ts.getDateValue(), timeNanos, + ts.getTimeZoneOffsetSeconds()); + return ValueDate.fromDateValue(DateTimeUtils + .dateValueFromLocalSeconds(epochSeconds + + provider.currentTimeZone().getTimeZoneOffsetUTC(epochSeconds))); + } + case VARCHAR: + case VARCHAR_IGNORECASE: + case CHAR: + return ValueDate.parse(getString().trim()); + default: + throw getDataConversionError(DATE); + case NULL: + throw DbException.getInternalError(); + } + } + + private ValueTime convertToTime(TypeInfo targetType, CastDataProvider provider, int conversionMode) { + ValueTime v; + switch (getValueType()) { + case TIME: + v = (ValueTime) this; + break; + case TIME_TZ: + v = ValueTime.fromNanos(getLocalTimeNanos(provider)); + break; + case TIMESTAMP: + v = ValueTime.fromNanos(((ValueTimestamp) this).getTimeNanos()); + break; + case TIMESTAMP_TZ: { + ValueTimestampTimeZone ts = (ValueTimestampTimeZone) this; + long timeNanos = ts.getTimeNanos(); + long epochSeconds = DateTimeUtils.getEpochSeconds(ts.getDateValue(), timeNanos, + ts.getTimeZoneOffsetSeconds()); + v = ValueTime.fromNanos( + DateTimeUtils.nanosFromLocalSeconds(epochSeconds + + provider.currentTimeZone().getTimeZoneOffsetUTC(epochSeconds)) + + timeNanos % DateTimeUtils.NANOS_PER_SECOND); + break; + } + case VARCHAR: + case VARCHAR_IGNORECASE: + case CHAR: + v = ValueTime.parse(getString().trim()); + break; + default: + throw getDataConversionError(TIME); + } + if (conversionMode != CONVERT_TO) { + int targetScale = targetType.getScale(); + if (targetScale < ValueTime.MAXIMUM_SCALE) { + long n = v.getNanos(); + long n2 = DateTimeUtils.convertScale(n, targetScale, DateTimeUtils.NANOS_PER_DAY); + if (n2 != n) { + v = ValueTime.fromNanos(n2); + } + } + } + return v; + } + + private ValueTimeTimeZone convertToTimeTimeZone(TypeInfo targetType, CastDataProvider provider, + int conversionMode) { + ValueTimeTimeZone v; + switch (getValueType()) { + case TIME_TZ: + v = (ValueTimeTimeZone) this; + break; + case TIME: + v = ValueTimeTimeZone.fromNanos(((ValueTime) this).getNanos(), + provider.currentTimestamp().getTimeZoneOffsetSeconds()); + break; + case TIMESTAMP: { + ValueTimestamp ts = (ValueTimestamp) this; + long timeNanos = ts.getTimeNanos(); + v = ValueTimeTimeZone.fromNanos(timeNanos, + provider.currentTimeZone().getTimeZoneOffsetLocal(ts.getDateValue(), timeNanos)); + break; + } + case TIMESTAMP_TZ: { + ValueTimestampTimeZone ts = (ValueTimestampTimeZone) this; + v = ValueTimeTimeZone.fromNanos(ts.getTimeNanos(), ts.getTimeZoneOffsetSeconds()); + break; + } + case VARCHAR: + case VARCHAR_IGNORECASE: + case CHAR: + v = ValueTimeTimeZone.parse(getString().trim()); + break; + default: + throw getDataConversionError(TIME_TZ); + } + if (conversionMode != CONVERT_TO) { + int targetScale = targetType.getScale(); + if (targetScale < ValueTime.MAXIMUM_SCALE) { + long n = v.getNanos(); + long n2 = DateTimeUtils.convertScale(n, targetScale, DateTimeUtils.NANOS_PER_DAY); + if (n2 != n) { + v = ValueTimeTimeZone.fromNanos(n2, v.getTimeZoneOffsetSeconds()); + } + } + } + return v; + } + + private ValueTimestamp convertToTimestamp(TypeInfo targetType, CastDataProvider provider, int conversionMode) { + ValueTimestamp v; + switch (getValueType()) { + case TIMESTAMP: + v = (ValueTimestamp) this; + break; + case TIME: + v = ValueTimestamp.fromDateValueAndNanos(provider.currentTimestamp().getDateValue(), + ((ValueTime) this).getNanos()); + break; + case TIME_TZ: + v = ValueTimestamp.fromDateValueAndNanos(provider.currentTimestamp().getDateValue(), + getLocalTimeNanos(provider)); + break; + case DATE: + // Scale is always 0 + return ValueTimestamp.fromDateValueAndNanos(((ValueDate) this).getDateValue(), 0); + case TIMESTAMP_TZ: { + ValueTimestampTimeZone ts = (ValueTimestampTimeZone) this; + long timeNanos = ts.getTimeNanos(); + long epochSeconds = DateTimeUtils.getEpochSeconds(ts.getDateValue(), timeNanos, + ts.getTimeZoneOffsetSeconds()); + epochSeconds += provider.currentTimeZone().getTimeZoneOffsetUTC(epochSeconds); + v = ValueTimestamp.fromDateValueAndNanos(DateTimeUtils.dateValueFromLocalSeconds(epochSeconds), + DateTimeUtils.nanosFromLocalSeconds(epochSeconds) + timeNanos % DateTimeUtils.NANOS_PER_SECOND); + break; + } + case VARCHAR: + case VARCHAR_IGNORECASE: + case CHAR: + v = ValueTimestamp.parse(getString().trim(), provider); + break; + default: + throw getDataConversionError(TIMESTAMP); + } + if (conversionMode != CONVERT_TO) { + int targetScale = targetType.getScale(); + if (targetScale < ValueTimestamp.MAXIMUM_SCALE) { + long dv = v.getDateValue(), n = v.getTimeNanos(); + long n2 = DateTimeUtils.convertScale(n, targetScale, + dv == DateTimeUtils.MAX_DATE_VALUE ? DateTimeUtils.NANOS_PER_DAY : Long.MAX_VALUE); + if (n2 != n) { + if (n2 >= DateTimeUtils.NANOS_PER_DAY) { + n2 -= DateTimeUtils.NANOS_PER_DAY; + dv = DateTimeUtils.incrementDateValue(dv); + } + v = ValueTimestamp.fromDateValueAndNanos(dv, n2); + } + } + } + return v; + } + + private long getLocalTimeNanos(CastDataProvider provider) { + ValueTimeTimeZone ts = (ValueTimeTimeZone) this; + int localOffset = provider.currentTimestamp().getTimeZoneOffsetSeconds(); + return DateTimeUtils.normalizeNanosOfDay(ts.getNanos() + + (ts.getTimeZoneOffsetSeconds() - localOffset) * DateTimeUtils.NANOS_PER_DAY); + } + + private ValueTimestampTimeZone convertToTimestampTimeZone(TypeInfo targetType, CastDataProvider provider, + int conversionMode) { + ValueTimestampTimeZone v; + switch (getValueType()) { + case TIMESTAMP_TZ: + v = (ValueTimestampTimeZone) this; + break; + case TIME: { + long dateValue = provider.currentTimestamp().getDateValue(); + long timeNanos = ((ValueTime) this).getNanos(); + v = ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, timeNanos, + provider.currentTimeZone().getTimeZoneOffsetLocal(dateValue, timeNanos)); + break; + } + case TIME_TZ: { + ValueTimeTimeZone t = (ValueTimeTimeZone) this; + v = ValueTimestampTimeZone.fromDateValueAndNanos(provider.currentTimestamp().getDateValue(), + t.getNanos(), t.getTimeZoneOffsetSeconds()); + break; + } + case DATE: { + long dateValue = ((ValueDate) this).getDateValue(); + // Scale is always 0 + return ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, 0L, + provider.currentTimeZone().getTimeZoneOffsetLocal(dateValue, 0L)); + } + case TIMESTAMP: { + ValueTimestamp ts = (ValueTimestamp) this; + long dateValue = ts.getDateValue(); + long timeNanos = ts.getTimeNanos(); + v = ValueTimestampTimeZone.fromDateValueAndNanos(dateValue, timeNanos, + provider.currentTimeZone().getTimeZoneOffsetLocal(dateValue, timeNanos)); + break; + } + case VARCHAR: + case VARCHAR_IGNORECASE: + case CHAR: + v = ValueTimestampTimeZone.parse(getString().trim(), provider); + break; + default: + throw getDataConversionError(TIMESTAMP_TZ); + } + if (conversionMode != CONVERT_TO) { + int targetScale = targetType.getScale(); + if (targetScale < ValueTimestamp.MAXIMUM_SCALE) { + long dv = v.getDateValue(); + long n = v.getTimeNanos(); + long n2 = DateTimeUtils.convertScale(n, targetScale, + dv == DateTimeUtils.MAX_DATE_VALUE ? DateTimeUtils.NANOS_PER_DAY : Long.MAX_VALUE); + if (n2 != n) { + if (n2 >= DateTimeUtils.NANOS_PER_DAY) { + n2 -= DateTimeUtils.NANOS_PER_DAY; + dv = DateTimeUtils.incrementDateValue(dv); + } + v = ValueTimestampTimeZone.fromDateValueAndNanos(dv, n2, v.getTimeZoneOffsetSeconds()); + } + } + } + return v; + } + + private ValueInterval convertToIntervalYearMonth(TypeInfo targetType, int conversionMode, Object column) { + ValueInterval v = convertToIntervalYearMonth(targetType.getValueType(), column); + if (conversionMode != CONVERT_TO) { + if (!v.checkPrecision(targetType.getPrecision())) { + throw v.getValueTooLongException(targetType, column); + } + } + return v; + } + + private ValueInterval convertToIntervalYearMonth(int targetType, Object column) { + long leading; + switch (getValueType()) { + case TINYINT: + case SMALLINT: + case INTEGER: + leading = getInt(); + break; + case BIGINT: + leading = getLong(); + break; + case REAL: + case DOUBLE: + if (targetType == INTERVAL_YEAR_TO_MONTH) { + return IntervalUtils.intervalFromAbsolute(IntervalQualifier.YEAR_TO_MONTH, getBigDecimal() + .multiply(BigDecimal.valueOf(12)).setScale(0, RoundingMode.HALF_UP).toBigInteger()); + } + leading = convertToLong(getDouble(), column); + break; + case NUMERIC: + case DECFLOAT: + if (targetType == INTERVAL_YEAR_TO_MONTH) { + return IntervalUtils.intervalFromAbsolute(IntervalQualifier.YEAR_TO_MONTH, getBigDecimal() + .multiply(BigDecimal.valueOf(12)).setScale(0, RoundingMode.HALF_UP).toBigInteger()); + } + leading = convertToLong(getBigDecimal(), column); + break; + case VARCHAR: + case VARCHAR_IGNORECASE: + case CHAR: { + String s = getString(); + try { + return (ValueInterval) IntervalUtils + .parseFormattedInterval(IntervalQualifier.valueOf(targetType - INTERVAL_YEAR), s) + .convertTo(targetType); + } catch (Exception e) { + throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2, e, "INTERVAL", s); + } + } + case INTERVAL_YEAR: + case INTERVAL_MONTH: + case INTERVAL_YEAR_TO_MONTH: + return IntervalUtils.intervalFromAbsolute(IntervalQualifier.valueOf(targetType - INTERVAL_YEAR), + IntervalUtils.intervalToAbsolute((ValueInterval) this)); + default: + throw getDataConversionError(targetType); + } + boolean negative = false; + if (leading < 0) { + negative = true; + leading = -leading; + } + return ValueInterval.from(IntervalQualifier.valueOf(targetType - INTERVAL_YEAR), negative, leading, + 0L); + } + + private ValueInterval convertToIntervalDayTime(TypeInfo targetType, int conversionMode, Object column) { + ValueInterval v = convertToIntervalDayTime(targetType.getValueType(), column); + if (conversionMode != CONVERT_TO) { + v = v.setPrecisionAndScale(targetType, column); + } + return v; + } + + private ValueInterval convertToIntervalDayTime(int targetType, Object column) { + long leading; + switch (getValueType()) { + case TINYINT: + case SMALLINT: + case INTEGER: + leading = getInt(); + break; + case BIGINT: + leading = getLong(); + break; + case REAL: + case DOUBLE: + if (targetType > INTERVAL_MINUTE) { + return convertToIntervalDayTime(getBigDecimal(), targetType); + } + leading = convertToLong(getDouble(), column); + break; + case NUMERIC: + case DECFLOAT: + if (targetType > INTERVAL_MINUTE) { + return convertToIntervalDayTime(getBigDecimal(), targetType); + } + leading = convertToLong(getBigDecimal(), column); + break; + case VARCHAR: + case VARCHAR_IGNORECASE: + case CHAR: { + String s = getString(); + try { + return (ValueInterval) IntervalUtils + .parseFormattedInterval(IntervalQualifier.valueOf(targetType - INTERVAL_YEAR), s) + .convertTo(targetType); + } catch (Exception e) { + throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2, e, "INTERVAL", s); + } + } + case INTERVAL_DAY: + case INTERVAL_HOUR: + case INTERVAL_MINUTE: + case INTERVAL_SECOND: + case INTERVAL_DAY_TO_HOUR: + case INTERVAL_DAY_TO_MINUTE: + case INTERVAL_DAY_TO_SECOND: + case INTERVAL_HOUR_TO_MINUTE: + case INTERVAL_HOUR_TO_SECOND: + case INTERVAL_MINUTE_TO_SECOND: + return IntervalUtils.intervalFromAbsolute(IntervalQualifier.valueOf(targetType - INTERVAL_YEAR), + IntervalUtils.intervalToAbsolute((ValueInterval) this)); + default: + throw getDataConversionError(targetType); + } + boolean negative = false; + if (leading < 0) { + negative = true; + leading = -leading; + } + return ValueInterval.from(IntervalQualifier.valueOf(targetType - INTERVAL_YEAR), negative, leading, + 0L); + } + + private ValueInterval convertToIntervalDayTime(BigDecimal bigDecimal, int targetType) { + long multiplier; + switch (targetType) { + case INTERVAL_SECOND: + multiplier = DateTimeUtils.NANOS_PER_SECOND; + break; + case INTERVAL_DAY_TO_HOUR: + case INTERVAL_DAY_TO_MINUTE: + case INTERVAL_DAY_TO_SECOND: + multiplier = DateTimeUtils.NANOS_PER_DAY; + break; + case INTERVAL_HOUR_TO_MINUTE: + case INTERVAL_HOUR_TO_SECOND: + multiplier = DateTimeUtils.NANOS_PER_HOUR; + break; + case INTERVAL_MINUTE_TO_SECOND: + multiplier = DateTimeUtils.NANOS_PER_MINUTE; + break; + default: + throw getDataConversionError(targetType); + } + return IntervalUtils.intervalFromAbsolute(IntervalQualifier.valueOf(targetType - INTERVAL_YEAR), + bigDecimal.multiply(BigDecimal.valueOf(multiplier)).setScale(0, RoundingMode.HALF_UP).toBigInteger()); + } + + /** + * Converts this value to a JAVA_OBJECT value. May not be called on a NULL + * value. + * + * @param targetType + * the type of the returned value + * @param conversionMode + * conversion mode + * @param column + * the column (if any), used to improve the error message if + * conversion fails + * @return the JAVA_OBJECT value + */ + public final ValueJavaObject convertToJavaObject(TypeInfo targetType, int conversionMode, Object column) { + ValueJavaObject v; + switch (getValueType()) { + case JAVA_OBJECT: + v = (ValueJavaObject) this; + break; + case BINARY: + case VARBINARY: + case BLOB: + v = ValueJavaObject.getNoCopy(getBytesNoCopy()); + break; + default: + throw getDataConversionError(JAVA_OBJECT); + case NULL: + throw DbException.getInternalError(); + } + if (conversionMode != CONVERT_TO && v.getBytesNoCopy().length > targetType.getPrecision()) { + throw v.getValueTooLongException(targetType, column); + } + return v; + } + + /** + * Converts this value to an ENUM value. May not be called on a NULL value. + * + * @param extTypeInfo + * the extended data type information + * @param provider + * the cast information provider + * @return the ENUM value + */ + public final ValueEnum convertToEnum(ExtTypeInfoEnum extTypeInfo, CastDataProvider provider) { + switch (getValueType()) { + case ENUM: { + ValueEnum v = (ValueEnum) this; + if (extTypeInfo.equals(v.getEnumerators())) { + return v; + } + return extTypeInfo.getValue(v.getString(), provider); + } + case TINYINT: + case SMALLINT: + case INTEGER: + case BIGINT: + case NUMERIC: + case DECFLOAT: + return extTypeInfo.getValue(getInt(), provider); + case VARCHAR: + case VARCHAR_IGNORECASE: + case CHAR: + return extTypeInfo.getValue(getString(), provider); + default: + throw getDataConversionError(ENUM); + case NULL: + throw DbException.getInternalError(); + } + } + + /** + * Converts this value to a GEOMETRY value. May not be called on a NULL + * value. + * + * @param extTypeInfo + * the extended data type information, or null + * @return the GEOMETRY value + */ + public final ValueGeometry convertToGeometry(ExtTypeInfoGeometry extTypeInfo) { + ValueGeometry result; + switch (getValueType()) { + case GEOMETRY: + result = (ValueGeometry) this; + break; + case BINARY: + case VARBINARY: + case BLOB: + result = ValueGeometry.getFromEWKB(getBytesNoCopy()); + break; + case JSON: { + int srid = 0; + if (extTypeInfo != null) { + Integer s = extTypeInfo.getSrid(); + if (s != null) { + srid = s; + } + } + try { + result = ValueGeometry.get(GeoJsonUtils.geoJsonToEwkb(getBytesNoCopy(), srid)); + } catch (RuntimeException ex) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, getTraceSQL()); + } + break; + } + case CHAR: + case VARCHAR: + case CLOB: + case VARCHAR_IGNORECASE: + result = ValueGeometry.get(getString()); + break; + default: + throw getDataConversionError(GEOMETRY); + case NULL: + throw DbException.getInternalError(); + } + if (extTypeInfo != null) { + int type = extTypeInfo.getType(); + Integer srid = extTypeInfo.getSrid(); + if (type != 0 && result.getTypeAndDimensionSystem() != type || srid != null && result.getSRID() != srid) { + StringBuilder builder = ExtTypeInfoGeometry + .toSQL(new StringBuilder(), result.getTypeAndDimensionSystem(), result.getSRID()) + .append(" -> "); + extTypeInfo.getSQL(builder, TRACE_SQL_FLAGS); + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, builder.toString()); + } + } + return result; + } + + private ValueJson convertToJson(TypeInfo targetType, int conversionMode, Object column) { + ValueJson v; + switch (getValueType()) { + case JSON: + v = (ValueJson) this; + break; + case BOOLEAN: + v = ValueJson.get(getBoolean()); + break; + case TINYINT: + case SMALLINT: + case INTEGER: + v = ValueJson.get(getInt()); + break; + case BIGINT: + v = ValueJson.get(getLong()); + break; + case REAL: + case DOUBLE: + case NUMERIC: + case DECFLOAT: + v = ValueJson.get(getBigDecimal()); + break; + case BINARY: + case VARBINARY: + case BLOB: + v = ValueJson.fromJson(getBytesNoCopy()); + break; + case CHAR: + case VARCHAR: + case CLOB: + case VARCHAR_IGNORECASE: + case DATE: + case TIME: + case TIME_TZ: + case ENUM: + case UUID: + v = ValueJson.get(getString()); + break; + case TIMESTAMP: + v = ValueJson.get(((ValueTimestamp) this).getISOString()); + break; + case TIMESTAMP_TZ: + v = ValueJson.get(((ValueTimestampTimeZone) this).getISOString()); + break; + case GEOMETRY: { + ValueGeometry vg = (ValueGeometry) this; + v = ValueJson.getInternal(GeoJsonUtils.ewkbToGeoJson(vg.getBytesNoCopy(), vg.getDimensionSystem())); + break; + } + case ARRAY: { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write('['); + for (Value e : ((ValueArray) this).getList()) { + JsonConstructorUtils.jsonArrayAppend(baos, e, 0); + } + baos.write(']'); + v = ValueJson.getInternal(baos.toByteArray()); + break; + } + default: + throw getDataConversionError(JSON); + } + if (conversionMode != CONVERT_TO && v.getBytesNoCopy().length > targetType.getPrecision()) { + throw v.getValueTooLongException(targetType, column); + } + return v; + } + + /** + * Converts this value to a UUID value. May not be called on a NULL value. + * + * @return the UUID value + */ + public final ValueUuid convertToUuid() { + switch (getValueType()) { + case UUID: + return (ValueUuid) this; + case BINARY: + case VARBINARY: + return ValueUuid.get(getBytesNoCopy()); + case JAVA_OBJECT: + return JdbcUtils.deserializeUuid(getBytesNoCopy()); + case CHAR: + case VARCHAR: + case VARCHAR_IGNORECASE: + return ValueUuid.get(getString()); + default: + throw getDataConversionError(UUID); + case NULL: + throw DbException.getInternalError(); + } + } + + private ValueArray convertToArray(TypeInfo targetType, CastDataProvider provider, int conversionMode, + Object column) { + TypeInfo componentType = (TypeInfo) targetType.getExtTypeInfo(); + int valueType = getValueType(); + ValueArray v; + if (valueType == ARRAY) { + v = (ValueArray) this; + } else { + Value[] a; + switch (valueType) { + case BLOB: + a = new Value[] { ValueVarbinary.get(getBytesNoCopy()) }; + break; + case CLOB: + a = new Value[] { ValueVarchar.get(getString()) }; + break; + default: + a = new Value[] { this }; + } + v = ValueArray.get(a, provider); + } + if (componentType != null) { + Value[] values = v.getList(); + int length = values.length; + loop: for (int i = 0; i < length; i++) { + Value v1 = values[i]; + Value v2 = v1.convertTo(componentType, provider, conversionMode, column); + if (v1 != v2) { + Value[] newValues = new Value[length]; + System.arraycopy(values, 0, newValues, 0, i); + newValues[i] = v2; + while (++i < length) { + newValues[i] = values[i].convertTo(componentType, provider, conversionMode, column); + } + v = ValueArray.get(componentType, newValues, provider); + break loop; + } + } + } + if (conversionMode != CONVERT_TO) { + Value[] values = v.getList(); + int cardinality = values.length; + if (conversionMode == CAST_TO) { + int p = MathUtils.convertLongToInt(targetType.getPrecision()); + if (cardinality > p) { + v = ValueArray.get(v.getComponentType(), Arrays.copyOf(values, p), provider); + } + } else if (cardinality > targetType.getPrecision()) { + throw v.getValueTooLongException(targetType, column); + } + } + return v; + } + + private Value convertToRow(TypeInfo targetType, CastDataProvider provider, int conversionMode, + Object column) { + ValueRow v; + if (getValueType() == ROW) { + v = (ValueRow) this; + } else { + v = ValueRow.get(new Value[] { this }); + } + ExtTypeInfoRow ext = (ExtTypeInfoRow) targetType.getExtTypeInfo(); + if (ext != null) { + Value[] values = v.getList(); + int length = values.length; + Set> fields = ext.getFields(); + if (length != fields.size()) { + throw getDataConversionError(targetType); + } + Iterator> iter = fields.iterator(); + loop: for (int i = 0; i < length; i++) { + Value v1 = values[i]; + TypeInfo componentType = iter.next().getValue(); + Value v2 = v1.convertTo(componentType, provider, conversionMode, column); + if (v1 != v2) { + Value[] newValues = new Value[length]; + System.arraycopy(values, 0, newValues, 0, i); + newValues[i] = v2; + while (++i < length) { + newValues[i] = values[i].convertTo(componentType, provider, conversionMode, column); + } + v = ValueRow.get(targetType, newValues); + break loop; + } + } + } + return v; + } + + /** + * Creates new instance of the DbException for data conversion error. + * + * @param targetType Target data type. + * @return instance of the DbException. + */ + final DbException getDataConversionError(int targetType) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, getTypeName(getValueType()) + " to " + + getTypeName(targetType)); + } + + /** + * Creates new instance of the DbException for data conversion error. + * + * @param targetType target data type. + * @return instance of the DbException. + */ + final DbException getDataConversionError(TypeInfo targetType) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, getTypeName(getValueType()) + " to " + + targetType.getTraceSQL()); + } + + final DbException getValueTooLongException(TypeInfo targetType, Object column) { + StringBuilder builder = new StringBuilder(); + if (column != null) { + builder.append(column).append(' '); + } + targetType.getSQL(builder, TRACE_SQL_FLAGS); + return DbException.getValueTooLongException(builder.toString(), getTraceSQL(), getType().getPrecision()); + } + + /** + * Compare this value against another value given that the values are of the + * same data type. + * + * @param v the other value + * @param mode the compare mode + * @param provider the cast information provider + * @return 0 if both values are equal, -1 if the other value is smaller, and + * 1 otherwise + */ + public abstract int compareTypeSafe(Value v, CompareMode mode, CastDataProvider provider); + + /** + * Compare this value against another value using the specified compare + * mode. + * + * @param v the other value + * @param provider the cast information provider + * @param compareMode the compare mode + * @return 0 if both values are equal, -1 if this value is smaller, and + * 1 otherwise + */ + public final int compareTo(Value v, CastDataProvider provider, CompareMode compareMode) { + if (this == v) { + return 0; + } + if (this == ValueNull.INSTANCE) { + return -1; + } else if (v == ValueNull.INSTANCE) { + return 1; + } + return compareToNotNullable(v, provider, compareMode); + } + + private int compareToNotNullable(Value v, CastDataProvider provider, CompareMode compareMode) { + Value l = this; + int leftType = l.getValueType(); + int rightType = v.getValueType(); + if (leftType != rightType || leftType == ENUM) { + int dataType = getHigherOrder(leftType, rightType); + if (dataType == ENUM) { + ExtTypeInfoEnum enumerators = ExtTypeInfoEnum.getEnumeratorsForBinaryOperation(l, v); + l = l.convertToEnum(enumerators, provider); + v = v.convertToEnum(enumerators, provider); + } else { + if (dataType <= BLOB) { + if (dataType <= CLOB) { + if (leftType == CHAR || rightType == CHAR) { + dataType = CHAR; + } + } else if (dataType >= BINARY && (leftType == BINARY || rightType == BINARY)) { + dataType = BINARY; + } + } + l = l.convertTo(dataType, provider); + v = v.convertTo(dataType, provider); + } + } + return l.compareTypeSafe(v, compareMode, provider); + } + + /** + * Compare this value against another value using the specified compare + * mode. + * + * @param v the other value + * @param forEquality perform only check for equality + * @param provider the cast information provider + * @param compareMode the compare mode + * @return 0 if both values are equal, -1 if this value is smaller, 1 + * if other value is larger, {@link Integer#MIN_VALUE} if order is + * not defined due to NULL comparison + */ + public int compareWithNull(Value v, boolean forEquality, CastDataProvider provider, + CompareMode compareMode) { + if (this == ValueNull.INSTANCE || v == ValueNull.INSTANCE) { + return Integer.MIN_VALUE; + } + return compareToNotNullable(v, provider, compareMode); + } + + /** + * Returns true if this value is NULL or contains NULL value. + * + * @return true if this value is NULL or contains NULL value + */ + public boolean containsNull() { + return false; + } + + private static byte convertToByte(long x, Object column) { + if (x > Byte.MAX_VALUE || x < Byte.MIN_VALUE) { + throw DbException.get( + ErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_2, Long.toString(x), getColumnName(column)); + } + return (byte) x; + } + + private static short convertToShort(long x, Object column) { + if (x > Short.MAX_VALUE || x < Short.MIN_VALUE) { + throw DbException.get( + ErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_2, Long.toString(x), getColumnName(column)); + } + return (short) x; + } + + /** + * Convert to integer, throwing exception if out of range. + * + * @param x integer value. + * @param column Column info. + * @return x + */ + public static int convertToInt(long x, Object column) { + if (x > Integer.MAX_VALUE || x < Integer.MIN_VALUE) { + throw DbException.get( + ErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_2, Long.toString(x), getColumnName(column)); + } + return (int) x; + } + + private static long convertToLong(double x, Object column) { + if (x > Long.MAX_VALUE || x < Long.MIN_VALUE) { + // TODO document that +Infinity, -Infinity throw an exception and + // NaN returns 0 + throw DbException.get( + ErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_2, Double.toString(x), getColumnName(column)); + } + return Math.round(x); + } + + private static long convertToLong(BigDecimal x, Object column) { + if (x.compareTo(MAX_LONG_DECIMAL) > 0 || + x.compareTo(MIN_LONG_DECIMAL) < 0) { + throw DbException.get( + ErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_2, x.toString(), getColumnName(column)); + } + return x.setScale(0, RoundingMode.HALF_UP).longValue(); + } + + private static String getColumnName(Object column) { + return column == null ? "" : column.toString(); + } + + @Override + public String toString() { + return getTraceSQL(); + } + + /** + * Create an exception meaning the specified operation is not supported for + * this data type. + * + * @param op the operation + * @return the exception + */ + protected final DbException getUnsupportedExceptionForOperation(String op) { + return DbException.getUnsupportedException(getTypeName(getValueType()) + ' ' + op); + } + + /** + * Returns length of this value in characters. + * + * @return length of this value in characters + * @throws NullPointerException if this value is {@code NULL} + */ + public long charLength() { + return getString().length(); + } + + /** + * Returns length of this value in bytes. + * + * @return length of this value in bytes + * @throws NullPointerException if this value is {@code NULL} + */ + public long octetLength() { + return getBytesNoCopy().length; + } + + /** + * Returns whether this value {@code IS TRUE}. + * + * @return {@code true} if it is. For {@code BOOLEAN} values returns + * {@code true} for {@code TRUE} and {@code false} for {@code FALSE} + * and {@code UNKNOWN} ({@code NULL}). + * @see #getBoolean() + * @see #isFalse() + */ + public final boolean isTrue() { + return this != ValueNull.INSTANCE ? getBoolean() : false; + } + + /** + * Returns whether this value {@code IS FALSE}. + * + * @return {@code true} if it is. For {@code BOOLEAN} values returns + * {@code true} for {@code FALSE} and {@code false} for {@code TRUE} + * and {@code UNKNOWN} ({@code NULL}). + * @see #getBoolean() + * @see #isTrue() + */ + public final boolean isFalse() { + return this != ValueNull.INSTANCE && !getBoolean(); + } + +} diff --git a/h2/src/main/org/h2/value/ValueArray.java b/h2/src/main/org/h2/value/ValueArray.java new file mode 100644 index 0000000..d6ec12b --- /dev/null +++ b/h2/src/main/org/h2/value/ValueArray.java @@ -0,0 +1,149 @@ +/* + * 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.value; + +import org.h2.engine.CastDataProvider; +import org.h2.engine.Constants; +import org.h2.message.DbException; + +/** + * Implementation of the ARRAY data type. + */ +public final class ValueArray extends ValueCollectionBase { + + /** + * Empty array. + */ + public static final ValueArray EMPTY = get(TypeInfo.TYPE_NULL, Value.EMPTY_VALUES, null); + + private TypeInfo type; + + private TypeInfo componentType; + + private ValueArray(TypeInfo componentType, Value[] list, CastDataProvider provider) { + super(list); + int length = list.length; + if (length > Constants.MAX_ARRAY_CARDINALITY) { + String typeName = getTypeName(getValueType()); + throw DbException.getValueTooLongException(typeName, typeName, length); + } + for (int i = 0; i < length; i++) { + list[i] = list[i].castTo(componentType, provider); + } + this.componentType = componentType; + } + + /** + * Get or create a array value for the given value array. + * Do not clone the data. + * + * @param list the value array + * @param provider the cast information provider + * @return the value + */ + public static ValueArray get(Value[] list, CastDataProvider provider) { + return new ValueArray(TypeInfo.getHigherType(list), list, provider); + } + + /** + * Get or create a array value for the given value array. + * Do not clone the data. + * + * @param componentType the type of elements, or {@code null} + * @param list the value array + * @param provider the cast information provider + * @return the value + */ + public static ValueArray get(TypeInfo componentType, Value[] list, CastDataProvider provider) { + return new ValueArray(componentType, list, provider); + } + + @Override + public TypeInfo getType() { + TypeInfo type = this.type; + if (type == null) { + TypeInfo componentType = getComponentType(); + this.type = type = TypeInfo.getTypeInfo(getValueType(), values.length, 0, componentType); + } + return type; + } + + @Override + public int getValueType() { + return ARRAY; + } + + public TypeInfo getComponentType() { + return componentType; + } + + @Override + public String getString() { + StringBuilder builder = new StringBuilder().append('['); + for (int i = 0; i < values.length; i++) { + if (i > 0) { + builder.append(", "); + } + builder.append(values[i].getString()); + } + return builder.append(']').toString(); + } + + @Override + public int compareTypeSafe(Value o, CompareMode mode, CastDataProvider provider) { + ValueArray v = (ValueArray) o; + if (values == v.values) { + return 0; + } + int l = values.length; + int ol = v.values.length; + int len = Math.min(l, ol); + for (int i = 0; i < len; i++) { + Value v1 = values[i]; + Value v2 = v.values[i]; + int comp = v1.compareTo(v2, provider, mode); + if (comp != 0) { + return comp; + } + } + return Integer.compare(l, ol); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + builder.append("ARRAY ["); + int length = values.length; + for (int i = 0; i < length; i++) { + if (i > 0) { + builder.append(", "); + } + values[i].getSQL(builder, sqlFlags); + } + return builder.append(']'); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof ValueArray)) { + return false; + } + ValueArray v = (ValueArray) other; + if (values == v.values) { + return true; + } + int len = values.length; + if (len != v.values.length) { + return false; + } + for (int i = 0; i < len; i++) { + if (!values[i].equals(v.values[i])) { + return false; + } + } + return true; + } + +} diff --git a/h2/src/main/org/h2/value/ValueBigDecimalBase.java b/h2/src/main/org/h2/value/ValueBigDecimalBase.java new file mode 100644 index 0000000..5c027b0 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueBigDecimalBase.java @@ -0,0 +1,37 @@ +/* + * 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.value; + +import java.math.BigDecimal; + +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.message.DbException; + +/** + * Base class for BigDecimal-based values. + */ +abstract class ValueBigDecimalBase extends Value { + + final BigDecimal value; + + TypeInfo type; + + ValueBigDecimalBase(BigDecimal value) { + if (value != null) { + if (value.getClass() != BigDecimal.class) { + throw DbException.get(ErrorCode.INVALID_CLASS_2, BigDecimal.class.getName(), + value.getClass().getName()); + } + int length = value.precision(); + if (length > Constants.MAX_NUMERIC_PRECISION) { + throw DbException.getValueTooLongException(getTypeName(getValueType()), value.toString(), length); + } + } + this.value = value; + } + +} diff --git a/h2/src/main/org/h2/value/ValueBigint.java b/h2/src/main/org/h2/value/ValueBigint.java new file mode 100644 index 0000000..871aed4 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueBigint.java @@ -0,0 +1,232 @@ +/* + * 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.value; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import org.h2.api.ErrorCode; +import org.h2.engine.CastDataProvider; +import org.h2.message.DbException; +import org.h2.util.Bits; + +/** + * Implementation of the BIGINT data type. + */ +public final class ValueBigint extends Value { + + /** + * The smallest {@code ValueLong} value. + */ + public static final ValueBigint MIN = get(Long.MIN_VALUE); + + /** + * The largest {@code ValueLong} value. + */ + public static final ValueBigint MAX = get(Long.MAX_VALUE); + + /** + * The largest Long value, as a BigInteger. + */ + public static final BigInteger MAX_BI = BigInteger.valueOf(Long.MAX_VALUE); + + /** + * The precision in bits. + */ + static final int PRECISION = 64; + + /** + * The approximate precision in decimal digits. + */ + public static final int DECIMAL_PRECISION = 19; + + /** + * The maximum display size of a BIGINT. + * Example: -9223372036854775808 + */ + public static final int DISPLAY_SIZE = 20; + + private static final int STATIC_SIZE = 100; + private static final ValueBigint[] STATIC_CACHE; + + private final long value; + + static { + STATIC_CACHE = new ValueBigint[STATIC_SIZE]; + for (int i = 0; i < STATIC_SIZE; i++) { + STATIC_CACHE[i] = new ValueBigint(i); + } + } + + private ValueBigint(long value) { + this.value = value; + } + + @Override + public Value add(Value v) { + long x = value; + long y = ((ValueBigint) v).value; + long result = x + y; + /* + * If signs of both summands are different from the sign of the sum there is an + * overflow. + */ + if (((x ^ result) & (y ^ result)) < 0) { + throw getOverflow(); + } + return ValueBigint.get(result); + } + + @Override + public int getSignum() { + return Long.signum(value); + } + + @Override + public Value negate() { + if (value == Long.MIN_VALUE) { + throw getOverflow(); + } + return ValueBigint.get(-value); + } + + private DbException getOverflow() { + return DbException.get(ErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_1, + Long.toString(value)); + } + + @Override + public Value subtract(Value v) { + long x = value; + long y = ((ValueBigint) v).value; + long result = x - y; + /* + * If minuend and subtrahend have different signs and minuend and difference + * have different signs there is an overflow. + */ + if (((x ^ y) & (x ^ result)) < 0) { + throw getOverflow(); + } + return ValueBigint.get(result); + } + + @Override + public Value multiply(Value v) { + long x = value; + long y = ((ValueBigint) v).value; + long result = x * y; + // Check whether numbers are large enough to overflow and second value != 0 + if ((Math.abs(x) | Math.abs(y)) >>> 31 != 0 && y != 0 + // Check with division + && (result / y != x + // Also check the special condition that is not handled above + || x == Long.MIN_VALUE && y == -1)) { + throw getOverflow(); + } + return ValueBigint.get(result); + } + + @Override + public Value divide(Value v, TypeInfo quotientType) { + long y = ((ValueBigint) v).value; + if (y == 0) { + throw DbException.get(ErrorCode.DIVISION_BY_ZERO_1, getTraceSQL()); + } + long x = value; + if (x == Long.MIN_VALUE && y == -1) { + throw getOverflow(); + } + return ValueBigint.get(x / y); + } + + @Override + public Value modulus(Value v) { + ValueBigint other = (ValueBigint) v; + if (other.value == 0) { + throw DbException.get(ErrorCode.DIVISION_BY_ZERO_1, getTraceSQL()); + } + return ValueBigint.get(this.value % other.value); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + if ((sqlFlags & NO_CASTS) == 0 && value == (int) value) { + return builder.append("CAST(").append(value).append(" AS BIGINT)"); + } + return builder.append(value); + } + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_BIGINT; + } + + @Override + public int getValueType() { + return BIGINT; + } + + @Override + public byte[] getBytes() { + byte[] b = new byte[8]; + Bits.writeLong(b, 0, getLong()); + return b; + } + + @Override + public long getLong() { + return value; + } + + @Override + public BigDecimal getBigDecimal() { + return BigDecimal.valueOf(value); + } + + @Override + public float getFloat() { + return value; + } + + @Override + public double getDouble() { + return value; + } + + @Override + public int compareTypeSafe(Value o, CompareMode mode, CastDataProvider provider) { + return Long.compare(value, ((ValueBigint) o).value); + } + + @Override + public String getString() { + return Long.toString(value); + } + + @Override + public int hashCode() { + return (int) (value ^ (value >> 32)); + } + + /** + * Get or create a BIGINT value for the given long. + * + * @param i the long + * @return the value + */ + public static ValueBigint get(long i) { + if (i >= 0 && i < STATIC_SIZE) { + return STATIC_CACHE[(int) i]; + } + return (ValueBigint) Value.cache(new ValueBigint(i)); + } + + @Override + public boolean equals(Object other) { + return other instanceof ValueBigint && value == ((ValueBigint) other).value; + } + +} diff --git a/h2/src/main/org/h2/value/ValueBinary.java b/h2/src/main/org/h2/value/ValueBinary.java new file mode 100644 index 0000000..f20a5a7 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueBinary.java @@ -0,0 +1,90 @@ +/* + * 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.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; + +/** + * Implementation of the BINARY data type. + */ +public final class ValueBinary extends ValueBytesBase { + + /** + * Associated TypeInfo. + */ + private TypeInfo type; + + 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); + } + } + + /** + * Get or create a VARBINARY value for the given byte array. + * Clone the data. + * + * @param b the byte array + * @return the value + */ + public static ValueBinary get(byte[] b) { + return getNoCopy(Utils.cloneByteArray(b)); + } + + /** + * Get or create a VARBINARY value for the given byte array. + * Do not clone the date. + * + * @param b the byte array + * @return the value + */ + public static ValueBinary getNoCopy(byte[] b) { + ValueBinary obj = new ValueBinary(b); + if (b.length > SysProperties.OBJECT_CACHE_MAX_PER_ELEMENT_SIZE) { + return obj; + } + return (ValueBinary) Value.cache(obj); + } + + @Override + public TypeInfo getType() { + TypeInfo type = this.type; + if (type == null) { + long precision = value.length; + this.type = type = new TypeInfo(BINARY, precision, 0, null); + } + return type; + } + + @Override + public int getValueType() { + return BINARY; + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + if ((sqlFlags & NO_CASTS) == 0) { + int length = value.length; + return super.getSQL(builder.append("CAST("), sqlFlags).append(" AS BINARY(") + .append(length > 0 ? length : 1).append("))"); + } + return super.getSQL(builder, sqlFlags); + } + + @Override + public String getString() { + return new String(value, StandardCharsets.UTF_8); + } + +} diff --git a/h2/src/main/org/h2/value/ValueBlob.java b/h2/src/main/org/h2/value/ValueBlob.java new file mode 100644 index 0000000..fcfbed1 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueBlob.java @@ -0,0 +1,329 @@ +/* + * 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.value; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.nio.charset.StandardCharsets; + +import org.h2.engine.CastDataProvider; +import org.h2.engine.Constants; +import org.h2.engine.SysProperties; +import org.h2.message.DbException; +import org.h2.store.DataHandler; +import org.h2.store.FileStore; +import org.h2.store.FileStoreOutputStream; +import org.h2.store.LobStorageInterface; +import org.h2.util.Bits; +import org.h2.util.IOUtils; +import org.h2.util.MathUtils; +import org.h2.util.StringUtils; +import org.h2.util.Utils; +import org.h2.value.lob.LobData; +import org.h2.value.lob.LobDataDatabase; +import org.h2.value.lob.LobDataFetchOnDemand; +import org.h2.value.lob.LobDataFile; +import org.h2.value.lob.LobDataInMemory; + +/** + * Implementation of the BINARY LARGE OBJECT data type. + */ +public final class ValueBlob extends ValueLob { + + /** + * Creates a small BLOB value that can be stored in the row directly. + * + * @param data + * the data + * @return the BLOB + */ + public static ValueBlob createSmall(byte[] data) { + return new ValueBlob(new LobDataInMemory(data), data.length); + } + + /** + * Create a temporary BLOB value from a stream. + * + * @param in + * the input stream + * @param length + * the number of characters to read, or -1 for no limit + * @param handler + * the data handler + * @return the lob value + */ + public static ValueBlob createTempBlob(InputStream in, long length, DataHandler handler) { + try { + long remaining = Long.MAX_VALUE; + if (length >= 0 && length < remaining) { + remaining = length; + } + int len = ValueLob.getBufferSize(handler, remaining); + byte[] buff; + if (len >= Integer.MAX_VALUE) { + buff = IOUtils.readBytesAndClose(in, -1); + len = buff.length; + } else { + buff = Utils.newBytes(len); + len = IOUtils.readFully(in, buff, len); + } + if (len <= handler.getMaxLengthInplaceLob()) { + return ValueBlob.createSmall(Utils.copyBytes(buff, len)); + } + return createTemporary(handler, buff, len, in, remaining); + } catch (IOException e) { + throw DbException.convertIOException(e, null); + } + } + + /** + * Create a BLOB in a temporary file. + */ + private static ValueBlob createTemporary(DataHandler handler, byte[] buff, int len, InputStream in, long remaining) + throws IOException { + String fileName = ValueLob.createTempLobFileName(handler); + FileStore tempFile = handler.openFile(fileName, "rw", false); + tempFile.autoDelete(); + long tmpPrecision = 0; + try (FileStoreOutputStream out = new FileStoreOutputStream(tempFile, null)) { + while (true) { + tmpPrecision += len; + out.write(buff, 0, len); + remaining -= len; + if (remaining <= 0) { + break; + } + len = ValueLob.getBufferSize(handler, remaining); + len = IOUtils.readFully(in, buff, len); + if (len <= 0) { + break; + } + } + } + return new ValueBlob(new LobDataFile(handler, fileName, tempFile), tmpPrecision); + } + + public ValueBlob(LobData lobData, long octetLength) { + super(lobData, octetLength, -1L); + } + + @Override + public int getValueType() { + return BLOB; + } + + @Override + public String getString() { + long p = charLength; + if (p >= 0L) { + if (p > Constants.MAX_STRING_LENGTH) { + throw getStringTooLong(p); + } + return readString((int) p); + } + // 1 Java character may be encoded with up to 3 bytes + if (octetLength > Constants.MAX_STRING_LENGTH * 3L) { + throw getStringTooLong(charLength()); + } + String s; + if (lobData instanceof LobDataInMemory) { + s = new String(((LobDataInMemory) lobData).getSmall(), StandardCharsets.UTF_8); + } else { + s = readString(Integer.MAX_VALUE); + } + charLength = p = s.length(); + if (p > Constants.MAX_STRING_LENGTH) { + throw getStringTooLong(p); + } + return s; + } + + @Override + byte[] getBytesInternal() { + if (octetLength > Constants.MAX_STRING_LENGTH) { + throw getBinaryTooLong(octetLength); + } + return readBytes((int) octetLength); + } + + @Override + public InputStream getInputStream() { + return lobData.getInputStream(octetLength); + } + + @Override + public InputStream getInputStream(long oneBasedOffset, long length) { + long p = octetLength; + return rangeInputStream(lobData.getInputStream(p), oneBasedOffset, length, p); + } + + @Override + public Reader getReader(long oneBasedOffset, long length) { + return rangeReader(getReader(), oneBasedOffset, length, -1L); + } + + @Override + public int compareTypeSafe(Value v, CompareMode mode, CastDataProvider provider) { + if (v == this) { + return 0; + } + ValueBlob v2 = (ValueBlob) v; + LobData lobData = this.lobData, lobData2 = v2.lobData; + if (lobData.getClass() == lobData2.getClass()) { + if (lobData instanceof LobDataInMemory) { + return Bits.compareNotNullUnsigned(((LobDataInMemory) lobData).getSmall(), + ((LobDataInMemory) lobData2).getSmall()); + } else if (lobData instanceof LobDataDatabase) { + if (((LobDataDatabase) lobData).getLobId() == ((LobDataDatabase) lobData2).getLobId()) { + return 0; + } + } else if (lobData instanceof LobDataFetchOnDemand) { + if (((LobDataFetchOnDemand) lobData).getLobId() == ((LobDataFetchOnDemand) lobData2).getLobId()) { + return 0; + } + } + } + return compare(this, v2); + } + + /** + * Compares two BLOB values directly. + * + * @param v1 + * first BLOB value + * @param v2 + * second BLOB value + * @return result of comparison + */ + private static int compare(ValueBlob v1, ValueBlob v2) { + long minPrec = Math.min(v1.octetLength, v2.octetLength); + try (InputStream is1 = v1.getInputStream(); InputStream is2 = v2.getInputStream()) { + byte[] buf1 = new byte[BLOCK_COMPARISON_SIZE]; + byte[] buf2 = new byte[BLOCK_COMPARISON_SIZE]; + for (; minPrec >= BLOCK_COMPARISON_SIZE; minPrec -= BLOCK_COMPARISON_SIZE) { + if (IOUtils.readFully(is1, buf1, BLOCK_COMPARISON_SIZE) != BLOCK_COMPARISON_SIZE + || IOUtils.readFully(is2, buf2, BLOCK_COMPARISON_SIZE) != BLOCK_COMPARISON_SIZE) { + throw DbException.getUnsupportedException("Invalid LOB"); + } + int cmp = Bits.compareNotNullUnsigned(buf1, buf2); + if (cmp != 0) { + return cmp; + } + } + for (;;) { + int c1 = is1.read(), c2 = is2.read(); + if (c1 < 0) { + return c2 < 0 ? 0 : -1; + } + if (c2 < 0) { + return 1; + } + if (c1 != c2) { + return (c1 & 0xFF) < (c2 & 0xFF) ? -1 : 1; + } + } + } catch (IOException ex) { + throw DbException.convert(ex); + } + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + if ((sqlFlags & REPLACE_LOBS_FOR_TRACE) != 0 + && (!(lobData instanceof LobDataInMemory) || octetLength > SysProperties.MAX_TRACE_DATA_LENGTH)) { + builder.append("CAST(REPEAT(CHAR(0), ").append(octetLength).append(") AS BINARY VARYING"); + LobDataDatabase lobDb = (LobDataDatabase) lobData; + builder.append(" /* table: ").append(lobDb.getTableId()).append(" id: ").append(lobDb.getLobId()) + .append(" */)"); + } else { + if ((sqlFlags & (REPLACE_LOBS_FOR_TRACE | NO_CASTS)) == 0) { + builder.append("CAST(X'"); + StringUtils.convertBytesToHex(builder, getBytesNoCopy()).append("' AS BINARY LARGE OBJECT(") + .append(octetLength).append("))"); + } else { + builder.append("X'"); + StringUtils.convertBytesToHex(builder, getBytesNoCopy()).append('\''); + } + } + return builder; + } + + /** + * Convert the precision to the requested value. + * + * @param precision + * the new precision + * @return the truncated or this value + */ + ValueBlob convertPrecision(long precision) { + if (this.octetLength <= precision) { + return this; + } + ValueBlob lob; + DataHandler handler = lobData.getDataHandler(); + if (handler != null) { + lob = createTempBlob(getInputStream(), precision, handler); + } else { + try { + lob = createSmall(IOUtils.readBytesAndClose(getInputStream(), MathUtils.convertLongToInt(precision))); + } catch (IOException e) { + throw DbException.convertIOException(e, null); + } + } + return lob; + } + + @Override + public ValueLob copy(DataHandler database, int tableId) { + if (lobData instanceof LobDataInMemory) { + byte[] small = ((LobDataInMemory) lobData).getSmall(); + if (small.length > database.getMaxLengthInplaceLob()) { + LobStorageInterface s = database.getLobStorage(); + ValueBlob v = s.createBlob(getInputStream(), octetLength); + ValueLob v2 = v.copy(database, tableId); + v.remove(); + return v2; + } + return this; + } else if (lobData instanceof LobDataDatabase) { + return database.getLobStorage().copyLob(this, tableId); + } else { + throw new UnsupportedOperationException(); + } + } + + @Override + public long charLength() { + long p = charLength; + if (p < 0L) { + if (lobData instanceof LobDataInMemory) { + p = new String(((LobDataInMemory) lobData).getSmall(), StandardCharsets.UTF_8).length(); + } else { + try (Reader r = getReader()) { + p = 0L; + for (;;) { + p += r.skip(Long.MAX_VALUE); + if (r.read() < 0) { + break; + } + p++; + } + } catch (IOException e) { + throw DbException.convertIOException(e, null); + } + } + charLength = p; + } + return p; + } + + @Override + public long octetLength() { + return octetLength; + } + +} diff --git a/h2/src/main/org/h2/value/ValueBoolean.java b/h2/src/main/org/h2/value/ValueBoolean.java new file mode 100644 index 0000000..daac873 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueBoolean.java @@ -0,0 +1,141 @@ +/* + * 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.value; + +import java.math.BigDecimal; + +import org.h2.engine.CastDataProvider; + +/** + * Implementation of the BOOLEAN data type. + */ +public final class ValueBoolean extends Value { + + /** + * The precision in digits. + */ + public static final int PRECISION = 1; + + /** + * The maximum display size of a boolean. + * Example: FALSE + */ + public static final int DISPLAY_SIZE = 5; + + /** + * TRUE value. + */ + public static final ValueBoolean TRUE = new ValueBoolean(true); + + /** + * FALSE value. + */ + public static final ValueBoolean FALSE = new ValueBoolean(false); + + private final boolean value; + + private ValueBoolean(boolean value) { + this.value = value; + } + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_BOOLEAN; + } + + @Override + public int getValueType() { + return BOOLEAN; + } + + @Override + public int getMemory() { + // Singleton TRUE and FALSE values + return 0; + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return builder.append(getString()); + } + + @Override + public String getString() { + return value ? "TRUE" : "FALSE"; + } + + @Override + public boolean getBoolean() { + return value; + } + + @Override + public byte getByte() { + return value ? (byte) 1 : (byte) 0; + } + + @Override + public short getShort() { + return value ? (short) 1 : (short) 0; + } + + @Override + public int getInt() { + return value ? 1 : 0; + } + + @Override + public long getLong() { + return value ? 1L : 0L; + } + + @Override + public BigDecimal getBigDecimal() { + return value ? BigDecimal.ONE : BigDecimal.ZERO; + } + + @Override + public float getFloat() { + return value ? 1f : 0f; + } + + @Override + public double getDouble() { + return value ? 1d : 0d; + } + + @Override + public Value negate() { + return value ? FALSE : TRUE; + } + + @Override + public int compareTypeSafe(Value o, CompareMode mode, CastDataProvider provider) { + return Boolean.compare(value, ((ValueBoolean) o).value); + } + + @Override + public int hashCode() { + return value ? 1 : 0; + } + + /** + * Get the boolean value for the given boolean. + * + * @param b the boolean + * @return the value + */ + public static ValueBoolean get(boolean b) { + return b ? TRUE : FALSE; + } + + @Override + public boolean equals(Object other) { + // there are only ever two instances, so the instance must match + return this == other; + } + +} diff --git a/h2/src/main/org/h2/value/ValueBytesBase.java b/h2/src/main/org/h2/value/ValueBytesBase.java new file mode 100644 index 0000000..aac8da5 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueBytesBase.java @@ -0,0 +1,77 @@ +/* + * 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.value; + +import java.util.Arrays; + +import org.h2.engine.CastDataProvider; +import org.h2.util.Bits; +import org.h2.util.StringUtils; +import org.h2.util.Utils; + +/** + * Base implementation of byte array based data types. + */ +abstract class ValueBytesBase extends Value { + + /** + * The value. + */ + byte[] value; + + /** + * The hash code. + */ + int hash; + + ValueBytesBase(byte[] value) { + this.value = value; + } + + @Override + public final byte[] getBytes() { + return Utils.cloneByteArray(value); + } + + @Override + public final byte[] getBytesNoCopy() { + return value; + } + + @Override + public final int compareTypeSafe(Value v, CompareMode mode, CastDataProvider provider) { + return Bits.compareNotNullUnsigned(value, ((ValueBytesBase) v).value); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return StringUtils.convertBytesToHex(builder.append("X'"), value).append('\''); + } + + @Override + public final int hashCode() { + int h = hash; + if (h == 0) { + h = getClass().hashCode() ^ Utils.getByteArrayHash(value); + if (h == 0) { + h = 1_234_570_417; + } + hash = h; + } + return h; + } + + @Override + public int getMemory() { + return value.length + 24; + } + + @Override + public final boolean equals(Object other) { + return other != null && getClass() == other.getClass() && Arrays.equals(value, ((ValueBytesBase) other).value); + } + +} diff --git a/h2/src/main/org/h2/value/ValueChar.java b/h2/src/main/org/h2/value/ValueChar.java new file mode 100644 index 0000000..be8aa22 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueChar.java @@ -0,0 +1,55 @@ +/* + * 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.value; + +import org.h2.engine.CastDataProvider; +import org.h2.engine.SysProperties; +import org.h2.util.StringUtils; + +/** + * Implementation of the CHARACTER data type. + */ +public final class ValueChar extends ValueStringBase { + + private ValueChar(String value) { + super(value); + } + + @Override + public int getValueType() { + return CHAR; + } + + @Override + public int compareTypeSafe(Value v, CompareMode mode, CastDataProvider provider) { + return mode.compareString(convertToChar().getString(), v.convertToChar().getString(), false); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + if ((sqlFlags & NO_CASTS) == 0) { + int length = value.length(); + return StringUtils.quoteStringSQL(builder.append("CAST("), value).append(" AS CHAR(") + .append(length > 0 ? length : 1).append("))"); + } + return StringUtils.quoteStringSQL(builder, value); + } + + /** + * Get or create a CHAR value for the given string. + * + * @param s the string + * @return the value + */ + public static ValueChar get(String s) { + ValueChar obj = new ValueChar(StringUtils.cache(s)); + if (s.length() > SysProperties.OBJECT_CACHE_MAX_PER_ELEMENT_SIZE) { + return obj; + } + return (ValueChar) Value.cache(obj); + } + +} diff --git a/h2/src/main/org/h2/value/ValueClob.java b/h2/src/main/org/h2/value/ValueClob.java new file mode 100644 index 0000000..ce75880 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueClob.java @@ -0,0 +1,369 @@ +/* + * 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.value; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.nio.charset.StandardCharsets; + +import org.h2.engine.CastDataProvider; +import org.h2.engine.Constants; +import org.h2.engine.SysProperties; +import org.h2.message.DbException; +import org.h2.store.DataHandler; +import org.h2.store.FileStore; +import org.h2.store.FileStoreOutputStream; +import org.h2.store.LobStorageInterface; +import org.h2.store.RangeReader; +import org.h2.util.Bits; +import org.h2.util.IOUtils; +import org.h2.util.MathUtils; +import org.h2.util.StringUtils; +import org.h2.value.lob.LobData; +import org.h2.value.lob.LobDataDatabase; +import org.h2.value.lob.LobDataFetchOnDemand; +import org.h2.value.lob.LobDataFile; +import org.h2.value.lob.LobDataInMemory; + +/** + * Implementation of the CHARACTER LARGE OBJECT data type. + */ +public final class ValueClob extends ValueLob { + + /** + * Creates a small CLOB value that can be stored in the row directly. + * + * @param data + * the data in UTF-8 encoding + * @return the CLOB + */ + public static ValueClob createSmall(byte[] data) { + return new ValueClob(new LobDataInMemory(data), data.length, + new String(data, StandardCharsets.UTF_8).length()); + } + + /** + * Creates a small CLOB value that can be stored in the row directly. + * + * @param data + * the data in UTF-8 encoding + * @param charLength + * the count of characters, must be exactly the same as count of + * characters in the data + * @return the CLOB + */ + public static ValueClob createSmall(byte[] data, long charLength) { + return new ValueClob(new LobDataInMemory(data), data.length, charLength); + } + + /** + * Creates a small CLOB value that can be stored in the row directly. + * + * @param string + * the string with value + * @return the CLOB + */ + public static ValueClob createSmall(String string) { + byte[] bytes = string.getBytes(StandardCharsets.UTF_8); + return new ValueClob(new LobDataInMemory(bytes), bytes.length, string.length()); + } + + /** + * Create a temporary CLOB value from a stream. + * + * @param in + * the reader + * @param length + * the number of characters to read, or -1 for no limit + * @param handler + * the data handler + * @return the lob value + */ + public static ValueClob createTempClob(Reader in, long length, DataHandler handler) { + if (length >= 0) { + // Otherwise BufferedReader may try to read more data than needed + // and that + // blocks the network level + try { + in = new RangeReader(in, 0, length); + } catch (IOException e) { + throw DbException.convert(e); + } + } + BufferedReader reader; + if (in instanceof BufferedReader) { + reader = (BufferedReader) in; + } else { + reader = new BufferedReader(in, Constants.IO_BUFFER_SIZE); + } + try { + long remaining = Long.MAX_VALUE; + if (length >= 0 && length < remaining) { + remaining = length; + } + int len = ValueLob.getBufferSize(handler, remaining); + char[] buff; + if (len >= Integer.MAX_VALUE) { + String data = IOUtils.readStringAndClose(reader, -1); + buff = data.toCharArray(); + len = buff.length; + } else { + buff = new char[len]; + reader.mark(len); + len = IOUtils.readFully(reader, buff, len); + } + if (len <= handler.getMaxLengthInplaceLob()) { + return ValueClob.createSmall(new String(buff, 0, len)); + } + reader.reset(); + return createTemporary(handler, reader, remaining); + } catch (IOException e) { + throw DbException.convertIOException(e, null); + } + } + + /** + * Create a CLOB in a temporary file. + */ + private static ValueClob createTemporary(DataHandler handler, Reader in, long remaining) throws IOException { + String fileName = ValueLob.createTempLobFileName(handler); + FileStore tempFile = handler.openFile(fileName, "rw", false); + tempFile.autoDelete(); + + long octetLength = 0L, charLength = 0L; + try (FileStoreOutputStream out = new FileStoreOutputStream(tempFile, null)) { + char[] buff = new char[Constants.IO_BUFFER_SIZE]; + while (true) { + int len = ValueLob.getBufferSize(handler, remaining); + len = IOUtils.readFully(in, buff, len); + if (len == 0) { + break; + } + // TODO reduce memory allocation + byte[] data = new String(buff, 0, len).getBytes(StandardCharsets.UTF_8); + out.write(data); + octetLength += data.length; + charLength += len; + } + } + return new ValueClob(new LobDataFile(handler, fileName, tempFile), octetLength, charLength); + } + + public ValueClob(LobData lobData, long octetLength, long charLength) { + super(lobData, octetLength, charLength); + } + + @Override + public int getValueType() { + return CLOB; + } + + @Override + public String getString() { + if (charLength > Constants.MAX_STRING_LENGTH) { + throw getStringTooLong(charLength); + } + if (lobData instanceof LobDataInMemory) { + return new String(((LobDataInMemory) lobData).getSmall(), StandardCharsets.UTF_8); + } + return readString((int) charLength); + } + + @Override + byte[] getBytesInternal() { + long p = octetLength; + if (p >= 0L) { + if (p > Constants.MAX_STRING_LENGTH) { + throw getBinaryTooLong(p); + } + return readBytes((int) p); + } + if (octetLength > Constants.MAX_STRING_LENGTH) { + throw getBinaryTooLong(octetLength()); + } + byte[] b = readBytes(Integer.MAX_VALUE); + octetLength = p = b.length; + if (p > Constants.MAX_STRING_LENGTH) { + throw getBinaryTooLong(p); + } + return b; + } + + @Override + public InputStream getInputStream() { + return lobData.getInputStream(-1L); + } + + @Override + public InputStream getInputStream(long oneBasedOffset, long length) { + return rangeInputStream(lobData.getInputStream(-1L), oneBasedOffset, length, -1L); + } + + @Override + public Reader getReader(long oneBasedOffset, long length) { + return rangeReader(getReader(), oneBasedOffset, length, charLength); + } + + @Override + public int compareTypeSafe(Value v, CompareMode mode, CastDataProvider provider) { + if (v == this) { + return 0; + } + ValueClob v2 = (ValueClob) v; + LobData lobData = this.lobData, lobData2 = v2.lobData; + if (lobData.getClass() == lobData2.getClass()) { + if (lobData instanceof LobDataInMemory) { + return Integer.signum(getString().compareTo(v2.getString())); + } else if (lobData instanceof LobDataDatabase) { + if (((LobDataDatabase) lobData).getLobId() == ((LobDataDatabase) lobData2).getLobId()) { + return 0; + } + } else if (lobData instanceof LobDataFetchOnDemand) { + if (((LobDataFetchOnDemand) lobData).getLobId() == ((LobDataFetchOnDemand) lobData2).getLobId()) { + return 0; + } + } + } + return compare(this, v2); + } + + /** + * Compares two CLOB values directly. + * + * @param v1 + * first CLOB value + * @param v2 + * second CLOB value + * @return result of comparison + */ + private static int compare(ValueClob v1, ValueClob v2) { + long minPrec = Math.min(v1.charLength, v2.charLength); + try (Reader reader1 = v1.getReader(); Reader reader2 = v2.getReader()) { + char[] buf1 = new char[BLOCK_COMPARISON_SIZE]; + char[] buf2 = new char[BLOCK_COMPARISON_SIZE]; + for (; minPrec >= BLOCK_COMPARISON_SIZE; minPrec -= BLOCK_COMPARISON_SIZE) { + if (IOUtils.readFully(reader1, buf1, BLOCK_COMPARISON_SIZE) != BLOCK_COMPARISON_SIZE + || IOUtils.readFully(reader2, buf2, BLOCK_COMPARISON_SIZE) != BLOCK_COMPARISON_SIZE) { + throw DbException.getUnsupportedException("Invalid LOB"); + } + int cmp = Bits.compareNotNull(buf1, buf2); + if (cmp != 0) { + return cmp; + } + } + for (;;) { + int c1 = reader1.read(), c2 = reader2.read(); + if (c1 < 0) { + return c2 < 0 ? 0 : -1; + } + if (c2 < 0) { + return 1; + } + if (c1 != c2) { + return c1 < c2 ? -1 : 1; + } + } + } catch (IOException ex) { + throw DbException.convert(ex); + } + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + if ((sqlFlags & REPLACE_LOBS_FOR_TRACE) != 0 + && (!(lobData instanceof LobDataInMemory) || charLength > SysProperties.MAX_TRACE_DATA_LENGTH)) { + builder.append("SPACE(").append(charLength); + LobDataDatabase lobDb = (LobDataDatabase) lobData; + builder.append(" /* table: ").append(lobDb.getTableId()).append(" id: ").append(lobDb.getLobId()) + .append(" */)"); + } else { + if ((sqlFlags & (REPLACE_LOBS_FOR_TRACE | NO_CASTS)) == 0) { + StringUtils.quoteStringSQL(builder.append("CAST("), getString()).append(" AS CHARACTER LARGE OBJECT(") + .append(charLength).append("))"); + } else { + StringUtils.quoteStringSQL(builder, getString()); + } + } + return builder; + } + + /** + * Convert the precision to the requested value. + * + * @param precision + * the new precision + * @return the truncated or this value + */ + ValueClob convertPrecision(long precision) { + if (this.charLength <= precision) { + return this; + } + ValueClob lob; + DataHandler handler = lobData.getDataHandler(); + if (handler != null) { + lob = createTempClob(getReader(), precision, handler); + } else { + try { + lob = createSmall(IOUtils.readStringAndClose(getReader(), MathUtils.convertLongToInt(precision))); + } catch (IOException e) { + throw DbException.convertIOException(e, null); + } + } + return lob; + } + + @Override + public ValueLob copy(DataHandler database, int tableId) { + if (lobData instanceof LobDataInMemory) { + byte[] small = ((LobDataInMemory) lobData).getSmall(); + if (small.length > database.getMaxLengthInplaceLob()) { + LobStorageInterface s = database.getLobStorage(); + ValueClob v = s.createClob(getReader(), charLength); + ValueLob v2 = v.copy(database, tableId); + v.remove(); + return v2; + } + return this; + } else if (lobData instanceof LobDataDatabase) { + return database.getLobStorage().copyLob(this, tableId); + } else { + throw new UnsupportedOperationException(); + } + } + + @Override + public long charLength() { + return charLength; + } + + @Override + public long octetLength() { + long p = octetLength; + if (p < 0L) { + if (lobData instanceof LobDataInMemory) { + p = ((LobDataInMemory) lobData).getSmall().length; + } else { + try (InputStream is = getInputStream()) { + p = 0L; + for (;;) { + p += is.skip(Long.MAX_VALUE); + if (is.read() < 0) { + break; + } + p++; + } + } catch (IOException e) { + throw DbException.convertIOException(e, null); + } + } + octetLength = p; + } + return p; + } + +} diff --git a/h2/src/main/org/h2/value/ValueCollectionBase.java b/h2/src/main/org/h2/value/ValueCollectionBase.java new file mode 100644 index 0000000..1136537 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueCollectionBase.java @@ -0,0 +1,114 @@ +/* + * 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.value; + +import org.h2.api.ErrorCode; +import org.h2.engine.CastDataProvider; +import org.h2.engine.Constants; +import org.h2.message.DbException; + +/** + * Base class for ARRAY and ROW values. + */ +public abstract class ValueCollectionBase extends Value { + + /** + * Values. + */ + final Value[] values; + + private int hash; + + ValueCollectionBase(Value[] values) { + this.values = values; + } + + public Value[] getList() { + return values; + } + + @Override + public int hashCode() { + if (hash != 0) { + return hash; + } + int h = getValueType(); + for (Value v : values) { + h = h * 31 + v.hashCode(); + } + hash = h; + return h; + } + + @Override + public int compareWithNull(Value v, boolean forEquality, CastDataProvider provider, CompareMode compareMode) { + if (v == ValueNull.INSTANCE) { + return Integer.MIN_VALUE; + } + ValueCollectionBase l = this; + int leftType = l.getValueType(); + int rightType = v.getValueType(); + if (rightType != leftType) { + throw v.getDataConversionError(leftType); + } + ValueCollectionBase r = (ValueCollectionBase) v; + Value[] leftArray = l.values, rightArray = r.values; + int leftLength = leftArray.length, rightLength = rightArray.length; + if (leftLength != rightLength) { + if (leftType == ROW) { + throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); + } + if (forEquality) { + return 1; + } + } + if (forEquality) { + boolean hasNull = false; + for (int i = 0; i < leftLength; i++) { + Value v1 = leftArray[i]; + Value v2 = rightArray[i]; + int comp = v1.compareWithNull(v2, forEquality, provider, compareMode); + if (comp != 0) { + if (comp != Integer.MIN_VALUE) { + return comp; + } + hasNull = true; + } + } + return hasNull ? Integer.MIN_VALUE : 0; + } + int len = Math.min(leftLength, rightLength); + for (int i = 0; i < len; i++) { + Value v1 = leftArray[i]; + Value v2 = rightArray[i]; + int comp = v1.compareWithNull(v2, forEquality, provider, compareMode); + if (comp != 0) { + return comp; + } + } + return Integer.compare(leftLength, rightLength); + } + + @Override + public boolean containsNull() { + for (Value v : values) { + if (v.containsNull()) { + return true; + } + } + return false; + } + + @Override + public int getMemory() { + int memory = 72 + values.length * Constants.MEMORY_POINTER; + for (Value v : values) { + memory += v.getMemory(); + } + return memory; + } + +} diff --git a/h2/src/main/org/h2/value/ValueDate.java b/h2/src/main/org/h2/value/ValueDate.java new file mode 100644 index 0000000..5c49e1d --- /dev/null +++ b/h2/src/main/org/h2/value/ValueDate.java @@ -0,0 +1,97 @@ +/* + * 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.value; + +import org.h2.api.ErrorCode; +import org.h2.engine.CastDataProvider; +import org.h2.message.DbException; +import org.h2.util.DateTimeUtils; + +/** + * Implementation of the DATE data type. + */ +public final class ValueDate extends Value { + + /** + * The default precision and display size of the textual representation of a date. + * Example: 2000-01-02 + */ + public static final int PRECISION = 10; + + private final long dateValue; + + private ValueDate(long dateValue) { + if (dateValue < DateTimeUtils.MIN_DATE_VALUE || dateValue > DateTimeUtils.MAX_DATE_VALUE) { + throw new IllegalArgumentException("dateValue out of range " + dateValue); + } + this.dateValue = dateValue; + } + + /** + * Get or create a date value for the given date. + * + * @param dateValue the date value + * @return the value + */ + public static ValueDate fromDateValue(long dateValue) { + return (ValueDate) Value.cache(new ValueDate(dateValue)); + } + + /** + * Parse a string to a ValueDate. + * + * @param s the string to parse + * @return the date + */ + public static ValueDate parse(String s) { + try { + return fromDateValue(DateTimeUtils.parseDateValue(s, 0, s.length())); + } catch (Exception e) { + throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2, + e, "DATE", s); + } + } + + public long getDateValue() { + return dateValue; + } + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_DATE; + } + + @Override + public int getValueType() { + return DATE; + } + + @Override + public String getString() { + return DateTimeUtils.appendDate(new StringBuilder(PRECISION), dateValue).toString(); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return DateTimeUtils.appendDate(builder.append("DATE '"), dateValue).append('\''); + } + + @Override + public int compareTypeSafe(Value o, CompareMode mode, CastDataProvider provider) { + return Long.compare(dateValue, ((ValueDate) o).dateValue); + } + + @Override + public boolean equals(Object other) { + return this == other || other instanceof ValueDate && dateValue == ((ValueDate) other).dateValue; + } + + @Override + public int hashCode() { + return (int) (dateValue ^ (dateValue >>> 32)); + } + +} diff --git a/h2/src/main/org/h2/value/ValueDecfloat.java b/h2/src/main/org/h2/value/ValueDecfloat.java new file mode 100644 index 0000000..2c08d55 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueDecfloat.java @@ -0,0 +1,361 @@ +/* + * 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.value; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +import org.h2.api.ErrorCode; +import org.h2.engine.CastDataProvider; +import org.h2.message.DbException; + +/** + * Implementation of the DECFLOAT data type. + */ +public final class ValueDecfloat extends ValueBigDecimalBase { + + /** + * The value 'zero'. + */ + public static final ValueDecfloat ZERO = new ValueDecfloat(BigDecimal.ZERO); + + /** + * The value 'one'. + */ + public static final ValueDecfloat ONE = new ValueDecfloat(BigDecimal.ONE); + + /** + * The positive infinity value. + */ + public static final ValueDecfloat POSITIVE_INFINITY = new ValueDecfloat(null); + + /** + * The negative infinity value. + */ + public static final ValueDecfloat NEGATIVE_INFINITY = new ValueDecfloat(null); + + /** + * The not a number value. + */ + public static final ValueDecfloat NAN = new ValueDecfloat(null); + + private ValueDecfloat(BigDecimal value) { + super(value); + } + + @Override + public String getString() { + if (value == null) { + if (this == POSITIVE_INFINITY) { + return "Infinity"; + } else if (this == NEGATIVE_INFINITY) { + return "-Infinity"; + } else { + return "NaN"; + } + } + return value.toString(); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + if ((sqlFlags & NO_CASTS) == 0) { + return getSQL(builder.append("CAST(")).append(" AS DECFLOAT)"); + } + return getSQL(builder); + } + + private StringBuilder getSQL(StringBuilder builder) { + if (value != null) { + return builder.append(value); + } else if (this == POSITIVE_INFINITY) { + return builder.append("'Infinity'"); + } else if (this == NEGATIVE_INFINITY) { + return builder.append("'-Infinity'"); + } else { + return builder.append("'NaN'"); + } + } + + @Override + public TypeInfo getType() { + TypeInfo type = this.type; + if (type == null) { + this.type = type = new TypeInfo(DECFLOAT, value != null ? value.precision() : 1, 0, null); + } + return type; + } + + @Override + public int getValueType() { + return DECFLOAT; + } + + @Override + public Value add(Value v) { + BigDecimal value2 = ((ValueDecfloat) v).value; + if (value != null) { + if (value2 != null) { + return get(value.add(value2)); + } + return v; + } else if (value2 != null || this == v) { + return this; + } + return NAN; + } + + @Override + public Value subtract(Value v) { + BigDecimal value2 = ((ValueDecfloat) v).value; + if (value != null) { + if (value2 != null) { + return get(value.subtract(value2)); + } + return v == POSITIVE_INFINITY ? NEGATIVE_INFINITY : v == NEGATIVE_INFINITY ? POSITIVE_INFINITY : NAN; + } else if (value2 != null) { + return this; + } else if (this == POSITIVE_INFINITY) { + if (v == NEGATIVE_INFINITY) { + return POSITIVE_INFINITY; + } + } else if (this == NEGATIVE_INFINITY && v == POSITIVE_INFINITY) { + return NEGATIVE_INFINITY; + } + return NAN; + } + + @Override + public Value negate() { + if (value != null) { + return get(value.negate()); + } + return this == POSITIVE_INFINITY ? NEGATIVE_INFINITY : this == NEGATIVE_INFINITY ? POSITIVE_INFINITY : NAN; + } + + @Override + public Value multiply(Value v) { + BigDecimal value2 = ((ValueDecfloat) v).value; + if (value != null) { + if (value2 != null) { + return get(value.multiply(value2)); + } + if (v == POSITIVE_INFINITY) { + int s = value.signum(); + if (s > 0) { + return POSITIVE_INFINITY; + } else if (s < 0) { + return NEGATIVE_INFINITY; + } + } else if (v == NEGATIVE_INFINITY) { + int s = value.signum(); + if (s > 0) { + return NEGATIVE_INFINITY; + } else if (s < 0) { + return POSITIVE_INFINITY; + } + } + } else if (value2 != null) { + if (this == POSITIVE_INFINITY) { + int s = value2.signum(); + if (s > 0) { + return POSITIVE_INFINITY; + } else if (s < 0) { + return NEGATIVE_INFINITY; + } + } else if (this == NEGATIVE_INFINITY) { + int s = value2.signum(); + if (s > 0) { + return NEGATIVE_INFINITY; + } else if (s < 0) { + return POSITIVE_INFINITY; + } + } + } else if (this == POSITIVE_INFINITY) { + if (v == POSITIVE_INFINITY) { + return POSITIVE_INFINITY; + } else if (v == NEGATIVE_INFINITY) { + return NEGATIVE_INFINITY; + } + } else if (this == NEGATIVE_INFINITY) { + if (v == POSITIVE_INFINITY) { + return NEGATIVE_INFINITY; + } else if (v == NEGATIVE_INFINITY) { + return POSITIVE_INFINITY; + } + } + return NAN; + } + + @Override + public Value divide(Value v, TypeInfo quotientType) { + BigDecimal value2 = ((ValueDecfloat) v).value; + if (value2 != null && value2.signum() == 0) { + throw DbException.get(ErrorCode.DIVISION_BY_ZERO_1, getTraceSQL()); + } + if (value != null) { + if (value2 != null) { + return divide(value, value2, quotientType); + } else { + if (v != NAN) { + return ZERO; + } + } + } else if (value2 != null && this != NAN) { + return (this == POSITIVE_INFINITY) == (value2.signum() > 0) ? POSITIVE_INFINITY : NEGATIVE_INFINITY; + } + return NAN; + } + + /** + * Divides to {@link BigDecimal} values and returns a {@code DECFLOAT} + * result of the specified data type. + * + * @param dividend the dividend + * @param divisor the divisor + * @param quotientType the type of quotient + * @return the quotient + */ + public static ValueDecfloat divide(BigDecimal dividend, BigDecimal divisor, TypeInfo quotientType) { + int quotientPrecision = (int) quotientType.getPrecision(); + BigDecimal quotient = dividend.divide(divisor, + dividend.scale() - dividend.precision() + divisor.precision() - divisor.scale() + quotientPrecision, + RoundingMode.HALF_DOWN); + int precision = quotient.precision(); + if (precision > quotientPrecision) { + quotient = quotient.setScale(quotient.scale() - precision + quotientPrecision, RoundingMode.HALF_UP); + } + return get(quotient); + } + + @Override + public Value modulus(Value v) { + BigDecimal value2 = ((ValueDecfloat) v).value; + if (value2 != null && value2.signum() == 0) { + throw DbException.get(ErrorCode.DIVISION_BY_ZERO_1, getTraceSQL()); + } + if (value != null) { + if (value2 != null) { + return get(value.remainder(value2)); + } else if (v != NAN) { + return this; + } + } + return NAN; + } + + @Override + public int compareTypeSafe(Value o, CompareMode mode, CastDataProvider provider) { + BigDecimal value2 = ((ValueDecfloat) o).value; + if (value != null) { + if (value2 != null) { + return value.compareTo(value2); + } + return o == NEGATIVE_INFINITY ? 1 : -1; + } else if (value2 != null) { + return this == NEGATIVE_INFINITY ? -1 : 1; + } else if (this == o) { + return 0; + } else if (this == NEGATIVE_INFINITY) { + return -1; + } else if (o == NEGATIVE_INFINITY) { + return 1; + } else { + return this == POSITIVE_INFINITY ? -1 : 1; + } + } + + @Override + public int getSignum() { + if (value != null) { + return value.signum(); + } + return this == POSITIVE_INFINITY ? 1 : this == NEGATIVE_INFINITY ? -1 : 0; + } + + @Override + public BigDecimal getBigDecimal() { + if (value != null) { + return value; + } + throw getDataConversionError(NUMERIC); + } + + @Override + public float getFloat() { + if (value != null) { + return value.floatValue(); + } else if (this == POSITIVE_INFINITY) { + return Float.POSITIVE_INFINITY; + } else if (this == NEGATIVE_INFINITY) { + return Float.NEGATIVE_INFINITY; + } else { + return Float.NaN; + } + } + + @Override + public double getDouble() { + if (value != null) { + return value.doubleValue(); + } else if (this == POSITIVE_INFINITY) { + return Double.POSITIVE_INFINITY; + } else if (this == NEGATIVE_INFINITY) { + return Double.NEGATIVE_INFINITY; + } else { + return Double.NaN; + } + } + + @Override + public int hashCode() { + return value != null ? getClass().hashCode() * 31 + value.hashCode() : System.identityHashCode(this); + } + + @Override + public boolean equals(Object other) { + if (other instanceof ValueDecfloat) { + BigDecimal value2 = ((ValueDecfloat) other).value; + if (value != null) { + return value.equals(value2); + } else if (value2 == null && this == other) { + return true; + } + } + return false; + } + + @Override + public int getMemory() { + return value != null ? value.precision() + 120 : 32; + } + + /** + * Returns {@code true}, if this value is finite. + * + * @return {@code true}, if this value is finite, {@code false} otherwise + */ + public boolean isFinite() { + return value != null; + } + + /** + * Get or create a DECFLOAT value for the given big decimal. + * + * @param dec the big decimal + * @return the value + */ + public static ValueDecfloat get(BigDecimal dec) { + dec = dec.stripTrailingZeros(); + if (BigDecimal.ZERO.equals(dec)) { + return ZERO; + } else if (BigDecimal.ONE.equals(dec)) { + return ONE; + } + return (ValueDecfloat) Value.cache(new ValueDecfloat(dec)); + } + +} diff --git a/h2/src/main/org/h2/value/ValueDouble.java b/h2/src/main/org/h2/value/ValueDouble.java new file mode 100644 index 0000000..9e3fc97 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueDouble.java @@ -0,0 +1,196 @@ +/* + * 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.value; + +import java.math.BigDecimal; + +import org.h2.api.ErrorCode; +import org.h2.engine.CastDataProvider; +import org.h2.message.DbException; + +/** + * Implementation of the DOUBLE PRECISION data type. + */ +public final class ValueDouble extends Value { + + /** + * The precision in bits. + */ + static final int PRECISION = 53; + + /** + * The approximate precision in decimal digits. + */ + public static final int DECIMAL_PRECISION = 17; + + /** + * The maximum display size of a DOUBLE. + * Example: -3.3333333333333334E-100 + */ + public static final int DISPLAY_SIZE = 24; + + /** + * Double.doubleToLongBits(0d) + */ + public static final long ZERO_BITS = 0L; + + /** + * The value 0. + */ + public static final ValueDouble ZERO = new ValueDouble(0d); + + /** + * The value 1. + */ + public static final ValueDouble ONE = new ValueDouble(1d); + + private static final ValueDouble NAN = new ValueDouble(Double.NaN); + + private final double value; + + private ValueDouble(double value) { + this.value = value; + } + + @Override + public Value add(Value v) { + return get(value + ((ValueDouble) v).value); + } + + @Override + public Value subtract(Value v) { + return get(value - ((ValueDouble) v).value); + } + + @Override + public Value negate() { + return get(-value); + } + + @Override + public Value multiply(Value v) { + return get(value * ((ValueDouble) v).value); + } + + @Override + public Value divide(Value v, TypeInfo quotientType) { + ValueDouble v2 = (ValueDouble) v; + if (v2.value == 0.0) { + throw DbException.get(ErrorCode.DIVISION_BY_ZERO_1, getTraceSQL()); + } + return get(value / v2.value); + } + + @Override + public ValueDouble modulus(Value v) { + ValueDouble other = (ValueDouble) v; + if (other.value == 0) { + throw DbException.get(ErrorCode.DIVISION_BY_ZERO_1, getTraceSQL()); + } + return get(value % other.value); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + if ((sqlFlags & NO_CASTS) == 0) { + return getSQL(builder.append("CAST(")).append(" AS DOUBLE PRECISION)"); + } + return getSQL(builder); + } + + private StringBuilder getSQL(StringBuilder builder) { + if (value == Double.POSITIVE_INFINITY) { + return builder.append("'Infinity'"); + } else if (value == Double.NEGATIVE_INFINITY) { + return builder.append("'-Infinity'"); + } else if (Double.isNaN(value)) { + return builder.append("'NaN'"); + } else { + return builder.append(value); + } + } + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_DOUBLE; + } + + @Override + public int getValueType() { + return DOUBLE; + } + + @Override + public int compareTypeSafe(Value o, CompareMode mode, CastDataProvider provider) { + return Double.compare(value, ((ValueDouble) o).value); + } + + @Override + public int getSignum() { + return value == 0 || Double.isNaN(value) ? 0 : value < 0 ? -1 : 1; + } + + @Override + public BigDecimal getBigDecimal() { + if (Double.isFinite(value)) { + return BigDecimal.valueOf(value); + } + // Infinite or NaN + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, Double.toString(value)); + } + + @Override + public float getFloat() { + return (float) value; + } + + @Override + public double getDouble() { + return value; + } + + @Override + public String getString() { + return Double.toString(value); + } + + @Override + public int hashCode() { + /* + * NaNs are normalized in get() method, so it's safe to use + * doubleToRawLongBits() instead of doubleToLongBits() here. + */ + long hash = Double.doubleToRawLongBits(value); + return (int) (hash ^ (hash >>> 32)); + } + + /** + * Get or create a DOUBLE PRECISION value for the given double. + * + * @param d the double + * @return the value + */ + public static ValueDouble get(double d) { + if (d == 1.0) { + return ONE; + } else if (d == 0.0) { + // -0.0 == 0.0, and we want to return 0.0 for both + return ZERO; + } else if (Double.isNaN(d)) { + return NAN; + } + return (ValueDouble) Value.cache(new ValueDouble(d)); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof ValueDouble)) { + return false; + } + return compareTypeSafe((ValueDouble) other, null, null) == 0; + } + +} diff --git a/h2/src/main/org/h2/value/ValueEnum.java b/h2/src/main/org/h2/value/ValueEnum.java new file mode 100644 index 0000000..2572e28 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueEnum.java @@ -0,0 +1,40 @@ +/* + * 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.value; + +import org.h2.util.StringUtils; + +/** + * ENUM value. + */ +public final class ValueEnum extends ValueEnumBase { + + private final ExtTypeInfoEnum enumerators; + + ValueEnum(ExtTypeInfoEnum enumerators, String label, int ordinal) { + super(label, ordinal); + this.enumerators = enumerators; + } + + @Override + public TypeInfo getType() { + return enumerators.getType(); + } + + public ExtTypeInfoEnum getEnumerators() { + return enumerators; + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + if ((sqlFlags & NO_CASTS) == 0) { + StringUtils.quoteStringSQL(builder.append("CAST("), label).append(" AS "); + return enumerators.getType().getSQL(builder, sqlFlags).append(')'); + } + return StringUtils.quoteStringSQL(builder, label); + } + +} diff --git a/h2/src/main/org/h2/value/ValueEnumBase.java b/h2/src/main/org/h2/value/ValueEnumBase.java new file mode 100644 index 0000000..5188fd5 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueEnumBase.java @@ -0,0 +1,144 @@ +/* + * 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.value; + +import java.math.BigDecimal; + +import org.h2.engine.CastDataProvider; +import org.h2.util.StringUtils; + +/** + * Base implementation of the ENUM data type. + * + * This base implementation is only used in 2.0.* clients when they work with + * 1.4.* servers. + */ +public class ValueEnumBase extends Value { + + final String label; + private final int ordinal; + + protected ValueEnumBase(final String label, final int ordinal) { + this.label = label; + this.ordinal = ordinal; + } + + @Override + public Value add(Value v) { + ValueInteger iv = v.convertToInt(null); + return convertToInt(null).add(iv); + } + + @Override + public int compareTypeSafe(Value v, CompareMode mode, CastDataProvider provider) { + return Integer.compare(getInt(), v.getInt()); + } + + @Override + public Value divide(Value v, TypeInfo quotientType) { + ValueInteger iv = v.convertToInt(null); + return convertToInt(null).divide(iv, quotientType); + } + + @Override + public boolean equals(final Object other) { + return other instanceof ValueEnumBase && + getInt() == ((ValueEnumBase) other).getInt(); + } + + /** + * Get or create an enum value with the given label and ordinal. + * + * @param label the label + * @param ordinal the ordinal + * @return the value + */ + public static ValueEnumBase get(String label, int ordinal) { + return new ValueEnumBase(label, ordinal); + } + + @Override + public int getInt() { + return ordinal; + } + + @Override + public long getLong() { + return ordinal; + } + + @Override + public BigDecimal getBigDecimal() { + return BigDecimal.valueOf(ordinal); + } + + @Override + public float getFloat() { + return ordinal; + } + + @Override + public double getDouble() { + return ordinal; + } + + @Override + public int getSignum() { + return Integer.signum(ordinal); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return StringUtils.quoteStringSQL(builder, label); + } + + @Override + public String getString() { + return label; + } + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_ENUM_UNDEFINED; + } + + @Override + public int getValueType() { + return ENUM; + } + + @Override + public int getMemory() { + return 120; + } + + @Override + public int hashCode() { + int results = 31; + results += getString().hashCode(); + results += getInt(); + return results; + } + + @Override + public Value modulus(Value v) { + ValueInteger iv = v.convertToInt(null); + return convertToInt(null).modulus(iv); + } + + @Override + public Value multiply(Value v) { + ValueInteger iv = v.convertToInt(null); + return convertToInt(null).multiply(iv); + } + + @Override + public Value subtract(Value v) { + ValueInteger iv = v.convertToInt(null); + return convertToInt(null).subtract(iv); + } + +} diff --git a/h2/src/main/org/h2/value/ValueGeometry.java b/h2/src/main/org/h2/value/ValueGeometry.java new file mode 100644 index 0000000..ca33eac --- /dev/null +++ b/h2/src/main/org/h2/value/ValueGeometry.java @@ -0,0 +1,260 @@ +/* + * 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.value; + +import static org.h2.util.geometry.EWKBUtils.EWKB_SRID; + +import org.h2.api.ErrorCode; +import org.h2.message.DbException; +import org.h2.util.Bits; +import org.h2.util.StringUtils; +import org.h2.util.geometry.EWKBUtils; +import org.h2.util.geometry.EWKTUtils; +import org.h2.util.geometry.GeometryUtils; +import org.h2.util.geometry.GeometryUtils.EnvelopeTarget; +import org.h2.util.geometry.JTSUtils; +import org.h2.util.geometry.EWKTUtils.EWKTTarget; +import org.locationtech.jts.geom.Geometry; + +/** + * Implementation of the GEOMETRY data type. + * + * @author Thomas Mueller + * @author Noel Grandin + * @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888 + */ +public final class ValueGeometry extends ValueBytesBase { + + private static final double[] UNKNOWN_ENVELOPE = new double[0]; + + /** + * Geometry type and dimension system in OGC geometry code format (type + + * dimensionSystem * 1000). + */ + private final int typeAndDimensionSystem; + + /** + * Spatial reference system identifier. + */ + private final int srid; + + /** + * The envelope of the value. Calculated only on request. + */ + private double[] envelope; + + /** + * The value. Converted from WKB only on request as conversion from/to WKB + * cost a significant amount of CPU cycles. + */ + private Object geometry; + + /** + * Create a new geometry object. + * + * @param bytes the EWKB bytes + * @param envelope the envelope + */ + private ValueGeometry(byte[] bytes, double[] envelope) { + super(bytes); + if (bytes.length < 9 || bytes[0] != 0) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, StringUtils.convertBytesToHex(bytes)); + } + this.value = bytes; + this.envelope = envelope; + int t = Bits.readInt(bytes, 1); + srid = (t & EWKB_SRID) != 0 ? Bits.readInt(bytes, 5) : 0; + typeAndDimensionSystem = (t & 0xffff) % 1_000 + EWKBUtils.type2dimensionSystem(t) * 1_000; + } + + /** + * Get or create a geometry value for the given geometry. + * + * @param o the geometry object (of type + * org.locationtech.jts.geom.Geometry) + * @return the value + */ + public static ValueGeometry getFromGeometry(Object o) { + try { + Geometry g = (Geometry) o; + return (ValueGeometry) Value.cache(new ValueGeometry(JTSUtils.geometry2ewkb(g), UNKNOWN_ENVELOPE)); + } catch (RuntimeException ex) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, String.valueOf(o)); + } + } + + /** + * Get or create a geometry value for the given geometry. + * + * @param s the WKT or EWKT representation of the geometry + * @return the value + */ + public static ValueGeometry get(String s) { + try { + return (ValueGeometry) Value.cache(new ValueGeometry(EWKTUtils.ewkt2ewkb(s), UNKNOWN_ENVELOPE)); + } catch (RuntimeException ex) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, s); + } + } + + /** + * Get or create a geometry value for the given internal EWKB representation. + * + * @param bytes the WKB representation of the geometry. May not be modified. + * @return the value + */ + public static ValueGeometry get(byte[] bytes) { + return (ValueGeometry) Value.cache(new ValueGeometry(bytes, UNKNOWN_ENVELOPE)); + } + + /** + * Get or create a geometry value for the given EWKB value. + * + * @param bytes the WKB representation of the geometry + * @return the value + */ + public static ValueGeometry getFromEWKB(byte[] bytes) { + try { + return (ValueGeometry) Value.cache(new ValueGeometry(EWKBUtils.ewkb2ewkb(bytes), UNKNOWN_ENVELOPE)); + } catch (RuntimeException ex) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, StringUtils.convertBytesToHex(bytes)); + } + } + + /** + * Creates a geometry value for the given envelope. + * + * @param envelope envelope. May not be modified. + * @return the value + */ + public static Value fromEnvelope(double[] envelope) { + return envelope != null + ? Value.cache(new ValueGeometry(EWKBUtils.envelope2wkb(envelope), envelope)) + : ValueNull.INSTANCE; + } + + /** + * Get a copy of geometry object. Geometry object is mutable. The returned + * object is therefore copied before returning. + * + * @return a copy of the geometry object + */ + public Geometry getGeometry() { + if (geometry == null) { + try { + geometry = JTSUtils.ewkb2geometry(value, getDimensionSystem()); + } catch (RuntimeException ex) { + throw DbException.convert(ex); + } + } + return ((Geometry) geometry).copy(); + } + + /** + * Returns geometry type and dimension system in OGC geometry code format + * (type + dimensionSystem * 1000). + * + * @return geometry type and dimension system + */ + public int getTypeAndDimensionSystem() { + return typeAndDimensionSystem; + } + + /** + * Returns geometry type. + * + * @return geometry type and dimension system + */ + public int getGeometryType() { + return typeAndDimensionSystem % 1_000; + } + + /** + * Return a minimal dimension system that can be used for this geometry. + * + * @return dimension system + */ + public int getDimensionSystem() { + return typeAndDimensionSystem / 1_000; + } + + /** + * Return a spatial reference system identifier. + * + * @return spatial reference system identifier + */ + public int getSRID() { + return srid; + } + + /** + * Return an envelope of this geometry. Do not modify the returned value. + * + * @return envelope of this geometry + */ + public double[] getEnvelopeNoCopy() { + if (envelope == UNKNOWN_ENVELOPE) { + EnvelopeTarget target = new EnvelopeTarget(); + EWKBUtils.parseEWKB(value, target); + envelope = target.getEnvelope(); + } + return envelope; + } + + /** + * Test if this geometry envelope intersects with the other geometry + * envelope. + * + * @param r the other geometry + * @return true if the two overlap + */ + public boolean intersectsBoundingBox(ValueGeometry r) { + return GeometryUtils.intersects(getEnvelopeNoCopy(), r.getEnvelopeNoCopy()); + } + + /** + * Get the union. + * + * @param r the other geometry + * @return the union of this geometry envelope and another geometry envelope + */ + public Value getEnvelopeUnion(ValueGeometry r) { + return fromEnvelope(GeometryUtils.union(getEnvelopeNoCopy(), r.getEnvelopeNoCopy())); + } + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_GEOMETRY; + } + + @Override + public int getValueType() { + return GEOMETRY; + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + builder.append("GEOMETRY "); + if ((sqlFlags & ADD_PLAN_INFORMATION) != 0) { + EWKBUtils.parseEWKB(value, new EWKTTarget(builder.append('\''), getDimensionSystem())); + builder.append('\''); + } else { + super.getSQL(builder, DEFAULT_SQL_FLAGS); + } + return builder; + } + + @Override + public String getString() { + return EWKTUtils.ewkb2ewkt(value, getDimensionSystem()); + } + + @Override + public int getMemory() { + return value.length * 20 + 24; + } + +} diff --git a/h2/src/main/org/h2/value/ValueInteger.java b/h2/src/main/org/h2/value/ValueInteger.java new file mode 100644 index 0000000..13ba4cb --- /dev/null +++ b/h2/src/main/org/h2/value/ValueInteger.java @@ -0,0 +1,196 @@ +/* + * 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.value; + +import java.math.BigDecimal; + +import org.h2.api.ErrorCode; +import org.h2.engine.CastDataProvider; +import org.h2.message.DbException; +import org.h2.util.Bits; + +/** + * Implementation of the INTEGER data type. + */ +public final class ValueInteger extends Value { + + /** + * The precision in bits. + */ + public static final int PRECISION = 32; + + /** + * The approximate precision in decimal digits. + */ + public static final int DECIMAL_PRECISION = 10; + + /** + * The maximum display size of an INT. + * Example: -2147483648 + */ + public static final int DISPLAY_SIZE = 11; + + private static final int STATIC_SIZE = 128; + // must be a power of 2 + private static final int DYNAMIC_SIZE = 256; + private static final ValueInteger[] STATIC_CACHE = new ValueInteger[STATIC_SIZE]; + private static final ValueInteger[] DYNAMIC_CACHE = new ValueInteger[DYNAMIC_SIZE]; + + private final int value; + + static { + for (int i = 0; i < STATIC_SIZE; i++) { + STATIC_CACHE[i] = new ValueInteger(i); + } + } + + private ValueInteger(int value) { + this.value = value; + } + + /** + * Get or create an INTEGER value for the given int. + * + * @param i the int + * @return the value + */ + public static ValueInteger get(int i) { + if (i >= 0 && i < STATIC_SIZE) { + return STATIC_CACHE[i]; + } + ValueInteger v = DYNAMIC_CACHE[i & (DYNAMIC_SIZE - 1)]; + if (v == null || v.value != i) { + v = new ValueInteger(i); + DYNAMIC_CACHE[i & (DYNAMIC_SIZE - 1)] = v; + } + return v; + } + + @Override + public Value add(Value v) { + ValueInteger other = (ValueInteger) v; + return checkRange((long) value + (long) other.value); + } + + private static ValueInteger checkRange(long x) { + if ((int) x != x) { + throw DbException.get(ErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_1, Long.toString(x)); + } + return ValueInteger.get((int) x); + } + + @Override + public int getSignum() { + return Integer.signum(value); + } + + @Override + public Value negate() { + return checkRange(-(long) value); + } + + @Override + public Value subtract(Value v) { + ValueInteger other = (ValueInteger) v; + return checkRange((long) value - (long) other.value); + } + + @Override + public Value multiply(Value v) { + ValueInteger other = (ValueInteger) v; + return checkRange((long) value * (long) other.value); + } + + @Override + public Value divide(Value v, TypeInfo quotientType) { + int y = ((ValueInteger) v).value; + if (y == 0) { + throw DbException.get(ErrorCode.DIVISION_BY_ZERO_1, getTraceSQL()); + } + int x = value; + if (x == Integer.MIN_VALUE && y == -1) { + throw DbException.get(ErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_1, "2147483648"); + } + return ValueInteger.get(x / y); + } + + @Override + public Value modulus(Value v) { + ValueInteger other = (ValueInteger) v; + if (other.value == 0) { + throw DbException.get(ErrorCode.DIVISION_BY_ZERO_1, getTraceSQL()); + } + return ValueInteger.get(value % other.value); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return builder.append(value); + } + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_INTEGER; + } + + @Override + public int getValueType() { + return INTEGER; + } + + @Override + public byte[] getBytes() { + byte[] b = new byte[4]; + Bits.writeInt(b, 0, getInt()); + return b; + } + + @Override + public int getInt() { + return value; + } + + @Override + public long getLong() { + return value; + } + + @Override + public BigDecimal getBigDecimal() { + return BigDecimal.valueOf(value); + } + + @Override + public float getFloat() { + return value; + } + + @Override + public double getDouble() { + return value; + } + + @Override + public int compareTypeSafe(Value o, CompareMode mode, CastDataProvider provider) { + return Integer.compare(value, ((ValueInteger) o).value); + } + + @Override + public String getString() { + return Integer.toString(value); + } + + @Override + public int hashCode() { + return value; + } + + @Override + public boolean equals(Object other) { + return other instanceof ValueInteger && value == ((ValueInteger) other).value; + } + +} diff --git a/h2/src/main/org/h2/value/ValueInterval.java b/h2/src/main/org/h2/value/ValueInterval.java new file mode 100644 index 0000000..542e94d --- /dev/null +++ b/h2/src/main/org/h2/value/ValueInterval.java @@ -0,0 +1,394 @@ +/* + * 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.value; + +import static org.h2.util.DateTimeUtils.NANOS_PER_DAY; +import static org.h2.util.DateTimeUtils.NANOS_PER_HOUR; +import static org.h2.util.DateTimeUtils.NANOS_PER_MINUTE; +import static org.h2.util.DateTimeUtils.NANOS_PER_SECOND; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +import org.h2.api.Interval; +import org.h2.api.IntervalQualifier; +import org.h2.engine.CastDataProvider; +import org.h2.message.DbException; +import org.h2.util.DateTimeUtils; +import org.h2.util.IntervalUtils; + +/** + * Implementation of the INTERVAL data type. + */ +public final class ValueInterval extends Value { + + /** + * The default leading field precision for intervals. + */ + public static final int DEFAULT_PRECISION = 2; + + /** + * The maximum leading field precision for intervals. + */ + public static final int MAXIMUM_PRECISION = 18; + + /** + * The default scale for intervals with seconds. + */ + public static final int DEFAULT_SCALE = 6; + + /** + * The maximum scale for intervals with seconds. + */ + public static final int MAXIMUM_SCALE = 9; + + private static final long[] MULTIPLIERS = { + // INTERVAL_SECOND + DateTimeUtils.NANOS_PER_SECOND, + // INTERVAL_YEAR_TO_MONTH + 12, + // INTERVAL_DAY_TO_HOUR + 24, + // INTERVAL_DAY_TO_MINUTE + 24 * 60, + // INTERVAL_DAY_TO_SECOND + DateTimeUtils.NANOS_PER_DAY, + // INTERVAL_HOUR_TO_MINUTE: + 60, + // INTERVAL_HOUR_TO_SECOND + DateTimeUtils.NANOS_PER_HOUR, + // INTERVAL_MINUTE_TO_SECOND + DateTimeUtils.NANOS_PER_MINUTE // + }; + + private final int valueType; + + private final boolean negative; + + private final long leading; + + private final long remaining; + + /** + * Create a ValueInterval instance. + * + * @param qualifier + * qualifier + * @param negative + * whether interval is negative + * @param leading + * value of leading field + * @param remaining + * values of all remaining fields + * @return interval value + */ + public static ValueInterval from(IntervalQualifier qualifier, boolean negative, long leading, long remaining) { + negative = IntervalUtils.validateInterval(qualifier, negative, leading, remaining); + return (ValueInterval) Value + .cache(new ValueInterval(qualifier.ordinal() + INTERVAL_YEAR, negative, leading, remaining)); + } + + /** + * Returns display size for the specified qualifier, precision and + * fractional seconds precision. + * + * @param type + * the value type + * @param precision + * leading field precision + * @param scale + * fractional seconds precision. Ignored if specified type of + * interval does not have seconds. + * @return display size + */ + public static int getDisplaySize(int type, int precision, int scale) { + switch (type) { + case INTERVAL_YEAR: + case INTERVAL_HOUR: + // INTERVAL '-11' YEAR + // INTERVAL '-11' HOUR + return 17 + precision; + case INTERVAL_MONTH: + // INTERVAL '-11' MONTH + return 18 + precision; + case INTERVAL_DAY: + // INTERVAL '-11' DAY + return 16 + precision; + case INTERVAL_MINUTE: + // INTERVAL '-11' MINUTE + return 19 + precision; + case INTERVAL_SECOND: + // INTERVAL '-11' SECOND + // INTERVAL '-11.999999' SECOND + return scale > 0 ? 20 + precision + scale : 19 + precision; + case INTERVAL_YEAR_TO_MONTH: + // INTERVAL '-11-11' YEAR TO MONTH + return 29 + precision; + case INTERVAL_DAY_TO_HOUR: + // INTERVAL '-11 23' DAY TO HOUR + return 27 + precision; + case INTERVAL_DAY_TO_MINUTE: + // INTERVAL '-11 23:59' DAY TO MINUTE + return 32 + precision; + case INTERVAL_DAY_TO_SECOND: + // INTERVAL '-11 23:59.59' DAY TO SECOND + // INTERVAL '-11 23:59.59.999999' DAY TO SECOND + return scale > 0 ? 36 + precision + scale : 35 + precision; + case INTERVAL_HOUR_TO_MINUTE: + // INTERVAL '-11:59' HOUR TO MINUTE + return 30 + precision; + case INTERVAL_HOUR_TO_SECOND: + // INTERVAL '-11:59:59' HOUR TO SECOND + // INTERVAL '-11:59:59.999999' HOUR TO SECOND + return scale > 0 ? 34 + precision + scale : 33 + precision; + case INTERVAL_MINUTE_TO_SECOND: + // INTERVAL '-11:59' MINUTE TO SECOND + // INTERVAL '-11:59.999999' MINUTE TO SECOND + return scale > 0 ? 33 + precision + scale : 32 + precision; + default: + throw DbException.getUnsupportedException(Integer.toString(type)); + } + } + + private ValueInterval(int type, boolean negative, long leading, long remaining) { + this.valueType = type; + this.negative = negative; + this.leading = leading; + this.remaining = remaining; + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return IntervalUtils.appendInterval(builder, getQualifier(), negative, leading, remaining); + } + + @Override + public TypeInfo getType() { + return TypeInfo.getTypeInfo(valueType); + } + + @Override + public int getValueType() { + return valueType; + } + + @Override + public int getMemory() { + // Java 11 with -XX:-UseCompressedOops + return 48; + } + + /** + * Check if the precision is smaller or equal than the given precision. + * + * @param prec + * the maximum precision + * @return true if the precision of this value is smaller or equal to the + * given precision + */ + boolean checkPrecision(long prec) { + if (prec < MAXIMUM_PRECISION) { + for (long l = leading, p = 1, precision = 0; l >= p; p *= 10) { + if (++precision > prec) { + return false; + } + } + } + return true; + } + + ValueInterval setPrecisionAndScale(TypeInfo targetType, Object column) { + int targetScale = targetType.getScale(); + ValueInterval v = this; + convertScale: if (targetScale < ValueInterval.MAXIMUM_SCALE) { + long range; + switch (valueType) { + case INTERVAL_SECOND: + range = NANOS_PER_SECOND; + break; + case INTERVAL_DAY_TO_SECOND: + range = NANOS_PER_DAY; + break; + case INTERVAL_HOUR_TO_SECOND: + range = NANOS_PER_HOUR; + break; + case INTERVAL_MINUTE_TO_SECOND: + range = NANOS_PER_MINUTE; + break; + default: + break convertScale; + } + long l = leading; + long r = DateTimeUtils.convertScale(remaining, targetScale, + l == 999_999_999_999_999_999L ? range : Long.MAX_VALUE); + if (r != remaining) { + if (r >= range) { + l++; + r -= range; + } + v = ValueInterval.from(v.getQualifier(), v.isNegative(), l, r); + } + } + if (!v.checkPrecision(targetType.getPrecision())) { + throw v.getValueTooLongException(targetType, column); + } + return v; + } + + @Override + public String getString() { + return IntervalUtils.appendInterval(new StringBuilder(), getQualifier(), negative, leading, remaining) + .toString(); + } + + @Override + public long getLong() { + long l = leading; + if (valueType >= INTERVAL_SECOND && remaining != 0L + && remaining >= MULTIPLIERS[valueType - INTERVAL_SECOND] >> 1) { + l++; + } + return negative ? -l : l; + } + + @Override + public BigDecimal getBigDecimal() { + if (valueType < INTERVAL_SECOND || remaining == 0L) { + return BigDecimal.valueOf(negative ? -leading : leading); + } + BigDecimal m = BigDecimal.valueOf(MULTIPLIERS[valueType - INTERVAL_SECOND]); + BigDecimal bd = BigDecimal.valueOf(leading) + .add(BigDecimal.valueOf(remaining).divide(m, m.precision(), RoundingMode.HALF_DOWN)) // + .stripTrailingZeros(); + return negative ? bd.negate() : bd; + } + + @Override + public float getFloat() { + if (valueType < INTERVAL_SECOND || remaining == 0L) { + return negative ? -leading : leading; + } + return getBigDecimal().floatValue(); + } + + @Override + public double getDouble() { + if (valueType < INTERVAL_SECOND || remaining == 0L) { + return negative ? -leading : leading; + } + return getBigDecimal().doubleValue(); + } + + /** + * Returns the interval. + * + * @return the interval + */ + public Interval getInterval() { + return new Interval(getQualifier(), negative, leading, remaining); + } + + /** + * Returns the interval qualifier. + * + * @return the interval qualifier + */ + public IntervalQualifier getQualifier() { + return IntervalQualifier.valueOf(valueType - INTERVAL_YEAR); + } + + /** + * Returns where the interval is negative. + * + * @return where the interval is negative + */ + public boolean isNegative() { + return negative; + } + + /** + * Returns value of leading field of this interval. For {@code SECOND} + * intervals returns integer part of seconds. + * + * @return value of leading field + */ + public long getLeading() { + return leading; + } + + /** + * Returns combined value of remaining fields of this interval. For + * {@code SECOND} intervals returns nanoseconds. + * + * @return combined value of remaining fields + */ + public long getRemaining() { + return remaining; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + valueType; + result = prime * result + (negative ? 1231 : 1237); + result = prime * result + (int) (leading ^ leading >>> 32); + result = prime * result + (int) (remaining ^ remaining >>> 32); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ValueInterval)) { + return false; + } + ValueInterval other = (ValueInterval) obj; + return valueType == other.valueType && negative == other.negative && leading == other.leading + && remaining == other.remaining; + } + + @Override + public int compareTypeSafe(Value v, CompareMode mode, CastDataProvider provider) { + ValueInterval other = (ValueInterval) v; + if (negative != other.negative) { + return negative ? -1 : 1; + } + int cmp = Long.compare(leading, other.leading); + if (cmp == 0) { + cmp = Long.compare(remaining, other.remaining); + } + return negative ? -cmp : cmp; + } + + @Override + public int getSignum() { + return negative ? -1 : leading == 0L && remaining == 0L ? 0 : 1; + } + + @Override + public Value add(Value v) { + return IntervalUtils.intervalFromAbsolute(getQualifier(), + IntervalUtils.intervalToAbsolute(this).add(IntervalUtils.intervalToAbsolute((ValueInterval) v))); + } + + @Override + public Value subtract(Value v) { + return IntervalUtils.intervalFromAbsolute(getQualifier(), + IntervalUtils.intervalToAbsolute(this).subtract(IntervalUtils.intervalToAbsolute((ValueInterval) v))); + } + + @Override + public Value negate() { + if (leading == 0L && remaining == 0L) { + return this; + } + return Value.cache(new ValueInterval(valueType, !negative, leading, remaining)); + } + +} diff --git a/h2/src/main/org/h2/value/ValueJavaObject.java b/h2/src/main/org/h2/value/ValueJavaObject.java new file mode 100644 index 0000000..8629a02 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueJavaObject.java @@ -0,0 +1,73 @@ +/* + * 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.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; + +/** + * Implementation of the JAVA_OBJECT data type. + */ +public final class ValueJavaObject extends ValueBytesBase { + + private static final ValueJavaObject EMPTY = new ValueJavaObject(Utils.EMPTY_BYTES); + + 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); + } + } + + /** + * Get or create a java object value for the given byte array. + * Do not clone the data. + * + * @param b the byte array + * @return the value + */ + public static ValueJavaObject getNoCopy(byte[] b) { + int length = b.length; + if (length == 0) { + return EMPTY; + } + ValueJavaObject obj = new ValueJavaObject(b); + if (length > SysProperties.OBJECT_CACHE_MAX_PER_ELEMENT_SIZE) { + return obj; + } + return (ValueJavaObject) Value.cache(obj); + } + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_JAVA_OBJECT; + } + + @Override + public int getValueType() { + return JAVA_OBJECT; + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + if ((sqlFlags & NO_CASTS) == 0) { + return super.getSQL(builder.append("CAST("), DEFAULT_SQL_FLAGS).append(" AS JAVA_OBJECT)"); + } + return super.getSQL(builder, DEFAULT_SQL_FLAGS); + } + + @Override + public String getString() { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, "JAVA_OBJECT to CHARACTER VARYING"); + } + +} diff --git a/h2/src/main/org/h2/value/ValueJson.java b/h2/src/main/org/h2/value/ValueJson.java new file mode 100644 index 0000000..aa0011a --- /dev/null +++ b/h2/src/main/org/h2/value/ValueJson.java @@ -0,0 +1,243 @@ +/* + * 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: Lazarev Nikita + */ +package org.h2.value; + +import java.io.ByteArrayOutputStream; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +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; +import org.h2.util.json.JSONBytesSource; +import org.h2.util.json.JSONItemType; +import org.h2.util.json.JSONStringSource; +import org.h2.util.json.JSONStringTarget; + +/** + * Implementation of the JSON data type. + */ +public final class ValueJson extends ValueBytesBase { + + private static final byte[] NULL_BYTES = "null".getBytes(StandardCharsets.ISO_8859_1), + TRUE_BYTES = "true".getBytes(StandardCharsets.ISO_8859_1), + FALSE_BYTES = "false".getBytes(StandardCharsets.ISO_8859_1); + + /** + * {@code null} JSON value. + */ + public static final ValueJson NULL = new ValueJson(NULL_BYTES); + + /** + * {@code true} JSON value. + */ + public static final ValueJson TRUE = new ValueJson(TRUE_BYTES); + + /** + * {@code false} JSON value. + */ + public static final ValueJson FALSE = new ValueJson(FALSE_BYTES); + + /** + * {@code 0} JSON value. + */ + public static final ValueJson ZERO = new ValueJson(new byte[] { '0' }); + + 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 + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + String s = JSONBytesSource.parse(value, new JSONStringTarget(true)); + return builder.append("JSON '").append(s).append('\''); + } + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_JSON; + } + + @Override + public int getValueType() { + return Value.JSON; + } + + @Override + public String getString() { + return new String(value, StandardCharsets.UTF_8); + } + + /** + * Returns JSON item type. + * + * @return JSON item type + */ + public JSONItemType getItemType() { + switch (value[0]) { + case '[': + return JSONItemType.ARRAY; + case '{': + return JSONItemType.OBJECT; + default: + return JSONItemType.SCALAR; + } + } + + /** + * Returns JSON value with the specified content. + * + * @param s + * JSON representation, will be normalized + * @return JSON value + * @throws DbException + * on invalid JSON + */ + public static ValueJson fromJson(String s) { + byte[] bytes; + try { + bytes = JSONStringSource.normalize(s); + } catch (RuntimeException ex) { + if (s.length() > 80) { + s = new StringBuilder(83).append(s, 0, 80).append("...").toString(); + } + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, s); + } + return getInternal(bytes); + } + + /** + * Returns JSON value with the specified content. + * + * @param bytes + * JSON representation, will be normalized + * @return JSON value + * @throws DbException + * on invalid JSON + */ + public static ValueJson fromJson(byte[] bytes) { + try { + bytes = JSONBytesSource.normalize(bytes); + } catch (RuntimeException ex) { + StringBuilder builder = new StringBuilder().append("X'"); + if (bytes.length > 40) { + StringUtils.convertBytesToHex(builder, bytes, 40).append("..."); + } else { + StringUtils.convertBytesToHex(builder, bytes); + } + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, builder.append('\'').toString()); + } + return getInternal(bytes); + } + + /** + * Returns JSON value with the specified boolean content. + * + * @param bool + * boolean value + * @return JSON value + */ + public static ValueJson get(boolean bool) { + return bool ? TRUE : FALSE; + } + + /** + * Returns JSON value with the specified numeric content. + * + * @param number + * integer value + * @return JSON value + */ + public static ValueJson get(int number) { + return number != 0 ? getNumber(Integer.toString(number)) : ZERO; + } + + /** + * Returns JSON value with the specified numeric content. + * + * @param number + * long value + * @return JSON value + */ + public static ValueJson get(long number) { + return number != 0L ? getNumber(Long.toString(number)) : ZERO; + } + + /** + * Returns JSON value with the specified numeric content. + * + * @param number + * big decimal value + * @return JSON value + */ + public static ValueJson get(BigDecimal number) { + if (number.signum() == 0 && number.scale() == 0) { + return ZERO; + } + String s = number.toString(); + int index = s.indexOf('E'); + if (index >= 0 && s.charAt(++index) == '+') { + int length = s.length(); + s = new StringBuilder(length - 1).append(s, 0, index).append(s, index + 1, length).toString(); + } + return getNumber(s); + } + + /** + * Returns JSON value with the specified string content. + * + * @param string + * string value + * @return JSON value + */ + public static ValueJson get(String string) { + return new ValueJson(JSONByteArrayTarget.encodeString( // + new ByteArrayOutputStream(string.length() + 2), string).toByteArray()); + } + + /** + * Returns JSON value with the specified content. + * + * @param bytes + * normalized JSON representation + * @return JSON value + */ + public static ValueJson getInternal(byte[] bytes) { + int l = bytes.length; + switch (l) { + case 1: + if (bytes[0] == '0') { + return ZERO; + } + break; + case 4: + if (Arrays.equals(TRUE_BYTES, bytes)) { + return TRUE; + } else if (Arrays.equals(NULL_BYTES, bytes)) { + return NULL; + } + break; + case 5: + if (Arrays.equals(FALSE_BYTES, bytes)) { + return FALSE; + } + } + return new ValueJson(bytes); + } + + private static ValueJson getNumber(String s) { + return new ValueJson(s.getBytes(StandardCharsets.ISO_8859_1)); + } + +} diff --git a/h2/src/main/org/h2/value/ValueLob.java b/h2/src/main/org/h2/value/ValueLob.java new file mode 100644 index 0000000..3e1f91a --- /dev/null +++ b/h2/src/main/org/h2/value/ValueLob.java @@ -0,0 +1,297 @@ +/* + * 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.value; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; + +import org.h2.engine.Constants; +import org.h2.engine.SysProperties; +import org.h2.message.DbException; +import org.h2.store.DataHandler; +import org.h2.store.LobStorageFrontend; +import org.h2.store.LobStorageInterface; +import org.h2.store.RangeInputStream; +import org.h2.store.RangeReader; +import org.h2.store.fs.FileUtils; +import org.h2.util.IOUtils; +import org.h2.util.MathUtils; +import org.h2.util.StringUtils; +import org.h2.util.Utils; +import org.h2.value.lob.LobData; +import org.h2.value.lob.LobDataDatabase; +import org.h2.value.lob.LobDataInMemory; + +/** + * A implementation of the BINARY LARGE OBJECT and CHARACTER LARGE OBJECT data + * types. Small objects are kept in memory and stored in the record. Large + * objects are either stored in the database, or in temporary files. + */ +public abstract class ValueLob extends Value { + + static final int BLOCK_COMPARISON_SIZE = 512; + + private static void rangeCheckUnknown(long zeroBasedOffset, long length) { + if (zeroBasedOffset < 0) { + throw DbException.getInvalidValueException("offset", zeroBasedOffset + 1); + } + if (length < 0) { + throw DbException.getInvalidValueException("length", length); + } + } + + /** + * Create an input stream that is s subset of the given stream. + * + * @param inputStream the source input stream + * @param oneBasedOffset the offset (1 means no offset) + * @param length the length of the result, in bytes + * @param dataSize the length of the input, in bytes + * @return the smaller input stream + */ + protected static InputStream rangeInputStream(InputStream inputStream, long oneBasedOffset, long length, + long dataSize) { + if (dataSize > 0) { + rangeCheck(oneBasedOffset - 1, length, dataSize); + } else { + rangeCheckUnknown(oneBasedOffset - 1, length); + } + try { + return new RangeInputStream(inputStream, oneBasedOffset - 1, length); + } catch (IOException e) { + throw DbException.getInvalidValueException("offset", oneBasedOffset); + } + } + + /** + * Create a reader that is s subset of the given reader. + * + * @param reader the input reader + * @param oneBasedOffset the offset (1 means no offset) + * @param length the length of the result, in bytes + * @param dataSize the length of the input, in bytes + * @return the smaller input stream + */ + static Reader rangeReader(Reader reader, long oneBasedOffset, long length, long dataSize) { + if (dataSize > 0) { + rangeCheck(oneBasedOffset - 1, length, dataSize); + } else { + rangeCheckUnknown(oneBasedOffset - 1, length); + } + try { + return new RangeReader(reader, oneBasedOffset - 1, length); + } catch (IOException e) { + throw DbException.getInvalidValueException("offset", oneBasedOffset); + } + } + + private TypeInfo type; + + final LobData lobData; + + /** + * Length in bytes. + */ + long octetLength; + + /** + * Length in characters. + */ + long charLength; + + /** + * Cache the hashCode because it can be expensive to compute. + */ + private int hash; + + ValueLob(LobData lobData, long octetLength, long charLength) { + this.lobData = lobData; + this.octetLength = octetLength; + this.charLength = charLength; + } + + /** + * Create file name for temporary LOB storage + * @param handler to get path from + * @return full path and name of the created file + * @throws IOException if file creation fails + */ + static String createTempLobFileName(DataHandler handler) throws IOException { + String path = handler.getDatabasePath(); + if (path.isEmpty()) { + path = SysProperties.PREFIX_TEMP_FILE; + } + return FileUtils.createTempFile(path, Constants.SUFFIX_TEMP_FILE, true); + } + + static int getBufferSize(DataHandler handler, long remaining) { + if (remaining < 0 || remaining > Integer.MAX_VALUE) { + remaining = Integer.MAX_VALUE; + } + int inplace = handler.getMaxLengthInplaceLob(); + long m = Constants.IO_BUFFER_SIZE; + if (m < remaining && m <= inplace) { + // using "1L" to force long arithmetic because + // inplace could be Integer.MAX_VALUE + m = Math.min(remaining, inplace + 1L); + // the buffer size must be bigger than the inplace lob, otherwise we + // can't know if it must be stored in-place or not + m = MathUtils.roundUpLong(m, Constants.IO_BUFFER_SIZE); + } + m = Math.min(remaining, m); + m = MathUtils.convertLongToInt(m); + if (m < 0) { + m = Integer.MAX_VALUE; + } + return (int) m; + } + + /** + * Check if this value is linked to a specific table. For values that are + * kept fully in memory, this method returns false. + * + * @return true if it is + */ + public boolean isLinkedToTable() { + return lobData.isLinkedToTable(); + } + + /** + * Remove the underlying resource, if any. For values that are kept fully in + * memory this method has no effect. + */ + public void remove() { + lobData.remove(this); + } + + /** + * Copy a large value, to be used in the given table. For values that are + * kept fully in memory this method has no effect. + * + * @param database the data handler + * @param tableId the table where this object is used + * @return the new value or itself + */ + public abstract ValueLob copy(DataHandler database, int tableId); + + @Override + public TypeInfo getType() { + TypeInfo type = this.type; + if (type == null) { + int valueType = getValueType(); + this.type = type = new TypeInfo(valueType, valueType == CLOB ? charLength : octetLength, 0, null); + } + return type; + } + + DbException getStringTooLong(long precision) { + return DbException.getValueTooLongException("CHARACTER VARYING", readString(81), precision); + } + + String readString(int len) { + try { + return IOUtils.readStringAndClose(getReader(), len); + } catch (IOException e) { + throw DbException.convertIOException(e, toString()); + } + } + + @Override + public Reader getReader() { + return IOUtils.getReader(getInputStream()); + } + + @Override + public byte[] getBytes() { + if (lobData instanceof LobDataInMemory) { + return Utils.cloneByteArray(getSmall()); + } + return getBytesInternal(); + } + + @Override + public byte[] getBytesNoCopy() { + if (lobData instanceof LobDataInMemory) { + return getSmall(); + } + return getBytesInternal(); + } + + private byte[] getSmall() { + byte[] small = ((LobDataInMemory) lobData).getSmall(); + int p = small.length; + if (p > Constants.MAX_STRING_LENGTH) { + throw DbException.getValueTooLongException("BINARY VARYING", StringUtils.convertBytesToHex(small, 41), p); + } + return small; + } + + abstract byte[] getBytesInternal(); + + DbException getBinaryTooLong(long precision) { + return DbException.getValueTooLongException("BINARY VARYING", StringUtils.convertBytesToHex(readBytes(41)), + precision); + } + + byte[] readBytes(int len) { + try { + return IOUtils.readBytesAndClose(getInputStream(), len); + } catch (IOException e) { + throw DbException.convertIOException(e, toString()); + } + } + + @Override + public int hashCode() { + if (hash == 0) { + int valueType = getValueType(); + long length = valueType == Value.CLOB ? charLength : octetLength; + if (length > 4096) { + // TODO: should calculate the hash code when saving, and store + // it in the database file + return (int) (length ^ (length >>> 32)); + } + hash = Utils.getByteArrayHash(getBytesNoCopy()); + } + return hash; + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof ValueLob)) + return false; + ValueLob otherLob = (ValueLob) other; + if (hashCode() != otherLob.hashCode()) + return false; + return compareTypeSafe((Value) other, null, null) == 0; + } + + @Override + public int getMemory() { + return lobData.getMemory(); + } + + public LobData getLobData() { + return lobData; + } + + /** + * Create an independent copy of this value, that will be bound to a result. + * + * @return the value (this for small objects) + */ + public ValueLob copyToResult() { + if (lobData instanceof LobDataDatabase) { + LobStorageInterface s = lobData.getDataHandler().getLobStorage(); + if (!s.isReadOnly()) { + return s.copyLob(this, LobStorageFrontend.TABLE_RESULT); + } + } + return this; + } + +} diff --git a/h2/src/main/org/h2/value/ValueNull.java b/h2/src/main/org/h2/value/ValueNull.java new file mode 100644 index 0000000..f6cda3b --- /dev/null +++ b/h2/src/main/org/h2/value/ValueNull.java @@ -0,0 +1,150 @@ +/* + * 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.value; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; + +import org.h2.engine.CastDataProvider; +import org.h2.message.DbException; + +/** + * Implementation of NULL. NULL is not a regular data type. + */ +public final class ValueNull extends Value { + + /** + * The main NULL instance. + */ + public static final ValueNull INSTANCE = new ValueNull(); + + /** + * The precision of NULL. + */ + static final int PRECISION = 1; + + /** + * The display size of the textual representation of NULL. + */ + static final int DISPLAY_SIZE = 4; + + private ValueNull() { + // don't allow construction + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return builder.append("NULL"); + } + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_NULL; + } + + @Override + public int getValueType() { + return NULL; + } + + @Override + public int getMemory() { + // Singleton value + return 0; + } + + @Override + public String getString() { + return null; + } + + @Override + public Reader getReader() { + return null; + } + + @Override + public Reader getReader(long oneBasedOffset, long length) { + return null; + } + + @Override + public byte[] getBytes() { + return null; + } + + @Override + public InputStream getInputStream() { + return null; + } + + @Override + public InputStream getInputStream(long oneBasedOffset, long length) { + return null; + } + + @Override + public boolean getBoolean() { + throw DbException.getInternalError(); + } + + @Override + public byte getByte() { + throw DbException.getInternalError(); + } + + @Override + public short getShort() { + throw DbException.getInternalError(); + } + + @Override + public int getInt() { + throw DbException.getInternalError(); + } + + @Override + public long getLong() { + throw DbException.getInternalError(); + } + + @Override + public BigDecimal getBigDecimal() { + return null; + } + + @Override + public float getFloat() { + throw DbException.getInternalError(); + } + + @Override + public double getDouble() { + throw DbException.getInternalError(); + } + + @Override + public int compareTypeSafe(Value v, CompareMode mode, CastDataProvider provider) { + throw DbException.getInternalError("compare null"); + } + + @Override + public boolean containsNull() { + return true; + } + + @Override + public int hashCode() { + return 0; + } + + @Override + public boolean equals(Object other) { + return other == this; + } + +} diff --git a/h2/src/main/org/h2/value/ValueNumeric.java b/h2/src/main/org/h2/value/ValueNumeric.java new file mode 100644 index 0000000..8a7a164 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueNumeric.java @@ -0,0 +1,218 @@ +/* + * 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.value; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; + +import org.h2.api.ErrorCode; +import org.h2.engine.CastDataProvider; +import org.h2.message.DbException; + +/** + * Implementation of the NUMERIC data type. + */ +public final class ValueNumeric extends ValueBigDecimalBase { + + /** + * The value 'zero'. + */ + public static final ValueNumeric ZERO = new ValueNumeric(BigDecimal.ZERO); + + /** + * The value 'one'. + */ + public static final ValueNumeric ONE = new ValueNumeric(BigDecimal.ONE); + + /** + * The default scale for a NUMERIC value. + */ + public static final int DEFAULT_SCALE = 0; + + /** + * The maximum scale. + */ + public static final int MAXIMUM_SCALE = 100_000; + + private ValueNumeric(BigDecimal value) { + super(value); + if (value == null) { + throw new IllegalArgumentException("null"); + } + int scale = value.scale(); + if (scale < 0 || scale > MAXIMUM_SCALE) { + throw DbException.get(ErrorCode.INVALID_VALUE_SCALE, Integer.toString(scale), "0", "" + MAXIMUM_SCALE); + } + } + + @Override + public String getString() { + return value.toPlainString(); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + String s = getString(); + if ((sqlFlags & NO_CASTS) == 0 && s.indexOf('.') < 0 && value.compareTo(MAX_LONG_DECIMAL) <= 0 + && value.compareTo(MIN_LONG_DECIMAL) >= 0) { + return builder.append("CAST(").append(value).append(" AS NUMERIC(").append(value.precision()).append("))"); + } + return builder.append(s); + } + + @Override + public TypeInfo getType() { + TypeInfo type = this.type; + if (type == null) { + this.type = type = new TypeInfo(NUMERIC, value.precision(), value.scale(), null); + } + return type; + } + + @Override + public int getValueType() { + return NUMERIC; + } + + @Override + public Value add(Value v) { + return get(value.add(((ValueNumeric) v).value)); + } + + @Override + public Value subtract(Value v) { + return get(value.subtract(((ValueNumeric) v).value)); + } + + @Override + public Value negate() { + return get(value.negate()); + } + + @Override + public Value multiply(Value v) { + return get(value.multiply(((ValueNumeric) v).value)); + } + + @Override + public Value divide(Value v, TypeInfo quotientType) { + BigDecimal divisor = ((ValueNumeric) v).value; + if (divisor.signum() == 0) { + throw DbException.get(ErrorCode.DIVISION_BY_ZERO_1, getTraceSQL()); + } + return get(value.divide(divisor, quotientType.getScale(), RoundingMode.HALF_DOWN)); + } + + @Override + public Value modulus(Value v) { + ValueBigDecimalBase dec = (ValueNumeric) v; + if (dec.value.signum() == 0) { + throw DbException.get(ErrorCode.DIVISION_BY_ZERO_1, getTraceSQL()); + } + return get(value.remainder(dec.value)); + } + + @Override + public int compareTypeSafe(Value o, CompareMode mode, CastDataProvider provider) { + return value.compareTo(((ValueNumeric) o).value); + } + + @Override + public int getSignum() { + return value.signum(); + } + + @Override + public BigDecimal getBigDecimal() { + return value; + } + + @Override + public float getFloat() { + return value.floatValue(); + } + + @Override + public double getDouble() { + return value.doubleValue(); + } + + @Override + public int hashCode() { + return getClass().hashCode() * 31 + value.hashCode(); + } + + @Override + public boolean equals(Object other) { + return other instanceof ValueNumeric && value.equals(((ValueNumeric) other).value); + } + + @Override + public int getMemory() { + return value.precision() + 120; + } + + /** + * Get or create a NUMERIC value for the given big decimal. + * + * @param dec the big decimal + * @return the value + */ + public static ValueNumeric get(BigDecimal dec) { + if (BigDecimal.ZERO.equals(dec)) { + return ZERO; + } else if (BigDecimal.ONE.equals(dec)) { + return ONE; + } + return (ValueNumeric) Value.cache(new ValueNumeric(dec)); + } + + /** + * Get or create a NUMERIC value for the given big decimal with possibly + * negative scale. If scale is negative, it is normalized to 0. + * + * @param dec + * the big decimal + * @return the value + */ + public static ValueNumeric getAnyScale(BigDecimal dec) { + if (dec.scale() < 0) { + dec = dec.setScale(0, RoundingMode.UNNECESSARY); + } + return get(dec); + } + + /** + * Get or create a NUMERIC value for the given big integer. + * + * @param bigInteger the big integer + * @return the value + */ + public static ValueNumeric get(BigInteger bigInteger) { + if (bigInteger.signum() == 0) { + return ZERO; + } else if (BigInteger.ONE.equals(bigInteger)) { + return ONE; + } + return (ValueNumeric) Value.cache(new ValueNumeric(new BigDecimal(bigInteger))); + } + + /** + * Set the scale of a BigDecimal value. + * + * @param bd the BigDecimal value + * @param scale the new scale + * @return the scaled value + */ + public static BigDecimal setScale(BigDecimal bd, int scale) { + if (scale < 0 || scale > MAXIMUM_SCALE) { + throw DbException.getInvalidValueException("scale", scale); + } + return bd.setScale(scale, RoundingMode.HALF_UP); + } + +} diff --git a/h2/src/main/org/h2/value/ValueReal.java b/h2/src/main/org/h2/value/ValueReal.java new file mode 100644 index 0000000..3470fa7 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueReal.java @@ -0,0 +1,196 @@ +/* + * 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.value; + +import java.math.BigDecimal; + +import org.h2.api.ErrorCode; +import org.h2.engine.CastDataProvider; +import org.h2.message.DbException; + +/** + * Implementation of the REAL data type. + */ +public final class ValueReal extends Value { + + /** + * The precision in bits. + */ + static final int PRECISION = 24; + + /** + * The approximate precision in decimal digits. + */ + static final int DECIMAL_PRECISION = 7; + + /** + * The maximum display size of a REAL. + * Example: -1.12345676E-20 + */ + static final int DISPLAY_SIZE = 15; + + /** + * Float.floatToIntBits(0f). + */ + public static final int ZERO_BITS = 0; + + /** + * The value 0. + */ + public static final ValueReal ZERO = new ValueReal(0f); + + /** + * The value 1. + */ + public static final ValueReal ONE = new ValueReal(1f); + + private static final ValueReal NAN = new ValueReal(Float.NaN); + + private final float value; + + private ValueReal(float value) { + this.value = value; + } + + @Override + public Value add(Value v) { + return get(value + ((ValueReal) v).value); + } + + @Override + public Value subtract(Value v) { + return get(value - ((ValueReal) v).value); + } + + @Override + public Value negate() { + return get(-value); + } + + @Override + public Value multiply(Value v) { + return get(value * ((ValueReal) v).value); + } + + @Override + public Value divide(Value v, TypeInfo quotientType) { + ValueReal v2 = (ValueReal) v; + if (v2.value == 0.0) { + throw DbException.get(ErrorCode.DIVISION_BY_ZERO_1, getTraceSQL()); + } + return get(value / v2.value); + } + + @Override + public Value modulus(Value v) { + ValueReal other = (ValueReal) v; + if (other.value == 0) { + throw DbException.get(ErrorCode.DIVISION_BY_ZERO_1, getTraceSQL()); + } + return get(value % other.value); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + if ((sqlFlags & NO_CASTS) == 0) { + return getSQL(builder.append("CAST(")).append(" AS REAL)"); + } + return getSQL(builder); + } + + private StringBuilder getSQL(StringBuilder builder) { + if (value == Float.POSITIVE_INFINITY) { + return builder.append("'Infinity'"); + } else if (value == Float.NEGATIVE_INFINITY) { + return builder.append("'-Infinity'"); + } else if (Float.isNaN(value)) { + return builder.append("'NaN'"); + } else { + return builder.append(value); + } + } + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_REAL; + } + + @Override + public int getValueType() { + return REAL; + } + + @Override + public int compareTypeSafe(Value o, CompareMode mode, CastDataProvider provider) { + return Float.compare(value, ((ValueReal) o).value); + } + + @Override + public int getSignum() { + return value == 0 || Float.isNaN(value) ? 0 : value < 0 ? -1 : 1; + } + + @Override + public BigDecimal getBigDecimal() { + if (Float.isFinite(value)) { + // better rounding behavior than BigDecimal.valueOf(f) + return new BigDecimal(Float.toString(value)); + } + // Infinite or NaN + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, Float.toString(value)); + } + + @Override + public float getFloat() { + return value; + } + + @Override + public double getDouble() { + return value; + } + + @Override + public String getString() { + return Float.toString(value); + } + + @Override + public int hashCode() { + /* + * NaNs are normalized in get() method, so it's safe to use + * floatToRawIntBits() instead of floatToIntBits() here. + */ + return Float.floatToRawIntBits(value); + } + + /** + * Get or create a REAL value for the given float. + * + * @param d the float + * @return the value + */ + public static ValueReal get(float d) { + if (d == 1.0F) { + return ONE; + } else if (d == 0.0F) { + // -0.0 == 0.0, and we want to return 0.0 for both + return ZERO; + } else if (Float.isNaN(d)) { + return NAN; + } + return (ValueReal) Value.cache(new ValueReal(d)); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof ValueReal)) { + return false; + } + return compareTypeSafe((ValueReal) other, null, null) == 0; + } + +} diff --git a/h2/src/main/org/h2/value/ValueRow.java b/h2/src/main/org/h2/value/ValueRow.java new file mode 100644 index 0000000..37095ee --- /dev/null +++ b/h2/src/main/org/h2/value/ValueRow.java @@ -0,0 +1,166 @@ +/* + * 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.value; + +import org.h2.api.ErrorCode; +import org.h2.engine.CastDataProvider; +import org.h2.engine.Constants; +import org.h2.message.DbException; +import org.h2.result.SimpleResult; + +/** + * Row value. + */ +public final class ValueRow extends ValueCollectionBase { + + /** + * Empty row. + */ + public static final ValueRow EMPTY = get(Value.EMPTY_VALUES); + + private TypeInfo type; + + private ValueRow(TypeInfo type, Value[] list) { + super(list); + int degree = list.length; + if (degree > Constants.MAX_COLUMNS) { + throw DbException.get(ErrorCode.TOO_MANY_COLUMNS_1, "" + Constants.MAX_COLUMNS); + } + if (type != null) { + if (type.getValueType() != ROW || ((ExtTypeInfoRow) type.getExtTypeInfo()).getFields().size() != degree) { + throw DbException.getInternalError(); + } + this.type = type; + } + } + + /** + * Get or create a row value for the given value array. + * Do not clone the data. + * + * @param list the value array + * @return the value + */ + public static ValueRow get(Value[] list) { + return new ValueRow(null, list); + } + + /** + * Get or create a typed row value for the given value array. + * Do not clone the data. + * + * @param extTypeInfo the extended data type information + * @param list the value array + * @return the value + */ + public static ValueRow get(ExtTypeInfoRow extTypeInfo, Value[] list) { + return new ValueRow(new TypeInfo(ROW, -1, -1, extTypeInfo), list); + } + + /** + * Get or create a typed row value for the given value array. + * Do not clone the data. + * + * @param typeInfo the data type information + * @param list the value array + * @return the value + */ + public static ValueRow get(TypeInfo typeInfo, Value[] list) { + return new ValueRow(typeInfo, list); + } + + @Override + public TypeInfo getType() { + TypeInfo type = this.type; + if (type == null) { + this.type = type = TypeInfo.getTypeInfo(Value.ROW, 0, 0, new ExtTypeInfoRow(values)); + } + return type; + } + + @Override + public int getValueType() { + return ROW; + } + + @Override + public String getString() { + StringBuilder builder = new StringBuilder("ROW ("); + for (int i = 0; i < values.length; i++) { + if (i > 0) { + builder.append(", "); + } + builder.append(values[i].getString()); + } + return builder.append(')').toString(); + } + + public SimpleResult getResult() { + SimpleResult result = new SimpleResult(); + for (int i = 0, l = values.length; i < l;) { + Value v = values[i++]; + result.addColumn("C" + i, v.getType()); + } + result.addRow(values); + return result; + } + + @Override + public int compareTypeSafe(Value o, CompareMode mode, CastDataProvider provider) { + ValueRow v = (ValueRow) o; + if (values == v.values) { + return 0; + } + int len = values.length; + if (len != v.values.length) { + throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH); + } + for (int i = 0; i < len; i++) { + Value v1 = values[i]; + Value v2 = v.values[i]; + int comp = v1.compareTo(v2, provider, mode); + if (comp != 0) { + return comp; + } + } + return 0; + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + builder.append("ROW ("); + int length = values.length; + for (int i = 0; i < length; i++) { + if (i > 0) { + builder.append(", "); + } + values[i].getSQL(builder, sqlFlags); + } + return builder.append(')'); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof ValueRow)) { + return false; + } + ValueRow v = (ValueRow) other; + if (values == v.values) { + return true; + } + int len = values.length; + if (len != v.values.length) { + return false; + } + for (int i = 0; i < len; i++) { + if (!values[i].equals(v.values[i])) { + return false; + } + } + return true; + } + +} diff --git a/h2/src/main/org/h2/value/ValueSmallint.java b/h2/src/main/org/h2/value/ValueSmallint.java new file mode 100644 index 0000000..f0608ad --- /dev/null +++ b/h2/src/main/org/h2/value/ValueSmallint.java @@ -0,0 +1,179 @@ +/* + * 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.value; + +import java.math.BigDecimal; + +import org.h2.api.ErrorCode; +import org.h2.engine.CastDataProvider; +import org.h2.message.DbException; + +/** + * Implementation of the SMALLINT data type. + */ +public final class ValueSmallint extends Value { + + /** + * The precision in bits. + */ + static final int PRECISION = 16; + + /** + * The approximate precision in decimal digits. + */ + public static final int DECIMAL_PRECISION = 5; + + /** + * The maximum display size of a SMALLINT. + * Example: -32768 + */ + static final int DISPLAY_SIZE = 6; + + private final short value; + + private ValueSmallint(short value) { + this.value = value; + } + + @Override + public Value add(Value v) { + ValueSmallint other = (ValueSmallint) v; + return checkRange(value + other.value); + } + + private static ValueSmallint checkRange(int x) { + if ((short) x != x) { + throw DbException.get(ErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_1, + Integer.toString(x)); + } + return ValueSmallint.get((short) x); + } + + @Override + public int getSignum() { + return Integer.signum(value); + } + + @Override + public Value negate() { + return checkRange(-(int) value); + } + + @Override + public Value subtract(Value v) { + ValueSmallint other = (ValueSmallint) v; + return checkRange(value - other.value); + } + + @Override + public Value multiply(Value v) { + ValueSmallint other = (ValueSmallint) v; + return checkRange(value * other.value); + } + + @Override + public Value divide(Value v, TypeInfo quotientType) { + ValueSmallint other = (ValueSmallint) v; + if (other.value == 0) { + throw DbException.get(ErrorCode.DIVISION_BY_ZERO_1, getTraceSQL()); + } + return checkRange(value / other.value); + } + + @Override + public Value modulus(Value v) { + ValueSmallint other = (ValueSmallint) v; + if (other.value == 0) { + throw DbException.get(ErrorCode.DIVISION_BY_ZERO_1, getTraceSQL()); + } + return ValueSmallint.get((short) (value % other.value)); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + if ((sqlFlags & NO_CASTS) == 0) { + return builder.append("CAST(").append(value).append(" AS SMALLINT)"); + } + return builder.append(value); + } + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_SMALLINT; + } + + @Override + public int getValueType() { + return SMALLINT; + } + + @Override + public byte[] getBytes() { + short value = this.value; + return new byte[] { (byte) (value >> 8), (byte) value }; + } + + @Override + public short getShort() { + return value; + } + + @Override + public int getInt() { + return value; + } + + @Override + public long getLong() { + return value; + } + + @Override + public BigDecimal getBigDecimal() { + return BigDecimal.valueOf(value); + } + + @Override + public float getFloat() { + return value; + } + + @Override + public double getDouble() { + return value; + } + + @Override + public int compareTypeSafe(Value o, CompareMode mode, CastDataProvider provider) { + return Integer.compare(value, ((ValueSmallint) o).value); + } + + @Override + public String getString() { + return Integer.toString(value); + } + + @Override + public int hashCode() { + return value; + } + + /** + * Get or create a SMALLINT value for the given short. + * + * @param i the short + * @return the value + */ + public static ValueSmallint get(short i) { + return (ValueSmallint) Value.cache(new ValueSmallint(i)); + } + + @Override + public boolean equals(Object other) { + return other instanceof ValueSmallint && value == ((ValueSmallint) other).value; + } + +} diff --git a/h2/src/main/org/h2/value/ValueStringBase.java b/h2/src/main/org/h2/value/ValueStringBase.java new file mode 100644 index 0000000..7607a4c --- /dev/null +++ b/h2/src/main/org/h2/value/ValueStringBase.java @@ -0,0 +1,188 @@ +/* + * 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.value; + +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; + +import org.h2.api.ErrorCode; +import org.h2.engine.CastDataProvider; +import org.h2.engine.Constants; +import org.h2.message.DbException; + +/** + * Base implementation of String based data types. + */ +abstract class ValueStringBase extends Value { + + /** + * The value. + */ + String value; + + private TypeInfo type; + + ValueStringBase(String v) { + int length = v.length(); + if (length > Constants.MAX_STRING_LENGTH) { + throw DbException.getValueTooLongException(getTypeName(getValueType()), v, length); + } + this.value = v; + } + + @Override + public final TypeInfo getType() { + TypeInfo type = this.type; + if (type == null) { + int length = value.length(); + this.type = type = new TypeInfo(getValueType(), length, 0, null); + } + return type; + } + + @Override + public int compareTypeSafe(Value v, CompareMode mode, CastDataProvider provider) { + return mode.compareString(value, ((ValueStringBase) v).value, false); + } + + @Override + public int hashCode() { + // TODO hash performance: could build a quicker hash + // by hashing the size and a few characters + return getClass().hashCode() ^ value.hashCode(); + + // proposed code: +// private int hash = 0; +// +// public int hashCode() { +// int h = hash; +// if (h == 0) { +// String s = value; +// int l = s.length(); +// if (l > 0) { +// if (l < 16) +// h = s.hashCode(); +// else { +// h = l; +// for (int i = 1; i <= l; i <<= 1) +// h = 31 * +// (31 * h + s.charAt(i - 1)) + +// s.charAt(l - i); +// } +// hash = h; +// } +// } +// return h; +// } + } + + @Override + public final String getString() { + return value; + } + + @Override + public final byte[] getBytes() { + return value.getBytes(StandardCharsets.UTF_8); + } + + @Override + public final boolean getBoolean() { + String s = value.trim(); + if (s.equalsIgnoreCase("true") || s.equalsIgnoreCase("t") || s.equalsIgnoreCase("yes") + || s.equalsIgnoreCase("y")) { + return true; + } else if (s.equalsIgnoreCase("false") || s.equalsIgnoreCase("f") || s.equalsIgnoreCase("no") + || s.equalsIgnoreCase("n")) { + return false; + } + try { + // convert to a number, and if it is not 0 then it is true + return new BigDecimal(s).signum() != 0; + } catch (NumberFormatException e) { + throw getDataConversionError(BOOLEAN); + } + } + + @Override + public final byte getByte() { + try { + return Byte.parseByte(value.trim()); + } catch (NumberFormatException e) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, e, value); + } + } + + @Override + public final short getShort() { + try { + return Short.parseShort(value.trim()); + } catch (NumberFormatException e) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, e, value); + } + } + + @Override + public final int getInt() { + try { + return Integer.parseInt(value.trim()); + } catch (NumberFormatException e) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, e, value); + } + } + + @Override + public final long getLong() { + try { + return Long.parseLong(value.trim()); + } catch (NumberFormatException e) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, e, value); + } + } + + @Override + public final BigDecimal getBigDecimal() { + try { + return new BigDecimal(value.trim()); + } catch (NumberFormatException e) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, e, value); + } + } + + @Override + public final float getFloat() { + try { + return Float.parseFloat(value.trim()); + } catch (NumberFormatException e) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, e, value); + } + } + + @Override + public final double getDouble() { + try { + return Double.parseDouble(value.trim()); + } catch (NumberFormatException e) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, e, value); + } + } + + @Override + public final int getMemory() { + /* + * Java 11 with -XX:-UseCompressedOops + * Empty string: 88 bytes + * 1 to 4 UTF-16 chars: 96 bytes + */ + return value.length() * 2 + 94; + } + + @Override + public boolean equals(Object other) { + return other != null && getClass() == other.getClass() && value.equals(((ValueStringBase) other).value); + } + +} diff --git a/h2/src/main/org/h2/value/ValueTime.java b/h2/src/main/org/h2/value/ValueTime.java new file mode 100644 index 0000000..4a22df9 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueTime.java @@ -0,0 +1,145 @@ +/* + * 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.value; + +import org.h2.api.ErrorCode; +import org.h2.engine.CastDataProvider; +import org.h2.message.DbException; +import org.h2.util.DateTimeUtils; + +/** + * Implementation of the TIME data type. + */ +public final class ValueTime extends Value { + + /** + * The default precision and display size of the textual representation of a time. + * Example: 10:00:00 + */ + public static final int DEFAULT_PRECISION = 8; + + /** + * The maximum precision and display size of the textual representation of a time. + * Example: 10:00:00.123456789 + */ + public static final int MAXIMUM_PRECISION = 18; + + /** + * The default scale for time. + */ + public static final int DEFAULT_SCALE = 0; + + /** + * The maximum scale for time. + */ + public static final int MAXIMUM_SCALE = 9; + + /** + * Nanoseconds since midnight + */ + private final long nanos; + + /** + * @param nanos nanoseconds since midnight + */ + private ValueTime(long nanos) { + this.nanos = nanos; + } + + /** + * Get or create a time value. + * + * @param nanos the nanoseconds since midnight + * @return the value + */ + public static ValueTime fromNanos(long nanos) { + if (nanos < 0L || nanos >= DateTimeUtils.NANOS_PER_DAY) { + throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2, "TIME", + DateTimeUtils.appendTime(new StringBuilder(), nanos).toString()); + } + return (ValueTime) Value.cache(new ValueTime(nanos)); + } + + /** + * Parse a string to a ValueTime. + * + * @param s the string to parse + * @return the time + */ + public static ValueTime parse(String s) { + try { + return fromNanos(DateTimeUtils.parseTimeNanos(s, 0, s.length())); + } catch (Exception e) { + throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2, + e, "TIME", s); + } + } + + /** + * @return nanoseconds since midnight + */ + public long getNanos() { + return nanos; + } + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_TIME; + } + + @Override + public int getValueType() { + return TIME; + } + + @Override + public String getString() { + return DateTimeUtils.appendTime(new StringBuilder(MAXIMUM_PRECISION), nanos).toString(); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return DateTimeUtils.appendTime(builder.append("TIME '"), nanos).append('\''); + } + + @Override + public int compareTypeSafe(Value o, CompareMode mode, CastDataProvider provider) { + return Long.compare(nanos, ((ValueTime) o).nanos); + } + + @Override + public boolean equals(Object other) { + return this == other || other instanceof ValueTime && nanos == ((ValueTime) other).nanos; + } + + @Override + public int hashCode() { + return (int) (nanos ^ (nanos >>> 32)); + } + + @Override + public Value add(Value v) { + ValueTime t = (ValueTime) v; + return ValueTime.fromNanos(nanos + t.getNanos()); + } + + @Override + public Value subtract(Value v) { + ValueTime t = (ValueTime) v; + return ValueTime.fromNanos(nanos - t.getNanos()); + } + + @Override + public Value multiply(Value v) { + return ValueTime.fromNanos((long) (nanos * v.getDouble())); + } + + @Override + public Value divide(Value v, TypeInfo quotientType) { + return ValueTime.fromNanos((long) (nanos / v.getDouble())); + } + +} diff --git a/h2/src/main/org/h2/value/ValueTimeTimeZone.java b/h2/src/main/org/h2/value/ValueTimeTimeZone.java new file mode 100644 index 0000000..5724848 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueTimeTimeZone.java @@ -0,0 +1,158 @@ +/* + * 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.value; + +import org.h2.api.ErrorCode; +import org.h2.engine.CastDataProvider; +import org.h2.message.DbException; +import org.h2.util.DateTimeUtils; + +/** + * Implementation of the TIME WITH TIME ZONE data type. + */ +public final class ValueTimeTimeZone extends Value { + + /** + * The default precision and display size of the textual representation of a + * time. Example: 10:00:00+10:00 + */ + public static final int DEFAULT_PRECISION = 14; + + /** + * The maximum precision and display size of the textual representation of a + * time. Example: 10:00:00.123456789+10:00 + */ + public static final int MAXIMUM_PRECISION = 24; + + /** + * Nanoseconds since midnight + */ + private final long nanos; + + /** + * Time zone offset from UTC in seconds, range of -18 hours to +18 hours. + * This range is compatible with OffsetTime from JSR-310. + */ + private final int timeZoneOffsetSeconds; + + /** + * @param nanos + * nanoseconds since midnight + */ + private ValueTimeTimeZone(long nanos, int timeZoneOffsetSeconds) { + this.nanos = nanos; + this.timeZoneOffsetSeconds = timeZoneOffsetSeconds; + } + + /** + * Get or create a time value. + * + * @param nanos + * the nanoseconds since midnight + * @param timeZoneOffsetSeconds + * the timezone offset in seconds + * @return the value + */ + public static ValueTimeTimeZone fromNanos(long nanos, int timeZoneOffsetSeconds) { + if (nanos < 0L || nanos >= DateTimeUtils.NANOS_PER_DAY) { + throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2, "TIME WITH TIME ZONE", + DateTimeUtils.appendTime(new StringBuilder(), nanos).toString()); + } + /* + * Some current and historic time zones have offsets larger than 12 + * hours. JSR-310 determines 18 hours as maximum possible offset in both + * directions, so we use this limit too for compatibility. + */ + if (timeZoneOffsetSeconds < (-18 * 60 * 60) || timeZoneOffsetSeconds > (18 * 60 * 60)) { + throw new IllegalArgumentException("timeZoneOffsetSeconds " + timeZoneOffsetSeconds); + } + return (ValueTimeTimeZone) Value.cache(new ValueTimeTimeZone(nanos, timeZoneOffsetSeconds)); + } + + /** + * Parse a string to a ValueTime. + * + * @param s + * the string to parse + * @return the time + */ + public static ValueTimeTimeZone parse(String s) { + try { + return DateTimeUtils.parseTimeWithTimeZone(s, null); + } catch (Exception e) { + throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2, e, "TIME WITH TIME ZONE", s); + } + } + + /** + * @return nanoseconds since midnight + */ + public long getNanos() { + return nanos; + } + + /** + * The time zone offset in seconds. + * + * @return the offset + */ + public int getTimeZoneOffsetSeconds() { + return timeZoneOffsetSeconds; + } + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_TIME_TZ; + } + + @Override + public int getValueType() { + return TIME_TZ; + } + + @Override + public int getMemory() { + return 32; + } + + @Override + public String getString() { + return toString(new StringBuilder(MAXIMUM_PRECISION)).toString(); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return toString(builder.append("TIME WITH TIME ZONE '")).append('\''); + } + + private StringBuilder toString(StringBuilder builder) { + return DateTimeUtils.appendTimeZone(DateTimeUtils.appendTime(builder, nanos), timeZoneOffsetSeconds); + } + + @Override + public int compareTypeSafe(Value o, CompareMode mode, CastDataProvider provider) { + ValueTimeTimeZone t = (ValueTimeTimeZone) o; + return Long.compare(nanos - timeZoneOffsetSeconds * DateTimeUtils.NANOS_PER_SECOND, + t.nanos - t.timeZoneOffsetSeconds * DateTimeUtils.NANOS_PER_SECOND); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (!(other instanceof ValueTimeTimeZone)) { + return false; + } + ValueTimeTimeZone t = (ValueTimeTimeZone) other; + return nanos == t.nanos && timeZoneOffsetSeconds == t.timeZoneOffsetSeconds; + } + + @Override + public int hashCode() { + return (int) (nanos ^ (nanos >>> 32) ^ timeZoneOffsetSeconds); + } + +} diff --git a/h2/src/main/org/h2/value/ValueTimestamp.java b/h2/src/main/org/h2/value/ValueTimestamp.java new file mode 100644 index 0000000..1f48d23 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueTimestamp.java @@ -0,0 +1,202 @@ +/* + * 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.value; + +import org.h2.api.ErrorCode; +import org.h2.engine.CastDataProvider; +import org.h2.message.DbException; +import org.h2.util.DateTimeUtils; + +/** + * Implementation of the TIMESTAMP data type. + */ +public final class ValueTimestamp extends Value { + + /** + * The default precision and display size of the textual representation of a timestamp. + * Example: 2001-01-01 23:59:59.123456 + */ + public static final int DEFAULT_PRECISION = 26; + + /** + * The maximum precision and display size of the textual representation of a timestamp. + * Example: 2001-01-01 23:59:59.123456789 + */ + public static final int MAXIMUM_PRECISION = 29; + + /** + * The default scale for timestamps. + */ + public static final int DEFAULT_SCALE = 6; + + /** + * The maximum scale for timestamps. + */ + public static final int MAXIMUM_SCALE = 9; + + /** + * A bit field with bits for the year, month, and day (see DateTimeUtils for + * encoding) + */ + private final long dateValue; + /** + * The nanoseconds since midnight. + */ + private final long timeNanos; + + private ValueTimestamp(long dateValue, long timeNanos) { + if (dateValue < DateTimeUtils.MIN_DATE_VALUE || dateValue > DateTimeUtils.MAX_DATE_VALUE) { + throw new IllegalArgumentException("dateValue out of range " + dateValue); + } + if (timeNanos < 0 || timeNanos >= DateTimeUtils.NANOS_PER_DAY) { + throw new IllegalArgumentException("timeNanos out of range " + timeNanos); + } + this.dateValue = dateValue; + this.timeNanos = timeNanos; + } + + /** + * Get or create a date value for the given date. + * + * @param dateValue the date value, a bit field with bits for the year, + * month, and day + * @param timeNanos the nanoseconds since midnight + * @return the value + */ + public static ValueTimestamp fromDateValueAndNanos(long dateValue, long timeNanos) { + return (ValueTimestamp) Value.cache(new ValueTimestamp(dateValue, timeNanos)); + } + + /** + * Parse a string to a ValueTimestamp, using the given {@link CastDataProvider}. + * This method supports the format +/-year-month-day[ -]hour[:.]minute[:.]seconds.fractional + * and an optional timezone part. + * + * @param s the string to parse + * @param provider + * the cast information provider, may be {@code null} for + * literals without time zone + * @return the date + */ + public static ValueTimestamp parse(String s, CastDataProvider provider) { + try { + return (ValueTimestamp) DateTimeUtils.parseTimestamp(s, provider, false); + } catch (Exception e) { + throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2, e, "TIMESTAMP", s); + } + } + + /** + * A bit field with bits for the year, month, and day (see DateTimeUtils for + * encoding). + * + * @return the data value + */ + public long getDateValue() { + return dateValue; + } + + /** + * The nanoseconds since midnight. + * + * @return the nanoseconds + */ + public long getTimeNanos() { + return timeNanos; + } + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_TIMESTAMP; + } + + @Override + public int getValueType() { + return TIMESTAMP; + } + + @Override + public int getMemory() { + return 32; + } + + @Override + public String getString() { + return toString(new StringBuilder(MAXIMUM_PRECISION), false).toString(); + } + + /** + * Returns value as string in ISO format. + * + * @return value as string in ISO format + */ + public String getISOString() { + return toString(new StringBuilder(MAXIMUM_PRECISION), true).toString(); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return toString(builder.append("TIMESTAMP '"), false).append('\''); + } + + private StringBuilder toString(StringBuilder builder, boolean iso) { + DateTimeUtils.appendDate(builder, dateValue).append(iso ? 'T' : ' '); + return DateTimeUtils.appendTime(builder, timeNanos); + } + + @Override + public int compareTypeSafe(Value o, CompareMode mode, CastDataProvider provider) { + ValueTimestamp t = (ValueTimestamp) o; + int c = Long.compare(dateValue, t.dateValue); + if (c != 0) { + return c; + } + return Long.compare(timeNanos, t.timeNanos); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (!(other instanceof ValueTimestamp)) { + return false; + } + ValueTimestamp x = (ValueTimestamp) other; + return dateValue == x.dateValue && timeNanos == x.timeNanos; + } + + @Override + public int hashCode() { + return (int) (dateValue ^ (dateValue >>> 32) ^ timeNanos ^ (timeNanos >>> 32)); + } + + @Override + public Value add(Value v) { + ValueTimestamp t = (ValueTimestamp) v; + long absoluteDay = DateTimeUtils.absoluteDayFromDateValue(dateValue) + + DateTimeUtils.absoluteDayFromDateValue(t.dateValue); + long nanos = timeNanos + t.timeNanos; + if (nanos >= DateTimeUtils.NANOS_PER_DAY) { + nanos -= DateTimeUtils.NANOS_PER_DAY; + absoluteDay++; + } + return ValueTimestamp.fromDateValueAndNanos(DateTimeUtils.dateValueFromAbsoluteDay(absoluteDay), nanos); + } + + @Override + public Value subtract(Value v) { + ValueTimestamp t = (ValueTimestamp) v; + long absoluteDay = DateTimeUtils.absoluteDayFromDateValue(dateValue) + - DateTimeUtils.absoluteDayFromDateValue(t.dateValue); + long nanos = timeNanos - t.timeNanos; + if (nanos < 0) { + nanos += DateTimeUtils.NANOS_PER_DAY; + absoluteDay--; + } + return ValueTimestamp.fromDateValueAndNanos(DateTimeUtils.dateValueFromAbsoluteDay(absoluteDay), nanos); + } + +} diff --git a/h2/src/main/org/h2/value/ValueTimestampTimeZone.java b/h2/src/main/org/h2/value/ValueTimestampTimeZone.java new file mode 100644 index 0000000..f2670bb --- /dev/null +++ b/h2/src/main/org/h2/value/ValueTimestampTimeZone.java @@ -0,0 +1,219 @@ +/* + * 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.value; + +import org.h2.api.ErrorCode; +import org.h2.engine.CastDataProvider; +import org.h2.message.DbException; +import org.h2.util.DateTimeUtils; + +/** + * Implementation of the TIMESTAMP WITH TIME ZONE data type. + */ +public final class ValueTimestampTimeZone extends Value { + + /** + * The default precision and display size of the textual representation of a timestamp. + * Example: 2001-01-01 23:59:59.123456+10:00 + */ + public static final int DEFAULT_PRECISION = 32; + + /** + * The maximum precision and display size of the textual representation of a timestamp. + * Example: 2001-01-01 23:59:59.123456789+10:00 + */ + public static final int MAXIMUM_PRECISION = 35; + + /** + * A bit field with bits for the year, month, and day (see DateTimeUtils for + * encoding) + */ + private final long dateValue; + /** + * The nanoseconds since midnight. + */ + private final long timeNanos; + /** + * Time zone offset from UTC in seconds, range of -18 hours to +18 hours. This + * range is compatible with OffsetDateTime from JSR-310. + */ + private final int timeZoneOffsetSeconds; + + private ValueTimestampTimeZone(long dateValue, long timeNanos, int timeZoneOffsetSeconds) { + if (dateValue < DateTimeUtils.MIN_DATE_VALUE || dateValue > DateTimeUtils.MAX_DATE_VALUE) { + throw new IllegalArgumentException("dateValue out of range " + dateValue); + } + if (timeNanos < 0 || timeNanos >= DateTimeUtils.NANOS_PER_DAY) { + throw new IllegalArgumentException( + "timeNanos out of range " + timeNanos); + } + /* + * Some current and historic time zones have offsets larger than 12 hours. + * JSR-310 determines 18 hours as maximum possible offset in both directions, so + * we use this limit too for compatibility. + */ + if (timeZoneOffsetSeconds < (-18 * 60 * 60) + || timeZoneOffsetSeconds > (18 * 60 * 60)) { + throw new IllegalArgumentException( + "timeZoneOffsetSeconds out of range " + timeZoneOffsetSeconds); + } + this.dateValue = dateValue; + this.timeNanos = timeNanos; + this.timeZoneOffsetSeconds = timeZoneOffsetSeconds; + } + + /** + * Get or create a date value for the given date. + * + * @param dateValue the date value, a bit field with bits for the year, + * month, and day + * @param timeNanos the nanoseconds since midnight + * @param timeZoneOffsetSeconds the timezone offset in seconds + * @return the value + */ + public static ValueTimestampTimeZone fromDateValueAndNanos(long dateValue, long timeNanos, + int timeZoneOffsetSeconds) { + return (ValueTimestampTimeZone) Value.cache(new ValueTimestampTimeZone( + dateValue, timeNanos, timeZoneOffsetSeconds)); + } + + /** + * Parse a string to a ValueTimestamp. This method supports the format + * +/-year-month-day hour:minute:seconds.fractional and an optional timezone + * part. + * + * @param s the string to parse + * @param provider + * the cast information provider, may be {@code null} for + * literals with time zone + * @return the date + */ + public static ValueTimestampTimeZone parse(String s, CastDataProvider provider) { + try { + return (ValueTimestampTimeZone) DateTimeUtils.parseTimestamp(s, provider, true); + } catch (Exception e) { + throw DbException.get(ErrorCode.INVALID_DATETIME_CONSTANT_2, e, "TIMESTAMP WITH TIME ZONE", s); + } + } + + /** + * A bit field with bits for the year, month, and day (see DateTimeUtils for + * encoding). + * + * @return the data value + */ + public long getDateValue() { + return dateValue; + } + + /** + * The nanoseconds since midnight. + * + * @return the nanoseconds + */ + public long getTimeNanos() { + return timeNanos; + } + + /** + * The time zone offset in seconds. + * + * @return the offset + */ + public int getTimeZoneOffsetSeconds() { + return timeZoneOffsetSeconds; + } + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_TIMESTAMP_TZ; + } + + @Override + public int getValueType() { + return TIMESTAMP_TZ; + } + + @Override + public int getMemory() { + // Java 11 with -XX:-UseCompressedOops + return 40; + } + + @Override + public String getString() { + return toString(new StringBuilder(MAXIMUM_PRECISION), false).toString(); + } + + /** + * Returns value as string in ISO format. + * + * @return value as string in ISO format + */ + public String getISOString() { + return toString(new StringBuilder(MAXIMUM_PRECISION), true).toString(); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return toString(builder.append("TIMESTAMP WITH TIME ZONE '"), false).append('\''); + } + + private StringBuilder toString(StringBuilder builder, boolean iso) { + DateTimeUtils.appendDate(builder, dateValue).append(iso ? 'T' : ' '); + DateTimeUtils.appendTime(builder, timeNanos); + return DateTimeUtils.appendTimeZone(builder, timeZoneOffsetSeconds); + } + + @Override + public int compareTypeSafe(Value o, CompareMode mode, CastDataProvider provider) { + ValueTimestampTimeZone t = (ValueTimestampTimeZone) o; + // Maximum time zone offset is +/-18 hours so difference in days between local + // and UTC cannot be more than one day + long dateValueA = dateValue; + long timeA = timeNanos - timeZoneOffsetSeconds * DateTimeUtils.NANOS_PER_SECOND; + if (timeA < 0) { + timeA += DateTimeUtils.NANOS_PER_DAY; + dateValueA = DateTimeUtils.decrementDateValue(dateValueA); + } else if (timeA >= DateTimeUtils.NANOS_PER_DAY) { + timeA -= DateTimeUtils.NANOS_PER_DAY; + dateValueA = DateTimeUtils.incrementDateValue(dateValueA); + } + long dateValueB = t.dateValue; + long timeB = t.timeNanos - t.timeZoneOffsetSeconds * DateTimeUtils.NANOS_PER_SECOND; + if (timeB < 0) { + timeB += DateTimeUtils.NANOS_PER_DAY; + dateValueB = DateTimeUtils.decrementDateValue(dateValueB); + } else if (timeB >= DateTimeUtils.NANOS_PER_DAY) { + timeB -= DateTimeUtils.NANOS_PER_DAY; + dateValueB = DateTimeUtils.incrementDateValue(dateValueB); + } + int cmp = Long.compare(dateValueA, dateValueB); + if (cmp != 0) { + return cmp; + } + return Long.compare(timeA, timeB); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (!(other instanceof ValueTimestampTimeZone)) { + return false; + } + ValueTimestampTimeZone x = (ValueTimestampTimeZone) other; + return dateValue == x.dateValue && timeNanos == x.timeNanos + && timeZoneOffsetSeconds == x.timeZoneOffsetSeconds; + } + + @Override + public int hashCode() { + return (int) (dateValue ^ (dateValue >>> 32) ^ timeNanos + ^ (timeNanos >>> 32) ^ timeZoneOffsetSeconds); + } + +} diff --git a/h2/src/main/org/h2/value/ValueTinyint.java b/h2/src/main/org/h2/value/ValueTinyint.java new file mode 100644 index 0000000..f80ee45 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueTinyint.java @@ -0,0 +1,183 @@ +/* + * 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.value; + +import java.math.BigDecimal; + +import org.h2.api.ErrorCode; +import org.h2.engine.CastDataProvider; +import org.h2.message.DbException; + +/** + * Implementation of the TINYINT data type. + */ +public final class ValueTinyint extends Value { + + /** + * The precision in bits. + */ + static final int PRECISION = 8; + + /** + * The approximate precision in decimal digits. + */ + public static final int DECIMAL_PRECISION = 3; + + /** + * The display size for a TINYINT. + * Example: -127 + */ + static final int DISPLAY_SIZE = 4; + + private final byte value; + + private ValueTinyint(byte value) { + this.value = value; + } + + @Override + public Value add(Value v) { + ValueTinyint other = (ValueTinyint) v; + return checkRange(value + other.value); + } + + private static ValueTinyint checkRange(int x) { + if ((byte) x != x) { + throw DbException.get(ErrorCode.NUMERIC_VALUE_OUT_OF_RANGE_1, + Integer.toString(x)); + } + return ValueTinyint.get((byte) x); + } + + @Override + public int getSignum() { + return Integer.signum(value); + } + + @Override + public Value negate() { + return checkRange(-(int) value); + } + + @Override + public Value subtract(Value v) { + ValueTinyint other = (ValueTinyint) v; + return checkRange(value - other.value); + } + + @Override + public Value multiply(Value v) { + ValueTinyint other = (ValueTinyint) v; + return checkRange(value * other.value); + } + + @Override + public Value divide(Value v, TypeInfo quotientType) { + ValueTinyint other = (ValueTinyint) v; + if (other.value == 0) { + throw DbException.get(ErrorCode.DIVISION_BY_ZERO_1, getTraceSQL()); + } + return checkRange(value / other.value); + } + + @Override + public Value modulus(Value v) { + ValueTinyint other = (ValueTinyint) v; + if (other.value == 0) { + throw DbException.get(ErrorCode.DIVISION_BY_ZERO_1, getTraceSQL()); + } + return ValueTinyint.get((byte) (value % other.value)); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + if ((sqlFlags & NO_CASTS) == 0) { + return builder.append("CAST(").append(value).append(" AS TINYINT)"); + } + return builder.append(value); + } + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_TINYINT; + } + + @Override + public int getValueType() { + return TINYINT; + } + + @Override + public byte[] getBytes() { + return new byte[] { value }; + } + + @Override + public byte getByte() { + return value; + } + + @Override + public short getShort() { + return value; + } + + @Override + public int getInt() { + return value; + } + + @Override + public long getLong() { + return value; + } + + @Override + public BigDecimal getBigDecimal() { + return BigDecimal.valueOf(value); + } + + @Override + public float getFloat() { + return value; + } + + @Override + public double getDouble() { + return value; + } + + @Override + public int compareTypeSafe(Value o, CompareMode mode, CastDataProvider provider) { + return Integer.compare(value, ((ValueTinyint) o).value); + } + + @Override + public String getString() { + return Integer.toString(value); + } + + @Override + public int hashCode() { + return value; + } + + /** + * Get or create a TINYINT value for the given byte. + * + * @param i the byte + * @return the value + */ + public static ValueTinyint get(byte i) { + return (ValueTinyint) Value.cache(new ValueTinyint(i)); + } + + @Override + public boolean equals(Object other) { + return other instanceof ValueTinyint && value == ((ValueTinyint) other).value; + } + +} diff --git a/h2/src/main/org/h2/value/ValueToObjectConverter.java b/h2/src/main/org/h2/value/ValueToObjectConverter.java new file mode 100644 index 0000000..84827b8 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueToObjectConverter.java @@ -0,0 +1,637 @@ +/* + * 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.value; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.SQLXML; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.Period; +import java.time.ZonedDateTime; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map.Entry; +import java.util.UUID; + +import org.h2.api.ErrorCode; +import org.h2.api.Interval; +import org.h2.engine.Session; +import org.h2.jdbc.JdbcArray; +import org.h2.jdbc.JdbcBlob; +import org.h2.jdbc.JdbcClob; +import org.h2.jdbc.JdbcConnection; +import org.h2.jdbc.JdbcLob; +import org.h2.jdbc.JdbcResultSet; +import org.h2.jdbc.JdbcSQLXML; +import org.h2.message.DbException; +import org.h2.message.TraceObject; +import org.h2.util.JSR310Utils; +import org.h2.util.JdbcUtils; +import org.h2.util.LegacyDateTimeUtils; + +/** + * Data type conversion methods between values and Java objects. + */ +public final class ValueToObjectConverter extends TraceObject { + + /** + * The Geometry class. This object is null if the JTS jar file is not in the + * classpath. + */ + public static final Class GEOMETRY_CLASS; + + private static final String GEOMETRY_CLASS_NAME = "org.locationtech.jts.geom.Geometry"; + + static { + Class g; + try { + g = JdbcUtils.loadUserClass(GEOMETRY_CLASS_NAME); + } catch (Exception e) { + g = null; + } + GEOMETRY_CLASS = g; + } + + /** + * Convert a Java object to a value. + * + * @param session + * the session + * @param x + * the value + * @param type + * the suggested value type, or {@code Value#UNKNOWN} + * @return the value + */ + public static Value objectToValue(Session session, Object x, int type) { + if (x == null) { + return ValueNull.INSTANCE; + } else if (type == Value.JAVA_OBJECT) { + return ValueJavaObject.getNoCopy(JdbcUtils.serialize(x, session.getJavaObjectSerializer())); + } else if (x instanceof Value) { + Value v = (Value) x; + if (v instanceof ValueLob) { + session.addTemporaryLob((ValueLob) v); + } + return v; + } + Class clazz = x.getClass(); + if (clazz == String.class) { + return ValueVarchar.get((String) x, session); + } else if (clazz == Long.class) { + return ValueBigint.get((Long) x); + } else if (clazz == Integer.class) { + return ValueInteger.get((Integer) x); + } else if (clazz == Boolean.class) { + return ValueBoolean.get((Boolean) x); + } else if (clazz == Byte.class) { + return ValueTinyint.get((Byte) x); + } else if (clazz == Short.class) { + return ValueSmallint.get((Short) x); + } else if (clazz == Float.class) { + return ValueReal.get((Float) x); + } else if (clazz == Double.class) { + return ValueDouble.get((Double) x); + } else if (clazz == byte[].class) { + return ValueVarbinary.get((byte[]) x); + } else if (clazz == UUID.class) { + return ValueUuid.get((UUID) x); + } else if (clazz == Character.class) { + return ValueChar.get(((Character) x).toString()); + } else if (clazz == LocalDate.class) { + return JSR310Utils.localDateToValue((LocalDate) x); + } else if (clazz == LocalTime.class) { + return JSR310Utils.localTimeToValue((LocalTime) x); + } else if (clazz == LocalDateTime.class) { + return JSR310Utils.localDateTimeToValue((LocalDateTime) x); + } else if (clazz == Instant.class) { + return JSR310Utils.instantToValue((Instant) x); + } else if (clazz == OffsetTime.class) { + return JSR310Utils.offsetTimeToValue((OffsetTime) x); + } else if (clazz == OffsetDateTime.class) { + return JSR310Utils.offsetDateTimeToValue((OffsetDateTime) x); + } else if (clazz == ZonedDateTime.class) { + return JSR310Utils.zonedDateTimeToValue((ZonedDateTime) x); + } else if (clazz == Interval.class) { + Interval i = (Interval) x; + return ValueInterval.from(i.getQualifier(), i.isNegative(), i.getLeading(), i.getRemaining()); + } else if (clazz == Period.class) { + return JSR310Utils.periodToValue((Period) x); + } else if (clazz == Duration.class) { + return JSR310Utils.durationToValue((Duration) x); + } + if (x instanceof Object[]) { + return arrayToValue(session, x); + } else if (GEOMETRY_CLASS != null && GEOMETRY_CLASS.isAssignableFrom(clazz)) { + return ValueGeometry.getFromGeometry(x); + } else if (x instanceof BigInteger) { + return ValueNumeric.get((BigInteger) x); + } else if (x instanceof BigDecimal) { + return ValueNumeric.getAnyScale((BigDecimal) x); + } else { + return otherToValue(session, x); + } + } + + private static Value otherToValue(Session session, Object x) { + if (x instanceof Array) { + Array array = (Array) x; + try { + return arrayToValue(session, array.getArray()); + } catch (SQLException e) { + throw DbException.convert(e); + } + } else if (x instanceof ResultSet) { + return resultSetToValue(session, (ResultSet) x); + } + ValueLob lob; + if (x instanceof Reader) { + Reader r = (Reader) x; + if (!(r instanceof BufferedReader)) { + r = new BufferedReader(r); + } + lob = session.getDataHandler().getLobStorage().createClob(r, -1); + } else if (x instanceof Clob) { + try { + Clob clob = (Clob) x; + Reader r = new BufferedReader(clob.getCharacterStream()); + lob = session.getDataHandler().getLobStorage().createClob(r, clob.length()); + } catch (SQLException e) { + throw DbException.convert(e); + } + } else if (x instanceof InputStream) { + lob = session.getDataHandler().getLobStorage().createBlob((InputStream) x, -1); + } else if (x instanceof Blob) { + try { + Blob blob = (Blob) x; + lob = session.getDataHandler().getLobStorage().createBlob(blob.getBinaryStream(), blob.length()); + } catch (SQLException e) { + throw DbException.convert(e); + } + } else if (x instanceof SQLXML) { + try { + lob = session.getDataHandler().getLobStorage() + .createClob(new BufferedReader(((SQLXML) x).getCharacterStream()), -1); + } catch (SQLException e) { + throw DbException.convert(e); + } + } else { + Value v = LegacyDateTimeUtils.legacyObjectToValue(session, x); + if (v != null) { + return v; + } + return ValueJavaObject.getNoCopy(JdbcUtils.serialize(x, session.getJavaObjectSerializer())); + } + return session.addTemporaryLob(lob); + } + + private static Value arrayToValue(Session session, Object x) { + // (a.getClass().isArray()); + // (a.getClass().getComponentType().isPrimitive()); + Object[] o = (Object[]) x; + int len = o.length; + Value[] v = new Value[len]; + for (int i = 0; i < len; i++) { + v[i] = objectToValue(session, o[i], Value.UNKNOWN); + } + return ValueArray.get(v, session); + } + + static Value resultSetToValue(Session session, ResultSet rs) { + try { + ResultSetMetaData meta = rs.getMetaData(); + int columnCount = meta.getColumnCount(); + LinkedHashMap columns = readResultSetMeta(session, meta, columnCount); + if (!rs.next()) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, "Empty ResultSet to ROW value"); + } + Value[] list = new Value[columnCount]; + Iterator> iterator = columns.entrySet().iterator(); + for (int j = 0; j < columnCount; j++) { + list[j] = ValueToObjectConverter.objectToValue(session, rs.getObject(j + 1), + iterator.next().getValue().getValueType()); + } + if (rs.next()) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, "Multi-row ResultSet to ROW value"); + } + return ValueRow.get(new ExtTypeInfoRow(columns), list); + } catch (SQLException e) { + throw DbException.convert(e); + } + } + + private static LinkedHashMap readResultSetMeta(Session session, ResultSetMetaData meta, + int columnCount) throws SQLException { + LinkedHashMap columns = new LinkedHashMap<>(); + for (int i = 0; i < columnCount; i++) { + String alias = meta.getColumnLabel(i + 1); + String columnTypeName = meta.getColumnTypeName(i + 1); + int columnType = DataType.convertSQLTypeToValueType(meta.getColumnType(i + 1), columnTypeName); + int precision = meta.getPrecision(i + 1); + int scale = meta.getScale(i + 1); + TypeInfo typeInfo; + if (columnType == Value.ARRAY && columnTypeName.endsWith(" ARRAY")) { + typeInfo = TypeInfo + .getTypeInfo(Value.ARRAY, -1L, 0, + TypeInfo.getTypeInfo(DataType.getTypeByName( + columnTypeName.substring(0, columnTypeName.length() - 6), + session.getMode()).type)); + } else { + typeInfo = TypeInfo.getTypeInfo(columnType, precision, scale, null); + } + columns.put(alias, typeInfo); + } + return columns; + } + + /** + * Converts the specified value to an object of the specified type. + * + * @param + * the type + * @param type + * the class + * @param value + * the value + * @param conn + * the connection + * @return the object of the specified class representing the specified + * value, or {@code null} + */ + @SuppressWarnings("unchecked") + public static T valueToObject(Class type, Value value, JdbcConnection conn) { + if (value == ValueNull.INSTANCE) { + return null; + } else if (type == BigDecimal.class) { + return (T) value.getBigDecimal(); + } else if (type == BigInteger.class) { + return (T) value.getBigDecimal().toBigInteger(); + } else if (type == String.class) { + return (T) value.getString(); + } else if (type == Boolean.class) { + return (T) (Boolean) value.getBoolean(); + } else if (type == Byte.class) { + return (T) (Byte) value.getByte(); + } else if (type == Short.class) { + return (T) (Short) value.getShort(); + } else if (type == Integer.class) { + return (T) (Integer) value.getInt(); + } else if (type == Long.class) { + return (T) (Long) value.getLong(); + } else if (type == Float.class) { + return (T) (Float) value.getFloat(); + } else if (type == Double.class) { + return (T) (Double) value.getDouble(); + } else if (type == UUID.class) { + return (T) value.convertToUuid().getUuid(); + } else if (type == byte[].class) { + return (T) value.getBytes(); + } else if (type == Character.class) { + String s = value.getString(); + return (T) (Character) (s.isEmpty() ? ' ' : s.charAt(0)); + } else if (type == Interval.class) { + if (!(value instanceof ValueInterval)) { + value = value.convertTo(TypeInfo.TYPE_INTERVAL_DAY_TO_SECOND); + } + ValueInterval v = (ValueInterval) value; + return (T) new Interval(v.getQualifier(), false, v.getLeading(), v.getRemaining()); + } else if (type == LocalDate.class) { + return (T) JSR310Utils.valueToLocalDate(value, conn); + } else if (type == LocalTime.class) { + return (T) JSR310Utils.valueToLocalTime(value, conn); + } else if (type == LocalDateTime.class) { + return (T) JSR310Utils.valueToLocalDateTime(value, conn); + } else if (type == OffsetTime.class) { + return (T) JSR310Utils.valueToOffsetTime(value, conn); + } else if (type == OffsetDateTime.class) { + return (T) JSR310Utils.valueToOffsetDateTime(value, conn); + } else if (type == ZonedDateTime.class) { + return (T) JSR310Utils.valueToZonedDateTime(value, conn); + } else if (type == Instant.class) { + return (T) JSR310Utils.valueToInstant(value, conn); + } else if (type == Period.class) { + return (T) JSR310Utils.valueToPeriod(value); + } else if (type == Duration.class) { + return (T) JSR310Utils.valueToDuration(value); + } else if (type.isArray()) { + return (T) valueToArray(type, value, conn); + } else if (GEOMETRY_CLASS != null && GEOMETRY_CLASS.isAssignableFrom(type)) { + return (T) value.convertToGeometry(null).getGeometry(); + } else { + return (T) valueToOther(type, value, conn); + } + } + + private static Object valueToArray(Class type, Value value, JdbcConnection conn) { + Value[] array = ((ValueArray) value).getList(); + Class componentType = type.getComponentType(); + int length = array.length; + Object[] objArray = (Object[]) java.lang.reflect.Array.newInstance(componentType, length); + for (int i = 0; i < length; i++) { + objArray[i] = valueToObject(componentType, array[i], conn); + } + return objArray; + } + + private static Object valueToOther(Class type, Value value, JdbcConnection conn) { + if (type == Object.class) { + return JdbcUtils.deserialize( + value.convertToJavaObject(TypeInfo.TYPE_JAVA_OBJECT, Value.CONVERT_TO, null).getBytesNoCopy(), + conn.getJavaObjectSerializer()); + } else if (type == InputStream.class) { + return value.getInputStream(); + } else if (type == Reader.class) { + return value.getReader(); + } else if (type == java.sql.Array.class) { + return new JdbcArray(conn, value, getNextId(TraceObject.ARRAY)); + } else if (type == Blob.class) { + return new JdbcBlob(conn, value, JdbcLob.State.WITH_VALUE, getNextId(TraceObject.BLOB)); + } else if (type == Clob.class) { + return new JdbcClob(conn, value, JdbcLob.State.WITH_VALUE, getNextId(TraceObject.CLOB)); + } else if (type == SQLXML.class) { + return new JdbcSQLXML(conn, value, JdbcLob.State.WITH_VALUE, getNextId(TraceObject.SQLXML)); + } else if (type == ResultSet.class) { + return new JdbcResultSet(conn, null, null, value.convertToAnyRow().getResult(), + getNextId(TraceObject.RESULT_SET), true, false, false); + } else { + Object obj = LegacyDateTimeUtils.valueToLegacyType(type, value, conn); + if (obj != null) { + return obj; + } + if (value.getValueType() == Value.JAVA_OBJECT) { + obj = JdbcUtils.deserialize(value.getBytesNoCopy(), conn.getJavaObjectSerializer()); + if (type.isAssignableFrom(obj.getClass())) { + return obj; + } + } + throw DbException.getUnsupportedException("converting to class " + type.getName()); + } + } + + /** + * Get the name of the Java class for the given value type. + * + * @param type + * the value type + * @param forJdbc + * if {@code true} get class for JDBC layer, if {@code false} get + * class for Java functions API + * @return the class + */ + public static Class getDefaultClass(int type, boolean forJdbc) { + switch (type) { + case Value.NULL: + return Void.class; + case Value.CHAR: + case Value.VARCHAR: + case Value.VARCHAR_IGNORECASE: + case Value.ENUM: + return String.class; + case Value.CLOB: + return Clob.class; + case Value.BINARY: + case Value.VARBINARY: + case Value.JSON: + return byte[].class; + case Value.BLOB: + return Blob.class; + case Value.BOOLEAN: + return Boolean.class; + case Value.TINYINT: + if (forJdbc) { + return Integer.class; + } + return Byte.class; + case Value.SMALLINT: + if (forJdbc) { + return Integer.class; + } + return Short.class; + case Value.INTEGER: + return Integer.class; + case Value.BIGINT: + return Long.class; + case Value.NUMERIC: + case Value.DECFLOAT: + return BigDecimal.class; + case Value.REAL: + return Float.class; + case Value.DOUBLE: + return Double.class; + case Value.DATE: + return forJdbc ? java.sql.Date.class : LocalDate.class; + case Value.TIME: + return forJdbc ? java.sql.Time.class : LocalTime.class; + case Value.TIME_TZ: + return OffsetTime.class; + case Value.TIMESTAMP: + return forJdbc ? java.sql.Timestamp.class : LocalDateTime.class; + case Value.TIMESTAMP_TZ: + return OffsetDateTime.class; + case Value.INTERVAL_YEAR: + case Value.INTERVAL_MONTH: + case Value.INTERVAL_DAY: + case Value.INTERVAL_HOUR: + case Value.INTERVAL_MINUTE: + case Value.INTERVAL_SECOND: + case Value.INTERVAL_YEAR_TO_MONTH: + case Value.INTERVAL_DAY_TO_HOUR: + case Value.INTERVAL_DAY_TO_MINUTE: + case Value.INTERVAL_DAY_TO_SECOND: + case Value.INTERVAL_HOUR_TO_MINUTE: + case Value.INTERVAL_HOUR_TO_SECOND: + case Value.INTERVAL_MINUTE_TO_SECOND: + return Interval.class; + case Value.JAVA_OBJECT: + return forJdbc ? Object.class : byte[].class; + case Value.GEOMETRY: { + Class clazz = GEOMETRY_CLASS; + return clazz != null ? clazz : String.class; + } + case Value.UUID: + return UUID.class; + case Value.ARRAY: + if (forJdbc) { + return Array.class; + } + return Object[].class; + case Value.ROW: + if (forJdbc) { + return ResultSet.class; + } + return Object[].class; + default: + throw DbException.getUnsupportedException("data type " + type); + } + } + + /** + * Converts the specified value to the default Java object for its type. + * + * @param value + * the value + * @param conn + * the connection + * @param forJdbc + * if {@code true} perform conversion for JDBC layer, if + * {@code false} perform conversion for Java functions API + * @return the object + */ + public static Object valueToDefaultObject(Value value, JdbcConnection conn, boolean forJdbc) { + switch (value.getValueType()) { + case Value.NULL: + return null; + case Value.CHAR: + case Value.VARCHAR: + case Value.VARCHAR_IGNORECASE: + case Value.ENUM: + return value.getString(); + case Value.CLOB: + return new JdbcClob(conn, value, JdbcLob.State.WITH_VALUE, getNextId(TraceObject.CLOB)); + case Value.BINARY: + case Value.VARBINARY: + case Value.JSON: + return value.getBytes(); + case Value.BLOB: + return new JdbcBlob(conn, value, JdbcLob.State.WITH_VALUE, getNextId(TraceObject.BLOB)); + case Value.BOOLEAN: + return value.getBoolean(); + case Value.TINYINT: + if (forJdbc) { + return value.getInt(); + } + return value.getByte(); + case Value.SMALLINT: + if (forJdbc) { + return value.getInt(); + } + return value.getShort(); + case Value.INTEGER: + return value.getInt(); + case Value.BIGINT: + return value.getLong(); + case Value.NUMERIC: + case Value.DECFLOAT: + return value.getBigDecimal(); + case Value.REAL: + return value.getFloat(); + case Value.DOUBLE: + return value.getDouble(); + case Value.DATE: + return forJdbc ? LegacyDateTimeUtils.toDate(conn, null, value) : JSR310Utils.valueToLocalDate(value, null); + case Value.TIME: + return forJdbc ? LegacyDateTimeUtils.toTime(conn, null, value) : JSR310Utils.valueToLocalTime(value, null); + case Value.TIME_TZ: + return JSR310Utils.valueToOffsetTime(value, null); + case Value.TIMESTAMP: + return forJdbc ? LegacyDateTimeUtils.toTimestamp(conn, null, value) + : JSR310Utils.valueToLocalDateTime(value, null); + case Value.TIMESTAMP_TZ: + return JSR310Utils.valueToOffsetDateTime(value, null); + case Value.INTERVAL_YEAR: + case Value.INTERVAL_MONTH: + case Value.INTERVAL_DAY: + case Value.INTERVAL_HOUR: + case Value.INTERVAL_MINUTE: + case Value.INTERVAL_SECOND: + case Value.INTERVAL_YEAR_TO_MONTH: + case Value.INTERVAL_DAY_TO_HOUR: + case Value.INTERVAL_DAY_TO_MINUTE: + case Value.INTERVAL_DAY_TO_SECOND: + case Value.INTERVAL_HOUR_TO_MINUTE: + case Value.INTERVAL_HOUR_TO_SECOND: + case Value.INTERVAL_MINUTE_TO_SECOND: + return ((ValueInterval) value).getInterval(); + case Value.JAVA_OBJECT: + return forJdbc ? JdbcUtils.deserialize(value.getBytesNoCopy(), conn.getJavaObjectSerializer()) + : value.getBytes(); + case Value.GEOMETRY: + return GEOMETRY_CLASS != null ? ((ValueGeometry) value).getGeometry() : value.getString(); + case Value.UUID: + return ((ValueUuid) value).getUuid(); + case Value.ARRAY: + if (forJdbc) { + return new JdbcArray(conn, value, getNextId(TraceObject.ARRAY)); + } + return valueToDefaultArray(value, conn, forJdbc); + case Value.ROW: + if (forJdbc) { + return new JdbcResultSet(conn, null, null, ((ValueRow) value).getResult(), + getNextId(TraceObject.RESULT_SET), true, false, false); + } + return valueToDefaultArray(value, conn, forJdbc); + default: + throw DbException.getUnsupportedException("data type " + value.getValueType()); + } + } + + /** + * Converts the specified array value to array of default Java objects for + * its type. + * + * @param value + * the array value + * @param conn + * the connection + * @param forJdbc + * if {@code true} perform conversion for JDBC layer, if + * {@code false} perform conversion for Java functions API + * @return the object + */ + public static Object valueToDefaultArray(Value value, JdbcConnection conn, boolean forJdbc) { + Value[] values = ((ValueCollectionBase) value).getList(); + int len = values.length; + Object[] list = new Object[len]; + for (int i = 0; i < len; i++) { + list[i] = valueToDefaultObject(values[i], conn, forJdbc); + } + return list; + } + + /** + * Read a value from the given result set. + * + * @param session + * the session + * @param rs + * the result set + * @param columnIndex + * the column index (1-based) + * @return the value + */ + public static Value readValue(Session session, JdbcResultSet rs, int columnIndex) { + Value value = rs.getInternal(columnIndex); + switch (value.getValueType()) { + case Value.CLOB: + value = session.addTemporaryLob( + session.getDataHandler().getLobStorage().createClob(new BufferedReader(value.getReader()), -1)); + break; + case Value.BLOB: + value = session + .addTemporaryLob(session.getDataHandler().getLobStorage().createBlob(value.getInputStream(), -1)); + } + return value; + } + + private ValueToObjectConverter() { + } + +} diff --git a/h2/src/main/org/h2/value/ValueToObjectConverter2.java b/h2/src/main/org/h2/value/ValueToObjectConverter2.java new file mode 100644 index 0000000..c7cb4a9 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueToObjectConverter2.java @@ -0,0 +1,432 @@ +/* + * 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.value; + +import static org.h2.value.ValueToObjectConverter.GEOMETRY_CLASS; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Date; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.Period; +import java.time.ZonedDateTime; +import java.util.UUID; + +import org.h2.api.IntervalQualifier; +import org.h2.engine.Session; +import org.h2.jdbc.JdbcResultSet; +import org.h2.message.DbException; +import org.h2.message.TraceObject; +import org.h2.util.IntervalUtils; +import org.h2.util.JSR310Utils; +import org.h2.util.JdbcUtils; +import org.h2.util.LegacyDateTimeUtils; +import org.h2.util.Utils; + +/** + * Data type conversion methods between values and Java objects to use on the + * server side on H2 only. + */ +public final class ValueToObjectConverter2 extends TraceObject { + + /** + * Get the type information for the given Java class. + * + * @param clazz + * the Java class + * @return the value type + */ + public static TypeInfo classToType(Class clazz) { + if (clazz == null) { + return TypeInfo.TYPE_NULL; + } + if (clazz.isPrimitive()) { + clazz = Utils.getNonPrimitiveClass(clazz); + } + if (clazz == Void.class) { + return TypeInfo.TYPE_NULL; + } else if (clazz == String.class || clazz == Character.class) { + return TypeInfo.TYPE_VARCHAR; + } else if (clazz == byte[].class) { + return TypeInfo.TYPE_VARBINARY; + } else if (clazz == Boolean.class) { + return TypeInfo.TYPE_BOOLEAN; + } else if (clazz == Byte.class) { + return TypeInfo.TYPE_TINYINT; + } else if (clazz == Short.class) { + return TypeInfo.TYPE_SMALLINT; + } else if (clazz == Integer.class) { + return TypeInfo.TYPE_INTEGER; + } else if (clazz == Long.class) { + return TypeInfo.TYPE_BIGINT; + } else if (clazz == Float.class) { + return TypeInfo.TYPE_REAL; + } else if (clazz == Double.class) { + return TypeInfo.TYPE_DOUBLE; + } else if (clazz == LocalDate.class) { + return TypeInfo.TYPE_DATE; + } else if (clazz == LocalTime.class) { + return TypeInfo.TYPE_TIME; + } else if (clazz == OffsetTime.class) { + return TypeInfo.TYPE_TIME_TZ; + } else if (clazz == LocalDateTime.class) { + return TypeInfo.TYPE_TIMESTAMP; + } else if (clazz == OffsetDateTime.class || clazz == ZonedDateTime.class || clazz == Instant.class) { + return TypeInfo.TYPE_TIMESTAMP_TZ; + } else if (clazz == Period.class) { + return TypeInfo.TYPE_INTERVAL_YEAR_TO_MONTH; + } else if (clazz == Duration.class) { + return TypeInfo.TYPE_INTERVAL_DAY_TO_SECOND; + } else if (UUID.class == clazz) { + return TypeInfo.TYPE_UUID; + } else if (clazz.isArray()) { + return TypeInfo.getTypeInfo(Value.ARRAY, Integer.MAX_VALUE, 0, classToType(clazz.getComponentType())); + } else if (Clob.class.isAssignableFrom(clazz) || Reader.class.isAssignableFrom(clazz)) { + return TypeInfo.TYPE_CLOB; + } else if (Blob.class.isAssignableFrom(clazz) || InputStream.class.isAssignableFrom(clazz)) { + return TypeInfo.TYPE_BLOB; + } else if (BigDecimal.class.isAssignableFrom(clazz)) { + return TypeInfo.TYPE_NUMERIC_FLOATING_POINT; + } else if (GEOMETRY_CLASS != null && GEOMETRY_CLASS.isAssignableFrom(clazz)) { + return TypeInfo.TYPE_GEOMETRY; + } else if (Array.class.isAssignableFrom(clazz)) { + return TypeInfo.TYPE_ARRAY_UNKNOWN; + } else if (ResultSet.class.isAssignableFrom(clazz)) { + return TypeInfo.TYPE_ROW_EMPTY; + } else { + TypeInfo t = LegacyDateTimeUtils.legacyClassToType(clazz); + if (t != null) { + return t; + } + return TypeInfo.TYPE_JAVA_OBJECT; + } + } + + /** + * Read a value from the given result set. + * + * @param session + * the session + * @param rs + * the result set + * @param columnIndex + * the column index (1-based) + * @param type + * the data type + * @return the value + */ + public static Value readValue(Session session, ResultSet rs, int columnIndex, int type) { + Value v; + if (rs instanceof JdbcResultSet) { + v = ValueToObjectConverter.readValue(session, (JdbcResultSet) rs, columnIndex); + } else { + try { + v = readValueOther(session, rs, columnIndex, type); + } catch (SQLException e) { + throw DbException.convert(e); + } + } + return v; + } + + private static Value readValueOther(Session session, ResultSet rs, int columnIndex, int type) + throws SQLException { + Value v; + switch (type) { + case Value.NULL: + v = ValueNull.INSTANCE; + break; + case Value.CHAR: { + String s = rs.getString(columnIndex); + v = (s == null) ? ValueNull.INSTANCE : ValueChar.get(s); + break; + } + case Value.VARCHAR: { + String s = rs.getString(columnIndex); + v = (s == null) ? ValueNull.INSTANCE : ValueVarchar.get(s, session); + break; + } + case Value.CLOB: { + if (session == null) { + String s = rs.getString(columnIndex); + v = s == null ? ValueNull.INSTANCE : ValueClob.createSmall(s); + } else { + Reader in = rs.getCharacterStream(columnIndex); + v = in == null ? ValueNull.INSTANCE + : session.addTemporaryLob( + session.getDataHandler().getLobStorage().createClob(new BufferedReader(in), -1)); + } + break; + } + case Value.VARCHAR_IGNORECASE: { + String s = rs.getString(columnIndex); + v = s == null ? ValueNull.INSTANCE : ValueVarcharIgnoreCase.get(s); + break; + } + case Value.BINARY: { + byte[] bytes = rs.getBytes(columnIndex); + v = bytes == null ? ValueNull.INSTANCE : ValueBinary.getNoCopy(bytes); + break; + } + case Value.VARBINARY: { + byte[] bytes = rs.getBytes(columnIndex); + v = bytes == null ? ValueNull.INSTANCE : ValueVarbinary.getNoCopy(bytes); + break; + } + case Value.BLOB: { + if (session == null) { + byte[] buff = rs.getBytes(columnIndex); + v = buff == null ? ValueNull.INSTANCE : ValueBlob.createSmall(buff); + } else { + InputStream in = rs.getBinaryStream(columnIndex); + v = in == null ? ValueNull.INSTANCE + : session.addTemporaryLob(session.getDataHandler().getLobStorage().createBlob(in, -1)); + } + break; + } + case Value.BOOLEAN: { + boolean value = rs.getBoolean(columnIndex); + v = rs.wasNull() ? ValueNull.INSTANCE : ValueBoolean.get(value); + break; + } + case Value.TINYINT: { + byte value = rs.getByte(columnIndex); + v = rs.wasNull() ? ValueNull.INSTANCE : ValueTinyint.get(value); + break; + } + case Value.SMALLINT: { + short value = rs.getShort(columnIndex); + v = rs.wasNull() ? ValueNull.INSTANCE : ValueSmallint.get(value); + break; + } + case Value.INTEGER: { + int value = rs.getInt(columnIndex); + v = rs.wasNull() ? ValueNull.INSTANCE : ValueInteger.get(value); + break; + } + case Value.BIGINT: { + long value = rs.getLong(columnIndex); + v = rs.wasNull() ? ValueNull.INSTANCE : ValueBigint.get(value); + break; + } + case Value.NUMERIC: { + BigDecimal value = rs.getBigDecimal(columnIndex); + v = value == null ? ValueNull.INSTANCE : ValueNumeric.getAnyScale(value); + break; + } + case Value.REAL: { + float value = rs.getFloat(columnIndex); + v = rs.wasNull() ? ValueNull.INSTANCE : ValueReal.get(value); + break; + } + case Value.DOUBLE: { + double value = rs.getDouble(columnIndex); + v = rs.wasNull() ? ValueNull.INSTANCE : ValueDouble.get(value); + break; + } + case Value.DECFLOAT: { + BigDecimal value = rs.getBigDecimal(columnIndex); + v = value == null ? ValueNull.INSTANCE : ValueDecfloat.get(value); + break; + } + case Value.DATE: { + try { + LocalDate value = rs.getObject(columnIndex, LocalDate.class); + v = value == null ? ValueNull.INSTANCE : JSR310Utils.localDateToValue(value); + break; + } catch (SQLException ignore) { + Date value = rs.getDate(columnIndex); + v = value == null ? ValueNull.INSTANCE : LegacyDateTimeUtils.fromDate(session, null, value); + } + break; + } + case Value.TIME: { + try { + LocalTime value = rs.getObject(columnIndex, LocalTime.class); + v = value == null ? ValueNull.INSTANCE : JSR310Utils.localTimeToValue(value); + break; + } catch (SQLException ignore) { + Time value = rs.getTime(columnIndex); + v = value == null ? ValueNull.INSTANCE : LegacyDateTimeUtils.fromTime(session, null, value); + } + break; + } + case Value.TIME_TZ: { + try { + OffsetTime value = rs.getObject(columnIndex, OffsetTime.class); + v = value == null ? ValueNull.INSTANCE : JSR310Utils.offsetTimeToValue(value); + break; + } catch (SQLException ignore) { + Object obj = rs.getObject(columnIndex); + if (obj == null) { + v = ValueNull.INSTANCE; + } else { + v = ValueTimeTimeZone.parse(obj.toString()); + } + } + break; + } + case Value.TIMESTAMP: { + try { + LocalDateTime value = rs.getObject(columnIndex, LocalDateTime.class); + v = value == null ? ValueNull.INSTANCE : JSR310Utils.localDateTimeToValue(value); + break; + } catch (SQLException ignore) { + Timestamp value = rs.getTimestamp(columnIndex); + v = value == null ? ValueNull.INSTANCE : LegacyDateTimeUtils.fromTimestamp(session, null, value); + } + break; + } + case Value.TIMESTAMP_TZ: { + try { + OffsetDateTime value = rs.getObject(columnIndex, OffsetDateTime.class); + v = value == null ? ValueNull.INSTANCE : JSR310Utils.offsetDateTimeToValue(value); + break; + } catch (SQLException ignore) { + Object obj = rs.getObject(columnIndex); + if (obj == null) { + v = ValueNull.INSTANCE; + } else if (obj instanceof ZonedDateTime) { + v = JSR310Utils.zonedDateTimeToValue((ZonedDateTime) obj); + } else { + v = ValueTimestampTimeZone.parse(obj.toString(), session); + } + } + break; + } + case Value.INTERVAL_YEAR: + case Value.INTERVAL_MONTH: + case Value.INTERVAL_DAY: + case Value.INTERVAL_HOUR: + case Value.INTERVAL_MINUTE: + case Value.INTERVAL_SECOND: + case Value.INTERVAL_YEAR_TO_MONTH: + case Value.INTERVAL_DAY_TO_HOUR: + case Value.INTERVAL_DAY_TO_MINUTE: + case Value.INTERVAL_DAY_TO_SECOND: + case Value.INTERVAL_HOUR_TO_MINUTE: + case Value.INTERVAL_HOUR_TO_SECOND: + case Value.INTERVAL_MINUTE_TO_SECOND: { + String s = rs.getString(columnIndex); + v = s == null ? ValueNull.INSTANCE + : IntervalUtils.parseFormattedInterval(IntervalQualifier.valueOf(type - Value.INTERVAL_YEAR), s); + break; + } + case Value.JAVA_OBJECT: { + byte[] buff; + try { + buff = rs.getBytes(columnIndex); + } catch (SQLException ignore) { + try { + Object o = rs.getObject(columnIndex); + buff = o != null ? JdbcUtils.serialize(o, session.getJavaObjectSerializer()) : null; + } catch (Exception e) { + throw DbException.convert(e); + } + } + v = buff == null ? ValueNull.INSTANCE : ValueJavaObject.getNoCopy(buff); + break; + } + case Value.ENUM: { + int value = rs.getInt(columnIndex); + v = rs.wasNull() ? ValueNull.INSTANCE : ValueInteger.get(value); + break; + } + case Value.GEOMETRY: { + Object x = rs.getObject(columnIndex); + v = x == null ? ValueNull.INSTANCE : ValueGeometry.getFromGeometry(x); + break; + } + case Value.JSON: { + Object x = rs.getObject(columnIndex); + if (x == null) { + v = ValueNull.INSTANCE; + } else { + Class clazz = x.getClass(); + if (clazz == byte[].class) { + v = ValueJson.fromJson((byte[]) x); + } else if (clazz == String.class) { + v = ValueJson.fromJson((String) x); + } else { + v = ValueJson.fromJson(x.toString()); + } + } + break; + } + case Value.UUID: { + Object o = rs.getObject(columnIndex); + if (o == null) { + v = ValueNull.INSTANCE; + } else if (o instanceof UUID) { + v = ValueUuid.get((UUID) o); + } else if (o instanceof byte[]) { + v = ValueUuid.get((byte[]) o); + } else { + v = ValueUuid.get((String) o); + } + break; + } + case Value.ARRAY: { + Array array = rs.getArray(columnIndex); + if (array == null) { + v = ValueNull.INSTANCE; + } else { + Object[] list = (Object[]) array.getArray(); + if (list == null) { + v = ValueNull.INSTANCE; + } else { + int len = list.length; + Value[] values = new Value[len]; + for (int i = 0; i < len; i++) { + values[i] = ValueToObjectConverter.objectToValue(session, list[i], Value.NULL); + } + v = ValueArray.get(values, session); + } + } + break; + } + case Value.ROW: { + Object o = rs.getObject(columnIndex); + if (o == null) { + v = ValueNull.INSTANCE; + } else if (o instanceof ResultSet) { + v = ValueToObjectConverter.resultSetToValue(session, (ResultSet) o); + } else { + Object[] list = (Object[]) o; + int len = list.length; + Value[] values = new Value[len]; + for (int i = 0; i < len; i++) { + values[i] = ValueToObjectConverter.objectToValue(session, list[i], Value.NULL); + } + v = ValueRow.get(values); + } + break; + } + default: + throw DbException.getInternalError("data type " + type); + } + return v; + } + + private ValueToObjectConverter2() { + } + +} diff --git a/h2/src/main/org/h2/value/ValueUuid.java b/h2/src/main/org/h2/value/ValueUuid.java new file mode 100644 index 0000000..9997abe --- /dev/null +++ b/h2/src/main/org/h2/value/ValueUuid.java @@ -0,0 +1,224 @@ +/* + * 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.value; + +import java.util.UUID; + +import org.h2.api.ErrorCode; +import org.h2.engine.CastDataProvider; +import org.h2.message.DbException; +import org.h2.util.Bits; +import org.h2.util.MathUtils; +import org.h2.util.StringUtils; + +/** + * Implementation of the UUID data type. + */ +public final class ValueUuid extends Value { + + /** + * The precision of this value in number of bytes. + */ + static final int PRECISION = 16; + + /** + * The display size of the textual representation of a UUID. + * Example: cd38d882-7ada-4589-b5fb-7da0ca559d9a + */ + static final int DISPLAY_SIZE = 36; + + private final long high, low; + + private ValueUuid(long high, long low) { + this.high = high; + this.low = low; + } + + @Override + public int hashCode() { + return (int) ((high >>> 32) ^ high ^ (low >>> 32) ^ low); + } + + /** + * Create a new UUID using the pseudo random number generator. + * + * @return the new UUID + */ + public static ValueUuid getNewRandom() { + long high = MathUtils.secureRandomLong(); + long low = MathUtils.secureRandomLong(); + // version 4 (random) + high = (high & ~0xf000L) | 0x4000L; + // variant (Leach-Salz) + low = (low & 0x3fff_ffff_ffff_ffffL) | 0x8000_0000_0000_0000L; + return new ValueUuid(high, low); + } + + /** + * Get or create a UUID for the given 16 bytes. + * + * @param binary the byte array + * @return the UUID + */ + public static ValueUuid get(byte[] binary) { + int length = binary.length; + if (length != 16) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, "UUID requires 16 bytes, got " + length); + } + return get(Bits.readLong(binary, 0), Bits.readLong(binary, 8)); + } + + /** + * Get or create a UUID for the given high and low order values. + * + * @param high the most significant bits + * @param low the least significant bits + * @return the UUID + */ + public static ValueUuid get(long high, long low) { + return (ValueUuid) Value.cache(new ValueUuid(high, low)); + } + + /** + * Get or create a UUID for the given Java UUID. + * + * @param uuid Java UUID + * @return the UUID + */ + public static ValueUuid get(UUID uuid) { + return get(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits()); + } + + /** + * Get or create a UUID for the given text representation. + * + * @param s the text representation of the UUID + * @return the UUID + */ + public static ValueUuid get(String s) { + long low = 0, high = 0; + int j = 0; + for (int i = 0, length = s.length(); i < length; i++) { + char c = s.charAt(i); + if (c >= '0' && c <= '9') { + low = (low << 4) | (c - '0'); + } else if (c >= 'a' && c <= 'f') { + low = (low << 4) | (c - ('a' - 0xa)); + } else if (c == '-') { + continue; + } else if (c >= 'A' && c <= 'F') { + low = (low << 4) | (c - ('A' - 0xa)); + } else if (c <= ' ') { + continue; + } else { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, s); + } + if (++j == 16) { + high = low; + low = 0; + } + } + if (j != 32) { + throw DbException.get(ErrorCode.DATA_CONVERSION_ERROR_1, s); + } + return get(high, low); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return addString(builder.append("UUID '")).append('\''); + } + + @Override + public TypeInfo getType() { + return TypeInfo.TYPE_UUID; + } + + @Override + public int getMemory() { + return 32; + } + + @Override + public int getValueType() { + return UUID; + } + + @Override + public String getString() { + return addString(new StringBuilder(36)).toString(); + } + + @Override + public byte[] getBytes() { + return Bits.uuidToBytes(high, low); + } + + private StringBuilder addString(StringBuilder builder) { + StringUtils.appendHex(builder, high >> 32, 4).append('-'); + StringUtils.appendHex(builder, high >> 16, 2).append('-'); + StringUtils.appendHex(builder, high, 2).append('-'); + StringUtils.appendHex(builder, low >> 48, 2).append('-'); + return StringUtils.appendHex(builder, low, 6); + } + + @Override + public int compareTypeSafe(Value o, CompareMode mode, CastDataProvider provider) { + if (o == this) { + return 0; + } + ValueUuid v = (ValueUuid) o; + int cmp = Long.compareUnsigned(high, v.high); + return cmp != 0 ? cmp : Long.compareUnsigned(low, v.low); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof ValueUuid)) { + return false; + } + ValueUuid v = (ValueUuid) other; + return high == v.high && low == v.low; + } + + /** + * Returns the UUID. + * + * @return the UUID + */ + public UUID getUuid() { + return new UUID(high, low); + } + + /** + * Get the most significant 64 bits of this UUID. + * + * @return the high order bits + */ + public long getHigh() { + return high; + } + + /** + * Get the least significant 64 bits of this UUID. + * + * @return the low order bits + */ + public long getLow() { + return low; + } + + @Override + public long charLength() { + return DISPLAY_SIZE; + } + + @Override + public long octetLength() { + return PRECISION; + } + +} diff --git a/h2/src/main/org/h2/value/ValueVarbinary.java b/h2/src/main/org/h2/value/ValueVarbinary.java new file mode 100644 index 0000000..12fa6be --- /dev/null +++ b/h2/src/main/org/h2/value/ValueVarbinary.java @@ -0,0 +1,92 @@ +/* + * 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.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; + +/** + * Implementation of the BINARY VARYING data type. + */ +public final class ValueVarbinary extends ValueBytesBase { + + /** + * Empty value. + */ + public static final ValueVarbinary EMPTY = new ValueVarbinary(Utils.EMPTY_BYTES); + + /** + * Associated TypeInfo. + */ + private TypeInfo type; + + 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); + } + } + + /** + * Get or create a VARBINARY value for the given byte array. + * Clone the data. + * + * @param b the byte array + * @return the value + */ + public static ValueVarbinary get(byte[] b) { + if (b.length == 0) { + return EMPTY; + } + b = Utils.cloneByteArray(b); + return getNoCopy(b); + } + + /** + * Get or create a VARBINARY value for the given byte array. + * Do not clone the date. + * + * @param b the byte array + * @return the value + */ + public static ValueVarbinary getNoCopy(byte[] b) { + if (b.length == 0) { + return EMPTY; + } + ValueVarbinary obj = new ValueVarbinary(b); + if (b.length > SysProperties.OBJECT_CACHE_MAX_PER_ELEMENT_SIZE) { + return obj; + } + return (ValueVarbinary) Value.cache(obj); + } + + @Override + public TypeInfo getType() { + TypeInfo type = this.type; + if (type == null) { + long precision = value.length; + this.type = type = new TypeInfo(VARBINARY, precision, 0, null); + } + return type; + } + + @Override + public int getValueType() { + return VARBINARY; + } + + @Override + public String getString() { + return new String(value, StandardCharsets.UTF_8); + } + +} diff --git a/h2/src/main/org/h2/value/ValueVarchar.java b/h2/src/main/org/h2/value/ValueVarchar.java new file mode 100644 index 0000000..381dfa7 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueVarchar.java @@ -0,0 +1,67 @@ +/* + * 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.value; + +import org.h2.engine.CastDataProvider; +import org.h2.engine.SysProperties; +import org.h2.util.StringUtils; + +/** + * Implementation of the CHARACTER VARYING data type. + */ +public final class ValueVarchar extends ValueStringBase { + + /** + * Empty string. Should not be used in places where empty string can be + * treated as {@code NULL} depending on database mode. + */ + public static final ValueVarchar EMPTY = new ValueVarchar(""); + + private ValueVarchar(String value) { + super(value); + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + return StringUtils.quoteStringSQL(builder, value); + } + + @Override + public int getValueType() { + return VARCHAR; + } + + /** + * Get or create a VARCHAR value for the given string. + * + * @param s the string + * @return the value + */ + public static Value get(String s) { + return get(s, null); + } + + /** + * Get or create a VARCHAR value for the given string. + * + * @param s the string + * @param provider the cast information provider, or {@code null} + * @return the value + */ + public static Value get(String s, CastDataProvider provider) { + if (s.isEmpty()) { + return provider != null && provider.getMode().treatEmptyStringsAsNull ? ValueNull.INSTANCE : EMPTY; + } + ValueVarchar obj = new ValueVarchar(StringUtils.cache(s)); + if (s.length() > SysProperties.OBJECT_CACHE_MAX_PER_ELEMENT_SIZE) { + return obj; + } + return Value.cache(obj); + // this saves memory, but is really slow + // return new ValueString(s.intern()); + } + +} diff --git a/h2/src/main/org/h2/value/ValueVarcharIgnoreCase.java b/h2/src/main/org/h2/value/ValueVarcharIgnoreCase.java new file mode 100644 index 0000000..7b8a032 --- /dev/null +++ b/h2/src/main/org/h2/value/ValueVarcharIgnoreCase.java @@ -0,0 +1,87 @@ +/* + * 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.value; + +import org.h2.engine.CastDataProvider; +import org.h2.engine.SysProperties; +import org.h2.util.StringUtils; + +/** + * Implementation of the VARCHAR_IGNORECASE data type. + */ +public final class ValueVarcharIgnoreCase extends ValueStringBase { + + private static final ValueVarcharIgnoreCase EMPTY = new ValueVarcharIgnoreCase(""); + + /** + * The hash code. + */ + private int hash; + + private ValueVarcharIgnoreCase(String value) { + super(value); + } + + @Override + public int getValueType() { + return VARCHAR_IGNORECASE; + } + + @Override + public int compareTypeSafe(Value v, CompareMode mode, CastDataProvider provider) { + return mode.compareString(value, ((ValueStringBase) v).value, true); + } + + @Override + public boolean equals(Object other) { + return other instanceof ValueVarcharIgnoreCase + && value.equalsIgnoreCase(((ValueVarcharIgnoreCase) other).value); + } + + @Override + public int hashCode() { + if (hash == 0) { + // this is locale sensitive + hash = value.toUpperCase().hashCode(); + } + return hash; + } + + @Override + public StringBuilder getSQL(StringBuilder builder, int sqlFlags) { + if ((sqlFlags & NO_CASTS) == 0) { + return StringUtils.quoteStringSQL(builder.append("CAST("), value).append(" AS VARCHAR_IGNORECASE(") + .append(value.length()).append("))"); + } + return StringUtils.quoteStringSQL(builder, value); + } + + /** + * Get or create a VARCHAR_IGNORECASE value for the given string. + * The value will have the same case as the passed string. + * + * @param s the string + * @return the value + */ + public static ValueVarcharIgnoreCase get(String s) { + int length = s.length(); + if (length == 0) { + return EMPTY; + } + ValueVarcharIgnoreCase obj = new ValueVarcharIgnoreCase(StringUtils.cache(s)); + if (length > SysProperties.OBJECT_CACHE_MAX_PER_ELEMENT_SIZE) { + return obj; + } + ValueVarcharIgnoreCase cache = (ValueVarcharIgnoreCase) Value.cache(obj); + // the cached object could have the wrong case + // (it would still be 'equal', but we don't like to store it) + if (cache.value.equals(s)) { + return cache; + } + return obj; + } + +} diff --git a/h2/src/main/org/h2/value/VersionedValue.java b/h2/src/main/org/h2/value/VersionedValue.java new file mode 100644 index 0000000..be9aceb --- /dev/null +++ b/h2/src/main/org/h2/value/VersionedValue.java @@ -0,0 +1,36 @@ +/* + * 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.value; + +/** + * A versioned value (possibly null). + * It contains current value and latest committed value if current one is uncommitted. + * Also for uncommitted values it contains operationId - a combination of + * transactionId and logId. + */ +public class VersionedValue { + + protected VersionedValue() {} + + public boolean isCommitted() { + return true; + } + + public long getOperationId() { + return 0L; + } + + @SuppressWarnings("unchecked") + public T getCurrentValue() { + return (T)this; + } + + @SuppressWarnings("unchecked") + public T getCommittedValue() { + return (T)this; + } + +} diff --git a/h2/src/main/org/h2/value/lob/LobData.java b/h2/src/main/org/h2/value/lob/LobData.java new file mode 100644 index 0000000..c0fe51f --- /dev/null +++ b/h2/src/main/org/h2/value/lob/LobData.java @@ -0,0 +1,53 @@ +/* + * 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.value.lob; + +import java.io.InputStream; + +import org.h2.store.DataHandler; +import org.h2.value.ValueLob; + +/** + * LOB data. + */ +public abstract class LobData { + + LobData() { + } + + /** + * Get stream to read LOB data from + * @param precision octet length of the stream, or -1 if unknown + * @return stream to read LOB data from + */ + public abstract InputStream getInputStream(long precision); + + public DataHandler getDataHandler() { + return null; + } + + public boolean isLinkedToTable() { + return false; + } + + /** + * Remove the underlying resource, if any. For values that are kept fully in + * memory this method has no effect. + * @param value to remove + */ + public void remove(ValueLob value) { + } + + /** + * Get the memory used by this object. + * + * @return the memory used in bytes + */ + public int getMemory() { + return 140; + } + +} diff --git a/h2/src/main/org/h2/value/lob/LobDataDatabase.java b/h2/src/main/org/h2/value/lob/LobDataDatabase.java new file mode 100644 index 0000000..893b5eb --- /dev/null +++ b/h2/src/main/org/h2/value/lob/LobDataDatabase.java @@ -0,0 +1,85 @@ +/* + * 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.value.lob; + +import java.io.IOException; +import java.io.InputStream; + +import org.h2.message.DbException; +import org.h2.store.DataHandler; +import org.h2.value.ValueLob; + +/** + * LOB data stored in database. + */ +public final class LobDataDatabase extends LobData { + + private final DataHandler handler; + + /** + * If the LOB is managed by the one the LobStorageBackend classes, these are + * the unique key inside that storage. + */ + private final int tableId; + + private final long lobId; + + public LobDataDatabase(DataHandler handler, int tableId, long lobId) { + this.handler = handler; + this.tableId = tableId; + this.lobId = lobId; + } + + @Override + public void remove(ValueLob value) { + if (handler != null) { + handler.getLobStorage().removeLob(value); + } + } + + /** + * Check if this value is linked to a specific table. For values that are + * kept fully in memory, this method returns false. + * + * @return true if it is + */ + @Override + public boolean isLinkedToTable() { + return tableId >= 0; + } + + /** + * Get the current table id of this lob. + * + * @return the table id + */ + public int getTableId() { + return tableId; + } + + public long getLobId() { + return lobId; + } + + @Override + public InputStream getInputStream(long precision) { + try { + return handler.getLobStorage().getInputStream(lobId, tableId, precision); + } catch (IOException e) { + throw DbException.convertIOException(e, toString()); + } + } + + @Override + public DataHandler getDataHandler() { + return handler; + } + + @Override + public String toString() { + return "lob-table: table: " + tableId + " id: " + lobId; + } +} diff --git a/h2/src/main/org/h2/value/lob/LobDataFetchOnDemand.java b/h2/src/main/org/h2/value/lob/LobDataFetchOnDemand.java new file mode 100644 index 0000000..ee1bc7e --- /dev/null +++ b/h2/src/main/org/h2/value/lob/LobDataFetchOnDemand.java @@ -0,0 +1,84 @@ +/* + * 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.value.lob; + +import java.io.BufferedInputStream; +import java.io.InputStream; + +import org.h2.engine.SessionRemote; +import org.h2.store.DataHandler; +import org.h2.store.LobStorageRemoteInputStream; + +/** + * A implementation of the LOB data used on the client side of a remote H2 + * connection. Fetches the underlying on data from the server. + */ +public final class LobDataFetchOnDemand extends LobData { + + private SessionRemote handler; + + /** + * If the LOB is managed by the one the LobStorageBackend classes, these are + * the unique key inside that storage. + */ + private final int tableId; + + private final long lobId; + + /** + * If this is a client-side ValueLobDb object returned by a ResultSet, the + * hmac acts a security cookie that the client can send back to the server + * to ask for data related to this LOB. + */ + private final byte[] hmac; + + public LobDataFetchOnDemand(DataHandler handler, int tableId, long lobId, byte[] hmac) { + this.hmac = hmac; + this.handler = (SessionRemote) handler; + this.tableId = tableId; + this.lobId = lobId; + } + + /** + * Check if this value is linked to a specific table. For values that are + * kept fully in memory, this method returns false. + * + * @return true if it is + */ + @Override + public boolean isLinkedToTable() { + throw new IllegalStateException(); + } + + /** + * Get the current table id of this lob. + * + * @return the table id + */ + public int getTableId() { + return tableId; + } + + public long getLobId() { + return lobId; + } + + @Override + public InputStream getInputStream(long precision) { + return new BufferedInputStream(new LobStorageRemoteInputStream(handler, lobId, hmac)); + } + + @Override + public DataHandler getDataHandler() { + return handler; + } + + @Override + public String toString() { + return "lob-table: table: " + tableId + " id: " + lobId; + } + +} diff --git a/h2/src/main/org/h2/value/lob/LobDataFile.java b/h2/src/main/org/h2/value/lob/LobDataFile.java new file mode 100644 index 0000000..2df7b30 --- /dev/null +++ b/h2/src/main/org/h2/value/lob/LobDataFile.java @@ -0,0 +1,72 @@ +/* + * 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.value.lob; + +import java.io.BufferedInputStream; +import java.io.InputStream; + +import org.h2.engine.Constants; +import org.h2.engine.SysProperties; +import org.h2.store.DataHandler; +import org.h2.store.FileStore; +import org.h2.store.FileStoreInputStream; +import org.h2.store.fs.FileUtils; +import org.h2.value.ValueLob; + +/** + * LOB data stored in a temporary file. + */ +public final class LobDataFile extends LobData { + + private DataHandler handler; + + /** + * If the LOB is a temporary LOB being managed by a temporary ResultSet, it + * is stored in a temporary file. + */ + private final String fileName; + + private final FileStore tempFile; + + public LobDataFile(DataHandler handler, String fileName, FileStore tempFile) { + this.handler = handler; + this.fileName = fileName; + this.tempFile = tempFile; + } + + @Override + public void remove(ValueLob value) { + if (fileName != null) { + if (tempFile != null) { + tempFile.stopAutoDelete(); + } + // synchronize on the database, to avoid concurrent temp file + // creation / deletion / backup + synchronized (handler.getLobSyncObject()) { + FileUtils.delete(fileName); + } + } + } + + @Override + public InputStream getInputStream(long precision) { + FileStore store = handler.openFile(fileName, "r", true); + boolean alwaysClose = SysProperties.lobCloseBetweenReads; + return new BufferedInputStream(new FileStoreInputStream(store, false, alwaysClose), + Constants.IO_BUFFER_SIZE); + } + + @Override + public DataHandler getDataHandler() { + return handler; + } + + @Override + public String toString() { + return "lob-file: " + fileName; + } + +} diff --git a/h2/src/main/org/h2/value/lob/LobDataInMemory.java b/h2/src/main/org/h2/value/lob/LobDataInMemory.java new file mode 100644 index 0000000..896c469 --- /dev/null +++ b/h2/src/main/org/h2/value/lob/LobDataInMemory.java @@ -0,0 +1,51 @@ +/* + * 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.value.lob; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +/** + * LOB data stored in memory. + */ +public final class LobDataInMemory extends LobData { + + /** + * If the LOB is below the inline size, we just store/load it directly here. + */ + private final byte[] small; + + public LobDataInMemory(byte[] small) { + if (small == null) { + throw new IllegalStateException(); + } + this.small = small; + } + + @Override + public InputStream getInputStream(long precision) { + return new ByteArrayInputStream(small); + } + + /** + * Get the data if this a small lob value. + * + * @return the data + */ + public byte[] getSmall() { + return small; + } + + @Override + public int getMemory() { + /* + * Java 11 with -XX:-UseCompressedOops 0 bytes: 120 bytes 1 byte: 128 + * bytes + */ + return small.length + 127; + } + +} diff --git a/h2/src/main/org/h2/value/lob/package.html b/h2/src/main/org/h2/value/lob/package.html new file mode 100644 index 0000000..6a43263 --- /dev/null +++ b/h2/src/main/org/h2/value/lob/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +LOB data for values. + +

\ No newline at end of file diff --git a/h2/src/main/org/h2/value/package.html b/h2/src/main/org/h2/value/package.html new file mode 100644 index 0000000..00897ff --- /dev/null +++ b/h2/src/main/org/h2/value/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

+ +Data type and value implementations. + +

\ No newline at end of file diff --git a/h2/src/test/META-INF/services/javax.annotation.processing.Processor b/h2/src/test/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 0000000..0a9f087 --- /dev/null +++ b/h2/src/test/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +org.h2.test.ap.TestAnnotationProcessor \ No newline at end of file diff --git a/h2/src/test/org/h2/samples/CachedPreparedStatements.java b/h2/src/test/org/h2/samples/CachedPreparedStatements.java new file mode 100644 index 0000000..0b9cec6 --- /dev/null +++ b/h2/src/test/org/h2/samples/CachedPreparedStatements.java @@ -0,0 +1,60 @@ +/* + * 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.samples; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.ConcurrentHashMap; + +/** + * This sample application shows how to cache prepared statements. + */ +public class CachedPreparedStatements { + + private Connection conn; + private Statement stat; + private final ConcurrentHashMap prepared = new ConcurrentHashMap<>(); + + /** + * This method is called when executing this sample application from the + * command line. + * + * @param args the command line parameters + * @throws Exception on failure + */ + public static void main(String... args) throws Exception { + new CachedPreparedStatements().run(); + } + + private void run() throws Exception { + Class.forName("org.h2.Driver"); + conn = DriverManager.getConnection( + "jdbc:h2:mem:", "sa", ""); + stat = conn.createStatement(); + stat.execute( + "create table test(id int primary key, name varchar)"); + PreparedStatement prep = prepare( + "insert into test values(?, ?)"); + prep.setInt(1, 1); + prep.setString(2, "Hello"); + prep.execute(); + conn.close(); + } + + private PreparedStatement prepare(String sql) + throws SQLException { + PreparedStatement prep = prepared.get(sql); + if (prep == null) { + prep = conn.prepareStatement(sql); + prepared.put(sql, prep); + } + return prep; + } + +} diff --git a/h2/src/test/org/h2/samples/Compact.java b/h2/src/test/org/h2/samples/Compact.java new file mode 100644 index 0000000..6ed07a3 --- /dev/null +++ b/h2/src/test/org/h2/samples/Compact.java @@ -0,0 +1,65 @@ +/* + * 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.samples; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; + +import org.h2.store.fs.FileUtils; +import org.h2.tools.DeleteDbFiles; +import org.h2.tools.RunScript; +import org.h2.tools.Script; + +/** + * This sample application shows how to compact the database files. + * This is done by creating a SQL script, and then re-creating the database + * using this script. + */ +public class Compact { + + /** + * This method is called when executing this sample application from the + * command line. + * + * @param args the command line parameters + * @throws Exception on failure + */ + public static void main(String... args) throws Exception { + DeleteDbFiles.execute("./data", "test", true); + Class.forName("org.h2.Driver"); + Connection conn = DriverManager.getConnection("jdbc:h2:./data/test", "sa", ""); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + stat.execute("INSERT INTO TEST VALUES(1, 'Hello'), (2, 'World');"); + stat.close(); + conn.close(); + System.out.println("Compacting..."); + compact("./data", "test", "sa", ""); + System.out.println("Done."); + } + + /** + * Utility method to compact a database. + * + * @param dir the directory + * @param dbName the database name + * @param user the user name + * @param password the password + * @throws SQLException on failure + */ + public static void compact(String dir, String dbName, + String user, String password) throws SQLException { + String url = "jdbc:h2:" + dir + "/" + dbName; + String file = "data/test.sql"; + Script.process(url, user, password, file, "", ""); + DeleteDbFiles.execute(dir, dbName, true); + RunScript.execute(url, user, password, file, null, false); + FileUtils.delete(file); + } + +} diff --git a/h2/src/test/org/h2/samples/CreateScriptFile.java b/h2/src/test/org/h2/samples/CreateScriptFile.java new file mode 100644 index 0000000..daef2e6 --- /dev/null +++ b/h2/src/test/org/h2/samples/CreateScriptFile.java @@ -0,0 +1,167 @@ +/* + * 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.samples; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.DriverManager; +import org.h2.engine.Constants; +import org.h2.security.SHA256; +import org.h2.store.FileStore; +import org.h2.store.FileStoreInputStream; +import org.h2.store.FileStoreOutputStream; +import org.h2.store.fs.FileUtils; +import org.h2.tools.CompressTool; +import org.h2.tools.RunScript; +import org.h2.tools.Script; + +/** + * This sample application shows how to manually + * create an encrypted and compressed script file. + */ +public class CreateScriptFile { + + /** + * This method is called when executing this sample application from the + * command line. + * + * @param args the command line parameters + * @throws Exception on failure + */ + public static void main(String... args) throws Exception { + + String file = "test.txt"; + String cipher = "AES"; + String filePassword = "password"; + String compressionAlgorithm = "DEFLATE"; + + String url = "jdbc:h2:mem:test"; + String user = "sa", dbPassword = "sa"; + + PrintWriter w = openScriptWriter(file, + compressionAlgorithm, + cipher, filePassword, "UTF-8"); + w.println("create table test(id int primary key);"); + w.println("insert into test select x from system_range(1, 10);"); + w.close(); + + Class.forName("org.h2.Driver"); + Connection conn = DriverManager.getConnection(url, user, dbPassword); + RunScript.main( + "-url", url, + "-user", user, "-password", dbPassword, + "-script", file, + "-options", + "compression", compressionAlgorithm, + "cipher", cipher, + "password", "'" + filePassword + "'" + ); + Script.main( + "-url", url, + "-user", user, "-password", dbPassword, + "-script", file, + "-options", + "compression", compressionAlgorithm, + "cipher", cipher, "password", "'" + filePassword + "'" + ); + conn.close(); + + LineNumberReader r = openScriptReader(file, + compressionAlgorithm, + cipher, filePassword, "UTF-8"); + while (true) { + String line = r.readLine(); + if (line == null) { + break; + } + System.out.println(line); + } + r.close(); + + } + + /** + * Open a script writer. + * + * @param fileName the file name (the file will be overwritten) + * @param compressionAlgorithm the compression algorithm (uppercase) + * @param cipher the encryption algorithm or null + * @param password the encryption password + * @param charset the character set (for example UTF-8) + * @return the print writer + * @throws IOException on failure + */ + public static PrintWriter openScriptWriter(String fileName, + String compressionAlgorithm, + String cipher, String password, + String charset) throws IOException { + try { + OutputStream out; + if (cipher != null) { + byte[] key = SHA256.getKeyPasswordHash("script", password.toCharArray()); + FileUtils.delete(fileName); + FileStore store = FileStore.open(null, fileName, "rw", cipher, key); + store.init(); + out = new FileStoreOutputStream(store, compressionAlgorithm); + out = new BufferedOutputStream(out, Constants.IO_BUFFER_SIZE_COMPRESS); + } else { + out = FileUtils.newOutputStream(fileName, false); + out = new BufferedOutputStream(out, Constants.IO_BUFFER_SIZE); + out = CompressTool.wrapOutputStream(out, + compressionAlgorithm, "script.sql"); + } + return new PrintWriter(new OutputStreamWriter(out, charset)); + } catch (Exception e) { + throw new IOException(e.getMessage(), e); + } + } + + /** + * Open a script reader. + * + * @param fileName the file name (the file will be overwritten) + * @param compressionAlgorithm the compression algorithm (uppercase) + * @param cipher the encryption algorithm or null + * @param password the encryption password + * @param charset the character set (for example UTF-8) + * @return the script reader + * @throws IOException on failure + */ + public static LineNumberReader openScriptReader(String fileName, + String compressionAlgorithm, + String cipher, String password, + String charset) throws IOException { + try { + InputStream in; + if (cipher != null) { + byte[] key = SHA256.getKeyPasswordHash("script", password.toCharArray()); + FileStore store = FileStore.open(null, fileName, "rw", cipher, key); + store.init(); + in = new FileStoreInputStream(store, compressionAlgorithm != null, false); + in = new BufferedInputStream(in, Constants.IO_BUFFER_SIZE_COMPRESS); + } else { + in = FileUtils.newInputStream(fileName); + in = new BufferedInputStream(in, Constants.IO_BUFFER_SIZE); + in = CompressTool.wrapInputStream(in, compressionAlgorithm, "script.sql"); + if (in == null) { + throw new IOException("Entry not found: script.sql in " + fileName); + } + } + return new LineNumberReader(new InputStreamReader(in, charset)); + } catch (Exception e) { + throw new IOException(e.getMessage(), e); + } + } + +} diff --git a/h2/src/test/org/h2/samples/CsvSample.java b/h2/src/test/org/h2/samples/CsvSample.java new file mode 100644 index 0000000..2b73041 --- /dev/null +++ b/h2/src/test/org/h2/samples/CsvSample.java @@ -0,0 +1,68 @@ +/* + * 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.samples; + +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Types; + +import org.h2.store.fs.FileUtils; +import org.h2.tools.Csv; +import org.h2.tools.SimpleResultSet; + +/** + * This sample application shows how to use the CSV tool + * to write CSV (comma separated values) files, and + * how to use the tool to read such files. + * See also the section CSV (Comma Separated Values) Support in the Tutorial. + */ +public class CsvSample { + + /** + * This method is called when executing this sample application from the + * command line. + * + * @param args the command line parameters + * @throws SQLException on failure + */ + public static void main(String... args) throws SQLException { + CsvSample.write(); + CsvSample.read(); + FileUtils.delete("data/test.csv"); + } + + /** + * Write a CSV file. + */ + static void write() throws SQLException { + SimpleResultSet rs = new SimpleResultSet(); + rs.addColumn("NAME", Types.VARCHAR, 255, 0); + rs.addColumn("EMAIL", Types.VARCHAR, 255, 0); + rs.addColumn("PHONE", Types.VARCHAR, 255, 0); + rs.addRow("Bob Meier", "bob.meier@abcde.abc", "+41123456789"); + rs.addRow("John Jones", "john.jones@abcde.abc", "+41976543210"); + new Csv().write("data/test.csv", rs, null); + } + + /** + * Read a CSV file. + */ + static void read() throws SQLException { + ResultSet rs = new Csv().read("data/test.csv", null, null); + ResultSetMetaData meta = rs.getMetaData(); + while (rs.next()) { + for (int i = 0; i < meta.getColumnCount(); i++) { + System.out.println( + meta.getColumnLabel(i + 1) + ": " + + rs.getString(i + 1)); + } + System.out.println(); + } + rs.close(); + } + +} diff --git a/h2/src/test/org/h2/samples/DirectInsert.java b/h2/src/test/org/h2/samples/DirectInsert.java new file mode 100644 index 0000000..825356a --- /dev/null +++ b/h2/src/test/org/h2/samples/DirectInsert.java @@ -0,0 +1,81 @@ +/* + * 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.samples; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.TimeUnit; + +import org.h2.tools.DeleteDbFiles; + +/** + * Demonstrates the benefit of using the CREATE TABLE ... AS SELECT + * optimization. + */ +public class DirectInsert { + + /** + * This method is called when executing this sample application from the + * command line. + * + * @param args the command line parameters + * @throws Exception on failure + */ + public static void main(String... args) throws Exception { + Class.forName("org.h2.Driver"); + DeleteDbFiles.execute("~", "test", true); + String url = "jdbc:h2:~/test"; + initialInsert(url, 200_000); + for (int i = 0; i < 3; i++) { + createAsSelect(url, true); + createAsSelect(url, false); + } + } + + private static void initialInsert(String url, int len) throws SQLException { + Connection conn = DriverManager.getConnection(url + ";LOG=0"); + Statement stat = conn.createStatement(); + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST VALUES(?, 'Test' || SPACE(100))"); + long time = System.nanoTime(); + for (int i = 0; i < len; i++) { + long now = System.nanoTime(); + if (now > time + TimeUnit.SECONDS.toNanos(1)) { + time = now; + System.out.println("Inserting " + (100L * i / len) + "%"); + } + prep.setInt(1, i); + prep.execute(); + } + conn.commit(); + prep.close(); + stat.close(); + conn.close(); + } + + private static void createAsSelect(String url, boolean optimize) + throws SQLException { + Connection conn = DriverManager.getConnection(url + + ";OPTIMIZE_INSERT_FROM_SELECT=" + optimize); + Statement stat = conn.createStatement(); + stat.execute("DROP TABLE IF EXISTS TEST2"); + System.out.println("CREATE TABLE ... AS SELECT " + + (optimize ? "(optimized)" : "")); + long time = System.nanoTime(); + stat.execute("CREATE TABLE TEST2 AS SELECT * FROM TEST"); + System.out.printf("%.3f sec.\n", (double) (System.nanoTime() - time) / + TimeUnit.SECONDS.toNanos(1)); + stat.execute("INSERT INTO TEST2 SELECT * FROM TEST2"); + stat.close(); + conn.close(); + } + +} diff --git a/h2/src/test/org/h2/samples/FileFunctions.java b/h2/src/test/org/h2/samples/FileFunctions.java new file mode 100644 index 0000000..985a313 --- /dev/null +++ b/h2/src/test/org/h2/samples/FileFunctions.java @@ -0,0 +1,91 @@ +/* + * 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.samples; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.Statement; + +/** + * This sample application shows how to create a user defined function + * to read a file from the file system. + */ +public class FileFunctions { + + /** + * This method is called when executing this sample application from the + * command line. + * + * @param args the command line parameters + * @throws Exception on failure + */ + public static void main(String... args) throws Exception { + Class.forName("org.h2.Driver"); + Connection conn = DriverManager.getConnection("jdbc:h2:mem:", "sa", ""); + Statement stat = conn.createStatement(); + stat.execute("CREATE ALIAS READ_TEXT_FILE FOR 'org.h2.samples.FileFunctions.readTextFile'"); + stat.execute("CREATE ALIAS READ_TEXT_FILE_WITH_ENCODING " + + "FOR 'org.h2.samples.FileFunctions.readTextFileWithEncoding'"); + stat.execute("CREATE ALIAS READ_FILE FOR 'org.h2.samples.FileFunctions.readFile'"); + ResultSet rs = stat.executeQuery("CALL READ_FILE('test.txt')"); + rs.next(); + byte[] data = rs.getBytes(1); + System.out.println("length: " + data.length); + rs = stat.executeQuery("CALL READ_TEXT_FILE('test.txt')"); + rs.next(); + String text = rs.getString(1); + System.out.println("text: " + text); + stat.close(); + conn.close(); + } + + /** + * Read a String from a file. The default encoding for this platform is + * used. + * + * @param fileName the file name + * @return the text + * @throws IOException on failure + */ + public static String readTextFile(String fileName) throws IOException { + byte[] buff = readFile(fileName); + String s = new String(buff); + return s; + } + + /** + * Read a String from a file using the specified encoding. + * + * @param fileName the file name + * @param encoding the encoding + * @return the text + * @throws IOException on failure + */ + public static String readTextFileWithEncoding(String fileName, + String encoding) throws IOException { + byte[] buff = readFile(fileName); + String s = new String(buff, encoding); + return s; + } + + /** + * Read a file into a byte array. + * + * @param fileName the file name + * @return the byte array + * @throws IOException on failure + */ + public static byte[] readFile(String fileName) throws IOException { + try (RandomAccessFile file = new RandomAccessFile(fileName, "r")) { + byte[] buff = new byte[(int) file.length()]; + file.readFully(buff); + return buff; + } + } +} diff --git a/h2/src/test/org/h2/samples/Function.java b/h2/src/test/org/h2/samples/Function.java new file mode 100644 index 0000000..cf084b6 --- /dev/null +++ b/h2/src/test/org/h2/samples/Function.java @@ -0,0 +1,157 @@ +/* + * 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.samples; + +import java.math.BigInteger; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import org.h2.tools.SimpleResultSet; + +/** + * This sample application shows how to define and use + * custom (user defined) functions in this database. + */ +public class Function { + + /** + * This method is called when executing this sample application from the + * command line. + * + * @param args the command line parameters + * @throws Exception on failure + */ + public static void main(String... args) throws Exception { + Class.forName("org.h2.Driver"); + Connection conn = DriverManager.getConnection( + "jdbc:h2:mem:", "sa", ""); + Statement stat = conn.createStatement(); + + // Using a custom Java function + stat.execute("CREATE ALIAS IS_PRIME FOR 'org.h2.samples.Function.isPrime'"); + ResultSet rs; + rs = stat.executeQuery("SELECT IS_PRIME(X), X " + + "FROM SYSTEM_RANGE(1, 20) ORDER BY X"); + while (rs.next()) { + boolean isPrime = rs.getBoolean(1); + if (isPrime) { + int x = rs.getInt(2); + System.out.println(x + " is prime"); + } + } + + // Calling the built-in 'table' function + stat.execute("CREATE TABLE TEST(ID INT) AS " + + "SELECT X FROM SYSTEM_RANGE(1, 100)"); + PreparedStatement prep; + prep = conn.prepareStatement( + "SELECT * FROM TABLE(X INT=?, O INT=?) J " + + "INNER JOIN TEST T ON J.X=T.ID ORDER BY J.O"); + prep.setObject(1, new Integer[] { 30, 20 }); + prep.setObject(2, new Integer[] { 1, 2 }); + rs = prep.executeQuery(); + while (rs.next()) { + System.out.println(rs.getInt(1)); + } + prep.close(); + rs.close(); + + // Using a custom function like table + stat.execute("CREATE ALIAS MATRIX FOR 'org.h2.samples.Function.getMatrix'"); + prep = conn.prepareStatement("SELECT * FROM MATRIX(?) " + + "ORDER BY X, Y"); + prep.setInt(1, 2); + rs = prep.executeQuery(); + while (rs.next()) { + System.out.println(rs.getInt(1) + "/" + rs.getInt(2)); + } + prep.close(); + + // Creating functions with source code + // in this case the JDK classes must be in the classpath + // where the database is running + stat.execute("create alias make_point as $$ " + + "java.awt.Point newPoint(int x, int y) { " + + "return new java.awt.Point(x, y); } $$"); + // parameters of type OTHER (or OBJECT or JAVA_OBJECT) + // are de-serialized to match the type + stat.execute("create alias get_x as $$ " + + "int pointX(java.awt.geom.Point2D p) { " + + "return (int) p.getX(); } $$"); + rs = stat.executeQuery("call get_x(make_point(10, 20))"); + while (rs.next()) { + System.out.println(rs.getString(1)); + } + + stat.close(); + conn.close(); + } + + /** + * Check if a value is a prime number. + * + * @param value the value + * @return true if it is a prime number + */ + public static boolean isPrime(int value) { + return BigInteger.valueOf(value).isProbablePrime(100); + } + + /** + * Execute a query. + * + * @param conn the connection + * @param sql the SQL statement + * @return the result set + * @throws SQLException on failure + */ + public static ResultSet query(Connection conn, String sql) throws SQLException { + return conn.createStatement().executeQuery(sql); + } + + /** + * Creates a simple result set with one row. + * + * @return the result set + */ + public static ResultSet simpleResultSet() { + SimpleResultSet rs = new SimpleResultSet(); + rs.addColumn("ID", Types.INTEGER, 10, 0); + rs.addColumn("NAME", Types.VARCHAR, 255, 0); + rs.addRow(0, "Hello"); + return rs; + } + + /** + * Creates a simple result set with two columns. + * + * @param conn the connection + * @param size the number of x and y values + * @return the result set with two columns + * @throws SQLException on failure + */ + public static ResultSet getMatrix(Connection conn, Integer size) + throws SQLException { + SimpleResultSet rs = new SimpleResultSet(); + rs.addColumn("X", Types.INTEGER, 10, 0); + rs.addColumn("Y", Types.INTEGER, 10, 0); + String url = conn.getMetaData().getURL(); + if (url.equals("jdbc:columnlist:connection")) { + return rs; + } + for (int s = size, x = 0; x < s; x++) { + for (int y = 0; y < s; y++) { + rs.addRow(x, y); + } + } + return rs; + } + +} diff --git a/h2/src/test/org/h2/samples/FunctionMultiReturn.java b/h2/src/test/org/h2/samples/FunctionMultiReturn.java new file mode 100644 index 0000000..8b1c60e --- /dev/null +++ b/h2/src/test/org/h2/samples/FunctionMultiReturn.java @@ -0,0 +1,160 @@ +/* + * 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.samples; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; + +import org.h2.tools.SimpleResultSet; + +/** + * User defined functions can return a result set, + * and can therefore be used like a table. + * This sample application uses such a function to convert + * polar to cartesian coordinates. + */ +public class FunctionMultiReturn { + + /** + * This method is called when executing this sample application from the + * command line. + * + * @param args the command line parameters + * @throws Exception on failure + */ + public static void main(String... args) throws Exception { + Class.forName("org.h2.Driver"); + Connection conn = DriverManager.getConnection( + "jdbc:h2:mem:", "sa", ""); + Statement stat = conn.createStatement(); + stat.execute("CREATE ALIAS P2C FOR 'org.h2.samples.FunctionMultiReturn.polar2Cartesian'"); + PreparedStatement prep = conn.prepareStatement( + "SELECT X, Y FROM P2C(?, ?)"); + prep.setDouble(1, 5.0); + prep.setDouble(2, 0.5); + ResultSet rs = prep.executeQuery(); + while (rs.next()) { + double x = rs.getDouble(1); + double y = rs.getDouble(2); + System.out.println("result: (x=" + x + ", y="+y+")"); + } + + stat.execute("CREATE TABLE TEST(ID IDENTITY, R DOUBLE, A DOUBLE)"); + stat.execute("INSERT INTO TEST(R, A) VALUES(5.0, 0.5), (10.0, 0.6)"); + stat.execute("CREATE ALIAS P2C_SET FOR 'org.h2.samples.FunctionMultiReturn.polar2CartesianSet'"); + rs = conn.createStatement().executeQuery( + "SELECT * FROM P2C_SET('SELECT * FROM TEST')"); + while (rs.next()) { + double r = rs.getDouble("R"); + double a = rs.getDouble("A"); + double x = rs.getDouble("X"); + double y = rs.getDouble("Y"); + System.out.println("(r="+r+" a="+a+") :" + + " (x=" + x + ", y="+y+")"); + } + + stat.execute("CREATE ALIAS P2C_A FOR 'org.h2.samples.FunctionMultiReturn.polar2CartesianArray'"); + rs = conn.createStatement().executeQuery( + "SELECT R, A, P2C_A(R, A) FROM TEST"); + while (rs.next()) { + double r = rs.getDouble(1); + double a = rs.getDouble(2); + 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 + ")"); + } + + rs = stat.executeQuery( + "SELECT R, A, ARRAY_GET(E, 1), ARRAY_GET(E, 2) " + + "FROM (SELECT R, A, P2C_A(R, A) E FROM TEST)"); + while (rs.next()) { + double r = rs.getDouble(1); + double a = rs.getDouble(2); + double x = rs.getDouble(3); + double y = rs.getDouble(4); + System.out.println("(r="+r+" a="+a+") :" + + " (x=" + x + ", y="+y+")"); + } + rs.close(); + + prep.close(); + conn.close(); + } + + /** + * Convert polar coordinates to cartesian coordinates. The function may be + * called twice, once to retrieve the result columns (with null parameters), + * and the second time to return the data. + * + * @param r the distance from the point 0/0 + * @param alpha the angle + * @return a result set with two columns: x and y + */ + public static ResultSet polar2Cartesian(Double r, Double alpha) { + SimpleResultSet rs = new SimpleResultSet(); + rs.addColumn("X", Types.DOUBLE, 0, 0); + rs.addColumn("Y", Types.DOUBLE, 0, 0); + if (r != null && alpha != null) { + double x = r * Math.cos(alpha); + double y = r * Math.sin(alpha); + rs.addRow(x, y); + } + return rs; + } + + /** + * Convert polar coordinates to cartesian coordinates. The function may be + * called twice, once to retrieve the result columns (with null parameters), + * and the second time to return the data. + * + * @param r the distance from the point 0/0 + * @param alpha the angle + * @return an array two values: x and y + */ + public static Double[] polar2CartesianArray(Double r, Double alpha) { + double x = r * Math.cos(alpha); + double y = r * Math.sin(alpha); + return new Double[]{x, y}; + } + + /** + * Convert a set of polar coordinates to cartesian coordinates. The function + * may be called twice, once to retrieve the result columns (with null + * parameters), and the second time to return the data. + * + * @param conn the connection + * @param query the query + * @return a result set with the coordinates + * @throws SQLException on failure + */ + public static ResultSet polar2CartesianSet(Connection conn, String query) + throws SQLException { + SimpleResultSet result = new SimpleResultSet(); + result.addColumn("R", Types.DOUBLE, 0, 0); + result.addColumn("A", Types.DOUBLE, 0, 0); + result.addColumn("X", Types.DOUBLE, 0, 0); + result.addColumn("Y", Types.DOUBLE, 0, 0); + if (query != null) { + ResultSet rs = conn.createStatement().executeQuery(query); + while (rs.next()) { + double r = rs.getDouble("R"); + double alpha = rs.getDouble("A"); + double x = r * Math.cos(alpha); + double y = r * Math.sin(alpha); + result.addRow(r, alpha, x, y); + } + } + return result; + } + +} diff --git a/h2/src/test/org/h2/samples/HelloWorld.java b/h2/src/test/org/h2/samples/HelloWorld.java new file mode 100644 index 0000000..8353d2b --- /dev/null +++ b/h2/src/test/org/h2/samples/HelloWorld.java @@ -0,0 +1,49 @@ +/* + * 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.samples; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.Statement; +import org.h2.tools.DeleteDbFiles; + +/** + * A very simple class that shows how to load the driver, create a database, + * create a table, and insert some data. + */ +public class HelloWorld { + + /** + * Called when ran from command line. + * + * @param args ignored + * @throws Exception on failure + */ + public static void main(String... args) throws Exception { + // delete the database named 'test' in the user home directory + DeleteDbFiles.execute("~", "test", true); + + Class.forName("org.h2.Driver"); + Connection conn = DriverManager.getConnection("jdbc:h2:~/test"); + Statement stat = conn.createStatement(); + + // this line would initialize the database + // from the SQL script file 'init.sql' + // stat.execute("runscript from 'init.sql'"); + + stat.execute("create table test(id int primary key, name varchar(255))"); + stat.execute("insert into test values(1, 'Hello')"); + ResultSet rs; + rs = stat.executeQuery("select * from test"); + while (rs.next()) { + System.out.println(rs.getString("name")); + } + stat.close(); + conn.close(); + } + +} diff --git a/h2/src/test/org/h2/samples/InitDatabaseFromJar.java b/h2/src/test/org/h2/samples/InitDatabaseFromJar.java new file mode 100644 index 0000000..dccf2aa --- /dev/null +++ b/h2/src/test/org/h2/samples/InitDatabaseFromJar.java @@ -0,0 +1,71 @@ +/* + * 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.samples; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.Statement; + +import org.h2.tools.RunScript; + +/** + * In this example a database is initialized from compressed script in a jar + * file. + */ +public class InitDatabaseFromJar { + + /** + * This method is called when executing this sample application from the + * command line. + * + * @param args the command line parameters + * @throws Exception on failure + */ + public static void main(String... args) throws Exception { + createScript(); + new InitDatabaseFromJar().initDb(); + } + + /** + * Create a script from a new database. + */ + private static void createScript() throws Exception { + Class.forName("org.h2.Driver"); + Connection conn = DriverManager.getConnection("jdbc:h2:mem:test"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(NAME VARCHAR)"); + stat.execute("INSERT INTO TEST VALUES('Hello World')"); + stat.execute("SCRIPT TO 'script.sql'"); + stat.close(); + conn.close(); + } + + /** + * Initialize a database from a SQL script file. + */ + void initDb() throws Exception { + Class.forName("org.h2.Driver"); + InputStream in = getClass().getResourceAsStream("script.sql"); + if (in == null) { + System.out.println("Please add the file script.sql to the classpath, package " + + getClass().getPackage().getName()); + } else { + Connection conn = DriverManager.getConnection("jdbc:h2:mem:test"); + RunScript.execute(conn, new InputStreamReader(in)); + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("SELECT * FROM TEST"); + while (rs.next()) { + System.out.println(rs.getString(1)); + } + rs.close(); + stat.close(); + conn.close(); + } + } +} diff --git a/h2/src/test/org/h2/samples/MixedMode.java b/h2/src/test/org/h2/samples/MixedMode.java new file mode 100644 index 0000000..a960191 --- /dev/null +++ b/h2/src/test/org/h2/samples/MixedMode.java @@ -0,0 +1,65 @@ +/* + * 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.samples; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import org.h2.tools.Server; + +/** + * This sample program opens the same database once in embedded mode, + * and once in the server mode. The embedded mode is faster, but only + * the server mode supports remote connections. + */ +public class MixedMode { + + /** + * This method is called when executing this sample application from the + * command line. + * + * @param args the command line parameters + * @throws Exception on failure + */ + public static void main(String... args) throws Exception { + + // start the server, allows to access the database remotely + Server server = Server.createTcpServer("-tcpPort", "9081"); + server.start(); + System.out.println( + "You can access the database remotely now, using the URL:"); + System.out.println( + "jdbc:h2:tcp://localhost:9081/~/test (user: sa, password: sa)"); + + // now use the database in your application in embedded mode + Class.forName("org.h2.Driver"); + Connection conn = DriverManager.getConnection( + "jdbc:h2:~/test", "sa", "sa"); + + // some simple 'business usage' + Statement stat = conn.createStatement(); + stat.execute("DROP TABLE TIMER IF EXISTS"); + stat.execute("CREATE TABLE TIMER(ID INT PRIMARY KEY, TIME VARCHAR)"); + System.out.println("Execute this a few times: " + + "SELECT TIME FROM TIMER"); + System.out.println("To stop this application " + + "(and the server), run: DROP TABLE TIMER"); + try { + while (true) { + // runs forever, except if you drop the table remotely + stat.execute("MERGE INTO TIMER VALUES(1, LOCALTIME)"); + Thread.sleep(1000); + } + } catch (SQLException e) { + System.out.println("Error: " + e.toString()); + } + conn.close(); + + // stop the server + server.stop(); + } +} diff --git a/h2/src/test/org/h2/samples/Newsfeed.java b/h2/src/test/org/h2/samples/Newsfeed.java new file mode 100644 index 0000000..b7602cf --- /dev/null +++ b/h2/src/test/org/h2/samples/Newsfeed.java @@ -0,0 +1,79 @@ +/* + * 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.samples; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; + +import org.h2.tools.RunScript; +import org.h2.util.StringUtils; + +/** + * The newsfeed application uses XML functions to create an RSS and Atom feed + * from a simple SQL script. A textual representation of the data is created as + * well. + */ +public class Newsfeed { + + /** + * This method is called when executing this sample application from the + * command line. + * + * @param args the command line parameters + * @throws Exception on failure + */ + public static void main(String... args) throws Exception { + Path targetDir = Paths.get(args.length == 0 ? "." : args[0]); + Class.forName("org.h2.Driver"); + Connection conn = DriverManager.getConnection("jdbc:h2:mem:", "sa", ""); + InputStream in = Newsfeed.class.getResourceAsStream("newsfeed.sql"); + ResultSet rs = RunScript.execute(conn, new InputStreamReader(in, StandardCharsets.ISO_8859_1)); + in.close(); + while (rs.next()) { + String file = rs.getString("FILE"); + String content = rs.getString("CONTENT"); + if (file.endsWith(".txt")) { + content = convertHtml2Text(content); + } + Files.createDirectories(targetDir); + Files.write(targetDir.resolve(file), content.getBytes(StandardCharsets.UTF_8)); + } + conn.close(); + } + + /** + * Convert HTML text to plain text. + * + * @param html the html text + * @return the plain text + */ + private static String convertHtml2Text(String html) { + String s = html; + s = StringUtils.replaceAll(s, "", ""); + s = StringUtils.replaceAll(s, "", ""); + s = StringUtils.replaceAll(s, "
    ", ""); + s = StringUtils.replaceAll(s, "
", ""); + s = StringUtils.replaceAll(s, "
  • ", "- "); + s = StringUtils.replaceAll(s, "
  • ", ""); + s = StringUtils.replaceAll(s, "", " ) "); + s = StringUtils.replaceAll(s, "", ""); + s = StringUtils.replaceAll(s, "
    ", ""); + s = StringUtils.replaceAll(s, "
    ", ""); + s = StringUtils.replaceAll(s, "
    ", ""); + if (s.indexOf('<') >= 0 || s.indexOf('>') >= 0) { + throw new RuntimeException("Unsupported HTML Tag: < or > in " + s); + } + return s; + } +} diff --git a/h2/src/test/org/h2/samples/ReadOnlyDatabaseInZip.java b/h2/src/test/org/h2/samples/ReadOnlyDatabaseInZip.java new file mode 100644 index 0000000..04b9a64 --- /dev/null +++ b/h2/src/test/org/h2/samples/ReadOnlyDatabaseInZip.java @@ -0,0 +1,70 @@ +/* + * 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.samples; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.Statement; +import org.h2.store.fs.FileUtils; +import org.h2.tools.Backup; +import org.h2.tools.DeleteDbFiles; + +/** + * This sample application shows how to create and use a read-only database in a + * zip file. The database file is split into multiple smaller files, to speed up + * random-access. Splitting up the file is only needed if the database file is + * larger than a few megabytes. + */ +public class ReadOnlyDatabaseInZip { + + /** + * This method is called when executing this sample application from the + * command line. + * + * @param args the command line parameters + * @throws Exception on failure + */ + public static void main(String... args) throws Exception { + + // delete all files in this directory + FileUtils.deleteRecursive("~/temp", false); + + Connection conn; + Class.forName("org.h2.Driver"); + + // create a database where the database file is split into + // multiple small files, 4 MB each (2^22). The larger the + // parts, the faster opening the database, but also the + // more files. 4 MB seems to be a good compromise, so + // the prefix split:22: is used, which means each part is + // 2^22 bytes long + conn = DriverManager.getConnection( + "jdbc:h2:split:22:~/temp/test"); + + System.out.println("adding test data..."); + Statement stat = conn.createStatement(); + stat.execute( + "create table test(id int primary key, name varchar) " + + "as select x, space(1000) from system_range(1, 2000)"); + + System.out.println("defrag to reduce random access..."); + stat.execute("shutdown defrag"); + conn.close(); + + System.out.println("create the zip file..."); + Backup.execute("~/temp/test.zip", "~/temp", "", true); + + // delete the old database files + DeleteDbFiles.execute("split:~/temp", "test", true); + + System.out.println("open the database from the zip file..."); + conn = DriverManager.getConnection( + "jdbc:h2:split:zip:~/temp/test.zip!/test"); + // the database can now be used + conn.close(); + } + +} diff --git a/h2/src/test/org/h2/samples/RowAccessRights.java b/h2/src/test/org/h2/samples/RowAccessRights.java new file mode 100644 index 0000000..dc75457 --- /dev/null +++ b/h2/src/test/org/h2/samples/RowAccessRights.java @@ -0,0 +1,124 @@ +/* + * 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.samples; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import org.h2.tools.DeleteDbFiles; +import org.h2.tools.TriggerAdapter; + +/** + * This sample application show how to emulate per-row access rights so that + * each user can only access rows created by the given user. + */ +public class RowAccessRights extends TriggerAdapter { + + private PreparedStatement prepDelete, prepInsert; + + /** + * Called when ran from command line. + * + * @param args ignored + * @throws Exception on failure + */ + public static void main(String... args) throws Exception { + DeleteDbFiles.execute("~", "test", true); + + Class.forName("org.h2.Driver"); + Connection conn = DriverManager.getConnection( + "jdbc:h2:~/test"); + Statement stat = conn.createStatement(); + + stat.execute("create table test_data(" + + "id int, `user` varchar, data varchar, primary key(id, `user`))"); + stat.execute("create index on test_data(id, `user`)"); + + stat.execute("create view test as select id, data " + + "from test_data where `user` = user"); + stat.execute("create trigger t_test instead of " + + "insert, update, delete on test for each row " + + "call \"" + RowAccessRights.class.getName() + "\""); + stat.execute("create user a password 'a'"); + stat.execute("create user b password 'b'"); + stat.execute("grant all on test to a"); + stat.execute("grant all on test to b"); + + ResultSet rs; + + Connection connA = DriverManager.getConnection( + "jdbc:h2:~/test", "a", "a"); + Statement statA = connA.createStatement(); + statA.execute("insert into test values(1, 'Hello'), (2, 'World')"); + statA.execute("update test set data = 'Hello!' where id = 1"); + statA.execute("delete from test where id = 2"); + + Connection connB = DriverManager.getConnection( + "jdbc:h2:~/test", "b", "b"); + Statement statB = connB.createStatement(); + statB.execute("insert into test values(1, 'Hallo'), (2, 'Welt')"); + statB.execute("update test set data = 'Hallo!' where id = 1"); + statB.execute("delete from test where id = 2"); + + rs = statA.executeQuery("select * from test"); + while (rs.next()) { + System.out.println("a: " + rs.getInt(1) + "/" + rs.getString(2)); + } + + rs = statB.executeQuery("select * from test"); + while (rs.next()) { + System.out.println("b: " + + rs.getInt(1) + "/" + rs.getString(2)); + } + + connA.close(); + connB.close(); + + rs = stat.executeQuery("select * from test_data"); + while (rs.next()) { + System.out.println(rs.getInt(1) + "/" + + rs.getString(2) + "/" + rs.getString(3)); + } + conn.close(); + + } + + @Override + public void init(Connection conn, String schemaName, String triggerName, + String tableName, boolean before, int type) throws SQLException { + prepDelete = conn.prepareStatement( + "delete from test_data where id = ? and `user` = ?"); + prepInsert = conn.prepareStatement( + "insert into test_data values(?, ?, ?)"); + super.init(conn, schemaName, triggerName, tableName, before, type); + } + + @Override + public void fire(Connection conn, ResultSet oldRow, ResultSet newRow) + throws SQLException { + String user = conn.getMetaData().getUserName(); + if (oldRow != null && oldRow.next()) { + prepDelete.setInt(1, oldRow.getInt(1)); + prepDelete.setString(2, user); + int deleted = prepDelete.executeUpdate(); + if (deleted == 0 && newRow != null) { + // update: + // if deleting failed, insert must fail as well + newRow = null; + } + } + if (newRow != null && newRow.next()) { + prepInsert.setInt(1, newRow.getInt(1)); + prepInsert.setString(2, user); + prepInsert.setString(3, newRow.getString(2)); + prepInsert.executeUpdate(); + } + } + +} diff --git a/h2/src/test/org/h2/samples/SQLInjection.java b/h2/src/test/org/h2/samples/SQLInjection.java new file mode 100644 index 0000000..6ad81ec --- /dev/null +++ b/h2/src/test/org/h2/samples/SQLInjection.java @@ -0,0 +1,434 @@ +/* + * 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.samples; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * SQL Injection is a common security vulnerability for applications that use + * database. It is one of the most common security vulnerabilities for web + * applications today. This sample application shows how SQL injection works, + * and how to protect the application from it. + */ +public class SQLInjection { + + private Connection conn; + private Statement stat; + + /** + * This method is called when executing this sample application from the + * command line. + * + * @param args the command line parameters + * @throws Exception on failure + */ + public static void main(String... args) throws Exception { + new SQLInjection().run("org.h2.Driver", + "jdbc:h2:./test", "sa", "sa"); +// new SQLInjection().run("org.postgresql.Driver", +// "jdbc:postgresql:jpox2", "sa", "sa"); +// new SQLInjection().run("com.mysql.cj.jdbc.Driver", +// "jdbc:mysql://localhost/test", "sa", "sa"); +// new SQLInjection().run("org.hsqldb.jdbcDriver", +// "jdbc:hsqldb:test", "sa", ""); +// new SQLInjection().run( +// "org.apache.derby.iapi.jdbc.AutoloadedDriver", +// "jdbc:derby:test3;create=true", "sa", "sa"); + } + + /** + * Run the test against the specified database. + * + * @param driver the JDBC driver name + * @param url the database URL + * @param user the user name + * @param password the password + */ + void run(String driver, String url, String user, String password) + throws Exception { + Class.forName(driver); + conn = DriverManager.getConnection(url, user, password); + stat = conn.createStatement(); + try { + stat.execute("DROP TABLE USERS"); + } catch (SQLException e) { + // ignore + } + stat.execute("CREATE TABLE USERS(ID INT PRIMARY KEY, " + + "NAME VARCHAR(255), PASSWORD VARCHAR(255))"); + stat.execute("INSERT INTO USERS VALUES(1, 'admin', 'super')"); + stat.execute("INSERT INTO USERS VALUES(2, 'guest', '123456')"); + stat.execute("INSERT INTO USERS VALUES(3, 'test', 'abc')"); + + loginByNameInsecure(); + + if (url.startsWith("jdbc:h2:")) { + loginStoredProcedureInsecure(); + limitRowAccess(); + } + + loginByNameSecure(); + + if (url.startsWith("jdbc:h2:")) { + stat.execute("SET ALLOW_LITERALS NONE"); + stat.execute("SET ALLOW_LITERALS NUMBERS"); + stat.execute("SET ALLOW_LITERALS ALL"); + } + + loginByIdInsecure(); + loginByIdSecure(); + + try { + stat.execute("DROP TABLE ITEMS"); + } catch (SQLException e) { + // ignore + } + + stat.execute("CREATE TABLE ITEMS(ID INT PRIMARY KEY, " + + "NAME VARCHAR(255), ACTIVE INT)"); + stat.execute("INSERT INTO ITEMS VALUES(0, 'XBox', 0)"); + stat.execute("INSERT INTO ITEMS VALUES(1, 'XBox 360', 1)"); + stat.execute("INSERT INTO ITEMS VALUES(2, 'PlayStation 1', 0)"); + stat.execute("INSERT INTO ITEMS VALUES(3, 'PlayStation 2', 1)"); + stat.execute("INSERT INTO ITEMS VALUES(4, 'PlayStation 3', 1)"); + + listActiveItems(); + + if (url.startsWith("jdbc:h2:")) { + stat.execute("DROP CONSTANT IF EXISTS TYPE_INACTIVE"); + stat.execute("DROP CONSTANT IF EXISTS TYPE_ACTIVE"); + stat.execute("CREATE CONSTANT TYPE_INACTIVE VALUE 0"); + stat.execute("CREATE CONSTANT TYPE_ACTIVE VALUE 1"); + listActiveItemsUsingConstants(); + } + + listItemsSortedInsecure(); + listItemsSortedSecure(); + + if (url.startsWith("jdbc:h2:")) { + listItemsSortedSecureParam(); + storePasswordHashWithSalt(); + } + + conn.close(); + } + + /** + * Simulate a login using an insecure method. + */ + void loginByNameInsecure() throws Exception { + System.out.println("Insecure Systems Inc. - login"); + String name = input("Name?"); + String password = input("Password?"); + ResultSet rs = stat.executeQuery("SELECT * FROM USERS WHERE " + + "NAME='" + name + "' AND PASSWORD='" + password + "'"); + if (rs.next()) { + System.out.println("Welcome!"); + } else { + System.out.println("Access denied!"); + } + } + + /** + * Utility method to get a user record given the user name and password. + * This method is secure. + * + * @param conn the database connection + * @param userName the user name + * @param password the password + * @return a result set with the user record if the password matches + * @throws Exception on failure + */ + public static ResultSet getUser(Connection conn, String userName, + String password) throws Exception { + PreparedStatement prep = conn.prepareStatement( + "SELECT * FROM USERS WHERE NAME=? AND PASSWORD=?"); + prep.setString(1, userName); + prep.setString(2, password); + return prep.executeQuery(); + } + + /** + * Utility method to change a password of a user. + * This method is secure, except that the old password is not checked. + * + * @param conn the database connection + * @param userName the user name + * @param password the password + * @return the new password + * @throws Exception on failure + */ + public static String changePassword(Connection conn, String userName, + String password) throws Exception { + PreparedStatement prep = conn.prepareStatement( + "UPDATE USERS SET PASSWORD=? WHERE NAME=?"); + prep.setString(1, password); + prep.setString(2, userName); + prep.executeUpdate(); + return password; + } + + /** + * Simulate a login using an insecure method. + * A stored procedure is used here. + */ + void loginStoredProcedureInsecure() throws Exception { + System.out.println("Insecure Systems Inc. - login using a stored procedure"); + stat.execute("CREATE ALIAS IF NOT EXISTS GET_USER FOR 'org.h2.samples.SQLInjection.getUser'"); + stat.execute("CREATE ALIAS IF NOT EXISTS CHANGE_PASSWORD FOR 'org.h2.samples.SQLInjection.changePassword'"); + String name = input("Name?"); + String password = input("Password?"); + ResultSet rs = stat.executeQuery( + "CALL GET_USER('" + name + "', '" + password + "')"); + if (rs.next()) { + System.out.println("Welcome!"); + } else { + System.out.println("Access denied!"); + } + } + + /** + * Simulate a login using a secure method. + */ + void loginByNameSecure() throws Exception { + System.out.println("Secure Systems Inc. - login using placeholders"); + String name = input("Name?"); + String password = input("Password?"); + PreparedStatement prep = conn.prepareStatement( + "SELECT * FROM USERS WHERE " + + "NAME=? AND PASSWORD=?"); + prep.setString(1, name); + prep.setString(2, password); + ResultSet rs = prep.executeQuery(); + if (rs.next()) { + System.out.println("Welcome!"); + } else { + System.out.println("Access denied!"); + } + rs.close(); + prep.close(); + } + + /** + * Sample code to limit access only to specific rows. + */ + void limitRowAccess() throws Exception { + System.out.println("Secure Systems Inc. - limit row access"); + stat.execute("DROP TABLE IF EXISTS SESSION_USER"); + stat.execute("CREATE TABLE SESSION_USER(ID INT, USER INT)"); + stat.execute("DROP VIEW IF EXISTS MY_USER"); + stat.execute("CREATE VIEW MY_USER AS " + + "SELECT U.* FROM SESSION_USER S, USERS U " + + "WHERE S.ID=SESSION_ID() AND S.USER=U.ID"); + stat.execute("INSERT INTO SESSION_USER VALUES(SESSION_ID(), 1)"); + ResultSet rs = stat.executeQuery("SELECT ID, NAME FROM MY_USER"); + while (rs.next()) { + System.out.println(rs.getString(1) + ": " + rs.getString(2)); + } + } + + /** + * Simulate a login using an insecure method. + */ + void loginByIdInsecure() throws Exception { + System.out.println("Half Secure Systems Inc. - login by id"); + String id = input("User ID?"); + String password = input("Password?"); + try { + PreparedStatement prep = conn.prepareStatement( + "SELECT * FROM USERS WHERE " + + "ID=" + id + " AND PASSWORD=?"); + prep.setString(1, password); + ResultSet rs = prep.executeQuery(); + if (rs.next()) { + System.out.println("Welcome!"); + } else { + System.out.println("Access denied!"); + } + rs.close(); + prep.close(); + } catch (SQLException e) { + System.out.println(e); + } + } + + /** + * Simulate a login using a secure method. + */ + void loginByIdSecure() throws Exception { + System.out.println("Secure Systems Inc. - login by id"); + String id = input("User ID?"); + String password = input("Password?"); + try { + PreparedStatement prep = conn.prepareStatement( + "SELECT * FROM USERS WHERE " + + "ID=? AND PASSWORD=?"); + prep.setInt(1, Integer.parseInt(id)); + prep.setString(2, password); + ResultSet rs = prep.executeQuery(); + if (rs.next()) { + System.out.println("Welcome!"); + } else { + System.out.println("Access denied!"); + } + rs.close(); + prep.close(); + } catch (Exception e) { + System.out.println(e); + } + } + + /** + * List active items. + * The method uses the hard coded value '1', and therefore the database + * can not verify if the SQL statement was constructed with user + * input or not. + */ + void listActiveItems() throws Exception { + System.out.println("Half Secure Systems Inc. - list active items"); + ResultSet rs = stat.executeQuery( + "SELECT NAME FROM ITEMS WHERE ACTIVE=1"); + while (rs.next()) { + System.out.println("Name: " + rs.getString(1)); + } + } + + /** + * List active items. + * The method uses a constant, and therefore the database + * knows it does not contain user input. + */ + void listActiveItemsUsingConstants() throws Exception { + System.out.println("Secure Systems Inc. - list active items"); + ResultSet rs = stat.executeQuery( + "SELECT NAME FROM ITEMS WHERE ACTIVE=TYPE_ACTIVE"); + while (rs.next()) { + System.out.println("Name: " + rs.getString(1)); + } + } + + /** + * List items using a specified sort order. + * The method is not secure as user input is used to construct the + * SQL statement. + */ + void listItemsSortedInsecure() throws Exception { + System.out.println("Insecure Systems Inc. - list items"); + String order = input("order (id, name)?"); + try { + ResultSet rs = stat.executeQuery( + "SELECT ID, NAME FROM ITEMS ORDER BY " + order); + while (rs.next()) { + System.out.println(rs.getString(1) + ": " + rs.getString(2)); + } + } catch (SQLException e) { + System.out.println(e); + } + } + + /** + * List items using a specified sort order. + * The method is secure as the user input is validated before use. + * However the database has no chance to verify this. + */ + void listItemsSortedSecure() throws Exception { + System.out.println("Secure Systems Inc. - list items"); + String order = input("order (id, name)?"); + if (!order.matches("[a-zA-Z0-9_]*")) { + order = "id"; + } + try { + ResultSet rs = stat.executeQuery( + "SELECT ID, NAME FROM ITEMS ORDER BY " + order); + while (rs.next()) { + System.out.println(rs.getString(1) + ": " + rs.getString(2)); + } + } catch (SQLException e) { + System.out.println(e); + } + } + + /** + * List items using a specified sort order. + * The method is secure as a parameterized statement is used. + */ + void listItemsSortedSecureParam() throws Exception { + System.out.println("Secure Systems Inc. - list items"); + String order = input("order (1, 2, -1, -2)?"); + PreparedStatement prep = conn.prepareStatement( + "SELECT ID, NAME FROM ITEMS ORDER BY ?"); + try { + prep.setInt(1, Integer.parseInt(order)); + ResultSet rs = prep.executeQuery(); + while (rs.next()) { + System.out.println(rs.getString(1) + ": " + rs.getString(2)); + } + rs.close(); + } catch (Exception e) { + System.out.println(e); + } + prep.close(); + } + + /** + * This method creates a one way hash from the password + * (using a random salt), and stores this information instead of the + * password. + */ + void storePasswordHashWithSalt() throws Exception { + System.out.println("Very Secure Systems Inc. - login"); + stat.execute("DROP TABLE IF EXISTS USERS2"); + stat.execute("CREATE TABLE USERS2(ID INT PRIMARY KEY, " + + "NAME VARCHAR, SALT BINARY, HASH BINARY)"); + stat.execute("INSERT INTO USERS2 VALUES" + + "(1, 'admin', SECURE_RAND(16), NULL)"); + stat.execute("DROP CONSTANT IF EXISTS HASH_ITERATIONS"); + stat.execute("DROP CONSTANT IF EXISTS HASH_ALGORITHM"); + stat.execute("CREATE CONSTANT HASH_ITERATIONS VALUE 100"); + stat.execute("CREATE CONSTANT HASH_ALGORITHM VALUE 'SHA256'"); + stat.execute("UPDATE USERS2 SET " + + "HASH=HASH(HASH_ALGORITHM, " + + "STRINGTOUTF8('abc' || SALT), HASH_ITERATIONS) " + + "WHERE ID=1"); + String user = input("user?"); + String password = input("password?"); + stat.execute("SET ALLOW_LITERALS NONE"); + PreparedStatement prep = conn.prepareStatement( + "SELECT * FROM USERS2 WHERE NAME=? AND " + + "HASH=HASH(HASH_ALGORITHM, STRINGTOUTF8(? || SALT), HASH_ITERATIONS)"); + prep.setString(1, user); + prep.setString(2, password); + ResultSet rs = prep.executeQuery(); + while (rs.next()) { + System.out.println("name: " + rs.getString("NAME")); + System.out.println("salt: " + rs.getString("SALT")); + System.out.println("hash: " + rs.getString("HASH")); + } + rs.close(); + prep.close(); + stat.execute("SET ALLOW_LITERALS ALL"); + stat.close(); + } + + /** + * Utility method to get user input from the command line. + * + * @param prompt the prompt + * @return the user input + */ + String input(String prompt) throws Exception { + System.out.print(prompt); + return new BufferedReader(new InputStreamReader(System.in)).readLine(); + } + +} diff --git a/h2/src/test/org/h2/samples/SecurePassword.java b/h2/src/test/org/h2/samples/SecurePassword.java new file mode 100644 index 0000000..d33a2c3 --- /dev/null +++ b/h2/src/test/org/h2/samples/SecurePassword.java @@ -0,0 +1,90 @@ +/* + * 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.samples; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.Properties; + +/** + * This example shows how to secure passwords + * (both database passwords, and account passwords). + */ +public class SecurePassword { + + /** + * This method is called when executing this sample application from the + * command line. + * + * @param args the command line parameters + * @throws Exception on failure + */ + public static void main(String... args) throws Exception { + + Class.forName("org.h2.Driver"); + String url = "jdbc:h2:data/simple"; + String user = "sam"; + char[] password = {'t', 'i', 'a', 'E', 'T', 'r', 'p'}; + + // This is the normal, but 'unsafe' way to connect: + // the password may reside in the main memory for an undefined time, + // or even written to disk (swap file): + // Connection conn = + // DriverManager.getConnection(url, user, new String(password)); + + // This is the most safe way to connect: the password is overwritten + // after use + Properties prop = new Properties(); + prop.setProperty("user", user); + prop.put("password", password); + Connection conn = DriverManager.getConnection(url, prop); + + // For security reasons, account passwords should not be stored directly + // in a database. Instead, only the hash should be stored. Also, + // PreparedStatements must be used to avoid SQL injection: + Statement stat = conn.createStatement(); + stat.execute( + "drop table account if exists"); + stat.execute( + "create table account(" + + "name varchar primary key, " + + "salt binary default secure_rand(16), " + + "hash binary)"); + PreparedStatement prep; + prep = conn.prepareStatement("insert into account(name) values(?)"); + prep.setString(1, "Joe"); + prep.execute(); + prep.close(); + + prep = conn.prepareStatement( + "update account set " + + "hash=hash('SHA256', stringtoutf8(salt||?), 10) " + + "where name=?"); + prep.setString(1, "secret"); + prep.setString(2, "Joe"); + prep.execute(); + prep.close(); + + prep = conn.prepareStatement( + "select * from account " + + "where name=? " + + "and hash=hash('SHA256', stringtoutf8(salt||?), 10)"); + prep.setString(1, "Joe"); + prep.setString(2, "secret"); + ResultSet rs = prep.executeQuery(); + while (rs.next()) { + System.out.println(rs.getString("name")); + } + rs.close(); + prep.close(); + stat.close(); + conn.close(); + } + +} diff --git a/h2/src/test/org/h2/samples/ShowProgress.java b/h2/src/test/org/h2/samples/ShowProgress.java new file mode 100644 index 0000000..fe34df7 --- /dev/null +++ b/h2/src/test/org/h2/samples/ShowProgress.java @@ -0,0 +1,174 @@ +/* + * 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.samples; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.TimeUnit; + +import org.h2.api.DatabaseEventListener; +import org.h2.engine.SessionLocal; +import org.h2.jdbc.JdbcConnection; + +/** + * This example application implements a database event listener. This is useful + * to display progress information while opening a large database, or to log + * database exceptions. + */ +public class ShowProgress implements DatabaseEventListener { + + private final long startNs; + private long lastNs; + + /** + * Create a new instance of this class, and startNs the timer. + */ + public ShowProgress() { + startNs = lastNs = System.nanoTime(); + } + + /** + * This method is called when executing this sample application from the + * command line. + * + * @param args the command line parameters + * @throws Exception on failure + */ + public static void main(String... args) throws Exception { + new ShowProgress().test(); + } + + /** + * Run the progress test. + */ + void test() throws Exception { + Class.forName("org.h2.Driver"); + Connection conn = DriverManager.getConnection("jdbc:h2:./test", "sa", ""); + Statement stat = conn.createStatement(); + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST VALUES(?, 'Test' || SPACE(100))"); + long time; + time = System.nanoTime(); + int len = 1000; + for (int i = 0; i < len; i++) { + long now = System.nanoTime(); + if (now > time + TimeUnit.SECONDS.toNanos(1)) { + time = now; + System.out.println("Inserting " + (100L * i / len) + "%"); + } + prep.setInt(1, i); + prep.execute(); + } + boolean abnormalTermination = true; + if (abnormalTermination) { + ((SessionLocal) ((JdbcConnection) conn).getSession()).getDatabase().setPowerOffCount(1); + try { + stat.execute("INSERT INTO TEST VALUES(-1, 'Test' || SPACE(100))"); + } catch (SQLException e) { + // ignore + } + } else { + conn.close(); + } + + System.out.println("Open connection..."); + time = System.nanoTime(); + conn = DriverManager.getConnection( + "jdbc:h2:./test;DATABASE_EVENT_LISTENER='" + + getClass().getName() + "'", "sa", ""); + time = System.nanoTime() - time; + System.out.println("Done after " + TimeUnit.NANOSECONDS.toMillis(time) + " ms"); + prep.close(); + stat.close(); + conn.close(); + + } + + /** + * This method is called if an exception occurs in the database. + * + * @param e the exception + * @param sql the SQL statement + */ + @Override + public void exceptionThrown(SQLException e, String sql) { + System.out.println("Error executing " + sql); + e.printStackTrace(); + } + + /** + * This method is called when opening the database to notify about the + * progress. + * + * @param state the current state + * @param name the object name (depends on the state) + * @param current the current progress + * @param max the 100% mark + */ + @Override + public void setProgress(int state, String name, long current, long max) { + long time = System.nanoTime(); + if (time < lastNs + TimeUnit.SECONDS.toNanos(5)) { + return; + } + lastNs = time; + String stateName = "?"; + switch (state) { + case STATE_SCAN_FILE: + stateName = "Scan " + name; + break; + case STATE_CREATE_INDEX: + stateName = "Create Index " + name; + break; + case STATE_RECOVER: + stateName = "Recover"; + break; + default: + return; + } + try { + Thread.sleep(1); + } catch (InterruptedException e) { + // ignore + } + System.out.println("State: " + stateName + " " + + (100 * current / max) + "% (" + + current + " of " + max + ") " + + TimeUnit.NANOSECONDS.toMillis(time - startNs) + " ms"); + } + + /** + * This method is called when the database is closed. + */ + @Override + public void closingDatabase() { + System.out.println("Closing the database"); + } + + /** + * This method is called just after creating the instance. + * + * @param url the database URL + */ + @Override + public void init(String url) { + System.out.println("Initializing the event listener for database " + url); + } + + /** + * This method is called when the database is open. + */ + @Override + public void opened() { + // do nothing + } + +} diff --git a/h2/src/test/org/h2/samples/ShutdownServer.java b/h2/src/test/org/h2/samples/ShutdownServer.java new file mode 100644 index 0000000..3b84c35 --- /dev/null +++ b/h2/src/test/org/h2/samples/ShutdownServer.java @@ -0,0 +1,26 @@ +/* + * 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.samples; + +import java.sql.SQLException; + +/** + * This very simple sample application stops a H2 TCP server + * if it is running. + */ +public class ShutdownServer { + + /** + * This method is called when executing this sample application from the + * command line. + * + * @param args the command line parameters + * @throws SQLException on failure + */ + public static void main(String... args) throws SQLException { + org.h2.tools.Server.shutdownTcpServer("tcp://localhost:9094", "", false, false); + } +} diff --git a/h2/src/test/org/h2/samples/TriggerPassData.java b/h2/src/test/org/h2/samples/TriggerPassData.java new file mode 100644 index 0000000..0cd6f8a --- /dev/null +++ b/h2/src/test/org/h2/samples/TriggerPassData.java @@ -0,0 +1,87 @@ +/* + * 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.samples; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.ConcurrentHashMap; + +import org.h2.api.Trigger; + +/** + * This sample application shows how to pass data to a trigger. Trigger data can + * be persisted by storing it in the database. + */ +public class TriggerPassData implements Trigger { + + private static final ConcurrentHashMap TRIGGERS = new ConcurrentHashMap<>(); + private String triggerData; + + /** + * This method is called when executing this sample application from the + * command line. + * + * @param args the command line parameters + * @throws Exception on failure + */ + public static void main(String... args) throws Exception { + Class.forName("org.h2.Driver"); + Connection conn = DriverManager.getConnection( + "jdbc:h2:mem:test", "sa", ""); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT)"); + stat.execute("CREATE ALIAS TRIGGER_SET FOR '" + + TriggerPassData.class.getName() + + ".setTriggerData'"); + stat.execute("CREATE TRIGGER T1 " + + "BEFORE INSERT ON TEST " + + "FOR EACH ROW CALL \"" + + TriggerPassData.class.getName() + "\""); + stat.execute("CALL TRIGGER_SET('T1', 'Hello')"); + stat.execute("INSERT INTO TEST VALUES(1)"); + stat.execute("CALL TRIGGER_SET('T1', 'World')"); + stat.execute("INSERT INTO TEST VALUES(2)"); + stat.close(); + conn.close(); + } + + @Override + public void init(Connection conn, String schemaName, + String triggerName, String tableName, boolean before, + int type) throws SQLException { + TRIGGERS.put(getPrefix(conn) + triggerName, this); + } + + @Override + public void fire(Connection conn, Object[] old, Object[] row) { + System.out.println(triggerData + ": " + row[0]); + } + + /** + * Call this method to change a specific trigger. + * + * @param conn the connection + * @param trigger the trigger name + * @param data the data + * @throws SQLException on failure + */ + public static void setTriggerData(Connection conn, String trigger, + String data) throws SQLException { + TRIGGERS.get(getPrefix(conn) + trigger).triggerData = data; + } + + private static String getPrefix(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery( + "call coalesce(database_path() || '_', '') || database() || '_'"); + rs.next(); + return rs.getString(1); + } + +} diff --git a/h2/src/test/org/h2/samples/TriggerSample.java b/h2/src/test/org/h2/samples/TriggerSample.java new file mode 100644 index 0000000..27c07a8 --- /dev/null +++ b/h2/src/test/org/h2/samples/TriggerSample.java @@ -0,0 +1,125 @@ +/* + * 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.samples; + +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.h2.api.Trigger; + +/** + * This sample application shows how to use database triggers. + */ +public class TriggerSample { + + /** + * This method is called when executing this sample application from the + * command line. + * + * @param args the command line parameters + * @throws Exception on failure + */ + public static void main(String... args) throws Exception { + Class.forName("org.h2.Driver"); + Connection conn = DriverManager.getConnection("jdbc:h2:mem:", "sa", ""); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE INVOICE(ID INT PRIMARY KEY, AMOUNT DECIMAL(10, 2))"); + stat.execute("CREATE TABLE INVOICE_SUM(AMOUNT DECIMAL(10, 2))"); + stat.execute("INSERT INTO INVOICE_SUM VALUES(0.0)"); + + stat.execute("CREATE TRIGGER INV_INS " + + "AFTER INSERT ON INVOICE FOR EACH ROW " + + "CALL \"org.h2.samples.TriggerSample$MyTrigger\" "); + stat.execute("CREATE TRIGGER INV_UPD " + + "AFTER UPDATE ON INVOICE FOR EACH ROW " + + "CALL \"org.h2.samples.TriggerSample$MyTrigger\" "); + stat.execute("CREATE TRIGGER INV_DEL " + + "AFTER DELETE ON INVOICE FOR EACH ROW " + + "CALL \"org.h2.samples.TriggerSample$MyTrigger\" "); + + stat.execute("INSERT INTO INVOICE VALUES(1, 10.0)"); + stat.execute("INSERT INTO INVOICE VALUES(2, 19.95)"); + stat.execute("UPDATE INVOICE SET AMOUNT=20.0 WHERE ID=2"); + stat.execute("DELETE FROM INVOICE WHERE ID=1"); + + ResultSet rs; + rs = stat.executeQuery("SELECT AMOUNT FROM INVOICE_SUM"); + rs.next(); + System.out.println("The sum is " + rs.getBigDecimal(1)); + rs.close(); + stat.close(); + conn.close(); + } + + /** + * This class is a simple trigger implementation. + */ + public static class MyTrigger implements Trigger { + + /** + * Initializes the trigger. + * + * @param conn a connection to the database + * @param schemaName the name of the schema + * @param triggerName the name of the trigger used in the CREATE TRIGGER + * statement + * @param tableName the name of the table + * @param before whether the fire method is called before or after the + * operation is performed + * @param type the operation type: INSERT, UPDATE, or DELETE + */ + @Override + public void init(Connection conn, String schemaName, + String triggerName, String tableName, boolean before, int type) { + // initialize the trigger object is necessary + } + + /** + * This method is called for each triggered action. + * + * @param conn a connection to the database + * @param oldRow the old row, or null if no old row is available (for + * INSERT) + * @param newRow the new row, or null if no new row is available (for + * DELETE) + * @throws SQLException if the operation must be undone + */ + @Override + public void fire(Connection conn, + Object[] oldRow, Object[] newRow) + throws SQLException { + BigDecimal diff = null; + if (newRow != null) { + diff = (BigDecimal) newRow[1]; + } + if (oldRow != null) { + BigDecimal m = (BigDecimal) oldRow[1]; + diff = diff == null ? m.negate() : diff.subtract(m); + } + PreparedStatement prep = conn.prepareStatement( + "UPDATE INVOICE_SUM SET AMOUNT=AMOUNT+?"); + prep.setBigDecimal(1, diff); + prep.execute(); + } + + @Override + public void close() { + // ignore + } + + @Override + public void remove() { + // ignore + } + + } + +} diff --git a/h2/src/test/org/h2/samples/UpdatableView.java b/h2/src/test/org/h2/samples/UpdatableView.java new file mode 100644 index 0000000..ec60d38 --- /dev/null +++ b/h2/src/test/org/h2/samples/UpdatableView.java @@ -0,0 +1,130 @@ +/* + * 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.samples; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; + +import org.h2.tools.TriggerAdapter; + +/** + * This sample application shows how to use triggers to create updatable views. + */ +public class UpdatableView extends TriggerAdapter { + + private PreparedStatement prepDelete, prepInsert; + + /** + * This method is called when executing this sample application from the + * command line. + * + * @param args ignored + * @throws Exception on failure + */ + public static void main(String... args) throws Exception { + Class.forName("org.h2.Driver"); + try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:")) { + Statement stat; + stat = conn.createStatement(); + + // Create the table TEST_TABLE and the view TEST_VIEW that simply + // selects everything from the TEST_TABLE. + stat.execute("CREATE TABLE TEST_TABLE" + + "(ID BIGINT GENERATED BY DEFAULT AS IDENTITY DEFAULT ON NULL PRIMARY KEY, NAME VARCHAR)"); + stat.execute("CREATE VIEW TEST_VIEW AS TABLE TEST_TABLE"); + + // Create the INSTEAD OF trigger that is called whenever the data in + // the view is modified. This trigger makes the view updatable. + stat.execute( + "CREATE TRIGGER T_TEST_VIEW INSTEAD OF INSERT, UPDATE, DELETE ON TEST_VIEW FOR EACH ROW CALL \"" + + UpdatableView.class.getName() + '"'); + + // Test an INSERT operation and check that generated keys from the + // source table are returned as expected. + stat.execute("INSERT INTO TEST_VIEW(NAME) VALUES 'Hello', 'World'", new String[] { "ID" }); + try (ResultSet rs = stat.getGeneratedKeys()) { + while (rs.next()) { + System.out.printf("Key %d was generated%n", rs.getLong(1)); + } + } + System.out.println(); + // Test UPDATE and DELETE operations. + stat.execute("UPDATE TEST_VIEW SET NAME = 'Hallo' WHERE ID = 1"); + stat.execute("DELETE FROM TEST_VIEW WHERE ID = 2"); + + // Print the contents of the table and the view, they should be the + // same. + System.out.println("TEST_TABLE:"); + try (ResultSet rs = stat.executeQuery("TABLE TEST_TABLE")) { + while (rs.next()) { + System.out.printf("%d %s%n", rs.getLong(1), rs.getString(2)); + } + } + System.out.println(); + System.out.println("TEST_VIEW:"); + try (ResultSet rs = stat.executeQuery("TABLE TEST_VIEW")) { + while (rs.next()) { + System.out.printf("%d %s%n", rs.getLong(1), rs.getString(2)); + } + } + } + } + + @Override + public void init(Connection conn, String schemaName, String triggerName, String tableName, boolean before, + int type) throws SQLException { + prepDelete = conn.prepareStatement("DELETE FROM TEST_TABLE WHERE ID = ?"); + // INSERT and UPDATE triggers should return the FINAL values of the row. + // Table TEST_TABLE has a generated column, so the FINAL row can be + // different from the row that we try to insert here. + prepInsert = conn.prepareStatement("SELECT * FROM FINAL TABLE(INSERT INTO TEST_TABLE VALUES (?, ?))"); + super.init(conn, schemaName, triggerName, tableName, before, type); + } + + @Override + public void fire(Connection conn, ResultSet oldRow, ResultSet newRow) throws SQLException { + if (oldRow != null && oldRow.next()) { + prepDelete.setLong(1, oldRow.getLong(1)); + prepDelete.execute(); + } + if (newRow != null && newRow.next()) { + long id = newRow.getLong(1); + if (newRow.wasNull()) { + prepInsert.setNull(1, Types.BIGINT); + } else { + prepInsert.setLong(1, id); + } + prepInsert.setString(2, newRow.getString(2)); + // Now we need to execute the INSERT statement and update the newRow + // with the FINAL values. + // It is necessary for the FINAL TABLE and getGeneratedKeys(); if we + // don't update the newRow, the FINAL TABLE will work like the NEW + // TABLE. + // It is only necessary when the source table has generated columns + // or other columns with default values, or it has a trigger that + // can change the inserted values; without such columns the NEW + // TABLE and the FINAL TABLE are the same. + try (ResultSet rs = prepInsert.executeQuery()) { + rs.next(); + newRow.updateLong(1, rs.getLong(1)); + newRow.updateString(2, rs.getString(2)); + newRow.rowUpdated(); + } + } + } + + @Override + public void close() throws SQLException { + prepInsert.close(); + prepDelete.close(); + } + +} diff --git a/h2/src/test/org/h2/samples/fullTextSearch.sql b/h2/src/test/org/h2/samples/fullTextSearch.sql new file mode 100644 index 0000000..a49cf04 --- /dev/null +++ b/h2/src/test/org/h2/samples/fullTextSearch.sql @@ -0,0 +1,59 @@ +/* + * 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 + */ + +// Example using the 'native' fulltext search implementation +CREATE ALIAS IF NOT EXISTS FT_INIT FOR "org.h2.fulltext.FullText.init"; +CALL FT_INIT(); +DROP TABLE IF EXISTS TEST; +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR); +INSERT INTO TEST VALUES(1, 'Hello World'); +CALL FT_CREATE_INDEX('PUBLIC', 'TEST', NULL); +SELECT * FROM FT_SEARCH('Hello', 0, 0); +SELECT * FROM FT_SEARCH('Hallo', 0, 0); +INSERT INTO TEST VALUES(2, 'Hallo Welt'); +SELECT * FROM FT_SEARCH('Hello', 0, 0); +SELECT * FROM FT_SEARCH('Hallo', 0, 0); +CALL FT_REINDEX(); +SELECT * FROM FT_SEARCH('Hello', 0, 0); +SELECT * FROM FT_SEARCH('Hallo', 0, 0); +INSERT INTO TEST VALUES(3, 'Hello World'); +INSERT INTO TEST VALUES(4, 'Hello World'); +INSERT INTO TEST VALUES(5, 'Hello World'); +SELECT * FROM FT_SEARCH('World', 0, 0); +SELECT * FROM FT_SEARCH('World', 1, 0); +SELECT * FROM FT_SEARCH('World', 0, 2); +SELECT * FROM FT_SEARCH('World', 2, 1); +SELECT * FROM FT_SEARCH('1', 0, 0); +CALL FT_DROP_INDEX('PUBLIC', 'TEST'); +SELECT * FROM FT_SEARCH('World', 2, 1); +CALL FT_DROP_ALL(); + +// Example using the 'Lucene' fulltext search implementation +CREATE ALIAS IF NOT EXISTS FTL_INIT FOR "org.h2.fulltext.FullTextLucene.init"; +CALL FTL_INIT(); +DROP TABLE IF EXISTS TEST; +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR); +INSERT INTO TEST VALUES(1, 'Hello World'); +CALL FTL_CREATE_INDEX('PUBLIC', 'TEST', NULL); +SELECT * FROM FTL_SEARCH('Hello', 0, 0); +SELECT * FROM FTL_SEARCH('Hallo', 0, 0); +INSERT INTO TEST VALUES(2, 'Hallo Welt'); +SELECT * FROM FTL_SEARCH('Hello', 0, 0); +SELECT * FROM FTL_SEARCH('Hallo', 0, 0); +CALL FTL_REINDEX(); +SELECT * FROM FTL_SEARCH('Hello', 0, 0); +SELECT * FROM FTL_SEARCH('Hallo', 0, 0); +INSERT INTO TEST VALUES(3, 'Hello World'); +INSERT INTO TEST VALUES(4, 'Hello World'); +INSERT INTO TEST VALUES(5, 'Hello World'); +SELECT * FROM FTL_SEARCH('World', 0, 0); +SELECT * FROM FTL_SEARCH('World', 1, 0); +SELECT * FROM FTL_SEARCH('World', 0, 2); +SELECT * FROM FTL_SEARCH('World', 2, 1); +SELECT * FROM FTL_SEARCH('1', 0, 0); +CALL FTL_DROP_ALL(); +SELECT * FROM FTL_SEARCH('World', 2, 1); +CALL FTL_DROP_ALL(); diff --git a/h2/src/test/org/h2/samples/newsfeed.sql b/h2/src/test/org/h2/samples/newsfeed.sql new file mode 100644 index 0000000..60f9a10 --- /dev/null +++ b/h2/src/test/org/h2/samples/newsfeed.sql @@ -0,0 +1,128 @@ +/* + * 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 + */ + +CREATE TABLE VERSION(ID INT PRIMARY KEY, VERSION VARCHAR, CREATED VARCHAR); +INSERT INTO VERSION VALUES + +(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'), +(151, '2.0.202', '2021-11-25'), +(150, '1.4.200', '2019-10-14'), +(149, '1.4.199', '2019-03-13'), +(148, '1.4.198', '2019-02-22'), +(147, '1.4.197', '2018-03-18'), +(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'); + +CREATE TABLE CHANNEL(TITLE VARCHAR, LINK VARCHAR, DESC VARCHAR, + LANGUAGE VARCHAR, PUB TIMESTAMP, LAST TIMESTAMP, AUTHOR VARCHAR); + +INSERT INTO CHANNEL VALUES('H2 Database Engine' , + 'https://h2database.com/', 'H2 Database Engine', 'en-us', LOCALTIMESTAMP, LOCALTIMESTAMP, 'Thomas Mueller'); + +CREATE VIEW ITEM AS +SELECT ID, 'New version available: ' || VERSION || ' (' || CREATED || ')' TITLE, +CAST((CREATED || ' 12:00:00') AS TIMESTAMP) ISSUED, +$$A new version of H2 is available for +download. +(You may have to click 'Refresh'). +
    +For details, see the +change log. +$$ AS DESC FROM VERSION; + +SELECT 'newsfeed-rss.xml' FILE, + XMLSTARTDOC() || + XMLNODE('rss', XMLATTR('version', '2.0'), + XMLNODE('channel', NULL, + XMLNODE('title', NULL, C.TITLE) || + XMLNODE('link', NULL, C.LINK) || + XMLNODE('description', NULL, C.DESC) || + XMLNODE('language', NULL, C.LANGUAGE) || + XMLNODE('pubDate', NULL, FORMATDATETIME(C.PUB, 'EEE, d MMM yyyy HH:mm:ss z', 'en', 'GMT')) || + XMLNODE('lastBuildDate', NULL, FORMATDATETIME(C.LAST, 'EEE, d MMM yyyy HH:mm:ss z', 'en', 'GMT')) || + GROUP_CONCAT( + XMLNODE('item', NULL, + XMLNODE('title', NULL, I.TITLE) || + XMLNODE('link', NULL, C.LINK) || + XMLNODE('description', NULL, XMLCDATA(I.TITLE)) + ) + ORDER BY I.ID DESC SEPARATOR '') + ) + ) CONTENT +FROM CHANNEL C, ITEM I +UNION +SELECT 'newsfeed-atom.xml' FILE, + XMLSTARTDOC() || + XMLNODE('feed', XMLATTR('xmlns', 'http://www.w3.org/2005/Atom') || XMLATTR('xml:lang', C.LANGUAGE), + XMLNODE('title', XMLATTR('type', 'text'), C.TITLE) || + XMLNODE('id', NULL, XMLTEXT(C.LINK)) || + XMLNODE('author', NULL, XMLNODE('name', NULL, C.AUTHOR)) || + XMLNODE('link', XMLATTR('rel', 'self') || XMLATTR('href', 'https://h2database.com/html/newsfeed-atom.xml'), NULL) || + XMLNODE('updated', NULL, FORMATDATETIME(C.LAST, 'yyyy-MM-dd''T''HH:mm:ss''Z''', 'en', 'GMT')) || + GROUP_CONCAT( + XMLNODE('entry', NULL, + XMLNODE('title', XMLATTR('type', 'text'), I.TITLE) || + XMLNODE('link', XMLATTR('rel', 'alternate') || XMLATTR('type', 'text/html') || XMLATTR('href', C.LINK), NULL) || + XMLNODE('id', NULL, XMLTEXT(C.LINK || '/' || I.ID)) || + XMLNODE('updated', NULL, FORMATDATETIME(I.ISSUED, 'yyyy-MM-dd''T''HH:mm:ss''Z''', 'en', 'GMT')) || + XMLNODE('content', XMLATTR('type', 'html'), XMLCDATA(I.DESC)) + ) + ORDER BY I.ID DESC SEPARATOR '') + ) CONTENT +FROM CHANNEL C, ITEM I +UNION +SELECT 'newsletter.txt' FILE, I.DESC CONTENT FROM ITEM I WHERE I.ID = (SELECT MAX(ID) FROM ITEM) +UNION +SELECT 'doap-h2.rdf' FILE, + XMLSTARTDOC() || +$$ + + H2 Database Engine + + Java + + + + + + + H2 Database Engine + + H2 is a relational database management system written in Java. + It can be embedded in Java applications or run in the client-server mode. + The disk footprint is about 1 MB. The main programming APIs are SQL and JDBC, + however the database also supports using the PostgreSQL ODBC driver by acting like a PostgreSQL server. + It is possible to create both in-memory tables, as well as disk-based tables. + Tables can be persistent or temporary. Index types are hash table and tree for in-memory tables, + and b-tree for disk-based tables. + All data manipulation operations are transactional. (from Wikipedia) + + + + + + + + +$$ || + GROUP_CONCAT( + XMLNODE('release', NULL, + XMLNODE('Version', NULL, + XMLNODE('name', NULL, 'H2 ' || V.VERSION) || + XMLNODE('created', NULL, V.CREATED) || + XMLNODE('revision', NULL, V.VERSION) + ) + ) + ORDER BY V.ID DESC SEPARATOR '') || +' +' CONTENT +FROM VERSION V diff --git a/h2/src/test/org/h2/samples/optimizations.sql b/h2/src/test/org/h2/samples/optimizations.sql new file mode 100644 index 0000000..208224a --- /dev/null +++ b/h2/src/test/org/h2/samples/optimizations.sql @@ -0,0 +1,293 @@ +/* + * 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 + */ + +------------------------------------------------------------------------------- +-- Optimize Count Star +------------------------------------------------------------------------------- +-- This code snippet shows how to quickly get the number of rows in a table. + +-- Initialize the data +CREATE TABLE TEST(ID INT PRIMARY KEY); +INSERT INTO TEST SELECT X FROM SYSTEM_RANGE(1, 1000); + +-- Query the count +SELECT COUNT(*) FROM TEST; +--> 1000 +; + +-- Display the query plan - 'direct lookup' means the index is used +EXPLAIN SELECT COUNT(*) FROM TEST; +--> SELECT +--> COUNT(*) +--> FROM "PUBLIC"."TEST" +--> /* PUBLIC.TEST.tableScan */ +--> /* direct lookup */ +; + +DROP TABLE TEST; + +------------------------------------------------------------------------------- +-- Optimize Distinct +------------------------------------------------------------------------------- +-- This code snippet shows how to quickly get all distinct values +-- of a column for the whole table. + +-- Initialize the data +CREATE TABLE TEST(ID INT PRIMARY KEY, TYPE INT); +CALL RAND(0); +--> 0.730967787376657 +; +INSERT INTO TEST SELECT X, MOD(X, 10) FROM SYSTEM_RANGE(1, 1000); + +-- Create an index on the column TYPE +CREATE INDEX IDX_TEST_TYPE ON TEST(TYPE); + +-- Calculate the selectivity - otherwise it will not be optimized +ANALYZE; + +-- Query the distinct values +SELECT DISTINCT TYPE FROM TEST ORDER BY TYPE LIMIT 3; +--> 0 +--> 1 +--> 2 +; + +-- Display the query plan - 'index sorted' means the index is used to order +EXPLAIN SELECT DISTINCT TYPE FROM TEST ORDER BY TYPE LIMIT 3; +--> SELECT DISTINCT +--> "TYPE" +--> FROM "PUBLIC"."TEST" +--> /* PUBLIC.IDX_TEST_TYPE */ +--> ORDER BY 1 +--> FETCH FIRST 3 ROWS ONLY +--> /* distinct */ +--> /* index sorted */ +; + +DROP TABLE TEST; + +------------------------------------------------------------------------------- +-- Optimize Min Max +------------------------------------------------------------------------------- +-- This code snippet shows how to quickly get the smallest and largest value +-- of a column for each group. + +-- Initialize the data +CREATE TABLE TEST(ID INT PRIMARY KEY, "VALUE" DECIMAL(100, 2)); +CALL RAND(0); +--> 0.730967787376657 +; +INSERT INTO TEST SELECT X, RAND()*100 FROM SYSTEM_RANGE(1, 1000); + +-- Create an index on the column VALUE +CREATE INDEX IDX_TEST_VALUE ON TEST("VALUE"); + +-- Query the largest and smallest value - this is optimized +SELECT MIN("VALUE"), MAX("VALUE") FROM TEST; +--> 0.01 99.89 +; + +-- Display the query plan - 'direct lookup' means it's optimized +EXPLAIN SELECT MIN("VALUE"), MAX("VALUE") FROM TEST; +--> SELECT +--> MIN("VALUE"), +--> MAX("VALUE") +--> FROM "PUBLIC"."TEST" +--> /* PUBLIC.IDX_TEST_VALUE */ +--> /* direct lookup */ +; + +DROP TABLE TEST; + +------------------------------------------------------------------------------- +-- Optimize Grouped Min Max +------------------------------------------------------------------------------- +-- This code snippet shows how to quickly get the smallest and largest value +-- of a column for each group. + +-- Initialize the data +CREATE TABLE TEST(ID INT PRIMARY KEY, TYPE INT, "VALUE" DECIMAL(100, 2)); +CALL RAND(0); +--> 0.730967787376657 +; +INSERT INTO TEST SELECT X, MOD(X, 5), RAND()*100 FROM SYSTEM_RANGE(1, 1000); + +-- Create an index on the columns TYPE and VALUE +CREATE INDEX IDX_TEST_TYPE_VALUE ON TEST(TYPE, "VALUE"); + +-- Analyze to optimize the DISTINCT part of the query +ANALYZE; + +-- Query the largest and smallest value - this is optimized +SELECT TYPE, (SELECT "VALUE" FROM TEST T2 WHERE T.TYPE = T2.TYPE +ORDER BY TYPE, "VALUE" LIMIT 1) MIN +FROM (SELECT DISTINCT TYPE FROM TEST) T ORDER BY TYPE; +--> 0 0.42 +--> 1 0.14 +--> 2 0.01 +--> 3 0.40 +--> 4 0.44 +; + +-- Display the query plan +EXPLAIN SELECT TYPE, (SELECT "VALUE" FROM TEST T2 WHERE T.TYPE = T2.TYPE +ORDER BY TYPE, "VALUE" LIMIT 1) MIN +FROM (SELECT DISTINCT TYPE FROM TEST) T ORDER BY TYPE; +--> SELECT +--> "TYPE", +--> (SELECT +--> "VALUE" +--> FROM "PUBLIC"."TEST" "T2" +--> /* PUBLIC.IDX_TEST_TYPE_VALUE: TYPE = T.TYPE */ +--> WHERE "T"."TYPE" = "T2"."TYPE" +--> ORDER BY "TYPE", 1 +--> FETCH FIRST ROW ONLY +--> /* index sorted */) AS "MIN" +--> FROM ( +--> SELECT DISTINCT +--> "TYPE" +--> FROM "PUBLIC"."TEST" +--> ) "T" +--> /* SELECT DISTINCT +--> TYPE +--> FROM PUBLIC.TEST +--> /* PUBLIC.IDX_TEST_TYPE_VALUE */ +--> /* distinct */ +--> */ +--> ORDER BY 1 +; + +DROP TABLE TEST; + +------------------------------------------------------------------------------- +-- Optimize Top N -- +------------------------------------------------------------------------------- +-- This code snippet shows how to quickly get the smallest and largest N +-- values of a column for the whole table. + +-- Initialize the data +CREATE TABLE TEST(ID INT PRIMARY KEY, TYPE INT, "VALUE" DECIMAL(100, 2)); +CALL RAND(0); +--> 0.730967787376657 +; +INSERT INTO TEST SELECT X, MOD(X, 100), RAND()*100 FROM SYSTEM_RANGE(1, 1000); + +-- Create an index on the column VALUE +CREATE INDEX IDX_TEST_VALUE ON TEST("VALUE"); + +-- Query the smallest 10 values +SELECT "VALUE" FROM TEST ORDER BY "VALUE" LIMIT 3; +--> 0.01 +--> 0.14 +--> 0.16 +; + +-- Display the query plan - 'index sorted' means the index is used +EXPLAIN SELECT "VALUE" FROM TEST ORDER BY "VALUE" LIMIT 10; +--> SELECT +--> "VALUE" +--> FROM "PUBLIC"."TEST" +--> /* PUBLIC.IDX_TEST_VALUE */ +--> ORDER BY 1 +--> FETCH FIRST 10 ROWS ONLY +--> /* index sorted */ +; + +-- To optimize getting the largest values, a new descending index is required +CREATE INDEX IDX_TEST_VALUE_D ON TEST("VALUE" DESC); + +-- Query the largest 10 values +SELECT "VALUE" FROM TEST ORDER BY "VALUE" DESC LIMIT 3; +--> 99.89 +--> 99.73 +--> 99.68 +; + +-- Display the query plan - 'index sorted' means the index is used +EXPLAIN SELECT "VALUE" FROM TEST ORDER BY "VALUE" DESC LIMIT 10; +--> SELECT +--> "VALUE" +--> FROM "PUBLIC"."TEST" +--> /* PUBLIC.IDX_TEST_VALUE_D */ +--> ORDER BY 1 DESC +--> FETCH FIRST 10 ROWS ONLY +--> /* index sorted */ +; + +DROP TABLE TEST; + +------------------------------------------------------------------------------- +-- Optimize IN(..) +------------------------------------------------------------------------------- +-- This code snippet shows how IN(...) uses an index (unlike .. OR ..). + +-- Initialize the data +CREATE TABLE TEST(ID INT PRIMARY KEY); +INSERT INTO TEST SELECT X FROM SYSTEM_RANGE(1, 1000); + +-- Query the count +SELECT * FROM TEST WHERE ID IN(1, 1000); +--> 1 +--> 1000 +; + +-- Display the query plan +EXPLAIN SELECT * FROM TEST WHERE ID IN(1, 1000); +--> SELECT +--> "PUBLIC"."TEST"."ID" +--> FROM "PUBLIC"."TEST" +--> /* PUBLIC.PRIMARY_KEY_2: ID IN(1, 1000) */ +--> WHERE "ID" IN(1, 1000) +; + +DROP TABLE TEST; + +------------------------------------------------------------------------------- +-- Optimize Multiple IN(..) +------------------------------------------------------------------------------- +-- This code snippet shows how multiple IN(...) conditions use an index. + +-- Initialize the data +CREATE TABLE TEST(ID INT PRIMARY KEY, DATA INT); +CREATE INDEX TEST_DATA ON TEST(DATA); + +INSERT INTO TEST SELECT X, MOD(X, 10) FROM SYSTEM_RANGE(1, 1000); + +-- Display the query plan +EXPLAIN SELECT * FROM TEST WHERE ID IN (10, 20) AND DATA IN (1, 2); +--> SELECT +--> "PUBLIC"."TEST"."ID", +--> "PUBLIC"."TEST"."DATA" +--> FROM "PUBLIC"."TEST" +--> /* PUBLIC.TEST_DATA: DATA IN(1, 2) */ +--> WHERE ("ID" IN(10, 20)) +--> AND ("DATA" IN(1, 2)) +; + +DROP TABLE TEST; + +------------------------------------------------------------------------------- +-- Optimize GROUP BY +------------------------------------------------------------------------------- +-- This code snippet shows how GROUP BY is using an index. + +-- Initialize the data +CREATE TABLE TEST(ID INT PRIMARY KEY, DATA INT); + +INSERT INTO TEST SELECT X, X/10 FROM SYSTEM_RANGE(1, 100); + +-- Display the query plan +EXPLAIN SELECT ID X, COUNT(*) FROM TEST GROUP BY ID; +--> SELECT +--> "ID" AS "X", +--> COUNT(*) +--> FROM "PUBLIC"."TEST" +--> /* PUBLIC.PRIMARY_KEY_2 */ +--> GROUP BY "ID" +--> /* group sorted */ +; + +DROP TABLE TEST; diff --git a/h2/src/test/org/h2/samples/package.html b/h2/src/test/org/h2/samples/package.html new file mode 100644 index 0000000..a65657a --- /dev/null +++ b/h2/src/test/org/h2/samples/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +Standalone sample applications. + +

    \ No newline at end of file diff --git a/h2/src/test/org/h2/test/TestAll.java b/h2/src/test/org/h2/test/TestAll.java new file mode 100644 index 0000000..4c5c3e5 --- /dev/null +++ b/h2/src/test/org/h2/test/TestAll.java @@ -0,0 +1,1141 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test; + +import java.lang.management.ManagementFactory; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.TimerTask; +import java.util.concurrent.TimeUnit; +import org.h2.Driver; +import org.h2.engine.Constants; +import org.h2.store.fs.FileUtils; +import org.h2.store.fs.rec.FilePathRec; +import org.h2.test.auth.TestAuthentication; +import org.h2.test.bench.TestPerformance; +import org.h2.test.db.TestAlter; +import org.h2.test.db.TestAlterSchemaRename; +import org.h2.test.db.TestAlterTableNotFound; +import org.h2.test.db.TestAnalyzeTableTx; +import org.h2.test.db.TestAutoRecompile; +import org.h2.test.db.TestBackup; +import org.h2.test.db.TestBigDb; +import org.h2.test.db.TestBigResult; +import org.h2.test.db.TestCases; +import org.h2.test.db.TestCheckpoint; +import org.h2.test.db.TestCluster; +import org.h2.test.db.TestCompatibility; +import org.h2.test.db.TestCompatibilityOracle; +import org.h2.test.db.TestCompatibilitySQLServer; +import org.h2.test.db.TestCsv; +import org.h2.test.db.TestDateStorage; +import org.h2.test.db.TestDeadlock; +import org.h2.test.db.TestDuplicateKeyUpdate; +import org.h2.test.db.TestEncryptedDb; +import org.h2.test.db.TestExclusive; +import org.h2.test.db.TestFullText; +import org.h2.test.db.TestFunctionOverload; +import org.h2.test.db.TestFunctions; +import org.h2.test.db.TestGeneralCommonTableQueries; +import org.h2.test.db.TestIgnoreCatalogs; +import org.h2.test.db.TestIndex; +import org.h2.test.db.TestIndexHints; +import org.h2.test.db.TestLargeBlob; +import org.h2.test.db.TestLinkedTable; +import org.h2.test.db.TestListener; +import org.h2.test.db.TestLob; +import org.h2.test.db.TestMemoryUsage; +import org.h2.test.db.TestMergeUsing; +import org.h2.test.db.TestMultiConn; +import org.h2.test.db.TestMultiDimension; +import org.h2.test.db.TestMultiThread; +import org.h2.test.db.TestMultiThreadedKernel; +import org.h2.test.db.TestOpenClose; +import org.h2.test.db.TestOptimizations; +import org.h2.test.db.TestOutOfMemory; +import org.h2.test.db.TestPersistentCommonTableExpressions; +import org.h2.test.db.TestPowerOff; +import org.h2.test.db.TestQueryCache; +import org.h2.test.db.TestReadOnly; +import org.h2.test.db.TestRecursiveQueries; +import org.h2.test.db.TestRights; +import org.h2.test.db.TestRunscript; +import org.h2.test.db.TestSQLInjection; +import org.h2.test.db.TestSelectTableNotFound; +import org.h2.test.db.TestSequence; +import org.h2.test.db.TestSessionsLocks; +import org.h2.test.db.TestSetCollation; +import org.h2.test.db.TestSpaceReuse; +import org.h2.test.db.TestSpatial; +import org.h2.test.db.TestSpeed; +import org.h2.test.db.TestSubqueryPerformanceOnLazyExecutionMode; +import org.h2.test.db.TestSynonymForTable; +import org.h2.test.db.TestTableEngines; +import org.h2.test.db.TestTempTables; +import org.h2.test.db.TestTransaction; +import org.h2.test.db.TestTriggersConstraints; +import org.h2.test.db.TestTwoPhaseCommit; +import org.h2.test.db.TestView; +import org.h2.test.db.TestViewAlterTable; +import org.h2.test.db.TestViewDropView; +import org.h2.test.jdbc.TestBatchUpdates; +import org.h2.test.jdbc.TestCallableStatement; +import org.h2.test.jdbc.TestCancel; +import org.h2.test.jdbc.TestConcurrentConnectionUsage; +import org.h2.test.jdbc.TestConnection; +import org.h2.test.jdbc.TestDatabaseEventListener; +import org.h2.test.jdbc.TestDriver; +import org.h2.test.jdbc.TestGetGeneratedKeys; +import org.h2.test.jdbc.TestJavaObjectSerializer; +import org.h2.test.jdbc.TestLobApi; +import org.h2.test.jdbc.TestManyJdbcObjects; +import org.h2.test.jdbc.TestMetaData; +import org.h2.test.jdbc.TestNativeSQL; +import org.h2.test.jdbc.TestPreparedStatement; +import org.h2.test.jdbc.TestResultSet; +import org.h2.test.jdbc.TestSQLXML; +import org.h2.test.jdbc.TestStatement; +import org.h2.test.jdbc.TestTransactionIsolation; +import org.h2.test.jdbc.TestUpdatableResultSet; +import org.h2.test.jdbc.TestUrlJavaObjectSerializer; +import org.h2.test.jdbc.TestZloty; +import org.h2.test.jdbcx.TestConnectionPool; +import org.h2.test.jdbcx.TestDataSource; +import org.h2.test.jdbcx.TestXA; +import org.h2.test.jdbcx.TestXASimple; +import org.h2.test.mvcc.TestMvcc1; +import org.h2.test.mvcc.TestMvcc2; +import org.h2.test.mvcc.TestMvcc3; +import org.h2.test.mvcc.TestMvcc4; +import org.h2.test.mvcc.TestMvccMultiThreaded; +import org.h2.test.mvcc.TestMvccMultiThreaded2; +import org.h2.test.poweroff.TestReorderWrites; +import org.h2.test.recover.RecoverLobTest; +import org.h2.test.rowlock.TestRowLocks; +import org.h2.test.scripts.TestScript; +import org.h2.test.server.TestAutoServer; +import org.h2.test.server.TestInit; +import org.h2.test.server.TestJakartaWeb; +import org.h2.test.server.TestNestedLoop; +import org.h2.test.server.TestWeb; +import org.h2.test.store.TestCacheConcurrentLIRS; +import org.h2.test.store.TestCacheLIRS; +import org.h2.test.store.TestCacheLongKeyLIRS; +import org.h2.test.store.TestDataUtils; +import org.h2.test.store.TestDefrag; +import org.h2.test.store.TestFreeSpace; +import org.h2.test.store.TestKillProcessWhileWriting; +import org.h2.test.store.TestMVRTree; +import org.h2.test.store.TestMVStore; +import org.h2.test.store.TestMVStoreBenchmark; +import org.h2.test.store.TestMVStoreConcurrent; +import org.h2.test.store.TestMVStoreStopCompact; +import org.h2.test.store.TestMVStoreTool; +import org.h2.test.store.TestMVTableEngine; +import org.h2.test.store.TestObjectDataType; +import org.h2.test.store.TestRandomMapOps; +import org.h2.test.store.TestSpinLock; +import org.h2.test.store.TestStreamStore; +import org.h2.test.store.TestTransactionStore; +import org.h2.test.synth.TestBtreeIndex; +import org.h2.test.synth.TestConcurrentUpdate; +import org.h2.test.synth.TestCrashAPI; +import org.h2.test.synth.TestDiskFull; +import org.h2.test.synth.TestFuzzOptimizations; +import org.h2.test.synth.TestHaltApp; +import org.h2.test.synth.TestJoin; +import org.h2.test.synth.TestKill; +import org.h2.test.synth.TestKillRestart; +import org.h2.test.synth.TestKillRestartMulti; +import org.h2.test.synth.TestLimit; +import org.h2.test.synth.TestMultiThreaded; +import org.h2.test.synth.TestNestedJoins; +import org.h2.test.synth.TestOuterJoins; +import org.h2.test.synth.TestRandomCompare; +import org.h2.test.synth.TestRandomSQL; +import org.h2.test.synth.TestTimer; +import org.h2.test.synth.sql.TestSynth; +import org.h2.test.synth.thread.TestMulti; +import org.h2.test.unit.TestAnsCompression; +import org.h2.test.unit.TestAutoReconnect; +import org.h2.test.unit.TestBinaryArithmeticStream; +import org.h2.test.unit.TestBinaryOperation; +import org.h2.test.unit.TestBitStream; +import org.h2.test.unit.TestBnf; +import org.h2.test.unit.TestCache; +import org.h2.test.unit.TestCharsetCollator; +import org.h2.test.unit.TestCollation; +import org.h2.test.unit.TestCompress; +import org.h2.test.unit.TestConcurrentJdbc; +import org.h2.test.unit.TestConnectionInfo; +import org.h2.test.unit.TestDate; +import org.h2.test.unit.TestDateIso8601; +import org.h2.test.unit.TestDateTimeUtils; +import org.h2.test.unit.TestDbException; +import org.h2.test.unit.TestExit; +import org.h2.test.unit.TestFile; +import org.h2.test.unit.TestFileLock; +import org.h2.test.unit.TestFileLockProcess; +import org.h2.test.unit.TestFileSystem; +import org.h2.test.unit.TestFtp; +import org.h2.test.unit.TestGeometryUtils; +import org.h2.test.unit.TestIntArray; +import org.h2.test.unit.TestIntPerfectHash; +import org.h2.test.unit.TestInterval; +import org.h2.test.unit.TestJmx; +import org.h2.test.unit.TestJsonUtils; +import org.h2.test.unit.TestKeywords; +import org.h2.test.unit.TestLocale; +import org.h2.test.unit.TestMVTempResult; +import org.h2.test.unit.TestMathUtils; +import org.h2.test.unit.TestMemoryUnmapper; +import org.h2.test.unit.TestMode; +import org.h2.test.unit.TestNetUtils; +import org.h2.test.unit.TestObjectDeserialization; +import org.h2.test.unit.TestOverflow; +import org.h2.test.unit.TestPageStoreCoverage; +import org.h2.test.unit.TestPattern; +import org.h2.test.unit.TestPerfectHash; +import org.h2.test.unit.TestPgServer; +import org.h2.test.unit.TestReader; +import org.h2.test.unit.TestRecovery; +import org.h2.test.unit.TestReopen; +import org.h2.test.unit.TestSampleApps; +import org.h2.test.unit.TestScriptReader; +import org.h2.test.unit.TestSecurity; +import org.h2.test.unit.TestShell; +import org.h2.test.unit.TestSort; +import org.h2.test.unit.TestStreams; +import org.h2.test.unit.TestStringCache; +import org.h2.test.unit.TestStringUtils; +import org.h2.test.unit.TestTimeStampWithTimeZone; +import org.h2.test.unit.TestTools; +import org.h2.test.unit.TestTraceSystem; +import org.h2.test.unit.TestUpgrade; +import org.h2.test.unit.TestUtils; +import org.h2.test.unit.TestValue; +import org.h2.test.unit.TestValueMemory; +import org.h2.test.utils.OutputCatcher; +import org.h2.test.utils.SelfDestructor; +import org.h2.tools.DeleteDbFiles; +import org.h2.tools.Server; +import org.h2.util.AbbaLockingDetector; +import org.h2.util.Profiler; +import org.h2.util.StringUtils; +import org.h2.util.Task; +import org.h2.util.ThreadDeadlockDetector; +import org.h2.util.Utils; + +/** + * The main test application. JUnit is not used because loops are easier to + * write in regular java applications (most tests are ran multiple times using + * different settings). + */ +public class TestAll { + + static { + // Locale.setDefault(new Locale("ru", "ru")); + } + +/* + +PIT test: +java org.pitest.mutationtest.MutationCoverageReport +--reportDir data --targetClasses org.h2.dev.store.btree.StreamStore* +--targetTests org.h2.test.store.TestStreamStore +--sourceDirs src/test,src/tools + +Dump heap on out of memory: +-XX:+HeapDumpOnOutOfMemoryError + +Random test: +java15 +cd h2database/h2/bin +del *.db +start cmd /k "java -cp .;%H2DRIVERS% org.h2.test.TestAll join >testJoin.txt" +start cmd /k "java -cp . org.h2.test.TestAll synth >testSynth.txt" +start cmd /k "java -cp . org.h2.test.TestAll all >testAll.txt" +start cmd /k "java -cp . org.h2.test.TestAll random >testRandom.txt" +start cmd /k "java -cp . org.h2.test.TestAll btree >testBtree.txt" +start cmd /k "java -cp . org.h2.test.TestAll halt >testHalt.txt" +java -cp . org.h2.test.TestAll crash >testCrash.txt + +java org.h2.test.TestAll timer + +*/ + + /** + * Set to true if any of the tests fail. Used to return an error code from + * the whole program. + */ + static boolean atLeastOneTestFailed; + + /** + * If the test should run with many rows. + */ + public boolean big; + + /** + * If remote database connections should be used. + */ + public boolean networked; + + /** + * If in-memory databases should be used. + */ + public boolean memory; + + /** + * If code coverage is enabled. + */ + public boolean codeCoverage; + + /** + * If lazy queries should be used. + */ + public boolean lazy; + + /** + * The cipher to use (null for unencrypted). + */ + public String cipher; + + /** + * The file trace level value to use. + */ + public int traceLevelFile; + + /** + * If test trace information should be written (for debugging only). + */ + public boolean traceTest; + + /** + * If testing on Google App Engine. + */ + public boolean googleAppEngine; + + /** + * If a small cache and a low number for MAX_MEMORY_ROWS should be used. + */ + public boolean diskResult; + + /** + * Test using the recording file system. + */ + public boolean reopen; + + /** + * Test the split file system. + */ + public boolean splitFileSystem; + + /** + * If only fast CI tests should be run. + */ + public boolean ci; + + /** + * the vmlens.com race condition tool + */ + public boolean vmlens; + + /** + * The lock timeout to use + */ + public int lockTimeout = 50; + + /** + * If the transaction log should be kept small (that is, the log should be + * switched early). + */ + boolean smallLog; + + /** + * If SSL should be used for remote connections. + */ + boolean ssl; + + /** + * If MAX_MEMORY_UNDO=3 should be used. + */ + boolean diskUndo; + + /** + * If TRACE_LEVEL_SYSTEM_OUT should be set to 2 (for debugging only). + */ + boolean traceSystemOut; + + /** + * If the tests should run forever. + */ + boolean endless; + + /** + * The THROTTLE value to use. + */ + public int throttle; + + /** + * The THROTTLE value to use by default. + */ + int throttleDefault = Integer.parseInt(System.getProperty("throttle", "0")); + + /** + * If the test should stop when the first error occurs. + */ + boolean stopOnError; + + /** + * The cache type. + */ + String cacheType; + + /** If not null the database should be opened with the collation parameter */ + public String collation; + + + /** + * The AB-BA locking detector. + */ + AbbaLockingDetector abbaLockingDetector; + + /** + * The list of tests. + */ + ArrayList tests = new ArrayList<>(); + + private Server server; + + HashSet excludedTests = new HashSet<>(); + + /** + * The map of executed tests to detect not executed tests. + * Boolean value is 'false' for a disabled test. + */ + HashMap, Boolean> executedTests = new HashMap<>(); + + /** + * Run all tests. + * + * @param args the command line arguments + */ + public static void main(String... args) throws Exception { + OutputCatcher catcher = OutputCatcher.start(); + run(args); + catcher.stop(); + catcher.writeTo("Test Output", "docs/html/testOutput.html"); + if (atLeastOneTestFailed) { + System.exit(1); + } + } + + private static void run(String... args) throws Exception { + SelfDestructor.startCountdown(4 * 60); + long time = System.nanoTime(); + printSystemInfo(); + + // use lower values, to better test those cases, + // and (for delays) to speed up the tests + + System.setProperty("h2.maxMemoryRows", "100"); + + System.setProperty("h2.delayWrongPasswordMin", "0"); + System.setProperty("h2.delayWrongPasswordMax", "0"); + System.setProperty("h2.useThreadContextClassLoader", "true"); + + // System.setProperty("h2.modifyOnWrite", "true"); + + // speedup + // System.setProperty("h2.syncMethod", ""); + +/* + +recovery tests with small freeList pages, page size 64 + +reopen org.h2.test.unit.TestPageStore +-Xmx1500m -D reopenOffset=3 -D reopenShift=1 + +power failure test +power failure test: larger binaries and additional index. +power failure test with randomly generating / dropping indexes and tables. + +drop table test; +create table test(id identity, name varchar(100) default space(100)); +@LOOP 10 insert into test select null, null from system_range(1, 100000); +delete from test; + +documentation: review package and class level javadocs +documentation: rolling review at main.html + +------------- + +kill a test: +kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` + +*/ + TestAll test = new TestAll(); + if (args.length > 0) { + if ("ci".equals(args[0])) { + test.ci = true; + test.testAll(args, 1); + } else if ("vmlens".equals(args[0])) { + test.vmlens = true; + test.testAll(args, 1); + } else if ("reopen".equals(args[0])) { + System.setProperty("h2.delayWrongPasswordMin", "0"); + System.setProperty("h2.analyzeAuto", "100"); + System.setProperty("h2.pageSize", "64"); + System.setProperty("h2.reopenShift", "5"); + FilePathRec.register(); + test.reopen = true; + TestReopen reopen = new TestReopen(); + reopen.init(); + FilePathRec.setRecorder(reopen); + test.runTests(); + } else if ("crash".equals(args[0])) { + test.endless = true; + new TestCrashAPI().runTest(test); + } else if ("synth".equals(args[0])) { + new TestSynth().runTest(test); + } else if ("kill".equals(args[0])) { + new TestKill().runTest(test); + } else if ("random".equals(args[0])) { + test.endless = true; + new TestRandomSQL().runTest(test); + } else if ("join".equals(args[0])) { + new TestJoin().runTest(test); + test.endless = true; + } else if ("btree".equals(args[0])) { + new TestBtreeIndex().runTest(test); + } else if ("all".equals(args[0])) { + test.testEverything(); + } else if ("codeCoverage".equals(args[0])) { + test.codeCoverage = true; + test.runCoverage(); + } else if ("multiThread".equals(args[0])) { + new TestMulti().runTest(test); + } else if ("halt".equals(args[0])) { + new TestHaltApp().runTest(test); + } else if ("timer".equals(args[0])) { + new TestTimer().runTest(test); + } + } else { + test.testAll(args, 0); + } + System.out.println(TestBase.formatTime(new StringBuilder(), + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - time)).append(" total").toString()); + } + + private void testAll(String[] args, int offset) throws Exception { + int l = args.length; + while (l > offset + 1) { + if ("-exclude".equals(args[offset])) { + excludedTests.add(args[offset + 1]); + offset += 2; + } else { + break; + } + } + runTests(); + if (!ci && !vmlens) { + Profiler prof = new Profiler(); + prof.depth = 16; + prof.interval = 1; + prof.startCollecting(); + TestPerformance.main("-init", "-db", "1", "-size", "1000"); + prof.stopCollecting(); + System.out.println(prof.getTop(5)); + TestPerformance.main("-init", "-db", "1", "-size", "1000"); + } + } + + /** + * Run all tests in all possible combinations. + */ + private void testEverything() throws SQLException { + for (int c = 0; c < 2; c++) { + if (c == 0) { + cipher = null; + } else { + cipher = "AES"; + } + for (int a = 0; a < 64; a++) { + smallLog = (a & 1) != 0; + big = (a & 2) != 0; + networked = (a & 4) != 0; + memory = (a & 8) != 0; + ssl = (a & 16) != 0; + diskResult = (a & 32) != 0; + for (int trace = 0; trace < 3; trace++) { + traceLevelFile = trace; + test(); + } + } + } + } + + /** + * Run the tests with a number of different settings. + */ + private void runTests() throws SQLException { + + if (Boolean.getBoolean("abba")) { + abbaLockingDetector = new AbbaLockingDetector().startCollecting(); + } + + smallLog = big = networked = memory = lazy = ssl = false; + diskResult = traceSystemOut = diskUndo = false; + traceTest = stopOnError = false; + traceLevelFile = throttle = 0; + cipher = null; + + // memory is a good match for multi-threaded, makes things happen + // faster, more chance of exposing race conditions + memory = true; + test(); + if (vmlens) { + return; + } + testAdditional(); + + // test utilities + big = !ci; + testUtils(); + big = false; + + // lazy + lazy = true; + memory = true; + test(); + lazy = false; + + // but sometimes race conditions need bigger windows + memory = false; + test(); + testAdditional(); + + networked = true; + + memory = true; + test(); + memory = false; + + lazy = true; + test(); + lazy = false; + + networked = false; + + diskUndo = true; + diskResult = true; + traceLevelFile = 3; + throttle = 1; + cacheType = "SOFT_LRU"; + cipher = "AES"; + test(); + + diskUndo = false; + diskResult = false; + traceLevelFile = 1; + throttle = 0; + cacheType = null; + cipher = null; + + if (!ci) { + traceLevelFile = 0; + smallLog = true; + networked = true; + ssl = true; + test(); + + big = true; + smallLog = false; + networked = false; + ssl = false; + traceLevelFile = 0; + test(); + testAdditional(); + + big = false; + cipher = "AES"; + test(); + cipher = null; + test(); + } + + for (Entry, Boolean> entry : executedTests.entrySet()) { + if (!entry.getValue()) { + System.out.println("Warning: test " + entry.getKey().getName() + " was not executed."); + } + } + } + + private void runCoverage() throws SQLException { + smallLog = big = networked = memory = ssl = false; + diskResult = traceSystemOut = diskUndo = false; + traceTest = stopOnError = false; + traceLevelFile = throttle = 0; + cipher = null; + + memory = true; + test(); + testAdditional(); + testUtils(); + + test(); + // testUnit(); + } + + /** + * Run all tests with the current settings. + */ + private void test() throws SQLException { + System.out.println(); + System.out.println("Test " + toString() + + " (" + Utils.getMemoryUsed() + " KB used)"); + beforeTest(); + try { + // db + addTest(new TestScript()); + addTest(new TestAlter()); + addTest(new TestAlterSchemaRename()); + addTest(new TestAutoRecompile()); + addTest(new TestBackup()); + addTest(new TestBigDb()); + addTest(new TestBigResult()); + addTest(new TestCases()); + addTest(new TestCheckpoint()); + addTest(new TestCompatibility()); + addTest(new TestCompatibilityOracle()); + addTest(new TestCompatibilitySQLServer()); + addTest(new TestCsv()); + addTest(new TestDeadlock()); + if (vmlens) { + return; + } + addTest(new TestDuplicateKeyUpdate()); + addTest(new TestEncryptedDb()); + addTest(new TestExclusive()); + addTest(new TestFullText()); + addTest(new TestFunctionOverload()); + addTest(new TestFunctions()); + addTest(new TestInit()); + addTest(new TestIndex()); + addTest(new TestIndexHints()); + addTest(new TestLargeBlob()); + addTest(new TestLinkedTable()); + addTest(new TestListener()); + addTest(new TestLob()); + addTest(new TestMergeUsing()); + addTest(new TestMultiConn()); + addTest(new TestMultiDimension()); + addTest(new TestMultiThreadedKernel()); + addTest(new TestOpenClose()); + addTest(new TestReadOnly()); + addTest(new TestRecursiveQueries()); + addTest(new TestGeneralCommonTableQueries()); + addTest(new TestAlterTableNotFound()); + addTest(new TestSelectTableNotFound()); + if (!memory) { + // requires persistent store for reconnection tests + addTest(new TestPersistentCommonTableExpressions()); + } + addTest(new TestRights()); + addTest(new TestRunscript()); + addTest(new TestSQLInjection()); + addTest(new TestSessionsLocks()); + addTest(new TestSequence()); + addTest(new TestSpaceReuse()); + addTest(new TestSpatial()); + addTest(new TestSpeed()); + addTest(new TestTableEngines()); + addTest(new TestTempTables()); + addTest(new TestTransaction()); + addTest(new TestTriggersConstraints()); + addTest(new TestTwoPhaseCommit()); + addTest(new TestView()); + addTest(new TestViewAlterTable()); + addTest(new TestViewDropView()); + addTest(new TestSynonymForTable()); + + // jdbc + addTest(new TestBatchUpdates()); + addTest(new TestCallableStatement()); + addTest(new TestCancel()); + addTest(new TestConcurrentConnectionUsage()); + addTest(new TestConnection()); + addTest(new TestDatabaseEventListener()); + addTest(new TestLobApi()); + addTest(new TestSQLXML()); + addTest(new TestManyJdbcObjects()); + addTest(new TestMetaData()); + addTest(new TestNativeSQL()); + addTest(new TestPreparedStatement()); + addTest(new TestResultSet()); + addTest(new TestStatement()); + addTest(new TestGetGeneratedKeys()); + addTest(new TestTransactionIsolation()); + addTest(new TestUpdatableResultSet()); + addTest(new TestZloty()); + addTest(new TestSetCollation()); + + // jdbcx + addTest(new TestConnectionPool()); + addTest(new TestDataSource()); + addTest(new TestXA()); + addTest(new TestXASimple()); + + // server + addTest(new TestAutoServer()); + addTest(new TestNestedLoop()); + + // mvcc & row level locking + addTest(new TestMvcc1()); + addTest(new TestMvcc2()); + addTest(new TestMvcc3()); + addTest(new TestMvcc4()); + addTest(new TestMvccMultiThreaded()); + addTest(new TestMvccMultiThreaded2()); + addTest(new TestRowLocks()); + addTest(new TestAnalyzeTableTx()); + + // synth + addTest(new TestBtreeIndex()); + addTest(new TestConcurrentUpdate()); + addTest(new TestDiskFull()); + addTest(new TestCrashAPI()); + addTest(new TestFuzzOptimizations()); + addTest(new TestLimit()); + addTest(new TestRandomCompare()); + addTest(new TestKillRestart()); + addTest(new TestKillRestartMulti()); + addTest(new TestMultiThreaded()); + addTest(new TestOuterJoins()); + addTest(new TestNestedJoins()); + + runAddedTests(); + + // serial + addTest(new TestDateStorage()); + addTest(new TestDriver()); + addTest(new TestJavaObjectSerializer()); + addTest(new TestLocale()); + addTest(new TestMemoryUsage()); + addTest(new TestMultiThread()); + addTest(new TestPowerOff()); + addTest(new TestReorderWrites()); + addTest(new TestRandomSQL()); + addTest(new TestQueryCache()); + addTest(new TestUrlJavaObjectSerializer()); + addTest(new TestWeb()); + addTest(new TestJakartaWeb()); + + // other unsafe + addTest(new TestOptimizations()); + addTest(new TestOutOfMemory()); + addTest(new TestIgnoreCatalogs()); + + + runAddedTests(1); + } finally { + afterTest(); + } + } + + /** + * Run additional tests. + */ + private void testAdditional() { + if (networked) { + throw new RuntimeException("testAdditional() is not allowed in networked mode"); + } + + addTest(new TestMVTableEngine()); + addTest(new TestAutoReconnect()); + addTest(new TestBnf()); + addTest(new TestCache()); + addTest(new TestCollation()); + addTest(new TestCompress()); + addTest(new TestConnectionInfo()); + addTest(new TestExit()); + addTest(new TestFileLock()); + addTest(new TestJmx()); + addTest(new TestMultiThreadedKernel()); + addTest(new TestPageStoreCoverage()); + addTest(new TestPgServer()); + addTest(new TestRecovery()); + addTest(new RecoverLobTest()); + addTest(createTest("org.h2.test.unit.TestServlet")); + addTest(createTest("org.h2.test.unit.TestJakartaServlet")); + addTest(new TestTimeStampWithTimeZone()); + addTest(new TestValue()); + + runAddedTests(); + + addTest(new TestCluster()); + addTest(new TestFileLockProcess()); + addTest(new TestDefrag()); + addTest(new TestTools()); + addTest(new TestSampleApps()); + addTest(new TestSubqueryPerformanceOnLazyExecutionMode()); + + runAddedTests(1); + } + + /** + * Run tests for utilities. + */ + private void testUtils() { + System.out.println(); + System.out.println("Test utilities (" + Utils.getMemoryUsed() + " KB used)"); + + // mv store + addTest(new TestCacheConcurrentLIRS()); + addTest(new TestCacheLIRS()); + addTest(new TestCacheLongKeyLIRS()); + addTest(new TestDataUtils()); + addTest(new TestFreeSpace()); + addTest(new TestKillProcessWhileWriting()); + addTest(new TestMVRTree()); + addTest(new TestMVStore()); + addTest(new TestMVStoreBenchmark()); + addTest(new TestMVStoreStopCompact()); + addTest(new TestMVStoreTool()); + addTest(new TestObjectDataType()); + addTest(new TestRandomMapOps()); + addTest(new TestSpinLock()); + addTest(new TestStreamStore()); + addTest(new TestTransactionStore()); + addTest(new TestMVTempResult()); + + // unit + addTest(new TestConcurrentJdbc()); + addTest(new TestAnsCompression()); + addTest(new TestBinaryArithmeticStream()); + addTest(new TestBinaryOperation()); + addTest(new TestBitStream()); + addTest(new TestCharsetCollator()); + addTest(new TestDateIso8601()); + addTest(new TestDbException()); + addTest(new TestFile()); + addTest(new TestFileSystem()); + addTest(new TestFtp()); + addTest(new TestGeometryUtils()); + addTest(new TestInterval()); + addTest(new TestIntArray()); + addTest(new TestIntPerfectHash()); + addTest(new TestJsonUtils()); + addTest(new TestKeywords()); + addTest(new TestMathUtils()); + addTest(new TestMemoryUnmapper()); + addTest(new TestMode()); + addTest(new TestObjectDeserialization()); + addTest(new TestOverflow()); + addTest(new TestPerfectHash()); + addTest(new TestReader()); + addTest(new TestScriptReader()); + addTest(new TestSecurity()); + addTest(new TestShell()); + addTest(new TestSort()); + addTest(new TestStreams()); + addTest(new TestStringUtils()); + addTest(new TestTraceSystem()); + addTest(new TestUtils()); + addTest(new TestUpgrade()); + + runAddedTests(); + + // serial + addTest(new TestDate()); + addTest(new TestDateTimeUtils()); + addTest(new TestMVStoreConcurrent()); + addTest(new TestNetUtils()); + addTest(new TestPattern()); + addTest(new TestStringCache()); + addTest(new TestValueMemory()); + addTest(new TestAuthentication()); + + runAddedTests(1); + } + + private void addTest(TestBase test) { + if (excludedTests.contains(test.getClass().getName())) { + return; + } + // tests.add(test); + // run directly for now, because concurrently running tests + // fails on Raspberry Pi quite often (seems to be a JVM problem) + + // event queue watchdog for tests that get stuck when running in Jenkins + final java.util.Timer watchdog = new java.util.Timer(); + // 5 minutes + watchdog.schedule(new TimerTask() { + @Override + public void run() { + ThreadDeadlockDetector.dumpAllThreadsAndLocks("test watchdog timed out"); + } + }, 5 * 60 * 1000); + try { + test.runTest(this); + } finally { + watchdog.cancel(); + } + } + + private void runAddedTests() { + int threadCount = ManagementFactory.getOperatingSystemMXBean().getAvailableProcessors(); + // threadCount = 2; + runAddedTests(threadCount); + } + + private void runAddedTests(int threadCount) { + Task[] tasks = new Task[threadCount]; + for (int i = 0; i < threadCount; i++) { + Task t = new Task() { + @Override + public void call() throws Exception { + while (true) { + TestBase test; + synchronized (tests) { + if (tests.isEmpty()) { + break; + } + test = tests.remove(0); + } + if (!excludedTests.contains(test.getClass().getName())) { + test.runTest(TestAll.this); + } + } + } + }; + t.execute(); + tasks[i] = t; + } + for (Task t : tasks) { + t.get(); + } + } + + private static TestBase createTest(String className) { + try { + Class clazz = Class.forName(className); + return (TestBase) clazz.getDeclaredConstructor().newInstance(); + } catch (Exception | NoClassDefFoundError e) { + // ignore + TestBase.printlnWithTime(0, className + " class not found"); + } + return new TestBase() { + + @Override + public void test() throws Exception { + // ignore + } + + }; + } + + /** + * This method is called before a complete set of tests is run. It deletes + * old database files in the test directory and trace files. It also starts + * a TCP server if the test uses remote connections. + */ + public void beforeTest() throws SQLException { + Driver.load(); + FileUtils.deleteRecursive(TestBase.BASE_TEST_DIR, true); + DeleteDbFiles.execute(TestBase.BASE_TEST_DIR, null, true); + FileUtils.deleteRecursive("trace.db", false); + if (networked) { + String[] args = ssl ? new String[] { "-ifNotExists", "-tcpSSL" } : new String[] { "-ifNotExists" }; + server = Server.createTcpServer(args); + try { + server.start(); + } catch (SQLException e) { + System.out.println("FAIL: can not start server (may already be running)"); + server = null; + } + } + } + + /** + * Stop the server if it was started. + */ + public void afterTest() { + if (networked && server != null) { + server.stop(); + } + FileUtils.deleteRecursive("trace.db", true); + FileUtils.deleteRecursive(TestBase.BASE_TEST_DIR, true); + } + + public int getPort() { + return server == null ? 9192 : server.getPort(); + } + + /** + * Print system information. + */ + public static void printSystemInfo() { + Properties prop = System.getProperties(); + System.out.println("H2 " + Constants.FULL_VERSION + + " @ " + new java.sql.Timestamp(System.currentTimeMillis()).toString()); + System.out.println("Java " + + prop.getProperty("java.runtime.version") + ", " + + prop.getProperty("java.vm.name")+", " + + prop.getProperty("java.vendor") + ", " + + prop.getProperty("sun.arch.data.model")); + System.out.println( + prop.getProperty("os.name") + ", " + + prop.getProperty("os.arch")+", "+ + prop.getProperty("os.version")+", "+ + prop.getProperty("sun.os.patch.level")+", "+ + prop.getProperty("file.separator")+" "+ + prop.getProperty("path.separator")+" "+ + StringUtils.javaEncode(prop.getProperty("line.separator")) + " " + + prop.getProperty("user.country") + " " + + prop.getProperty("user.language") + " " + + prop.getProperty("user.timezone") + " " + + prop.getProperty("user.variant")+" "+ + prop.getProperty("file.encoding")); + } + + @Override + public String toString() { + StringBuilder buff = new StringBuilder(); + appendIf(buff, lazy, "lazy"); + appendIf(buff, big, "big"); + appendIf(buff, networked, "net"); + appendIf(buff, memory, "memory"); + appendIf(buff, codeCoverage, "codeCoverage"); + appendIf(buff, cipher != null, cipher); + appendIf(buff, cacheType != null, cacheType); + appendIf(buff, smallLog, "smallLog"); + appendIf(buff, ssl, "ssl"); + appendIf(buff, diskUndo, "diskUndo"); + appendIf(buff, diskResult, "diskResult"); + appendIf(buff, traceSystemOut, "traceSystemOut"); + appendIf(buff, endless, "endless"); + appendIf(buff, traceLevelFile > 0, "traceLevelFile"); + appendIf(buff, throttle > 0, "throttle:" + throttle); + appendIf(buff, traceTest, "traceTest"); + appendIf(buff, stopOnError, "stopOnError"); + appendIf(buff, splitFileSystem, "split"); + appendIf(buff, collation != null, collation); + return buff.toString(); + } + + private static void appendIf(StringBuilder buff, boolean flag, String text) { + if (flag) { + buff.append(text); + buff.append(' '); + } + } + +} diff --git a/h2/src/test/org/h2/test/TestAllJunit.java b/h2/src/test/org/h2/test/TestAllJunit.java new file mode 100644 index 0000000..2ebc55a --- /dev/null +++ b/h2/src/test/org/h2/test/TestAllJunit.java @@ -0,0 +1,23 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test; + +import org.junit.jupiter.api.Test; + +/** + * This class is a bridge between JUnit and the custom test framework + * used by H2. + */ +public class TestAllJunit { + + /** + * Run all the fast tests. + */ + @Test + public void testCI() throws Exception { + TestAll.main("ci"); + } +} diff --git a/h2/src/test/org/h2/test/TestBase.java b/h2/src/test/org/h2/test/TestBase.java new file mode 100644 index 0000000..c0fdffb --- /dev/null +++ b/h2/src/test/org/h2/test/TestBase.java @@ -0,0 +1,1722 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.io.Reader; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.Objects; +import java.util.SimpleTimeZone; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +import org.h2.engine.SessionLocal; +import org.h2.jdbc.JdbcConnection; +import org.h2.message.DbException; +import org.h2.mvstore.MVStoreException; +import org.h2.store.fs.FilePath; +import org.h2.store.fs.FileUtils; +import org.h2.test.utils.ResultVerifier; +import org.h2.util.StringUtils; +import org.h2.util.Utils; + +/** + * The base class for all tests. + */ +public abstract class TestBase { + + /** + * The base directory. + */ + public static final String BASE_TEST_DIR = "./data"; + + /** + * An id used to create unique file names. + */ + protected static int uniqueId; + + /** + * The temporary directory. + */ + private static final String TEMP_DIR = "./data/temp"; + + /** + * The base directory to write test databases. + */ + private static String baseDir = getTestDir(""); + + /** + * The maximum size of byte array. + */ + private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + + /** + * The test configuration. + */ + public TestAll config; + + /** + * The time when the test was started. + */ + protected long start; + + private final LinkedList memory = new LinkedList<>(); + + private static final DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("HH:mm:ss"); + + /** + * Get the test directory for this test. + * + * @param name the directory name suffix + * @return the test directory + */ + public static String getTestDir(String name) { + return BASE_TEST_DIR + "/test" + name; + } + + /** + * Initialize the test configuration using the default settings. + * + * @return itself + */ + public TestBase init() throws Exception { + return init(new TestAll()); + } + + /** + * Initialize the test configuration. + * + * @param conf the configuration + * @return itself + */ + public TestBase init(TestAll conf) throws Exception { + baseDir = getTestDir(""); + FileUtils.createDirectories(baseDir); + System.setProperty("java.io.tmpdir", TEMP_DIR); + this.config = conf; + return this; + } + + /** + * This method is initializes the test, runs the test by calling the test() + * method, and prints status information. It also catches exceptions so that + * the tests can continue. + * + * @param conf the test configuration + */ + public void runTest(TestAll conf) { + if (conf.abbaLockingDetector != null) { + conf.abbaLockingDetector.reset(); + } + try { + init(conf); + if (!isEnabled()) { + conf.executedTests.putIfAbsent(getClass(), false); + return; + } + conf.executedTests.put(getClass(), true); + start = System.nanoTime(); + test(); + println(""); + } catch (Throwable e) { + println("FAIL " + e.toString()); + logError("FAIL ("+conf+") " + e.toString(), e); + if (config.stopOnError) { + throw new AssertionError("ERROR"); + } + TestAll.atLeastOneTestFailed = true; + if (e instanceof OutOfMemoryError) { + throw (OutOfMemoryError) e; + } + } + } + + /** + * Get the password to use to login for the given user password. The file + * password is added if required. + * + * @param userPassword the password of this user + * @return the login password + */ + protected String getPassword(String userPassword) { + return config == null || config.cipher == null ? + userPassword : getFilePassword() + " " + userPassword; + } + + /** + * Get the file password (only required if file encryption is used). + * + * @return the file password + */ + protected String getFilePassword() { + return "filePassword"; + } + + /** + * Get the login password. This is usually the user password. If file + * encryption is used it is combined with the file password. + * + * @return the login password + */ + protected String getPassword() { + return getPassword("123"); + } + + /** + * Get the base directory for tests. + * If a special file system is used, the prefix is prepended. + * + * @return the directory, possibly including file system prefix + */ + public String getBaseDir() { + String dir = baseDir; + if (config != null) { + if (config.reopen) { + dir = "rec:memFS:" + dir; + } + if (config.splitFileSystem) { + dir = "split:16:" + dir; + } + } + // return "split:nioMapped:" + baseDir; + return dir; + } + + + + /** + * Get the small or the big value depending on the configuration. + * + * @param small the value to return if the current test mode is 'small' + * @param big the value to return if the current test mode is 'big' + * @return small or big, depending on the configuration + */ + protected int getSize(int small, int big) { + return config.endless ? Integer.MAX_VALUE : config.big ? big : small; + } + + protected String getUser() { + return "sa"; + } + + /** + * Write a message to system out if trace is enabled. + * + * @param x the value to write + */ + protected void trace(int x) { + trace("" + x); + } + + /** + * Write a message to system out if trace is enabled. + * + * @param s the message to write + */ + public void trace(String s) { + if (config.traceTest) { + println(s); + } + } + + /** + * Print how much memory is currently used. + */ + protected void traceMemory() { + if (config.traceTest) { + trace("mem=" + getMemoryUsed()); + } + } + + /** + * Print the currently used memory, the message and the given time in + * milliseconds. + * + * @param s the message + * @param time the time in millis + */ + public void printTimeMemory(String s, long time) { + if (config.big) { + Runtime rt = Runtime.getRuntime(); + long memNow = rt.totalMemory() - rt.freeMemory(); + println(memNow / 1024 / 1024 + " MB: " + s + " ms: " + time); + } + } + + /** + * Get the number of megabytes heap memory in use. + * + * @return the used megabytes + */ + public static int getMemoryUsed() { + return (int) (getMemoryUsedBytes() / 1024 / 1024); + } + + /** + * Get the number of bytes heap memory in use. + * + * @return the used bytes + */ + public static long getMemoryUsedBytes() { + Runtime rt = Runtime.getRuntime(); + long memory = Long.MAX_VALUE; + for (int i = 0; i < 8; i++) { + rt.gc(); + long memNow = rt.totalMemory() - rt.freeMemory(); + if (memNow >= memory) { + break; + } + memory = memNow; + } + return memory; + } + + /** + * Called if the test reached a point that was not expected. + * + * @throws AssertionError always throws an AssertionError + */ + public void fail() { + fail("Failure"); + } + + /** + * Called if the test reached a point that was not expected. + * + * @param string the error message + * @throws AssertionError always throws an AssertionError + */ + protected void fail(String string) { + if (string.length() > 100) { + // avoid long strings with special characters, because they are slow + // to display in Eclipse + char[] data = string.toCharArray(); + for (int i = 0; i < data.length; i++) { + char c = data[i]; + if (c >= 128 || c < 32) { + data[i] = (char) ('a' + (c & 15)); + string = null; + } + } + if (string == null) { + string = new String(data); + } + } + println(string); + throw new AssertionError(string); + } + + /** + * Log an error message. + * + * @param s the message + */ + public static void logErrorMessage(String s) { + System.out.flush(); + System.err.println("ERROR: " + s + "------------------------------"); + logThrowable(s, null); + } + + /** + * Log an error message. + * + * @param s the message + * @param e the exception + */ + public static void logError(String s, Throwable e) { + if (e == null) { + e = new Exception(s); + } + System.out.flush(); + System.err.println("ERROR: " + s + " " + e.toString() + + " ------------------------------"); + e.printStackTrace(); + logThrowable(null, e); + } + + private static void logThrowable(String s, Throwable e) { + // synchronize on this class, because file locks are only visible to + // other JVMs + synchronized (TestBase.class) { + try { + // lock + FileChannel fc = FilePath.get("error.lock").open("rw"); + FileLock lock; + while (true) { + lock = fc.tryLock(); + if (lock != null) { + break; + } + Thread.sleep(10); + } + // append + FileWriter fw = new FileWriter("error.txt", true); + if (s != null) { + fw.write(s); + } + if (e != null) { + PrintWriter pw = new PrintWriter(fw); + e.printStackTrace(pw); + pw.close(); + } + fw.close(); + // unlock + lock.release(); + } catch (Throwable t) { + t.printStackTrace(); + } + } + System.err.flush(); + } + + /** + * Print a message to system out. + * + * @param s the message + */ + public void println(String s) { + long now = System.nanoTime(); + long time = TimeUnit.NANOSECONDS.toMillis(now - start); + printlnWithTime(time, getClass().getName() + ' ' + s); + } + + /** + * Print a message, prepended with the specified time in milliseconds. + * + * @param millis the time in milliseconds + * @param s the message + */ + static synchronized void printlnWithTime(long millis, String s) { + StringBuilder builder = new StringBuilder(s.length() + 19); + timeFormat.formatTo(LocalTime.now(), builder); + System.out.println(formatTime(builder.append(' '), millis).append(' ').append(s).toString()); + } + + /** + * Print the current time and a message to system out. + * + * @param s the message + */ + protected void printTime(String s) { + StringBuilder builder = new StringBuilder(s.length() + 9); + timeFormat.formatTo(LocalTime.now(), builder); + println(builder.append(' ').append(s).toString()); + } + + /** + * Format the time in the format mm:ss.123 or hh:mm:ss.123 where 123 is + * milliseconds. + * + * @param builder the string builder to append to + * @param millis the time in milliseconds, non-negative + * @return the specified string builder + */ + static StringBuilder formatTime(StringBuilder builder, long millis) { + int s = (int) (millis / 1_000); + int m = s / 60; + s %= 60; + int h = m / 60; + if (h != 0) { + builder.append(h).append(':'); + m %= 60; + } + StringUtils.appendTwoDigits(builder, m).append(':'); + StringUtils.appendTwoDigits(builder, s).append('.'); + StringUtils.appendZeroPadded(builder, 3, millis % 1_000); + return builder; + } + + /** + * @return whether this test is enabled in the current configuration + */ + public boolean isEnabled() { + return true; + } + + /** + * This method will be called by the test framework. + * + * @throws Exception if an exception in the test occurs + */ + public abstract void test() throws Exception; + + /** + * Only called from individual test classes main() method, + * makes sure to run the before/after stuff. + * + * @throws Exception if an exception in the test occurs + */ + public final void testFromMain() throws Exception { + config.beforeTest(); + test(); + config.afterTest(); + } + + /** + * Check if two values are equal, and if not throw an exception. + * + * @param message the message to print in case of error + * @param expected the expected value + * @param actual the actual value + * @throws AssertionError if the values are not equal + */ + public void assertEquals(String message, int expected, int actual) { + if (expected != actual) { + fail("Expected: " + expected + " actual: " + actual + " message: " + message); + } + } + + /** + * Check if two values are equal, and if not throw an exception. + * + * @param expected the expected value + * @param actual the actual value + * @throws AssertionError if the values are not equal + */ + public void assertEquals(int expected, int actual) { + if (expected != actual) { + fail("Expected: " + expected + " actual: " + actual); + } + } + + /** + * Check if two values are equal, and if not throw an exception. + * + * @param expected the expected value + * @param actual the actual value + * @throws AssertionError if the values are not equal + */ + public void assertEquals(byte[] expected, byte[] actual) { + if (expected == null || actual == null) { + assertTrue(expected == actual); + return; + } + assertEquals(expected.length, actual.length); + for (int i = 0; i < expected.length; i++) { + if (expected[i] != actual[i]) { + fail("[" + i + "]: expected: " + (int) expected[i] + + " actual: " + (int) actual[i]); + } + } + } + + /** + * Check if two values are equal, and if not throw an exception. + * + * @param expected the expected value + * @param actual the actual value + * @throws AssertionError if the values are not equal + */ + public void assertEquals(java.util.Date expected, java.util.Date actual) { + if (!Objects.equals(expected, actual)) { + DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + SimpleTimeZone gmt = new SimpleTimeZone(0, "Z"); + df.setTimeZone(gmt); + fail("Expected: " + + (expected != null ? df.format(expected) : "null") + + " actual: " + + (actual != null ? df.format(actual) : "null")); + } + } + + /** + * Check if two arrays are equal, and if not throw an exception. + * If some of the elements in the arrays are themselves arrays this + * check is called recursively. + * + * @param expected the expected value + * @param actual the actual value + * @throws AssertionError if the values are not equal + */ + public void assertEquals(Object[] expected, Object[] actual) { + if (expected == null || actual == null) { + assertTrue(expected == actual); + return; + } + assertEquals(expected.length, actual.length); + for (int i = 0; i < expected.length; i++) { + if (expected[i] == null || actual[i] == null) { + if (expected[i] != actual[i]) { + fail("[" + i + "]: expected: " + expected[i] + " actual: " + actual[i]); + } + } else if (expected[i] instanceof Object[] && actual[i] instanceof Object[]) { + assertEquals((Object[]) expected[i], (Object[]) actual[i]); + } else if (!expected[i].equals(actual[i])) { + fail("[" + i + "]: expected: " + expected[i] + " actual: " + actual[i]); + } + } + } + + /** + * Check if two values are equal, and if not throw an exception. + * + * @param expected the expected value + * @param actual the actual value + * @throws AssertionError if the values are not equal + */ + public void assertEquals(Object expected, Object actual) { + if (!Objects.equals(expected, actual)) { + fail(" expected: " + expected + " actual: " + actual); + } + } + + /** + * Check if two readers are equal, and if not throw an exception. + * + * @param expected the expected value + * @param actual the actual value + * @param len the maximum length, or -1 + * @throws AssertionError if the values are not equal + */ + protected void assertEqualReaders(Reader expected, Reader actual, int len) + throws IOException { + for (int i = 0; len < 0 || i < len; i++) { + int ce = expected.read(); + int ca = actual.read(); + assertEquals("pos:" + i, ce, ca); + if (ce == -1) { + break; + } + } + expected.close(); + actual.close(); + } + + /** + * Check if two streams are equal, and if not throw an exception. + * + * @param expected the expected value + * @param actual the actual value + * @param len the maximum length, or -1 + * @throws AssertionError if the values are not equal + */ + protected void assertEqualStreams(InputStream expected, InputStream actual, + int len) throws IOException { + // this doesn't actually read anything - just tests reading 0 bytes + actual.read(new byte[0]); + expected.read(new byte[0]); + actual.read(new byte[10], 3, 0); + expected.read(new byte[10], 0, 0); + + for (int i = 0; len < 0 || i < len; i++) { + int ca = actual.read(); + actual.read(new byte[0]); + int ce = expected.read(); + if (ca != ce) { + assertEquals("Error at index " + i, ce, ca); + } + if (ca == -1) { + break; + } + } + actual.read(new byte[10], 3, 0); + expected.read(new byte[10], 0, 0); + actual.read(new byte[0]); + expected.read(new byte[0]); + actual.close(); + expected.close(); + } + + /** + * Check if two values are equal, and if not throw an exception. + * + * @param message the message to use if the check fails + * @param expected the expected value + * @param actual the actual value + * @throws AssertionError if the values are not equal + */ + protected void assertEquals(String message, String expected, String actual) { + if (expected == null && actual == null) { + return; + } else if (expected == null || actual == null) { + fail("Expected: " + expected + " Actual: " + actual + " " + message); + } else if (!expected.equals(actual)) { + int al = expected.length(); + int bl = actual.length(); + for (int i = 0; i < expected.length(); i++) { + String s = expected.substring(0, i); + if (!actual.startsWith(s)) { + expected = expected.substring(0, i) + "<*>" + expected.substring(i); + if (al > 20) { + expected = "@" + i + " " + expected; + } + break; + } + } + if (al > 4000) { + expected = expected.substring(0, 4000); + } + if (bl > 4000) { + actual = actual.substring(0, 4000); + } + fail("Expected: " + expected + " (" + al + ") actual: " + actual + + " (" + bl + ") " + message); + } + } + + /** + * Check if two values are equal, and if not throw an exception. + * + * @param expected the expected value + * @param actual the actual value + * @throws AssertionError if the values are not equal + */ + protected void assertEquals(String expected, String actual) { + assertEquals("", expected, actual); + } + + /** + * Check if two result sets are equal, and if not throw an exception. + * + * @param message the message to use if the check fails + * @param rs0 the first result set + * @param rs1 the second result set + * @throws AssertionError if the values are not equal + */ + protected void assertEquals(String message, ResultSet rs0, ResultSet rs1) + throws SQLException { + ResultSetMetaData meta = rs0.getMetaData(); + int columns = meta.getColumnCount(); + assertEquals(columns, rs1.getMetaData().getColumnCount()); + while (rs0.next()) { + assertTrue(message, rs1.next()); + for (int i = 0; i < columns; i++) { + assertEquals(message, rs0.getString(i + 1), rs1.getString(i + 1)); + } + } + assertFalse(message, rs0.next()); + assertFalse(message, rs1.next()); + } + + /** + * Check if two objects are the same, and if not throw an exception. + * + * @param expected the expected value + * @param actual the actual value + * @throws AssertionError if the objects are not the same + */ + public void assertSame(Object expected, Object actual) { + if (expected != actual) { + fail(" expected: " + expected + " != actual: " + actual); + } + } + + /** + * Check if the first value is larger or equal than the second value, and if + * not throw an exception. + * + * @param a the first value + * @param b the second value (must be smaller than the first value) + * @throws AssertionError if the first value is smaller + */ + protected void assertSmaller(long a, long b) { + if (a >= b) { + fail("a: " + a + " is not smaller than b: " + b); + } + } + + /** + * Check that a result contains the given substring. + * + * @param result the result value + * @param contains the term that should appear in the result + * @throws AssertionError if the term was not found + */ + protected void assertContains(String result, String contains) { + if (result.indexOf(contains) < 0) { + fail(result + " does not contain: " + contains); + } + } + + /** + * Check that a text starts with the expected characters.. + * + * @param text the text + * @param expectedStart the expected prefix + * @throws AssertionError if the text does not start with the expected + * characters + */ + protected void assertStartsWith(String text, String expectedStart) { + if (!text.startsWith(expectedStart)) { + fail("[" + text + "] does not start with: [" + expectedStart + "]"); + } + } + + /** + * Check if two values are equal, and if not throw an exception. + * + * @param expected the expected value + * @param actual the actual value + * @throws AssertionError if the values are not equal + */ + protected void assertEquals(long expected, long actual) { + if (expected != actual) { + fail("Expected: " + expected + " actual: " + actual); + } + } + + /** + * Check if two values are equal, and if not throw an exception. + * + * @param expected the expected value + * @param actual the actual value + * @throws AssertionError if the values are not equal + */ + protected void assertEquals(double expected, double actual) { + if (expected != actual) { + if (Double.isNaN(expected) && Double.isNaN(actual)) { + // if both a NaN, then there is no error + } else { + fail("Expected: " + expected + " actual: " + actual); + } + } + } + + /** + * Check if two values are equal, and if not throw an exception. + * + * @param expected the expected value + * @param actual the actual value + * @throws AssertionError if the values are not equal + */ + protected void assertEquals(float expected, float actual) { + if (expected != actual) { + if (Float.isNaN(expected) && Float.isNaN(actual)) { + // if both a NaN, then there is no error + } else { + fail("Expected: " + expected + " actual: " + actual); + } + } + } + + /** + * Check if two values are equal, and if not throw an exception. + * + * @param expected the expected value + * @param actual the actual value + * @throws AssertionError if the values are not equal + */ + protected void assertEquals(boolean expected, boolean actual) { + if (expected != actual) { + fail("Boolean expected: " + expected + " actual: " + actual); + } + } + + /** + * Check that the passed boolean is true. + * + * @param condition the condition + * @throws AssertionError if the condition is false + */ + public void assertTrue(boolean condition) { + if (!condition) { + fail("Expected: true got: false"); + } + } + + /** + * Check that the passed object is null. + * + * @param obj the object + * @throws AssertionError if the condition is false + */ + public void assertNull(Object obj) { + if (obj != null) { + fail("Expected: null got: " + obj); + } + } + + /** + * Check that the passed object is not null. + * + * @param obj the object + * @throws AssertionError if the condition is false + */ + public void assertNotNull(Object obj) { + if (obj == null) { + fail("Expected: not null got: null"); + } + } + + /** + * Check that the passed object is not null. + * + * @param message the message to print if the condition is false + * @param obj the object + * @throws AssertionError if the condition is false + */ + public void assertNotNull(String message, Object obj) { + if (obj == null) { + fail(message); + } + } + + /** + * Check that the passed boolean is true. + * + * @param message the message to print if the condition is false + * @param condition the condition + * @throws AssertionError if the condition is false + */ + public void assertTrue(String message, boolean condition) { + if (!condition) { + fail(message); + } + } + + /** + * Check that the passed boolean is false. + * + * @param value the condition + * @throws AssertionError if the condition is true + */ + protected void assertFalse(boolean value) { + if (value) { + fail("Expected: false got: true"); + } + } + + /** + * Check that the passed boolean is false. + * + * @param message the message to print if the condition is false + * @param value the condition + * @throws AssertionError if the condition is true + */ + protected void assertFalse(String message, boolean value) { + if (value) { + fail(message); + } + } + + /** + * Check that the result set row count matches. + * + * @param expected the number of expected rows + * @param rs the result set + * @throws AssertionError if a different number of rows have been found + */ + protected void assertResultRowCount(int expected, ResultSet rs) + throws SQLException { + int i = 0; + while (rs.next()) { + i++; + } + assertEquals(expected, i); + } + + /** + * Check that the result set of a query is exactly this value. + * + * @param stat the statement + * @param sql the SQL statement to execute + * @param expected the expected result value + * @throws AssertionError if a different result value was returned + */ + protected void assertSingleValue(Statement stat, String sql, int expected) + throws SQLException { + ResultSet rs = stat.executeQuery(sql); + assertTrue(rs.next()); + assertEquals(expected, rs.getInt(1)); + assertFalse(rs.next()); + } + + /** + * Check that the result set of a query is exactly this value. + * + * @param expected the expected result value + * @param stat the statement + * @param sql the SQL statement to execute + * @throws AssertionError if a different result value was returned + */ + protected void assertResult(String expected, Statement stat, String sql) + throws SQLException { + ResultSet rs = stat.executeQuery(sql); + if (rs.next()) { + String actual = rs.getString(1); + assertEquals(expected, actual); + } else { + assertEquals(expected, null); + } + } + + /** + * Check that executing the specified query results in the specified error. + * + * @param expectedErrorCode the expected error code + * @param stat the statement + * @param sql the SQL statement to execute + */ + protected void assertThrows(int expectedErrorCode, Statement stat, + String sql) { + try { + execute(stat, sql); + fail("Expected error: " + expectedErrorCode); + } catch (SQLException ex) { + assertEquals(expectedErrorCode, ex.getErrorCode()); + } + } + + /** + * Execute the statement. + * + * @param stat the statement + */ + public void execute(PreparedStatement stat) throws SQLException { + execute(stat, null); + } + + /** + * Execute the statement. + * + * @param stat the statement + * @param sql the SQL command + */ + protected void execute(Statement stat, String sql) throws SQLException { + boolean query = sql == null ? ((PreparedStatement) stat).execute() : + stat.execute(sql); + + if (query && config.lazy) { + try (ResultSet rs = stat.getResultSet()) { + while (rs.next()) { + // just loop + } + } + } + } + + /** + * Check if the result set meta data is correct. + * + * @param rs the result set + * @param columnCount the expected column count + * @param labels the expected column labels + * @param datatypes the expected data types + * @param precision the expected precisions + * @param scale the expected scales + */ + protected void assertResultSetMeta(ResultSet rs, int columnCount, + String[] labels, int[] datatypes, int[] precision, int[] scale) + throws SQLException { + ResultSetMetaData meta = rs.getMetaData(); + int cc = meta.getColumnCount(); + if (cc != columnCount) { + fail("result set contains " + cc + " columns not " + columnCount); + } + for (int i = 0; i < columnCount; i++) { + if (labels != null) { + String l = meta.getColumnLabel(i + 1); + if (!labels[i].equals(l)) { + fail("column label " + i + " is " + l + " not " + labels[i]); + } + } + if (datatypes != null) { + int t = meta.getColumnType(i + 1); + if (datatypes[i] != t) { + fail("column datatype " + i + " is " + t + " not " + datatypes[i] + " (prec=" + + meta.getPrecision(i + 1) + " scale=" + meta.getScale(i + 1) + ")"); + } + String typeName = meta.getColumnTypeName(i + 1); + String className = meta.getColumnClassName(i + 1); + switch (t) { + case Types.INTEGER: + assertEquals("INTEGER", typeName); + assertEquals("java.lang.Integer", className); + break; + case Types.VARCHAR: + assertEquals("CHARACTER VARYING", typeName); + assertEquals("java.lang.String", className); + break; + case Types.SMALLINT: + assertEquals("SMALLINT", typeName); + assertEquals("java.lang.Integer", className); + break; + case Types.TIMESTAMP: + assertEquals("TIMESTAMP", typeName); + assertEquals("java.sql.Timestamp", className); + break; + case Types.NUMERIC: + assertEquals("NUMERIC", typeName); + assertEquals("java.math.BigDecimal", className); + break; + default: + } + } + if (precision != null) { + int p = meta.getPrecision(i + 1); + if (precision[i] != p) { + fail("column precision " + i + " is " + p + " not " + precision[i]); + } + } + if (scale != null) { + int s = meta.getScale(i + 1); + if (scale[i] != s) { + fail("column scale " + i + " is " + s + " not " + scale[i]); + } + } + + } + } + + /** + * Check if a result set contains the expected data. + * The sort order is significant + * + * @param rs the result set + * @param data the expected data + * @param ignoreColumns columns to ignore, or {@code null} + * @throws AssertionError if there is a mismatch + */ + protected void assertResultSetOrdered(ResultSet rs, String[][] data, int[] ignoreColumns) + throws SQLException { + assertResultSet(true, rs, data, ignoreColumns); + } + + /** + * Check if a result set contains the expected data. + * The sort order is significant + * + * @param rs the result set + * @param data the expected data + * @throws AssertionError if there is a mismatch + */ + protected void assertResultSetOrdered(ResultSet rs, String[][] data) + throws SQLException { + assertResultSet(true, rs, data, null); + } + + /** + * Check if a result set contains the expected data. + * + * @param ordered if the sort order is significant + * @param rs the result set + * @param data the expected data + * @param ignoreColumns columns to ignore, or {@code null} + * @throws AssertionError if there is a mismatch + */ + private void assertResultSet(boolean ordered, ResultSet rs, String[][] data, int[] ignoreColumns) + throws SQLException { + int len = rs.getMetaData().getColumnCount(); + int rows = data.length; + if (rows == 0) { + // special case: no rows + if (rs.next()) { + fail("testResultSet expected rowCount:" + rows + " got:0"); + } + } + int len2 = data[0].length; + if (len < len2) { + fail("testResultSet expected columnCount:" + len2 + " got:" + len); + } + for (int i = 0; i < rows; i++) { + if (!rs.next()) { + fail("testResultSet expected rowCount:" + rows + " got:" + i); + } + String[] row = getData(rs, len); + if (ordered) { + String[] good = data[i]; + if (!testRow(good, row, good.length, ignoreColumns)) { + fail("testResultSet row not equal, got:\n" + formatRow(row) + + "\n" + formatRow(good)); + } + } else { + boolean found = false; + for (int j = 0; j < rows; j++) { + String[] good = data[i]; + if (testRow(good, row, good.length, ignoreColumns)) { + found = true; + break; + } + } + if (!found) { + fail("testResultSet no match for row:" + formatRow(row)); + } + } + } + if (rs.next()) { + String[] row = getData(rs, len); + fail("testResultSet expected rowcount:" + rows + " got:>=" + + (rows + 1) + " data:" + formatRow(row)); + } + } + + private static boolean testRow(String[] a, String[] b, int len, int[] ignoreColumns) { + loop: for (int i = 0; i < len; i++) { + if (ignoreColumns != null) { + for (int c : ignoreColumns) { + if (c == i) { + continue loop; + } + } + } + String sa = a[i]; + String sb = b[i]; + if (sa == null || sb == null) { + if (sa != sb) { + return false; + } + } else { + if (!sa.equals(sb)) { + return false; + } + } + } + return true; + } + + private static String[] getData(ResultSet rs, int len) throws SQLException { + String[] data = new String[len]; + for (int i = 0; i < len; i++) { + data[i] = rs.getString(i + 1); + // just check if it works + rs.getObject(i + 1); + } + return data; + } + + private static String formatRow(String[] row) { + String sb = ""; + for (String r : row) { + sb += "{" + r + "}"; + } + return "{" + sb + "}"; + } + + /** + * Simulate a database crash. This method will also close the database + * files, but the files are in a state as the power was switched off. It + * doesn't throw an exception. + * + * @param conn the database connection + */ + protected void crash(Connection conn) { + setPowerOffCount(conn, 1); + try { + conn.createStatement().execute("SET WRITE_DELAY 0"); + conn.createStatement().execute("CREATE TABLE TEST_A(ID INT)"); + fail("should be crashed already"); + } catch (SQLException e) { + // expected + } + try { + conn.close(); + } catch (SQLException e) { + // ignore + } + } + + /** + * Set the number of disk operations before power failure is simulated. + * To disable the countdown, use 0. + * + * @param conn the connection + * @param i the number of operations + */ + public static void setPowerOffCount(Connection conn, int i) { + SessionLocal session = (SessionLocal) ((JdbcConnection) conn).getSession(); + if (session != null) { + session.getDatabase().setPowerOffCount(i); + } + } + + /** + * Returns the number of disk operations before power failure is simulated. + * + * @param conn the connection + * @return the number of disk operations before power failure is simulated + */ + protected static int getPowerOffCount(Connection conn) { + SessionLocal session = (SessionLocal) ((JdbcConnection) conn).getSession(); + return session != null && !session.isClosed() ? session.getDatabase().getPowerOffCount() : 0; + } + + /** + * Read a string from the reader. This method reads until end of file. + * + * @param reader the reader + * @return the string read + */ + protected String readString(Reader reader) { + if (reader == null) { + return null; + } + StringBuilder buffer = new StringBuilder(); + try { + while (true) { + int c = reader.read(); + if (c == -1) { + break; + } + buffer.append((char) c); + } + return buffer.toString(); + } catch (Exception e) { + assertTrue(false); + return null; + } + } + + /** + * Check that a given exception is not an unexpected 'general error' + * exception. + * + * @param e the error + */ + public void assertKnownException(SQLException e) { + assertKnownException("", e); + } + + /** + * Check that a given exception is not an unexpected 'general error' + * exception. + * + * @param message the message + * @param e the exception + */ + protected void assertKnownException(String message, SQLException e) { + if (e != null && e.getSQLState().startsWith("HY000")) { + TestBase.logError("Unexpected General error " + message, e); + } + } + + /** + * Check if two values are equal, and if not throw an exception. + * + * @param expected the expected value + * @param actual the actual value + * @throws AssertionError if the values are not equal + */ + protected void assertEquals(Integer expected, Integer actual) { + if (expected == null || actual == null) { + if (expected != actual) { + assertEquals("" + expected, "" + actual); + } + } else { + assertEquals(expected.intValue(), actual.intValue()); + } + } + + /** + * Check if two databases contain the same meta data. + * + * @param stat1 the connection to the first database + * @param stat2 the connection to the second database + * @throws AssertionError if the databases don't match + */ + protected void assertEqualDatabases(Statement stat1, Statement stat2) + throws SQLException { + ResultSet rs = stat1.executeQuery( + "SELECT SETTING_VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME = 'ANALYZE_AUTO'"); + int analyzeAuto = rs.next() ? rs.getInt(1) : 0; + if (analyzeAuto > 0) { + stat1.execute("analyze"); + stat2.execute("analyze"); + } + ResultSet rs1 = stat1.executeQuery("SCRIPT simple NOPASSWORDS"); + ResultSet rs2 = stat2.executeQuery("SCRIPT simple NOPASSWORDS"); + ArrayList list1 = new ArrayList<>(); + ArrayList list2 = new ArrayList<>(); + while (rs1.next()) { + String s1 = rs1.getString(1); + s1 = removeRowCount(s1); + if (!rs2.next()) { + fail("expected: " + s1); + } + String s2 = rs2.getString(1); + s2 = removeRowCount(s2); + if (!s1.equals(s2)) { + list1.add(s1); + list2.add(s2); + } + } + for (String s : list1) { + if (!list2.remove(s)) { + fail("only found in first: " + s + " remaining: " + list2); + } + } + assertEquals("remaining: " + list2, 0, list2.size()); + assertFalse(rs2.next()); + } + + private static String removeRowCount(String scriptLine) { + int index = scriptLine.indexOf("+/-"); + if (index >= 0) { + scriptLine = scriptLine.substring(index); + } + return scriptLine; + } + + /** + * Create a new object of the calling class. + * + * @return the new test + */ + public static TestBase createCaller() { + org.h2.Driver.load(); + try { + return (TestBase) new SecurityManager() { + Class clazz = getClassContext()[2]; + }.clazz.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Get the classpath list used to execute java -cp ... + * + * @return the classpath list + */ + protected String getClassPath() { + return System.getProperty("java.class.path"); + } + + /** + * Get the path to a java executable of the current process + * + * @return the path to java + */ + public static String getJVM() { + return System.getProperty("java.home") + File.separatorChar + "bin" + + File.separator + "java"; + } + + /** + * Use up almost all memory. + * + * @param remainingKB the number of kilobytes that are not referenced + */ + protected void eatMemory(int remainingKB) { + long memoryFreeKB; + try { + while ((memoryFreeKB = Utils.getMemoryFree()) > remainingKB) { + long blockSize = Math.max((memoryFreeKB - remainingKB) / 16, 16) * 1024; + memory.add(new byte[blockSize > MAX_ARRAY_SIZE ? MAX_ARRAY_SIZE : (int) blockSize]); + } + } catch (OutOfMemoryError e) { + if (remainingKB >= 3000) { // OOM is not expected + memory.clear(); + throw e; + } + // OOM can be ignored because it's tolerable (separate process?) + } + } + + /** + * Remove the hard reference to the memory. + */ + protected void freeMemory() { + memory.clear(); + for (int i = 0; i < 5; i++) { + System.gc(); + try { + Thread.sleep(20); + } catch (InterruptedException ignore) {/**/} + } + } + + /** + * Verify the next method call on the object will throw an exception. + * + * @param the class of the object + * @param expectedExceptionClass the expected exception class to be thrown + * @param obj the object to wrap + * @return a proxy for the object + */ + protected T assertThrows(final Class expectedExceptionClass, + final T obj) { + return assertThrows((returnValue, t, m, args) -> { + if (t == null) { + throw new AssertionError("Expected an exception of type " + + expectedExceptionClass.getSimpleName() + + " to be thrown, but the method returned " + + returnValue + + " for " + formatMethodCall(m, args)); + } + if (!expectedExceptionClass.isAssignableFrom(t.getClass())) { + AssertionError ae = new AssertionError("Expected an exception of type\n" + + expectedExceptionClass.getSimpleName() + + " to be thrown, but the method under test threw an exception of type\n" + + t.getClass().getSimpleName() + + " (see in the 'Caused by' for the exception that was thrown) for " + + formatMethodCall(m, args)); + ae.initCause(t); + throw ae; + } + return false; + }, obj); + } + + private static String formatMethodCall(Method m, Object... args) { + StringBuilder builder = new StringBuilder(); + builder.append(m.getName()).append('('); + for (int i = 0; i < args.length; i++) { + Object a = args[i]; + if (i > 0) { + builder.append(", "); + } + builder.append(a == null ? "null" : a.toString()); + } + builder.append(")"); + return builder.toString(); + } + + /** + * Verify the next method call on the object will throw an exception. + * + * @param the class of the object + * @param expectedErrorCode the expected error code + * @param obj the object to wrap + * @return a proxy for the object + */ + protected T assertThrows(int expectedErrorCode, T obj) { + return assertThrows((returnValue, t, m, args) -> { + checkErrorCode(expectedErrorCode, t); + return false; + }, obj); + } + + /** + * Verify the next method call on the object will throw an exception. + * + * @param the class of the object + * @param verifier the result verifier to call + * @param obj the object to wrap + * @return a proxy for the object + */ + @SuppressWarnings("unchecked") + protected T assertThrows(final ResultVerifier verifier, final T obj) { + Class c = obj.getClass(); + InvocationHandler ih = new InvocationHandler() { + private Exception called = new Exception("No method called"); + @Override + protected void finalize() { + if (called != null) { + called.printStackTrace(System.err); + } + } + @Override + public Object invoke(Object proxy, Method method, Object[] args) + throws Exception { + try { + called = null; + Object ret = method.invoke(obj, args); + verifier.verify(ret, null, method, args); + return ret; + } catch (InvocationTargetException e) { + verifier.verify(null, e.getTargetException(), method, args); + Class retClass = method.getReturnType(); + if (!retClass.isPrimitive()) { + return null; + } + if (retClass == boolean.class) { + return false; + } else if (retClass == byte.class) { + return (byte) 0; + } else if (retClass == char.class) { + return (char) 0; + } else if (retClass == short.class) { + return (short) 0; + } else if (retClass == int.class) { + return 0; + } else if (retClass == long.class) { + return 0L; + } else if (retClass == float.class) { + return 0F; + } else if (retClass == double.class) { + return 0D; + } + return null; + } + } + }; + Class[] interfaces = c.getInterfaces(); + if (interfaces.length == 0) { + throw new RuntimeException("Can not create a proxy for the class " + + c.getSimpleName() + + " because it doesn't implement any interfaces and is final"); + } + return (T) Proxy.newProxyInstance(c.getClassLoader(), interfaces, ih); + } + + @FunctionalInterface + protected interface VoidCallable { + + /** + * call the lambda + */ + void call() throws Exception; + + } + + /** + * Assert that the lambda function throws an exception of the expected class. + * + * @param expectedExceptionClass expected exception class + * @param c lambda function + */ + protected void assertThrows(Class expectedExceptionClass, Callable c) { + try { + Object returnValue = c.call(); + throw new AssertionError("Expected an exception of type " + expectedExceptionClass.getSimpleName() + + " to be thrown, but the method returned " + returnValue); + } catch (Throwable t) { + checkException(expectedExceptionClass, t); + } + } + + /** + * Assert that the lambda function throws an exception of the expected class. + * + * @param expectedExceptionClass expected exception class + * @param c lambda function + */ + protected void assertThrows(Class expectedExceptionClass, VoidCallable c) { + try { + c.call(); + throw new AssertionError("Expected an exception of type " + expectedExceptionClass.getSimpleName() + + " to be thrown, but the method returned successfully"); + } catch (Throwable t) { + checkException(expectedExceptionClass, t); + } + } + + /** + * Assert that the lambda function throws a SQLException or DbException with the + * expected error code. + * + * @param expectedErrorCode SQL error code + * @param c lambda function + */ + protected void assertThrows(int expectedErrorCode, Callable c) { + try { + Object returnValue = c.call(); + throw new AssertionError("Expected an SQLException or DbException with error code " + expectedErrorCode + + " to be thrown, but the method returned " + returnValue); + } catch (Throwable t) { + checkErrorCode(expectedErrorCode, t); + } + } + + /** + * Assert that the lambda function throws a SQLException or DbException with the + * expected error code. + * + * @param expectedErrorCode SQL error code + * @param c lambda function + */ + protected void assertThrows(int expectedErrorCode, VoidCallable c) { + try { + c.call(); + throw new AssertionError("Expected an SQLException or DbException with error code " + expectedErrorCode + + " to be thrown, but the method returned successfully"); + } catch (Throwable t) { + checkErrorCode(expectedErrorCode, t); + } + } + + private static void checkException(Class expectedExceptionClass, Throwable t) throws AssertionError { + if (!expectedExceptionClass.isAssignableFrom(t.getClass())) { + AssertionError ae = new AssertionError("Expected an exception of type\n" + + expectedExceptionClass.getSimpleName() + " to be thrown, but an exception of type\n" + + t.getClass().getSimpleName() + " was thrown"); + ae.initCause(t); + throw ae; + } + } + + /** + * Verify that actual error code is the one expected + * @param expectedErrorCode to compare against + * @param t actual exception to extract error code from + * @throws AssertionError if code is unexpected + */ + public static void checkErrorCode(int expectedErrorCode, Throwable t) throws AssertionError { + int errorCode; + if (t instanceof DbException) { + errorCode = ((DbException) t).getErrorCode(); + } else if (t instanceof SQLException) { + errorCode = ((SQLException) t).getErrorCode(); + } else if (t instanceof MVStoreException) { + errorCode = ((MVStoreException) t).getErrorCode(); + } else { + errorCode = 0; + } + if (errorCode != expectedErrorCode) { + AssertionError ae = new AssertionError("Expected an SQLException or DbException with error code " + + expectedErrorCode + ", but got a " + + (t == null ? "null" : t.getClass().getName() + " exception " + " with error code " + errorCode)); + ae.initCause(t); + throw ae; + } + } + + /** + * Construct a stream of 20 KB that fails while reading with the provided + * exception. + * + * @param e the exception + * @return the stream + */ + public static ByteArrayInputStream createFailingStream(final Exception e) { + return new ByteArrayInputStream(new byte[20 * 1024]) { + @Override + public int read(byte[] buffer, int off, int len) { + if (this.pos > 10 * 1024) { + throwException(e); + } + return super.read(buffer, off, len); + } + }; + } + + /** + * Throw a checked exception, without having to declare the method as + * throwing a checked exception. + * + * @param e the exception to throw + */ + public static void throwException(Throwable e) { + TestBase.throwThis(e); + } + + @SuppressWarnings("unchecked") + private static void throwThis(Throwable e) throws E { + throw (E) e; + } + + /** + * Get the name of the test. + * + * @return the name of the test class + */ + public String getTestName() { + return getClass().getSimpleName(); + } +} diff --git a/h2/src/test/org/h2/test/TestDb.java b/h2/src/test/org/h2/test/TestDb.java new file mode 100644 index 0000000..5694a05 --- /dev/null +++ b/h2/src/test/org/h2/test/TestDb.java @@ -0,0 +1,223 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.h2.test.utils.SelfDestructor; +import org.h2.tools.DeleteDbFiles; + +/** + * The base class for tests that use connections to database. + */ +public abstract class TestDb extends TestBase { + + /** + * Open a database connection in admin mode. The default user name and + * password is used. + * + * @param name the database name + * @return the connection + */ + public Connection getConnection(String name) throws SQLException { + return getConnectionInternal(getURL(name, true), getUser(), + getPassword()); + } + + /** + * Open a database connection. + * + * @param name the database name + * @param user the user name to use + * @param password the password to use + * @return the connection + */ + public Connection getConnection(String name, String user, String password) + throws SQLException { + return getConnectionInternal(getURL(name, false), user, password); + } + + /** + * Get the database URL for the given database name using the current + * configuration options. + * + * @param name the database name + * @param admin true if the current user is an admin + * @return the database URL + */ + protected String getURL(String name, boolean admin) { + String url; + if (name.startsWith("jdbc:")) { + name = addOption(name, "MV_STORE", "true"); + return name; + } + if (admin) { + // name = addOption(name, "RETENTION_TIME", "10"); + // name = addOption(name, "WRITE_DELAY", "10"); + } + int idx = name.indexOf(':'); + if (idx == -1 && config.memory) { + name = "mem:" + name; + } else { + if (idx < 0 || idx > 10) { + // index > 10 if in options + name = getBaseDir() + "/" + name; + } + } + if (config.networked) { + if (config.ssl) { + url = "ssl://localhost:"+config.getPort()+"/" + name; + } else { + url = "tcp://localhost:"+config.getPort()+"/" + name; + } + } else if (config.googleAppEngine) { + url = "gae://" + name + + ";FILE_LOCK=NO;AUTO_SERVER=FALSE;DB_CLOSE_ON_EXIT=FALSE"; + } else { + url = name; + } + url = addOption(url, "MV_STORE", "true"); + url = addOption(url, "MAX_COMPACT_TIME", "0"); // to speed up tests + if (!config.memory) { + if (config.smallLog && admin) { + url = addOption(url, "MAX_LOG_SIZE", "1"); + } + } + if (config.traceSystemOut) { + url = addOption(url, "TRACE_LEVEL_SYSTEM_OUT", "2"); + } + if (config.traceLevelFile > 0 && admin) { + url = addOption(url, "TRACE_LEVEL_FILE", "" + config.traceLevelFile); + url = addOption(url, "TRACE_MAX_FILE_SIZE", "8"); + } + if (config.throttleDefault > 0) { + url = addOption(url, "THROTTLE", "" + config.throttleDefault); + } else if (config.throttle > 0) { + url = addOption(url, "THROTTLE", "" + config.throttle); + } + url = addOption(url, "LOCK_TIMEOUT", "" + config.lockTimeout); + if (config.diskUndo && admin) { + url = addOption(url, "MAX_MEMORY_UNDO", "3"); + } + if (config.big && admin) { + // force operations to disk + url = addOption(url, "MAX_OPERATION_MEMORY", "1"); + } + if (config.lazy) { + url = addOption(url, "LAZY_QUERY_EXECUTION", "1"); + } + if (config.cacheType != null && admin) { + url = addOption(url, "CACHE_TYPE", config.cacheType); + } + if (config.diskResult && admin) { + url = addOption(url, "MAX_MEMORY_ROWS", "100"); + url = addOption(url, "CACHE_SIZE", "0"); + } + if (config.cipher != null) { + url = addOption(url, "CIPHER", config.cipher); + } + if (config.collation != null) { + url = addOption(url, "COLLATION", config.collation); + } + return "jdbc:h2:" + url; + } + + private static String addOption(String url, String option, String value) { + if (url.indexOf(";" + option + "=") < 0) { + url += ";" + option + "=" + value; + } + return url; + } + + private static Connection getConnectionInternal(String url, String user, + String password) throws SQLException { + org.h2.Driver.load(); + // url += ";DEFAULT_TABLE_TYPE=1"; + // Class.forName("org.hsqldb.jdbcDriver"); + // return DriverManager.getConnection("jdbc:hsqldb:" + name, "sa", ""); + return DriverManager.getConnection(url, user, password); + } + + /** + * Delete all database files for this database. + * + * @param name the database name + */ + protected void deleteDb(String name) { + deleteDb(getBaseDir(), name); + } + + /** + * Delete all database files for a database. + * + * @param dir the directory where the database files are located + * @param name the database name + */ + protected void deleteDb(String dir, String name) { + DeleteDbFiles.execute(dir, name, true); + // ArrayList list; + // list = FileLister.getDatabaseFiles(baseDir, name, true); + // if (list.size() > 0) { + // System.out.println("Not deleted: " + list); + // } + } + + /** + * Build a child process. + * + * @param name the name + * @param childClass the class + * @param jvmArgs the argument list + * @return the process builder + */ + public ProcessBuilder buildChild(String name, Class childClass, + String... jvmArgs) { + List args = new ArrayList<>(16); + args.add(getJVM()); + Collections.addAll(args, jvmArgs); + Collections.addAll(args, "-cp", getClassPath(), + SelfDestructor.getPropertyString(1), + childClass.getName(), + "-url", getURL(name, true), + "-user", getUser(), + "-password", getPassword()); + ProcessBuilder processBuilder = new ProcessBuilder() +// .redirectError(ProcessBuilder.Redirect.INHERIT) + .redirectErrorStream(true) + .redirectOutput(ProcessBuilder.Redirect.INHERIT) + .command(args); + return processBuilder; + } + + public abstract static class Child extends TestDb { + private String url; + private String user; + private String password; + + public Child(String... args) { + for (int i = 0; i < args.length; i++) { + if ("-url".equals(args[i])) { + url = args[++i]; + } else if ("-user".equals(args[i])) { + user = args[++i]; + } else if ("-password".equals(args[i])) { + password = args[++i]; + } + SelfDestructor.startCountdown(60); + } + } + + public Connection getConnection() throws SQLException { + return getConnection(url, user, password); + } + } + +} diff --git a/h2/src/test/org/h2/test/ap/TestAnnotationProcessor.java b/h2/src/test/org/h2/test/ap/TestAnnotationProcessor.java new file mode 100644 index 0000000..16a8b2a --- /dev/null +++ b/h2/src/test/org/h2/test/ap/TestAnnotationProcessor.java @@ -0,0 +1,83 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.ap; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; + +/** + * An annotation processor for testing. + */ +public class TestAnnotationProcessor extends AbstractProcessor { + + /** + * The message key. + */ + public static final String MESSAGES_KEY = + TestAnnotationProcessor.class.getName() + "-messages"; + + @Override + public Set getSupportedAnnotationTypes() { + + for (OutputMessage outputMessage : findMessages()) { + processingEnv.getMessager().printMessage(outputMessage.kind, outputMessage.message); + } + + return Collections.emptySet(); + } + + private static List findMessages() { + final String messagesStr = System.getProperty(MESSAGES_KEY); + if (messagesStr == null || messagesStr.isEmpty()) { + return Collections.emptyList(); + } + List outputMessages = new ArrayList<>(); + + for (String msg : messagesStr.split("\\|")) { + String[] split = msg.split(","); + if (split.length == 2) { + outputMessages.add(new OutputMessage(Diagnostic.Kind.valueOf(split[0]), split[1])); + } else { + throw new IllegalStateException( + "Unable to parse messages definition for: '" + + messagesStr + "'"); + } + } + + return outputMessages; + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + return false; + } + + /** + * An output message. + */ + private static class OutputMessage { + public final Diagnostic.Kind kind; + public final String message; + + OutputMessage(Diagnostic.Kind kind, String message) { + this.kind = kind; + this.message = message; + } + } +} diff --git a/h2/src/test/org/h2/test/ap/package.html b/h2/src/test/org/h2/test/ap/package.html new file mode 100644 index 0000000..588b02b --- /dev/null +++ b/h2/src/test/org/h2/test/ap/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +An annotation processor used for testing. + +

    \ No newline at end of file diff --git a/h2/src/test/org/h2/test/auth/MyLoginModule.java b/h2/src/test/org/h2/test/auth/MyLoginModule.java new file mode 100644 index 0000000..0a899bb --- /dev/null +++ b/h2/src/test/org/h2/test/auth/MyLoginModule.java @@ -0,0 +1,61 @@ +/* + * 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: Alessandro Ventura + */ +package org.h2.test.auth; + +import java.util.Map; +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.login.LoginException; +import javax.security.auth.spi.LoginModule; + +/** + * Dummy login module used for test cases + */ +public class MyLoginModule implements LoginModule{ + + String password; + CallbackHandler callbackHandler; + + @Override + public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, + Map options) { + this.callbackHandler=callbackHandler; + password=""+options.get("password"); + } + + @Override + public boolean login() throws LoginException { + if (callbackHandler==null) { + throw new LoginException("no callbackHandler available"); + } + NameCallback nameCallback= new NameCallback("user name"); + PasswordCallback passwordCallback= new PasswordCallback("user name",false); + try { + callbackHandler.handle(new Callback[] {nameCallback,passwordCallback}); + } catch (Exception exception) { + throw new LoginException("an exception occurred during inquiry of username and password"); + } + return password.equals(new String(passwordCallback.getPassword())); + } + + @Override + public boolean logout() throws LoginException { + return true; + } + + @Override + public boolean abort() throws LoginException { + return true; + } + + @Override + public boolean commit() throws LoginException { + return true; + } +} diff --git a/h2/src/test/org/h2/test/auth/TestAuthentication.java b/h2/src/test/org/h2/test/auth/TestAuthentication.java new file mode 100644 index 0000000..68a581c --- /dev/null +++ b/h2/src/test/org/h2/test/auth/TestAuthentication.java @@ -0,0 +1,289 @@ +/* + * 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: Alessandro Ventura + */ +package org.h2.test.auth; + +import java.io.ByteArrayInputStream; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.UUID; + +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag; +import javax.security.auth.login.Configuration; +import javax.sql.DataSource; + +import org.h2.engine.ConnectionInfo; +import org.h2.engine.Database; +import org.h2.engine.Engine; +import org.h2.engine.Role; +import org.h2.engine.SessionLocal; +import org.h2.engine.User; +import org.h2.jdbcx.JdbcConnectionPool; +import org.h2.security.auth.DefaultAuthenticator; +import org.h2.security.auth.H2AuthConfig; +import org.h2.security.auth.H2AuthConfigXml; +import org.h2.security.auth.impl.AssignRealmNameRole; +import org.h2.security.auth.impl.JaasCredentialsValidator; +import org.h2.security.auth.impl.StaticRolesMapper; +import org.h2.security.auth.impl.StaticUserCredentialsValidator; +import org.h2.test.TestBase; + +/** + * Test for custom authentication. + */ +public class TestAuthentication extends TestBase { + + private static final String TESTXML = "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + + private static final String JAAS_CONFIG_NAME = "testJaasH2"; + + private String externalUserPassword; + private DefaultAuthenticator defaultAuthenticator; + private SessionLocal session; + private Database database; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + /** + * Gets external user password. + * + * @return external user password. + */ + String getExternalUserPassword() { + if (externalUserPassword == null) { + externalUserPassword = UUID.randomUUID().toString(); + } + return externalUserPassword; + } + + private static String getRealmName() { + return "testRealm"; + } + + private static String getStaticRoleName() { + return "staticRole"; + } + + private void configureAuthentication(Database database) { + defaultAuthenticator = new DefaultAuthenticator(true); + defaultAuthenticator.setAllowUserRegistration(true); + defaultAuthenticator.setCreateMissingRoles(true); + defaultAuthenticator.addRealm(getRealmName(), new JaasCredentialsValidator(JAAS_CONFIG_NAME)); + defaultAuthenticator.addRealm(getRealmName() + "_STATIC", + new StaticUserCredentialsValidator("staticuser[0-9]", "staticpassword")); + defaultAuthenticator.setUserToRolesMappers(new AssignRealmNameRole("@%s"), + new StaticRolesMapper(getStaticRoleName())); + database.setAuthenticator(defaultAuthenticator); + } + + private void configureJaas() { + final Configuration innerConfiguration = Configuration.getConfiguration(); + Configuration.setConfiguration(new Configuration() { + @Override + public AppConfigurationEntry[] getAppConfigurationEntry(String name) { + if (name.equals(JAAS_CONFIG_NAME)) { + HashMap options = new HashMap<>(); + options.put("password", getExternalUserPassword()); + return new AppConfigurationEntry[] { new AppConfigurationEntry(MyLoginModule.class.getName(), + LoginModuleControlFlag.REQUIRED, options) }; + } + return innerConfiguration.getAppConfigurationEntry(name); + } + }); + } + + private String getDatabaseURL() { + return "jdbc:h2:mem:" + getClass().getSimpleName(); + } + + private static String getExternalUser() { + return "user"; + } + + @Override + public void test() throws Exception { + Configuration oldConfiguration = Configuration.getConfiguration(); + try { + configureJaas(); + ConnectionInfo connectionInfo = new ConnectionInfo(getDatabaseURL(), null, "dba", null); + session = Engine.createSession(connectionInfo); + database = session.getDatabase(); + configureAuthentication(database); + try { + allTests(); + } finally { + session.close(); + } + } finally { + Configuration.setConfiguration(oldConfiguration); + } + } + + private void allTests() throws Exception { + testInvalidPassword(); + testExternalUserWithoutRealm(); + testExternalUser(); + testAssignRealNameRole(); + testStaticRole(); + testStaticUserCredentials(); + testUserRegistration(); + testSet(); + testDatasource(); + testXmlConfig(); + } + + private void testInvalidPassword() throws Exception { + try { + Connection wrongLoginConnection = DriverManager.getConnection( + getDatabaseURL() + ";AUTHREALM=" + getRealmName().toUpperCase(), getExternalUser(), ""); + wrongLoginConnection.close(); + throw new Exception("user should not be able to login with an invalid password"); + } catch (SQLException ignored) { + } + } + + private void testExternalUserWithoutRealm() throws Exception { + try { + Connection wrongLoginConnection = DriverManager.getConnection(getDatabaseURL(), getExternalUser(), + getExternalUserPassword()); + wrongLoginConnection.close(); + throw new Exception("user should not be able to login without a realm"); + } catch (SQLException ignored) { + } + } + + private void testExternalUser() throws Exception { + try (Connection ignored = DriverManager.getConnection( + getDatabaseURL() + ";AUTHREALM=" + getRealmName().toUpperCase(), getExternalUser(), + getExternalUserPassword())) { + User user = session.getDatabase().findUser((getExternalUser() + "@" + getRealmName()).toUpperCase()); + assertNotNull(user); + } + } + + private void testDatasource() throws Exception { + DataSource dataSource = JdbcConnectionPool.create( + getDatabaseURL() + ";AUTHREALM=" + getRealmName().toUpperCase(), getExternalUser(), + getExternalUserPassword()); + try (Connection ignored = dataSource.getConnection()) { + User user = session.getDatabase().findUser((getExternalUser() + "@" + getRealmName()).toUpperCase()); + assertNotNull(user); + } + } + + private void testAssignRealNameRole() throws Exception { + String realmNameRoleName = "@" + getRealmName().toUpperCase(); + Role realmNameRole = database.findRole(realmNameRoleName); + if (realmNameRole == null) { + realmNameRole = new Role(database, database.allocateObjectId(), realmNameRoleName, false); + session.getDatabase().addDatabaseObject(session, realmNameRole); + session.commit(false); + } + try (Connection ignored = DriverManager.getConnection( + getDatabaseURL() + ";AUTHREALM=" + getRealmName().toUpperCase(), getExternalUser(), + getExternalUserPassword())) { + User user = session.getDatabase().findUser((getExternalUser() + "@" + getRealmName()).toUpperCase()); + assertNotNull(user); + assertTrue(user.isRoleGranted(realmNameRole)); + } + } + + private void testStaticRole() throws Exception { + try (Connection ignored = DriverManager.getConnection( + getDatabaseURL() + ";AUTHREALM=" + getRealmName().toUpperCase(), getExternalUser(), + getExternalUserPassword())) { + User user = session.getDatabase().findUser((getExternalUser() + "@" + getRealmName()).toUpperCase()); + assertNotNull(user); + Role staticRole = session.getDatabase().findRole(getStaticRoleName()); + if (staticRole != null) { + assertTrue(user.isRoleGranted(staticRole)); + } + } + } + + private void testUserRegistration() throws Exception { + boolean initialValueAllow = defaultAuthenticator.isAllowUserRegistration(); + defaultAuthenticator.setAllowUserRegistration(false); + try { + try { + Connection wrongLoginConnection = DriverManager.getConnection( + getDatabaseURL() + ";AUTHREALM=" + getRealmName().toUpperCase(), "___" + getExternalUser(), + ""); + wrongLoginConnection.close(); + throw new Exception( + "unregistered external users should not be able to login when allowUserRegistration=false"); + } catch (SQLException ignored) { + } + String validUserName = "new_" + getExternalUser(); + User validUser = new User(database, database.allocateObjectId(), + (validUserName.toUpperCase() + "@" + getRealmName()).toUpperCase(), false); + validUser.setUserPasswordHash(new byte[] {}); + database.addDatabaseObject(session, validUser); + session.commit(false); + Connection connectionWithRegisterUser = DriverManager.getConnection( + getDatabaseURL() + ";AUTHREALM=" + getRealmName().toUpperCase(), validUserName, + getExternalUserPassword()); + connectionWithRegisterUser.close(); + } finally { + defaultAuthenticator.setAllowUserRegistration(initialValueAllow); + } + } + + private void testStaticUserCredentials() throws Exception { + String userName="STATICUSER3"; + try (Connection ignored = DriverManager.getConnection( + getDatabaseURL() + ";AUTHREALM=" + getRealmName().toUpperCase() + "_STATIC", userName, + "staticpassword")) { + User user = session.getDatabase().findUser(userName + "@" + getRealmName().toUpperCase() + "_STATIC"); + assertNotNull(user); + } + } + + private void testSet() throws Exception{ + try (Connection ignored = DriverManager.getConnection( + getDatabaseURL() + ";AUTHENTICATOR=FALSE", "DBA", "")) { + try { + testExternalUser(); + throw new Exception("External user shouldn't be allowed"); + } catch (Exception e) { + } + } finally { + configureAuthentication(database); + } + testExternalUser(); + } + + private void testXmlConfig() throws Exception { + ByteArrayInputStream inputStream = new ByteArrayInputStream(TESTXML.getBytes()); + H2AuthConfig config = H2AuthConfigXml.parseFrom(inputStream); + assertTrue(config.isAllowUserRegistration()); + assertFalse(config.isCreateMissingRoles()); + assertEquals("ciao",config.getRealms().get(0).getName()); + assertEquals("myclass",config.getRealms().get(0).getValidatorClass()); + assertEquals("prop1",config.getRealms().get(1).getProperties().get(0).getName()); + assertEquals("value1",config.getRealms().get(1).getProperties().get(0).getValue()); + assertEquals("class1",config.getUserToRolesMappers().get(0).getClassName()); + assertEquals("prop2",config.getUserToRolesMappers().get(0).getProperties().get(0).getName()); + assertEquals("value2",config.getUserToRolesMappers().get(0).getProperties().get(0).getValue()); + } +} \ No newline at end of file diff --git a/h2/src/test/org/h2/test/auth/package.html b/h2/src/test/org/h2/test/auth/package.html new file mode 100644 index 0000000..3a5a38a --- /dev/null +++ b/h2/src/test/org/h2/test/auth/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +Tests for custom authentication. + +

    \ No newline at end of file diff --git a/h2/src/test/org/h2/test/bench/Bench.java b/h2/src/test/org/h2/test/bench/Bench.java new file mode 100644 index 0000000..89c42a1 --- /dev/null +++ b/h2/src/test/org/h2/test/bench/Bench.java @@ -0,0 +1,36 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.bench; + +import java.sql.SQLException; + +/** + * The interface for benchmark tests. + */ +public interface Bench { + + /** + * Initialize the database. This includes creating tables and inserting + * data. + * + * @param db the database object + * @param size the amount of data + */ + void init(Database db, int size) throws SQLException; + + /** + * Run the test. + */ + void runTest() throws Exception; + + /** + * Get the name of the test. + * + * @return the test name + */ + String getName(); + +} diff --git a/h2/src/test/org/h2/test/bench/BenchA.java b/h2/src/test/org/h2/test/bench/BenchA.java new file mode 100644 index 0000000..16818da --- /dev/null +++ b/h2/src/test/org/h2/test/bench/BenchA.java @@ -0,0 +1,190 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.bench; + +import java.math.BigDecimal; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Random; + +/** + * This test is similar to the TPC-A test of the Transaction Processing Council + * (TPC). However, only one connection and one thread is used. + *

    + * See also: http://www.tpc.org/tpca/spec/tpca_current.pdf + */ +public class BenchA implements Bench { + + private static final String FILLER = "abcdefghijklmnopqrstuvwxyz"; + private static final int DELTA = 10000; + + private Database database; + + private int branches; + private int tellers; + private int accounts; + private int transactions; + + @Override + public void init(Database db, int size) throws SQLException { + this.database = db; + transactions = size * 6; + + int scale = 2; + accounts = size * 30; + tellers = Math.max(accounts / 10, 1); + branches = Math.max(tellers / 10, 1); + + db.start(this, "Init"); + + db.openConnection(); + + db.dropTable("BRANCHES"); + db.dropTable("TELLERS"); + db.dropTable("ACCOUNTS"); + db.dropTable("HISTORY"); + + String[] create = { + "CREATE TABLE BRANCHES(BID INT NOT NULL PRIMARY KEY, " + + "BBALANCE DECIMAL(15,2), FILLER VARCHAR(88))", + "CREATE TABLE TELLERS(TID INT NOT NULL PRIMARY KEY, " + + "BID INT, TBALANCE DECIMAL(15,2), FILLER VARCHAR(84))", + "CREATE TABLE ACCOUNTS(AID INT NOT NULL PRIMARY KEY, " + + "BID INT, ABALANCE DECIMAL(15,2), FILLER VARCHAR(84))", + "CREATE TABLE HISTORY(TID INT, " + + "BID INT, AID INT, DELTA DECIMAL(15,2), HTIME DATETIME, " + + "FILLER VARCHAR(40))" }; + + for (String sql : create) { + db.update(sql); + } + + PreparedStatement prep; + db.setAutoCommit(false); + int commitEvery = 1000; + prep = db.prepare( + "INSERT INTO BRANCHES(BID, BBALANCE, FILLER) " + + "VALUES(?, 10000.00, '" + FILLER + "')"); + for (int i = 0; i < branches * scale; i++) { + prep.setInt(1, i); + db.update(prep, "insertBranches"); + if ((i+1) % commitEvery == 0) { + db.commit(); + } + } + db.commit(); + prep = db.prepare( + "INSERT INTO TELLERS(TID, BID, TBALANCE, FILLER) " + + "VALUES(?, ?, 10000.00, '" + FILLER + "')"); + for (int i = 0; i < tellers * scale; i++) { + prep.setInt(1, i); + prep.setInt(2, i / tellers); + db.update(prep, "insertTellers"); + if ((i+1) % commitEvery == 0) { + db.commit(); + } + } + db.commit(); + int len = accounts * scale; + prep = db.prepare( + "INSERT INTO ACCOUNTS(AID, BID, ABALANCE, FILLER) " + + "VALUES(?, ?, 10000.00, '" + FILLER + "')"); + for (int i = 0; i < len; i++) { + prep.setInt(1, i); + prep.setInt(2, i / accounts); + db.update(prep, "insertAccounts"); + if ((i+1) % commitEvery == 0) { + db.commit(); + } + } + db.commit(); + db.closeConnection(); + db.end(); + } + + @Override + public void runTest() throws SQLException { + database.openConnection(); + database.start(this, "Transactions"); + processTransactions(); + database.end(); + database.logMemory(this, "Memory Usage"); + database.closeConnection(); + } + + private void processTransactions() throws SQLException { + Random random = database.getRandom(); + int branch = random.nextInt(branches); + int teller = random.nextInt(tellers); + + PreparedStatement updateAccount = database.prepare( + "UPDATE ACCOUNTS SET ABALANCE=ABALANCE+? WHERE AID=?"); + PreparedStatement selectBalance = database.prepare( + "SELECT ABALANCE FROM ACCOUNTS WHERE AID=?"); + PreparedStatement updateTeller = database.prepare( + "UPDATE TELLERS SET TBALANCE=TBALANCE+? WHERE TID=?"); + PreparedStatement updateBranch = database.prepare( + "UPDATE BRANCHES SET BBALANCE=BBALANCE+? WHERE BID=?"); + PreparedStatement insertHistory = database.prepare( + "INSERT INTO HISTORY(AID, TID, BID, DELTA, HTIME, FILLER) " + + "VALUES(?, ?, ?, ?, ?, ?)"); + int accountsPerBranch = accounts / branches; + database.setAutoCommit(false); + + for (int i = 0; i < transactions; i++) { + int account; + if (random.nextInt(100) < 85) { + account = random.nextInt(accountsPerBranch) + branch * accountsPerBranch; + } else { + account = random.nextInt(accounts); + } + int max = BenchA.DELTA; + // delta: -max .. +max + + BigDecimal delta = BigDecimal.valueOf(random.nextInt(max * 2) - max); + long current = System.currentTimeMillis(); + + updateAccount.setBigDecimal(1, delta); + updateAccount.setInt(2, account); + database.update(updateAccount, "updateAccount"); + + updateTeller.setBigDecimal(1, delta); + updateTeller.setInt(2, teller); + database.update(updateTeller, "updateTeller"); + + updateBranch.setBigDecimal(1, delta); + updateBranch.setInt(2, branch); + database.update(updateBranch, "updateBranch"); + + selectBalance.setInt(1, account); + database.queryReadResult(selectBalance); + + insertHistory.setInt(1, account); + insertHistory.setInt(2, teller); + insertHistory.setInt(3, branch); + insertHistory.setBigDecimal(4, delta); + // TODO convert: should be able to convert date to timestamp + // (by using 0 for remaining fields) + // insertHistory.setDate(5, new java.sql.Date(current)); + insertHistory.setTimestamp(5, new java.sql.Timestamp(current)); + insertHistory.setString(6, BenchA.FILLER); + database.update(insertHistory, "insertHistory"); + + database.commit(); + } + updateAccount.close(); + selectBalance.close(); + updateTeller.close(); + updateBranch.close(); + insertHistory.close(); + } + + @Override + public String getName() { + return "BenchA"; + } + +} diff --git a/h2/src/test/org/h2/test/bench/BenchB.java b/h2/src/test/org/h2/test/bench/BenchB.java new file mode 100644 index 0000000..2aa5536 --- /dev/null +++ b/h2/src/test/org/h2/test/bench/BenchB.java @@ -0,0 +1,242 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.bench; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Random; + +/** + * This test is similar to the TPC-B test of the Transaction Processing Council + * (TPC). Multiple threads are used (one thread per connection). Referential + * integrity is not implemented. + *

    + * See also http://www.tpc.org/tpcb + */ +public class BenchB implements Bench, Runnable { + + private static final int SCALE = 4; + private static final int BRANCHES = 10; + private static final int TELLERS = 100; + private static final int ACCOUNTS = 100000; + + private int threadCount = 10; + + // master data + private Database database; + private int transactionPerClient; + + // client data + private BenchB master; + private Connection conn; + private PreparedStatement selectAccount; + private PreparedStatement updateAccount; + private PreparedStatement updateTeller; + private PreparedStatement updateBranch; + private PreparedStatement insertHistory; + private Random random; + + public BenchB() { + // nothing to do + } + + private BenchB(BenchB master, int seed) throws SQLException { + this.master = master; + random = new Random(seed); + conn = master.database.openNewConnection(); + conn.setAutoCommit(false); + try { + selectAccount = conn.prepareStatement( + "SELECT ABALANCE FROM ACCOUNTS WHERE AID=? FOR UPDATE"); + } catch (SQLException ignored) { + selectAccount = conn.prepareStatement( + "SELECT ABALANCE FROM ACCOUNTS WHERE AID=?"); + } + updateAccount = conn.prepareStatement( + "UPDATE ACCOUNTS SET ABALANCE=ABALANCE+? WHERE AID=?"); + updateTeller = conn.prepareStatement( + "UPDATE TELLERS SET TBALANCE=TBALANCE+? WHERE TID=?"); + updateBranch = conn.prepareStatement( + "UPDATE BRANCHES SET BBALANCE=BBALANCE+? WHERE BID=?"); + insertHistory = conn.prepareStatement( + "INSERT INTO HISTORY(TID, BID, AID, DELTA) VALUES(?, ?, ?, ?)"); + } + + @Override + public void init(Database db, int size) throws SQLException { + this.database = db; + this.transactionPerClient = getTransactionsPerClient(size); + + db.start(this, "Init"); + db.openConnection(); + db.dropTable("BRANCHES"); + db.dropTable("TELLERS"); + db.dropTable("ACCOUNTS"); + db.dropTable("HISTORY"); + String[] create = { + "CREATE TABLE BRANCHES(" + + "BID INT NOT NULL PRIMARY KEY, " + + "BBALANCE INT, FILLER VARCHAR(88))", + "CREATE TABLE TELLERS(" + + "TID INT NOT NULL PRIMARY KEY, " + + "BID INT, TBALANCE INT, FILLER VARCHAR(84))", + "CREATE TABLE ACCOUNTS(" + + "AID INT NOT NULL PRIMARY KEY, " + + "BID INT, ABALANCE INT, FILLER VARCHAR(84))", + "CREATE TABLE HISTORY(" + + "TID INT, BID INT, AID INT, " + + "DELTA INT, HTIME DATETIME, FILLER VARCHAR(22))" }; + for (String sql : create) { + db.update(sql); + } + PreparedStatement prep; + db.setAutoCommit(false); + int commitEvery = 1000; + prep = db.prepare( + "INSERT INTO BRANCHES(BID, BBALANCE) VALUES(?, 0)"); + for (int i = 0; i < BRANCHES * SCALE; i++) { + prep.setInt(1, i); + db.update(prep, "insertBranches"); + if ((i+1) % commitEvery == 0) { + db.commit(); + } + } + db.commit(); + prep = db.prepare( + "INSERT INTO TELLERS(TID, BID, TBALANCE) VALUES(?, ?, 0)"); + for (int i = 0; i < TELLERS * SCALE; i++) { + prep.setInt(1, i); + prep.setInt(2, i / TELLERS); + db.update(prep, "insertTellers"); + if ((i+1) % commitEvery == 0) { + db.commit(); + } + } + db.commit(); + int len = ACCOUNTS * SCALE; + prep = db.prepare( + "INSERT INTO ACCOUNTS(AID, BID, ABALANCE) VALUES(?, ?, 0)"); + for (int i = 0; i < len; i++) { + prep.setInt(1, i); + prep.setInt(2, i / ACCOUNTS); + db.update(prep, "insertAccounts"); + if ((i+1) % commitEvery == 0) { + db.commit(); + } + } + db.commit(); + db.closeConnection(); + db.end(); + } + + /** + * Get the number of transactions per client. + * + * @param size test size + * @return the transactions per client + */ + protected int getTransactionsPerClient(int size) { + return size / 8; + } + + @Override + public void run() { + int accountsPerBranch = ACCOUNTS / BRANCHES; + for (int i = 0; i < master.transactionPerClient; i++) { + try { + int branch = random.nextInt(BRANCHES); + int teller = random.nextInt(TELLERS); + int account; + if (random.nextInt(100) < 85) { + account = random.nextInt(accountsPerBranch) + branch * accountsPerBranch; + } else { + account = random.nextInt(ACCOUNTS); + } + int delta = random.nextInt(1000); + doOne(branch, teller, account, -delta); + try { + conn.commit(); + } catch (SQLException e) { + e.printStackTrace(); + } + } catch (SQLException ignore) { + try { + conn.rollback(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + } + try { + conn.setAutoCommit(true); + conn.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + private void doOne(int branch, int teller, int account, int delta) throws SQLException { + selectAccount.setInt(1, account); + master.database.queryReadResult(selectAccount); + + updateAccount.setInt(1, delta); + updateAccount.setInt(2, account); + master.database.update(updateAccount, "UpdateAccounts"); + + updateTeller.setInt(1, delta); + updateTeller.setInt(2, teller); + master.database.update(updateTeller, "UpdateTeller"); + + updateBranch.setInt(1, delta); + updateBranch.setInt(2, branch); + master.database.update(updateBranch, "UpdateBranch"); + + insertHistory.setInt(1, teller); + insertHistory.setInt(2, branch); + insertHistory.setInt(3, account); + insertHistory.setInt(4, delta); + master.database.update(insertHistory, "InsertHistory"); + } + + private void clearHistory() throws SQLException { + database.update("DELETE FROM HISTORY"); + } + + @Override + public void runTest() throws Exception { + Database db = database; + db.openConnection(); + db.start(this, "Transactions"); + processTransactions(); + db.end(); + db.logMemory(this, "Memory Usage"); + clearHistory(); + db.closeConnection(); + } + + private void processTransactions() throws Exception { + Thread[] threads = new Thread[threadCount]; + for (int i = 0; i < threadCount; i++) { + threads[i] = new Thread(new BenchB(this, i), "BenchB-" + i); + } + for (Thread t : threads) { + t.start(); + } + for (Thread t : threads) { + t.join(); + } + } + + @Override + public String getName() { + return "BenchB"; + } + + public void setThreadCount(int threadCount) { + this.threadCount = threadCount; + } +} diff --git a/h2/src/test/org/h2/test/bench/BenchC.java b/h2/src/test/org/h2/test/bench/BenchC.java new file mode 100644 index 0000000..ffe906d --- /dev/null +++ b/h2/src/test/org/h2/test/bench/BenchC.java @@ -0,0 +1,564 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.bench; + +import java.math.BigDecimal; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.sql.Types; + +/** + * This test is similar to the TPC-C test of the Transaction Processing Council + * (TPC). Only one connection and one thread is used. Referential integrity is + * not implemented. + *

    + * See also http://www.tpc.org + */ +public class BenchC implements Bench { + + private static final int COMMIT_EVERY = 1000; + + private static final String[] TABLES = { "WAREHOUSE", "DISTRICT", + "CUSTOMER", "HISTORY", "ORDERS", "NEW_ORDER", "ITEM", "STOCK", + "ORDER_LINE", "RESULTS" }; + + private static final String[] CREATE_SQL = { + "CREATE TABLE WAREHOUSE(\n" + + " W_ID INT NOT NULL PRIMARY KEY,\n" + + " W_NAME VARCHAR(10),\n" + + " W_STREET_1 VARCHAR(20),\n" + + " W_STREET_2 VARCHAR(20),\n" + + " W_CITY VARCHAR(20),\n" + + " W_STATE CHAR(2),\n" + + " W_ZIP CHAR(9),\n" + + " W_TAX DECIMAL(4, 4),\n" + + " W_YTD DECIMAL(12, 2))", + "CREATE TABLE DISTRICT(\n" + + " D_ID INT NOT NULL,\n" + + " D_W_ID INT NOT NULL,\n" + + " D_NAME VARCHAR(10),\n" + + " D_STREET_1 VARCHAR(20),\n" + + " D_STREET_2 VARCHAR(20),\n" + + " D_CITY VARCHAR(20),\n" + + " D_STATE CHAR(2),\n" + + " D_ZIP CHAR(9),\n" + + " D_TAX DECIMAL(4, 4),\n" + + " D_YTD DECIMAL(12, 2),\n" + + " D_NEXT_O_ID INT,\n" + + " PRIMARY KEY (D_ID, D_W_ID))", + // + " FOREIGN KEY (D_W_ID)\n" + // + " REFERENCES WAREHOUSE(W_ID))", + "CREATE TABLE CUSTOMER(\n" + + " C_ID INT NOT NULL,\n" + + " C_D_ID INT NOT NULL,\n" + + " C_W_ID INT NOT NULL,\n" + + " C_FIRST VARCHAR(16),\n" + + " C_MIDDLE CHAR(2),\n" + + " C_LAST VARCHAR(16),\n" + + " C_STREET_1 VARCHAR(20),\n" + + " C_STREET_2 VARCHAR(20),\n" + + " C_CITY VARCHAR(20),\n" + + " C_STATE CHAR(2),\n" + + " C_ZIP CHAR(9),\n" + + " C_PHONE CHAR(16),\n" + + " C_SINCE TIMESTAMP,\n" + + " C_CREDIT CHAR(2),\n" + + " C_CREDIT_LIM DECIMAL(12, 2),\n" + + " C_DISCOUNT DECIMAL(4, 4),\n" + + " C_BALANCE DECIMAL(12, 2),\n" + + " C_YTD_PAYMENT DECIMAL(12, 2),\n" + + " C_PAYMENT_CNT DECIMAL(4),\n" + + " C_DELIVERY_CNT DECIMAL(4),\n" + + " C_DATA VARCHAR(500),\n" + + " PRIMARY KEY (C_W_ID, C_D_ID, C_ID))", + // + " FOREIGN KEY (C_W_ID, C_D_ID)\n" + // + " REFERENCES DISTRICT(D_W_ID, D_ID))", + "CREATE INDEX CUSTOMER_NAME ON CUSTOMER(C_LAST, C_D_ID, C_W_ID)", + "CREATE TABLE HISTORY(\n" + + " H_C_ID INT,\n" + + " H_C_D_ID INT,\n" + + " H_C_W_ID INT,\n" + + " H_D_ID INT,\n" + + " H_W_ID INT,\n" + + " H_DATE TIMESTAMP,\n" + + " H_AMOUNT DECIMAL(6, 2),\n" + + " H_DATA VARCHAR(24))", + // + " FOREIGN KEY(H_C_W_ID, H_C_D_ID, H_C_ID)\n" + // + " REFERENCES CUSTOMER(C_W_ID, C_D_ID, C_ID),\n" + // + " FOREIGN KEY(H_W_ID, H_D_ID)\n" + // + " REFERENCES DISTRICT(D_W_ID, D_ID))", + "CREATE TABLE ORDERS(\n" + + " O_ID INT NOT NULL,\n" + + " O_D_ID INT NOT NULL,\n" + + " O_W_ID INT NOT NULL,\n" + + " O_C_ID INT,\n" + + " O_ENTRY_D TIMESTAMP,\n" + + " O_CARRIER_ID INT,\n" + + " O_OL_CNT INT,\n" + + " O_ALL_LOCAL DECIMAL(1),\n" + + " PRIMARY KEY(O_W_ID, O_D_ID, O_ID))", + // + " FOREIGN KEY(O_W_ID, O_D_ID, O_C_ID)\n" + // + " REFERENCES CUSTOMER(C_W_ID, C_D_ID, C_ID))", + "CREATE INDEX ORDERS_OID ON ORDERS(O_ID)", + "CREATE TABLE NEW_ORDER(\n" + + " NO_O_ID INT NOT NULL,\n" + + " NO_D_ID INT NOT NULL,\n" + + " NO_W_ID INT NOT NULL,\n" + + " PRIMARY KEY(NO_W_ID, NO_D_ID, NO_O_ID))", + // + " FOREIGN KEY(NO_W_ID, NO_D_ID, NO_O_ID)\n" + // + " REFERENCES ORDER(O_W_ID, O_D_ID, O_ID))", + "CREATE TABLE ITEM(\n" + + " I_ID INT NOT NULL,\n" + + " I_IM_ID INT,\n" + + " I_NAME VARCHAR(24),\n" + + " I_PRICE DECIMAL(5, 2),\n" + + " I_DATA VARCHAR(50),\n" + + " PRIMARY KEY(I_ID))", + "CREATE TABLE STOCK(\n" + + " S_I_ID INT NOT NULL,\n" + + " S_W_ID INT NOT NULL,\n" + + " S_QUANTITY DECIMAL(4),\n" + + " S_DIST_01 CHAR(24),\n" + + " S_DIST_02 CHAR(24),\n" + + " S_DIST_03 CHAR(24),\n" + + " S_DIST_04 CHAR(24),\n" + + " S_DIST_05 CHAR(24),\n" + + " S_DIST_06 CHAR(24),\n" + + " S_DIST_07 CHAR(24),\n" + + " S_DIST_08 CHAR(24),\n" + + " S_DIST_09 CHAR(24),\n" + + " S_DIST_10 CHAR(24),\n" + + " S_YTD DECIMAL(8),\n" + + " S_ORDER_CNT DECIMAL(4),\n" + + " S_REMOTE_CNT DECIMAL(4),\n" + + " S_DATA VARCHAR(50),\n" + + " PRIMARY KEY(S_W_ID, S_I_ID))", + // + " FOREIGN KEY(S_W_ID)\n" + // + " REFERENCES WAREHOUSE(W_ID),\n" + // + " FOREIGN KEY(S_I_ID)\n" + " REFERENCES ITEM(I_ID))", + "CREATE TABLE ORDER_LINE(\n" + + " OL_O_ID INT NOT NULL,\n" + + " OL_D_ID INT NOT NULL,\n" + + " OL_W_ID INT NOT NULL,\n" + + " OL_NUMBER INT NOT NULL,\n" + + " OL_I_ID INT,\n" + + " OL_SUPPLY_W_ID INT,\n" + + " OL_DELIVERY_D TIMESTAMP,\n" + + " OL_QUANTITY DECIMAL(2),\n" + + " OL_AMOUNT DECIMAL(6, 2),\n" + + " OL_DIST_INFO CHAR(24),\n" + + " PRIMARY KEY (OL_W_ID, OL_D_ID, OL_O_ID, OL_NUMBER))", + // + " FOREIGN KEY(OL_W_ID, OL_D_ID, OL_O_ID)\n" + // + " REFERENCES ORDER(O_W_ID, O_D_ID, O_ID),\n" + // + " FOREIGN KEY(OL_SUPPLY_W_ID, OL_I_ID)\n" + // + " REFERENCES STOCK(S_W_ID, S_I_ID))", + "CREATE TABLE RESULTS(\n" + + " ID INT NOT NULL PRIMARY KEY,\n" + + " TERMINAL INT,\n" + + " OPERATION INT,\n" + + " RESPONSE_TIME INT,\n" + + " PROCESSING_TIME INT,\n" + + " KEYING_TIME INT,\n" + + " THINK_TIME INT,\n" + + " SUCCESSFUL INT,\n" + + " NOW TIMESTAMP)" }; + + int warehouses = 2; + int items = 10000; + int districtsPerWarehouse = 10; + int customersPerDistrict = 300; + + private Database database; + + private int ordersPerDistrict = 300; + + private BenchCRandom random; + private String action; + + @Override + public void init(Database db, int size) throws SQLException { + this.database = db; + + random = new BenchCRandom(); + + items = size * 10; + warehouses = 2; + districtsPerWarehouse = Math.max(1, size / 100); + customersPerDistrict = Math.max(1, size / 100); + ordersPerDistrict = Math.max(1, size / 1000); + + db.start(this, "Init"); + db.openConnection(); + load(); + db.commit(); + db.closeConnection(); + db.end(); + + // db.start(this, "Open/Close"); + // db.openConnection(); + // db.closeConnection(); + // db.end(); + + } + + private void load() throws SQLException { + for (String sql : TABLES) { + database.dropTable(sql); + } + for (String sql : CREATE_SQL) { + database.update(sql); + } + database.setAutoCommit(false); + loadItem(); + loadWarehouse(); + loadCustomer(); + loadOrder(); + database.commit(); + trace("Load done"); + } + + private void trace(String s) { + action = s; + } + + private void trace(int i, int max) { + database.trace(action, i, max); + } + + private void loadItem() throws SQLException { + trace("Loading item table"); + boolean[] original = random.getBoolean(items, items / 10); + PreparedStatement prep = database.prepare( + "INSERT INTO ITEM(I_ID, I_IM_ID, I_NAME, I_PRICE, I_DATA) " + + "VALUES(?, ?, ?, ?, ?)"); + for (int id = 1; id <= items; id++) { + String name = random.getString(14, 24); + BigDecimal price = random.getBigDecimal(random.getInt(100, 10000), 2); + String data = random.getString(26, 50); + if (original[id - 1]) { + data = random.replace(data, "original"); + } + prep.setInt(1, id); + prep.setInt(2, random.getInt(1, 10000)); + prep.setString(3, name); + prep.setBigDecimal(4, price); + prep.setString(5, data); + database.update(prep, "insertItem"); + trace(id, items); + if (id % COMMIT_EVERY == 0) { + database.commit(); + } + } + } + + private void loadWarehouse() throws SQLException { + trace("Loading warehouse table"); + PreparedStatement prep = database.prepare( + "INSERT INTO WAREHOUSE(W_ID, W_NAME, W_STREET_1, " + + "W_STREET_2, W_CITY, W_STATE, W_ZIP, W_TAX, W_YTD) " + + "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)"); + for (int id = 1; id <= warehouses; id++) { + String name = random.getString(6, 10); + String[] address = random.getAddress(); + String street1 = address[0]; + String street2 = address[1]; + String city = address[2]; + String state = address[3]; + String zip = address[4]; + BigDecimal tax = random.getBigDecimal(random.getInt(0, 2000), 4); + BigDecimal ytd = new BigDecimal("300000.00"); + prep.setInt(1, id); + prep.setString(2, name); + prep.setString(3, street1); + prep.setString(4, street2); + prep.setString(5, city); + prep.setString(6, state); + prep.setString(7, zip); + prep.setBigDecimal(8, tax); + prep.setBigDecimal(9, ytd); + database.update(prep, "insertWarehouse"); + loadStock(id); + loadDistrict(id); + if (id % COMMIT_EVERY == 0) { + database.commit(); + } + } + } + + private void loadCustomer() throws SQLException { + trace("Load customer table"); + int max = warehouses * districtsPerWarehouse; + int i = 0; + for (int id = 1; id <= warehouses; id++) { + for (int districtId = 1; districtId <= districtsPerWarehouse; districtId++) { + loadCustomerSub(districtId, id); + trace(i++, max); + if (i % COMMIT_EVERY == 0) { + database.commit(); + } + } + } + } + + private void loadCustomerSub(int dId, int wId) throws SQLException { + Timestamp timestamp = new Timestamp(System.currentTimeMillis()); + PreparedStatement prepCustomer = database.prepare( + "INSERT INTO CUSTOMER(C_ID, C_D_ID, C_W_ID, " + + "C_FIRST, C_MIDDLE, C_LAST, " + + "C_STREET_1, C_STREET_2, C_CITY, C_STATE, C_ZIP, " + + "C_PHONE, C_SINCE, C_CREDIT, " + + "C_CREDIT_LIM, C_DISCOUNT, C_BALANCE, C_DATA, " + + "C_YTD_PAYMENT, C_PAYMENT_CNT, C_DELIVERY_CNT) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PreparedStatement prepHistory = database.prepare( + "INSERT INTO HISTORY(H_C_ID, H_C_D_ID, H_C_W_ID, " + + "H_W_ID, H_D_ID, H_DATE, H_AMOUNT, H_DATA) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); + for (int cId = 1; cId <= customersPerDistrict; cId++) { + String first = random.getString(8, 16); + String middle = "OE"; + String last; + if (cId < 1000) { + last = random.getLastname(cId); + } else { + last = random.getLastname(random.getNonUniform(255, 0, 999)); + } + String[] address = random.getAddress(); + String street1 = address[0]; + String street2 = address[1]; + String city = address[2]; + String state = address[3]; + String zip = address[4]; + String phone = random.getNumberString(16, 16); + String credit; + if (random.getInt(0, 1) == 0) { + credit = "GC"; + } else { + credit = "BC"; + } + BigDecimal discount = random.getBigDecimal(random.getInt(0, 5000), 4); + BigDecimal balance = new BigDecimal("-10.00"); + BigDecimal creditLim = new BigDecimal("50000.00"); + String data = random.getString(300, 500); + BigDecimal ytdPayment = new BigDecimal("10.00"); + int paymentCnt = 1; + int deliveryCnt = 1; + prepCustomer.setInt(1, cId); + prepCustomer.setInt(2, dId); + prepCustomer.setInt(3, wId); + prepCustomer.setString(4, first); + prepCustomer.setString(5, middle); + prepCustomer.setString(6, last); + prepCustomer.setString(7, street1); + prepCustomer.setString(8, street2); + prepCustomer.setString(9, city); + prepCustomer.setString(10, state); + prepCustomer.setString(11, zip); + prepCustomer.setString(12, phone); + prepCustomer.setTimestamp(13, timestamp); + prepCustomer.setString(14, credit); + prepCustomer.setBigDecimal(15, creditLim); + prepCustomer.setBigDecimal(16, discount); + prepCustomer.setBigDecimal(17, balance); + prepCustomer.setString(18, data); + prepCustomer.setBigDecimal(19, ytdPayment); + prepCustomer.setInt(20, paymentCnt); + prepCustomer.setInt(21, deliveryCnt); + database.update(prepCustomer, "insertCustomer"); + BigDecimal amount = new BigDecimal("10.00"); + String hData = random.getString(12, 24); + prepHistory.setInt(1, cId); + prepHistory.setInt(2, dId); + prepHistory.setInt(3, wId); + prepHistory.setInt(4, wId); + prepHistory.setInt(5, dId); + prepHistory.setTimestamp(6, timestamp); + prepHistory.setBigDecimal(7, amount); + prepHistory.setString(8, hData); + database.update(prepHistory, "insertHistory"); + } + } + + private void loadOrder() throws SQLException { + trace("Loading order table"); + int max = warehouses * districtsPerWarehouse; + int i = 0; + for (int wId = 1; wId <= warehouses; wId++) { + for (int dId = 1; dId <= districtsPerWarehouse; dId++) { + loadOrderSub(dId, wId); + trace(i++, max); + } + } + } + + private void loadOrderSub(int dId, int wId) throws SQLException { + Timestamp timestamp = new Timestamp(System.currentTimeMillis()); + int[] orderid = random.getPermutation(ordersPerDistrict); + PreparedStatement prepOrder = database.prepare( + "INSERT INTO ORDERS(O_ID, O_C_ID, O_D_ID, O_W_ID, " + + "O_ENTRY_D, O_CARRIER_ID, O_OL_CNT, O_ALL_LOCAL) " + + "VALUES(?, ?, ?, ?, ?, ?, ?, 1)"); + PreparedStatement prepNewOrder = database.prepare( + "INSERT INTO NEW_ORDER (NO_O_ID, NO_D_ID, NO_W_ID) " + + "VALUES (?, ?, ?)"); + PreparedStatement prepLine = database.prepare( + "INSERT INTO ORDER_LINE(" + + "OL_O_ID, OL_D_ID, OL_W_ID, OL_NUMBER, " + + "OL_I_ID, OL_SUPPLY_W_ID, OL_QUANTITY, OL_AMOUNT, " + + "OL_DIST_INFO, OL_DELIVERY_D)" + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)"); + for (int oId = 1, i = 0; oId <= ordersPerDistrict; oId++) { + int cId = orderid[oId - 1]; + int carrierId = random.getInt(1, 10); + int olCnt = random.getInt(5, 15); + prepOrder.setInt(1, oId); + prepOrder.setInt(2, cId); + prepOrder.setInt(3, dId); + prepOrder.setInt(4, wId); + prepOrder.setTimestamp(5, timestamp); + prepOrder.setInt(7, olCnt); + if (oId <= 2100) { + prepOrder.setInt(6, carrierId); + } else { + // the last 900 orders have not been delivered + prepOrder.setNull(6, Types.INTEGER); + prepNewOrder.setInt(1, oId); + prepNewOrder.setInt(2, dId); + prepNewOrder.setInt(3, wId); + database.update(prepNewOrder, "newNewOrder"); + } + database.update(prepOrder, "insertOrder"); + for (int ol = 1; ol <= olCnt; ol++) { + int id = random.getInt(1, items); + int supplyId = wId; + int quantity = 5; + String distInfo = random.getString(24); + BigDecimal amount; + if (oId < 2101) { + amount = random.getBigDecimal(0, 2); + } else { + amount = random.getBigDecimal(random.getInt(0, 1000000), 2); + } + prepLine.setInt(1, oId); + prepLine.setInt(2, dId); + prepLine.setInt(3, wId); + prepLine.setInt(4, ol); + prepLine.setInt(5, id); + prepLine.setInt(6, supplyId); + prepLine.setInt(7, quantity); + prepLine.setBigDecimal(8, amount); + prepLine.setString(9, distInfo); + database.update(prepLine, "insertOrderLine"); + if (i++ % COMMIT_EVERY == 0) { + database.commit(); + } + } + } + } + + private void loadStock(int wId) throws SQLException { + trace("Loading stock table (warehouse " + wId + ")"); + boolean[] original = random.getBoolean(items, items / 10); + PreparedStatement prep = database.prepare( + "INSERT INTO STOCK(S_I_ID, S_W_ID, S_QUANTITY, " + + "S_DIST_01, S_DIST_02, S_DIST_03, S_DIST_04, S_DIST_05, " + + "S_DIST_06, S_DIST_07, S_DIST_08, S_DIST_09, S_DIST_10, " + + "S_DATA, S_YTD, S_ORDER_CNT, S_REMOTE_CNT) " + + "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + for (int id = 1; id <= items; id++) { + int quantity = random.getInt(10, 100); + String dist01 = random.getString(24); + String dist02 = random.getString(24); + String dist03 = random.getString(24); + String dist04 = random.getString(24); + String dist05 = random.getString(24); + String dist06 = random.getString(24); + String dist07 = random.getString(24); + String dist08 = random.getString(24); + String dist09 = random.getString(24); + String dist10 = random.getString(24); + String data = random.getString(26, 50); + if (original[id - 1]) { + data = random.replace(data, "original"); + } + prep.setInt(1, id); + prep.setInt(2, wId); + prep.setInt(3, quantity); + prep.setString(4, dist01); + prep.setString(5, dist02); + prep.setString(6, dist03); + prep.setString(7, dist04); + prep.setString(8, dist05); + prep.setString(9, dist06); + prep.setString(10, dist07); + prep.setString(11, dist08); + prep.setString(12, dist09); + prep.setString(13, dist10); + prep.setString(14, data); + prep.setInt(15, 0); + prep.setInt(16, 0); + prep.setInt(17, 0); + database.update(prep, "insertStock"); + if (id % COMMIT_EVERY == 0) { + database.commit(); + } + trace(id, items); + } + } + + private void loadDistrict(int wId) throws SQLException { + BigDecimal ytd = new BigDecimal("300000.00"); + int nextId = 3001; + PreparedStatement prep = database.prepare( + "INSERT INTO DISTRICT(D_ID, D_W_ID, D_NAME, " + + "D_STREET_1, D_STREET_2, D_CITY, D_STATE, D_ZIP, " + + "D_TAX, D_YTD, D_NEXT_O_ID) " + + "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + for (int dId = 1; dId <= districtsPerWarehouse; dId++) { + String name = random.getString(6, 10); + String[] address = random.getAddress(); + String street1 = address[0]; + String street2 = address[1]; + String city = address[2]; + String state = address[3]; + String zip = address[4]; + BigDecimal tax = random.getBigDecimal(random.getInt(0, 2000), 4); + prep.setInt(1, dId); + prep.setInt(2, wId); + prep.setString(3, name); + prep.setString(4, street1); + prep.setString(5, street2); + prep.setString(6, city); + prep.setString(7, state); + prep.setString(8, zip); + prep.setBigDecimal(9, tax); + prep.setBigDecimal(10, ytd); + prep.setInt(11, nextId); + database.update(prep, "insertDistrict"); + trace(dId, districtsPerWarehouse); + } + } + + @Override + public void runTest() throws SQLException { + database.openConnection(); + database.start(this, "Transactions"); + for (int i = 0; i < 70; i++) { + BenchCThread process = new BenchCThread(database, this, random, i); + process.process(); + } + database.end(); + database.logMemory(this, "Memory Usage"); + database.closeConnection(); + } + + @Override + public String getName() { + return "BenchC"; + } + +} diff --git a/h2/src/test/org/h2/test/bench/BenchCRandom.java b/h2/src/test/org/h2/test/bench/BenchCRandom.java new file mode 100644 index 0000000..4e6e2bb --- /dev/null +++ b/h2/src/test/org/h2/test/bench/BenchCRandom.java @@ -0,0 +1,179 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.bench; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Random; + +/** + * The random data generator used for BenchC. + */ +public class BenchCRandom { + + private final Random random = new Random(10); + + /** + * Get a non-uniform random integer value between min and max. + * + * @param a the bit mask + * @param min the minimum value + * @param max the maximum value + * @return the random value + */ + int getNonUniform(int a, int min, int max) { + int c = 0; + return (((getInt(0, a) | getInt(min, max)) + c) % (max - min + 1)) + + min; + } + + /** + * Get a random integer value between min and max. + * + * @param min the minimum value + * @param max the maximum value + * @return the random value + */ + int getInt(int min, int max) { + return max <= min ? min : (random.nextInt(max - min) + min); + } + + /** + * Generate a boolean array with this many items set to true (randomly + * distributed). + * + * @param length the size of the array + * @param trueCount the number of true elements + * @return the boolean array + */ + boolean[] getBoolean(int length, int trueCount) { + boolean[] data = new boolean[length]; + for (int i = 0, pos; i < trueCount; i++) { + do { + pos = getInt(0, length); + } while (data[pos]); + data[pos] = true; + } + return data; + } + + /** + * Replace a random part of the string with another text. + * + * @param text the original text + * @param replacement the replacement + * @return the patched string + */ + String replace(String text, String replacement) { + int pos = getInt(0, text.length() - replacement.length()); + StringBuilder buffer = new StringBuilder(text); + buffer.replace(pos, pos + 7, replacement); + return buffer.toString(); + } + + /** + * Get a random number string. + * + * @param min the minimum value + * @param max the maximum value + * @return the number string + */ + String getNumberString(int min, int max) { + int len = getInt(min, max); + char[] buff = new char[len]; + for (int i = 0; i < len; i++) { + buff[i] = (char) getInt('0', '9'); + } + return new String(buff); + } + + /** + * Get random address data. + * + * @return the address + */ + String[] getAddress() { + String str1 = getString(10, 20); + String str2 = getString(10, 20); + String city = getString(10, 20); + String state = getString(2); + String zip = getNumberString(9, 9); + return new String[] { str1, str2, city, state, zip }; + } + + /** + * Get a random string. + * + * @param min the minimum size + * @param max the maximum size + * @return the string + */ + String getString(int min, int max) { + return getString(getInt(min, max)); + } + + /** + * Get a random string. + * + * @param len the size + * @return the string + */ + String getString(int len) { + char[] buff = new char[len]; + for (int i = 0; i < len; i++) { + buff[i] = (char) getInt('A', 'Z'); + } + return new String(buff); + } + + /** + * Generate a random permutation if the values 0 .. length. + * + * @param length the number of elements + * @return the random permutation + */ + int[] getPermutation(int length) { + int[] data = new int[length]; + for (int i = 0; i < length; i++) { + data[i] = i; + } + for (int i = 0; i < length; i++) { + int j = getInt(0, length); + int temp = data[i]; + data[i] = data[j]; + data[j] = temp; + } + return data; + } + + /** + * Create a big decimal value. + * + * @param value the value + * @param scale the scale + * @return the big decimal object + */ + BigDecimal getBigDecimal(int value, int scale) { + return new BigDecimal(BigInteger.valueOf(value), scale); + } + + /** + * Generate a last name composed of three elements + * + * @param i the last name index + * @return the name + */ + String getLastname(int i) { + String[] n = { "BAR", "OUGHT", "ABLE", "PRI", "PRES", "ESE", "ANTI", + "CALLY", "ATION", "EING" }; + StringBuilder buff = new StringBuilder(); + buff.append(n[i / 100]); + buff.append(n[(i / 10) % 10]); + buff.append(n[i % 10]); + return buff.toString(); + } + +} diff --git a/h2/src/test/org/h2/test/bench/BenchCThread.java b/h2/src/test/org/h2/test/bench/BenchCThread.java new file mode 100644 index 0000000..eee6b84 --- /dev/null +++ b/h2/src/test/org/h2/test/bench/BenchCThread.java @@ -0,0 +1,726 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.bench; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.HashMap; + +/** + * This class implements the functionality of one thread of BenchC. + */ +public class BenchCThread { + + private static final int OP_NEW_ORDER = 0, OP_PAYMENT = 1, + OP_ORDER_STATUS = 2, OP_DELIVERY = 3, + OP_STOCK_LEVEL = 4; + private static final BigDecimal ONE = new BigDecimal("1"); + + private final Database db; + private final int warehouseId; + private final int terminalId; + private final HashMap prepared = + new HashMap<>(); + private final BenchCRandom random; + private final BenchC bench; + + BenchCThread(Database db, BenchC bench, BenchCRandom random, int terminal) + throws SQLException { + this.db = db; + this.bench = bench; + this.terminalId = terminal; + db.setAutoCommit(false); + this.random = random; + warehouseId = random.getInt(1, bench.warehouses); + } + + /** + * Process the list of operations (a 'deck') in random order. + */ + void process() throws SQLException { + int[] deck = { OP_NEW_ORDER, OP_NEW_ORDER, OP_NEW_ORDER, + OP_NEW_ORDER, OP_NEW_ORDER, OP_NEW_ORDER, OP_NEW_ORDER, + OP_NEW_ORDER, OP_NEW_ORDER, OP_NEW_ORDER, OP_PAYMENT, + OP_PAYMENT, OP_PAYMENT, OP_PAYMENT, OP_PAYMENT, OP_PAYMENT, + OP_PAYMENT, OP_PAYMENT, OP_PAYMENT, OP_PAYMENT, + OP_ORDER_STATUS, OP_DELIVERY, OP_STOCK_LEVEL }; + int len = deck.length; + for (int i = 0; i < len; i++) { + int temp = deck[i]; + int j = random.getInt(0, len); + deck[i] = deck[j]; + deck[j] = temp; + } + for (int op : deck) { + switch (op) { + case OP_NEW_ORDER: + processNewOrder(); + break; + case OP_PAYMENT: + processPayment(); + break; + case OP_ORDER_STATUS: + processOrderStatus(); + break; + case OP_DELIVERY: + processDelivery(); + break; + case OP_STOCK_LEVEL: + processStockLevel(); + break; + default: + throw new AssertionError("op=" + op); + } + } + } + + private void processNewOrder() throws SQLException { + int dId = random.getInt(1, bench.districtsPerWarehouse); + int cId = random.getNonUniform(1023, 1, bench.customersPerDistrict); + int olCnt = random.getInt(5, 15); + boolean rollback = random.getInt(1, 100) == 1; + int[] supplyId = new int[olCnt]; + int[] itemId = new int[olCnt]; + int[] quantity = new int[olCnt]; + int allLocal = 1; + for (int i = 0; i < olCnt; i++) { + int w; + if (bench.warehouses > 1 && random.getInt(1, 100) == 1) { + do { + w = random.getInt(1, bench.warehouses); + } while (w != warehouseId); + allLocal = 0; + } else { + w = warehouseId; + } + supplyId[i] = w; + int item; + if (rollback && i == olCnt - 1) { + // unused order number + item = -1; + } else { + item = random.getNonUniform(8191, 1, bench.items); + } + itemId[i] = item; + quantity[i] = random.getInt(1, 10); + } + char[] bg = new char[olCnt]; + int[] stock = new int[olCnt]; + BigDecimal[] amt = new BigDecimal[olCnt]; + Timestamp datetime = new Timestamp(System.currentTimeMillis()); + PreparedStatement prep; + ResultSet rs; + + prep = prepare("UPDATE DISTRICT SET D_NEXT_O_ID=D_NEXT_O_ID+1 " + + "WHERE D_ID=? AND D_W_ID=?"); + prep.setInt(1, dId); + prep.setInt(2, warehouseId); + db.update(prep, "updateDistrict"); + prep = prepare("SELECT D_NEXT_O_ID, D_TAX FROM DISTRICT " + + "WHERE D_ID=? AND D_W_ID=?"); + prep.setInt(1, dId); + prep.setInt(2, warehouseId); + rs = db.query(prep); + rs.next(); + int oId = rs.getInt(1) - 1; + BigDecimal tax = rs.getBigDecimal(2); + rs.close(); + prep = prepare("SELECT C_DISCOUNT, C_LAST, C_CREDIT, W_TAX " + + "FROM CUSTOMER, WAREHOUSE " + + "WHERE C_ID=? AND W_ID=? AND C_W_ID=W_ID AND C_D_ID=?"); + prep.setInt(1, cId); + prep.setInt(2, warehouseId); + prep.setInt(3, dId); + rs = db.query(prep); + rs.next(); + BigDecimal discount = rs.getBigDecimal(1); + // c_last + rs.getString(2); + // c_credit + rs.getString(3); + BigDecimal wTax = rs.getBigDecimal(4); + rs.close(); + BigDecimal total = new BigDecimal("0"); + for (int number = 1; number <= olCnt; number++) { + int olId = itemId[number - 1]; + int olSupplyId = supplyId[number - 1]; + int olQuantity = quantity[number - 1]; + prep = prepare("SELECT I_PRICE, I_NAME, I_DATA " + + "FROM ITEM WHERE I_ID=?"); + prep.setInt(1, olId); + rs = db.query(prep); + if (!rs.next()) { + if (rollback) { + // item not found - correct behavior + db.rollback(); + return; + } + throw new SQLException("item not found: " + olId + " " + + olSupplyId); + } + BigDecimal price = rs.getBigDecimal(1); + // i_name + rs.getString(2); + String data = rs.getString(3); + rs.close(); + prep = prepare("SELECT S_QUANTITY, S_DATA, " + + "S_DIST_01, S_DIST_02, S_DIST_03, S_DIST_04, S_DIST_05, " + + "S_DIST_06, S_DIST_07, S_DIST_08, S_DIST_09, S_DIST_10 " + + "FROM STOCK WHERE S_I_ID=? AND S_W_ID=?"); + prep.setInt(1, olId); + prep.setInt(2, olSupplyId); + rs = db.query(prep); + if (!rs.next()) { + if (rollback) { + // item not found - correct behavior + db.rollback(); + return; + } + throw new SQLException("item not found: " + olId + " " + + olSupplyId); + } + int sQuantity = rs.getInt(1); + String sData = rs.getString(2); + String[] dist = new String[10]; + for (int i = 0; i < 10; i++) { + dist[i] = rs.getString(3 + i); + } + rs.close(); + String distInfo = dist[(dId - 1) % 10]; + stock[number - 1] = sQuantity; + if (data.contains("original") + && sData.contains("original")) { + bg[number - 1] = 'B'; + } else { + bg[number - 1] = 'G'; + } + if (sQuantity > olQuantity) { + sQuantity = sQuantity - olQuantity; + } else { + sQuantity = sQuantity - olQuantity + 91; + } + prep = prepare("UPDATE STOCK SET S_QUANTITY=? " + + "WHERE S_W_ID=? AND S_I_ID=?"); + prep.setInt(1, sQuantity); + prep.setInt(2, olSupplyId); + prep.setInt(3, olId); + db.update(prep, "updateStock"); + BigDecimal olAmount = new BigDecimal(olQuantity).multiply( + price).multiply(ONE.add(wTax).add(tax)).multiply( + ONE.subtract(discount)); + olAmount = olAmount.setScale(2, RoundingMode.HALF_UP); + amt[number - 1] = olAmount; + total = total.add(olAmount); + prep = prepare("INSERT INTO ORDER_LINE (OL_O_ID, OL_D_ID, OL_W_ID, OL_NUMBER, " + + "OL_I_ID, OL_SUPPLY_W_ID, " + + "OL_QUANTITY, OL_AMOUNT, OL_DIST_INFO) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"); + prep.setInt(1, oId); + prep.setInt(2, dId); + prep.setInt(3, warehouseId); + prep.setInt(4, number); + prep.setInt(5, olId); + prep.setInt(6, olSupplyId); + prep.setInt(7, olQuantity); + prep.setBigDecimal(8, olAmount); + prep.setString(9, distInfo); + db.update(prep, "insertOrderLine"); + } + prep = prepare("INSERT INTO ORDERS (O_ID, O_D_ID, O_W_ID, O_C_ID, " + + "O_ENTRY_D, O_OL_CNT, O_ALL_LOCAL) " + + "VALUES (?, ?, ?, ?, ?, ?, ?)"); + prep.setInt(1, oId); + prep.setInt(2, dId); + prep.setInt(3, warehouseId); + prep.setInt(4, cId); + prep.setTimestamp(5, datetime); + prep.setInt(6, olCnt); + prep.setInt(7, allLocal); + db.update(prep, "insertOrders"); + prep = prepare("INSERT INTO NEW_ORDER (NO_O_ID, NO_D_ID, NO_W_ID) " + + "VALUES (?, ?, ?)"); + prep.setInt(1, oId); + prep.setInt(2, dId); + prep.setInt(3, warehouseId); + db.update(prep, "insertNewOrder"); + db.commit(); + } + + private void processPayment() throws SQLException { + int dId = random.getInt(1, bench.districtsPerWarehouse); + int wId, cdId; + if (bench.warehouses > 1 && random.getInt(1, 100) <= 15) { + do { + wId = random.getInt(1, bench.warehouses); + } while (wId != warehouseId); + cdId = random.getInt(1, bench.districtsPerWarehouse); + } else { + wId = warehouseId; + cdId = dId; + } + boolean byName; + String last; + int cId = 1; + if (random.getInt(1, 100) <= 60) { + byName = true; + last = random.getLastname(random.getNonUniform(255, 0, 999)); + } else { + byName = false; + last = ""; + cId = random.getNonUniform(1023, 1, bench.customersPerDistrict); + } + BigDecimal amount = random.getBigDecimal(random.getInt(100, 500000), + 2); + Timestamp datetime = new Timestamp(System.currentTimeMillis()); + PreparedStatement prep; + ResultSet rs; + + prep = prepare("UPDATE DISTRICT SET D_YTD = D_YTD+? " + + "WHERE D_ID=? AND D_W_ID=?"); + prep.setBigDecimal(1, amount); + prep.setInt(2, dId); + prep.setInt(3, warehouseId); + db.update(prep, "updateDistrict"); + prep = prepare("UPDATE WAREHOUSE SET W_YTD=W_YTD+? WHERE W_ID=?"); + prep.setBigDecimal(1, amount); + prep.setInt(2, warehouseId); + db.update(prep, "updateWarehouse"); + prep = prepare("SELECT W_STREET_1, W_STREET_2, W_CITY, W_STATE, W_ZIP, W_NAME " + + "FROM WAREHOUSE WHERE W_ID=?"); + prep.setInt(1, warehouseId); + rs = db.query(prep); + rs.next(); + // w_street_1 + rs.getString(1); + // w_street_2 + rs.getString(2); + // w_city + rs.getString(3); + // w_state + rs.getString(4); + // w_zip + rs.getString(5); + String wName = rs.getString(6); + rs.close(); + prep = prepare("SELECT D_STREET_1, D_STREET_2, D_CITY, D_STATE, D_ZIP, D_NAME " + + "FROM DISTRICT WHERE D_ID=? AND D_W_ID=?"); + prep.setInt(1, dId); + prep.setInt(2, warehouseId); + rs = db.query(prep); + rs.next(); + // d_street_1 + rs.getString(1); + // d_street_2 + rs.getString(2); + // d_city + rs.getString(3); + // d_state + rs.getString(4); + // d_zip + rs.getString(5); + String dName = rs.getString(6); + rs.close(); + BigDecimal balance; + String credit; + if (byName) { + prep = prepare("SELECT COUNT(C_ID) FROM CUSTOMER " + + "WHERE C_LAST=? AND C_D_ID=? AND C_W_ID=?"); + prep.setString(1, last); + prep.setInt(2, cdId); + prep.setInt(3, wId); + rs = db.query(prep); + rs.next(); + int namecnt = rs.getInt(1); + rs.close(); + if (namecnt == 0) { + // TODO TPC-C: check if this can happen + db.rollback(); + return; + } + prep = prepare("SELECT C_FIRST, C_MIDDLE, C_ID, " + + "C_STREET_1, C_STREET_2, C_CITY, C_STATE, C_ZIP, " + + "C_PHONE, C_CREDIT, C_CREDIT_LIM, " + + "C_DISCOUNT, C_BALANCE, C_SINCE FROM CUSTOMER " + + "WHERE C_LAST=? AND C_D_ID=? AND C_W_ID=? " + + "ORDER BY C_FIRST"); + prep.setString(1, last); + prep.setInt(2, cdId); + prep.setInt(3, wId); + rs = db.query(prep); + // locate midpoint customer + if (namecnt % 2 != 0) { + namecnt++; + } + for (int n = 0; n < namecnt / 2; n++) { + rs.next(); + } + // c_first + rs.getString(1); + // c_middle + rs.getString(2); + cId = rs.getInt(3); + // c_street_1 + rs.getString(4); + // c_street_2 + rs.getString(5); + // c_city + rs.getString(6); + // c_state + rs.getString(7); + // c_zip + rs.getString(8); + // c_phone + rs.getString(9); + credit = rs.getString(10); + // c_credit_lim + rs.getString(11); + // c_discount + rs.getBigDecimal(12); + balance = rs.getBigDecimal(13); + // c_since + rs.getTimestamp(14); + rs.close(); + } else { + prep = prepare("SELECT C_FIRST, C_MIDDLE, C_LAST, " + + "C_STREET_1, C_STREET_2, C_CITY, C_STATE, C_ZIP, " + + "C_PHONE, C_CREDIT, C_CREDIT_LIM, " + + "C_DISCOUNT, C_BALANCE, C_SINCE FROM CUSTOMER " + + "WHERE C_ID=? AND C_D_ID=? AND C_W_ID=?"); + prep.setInt(1, cId); + prep.setInt(2, cdId); + prep.setInt(3, wId); + rs = db.query(prep); + rs.next(); + // c_first + rs.getString(1); + // c_middle + rs.getString(2); + // c_last + rs.getString(3); + // c_street_1 + rs.getString(4); + // c_street_2 + rs.getString(5); + // c_city + rs.getString(6); + // c_state + rs.getString(7); + // c_zip + rs.getString(8); + // c_phone + rs.getString(9); + credit = rs.getString(10); + // c_credit_lim + rs.getString(11); + // c_discount + rs.getBigDecimal(12); + balance = rs.getBigDecimal(13); + // c_since + rs.getTimestamp(14); + rs.close(); + } + balance = balance.add(amount); + if (credit.equals("BC")) { + prep = prepare("SELECT C_DATA INTO FROM CUSTOMER " + + "WHERE C_ID=? AND C_D_ID=? AND C_W_ID=?"); + prep.setInt(1, cId); + prep.setInt(2, cdId); + prep.setInt(3, wId); + rs = db.query(prep); + rs.next(); + String cData = rs.getString(1); + rs.close(); + String cNewData = "| " + cId + " " + cdId + " " + wId + + " " + dId + " " + warehouseId + " " + amount + " " + + cData; + if (cNewData.length() > 500) { + cNewData = cNewData.substring(0, 500); + } + prep = prepare("UPDATE CUSTOMER SET C_BALANCE=?, C_DATA=? " + + "WHERE C_ID=? AND C_D_ID=? AND C_W_ID=?"); + prep.setBigDecimal(1, balance); + prep.setString(2, cNewData); + prep.setInt(3, cId); + prep.setInt(4, cdId); + prep.setInt(5, wId); + db.update(prep, "updateCustomer"); + } else { + prep = prepare("UPDATE CUSTOMER SET C_BALANCE=? " + + "WHERE C_ID=? AND C_D_ID=? AND C_W_ID=?"); + prep.setBigDecimal(1, balance); + prep.setInt(2, cId); + prep.setInt(3, cdId); + prep.setInt(4, wId); + db.update(prep, "updateCustomer"); + } + // MySQL bug? +// String h_data = w_name + " " + d_name; + String hData = wName + " " + dName; + prep = prepare("INSERT INTO HISTORY (H_C_D_ID, H_C_W_ID, H_C_ID, H_D_ID, " + + "H_W_ID, H_DATE, H_AMOUNT, H_DATA) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); + prep.setInt(1, cdId); + prep.setInt(2, wId); + prep.setInt(3, cId); + prep.setInt(4, dId); + prep.setInt(5, warehouseId); + prep.setTimestamp(6, datetime); + prep.setBigDecimal(7, amount); + prep.setString(8, hData); + db.update(prep, "insertHistory"); + db.commit(); + } + + private void processOrderStatus() throws SQLException { + int dId = random.getInt(1, bench.districtsPerWarehouse); + boolean byName; + String last = null; + int cId = -1; + if (random.getInt(1, 100) <= 60) { + byName = true; + last = random.getLastname(random.getNonUniform(255, 0, 999)); + } else { + byName = false; + cId = random.getNonUniform(1023, 1, bench.customersPerDistrict); + } + PreparedStatement prep; + ResultSet rs; + + prep = prepare("UPDATE DISTRICT SET D_NEXT_O_ID=-1 WHERE D_ID=-1"); + db.update(prep, "updateDistrict"); + if (byName) { + prep = prepare("SELECT COUNT(C_ID) FROM CUSTOMER " + + "WHERE C_LAST=? AND C_D_ID=? AND C_W_ID=?"); + prep.setString(1, last); + prep.setInt(2, dId); + prep.setInt(3, warehouseId); + rs = db.query(prep); + rs.next(); + int namecnt = rs.getInt(1); + rs.close(); + if (namecnt == 0) { + // TODO TPC-C: check if this can happen + db.rollback(); + return; + } + prep = prepare("SELECT C_BALANCE, C_FIRST, C_MIDDLE, C_ID " + + "FROM CUSTOMER " + + "WHERE C_LAST=? AND C_D_ID=? AND C_W_ID=? " + + "ORDER BY C_FIRST"); + prep.setString(1, last); + prep.setInt(2, dId); + prep.setInt(3, warehouseId); + rs = db.query(prep); + if (namecnt % 2 != 0) { + namecnt++; + } + for (int n = 0; n < namecnt / 2; n++) { + rs.next(); + } + // c_balance + rs.getBigDecimal(1); + // c_first + rs.getString(2); + // c_middle + rs.getString(3); + rs.close(); + } else { + prep = prepare("SELECT C_BALANCE, C_FIRST, C_MIDDLE, C_LAST " + + "FROM CUSTOMER " + + "WHERE C_ID=? AND C_D_ID=? AND C_W_ID=?"); + prep.setInt(1, cId); + prep.setInt(2, dId); + prep.setInt(3, warehouseId); + rs = db.query(prep); + rs.next(); + // c_balance + rs.getBigDecimal(1); + // c_first + rs.getString(2); + // c_middle + rs.getString(3); + // c_last + rs.getString(4); + rs.close(); + } + prep = prepare("SELECT MAX(O_ID) " + + "FROM ORDERS WHERE O_C_ID=? AND O_D_ID=? AND O_W_ID=?"); + prep.setInt(1, cId); + prep.setInt(2, dId); + prep.setInt(3, warehouseId); + rs = db.query(prep); + int oId = -1; + if (rs.next()) { + oId = rs.getInt(1); + if (rs.wasNull()) { + oId = -1; + } + } + rs.close(); + if (oId != -1) { + prep = prepare("SELECT O_ID, O_CARRIER_ID, O_ENTRY_D " + + "FROM ORDERS WHERE O_ID=?"); + prep.setInt(1, oId); + rs = db.query(prep); + rs.next(); + oId = rs.getInt(1); + // o_carrier_id + rs.getInt(2); + // o_entry_d + rs.getTimestamp(3); + rs.close(); + prep = prepare("SELECT OL_I_ID, OL_SUPPLY_W_ID, OL_QUANTITY, " + + "OL_AMOUNT, OL_DELIVERY_D FROM ORDER_LINE " + + "WHERE OL_O_ID=? AND OL_D_ID=? AND OL_W_ID=?"); + prep.setInt(1, oId); + prep.setInt(2, dId); + prep.setInt(3, warehouseId); + rs = db.query(prep); + while (rs.next()) { + // o_i_id + rs.getInt(1); + // ol_supply_w_id + rs.getInt(2); + // ol_quantity + rs.getInt(3); + // ol_amount + rs.getBigDecimal(4); + // ol_delivery_d + rs.getTimestamp(5); + } + rs.close(); + } + db.commit(); + } + + private void processDelivery() throws SQLException { + int carrierId = random.getInt(1, 10); + Timestamp datetime = new Timestamp(System.currentTimeMillis()); + PreparedStatement prep; + ResultSet rs; + + prep = prepare("UPDATE DISTRICT SET D_NEXT_O_ID=-1 WHERE D_ID=-1"); + db.update(prep, "updateDistrict"); + for (int dId = 1; dId <= bench.districtsPerWarehouse; dId++) { + prep = prepare("SELECT MIN(NO_O_ID) FROM NEW_ORDER " + + "WHERE NO_D_ID=? AND NO_W_ID=?"); + prep.setInt(1, dId); + prep.setInt(2, warehouseId); + rs = db.query(prep); + int noId = -1; + if (rs.next()) { + noId = rs.getInt(1); + if (rs.wasNull()) { + noId = -1; + } + } + rs.close(); + if (noId != -1) { + prep = prepare("DELETE FROM NEW_ORDER " + + "WHERE NO_O_ID=? AND NO_D_ID=? AND NO_W_ID=?"); + prep.setInt(1, noId); + prep.setInt(2, dId); + prep.setInt(3, warehouseId); + db.update(prep, "deleteNewOrder"); + prep = prepare("SELECT O_C_ID FROM ORDERS " + + "WHERE O_ID=? AND O_D_ID=? AND O_W_ID=?"); + prep.setInt(1, noId); + prep.setInt(2, dId); + prep.setInt(3, warehouseId); + rs = db.query(prep); + rs.next(); + // o_c_id + rs.getInt(1); + rs.close(); + prep = prepare("UPDATE ORDERS SET O_CARRIER_ID=? " + + "WHERE O_ID=? AND O_D_ID=? AND O_W_ID=?"); + prep.setInt(1, carrierId); + prep.setInt(2, noId); + prep.setInt(3, dId); + prep.setInt(4, warehouseId); + db.update(prep, "updateOrders"); + prep = prepare("UPDATE ORDER_LINE SET OL_DELIVERY_D=? " + + "WHERE OL_O_ID=? AND OL_D_ID=? AND OL_W_ID=?"); + prep.setTimestamp(1, datetime); + prep.setInt(2, noId); + prep.setInt(3, dId); + prep.setInt(4, warehouseId); + db.update(prep, "updateOrderLine"); + prep = prepare("SELECT SUM(OL_AMOUNT) FROM ORDER_LINE " + + "WHERE OL_O_ID=? AND OL_D_ID=? AND OL_W_ID=?"); + prep.setInt(1, noId); + prep.setInt(2, dId); + prep.setInt(3, warehouseId); + rs = db.query(prep); + rs.next(); + BigDecimal amount = rs.getBigDecimal(1); + rs.close(); + prep = prepare("UPDATE CUSTOMER SET C_BALANCE=C_BALANCE+? " + + "WHERE C_ID=? AND C_D_ID=? AND C_W_ID=?"); + prep.setBigDecimal(1, amount); + prep.setInt(2, noId); + prep.setInt(3, dId); + prep.setInt(4, warehouseId); + db.update(prep, "updateCustomer"); + } + } + db.commit(); + } + + private void processStockLevel() throws SQLException { + int dId = (terminalId % bench.districtsPerWarehouse) + 1; + int threshold = random.getInt(10, 20); + PreparedStatement prep; + ResultSet rs; + + prep = prepare("UPDATE DISTRICT SET D_NEXT_O_ID=-1 WHERE D_ID=-1"); + db.update(prep, "updateDistrict"); + + prep = prepare("SELECT D_NEXT_O_ID FROM DISTRICT " + + "WHERE D_ID=? AND D_W_ID=?"); + prep.setInt(1, dId); + prep.setInt(2, warehouseId); + rs = db.query(prep); + rs.next(); + int oId = rs.getInt(1); + rs.close(); + prep = prepare("SELECT COUNT(DISTINCT S_I_ID) " + + "FROM ORDER_LINE, STOCK WHERE " + + "OL_W_ID=? AND " + + "OL_D_ID=? AND " + + "OL_O_ID=?-20 AND " + + "S_W_ID=? AND " + + "S_I_ID=OL_I_ID AND " + + "S_QUANTITY replace = new ArrayList<>(); + private String currentAction; + private long startTimeNs; + private long initialGCTime; + private Connection conn; + private Statement stat; + private long lastTrace; + private final Random random = new Random(1); + private ArrayList results = new ArrayList<>(); + private int totalTime; + private int totalGCTime; + private final AtomicInteger executedStatements = new AtomicInteger(); + + private Server serverH2; + private Object serverDerby; + private boolean serverHSQLDB; + + /** + * Get the database name. + * + * @return the database name + */ + String getName() { + return name; + } + + /** + * Get the total measured time. + * + * @return the time + */ + int getTotalTime() { + return totalTime; + } + + /** + * Get the total measured GC time. + * + * @return the time in milliseconds + */ + int getTotalGCTime() { + return totalGCTime; + } + + /** + * Get the result array. + * + * @return the result array + */ + ArrayList getResults() { + return results; + } + + ArrayList reset() { + executedStatements.set(0); + totalTime = 0; + totalGCTime = 0; + lastTrace = 0; + ArrayList measurements = results; + results = new ArrayList<>(); + return measurements; + } + + /** + * Get the random number generator. + * + * @return the generator + */ + Random getRandom() { + return random; + } + + /** + * Start the server if the this is a remote connection. + */ + void startServer() throws Exception { + if (url.startsWith("jdbc:h2:tcp:")) { + try { + serverH2 = Server.createTcpServer("-ifNotExists").start(); + } catch (SQLException e) { + serverH2 = Server.createTcpServer().start(); + } + Thread.sleep(100); + } else if (url.startsWith("jdbc:derby://")) { + serverDerby = Class.forName( + "org.apache.derby.drda.NetworkServerControl").getDeclaredConstructor().newInstance(); + Method m = serverDerby.getClass().getMethod("start", PrintWriter.class); + m.invoke(serverDerby, new Object[] { null }); + // serverDerby = new NetworkServerControl(); + // serverDerby.start(null); + Thread.sleep(100); + } else if (url.startsWith("jdbc:hsqldb:hsql:")) { + if (!serverHSQLDB) { + Class c; + try { + c = Class.forName("org.hsqldb.server.Server"); + } catch (Exception e) { + c = Class.forName("org.hsqldb.Server"); + } + Method m = c.getMethod("main", String[].class); + m.invoke(null, new Object[] { new String[] { "-database.0", + "data/mydb;hsqldb.default_table_type=cached;hsqldb.write_delay_millis=1000", + "-dbname.0", "xdb" } }); + // org.hsqldb.Server.main(new String[]{"-database.0", "mydb", "-dbname.0", "xdb"}); + serverHSQLDB = true; + Thread.sleep(100); + } + } + } + + /** + * Stop the server if this is a remote connection. + */ + void stopServer() throws Exception { + if (serverH2 != null) { + serverH2.stop(); + serverH2 = null; + } + if (serverDerby != null) { + Method m = serverDerby.getClass().getMethod("shutdown"); + // cast for JDK 1.5 + m.invoke(serverDerby, (Object[]) null); + // serverDerby.shutdown(); + serverDerby = null; + } else if (serverHSQLDB) { + // can not shut down (shutdown calls System.exit) + // openConnection(); + // update("SHUTDOWN"); + // closeConnection(); + // serverHSQLDB = false; + } + } + + /** + * Parse a database configuration and create a database object from it. + * + * @param test the test application + * @param id the database id + * @param dbString the configuration string + * @param properties to use + * @return a new database object with the given settings + */ + static Database parse(DatabaseTest test, int id, String dbString, Properties properties) { + try { + StringTokenizer tokenizer = new StringTokenizer(dbString, ","); + Database db = new Database(); + db.id = id; + db.test = test; + db.name = tokenizer.nextToken().trim(); + String driver = tokenizer.nextToken().trim(); + Class.forName(driver); + db.url = tokenizer.nextToken().trim(); + db.user = tokenizer.nextToken().trim(); + db.password = null; + if (tokenizer.hasMoreTokens()) { + db.password = tokenizer.nextToken().trim(); + } + db.setTranslations(properties); + return db; + } catch (Exception e) { + System.out.println("Cannot load database " + dbString + ": " + e); + return null; + } + } + + /** + * Open a new database connection. This connection must be closed + * by calling conn.close(). + * + * @return the opened connection + */ + Connection openNewConnection() throws SQLException { + Connection newConn = DriverManager.getConnection(url, user, password); + if (url.startsWith("jdbc:derby:")) { + // Derby: use higher cache size + try (Statement s = newConn.createStatement()) { + // stat.execute("CALL + // SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY( + // 'derby.storage.pageCacheSize', '64')"); + // stat.execute("CALL + // SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY( + // 'derby.storage.pageSize', '8192')"); + } + } else if (url.startsWith("jdbc:hsqldb:")) { + // HSQLDB: use a WRITE_DELAY of 1 second + 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; + } + + /** + * Open the database connection. + */ + void openConnection() throws SQLException { + conn = openNewConnection(); + stat = conn.createStatement(); + } + + /** + * Close the database connection. + */ + void closeConnection() throws SQLException { + // if(!serverHSQLDB && url.startsWith("jdbc:hsqldb:")) { + // stat.execute("SHUTDOWN"); + // } + conn.close(); + stat = null; + conn = null; + } + + /** + * Initialize the SQL statement translation of this database. + * + * @param prop the properties with the translations to use + */ + void setTranslations(Properties prop) { + String databaseType = url.substring("jdbc:".length()); + databaseType = databaseType.substring(0, databaseType.indexOf(':')); + for (Object k : prop.keySet()) { + String key = (String) k; + if (key.startsWith(databaseType + ".")) { + String pattern = key.substring(databaseType.length() + 1); + pattern = pattern.replace('_', ' '); + pattern = StringUtils.toUpperEnglish(pattern); + String replacement = prop.getProperty(key); + replace.add(new String[]{pattern, replacement}); + } + } + } + + /** + * Prepare a SQL statement. + * + * @param sql the SQL statement + * @return the prepared statement + */ + PreparedStatement prepare(String sql) throws SQLException { + sql = getSQL(sql); + return conn.prepareStatement(sql); + } + + private String getSQL(String sql) { + for (String[] pair : replace) { + String pattern = pair[0]; + String replacement = pair[1]; + sql = StringUtils.replaceAll(sql, pattern, replacement); + } + return sql; + } + + /** + * Start the benchmark. + * + * @param bench the benchmark + * @param action the action + */ + void start(Bench bench, String action) { + this.currentAction = bench.getName() + ": " + action; + this.startTimeNs = System.nanoTime(); + this.initialGCTime = getGarbageCollectionTime(); + } + + /** + * This method is called when the test run ends. This will stop collecting + * data. + */ + void end() { + long time = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTimeNs); + long gcCollectionTime = getGarbageCollectionTime() - initialGCTime; + log(currentAction, "ms", (int) time); + if (test.isCollect()) { + totalTime += time; + totalGCTime += gcCollectionTime; + } + } + + public static long getGarbageCollectionTime() { + long totalGCTime = 0; + for (GarbageCollectorMXBean gcMXBean : ManagementFactory.getGarbageCollectorMXBeans()) { + long collectionTime = gcMXBean.getCollectionTime(); + if(collectionTime > 0) { + totalGCTime += collectionTime; + } + } + return totalGCTime; + } + + /** + * Drop a table. Errors are ignored. + * + * @param table the table name + */ + void dropTable(String table) { + try { + update("DROP TABLE " + table); + } catch (Exception e) { + // ignore - table may not exist + } + } + + /** + * Execute an SQL statement. + * + * @param prep the prepared statement + * @param traceMessage the trace message + */ + void update(PreparedStatement prep, String traceMessage) throws SQLException { + test.trace(traceMessage); + prep.executeUpdate(); + if (test.isCollect()) { + executedStatements.incrementAndGet(); + } + } + + /** + * Execute an SQL statement. + * + * @param sql the SQL statement + */ + void update(String sql) throws SQLException { + sql = getSQL(sql); + if (sql.trim().length() > 0) { + if (test.isCollect()) { + executedStatements.incrementAndGet(); + } + stat.execute(sql); + } else { + System.out.println("?"); + } + } + + /** + * Enable or disable auto-commit. + * + * @param b false to disable + */ + void setAutoCommit(boolean b) throws SQLException { + conn.setAutoCommit(b); + } + + /** + * Commit a transaction. + */ + void commit() throws SQLException { + conn.commit(); + } + + /** + * Roll a transaction back. + */ + void rollback() throws SQLException { + conn.rollback(); + } + + /** + * Print trace information if trace is enabled. + * + * @param action the action + * @param i the current value + * @param max the maximum value + */ + void trace(String action, int i, int max) { + if (TRACE) { + long time = System.nanoTime(); + if (i == 0 || lastTrace == 0) { + lastTrace = time; + } else if (time > lastTrace + TimeUnit.SECONDS.toNanos(1)) { + System.out.println(action + ": " + ((100 * i / max) + "%")); + lastTrace = time; + } + } + } + + /** + * If data collection is enabled, add the currently used memory size to the + * log. + * + * @param bench the benchmark + * @param action the action + */ + void logMemory(Bench bench, String action) { + log(bench.getName() + ": " + action, "MB", TestBase.getMemoryUsed()); + } + + /** + * If data collection is enabled, add this information to the log. + * + * @param action the action + * @param unit of the value + * @param value the value + */ + void log(String action, String unit, int value) { + if (test.isCollect()) { + results.add(new Measurement(action, unit, value)); + } + } + + /** + * Execute a query. + * + * @param prep the prepared statement + * @return the result set + */ + ResultSet query(PreparedStatement prep) throws SQLException { + // long time = System.nanoTime(); + ResultSet rs = prep.executeQuery(); + // time = System.nanoTime() - time; + // if(time > 100) { + // System.out.println("time="+time); + // } + if (test.isCollect()) { + executedStatements.incrementAndGet(); + } + return rs; + } + + /** + * Execute a query and read all rows. + * + * @param prep the prepared statement + */ + void queryReadResult(PreparedStatement prep) throws SQLException { + try (ResultSet rs = query(prep)) { + ResultSetMetaData meta = rs.getMetaData(); + int columnCount = meta.getColumnCount(); + while (rs.next()) { + for (int i = 0; i < columnCount; i++) { + rs.getString(i + 1); + } + } + } + } + + /** + * Get the number of executed statements. + * + * @return the number of statements + */ + int getExecutedStatements() { + return executedStatements.get(); + } + + /** + * Get the database id. + * + * @return the id + */ + int getId() { + return id; + } + + /** + * The interface used for a test. + */ + public interface DatabaseTest { + + /** + * Whether data needs to be collected. + * + * @return true if yes + */ + boolean isCollect(); + + /** + * Print a message to system out if trace is enabled. + * + * @param msg the message + */ + void trace(String msg); + + /** + * Load testing properties + * @return Properties + * @throws IOException on failure + */ + default Properties loadProperties() throws IOException { + Properties prop = new Properties(); + try (InputStream in = getClass().getResourceAsStream("test.properties")) { + prop.load(in); + } + return prop; + } + } + + public static final class Measurement + { + final String name; + final String unit; + final int value; + + public Measurement(String name, String unit, int value) { + this.name = name; + this.unit = unit; + this.value = value; + } + } +} diff --git a/h2/src/test/org/h2/test/bench/TestPerformance.java b/h2/src/test/org/h2/test/bench/TestPerformance.java new file mode 100644 index 0000000..e8b8ee4 --- /dev/null +++ b/h2/src/test/org/h2/test/bench/TestPerformance.java @@ -0,0 +1,224 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.bench; + +import java.io.FileWriter; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Properties; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.util.JdbcUtils; + +/** + * The main controller class of the benchmark application. + * To run the benchmark, call the main method of this class. + */ +public class TestPerformance implements Database.DatabaseTest { + + /** + * Whether data should be collected. + */ + boolean collect; + + /** + * The flag used to enable or disable trace messages. + */ + boolean trace; + + /** + * This method is called when executing this sample application. + * + * @param args the command line parameters + */ + public static void main(String... args) throws Exception { + new TestPerformance().test(args); + } + + private static Connection getResultConnection() throws SQLException { + org.h2.Driver.load(); + return DriverManager.getConnection("jdbc:h2:./data/results"); + } + + private static void openResults() throws SQLException { + Connection conn = null; + Statement stat = null; + try { + conn = getResultConnection(); + stat = conn.createStatement(); + stat.execute( + "CREATE TABLE IF NOT EXISTS RESULTS(" + + "TESTID INT, TEST VARCHAR, " + + "UNIT VARCHAR, DBID INT, DB VARCHAR, RESULT VARCHAR)"); + } finally { + JdbcUtils.closeSilently(stat); + JdbcUtils.closeSilently(conn); + } + } + + private void test(String... args) throws Exception { + int dbId = -1; + boolean exit = false; + String out = "benchmark.html"; + Properties prop = loadProperties(); + int size = Integer.parseInt(prop.getProperty("size")); + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + if ("-db".equals(arg)) { + dbId = Integer.parseInt(args[++i]); + } else if ("-init".equals(arg)) { + FileUtils.deleteRecursive("data", true); + } else if ("-out".equals(arg)) { + out = args[++i]; + } else if ("-trace".equals(arg)) { + trace = true; + } else if ("-exit".equals(arg)) { + exit = true; + } else if ("-size".equals(arg)) { + size = Integer.parseInt(args[++i]); + } + } + ArrayList dbs = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + if (dbId != -1 && i != dbId) { + continue; + } + String dbString = prop.getProperty("db" + i); + if (dbString != null) { + Database db = Database.parse(this, i, dbString, prop); + if (db != null) { + dbs.add(db); + } + } + } + ArrayList tests = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + String testString = prop.getProperty("test" + i); + if (testString != null) { + Bench bench = (Bench) Class.forName(testString).getDeclaredConstructor().newInstance(); + tests.add(bench); + } + } + testAll(dbs, tests, size); + collect = false; + if (dbs.isEmpty()) { + return; + } + ArrayList results = dbs.get(0).getResults(); + try (Connection conn = getResultConnection()) { + openResults(); + try (PreparedStatement prep = conn.prepareStatement( + "INSERT INTO RESULTS(TESTID, TEST, " + + "UNIT, DBID, DB, RESULT) VALUES(?, ?, ?, ?, ?, ?)")) { + for (int i = 0; i < results.size(); i++) { + Database.Measurement res = results.get(i); + prep.setInt(1, i); + prep.setString(2, res.name); + prep.setString(3, res.unit); + for (Database db : dbs) { + prep.setInt(4, db.getId()); + prep.setString(5, db.getName()); + Database.Measurement measurement = db.getResults().get(i); + prep.setString(6, String.valueOf(measurement.value)); + prep.execute(); + } + } + } + + try (Statement stat = conn.createStatement(); + PrintWriter writer = new PrintWriter(new FileWriter(out)); + ResultSet rs = stat.executeQuery( + "CALL '' " + + "|| (SELECT GROUP_CONCAT('' " + + "ORDER BY DBID SEPARATOR '') FROM " + + "(SELECT DISTINCT DBID, DB FROM RESULTS))" + + "|| '' || CHAR(10) " + + "|| (SELECT GROUP_CONCAT('' || ( " + + "SELECT GROUP_CONCAT('' " + + "ORDER BY DBID SEPARATOR '') FROM RESULTS R2 WHERE " + + "R2.TESTID = R1.TESTID) || '' " + + "ORDER BY TESTID SEPARATOR CHAR(10)) FROM " + + "(SELECT DISTINCT TESTID, TEST, UNIT FROM RESULTS) R1)" + + "|| '
    Test CaseUnit' || DB || '
    ' || TEST || " + + "'' || UNIT || '' || RESULT || '
    '")) { + rs.next(); + String result = rs.getString(1); + writer.println(result); + } + } + + if (exit) { + System.exit(0); + } + } + + private void testAll(ArrayList dbs, ArrayList tests, + int size) throws Exception { + for (int i = 0; i < dbs.size(); i++) { + if (i > 0) { + Thread.sleep(1000); + } + // calls garbage collection + TestBase.getMemoryUsed(); + Database db = dbs.get(i); + System.out.println(); + System.out.println("Testing the performance of " + db.getName()); + db.startServer(); + Connection conn = db.openNewConnection(); + DatabaseMetaData meta = conn.getMetaData(); + System.out.println("Database: " + meta.getDatabaseProductName() + " " + meta.getDatabaseProductVersion()); + System.out.println("Driver: " + meta.getDriverName() + " " + meta.getDriverVersion()); + runDatabase(db, tests, 1); + runDatabase(db, tests, 1); + db.reset(); + collect = true; + runDatabase(db, tests, size); + conn.close(); + db.log("Executed statements", "#", db.getExecutedStatements()); + db.log("Total time", "ms", db.getTotalTime()); + System.out.println("Total time: " + db.getTotalTime() + " ms"); + int statPerSec = (int) (db.getExecutedStatements() * 1000L / db.getTotalTime()); + db.log("Statements per second", "#/s", statPerSec); + System.out.println("Statements per second: " + statPerSec); + System.out.println("GC overhead: " + (100 * db.getTotalGCTime() / db.getTotalTime()) + "%"); + collect = false; + db.stopServer(); + } + } + + private static void runDatabase(Database db, ArrayList tests, + int size) throws Exception { + for (Bench bench : tests) { + runTest(db, bench, size); + } + } + + private static void runTest(Database db, Bench bench, int size) + throws Exception { + bench.init(db, size); + bench.runTest(); + } + + @Override + public void trace(String msg) { + if (trace) { + System.out.println(msg); + } + } + + @Override + public boolean isCollect() { + return collect; + } + +} diff --git a/h2/src/test/org/h2/test/bench/TestScalability.java b/h2/src/test/org/h2/test/bench/TestScalability.java new file mode 100644 index 0000000..998cde6 --- /dev/null +++ b/h2/src/test/org/h2/test/bench/TestScalability.java @@ -0,0 +1,263 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.bench; + +import java.io.FileWriter; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.bench.Database.Measurement; + +/** + * Used to compare scalability between the old engine and the new MVStore + * engine. Mostly it runs BenchB with various numbers of threads. + */ +public class TestScalability implements Database.DatabaseTest { + + /** + * Whether data should be collected. + */ + boolean collect; + + /** + * The flag used to enable or disable trace messages. + */ + boolean trace; + + /** + * This method is called when executing this sample application. + * + * @param args the command line parameters + */ + public static void main(String... args) throws Exception { + new TestScalability().test(args); + } + + private static Connection getResultConnection() throws SQLException { + org.h2.Driver.load(); + return DriverManager.getConnection("jdbc:h2:./data/results"); + } + + private static void openResults() throws SQLException { + try (Connection conn = getResultConnection(); + Statement stat = conn.createStatement()) { + stat.execute( + "CREATE TABLE IF NOT EXISTS RESULTS(TESTID INT, " + + "TEST VARCHAR, UNIT VARCHAR, DBID INT, " + + "DB VARCHAR, TCNT INT, RESULT VARCHAR)"); + } + } + + private void test(String... args) throws Exception { + int dbId = -1; + boolean exit = false; + String out = "scalability.html"; + int size = 400; + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + if ("-db".equals(arg)) { + dbId = Integer.parseInt(args[++i]); + } else if ("-init".equals(arg)) { + FileUtils.deleteRecursive("data", true); + } else if ("-out".equals(arg)) { + out = args[++i]; + } else if ("-trace".equals(arg)) { + trace = true; + } else if ("-exit".equals(arg)) { + exit = true; + } else if ("-size".equals(arg)) { + size = Integer.parseInt(args[++i]); + } + } + + Properties prop = loadProperties(); + + ArrayList dbs = new ArrayList<>(); + for (int id = 0; id < 100; id++) { + if (dbId != -1 && id != dbId) { + continue; + } + String dbString = prop.getProperty("db" + id); + if (dbString != null) { + Database db = Database.parse(this, id, dbString, prop); + if (db != null) { + int runCount = 8; + String valueStr = prop.getProperty("runCount" + id); + if (valueStr != null) { + runCount = Integer.parseInt(valueStr); + } + dbs.add(new RunSequence(db, runCount)); + } + } + } + + BenchB test = new BenchB() { + // Since we focus on scalability here, lets emphasize multi-threaded + // part of the test (transactions) and minimize impact of the init. + @Override + protected int getTransactionsPerClient(int size) { + return size * 8; + } + }; + testAll(dbs, test, size); + + List results = dbs.get(0).results.get(0); + try (Connection conn = getResultConnection()) { + openResults(); + try (PreparedStatement prep = conn.prepareStatement( + "INSERT INTO RESULTS(TESTID, " + + "TEST, UNIT, DBID, DB, TCNT, RESULT) VALUES(?, ?, ?, ?, ?, ?, ?)")) { + for (int i = 0; i < results.size(); i++) { + Measurement res = results.get(i); + prep.setInt(1, i); + prep.setString(2, res.name); + prep.setString(3, res.unit); + for (RunSequence runSequence : dbs) { + Database db = runSequence.database; + int threadCount = 1; + for (List result : runSequence.results) { + if (result.size() > i) { + Measurement measurement = result.get(i); + prep.setInt(4, db.getId()); + prep.setString(5, db.getName()); + prep.setInt(6, threadCount); + prep.setString(7, String.valueOf(measurement.value)); + prep.execute(); + threadCount <<= 1; + } + } + } + } + } + + try (Statement stat = conn.createStatement(); + PrintWriter writer = new PrintWriter(new FileWriter(out)); + ResultSet rs = stat.executeQuery( + "CALL '" + + "' " + + "|| (SELECT GROUP_CONCAT('' " + + "ORDER BY TCNT SEPARATOR '') FROM " + + "(SELECT TCNT, COUNT(*) COLSPAN FROM (SELECT DISTINCT DB, TCNT FROM RESULTS) GROUP BY TCNT))" + + "|| '' || CHAR(10) " + + "|| '' || (SELECT GROUP_CONCAT('' ORDER BY TCNT, DB SEPARATOR '')" + + " FROM (SELECT DISTINCT DB, TCNT FROM RESULTS)) || '' || CHAR(10) " + + "|| (SELECT GROUP_CONCAT('' || ( " + + "SELECT GROUP_CONCAT('' ORDER BY TCNT,DB SEPARATOR '')" + + " FROM RESULTS R2 WHERE R2.TESTID = R1.TESTID) || '' " + + "ORDER BY TESTID SEPARATOR CHAR(10)) FROM " + + "(SELECT DISTINCT TESTID, TEST, UNIT FROM RESULTS) R1)" + + "|| '
    Test CaseUnit' || TCNT || '
    ' || DB || '
    ' || TEST || '' || UNIT || '' || RESULT || '
    '")) { + rs.next(); + String result = rs.getString(1); + writer.println(result); + } + } + + if (exit) { + System.exit(0); + } + } + + private void testAll(ArrayList runSequences, BenchB test, int size) throws Exception { + Database lastDb = null; + Connection conn = null; + for (RunSequence runSequence : runSequences) { + Database db = runSequence.database; + try { + if (lastDb != null) { + conn.close(); + lastDb.stopServer(); + Thread.sleep(1000); + // calls garbage collection + TestBase.getMemoryUsed(); + } + String dbName = db.getName(); + System.out.println("------------------"); + System.out.println("Testing the performance of " + dbName); + db.startServer(); + // hold one connection open during the whole test to keep database up + conn = db.openNewConnection(); + test.init(db, size); + + for (int runNo = 0, threadCount = 1; runNo < runSequence.runCount; runNo++, threadCount <<= 1) { + System.out.println("Testing the performance of " + dbName + + " (" + threadCount + " threads)"); + + DatabaseMetaData meta = conn.getMetaData(); + System.out.println(" " + meta.getDatabaseProductName() + " " + + meta.getDatabaseProductVersion()); + test.setThreadCount(threadCount); + + test.runTest(); + test.runTest(); + db.reset(); + collect = true; + test.runTest(); + + int executedStatements = db.getExecutedStatements(); + int totalTime = db.getTotalTime(); + int totalGCTime = db.getTotalGCTime(); + db.log("Executed statements", "#", executedStatements); + db.log("Total time", "ms", totalTime); + int statPerSec = (int) (executedStatements * 1000L / totalTime); + db.log("Statements per second", "#/s", statPerSec); + collect = false; + System.out.println("Statements per second: " + statPerSec); + System.out.println("GC overhead: " + (100 * totalGCTime / totalTime) + "%"); + ArrayList measurements = db.reset(); + runSequence.results.add(measurements); + } + } catch (Throwable ex) { + ex.printStackTrace(); + } finally { + lastDb = db; + } + } + if (lastDb != null) { + conn.close(); + lastDb.stopServer(); + } + } + + /** + * Print a message to system out if trace is enabled. + * + * @param s the message + */ + @Override + public void trace(String s) { + if (trace) { + System.out.println(s); + } + } + + @Override + public boolean isCollect() { + return collect; + } + + private static final class RunSequence + { + final Database database; + final int runCount; + final List> results = new ArrayList<>(); + + public RunSequence(Database dataBase, int runCount) { + this.database = dataBase; + this.runCount = runCount; + } + } +} diff --git a/h2/src/test/org/h2/test/bench/package.html b/h2/src/test/org/h2/test/bench/package.html new file mode 100644 index 0000000..e33caee --- /dev/null +++ b/h2/src/test/org/h2/test/bench/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +The implementation of the benchmark application. + +

    \ No newline at end of file diff --git a/h2/src/test/org/h2/test/bench/test.properties b/h2/src/test/org/h2/test/bench/test.properties new file mode 100644 index 0000000..82b0805 --- /dev/null +++ b/h2/src/test/org/h2/test/bench/test.properties @@ -0,0 +1,45 @@ +db1 = H2, org.h2.Driver, jdbc:h2:./data/test, sa, sa + +#db1 = H2 (forced), org.h2.Driver, jdbc:h2:./data/test;LOG=1;LOCK_TIMEOUT=10000;LOCK_MODE=3;ACCESS_MODE_DATA=rwd, sa, sa +#db1 = H2 (nio), org.h2.Driver, jdbc:h2:nio:data/test;LOCK_TIMEOUT=10000;LOCK_MODE=3, sa, sa +#db1 = H2 (nioMapped), org.h2.Driver, jdbc:h2:nioMapped:data/test;LOCK_TIMEOUT=10000;LOCK_MODE=3, sa, sa +#db1 = H2 (XTEA), org.h2.Driver, jdbc:h2:./data/test_xtea;LOCK_TIMEOUT=10000;LOCK_MODE=3;CIPHER=XTEA, sa, sa 123 +#db1 = H2 (AES), org.h2.Driver, jdbc:h2:./data/test_aes;LOCK_TIMEOUT=10000;LOCK_MODE=3;CIPHER=AES, sa, sa 123 + +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 +db6 = Derby (C/S), org.apache.derby.jdbc.ClientDriver, jdbc:derby://localhost/data/derbyServer;create=true, sa, sa +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 + +#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 + +firebirdsql.datetime = TIMESTAMP +postgresql.datetime = TIMESTAMP +derby.datetime = TIMESTAMP +oracle.datetime = TIMESTAMP + +test1 = org.h2.test.bench.BenchSimple +test2 = org.h2.test.bench.BenchA +test3 = org.h2.test.bench.BenchB +test4 = org.h2.test.bench.BenchC + +size = 5000 + +runCount3 = 4 +runCount5 = 4 +runCount6 = 4 +runCount7 = 7 +runCount8 = 4 +runCount12 = 5 \ No newline at end of file diff --git a/h2/src/test/org/h2/test/coverage/Coverage.java b/h2/src/test/org/h2/test/coverage/Coverage.java new file mode 100644 index 0000000..380baea --- /dev/null +++ b/h2/src/test/org/h2/test/coverage/Coverage.java @@ -0,0 +1,531 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.coverage; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; + +/** + * Tool to instrument java files with profiler calls. The tool can be used for + * profiling an application and for coverage testing. This class is not used at + * runtime of the tested application. + */ +public class Coverage { + private static final String IMPORT = "import " + + Coverage.class.getPackage().getName() + ".Profile"; + private final ArrayList files = new ArrayList<>(); + private final ArrayList exclude = new ArrayList<>(); + private Tokenizer tokenizer; + private Writer writer; + private Writer data; + private String token = ""; + private String add = ""; + private String file; + private int index; + private int indent; + private int line; + private String last; + private String word, function; + private boolean perClass; + private boolean perFunction = true; + + private void printUsage() { + System.out + .println("Usage:\n" + + "- copy all your source files to another directory\n" + + " (be careful, they will be modified - don't take originals!)\n" + + "- java " + getClass().getName() + " \n" + + " this will modified the source code and create 'profile.txt'\n" + + "- compile the modified source files\n" + + "- run your main application\n" + + "- after the application exits, a file 'notCovered.txt' is created,\n" + + " which contains the class names, function names and line numbers\n" + + " of code that has not been covered\n\n" + + "Options:\n" + "-r recurse all subdirectories\n" + + "-e exclude files\n" + + "-c coverage on a per-class basis\n" + + "-f coverage on a per-function basis\n" + + " directory name (. for current directory)"); + } + + /** + * This method is called when executing this application. + * + * @param args the command line parameters + */ + public static void main(String... args) { + new Coverage().run(args); + } + + private void run(String... args) { + if (args.length == 0 || args[0].equals("-?")) { + printUsage(); + return; + } + Coverage c = new Coverage(); + int recurse = 1; + for (int i = 0; i < args.length; i++) { + String s = args[i]; + if (s.equals("-r")) { + // maximum recurse is 100 subdirectories, that should be enough + recurse = 100; + } else if (s.equals("-c")) { + c.perClass = true; + } else if (s.equals("-f")) { + c.perFunction = true; + } else if (s.equals("-e")) { + c.addExclude(args[++i]); + } else { + c.addDir(s, recurse); + } + } + try { + c.data = new BufferedWriter(new FileWriter("profile.txt")); + c.processAll(); + c.data.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void addExclude(String fileName) { + exclude.add(fileName); + } + + private boolean isExcluded(String s) { + for (String e : exclude) { + if (s.startsWith(e)) { + return true; + } + } + return false; + } + + private void addDir(String path, int recurse) { + File f = new File(path); + if (f.isFile() && path.endsWith(".java")) { + if (!isExcluded(path)) { + files.add(path); + } + } else if (f.isDirectory() && recurse > 0) { + for (String name : f.list()) { + addDir(path + "/" + name, recurse - 1); + } + } + } + + private void processAll() { + int len = files.size(); + long time = System.nanoTime(); + for (int i = 0; i < len; i++) { + long t2 = System.nanoTime(); + if (t2 - time > TimeUnit.SECONDS.toNanos(1) || i >= len - 1) { + System.out.println((i + 1) + " of " + len + + " " + (100 * i / len) + "%"); + time = t2; + } + String fileName = files.get(i); + processFile(fileName); + } + } + + private void processFile(String name) { + file = name; + int i; + i = file.lastIndexOf('.'); + if (i != -1) { + file = file.substring(0, i); + } + while (true) { + i = file.indexOf('/'); + if (i < 0) { + i = file.indexOf('\\'); + } + if (i < 0) { + break; + } + file = file.substring(0, i) + "." + file.substring(i + 1); + } + if (name.endsWith("Coverage.java") || + name.endsWith("Tokenizer.java") || + name.endsWith("Profile.java")) { + return; + } + File f = new File(name); + File fileNew = new File(name + ".new"); + try { + writer = new BufferedWriter(new FileWriter(fileNew)); + Reader r = new BufferedReader(new FileReader(f)); + tokenizer = new Tokenizer(r); + indent = 0; + try { + process(); + } catch (Exception e) { + r.close(); + writer.close(); + e.printStackTrace(); + printError(e.getMessage()); + throw e; + } + r.close(); + writer.close(); + File backup = new File(name + ".bak"); + backup.delete(); + f.renameTo(backup); + File copy = new File(name); + fileNew.renameTo(copy); + if (perClass) { + nextDebug(); + } + } catch (Exception e) { + e.printStackTrace(); + printError(e.getMessage()); + } + } + + private void read() throws IOException { + last = token; + String write = token; + token = null; + tokenizer.initToken(); + int i = tokenizer.nextToken(); + if (i != Tokenizer.TYPE_EOF) { + token = tokenizer.getString(); + if (token == null) { + token = "" + ((char) i); + } else if (i == '\'') { + // mToken="'"+getEscape(mToken)+"'"; + token = tokenizer.getToken(); + } else if (i == '\"') { + // mToken="\""+getEscape(mToken)+"\""; + token = tokenizer.getToken(); + } else { + if (write == null) { + write = ""; + } else { + write = write + " "; + } + } + } + if (write == null || (!write.equals("else ") && + !write.equals("else") && !write.equals("super ") && + !write.equals("super") && !write.equals("this ") && + !write.equals("this") && !write.equals("} ") && + !write.equals("}"))) { + if (add != null && !add.equals("")) { + writeLine(); + write(add); + if (!perClass) { + nextDebug(); + } + } + } + add = ""; + if (write != null) { + write(write); + } + } + + private void readThis(String s) throws IOException { + if (!token.equals(s)) { + throw new IOException("Expected: " + s + " got:" + token); + } + read(); + } + + private void process() throws IOException { + boolean imp = false; + read(); + do { + while (true) { + if (token == null || token.equals("{")) { + break; + } else if (token.equals(";")) { + if (!imp) { + write(";" + IMPORT); + imp = true; + } + } + read(); + } + processClass(); + } while (token != null); + } + + private void processInit() throws IOException { + do { + if (token.equals("{")) { + read(); + processInit(); + } else if (token.equals("}")) { + read(); + return; + } else { + read(); + } + } while (true); + } + + private void processClass() throws IOException { + int type = 0; + while (true) { + if (token == null) { + break; + } else if (token.equals("class")) { + read(); + type = 1; + } else if (token.equals("=")) { + read(); + type = 2; + } else if (token.equals("static")) { + word = "static"; + read(); + type = 3; + } else if (token.equals("(")) { + word = last + "("; + read(); + if (!token.equals(")")) { + word = word + token; + } + type = 3; + } else if (token.equals(",")) { + read(); + word = word + "," + token; + } else if (token.equals(")")) { + word = word + ")"; + read(); + } else if (token.equals(";")) { + read(); + type = 0; + } else if (token.equals("{")) { + read(); + if (type == 1) { + processClass(); + } else if (type == 2) { + processInit(); + } else if (type == 3) { + writeLine(); + setLine(); + processFunction(); + writeLine(); + } + } else if (token.equals("}")) { + read(); + break; + } else { + read(); + } + } + } + + private void processBracket() throws IOException { + do { + if (token.equals("(")) { + read(); + processBracket(); + } else if (token.equals(")")) { + read(); + return; + } else { + read(); + } + } while (true); + } + + private void processFunction() throws IOException { + function = word; + writeLine(); + do { + processStatement(); + } while (!token.equals("}")); + read(); + writeLine(); + } + + private void processBlockOrStatement() throws IOException { + if (!token.equals("{")) { + write("{ //++"); + writeLine(); + setLine(); + processStatement(); + write("} //++"); + writeLine(); + } else { + read(); + setLine(); + processFunction(); + } + } + + private void processStatement() throws IOException { + while (true) { + if (token.equals("while") || token.equals("for") || + token.equals("synchronized")) { + read(); + readThis("("); + processBracket(); + indent++; + processBlockOrStatement(); + indent--; + return; + } else if (token.equals("if")) { + read(); + readThis("("); + processBracket(); + indent++; + processBlockOrStatement(); + indent--; + if (token.equals("else")) { + read(); + indent++; + processBlockOrStatement(); + indent--; + } + return; + } else if (token.equals("try")) { + read(); + indent++; + processBlockOrStatement(); + indent--; + while (true) { + if (token.equals("catch")) { + read(); + readThis("("); + processBracket(); + indent++; + processBlockOrStatement(); + indent--; + } else if (token.equals("finally")) { + read(); + indent++; + processBlockOrStatement(); + indent--; + } else { + break; + } + } + return; + } else if (token.equals("{")) { + if (last.equals(")")) { + // process anonymous inner classes (this is a hack) + read(); + processClass(); + return; + } else if (last.equals("]")) { + // process object array initialization (another hack) + while (!token.equals("}")) { + read(); + } + read(); + return; + } + indent++; + processBlockOrStatement(); + indent--; + return; + } else if (token.equals("do")) { + read(); + indent++; + processBlockOrStatement(); + readThis("while"); + readThis("("); + processBracket(); + readThis(";"); + setLine(); + indent--; + return; + } else if (token.equals("case")) { + add = ""; + read(); + while (!token.equals(":")) { + read(); + } + read(); + setLine(); + } else if (token.equals("default")) { + add = ""; + read(); + readThis(":"); + setLine(); + } else if (token.equals("switch")) { + read(); + readThis("("); + processBracket(); + indent++; + processBlockOrStatement(); + indent--; + return; + } else if (token.equals("class")) { + read(); + processClass(); + return; + } else if (token.equals("(")) { + read(); + processBracket(); + } else if (token.equals("=")) { + read(); + if (token.equals("{")) { + read(); + processInit(); + } + } else if (token.equals(";")) { + read(); + setLine(); + return; + } else if (token.equals("}")) { + return; + } else { + read(); + } + } + } + + private void setLine() { + add += "Profile.visit(" + index + ");"; + line = tokenizer.getLine(); + } + + private void nextDebug() throws IOException { + if (perFunction) { + int i = function.indexOf('('); + String func = i < 0 ? function : function.substring(0, i); + String fileLine = file + "." + func + "("; + i = file.lastIndexOf('.'); + String className = i < 0 ? file : file.substring(i + 1); + fileLine += className + ".java:" + line + ")"; + data.write(fileLine + " " + last + "\r\n"); + } else { + data.write(file + " " + line + "\r\n"); + } + index++; + } + + private void writeLine() throws IOException { + write("\r\n"); + for (int i = 0; i < indent; i++) { + writer.write(' '); + } + } + + private void write(String s) throws IOException { + writer.write(s); + // System.out.print(s); + } + + private void printError(String error) { + System.out.println(""); + System.out.println("File:" + file); + System.out.println("ERROR: " + error); + } +} diff --git a/h2/src/test/org/h2/test/coverage/Profile.java b/h2/src/test/org/h2/test/coverage/Profile.java new file mode 100644 index 0000000..06d57be --- /dev/null +++ b/h2/src/test/org/h2/test/coverage/Profile.java @@ -0,0 +1,228 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.coverage; + +import java.io.BufferedWriter; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.LineNumberReader; +import java.util.concurrent.TimeUnit; + +import org.h2.util.IOUtils; + +/** + * The class used at runtime to measure the code usage and performance. + */ +public class Profile extends Thread { + private static final boolean LIST_UNVISITED = false; + private static final boolean TRACE = false; + private static final Profile MAIN = new Profile(); + private static int top = 15; + private int[] count; + private int[] time; + private boolean stop; + private int maxIndex; + private int lastIndex; + private long lastTimeNs; + private BufferedWriter trace; + + private Profile() { + try (LineNumberReader r = new LineNumberReader(new FileReader("profile.txt"))) { + while (r.readLine() != null) { + // nothing - just count lines + } + maxIndex = r.getLineNumber(); + count = new int[maxIndex]; + time = new int[maxIndex]; + lastTimeNs = System.nanoTime(); + Runtime.getRuntime().addShutdownHook(this); + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + } + + static { + try { + String s = System.getProperty("profile.top"); + if (s != null) { + top = Integer.parseInt(s); + } + } catch (Throwable e) { + // ignore SecurityExceptions + } + } + + /** + * This method is called by an instrumented application whenever a line of + * code is executed. + * + * @param i the line number that is executed + */ + public static void visit(int i) { + MAIN.addVisit(i); + } + + @Override + public void run() { + list(); + } + + /** + * Start collecting data. + */ + public static void startCollecting() { + MAIN.stop = false; + MAIN.lastTimeNs = System.nanoTime(); + } + + /** + * Stop collecting data. + */ + public static void stopCollecting() { + MAIN.stop = true; + } + + /** + * List all captured data. + */ + public static void list() { + if (MAIN.lastIndex == 0) { + // don't list anything if no statistics collected + return; + } + try { + MAIN.listUnvisited(); + MAIN.listTop("MOST CALLED", MAIN.count, top); + MAIN.listTop("MOST TIME USED", MAIN.time, top); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void addVisit(int i) { + if (stop) { + return; + } + long now = System.nanoTime(); + if (TRACE) { + if (trace != null) { + long duration = TimeUnit.NANOSECONDS.toMillis(now - lastTimeNs); + try { + trace.write(i + "\t" + duration + "\r\n"); + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + } + } + count[i]++; + time[lastIndex] += (int) TimeUnit.NANOSECONDS.toMillis(now - lastTimeNs); + lastTimeNs = now; + lastIndex = i; + } + + private void listUnvisited() throws IOException { + printLine('='); + print("NOT COVERED"); + printLine('-'); + LineNumberReader r = null; + BufferedWriter writer = null; + try { + r = new LineNumberReader(new FileReader("profile.txt")); + writer = new BufferedWriter(new FileWriter("notCovered.txt")); + int unvisited = 0; + int unvisitedThrow = 0; + for (int i = 0; i < maxIndex; i++) { + String line = r.readLine(); + if (count[i] == 0) { + if (!line.endsWith("throw")) { + writer.write(line + "\r\n"); + if (LIST_UNVISITED) { + print(line + "\r\n"); + } + unvisited++; + } else { + unvisitedThrow++; + } + } + } + int percent = 100 * unvisited / maxIndex; + print("Not covered: " + percent + " % " + " (" + + unvisited + " of " + maxIndex + "; throw=" + + unvisitedThrow + ")"); + } finally { + IOUtils.closeSilently(writer); + IOUtils.closeSilently(r); + } + } + + private void listTop(String title, int[] list, int max) throws IOException { + printLine('-'); + int total = 0; + int totalLines = 0; + for (int j = 0; j < maxIndex; j++) { + int l = list[j]; + if (l > 0) { + total += list[j]; + totalLines++; + } + } + if (max == 0) { + max = totalLines; + } + print(title); + print("Total: " + total); + printLine('-'); + String[] text = new String[max]; + int[] index = new int[max]; + for (int i = 0; i < max; i++) { + int big = list[0]; + int bigIndex = 0; + for (int j = 1; j < maxIndex; j++) { + int l = list[j]; + if (l > big) { + big = l; + bigIndex = j; + } + } + list[bigIndex] = -(big + 1); + index[i] = bigIndex; + } + + try (LineNumberReader r = new LineNumberReader(new FileReader("profile.txt"))) { + for (int i = 0; i < maxIndex; i++) { + String line = r.readLine(); + int k = list[i]; + if (k < 0) { + k = -(k + 1); + list[i] = k; + for (int j = 0; j < max; j++) { + if (index[j] == i) { + int percent = 100 * k / total; + text[j] = k + " " + percent + "%: " + line; + } + } + } + } + for (int i = 0; i < max; i++) { + print(text[i]); + } + } + } + + private static void print(String s) { + System.out.println(s); + } + + private static void printLine(char c) { + for (int i = 0; i < 60; i++) { + System.out.print(c); + } + print(""); + } +} diff --git a/h2/src/test/org/h2/test/coverage/Tokenizer.java b/h2/src/test/org/h2/test/coverage/Tokenizer.java new file mode 100644 index 0000000..611800f --- /dev/null +++ b/h2/src/test/org/h2/test/coverage/Tokenizer.java @@ -0,0 +1,277 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.coverage; + +import java.io.EOFException; +import java.io.IOException; +import java.io.Reader; +import java.util.Arrays; + +/** + * Helper class for the java file parser. + */ +public class Tokenizer { + + /** + * This token type means no more tokens are available. + */ + static final int TYPE_EOF = -1; + + private static final int TYPE_WORD = -2; + private static final int TYPE_NOTHING = -3; + private static final byte WHITESPACE = 1; + private static final byte ALPHA = 4; + private static final byte QUOTE = 8; + + private StringBuilder buffer; + + private Reader reader; + + private char[] chars = new char[20]; + private int peekChar; + private int line = 1; + + private byte[] charTypes = new byte[256]; + + private int type = TYPE_NOTHING; + private String value; + + private Tokenizer() { + wordChars('a', 'z'); + wordChars('A', 'Z'); + wordChars('0', '9'); + wordChars('.', '.'); + wordChars('+', '+'); + wordChars('-', '-'); + wordChars('_', '_'); + wordChars(128 + 32, 255); + whitespaceChars(0, ' '); + charTypes['"'] = QUOTE; + charTypes['\''] = QUOTE; + } + + Tokenizer(Reader r) { + this(); + reader = r; + } + + String getString() { + return value; + } + + private void wordChars(int low, int hi) { + while (low <= hi) { + charTypes[low++] |= ALPHA; + } + } + + private void whitespaceChars(int low, int hi) { + while (low <= hi) { + charTypes[low++] = WHITESPACE; + } + } + + private int read() throws IOException { + int i = reader.read(); + if (i != -1) { + append(i); + } + return i; + } + + /** + * Initialize the tokenizer. + */ + void initToken() { + buffer = new StringBuilder(); + } + + String getToken() { + buffer.setLength(buffer.length() - 1); + return buffer.toString(); + } + + private void append(int i) { + buffer.append((char) i); + } + + /** + * Read the next token and get the token type. + * + * @return the token type + */ + int nextToken() throws IOException { + byte[] ct = charTypes; + int c; + value = null; + + if (type == TYPE_NOTHING) { + c = read(); + if (c >= 0) { + type = c; + } + } else { + c = peekChar; + if (c < 0) { + try { + c = read(); + if (c >= 0) { + type = c; + } + } catch (EOFException e) { + c = -1; + } + } + } + + if (c < 0) { + return type = TYPE_EOF; + } + int charType = c < 256 ? ct[c] : ALPHA; + while ((charType & WHITESPACE) != 0) { + if (c == '\r') { + line++; + c = read(); + if (c == '\n') { + c = read(); + } + } else { + if (c == '\n') { + line++; + } + c = read(); + } + if (c < 0) { + return type = TYPE_EOF; + } + charType = c < 256 ? ct[c] : ALPHA; + } + if ((charType & ALPHA) != 0) { + initToken(); + append(c); + int i = 0; + do { + if (i >= chars.length) { + chars = Arrays.copyOf(chars, chars.length * 2); + } + chars[i++] = (char) c; + c = read(); + charType = c < 0 ? WHITESPACE : c < 256 ? ct[c] : ALPHA; + } while ((charType & ALPHA) != 0); + peekChar = c; + value = String.copyValueOf(chars, 0, i); + return type = TYPE_WORD; + } + if ((charType & QUOTE) != 0) { + initToken(); + append(c); + type = c; + int i = 0; + // \octal needs a lookahead + peekChar = read(); + while (peekChar >= 0 && peekChar != type && peekChar != '\n' + && peekChar != '\r') { + if (peekChar == '\\') { + c = read(); + // to allow \377, but not \477 + int first = c; + if (c >= '0' && c <= '7') { + c = c - '0'; + int c2 = read(); + if ('0' <= c2 && c2 <= '7') { + c = (c << 3) + (c2 - '0'); + c2 = read(); + if ('0' <= c2 && c2 <= '7' && first <= '3') { + c = (c << 3) + (c2 - '0'); + peekChar = read(); + } else { + peekChar = c2; + } + } else { + peekChar = c2; + } + } else { + switch (c) { + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + default: + } + peekChar = read(); + } + } else { + c = peekChar; + peekChar = read(); + } + + if (i >= chars.length) { + chars = Arrays.copyOf(chars, chars.length * 2); + } + chars[i++] = (char) c; + } + if (peekChar == type) { + // keep \n or \r intact in peekChar + peekChar = read(); + } + value = String.copyValueOf(chars, 0, i); + return type; + } + if (c == '/') { + c = read(); + if (c == '*') { + int prevChar = 0; + while ((c = read()) != '/' || prevChar != '*') { + if (c == '\r') { + line++; + c = read(); + if (c == '\n') { + c = read(); + } + } else { + if (c == '\n') { + line++; + c = read(); + } + } + if (c < 0) { + return type = TYPE_EOF; + } + prevChar = c; + } + peekChar = read(); + return nextToken(); + } else if (c == '/') { + while ((c = read()) != '\n' && c != '\r' && c >= 0) { + // nothing + } + peekChar = c; + return nextToken(); + } else { + peekChar = c; + return type = '/'; + } + } + peekChar = read(); + return type = c; + } + + int getLine() { + return line; + } +} + diff --git a/h2/src/test/org/h2/test/coverage/package.html b/h2/src/test/org/h2/test/coverage/package.html new file mode 100644 index 0000000..72a52ae --- /dev/null +++ b/h2/src/test/org/h2/test/coverage/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +A standalone code coverage tool. + +

    \ No newline at end of file diff --git a/h2/src/test/org/h2/test/db/AbstractBaseForCommonTableExpressions.java b/h2/src/test/org/h2/test/db/AbstractBaseForCommonTableExpressions.java new file mode 100644 index 0000000..89a6929 --- /dev/null +++ b/h2/src/test/org/h2/test/db/AbstractBaseForCommonTableExpressions.java @@ -0,0 +1,103 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; + +import org.h2.test.TestDb; + +/** + * Base class for common table expression tests + */ +public abstract class AbstractBaseForCommonTableExpressions extends TestDb { + + /** + * Test a query. + * + * @param maxRetries the number of times the query is run + * @param expectedRowData the expected result data + * @param expectedColumnNames the expected columns of the result + * @param expectedNumberOfRows the expected number of rows + * @param setupSQL the SQL statement used for setup + * @param withQuery the query + * @param closeAndReopenDatabaseConnectionOnIteration whether the connection + * should be re-opened each time + * @param expectedColumnTypes the expected datatypes of the result + * @param anyOrder whether any order of rows should be allowed. + * If {@code true}, this method may sort expectedRowData. + */ + void testRepeatedQueryWithSetup(int maxRetries, String[] expectedRowData, String[] expectedColumnNames, + int expectedNumberOfRows, String setupSQL, String withQuery, + int closeAndReopenDatabaseConnectionOnIteration, String[] expectedColumnTypes, + boolean anyOrder) throws SQLException { + + deleteDb("commonTableExpressionQueries"); + Connection conn = getConnection("commonTableExpressionQueries"); + PreparedStatement prep; + ResultSet rs; + + if (anyOrder) { + Arrays.sort(expectedRowData); + } + ArrayList rowData = new ArrayList<>(); + StringBuilder buf = new StringBuilder(); + + for (int queryRunTries = 1; queryRunTries <= maxRetries; queryRunTries++) { + + Statement stat = conn.createStatement(); + stat.execute(setupSQL); + stat.close(); + + // close and re-open connection for one iteration to make sure the query work + // between connections + if (queryRunTries == closeAndReopenDatabaseConnectionOnIteration) { + conn.close(); + + conn = getConnection("commonTableExpressionQueries"); + } + prep = conn.prepareStatement(withQuery); + + rs = prep.executeQuery(); + for (int columnIndex = 1; columnIndex <= rs.getMetaData().getColumnCount(); columnIndex++) { + + assertNotNull(rs.getMetaData().getColumnLabel(columnIndex)); + assertEquals(expectedColumnNames[columnIndex - 1], rs.getMetaData().getColumnLabel(columnIndex)); + assertEquals( + "wrong type of column " + rs.getMetaData().getColumnLabel(columnIndex) + " on iteration #" + + queryRunTries, + expectedColumnTypes[columnIndex - 1], rs.getMetaData().getColumnTypeName(columnIndex)); + } + + rowData.clear(); + while (rs.next()) { + buf.setLength(0); + for (int columnIndex = 1; columnIndex <= rs.getMetaData().getColumnCount(); columnIndex++) { + buf.append('|').append(rs.getString(columnIndex)); + } + rowData.add(buf.toString()); + } + if (anyOrder) { + Collections.sort(rowData); + } + assertEquals(expectedRowData, rowData.toArray(new String[0])); + + rs.close(); + prep.close(); + } + + conn.close(); + deleteDb("commonTableExpressionQueries"); + + } + +} diff --git a/h2/src/test/org/h2/test/db/Db.java b/h2/src/test/org/h2/test/db/Db.java new file mode 100644 index 0000000..4c0542d --- /dev/null +++ b/h2/src/test/org/h2/test/db/Db.java @@ -0,0 +1,252 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.io.InputStream; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A simple wrapper around the JDBC API. + * Currently used for testing. + * Features: + *
      + *
    • No checked exceptions + *
    • Easy to use, fluent API + *
    + */ +public class Db { + + private Connection conn; + private Statement stat; + private final HashMap prepared = + new HashMap<>(); + + /** + * Create a database object using the given connection. + * + * @param conn the database connection + */ + public Db(Connection conn) { + try { + this.conn = conn; + stat = conn.createStatement(); + } catch (SQLException e) { + throw convert(e); + } + } + + /** + * Prepare a SQL statement. + * + * @param sql the SQL statement + * @return the prepared statement + */ + public Prepared prepare(String sql) { + try { + PreparedStatement prep = prepared.get(sql); + if (prep == null) { + prep = conn.prepareStatement(sql); + prepared.put(sql, prep); + } + return new Prepared(conn.prepareStatement(sql)); + } catch (SQLException e) { + throw convert(e); + } + } + + /** + * Execute a SQL statement. + * + * @param sql the SQL statement + */ + public void execute(String sql) { + try { + stat.execute(sql); + } catch (SQLException e) { + throw convert(e); + } + } + + /** + * Read a result set. + * + * @param rs the result set + * @return a list of maps + */ + static List> query(ResultSet rs) throws SQLException { + List> list = new ArrayList<>(); + ResultSetMetaData meta = rs.getMetaData(); + int columnCount = meta.getColumnCount(); + while (rs.next()) { + HashMap map = new HashMap<>(); + for (int i = 0; i < columnCount; i++) { + map.put(meta.getColumnLabel(i+1), rs.getObject(i+1)); + } + list.add(map); + } + return list; + } + + /** + * Execute a SQL statement. + * + * @param sql the SQL statement + * @return a list of maps + */ + public List> query(String sql) { + try { + return query(stat.executeQuery(sql)); + } catch (SQLException e) { + throw convert(e); + } + } + + /** + * Close the database connection. + */ + public void close() { + try { + conn.close(); + } catch (SQLException e) { + throw convert(e); + } + } + + /** + * This class represents a prepared statement. + */ + public static class Prepared { + private final PreparedStatement prep; + private int index; + + Prepared(PreparedStatement prep) { + this.prep = prep; + } + + /** + * Set the value of the current parameter. + * + * @param x the value + * @return itself + */ + public Prepared set(int x) { + try { + prep.setInt(++index, x); + return this; + } catch (SQLException e) { + throw convert(e); + } + } + + /** + * Set the value of the current parameter. + * + * @param x the value + * @return itself + */ + public Prepared set(String x) { + try { + prep.setString(++index, x); + return this; + } catch (SQLException e) { + throw convert(e); + } + } + + /** + * Set the value of the current parameter. + * + * @param x the value + * @return itself + */ + public Prepared set(byte[] x) { + try { + prep.setBytes(++index, x); + return this; + } catch (SQLException e) { + throw convert(e); + } + } + + /** + * Set the value of the current parameter. + * + * @param x the value + * @return itself + */ + public Prepared set(InputStream x) { + try { + prep.setBinaryStream(++index, x, -1); + return this; + } catch (SQLException e) { + throw convert(e); + } + } + + /** + * Execute the prepared statement. + */ + public void execute() { + try { + prep.execute(); + } catch (SQLException e) { + throw convert(e); + } + } + + /** + * Execute the prepared query. + * + * @return the result list + */ + public List> query() { + try { + return Db.query(prep.executeQuery()); + } catch (SQLException e) { + throw convert(e); + } + } + } + + /** + * Convert a checked exception to a runtime exception. + * + * @param e the checked exception + * @return the runtime exception + */ + static RuntimeException convert(Exception e) { + return new RuntimeException(e.toString(), e); + } + + public void setAutoCommit(boolean autoCommit) { + try { + conn.setAutoCommit(autoCommit); + } catch (SQLException e) { + throw convert(e); + } + } + + /** + * Commit a pending transaction. + */ + public void commit() { + try { + conn.commit(); + } catch (SQLException e) { + throw convert(e); + } + } + +} diff --git a/h2/src/test/org/h2/test/db/TaskDef.java b/h2/src/test/org/h2/test/db/TaskDef.java new file mode 100644 index 0000000..46a2f15 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TaskDef.java @@ -0,0 +1,76 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Arrays; + +import org.h2.test.utils.SelfDestructor; + +/** + * A task that can be run as a separate process. + */ +public abstract class TaskDef { + + /** + * Run the class. This method is called by the task framework, and should + * not be called directly from the application. + * + * @param args the command line arguments + */ + public static void main(String... args) { + SelfDestructor.startCountdown(60); + TaskDef task; + try { + String className = args[0]; + task = (TaskDef) Class.forName(className).getDeclaredConstructor().newInstance(); + System.out.println("running"); + } catch (Throwable t) { + System.out.println("init error: " + t); + t.printStackTrace(); + return; + } + try { + task.run(Arrays.copyOf(args, args.length - 1)); + } catch (Throwable t) { + System.out.println("error: " + t); + t.printStackTrace(); + } + } + + /** + * Run the task. + * + * @param args the command line arguments + */ + abstract void run(String... args) throws Exception; + + /** + * Receive a message from the process over the standard output. + * + * @return the message + */ + protected String receive() { + try { + return new BufferedReader(new InputStreamReader(System.in)).readLine(); + } catch (IOException e) { + throw new RuntimeException("Error reading from input", e); + } + } + + /** + * Send a message to the process over the standard input. + * + * @param message the message + */ + protected void send(String message) { + System.out.println(message); + System.out.flush(); + } + +} diff --git a/h2/src/test/org/h2/test/db/TaskProcess.java b/h2/src/test/org/h2/test/db/TaskProcess.java new file mode 100644 index 0000000..7fdd01d --- /dev/null +++ b/h2/src/test/org/h2/test/db/TaskProcess.java @@ -0,0 +1,132 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.util.ArrayList; +import java.util.Arrays; + +import org.h2.test.TestBase; +import org.h2.test.utils.SelfDestructor; +import org.h2.util.StringUtils; +import org.h2.util.Task; + +/** + * A task that is run as an external process. This class communicates over + * standard input / output with the process. The standard error stream of the + * process is directly send to the standard error stream of this process. + */ +public class TaskProcess { + private final TaskDef taskDef; + private Process process; + private BufferedReader reader; + private BufferedWriter writer; + + /** + * Construct a new task process. The process is not started yet. + * + * @param taskDef the task + */ + public TaskProcess(TaskDef taskDef) { + this.taskDef = taskDef; + } + + /** + * Start the task with the given arguments. + * + * @param args the arguments, or null + */ + public void start(String... args) { + try { + String selfDestruct = SelfDestructor.getPropertyString(60); + ArrayList list = new ArrayList<>(); + list.add(TestBase.getJVM()); + list.add(selfDestruct); + list.add("-cp"); + list.add("bin" + File.pathSeparator + "."); + list.add(TaskDef.class.getName()); + list.add(taskDef.getClass().getName()); + if (args != null && args.length > 0) { + list.addAll(Arrays.asList(args)); + } + String[] procDef = list.toArray(new String[0]); + process = Runtime.getRuntime().exec(procDef); + copyInThread(process.getErrorStream(), System.err); + reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + writer = new BufferedWriter(new OutputStreamWriter(process.getOutputStream())); + String line = reader.readLine(); + if (line == null) { + throw new RuntimeException( + "No reply from process, command: " + + StringUtils.arrayCombine(procDef, ' ')); + } else if (line.startsWith("running")) { + } else if (line.startsWith("init error")) { + throw new RuntimeException(line); + } + } catch (Throwable t) { + throw new RuntimeException("Error starting task", t); + } + } + + private static void copyInThread(final InputStream in, final OutputStream out) { + new Task() { + @Override + public void call() throws IOException { + while (true) { + int x = in.read(); + if (x < 0) { + return; + } + if (out != null) { + out.write(x); + } + } + } + }.execute(); + } + + /** + * Receive a message from the process over the standard output. + * + * @return the message + */ + public String receive() { + try { + return reader.readLine(); + } catch (IOException e) { + throw new RuntimeException("Error reading", e); + } + } + + /** + * Send a message to the process over the standard input. + * + * @param message the message + */ + public void send(String message) { + try { + writer.write(message + "\n"); + writer.flush(); + } catch (IOException e) { + throw new RuntimeException("Error writing " + message, e); + } + } + + /** + * Kill the process if it still runs. + */ + public void destroy() { + process.destroy(); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestAlter.java b/h2/src/test/org/h2/test/db/TestAlter.java new file mode 100644 index 0000000..1d27fdd --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestAlter.java @@ -0,0 +1,244 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Collection; + +import org.h2.api.ErrorCode; +import org.h2.engine.Session; +import org.h2.engine.SessionLocal; +import org.h2.jdbc.JdbcConnection; +import org.h2.schema.Sequence; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Test ALTER statements. + */ +public class TestAlter extends TestDb { + + private Connection conn; + private Statement stat; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb(getTestName()); + conn = getConnection(getTestName()); + stat = conn.createStatement(); + testAlterTableRenameConstraint(); + testAlterTableDropColumnWithReferences(); + testAlterTableDropMultipleColumns(); + testAlterTableAddColumnIdentity(); + testAlterTableDropIdentityColumn(); + testAlterTableAddColumnIfNotExists(); + testAlterTableAddMultipleColumns(); + testAlterTableAddColumnBefore(); + testAlterTableAddColumnAfter(); + testAlterTableAddMultipleColumnsBefore(); + testAlterTableAddMultipleColumnsAfter(); + conn.close(); + deleteDb(getTestName()); + } + + private void testAlterTableDropColumnWithReferences() throws SQLException { + stat.execute("create table parent(id int primary key, b int)"); + stat.execute("create table child(p int primary key)"); + stat.execute("alter table child add foreign key(p) references parent(id)"); + stat.execute("alter table parent drop column id"); + stat.execute("drop table parent"); + stat.execute("drop table child"); + + stat.execute("create table test(id int, name varchar(255))"); + stat.execute("alter table test add constraint x check (id > name)"); + + // the constraint references multiple columns + assertThrows(ErrorCode.COLUMN_IS_REFERENCED_1, stat). + execute("alter table test drop column id"); + + stat.execute("drop table test"); + + stat.execute("create table test(id int, name varchar(255))"); + stat.execute("alter table test add constraint x unique(id, name)"); + + // the constraint references multiple columns + assertThrows(ErrorCode.COLUMN_IS_REFERENCED_1, stat). + execute("alter table test drop column id"); + + stat.execute("drop table test"); + + stat.execute("create table test(id int, name varchar(255))"); + stat.execute("alter table test add constraint x check (id > 1)"); + stat.execute("alter table test drop column id"); + stat.execute("drop table test"); + + stat.execute("create table test(id int, name varchar(255))"); + stat.execute("alter table test add constraint x check (name > 'TEST.ID')"); + // previous versions of H2 used sql.indexOf(columnName) + // to check if the column is referenced + stat.execute("alter table test drop column id"); + stat.execute("drop table test"); + + stat.execute("create table test(id int, name varchar(255))"); + stat.execute("alter table test add constraint x unique(id)"); + stat.execute("alter table test drop column id"); + stat.execute("drop table test"); + + } + + private void testAlterTableDropMultipleColumns() throws SQLException { + stat.execute("create table test(id int, b varchar, c int, d int)"); + stat.execute("alter table test drop column b, c"); + stat.execute("alter table test drop d"); + stat.execute("drop table test"); + // Test-Case: Same as above but using brackets (Oracle style) + stat.execute("create table test(id int, b varchar, c int, d int)"); + stat.execute("alter table test drop column (b, c)"); + assertThrows(ErrorCode.COLUMN_NOT_FOUND_1, stat). + execute("alter table test drop column b"); + stat.execute("alter table test drop (d)"); + stat.execute("drop table test"); + // Test-Case: Error if dropping all columns + stat.execute("create table test(id int, name varchar, name2 varchar)"); + assertThrows(ErrorCode.CANNOT_DROP_LAST_COLUMN, stat). + execute("alter table test drop column id, name, name2"); + stat.execute("drop table test"); + } + + private void testAlterTableRenameConstraint() throws SQLException { + stat.execute("create table test(id int, name varchar(255))"); + stat.execute("alter table test add constraint x check (id > name)"); + stat.execute("alter table test rename constraint x to x2"); + stat.execute("drop table test"); + } + + private void testAlterTableDropIdentityColumn() throws SQLException { + Session iface = ((JdbcConnection) stat.getConnection()).getSession(); + if (!(iface instanceof SessionLocal)) { + return; + } + Collection allSequences = ((SessionLocal) iface).getDatabase().getMainSchema().getAllSequences(); + stat.execute("create table test(id int auto_increment, name varchar)"); + stat.execute("alter table test drop column id"); + assertEquals(0, allSequences.size()); + stat.execute("drop table test"); + + stat.execute("create table test(id int auto_increment, name varchar)"); + stat.execute("alter table test drop column name"); + assertEquals(1, allSequences.size()); + stat.execute("drop table test"); + } + + private void testAlterTableAddColumnIdentity() throws SQLException { + stat.execute("create table t(x varchar)"); + stat.execute("alter table t add id bigint generated by default as identity(start with 5 increment by 5)" + + " default on null"); + stat.execute("insert into t values (null, null)"); + stat.execute("insert into t values (null, null)"); + ResultSet rs = stat.executeQuery("select id from t order by id"); + assertTrue(rs.next()); + assertEquals(5, rs.getInt(1)); + assertTrue(rs.next()); + assertEquals(10, rs.getInt(1)); + assertFalse(rs.next()); + stat.execute("drop table t"); + } + + private void testAlterTableAddColumnIfNotExists() throws SQLException { + stat.execute("create table t(x varchar) as select 'x'"); + stat.execute("alter table t add if not exists x int"); + stat.execute("drop table t"); + stat.execute("create table t(x varchar) as select 'x'"); + stat.execute("alter table t add if not exists y int"); + stat.execute("select x, y from t"); + stat.execute("drop table t"); + } + + private void testAlterTableAddMultipleColumns() throws SQLException { + stat.execute("create table t(x varchar) as select 'x'"); + stat.execute("alter table t add (y int, z varchar)"); + stat.execute("drop table t"); + stat.execute("create table t(x varchar) as select 'x'"); + stat.execute("alter table t add (y int)"); + stat.execute("drop table t"); + } + + + + // column and field names must be upper-case due to getMetaData sensitivity + private void testAlterTableAddMultipleColumnsBefore() throws SQLException { + stat.execute("create table T(X varchar)"); + stat.execute("alter table T add (Y int, Z int) before X"); + DatabaseMetaData dbMeta = conn.getMetaData(); + ResultSet rs = dbMeta.getColumns(null, null, "T", null); + assertTrue(rs.next()); + assertEquals("Y", rs.getString("COLUMN_NAME")); + assertTrue(rs.next()); + assertEquals("Z", rs.getString("COLUMN_NAME")); + assertTrue(rs.next()); + assertEquals("X", rs.getString("COLUMN_NAME")); + assertFalse(rs.next()); + stat.execute("drop table T"); + } + + // column and field names must be upper-case due to getMetaData sensitivity + private void testAlterTableAddMultipleColumnsAfter() throws SQLException { + stat.execute("create table T(X varchar)"); + stat.execute("alter table T add (Y int, Z int) after X"); + DatabaseMetaData dbMeta = conn.getMetaData(); + ResultSet rs = dbMeta.getColumns(null, null, "T", null); + assertTrue(rs.next()); + assertEquals("X", rs.getString("COLUMN_NAME")); + assertTrue(rs.next()); + assertEquals("Y", rs.getString("COLUMN_NAME")); + assertTrue(rs.next()); + assertEquals("Z", rs.getString("COLUMN_NAME")); + assertFalse(rs.next()); + stat.execute("drop table T"); + } + + // column and field names must be upper-case due to getMetaData sensitivity + private void testAlterTableAddColumnBefore() throws SQLException { + stat.execute("create table T(X varchar)"); + stat.execute("alter table T add Y int before X"); + DatabaseMetaData dbMeta = conn.getMetaData(); + ResultSet rs = dbMeta.getColumns(null, null, "T", null); + assertTrue(rs.next()); + assertEquals("Y", rs.getString("COLUMN_NAME")); + assertTrue(rs.next()); + assertEquals("X", rs.getString("COLUMN_NAME")); + assertFalse(rs.next()); + stat.execute("drop table T"); + } + + // column and field names must be upper-case due to getMetaData sensitivity + private void testAlterTableAddColumnAfter() throws SQLException { + stat.execute("create table T(X varchar)"); + stat.execute("alter table T add Y int after X"); + DatabaseMetaData dbMeta = conn.getMetaData(); + ResultSet rs = dbMeta.getColumns(null, null, "T", null); + assertTrue(rs.next()); + assertEquals("X", rs.getString("COLUMN_NAME")); + assertTrue(rs.next()); + assertEquals("Y", rs.getString("COLUMN_NAME")); + assertFalse(rs.next()); + stat.execute("drop table T"); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestAlterSchemaRename.java b/h2/src/test/org/h2/test/db/TestAlterSchemaRename.java new file mode 100644 index 0000000..fa778da --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestAlterSchemaRename.java @@ -0,0 +1,127 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Test ALTER SCHEMA RENAME statements. + */ +public class TestAlterSchemaRename extends TestDb { + + private Connection conn; + private Statement stat; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb(getTestName()); + conn = getConnection(getTestName()); + stat = conn.createStatement(); + testTryToRenameSystemSchemas(); + testSimpleRename(); + testRenameToExistingSchema(); + testCrossSchemaViews(); + testAlias(); + conn.close(); + deleteDb(getTestName()); + } + + private void testTryToRenameSystemSchemas() throws SQLException { + assertThrows(ErrorCode.SCHEMA_CAN_NOT_BE_DROPPED_1, stat). + execute("alter schema information_schema rename to test_info"); + stat.execute("create sequence test_sequence"); + assertThrows(ErrorCode.SCHEMA_CAN_NOT_BE_DROPPED_1, stat). + execute("alter schema public rename to test_schema"); + } + + private void testSimpleRename() throws SQLException { + stat.execute("create schema s1"); + stat.execute("create table s1.tab(val int)"); + stat.execute("insert into s1.tab(val) values (3)"); + ResultSet rs = stat.executeQuery("select * from s1.tab"); + assertTrue(rs.next()); + assertEquals(3, rs.getInt(1)); + stat.execute("alter schema s1 rename to s2"); + rs = stat.executeQuery("select * from s2.tab"); + assertTrue(rs.next()); + assertEquals(3, rs.getInt(1)); + stat.execute("drop schema s2 cascade"); + } + + + private void testRenameToExistingSchema() throws SQLException { + stat.execute("create schema s1"); + stat.execute("create schema s2"); + assertThrows(ErrorCode.SCHEMA_ALREADY_EXISTS_1, stat). + execute("alter schema s1 rename to s2"); + stat.execute("drop schema s1"); + stat.execute("drop schema s2"); + } + + + private void testCrossSchemaViews() throws SQLException { + stat.execute("create schema s1"); + stat.execute("create schema s2"); + stat.execute("create table s1.tab(val int)"); + stat.execute("insert into s1.tab(val) values (3)"); + stat.execute("create view s1.v1 as select * from s1.tab"); + stat.execute("create view s2.v1 as select val * 2 from s1.tab"); + stat.execute("alter schema s2 rename to s2_new"); + ResultSet rs = stat.executeQuery("select * from s1.v1"); + assertTrue(rs.next()); + assertEquals(3, rs.getInt(1)); + rs = stat.executeQuery("select * from s2_new.v1"); + assertTrue(rs.next()); + assertEquals(6, rs.getInt(1)); + if (!config.memory) { + conn.close(); + conn = getConnection(getTestName()); + stat = conn.createStatement(); + stat.executeQuery("select * from s2_new.v1"); + } + stat.execute("drop schema s1 cascade"); + stat.execute("drop schema s2_new cascade"); + } + + /** + * Check that aliases in the schema got moved + */ + private void testAlias() throws SQLException { + stat.execute("create schema s1"); + stat.execute("CREATE ALIAS S1.REVERSE AS $$ " + + "String reverse(String s) {" + + " return new StringBuilder(s).reverse().toString();" + + "} $$;"); + stat.execute("alter schema s1 rename to s2"); + ResultSet rs = stat.executeQuery("CALL S2.REVERSE('1234')"); + assertTrue(rs.next()); + assertEquals("4321", rs.getString(1)); + if (!config.memory) { + conn.close(); + conn = getConnection(getTestName()); + stat = conn.createStatement(); + stat.executeQuery("CALL S2.REVERSE('1234')"); + } + stat.execute("drop schema s2 cascade"); + } + +} \ No newline at end of file diff --git a/h2/src/test/org/h2/test/db/TestAlterTableNotFound.java b/h2/src/test/org/h2/test/db/TestAlterTableNotFound.java new file mode 100644 index 0000000..568f3c9 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestAlterTableNotFound.java @@ -0,0 +1,174 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +public class TestAlterTableNotFound extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testWithoutAnyCandidate(); + testWithoutAnyCandidateWhenDatabaseToLower(); + testWithoutAnyCandidateWhenDatabaseToUpper(); + testWithoutAnyCandidateWhenCaseInsensitiveIdentifiers(); + testWithOneCandidate(); + testWithOneCandidateWhenDatabaseToLower(); + testWithOneCandidateWhenDatabaseToUpper(); + testWithOneCandidateWhenCaseInsensitiveIdentifiers(); + testWithTwoCandidates(); + } + + private void testWithoutAnyCandidate() throws SQLException { + deleteDb(getTestName()); + Connection conn = getConnectionWithSettings("DATABASE_TO_UPPER=FALSE"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE T2 ( ID INT GENERATED BY DEFAULT AS IDENTITY )"); + try { + stat.execute("ALTER TABLE t1 DROP COLUMN ID"); + fail("Table `t1` was accessible but should not have been."); + } catch (SQLException e) { + String message = e.getMessage(); + assertContains(message, "Table \"t1\" not found;"); + } + + conn.close(); + deleteDb(getTestName()); + } + + private void testWithoutAnyCandidateWhenDatabaseToLower() throws SQLException { + deleteDb(getTestName()); + Connection conn = getConnectionWithSettings("DATABASE_TO_LOWER=TRUE"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE T2 ( ID INT GENERATED BY DEFAULT AS IDENTITY )"); + try { + stat.execute("ALTER TABLE T1 DROP COLUMN ID"); + fail("Table `t1` was accessible but should not have been."); + } catch (SQLException e) { + String message = e.getMessage(); + assertContains(message, "Table \"t1\" not found;"); + } + + conn.close(); + deleteDb(getTestName()); + } + + private void testWithoutAnyCandidateWhenDatabaseToUpper() throws SQLException { + deleteDb(getTestName()); + Connection conn = getConnectionWithSettings("DATABASE_TO_LOWER=FALSE;DATABASE_TO_UPPER=TRUE"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE T2 ( ID INT GENERATED BY DEFAULT AS IDENTITY )"); + try { + stat.execute("ALTER TABLE t1 DROP COLUMN ID"); + fail("Table `T1` was accessible but should not have been."); + } catch (SQLException e) { + String message = e.getMessage(); + assertContains(message, "Table \"T1\" not found;"); + } + + conn.close(); + deleteDb(getTestName()); + } + + private void testWithoutAnyCandidateWhenCaseInsensitiveIdentifiers() throws SQLException { + deleteDb(getTestName()); + Connection conn = getConnectionWithSettings("DATABASE_TO_UPPER=FALSE;CASE_INSENSITIVE_IDENTIFIERS=TRUE"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE T2 ( ID INT GENERATED BY DEFAULT AS IDENTITY )"); + try { + stat.execute("ALTER TABLE t1 DROP COLUMN ID"); + fail("Table `t1` was accessible but should not have been."); + } catch (SQLException e) { + String message = e.getMessage(); + assertContains(message, "Table \"t1\" not found;"); + } + + conn.close(); + deleteDb(getTestName()); + } + + private void testWithOneCandidate() throws SQLException { + deleteDb(getTestName()); + Connection conn = getConnectionWithSettings("DATABASE_TO_UPPER=FALSE"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE T1 ( ID INT GENERATED BY DEFAULT AS IDENTITY )"); + try { + stat.execute("ALTER TABLE t1 DROP COLUMN ID"); + fail("Table `t1` was accessible but should not have been."); + } catch (SQLException e) { + String message = e.getMessage(); + assertContains(message, "Table \"t1\" not found (candidates are: \"T1\")"); + } + + conn.close(); + deleteDb(getTestName()); + } + + private void testWithOneCandidateWhenDatabaseToLower() throws SQLException { + deleteDb(getTestName()); + Connection conn = getConnectionWithSettings("DATABASE_TO_LOWER=TRUE"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE t1 ( ID INT GENERATED BY DEFAULT AS IDENTITY, PAYLOAD INT )"); + stat.execute("ALTER TABLE T1 DROP COLUMN PAYLOAD"); + conn.close(); + deleteDb(getTestName()); + } + + private void testWithOneCandidateWhenDatabaseToUpper() throws SQLException { + deleteDb(getTestName()); + Connection conn = getConnectionWithSettings("DATABASE_TO_UPPER=TRUE"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE T1 ( ID INT GENERATED BY DEFAULT AS IDENTITY, PAYLOAD INT )"); + stat.execute("ALTER TABLE t1 DROP COLUMN PAYLOAD"); + conn.close(); + deleteDb(getTestName()); + } + + private void testWithOneCandidateWhenCaseInsensitiveIdentifiers() throws SQLException { + deleteDb(getTestName()); + Connection conn = getConnectionWithSettings("DATABASE_TO_UPPER=FALSE;CASE_INSENSITIVE_IDENTIFIERS=TRUE"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE T1 ( ID INT GENERATED BY DEFAULT AS IDENTITY, PAYLOAD INT )"); + stat.execute("ALTER TABLE t1 DROP COLUMN PAYLOAD"); + conn.close(); + deleteDb(getTestName()); + } + + private void testWithTwoCandidates() throws SQLException { + deleteDb(getTestName()); + Connection conn = getConnectionWithSettings("DATABASE_TO_UPPER=FALSE"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE Toast ( ID INT GENERATED BY DEFAULT AS IDENTITY )"); + stat.execute("CREATE TABLE TOAST ( ID INT GENERATED BY DEFAULT AS IDENTITY )"); + try { + stat.execute("ALTER TABLE toast DROP COLUMN ID"); + fail("Table `toast` was accessible but should not have been."); + } catch (SQLException e) { + String message = e.getMessage(); + assertContains(message, "Table \"toast\" not found (candidates are: \"TOAST, Toast\")"); + } + + conn.close(); + deleteDb(getTestName()); + } + + private Connection getConnectionWithSettings(String settings) throws SQLException { + return getConnection(getTestName() + ";" + settings); + } +} diff --git a/h2/src/test/org/h2/test/db/TestAnalyzeTableTx.java b/h2/src/test/org/h2/test/db/TestAnalyzeTableTx.java new file mode 100644 index 0000000..ca65c14 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestAnalyzeTableTx.java @@ -0,0 +1,61 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; + +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +public class TestAnalyzeTableTx extends TestDb { + private static final int C = 10_000; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + return !config.networked && !config.big; + } + + @Override + public void test() throws Exception { + deleteDb(getTestName()); + Connection[] connections = new Connection[C]; + try (Connection shared = getConnection(getTestName())) { + Statement statement = shared.createStatement(); + statement.executeUpdate("DROP TABLE IF EXISTS TEST"); + statement.executeUpdate("CREATE TABLE TEST(ID INT PRIMARY KEY)"); + for (int i = 0; i < C; i++) { + Connection c = getConnection(getTestName()); + c.createStatement().executeUpdate("INSERT INTO TEST VALUES (" + i + ')'); + connections[i] = c; + } + try (ResultSet rs = statement.executeQuery("SELECT * FROM TEST")) { + for (int i = 0; i < C; i++) { + if (!rs.next()) + throw new Exception("next"); + if (rs.getInt(1) != i) + throw new Exception(Integer.toString(i)); + } + } + } finally { + for (Connection connection : connections) { + if (connection != null) { + try { connection.close(); } catch (Throwable ignore) {/**/} + } + } + } + } +} diff --git a/h2/src/test/org/h2/test/db/TestAutoRecompile.java b/h2/src/test/org/h2/test/db/TestAutoRecompile.java new file mode 100644 index 0000000..e7fb639 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestAutoRecompile.java @@ -0,0 +1,54 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests if prepared statements are re-compiled when required. + */ +public class TestAutoRecompile extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + deleteDb("autoRecompile"); + Connection conn = getConnection("autoRecompile"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY)"); + PreparedStatement prep = conn.prepareStatement("SELECT * FROM TEST"); + assertEquals(1, prep.executeQuery().getMetaData().getColumnCount()); + stat.execute("ALTER TABLE TEST ADD COLUMN NAME VARCHAR(255)"); + assertEquals(2, prep.executeQuery().getMetaData().getColumnCount()); + stat.execute("DROP TABLE TEST"); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, X INT, Y INT)"); + assertEquals(3, prep.executeQuery().getMetaData().getColumnCount()); + // TODO test auto-recompile with insert..select, views and so on + + prep = conn.prepareStatement("INSERT INTO TEST VALUES(1, 2, 3)"); + stat.execute("ALTER TABLE TEST ADD COLUMN Z INT"); + assertThrows(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH, prep).execute(); + assertThrows(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH, prep).execute(); + conn.close(); + deleteDb("autoRecompile"); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestBackup.java b/h2/src/test/org/h2/test/db/TestBackup.java new file mode 100644 index 0000000..31801b2 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestBackup.java @@ -0,0 +1,195 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import org.h2.api.DatabaseEventListener; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.Backup; +import org.h2.tools.Restore; +import org.h2.util.Task; + +/** + * Test for the BACKUP SQL statement. + */ +public class TestBackup extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + if (config.memory) { + return false; + } + return true; + } + + @Override + public void test() throws SQLException { + testConcurrentBackup(); + testBackupRestoreLobStatement(); + testBackupRestoreLob(); + testBackup(); + deleteDb("backup"); + FileUtils.delete(getBaseDir() + "/backup.zip"); + } + + private void testConcurrentBackup() throws SQLException { + if (config.networked || !config.big) { + return; + } + deleteDb("backup"); + String url = getURL("backup", true); + Connection conn = getConnection(url); + final Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name varchar)"); + stat.execute("insert into test select x, 'Hello' from system_range(1, 2)"); + conn.setAutoCommit(false); + Connection conn1; + conn1 = getConnection(url); + final AtomicLong updateEnd = new AtomicLong(); + final Statement stat1 = conn.createStatement(); + Task task = new Task() { + @Override + public void call() throws Exception { + while (!stop) { + if (System.nanoTime() < updateEnd.get()) { + stat.execute("update test set name = 'Hallo'"); + stat1.execute("checkpoint"); + stat.execute("update test set name = 'Hello'"); + stat.execute("commit"); + stat.execute("checkpoint"); + } else { + Thread.sleep(10); + } + } + } + }; + Connection conn2; + conn2 = getConnection(url + ";database_event_listener='" + + BackupListener.class.getName() + "'"); + Statement stat2 = conn2.createStatement(); + task.execute(); + for (int i = 0; i < 10; i++) { + updateEnd.set(System.nanoTime() + TimeUnit.SECONDS.toNanos(2)); + stat2.execute("backup to '"+getBaseDir()+"/backup.zip'"); + stat2.execute("checkpoint"); + Restore.execute(getBaseDir() + "/backup.zip", getBaseDir() + "/t" + i, "backup"); + Connection conn3 = getConnection("t" + i + "/backup"); + Statement stat3 = conn3.createStatement(); + stat3.execute("script"); + ResultSet rs = stat3.executeQuery( + "select * from test where name='Hallo'"); + while (rs.next()) { + fail(); + } + conn3.close(); + } + task.get(); + conn2.close(); + conn.close(); + conn1.close(); + } + + /** + * A backup listener to test concurrent backup. + */ + public static class BackupListener implements DatabaseEventListener { + + @Override + public void setProgress(int state, String name, long x, long max) { + try { + Thread.sleep(1); + } catch (InterruptedException e) { + // ignore + } + if (x % 400 == 0) { + // System.out.println("state: " + state + + // " name: " + name + " x:" + x + "/" + max); + } + } + + } + + private void testBackupRestoreLob() throws SQLException { + deleteDb("backup"); + Connection conn = getConnection("backup"); + conn.createStatement().execute( + "create table test(x clob) as select space(10000)"); + conn.close(); + Backup.execute(getBaseDir() + "/backup.zip", + getBaseDir(), "backup", true); + deleteDb("backup"); + Restore.execute(getBaseDir() + "/backup.zip", + getBaseDir(), "backup"); + } + + private void testBackupRestoreLobStatement() throws SQLException { + deleteDb("backup"); + Connection conn = getConnection("backup"); + conn.createStatement().execute( + "create table test(x clob) as select space(10000)"); + conn.createStatement().execute("backup to '" + + getBaseDir() + "/backup.zip"+"'"); + conn.close(); + deleteDb("backup"); + Restore.execute(getBaseDir() + "/backup.zip", + getBaseDir(), "backup"); + } + + private void testBackup() throws SQLException { + deleteDb("backup"); + deleteDb("restored"); + Connection conn1, conn2, conn3; + Statement stat1, stat2, stat3; + conn1 = getConnection("backup"); + stat1 = conn1.createStatement(); + stat1.execute("create table test" + + "(id int primary key, name varchar(255))"); + stat1.execute("insert into test values" + + "(1, 'first'), (2, 'second')"); + stat1.execute("create table testlob" + + "(id int primary key, b blob, c clob)"); + stat1.execute("insert into testlob values" + + "(1, repeat(char(0), 10000), space(10000))"); + conn2 = getConnection("backup"); + stat2 = conn2.createStatement(); + stat2.execute("insert into test values(3, 'third')"); + conn2.setAutoCommit(false); + stat2.execute("insert into test values(4, 'fourth (uncommitted)')"); + stat2.execute("insert into testlob values(2, ' ', '00')"); + + stat1.execute("backup to '" + getBaseDir() + "/backup.zip'"); + conn2.rollback(); + assertEqualDatabases(stat1, stat2); + + Restore.execute(getBaseDir() + "/backup.zip", getBaseDir(), "restored"); + conn3 = getConnection("restored"); + stat3 = conn3.createStatement(); + assertEqualDatabases(stat1, stat3); + + conn1.close(); + conn2.close(); + conn3.close(); + deleteDb("restored"); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestBigDb.java b/h2/src/test/org/h2/test/db/TestBigDb.java new file mode 100644 index 0000000..a4e35d0 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestBigDb.java @@ -0,0 +1,170 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.TimeUnit; + +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.Utils; + +/** + * Test for big databases. + */ +public class TestBigDb extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + if (config.memory) { + return false; + } + if (config.networked && config.big) { + return false; + } + return true; + } + + @Override + public void test() throws SQLException { + testLargeTable(); + testInsert(); + testLeftSummary(); + deleteDb("bigDb"); + } + + private void testLargeTable() throws SQLException { + deleteDb("bigDb"); + Connection conn = getConnection("bigDb"); + Statement stat = conn.createStatement(); + stat.execute("CREATE CACHED TABLE TEST(" + + "M_CODE CHAR(1) DEFAULT CAST(RAND()*9 AS INT)," + + "PRD_CODE CHAR(20) DEFAULT SECURE_RAND(10)," + + "ORG_CODE_SUPPLIER CHAR(13) DEFAULT SECURE_RAND(6)," + + "PRD_CODE_1 CHAR(14) DEFAULT SECURE_RAND(7)," + + "PRD_CODE_2 CHAR(20) DEFAULT SECURE_RAND(10)," + + "ORG_CODE CHAR(13) DEFAULT SECURE_RAND(6)," + + "SUBSTITUTED_BY CHAR(20) DEFAULT SECURE_RAND(10)," + + "SUBSTITUTED_BY_2 CHAR(14) DEFAULT SECURE_RAND(7)," + + "SUBSTITUTION_FOR CHAR(20) DEFAULT SECURE_RAND(10)," + + "SUBSTITUTION_FOR_2 CHAR(14) DEFAULT SECURE_RAND(7)," + + "TEST CHAR(2) DEFAULT SECURE_RAND(1)," + + "TEST_2 CHAR(2) DEFAULT SECURE_RAND(1)," + + "TEST_3 DECIMAL(7,2) DEFAULT RAND()," + + "PRIMARY_UNIT_CODE CHAR(3) DEFAULT SECURE_RAND(1)," + + "RATE_PRICE_ORDER_UNIT DECIMAL(9,3) DEFAULT RAND()," + + "ORDER_UNIT_CODE CHAR(3) DEFAULT SECURE_RAND(1)," + + "ORDER_QTY_MIN DECIMAL(6,1) DEFAULT RAND()," + + "ORDER_QTY_LOT_SIZE DECIMAL(6,1) DEFAULT RAND()," + + "ORDER_UNIT_CODE_2 CHAR(3) DEFAULT SECURE_RAND(1)," + + "PRICE_GROUP CHAR(20) DEFAULT SECURE_RAND(10)," + + "LEAD_TIME INTEGER DEFAULT RAND()," + + "LEAD_TIME_UNIT_CODE CHAR(3) DEFAULT SECURE_RAND(1)," + + "PRD_GROUP CHAR(10) DEFAULT SECURE_RAND(5)," + + "WEIGHT_GROSS DECIMAL(7,3) DEFAULT RAND()," + + "WEIGHT_UNIT_CODE CHAR(3) DEFAULT SECURE_RAND(1)," + + "PACK_UNIT_CODE CHAR(3) DEFAULT SECURE_RAND(1)," + + "PACK_LENGTH DECIMAL(7,3) DEFAULT RAND()," + + "PACK_WIDTH DECIMAL(7,3) DEFAULT RAND()," + + "PACK_HEIGHT DECIMAL(7,3) DEFAULT RAND()," + + "SIZE_UNIT_CODE CHAR(3) DEFAULT SECURE_RAND(1)," + + "STATUS_CODE CHAR(3) DEFAULT SECURE_RAND(1)," + + "INTRA_STAT_CODE CHAR(12) DEFAULT SECURE_RAND(6)," + + "PRD_TITLE CHAR(50) DEFAULT SECURE_RAND(25)," + + "VALID_FROM DATE DEFAULT CURRENT_DATE," + + "MOD_DATUM DATE DEFAULT CURRENT_DATE)"); + int len = getSize(10, 50000); + try { + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST(PRD_CODE) VALUES('abc' || ?)"); + long time = System.nanoTime(); + for (int i = 0; i < len; i++) { + if ((i % 1000) == 0) { + long t = System.nanoTime(); + if (t - time > TimeUnit.SECONDS.toNanos(1)) { + time = t; + long free = Utils.getMemoryFree(); + println("i: " + i + " free: " + free + " used: " + Utils.getMemoryUsed()); + } + } + prep.setInt(1, i); + prep.execute(); + } + stat.execute("CREATE INDEX IDX_TEST_PRD_CODE ON TEST(PRD_CODE)"); + ResultSet rs = stat.executeQuery("SELECT * FROM TEST"); + int columns = rs.getMetaData().getColumnCount(); + while (rs.next()) { + for (int i = 0; i < columns; i++) { + rs.getString(i + 1); + } + } + } catch (OutOfMemoryError e) { + TestBase.logError("memory", e); + conn.close(); + throw e; + } + conn.close(); + } + + private void testLeftSummary() throws SQLException { + deleteDb("bigDb"); + Connection conn = getConnection("bigDb"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT, NEG INT AS -ID, " + + "NAME VARCHAR, PRIMARY KEY(ID, NAME))"); + stat.execute("CREATE INDEX IDX_NEG ON TEST(NEG, NAME)"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST(ID, NAME) VALUES(?, '1234567890')"); + int len = getSize(10, 1000); + int block = getSize(3, 10); + int left, x = 0; + for (int i = 0; i < len; i++) { + left = x + block / 2; + for (int j = 0; j < block; j++) { + prep.setInt(1, x++); + prep.execute(); + } + stat.execute("DELETE FROM TEST WHERE ID>" + left); + ResultSet rs = stat.executeQuery("SELECT COUNT(*) FROM TEST"); + rs.next(); + int count = rs.getInt(1); + trace("count: " + count); + } + conn.close(); + } + + private void testInsert() throws SQLException { + deleteDb("bigDb"); + Connection conn = getConnection("bigDb"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID IDENTITY, NAME VARCHAR)"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST(NAME) VALUES('Hello World')"); + int len = getSize(1000, 10000); + for (int i = 0; i < len; i++) { + if (i % 1000 == 0) { + println("rows: " + i); + Thread.yield(); + } + prep.execute(); + } + conn.close(); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestBigResult.java b/h2/src/test/org/h2/test/db/TestBigResult.java new file mode 100644 index 0000000..bb2e3fb --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestBigResult.java @@ -0,0 +1,499 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; + +import org.h2.message.TraceSystem; +import org.h2.store.FileLister; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Test for big result sets. + */ +public class TestBigResult extends TestDb { + + /** + * Run just this test. + * + * @param a + * ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + if (config.memory) { + return false; + } + return true; + } + + @Override + public void test() throws SQLException { + testLargeSubquery(); + testSortingAndDistinct(); + testLOB(); + testLargeUpdateDelete(); + testCloseConnectionDelete(); + testOrderGroup(); + testLimitBufferedResult(); + deleteDb("bigResult"); + } + + private void testLargeSubquery() throws SQLException { + deleteDb("bigResult"); + Connection conn = getConnection("bigResult"); + Statement stat = conn.createStatement(); + int len = getSize(1000, 4000); + stat.execute("SET MAX_MEMORY_ROWS " + (len / 10)); + stat.execute("CREATE TABLE RECOVERY(TRANSACTION_ID INT, SQL_STMT VARCHAR)"); + stat.execute("INSERT INTO RECOVERY " + "SELECT X, CASE MOD(X, 2) WHEN 0 THEN 'commit' ELSE 'begin' END " + + "FROM SYSTEM_RANGE(1, " + len + ")"); + ResultSet rs = stat.executeQuery("SELECT * FROM RECOVERY " + "WHERE SQL_STMT LIKE 'begin%' AND " + + "TRANSACTION_ID NOT IN(SELECT TRANSACTION_ID FROM RECOVERY " + + "WHERE SQL_STMT='commit' OR SQL_STMT='rollback')"); + int count = 0, last = 1; + while (rs.next()) { + assertEquals(last, rs.getInt(1)); + last += 2; + count++; + } + assertEquals(len / 2, count); + conn.close(); + } + + private void testSortingAndDistinct() throws SQLException { + deleteDb("bigResult"); + Connection conn = getConnection("bigResult"); + Statement stat = conn.createStatement(); + int count = getSize(1000, 4000); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, V INT NOT NULL)"); + PreparedStatement ps = conn.prepareStatement("INSERT INTO TEST VALUES (?, ?)"); + for (int i = 0; i < count; i++) { + ps.setInt(1, i); + ps.setInt(2, count - i); + ps.executeUpdate(); + } + // local result + testSortingAndDistinct1(stat, count, count); + // external result + testSortingAndDistinct1(stat, 10, count); + stat.execute("DROP TABLE TEST"); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, VALUE1 INT NOT NULL, VALUE2 INT NOT NULL)"); + ps = conn.prepareStatement("INSERT INTO TEST VALUES (?, ?, ?)"); + int partCount = count / 10; + for (int i = 0; i < count; i++) { + ps.setInt(1, i); + int a = i / 10; + int b = i % 10; + ps.setInt(2, partCount - a); + ps.setInt(3, 10 - b); + ps.executeUpdate(); + } + String sql; + /* + * Sorting only + */ + sql = "SELECT VALUE2, VALUE1 FROM (SELECT ID, VALUE2, VALUE1 FROM TEST ORDER BY VALUE2)"; + // local result + testSortingAndDistinct2(stat, sql, count, partCount); + // external result + testSortingAndDistinct2(stat, sql, 10, partCount); + /* + * Distinct only + */ + sql = "SELECT VALUE2, VALUE1 FROM (SELECT DISTINCT ID, VALUE2, VALUE1 FROM TEST)"; + // local result + testSortingAndDistinct2DistinctOnly(stat, sql, count, partCount); + // external result + testSortingAndDistinct2DistinctOnly(stat, sql, 10, partCount); + /* + * Sorting and distinct + */ + sql = "SELECT VALUE2, VALUE1 FROM (SELECT DISTINCT ID, VALUE2, VALUE1 FROM TEST ORDER BY VALUE2)"; + // local result + testSortingAndDistinct2(stat, sql, count, partCount); + // external result + testSortingAndDistinct2(stat, sql, 10, partCount); + /* + * One more distinct only + */ + sql = "SELECT VALUE1 FROM (SELECT DISTINCT VALUE1 FROM TEST)"; + // local result + testSortingAndDistinct3DistinctOnly(stat, sql, count, partCount); + // external result + testSortingAndDistinct3DistinctOnly(stat, sql, 1, partCount); + /* + * One more sorting and distinct + */ + sql = "SELECT VALUE1 FROM (SELECT DISTINCT VALUE1 FROM TEST ORDER BY VALUE1)"; + // local result + testSortingAndDistinct3(stat, sql, count, partCount); + // external result + testSortingAndDistinct3(stat, sql, 1, partCount); + stat.execute("DROP TABLE TEST"); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, V INT)"); + ps = conn.prepareStatement("INSERT INTO TEST VALUES (?, ?)"); + for (int i = 0; i < count; i++) { + ps.setInt(1, i); + int j = i / 10; + if (j == 0) { + ps.setNull(2, Types.INTEGER); + } else { + ps.setInt(2, j); + } + ps.executeUpdate(); + } + /* + * Sorting and distinct + */ + sql = "SELECT DISTINCT V FROM TEST ORDER BY V"; + // local result + testSortingAndDistinct4(stat, sql, count, partCount); + // external result + testSortingAndDistinct4(stat, sql, 1, partCount); + /* + * Distinct only + */ + sql = "SELECT DISTINCT V FROM TEST"; + // local result + testSortingAndDistinct4DistinctOnly(stat, sql, count, partCount); + // external result + testSortingAndDistinct4DistinctOnly(stat, sql, 1, partCount); + /* + * Sorting only + */ + sql = "SELECT V FROM TEST ORDER BY V"; + // local result + testSortingAndDistinct4SortingOnly(stat, sql, count, partCount); + // external result + testSortingAndDistinct4SortingOnly(stat, sql, 1, partCount); + conn.close(); + } + + private void testSortingAndDistinct1(Statement stat, int maxRows, int count) throws SQLException { + stat.execute("SET MAX_MEMORY_ROWS " + maxRows); + ResultSet rs = stat.executeQuery("SELECT V FROM (SELECT DISTINCT ID, V FROM TEST ORDER BY V)"); + for (int i = 1; i <= count; i++) { + assertTrue(rs.next()); + assertEquals(rs.getInt(1), i); + } + assertFalse(rs.next()); + } + + private void testSortingAndDistinct2(Statement stat, String sql, int maxRows, int partCount) throws SQLException { + ResultSet rs; + stat.execute("SET MAX_MEMORY_ROWS " + maxRows); + rs = stat.executeQuery(sql); + BitSet set = new BitSet(partCount); + for (int i = 1; i <= 10; i++) { + set.clear(); + for (int j = 1; j <= partCount; j++) { + assertTrue(rs.next()); + assertEquals(i, rs.getInt(1)); + set.set(rs.getInt(2)); + } + assertEquals(partCount + 1, set.nextClearBit(1)); + } + assertFalse(rs.next()); + } + + private void testSortingAndDistinct2DistinctOnly(Statement stat, String sql, int maxRows, int partCount) + throws SQLException { + ResultSet rs; + stat.execute("SET MAX_MEMORY_ROWS " + maxRows); + rs = stat.executeQuery(sql); + BitSet set = new BitSet(partCount * 10); + for (int i = 1; i <= 10; i++) { + for (int j = 1; j <= partCount; j++) { + assertTrue(rs.next()); + set.set(rs.getInt(1) * partCount + rs.getInt(2)); + } + } + assertEquals(partCount * 11 + 1, set.nextClearBit(partCount + 1)); + assertFalse(rs.next()); + } + + private void testSortingAndDistinct3(Statement stat, String sql, int maxRows, int partCount) throws SQLException { + ResultSet rs; + stat.execute("SET MAX_MEMORY_ROWS " + maxRows); + rs = stat.executeQuery(sql); + for (int i = 1; i <= partCount; i++) { + assertTrue(rs.next()); + assertEquals(i, rs.getInt(1)); + } + assertFalse(rs.next()); + } + + private void testSortingAndDistinct3DistinctOnly(Statement stat, String sql, int maxRows, int partCount) + throws SQLException { + ResultSet rs; + stat.execute("SET MAX_MEMORY_ROWS " + maxRows); + rs = stat.executeQuery(sql); + BitSet set = new BitSet(partCount); + for (int i = 1; i <= partCount; i++) { + assertTrue(rs.next()); + set.set(rs.getInt(1)); + } + assertEquals(partCount + 1, set.nextClearBit(1)); + assertFalse(rs.next()); + } + + private void testSortingAndDistinct4(Statement stat, String sql, int maxRows, int count) throws SQLException { + stat.execute("SET MAX_MEMORY_ROWS " + maxRows); + ResultSet rs = stat.executeQuery(sql); + for (int i = 0; i < count; i++) { + assertTrue(rs.next()); + assertEquals(i, rs.getInt(1)); + if (i == 0) { + assertTrue(rs.wasNull()); + } + } + assertFalse(rs.next()); + } + + private void testSortingAndDistinct4DistinctOnly(Statement stat, String sql, int maxRows, int count) + throws SQLException { + stat.execute("SET MAX_MEMORY_ROWS " + maxRows); + ResultSet rs = stat.executeQuery(sql); + BitSet set = new BitSet(); + for (int i = 0; i < count; i++) { + assertTrue(rs.next()); + int v = rs.getInt(1); + if (v == 0) { + assertTrue(rs.wasNull()); + } + assertFalse(set.get(v)); + set.set(v); + } + assertFalse(rs.next()); + assertEquals(count, set.nextClearBit(0)); + } + + private void testSortingAndDistinct4SortingOnly(Statement stat, String sql, int maxRows, int count) + throws SQLException { + stat.execute("SET MAX_MEMORY_ROWS " + maxRows); + ResultSet rs = stat.executeQuery(sql); + for (int i = 0; i < count; i++) { + for (int j = 0; j < 10; j++) { + assertTrue(rs.next()); + assertEquals(i, rs.getInt(1)); + if (i == 0) { + assertTrue(rs.wasNull()); + } + } + } + assertFalse(rs.next()); + } + + private void testLOB() throws SQLException { + if (config.traceLevelFile == TraceSystem.DEBUG) { + // Trace system on this level can throw OOME with such large + // arguments as used in this test. + return; + } + deleteDb("bigResult"); + Connection conn = getConnection("bigResult"); + Statement stat = conn.createStatement(); + stat.execute("SET MAX_MEMORY_ROWS " + 1); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, V BLOB NOT NULL)"); + PreparedStatement ps = conn.prepareStatement("INSERT INTO TEST VALUES (?, ?)"); + int length = 1_000_000; + byte[] data = new byte[length]; + for (int i = 1; i <= 10; i++) { + ps.setInt(1, i); + Arrays.fill(data, (byte) i); + ps.setBytes(2, data); + ps.executeUpdate(); + } + Blob[] blobs = new Blob[10]; + ResultSet rs = stat.executeQuery("SELECT * FROM TEST"); + for (int i = 1; i <= 10; i++) { + assertTrue(rs.next()); + assertEquals(i, rs.getInt(1)); + blobs[i - 1] = rs.getBlob(2); + } + assertFalse(rs.next()); + rs.close(); + for (int i = 1; i <= 10; i++) { + Blob b = blobs[i - 1]; + byte[] bytes = b.getBytes(1, (int) b.length()); + Arrays.fill(data, (byte) i); + assertEquals(data, bytes); + b.free(); + } + stat.execute("DROP TABLE TEST"); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, V CLOB NOT NULL)"); + ps = conn.prepareStatement("INSERT INTO TEST VALUES (?, ?)"); + char[] cdata = new char[length]; + for (int i = 1; i <= 10; i++) { + ps.setInt(1, i); + Arrays.fill(cdata, (char) i); + ps.setString(2, new String(cdata)); + ps.executeUpdate(); + } + Clob[] clobs = new Clob[10]; + rs = stat.executeQuery("SELECT * FROM TEST"); + for (int i = 1; i <= 10; i++) { + assertTrue(rs.next()); + assertEquals(i, rs.getInt(1)); + clobs[i - 1] = rs.getClob(2); + } + assertFalse(rs.next()); + rs.close(); + for (int i = 1; i <= 10; i++) { + Clob c = clobs[i - 1]; + String string = c.getSubString(1, (int) c.length()); + Arrays.fill(cdata, (char) i); + assertEquals(new String(cdata), string); + c.free(); + } + conn.close(); + } + + private void testLargeUpdateDelete() throws SQLException { + deleteDb("bigResult"); + Connection conn = getConnection("bigResult"); + Statement stat = conn.createStatement(); + int len = getSize(10000, 100000); + stat.execute("SET MAX_OPERATION_MEMORY 4096"); + stat.execute("CREATE TABLE TEST AS SELECT * FROM SYSTEM_RANGE(1, " + len + ")"); + stat.execute("UPDATE TEST SET X=X+1"); + stat.execute("DELETE FROM TEST"); + conn.close(); + } + + private void testCloseConnectionDelete() throws SQLException { + deleteDb("bigResult"); + Connection conn = getConnection("bigResult"); + Statement stat = conn.createStatement(); + stat.execute("SET MAX_MEMORY_ROWS 2"); + ResultSet rs = stat.executeQuery("SELECT * FROM SYSTEM_RANGE(1, 100)"); + while (rs.next()) { + // ignore + } + // rs.close(); + conn.close(); + deleteDb("bigResult"); + ArrayList files = FileLister.getDatabaseFiles(getBaseDir(), "bigResult", true); + if (files.size() > 0) { + fail("file not deleted: " + files.get(0)); + } + } + + private void testLimitBufferedResult() throws SQLException { + deleteDb("bigResult"); + Connection conn = getConnection("bigResult"); + Statement stat = conn.createStatement(); + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST(ID INT)"); + for (int i = 0; i < 200; i++) { + stat.execute("INSERT INTO TEST(ID) VALUES(" + i + ")"); + } + stat.execute("SET MAX_MEMORY_ROWS 100"); + ResultSet rs; + rs = stat.executeQuery("select id from test order by id limit 10 offset 85"); + for (int i = 85; rs.next(); i++) { + assertEquals(i, rs.getInt(1)); + } + rs = stat.executeQuery("select id from test order by id limit 10 offset 95"); + for (int i = 95; rs.next(); i++) { + assertEquals(i, rs.getInt(1)); + } + rs = stat.executeQuery("select id from test order by id limit 10 offset 105"); + for (int i = 105; rs.next(); i++) { + assertEquals(i, rs.getInt(1)); + } + conn.close(); + } + + private void testOrderGroup() throws SQLException { + deleteDb("bigResult"); + Connection conn = getConnection("bigResult"); + Statement stat = conn.createStatement(); + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST(" + "ID INT PRIMARY KEY, " + "Name VARCHAR(255), " + "FirstName VARCHAR(255), " + + "Points INT," + "LicenseID INT)"); + int len = getSize(10, 5000); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST VALUES(?, ?, ?, ?, ?)"); + for (int i = 0; i < len; i++) { + prep.setInt(1, i); + prep.setString(2, "Name " + i); + prep.setString(3, "First Name " + i); + prep.setInt(4, i * 10); + prep.setInt(5, i * i); + prep.execute(); + } + conn.close(); + conn = getConnection("bigResult"); + stat = conn.createStatement(); + stat.setMaxRows(len + 1); + ResultSet rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + for (int i = 0; i < len; i++) { + rs.next(); + assertEquals(i, rs.getInt(1)); + assertEquals("Name " + i, rs.getString(2)); + assertEquals("First Name " + i, rs.getString(3)); + assertEquals(i * 10, rs.getInt(4)); + assertEquals(i * i, rs.getInt(5)); + } + + stat.setMaxRows(len + 1); + rs = stat.executeQuery("SELECT * FROM TEST WHERE ID >= 1000 ORDER BY ID"); + for (int i = 1000; i < len; i++) { + rs.next(); + assertEquals(i, rs.getInt(1)); + assertEquals("Name " + i, rs.getString(2)); + assertEquals("First Name " + i, rs.getString(3)); + assertEquals(i * 10, rs.getInt(4)); + assertEquals(i * i, rs.getInt(5)); + } + + stat.execute("SET MAX_MEMORY_ROWS 2"); + rs = stat.executeQuery("SELECT Name, SUM(ID) FROM TEST GROUP BY NAME"); + while (rs.next()) { + rs.getString(1); + rs.getInt(2); + } + + conn.setAutoCommit(false); + stat.setMaxRows(0); + stat.execute("SET MAX_MEMORY_ROWS 0"); + stat.execute("CREATE TABLE DATA(ID INT, NAME VARCHAR_IGNORECASE(255))"); + prep = conn.prepareStatement("INSERT INTO DATA VALUES(?, ?)"); + for (int i = 0; i < len; i++) { + prep.setInt(1, i); + prep.setString(2, "" + i / 200); + prep.execute(); + } + Statement s2 = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); + rs = s2.executeQuery("SELECT NAME FROM DATA"); + rs.last(); + conn.setAutoCommit(true); + + rs = s2.executeQuery("SELECT NAME FROM DATA ORDER BY ID"); + while (rs.next()) { + // do nothing + } + + conn.close(); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestCases.java b/h2/src/test/org/h2/test/db/TestCases.java new file mode 100644 index 0000000..d951203 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestCases.java @@ -0,0 +1,1765 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.StringReader; +import java.sql.Connection; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.List; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import org.h2.api.ErrorCode; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Various test cases. + */ +public class TestCases extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testMinimalCoveringIndexPlan(); + testMinMaxDirectLookupIndex(); + testReferenceLaterTable(); + testAutoCommitInDatabaseURL(); + testReferenceableIndexUsage(); + testClearSyntaxException(); + testEmptyStatements(); + testViewParameters(); + testLargeKeys(); + testExtraSemicolonInDatabaseURL(); + testGroupSubquery(); + testCountDistinctNotNull(); + testDependencies(); + testConvertType(); + testSortedSelect(); + testMaxMemoryRows(); + testLikeExpressions(); + testUnicode(); + testOuterJoin(); + testCommentOnColumnWithSchemaEqualDatabase(); + testColumnWithConstraintAndComment(); + testTruncateConstraintsDisabled(); + testPreparedSubquery2(); + testPreparedSubquery(); + testCompareDoubleWithIntColumn(); + testDeleteIndexOutOfBounds(); + testOrderByWithSubselect(); + testInsertDeleteRollback(); + testLargeRollback(); + testConstraintAlterTable(); + testJoinWithView(); + testLobDecrypt(); + testInvalidDatabaseName(); + testReuseSpace(); + testDeleteGroup(); + testDisconnect(); + testExecuteTrace(); + testExplain(); + testExplainAnalyze(); + testDataChangeDeltaTable(); + testGroupSortedReset(); + if (config.memory) { + return; + } + testCheckConstraintWithFunction(); + testDeleteAndDropTableWithLobs(true); + testDeleteAndDropTableWithLobs(false); + testEmptyBtreeIndex(); + testReservedKeywordReconnect(); + testSpecialSQL(); + testUpperCaseLowerCaseDatabase(); + testManualCommitSet(); + testSchemaIdentityReconnect(); + testAlterTableReconnect(); + testPersistentSettings(); + testInsertSelectUnion(); + testViewReconnect(); + testDefaultQueryReconnect(); + testBigString(); + testRenameReconnect(); + testCreateDrop(); + testPolePos(); + testQuick(); + testMutableObjects(); + testSelectForUpdate(); + testDoubleRecovery(); + testConstraintReconnect(); + testCollation(); + deleteDb("cases"); + } + + private void testReferenceLaterTable() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("create table a(id int)"); + stat.execute("create table b(id int)"); + stat.execute("drop table a"); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, stat). + execute("create table a(id int check id < select max(id) from b)"); + stat.execute("drop table b"); + stat.execute("create table b(id int)"); + stat.execute("create table a(id int check id < select max(id) from b)"); + conn.close(); + conn = getConnection("cases"); + conn.close(); + } + + private void testAutoCommitInDatabaseURL() throws SQLException { + Connection conn = getConnection("cases;autocommit=false"); + assertFalse(conn.getAutoCommit()); + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("call autocommit()"); + rs.next(); + assertFalse(rs.getBoolean(1)); + conn.close(); + } + + private void testReferenceableIndexUsage() throws SQLException { + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("drop table if exists a, b"); + stat.execute("create table a(id int, x int) as select 1, 100"); + stat.execute("create index idx1 on a(id, x)"); + stat.execute("alter table a add unique(id)"); + stat.execute("create table b(id int primary key, a_id int) as select 1, 1"); + stat.execute("alter table b add constraint x " + + "foreign key(a_id) references a(id)"); + stat.execute("update a set x=200"); + stat.execute("drop table if exists a, b cascade"); + conn.close(); + } + + private void testClearSyntaxException() throws SQLException { + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + assertThrows(42000, stat).execute("select t.x, t.x t.y from dual t"); + conn.close(); + } + + private void testEmptyStatements() throws SQLException { + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;"); + stat.execute(""); + stat.execute(";"); + stat.execute(" ;"); + conn.close(); + } + + private void testViewParameters() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute( + "create view test as select 0 v, 'x' name from dual"); + PreparedStatement prep = conn.prepareStatement( + "select 1 from test where name=? and v=? and v<=?"); + prep.setString(1, "x"); + prep.setInt(2, 0); + prep.setInt(3, 1); + prep.executeQuery(); + conn.close(); + } + + private void testLargeKeys() throws SQLException { + if (config.memory) { + return; + } + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name varchar)"); + stat.execute("create index on test(name)"); + stat.execute("insert into test values(1, '1' || space(1500))"); + conn.close(); + conn = getConnection("cases"); + stat = conn.createStatement(); + stat.execute("insert into test values(2, '2' || space(1500))"); + conn.close(); + conn = getConnection("cases"); + stat = conn.createStatement(); + stat.executeQuery("select name from test order by name"); + conn.close(); + } + + private void testExtraSemicolonInDatabaseURL() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases;"); + conn.close(); + conn = getConnection("cases;;mode=mysql;"); + conn.close(); + } + + private void testGroupSubquery() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("create table test(a int, b int)"); + stat.execute("create index idx on test(a)"); + stat.execute("insert into test values (1, 9), (2, 9), (3, 9)"); + ResultSet rs = stat.executeQuery("select (select count(*)" + + " from test where a = t.a and b = 0) from test t group by a"); + rs.next(); + assertEquals(0, rs.getInt(1)); + conn.close(); + } + + private void testCountDistinctNotNull() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int not null) as " + + "select 1 from system_range(1, 10)"); + ResultSet rs = stat.executeQuery("select count(distinct id) from test"); + rs.next(); + assertEquals(1, rs.getInt(1)); + conn.close(); + } + + private void testDependencies() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + + // avoid endless recursion when adding dependencies + stat.execute("create table test(id int primary key, parent int)"); + stat.execute("alter table test add constraint test check " + + "(select count(*) from test) < 10"); + stat.execute("create table b()"); + stat.execute("drop table b"); + stat.execute("drop table test"); + + // ensure the dependency is detected + stat.execute("create alias is_positive as " + + "'boolean isPositive(int x) { return x > 0; }'"); + stat.execute("create table a(a integer, constraint test check is_positive(a))"); + assertThrows(ErrorCode.CANNOT_DROP_2, stat). + execute("drop alias is_positive"); + stat.execute("drop table a"); + stat.execute("drop alias is_positive"); + + // ensure trying to reference the table fails + // (otherwise re-opening the database is not possible) + stat.execute("create table test(id int primary key)"); + assertThrows(ErrorCode.COLUMN_IS_REFERENCED_1, stat). + execute("alter table test alter column id " + + "set default ifnull((select max(id) from test)+1, 0)"); + stat.execute("drop table test"); + conn.close(); + } + + private void testConvertType() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("create table test as select cast(0 as dec(10, 2)) x"); + ResultSetMetaData meta = stat.executeQuery("select * from test").getMetaData(); + assertEquals(10, meta.getPrecision(1)); + assertEquals(2, meta.getScale(1)); + stat.execute("alter table test add column y int"); + stat.execute("drop table test"); + conn.close(); + } + + private void testSortedSelect() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("create memory temporary table test(id int) not persistent"); + stat.execute("insert into test(id) direct sorted select 1"); + stat.execute("drop table test"); + conn.close(); + } + + private void testMaxMemoryRows() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection( + "cases;MAX_MEMORY_ROWS=1"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key)"); + stat.execute("insert into test values(1), (2)"); + stat.execute("select * from system_range(1, 1) where x not in " + + "(select id from test order by id)"); + stat.execute("select * from system_range(1, 1) where x not in " + + "(select id from test union select id from test)"); + stat.execute("(select id from test order by id) " + + "intersect (select id from test order by id)"); + conn.close(); + } + + private void testUnicode() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id identity, name text)"); + String[] data = { "\uff1e", "\ud848\udf1e" }; + PreparedStatement prep = conn.prepareStatement( + "insert into test(name) values(?)"); + for (int i = 0; i < data.length; i++) { + prep.setString(1, data[i]); + prep.execute(); + } + prep = conn.prepareStatement("select * from test order by id"); + ResultSet rs = prep.executeQuery(); + for (int i = 0; i < data.length; i++) { + assertTrue(rs.next()); + assertEquals(data[i], rs.getString(2)); + } + stat.execute("drop table test"); + conn.close(); + } + + private void testCheckConstraintWithFunction() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("create alias is_email as " + + "'boolean isEmail(String x) { return x != null && x.indexOf(''@'') > 0; }'"); + stat.execute("create domain email as varchar check is_email(value)"); + stat.execute("create table test(e email)"); + conn.close(); + conn = getConnection("cases"); + conn.close(); + } + + private void testOuterJoin() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("create table parent(p int primary key) as select 1"); + stat.execute("create table child(c int primary key, pc int) as select 2, 1"); + ResultSet rs = stat.executeQuery("select * from parent " + + "left outer join child on p = pc where c is null"); + assertFalse(rs.next()); + stat.execute("drop all objects"); + conn.close(); + } + + private void testCommentOnColumnWithSchemaEqualDatabase() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("create schema cases"); + stat.execute("create table cases.cases(cases int)"); + stat.execute("comment on column " + + "cases.cases.cases is 'schema.table.column'"); + stat.execute("comment on column " + + "cases.cases.cases.cases is 'db.schema.table.column'"); + conn.close(); + } + + private void testColumnWithConstraintAndComment() throws SQLException { + if (config.memory) { + return; + } + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int check id < 500)"); + stat.execute("comment on column test.id is 'comment'"); + conn.close(); + conn = getConnection("cases"); + conn.close(); + } + + private void testTruncateConstraintsDisabled() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("create table parent(id identity) as select 0"); + stat.execute("create table child(id identity, " + + "parent int references parent(id)) as select 0, 0"); + assertThrows(ErrorCode.CANNOT_TRUNCATE_1, stat). + execute("truncate table parent"); + assertThrows(ErrorCode.REFERENTIAL_INTEGRITY_VIOLATED_CHILD_EXISTS_1, stat). + execute("delete from parent"); + stat.execute("alter table parent set referential_integrity false"); + stat.execute("delete from parent"); + stat.execute("truncate table parent"); + stat.execute("alter table parent set referential_integrity true"); + assertThrows(ErrorCode.CANNOT_TRUNCATE_1, stat). + execute("truncate table parent"); + stat.execute("set referential_integrity false"); + stat.execute("truncate table parent"); + conn.close(); + } + + private void testPreparedSubquery2() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name varchar(255))"); + stat.execute("insert into test values(1, 'Hello')"); + stat.execute("insert into test values(2, 'World')"); + + PreparedStatement ps = conn.prepareStatement( + "select name from test where id in " + + "(select id from test where name = ?)"); + ps.setString(1, "Hello"); + ResultSet rs = ps.executeQuery(); + if (rs.next()) { + if (!rs.getString("name").equals("Hello")) { + fail("'" + rs.getString("name") + "' must be 'Hello'"); + } + } else { + fail("Must have a result!"); + } + rs.close(); + + ps.setString(1, "World"); + rs = ps.executeQuery(); + if (rs.next()) { + if (!rs.getString("name").equals("World")) { + fail("'" + rs.getString("name") + "' must be 'World'"); + } + } else { + fail("Must have a result!"); + } + rs.close(); + conn.close(); + } + + private void testPreparedSubquery() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int)"); + stat.execute("insert into test values(1)"); + String sql = "select ?, ?, (select count(*) from test inner join " + + "(select id from test where 0=?) as t2 on t2.id=test.id) from test"; + ResultSet rs; + rs = stat.executeQuery(sql.replace('?', '0')); + rs.next(); + assertEquals(1, rs.getInt(3)); + PreparedStatement prep = conn.prepareStatement(sql); + prep.setInt(1, 0); + prep.setInt(2, 0); + prep.setInt(3, 0); + rs = prep.executeQuery(); + rs.next(); + assertEquals(1, rs.getInt(3)); + conn.close(); + } + + private void testCompareDoubleWithIntColumn() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + testCompareDoubleWithIntColumn(stat, false, 0.1, false); + testCompareDoubleWithIntColumn(stat, false, 0.1, true); + testCompareDoubleWithIntColumn(stat, false, 0.9, false); + testCompareDoubleWithIntColumn(stat, false, 0.9, true); + testCompareDoubleWithIntColumn(stat, true, 0.1, false); + testCompareDoubleWithIntColumn(stat, true, 0.1, true); + testCompareDoubleWithIntColumn(stat, true, 0.9, false); + testCompareDoubleWithIntColumn(stat, true, 0.9, true); + conn.close(); + } + + private void testCompareDoubleWithIntColumn(Statement stat, boolean pk, + double x, boolean prepared) throws SQLException { + if (pk) { + stat.execute("create table test(id int primary key)"); + } else { + stat.execute("create table test(id int)"); + } + stat.execute("insert into test values(1)"); + ResultSet rs; + if (prepared) { + PreparedStatement prep = stat.getConnection().prepareStatement( + "select * from test where id > ?"); + prep.setDouble(1, x); + rs = prep.executeQuery(); + } else { + rs = stat.executeQuery("select * from test where id > " + x); + } + assertTrue(rs.next()); + stat.execute("drop table test"); + } + + private void testDeleteIndexOutOfBounds() throws SQLException { + if (config.memory || !config.big) { + return; + } + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE IF NOT EXISTS test " + + "(rowid INTEGER PRIMARY KEY AUTO_INCREMENT, txt VARCHAR(64000));"); + PreparedStatement prep = conn.prepareStatement( + "insert into test (txt) values(space(?))"); + for (int i = 0; i < 3000; i++) { + prep.setInt(1, i * 3); + prep.execute(); + } + stat.execute("DELETE FROM test;"); + conn.close(); + } + + private void testInsertDeleteRollback() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("set cache_size 1"); + stat.execute("SET MAX_MEMORY_ROWS " + Integer.MAX_VALUE); + stat.execute("SET MAX_MEMORY_UNDO " + Integer.MAX_VALUE); + stat.execute("SET MAX_OPERATION_MEMORY " + Integer.MAX_VALUE); + stat.execute("create table test(id identity)"); + conn.setAutoCommit(false); + stat.execute("insert into test select x from system_range(1, 11)"); + stat.execute("delete from test"); + conn.rollback(); + conn.close(); + } + + private void testLargeRollback() throws SQLException { + Connection conn; + Statement stat; + + deleteDb("cases"); + conn = getConnection("cases"); + stat = conn.createStatement(); + stat.execute("set max_operation_memory 1"); + stat.execute("create table test(id int)"); + stat.execute("insert into test values(1), (2)"); + stat.execute("create index idx on test(id)"); + conn.setAutoCommit(false); + stat.execute("update test set id = id where id=2"); + stat.execute("update test set id = id"); + conn.rollback(); + conn.close(); + + deleteDb("cases"); + conn = getConnection("cases"); + conn.createStatement().execute("set MAX_MEMORY_UNDO 1"); + conn.createStatement().execute("create table test(id number primary key)"); + conn.createStatement().execute( + "insert into test(id) select x from system_range(1, 2)"); + Connection conn2 = getConnection("cases"); + conn2.setAutoCommit(false); + assertEquals(2, conn2.createStatement().executeUpdate( + "delete from test")); + conn2.close(); + conn.close(); + + deleteDb("cases"); + conn = getConnection("cases"); + conn.createStatement().execute("set MAX_MEMORY_UNDO 8"); + conn.createStatement().execute("create table test(id number primary key)"); + conn.setAutoCommit(false); + conn.createStatement().execute( + "insert into test select x from system_range(1, 10)"); + conn.createStatement().execute("delete from test"); + conn.rollback(); + conn.close(); + } + + private void testConstraintAlterTable() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("create table parent (pid int primary key)"); + stat.execute("create table child (cid int primary key, pid int)"); + stat.execute("alter table child add foreign key (pid) references parent(pid)"); + stat.execute("alter table child add column c2 int"); + stat.execute("alter table parent add column p2 varchar"); + conn.close(); + } + + private void testEmptyBtreeIndex() throws SQLException { + deleteDb("cases"); + Connection conn; + conn = getConnection("cases"); + conn.createStatement().execute("CREATE TABLE test(id int PRIMARY KEY);"); + conn.createStatement().execute( + "INSERT INTO test SELECT X FROM SYSTEM_RANGE(1, 77)"); + conn.createStatement().execute("DELETE from test"); + conn.close(); + conn = getConnection("cases"); + conn.createStatement().execute("INSERT INTO test (id) VALUES (1)"); + conn.close(); + conn = getConnection("cases"); + conn.createStatement().execute("DELETE from test"); + conn.createStatement().execute("drop table test"); + conn.close(); + } + + private void testJoinWithView() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + conn.createStatement().execute( + "create table t(i identity, n varchar) as select 1, 'x'"); + PreparedStatement prep = conn.prepareStatement( + "select 1 from dual " + + "inner join(select n from t where i=?) a on a.n='x' " + + "inner join(select n from t where i=?) b on b.n='x'"); + prep.setInt(1, 1); + prep.setInt(2, 1); + prep.execute(); + conn.close(); + } + + private void testLobDecrypt() throws SQLException { + Connection conn = getConnection("cases"); + String key = "key"; + String value = "Hello World"; + PreparedStatement prep = conn.prepareStatement( + "CALL ENCRYPT('AES', RAWTOHEX(?), STRINGTOUTF8(?))"); + prep.setCharacterStream(1, new StringReader(key), -1); + prep.setCharacterStream(2, new StringReader(value), -1); + ResultSet rs = prep.executeQuery(); + rs.next(); + byte[] encrypted = rs.getBytes(1); + PreparedStatement prep2 = conn.prepareStatement( + "CALL TRIM(CHAR(0) FROM " + + "UTF8TOSTRING(DECRYPT('AES', RAWTOHEX(?), ?)))"); + prep2.setCharacterStream(1, new StringReader(key), -1); + prep2.setBinaryStream(2, new ByteArrayInputStream(encrypted), -1); + ResultSet rs2 = prep2.executeQuery(); + rs2.first(); + String decrypted = rs2.getString(1); + prep2.close(); + assertEquals(value, decrypted); + conn.close(); + } + + private void testReservedKeywordReconnect() throws SQLException { + if (config.memory) { + return; + } + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("create table \"UNIQUE\"(\"UNIQUE\" int)"); + conn.close(); + conn = getConnection("cases"); + stat = conn.createStatement(); + stat.execute("select \"UNIQUE\" from \"UNIQUE\""); + stat.execute("drop table \"UNIQUE\""); + conn.close(); + } + + private void testInvalidDatabaseName() { + if (config.memory) { + return; + } + assertThrows(ErrorCode.INVALID_DATABASE_NAME_1, () -> getConnection("cases/")); + } + + private void testReuseSpace() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + int tableCount = getSize(2, 5); + for (int i = 0; i < tableCount; i++) { + stat.execute("create table t" + i + "(data varchar)"); + } + Random random = new Random(1); + int len = getSize(50, 500); + for (int i = 0; i < len; i++) { + String table = "t" + random.nextInt(tableCount); + String sql; + if (random.nextBoolean()) { + sql = "insert into " + table + " values(space(100000))"; + } else { + sql = "delete from " + table; + } + stat.execute(sql); + stat.execute("script to '" + getBaseDir() + "/test.sql'"); + } + conn.close(); + FileUtils.delete(getBaseDir() + "/test.sql"); + } + + private void testDeleteGroup() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("set max_memory_rows 2"); + stat.execute("create table test(id int primary key, x int)"); + stat.execute("insert into test values(0, 0), (1, 1), (2, 2)"); + stat.execute("delete from test where id not in " + + "(select min(x) from test group by id)"); + conn.close(); + } + + private void testSpecialSQL() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + assertThrows(ErrorCode.SYNTAX_ERROR_2, stat). + execute("create table address" + + "(id identity, name varchar check? instr(value, '@') > 1)"); + stat.execute("SET AUTOCOMMIT OFF; \n//" + + "create sequence if not exists object_id;\n"); + stat.execute("SET AUTOCOMMIT OFF;\n//" + + "create sequence if not exists object_id;\n"); + stat.execute("SET AUTOCOMMIT OFF; //" + + "create sequence if not exists object_id;"); + stat.execute("SET AUTOCOMMIT OFF;//" + + "create sequence if not exists object_id;"); + stat.execute("SET AUTOCOMMIT OFF \n//" + + "create sequence if not exists object_id;"); + stat.execute("SET AUTOCOMMIT OFF\n//" + + "create sequence if not exists object_id;"); + stat.execute("SET AUTOCOMMIT OFF //" + + "create sequence if not exists object_id;"); + stat.execute("SET AUTOCOMMIT OFF//" + + "create sequence if not exists object_id;"); + stat.execute("SET AUTOCOMMIT OFF; \n///" + + "create sequence if not exists object_id;"); + stat.execute("SET AUTOCOMMIT OFF;\n///" + + "create sequence if not exists object_id;"); + stat.execute("SET AUTOCOMMIT OFF; ///" + + "create sequence if not exists object_id;"); + stat.execute("SET AUTOCOMMIT OFF;///" + + "create sequence if not exists object_id;"); + stat.execute("SET AUTOCOMMIT OFF \n///" + + "create sequence if not exists object_id;"); + stat.execute("SET AUTOCOMMIT OFF\n///" + + "create sequence if not exists object_id;"); + stat.execute("SET AUTOCOMMIT OFF ///" + + "create sequence if not exists object_id;"); + stat.execute("SET AUTOCOMMIT OFF///" + + "create sequence if not exists object_id;"); + conn.close(); + } + + private void testUpperCaseLowerCaseDatabase() throws SQLException { + if (File.separatorChar != '\\' || config.googleAppEngine) { + return; + } + deleteDb("cases"); + deleteDb("CaSeS"); + Connection conn, conn2; + ResultSet rs; + conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("CHECKPOINT"); + stat.execute("CREATE TABLE TEST(ID INT)"); + stat.execute("INSERT INTO TEST VALUES(1)"); + stat.execute("CHECKPOINT"); + + conn2 = getConnection("CaSeS"); + rs = conn.createStatement().executeQuery("SELECT * FROM TEST"); + assertTrue(rs.next()); + conn2.close(); + + conn.close(); + + conn = getConnection("cases"); + rs = conn.createStatement().executeQuery("SELECT * FROM TEST"); + assertTrue(rs.next()); + conn.close(); + + conn = getConnection("CaSeS"); + rs = conn.createStatement().executeQuery("SELECT * FROM TEST"); + assertTrue(rs.next()); + conn.close(); + + deleteDb("cases"); + deleteDb("CaSeS"); + + } + + private void testManualCommitSet() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Connection conn2 = getConnection("cases"); + conn.setAutoCommit(false); + conn2.setAutoCommit(false); + conn.createStatement().execute("SET MODE REGULAR"); + conn2.createStatement().execute("SET MODE REGULAR"); + conn.close(); + conn2.close(); + } + + private void testSchemaIdentityReconnect() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("create schema s authorization sa"); + stat.execute("create table s.test(id identity)"); + conn.close(); + conn = getConnection("cases"); + ResultSet rs = conn.createStatement().executeQuery( + "SELECT * FROM S.TEST"); + while (rs.next()) { + // ignore + } + conn.close(); + } + + private void testDisconnect() throws Exception { + if (config.networked || config.codeCoverage) { + return; + } + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID IDENTITY)"); + for (int i = 0; i < 1000; i++) { + stat.execute("INSERT INTO TEST() VALUES()"); + } + SQLException[] stopped = { null }; + Thread t = new Thread(() -> { + try { + long time = System.nanoTime(); + ResultSet rs = stat.executeQuery("SELECT MAX(T.ID) " + + "FROM TEST T, TEST, TEST, TEST, TEST, " + + "TEST, TEST, TEST, TEST, TEST, TEST"); + rs.next(); + time = System.nanoTime() - time; + TestBase.logError("query was too quick; result: " + + rs.getInt(1) + " time:" + TimeUnit.NANOSECONDS.toMillis(time), null); + } catch (SQLException e) { + stopped[0] = e; + // ok + } + }); + t.start(); + Thread.sleep(300); + long time = System.nanoTime(); + conn.close(); + t.join(5000); + if (stopped[0] == null) { + fail("query still running"); + } else { + assertKnownException(stopped[0]); + } + time = System.nanoTime() - time; + if (time > TimeUnit.SECONDS.toNanos(5)) { + if (!config.reopen) { + fail("closing took " + time); + } + } + deleteDb("cases"); + } + + private void testExecuteTrace() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery( + "SELECT ? FROM DUAL {1: 'Hello'}"); + rs.next(); + assertEquals("Hello", rs.getString(1)); + assertFalse(rs.next()); + + rs = stat.executeQuery("SELECT ? FROM DUAL UNION ALL " + + "SELECT ? FROM DUAL {1: 'Hello', 2:'World' }"); + rs.next(); + assertEquals("Hello", rs.getString(1)); + rs.next(); + assertEquals("World", rs.getString(1)); + assertFalse(rs.next()); + + conn.close(); + } + + private void checkExplain(Statement stat, String sql, String expected) throws SQLException { + ResultSet rs = stat.executeQuery(sql); + + assertTrue(rs.next()); + + assertEquals(expected, rs.getString(1)); + } + + private void testExplain() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + + stat.execute("CREATE TABLE ORGANIZATION" + + "(id int primary key, name varchar(100))"); + stat.execute("CREATE TABLE PERSON" + + "(id int primary key, orgId int, name varchar(100), salary int)"); + + checkExplain(stat, "/* bla-bla */ EXPLAIN SELECT ID FROM ORGANIZATION WHERE id = ?", + "SELECT\n" + + " \"ID\"\n" + + "FROM \"PUBLIC\".\"ORGANIZATION\"\n" + + " /* PUBLIC.PRIMARY_KEY_D: ID = ?1 */\n" + + "WHERE \"ID\" = ?1"); + + checkExplain(stat, "EXPLAIN SELECT ID FROM ORGANIZATION WHERE id = 1", + "SELECT\n" + + " \"ID\"\n" + + "FROM \"PUBLIC\".\"ORGANIZATION\"\n" + + " /* PUBLIC.PRIMARY_KEY_D: ID = 1 */\n" + + "WHERE \"ID\" = 1"); + + checkExplain(stat, "EXPLAIN SELECT * FROM PERSON WHERE id = ?", + "SELECT\n" + + " \"PUBLIC\".\"PERSON\".\"ID\",\n" + + " \"PUBLIC\".\"PERSON\".\"ORGID\",\n" + + " \"PUBLIC\".\"PERSON\".\"NAME\",\n" + + " \"PUBLIC\".\"PERSON\".\"SALARY\"\n" + + "FROM \"PUBLIC\".\"PERSON\"\n" + + " /* PUBLIC.PRIMARY_KEY_8: ID = ?1 */\n" + + "WHERE \"ID\" = ?1"); + + checkExplain(stat, "EXPLAIN SELECT * FROM PERSON WHERE id = 50", + "SELECT\n" + + " \"PUBLIC\".\"PERSON\".\"ID\",\n" + + " \"PUBLIC\".\"PERSON\".\"ORGID\",\n" + + " \"PUBLIC\".\"PERSON\".\"NAME\",\n" + + " \"PUBLIC\".\"PERSON\".\"SALARY\"\n" + + "FROM \"PUBLIC\".\"PERSON\"\n" + + " /* PUBLIC.PRIMARY_KEY_8: ID = 50 */\n" + + "WHERE \"ID\" = 50"); + + checkExplain(stat, "EXPLAIN SELECT * FROM PERSON WHERE salary > ? and salary < ?", + "SELECT\n" + + " \"PUBLIC\".\"PERSON\".\"ID\",\n" + + " \"PUBLIC\".\"PERSON\".\"ORGID\",\n" + + " \"PUBLIC\".\"PERSON\".\"NAME\",\n" + + " \"PUBLIC\".\"PERSON\".\"SALARY\"\n" + + "FROM \"PUBLIC\".\"PERSON\"\n" + + " /* PUBLIC.PERSON.tableScan */\n" + + "WHERE (\"SALARY\" > ?1)\n" + + " AND (\"SALARY\" < ?2)"); + + checkExplain(stat, "EXPLAIN SELECT * FROM PERSON WHERE salary > 1000 and salary < 2000", + "SELECT\n" + + " \"PUBLIC\".\"PERSON\".\"ID\",\n" + + " \"PUBLIC\".\"PERSON\".\"ORGID\",\n" + + " \"PUBLIC\".\"PERSON\".\"NAME\",\n" + + " \"PUBLIC\".\"PERSON\".\"SALARY\"\n" + + "FROM \"PUBLIC\".\"PERSON\"\n" + + " /* PUBLIC.PERSON.tableScan */\n" + + "WHERE (\"SALARY\" > 1000)\n" + + " AND (\"SALARY\" < 2000)"); + + checkExplain(stat, "EXPLAIN SELECT * FROM PERSON WHERE name = lower(?)", + "SELECT\n" + + " \"PUBLIC\".\"PERSON\".\"ID\",\n" + + " \"PUBLIC\".\"PERSON\".\"ORGID\",\n" + + " \"PUBLIC\".\"PERSON\".\"NAME\",\n" + + " \"PUBLIC\".\"PERSON\".\"SALARY\"\n" + + "FROM \"PUBLIC\".\"PERSON\"\n" + + " /* PUBLIC.PERSON.tableScan */\n" + + "WHERE \"NAME\" = LOWER(?1)"); + + checkExplain(stat, "EXPLAIN SELECT * FROM PERSON WHERE name = lower('Smith')", + "SELECT\n" + + " \"PUBLIC\".\"PERSON\".\"ID\",\n" + + " \"PUBLIC\".\"PERSON\".\"ORGID\",\n" + + " \"PUBLIC\".\"PERSON\".\"NAME\",\n" + + " \"PUBLIC\".\"PERSON\".\"SALARY\"\n" + + "FROM \"PUBLIC\".\"PERSON\"\n" + + " /* PUBLIC.PERSON.tableScan */\n" + + "WHERE \"NAME\" = 'smith'"); + + checkExplain(stat, "EXPLAIN SELECT * FROM PERSON p " + + "INNER JOIN ORGANIZATION o ON p.id = o.id WHERE o.id = ? AND p.salary > ?", + "SELECT\n" + + " \"P\".\"ID\",\n" + + " \"P\".\"ORGID\",\n" + + " \"P\".\"NAME\",\n" + + " \"P\".\"SALARY\",\n" + + " \"O\".\"ID\",\n" + + " \"O\".\"NAME\"\n" + + "FROM \"PUBLIC\".\"ORGANIZATION\" \"O\"\n" + + " /* PUBLIC.PRIMARY_KEY_D: ID = ?1 */\n" + + " /* WHERE O.ID = ?1\n" + + " */\n" + + "INNER JOIN \"PUBLIC\".\"PERSON\" \"P\"\n" + + " /* PUBLIC.PRIMARY_KEY_8: ID = O.ID */\n" + + " ON 1=1\n" + + "WHERE (\"P\".\"ID\" = \"O\".\"ID\")\n" + + " AND (\"O\".\"ID\" = ?1)\n" + + " AND (\"P\".\"SALARY\" > ?2)"); + + checkExplain(stat, "EXPLAIN SELECT * FROM PERSON p " + + "INNER JOIN ORGANIZATION o ON p.id = o.id WHERE o.id = 10 AND p.salary > 1000", + "SELECT\n" + + " \"P\".\"ID\",\n" + + " \"P\".\"ORGID\",\n" + + " \"P\".\"NAME\",\n" + + " \"P\".\"SALARY\",\n" + + " \"O\".\"ID\",\n" + + " \"O\".\"NAME\"\n" + + "FROM \"PUBLIC\".\"ORGANIZATION\" \"O\"\n" + + " /* PUBLIC.PRIMARY_KEY_D: ID = 10 */\n" + + " /* WHERE O.ID = 10\n" + + " */\n" + + "INNER JOIN \"PUBLIC\".\"PERSON\" \"P\"\n" + + " /* PUBLIC.PRIMARY_KEY_8: ID = O.ID */\n" + + " ON 1=1\n" + + "WHERE (\"P\".\"ID\" = \"O\".\"ID\")\n" + + " AND (\"O\".\"ID\" = 10)\n" + + " AND (\"P\".\"SALARY\" > 1000)"); + + PreparedStatement pStat = conn.prepareStatement( + "/* bla-bla */ EXPLAIN SELECT ID FROM ORGANIZATION WHERE id = ?"); + + ResultSet rs = pStat.executeQuery(); + + assertTrue(rs.next()); + + assertEquals("SELECT\n" + + " \"ID\"\n" + + "FROM \"PUBLIC\".\"ORGANIZATION\"\n" + + " /* PUBLIC.PRIMARY_KEY_D: ID = ?1 */\n" + + "WHERE \"ID\" = ?1", + rs.getString(1)); + + conn.close(); + } + + private void testExplainAnalyze() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + + stat.execute("CREATE TABLE ORGANIZATION" + + "(id int primary key, name varchar(100))"); + stat.execute("CREATE TABLE PERSON" + + "(id int primary key, orgId int, name varchar(100), salary int)"); + + stat.execute("INSERT INTO ORGANIZATION VALUES(1, 'org1')"); + stat.execute("INSERT INTO ORGANIZATION VALUES(2, 'org2')"); + + stat.execute("INSERT INTO PERSON VALUES(1, 1, 'person1', 1000)"); + stat.execute("INSERT INTO PERSON VALUES(2, 1, 'person2', 2000)"); + stat.execute("INSERT INTO PERSON VALUES(3, 2, 'person3', 3000)"); + stat.execute("INSERT INTO PERSON VALUES(4, 2, 'person4', 4000)"); + + assertThrows(ErrorCode.PARAMETER_NOT_SET_1, stat, + "/* bla-bla */ EXPLAIN ANALYZE SELECT ID FROM ORGANIZATION WHERE id = ?"); + + PreparedStatement pStat = conn.prepareStatement( + "/* bla-bla */ EXPLAIN ANALYZE SELECT ID FROM ORGANIZATION WHERE id = ?"); + + assertThrows(ErrorCode.PARAMETER_NOT_SET_1, pStat).executeQuery(); + + pStat.setInt(1, 1); + + ResultSet rs = pStat.executeQuery(); + + assertTrue(rs.next()); + + assertEquals("SELECT\n" + + " \"ID\"\n" + + "FROM \"PUBLIC\".\"ORGANIZATION\"\n" + + " /* PUBLIC.PRIMARY_KEY_D: ID = ?1 */\n" + + " /* scanCount: 2 */\n" + + "WHERE \"ID\" = ?1", + rs.getString(1)); + + pStat = conn.prepareStatement("EXPLAIN ANALYZE SELECT * FROM PERSON p " + + "INNER JOIN ORGANIZATION o ON o.id = p.id WHERE o.id = ?"); + + assertThrows(ErrorCode.PARAMETER_NOT_SET_1, pStat).executeQuery(); + + pStat.setInt(1, 1); + + rs = pStat.executeQuery(); + + assertTrue(rs.next()); + + assertEquals("SELECT\n" + + " \"P\".\"ID\",\n" + + " \"P\".\"ORGID\",\n" + + " \"P\".\"NAME\",\n" + + " \"P\".\"SALARY\",\n" + + " \"O\".\"ID\",\n" + + " \"O\".\"NAME\"\n" + + "FROM \"PUBLIC\".\"PERSON\" \"P\"\n" + + " /* PUBLIC.PRIMARY_KEY_8: ID = ?1 */\n" + + " /* scanCount: 2 */\n" + + "INNER JOIN \"PUBLIC\".\"ORGANIZATION\" \"O\"\n" + + " /* PUBLIC.PRIMARY_KEY_D: ID = ?1\n" + + " AND ID = P.ID\n" + + " */\n" + + " ON 1=1\n" + + " /* scanCount: 2 */\n" + + "WHERE (\"O\".\"ID\" = ?1)\n" + + " AND (\"O\".\"ID\" = \"P\".\"ID\")", + rs.getString(1)); + + conn.close(); + } + + private void testAlterTableReconnect() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id identity);"); + stat.execute("insert into test values(1);"); + assertThrows(ErrorCode.NULL_NOT_ALLOWED, stat). + execute("alter table test add column name varchar not null;"); + conn.close(); + conn = getConnection("cases"); + ResultSet rs = conn.createStatement().executeQuery( + "SELECT * FROM TEST"); + rs.next(); + assertEquals("1", rs.getString(1)); + assertFalse(rs.next()); + stat = conn.createStatement(); + stat.execute("drop table test"); + stat.execute("create table test(id identity)"); + stat.execute("insert into test values(1)"); + stat.execute("alter table test alter column id set default 'x'"); + conn.close(); + conn = getConnection("cases"); + stat = conn.createStatement(); + rs = conn.createStatement().executeQuery("SELECT * FROM TEST"); + rs.next(); + assertEquals("1", rs.getString(1)); + assertFalse(rs.next()); + stat.execute("drop table test"); + stat.execute("create table test(id identity)"); + stat.execute("insert into test values(1)"); + assertThrows(ErrorCode.DATA_CONVERSION_ERROR_1, stat). + execute("alter table test alter column id date"); + conn.close(); + conn = getConnection("cases"); + rs = conn.createStatement().executeQuery("SELECT * FROM TEST"); + rs.next(); + assertEquals("1", rs.getString(1)); + assertFalse(rs.next()); + conn.close(); + } + + private void testCollation() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("SET COLLATION ENGLISH STRENGTH PRIMARY"); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, " + + "NAME VARCHAR(255))"); + stat.execute("INSERT INTO TEST VALUES(1, 'Hello'), " + + "(2, 'World'), (3, 'WORLD'), (4, 'HELLO')"); + stat.execute("create index idxname on test(name)"); + ResultSet rs; + rs = stat.executeQuery("select name from test order by name"); + rs.next(); + assertEquals("Hello", rs.getString(1)); + rs.next(); + assertEquals("HELLO", rs.getString(1)); + rs.next(); + assertEquals("World", rs.getString(1)); + rs.next(); + assertEquals("WORLD", rs.getString(1)); + rs = stat.executeQuery("select name from test where name like 'He%'"); + rs.next(); + assertEquals("Hello", rs.getString(1)); + rs.next(); + assertEquals("HELLO", rs.getString(1)); + conn.close(); + } + + private void testPersistentSettings() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("SET COLLATION de_DE"); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, " + + "NAME VARCHAR)"); + stat.execute("CREATE INDEX IDXNAME ON TEST(NAME)"); + // \u00f6 = oe + stat.execute("INSERT INTO TEST VALUES(1, 'B\u00f6hlen'), " + + "(2, 'Bach'), (3, 'Bucher')"); + conn.close(); + conn = getConnection("cases"); + ResultSet rs = conn.createStatement().executeQuery( + "SELECT NAME FROM TEST ORDER BY NAME"); + rs.next(); + assertEquals("Bach", rs.getString(1)); + rs.next(); + assertEquals("B\u00f6hlen", rs.getString(1)); + rs.next(); + assertEquals("Bucher", rs.getString(1)); + conn.close(); + } + + private void testInsertSelectUnion() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ORDER_ID INT PRIMARY KEY, " + + "ORDER_DATE DATETIME, " + + "USER_ID INT, DESCRIPTION VARCHAR, STATE VARCHAR, " + + "TRACKING_ID VARCHAR)"); + Timestamp orderDate = Timestamp.valueOf("2005-05-21 17:46:00"); + String sql = "insert into TEST (ORDER_ID,ORDER_DATE," + + "USER_ID,DESCRIPTION,STATE,TRACKING_ID) " + + "select cast(? as int),cast(? as date),cast(? as int),cast(? as varchar)," + + "cast(? as varchar),cast(? as varchar) union all select ?,?,?,?,?,?"; + PreparedStatement ps = conn.prepareStatement(sql); + ps.setInt(1, 5555); + ps.setTimestamp(2, orderDate); + ps.setInt(3, 2222); + ps.setString(4, "test desc"); + ps.setString(5, "test_state"); + ps.setString(6, "testid"); + ps.setInt(7, 5556); + ps.setTimestamp(8, orderDate); + ps.setInt(9, 2222); + ps.setString(10, "test desc"); + ps.setString(11, "test_state"); + ps.setString(12, "testid"); + assertEquals(2, ps.executeUpdate()); + ps.close(); + conn.close(); + } + + private void testViewReconnect() throws SQLException { + trace("testViewReconnect"); + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int)"); + stat.execute("create view abc as select * from test"); + stat.execute("drop table test cascade"); + conn.close(); + conn = getConnection("cases"); + stat = conn.createStatement(); + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1, stat). + execute("select * from abc"); + conn.close(); + } + + private void testDefaultQueryReconnect() throws SQLException { + trace("testDefaultQueryReconnect"); + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("create table parent(id int)"); + stat.execute("insert into parent values(1)"); + stat.execute("create table test(id int default " + + "(select max(id) from parent), name varchar)"); + + conn.close(); + conn = getConnection("cases"); + stat = conn.createStatement(); + conn.setAutoCommit(false); + stat.execute("insert into parent values(2)"); + stat.execute("insert into test(name) values('test')"); + ResultSet rs = stat.executeQuery("select * from test"); + rs.next(); + assertEquals(2, rs.getInt(1)); + assertFalse(rs.next()); + conn.close(); + } + + private void testBigString() throws SQLException { + trace("testBigString"); + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST(ID INT, TEXT VARCHAR, TEXT_C CLOB)"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST VALUES(?, ?, ?)"); + int len = getSize(1000, 66000); + char[] buff = new char[len]; + + // Unicode problem: + // The UCS code values 0xd800-0xdfff (UTF-16 surrogates) + // as well as 0xfffe and 0xffff (UCS non-characters) + // should not appear in conforming UTF-8 streams. + // (String.getBytes("UTF-8") only returns 1 byte for 0xd800-0xdfff) + Random random = new Random(); + random.setSeed(1); + for (int i = 0; i < len; i++) { + char c; + do { + c = (char) random.nextInt(); + } while (c >= 0xd800 && c <= 0xdfff); + buff[i] = c; + } + String big = new String(buff); + prep.setInt(1, 1); + prep.setString(2, big); + prep.setString(3, big); + prep.execute(); + prep.setInt(1, 2); + prep.setCharacterStream(2, new StringReader(big), 0); + prep.setCharacterStream(3, new StringReader(big), 0); + prep.execute(); + ResultSet rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals(big, rs.getString(2)); + assertEquals(big, readString(rs.getCharacterStream(2))); + assertEquals(big, rs.getString(3)); + assertEquals(big, readString(rs.getCharacterStream(3))); + rs.next(); + assertEquals(2, rs.getInt(1)); + assertEquals(big, rs.getString(2)); + assertEquals(big, readString(rs.getCharacterStream(2))); + assertEquals(big, rs.getString(3)); + assertEquals(big, readString(rs.getCharacterStream(3))); + rs.next(); + assertFalse(rs.next()); + conn.close(); + } + + private void testConstraintReconnect() throws SQLException { + trace("testConstraintReconnect"); + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("drop table if exists parent"); + stat.execute("drop table if exists child"); + stat.execute("create table parent(id int primary key)"); + stat.execute("create table child(c_id int, p_id int, " + + "foreign key(p_id) references parent(id))"); + stat.execute("insert into parent values(1), (2)"); + stat.execute("insert into child values(1, 1)"); + stat.execute("insert into child values(2, 2)"); + stat.execute("insert into child values(3, 2)"); + stat.execute("delete from child"); + conn.close(); + conn = getConnection("cases"); + conn.close(); + } + + private void testDoubleRecovery() throws SQLException { + if (config.networked || config.googleAppEngine) { + return; + } + trace("testDoubleRecovery"); + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("SET WRITE_DELAY 0"); + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST" + + "(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + stat.execute("INSERT INTO TEST VALUES(1, 'Hello')"); + conn.setAutoCommit(false); + stat.execute("INSERT INTO TEST VALUES(2, 'World')"); + crash(conn); + + conn = getConnection("cases"); + stat = conn.createStatement(); + stat.execute("SET WRITE_DELAY 0"); + stat.execute("INSERT INTO TEST VALUES(3, 'Break')"); + crash(conn); + + conn = getConnection("cases"); + stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + rs.next(); + assertEquals(3, rs.getInt(1)); + assertEquals("Break", rs.getString(2)); + conn.close(); + } + + private void testRenameReconnect() throws SQLException { + trace("testRenameReconnect"); + deleteDb("cases"); + Connection conn = getConnection("cases"); + conn.createStatement().execute("CREATE TABLE TEST_SEQ" + + "(ID INT GENERATED BY DEFAULT AS IDENTITY, NAME VARCHAR(255))"); + conn.createStatement().execute("CREATE TABLE TEST" + + "(ID INT PRIMARY KEY)"); + conn.createStatement().execute("ALTER TABLE TEST RENAME TO TEST2"); + conn.createStatement().execute("CREATE TABLE TEST_B" + + "(ID INT PRIMARY KEY, NAME VARCHAR, UNIQUE(NAME))"); + conn.close(); + conn = getConnection("cases"); + ResultSet rs = conn.createStatement().executeQuery( + "SELECT ID FROM FINAL TABLE(INSERT INTO TEST_SEQ(NAME) VALUES('Hi'))"); + rs.next(); + assertEquals(1, rs.getInt(1)); + conn.createStatement().execute("SELECT * FROM TEST2"); + conn.createStatement().execute("SELECT * FROM TEST_B"); + conn.createStatement().execute("ALTER TABLE TEST_B RENAME TO TEST_B2"); + conn.close(); + conn = getConnection("cases"); + conn.createStatement().execute("SELECT * FROM TEST_B2"); + rs = conn.createStatement().executeQuery( + "SELECT ID FROM FINAL TABLE(INSERT INTO TEST_SEQ(NAME) VALUES('World'))"); + rs.next(); + assertEquals(2, rs.getInt(1)); + conn.close(); + } + + private void testSelectForUpdate() throws SQLException { + trace("testSelectForUpdate"); + deleteDb("cases"); + Connection conn1 = getConnection("cases"); + Statement stat1 = conn1.createStatement(); + stat1.execute("CREATE TABLE TEST(ID INT)"); + stat1.execute("INSERT INTO TEST VALUES(1)"); + conn1.setAutoCommit(false); + stat1.execute("SELECT * FROM TEST FOR UPDATE"); + Connection conn2 = getConnection("cases"); + Statement stat2 = conn2.createStatement(); + assertThrows(ErrorCode.LOCK_TIMEOUT_1, stat2). + execute("UPDATE TEST SET ID=2"); + conn1.commit(); + stat2.execute("UPDATE TEST SET ID=2"); + conn1.close(); + conn2.close(); + } + + private void testMutableObjects() throws SQLException { + trace("testMutableObjects"); + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT, D DATE, T TIME, TS TIMESTAMP)"); + stat.execute("INSERT INTO TEST VALUES(1, '2001-01-01', " + + "'20:00:00', '2002-02-02 22:22:22.2')"); + stat.execute("INSERT INTO TEST VALUES(1, '2001-01-01', " + + "'20:00:00', '2002-02-02 22:22:22.2')"); + ResultSet rs = stat.executeQuery("SELECT * FROM TEST"); + rs.next(); + Date d1 = rs.getDate("D"); + Time t1 = rs.getTime("T"); + Timestamp ts1 = rs.getTimestamp("TS"); + rs.next(); + Date d2 = rs.getDate("D"); + Time t2 = rs.getTime("T"); + Timestamp ts2 = rs.getTimestamp("TS"); + assertTrue(ts1 != ts2); + assertTrue(d1 != d2); + assertTrue(t1 != t2); + assertTrue(t2 != rs.getObject("T")); + assertTrue(d2 != rs.getObject("D")); + assertTrue(ts2 != rs.getObject("TS")); + assertFalse(rs.next()); + conn.close(); + } + + private void testCreateDrop() throws SQLException { + trace("testCreateDrop"); + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("create table employee(id int, firstName VARCHAR(50), " + + "salary decimal(10, 2), " + + "superior_id int, CONSTRAINT PK_employee PRIMARY KEY (id), " + + "CONSTRAINT FK_superior FOREIGN KEY (superior_id) " + + "REFERENCES employee(ID))"); + stat.execute("DROP TABLE employee"); + conn.close(); + conn = getConnection("cases"); + conn.close(); + } + + private void testPolePos() throws SQLException { + trace("testPolePos"); + // poleposition-0.20 + + Connection c0 = getConnection("cases"); + c0.createStatement().executeUpdate("SET AUTOCOMMIT FALSE"); + c0.createStatement().executeUpdate( + "create table australia (ID INTEGER NOT NULL, " + + "Name VARCHAR(100), firstName VARCHAR(100), " + + "Points INTEGER, LicenseID INTEGER, PRIMARY KEY(ID))"); + c0.createStatement().executeUpdate("COMMIT"); + c0.close(); + + c0 = getConnection("cases"); + c0.createStatement().executeUpdate("SET AUTOCOMMIT FALSE"); + PreparedStatement p15 = c0.prepareStatement("insert into australia" + + "(id, Name, firstName, Points, LicenseID) values (?, ?, ?, ?, ?)"); + int len = getSize(1, 1000); + for (int i = 0; i < len; i++) { + p15.setInt(1, i); + p15.setString(2, "Pilot_" + i); + p15.setString(3, "Herkules"); + p15.setInt(4, i); + p15.setInt(5, i); + p15.executeUpdate(); + } + c0.createStatement().executeUpdate("COMMIT"); + c0.close(); + + // c0=getConnection("cases"); + // c0.createStatement().executeUpdate("SET AUTOCOMMIT FALSE"); + // c0.createStatement().executeQuery("select * from australia"); + // c0.createStatement().executeQuery("select * from australia"); + // c0.close(); + + // c0=getConnection("cases"); + // c0.createStatement().executeUpdate("SET AUTOCOMMIT FALSE"); + // c0.createStatement().executeUpdate("COMMIT"); + // c0.createStatement().executeUpdate("delete from australia"); + // c0.createStatement().executeUpdate("COMMIT"); + // c0.close(); + + c0 = getConnection("cases"); + c0.createStatement().executeUpdate("SET AUTOCOMMIT FALSE"); + c0.createStatement().executeUpdate("drop table australia"); + c0.createStatement().executeUpdate("create table australia " + + "(ID INTEGER NOT NULL, Name VARCHAR(100), " + + "firstName VARCHAR(100), Points INTEGER, " + + "LicenseID INTEGER, PRIMARY KEY(ID))"); + c0.createStatement().executeUpdate("COMMIT"); + c0.close(); + + c0 = getConnection("cases"); + c0.createStatement().executeUpdate("SET AUTOCOMMIT FALSE"); + PreparedStatement p65 = c0.prepareStatement( + "insert into australia" + + "(id, Name, FirstName, Points, LicenseID) values (?, ?, ?, ?, ?)"); + len = getSize(1, 1000); + for (int i = 0; i < len; i++) { + p65.setInt(1, i); + p65.setString(2, "Pilot_" + i); + p65.setString(3, "Herkules"); + p65.setInt(4, i); + p65.setInt(5, i); + p65.executeUpdate(); + } + c0.createStatement().executeUpdate("COMMIT"); + c0.createStatement().executeUpdate("COMMIT"); + c0.createStatement().executeUpdate("COMMIT"); + c0.close(); + + c0 = getConnection("cases"); + c0.close(); + } + + private void testQuick() throws SQLException { + trace("testQuick"); + deleteDb("cases"); + + Connection c0 = getConnection("cases"); + c0.createStatement().executeUpdate( + "create table test (ID int PRIMARY KEY)"); + c0.createStatement().executeUpdate("insert into test values(1)"); + c0.createStatement().executeUpdate("drop table test"); + c0.createStatement().executeUpdate( + "create table test (ID int PRIMARY KEY)"); + c0.close(); + + c0 = getConnection("cases"); + c0.createStatement().executeUpdate("insert into test values(1)"); + c0.close(); + + c0 = getConnection("cases"); + c0.close(); + } + + private void testOrderByWithSubselect() throws SQLException { + deleteDb("cases"); + + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("create table master" + + "(id number primary key, name varchar2(30));"); + stat.execute("create table detail" + + "(id number references master(id), location varchar2(30));"); + + stat.execute("Insert into master values(1,'a'), (2,'b'), (3,'c');"); + stat.execute("Insert into detail values(1,'a'), (2,'b'), (3,'c');"); + + ResultSet rs = stat.executeQuery( + "select master.id, master.name " + + "from master " + + "where master.id in (select detail.id from detail) " + + "order by master.id"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertTrue(rs.next()); + assertEquals(3, rs.getInt(1)); + + conn.close(); + } + + private void testDeleteAndDropTableWithLobs(boolean useDrop) + throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(id int, content BLOB)"); + stat.execute("set MAX_LENGTH_INPLACE_LOB 1"); + + PreparedStatement prepared = conn.prepareStatement( + "INSERT INTO TEST VALUES(?, ?)"); + byte[] blobContent = "BLOB_CONTENT".getBytes(); + prepared.setInt(1, 1); + prepared.setBytes(2, blobContent); + prepared.execute(); + + if (useDrop) { + stat.execute("DROP TABLE TEST"); + } else { + stat.execute("DELETE FROM TEST"); + } + + conn.close(); + + List list = FileUtils.newDirectoryStream(getBaseDir() + + "/cases.lobs.db"); + assertEquals("Lob file was not deleted: " + list, 0, list.size()); + } + + private void testMinimalCoveringIndexPlan() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + + stat.execute("create table t(a int, b int, c int)"); + stat.execute("create index a_idx on t(a)"); + stat.execute("create index b_idx on t(b)"); + stat.execute("create index ab_idx on t(a, b)"); + stat.execute("create index abc_idx on t(a, b, c)"); + + ResultSet rs; + String plan; + + rs = stat.executeQuery("explain select a from t"); + assertTrue(rs.next()); + plan = rs.getString(1); + assertContains(plan, "/* PUBLIC.A_IDX */"); + rs.close(); + + rs = stat.executeQuery("explain select b from t"); + assertTrue(rs.next()); + plan = rs.getString(1); + assertContains(plan, "/* PUBLIC.B_IDX */"); + rs.close(); + + rs = stat.executeQuery("explain select b, a from t"); + assertTrue(rs.next()); + plan = rs.getString(1); + assertContains(plan, "/* PUBLIC.AB_IDX */"); + rs.close(); + + rs = stat.executeQuery("explain select b, a, c from t"); + assertTrue(rs.next()); + plan = rs.getString(1); + assertContains(plan, "/* PUBLIC.ABC_IDX */"); + rs.close(); + + conn.close(); + } + + private void testMinMaxDirectLookupIndex() throws SQLException { + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + + stat.execute("create table t(a int, b int)"); + stat.execute("create index b_idx on t(b desc)"); + stat.execute("create index ab_idx on t(a, b)"); + + final int count = 100; + + PreparedStatement p = conn.prepareStatement("insert into t values (?,?)"); + for (int i = 0; i <= count; i++) { + p.setInt(1, i); + p.setInt(2, count - i); + assertEquals(1, p.executeUpdate()); + } + p.close(); + + ResultSet rs; + String plan; + + rs = stat.executeQuery("select max(b) from t"); + assertTrue(rs.next()); + assertEquals(count, rs.getInt(1)); + rs.close(); + + rs = stat.executeQuery("explain select max(b) from t"); + assertTrue(rs.next()); + plan = rs.getString(1); + assertContains(plan, "/* PUBLIC.B_IDX */"); + assertContains(plan, "/* direct lookup */"); + rs.close(); + + rs = stat.executeQuery("select min(b) from t"); + assertTrue(rs.next()); + assertEquals(0, rs.getInt(1)); + rs.close(); + + rs = stat.executeQuery("explain select min(b) from t"); + assertTrue(rs.next()); + plan = rs.getString(1); + assertContains(plan, "/* PUBLIC.B_IDX */"); + assertContains(plan, "/* direct lookup */"); + rs.close(); + + conn.close(); + } + + /** Tests fix for bug #682: Queries with 'like' expressions may filter rows incorrectly */ + private void testLikeExpressions() throws SQLException { + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("select * from (select 'fo%' a union all select '%oo') where 'foo' like a"); + assertTrue(rs.next()); + assertEquals("fo%", rs.getString(1)); + assertTrue(rs.next()); + assertEquals("%oo", rs.getString(1)); + conn.close(); + } + + private void testDataChangeDeltaTable() throws SQLException { + /* + * This test case didn't reproduce the issue in the TestScript. + * + * The same UPDATE is necessary before and after usage of a data change + * delta table. + */ + String updateCommand = "UPDATE TEST SET V = 3 WHERE ID = 1"; + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT, V INT)"); + assertEquals(0, stat.executeUpdate(updateCommand)); + ResultSet rs = stat.executeQuery("SELECT V FROM FINAL TABLE (INSERT INTO TEST VALUES (1, 1))"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals(1, stat.executeUpdate(updateCommand)); + rs = stat.executeQuery("SELECT V FROM TEST"); + assertTrue(rs.next()); + assertEquals(3, rs.getInt(1)); + conn.close(); + } + + private void testGroupSortedReset() throws SQLException { + // This test case didn't reproduce the issue in the TestScript. + deleteDb("cases"); + Connection conn = getConnection("cases"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE T1(A INT PRIMARY KEY, B INT) AS VALUES (1, 4), (2, 5), (3, 6)"); + String sql = "SELECT B FROM T1 LEFT JOIN (VALUES 2) T2(A) USING(A) WHERE T2.A = 2 GROUP BY T1.A"; + stat.execute(sql); + stat.execute("UPDATE T1 SET B = 7 WHERE A = 3"); + stat.execute(sql); + conn.close(); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestCheckpoint.java b/h2/src/test/org/h2/test/db/TestCheckpoint.java new file mode 100644 index 0000000..6cfc1e7 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestCheckpoint.java @@ -0,0 +1,58 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests the CHECKPOINT SQL statement. + */ +public class TestCheckpoint extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + // TODO test checkpoint with rollback, not only just run the command + deleteDb("checkpoint"); + Connection c0 = getConnection("checkpoint"); + Statement s0 = c0.createStatement(); + Connection c1 = getConnection("checkpoint"); + Statement s1 = c1.createStatement(); + s1.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + s1.execute("INSERT INTO TEST VALUES(1, 'Hello')"); + s0.execute("CHECKPOINT"); + + s1.execute("INSERT INTO TEST VALUES(2, 'World')"); + c1.setAutoCommit(false); + s1.execute("INSERT INTO TEST VALUES(3, 'Maybe')"); + s0.execute("CHECKPOINT"); + + s1.execute("INSERT INTO TEST VALUES(4, 'Or not')"); + s0.execute("CHECKPOINT"); + + s1.execute("INSERT INTO TEST VALUES(5, 'ok yes')"); + s1.execute("COMMIT"); + s0.execute("CHECKPOINT"); + + c0.close(); + c1.close(); + deleteDb("checkpoint"); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestCluster.java b/h2/src/test/org/h2/test/db/TestCluster.java new file mode 100644 index 0000000..6884892 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestCluster.java @@ -0,0 +1,518 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Properties; + +import org.h2.api.ErrorCode; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.CreateCluster; +import org.h2.tools.DeleteDbFiles; +import org.h2.tools.Server; +import org.h2.util.JdbcUtils; + +/** + * Test the cluster feature. + */ +public class TestCluster extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + if (config.memory || config.networked || config.cipher != null) { + return false; + } + return true; + } + + @Override + public void test() throws Exception { + testClob(); + testRecover(); + testRollback(); + testCase(); + testClientInfo(); + testCreateClusterAtRuntime(); + testStartStopCluster(); + } + + private void testClob() throws SQLException { + deleteFiles(); + + org.h2.Driver.load(); + String user = getUser(), password = getPassword(); + Connection conn; + Statement stat; + + Server n1 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node1").start(); + int port1 = n1.getPort(); + String url1 = getURL("jdbc:h2:tcp://localhost:" + port1 + "/test", false); + + conn = getConnection(url1, user, password); + stat = conn.createStatement(); + stat.execute("create table t1(id int, name clob)"); + stat.execute("insert into t1 values(1, repeat('Hello', 50))"); + conn.close(); + + Server n2 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node2").start(); + int port2 = n2.getPort(); + String url2 = getURL("jdbc:h2:tcp://localhost:" + port2 + "/test", false); + + String serverList = "localhost:" + port1 + ",localhost:" + port2; + String urlCluster = getURL("jdbc:h2:tcp://" + serverList + "/test", true); + CreateCluster.main("-urlSource", url1, "-urlTarget", url2, + "-user", user, "-password", password, "-serverList", + serverList); + + conn = getConnection(urlCluster, user, password); + conn.close(); + + n1.stop(); + n2.stop(); + deleteFiles(); + } + + private void testRecover() throws SQLException { + deleteFiles(); + + org.h2.Driver.load(); + String user = getUser(), password = getPassword(); + Connection conn; + Statement stat; + ResultSet rs; + + + Server server1 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node1") + .start(); + int port1 = server1.getPort(); + Server server2 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node2") + .start(); + int port2 = server2.getPort(); + + String url1 = getURL("jdbc:h2:tcp://localhost:" + port1 + "/test", true); + String url2 = getURL("jdbc:h2:tcp://localhost:" + port2 + "/test", true); + String serverList = "localhost:" + port1 + ",localhost:" + port2; + String urlCluster = getURL("jdbc:h2:tcp://" + serverList + "/test", true); + + CreateCluster.main("-urlSource", url1, "-urlTarget", url2, + "-user", user, "-password", password, "-serverList", + serverList); + + conn = getConnection(urlCluster, user, password); + stat = conn.createStatement(); + stat.execute("create table t1(id int, name varchar(30))"); + stat.execute("insert into t1 values(1, 'a'), (2, 'b'), (3, 'c')"); + rs = stat.executeQuery("select count(*) from t1"); + rs.next(); + assertEquals(3, rs.getInt(1)); + + server2.stop(); + DeleteDbFiles.main("-dir", getBaseDir() + "/node2", "-quiet"); + + stat.execute("insert into t1 values(4, 'd'), (5, 'e')"); + rs = stat.executeQuery("select count(*) from t1"); + rs.next(); + assertEquals(5, rs.getInt(1)); + + server2 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-tcpPort", + "" + port2 , "-baseDir", getBaseDir() + "/node2").start(); + CreateCluster.main("-urlSource", url1, "-urlTarget", url2, + "-user", user, "-password", password, "-serverList", + serverList); + + conn.close(); + + conn = getConnection(urlCluster, user, password); + stat = conn.createStatement(); + rs = stat.executeQuery("select count(*) from t1"); + rs.next(); + assertEquals(5, rs.getInt(1)); + conn.close(); + + server1.stop(); + server2.stop(); + deleteFiles(); + } + + private void testRollback() throws SQLException { + deleteFiles(); + + org.h2.Driver.load(); + String user = getUser(), password = getPassword(); + Connection conn; + Statement stat; + ResultSet rs; + + Server n1 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node1").start(); + int port1 = n1.getPort(); + Server n2 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node2").start(); + int port2 = n2.getPort(); + + String url1 = getURL("jdbc:h2:tcp://localhost:" + port1 + "/test", true); + String url2 = getURL("jdbc:h2:tcp://localhost:" + port2 + "/test", true); + String serverList = "localhost:" + port1 + ",localhost:" + port2; + String urlCluster = getURL("jdbc:h2:tcp://" + serverList + "/test", true); + + CreateCluster.main("-urlSource", url1, "-urlTarget", url2, + "-user", user, "-password", password, "-serverList", + serverList); + + conn = getConnection(urlCluster, user, password); + stat = conn.createStatement(); + assertTrue(conn.getAutoCommit()); + stat.execute("create table test(id int, name varchar)"); + assertTrue(conn.getAutoCommit()); + stat.execute("set autocommit false"); + // issue 259 + // assertFalse(conn.getAutoCommit()); + conn.setAutoCommit(false); + assertFalse(conn.getAutoCommit()); + stat.execute("insert into test values(1, 'Hello')"); + stat.execute("rollback"); + rs = stat.executeQuery("select * from test order by id"); + assertFalse(rs.next()); + conn.close(); + + n1.stop(); + n2.stop(); + deleteFiles(); + } + + private void testCase() throws SQLException { + deleteFiles(); + + org.h2.Driver.load(); + String user = getUser(), password = getPassword(); + Connection conn; + Statement stat; + ResultSet rs; + + + Server n1 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node1").start(); + int port1 = n1.getPort(); + Server n2 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node2").start(); + int port2 = n2.getPort(); + String serverList = "localhost:" + port1 + ",localhost:" + port2; + String url1 = getURL("jdbc:h2:tcp://localhost:" + port1 + "/test", true); + String url2 = getURL("jdbc:h2:tcp://localhost:" + port2 + "/test", true); + String urlCluster = getURL("jdbc:h2:tcp://" + serverList + "/test", true); + + CreateCluster.main("-urlSource", url1, "-urlTarget", url2, + "-user", user, "-password", password, "-serverList", + serverList); + + conn = getConnection(urlCluster, user, password); + stat = conn.createStatement(); + assertTrue(conn.getAutoCommit()); + stat.execute("create table test(name int)"); + assertTrue(conn.getAutoCommit()); + stat.execute("insert into test values(1)"); + conn.setAutoCommit(false); + assertFalse(conn.getAutoCommit()); + stat.execute("insert into test values(2)"); + assertFalse(conn.getAutoCommit()); + conn.rollback(); + rs = stat.executeQuery("select * from test order by name"); + assertTrue(rs.next()); + assertFalse(rs.next()); + conn.close(); + + // stop server 2, and test if only one server is available + n2.stop(); + + conn = getConnection(urlCluster, user, password); + stat = conn.createStatement(); + rs = stat.executeQuery("select * from test"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + conn.close(); + + n1.stop(); + deleteFiles(); + } + + private void testClientInfo() throws SQLException { + deleteFiles(); + + org.h2.Driver.load(); + String user = getUser(), password = getPassword(); + Connection conn; + + + Server n1 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node1").start(); + int port1 = n1.getPort(); + Server n2 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node2").start(); + int port2 = n2.getPort(); + + String serverList = "localhost:" + port1 + ",localhost:" + port2; + String url1 = getURL("jdbc:h2:tcp://localhost:" + port1 + "/test", true); + String url2 = getURL("jdbc:h2:tcp://localhost:" + port2 + "/test", true); + String urlCluster = getURL("jdbc:h2:tcp://" + serverList + "/test", true); + + CreateCluster.main("-urlSource", url1, "-urlTarget", url2, + "-user", user, "-password", password, "-serverList", + serverList); + + conn = getConnection(urlCluster, user, password); + Properties p = conn.getClientInfo(); + assertEquals("2", p.getProperty("numServers")); + assertEquals("127.0.0.1:" + port1, p.getProperty("server0")); + assertEquals("127.0.0.1:" + port2, p.getProperty("server1")); + + assertEquals("2", conn.getClientInfo("numServers")); + assertEquals("127.0.0.1:" + port1, conn.getClientInfo("server0")); + assertEquals("127.0.0.1:" + port2, conn.getClientInfo("server1")); + conn.close(); + + // stop server 2, and test if only one server is available + n2.stop(); + + conn = getConnection(urlCluster, user, password); + p = conn.getClientInfo(); + + assertEquals("1", p.getProperty("numServers")); + assertEquals("127.0.0.1:" + port1, p.getProperty("server0")); + assertEquals("1", conn.getClientInfo("numServers")); + assertEquals("127.0.0.1:" + port1, conn.getClientInfo("server0")); + conn.close(); + + n1.stop(); + deleteFiles(); + } + + private void testCreateClusterAtRuntime() throws SQLException { + deleteFiles(); + + org.h2.Driver.load(); + String user = getUser(), password = getPassword(); + Connection conn; + Statement stat; + int len = 10; + + // initialize the database + Server n1 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node1").start(); + int port1 = n1.getPort(); + String url1 = getURL("jdbc:h2:tcp://localhost:" + port1 + "/test", false); + conn = getConnection(url1, user, password); + stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name varchar) as " + + "select x, 'Data' || x from system_range(0, " + (len - 1) + ")"); + stat.execute("create user test password 'test'"); + stat.execute("grant all on test to test"); + + // start the second server + Server n2 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-baseDir", getBaseDir() + "/node2").start(); + int port2 = n2.getPort(); + String url2 = getURL("jdbc:h2:tcp://localhost:" + port2 + "/test", false); + + // copy the database and initialize the cluster + String serverList = "localhost:" + port1 + ",localhost:" + port2; + CreateCluster.main("-urlSource", url1, "-urlTarget", url2, + "-user", user, "-password", password, "-serverList", + serverList); + + // check the original connection is closed + assertThrows(ErrorCode.CONNECTION_BROKEN_1, stat). + execute("select * from test"); + JdbcUtils.closeSilently(conn); + + // test the cluster connection + String urlCluster = getURL("jdbc:h2:tcp://" + serverList + "/test", false); + Connection connApp = getConnection(urlCluster + + ";AUTO_RECONNECT=TRUE", user, password); + check(connApp, len, "'" + serverList + "'"); + + // delete the rows, but don't commit + connApp.setAutoCommit(false); + connApp.createStatement().execute("delete from test"); + + // stop server 2, and test if only one server is available + n2.stop(); + + // rollback the transaction + connApp.createStatement().executeQuery("select count(*) from test"); + connApp.rollback(); + check(connApp, len, "''"); + connApp.setAutoCommit(true); + + // re-create the cluster + n2 = org.h2.tools.Server.createTcpServer("-ifNotExists", "-tcpPort", "" + port2, + "-baseDir", getBaseDir() + "/node2").start(); + CreateCluster.main("-urlSource", url1, "-urlTarget", url2, + "-user", user, "-password", password, "-serverList", + serverList); + + // test the cluster connection + check(connApp, len, "'" + serverList + "'"); + connApp.close(); + + // test a non-admin user + String user2 = "test", password2 = getPassword("test"); + connApp = getConnection(urlCluster, user2, password2); + check(connApp, len, "'" + serverList + "'"); + connApp.close(); + + n1.stop(); + + // test non-admin cluster connection if only one server runs + Connection connApp2 = getConnection(urlCluster + + ";AUTO_RECONNECT=TRUE", user2, password2); + check(connApp2, len, "''"); + connApp2.close(); + // test non-admin cluster connection if only one server runs + connApp2 = getConnection(urlCluster + + ";AUTO_RECONNECT=TRUE", user2, password2); + check(connApp2, len, "''"); + connApp2.close(); + + n2.stop(); + deleteFiles(); + } + + private void testStartStopCluster() throws SQLException { + int port1 = 9193, port2 = 9194; + String serverList = "localhost:" + port1 + ",localhost:" + port2; + deleteFiles(); + + // initialize the database + Connection conn; + org.h2.Driver.load(); + + String urlNode1 = getURL("node1/test", true); + String urlNode2 = getURL("node2/test", true); + String user = getUser(), password = getPassword(); + conn = getConnection(urlNode1, user, password); + Statement stat; + stat = conn.createStatement(); + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST VALUES(?, ?)"); + int len = getSize(10, 1000); + for (int i = 0; i < len; i++) { + prep.setInt(1, i); + prep.setString(2, "Data" + i); + prep.executeUpdate(); + } + check(conn, len, "''"); + conn.close(); + + // copy the database and initialize the cluster + CreateCluster.main("-urlSource", urlNode1, "-urlTarget", + urlNode2, "-user", user, "-password", password, "-serverList", + serverList); + + // start both servers + Server n1 = org.h2.tools.Server.createTcpServer("-tcpPort", "" + + port1, "-baseDir", getBaseDir() + "/node1").start(); + Server n2 = org.h2.tools.Server.createTcpServer("-tcpPort", "" + + port2, "-baseDir", getBaseDir() + "/node2").start(); + + // try to connect in standalone mode - should fail + // should not be able to connect in standalone mode + assertThrows(ErrorCode.CLUSTER_ERROR_DATABASE_RUNS_CLUSTERED_1, + () -> getConnection("jdbc:h2:tcp://localhost:" + port1 + "/test", user, password)); + assertThrows(ErrorCode.CLUSTER_ERROR_DATABASE_RUNS_CLUSTERED_1, + () -> getConnection("jdbc:h2:tcp://localhost:" + port2 + "/test", user, password)); + + // test a cluster connection + conn = getConnection("jdbc:h2:tcp://" + serverList + "/test", user, password); + check(conn, len, "'"+serverList+"'"); + conn.close(); + + // stop server 2, and test if only one server is available + n2.stop(); + conn = getConnection("jdbc:h2:tcp://" + serverList + "/test", user, password); + check(conn, len, "''"); + conn.close(); + conn = getConnection("jdbc:h2:tcp://" + serverList + "/test", user, password); + check(conn, len, "''"); + conn.close(); + + // disable the cluster + conn = getConnection("jdbc:h2:tcp://localhost:"+ + port1+"/test;CLUSTER=''", user, password); + conn.close(); + n1.stop(); + + // re-create the cluster + DeleteDbFiles.main("-dir", getBaseDir() + "/node2", "-quiet"); + CreateCluster.main("-urlSource", urlNode1, "-urlTarget", + urlNode2, "-user", user, "-password", password, "-serverList", + serverList); + n1 = org.h2.tools.Server.createTcpServer("-tcpPort", "" + + port1, "-baseDir", getBaseDir() + "/node1").start(); + n2 = org.h2.tools.Server.createTcpServer("-tcpPort", "" + + port2, "-baseDir", getBaseDir() + "/node2").start(); + + conn = getConnection("jdbc:h2:tcp://" + serverList + "/test", user, password); + stat = conn.createStatement(); + stat.execute("CREATE TABLE BOTH(ID INT)"); + + n1.stop(); + + stat.execute("CREATE TABLE A(ID INT)"); + conn.close(); + n2.stop(); + + n1 = org.h2.tools.Server.createTcpServer("-tcpPort", "" + + port1, "-baseDir", getBaseDir() + "/node1").start(); + conn = getConnection("jdbc:h2:tcp://localhost:"+ + port1+"/test;CLUSTER=''", user, password); + check(conn, len, "''"); + conn.close(); + n1.stop(); + + n2 = org.h2.tools.Server.createTcpServer("-tcpPort", "" + + port2, "-baseDir", getBaseDir() + "/node2").start(); + conn = getConnection("jdbc:h2:tcp://localhost:" + + port2 + "/test;CLUSTER=''", user, password); + check(conn, len, "''"); + conn.createStatement().execute("SELECT * FROM A"); + conn.close(); + n2.stop(); + deleteFiles(); + } + + private void deleteFiles() throws SQLException { + DeleteDbFiles.main("-dir", getBaseDir() + "/node1", "-quiet"); + DeleteDbFiles.main("-dir", getBaseDir() + "/node2", "-quiet"); + FileUtils.delete(getBaseDir() + "/node1"); + FileUtils.delete(getBaseDir() + "/node2"); + } + + private void check(Connection conn, int len, String expectedCluster) + throws SQLException { + PreparedStatement prep = conn.prepareStatement("SELECT * FROM TEST WHERE ID=?"); + for (int i = 0; i < len; i++) { + prep.setInt(1, i); + ResultSet rs = prep.executeQuery(); + rs.next(); + assertEquals("Data" + i, rs.getString(2)); + assertFalse(rs.next()); + } + ResultSet rs = conn.createStatement().executeQuery( + "SELECT SETTING_VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME = 'CLUSTER'"); + String cluster = rs.next() ? rs.getString(1) : "''"; + assertEquals(expectedCluster, cluster); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestCompatibility.java b/h2/src/test/org/h2/test/db/TestCompatibility.java new file mode 100644 index 0000000..b64cb97 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestCompatibility.java @@ -0,0 +1,788 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Locale; +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests the compatibility with other databases. + */ +public class TestCompatibility extends TestDb { + + private Connection conn; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + deleteDb("compatibility"); + + testCaseSensitiveIdentifiers(); + + conn = getConnection("compatibility"); + testDomain(); + testColumnAlias(); + testUniqueIndexSingleNull(); + testUniqueIndexOracle(); + testPostgreSQL(); + testHsqlDb(); + testMySQL(); + testDB2(); + testDerby(); + testSybaseAndMSSQLServer(); + + testUnknownSet(); + + conn.close(); + testIdentifiers(); + testIdentifiersCaseInResultSet(); + testDatabaseToLowerParser(); + testOldInformationSchema(); + deleteDb("compatibility"); + + testUnknownURL(); + } + + private void testCaseSensitiveIdentifiers() throws SQLException { + Connection c = getConnection("compatibility;DATABASE_TO_UPPER=FALSE;CASE_INSENSITIVE_IDENTIFIERS=TRUE"); + Statement stat = c.createStatement(); + stat.execute("create table test(id int primary key, name varchar) " + + "as select 1, 'hello'"); + assertThrows(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, stat). + execute("create table test(id int primary key, name varchar)"); + assertThrows(ErrorCode.DUPLICATE_COLUMN_NAME_1, stat). + execute("alter table test add column Name varchar"); + ResultSet rs; + + DatabaseMetaData meta = c.getMetaData(); + rs = meta.getTables(null, null, "test", null); + assertTrue(rs.next()); + assertEquals("test", rs.getString("TABLE_NAME")); + + rs = stat.executeQuery("select id, name from test"); + assertEquals("id", rs.getMetaData().getColumnLabel(1)); + assertEquals("name", rs.getMetaData().getColumnLabel(2)); + + rs = stat.executeQuery("select Id, Name from Test"); + assertEquals("id", rs.getMetaData().getColumnLabel(1)); + assertEquals("name", rs.getMetaData().getColumnLabel(2)); + + rs = stat.executeQuery("select ID, NAME from TEST"); + assertEquals("id", rs.getMetaData().getColumnLabel(1)); + assertEquals("name", rs.getMetaData().getColumnLabel(2)); + + stat.execute("select COUNT(*), count(*), Count(*), Sum(id) from test"); + + stat.execute("select LENGTH(name), length(name), Length(name) from test"); + + stat.execute("select t.id from test t group by t.id"); + stat.execute("select id from test t group by t.id"); + stat.execute("select id from test group by ID"); + stat.execute("select id as c from test group by c"); + stat.execute("select t.id from test t group by T.ID"); + stat.execute("select id from test t group by T.ID"); + + stat.execute("drop table test"); + + rs = stat.executeQuery("select 1e10, 1000000000000000000000e10, 0xfAfBl"); + assertTrue(rs.next()); + assertEquals(1e10, rs.getDouble(1)); + assertEquals(1000000000000000000000e10, rs.getDouble(2)); + assertEquals(0xfafbL, rs.getLong(3)); + assertFalse(rs.next()); + + stat.execute("create table \"t 1\" (a int, b int)"); + stat.execute("create view v as select * from \"t 1\""); + stat.executeQuery("select * from v").close(); + stat.execute("drop view v"); + stat.execute("drop table \"t 1\""); + + c.close(); + } + + private void testDomain() throws SQLException { + if (config.memory) { + return; + } + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key) as select 1"); + assertThrows(ErrorCode.DOMAIN_ALREADY_EXISTS_1, stat). + execute("create domain int as varchar"); + conn.close(); + conn = getConnection("compatibility"); + stat = conn.createStatement(); + stat.execute("insert into test values(2)"); + stat.execute("drop table test"); + } + + private void testColumnAlias() throws SQLException { + Statement stat = conn.createStatement(); + String[] modes = { "PostgreSQL", "MySQL", "HSQLDB", "MSSQLServer", + "Derby", "Oracle", "Regular" }; + String columnAlias; + columnAlias = "HSQLDB,MySQL,Regular"; + stat.execute("CREATE TABLE TEST(ID INT)"); + for (String mode : modes) { + stat.execute("SET MODE " + mode); + ResultSet rs = stat.executeQuery("SELECT ID I FROM TEST"); + ResultSetMetaData meta = rs.getMetaData(); + assertEquals(mode + " mode", "I", meta.getColumnLabel(1)); + String columnName = meta.getColumnName(1); + String tableName = meta.getTableName(1); + String schemaName = meta.getSchemaName(1); + if (columnAlias.contains(mode)) { + assertEquals(mode + " mode", "ID", columnName); + assertEquals(mode + " mode", "TEST", tableName); + assertEquals(mode + " mode", "PUBLIC", schemaName); + } else { + assertEquals(mode + " mode", "I", columnName); + assertEquals(mode + " mode", "", tableName); + assertEquals(mode + " mode", "", schemaName); + } + } + stat.execute("DROP TABLE TEST"); + } + + private void testUniqueIndexSingleNull() throws SQLException { + Statement stat = conn.createStatement(); + String[] modes = { "PostgreSQL", "MySQL", "HSQLDB", "MSSQLServer", + "Derby", "Oracle", "Regular" }; + String multiNull = "PostgreSQL,MySQL,HSQLDB,Oracle,Regular"; + for (String mode : modes) { + stat.execute("SET MODE " + mode); + stat.execute("CREATE TABLE TEST(ID INT)"); + stat.execute("CREATE UNIQUE INDEX IDX_ID_U ON TEST(ID)"); + try { + stat.execute("INSERT INTO TEST VALUES(1), (2), (NULL), (NULL)"); + assertTrue(mode + " mode should not support multiple NULL", + multiNull.contains(mode)); + } catch (SQLException e) { + assertTrue(mode + " mode should support multiple NULL", + multiNull.indexOf(mode) < 0); + } + stat.execute("DROP TABLE TEST"); + } + } + + private void testUniqueIndexOracle() throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("SET MODE ORACLE"); + stat.execute("create table t2(c1 int, c2 int)"); + stat.execute("create unique index i2 on t2(c1, c2)"); + stat.execute("insert into t2 values (null, 1)"); + assertThrows(ErrorCode.DUPLICATE_KEY_1, stat). + execute("insert into t2 values (null, 1)"); + stat.execute("insert into t2 values (null, null)"); + stat.execute("insert into t2 values (null, null)"); + stat.execute("insert into t2 values (1, null)"); + assertThrows(ErrorCode.DUPLICATE_KEY_1, stat). + execute("insert into t2 values (1, null)"); + stat.execute("DROP TABLE T2"); + } + + private void testHsqlDb() throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("set mode hsqldb"); + testLog(Math.log(10), stat); + + stat.execute("DROP TABLE TEST IF EXISTS; " + + "CREATE TABLE TEST(ID INT PRIMARY KEY); "); + stat.execute("CALL CURRENT_TIME"); + stat.execute("CALL CURRENT_TIMESTAMP"); + stat.execute("CALL CURRENT_DATE"); + stat.execute("CALL SYSDATE"); + stat.execute("CALL TODAY"); + + stat.execute("DROP TABLE TEST IF EXISTS"); + } + + private void testLog(double expected, Statement stat) throws SQLException { + stat.execute("create table log(id int)"); + stat.execute("insert into log values(1)"); + ResultSet rs = stat.executeQuery("select log(10) from log"); + rs.next(); + assertEquals((int) (expected * 100), (int) (rs.getDouble(1) * 100)); + rs = stat.executeQuery("select ln(10) from log"); + rs.next(); + assertEquals((int) (Math.log(10) * 100), (int) (rs.getDouble(1) * 100)); + stat.execute("drop table log"); + } + + private void testPostgreSQL() throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("SET MODE PostgreSQL"); + testLog(Math.log10(10), stat); + + assertResult("ABC", stat, "SELECT SUBSTRING('ABCDEF' FOR 3)"); + assertResult("ABCD", stat, "SELECT SUBSTRING('0ABCDEF' FROM 2 FOR 4)"); + + /* --------- Behaviour of CHAR(N) --------- */ + + /* Test right-padding of CHAR(N) at INSERT */ + stat.execute("CREATE TABLE TEST(CH CHAR(10))"); + stat.execute("INSERT INTO TEST (CH) VALUES ('Hello')"); + assertResult("Hello ", stat, "SELECT CH FROM TEST"); + + /* Test that WHERE clauses accept unpadded values and will pad before comparison */ + assertResult("Hello ", stat, "SELECT CH FROM TEST WHERE CH = 'Hello'"); + + /* Test CHAR which is identical to CHAR(1) */ + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST(CH CHAR)"); + stat.execute("INSERT INTO TEST (CH) VALUES ('')"); + assertResult(" ", stat, "SELECT CH FROM TEST"); + assertResult(" ", stat, "SELECT CH FROM TEST WHERE CH = ''"); + + /* Test that excessive spaces are trimmed */ + stat.execute("DELETE FROM TEST"); + stat.execute("INSERT INTO TEST (CH) VALUES ('1 ')"); + assertResult("1", stat, "SELECT CH FROM TEST"); + assertResult("1", stat, "SELECT CH FROM TEST WHERE CH = '1 '"); + + /* Test that we do not trim too far */ + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST(CH CHAR(2))"); + stat.execute("INSERT INTO TEST (CH) VALUES ('1 ')"); + assertResult("1 ", stat, "SELECT CH FROM TEST"); + assertResult("1 ", stat, "SELECT CH FROM TEST WHERE CH = '1 '"); + + /* --------- Disallowed column types --------- */ + + String[] DISALLOWED_TYPES = {"NUMBER", "IDENTITY", "TINYINT", "BLOB"}; + for (String type : DISALLOWED_TYPES) { + stat.execute("DROP TABLE IF EXISTS TEST"); + assertThrows(ErrorCode.UNKNOWN_DATA_TYPE_1, stat).execute("CREATE TABLE TEST(COL " + type + ")"); + } + + /* Test MONEY data type */ + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST(M MONEY)"); + stat.execute("INSERT INTO TEST(M) VALUES (-92233720368547758.08)"); + stat.execute("INSERT INTO TEST(M) VALUES (0.11111)"); + stat.execute("INSERT INTO TEST(M) VALUES (92233720368547758.07)"); + ResultSet rs = stat.executeQuery("SELECT M FROM TEST ORDER BY M"); + assertTrue(rs.next()); + assertEquals(new BigDecimal("-92233720368547758.08"), rs.getBigDecimal(1)); + assertTrue(rs.next()); + assertEquals(new BigDecimal("0.11"), rs.getBigDecimal(1)); + assertTrue(rs.next()); + assertEquals(new BigDecimal("92233720368547758.07"), rs.getBigDecimal(1)); + assertFalse(rs.next()); + + /* Test SET STATEMENT_TIMEOUT */ + assertEquals(0, stat.getQueryTimeout()); + conn.close(); + deleteDb("compatibility"); + // `stat.getQueryTimeout()` caches the result, so create another connection + conn = getConnection("compatibility;MODE=PostgreSQL"); + stat = conn.createStatement(); + // `STATEMENT_TIMEOUT` uses milliseconds + stat.execute("SET STATEMENT_TIMEOUT TO 30000"); + // `stat.getQueryTimeout()` returns seconds + assertEquals(30, stat.getQueryTimeout()); + } + + private void testMySQL() throws SQLException { + // need to reconnect to change DATABASE_TO_LOWER + conn.close(); + deleteDb("compatibility"); + conn = getConnection("compatibility;MODE=MYSQL;DATABASE_TO_LOWER=TRUE"); + Statement stat = conn.createStatement(); + stat.execute("create schema test_schema"); + stat.execute("use test_schema"); + assertResult("test_schema", stat, "select schema()"); + stat.execute("use public"); + assertResult("public", stat, "select schema()"); + + stat.execute("SELECT 1"); + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE `TEST`(ID INT PRIMARY KEY, NAME VARCHAR)"); + stat.execute("INSERT INTO TEST VALUES(1, 'Hello'), (2, 'World')"); + assertResult("0", stat, "SELECT UNIX_TIMESTAMP('1970-01-01 00:00:00Z')"); + assertResult("1196418619", stat, "SELECT UNIX_TIMESTAMP('2007-11-30 10:30:19Z')"); + assertResult("1196418619", stat, "SELECT UNIX_TIMESTAMP(FROM_UNIXTIME(1196418619))"); + assertResult("2007 November", stat, "SELECT FROM_UNIXTIME(1196300000, '%Y %M')"); + assertResult("2003-12-31", stat, "SELECT DATE('2003-12-31 11:02:03')"); + assertResult("2003-12-31", stat, "SELECT DATE('2003-12-31 11:02:03')"); + assertResult(null, stat, "SELECT DATE('100')"); + // check the weird MySQL variant of DELETE + stat.execute("DELETE TEST FROM TEST WHERE 1=2"); + + // Check conversion between VARCHAR and VARBINARY + String string = "ABCD\u1234"; + byte[] bytes = string.getBytes(StandardCharsets.UTF_8); + stat.execute("CREATE TABLE TEST2(C VARCHAR, B VARBINARY)"); + stat.execute("INSERT INTO TEST2(C) VALUES ('" + string + "')"); + assertEquals(1, stat.executeUpdate("UPDATE TEST2 SET B = C")); + ResultSet rs = stat.executeQuery("SELECT B FROM TEST2"); + assertTrue(rs.next()); + assertEquals(bytes, rs.getBytes(1)); + assertEquals(bytes, rs.getBytes("B")); + assertEquals(1, stat.executeUpdate("UPDATE TEST2 SET C = B")); + testMySQLBytesCheck(stat, string, bytes); + PreparedStatement prep = conn.prepareStatement("UPDATE TEST2 SET C = ?"); + prep.setBytes(1, bytes); + assertEquals(1, prep.executeUpdate()); + testMySQLBytesCheck(stat, string, bytes); + stat.execute("DELETE FROM TEST2"); + prep = conn.prepareStatement("INSERT INTO TEST2(C) VALUES (?)"); + prep.setBytes(1, bytes); + assertEquals(1, prep.executeUpdate()); + testMySQLBytesCheck(stat, string, bytes); + prep = conn.prepareStatement("SELECT C FROM TEST2 WHERE C = ?"); + prep.setBytes(1, bytes); + testMySQLBytesCheck(prep.executeQuery(), string, bytes); + stat.execute("CREATE INDEX TEST2_C ON TEST2(C)"); + prep = conn.prepareStatement("SELECT C FROM TEST2 WHERE C = ?"); + prep.setBytes(1, bytes); + testMySQLBytesCheck(prep.executeQuery(), string, bytes); + stat.execute("DROP TABLE TEST2"); + + if (config.memory) { + return; + } + // need to reconnect, because meta data tables may be initialized + conn.close(); + conn = getConnection("compatibility;MODE=MYSQL;DATABASE_TO_LOWER=TRUE"); + stat = conn.createStatement(); + testLog(Math.log(10), stat); + + DatabaseMetaData meta = conn.getMetaData(); + assertTrue(meta.storesLowerCaseIdentifiers()); + assertFalse(meta.storesLowerCaseQuotedIdentifiers()); + assertFalse(meta.storesMixedCaseIdentifiers()); + assertFalse(meta.storesMixedCaseQuotedIdentifiers()); + assertFalse(meta.storesUpperCaseIdentifiers()); + assertFalse(meta.storesUpperCaseQuotedIdentifiers()); + + stat = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, + ResultSet.CONCUR_UPDATABLE); + assertResult("test", stat, "SHOW TABLES"); + rs = stat.executeQuery("SELECT * FROM TEST"); + rs.next(); + rs.updateString(2, "Hallo"); + rs.updateRow(); + + // we used to have an NullPointerException in the MetaTable.checkIndex() + // method + rs = stat.executeQuery("SELECT * FROM " + + "INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME > 'aaaa'"); + rs = stat.executeQuery("SELECT * FROM " + + "INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME < 'aaaa'"); + + stat.execute("CREATE TABLE TEST_1" + + "(ID INT PRIMARY KEY) ENGINE=InnoDb"); + stat.execute("CREATE TABLE TEST_2" + + "(ID INT PRIMARY KEY) ENGINE=MyISAM"); + stat.execute("CREATE TABLE TEST_3" + + "(ID INT PRIMARY KEY) ENGINE=InnoDb charset=UTF8"); + stat.execute("CREATE TABLE TEST_4" + + "(ID INT PRIMARY KEY) charset=UTF8"); + stat.execute("CREATE TABLE TEST_5" + + "(ID INT AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDb auto_increment=3 default charset=UTF8"); + stat.execute("CREATE TABLE TEST_6" + + "(ID INT AUTO_INCREMENT PRIMARY KEY) ENGINE=MyISAM default character set UTF8MB4, auto_increment 3"); + stat.execute("CREATE TABLE TEST_7" + + "(ID INT AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDb auto_increment=3 charset=UTF8 comment 'text'"); + stat.execute("CREATE TABLE TEST_8" + + "(ID INT AUTO_INCREMENT PRIMARY KEY) ENGINE=InnoDb auto_increment=3 character set=UTF8"); + stat.execute("CREATE TABLE TEST_9" + + "(ID INT, KEY TEST_7_IDX(ID) USING BTREE)"); + stat.execute("CREATE TABLE TEST_10" + + "(ID INT, UNIQUE KEY TEST_10_IDX(ID) USING BTREE)"); + stat.execute("CREATE TABLE TEST_11(ID INT) COLLATE UTF8"); + stat.execute("CREATE TABLE TEST_12(ID INT) DEFAULT COLLATE UTF8"); + stat.execute("CREATE TABLE TEST_13(a VARCHAR(10) COLLATE UTF8MB4)"); + stat.execute("CREATE TABLE TEST_14(a VARCHAR(10) NULL CHARACTER SET UTF8MB4 COLLATE UTF8MB4_BIN)"); + stat.execute("ALTER TABLE TEST_14 CONVERT TO CHARACTER SET UTF8MB4 COLLATE UTF8MB4_UNICODE_CI"); + stat.execute("ALTER TABLE TEST_14 MODIFY a VARCHAR(10) NOT NULL CHARACTER SET UTF8MB4 COLLATE UTF8"); + assertThrows(ErrorCode.SYNTAX_ERROR_2, stat).execute("CREATE TABLE TEST_99" + + "(ID INT PRIMARY KEY) CHARSET UTF8,"); + assertThrows(ErrorCode.COLUMN_NOT_FOUND_1, stat).execute("CREATE TABLE TEST_99" + + "(ID INT PRIMARY KEY) AUTO_INCREMENT 100"); + assertThrows(ErrorCode.COLUMN_NOT_FOUND_1, stat).execute("CREATE TABLE TEST_99" + + "(ID INT) AUTO_INCREMENT 100"); + + // this maps to SET REFERENTIAL_INTEGRITY TRUE/FALSE + stat.execute("SET foreign_key_checks = 0"); + stat.execute("SET foreign_key_checks = 1"); + + // Check if mysql comments are supported, ensure clean connection + conn.close(); + conn = getConnection("compatibility;MODE=MYSQL;DATABASE_TO_LOWER=TRUE"); + stat = conn.createStatement(); + stat.execute("DROP TABLE IF EXISTS TEST_NO_COMMENT"); + stat.execute("CREATE table TEST_NO_COMMENT " + + "(ID bigint not null auto_increment, " + + "SOME_STR varchar(255), primary key (ID))"); + // now test creating a table with a comment + stat.execute("DROP TABLE IF EXISTS TEST_COMMENT"); + stat.execute("create table TEST_COMMENT (ID bigint not null auto_increment, " + + "SOME_STR varchar(255), primary key (ID)) comment='Some comment.'"); + // now test creating a table with a comment and engine + // and other typical mysql stuff as generated by hibernate + stat.execute("DROP TABLE IF EXISTS TEST_COMMENT_ENGINE"); + stat.execute("create table TEST_COMMENT_ENGINE " + + "(ID bigint not null auto_increment, " + + "ATTACHMENT_ID varchar(255), " + + "SOME_ITEM_ID bigint not null, primary key (ID), " + + "unique (ATTACHMENT_ID, SOME_ITEM_ID)) " + + "comment='Comment Again' ENGINE=InnoDB"); + + stat.execute("CREATE TABLE TEST2(ID INT) ROW_FORMAT=DYNAMIC"); + + // check the MySQL index dropping syntax + stat.execute("ALTER TABLE TEST_COMMENT_ENGINE ADD CONSTRAINT CommentUnique UNIQUE (SOME_ITEM_ID)"); + stat.execute("ALTER TABLE TEST_COMMENT_ENGINE DROP INDEX CommentUnique"); + stat.execute("CREATE INDEX IDX_ATTACHMENT_ID ON TEST_COMMENT_ENGINE (ATTACHMENT_ID)"); + stat.execute("DROP INDEX IDX_ATTACHMENT_ID ON TEST_COMMENT_ENGINE"); + + stat.execute("DROP ALL OBJECTS"); + + conn.close(); + deleteDb("compatibility"); + conn = getConnection("compatibility"); + } + + private void testMySQLBytesCheck(Statement stat, String string, byte[] bytes) throws SQLException { + testMySQLBytesCheck(stat.executeQuery("SELECT C FROM TEST2"), string, bytes); + } + + private void testMySQLBytesCheck(ResultSet rs, String string, byte[] bytes) throws SQLException { + assertTrue(rs.next()); + assertEquals(string, rs.getString(1)); + assertEquals(bytes, rs.getBytes(1)); + assertEquals(bytes, rs.getBytes("C")); + } + + private void testSybaseAndMSSQLServer() throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("SET MODE MSSQLServer"); + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST(NAME VARCHAR(50), SURNAME VARCHAR(50))"); + stat.execute("INSERT INTO TEST VALUES('John', 'Doe')"); + stat.execute("INSERT INTO TEST VALUES('Jack', 'Sullivan')"); + + assertResult("abcd123", stat, "SELECT 'abc' + 'd123'"); + + assertResult("Doe, John", stat, + "SELECT surname + ', ' + name FROM test " + + "WHERE SUBSTRING(NAME,1,1)+SUBSTRING(SURNAME,1,1) = 'JD'"); + + stat.execute("ALTER TABLE TEST ADD COLUMN full_name VARCHAR(100)"); + stat.execute("UPDATE TEST SET full_name = name + ', ' + surname"); + assertResult("John, Doe", stat, "SELECT full_name FROM TEST where name='John'"); + + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST VALUES(?, ?, ? + ', ' + ?)"); + int ca = 1; + prep.setString(ca++, "Paul"); + prep.setString(ca++, "Frank"); + prep.setString(ca++, "Paul"); + prep.setString(ca++, "Frank"); + prep.executeUpdate(); + prep.close(); + + assertResult("Paul, Frank", stat, "SELECT full_name FROM test " + + "WHERE name = 'Paul'"); + + prep = conn.prepareStatement("SELECT ? + ?"); + int cb = 1; + prep.setString(cb++, "abcd123"); + prep.setString(cb++, "d123"); + prep.executeQuery(); + prep.close(); + + prep = conn.prepareStatement("SELECT full_name FROM test " + + "WHERE (SUBSTRING(name, 1, 1) + SUBSTRING(surname, 2, 3)) = ?"); + prep.setString(1, "Joe"); + ResultSet rs = prep.executeQuery(); + assertTrue("Result cannot be empty", rs.next()); + assertEquals("John, Doe", rs.getString(1)); + rs.close(); + prep.close(); + + // CONVERT has it's parameters the other way around from the default + // mode + rs = stat.executeQuery("SELECT CONVERT(INT, '10')"); + rs.next(); + assertEquals(10, rs.getInt(1)); + rs.close(); + rs = stat.executeQuery("SELECT X FROM (SELECT CONVERT(INT, '10') AS X)"); + rs.next(); + assertEquals(10, rs.getInt(1)); + rs.close(); + + // make sure we're ignoring the index part of the statement + rs = stat.executeQuery("select * from test (index table1_index)"); + rs.close(); + + // UNIQUEIDENTIFIER is MSSQL's equivalent of UUID + stat.execute("create table test3 (id UNIQUEIDENTIFIER)"); + + /* Test MONEY data type */ + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST(M MONEY)"); + stat.execute("INSERT INTO TEST(M) VALUES (-922337203685477.5808)"); + stat.execute("INSERT INTO TEST(M) VALUES (0.11111)"); + stat.execute("INSERT INTO TEST(M) VALUES (922337203685477.5807)"); + rs = stat.executeQuery("SELECT M FROM TEST ORDER BY M"); + assertTrue(rs.next()); + assertEquals(new BigDecimal("-922337203685477.5808"), rs.getBigDecimal(1)); + assertTrue(rs.next()); + assertEquals(new BigDecimal("0.1111"), rs.getBigDecimal(1)); + assertTrue(rs.next()); + assertEquals(new BigDecimal("922337203685477.5807"), rs.getBigDecimal(1)); + assertFalse(rs.next()); + + /* Test SMALLMONEY data type */ + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST(M SMALLMONEY)"); + stat.execute("INSERT INTO TEST(M) VALUES (-214748.3648)"); + stat.execute("INSERT INTO TEST(M) VALUES (0.11111)"); + stat.execute("INSERT INTO TEST(M) VALUES (214748.3647)"); + rs = stat.executeQuery("SELECT M FROM TEST ORDER BY M"); + assertTrue(rs.next()); + assertEquals(new BigDecimal("-214748.3648"), rs.getBigDecimal(1)); + assertTrue(rs.next()); + assertEquals(new BigDecimal("0.1111"), rs.getBigDecimal(1)); + assertTrue(rs.next()); + assertEquals(new BigDecimal("214748.3647"), rs.getBigDecimal(1)); + assertFalse(rs.next()); + } + + private void testDB2() throws SQLException { + conn.close(); + conn = getConnection("compatibility;MODE=DB2"); + Statement stat = conn.createStatement(); + testLog(Math.log(10), stat); + + ResultSet res = conn.createStatement().executeQuery( + "SELECT 1 FROM sysibm.sysdummy1"); + res.next(); + assertEquals("1", res.getString(1)); + conn.close(); + conn = getConnection("compatibility;MODE=MySQL"); + assertThrows(ErrorCode.SCHEMA_NOT_FOUND_1, conn.createStatement()). + executeQuery("SELECT 1 FROM sysibm.sysdummy1"); + conn.close(); + conn = getConnection("compatibility;MODE=DB2"); + stat = conn.createStatement(); + stat.execute("drop table test if exists"); + stat.execute("create table test(id varchar)"); + stat.execute("insert into test values ('3'),('1'),('2')"); + res = stat.executeQuery("select id from test order by id " + + "fetch next 2 rows only"); + res.next(); + assertEquals("1", res.getString(1)); + res.next(); + assertEquals("2", res.getString(1)); + assertFalse(res.next()); + conn.close(); + + // test isolation-clause + conn = getConnection("compatibility;MODE=DB2"); + stat = conn.createStatement(); + stat.execute("drop table test if exists"); + stat.execute("create table test(id varchar)"); + res = stat.executeQuery("select * from test with ur"); + stat.executeUpdate("insert into test values (1) with ur"); + res = stat.executeQuery("select * from test where id = 1 with rr"); + res = stat.executeQuery("select * from test order by id " + + "fetch next 2 rows only with rr"); + res = stat.executeQuery("select * from test order by id " + + "fetch next 2 rows only with rs"); + res = stat.executeQuery("select * from test order by id " + + "fetch next 2 rows only with cs"); + res = stat.executeQuery("select * from test order by id " + + "fetch next 2 rows only with ur"); + // test isolation-clause with lock-request-clause + res = stat.executeQuery("select * from test order by id " + + "fetch next 2 rows only with rr use and keep share locks"); + res = stat.executeQuery("select * from test order by id " + + "fetch next 2 rows only with rs use and keep update locks"); + res = stat.executeQuery("select * from test order by id " + + "fetch next 2 rows only with rr use and keep exclusive locks"); + + // Test DB2 TIMESTAMP format with dash separating date and time + stat.execute("drop table test if exists"); + stat.execute("create table test(date TIMESTAMP)"); + stat.executeUpdate("insert into test (date) values ('2014-04-05-09.48.28.020005')"); + assertResult("2014-04-05 09:48:28.020005", stat, + "select date from test"); // <- result is always H2 format timestamp! + assertResult("2014-04-05 09:48:28.020005", stat, + "select date from test where date = '2014-04-05-09.48.28.020005'"); + assertResult("2014-04-05 09:48:28.020005", stat, + "select date from test where date = '2014-04-05 09:48:28.020005'"); + + // Test limited support for DB2's special registers + + // Standard SQL functions like LOCALTIMESTAMP, CURRENT_TIMESTAMP and + // others are used to compare values, their implementation in H2 is + // compatible with standard, but may be not really compatible with DB2. + assertResult("TRUE", stat, "SELECT LOCALTIMESTAMP = CURRENT TIMESTAMP"); + assertResult("TRUE", stat, "SELECT CAST(LOCALTIMESTAMP AS VARCHAR) = CAST(CURRENT TIMESTAMP AS VARCHAR)"); + assertResult("TRUE", stat, "SELECT CURRENT_TIMESTAMP = CURRENT TIMESTAMP WITH TIME ZONE"); + assertResult("TRUE", stat, + "SELECT CAST(CURRENT_TIMESTAMP AS VARCHAR) = CAST(CURRENT TIMESTAMP WITH TIME ZONE AS VARCHAR)"); + assertResult("TRUE", stat, "SELECT CURRENT_TIME = CURRENT TIME"); + assertResult("TRUE", stat, "SELECT CURRENT_DATE = CURRENT DATE"); + } + + private void testDerby() throws SQLException { + conn.close(); + conn = getConnection("compatibility;MODE=Derby"); + Statement stat = conn.createStatement(); + testLog(Math.log(10), stat); + + ResultSet res = conn.createStatement().executeQuery( + "SELECT 1 FROM sysibm.sysdummy1 fetch next 1 row only"); + res.next(); + assertEquals("1", res.getString(1)); + conn.close(); + conn = getConnection("compatibility;MODE=PostgreSQL"); + assertThrows(ErrorCode.SCHEMA_NOT_FOUND_1, conn.createStatement()). + executeQuery("SELECT 1 FROM sysibm.sysdummy1"); + conn.close(); + conn = getConnection("compatibility"); + } + + private void testUnknownSet() throws SQLException { + Statement stat = conn.createStatement(); + assertThrows(ErrorCode.UNKNOWN_MODE_1, stat).execute("SET MODE UnknownMode"); + } + + private void testIdentifiers() throws SQLException { + deleteDb("compatibility"); + testIdentifiers(false, false, false); + testIdentifiers(false, false, true); + testIdentifiers(true, false, false); + testIdentifiers(true, false, true); + testIdentifiers(false, true, false); + testIdentifiers(false, true, true); + } + + private void testIdentifiers(boolean upper, boolean lower, boolean caseInsensitiveIdentifiers) // + throws SQLException { + try (Connection conn = getConnection("compatibility;DATABASE_TO_UPPER=" + upper + ";DATABASE_TO_LOWER=" + lower + + ";CASE_INSENSITIVE_IDENTIFIERS=" + caseInsensitiveIdentifiers)) { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE Test(Id INT) AS VALUES 2"); + String schema = "PUBLIC", table = "Test", column = "Id"; + if (upper) { + table = table.toUpperCase(Locale.ROOT); + column = column.toUpperCase(Locale.ROOT); + } else if (lower) { + schema = schema.toLowerCase(Locale.ROOT); + table = table.toLowerCase(Locale.ROOT); + column = column.toLowerCase(Locale.ROOT); + } + try (ResultSet rs = stat.executeQuery("SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME" + + " FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME ILIKE 'Test'")) { + assertTrue(rs.next()); + assertEquals(schema, rs.getString(1)); + assertEquals(table, rs.getString(2)); + assertEquals(column, rs.getString(3)); + } + testIdentifiers(stat, "Test", "Id", true); + testIdentifiers(stat, "`Test`", "`Id`", true); + boolean ok = upper || lower || caseInsensitiveIdentifiers; + testIdentifiers(stat, "TEST", "ID", ok); + testIdentifiers(stat, "`TEST`", "`ID`", ok); + testIdentifiers(stat, "test", "id", ok); + testIdentifiers(stat, "`test`", "`id`", ok); + testIdentifiers(stat, '"' + table + '"', '"' + column + '"', true); + testIdentifiers(stat, "\"TeSt\"", "\"iD\"", caseInsensitiveIdentifiers); + stat.execute("CREATE TABLE T2(\"`\" INT, `\"'\"` INT) AS VALUES (1, 2)"); + try (ResultSet rs = stat.executeQuery("SELECT ````, \"\"\"'\"\"\" FROM T2")) { + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals(2, rs.getInt(2)); + } + } finally { + deleteDb("compatibility"); + } + } + + private void testIdentifiers(Statement stat, String table, String column, boolean ok) throws SQLException { + String query = "SELECT _ROWID_, " + column + " FROM " + table; + if (ok) { + try (ResultSet rs = stat.executeQuery(query)) { + assertTrue(rs.next()); + assertEquals(1L, rs.getLong(1)); + assertEquals(2, rs.getInt(2)); + } + } else { + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_WITH_CANDIDATES_2, stat).executeQuery(query); + } + } + + private void testUnknownURL() { + assertThrows(ErrorCode.UNKNOWN_MODE_1, () -> { + getConnection("compatibility;MODE=Unknown").close(); + deleteDb("compatibility"); + }); + } + + private void testIdentifiersCaseInResultSet() throws SQLException { + try (Connection conn = getConnection( + "compatibility;DATABASE_TO_UPPER=FALSE;CASE_INSENSITIVE_IDENTIFIERS=TRUE")) { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(A INT)"); + ResultSet rs = stat.executeQuery("SELECT a from test"); + ResultSetMetaData md = rs.getMetaData(); + assertEquals("A", md.getColumnName(1)); + rs = stat.executeQuery("SELECT a FROM (SELECT 1) t(A)"); + md = rs.getMetaData(); + assertEquals("A", md.getColumnName(1)); + } finally { + deleteDb("compatibility"); + } + } + + private void testDatabaseToLowerParser() throws SQLException { + try (Connection conn = getConnection("compatibility;DATABASE_TO_LOWER=TRUE")) { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("SELECT 0x1234567890AbCdEf"); + rs.next(); + assertEquals(0x1234567890ABCDEFL, rs.getLong(1)); + } finally { + deleteDb("compatibility"); + } + } + + private void testOldInformationSchema() throws SQLException { + try (Connection conn = getConnection( + "compatibility;OLD_INFORMATION_SCHEMA=TRUE")) { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("TABLE INFORMATION_SCHEMA.TABLE_TYPES"); + rs.next(); + assertEquals("TABLE", rs.getString(1)); + } finally { + deleteDb("compatibility"); + } + } + +} diff --git a/h2/src/test/org/h2/test/db/TestCompatibilityOracle.java b/h2/src/test/org/h2/test/db/TestCompatibilityOracle.java new file mode 100644 index 0000000..82ca638 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestCompatibilityOracle.java @@ -0,0 +1,417 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Timestamp; +import java.sql.Types; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Locale; + +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.SimpleResultSet; + +/** + * Test Oracle compatibility mode. + */ +public class TestCompatibilityOracle extends TestDb { + + /** + * Run just this test. + * + * @param s ignored + */ + public static void main(String... s) throws Exception { + TestBase test = TestBase.createCaller().init(); + test.testFromMain(); + } + + @Override + public void test() throws Exception { + testNotNullSyntax(); + testTreatEmptyStringsAsNull(); + testDecimalScale(); + testPoundSymbolInColumnName(); + testToDate(); + testSpecialTypes(); + testDate(); + testSequenceNextval(); + testVarchar(); + deleteDb("oracle"); + } + + private void testNotNullSyntax() throws SQLException { + deleteDb("oracle"); + Connection conn = getConnection("oracle;MODE=Oracle"); + Statement stat = conn.createStatement(); + // Some other variation (oracle syntax) + stat.execute("create table T (C int not null enable)"); + stat.execute("insert into T values(1)"); + stat.execute("drop table T"); + stat.execute("create table T (C int not null enable validate)"); + stat.execute("insert into T values(1)"); + stat.execute("drop table T"); + // can set NULL + // can set NULL even with 'not null syntax' (oracle) + stat.execute("create table T (C int not null disable)"); + stat.execute("insert into T values(null)"); + stat.execute("drop table T"); + // can set NULL even with 'not null syntax' (oracle) + stat.execute("create table T (C int not null enable novalidate)"); + stat.execute("insert into T values(null)"); + stat.execute("drop table T"); + + // Some other variation with oracle syntax + stat.execute("create table T (C int not null)"); + stat.execute("insert into T values(1)"); + stat.execute("alter table T modify C not null"); + stat.execute("insert into T values(1)"); + stat.execute("alter table T modify C not null enable"); + stat.execute("insert into T values(1)"); + stat.execute("alter table T modify C not null enable validate"); + stat.execute("insert into T values(1)"); + stat.execute("drop table T"); + // can set NULL + stat.execute("create table T (C int null)"); + stat.execute("insert into T values(null)"); + stat.execute("alter table T modify C null enable"); + stat.execute("alter table T modify C null enable validate"); + stat.execute("insert into T values(null)"); + // can set NULL even with 'not null syntax' (oracle) + stat.execute("alter table T modify C not null disable"); + stat.execute("insert into T values(null)"); + // can set NULL even with 'not null syntax' (oracle) + stat.execute("alter table T modify C not null enable novalidate"); + stat.execute("insert into T values(null)"); + stat.execute("drop table T"); + + conn.close(); + } + + private void testSpecialTypes() throws SQLException { + // Test VARCHAR, VARCHAR2 with CHAR and BYTE + deleteDb("oracle"); + Connection conn = getConnection("oracle;MODE=Oracle"); + Statement stat = conn.createStatement(); + stat.execute("create table T (ID NUMBER)"); + stat.execute("alter table T add A_1 VARCHAR(1)"); + stat.execute("alter table T add A_2 VARCHAR2(1)"); + stat.execute("alter table T add B_1 VARCHAR(1 byte)"); // with BYTE + stat.execute("alter table T add B_2 VARCHAR2(1 byte)"); + stat.execute("alter table T add C_1 VARCHAR(1 char)"); // with CHAR + stat.execute("alter table T add C_2 VARCHAR2(1 char)"); + stat.execute("alter table T add B_255 VARCHAR(255 byte)"); + stat.execute("alter table T add C_255 VARCHAR(255 char)"); + stat.execute("drop table T"); + conn.close(); + } + + private void testTreatEmptyStringsAsNull() throws SQLException { + deleteDb("oracle"); + Connection conn = getConnection("oracle;MODE=Oracle"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE A (ID NUMBER, X VARCHAR2(1))"); + stat.execute("INSERT INTO A VALUES (1, 'a')"); + stat.execute("INSERT INTO A VALUES (2, '')"); + stat.execute("INSERT INTO A VALUES (3, ' ')"); + assertResult("3", stat, "SELECT COUNT(*) FROM A"); + assertResult("1", stat, "SELECT COUNT(*) FROM A WHERE X IS NULL"); + assertResult("2", stat, "SELECT COUNT(*) FROM A WHERE TRIM(X) IS NULL"); + assertResult("0", stat, "SELECT COUNT(*) FROM A WHERE X = ''"); + assertResult(new Object[][] { { 1, "a" }, { 2, null }, { 3, " " } }, + stat, "SELECT * FROM A"); + assertResult(new Object[][] { { 1, "a" }, { 2, null }, { 3, null } }, + stat, "SELECT ID, TRIM(X) FROM A"); + + stat.execute("CREATE TABLE B (ID NUMBER, X NUMBER)"); + stat.execute("INSERT INTO B VALUES (1, '5')"); + stat.execute("INSERT INTO B VALUES (2, '')"); + assertResult("2", stat, "SELECT COUNT(*) FROM B"); + assertResult("1", stat, "SELECT COUNT(*) FROM B WHERE X IS NULL"); + assertResult("0", stat, "SELECT COUNT(*) FROM B WHERE X = ''"); + assertResult(new Object[][] { { 1, 5 }, { 2, null } }, + stat, "SELECT * FROM B"); + + stat.execute("CREATE TABLE C (ID NUMBER, X TIMESTAMP)"); + stat.execute("INSERT INTO C VALUES (1, '1979-11-12')"); + stat.execute("INSERT INTO C VALUES (2, '')"); + assertResult("2", stat, "SELECT COUNT(*) FROM C"); + assertResult("1", stat, "SELECT COUNT(*) FROM C WHERE X IS NULL"); + assertResult("0", stat, "SELECT COUNT(*) FROM C WHERE X = ''"); + assertResult(new Object[][] { { 1, "1979-11-12 00:00:00.0" }, { 2, null } }, + stat, "SELECT * FROM C"); + + stat.execute("CREATE TABLE D (ID NUMBER, X VARCHAR2(1))"); + stat.execute("INSERT INTO D VALUES (1, 'a')"); + stat.execute("SET @FOO = ''"); + stat.execute("INSERT INTO D VALUES (2, @FOO)"); + assertResult("2", stat, "SELECT COUNT(*) FROM D"); + assertResult("1", stat, "SELECT COUNT(*) FROM D WHERE X IS NULL"); + assertResult("0", stat, "SELECT COUNT(*) FROM D WHERE X = ''"); + assertResult(new Object[][] { { 1, "a" }, { 2, null } }, + stat, "SELECT * FROM D"); + + stat.execute("CREATE TABLE E (ID NUMBER, X RAW(1))"); + stat.execute("INSERT INTO E VALUES (1, HEXTORAW('0A'))"); + stat.execute("INSERT INTO E VALUES (2, '')"); + assertResult("2", stat, "SELECT COUNT(*) FROM E"); + assertResult("1", stat, "SELECT COUNT(*) FROM E WHERE X IS NULL"); + assertResult("0", stat, "SELECT COUNT(*) FROM E WHERE X = ''"); + assertResult(new Object[][] { { 1, new byte[] { 10 } }, { 2, null } }, + stat, "SELECT * FROM E"); + + stat.execute("CREATE TABLE F (ID NUMBER, X VARCHAR2(1))"); + stat.execute("INSERT INTO F VALUES (1, 'a')"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO F VALUES (2, ?)"); + prep.setString(1, ""); + prep.execute(); + assertResult("2", stat, "SELECT COUNT(*) FROM F"); + assertResult("1", stat, "SELECT COUNT(*) FROM F WHERE X IS NULL"); + assertResult("0", stat, "SELECT COUNT(*) FROM F WHERE X = ''"); + assertResult(new Object[][]{{1, "a"}, {2, null}}, stat, "SELECT * FROM F"); + + conn.close(); + } + + private void testDecimalScale() throws SQLException { + deleteDb("oracle"); + Connection conn = getConnection("oracle;MODE=Oracle"); + Statement stat = conn.createStatement(); + + stat.execute("CREATE TABLE A (ID NUMBER, X DECIMAL(9,5))"); + stat.execute("INSERT INTO A VALUES (1, 2)"); + stat.execute("INSERT INTO A VALUES (2, 4.3)"); + stat.execute("INSERT INTO A VALUES (3, '6.78')"); + assertResult("3", stat, "SELECT COUNT(*) FROM A"); + assertResult(new Object[][] { { 1, 2 }, { 2, 4.3 }, { 3, 6.78 } }, + stat, "SELECT * FROM A"); + + conn.close(); + } + + /** + * Test the # in a column name for oracle compatibility + */ + private void testPoundSymbolInColumnName() throws SQLException { + deleteDb("oracle"); + Connection conn = getConnection("oracle;MODE=Oracle"); + Statement stat = conn.createStatement(); + + stat.execute( + "CREATE TABLE TEST(ID INT PRIMARY KEY, U##NAME VARCHAR(255))"); + stat.execute( + "INSERT INTO TEST VALUES(1, 'Hello'), (2, 'HelloWorld'), (3, 'HelloWorldWorld')"); + + assertResult("1", stat, "SELECT ID FROM TEST where U##NAME ='Hello'"); + + conn.close(); + } + + private void testToDate() throws SQLException { + if (config.ci || Locale.getDefault() != Locale.ENGLISH) { + return; + } + deleteDb("oracle"); + Connection conn = getConnection("oracle;MODE=Oracle"); + Statement stat = conn.createStatement(); + + stat.execute("CREATE TABLE DATE_TABLE (ID NUMBER PRIMARY KEY, TEST_VAL TIMESTAMP)"); + stat.execute("INSERT INTO DATE_TABLE VALUES (1, " + + "to_date('31-DEC-9999 23:59:59','DD-MON-RRRR HH24:MI:SS'))"); + stat.execute("INSERT INTO DATE_TABLE VALUES (2, " + + "to_date('01-JAN-0001 00:00:00','DD-MON-RRRR HH24:MI:SS'))"); + + assertResultDate("9999-12-31T23:59:59", stat, + "SELECT TEST_VAL FROM DATE_TABLE WHERE ID=1"); + assertResultDate("0001-01-01T00:00:00", stat, + "SELECT TEST_VAL FROM DATE_TABLE WHERE ID=2"); + + conn.close(); + } + + private void testDate() throws SQLException { + deleteDb("oracle"); + Connection conn = getConnection("oracle;MODE=Oracle"); + Statement stat = conn.createStatement(); + + Timestamp t1 = Timestamp.valueOf("2011-02-03 12:11:10"); + Timestamp t2 = Timestamp.valueOf("1999-10-15 13:14:15"); + Timestamp t3 = Timestamp.valueOf("2030-11-22 11:22:33"); + Timestamp t4 = Timestamp.valueOf("2018-01-10 22:10:01"); + + stat.execute("CREATE TABLE TEST (ID INT PRIMARY KEY, D DATE)"); + stat.executeUpdate("INSERT INTO TEST VALUES(1, TIMESTAMP '2011-02-03 12:11:10.1')"); + stat.executeUpdate("INSERT INTO TEST VALUES(2, CAST ('1999-10-15 13:14:15.1' AS DATE))"); + stat.executeUpdate("INSERT INTO TEST VALUES(3, '2030-11-22 11:22:33.1')"); + PreparedStatement ps = conn.prepareStatement("INSERT INTO TEST VALUES (?, ?)"); + ps.setInt(1, 4); + ps.setTimestamp(2, Timestamp.valueOf("2018-01-10 22:10:01.1")); + ps.executeUpdate(); + ResultSet rs = stat.executeQuery("SELECT D FROM TEST ORDER BY ID"); + rs.next(); + assertEquals(t1, rs.getTimestamp(1)); + rs.next(); + assertEquals(t2, rs.getTimestamp(1)); + rs.next(); + assertEquals(t3, rs.getTimestamp(1)); + rs.next(); + assertEquals(t4, rs.getTimestamp(1)); + assertFalse(rs.next()); + + conn.close(); + } + + private void testSequenceNextval() throws SQLException { + // Test NEXTVAL without Oracle MODE should return BIGINT + checkSequenceTypeWithMode("REGULAR", Types.BIGINT, false); + // Test NEXTVAL with Oracle MODE should return DECIMAL + checkSequenceTypeWithMode("Oracle", Types.NUMERIC, true); + } + + private void checkSequenceTypeWithMode(String mode, int expectedType, boolean usePseudoColumn) + throws SQLException { + deleteDb("oracle"); + Connection conn = getConnection("oracle;MODE=" + mode); + Statement stat = conn.createStatement(); + + stat.execute("CREATE SEQUENCE seq"); + ResultSet rs = stat.executeQuery( + usePseudoColumn ? "SELECT seq.NEXTVAL FROM DUAL" : "VALUES NEXT VALUE FOR seq"); + // Check type: + assertEquals(rs.getMetaData().getColumnType(1), expectedType); + conn.close(); + } + + private void testVarchar() throws SQLException { + deleteDb("oracle"); + Connection conn = getConnection("oracle;MODE=Oracle"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, V VARCHAR) AS VALUES (1, 'a')"); + PreparedStatement prep = conn.prepareStatement("UPDATE TEST SET V = ? WHERE ID = ?"); + prep.setInt(2, 1); + prep.setString(1, ""); + prep.executeUpdate(); + ResultSet rs = stat.executeQuery("SELECT V FROM TEST"); + assertTrue(rs.next()); + assertNull(rs.getString(1)); + assertFalse(rs.next()); + prep.setNString(1, ""); + prep.executeUpdate(); + Statement stat2 = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE); + rs = stat2.executeQuery("SELECT ID, V FROM TEST"); + assertTrue(rs.next()); + assertNull(rs.getString(2)); + rs.updateString(2, ""); + rs.updateRow(); + assertFalse(rs.next()); + rs = stat2.executeQuery("SELECT ID, V FROM TEST"); + assertTrue(rs.next()); + assertNull(rs.getString(2)); + rs.updateString("V", ""); + rs.updateRow(); + assertFalse(rs.next()); + rs = stat2.executeQuery("SELECT ID, V FROM TEST"); + assertTrue(rs.next()); + assertNull(rs.getString(2)); + rs.updateNString(2, ""); + rs.updateRow(); + assertFalse(rs.next()); + rs = stat2.executeQuery("SELECT ID, V FROM TEST"); + assertTrue(rs.next()); + assertNull(rs.getString(2)); + rs.updateNString("V", ""); + rs.updateRow(); + assertFalse(rs.next()); + rs = stat2.executeQuery("SELECT ID, V FROM TEST"); + assertTrue(rs.next()); + assertNull(rs.getString(2)); + rs.updateObject(2, ""); + rs.updateRow(); + assertFalse(rs.next()); + rs = stat2.executeQuery("SELECT ID, V FROM TEST"); + assertTrue(rs.next()); + assertNull(rs.getString(2)); + rs.updateObject("V", ""); + rs.updateRow(); + assertFalse(rs.next()); + rs = stat.executeQuery("SELECT V FROM TEST"); + assertTrue(rs.next()); + assertNull(rs.getString(1)); + assertFalse(rs.next()); + conn.close(); + } + + private void assertResultDate(String expected, Statement stat, String sql) + throws SQLException { + SimpleDateFormat iso8601 = new SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss"); + ResultSet rs = stat.executeQuery(sql); + if (rs.next()) { + assertEquals(expected, iso8601.format(rs.getTimestamp(1))); + } else { + assertEquals(expected, null); + } + } + + private void assertResult(Object[][] expectedRowsOfValues, Statement stat, + String sql) throws SQLException { + assertResult(newSimpleResultSet(expectedRowsOfValues), stat, sql); + } + + private void assertResult(ResultSet expected, Statement stat, String sql) + throws SQLException { + ResultSet actual = stat.executeQuery(sql); + int expectedColumnCount = expected.getMetaData().getColumnCount(); + assertEquals(expectedColumnCount, actual.getMetaData().getColumnCount()); + while (true) { + boolean expectedNext = expected.next(); + boolean actualNext = actual.next(); + if (!expectedNext && !actualNext) { + return; + } + if (expectedNext != actualNext) { + fail("number of rows in actual and expected results sets does not match"); + } + for (int i = 0; i < expectedColumnCount; i++) { + String expectedString = columnResultToString(expected.getObject(i + 1)); + String actualString = columnResultToString(actual.getObject(i + 1)); + assertEquals(expectedString, actualString); + } + } + } + + private static String columnResultToString(Object object) { + if (object == null) { + return null; + } + if (object instanceof Object[]) { + return Arrays.deepToString((Object[]) object); + } + if (object instanceof byte[]) { + return Arrays.toString((byte[]) object); + } + return object.toString(); + } + + private static SimpleResultSet newSimpleResultSet(Object[][] rowsOfValues) { + SimpleResultSet result = new SimpleResultSet(); + for (int i = 0; i < rowsOfValues[0].length; i++) { + result.addColumn(i + "", Types.JAVA_OBJECT, 0, 0); + } + for (int i = 0; i < rowsOfValues.length; i++) { + result.addRow(rowsOfValues[i]); + } + return result; + } + +} diff --git a/h2/src/test/org/h2/test/db/TestCompatibilitySQLServer.java b/h2/src/test/org/h2/test/db/TestCompatibilitySQLServer.java new file mode 100644 index 0000000..5d1fa24 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestCompatibilitySQLServer.java @@ -0,0 +1,85 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Test MSSQLServer compatibility mode. + */ +public class TestCompatibilitySQLServer extends TestDb { + + /** + * Run just this test. + * + * @param s ignored + */ + public static void main(String... s) throws Exception { + TestBase test = TestBase.createCaller().init(); + test.testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb("sqlserver"); + + final Connection conn = getConnection("sqlserver;MODE=MSSQLServer"); + try { + testDiscardTableHints(conn); + testPrimaryKeyIdentity(conn); + } finally { + conn.close(); + deleteDb("sqlserver"); + } + } + + private void testDiscardTableHints(Connection conn) throws SQLException { + final Statement stat = conn.createStatement(); + + stat.execute("create table parent(id int primary key, name varchar(255))"); + stat.execute("create table child(" + + "id int primary key, " + + "parent_id int, " + + "name varchar(255), " + + "foreign key (parent_id) references public.parent(id))"); + + stat.execute("select * from parent"); + stat.execute("select * from parent with(nolock)"); + stat.execute("select * from parent with(nolock, index = id)"); + stat.execute("select * from parent with(nolock, index(id, name))"); + + stat.execute("select * from parent p " + + "join child ch on ch.parent_id = p.id"); + stat.execute("select * from parent p with(nolock) " + + "join child ch with(nolock) on ch.parent_id = p.id"); + stat.execute("select * from parent p with(nolock) " + + "join child ch with(nolock, index = id) on ch.parent_id = p.id"); + stat.execute("select * from parent p with(nolock) " + + "join child ch with(nolock, index(id, name)) on ch.parent_id = p.id"); + } + + private void testPrimaryKeyIdentity(Connection conn) throws SQLException { + final Statement stat = conn.createStatement(); + + // IDENTITY after PRIMARY KEY is an undocumented syntax of MS SQL + stat.execute("create table test(id int primary key identity, expected_id int)"); + stat.execute("insert into test (expected_id) VALUES (1), (2), (3)"); + + final ResultSet results = stat.executeQuery("select * from test"); + while (results.next()) { + assertEquals(results.getInt("expected_id"), results.getInt("id")); + } + + stat.execute("create table test2 (id int primary key not null identity)"); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestCsv.java b/h2/src/test/org/h2/test/db/TestCsv.java new file mode 100644 index 0000000..3dc6b19 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestCsv.java @@ -0,0 +1,585 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import org.h2.api.ErrorCode; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.Csv; +import org.h2.util.IOUtils; +import org.h2.util.StringUtils; + +/** + * CSVREAD and CSVWRITE tests. + * + * @author Thomas Mueller + * @author Sylvain Cuaz (testNull) + */ +public class TestCsv extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); + test.config.traceTest = true; + test.testFromMain(); + } + + @Override + public void test() throws Exception { + testWriteColumnHeader(); + testCaseSensitiveColumnNames(); + testWriteResultSetDataType(); + testPreserveWhitespace(); + testChangeData(); + testOptions(); + testPseudoBom(); + testWriteRead(); + testColumnNames(); + testSpaceSeparated(); + testNull(); + testRandomData(); + testEmptyFieldDelimiter(); + testFieldDelimiter(); + testAsTable(); + testRead(); + testPipe(); + deleteDb("csv"); + } + + private void testWriteColumnHeader() throws Exception { + Connection conn = getConnection("csv"); + Statement stat = conn.createStatement(); + stat.execute("call csvwrite('" + getBaseDir() + + "/test.tsv', 'select x from system_range(1, 1)', 'writeColumnHeader=false')"); + String x = IOUtils.readStringAndClose(IOUtils.getReader( + FileUtils.newInputStream(getBaseDir() + "/test.tsv")), -1); + assertEquals("\"1\"", x.trim()); + stat.execute("call csvwrite('" + getBaseDir() + + "/test.tsv', 'select x from system_range(1, 1)', 'writeColumnHeader=true')"); + x = IOUtils.readStringAndClose(IOUtils.getReader( + FileUtils.newInputStream(getBaseDir() + "/test.tsv")), -1); + x = x.trim(); + assertTrue(x.startsWith("\"X\"")); + assertTrue(x.endsWith("\"1\"")); + conn.close(); + } + + + private void testWriteResultSetDataType() throws Exception { + // Oracle: ResultSet.getString on a date or time column returns a + // strange result (2009-6-30.16.17. 21. 996802000 according to a + // customer) + StringWriter writer = new StringWriter(); + Connection conn = getConnection("csv"); + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery( + "select timestamp '-100-01-01 12:00:00.0' ts, null n"); + Csv csv = new Csv(); + csv.setFieldDelimiter((char) 0); + csv.setLineSeparator(";"); + csv.write(writer, rs); + conn.close(); + assertEquals("TS,N;-0100-01-01 12:00:00,;", writer.toString()); + } + + private void testCaseSensitiveColumnNames() throws Exception { + OutputStream out = FileUtils.newOutputStream( + getBaseDir() + "/test.tsv", false); + out.write("lower,Mixed,UPPER\n 1 , 2, 3 \n".getBytes()); + out.close(); + Connection conn = getConnection("csv"); + Statement stat = conn.createStatement(); + ResultSet rs; + rs = stat.executeQuery("select * from csvread('" + + getBaseDir() + "/test.tsv')"); + rs.next(); + assertEquals("LOWER", rs.getMetaData().getColumnName(1)); + assertEquals("MIXED", rs.getMetaData().getColumnName(2)); + assertEquals("UPPER", rs.getMetaData().getColumnName(3)); + rs = stat.executeQuery("select * from csvread('" + + getBaseDir() + + "/test.tsv', null, 'caseSensitiveColumnNames=true')"); + rs.next(); + assertEquals("lower", rs.getMetaData().getColumnName(1)); + assertEquals("Mixed", rs.getMetaData().getColumnName(2)); + assertEquals("UPPER", rs.getMetaData().getColumnName(3)); + conn.close(); + } + + private void testPreserveWhitespace() throws Exception { + OutputStream out = FileUtils.newOutputStream( + getBaseDir() + "/test.tsv", false); + out.write("a,b\n 1 , 2 \n".getBytes()); + out.close(); + Connection conn = getConnection("csv"); + Statement stat = conn.createStatement(); + ResultSet rs; + rs = stat.executeQuery("select * from csvread('" + + getBaseDir() + "/test.tsv')"); + rs.next(); + assertEquals("1", rs.getString(1)); + assertEquals("2", rs.getString(2)); + rs = stat.executeQuery("select * from csvread('" + + getBaseDir() + "/test.tsv', null, 'preserveWhitespace=true')"); + rs.next(); + assertEquals(" 1 ", rs.getString(1)); + assertEquals(" 2 ", rs.getString(2)); + conn.close(); + } + + private void testChangeData() throws Exception { + OutputStream out = FileUtils.newOutputStream( + getBaseDir() + "/test.tsv", false); + out.write("a,b,c,d,e,f,g\n1".getBytes()); + out.close(); + Connection conn = getConnection("csv"); + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("select * from csvread('" + + getBaseDir() + "/test.tsv')"); + assertEquals(7, rs.getMetaData().getColumnCount()); + assertEquals("A", rs.getMetaData().getColumnLabel(1)); + rs.next(); + assertEquals(1, rs.getInt(1)); + out = FileUtils.newOutputStream(getBaseDir() + "/test.tsv", false); + out.write("x".getBytes()); + out.close(); + rs = stat.executeQuery("select * from csvread('" + + getBaseDir() + "/test.tsv')"); + assertEquals(1, rs.getMetaData().getColumnCount()); + assertEquals("X", rs.getMetaData().getColumnLabel(1)); + assertFalse(rs.next()); + conn.close(); + } + + private void testOptions() { + Csv csv = new Csv(); + assertEquals(",", csv.getFieldSeparatorWrite()); + assertEquals(System.lineSeparator(), csv.getLineSeparator()); + assertEquals("", csv.getNullString()); + assertEquals('\"', csv.getEscapeCharacter()); + assertEquals('"', csv.getFieldDelimiter()); + assertEquals(',', csv.getFieldSeparatorRead()); + assertEquals(",", csv.getFieldSeparatorWrite()); + assertEquals(0, csv.getLineCommentCharacter()); + assertEquals(false, csv.getPreserveWhitespace()); + + String charset; + + charset = csv.setOptions("escape=\\ fieldDelimiter=\\\\ fieldSeparator=\n " + + "lineComment=\" lineSeparator=\\ \\\\\\ "); + assertEquals(' ', csv.getEscapeCharacter()); + assertEquals('\\', csv.getFieldDelimiter()); + assertEquals('\n', csv.getFieldSeparatorRead()); + assertEquals("\n", csv.getFieldSeparatorWrite()); + assertEquals('"', csv.getLineCommentCharacter()); + assertEquals(" \\ ", csv.getLineSeparator()); + assertFalse(csv.getPreserveWhitespace()); + assertFalse(csv.getCaseSensitiveColumnNames()); + + charset = csv.setOptions("escape=1x fieldDelimiter=2x " + + "fieldSeparator=3x " + "lineComment=4x lineSeparator=5x " + + "null=6x charset=7x " + + "preserveWhitespace=true caseSensitiveColumnNames=true"); + assertEquals('1', csv.getEscapeCharacter()); + assertEquals('2', csv.getFieldDelimiter()); + assertEquals('3', csv.getFieldSeparatorRead()); + assertEquals("3x", csv.getFieldSeparatorWrite()); + assertEquals('4', csv.getLineCommentCharacter()); + assertEquals("5x", csv.getLineSeparator()); + assertEquals("6x", csv.getNullString()); + assertEquals("7x", charset); + assertTrue(csv.getPreserveWhitespace()); + assertTrue(csv.getCaseSensitiveColumnNames()); + + charset = csv.setOptions("escape= fieldDelimiter= " + + "fieldSeparator= " + "lineComment= lineSeparator=\r\n " + + "null=\0 charset="); + assertEquals(0, csv.getEscapeCharacter()); + assertEquals(0, csv.getFieldDelimiter()); + assertEquals(0, csv.getFieldSeparatorRead()); + assertEquals("", csv.getFieldSeparatorWrite()); + assertEquals(0, csv.getLineCommentCharacter()); + assertEquals("\r\n", csv.getLineSeparator()); + assertEquals("\0", csv.getNullString()); + assertEquals("", charset); + + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, () -> csv.setOptions("escape=a error=b")); + assertEquals('a', csv.getEscapeCharacter()); + } + + private void testPseudoBom() throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + // UTF-8 "BOM" / marker + out.write(StringUtils.convertHexToBytes("ef" + "bb" + "bf")); + out.write("\"ID\", \"NAME\"\n1, Hello".getBytes(StandardCharsets.UTF_8)); + byte[] buff = out.toByteArray(); + Reader r = new InputStreamReader(new ByteArrayInputStream(buff), StandardCharsets.UTF_8); + ResultSet rs = new Csv().read(r, null); + assertEquals("ID", rs.getMetaData().getColumnLabel(1)); + assertEquals("NAME", rs.getMetaData().getColumnLabel(2)); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + assertFalse(rs.next()); + } + + private void testColumnNames() throws Exception { + ResultSet rs; + rs = new Csv().read(new StringReader("Id,First Name,2x,_x2\n1,2,3"), null); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("First Name", rs.getMetaData().getColumnName(2)); + assertEquals("2x", rs.getMetaData().getColumnName(3)); + assertEquals("_X2", rs.getMetaData().getColumnName(4)); + + rs = new Csv().read(new StringReader("a,a\n1,2"), null); + assertEquals("A", rs.getMetaData().getColumnName(1)); + assertEquals("A1", rs.getMetaData().getColumnName(2)); + + rs = new Csv().read(new StringReader("1,2"), new String[] { "", null }); + assertEquals("C1", rs.getMetaData().getColumnName(1)); + assertEquals("C2", rs.getMetaData().getColumnName(2)); + } + + private void testSpaceSeparated() throws SQLException { + deleteDb("csv"); + File f = new File(getBaseDir() + "/testSpace.csv"); + FileUtils.delete(f.getAbsolutePath()); + + Connection conn = getConnection("csv"); + Statement stat = conn.createStatement(); + stat.execute("create temporary table test (a int, b int, c int)"); + stat.execute("insert into test values(1,2,3)"); + stat.execute("insert into test values(4,null,5)"); + stat.execute("call csvwrite('" + getBaseDir() + + "/test.tsv','select * from test',null,' ')"); + ResultSet rs1 = stat.executeQuery("select * from test"); + assertResultSetOrdered(rs1, new String[][] { + new String[] { "1", "2", "3" }, new String[] { "4", null, "5" } }); + ResultSet rs2 = stat.executeQuery("select * from csvread('" + + getBaseDir() + "/test.tsv',null,null,' ')"); + assertResultSetOrdered(rs2, new String[][] { + new String[] { "1", "2", "3" }, new String[] { "4", null, "5" } }); + conn.close(); + FileUtils.delete(f.getAbsolutePath()); + FileUtils.delete(getBaseDir() + "/test.tsv"); + } + + /** + * Test custom NULL string. + */ + private void testNull() throws Exception { + deleteDb("csv"); + + String fileName = getBaseDir() + "/testNull.csv"; + FileUtils.delete(fileName); + + OutputStream out = FileUtils.newOutputStream(fileName, false); + String csvContent = "\"A\",\"B\",\"C\",\"D\"\n\\N,\"\",\"\\N\","; + byte[] b = csvContent.getBytes(StandardCharsets.UTF_8); + out.write(b, 0, b.length); + out.close(); + Csv csv = new Csv(); + csv.setNullString("\\N"); + ResultSet rs = csv.read(fileName, null, "UTF8"); + ResultSetMetaData meta = rs.getMetaData(); + assertEquals(4, meta.getColumnCount()); + assertEquals("A", meta.getColumnLabel(1)); + assertEquals("B", meta.getColumnLabel(2)); + assertEquals("C", meta.getColumnLabel(3)); + assertEquals("D", meta.getColumnLabel(4)); + assertTrue(rs.next()); + assertEquals(null, rs.getString(1)); + assertEquals("", rs.getString(2)); + // null is never quoted + assertEquals("\\N", rs.getString(3)); + // an empty string is always parsed as null + assertEquals(null, rs.getString(4)); + assertFalse(rs.next()); + + Connection conn = getConnection("csv"); + Statement stat = conn.createStatement(); + stat.execute("call csvwrite('" + fileName + + "', 'select NULL as a, '''' as b, ''\\N'' as c, NULL as d', " + + "'UTF8', ',', '\"', NULL, '\\N', '\n')"); + InputStreamReader reader = new InputStreamReader( + FileUtils.newInputStream(fileName)); + // on read, an empty string is treated like null, + // but on write a null is always written with the nullString + String data = IOUtils.readStringAndClose(reader, -1); + assertEquals(csvContent + "\\N", data.trim()); + conn.close(); + + FileUtils.delete(fileName); + } + + private void testRandomData() throws SQLException { + deleteDb("csv"); + Connection conn = getConnection("csv"); + Statement stat = conn.createStatement(); + stat.execute("drop table if exists test"); + stat.execute("create table test(id identity, a varchar, b varchar)"); + int len = getSize(1000, 10000); + PreparedStatement prep = conn.prepareStatement( + "insert into test(a, b) values(?, ?)"); + ArrayList list = new ArrayList<>(len); + Random random = new Random(1); + for (int i = 0; i < len; i++) { + String a = randomData(random), b = randomData(random); + prep.setString(1, a); + prep.setString(2, b); + list.add(new String[] { a, b }); + prep.execute(); + } + stat.execute("call csvwrite('" + getBaseDir() + + "/test.csv', 'select a, b from test order by id', 'UTF-8', '|', '#')"); + Csv csv = new Csv(); + csv.setFieldSeparatorRead('|'); + csv.setFieldDelimiter('#'); + ResultSet rs = csv.read(getBaseDir() + "/test.csv", null, "UTF-8"); + for (int i = 0; i < len; i++) { + assertTrue(rs.next()); + String[] pair = list.get(i); + assertEquals(pair[0], rs.getString(1)); + assertEquals(pair[1], rs.getString(2)); + } + assertFalse(rs.next()); + conn.close(); + FileUtils.delete(getBaseDir() + "/test.csv"); + } + + private static String randomData(Random random) { + if (random.nextInt(10) == 1) { + return null; + } + int len = random.nextInt(5); + StringBuilder buff = new StringBuilder(); + String chars = "\\\'\",\r\n\t ;.-123456|#"; + for (int i = 0; i < len; i++) { + buff.append(chars.charAt(random.nextInt(chars.length()))); + } + return buff.toString(); + } + + private void testEmptyFieldDelimiter() throws Exception { + String fileName = getBaseDir() + "/test.csv"; + FileUtils.delete(fileName); + Connection conn = getConnection("csv"); + Statement stat = conn.createStatement(); + stat.execute("call csvwrite('" + fileName + + "', 'select 1 id, ''Hello'' name', null, '|', '', null, null, chr(10))"); + InputStreamReader reader = new InputStreamReader( + FileUtils.newInputStream(fileName)); + String text = IOUtils.readStringAndClose(reader, -1).trim(); + text = text.replace('\n', ' '); + assertEquals("ID|NAME 1|Hello", text); + ResultSet rs = stat.executeQuery("select * from csvread('" + + fileName + "', null, null, '|', '')"); + ResultSetMetaData meta = rs.getMetaData(); + assertEquals(2, meta.getColumnCount()); + assertEquals("ID", meta.getColumnLabel(1)); + assertEquals("NAME", meta.getColumnLabel(2)); + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals("Hello", rs.getString(2)); + assertFalse(rs.next()); + conn.close(); + FileUtils.delete(fileName); + } + + private void testFieldDelimiter() throws Exception { + String fileName = getBaseDir() + "/test.csv"; + String fileName2 = getBaseDir() + "/test2.csv"; + FileUtils.delete(fileName); + OutputStream out = FileUtils.newOutputStream(fileName, false); + byte[] b = "'A'; 'B'\n\'It\\'s nice\'; '\nHello\\*\n'".getBytes(); + out.write(b, 0, b.length); + out.close(); + Connection conn = getConnection("csv"); + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("select * from csvread('" + + fileName + "', null, null, ';', '''', '\\')"); + ResultSetMetaData meta = rs.getMetaData(); + assertEquals(2, meta.getColumnCount()); + assertEquals("A", meta.getColumnLabel(1)); + assertEquals("B", meta.getColumnLabel(2)); + assertTrue(rs.next()); + assertEquals("It's nice", rs.getString(1)); + assertEquals("\nHello*\n", rs.getString(2)); + assertFalse(rs.next()); + stat.execute("call csvwrite('" + fileName2 + + "', 'select * from csvread(''" + fileName + + "'', null, null, '';'', '''''''', ''\\'')', null, '+', '*', '#')"); + rs = stat.executeQuery("select * from csvread('" + fileName2 + + "', null, null, '+', '*', '#')"); + meta = rs.getMetaData(); + assertEquals(2, meta.getColumnCount()); + assertEquals("A", meta.getColumnLabel(1)); + assertEquals("B", meta.getColumnLabel(2)); + assertTrue(rs.next()); + assertEquals("It's nice", rs.getString(1)); + assertEquals("\nHello*\n", rs.getString(2)); + assertFalse(rs.next()); + conn.close(); + FileUtils.delete(fileName); + FileUtils.delete(fileName2); + } + + private void testPipe() throws SQLException { + deleteDb("csv"); + Connection conn = getConnection("csv"); + Statement stat = conn.createStatement(); + stat.execute("call csvwrite('" + getBaseDir() + + "/test.csv', 'select 1 id, ''Hello'' name', 'utf-8', '|')"); + ResultSet rs = stat.executeQuery("select * from csvread('" + + getBaseDir() + "/test.csv', null, 'utf-8', '|')"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + assertFalse(rs.next()); + new File(getBaseDir() + "/test.csv").delete(); + + // PreparedStatement prep = conn.prepareStatement("select * from + // csvread(?, null, ?, ?)"); + // prep.setString(1, BASE_DIR+"/test.csv"); + // prep.setString(2, "utf-8"); + // prep.setString(3, "|"); + // rs = prep.executeQuery(); + + conn.close(); + FileUtils.delete(getBaseDir() + "/test.csv"); + } + + private void testAsTable() throws SQLException { + deleteDb("csv"); + Connection conn = getConnection("csv"); + Statement stat = conn.createStatement(); + stat.execute("call csvwrite('" + getBaseDir() + + "/test.csv', 'select 1 id, ''Hello'' name')"); + ResultSet rs = stat.executeQuery("select name from csvread('" + + getBaseDir() + "/test.csv')"); + assertTrue(rs.next()); + assertEquals("Hello", rs.getString(1)); + assertFalse(rs.next()); + rs = stat.executeQuery("select * from csvread('" + getBaseDir() + "/test.csv')"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + assertFalse(rs.next()); + new File(getBaseDir() + "/test.csv").delete(); + conn.close(); + } + + private void testRead() throws Exception { + String fileName = getBaseDir() + "/test.csv"; + FileUtils.delete(fileName); + OutputStream out = FileUtils.newOutputStream(fileName, false); + byte[] b = ("a,b,c,d\n201,-2,0,18\n, \"abc\"\"\" ," + + ",\"\"\n 1 ,2 , 3, 4 \n5, 6, 7, 8").getBytes(); + out.write(b, 0, b.length); + out.close(); + ResultSet rs = new Csv().read(fileName, null, "UTF8"); + ResultSetMetaData meta = rs.getMetaData(); + assertEquals(4, meta.getColumnCount()); + assertEquals("A", meta.getColumnLabel(1)); + assertEquals("B", meta.getColumnLabel(2)); + assertEquals("C", meta.getColumnLabel(3)); + assertEquals("D", meta.getColumnLabel(4)); + assertTrue(rs.next()); + assertEquals("201", rs.getString(1)); + assertEquals("-2", rs.getString(2)); + assertEquals("0", rs.getString(3)); + assertEquals("18", rs.getString(4)); + assertTrue(rs.next()); + assertEquals(null, rs.getString(1)); + assertEquals("abc\"", rs.getString(2)); + assertEquals(null, rs.getString(3)); + assertEquals("", rs.getString(4)); + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals("2", rs.getString(2)); + assertEquals("3", rs.getString(3)); + assertEquals("4", rs.getString(4)); + assertTrue(rs.next()); + assertEquals("5", rs.getString(1)); + assertEquals("6", rs.getString(2)); + assertEquals("7", rs.getString(3)); + assertEquals("8", rs.getString(4)); + assertFalse(rs.next()); + + // a,b,c,d + // 201,-2,0,18 + // 201,2,0,18 + // 201,2,0,18 + // 201,2,0,18 + // 201,2,0,18 + // 201,2,0,18 + FileUtils.delete(fileName); + } + + private void testWriteRead() throws SQLException { + deleteDb("csv"); + Connection conn = getConnection("csv"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID IDENTITY, NAME VARCHAR)"); + // int len = 100000; + int len = 100; + for (int i = 0; i < len; i++) { + stat.execute("INSERT INTO TEST(NAME) VALUES('Ruebezahl')"); + } + long time; + time = System.nanoTime(); + new Csv().write(conn, getBaseDir() + "/testRW.csv", + "SELECT X ID, 'Ruebezahl' NAME FROM SYSTEM_RANGE(1, " + len + ")", "UTF8"); + trace("write: " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - time)); + ResultSet rs; + time = System.nanoTime(); + for (int i = 0; i < 30; i++) { + rs = new Csv().read(getBaseDir() + "/testRW.csv", null, "UTF8"); + while (rs.next()) { + // ignore + } + } + trace("read: " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - time)); + rs = new Csv().read(getBaseDir() + "/testRW.csv", null, "UTF8"); + // stat.execute("CREATE ALIAS CSVREAD FOR 'org.h2.tools.Csv.read'"); + ResultSetMetaData meta = rs.getMetaData(); + assertEquals(2, meta.getColumnCount()); + for (int i = 0; i < len; i++) { + rs.next(); + assertEquals("" + (i + 1), rs.getString("ID")); + assertEquals("Ruebezahl", rs.getString("NAME")); + } + assertFalse(rs.next()); + rs.close(); + conn.close(); + FileUtils.delete(getBaseDir() + "/testRW.csv"); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestDateStorage.java b/h2/src/test/org/h2/test/db/TestDateStorage.java new file mode 100644 index 0000000..98a7f05 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestDateStorage.java @@ -0,0 +1,228 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.SimpleTimeZone; +import java.util.TimeZone; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.test.unit.TestDate; +import org.h2.value.ValueTimestamp; + +/** + * Tests the date transfer and storage. + */ +public class TestDateStorage extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + deleteDb(getTestName()); + testDateTimeTimestampWithCalendar(); + testAllTimeZones(); + testCurrentTimeZone(); + } + + private void testDateTimeTimestampWithCalendar() throws SQLException { + Connection conn = getConnection(getTestName()); + Statement stat = conn.createStatement(); + stat.execute("create table ts(x timestamp primary key)"); + stat.execute("create table t(x time primary key)"); + stat.execute("create table d(x date)"); + Calendar utcCalendar = new GregorianCalendar(new SimpleTimeZone(0, "Z")); + stat.execute("SET TIME ZONE 'PST'"); + TimeZone old = TimeZone.getDefault(); + TimeZone.setDefault(TimeZone.getTimeZone("PST")); + try { + // 2010-03-14T02:15:00Z + Timestamp ts1 = Timestamp.valueOf("2010-03-13 18:15:00"); + Time t1 = new Time(ts1.getTime()); + Date d1 = new Date(ts1.getTime()); + // when converted to UTC, this is 03:15, which doesn't actually + // exist because of summer time change at that day + // 2010-03-14T03:15:00Z + Timestamp ts2 = Timestamp.valueOf("2010-03-13 19:15:00"); + Time t2 = new Time(ts2.getTime()); + Date d2 = new Date(ts2.getTime()); + PreparedStatement prep; + ResultSet rs; + prep = conn.prepareStatement("insert into ts values(?)"); + prep.setTimestamp(1, ts1, utcCalendar); + prep.execute(); + prep.setTimestamp(1, ts2, utcCalendar); + prep.execute(); + prep = conn.prepareStatement("insert into t values(?)"); + prep.setTime(1, t1, utcCalendar); + prep.execute(); + prep.setTime(1, t2, utcCalendar); + prep.execute(); + prep = conn.prepareStatement("insert into d values(?)"); + prep.setDate(1, d1, utcCalendar); + prep.execute(); + prep.setDate(1, d2, utcCalendar); + prep.execute(); + rs = stat.executeQuery("select * from ts order by x"); + rs.next(); + assertEquals("2010-03-14 02:15:00", + rs.getString(1)); + assertEquals("2010-03-13 18:15:00.0", + rs.getTimestamp(1, utcCalendar).toString()); + assertEquals("2010-03-14 03:15:00.0", + rs.getTimestamp(1).toString()); + assertEquals("2010-03-14 02:15:00", + rs.getString("x")); + assertEquals("2010-03-13 18:15:00.0", + rs.getTimestamp("x", utcCalendar).toString()); + assertEquals("2010-03-14 03:15:00.0", + rs.getTimestamp("x").toString()); + rs.next(); + assertEquals("2010-03-14 03:15:00", + rs.getString(1)); + assertEquals("2010-03-13 19:15:00.0", + rs.getTimestamp(1, utcCalendar).toString()); + assertEquals("2010-03-14 03:15:00.0", + rs.getTimestamp(1).toString()); + assertEquals("2010-03-14 03:15:00", + rs.getString("x")); + assertEquals("2010-03-13 19:15:00.0", + rs.getTimestamp("x", utcCalendar).toString()); + assertEquals("2010-03-14 03:15:00.0", + rs.getTimestamp("x").toString()); + rs = stat.executeQuery("select * from t order by x"); + rs.next(); + assertEquals("02:15:00", rs.getString(1)); + assertEquals("18:15:00", rs.getTime(1, utcCalendar).toString()); + assertEquals("02:15:00", rs.getTime(1).toString()); + assertEquals("02:15:00", rs.getString("x")); + assertEquals("18:15:00", rs.getTime("x", utcCalendar).toString()); + assertEquals("02:15:00", rs.getTime("x").toString()); + rs.next(); + assertEquals("03:15:00", rs.getString(1)); + assertEquals("19:15:00", rs.getTime(1, utcCalendar).toString()); + assertEquals("03:15:00", rs.getTime(1).toString()); + assertEquals("03:15:00", rs.getString("x")); + assertEquals("19:15:00", rs.getTime("x", utcCalendar).toString()); + assertEquals("03:15:00", rs.getTime("x").toString()); + rs = stat.executeQuery("select * from d order by x"); + rs.next(); + assertEquals("2010-03-14", rs.getString(1)); + assertEquals("2010-03-13", rs.getDate(1, utcCalendar).toString()); + assertEquals("2010-03-14", rs.getDate(1).toString()); + assertEquals("2010-03-14", rs.getString("x")); + assertEquals("2010-03-13", rs.getDate("x", utcCalendar).toString()); + assertEquals("2010-03-14", rs.getDate("x").toString()); + rs.next(); + assertEquals("2010-03-14", rs.getString(1)); + assertEquals("2010-03-13", rs.getDate(1, utcCalendar).toString()); + assertEquals("2010-03-14", rs.getDate(1).toString()); + assertEquals("2010-03-14", rs.getString("x")); + assertEquals("2010-03-13", rs.getDate("x", utcCalendar).toString()); + assertEquals("2010-03-14", rs.getDate("x").toString()); + } finally { + stat.execute("SET TIME ZONE LOCAL"); + TimeZone.setDefault(old); + } + stat.execute("drop table ts"); + stat.execute("drop table t"); + stat.execute("drop table d"); + conn.close(); + } + + private static void testCurrentTimeZone() { + for (int year = 1890; year < 2050; year += 3) { + for (int month = 1; month <= 12; month++) { + for (int day = 1; day < 29; day++) { + for (int hour = 0; hour < 24; hour++) { + test(year, month, day, hour); + } + } + } + } + } + + private static void test(int year, int month, int day, int hour) { + ValueTimestamp.parse(year + "-" + month + "-" + day + " " + hour + ":00:00", null); + } + + private void testAllTimeZones() throws SQLException { + Connection conn = getConnection(getTestName()); + TimeZone defaultTimeZone = TimeZone.getDefault(); + PreparedStatement prepTimeZone = conn.prepareStatement("SET TIME ZONE ?"); + PreparedStatement prep = conn.prepareStatement("CALL CAST(? AS DATE)"); + try { + ArrayList distinct = TestDate.getDistinctTimeZones(); + for (TimeZone tz : distinct) { + /* + * Some OpenJDKs have unusable timezones with negative DST that + * causes IAE in SimpleTimeZone(). + */ + if (tz.getID().startsWith("SystemV/")) { + if (tz.getDSTSavings() < 0) { + continue; + } + } + // println(tz.getID()); + prepTimeZone.setString(1, tz.getID()); + prepTimeZone.executeUpdate(); + TimeZone.setDefault(tz); + for (int d = 101; d < 129; d++) { + test(prep, d); + } + } + } finally { + TimeZone.setDefault(defaultTimeZone); + } + conn.close(); + deleteDb(getTestName()); + } + + private void test(PreparedStatement prep, int d) throws SQLException { + String s = "2040-10-" + ("" + d).substring(1); + // some dates don't work in some versions of Java + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6772689 + java.sql.Date date = java.sql.Date.valueOf(s); + long time = date.getTime(); + while (true) { + date = new java.sql.Date(time); + String x = date.toString(); + if (x.equals(s)) { + break; + } + time += 1000; + } + if (!date.toString().equals(s)) { + println(TimeZone.getDefault().getID() + " " + s + " <> " + date.toString()); + return; + } + prep.setString(1, s); + ResultSet rs = prep.executeQuery(); + rs.next(); + String t = rs.getString(1); + if (!s.equals(t)) { + assertEquals(TimeZone.getDefault().getID(), s, t); + } + } + +} diff --git a/h2/src/test/org/h2/test/db/TestDeadlock.java b/h2/src/test/org/h2/test/db/TestDeadlock.java new file mode 100644 index 0000000..03d5b5c --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestDeadlock.java @@ -0,0 +1,246 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.io.Reader; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.TimeUnit; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.Task; + +/** + * Test for deadlocks in the code, and test the deadlock detection mechanism. + */ +public class TestDeadlock extends TestDb { + + /** + * The first connection. + */ + Connection c1; + + /** + * The second connection. + */ + Connection c2; + + /** + * The third connection. + */ + Connection c3; + private volatile SQLException lastException; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb("deadlock"); + testTemporaryTablesAndMetaDataLocking(); + testDeadlockInFulltextSearch(); + testConcurrentLobReadAndTempResultTableDelete(); + testNoDeadlock(); + deleteDb("deadlock"); + } + + private void testDeadlockInFulltextSearch() throws SQLException { + deleteDb("deadlock"); + String url = "deadlock"; + Connection conn, conn2; + conn = getConnection(url); + conn2 = getConnection(url); + final Statement stat = conn.createStatement(); + Statement stat2 = conn2.createStatement(); + stat.execute("create alias if not exists ft_init for " + + "\"org.h2.fulltext.FullText.init\""); + stat.execute("call ft_init()"); + stat.execute("create table test(id int primary key, name varchar)"); + stat.execute("call ft_create_index('PUBLIC', 'TEST', null)"); + Task t = new Task() { + @Override + public void call() throws Exception { + while (!stop) { + stat.executeQuery("select * from test"); + } + } + }; + t.execute(); + long start = System.nanoTime(); + while (System.nanoTime() - start < TimeUnit.SECONDS.toNanos(1)) { + stat2.execute("insert into test values(1, 'Hello')"); + stat2.execute("delete from test"); + } + t.get(); + conn2.close(); + conn.close(); + conn = getConnection(url); + conn.createStatement().execute("drop all objects"); + conn.close(); + } + + private void testConcurrentLobReadAndTempResultTableDelete() throws Exception { + deleteDb("deadlock"); + String url = "deadlock;MAX_MEMORY_ROWS=10"; + Connection conn, conn2; + Statement stat2; + conn = getConnection(url); + conn2 = getConnection(url); + final Statement stat = conn.createStatement(); + stat2 = conn2.createStatement(); + stat.execute("create table test(id int primary key, name varchar) as " + + "select x, 'Hello' from system_range(1,20)"); + stat2.execute("create table test_clob(id int primary key, data clob) as " + + "select 1, space(10000)"); + ResultSet rs2 = stat2.executeQuery("select * from test_clob"); + rs2.next(); + Task t = new Task() { + @Override + public void call() throws Exception { + while (!stop) { + stat.execute("select * from (select distinct id from test)"); + } + } + }; + t.execute(); + long start = System.nanoTime(); + while (System.nanoTime() - start < TimeUnit.SECONDS.toNanos(1)) { + Reader r = rs2.getCharacterStream(2); + char[] buff = new char[1024]; + while (true) { + int x = r.read(buff); + if (x < 0) { + break; + } + } + } + t.get(); + stat.execute("drop all objects"); + conn.close(); + conn2.close(); + } + + private void initTest() throws SQLException { + c1 = getConnection("deadlock"); + c2 = getConnection("deadlock"); + c3 = getConnection("deadlock"); + c1.createStatement().execute("SET LOCK_TIMEOUT 1000"); + c2.createStatement().execute("SET LOCK_TIMEOUT 1000"); + c3.createStatement().execute("SET LOCK_TIMEOUT 1000"); + c1.setAutoCommit(false); + c2.setAutoCommit(false); + c3.setAutoCommit(false); + lastException = null; + } + + private void end() throws SQLException { + c1.close(); + c2.close(); + c3.close(); + } + + /** + * This class wraps exception handling to simplify creating small threads + * that execute a statement. + */ + abstract class DoIt extends Thread { + + /** + * The operation to execute. + */ + abstract void execute() throws SQLException; + + @Override + public void run() { + try { + execute(); + } catch (SQLException e) { + catchDeadlock(e); + } + } + } + + /** + * Add the exception to the list of exceptions. + * + * @param e the exception + */ + void catchDeadlock(SQLException e) { + if (lastException != null) { + lastException.setNextException(e); + } else { + lastException = e; + } + } + + private void testNoDeadlock() throws Exception { + initTest(); + c1.createStatement().execute("CREATE TABLE TEST_A(ID INT PRIMARY KEY)"); + c1.createStatement().execute("CREATE TABLE TEST_B(ID INT PRIMARY KEY)"); + c1.createStatement().execute("CREATE TABLE TEST_C(ID INT PRIMARY KEY)"); + c1.commit(); + c1.createStatement().execute("INSERT INTO TEST_A VALUES(1)"); + c2.createStatement().execute("INSERT INTO TEST_B VALUES(1)"); + c3.createStatement().execute("INSERT INTO TEST_C VALUES(1)"); + DoIt t2 = new DoIt() { + @Override + public void execute() throws SQLException { + c1.createStatement().execute("DELETE FROM TEST_B"); + c1.commit(); + } + }; + t2.start(); + DoIt t3 = new DoIt() { + @Override + public void execute() throws SQLException { + c2.createStatement().execute("DELETE FROM TEST_C"); + c2.commit(); + } + }; + t3.start(); + Thread.sleep(500); + try { + c3.createStatement().execute("DELETE FROM TEST_C"); + c3.commit(); + } catch (SQLException e) { + catchDeadlock(e); + } + t2.join(); + t3.join(); + if (lastException != null) { + throw lastException; + } + c1.commit(); + c2.commit(); + c3.commit(); + c1.createStatement().execute("DROP TABLE TEST_A, TEST_B, TEST_C"); + end(); + + } + + + // there was a bug in the meta data locking here + private void testTemporaryTablesAndMetaDataLocking() throws Exception { + deleteDb("deadlock"); + Connection conn = getConnection("deadlock"); + Statement stmt = conn.createStatement(); + conn.setAutoCommit(false); + stmt.execute("CREATE SEQUENCE IF NOT EXISTS SEQ1 START WITH 1000000"); + stmt.execute("CREATE FORCE VIEW V1 AS WITH RECURSIVE TEMP(X) AS " + + "(SELECT x FROM DUAL) SELECT * FROM TEMP"); + stmt.executeQuery("SELECT NEXT VALUE FOR SEQ1"); + conn.close(); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestDuplicateKeyUpdate.java b/h2/src/test/org/h2/test/db/TestDuplicateKeyUpdate.java new file mode 100644 index 0000000..8415793 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestDuplicateKeyUpdate.java @@ -0,0 +1,314 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests for the ON DUPLICATE KEY UPDATE in the Insert class. + */ +public class TestDuplicateKeyUpdate extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + deleteDb("duplicateKeyUpdate"); + Connection conn = getConnection("duplicateKeyUpdate;MODE=MySQL"); + testDuplicateOnPrimary(conn); + testDuplicateOnUnique(conn); + testDuplicateCache(conn); + testDuplicateExpression(conn); + testOnDuplicateKeyInsertBatch(conn); + testOnDuplicateKeyInsertMultiValue(conn); + testPrimaryKeyAndUniqueKey(conn); + testUpdateCountAndQualifiedNames(conn); + testEnum(conn); + conn.close(); + deleteDb("duplicateKeyUpdate"); + } + + private void testDuplicateOnPrimary(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + ResultSet rs; + + stat.execute("CREATE TABLE table_test (\n" + + " id bigint(20) NOT NULL ,\n" + + " a_text varchar(254) NOT NULL,\n" + + " some_text varchar(254) NULL,\n" + + " PRIMARY KEY (id)\n" + + ") ;"); + + stat.execute("INSERT INTO table_test ( id, a_text, some_text ) VALUES " + + "(1, 'aaaaaaaaaa', 'aaaaaaaaaa'), " + + "(2, 'bbbbbbbbbb', 'bbbbbbbbbb'), "+ + "(3, 'cccccccccc', 'cccccccccc'), " + + "(4, 'dddddddddd', 'dddddddddd'), " + + "(5, 'eeeeeeeeee', 'eeeeeeeeee')"); + + stat.execute("INSERT INTO table_test ( id , a_text, some_text ) " + + "VALUES (1, 'zzzzzzzzzz', 'abcdefghij') " + + "ON DUPLICATE KEY UPDATE some_text='UPDATE'"); + + rs = stat.executeQuery("SELECT some_text FROM table_test where id = 1"); + rs.next(); + assertEquals("UPDATE", rs.getNString(1)); + + stat.execute("INSERT INTO table_test ( id , a_text, some_text ) " + + "VALUES (3, 'zzzzzzzzzz', 'SOME TEXT') " + + "ON DUPLICATE KEY UPDATE some_text=values(some_text)"); + rs = stat.executeQuery("SELECT some_text FROM table_test where id = 3"); + rs.next(); + assertEquals("SOME TEXT", rs.getNString(1)); + } + + private void testDuplicateOnUnique(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + ResultSet rs; + + stat.execute("CREATE TABLE table_test2 (\n" + + " id bigint(20) NOT NULL AUTO_INCREMENT,\n" + + " a_text varchar(254) NOT NULL,\n" + + " some_text varchar(254) NOT NULL,\n" + + " updatable_text varchar(254) NULL,\n" + + " PRIMARY KEY (id)\n" + ") ;"); + + stat.execute("CREATE UNIQUE INDEX index_name \n" + + "ON table_test2 (a_text, some_text);"); + + stat.execute("INSERT INTO table_test2 " + + "( a_text, some_text, updatable_text ) VALUES ('a', 'a', '1')"); + stat.execute("INSERT INTO table_test2 " + + "( a_text, some_text, updatable_text ) VALUES ('b', 'b', '2')"); + stat.execute("INSERT INTO table_test2 " + + "( a_text, some_text, updatable_text ) VALUES ('c', 'c', '3')"); + stat.execute("INSERT INTO table_test2 " + + "( a_text, some_text, updatable_text ) VALUES ('d', 'd', '4')"); + stat.execute("INSERT INTO table_test2 " + + "( a_text, some_text, updatable_text ) VALUES ('e', 'e', '5')"); + + stat.execute("INSERT INTO table_test2 ( a_text, some_text ) " + + "VALUES ('e', 'e') ON DUPLICATE KEY UPDATE updatable_text='UPDATE'"); + + rs = stat.executeQuery("SELECT updatable_text " + + "FROM table_test2 where a_text = 'e'"); + rs.next(); + assertEquals("UPDATE", rs.getNString(1)); + + stat.execute("INSERT INTO table_test2 (a_text, some_text, updatable_text ) " + + "VALUES ('b', 'b', 'test'), ('c', 'c', 'test2') " + + "ON DUPLICATE KEY UPDATE updatable_text=values(updatable_text)"); + rs = stat.executeQuery("SELECT updatable_text " + + "FROM table_test2 where a_text in ('b', 'c') order by a_text"); + rs.next(); + assertEquals("test", rs.getNString(1)); + rs.next(); + assertEquals("test2", rs.getNString(1)); + } + + private void testDuplicateCache(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + ResultSet rs; + + stat.execute("CREATE TABLE table_test3 (\n" + + " id bigint(20) NOT NULL ,\n" + + " a_text varchar(254) NOT NULL,\n" + + " some_text varchar(254) NULL,\n" + + " PRIMARY KEY (id)\n" + + ") ;"); + + stat.execute("INSERT INTO table_test3 ( id, a_text, some_text ) " + + "VALUES (1, 'aaaaaaaaaa', 'aaaaaaaaaa')"); + + stat.execute("INSERT INTO table_test3 ( id , a_text, some_text ) " + + "VALUES (1, 'zzzzzzzzzz', 'SOME TEXT') " + + "ON DUPLICATE KEY UPDATE some_text=values(some_text)"); + rs = stat.executeQuery("SELECT some_text FROM table_test3 where id = 1"); + rs.next(); + assertEquals("SOME TEXT", rs.getNString(1)); + + // Execute twice the same query to use the one from cache without + // parsing, caused the values parameter to be seen as ambiguous + stat.execute("INSERT INTO table_test3 ( id , a_text, some_text ) " + + "VALUES (1, 'zzzzzzzzzz', 'SOME TEXT') " + + "ON DUPLICATE KEY UPDATE some_text=values(some_text)"); + rs = stat.executeQuery("SELECT some_text FROM table_test3 where id = 1"); + rs.next(); + assertEquals("SOME TEXT", rs.getNString(1)); + } + + private void testDuplicateExpression(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + ResultSet rs; + + stat.execute("CREATE TABLE table_test4 (\n" + + " id bigint(20) NOT NULL ,\n" + + " a_text varchar(254) NOT NULL,\n" + + " some_value int(10) NULL,\n" + + " PRIMARY KEY (id)\n" + + ") ;"); + + stat.execute("INSERT INTO table_test4 ( id, a_text, some_value ) " + + "VALUES (1, 'aaaaaaaaaa', 5)"); + stat.execute("INSERT INTO table_test4 ( id, a_text, some_value ) " + + "VALUES (2, 'aaaaaaaaaa', 5)"); + + stat.execute("INSERT INTO table_test4 ( id , a_text, some_value ) " + + "VALUES (1, 'b', 1) " + + "ON DUPLICATE KEY UPDATE some_value=some_value + values(some_value)"); + stat.execute("INSERT INTO table_test4 ( id , a_text, some_value ) " + + "VALUES (1, 'b', 1) " + + "ON DUPLICATE KEY UPDATE some_value=some_value + 100"); + stat.execute("INSERT INTO table_test4 ( id , a_text, some_value ) " + + "VALUES (2, 'b', 1) " + + "ON DUPLICATE KEY UPDATE some_value=values(some_value) + 1"); + rs = stat.executeQuery("SELECT some_value FROM table_test4 where id = 1"); + rs.next(); + assertEquals(106, rs.getInt(1)); + rs = stat.executeQuery( + "SELECT some_value FROM table_test4 where id = 2"); + rs.next(); + assertEquals(2, rs.getInt(1)); + } + + private void testOnDuplicateKeyInsertBatch(Connection conn) + throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("create table test " + + "(id varchar(1) primary key, count int not null)"); + + // Insert multiple values as a batch + for (int i = 0; i <= 2; ++i) { + PreparedStatement prep = conn.prepareStatement( + "insert into test(id, count) values(?, ?) " + + "on duplicate key update count = count + 1"); + prep.setString(1, "a"); + prep.setInt(2, 1); + prep.addBatch(); + prep.setString(1, "b"); + prep.setInt(2, 1); + prep.addBatch(); + prep.setString(1, "b"); + prep.setInt(2, 1); + prep.addBatch(); + prep.executeBatch(); + } + + // Check result + ResultSet rs = stat.executeQuery( + "select count from test where id = 'a'"); + rs.next(); + assertEquals(3, rs.getInt(1)); + + stat.execute("drop table test"); + } + + private void testOnDuplicateKeyInsertMultiValue(Connection conn) + throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("create table test" + + "(id varchar(1) primary key, count int not null)"); + + // Insert multiple values in single insert operation + for (int i = 0; i <= 2; ++i) { + PreparedStatement prep = conn.prepareStatement( + "insert into test(id, count) values(?, ?), (?, ?), (?, ?) " + + "on duplicate key update count = count + 1"); + prep.setString(1, "a"); + prep.setInt(2, 1); + prep.setString(3, "b"); + prep.setInt(4, 1); + prep.setString(5, "b"); + prep.setInt(6, 1); + prep.executeUpdate(); + } + conn.commit(); + + // Check result + ResultSet rs = stat.executeQuery("select count from test where id = 'a'"); + rs.next(); + assertEquals(3, rs.getInt(1)); + + stat.execute("drop table test"); + } + + private void testPrimaryKeyAndUniqueKey(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE test (id INT, dup INT, " + + "counter INT, PRIMARY KEY(id), UNIQUE(dup))"); + stat.execute("INSERT INTO test (id, dup, counter) VALUES (1, 1, 1)"); + stat.execute("INSERT INTO test (id, dup, counter) VALUES (2, 1, 1) " + + "ON DUPLICATE KEY UPDATE counter = counter + VALUES(counter)"); + + // Check result + ResultSet rs = stat.executeQuery("SELECT counter FROM test ORDER BY id"); + rs.next(); + assertEquals(2, rs.getInt(1)); + assertEquals(false, rs.next()); + + stat.execute("drop table test"); + } + + private void testUpdateCountAndQualifiedNames(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("set mode mysql"); + stat.execute("create schema s2"); + stat.execute("create table s2.test(id int primary key, name varchar(255))"); + stat.execute("insert into s2.test(id, name) values(1, 'a')"); + assertEquals(2, stat.executeUpdate("insert into s2.test(id, name) values(1, 'b') " + + "on duplicate key update name = values(name)")); + assertEquals(0, stat.executeUpdate("insert into s2.test(id, name) values(1, 'b') " + + "on duplicate key update name = values(name)")); + assertEquals(1, stat.executeUpdate("insert into s2.test(id, name) values(2, 'c') " + + "on duplicate key update name = values(name)")); + ResultSet rs = stat.executeQuery("select id, name from s2.test order by id"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals("b", rs.getString(2)); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertEquals("c", rs.getString(2)); + assertFalse(rs.next()); + // Check qualified names in ON UPDATE case + assertEquals(2, stat.executeUpdate("insert into s2.test(id, name) values(2, 'd') " + + "on duplicate key update test.name = values(name)")); + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, stat) + .executeUpdate("insert into s2.test(id, name) values(2, 'd') " + + "on duplicate key update test2.name = values(name)"); + assertEquals(2, stat.executeUpdate("insert into s2.test(id, name) values(2, 'e') " + + "on duplicate key update s2.test.name = values(name)")); + assertThrows(ErrorCode.SCHEMA_NAME_MUST_MATCH, stat) + .executeUpdate("insert into s2.test(id, name) values(2, 'd') " + + "on duplicate key update s3.test.name = values(name)"); + stat.execute("drop schema s2 cascade"); + } + + private void testEnum(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("create table test(e enum('a', 'b') unique)"); + PreparedStatement ps = conn.prepareStatement("insert into test(e) values (?) on duplicate key update e = e"); + ps.setString(1, "a"); + assertEquals(1, ps.executeUpdate()); + assertEquals(0, ps.executeUpdate()); + stat.execute("drop table test"); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestEncryptedDb.java b/h2/src/test/org/h2/test/db/TestEncryptedDb.java new file mode 100644 index 0000000..de2f8fa --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestEncryptedDb.java @@ -0,0 +1,67 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Test using an encrypted database. + */ +public class TestEncryptedDb extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + if (config.memory || config.cipher != null) { + return false; + } + return true; + } + + @Override + public void test() throws SQLException { + deleteDb("encrypted"); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, + () -> getConnection("encrypted;CIPHER=AES;PAGE_SIZE=2048", "sa", "1234 1234")); + try (Connection conn = getConnection("encrypted;CIPHER=AES", "sa", "123 123")) { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT)"); + stat.execute("CHECKPOINT"); + stat.execute("SET WRITE_DELAY 0"); + stat.execute("INSERT INTO TEST VALUES(1)"); + stat.execute("SHUTDOWN IMMEDIATELY"); + } + + assertThrows(ErrorCode.FILE_ENCRYPTION_ERROR_1, // + () -> getConnection("encrypted;CIPHER=AES", "sa", "1234 1234")); + + try (Connection conn = getConnection("encrypted;CIPHER=AES", "sa", "123 123")) { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("SELECT * FROM TEST"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertFalse(rs.next()); + } +// conn.close(); + deleteDb("encrypted"); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestExclusive.java b/h2/src/test/org/h2/test/db/TestExclusive.java new file mode 100644 index 0000000..0fb4c2c --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestExclusive.java @@ -0,0 +1,126 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.atomic.AtomicInteger; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.Task; + +/** + * Test for the exclusive mode. + */ +public class TestExclusive extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testSetExclusiveTrueFalse(); + testSetExclusiveGetExclusive(); + } + + private void testSetExclusiveTrueFalse() throws Exception { + deleteDb("exclusive"); + Connection conn = getConnection("exclusive"); + Statement stat = conn.createStatement(); + stat.execute("set exclusive true"); + assertThrows(ErrorCode.DATABASE_IS_IN_EXCLUSIVE_MODE, () -> getConnection("exclusive")); + + stat.execute("set exclusive false"); + Connection conn2 = getConnection("exclusive"); + final Statement stat2 = conn2.createStatement(); + stat.execute("set exclusive true"); + final AtomicInteger state = new AtomicInteger(); + Task task = new Task() { + @Override + public void call() throws SQLException { + stat2.execute("select * from dual"); + if (state.get() != 1) { + new Error("unexpected state: " + state.get()).printStackTrace(); + } + } + }; + task.execute(); + state.set(1); + stat.execute("set exclusive false"); + task.get(); + stat.execute("set exclusive true"); + conn.close(); + + // check that exclusive mode is off when disconnected + stat2.execute("select * from dual"); + conn2.close(); + deleteDb("exclusive"); + } + + private void testSetExclusiveGetExclusive() throws SQLException { + deleteDb("exclusive"); + try (Connection connection = getConnection("exclusive")) { + assertFalse(getExclusiveMode(connection)); + + setExclusiveMode(connection, 1); + assertTrue(getExclusiveMode(connection)); + + setExclusiveMode(connection, 0); + assertFalse(getExclusiveMode(connection)); + + // Setting to existing mode should not throws exception + setExclusiveMode(connection, 0); + assertFalse(getExclusiveMode(connection)); + + setExclusiveMode(connection, 1); + assertTrue(getExclusiveMode(connection)); + + // Setting to existing mode throws exception + setExclusiveMode(connection, 1); + assertTrue(getExclusiveMode(connection)); + + setExclusiveMode(connection, 2); + assertTrue(getExclusiveMode(connection)); + + setExclusiveMode(connection, 0); + assertFalse(getExclusiveMode(connection)); + } + } + + + private static void setExclusiveMode(Connection connection, int exclusiveMode) throws SQLException { + String sql = "SET EXCLUSIVE " + exclusiveMode; + + try (PreparedStatement statement = connection.prepareStatement(sql)) { + statement.execute(); + } + } + + private static boolean getExclusiveMode(Connection connection) throws SQLException{ + boolean exclusiveMode = false; + + String sql = "SELECT SETTING_VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME = 'EXCLUSIVE'"; + try (PreparedStatement statement = connection.prepareStatement(sql)) { + ResultSet result = statement.executeQuery(); + if (result.next()) { + exclusiveMode = result.getBoolean(1); + } + } + + return exclusiveMode; + } +} diff --git a/h2/src/test/org/h2/test/db/TestFullText.java b/h2/src/test/org/h2/test/db/TestFullText.java new file mode 100644 index 0000000..0e7da44 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestFullText.java @@ -0,0 +1,639 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.io.Reader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Random; +import java.util.StringTokenizer; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import org.h2.fulltext.FullText; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.IOUtils; +import org.h2.util.Task; + +/** + * Fulltext search tests. + */ +public class TestFullText extends TestDb { + + /** + * The words used in this test. + */ + static final String[] KNOWN_WORDS = { "skiing", "balance", "storage", + "water", "train" }; + private static final String LUCENE_FULLTEXT_CLASS_NAME = + "org.h2.fulltext.FullTextLucene"; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testUuidPrimaryKey(false); + testAutoAnalyze(); + testNativeFeatures(); + testTransaction(false); + testCreateDropNative(); + testStreamLob(); + test(false, "VARCHAR"); + test(false, "CLOB"); + testPerformance(false); + testReopen(false); + testDropIndex(false); + if (!config.reopen) { + try { + Class.forName(LUCENE_FULLTEXT_CLASS_NAME); + testCreateDropLucene(); + testUuidPrimaryKey(true); + testMultiThreaded(true); + testMultiThreaded(false); + testTransaction(true); + test(true, "VARCHAR"); + test(true, "CLOB"); + testPerformance(true); + testReopen(true); + testDropIndex(true); + } catch (ClassNotFoundException | NoClassDefFoundError e) { + println("Class not found, not tested: " + LUCENE_FULLTEXT_CLASS_NAME); + // ok + } + FullText.closeAll(); + } + deleteDb("fullText"); + deleteDb("fullTextReopen"); + } + + private static void close(Collection list) { + for (Connection conn : list) { + IOUtils.closeSilently(conn); + } + } + + private Connection getConnection(String name, Collection list) + throws SQLException { + Connection conn = getConnection(name + ";MODE=STRICT"); + list.add(conn); + return conn; + } + + private void testAutoAnalyze() throws SQLException { + deleteDb("fullTextNative"); + Connection conn; + Statement stat; + + ArrayList connList = new ArrayList<>(); + + conn = getConnection("fullTextNative", connList); + stat = conn.createStatement(); + stat.execute("create alias if not exists ft_init for 'org.h2.fulltext.FullText.init'"); + stat.execute("call ft_init()"); + stat.execute("create table test(id int primary key, name varchar)"); + stat.execute("call ft_create_index('PUBLIC', 'TEST', 'NAME')"); + + if (!config.memory) { + conn.close(); + } + + conn = getConnection("fullTextNative", connList); + stat = conn.createStatement(); + stat.execute("insert into test select x, 'x' from system_range(1, 3000)"); + close(connList); + } + + private void testNativeFeatures() throws SQLException { + deleteDb("fullTextNative"); + ArrayList connList = new ArrayList<>(); + Connection conn = getConnection("fullTextNative", connList); + Statement stat = conn.createStatement(); + stat.execute("CREATE ALIAS IF NOT EXISTS FT_INIT FOR 'org.h2.fulltext.FullText.init'"); + stat.execute("CALL FT_INIT()"); + FullText.setIgnoreList(conn, "to,this"); + FullText.setWhitespaceChars(conn, " ,.-"); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + stat.execute("INSERT INTO TEST VALUES(1, 'Welcome to this world, One_Word')"); + stat.execute("CALL FT_CREATE_INDEX('PUBLIC', 'TEST', NULL)"); + ResultSet rs; + rs = stat.executeQuery("SELECT * FROM FT_SEARCH('Welcome', 0, 0)"); + assertTrue(rs.next()); + assertEquals("QUERY", rs.getMetaData().getColumnLabel(1)); + assertEquals("SCORE", rs.getMetaData().getColumnLabel(2)); + assertEquals("\"PUBLIC\".\"TEST\" WHERE \"ID\"=1", rs.getString(1)); + assertEquals("1.0", rs.getString(2)); + rs = stat.executeQuery("SELECT * FROM FT_SEARCH_DATA('One', 0, 0)"); + assertFalse(rs.next()); + rs = stat.executeQuery("SELECT * FROM FT_SEARCH_DATA('One_Word', 0, 0)"); + assertTrue(rs.next()); + rs = stat.executeQuery("SELECT * FROM FT_SEARCH_DATA('Welcome', 0, 0)"); + assertTrue(rs.next()); + assertEquals("SCHEMA", rs.getMetaData().getColumnLabel(1)); + assertEquals("TABLE", rs.getMetaData().getColumnLabel(2)); + assertEquals("COLUMNS", rs.getMetaData().getColumnLabel(3)); + assertEquals("KEYS", rs.getMetaData().getColumnLabel(4)); + assertEquals("PUBLIC", rs.getString(1)); + assertEquals("TEST", rs.getString(2)); + assertEquals("[ID]", rs.getString(3)); + assertEquals("[1]", rs.getString(4)); + + rs = stat.executeQuery("SELECT * FROM FT_SEARCH('this', 0, 0)"); + assertFalse(rs.next()); + + if (!config.memory) { + conn.close(); + } + conn = getConnection("fullTextNative", connList); + stat = conn.createStatement(); + conn.setAutoCommit(false); + rs = stat.executeQuery("SELECT * FROM FT_SEARCH_DATA('Welcome', 0, 0)"); + assertTrue(rs.next()); + stat.execute("delete from test"); + rs = stat.executeQuery("SELECT * FROM FT_SEARCH_DATA('Welcome', 0, 0)"); + assertFalse(rs.next()); + conn.rollback(); + rs = stat.executeQuery("SELECT * FROM FT_SEARCH_DATA('Welcome', 0, 0)"); + assertTrue(rs.next()); + conn.setAutoCommit(true); + close(connList); + } + + private void testUuidPrimaryKey(boolean lucene) throws SQLException { + deleteDb("fullText"); + Connection conn = getConnection("fullText"); + Statement stat = conn.createStatement(); + String prefix = lucene ? "FTL" : "FT"; + initFullText(stat, lucene); + stat.execute("CREATE TABLE TEST(ID UUID PRIMARY KEY, NAME VARCHAR)"); + String id = UUID.randomUUID().toString(); + stat.execute("INSERT INTO TEST VALUES('" + id + "', 'Hello World')"); + stat.execute("CALL " + prefix + "_CREATE_INDEX('PUBLIC', 'TEST', 'NAME')"); + ResultSet rs = stat.executeQuery("SELECT * FROM " + + prefix + "_SEARCH('Hello', 0, 0)"); + assertTrue(rs.next()); + stat.execute("UPDATE TEST SET NAME=NULL WHERE ID='" + id + "'"); + rs = stat.executeQuery("SELECT * FROM " + + prefix + "_SEARCH('Hello', 0, 0)"); + assertFalse(rs.next()); + stat.execute("UPDATE TEST SET NAME='Good Bye' WHERE ID='" + id + "'"); + rs = stat.executeQuery("SELECT * FROM " + + prefix + "_SEARCH('bye', 0, 0)"); + assertTrue(rs.next()); + FullText.dropAll(conn); + conn.close(); + deleteDb("fullText"); + } + + private void testTransaction(boolean lucene) throws SQLException { + String prefix = lucene ? "FTL" : "FT"; + deleteDb("fullTextTransaction"); + FileUtils.deleteRecursive(getBaseDir() + "/fullTextTransaction", false); + ArrayList connList = new ArrayList<>(); + Connection conn = getConnection("fullTextTransaction", connList); + Statement stat = conn.createStatement(); + initFullText(stat, lucene); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + stat.execute("INSERT INTO TEST VALUES(1, 'Hello World')"); + stat.execute("CALL " + prefix + "_CREATE_INDEX('PUBLIC', 'TEST', NULL)"); + ResultSet rs = stat.executeQuery("SELECT * FROM " + + prefix + "_SEARCH('Hello', 0, 0)"); + assertTrue(rs.next()); + stat.execute("UPDATE TEST SET NAME=NULL WHERE ID=1"); + stat.execute("UPDATE TEST SET NAME='Hello World' WHERE ID=1"); + conn.setAutoCommit(false); + stat.execute("insert into test values(2, 'Hello Moon!')"); + conn.rollback(); + if (!config.memory) { + conn.close(); + } + conn = getConnection("fullTextTransaction", connList); + stat = conn.createStatement(); + rs = stat.executeQuery("SELECT * FROM " + + prefix + "_SEARCH('Hello', 0, 0)"); + assertTrue(rs.next()); + rs = stat.executeQuery("SELECT * FROM " + + prefix + "_SEARCH('Moon', 0, 0)"); + assertFalse(rs.next()); + FullText.dropAll(conn); + close(connList); + deleteDb("fullTextTransaction"); + FileUtils.deleteRecursive(getBaseDir() + "/fullTextTransaction", false); + } + + private void testMultiThreaded(boolean lucene) throws Exception { + final String prefix = lucene ? "FTL" : "FT"; + trace("Testing multithreaded " + prefix); + deleteDb("fullText"); + ArrayList connList = new ArrayList<>(); + try { + int len = 2; + Task[] task = new Task[len]; + for (int i = 0; i < len; i++) { + final Connection conn = getConnection("fullText;LOCK_TIMEOUT=60000", connList); + Statement stat = conn.createStatement(); + initFullText(stat, lucene); + initFullText(stat, lucene); + final String tableName = "TEST" + i; + stat.execute("CREATE TABLE " + tableName + + "(ID INT PRIMARY KEY, DATA VARCHAR)"); + stat.execute("CALL " + prefix + + "_CREATE_INDEX('PUBLIC', '" + tableName + "', NULL)"); + task[i] = new Task() { + @Override + public void call() throws SQLException { + trace("starting thread " + Thread.currentThread()); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO " + tableName + " VALUES(?, ?)"); + Statement stat = conn.createStatement(); + Random random = new Random(); + int x = 0; + while (!stop) { + trace("stop = " + stop + " for " + Thread.currentThread()); + StringBuilder buff = new StringBuilder(); + for (int j = 0; j < 1000; j++) { + buff.append(" ").append(random.nextInt(10000)); + buff.append(" x").append(j); + buff.append(" ").append(KNOWN_WORDS[j % KNOWN_WORDS.length]); + } + prep.setInt(1, x); + prep.setString(2, buff.toString()); + prep.execute(); + x++; + for (String knownWord : KNOWN_WORDS) { + trace("searching for " + knownWord + " with " + + Thread.currentThread()); + ResultSet rs = stat.executeQuery("SELECT * FROM " + + prefix + "_SEARCH('" + knownWord + + "', 0, 0)"); + assertTrue(rs.next()); + } + } + trace("closing connection"); + if (!config.memory) { + conn.close(); + } + trace("completed thread " + Thread.currentThread()); + } + }; + } + for (Task t : task) { + t.execute(); + } + trace("sleeping"); + Thread.sleep(1000); + + trace("setting stop to true"); + for (Task t : task) { + trace("joining " + t); + t.get(); + trace("done joining " + t); + } + } finally { + close(connList); + } + } + + private void testStreamLob() throws SQLException { + deleteDb("fullText"); + Connection conn = getConnection("fullText"); + Statement stat = conn.createStatement(); + stat.execute("CREATE ALIAS IF NOT EXISTS FT_INIT FOR 'org.h2.fulltext.FullText.init'"); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, DATA CLOB)"); + FullText.createIndex(conn, "PUBLIC", "TEST", null); + conn.setAutoCommit(false); + stat.execute("insert into test values(1, 'Hello Moon!')"); + conn.rollback(); + conn.setAutoCommit(true); + stat.execute("insert into test values(0, 'Hello World!')"); + PreparedStatement prep = conn.prepareStatement( + "insert into test values(1, ?)"); + final int length = 1024 * 1024; + prep.setCharacterStream(1, new Reader() { + int remaining = length; + + @Override + public void close() { + // ignore + } + + @Override + public int read(char[] buff, int off, int len) { + if (remaining >= len) { + remaining -= len; + return len; + } + remaining = -1; + return -1; + } + }, length); + prep.execute(); + ResultSet rs = stat.executeQuery( + "SELECT * FROM FT_SEARCH('World', 0, 0)"); + assertTrue(rs.next()); + rs = stat.executeQuery("SELECT * FROM FT_SEARCH('Moon', 0, 0)"); + assertFalse(rs.next()); + FullText.dropAll(conn); + conn.close(); + deleteDb("fullText"); + } + + private void testCreateDropNative() throws SQLException { + deleteDb("fullText"); + FileUtils.deleteRecursive(getBaseDir() + "/fullText", false); + Connection conn = getConnection("fullText"); + Statement stat = conn.createStatement(); + stat.execute("CREATE ALIAS IF NOT EXISTS FT_INIT FOR 'org.h2.fulltext.FullText.init'"); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + for (int i = 0; i < 10; i++) { + FullText.createIndex(conn, "PUBLIC", "TEST", null); + FullText.dropIndex(conn, "PUBLIC", "TEST"); + } + conn.close(); + deleteDb("fullText"); + FileUtils.deleteRecursive(getBaseDir() + "/fullText", false); + } + + private void testCreateDropLucene() throws SQLException, SecurityException, + NoSuchMethodException, ClassNotFoundException, + IllegalArgumentException, IllegalAccessException, + InvocationTargetException { + deleteDb("fullText"); + FileUtils.deleteRecursive(getBaseDir() + "/fullText", false); + Connection conn = getConnection("fullText"); + Statement stat = conn.createStatement(); + initFullText(stat, true); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + Method createIndexMethod = Class.forName( + LUCENE_FULLTEXT_CLASS_NAME).getMethod("createIndex", + new Class[] { java.sql.Connection.class, + String.class, String.class, String.class }); + Method dropIndexMethod = Class.forName( + LUCENE_FULLTEXT_CLASS_NAME).getMethod("dropIndex", + new Class[] { java.sql.Connection.class, + String.class, String.class }); + for (int i = 0; i < 10; i++) { + createIndexMethod.invoke(null, conn, "PUBLIC", "TEST", null); + dropIndexMethod.invoke(null, conn, "PUBLIC", "TEST"); + } + conn.close(); + deleteDb("fullText"); + FileUtils.deleteRecursive(getBaseDir() + "/fullText", false); + } + + private void testReopen(boolean lucene) throws SQLException { + if (config.memory) { + return; + } + String prefix = lucene ? "FTL" : "FT"; + deleteDb("fullTextReopen"); + FileUtils.deleteRecursive(getBaseDir() + "/fullTextReopen", false); + Connection conn = getConnection("fullTextReopen"); + Statement stat = conn.createStatement(); + initFullText(stat, lucene); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + stat.execute("INSERT INTO TEST VALUES(1, 'Hello World')"); + stat.execute("CALL " + prefix + "_CREATE_INDEX('PUBLIC', 'TEST', NULL)"); + stat.execute("UPDATE TEST SET NAME=NULL WHERE ID=1"); + stat.execute("UPDATE TEST SET NAME='Hello World' WHERE ID=1"); + conn.close(); + + conn = getConnection("fullTextReopen"); + stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("SELECT * FROM " + + prefix + "_SEARCH('Hello', 0, 0)"); + assertTrue(rs.next()); + stat.executeQuery("SELECT * FROM " + prefix + "_SEARCH(NULL, 0, 0)"); + stat.execute("INSERT INTO TEST VALUES(2, NULL)"); + conn.close(); + + FullText.closeAll(); + conn = getConnection("fullTextReopen"); + stat = conn.createStatement(); + stat.execute("INSERT INTO TEST VALUES(3, 'Hello')"); + conn.close(); + FileUtils.deleteRecursive(getBaseDir() + "/fullTextReopen", false); + } + + private void testPerformance(boolean lucene) throws SQLException { + deleteDb("fullText"); + FileUtils.deleteRecursive(getBaseDir() + "/fullText", false); + Connection conn = getConnection("fullText"); + String prefix = lucene ? "FTL" : "FT"; + Statement stat = conn.createStatement(); + initFullText(stat, lucene); + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute( + "CREATE TABLE TEST(ID BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY," + + " SECTION VARCHAR, TOPIC VARCHAR, SYNTAX VARCHAR, TEXT VARCHAR)"); + PreparedStatement ps = conn.prepareStatement( + "INSERT INTO TEST(SECTION, TOPIC, SYNTAX, TEXT) VALUES (?, ?, ?, ?)"); + try (ResultSet rs = stat.executeQuery("HELP \"\"")) { + while (rs.next()) { + for (int i = 1; i <= 4; i++) { + ps.setString(i, rs.getString(i)); + } + ps.addBatch(); + } + } + ps.executeUpdate(); + long time = System.nanoTime(); + stat.execute("CALL " + prefix + "_CREATE_INDEX('PUBLIC', 'TEST', NULL)"); + println("create " + prefix + ": " + + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - time)); + PreparedStatement prep = conn.prepareStatement( + "SELECT * FROM " + prefix + "_SEARCH(?, 0, 0)"); + time = System.nanoTime(); + ResultSet rs = stat.executeQuery("SELECT TEXT FROM TEST"); + int count = 0; + while (rs.next()) { + String text = rs.getString(1); + StringTokenizer tokenizer = new StringTokenizer( + text, " ()[].,;:-+*/!?=<>{}#@'\"~$_%&|"); + while (tokenizer.hasMoreTokens()) { + String word = tokenizer.nextToken(); + if (word.length() < 10) { + continue; + } + prep.setString(1, word); + ResultSet rs2 = prep.executeQuery(); + while (rs2.next()) { + rs2.getString(1); + count++; + } + } + } + println("search " + prefix + ": " + + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - time) + " count: " + count); + stat.execute("CALL " + prefix + "_DROP_ALL()"); + conn.close(); + } + + private void test(boolean lucene, String dataType) throws SQLException { + if (lucene && getBaseDir().indexOf(':') > 0) { + return; + } + deleteDb("fullText"); + Connection conn = getConnection("fullText"); + String prefix = lucene ? "FTL_" : "FT_"; + Statement stat = conn.createStatement(); + String className = lucene ? "FullTextLucene" : "FullText"; + stat.execute("CREATE ALIAS IF NOT EXISTS " + prefix + "INIT FOR 'org.h2.fulltext." + className + ".init'"); + stat.execute("CALL " + prefix + "INIT()"); + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME " + dataType + ")"); + stat.execute("INSERT INTO TEST VALUES(1, 'Hello World')"); + stat.execute("CALL " + prefix + "CREATE_INDEX('PUBLIC', 'TEST', NULL)"); + ResultSet rs; + rs = stat.executeQuery("SELECT * FROM " + + prefix + "SEARCH('Hello', 0, 0)"); + rs.next(); + assertEquals("\"PUBLIC\".\"TEST\" WHERE \"ID\"=1", rs.getString(1)); + assertFalse(rs.next()); + rs = stat.executeQuery("SELECT * FROM " + + prefix + "SEARCH('Hallo', 0, 0)"); + assertFalse(rs.next()); + stat.execute("INSERT INTO TEST VALUES(2, 'Hallo Welt')"); + rs = stat.executeQuery("SELECT * FROM " + + prefix + "SEARCH('Hello', 0, 0)"); + rs.next(); + assertEquals("\"PUBLIC\".\"TEST\" WHERE \"ID\"=1", rs.getString(1)); + assertFalse(rs.next()); + rs = stat.executeQuery("SELECT * FROM " + + prefix + "SEARCH('Hallo', 0, 0)"); + rs.next(); + assertEquals("\"PUBLIC\".\"TEST\" WHERE \"ID\"=2", rs.getString(1)); + assertFalse(rs.next()); + + stat.execute("CALL " + prefix + "REINDEX()"); + rs = stat.executeQuery("SELECT * FROM " + + prefix + "SEARCH('Hello', 0, 0)"); + rs.next(); + assertEquals("\"PUBLIC\".\"TEST\" WHERE \"ID\"=1", rs.getString(1)); + assertFalse(rs.next()); + rs = stat.executeQuery("SELECT * FROM " + + prefix + "SEARCH('Hallo', 0, 0)"); + rs.next(); + assertEquals("\"PUBLIC\".\"TEST\" WHERE \"ID\"=2", rs.getString(1)); + assertFalse(rs.next()); + + stat.execute("INSERT INTO TEST VALUES(3, 'Hello World')"); + stat.execute("INSERT INTO TEST VALUES(4, 'Hello World')"); + stat.execute("INSERT INTO TEST VALUES(5, 'Hello World')"); + + rs = stat.executeQuery("SELECT * FROM " + + prefix + "SEARCH('World', 0, 0) ORDER BY QUERY"); + rs.next(); + assertEquals("\"PUBLIC\".\"TEST\" WHERE \"ID\"=1", rs.getString(1)); + rs.next(); + assertEquals("\"PUBLIC\".\"TEST\" WHERE \"ID\"=3", rs.getString(1)); + rs.next(); + assertEquals("\"PUBLIC\".\"TEST\" WHERE \"ID\"=4", rs.getString(1)); + rs.next(); + assertEquals("\"PUBLIC\".\"TEST\" WHERE \"ID\"=5", rs.getString(1)); + assertFalse(rs.next()); + + rs = stat.executeQuery("SELECT * FROM " + + prefix + "SEARCH('World', 1, 0)"); + rs.next(); + assertTrue(rs.getString(1).startsWith("\"PUBLIC\".\"TEST\" WHERE \"ID\"=")); + assertFalse(rs.next()); + + rs = stat.executeQuery("SELECT * FROM " + + prefix + "SEARCH('World', 0, 2) ORDER BY QUERY"); + rs.next(); + assertTrue(rs.getString(1).startsWith("\"PUBLIC\".\"TEST\" WHERE \"ID\"=")); + rs.next(); + assertTrue(rs.getString(1).startsWith("\"PUBLIC\".\"TEST\" WHERE \"ID\"=")); + assertFalse(rs.next()); + + rs = stat.executeQuery("SELECT * FROM " + + prefix + "SEARCH('World', 2, 1) ORDER BY QUERY"); + rs.next(); + assertTrue(rs.getString(1).startsWith("\"PUBLIC\".\"TEST\" WHERE \"ID\"=")); + rs.next(); + assertTrue(rs.getString(1).startsWith("\"PUBLIC\".\"TEST\" WHERE \"ID\"=")); + assertFalse(rs.next()); + + rs = stat.executeQuery("SELECT * FROM " + + prefix + "SEARCH('1', 0, 0)"); + rs.next(); + assertEquals("\"PUBLIC\".\"TEST\" WHERE \"ID\"=1", rs.getString(1)); + assertFalse(rs.next()); + + if (lucene) { + rs = stat.executeQuery("SELECT * FROM " + + prefix + "SEARCH('NAME:Hallo', 0, 0)"); + rs.next(); + assertEquals("\"PUBLIC\".\"TEST\" WHERE \"ID\"=2", rs.getString(1)); + assertFalse(rs.next()); + } + + if (!config.memory) { + conn.close(); + conn = getConnection("fullText"); + } + + stat = conn.createStatement(); + stat.executeQuery("SELECT * FROM " + prefix + "SEARCH('World', 0, 0)"); + + stat.execute("CALL " + prefix + "DROP_ALL()"); + + conn.close(); + } + + private void testDropIndex(boolean lucene) throws SQLException { + if (config.memory) { + return; + } + deleteDb("fullTextDropIndex"); + String prefix = lucene ? "FTL" : "FT"; + FileUtils.deleteRecursive(getBaseDir() + "/fullTextDropIndex", false); + Connection conn = getConnection("fullTextDropIndex"); + Statement stat = conn.createStatement(); + initFullText(stat, lucene); + stat.execute("CREATE TABLE TEST" + + "(ID INT PRIMARY KEY, NAME1 VARCHAR, NAME2 VARCHAR)"); + stat.execute("INSERT INTO TEST VALUES" + + "(1, 'Hello World', 'Hello Again')"); + stat.execute("CALL " + prefix + + "_CREATE_INDEX('PUBLIC', 'TEST', 'NAME1')"); + stat.execute("UPDATE TEST SET NAME1=NULL WHERE ID=1"); + stat.execute("UPDATE TEST SET NAME1='Hello World' WHERE ID=1"); + stat.execute("CALL " + prefix + + "_DROP_INDEX('PUBLIC', 'TEST')"); + stat.execute("CALL " + prefix + + "_CREATE_INDEX('PUBLIC', 'TEST', 'NAME1, NAME2')"); + stat.execute("UPDATE TEST SET NAME2=NULL WHERE ID=1"); + stat.execute("UPDATE TEST SET NAME2='Hello World' WHERE ID=1"); + + conn.close(); + FileUtils.deleteRecursive(getBaseDir() + "/fullTextDropIndex", false); + } + + private static void initFullText(Statement stat, boolean lucene) + throws SQLException { + String prefix = lucene ? "FTL" : "FT"; + String className = lucene ? "FullTextLucene" : "FullText"; + stat.execute("CREATE ALIAS IF NOT EXISTS " + prefix + "_INIT FOR 'org.h2.fulltext." + className + ".init'"); + stat.execute("CALL " + prefix + "_INIT()"); + } +} diff --git a/h2/src/test/org/h2/test/db/TestFunctionOverload.java b/h2/src/test/org/h2/test/db/TestFunctionOverload.java new file mode 100644 index 0000000..fe598c6 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestFunctionOverload.java @@ -0,0 +1,200 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests for overloaded user defined functions. + * + * @author Gary Tong + */ +public class TestFunctionOverload extends TestDb { + + private static final String ME = TestFunctionOverload.class.getName(); + private Connection conn; + private DatabaseMetaData meta; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + deleteDb("functionOverload"); + conn = getConnection("functionOverload"); + meta = conn.getMetaData(); + testControl(); + testOverload(); + testOverloadNamedArgs(); + testOverloadWithConnection(); + testOverloadError(); + conn.close(); + deleteDb("functionOverload"); + } + + private void testOverloadError() throws SQLException { + Statement stat = conn.createStatement(); + assertThrows(ErrorCode.METHODS_MUST_HAVE_DIFFERENT_PARAMETER_COUNTS_2, stat). + execute("create alias overloadError for '" + ME + ".overloadError'"); + } + + private void testControl() throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("create alias overload0 for '" + ME + ".overload0'"); + ResultSet rs = stat.executeQuery("select overload0() from dual"); + assertTrue(rs.next()); + assertEquals("0 args", 0, rs.getInt(1)); + assertFalse(rs.next()); + rs = meta.getProcedures(null, null, "OVERLOAD0"); + rs.next(); + assertFalse(rs.next()); + } + + private void testOverload() throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("create alias overload1or2 for '" + ME + ".overload1or2'"); + ResultSet rs = stat.executeQuery("select overload1or2(1) from dual"); + rs.next(); + assertEquals("1 arg", 1, rs.getInt(1)); + assertFalse(rs.next()); + rs = stat.executeQuery("select overload1or2(1, 2) from dual"); + rs.next(); + assertEquals("2 args", 3, rs.getInt(1)); + assertFalse(rs.next()); + rs = meta.getProcedures(null, null, "OVERLOAD1OR2"); + rs.next(); + assertEquals("OVERLOAD1OR2_1", rs.getString("SPECIFIC_NAME")); + rs.next(); + assertEquals("OVERLOAD1OR2_2", rs.getString("SPECIFIC_NAME")); + assertFalse(rs.next()); + } + + private void testOverloadNamedArgs() throws SQLException { + Statement stat = conn.createStatement(); + + stat.execute("create alias overload1or2Named for '" + ME + ".overload1or2(int)'"); + + ResultSet rs = stat.executeQuery("select overload1or2Named(1) from dual"); + assertTrue("First Row", rs.next()); + assertEquals("1 arg", 1, rs.getInt(1)); + assertFalse("Second Row", rs.next()); + rs.close(); + assertThrows(ErrorCode.METHOD_NOT_FOUND_1, stat). + executeQuery("select overload1or2Named(1, 2) from dual"); + stat.close(); + } + + private void testOverloadWithConnection() throws SQLException { + Statement stat = conn.createStatement(); + + stat.execute("create alias overload1or2WithConn for '" + ME + ".overload1or2WithConn'"); + + ResultSet rs = stat.executeQuery("select overload1or2WithConn(1) from dual"); + rs.next(); + assertEquals("1 arg", 1, rs.getInt(1)); + assertFalse(rs.next()); + rs.close(); + + rs = stat.executeQuery("select overload1or2WithConn(1, 2) from dual"); + rs.next(); + assertEquals("2 args", 3, rs.getInt(1)); + assertFalse(rs.next()); + rs.close(); + + stat.close(); + } + + /** + * This method is called via reflection from the database. + * + * @return 0 + */ + public static int overload0() { + return 0; + } + + /** + * This method is called via reflection from the database. + * + * @param one the value + * @return the value + */ + public static int overload1or2(int one) { + return one; + } + + /** + * This method is called via reflection from the database. + * + * @param one the first value + * @param two the second value + * @return the sum of both + */ + public static int overload1or2(int one, int two) { + return one + two; + } + + /** + * This method is called via reflection from the database. + * + * @param conn the connection + * @param one the value + * @return the value + */ + public static int overload1or2WithConn(Connection conn, int one) + throws SQLException { + conn.createStatement().executeQuery("select 1 from dual"); + return one; + } + + /** + * This method is called via reflection from the database. + * + * @param one the first value + * @param two the second value + * @return the sum of both + */ + public static int overload1or2WithConn(int one, int two) { + return one + two; + } + + /** + * This method is called via reflection from the database. + * + * @param one the first value + * @param two the second value + * @return the sum of both + */ + public static int overloadError(int one, int two) { + return one + two; + } + + /** + * This method is called via reflection from the database. + * + * @param one the first value + * @param two the second value + * @return the sum of both + */ + public static int overloadError(double one, double two) { + return (int) (one + two); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestFunctions.java b/h2/src/test/org/h2/test/db/TestFunctions.java new file mode 100644 index 0000000..cb2521a --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestFunctions.java @@ -0,0 +1,2398 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.math.BigDecimal; +import java.sql.Array; +import java.sql.Blob; +import java.sql.CallableStatement; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Timestamp; +import java.sql.Types; +import java.text.DecimalFormatSymbols; +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; +import java.time.temporal.WeekFields; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Currency; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Locale; +import java.util.Properties; +import java.util.TimeZone; +import java.util.UUID; + +import org.h2.api.Aggregate; +import org.h2.api.AggregateFunction; +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.engine.SessionLocal; +import org.h2.expression.function.ToCharFunction; +import org.h2.expression.function.ToCharFunction.Capitalization; +import org.h2.jdbc.JdbcConnection; +import org.h2.mode.ToDateParser; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.test.ap.TestAnnotationProcessor; +import org.h2.tools.SimpleResultSet; +import org.h2.util.IOUtils; +import org.h2.util.StringUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueNumeric; +import org.h2.value.ValueTimestamp; +import org.h2.value.ValueTimestampTimeZone; + +/** + * Tests for user defined functions and aggregates. + */ +public class TestFunctions extends TestDb implements AggregateFunction { + + static int count; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + // Locale.setDefault(Locale.GERMANY); + // Locale.setDefault(Locale.US); + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb("functions"); + testOverrideAlias(); + deleteDb("functions"); + if (!config.networked) { + JdbcConnection conn = (JdbcConnection) getConnection("functions"); + SessionLocal session = (SessionLocal) conn.getSession(); + testToDate(session); + testToDateException(session); + conn.close(); + } + testVersion(); + testFunctionTable(); + testFunctionTableVarArgs(); + testArray(); + testArrayParameters(); + testDefaultConnection(); + testFunctionInSchema(); + testGreatest(); + testSource(); + testDynamicArgumentAndReturn(); + testUUID(); + testWhiteSpacesInParameters(); + testSchemaSearchPath(); + testDeterministic(); + testTransactionId(); + testPrecision(); + testVarArgs(); + testAggregate(); + testAggregateType(); + testFunctions(); + testDateTimeFunctions(); + testFileRead(); + testValue(); + testNvl2(); + testToCharFromDateTime(); + testToCharFromNumber(); + testToCharFromText(); + testFileWrite(); + testThatCurrentTimestampIsSane(); + testThatCurrentTimestampStaysTheSameWithinATransaction(); + testThatCurrentTimestampUpdatesOutsideATransaction(); + testCompatibilityDateTime(); + testAnnotationProcessorsOutput(); + testSignal(); + testLegacyDateTime(); + + deleteDb("functions"); + } + + private void testVersion() throws SQLException { + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + String query = "select h2version()"; + ResultSet rs = stat.executeQuery(query); + assertTrue(rs.next()); + String version = rs.getString(1); + assertEquals(Constants.VERSION, version); + assertFalse(rs.next()); + rs.close(); + stat.close(); + conn.close(); + } + + 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'"); + 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(); + assertTrue(rs.next()); + assertEquals(10, rs.getInt(1)); + assertEquals("X", rs.getString(2)); + conn.close(); + } + + private void testFunctionTableVarArgs() throws SQLException { + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + stat.execute("create alias varargs_function_table for '" + TestFunctions.class.getName() + + ".varArgsFunctionTable'"); + ResultSet rs = stat.executeQuery("select * from varargs_function_table(1,2,3,5,8,13)"); + for (int i : new int[] { 1, 2, 3, 5, 8, 13 }) { + assertTrue(rs.next()); + assertEquals(i, rs.getInt(1)); + } + assertFalse(rs.next()); + conn.close(); + } + + /** + * This method is called via reflection from the database. + * + * @param conn the connection + * @return a result set + */ + public static ResultSet simpleFunctionTable(@SuppressWarnings("unused") Connection conn) { + SimpleResultSet result = new SimpleResultSet(); + result.addColumn("A", Types.INTEGER, 0, 0); + result.addColumn("B", Types.CHAR, 0, 0); + result.addRow(42, 'X'); + return result; + } + + /** + * This method is called via reflection from the database. + * + * @param conn the connection + * @param p the parameter + * @return a result set + */ + public static ResultSet functionTableWithParameter(@SuppressWarnings("unused") Connection conn, int p) { + SimpleResultSet result = new SimpleResultSet(); + result.addColumn("A", Types.INTEGER, 0, 0); + result.addColumn("B", Types.CHAR, 0, 0); + result.addRow(p, 'X'); + return result; + } + + /** + * This method is called via reflection from the database. + * + * @param values the value array + * @return a result set + */ + public static ResultSet varArgsFunctionTable(int... values) throws SQLException { + if (values.length != 6) { + throw new SQLException("Unexpected argument count"); + } + SimpleResultSet result = new SimpleResultSet(); + result.addColumn("A", Types.INTEGER, 0, 0); + for (int value : values) { + result.addRow(value); + } + return result; + } + + private void testNvl2() throws SQLException { + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + + String createSQL = "CREATE TABLE testNvl2(id BIGINT, txt1 " + + "varchar, txt2 varchar, num number(9, 0));"; + stat.execute(createSQL); + stat.execute("insert into testNvl2(id, txt1, txt2, num) " + + "values(1, 'test1', 'test2', null)"); + stat.execute("insert into testNvl2(id, txt1, txt2, num) " + + "values(2, null, 'test4', null)"); + stat.execute("insert into testNvl2(id, txt1, txt2, num) " + + "values(3, 'test5', null, null)"); + stat.execute("insert into testNvl2(id, txt1, txt2, num) " + + "values(4, null, null, null)"); + stat.execute("insert into testNvl2(id, txt1, txt2, num) " + + "values(5, '2', null, 1)"); + stat.execute("insert into testNvl2(id, txt1, txt2, num) " + + "values(6, '2', null, null)"); + stat.execute("insert into testNvl2(id, txt1, txt2, num) " + + "values(7, 'test2', null, null)"); + + String query = "SELECT NVL2(txt1, txt1, txt2), txt1 " + + "FROM testNvl2 order by id asc"; + ResultSet rs = stat.executeQuery(query); + rs.next(); + String actual = rs.getString(1); + assertEquals("test1", actual); + rs.next(); + actual = rs.getString(1); + assertEquals("test4", actual); + rs.next(); + actual = rs.getString(1); + assertEquals("test5", actual); + rs.next(); + actual = rs.getString(1); + assertEquals(null, actual); + assertEquals(rs.getMetaData().getColumnType(2), + rs.getMetaData().getColumnType(1)); + rs.close(); + + rs = stat.executeQuery("SELECT NVL2(num, num, txt1), num " + + "FROM testNvl2 where id in(5, 6) order by id asc"); + rs.next(); + assertEquals(rs.getMetaData().getColumnType(2), + rs.getMetaData().getColumnType(1)); + + assertThrows(ErrorCode.DATA_CONVERSION_ERROR_1, stat). + executeQuery("SELECT NVL2(num, num, txt1), num " + + "FROM testNvl2 where id = 7 order by id asc"); + + // nvl2 should return expr2's datatype, if expr2 is character data. + rs = stat.executeQuery("SELECT NVL2(1, 'test', 123), 'test' FROM dual"); + rs.next(); + actual = rs.getString(1); + assertEquals("test", actual); + assertEquals(rs.getMetaData().getColumnType(2), + rs.getMetaData().getColumnType(1)); + + conn.close(); + } + + private void testValue() throws SQLException { + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + ResultSet rs; + stat.execute("create alias TO_CHAR_2 for '" + getClass().getName() + ".toChar'"); + rs = stat.executeQuery( + "call TO_CHAR_2(TIMESTAMP '2001-02-03 04:05:06', 'format')"); + rs.next(); + assertEquals("2001-02-03 04:05:06", rs.getString(1)); + stat.execute("drop alias TO_CHAR_2"); + conn.close(); + } + + /** + * This method is called via reflection from the database. + * + * @param args the argument list + * @return the value + */ + public static Value toChar(Value... args) { + if (args.length == 0) { + return null; + } + return args[0].convertTo(TypeInfo.TYPE_VARCHAR); + } + + private void testDefaultConnection() throws SQLException { + Connection conn = getConnection("functions;DEFAULT_CONNECTION=TRUE"); + Statement stat = conn.createStatement(); + stat.execute("create alias test for '" + TestFunctions.class.getName() + ".testDefaultConn'"); + stat.execute("call test()"); + stat.execute("drop alias test"); + conn.close(); + } + + /** + * This method is called via reflection from the database. + */ + public static void testDefaultConn() throws SQLException { + DriverManager.getConnection("jdbc:default:connection"); + } + + private void testFunctionInSchema() throws SQLException { + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + + stat.execute("create schema schema2"); + stat.execute("create alias schema2.func as 'int x() { return 1; }'"); + stat.execute("create view test as select schema2.func()"); + ResultSet rs; + rs = stat.executeQuery("select * from information_schema.views where table_schema = 'PUBLIC'"); + rs.next(); + assertContains(rs.getString("VIEW_DEFINITION"), "\"SCHEMA2\".\"FUNC\""); + + stat.execute("drop view test"); + stat.execute("drop schema schema2 cascade"); + + conn.close(); + } + + private void testGreatest() throws SQLException { + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + + String createSQL = "CREATE TABLE testGreatest (id BIGINT);"; + stat.execute(createSQL); + stat.execute("insert into testGreatest values (1)"); + + String query = "SELECT GREATEST(id, " + + ((long) Integer.MAX_VALUE) + ") FROM testGreatest"; + ResultSet rs = stat.executeQuery(query); + rs.next(); + Object o = rs.getObject(1); + assertEquals(Long.class.getName(), o.getClass().getName()); + + String query2 = "SELECT GREATEST(id, " + + ((long) Integer.MAX_VALUE + 1) + ") FROM testGreatest"; + ResultSet rs2 = stat.executeQuery(query2); + rs2.next(); + Object o2 = rs2.getObject(1); + assertEquals(Long.class.getName(), o2.getClass().getName()); + + conn.close(); + } + + private void testSource() throws SQLException { + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + ResultSet rs; + stat.execute("create force alias sayHi as 'String test(String name) {\n" + + "return \"Hello \" + name;\n}'"); + rs = stat.executeQuery("SELECT ROUTINE_NAME " + + "FROM INFORMATION_SCHEMA.ROUTINES"); + rs.next(); + assertEquals("SAY" + "HI", rs.getString(1)); + rs = stat.executeQuery("call sayHi('Joe')"); + rs.next(); + assertEquals("Hello Joe", rs.getString(1)); + if (!config.memory) { + conn.close(); + conn = getConnection("functions"); + stat = conn.createStatement(); + rs = stat.executeQuery("call sayHi('Joe')"); + rs.next(); + assertEquals("Hello Joe", rs.getString(1)); + } + stat.execute("drop alias sayHi"); + conn.close(); + } + + private void testDynamicArgumentAndReturn() throws SQLException { + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + ResultSet rs; + stat.execute("create alias dynamic deterministic for '" + getClass().getName() + ".dynamic'"); + setCount(0); + rs = stat.executeQuery("call dynamic(ARRAY['a', '1'])[1]"); + rs.next(); + String a = rs.getString(1); + assertEquals("a1", a); + stat.execute("drop alias dynamic"); + conn.close(); + } + + private void testUUID() throws SQLException { + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + ResultSet rs; + + stat.execute("create alias xorUUID for '" + getClass().getName() + ".xorUUID'"); + setCount(0); + rs = stat.executeQuery("call xorUUID(random_uuid(), random_uuid())"); + rs.next(); + Object o = rs.getObject(1); + assertEquals(UUID.class.toString(), o.getClass().toString()); + stat.execute("drop alias xorUUID"); + + conn.close(); + } + + private void testDeterministic() throws SQLException { + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + ResultSet rs; + + stat.execute("create alias getCount for '" + getClass().getName() + ".getCount'"); + setCount(0); + rs = stat.executeQuery("select getCount() from system_range(1, 2)"); + rs.next(); + assertEquals(0, rs.getInt(1)); + rs.next(); + assertEquals(1, rs.getInt(1)); + stat.execute("drop alias getCount"); + + stat.execute("create alias getCount deterministic for '" + getClass().getName() + ".getCount'"); + setCount(0); + rs = stat.executeQuery("select getCount() from system_range(1, 2)"); + rs.next(); + assertEquals(0, rs.getInt(1)); + rs.next(); + assertEquals(0, rs.getInt(1)); + stat.execute("drop alias getCount"); + rs = stat.executeQuery("SELECT * FROM " + + "INFORMATION_SCHEMA.ROUTINES " + + "WHERE UPPER(ROUTINE_NAME) = 'GET' || 'COUNT'"); + assertFalse(rs.next()); + stat.execute("create alias reverse deterministic for '" + getClass().getName() + ".reverse'"); + rs = stat.executeQuery("select reverse(x) from system_range(700, 700)"); + rs.next(); + assertEquals("007", rs.getString(1)); + stat.execute("drop alias reverse"); + + conn.close(); + } + + private void testTransactionId() throws SQLException { + if (config.memory) { + return; + } + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int)"); + ResultSet rs; + rs = stat.executeQuery("call transaction_id()"); + rs.next(); + assertTrue(rs.getString(1) == null && rs.wasNull()); + stat.execute("insert into test values(1)"); + rs = stat.executeQuery("call transaction_id()"); + rs.next(); + assertTrue(rs.getString(1) == null && rs.wasNull()); + conn.setAutoCommit(false); + stat.execute("delete from test"); + rs = stat.executeQuery("call transaction_id()"); + rs.next(); + assertNotNull(rs.getString(1)); + stat.execute("drop table test"); + conn.close(); + } + + private void testPrecision() throws SQLException { + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + stat.execute("create alias no_op for '" + getClass().getName() + ".noOp'"); + PreparedStatement prep = conn.prepareStatement( + "select * from dual where no_op(1.6)=?"); + prep.setBigDecimal(1, new BigDecimal("1.6")); + ResultSet rs = prep.executeQuery(); + assertTrue(rs.next()); + + stat.execute("create aggregate agg_sum for '" + getClass().getName() + '\''); + rs = stat.executeQuery("select agg_sum(1), sum(1.6) from dual"); + rs.next(); + assertEquals(1, rs.getMetaData().getScale(2)); + assertEquals(ValueNumeric.MAXIMUM_SCALE / 2, rs.getMetaData().getScale(1)); + stat.executeQuery("select * from information_schema.routines"); + conn.close(); + } + + private void testVarArgs() throws SQLException { + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + stat.execute("CREATE ALIAS mean FOR '" + getClass().getName() + ".mean'"); + ResultSet rs = stat.executeQuery( + "select mean(), mean(10), mean(10, 20), mean(10, 20, 30)"); + rs.next(); + assertEquals(1.0, rs.getDouble(1)); + assertEquals(10.0, rs.getDouble(2)); + assertEquals(15.0, rs.getDouble(3)); + assertEquals(20.0, rs.getDouble(4)); + + stat.execute("CREATE ALIAS mean2 FOR '" + getClass().getName() + ".mean2'"); + rs = stat.executeQuery( + "select mean2(), mean2(10), mean2(10, 20)"); + rs.next(); + assertEquals(Double.NaN, rs.getDouble(1)); + assertEquals(10.0, rs.getDouble(2)); + assertEquals(15.0, rs.getDouble(3)); + + DatabaseMetaData meta = conn.getMetaData(); + rs = meta.getProcedureColumns(null, null, "MEAN2", null); + assertTrue(rs.next()); + assertEquals("RESULT", rs.getString("COLUMN_NAME")); + assertTrue(rs.next()); + assertEquals("FUNCTIONS", rs.getString("PROCEDURE_CAT")); + assertEquals("PUBLIC", rs.getString("PROCEDURE_SCHEM")); + assertEquals("MEAN2", rs.getString("PROCEDURE_NAME")); + assertEquals("P1", rs.getString("COLUMN_NAME")); + assertEquals(DatabaseMetaData.procedureColumnIn, + rs.getInt("COLUMN_TYPE")); + assertEquals("DOUBLE PRECISION ARRAY", rs.getString("TYPE_NAME")); + assertEquals(Constants.MAX_ARRAY_CARDINALITY, rs.getInt("PRECISION")); + assertEquals(Constants.MAX_ARRAY_CARDINALITY, rs.getInt("LENGTH")); + assertEquals(0, rs.getInt("SCALE")); + assertEquals(DatabaseMetaData.columnNullableUnknown, + rs.getInt("NULLABLE")); + assertNull(rs.getString("REMARKS")); + assertEquals(null, rs.getString("COLUMN_DEF")); + assertEquals(0, rs.getInt("SQL_DATA_TYPE")); + assertEquals(0, rs.getInt("SQL_DATETIME_SUB")); + assertEquals(0, rs.getInt("CHAR_OCTET_LENGTH")); + assertEquals(1, rs.getInt("ORDINAL_POSITION")); + assertEquals("", rs.getString("IS_NULLABLE")); + assertEquals("MEAN2_1", rs.getString("SPECIFIC_NAME")); + assertFalse(rs.next()); + + stat.execute("CREATE ALIAS printMean FOR '" + getClass().getName() + ".printMean'"); + rs = stat.executeQuery( + "select printMean('A'), printMean('A', 10), " + + "printMean('BB', 10, 20), printMean ('CCC', 10, 20, 30)"); + rs.next(); + assertEquals("A: 0", rs.getString(1)); + assertEquals("A: 10", rs.getString(2)); + assertEquals("BB: 15", rs.getString(3)); + assertEquals("CCC: 20", rs.getString(4)); + conn.close(); + } + + private void testFileRead() throws Exception { + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + String fileName = getBaseDir() + "/test.txt"; + Properties prop = System.getProperties(); + OutputStream out = FileUtils.newOutputStream(fileName, false); + prop.store(out, ""); + out.close(); + ResultSet rs = stat.executeQuery("SELECT LENGTH(FILE_READ('" + + fileName + "')) LEN"); + rs.next(); + assertEquals(FileUtils.size(fileName), rs.getInt(1)); + rs = stat.executeQuery("SELECT FILE_READ('" + + fileName + "') PROP"); + rs.next(); + Properties p2 = new Properties(); + p2.load(rs.getBinaryStream(1)); + assertEquals(prop.size(), p2.size()); + rs = stat.executeQuery("SELECT FILE_READ('" + + fileName + "', NULL) PROP"); + rs.next(); + String ps = rs.getString(1); + InputStreamReader r = new InputStreamReader(FileUtils.newInputStream(fileName)); + String ps2 = IOUtils.readStringAndClose(r, -1); + assertEquals(ps, ps2); + FileUtils.delete(fileName); + // Test classpath prefix using this test class as input + fileName = "/" + this.getClass().getName().replaceAll("\\.", "/") + ".class"; + rs = stat.executeQuery("SELECT LENGTH(FILE_READ('classpath:" + fileName + "')) LEN"); + rs.next(); + int fileSize = rs.getInt(1); + assertTrue(fileSize > 0); + conn.close(); + } + + + private void testFileWrite() throws Exception { + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + // Copy data into clob table + stat.execute("DROP TABLE TEST IF EXISTS"); + PreparedStatement pst = conn.prepareStatement( + "CREATE TABLE TEST(data clob) AS SELECT ? " + "data"); + Properties prop = System.getProperties(); + ByteArrayOutputStream os = new ByteArrayOutputStream(prop.size()); + prop.store(os, ""); + pst.setBinaryStream(1, new ByteArrayInputStream(os.toByteArray())); + pst.execute(); + os.close(); + String fileName = new File(getBaseDir(), "test.txt").getPath(); + FileUtils.delete(fileName); + ResultSet rs = stat.executeQuery("SELECT FILE_WRITE(data, " + + StringUtils.quoteStringSQL(fileName) + ") len from test"); + assertTrue(rs.next()); + assertEquals(os.size(), rs.getInt(1)); + InputStreamReader r = new InputStreamReader(FileUtils.newInputStream(fileName)); + // Compare expected content with written file content + String ps2 = IOUtils.readStringAndClose(r, -1); + assertEquals(os.toString(), ps2); + conn.close(); + FileUtils.delete(fileName); + } + + + + /** + * This median implementation keeps all objects in memory. + */ + public static class MedianString implements AggregateFunction { + + private final ArrayList list = new ArrayList<>(); + + @Override + public void add(Object value) { + list.add(value.toString()); + } + + @Override + public Object getResult() { + Collections.sort(list); + return list.get(list.size() / 2); + } + + @Override + public int getType(int[] inputType) { + return Types.VARCHAR; + } + + } + + /** + * This median implementation keeps all objects in memory. + */ + public static class MedianStringType implements Aggregate { + + private final ArrayList list = new ArrayList<>(); + + @Override + public void add(Object value) { + list.add(value.toString()); + } + + @Override + public Object getResult() { + return list.get(list.size() / 2); + } + + @Override + public int getInternalType(int[] inputTypes) throws SQLException { + return Value.VARCHAR; + } + + } + + private void testAggregateType() throws SQLException { + deleteDb("functions"); + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + stat.execute("CREATE AGGREGATE SIMPLE_MEDIAN FOR '" + MedianStringType.class.getName() + '\''); + stat.execute("CREATE AGGREGATE IF NOT EXISTS SIMPLE_MEDIAN FOR '" + MedianStringType.class.getName() + '\''); + ResultSet rs = stat.executeQuery( + "SELECT SIMPLE_MEDIAN(X) FROM SYSTEM_RANGE(1, 9)"); + rs.next(); + assertEquals("5", rs.getString(1)); + rs = stat.executeQuery( + "SELECT SIMPLE_MEDIAN(X) FILTER (WHERE X > 2) FROM SYSTEM_RANGE(1, 9)"); + rs.next(); + assertEquals("6", rs.getString(1)); + rs = stat.executeQuery("SELECT SIMPLE_MEDIAN(X) OVER () FROM SYSTEM_RANGE(1, 9)"); + for (int i = 1; i < 9; i++) { + assertTrue(rs.next()); + assertEquals("5", rs.getString(1)); + } + rs = stat.executeQuery("SELECT SIMPLE_MEDIAN(X) OVER (PARTITION BY X) FROM SYSTEM_RANGE(1, 9)"); + for (int i = 1; i < 9; i++) { + assertTrue(rs.next()); + assertEquals(Integer.toString(i), rs.getString(1)); + } + conn.close(); + + if (config.memory) { + return; + } + + conn = getConnection("functions"); + stat = conn.createStatement(); + stat.executeQuery("SELECT SIMPLE_MEDIAN(X) FROM SYSTEM_RANGE(1, 9)"); + DatabaseMetaData meta = conn.getMetaData(); + rs = meta.getProcedures(null, null, "SIMPLE_MEDIAN"); + assertTrue(rs.next()); + assertFalse(rs.next()); + rs = stat.executeQuery("SCRIPT"); + boolean found = false; + while (rs.next()) { + String sql = rs.getString(1); + if (sql.contains("SIMPLE_MEDIAN")) { + found = true; + } + } + assertTrue(found); + stat.execute("DROP AGGREGATE SIMPLE_MEDIAN"); + stat.execute("DROP AGGREGATE IF EXISTS SIMPLE_MEDIAN"); + conn.close(); + } + + private void testAggregate() throws SQLException { + deleteDb("functions"); + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + stat.execute("CREATE AGGREGATE SIMPLE_MEDIAN FOR '" + MedianString.class.getName() + '\''); + stat.execute("CREATE AGGREGATE IF NOT EXISTS SIMPLE_MEDIAN FOR '" + MedianString.class.getName() + '\''); + stat.execute("CREATE SCHEMA S1"); + stat.execute("CREATE AGGREGATE S1.MEDIAN2 FOR '" + MedianString.class.getName() + '\''); + ResultSet rs = stat.executeQuery("SELECT SIMPLE_MEDIAN(X) FROM SYSTEM_RANGE(1, 9)"); + rs.next(); + assertEquals("5", rs.getString(1)); + assertThrows(ErrorCode.FUNCTION_NOT_FOUND_1, stat).executeQuery("SELECT MEDIAN2(X) FROM SYSTEM_RANGE(1, 9)"); + rs = stat.executeQuery("SELECT S1.MEDIAN2(X) FROM SYSTEM_RANGE(1, 9)"); + rs.next(); + assertEquals("5", rs.getString(1)); + + stat.execute("CREATE TABLE DATA(V INT)"); + stat.execute("INSERT INTO DATA VALUES (1), (3), (2), (1), (1), (2), (1), (1), (1), (1), (1)"); + rs = stat.executeQuery("SELECT SIMPLE_MEDIAN(V), SIMPLE_MEDIAN(DISTINCT V) FROM DATA"); + rs.next(); + assertEquals("1", rs.getString(1)); + assertEquals("2", rs.getString(2)); + + conn.close(); + + if (config.memory) { + return; + } + + conn = getConnection("functions"); + stat = conn.createStatement(); + stat.executeQuery("SELECT SIMPLE_MEDIAN(X) FROM SYSTEM_RANGE(1, 9)"); + DatabaseMetaData meta = conn.getMetaData(); + rs = meta.getProcedures(null, null, "SIMPLE_MEDIAN"); + assertTrue(rs.next()); + assertEquals("PUBLIC", rs.getString("PROCEDURE_SCHEM")); + assertFalse(rs.next()); + rs = meta.getProcedures(null, null, "MEDIAN2"); + assertTrue(rs.next()); + assertEquals("S1", rs.getString("PROCEDURE_SCHEM")); + assertFalse(rs.next()); + rs = stat.executeQuery("SCRIPT"); + boolean found1 = false, found2 = false; + while (rs.next()) { + String sql = rs.getString(1); + if (sql.contains("\"PUBLIC\".\"SIMPLE_MEDIAN\"")) { + found1 = true; + } else if (sql.contains("\"S1\".\"MEDIAN2\"")) { + found2 = true; + } + } + assertTrue(found1); + assertTrue(found2); + stat.execute("DROP AGGREGATE SIMPLE_MEDIAN"); + stat.execute("DROP AGGREGATE IF EXISTS SIMPLE_MEDIAN"); + stat.execute("DROP AGGREGATE S1.MEDIAN2"); + stat.execute("DROP SCHEMA S1"); + conn.close(); + } + + private void testFunctions() throws SQLException { + deleteDb("functions"); + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + assertCallResult(null, stat, "abs(null)"); + assertCallResult("1", stat, "abs(1)"); + assertCallResult("1", stat, "abs(1)"); + + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + stat.execute("CREATE ALIAS ADD_ROW FOR '" + getClass().getName() + ".addRow'"); + ResultSet rs; + rs = stat.executeQuery("CALL ADD_ROW(1, 'Hello')"); + rs.next(); + assertEquals(1, rs.getInt(1)); + rs = stat.executeQuery("SELECT * FROM TEST"); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + assertFalse(rs.next()); + + DatabaseMetaData meta = conn.getMetaData(); + rs = meta.getProcedureColumns(null, null, "ADD_ROW", null); + assertTrue(rs.next()); + assertEquals("RESULT", rs.getString("COLUMN_NAME")); + assertTrue(rs.next()); + assertEquals("FUNCTIONS", rs.getString("PROCEDURE_CAT")); + assertEquals("PUBLIC", rs.getString("PROCEDURE_SCHEM")); + assertEquals("ADD_ROW", rs.getString("PROCEDURE_NAME")); + assertEquals("P1", rs.getString("COLUMN_NAME")); + assertEquals(DatabaseMetaData.procedureColumnIn, + rs.getInt("COLUMN_TYPE")); + assertEquals("INTEGER", rs.getString("TYPE_NAME")); + assertEquals(32, rs.getInt("PRECISION")); + assertEquals(32, rs.getInt("LENGTH")); + assertEquals(0, rs.getInt("SCALE")); + assertEquals(DatabaseMetaData.columnNoNulls, rs.getInt("NULLABLE")); + assertNull(rs.getString("REMARKS")); + assertEquals(null, rs.getString("COLUMN_DEF")); + assertEquals(0, rs.getInt("SQL_DATA_TYPE")); + assertEquals(0, rs.getInt("SQL_DATETIME_SUB")); + assertEquals(0, rs.getInt("CHAR_OCTET_LENGTH")); + assertEquals(1, rs.getInt("ORDINAL_POSITION")); + assertEquals("", rs.getString("IS_NULLABLE")); + assertEquals("ADD_ROW_1", rs.getString("SPECIFIC_NAME")); + assertTrue(rs.next()); + assertEquals("P2", rs.getString("COLUMN_NAME")); + assertEquals("CHARACTER VARYING", rs.getString("TYPE_NAME")); + assertFalse(rs.next()); + + stat.executeQuery("CALL ADD_ROW(2, 'World')"); + + stat.execute("CREATE ALIAS SELECT_F FOR '" + getClass().getName() + ".select'"); + rs = stat.executeQuery("SELECT * FROM SELECT_F('SELECT * " + + "FROM TEST ORDER BY ID')"); + assertEquals(2, rs.getMetaData().getColumnCount()); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + rs.next(); + assertEquals(2, rs.getInt(1)); + assertEquals("World", rs.getString(2)); + assertFalse(rs.next()); + + rs = stat.executeQuery("SELECT NAME FROM SELECT_F('SELECT * " + + "FROM TEST ORDER BY NAME') ORDER BY NAME DESC"); + assertEquals(1, rs.getMetaData().getColumnCount()); + rs.next(); + assertEquals("World", rs.getString(1)); + rs.next(); + assertEquals("Hello", rs.getString(1)); + assertFalse(rs.next()); + + assertThrows(ErrorCode.SYNTAX_ERROR_2, stat). + executeQuery("SELECT * FROM SELECT_F('ERROR')"); + stat.execute("CREATE ALIAS SIMPLE FOR '" + getClass().getName() + ".simpleResultSet'"); + rs = stat.executeQuery("SELECT * FROM SIMPLE(2, 1, 1, 1, 1, 1, 1, 1)"); + assertEquals(2, rs.getMetaData().getColumnCount()); + rs.next(); + assertEquals(0, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals("World", rs.getString(2)); + assertFalse(rs.next()); + + rs = stat.executeQuery("SELECT * FROM SIMPLE(1, 1, 1, 1, 1, 1, 1, 1)"); + assertEquals(2, rs.getMetaData().getColumnCount()); + rs.next(); + assertEquals(0, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + assertFalse(rs.next()); + + stat.execute("CREATE ALIAS GET_ARRAY FOR '" + getClass().getName() + ".getArray'"); + rs = stat.executeQuery("CALL GET_ARRAY()"); + assertEquals(1, rs.getMetaData().getColumnCount()); + rs.next(); + Array a = rs.getArray(1); + Object[] array = (Object[]) a.getArray(); + assertEquals(2, array.length); + assertEquals("0", (String) array[0]); + assertEquals("Hello", (String) array[1]); + assertThrows(ErrorCode.INVALID_VALUE_2, a).getArray(1, -1); + assertEquals(2, ((Object[]) a.getArray(1, 3)).length); + assertEquals(0, ((Object[]) a.getArray(1, 0)).length); + assertEquals(0, ((Object[]) a.getArray(2, 0)).length); + assertThrows(ErrorCode.INVALID_VALUE_2, a).getArray(0, 0); + assertThrows(ErrorCode.INVALID_VALUE_2, a).getArray(3, 0); + HashMap> map = new HashMap<>(); + assertEquals(0, ((Object[]) a.getArray(1, 0, map)).length); + assertEquals(2, ((Object[]) a.getArray(map)).length); + assertEquals(2, ((Object[]) a.getArray(null)).length); + map.put("x", Object.class); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, a).getArray(1, 0, map); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, a).getArray(map); + + ResultSet rs2; + rs2 = a.getResultSet(); + rs2.next(); + assertEquals(1, rs2.getInt(1)); + assertEquals(0, rs2.getInt(2)); + rs2.next(); + assertEquals(2, rs2.getInt(1)); + assertEquals("Hello", rs2.getString(2)); + assertFalse(rs.next()); + + map.clear(); + rs2 = a.getResultSet(map); + rs2.next(); + assertEquals(1, rs2.getInt(1)); + assertEquals(0, rs2.getInt(2)); + rs2.next(); + assertEquals(2, rs2.getInt(1)); + assertEquals("Hello", rs2.getString(2)); + assertFalse(rs.next()); + + rs2 = a.getResultSet(2, 1); + rs2.next(); + assertEquals(2, rs2.getInt(1)); + assertEquals("Hello", rs2.getString(2)); + assertFalse(rs.next()); + + rs2 = a.getResultSet(1, 1, map); + rs2.next(); + assertEquals(1, rs2.getInt(1)); + assertEquals(0, rs2.getInt(2)); + assertFalse(rs.next()); + + map.put("x", Object.class); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, a).getResultSet(map); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, a).getResultSet(0, 1, map); + + a.free(); + assertThrows(ErrorCode.OBJECT_CLOSED, a).getArray(); + assertThrows(ErrorCode.OBJECT_CLOSED, a).getResultSet(); + + stat.execute("CREATE ALIAS ROOT FOR '" + getClass().getName() + ".root'"); + rs = stat.executeQuery("CALL ROOT(9)"); + rs.next(); + assertEquals(3, rs.getInt(1)); + assertFalse(rs.next()); + + stat.execute("CREATE ALIAS MAX_ID FOR '" + getClass().getName() + ".selectMaxId'"); + + rs = stat.executeQuery("SELECT * FROM MAX_ID()"); + rs.next(); + assertEquals(2, rs.getInt(1)); + assertFalse(rs.next()); + + rs = stat.executeQuery("CALL CASE WHEN -9 < 0 THEN 0 ELSE ROOT(-9) END"); + rs.next(); + assertEquals(0, rs.getInt(1)); + assertFalse(rs.next()); + + stat.execute("CREATE ALIAS blob FOR '" + getClass().getName() + ".blob'"); + rs = stat.executeQuery("SELECT blob(CAST('0102' AS BLOB)) FROM DUAL"); + while (rs.next()) { + // ignore + } + rs.close(); + + stat.execute("CREATE ALIAS clob FOR '" + getClass().getName() + ".clob'"); + rs = stat.executeQuery("SELECT clob(CAST('Hello' AS CLOB)) FROM DUAL"); + while (rs.next()) { + // ignore + } + rs.close(); + + stat.execute("create alias sql as " + + "'ResultSet sql(Connection conn, String sql) " + + "throws SQLException { return conn.createStatement().executeQuery(sql); }'"); + rs = stat.executeQuery("select * from sql('select cast(''Hello'' as clob)')"); + assertTrue(rs.next()); + assertEquals("Hello", rs.getString(1)); + + rs = stat.executeQuery("select * from sql('select cast(X''4869'' as blob)')"); + assertTrue(rs.next()); + assertEquals("Hi", new String(rs.getBytes(1))); + + rs = stat.executeQuery("select * from sql('select 1 a, ''Hello'' b')"); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + ResultSetMetaData meta2 = rs.getMetaData(); + assertEquals(Types.INTEGER, meta2.getColumnType(1)); + assertEquals("INTEGER", meta2.getColumnTypeName(1)); + assertEquals("java.lang.Integer", meta2.getColumnClassName(1)); + assertEquals(Types.VARCHAR, meta2.getColumnType(2)); + assertEquals("CHARACTER VARYING", meta2.getColumnTypeName(2)); + assertEquals("java.lang.String", meta2.getColumnClassName(2)); + + stat.execute("CREATE ALIAS blob2stream FOR '" + getClass().getName() + ".blob2stream'"); + stat.execute("CREATE ALIAS stream2stream FOR '" + getClass().getName() + ".stream2stream'"); + stat.execute("CREATE TABLE TEST_BLOB(ID INT PRIMARY KEY, \"VALUE\" BLOB)"); + stat.execute("INSERT INTO TEST_BLOB VALUES(0, null)"); + stat.execute("INSERT INTO TEST_BLOB VALUES(1, 'edd1f011edd1f011edd1f011')"); + rs = stat.executeQuery("SELECT blob2stream(\"VALUE\") FROM TEST_BLOB"); + while (rs.next()) { + // ignore + } + rs.close(); + rs = stat.executeQuery("SELECT stream2stream(\"VALUE\") FROM TEST_BLOB"); + while (rs.next()) { + // ignore + } + + conn.close(); + } + + private void testDateTimeFunctions() throws SQLException { + deleteDb("functions"); + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + ResultSet rs; + WeekFields wf = WeekFields.of(Locale.getDefault()); + for (int y = 2001; y <= 2010; y++) { + for (int d = 1; d <= 7; d++) { + String date1 = y + "-01-0" + d, date2 = y + "-01-0" + (d + 1); + LocalDate local1 = LocalDate.parse(date1), local2 = LocalDate.parse(date2); + rs = stat.executeQuery( + "SELECT EXTRACT(DAY_OF_WEEK FROM C1), EXTRACT(WEEK FROM C1), EXTRACT(WEEK_YEAR FROM C1)," + + " DATEDIFF(WEEK, C1, C2), DATE_TRUNC(WEEK, C1), DATE_TRUNC(WEEK_YEAR, C1) FROM" + + " VALUES (DATE '" + date1 + "', DATE '" + date2 + "')"); + rs.next(); + assertEquals(local1.get(wf.dayOfWeek()), rs.getInt(1)); + int w1 = local1.get(wf.weekOfWeekBasedYear()); + assertEquals(w1, rs.getInt(2)); + int weekYear = local1.get(wf.weekBasedYear()); + assertEquals(weekYear, rs.getInt(3)); + assertEquals(w1 == local2.get(wf.weekOfWeekBasedYear()) ? 0 : 1, rs.getInt(4)); + assertEquals(local1.minus(local1.get(wf.dayOfWeek()) - 1, ChronoUnit.DAYS), + rs.getObject(5, LocalDate.class)); + assertEquals(DateTimeFormatter.ofPattern("Y-w-e").parse(weekYear + "-1-1") + .query(TemporalQueries.localDate()), rs.getObject(6, LocalDate.class)); + } + } + conn.close(); + } + + private void testWhiteSpacesInParameters() throws SQLException { + deleteDb("functions"); + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + // with white space + stat.execute("CREATE ALIAS PARSE_INT2 FOR " + + "\"java.lang.Integer.parseInt(java.lang.String, int)\""); + ResultSet rs; + rs = stat.executeQuery("CALL PARSE_INT2('473', 10)"); + rs.next(); + assertEquals(473, rs.getInt(1)); + stat.execute("DROP ALIAS PARSE_INT2"); + // without white space + stat.execute("CREATE ALIAS PARSE_INT2 FOR " + + "\"java.lang.Integer.parseInt(java.lang.String,int)\""); + stat.execute("DROP ALIAS PARSE_INT2"); + conn.close(); + } + + private void testSchemaSearchPath() throws SQLException { + deleteDb("functions"); + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + ResultSet rs; + stat.execute("CREATE SCHEMA TEST"); + stat.execute("SET SCHEMA TEST"); + stat.execute("CREATE ALIAS PARSE_INT2 FOR " + + "\"java.lang.Integer.parseInt(java.lang.String, int)\";"); + rs = stat.executeQuery("SELECT ROUTINE_NAME FROM " + + "INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA ='TEST'"); + rs.next(); + assertEquals("PARSE_INT2", rs.getString(1)); + stat.execute("DROP ALIAS PARSE_INT2"); + + stat.execute("SET SCHEMA PUBLIC"); + stat.execute("CREATE ALIAS TEST.PARSE_INT2 FOR " + + "\"java.lang.Integer.parseInt(java.lang.String, int)\";"); + stat.execute("SET SCHEMA_SEARCH_PATH PUBLIC, TEST"); + + rs = stat.executeQuery("CALL PARSE_INT2('-FF', 16)"); + rs.next(); + assertEquals(-255, rs.getInt(1)); + rs = stat.executeQuery("SELECT ROUTINE_NAME FROM " + + "INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA ='TEST'"); + rs.next(); + assertEquals("PARSE_INT2", rs.getString(1)); + rs = stat.executeQuery("CALL TEST.PARSE_INT2('-2147483648', 10)"); + rs.next(); + assertEquals(-2147483648, rs.getInt(1)); + rs = stat.executeQuery("CALL FUNCTIONS.TEST.PARSE_INT2('-2147483648', 10)"); + rs.next(); + assertEquals(-2147483648, rs.getInt(1)); + conn.close(); + } + + private void testArray() throws SQLException { + deleteDb("functions"); + Connection conn = getConnection("functions"); + PreparedStatement prep = conn.prepareStatement("SELECT ARRAY_MAX_CARDINALITY(?)"); + prep.setObject(1, new Integer[] { 1, 2, 3 }); + try (ResultSet rs = prep.executeQuery()) { + rs.next(); + assertEquals(3, rs.getInt(1)); + } + conn.close(); + } + + private void testArrayParameters() throws SQLException { + deleteDb("functions"); + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + stat.execute("create alias array_test AS " + + "$$ Integer[] array_test(Integer[] in_array) " + + "{ return in_array; } $$;"); + + PreparedStatement prep = conn.prepareStatement( + "select array_test(?) from dual"); + prep.setObject(1, new Integer[] { 1, 2 }); + try (ResultSet rs = prep.executeQuery()) { + rs.next(); + assertTrue(rs.getObject(1) instanceof Array); + } + + CallableStatement call = conn.prepareCall("{ ? = call array_test(?) }"); + call.setObject(2, new Integer[] { 2, 1 }); + call.registerOutParameter(1, Types.ARRAY); + call.execute(); + assertEquals(Object[].class.getName(), call.getArray(1).getArray() + .getClass().getName()); + assertEquals(new Object[]{2, 1}, (Object[]) ((Array) call.getObject(1)).getArray()); + + stat.execute("drop alias array_test"); + + stat.execute("CREATE ALIAS F DETERMINISTIC FOR '" + TestFunctions.class.getName() + ".arrayParameters1'"); + prep = conn.prepareStatement("SELECT F(ARRAY[ARRAY['1', '2'], ARRAY['3']])"); + try (ResultSet rs = prep.executeQuery()) { + rs.next(); + assertEquals(new Integer[][] {{1, 2}, {3}}, rs.getObject(1, Integer[][].class)); + } + prep = conn.prepareStatement("SELECT F(ARRAY[ARRAY[1::BIGINT, 2::BIGINT], ARRAY[3::BIGINT]])"); + try (ResultSet rs = prep.executeQuery()) { + rs.next(); + assertEquals(new Short[][] {{1, 2}, {3}}, rs.getObject(1, Short[][].class)); + } + stat.execute("DROP ALIAS F"); + + conn.close(); + } + + /** + * This method is called with reflection. + * + * @param x argument + * @return result + */ + public static Integer[][] arrayParameters1(String[][] x) { + int l = x.length; + Integer[][] result = new Integer[l][]; + for (int i = 0; i < l; i++) { + String[] x1 = x[i]; + int l1 = x1.length; + Integer[] r1 = new Integer[l1]; + for (int j = 0; j < l1; j++) { + r1[j] = Integer.parseInt(x1[j]); + } + result[i] = r1; + } + return result; + } + + private void testToDateException(SessionLocal session) { + assertThrows(ErrorCode.INVALID_TO_DATE_FORMAT, + () -> ToDateParser.toDate(session, "1979-ThisWillFail-12", "YYYY-MM-DD")); + assertThrows(ErrorCode.INVALID_TO_DATE_FORMAT, // + () -> ToDateParser.toDate(session, "1-DEC-0000", "DD-MON-RRRR")); + } + + private void testToDate(SessionLocal session) { + GregorianCalendar calendar = new GregorianCalendar(); + int year = calendar.get(Calendar.YEAR); + int month = calendar.get(Calendar.MONTH) + 1; + // Default date in Oracle is the first day of the current month + String defDate = year + "-" + month + "-1 "; + ValueTimestamp date = null; + date = ValueTimestamp.parse("1979-11-12", null); + assertEquals(date, ToDateParser.toDate(session, "1979-11-12T00:00:00Z", "YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"")); + assertEquals(date, ToDateParser.toDate(session, "1979*foo*1112", "YYYY\"*foo*\"MM\"\"DD")); + assertEquals(date, ToDateParser.toDate(session, "1979-11-12", "YYYY-MM-DD")); + assertEquals(date, ToDateParser.toDate(session, "1979/11/12", "YYYY/MM/DD")); + assertEquals(date, ToDateParser.toDate(session, "1979,11,12", "YYYY,MM,DD")); + assertEquals(date, ToDateParser.toDate(session, "1979.11.12", "YYYY.MM.DD")); + assertEquals(date, ToDateParser.toDate(session, "1979;11;12", "YYYY;MM;DD")); + assertEquals(date, ToDateParser.toDate(session, "1979:11:12", "YYYY:MM:DD")); + + date = ValueTimestamp.parse("1979-" + month + "-01", null); + assertEquals(date, ToDateParser.toDate(session, "1979", "YYYY")); + assertEquals(date, ToDateParser.toDate(session, "1979 AD", "YYYY AD")); + assertEquals(date, ToDateParser.toDate(session, "1979 A.D.", "YYYY A.D.")); + assertEquals(date, ToDateParser.toDate(session, "1979 A.D.", "YYYY BC")); + assertEquals(date, ToDateParser.toDate(session, "+1979", "SYYYY")); + assertEquals(date, ToDateParser.toDate(session, "79", "RRRR")); + + date = ValueTimestamp.parse(defDate + "00:12:00", null); + assertEquals(date, ToDateParser.toDate(session, "12", "MI")); + + date = ValueTimestamp.parse("1970-11-01", null); + assertEquals(date, ToDateParser.toDate(session, "11", "MM")); + assertEquals(date, ToDateParser.toDate(session, "11", "Mm")); + assertEquals(date, ToDateParser.toDate(session, "11", "mM")); + assertEquals(date, ToDateParser.toDate(session, "11", "mm")); + assertEquals(date, ToDateParser.toDate(session, "XI", "RM")); + + int y = (year / 10) * 10 + 9; + date = ValueTimestamp.parse(y + "-" + month + "-01", null); + assertEquals(date, ToDateParser.toDate(session, "9", "Y")); + y = (year / 100) * 100 + 79; + date = ValueTimestamp.parse(y + "-" + month + "-01", null); + assertEquals(date, ToDateParser.toDate(session, "79", "YY")); + y = (year / 1_000) * 1_000 + 979; + date = ValueTimestamp.parse(y + "-" + month + "-01", null); + assertEquals(date, ToDateParser.toDate(session, "979", "YYY")); + + // Gregorian calendar does not have a year 0. + // 0 = 0001 BC, -1 = 0002 BC, ... so we adjust + date = ValueTimestamp.parse("-99-" + month + "-01", null); + assertEquals(date, ToDateParser.toDate(session, "0100 BC", "YYYY BC")); + assertEquals(date, ToDateParser.toDate(session, "0100 B.C.", "YYYY B.C.")); + assertEquals(date, ToDateParser.toDate(session, "-0100", "SYYYY")); + assertEquals(date, ToDateParser.toDate(session, "-0100", "YYYY")); + + // Gregorian calendar does not have a year 0. + // 0 = 0001 BC, -1 = 0002 BC, ... so we adjust + y = -((year / 1_000) * 1_000 + 99); + date = ValueTimestamp.parse(y + "-" + month + "-01", null); + assertEquals(date, ToDateParser.toDate(session, "100 BC", "YYY BC")); + + // Gregorian calendar does not have a year 0. + // 0 = 0001 BC, -1 = 0002 BC, ... so we adjust + y = -((year / 100) * 100); + date = ValueTimestamp.parse(y + "-" + month + "-01", null); + assertEquals(date, ToDateParser.toDate(session, "01 BC", "YY BC")); + y = -((year / 10) * 10); + date = ValueTimestamp.parse(y + "-" + month + "-01", null); + assertEquals(date, ToDateParser.toDate(session, "1 BC", "Y BC")); + + date = ValueTimestamp.parse(defDate + "08:12:00", null); + assertEquals(date, ToDateParser.toDate(session, "08:12 AM", "HH:MI AM")); + assertEquals(date, ToDateParser.toDate(session, "08:12 A.M.", "HH:MI A.M.")); + assertEquals(date, ToDateParser.toDate(session, "08:12", "HH24:MI")); + + date = ValueTimestamp.parse(defDate + "08:12:00", null); + assertEquals(date, ToDateParser.toDate(session, "08:12", "HH:MI")); + assertEquals(date, ToDateParser.toDate(session, "08:12", "HH12:MI")); + + date = ValueTimestamp.parse(defDate + "08:12:34", null); + assertEquals(date, ToDateParser.toDate(session, "08:12:34", "HH:MI:SS")); + + date = ValueTimestamp.parse(defDate + "12:00:00", null); + assertEquals(date, ToDateParser.toDate(session, "12:00:00 PM", "HH12:MI:SS AM")); + + date = ValueTimestamp.parse(defDate + "00:00:00", null); + assertEquals(date, ToDateParser.toDate(session, "12:00:00 AM", "HH12:MI:SS AM")); + + date = ValueTimestamp.parse(defDate + "00:00:34", null); + assertEquals(date, ToDateParser.toDate(session, "34", "SS")); + + date = ValueTimestamp.parse(defDate + "08:12:34", null); + assertEquals(date, ToDateParser.toDate(session, "29554", "SSSSS")); + + date = ValueTimestamp.parse(defDate + "08:12:34.550", null); + assertEquals(date, ToDateParser.toDate(session, "08:12:34 550", "HH:MI:SS FF")); + assertEquals(date, ToDateParser.toDate(session, "08:12:34 55", "HH:MI:SS FF2")); + + date = ValueTimestamp.parse(defDate + "14:04:00", null); + assertEquals(date, ToDateParser.toDate(session, "02:04 P.M.", "HH:MI p.M.")); + assertEquals(date, ToDateParser.toDate(session, "02:04 PM", "HH:MI PM")); + + date = ValueTimestamp.parse("1970-" + month + "-12", null); + assertEquals(date, ToDateParser.toDate(session, "12", "DD")); + + date = ValueTimestamp.parse(year + (calendar.isLeapYear(year) ? "-11-11" : "-11-12"), null); + assertEquals(date, ToDateParser.toDate(session, "316", "DDD")); + assertEquals(date, ToDateParser.toDate(session, "316", "DdD")); + assertEquals(date, ToDateParser.toDate(session, "316", "dDD")); + assertEquals(date, ToDateParser.toDate(session, "316", "ddd")); + + date = ValueTimestamp.parse("2013-01-29", null); + assertEquals(date, ToDateParser.toDate(session, "2456322", "J")); + + if (Locale.getDefault().getLanguage().equals("en")) { + date = ValueTimestamp.parse("9999-12-31 23:59:59", null); + assertEquals(date, ToDateParser.toDate(session, "31-DEC-9999 23:59:59", "DD-MON-YYYY HH24:MI:SS")); + assertEquals(date, ToDateParser.toDate(session, "31-DEC-9999 23:59:59", "DD-MON-RRRR HH24:MI:SS")); + assertEquals(ValueTimestamp.parse("0001-03-01", null), + ToDateParser.toDate(session, "1-MAR-0001", "DD-MON-RRRR")); + assertEquals(ValueTimestamp.parse("9999-03-01", null), + ToDateParser.toDate(session, "1-MAR-9999", "DD-MON-RRRR")); + assertEquals(ValueTimestamp.parse("2000-03-01", null), + ToDateParser.toDate(session, "1-MAR-000", "DD-MON-RRRR")); + assertEquals(ValueTimestamp.parse("1999-03-01", null), + ToDateParser.toDate(session, "1-MAR-099", "DD-MON-RRRR")); + assertEquals(ValueTimestamp.parse("0100-03-01", null), + ToDateParser.toDate(session, "1-MAR-100", "DD-MON-RRRR")); + assertEquals(ValueTimestamp.parse("2000-03-01", null), + ToDateParser.toDate(session, "1-MAR-00", "DD-MON-RRRR")); + assertEquals(ValueTimestamp.parse("2049-03-01", null), + ToDateParser.toDate(session, "1-MAR-49", "DD-MON-RRRR")); + assertEquals(ValueTimestamp.parse("1950-03-01", null), + ToDateParser.toDate(session, "1-MAR-50", "DD-MON-RRRR")); + assertEquals(ValueTimestamp.parse("1999-03-01", null), + ToDateParser.toDate(session, "1-MAR-99", "DD-MON-RRRR")); + } + + assertEquals(ValueTimestampTimeZone.parse("2000-05-10 10:11:12-08:15", null), + ToDateParser.toTimestampTz(session, "2000-05-10 10:11:12 -8:15", "YYYY-MM-DD HH24:MI:SS TZH:TZM")); + assertEquals(ValueTimestampTimeZone.parse("2000-05-10 10:11:12-08:15", null), + ToDateParser.toTimestampTz(session, "2000-05-10 10:11:12 GMT-08:15", "YYYY-MM-DD HH24:MI:SS TZR")); + assertEquals(ValueTimestampTimeZone.parse("2000-02-10 10:11:12-08", null), + ToDateParser.toTimestampTz(session, "2000-02-10 10:11:12 US/Pacific", "YYYY-MM-DD HH24:MI:SS TZR")); + assertEquals(ValueTimestampTimeZone.parse("2000-02-10 10:11:12-08", null), + ToDateParser.toTimestampTz(session, "2000-02-10 10:11:12 PST", "YYYY-MM-DD HH24:MI:SS TZD")); + } + + private void testToCharFromDateTime() throws SQLException { + ToCharFunction.clearNames(); + deleteDb("functions"); + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + + TimeZone tz = TimeZone.getDefault(); + final Timestamp timestamp1979 = Timestamp.valueOf("1979-11-12 08:12:34.560"); + boolean daylight = tz.inDaylightTime(timestamp1979); + String tzShortName = tz.getDisplayName(daylight, TimeZone.SHORT); + String tzLongName = tz.getID(); + if (tzLongName.equals("Etc/UTC")) { + tzLongName = "UTC"; + } + + stat.executeUpdate("CREATE TABLE T (X TIMESTAMP(6))"); + stat.executeUpdate("INSERT INTO T VALUES " + + "(TIMESTAMP '"+timestamp1979.toString()+"')"); + stat.executeUpdate("CREATE TABLE U (X TIMESTAMP(6))"); + stat.executeUpdate("INSERT INTO U VALUES " + + "(TIMESTAMP '-100-01-15 14:04:02.120')"); + + assertResult("1979-11-12 08:12:34.56", stat, "SELECT X FROM T"); + assertResult("-0100-01-15 14:04:02.12", stat, "SELECT X FROM U"); + String expected = String.format("%tb", timestamp1979).toUpperCase(); + expected = stripTrailingPeriod(expected); + assertResult("12-" + expected + "-79 08.12.34.560000000 AM", stat, + "SELECT TO_CHAR(X) FROM T"); + assertResult("- / , . ; : text - /", stat, + "SELECT TO_CHAR(X, '- / , . ; : \"text\" - /') FROM T"); + assertResult("1979-11-12", stat, + "SELECT TO_CHAR(X, 'YYYY-MM-DD') FROM T"); + assertResult("1979/11/12", stat, + "SELECT TO_CHAR(X, 'YYYY/MM/DD') FROM T"); + assertResult("1979,11,12", stat, + "SELECT TO_CHAR(X, 'YYYY,MM,DD') FROM T"); + assertResult("1979.11.12", stat, + "SELECT TO_CHAR(X, 'YYYY.MM.DD') FROM T"); + assertResult("1979;11;12", stat, + "SELECT TO_CHAR(X, 'YYYY;MM;DD') FROM T"); + assertResult("1979:11:12", stat, + "SELECT TO_CHAR(X, 'YYYY:MM:DD') FROM T"); + assertResult("year 1979!", stat, + "SELECT TO_CHAR(X, '\"year \"YYYY\"!\"') FROM T"); + assertResult("1979 AD", stat, + "SELECT TO_CHAR(X, 'YYYY AD') FROM T"); + assertResult("1979 A.D.", stat, + "SELECT TO_CHAR(X, 'YYYY A.D.') FROM T"); + assertResult("0100 B.C.", stat, + "SELECT TO_CHAR(X, 'YYYY A.D.') FROM U"); + assertResult("1979 AD", stat, + "SELECT TO_CHAR(X, 'YYYY BC') FROM T"); + assertResult("100 BC", stat, + "SELECT TO_CHAR(X, 'YYY BC') FROM U"); + assertResult("00 BC", stat, + "SELECT TO_CHAR(X, 'YY BC') FROM U"); + assertResult("0 BC", stat, + "SELECT TO_CHAR(X, 'Y BC') FROM U"); + assertResult("1979 A.D.", stat, "SELECT TO_CHAR(X, 'YYYY B.C.') FROM T"); + assertResult("2013", stat, "SELECT TO_CHAR(DATE '2013-12-30', 'YYYY') FROM DUAL"); + assertResult("013", stat, "SELECT TO_CHAR(DATE '2013-12-30', 'YYY') FROM DUAL"); + assertResult("13", stat, "SELECT TO_CHAR(DATE '2013-12-30', 'YY') FROM DUAL"); + assertResult("3", stat, "SELECT TO_CHAR(DATE '2013-12-30', 'Y') FROM DUAL"); + // ISO week year + assertResult("2014", stat, "SELECT TO_CHAR(DATE '2013-12-30', 'IYYY') FROM DUAL"); + assertResult("014", stat, "SELECT TO_CHAR(DATE '2013-12-30', 'IYY') FROM DUAL"); + assertResult("14", stat, "SELECT TO_CHAR(DATE '2013-12-30', 'IY') FROM DUAL"); + assertResult("4", stat, "SELECT TO_CHAR(DATE '2013-12-30', 'I') FROM DUAL"); + assertResult("0002", stat, "SELECT TO_CHAR(DATE '-0001-01-01', 'IYYY') FROM DUAL"); + assertResult("0001", stat, "SELECT TO_CHAR(DATE '-0001-01-04', 'IYYY') FROM DUAL"); + assertResult("0004", stat, "SELECT TO_CHAR(DATE '-0004-01-01', 'IYYY') FROM DUAL"); + assertResult("08:12 AM", stat, "SELECT TO_CHAR(X, 'HH:MI AM') FROM T"); + assertResult("08:12 A.M.", stat, "SELECT TO_CHAR(X, 'HH:MI A.M.') FROM T"); + assertResult("02:04 P.M.", stat, "SELECT TO_CHAR(X, 'HH:MI A.M.') FROM U"); + assertResult("08:12 AM", stat, "SELECT TO_CHAR(X, 'HH:MI PM') FROM T"); + assertResult("02:04 PM", stat, "SELECT TO_CHAR(X, 'HH:MI PM') FROM U"); + assertResult("08:12 A.M.", stat, "SELECT TO_CHAR(X, 'HH:MI P.M.') FROM T"); + assertResult("12 PM", stat, "SELECT TO_CHAR(TIME '12:00:00', 'HH AM')"); + assertResult("12 AM", stat, "SELECT TO_CHAR(TIME '00:00:00', 'HH AM')"); + assertResult("A.M.", stat, "SELECT TO_CHAR(X, 'P.M.') FROM T"); + assertResult("a.m.", stat, "SELECT TO_CHAR(X, 'p.M.') FROM T"); + assertResult("a.m.", stat, "SELECT TO_CHAR(X, 'p.m.') FROM T"); + assertResult("AM", stat, "SELECT TO_CHAR(X, 'PM') FROM T"); + assertResult("Am", stat, "SELECT TO_CHAR(X, 'Pm') FROM T"); + assertResult("am", stat, "SELECT TO_CHAR(X, 'pM') FROM T"); + assertResult("am", stat, "SELECT TO_CHAR(X, 'pm') FROM T"); + assertResult("2", stat, "SELECT TO_CHAR(X, 'D') FROM T"); + assertResult("2", stat, "SELECT TO_CHAR(X, 'd') FROM T"); + expected = String.format("%tA", timestamp1979); + expected = expected.substring(0, 1).toUpperCase() + expected.substring(1); + String spaces = " "; + String first9 = (expected + spaces).substring(0, 9); + assertResult(StringUtils.toUpperEnglish(first9), + stat, "SELECT TO_CHAR(X, 'DAY') FROM T"); + assertResult(first9, + stat, "SELECT TO_CHAR(X, 'Day') FROM T"); + assertResult(first9.toLowerCase(), + stat, "SELECT TO_CHAR(X, 'day') FROM T"); + assertResult(first9.toLowerCase(), + stat, "SELECT TO_CHAR(X, 'dAY') FROM T"); + assertResult(expected, + stat, "SELECT TO_CHAR(X, 'fmDay') FROM T"); + assertResult("12", stat, "SELECT TO_CHAR(X, 'DD') FROM T"); + assertResult("316", stat, "SELECT TO_CHAR(X, 'DDD') FROM T"); + assertResult("316", stat, "SELECT TO_CHAR(X, 'DdD') FROM T"); + assertResult("316", stat, "SELECT TO_CHAR(X, 'dDD') FROM T"); + assertResult("316", stat, "SELECT TO_CHAR(X, 'ddd') FROM T"); + expected = String.format("%1$tA, %1$tB %1$te, %1$tY", timestamp1979); + assertResult(expected, stat, + "SELECT TO_CHAR(X, 'DL') FROM T"); + assertResult("11/12/1979", stat, "SELECT TO_CHAR(X, 'DS') FROM T"); + assertResult("11/12/1979", stat, "SELECT TO_CHAR(X, 'Ds') FROM T"); + assertResult("11/12/1979", stat, "SELECT TO_CHAR(X, 'dS') FROM T"); + assertResult("11/12/1979", stat, "SELECT TO_CHAR(X, 'ds') FROM T"); + expected = String.format("%1$ta", timestamp1979); + assertResult(expected.toUpperCase(), stat, "SELECT TO_CHAR(X, 'DY') FROM T"); + assertResult(Capitalization.CAPITALIZE.apply(expected), stat, "SELECT TO_CHAR(X, 'Dy') FROM T"); + assertResult(expected.toLowerCase(), stat, "SELECT TO_CHAR(X, 'dy') FROM T"); + assertResult(expected.toLowerCase(), stat, "SELECT TO_CHAR(X, 'dY') FROM T"); + assertResult("08:12:34.560000000", stat, + "SELECT TO_CHAR(X, 'HH:MI:SS.FF') FROM T"); + assertResult("08:12:34.5", stat, + "SELECT TO_CHAR(X, 'HH:MI:SS.FF1') FROM T"); + assertResult("08:12:34.56", stat, + "SELECT TO_CHAR(X, 'HH:MI:SS.FF2') FROM T"); + assertResult("08:12:34.560", stat, + "SELECT TO_CHAR(X, 'HH:MI:SS.FF3') FROM T"); + assertResult("08:12:34.5600", stat, + "SELECT TO_CHAR(X, 'HH:MI:SS.FF4') FROM T"); + assertResult("08:12:34.56000", stat, + "SELECT TO_CHAR(X, 'HH:MI:SS.FF5') FROM T"); + assertResult("08:12:34.560000", stat, + "SELECT TO_CHAR(X, 'HH:MI:SS.FF6') FROM T"); + assertResult("08:12:34.5600000", stat, + "SELECT TO_CHAR(X, 'HH:MI:SS.FF7') FROM T"); + assertResult("08:12:34.56000000", stat, + "SELECT TO_CHAR(X, 'HH:MI:SS.FF8') FROM T"); + assertResult("08:12:34.560000000", stat, + "SELECT TO_CHAR(X, 'HH:MI:SS.FF9') FROM T"); + assertResult("012345678", stat, + "SELECT TO_CHAR(TIME '0:00:00.012345678', 'FF') FROM T"); + assertResult("00", stat, + "SELECT TO_CHAR(TIME '0:00:00.000', 'FF2') FROM T"); + assertResult("08:12", stat, "SELECT TO_CHAR(X, 'HH:MI') FROM T"); + assertResult("08:12", stat, "SELECT TO_CHAR(X, 'HH12:MI') FROM T"); + assertResult("08:12", stat, "SELECT TO_CHAR(X, 'HH24:MI') FROM T"); + assertResult("46", stat, "SELECT TO_CHAR(X, 'IW') FROM T"); + assertResult("46", stat, "SELECT TO_CHAR(X, 'WW') FROM T"); + assertResult("2", stat, "SELECT TO_CHAR(X, 'W') FROM T"); + assertResult("9", stat, "SELECT TO_CHAR(X, 'I') FROM T"); + assertResult("79", stat, "SELECT TO_CHAR(X, 'IY') FROM T"); + assertResult("979", stat, "SELECT TO_CHAR(X, 'IYY') FROM T"); + assertResult("1979", stat, "SELECT TO_CHAR(X, 'IYYY') FROM T"); + assertResult("2444190", stat, "SELECT TO_CHAR(X, 'J') FROM T"); + assertResult("12", stat, "SELECT TO_CHAR(X, 'MI') FROM T"); + assertResult("11", stat, "SELECT TO_CHAR(X, 'MM') FROM T"); + assertResult("11", stat, "SELECT TO_CHAR(X, 'Mm') FROM T"); + assertResult("11", stat, "SELECT TO_CHAR(X, 'mM') FROM T"); + assertResult("11", stat, "SELECT TO_CHAR(X, 'mm') FROM T"); + expected = String.format("%1$tb", timestamp1979); + expected = stripTrailingPeriod(expected); + expected = expected.substring(0, 1).toUpperCase() + expected.substring(1); + assertResult(expected.toUpperCase(), stat, + "SELECT TO_CHAR(X, 'MON') FROM T"); + assertResult(expected, stat, + "SELECT TO_CHAR(X, 'Mon') FROM T"); + assertResult(expected.toLowerCase(), stat, + "SELECT TO_CHAR(X, 'mon') FROM T"); + expected = String.format("%1$tB", timestamp1979); + expected = (expected + " ").substring(0, 9); + assertResult(expected.toUpperCase(), stat, + "SELECT TO_CHAR(X, 'MONTH') FROM T"); + assertResult(Capitalization.CAPITALIZE.apply(expected), stat, + "SELECT TO_CHAR(X, 'Month') FROM T"); + assertResult(expected.toLowerCase(), stat, + "SELECT TO_CHAR(X, 'month') FROM T"); + assertResult(Capitalization.CAPITALIZE.apply(expected.trim()), stat, + "SELECT TO_CHAR(X, 'fmMonth') FROM T"); + assertResult("4", stat, "SELECT TO_CHAR(X, 'Q') FROM T"); + assertResult("XI", stat, "SELECT TO_CHAR(X, 'RM') FROM T"); + assertResult("xi", stat, "SELECT TO_CHAR(X, 'rm') FROM T"); + assertResult("Xi", stat, "SELECT TO_CHAR(X, 'Rm') FROM T"); + assertResult("79", stat, "SELECT TO_CHAR(X, 'RR') FROM T"); + assertResult("1979", stat, "SELECT TO_CHAR(X, 'RRRR') FROM T"); + assertResult("34", stat, "SELECT TO_CHAR(X, 'SS') FROM T"); + assertResult("29554", stat, "SELECT TO_CHAR(X, 'SSSSS') FROM T"); + expected = new SimpleDateFormat("h:mm:ss aa").format(timestamp1979); + if (Locale.getDefault().getLanguage().equals(Locale.ENGLISH.getLanguage())) { + assertEquals("8:12:34 AM", expected); + } + assertResult(expected, stat, "SELECT TO_CHAR(X, 'TS') FROM T"); + assertResult(tzLongName, stat, "SELECT TO_CHAR(X, 'TZR') FROM T"); + assertResult(tzShortName, stat, "SELECT TO_CHAR(X, 'TZD') FROM T"); + assertResult("GMT+10:30", stat, + "SELECT TO_CHAR(TIMESTAMP WITH TIME ZONE '2010-01-01 0:00:00+10:30', 'TZR')"); + assertResult("GMT+10:30", stat, + "SELECT TO_CHAR(TIMESTAMP WITH TIME ZONE '2010-01-01 0:00:00+10:30', 'TZD')"); + + assertResult("-10", stat, "SELECT TO_CHAR(TIMESTAMP WITH TIME ZONE '2010-01-01 0:00:00-10:00', 'TZH')"); + assertResult("+10", stat, "SELECT TO_CHAR(TIMESTAMP WITH TIME ZONE '2010-01-01 0:00:00+10:00', 'TZH')"); + assertResult("+00", stat, "SELECT TO_CHAR(TIMESTAMP WITH TIME ZONE '2010-01-01 0:00:00+00:00', 'TZH')"); + assertResult("50", stat, "SELECT TO_CHAR(TIMESTAMP WITH TIME ZONE '2010-01-01 0:00:00+00:50', 'TZM')"); + assertResult("00", stat, "SELECT TO_CHAR(TIMESTAMP WITH TIME ZONE '2010-01-01 0:00:00+00:00', 'TZM')"); + assertResult("-10:50", stat, "SELECT TO_CHAR(TIMESTAMP WITH TIME ZONE '2010-01-01 0:00:00-10:50', 'TZH:TZM')"); + assertResult("+10:50", stat, "SELECT TO_CHAR(TIMESTAMP WITH TIME ZONE '2010-01-01 0:00:00+10:50', 'TZH:TZM')"); + assertResult("+00:00", stat, "SELECT TO_CHAR(TIMESTAMP WITH TIME ZONE '2010-01-01 0:00:00+00:00', 'TZH:TZM')"); + + expected = String.format("%f", 1.1).substring(1, 2); + assertResult(expected, stat, "SELECT TO_CHAR(X, 'X') FROM T"); + expected = String.format("%,d", 1979); + assertResult(expected, stat, "SELECT TO_CHAR(X, 'Y,YYY') FROM T"); + assertResult("1979", stat, "SELECT TO_CHAR(X, 'YYYY') FROM T"); + assertResult("1979", stat, "SELECT TO_CHAR(X, 'SYYYY') FROM T"); + assertResult("-0100", stat, "SELECT TO_CHAR(X, 'SYYYY') FROM U"); + assertResult("979", stat, "SELECT TO_CHAR(X, 'YYY') FROM T"); + assertResult("79", stat, "SELECT TO_CHAR(X, 'YY') FROM T"); + assertResult("9", stat, "SELECT TO_CHAR(X, 'Y') FROM T"); + assertResult("7979", stat, "SELECT TO_CHAR(X, 'yyfxyy') FROM T"); + assertThrows(ErrorCode.INVALID_TO_CHAR_FORMAT, stat, + "SELECT TO_CHAR(X, 'A') FROM T"); + + assertResult("01-1 2000-01 1999-52", stat, "SELECT TO_CHAR(DATE '2000-01-01', 'MM-W YYYY-WW IYYY-IW')"); + assertResult("01-1 2000-01 1999-52", stat, "SELECT TO_CHAR(DATE '2000-01-02', 'MM-W YYYY-WW IYYY-IW')"); + assertResult("01-1 2000-01 2000-01", stat, "SELECT TO_CHAR(DATE '2000-01-03', 'MM-W YYYY-WW IYYY-IW')"); + assertResult("01-1 2000-01 2000-01", stat, "SELECT TO_CHAR(DATE '2000-01-04', 'MM-W YYYY-WW IYYY-IW')"); + assertResult("01-1 2000-01 2000-01", stat, "SELECT TO_CHAR(DATE '2000-01-05', 'MM-W YYYY-WW IYYY-IW')"); + assertResult("01-1 2000-01 2000-01", stat, "SELECT TO_CHAR(DATE '2000-01-06', 'MM-W YYYY-WW IYYY-IW')"); + assertResult("01-1 2000-01 2000-01", stat, "SELECT TO_CHAR(DATE '2000-01-07', 'MM-W YYYY-WW IYYY-IW')"); + assertResult("01-2 2000-02 2000-01", stat, "SELECT TO_CHAR(DATE '2000-01-08', 'MM-W YYYY-WW IYYY-IW')"); + assertResult("02-1 2000-05 2000-05", stat, "SELECT TO_CHAR(DATE '2000-02-01', 'MM-W YYYY-WW IYYY-IW')"); + assertResult("12-5 2000-53 2000-52", stat, "SELECT TO_CHAR(DATE '2000-12-31', 'MM-W YYYY-WW IYYY-IW')"); + + // check a bug we had when the month or day of the month is 1 digit + stat.executeUpdate("TRUNCATE TABLE T"); + stat.executeUpdate("INSERT INTO T VALUES (TIMESTAMP '1985-01-01 08:12:34.560')"); + assertResult("19850101", stat, "SELECT TO_CHAR(X, 'YYYYMMDD') FROM T"); + + conn.close(); + } + + private static String stripTrailingPeriod(String expected) { + // CLDR provider appends period on some locales + int l = expected.length() - 1; + if (expected.charAt(l) == '.') + expected = expected.substring(0, l); + return expected; + } + + private void testToCharFromNumber() throws SQLException { + deleteDb("functions"); + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + Locale.setDefault(new Locale("en")); + + Locale locale = Locale.getDefault(); + Currency currency = Currency.getInstance(locale.getCountry().length() == 2 ? locale : Locale.US); + String cc = currency.getCurrencyCode(); + String cs = currency.getSymbol(); + + assertResult(".45", stat, + "SELECT TO_CHAR(0.45) FROM DUAL"); + assertResult("12923", stat, + "SELECT TO_CHAR(12923) FROM DUAL"); + assertResult(" 12923.00", stat, + "SELECT TO_CHAR(12923, '99999.99', 'NLS_CURRENCY = BTC') FROM DUAL"); + assertResult("12923.", stat, + "SELECT TO_CHAR(12923, 'FM99999.99', 'NLS_CURRENCY = BTC') FROM DUAL"); + assertResult("######", stat, + "SELECT TO_CHAR(12345, '9,999') FROM DUAL"); + assertResult("######", stat, + "SELECT TO_CHAR(1234567, '9,999') FROM DUAL"); + assertResult(" 12,345", stat, + "SELECT TO_CHAR(12345, '99,999') FROM DUAL"); + assertResult(" 123,45", stat, + "SELECT TO_CHAR(12345, '999,99') FROM DUAL"); + assertResult("######", stat, + "SELECT TO_CHAR(12345, '9.999') FROM DUAL"); + assertResult("#######", stat, + "SELECT TO_CHAR(12345, '99.999') FROM DUAL"); + assertResult("########", stat, + "SELECT TO_CHAR(12345, '999.999') FROM DUAL"); + assertResult("#########", stat, + "SELECT TO_CHAR(12345, '9999.999') FROM DUAL"); + assertResult(" 12345.000", stat, + "SELECT TO_CHAR(12345, '99999.999') FROM DUAL"); + assertResult("###", stat, + "SELECT TO_CHAR(12345, '$9') FROM DUAL"); + assertResult("#####", stat, + "SELECT TO_CHAR(12345, '$999') FROM DUAL"); + assertResult("######", stat, + "SELECT TO_CHAR(12345, '$9999') FROM DUAL"); + String expected = String.format("%,d", 12345); + if (locale == Locale.ENGLISH) { + assertResult(String.format("%5s12345", cs), stat, + "SELECT TO_CHAR(12345, '$99999999') FROM DUAL"); + assertResult(String.format("%6s12,345.35", cs), stat, + "SELECT TO_CHAR(12345.345, '$99,999,999.99') FROM DUAL"); + assertResult(String.format("%5s%s", cs, expected), stat, + "SELECT TO_CHAR(12345.345, '$99g999g999') FROM DUAL"); + assertResult(" " + cs + "123.45", stat, + "SELECT TO_CHAR(123.45, 'L999.99') FROM DUAL"); + assertResult(" -" + cs + "123.45", stat, + "SELECT TO_CHAR(-123.45, 'L999.99') FROM DUAL"); + assertResult(cs + "123.45", stat, + "SELECT TO_CHAR(123.45, 'FML999.99') FROM DUAL"); + assertResult(" " + cs + "123.45", stat, + "SELECT TO_CHAR(123.45, 'U999.99') FROM DUAL"); + assertResult(" " + cs + "123.45", stat, + "SELECT TO_CHAR(123.45, 'u999.99') FROM DUAL"); + + } + assertResult(" 12,345.35", stat, + "SELECT TO_CHAR(12345.345, '99,999,999.99') FROM DUAL"); + assertResult("12,345.35", stat, + "SELECT TO_CHAR(12345.345, 'FM99,999,999.99') FROM DUAL"); + assertResult(" 00,012,345.35", stat, + "SELECT TO_CHAR(12345.345, '00,000,000.00') FROM DUAL"); + assertResult("00,012,345.35", stat, + "SELECT TO_CHAR(12345.345, 'FM00,000,000.00') FROM DUAL"); + assertResult("###", stat, + "SELECT TO_CHAR(12345, '09') FROM DUAL"); + assertResult("#####", stat, + "SELECT TO_CHAR(12345, '0999') FROM DUAL"); + assertResult(" 00012345", stat, + "SELECT TO_CHAR(12345, '09999999') FROM DUAL"); + assertResult(" 0000012345", stat, + "SELECT TO_CHAR(12345, '0009999999') FROM DUAL"); + assertResult("###", stat, + "SELECT TO_CHAR(12345, '90') FROM DUAL"); + assertResult("#####", stat, + "SELECT TO_CHAR(12345, '9990') FROM DUAL"); + assertResult(" 12345", stat, + "SELECT TO_CHAR(12345, '99999990') FROM DUAL"); + assertResult(" 12345", stat, + "SELECT TO_CHAR(12345, '9999999000') FROM DUAL"); + assertResult(" 12345", stat, + "SELECT TO_CHAR(12345, '9999999990') FROM DUAL"); + assertResult("12345", stat, + "SELECT TO_CHAR(12345, 'FM9999999990') FROM DUAL"); + assertResult(" 12345.2300", stat, + "SELECT TO_CHAR(12345.23, '9999999.9000') FROM DUAL"); + assertResult(" 12345", stat, + "SELECT TO_CHAR(12345, '9999999') FROM DUAL"); + assertResult(" 12345", stat, + "SELECT TO_CHAR(12345, '999999') FROM DUAL"); + assertResult(" 12345", stat, + "SELECT TO_CHAR(12345, '99999') FROM DUAL"); + assertResult(" 12345", stat, + "SELECT TO_CHAR(12345, '00000') FROM DUAL"); + assertResult("#####", stat, + "SELECT TO_CHAR(12345, '9999') FROM DUAL"); + assertResult("#####", stat, + "SELECT TO_CHAR(12345, '0000') FROM DUAL"); + assertResult(" -12345", stat, + "SELECT TO_CHAR(-12345, '99999999') FROM DUAL"); + assertResult(" -12345", stat, + "SELECT TO_CHAR(-12345, '9999999') FROM DUAL"); + assertResult(" -12345", stat, + "SELECT TO_CHAR(-12345, '999999') FROM DUAL"); + assertResult("-12345", stat, + "SELECT TO_CHAR(-12345, '99999') FROM DUAL"); + assertResult("#####", stat, + "SELECT TO_CHAR(-12345, '9999') FROM DUAL"); + assertResult("####", stat, + "SELECT TO_CHAR(-12345, '999') FROM DUAL"); + assertResult(" 0", stat, + "SELECT TO_CHAR(0, '9999999') FROM DUAL"); + assertResult(" 00.30", stat, + "SELECT TO_CHAR(0.3, '00.99') FROM DUAL"); + assertResult("00.3", stat, + "SELECT TO_CHAR(0.3, 'FM00.99') FROM DUAL"); + assertResult(" 00.30", stat, + "SELECT TO_CHAR(0.3, '00.00') FROM DUAL"); + assertResult(" .30000", stat, + "SELECT TO_CHAR(0.3, '99.00000') FROM DUAL"); + assertResult(".30000", stat, + "SELECT TO_CHAR(0.3, 'FM99.00000') FROM DUAL"); + assertResult(" 00.30", stat, + "SELECT TO_CHAR(0.3, 'B00.99') FROM DUAL"); + assertResult(" .30", stat, + "SELECT TO_CHAR(0.3, 'B99.99') FROM DUAL"); + assertResult(" .30", stat, + "SELECT TO_CHAR(0.3, '99.99') FROM DUAL"); + assertResult(".3", stat, + "SELECT TO_CHAR(0.3, 'FMB99.99') FROM DUAL"); + assertResult(" 00.30", stat, + "SELECT TO_CHAR(0.3, 'B09.99') FROM DUAL"); + assertResult(" 00.30", stat, + "SELECT TO_CHAR(0.3, 'B00.00') FROM DUAL"); + assertResult(" " + cc + "123.45", stat, + "SELECT TO_CHAR(123.45, 'C999.99') FROM DUAL"); + assertResult(" -" + cc + "123.45", stat, + "SELECT TO_CHAR(-123.45, 'C999.99') FROM DUAL"); + assertResult(" " + cc + "123.45", stat, + "SELECT TO_CHAR(123.45, 'C999,999.99') FROM DUAL"); + assertResult(" " + cc + "123", stat, + "SELECT TO_CHAR(123.45, 'C999g999') FROM DUAL"); + assertResult(cc + "123.45", stat, + "SELECT TO_CHAR(123.45, 'FMC999,999.99') FROM DUAL"); + expected = String.format("%.2f", 0.33f).substring(1); + assertResult(" " + expected, stat, + "SELECT TO_CHAR(0.326, '99D99') FROM DUAL"); + assertResult(" 1.2E+02", stat, + "SELECT TO_CHAR(123.456, '9.9EEEE') FROM DUAL"); + assertResult(" 1.2E+14", stat, + "SELECT TO_CHAR(123456789012345, '9.9EEEE') FROM DUAL"); + assertResult(" 1E+02", stat, "SELECT TO_CHAR(123.456, '9EEEE') FROM DUAL"); + assertResult(" 1E+02", stat, "SELECT TO_CHAR(123.456, '999EEEE') FROM DUAL"); + assertResult(" 1E-03", stat, "SELECT TO_CHAR(.00123456, '999EEEE') FROM DUAL"); + assertResult(" 1E+00", stat, "SELECT TO_CHAR(1, '999EEEE') FROM DUAL"); + assertResult(" -1E+00", stat, "SELECT TO_CHAR(-1, '999EEEE') FROM DUAL"); + assertResult(" 1.23456000E+02", stat, + "SELECT TO_CHAR(123.456, '00.00000000EEEE') FROM DUAL"); + assertResult("1.23456000E+02", stat, + "SELECT TO_CHAR(123.456, 'fm00.00000000EEEE') FROM DUAL"); + expected = String.format("%,d", 1234567); + assertResult(" " + expected, stat, + "SELECT TO_CHAR(1234567, '9G999G999') FROM DUAL"); + assertResult("-" + expected, stat, + "SELECT TO_CHAR(-1234567, '9G999G999') FROM DUAL"); + assertResult("123.45-", stat, "SELECT TO_CHAR(-123.45, '999.99MI') FROM DUAL"); + assertResult("123.45-", stat, "SELECT TO_CHAR(-123.45, '999.99mi') FROM DUAL"); + assertResult("123.45-", stat, "SELECT TO_CHAR(-123.45, '999.99mI') FROM DUAL"); + assertResult("230.00-", stat, "SELECT TO_CHAR(-230, '999.99MI') FROM DUAL"); + assertResult("230-", stat, "SELECT TO_CHAR(-230, '999MI') FROM DUAL"); + assertResult("123.45 ", stat, "SELECT TO_CHAR(123.45, '999.99MI') FROM DUAL"); + assertResult("230.00 ", stat, "SELECT TO_CHAR(230, '999.99MI') FROM DUAL"); + assertResult("230 ", stat, "SELECT TO_CHAR(230, '999MI') FROM DUAL"); + assertResult("230", stat, "SELECT TO_CHAR(230, 'FM999MI') FROM DUAL"); + assertResult("<230>", stat, "SELECT TO_CHAR(-230, '999PR') FROM DUAL"); + assertResult("<230>", stat, "SELECT TO_CHAR(-230, '999pr') FROM DUAL"); + assertResult("<230>", stat, "SELECT TO_CHAR(-230, 'fm999pr') FROM DUAL"); + assertResult(" 230 ", stat, "SELECT TO_CHAR(230, '999PR') FROM DUAL"); + assertResult("230", stat, "SELECT TO_CHAR(230, 'FM999PR') FROM DUAL"); + assertResult("0", stat, "SELECT TO_CHAR(0, 'fm999pr') FROM DUAL"); + assertResult(" XI", stat, "SELECT TO_CHAR(11, 'RN') FROM DUAL"); + assertResult("XI", stat, "SELECT TO_CHAR(11, 'FMRN') FROM DUAL"); + assertResult("xi", stat, "SELECT TO_CHAR(11, 'FMrN') FROM DUAL"); + assertResult(" XI", stat, "SELECT TO_CHAR(11, 'RN') FROM DUAL;"); + assertResult(" xi", stat, "SELECT TO_CHAR(11, 'rN') FROM DUAL"); + assertResult(" xi", stat, "SELECT TO_CHAR(11, 'rn') FROM DUAL"); + assertResult(" +42", stat, "SELECT TO_CHAR(42, 'S999') FROM DUAL"); + assertResult(" +42", stat, "SELECT TO_CHAR(42, 's999') FROM DUAL"); + assertResult(" 42+", stat, "SELECT TO_CHAR(42, '999S') FROM DUAL"); + assertResult(" -42", stat, "SELECT TO_CHAR(-42, 'S999') FROM DUAL"); + assertResult(" 42-", stat, "SELECT TO_CHAR(-42, '999S') FROM DUAL"); + assertResult("42", stat, "SELECT TO_CHAR(42, 'TM') FROM DUAL"); + assertResult("-42", stat, "SELECT TO_CHAR(-42, 'TM') FROM DUAL"); + assertResult("4212341241234.23412342", stat, + "SELECT TO_CHAR(4212341241234.23412342, 'tm') FROM DUAL"); + assertResult(".23412342", stat, "SELECT TO_CHAR(0.23412342, 'tm') FROM DUAL"); + assertResult(" 12300", stat, "SELECT TO_CHAR(123, '999V99') FROM DUAL"); + assertResult("######", stat, "SELECT TO_CHAR(1234, '999V99') FROM DUAL"); + assertResult("123400", stat, "SELECT TO_CHAR(1234, 'FM9999v99') FROM DUAL"); + assertResult("1234", stat, "SELECT TO_CHAR(123.4, 'FM9999V9') FROM DUAL"); + assertResult("123", stat, "SELECT TO_CHAR(123.4, 'FM9999V') FROM DUAL"); + assertResult("123400000", stat, + "SELECT TO_CHAR(123.4, 'FM9999V090909') FROM DUAL"); + assertResult("##", stat, "SELECT TO_CHAR(123, 'X') FROM DUAL"); + assertResult(" 7B", stat, "SELECT TO_CHAR(123, 'XX') FROM DUAL"); + assertResult(" 7b", stat, "SELECT TO_CHAR(123, 'Xx') FROM DUAL"); + assertResult(" 7b", stat, "SELECT TO_CHAR(123, 'xX') FROM DUAL"); + assertResult(" 7B", stat, "SELECT TO_CHAR(123, 'XXXX') FROM DUAL"); + assertResult(" 007B", stat, "SELECT TO_CHAR(123, '000X') FROM DUAL"); + assertResult(" 007B", stat, "SELECT TO_CHAR(123, '0XXX') FROM DUAL"); + assertResult("####", stat, "SELECT TO_CHAR(123456789, 'FMXXX') FROM DUAL"); + assertResult("7B", stat, "SELECT TO_CHAR(123, 'FMXX') FROM DUAL"); + assertResult("C6", stat, "SELECT TO_CHAR(197.6, 'FMXX') FROM DUAL"); + assertResult(" 7", stat, "SELECT TO_CHAR(7, 'XX') FROM DUAL"); + assertResult("123", stat, "SELECT TO_CHAR(123, 'TM') FROM DUAL"); + assertResult("123", stat, "SELECT TO_CHAR(123, 'tm') FROM DUAL"); + assertResult("123", stat, "SELECT TO_CHAR(123, 'tM9') FROM DUAL"); + assertResult("1.23E+02", stat, "SELECT TO_CHAR(123, 'TME') FROM DUAL"); + assertResult("1.23456789012345E+14", stat, + "SELECT TO_CHAR(123456789012345, 'TME') FROM DUAL"); + assertResult("4.5E-01", stat, "SELECT TO_CHAR(0.45, 'TME') FROM DUAL"); + assertResult("4.5E-01", stat, "SELECT TO_CHAR(0.45, 'tMe') FROM DUAL"); + assertThrows(ErrorCode.INVALID_TO_CHAR_FORMAT, stat, + "SELECT TO_CHAR(123.45, '999.99q') FROM DUAL"); + assertThrows(ErrorCode.INVALID_TO_CHAR_FORMAT, stat, + "SELECT TO_CHAR(123.45, 'fm999.99q') FROM DUAL"); + assertThrows(ErrorCode.INVALID_TO_CHAR_FORMAT, stat, + "SELECT TO_CHAR(123.45, 'q999.99') FROM DUAL"); + + // ISSUE-115 + assertResult("0.123", stat, "select to_char(0.123, 'FM0.099') from dual;"); + assertResult("1.123", stat, "select to_char(1.1234, 'FM0.099') from dual;"); + assertResult("1.1234", stat, "select to_char(1.1234, 'FM0.0999') from dual;"); + assertResult("1.023", stat, "select to_char(1.023, 'FM0.099') from dual;"); + assertResult("0.012", stat, "select to_char(0.012, 'FM0.099') from dual;"); + assertResult("0.123", stat, "select to_char(0.123, 'FM0.099') from dual;"); + assertResult("0.001", stat, "select to_char(0.001, 'FM0.099') from dual;"); + assertResult("0.001", stat, "select to_char(0.0012, 'FM0.099') from dual;"); + assertResult("0.002", stat, "select to_char(0.0019, 'FM0.099') from dual;"); + final char decimalSeparator = DecimalFormatSymbols.getInstance().getDecimalSeparator(); + final String oneDecimal = "0" + decimalSeparator + "0"; + final String twoDecimals = "0" + decimalSeparator + "00"; + assertResult(oneDecimal, stat, "select to_char(0, 'FM0D099') from dual;"); + assertResult(twoDecimals, stat, "select to_char(0., 'FM0D009') from dual;"); + assertResult("0" + decimalSeparator + "000000000", + stat, "select to_char(0.000000000, 'FM0D999999999') from dual;"); + assertResult("0" + decimalSeparator, stat, "select to_char(0, 'FM0D9') from dual;"); + assertResult(oneDecimal, stat, "select to_char(0.0, 'FM0D099') from dual;"); + assertResult(twoDecimals, stat, "select to_char(0.00, 'FM0D009') from dual;"); + assertResult(twoDecimals, stat, "select to_char(0, 'FM0D009') from dual;"); + assertResult(oneDecimal, stat, "select to_char(0, 'FM0D09') from dual;"); + assertResult(oneDecimal, stat, "select to_char(0, 'FM0D0') from dual;"); + + assertResult("10,000,000.", stat, + "SELECT TO_CHAR(CAST(10000000 AS DOUBLE PRECISION), 'FM999,999,999.99') FROM DUAL"); + conn.close(); + } + + private void testToCharFromText() throws SQLException { + deleteDb("functions"); + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + assertResult("abc", stat, "SELECT TO_CHAR('abc') FROM DUAL"); + conn.close(); + } + + private void testAnnotationProcessorsOutput() { + try { + System.setProperty(TestAnnotationProcessor.MESSAGES_KEY, "WARNING,foo1|ERROR,foo2"); + callCompiledFunction("test_annotation_processor_warn_and_error"); + fail(); + } catch (SQLException e) { + assertEquals(ErrorCode.SYNTAX_ERROR_1, e.getErrorCode()); + assertContains(e.getMessage(), "foo1"); + assertContains(e.getMessage(), "foo2"); + } finally { + System.clearProperty(TestAnnotationProcessor.MESSAGES_KEY); + } + } + + private void testSignal() throws SQLException { + deleteDb("functions"); + + Connection conn = getConnection("functions"); + Statement stat = conn.createStatement(); + + assertThrows(ErrorCode.INVALID_VALUE_2, stat).execute("call signal('00145', 'success class is invalid')"); + assertThrows(ErrorCode.INVALID_VALUE_2, stat).execute("call signal('foo', 'SQLSTATE has 5 chars')"); + assertThrows(ErrorCode.INVALID_VALUE_2, stat) + .execute("call signal('Ab123', 'SQLSTATE has only digits or upper-case letters')"); + try { + stat.execute("call signal('AB123', 'some custom error')"); + fail("Should have thrown"); + } catch (SQLException e) { + assertEquals("AB123", e.getSQLState()); + assertContains(e.getMessage(), "some custom error"); + } + + 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"); + + Date before = new Date(); + + Connection conn = getConnection("functions"); + conn.setAutoCommit(false); + Statement stat = conn.createStatement(); + + + final String formatted; + final ResultSet rs = stat.executeQuery( + "select to_char(current_timestamp(9), 'YYYY MM DD HH24 MI SS FF3') from dual"); + rs.next(); + formatted = rs.getString(1); + rs.close(); + + Date after = new Date(); + + Date parsed = new SimpleDateFormat("y M d H m s S").parse(formatted); + + assertFalse(parsed.before(before)); + assertFalse(parsed.after(after)); + conn.close(); + } + + + private void testThatCurrentTimestampStaysTheSameWithinATransaction() + throws SQLException, InterruptedException { + deleteDb("functions"); + Connection conn = getConnection("functions"); + conn.setAutoCommit(false); + Statement stat = conn.createStatement(); + + Timestamp first; + ResultSet rs = stat.executeQuery("select CURRENT_TIMESTAMP from DUAL"); + rs.next(); + first = rs.getTimestamp(1); + rs.close(); + + Thread.sleep(1); + + Timestamp second; + rs = stat.executeQuery("select CURRENT_TIMESTAMP from DUAL"); + rs.next(); + second = rs.getTimestamp(1); + rs.close(); + + assertEquals(first, second); + conn.close(); + } + + private void testThatCurrentTimestampUpdatesOutsideATransaction() + throws SQLException, InterruptedException { + if (config.lazy && config.networked) { + return; + } + deleteDb("functions"); + Connection conn = getConnection("functions"); + conn.setAutoCommit(true); + Statement stat = conn.createStatement(); + + Timestamp first; + ResultSet rs = stat.executeQuery("select CURRENT_TIMESTAMP from DUAL"); + rs.next(); + first = rs.getTimestamp(1); + rs.close(); + + Thread.sleep(1); + + Timestamp second; + rs = stat.executeQuery("select CURRENT_TIMESTAMP from DUAL"); + rs.next(); + second = rs.getTimestamp(1); + rs.close(); + + assertTrue(second.after(first)); + 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"); + conn.setAutoCommit(true); + Statement stat = conn.createStatement(); + + assertThrows(ErrorCode.FUNCTION_ALIAS_ALREADY_EXISTS_1, stat).execute("create alias CURRENT_TIMESTAMP for '" + + getClass().getName() + ".currentTimestamp'"); + + stat.execute("set BUILTIN_ALIAS_OVERRIDE true"); + + stat.execute("create alias CURRENT_TIMESTAMP for '" + getClass().getName() + ".currentTimestampOverride'"); + + assertCallResult("3141", stat, "CURRENT_TIMESTAMP"); + + conn.close(); + } + + private void callCompiledFunction(String functionName) throws SQLException { + deleteDb("functions"); + try (Connection conn = getConnection("functions")) { + Statement stat = conn.createStatement(); + ResultSet rs; + stat.execute("create alias " + functionName + " AS " + + "$$ boolean " + functionName + "() " + + "{ return true; } $$;"); + + PreparedStatement stmt = conn.prepareStatement( + "select " + functionName + "() from dual"); + rs = stmt.executeQuery(); + rs.next(); + assertEquals(Boolean.class.getName(), rs.getObject(1).getClass().getName()); + + stat.execute("drop alias " + functionName + ""); + } + } + + private void assertCallResult(String expected, Statement stat, String sql) + throws SQLException { + ResultSet rs = stat.executeQuery("CALL " + sql); + rs.next(); + String s = rs.getString(1); + assertEquals(expected, s); + } + + /** + * This method is called via reflection from the database. + * + * @param value the blob + * @return the input stream + */ + public static BufferedInputStream blob2stream(Blob value) + throws SQLException { + if (value == null) { + return null; + } + BufferedInputStream bufferedInStream = new BufferedInputStream( + value.getBinaryStream()); + return bufferedInStream; + } + + /** + * This method is called via reflection from the database. + * + * @param value the blob + * @return the blob + */ + public static Blob blob(Blob value) { + return value; + } + + /** + * This method is called via reflection from the database. + * + * @param value the blob + * @return the blob + */ + public static Clob clob(Clob value) { + return value; + } + + /** + * This method is called via reflection from the database. + * + * @param value the input stream + * @return the buffered input stream + */ + public static BufferedInputStream stream2stream(InputStream value) { + if (value == null) { + return null; + } + BufferedInputStream bufferedInStream = new BufferedInputStream(value); + return bufferedInStream; + } + + /** + * This method is called via reflection from the database. + * + * @param conn the connection + * @param id the test id + * @param name the text + * @return the count + */ + public static int addRow(Connection conn, int id, String name) + throws SQLException { + conn.createStatement().execute( + "INSERT INTO TEST VALUES(" + id + ", '" + name + "')"); + ResultSet rs = conn.createStatement().executeQuery( + "SELECT COUNT(*) FROM TEST"); + rs.next(); + int result = rs.getInt(1); + rs.close(); + return result; + } + + /** + * This method is called via reflection from the database. + * + * @param conn the connection + * @param sql the SQL statement + * @return the result set + */ + public static ResultSet select(Connection conn, String sql) + throws SQLException { + Statement stat = conn.createStatement( + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + return stat.executeQuery(sql); + } + + /** + * This method is called via reflection from the database. + * + * @param conn the connection + * @return the result set + */ + public static ResultSet selectMaxId(Connection conn) throws SQLException { + return conn.createStatement().executeQuery( + "SELECT MAX(ID) FROM TEST"); + } + + /** + * This method is called via reflection from the database. + * + * @return the test array + */ + public static String[] getArray() { + return new String[] { "0", "Hello" }; + } + + /** + * This method is called via reflection from the database. + * + * @param conn the connection + * @return the result set + */ + public static ResultSet resultSetWithNull(Connection conn) throws SQLException { + PreparedStatement statement = conn.prepareStatement( + "select null from system_range(1,1)"); + return statement.executeQuery(); + } + + /** + * Test method to create a simple result set. + * + * @param rowCount the number of rows + * @param ip an int + * @param bp a boolean + * @param fp a float + * @param dp a double + * @param lp a long + * @param byParam a byte + * @param sp a short + * @return a result set + */ + public static ResultSet simpleResultSet(Integer rowCount, int ip, + boolean bp, float fp, double dp, long lp, byte byParam, short sp) { + SimpleResultSet rs = new SimpleResultSet(); + rs.addColumn("ID", Types.INTEGER, 10, 0); + rs.addColumn("NAME", Types.VARCHAR, 255, 0); + if (rowCount == null) { + if (ip != 0 || bp || fp != 0.0 || dp != 0.0 || + sp != 0 || lp != 0 || byParam != 0) { + throw new AssertionError("params not 0/false"); + } + } + if (rowCount != null) { + if (ip != 1 || !bp || fp != 1.0 || dp != 1.0 || + sp != 1 || lp != 1 || byParam != 1) { + throw new AssertionError("params not 1/true"); + } + if (rowCount >= 1) { + rs.addRow(0, "Hello"); + } + if (rowCount >= 2) { + rs.addRow(1, "World"); + } + } + return rs; + } + + /** + * This method is called via reflection from the database. + * + * @param value the value + * @return the square root + */ + public static int root(int value) { + if (value < 0) { + TestBase.logError("function called but should not", null); + } + return (int) Math.sqrt(value); + } + + /** + * This method is called via reflection from the database. + * + * @return 1 + */ + public static double mean() { + return 1; + } + + /** + * This method is called via reflection from the database. + * + * @param dec the value + * @return the value + */ + public static BigDecimal noOp(BigDecimal dec) { + return dec; + } + + /** + * This method is called via reflection from the database. + * + * @return the count + */ + public static int getCount() { + return count++; + } + + private static void setCount(int newCount) { + count = newCount; + } + + /** + * This method is called via reflection from the database. + * + * @param s the string + * @return the string, reversed + */ + public static String reverse(String s) { + return new StringBuilder(s).reverse().toString(); + } + + /** + * This method is called via reflection from the database. + * + * @param values the values + * @return the mean value + */ + public static double mean(double... values) { + double sum = 0; + for (double x : values) { + sum += x; + } + return sum / values.length; + } + + /** + * This method is called via reflection from the database. + * + * @param conn the connection + * @param values the values + * @return the mean value + */ + public static double mean2(Connection conn, double... values) { + conn.getClass(); + double sum = 0; + for (double x : values) { + sum += x; + } + return sum / values.length; + } + + /** + * This method is called via reflection from the database. + * + * @param prefix the print prefix + * @param values the values + * @return the text + */ + public static String printMean(String prefix, double... values) { + double sum = 0; + for (double x : values) { + sum += x; + } + return prefix + ": " + (int) (sum / values.length); + } + + /** + * This method is called via reflection from the database. + * + * @param a the first UUID + * @param b the second UUID + * @return a xor b + */ + public static UUID xorUUID(UUID a, UUID b) { + return new UUID(a.getMostSignificantBits() ^ b.getMostSignificantBits(), + a.getLeastSignificantBits() ^ b.getLeastSignificantBits()); + } + + /** + * This method is called via reflection from the database. + * + * @param args the argument list + * @return an array of one element + */ + public static String[] dynamic(String[] args) { + StringBuilder buff = new StringBuilder(); + for (Object a : args) { + buff.append(a); + } + return new String[] { buff.toString() }; + } + + /** + * This method is called via reflection from the database. + * + * @return a fixed number + */ + public static long currentTimestampOverride() { + return 3141; + } + + @Override + public void add(Object value) { + // ignore + } + + @Override + public Object getResult() { + return new BigDecimal("1.6"); + } + + @Override + public int getType(int[] inputTypes) { + if (inputTypes.length != 1 || inputTypes[0] != Types.INTEGER) { + throw new RuntimeException("unexpected data type"); + } + return Types.DECIMAL; + } + +} diff --git a/h2/src/test/org/h2/test/db/TestGeneralCommonTableQueries.java b/h2/src/test/org/h2/test/db/TestGeneralCommonTableQueries.java new file mode 100644 index 0000000..654da27 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestGeneralCommonTableQueries.java @@ -0,0 +1,581 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import org.h2.test.TestAll; +import org.h2.test.TestBase; + +/** + * Test non-recursive queries using WITH, but more than one common table defined. + */ +public class TestGeneralCommonTableQueries extends AbstractBaseForCommonTableExpressions { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testSimpleSelect(); + testImpliedColumnNames(); + testChainedQuery(); + testParameterizedQuery(); + testNumberedParameterizedQuery(); + testColumnNames(); + + testInsert(); + testUpdate(); + testDelete(); + testMerge(); + testCreateTable(); + testNestedSQL(); + testSimple4RowRecursiveQuery(); + testSimple2By4RowRecursiveQuery(); + testSimple3RowRecursiveQueryWithLazyEval(); + testSimple3RowRecursiveQueryDropAllObjects(); + } + + private void testSimpleSelect() throws Exception { + deleteDb("commonTableExpressionQueries"); + Connection conn = getConnection("commonTableExpressionQueries"); + Statement stat; + PreparedStatement prep; + ResultSet rs; + + stat = conn.createStatement(); + final String simpleTwoColumnQuery = "with " + + "t1(n) as (select 1 as first) " + + ",t2(n) as (select 2 as first) " + + "select * from t1 union all select * from t2"; + rs = stat.executeQuery(simpleTwoColumnQuery); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertFalse(rs.next()); + + prep = conn.prepareStatement(simpleTwoColumnQuery); + rs = prep.executeQuery(); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertFalse(rs.next()); + + prep = conn.prepareStatement("with " + + "t1(n) as (select 2 as first) " + + ",t2(n) as (select 3 as first) " + + "select * from t1 union all select * from t2 where n<>?"); + + prep.setInt(1, 0); + rs = prep.executeQuery(); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertTrue(rs.next()); + assertEquals(3, rs.getInt(1)); + assertFalse(rs.next()); + + prep = conn.prepareStatement("with " + + "t1(n) as (select 2 as first) " + + ",t2(n) as (select 3 as first) " + + ",t3(n) as (select 4 as first) " + + "select * from t1 union all select * from t2 union all select * from t3 where n<>?"); + + prep.setInt(1, 4); + rs = prep.executeQuery(); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertTrue(rs.next()); + assertEquals(3, rs.getInt(1)); + assertFalse(rs.next()); + + conn.close(); + deleteDb("commonTableExpressionQueries"); + } + + private void testImpliedColumnNames() throws Exception { + deleteDb("commonTableExpressionQueries"); + Connection conn = getConnection("commonTableExpressionQueries"); + PreparedStatement prep; + ResultSet rs; + + prep = conn.prepareStatement("with " + + "t1 as (select 2 as first_col) " + + ",t2 as (select first_col+1 from t1) " + + ",t3 as (select 4 as first_col) " + + "select * from t1 union all select * from t2 union all select * from t3 where first_col<>?"); + + prep.setInt(1, 4); + rs = prep.executeQuery(); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertTrue(rs.next()); + assertEquals(3, rs.getInt("FIRST_COL")); + assertFalse(rs.next()); + assertEquals(rs.getMetaData().getColumnCount(), 1); + assertEquals("FIRST_COL", rs.getMetaData().getColumnLabel(1)); + + conn.close(); + deleteDb("commonTableExpressionQueries"); + } + + private void testChainedQuery() throws Exception { + deleteDb("commonTableExpressionQueries"); + Connection conn = getConnection("commonTableExpressionQueries"); + PreparedStatement prep; + ResultSet rs; + + prep = conn.prepareStatement( + " WITH t1 AS (" + + " SELECT 1 AS FIRST_COLUMN" + + ")," + + " t2 AS (" + + " SELECT FIRST_COLUMN+1 AS FIRST_COLUMN FROM t1 " + + ") " + + "SELECT sum(FIRST_COLUMN) FROM t2"); + + rs = prep.executeQuery(); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertFalse(rs.next()); + + conn.close(); + deleteDb("commonTableExpressionQueries"); + } + + private void testParameterizedQuery() throws Exception { + deleteDb("commonTableExpressionQueries"); + Connection conn = getConnection("commonTableExpressionQueries"); + PreparedStatement prep; + ResultSet rs; + + prep = conn.prepareStatement("WITH t1 AS (" + + " SELECT X, 'T1' FROM SYSTEM_RANGE(?,?)" + + ")," + + "t2 AS (" + + " SELECT X, 'T2' FROM SYSTEM_RANGE(?,?)" + + ") " + + "SELECT * FROM t1 UNION ALL SELECT * FROM t2 " + + "UNION ALL SELECT X, 'Q' FROM SYSTEM_RANGE(?,?)"); + prep.setInt(1, 1); + prep.setInt(2, 2); + prep.setInt(3, 3); + prep.setInt(4, 4); + prep.setInt(5, 5); + prep.setInt(6, 6); + rs = prep.executeQuery(); + + for (int n: new int[]{1, 2, 3, 4, 5, 6}) { + assertTrue(rs.next()); + assertEquals(n, rs.getInt(1)); + } + assertFalse(rs.next()); + + // call it twice + rs = prep.executeQuery(); + + for (int n: new int[]{1, 2, 3, 4, 5, 6}) { + assertTrue(rs.next()); + assertEquals(n, rs.getInt(1)); + } + assertFalse(rs.next()); + + conn.close(); + deleteDb("commonTableExpressionQueries"); + } + + private void testNumberedParameterizedQuery() throws Exception { + deleteDb("commonTableExpressionQueries"); + Connection conn = getConnection("commonTableExpressionQueries"); + PreparedStatement prep; + ResultSet rs; + + conn.setAutoCommit(false); + + prep = conn.prepareStatement("WITH t1 AS (" + +" SELECT R.X, 'T1' FROM SYSTEM_RANGE(?1,?2) R" + +")," + +"t2 AS (" + +" SELECT R.X, 'T2' FROM SYSTEM_RANGE(?3,?4) R" + +") " + +"SELECT * FROM t1 UNION ALL SELECT * FROM t2 UNION ALL SELECT X, 'Q' FROM SYSTEM_RANGE(?5,?6)"); + prep.setInt(1, 1); + prep.setInt(2, 2); + prep.setInt(3, 3); + prep.setInt(4, 4); + prep.setInt(5, 5); + prep.setInt(6, 6); + rs = prep.executeQuery(); + + for (int n : new int[] { 1, 2, 3, 4, 5, 6 }) { + assertTrue(rs.next()); + assertEquals(n, rs.getInt(1)); + } + assertEquals("X", rs.getMetaData().getColumnLabel(1)); + assertEquals("'T1'", rs.getMetaData().getColumnLabel(2)); + + assertFalse(rs.next()); + + try { + prep = conn.prepareStatement("SELECT * FROM t1 UNION ALL SELECT * FROM t2 "+ + "UNION ALL SELECT X, 'Q' FROM SYSTEM_RANGE(5,6)"); + rs = prep.executeQuery(); + fail("Temp view T1 was accessible after previous WITH statement finished "+ + "- but should not have been."); + } catch (SQLException e) { + // ensure the T1 table has been removed even without auto commit + assertContains(e.getMessage(), "Table \"T1\" not found (this database is empty);"); + } + + conn.close(); + deleteDb("commonTableExpressionQueries"); + } + + private void testInsert() throws Exception { + deleteDb("commonTableExpressionQueries"); + Connection conn = getConnection("commonTableExpressionQueries"); + Statement stat; + PreparedStatement prep; + ResultSet rs; + int rowCount; + + stat = conn.createStatement(); + stat.execute("CREATE TABLE T1 ( ID INT GENERATED BY DEFAULT AS IDENTITY, X INT NULL, Y VARCHAR(100) NULL )"); + + prep = conn.prepareStatement("WITH v1 AS (" + + " SELECT R.X, 'X1' AS Y FROM SYSTEM_RANGE(?1,?2) R" + + ")" + + "INSERT INTO T1 (X,Y) SELECT v1.X, v1.Y FROM v1"); + prep.setInt(1, 1); + prep.setInt(2, 2); + rowCount = prep.executeUpdate(); + + assertEquals(2, rowCount); + + rs = stat.executeQuery("SELECT ID, X,Y FROM T1"); + + for (int n : new int[]{1, 2}) { + assertTrue(rs.next()); + assertTrue(rs.getInt(1) != 0); + assertEquals(n, rs.getInt(2)); + assertEquals("X1", rs.getString(3)); + } + conn.close(); + deleteDb("commonTableExpressionQueries"); + } + + private void testUpdate() throws Exception { + deleteDb("commonTableExpressionQueries"); + Connection conn = getConnection("commonTableExpressionQueries"); + Statement stat; + PreparedStatement prep; + ResultSet rs; + int rowCount; + + stat = conn.createStatement(); + stat.execute("CREATE TABLE IF NOT EXISTS T1 AS SELECT R.X AS ID, R.X, 'X1' AS Y FROM SYSTEM_RANGE(1,2) R"); + + prep = conn.prepareStatement("WITH v1 AS (" + +" SELECT R.X, 'X1' AS Y FROM SYSTEM_RANGE(?1,?2) R" + +")" + +"UPDATE T1 SET Y = 'Y1' WHERE X IN ( SELECT v1.X FROM v1 )"); + prep.setInt(1, 1); + prep.setInt(2, 2); + rowCount = prep.executeUpdate(); + + assertEquals(2, rowCount); + + rs = stat.executeQuery("SELECT ID, X,Y FROM T1"); + + for (int n : new int[] { 1, 2 }) { + assertTrue(rs.next()); + assertTrue(rs.getInt(1) != 0); + assertEquals(n, rs.getInt(2)); + assertEquals("Y1", rs.getString(3)); + } + conn.close(); + deleteDb("commonTableExpressionQueries"); + } + + private void testDelete() throws Exception { + deleteDb("commonTableExpressionQueries"); + Connection conn = getConnection("commonTableExpressionQueries"); + Statement stat; + PreparedStatement prep; + ResultSet rs; + int rowCount; + + stat = conn.createStatement(); + stat.execute("CREATE TABLE IF NOT EXISTS T1 AS SELECT R.X AS ID, R.X, 'X1' AS Y FROM SYSTEM_RANGE(1,2) R"); + + prep = conn.prepareStatement("WITH v1 AS (" + +" SELECT R.X, 'X1' AS Y FROM SYSTEM_RANGE(1,2) R" + +")" + +"DELETE FROM T1 WHERE X IN ( SELECT v1.X FROM v1 )"); + rowCount = prep.executeUpdate(); + + assertEquals(2, rowCount); + + rs = stat.executeQuery("SELECT ID, X,Y FROM T1"); + + assertFalse(rs.next()); + + conn.close(); + deleteDb("commonTableExpressionQueries"); + } + + private void testMerge() throws Exception { + deleteDb("commonTableExpressionQueries"); + Connection conn = getConnection("commonTableExpressionQueries"); + Statement stat; + PreparedStatement prep; + ResultSet rs; + int rowCount; + + stat = conn.createStatement(); + stat.execute("CREATE TABLE IF NOT EXISTS T1 AS SELECT R.X AS ID, R.X, 'X1' AS Y FROM SYSTEM_RANGE(1,2) R"); + + prep = conn.prepareStatement("WITH v1 AS (" + +" SELECT R.X, 'X1' AS Y FROM SYSTEM_RANGE(1,3) R" + +")" + +"MERGE INTO T1 KEY(ID) SELECT v1.X AS ID, v1.X, v1.Y FROM v1"); + rowCount = prep.executeUpdate(); + + assertEquals(3, rowCount); + + rs = stat.executeQuery("SELECT ID, X,Y FROM T1"); + + for (int n : new int[] { 1, 2, 3 }) { + assertTrue(rs.next()); + assertTrue(rs.getInt(1) != 0); + assertEquals(n, rs.getInt(2)); + assertEquals("X1", rs.getString(3)); + } + conn.close(); + deleteDb("commonTableExpressionQueries"); + } + + private void testCreateTable() throws Exception { + deleteDb("commonTableExpressionQueries"); + Connection conn = getConnection("commonTableExpressionQueries"); + Statement stat; + PreparedStatement prep; + ResultSet rs; + boolean success; + + stat = conn.createStatement(); + prep = conn.prepareStatement("WITH v1 AS (" + +" SELECT R.X, 'X1' AS Y FROM SYSTEM_RANGE(1,3) R" + +")" + +"CREATE TABLE IF NOT EXISTS T1 AS SELECT v1.X AS ID, v1.X, v1.Y FROM v1"); + success = prep.execute(); + + assertEquals(false, success); + + rs = stat.executeQuery("SELECT ID, X,Y FROM T1"); + + for (int n : new int[] { 1, 2, 3 }) { + assertTrue(rs.next()); + assertTrue(rs.getInt(1) != 0); + assertEquals(n, rs.getInt(2)); + assertEquals("X1", rs.getString(3)); + } + conn.close(); + deleteDb("commonTableExpressionQueries"); + } + + private void testNestedSQL() throws Exception { + deleteDb("commonTableExpressionQueries"); + Connection conn = getConnection("commonTableExpressionQueries"); + PreparedStatement prep; + ResultSet rs; + + prep = conn.prepareStatement( + "WITH T1 AS ( "+ + " SELECT * "+ + " FROM TABLE ( "+ + " K VARCHAR = ('a', 'b'), "+ + " V INTEGER = (1, 2) "+ + " ) "+ + "), "+ + " "+ + " "+ + "T2 AS ( "+ + " SELECT * "+ + " FROM TABLE ( "+ + " K VARCHAR = ('a', 'b'), "+ + " V INTEGER = (3, 4) "+ + " ) "+ + "), "+ + " "+ + " "+ + "JOIN_CTE AS ( "+ + " SELECT T1.* "+ + " "+ + " FROM "+ + " T1 "+ + " JOIN T2 ON ( "+ + " T1.K = T2.K "+ + " ) "+ + ") "+ + " "+ + "SELECT * FROM JOIN_CTE"); + + rs = prep.executeQuery(); + + for (String keyLetter : new String[] { "a", "b" }) { + assertTrue(rs.next()); + assertContains("ab", rs.getString(1)); + assertEquals(rs.getString(1), keyLetter); + assertTrue(rs.getInt(2) != 0); + } + conn.close(); + deleteDb("commonTableExpressionQueries"); + } + + private void testColumnNames() throws Exception { + deleteDb("commonTableExpressionQueries"); + Connection conn = getConnection("commonTableExpressionQueries"); + PreparedStatement prep; + ResultSet rs; + + conn.setAutoCommit(false); + + prep = conn.prepareStatement("WITH t1 AS (" + +" SELECT 1 AS ONE, R.X AS TWO, 'T1' AS THREE, X FROM SYSTEM_RANGE(1,1) R" + +")" + +"SELECT * FROM t1"); + rs = prep.executeQuery(); + + for (int n : new int[] { 1 }) { + assertTrue(rs.next()); + assertEquals(n, rs.getInt(1)); + assertEquals(n, rs.getInt(4)); + } + assertEquals("ONE", rs.getMetaData().getColumnLabel(1)); + assertEquals("TWO", rs.getMetaData().getColumnLabel(2)); + assertEquals("THREE", rs.getMetaData().getColumnLabel(3)); + assertEquals("X", rs.getMetaData().getColumnLabel(4)); + + assertFalse(rs.next()); + + conn.close(); + deleteDb("commonTableExpressionQueries"); + } + + private void testSimple4RowRecursiveQuery() throws Exception { + + String[] expectedRowData = new String[]{"|1", "|2", "|3"}; + String[] expectedColumnTypes = new String[]{"INTEGER"}; + String[] expectedColumnNames = new String[]{"N"}; + + String setupSQL = "-- do nothing"; + String withQuery = "with recursive r(n) as (\n"+ + "(select 1) union all (select n+1 from r where n < 3)\n"+ + ")\n"+ + "select n from r"; + + int maxRetries = 3; + int expectedNumberOfRows = expectedRowData.length; + + testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL, + withQuery, maxRetries - 1, expectedColumnTypes, false); + + } + + private void testSimple2By4RowRecursiveQuery() throws Exception { + + String[] expectedRowData = new String[]{"|0|1|10", "|1|2|11", "|2|3|12", "|3|4|13"}; + String[] expectedColumnTypes = new String[]{"INTEGER", "INTEGER", "INTEGER"}; + String[] expectedColumnNames = new String[]{"K", "N", "N2"}; + + String setupSQL = "-- do nothing"; + String withQuery = "with \n"+ + "r1(n,k) as ((select 1, 0) union all (select n+1,k+1 from r1 where n <= 3)),"+ + "r2(n,k) as ((select 10,0) union all (select n+1,k+1 from r2 where n <= 13))"+ + "select r1.k, r1.n, r2.n AS n2 from r1 inner join r2 ON r1.k= r2.k "; + + int maxRetries = 3; + int expectedNumberOfRows = expectedRowData.length; + + testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL, + withQuery, maxRetries - 1, expectedColumnTypes, false); + + } + + private void testSimple3RowRecursiveQueryWithLazyEval() throws Exception { + if (config.lazy && config.networked) { + return; + } + + String[] expectedRowData = new String[]{"|6"}; + String[] expectedColumnTypes = new String[]{"BIGINT"}; + String[] expectedColumnNames = new String[]{"SUM(N)"}; + + // back up the config - to restore it after this test + TestAll backupConfig = config; + config = new TestAll(); + + try { + // Test with settings: lazy mvStore memory multiThreaded + // connection url is + // mem:script;MV_STORE=true;LOG=1;LOCK_TIMEOUT=50; + // LAZY_QUERY_EXECUTION=1 + config.lazy = true; + config.memory = true; + + String setupSQL = "--no config set"; + String withQuery = "select sum(n) from (\n" + +" with recursive r(n) as (\n" + +" (select 1) union all (select n+1 from r where n < 3) \n" + +" )\n" + +" select n from r \n" + +")\n"; + + int maxRetries = 10; + int expectedNumberOfRows = expectedRowData.length; + + testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, + setupSQL, withQuery, maxRetries - 1, expectedColumnTypes, false); + } finally { + config = backupConfig; + } + } + + private void testSimple3RowRecursiveQueryDropAllObjects() throws Exception { + + String[] expectedRowData = new String[]{"|6"}; + String[] expectedColumnTypes = new String[]{"BIGINT"}; + String[] expectedColumnNames = new String[]{"SUM(N)"}; + + String setupSQL = "DROP ALL OBJECTS;"; + String withQuery = "select sum(n) from (" + +" with recursive r(n) as (" + +" (select 1) union all (select n+1 from r where n < 3)" + +" )," + +" dummyUnusedCte(n) as (" + +" select 1 " + +" )" + +" select n from r" + +")"; + + int maxRetries = 10; + int expectedNumberOfRows = expectedRowData.length; + + testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL, + withQuery, maxRetries - 1, expectedColumnTypes, false); + } +} diff --git a/h2/src/test/org/h2/test/db/TestIgnoreCatalogs.java b/h2/src/test/org/h2/test/db/TestIgnoreCatalogs.java new file mode 100644 index 0000000..7e07120 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestIgnoreCatalogs.java @@ -0,0 +1,240 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * @author aschoerk + */ +public class TestIgnoreCatalogs extends TestDb { + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + canCommentOn(); + canUseDefaultSchema(); + canYetIdentifyWrongCatalogName(); + canUseSettingInUrl(); + canUseSetterSyntax(); + canCatalogNameEqualSchemaName(); + canUseCatalogAtIndexName(); + canCommentOn(); + canAllCombined(); + doesNotAcceptEmptySchemaWhenNotMSSQL(); + } + + private void doesNotAcceptEmptySchemaWhenNotMSSQL() throws SQLException { + try (Connection conn = getConnection("ignoreCatalogs;IGNORE_CATALOGS=TRUE")) { + try (Statement stat = conn.createStatement()) { + prepareDbAndSetDefaultSchema(stat); + stat.execute("set schema dbo"); + stat.execute("create table catalog1.dbo.test(id int primary key, name varchar(255))"); + assertThrows(ErrorCode.SYNTAX_ERROR_2, stat, "comment on table catalog1..test is 'table comment3'"); + assertThrows(ErrorCode.SYNTAX_ERROR_2, stat, "create table catalog1..test2(id int primary key, " + + "name varchar(255))"); + stat.execute("comment on table catalog1.dbo.test is 'table comment1'"); + stat.execute("insert into test values(1, 'Hello')"); + stat.execute("insert into cat.dbo.test values(2, 'Hello2')"); + assertThrows(ErrorCode.SYNTAX_ERROR_2, stat, "comment on column catalog1...test.id is 'id comment1'"); + assertThrows(ErrorCode.SYNTAX_ERROR_2, stat, "comment on column catalog1..test..id is 'id comment1'"); + assertThrows(ErrorCode.SYNTAX_ERROR_2, stat, "comment on column ..test..id is 'id comment1'"); + } + } finally { + deleteDb("ignoreCatalogs"); + } + } + + private void canCommentOn() throws Exception { + try (Connection conn = getConnection("ignoreCatalogs;MODE=MSSQLSERVER;IGNORE_CATALOGS=TRUE;")) { + try (Statement stat = conn.createStatement()) { + prepareDbAndSetDefaultSchema(stat); + stat.execute("create table catalog1.dbo.test(id int primary key, name varchar(255))"); + stat.execute("comment on table catalog1.dbo.test is 'table comment1'"); + stat.execute("comment on table dbo.test is 'table comment2'"); + stat.execute("comment on table catalog1..test is 'table comment3'"); + stat.execute("comment on table test is 'table comment4'"); + stat.execute("comment on column catalog1..test.id is 'id comment1'"); + stat.execute("comment on column catalog1.dbo.test.id is 'id comment1'"); + stat.execute("comment on column dbo.test.id is 'id comment1'"); + stat.execute("comment on column test.id is 'id comment1'"); + assertThrows(ErrorCode.SYNTAX_ERROR_2, stat, "comment on column catalog1...id is 'id comment1'"); + assertThrows(ErrorCode.SYNTAX_ERROR_2, stat, "comment on column catalog1...test.id is 'id comment1'"); + assertThrows(ErrorCode.SYNTAX_ERROR_2, stat, "comment on column catalog1..test..id is 'id comment1'"); + assertThrows(ErrorCode.SYNTAX_ERROR_2, stat, "comment on column ..test..id is 'id comment1'"); + assertThrows(ErrorCode.SYNTAX_ERROR_2, stat, "comment on column test..id is 'id comment1'"); + assertThrows(ErrorCode.SYNTAX_ERROR_2, stat, "comment on column .PUBLIC.TEST.ID 'id comment1'"); + assertThrows(ErrorCode.SYNTAX_ERROR_2, stat, "comment on column .TEST.ID 'id comment1'"); + } + } finally { + deleteDb("ignoreCatalogs"); + } + } + + private void canUseDefaultSchema() throws Exception { + try (Connection conn = getConnection("ignoreCatalogs;MODE=MSSQLSERVER;IGNORE_CATALOGS=TRUE;")) { + try (Statement stat = conn.createStatement()) { + prepareDbAndSetDefaultSchema(stat); + stat.execute("create table catalog1..test(id int primary key, name varchar(255))"); + + stat.execute("create table test2(id int primary key, name varchar(255))"); + // expect table already exists + assertThrows(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, stat, + "create table catalog2.dbo.test(id int primary key, name varchar(255))"); + stat.execute("insert into test values(1, 'Hello')"); + stat.execute("insert into test2 values(1, 'Hello')"); + } + } finally { + deleteDb("ignoreCatalogs"); + } + } + + private void canUseSettingInUrl() throws Exception { + try (Connection conn = getConnection("ignoreCatalogs;MODE=MSSQLSERVER;IGNORE_CATALOGS=TRUE;")) { + try (Statement stat = conn.createStatement()) { + prepareDb(stat); + stat.execute("create table catalog1.dbo.test(id int primary key, name varchar(255))"); + // expect table already exists + assertThrows(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, stat, + "create table catalog2.dbo.test(id int primary key, name varchar(255))"); + stat.execute("insert into dbo.test values(1, 'Hello')"); + } + } finally { + deleteDb("ignoreCatalogs"); + } + + } + + private void canUseSetterSyntax() throws Exception { + try (Connection conn = getConnection("ignoreCatalogs;MODE=MSSQLSERVER;")) { + try (Statement stat = conn.createStatement()) { + prepareDb(stat); + stat.execute("set IGNORE_CATALOGS=TRUE"); + stat.execute("create table catalog1.dbo.test(id int primary key, name varchar(255))"); + // expect table already exists + assertThrows(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, stat, + "create table catalog2.dbo.test(id int primary key, name varchar(255))"); + stat.execute("insert into dbo.test values(1, 'Hello')"); + } + } finally { + deleteDb("ignoreCatalogs"); + } + } + + private void canCatalogNameEqualSchemaName() throws Exception { + try (Connection conn = getConnection("ignoreCatalogs;MODE=MSSQLSERVER;")) { + try (Statement stat = conn.createStatement()) { + prepareDb(stat); + stat.execute("set IGNORE_CATALOGS=TRUE"); + stat.execute("create table dbo.dbo.test(id int primary key, name varchar(255))"); + // expect object already exists + assertThrows(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, stat, + "create table catalog2.dbo.test(id int primary key, name varchar(255))"); + stat.execute("insert into dbo.test values(1, 'Hello')"); + } + } finally { + deleteDb("ignoreCatalogs"); + } + } + + private void canYetIdentifyWrongCatalogName() throws Exception { + try (Connection conn = getConnection("ignoreCatalogs;MODE=MSSQLSERVER;")) { + try (Statement stat = conn.createStatement()) { + prepareDb(stat); + // works, since catalog name equals database name + stat.execute("create table ignoreCatalogs.dbo.test(id int primary key, name varchar(255))"); + // schema test_x not found error + assertThrows(ErrorCode.SCHEMA_NOT_FOUND_1, stat, + "create table test_x.dbo.test(id int primary key, name varchar(255))"); + assertThrows(ErrorCode.DATABASE_NOT_FOUND_1, stat, "comment on column db..test.id is 'id'"); + } + } finally { + deleteDb("ignoreCatalogs"); + } + } + + private void canUseCatalogAtIndexName() throws Exception { + try (Connection conn = getConnection("ignoreCatalogs;MODE=MSSQLSERVER;")) { + try (Statement stat = conn.createStatement()) { + prepareDb(stat); + stat.execute("set IGNORE_CATALOGS=TRUE"); + stat.execute("create table dbo.dbo.test(id int primary key, name varchar(255))"); + stat.execute("create index i on dbo.dbo.test(id,name)"); + stat.execute("create index dbo.i2 on dbo.dbo.test(id,name)"); + stat.execute("create index catalog.dbo.i3 on dbo.dbo.test(id,name)"); + assertThrows(ErrorCode.SCHEMA_NOT_FOUND_1, stat, + "create index dboNotExistent.i4 on dbo.dbo.test(id,name)"); + // expect object already exists + stat.execute("insert into dbo.test values(1, 'Hello')"); + } + } finally { + deleteDb("ignoreCatalogs"); + } + } + + private void canAllCombined() throws SQLException { + try (Connection conn = getConnection("ignoreCatalogs;MODE=MSSQLSERVER;IGNORE_CATALOGS=TRUE;")) { + try (Statement stat = conn.createStatement()) { + prepareDbAndSetDefaultSchema(stat); + stat.execute("create table dbo.test(id int primary key, name varchar(255))"); + stat.execute("create table catalog1.dbo.test2(id int primary key, name varchar(255))"); + stat.execute("insert into dbo.test values(1, 'Hello')"); + stat.execute("insert into dbo.test2 values(1, 'Hello2')"); + stat.execute("set ignore_catalogs=false"); + assertThrows(ErrorCode.SCHEMA_NOT_FOUND_1, stat, + "insert into catalog1.dbo.test2 values(2, 'Hello2')"); + stat.execute("set ignore_catalogs=true"); + assertResult("1", stat, "select * from test"); + assertResult("1", stat, "select * from test2"); + stat.execute("alter table xxx.dbo.test add column (a varchar(200))"); + stat.execute("alter table xxx..test add column (b varchar(200))"); + stat.execute("alter table test add column (c varchar(200))"); + stat.execute("drop table xxx.dbo.test"); + stat.execute("drop table catalog1.dbo.test2"); + stat.execute("drop table if exists xxx.dbo.test"); + stat.execute("drop table if exists catalog1.dbo.test2"); + stat.execute("set ignore_catalogs=false"); + assertThrows(ErrorCode.SCHEMA_NOT_FOUND_1, stat, + "alter table xxx.dbo.test add column (a varchar(200))"); + assertThrows(ErrorCode.SCHEMA_NOT_FOUND_1, stat, + "alter table xxx..test add column (b varchar(200))"); + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, stat, + "alter table test add column (c varchar(200))"); + assertThrows(ErrorCode.SCHEMA_NOT_FOUND_1, stat, + "drop table if exists xxx.dbo.test"); + assertThrows(ErrorCode.SCHEMA_NOT_FOUND_1, stat, + "drop table if exists xxx2..test"); + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, stat, "drop table test"); + } + } finally { + deleteDb("ignoreCatalogs"); + } + } + + private static void prepareDb(Statement stat) throws SQLException { + stat.execute("drop all objects"); + stat.execute("create schema dbo"); + } + + private static void prepareDbAndSetDefaultSchema(Statement stat) throws SQLException { + prepareDb(stat); + stat.execute("set schema dbo"); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestIndex.java b/h2/src/test/org/h2/test/db/TestIndex.java new file mode 100644 index 0000000..1b2fa80 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestIndex.java @@ -0,0 +1,783 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import org.h2.api.ErrorCode; +import org.h2.command.query.Select; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.SimpleResultSet; +import org.h2.value.ValueInteger; + +/** + * Index tests. + */ +public class TestIndex extends TestDb { + + private static int testFunctionIndexCounter; + + private Connection conn; + private Statement stat; + private final Random random = new Random(); + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + deleteDb("index"); + testOrderIndex(); + testIndexTypes(); + testHashIndexOnMemoryTable(); + testErrorMessage(); + testDuplicateKeyException(); + int to = config.lockTimeout; + config.lockTimeout = 50000; + try { + testConcurrentUpdate(); + } finally { + config.lockTimeout = to; + } + testNonUniqueHashIndex(); + testRenamePrimaryKey(); + testRandomized(); + testDescIndex(); + testHashIndex(); + + if (config.networked && config.big) { + return; + } + + random.setSeed(100); + + deleteDb("index"); + testWideIndex(147); + testWideIndex(313); + testWideIndex(979); + testWideIndex(1200); + testWideIndex(2400); + if (config.big) { + Random r = new Random(); + for (int j = 0; j < 10; j++) { + int i = r.nextInt(3000); + if ((i % 100) == 0) { + println("width: " + i); + } + testWideIndex(i); + } + } + + testLike(); + reconnect(); + testConstraint(); + testLargeIndex(); + testMultiColumnIndex(); + // long time; + // time = System.nanoTime(); + testHashIndex(true, false); + + testHashIndex(false, false); + testHashIndex(true, true); + testHashIndex(false, true); + + testMultiColumnHashIndex(); + + testFunctionIndex(); + + conn.close(); + deleteDb("index"); + + // This test uses own connection + testEnumIndex(); + } + + private void testOrderIndex() throws SQLException { + Connection conn = getConnection("index"); + stat = conn.createStatement(); + stat.execute("create table test(id int, name varchar)"); + stat.execute("insert into test values (2, 'a'), (1, 'a')"); + stat.execute("create index on test(name)"); + ResultSet rs = stat.executeQuery( + "select id from test where name = 'a' order by id"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertFalse(rs.next()); + conn.close(); + deleteDb("index"); + } + + private void testIndexTypes() throws SQLException { + Connection conn = getConnection("index"); + stat = conn.createStatement(); + for (String type : new String[] { "unique", "hash", "unique hash" }) { + stat.execute("create table test(id int)"); + stat.execute("create " + type + " index idx_name on test(id)"); + stat.execute("insert into test select x from system_range(1, 1000)"); + ResultSet rs = stat.executeQuery("select * from test where id=100"); + assertTrue(rs.next()); + assertFalse(rs.next()); + stat.execute("delete from test where id=100"); + rs = stat.executeQuery("select * from test where id=100"); + assertFalse(rs.next()); + rs = stat.executeQuery("select min(id), max(id) from test"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals(1000, rs.getInt(2)); + stat.execute("drop table test"); + } + conn.close(); + deleteDb("index"); + } + + private void testErrorMessage() throws SQLException { + reconnect(); + stat.execute("create table test(id int primary key, name varchar)"); + testErrorMessage("PRIMARY", "KEY", " ON PUBLIC.TEST(ID)"); + stat.execute("create table test(id int, name varchar primary key)"); + testErrorMessage("PRIMARY_KEY_2 ON PUBLIC.TEST(NAME)"); + stat.execute("create table test(id int, name varchar, primary key(id, name))"); + testErrorMessage("PRIMARY_KEY_2 ON PUBLIC.TEST(ID, NAME)"); + stat.execute("create table test(id int, name varchar, primary key(name, id))"); + testErrorMessage("PRIMARY_KEY_2 ON PUBLIC.TEST(NAME, ID)"); + stat.execute("create table test(id int, name int primary key)"); + testErrorMessage("PRIMARY", "KEY", " ON PUBLIC.TEST(NAME)"); + stat.execute("create table test(id int, name int, unique(name))"); + testErrorMessage("CONSTRAINT_INDEX_2 ON PUBLIC.TEST(NAME NULLS FIRST)"); + stat.execute("create table test(id int, name int, " + + "constraint abc unique(name, id))"); + testErrorMessage("ABC_INDEX_2 ON PUBLIC.TEST(NAME NULLS FIRST, ID NULLS FIRST)"); + } + + private void testErrorMessage(String... expected) throws SQLException { + try { + stat.execute("INSERT INTO TEST VALUES(1, 1)"); + stat.execute("INSERT INTO TEST VALUES(1, 1)"); + fail(); + } catch (SQLException e) { + String m = e.getMessage(); + int start = m.indexOf('"'), end = m.lastIndexOf('"'); + String s = m.substring(start + 1, end); + for (String t : expected) { + assertContains(s, t); + } + } + stat.execute("drop table test"); + } + + private void testDuplicateKeyException() throws SQLException { + reconnect(); + stat.execute("create table test(id int primary key, name varchar(255))"); + stat.execute("create unique index idx_test_name on test(name)"); + stat.execute("insert into TEST values(1, 'Hello')"); + try { + stat.execute("insert into TEST values(2, 'Hello')"); + fail(); + } catch (SQLException ex) { + assertEquals(ErrorCode.DUPLICATE_KEY_1, ex.getErrorCode()); + String m = ex.getMessage(); + // The format of the VALUES clause varies a little depending on the + // type of the index, so just test that we're getting useful info + // back. + assertContains(m, "IDX_TEST_NAME ON PUBLIC.TEST(NAME NULLS FIRST)"); + assertContains(m, "'Hello'"); + } + stat.execute("drop table test"); + } + + private static class ConcurrentUpdateThread extends Thread { + private final AtomicInteger concurrentUpdateId, concurrentUpdateValue; + + private final PreparedStatement psInsert, psDelete; + + boolean haveDuplicateKeyException; + + ConcurrentUpdateThread(Connection c, AtomicInteger concurrentUpdateId, + AtomicInteger concurrentUpdateValue) throws SQLException { + this.concurrentUpdateId = concurrentUpdateId; + this.concurrentUpdateValue = concurrentUpdateValue; + psInsert = c.prepareStatement("insert into test(id, v) values (?, ?)"); + psDelete = c.prepareStatement("delete from test where v = ?"); + } + + @Override + public void run() { + for (int i = 0; i < 10000; i++) { + try { + if (Math.random() > 0.05) { + psInsert.setInt(1, concurrentUpdateId.incrementAndGet()); + psInsert.setInt(2, concurrentUpdateValue.get()); + psInsert.executeUpdate(); + } else { + psDelete.setInt(1, concurrentUpdateValue.get()); + psDelete.executeUpdate(); + } + } catch (SQLException ex) { + switch (ex.getErrorCode()) { + case 23505: + haveDuplicateKeyException = true; + break; + case 90131: + // Unlikely but possible + break; + default: + ex.printStackTrace(); + } + } + if (Math.random() > 0.95) + concurrentUpdateValue.incrementAndGet(); + } + } + } + + private void testConcurrentUpdate() throws SQLException { + Connection c = getConnection("index"); + Statement stat = c.createStatement(); + stat.execute("create table test(id int primary key, v int)"); + stat.execute("create unique index idx_value_name on test(v)"); + PreparedStatement check = c.prepareStatement("select v from test"); + ConcurrentUpdateThread[] threads = new ConcurrentUpdateThread[4]; + AtomicInteger concurrentUpdateId = new AtomicInteger(), concurrentUpdateValue = new AtomicInteger(); + + // The same connection + for (int i = 0; i < threads.length; i++) { + threads[i] = new ConcurrentUpdateThread(c, concurrentUpdateId, concurrentUpdateValue); + } + testConcurrentUpdateRun(threads, check); + // Different connections + Connection[] connections = new Connection[threads.length]; + for (int i = 0; i < threads.length; i++) { + Connection c2 = getConnection("index"); + connections[i] = c2; + threads[i] = new ConcurrentUpdateThread(c2, concurrentUpdateId, concurrentUpdateValue); + } + testConcurrentUpdateRun(threads, check); + for (Connection c2 : connections) { + c2.close(); + } + stat.execute("drop table test"); + c.close(); + } + + private void testConcurrentUpdateRun(ConcurrentUpdateThread[] threads, PreparedStatement check) + throws SQLException { + for (ConcurrentUpdateThread t : threads) { + t.start(); + } + boolean haveDuplicateKeyException = false; + for (ConcurrentUpdateThread t : threads) { + try { + t.join(); + haveDuplicateKeyException |= t.haveDuplicateKeyException; + } catch (InterruptedException e) { + } + } + assertTrue("haveDuplicateKeys", haveDuplicateKeyException); + HashSet set = new HashSet<>(); + try (ResultSet rs = check.executeQuery()) { + while (rs.next()) { + if (!set.add(rs.getInt(1))) { + fail("unique index violation"); + } + } + } + } + + private void testNonUniqueHashIndex() throws SQLException { + reconnect(); + stat.execute("create memory table test(id bigint, data bigint)"); + stat.execute("create hash index on test(id)"); + Random rand = new Random(1); + PreparedStatement prepInsert = conn.prepareStatement( + "insert into test values(?, ?)"); + PreparedStatement prepDelete = conn.prepareStatement( + "delete from test where id=?"); + PreparedStatement prepSelect = conn.prepareStatement( + "select count(*) from test where id=?"); + HashMap map = new HashMap<>(); + for (int i = 0; i < 1000; i++) { + long key = rand.nextInt(10) * 1000000000L; + Integer r = map.get(key); + int result = r == null ? 0 : (int) r; + if (rand.nextBoolean()) { + prepSelect.setLong(1, key); + ResultSet rs = prepSelect.executeQuery(); + rs.next(); + assertEquals(result, rs.getInt(1)); + } else { + if (rand.nextBoolean()) { + prepInsert.setLong(1, key); + prepInsert.setInt(2, rand.nextInt()); + prepInsert.execute(); + map.put(key, result + 1); + } else { + prepDelete.setLong(1, key); + prepDelete.execute(); + map.put(key, 0); + } + } + } + stat.execute("drop table test"); + conn.close(); + } + + private void testRenamePrimaryKey() throws SQLException { + if (config.memory) { + return; + } + reconnect(); + stat.execute("create table test(id int not null)"); + stat.execute("alter table test add constraint x primary key(id)"); + ResultSet rs; + rs = conn.getMetaData().getIndexInfo(null, null, "TEST", true, false); + rs.next(); + String old = rs.getString("INDEX_NAME"); + stat.execute("alter index " + old + " rename to y"); + rs = conn.getMetaData().getIndexInfo(null, null, "TEST", true, false); + rs.next(); + assertEquals("Y", rs.getString("INDEX_NAME")); + reconnect(); + rs = conn.getMetaData().getIndexInfo(null, null, "TEST", true, false); + rs.next(); + assertEquals("Y", rs.getString("INDEX_NAME")); + stat.execute("drop table test"); + } + + private void testRandomized() throws SQLException { + boolean reopen = !config.memory; + Random rand = new Random(1); + reconnect(); + stat.execute("drop all objects"); + stat.execute("CREATE TABLE TEST(ID identity default on null)"); + int len = getSize(100, 1000); + for (int i = 0; i < len; i++) { + switch (rand.nextInt(4)) { + case 0: + if (rand.nextInt(10) == 0) { + if (reopen) { + trace("reconnect"); + reconnect(); + } + } + break; + case 1: + trace("insert"); + stat.execute("insert into test(id) values(null)"); + break; + case 2: + trace("delete"); + stat.execute("delete from test"); + break; + case 3: + trace("insert 1-100"); + stat.execute("insert into test select null from system_range(1, 100)"); + break; + } + } + stat.execute("drop table test"); + } + + private void testHashIndex() throws SQLException { + reconnect(); + stat.execute("create table testA(id int primary key, name varchar)"); + stat.execute("create table testB(id int primary key hash, name varchar)"); + int len = getSize(300, 3000); + stat.execute("insert into testA select x, 'Hello' from " + + "system_range(1, " + len + ")"); + stat.execute("insert into testB select x, 'Hello' from " + + "system_range(1, " + len + ")"); + Random rand = new Random(1); + for (int i = 0; i < len; i++) { + int x = rand.nextInt(len); + String sql = ""; + switch (rand.nextInt(3)) { + case 0: + sql = "delete from testA where id = " + x; + break; + case 1: + sql = "update testA set name = " + rand.nextInt(100) + " where id = " + x; + break; + case 2: + sql = "select name from testA where id = " + x; + break; + default: + } + boolean result = stat.execute(sql); + if (result) { + ResultSet rs = stat.getResultSet(); + String s1 = rs.next() ? rs.getString(1) : null; + rs = stat.executeQuery(sql.replace('A', 'B')); + String s2 = rs.next() ? rs.getString(1) : null; + assertEquals(s1, s2); + } else { + int count1 = stat.getUpdateCount(); + int count2 = stat.executeUpdate(sql.replace('A', 'B')); + assertEquals(count1, count2); + } + } + stat.execute("drop table testA, testB"); + conn.close(); + } + + private void reconnect() throws SQLException { + if (conn != null) { + conn.close(); + conn = null; + } + conn = getConnection("index"); + stat = conn.createStatement(); + } + + private void testDescIndex() throws SQLException { + if (config.memory) { + return; + } + ResultSet rs; + reconnect(); + stat.execute("CREATE TABLE TEST(ID INT)"); + stat.execute("CREATE INDEX IDX_ND ON TEST(ID DESC)"); + rs = conn.getMetaData().getIndexInfo(null, null, "TEST", false, false); + rs.next(); + assertEquals("D", rs.getString("ASC_OR_DESC")); + stat.execute("INSERT INTO TEST SELECT X FROM SYSTEM_RANGE(1, 30)"); + rs = stat.executeQuery( + "SELECT COUNT(*) FROM TEST WHERE ID BETWEEN 10 AND 20"); + rs.next(); + assertEquals(11, rs.getInt(1)); + reconnect(); + rs = conn.getMetaData().getIndexInfo(null, null, "TEST", false, false); + rs.next(); + assertEquals("D", rs.getString("ASC_OR_DESC")); + rs = stat.executeQuery( + "SELECT COUNT(*) FROM TEST WHERE ID BETWEEN 10 AND 20"); + rs.next(); + assertEquals(11, rs.getInt(1)); + stat.execute("DROP TABLE TEST"); + + stat.execute("create table test(x int, y int)"); + stat.execute("insert into test values(1, 1), (1, 2)"); + stat.execute("create index test_x_y on test (x desc, y desc)"); + rs = stat.executeQuery("select * from test where x=1 and y<2"); + assertTrue(rs.next()); + + conn.close(); + } + + private String getRandomString(int len) { + StringBuilder buff = new StringBuilder(); + for (int i = 0; i < len; i++) { + buff.append((char) ('a' + random.nextInt(26))); + } + return buff.toString(); + } + + private void testWideIndex(int length) throws SQLException { + reconnect(); + stat.execute("drop all objects"); + stat.execute("CREATE TABLE TEST(ID INT, NAME VARCHAR)"); + stat.execute("CREATE INDEX IDXNAME ON TEST(NAME)"); + for (int i = 0; i < 100; i++) { + stat.execute("INSERT INTO TEST VALUES(" + i + + ", SPACE(" + length + ") || " + i + " )"); + } + ResultSet rs = stat.executeQuery("SELECT * FROM TEST ORDER BY NAME"); + while (rs.next()) { + int id = rs.getInt("ID"); + String name = rs.getString("NAME"); + assertEquals("" + id, name.trim()); + } + if (!config.memory) { + reconnect(); + rs = stat.executeQuery("SELECT * FROM TEST ORDER BY NAME"); + while (rs.next()) { + int id = rs.getInt("ID"); + String name = rs.getString("NAME"); + assertEquals("" + id, name.trim()); + } + } + stat.execute("drop all objects"); + } + + private void testLike() throws SQLException { + reconnect(); + stat.execute("CREATE TABLE ABC(ID INT, NAME VARCHAR)"); + stat.execute("INSERT INTO ABC VALUES(1, 'Hello')"); + PreparedStatement prep = conn.prepareStatement( + "SELECT * FROM ABC WHERE NAME LIKE CAST(? AS VARCHAR)"); + prep.setString(1, "Hi%"); + prep.execute(); + stat.execute("DROP TABLE ABC"); + } + + private void testConstraint() throws SQLException { + if (config.memory) { + return; + } + stat.execute("CREATE TABLE PARENT(ID INT PRIMARY KEY)"); + stat.execute("CREATE TABLE CHILD(ID INT PRIMARY KEY, " + + "PID INT, FOREIGN KEY(PID) REFERENCES PARENT(ID))"); + reconnect(); + stat.execute("DROP TABLE PARENT, CHILD"); + } + + private void testLargeIndex() throws SQLException { + random.setSeed(10); + for (int i = 1; i < 100; i += getSize(1000, 7)) { + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST(NAME VARCHAR(" + i + "))"); + stat.execute("CREATE INDEX IDXNAME ON TEST(NAME)"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST VALUES(?)"); + for (int j = 0; j < getSize(2, 5); j++) { + prep.setString(1, getRandomString(i)); + prep.execute(); + } + if (!config.memory) { + conn.close(); + conn = getConnection("index"); + stat = conn.createStatement(); + } + ResultSet rs = stat.executeQuery( + "SELECT COUNT(*) FROM TEST WHERE NAME > 'mdd'"); + rs.next(); + int count = rs.getInt(1); + trace(i + " count=" + count); + } + + stat.execute("DROP TABLE IF EXISTS TEST"); + } + + private void testHashIndex(boolean primaryKey, boolean hash) + throws SQLException { + if (config.memory) { + return; + } + + reconnect(); + + stat.execute("DROP TABLE IF EXISTS TEST"); + if (primaryKey) { + stat.execute("CREATE TABLE TEST(A INT PRIMARY KEY " + + (hash ? "HASH" : "") + ", B INT)"); + } else { + stat.execute("CREATE TABLE TEST(A INT, B INT)"); + stat.execute("CREATE UNIQUE " + (hash ? "HASH" : "") + " INDEX ON TEST(A)"); + } + PreparedStatement prep; + prep = conn.prepareStatement("INSERT INTO TEST VALUES(?, ?)"); + int len = getSize(5, 1000); + for (int a = 0; a < len; a++) { + prep.setInt(1, a); + prep.setInt(2, a); + prep.execute(); + assertEquals(1, + getValue("SELECT COUNT(*) FROM TEST WHERE A=" + a)); + assertEquals(0, + getValue("SELECT COUNT(*) FROM TEST WHERE A=-1-" + a)); + } + + reconnect(); + + prep = conn.prepareStatement("DELETE FROM TEST WHERE A=?"); + for (int a = 0; a < len; a++) { + if (getValue("SELECT COUNT(*) FROM TEST WHERE A=" + a) != 1) { + assertEquals(1, + getValue("SELECT COUNT(*) FROM TEST WHERE A=" + a)); + } + prep.setInt(1, a); + assertEquals(1, prep.executeUpdate()); + } + assertEquals(0, getValue("SELECT COUNT(*) FROM TEST")); + } + + private void testMultiColumnIndex() throws SQLException { + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST(A INT, B INT)"); + PreparedStatement prep; + prep = conn.prepareStatement("INSERT INTO TEST VALUES(?, ?)"); + int len = getSize(3, 260); + for (int a = 0; a < len; a++) { + prep.setInt(1, a); + prep.setInt(2, a); + prep.execute(); + } + stat.execute("INSERT INTO TEST SELECT A, B FROM TEST"); + stat.execute("CREATE INDEX ON TEST(A, B)"); + prep = conn.prepareStatement("DELETE FROM TEST WHERE A=?"); + for (int a = 0; a < len; a++) { + log("SELECT * FROM TEST"); + assertEquals(2, + getValue("SELECT COUNT(*) FROM TEST WHERE A=" + (len - a - 1))); + assertEquals((len - a) * 2, getValue("SELECT COUNT(*) FROM TEST")); + prep.setInt(1, len - a - 1); + prep.execute(); + } + assertEquals(0, getValue("SELECT COUNT(*) FROM TEST")); + } + + private void testMultiColumnHashIndex() throws SQLException { + if (config.memory) { + return; + } + + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST(A INT, B INT, DATA VARCHAR(255))"); + stat.execute("CREATE UNIQUE HASH INDEX IDX_AB ON TEST(A, B)"); + PreparedStatement prep; + prep = conn.prepareStatement("INSERT INTO TEST VALUES(?, ?, ?)"); + // speed is quadratic (len*len) + int len = getSize(2, 14); + for (int a = 0; a < len; a++) { + for (int b = 0; b < len; b += 2) { + prep.setInt(1, a); + prep.setInt(2, b); + prep.setString(3, "i(" + a + "," + b + ")"); + prep.execute(); + } + } + + reconnect(); + + prep = conn.prepareStatement( + "UPDATE TEST SET DATA=DATA||? WHERE A=? AND B=?"); + for (int a = 0; a < len; a++) { + for (int b = 0; b < len; b += 2) { + prep.setString(1, "u(" + a + "," + b + ")"); + prep.setInt(2, a); + prep.setInt(3, b); + prep.execute(); + } + } + + reconnect(); + + ResultSet rs = stat.executeQuery( + "SELECT * FROM TEST WHERE DATA <> 'i('||a||','||b||')u('||a||','||b||')'"); + assertFalse(rs.next()); + assertEquals(len * (len / 2), getValue("SELECT COUNT(*) FROM TEST")); + stat.execute("DROP TABLE TEST"); + } + + private void testHashIndexOnMemoryTable() throws SQLException { + reconnect(); + stat.execute("drop table if exists hash_index_test"); + stat.execute("create memory table hash_index_test as " + + "select x as id, x % 10 as data from (select * from system_range(1, 100))"); + stat.execute("create hash index idx2 on hash_index_test(data)"); + assertEquals(10, + getValue("select count(*) from hash_index_test where data = 1")); + + stat.execute("drop index idx2"); + stat.execute("create unique hash index idx2 on hash_index_test(id)"); + assertEquals(1, + getValue("select count(*) from hash_index_test where id = 1")); + } + + private int getValue(String sql) throws SQLException { + ResultSet rs = stat.executeQuery(sql); + rs.next(); + return rs.getInt(1); + } + + private void log(String sql) throws SQLException { + trace(sql); + ResultSet rs = stat.executeQuery(sql); + int cols = rs.getMetaData().getColumnCount(); + while (rs.next()) { + StringBuilder buff = new StringBuilder(); + for (int i = 0; i < cols; i++) { + if (i > 0) { + buff.append(", "); + } + buff.append("[" + i + "]=" + rs.getString(i + 1)); + } + trace(buff.toString()); + } + trace("---done---"); + } + + /** + * This method is called from the database. + * + * @return the result set + */ + public static ResultSet testFunctionIndexFunction() { + // There are additional callers like JdbcConnection.prepareCommand() and + // CommandContainer.recompileIfRequired() + for (StackTraceElement element : Thread.currentThread().getStackTrace()) { + if (element.getClassName().startsWith(Select.class.getName())) { + testFunctionIndexCounter++; + break; + } + } + SimpleResultSet rs = new SimpleResultSet(); + rs.addColumn("ID", Types.INTEGER, ValueInteger.PRECISION, 0); + rs.addColumn("VALUE", Types.INTEGER, ValueInteger.PRECISION, 0); + rs.addRow(1, 10); + rs.addRow(2, 20); + rs.addRow(3, 30); + return rs; + } + + private void testFunctionIndex() throws SQLException { + testFunctionIndexCounter = 0; + stat.execute("CREATE ALIAS TEST_INDEX FOR '" + TestIndex.class.getName() + ".testFunctionIndexFunction'"); + try (ResultSet rs = stat.executeQuery("SELECT * FROM TEST_INDEX() WHERE ID = 1 OR ID = 3")) { + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals(10, rs.getInt(2)); + assertTrue(rs.next()); + assertEquals(3, rs.getInt(1)); + assertEquals(30, rs.getInt(2)); + assertFalse(rs.next()); + } finally { + stat.execute("DROP ALIAS TEST_INDEX"); + } + assertEquals(1, testFunctionIndexCounter); + } + + private void testEnumIndex() throws SQLException { + if (config.memory || config.networked) { + return; + } + deleteDb("index"); + String url = "jdbc:h2:" + getBaseDir() + "/index;DB_CLOSE_DELAY=0"; + Connection conn = DriverManager.getConnection(url); + Statement stat = conn.createStatement(); + + stat.execute("CREATE TABLE TEST(ID INT, V ENUM('A', 'B'), CONSTRAINT PK PRIMARY KEY(ID, V))"); + stat.execute("INSERT INTO TEST VALUES (1, 'A'), (2, 'B')"); + + conn.close(); + conn = DriverManager.getConnection(url); + stat = conn.createStatement(); + + stat.execute("DELETE FROM TEST WHERE V = 'A'"); + stat.execute("DROP TABLE TEST"); + + conn.close(); + deleteDb("index"); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestIndexHints.java b/h2/src/test/org/h2/test/db/TestIndexHints.java new file mode 100644 index 0000000..a992869 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestIndexHints.java @@ -0,0 +1,136 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests the index hints feature of this database. + */ +public class TestIndexHints extends TestDb { + + private Connection conn; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb("indexhints"); + createDb(); + testQuotedIdentifier(); + testWithSingleIndexName(); + testWithEmptyIndexHintsList(); + testWithInvalidIndexName(); + testWithMultipleIndexNames(); + testPlanSqlHasIndexesInCorrectOrder(); + testWithTableAlias(); + testWithTableAliasCalledUse(); + conn.close(); + deleteDb("indexhints"); + } + + private void createDb() throws SQLException { + conn = getConnection("indexhints"); + Statement stat = conn.createStatement(); + stat.execute("create table test (x int, y int)"); + stat.execute("create index idx1 on test (x)"); + stat.execute("create index idx2 on test (x, y)"); + stat.execute("create index \"Idx3\" on test (y, x)"); + } + + private void testQuotedIdentifier() throws SQLException { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("explain analyze select * " + + "from test use index(\"Idx3\") where x=1 and y=1"); + assertTrue(rs.next()); + String plan = rs.getString(1); + rs.close(); + assertTrue(plan.contains("/* PUBLIC.Idx3:")); + assertTrue(plan.contains("USE INDEX (\"Idx3\")")); + rs = stat.executeQuery("EXPLAIN ANALYZE " + plan); + assertTrue(rs.next()); + plan = rs.getString(1); + assertTrue(plan.contains("/* PUBLIC.Idx3:")); + assertTrue(plan.contains("USE INDEX (\"Idx3\")")); + } + + private void testWithSingleIndexName() throws SQLException { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("explain analyze select * " + + "from test use index(idx1) where x=1 and y=1"); + rs.next(); + String result = rs.getString(1); + assertTrue(result.contains("/* PUBLIC.IDX1:")); + } + + private void testWithTableAlias() throws SQLException { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("explain analyze select * " + + "from test t use index(idx2) where x=1 and y=1"); + rs.next(); + String result = rs.getString(1); + assertTrue(result.contains("/* PUBLIC.IDX2:")); + } + + private void testWithTableAliasCalledUse() throws SQLException { + // make sure that while adding new syntax for table hints, code + // that uses "USE" as a table alias still works + Statement stat = conn.createStatement(); + stat.executeQuery("explain analyze select * " + + "from test use where use.x=1 and use.y=1"); + } + + private void testWithMultipleIndexNames() throws SQLException { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("explain analyze select * " + + "from test use index(idx1, idx2) where x=1 and y=1"); + rs.next(); + String result = rs.getString(1); + assertTrue(result.contains("/* PUBLIC.IDX2:")); + } + + private void testPlanSqlHasIndexesInCorrectOrder() throws SQLException { + ResultSet rs = conn.createStatement().executeQuery("explain analyze select * " + + "from test use index(idx1, idx2) where x=1 and y=1"); + rs.next(); + assertTrue(rs.getString(1).contains("USE INDEX (\"IDX1\", \"IDX2\")")); + + ResultSet rs2 = conn.createStatement().executeQuery("explain analyze select * " + + "from test use index(idx2, idx1) where x=1 and y=1"); + rs2.next(); + assertTrue(rs2.getString(1).contains("USE INDEX (\"IDX2\", \"IDX1\")")); + } + + private void testWithEmptyIndexHintsList() throws SQLException { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("explain analyze select * " + + "from test use index () where x=1 and y=1"); + rs.next(); + String result = rs.getString(1); + assertTrue(result.contains("/* PUBLIC.TEST.tableScan")); + } + + private void testWithInvalidIndexName() throws SQLException { + Statement stat = conn.createStatement(); + assertThrows(ErrorCode.INDEX_NOT_FOUND_1, stat).executeQuery("explain analyze select * " + + "from test use index(idx_doesnt_exist) where x=1 and y=1"); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestLIRSMemoryConsumption.java b/h2/src/test/org/h2/test/db/TestLIRSMemoryConsumption.java new file mode 100644 index 0000000..55d27c2 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestLIRSMemoryConsumption.java @@ -0,0 +1,103 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.util.Random; +import org.h2.mvstore.cache.CacheLongKeyLIRS; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.Utils; + +/** + * Class TestLIRSMemoryConsumption. + *
      + *
    • 8/5/18 10:57 PM initial creation + *
    + * + * @author Andrei Tokar + */ +public class TestLIRSMemoryConsumption extends TestDb { + + /** + * Run just this test. + * + * @param a + * ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() { + testMemoryConsumption(); + System.out.println("-----------------------"); + testMemoryConsumption(); + System.out.println("-----------------------"); + testMemoryConsumption(); + } + + private static void testMemoryConsumption() { + int size = 1_000_000; + Random rng = new Random(); + CacheLongKeyLIRS.Config config = new CacheLongKeyLIRS.Config(); + for (int mb = 1; mb <= 16; mb *= 2) { + config.maxMemory = mb * 1024 * 1024; + CacheLongKeyLIRS cache = new CacheLongKeyLIRS<>(config); + long memoryUsedInitial = Utils.getMemoryUsed(); + for (int i = 0; i < size; i++) { + cache.put(i, createValue(i), getValueSize(i)); + } + for (int i = 0; i < size; i++) { + int key; + int mode = rng.nextInt(4); + switch(mode) { + default: + case 0: + key = rng.nextInt(10); + break; + case 1: + key = rng.nextInt(100); + break; + case 2: + key = rng.nextInt(10_000); + break; + case 3: + key = rng.nextInt(1_000_000); + break; + } + Object val = cache.get(key); + if (val == null) { + cache.put(key, createValue(key), getValueSize(key)); + } + } + Utils.collectGarbage(); + cache.trimNonResidentQueue(); + long memoryUsed = Utils.getMemoryUsed(); + + int sizeHot = cache.sizeHot(); + int sizeResident = cache.size(); + int sizeNonResident = cache.sizeNonResident(); + long hits = cache.getHits(); + long misses = cache.getMisses(); + System.out.println(mb + " | " + + (memoryUsed - memoryUsedInitial + 512) / 1024 + " | " + + (sizeResident+sizeNonResident) + " | " + + sizeHot + " | " + (sizeResident - sizeHot) + " | " + sizeNonResident + + " | " + (hits * 100 / (hits + misses)) ); + } + } + + private static Object createValue(long key) { +// return new Object(); + return new byte[2540]; + } + + private static int getValueSize(long key) { +// return 16; + return 2560; + } +} diff --git a/h2/src/test/org/h2/test/db/TestLargeBlob.java b/h2/src/test/org/h2/test/db/TestLargeBlob.java new file mode 100644 index 0000000..56a94cd --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestLargeBlob.java @@ -0,0 +1,91 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.io.InputStream; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Test a BLOB larger than Integer.MAX_VALUE + */ +public class TestLargeBlob extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + if (!config.big || config.memory || config.networked) { + return false; + } + return true; + } + + @Override + public void test() throws Exception { + deleteDb("largeBlob"); + String url = getURL("largeBlob;TRACE_LEVEL_FILE=0", true); + Connection conn = getConnection(url); + final long testLength = Integer.MAX_VALUE + 110L; + Statement stat = conn.createStatement(); + stat.execute("create table test(x blob)"); + PreparedStatement prep = conn.prepareStatement( + "insert into test values(?)"); + prep.setBinaryStream(1, new InputStream() { + long remaining = testLength; + int p; + byte[] oneByte = { 0 }; + @Override + public void close() { + // ignore + } + @Override + public int read(byte[] buff, int off, int len) { + len = (int) Math.min(remaining, len); + remaining -= len; + if (p++ % 5000 == 0) { + println("" + remaining); + } + return len == 0 ? -1 : len; + } + @Override + public int read() { + return read(oneByte, 0, 1) < 0 ? -1 : oneByte[0]; + } + }, -1); + prep.executeUpdate(); + ResultSet rs = stat.executeQuery( + "select length(x) from test"); + rs.next(); + assertEquals(testLength, rs.getLong(1)); + rs = stat.executeQuery("select x from test"); + rs.next(); + InputStream in = rs.getBinaryStream(1); + byte[] buff = new byte[4 * 1024]; + long length = 0; + while (true) { + int len = in.read(buff); + if (len < 0) { + break; + } + length += len; + } + assertEquals(testLength, length); + conn.close(); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestLinkedTable.java b/h2/src/test/org/h2/test/db/TestLinkedTable.java new file mode 100644 index 0000000..d33f137 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestLinkedTable.java @@ -0,0 +1,777 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Timestamp; +import org.h2.api.ErrorCode; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests the linked table feature (CREATE LINKED TABLE). + */ +public class TestLinkedTable extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + testLinkedServerMode(); + testDefaultValues(); + testHiddenSQL(); + // testLinkAutoAdd(); + testNestedQueriesToSameTable(); + testSharedConnection(); + testMultipleSchemas(); + testReadOnlyLinkedTable(); + testLinkOtherSchema(); + testLinkDrop(); + testLinkSchema(); + testLinkEmitUpdates(); + testLinkTable(); + testLinkTwoTables(); + testCachingResults(); + testLinkedTableInReadOnlyDb(); + testGeometry(); + testFetchSize(); + testFetchSizeWithAutoCommit(); + deleteDb("linkedTable"); + } + + private void testLinkedServerMode() throws SQLException { + if (config.memory) { + return; + } + // the network mode will result in a deadlock + if (config.networked) { + return; + } + deleteDb("linkedTable1"); + deleteDb("linkedTable2"); + String url2 = getURL("linkedTable2", true); + String user = getUser(), password = getPassword(); + Connection conn = getConnection("linkedTable2"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int)"); + conn.close(); + conn = getConnection("linkedTable1"); + stat = conn.createStatement(); + stat.execute("create linked table link(null, '"+url2+ + "', '"+user+"', '"+password+"', 'TEST')"); + conn.close(); + conn = getConnection("linkedTable1"); + conn.close(); + } + + private void testDefaultValues() throws SQLException { + if (config.memory) { + return; + } + deleteDb("linkedTable"); + Connection connMain = DriverManager.getConnection("jdbc:h2:mem:linkedTable"); + Statement statMain = connMain.createStatement(); + statMain.execute("create table test(id identity, name varchar default 'test')"); + + Connection conn = getConnection("linkedTable"); + Statement stat = conn.createStatement(); + stat.execute("create linked table test1('', " + + "'jdbc:h2:mem:linkedTable', '', '', 'TEST') emit updates"); + stat.execute("create linked table test2('', " + + "'jdbc:h2:mem:linkedTable', '', '', 'TEST')"); + stat.execute("insert into test1 values(default, default)"); + stat.execute("insert into test2 values(default, default)"); + stat.execute("merge into test2 values(3, default)"); + stat.execute("update test1 set name=default where id=1"); + stat.execute("update test2 set name=default where id=2"); + + ResultSet rs = statMain.executeQuery("select * from test order by id"); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals("test", rs.getString(2)); + rs.next(); + assertEquals(2, rs.getInt(1)); + assertEquals("test", rs.getString(2)); + rs.next(); + assertEquals(3, rs.getInt(1)); + assertEquals("test", rs.getString(2)); + assertFalse(rs.next()); + + stat.execute("delete from test1 where id=1"); + stat.execute("delete from test2 where id=2"); + stat.execute("delete from test2 where id=3"); + conn.close(); + rs = statMain.executeQuery("select * from test order by id"); + assertFalse(rs.next()); + + connMain.close(); + } + + private void testHiddenSQL() throws SQLException { + if (config.memory) { + return; + } + org.h2.Driver.load(); + deleteDb("linkedTable"); + Connection conn = getConnection( + "linkedTable;SHARE_LINKED_CONNECTIONS=TRUE"); + try { + conn.createStatement().execute("create linked table test" + + "(null, 'jdbc:h2:mem:', 'sa', 'pwd', 'DUAL2')"); + fail(); + } catch (SQLException e) { + assertContains(e.toString(), "pwd"); + } + try { + conn.createStatement().execute("create linked table test" + + "(null, 'jdbc:h2:mem:', 'sa', 'pwd', 'DUAL2') --hide--"); + fail(); + } catch (SQLException e) { + assertTrue(e.toString().indexOf("pwd") < 0); + } + conn.close(); + } + + // this is not a bug, it is the documented behavior +// private void testLinkAutoAdd() throws SQLException { +// Class.forName("org.h2.Driver"); +// 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 ONE (X NUMBER)"); +// sb.execute( +// "CALL LINK_SCHEMA('GOOD', '', " + +// "'jdbc:h2:mem:one', 'sa', 'sa', 'PUBLIC'); "); +// sb.executeQuery("SELECT * FROM GOOD.ONE"); +// sa.execute("CREATE TABLE TWO (X NUMBER)"); +// sb.executeQuery("SELECT * FROM GOOD.TWO"); // FAILED +// ca.close(); +// cb.close(); +// } + + private void testNestedQueriesToSameTable() throws SQLException { + if (config.memory) { + return; + } + org.h2.Driver.load(); + deleteDb("linkedTable"); + String url = getURL("linkedTable;SHARE_LINKED_CONNECTIONS=TRUE", true); + String user = getUser(); + String password = getPassword(); + Connection ca = getConnection(url, user, password); + Statement sa = ca.createStatement(); + sa.execute("CREATE TABLE TEST(ID INT) AS SELECT 1"); + ca.close(); + Connection cb = DriverManager.getConnection("jdbc:h2:mem:two", "sa", "sa"); + Statement sb = cb.createStatement(); + sb.execute("CREATE LINKED TABLE T1(NULL, '" + + url + "', '"+user+"', '"+password+"', 'TEST')"); + sb.executeQuery("SELECT * FROM DUAL A " + + "LEFT OUTER JOIN T1 A ON A.ID=1 LEFT OUTER JOIN T1 B ON B.ID=1"); + sb.execute("DROP ALL OBJECTS"); + cb.close(); + } + + private void testSharedConnection() throws SQLException { + if (config.memory) { + return; + } + org.h2.Driver.load(); + deleteDb("linkedTable"); + String url = getURL("linkedTable;SHARE_LINKED_CONNECTIONS=TRUE", true); + String user = getUser(); + String password = getPassword(); + Connection ca = getConnection(url, user, password); + Statement sa = ca.createStatement(); + sa.execute("CREATE TABLE TEST(ID INT)"); + ca.close(); + Connection cb = DriverManager.getConnection("jdbc:h2:mem:two", "sa", "sa"); + Statement sb = cb.createStatement(); + sb.execute("CREATE LINKED TABLE T1(NULL, '" + url + + ";OPEN_NEW=TRUE', '"+user+"', '"+password+"', 'TEST')"); + sb.execute("CREATE LINKED TABLE T2(NULL, '" + url + + ";OPEN_NEW=TRUE', '"+user+"', '"+password+"', 'TEST')"); + sb.execute("DROP ALL OBJECTS"); + cb.close(); + } + + private void testMultipleSchemas() throws SQLException { + 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(ID INT)"); + sa.execute("CREATE SCHEMA P"); + sa.execute("CREATE TABLE P.TEST(X INT)"); + sa.execute("INSERT INTO TEST VALUES(1)"); + sa.execute("INSERT INTO P.TEST VALUES(2)"); + assertThrows(ErrorCode.SCHEMA_NAME_MUST_MATCH, sb). + execute("CREATE LINKED TABLE T(NULL, " + + "'jdbc:h2:mem:one', 'sa', 'sa', 'TEST')"); + sb.execute("CREATE LINKED TABLE T(NULL, " + + "'jdbc:h2:mem:one', 'sa', 'sa', 'PUBLIC', 'TEST')"); + sb.execute("CREATE LINKED TABLE T2(NULL, " + + "'jdbc:h2:mem:one', 'sa', 'sa', 'P', 'TEST')"); + assertSingleValue(sb, "SELECT * FROM T", 1); + assertSingleValue(sb, "SELECT * FROM T2", 2); + sa.execute("DROP ALL OBJECTS"); + sb.execute("DROP ALL OBJECTS"); + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1, sa). + execute("SELECT * FROM TEST"); + ca.close(); + cb.close(); + } + + private void testReadOnlyLinkedTable() throws SQLException { + 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(ID INT)"); + sa.execute("INSERT INTO TEST VALUES(1)"); + String[] suffix = {"", "READONLY", "EMIT UPDATES"}; + for (int i = 0; i < suffix.length; i++) { + String sql = "CREATE LINKED TABLE T(NULL, " + + "'jdbc:h2:mem:one', 'sa', 'sa', 'TEST')" + suffix[i]; + sb.execute(sql); + sb.executeQuery("SELECT * FROM T"); + String[] update = {"DELETE FROM T", + "INSERT INTO T VALUES(2)", "UPDATE T SET ID = 3"}; + for (String u : update) { + try { + sb.execute(u); + if (i == 1) { + fail(); + } + } catch (SQLException e) { + if (i == 1) { + assertKnownException(e); + } else { + throw e; + } + } + } + sb.execute("DROP TABLE T"); + } + ca.close(); + cb.close(); + } + + private static void testLinkOtherSchema() throws SQLException { + 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 GOOD (X NUMBER)"); + sa.execute("CREATE SCHEMA S"); + sa.execute("CREATE TABLE S.BAD (X NUMBER)"); + sb.execute("SELECT * FROM LINK_SCHEMA('G', '', " + + "'jdbc:h2:mem:one', 'sa', 'sa', 'PUBLIC'); "); + sb.execute("SELECT * FROM LINK_SCHEMA('B', '', " + + "'jdbc:h2:mem:one', 'sa', 'sa', 'S'); "); + // OK + sb.executeQuery("SELECT * FROM G.GOOD"); + // FAILED + sb.executeQuery("SELECT * FROM B.BAD"); + ca.close(); + cb.close(); + } + + private void testLinkTwoTables() throws SQLException { + org.h2.Driver.load(); + Connection conn = DriverManager.getConnection( + "jdbc:h2:mem:one", "sa", "sa"); + Statement stat = conn.createStatement(); + stat.execute("CREATE SCHEMA Y"); + stat.execute("CREATE TABLE A( C INT)"); + stat.execute("INSERT INTO A VALUES(1)"); + stat.execute("CREATE TABLE Y.A (C INT)"); + stat.execute("INSERT INTO Y.A VALUES(2)"); + Connection conn2 = DriverManager.getConnection("jdbc:h2:mem:two"); + Statement stat2 = conn2.createStatement(); + stat2.execute("CREATE LINKED TABLE one('org.h2.Driver', " + + "'jdbc:h2:mem:one', 'sa', 'sa', 'Y.A');"); + stat2.execute("CREATE LINKED TABLE two('org.h2.Driver', " + + "'jdbc:h2:mem:one', 'sa', 'sa', 'PUBLIC.A');"); + ResultSet rs = stat2.executeQuery("SELECT * FROM one"); + rs.next(); + assertEquals(2, rs.getInt(1)); + rs = stat2.executeQuery("SELECT * FROM two"); + rs.next(); + assertEquals(1, rs.getInt(1)); + conn.close(); + conn2.close(); + } + + private static void testLinkDrop() throws SQLException { + org.h2.Driver.load(); + Connection connA = DriverManager.getConnection("jdbc:h2:mem:a"); + Statement statA = connA.createStatement(); + statA.execute("CREATE TABLE TEST(ID INT)"); + Connection connB = DriverManager.getConnection("jdbc:h2:mem:b"); + Statement statB = connB.createStatement(); + statB.execute("CREATE LINKED TABLE " + + "TEST_LINK('', 'jdbc:h2:mem:a', '', '', 'TEST')"); + connA.close(); + // the connection should be closed now + // (and the table should disappear because the last connection was + // closed) + statB.execute("DROP TABLE TEST_LINK"); + connA = DriverManager.getConnection("jdbc:h2:mem:a"); + statA = connA.createStatement(); + // table should not exist now + statA.execute("CREATE TABLE TEST(ID INT)"); + connA.close(); + connB.close(); + } + + private void testLinkEmitUpdates() throws SQLException { + if (config.memory || config.networked) { + return; + } + + deleteDb("linked1"); + deleteDb("linked2"); + org.h2.Driver.load(); + + String url1 = getURL("linked1", true); + String url2 = getURL("linked2", true); + + Connection conn = DriverManager.getConnection(url1, "sa1", "abc abc"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + + Connection conn2 = DriverManager.getConnection(url2, "sa2", "def def"); + Statement stat2 = conn2.createStatement(); + String link = "CREATE LINKED TABLE TEST_LINK_U('', '" + url1 + + "', 'sa1', 'abc abc', 'TEST') EMIT UPDATES"; + stat2.execute(link); + link = "CREATE LINKED TABLE TEST_LINK_DI('', '" + url1 + + "', 'sa1', 'abc abc', 'TEST')"; + stat2.execute(link); + stat2.executeUpdate("INSERT INTO TEST_LINK_U VALUES(1, 'Hello')"); + stat2.executeUpdate("INSERT INTO TEST_LINK_DI VALUES(2, 'World')"); + assertThrows(ErrorCode.ERROR_ACCESSING_LINKED_TABLE_2, stat2). + executeUpdate("UPDATE TEST_LINK_U SET ID=ID+1"); + stat2.executeUpdate("UPDATE TEST_LINK_DI SET ID=ID+1"); + stat2.executeUpdate("UPDATE TEST_LINK_U SET NAME=NAME || ID"); + ResultSet rs; + + rs = stat2.executeQuery("SELECT * FROM TEST_LINK_DI ORDER BY ID"); + rs.next(); + assertEquals(2, rs.getInt(1)); + assertEquals("Hello2", rs.getString(2)); + rs.next(); + assertEquals(3, rs.getInt(1)); + assertEquals("World3", rs.getString(2)); + assertFalse(rs.next()); + + rs = stat2.executeQuery("SELECT * FROM TEST_LINK_U ORDER BY ID"); + rs.next(); + assertEquals(2, rs.getInt(1)); + assertEquals("Hello2", rs.getString(2)); + rs.next(); + assertEquals(3, rs.getInt(1)); + assertEquals("World3", rs.getString(2)); + assertFalse(rs.next()); + + rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + rs.next(); + assertEquals(2, rs.getInt(1)); + assertEquals("Hello2", rs.getString(2)); + rs.next(); + assertEquals(3, rs.getInt(1)); + assertEquals("World3", rs.getString(2)); + assertFalse(rs.next()); + + conn.close(); + conn2.close(); + } + + private void testLinkSchema() throws SQLException { + if (config.memory || config.networked) { + return; + } + + deleteDb("linked1"); + deleteDb("linked2"); + org.h2.Driver.load(); + String url1 = getURL("linked1", true); + String url2 = getURL("linked2", true); + + Connection conn = DriverManager.getConnection(url1, "sa1", "abc abc"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST1(ID INT PRIMARY KEY)"); + + Connection conn2 = DriverManager.getConnection(url2, "sa2", "def def"); + Statement stat2 = conn2.createStatement(); + String link = "SELECT * FROM LINK_SCHEMA('LINKED', '', '" + url1 + + "', 'sa1', 'abc abc', 'PUBLIC')"; + stat2.execute(link); + stat2.executeQuery("SELECT * FROM LINKED.TEST1"); + + stat.execute("CREATE TABLE TEST2(ID INT PRIMARY KEY)"); + stat2.execute(link); + stat2.executeQuery("SELECT * FROM LINKED.TEST1"); + stat2.executeQuery("SELECT * FROM LINKED.TEST2"); + + conn.close(); + conn2.close(); + } + + private void testLinkTable() throws SQLException { + if (config.memory || config.networked || config.reopen) { + return; + } + + deleteDb("linked1"); + deleteDb("linked2"); + org.h2.Driver.load(); + + String url1 = getURL("linked1", true); + String url2 = getURL("linked2", true); + + Connection conn = DriverManager.getConnection(url1, "sa1", "abc abc"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TEMP TABLE TEST_TEMP(ID INT PRIMARY KEY)"); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, " + + "NAME VARCHAR(200), XT TINYINT, XD DECIMAL(10,2), " + + "XTS TIMESTAMP, XBY VARBINARY(255), XBO BIT, XSM SMALLINT, " + + "XBI BIGINT, XBL BLOB, XDA DATE, XTI TIME, XCL CLOB, XDO DOUBLE)"); + stat.execute("CREATE INDEX IDXNAME ON TEST(NAME)"); + stat.execute("INSERT INTO TEST VALUES(0, NULL, NULL, NULL, NULL, " + + "NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL)"); + stat.execute("INSERT INTO TEST VALUES(1, 'Hello', -1, 10.30, " + + "'2001-02-03 11:22:33.4455', X'FF0102', TRUE, 3000, " + + "1234567890123456789, X'1122AA', DATE '0002-01-01', " + + "TIME '00:00:00', 'J\u00fcrg', 2.25)"); + testRow(stat, "TEST"); + stat.execute("INSERT INTO TEST VALUES(2, 'World', 30, 100.05, " + + "'2005-12-31 12:34:56.789', X'FFEECC33', FALSE, 1, " + + "-1234567890123456789, X'4455FF', DATE '9999-12-31', " + + "TIME '23:59:59', 'George', -2.5)"); + testRow(stat, "TEST"); + stat.execute("SELECT * FROM TEST_TEMP"); + conn.close(); + + conn = DriverManager.getConnection(url1, "sa1", "abc abc"); + stat = conn.createStatement(); + testRow(stat, "TEST"); + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, stat). + execute("SELECT * FROM TEST_TEMP"); + conn.close(); + + conn = DriverManager.getConnection(url2, "sa2", "def def"); + stat = conn.createStatement(); + stat.execute("CREATE LINKED TABLE IF NOT EXISTS " + + "LINK_TEST('org.h2.Driver', '" + url1 + + "', 'sa1', 'abc abc', 'TEST')"); + stat.execute("CREATE LINKED TABLE IF NOT EXISTS " + + "LINK_TEST('org.h2.Driver', '" + url1 + + "', 'sa1', 'abc abc', 'TEST')"); + testRow(stat, "LINK_TEST"); + ResultSet rs = stat.executeQuery("SELECT * FROM LINK_TEST"); + ResultSetMetaData meta = rs.getMetaData(); + assertEquals(32, meta.getPrecision(1)); + assertEquals(200, meta.getPrecision(2)); + + conn.close(); + conn = DriverManager.getConnection(url2, "sa2", "def def"); + stat = conn.createStatement(); + + stat.execute("INSERT INTO LINK_TEST VALUES(3, 'Link Test', " + + "30, 100.05, '2005-12-31 12:34:56.789', X'FFEECC33', " + + "FALSE, 1, -1234567890123456789, X'4455FF', " + + "DATE '9999-12-31', TIME '23:59:59', 'George', -2.5)"); + + rs = stat.executeQuery("SELECT COUNT(*) FROM LINK_TEST"); + rs.next(); + assertEquals(4, rs.getInt(1)); + + rs = stat.executeQuery("SELECT COUNT(*) FROM LINK_TEST WHERE NAME='Link Test'"); + rs.next(); + assertEquals(1, rs.getInt(1)); + + int uc = stat.executeUpdate("DELETE FROM LINK_TEST WHERE ID=3"); + assertEquals(1, uc); + + rs = stat.executeQuery("SELECT COUNT(*) FROM LINK_TEST"); + rs.next(); + assertEquals(3, rs.getInt(1)); + + rs = stat.executeQuery("SELECT * FROM " + + "INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='LINK_TEST'"); + rs.next(); + assertEquals("TABLE LINK", rs.getString("STORAGE_TYPE")); + + rs.next(); + rs = stat.executeQuery("SELECT * FROM LINK_TEST WHERE ID=0"); + rs.next(); + assertTrue(rs.getString("NAME") == null && rs.wasNull()); + assertTrue(rs.getString("XT") == null && rs.wasNull()); + assertTrue(rs.getInt("ID") == 0 && !rs.wasNull()); + assertTrue(rs.getBigDecimal("XD") == null && rs.wasNull()); + assertTrue(rs.getTimestamp("XTS") == null && rs.wasNull()); + assertTrue(rs.getBytes("XBY") == null && rs.wasNull()); + assertTrue(!rs.getBoolean("XBO") && rs.wasNull()); + assertTrue(rs.getShort("XSM") == 0 && rs.wasNull()); + assertTrue(rs.getLong("XBI") == 0 && rs.wasNull()); + assertTrue(rs.getString("XBL") == null && rs.wasNull()); + assertTrue(rs.getString("XDA") == null && rs.wasNull()); + assertTrue(rs.getString("XTI") == null && rs.wasNull()); + assertTrue(rs.getString("XCL") == null && rs.wasNull()); + assertTrue(rs.getString("XDO") == null && rs.wasNull()); + assertFalse(rs.next()); + + stat.execute("DROP TABLE LINK_TEST"); + + stat.execute("CREATE LINKED TABLE LINK_TEST('org.h2.Driver', '" + url1 + + "', 'sa1', 'abc abc', '(SELECT COUNT(*) FROM TEST)')"); + rs = stat.executeQuery("SELECT * FROM LINK_TEST"); + rs.next(); + assertEquals(3, rs.getInt(1)); + assertFalse(rs.next()); + + conn.close(); + + deleteDb("linked1"); + deleteDb("linked2"); + } + + private void testRow(Statement stat, String name) throws SQLException { + ResultSet rs = stat.executeQuery("SELECT * FROM " + name + " WHERE ID=1"); + rs.next(); + assertEquals("Hello", rs.getString("NAME")); + assertEquals(-1, rs.getByte("XT")); + BigDecimal bd = rs.getBigDecimal("XD"); + assertTrue(bd.equals(new BigDecimal("10.30"))); + Timestamp ts = rs.getTimestamp("XTS"); + String s = ts.toString(); + assertEquals("2001-02-03 11:22:33.4455", s); + assertTrue(ts.equals(Timestamp.valueOf("2001-02-03 11:22:33.4455"))); + assertEquals(new byte[] { (byte) 255, (byte) 1, (byte) 2 }, rs.getBytes("XBY")); + assertTrue(rs.getBoolean("XBO")); + assertEquals(3000, rs.getShort("XSM")); + assertEquals(1234567890123456789L, rs.getLong("XBI")); + assertEquals(new byte[] {0x11, 0x22, (byte) 0xAA }, rs.getBytes("XBL")); + assertEquals("0002-01-01", rs.getString("XDA")); + assertEquals("00:00:00", rs.getString("XTI")); + assertEquals("J\u00fcrg", rs.getString("XCL")); + assertEquals("2.25", rs.getString("XDO")); + } + + private void testCachingResults() throws SQLException { + 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(ID VARCHAR)"); + sa.execute("INSERT INTO TEST (ID) VALUES('abc')"); + sb.execute("CREATE LOCAL TEMPORARY LINKED TABLE T" + + "(NULL, 'jdbc:h2:mem:one', 'sa', 'sa', 'TEST')"); + + PreparedStatement paData = ca.prepareStatement( + "select id from TEST where id = ?"); + PreparedStatement pbData = cb.prepareStatement( + "select id from T where id = ?"); + PreparedStatement paCount = ca.prepareStatement( + "select count(*) from TEST"); + PreparedStatement pbCount = cb.prepareStatement( + "select count(*) from T"); + + // Direct query => Result 1 + testCachingResultsCheckResult(paData, 1, "abc"); + testCachingResultsCheckResult(paCount, 1); + + // Via linked table => Result 1 + testCachingResultsCheckResult(pbData, 1, "abc"); + testCachingResultsCheckResult(pbCount, 1); + + sa.execute("INSERT INTO TEST (ID) VALUES('abc')"); + + // Direct query => Result 2 + testCachingResultsCheckResult(paData, 2, "abc"); + testCachingResultsCheckResult(paCount, 2); + + // Via linked table => Result must be 2 + testCachingResultsCheckResult(pbData, 2, "abc"); + testCachingResultsCheckResult(pbCount, 2); + + ca.close(); + cb.close(); + } + + private void testCachingResultsCheckResult(PreparedStatement ps, + int expected) throws SQLException { + ResultSet rs = ps.executeQuery(); + rs.next(); + assertEquals(expected, rs.getInt(1)); + } + + private void testCachingResultsCheckResult(PreparedStatement ps, + int expected, String value) throws SQLException { + ps.setString(1, value); + ResultSet rs = ps.executeQuery(); + int counter = 0; + while (rs.next()) { + counter++; + String result = rs.getString(1); + assertEquals(result, value); + } + assertEquals(expected, counter); + } + + private void testLinkedTableInReadOnlyDb() throws SQLException { + if (config.memory || config.networked || config.googleAppEngine) { + return; + } + + deleteDb("testLinkedTableInReadOnlyDb"); + org.h2.Driver.load(); + + Connection memConn = DriverManager.getConnection( + "jdbc:h2:mem:one", "sa", "sa"); + Statement memStat = memConn.createStatement(); + memStat.execute("CREATE TABLE TEST(ID VARCHAR)"); + + String url1 = getURL("testLinkedTableInReadOnlyDb", true); + Connection conn = DriverManager.getConnection(url1, "sa1", "abc abc"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY)"); + conn.close(); + + for (String file : FileUtils.newDirectoryStream(getBaseDir())) { + String name = FileUtils.getName(file); + if ((name.startsWith("testLinkedTableInReadOnlyDb")) && + (!name.endsWith(".trace.db"))) { + FileUtils.setReadOnly(file); + boolean isReadOnly = !FileUtils.canWrite(file); + if (!isReadOnly) { + fail("File " + file + " is not read only. Can't test it."); + } + } + } + + // Now it's read only + conn = DriverManager.getConnection(url1, "sa1", "abc abc"); + stat = conn.createStatement(); + stat.execute("CREATE LOCAL TEMPORARY LINKED TABLE T" + + "(NULL, 'jdbc:h2:mem:one', 'sa', 'sa', 'TEST')"); + // This is valid because it's a linked table + stat.execute("INSERT INTO T VALUES('abc')"); + + conn.close(); + memConn.close(); + + deleteDb("testLinkedTableInReadOnlyDb"); + } + + private void testGeometry() 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(ID BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY," + + " THE_GEOM GEOMETRY, THE_GEOM_2 GEOMETRY(POINT, 4326))"); + sa.execute("INSERT INTO TEST(THE_GEOM, THE_GEOM_2) VALUES" + + " (GEOMETRY 'POINT (1 1)', GEOMETRY 'SRID=4326;POINT(2 2)')"); + String sql = "CREATE LINKED TABLE T(NULL, " + + "'jdbc:h2:mem:one', 'sa', 'sa', 'TEST') READONLY"; + sb.execute(sql); + try (ResultSet rs = sb.executeQuery("SELECT * FROM T")) { + assertTrue(rs.next()); + assertEquals("POINT (1 1)", rs.getString("THE_GEOM")); + assertEquals("SRID=4326;POINT (2 2)", rs.getString("THE_GEOM_2")); + } + sb.execute("DROP TABLE T"); + ca.close(); + cb.close(); + } + + private void testFetchSize() 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("DROP TABLE IF EXISTS TEST; " + + "CREATE TABLE TEST as select * from SYSTEM_RANGE(1,1000) as n;"); + String sql = "CREATE LINKED TABLE T(NULL, " + + "'jdbc:h2:mem:one', 'sa', 'sa', 'TEST') FETCH_SIZE 10"; + sb.execute(sql); + try (ResultSet rs = sb.executeQuery("SELECT count(*) FROM T")) { + assertTrue(rs.next()); + assertEquals(1000, rs.getInt(1)); + } + ResultSet res = sb.executeQuery("CALL DB_OBJECT_SQL('TABLE', 'PUBLIC', 'T')"); + res.next(); + assertEquals("CREATE FORCE LINKED TABLE \"PUBLIC\".\"T\"(NULL, 'jdbc:h2:mem:one', 'sa', 'sa', 'TEST')" + + " FETCH_SIZE 10 /*--hide--*/", res.getString(1)); + sb.execute("DROP TABLE T"); + ca.close(); + cb.close(); + } + + private void testFetchSizeWithAutoCommit() 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("DROP TABLE IF EXISTS TEST; " + + "CREATE TABLE TEST as select * from SYSTEM_RANGE(1,1000) as n;"); + String sql = "CREATE LINKED TABLE T(NULL, " + + "'jdbc:h2:mem:one', 'sa', 'sa', 'TEST') FETCH_SIZE 10 AUTOCOMMIT OFF"; + sb.execute(sql); + try (ResultSet rs = sb.executeQuery("SELECT count(*) FROM T")) { + assertTrue(rs.next()); + assertEquals(1000, rs.getInt(1)); + } + ResultSet res = sb.executeQuery("CALL DB_OBJECT_SQL('TABLE', 'PUBLIC', 'T')"); + res.next(); + assertEquals("CREATE FORCE LINKED TABLE \"PUBLIC\".\"T\"(NULL, 'jdbc:h2:mem:one', 'sa', 'sa', 'TEST')" + + " FETCH_SIZE 10 AUTOCOMMIT OFF /*--hide--*/", res.getString(1)); + sb.execute("DROP TABLE T"); + ca.close(); + cb.close(); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestListener.java b/h2/src/test/org/h2/test/db/TestListener.java new file mode 100644 index 0000000..5e04274 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestListener.java @@ -0,0 +1,151 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.TimeUnit; + +import org.h2.api.DatabaseEventListener; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests the DatabaseEventListener. + */ +public class TestListener extends TestDb implements DatabaseEventListener { + + private long last; + private int lastState = -1; + private String databaseUrl; + + public TestListener() { + start = last = System.nanoTime(); + } + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + if (config.networked || config.cipher != null) { + return false; + } + return true; + } + + @Override + public void test() throws SQLException { + deleteDb("listener"); + Connection conn; + conn = getConnection("listener"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST VALUES(?, 'Test' || SPACE(100))"); + int len = getSize(100, 100000); + for (int i = 0; i < len; i++) { + prep.setInt(1, i); + prep.execute(); + } + crash(conn); + + conn = getConnection("listener;database_event_listener='" + + getClass().getName() + "'"); + conn.close(); + deleteDb("listener"); + } + + @Override + public void exceptionThrown(SQLException e, String sql) { + TestBase.logError("exceptionThrown sql=" + sql, e); + } + + @Override + public void setProgress(int state, String name, long current, long max) { + long time = System.nanoTime(); + if (state == lastState && time < last + TimeUnit.SECONDS.toNanos(1)) { + return; + } + if (state == STATE_STATEMENT_START || + state == STATE_STATEMENT_END || + state == STATE_STATEMENT_PROGRESS) { + return; + } + if (name.length() > 30) { + name = "..." + name.substring(name.length() - 30); + } + last = time; + lastState = state; + String stateName; + switch (state) { + case STATE_SCAN_FILE: + stateName = "Scan " + name; + break; + case STATE_CREATE_INDEX: + stateName = "Create Index " + name; + break; + case STATE_RECOVER: + stateName = "Recover"; + break; + default: + TestBase.logError("unknown state: " + state, null); + stateName = "? " + name; + } + try { + Thread.sleep(1); + } catch (InterruptedException e) { + // ignore + } + printTime("state: " + stateName + " " + + (100 * current / max) + " " + TimeUnit.NANOSECONDS.toMillis(time - start)); + } + + @Override + public void closingDatabase() { + if (databaseUrl.toUpperCase().contains("CIPHER")) { + return; + } + + try (Connection conn = DriverManager.getConnection(databaseUrl, + getUser(), getPassword())) { + conn.createStatement().execute("DROP TABLE TEST2"); + conn.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + @Override + public void init(String url) { + this.databaseUrl = url; + } + + @Override + public void opened() { + if (databaseUrl.toUpperCase().contains("CIPHER")) { + return; + } + + try (Connection conn = DriverManager.getConnection(databaseUrl, + getUser(), getPassword())) { + conn.createStatement().execute("CREATE TABLE IF NOT EXISTS TEST2(ID INT)"); + conn.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + +} diff --git a/h2/src/test/org/h2/test/db/TestLob.java b/h2/src/test/org/h2/test/db/TestLob.java new file mode 100644 index 0000000..0c660fc --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestLob.java @@ -0,0 +1,1628 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.io.ByteArrayInputStream; +import java.io.CharArrayReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Savepoint; +import java.sql.Statement; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.engine.SysProperties; +import org.h2.jdbc.JdbcConnection; +import org.h2.message.DbException; +import org.h2.store.FileLister; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.Recover; +import org.h2.tools.SimpleResultSet; +import org.h2.util.IOUtils; +import org.h2.util.JdbcUtils; +import org.h2.util.Task; +import org.h2.value.ValueBlob; +import org.h2.value.ValueClob; +import org.h2.value.ValueLob; + +/** + * Tests LOB and CLOB data types. + */ +public class TestLob extends TestDb { + + private static final String MORE_THAN_128_CHARS = + "12345678901234567890123456789012345678901234567890" + + "12345678901234567890123456789012345678901234567890" + + "12345678901234567890123456789"; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); + test.config.big = true; + test.testFromMain(); + } + + @Override + public void test() throws Exception { + testConcurrentSelectAndUpdate(); + testReclamationOnInDoubtRollback(); + testRemoveAfterDeleteAndClose(); + testRemovedAfterTimeout(); + testConcurrentRemoveRead(); + testCloseLobTwice(); + testClobWithRandomUnicodeChars(); + testCommitOnExclusiveConnection(); + testReadManyLobs(); + testLobSkip(); + testLobSkipPastEnd(); + testCreateIndexOnLob(); + testBlobInputStreamSeek(true); + testBlobInputStreamSeek(false); + testDeadlock(); + testCopyManyLobs(); + testCopyLob(); + testConcurrentCreate(); + testLobInLargeResult(); + testUniqueIndex(); + testConvert(); + testCreateAsSelect(); + testLobServerMemory(); + testUpdatingLobRow(); + testBufferedInputStreamBug(); + if (config.memory) { + return; + } + testLargeClob(); + testLobUpdateMany(); + testLobVariable(); + testLobDrop(); + testLobNoClose(); + testLobTransactions(10); + testLobTransactions(10000); + testLobRollbackStop(); + testLobCopy(); + testLobHibernate(); + testLobCopy2(); + testManyLobs(); + testClob(); + testUpdateLob(); + testLobReconnect(); + testLob(false); + testLob(true); + testJavaObject(); + testLobInValueResultSet(); + // cannot run this on CI, will cause OOM + // testLimits(); + deleteDb("lob"); + } + + private void testReclamationOnInDoubtRollback() throws Exception { + if (config.memory || config.cipher != null) { + return; + } + deleteDb("lob"); + try (Connection conn = getConnection("lob")) { + try (Statement st = conn.createStatement()) { + st.executeUpdate("CREATE TABLE IF NOT EXISTS dataTable(" + + "dataStamp BIGINT PRIMARY KEY, " + + "data BLOB)"); + } + + conn.setAutoCommit(false); + Random rnd = new Random(0); + try (PreparedStatement pstmt = conn.prepareStatement("INSERT INTO dataTable VALUES(?, ?)")) { + for (int i = 0; i < 100; ++i) { + int numBytes = 1024 * 1024; + byte[] data = new byte[numBytes]; + rnd.nextBytes(data); + pstmt.setLong(1, i); + pstmt.setBytes(2, data); + pstmt.executeUpdate(); + } + } + try (Statement st = conn.createStatement()) { + st.executeUpdate("PREPARE COMMIT lobtx"); + st.execute("SHUTDOWN IMMEDIATELY"); + } + } + + try (Connection conn = getConnection("lob")) { + try (Statement st = conn.createStatement(); + ResultSet rs = st.executeQuery("SELECT * FROM INFORMATION_SCHEMA.IN_DOUBT")) { + assertTrue("No in-doubt tx", rs.first()); + assertEquals("LOBTX", rs.getString("TRANSACTION_NAME")); + assertFalse("more than one in-doubt tx", rs.next()); + st.executeUpdate("ROLLBACK TRANSACTION lobtx; CHECKPOINT SYNC"); + } + } + + try (Connection conn = getConnection("lob")) { + try (Statement st = conn.createStatement()) { + st.execute("SHUTDOWN COMPACT"); + } + } + + ArrayList dbFiles = FileLister.getDatabaseFiles(getBaseDir(), "lob", false); + assertEquals(1, dbFiles.size()); + File file = new File(dbFiles.get(0)); + assertTrue(file.exists()); + long fileSize = file.length(); + assertTrue("File size=" + fileSize, fileSize < 13000); + } + + private void testRemoveAfterDeleteAndClose() throws Exception { + if (config.memory || config.cipher != null) { + return; + } + deleteDb("lob"); + Connection conn = getConnection("lob"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, data clob)"); + for (int i = 0; i < 10; i++) { + stat.execute("insert into test values(1, space(100000))"); + if (i > 5) { + ResultSet rs = stat.executeQuery("select * from test"); + rs.next(); + Clob c = rs.getClob(2); + stat.execute("delete from test where id = 1"); + c.getSubString(1, 10); + } else { + stat.execute("delete from test where id = 1"); + } + } + // some clobs are removed only here (those that were queries for) + conn.close(); + Recover.execute(getBaseDir(), "lob"); + long size = FileUtils.size(getBaseDir() + "/lob.h2.sql"); + assertTrue("size: " + size, size > 1000 && size < 10000); + } + + private void testLargeClob() throws Exception { + deleteDb("lob"); + Connection conn; + conn = reconnect(null); + conn.createStatement().execute( + "CREATE TABLE TEST(ID IDENTITY, C CLOB)"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST(C) VALUES(?)"); + int len = SysProperties.LOB_CLIENT_MAX_SIZE_MEMORY + 1; + prep.setCharacterStream(1, getRandomReader(len, 2), -1); + prep.execute(); + conn = reconnect(conn); + ResultSet rs = conn.createStatement().executeQuery( + "SELECT * FROM TEST ORDER BY ID"); + rs.next(); + assertEqualReaders(getRandomReader(len, 2), + rs.getCharacterStream("C"), -1); + assertFalse(rs.next()); + conn.close(); + } + + private void testRemovedAfterTimeout() throws Exception { + if (config.lazy) { + return; + } + deleteDb("lob"); + final String url = getURL("lob;lob_timeout=200", true); + Connection conn = getConnection(url); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, data clob)"); + PreparedStatement prep = conn.prepareStatement("insert into test values(?, ?)"); + prep.setInt(1, 1); + prep.setString(2, "aaa" + new String(new char[1024 * 16]).replace((char) 0, 'x')); + prep.execute(); + prep.setInt(1, 2); + prep.setString(2, "bbb" + new String(new char[1024 * 16]).replace((char) 0, 'x')); + prep.execute(); + ResultSet rs = stat.executeQuery("select * from test order by id"); + rs.next(); + Clob c1 = rs.getClob(2); + assertEquals("aaa", c1.getSubString(1, 3)); + rs.next(); + assertEquals("aaa", c1.getSubString(1, 3)); + rs.close(); + assertEquals("aaa", c1.getSubString(1, 3)); + stat.execute("delete from test"); + c1.getSubString(1, 3); + // wait until it times out + Thread.sleep(250); + // start a new transaction, to be sure + stat.execute("delete from test"); + c1.getSubString(1, 3); + conn.close(); + assertThrows(SQLException.class, c1).getSubString(1, 3); + } + + private void testConcurrentRemoveRead() throws Exception { + if (config.lazy) { + return; + } + deleteDb("lob"); + final String url = getURL("lob", true); + Connection conn = getConnection(url); + Statement stat = conn.createStatement(); + stat.execute("set max_length_inplace_lob 5"); + stat.execute("create table lob(data clob)"); + stat.execute("insert into lob values(space(100))"); + Connection conn2 = getConnection(url); + Statement stat2 = conn2.createStatement(); + ResultSet rs = stat2.executeQuery("select data from lob"); + rs.next(); + stat.execute("delete lob"); + InputStream in = rs.getBinaryStream(1); + in.read(); + conn2.close(); + conn.close(); + } + + private void testCloseLobTwice() throws SQLException { + deleteDb("lob"); + Connection conn = getConnection("lob"); + PreparedStatement prep = conn.prepareStatement("set @c = ?"); + prep.setCharacterStream(1, new StringReader( + new String(new char[10000])), 10000); + prep.execute(); + prep.setCharacterStream(1, new StringReader( + new String(new char[10001])), 10001); + prep.execute(); + conn.setAutoCommit(true); + conn.close(); + } + + private void testReadManyLobs() throws Exception { + deleteDb("lob"); + Connection conn; + conn = getConnection("lob"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id identity, data clob)"); + PreparedStatement prep = conn.prepareStatement( + "insert into test(data) values ?"); + byte[] data = new byte[256]; + Random r = new Random(1); + for (int i = 0; i < 1000; i++) { + r.nextBytes(data); + prep.setBinaryStream(1, new ByteArrayInputStream(data), -1); + prep.execute(); + } + ResultSet rs = stat.executeQuery("select * from test"); + while (rs.next()) { + rs.getString(2); + } + conn.close(); + } + + private void testLobSkip() throws Exception { + deleteDb("lob"); + Connection conn; + conn = getConnection("lob"); + Statement stat = conn.createStatement(); + stat.executeUpdate("create table test(x blob) as select secure_rand(1000)"); + ResultSet rs = stat.executeQuery("select * from test"); + rs.next(); + Blob b = rs.getBlob(1); + byte[] test = b.getBytes(5 + 1, 1000 - 5); + assertEquals(1000 - 5, test.length); + stat.execute("drop table test"); + conn.close(); + } + + private void testLobSkipPastEnd() throws Exception { + if (config.memory) { + return; + } + deleteDb("lob"); + Connection conn; + conn = getConnection("lob"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int, data blob)"); + byte[] data = new byte[150000]; + new Random(0).nextBytes(data); + PreparedStatement prep = conn.prepareStatement("insert into test values(1, ?)"); + prep.setBytes(1, data); + prep.execute(); + ResultSet rs = stat.executeQuery("select data from test"); + rs.next(); + for (int blockSize = 1; blockSize < 100000; blockSize *= 10) { + for (int i = 0; i < data.length; i += 1000) { + InputStream in = rs.getBinaryStream(1); + in.skip(i); + byte[] d2 = new byte[data.length]; + int l = Math.min(blockSize, d2.length - i); + l = in.read(d2, i, l); + if (i >= data.length) { + assertEquals(-1, l); + } else if (i + blockSize >= data.length) { + assertEquals(data.length - i, l); + } + for (int j = i; j < blockSize && j < d2.length; j++) { + assertEquals(data[j], d2[j]); + } + } + } + stat.execute("drop table test"); + conn.close(); + } + + private void testCreateIndexOnLob() throws Exception { + if (config.memory) { + return; + } + deleteDb("lob"); + Connection conn; + conn = getConnection("lob"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int, name clob)"); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, stat). + execute("create index idx_n on test(name)"); + stat.execute("drop table test"); + conn.close(); + } + + private void testBlobInputStreamSeek(boolean upgraded) throws Exception { + deleteDb("lob"); + Connection conn; + conn = getConnection("lob"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, data blob)"); + PreparedStatement prep; + Random random = new Random(); + byte[] buff = new byte[500000]; + for (int i = 0; i < 10; i++) { + prep = conn.prepareStatement("insert into test values(?, ?)"); + prep.setInt(1, i); + random.setSeed(i); + random.nextBytes(buff); + prep.setBinaryStream(2, new ByteArrayInputStream(buff), -1); + prep.execute(); + } + prep = conn.prepareStatement("select * from test where id = ?"); + for (int i = 0; i < 1; i++) { + random.setSeed(i); + random.nextBytes(buff); + for (int j = 0; j < buff.length; j += 10000) { + prep.setInt(1, i); + ResultSet rs = prep.executeQuery(); + rs.next(); + InputStream in = rs.getBinaryStream(2); + in.skip(j); + int t = in.read(); + assertEquals(t, buff[j] & 0xff); + } + } + conn.close(); + } + + /** + * Test for issue 315: Java Level Deadlock on Database & Session Objects + */ + private void testDeadlock() throws Exception { + deleteDb("lob"); + Connection conn = getConnection("lob"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name clob)"); + stat.execute("insert into test select x, space(10000) from system_range(1, 3)"); + final Connection conn2 = getConnection("lob"); + Task task = new Task() { + + @Override + public void call() throws Exception { + Statement stat = conn2.createStatement(); + stat.setFetchSize(1); + for (int i = 0; !stop; i++) { + ResultSet rs = stat.executeQuery( + "select * from test where id > -" + i); + while (rs.next()) { + // ignore + } + } + } + + }; + task.execute(); + stat.execute("create table test2(id int primary key, name clob)"); + for (int i = 0; i < 100; i++) { + stat.execute("delete from test2"); + stat.execute("insert into test2 values(1, space(10000 + " + i + "))"); + } + task.get(); + conn.close(); + conn2.close(); + } + + Connection getDeadlock2Connection() throws SQLException { + return getConnection("lob;LOCK_TIMEOUT=60000"); + } + + private void testCopyManyLobs() throws Exception { + deleteDb("lob"); + Connection conn = getConnection("lob"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id identity default on null, data clob) " + + "as select null, space(10000)"); + stat.execute("insert into test(data) select data from test"); + stat.execute("insert into test(data) select data from test"); + stat.execute("insert into test(data) select data from test"); + stat.execute("insert into test(data) select data from test"); + stat.execute("delete from test where id < 10"); + stat.execute("shutdown compact"); + conn.close(); + } + + private void testCopyLob() throws Exception { + if (config.memory) { + return; + } + deleteDb("lob"); + Connection conn; + Statement stat; + ResultSet rs; + conn = getConnection("lob"); + stat = conn.createStatement(); + stat.execute("create table test(id identity, data clob) " + + "as select 1, space(10000)"); + stat.execute("insert into test(id, data) select 2, data from test"); + stat.execute("delete from test where id = 1"); + conn.close(); + conn = getConnection("lob"); + stat = conn.createStatement(); + rs = stat.executeQuery("select * from test"); + rs.next(); + assertEquals(10000, rs.getString(2).length()); + conn.close(); + } + + private void testConcurrentCreate() throws Exception { + deleteDb("lob"); + final JdbcConnection conn1 = (JdbcConnection) getConnection("lob"); + final JdbcConnection conn2 = (JdbcConnection) getConnection("lob"); + conn1.setAutoCommit(false); + conn2.setAutoCommit(false); + + final byte[] buffer = new byte[10000]; + + Task task1 = new Task() { + @Override + public void call() throws Exception { + while (!stop) { + Blob b = conn1.createBlob(); + OutputStream out = b.setBinaryStream(1); + out.write(buffer); + out.close(); + } + } + }; + Task task2 = new Task() { + @Override + public void call() throws Exception { + while (!stop) { + Blob b = conn2.createBlob(); + OutputStream out = b.setBinaryStream(1); + out.write(buffer); + out.close(); + } + } + }; + task1.execute(); + task2.execute(); + Thread.sleep(1000); + task1.get(); + task2.get(); + conn1.close(); + conn2.close(); + } + + private void testLobInLargeResult() throws Exception { + deleteDb("lob"); + Connection conn; + Statement stat; + conn = getConnection("lob"); + stat = conn.createStatement(); + stat.execute("create table test(id int, data clob) as " + + "select x, null from system_range(1, 1000)"); + stat.execute("insert into test values(0, space(10000))"); + stat.execute("set max_memory_rows 100"); + ResultSet rs = stat.executeQuery("select * from test order by id desc"); + while (rs.next()) { + // this threw a NullPointerException because + // the disk based result set didn't know the lob handler + } + conn.close(); + } + + private void testUniqueIndex() throws Exception { + deleteDb("lob"); + Connection conn; + Statement stat; + conn = getConnection("lob"); + stat = conn.createStatement(); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, stat).execute("create memory table test(x clob unique)"); + conn.close(); + } + + private void testConvert() throws Exception { + deleteDb("lob"); + Connection conn; + Statement stat; + conn = getConnection("lob"); + stat = conn.createStatement(); + stat.execute("create table test(id int, data blob)"); + stat.execute("insert into test values(1, '')"); + ResultSet rs; + rs = stat.executeQuery("select cast(data as clob) from test"); + rs.next(); + assertEquals("", rs.getString(1)); + stat.execute("drop table test"); + + stat.execute("create table test(id int, data clob)"); + stat.execute("insert into test values(1, '')"); + rs = stat.executeQuery("select cast(data as blob) from test"); + rs.next(); + assertEquals("", rs.getString(1)); + + conn.close(); + } + + private void testCreateAsSelect() throws Exception { + deleteDb("lob"); + Connection conn; + Statement stat; + conn = getConnection("lob"); + stat = conn.createStatement(); + stat.execute("create table test(id int, data clob) as select 1, space(10000)"); + conn.close(); + } + + private void testLobUpdateMany() throws SQLException { + deleteDb("lob"); + Connection conn = getConnection("lob"); + Statement stat = conn.createStatement(); + stat.execute("create table post(id int primary key, text clob) as " + + "select x, space(96) from system_range(1, 329)"); + PreparedStatement prep = conn.prepareStatement("update post set text = ?"); + prep.setCharacterStream(1, new StringReader(new String(new char[1025])), -1); + prep.executeUpdate(); + conn.close(); + } + + private void testLobServerMemory() throws SQLException { + deleteDb("lob"); + Connection conn = getConnection("lob"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT, DATA CLOB)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST VALUES(1, ?)"); + StringReader reader = new StringReader(new String(new char[100000])); + prep.setCharacterStream(1, reader, -1); + prep.execute(); + conn.close(); + } + + private void testLobVariable() throws SQLException { + deleteDb("lob"); + Connection conn = reconnect(null); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT, DATA CLOB)"); + stat.execute("INSERT INTO TEST VALUES(1, SPACE(100000))"); + stat.execute("SET @TOTAL = SELECT DATA FROM TEST WHERE ID=1"); + stat.execute("DROP TABLE TEST"); + stat.execute("CALL @TOTAL LIKE '%X'"); + stat.execute("CREATE TABLE TEST(ID INT, DATA CLOB)"); + stat.execute("INSERT INTO TEST VALUES(1, @TOTAL)"); + stat.execute("INSERT INTO TEST VALUES(2, @TOTAL)"); + stat.execute("DROP TABLE TEST"); + stat.execute("CALL @TOTAL LIKE '%X'"); + conn.close(); + } + + private void testLobDrop() throws SQLException { + if (config.networked) { + return; + } + deleteDb("lob"); + Connection conn = reconnect(null); + Statement stat = conn.createStatement(); + for (int i = 0; i < 500; i++) { + stat.execute("CREATE TABLE T" + i + "(ID INT, C CLOB)"); + } + stat.execute("CREATE TABLE TEST(ID INT, C CLOB)"); + stat.execute("INSERT INTO TEST VALUES(1, SPACE(10000))"); + for (int i = 0; i < 500; i++) { + stat.execute("DROP TABLE T" + i); + } + ResultSet rs = stat.executeQuery("SELECT * FROM TEST"); + while (rs.next()) { + rs.getString("C"); + } + conn.close(); + } + + private void testLobNoClose() throws Exception { + if (config.networked) { + return; + } + deleteDb("lob"); + Connection conn = reconnect(null); + conn.createStatement().execute( + "CREATE TABLE TEST(ID IDENTITY, DATA CLOB)"); + conn.createStatement().execute( + "INSERT INTO TEST VALUES(1, SPACE(10000))"); + ResultSet rs = conn.createStatement().executeQuery( + "SELECT DATA FROM TEST"); + rs.next(); + SysProperties.lobCloseBetweenReads = true; + Reader in = rs.getCharacterStream(1); + in.read(); + conn.createStatement().execute("DELETE FROM TEST"); + SysProperties.lobCloseBetweenReads = false; + conn.createStatement().execute( + "INSERT INTO TEST VALUES(1, SPACE(10000))"); + rs = conn.createStatement().executeQuery( + "SELECT DATA FROM TEST"); + rs.next(); + in = rs.getCharacterStream(1); + in.read(); + conn.setAutoCommit(false); + try { + conn.createStatement().execute("DELETE FROM TEST"); + conn.commit(); + // DELETE does not fail in Linux, but in Windows + // error("Error expected"); + // but reading afterwards should fail + int len = 0; + while (true) { + int x = in.read(); + if (x < 0) { + break; + } + len++; + } + in.close(); + if (len > 0) { + // in Linux, it seems it is still possible to read in files + // even if they are deleted + if (System.getProperty("os.name").indexOf("Windows") > 0) { + fail("Error expected; len=" + len); + } + } + } catch (SQLException e) { + assertKnownException(e); + } + conn.rollback(); + conn.close(); + } + + private void testLobTransactions(int spaceLen) throws SQLException { + deleteDb("lob"); + Connection conn = reconnect(null); + conn.createStatement().execute("CREATE TABLE TEST(ID IDENTITY, " + + "DATA CLOB, DATA2 VARCHAR)"); + conn.setAutoCommit(false); + Random random = new Random(0); + int rows = 0; + Savepoint sp = null; + int len = getSize(100, 400); + // config.traceTest = true; + for (int i = 0; i < len; i++) { + switch (random.nextInt(10)) { + case 0: + trace("insert " + i); + conn.createStatement().execute( + "INSERT INTO TEST(DATA, DATA2) VALUES('" + i + + "' || SPACE(" + spaceLen + "), '" + i + "')"); + rows++; + break; + case 1: + if (rows > 0) { + int x = random.nextInt(rows); + trace("delete " + x); + conn.createStatement().execute( + "DELETE FROM TEST WHERE ID=" + x); + } + break; + case 2: + if (rows > 0) { + int x = random.nextInt(rows); + trace("update " + x); + conn.createStatement().execute( + "UPDATE TEST SET DATA='x' || DATA, " + + "DATA2='x' || DATA2 WHERE ID=" + x); + } + break; + case 3: + if (rows > 0) { + trace("commit"); + conn.commit(); + sp = null; + } + break; + case 4: + if (rows > 0) { + trace("rollback"); + conn.rollback(); + sp = null; + } + break; + case 5: + trace("savepoint"); + sp = conn.setSavepoint(); + break; + case 6: + if (sp != null) { + trace("rollback to savepoint"); + conn.rollback(sp); + } + break; + case 7: + if (rows > 0) { + trace("checkpoint"); + conn.createStatement().execute("CHECKPOINT"); + trace("shutdown immediately"); + conn.createStatement().execute("SHUTDOWN IMMEDIATELY"); + trace("shutdown done"); + conn = reconnect(conn); + conn.setAutoCommit(false); + sp = null; + } + break; + default: + } + ResultSet rs = conn.createStatement().executeQuery( + "SELECT * FROM TEST"); + while (rs.next()) { + int id = rs.getInt("ID"); + String d1 = rs.getString("DATA").trim(); + String d2 = rs.getString("DATA2"); + assertEquals("id:" + id, d2, d1); + } + + } + conn.close(); + } + + private void testLobRollbackStop() throws SQLException { + deleteDb("lob"); + Connection conn = reconnect(null); + conn.createStatement().execute( + "CREATE TABLE TEST(ID INT PRIMARY KEY, DATA CLOB)"); + conn.createStatement().execute( + "INSERT INTO TEST VALUES(1, SPACE(10000))"); + conn.setAutoCommit(false); + conn.createStatement().execute("DELETE FROM TEST"); + conn.createStatement().execute("CHECKPOINT"); + conn.createStatement().execute("SHUTDOWN IMMEDIATELY"); + conn = reconnect(conn); + ResultSet rs = conn.createStatement().executeQuery("SELECT * FROM TEST"); + assertTrue(rs.next()); + rs.getInt(1); + assertEquals(10000, rs.getString(2).length()); + conn.close(); + } + + private void testLobCopy() throws SQLException { + deleteDb("lob"); + Connection conn = reconnect(null); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int, data clob)"); + stat.execute("insert into test values(1, space(1000));"); + stat.execute("insert into test values(2, space(10000));"); + stat.execute("create table test2(id int, data clob);"); + stat.execute("insert into test2 select * from test;"); + stat.execute("drop table test;"); + stat.execute("select * from test2;"); + stat.execute("update test2 set id=id;"); + stat.execute("select * from test2;"); + conn.close(); + } + + private void testLobHibernate() throws Exception { + deleteDb("lob"); + Connection conn0 = reconnect(null); + + conn0.getAutoCommit(); + conn0.setAutoCommit(false); + DatabaseMetaData dbMeta0 = conn0.getMetaData(); + dbMeta0.getDatabaseProductName(); + dbMeta0.getDatabaseMajorVersion(); + dbMeta0.getDatabaseProductVersion(); + dbMeta0.getDriverName(); + dbMeta0.getDriverVersion(); + dbMeta0.supportsResultSetType(1004); + dbMeta0.supportsBatchUpdates(); + dbMeta0.dataDefinitionCausesTransactionCommit(); + dbMeta0.dataDefinitionIgnoredInTransactions(); + dbMeta0.supportsGetGeneratedKeys(); + conn0.getAutoCommit(); + conn0.getAutoCommit(); + conn0.commit(); + conn0.setAutoCommit(true); + Statement stat0 = conn0.createStatement(); + stat0.executeUpdate("drop table CLOB_ENTITY if exists"); + stat0.getWarnings(); + stat0.executeUpdate("create table CLOB_ENTITY (ID bigint not null, " + + "DATA clob, CLOB_DATA clob, primary key (ID))"); + stat0.getWarnings(); + stat0.close(); + conn0.getWarnings(); + conn0.clearWarnings(); + conn0.setAutoCommit(false); + conn0.getAutoCommit(); + conn0.getAutoCommit(); + PreparedStatement prep0 = conn0.prepareStatement( + "select max(ID) from CLOB_ENTITY"); + ResultSet rs0 = prep0.executeQuery(); + rs0.next(); + rs0.getLong(1); + rs0.wasNull(); + rs0.close(); + prep0.close(); + conn0.getAutoCommit(); + PreparedStatement prep1 = conn0 + .prepareStatement("insert into CLOB_ENTITY" + + "(DATA, CLOB_DATA, ID) values (?, ?, ?)"); + prep1.setNull(1, 2005); + StringBuilder buff = new StringBuilder(10000); + for (int i = 0; i < 10000; i++) { + buff.append((char) ('0' + (i % 10))); + } + Reader x = new StringReader(buff.toString()); + prep1.setCharacterStream(2, x, 10000); + prep1.setLong(3, 1); + prep1.addBatch(); + prep1.executeBatch(); + prep1.close(); + conn0.getAutoCommit(); + conn0.getAutoCommit(); + conn0.commit(); + conn0.isClosed(); + conn0.getWarnings(); + conn0.clearWarnings(); + conn0.getAutoCommit(); + conn0.getAutoCommit(); + PreparedStatement prep2 = conn0 + .prepareStatement("select c_.ID as ID0_0_, c_.DATA as S_, " + + "c_.CLOB_DATA as CLOB3_0_0_ from CLOB_ENTITY c_ where c_.ID=?"); + prep2.setLong(1, 1); + ResultSet rs1 = prep2.executeQuery(); + rs1.next(); + rs1.getCharacterStream("S_"); + Clob clob0 = rs1.getClob("CLOB3_0_0_"); + rs1.wasNull(); + rs1.next(); + rs1.close(); + prep2.getMaxRows(); + prep2.getQueryTimeout(); + prep2.close(); + conn0.getAutoCommit(); + Reader r; + int ch; + r = clob0.getCharacterStream(); + for (int i = 0; i < 10000; i++) { + ch = r.read(); + if (ch != ('0' + (i % 10))) { + fail("expected " + (char) ('0' + (i % 10)) + + " got: " + ch + " (" + (char) ch + ")"); + } + } + ch = r.read(); + if (ch != -1) { + fail("expected -1 got: " + ch); + } + r.close(); + r = clob0.getCharacterStream(1235, 1000); + for (int i = 1234; i < 2234; i++) { + ch = r.read(); + if (ch != ('0' + (i % 10))) { + fail("expected " + (char) ('0' + (i % 10)) + + " got: " + ch + " (" + (char) ch + ")"); + } + } + ch = r.read(); + if (ch != -1) { + fail("expected -1 got: " + ch); + } + r.close(); + assertThrows(ErrorCode.INVALID_VALUE_2, clob0).getCharacterStream(10001, 1); + assertThrows(ErrorCode.INVALID_VALUE_2, clob0).getCharacterStream(10002, 0); + conn0.close(); + } + + private void testLobCopy2() throws SQLException { + deleteDb("lob"); + Connection conn; + conn = reconnect(null); + Statement stat = conn.createStatement(); + conn = reconnect(conn); + stat = conn.createStatement(); + stat.execute("create table test(text clob)"); + stat.execute("create table test2(text clob)"); + StringBuilder buff = new StringBuilder(); + for (int i = 0; i < 1000; i++) { + buff.append(' '); + } + String spaces = buff.toString(); + stat.execute("insert into test values('" + spaces + "')"); + stat.execute("insert into test2 select * from test"); + ResultSet rs = stat.executeQuery("select * from test2"); + rs.next(); + assertEquals(spaces, rs.getString(1)); + stat.execute("drop table test"); + rs = stat.executeQuery("select * from test2"); + rs.next(); + assertEquals(spaces, rs.getString(1)); + stat.execute("alter table test2 add column id int before text"); + rs = stat.executeQuery("select * from test2"); + rs.next(); + assertEquals(spaces, rs.getString("text")); + conn.close(); + } + + private void testManyLobs() throws Exception { + deleteDb("lob"); + Connection conn; + conn = reconnect(null); + conn.createStatement().execute( + "CREATE TABLE TEST(ID INT PRIMARY KEY, B BLOB, C CLOB)"); + int len = getSize(10, 2000); + if (config.networked) { + len = 100; + } + + int first = 1, increment = 19; + + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST(ID, B, C) VALUES(?, ?, ?)"); + for (int i = first; i < len; i += increment) { + int l = i; + prep.setInt(1, i); + prep.setBinaryStream(2, getRandomStream(l, i), -1); + prep.setCharacterStream(3, getRandomReader(l, i), -1); + prep.execute(); + } + + conn = reconnect(conn); + ResultSet rs = conn.createStatement().executeQuery( + "SELECT * FROM TEST ORDER BY ID"); + while (rs.next()) { + int i = rs.getInt("ID"); + Blob b = rs.getBlob("B"); + Clob c = rs.getClob("C"); + int l = i; + assertEquals(l, b.length()); + assertEquals(l, c.length()); + assertEqualStreams(getRandomStream(l, i), b.getBinaryStream(), -1); + assertEqualReaders(getRandomReader(l, i), c.getCharacterStream(), -1); + } + + prep = conn.prepareStatement( + "UPDATE TEST SET B=?, C=? WHERE ID=?"); + for (int i = first; i < len; i += increment) { + int l = i; + prep.setBinaryStream(1, getRandomStream(l, -i), -1); + prep.setCharacterStream(2, getRandomReader(l, -i), -1); + prep.setInt(3, i); + prep.execute(); + } + + conn = reconnect(conn); + rs = conn.createStatement().executeQuery( + "SELECT * FROM TEST ORDER BY ID"); + while (rs.next()) { + int i = rs.getInt("ID"); + Blob b = rs.getBlob("B"); + Clob c = rs.getClob("C"); + int l = i; + assertEquals(l, b.length()); + assertEquals(l, c.length()); + assertEqualStreams(getRandomStream(l, -i), b.getBinaryStream(), -1); + assertEqualReaders(getRandomReader(l, -i), c.getCharacterStream(), -1); + } + + conn.close(); + } + + private void testClob() throws Exception { + deleteDb("lob"); + Connection conn; + conn = reconnect(null); + conn.createStatement().execute( + "CREATE TABLE TEST(ID IDENTITY, C CLOB)"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST(C) VALUES(?)"); + prep.setCharacterStream(1, + new CharArrayReader("Bohlen".toCharArray()), "Bohlen".length()); + prep.execute(); + prep.setCharacterStream(1, + new CharArrayReader("B\u00f6hlen".toCharArray()), "B\u00f6hlen".length()); + prep.execute(); + prep.setCharacterStream(1, getRandomReader(501, 1), -1); + prep.execute(); + prep.setCharacterStream(1, getRandomReader(1501, 2), 401); + prep.execute(); + conn = reconnect(conn); + ResultSet rs = conn.createStatement().executeQuery( + "SELECT * FROM TEST ORDER BY ID"); + rs.next(); + assertEquals("Bohlen", rs.getString("C")); + assertEqualReaders(new CharArrayReader("Bohlen".toCharArray()), + rs.getCharacterStream("C"), -1); + rs.next(); + assertEqualReaders(new CharArrayReader("B\u00f6hlen".toCharArray()), + rs.getCharacterStream("C"), -1); + rs.next(); + assertEqualReaders(getRandomReader(501, 1), + rs.getCharacterStream("C"), -1); + Clob clob = rs.getClob("C"); + assertEqualReaders(getRandomReader(501, 1), + clob.getCharacterStream(), -1); + assertEquals(501, clob.length()); + rs.next(); + assertEqualReaders(getRandomReader(401, 2), + rs.getCharacterStream("C"), -1); + assertEqualReaders(getRandomReader(1500, 2), + rs.getCharacterStream("C"), 401); + clob = rs.getClob("C"); + assertEqualReaders(getRandomReader(1501, 2), + clob.getCharacterStream(), 401); + assertEqualReaders(getRandomReader(401, 2), + clob.getCharacterStream(), 401); + assertEquals(401, clob.length()); + assertFalse(rs.next()); + conn.close(); + } + + private Connection reconnect(Connection conn) throws SQLException { + long time = System.nanoTime(); + if (conn != null) { + JdbcUtils.closeSilently(conn); + } + conn = getConnection("lob"); + trace("re-connect=" + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - time)); + return conn; + } + + private void testUpdateLob() throws SQLException { + deleteDb("lob"); + Connection conn; + conn = reconnect(null); + + PreparedStatement prep = conn + .prepareStatement( + "CREATE TABLE IF NOT EXISTS p( id int primary key, rawbyte BLOB ); "); + prep.execute(); + prep.close(); + prep = conn.prepareStatement("INSERT INTO p(id) VALUES(?);"); + for (int i = 0; i < 10; i++) { + prep.setInt(1, i); + prep.execute(); + } + prep.close(); + + prep = conn.prepareStatement("UPDATE p set rawbyte=? WHERE id=?"); + for (int i = 0; i < 8; i++) { + prep.setBinaryStream(1, getRandomStream(10000, i), 0); + prep.setInt(2, i); + prep.execute(); + } + prep.close(); + conn.commit(); + + conn = reconnect(conn); + + conn.setAutoCommit(true); + prep = conn.prepareStatement("UPDATE p set rawbyte=? WHERE id=?"); + for (int i = 8; i < 10; i++) { + prep.setBinaryStream(1, getRandomStream(10000, i), 0); + prep.setInt(2, i); + prep.execute(); + } + prep.close(); + + prep = conn.prepareStatement("SELECT * from p"); + ResultSet rs = prep.executeQuery(); + while (rs.next()) { + for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++) { + rs.getMetaData().getColumnName(i); + rs.getString(i); + } + } + conn.close(); + } + + private void testLobReconnect() throws Exception { + deleteDb("lob"); + Connection conn = reconnect(null); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, TEXT CLOB)"); + PreparedStatement prep; + prep = conn.prepareStatement("INSERT INTO TEST VALUES(1, ?)"); + String s = new String(getRandomChars(10000, 1)); + byte[] data = s.getBytes(StandardCharsets.UTF_8); + // if we keep the string, debugging with Eclipse is not possible + // because Eclipse wants to display the large string and fails + s = ""; + prep.setBinaryStream(1, new ByteArrayInputStream(data), 0); + prep.execute(); + + conn = reconnect(conn); + stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("SELECT * FROM TEST WHERE ID=1"); + rs.next(); + InputStream in = new ByteArrayInputStream(data); + assertEqualStreams(in, rs.getBinaryStream("TEXT"), -1); + + prep = conn.prepareStatement("UPDATE TEST SET TEXT = ?"); + prep.setBinaryStream(1, new ByteArrayInputStream(data), 0); + prep.execute(); + + conn = reconnect(conn); + stat = conn.createStatement(); + rs = stat.executeQuery("SELECT * FROM TEST WHERE ID=1"); + rs.next(); + assertEqualStreams(rs.getBinaryStream("TEXT"), + new ByteArrayInputStream(data), -1); + + stat.execute("DROP TABLE IF EXISTS TEST"); + conn.close(); + } + + private void testLob(boolean clob) throws Exception { + deleteDb("lob"); + Connection conn = reconnect(null); + conn = reconnect(conn); + Statement stat = conn.createStatement(); + stat.execute("DROP TABLE IF EXISTS TEST"); + PreparedStatement prep; + ResultSet rs; + long time; + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, V " + + (clob ? "CLOB" : "BLOB") + ")"); + + int len = getSize(1, 1000); + if (config.networked && config.big) { + len = 100; + } + + time = System.nanoTime(); + prep = conn.prepareStatement("INSERT INTO TEST VALUES(?, ?)"); + for (int i = 0; i < len; i += i + i + 1) { + prep.setInt(1, i); + int size = i * i; + if (clob) { + prep.setCharacterStream(2, getRandomReader(size, i), 0); + } else { + prep.setBinaryStream(2, getRandomStream(size, i), 0); + } + prep.execute(); + } + trace("insert=" + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - time)); + traceMemory(); + conn = reconnect(conn); + + time = System.nanoTime(); + prep = conn.prepareStatement("SELECT ID, V FROM TEST"); + rs = prep.executeQuery(); + while (rs.next()) { + int id = rs.getInt("ID"); + int size = id * id; + if (clob) { + Reader rt = rs.getCharacterStream(2); + assertEqualReaders(getRandomReader(size, id), rt, -1); + Object obj = rs.getObject(2); + if (obj instanceof Clob) { + obj = ((Clob) obj).getCharacterStream(); + } + assertEqualReaders(getRandomReader(size, id), + (Reader) obj, -1); + } else { + InputStream in = rs.getBinaryStream(2); + assertEqualStreams(getRandomStream(size, id), in, -1); + Object obj = rs.getObject(2); + if (obj instanceof Blob) { + obj = ((Blob) obj).getBinaryStream(); + } + assertEqualStreams(getRandomStream(size, id), + (InputStream) obj, -1); + } + } + trace("select=" + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - time)); + traceMemory(); + + conn = reconnect(conn); + + time = System.nanoTime(); + prep = conn.prepareStatement("DELETE FROM TEST WHERE ID=?"); + for (int i = 0; i < len; i++) { + prep.setInt(1, i); + prep.executeUpdate(); + } + trace("delete=" + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - time)); + traceMemory(); + conn = reconnect(conn); + + conn.setAutoCommit(false); + prep = conn.prepareStatement("INSERT INTO TEST VALUES(1, ?)"); + if (clob) { + prep.setCharacterStream(1, getRandomReader(0, 0), 0); + } else { + prep.setBinaryStream(1, getRandomStream(0, 0), 0); + } + prep.execute(); + conn.rollback(); + prep.execute(); + conn.commit(); + + conn.createStatement().execute("DELETE FROM TEST WHERE ID=1"); + conn.rollback(); + conn.createStatement().execute("DELETE FROM TEST WHERE ID=1"); + conn.commit(); + + conn.createStatement().execute("DROP TABLE TEST"); + conn.close(); + } + + private void testJavaObject() throws SQLException { + deleteDb("lob"); + JdbcConnection conn = (JdbcConnection) getConnection("lob"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, DATA OTHER)"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST VALUES(1, ?)"); + prep.setObject(1, new TestLobObject("abc")); + prep.execute(); + ResultSet rs = conn.createStatement().executeQuery("SELECT * FROM TEST"); + rs.next(); + Object oa = rs.getObject(2); + assertEquals(TestLobObject.class.getName(), oa.getClass().getName()); + Object ob = rs.getObject("DATA"); + assertEquals(TestLobObject.class.getName(), ob.getClass().getName()); + assertEquals("TestLobObject: abc", oa.toString()); + assertEquals("TestLobObject: abc", ob.toString()); + assertFalse(rs.next()); + + conn.createStatement().execute("drop table test"); + stat.execute("create table test(v other)"); + prep = conn.prepareStatement("insert into test values(?)"); + prep.setObject(1, JdbcUtils.serialize("", conn.getJavaObjectSerializer())); + prep.execute(); + rs = stat.executeQuery("select v from test"); + while (rs.next()) { + assertEquals("", (String) rs.getObject("v")); + } + conn.close(); + } + + /** + * Test a bug where the usage of BufferedInputStream in LobStorageMap was + * causing a deadlock. + */ + private void testBufferedInputStreamBug() throws SQLException { + deleteDb("lob"); + JdbcConnection conn = (JdbcConnection) getConnection("lob"); + conn.createStatement().execute("CREATE TABLE TEST(test BLOB)"); + PreparedStatement ps = conn.prepareStatement("INSERT INTO TEST(test) VALUES(?)"); + ps.setBlob(1, new ByteArrayInputStream(new byte[257])); + ps.executeUpdate(); + conn.close(); + } + + private static Reader getRandomReader(int len, int seed) { + return new CharArrayReader(getRandomChars(len, seed)); + } + + private static char[] getRandomChars(int len, int seed) { + Random random = new Random(seed); + char[] buff = new char[len]; + for (int i = 0; i < len; i++) { + char ch; + do { + ch = (char) random.nextInt(Character.MAX_VALUE); + // UTF8: String.getBytes("UTF-8") only returns 1 byte for + // 0xd800-0xdfff + } while (ch >= 0xd800 && ch <= 0xdfff); + buff[i] = ch; + } + return buff; + } + + private static InputStream getRandomStream(int len, int seed) { + Random random = new Random(seed); + byte[] buff = new byte[len]; + random.nextBytes(buff); + return new ByteArrayInputStream(buff); + } + + /** + * Test the combination of updating a table which contains an LOB, and + * reading from the LOB at the same time + */ + private void testUpdatingLobRow() throws Exception { + if (config.memory) { + return; + } + deleteDb("lob"); + Connection conn = getConnection("lob"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, " + + "name clob, counter int)"); + stat.execute("insert into test(id, name) select x, " + + "space(100000) from system_range(1, 3)"); + + ResultSet rs = stat.executeQuery("select name " + + "from test where id = 1"); + rs.next(); + Reader r = rs.getClob("name").getCharacterStream(); + Random random = new Random(); + char[] tmp = new char[256]; + while (r.read(tmp) > 0) { + stat.execute("update test set counter = " + + random.nextInt(1000) + " where id = 1"); + } + r.close(); + conn.close(); + } + + private void testCommitOnExclusiveConnection() throws Exception { + deleteDb("lob"); + Connection conn = getConnection("lob;EXCLUSIVE=1"); + Statement statement = conn.createStatement(); + statement.execute("drop table if exists TEST"); + statement.execute("create table TEST (COL INTEGER, LOB CLOB)"); + conn.setAutoCommit(false); + statement.execute("insert into TEST (COL, LOB) values (1, '" + + MORE_THAN_128_CHARS + "')"); + statement.execute("update TEST set COL=2"); + // OK + // statement.execute("commit"); + // KO : should not hang + conn.commit(); + conn.close(); + } + + private void testClobWithRandomUnicodeChars() throws Exception { + // This tests an issue we had with storing unicode surrogate pairs, + // which only manifested at the boundaries between blocks i.e. at 4k + // boundaries + deleteDb("lob"); + Connection conn = getConnection("lob"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE logs" + + "(id int primary key auto_increment, message CLOB)"); + PreparedStatement s1 = conn.prepareStatement( + "INSERT INTO logs (message) VALUES ?"); + final Random rand = new Random(1); + for (int i = 1; i <= 100; i++) { + String data = randomUnicodeString(rand); + s1.setString(1, data); + s1.executeUpdate(); + ResultSet rs = stat.executeQuery("SELECT id, message " + + "FROM logs ORDER BY id DESC LIMIT 1"); + rs.next(); + String read = rs.getString(2); + if (!read.equals(data)) { + for (int j = 0; j < read.length(); j++) { + assertEquals("pos: " + j + " i:" + i, read.charAt(j), data.charAt(j)); + } + } + assertEquals(read, data); + } + conn.close(); + } + + private static String randomUnicodeString(Random rand) { + int count = 10000; + final char[] buffer = new char[count]; + while (count-- != 0) { + char ch = (char) rand.nextInt(); + if (ch >= 56320 && ch <= 57343) { + if (count == 0) { + count++; + } else { + // low surrogate, insert high surrogate after putting it + // in + buffer[count] = ch; + count--; + buffer[count] = (char) (55296 + rand.nextInt(128)); + } + } else if (ch >= 55296 && ch <= 56191) { + if (count == 0) { + count++; + } else { + // high surrogate, insert low surrogate before putting + // it in + buffer[count] = (char) (56320 + rand.nextInt(128)); + count--; + buffer[count] = ch; + } + } else if (ch >= 56192 && ch <= 56319) { + // private high surrogate: no clue, so skip it + count++; + } else { + buffer[count] = ch; + } + } + return new String(buffer); + } + + private void testLobInValueResultSet() throws SQLException { + deleteDb("lob"); + JdbcConnection conn = (JdbcConnection) getConnection("lob"); + Statement stat = conn.createStatement(); + stat.execute("CREATE ALIAS VRS FOR '" + getClass().getName() + ".testLobInValueResultSetGet'"); + ResultSet rs = stat.executeQuery("SELECT * FROM VRS()"); + assertTrue(rs.next()); + Clob clob = rs.getClob(1); + assertFalse(rs.next()); + assertEquals(MORE_THAN_128_CHARS, clob.getSubString(1, Integer.MAX_VALUE)); + conn.close(); + } + + /** + * This method is called via reflection from the database. + * + * @param conn connection + * @return the result set + * @throws SQLException on exception + */ + public static SimpleResultSet testLobInValueResultSetGet(Connection conn) throws SQLException { + final Clob c = conn.createClob(); + c.setString(1, MORE_THAN_128_CHARS); + SimpleResultSet rs = new SimpleResultSet() { + @Override + public Object getObject(int columnIndex) throws SQLException { + return c; + } + }; + rs.addColumn("L", Types.CLOB, 1000, 0); + rs.addRow(MORE_THAN_128_CHARS); + return rs; + } + + private void testLimits() throws Exception { + deleteDb("lob"); + JdbcConnection conn = (JdbcConnection) getConnection("lob"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INTEGER, B BLOB, C CLOB)"); + PreparedStatement ps = conn.prepareStatement("INSERT INTO TEST VALUES (?, ?, ?)"); + ps.setInt(1, 1); + byte[] b = new byte[Constants.MAX_STRING_LENGTH]; + Arrays.fill(b, (byte) 'A'); + String s = new String(b, StandardCharsets.UTF_8); + ps.setBytes(2, b); + ps.setString(3, s); + ps.executeUpdate(); + byte[] b2 = new byte[Constants.MAX_STRING_LENGTH + 1]; + Arrays.fill(b2, (byte) 'A'); + String s2 = new String(b2, StandardCharsets.UTF_8); + assertThrows(ErrorCode.VALUE_TOO_LONG_2, ps).setBytes(2, b2); + ps.setBinaryStream(2, new ByteArrayInputStream(b2)); + assertThrows(ErrorCode.VALUE_TOO_LONG_2, ps).setString(3, s2); + ps.setCharacterStream(3, new StringReader(s2)); + ps.executeUpdate(); + try (ResultSet rs = stat.executeQuery("TABLE TEST ORDER BY ID")) { + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + testLimitsSmall(b, s, rs, 2); + testLimitsSmall(b, s, rs, 2); + testLimitsSmall(b, s, rs, 3); + testLimitsSmall(b, s, rs, 3); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + testLimitsLarge(b2, s2, rs, 2); + testLimitsLarge(b2, s2, rs, 2); + testLimitsLarge(b2, s2, rs, 3); + testLimitsLarge(b2, s2, rs, 3); + assertFalse(rs.next()); + } + conn.close(); + testLimitsSmall(b, s, ValueBlob.createSmall(b)); + testLimitsSmall(b, s, ValueClob.createSmall(b, Constants.MAX_STRING_LENGTH)); + testLimitsLarge(b2, s2, ValueBlob.createSmall(b2)); + testLimitsLarge(b2, s2, ValueClob.createSmall(b2, Constants.MAX_STRING_LENGTH + 1)); + } + + private void testLimitsSmall(byte[] b, String s, ResultSet rs, int index) throws SQLException { + assertEquals(b, rs.getBytes(index)); + assertEquals(s, rs.getString(index)); + } + + private void testLimitsLarge(byte[] b, String s, ResultSet rs, int index) throws SQLException, IOException { + assertThrows(ErrorCode.VALUE_TOO_LONG_2, rs).getBytes(index); + assertEquals(b, IOUtils.readBytesAndClose(rs.getBlob(index).getBinaryStream(), -1)); + assertThrows(ErrorCode.VALUE_TOO_LONG_2, rs).getString(index); + assertEquals(s, IOUtils.readStringAndClose(rs.getClob(index).getCharacterStream(), -1)); + } + + private void testLimitsSmall(byte[] b, String s, ValueLob v) { + assertEquals(b, v.getBytesNoCopy()); + assertEquals(s, v.getString()); + assertEquals(s, v.getString()); + } + + private void testLimitsLarge(byte[] b, String s, ValueLob v) throws IOException { + try { + assertEquals(b, v.getBytesNoCopy()); + throw new AssertionError(); + } catch (DbException e) { + assertEquals(ErrorCode.VALUE_TOO_LONG_2, e.getErrorCode()); + } + assertEquals(b, IOUtils.readBytesAndClose(v.getInputStream(), -1)); + for (int i = 0; i < 2; i++) { + try { + assertEquals(s, v.getString()); + throw new AssertionError(); + } catch (DbException e) { + assertEquals(ErrorCode.VALUE_TOO_LONG_2, e.getErrorCode()); + } + 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/TestLobObject.java b/h2/src/test/org/h2/test/db/TestLobObject.java new file mode 100644 index 0000000..b150fc5 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestLobObject.java @@ -0,0 +1,26 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.io.Serializable; + +/** + * A utility class for TestLob. + */ +class TestLobObject implements Serializable { + + private static final long serialVersionUID = 1L; + String data; + + TestLobObject(String data) { + this.data = data; + } + + @Override + public String toString() { + return "TestLobObject: " + data; + } +} diff --git a/h2/src/test/org/h2/test/db/TestMemoryUsage.java b/h2/src/test/org/h2/test/db/TestMemoryUsage.java new file mode 100644 index 0000000..dbf367d --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestMemoryUsage.java @@ -0,0 +1,305 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.Utils; + +/** + * Tests the memory usage of the cache. + */ +public class TestMemoryUsage extends TestDb { + + private Connection conn; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + testOpenCloseConnections(); + if (getBaseDir().indexOf(':') >= 0) { + // can't test in-memory databases + return; + } + // comment this out for now, not reliable when running on my 64-bit + // Java1.8 VM + // testCreateDropLoop(); + testCreateIndex(); + testClob(); + testReconnectOften(); + + deleteDb("memoryUsage"); + reconnect(); + insertUpdateSelectDelete(); + reconnect(); + insertUpdateSelectDelete(); + conn.close(); + + deleteDb("memoryUsage"); + } + + private void testOpenCloseConnections() throws SQLException { + if (!config.big) { + return; + } + deleteDb("memoryUsage"); + // to eliminate background thread interference + conn = getConnection("memoryUsage;WRITE_DELAY=0"); + try { + eatMemory(4000); + for (int i = 0; i < 4000; i++) { + Connection c2 = getConnection("memoryUsage"); + c2.createStatement(); + c2.close(); + } + } finally { + freeMemory(); + closeConnection(conn); + } + } + + private void testCreateDropLoop() throws SQLException { + deleteDb("memoryUsageCreateDropLoop"); + conn = getConnection("memoryUsageCreateDropLoop"); + Statement stat = conn.createStatement(); + for (int i = 0; i < 100; i++) { + stat.execute("CREATE TABLE TEST(ID INT)"); + stat.execute("DROP TABLE TEST"); + } + stat.execute("checkpoint"); + long used = Utils.getMemoryUsed(); + for (int i = 0; i < 1000; i++) { + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY)"); + stat.execute("DROP TABLE TEST"); + } + stat.execute("checkpoint"); + long usedNow = Utils.getMemoryUsed(); + if (usedNow > used * 1.3) { + // try to lower memory usage (because it might be wrong) + // by forcing OOME + for (int i = 1024; i < (1 >> 31); i *= 2) { + try { + byte[] oome = new byte[1024 * 1024 * 256]; + oome[0] = (byte) i; + } catch (OutOfMemoryError e) { + break; + } + } + usedNow = Utils.getMemoryUsed(); + if (usedNow > used * 1.3) { + assertEquals(used, usedNow); + } + } + conn.close(); + } + + + private void reconnect() throws SQLException { + if (conn != null) { + conn.close(); + } + // Class.forName("org.hsqldb.jdbcDriver"); + // conn = DriverManager.getConnection("jdbc:hsqldb:test", "sa", ""); + conn = getConnection("memoryUsage"); + } + + private void testClob() throws SQLException { + if (config.memory || !config.big) { + return; + } + deleteDb("memoryUsageClob"); + conn = getConnection("memoryUsageClob;WRITE_DELAY=0"); + Statement stat = conn.createStatement(); + stat.execute("SET MAX_LENGTH_INPLACE_LOB 8192"); + stat.execute("SET CACHE_SIZE 8000"); + stat.execute("CREATE TABLE TEST(ID IDENTITY, DATA CLOB)"); + try { + long base = Utils.getMemoryUsed(); + for (int i = 0; i < 4; i++) { + stat.execute("INSERT INTO TEST(DATA) " + + "SELECT SPACE(8000) FROM SYSTEM_RANGE(1, 800)"); + long used = Utils.getMemoryUsed(); + if ((used - base) > 3 * 8192) { + fail("Used: " + (used - base) + " i: " + i); + } + } + } finally { + freeMemory(); + closeConnection(conn); + } + } + + /** + * Closes the specified connection. It silently consumes OUT_OF_MEMORY that + * may happen in background thread during the tests. + * + * @param conn connection to close + * @throws SQLException on other SQL exception + */ + private static void closeConnection(Connection conn) throws SQLException { + try { + conn.close(); + } catch (SQLException e) { + if (e.getErrorCode() != ErrorCode.OUT_OF_MEMORY) { + throw e; + } + } + } + + private void testCreateIndex() throws SQLException { + if (config.memory) { + return; + } + deleteDb("memoryUsage"); + conn = getConnection("memoryUsage"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int, name varchar(255))"); + PreparedStatement prep = conn.prepareStatement( + "insert into test values(?, space(200))"); + int len = getSize(10000, 100000); + for (int i = 0; i < len; i++) { + if (i % 1000 == 0) { + // trace("[" + i + "/" + len + "] KB: " + + // MemoryUtils.getMemoryUsed()); + } + prep.setInt(1, i); + prep.executeUpdate(); + } + long base = Utils.getMemoryUsed(); + stat.execute("create index idx_test_id on test(id)"); + for (int i = 0;; i++) { + System.gc(); + long used = Utils.getMemoryUsed() - base; + if (used <= getSize(7500, 12000)) { + break; + } + if (i < 16) { + continue; + } + fail("Used: " + used); + } + stat.execute("drop table test"); + conn.close(); + } + + private void testReconnectOften() throws SQLException { + deleteDb("memoryUsage"); + Connection conn1 = getConnection("memoryUsage"); + int len = getSize(1, 2000); + printTimeMemory("start", 0); + long time = System.nanoTime(); + for (int i = 0; i < len; i++) { + Connection conn2 = getConnection("memoryUsage"); + conn2.close(); + if (i % 10000 == 0) { + printTimeMemory("connect", + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - time)); + } + } + printTimeMemory("connect", + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - time)); + conn1.close(); + } + + private void insertUpdateSelectDelete() throws SQLException { + Statement stat = conn.createStatement(); + long time; + int len = getSize(1, 2000); + + // insert + time = System.nanoTime(); + stat.execute("DROP TABLE IF EXISTS TEST"); + trace("drop=" + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - time)); + stat.execute("CREATE CACHED TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST VALUES(?, 'Hello World')"); + printTimeMemory("start", 0); + time = System.nanoTime(); + for (int i = 0; i < len; i++) { + prep.setInt(1, i); + prep.execute(); + if (i % 50000 == 0) { + trace(" " + (100 * i / len) + "%"); + } + } + printTimeMemory("insert", TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - time)); + + // update + time = System.nanoTime(); + prep = conn.prepareStatement( + "UPDATE TEST SET NAME='Hallo Welt' || ID WHERE ID = ?"); + for (int i = 0; i < len; i++) { + prep.setInt(1, i); + prep.execute(); + if (i % 50000 == 0) { + trace(" " + (100 * i / len) + "%"); + } + } + printTimeMemory("update", TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - time)); + + // select + time = System.nanoTime(); + prep = conn.prepareStatement("SELECT * FROM TEST WHERE ID = ?"); + for (int i = 0; i < len; i++) { + prep.setInt(1, i); + ResultSet rs = prep.executeQuery(); + rs.next(); + assertFalse(rs.next()); + if (i % 50000 == 0) { + trace(" " + (100 * i / len) + "%"); + } + } + printTimeMemory("select", + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - time)); + + // select randomized + Random random = new Random(1); + time = System.nanoTime(); + prep = conn.prepareStatement("SELECT * FROM TEST WHERE ID = ?"); + for (int i = 0; i < len; i++) { + prep.setInt(1, random.nextInt(len)); + ResultSet rs = prep.executeQuery(); + rs.next(); + assertFalse(rs.next()); + if (i % 50000 == 0) { + trace(" " + (100 * i / len) + "%"); + } + } + printTimeMemory("select randomized", + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - time)); + + // delete + time = System.nanoTime(); + prep = conn.prepareStatement("DELETE FROM TEST WHERE ID = ?"); + for (int i = 0; i < len; i++) { + prep.setInt(1, random.nextInt(len)); + prep.executeUpdate(); + if (i % 50000 == 0) { + trace(" " + (100 * i / len) + "%"); + } + } + printTimeMemory("delete", + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - time)); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestMergeUsing.java b/h2/src/test/org/h2/test/db/TestMergeUsing.java new file mode 100644 index 0000000..4e07848 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestMergeUsing.java @@ -0,0 +1,326 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import org.h2.api.Trigger; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Test merge using syntax. + */ +public class TestMergeUsing extends TestDb implements Trigger { + + private static final String GATHER_ORDERED_RESULTS_SQL = "SELECT ID, NAME FROM PARENT ORDER BY ID ASC"; + private static int triggerTestingUpdateCount; + + private String triggerName; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public void test() throws Exception { + // Simple ID,NAME inserts, target table with PK initially empty + testMergeUsing( + "CREATE TABLE PARENT(ID INT, NAME VARCHAR, PRIMARY KEY(ID) );", + "MERGE INTO PARENT AS P USING (SELECT X AS ID, 'Marcy'||X AS NAME " + + "FROM SYSTEM_RANGE(1,2) ) AS S ON (P.ID = S.ID AND 1=1 AND S.ID = P.ID) " + + "WHEN MATCHED THEN " + + "UPDATE SET P.NAME = S.NAME WHERE 2 = 2 WHEN NOT MATCHED THEN INSERT (ID, NAME) VALUES (S.ID, S.NAME)", + GATHER_ORDERED_RESULTS_SQL, + "SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2)", 2); + // Simple NAME updates, target table missing PK + testMergeUsing( + "CREATE TABLE PARENT AS (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2) );", + "MERGE INTO PARENT AS P USING (" + + "SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2) ) AS S " + + "ON (P.ID = S.ID AND 1=1 AND S.ID = P.ID) " + + "WHEN MATCHED THEN UPDATE SET P.NAME = S.NAME||S.ID WHERE 1 = 1 WHEN NOT MATCHED THEN " + + "INSERT (ID, NAME) VALUES (S.ID, S.NAME)", + GATHER_ORDERED_RESULTS_SQL, + "SELECT X AS ID, 'Marcy'||X||X AS NAME FROM SYSTEM_RANGE(1,2)", + 2); + // No NAME updates, WHERE clause is always false, insert clause missing + testMergeUsing( + "CREATE TABLE PARENT AS (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2) );", + "MERGE INTO PARENT AS P USING (" + + "SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2) ) AS S ON (P.ID = S.ID) " + + "WHEN MATCHED THEN UPDATE SET P.NAME = S.NAME||S.ID WHERE 1 = 2", + GATHER_ORDERED_RESULTS_SQL, + "SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2)", 0); + // No NAME updates, no WHERE clause, insert clause missing + testMergeUsing( + "CREATE TABLE PARENT AS (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2) );", + "MERGE INTO PARENT AS P USING (" + + "SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2) ) AS S ON (P.ID = S.ID) " + + "WHEN MATCHED THEN UPDATE SET P.NAME = S.NAME||S.ID", + GATHER_ORDERED_RESULTS_SQL, + "SELECT X AS ID, 'Marcy'||X||X AS NAME FROM SYSTEM_RANGE(1,2)", + 2); + // Two delete updates done, no WHERE clause, insert clause missing + testMergeUsing( + "CREATE TABLE PARENT AS (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2) );", + "MERGE INTO PARENT AS P USING (" + + "SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2) ) AS S ON (P.ID = S.ID) " + + "WHEN MATCHED THEN DELETE", + GATHER_ORDERED_RESULTS_SQL, + "SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2) WHERE 1=0", + 2); + // One insert, one update one delete happens, target table missing PK + testMergeUsing( + "CREATE TABLE PARENT AS (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2) );", + "MERGE INTO PARENT AS P USING (" + + "SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,3) ) AS S ON (P.ID = S.ID) " + + "WHEN MATCHED THEN UPDATE SET P.NAME = S.NAME||S.ID WHERE P.ID = 2 " + + "WHEN MATCHED THEN DELETE WHERE P.ID = 1 WHEN NOT MATCHED THEN " + + "INSERT (ID, NAME) VALUES (S.ID, S.NAME)", + GATHER_ORDERED_RESULTS_SQL, + "SELECT X AS ID, 'Marcy'||X||X AS NAME FROM SYSTEM_RANGE(2,2) UNION ALL " + + "SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(3,3)", + 3); + // One insert, one update one delete happens, target table missing PK + testMergeUsing( + "CREATE TABLE PARENT AS (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2) );" + + "CREATE TABLE SOURCE AS (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,3) );", + "MERGE INTO PARENT AS P USING SOURCE AS S ON (P.ID = S.ID) " + + "WHEN MATCHED THEN UPDATE SET P.NAME = S.NAME||S.ID WHERE P.ID = 2 " + + "WHEN MATCHED THEN DELETE WHERE P.ID = 1 WHEN NOT MATCHED THEN " + + "INSERT (ID, NAME) VALUES (S.ID, S.NAME)", + GATHER_ORDERED_RESULTS_SQL, + "SELECT X AS ID, 'Marcy'||X||X AS NAME FROM SYSTEM_RANGE(2,2) UNION ALL " + + "SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(3,3)", + 3); + // One insert, one update one delete happens, target table missing PK, + // no source alias + testMergeUsing( + "CREATE TABLE PARENT AS (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2) );" + + "CREATE TABLE SOURCE AS (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,3) );", + "MERGE INTO PARENT AS P USING SOURCE ON (P.ID = SOURCE.ID) " + + "WHEN MATCHED THEN UPDATE SET P.NAME = SOURCE.NAME||SOURCE.ID WHERE P.ID = 2 " + + "WHEN MATCHED THEN DELETE WHERE P.ID = 1 " + + "WHEN NOT MATCHED THEN INSERT (ID, NAME) VALUES (SOURCE.ID, SOURCE.NAME)", + GATHER_ORDERED_RESULTS_SQL, + "SELECT X AS ID, 'Marcy'||X||X AS NAME FROM SYSTEM_RANGE(2,2) UNION ALL " + + "SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(3,3)", + 3); + // One insert, one update one delete happens, target table missing PK, + // no source or target alias + testMergeUsing( + "CREATE TABLE PARENT AS (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2) );" + + "CREATE TABLE SOURCE AS (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,3) );", + "MERGE INTO PARENT USING SOURCE ON (PARENT.ID = SOURCE.ID) " + + "WHEN MATCHED THEN UPDATE SET PARENT.NAME = SOURCE.NAME||SOURCE.ID WHERE PARENT.ID = 2 " + + "WHEN MATCHED THEN DELETE WHERE PARENT.ID = 1 " + + "WHEN NOT MATCHED THEN INSERT (ID, NAME) VALUES (SOURCE.ID, SOURCE.NAME)", + GATHER_ORDERED_RESULTS_SQL, + "SELECT X AS ID, 'Marcy'||X||X AS NAME FROM SYSTEM_RANGE(2,2) UNION ALL " + + "SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(3,3)", + 3); + + // Only insert clause, no update or delete clauses + testMergeUsing( + "CREATE TABLE PARENT AS (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,1) );" + + "DELETE FROM PARENT;", + "MERGE INTO PARENT AS P USING (" + + "SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,3) ) AS S ON (P.ID = S.ID) " + + "WHEN NOT MATCHED THEN INSERT (ID, NAME) VALUES (S.ID, S.NAME)", + GATHER_ORDERED_RESULTS_SQL, + "SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,3)", 3); + // no insert, no update, no delete clauses - essentially a no-op + testMergeUsingException( + "CREATE TABLE PARENT AS (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,1) );" + + "DELETE FROM PARENT;", + "MERGE INTO PARENT AS P USING (" + + "SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,3) ) AS S ON (P.ID = S.ID)", + GATHER_ORDERED_RESULTS_SQL, + "SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,3) WHERE X<0", + 0, + "WHEN\""); + // One insert, one update one delete happens, target table missing PK, + // triggers update all NAME fields + triggerTestingUpdateCount = 0; + testMergeUsing( + "CREATE TABLE PARENT AS (SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,2));" + + getCreateTriggerSQL(), + "MERGE INTO PARENT AS P USING " + + "(SELECT X AS ID, 'Marcy'||X AS NAME FROM SYSTEM_RANGE(1,4) ) AS S ON (P.ID = S.ID) " + + "WHEN MATCHED THEN UPDATE SET P.NAME = S.NAME||S.ID WHERE P.ID = 2 " + + "WHEN MATCHED THEN DELETE WHERE P.ID = 1 " + + "WHEN NOT MATCHED THEN INSERT (ID, NAME) VALUES (S.ID, S.NAME)", + GATHER_ORDERED_RESULTS_SQL, + "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(); + } + + /** + * Run a test case of the merge using syntax + * + * @param setupSQL - one or more SQL statements to setup the case + * @param statementUnderTest - the merge statement being tested + * @param gatherResultsSQL - a select which gathers the results of the merge + * from the target table + * @param expectedResultsSQL - a select which returns the expected results + * in the target table + * @param expectedRowUpdateCount - how many updates should be expected from + * the merge using + */ + private void testMergeUsing(String setupSQL, String statementUnderTest, + String gatherResultsSQL, String expectedResultsSQL, + int expectedRowUpdateCount) throws Exception { + deleteDb("mergeUsingQueries"); + + try (Connection conn = getConnection("mergeUsingQueries;MODE=Oracle")) { + Statement stat = conn.createStatement(); + stat.execute(setupSQL); + + PreparedStatement prep = conn.prepareStatement(statementUnderTest); + int rowCountUpdate = prep.executeUpdate(); + + // compare actual results from SQL result set with expected results + // - by diffing (aka set MINUS operation) + ResultSet rs = stat.executeQuery("( " + gatherResultsSQL + " ) MINUS ( " + + expectedResultsSQL + " )"); + + int rowCount = 0; + StringBuilder diffBuffer = new StringBuilder(""); + while (rs.next()) { + rowCount++; + diffBuffer.append("|"); + for (int i = 1; i <= rs.getMetaData().getColumnCount(); i++) { + diffBuffer.append(rs.getObject(i)); + diffBuffer.append("|\n"); + } + } + assertEquals("Differences between expected and actual output found:" + + diffBuffer, 0, rowCount); + assertEquals("Expected update counts differ", + expectedRowUpdateCount, rowCountUpdate); + } finally { + deleteDb("mergeUsingQueries"); + } + } + + /** + * Run a test case of the merge using syntax + * + * @param setupSQL - one or more SQL statements to setup the case + * @param statementUnderTest - the merge statement being tested + * @param gatherResultsSQL - a select which gathers the results of the merge + * from the target table + * @param expectedResultsSQL - a select which returns the expected results + * in the target table + * @param expectedRowUpdateCount - how many updates should be expected from + * the merge using + * @param exceptionMessage - the exception message expected + */ + private void testMergeUsingException(String setupSQL, + String statementUnderTest, String gatherResultsSQL, + String expectedResultsSQL, int expectedRowUpdateCount, + String exceptionMessage) throws Exception { + try { + testMergeUsing(setupSQL, statementUnderTest, gatherResultsSQL, + expectedResultsSQL, expectedRowUpdateCount); + } catch (RuntimeException | SQLException e) { + if (!e.getMessage().contains(exceptionMessage)) { + e.printStackTrace(); + } + assertContains(e.getMessage(), exceptionMessage); + return; + } + fail("Failed to see exception with message:" + exceptionMessage); + } + + @Override + public void fire(Connection conn, Object[] oldRow, Object[] newRow) + throws SQLException { + + if (conn == null) { + throw new AssertionError("connection is null"); + } + if (triggerName.startsWith("INS_BEFORE")) { + newRow[1] = newRow[1] + "-inserted" + (++triggerTestingUpdateCount); + } else if (triggerName.startsWith("UPD_BEFORE")) { + newRow[1] = newRow[1] + "-updated" + (++triggerTestingUpdateCount); + } else if (triggerName.startsWith("DEL_BEFORE")) { + oldRow[1] = oldRow[1] + "-deleted" + (++triggerTestingUpdateCount); + } + } + + @Override + public void init(Connection conn, String schemaName, String trigger, + String tableName, boolean before, int type) { + this.triggerName = trigger; + if (!"PARENT".equals(tableName)) { + throw new AssertionError("supposed to be PARENT"); + } + if ((trigger.endsWith("AFTER") && before) + || (trigger.endsWith("BEFORE") && !before)) { + throw new AssertionError( + "triggerName: " + trigger + " before:" + before); + } + if ((trigger.startsWith("UPD") && type != UPDATE) + || (trigger.startsWith("INS") && type != INSERT) + || (trigger.startsWith("DEL") && type != DELETE)) { + throw new AssertionError( + "triggerName: " + trigger + " type:" + type); + } + } + + private String getCreateTriggerSQL() { + StringBuilder buf = new StringBuilder(); + buf.append("CREATE TRIGGER INS_BEFORE " + "BEFORE INSERT ON PARENT " + + "FOR EACH ROW NOWAIT CALL \"" + getClass().getName() + "\";"); + buf.append("CREATE TRIGGER UPD_BEFORE " + "BEFORE UPDATE ON PARENT " + + "FOR EACH ROW NOWAIT CALL \"" + getClass().getName() + "\";"); + buf.append("CREATE TRIGGER DEL_BEFORE " + "BEFORE DELETE ON PARENT " + + "FOR EACH ROW NOWAIT CALL \"" + getClass().getName() + "\";"); + 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/TestMultiConn.java b/h2/src/test/org/h2/test/db/TestMultiConn.java new file mode 100644 index 0000000..891042c --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestMultiConn.java @@ -0,0 +1,217 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import org.h2.api.DatabaseEventListener; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.Task; + +/** + * Multi-connection tests. + */ +public class TestMultiConn extends TestDb { + + /** + * How long to wait in milliseconds. + */ + static int wait; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testConcurrentShutdownQuery(); + testCommitRollback(); + testConcurrentOpen(); + testThreeThreads(); + deleteDb("multiConn"); + } + + private void testConcurrentShutdownQuery() throws Exception { + Connection conn1 = getConnection("multiConn"); + Connection conn2 = getConnection("multiConn"); + final Statement stat1 = conn1.createStatement(); + stat1.execute("CREATE ALIAS SLEEP FOR 'java.lang.Thread.sleep(long)'"); + final Statement stat2 = conn2.createStatement(); + stat1.execute("SET THROTTLE 100"); + Task t = new Task() { + @Override + public void call() throws Exception { + stat2.executeQuery("CALL SLEEP(100)"); + Thread.sleep(10); + stat2.executeQuery("CALL SLEEP(100)"); + } + }; + t.execute(); + Thread.sleep(50); + stat1.execute("SHUTDOWN"); + conn1.close(); + try { + conn2.close(); + } catch (SQLException e) { + // ignore + } + try { + t.get(); + } catch (Exception e) { + // ignore + } + } + + private void testThreeThreads() throws Exception { + deleteDb("multiConn"); + Connection conn1 = getConnection("multiConn"); + Connection conn2 = getConnection("multiConn"); + Connection conn3 = getConnection("multiConn"); + conn1.setAutoCommit(false); + conn2.setAutoCommit(false); + conn3.setAutoCommit(false); + Statement s1 = conn1.createStatement(); + Statement s2 = conn2.createStatement(); + Statement s3 = conn3.createStatement(); + s1.execute("CREATE TABLE TEST1(ID INT)"); + s2.execute("CREATE TABLE TEST2(ID INT)"); + s3.execute("CREATE TABLE TEST3(ID INT)"); + s1.execute("INSERT INTO TEST1 VALUES(1)"); + s2.execute("INSERT INTO TEST2 VALUES(2)"); + s3.execute("INSERT INTO TEST3 VALUES(3)"); + s1.execute("SET LOCK_TIMEOUT 1000"); + s2.execute("SET LOCK_TIMEOUT 1000"); + s3.execute("SET LOCK_TIMEOUT 1000"); + Thread t1 = new Thread(() -> { + try { + s3.execute("INSERT INTO TEST2 VALUES(4)"); + conn3.commit(); + } catch (SQLException e) { + TestBase.logError("insert", e); + } + }); + t1.start(); + Thread.sleep(20); + Thread t2 = new Thread(() -> { + try { + s2.execute("INSERT INTO TEST1 VALUES(5)"); + conn2.commit(); + } catch (SQLException e) { + TestBase.logError("insert", e); + } + }); + t2.start(); + Thread.sleep(20); + conn1.commit(); + t2.join(1000); + t1.join(1000); + ResultSet rs = s1.executeQuery("SELECT * FROM TEST1 ORDER BY ID"); + rs.next(); + assertEquals(1, rs.getInt(1)); + rs.next(); + assertEquals(5, rs.getInt(1)); + assertFalse(rs.next()); + conn1.close(); + conn2.close(); + conn3.close(); + } + + private void testConcurrentOpen() throws Exception { + if (config.memory || config.googleAppEngine) { + return; + } + deleteDb("multiConn"); + Connection conn = getConnection("multiConn"); + conn.createStatement().execute( + "CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + conn.createStatement().execute( + "INSERT INTO TEST VALUES(0, 'Hello'), (1, 'World')"); + conn.createStatement().execute("SHUTDOWN"); + conn.close(); + final String listener = MyDatabaseEventListener.class.getName(); + Runnable r = () -> { + try { + Connection c1 = getConnection("multiConn;DATABASE_EVENT_LISTENER='" + listener + + "';file_lock=socket"); + c1.close(); + } catch (Exception e) { + TestBase.logError("connect", e); + } + }; + Thread thread = new Thread(r); + thread.start(); + Thread.sleep(10); + Connection c2 = getConnection("multiConn;file_lock=socket"); + c2.close(); + thread.join(); + } + + private void testCommitRollback() throws SQLException { + deleteDb("multiConn"); + Connection c1 = getConnection("multiConn"); + Connection c2 = getConnection("multiConn"); + c1.setAutoCommit(false); + c2.setAutoCommit(false); + Statement s1 = c1.createStatement(); + s1.execute("DROP TABLE IF EXISTS MULTI_A"); + s1.execute("CREATE TABLE MULTI_A(ID INT, NAME VARCHAR(255))"); + s1.execute("INSERT INTO MULTI_A VALUES(0, '0-insert-A')"); + Statement s2 = c2.createStatement(); + s1.execute("DROP TABLE IF EXISTS MULTI_B"); + s1.execute("CREATE TABLE MULTI_B(ID INT, NAME VARCHAR(255))"); + s2.execute("INSERT INTO MULTI_B VALUES(0, '1-insert-B')"); + c1.commit(); + c2.rollback(); + s1.execute("INSERT INTO MULTI_A VALUES(1, '0-insert-C')"); + s2.execute("INSERT INTO MULTI_B VALUES(1, '1-insert-D')"); + c1.rollback(); + c2.commit(); + c1.close(); + c2.close(); + + if (!config.memory) { + Connection conn = getConnection("multiConn"); + ResultSet rs; + rs = conn.createStatement().executeQuery("SELECT * FROM MULTI_A ORDER BY ID"); + rs.next(); + assertEquals("0-insert-A", rs.getString("NAME")); + assertFalse(rs.next()); + rs = conn.createStatement().executeQuery("SELECT * FROM MULTI_B ORDER BY ID"); + rs.next(); + assertEquals("1-insert-D", rs.getString("NAME")); + assertFalse(rs.next()); + conn.close(); + } + + } + + /** + * A database event listener used in this test. + */ + public static final class MyDatabaseEventListener implements DatabaseEventListener { + + @Override + public void setProgress(int state, String name, long x, long max) { + if (wait > 0) { + try { + Thread.sleep(wait); + } catch (InterruptedException e) { + TestBase.logError("sleep", e); + } + } + } + + } + +} diff --git a/h2/src/test/org/h2/test/db/TestMultiDimension.java b/h2/src/test/org/h2/test/db/TestMultiDimension.java new file mode 100644 index 0000000..afd99bd --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestMultiDimension.java @@ -0,0 +1,268 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.MultiDimension; + +/** + * Tests the multi-dimension index tool. + */ +public class TestMultiDimension extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); + test.config.traceTest = true; + test.testFromMain(); + } + + @Override + public void test() throws SQLException { + testHelperMethods(); + testPerformance2d(); + testPerformance3d(); + } + + private void testHelperMethods() { + MultiDimension m = MultiDimension.getInstance(); + assertEquals(Integer.MAX_VALUE, m.getMaxValue(2)); + assertEquals(0, m.normalize(2, 0, 0, 100)); + assertEquals(Integer.MAX_VALUE / 2, m.normalize(2, 50, 0, 100)); + assertEquals(Integer.MAX_VALUE, m.normalize(2, 100, 0, 100)); + assertEquals(Integer.MAX_VALUE / 10, m.normalize(2, 0.1, 0, 1)); + assertEquals(0, m.normalize(2, 1, 1, 1)); + assertEquals(0, m.normalize(2, 0, 0, 0)); + assertEquals(3, m.interleave(1, 1)); + assertEquals(3, m.interleave(new int[]{1, 1})); + assertEquals(5, m.interleave(3, 0)); + assertEquals(5, m.interleave(new int[]{3, 0})); + assertEquals(10, m.interleave(0, 3)); + assertEquals(10, m.interleave(new int[] { 0, 3 })); + long v = Integer.MAX_VALUE | ((long) Integer.MAX_VALUE << 31L); + assertEquals(v, m.interleave(Integer.MAX_VALUE, Integer.MAX_VALUE)); + assertEquals(v, m.interleave(new int[] { + Integer.MAX_VALUE, Integer.MAX_VALUE })); + Random random = new Random(1); + for (int i = 0; i < 1000; i++) { + int x = random.nextInt(Integer.MAX_VALUE), y = + random.nextInt(Integer.MAX_VALUE); + v = m.interleave(new int[] {x, y}); + long v2 = m.interleave(x, y); + assertEquals(v, v2); + int x1 = m.deinterleave(2, v, 0); + int y1 = m.deinterleave(2, v, 1); + assertEquals(x, x1); + assertEquals(y, y1); + } + for (int i = 0; i < 1000; i++) { + int x = random.nextInt(1000), y = random.nextInt(1000), + z = random.nextInt(1000); + MultiDimension tool = MultiDimension.getInstance(); + long xyz = tool.interleave(new int[] { x, y, z }); + assertEquals(x, tool.deinterleave(3, xyz, 0)); + assertEquals(y, tool.deinterleave(3, xyz, 1)); + assertEquals(z, tool.deinterleave(3, xyz, 2)); + } + assertThrows(IllegalArgumentException.class, () -> m.getMaxValue(1)); + assertThrows(IllegalArgumentException.class, () -> m.getMaxValue(33)); + assertThrows(IllegalArgumentException.class, () -> m.normalize(2, 10, 11, 12)); + assertThrows(IllegalArgumentException.class, () -> m.normalize(2, 5, 10, 0)); + assertThrows(IllegalArgumentException.class, () -> m.normalize(2, 10, 0, 9)); + assertThrows(IllegalArgumentException.class, () -> m.interleave(-1, 5)); + assertThrows(IllegalArgumentException.class, () -> m.interleave(5, -1)); + assertThrows(IllegalArgumentException.class, + () -> m.interleave(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE)); + } + + private void testPerformance2d() throws SQLException { + deleteDb("multiDimension"); + Connection conn; + conn = getConnection("multiDimension"); + Statement stat = conn.createStatement(); + stat.execute("CREATE ALIAS MAP FOR '" + getClass().getName() + ".interleave'"); + stat.execute("CREATE TABLE TEST(X INT NOT NULL, Y INT NOT NULL, " + + "XY BIGINT AS MAP(X, Y), DATA VARCHAR)"); + stat.execute("CREATE INDEX IDX_X ON TEST(X, Y)"); + stat.execute("CREATE INDEX IDX_XY ON TEST(XY)"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST(X, Y, DATA) VALUES(?, ?, ?)"); + // the MultiDimension tool is faster for 4225 (65^2) points + // the more the bigger the difference + int max = getSize(30, 65); + long time = System.nanoTime(); + for (int x = 0; x < max; x++) { + for (int y = 0; y < max; y++) { + long t2 = System.nanoTime(); + if (t2 - time > TimeUnit.SECONDS.toNanos(1)) { + int percent = (int) (100.0 * ((double) x * max + y) / + ((double) max * max)); + trace(percent + "%"); + time = t2; + } + prep.setInt(1, x); + prep.setInt(2, y); + prep.setString(3, "Test data"); + prep.execute(); + } + } + stat.execute("ANALYZE SAMPLE_SIZE 10000"); + PreparedStatement prepRegular = conn.prepareStatement( + "SELECT * FROM TEST WHERE X BETWEEN ? AND ? " + + "AND Y BETWEEN ? AND ? ORDER BY X, Y"); + MultiDimension multi = MultiDimension.getInstance(); + String sql = multi.generatePreparedQuery("TEST", "XY", + new String[] { "X", "Y" }); + sql += " ORDER BY X, Y"; + PreparedStatement prepMulti = conn.prepareStatement(sql); + long timeMulti = 0, timeRegular = 0; + int timeMax = getSize(500, 2000); + Random rand = new Random(1); + while (timeMulti < timeMax) { + int size = rand.nextInt(max / 10); + int minX = rand.nextInt(max - size); + int minY = rand.nextInt(max - size); + int maxX = minX + size, maxY = minY + size; + time = System.nanoTime(); + ResultSet rs1 = multi.getResult(prepMulti, + new int[] { minX, minY }, new int[] { maxX, maxY }); + timeMulti += System.nanoTime() - time; + time = System.nanoTime(); + prepRegular.setInt(1, minX); + prepRegular.setInt(2, maxX); + prepRegular.setInt(3, minY); + prepRegular.setInt(4, maxY); + ResultSet rs2 = prepRegular.executeQuery(); + timeRegular += System.nanoTime() - time; + while (rs1.next()) { + assertTrue(rs2.next()); + assertEquals(rs1.getInt(1), rs2.getInt(1)); + assertEquals(rs1.getInt(2), rs2.getInt(2)); + } + assertFalse(rs2.next()); + } + conn.close(); + deleteDb("multiDimension"); + trace("2d: regular: " + TimeUnit.NANOSECONDS.toMillis(timeRegular) + + " MultiDimension: " + TimeUnit.NANOSECONDS.toMillis(timeMulti)); + } + + private void testPerformance3d() throws SQLException { + deleteDb("multiDimension"); + Connection conn; + conn = getConnection("multiDimension"); + Statement stat = conn.createStatement(); + stat.execute("CREATE ALIAS MAP FOR '" + getClass().getName() + ".interleave'"); + stat.execute("CREATE TABLE TEST(X INT NOT NULL, " + + "Y INT NOT NULL, Z INT NOT NULL, " + + "XYZ BIGINT AS MAP(X, Y, Z), DATA VARCHAR)"); + stat.execute("CREATE INDEX IDX_X ON TEST(X, Y, Z)"); + stat.execute("CREATE INDEX IDX_XYZ ON TEST(XYZ)"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST(X, Y, Z, DATA) VALUES(?, ?, ?, ?)"); + // the MultiDimension tool is faster for 8000 (20^3) points + // the more the bigger the difference + int max = getSize(10, 20); + long time = System.nanoTime(); + for (int x = 0; x < max; x++) { + for (int y = 0; y < max; y++) { + for (int z = 0; z < max; z++) { + long t2 = System.nanoTime(); + if (t2 - time > TimeUnit.SECONDS.toNanos(1)) { + int percent = (int) (100.0 * ((double) x * max + y) / + ((double) max * max)); + trace(percent + "%"); + time = t2; + } + prep.setInt(1, x); + prep.setInt(2, y); + prep.setInt(3, z); + prep.setString(4, "Test data"); + prep.execute(); + } + } + } + stat.execute("ANALYZE SAMPLE_SIZE 10000"); + PreparedStatement prepRegular = conn.prepareStatement( + "SELECT * FROM TEST WHERE X BETWEEN ? AND ? " + + "AND Y BETWEEN ? AND ? AND Z BETWEEN ? AND ? ORDER BY X, Y, Z"); + MultiDimension multi = MultiDimension.getInstance(); + String sql = multi.generatePreparedQuery("TEST", "XYZ", new String[] { + "X", "Y", "Z" }); + sql += " ORDER BY X, Y, Z"; + PreparedStatement prepMulti = conn.prepareStatement(sql); + long timeMulti = 0, timeRegular = 0; + int timeMax = getSize(500, 2000); + Random rand = new Random(1); + while (timeMulti < timeMax) { + int size = rand.nextInt(max / 10); + int minX = rand.nextInt(max - size); + int minY = rand.nextInt(max - size); + int minZ = rand.nextInt(max - size); + int maxX = minX + size, maxY = minY + size, maxZ = minZ + size; + time = System.nanoTime(); + ResultSet rs1 = multi.getResult(prepMulti, new int[] { minX, minY, + minZ }, new int[] { maxX, maxY, maxZ }); + timeMulti += System.nanoTime() - time; + time = System.nanoTime(); + prepRegular.setInt(1, minX); + prepRegular.setInt(2, maxX); + prepRegular.setInt(3, minY); + prepRegular.setInt(4, maxY); + prepRegular.setInt(5, minZ); + prepRegular.setInt(6, maxZ); + ResultSet rs2 = prepRegular.executeQuery(); + timeRegular += System.nanoTime() - time; + while (rs1.next()) { + assertTrue(rs2.next()); + assertEquals(rs1.getInt(1), rs2.getInt(1)); + assertEquals(rs1.getInt(2), rs2.getInt(2)); + } + assertFalse(rs2.next()); + } + conn.close(); + deleteDb("multiDimension"); + trace("3d: regular: " + TimeUnit.NANOSECONDS.toMillis(timeRegular) + + " MultiDimension: " + TimeUnit.NANOSECONDS.toMillis(timeMulti)); + } + + /** + * This method is called via reflection from the database. + * + * @param x the x value + * @param y the y value + * @return the bit-interleaved value + */ + public static long interleave(int x, int y) { + return MultiDimension.getInstance().interleave(x, y); + } + + /** + * This method is called via reflection from the database. + * + * @param x the x value + * @param y the y value + * @param z the z value + * @return the bit-interleaved value + */ + public static long interleave(int x, int y, int z) { + return MultiDimension.getInstance().interleave(new int[] { x, y, z }); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestMultiThread.java b/h2/src/test/org/h2/test/db/TestMultiThread.java new file mode 100644 index 0000000..ea6f060 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestMultiThread.java @@ -0,0 +1,504 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.io.StringReader; +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Random; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import org.h2.api.ErrorCode; +import org.h2.test.TestAll; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.IOUtils; +import org.h2.util.Task; + +/** + * Multi-threaded tests. + */ +public class TestMultiThread extends TestDb implements Runnable { + + private boolean stop; + private TestMultiThread parent; + private Random random; + + public TestMultiThread() { + // nothing to do + } + + private TestMultiThread(TestAll config, TestMultiThread parent) { + this.config = config; + this.parent = parent; + random = new Random(); + } + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testConcurrentSchemaChange(); + testConcurrentLobAdd(); + testConcurrentAlter(); + testConcurrentInsertUpdateSelect(); + testViews(); + testConcurrentInsert(); + testConcurrentUpdate(); + testConcurrentUpdate2(); + testCheckConstraint(); + } + + private void testConcurrentSchemaChange() throws Exception { + String db = getTestName(); + deleteDb(db); + final String url = getURL(db + ";LOCK_TIMEOUT=10000", true); + try (Connection conn = getConnection(url)) { + Task[] tasks = new Task[2]; + for (int i = 0; i < tasks.length; i++) { + final int x = i; + Task t = new Task() { + @Override + public void call() throws Exception { + try (Connection c2 = getConnection(url)) { + Statement stat = c2.createStatement(); + for (int i = 0; !stop; i++) { + stat.execute("create table test" + x + "_" + i); + c2.getMetaData().getTables(null, null, null, null); + stat.execute("drop table test" + x + "_" + i); + } + } + } + }; + tasks[i] = t; + t.execute(); + } + Thread.sleep(1000); + for (Task t : tasks) { + t.get(); + } + } + } + + private void testConcurrentLobAdd() throws Exception { + String db = getTestName(); + deleteDb(db); + final String url = getURL(db, true); + try (Connection conn = getConnection(url)) { + Statement stat = conn.createStatement(); + stat.execute("create table test(id identity, data clob)"); + Task[] tasks = new Task[2]; + for (int i = 0; i < tasks.length; i++) { + Task t = new Task() { + @Override + public void call() throws Exception { + try (Connection c2 = getConnection(url)) { + PreparedStatement p2 = c2 + .prepareStatement("insert into test(data) values(?)"); + while (!stop) { + p2.setCharacterStream(1, new StringReader(new String( + new char[10 * 1024]))); + p2.execute(); + } + } + } + }; + tasks[i] = t; + t.execute(); + } + Thread.sleep(500); + for (Task t : tasks) { + t.get(); + } + } + } + + private void testConcurrentAlter() throws Exception { + deleteDb(getTestName()); + try (final Connection conn = getConnection(getTestName())) { + Statement stat = conn.createStatement(); + Task t = new Task() { + @Override + public void call() throws Exception { + while (!stop) { + conn.prepareStatement("select * from test"); + } + } + }; + stat.execute("create table test(id int)"); + t.execute(); + for (int i = 0; i < 200; i++) { + stat.execute("alter table test add column x int"); + stat.execute("alter table test drop column x"); + } + t.get(); + } + } + + private void testConcurrentInsertUpdateSelect() throws Exception { + try (Connection conn = getConnection()) { + Statement stmt = conn.createStatement(); + stmt.execute("CREATE TABLE TEST(ID IDENTITY, NAME VARCHAR)"); + int len = getSize(10, 200); + Thread[] threads = new Thread[len]; + for (int i = 0; i < len; i++) { + threads[i] = new Thread(new TestMultiThread(config, this)); + } + for (int i = 0; i < len; i++) { + threads[i].start(); + } + int sleep = getSize(400, 10000); + Thread.sleep(sleep); + this.stop = true; + for (int i = 0; i < len; i++) { + threads[i].join(); + } + ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM TEST"); + rs.next(); + trace("max id=" + rs.getInt(1)); + } + } + + private Connection getConnection() throws SQLException { + return getConnection("jdbc:h2:mem:" + getTestName()); + } + + @Override + public void run() { + try (Connection conn = getConnection()) { + Statement stmt = conn.createStatement(); + while (!parent.stop) { + stmt.execute("SELECT COUNT(*) FROM TEST"); + stmt.execute("INSERT INTO TEST(NAME) VALUES('Hi')"); + PreparedStatement prep = conn.prepareStatement( + "UPDATE TEST SET NAME='Hello' WHERE ID=?"); + prep.setInt(1, random.nextInt(10000)); + prep.execute(); + prep = conn.prepareStatement("SELECT * FROM TEST WHERE ID=?"); + prep.setInt(1, random.nextInt(10000)); + ResultSet rs = prep.executeQuery(); + while (rs.next()) { + rs.getString("NAME"); + } + } + } catch (Exception e) { + logError("multi", e); + } + } + + private void testViews() throws Exception { + // is not supported + deleteDb("lockMode"); + String url = getURL("lockMode", true); + + // create some common tables and views + ExecutorService executor = Executors.newFixedThreadPool(8); + Connection conn = getConnection(url); + try { + Statement stat = conn.createStatement(); + stat.execute( + "CREATE TABLE INVOICE(INVOICE_ID INT PRIMARY KEY, AMOUNT DECIMAL)"); + stat.execute("CREATE VIEW INVOICE_VIEW as SELECT * FROM INVOICE"); + + stat.execute( + "CREATE TABLE INVOICE_DETAIL(DETAIL_ID INT PRIMARY KEY, " + + "INVOICE_ID INT, DESCRIPTION VARCHAR)"); + stat.execute( + "CREATE VIEW INVOICE_DETAIL_VIEW as SELECT * FROM INVOICE_DETAIL"); + + stat.close(); + + // create views that reference the common views in different threads + ArrayList> jobs = new ArrayList<>(); + for (int i = 0; i < 1000; i++) { + final int j = i; + jobs.add(executor.submit(() -> { + try (Connection conn2 = getConnection(url)) { + Statement stat2 = conn2.createStatement(); + + stat2.execute("CREATE VIEW INVOICE_VIEW" + j + + " as SELECT * FROM INVOICE_VIEW"); + + // the following query intermittently results in a + // NullPointerException + stat2.execute("CREATE VIEW INVOICE_DETAIL_VIEW" + j + + " as SELECT DTL.* FROM INVOICE_VIEW" + j + + " INV JOIN INVOICE_DETAIL_VIEW DTL " + + "ON INV.INVOICE_ID = DTL.INVOICE_ID" + + " WHERE DESCRIPTION='TEST'"); + + ResultSet rs = stat2 + .executeQuery("SELECT * FROM INVOICE_VIEW" + j); + rs.next(); + rs.close(); + + rs = stat2.executeQuery( + "SELECT * FROM INVOICE_DETAIL_VIEW" + j); + rs.next(); + rs.close(); + + stat2.close(); + } + return null; + })); + } + // check for exceptions + for (Future job : jobs) { + try { + job.get(); + } catch (ExecutionException ex) { + // ignore timeout exceptions, happens periodically when the + // machine is really busy and it's not the thing we are + // trying to test + if (!(ex.getCause() instanceof SQLException) + || ((SQLException) ex.getCause()).getErrorCode() != ErrorCode.LOCK_TIMEOUT_1) { + throw ex; + } + } + } + } finally { + IOUtils.closeSilently(conn); + executor.shutdown(); + executor.awaitTermination(20, TimeUnit.SECONDS); + } + + deleteDb("lockMode"); + } + + private void testConcurrentInsert() throws Exception { + deleteDb("lockMode"); + + final String url = getURL("lockMode;LOCK_TIMEOUT=10000", true); + int threadCount = 25; + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + Connection conn = getConnection(url); + try { + conn.createStatement().execute( + "CREATE TABLE IF NOT EXISTS TRAN (ID NUMBER(18,0) not null PRIMARY KEY)"); + + final ArrayList> callables = new ArrayList<>(); + for (int i = 0; i < threadCount; i++) { + final long initialTransactionId = i * 1000000L; + callables.add(() -> { + try (Connection taskConn = getConnection(url)) { + taskConn.setAutoCommit(false); + PreparedStatement insertTranStmt = taskConn + .prepareStatement("INSERT INTO tran (id) VALUES(?)"); + // to guarantee uniqueness + long tranId = initialTransactionId; + for (int j = 0; j < 1000; j++) { + insertTranStmt.setLong(1, tranId++); + insertTranStmt.execute(); + taskConn.commit(); + } + } + return null; + }); + } + + final ArrayList> jobs = new ArrayList<>(); + for (int i = 0; i < threadCount; i++) { + jobs.add(executor.submit(callables.get(i))); + } + // check for exceptions + for (Future job : jobs) { + job.get(5, TimeUnit.MINUTES); + } + } finally { + IOUtils.closeSilently(conn); + executor.shutdown(); + executor.awaitTermination(20, TimeUnit.SECONDS); + } + + deleteDb("lockMode"); + } + + private void testConcurrentUpdate() throws Exception { + deleteDb("lockMode"); + + final int objectCount = 10000; + final String url = getURL("lockMode;LOCK_TIMEOUT=10000", true); + int threadCount = 25; + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + Connection conn = getConnection(url); + try { + conn.createStatement().execute( + "CREATE TABLE IF NOT EXISTS ACCOUNT" + + "(ID NUMBER(18,0) not null PRIMARY KEY, BALANCE NUMBER null)"); + final PreparedStatement mergeAcctStmt = conn + .prepareStatement("MERGE INTO Account(id, balance) key (id) VALUES (?, ?)"); + for (int i = 0; i < objectCount; i++) { + mergeAcctStmt.setLong(1, i); + mergeAcctStmt.setBigDecimal(2, BigDecimal.ZERO); + mergeAcctStmt.execute(); + } + + final ArrayList> callables = new ArrayList<>(); + for (int i = 0; i < threadCount; i++) { + callables.add(() -> { + try (Connection taskConn = getConnection(url)) { + taskConn.setAutoCommit(false); + final PreparedStatement updateAcctStmt = taskConn + .prepareStatement("UPDATE account SET balance = ? WHERE id = ?"); + for (int j = 0; j < 1000; j++) { + updateAcctStmt.setDouble(1, Math.random()); + updateAcctStmt.setLong(2, (int) (Math.random() * objectCount)); + updateAcctStmt.execute(); + taskConn.commit(); + } + } + return null; + }); + } + + final ArrayList> jobs = new ArrayList<>(); + for (int i = 0; i < threadCount; i++) { + jobs.add(executor.submit(callables.get(i))); + } + // check for exceptions + for (Future job : jobs) { + job.get(5, TimeUnit.MINUTES); + } + } finally { + IOUtils.closeSilently(conn); + executor.shutdown(); + executor.awaitTermination(20, TimeUnit.SECONDS); + } + + deleteDb("lockMode"); + } + + private final class ConcurrentUpdate2 extends Thread { + private final String column; + + Throwable exception; + + ConcurrentUpdate2(String column) { + this.column = column; + } + + @Override + public void run() { + try (Connection c = getConnection("concurrentUpdate2;LOCK_TIMEOUT=10000")) { + PreparedStatement ps = c.prepareStatement("UPDATE TEST SET V = ? WHERE " + column + " = ?"); + for (int test = 0; test < 1000; test++) { + for (int i = 0; i < 16; i++) { + ps.setInt(1, test); + ps.setInt(2, i); + assertEquals(16, ps.executeUpdate()); + } + } + } catch (Throwable e) { + exception = e; + } + } + } + + private void testConcurrentUpdate2() throws Exception { + deleteDb("concurrentUpdate2"); + try (Connection c = getConnection("concurrentUpdate2")) { + Statement s = c.createStatement(); + s.execute("CREATE TABLE TEST(A INT, B INT, V INT, PRIMARY KEY(A, B))"); + PreparedStatement ps = c.prepareStatement("INSERT INTO TEST VALUES (?, ?, ?)"); + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + ps.setInt(1, i); + ps.setInt(2, j); + ps.setInt(3, 0); + ps.executeUpdate(); + } + } + ConcurrentUpdate2 a = new ConcurrentUpdate2("A"); + ConcurrentUpdate2 b = new ConcurrentUpdate2("B"); + a.start(); + b.start(); + a.join(); + b.join(); + Throwable e = a.exception; + if (e == null) { + e = b.exception; + } + if (e != null) { + if (e instanceof Exception) { + throw (Exception) e; + } + throw (Error) e; + } + } finally { + deleteDb("concurrentUpdate2"); + } + } + + private void testCheckConstraint() throws Exception { + deleteDb("checkConstraint"); + try (Connection c = getConnection("checkConstraint")) { + Statement s = c.createStatement(); + s.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, A INT, B INT)"); + PreparedStatement ps = c.prepareStatement("INSERT INTO TEST VALUES (?, ?, ?)"); + s.execute("ALTER TABLE TEST ADD CONSTRAINT CHECK_A_B CHECK A = B"); + final int numRows = 10; + for (int i = 0; i < numRows; i++) { + ps.setInt(1, i); + ps.setInt(2, 0); + ps.setInt(3, 0); + ps.executeUpdate(); + } + int numThreads = 4; + Thread[] threads = new Thread[numThreads]; + final AtomicBoolean error = new AtomicBoolean(); + for (int i = 0; i < numThreads; i++) { + threads[i] = new Thread() { + @Override + public void run() { + try (Connection c = getConnection("checkConstraint")) { + PreparedStatement ps = c.prepareStatement("UPDATE TEST SET A = ?, B = ? WHERE ID = ?"); + Random r = new Random(); + for (int i = 0; i < 1_000; i++) { + int v = r.nextInt(1_000); + ps.setInt(1, v); + ps.setInt(2, v); + ps.setInt(3, r.nextInt(numRows)); + ps.executeUpdate(); + } + } catch (SQLException e) { + error.set(true); + synchronized (TestMultiThread.this) { + logError("Error in CHECK constraint", e); + } + } + } + }; + } + for (int i = 0; i < numThreads; i++) { + threads[i].start(); + } + for (int i = 0; i < numThreads; i++) { + threads[i].join(); + } + assertFalse(error.get()); + } finally { + deleteDb("checkConstraint"); + } + } + +} diff --git a/h2/src/test/org/h2/test/db/TestMultiThreadedKernel.java b/h2/src/test/org/h2/test/db/TestMultiThreadedKernel.java new file mode 100644 index 0000000..b700b2f --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestMultiThreadedKernel.java @@ -0,0 +1,183 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Random; + +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.JdbcUtils; +import org.h2.util.Task; + +/** + * A multi-threaded test case. + */ +public class TestMultiThreadedKernel extends TestDb { + + /** + * Stop the current thread. + */ + volatile boolean stop; + + /** + * The exception that occurred in the thread. + */ + Exception exception; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb("multiThreadedKernel"); + testConcurrentRead(); + testCache(); + deleteDb("multiThreadedKernel"); + final String url = getURL("multiThreadedKernel;DB_CLOSE_DELAY=-1", true); + final String user = getUser(), password = getPassword(); + int len = 3; + Thread[] threads = new Thread[len]; + for (int i = 0; i < len; i++) { + threads[i] = new Thread(new Runnable() { + @Override + public void run() { + Connection conn = null; + try { + for (int j = 0; j < 100 && !stop; j++) { + conn = DriverManager.getConnection( + url, user, password); + work(conn); + } + } catch (Exception e) { + exception = e; + } finally { + JdbcUtils.closeSilently(conn); + } + } + private void work(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + stat.execute( + "create local temporary table temp(id identity)"); + stat.execute( + "insert into temp values(1)"); + conn.close(); + } + }); + } + for (int i = 0; i < len; i++) { + threads[i].start(); + } + Thread.sleep(1000); + stop = true; + for (int i = 0; i < len; i++) { + threads[i].join(); + } + Connection conn = DriverManager.getConnection(url, user, password); + conn.createStatement().execute("shutdown"); + conn.close(); + if (exception != null) { + throw exception; + } + deleteDb("multiThreadedKernel"); + } + + private void testConcurrentRead() throws Exception { + int size = 2; + final int count = 1000; + ArrayList list = new ArrayList<>(size); + final Connection[] connections = new Connection[count]; + String url = getURL("multiThreadedKernel;CACHE_SIZE=16", true); + for (int i = 0; i < size; i++) { + final Connection conn = DriverManager.getConnection( + url, getUser(), getPassword()); + connections[i] = conn; + if (i == 0) { + Statement stat = conn.createStatement(); + stat.execute("drop table test if exists"); + stat.execute("create table test(id int primary key, name varchar) " + + "as select x, x || space(10) from system_range(1, " + count + ")"); + } + final Random random = new Random(i); + Task t = new Task() { + @Override + public void call() throws Exception { + PreparedStatement prep = conn.prepareStatement( + "select * from test where id = ?"); + while (!stop) { + prep.setInt(1, random.nextInt(count)); + prep.execute(); + } + } + }; + t.execute(); + list.add(t); + } + Thread.sleep(1000); + for (Task t : list) { + t.get(); + } + for (int i = 0; i < size; i++) { + connections[i].close(); + } + } + + private void testCache() throws Exception { + int size = 3; + final int count = 100; + ArrayList list = new ArrayList<>(size); + final Connection[] connections = new Connection[count]; + String url = getURL("multiThreadedKernel;CACHE_SIZE=1", true); + for (int i = 0; i < size; i++) { + final Connection conn = DriverManager.getConnection( + url, getUser(), getPassword()); + connections[i] = conn; + if (i == 0) { + Statement stat = conn.createStatement(); + stat.execute("drop table test if exists"); + stat.execute("create table test(id int primary key, name varchar) " + + "as select x, space(3000) from system_range(1, " + count + ")"); + } + final Random random = new Random(i); + Task t = new Task() { + @Override + public void call() throws SQLException { + PreparedStatement prep = conn.prepareStatement( + "select * from test where id = ?"); + while (!stop) { + prep.setInt(1, random.nextInt(count)); + prep.execute(); + } + } + }; + t.execute(); + list.add(t); + } + Thread.sleep(1000); + for (Task t : list) { + t.get(); + } + for (int i = 0; i < size; i++) { + connections[i].close(); + } + } + + @Override + protected String getURL(String name, boolean admin) { + return super.getURL(name + ";LOCK_TIMEOUT=2000", admin); + } +} diff --git a/h2/src/test/org/h2/test/db/TestOpenClose.java b/h2/src/test/org/h2/test/db/TestOpenClose.java new file mode 100644 index 0000000..3a58f0d --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestOpenClose.java @@ -0,0 +1,272 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.TimeUnit; + +import org.h2.api.DatabaseEventListener; +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.Restore; +import org.h2.util.Task; + +/** + * Tests opening and closing a database. + */ +public class TestOpenClose extends TestDb { + + private int nextId = 10; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testErrorMessageLocked(); + testErrorMessageWrongSplit(); + testCloseDelay(); + testBackup(); + testCase(); + testReconnectFast(); + test1_1(); + deleteDb("openClose"); + } + + private void testErrorMessageLocked() throws Exception { + if (config.memory) { + return; + } + deleteDb("openClose"); + Connection conn; + conn = getConnection("jdbc:h2:" + getBaseDir() + "/openClose;FILE_LOCK=FS"); + assertThrows(ErrorCode.DATABASE_ALREADY_OPEN_1, + () -> getConnection("jdbc:h2:" + getBaseDir() + "/openClose;FILE_LOCK=FS;OPEN_NEW=TRUE")); + conn.close(); + } + + private void testErrorMessageWrongSplit() throws Exception { + if (config.memory || config.reopen) { + return; + } + String fn = getBaseDir() + "/openClose2" + Constants.SUFFIX_MV_FILE; + FileUtils.delete("split:" + fn); + Connection conn; + String url = getURL("jdbc:h2:split:18:" + getBaseDir() + "/openClose2", true); + conn = DriverManager.getConnection(url); + conn.createStatement().execute("create table test(id int, name varchar) " + + "as select 1, space(1000000)"); + conn.close(); + FileChannel c = FileUtils.open(fn+".1.part", "rw"); + c.position(c.size() * 2 - 1); + c.write(ByteBuffer.wrap(new byte[1])); + c.close(); + assertThrows(ErrorCode.IO_EXCEPTION_1, () -> getConnection(url)); + FileUtils.delete("split:" + fn); + } + + private void testCloseDelay() throws Exception { + deleteDb("openClose"); + String url = getURL("openClose;DB_CLOSE_DELAY=1", true); + String user = getUser(), password = getPassword(); + Connection conn = DriverManager.getConnection(url, user, password); + conn.close(); + Thread.sleep(950); + long time = System.nanoTime(); + while (System.nanoTime() - time < TimeUnit.MILLISECONDS.toNanos(100)) { + conn = DriverManager.getConnection(url, user, password); + conn.close(); + } + conn = DriverManager.getConnection(url, user, password); + conn.createStatement().execute("SHUTDOWN"); + conn.close(); + } + + private void testBackup() throws SQLException { + if (config.memory) { + return; + } + deleteDb("openClose"); + String url = getURL("openClose", true); + org.h2.Driver.load(); + Connection conn = DriverManager.getConnection(url, "sa", "abc def"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(C CLOB)"); + stat.execute("INSERT INTO TEST VALUES(SPACE(10000))"); + stat.execute("BACKUP TO '" + getBaseDir() + "/test.zip'"); + conn.close(); + deleteDb("openClose"); + Restore.execute(getBaseDir() + "/test.zip", getBaseDir(), null); + conn = DriverManager.getConnection(url, "sa", "abc def"); + stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("SELECT * FROM TEST"); + rs.next(); + assertEquals(10000, rs.getString(1).length()); + assertFalse(rs.next()); + conn.close(); + FileUtils.delete(getBaseDir() + "/test.zip"); + } + + private void testReconnectFast() throws SQLException { + if (config.memory) { + return; + } + + deleteDb("openClose"); + String user = getUser(), password = getPassword(); + String url = getURL("openClose;DATABASE_EVENT_LISTENER='" + + MyDatabaseEventListener.class.getName() + "'", true); + Connection conn = DriverManager.getConnection(url, user, password); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID IDENTITY, NAME VARCHAR)"); + stat.execute("SET MAX_MEMORY_UNDO 100000"); + stat.execute("CREATE INDEX IDXNAME ON TEST(NAME)"); + stat.execute("INSERT INTO TEST SELECT X, X || ' Data' " + + "FROM SYSTEM_RANGE(1, 1000)"); + stat.close(); + conn.close(); + conn = DriverManager.getConnection(url, user, password); + stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("SELECT * FROM SYSTEM_RANGE(1, 1)"); + if (rs.next()) { + rs.getString(1); + } + rs.close(); + stat.close(); + conn.close(); + conn = DriverManager.getConnection(url, user, password); + stat = conn.createStatement(); + // stat.execute("SET DB_CLOSE_DELAY 0"); + stat.executeUpdate("SHUTDOWN"); + stat.close(); + conn.close(); + } + + private void testCase() throws Exception { + if (config.memory) { + return; + } + + org.h2.Driver.load(); + deleteDb("openClose"); + final String url = getURL("openClose;FILE_LOCK=NO", true); + final String user = getUser(), password = getPassword(); + Connection conn = DriverManager.getConnection(url, user, password); + conn.createStatement().execute("drop table employee if exists"); + conn.createStatement().execute( + "create table employee(id int primary key, name varchar, salary int)"); + conn.close(); + // previously using getSize(200, 1000); + // but for Ubuntu, the default ulimit is 1024, + // which breaks the test + int len = getSize(10, 50); + Task[] tasks = new Task[len]; + for (int i = 0; i < len; i++) { + tasks[i] = new Task() { + @Override + public void call() throws SQLException { + Connection c = DriverManager.getConnection(url, user, password); + PreparedStatement prep = c + .prepareStatement("insert into employee values(?, ?, 0)"); + int id = getNextId(); + prep.setInt(1, id); + prep.setString(2, "employee " + id); + prep.execute(); + c.close(); + } + }; + tasks[i].execute(); + } + // for(int i=0; i DriverManager.getConnection("jdbc:h2:" + getBaseDir() + "/db")); + } finally { + Files.deleteIfExists(old); + } + } + + + /** + * A database event listener used in this test. + */ + public static final class MyDatabaseEventListener implements DatabaseEventListener { + + @Override + public void exceptionThrown(SQLException e, String sql) { + throw new AssertionError("unexpected: " + e + " sql: " + sql); + } + + @Override + public void setProgress(int state, String name, long current, long max) { + String stateName; + switch (state) { + case STATE_SCAN_FILE: + stateName = "Scan " + name + " " + current + "/" + max; + if (current > 0) { + throw new AssertionError("unexpected: " + stateName); + } + break; + case STATE_STATEMENT_START: + break; + case STATE_CREATE_INDEX: + stateName = "Create Index " + name + " " + current + "/" + max; + if (!"SYS:SYS_ID".equals(name)) { + throw new AssertionError("unexpected: " + stateName); + } + break; + case STATE_RECOVER: + stateName = "Recover " + current + "/" + max; + break; + default: + stateName = "?"; + } + // System.out.println(": " + stateName); + } + + } + +} diff --git a/h2/src/test/org/h2/test/db/TestOptimizations.java b/h2/src/test/org/h2/test/db/TestOptimizations.java new file mode 100644 index 0000000..2395824 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestOptimizations.java @@ -0,0 +1,1204 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Random; +import java.util.TreeSet; +import java.util.concurrent.TimeUnit; +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.SimpleResultSet; +import org.h2.util.Task; + +/** + * Test various optimizations (query cache, optimization for MIN(..), and + * MAX(..)). + */ +public class TestOptimizations extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb("optimizations"); + testConditionsStackOverflow(); + testIdentityIndexUsage(); + testFastRowIdCondition(); + testExplainRoundTrip(); + testOrderByExpression(); + testGroupSubquery(); + testAnalyzeLob(); + testLike(); + testExistsSubquery(); + testQueryCacheConcurrentUse(); + testQueryCacheResetParams(); + testRowId(); + testSortIndex(); + testAutoAnalyze(); + testInAndBetween(); + testNestedIn(); + testConstantIn1(); + testConstantIn2(); + testConstantTypeConversionToColumnType(); + testNestedInSelectAndLike(); + testNestedInSelect(); + testInSelectJoin(); + testMinMaxNullOptimization(); + testUseCoveringIndex(); + // testUseIndexWhenAllColumnsNotInOrderBy(); + if (config.networked) { + return; + } + testOptimizeInJoinSelect(); + testOptimizeInJoin(); + testMultiColumnRangeQuery(); + testDistinctOptimization(); + testQueryCacheTimestamp(); + if (!config.lazy) { + testQueryCacheSpeed(); + } + testQueryCache(true); + testQueryCache(false); + testIn(); + testMinMaxCountOptimization(true); + testMinMaxCountOptimization(false); + testOrderedIndexes(); + testIndexUseDespiteNullsFirst(); + testConvertOrToIn(); + testConditionAndOrDistributiveLaw(); + deleteDb("optimizations"); + } + + private void testIdentityIndexUsage() throws Exception { + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + stat.execute("create table test(a identity)"); + stat.execute("insert into test values()"); + ResultSet rs = stat.executeQuery("explain select * from test where a = 1"); + rs.next(); + assertContains(rs.getString(1), "PRIMARY_KEY"); + stat.execute("drop table test"); + conn.close(); + } + + private void testFastRowIdCondition() throws Exception { + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + stat.executeUpdate("create table many(id int) " + + "as select x from system_range(1, 10000)"); + ResultSet rs = stat.executeQuery("explain analyze select * from many " + + "where _rowid_ = 400"); + rs.next(); + assertContains(rs.getString(1), "/* scanCount: 2 */"); + conn.close(); + } + + private void testExplainRoundTrip() throws Exception { + Connection conn = getConnection("optimizations"); + assertExplainRoundTrip(conn, "SELECT \"X\" FROM SYSTEM_RANGE(1, 1)" + + " WHERE \"X\" > ANY(SELECT DISTINCT \"X\" FROM SYSTEM_RANGE(1, 1))"); + conn.close(); + } + + private void assertExplainRoundTrip(Connection conn, String sql) + throws SQLException { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("explain " + sql); + rs.next(); + String plan = rs.getString(1); + plan = plan.replaceAll("\\s+", " "); + plan = plan.replaceAll("/\\*[^\\*]*\\*/", ""); + plan = plan.replaceAll("\\s+", " "); + plan = plan.replaceAll("\\( ", "\\("); + plan = plan.replaceAll(" \\)", "\\)"); + assertEquals(sql, plan); + } + + private void testOrderByExpression() throws Exception { + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name varchar)"); + stat.execute("insert into test values(1, 'Hello'), (2, 'Hello'), (3, 'Hello')"); + ResultSet rs; + rs = stat.executeQuery( + "explain select name from test where name='Hello' order by name"); + rs.next(); + String plan = rs.getString(1); + assertContains(plan, "tableScan"); + stat.execute("drop table test"); + conn.close(); + } + + private void testGroupSubquery() throws Exception { + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + stat.execute("create table t1(id int)"); + stat.execute("create table t2(id int)"); + stat.execute("insert into t1 values(2), (2), (3)"); + stat.execute("insert into t2 values(2), (3)"); + stat.execute("create index t1id_index on t1(id)"); + ResultSet rs; + rs = stat.executeQuery("select id, (select count(*) from t2 " + + "where t2.id = t1.id) cc from t1 group by id order by id"); + rs.next(); + assertEquals(2, rs.getInt(1)); + assertEquals(1, rs.getInt(2)); + rs.next(); + assertEquals(3, rs.getInt(1)); + assertEquals(1, rs.getInt(2)); + rs.next(); + stat.execute("drop table t1, t2"); + conn.close(); + } + + private void testAnalyzeLob() throws Exception { + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + stat.execute("create table test(v varchar, b varbinary, cl clob, bl blob) as " + + "select ' ', '00', ' ', '00' from system_range(1, 100)"); + stat.execute("analyze"); + ResultSet rs = stat.executeQuery("select column_name, selectivity " + + "from information_schema.columns where table_name='TEST'"); + rs.next(); + assertEquals("V", rs.getString(1)); + assertEquals(1, rs.getInt(2)); + rs.next(); + assertEquals("B", rs.getString(1)); + assertEquals(1, rs.getInt(2)); + rs.next(); + assertEquals("CL", rs.getString(1)); + assertEquals(50, rs.getInt(2)); + rs.next(); + assertEquals("BL", rs.getString(1)); + assertEquals(50, rs.getInt(2)); + stat.execute("drop table test"); + conn.close(); + } + + private void testLike() throws Exception { + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + stat.execute("create table test(name varchar primary key) as " + + "select x from system_range(1, 10)"); + ResultSet rs = stat.executeQuery("explain select * from test " + + "where name like ? || '%' {1: 'Hello'}"); + rs.next(); + // ensure the ID = 10 part is evaluated first + assertContains(rs.getString(1), "PRIMARY_KEY_"); + stat.execute("drop table test"); + conn.close(); + } + + private void testExistsSubquery() throws Exception { + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int) as select x from system_range(1, 10)"); + ResultSet rs = stat.executeQuery("explain select * from test " + + "where exists(select 1 from test, test, test) and id = 10"); + rs.next(); + // ensure the ID = 10 part is evaluated first + assertContains(rs.getString(1), "WHERE (\"ID\" = 10)"); + stat.execute("drop table test"); + conn.close(); + } + + private void testQueryCacheConcurrentUse() throws Exception { + if (config.lazy) { + return; + } + final Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, data clob)"); + stat.execute("insert into test values(0, space(10000))"); + stat.execute("insert into test values(1, space(10001))"); + Task[] tasks = new Task[2]; + for (int i = 0; i < tasks.length; i++) { + tasks[i] = new Task() { + @Override + public void call() throws Exception { + PreparedStatement prep = conn.prepareStatement( + "select * from test where id = ?"); + while (!stop) { + int x = (int) (Math.random() * 2); + prep.setInt(1, x); + ResultSet rs = prep.executeQuery(); + rs.next(); + String data = rs.getString(2); + if (data.length() != 10000 + x) { + throw new Exception(data.length() + " != " + x); + } + rs.close(); + } + } + }; + tasks[i].execute(); + } + Thread.sleep(1000); + for (Task t : tasks) { + t.get(); + } + stat.execute("drop table test"); + conn.close(); + } + + private void testQueryCacheResetParams() throws SQLException { + Connection conn = getConnection("optimizations"); + PreparedStatement prep; + prep = conn.prepareStatement("select ?"); + prep.setString(1, "Hello"); + prep.execute(); + prep.close(); + prep = conn.prepareStatement("select ?"); + assertThrows(ErrorCode.PARAMETER_NOT_SET_1, prep).execute(); + prep.close(); + conn.close(); + } + + private void testRowId() throws SQLException { + if (config.memory) { + return; + } + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + ResultSet rs; + + stat.execute("create table test(data varchar)"); + stat.execute("select min(_rowid_ + 1) from test"); + stat.execute("insert into test(_rowid_, data) values(10, 'Hello')"); + stat.execute("insert into test(data) values('World')"); + stat.execute("insert into test(_rowid_, data) values(20, 'Hello')"); + stat.execute( + "merge into test using (values(20, 'Hallo')) s(id, data) on test._rowid_ = s.id" + + " when matched then update set data = s.data"); + rs = stat.executeQuery( + "select _rowid_, data from test order by _rowid_"); + rs.next(); + assertEquals(10, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + rs.next(); + assertEquals(11, rs.getInt(1)); + assertEquals("World", rs.getString(2)); + rs.next(); + assertEquals(20, rs.getInt(1)); + assertEquals("Hallo", rs.getString(2)); + assertFalse(rs.next()); + stat.execute("drop table test"); + + stat.execute("create table test(id int primary key, name varchar)"); + stat.execute("insert into test values(0, 'Hello')"); + stat.execute("insert into test values(3, 'Hello')"); + stat.execute("insert into test values(2, 'Hello')"); + + rs = stat.executeQuery("explain select * from test where _rowid_ = 2"); + rs.next(); + assertContains(rs.getString(1), ".tableScan: _ROWID_ ="); + + rs = stat.executeQuery("explain select * from test where _rowid_ > 2"); + rs.next(); + assertContains(rs.getString(1), ".tableScan: _ROWID_ >"); + + rs = stat.executeQuery("explain select * from test order by _rowid_"); + rs.next(); + assertContains(rs.getString(1), "/* index sorted */"); + rs = stat.executeQuery("select _rowid_, * from test order by _rowid_"); + rs.next(); + assertEquals(0, rs.getInt(1)); + assertEquals(0, rs.getInt(2)); + rs.next(); + assertEquals(2, rs.getInt(1)); + assertEquals(2, rs.getInt(2)); + rs.next(); + assertEquals(3, rs.getInt(1)); + assertEquals(3, rs.getInt(2)); + + stat.execute("drop table test"); + conn.close(); + } + + private void testSortIndex() throws SQLException { + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + stat.execute("drop table test if exists"); + stat.execute("create table test(id int)"); + stat.execute("create index idx_id_desc on test(id desc)"); + stat.execute("create index idx_id_asc on test(id)"); + ResultSet rs; + + rs = stat.executeQuery("explain select * from test " + + "where id > 10 order by id"); + rs.next(); + assertContains(rs.getString(1), "IDX_ID_ASC"); + + rs = stat.executeQuery("explain select * from test " + + "where id < 10 order by id desc"); + rs.next(); + assertContains(rs.getString(1), "IDX_ID_DESC"); + + rs.next(); + stat.execute("drop table test"); + conn.close(); + } + + private void testAutoAnalyze() throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery( + "SELECT SETTING_VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME = 'analyzeAuto'"); + int auto = rs.next() ? rs.getInt(1) : 0; + if (auto != 0) { + stat.execute("create table test(id int)"); + stat.execute("create user onlyInsert password ''"); + stat.execute("grant insert on test to onlyInsert"); + Connection conn2 = getConnection( + "optimizations", "onlyInsert", getPassword("")); + Statement stat2 = conn2.createStatement(); + stat2.execute("insert into test select x " + + "from system_range(1, " + (auto + 10) + ")"); + conn2.close(); + } + conn.close(); + } + + private void testInAndBetween() throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + ResultSet rs; + stat.execute("create table test(id int, name varchar)"); + stat.execute("create index idx_name on test(id, name)"); + stat.execute("insert into test values(1, 'Hello'), (2, 'World')"); + rs = stat.executeQuery("select * from test " + + "where id between 1 and 3 and name in ('World')"); + assertTrue(rs.next()); + rs = stat.executeQuery("select * from test " + + "where id between 1 and 3 and name in (select 'World')"); + assertTrue(rs.next()); + stat.execute("drop table test"); + conn.close(); + } + + private void testNestedIn() throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + ResultSet rs; + + stat.execute("create table accounts(id integer primary key, " + + "status varchar(255), tag varchar(255))"); + stat.execute("insert into accounts values (31, 'X', 'A')"); + stat.execute("create table parent(id int)"); + stat.execute("insert into parent values(31)"); + stat.execute("create view test_view as select a.status, a.tag " + + "from accounts a, parent t where a.id = t.id"); + rs = stat.executeQuery("select * from test_view " + + "where status='X' and tag in ('A','B')"); + assertTrue(rs.next()); + rs = stat.executeQuery("select * from (select a.status, a.tag " + + "from accounts a, parent t where a.id = t.id) x " + + "where status='X' and tag in ('A','B')"); + assertTrue(rs.next()); + + stat.execute("create table test(id int primary key, name varchar(255))"); + stat.execute("create unique index idx_name on test(name, id)"); + stat.execute("insert into test values(1, 'Hello'), (2, 'World')"); + rs = stat.executeQuery("select * from (select * from test) " + + "where id=1 and name in('Hello', 'World')"); + assertTrue(rs.next()); + stat.execute("drop table test"); + + conn.close(); + } + + private void testConstantIn1() throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + + stat.execute("create table test(id int primary key, name varchar(255))"); + stat.execute("insert into test values(1, 'Hello'), (2, 'World')"); + assertSingleValue(stat, + "select count(*) from test where name in ('Hello', 'World', '1')", 2); + assertSingleValue(stat, + "select count(*) from test where name in ('Hello', 'World')", 2); + assertSingleValue(stat, + "select count(*) from test where name in ('Hello', 'Not')", 1); + stat.execute("drop table test"); + + conn.close(); + } + + private void testConstantIn2() throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations;IGNORECASE=TRUE"); + Statement stat = conn.createStatement(); + + stat.executeUpdate("CREATE TABLE testValues (x VARCHAR(50))"); + stat.executeUpdate("INSERT INTO testValues (x) SELECT 'foo' x"); + ResultSet resultSet; + resultSet = stat.executeQuery( + "SELECT x FROM testValues WHERE x IN ('foo')"); + assertTrue(resultSet.next()); + resultSet = stat.executeQuery( + "SELECT x FROM testValues WHERE x IN ('FOO')"); + assertTrue(resultSet.next()); + resultSet = stat.executeQuery( + "SELECT x FROM testValues WHERE x IN ('foo','bar')"); + assertTrue(resultSet.next()); + resultSet = stat.executeQuery( + "SELECT x FROM testValues WHERE x IN ('FOO','bar')"); + assertTrue(resultSet.next()); + + conn.close(); + } + + private void testConstantTypeConversionToColumnType() throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations;IGNORECASE=TRUE"); + Statement stat = conn.createStatement(); + + stat.executeUpdate("CREATE TABLE test (x int)"); + ResultSet resultSet; + resultSet = stat.executeQuery( + "EXPLAIN SELECT x FROM test WHERE x = '5'"); + + assertTrue(resultSet.next()); + // String constant '5' has been converted to int constant 5 on + // optimization + assertTrue(resultSet.getString(1).endsWith("\"X\" = 5")); + + stat.execute("drop table test"); + + conn.close(); + } + + private void testNestedInSelect() throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + ResultSet rs; + + stat.execute("create table test(id int primary key, name varchar) " + + "as select 1, 'Hello'"); + stat.execute("select * from (select * from test) " + + "where id=1 and name in('Hello', 'World')"); + + stat.execute("drop table test"); + + stat.execute("create table test(id int, name varchar) as select 1, 'Hello'"); + stat.execute("create index idx2 on test(id, name)"); + rs = stat.executeQuery("select count(*) from test " + + "where id=1 and name in('Hello', 'x')"); + rs.next(); + assertEquals(1, rs.getInt(1)); + + conn.close(); + } + + private void testNestedInSelectAndLike() throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + + stat.execute("create table test(id int primary key)"); + stat.execute("insert into test values(2)"); + ResultSet rs = stat.executeQuery("select * from test where id in(1, 2)"); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertFalse(rs.next()); + stat.execute("create table test2(id int primary key hash)"); + stat.execute("insert into test2 values(2)"); + rs = stat.executeQuery("select * from test where id in(1, 2)"); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertFalse(rs.next()); + + PreparedStatement prep; + prep = conn.prepareStatement("SELECT * FROM SYSTEM_RANGE(1, 1) A " + + "WHERE A.X IN (SELECT B.X FROM SYSTEM_RANGE(1, 1) B WHERE B.X LIKE ?)"); + prep.setString(1, "1"); + prep.execute(); + prep = conn.prepareStatement("SELECT * FROM SYSTEM_RANGE(1, 1) A " + + "WHERE A.X IN (SELECT B.X FROM SYSTEM_RANGE(1, 1) B WHERE B.X IN (?, ?))"); + prep.setInt(1, 1); + prep.setInt(2, 1); + prep.executeQuery(); + conn.close(); + } + + private void testInSelectJoin() throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + stat.execute("create table test(a int, b int, c int, d int) " + + "as select 1, 1, 1, 1 from dual;"); + ResultSet rs; + PreparedStatement prep; + prep = conn.prepareStatement("SELECT 2 FROM TEST A " + + "INNER JOIN (SELECT DISTINCT B.C AS X FROM TEST B " + + "WHERE B.D = ?2) V ON 1=1 WHERE (A = ?1) AND (B = V.X)"); + prep.setInt(1, 1); + prep.setInt(2, 1); + rs = prep.executeQuery(); + assertTrue(rs.next()); + assertFalse(rs.next()); + + prep = conn.prepareStatement( + "select 2 from test a where a=? and b in(" + + "select b.c from test b where b.d=?)"); + prep.setInt(1, 1); + prep.setInt(2, 1); + rs = prep.executeQuery(); + assertTrue(rs.next()); + assertFalse(rs.next()); + conn.close(); + } + + + private void testOptimizeInJoinSelect() throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + stat.execute("create table item(id int primary key)"); + stat.execute("insert into item values(1)"); + stat.execute("create alias opt for '" + getClass().getName() + ".optimizeInJoinSelect'"); + PreparedStatement prep = conn.prepareStatement( + "select * from item where id in (select x from opt())"); + ResultSet rs = prep.executeQuery(); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertFalse(rs.next()); + conn.close(); + } + + /** + * This method is called via reflection from the database. + * + * @return a result set + */ + public static ResultSet optimizeInJoinSelect() { + SimpleResultSet rs = new SimpleResultSet(); + rs.addColumn("X", Types.INTEGER, 0, 0); + rs.addRow(1); + return rs; + } + + private void testOptimizeInJoin() throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + + stat.execute("create table test(id int primary key)"); + stat.execute("insert into test select x from system_range(1, 1000)"); + ResultSet rs = stat.executeQuery("explain select * " + + "from test where id in (400, 300)"); + rs.next(); + String plan = rs.getString(1); + if (plan.indexOf("/* PUBLIC.PRIMARY_KEY_") < 0) { + fail("Expected using the primary key, got: " + plan); + } + conn.close(); + } + + private void testMinMaxNullOptimization() throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + Random random = new Random(1); + int len = getSize(50, 500); + for (int i = 0; i < len; i++) { + stat.execute("drop table if exists test"); + stat.execute("create table test(x int)"); + if (random.nextBoolean()) { + int count = random.nextBoolean() ? 1 : 1 + random.nextInt(len); + if (count > 0) { + stat.execute("insert into test select null " + + "from system_range(1, " + count + ")"); + } + } + int maxExpected = -1; + int minExpected = -1; + if (random.nextInt(10) != 1) { + minExpected = 1; + maxExpected = 1 + random.nextInt(len); + stat.execute("insert into test select x " + + "from system_range(1, " + maxExpected + ")"); + } + String sql = "create index idx on test(x"; + if (random.nextBoolean()) { + sql += " desc"; + } + if (random.nextBoolean()) { + if (random.nextBoolean()) { + sql += " nulls first"; + } else { + sql += " nulls last"; + } + } + sql += ")"; + stat.execute(sql); + ResultSet rs = stat.executeQuery( + "explain select min(x), max(x) from test"); + rs.next(); + rs = stat.executeQuery("select min(x), max(x) from test"); + rs.next(); + int min = rs.getInt(1); + if (rs.wasNull()) { + min = -1; + } + int max = rs.getInt(2); + if (rs.wasNull()) { + max = -1; + } + assertEquals(minExpected, min); + assertEquals(maxExpected, max); + } + conn.close(); + } + + private void testMultiColumnRangeQuery() throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE Logs(id INT PRIMARY KEY, type INT)"); + stat.execute("CREATE unique INDEX type_index ON Logs(type, id)"); + stat.execute("INSERT INTO Logs SELECT X, MOD(X, 3) " + + "FROM SYSTEM_RANGE(1, 1000)"); + stat.execute("ANALYZE SAMPLE_SIZE 0"); + ResultSet rs; + rs = stat.executeQuery("EXPLAIN SELECT id FROM Logs " + + "WHERE id < 100 and type=2 AND id<100"); + rs.next(); + String plan = rs.getString(1); + assertContains(plan, "TYPE_INDEX"); + conn.close(); + } + + private void testUseIndexWhenAllColumnsNotInOrderBy() throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, account int, tx int)"); + stat.execute("insert into test select x, x*100, x from system_range(1, 10000)"); + stat.execute("analyze sample_size 5"); + stat.execute("create unique index idx_test_account_tx on test(account, tx desc)"); + ResultSet rs; + rs = stat.executeQuery("explain analyze " + + "select tx from test " + + "where account=22 and tx<9999999 " + + "order by tx desc limit 25"); + rs.next(); + String plan = rs.getString(1); + assertContains(plan, "index sorted"); + conn.close(); + } + + private void testDistinctOptimization() throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, " + + "NAME VARCHAR, TYPE INT)"); + stat.execute("CREATE INDEX IDX_TEST_TYPE ON TEST(TYPE)"); + Random random = new Random(1); + int len = getSize(10000, 100000); + int[] groupCount = new int[10]; + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST VALUES(?, ?, ?)"); + for (int i = 0; i < len; i++) { + prep.setInt(1, i); + prep.setString(2, "Hello World"); + int type = random.nextInt(10); + groupCount[type]++; + prep.setInt(3, type); + prep.execute(); + } + ResultSet rs; + rs = stat.executeQuery("SELECT TYPE, COUNT(*) FROM TEST " + + "GROUP BY TYPE ORDER BY TYPE"); + for (int i = 0; rs.next(); i++) { + assertEquals(i, rs.getInt(1)); + assertEquals(groupCount[i], rs.getInt(2)); + } + assertFalse(rs.next()); + rs = stat.executeQuery("SELECT DISTINCT TYPE FROM TEST " + + "ORDER BY TYPE"); + for (int i = 0; rs.next(); i++) { + assertEquals(i, rs.getInt(1)); + } + assertFalse(rs.next()); + stat.execute("ANALYZE"); + rs = stat.executeQuery("SELECT DISTINCT TYPE FROM TEST " + + "ORDER BY TYPE"); + for (int i = 0; i < 10; i++) { + assertTrue(rs.next()); + assertEquals(i, rs.getInt(1)); + } + assertFalse(rs.next()); + rs = stat.executeQuery("SELECT DISTINCT TYPE FROM TEST " + + "ORDER BY TYPE LIMIT 5 OFFSET 2"); + for (int i = 2; i < 7; i++) { + assertTrue(rs.next()); + assertEquals(i, rs.getInt(1)); + } + assertFalse(rs.next()); + conn.close(); + } + + private void testQueryCacheTimestamp() throws Exception { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + PreparedStatement prep = conn.prepareStatement( + "SELECT CURRENT_TIMESTAMP()"); + ResultSet rs = prep.executeQuery(); + rs.next(); + String a = rs.getString(1); + Thread.sleep(50); + rs = prep.executeQuery(); + rs.next(); + String b = rs.getString(1); + assertFalse(a.equals(b)); + conn.close(); + } + + private void testQueryCacheSpeed() throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + testQuerySpeed(stat, + "select sum(a.n), sum(b.x) from system_range(1, 100) b, " + + "(select sum(x) n from system_range(1, 4000)) a"); + conn.close(); + } + + private void testQuerySpeed(Statement stat, String sql) throws SQLException { + long totalTime = 0; + long totalTimeOptimized = 0; + for (int i = 0; i < 3; i++) { + totalTime += measureQuerySpeed(stat, sql, false); + totalTimeOptimized += measureQuerySpeed(stat, sql, true); + } + // System.out.println( + // TimeUnit.NANOSECONDS.toMillis(totalTime) + " " + + // TimeUnit.NANOSECONDS.toMillis(totalTimeOptimized)); + if (totalTimeOptimized > totalTime) { + fail("not optimized: " + TimeUnit.NANOSECONDS.toMillis(totalTime) + + " optimized: " + TimeUnit.NANOSECONDS.toMillis(totalTimeOptimized) + + " sql:" + sql); + } + } + + private static long measureQuerySpeed(Statement stat, String sql, boolean optimized) throws SQLException { + stat.execute("set OPTIMIZE_REUSE_RESULTS " + (optimized ? "1" : "0")); + stat.execute(sql); + long time = System.nanoTime(); + stat.execute(sql); + return System.nanoTime() - time; + } + + private void testQueryCache(boolean optimize) throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + if (optimize) { + stat.execute("set OPTIMIZE_REUSE_RESULTS 1"); + } else { + stat.execute("set OPTIMIZE_REUSE_RESULTS 0"); + } + stat.execute("create table test(id int)"); + stat.execute("create table test2(id int)"); + stat.execute("insert into test values(1), (1), (2)"); + stat.execute("insert into test2 values(1)"); + PreparedStatement prep = conn.prepareStatement( + "select * from test where id = (select id from test2)"); + ResultSet rs1 = prep.executeQuery(); + rs1.next(); + assertEquals(1, rs1.getInt(1)); + rs1.next(); + assertEquals(1, rs1.getInt(1)); + assertFalse(rs1.next()); + + stat.execute("update test2 set id = 2"); + ResultSet rs2 = prep.executeQuery(); + rs2.next(); + assertEquals(2, rs2.getInt(1)); + + conn.close(); + } + + private void testMinMaxCountOptimization(boolean memory) + throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + stat.execute("create " + (memory ? "memory" : "") + + " table test(id int primary key, v int)"); + stat.execute("create index idx_v_id on test(v, id);"); + int len = getSize(1000, 10000); + HashMap map = new HashMap<>(); + TreeSet set = new TreeSet<>(); + Random random = new Random(1); + for (int i = 0; i < len; i++) { + if (i == len / 2) { + if (!config.memory) { + conn.close(); + conn = getConnection("optimizations"); + stat = conn.createStatement(); + } + } + switch (random.nextInt(10)) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + if (random.nextInt(1000) == 1) { + stat.execute("insert into test values(" + i + ", null)"); + map.put(i, null); + } else { + int value = random.nextInt(); + stat.execute("insert into test values(" + i + ", " + value + ")"); + map.put(i, value); + set.add(value); + } + break; + case 6: + case 7: + case 8: { + if (map.size() > 0) { + for (int j = random.nextInt(i), k = 0; k < 10; k++, j++) { + if (map.containsKey(j)) { + Integer x = map.remove(j); + if (x != null) { + set.remove(x); + } + stat.execute("delete from test where id=" + j); + } + } + } + break; + } + case 9: { + ArrayList list = new ArrayList<>(map.values()); + int count = list.size(); + Integer min = null, max = null; + if (count > 0) { + min = set.first(); + max = set.last(); + } + ResultSet rs = stat.executeQuery( + "select min(v), max(v), count(*) from test"); + rs.next(); + Integer minDb = (Integer) rs.getObject(1); + Integer maxDb = (Integer) rs.getObject(2); + int countDb = rs.getInt(3); + assertEquals(minDb, min); + assertEquals(maxDb, max); + assertEquals(countDb, count); + break; + } + default: + } + } + conn.close(); + } + + private void testIn() throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + PreparedStatement prep; + ResultSet rs; + + assertFalse(stat.executeQuery("select * from dual " + + "where x in()").next()); + assertFalse(stat.executeQuery("select * from dual " + + "where null in(1)").next()); + assertFalse(stat.executeQuery("select * from dual " + + "where null in(null)").next()); + assertFalse(stat.executeQuery("select * from dual " + + "where null in(null, 1)").next()); + + assertFalse(stat.executeQuery("select * from system_range(1, 1) " + + "where 1+x in(3, 4)").next()); + assertFalse(stat.executeQuery("select * from system_range(1, 1) d1, dual d2 " + + "where d1.x in(3, 4)").next()); + + stat.execute("create table test(id int primary key, name varchar)"); + stat.execute("insert into test values(1, 'Hello')"); + stat.execute("insert into test values(2, 'World')"); + + prep = conn.prepareStatement("select * from test t1 where t1.id in(?)"); + prep.setInt(1, 1); + rs = prep.executeQuery(); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + assertFalse(rs.next()); + + prep = conn.prepareStatement("select * from test t1 " + + "where t1.id in(?, ?) order by id"); + prep.setInt(1, 1); + prep.setInt(2, 2); + rs = prep.executeQuery(); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + rs.next(); + assertEquals(2, rs.getInt(1)); + assertEquals("World", rs.getString(2)); + assertFalse(rs.next()); + + prep = conn.prepareStatement("select * from test t1 where t1.id " + + "in(select t2.id from test t2 where t2.id=?)"); + prep.setInt(1, 2); + rs = prep.executeQuery(); + rs.next(); + assertEquals(2, rs.getInt(1)); + assertEquals("World", rs.getString(2)); + assertFalse(rs.next()); + + prep = conn.prepareStatement("select * from test t1 where t1.id " + + "in(select t2.id from test t2 where t2.id=? and t1.id<>t2.id)"); + prep.setInt(1, 2); + rs = prep.executeQuery(); + assertFalse(rs.next()); + + prep = conn.prepareStatement("select * from test t1 where t1.id " + + "in(select t2.id from test t2 where t2.id in(cast(?+10 as varchar)))"); + prep.setInt(1, 2); + rs = prep.executeQuery(); + assertFalse(rs.next()); + + conn.close(); + } + + /** + * Where there are multiple indices, and we have an ORDER BY, select the + * index that already has the required ordering. + */ + private void testOrderedIndexes() throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + + stat.execute("CREATE TABLE my_table(K1 INT, K2 INT, " + + "VAL VARCHAR, PRIMARY KEY(K1, K2))"); + stat.execute("CREATE INDEX my_index ON my_table(K1, VAL)"); + ResultSet rs = stat.executeQuery( + "EXPLAIN PLAN FOR SELECT * FROM my_table WHERE K1=7 " + + "ORDER BY K1, VAL"); + rs.next(); + assertContains(rs.getString(1), "/* PUBLIC.MY_INDEX: K1 = 7 */"); + + stat.execute("DROP TABLE my_table"); + + // where we have two covering indexes, make sure + // we choose the one that covers more + stat.execute("CREATE TABLE my_table(K1 INT, K2 INT, VAL VARCHAR)"); + stat.execute("CREATE INDEX my_index1 ON my_table(K1, K2)"); + stat.execute("CREATE INDEX my_index2 ON my_table(K1, K2, VAL)"); + rs = stat.executeQuery( + "EXPLAIN PLAN FOR SELECT * FROM my_table WHERE K1=7 " + + "ORDER BY K1, K2, VAL"); + rs.next(); + assertContains(rs.getString(1), "/* PUBLIC.MY_INDEX2: K1 = 7 */"); + + conn.close(); + } + + private void testIndexUseDespiteNullsFirst() throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + + stat.execute("CREATE TABLE my_table(K1 INT)"); + stat.execute("CREATE INDEX my_index ON my_table(K1)"); + stat.execute("INSERT INTO my_table VALUES (NULL)"); + stat.execute("INSERT INTO my_table VALUES (1)"); + stat.execute("INSERT INTO my_table VALUES (2)"); + + ResultSet rs; + String result; + + + rs = stat.executeQuery( + "EXPLAIN PLAN FOR SELECT * FROM my_table " + + "ORDER BY K1 ASC NULLS FIRST"); + rs.next(); + result = rs.getString(1); + assertContains(result, "/* index sorted */"); + + rs = stat.executeQuery( + "SELECT * FROM my_table " + + "ORDER BY K1 ASC NULLS FIRST"); + rs.next(); + assertNull(rs.getObject(1)); + rs.next(); + assertEquals(1, rs.getInt(1)); + rs.next(); + assertEquals(2, rs.getInt(1)); + + // === + rs = stat.executeQuery( + "EXPLAIN PLAN FOR SELECT * FROM my_table " + + "ORDER BY K1 DESC NULLS FIRST"); + rs.next(); + result = rs.getString(1); + if (result.contains("/* index sorted */")) { + fail(result + " does not contain: /* index sorted */"); + } + + rs = stat.executeQuery( + "SELECT * FROM my_table " + + "ORDER BY K1 DESC NULLS FIRST"); + rs.next(); + assertNull(rs.getObject(1)); + rs.next(); + assertEquals(2, rs.getInt(1)); + rs.next(); + assertEquals(1, rs.getInt(1)); + + // === + rs = stat.executeQuery( + "EXPLAIN PLAN FOR SELECT * FROM my_table " + + "ORDER BY K1 ASC NULLS LAST"); + rs.next(); + result = rs.getString(1); + if (result.contains("/* index sorted */")) { + fail(result + " does not contain: /* index sorted */"); + } + + rs = stat.executeQuery( + "SELECT * FROM my_table " + + "ORDER BY K1 ASC NULLS LAST"); + rs.next(); + assertEquals(1, rs.getInt(1)); + rs.next(); + assertEquals(2, rs.getInt(1)); + rs.next(); + assertNull(rs.getObject(1)); + + // TODO: Test "EXPLAIN PLAN FOR SELECT * FROM my_table ORDER BY K1 DESC NULLS FIRST" + // Currently fails, as using the index when sorting DESC is currently not supported. + + stat.execute("DROP TABLE my_table"); + conn.close(); + } + + private void testConvertOrToIn() throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + + stat.execute("create table test(id int primary key, name varchar(255))"); + stat.execute("insert into test values" + + "(1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, '5')"); + + ResultSet rs = stat.executeQuery("EXPLAIN PLAN FOR SELECT * " + + "FROM test WHERE ID=1 OR ID=2 OR ID=3 OR ID=4 OR ID=5"); + rs.next(); + assertContains(rs.getString(1), "\"ID\" IN(1, 2, 3, 4, 5)"); + + rs = stat.executeQuery("SELECT COUNT(*) FROM test " + + "WHERE ID=1 OR ID=2 OR ID=3 OR ID=4 OR ID=5"); + rs.next(); + assertEquals(5, rs.getInt(1)); + + conn.close(); + } + + private void testUseCoveringIndex() throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TABLE_A(id IDENTITY PRIMARY KEY NOT NULL, " + + "name VARCHAR NOT NULL, active BOOLEAN DEFAULT TRUE, " + + "CONSTRAINT TABLE_A_UK UNIQUE (name) )"); + stat.execute("CREATE TABLE TABLE_B(id IDENTITY PRIMARY KEY NOT NULL, " + + "TABLE_a_id BIGINT NOT NULL, createDate TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, " + + "CONSTRAINT TABLE_B_UK UNIQUE (table_a_id, createDate))"); + stat.execute("CREATE INDEX TABLE_B_IDX ON TABLE_B(TABLE_A_ID)"); + stat.execute("ALTER TABLE TABLE_B ADD FOREIGN KEY (table_a_id) REFERENCES TABLE_A(id)"); + stat.execute("INSERT INTO TABLE_A (name) SELECT 'package_' || CAST(X as VARCHAR) " + + "FROM SYSTEM_RANGE(1, 100) WHERE X <= 100"); + int count = config.memory ? 30_000 : 50_000; + stat.execute("INSERT INTO TABLE_B (table_a_id, createDate) SELECT " + + "CASE WHEN table_a_id = 0 THEN 1 ELSE table_a_id END, createDate " + + "FROM ( SELECT ROUND((RAND() * 100)) AS table_a_id, " + + "DATEADD('SECOND', X, CURRENT_TIMESTAMP) as createDate FROM SYSTEM_RANGE(1, " + count + ") " + + "WHERE X < " + count + " )"); + stat.execute("ANALYZE"); + + ResultSet rs = stat.executeQuery("EXPLAIN ANALYZE SELECT MAX(b.id) as id " + + "FROM table_b b JOIN table_a a ON b.table_a_id = a.id GROUP BY b.table_a_id " + + "HAVING A.ACTIVE = TRUE"); + rs.next(); + assertContains(rs.getString(1), "/* PUBLIC.TABLE_B_IDX: TABLE_A_ID = A.ID */"); + + rs = stat.executeQuery("EXPLAIN ANALYZE SELECT MAX(id) FROM table_b GROUP BY table_a_id"); + rs.next(); + assertContains(rs.getString(1), "/* PUBLIC.TABLE_B_IDX"); + conn.close(); + } + + private void testConditionAndOrDistributiveLaw() throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE IF NOT EXISTS TABLE_A (" + + "id int NOT NULL AUTO_INCREMENT, " + + "name VARCHAR(30) NOT NULL," + + "occupation VARCHAR(20)," + + "age int," + + "salary int," + + "PRIMARY KEY(id))"); + stat.execute("INSERT INTO TABLE_A (name,occupation,age,salary) VALUES" + + "('mark', 'doctor',25,5000)," + + "('kevin', 'artist',20,4000)," + + "('isuru', 'engineer',25,5000)," + + "('josaph', 'businessman',30,7000)," + + "('sajeewa', 'analyst',24,5000)," + + "('randil', 'engineer',25,5000)," + + "('ashan', 'developer',24,5000)"); + ResultSet rs = stat.executeQuery("SELECT * FROM TABLE_A WHERE (salary = 5000 AND name = 'isuru') OR" + + "(age = 25 AND name = 'isuru') "); + rs.next(); + assertTrue("engineer".equals(rs.getString("occupation"))); + conn.close(); + } + + private void testConditionsStackOverflow() throws SQLException { + deleteDb("optimizations"); + Connection conn = getConnection("optimizations"); + Statement stat = conn.createStatement(); + StringBuilder b = new StringBuilder("SELECT 1"); + for (int i=0; i<10000; i++) { + b.append(" AND 1"); + } + ResultSet rs = stat.executeQuery(b.toString()); + rs.next(); + assertTrue(rs.getBoolean(1)); + conn.close(); + } +} diff --git a/h2/src/test/org/h2/test/db/TestOutOfMemory.java b/h2/src/test/org/h2/test/db/TestOutOfMemory.java new file mode 100644 index 0000000..c93c5b8 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestOutOfMemory.java @@ -0,0 +1,254 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.atomic.AtomicReference; +import org.h2.api.ErrorCode; +import org.h2.mvstore.MVStore; +import org.h2.mvstore.MVStoreException; +import org.h2.store.fs.FilePath; +import org.h2.store.fs.FileUtils; +import org.h2.store.fs.mem.FilePathMem; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.Utils; + +/** + * Tests out of memory situations. The database must not get corrupted, and + * transactions must stay atomic. + */ +public class TestOutOfMemory extends TestDb { + + private static final String DB_NAME = "outOfMemory"; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + if (config.vmlens) { + // running out of memory will cause the vmlens agent to stop working + return false; + } + return true; + } + + @Override + public void test() throws Exception { + try { + if (!config.ci) { + System.gc(); + testMVStoreUsingInMemoryFileSystem(); + System.gc(); + testDatabaseUsingInMemoryFileSystem(); + } + System.gc(); + if (!config.networked) { // for some unknown reason it fails + testUpdateWhenNearlyOutOfMemory(); + } + } finally { + System.gc(); + } + } + + private void testMVStoreUsingInMemoryFileSystem() { + FilePath.register(new FilePathMem()); + String fileName = "memFS:" + getTestName(); + AtomicReference exRef = new AtomicReference<>(); + MVStore store = new MVStore.Builder() + .fileName(fileName) + .backgroundExceptionHandler((t, e) -> exRef.compareAndSet(null, e)) + .open(); + try { + Map map = store.openMap("test"); + Random r = new Random(1); + try { + for (int i = 0; i < 100; i++) { + byte[] data = new byte[10 * 1024 * 1024]; + r.nextBytes(data); + map.put(i, data); + } + Throwable throwable = exRef.get(); + if(throwable instanceof OutOfMemoryError) throw (OutOfMemoryError)throwable; + if(throwable instanceof MVStoreException) throw (MVStoreException)throwable; + fail(); + } catch (OutOfMemoryError | MVStoreException e) { + // expected + } + try { + store.close(); + } catch (MVStoreException e) { + // expected + } + store.closeImmediately(); + store = MVStore.open(fileName); + store.openMap("test"); + store.close(); + } finally { + // just in case, otherwise if this test suffers a spurious failure, + // succeeding tests will too, because they will OOM + store.closeImmediately(); + FileUtils.delete(fileName); + } + } + + private void testDatabaseUsingInMemoryFileSystem() throws SQLException, InterruptedException { + String filename = "memFS:" + getTestName(); + String url = "jdbc:h2:" + filename + "/test"; + try { + Connection conn = DriverManager.getConnection(url); + Statement stat = conn.createStatement(); + long memoryFree = Utils.getMemoryFree(); + try { + stat.execute("create table test(id int, name varchar) as " + + "select x, space(1000000+x) from system_range(1, 10000)"); + fail(); + } catch (SQLException e) { + assertTrue("Unexpected error code: " + e.getErrorCode(), + ErrorCode.OUT_OF_MEMORY == e.getErrorCode() || + ErrorCode.FILE_CORRUPTED_1 == e.getErrorCode() || + ErrorCode.DATABASE_IS_CLOSED == e.getErrorCode() || + ErrorCode.GENERAL_ERROR_1 == e.getErrorCode()); + } + recoverAfterOOM(memoryFree * 3 / 4); + try { + conn.close(); + fail(); + } catch (SQLException e) { + assertTrue("Unexpected error code: " + e.getErrorCode(), + ErrorCode.OUT_OF_MEMORY == e.getErrorCode() || + ErrorCode.FILE_CORRUPTED_1 == e.getErrorCode() || + ErrorCode.DATABASE_IS_CLOSED == e.getErrorCode() || + ErrorCode.GENERAL_ERROR_1 == e.getErrorCode()); + } + recoverAfterOOM(memoryFree * 3 / 4); + conn = DriverManager.getConnection(url); + stat = conn.createStatement(); + stat.execute("SELECT 1"); + conn.close(); + } finally { + // release the static data this test generates + FileUtils.deleteRecursive(filename, true); + } + } + + private static void recoverAfterOOM(long expectedFreeMemory) throws InterruptedException { + for (int i = 0; i < 50; i++) { + if (Utils.getMemoryFree() > expectedFreeMemory) { + break; + } + Thread.sleep(20); + } + } + + private void testUpdateWhenNearlyOutOfMemory() throws Exception { + if (config.memory) { + return; + } + deleteDb(DB_NAME); + + ProcessBuilder processBuilder = buildChild( + DB_NAME + ";MAX_OPERATION_MEMORY=1000000", + MyChild.class, + "-XX:+UseParallelGC", +// "-XX:+UseG1GC", + "-Xmx128m"); +//* + processBuilder.start().waitFor(); +/*/ + List args = processBuilder.command(); + for (Iterator iter = args.iterator(); iter.hasNext(); ) { + String arg = iter.next(); + if(arg.equals(MyChild.class.getName())) { + iter.remove(); + break; + } + iter.remove(); + } + MyChild.main(args.toArray(new String[0])); +//*/ + try (Connection conn = getConnection(DB_NAME)) { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("SELECT count(*) FROM stuff"); + assertTrue(rs.next()); + assertEquals(3000, rs.getInt(1)); + + rs = stat.executeQuery("SELECT * FROM stuff WHERE id = 3000"); + assertTrue(rs.next()); + String text = rs.getString(2); + assertFalse(rs.wasNull()); + assertEquals(1004, text.length()); + + // TODO: there are intermittent failures here + // where number is about 1000 short of expected value. + // This indicates a real problem - durability failure + // and need to be looked at. + rs = stat.executeQuery("SELECT sum(length(text)) FROM stuff"); + assertTrue(rs.next()); + int totalSize = rs.getInt(1); + if (3010893 > totalSize) { + TestBase.logErrorMessage("Durability failure - expected: 3010893, actual: " + totalSize); + } + } finally { + deleteDb(DB_NAME); + } + } + + public static final class MyChild extends TestDb.Child { + + /** + * Run just this test. + * + * @param args the arguments + */ + public static void main(String... args) throws Exception { + new MyChild(args).init().test(); + } + + private MyChild(String... args) { + super(args); + } + + @Override + public void test() { + try (Connection conn = getConnection()) { + Statement stat = conn.createStatement(); + stat.execute("DROP ALL OBJECTS"); + stat.execute("CREATE TABLE stuff (id INT, text VARCHAR)"); + stat.execute("INSERT INTO stuff(id) SELECT x FROM system_range(1, 3000)"); + PreparedStatement prep = conn.prepareStatement( + "UPDATE stuff SET text = IFNULL(text,'') || space(1000) || id"); + prep.execute(); + stat.execute("CHECKPOINT"); + + ResultSet rs = stat.executeQuery("SELECT sum(length(text)) FROM stuff"); + assertTrue(rs.next()); + assertEquals(3010893, rs.getInt(1)); + + eatMemory(80); + prep.execute(); + fail(); + } catch (SQLException ignore) { + } finally { + freeMemory(); + } + } + } +} diff --git a/h2/src/test/org/h2/test/db/TestPersistentCommonTableExpressions.java b/h2/src/test/org/h2/test/db/TestPersistentCommonTableExpressions.java new file mode 100644 index 0000000..e020fbc --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestPersistentCommonTableExpressions.java @@ -0,0 +1,253 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import org.h2.test.TestBase; + +/** + * Test persistent common table expressions queries using WITH. + */ +public class TestPersistentCommonTableExpressions extends AbstractBaseForCommonTableExpressions { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + // persistent cte tests - also tests reconnects and database reloading... + testRecursiveTable(); + testPersistentNonRecursiveTableInCreateView(); + testPersistentRecursiveTableInCreateView(); + testPersistentNonRecursiveTableInCreateViewDropAllObjects(); + testPersistentRecursiveTableInCreateViewDropAllObjects(); + } + + private void testRecursiveTable() throws Exception { + String[] expectedRowData = new String[]{"|meat|null", "|fruit|3", "|veg|2"}; + String[] expectedColumnTypes = new String[]{"CHARACTER VARYING", "NUMERIC"}; + String[] expectedColumnNames = new String[]{"VAL", + "SUM((SELECT\n" + + " X\n" + + "FROM PUBLIC.\"\" BB\n" + + "WHERE BB.A IS NOT DISTINCT FROM A.VAL))"}; + + String setupSQL = + "DROP TABLE IF EXISTS A; " + +"DROP TABLE IF EXISTS B; " + +"DROP TABLE IF EXISTS C; " + +"CREATE TABLE A(VAL VARCHAR(255)); " + +"CREATE TABLE B(A VARCHAR(255), VAL VARCHAR(255)); " + +"CREATE TABLE C(B VARCHAR(255), VAL VARCHAR(255)); " + +" " + +"INSERT INTO A VALUES('fruit'); " + +"INSERT INTO B VALUES('fruit','apple'); " + +"INSERT INTO B VALUES('fruit','banana'); " + +"INSERT INTO C VALUES('apple', 'golden delicious');" + +"INSERT INTO C VALUES('apple', 'granny smith'); " + +"INSERT INTO C VALUES('apple', 'pippin'); " + +"INSERT INTO A VALUES('veg'); " + +"INSERT INTO B VALUES('veg', 'carrot'); " + +"INSERT INTO C VALUES('carrot', 'nantes'); " + +"INSERT INTO C VALUES('carrot', 'imperator'); " + +"INSERT INTO C VALUES(null, 'banapple'); " + +"INSERT INTO A VALUES('meat'); "; + + String withQuery = "WITH BB as (SELECT \n" + + "sum(1) as X, \n" + + "a \n" + + "FROM B \n" + + "JOIN C ON B.val=C.b \n" + + "GROUP BY a) \n" + + "SELECT \n" + + "A.val, \n" + + "sum((SELECT X FROM BB WHERE BB.a IS NOT DISTINCT FROM A.val))\n" + + "FROM A \n" + "GROUP BY A.val"; + int maxRetries = 3; + int expectedNumberOfRows = expectedRowData.length; + + testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL, + withQuery, maxRetries - 1, expectedColumnTypes, true); + + } + + private void testPersistentRecursiveTableInCreateView() throws Exception { + String setupSQL = "--SET TRACE_LEVEL_SYSTEM_OUT 3;\n" + +"DROP TABLE IF EXISTS my_tree; \n" + +"DROP VIEW IF EXISTS v_my_tree; \n" + +"CREATE TABLE my_tree ( \n" + +" id INTEGER, \n" + +" parent_fk INTEGER \n" + +"); \n" + +" \n" + +"INSERT INTO my_tree ( id, parent_fk) VALUES ( 1, NULL ); \n" + +"INSERT INTO my_tree ( id, parent_fk) VALUES ( 11, 1 ); \n" + +"INSERT INTO my_tree ( id, parent_fk) VALUES ( 111, 11 ); \n" + +"INSERT INTO my_tree ( id, parent_fk) VALUES ( 12, 1 ); \n" + +"INSERT INTO my_tree ( id, parent_fk) VALUES ( 121, 12 ); \n" + +" \n" + +"CREATE OR REPLACE VIEW v_my_tree AS \n" + +"WITH RECURSIVE tree_cte (sub_tree_root_id, tree_level, parent_fk, child_fk) AS ( \n" + +" SELECT mt.ID AS sub_tree_root_id, CAST(0 AS INT) AS tree_level, mt.parent_fk, mt.id \n" + +" FROM my_tree mt \n" + +" UNION ALL \n" + +" SELECT sub_tree_root_id, mtc.tree_level + 1 AS tree_level, mtc.parent_fk, mt.id \n" + +" FROM my_tree mt \n" + +"INNER JOIN tree_cte mtc ON mtc.child_fk = mt.parent_fk \n" + +"), \n" + +"unused_cte AS ( SELECT 1 AS unUsedColumn ) \n" + +"SELECT sub_tree_root_id, tree_level, parent_fk, child_fk FROM tree_cte; \n"; + + String withQuery = "SELECT * FROM v_my_tree"; + int maxRetries = 4; + String[] expectedRowData = new String[]{"|1|0|null|1", + "|11|0|1|11", + "|111|0|11|111", + "|12|0|1|12", + "|121|0|12|121", + "|1|1|null|11", + "|11|1|1|111", + "|1|1|null|12", + "|12|1|1|121", + "|1|2|null|111", + "|1|2|null|121" + }; + String[] expectedColumnNames = new String[]{"SUB_TREE_ROOT_ID", "TREE_LEVEL", "PARENT_FK", "CHILD_FK"}; + String[] expectedColumnTypes = new String[]{"INTEGER", "INTEGER", "INTEGER", "INTEGER"}; + int expectedNumberOfRows = 11; + testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL, + withQuery, maxRetries - 1, expectedColumnTypes, false); + } + + private void testPersistentNonRecursiveTableInCreateView() throws Exception { + String setupSQL = "" + +"DROP VIEW IF EXISTS v_my_nr_tree; \n" + +"DROP TABLE IF EXISTS my_table; \n" + +"CREATE TABLE my_table ( \n" + +" id INTEGER, \n" + +" parent_fk INTEGER \n" + +"); \n" + +" \n" + +"INSERT INTO my_table ( id, parent_fk) VALUES ( 1, NULL ); \n" + +"INSERT INTO my_table ( id, parent_fk) VALUES ( 11, 1 ); \n" + +"INSERT INTO my_table ( id, parent_fk) VALUES ( 111, 11 ); \n" + +"INSERT INTO my_table ( id, parent_fk) VALUES ( 12, 1 ); \n" + +"INSERT INTO my_table ( id, parent_fk) VALUES ( 121, 12 ); \n" + +" \n" + +"CREATE OR REPLACE VIEW v_my_nr_tree AS \n" + +"WITH tree_cte_nr (sub_tree_root_id, tree_level, parent_fk, child_fk) AS ( \n" + +" SELECT mt.ID AS sub_tree_root_id, CAST(0 AS INT) AS tree_level, mt.parent_fk, mt.id \n" + +" FROM my_table mt \n" + +"), \n" + +"unused_cte AS ( SELECT 1 AS unUsedColumn ) \n" + +"SELECT sub_tree_root_id, tree_level, parent_fk, child_fk FROM tree_cte_nr; \n"; + + String withQuery = "SELECT * FROM v_my_nr_tree"; + int maxRetries = 6; + String[] expectedRowData = new String[]{ + "|1|0|null|1", + "|11|0|1|11", + "|111|0|11|111", + "|12|0|1|12", + "|121|0|12|121", + }; + String[] expectedColumnNames = new String[]{"SUB_TREE_ROOT_ID", "TREE_LEVEL", "PARENT_FK", "CHILD_FK"}; + String[] expectedColumnTypes = new String[]{"INTEGER", "INTEGER", "INTEGER", "INTEGER"}; + int expectedNumberOfRows = 5; + testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL, + withQuery, maxRetries - 1, expectedColumnTypes, false); + } + + private void testPersistentNonRecursiveTableInCreateViewDropAllObjects() throws Exception { + String setupSQL = "" + +"DROP ALL OBJECTS; \n" + +"CREATE TABLE my_table ( \n" + +" id INTEGER, \n" + +" parent_fk INTEGER \n" + +"); \n" + +" \n" + +"INSERT INTO my_table ( id, parent_fk) VALUES ( 1, NULL ); \n" + +"INSERT INTO my_table ( id, parent_fk) VALUES ( 11, 1 ); \n" + +"INSERT INTO my_table ( id, parent_fk) VALUES ( 111, 11 ); \n" + +"INSERT INTO my_table ( id, parent_fk) VALUES ( 12, 1 ); \n" + +"INSERT INTO my_table ( id, parent_fk) VALUES ( 121, 12 ); \n" + +" \n" + +"CREATE OR REPLACE VIEW v_my_nr_tree AS \n" + +"WITH tree_cte_nr (sub_tree_root_id, tree_level, parent_fk, child_fk) AS ( \n" + +" SELECT mt.ID AS sub_tree_root_id, CAST(0 AS INT) AS tree_level, mt.parent_fk, mt.id \n" + +" FROM my_table mt \n" + +"), \n" + +"unused_cte AS ( SELECT 1 AS unUsedColumn ) \n" + +"SELECT sub_tree_root_id, tree_level, parent_fk, child_fk FROM tree_cte_nr; \n"; + + String withQuery = "SELECT * FROM v_my_nr_tree"; + int maxRetries = 6; + String[] expectedRowData = new String[]{ + "|1|0|null|1", + "|11|0|1|11", + "|111|0|11|111", + "|12|0|1|12", + "|121|0|12|121", + }; + String[] expectedColumnNames = new String[]{"SUB_TREE_ROOT_ID", "TREE_LEVEL", "PARENT_FK", "CHILD_FK"}; + String[] expectedColumnTypes = new String[]{"INTEGER", "INTEGER", "INTEGER", "INTEGER"}; + int expectedNumberOfRows = 5; + testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL, + withQuery, maxRetries - 1, expectedColumnTypes, false); + } + + private void testPersistentRecursiveTableInCreateViewDropAllObjects() throws Exception { + String setupSQL = "--SET TRACE_LEVEL_SYSTEM_OUT 3;\n" + +"DROP ALL OBJECTS; \n" + +"CREATE TABLE my_tree ( \n" + +" id INTEGER, \n" + +" parent_fk INTEGER \n" + +"); \n" + +" \n" + +"INSERT INTO my_tree ( id, parent_fk) VALUES ( 1, NULL ); \n" + +"INSERT INTO my_tree ( id, parent_fk) VALUES ( 11, 1 ); \n" + +"INSERT INTO my_tree ( id, parent_fk) VALUES ( 111, 11 ); \n" + +"INSERT INTO my_tree ( id, parent_fk) VALUES ( 12, 1 ); \n" + +"INSERT INTO my_tree ( id, parent_fk) VALUES ( 121, 12 ); \n" + +" \n" + +"CREATE OR REPLACE VIEW v_my_tree AS \n" + +"WITH RECURSIVE tree_cte (sub_tree_root_id, tree_level, parent_fk, child_fk) AS ( \n" + +" SELECT mt.ID AS sub_tree_root_id, CAST(0 AS INT) AS tree_level, mt.parent_fk, mt.id \n" + +" FROM my_tree mt \n" + +" UNION ALL \n" + +" SELECT sub_tree_root_id, mtc.tree_level + 1 AS tree_level, mtc.parent_fk, mt.id \n" + +" FROM my_tree mt \n" + +"INNER JOIN tree_cte mtc ON mtc.child_fk = mt.parent_fk \n" + +"), \n" + +"unused_cte AS ( SELECT 1 AS unUsedColumn ) \n" + +"SELECT sub_tree_root_id, tree_level, parent_fk, child_fk FROM tree_cte; \n"; + + String withQuery = "SELECT * FROM v_my_tree"; + int maxRetries = 4; + String[] expectedRowData = new String[]{"|1|0|null|1", + "|11|0|1|11", + "|111|0|11|111", + "|12|0|1|12", + "|121|0|12|121", + "|1|1|null|11", + "|11|1|1|111", + "|1|1|null|12", + "|12|1|1|121", + "|1|2|null|111", + "|1|2|null|121" + }; + String[] expectedColumnNames = new String[]{"SUB_TREE_ROOT_ID", "TREE_LEVEL", "PARENT_FK", "CHILD_FK"}; + String[] expectedColumnTypes = new String[]{"INTEGER", "INTEGER", "INTEGER", "INTEGER"}; + int expectedNumberOfRows = 11; + testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumberOfRows, setupSQL, + withQuery, maxRetries - 1, expectedColumnTypes, false); + } +} diff --git a/h2/src/test/org/h2/test/db/TestPowerOff.java b/h2/src/test/org/h2/test/db/TestPowerOff.java new file mode 100644 index 0000000..e1f5e67 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestPowerOff.java @@ -0,0 +1,356 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Random; + +import org.h2.api.ErrorCode; +import org.h2.engine.Database; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.JdbcUtils; + +/** + * Tests simulated power off conditions. + */ +public class TestPowerOff extends TestDb { + + private static final String DB_NAME = "powerOff"; + private String dir, url; + + private int maxPowerOffCount; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + if (config.memory) { + return false; + } + return true; + } + + @Override + public void test() throws SQLException { + if (config.big || config.googleAppEngine) { + dir = getBaseDir(); + url = DB_NAME; + } else { + dir = "memFS:"; + url = "memFS:/" + DB_NAME; + } + url += ";FILE_LOCK=NO;TRACE_LEVEL_FILE=0"; + testLobCrash(); + testSummaryCrash(); + testCrash(); + testShutdown(); + testMemoryTables(); + testPersistentTables(); + deleteDb(dir, DB_NAME); + } + + private void testLobCrash() throws SQLException { + if (config.networked) { + return; + } + deleteDb(dir, DB_NAME); + Connection conn = getConnection(url); + Statement stat = conn.createStatement(); + stat.execute("create table test(id identity, data clob)"); + conn.close(); + conn = getConnection(url); + stat = conn.createStatement(); + stat.execute("set write_delay 0"); + setPowerOffCount(conn, Integer.MAX_VALUE); + stat.execute("insert into test(data) values space(11000)"); + int max = Integer.MAX_VALUE - getPowerOffCount(conn); + for (int i = 0; i < max + 10; i++) { + conn.close(); + conn = getConnection(url); + stat = conn.createStatement(); + stat.execute("insert into test(data) values space(11000)"); + stat.execute("set write_delay 0"); + setPowerOffCount(conn, i); + try { + stat.execute("insert into test(data) values space(11000)"); + } catch (SQLException e) { + // ignore + } + JdbcUtils.closeSilently(conn); + } + } + + private void testSummaryCrash() throws SQLException { + if (config.networked) { + return; + } + deleteDb(dir, DB_NAME); + Connection conn = getConnection(url); + Statement stat = conn.createStatement(); + for (int i = 0; i < 10; i++) { + stat.execute("CREATE TABLE TEST" + i + + "(ID INT PRIMARY KEY, NAME VARCHAR)"); + for (int j = 0; j < 10; j++) { + stat.execute("INSERT INTO TEST" + i + + " VALUES(" + j + ", 'Hello')"); + } + } + for (int i = 0; i < 10; i += 2) { + stat.execute("DROP TABLE TEST" + i); + } + stat.execute("SET WRITE_DELAY 0"); + stat.execute("CHECKPOINT"); + for (int j = 0; j < 10; j++) { + stat.execute("INSERT INTO TEST1 VALUES(" + (10 + j) + ", 'World')"); + } + stat.execute("SHUTDOWN IMMEDIATELY"); + JdbcUtils.closeSilently(conn); + conn = getConnection(url); + stat = conn.createStatement(); + for (int i = 1; i < 10; i += 2) { + ResultSet rs = stat.executeQuery( + "SELECT * FROM TEST" + i + " ORDER BY ID"); + for (int j = 0; j < 10; j++) { + rs.next(); + assertEquals(j, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + } + if (i == 1) { + for (int j = 0; j < 10; j++) { + rs.next(); + assertEquals(j + 10, rs.getInt(1)); + assertEquals("World", rs.getString(2)); + } + } + assertFalse(rs.next()); + } + conn.close(); + } + + private void testCrash() throws SQLException { + if (config.networked) { + return; + } + deleteDb(dir, DB_NAME); + Random random = new Random(1); + int repeat = getSize(1, 20); + for (int i = 0; i < repeat; i++) { + Connection conn = getConnection(url); + conn.close(); + conn = getConnection(url); + Statement stat = conn.createStatement(); + stat.execute("SET WRITE_DELAY 0"); + setPowerOffCount(conn, random.nextInt(100)); + try { + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST" + + "(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + conn.setAutoCommit(false); + int len = getSize(3, 100); + for (int j = 0; j < len; j++) { + stat.execute("INSERT INTO TEST VALUES(" + j + ", 'Hello')"); + if (random.nextInt(5) == 0) { + conn.commit(); + } + if (random.nextInt(10) == 0) { + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST" + + "(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + } + } + stat.execute("DROP TABLE IF EXISTS TEST"); + conn.close(); + } catch (SQLException e) { + if (!e.getSQLState().equals("90098")) { + TestBase.logError("power", e); + } + } + } + } + + private void testShutdown() throws SQLException { + deleteDb(dir, DB_NAME); + Connection conn = getConnection(url); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST" + + "(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + stat.execute("INSERT INTO TEST VALUES(1, 'Hello')"); + stat.execute("SHUTDOWN"); + conn.close(); + + conn = getConnection(url); + stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("SELECT * FROM TEST"); + assertTrue(rs.next()); + assertFalse(rs.next()); + conn.close(); + } + + private void testMemoryTables() throws SQLException { + if (config.networked) { + return; + } + deleteDb(dir, DB_NAME); + + Connection conn = getConnection(url); + Statement stat = conn.createStatement(); + stat.execute("CREATE MEMORY TABLE TEST" + + "(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + stat.execute("INSERT INTO TEST VALUES(1, 'Hello')"); + stat.execute("CHECKPOINT"); + setPowerOffCount(conn, 1); + try { + stat.execute("INSERT INTO TEST VALUES(2, 'Hello')"); + stat.execute("INSERT INTO TEST VALUES(3, 'Hello')"); + stat.execute("CHECKPOINT"); + fail(); + } catch (SQLException e) { + assertKnownException(e); + } + + setPowerOffCount(conn, 0); + try { + conn.close(); + } catch (SQLException e) { + // ignore + } + conn = getConnection(url); + stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("SELECT COUNT(*) FROM TEST"); + rs.next(); + assertEquals(1, rs.getInt(1)); + conn.close(); + } + + private void testPersistentTables() throws SQLException { + if (config.networked) { + return; + } + if (config.cipher != null) { + // this would take too long (setLength uses + // individual writes, many thousand operations) + return; + } + deleteDb(dir, DB_NAME); + + // ((JdbcConnection)conn).setPowerOffCount(Integer.MAX_VALUE); + testRun(true); + int max = maxPowerOffCount; + trace("max=" + max); + runTest(0, max, true); + recoverAndCheckConsistency(); + runTest(0, max, false); + recoverAndCheckConsistency(); + } + + private void runTest(int min, int max, boolean withConsistencyCheck) + throws SQLException { + for (int i = min; i < max; i++) { + deleteDb(dir, DB_NAME); + Database.setInitialPowerOffCount(i); + int expect = testRun(false); + if (withConsistencyCheck) { + int got = recoverAndCheckConsistency(); + trace("test " + i + " of " + max + " expect=" + expect + " got=" + got); + } else { + trace("test " + i + " of " + max + " expect=" + expect); + } + } + Database.setInitialPowerOffCount(0); + } + + private int testRun(boolean init) throws SQLException { + if (init) { + Database.setInitialPowerOffCount(Integer.MAX_VALUE); + } + int state = 0; + Connection conn = null; + try { + conn = getConnection(url); + Statement stat = conn.createStatement(); + stat.execute("SET WRITE_DELAY 0"); + stat.execute("CREATE TABLE IF NOT EXISTS TEST" + + "(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + state = 1; + conn.setAutoCommit(false); + stat.execute("INSERT INTO TEST VALUES(1, 'Hello')"); + stat.execute("INSERT INTO TEST VALUES(2, 'World')"); + conn.commit(); + state = 2; + stat.execute("UPDATE TEST SET NAME='Hallo' WHERE ID=1"); + stat.execute("UPDATE TEST SET NAME='Welt' WHERE ID=2"); + conn.commit(); + state = 3; + stat.execute("DELETE FROM TEST WHERE ID=1"); + stat.execute("DELETE FROM TEST WHERE ID=2"); + conn.commit(); + state = 1; + stat.execute("DROP TABLE TEST"); + state = 0; + if (init) { + maxPowerOffCount = Integer.MAX_VALUE - getPowerOffCount(conn); + } + conn.close(); + } catch (SQLException e) { + if (e.getSQLState().equals("" + ErrorCode.DATABASE_IS_CLOSED)) { + // this is ok + } else { + throw e; + } + } + JdbcUtils.closeSilently(conn); + return state; + } + + private int recoverAndCheckConsistency() throws SQLException { + int state; + Database.setInitialPowerOffCount(0); + Connection conn = getConnection(url); + assertEquals(0, getPowerOffCount(conn)); + Statement stat = conn.createStatement(); + DatabaseMetaData meta = conn.getMetaData(); + ResultSet rs = meta.getTables(null, null, "TEST", null); + if (!rs.next()) { + state = 0; + } else { + // table does not exist + rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + if (!rs.next()) { + state = 1; + } else { + assertEquals(1, rs.getInt(1)); + String name1 = rs.getString(2); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + String name2 = rs.getString(2); + assertFalse(rs.next()); + if ("Hello".equals(name1)) { + assertEquals("World", name2); + state = 2; + } else { + assertEquals("Hallo", name1); + assertEquals("Welt", name2); + state = 3; + } + } + } + conn.close(); + return state; + } + +} diff --git a/h2/src/test/org/h2/test/db/TestQueryCache.java b/h2/src/test/org/h2/test/db/TestQueryCache.java new file mode 100644 index 0000000..476bc65 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestQueryCache.java @@ -0,0 +1,110 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests the query cache. + */ +public class TestQueryCache extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb("queryCache"); + test1(); + testClearingCacheWithTableStructureChanges(); + deleteDb("queryCache"); + } + + private void test1() throws Exception { + try (Connection conn = getConnection("queryCache;QUERY_CACHE_SIZE=10")) { + Statement stat = conn.createStatement(); + stat.execute("create table test(id int, name varchar)"); + PreparedStatement prep; + // query execution may be fast here but the parsing must be slow + StringBuilder queryBuilder = new StringBuilder("select count(*) from test t1 where \n"); + for (int i = 0; i < 1000; i++) { + if (i != 0) { + queryBuilder.append(" and "); + } + queryBuilder.append(" TIMESTAMP '2005-12-31 23:59:59' = TIMESTAMP '2005-12-31 23:59:59' "); + } + String query = queryBuilder.toString(); + conn.prepareStatement(query); + int firstGreater = 0; + int firstSmaller = 0; + long time; + ResultSet rs; + long first = 0; + // 1000 iterations to warm up and avoid JIT effects + for (int i = 0; i < 1005; i++) { + // this should both ensure results are not re-used + // stat.execute("set mode regular"); + // stat.execute("create table x()"); + // stat.execute("drop table x"); + time = System.nanoTime(); + prep = conn.prepareStatement(query); + execute(prep); + prep.close(); + rs = stat.executeQuery(query); + rs.next(); + int c = rs.getInt(1); + rs.close(); + assertEquals(0, c); + time = System.nanoTime() - time; + if (i == 1000) { + // take from cache and do not close, + // so that next iteration will have a cache miss + prep = conn.prepareStatement(query); + } else if (i == 1001) { + first = time; + } else if (i > 1001) { + if (first > time) { + firstGreater++; + } else { + firstSmaller++; + } + } + } + // first prepare time must be always greater because of query cache, + // but JVM is too unpredictable to assert that, so just check that + // usually this is true + assertSmaller(firstSmaller, firstGreater); + stat.execute("drop table test"); + } + } + + private void testClearingCacheWithTableStructureChanges() throws Exception { + try (Connection conn = getConnection("queryCache;QUERY_CACHE_SIZE=10")) { + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1, conn). + prepareStatement("SELECT * FROM TEST"); + Statement stat = conn.createStatement(); + stat.executeUpdate("CREATE TABLE TEST(col1 bigint, col2 varchar(255))"); + PreparedStatement prep = conn.prepareStatement("SELECT * FROM TEST"); + prep.close(); + stat.executeUpdate("DROP TABLE TEST"); + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1, conn). + prepareStatement("SELECT * FROM TEST"); + } + } +} diff --git a/h2/src/test/org/h2/test/db/TestReadOnly.java b/h2/src/test/org/h2/test/db/TestReadOnly.java new file mode 100644 index 0000000..84bc97b --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestReadOnly.java @@ -0,0 +1,207 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.io.File; +import java.io.RandomAccessFile; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; + +import org.h2.api.ErrorCode; +import org.h2.dev.fs.FilePathZip2; +import org.h2.store.FileLister; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.Backup; +import org.h2.tools.Server; + +/** + * Test for the read-only database feature. + */ +public class TestReadOnly extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + if (config.memory) { + return false; + } + return true; + } + + @Override + public void test() throws Exception { + testReadOnlyInZip(); + testReadOnlyTempTableResult(); + testReadOnlyConnect(); + testReadOnlyDbCreate(); + if (!config.googleAppEngine) { + testReadOnlyFiles(true); + } + testReadOnlyFiles(false); + } + + private void testReadOnlyInZip() throws SQLException { + if (config.cipher != null) { + return; + } + deleteDb("readonlyInZip"); + String dir = getBaseDir(); + Connection conn = getConnection("readonlyInZip"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT) AS " + + "SELECT X FROM SYSTEM_RANGE(1, 20)"); + conn.close(); + Backup.execute(dir + "/readonly.zip", dir, "readonlyInZip", true); + conn = getConnection( + "jdbc:h2:zip:"+dir+"/readonly.zip!/readonlyInZip", getUser(), getPassword()); + conn.createStatement().execute("select * from test where id=1"); + conn.close(); + Server server = Server.createTcpServer("-baseDir", dir); + server.start(); + int port = server.getPort(); + try { + conn = getConnection( + "jdbc:h2:tcp://localhost:" + port + "/zip:readonly.zip!/readonlyInZip", + getUser(), getPassword()); + conn.createStatement().execute("select * from test where id=1"); + conn.close(); + FilePathZip2.register(); + conn = getConnection( + "jdbc:h2:tcp://localhost:" + port + "/zip2:readonly.zip!/readonlyInZip", + getUser(), getPassword()); + conn.createStatement().execute("select * from test where id=1"); + conn.close(); + } finally { + server.stop(); + } + deleteDb("readonlyInZip"); + } + + private void testReadOnlyTempTableResult() throws SQLException { + deleteDb("readonlyTemp"); + Connection conn = getConnection("readonlyTemp"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT) AS " + + "SELECT X FROM SYSTEM_RANGE(1, 20)"); + conn.close(); + conn = getConnection( + "readonlyTemp;ACCESS_MODE_DATA=r;" + + "MAX_MEMORY_ROWS=10"); + stat = conn.createStatement(); + stat.execute("SELECT DISTINCT ID FROM TEST"); + conn.close(); + deleteDb("readonlyTemp"); + } + + private void testReadOnlyDbCreate() throws SQLException { + deleteDb("readonlyDbCreate"); + Connection conn = getConnection("readonlyDbCreate"); + Statement stat = conn.createStatement(); + stat.execute("create table a(id int)"); + stat.execute("create index ai on a(id)"); + conn.close(); + conn = getConnection("readonlyDbCreate;ACCESS_MODE_DATA=r"); + stat = conn.createStatement(); + stat.execute("create table if not exists a(id int)"); + stat.execute("create index if not exists ai on a(id)"); + assertThrows(ErrorCode.DATABASE_IS_READ_ONLY, stat). + execute("CREATE TABLE TEST(ID INT)"); + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, stat). + execute("SELECT * FROM TEST"); + stat.execute("create local temporary linked table test(" + + "null, 'jdbc:h2:mem:test3', 'sa', 'sa', 'INFORMATION_SCHEMA.TABLES')"); + ResultSet rs = stat.executeQuery("select * from test"); + assertTrue(rs.next()); + conn.close(); + } + + private void testReadOnlyFiles(boolean setReadOnly) throws Exception { + new File(System.getProperty("java.io.tmpdir")).mkdirs(); + File f = File.createTempFile("test", "temp"); + assertTrue(f.canWrite()); + f.setReadOnly(); + assertFalse(f.canWrite()); + f.delete(); + + f = File.createTempFile("test", "temp"); + RandomAccessFile r = new RandomAccessFile(f, "rw"); + r.write(1); + f.setReadOnly(); + r.close(); + assertFalse(f.canWrite()); + f.delete(); + + deleteDb("readonlyFiles"); + Connection conn = getConnection("readonlyFiles"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + stat.execute("INSERT INTO TEST VALUES(1, 'Hello')"); + stat.execute("INSERT INTO TEST VALUES(2, 'World')"); + assertFalse(conn.isReadOnly()); + conn.close(); + + if (setReadOnly) { + setReadOnly(); + conn = getConnection("readonlyFiles"); + } else { + conn = getConnection("readonlyFiles;ACCESS_MODE_DATA=r"); + } + assertTrue(conn.isReadOnly()); + stat = conn.createStatement(); + stat.execute("SELECT * FROM TEST"); + assertThrows(ErrorCode.DATABASE_IS_READ_ONLY, stat). + execute("DELETE FROM TEST"); + conn.close(); + + if (setReadOnly) { + conn = getConnection( + "readonlyFiles;DB_CLOSE_DELAY=1"); + } else { + conn = getConnection( + "readonlyFiles;DB_CLOSE_DELAY=1;ACCESS_MODE_DATA=r"); + } + stat = conn.createStatement(); + stat.execute("SELECT * FROM TEST"); + assertThrows(ErrorCode.DATABASE_IS_READ_ONLY, stat). + execute("DELETE FROM TEST"); + stat.execute("SET DB_CLOSE_DELAY=0"); + conn.close(); + } + + private void setReadOnly() { + ArrayList list = FileLister.getDatabaseFiles( + getBaseDir(), "readonlyFiles", true); + for (String fileName : list) { + FileUtils.setReadOnly(fileName); + } + } + + private void testReadOnlyConnect() throws SQLException { + deleteDb("readonlyConnect"); + Connection conn = getConnection("readonlyConnect;OPEN_NEW=TRUE"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id identity)"); + stat.execute("insert into test select x from system_range(1, 11)"); + assertThrows(ErrorCode.DATABASE_ALREADY_OPEN_1, + () -> getConnection("readonlyConnect;ACCESS_MODE_DATA=r;OPEN_NEW=TRUE")); + conn.close(); + deleteDb("readonlyConnect"); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestRecursiveQueries.java b/h2/src/test/org/h2/test/db/TestRecursiveQueries.java new file mode 100644 index 0000000..2a8d27a --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestRecursiveQueries.java @@ -0,0 +1,183 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; +import java.sql.Types; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Test recursive queries using WITH. + */ +public class TestRecursiveQueries extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testWrongLinkLargeResult(); + testSimpleUnionAll(); + testSimpleUnion(); + } + + private void testWrongLinkLargeResult() throws Exception { + deleteDb("recursiveQueries"); + Connection conn = getConnection("recursiveQueries"); + Statement stat; + stat = conn.createStatement(); + stat.execute("create table test(parent varchar(255), child varchar(255))"); + stat.execute("insert into test values('/', 'a'), ('a', 'b1'), " + + "('a', 'b2'), ('a', 'c'), ('c', 'd1'), ('c', 'd2')"); + + ResultSet rs = stat.executeQuery( + "with recursive rec_test(depth, parent, child) as (" + + "select 0, parent, child from test where parent = '/' " + + "union all " + + "select depth+1, r.parent, r.child from test i join rec_test r " + + "on (i.parent = r.child) where depth<9 " + + ") select count(*) from rec_test"); + rs.next(); + assertEquals(29524, rs.getInt(1)); + stat.execute("with recursive rec_test(depth, parent, child) as ( "+ + "select 0, parent, child from test where parent = '/' "+ + "union all "+ + "select depth+1, i.parent, i.child from test i join rec_test r "+ + "on (r.child = i.parent) where depth<10 "+ + ") select * from rec_test"); + conn.close(); + deleteDb("recursiveQueries"); + } + + private void testSimpleUnionAll() throws Exception { + deleteDb("recursiveQueries"); + Connection conn = getConnection("recursiveQueries"); + Statement stat; + PreparedStatement prep, prep2; + ResultSet rs; + + stat = conn.createStatement(); + rs = stat.executeQuery("with recursive t(n) as " + + "(select 1 union all select n+1 from t where n<3) " + + "select * from t"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertTrue(rs.next()); + assertEquals(3, rs.getInt(1)); + assertFalse(rs.next()); + + prep = conn.prepareStatement("with recursive t(n) as " + + "(select 1 union all select n+1 from t where n<3) " + + "select * from t where n>?"); + prep.setInt(1, 2); + rs = prep.executeQuery(); + assertTrue(rs.next()); + assertEquals(3, rs.getInt(1)); + assertFalse(rs.next()); + + prep.setInt(1, 1); + rs = prep.executeQuery(); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertTrue(rs.next()); + assertEquals(3, rs.getInt(1)); + assertFalse(rs.next()); + + prep = conn.prepareStatement("with recursive t(n) as " + + "(select @start union all select n+@inc from t where n<@end_index) " + + "select * from t"); + prep2 = conn.prepareStatement("select @start:=?, @inc:=?, @end_index:=?"); + prep2.setInt(1, 10); + prep2.setInt(2, 2); + prep2.setInt(3, 14); + assertTrue(prep2.executeQuery().next()); + rs = prep.executeQuery(); + assertTrue(rs.next()); + assertEquals(10, rs.getInt(1)); + assertTrue(rs.next()); + assertEquals(12, rs.getInt(1)); + assertTrue(rs.next()); + assertEquals(14, rs.getInt(1)); + assertFalse(rs.next()); + + prep2.setInt(1, 100); + prep2.setInt(2, 3); + prep2.setInt(3, 103); + assertTrue(prep2.executeQuery().next()); + rs = prep.executeQuery(); + assertTrue(rs.next()); + assertEquals(100, rs.getInt(1)); + assertTrue(rs.next()); + assertEquals(103, rs.getInt(1)); + assertFalse(rs.next()); + + prep = conn.prepareStatement("with recursive t(n) as " + + "(select ? union all select n+? from t where n getConnection("rights")); + } + } + +// public void testLowerCaseUser() throws SQLException { + // Documentation: for compatibility, + // only unquoted or uppercase user names are allowed. +// deleteDb("rights"); +// Connection conn = getConnection("rights"); +// stat = conn.createStatement(); +// stat.execute("CREATE USER \"TEST1\" PASSWORD 'abc'"); +// stat.execute("CREATE USER \"Test2\" PASSWORD 'abc'"); +// conn.close(); +// conn = getConnection("rights", "TEST1", "abc"); +// conn.close(); +// conn = getConnection("rights", "Test2", "abc"); +// conn.close(); +// } + + private void testGetTables() throws SQLException { + deleteDb("rights"); + Connection conn = getConnection("rights"); + stat = conn.createStatement(); + + stat.execute("CREATE USER IF NOT EXISTS TEST PASSWORD 'TEST'"); + stat.execute("CREATE TABLE TEST(ID INT)"); + stat.execute("GRANT ALL ON TABLE TEST TO TEST"); + Connection conn2 = getConnection("rights", "TEST", getPassword("TEST")); + DatabaseMetaData meta = conn2.getMetaData(); + meta.getTables(null, null, "%", new String[]{"TABLE", "VIEW", "SEQUENCE"}); + conn2.close(); + conn.close(); + } + + private void testDropTempTables() throws SQLException { + deleteDb("rights"); + Connection conn = getConnection("rights"); + stat = conn.createStatement(); + stat.execute("CREATE USER IF NOT EXISTS READER PASSWORD 'READER'"); + stat.execute("CREATE TABLE TEST(ID INT)"); + Connection conn2 = getConnection("rights", "READER", getPassword("READER")); + Statement stat2 = conn2.createStatement(); + assertThrows(ErrorCode.NOT_ENOUGH_RIGHTS_FOR_1, stat2). + execute("SELECT * FROM TEST"); + stat2.execute("CREATE LOCAL TEMPORARY TABLE IF NOT EXISTS MY_TEST(ID INT)"); + stat2.execute("INSERT INTO MY_TEST VALUES(1)"); + stat2.execute("SELECT * FROM MY_TEST"); + stat2.execute("DROP TABLE MY_TEST"); + conn2.close(); + conn.close(); + } + + private void testSchemaRenameUser() throws SQLException { + if (config.memory) { + return; + } + deleteDb("rights"); + Connection conn = getConnection("rights"); + stat = conn.createStatement(); + stat.execute("create user test password ''"); + stat.execute("create schema b authorization test"); + stat.execute("create table b.test(id int)"); + stat.execute("alter user test rename to test1"); + conn.close(); + conn = getConnection("rights"); + stat = conn.createStatement(); + stat.execute("select * from b.test"); + assertThrows(ErrorCode.CANNOT_DROP_2, stat). + execute("drop user test1"); + stat.execute("drop schema b cascade"); + stat.execute("drop user test1"); + conn.close(); + } + + private void testSchemaAdminRole() throws SQLException { + if (config.memory) { + return; + } + + deleteDb("rights"); + Connection conn = getConnection("rights"); + stat = conn.createStatement(); + // default table type + testTableType(conn, "MEMORY"); + testTableType(conn, "CACHED"); + + /* make sure admin can still do it. */ + + executeSuccess("CREATE USER SCHEMA_CREATOR PASSWORD 'xyz'"); + + executeSuccess("CREATE SCHEMA SCHEMA_RIGHT_TEST"); + executeSuccess("ALTER SCHEMA SCHEMA_RIGHT_TEST " + + "RENAME TO SCHEMA_RIGHT_TEST_RENAMED"); + executeSuccess("DROP SCHEMA SCHEMA_RIGHT_TEST_RENAMED"); + + /* create this for tests below */ + executeSuccess("CREATE SCHEMA SCHEMA_RIGHT_TEST_EXISTS"); + executeSuccess("CREATE TABLE SCHEMA_RIGHT_TEST_EXISTS.TEST_EXISTS" + + "(ID INT PRIMARY KEY, NAME VARCHAR)"); + conn.close(); + + String url = "rights"; + + // try and fail (no rights yet) + conn = getConnection(url, "SCHEMA_CREATOR", getPassword("xyz")); + stat = conn.createStatement(); + assertThrows(ErrorCode.ADMIN_RIGHTS_REQUIRED, stat).execute( + "CREATE SCHEMA SCHEMA_RIGHT_TEST_WILL_FAIL"); + assertThrows(ErrorCode.ADMIN_RIGHTS_REQUIRED, stat).execute( + "ALTER SCHEMA SCHEMA_RIGHT_TEST_EXISTS RENAME TO SCHEMA_RIGHT_TEST_WILL_FAIL"); + assertThrows(ErrorCode.NOT_ENOUGH_RIGHTS_FOR_1, stat).execute( + "DROP SCHEMA SCHEMA_RIGHT_TEST_EXISTS"); + conn.close(); + + // grant the right + conn = getConnection("rights"); + stat = conn.createStatement(); + executeSuccess("GRANT ALTER ANY SCHEMA TO SCHEMA_CREATOR"); + conn.close(); + + // try and succeed + conn = getConnection(url, "SCHEMA_CREATOR", getPassword("xyz")); + stat = conn.createStatement(); + + // should be able to create a schema and manipulate tables on that + // schema... + executeSuccess("CREATE SCHEMA SCHEMA_RIGHT_TEST"); + executeSuccess("ALTER SCHEMA SCHEMA_RIGHT_TEST RENAME TO S"); + executeSuccess("CREATE TABLE S.TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + executeSuccess("ALTER TABLE S.TEST ADD COLUMN QUESTION VARCHAR"); + executeSuccess("INSERT INTO S.TEST (ID, NAME) VALUES (42, 'Adams')"); + executeSuccess("UPDATE S.TEST Set NAME = 'Douglas'"); + executeSuccess("DELETE FROM S.TEST"); + executeSuccess("DROP SCHEMA S CASCADE"); + + // ...and on other schemata + executeSuccess("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + executeSuccess("ALTER TABLE TEST ADD COLUMN QUESTION VARCHAR"); + executeSuccess("INSERT INTO TEST (ID, NAME) VALUES (42, 'Adams')"); + executeSuccess("UPDATE TEST Set NAME = 'Douglas'"); + executeSuccess("DELETE FROM TEST"); + + conn.close(); + + // revoke the right + conn = getConnection("rights"); + stat = conn.createStatement(); + executeSuccess("REVOKE ALTER ANY SCHEMA FROM SCHEMA_CREATOR"); + conn.close(); + + // try again and fail + conn = getConnection(url, "SCHEMA_CREATOR", getPassword("xyz")); + stat = conn.createStatement(); + assertThrows(ErrorCode.ADMIN_RIGHTS_REQUIRED, stat). + execute("CREATE SCHEMA SCHEMA_RIGHT_TEST"); + assertThrows(ErrorCode.ADMIN_RIGHTS_REQUIRED, stat). + execute("ALTER SCHEMA SCHEMA_RIGHT_TEST_EXISTS " + + "RENAME TO SCHEMA_RIGHT_TEST_RENAMED"); + assertThrows(ErrorCode.NOT_ENOUGH_RIGHTS_FOR_1, stat). + execute("DROP SCHEMA SCHEMA_RIGHT_TEST_EXISTS"); + assertThrows(ErrorCode.NOT_ENOUGH_RIGHTS_FOR_1, stat). + execute("CREATE TABLE SCHEMA_RIGHT_TEST_EXISTS.TEST" + + "(ID INT PRIMARY KEY, NAME VARCHAR)"); + assertThrows(ErrorCode.NOT_ENOUGH_RIGHTS_FOR_1, stat). + execute("INSERT INTO SCHEMA_RIGHT_TEST_EXISTS.TEST_EXISTS " + + "(ID, NAME) VALUES (42, 'Adams')"); + assertThrows(ErrorCode.NOT_ENOUGH_RIGHTS_FOR_1, stat). + execute("UPDATE SCHEMA_RIGHT_TEST_EXISTS.TEST_EXISTS Set NAME = 'Douglas'"); + assertThrows(ErrorCode.NOT_ENOUGH_RIGHTS_FOR_1, stat). + execute("DELETE FROM SCHEMA_RIGHT_TEST_EXISTS.TEST_EXISTS"); + conn.close(); + } + + private void testTableRename() throws SQLException { + if (config.memory) { + return; + } + deleteDb("rights"); + Connection conn = getConnection("rights"); + stat = conn.createStatement(); + stat.execute("create user test password '' admin"); + stat.execute("create schema b"); + stat.execute("create table b.t1(id int)"); + stat.execute("grant select on b.t1 to test"); + stat.execute("alter table b.t1 rename to b.t2"); + conn.close(); + conn = getConnection("rights"); + stat = conn.createStatement(); + stat.execute("drop user test"); + conn.close(); + } + + private void testSchemaRename() throws SQLException { + if (config.memory) { + return; + } + deleteDb("rights"); + Connection conn = getConnection("rights"); + stat = conn.createStatement(); + stat.execute("create user test password '' admin"); + stat.execute("create schema b"); + stat.execute("grant select on schema b to test"); + stat.execute("alter schema b rename to c"); + conn.close(); + conn = getConnection("rights"); + stat = conn.createStatement(); + stat.execute("drop user test"); + conn.close(); + } + + private void testSchemaDrop() throws SQLException { + if (config.memory) { + return; + } + deleteDb("rights"); + Connection conn = getConnection("rights"); + stat = conn.createStatement(); + stat.execute("create user test password '' admin"); + stat.execute("create schema b"); + stat.execute("grant select on schema b to test"); + stat.execute("drop schema b cascade"); + conn.close(); + conn = getConnection("rights"); + stat = conn.createStatement(); + stat.execute("drop user test"); + conn.close(); + } + + private void testAccessRights() throws SQLException { + if (config.memory) { + return; + } + + deleteDb("rights"); + Connection conn = getConnection("rights"); + stat = conn.createStatement(); + // default table type + testTableType(conn, "MEMORY"); + testTableType(conn, "CACHED"); + + // rights on tables and views + executeSuccess("CREATE USER PASS_READER PASSWORD 'abc'"); + executeSuccess("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + executeSuccess("CREATE TABLE PASS(ID INT PRIMARY KEY, " + + "NAME VARCHAR, PASSWORD VARCHAR)"); + executeSuccess("CREATE VIEW PASS_NAME AS SELECT ID, NAME FROM PASS"); + executeSuccess("GRANT SELECT ON PASS_NAME TO PASS_READER"); + executeSuccess("GRANT SELECT, INSERT, UPDATE ON TEST TO PASS_READER"); + conn.close(); + + String url = "rights"; + conn = getConnection(url, "PASS_READER", getPassword("abc")); + stat = conn.createStatement(); + executeSuccess("SELECT * FROM PASS_NAME"); + executeSuccess("SELECT * FROM (SELECT * FROM PASS_NAME)"); + executeSuccess("SELECT (SELECT NAME FROM PASS_NAME) P FROM PASS_NAME"); + executeError("SELECT (SELECT PASSWORD FROM PASS) P FROM PASS_NAME"); + executeError("SELECT * FROM PASS"); + executeError("INSERT INTO TEST SELECT 1, PASSWORD FROM PASS"); + executeError("INSERT INTO TEST VALUES(SELECT PASSWORD FROM PASS)"); + executeError("UPDATE TEST SET NAME=(SELECT PASSWORD FROM PASS)"); + executeError("DELETE FROM TEST WHERE NAME=(SELECT PASSWORD FROM PASS)"); + executeError("SELECT * FROM (SELECT * FROM PASS)"); + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, stat). + execute("CREATE VIEW X AS SELECT * FROM PASS_READER"); + assertThrows(ErrorCode.NOT_ENOUGH_RIGHTS_FOR_1, stat). + execute("CREATE VIEW X AS SELECT * FROM PASS_NAME"); + conn.close(); + + conn = getConnection("rights"); + stat = conn.createStatement(); + + executeSuccess("DROP TABLE TEST"); + executeSuccess("CREATE USER TEST PASSWORD 'abc'"); + executeSuccess("ALTER USER TEST ADMIN TRUE"); + executeSuccess("CREATE TABLE TEST(ID INT)"); + executeSuccess("CREATE SCHEMA SCHEMA_A AUTHORIZATION SA"); + executeSuccess("CREATE TABLE SCHEMA_A.TABLE_B(ID INT)"); + executeSuccess("GRANT ALL ON SCHEMA_A.TABLE_B TO TEST"); + executeSuccess("CREATE TABLE HIDDEN(ID INT)"); + executeSuccess("CREATE TABLE PUB_TABLE(ID INT)"); + executeSuccess("CREATE TABLE ROLE_TABLE(ID INT)"); + executeSuccess("CREATE ROLE TEST_ROLE"); + executeSuccess("GRANT SELECT ON ROLE_TABLE TO TEST_ROLE"); + executeSuccess("GRANT UPDATE ON ROLE_TABLE TO TEST_ROLE"); + executeSuccess("REVOKE UPDATE ON ROLE_TABLE FROM TEST_ROLE"); + assertThrows(ErrorCode.ROLES_AND_RIGHT_CANNOT_BE_MIXED, stat). + execute("REVOKE SELECT, SUB1 ON ROLE_TABLE FROM TEST_ROLE"); + executeSuccess("GRANT TEST_ROLE TO TEST"); + executeSuccess("GRANT SELECT ON PUB_TABLE TO PUBLIC"); + executeSuccess("GRANT SELECT ON TEST TO TEST"); + executeSuccess("CREATE ROLE SUB1"); + executeSuccess("CREATE ROLE SUB2"); + executeSuccess("CREATE TABLE SUB_TABLE(ID INT)"); + executeSuccess("GRANT ALL ON SUB_TABLE TO SUB2"); + executeSuccess("REVOKE UPDATE, DELETE ON SUB_TABLE FROM SUB2"); + executeSuccess("GRANT SUB2 TO SUB1"); + executeSuccess("GRANT SUB1 TO TEST"); + + executeSuccess("ALTER USER TEST SET PASSWORD 'def'"); + executeSuccess("CREATE USER TEST2 PASSWORD 'def' ADMIN"); + executeSuccess("ALTER USER TEST ADMIN FALSE"); + executeSuccess("SCRIPT TO '" + getBaseDir() + + "/rights.sql' CIPHER AES PASSWORD 'test'"); + conn.close(); + + try { + getConnection("rights", "Test", getPassword("abc")); + fail("mixed case user name"); + } catch (SQLException e) { + assertKnownException(e); + } + try { + getConnection("rights", "TEST", getPassword("abc")); + fail("wrong password"); + } catch (SQLException e) { + assertKnownException(e); + } + try { + getConnection("rights", "TEST", getPassword("")); + fail("wrong password"); + } catch (SQLException e) { + assertKnownException(e); + } + conn = getConnection(url, "TEST", getPassword("def")); + stat = conn.createStatement(); + + assertThrows(ErrorCode.ADMIN_RIGHTS_REQUIRED, stat). + execute("SET DEFAULT_TABLE_TYPE MEMORY"); + + executeSuccess("SELECT * FROM TEST"); + executeSuccess("SELECT * FROM SYSTEM_RANGE(1,2)"); + executeSuccess("SELECT * FROM SCHEMA_A.TABLE_B"); + executeSuccess("SELECT * FROM PUB_TABLE"); + executeSuccess("SELECT * FROM ROLE_TABLE"); + executeError("UPDATE ROLE_TABLE SET ID=0"); + executeError("DELETE FROM ROLE_TABLE"); + executeError("SELECT * FROM HIDDEN"); + executeError("UPDATE TEST SET ID=0"); + executeError("CALL SELECT MIN(PASSWORD) FROM PASS"); + executeSuccess("SELECT * FROM SUB_TABLE"); + executeSuccess("INSERT INTO SUB_TABLE VALUES(1)"); + executeError("DELETE FROM SUB_TABLE"); + executeError("UPDATE SUB_TABLE SET ID=0"); + + assertThrows(ErrorCode.ADMIN_RIGHTS_REQUIRED, stat). + execute("CREATE USER TEST3 PASSWORD 'def'"); + assertThrows(ErrorCode.ADMIN_RIGHTS_REQUIRED, stat). + execute("ALTER USER TEST2 ADMIN FALSE"); + assertThrows(ErrorCode.ADMIN_RIGHTS_REQUIRED, stat). + execute("ALTER USER TEST2 SET PASSWORD 'ghi'"); + assertThrows(ErrorCode.ADMIN_RIGHTS_REQUIRED, stat). + execute("ALTER USER TEST2 RENAME TO TEST_X"); + assertThrows(ErrorCode.ADMIN_RIGHTS_REQUIRED, stat). + execute("ALTER USER TEST RENAME TO TEST_X"); + executeSuccess("ALTER USER TEST SET PASSWORD 'ghi'"); + assertThrows(ErrorCode.ADMIN_RIGHTS_REQUIRED, stat). + execute("DROP USER TEST2"); + + conn.close(); + conn = getConnection("rights"); + stat = conn.createStatement(); + executeSuccess("DROP ROLE SUB1"); + executeSuccess("DROP TABLE ROLE_TABLE"); + executeSuccess("DROP USER TEST"); + + conn.close(); + conn = getConnection("rights"); + stat = conn.createStatement(); + + executeSuccess("DROP TABLE IF EXISTS TEST"); + executeSuccess("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + executeSuccess("CREATE USER GUEST PASSWORD 'abc'"); + executeSuccess("GRANT SELECT ON TEST TO GUEST"); + executeSuccess("ALTER USER GUEST RENAME TO GAST"); + conn.close(); + conn = getConnection("rights"); + conn.close(); + FileUtils.delete(getBaseDir() + "/rights.sql"); + } + + private void testTableType(Connection conn, String type) throws SQLException { + executeSuccess("SET DEFAULT_TABLE_TYPE " + type); + executeSuccess("CREATE TABLE TEST(ID INT)"); + ResultSet rs = conn.createStatement().executeQuery( + "SELECT STORAGE_TYPE FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='TEST'"); + rs.next(); + assertEquals(type, rs.getString(1)); + executeSuccess("DROP TABLE TEST"); + } + + private void testDropTable() throws SQLException { + deleteDb("rights"); + Connection conn = getConnection("rights"); + stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT)"); + stat.execute("CREATE USER U PASSWORD '1'"); + stat.execute("GRANT ALL PRIVILEGES ON TEST TO U"); + Connection conn2 = getConnection("rights", "U", getPassword("1")); + conn.close(); + stat = conn2.createStatement(); + assertEquals(1, stat.executeUpdate("INSERT INTO TEST VALUES 1")); + assertEquals(1, stat.executeUpdate("UPDATE TEST SET ID = 2 WHERE ID = 1")); + assertEquals(1, stat.executeUpdate("DELETE FROM TEST WHERE ID = 2")); + executeError("DROP TABLE TEST"); + conn2.close(); + } + + private void testSchemaOwner() throws SQLException { + deleteDb("rights"); + Connection connAdmin = getConnection("rights"); + Statement statAdmin = connAdmin.createStatement(); + statAdmin.execute("CREATE USER SCHEMA_ADMIN PASSWORD '1'"); + statAdmin.execute("GRANT ALTER ANY SCHEMA TO SCHEMA_ADMIN"); + Connection connSchemaAdmin = getConnection("rights", "SCHEMA_ADMIN", getPassword("1")); + Statement statSchemaAdmin = connSchemaAdmin.createStatement(); + statAdmin.execute("CREATE USER SCHEMA_OWNER PASSWORD '1'"); + Connection connSchemaOwner = getConnection("rights", "SCHEMA_OWNER", getPassword("1")); + Statement statSchemaOwner = connSchemaOwner.createStatement(); + statAdmin.execute("CREATE USER OTHER PASSWORD '1'"); + Connection connOther = getConnection("rights", "OTHER", getPassword("1")); + Statement statOther = connOther.createStatement(); + testSchemaOwner(statAdmin, statSchemaAdmin, statSchemaOwner, statOther, "SCHEMA_OWNER"); + statAdmin.execute("CREATE ROLE SCHEMA_OWNER_ROLE"); + statAdmin.execute("GRANT SCHEMA_OWNER_ROLE TO SCHEMA_OWNER"); + testSchemaOwner(statAdmin, statSchemaAdmin, statSchemaOwner, statOther, "SCHEMA_OWNER_ROLE"); + testAdminAndSchemaOwner(statAdmin, statSchemaAdmin); + statAdmin.close(); + statSchemaAdmin.close(); + statSchemaOwner.close(); + } + + private void testSchemaOwner(Statement statAdmin, Statement statSchemaAdmin, Statement statSchemaOwner, + Statement statOther, String authorization) throws SQLException { + executeSuccessErrorAdmin(statSchemaAdmin, statSchemaOwner, "CREATE SCHEMA S AUTHORIZATION " + authorization); + executeSuccessError(statSchemaOwner, statOther, "CREATE DOMAIN S.D INT"); + executeSuccessError(statSchemaOwner, statOther, "ALTER DOMAIN S.D ADD CONSTRAINT S.D_C CHECK (VALUE > 0)"); + executeSuccessError(statSchemaOwner, statOther, "ALTER DOMAIN S.D DROP CONSTRAINT S.D_C"); + executeSuccessError(statSchemaOwner, statOther, "ALTER DOMAIN S.D RENAME TO S.D2"); + executeSuccessError(statSchemaOwner, statOther, "DROP DOMAIN S.D2"); + executeSuccessError(statSchemaOwner, statOther, "CREATE CONSTANT S.C VALUE 1"); + executeSuccessError(statSchemaOwner, statOther, "DROP CONSTANT S.C"); + executeSuccessErrorAdmin(statAdmin, statSchemaAdmin, "CREATE ALIAS S.F FOR 'java.lang.Math.max(long,long)'"); + executeSuccessError(statSchemaOwner, statOther, "DROP ALIAS S.F"); + executeSuccessErrorAdmin(statAdmin, statSchemaAdmin, + "CREATE AGGREGATE S.A FOR \'" + TestFunctions.MedianStringType.class.getName() + '\''); + executeSuccessError(statSchemaOwner, statOther, "DROP AGGREGATE S.A"); + executeSuccessError(statSchemaOwner, statOther, "CREATE SEQUENCE S.S"); + executeSuccessError(statSchemaOwner, statOther, "ALTER SEQUENCE S.S RESTART WITH 2"); + executeSuccessError(statSchemaOwner, statOther, "DROP SEQUENCE S.S"); + executeSuccessError(statSchemaOwner, statOther, "CREATE VIEW S.V AS SELECT 1"); + executeSuccessError(statSchemaOwner, statOther, "ALTER VIEW S.V RECOMPILE"); + executeSuccessError(statSchemaOwner, statOther, "ALTER VIEW S.V RENAME TO S.V2"); + executeSuccessError(statSchemaOwner, statOther, "DROP VIEW S.V2"); + executeSuccessError(statSchemaOwner, statOther, "CREATE TABLE S.T(ID INT)"); + executeSuccessError(statSchemaOwner, statOther, "ALTER TABLE S.T ADD V INT"); + executeSuccessError(statSchemaOwner, statOther, "ALTER TABLE S.T ADD CONSTRAINT S.T_C UNIQUE(V)"); + executeSuccessError(statSchemaOwner, statOther, "ALTER TABLE S.T DROP CONSTRAINT S.T_C"); + executeSuccessError(statSchemaOwner, statOther, "CREATE UNIQUE INDEX S.I ON S.T(V)"); + executeSuccessError(statSchemaOwner, statOther, "ALTER INDEX S.I RENAME TO S.I2"); + executeSuccessError(statSchemaOwner, statOther, "DROP INDEX S.I2"); + executeSuccessErrorAdmin(statAdmin, statSchemaAdmin, + "CREATE TRIGGER S.G BEFORE INSERT ON S.T FOR EACH ROW CALL \'" + TestTrigger.class.getName() + '\''); + executeSuccessError(statSchemaOwner, statOther, "DROP TRIGGER S.G"); + executeSuccessError(statSchemaOwner, statOther, "GRANT SELECT ON S.T TO OTHER"); + executeSuccessError(statSchemaOwner, statOther, "REVOKE SELECT ON S.T FROM OTHER"); + executeSuccessError(statSchemaOwner, statOther, "ALTER TABLE S.T RENAME TO S.T2"); + executeSuccessError(statSchemaOwner, statOther, "DROP TABLE S.T2"); + executeSuccessError(statSchemaOwner, statOther, "DROP SCHEMA S"); + } + + private void testAdminAndSchemaOwner(Statement statAdmin, Statement statSchemaAdmin) throws SQLException { + executeSuccessErrorAdmin(statAdmin, statSchemaAdmin, "GRANT ALTER ANY SCHEMA TO OTHER"); + executeSuccessErrorAdmin(statAdmin, statSchemaAdmin, "REVOKE ALTER ANY SCHEMA FROM OTHER"); + executeSuccessErrorAdmin(statAdmin, statSchemaAdmin, "CREATE USER U PASSWORD '1'"); + executeSuccessErrorAdmin(statAdmin, statSchemaAdmin, "CREATE ROLE R"); + executeSuccessErrorAdmin(statAdmin, statSchemaAdmin, "GRANT R TO U"); + executeSuccessErrorAdmin(statAdmin, statSchemaAdmin, "REVOKE R FROM U"); + executeSuccessErrorAdmin(statAdmin, statSchemaAdmin, "DROP USER U"); + executeSuccessErrorAdmin(statAdmin, statSchemaAdmin, "DROP ROLE R"); + } + + public static class TestTrigger implements Trigger { + + @Override + public void fire(Connection conn, Object[] oldRow, Object[] newRow) throws SQLException { + } + + } + + private void executeSuccessErrorAdmin(Statement success, Statement error, String sql) throws SQLException { + assertThrows(ErrorCode.ADMIN_RIGHTS_REQUIRED, error).execute(sql); + success.execute(sql); + } + + private void executeSuccessError(Statement success, Statement error, String sql) throws SQLException { + assertThrows(ErrorCode.NOT_ENOUGH_RIGHTS_FOR_1, error).execute(sql); + success.execute(sql); + } + + private void executeError(String sql) throws SQLException { + assertThrows(ErrorCode.NOT_ENOUGH_RIGHTS_FOR_1, stat).execute(sql); + } + + private void executeSuccess(String sql) throws SQLException { + if (stat.execute(sql)) { + ResultSet rs = stat.getResultSet(); + + // this will check if the result set is updatable + rs.getConcurrency(); + + ResultSetMetaData meta = rs.getMetaData(); + int columnCount = meta.getColumnCount(); + for (int i = 0; i < columnCount; i++) { + meta.getCatalogName(i + 1); + meta.getColumnClassName(i + 1); + meta.getColumnDisplaySize(i + 1); + meta.getColumnLabel(i + 1); + meta.getColumnName(i + 1); + meta.getColumnType(i + 1); + meta.getColumnTypeName(i + 1); + meta.getPrecision(i + 1); + meta.getScale(i + 1); + meta.getSchemaName(i + 1); + meta.getTableName(i + 1); + } + while (rs.next()) { + for (int i = 0; i < columnCount; i++) { + rs.getObject(i + 1); + } + } + } + } + +} diff --git a/h2/src/test/org/h2/test/db/TestRunscript.java b/h2/src/test/org/h2/test/db/TestRunscript.java new file mode 100644 index 0000000..eeba97a --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestRunscript.java @@ -0,0 +1,625 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.util.Collections; + +import org.h2.api.ErrorCode; +import org.h2.api.Trigger; +import org.h2.engine.Constants; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.ChangeFileEncryption; +import org.h2.tools.Recover; +import org.h2.util.Task; + +/** + * Tests the RUNSCRIPT SQL statement. + */ +public class TestRunscript extends TestDb implements Trigger { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + org.h2.test.TestAll config = new org.h2.test.TestAll(); + config.traceLevelFile = 1; + System.out.println(config); + TestBase test = createCaller(); + test.runTest(config); +// TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + test(false); + test(true); + testDropReferencedUserDefinedFunction(); + testDropCascade(); + testScriptExcludeSchema(); + testScriptExcludeTable(); + testScriptExcludeFunctionAlias(); + testScriptExcludeConstant(); + testScriptExcludeSequence(); + testScriptExcludeConstraint(); + testScriptExcludeTrigger(); + testScriptExcludeRight(); + testRunscriptFromClasspath(); + testCancelScript(); + testEncoding(); + testClobPrimaryKey(); + testTruncateLargeLength(); + testVariableBinary(); + deleteDb("runscript"); + } + + private void testDropReferencedUserDefinedFunction() throws Exception { + deleteDb("runscript"); + Connection conn; + conn = getConnection("runscript"); + Statement stat = conn.createStatement(); + stat.execute("create alias int_decode for 'java.lang.Integer.decode'"); + stat.execute("create table test(x varchar, y int as int_decode(x))"); + stat.execute("script simple drop to '" + + getBaseDir() + "/backup.sql'"); + stat.execute("runscript from '" + + getBaseDir() + "/backup.sql'"); + conn.close(); + } + + private void testDropCascade() throws Exception { + deleteDb("runscript"); + Connection conn; + conn = getConnection("runscript"); + Statement stat = conn.createStatement(); + stat.execute("create table b(x int)"); + stat.execute("create view a as select * from b"); + stat.execute("script simple drop to '" + + getBaseDir() + "/backup.sql'"); + stat.execute("runscript from '" + + getBaseDir() + "/backup.sql'"); + conn.close(); + } + + private void testScriptExcludeSchema() throws Exception { + deleteDb("runscript"); + Connection conn; + ResultSet rs; + conn = getConnection("runscript"); + Statement stat = conn.createStatement(); + stat.execute("create schema include_schema1"); + stat.execute("create schema exclude_schema1"); + stat.execute("script schema include_schema1"); + rs = stat.getResultSet(); + while (rs.next()) { + assertFalse("The schema 'exclude_schema1' should not be present in the script", + rs.getString(1).contains("exclude_schema1".toUpperCase())); + } + rs.close(); + stat.execute("create schema include_schema2"); + stat.execute("script nosettings schema include_schema1, include_schema2"); + rs = stat.getResultSet(); + // version, user, and one row per schema = 4 + assertResultRowCount(4, rs); + rs.close(); + conn.close(); + } + + private void testScriptExcludeTable() throws Exception { + deleteDb("runscript"); + Connection conn; + ResultSet rs; + conn = getConnection("runscript"); + Statement stat = conn.createStatement(); + stat.execute("create schema a"); + stat.execute("create schema b"); + stat.execute("create schema c"); + stat.execute("create table a.test1(x varchar, y int)"); + stat.execute("create table a.test2(x varchar, y int)"); + stat.execute("create table b.test1(x varchar, y int)"); + stat.execute("create table b.test2(x varchar, y int)"); + stat.execute("script table a.test1"); + rs = stat.getResultSet(); + while (rs.next()) { + assertFalse("The table 'a.test2' should not be present in the script", + rs.getString(1).contains("a.test2".toUpperCase())); + assertFalse("The table 'b.test1' should not be present in the script", + rs.getString(1).contains("b.test1".toUpperCase())); + assertFalse("The table 'b.test2' should not be present in the script", + rs.getString(1).contains("b.test2".toUpperCase())); + } + rs.close(); + stat.execute("set schema b"); + stat.execute("script table test1"); + rs = stat.getResultSet(); + while (rs.next()) { + assertFalse("The table 'a.test1' should not be present in the script", + rs.getString(1).contains("a.test1".toUpperCase())); + assertFalse("The table 'a.test2' should not be present in the script", + rs.getString(1).contains("a.test2".toUpperCase())); + assertFalse("The table 'b.test2' should not be present in the script", + rs.getString(1).contains("b.test2".toUpperCase())); + } + stat.execute("script nosettings table a.test1, test2"); + rs = stat.getResultSet(); + // version, user, schemas 'a' & 'b', and 2 rows per table = 7 + assertResultRowCount(8, rs); + rs.close(); + conn.close(); + } + + private void testScriptExcludeFunctionAlias() throws Exception { + deleteDb("runscript"); + Connection conn; + ResultSet rs; + conn = getConnection("runscript"); + Statement stat = conn.createStatement(); + stat.execute("create schema a"); + stat.execute("create schema b"); + stat.execute("create schema c"); + stat.execute("create alias a.int_decode for 'java.lang.Integer.decode'"); + stat.execute("create table a.test(x varchar, y int as a.int_decode(x))"); + stat.execute("script schema b"); + rs = stat.getResultSet(); + while (rs.next()) { + assertFalse("The function alias 'int_decode' " + + "should not be present in the script", + rs.getString(1).contains("int_decode".toUpperCase())); + } + rs.close(); + conn.close(); + } + + private void testScriptExcludeConstant() throws Exception { + deleteDb("runscript"); + Connection conn; + ResultSet rs; + conn = getConnection("runscript"); + Statement stat = conn.createStatement(); + stat.execute("create schema a"); + stat.execute("create schema b"); + stat.execute("create schema c"); + stat.execute("create constant a.default_email value 'no@thanks.org'"); + stat.execute("create table a.test1(x varchar, " + + "email varchar default a.default_email)"); + stat.execute("script schema b"); + rs = stat.getResultSet(); + while (rs.next()) { + assertFalse("The constant 'default_email' " + + "should not be present in the script", + rs.getString(1).contains("default_email".toUpperCase())); + } + rs.close(); + conn.close(); + } + + private void testScriptExcludeSequence() throws Exception { + deleteDb("runscript"); + Connection conn; + ResultSet rs; + conn = getConnection("runscript"); + Statement stat = conn.createStatement(); + stat.execute("create schema a"); + stat.execute("create schema b"); + stat.execute("create schema c"); + stat.execute("create sequence a.seq_id"); + stat.execute("script schema b"); + rs = stat.getResultSet(); + while (rs.next()) { + assertFalse("The sequence 'seq_id' should not be present in the script", + rs.getString(1).contains("seq_id".toUpperCase())); + } + rs.close(); + conn.close(); + } + + private void testScriptExcludeConstraint() throws Exception { + deleteDb("runscript"); + Connection conn; + ResultSet rs; + conn = getConnection("runscript"); + Statement stat = conn.createStatement(); + stat.execute("create schema a"); + stat.execute("create schema b"); + stat.execute("create schema c"); + stat.execute("create table a.test1(x varchar, y int)"); + stat.execute("alter table a.test1 add constraint " + + "unique_constraint unique (x, y) "); + stat.execute("script schema b"); + rs = stat.getResultSet(); + while (rs.next()) { + assertFalse("The sequence 'unique_constraint' " + + "should not be present in the script", + rs.getString(1).contains("unique_constraint".toUpperCase())); + } + rs.close(); + stat.execute("create table a.test2(x varchar, y int)"); + stat.execute("script table a.test2"); + rs = stat.getResultSet(); + while (rs.next()) { + assertFalse("The sequence 'unique_constraint' " + + "should not be present in the script", + rs.getString(1).contains("unique_constraint".toUpperCase())); + } + rs.close(); + conn.close(); + } + + private void testScriptExcludeTrigger() throws Exception { + deleteDb("runscript"); + Connection conn; + ResultSet rs; + conn = getConnection("runscript"); + Statement stat = conn.createStatement(); + stat.execute("create schema a"); + stat.execute("create schema b"); + stat.execute("create schema c"); + stat.execute("create table a.test1(x varchar, y int)"); + stat.execute("create trigger trigger_insert before insert on a.test1 " + + "for each row call \"org.h2.test.db.TestRunscript\""); + stat.execute("script schema b"); + rs = stat.getResultSet(); + while (rs.next()) { + assertFalse("The trigger 'trigger_insert' should not be present in the script", + rs.getString(1).contains("trigger_insert".toUpperCase())); + } + rs.close(); + stat.execute("create table a.test2(x varchar, y int)"); + stat.execute("script table a.test2"); + rs = stat.getResultSet(); + while (rs.next()) { + assertFalse("The trigger 'trigger_insert' should not be present in the script", + rs.getString(1).contains("trigger_insert".toUpperCase())); + } + rs.close(); + conn.close(); + } + + private void testScriptExcludeRight() throws Exception { + deleteDb("runscript"); + Connection conn; + ResultSet rs; + conn = getConnection("runscript"); + Statement stat = conn.createStatement(); + stat.execute("create user USER_A1 password 'test'"); + stat.execute("create user USER_B1 password 'test'"); + stat.execute("create schema a"); + stat.execute("create schema b"); + stat.execute("create schema c"); + stat.execute("create table a.test1(x varchar, y int)"); + stat.execute("create table b.test1(x varchar, y int)"); + stat.execute("grant select on a.test1 to USER_A1"); + stat.execute("grant select on b.test1 to USER_B1"); + stat.execute("script schema b"); + rs = stat.getResultSet(); + while (rs.next()) { + assertFalse("The grant to 'USER_A1' should not be present in the script", + rs.getString(1).contains("to USER_A1".toUpperCase())); + } + rs.close(); + stat.execute("create user USER_A2 password 'test'"); + stat.execute("create table a.test2(x varchar, y int)"); + stat.execute("grant select on a.test2 to USER_A2"); + stat.execute("script table a.test2"); + rs = stat.getResultSet(); + while (rs.next()) { + assertFalse("The grant to 'USER_A1' should not be present in the script", + rs.getString(1).contains("to USER_A1".toUpperCase())); + assertFalse("The grant to 'USER_B1' should not be present in the script", + rs.getString(1).contains("to USER_B1".toUpperCase())); + } + rs.close(); + conn.close(); + } + + private void testRunscriptFromClasspath() throws Exception { + deleteDb("runscript"); + Connection conn; + conn = getConnection("runscript"); + Statement stat = conn.createStatement(); + stat.execute("runscript from 'classpath:/org/h2/samples/newsfeed.sql'"); + stat.execute("select * from version"); + conn.close(); + } + + private void testCancelScript() throws Exception { + if (config.ci) { + // fails regularly under Travis, not sure why + return; + } + deleteDb("runscript"); + Connection conn; + conn = getConnection("runscript"); + final Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key) as " + + "select x from system_range(1, 20000)"); + stat.execute("script simple drop to '"+ + getBaseDir()+"/backup.sql'"); + stat.execute("set throttle 1000"); + // need to wait a bit (throttle is only used every 50 ms) + Thread.sleep(200); + final String dir = getBaseDir(); + Task task; + task = new Task() { + @Override + public void call() throws SQLException { + stat.execute("script simple drop to '"+dir+"/backup2.sql'"); + } + }; + task.execute(); + Thread.sleep(200); + stat.cancel(); + SQLException e = (SQLException) task.getException(); + assertNotNull(e); + assertEquals(ErrorCode.STATEMENT_WAS_CANCELED, e.getErrorCode()); + + stat.execute("set throttle 1000"); + // need to wait a bit (throttle is only used every 50 ms) + Thread.sleep(100); + + task = new Task() { + @Override + public void call() throws SQLException { + stat.execute("runscript from '"+dir+"/backup.sql'"); + } + }; + task.execute(); + Thread.sleep(200); + stat.cancel(); + e = (SQLException) task.getException(); + assertNotNull(e); + assertEquals(ErrorCode.STATEMENT_WAS_CANCELED, e.getErrorCode()); + + conn.close(); + FileUtils.delete(getBaseDir() + "/backup.sql"); + FileUtils.delete(getBaseDir() + "/backup2.sql"); + } + + private void testEncoding() throws SQLException { + deleteDb("runscript"); + Connection conn; + Statement stat; + conn = getConnection("runscript"); + stat = conn.createStatement(); + stat.execute("create table \"t\u00f6\"(id int)"); + stat.execute("script to '"+ + getBaseDir()+"/backup.sql'"); + stat.execute("drop all objects"); + stat.execute("runscript from '"+ + getBaseDir()+"/backup.sql'"); + stat.execute("select * from \"t\u00f6\""); + stat.execute("script to '"+ + getBaseDir()+"/backup.sql' charset 'UTF-8'"); + stat.execute("drop all objects"); + stat.execute("runscript from '"+ + getBaseDir()+"/backup.sql' charset 'UTF-8'"); + stat.execute("select * from \"t\u00f6\""); + conn.close(); + FileUtils.delete(getBaseDir() + "/backup.sql"); + } + + /** + * This method is called via reflection from the database. + * + * @param a the value + * @return the absolute value + */ + public static int test(int a) { + return Math.abs(a); + } + + private void testClobPrimaryKey() throws SQLException { + deleteDb("runscript"); + Connection conn; + Statement stat; + conn = getConnection("runscript"); + stat = conn.createStatement(); + stat.execute("create table test(id int not null, data clob) " + + "as select 1, space(4100)"); + // the primary key for SYSTEM_LOB_STREAM used to be named like this + stat.execute("alter table test add constraint primary_key_e primary key(id)"); + stat.execute("script to '" + getBaseDir() + "/backup.sql'"); + conn.close(); + deleteDb("runscript"); + conn = getConnection("runscript"); + stat = conn.createStatement(); + stat.execute("runscript from '" + getBaseDir() + "/backup.sql'"); + conn.close(); + deleteDb("runscriptRestore"); + FileUtils.delete(getBaseDir() + "/backup.sql"); + } + + private void test(boolean password) throws SQLException { + deleteDb("runscript"); + Connection conn1, conn2; + Statement stat1, stat2; + conn1 = getConnection("runscript"); + stat1 = conn1.createStatement(); + stat1.execute("create table test (id identity, name varchar(12))"); + stat1.execute("insert into test (name) values ('first'), ('second')"); + stat1.execute("create table test2(id int primary key) as " + + "select x from system_range(1, 5000)"); + stat1.execute("create sequence testSeq start with 100 increment by 10"); + stat1.execute("create alias myTest for '" + getClass().getName() + ".test'"); + stat1.execute("create trigger myTrigger before insert " + + "on test nowait call \"" + getClass().getName() + "\""); + stat1.execute("create view testView as select * " + + "from test where 1=0 union all " + + "select * from test where 0=1"); + stat1.execute("create user testAdmin salt '00' hash '01' admin"); + stat1.execute("create schema testSchema authorization testAdmin"); + stat1.execute("create table testSchema.parent" + + "(id int primary key, name varchar)"); + stat1.execute("create index idxname on testSchema.parent(name)"); + stat1.execute("create table testSchema.child(id int primary key, " + + "parentId int, name varchar, foreign key(parentId) " + + "references parent(id))"); + stat1.execute("create user testUser salt '02' hash '03'"); + stat1.execute("create role testRole"); + stat1.execute("grant all on testSchema.child to testUser"); + stat1.execute("grant select, insert on testSchema.parent to testRole"); + stat1.execute("grant testRole to testUser"); + stat1.execute("create table blob (v blob)"); + PreparedStatement prep = conn1.prepareStatement( + "insert into blob values (?)"); + prep.setBytes(1, new byte[65536]); + prep.execute(); + String sql = "script to ?"; + if (password) { + sql += " CIPHER AES PASSWORD ?"; + } + prep = conn1.prepareStatement(sql); + prep.setString(1, getBaseDir() + "/backup.2.sql"); + if (password) { + prep.setString(2, "t1e2s3t4"); + } + prep.execute(); + + deleteDb("runscriptRestore"); + conn2 = getConnection("runscriptRestore"); + stat2 = conn2.createStatement(); + sql = "runscript from '" + getBaseDir() + "/backup.2.sql'"; + if (password) { + sql += " CIPHER AES PASSWORD 'wrongPassword'"; + } + if (password) { + assertThrows(ErrorCode.FILE_ENCRYPTION_ERROR_1, stat2). + execute(sql); + } + sql = "runscript from '" + getBaseDir() + "/backup.2.sql'"; + if (password) { + sql += " CIPHER AES PASSWORD 't1e2s3t4'"; + } + stat2.execute(sql); + stat2.execute("script to '" + getBaseDir() + "/backup.3.sql'"); + + assertEqualDatabases(stat1, stat2); + + if (!config.memory && !config.reopen) { + conn1.close(); + + if (config.cipher != null) { + ChangeFileEncryption.execute(getBaseDir(), "runscript", + config.cipher, getFilePassword().toCharArray(), null, true); + } + Recover.execute(getBaseDir(), "runscript"); + + deleteDb("runscriptRestoreRecover"); + Connection conn3 = getConnection("runscriptRestoreRecover"); + Statement stat3 = conn3.createStatement(); + stat3.execute("runscript from '" + getBaseDir() + "/runscript.h2.sql'"); + conn3.close(); + conn3 = getConnection("runscriptRestoreRecover"); + stat3 = conn3.createStatement(); + + if (config.cipher != null) { + ChangeFileEncryption.execute(getBaseDir(), + "runscript", config.cipher, null, getFilePassword().toCharArray(), true); + } + + conn1 = getConnection("runscript"); + stat1 = conn1.createStatement(); + + assertEqualDatabases(stat1, stat3); + conn3.close(); + } + + assertEqualDatabases(stat1, stat2); + + conn1.close(); + conn2.close(); + deleteDb("runscriptRestore"); + deleteDb("runscriptRestoreRecover"); + FileUtils.delete(getBaseDir() + "/backup.2.sql"); + FileUtils.delete(getBaseDir() + "/backup.3.sql"); + FileUtils.delete(getBaseDir() + "/runscript.h2.sql"); + + } + + private void testTruncateLargeLength() throws Exception { + deleteDb("runscript"); + Connection conn; + Statement stat; + Files.write(Paths.get(getBaseDir() + "/backup.sql"), + Collections.singleton("CREATE TABLE TEST(V VARCHAR(2147483647))"), // + StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + conn = getConnection("runscript"); + stat = conn.createStatement(); + assertThrows(ErrorCode.INVALID_VALUE_PRECISION, stat) + .execute("RUNSCRIPT FROM '" + getBaseDir() + "/backup.sql'"); + stat.execute("RUNSCRIPT FROM '" + getBaseDir() + "/backup.sql' QUIRKS_MODE"); + assertEquals(Constants.MAX_STRING_LENGTH, stat.executeQuery("TABLE TEST").getMetaData().getPrecision(1)); + conn.close(); + deleteDb("runscript"); + FileUtils.delete(getBaseDir() + "/backup.sql"); + } + + private void testVariableBinary() throws SQLException { + deleteDb("runscript"); + Connection conn; + Statement stat; + conn = getConnection("runscript"); + stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(B BINARY)"); + assertEquals(Types.BINARY, stat.executeQuery("TABLE TEST").getMetaData().getColumnType(1)); + stat.execute("SCRIPT TO '" + getBaseDir() + "/backup.sql'"); + conn.close(); + deleteDb("runscript"); + conn = getConnection("runscript"); + stat = conn.createStatement(); + stat.execute("RUNSCRIPT FROM '" + getBaseDir() + "/backup.sql'"); + assertEquals(Types.BINARY, stat.executeQuery("TABLE TEST").getMetaData().getColumnType(1)); + conn.close(); + deleteDb("runscript"); + conn = getConnection("runscript"); + stat = conn.createStatement(); + stat.execute("RUNSCRIPT FROM '" + getBaseDir() + "/backup.sql' VARIABLE_BINARY"); + assertEquals(Types.VARBINARY, stat.executeQuery("TABLE TEST").getMetaData().getColumnType(1)); + conn.close(); + deleteDb("runscript"); + FileUtils.delete(getBaseDir() + "/backup.sql"); + } + + @Override + public void init(Connection conn, String schemaName, String triggerName, + String tableName, boolean before, int type) { + if (!before) { + throw new InternalError("before:" + before); + } + if (type != INSERT) { + throw new InternalError("type:" + type); + } + } + + @Override + public void fire(Connection conn, Object[] oldRow, Object[] newRow) { + // nothing to do + } + + @Override + public void close() { + // ignore + } + + @Override + public void remove() { + // ignore + } + +} diff --git a/h2/src/test/org/h2/test/db/TestSQLInjection.java b/h2/src/test/org/h2/test/db/TestSQLInjection.java new file mode 100644 index 0000000..8cb9dca --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestSQLInjection.java @@ -0,0 +1,123 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests the ALLOW_LITERALS feature (protection against SQL injection). + */ +public class TestSQLInjection extends TestDb { + + private Connection conn; + private Statement stat; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + if (config.reopen) { + return false; + } + return true; + } + + @Override + public void test() throws SQLException { + deleteDb("sqlInjection"); + reconnect("sqlInjection"); + stat.execute("DROP TABLE IF EXISTS USERS"); + stat.execute("CREATE TABLE USERS(NAME VARCHAR PRIMARY KEY, " + + "PASSWORD VARCHAR, TYPE VARCHAR)"); + stat.execute("CREATE SCHEMA CONST"); + stat.execute("CREATE CONSTANT CONST.ACTIVE VALUE 'Active'"); + stat.execute("INSERT INTO USERS VALUES('James', '123456', CONST.ACTIVE)"); + assertTrue(checkPasswordInsecure("123456")); + assertFalse(checkPasswordInsecure("abcdef")); + assertTrue(checkPasswordInsecure("' OR ''='")); + assertTrue(checkPasswordSecure("123456")); + assertFalse(checkPasswordSecure("abcdef")); + assertFalse(checkPasswordSecure("' OR ''='")); + stat.execute("CALL 123"); + stat.execute("CALL 'Hello'"); + stat.execute("CALL $$Hello World$$"); + stat.execute("SET ALLOW_LITERALS NUMBERS"); + stat.execute("CALL 123"); + assertThrows(ErrorCode.LITERALS_ARE_NOT_ALLOWED, stat). + execute("CALL 'Hello'"); + assertThrows(ErrorCode.LITERALS_ARE_NOT_ALLOWED, stat). + execute("CALL $$Hello World$$"); + stat.execute("SET ALLOW_LITERALS NONE"); + try { + checkPasswordInsecure("123456"); + fail(); + } catch (SQLException e) { + assertKnownException(e); + } + assertTrue(checkPasswordSecure("123456")); + assertFalse(checkPasswordSecure("' OR ''='")); + conn.close(); + + if (config.memory) { + return; + } + + reconnect("sqlInjection"); + + try { + checkPasswordInsecure("123456"); + fail(); + } catch (SQLException e) { + assertKnownException(e); + } + assertTrue(checkPasswordSecure("123456")); + assertFalse(checkPasswordSecure("' OR ''='")); + conn.close(); + deleteDb("sqlInjection"); + } + + private boolean checkPasswordInsecure(String pwd) throws SQLException { + String sql = "SELECT * FROM USERS WHERE PASSWORD='" + pwd + "'"; + ResultSet rs = conn.createStatement().executeQuery(sql); + return rs.next(); + } + + private boolean checkPasswordSecure(String pwd) throws SQLException { + String sql = "SELECT * FROM USERS WHERE PASSWORD=?"; + PreparedStatement prep = conn.prepareStatement(sql); + prep.setString(1, pwd); + ResultSet rs = prep.executeQuery(); + return rs.next(); + } + + private void reconnect(String name) throws SQLException { + if (!config.memory) { + if (conn != null) { + conn.close(); + conn = null; + } + } + if (conn == null) { + conn = getConnection(name); + stat = conn.createStatement(); + } + } +} diff --git a/h2/src/test/org/h2/test/db/TestSelectTableNotFound.java b/h2/src/test/org/h2/test/db/TestSelectTableNotFound.java new file mode 100644 index 0000000..bed6108 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestSelectTableNotFound.java @@ -0,0 +1,177 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +public class TestSelectTableNotFound extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testWithoutAnyCandidate(); + testWithOneCandidate(); + testWithTwoCandidates(); + testWithSchema(); + testWithSchemaSearchPath(); + testWhenSchemaIsEmpty(); + testWithSchemaWhenSchemaIsEmpty(); + testWithSchemaSearchPathWhenSchemaIsEmpty(); + } + + private void testWithoutAnyCandidate() throws SQLException { + deleteDb(getTestName()); + Connection conn = getConnection(); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE T2 ( ID INT GENERATED BY DEFAULT AS IDENTITY )"); + try { + stat.executeQuery("SELECT 1 FROM t1"); + fail("Table `t1` was accessible but should not have been."); + } catch (SQLException e) { + String message = e.getMessage(); + assertContains(message, "Table \"t1\" not found;"); + } + + conn.close(); + deleteDb(getTestName()); + } + + private void testWithOneCandidate() throws SQLException { + deleteDb(getTestName()); + Connection conn = getConnection(); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE T1 ( ID INT GENERATED BY DEFAULT AS IDENTITY )"); + try { + stat.executeQuery("SELECT 1 FROM t1"); + fail("Table `t1` was accessible but should not have been."); + } catch (SQLException e) { + String message = e.getMessage(); + assertContains(message, "Table \"t1\" not found (candidates are: \"T1\")"); + } + + conn.close(); + deleteDb(getTestName()); + } + + private void testWithTwoCandidates() throws SQLException { + deleteDb(getTestName()); + Connection conn = getConnection(); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE Toast ( ID INT GENERATED BY DEFAULT AS IDENTITY )"); + stat.execute("CREATE TABLE TOAST ( ID INT GENERATED BY DEFAULT AS IDENTITY )"); + try { + stat.executeQuery("SELECT 1 FROM toast"); + fail("Table `toast` was accessible but should not have been."); + } catch (SQLException e) { + String message = e.getMessage(); + assertContains(message, "Table \"toast\" not found (candidates are: \"TOAST, Toast\")"); + } + + conn.close(); + deleteDb(getTestName()); + } + + private void testWithSchema() throws SQLException { + deleteDb(getTestName()); + Connection conn = getConnection(); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE T1 ( ID INT GENERATED BY DEFAULT AS IDENTITY )"); + try { + stat.executeQuery("SELECT 1 FROM PUBLIC.t1"); + fail("Table `t1` was accessible but should not have been."); + } catch (SQLException e) { + String message = e.getMessage(); + assertContains(message, "Table \"t1\" not found (candidates are: \"T1\")"); + } + + conn.close(); + deleteDb(getTestName()); + } + + private void testWithSchemaSearchPath() throws SQLException { + deleteDb(getTestName()); + Connection conn = getConnection(); + Statement stat = conn.createStatement(); + stat.execute("SET SCHEMA_SEARCH_PATH PUBLIC"); + stat.execute("CREATE TABLE T1 ( ID INT GENERATED BY DEFAULT AS IDENTITY )"); + try { + stat.executeQuery("SELECT 1 FROM t1"); + fail("Table `t1` was accessible but should not have been."); + } catch (SQLException e) { + String message = e.getMessage(); + assertContains(message, "Table \"t1\" not found (candidates are: \"T1\")"); + } + + conn.close(); + deleteDb(getTestName()); + } + + private void testWhenSchemaIsEmpty() throws SQLException { + deleteDb(getTestName()); + Connection conn = getConnection(); + Statement stat = conn.createStatement(); + try { + stat.executeQuery("SELECT 1 FROM t1"); + fail("Table `t1` was accessible but should not have been."); + } catch (SQLException e) { + String message = e.getMessage(); + assertContains(message, "Table \"t1\" not found (this database is empty)"); + } + + conn.close(); + deleteDb(getTestName()); + } + + private void testWithSchemaWhenSchemaIsEmpty() throws SQLException { + deleteDb(getTestName()); + Connection conn = getConnection(); + Statement stat = conn.createStatement(); + try { + stat.executeQuery("SELECT 1 FROM PUBLIC.t1"); + fail("Table `t1` was accessible but should not have been."); + } catch (SQLException e) { + String message = e.getMessage(); + assertContains(message, "Table \"t1\" not found (this database is empty)"); + } + + conn.close(); + deleteDb(getTestName()); + } + + private void testWithSchemaSearchPathWhenSchemaIsEmpty() throws SQLException { + deleteDb(getTestName()); + Connection conn = getConnection(); + Statement stat = conn.createStatement(); + stat.execute("SET SCHEMA_SEARCH_PATH PUBLIC"); + try { + stat.executeQuery("SELECT 1 FROM t1"); + fail("Table `t1` was accessible but should not have been."); + } catch (SQLException e) { + String message = e.getMessage(); + assertContains(message, "Table \"t1\" not found (this database is empty)"); + } + + conn.close(); + deleteDb(getTestName()); + } + + private Connection getConnection() throws SQLException { + return getConnection(getTestName() + ";DATABASE_TO_UPPER=FALSE"); + } +} diff --git a/h2/src/test/org/h2/test/db/TestSequence.java b/h2/src/test/org/h2/test/db/TestSequence.java new file mode 100644 index 0000000..689ada2 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestSequence.java @@ -0,0 +1,500 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.h2.api.Trigger; +import org.h2.engine.Constants; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.Task; + +/** + * Tests the sequence feature of this database. + */ +public class TestSequence extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testConcurrentCreate(); + testConcurrentNextAndCurrentValue(); + testSchemaSearchPath(); + testAlterSequenceColumn(); + testAlterSequence(); + testCache(); + testTwo(); + testMetaTable(); + testCreateWithMinValue(); + testCreateWithMaxValue(); + testCreationErrors(); + testCreateSql(); + testDefaultMinMax(); + deleteDb("sequence"); + } + + private void testConcurrentCreate() throws Exception { + deleteDb("sequence"); + final String url = getURL("sequence;LOCK_TIMEOUT=2000", true); + Connection conn = getConnection(url); + Task[] tasks = new Task[2]; + try { + Statement stat = conn.createStatement(); + stat.execute("create table dummy(id bigint primary key)"); + stat.execute("create table test(id bigint primary key)"); + stat.execute("create sequence test_seq cache 2"); + for (int i = 0; i < tasks.length; i++) { + final int x = i; + tasks[i] = new Task() { + @Override + public void call() throws Exception { + try (Connection conn = getConnection(url)) { + PreparedStatement prep = conn.prepareStatement( + "insert into test(id) values(next value for test_seq)"); + PreparedStatement prep2 = conn.prepareStatement( + "delete from test"); + while (!stop) { + prep.execute(); + if (Math.random() < 0.01) { + prep2.execute(); + } + if (Math.random() < 0.01) { + createDropTrigger(conn); + } + } + } + } + + private void createDropTrigger(Connection conn) throws Exception { + String triggerName = "t_" + x; + Statement stat = conn.createStatement(); + stat.execute("create trigger " + triggerName + + " before insert on dummy call \"" + + TriggerTest.class.getName() + "\""); + stat.execute("drop trigger " + triggerName); + } + + }.execute(); + } + Thread.sleep(1000); + for (Task t : tasks) { + t.get(); + } + } finally { + for (Task t : tasks) { + t.join(); + } + conn.close(); + } + } + + private void testConcurrentNextAndCurrentValue() throws Exception { + deleteDb("sequence"); + final String url = getURL("sequence", true); + Connection conn = getConnection(url); + Task[] tasks = new Task[2]; + try { + Statement stat = conn.createStatement(); + stat.execute("CREATE SEQUENCE SEQ1"); + stat.execute("CREATE SEQUENCE SEQ2"); + for (int i = 0; i < tasks.length; i++) { + tasks[i] = new Task() { + @Override + public void call() throws Exception { + try (Connection conn = getConnection(url)) { + PreparedStatement next1 = conn.prepareStatement("CALL NEXT VALUE FOR SEQ1"); + PreparedStatement next2 = conn.prepareStatement("CALL NEXT VALUE FOR SEQ2"); + PreparedStatement current1 = conn.prepareStatement("CALL CURRENT VALUE FOR SEQ1"); + PreparedStatement current2 = conn.prepareStatement("CALL CURRENT VALUE FOR SEQ2"); + while (!stop) { + long v1, v2; + try (ResultSet rs = next1.executeQuery()) { + rs.next(); + v1 = rs.getLong(1); + } + try (ResultSet rs = next2.executeQuery()) { + rs.next(); + v2 = rs.getLong(1); + } + try (ResultSet rs = current1.executeQuery()) { + rs.next(); + if (v1 != rs.getLong(1)) { + throw new RuntimeException("Unexpected CURRENT VALUE FOR SEQ1"); + } + } + try (ResultSet rs = current2.executeQuery()) { + rs.next(); + if (v2 != rs.getLong(1)) { + throw new RuntimeException("Unexpected CURRENT VALUE FOR SEQ2"); + } + } + } + } + } + }.execute(); + } + Thread.sleep(1000); + for (Task t : tasks) { + Exception e = t.getException(); + if (e != null) { + throw new AssertionError(e.getMessage()); + } + } + } finally { + for (Task t : tasks) { + t.join(); + } + conn.close(); + } + } + + private void testSchemaSearchPath() throws SQLException { + deleteDb("sequence"); + Connection conn = getConnection("sequence"); + Statement stat = conn.createStatement(); + stat.execute("CREATE SCHEMA TEST"); + stat.execute("CREATE SEQUENCE TEST.TEST_SEQ"); + stat.execute("SET SCHEMA_SEARCH_PATH PUBLIC, TEST"); + stat.execute("CALL NEXT VALUE FOR TEST_SEQ"); + stat.execute("CALL CURRENT VALUE FOR TEST_SEQ"); + conn.close(); + } + + private void testAlterSequenceColumn() throws SQLException { + deleteDb("sequence"); + Connection conn = getConnection("sequence"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT , NAME VARCHAR(255))"); + stat.execute("INSERT INTO TEST VALUES(1, 'Hello')"); + stat.execute("ALTER TABLE TEST ALTER COLUMN ID INT GENERATED BY DEFAULT AS IDENTITY"); + stat.execute("ALTER TABLE test ALTER COLUMN ID RESTART WITH 3"); + stat.execute("INSERT INTO TEST (name) VALUES('Other World')"); + conn.close(); + } + + private void testAlterSequence() throws SQLException { + test("create sequence s; alter sequence s restart with 2", null, 2, 3, 4); + test("create sequence s; alter sequence s restart with 7", null, 7, 8, 9, 10); + test("create sequence s; alter sequence s start with 3 restart with 11 minvalue 3 maxvalue 12 cycle", + null, 11, 12, 3, 4); + test("create sequence s; alter sequence s restart with 5 cache 2", + null, 5, 6, 7, 8); + test("create sequence s; alter sequence s restart with 9 " + + "maxvalue 12 nocycle nocache", + "Sequence \"S\" has run out of numbers", 9, 10, 11, 12); + } + + private void testCache() throws SQLException { + if (config.memory) { + return; + } + deleteDb("sequence"); + Connection conn = getConnection("sequence"); + Statement stat = conn.createStatement(); + stat.execute("create sequence test_Sequence"); + stat.execute("create sequence test_Sequence3 cache 3"); + conn.close(); + conn = getConnection("sequence"); + stat = conn.createStatement(); + stat.execute("call next value for test_Sequence"); + stat.execute("call next value for test_Sequence3"); + ResultSet rs = stat.executeQuery("select * from " + + "information_schema.sequences order by sequence_name"); + rs.next(); + assertEquals("TEST_SEQUENCE", rs.getString("SEQUENCE_NAME")); + assertEquals("32", rs.getString("CACHE")); + rs.next(); + assertEquals("TEST_SEQUENCE3", rs.getString("SEQUENCE_NAME")); + assertEquals("3", rs.getString("CACHE")); + assertFalse(rs.next()); + conn.close(); + } + + private void testMetaTable() throws SQLException { + deleteDb("sequence"); + Connection conn = getConnection("sequence"); + Statement stat = conn.createStatement(); + stat.execute("create sequence a"); + stat.execute("create sequence b start with 7 minvalue 5 " + + "maxvalue 9 cycle increment by 2 nocache"); + stat.execute("create sequence c start with -4 minvalue -9 " + + "maxvalue -3 no cycle increment by -2 cache 3"); + + if (!config.memory) { + conn.close(); + conn = getConnection("sequence"); + } + + stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("select * from " + + "information_schema.sequences order by sequence_name"); + rs.next(); + assertEquals("SEQUENCE", rs.getString("SEQUENCE_CATALOG")); + assertEquals("PUBLIC", rs.getString("SEQUENCE_SCHEMA")); + assertEquals("A", rs.getString("SEQUENCE_NAME")); + assertEquals(1, rs.getLong("BASE_VALUE")); + assertEquals(1, rs.getLong("INCREMENT")); + assertNull(rs.getString("REMARKS")); + assertEquals(32, rs.getLong("CACHE")); + assertEquals(1, rs.getLong("MINIMUM_VALUE")); + assertEquals(Long.MAX_VALUE, rs.getLong("MAXIMUM_VALUE")); + assertEquals("NO", rs.getString("CYCLE_OPTION")); + rs.next(); + assertEquals("SEQUENCE", rs.getString("SEQUENCE_CATALOG")); + assertEquals("PUBLIC", rs.getString("SEQUENCE_SCHEMA")); + assertEquals("B", rs.getString("SEQUENCE_NAME")); + assertEquals(7, rs.getLong("BASE_VALUE")); + assertEquals(2, rs.getLong("INCREMENT")); + assertNull(rs.getString("REMARKS")); + assertEquals(1, rs.getLong("CACHE")); + assertEquals(5, rs.getLong("MINIMUM_VALUE")); + assertEquals(9, rs.getLong("MAXIMUM_VALUE")); + assertEquals("YES", rs.getString("CYCLE_OPTION")); + rs.next(); + assertEquals("SEQUENCE", rs.getString("SEQUENCE_CATALOG")); + assertEquals("PUBLIC", rs.getString("SEQUENCE_SCHEMA")); + assertEquals("C", rs.getString("SEQUENCE_NAME")); + assertEquals(-4, rs.getLong("BASE_VALUE")); + assertEquals(-2, rs.getLong("INCREMENT")); + assertNull(rs.getString("REMARKS")); + assertEquals(3, rs.getLong("CACHE")); + assertEquals(-9, rs.getLong("MINIMUM_VALUE")); + assertEquals(-3, rs.getLong("MAXIMUM_VALUE")); + assertEquals("NO", rs.getString("CYCLE_OPTION")); + assertFalse(rs.next()); + conn.close(); + } + + private void testCreateWithMinValue() throws SQLException { + test("create sequence s minvalue 3", null, 3, 4, 5, 6); + test("create sequence s minvalue -3 increment by -1 cycle", + null, -1, -2, -3, -1); + test("create sequence s minvalue -3 increment by -1", + "Sequence \"S\" has run out of numbers", -1, -2, -3); + test("create sequence s minvalue -3 increment by -1 nocycle", + "Sequence \"S\" has run out of numbers", -1, -2, -3); + test("create sequence s minvalue -3 increment by -1 no cycle", + "Sequence \"S\" has run out of numbers", -1, -2, -3); + test("create sequence s minvalue -3 increment by -1 nocache cycle", + null, -1, -2, -3, -1); + test("create sequence s minvalue -3 increment by -1 nocache", + "Sequence \"S\" has run out of numbers", -1, -2, -3); + test("create sequence s minvalue -3 increment by -1 nocache nocycle", + "Sequence \"S\" has run out of numbers", -1, -2, -3); + test("create sequence s minvalue -3 increment by -1 no cache no cycle", + "Sequence \"S\" has run out of numbers", -1, -2, -3); + } + + private void testCreateWithMaxValue() throws SQLException { + test("create sequence s maxvalue -3 increment by -1", + null, -3, -4, -5, -6); + test("create sequence s maxvalue 3 cycle", null, 1, 2, 3, 1); + test("create sequence s maxvalue 3", + "Sequence \"S\" has run out of numbers", 1, 2, 3); + test("create sequence s maxvalue 3 nocycle", + "Sequence \"S\" has run out of numbers", 1, 2, 3); + test("create sequence s maxvalue 3 no cycle", + "Sequence \"S\" has run out of numbers", 1, 2, 3); + test("create sequence s maxvalue 3 nocache cycle", + null, 1, 2, 3, 1); + test("create sequence s maxvalue 3 nocache", + "Sequence \"S\" has run out of numbers", 1, 2, 3); + test("create sequence s maxvalue 3 nocache nocycle", + "Sequence \"S\" has run out of numbers", 1, 2, 3); + test("create sequence s maxvalue 3 no cache no cycle", + "Sequence \"S\" has run out of numbers", 1, 2, 3); + } + + private void testCreationErrors() throws SQLException { + deleteDb("sequence"); + Connection conn = getConnection("sequence"); + Statement stat = conn.createStatement(); + expectError( + stat, + "create sequence a minvalue 5 start with 2", + "Unable to create or alter sequence \"A\" because of " + + "invalid attributes (base value \"2\", start value \"2\", " + + "min value \"5\", max value \"" + Long.MAX_VALUE + + "\", increment \"1\", cache size \"32\")"); + expectError( + stat, + "create sequence b maxvalue 5 start with 7", + "Unable to create or alter sequence \"B\" because of " + + "invalid attributes (base value \"7\", start value \"7\", " + + "min value \"1\", max value \"5\", increment \"1\", cache size \"32\")"); + expectError( + stat, + "create sequence c minvalue 5 maxvalue 2", + "Unable to create or alter sequence \"C\" because of " + + "invalid attributes (base value \"5\", start value \"5\", " + + "min value \"5\", max value \"2\", increment \"1\", cache size \"32\")"); + expectError( + stat, + "create sequence d increment by 0", + "Unable to create or alter sequence \"D\" because of " + + "invalid attributes (base value \"1\", start value \"1\", " + + "min value \"1\", max value \"" + + Long.MAX_VALUE + "\", increment \"0\", cache size \"32\")"); + expectError(stat, + "create sequence e minvalue 1 maxvalue 5 increment 99", + "Unable to create or alter sequence \"E\" because of " + + "invalid attributes (base value \"1\", start value \"1\", " + + "min value \"1\", max value \"5\", increment \"99\", cache size \"32\")"); + conn.close(); + } + + private void testCreateSql() throws SQLException { + deleteDb("sequence"); + Connection conn = getConnection("sequence"); + Statement stat = conn.createStatement(); + stat.execute("create sequence a"); + stat.execute("create sequence b start with 5 increment by 2 " + + "minvalue 3 maxvalue 7 cycle nocache"); + stat.execute("create sequence c start with 3 increment by 1 " + + "minvalue 2 maxvalue 9 nocycle cache 2"); + stat.execute("create sequence d nomaxvalue no minvalue no cache nocycle"); + stat.execute("create sequence e cache 1"); + List script = new ArrayList<>(); + ResultSet rs = stat.executeQuery("script nodata"); + while (rs.next()) { + script.add(rs.getString(1)); + } + Collections.sort(script); + assertEquals("-- H2 " + Constants.VERSION + ";", script.get(0)); + assertEquals("CREATE SEQUENCE \"PUBLIC\".\"A\" START WITH 1;", script.get(1)); + assertEquals("CREATE SEQUENCE \"PUBLIC\".\"B\" START " + + "WITH 5 INCREMENT BY 2 " + + "MINVALUE 3 MAXVALUE 7 CYCLE NO CACHE;", script.get(2)); + assertEquals("CREATE SEQUENCE \"PUBLIC\".\"C\" START " + + "WITH 3 MINVALUE 2 MAXVALUE 9 CACHE 2;", + script.get(3)); + assertEquals("CREATE SEQUENCE \"PUBLIC\".\"D\" START " + + "WITH 1 NO CACHE;", script.get(4)); + assertEquals("CREATE SEQUENCE \"PUBLIC\".\"E\" START " + + "WITH 1 NO CACHE;", script.get(5)); + conn.close(); + } + + private void testDefaultMinMax() throws SQLException { + // test that we calculate default MIN and MAX values correctly + deleteDb("sequence"); + Connection conn = getConnection("sequence"); + Statement stat = conn.createStatement(); + stat.execute("create sequence a START WITH -7320917853639540658"); + stat.execute("create sequence b START WITH 7320917853639540658 INCREMENT -1"); + conn.close(); + } + + private void testTwo() throws SQLException { + deleteDb("sequence"); + Connection conn = getConnection("sequence"); + Statement stat = conn.createStatement(); + stat.execute("create sequence s"); + conn.setAutoCommit(false); + + Connection conn2 = getConnection("sequence"); + Statement stat2 = conn2.createStatement(); + conn2.setAutoCommit(false); + + long last = 0; + for (int i = 0; i < 100; i++) { + long v1 = getNext(stat); + assertTrue(v1 > last); + last = v1; + for (int j = 0; j < 100; j++) { + long v2 = getNext(stat2); + assertTrue(v2 > last); + last = v2; + } + } + + conn2.close(); + conn.close(); + } + + private void test(String setupSql, String finalError, long... values) + throws SQLException { + + deleteDb("sequence"); + + Connection conn = getConnection("sequence"); + Statement stat = conn.createStatement(); + stat.execute(setupSql); + + if (!config.memory) { + conn.close(); + conn = getConnection("sequence"); + } + + stat = conn.createStatement(); + for (long value : values) { + assertEquals(value, getNext(stat)); + } + + if (finalError != null) { + try { + getNext(stat); + fail("Expected error: " + finalError); + } catch (SQLException e) { + assertContains(e.getMessage(), finalError); + } + } + + conn.close(); + } + + private void expectError(Statement stat, String sql, String error) { + try { + stat.execute(sql); + fail("Expected error: " + error); + } catch (SQLException e) { + assertContains(e.getMessage(), error); + } + } + + private static long getNext(Statement stat) throws SQLException { + ResultSet rs = stat.executeQuery("call next value for s"); + rs.next(); + long value = rs.getLong(1); + return value; + } + + /** + * A test trigger. + */ + public static class TriggerTest implements Trigger { + + @Override + public void init(Connection conn, String schemaName, + String triggerName, String tableName, boolean before, int type) + throws SQLException { + conn.createStatement().executeQuery("call next value for test_seq"); + } + + @Override + public void fire(Connection conn, Object[] oldRow, Object[] newRow) + throws SQLException { + // ignore + } + + } + +} diff --git a/h2/src/test/org/h2/test/db/TestSessionsLocks.java b/h2/src/test/org/h2/test/db/TestSessionsLocks.java new file mode 100644 index 0000000..874cabe --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestSessionsLocks.java @@ -0,0 +1,188 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests the meta data tables information_schema.locks and sessions. + */ +public class TestSessionsLocks extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public void test() throws Exception { + testCancelStatement(); + testLocks(); + testAbortStatement(); + deleteDb("sessionsLocks"); + } + + private void testLocks() throws SQLException { + deleteDb("sessionsLocks"); + Connection conn = getConnection("sessionsLocks"); + Statement stat = conn.createStatement(); + ResultSet rs; + rs = stat.executeQuery("select * from information_schema.locks " + + "order by session_id"); + assertFalse(rs.next()); + Connection conn2 = getConnection("sessionsLocks"); + Statement stat2 = conn2.createStatement(); + stat2.execute("create table test(id int primary key, name varchar)"); + conn2.setAutoCommit(false); + stat2.execute("insert into test values(1, 'Hello')"); + rs = stat.executeQuery("select * from information_schema.locks " + + "order by session_id"); + rs.next(); + assertEquals("PUBLIC", rs.getString("TABLE_SCHEMA")); + assertEquals("TEST", rs.getString("TABLE_NAME")); + rs.getString("SESSION_ID"); + assertEquals("READ", rs.getString("LOCK_TYPE")); + assertFalse(rs.next()); + conn2.commit(); + conn2.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); + stat2.execute("SELECT * FROM TEST"); + rs = stat.executeQuery("select * from information_schema.locks " + + "order by session_id"); + assertFalse(rs.next()); + conn2.commit(); + rs = stat.executeQuery("select * from information_schema.locks " + + "order by session_id"); + assertFalse(rs.next()); + conn.close(); + conn2.close(); + } + + private void testCancelStatement() throws Exception { + deleteDb("sessionsLocks"); + Connection conn = getConnection("sessionsLocks"); + Statement stat = conn.createStatement(); + ResultSet rs; + rs = stat.executeQuery("select * from information_schema.sessions " + + "order by SESSION_START, SESSION_ID"); + rs.next(); + int sessionId = rs.getInt("SESSION_ID"); + rs.getString("USER_NAME"); + rs.getTimestamp("SESSION_START"); + rs.getString("EXECUTING_STATEMENT"); + rs.getTimestamp("EXECUTING_STATEMENT_START"); + assertFalse(rs.next()); + Connection conn2 = getConnection("sessionsLocks"); + Statement stat2 = conn2.createStatement(); + rs = stat.executeQuery("select * from information_schema.sessions " + + "order by SESSION_START, SESSION_ID"); + assertTrue(rs.next()); + assertEquals(sessionId, rs.getInt("SESSION_ID")); + assertTrue(rs.next()); + int otherId = rs.getInt("SESSION_ID"); + assertTrue(otherId != sessionId); + assertFalse(rs.next()); + stat2.execute("set throttle 1"); + boolean[] done = { false }; + Runnable runnable = () -> { + try { + stat2.execute("select count(*) from " + + "system_range(1, 10000000) t1, system_range(1, 10000000) t2"); + new Error("Unexpected success").printStackTrace(); + } catch (SQLException e) { + done[0] = true; + } + }; + new Thread(runnable).start(); + while (true) { + Thread.sleep(100); + rs = stat.executeQuery("CALL CANCEL_SESSION(" + otherId + ")"); + rs.next(); + if (rs.getBoolean(1)) { + for (int i = 0; i < 20; i++) { + Thread.sleep(100); + if (done[0]) { + break; + } + } + assertTrue(done[0]); + break; + } + } + conn2.close(); + conn.close(); + } + + private void testAbortStatement() throws Exception { + deleteDb("sessionsLocks"); + Connection conn = getConnection("sessionsLocks"); + Statement stat = conn.createStatement(); + ResultSet rs; + rs = stat.executeQuery("select session_id() as ID"); + rs.next(); + int sessionId = rs.getInt("ID"); + + // Setup session to be aborted + Connection conn2 = getConnection("sessionsLocks"); + Statement stat2 = conn2.createStatement(); + stat2.execute("create table test(id int primary key, name varchar)"); + conn2.setAutoCommit(false); + stat2.execute("insert into test values(1, 'Hello')"); + conn2.commit(); + // grab a lock + stat2.executeUpdate("update test set name = 'Again' where id = 1"); + + rs = stat2.executeQuery("select session_id() as ID"); + rs.next(); + + int otherId = rs.getInt("ID"); + assertTrue(otherId != sessionId); + assertFalse(rs.next()); + + // expect one lock + assertEquals(1, getLockCountForSession(stat, otherId)); + rs = stat.executeQuery("CALL ABORT_SESSION(" + otherId + ")"); + rs.next(); + assertTrue(rs.getBoolean(1)); + + // expect the lock to be released along with its session + assertEquals(0, getLockCountForSession(stat, otherId)); + rs = stat.executeQuery("CALL ABORT_SESSION(" + otherId + ")"); + rs.next(); + assertFalse("Session is expected to be already aborted", rs.getBoolean(1)); + + // using the connection for the aborted session is expected to throw an + // exception + assertThrows(config.networked ? ErrorCode.CONNECTION_BROKEN_1 : ErrorCode.DATABASE_CALLED_AT_SHUTDOWN, stat2) + .executeQuery("select count(*) from test"); + + conn2.close(); + conn.close(); + } + + private int getLockCountForSession(Statement stmnt, int otherId) throws SQLException { + try (ResultSet rs = stmnt + .executeQuery("select count(*) from information_schema.locks where session_id = " + otherId)) { + assertTrue(rs.next()); + return rs.getInt(1); + } + } +} diff --git a/h2/src/test/org/h2/test/db/TestSetCollation.java b/h2/src/test/org/h2/test/db/TestSetCollation.java new file mode 100644 index 0000000..7c0559f --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestSetCollation.java @@ -0,0 +1,191 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +public class TestSetCollation extends TestDb { + private static final String[] TEST_STRINGS = new String[]{"A", "\u00c4", "AA", "B", "$", "1A", null}; + + private static final String DB_NAME = "collator"; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testDefaultCollator(); + testCp500Collator(); + testDeCollator(); + testUrlParameter(); + testReopenDatabase(); + testReopenDatabaseWithUrlParameter(); + testReopenDatabaseWithDifferentCollationInUrl(); + testReopenDatabaseWithSameCollationInUrl(); + } + + + private void testDefaultCollator() throws Exception { + assertEquals(Arrays.asList(null, "$", "1A", "A", "AA", "B", "\u00c4"), orderedWithCollator(null)); + } + + private void testDeCollator() throws Exception { + assertEquals(Arrays.asList(null, "$", "1A", "A", "\u00c4", "AA", "B"), orderedWithCollator("DE")); + assertEquals(Arrays.asList(null, "$", "1A", "A", "\u00c4", "AA", "B"), orderedWithCollator("DEFAULT_DE")); + } + + private void testCp500Collator() throws Exception { + // IBM z/OS codepage + assertEquals(Arrays.asList(null, "A", "AA", "B", "1A", "$", "\u00c4"), + orderedWithCollator("CHARSET_CP500")); + } + + private void testUrlParameter() throws Exception { + // Specifying the collator in the JDBC Url should have the same effect + // as setting it with a set statement + config.collation = "CHARSET_CP500"; + try { + assertEquals(Arrays.asList(null, "A", "AA", "B", "1A", "$", "\u00c4"), orderedWithCollator(null)); + } finally { + config.collation = null; + } + } + + private void testReopenDatabase() throws Exception { + if (config.memory) { + return; + } + + orderedWithCollator("DE"); + + try (Connection con = getConnection(DB_NAME)) { + insertValues(con, new String[]{"A", "\u00c4"}, 100); + + assertEquals(Arrays.asList(null, "$", "1A", "A", "A", "\u00c4", "\u00c4", "AA", "B"), + loadTableValues(con)); + } + } + + private void testReopenDatabaseWithUrlParameter() throws Exception { + if (config.memory) { + return; + } + + config.collation = "DE"; + try { + orderedWithCollator(null); + } finally { + config.collation = null; + } + + // reopen the database without specifying a collation in the url. + // This should keep the initial collation. + try (Connection con = getConnection(DB_NAME)) { + insertValues(con, new String[]{"A", "\u00c4"}, 100); + + assertEquals(Arrays.asList(null, "$", "1A", "A", "A", "\u00c4", "\u00c4", "AA", "B"), + loadTableValues(con)); + } + + } + + private void testReopenDatabaseWithDifferentCollationInUrl() throws Exception { + if (config.memory) { + return; + } + config.collation = "DE"; + try { + orderedWithCollator(null); + } finally { + config.collation = null; + } + + config.collation = "CHARSET_CP500"; + try { + getConnection(DB_NAME); + fail(); + } catch (SQLException e) { + // expected + } finally { + config.collation = null; + } + } + + private void testReopenDatabaseWithSameCollationInUrl() throws Exception { + if (config.memory) { + return; + } + config.collation = "DE"; + try { + orderedWithCollator(null); + } finally { + config.collation = null; + } + + config.collation = "DE"; + try (Connection con = getConnection(DB_NAME)) { + insertValues(con, new String[]{"A", "\u00c4"}, 100); + + assertEquals(Arrays.asList(null, "$", "1A", "A", "A", "\u00c4", "\u00c4", "AA", "B"), + loadTableValues(con)); + } finally { + config.collation = null; + } + } + + + private List orderedWithCollator(String collator) throws SQLException { + deleteDb(DB_NAME); + try (Connection con = getConnection(DB_NAME); Statement statement = con.createStatement()) { + if (collator != null) { + statement.execute("SET COLLATION " + collator); + } + statement.execute("CREATE TABLE charsettable(id INT PRIMARY KEY, testvalue VARCHAR(50))"); + + insertValues(con, TEST_STRINGS, 1); + + return loadTableValues(con); + } + } + + private static void insertValues(Connection con, String[] values, int startId) throws SQLException { + PreparedStatement ps = con.prepareStatement("INSERT INTO charsettable VALUES (?, ?)"); + int id = startId; + for (String value : values) { + ps.setInt(1, id++); + ps.setString(2, value); + ps.execute(); + } + ps.close(); + } + + private static List loadTableValues(Connection con) throws SQLException { + List results = new ArrayList<>(); + Statement statement = con.createStatement(); + ResultSet resultSet = statement.executeQuery("select testvalue from charsettable order by testvalue"); + while (resultSet.next()) { + results.add(resultSet.getString(1)); + } + statement.close(); + return results; + } + +} diff --git a/h2/src/test/org/h2/test/db/TestSpaceReuse.java b/h2/src/test/org/h2/test/db/TestSpaceReuse.java new file mode 100644 index 0000000..dd21cf5 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestSpaceReuse.java @@ -0,0 +1,66 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import org.h2.engine.Constants; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests if disk space is reused after deleting many rows. + */ +public class TestSpaceReuse extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + if (config.memory) { + return false; + } + return true; + } + + @Override + public void test() throws SQLException { + deleteDb("spaceReuse"); + long max = 0, now = 0, min = Long.MAX_VALUE; + for (int i = 0; i < 20; i++) { + Connection conn = getConnection("spaceReuse"); + Statement stat = conn.createStatement(); + stat.execute("set retention_time 0"); + stat.execute("set write_delay 0"); // disable auto-commit so that free-unused runs on commit + stat.execute("create table if not exists t(i int)"); + stat.execute("insert into t select x from system_range(1, 500)"); + conn.close(); + conn = getConnection("spaceReuse"); + conn.createStatement().execute("delete from t"); + conn.close(); + String fileName = getBaseDir() + "/spaceReuse" + Constants.SUFFIX_MV_FILE; + now = FileUtils.size(fileName); + assertTrue(now > 0); + if (i < 10) { + max = Math.max(max, now); + } else { + min = Math.min(min, now); + } + } + assertTrue("min: " + min + " max: " + max, min <= max); + deleteDb("spaceReuse"); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestSpatial.java b/h2/src/test/org/h2/test/db/TestSpatial.java new file mode 100644 index 0000000..0de3de0 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestSpatial.java @@ -0,0 +1,1209 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Savepoint; +import java.sql.Statement; +import java.sql.Types; +import java.util.Random; +import org.h2.api.Aggregate; +import org.h2.api.ErrorCode; +import org.h2.message.DbException; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.SimpleResultSet; +import org.h2.tools.SimpleRowSource; +import org.h2.util.HasSQL; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueGeometry; +import org.h2.value.ValueToObjectConverter; +import org.h2.value.ValueToObjectConverter2; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.CoordinateSequence; +import org.locationtech.jts.geom.Envelope; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.MultiPoint; +import org.locationtech.jts.geom.Point; +import org.locationtech.jts.geom.Polygon; +import org.locationtech.jts.geom.PrecisionModel; +import org.locationtech.jts.geom.impl.CoordinateArraySequenceFactory; +import org.locationtech.jts.geom.util.AffineTransformation; +import org.locationtech.jts.io.ByteOrderValues; +import org.locationtech.jts.io.ParseException; +import org.locationtech.jts.io.WKBWriter; +import org.locationtech.jts.io.WKTReader; + +/** + * Spatial datatype and index tests. + * + * @author Thomas Mueller + * @author Noel Grandin + * @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888 + */ +public class TestSpatial extends TestDb { + + private static final String URL = "spatial"; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + if (config.memory) { + return false; + } + if (ValueToObjectConverter.GEOMETRY_CLASS == null) { + return false; + } + return true; + } + + @Override + public void test() throws SQLException { + deleteDb("spatial"); + testSpatial(); + deleteDb("spatial"); + } + + private void testSpatial() throws SQLException { + testNaNs(); + testBug1(); + testSpatialValues(); + testOverlap(); + testNotOverlap(); + testPersistentSpatialIndex(); + testSpatialIndexQueryMultipleTable(); + testIndexTransaction(); + testJavaAlias(); + testJavaAliasTableFunction(); + testMemorySpatialIndex(); + testGeometryDataType(); + testWKB(); + testValueConversion(); + testEquals(); + testTableFunctionGeometry(); + testAggregateWithGeometry(); + testTableViewSpatialPredicate(); + testValueGeometryScript(); + testInPlaceUpdate(); + testScanIndexOnNonSpatialQuery(); + testStoreCorruption(); + testExplainSpatialIndexWithPk(); + testNullableGeometry(); + testNullableGeometryDelete(); + testNullableGeometryInsert(); + testNullableGeometryUpdate(); + testIndexUpdateNullGeometry(); + testInsertNull(); + testSpatialIndexWithOrder(); + } + + private void testNaNs() { + GeometryFactory factory = new GeometryFactory(new PrecisionModel(), 0, + CoordinateArraySequenceFactory.instance()); + CoordinateSequence c2 = factory.getCoordinateSequenceFactory().create(1, 2, 0); + c2.setOrdinate(0, 0, 1d); + c2.setOrdinate(0, 1, 1d); + CoordinateSequence c3 = factory.getCoordinateSequenceFactory().create(1, 3, 0); + c3.setOrdinate(0, 0, 1d); + c3.setOrdinate(0, 1, 2d); + c3.setOrdinate(0, 2, 3d); + Point p2 = factory.createPoint(c2); + Point p3 = factory.createPoint(c3); + try { + ValueGeometry.getFromGeometry(new MultiPoint(new Point[] { p2, p3 }, factory)); + fail("Expected exception"); + } catch (DbException e) { + assertEquals(ErrorCode.DATA_CONVERSION_ERROR_1, e.getErrorCode()); + } + } + + private void testBug1() throws SQLException { + deleteDb("spatial"); + Connection conn = getConnection(URL); + Statement stat = conn.createStatement(); + + stat.execute("CREATE TABLE VECTORS (ID INTEGER NOT NULL, GEOM GEOMETRY, S INTEGER)"); + stat.execute("INSERT INTO VECTORS(ID, GEOM, S) " + + "VALUES(0, 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))', 1)"); + + stat.executeQuery("select * from (select * from VECTORS) WHERE S=1 " + + "AND GEOM && 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))'"); + conn.close(); + deleteDb("spatial"); + } + + private void testSpatialValues() throws SQLException { + deleteDb("spatial"); + Connection conn = getConnection(URL); + Statement stat = conn.createStatement(); + + stat.execute("create memory table test" + + "(id int primary key, polygon geometry)"); + stat.execute("insert into test values(1, " + + "'POLYGON ((1 1, 1 2, 2 2, 1 1))')"); + ResultSet rs = stat.executeQuery("select * from test"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals("POLYGON ((1 1, 1 2, 2 2, 1 1))", rs.getString(2)); + GeometryFactory f = new GeometryFactory(); + Polygon polygon = f.createPolygon(new Coordinate[] { + new Coordinate(1, 1), + new Coordinate(1, 2), + new Coordinate(2, 2), + new Coordinate(1, 1) }); + assertTrue(polygon.equals(rs.getObject(2))); + rs.close(); + rs = stat.executeQuery("select id, cast(polygon as varchar) from test"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals("POLYGON ((1 1, 1 2, 2 2, 1 1))", rs.getObject(2)); + assertTrue(polygon.equals(rs.getObject(2, Geometry.class))); + rs.close(); + + rs = stat.executeQuery("select * from test where polygon = " + + "'POLYGON ((1 1, 1 2, 2 2, 1 1))'"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + stat.executeQuery("select * from test where polygon > " + + "'POLYGON ((1 1, 1 2, 2 2, 1 1))'"); + stat.executeQuery("select * from test where polygon < " + + "'POLYGON ((1 1, 1 2, 2 2, 1 1))'"); + + stat.execute("drop table test"); + conn.close(); + deleteDb("spatial"); + } + + /** + * Generate a random line string under the given bounding box. + * + * @param geometryRand the random generator + * @param minX Bounding box min x + * @param maxX Bounding box max x + * @param minY Bounding box min y + * @param maxY Bounding box max y + * @param maxLength LineString maximum length + * @return A segment within this bounding box + */ + static Geometry getRandomGeometry(Random geometryRand, + double minX, double maxX, + double minY, double maxY, double maxLength) { + GeometryFactory factory = new GeometryFactory(); + // Create the start point + Coordinate start = new Coordinate( + geometryRand.nextDouble() * (maxX - minX) + minX, + geometryRand.nextDouble() * (maxY - minY) + minY); + // Compute an angle + double angle = geometryRand.nextDouble() * Math.PI * 2; + // Compute length + double length = geometryRand.nextDouble() * maxLength; + // Compute end point + Coordinate end = new Coordinate( + start.x + Math.cos(angle) * length, + start.y + Math.sin(angle) * length); + return factory.createLineString(new Coordinate[] { start, end }); + } + + private void testOverlap() throws SQLException { + deleteDb("spatial"); + try (Connection conn = getConnection(URL)) { + Statement stat = conn.createStatement(); + stat.execute("create memory table test" + + "(id int primary key, poly geometry)"); + stat.execute("insert into test values(1, " + + "'POLYGON ((1 1, 1 2, 2 2, 1 1))')"); + stat.execute("insert into test values(2, " + + "'POLYGON ((3 1, 3 2, 4 2, 3 1))')"); + stat.execute("insert into test values(3, " + + "'POLYGON ((1 3, 1 4, 2 4, 1 3))')"); + + ResultSet rs = stat.executeQuery( + "select * from test " + + "where poly && 'POINT (1.5 1.5)'::Geometry"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt("id")); + assertFalse(rs.next()); + stat.execute("drop table test"); + } + } + private void testPersistentSpatialIndex() throws SQLException { + deleteDb("spatial"); + try (Connection conn = getConnection(URL)) { + Statement stat = conn.createStatement(); + stat.execute("create table test" + + "(id int primary key, poly geometry)"); + stat.execute("insert into test values(1, " + + "'POLYGON ((1 1, 1 2, 2 2, 1 1))')"); + stat.execute("insert into test values(2,null)"); + stat.execute("insert into test values(3, " + + "'POLYGON ((3 1, 3 2, 4 2, 3 1))')"); + stat.execute("insert into test values(4,null)"); + stat.execute("insert into test values(5, " + + "'POLYGON ((1 3, 1 4, 2 4, 1 3))')"); + stat.execute("create spatial index on test(poly)"); + + ResultSet rs = stat.executeQuery( + "select * from test " + + "where poly && 'POINT (1.5 1.5)'::Geometry"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt("id")); + assertFalse(rs.next()); + rs.close(); + + // Test with multiple operator + rs = stat.executeQuery( + "select * from test " + + "where poly && 'POINT (1.5 1.5)'::Geometry " + + "AND poly && 'POINT (1.7 1.75)'::Geometry"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt("id")); + assertFalse(rs.next()); + rs.close(); + } + + if (config.memory) { + return; + } + + try (Connection conn = getConnection(URL)) { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery( + "select * from test " + + "where poly && 'POINT (1.5 1.5)'::Geometry"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt("id")); + assertFalse(rs.next()); + stat.execute("drop table test"); + } + } + + private void testNotOverlap() throws SQLException { + deleteDb("spatial"); + try (Connection conn = getConnection(URL)) { + Statement stat = conn.createStatement(); + stat.execute("create memory table test" + + "(id int primary key, poly geometry)"); + stat.execute("insert into test values(1, " + + "'POLYGON ((1 1, 1 2, 2 2, 1 1))')"); + stat.execute("insert into test values(2,null)"); + stat.execute("insert into test values(3, " + + "'POLYGON ((3 1, 3 2, 4 2, 3 1))')"); + stat.execute("insert into test values(4,null)"); + stat.execute("insert into test values(5, " + + "'POLYGON ((1 3, 1 4, 2 4, 1 3))')"); + + ResultSet rs = stat.executeQuery( + "select * from test " + + "where NOT poly && 'POINT (1.5 1.5)'::Geometry"); + assertTrue(rs.next()); + assertEquals(3, rs.getInt("id")); + assertTrue(rs.next()); + assertEquals(5, rs.getInt("id")); + assertFalse(rs.next()); + stat.execute("drop table test"); + } + } + + private static void createTestTable(Statement stat) throws SQLException { + stat.execute("create table area(idArea int primary key, the_geom geometry)"); + stat.execute("create spatial index on area(the_geom)"); + stat.execute("insert into area values(1, " + + "'POLYGON ((-10 109, 90 109, 90 9, -10 9, -10 109))')"); + stat.execute("insert into area values(2, " + + "'POLYGON ((90 109, 190 109, 190 9, 90 9, 90 109))')"); + stat.execute("insert into area values(3, " + + "'POLYGON ((190 109, 290 109, 290 9, 190 9, 190 109))')"); + stat.execute("insert into area values(4, " + + "'POLYGON ((-10 9, 90 9, 90 -91, -10 -91, -10 9))')"); + stat.execute("insert into area values(5, " + + "'POLYGON ((90 9, 190 9, 190 -91, 90 -91, 90 9))')"); + stat.execute("insert into area values(6, " + + "'POLYGON ((190 9, 290 9, 290 -91, 190 -91, 190 9))')"); + stat.execute("insert into area values(7,null)"); + stat.execute("insert into area values(8,null)"); + + + stat.execute("create table roads(idRoad int primary key, the_geom geometry)"); + stat.execute("create spatial index on roads(the_geom)"); + stat.execute("insert into roads values(1, " + + "'LINESTRING (27.65595463138 -16.728733459357244, " + + "47.61814744801515 40.435727788279806)')"); + stat.execute("insert into roads values(2, " + + "'LINESTRING (17.674858223062415 55.861058601134246, " + + "55.78449905482046 76.73062381852554)')"); + stat.execute("insert into roads values(3, " + + "'LINESTRING (68.48771266540646 67.65689981096412, " + + "108.4120982986768 88.52646502835542)')"); + stat.execute("insert into roads values(4, " + + "'LINESTRING (177.3724007561437 18.65879017013235, " + + "196.4272211720227 -16.728733459357244)')"); + stat.execute("insert into roads values(5, " + + "'LINESTRING (106.5973534971645 -12.191871455576518, " + + "143.79962192816637 30.454631379962223)')"); + stat.execute("insert into roads values(6, " + + "'LINESTRING (144.70699432892252 55.861058601134246, " + + "150.1512287334594 83.9896030245747)')"); + stat.execute("insert into roads values(7, " + + "'LINESTRING (60.321361058601155 -13.099243856332663, " + + "149.24385633270325 5.955576559546344)')"); + stat.execute("insert into roads values(8, null)"); + stat.execute("insert into roads values(9, null)"); + + } + + private void testSpatialIndexQueryMultipleTable() throws SQLException { + deleteDb("spatial"); + try (Connection conn = getConnection(URL)) { + Statement stat = conn.createStatement(); + createTestTable(stat); + testRoadAndArea(stat); + } + deleteDb("spatial"); + } + private void testRoadAndArea(Statement stat) throws SQLException { + ResultSet rs = stat.executeQuery( + "select idArea, COUNT(idRoad) roadCount " + + "from area, roads " + + "where area.the_geom && roads.the_geom " + + "GROUP BY idArea ORDER BY idArea"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt("idArea")); + assertEquals(3, rs.getInt("roadCount")); + assertTrue(rs.next()); + assertEquals(2, rs.getInt("idArea")); + assertEquals(4, rs.getInt("roadCount")); + assertTrue(rs.next()); + assertEquals(3, rs.getInt("idArea")); + assertEquals(1, rs.getInt("roadCount")); + assertTrue(rs.next()); + assertEquals(4, rs.getInt("idArea")); + assertEquals(2, rs.getInt("roadCount")); + assertTrue(rs.next()); + assertEquals(5, rs.getInt("idArea")); + assertEquals(3, rs.getInt("roadCount")); + assertTrue(rs.next()); + assertEquals(6, rs.getInt("idArea")); + assertEquals(1, rs.getInt("roadCount")); + assertFalse(rs.next()); + rs.close(); + } + private void testIndexTransaction() throws SQLException { + // Check session management in index + deleteDb("spatial"); + try (Connection conn = getConnection(URL)) { + conn.setAutoCommit(false); + Statement stat = conn.createStatement(); + createTestTable(stat); + Savepoint sp = conn.setSavepoint(); + // Remove a row but do not commit + stat.execute("delete from roads where idRoad=9"); + stat.execute("delete from roads where idRoad=7"); + // Check if index is updated + ResultSet rs = stat.executeQuery( + "select idArea, COUNT(idRoad) roadCount " + + "from area, roads " + + "where area.the_geom && roads.the_geom " + + "GROUP BY idArea ORDER BY idArea"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt("idArea")); + assertEquals(3, rs.getInt("roadCount")); + assertTrue(rs.next()); + assertEquals(2, rs.getInt("idArea")); + assertEquals(4, rs.getInt("roadCount")); + assertTrue(rs.next()); + assertEquals(3, rs.getInt("idArea")); + assertEquals(1, rs.getInt("roadCount")); + assertTrue(rs.next()); + assertEquals(4, rs.getInt("idArea")); + assertEquals(1, rs.getInt("roadCount")); + assertTrue(rs.next()); + assertEquals(5, rs.getInt("idArea")); + assertEquals(2, rs.getInt("roadCount")); + assertTrue(rs.next()); + assertEquals(6, rs.getInt("idArea")); + assertEquals(1, rs.getInt("roadCount")); + assertFalse(rs.next()); + rs.close(); + conn.rollback(sp); + // Check if the index is restored + testRoadAndArea(stat); + } + } + + /** + * Test the in the in-memory spatial index + */ + private void testMemorySpatialIndex() throws SQLException { + deleteDb("spatial"); + Connection conn = getConnection(URL); + Statement stat = conn.createStatement(); + + stat.execute("create memory table test(id int primary key, polygon geometry)"); + stat.execute("create spatial index idx_test_polygon on test(polygon)"); + stat.execute("insert into test values(1, 'POLYGON ((1 1, 1 2, 2 2, 1 1))')"); + stat.execute("insert into test values(2, null)"); + ResultSet rs; + + // an query that can not possibly return a result + rs = stat.executeQuery("select * from test " + + "where polygon && 'POLYGON ((1 1, 1 2, 2 2, 1 1))'::Geometry " + + "and polygon && 'POLYGON ((10 10, 10 20, 20 20, 10 10))'::Geometry"); + assertFalse(rs.next()); + + rs = stat.executeQuery( + "explain select * from test " + + "where polygon && 'POLYGON ((1 1, 1 2, 2 2, 1 1))'::Geometry"); + rs.next(); + assertContains(rs.getString(1), "/* PUBLIC.IDX_TEST_POLYGON: POLYGON &&"); + + // TODO equality should probably also use the spatial index + // rs = stat.executeQuery("explain select * from test " + + // "where polygon = 'POLYGON ((1 1, 1 2, 2 2, 1 1))'"); + // rs.next(); + // assertContains(rs.getString(1), + // "/* PUBLIC.IDX_TEST_POLYGON: POLYGON ="); + + // these queries actually have no meaning in the context of a spatial + // index, but + // check them anyhow + stat.executeQuery("select * from test where polygon > " + + "'POLYGON ((1 1, 1 2, 2 2, 1 1))'::Geometry"); + stat.executeQuery("select * from test where polygon < " + + "'POLYGON ((1 1, 1 2, 2 2, 1 1))'::Geometry"); + + rs = stat.executeQuery( + "select * from test " + + "where intersects(polygon, 'POLYGON ((1 1, 1 2, 2 2, 1 1))')"); + assertTrue(rs.next()); + + rs = stat.executeQuery( + "select * from test " + + "where intersects(polygon, 'POINT (1 1)')"); + assertTrue(rs.next()); + + rs = stat.executeQuery( + "select * from test " + + "where intersects(polygon, 'POINT (0 0)')"); + assertFalse(rs.next()); + + stat.execute("drop table test"); + conn.close(); + deleteDb("spatial"); + } + + /** + * Test java alias with Geometry type. + */ + private void testJavaAlias() throws SQLException { + deleteDb("spatial"); + try (Connection conn = getConnection(URL)) { + Statement stat = conn.createStatement(); + stat.execute("CREATE ALIAS T_GEOM_FROM_TEXT FOR '" + TestSpatial.class.getName() + ".geomFromText'"); + stat.execute("create table test(id int primary key " + + "auto_increment, the_geom geometry)"); + stat.execute("insert into test(the_geom) values(" + + "T_GEOM_FROM_TEXT('POLYGON ((" + + "62 48, 84 48, 84 42, 56 34, 62 48))',1488))"); + stat.execute("DROP ALIAS T_GEOM_FROM_TEXT"); + ResultSet rs = stat.executeQuery("select the_geom from test"); + assertTrue(rs.next()); + assertEquals("POLYGON ((62 48, 84 48, 84 42, 56 34, 62 48))", + rs.getObject(1).toString()); + } + deleteDb("spatial"); + } + + /** + * Test java alias with Geometry type. + */ + private void testJavaAliasTableFunction() throws SQLException { + deleteDb("spatial"); + try (Connection conn = getConnection(URL)) { + Statement stat = conn.createStatement(); + stat.execute("CREATE ALIAS T_RANDOM_GEOM_TABLE FOR '" + + TestSpatial.class.getName() + ".getRandomGeometryTable'"); + stat.execute( + "create table test as " + + "select * from T_RANDOM_GEOM_TABLE(42,20,-100,100,-100,100,4)"); + stat.execute("DROP ALIAS T_RANDOM_GEOM_TABLE"); + ResultSet rs = stat.executeQuery("select count(*) from test"); + assertTrue(rs.next()); + assertEquals(20, rs.getInt(1)); + } + deleteDb("spatial"); + } + + /** + * Generate a result set with random geometry data. + * Used as an ALIAS function. + * + * @param seed the random seed + * @param rowCount the number of rows + * @param minX the smallest x + * @param maxX the largest x + * @param minY the smallest y + * @param maxY the largest y + * @param maxLength the maximum length + * @return a result set + */ + public static ResultSet getRandomGeometryTable( + final long seed, final long rowCount, + final double minX, final double maxX, + final double minY, final double maxY, final double maxLength) { + + SimpleResultSet rs = new SimpleResultSet(new SimpleRowSource() { + + private final Random random = new Random(seed); + private int currentRow; + + @Override + public Object[] readRow() throws SQLException { + if (currentRow++ < rowCount) { + return new Object[] { + getRandomGeometry(random, + minX, maxX, minY, maxY, maxLength) }; + } + return null; + } + + @Override + public void close() { + // nothing to do + } + + @Override + public void reset() throws SQLException { + random.setSeed(seed); + } + }); + rs.addColumn("the_geom", Types.OTHER, "GEOMETRY", Integer.MAX_VALUE, 0); + return rs; + } + + /** + * Convert the text to a geometry object. + * + * @param text the geometry as a Well Known Text + * @param srid the projection id + * @return Geometry object + */ + public static Geometry geomFromText(String text, int srid) throws SQLException { + WKTReader wktReader = new WKTReader(); + wktReader.setIsOldJtsCoordinateSyntaxAllowed(false); + try { + Geometry geom = wktReader.read(text); + geom.setSRID(srid); + return geom; + } catch (ParseException ex) { + throw new SQLException(ex); + } + } + + private void testGeometryDataType() { + GeometryFactory geometryFactory = new GeometryFactory(); + Geometry geometry = geometryFactory.createPoint(new Coordinate(0, 0)); + assertEquals(TypeInfo.TYPE_GEOMETRY, ValueToObjectConverter2.classToType(geometry.getClass())); + } + + /** + * Test serialization of Z and SRID values. + */ + private void testWKB() { + String ewkt = "SRID=27572;POLYGON Z ((67 13 6, 67 18 5, 59 18 4, 59 13 6, 67 13 6))"; + ValueGeometry geom3d = ValueGeometry.get(ewkt); + assertEquals(ewkt, geom3d.getString()); + ValueGeometry copy = ValueGeometry.get(geom3d.getBytes()); + Geometry g = copy.getGeometry(); + assertEquals(6, g.getCoordinates()[0].getZ()); + assertEquals(5, g.getCoordinates()[1].getZ()); + assertEquals(4, g.getCoordinates()[2].getZ()); + // Test SRID + copy = ValueGeometry.get(geom3d.getBytes()); + assertEquals(27572, g.getSRID()); + + Point point = new GeometryFactory().createPoint((new Coordinate(1.1d, 1.2d))); + // SRID 0 + checkSRID(ValueGeometry.getFromGeometry(point).getBytes(), 0); + checkSRID(new WKBWriter(2, ByteOrderValues.BIG_ENDIAN, false).write(point), 0); + checkSRID(new WKBWriter(2, ByteOrderValues.BIG_ENDIAN, true).write(point), 0); + checkSRID(new WKBWriter(2, ByteOrderValues.LITTLE_ENDIAN, false).write(point), 0); + checkSRID(new WKBWriter(2, ByteOrderValues.LITTLE_ENDIAN, true).write(point), 0); + ewkt = "POINT (1.1 1.2)"; + assertEquals(ewkt, ValueGeometry.getFromGeometry(point).getString()); + assertEquals(ewkt, ValueGeometry.get(ewkt).getString()); + // SRID 1,000,000,000 + point.setSRID(1_000_000_000); + checkSRID(ValueGeometry.getFromGeometry(point).getBytes(), 1_000_000_000); + checkSRID(new WKBWriter(2, ByteOrderValues.BIG_ENDIAN, true).write(point), 1_000_000_000); + checkSRID(new WKBWriter(2, ByteOrderValues.LITTLE_ENDIAN, true).write(point), 1_000_000_000); + ewkt = "SRID=1000000000;POINT (1.1 1.2)"; + assertEquals(ewkt, ValueGeometry.getFromGeometry(point).getString()); + assertEquals(ewkt, ValueGeometry.get(ewkt).getString()); + } + + private void checkSRID(byte[] bytes, int srid) { + Point point = (Point) ValueGeometry.getFromEWKB(bytes).getGeometry(); + assertEquals(1.1, point.getX()); + assertEquals(1.2, point.getY()); + assertEquals(srid, point.getSRID()); + assertEquals(srid, point.getFactory().getSRID()); + } + + /** + * Test conversion of Geometry object into Object + */ + private void testValueConversion() throws SQLException { + deleteDb("spatial"); + Connection conn = getConnection(URL); + Statement stat = conn.createStatement(); + stat.execute("CREATE ALIAS OBJ_STRING FOR '" + TestSpatial.class.getName() + ".getObjectString'"); + ResultSet rs = stat.executeQuery( + "select OBJ_STRING('POINT( 15 25 )'::geometry)"); + assertTrue(rs.next()); + assertEquals("POINT (15 25)", rs.getString(1)); + conn.close(); + deleteDb("spatial"); + } + + /** + * Get the toString value of the object. + * + * @param object the object + * @return the string representation + */ + public static String getObjectString(Geometry object) { + return object.toString(); + } + + /** + * Test equality method on ValueGeometry + */ + private void testEquals() { + // 3d equality test + ValueGeometry geom3d = ValueGeometry.get( + "POLYGON Z((67 13 6, 67 18 5, 59 18 4, 59 13 6, 67 13 6))"); + ValueGeometry geom2d = ValueGeometry.get( + "POLYGON ((67 13, 67 18, 59 18, 59 13, 67 13))"); + assertFalse(geom3d.equals(geom2d)); + // SRID equality test + GeometryFactory geometryFactory = new GeometryFactory(); + Geometry geometry = geometryFactory.createPoint(new Coordinate(0, 0)); + geometry.setSRID(27572); + ValueGeometry valueGeometry = ValueGeometry.getFromGeometry(geometry); + Geometry geometry2 = geometryFactory.createPoint(new Coordinate(0, 0)); + geometry2.setSRID(5326); + ValueGeometry valueGeometry2 = ValueGeometry.getFromGeometry(geometry2); + assertFalse(valueGeometry.equals(valueGeometry2)); + ValueGeometry valueGeometry3 = ValueGeometry.getFromGeometry(geometry); + assertEquals(valueGeometry, valueGeometry3); + assertEquals(geometry.getSRID(), valueGeometry3.getGeometry().getSRID()); + } + + /** + * Check that geometry column type is kept with a table function + */ + private void testTableFunctionGeometry() throws SQLException { + deleteDb("spatial"); + try (Connection conn = getConnection(URL)) { + Statement stat = conn.createStatement(); + stat.execute("CREATE ALIAS POINT_TABLE FOR '" + TestSpatial.class.getName() + ".pointTable'"); + stat.execute("create table test as select * from point_table(1, 1)"); + // Read column type + ResultSet columnMeta = conn.getMetaData(). + getColumns(null, null, "TEST", "THE_GEOM"); + assertTrue(columnMeta.next()); + assertEquals("geometry", + columnMeta.getString("TYPE_NAME").toLowerCase()); + assertFalse(columnMeta.next()); + } + deleteDb("spatial"); + } + + /** + * This method is called via reflection from the database. + * + * @param x the x position of the point + * @param y the y position of the point + * @return a result set with this point + */ + public static ResultSet pointTable(double x, double y) { + GeometryFactory factory = new GeometryFactory(); + SimpleResultSet rs = new SimpleResultSet(); + rs.addColumn("THE_GEOM", Types.OTHER, "GEOMETRY", 0, 0); + rs.addRow(factory.createPoint(new Coordinate(x, y))); + return rs; + } + + private void testAggregateWithGeometry() throws SQLException { + deleteDb("spatialIndex"); + try (Connection conn = getConnection("spatialIndex")) { + Statement st = conn.createStatement(); + st.execute("CREATE AGGREGATE TABLE_ENVELOPE FOR '" + TableEnvelope.class.getName() + '\''); + st.execute("CREATE TABLE test(the_geom GEOMETRY)"); + st.execute("INSERT INTO test VALUES ('POINT(1 1)'), (null), (null), ('POINT(10 5)')"); + ResultSet rs = st.executeQuery("select TABLE_ENVELOPE(the_geom) from test"); + assertEquals("geometry", rs.getMetaData(). + getColumnTypeName(1).toLowerCase()); + assertTrue(rs.next()); + assertTrue(rs.getObject(1) instanceof Geometry); + assertTrue(new Envelope(1, 10, 1, 5).equals( + ((Geometry) rs.getObject(1)).getEnvelopeInternal())); + assertFalse(rs.next()); + } + deleteDb("spatialIndex"); + } + + /** + * An aggregate function that calculates the envelope. + */ + public static class TableEnvelope implements Aggregate { + private Envelope tableEnvelope; + + @Override + public int getInternalType(int[] inputTypes) throws SQLException { + for (int inputType : inputTypes) { + if (inputType != Value.GEOMETRY) { + throw new SQLException("TableEnvelope accept only Geometry argument"); + } + } + return Value.GEOMETRY; + } + + @Override + public void init(Connection conn) throws SQLException { + tableEnvelope = null; + } + + @Override + public void add(Object value) throws SQLException { + if (value instanceof Geometry) { + if (tableEnvelope == null) { + tableEnvelope = ((Geometry) value).getEnvelopeInternal(); + } else { + tableEnvelope.expandToInclude(((Geometry) value).getEnvelopeInternal()); + } + } + } + + @Override + public Object getResult() throws SQLException { + return new GeometryFactory().toGeometry(tableEnvelope); + } + } + + private void testTableViewSpatialPredicate() throws SQLException { + deleteDb("spatial"); + try (Connection conn = getConnection(URL)) { + Statement stat = conn.createStatement(); + stat.execute("drop table if exists test"); + stat.execute("drop view if exists test_view"); + stat.execute("create table test(id int primary key, poly geometry)"); + stat.execute("insert into test values(1, 'POLYGON ((1 1, 1 2, 2 2, 1 1))')"); + stat.execute("insert into test values(4, null)"); + stat.execute("insert into test values(2, 'POLYGON ((3 1, 3 2, 4 2, 3 1))')"); + stat.execute("insert into test values(3, 'POLYGON ((1 3, 1 4, 2 4, 1 3))')"); + stat.execute("create view test_view as select * from test"); + + //Check result with view + ResultSet rs; + rs = stat.executeQuery( + "select * from test where poly && 'POINT (1.5 1.5)'::Geometry"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt("id")); + assertFalse(rs.next()); + + rs = stat.executeQuery( + "select * from test_view where poly && 'POINT (1.5 1.5)'::Geometry"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt("id")); + assertFalse(rs.next()); + rs.close(); + } + deleteDb("spatial"); + } + + /** + * Check ValueGeometry conversion into SQL script + */ + private void testValueGeometryScript() throws SQLException { + ValueGeometry valueGeometry = ValueGeometry.get("POINT Z(1 1 5)"); + try (Connection conn = getConnection(URL)) { + ResultSet rs = conn.createStatement().executeQuery( + "SELECT " + valueGeometry.getSQL(HasSQL.DEFAULT_SQL_FLAGS)); + assertTrue(rs.next()); + Object obj = rs.getObject(1); + ValueGeometry g = ValueGeometry.getFromGeometry(obj); + assertTrue("got: " + g + " exp: " + valueGeometry, valueGeometry.equals(g)); + } + } + + /** + * If the user mutate the geometry of the object, the object cache must not + * be updated. + */ + private void testInPlaceUpdate() throws SQLException { + try (Connection conn = getConnection(URL)) { + ResultSet rs = conn.createStatement().executeQuery( + "SELECT 'POINT(1 1)'::geometry"); + assertTrue(rs.next()); + // Mutate the geometry + ((Geometry) rs.getObject(1)).apply(new AffineTransformation(1, 0, + 1, 1, 0, 1)); + rs.close(); + rs = conn.createStatement().executeQuery( + "SELECT 'POINT(1 1)'::geometry"); + assertTrue(rs.next()); + // Check if the geometry is the one requested + assertEquals(1, ((Point) rs.getObject(1)).getX()); + assertEquals(1, ((Point) rs.getObject(1)).getY()); + rs.close(); + } + } + + private void testScanIndexOnNonSpatialQuery() throws SQLException { + deleteDb("spatial"); + try (Connection conn = getConnection(URL)) { + Statement stat = conn.createStatement(); + stat.execute("drop table if exists test"); + stat.execute("create table test(id serial primary key, " + + "v double, the_geom geometry)"); + stat.execute("create spatial index spatial on test(the_geom)"); + ResultSet rs = stat.executeQuery("explain select * from test where _ROWID_ = 5"); + assertTrue(rs.next()); + assertFalse(rs.getString(1).contains("/* PUBLIC.SPATIAL: _ROWID_ = " + + "5 */")); + } + deleteDb("spatial"); + } + + private void testStoreCorruption() throws SQLException { + deleteDb("spatial"); + try (Connection conn = getConnection(URL)) { + Statement stat = conn.createStatement(); + stat.execute("drop table if exists pt_cloud;\n" + + "CREATE TABLE PT_CLOUD AS " + + " SELECT CONCAT('POINT(',A.X,' ',B.X,')')::geometry the_geom from" + + " system_range(1e6,1e6+10) A,system_range(6e6,6e6+10) B;\n" + + "create spatial index pt_index on pt_cloud(the_geom);"); + // Wait some time + try { + Thread.sleep(1000); + } catch (InterruptedException ex) { + throw new SQLException(ex); + } + stat.execute("drop table if exists pt_cloud;\n" + + "CREATE TABLE PT_CLOUD AS " + + " SELECT CONCAT('POINT(',A.X,' ',B.X,')')::geometry the_geom from" + + " system_range(1e6,1e6+50) A,system_range(6e6,6e6+50) B;\n" + + "create spatial index pt_index on pt_cloud(the_geom);\n" + + "shutdown compact;"); + } + deleteDb("spatial"); + } + + private void testExplainSpatialIndexWithPk() throws SQLException { + deleteDb("spatial"); + try (Connection conn = getConnection(URL)) { + Statement stat = conn.createStatement(); + stat.execute("drop table if exists pt_cloud;"); + stat.execute("CREATE TABLE PT_CLOUD(id serial, the_geom geometry)"); + stat.execute("INSERT INTO PT_CLOUD(the_geom) " + + "SELECT 'POINT(' || A.X || ' ' || B.X || ')' " + + "from system_range(0,120) A,system_range(0,10) B;"); + stat.execute("create spatial index on pt_cloud(the_geom);"); + try (ResultSet rs = stat.executeQuery( + "explain select * from PT_CLOUD " + + "where the_geom && 'POINT(1 1)'")) { + assertTrue(rs.next()); + assertFalse("H2 should use spatial index got this explain:\n" + + rs.getString(1), rs.getString(1).contains("tableScan")); + } + } + deleteDb("spatial"); + } + + private void testNullableGeometry() throws SQLException { + deleteDb("spatial"); + Connection conn = getConnection(URL); + Statement stat = conn.createStatement(); + + stat.execute("create memory table test" + + "(id int primary key, the_geom geometry)"); + stat.execute("create spatial index on test(the_geom)"); + stat.execute("insert into test values(1, null)"); + stat.execute("insert into test values(2, null)"); + stat.execute("delete from test where the_geom is null"); + stat.execute("insert into test values(1, null)"); + stat.execute("insert into test values(2, null)"); + stat.execute("insert into test values(3, " + + "'POLYGON ((1000 2000, 1000 3000, 2000 3000, 1000 2000))')"); + stat.execute("insert into test values(4, null)"); + stat.execute("insert into test values(5, null)"); + stat.execute("insert into test values(6, " + + "'POLYGON ((1000 3000, 1000 4000, 2000 4000, 1000 3000))')"); + + ResultSet rs = stat.executeQuery("select * from test"); + int count = 0; + while (rs.next()) { + count++; + int id = rs.getInt(1); + if (id == 3 || id == 6) { + assertNotNull(rs.getObject(2)); + } else { + assertNull(rs.getObject(2)); + } + } + assertEquals(6, count); + + rs = stat.executeQuery("select * from test where the_geom is null"); + count = 0; + while (rs.next()) { + count++; + assertNull(rs.getObject(2)); + } + assertEquals(4, count); + + rs = stat.executeQuery("select * from test where the_geom is not null"); + count = 0; + while (rs.next()) { + count++; + assertNotNull(rs.getObject(2)); + } + assertEquals(2, count); + + rs = stat.executeQuery( + "select * from test " + + "where intersects(the_geom, " + + "'POLYGON ((1000 1000, 1000 2000, 2000 2000, 1000 1000))')"); + + conn.close(); + if (!config.memory) { + conn = getConnection(URL); + stat = conn.createStatement(); + rs = stat.executeQuery("select * from test"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertNull(rs.getObject(2)); + conn.close(); + } + deleteDb("spatial"); + } + + private void testNullableGeometryDelete() throws SQLException { + deleteDb("spatial"); + Connection conn = getConnection(URL); + Statement stat = conn.createStatement(); + stat.execute("create memory table test" + + "(id int primary key, the_geom geometry)"); + stat.execute("create spatial index on test(the_geom)"); + stat.execute("insert into test values(1, null)"); + stat.execute("insert into test values(2, null)"); + stat.execute("insert into test values(3, null)"); + ResultSet rs = stat.executeQuery("select * from test order by id"); + while (rs.next()) { + assertNull(rs.getObject(2)); + } + stat.execute("delete from test where id = 1"); + stat.execute("delete from test where id = 2"); + stat.execute("delete from test where id = 3"); + stat.execute("insert into test values(4, null)"); + stat.execute("insert into test values(5, null)"); + stat.execute("insert into test values(6, null)"); + stat.execute("delete from test where id = 4"); + stat.execute("delete from test where id = 5"); + stat.execute("delete from test where id = 6"); + conn.close(); + deleteDb("spatial"); + } + + private void testNullableGeometryInsert() throws SQLException { + deleteDb("spatial"); + Connection conn = getConnection(URL); + Statement stat = conn.createStatement(); + stat.execute("create memory table test" + + "(id identity, the_geom geometry)"); + stat.execute("create spatial index on test(the_geom)"); + for (int i = 0; i < 1000; i++) { + stat.execute("insert into test(the_geom) values null"); + } + ResultSet rs = stat.executeQuery("select * from test"); + while (rs.next()) { + assertNull(rs.getObject(2)); + } + conn.close(); + deleteDb("spatial"); + } + + private void testNullableGeometryUpdate() throws SQLException { + deleteDb("spatial"); + Connection conn = getConnection(URL); + Statement stat = conn.createStatement(); + stat.execute("create memory table test" + + "(id int primary key, the_geom geometry, description varchar2(32))"); + stat.execute("create spatial index on test(the_geom)"); + for (int i = 0; i < 1000; i++) { + stat.execute("insert into test values("+ (i + 1) +", null, null)"); + } + ResultSet rs = stat.executeQuery("select * from test"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertNull(rs.getObject(2)); + stat.execute("update test set description='DESCRIPTION' where id = 1"); + stat.execute("update test set description='DESCRIPTION' where id = 2"); + stat.execute("update test set description='DESCRIPTION' where id = 3"); + conn.close(); + deleteDb("spatial"); + } + + private void testIndexUpdateNullGeometry() throws SQLException { + deleteDb("spatial"); + Connection conn = getConnection(URL); + Statement stat = conn.createStatement(); + stat.execute("drop table if exists DUMMY_11;"); + stat.execute("CREATE TABLE PUBLIC.DUMMY_11 (fid serial, GEOM GEOMETRY);"); + stat.execute("CREATE SPATIAL INDEX PUBLIC_DUMMY_11_SPATIAL_INDEX on" + + " PUBLIC.DUMMY_11(GEOM);"); + stat.execute("insert into PUBLIC.DUMMY_11(geom) values(null);"); + stat.execute("update PUBLIC.DUMMY_11 set geom =" + + " 'POLYGON ((1 1, 5 1, 5 5, 1 5, 1 1))';"); + ResultSet rs = stat.executeQuery("select fid, GEOM from DUMMY_11 " + + "where GEOM && " + + "'POLYGON" + + "((1 1,5 1,5 5,1 5,1 1))';"); + try { + assertTrue(rs.next()); + assertEquals("POLYGON ((1 1, 5 1, 5 5, 1 5, 1 1))", rs.getString(2)); + } finally { + rs.close(); + } + // Update again the geometry elsewhere + stat.execute("update PUBLIC.DUMMY_11 set geom =" + + " 'POLYGON ((10 10, 50 10, 50 50, 10 50, 10 10))';"); + + rs = stat.executeQuery("select fid, GEOM from DUMMY_11 " + + "where GEOM && " + + "'POLYGON ((10 10, 50 10, 50 50, 10 50, 10 10))';"); + try { + assertTrue(rs.next()); + assertEquals("POLYGON ((10 10, 50 10, 50 50, 10 50, 10 10))", rs.getString(2)); + } finally { + rs.close(); + } + conn.close(); + deleteDb("spatial"); + } + + private void testInsertNull() throws SQLException { + deleteDb("spatial"); + try (Connection conn = getConnection(URL)) { + Statement stat = conn.createStatement(); + stat.execute("\n" + + "drop table if exists PUBLIC.DUMMY_12;\n" + + "CREATE TABLE PUBLIC.DUMMY_12 (\n" + + " \"fid\" serial,\n" + + " Z_ID INTEGER,\n" + + " GEOM GEOMETRY,\n" + + " CONSTRAINT CONSTRAINT_DUMMY_12 PRIMARY KEY (\"fid\")\n" + + ");\n" + + "CREATE INDEX PRIMARY_KEY_DUMMY_12 ON PUBLIC.DUMMY_12 (\"fid\");\n" + + "CREATE spatial INDEX PUBLIC_DUMMY_12_SPATIAL_INDEX_ ON PUBLIC.DUMMY_12 (GEOM);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (123,3125163,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (124,3125164,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (125,3125173,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (126,3125174,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (127,3125175,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (128,3125176,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (129,3125177,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (130,3125178,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (131,3125179,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (132,3125180,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (133,3125335,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (134,3125336,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (135,3125165,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (136,3125337,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (137,3125338,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (138,3125339,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (139,3125340,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (140,3125341,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (141,3125342,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (142,3125343,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (143,3125344,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (144,3125345,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (145,3125346,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (146,3125166,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (147,3125347,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (148,3125348,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (149,3125349,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (150,3125350,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (151,3125351,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (152,3125352,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (153,3125353,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (154,3125354,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (155,3125355,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (156,3125356,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (157,3125167,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (158,3125357,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (159,3125358,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (160,3125359,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (161,3125360,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (162,3125361,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (163,3125362,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (164,3125363,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (165,3125364,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (166,3125365,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (167,3125366,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (168,3125168,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (169,3125367,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (170,3125368,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (171,3125369,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (172,3125370,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (173,3125169,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (174,3125170,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (175,3125171,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (176,3125172,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (177,-2,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (178,-1,NULL);\n" + + "INSERT INTO PUBLIC.DUMMY_12 (\"fid\",Z_ID,GEOM) VALUES (179," + + "-1,NULL);"); + try (ResultSet rs = stat.executeQuery("select * from DUMMY_12")) { + assertTrue(rs.next()); + } + } + deleteDb("spatial"); + } + + private void testSpatialIndexWithOrder() throws SQLException { + deleteDb("spatial"); + try (Connection conn = getConnection(URL)) { + Statement stat = conn.createStatement(); + stat.execute("DROP TABLE IF EXISTS BUILDINGS;" + + "CREATE TABLE BUILDINGS (PK BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, " + + "THE_GEOM geometry);" + + "insert into buildings(the_geom) SELECT 'POINT(1 1)" + + "'::geometry from SYSTEM_RANGE(1,10000);\n" + + "CREATE SPATIAL INDEX ON PUBLIC.BUILDINGS(THE_GEOM);\n"); + + try (ResultSet rs = stat.executeQuery("EXPLAIN SELECT * FROM " + + "BUILDINGS ORDER BY PK LIMIT 51;")) { + assertTrue(rs.next()); + assertTrue(rs.getString(1).contains("PRIMARY_KEY")); + } + } + deleteDb("spatial"); + } +} diff --git a/h2/src/test/org/h2/test/db/TestSpeed.java b/h2/src/test/org/h2/test/db/TestSpeed.java new file mode 100644 index 0000000..3e4d6a8 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestSpeed.java @@ -0,0 +1,164 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.TimeUnit; + +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Various small performance tests. + */ +public class TestSpeed extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + + deleteDb("speed"); + Connection conn; + + conn = getConnection("speed"); + + Statement stat = conn.createStatement(); + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + int len = getSize(1, 10000); + for (int i = 0; i < len; i++) { + stat.execute("SELECT ID, NAME FROM TEST ORDER BY ID"); + } + + // drop table if exists test; + // CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255)); + // @LOOP 100000 INSERT INTO TEST VALUES(?, 'Hello'); + // @LOOP 100000 SELECT * FROM TEST WHERE ID = ?; + + // stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME + // VARCHAR(255))"); + // for(int i=0; i<1000; i++) { + // stat.execute("INSERT INTO TEST VALUES("+i+", 'Hello')"); + // } + // stat.execute("CREATE TABLE TEST_A(ID INT PRIMARY KEY, NAME + // VARCHAR(255))"); + // stat.execute("INSERT INTO TEST_A VALUES(0, 'Hello')"); + long time = System.nanoTime(); + // for(int i=1; i<8000; i*=2) { + // stat.execute("INSERT INTO TEST_A SELECT ID+"+i+", NAME FROM TEST_A"); + // + // // stat.execute("INSERT INTO TEST_A VALUES("+i+", 'Hello')"); + // } + // for(int i=0; i<4; i++) { + // ResultSet rs = stat.executeQuery("SELECT * FROM TEST_A"); + // while(rs.next()) { + // rs.getInt(1); + // rs.getString(2); + // } + // } + + // + // stat.execute("CREATE TABLE TEST_B(ID INT PRIMARY KEY, NAME + // VARCHAR(255))"); + // for(int i=0; i<80000; i++) { + // stat.execute("INSERT INTO TEST_B VALUES("+i+", 'Hello')"); + // } + + // conn.close(); + // System.exit(0); + // int testParser; + // java -Xrunhprof:cpu=samples,depth=8 -cp . org.h2.test.TestAll + // + // stat.execute("CREATE TABLE TEST(ID INT)"); + // stat.execute("INSERT INTO TEST VALUES(1)"); + // ResultSet rs = stat.executeQuery("SELECT ID OTHER_ID FROM TEST"); + // rs.next(); + // rs.getString("ID"); + // stat.execute("DROP TABLE TEST"); + + // long time = System.nanoTime(); + + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE CACHED TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST VALUES(?, ?)"); + + int max = getSize(1, 10000); + for (int i = 0; i < max; i++) { + prep.setInt(1, i); + prep.setString(2, + "abchelloasdfaldsjflajdflajdslfoajlskdfkjasdf" + + "abcfasdfadsfadfsalksdjflasjflajsdlkfjaksdjflkskd" + i); + prep.execute(); + } + + // System.exit(0); + // System.out.println("END "+Value.cacheHit+" "+Value.cacheMiss); + + time = System.nanoTime() - time; + trace(TimeUnit.NANOSECONDS.toMillis(time) + " insert"); + + // if(true) return; + + // if(config.log) { + // System.gc(); + // System.gc(); + // log("mem="+(Runtime.getRuntime().totalMemory() - + // Runtime.getRuntime().freeMemory())/1024); + // } + + // conn.close(); + + time = System.nanoTime(); + + prep = conn.prepareStatement("UPDATE TEST " + + "SET NAME='Another data row which is long' WHERE ID=?"); + for (int i = 0; i < max; i++) { + prep.setInt(1, i); + prep.execute(); + + // System.out.println("updated "+i); + // stat.execute("UPDATE TEST SET NAME='Another data row which is + // long' WHERE ID="+i); + // ResultSet rs = stat.executeQuery("SELECT * FROM TEST WHERE + // ID="+i); + // if(!rs.next()) { + // throw new AssertionError("hey! i="+i); + // } + // if(rs.next()) { + // throw new AssertionError("hey! i="+i); + // } + } + // for(int i=0; i tLazy) { + successCnt++; + if (i == 0) { + break; + } + } else { + failCnt++; + } + } + + if (failCnt > successCnt) { + fail("Lazy execution too slow. Avg lazy time: " + + (totalLazy / FAIL_REPEATS) + ", avg not lazy time: " + (totalNotLazy / FAIL_REPEATS)); + } + } + + /** + * @return Time of the query execution. + */ + private long executeAndCheckResult(Statement stmt, String sql, boolean lazy, int expected) throws SQLException { + if (lazy) { + stmt.execute("SET LAZY_QUERY_EXECUTION 1"); + } + else { + stmt.execute("SET LAZY_QUERY_EXECUTION 0"); + } + + long t0 = System.currentTimeMillis(); + try (ResultSet rs = stmt.executeQuery(sql)) { + rs.next(); + assertEquals(expected, rs.getInt(1)); + } + + return System.currentTimeMillis() - t0; + } +} diff --git a/h2/src/test/org/h2/test/db/TestSynonymForTable.java b/h2/src/test/org/h2/test/db/TestSynonymForTable.java new file mode 100644 index 0000000..61c0408 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestSynonymForTable.java @@ -0,0 +1,333 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests for table synonyms. + */ +public class TestSynonymForTable extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + testSelectFromSynonym(); + testInsertIntoSynonym(); + testInsertWithColumnNameIntoSynonym(); + testUpdate(); + testDeleteFromSynonym(); + testTruncateSynonym(); + testExistingTableName(); + testCreateForUnknownTable(); + testMetaData(); + testCreateOrReplace(); + testCreateOrReplaceExistingTable(); + testSynonymInDifferentSchema(); + testReopenDatabase(); + testDropSynonym(); + testDropTable(); + testDropSchema(); + } + + private void testUpdate() throws SQLException { + Connection conn = getConnection("synonym"); + createTableWithSynonym(conn); + insertIntoSynonym(conn, 25); + + Statement stmnt = conn.createStatement(); + assertEquals(1, stmnt.executeUpdate("UPDATE testsynonym set id = 30 WHERE id = 25")); + + assertSynonymContains(conn, 30); + + conn.close(); + } + + private void testDropSchema() throws SQLException { + Connection conn = getConnection("synonym"); + Statement stat = conn.createStatement(); + + stat.execute("CREATE SCHEMA IF NOT EXISTS s1"); + stat.execute("CREATE TABLE IF NOT EXISTS s1.backingtable(id INT PRIMARY KEY)"); + stat.execute("CREATE OR REPLACE SYNONYM testsynonym FOR s1.backingtable"); + stat.execute("DROP SCHEMA s1 CASCADE"); + + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1, stat).execute("SELECT id FROM testsynonym"); + conn.close(); + } + + private void testDropTable() throws SQLException { + Connection conn = getConnection("synonym"); + createTableWithSynonym(conn); + Statement stat = conn.createStatement(); + stat.execute("DROP TABLE backingtable"); + + // Backing table does not exist anymore. + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1, stat).execute("SELECT id FROM testsynonym"); + + // Synonym should be dropped as well + ResultSet synonyms = conn.createStatement().executeQuery( + "SELECT * FROM INFORMATION_SCHEMA.SYNONYMS WHERE SYNONYM_NAME='TESTSYNONYM'"); + assertFalse(synonyms.next()); + conn.close(); + + // Reopening should work with dropped synonym + Connection conn2 = getConnection("synonym"); + assertThrows(ErrorCode.OBJECT_CLOSED, stat).execute("SELECT id FROM testsynonym"); + conn2.close(); + } + + private void testDropSynonym() throws SQLException { + Connection conn = getConnection("synonym"); + createTableWithSynonym(conn); + Statement stat = conn.createStatement(); + + stat.execute("DROP SYNONYM testsynonym"); + + // Synonym does not exist anymore. + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, stat).execute("SELECT id FROM testsynonym"); + + // Dropping with "if exists" should succeed even if the synonym does not exist anymore. + stat.execute("DROP SYNONYM IF EXISTS testsynonym"); + + // Without "if exists" the command should fail if the synonym does not exist. + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, stat).execute("DROP SYNONYM testsynonym"); + conn.close(); + } + + private void testSynonymInDifferentSchema() throws SQLException { + Connection conn = getConnection("synonym"); + Statement stat = conn.createStatement(); + + stat.execute("CREATE SCHEMA IF NOT EXISTS s1"); + stat.execute("CREATE TABLE IF NOT EXISTS s1.backingtable(id INT PRIMARY KEY)"); + stat.execute("TRUNCATE TABLE s1.backingtable"); + stat.execute("CREATE OR REPLACE SYNONYM testsynonym FOR s1.backingtable"); + stat.execute("INSERT INTO s1.backingtable VALUES(15)"); + assertSynonymContains(conn, 15); + conn.close(); + } + + private void testCreateOrReplaceExistingTable() throws SQLException { + Connection conn = getConnection("synonym"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE IF NOT EXISTS backingtable(id INT PRIMARY KEY)"); + + assertThrows(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, stat) + .execute("CREATE OR REPLACE SYNONYM backingtable FOR backingtable"); + conn.close(); + } + + private void testCreateOrReplace() throws SQLException { + // start with a fresh db so the first create or replace has to actually create the synonym. + deleteDb("synonym"); + Connection conn = getConnection("synonym"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE IF NOT EXISTS backingtable(id INT PRIMARY KEY)"); + stat.execute("CREATE TABLE IF NOT EXISTS backingtable2(id INT PRIMARY KEY)"); + stat.execute("CREATE OR REPLACE SYNONYM testsynonym FOR backingtable"); + insertIntoBackingTable(conn, 17); + + ResultSet rs = stat.executeQuery("SELECT id FROM testsynonym"); + assertTrue(rs.next()); + assertEquals(17, rs.getInt(1)); + + stat.execute("CREATE OR REPLACE SYNONYM testsynonym FOR backingtable2"); + + // Should not return a result, since backingtable2 is empty. + ResultSet rs2 = stat.executeQuery("SELECT id FROM testsynonym"); + assertFalse(rs2.next()); + conn.close(); + + deleteDb("synonym"); + } + + private void testMetaData() throws SQLException { + Connection conn = getConnection("synonym"); + createTableWithSynonym(conn); + + ResultSet tables = conn.getMetaData().getTables(null, Constants.SCHEMA_MAIN, null, + new String[]{"SYNONYM"}); + assertTrue(tables.next()); + assertEquals(tables.getString("TABLE_NAME"), "TESTSYNONYM"); + assertEquals(tables.getString("TABLE_TYPE"), "SYNONYM"); + assertFalse(tables.next()); + + ResultSet columns = conn.getMetaData().getColumns(null, Constants.SCHEMA_MAIN, "TESTSYNONYM", null); + assertTrue(columns.next()); + assertEquals(columns.getString("TABLE_NAME"), "TESTSYNONYM"); + assertEquals(columns.getString("COLUMN_NAME"), "ID"); + assertFalse(columns.next()); + + ResultSet synonyms = conn.createStatement().executeQuery("SELECT * FROM INFORMATION_SCHEMA.SYNONYMS"); + assertTrue(synonyms.next()); + assertEquals("SYNONYM", synonyms.getString("SYNONYM_CATALOG")); + assertEquals("PUBLIC", synonyms.getString("SYNONYM_SCHEMA")); + assertEquals("TESTSYNONYM", synonyms.getString("SYNONYM_NAME")); + assertEquals("BACKINGTABLE", synonyms.getString("SYNONYM_FOR")); + assertEquals("VALID", synonyms.getString("STATUS")); + assertNull(synonyms.getString("REMARKS")); + assertFalse(synonyms.next()); + conn.close(); + } + + private void testCreateForUnknownTable() throws SQLException { + Connection conn = getConnection("synonym"); + Statement stat = conn.createStatement(); + + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, stat) + .execute("CREATE SYNONYM someSynonym FOR nonexistingTable"); + conn.close(); + } + + private void testExistingTableName() throws SQLException { + Connection conn = getConnection("synonym"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE IF NOT EXISTS backingtable(id INT PRIMARY KEY)"); + + assertThrows(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, stat) + .execute("CREATE SYNONYM backingtable FOR backingtable"); + conn.close(); + } + + /** + * Make sure, that the schema changes are persisted when reopening the database + */ + private void testReopenDatabase() throws SQLException { + if(!config.memory) { + deleteDb("synonym"); + Connection conn = getConnection("synonym"); + createTableWithSynonym(conn); + insertIntoBackingTable(conn, 9); + conn.close(); + Connection conn2 = getConnection("synonym"); + assertSynonymContains(conn2, 9); + conn2.close(); + } + } + + private void testTruncateSynonym() throws SQLException { + Connection conn = getConnection("synonym"); + createTableWithSynonym(conn); + + insertIntoBackingTable(conn, 7); + assertBackingTableContains(conn, 7); + + conn.createStatement().execute("TRUNCATE TABLE testsynonym"); + + assertBackingTableIsEmpty(conn); + conn.close(); + } + + private void testDeleteFromSynonym() throws SQLException { + Connection conn = getConnection("synonym"); + createTableWithSynonym(conn); + + insertIntoBackingTable(conn, 7); + assertBackingTableContains(conn, 7); + deleteFromSynonym(conn, 7); + + assertBackingTableIsEmpty(conn); + conn.close(); + } + + private static void deleteFromSynonym(Connection conn, int id) throws SQLException { + PreparedStatement prep = conn.prepareStatement( + "DELETE FROM testsynonym WHERE id = ?"); + prep.setInt(1, id); + prep.execute(); + } + + private void assertBackingTableIsEmpty(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("SELECT id FROM backingtable"); + assertFalse(rs.next()); + } + + private void testInsertIntoSynonym() throws SQLException { + Connection conn = getConnection("synonym"); + createTableWithSynonym(conn); + + insertIntoSynonym(conn, 5); + assertBackingTableContains(conn, 5); + conn.close(); + } + + private void testInsertWithColumnNameIntoSynonym() throws SQLException { + Connection conn = getConnection("synonym"); + createTableWithSynonym(conn); + + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO testsynonym (id) VALUES(?)"); + prep.setInt(1, 55); + prep.execute(); + assertBackingTableContains(conn, 55); + conn.close(); + } + + private void assertBackingTableContains(Connection conn, int testValue) throws SQLException { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("SELECT id FROM backingtable"); + assertTrue(rs.next()); + assertEquals(testValue, rs.getInt(1)); + assertFalse(rs.next()); + } + + private void testSelectFromSynonym() throws SQLException { + deleteDb("synonym"); + Connection conn = getConnection("synonym"); + createTableWithSynonym(conn); + insertIntoBackingTable(conn, 1); + assertSynonymContains(conn, 1); + conn.close(); + } + + private void assertSynonymContains(Connection conn, int id) throws SQLException { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("SELECT id FROM testsynonym"); + assertTrue(rs.next()); + assertEquals(id, rs.getInt(1)); + assertFalse(rs.next()); + } + + private static void insertIntoSynonym(Connection conn, int id) throws SQLException { + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO testsynonym VALUES(?)"); + prep.setInt(1, id); + prep.execute(); + } + + private static void insertIntoBackingTable(Connection conn, int id) throws SQLException { + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO backingtable VALUES(?)"); + prep.setInt(1, id); + prep.execute(); + } + + private static void createTableWithSynonym(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE IF NOT EXISTS backingtable(id INT PRIMARY KEY)"); + stat.execute("CREATE OR REPLACE SYNONYM testsynonym FOR backingtable"); + stat.execute("TRUNCATE TABLE backingtable"); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestTableEngines.java b/h2/src/test/org/h2/test/db/TestTableEngines.java new file mode 100644 index 0000000..a87646f --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestTableEngines.java @@ -0,0 +1,1152 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import org.h2.api.TableEngine; +import org.h2.command.ddl.CreateTableData; +import org.h2.command.query.AllColumnsForPlan; +import org.h2.engine.SessionLocal; +import org.h2.index.Cursor; +import org.h2.index.Index; +import org.h2.index.IndexType; +import org.h2.index.SingleRowCursor; +import org.h2.message.DbException; +import org.h2.result.Row; +import org.h2.result.SearchRow; +import org.h2.result.SortOrder; +import org.h2.table.IndexColumn; +import org.h2.table.Table; +import org.h2.table.TableBase; +import org.h2.table.TableFilter; +import org.h2.table.TableType; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.value.Value; +import org.h2.value.ValueInteger; +import org.h2.value.ValueNull; + +/** + * The class for external table engines mechanism testing. + * + * @author Sergi Vladykin + */ +public class TestTableEngines extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String[] a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testQueryExpressionFlag(); + testSubQueryInfo(); + testEngineParams(); + testSchemaEngineParams(); + testSimpleQuery(); + testMultiColumnTreeSetIndex(); + } + + private void testEngineParams() throws SQLException { + deleteDb("tableEngine"); + Connection conn = getConnection("tableEngine"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE t1(id int, name varchar) ENGINE \"" + + EndlessTableEngine.class.getName() + "\" WITH \"param1\", \"param2\""); + assertEquals(2, + EndlessTableEngine.createTableData.tableEngineParams.size()); + assertEquals("param1", + EndlessTableEngine.createTableData.tableEngineParams.get(0)); + assertEquals("param2", + EndlessTableEngine.createTableData.tableEngineParams.get(1)); + stat.execute("CREATE TABLE t2(id int, name varchar) WITH \"param1\", \"param2\""); + assertEquals(2, + EndlessTableEngine.createTableData.tableEngineParams.size()); + assertEquals("param1", + EndlessTableEngine.createTableData.tableEngineParams.get(0)); + assertEquals("param2", + EndlessTableEngine.createTableData.tableEngineParams.get(1)); + conn.close(); + if (!config.memory) { + // Test serialization of table parameters + EndlessTableEngine.createTableData.tableEngineParams.clear(); + conn = getConnection("tableEngine"); + assertEquals(2, + EndlessTableEngine.createTableData.tableEngineParams.size()); + assertEquals("param1", + EndlessTableEngine.createTableData.tableEngineParams.get(0)); + assertEquals("param2", + EndlessTableEngine.createTableData.tableEngineParams.get(1)); + conn.close(); + } + // Prevent memory leak + EndlessTableEngine.createTableData = null; + deleteDb("tableEngine"); + } + + private void testSchemaEngineParams() throws SQLException { + deleteDb("tableEngine"); + Connection conn = getConnection("tableEngine"); + Statement stat = conn.createStatement(); + stat.execute("CREATE SCHEMA s1 WITH \"param1\", \"param2\""); + + stat.execute("CREATE TABLE s1.t1(id int, name varchar) ENGINE \"" + + EndlessTableEngine.class.getName() + '\"'); + assertEquals(2, + EndlessTableEngine.createTableData.tableEngineParams.size()); + assertEquals("param1", + EndlessTableEngine.createTableData.tableEngineParams.get(0)); + assertEquals("param2", + EndlessTableEngine.createTableData.tableEngineParams.get(1)); + conn.close(); + // Prevent memory leak + EndlessTableEngine.createTableData = null; + deleteDb("tableEngine"); + } + + private void testSimpleQuery() throws SQLException { + + deleteDb("tableEngine"); + + Connection conn = getConnection("tableEngine"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE t1(id int, name varchar) ENGINE \"" + + OneRowTableEngine.class.getName() + "\""); + + testStatements(stat); + + stat.close(); + conn.close(); + + if (!config.memory) { + conn = getConnection("tableEngine"); + stat = conn.createStatement(); + + ResultSet rs = stat.executeQuery("SELECT name FROM t1"); + assertFalse(rs.next()); + rs.close(); + + testStatements(stat); + + stat.close(); + conn.close(); + } + + deleteDb("tableEngine"); + + } + + private void testStatements(Statement stat) throws SQLException { + assertEquals(stat.executeUpdate("INSERT INTO t1 VALUES(2, 'abc')"), 1); + assertEquals(stat.executeUpdate("UPDATE t1 SET name = 'abcdef' WHERE id=2"), 1); + assertEquals(stat.executeUpdate("INSERT INTO t1 VALUES(3, 'abcdefghi')"), 1); + + assertEquals(stat.executeUpdate("DELETE FROM t1 WHERE id=2"), 0); + assertEquals(stat.executeUpdate("DELETE FROM t1 WHERE id=3"), 1); + + ResultSet rs = stat.executeQuery("SELECT name FROM t1"); + assertFalse(rs.next()); + rs.close(); + + assertEquals(stat.executeUpdate("INSERT INTO t1 VALUES(2, 'abc')"), 1); + assertEquals(stat.executeUpdate("UPDATE t1 SET name = 'abcdef' WHERE id=2"), 1); + assertEquals(stat.executeUpdate("INSERT INTO t1 VALUES(3, 'abcdefghi')"), 1); + + rs = stat.executeQuery("SELECT name FROM t1"); + assertTrue(rs.next()); + assertEquals(rs.getString(1), "abcdefghi"); + assertFalse(rs.next()); + rs.close(); + + } + + private void testMultiColumnTreeSetIndex() throws SQLException { + deleteDb("tableEngine"); + Connection conn = getConnection("tableEngine"); + Statement stat = conn.createStatement(); + + stat.executeUpdate("CREATE TABLE T(A INT, B VARCHAR, C BIGINT, " + + "D BIGINT DEFAULT 0) ENGINE \"" + + TreeSetIndexTableEngine.class.getName() + "\""); + + stat.executeUpdate("CREATE INDEX IDX_C_B_A ON T(C, B, A)"); + stat.executeUpdate("CREATE INDEX IDX_B_A ON T(B, A)"); + + List> dataSet = new ArrayList<>(); + + dataSet.add(Arrays.asList(1, "1", 1L)); + dataSet.add(Arrays.asList(1, "0", 2L)); + dataSet.add(Arrays.asList(2, "0", -1L)); + dataSet.add(Arrays.asList(0, "0", 1L)); + dataSet.add(Arrays.asList(0, "1", null)); + dataSet.add(Arrays.asList(2, null, 0L)); + + PreparedStatement prep = conn.prepareStatement("INSERT INTO T(A,B,C) VALUES(?,?,?)"); + for (List row : dataSet) { + for (int i = 0; i < row.size(); i++) { + prep.setObject(i + 1, row.get(i)); + } + assertEquals(1, prep.executeUpdate()); + } + prep.close(); + + checkPlan(stat, "select max(c) from t", "direct lookup"); + checkPlan(stat, "select min(c) from t", "direct lookup"); + checkPlan(stat, "select count(*) from t", "direct lookup"); + + checkPlan(stat, "select * from t", "scan"); + + checkPlan(stat, "select * from t order by c", "IDX_C_B_A"); + checkPlan(stat, "select * from t order by c, b", "IDX_C_B_A"); + checkPlan(stat, "select * from t order by b", "IDX_B_A"); + checkPlan(stat, "select * from t order by b, a", "IDX_B_A"); + checkPlan(stat, "select * from t order by b, c", "scan"); + checkPlan(stat, "select * from t order by a, b", "scan"); + checkPlan(stat, "select * from t order by a, c, b", "scan"); + + checkPlan(stat, "select * from t where b > ''", "IDX_B_A"); + checkPlan(stat, "select * from t where a > 0 and b > ''", "IDX_B_A"); + checkPlan(stat, "select * from t where b < ''", "IDX_B_A"); + checkPlan(stat, "select * from t where b < '' and c < 1", "IDX_C_B_A"); + checkPlan(stat, "select * from t where a = 0", "scan"); + checkPlan(stat, "select * from t where a > 0 order by c, b", "IDX_C_B_A"); + checkPlan(stat, "select * from t where a = 0 and c > 0", "IDX_C_B_A"); + checkPlan(stat, "select * from t where a = 0 and b < '0'", "IDX_B_A"); + + assertEquals(6, ((Number) query(stat, "select count(*) from t").get(0).get(0)).intValue()); + + checkResultsNoOrder(stat, 6, "select * from t", "select * from t order by a"); + checkResultsNoOrder(stat, 6, "select * from t", "select * from t order by b"); + checkResultsNoOrder(stat, 6, "select * from t", "select * from t order by c"); + checkResultsNoOrder(stat, 6, "select * from t", "select * from t order by c, a"); + checkResultsNoOrder(stat, 6, "select * from t", "select * from t order by b, a"); + checkResultsNoOrder(stat, 6, "select * from t", "select * from t order by c, b, a"); + checkResultsNoOrder(stat, 6, "select * from t", "select * from t order by a, c, b"); + + checkResultsNoOrder(stat, 4, "select * from t where a > 0", + "select * from t where a > 0 order by a"); + checkResultsNoOrder(stat, 4, "select * from t where a > 0", + "select * from t where a > 0 order by b"); + checkResultsNoOrder(stat, 4, "select * from t where a > 0", + "select * from t where a > 0 order by c"); + checkResultsNoOrder(stat, 4, "select * from t where a > 0", + "select * from t where a > 0 order by c, a"); + checkResultsNoOrder(stat, 4, "select * from t where a > 0", + "select * from t where a > 0 order by b, a"); + checkResultsNoOrder(stat, 4, "select * from t where a > 0", + "select * from t where a > 0 order by c, b, a"); + checkResultsNoOrder(stat, 4, "select * from t where a > 0", + "select * from t where a > 0 order by a, c, b"); + + checkResults(6, dataSet, stat, + "select * from t order by a", null, new RowComparator(0)); + checkResults(6, dataSet, stat, + "select * from t order by a desc", null, new RowComparator(true, 0)); + checkResults(6, dataSet, stat, + "select * from t order by b, c", null, new RowComparator(1, 2)); + checkResults(6, dataSet, stat, + "select * from t order by c, a", null, new RowComparator(2, 0)); + checkResults(6, dataSet, stat, + "select * from t order by b, a", null, new RowComparator(1, 0)); + checkResults(6, dataSet, stat, + "select * from t order by c, b, a", null, new RowComparator(2, 1, 0)); + + checkResults(4, dataSet, stat, + "select * from t where a > 0", new RowFilter() { + @Override + protected boolean accept(List row) { + return getInt(row, 0) > 0; + } + }, null); + checkResults(3, dataSet, stat, "select * from t where b = '0'", new RowFilter() { + @Override + protected boolean accept(List row) { + return "0".equals(getString(row, 1)); + } + }, null); + checkResults(5, dataSet, stat, "select * from t where b >= '0'", new RowFilter() { + @Override + protected boolean accept(List row) { + String b = getString(row, 1); + return b != null && b.compareTo("0") >= 0; + } + }, null); + checkResults(2, dataSet, stat, "select * from t where b > '0'", new RowFilter() { + @Override + protected boolean accept(List row) { + String b = getString(row, 1); + return b != null && b.compareTo("0") > 0; + } + }, null); + checkResults(1, dataSet, stat, "select * from t where b > '0' and c > 0", new RowFilter() { + @Override + protected boolean accept(List row) { + String b = getString(row, 1); + Long c = getLong(row, 2); + return b != null && b.compareTo("0") > 0 && c != null && c > 0; + } + }, null); + checkResults(1, dataSet, stat, "select * from t where b > '0' and c < 2", new RowFilter() { + @Override + protected boolean accept(List row) { + String b = getString(row, 1); + Long c = getLong(row, 2); + return b != null && b.compareTo("0") > 0 && c != null && c < 2; + } + }, null); + checkResults(2, dataSet, stat, "select * from t where b > '0' and a < 2", new RowFilter() { + @Override + protected boolean accept(List row) { + Integer a = getInt(row, 0); + String b = getString(row, 1); + return b != null && b.compareTo("0") > 0 && a != null && a < 2; + } + }, null); + checkResults(1, dataSet, stat, "select * from t where b > '0' and a > 0", new RowFilter() { + @Override + protected boolean accept(List row) { + Integer a = getInt(row, 0); + String b = getString(row, 1); + return b != null && b.compareTo("0") > 0 && a != null && a > 0; + } + }, null); + checkResults(2, dataSet, stat, "select * from t where b = '0' and a > 0", new RowFilter() { + @Override + protected boolean accept(List row) { + Integer a = getInt(row, 0); + String b = getString(row, 1); + return "0".equals(b) && a != null && a > 0; + } + }, null); + checkResults(2, dataSet, stat, "select * from t where b = '0' and a < 2", new RowFilter() { + @Override + protected boolean accept(List row) { + Integer a = getInt(row, 0); + String b = getString(row, 1); + return "0".equals(b) && a != null && a < 2; + } + }, null); + conn.close(); + deleteDb("tableEngine"); + } + + private void testQueryExpressionFlag() throws SQLException { + deleteDb("testQueryExpressionFlag"); + Connection conn = getConnection("testQueryExpressionFlag"); + Statement stat = conn.createStatement(); + stat.execute("create table QUERY_EXPR_TEST(id int) ENGINE \"" + + TreeSetIndexTableEngine.class.getName() + "\""); + stat.execute("create table QUERY_EXPR_TEST_NO(id int) ENGINE \"" + + TreeSetIndexTableEngine.class.getName() + "\""); + stat.executeQuery("select 1 + (select 1 from QUERY_EXPR_TEST)").next(); + stat.executeQuery("select 1 from QUERY_EXPR_TEST_NO where id in " + + "(select id from QUERY_EXPR_TEST)"); + stat.executeQuery("select 1 from QUERY_EXPR_TEST_NO n " + + "where exists(select 1 from QUERY_EXPR_TEST y where y.id = n.id)"); + conn.close(); + deleteDb("testQueryExpressionFlag"); + } + + private void testSubQueryInfo() throws SQLException { + deleteDb("testSubQueryInfo"); + Connection conn = getConnection("testSubQueryInfo"); + Statement stat = conn.createStatement(); + stat.execute("create table SUB_QUERY_TEST(id int primary key, name varchar) ENGINE \"" + + TreeSetIndexTableEngine.class.getName() + "\""); + // test sub-queries + stat.executeQuery("select * from " + + "(select t2.id from " + + "(select t3.id from sub_query_test t3 where t3.name = '') t4, " + + "sub_query_test t2 " + + "where t2.id = t4.id) t5").next(); + // test view 1 + stat.execute("create view t4 as (select t3.id from sub_query_test t3 where t3.name = '')"); + stat.executeQuery("select * from " + + "(select t2.id from t4, sub_query_test t2 where t2.id = t4.id) t5").next(); + // test view 2 + stat.execute("create view t5 as " + + "(select t2.id from t4, sub_query_test t2 where t2.id = t4.id)"); + stat.executeQuery("select * from t5").next(); + // test select expressions + stat.execute("create table EXPR_TEST(id int) ENGINE \"" + + TreeSetIndexTableEngine.class.getName() + "\""); + stat.executeQuery("select * from (select (select id from EXPR_TEST x limit 1) a " + + "from dual where 1 = (select id from EXPR_TEST y limit 1)) z").next(); + // test select expressions 2 + stat.execute("create table EXPR_TEST2(id int) ENGINE \"" + + TreeSetIndexTableEngine.class.getName() + "\""); + stat.executeQuery("select * from (select (select 1 from " + + "(select (select 2 from EXPR_TEST) from EXPR_TEST2) ZZ) from dual)").next(); + // test select expression plan + stat.execute("create table test_plan(id int primary key, name varchar)"); + stat.execute("create index MY_NAME_INDEX on test_plan(name)"); + checkPlan(stat, "select * from (select (select id from test_plan " + + "where name = 'z') from dual)", + "MY_NAME_INDEX"); + conn.close(); + deleteDb("testSubQueryInfo"); + } + + /** + * A static assertion method. + * + * @param condition the condition + * @param message the error message + */ + static void assert0(boolean condition, String message) { + if (!condition) { + throw new AssertionError(message); + } + } + + private void checkResultsNoOrder(Statement stat, int size, String query1, String query2) + throws SQLException { + List> res1 = query(stat, query1); + List> res2 = query(stat, query2); + if (size != res1.size() || size != res2.size()) { + fail("Wrong size: \n" + res1 + "\n" + res2); + } + if (size == 0) { + return; + } + int[] cols = new int[res1.get(0).size()]; + for (int i = 0; i < cols.length; i++) { + cols[i] = i; + } + Comparator> comp = new RowComparator(cols); + res1.sort(comp); + res2.sort(comp); + assertTrue("Wrong data: \n" + res1 + "\n" + res2, res1.equals(res2)); + } + + private void checkResults(int size, List> dataSet, + Statement stat, String query, RowFilter filter, RowComparator sort) + throws SQLException { + List> res1 = query(stat, query); + List> res2 = query(dataSet, filter, sort); + + assertTrue("Wrong size: " + size + " \n" + res1 + "\n" + res2, + res1.size() == size && res2.size() == size); + assertTrue(filter != null || sort != null); + + for (int i = 0; i < res1.size(); i++) { + List row1 = res1.get(i); + List row2 = res2.get(i); + + assertTrue("Filter failed on row " + i + " of \n" + res1 + "\n" + res2, + filter == null || filter.accept(row1)); + assertTrue("Sort failed on row " + i + " of \n" + res1 + "\n" + res2, + sort == null || sort.compare(row1, row2) == 0); + } + } + + private static List> query(List> dataSet, + RowFilter filter, RowComparator sort) { + List> res = new ArrayList<>(); + if (filter == null) { + res.addAll(dataSet); + } else { + for (List row : dataSet) { + if (filter.accept(row)) { + res.add(row); + } + } + } + if (sort != null) { + res.sort(sort); + } + return res; + } + + private static List> query(Statement stat, String query) throws SQLException { + ResultSet rs = stat.executeQuery(query); + int cols = rs.getMetaData().getColumnCount(); + List> list = new ArrayList<>(); + while (rs.next()) { + List row = new ArrayList<>(cols); + for (int i = 1; i <= cols; i++) { + row.add(rs.getObject(i)); + } + list.add(row); + } + rs.close(); + return list; + } + + private void checkPlan(Statement stat, String query, String index) + throws SQLException { + String plan = query(stat, "EXPLAIN " + query).get(0).get(0).toString(); + assertTrue("Index '" + index + "' is not used in query plan: " + plan, + plan.contains(index)); + } + + /** + * A test table factory. + */ + public static class OneRowTableEngine implements TableEngine { + + /** + * A table implementation with one row. + */ + private static class OneRowTable extends TableBase { + + /** + * A scan index for one row. + */ + public class Scan extends Index { + + Scan(Table table) { + super(table, table.getId(), table.getName() + "_SCAN", + IndexColumn.wrap(table.getColumns()), 0, IndexType.createScan(false)); + } + + @Override + public long getRowCountApproximation(SessionLocal session) { + return table.getRowCountApproximation(session); + } + + @Override + public long getDiskSpaceUsed() { + return table.getDiskSpaceUsed(); + } + + @Override + public long getRowCount(SessionLocal session) { + return table.getRowCount(session); + } + + @Override + public void truncate(SessionLocal session) { + // do nothing + } + + @Override + public void remove(SessionLocal session) { + // do nothing + } + + @Override + public void remove(SessionLocal session, Row r) { + // do nothing + } + + @Override + public boolean needRebuild() { + return false; + } + + @Override + public double getCost(SessionLocal session, int[] masks, + TableFilter[] filters, int filter, SortOrder sortOrder, + AllColumnsForPlan allColumnsSet) { + return 0; + } + + @Override + public Cursor findFirstOrLast(SessionLocal session, boolean first) { + return new SingleRowCursor(row); + } + + @Override + public Cursor find(SessionLocal session, SearchRow first, SearchRow last) { + return new SingleRowCursor(row); + } + + @Override + public void close(SessionLocal session) { + // do nothing + } + + @Override + public boolean canGetFirstOrLast() { + return true; + } + + @Override + public void add(SessionLocal session, Row r) { + // do nothing + } + } + + protected Index scanIndex; + + volatile Row row; + + OneRowTable(CreateTableData data) { + super(data); + scanIndex = new Scan(this); + } + + @Override + public Index addIndex(SessionLocal session, String indexName, int indexId, IndexColumn[] cols, + int uniqueColumnCount, IndexType indexType, boolean create, String indexComment) { + return null; + } + + @Override + public void addRow(SessionLocal session, Row r) { + this.row = r; + } + + @Override + public boolean canDrop() { + return true; + } + + @Override + public boolean canGetRowCount(SessionLocal session) { + return true; + } + + @Override + public void checkSupportAlter() { + // do nothing + } + + @Override + public void close(SessionLocal session) { + // do nothing + } + + @Override + public ArrayList getIndexes() { + return null; + } + + @Override + public long getMaxDataModificationId() { + return 0; + } + + @Override + public long getRowCount(SessionLocal session) { + return getRowCountApproximation(session); + } + + @Override + public long getRowCountApproximation(SessionLocal session) { + return row == null ? 0 : 1; + } + + @Override + public Index getScanIndex(SessionLocal session) { + return scanIndex; + } + + @Override + public TableType getTableType() { + return TableType.EXTERNAL_TABLE_ENGINE; + } + + @Override + public boolean isDeterministic() { + return false; + } + + @Override + public void removeRow(SessionLocal session, Row r) { + this.row = null; + } + + @Override + public long truncate(SessionLocal session) { + long result = row != null ? 1L : 0L; + row = null; + return result; + } + + } + + /** + * Create a new OneRowTable. + * + * @param data the meta data of the table to create + * @return the new table + */ + @Override + public OneRowTable createTable(CreateTableData data) { + return new OneRowTable(data); + } + + } + + /** + * A test table factory. + */ + public static class EndlessTableEngine implements TableEngine { + + public static CreateTableData createTableData; + + /** + * A table implementation with one row. + */ + private static class EndlessTable extends OneRowTableEngine.OneRowTable { + + EndlessTable(CreateTableData data) { + super(data); + row = Row.get(new Value[] { ValueInteger.get(1), ValueNull.INSTANCE }, 0); + scanIndex = new Auto(this); + } + + /** + * A scan index for one row. + */ + public class Auto extends OneRowTableEngine.OneRowTable.Scan { + + Auto(Table table) { + super(table); + } + + @Override + public Cursor find(SessionLocal session, SearchRow first, SearchRow last) { + return new SingleRowCursor(row); + } + + } + + } + + /** + * Create a new table. + * + * @param data the meta data of the table to create + * @return the new table + */ + @Override + public EndlessTable createTable(CreateTableData data) { + createTableData = data; + return new EndlessTable(data); + } + + } + + /** + * A table engine that internally uses a tree set. + */ + public static class TreeSetIndexTableEngine implements TableEngine { + + static TreeSetTable created; + + @Override + public Table createTable(CreateTableData data) { + return created = new TreeSetTable(data); + } + } + + /** + * A table that internally uses a tree set. + */ + private static class TreeSetTable extends TableBase { + int dataModificationId; + + ArrayList indexes; + + TreeSetIndex scan = new TreeSetIndex(this, "scan", + IndexColumn.wrap(getColumns()), IndexType.createScan(false)) { + @Override + public double getCost(SessionLocal session, int[] masks, + TableFilter[] filters, int filter, SortOrder sortOrder, + AllColumnsForPlan allColumnsSet) { + return getCostRangeIndex(masks, getRowCount(session), filters, + filter, sortOrder, true, allColumnsSet); + } + }; + + TreeSetTable(CreateTableData data) { + super(data); + } + + @Override + public long truncate(SessionLocal session) { + long result = getRowCountApproximation(session); + if (indexes != null) { + for (Index index : indexes) { + index.truncate(session); + } + } else { + scan.truncate(session); + } + dataModificationId++; + return result; + } + + @Override + public void removeRow(SessionLocal session, Row row) { + if (indexes != null) { + for (Index index : indexes) { + index.remove(session, row); + } + } else { + scan.remove(session, row); + } + dataModificationId++; + } + + @Override + public void addRow(SessionLocal session, Row row) { + if (indexes != null) { + for (Index index : indexes) { + index.add(session, row); + } + } else { + scan.add(session, row); + } + dataModificationId++; + } + + @Override + public Index addIndex(SessionLocal session, String indexName, int indexId, IndexColumn[] cols, + int uniqueColumnCount, IndexType indexType, boolean create, String indexComment) { + if (indexes == null) { + indexes = new ArrayList<>(2); + // Scan must be always at 0. + indexes.add(scan); + } + Index index = new TreeSetIndex(this, indexName, cols, indexType); + for (SearchRow row : scan.set) { + index.add(session, (Row) row); + } + indexes.add(index); + dataModificationId++; + setModified(); + return index; + } + + @Override + public boolean isDeterministic() { + return false; + } + + @Override + public TableType getTableType() { + return TableType.EXTERNAL_TABLE_ENGINE; + } + + @Override + public Index getScanIndex(SessionLocal session) { + return scan; + } + + @Override + public long getRowCountApproximation(SessionLocal session) { + return getScanIndex(null).getRowCountApproximation(session); + } + + @Override + public long getRowCount(SessionLocal session) { + return scan.getRowCount(session); + } + + @Override + public long getMaxDataModificationId() { + return dataModificationId; + } + + @Override + public ArrayList getIndexes() { + return indexes; + } + + @Override + public void close(SessionLocal session) { + // No-op. + } + + @Override + public void checkSupportAlter() { + // No-op. + } + + @Override + public boolean canGetRowCount(SessionLocal session) { + return true; + } + + @Override + public boolean canDrop() { + return true; + } + } + + /** + * An index that internally uses a tree set. + */ + private static class TreeSetIndex extends Index implements Comparator { + + final TreeSet set = new TreeSet<>(this); + + TreeSetIndex(Table t, String name, IndexColumn[] cols, IndexType type) { + super(t, 0, name, cols, 0, type); + } + + @Override + public int compare(SearchRow o1, SearchRow o2) { + int res = compareRows(o1, o2); + if (res == 0) { + if (o1.getKey() == Long.MAX_VALUE || o2.getKey() == Long.MIN_VALUE) { + res = 1; + } else if (o1.getKey() == Long.MIN_VALUE || o2.getKey() == Long.MAX_VALUE) { + res = -1; + } + } + return res; + } + + @Override + public void close(SessionLocal session) { + // No-op. + } + + @Override + public void add(SessionLocal session, Row row) { + set.add(row); + } + + @Override + public void remove(SessionLocal session, Row row) { + set.remove(row); + } + + private static SearchRow mark(SearchRow row, boolean first) { + if (row != null) { + // Mark this row to be a search row. + row.setKey(first ? Long.MIN_VALUE : Long.MAX_VALUE); + } + return row; + } + + @Override + public Cursor find(SessionLocal session, SearchRow first, SearchRow last) { + Set subSet; + if (first != null && last != null && compareRows(last, first) < 0) { + subSet = Collections.emptySet(); + } else { + if (first != null) { + first = set.floor(mark(first, true)); + } + if (last != null) { + last = set.ceiling(mark(last, false)); + } + if (first == null && last == null) { + subSet = set; + } else if (first != null) { + if (last != null) { + subSet = set.subSet(first, true, last, true); + } else { + subSet = set.tailSet(first, true); + } + } else if (last != null) { + subSet = set.headSet(last, true); + } else { + throw new IllegalStateException(); + } + } + return new IteratorCursor(subSet.iterator()); + } + + @Override + public double getCost(SessionLocal session, int[] masks, + TableFilter[] filters, int filter, SortOrder sortOrder, + AllColumnsForPlan allColumnsSet) { + return getCostRangeIndex(masks, set.size(), filters, filter, + sortOrder, false, allColumnsSet); + } + + @Override + public void remove(SessionLocal session) { + // No-op. + } + + @Override + public void truncate(SessionLocal session) { + set.clear(); + } + + @Override + public boolean canGetFirstOrLast() { + return true; + } + + @Override + public Cursor findFirstOrLast(SessionLocal session, boolean first) { + return new SingleRowCursor((Row) + (set.isEmpty() ? null : first ? set.first() : set.last())); + } + + @Override + public boolean needRebuild() { + return true; + } + + @Override + public long getRowCount(SessionLocal session) { + return set.size(); + } + + @Override + public long getRowCountApproximation(SessionLocal session) { + return getRowCount(null); + } + + } + + /** + */ + private static class IteratorCursor implements Cursor { + Iterator it; + private Row current; + + IteratorCursor(Iterator it) { + this.it = it; + } + + @Override + public boolean previous() { + throw DbException.getUnsupportedException("prev"); + } + + @Override + public boolean next() { + if (it.hasNext()) { + current = (Row) it.next(); + return true; + } + current = null; + return false; + } + + @Override + public SearchRow getSearchRow() { + return get(); + } + + @Override + public Row get() { + return current; + } + + @Override + public String toString() { + return "IteratorCursor->" + current; + } + } + + /** + * A comparator for rows (lists of comparable objects). + */ + private static class RowComparator implements Comparator> { + private int[] cols; + private boolean descending; + + RowComparator(int... cols) { + this.descending = false; + this.cols = cols; + } + + RowComparator(boolean descending, int... cols) { + this.descending = descending; + this.cols = cols; + } + + @SuppressWarnings("unchecked") + @Override + public int compare(List row1, List row2) { + for (int i = 0; i < cols.length; i++) { + int col = cols[i]; + Comparable o1 = (Comparable) row1.get(col); + Comparable o2 = (Comparable) row2.get(col); + if (o1 == null) { + return applyDescending(o2 == null ? 0 : -1); + } + if (o2 == null) { + return applyDescending(1); + } + int res = o1.compareTo(o2); + if (res != 0) { + return applyDescending(res); + } + } + return 0; + } + + private int applyDescending(int v) { + if (!descending) { + return v; + } + if (v == 0) { + return v; + } + return -v; + } + } + + /** + * A filter for rows (lists of objects). + */ + abstract static class RowFilter { + + /** + * Check whether the row needs to be processed. + * + * @param row the row + * @return true if yes + */ + protected abstract boolean accept(List row); + + /** + * Get an integer from a row. + * + * @param row the row + * @param col the column index + * @return the value + */ + protected Integer getInt(List row, int col) { + return (Integer) row.get(col); + } + + /** + * Get a long from a row. + * + * @param row the row + * @param col the column index + * @return the value + */ + protected Long getLong(List row, int col) { + return (Long) row.get(col); + } + + /** + * Get a string from a row. + * + * @param row the row + * @param col the column index + * @return the value + */ + protected String getString(List row, int col) { + return (String) row.get(col); + } + + } + +} diff --git a/h2/src/test/org/h2/test/db/TestTempTables.java b/h2/src/test/org/h2/test/db/TestTempTables.java new file mode 100644 index 0000000..416c7ae --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestTempTables.java @@ -0,0 +1,332 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import org.h2.api.ErrorCode; +import org.h2.engine.Session; +import org.h2.engine.SessionLocal; +import org.h2.jdbc.JdbcConnection; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Temporary table tests. + */ +public class TestTempTables extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + deleteDb("tempTables"); + testAnalyzeReuseObjectId(); + testTempSequence(); + testTempFileResultSet(); + testTempTableResultSet(); + testTransactionalTemp(); + Connection c1 = getConnection("tempTables"); + testAlter(c1); + Connection c2 = getConnection("tempTables"); + testConstraints(c1, c2); + testTables(c1, c2); + testIndexes(c1, c2); + c1.close(); + c2.close(); + testLotsOfTables(); + testCreateAsSelectDistinct(); + deleteDb("tempTables"); + } + + private void testAnalyzeReuseObjectId() throws SQLException { + deleteDb("tempTables"); + Connection conn = getConnection("tempTables"); + Statement stat = conn.createStatement(); + stat.execute("create local temporary table test(id identity)"); + PreparedStatement prep = conn + .prepareStatement("insert into test default values"); + for (int i = 0; i < 10000; i++) { + prep.execute(); + } + stat.execute("create local temporary table " + + "test2(id identity) as select x from system_range(1, 10)"); + conn.close(); + } + + private void testTempSequence() throws SQLException { + deleteDb("tempTables"); + Connection conn = getConnection("tempTables"); + Statement stat = conn.createStatement(); + stat.execute("create local temporary table test(id identity)"); + Session iface = ((JdbcConnection) conn).getSession(); + if ((iface instanceof SessionLocal)) { + assertEquals(1, ((SessionLocal) iface).getDatabase().getMainSchema().getAllSequences().size()); + } + stat.execute("insert into test default values"); + stat.execute("shutdown"); + conn.close(); + conn = getConnection("tempTables"); + iface = ((JdbcConnection) conn).getSession(); + if ((iface instanceof SessionLocal)) { + assertEquals(0, ((SessionLocal) iface).getDatabase().getMainSchema().getAllSequences().size()); + } + conn.close(); + } + + private void testTempFileResultSet() throws SQLException { + if (config.lazy) { + return; + } + deleteDb("tempTables"); + Connection conn = getConnection("tempTables;MAX_MEMORY_ROWS=10"); + ResultSet rs1, rs2; + Statement stat1 = conn.createStatement( + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + Statement stat2 = conn.createStatement( + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + rs1 = stat1.executeQuery("select * from system_range(1, 20)"); + rs2 = stat2.executeQuery("select * from system_range(1, 20)"); + for (int i = 0; i < 20; i++) { + assertTrue(rs1.next()); + assertTrue(rs2.next()); + assertEquals(i + 1, rs1.getInt(1)); + assertEquals(i + 1, rs2.getInt(1)); + } + rs2.close(); + // verify the temp table is not deleted yet + rs1.beforeFirst(); + for (int i = 0; i < 20; i++) { + rs1.next(); + rs1.getInt(1); + } + rs1.close(); + + rs1 = stat1.executeQuery( + "select * from system_range(1, 20) order by x desc"); + rs2 = stat2.executeQuery( + "select * from system_range(1, 20) order by x desc"); + for (int i = 0; i < 20; i++) { + rs1.next(); + rs2.next(); + rs1.getInt(1); + rs2.getInt(1); + } + rs1.close(); + // verify the temp table is not deleted yet + rs2.beforeFirst(); + for (int i = 0; i < 20; i++) { + rs2.next(); + rs2.getInt(1); + } + rs2.close(); + + conn.close(); + } + + private void testTempTableResultSet() throws SQLException { + deleteDb("tempTables"); + Connection conn = getConnection( + "tempTables;MAX_MEMORY_ROWS=10"); + Statement stat1 = conn.createStatement( + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + Statement stat2 = conn.createStatement( + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + ResultSet rs1, rs2; + rs1 = stat1.executeQuery("select distinct * from system_range(1, 20)"); + // this will re-use the same temp table + rs2 = stat2.executeQuery("select distinct * from system_range(1, 20)"); + for (int i = 0; i < 20; i++) { + rs1.next(); + rs2.next(); + rs1.getInt(1); + rs2.getInt(1); + } + rs2.close(); + // verify the temp table is not deleted yet + rs1.beforeFirst(); + for (int i = 0; i < 20; i++) { + rs1.next(); + rs1.getInt(1); + } + rs1.close(); + + rs1 = stat1.executeQuery("select distinct * from system_range(1, 20)"); + rs2 = stat2.executeQuery("select distinct * from system_range(1, 20)"); + for (int i = 0; i < 20; i++) { + rs1.next(); + rs2.next(); + rs1.getInt(1); + rs2.getInt(1); + } + rs1.close(); + // verify the temp table is not deleted yet + rs2.beforeFirst(); + for (int i = 0; i < 20; i++) { + rs2.next(); + rs2.getInt(1); + } + rs2.close(); + + conn.close(); + } + + private void testTransactionalTemp() throws SQLException { + deleteDb("tempTables"); + Connection conn = getConnection("tempTables"); + conn.setAutoCommit(false); + Statement stat = conn.createStatement(); + ResultSet rs; + stat.execute("create table test(id int primary key)"); + stat.execute("insert into test values(1)"); + stat.execute("commit"); + stat.execute("insert into test values(2)"); + stat.execute("create local temporary table temp(" + + "id int primary key, name varchar, constraint x unique(name)) transactional"); + stat.execute("insert into temp values(3, 'test')"); + stat.execute("rollback"); + rs = stat.executeQuery("select * from test"); + assertTrue(rs.next()); + assertFalse(rs.next()); + stat.execute("drop table test"); + stat.execute("drop table temp"); + conn.close(); + } + + private void testAlter(Connection conn) throws SQLException { + Statement stat; + stat = conn.createStatement(); + stat.execute("create temporary table test(id varchar)"); + stat.execute("create index idx1 on test(id)"); + stat.execute("drop index idx1"); + stat.execute("create index idx1 on test(id)"); + stat.execute("insert into test select x from system_range(1, 10)"); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, stat). + execute("alter table test add column x int"); + stat.execute("drop table test"); + } + + private static void testConstraints(Connection conn1, Connection conn2) + throws SQLException { + Statement s1 = conn1.createStatement(), s2 = conn2.createStatement(); + s1.execute("create local temporary table test(id int unique)"); + s2.execute("create local temporary table test(id int unique)"); + s1.execute("alter table test add constraint a unique(id)"); + s2.execute("alter table test add constraint a unique(id)"); + s1.execute("drop table test"); + s2.execute("drop table test"); + } + + private static void testIndexes(Connection conn1, Connection conn2) + throws SQLException { + conn1.createStatement().executeUpdate( + "create local temporary table test(id int)"); + conn1.createStatement().executeUpdate( + "create index idx_id on test(id)"); + conn2.createStatement().executeUpdate( + "create local temporary table test(id int)"); + conn2.createStatement().executeUpdate( + "create index idx_id on test(id)"); + conn2.createStatement().executeUpdate("drop index idx_id"); + conn2.createStatement().executeUpdate("drop table test"); + conn2.createStatement().executeUpdate("create table test(id int)"); + conn2.createStatement().executeUpdate("create index idx_id on test(id)"); + conn1.createStatement().executeUpdate("drop table test"); + conn1.createStatement().executeUpdate("drop table test"); + } + + private void testTables(Connection c1, Connection c2) throws SQLException { + Statement s1 = c1.createStatement(); + Statement s2 = c2.createStatement(); + s1.execute("CREATE LOCAL TEMPORARY TABLE LT(A INT)"); + s1.execute("CREATE GLOBAL TEMPORARY TABLE GT1(ID INT)"); + s2.execute("CREATE GLOBAL TEMPORARY TABLE GT2(ID INT)"); + s2.execute("CREATE LOCAL TEMPORARY TABLE LT(B INT)"); + s2.execute("SELECT B FROM LT"); + s1.execute("SELECT A FROM LT"); + s1.execute("SELECT * FROM GT1"); + s2.execute("SELECT * FROM GT1"); + s1.execute("SELECT * FROM GT2"); + s2.execute("SELECT * FROM GT2"); + s2.execute("DROP TABLE GT1"); + s2.execute("DROP TABLE GT2"); + s2.execute("DROP TABLE LT"); + s1.execute("DROP TABLE LT"); + + // temp tables: 'on commit' syntax is currently not documented, because + // not tested well + // and hopefully nobody is using it, as it looks like functional sugar + // (this features are here for compatibility only) + ResultSet rs; + c1.setAutoCommit(false); + s1.execute("create local temporary table test_temp(id int) " + + "on commit delete rows"); + s1.execute("insert into test_temp values(1)"); + rs = s1.executeQuery("select * from test_temp"); + assertResultRowCount(1, rs); + c1.commit(); + rs = s1.executeQuery("select * from test_temp"); + assertResultRowCount(0, rs); + s1.execute("drop table test_temp"); + + s1.execute("create local temporary table test_temp(id int) on commit drop"); + s1.execute("insert into test_temp values(1)"); + rs = s1.executeQuery("select * from test_temp"); + assertResultRowCount(1, rs); + c1.commit(); + // test_temp should have been dropped automatically + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1, s1). + executeQuery("select * from test_temp"); + } + + /** + * There was a bug where creating lots of tables would overflow the + * transaction table in the MVStore + */ + private void testLotsOfTables() throws SQLException { + if (config.networked || config.throttle > 0) { + return; // just to save some testing time + } + deleteDb("tempTables"); + Connection conn = getConnection("tempTables"); + Statement stat = conn.createStatement(); + for (int i = 0; i < 100000; i++) { + stat.executeUpdate("create local temporary table t(id int)"); + stat.executeUpdate("drop table t"); + } + conn.close(); + } + + /** + * Issue #401: NPE in "SELECT DISTINCT * ORDER BY" + */ + private void testCreateAsSelectDistinct() throws SQLException { + deleteDb("tempTables"); + Connection conn = getConnection("tempTables;MAX_MEMORY_ROWS=1000"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE ONE(S1 VARCHAR(255), S2 VARCHAR(255))"); + PreparedStatement prep = conn + .prepareStatement("insert into one values(?,?)"); + for (int row = 0; row < 10000; row++) { + prep.setString(1, "abc"); + prep.setString(2, "def" + row); + prep.execute(); + } + stat.execute( + "CREATE TABLE TWO AS SELECT DISTINCT * FROM ONE ORDER BY S1"); + conn.close(); + } +} diff --git a/h2/src/test/org/h2/test/db/TestTransaction.java b/h2/src/test/org/h2/test/db/TestTransaction.java new file mode 100644 index 0000000..22b8b9c --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestTransaction.java @@ -0,0 +1,1271 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Savepoint; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Random; +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Transactional tests, including transaction isolation tests, and tests related + * to savepoints. + */ +public class TestTransaction extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase init = TestBase.createCaller().init(); + init.testFromMain(); + } + + @Override + public void test() throws Exception { + testClosingConnectionWithSessionTempTable(); + testClosingConnectionWithLockedTable(); + testConstraintCreationRollback(); + testCommitOnAutoCommitChange(); + testConcurrentSelectForUpdate(); + testRollback(); + testRollback2(); + testForUpdate(); + testForUpdate2(); + testForUpdate3(); + testUpdate(); + testMergeUsing(); + testDelete(); + testSetTransaction(); + testReferential(); + testSavepoint(); + testIsolation(); + testIsolationLevels(); + testIsolationLevels2(); + testIsolationLevels3(); + testIsolationLevels4(); + testIsolationLevelsCountAggregate(); + testIsolationLevelsCountAggregate2(); + deleteDb("transaction"); + } + + private void testConstraintCreationRollback() throws SQLException { + deleteDb("transaction"); + Connection conn = getConnection("transaction"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int unique, p int)"); + stat.execute("insert into test values(1, 2)"); + assertThrows(ErrorCode.REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1, stat).execute( + "alter table test add constraint fail foreign key(p) references test(id)"); + stat.execute("insert into test values(2, 3)"); + stat.execute("drop table test"); + conn.close(); + } + + private void testCommitOnAutoCommitChange() throws SQLException { + deleteDb("transaction"); + Connection conn = getConnection("transaction"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key)"); + + Connection conn2 = getConnection("transaction"); + Statement stat2 = conn2.createStatement(); + + conn.setAutoCommit(false); + stat.execute("insert into test values(1)"); + + // should have no effect + conn.setAutoCommit(false); + + ResultSet rs = stat2.executeQuery("select count(*) from test"); + rs.next(); + assertEquals(0, rs.getInt(1)); + + // should commit + conn.setAutoCommit(true); + + rs = stat2.executeQuery("select * from test"); + assertTrue(rs.next()); + + stat.execute("drop table test"); + + conn2.close(); + conn.close(); + } + + private void testConcurrentSelectForUpdate() throws SQLException { + deleteDb("transaction"); + Connection conn = getConnection("transaction"); + conn.setAutoCommit(false); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name varchar)"); + stat.execute("create table test2(id int primary key, name varchar)"); + stat.execute("insert into test values(1, 'Hello'), (2, 'World')"); + stat.execute("insert into test2 values(1, 'A'), (2, 'B')"); + conn.commit(); + testConcurrentSelectForUpdateImpl(conn, "*"); + testConcurrentSelectForUpdateImpl(conn, "*, count(*) over ()"); + conn.close(); + } + + private void testConcurrentSelectForUpdateImpl(Connection conn, String expressions) throws SQLException { + Connection conn2; + PreparedStatement prep; + prep = conn.prepareStatement("select * from test for update"); + prep.execute(); + conn2 = getConnection("transaction"); + conn2.setAutoCommit(false); + assertThrows(ErrorCode.LOCK_TIMEOUT_1, conn2.createStatement()). + execute("select " + expressions + " from test for update"); + conn2.close(); + conn.commit(); + + prep = conn.prepareStatement("select " + expressions + + " from test join test2 on test.id = test2.id for update"); + prep.execute(); + conn2 = getConnection("transaction"); + conn2.setAutoCommit(false); + assertThrows(ErrorCode.LOCK_TIMEOUT_1, conn2.createStatement()). + execute("select * from test for update"); + assertThrows(ErrorCode.LOCK_TIMEOUT_1, conn2.createStatement()). + execute("select * from test2 for update"); + conn2.close(); + conn.commit(); + } + + private void testForUpdate() throws SQLException { + deleteDb("transaction"); + Connection conn = getConnection("transaction"); + conn.setAutoCommit(false); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name varchar)"); + stat.execute("insert into test values(1, 'Hello'), (2, 'World')"); + conn.commit(); + PreparedStatement prep = conn.prepareStatement( + "select * from test where id = 1 for update"); + prep.execute(); + // releases the lock + conn.commit(); + prep.execute(); + Connection conn2 = getConnection("transaction"); + conn2.setAutoCommit(false); + Statement stat2 = conn2.createStatement(); + stat2.execute("update test set name = 'Welt' where id = 2"); + assertThrows(ErrorCode.LOCK_TIMEOUT_1, stat2). + execute("update test set name = 'Hallo' where id = 1"); + conn2.close(); + conn.close(); + } + + private void testForUpdate2() throws Exception { + // Exclude some configurations to avoid spending too much time in sleep() + if (config.networked || config.cipher != null) { + return; + } + deleteDb("transaction"); + Connection conn1 = getConnection("transaction"); + Connection conn2 = getConnection("transaction"); + Statement stat1 = conn1.createStatement(); + stat1.execute("CREATE TABLE TEST (ID INT PRIMARY KEY, V INT)"); + conn1.setAutoCommit(false); + conn2.createStatement().execute("SET LOCK_TIMEOUT 2000"); + testForUpdate2(conn1, stat1, conn2, false); + testForUpdate2(conn1, stat1, conn2, true); + conn1.close(); + conn2.close(); + } + + private void testForUpdate2(Connection conn1, Statement stat1, Connection conn2, boolean forUpdate) + throws Exception { + testForUpdate2(conn1, stat1, conn2, forUpdate, false); + testForUpdate2(conn1, stat1, conn2, forUpdate, true); + } + + private void testForUpdate2(Connection conn1, Statement stat1, Connection conn2, boolean forUpdate, + boolean window) throws Exception { + testForUpdate2(conn1, stat1, conn2, forUpdate, window, false, false); + testForUpdate2(conn1, stat1, conn2, forUpdate, window, false, true); + testForUpdate2(conn1, stat1, conn2, forUpdate, window, true, false); + } + + private void testForUpdate2(Connection conn1, Statement stat1, final Connection conn2, boolean forUpdate, + boolean window, boolean deleted, boolean excluded) throws Exception { + stat1.execute("MERGE INTO TEST KEY(ID) VALUES (1, 1)"); + conn1.commit(); + stat1.execute(deleted ? "DELETE FROM TEST WHERE ID = 1" : "UPDATE TEST SET V = 2 WHERE ID = 1"); + final int[] res = new int[1]; + final Exception[] ex = new Exception[1]; + StringBuilder builder = new StringBuilder("SELECT V"); + if (window) { + builder.append(", RANK() OVER (ORDER BY ID)"); + } + builder.append(" FROM TEST WHERE ID = 1"); + if (excluded) { + builder.append(" AND V = 1"); + } + if (forUpdate) { + builder.append(" FOR UPDATE"); + } + String query = builder.toString(); + final PreparedStatement prep2 = conn2.prepareStatement(query); + Thread t = new Thread() { + @Override + public void run() { + try { + ResultSet resultSet = prep2.executeQuery(); + res[0] = resultSet.next() ? resultSet.getInt(1) : -1; + conn2.commit(); + } catch (SQLException e) { + ex[0] = e; + } + } + }; + t.start(); + Thread.sleep(500); + conn1.commit(); + t.join(); + if (ex[0] != null) { + throw ex[0]; + } + assertEquals(forUpdate ? (deleted || excluded) ? -1 : 2 : 1, res[0]); + } + + private void testForUpdate3() throws Exception { + // Exclude some configurations to avoid spending too much time in sleep() + if (config.networked || config.cipher != null) { + return; + } + deleteDb("transaction"); + Connection conn1 = getConnection("transaction"); + final Connection conn2 = getConnection("transaction"); + Statement stat1 = conn1.createStatement(); + stat1.execute("CREATE TABLE TEST (ID INT PRIMARY KEY, V INT UNIQUE)"); + conn1.setAutoCommit(false); + conn2.createStatement().execute("SET LOCK_TIMEOUT 2000"); + stat1.execute("MERGE INTO TEST KEY(ID) VALUES (1, 1), (2, 2), (3, 3), (4, 4)"); + conn1.commit(); + stat1.execute("UPDATE TEST SET V = 10 - V"); + final Exception[] ex = new Exception[1]; + StringBuilder builder = new StringBuilder("SELECT V FROM TEST ORDER BY V FOR UPDATE"); + String query = builder.toString(); + final PreparedStatement prep2 = conn2.prepareStatement(query); + Thread t = new Thread() { + @Override + public void run() { + try { + ResultSet resultSet = prep2.executeQuery(); + int previous = -1; + while (resultSet.next()) { + int value = resultSet.getInt(1); + assertTrue(previous + ">=" + value, previous < value); + previous = value; + } + conn2.commit(); + } catch (SQLException e) { + ex[0] = e; + } + } + }; + t.start(); + Thread.sleep(500); + conn1.commit(); + t.join(); + if (ex[0] != null) { + throw ex[0]; + } + conn1.close(); + conn2.close(); + } + + private void testUpdate() throws Exception { + final int count = 50; + deleteDb("transaction"); + final Connection conn1 = getConnection("transaction"); + conn1.setAutoCommit(false); + Connection conn2 = getConnection("transaction"); + conn2.setAutoCommit(false); + Statement stat1 = conn1.createStatement(); + Statement stat2 = conn2.createStatement(); + stat1.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, \"VALUE\" BOOLEAN) AS " + + "SELECT X, FALSE FROM GENERATE_SERIES(1, " + count + ')'); + conn1.commit(); + stat1.executeQuery("SELECT * FROM TEST").close(); + stat2.executeQuery("SELECT * FROM TEST").close(); + final int[] r = new int[1]; + Thread t = new Thread() { + @Override + public void run() { + int sum = 0; + try { + PreparedStatement prep = conn1.prepareStatement( + "UPDATE TEST SET \"VALUE\" = TRUE WHERE ID = ? AND NOT \"VALUE\""); + for (int i = 1; i <= count; i++) { + prep.setInt(1, i); + prep.addBatch(); + } + int[] a = prep.executeBatch(); + for (int i : a) { + sum += i; + } + conn1.commit(); + } catch (SQLException e) { + // Ignore + } + r[0] = sum; + } + }; + t.start(); + int sum = 0; + PreparedStatement prep = conn2.prepareStatement( + "UPDATE TEST SET \"VALUE\" = TRUE WHERE ID = ? AND NOT \"VALUE\""); + for (int i = 1; i <= count; i++) { + prep.setInt(1, i); + prep.addBatch(); + } + int[] a = prep.executeBatch(); + for (int i : a) { + sum += i; + } + conn2.commit(); + t.join(); + assertEquals(count, sum + r[0]); + conn2.close(); + conn1.close(); + } + + private void testMergeUsing() throws Exception { + final int count = 50; + deleteDb("transaction"); + final Connection conn1 = getConnection("transaction"); + conn1.setAutoCommit(false); + Connection conn2 = getConnection("transaction"); + conn2.setAutoCommit(false); + Statement stat1 = conn1.createStatement(); + Statement stat2 = conn2.createStatement(); + stat1.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, \"VALUE\" BOOLEAN) AS " + + "SELECT X, FALSE FROM GENERATE_SERIES(1, " + count + ')'); + conn1.commit(); + stat1.executeQuery("SELECT * FROM TEST").close(); + stat2.executeQuery("SELECT * FROM TEST").close(); + final int[] r = new int[1]; + Thread t = new Thread() { + @Override + public void run() { + int sum = 0; + try { + PreparedStatement prep = conn1.prepareStatement( + "MERGE INTO TEST T USING (SELECT ?1::INT X) S ON T.ID = S.X AND NOT T.\"VALUE\"" + + " WHEN MATCHED THEN UPDATE SET T.\"VALUE\" = TRUE" + + " WHEN NOT MATCHED THEN INSERT VALUES (10000 + ?1, FALSE)"); + for (int i = 1; i <= count; i++) { + prep.setInt(1, i); + prep.addBatch(); + } + int[] a = prep.executeBatch(); + for (int i : a) { + sum += i; + } + conn1.commit(); + } catch (SQLException e) { + // Ignore + } + r[0] = sum; + } + }; + t.start(); + int sum = 0; + PreparedStatement prep = conn2.prepareStatement( + "MERGE INTO TEST T USING (SELECT ?1::INT X) S ON T.ID = S.X AND NOT T.\"VALUE\"" + + " WHEN MATCHED THEN UPDATE SET T.\"VALUE\" = TRUE" + + " WHEN NOT MATCHED THEN INSERT VALUES (10000 + ?1, FALSE)"); + for (int i = 1; i <= count; i++) { + prep.setInt(1, i); + prep.addBatch(); + } + int[] a = prep.executeBatch(); + for (int i : a) { + sum += i; + } + conn2.commit(); + t.join(); + assertEquals(count * 2, sum + r[0]); + conn2.close(); + conn1.close(); + } + + private void testDelete() throws Exception { + String sql1 = "DELETE FROM TEST WHERE ID = ? AND NOT \"VALUE\""; + String sql2 = "UPDATE TEST SET \"VALUE\" = TRUE WHERE ID = ? AND NOT \"VALUE\""; + testDeleteImpl(sql1, sql2); + testDeleteImpl(sql2, sql1); + } + + private void testDeleteImpl(final String sql1, String sql2) throws Exception { + final int count = 50; + deleteDb("transaction"); + final Connection conn1 = getConnection("transaction"); + conn1.setAutoCommit(false); + Connection conn2 = getConnection("transaction"); + conn2.setAutoCommit(false); + Statement stat1 = conn1.createStatement(); + Statement stat2 = conn2.createStatement(); + stat1.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, \"VALUE\" BOOLEAN) AS " + + "SELECT X, FALSE FROM GENERATE_SERIES(1, " + count + ')'); + conn1.commit(); + stat1.executeQuery("SELECT * FROM TEST").close(); + stat2.executeQuery("SELECT * FROM TEST").close(); + final int[] r = new int[1]; + Thread t = new Thread() { + @Override + public void run() { + int sum = 0; + try { + PreparedStatement prep = conn1.prepareStatement(sql1); + for (int i = 1; i <= count; i++) { + prep.setInt(1, i); + prep.addBatch(); + } + int[] a = prep.executeBatch(); + for (int i : a) { + sum += i; + } + conn1.commit(); + } catch (SQLException e) { + // Ignore + } + r[0] = sum; + } + }; + t.start(); + int sum = 0; + PreparedStatement prep = conn2.prepareStatement( + sql2); + for (int i = 1; i <= count; i++) { + prep.setInt(1, i); + prep.addBatch(); + } + int[] a = prep.executeBatch(); + for (int i : a) { + sum += i; + } + conn2.commit(); + t.join(); + assertEquals(count, sum + r[0]); + conn2.close(); + conn1.close(); + } + + private void testRollback() throws SQLException { + deleteDb("transaction"); + Connection conn = getConnection("transaction"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int)"); + stat.execute("create index idx_id on test(id)"); + stat.execute("insert into test values(1), (1), (1)"); + if (!config.memory) { + conn.close(); + conn = getConnection("transaction"); + stat = conn.createStatement(); + } + conn.setAutoCommit(false); + stat.execute("delete from test"); + conn.rollback(); + ResultSet rs; + rs = stat.executeQuery("select * from test"); + assertResultRowCount(3, rs); + rs = stat.executeQuery("select * from test where id = 1"); + assertResultRowCount(3, rs); + conn.close(); + + conn = getConnection("transaction"); + stat = conn.createStatement(); + stat.execute("create table master(id int primary key) as select 1"); + stat.execute("create table child1(id int references master(id) " + + "on delete cascade)"); + stat.execute("insert into child1 values(1), (1), (1)"); + stat.execute("create table child2(id int references master(id)) as select 1"); + if (!config.memory) { + conn.close(); + conn = getConnection("transaction"); + } + stat = conn.createStatement(); + assertThrows( + ErrorCode.REFERENTIAL_INTEGRITY_VIOLATED_CHILD_EXISTS_1, stat). + execute("delete from master"); + conn.rollback(); + rs = stat.executeQuery("select * from master where id=1"); + assertResultRowCount(1, rs); + rs = stat.executeQuery("select * from child1"); + assertResultRowCount(3, rs); + rs = stat.executeQuery("select * from child1 where id=1"); + assertResultRowCount(3, rs); + conn.close(); + } + + private void testRollback2() throws SQLException { + deleteDb("transaction"); + Connection conn = getConnection("transaction"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int)"); + stat.execute("create index idx_id on test(id)"); + stat.execute("insert into test values(1), (1)"); + if (!config.memory) { + conn.close(); + conn = getConnection("transaction"); + stat = conn.createStatement(); + } + conn.setAutoCommit(false); + stat.execute("delete from test"); + conn.rollback(); + ResultSet rs; + rs = stat.executeQuery("select * from test where id = 1"); + assertResultRowCount(2, rs); + conn.close(); + + conn = getConnection("transaction"); + stat = conn.createStatement(); + stat.execute("create table master(id int primary key) as select 1"); + stat.execute("create table child1(id int references master(id) " + + "on delete cascade)"); + stat.execute("insert into child1 values(1), (1)"); + stat.execute("create table child2(id int references master(id)) as select 1"); + if (!config.memory) { + conn.close(); + conn = getConnection("transaction"); + } + stat = conn.createStatement(); + assertThrows( + ErrorCode.REFERENTIAL_INTEGRITY_VIOLATED_CHILD_EXISTS_1, stat). + execute("delete from master"); + rs = stat.executeQuery("select * from master where id=1"); + assertResultRowCount(1, rs); + rs = stat.executeQuery("select * from child1 where id=1"); + assertResultRowCount(2, rs); + conn.close(); + } + + private void testSetTransaction() throws SQLException { + deleteDb("transaction"); + Connection conn = getConnection("transaction"); + conn.setAutoCommit(false); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int)"); + stat.execute("insert into test values(1)"); + stat.execute("set @x = 1"); + conn.commit(); + assertSingleValue(stat, "select id from test", 1); + assertSingleValue(stat, "call @x", 1); + + stat.execute("update test set id=2"); + stat.execute("set @x = 2"); + conn.rollback(); + assertSingleValue(stat, "select id from test", 1); + assertSingleValue(stat, "call @x", 2); + + conn.close(); + } + + private void testReferential() throws SQLException { + deleteDb("transaction"); + Connection c1 = getConnection("transaction"); + c1.setAutoCommit(false); + Statement s1 = c1.createStatement(); + s1.execute("drop table if exists a"); + s1.execute("drop table if exists b"); + s1.execute("create table a (id integer generated by default as identity, " + + "code varchar(10) not null, primary key(id))"); + s1.execute("create table b (name varchar(100) not null, a integer, " + + "primary key(name), foreign key(a) references a(id))"); + Connection c2 = getConnection("transaction"); + c2.setAutoCommit(false); + s1.executeUpdate("insert into A(code) values('one')"); + Statement s2 = c2.createStatement(); + assertThrows( + ErrorCode.REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1, s2). + executeUpdate("insert into B values('two', 1)"); + c2.commit(); + c1.rollback(); + c1.close(); + c2.close(); + } + + private void testClosingConnectionWithLockedTable() throws SQLException { + deleteDb("transaction"); + Connection c1 = getConnection("transaction"); + Connection c2 = getConnection("transaction"); + c1.setAutoCommit(false); + c2.setAutoCommit(false); + + Statement s1 = c1.createStatement(); + s1.execute("create table a (id integer generated by default as identity, " + + "code varchar(10) not null, primary key(id))"); + s1.executeUpdate("insert into a(code) values('one')"); + c1.commit(); + s1.executeQuery("select * from a for update"); + c1.close(); + + Statement s2 = c2.createStatement(); + s2.executeQuery("select * from a for update"); + c2.close(); + } + + private void testClosingConnectionWithSessionTempTable() throws SQLException { + deleteDb("transaction"); + Connection c1 = getConnection("transaction"); + Connection c2 = getConnection("transaction"); + c1.setAutoCommit(false); + c2.setAutoCommit(false); + + Statement s1 = c1.createStatement(); + s1.execute("create local temporary table a (id int, x BLOB)"); + c1.commit(); + c1.close(); + + Statement s2 = c2.createStatement(); + s2.execute("create table c (id int)"); + c2.close(); + } + + private void testSavepoint() throws SQLException { + deleteDb("transaction"); + Connection conn = getConnection("transaction"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST0(ID IDENTITY, NAME VARCHAR)"); + stat.execute("CREATE TABLE TEST1(NAME VARCHAR, " + + "ID IDENTITY, X TIMESTAMP DEFAULT CURRENT_TIMESTAMP)"); + conn.setAutoCommit(false); + int[] count = new int[2]; + int[] countCommitted = new int[2]; + int[] countSave = new int[2]; + int len = getSize(2000, 10000); + Random random = new Random(10); + Savepoint sp = null; + for (int i = 0; i < len; i++) { + int tableId = random.nextInt(2); + String table = "TEST" + tableId; + int op = random.nextInt(6); + switch (op) { + case 0: + stat.execute("INSERT INTO " + table + "(NAME) VALUES('op" + i + "')"); + count[tableId]++; + break; + case 1: + if (count[tableId] > 0) { + int updateCount = stat.executeUpdate( + "DELETE FROM " + table + + " WHERE ID=SELECT MIN(ID) FROM " + table); + assertEquals(1, updateCount); + count[tableId]--; + } + break; + case 2: + sp = conn.setSavepoint(); + countSave[0] = count[0]; + countSave[1] = count[1]; + break; + case 3: + if (sp != null) { + conn.rollback(sp); + count[0] = countSave[0]; + count[1] = countSave[1]; + } + break; + case 4: + conn.commit(); + sp = null; + countCommitted[0] = count[0]; + countCommitted[1] = count[1]; + break; + case 5: + conn.rollback(); + sp = null; + count[0] = countCommitted[0]; + count[1] = countCommitted[1]; + break; + default: + } + checkTableCount(stat, "TEST0", count[0]); + checkTableCount(stat, "TEST1", count[1]); + } + conn.close(); + } + + private void checkTableCount(Statement stat, String tableName, int count) + throws SQLException { + ResultSet rs; + rs = stat.executeQuery("SELECT COUNT(*) FROM " + tableName); + rs.next(); + assertEquals(count, rs.getInt(1)); + } + + private void testIsolation() throws SQLException { + Connection conn = getConnection("transaction"); + trace("default TransactionIsolation=" + conn.getTransactionIsolation()); + conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); + assertEquals(Connection.TRANSACTION_READ_COMMITTED, conn.getTransactionIsolation()); + conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); + assertEquals(Connection.TRANSACTION_SERIALIZABLE, conn.getTransactionIsolation()); + Statement stat = conn.createStatement(); + assertTrue(conn.getAutoCommit()); + conn.setAutoCommit(false); + assertFalse(conn.getAutoCommit()); + conn.setAutoCommit(true); + assertTrue(conn.getAutoCommit()); + test(stat, "CREATE TABLE TEST(ID INT PRIMARY KEY)"); + conn.commit(); + test(stat, "INSERT INTO TEST VALUES(0)"); + conn.rollback(); + testValue(stat, "SELECT COUNT(*) FROM TEST", "1"); + conn.setAutoCommit(false); + test(stat, "DELETE FROM TEST"); + // testValue("SELECT COUNT(*) FROM TEST", "0"); + conn.rollback(); + testValue(stat, "SELECT COUNT(*) FROM TEST", "1"); + conn.commit(); + conn.setAutoCommit(true); + testNestedResultSets(conn); + conn.setAutoCommit(false); + testNestedResultSets(conn); + conn.close(); + } + + private void testIsolationLevels() throws SQLException { + for (int isolationLevel : new int[] { Connection.TRANSACTION_REPEATABLE_READ, Constants.TRANSACTION_SNAPSHOT, + Connection.TRANSACTION_SERIALIZABLE }) { + deleteDb("transaction"); + try (Connection conn1 = getConnection("transaction"); Connection conn2 = getConnection("transaction"); + Connection conn3 = getConnection("transaction")) { + conn3.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); + Statement stat1 = conn1.createStatement(); + Statement stat2 = conn2.createStatement(); + Statement stat3 = conn3.createStatement(); + stat1.execute("CREATE TABLE TEST1(ID INT PRIMARY KEY) AS VALUES 1, 2"); + stat1.execute("CREATE TABLE TEST2(ID INT PRIMARY KEY, V INT) AS VALUES (1, 10), (2, 20)"); + conn2.setAutoCommit(false); + // Read committed + testIsolationLevelsCheckRowsAndCount(stat2, 1, 2); + stat1.execute("INSERT INTO TEST1 VALUES 3"); + testIsolationLevelsCheckRowsAndCount(stat2, 1, 3); + testIsolationLevelsCheckRowsAndCount(stat2, 2, 2); + stat1.execute("INSERT INTO TEST2 VALUES (3, 30)"); + testIsolationLevelsCheckRowsAndCount(stat2, 2, 3); + // Repeatable read or serializable + conn2.setTransactionIsolation(isolationLevel); + testIsolationLevelsCheckRowsAndCount(stat2, 1, 3); + + stat1.execute("INSERT INTO TEST1 VALUES 4"); + testIsolationLevelsCheckRowsAndCount(stat2, 1, 3); + testIsolationLevelsCheckRowsAndCount(stat2, 2, 3); + stat1.execute("INSERT INTO TEST2 VALUES (4, 40)"); + testIsolationLevelsCheckRowsAndCount(stat2, 2, 3); + conn2.commit(); + testIsolationLevelsCheckRowsAndCount(stat2, 1, 4); + testIsolationLevelsCheckRowsAndCount(stat2, 2, 4); + stat1.execute("ALTER TABLE TEST2 ADD CONSTRAINT FK FOREIGN KEY(ID) REFERENCES TEST1(ID)"); + conn2.commit(); + testIsolationLevelsCheckRowsAndCount(stat2, 1, 4); + stat1.execute("INSERT INTO TEST1 VALUES 5"); + stat1.execute("INSERT INTO TEST2 VALUES (5, 50)"); + testIsolationLevelsCheckRowsAndCount(stat2, 1, 4); + testIsolationLevelsCheckRowsAndCount(stat2, 2, 4); + conn2.commit(); + testIsolationLevelsCheckRowsAndCount(stat2, 1, 5); + testIsolationLevelsCheckRowsAndCount(stat2, 2, 5); + stat2.execute("INSERT INTO TEST1 VALUES 6"); + stat2.execute("INSERT INTO TEST2 VALUES (6, 60)"); + stat2.execute("DELETE FROM TEST2 WHERE ID IN (1, 3)"); + stat2.execute("UPDATE TEST2 SET V = 45 WHERE ID = 4"); + stat1.execute("INSERT INTO TEST1 VALUES 7"); + stat1.execute("INSERT INTO TEST2 VALUES (7, 70)"); + stat2.execute("INSERT INTO TEST1 VALUES 8"); + stat2.execute("INSERT INTO TEST2 VALUES (8, 80)"); + stat2.execute("INSERT INTO TEST1 VALUES 9"); + stat2.execute("INSERT INTO TEST2 VALUES (9, 90)"); + stat2.execute("DELETE FROM TEST2 WHERE ID = 9"); + testIsolationLevelsCheckRowsAndCount2(stat2, 1, 1, 2, 3, 4, 5, 6, 8, 9); + // Read uncommitted + testIsolationLevelsCheckRowsAndCount2(stat3, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9); + // Repeatable read or serializable + try (ResultSet rs = stat2.executeQuery("SELECT COUNT(*) FROM TEST2")) { + rs.next(); + assertEquals(5, rs.getLong(1)); + } + try (ResultSet rs = stat2.executeQuery("SELECT ID, V FROM TEST2 ORDER BY ID")) { + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertEquals(20, rs.getInt(2)); + assertTrue(rs.next()); + assertEquals(4, rs.getInt(1)); + assertEquals(45, rs.getInt(2)); + assertTrue(rs.next()); + assertEquals(5, rs.getInt(1)); + assertEquals(50, rs.getInt(2)); + assertTrue(rs.next()); + assertEquals(6, rs.getInt(1)); + assertEquals(60, rs.getInt(2)); + assertTrue(rs.next()); + assertEquals(8, rs.getInt(1)); + assertEquals(80, rs.getInt(2)); + assertFalse(rs.next()); + } + stat1.execute("INSERT INTO TEST1 VALUES 11"); + stat1.execute("INSERT INTO TEST2 VALUES (11, 110)"); + conn2.commit(); + testIsolationLevelsCheckRowsAndCount2(stat1, 2, 2, 4, 5, 6, 7, 8, 11); + testIsolationLevelsCheckRowsAndCount2(stat2, 2, 2, 4, 5, 6, 7, 8, 11); + stat2.execute("INSERT INTO TEST1 VALUES 10"); + stat2.execute("INSERT INTO TEST2 VALUES (9, 90), (10, 100)"); + stat2.execute("DELETE FROM TEST2 WHERE ID = 9"); + testIsolationLevelsCheckRowsAndCount2(stat2, 2, 2, 4, 5, 6, 7, 8, 10, 11); + stat1.execute("ALTER TABLE TEST2 DROP CONSTRAINT FK"); + conn2.commit(); + try (ResultSet rs = stat2.executeQuery("SELECT COUNT(*) FROM TEST1")) { + rs.next(); + assertEquals(11, rs.getLong(1)); + } + stat1.execute("INSERT INTO TEST2 VALUES (20, 200)"); + try (ResultSet rs = stat2.executeQuery("SELECT COUNT(*) FROM TEST2")) { + rs.next(); + assertEquals(isolationLevel != Connection.TRANSACTION_REPEATABLE_READ ? 8 : 9, rs.getLong(1)); + } + } + } + deleteDb("transaction"); + } + + private void testIsolationLevelsCheckRowsAndCount(Statement stat, int table, int expected) + throws SQLException { + try (ResultSet rs = stat.executeQuery("SELECT COUNT(*) FROM TEST" + table)) { + rs.next(); + assertEquals(expected, rs.getLong(1)); + } + try (ResultSet rs = stat.executeQuery("SELECT ID FROM TEST" + table + " ORDER BY ID")) { + for (int i = 0; ++i <= expected;) { + assertTrue(rs.next()); + assertEquals(i, rs.getInt(1)); + } + assertFalse(rs.next()); + } + } + + private void testIsolationLevelsCheckRowsAndCount2(Statement stat, int table, int... values) + throws SQLException { + try (ResultSet rs = stat.executeQuery("SELECT COUNT(*) FROM TEST" + table)) { + rs.next(); + assertEquals(values.length, rs.getLong(1)); + } + try (ResultSet rs = stat.executeQuery("SELECT ID FROM TEST" + table + " ORDER BY ID")) { + for (int expected : values) { + assertTrue(rs.next()); + assertEquals(expected, rs.getInt(1)); + } + assertFalse(rs.next()); + } + } + + private void testNestedResultSets(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + test(stat, "CREATE TABLE NEST1(ID INT PRIMARY KEY,\"VALUE\" VARCHAR(255))"); + test(stat, "CREATE TABLE NEST2(ID INT PRIMARY KEY,\"VALUE\" VARCHAR(255))"); + DatabaseMetaData meta = conn.getMetaData(); + ArrayList result = new ArrayList<>(); + ResultSet rs1, rs2; + rs1 = meta.getTables(null, null, "NEST%", null); + while (rs1.next()) { + String table = rs1.getString("TABLE_NAME"); + rs2 = meta.getColumns(null, null, table, null); + while (rs2.next()) { + String column = rs2.getString("COLUMN_NAME"); + trace("Table: " + table + " Column: " + column); + result.add(table + "." + column); + } + } + // should be NEST1.ID, NEST1.NAME, NEST2.ID, NEST2.NAME + assertEquals(result.toString(), 4, result.size()); + result = new ArrayList<>(); + test(stat, "INSERT INTO NEST1 VALUES(1,'A')"); + test(stat, "INSERT INTO NEST1 VALUES(2,'B')"); + test(stat, "INSERT INTO NEST2 VALUES(1,'1')"); + test(stat, "INSERT INTO NEST2 VALUES(2,'2')"); + Statement s1 = conn.createStatement(); + Statement s2 = conn.createStatement(); + rs1 = s1.executeQuery("SELECT * FROM NEST1 ORDER BY ID"); + while (rs1.next()) { + rs2 = s2.executeQuery("SELECT * FROM NEST2 ORDER BY ID"); + while (rs2.next()) { + String v1 = rs1.getString("VALUE"); + String v2 = rs2.getString("VALUE"); + result.add(v1 + "/" + v2); + } + } + // should be A/1, A/2, B/1, B/2 + assertEquals(result.toString(), 4, result.size()); + result = new ArrayList<>(); + rs1 = s1.executeQuery("SELECT * FROM NEST1 ORDER BY ID"); + rs2 = s1.executeQuery("SELECT * FROM NEST2 ORDER BY ID"); + assertThrows(ErrorCode.OBJECT_CLOSED, rs1).next(); + // this is already closed, so but closing again should no do any harm + rs1.close(); + while (rs2.next()) { + String v1 = rs2.getString("VALUE"); + result.add(v1); + } + // should be A, B + assertEquals(result.toString(), 2, result.size()); + test(stat, "DROP TABLE NEST1"); + test(stat, "DROP TABLE NEST2"); + } + + private void testValue(Statement stat, String sql, String data) + throws SQLException { + ResultSet rs = stat.executeQuery(sql); + rs.next(); + String s = rs.getString(1); + assertEquals(data, s); + } + + private void test(Statement stat, String sql) throws SQLException { + trace(sql); + stat.execute(sql); + } + + private void testIsolationLevels2() throws SQLException { + for (int isolationLevel : new int[] { Connection.TRANSACTION_READ_UNCOMMITTED, + Connection.TRANSACTION_READ_COMMITTED, Connection.TRANSACTION_REPEATABLE_READ, + Constants.TRANSACTION_SNAPSHOT, Connection.TRANSACTION_SERIALIZABLE }) { + deleteDb("transaction"); + try (Connection conn1 = getConnection("transaction"); Connection conn2 = getConnection("transaction")) { + conn1.setTransactionIsolation(isolationLevel); + conn2.setTransactionIsolation(isolationLevel); + conn1.setAutoCommit(false); + conn2.setAutoCommit(false); + Statement stat1 = conn1.createStatement(); + Statement stat2 = conn2.createStatement(); + // Test a table without constraints + stat1.execute("CREATE TABLE TEST(\"VALUE\" INT)"); + stat1.executeQuery("TABLE TEST").close(); + stat1.execute("DROP TABLE TEST"); + // Other tests + stat1.execute("CREATE TABLE TEST(ID VARCHAR PRIMARY KEY, \"VALUE\" INT)"); + stat1.execute("INSERT INTO TEST VALUES ('1', 1)"); + conn1.commit(); + try (ResultSet rs = stat1.executeQuery("SELECT * FROM TEST WHERE ID = '1'")) { + rs.next(); + assertEquals(1, rs.getInt(2)); + } + stat2.executeUpdate("UPDATE TEST SET \"VALUE\" = \"VALUE\" + 1"); + try (ResultSet rs = stat1.executeQuery("SELECT * FROM TEST WHERE ID = '1'")) { + rs.next(); + assertEquals(isolationLevel == Connection.TRANSACTION_READ_UNCOMMITTED ? 2 : 1, rs.getInt(2)); + } + assertThrows(ErrorCode.LOCK_TIMEOUT_1, stat1) + .executeQuery("SELECT * FROM TEST WHERE ID = '1' FOR UPDATE"); + conn2.commit(); + if (isolationLevel >= Connection.TRANSACTION_REPEATABLE_READ) { + assertThrows(ErrorCode.DEADLOCK_1, stat1) + .executeQuery("SELECT * FROM TEST WHERE ID = '1' FOR UPDATE"); + } else { + try (ResultSet rs = stat1.executeQuery("SELECT * FROM TEST WHERE ID = '1' FOR UPDATE")) { + rs.next(); + assertEquals(2, rs.getInt(2)); + } + try (ResultSet rs = stat1.executeQuery("SELECT * FROM TEST")) { + rs.next(); + assertEquals(2, rs.getInt(2)); + } + try (ResultSet rs = stat1.executeQuery("SELECT * FROM TEST WHERE ID = '1'")) { + rs.next(); + assertEquals(2, rs.getInt(2)); + } + } + } + } + deleteDb("transaction"); + } + + private void testIsolationLevels3() throws SQLException { + for (int isolationLevel : new int[] { Connection.TRANSACTION_READ_UNCOMMITTED, + Connection.TRANSACTION_READ_COMMITTED, Connection.TRANSACTION_REPEATABLE_READ, + Constants.TRANSACTION_SNAPSHOT, Connection.TRANSACTION_SERIALIZABLE }) { + deleteDb("transaction"); + try (Connection conn1 = getConnection("transaction"); Connection conn2 = getConnection("transaction")) { + conn1.setTransactionIsolation(isolationLevel); + conn2.setTransactionIsolation(isolationLevel); + conn1.setAutoCommit(false); + conn2.setAutoCommit(false); + Statement stat1 = conn1.createStatement(); + Statement stat2 = conn2.createStatement(); + stat1.execute("CREATE TABLE TEST(ID BIGINT PRIMARY KEY, ID2 INT UNIQUE, \"VALUE\" INT)"); + stat1.execute("INSERT INTO TEST VALUES (1, 1, 1), (2, 2, 2), (3, 3, 3)"); + conn1.commit(); + try (ResultSet rs = stat1.executeQuery("SELECT * FROM TEST WHERE ID2 IN (1, 2)")) { + rs.next(); + assertEquals(1, rs.getInt(3)); + rs.next(); + assertEquals(2, rs.getInt(3)); + } + stat2.executeUpdate("UPDATE TEST SET ID2 = 4, \"VALUE\" = 5 WHERE ID2 = 2"); + try (ResultSet rs = stat1.executeQuery("SELECT * FROM TEST WHERE ID2 IN (1, 2)")) { + rs.next(); + assertEquals(1, rs.getInt(3)); + if (isolationLevel == Connection.TRANSACTION_READ_UNCOMMITTED) { + assertFalse(rs.next()); + } else { + assertTrue(rs.next()); + assertEquals(2, rs.getInt(3)); + } + } + if (isolationLevel == Connection.TRANSACTION_READ_UNCOMMITTED) { + assertFalse(stat1.executeQuery("SELECT * FROM TEST WHERE ID2 = 2 FOR UPDATE").next()); + assertThrows(ErrorCode.LOCK_TIMEOUT_1, stat1) + .executeQuery("SELECT * FROM TEST WHERE ID2 = 4 FOR UPDATE"); + } else { + assertThrows(ErrorCode.LOCK_TIMEOUT_1, stat1) + .executeQuery("SELECT * FROM TEST WHERE ID2 = 2 FOR UPDATE"); + assertFalse(stat1.executeQuery("SELECT * FROM TEST WHERE ID2 = 4 FOR UPDATE").next()); + } + stat2.executeUpdate("UPDATE TEST SET \"VALUE\" = 6 WHERE ID2 = 3"); + conn2.commit(); + if (isolationLevel == Connection.TRANSACTION_READ_UNCOMMITTED + || isolationLevel == Connection.TRANSACTION_READ_COMMITTED) { + try (ResultSet rs = stat1.executeQuery("SELECT * FROM TEST WHERE ID2 = 4 FOR UPDATE")) { + rs.next(); + assertEquals(5, rs.getInt(3)); + } + try (ResultSet rs = stat1.executeQuery("SELECT * FROM TEST")) { + rs.next(); + assertEquals(1, rs.getInt(3)); + rs.next(); + assertEquals(5, rs.getInt(3)); + rs.next(); + assertEquals(6, rs.getInt(3)); + } + try (ResultSet rs = stat1.executeQuery("SELECT * FROM TEST WHERE ID2 = 4")) { + rs.next(); + assertEquals(5, rs.getInt(3)); + } + } else { + try (ResultSet rs = stat1.executeQuery("SELECT * FROM TEST WHERE ID2 = 3")) { + rs.next(); + assertEquals(3, rs.getInt(3)); + } + assertThrows(ErrorCode.DEADLOCK_1, stat1) + .executeQuery("SELECT * FROM TEST WHERE ID2 = 3 FOR UPDATE"); + } + } + } + deleteDb("transaction"); + } + + private void testIsolationLevels4() throws SQLException { + testIsolationLevels4(true); + testIsolationLevels4(false); + } + + private void testIsolationLevels4(boolean primaryKey) throws SQLException { + for (int isolationLevel : new int[] { Connection.TRANSACTION_READ_UNCOMMITTED, + Connection.TRANSACTION_READ_COMMITTED, Connection.TRANSACTION_REPEATABLE_READ, + Constants.TRANSACTION_SNAPSHOT, Connection.TRANSACTION_SERIALIZABLE }) { + deleteDb("transaction"); + try (Connection conn1 = getConnection("transaction"); Connection conn2 = getConnection("transaction")) { + Statement stat1 = conn1.createStatement(); + stat1.execute("CREATE TABLE TEST(ID INT " + (primaryKey ? "PRIMARY KEY" : "UNIQUE") + + ", V INT) AS VALUES (1, 2)"); + conn2.setAutoCommit(false); + conn2.setTransactionIsolation(isolationLevel); + Statement stat2 = conn2.createStatement(); + try (ResultSet rs = stat2.executeQuery("SELECT V FROM TEST WHERE ID = 1")) { + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertFalse(rs.next()); + } + stat1.execute("UPDATE TEST SET V = V + 1"); + try (ResultSet rs = stat2.executeQuery("SELECT V FROM TEST WHERE ID = 1")) { + assertTrue(rs.next()); + assertEquals(isolationLevel >= Connection.TRANSACTION_REPEATABLE_READ ? 2 : 3, rs.getInt(1)); + assertFalse(rs.next()); + } + if (isolationLevel >= Connection.TRANSACTION_REPEATABLE_READ) { + assertThrows(ErrorCode.DEADLOCK_1, stat2).executeUpdate("UPDATE TEST SET V = V + 2"); + try (ResultSet rs = stat2.executeQuery("SELECT V FROM TEST WHERE ID = 1")) { + assertTrue(rs.next()); + assertEquals(3, rs.getInt(1)); + assertFalse(rs.next()); + } + stat1.execute("DELETE FROM TEST"); + assertThrows(ErrorCode.DEADLOCK_1, stat2).executeUpdate("UPDATE TEST SET V = V + 2"); + stat1.execute("INSERT INTO TEST VALUES (1, 2)"); + try (ResultSet rs = stat2.executeQuery("SELECT V FROM TEST WHERE ID = 1")) { + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertFalse(rs.next()); + } + stat1.execute("DELETE FROM TEST"); + stat1.execute("INSERT INTO TEST VALUES (1, 2)"); + if (primaryKey) { + // With a delegate index the row was completely + // restored, so no error + assertEquals(1, stat2.executeUpdate("UPDATE TEST SET V = V + 2")); + try (ResultSet rs = stat2.executeQuery("SELECT V FROM TEST WHERE ID = 1")) { + assertTrue(rs.next()); + assertEquals(4, rs.getInt(1)); + assertFalse(rs.next()); + } + conn2.commit(); + try (ResultSet rs = stat2.executeQuery("SELECT V FROM TEST WHERE ID = 1")) { + assertTrue(rs.next()); + assertEquals(4, rs.getInt(1)); + assertFalse(rs.next()); + } + } else { + // With a secondary index restored row is not the same + assertThrows(ErrorCode.DEADLOCK_1, stat2).executeUpdate("UPDATE TEST SET V = V + 2"); + try (ResultSet rs = stat2.executeQuery("SELECT V FROM TEST WHERE ID = 1")) { + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertFalse(rs.next()); + } + } + stat1.execute("DELETE FROM TEST"); + assertThrows(ErrorCode.DUPLICATE_KEY_1, stat2).execute("INSERT INTO TEST VALUES (1, 3)"); + } + } + } + deleteDb("transaction"); + } + + private void testIsolationLevelsCountAggregate() throws SQLException { + testIsolationLevelsCountAggregate(Connection.TRANSACTION_READ_UNCOMMITTED, 12, 15, 15, 16); + testIsolationLevelsCountAggregate(Connection.TRANSACTION_READ_COMMITTED, 6, 9, 15, 16); + testIsolationLevelsCountAggregate(Connection.TRANSACTION_REPEATABLE_READ, 6, 9, 9, 15); + testIsolationLevelsCountAggregate(Constants.TRANSACTION_SNAPSHOT, 6, 9, 9, 15); + testIsolationLevelsCountAggregate(Connection.TRANSACTION_SERIALIZABLE, 6, 9, 9, 15); + } + + private void testIsolationLevelsCountAggregate(int isolationLevel, long uncommitted1, long uncommitted2, + long committed, long committedOther) throws SQLException { + deleteDb("transaction"); + try (Connection conn1 = getConnection("transaction"); Connection conn2 = getConnection("transaction")) { + Statement stat1 = conn1.createStatement(); + stat1.execute("CREATE TABLE TEST(V BIGINT) AS VALUES 1, 2, 3, 4, 5, 18"); + conn1.setTransactionIsolation(isolationLevel); + conn1.setAutoCommit(false); + PreparedStatement all = conn1.prepareStatement("SELECT COUNT(*) FROM TEST"); + PreparedStatement simple = conn1.prepareStatement("SELECT COUNT(V) FROM TEST"); + conn2.setAutoCommit(false); + Statement stat2 = conn2.createStatement(); + testIsolationLevelsCountAggregate(all, simple, 6); + stat2.executeUpdate("DELETE FROM TEST WHERE V IN(3, 4)"); + stat2.executeUpdate("INSERT INTO TEST SELECT * FROM SYSTEM_RANGE(10, 17)"); + testIsolationLevelsCountAggregate(all, simple, uncommitted1); + stat1.executeUpdate("DELETE FROM TEST WHERE V = 2"); + stat1.executeUpdate("INSERT INTO TEST SELECT * FROM SYSTEM_RANGE(6, 9)"); + testIsolationLevelsCountAggregate(all, simple, uncommitted2); + conn2.commit(); + testIsolationLevelsCountAggregate(all, simple, committed); + conn1.commit(); + testIsolationLevelsCountAggregate(all, simple, 15); + stat2.executeUpdate("DELETE FROM TEST WHERE V = 17"); + stat2.executeUpdate("INSERT INTO TEST VALUES 19, 20"); + conn2.commit(); + testIsolationLevelsCountAggregate(all, simple, committedOther); + } + } + + private void testIsolationLevelsCountAggregate(PreparedStatement all, PreparedStatement simple, long expected) + throws SQLException { + try (ResultSet rs = all.executeQuery()) { + rs.next(); + assertEquals(expected, rs.getLong(1)); + } + try (ResultSet rs = simple.executeQuery()) { + rs.next(); + assertEquals(expected, rs.getLong(1)); + } + } + + private void testIsolationLevelsCountAggregate2() throws SQLException { + testIsolationLevelsCountAggregate2(Connection.TRANSACTION_READ_UNCOMMITTED); + testIsolationLevelsCountAggregate2(Connection.TRANSACTION_READ_COMMITTED); + testIsolationLevelsCountAggregate2(Connection.TRANSACTION_REPEATABLE_READ); + testIsolationLevelsCountAggregate2(Constants.TRANSACTION_SNAPSHOT); + testIsolationLevelsCountAggregate2(Connection.TRANSACTION_SERIALIZABLE); + } + + private void testIsolationLevelsCountAggregate2(int isolationLevel) + throws SQLException { + deleteDb("transaction"); + try (Connection conn1 = getConnection("transaction"); Connection conn2 = getConnection("transaction")) { + conn1.setTransactionIsolation(isolationLevel); + conn1.setAutoCommit(false); + Statement stat1 = conn1.createStatement(); + Statement stat2 = conn2.createStatement(); + stat1.executeUpdate( + "CREATE TABLE TEST(X INTEGER PRIMARY KEY, Y INTEGER) AS SELECT X, 1 FROM SYSTEM_RANGE(1, 100)"); + conn1.commit(); + conn2.setTransactionIsolation(isolationLevel); + conn2.setAutoCommit(false); + PreparedStatement prep = conn1.prepareStatement("SELECT COUNT(*) FROM TEST"); + // Initial count + testIsolationLevelCountAggregate2(prep, 100L); + stat1.executeUpdate("INSERT INTO TEST VALUES (101, 2)"); + stat1.executeUpdate("DELETE FROM TEST WHERE X BETWEEN 2 AND 3"); + stat1.executeUpdate("UPDATE TEST SET Y = 2 WHERE X BETWEEN 4 AND 7"); + // Own uncommitted changes + testIsolationLevelCountAggregate2(prep, 99L); + stat2.executeUpdate("INSERT INTO TEST VALUES (102, 2)"); + stat2.executeUpdate("DELETE FROM TEST WHERE X BETWEEN 12 AND 13"); + stat2.executeUpdate("UPDATE TEST SET Y = 2 WHERE X BETWEEN 14 AND 17"); + // Own and concurrent uncommitted changes + testIsolationLevelCountAggregate2(prep, + isolationLevel == Connection.TRANSACTION_READ_UNCOMMITTED ? 98L : 99L); + conn2.commit(); + // Own uncommitted and concurrent committed changes + testIsolationLevelCountAggregate2(prep, + isolationLevel <= Connection.TRANSACTION_READ_COMMITTED ? 98L: 99L); + conn1.commit(); + // Everything is committed + testIsolationLevelCountAggregate2(prep, 98L); + stat2.executeUpdate("INSERT INTO TEST VALUES (103, 2)"); + stat2.executeUpdate("DELETE FROM TEST WHERE X BETWEEN 22 AND 23"); + stat2.executeUpdate("UPDATE TEST SET Y = 2 WHERE X BETWEEN 24 AND 27"); + // Concurrent uncommitted changes + testIsolationLevelCountAggregate2(prep, + isolationLevel == Connection.TRANSACTION_READ_UNCOMMITTED ? 97L : 98L); + conn2.commit(); + // Concurrent committed changes + testIsolationLevelCountAggregate2(prep, + isolationLevel <= Connection.TRANSACTION_READ_COMMITTED ? 97L: 98L); + conn1.commit(); + // Everything is committed again + testIsolationLevelCountAggregate2(prep, 97L); + stat2.executeUpdate("INSERT INTO TEST VALUES (104, 2)"); + conn1.commit(); + // Transaction was started with concurrent uncommitted change + testIsolationLevelCountAggregate2(prep, + isolationLevel == Connection.TRANSACTION_READ_UNCOMMITTED ? 98L : 97L); + } + } + + private void testIsolationLevelCountAggregate2(PreparedStatement prep, long expected) throws SQLException { + ResultSet rs; + rs = prep.executeQuery(); + rs.next(); + assertEquals(expected, rs.getLong(1)); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestTriggersConstraints.java b/h2/src/test/org/h2/test/db/TestTriggersConstraints.java new file mode 100644 index 0000000..30c3d34 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestTriggersConstraints.java @@ -0,0 +1,830 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Arrays; +import java.util.HashSet; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicIntegerArray; + +import org.h2.api.ErrorCode; +import org.h2.api.Trigger; +import org.h2.message.DbException; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.TriggerAdapter; +import org.h2.util.StringUtils; +import org.h2.util.Task; +import org.h2.value.ValueBigint; + +/** + * Tests for trigger and constraints. + */ +public class TestTriggersConstraints extends TestDb implements Trigger { + + private static boolean mustNotCallTrigger; + private String triggerName; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb("trigger"); + testWrongDataType(); + testTriggerDeadlock(); + testTriggerAdapter(); + testTriggerSelectEachRow(); + testViewTrigger(); + testViewTriggerGeneratedKeys(); + testTriggerBeforeSelect(); + testTriggerAlterTable(); + testTriggerAsSource(); + testTriggerAsJavascript(); + testTriggers(); + testConstraints(); + testCheckConstraintErrorMessage(); + testMultiPartForeignKeys(); + testConcurrent(); + deleteDb("trigger"); + } + + /** + * A trigger that deletes all rows in the test table. + */ + public static class DeleteTrigger extends TriggerAdapter { + @Override + public void fire(Connection conn, ResultSet oldRow, ResultSet newRow) + throws SQLException { + conn.createStatement().execute("delete from test"); + } + } + + /** + * Trigger that sets value of the wrong data type. + */ + public static class WrongTrigger implements Trigger { + @Override + public void fire(Connection conn, Object[] oldRow, Object[] newRow) throws SQLException { + newRow[1] = "Wrong value"; + } + } + + /** + * Trigger that sets value of the wrong data type. + */ + public static class WrongTriggerAdapter extends TriggerAdapter { + @Override + public void fire(Connection conn, ResultSet oldRow, ResultSet newRow) throws SQLException { + newRow.updateString(2, "Wrong value"); + } + } + + /** + * Trigger that sets null value. + */ + public static class NullTrigger implements Trigger { + @Override + public void fire(Connection conn, Object[] oldRow, Object[] newRow) throws SQLException { + newRow[1] = null; + } + } + + /** + * Trigger that sets null value. + */ + public static class NullTriggerAdapter extends TriggerAdapter { + @Override + public void fire(Connection conn, ResultSet oldRow, ResultSet newRow) throws SQLException { + newRow.updateNull(2); + } + } + + private void testWrongDataType() throws Exception { + try (Connection conn = getConnection("trigger")) { + Statement stat = conn.createStatement(); + stat.executeUpdate("CREATE TABLE TEST(A INTEGER, B INTEGER NOT NULL)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST VALUES (1, 2)"); + + stat.executeUpdate("CREATE TRIGGER TEST_TRIGGER BEFORE INSERT ON TEST FOR EACH ROW CALL '" + + WrongTrigger.class.getName() + '\''); + assertThrows(ErrorCode.DATA_CONVERSION_ERROR_1, prep).executeUpdate(); + stat.executeUpdate("DROP TRIGGER TEST_TRIGGER"); + + stat.executeUpdate("CREATE TRIGGER TEST_TRIGGER BEFORE INSERT ON TEST FOR EACH ROW CALL '" + + WrongTriggerAdapter.class.getName() + '\''); + assertThrows(ErrorCode.DATA_CONVERSION_ERROR_1, prep).executeUpdate(); + stat.executeUpdate("DROP TRIGGER TEST_TRIGGER"); + + stat.executeUpdate("CREATE TRIGGER TEST_TRIGGER BEFORE INSERT ON TEST FOR EACH ROW CALL '" + + NullTrigger.class.getName() + '\''); + assertThrows(ErrorCode.NULL_NOT_ALLOWED, prep).executeUpdate(); + stat.executeUpdate("DROP TRIGGER TEST_TRIGGER"); + + stat.executeUpdate("CREATE TRIGGER TEST_TRIGGER BEFORE INSERT ON TEST FOR EACH ROW CALL '" + + NullTriggerAdapter.class.getName() + '\''); + assertThrows(ErrorCode.NULL_NOT_ALLOWED, prep).executeUpdate(); + stat.executeUpdate("DROP TRIGGER TEST_TRIGGER"); + + stat.executeUpdate("DROP TABLE TEST"); + } + } + + private void testTriggerDeadlock() throws Exception { + final CountDownLatch latch = new CountDownLatch(2); + try (Connection conn = getConnection("trigger")) { + Statement stat = conn.createStatement(); + stat.execute("create table test(id int) as select 1"); + stat.execute("create table test2(id int) as select 1"); + stat.execute("create trigger test_u before update on test2 " + + "for each row call \"" + DeleteTrigger.class.getName() + "\""); + conn.setAutoCommit(false); + stat.execute("update test set id = 2"); + Task task = new Task() { + @Override + public void call() throws Exception { + try (Connection conn2 = getConnection("trigger")) { + conn2.setAutoCommit(false); + try (Statement stat2 = conn2.createStatement()) { + latch.countDown(); + latch.await(); + stat2.execute("update test2 set id = 4"); + } + conn2.rollback(); + } catch (SQLException e) { + int errorCode = e.getErrorCode(); + assertTrue(String.valueOf(errorCode), + ErrorCode.LOCK_TIMEOUT_1 == errorCode || + ErrorCode.DEADLOCK_1 == errorCode); + } + } + }; + task.execute(); + latch.countDown(); + latch.await(); + try { + stat.execute("update test2 set id = 3"); + } catch (SQLException e) { + int errorCode = e.getErrorCode(); + assertTrue(String.valueOf(errorCode), + ErrorCode.LOCK_TIMEOUT_1 == errorCode || + ErrorCode.DEADLOCK_1 == errorCode); + } + task.get(); + conn.rollback(); + stat.execute("drop table test"); + stat.execute("drop table test2"); + } + } + + private void testTriggerAdapter() throws SQLException { + Connection conn; + Statement stat; + conn = getConnection("trigger"); + stat = conn.createStatement(); + stat.execute("drop table if exists test"); + stat.execute("create table test(id int, c clob, b blob)"); + stat.execute("create table message(name varchar)"); + stat.execute( + "create trigger test_insert before insert, update, delete on test " + + "for each row call \"" + TestTriggerAdapter.class.getName() + "\""); + stat.execute("insert into test values(1, 'hello', 'abcd')"); + ResultSet rs; + rs = stat.executeQuery("select * from test"); + rs.next(); + assertEquals(10, rs.getInt(1)); + stat.execute("update test set id = 2"); + rs = stat.executeQuery("select * from test"); + rs.next(); + assertEquals(20, rs.getInt(1)); + stat.execute("delete from test"); + rs = stat.executeQuery("select * from message"); + assertTrue(rs.next()); + assertEquals("+1;", rs.getString(1)); + assertTrue(rs.next()); + assertEquals("-10;+2;", rs.getString(1)); + assertTrue(rs.next()); + assertEquals("-20;", rs.getString(1)); + assertFalse(rs.next()); + stat.execute("drop table test, message"); + conn.close(); + } + + private void testTriggerSelectEachRow() throws SQLException { + Connection conn; + Statement stat; + conn = getConnection("trigger"); + stat = conn.createStatement(); + stat.execute("drop table if exists test"); + stat.execute("create table test(id int)"); + assertThrows(ErrorCode.INVALID_TRIGGER_FLAGS_1, stat) + .execute("create trigger test_insert before select on test " + + "for each row call \"" + TestTriggerAdapter.class.getName() + "\""); + conn.close(); + } + + private void testViewTrigger() throws SQLException { + Connection conn; + Statement stat; + conn = getConnection("trigger"); + stat = conn.createStatement(); + stat.execute("drop table if exists test"); + stat.execute("create table test(id int)"); + stat.execute("create view test_view as select * from test"); + stat.execute("create trigger test_view_insert " + + "instead of insert on test_view for each row call \"" + + TestView.class.getName() + "\""); + stat.execute("create trigger test_view_delete " + + "instead of delete on test_view for each row call \"" + + TestView.class.getName() + "\""); + if (!config.memory) { + conn.close(); + conn = getConnection("trigger"); + stat = conn.createStatement(); + } + int count = stat.executeUpdate("insert into test_view values(1)"); + assertEquals(1, count); + ResultSet rs; + rs = stat.executeQuery("select * from test"); + assertTrue(rs.next()); + assertFalse(rs.next()); + count = stat.executeUpdate("delete from test_view"); + assertEquals(1, count); + stat.execute("drop view test_view"); + stat.execute("drop table test"); + conn.close(); + } + + private void testViewTriggerGeneratedKeys() throws SQLException { + Connection conn; + Statement stat; + conn = getConnection("trigger"); + stat = conn.createStatement(); + stat.execute("drop table if exists test"); + stat.execute("create table test(id int generated by default as identity)"); + stat.execute("create view test_view as select * from test"); + stat.execute("create trigger test_view_insert " + + "instead of insert on test_view for each row call \"" + + TestViewGeneratedKeys.class.getName() + "\""); + if (!config.memory) { + conn.close(); + conn = getConnection("trigger"); + stat = conn.createStatement(); + } + + PreparedStatement pstat; + pstat = conn.prepareStatement( + "insert into test_view values()", new int[] { 1 }); + int count = pstat.executeUpdate(); + assertEquals(1, count); + + ResultSet gkRs; + gkRs = pstat.getGeneratedKeys(); + + assertTrue(gkRs.next()); + assertEquals(1, gkRs.getInt(1)); + assertFalse(gkRs.next()); + + ResultSet rs; + rs = stat.executeQuery("select * from test"); + assertTrue(rs.next()); + assertFalse(rs.next()); + stat.execute("drop view test_view"); + stat.execute("drop table test"); + conn.close(); + } + + /** + * A test trigger adapter implementation. + */ + public static class TestTriggerAdapter extends TriggerAdapter { + + @Override + public void fire(Connection conn, ResultSet oldRow, ResultSet newRow) + throws SQLException { + StringBuilder buff = new StringBuilder(); + if (oldRow != null) { + buff.append("-").append(oldRow.getString("id")).append(';'); + } + if (newRow != null) { + buff.append("+").append(newRow.getString("id")).append(';'); + } + if (!"TEST_INSERT".equals(triggerName)) { + throw new RuntimeException("Wrong trigger name: " + triggerName); + } + if (!"TEST".equals(tableName)) { + throw new RuntimeException("Wrong table name: " + tableName); + } + if (!"PUBLIC".equals(schemaName)) { + throw new RuntimeException("Wrong schema name: " + schemaName); + } + if (type != (Trigger.INSERT | Trigger.UPDATE | Trigger.DELETE)) { + throw new RuntimeException("Wrong type: " + type); + } + if (newRow != null) { + if (oldRow == null) { + if (newRow.getInt(1) != 1) { + throw new RuntimeException("Expected: 1 got: " + + newRow.getString(1)); + } + } else { + if (newRow.getInt(1) != 2) { + throw new RuntimeException("Expected: 2 got: " + + newRow.getString(1)); + } + } + newRow.getCharacterStream(2); + newRow.getBinaryStream(3); + newRow.updateInt(1, newRow.getInt(1) * 10); + } + conn.createStatement().execute("insert into message values('" + + buff.toString() + "')"); + } + + } + + /** + * A test trigger implementation. + */ + public static class TestView implements Trigger { + + PreparedStatement prepInsert; + + @Override + public void init(Connection conn, String schemaName, + String triggerName, String tableName, boolean before, int type) + throws SQLException { + prepInsert = conn.prepareStatement("insert into test values(?)"); + } + + @Override + public void fire(Connection conn, Object[] oldRow, Object[] newRow) + throws SQLException { + if (newRow != null) { + prepInsert.setInt(1, (Integer) newRow[0]); + prepInsert.execute(); + } + } + + } + + /** + * + */ + public static class TestViewGeneratedKeys implements Trigger { + + PreparedStatement prepInsert; + + @Override + public void init(Connection conn, String schemaName, + String triggerName, String tableName, boolean before, int type) + throws SQLException { + prepInsert = conn.prepareStatement( + "insert into test values()", Statement.RETURN_GENERATED_KEYS); + } + + @Override + public void fire(Connection conn, Object[] oldRow, Object[] newRow) + throws SQLException { + if (newRow != null) { + prepInsert.execute(); + ResultSet rs = prepInsert.getGeneratedKeys(); + if (rs.next()) { + newRow[0] = ValueBigint.get(rs.getLong(1)); + } + } + } + + } + + private void testTriggerBeforeSelect() throws SQLException { + Connection conn; + Statement stat; + conn = getConnection("trigger"); + stat = conn.createStatement(); + stat.execute("drop table if exists meta_tables"); + stat.execute("create table meta_tables(name varchar)"); + stat.execute("create trigger meta_tables_select " + + "before select on meta_tables call \"" + TestSelect.class.getName() + "\""); + ResultSet rs; + rs = stat.executeQuery("select * from meta_tables"); + assertTrue(rs.next()); + assertFalse(rs.next()); + stat.execute("create table test(id int)"); + rs = stat.executeQuery("select * from meta_tables"); + assertTrue(rs.next()); + assertTrue(rs.next()); + assertFalse(rs.next()); + conn.close(); + if (!config.memory) { + conn = getConnection("trigger"); + stat = conn.createStatement(); + stat.execute("create table test2(id int)"); + rs = stat.executeQuery("select * from meta_tables"); + assertTrue(rs.next()); + assertTrue(rs.next()); + assertTrue(rs.next()); + assertFalse(rs.next()); + conn.close(); + } + } + + /** + * A test trigger implementation. + */ + public static class TestSelect implements Trigger { + + PreparedStatement prepMeta; + + @Override + public void init(Connection conn, String schemaName, + String triggerName, String tableName, boolean before, int type) + throws SQLException { + prepMeta = conn.prepareStatement("insert into meta_tables " + + "select table_name from information_schema.tables " + + "where table_schema='PUBLIC'"); + } + + @Override + public void fire(Connection conn, Object[] oldRow, Object[] newRow) + throws SQLException { + if (oldRow != null || newRow != null) { + throw new SQLException("old and new must be null"); + } + conn.createStatement().execute("delete from meta_tables"); + prepMeta.execute(); + } + + } + + /** + * A test trigger implementation. + */ + public static class TestTriggerAlterTable implements Trigger { + + @Override + public void fire(Connection conn, Object[] oldRow, Object[] newRow) + throws SQLException { + conn.createStatement().execute("call next value for seq"); + } + + @Override + public void close() { + // ignore + } + + @Override + public void remove() { + // ignore + } + + } + + private void testTriggerAlterTable() throws SQLException { + deleteDb("trigger"); + testTrigger(null); + } + + private void testTriggerAsSource() throws SQLException { + deleteDb("trigger"); + testTrigger("java"); + } + + private void testTriggerAsJavascript() throws SQLException { + deleteDb("trigger"); + testTrigger("javascript"); + } + + private void testTrigger(final String sourceLang) throws SQLException { + final String callSeq = "call next value for seq"; + Connection conn = getConnection("trigger"); + Statement stat = conn.createStatement(); + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("create sequence seq"); + stat.execute("create table test(id int primary key)"); + assertSingleValue(stat, callSeq, 1); + conn.setAutoCommit(false); + Trigger t = new org.h2.test.db.TestTriggersConstraints.TestTriggerAlterTable(); + t.close(); + if ("java".equals(sourceLang)) { + String triggerClassName = this.getClass().getName() + "." + + TestTriggerAlterTable.class.getSimpleName(); + stat.execute("create trigger test_upd before insert on test " + + "as $$org.h2.api.Trigger create() " + "{ return new " + + triggerClassName + "(); } $$"); + } else if ("javascript".equals(sourceLang)) { + String triggerClassName = this.getClass().getName() + "." + + TestTriggerAlterTable.class.getSimpleName(); + final String body = "//javascript\n" + + "new Packages." + triggerClassName + "();"; + stat.execute("create trigger test_upd before insert on test as $$" + + body + " $$"); + } else { + stat.execute("create trigger test_upd before insert on test call \"" + + TestTriggerAlterTable.class.getName() + "\""); + } + stat.execute("insert into test values(1)"); + assertSingleValue(stat, callSeq, 3); + stat.execute("alter table test add column name varchar"); + assertSingleValue(stat, callSeq, 4); + stat.execute("drop sequence seq"); + stat.execute("drop table test"); + conn.close(); + } + + private void testConstraints() throws SQLException { + Connection conn = getConnection("trigger"); + Statement stat = conn.createStatement(); + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("create table test(id int primary key, parent int)"); + stat.execute("alter table test add constraint test_parent_id " + + "foreign key(parent) references test (id) on delete cascade"); + stat.execute("insert into test select x, x/2 from system_range(0, 100)"); + stat.execute("delete from test"); + assertSingleValue(stat, "select count(*) from test", 0); + stat.execute("drop table test"); + conn.close(); + } + + private void testCheckConstraintErrorMessage() throws SQLException { + Connection conn = getConnection("trigger"); + Statement stat = conn.createStatement(); + + stat.execute("create table companies(id identity)"); + stat.execute("create table departments(id identity, " + + "company_id int not null, " + + "foreign key(company_id) references companies(id))"); + stat.execute("create table connections (id identity, company_id int not null, " + + "first int not null, `second` int not null, " + + "foreign key (company_id) references companies(id), " + + "foreign key (first) references departments(id), " + + "foreign key (`second`) references departments(id), " + + "check (select departments.company_id from departments, companies where " + + " departments.id in (first, `second`)) = company_id)"); + stat.execute("insert into companies(id) values(1)"); + stat.execute("insert into departments(id, company_id) " + + "values(10, 1)"); + stat.execute("insert into departments(id, company_id) " + + "values(20, 1)"); + assertThrows(ErrorCode.CHECK_CONSTRAINT_INVALID, stat) + .execute("insert into connections(id, company_id, first, `second`) " + + "values(100, 1, 10, 20)"); + + stat.execute("drop table connections"); + stat.execute("drop table departments"); + stat.execute("drop table companies"); + conn.close(); + } + + /** + * Regression test: we had a bug where the AlterTableAddConstraint class + * used to sometimes pick the wrong unique index for a foreign key. + */ + private void testMultiPartForeignKeys() throws SQLException { + Connection conn = getConnection("trigger"); + Statement stat = conn.createStatement(); + stat.execute("DROP TABLE IF EXISTS TEST1"); + stat.execute("DROP TABLE IF EXISTS TEST2"); + + stat.execute("create table test1(id int primary key, col1 int)"); + stat.execute("alter table test1 add constraint unique_test1 " + + "unique (id,col1)"); + + stat.execute("create table test2(id int primary key, col1 int)"); + stat.execute("alter table test2 add constraint fk_test2 " + + "foreign key(id,col1) references test1 (id,col1)"); + + stat.execute("insert into test1 values (1,1)"); + stat.execute("insert into test1 values (2,2)"); + stat.execute("insert into test1 values (3,3)"); + stat.execute("insert into test2 values (1,1)"); + assertThrows(23506, stat).execute("insert into test2 values (2,1)"); + assertSingleValue(stat, "select count(*) from test1", 3); + assertSingleValue(stat, "select count(*) from test2", 1); + + stat.execute("drop table test1, test2"); + conn.close(); + } + + private void testTriggers() throws SQLException { + mustNotCallTrigger = false; + Connection conn = getConnection("trigger"); + Statement stat = conn.createStatement(); + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + // CREATE TRIGGER trigger {BEFORE|AFTER} + // {INSERT|UPDATE|DELETE|ROLLBACK} ON table + // [FOR EACH ROW] [QUEUE n] [NOWAIT] CALL triggeredClass + stat.execute("CREATE TRIGGER IF NOT EXISTS INS_BEFORE " + + "BEFORE INSERT ON TEST " + + "FOR EACH ROW NOWAIT CALL '" + getClass().getName() + '\''); + stat.execute("CREATE TRIGGER IF NOT EXISTS INS_BEFORE " + + "BEFORE INSERT ON TEST " + + "FOR EACH ROW NOWAIT CALL '" + getClass().getName() + '\''); + stat.execute("CREATE TRIGGER INS_AFTER " + "" + + "AFTER INSERT ON TEST " + + "FOR EACH ROW NOWAIT CALL '" + getClass().getName() + '\''); + stat.execute("CREATE TRIGGER UPD_BEFORE " + + "BEFORE UPDATE ON TEST " + + "FOR EACH ROW NOWAIT CALL '" + getClass().getName() + '\''); + stat.execute("CREATE TRIGGER INS_AFTER_ROLLBACK " + + "AFTER INSERT, ROLLBACK ON TEST " + + "FOR EACH ROW NOWAIT CALL '" + getClass().getName() + '\''); + stat.execute("INSERT INTO TEST VALUES(1, 'Hello')"); + ResultSet rs; + rs = stat.executeQuery("SCRIPT"); + checkRows(rs, new String[] { + "CREATE FORCE TRIGGER \"PUBLIC\".\"INS_BEFORE\" " + + "BEFORE INSERT ON \"PUBLIC\".\"TEST\" " + + "FOR EACH ROW NOWAIT CALL '" + getClass().getName() + "';", + "CREATE FORCE TRIGGER \"PUBLIC\".\"INS_AFTER\" " + + "AFTER INSERT ON \"PUBLIC\".\"TEST\" " + + "FOR EACH ROW NOWAIT CALL '" + getClass().getName() + "';", + "CREATE FORCE TRIGGER \"PUBLIC\".\"UPD_BEFORE\" " + + "BEFORE UPDATE ON \"PUBLIC\".\"TEST\" " + + "FOR EACH ROW NOWAIT CALL '" + getClass().getName() + "';", + "CREATE FORCE TRIGGER \"PUBLIC\".\"INS_AFTER_ROLLBACK\" " + + "AFTER INSERT, ROLLBACK ON \"PUBLIC\".\"TEST\" " + + "FOR EACH ROW NOWAIT CALL '" + getClass().getName() + "';", + }); + while (rs.next()) { + String sql = rs.getString(1); + if (sql.startsWith("CREATE TRIGGER")) { + System.out.println(sql); + } + } + + rs = stat.executeQuery("SELECT * FROM TEST"); + rs.next(); + assertEquals("Hello-updated", rs.getString(2)); + assertFalse(rs.next()); + stat.execute("UPDATE TEST SET NAME=NAME||'-upd'"); + rs = stat.executeQuery("SELECT * FROM TEST"); + rs.next(); + assertEquals("Hello-updated-upd-updated2", rs.getString(2)); + assertFalse(rs.next()); + + mustNotCallTrigger = true; + stat.execute("DROP TRIGGER IF EXISTS INS_BEFORE"); + stat.execute("DROP TRIGGER IF EXISTS INS_BEFORE"); + stat.execute("DROP TRIGGER IF EXISTS INS_AFTER_ROLLBACK"); + assertThrows(ErrorCode.TRIGGER_NOT_FOUND_1, stat). + execute("DROP TRIGGER INS_BEFORE"); + stat.execute("DROP TRIGGER INS_AFTER"); + stat.execute("DROP TRIGGER UPD_BEFORE"); + stat.execute("UPDATE TEST SET NAME=NAME||'-upd-no_trigger'"); + stat.execute("INSERT INTO TEST VALUES(100, 'Insert-no_trigger')"); + conn.close(); + + conn = getConnection("trigger"); + + mustNotCallTrigger = false; + conn.close(); + } + + private void checkRows(ResultSet rs, String[] expected) throws SQLException { + HashSet set = new HashSet<>(Arrays.asList(expected)); + while (rs.next()) { + set.remove(rs.getString(1)); + } + if (set.size() > 0) { + fail("set should be empty: " + set); + } + } + + private void testConcurrent() throws Exception { + deleteDb("trigger"); + Connection conn = getConnection("trigger"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(A INT)"); + stat.execute("CREATE TRIGGER TEST_BEFORE BEFORE INSERT, UPDATE ON TEST FOR EACH ROW CALL " + + StringUtils.quoteStringSQL(ConcurrentTrigger.class.getName())); + Thread[] threads = new Thread[ConcurrentTrigger.N_T]; + AtomicInteger a = new AtomicInteger(); + for (int i = 0; i < ConcurrentTrigger.N_T; i++) { + Thread thread = new Thread() { + @Override + public void run() { + try (Connection conn = getConnection("trigger")) { + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(A) VALUES ?"); + for (int j = 0; j < ConcurrentTrigger.N_R; j++) { + prep.setInt(1, a.getAndIncrement()); + prep.executeUpdate(); + } + } catch (SQLException e) { + throw DbException.convert(e); + } + } + }; + threads[i] = thread; + } + synchronized (TestTriggersConstraints.class) { + AtomicIntegerArray array = ConcurrentTrigger.array; + int l = array.length(); + for (int i = 0; i < l; i++) { + array.set(i, 0); + } + for (Thread thread : threads) { + thread.start(); + } + for (Thread thread : threads) { + thread.join(); + } + for (int i = 0; i < l; i++) { + assertEquals(1, array.get(i)); + } + } + conn.close(); + } + + public static final class ConcurrentTrigger extends TriggerAdapter { + + static final int N_T = 4; + + static final int N_R = 250; + + static final AtomicIntegerArray array = new AtomicIntegerArray(N_T * N_R); + + @Override + public void fire(Connection conn, ResultSet oldRow, ResultSet newRow) throws SQLException { + array.set(newRow.getInt(1), 1); + } + + } + + @Override + public void fire(Connection conn, Object[] oldRow, Object[] newRow) + throws SQLException { + if (mustNotCallTrigger) { + throw new AssertionError("must not be called now"); + } + if (conn == null) { + throw new AssertionError("connection is null"); + } + if (triggerName.startsWith("INS_BEFORE")) { + newRow[1] = newRow[1] + "-updated"; + } else if (triggerName.startsWith("INS_AFTER")) { + if (!newRow[1].toString().endsWith("-updated")) { + throw new AssertionError("supposed to be updated"); + } + checkCommit(conn); + } else if (triggerName.startsWith("UPD_BEFORE")) { + newRow[1] = newRow[1] + "-updated2"; + } else if (triggerName.startsWith("UPD_AFTER")) { + if (!newRow[1].toString().endsWith("-updated2")) { + throw new AssertionError("supposed to be updated2"); + } + checkCommit(conn); + } + } + + @Override + public void close() { + // ignore + } + + @Override + public void remove() { + // ignore + } + + private void checkCommit(Connection conn) throws SQLException { + assertThrows(ErrorCode.COMMIT_ROLLBACK_NOT_ALLOWED, conn).commit(); + assertThrows(ErrorCode.COMMIT_ROLLBACK_NOT_ALLOWED, conn.createStatement()). + execute("CREATE TABLE X(ID INT)"); + } + + @Override + public void init(Connection conn, String schemaName, String trigger, + String tableName, boolean before, int type) { + this.triggerName = trigger; + if (!"TEST".equals(tableName)) { + throw new AssertionError("supposed to be TEST"); + } + if ((trigger.endsWith("AFTER") && before) || + (trigger.endsWith("BEFORE") && !before)) { + throw new AssertionError("triggerName: " + trigger + " before:" + before); + } + if ((trigger.startsWith("UPD") && type != UPDATE) || + (trigger.startsWith("INS") && type != INSERT) || + (trigger.startsWith("DEL") && type != DELETE)) { + throw new AssertionError("triggerName: " + trigger + " type:" + type); + } + } + +} diff --git a/h2/src/test/org/h2/test/db/TestTwoPhaseCommit.java b/h2/src/test/org/h2/test/db/TestTwoPhaseCommit.java new file mode 100644 index 0000000..3f1380b --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestTwoPhaseCommit.java @@ -0,0 +1,157 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests for the two-phase-commit feature. + */ +public class TestTwoPhaseCommit extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + if (config.memory || config.networked) { + return false; + } + return true; + } + + @Override + public void test() throws SQLException { + deleteDb("twoPhaseCommit"); + + prepare(); + openWith(true); + test(true); + + prepare(); + openWith(false); + test(false); + + testInDoubtAfterShutdown(); + + deleteDb("twoPhaseCommit"); + } + + private void test(boolean rolledBack) throws SQLException { + Connection conn = getConnection("twoPhaseCommit"); + Statement stat = conn.createStatement(); + stat.execute("SET WRITE_DELAY 0"); + ResultSet rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + if (!rolledBack) { + rs.next(); + assertEquals(2, rs.getInt(1)); + assertEquals("World", rs.getString(2)); + } + assertFalse(rs.next()); + conn.close(); + } + + private void openWith(boolean rollback) throws SQLException { + Connection conn = getConnection("twoPhaseCommit"); + Statement stat = conn.createStatement(); + ArrayList list = new ArrayList<>(); + ResultSet rs = stat.executeQuery("SELECT * FROM INFORMATION_SCHEMA.IN_DOUBT"); + while (rs.next()) { + list.add(rs.getString("TRANSACTION_NAME")); + } + for (String s : list) { + if (rollback) { + stat.execute("ROLLBACK TRANSACTION " + s); + } else { + stat.execute("COMMIT TRANSACTION " + s); + } + } + conn.close(); + } + + private void prepare() throws SQLException { + deleteDb("twoPhaseCommit"); + Connection conn = getConnection("twoPhaseCommit"); + Statement stat = conn.createStatement(); + stat.execute("SET WRITE_DELAY 0"); + conn.setAutoCommit(false); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + stat.execute("INSERT INTO TEST VALUES(1, 'Hello')"); + conn.commit(); + stat.execute("INSERT INTO TEST VALUES(2, 'World')"); + stat.execute("PREPARE COMMIT XID_TEST_TRANSACTION_WITH_LONG_NAME"); + crash(conn); + } + + private void testInDoubtAfterShutdown() throws SQLException { + if (config.memory) { + return; + } + deleteDb("twoPhaseCommit"); + Connection conn = getConnection("twoPhaseCommit"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID INT PRIMARY KEY)"); + conn.setAutoCommit(false); + stat.execute("INSERT INTO TEST VALUES (1)"); + stat.execute("PREPARE COMMIT \"#1\""); + conn.commit(); + stat.execute("SHUTDOWN IMMEDIATELY"); + conn = getConnection("twoPhaseCommit"); + stat = conn.createStatement(); + ResultSet rs = stat.executeQuery( + "SELECT TRANSACTION_NAME, TRANSACTION_STATE FROM INFORMATION_SCHEMA.IN_DOUBT"); + assertFalse(rs.next()); + rs = stat.executeQuery("SELECT ID FROM TEST"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertFalse(rs.next()); + conn.setAutoCommit(false); + stat.execute("INSERT INTO TEST VALUES (2)"); + stat.execute("PREPARE COMMIT \"#2\""); + conn.rollback(); + stat.execute("SHUTDOWN IMMEDIATELY"); + conn = getConnection("twoPhaseCommit"); + stat = conn.createStatement(); + rs = stat.executeQuery("SELECT TRANSACTION_NAME, TRANSACTION_STATE FROM INFORMATION_SCHEMA.IN_DOUBT"); + assertFalse(rs.next()); + rs = stat.executeQuery("SELECT ID FROM TEST"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertFalse(rs.next()); + conn.setAutoCommit(false); + stat.execute("INSERT INTO TEST VALUES (3)"); + stat.execute("PREPARE COMMIT \"#3\""); + stat.execute("SHUTDOWN IMMEDIATELY"); + conn = getConnection("twoPhaseCommit"); + stat = conn.createStatement(); + rs = stat.executeQuery("SELECT TRANSACTION_NAME, TRANSACTION_STATE FROM INFORMATION_SCHEMA.IN_DOUBT"); + assertTrue(rs.next()); + assertEquals("#3", rs.getString("TRANSACTION_NAME")); + assertEquals("IN_DOUBT", rs.getString("TRANSACTION_STATE")); + rs = stat.executeQuery("SELECT ID FROM TEST"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertFalse(rs.next()); + conn.close(); + deleteDb("twoPhaseCommit"); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestView.java b/h2/src/test/org/h2/test/db/TestView.java new file mode 100644 index 0000000..1dffd44 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestView.java @@ -0,0 +1,350 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import org.h2.api.ErrorCode; +import org.h2.engine.SessionLocal; +import org.h2.jdbc.JdbcConnection; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Test for views. + */ +public class TestView extends TestDb { + + private static int x; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + deleteDb("view"); + testSubSubQuery(); + testSubQueryViewIndexCache(); + testInnerSelectWithRownum(); + testInnerSelectWithRange(); + testEmptyColumn(); + testChangeSchemaSearchPath(); + testParameterizedView(); + testCache(); + testCacheFunction(true); + testCacheFunction(false); + testInSelect(); + testUnionReconnect(); + testManyViews(); + testReferenceView(); + testViewAlterAndCommandCache(); + deleteDb("view"); + } + + private void testSubSubQuery() throws SQLException { + Connection conn = getConnection("view"); + Statement stat = conn.createStatement(); + stat.execute("drop table test if exists"); + stat.execute("create table test(a int, b int, c int)"); + stat.execute("insert into test values(1, 1, 1)"); + ResultSet rs = stat.executeQuery("select 1 x from (select a, b, c from " + + "(select * from test) bbb where bbb.a >=1 and bbb.a <= 1) sp " + + "where sp.a = 1 and sp.b = 1 and sp.c = 1"); + assertTrue(rs.next()); + conn.close(); + } + + private void testSubQueryViewIndexCache() throws SQLException { + if (config.networked) { + return; + } + Connection conn = getConnection("view"); + Statement stat = conn.createStatement(); + stat.execute("drop table test if exists"); + stat.execute("create table test(id int primary key, " + + "name varchar(25) unique, age int unique)"); + + // check that initial cache size is empty + SessionLocal s = (SessionLocal) ((JdbcConnection) conn).getSession(); + s.clearViewIndexCache(); + assertTrue(s.getViewIndexCache(true).isEmpty()); + assertTrue(s.getViewIndexCache(false).isEmpty()); + + // create view command should not affect caches + stat.execute("create view v as select * from test"); + assertTrue(s.getViewIndexCache(true).isEmpty()); + assertTrue(s.getViewIndexCache(false).isEmpty()); + + // check view index cache + stat.executeQuery("select * from v where id > 0").next(); + int size1 = s.getViewIndexCache(false).size(); + assertTrue(size1 > 0); + assertTrue(s.getViewIndexCache(true).isEmpty()); + stat.executeQuery("select * from v where name = 'xyz'").next(); + int size2 = s.getViewIndexCache(false).size(); + assertTrue(size2 > size1); + assertTrue(s.getViewIndexCache(true).isEmpty()); + + // check we did not add anything to view cache if we run a sub-query + stat.executeQuery("select * from (select * from test) where age = 17").next(); + int size3 = s.getViewIndexCache(false).size(); + assertEquals(size2, size3); + assertTrue(s.getViewIndexCache(true).isEmpty()); + + // check clear works + s.clearViewIndexCache(); + assertTrue(s.getViewIndexCache(false).isEmpty()); + assertTrue(s.getViewIndexCache(true).isEmpty()); + + // drop everything + stat.execute("drop view v"); + stat.execute("drop table test"); + conn.close(); + } + + private void testInnerSelectWithRownum() throws SQLException { + Connection conn = getConnection("view"); + Statement stat = conn.createStatement(); + stat.execute("drop table test if exists"); + stat.execute("create table test(id int primary key, name varchar(1))"); + stat.execute("insert into test(id, name) values(1, 'b'), (3, 'a')"); + ResultSet rs; + rs = stat.executeQuery( + "select nr from (select rownum() as nr, " + + "a.id as id from (select id from test order by name) as a) as b " + + "where b.id = 1;"); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertFalse(rs.next()); + rs = stat.executeQuery( + "select nr from (select row_number() over() as nr, " + + "a.id as id from (select id from test order by name) as a) as b " + + "where b.id = 1;"); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertFalse(rs.next()); + stat.execute("drop table test"); + conn.close(); + } + + private void testInnerSelectWithRange() throws SQLException { + Connection conn = getConnection("view"); + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery( + "select x from (select x from (" + + "select x from system_range(1, 5)) " + + "where x > 2 and x < 4) where x = 3"); + assertTrue(rs.next()); + assertFalse(rs.next()); + rs = stat.executeQuery( + "select x from (select x from (" + + "select x from system_range(1, 5)) " + + "where x = 3) where x > 2 and x < 4"); + assertTrue(rs.next()); + assertFalse(rs.next()); + conn.close(); + } + + private void testEmptyColumn() throws SQLException { + deleteDb("view"); + Connection conn = getConnection("view"); + Statement stat = conn.createStatement(); + stat.execute("create table test(a int, b int)"); + stat.execute("create view test_view as select a, b from test"); + stat.execute("select * from test_view where a between 1 and 2 and b = 2"); + conn.close(); + } + + private void testChangeSchemaSearchPath() throws SQLException { + deleteDb("view"); + Connection conn = getConnection("view"); + Statement stat = conn.createStatement(); + stat.execute("CREATE ALIAS X AS $$ int x() { return 1; } $$;"); + stat.execute("CREATE SCHEMA S"); + stat.execute("CREATE VIEW S.TEST AS SELECT X() FROM DUAL"); + stat.execute("SET SCHEMA=S"); + stat.execute("SET SCHEMA_SEARCH_PATH=S"); + stat.execute("SELECT * FROM TEST"); + conn.close(); + } + + private void testParameterizedView() throws SQLException { + deleteDb("view"); + Connection conn = getConnection("view"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE Test(id INT AUTO_INCREMENT NOT NULL, " + + "f1 VARCHAR NOT NULL, f2 VARCHAR NOT NULL)"); + stat.execute("INSERT INTO Test(f1, f2) VALUES ('value1','value2')"); + stat.execute("INSERT INTO Test(f1, f2) VALUES ('value1','value3')"); + PreparedStatement ps = conn.prepareStatement( + "CREATE VIEW Test_View AS SELECT f2 FROM Test WHERE f1=?"); + ps.setString(1, "value1"); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, ps). + executeUpdate(); + // ResultSet rs; + // rs = stat.executeQuery("SELECT * FROM Test_View"); + // assertTrue(rs.next()); + // assertFalse(rs.next()); + // rs = stat.executeQuery("select VIEW_DEFINITION " + + // "from information_schema.views " + + // "where TABLE_NAME='TEST_VIEW'"); + // rs.next(); + // assertEquals("...", rs.getString(1)); + conn.close(); + } + + private void testCacheFunction(boolean deterministic) throws SQLException { + deleteDb("view"); + Connection conn = getConnection("view"); + Statement stat = conn.createStatement(); + x = 8; + stat.execute("CREATE ALIAS GET_X " + + (deterministic ? "DETERMINISTIC" : "") + + " FOR '" + getClass().getName() + ".getX'"); + stat.execute("CREATE VIEW V AS SELECT * FROM (SELECT GET_X())"); + ResultSet rs; + rs = stat.executeQuery("SELECT * FROM V"); + rs.next(); + assertEquals(8, rs.getInt(1)); + x = 5; + rs = stat.executeQuery("SELECT * FROM V"); + rs.next(); + assertEquals(deterministic ? 8 : 5, rs.getInt(1)); + conn.close(); + } + + /** + * This method is called via reflection from the database. + * + * @return the static value x + */ + public static int getX() { + return x; + } + + private void testCache() throws SQLException { + deleteDb("view"); + Connection conn = getConnection("view"); + Statement stat = conn.createStatement(); + stat.execute("SET @X 8"); + stat.execute("CREATE VIEW V AS SELECT * FROM (SELECT @X)"); + ResultSet rs; + rs = stat.executeQuery("SELECT * FROM V"); + rs.next(); + assertEquals(8, rs.getInt(1)); + stat.execute("SET @X 5"); + rs = stat.executeQuery("SELECT * FROM V"); + rs.next(); + assertEquals(5, rs.getInt(1)); + conn.close(); + } + + private void testInSelect() throws SQLException { + deleteDb("view"); + Connection conn = getConnection("view"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key) as select 1"); + PreparedStatement prep = conn.prepareStatement( + "select * from test t where t.id in " + + "(select t2.id from test t2 where t2.id in (?, ?))"); + prep.setInt(1, 1); + prep.setInt(2, 2); + prep.execute(); + conn.close(); + } + + private void testUnionReconnect() throws SQLException { + if (config.memory) { + return; + } + deleteDb("view"); + Connection conn = getConnection("view"); + Statement stat = conn.createStatement(); + stat.execute("create table t1(k smallint, ts timestamp(6))"); + stat.execute("create table t2(k smallint, ts timestamp(6))"); + stat.execute("create table t3(k smallint, ts timestamp(6))"); + stat.execute("create view v_max_ts as select " + + "max(ts) from (select max(ts) as ts from t1 " + + "union select max(ts) as ts from t2 " + + "union select max(ts) as ts from t3)"); + stat.execute("create view v_test as select max(ts) as ts from t1 " + + "union select max(ts) as ts from t2 " + + "union select max(ts) as ts from t3"); + conn.close(); + conn = getConnection("view"); + stat = conn.createStatement(); + stat.execute("select * from v_max_ts"); + conn.close(); + deleteDb("view"); + } + + private void testManyViews() throws SQLException { + deleteDb("view"); + Connection conn = getConnection("view"); + Statement s = conn.createStatement(); + s.execute("create table t0(id int primary key)"); + s.execute("insert into t0 values(1), (2), (3)"); + for (int i = 0; i < 30; i++) { + s.execute("create view t" + (i + 1) + " as select * from t" + i); + s.execute("select * from t" + (i + 1)); + ResultSet rs = s.executeQuery( + "select count(*) from t" + (i + 1) + " where id=2"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + } + conn.close(); + conn = getConnection("view"); + conn.close(); + deleteDb("view"); + } + + private void testReferenceView() throws SQLException { + deleteDb("view"); + Connection conn = getConnection("view"); + Statement s = conn.createStatement(); + s.execute("create table t0(id int primary key)"); + s.execute("create view t1 as select * from t0"); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, s).execute( + "create table t2(id int primary key, " + + "col1 int not null, foreign key (col1) references t1(id))"); + conn.close(); + deleteDb("view"); + } + + /** + * Make sure that when we change a view, that change in reflected in other + * sessions command cache. + */ + private void testViewAlterAndCommandCache() throws SQLException { + deleteDb("view"); + Connection conn = getConnection("view"); + Statement stat = conn.createStatement(); + stat.execute("create table t0(id int primary key)"); + stat.execute("create table t1(id int primary key)"); + stat.execute("insert into t0 values(0)"); + stat.execute("insert into t1 values(1)"); + stat.execute("create view v1 as select * from t0"); + ResultSet rs = stat.executeQuery("select * from v1"); + assertTrue(rs.next()); + assertEquals(0, rs.getInt(1)); + stat.execute("create or replace view v1 as select * from t1"); + rs = stat.executeQuery("select * from v1"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + conn.close(); + deleteDb("view"); + } + +} diff --git a/h2/src/test/org/h2/test/db/TestViewAlterTable.java b/h2/src/test/org/h2/test/db/TestViewAlterTable.java new file mode 100644 index 0000000..6e8febc --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestViewAlterTable.java @@ -0,0 +1,215 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Test the impact of ALTER TABLE statements on views. + */ +public class TestViewAlterTable extends TestDb { + + private Connection conn; + private Statement stat; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb(getTestName()); + conn = getConnection(getTestName()); + stat = conn.createStatement(); + + testDropColumnWithoutViews(); + testViewsAreWorking(); + testAlterTableDropColumnNotInView(); + testAlterTableDropColumnInView(); + testAlterTableAddColumnWithView(); + testAlterTableAlterColumnDataTypeWithView(); + testSelectStar(); + testJoinAndAlias(); + testSubSelect(); + testForeignKey(); + testAlterTableDropColumnInViewWithDoubleQuotes(); + + conn.close(); + deleteDb(getTestName()); + } + + private void testDropColumnWithoutViews() throws SQLException { + stat.execute("create table test(a int, b int, c int)"); + stat.execute("alter table test drop column c"); + stat.execute("drop table test"); + } + + private void testViewsAreWorking() throws SQLException { + createTestData(); + checkViewRemainsValid(); + } + + private void testAlterTableDropColumnNotInView() throws SQLException { + createTestData(); + stat.execute("alter table test drop column c"); + checkViewRemainsValid(); + } + + private void testAlterTableDropColumnInView() throws SQLException { + // simple + stat.execute("create table test(id identity, name varchar) " + + "as select 1, 'Hello' from dual"); + stat.execute("create view test_view as select * from test"); + assertThrows(ErrorCode.COLUMN_IS_REFERENCED_1, stat). + execute("alter table test drop name"); + ResultSet rs = stat.executeQuery("select * from test_view"); + assertTrue(rs.next()); + stat.execute("drop view test_view"); + stat.execute("drop table test"); + + // nested + createTestData(); + // should throw exception because V1 uses column A + assertThrows(ErrorCode.COLUMN_IS_REFERENCED_1, stat). + execute("alter table test drop column a"); + stat.execute("drop table test cascade"); + } + + private void testAlterTableAddColumnWithView() throws SQLException { + createTestData(); + stat.execute("alter table test add column d int"); + checkViewRemainsValid(); + } + + private void testAlterTableAlterColumnDataTypeWithView() + throws SQLException { + createTestData(); + stat.execute("alter table test alter b char(1)"); + checkViewRemainsValid(); + } + + private void testSelectStar() throws SQLException { + createTestData(); + stat.execute("create view v4 as select * from test"); + stat.execute("alter table test add d int default 6"); + // H2 doesn't remember v4 as 'select * from test', it instead remembers + // each individual column that was in 'test' when the view was + // originally created. This is consistent with PostgreSQL. + assertThrows(ErrorCode.COLUMN_NOT_FOUND_1, stat). + executeQuery("select d from v4"); + checkViewRemainsValid(); + } + + private void testJoinAndAlias() throws SQLException { + createTestData(); + stat.execute("create view v4 as select v1.a dog, v3.a cat " + + "from v1 join v3 on v1.b = v3.a"); + // should make no difference + stat.execute("alter table test add d int default 6"); + ResultSet rs = stat.executeQuery("select cat, dog from v4"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals(2, rs.getInt(2)); + assertFalse(rs.next()); + checkViewRemainsValid(); + } + + private void testSubSelect() throws SQLException { + createTestData(); + stat.execute("create view v4 as select * from v3 " + + "where a in (select b from v2)"); + // should make no difference + stat.execute("alter table test add d int default 6"); + ResultSet rs = stat.executeQuery("select a from v4"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertFalse(rs.next()); + checkViewRemainsValid(); + } + + private void testForeignKey() throws SQLException { + createTestData(); + stat.execute("create table test2(z int, a int, primary key(z), " + + "foreign key (a) references TEST(a))"); + stat.execute("insert into test2(z, a) values (99, 1)"); + // should make no difference + stat.execute("alter table test add d int default 6"); + ResultSet rs = stat.executeQuery("select z from test2"); + assertTrue(rs.next()); + assertEquals(99, rs.getInt(1)); + assertFalse(rs.next()); + stat.execute("drop table test2"); + checkViewRemainsValid(); + } + + private void createTestData() throws SQLException { + stat.execute("create table test(a int primary key, b int, c int)"); + stat.execute("insert into test(a, b, c) values (1, 2, 3)"); + stat.execute("create view v1 as select a as b, b as a from test"); + // child of v1 + stat.execute("create view v2 as select * from v1"); + stat.execute("create user if not exists test_user password 'x'"); + stat.execute("grant select on v2 to test_user"); + // sibling of v1 + stat.execute("create view v3 as select a from test"); + } + + private void checkViewRemainsValid() throws SQLException { + ResultSet rs = stat.executeQuery("select b from v1"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertFalse(rs.next()); + + rs = stat.executeQuery("select b from v2"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertFalse(rs.next()); + + rs = stat.executeQuery("select * from information_schema.rights"); + assertTrue(rs.next()); + assertEquals("TEST_USER", rs.getString("GRANTEE")); + assertEquals("V2", rs.getString("TABLE_NAME")); + rs = stat.executeQuery("select b from test"); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertFalse(rs.next()); + + stat.execute("drop table test cascade"); + + rs = conn.getMetaData().getTables(null, null, null, null); + while (rs.next()) { + // should have no tables left in the database + assertEquals(rs.getString(2) + "." + rs.getString(3), + "INFORMATION_SCHEMA", rs.getString(2)); + } + + } + + // original error: table "XX_COPY_xx_xx" not found + private void testAlterTableDropColumnInViewWithDoubleQuotes() throws SQLException{ + // simple + stat.execute("create table \"test\"(id identity, name varchar) " + + "as select 1, 'Hello' from dual"); + stat.execute("create view test_view as select * from \"test\""); + assertThrows(ErrorCode.COLUMN_IS_REFERENCED_1, stat). + execute("alter table \"test\" drop name"); + ResultSet rs = stat.executeQuery("select * from test_view"); + assertTrue(rs.next()); + stat.execute("drop view test_view"); + stat.execute("drop table \"test\""); + } +} diff --git a/h2/src/test/org/h2/test/db/TestViewDropView.java b/h2/src/test/org/h2/test/db/TestViewDropView.java new file mode 100644 index 0000000..6361704 --- /dev/null +++ b/h2/src/test/org/h2/test/db/TestViewDropView.java @@ -0,0 +1,172 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.db; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Test the impact of DROP VIEW statements on dependent views. + */ +public class TestViewDropView extends TestDb { + + private Connection conn; + private Statement stat; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb(getTestName()); + conn = getConnection(getTestName()); + stat = conn.createStatement(); + + testDropViewDefaultBehaviour(); + testDropViewRestrict(); + testDropViewCascade(); + testCreateForceView(); + testCreateOrReplaceView(); + testCreateOrReplaceViewWithNowInvalidDependentViews(); + testCreateOrReplaceForceViewWithNowInvalidDependentViews(); + + conn.close(); + deleteDb(getTestName()); + } + + private void testCreateForceView() throws SQLException { + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1, stat). + execute("create view test_view as select * from test"); + stat.execute("create force view test_view as select * from test"); + stat.execute("create table test(id int)"); + stat.execute("alter view test_view recompile"); + stat.execute("select * from test_view"); + stat.execute("drop table test_view, test cascade"); + stat.execute("create force view test_view as select * from test where 1=0"); + stat.execute("create table test(id int)"); + stat.execute("alter view test_view recompile"); + stat.execute("select * from test_view"); + stat.execute("drop table test_view, test cascade"); + } + + private void testDropViewDefaultBehaviour() throws SQLException { + createTestData(); + ResultSet rs = stat.executeQuery( + "SELECT SETTING_VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME = 'DROP_RESTRICT'"); + rs.next(); + boolean dropRestrict = rs.getBoolean(1); + if (dropRestrict) { + // should fail because have dependencies + assertThrows(ErrorCode.CANNOT_DROP_2, stat). + execute("drop view v1"); + } else { + stat.execute("drop view v1"); + checkViewRemainsValid(); + } + } + + private void testDropViewRestrict() throws SQLException { + createTestData(); + // should fail because have dependencies + assertThrows(ErrorCode.CANNOT_DROP_2, stat). + execute("drop view v1 restrict"); + checkViewRemainsValid(); + } + + private void testDropViewCascade() throws SQLException { + createTestData(); + stat.execute("drop view v1 cascade"); + // v1, v2, v3 should be deleted + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, stat). + execute("select * from v1"); + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, stat). + execute("select * from v2"); + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, stat). + execute("select * from v3"); + stat.execute("drop table test"); + } + + private void testCreateOrReplaceView() throws SQLException { + createTestData(); + + stat.execute("create or replace view v1 as select a as b, b as a, c from test"); + + checkViewRemainsValid(); + } + + private void testCreateOrReplaceViewWithNowInvalidDependentViews() + throws SQLException { + createTestData(); + // v2 and v3 need more than just "c", so we should get an error + // dependent views need more columns than just 'c' + assertThrows(ErrorCode.COLUMN_NOT_FOUND_1, stat). + execute("create or replace view v1 as select c from test"); + // make sure our old views come back ok + checkViewRemainsValid(); + } + + private void testCreateOrReplaceForceViewWithNowInvalidDependentViews() + throws SQLException { + createTestData(); + + // v2 and v3 need more than just "c", + // but we want to force the creation of v1 anyway + stat.execute("create or replace force view v1 as select c from test"); + // now v2 and v3 are broken, but they still exist + assertThrows(ErrorCode.COLUMN_NOT_FOUND_1, stat). + executeQuery("select b from v2"); + stat.execute("drop table test cascade"); + } + + private void createTestData() throws SQLException { + stat.execute("drop all objects"); + stat.execute("create table test(a int, b int, c int)"); + stat.execute("insert into test(a, b, c) values (1, 2, 3)"); + stat.execute("create view v1 as select a as b, b as a from test"); + // child of v1 + stat.execute("create view v2 as select * from v1"); + // child of v2 + stat.execute("create view v3 as select * from v2"); + } + + private void checkViewRemainsValid() throws SQLException { + ResultSet rs = stat.executeQuery("select b from v1"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertFalse(rs.next()); + + rs = stat.executeQuery("select b from v2"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertFalse(rs.next()); + + rs = stat.executeQuery("select b from test"); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertFalse(rs.next()); + + stat.execute("drop table test cascade"); + + ResultSet d = conn.getMetaData().getTables(null, null, null, null); + while (d.next()) { + // should have no tables left in the database + assertEquals(d.getString(2) + "." + d.getString(3), + "INFORMATION_SCHEMA", d.getString(2)); + } + } +} diff --git a/h2/src/test/org/h2/test/db/package.html b/h2/src/test/org/h2/test/db/package.html new file mode 100644 index 0000000..7b975d2 --- /dev/null +++ b/h2/src/test/org/h2/test/db/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +Database tests. Most tests are on the SQL level. + +

    \ No newline at end of file diff --git a/h2/src/test/org/h2/test/jdbc/TestBatchUpdates.java b/h2/src/test/org/h2/test/jdbc/TestBatchUpdates.java new file mode 100644 index 0000000..1a153b9 --- /dev/null +++ b/h2/src/test/org/h2/test/jdbc/TestBatchUpdates.java @@ -0,0 +1,545 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbc; + +import java.sql.BatchUpdateException; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Test for batch updates. + */ +public class TestBatchUpdates extends TestDb { + + private static final String COFFEE_UPDATE = + "UPDATE TEST SET PRICE=PRICE*20 WHERE TYPE_ID=?"; + private static final String COFFEE_SELECT = + "SELECT PRICE FROM TEST WHERE KEY_ID=?"; + // private static final String COFFEE_QUERY = + // "SELECT C_NAME,PRICE FROM TEST WHERE TYPE_ID=?"; + // private static final String COFFEE_DELETE = + // "DELETE FROM TEST WHERE KEY_ID=?"; + private static final String COFFEE_INSERT1 = + "INSERT INTO TEST VALUES(9,'COFFEE-9',9.0,5)"; + private static final String COFFEE_DELETE1 = + "DELETE FROM TEST WHERE KEY_ID=9"; + private static final String COFFEE_UPDATE1 = + "UPDATE TEST SET PRICE=PRICE*20 WHERE TYPE_ID=1"; + private static final String COFFEE_SELECT1 = + "SELECT PRICE FROM TEST WHERE KEY_ID>4"; + private static final String COFFEE_UPDATE_SET = + "UPDATE TEST SET KEY_ID=?, C_NAME=? WHERE C_NAME=?"; + private static final String COFFEE_SELECT_CONTINUED = + "SELECT COUNT(*) FROM TEST WHERE C_NAME='Continue-1'"; + + private static final int COFFEE_SIZE = 10; + private static final int COFFEE_TYPE = 11; + + private Connection conn; + private Statement stat; + private PreparedStatement prep; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + testRootCause(); + testExecuteCall(); + testException(); + testCoffee(); + deleteDb("batchUpdates"); + } + + private void testRootCause() throws SQLException { + deleteDb("batchUpdates"); + conn = getConnection("batchUpdates"); + stat = conn.createStatement(); + stat.addBatch("select * from test_x"); + stat.addBatch("select * from test_y"); + try { + stat.executeBatch(); + } catch (SQLException e) { + assertContains(e.toString(), "TEST_X"); + e = e.getNextException(); + assertNotNull(e); + assertContains(e.toString(), "TEST_X"); + e = e.getNextException(); + assertNotNull(e); + assertContains(e.toString(), "TEST_Y"); + e = e.getNextException(); + assertNull(e); + } + stat.execute("create table test(id int)"); + PreparedStatement prep = conn.prepareStatement("insert into test values(?)"); + prep.setString(1, "TEST_X"); + prep.addBatch(); + prep.setString(1, "TEST_Y"); + prep.addBatch(); + try { + prep.executeBatch(); + } catch (SQLException e) { + assertContains(e.toString(), "TEST_X"); + e = e.getNextException(); + assertNotNull(e); + assertContains(e.toString(), "TEST_X"); + e = e.getNextException(); + assertNotNull(e); + assertContains(e.toString(), "TEST_Y"); + e = e.getNextException(); + assertNull(e); + } + stat.execute("drop table test"); + conn.close(); + } + + private void testExecuteCall() throws SQLException { + deleteDb("batchUpdates"); + conn = getConnection("batchUpdates"); + stat = conn.createStatement(); + stat.execute("CREATE ALIAS updatePrices FOR '" + getClass().getName() + ".updatePrices'"); + CallableStatement call = conn.prepareCall("{call updatePrices(?, ?)}"); + call.setString(1, "Hello"); + call.setFloat(2, 1.4f); + call.addBatch(); + call.setString(1, "World"); + call.setFloat(2, 3.2f); + call.addBatch(); + int[] updateCounts = call.executeBatch(); + int total = 0; + for (int t : updateCounts) { + total += t; + } + assertEquals(4, total); + conn.close(); + } + + /** + * This method is called by the database. + * + * @param message the message (currently not used) + * @param f the float + * @return the float converted to an int + */ + public static int updatePrices(@SuppressWarnings("unused") String message, double f) { + return (int) f; + } + + private void testException() throws SQLException { + deleteDb("batchUpdates"); + conn = getConnection("batchUpdates"); + stat = conn.createStatement(); + stat.execute("create table test(id int primary key)"); + prep = conn.prepareStatement("insert into test values(?)"); + for (int i = 0; i < 700; i++) { + prep.setString(1, "x"); + prep.addBatch(); + } + assertThrows(BatchUpdateException.class, prep).executeBatch(); + conn.close(); + } + + private void testCoffee() throws SQLException { + deleteDb("batchUpdates"); + conn = getConnection("batchUpdates"); + stat = conn.createStatement(); + DatabaseMetaData meta = conn.getMetaData(); + assertTrue(meta.supportsBatchUpdates()); + stat.executeUpdate("CREATE TABLE TEST(KEY_ID INT PRIMARY KEY," + + "C_NAME VARCHAR(255),PRICE DECIMAL(20,2),TYPE_ID INT)"); + String newName = null; + float newPrice = 0; + int newType = 0; + prep = conn.prepareStatement("INSERT INTO TEST VALUES(?,?,?,?)"); + int newKey = 1; + for (int i = 1; i <= COFFEE_TYPE && newKey <= COFFEE_SIZE; i++) { + for (int j = 1; j <= i && newKey <= COFFEE_SIZE; j++) { + newName = "COFFEE-" + newKey; + newPrice = newKey + (float) .00; + newType = i; + prep.setInt(1, newKey); + prep.setString(2, newName); + prep.setFloat(3, newPrice); + prep.setInt(4, newType); + prep.execute(); + newKey = newKey + 1; + } + } + trace("Inserted the Rows "); + testAddBatch01(); + testAddBatch02(); + testClearBatch01(); + testClearBatch02(); + testExecuteBatch01(); + testExecuteBatch02(); + testExecuteBatch03(); + testExecuteBatch04(); + testExecuteBatch05(); + testExecuteBatch06(); + testExecuteBatch07(); + testContinueBatch01(); + + conn.close(); + } + + private void testAddBatch01() throws SQLException { + trace("testAddBatch01"); + int i = 0; + int[] retValue = { 0, 0, 0 }; + String s = COFFEE_UPDATE; + trace("Prepared Statement String:" + s); + prep = conn.prepareStatement(s); + assertThrows(ErrorCode.PARAMETER_NOT_SET_1, prep).addBatch(); + prep.setInt(1, 2); + prep.addBatch(); + prep.setInt(1, 3); + prep.addBatch(); + prep.setInt(1, 4); + prep.addBatch(); + int[] updateCount = prep.executeBatch(); + int updateCountLen = updateCount.length; + + // PreparedStatement p; + // p = conn.prepareStatement(COFFEE_UPDATE); + // p.setInt(1,2); + // System.out.println("upc="+p.executeUpdate()); + // p.setInt(1,3); + // System.out.println("upc="+p.executeUpdate()); + // p.setInt(1,4); + // System.out.println("upc="+p.executeUpdate()); + + trace("updateCount length:" + updateCountLen); + assertEquals(3, updateCountLen); + String query1 = "SELECT COUNT(*) FROM TEST WHERE TYPE_ID=2"; + String query2 = "SELECT COUNT(*) FROM TEST WHERE TYPE_ID=3"; + String query3 = "SELECT COUNT(*) FROM TEST WHERE TYPE_ID=4"; + ResultSet rs = stat.executeQuery(query1); + rs.next(); + retValue[i++] = rs.getInt(1); + rs = stat.executeQuery(query2); + rs.next(); + retValue[i++] = rs.getInt(1); + rs = stat.executeQuery(query3); + rs.next(); + retValue[i++] = rs.getInt(1); + for (int j = 0; j < updateCount.length; j++) { + trace("UpdateCount:" + updateCount[j]); + assertEquals(updateCount[j], retValue[j]); + } + } + + private void testAddBatch02() throws SQLException { + trace("testAddBatch02"); + int i = 0; + int[] retValue = { 0, 0, 0 }; + int updCountLength = 0; + String sUpdCoffee = COFFEE_UPDATE1; + String sDelCoffee = COFFEE_DELETE1; + String sInsCoffee = COFFEE_INSERT1; + stat.addBatch(sUpdCoffee); + stat.addBatch(sDelCoffee); + stat.addBatch(sInsCoffee); + int[] updateCount = stat.executeBatch(); + updCountLength = updateCount.length; + trace("updateCount Length:" + updCountLength); + assertEquals(3, updCountLength); + String query1 = "SELECT COUNT(*) FROM TEST WHERE TYPE_ID=1"; + ResultSet rs = stat.executeQuery(query1); + rs.next(); + retValue[i++] = rs.getInt(1); + // 1 as delete Statement will delete only one row + retValue[i++] = 1; + // 1 as insert Statement will insert only one row + retValue[i++] = 1; + trace("ReturnValue count : " + retValue.length); + for (int j = 0; j < updateCount.length; j++) { + trace("Update Count:" + updateCount[j]); + trace("Returned Value : " + retValue[j]); + assertEquals("j:" + j, retValue[j], updateCount[j]); + } + } + + private void testClearBatch01() throws SQLException { + trace("testClearBatch01"); + String sPrepStmt = COFFEE_UPDATE; + trace("Prepared Statement String:" + sPrepStmt); + prep = conn.prepareStatement(sPrepStmt); + prep.setInt(1, 2); + prep.addBatch(); + prep.setInt(1, 3); + prep.addBatch(); + prep.setInt(1, 4); + prep.addBatch(); + prep.clearBatch(); + assertEquals(0, prep.executeBatch().length); + } + + private void testClearBatch02() throws SQLException { + trace("testClearBatch02"); + String sUpdCoffee = COFFEE_UPDATE1; + String sInsCoffee = COFFEE_INSERT1; + String sDelCoffee = COFFEE_DELETE1; + stat.addBatch(sUpdCoffee); + stat.addBatch(sDelCoffee); + stat.addBatch(sInsCoffee); + stat.clearBatch(); + assertEquals(0, stat.executeBatch().length); + } + + private void testExecuteBatch01() throws SQLException { + trace("testExecuteBatch01"); + int i = 0; + int[] retValue = { 0, 0, 0 }; + int updCountLength = 0; + String sPrepStmt = COFFEE_UPDATE; + trace("Prepared Statement String:" + sPrepStmt); + // get the PreparedStatement object + prep = conn.prepareStatement(sPrepStmt); + prep.setInt(1, 1); + prep.addBatch(); + prep.setInt(1, 2); + prep.addBatch(); + prep.setInt(1, 3); + prep.addBatch(); + int[] updateCount = prep.executeBatch(); + updCountLength = updateCount.length; + trace("Successfully Updated"); + trace("updateCount Length:" + updCountLength); + if (updCountLength != 3) { + fail("executeBatch"); + } else { + trace("executeBatch executes the Batch of SQL statements"); + } + // 1 is the number that is set First for Type Id in Prepared Statement + String query1 = "SELECT COUNT(*) FROM TEST WHERE TYPE_ID=1"; + // 2 is the number that is set second for Type id in Prepared Statement + String query2 = "SELECT COUNT(*) FROM TEST WHERE TYPE_ID=2"; + // 3 is the number that is set Third for Type id in Prepared Statement + String query3 = "SELECT COUNT(*) FROM TEST WHERE TYPE_ID=3"; + ResultSet rs = stat.executeQuery(query1); + rs.next(); + retValue[i++] = rs.getInt(1); + rs = stat.executeQuery(query2); + rs.next(); + retValue[i++] = rs.getInt(1); + rs = stat.executeQuery(query3); + rs.next(); + retValue[i++] = rs.getInt(1); + trace("retValue length : " + retValue.length); + for (int j = 0; j < updateCount.length; j++) { + trace("UpdateCount Value:" + updateCount[j]); + trace("RetValue : " + retValue[j]); + if (updateCount[j] != retValue[j]) { + fail("j=" + j + " right:" + retValue[j]); + } + } + } + + private void testExecuteBatch02() throws SQLException { + trace("testExecuteBatch02"); + String sPrepStmt = COFFEE_UPDATE; + trace("Prepared Statement String:" + sPrepStmt); + prep = conn.prepareStatement(sPrepStmt); + prep.setInt(1, 1); + prep.setInt(1, 2); + prep.setInt(1, 3); + int[] updateCount = prep.executeBatch(); + int updCountLength = updateCount.length; + trace("UpdateCount Length : " + updCountLength); + if (updCountLength == 0) { + trace("executeBatch does not execute Empty Batch"); + } else { + fail("executeBatch"); + } + } + + private void testExecuteBatch03() throws SQLException { + trace("testExecuteBatch03"); + boolean batchExceptionFlag = false; + String sPrepStmt = COFFEE_SELECT; + trace("Prepared Statement String :" + sPrepStmt); + prep = conn.prepareStatement(sPrepStmt); + prep.setInt(1, 1); + prep.addBatch(); + try { + int[] updateCount = prep.executeBatch(); + trace("Update Count" + updateCount.length); + } catch (BatchUpdateException b) { + batchExceptionFlag = true; + } + if (batchExceptionFlag) { + trace("select not allowed; correct"); + } else { + fail("executeBatch select"); + } + } + + private void testExecuteBatch04() throws SQLException { + trace("testExecuteBatch04"); + int i = 0; + int[] retValue = { 0, 0, 0 }; + int updCountLength = 0; + String sUpdCoffee = COFFEE_UPDATE1; + String sInsCoffee = COFFEE_INSERT1; + String sDelCoffee = COFFEE_DELETE1; + stat.addBatch(sUpdCoffee); + stat.addBatch(sDelCoffee); + stat.addBatch(sInsCoffee); + int[] updateCount = stat.executeBatch(); + updCountLength = updateCount.length; + trace("Successfully Updated"); + trace("updateCount Length:" + updCountLength); + if (updCountLength != 3) { + fail("executeBatch"); + } else { + trace("executeBatch executes the Batch of SQL statements"); + } + String query1 = "SELECT COUNT(*) FROM TEST WHERE TYPE_ID=1"; + ResultSet rs = stat.executeQuery(query1); + rs.next(); + retValue[i++] = rs.getInt(1); + // 1 as Delete Statement will delete only one row + retValue[i++] = 1; + // 1 as Insert Statement will insert only one row + retValue[i++] = 1; + for (int j = 0; j < updateCount.length; j++) { + trace("Update Count : " + updateCount[j]); + if (updateCount[j] != retValue[j]) { + fail("j=" + j + " right:" + retValue[j]); + } + } + } + + private void testExecuteBatch05() throws SQLException { + trace("testExecuteBatch05"); + int updCountLength = 0; + int[] updateCount = stat.executeBatch(); + updCountLength = updateCount.length; + trace("updateCount Length:" + updCountLength); + if (updCountLength == 0) { + trace("executeBatch Method does not execute the Empty Batch "); + } else { + fail("executeBatch 0!=" + updCountLength); + } + } + + private void testExecuteBatch06() throws SQLException { + trace("testExecuteBatch06"); + boolean batchExceptionFlag = false; + // Insert a row which is already Present + String sInsCoffee = COFFEE_INSERT1; + String sDelCoffee = COFFEE_DELETE1; + stat.addBatch(sInsCoffee); + stat.addBatch(sInsCoffee); + stat.addBatch(sDelCoffee); + try { + stat.executeBatch(); + } catch (BatchUpdateException b) { + batchExceptionFlag = true; + for (int uc : b.getUpdateCounts()) { + trace("Update counts:" + uc); + } + } + if (batchExceptionFlag) { + trace("executeBatch insert duplicate; correct"); + } else { + fail("executeBatch"); + } + } + + private void testExecuteBatch07() throws SQLException { + trace("testExecuteBatch07"); + boolean batchExceptionFlag = false; + String selectCoffee = COFFEE_SELECT1; + trace("selectCoffee = " + selectCoffee); + Statement stmt = conn.createStatement(); + stmt.addBatch(selectCoffee); + try { + int[] updateCount = stmt.executeBatch(); + trace("updateCount Length : " + updateCount.length); + } catch (BatchUpdateException be) { + batchExceptionFlag = true; + } + if (batchExceptionFlag) { + trace("executeBatch select"); + } else { + fail("executeBatch"); + } + } + + private void testContinueBatch01() throws SQLException { + trace("testContinueBatch01"); + int[] batchUpdates = { 0, 0, 0 }; + int buCountLen = 0; + try { + String sPrepStmt = COFFEE_UPDATE_SET; + trace("Prepared Statement String:" + sPrepStmt); + prep = conn.prepareStatement(sPrepStmt); + // Now add a legal update to the batch + prep.setInt(1, 1); + prep.setString(2, "Continue-1"); + prep.setString(3, "COFFEE-1"); + prep.addBatch(); + // Now add an illegal update to the batch by + // forcing a unique constraint violation + // Try changing the key_id of row 3 to 1. + prep.setInt(1, 1); + prep.setString(2, "Invalid"); + prep.setString(3, "COFFEE-3"); + prep.addBatch(); + // Now add a second legal update to the batch + // which will be processed ONLY if the driver supports + // continued batch processing according to 6.2.2.3 + // of the J2EE platform spec. + prep.setInt(1, 2); + prep.setString(2, "Continue-2"); + prep.setString(3, "COFFEE-2"); + prep.addBatch(); + // The executeBatch() method will result in a + // BatchUpdateException + prep.executeBatch(); + } catch (BatchUpdateException b) { + trace("expected BatchUpdateException"); + batchUpdates = b.getUpdateCounts(); + buCountLen = batchUpdates.length; + } + if (buCountLen == 1) { + trace("no continued updates - OK"); + return; + } else if (buCountLen == 3) { + trace("Driver supports continued updates."); + // Check to see if the third row from the batch was added + String query = COFFEE_SELECT_CONTINUED; + trace("Query is: " + query); + ResultSet rs = stat.executeQuery(query); + rs.next(); + int count = rs.getInt(1); + rs.close(); + stat.close(); + trace("Count val is: " + count); + // make sure that we have the correct error code for + // the failed update. + if (batchUpdates[1] != -3 || count != 1) { + fail("insert failed"); + } + } + } +} diff --git a/h2/src/test/org/h2/test/jdbc/TestCallableStatement.java b/h2/src/test/org/h2/test/jdbc/TestCallableStatement.java new file mode 100644 index 0000000..c1e7585 --- /dev/null +++ b/h2/src/test/org/h2/test/jdbc/TestCallableStatement.java @@ -0,0 +1,538 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbc; + +import java.io.ByteArrayInputStream; +import java.io.Reader; +import java.io.StringReader; +import java.math.BigDecimal; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.sql.Array; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLXML; +import java.sql.Statement; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Collections; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.SimpleResultSet; +import org.h2.util.IOUtils; +import org.h2.util.JdbcUtils; +import org.h2.util.Utils; + +/** + * Tests for the CallableStatement class. + */ +public class TestCallableStatement extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb("callableStatement"); + Connection conn = getConnection("callableStatement"); + testOutParameter(conn); + testUnsupportedOperations(conn); + testGetters(conn); + testCallWithResultSet(conn); + testPreparedStatement(conn); + testCallWithResult(conn); + testPrepare(conn); + testClassLoader(conn); + testArrayArgument(conn); + testArrayReturnValue(conn); + conn.close(); + deleteDb("callableStatement"); + } + + private void testOutParameter(Connection conn) throws SQLException { + conn.createStatement().execute("CREATE SEQUENCE SEQ"); + for (int i = 1; i < 20; i++) { + CallableStatement cs = conn.prepareCall("{ ? = CALL NEXT VALUE FOR SEQ}"); + cs.registerOutParameter(1, Types.BIGINT); + cs.execute(); + long id = cs.getLong(1); + assertEquals(i, id); + cs.close(); + } + conn.createStatement().execute("DROP SEQUENCE SEQ"); + } + + private void testUnsupportedOperations(Connection conn) throws SQLException { + CallableStatement call; + call = conn.prepareCall("select 10 as a"); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, call). + getURL(1); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, call). + getObject(1, Collections.emptyMap()); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, call). + getRef(1); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, call). + getRowId(1); + + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, call). + getURL("a"); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, call). + getObject("a", Collections.emptyMap()); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, call). + getRef("a"); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, call). + getRowId("a"); + + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, call). + setURL(1, (URL) null); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, call). + setRef(1, (Ref) null); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, call). + setRowId(1, (RowId) null); + + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, call). + setURL("a", (URL) null); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, call). + setRowId("a", (RowId) null); + } + + private void testCallWithResultSet(Connection conn) throws SQLException { + CallableStatement call; + ResultSet rs; + call = conn.prepareCall("select 10 as a"); + call.execute(); + rs = call.getResultSet(); + rs.next(); + assertEquals(10, rs.getInt(1)); + } + + private void testPreparedStatement(Connection conn) throws SQLException { + // using a callable statement like a prepared statement + CallableStatement call; + call = conn.prepareCall("create table test(id int)"); + call.executeUpdate(); + call = conn.prepareCall("insert into test values(1), (2)"); + assertEquals(2, call.executeUpdate()); + call = conn.prepareCall("drop table test"); + call.executeUpdate(); + } + + private void testGetters(Connection conn) throws SQLException { + CallableStatement call; + call = conn.prepareCall("{?=call ?}"); + call.setLong(2, 1); + call.registerOutParameter(1, Types.BIGINT); + call.execute(); + assertEquals(1, call.getLong(1)); + assertEquals(1, call.getByte(1)); + assertEquals(1, ((Long) call.getObject(1)).longValue()); + assertEquals(1, call.getObject(1, Long.class).longValue()); + assertFalse(call.wasNull()); + + call.setFloat(2, 1.1f); + call.registerOutParameter(1, Types.REAL); + call.execute(); + assertEquals(1.1f, call.getFloat(1)); + + call.setDouble(2, Math.PI); + call.registerOutParameter(1, Types.DOUBLE); + call.execute(); + assertEquals(Math.PI, call.getDouble(1)); + + call.setBytes(2, new byte[11]); + call.registerOutParameter(1, Types.BINARY); + call.execute(); + assertEquals(11, call.getBytes(1).length); + assertEquals(11, call.getBlob(1).length()); + + call.setDate(2, java.sql.Date.valueOf("2000-01-01")); + call.registerOutParameter(1, Types.DATE); + call.execute(); + assertEquals("2000-01-01", call.getDate(1).toString()); + assertEquals("2000-01-01", call.getObject(1, LocalDate.class).toString()); + + call.setTime(2, java.sql.Time.valueOf("01:02:03")); + call.registerOutParameter(1, Types.TIME); + call.execute(); + assertEquals("01:02:03", call.getTime(1).toString()); + assertEquals("01:02:03", call.getObject(1, LocalTime.class).toString()); + + call.setTimestamp(2, java.sql.Timestamp.valueOf( + "2001-02-03 04:05:06.789")); + call.registerOutParameter(1, Types.TIMESTAMP); + call.execute(); + assertEquals("2001-02-03 04:05:06.789", call.getTimestamp(1).toString()); + assertEquals("2001-02-03T04:05:06.789", call.getObject(1, LocalDateTime.class).toString()); + + call.setBoolean(2, true); + call.registerOutParameter(1, Types.BIT); + call.execute(); + assertEquals(true, call.getBoolean(1)); + + call.setShort(2, (short) 123); + call.registerOutParameter(1, Types.SMALLINT); + call.execute(); + assertEquals(123, call.getShort(1)); + + call.setBigDecimal(2, BigDecimal.TEN); + call.registerOutParameter(1, Types.DECIMAL); + call.execute(); + assertEquals("10", call.getBigDecimal(1).toString()); + } + + private void testCallWithResult(Connection conn) throws SQLException { + CallableStatement call; + for (String s : new String[]{"{?= call abs(?)}", + " { ? = call abs(?)}", " {? = call abs(?)}"}) { + call = conn.prepareCall(s); + call.setInt(2, -3); + call.registerOutParameter(1, Types.INTEGER); + call.execute(); + assertEquals(3, call.getInt(1)); + call.executeUpdate(); + assertEquals(3, call.getInt(1)); + } + } + + private void testPrepare(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + CallableStatement call; + ResultSet rs; + stat.execute("CREATE TABLE TEST(ID INT, NAME VARCHAR)"); + call = conn.prepareCall("INSERT INTO TEST VALUES(?, ?)"); + call.setInt(1, 1); + call.setString(2, "Hello"); + call.execute(); + call = conn.prepareCall("SELECT * FROM TEST", + ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_READ_ONLY); + rs = call.executeQuery(); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + assertFalse(rs.next()); + call = conn.prepareCall("SELECT * FROM TEST", + ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_READ_ONLY, + ResultSet.HOLD_CURSORS_OVER_COMMIT); + rs = call.executeQuery(); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + assertFalse(rs.next()); + stat.execute("CREATE ALIAS testCall FOR '" + getClass().getName() + ".testCall'"); + call = conn.prepareCall("{SELECT * FROM testCall(?, ?, ?, ?)}"); + call.setInt("A", 50); + call.setString("B", "abc"); + long t = System.currentTimeMillis(); + call.setTimestamp("C", new Timestamp(t)); + call.setTimestamp("D", Timestamp.valueOf("2001-02-03 10:20:30.0")); + call.registerOutParameter(1, Types.INTEGER); + call.registerOutParameter("B", Types.VARCHAR); + call.executeUpdate(); + assertThrows(ErrorCode.INVALID_VALUE_2, call).getTimestamp("C"); + call.registerOutParameter(3, Types.TIMESTAMP); + call.registerOutParameter(4, Types.TIMESTAMP); + call.executeUpdate(); + + assertEquals(t + 1, call.getTimestamp(3).getTime()); + assertEquals(t + 1, call.getTimestamp("C").getTime()); + + assertEquals("2001-02-03 10:20:30.0", call.getTimestamp(4).toString()); + assertEquals("2001-02-03 10:20:30.0", call.getTimestamp("D").toString()); + assertEquals("2001-02-03T10:20:30", call.getObject(4, LocalDateTime.class).toString()); + assertEquals("2001-02-03T10:20:30", call.getObject("D", LocalDateTime.class).toString()); + assertEquals("10:20:30", call.getTime(4).toString()); + assertEquals("10:20:30", call.getTime("D").toString()); + assertEquals("10:20:30", call.getObject(4, LocalTime.class).toString()); + assertEquals("10:20:30", call.getObject("D", LocalTime.class).toString()); + assertEquals("2001-02-03", call.getDate(4).toString()); + assertEquals("2001-02-03", call.getDate("D").toString()); + assertEquals("2001-02-03", call.getObject(4, LocalDate.class).toString()); + assertEquals("2001-02-03", call.getObject("D", LocalDate.class).toString()); + + assertEquals(100, call.getInt(1)); + assertEquals(100, call.getInt("A")); + assertEquals(100, call.getLong(1)); + assertEquals(100, call.getLong("A")); + assertEquals("100", call.getBigDecimal(1).toString()); + assertEquals("100", call.getBigDecimal("A").toString()); + assertEquals(100, call.getFloat(1)); + assertEquals(100, call.getFloat("A")); + assertEquals(100, call.getDouble(1)); + assertEquals(100, call.getDouble("A")); + assertEquals(100, call.getByte(1)); + assertEquals(100, call.getByte("A")); + assertEquals(100, call.getShort(1)); + assertEquals(100, call.getShort("A")); + assertTrue(call.getBoolean(1)); + assertTrue(call.getBoolean("A")); + + assertEquals("ABC", call.getString(2)); + Reader r = call.getCharacterStream(2); + assertEquals("ABC", IOUtils.readStringAndClose(r, -1)); + r = call.getNCharacterStream(2); + assertEquals("ABC", IOUtils.readStringAndClose(r, -1)); + assertEquals("ABC", call.getString("B")); + assertEquals("ABC", call.getNString(2)); + assertEquals("ABC", call.getNString("B")); + assertEquals("ABC", call.getClob(2).getSubString(1, 3)); + assertEquals("ABC", call.getClob("B").getSubString(1, 3)); + assertEquals("ABC", call.getNClob(2).getSubString(1, 3)); + assertEquals("ABC", call.getNClob("B").getSubString(1, 3)); + assertEquals("ABC", call.getSQLXML(2).getString()); + assertEquals("ABC", call.getSQLXML("B").getString()); + + assertThrows(ErrorCode.INVALID_VALUE_2, call).getString(100); + assertThrows(ErrorCode.INVALID_VALUE_2, call).getString(0); + assertThrows(ErrorCode.INVALID_VALUE_2, call).getBoolean("X"); + + call.setCharacterStream("B", + new StringReader("xyz")); + call.executeUpdate(); + assertEquals("XYZ", call.getString("B")); + call.setCharacterStream("B", + new StringReader("xyz-"), 3); + call.executeUpdate(); + assertEquals("XYZ", call.getString("B")); + call.setCharacterStream("B", + new StringReader("xyz-"), 3L); + call.executeUpdate(); + assertEquals("XYZ", call.getString("B")); + call.setAsciiStream("B", + new ByteArrayInputStream("xyz".getBytes(StandardCharsets.UTF_8))); + call.executeUpdate(); + assertEquals("XYZ", call.getString("B")); + call.setAsciiStream("B", + new ByteArrayInputStream("xyz-".getBytes(StandardCharsets.UTF_8)), 3); + call.executeUpdate(); + assertEquals("XYZ", call.getString("B")); + call.setAsciiStream("B", + new ByteArrayInputStream("xyz-".getBytes(StandardCharsets.UTF_8)), 3L); + call.executeUpdate(); + assertEquals("XYZ", call.getString("B")); + + call.setClob("B", new StringReader("xyz")); + call.executeUpdate(); + assertEquals("XYZ", call.getString("B")); + call.setClob("B", new StringReader("xyz-"), 3); + call.executeUpdate(); + assertEquals("XYZ", call.getString("B")); + + call.setNClob("B", new StringReader("xyz")); + call.executeUpdate(); + assertEquals("XYZ", call.getString("B")); + call.setNClob("B", new StringReader("xyz-"), 3); + call.executeUpdate(); + assertEquals("XYZ", call.getString("B")); + + call.setString("B", "xyz"); + call.executeUpdate(); + assertEquals("XYZ", call.getString("B")); + call.setNString("B", "xyz"); + call.executeUpdate(); + assertEquals("XYZ", call.getString("B")); + SQLXML xml = conn.createSQLXML(); + xml.setString("xyz"); + call.setSQLXML("B", xml); + call.executeUpdate(); + assertEquals("XYZ", call.getString("B")); + + // test for exceptions after closing + call.close(); + assertThrows(ErrorCode.OBJECT_CLOSED, call). + executeUpdate(); + assertThrows(ErrorCode.OBJECT_CLOSED, call). + registerOutParameter(1, Types.INTEGER); + assertThrows(ErrorCode.OBJECT_CLOSED, call). + getString("X"); + } + + private void testClassLoader(Connection conn) throws SQLException { + Utils.ClassFactory myFactory = new TestClassFactory(); + JdbcUtils.addClassFactory(myFactory); + try { + Statement stat = conn.createStatement(); + stat.execute("CREATE ALIAS T_CLASSLOADER FOR 'TestClassFactory.testClassF'"); + ResultSet rs = stat.executeQuery("SELECT T_CLASSLOADER(true)"); + assertTrue(rs.next()); + assertEquals(false, rs.getBoolean(1)); + } finally { + JdbcUtils.removeClassFactory(myFactory); + } + } + + private void testArrayArgument(Connection connection) throws SQLException { + Array array = connection.createArrayOf("Int", new Object[] {0, 1, 2}); + try (Statement statement = connection.createStatement()) { + statement.execute("CREATE ALIAS getArrayLength FOR '" + getClass().getName() + ".getArrayLength'"); + + // test setArray + try (CallableStatement callableStatement = connection + .prepareCall("{call getArrayLength(?)}")) { + callableStatement.setArray(1, array); + assertTrue(callableStatement.execute()); + + try (ResultSet resultSet = callableStatement.getResultSet()) { + assertTrue(resultSet.next()); + assertEquals(3, resultSet.getInt(1)); + assertFalse(resultSet.next()); + } + } + + // test setObject + try (CallableStatement callableStatement = connection + .prepareCall("{call getArrayLength(?)}")) { + callableStatement.setObject(1, array); + assertTrue(callableStatement.execute()); + + try (ResultSet resultSet = callableStatement.getResultSet()) { + assertTrue(resultSet.next()); + assertEquals(3, resultSet.getInt(1)); + assertFalse(resultSet.next()); + } + } + } finally { + array.free(); + } + } + + private void testArrayReturnValue(Connection connection) throws SQLException { + Integer[][] arraysToTest = new Integer[][] { + {0, 1, 2}, + {0, 1, 2}, + {0, null, 2}, + }; + try (Statement statement = connection.createStatement()) { + statement.execute("CREATE ALIAS arrayIdentiy FOR '" + getClass().getName() + ".arrayIdentiy'"); + + for (Integer[] arrayToTest : arraysToTest) { + Array sqlInputArray = connection.createArrayOf("INTEGER", arrayToTest); + try { + try (CallableStatement callableStatement = connection + .prepareCall("{call arrayIdentiy(?)}")) { + callableStatement.setArray(1, sqlInputArray); + assertTrue(callableStatement.execute()); + + try (ResultSet resultSet = callableStatement.getResultSet()) { + assertTrue(resultSet.next()); + + // test getArray() + Array sqlReturnArray = resultSet.getArray(1); + try { + assertEquals( + (Object[]) sqlInputArray.getArray(), + (Object[]) sqlReturnArray.getArray()); + } finally { + sqlReturnArray.free(); + } + + // test getObject(Array.class) + sqlReturnArray = resultSet.getObject(1, Array.class); + try { + assertEquals( + (Object[]) sqlInputArray.getArray(), + (Object[]) sqlReturnArray.getArray()); + } finally { + sqlReturnArray.free(); + } + + assertFalse(resultSet.next()); + } + } + } finally { + sqlInputArray.free(); + } + + } + } + } + + /** + * Class factory unit test + * @param b boolean value + * @return !b + */ + public static Boolean testClassF(Boolean b) { + return !b; + } + + /** + * This method is called via reflection from the database. + * + * @param array the array + * @return the length of the array + */ + public static int getArrayLength(Integer[] array) { + return array == null ? 0 : array.length; + } + + /** + * This method is called via reflection from the database. + * + * @param array the array + * @return the array + */ + public static Integer[] arrayIdentiy(Integer[] array) { + return array; + } + + /** + * This method is called via reflection from the database. + * + * @param conn the connection + * @param a the value a + * @param b the value b + * @param c the value c + * @param d the value d + * @return a result set + */ + public static ResultSet testCall(Connection conn, int a, String b, + Timestamp c, Timestamp d) throws SQLException { + SimpleResultSet rs = new SimpleResultSet(); + rs.addColumn("A", Types.INTEGER, 0, 0); + rs.addColumn("B", Types.VARCHAR, 0, 0); + rs.addColumn("C", Types.TIMESTAMP, 0, 0); + rs.addColumn("D", Types.TIMESTAMP, 0, 0); + if ("jdbc:columnlist:connection".equals(conn.getMetaData().getURL())) { + return rs; + } + rs.addRow(a * 2, b.toUpperCase(), new Timestamp(c.getTime() + 1), d); + return rs; + } + + /** + * A class factory used for testing. + */ + static class TestClassFactory implements Utils.ClassFactory { + + @Override + public boolean match(String name) { + return name.equals("TestClassFactory"); + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + return TestCallableStatement.class; + } + } + +} diff --git a/h2/src/test/org/h2/test/jdbc/TestCancel.java b/h2/src/test/org/h2/test/jdbc/TestCancel.java new file mode 100644 index 0000000..271fd71 --- /dev/null +++ b/h2/src/test/org/h2/test/jdbc/TestCancel.java @@ -0,0 +1,212 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbc; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Savepoint; +import java.sql.Statement; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests Statement.cancel + */ +public class TestCancel extends TestDb { + + private static int lastVisited; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + /** + * This thread cancels a statement after some time. + */ + static class CancelThread extends Thread { + private final Statement cancel; + private final int wait; + private volatile boolean stop; + + CancelThread(Statement cancel, int wait) { + this.cancel = cancel; + this.wait = wait; + } + + /** + * Stop the test now. + */ + public void stopNow() { + this.stop = true; + } + + @Override + public void run() { + while (!stop) { + try { + Thread.sleep(wait); + cancel.cancel(); + Thread.yield(); + } catch (SQLException e) { + // ignore errors on closed statements + } catch (Exception e) { + TestBase.logError("sleep", e); + } + } + } + } + + @Override + public void test() throws Exception { + testQueryTimeoutInTransaction(); + testReset(); + testMaxQueryTimeout(); + testQueryTimeout(); + testJdbcQueryTimeout(); + testCancelStatement(); + deleteDb("cancel"); + } + + private void testReset() throws SQLException { + deleteDb("cancel"); + Connection conn = getConnection("cancel"); + Statement stat = conn.createStatement(); + stat.execute("set query_timeout 1"); + assertThrows(ErrorCode.STATEMENT_WAS_CANCELED, stat). + execute("select count(*) from system_range(1, 1000000), " + + "system_range(1, 1000000)"); + stat.execute("set query_timeout 0"); + stat.execute("select count(*) from system_range(1, 1000), " + + "system_range(1, 1000)"); + conn.close(); + } + + private void testQueryTimeoutInTransaction() throws SQLException { + deleteDb("cancel"); + Connection conn = getConnection("cancel"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT)"); + conn.setAutoCommit(false); + stat.execute("INSERT INTO TEST VALUES(1)"); + Savepoint sp = conn.setSavepoint(); + stat.execute("INSERT INTO TEST VALUES(2)"); + stat.setQueryTimeout(1); + conn.rollback(sp); + conn.commit(); + conn.close(); + } + + private void testJdbcQueryTimeout() throws SQLException { + deleteDb("cancel"); + Connection conn = getConnection("cancel"); + Statement stat = conn.createStatement(); + assertEquals(0, stat.getQueryTimeout()); + stat.setQueryTimeout(1); + assertEquals(1, stat.getQueryTimeout()); + Statement s2 = conn.createStatement(); + assertEquals(1, s2.getQueryTimeout()); + ResultSet rs = s2.executeQuery( + "SELECT SETTING_VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME = 'QUERY_TIMEOUT'"); + rs.next(); + assertEquals(1000, rs.getInt(1)); + assertThrows(ErrorCode.STATEMENT_WAS_CANCELED, stat). + executeQuery("SELECT MAX(RAND()) " + + "FROM SYSTEM_RANGE(1, 100000000)"); + stat.setQueryTimeout(0); + stat.execute("SET QUERY_TIMEOUT 1100"); + // explicit changes are not detected except, as documented + assertEquals(0, stat.getQueryTimeout()); + conn.close(); + } + + private void testQueryTimeout() throws SQLException { + deleteDb("cancel"); + Connection conn = getConnection("cancel"); + Statement stat = conn.createStatement(); + stat.execute("SET QUERY_TIMEOUT 10"); + assertThrows(ErrorCode.STATEMENT_WAS_CANCELED, stat). + executeQuery("SELECT MAX(RAND()) " + + "FROM SYSTEM_RANGE(1, 100000000)"); + conn.close(); + } + + private void testMaxQueryTimeout() throws SQLException { + deleteDb("cancel"); + Connection conn = getConnection("cancel;MAX_QUERY_TIMEOUT=10"); + Statement stat = conn.createStatement(); + assertThrows(ErrorCode.STATEMENT_WAS_CANCELED, stat). + executeQuery("SELECT MAX(RAND()) " + + "FROM SYSTEM_RANGE(1, 100000000)"); + conn.close(); + } + + /** + * This method is called via reflection from the database. + * + * @param x the value + * @return the value + */ + public static int visit(int x) { + lastVisited = x; + return x; + } + + private void testCancelStatement() throws Exception { + if (config.lazy && config.networked) { + return; + } + deleteDb("cancel"); + Connection conn = getConnection("cancel"); + Statement stat = conn.createStatement(); + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE ALIAS VISIT FOR '" + getClass().getName() + ".visit'"); + stat.execute("CREATE MEMORY TABLE TEST" + + "(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST VALUES(?, ?)"); + trace("insert"); + int len = getSize(10, 1000); + for (int i = 0; i < len; i++) { + prep.setInt(1, i); + // prep.setString(2, "Test Value "+i); + prep.setString(2, "hi"); + prep.execute(); + } + trace("inserted"); + // TODO test insert into ... select + for (int i = 1;;) { + Statement query = conn.createStatement(); + CancelThread cancel = new CancelThread(query, i); + visit(0); + cancel.start(); + try { + Thread.yield(); + assertThrows(ErrorCode.STATEMENT_WAS_CANCELED, query, + "SELECT VISIT(ID), (SELECT SUM(X) " + + "FROM SYSTEM_RANGE(1, 100000) WHERE X<>ID) FROM TEST ORDER BY ID"); + } finally { + cancel.stopNow(); + cancel.join(); + } + if (lastVisited == 0) { + i += 10; + } else { + break; + } + } + conn.close(); + } + +} diff --git a/h2/src/test/org/h2/test/jdbc/TestConcurrentConnectionUsage.java b/h2/src/test/org/h2/test/jdbc/TestConcurrentConnectionUsage.java new file mode 100644 index 0000000..e2f15bb --- /dev/null +++ b/h2/src/test/org/h2/test/jdbc/TestConcurrentConnectionUsage.java @@ -0,0 +1,59 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbc; + +import java.io.ByteArrayInputStream; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.Task; + +/** + * Test concurrent usage of the same connection. + */ +public class TestConcurrentConnectionUsage extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + testAutoCommit(); + } + + private void testAutoCommit() throws SQLException { + deleteDb(getTestName()); + final Connection conn = getConnection(getTestName()); + final PreparedStatement p1 = conn.prepareStatement("select 1 from dual"); + Task t = new Task() { + @Override + public void call() throws Exception { + while (!stop) { + p1.executeQuery(); + conn.setAutoCommit(true); + conn.setAutoCommit(false); + } + } + }.execute(); + PreparedStatement prep = conn.prepareStatement("select ? from dual"); + for (int i = 0; i < 10; i++) { + prep.setBinaryStream(1, new ByteArrayInputStream(new byte[1024])); + prep.executeQuery(); + } + t.get(); + conn.close(); + } + +} diff --git a/h2/src/test/org/h2/test/jdbc/TestConnection.java b/h2/src/test/org/h2/test/jdbc/TestConnection.java new file mode 100644 index 0000000..1420637 --- /dev/null +++ b/h2/src/test/org/h2/test/jdbc/TestConnection.java @@ -0,0 +1,402 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbc; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLClientInfoException; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Properties; +import java.util.TimeZone; + +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.engine.SysProperties; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.DateTimeUtils; + +/** + * Tests the client info + */ +public class TestConnection extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testSetSupportedClientInfo(); + testSetUnsupportedClientInfo(); + testGetUnsupportedClientInfo(); + testSetSupportedClientInfoProperties(); + testSetUnsupportedClientInfoProperties(); + testSetInternalProperty(); + testSetInternalPropertyToInitialValue(); + testTransactionIsolationSetAndGet(); + testSetGetSchema(); + testCommitOnAutoCommitSetRunner(); + testRollbackOnAutoCommitSetRunner(); + testChangeTransactionLevelCommitRunner(); + testLockTimeout(); + testIgnoreUnknownSettings(); + testTimeZone(); + } + + private void testSetInternalProperty() throws SQLException { + // Use MySQL-mode since this allows all property names + // (apart from h2 internal names). + Connection conn = getConnection("clientInfoMySQL;MODE=MySQL"); + + assertThrows(SQLClientInfoException.class, conn).setClientInfo("numServers", "SomeValue"); + assertThrows(SQLClientInfoException.class, conn).setClientInfo("server23", "SomeValue"); + conn.close(); + } + + /** + * Test that no exception is thrown if the client info of a connection + * managed in a connection pool is reset to its initial values. + * + * This is needed when using h2 in websphere liberty. + */ + private void testSetInternalPropertyToInitialValue() throws SQLException { + // Use MySQL-mode since this allows all property names + // (apart from h2 internal names). + Connection conn = getConnection("clientInfoMySQL;MODE=MySQL"); + String numServersPropertyName = "numServers"; + String numServers = conn.getClientInfo(numServersPropertyName); + conn.setClientInfo(numServersPropertyName, numServers); + assertEquals(conn.getClientInfo(numServersPropertyName), numServers); + conn.close(); + } + + private void testSetUnsupportedClientInfoProperties() throws SQLException { + Connection conn = getConnection("clientInfo"); + Properties properties = new Properties(); + properties.put("ClientUser", "someUser"); + assertThrows(SQLClientInfoException.class, conn).setClientInfo(properties); + conn.close(); + } + + private void testSetSupportedClientInfoProperties() throws SQLException { + Connection conn = getConnection("clientInfoDB2;MODE=DB2"); + conn.setClientInfo("ApplicationName", "Connection Test"); + + Properties properties = new Properties(); + properties.put("ClientUser", "someUser"); + conn.setClientInfo(properties); + // old property should have been removed + assertNull(conn.getClientInfo("ApplicationName")); + // new property has been set + assertEquals(conn.getClientInfo("ClientUser"), "someUser"); + conn.close(); + } + + private void testSetSupportedClientInfo() throws SQLException { + Connection conn = getConnection("clientInfoDB2;MODE=DB2"); + conn.setClientInfo("ApplicationName", "Connection Test"); + + assertEquals(conn.getClientInfo("ApplicationName"), "Connection Test"); + conn.close(); + } + + private void testSetUnsupportedClientInfo() throws SQLException { + Connection conn = getConnection("clientInfoDB2;MODE=DB2"); + assertThrows(SQLClientInfoException.class, conn).setClientInfo( + "UnsupportedName", "SomeValue"); + conn.close(); + } + + private void testGetUnsupportedClientInfo() throws SQLException { + Connection conn = getConnection("clientInfo"); + assertNull(conn.getClientInfo("UnknownProperty")); + conn.close(); + } + + private void testTransactionIsolationSetAndGet() throws Exception { + deleteDb("transactionIsolation"); + try (Connection conn = getConnection("transactionIsolation")) { + assertEquals(Connection.TRANSACTION_READ_COMMITTED, conn.getTransactionIsolation()); + conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); + assertEquals(Connection.TRANSACTION_READ_UNCOMMITTED, conn.getTransactionIsolation()); + conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ); + assertEquals(Connection.TRANSACTION_REPEATABLE_READ, + conn.getTransactionIsolation()); + conn.setTransactionIsolation(Constants.TRANSACTION_SNAPSHOT); + assertEquals(Constants.TRANSACTION_SNAPSHOT, + conn.getTransactionIsolation()); + conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); + assertEquals(Connection.TRANSACTION_SERIALIZABLE, conn.getTransactionIsolation()); + } finally { + deleteDb("transactionIsolation"); + } + } + + private void testCommitOnAutoCommitSetRunner() throws Exception { + assertFalse("Default value must be false", SysProperties.FORCE_AUTOCOMMIT_OFF_ON_COMMIT); + testCommitOnAutoCommitSet(false); + try { + SysProperties.FORCE_AUTOCOMMIT_OFF_ON_COMMIT = true; + testCommitOnAutoCommitSet(true); + } finally { + SysProperties.FORCE_AUTOCOMMIT_OFF_ON_COMMIT = false; + } + + } + + private void testCommitOnAutoCommitSet(boolean expectedPropertyEnabled) throws Exception { + assertEquals(SysProperties.FORCE_AUTOCOMMIT_OFF_ON_COMMIT, expectedPropertyEnabled); + Connection conn = getConnection("clientInfo"); + conn.setAutoCommit(false); + Statement stat = conn.createStatement(); + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST VALUES(?, ?)"); + int index = 1; + prep.setInt(index++, 1); + prep.setString(index++, "test1"); + prep.execute(); + conn.commit(); + // no error expected + + conn.setAutoCommit(true); + index = 1; + prep.setInt(index++, 2); + prep.setString(index++, "test2"); + if (expectedPropertyEnabled) { + prep.execute(); + try { + conn.commit(); + throw new AssertionError("SQLException expected"); + } catch (SQLException e) { + assertTrue(e.getMessage().contains("commit()")); + assertEquals(ErrorCode.METHOD_DISABLED_ON_AUTOCOMMIT_TRUE, e.getErrorCode()); + } + ResultSet rs = conn.createStatement().executeQuery("SELECT COUNT(*) FROM TEST"); + rs.next(); + assertTrue(rs.getInt(1) == 2); + rs.close(); + } else { + prep.execute(); + conn.commit(); + ResultSet rs = conn.createStatement().executeQuery("SELECT COUNT(*) FROM TEST"); + rs.next(); + assertTrue(rs.getInt(1) == 2); + rs.close(); + } + + conn.close(); + prep.close(); + } + + private void testChangeTransactionLevelCommitRunner() throws Exception { + assertFalse("Default value must be false", SysProperties.FORCE_AUTOCOMMIT_OFF_ON_COMMIT); + testChangeTransactionLevelCommit(false); + testChangeTransactionLevelCommit(true); + try { + SysProperties.FORCE_AUTOCOMMIT_OFF_ON_COMMIT = true; + testChangeTransactionLevelCommit(true); + testChangeTransactionLevelCommit(false); + } finally { + SysProperties.FORCE_AUTOCOMMIT_OFF_ON_COMMIT = false; + } + } + + private void testChangeTransactionLevelCommit(boolean setAutoCommit) throws Exception { + Connection conn = getConnection("clientInfo"); + conn.setAutoCommit(setAutoCommit); + Statement stat = conn.createStatement(); + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST VALUES(?, ?)"); + int index = 1; + prep.setInt(index++, 1); + prep.setString(index++, "test1"); + prep.execute(); + conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); + + conn.createStatement().executeQuery("SELECT COUNT(*) FROM TEST"); + // throws exception if TransactionIsolation did not commit + + conn.close(); + prep.close(); + } + + private void testRollbackOnAutoCommitSetRunner() throws Exception { + assertFalse("Default value must be false", SysProperties.FORCE_AUTOCOMMIT_OFF_ON_COMMIT); + testRollbackOnAutoCommitSet(false); + try { + SysProperties.FORCE_AUTOCOMMIT_OFF_ON_COMMIT = true; + testRollbackOnAutoCommitSet(true); + } finally { + SysProperties.FORCE_AUTOCOMMIT_OFF_ON_COMMIT = false; + } + } + + private void testRollbackOnAutoCommitSet(boolean expectedPropertyEnabled) throws Exception { + assertEquals(SysProperties.FORCE_AUTOCOMMIT_OFF_ON_COMMIT, expectedPropertyEnabled); + Connection conn = getConnection("clientInfo"); + conn.setAutoCommit(false); + Statement stat = conn.createStatement(); + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST VALUES(?, ?)"); + int index = 1; + prep.setInt(index++, 1); + prep.setString(index++, "test1"); + prep.execute(); + conn.rollback(); + // no error expected + + + conn.setAutoCommit(true); + index = 1; + prep.setInt(index++, 2); + prep.setString(index++, "test2"); + if (expectedPropertyEnabled) { + prep.execute(); + try { + conn.rollback(); + throw new AssertionError("SQLException expected"); + } catch (SQLException e) { + assertEquals(ErrorCode.METHOD_DISABLED_ON_AUTOCOMMIT_TRUE, e.getErrorCode()); + assertTrue(e.getMessage().contains("rollback()")); + } + ResultSet rs = conn.createStatement().executeQuery("SELECT COUNT(*) FROM TEST"); + rs.next(); + int count = rs.getInt(1); + assertTrue("Found " +count + " rows", count == 1); + rs.close(); + } else { + prep.execute(); + // rollback is permitted, however has no effects in autocommit=true + conn.rollback(); + ResultSet rs = conn.createStatement().executeQuery("SELECT COUNT(*) FROM TEST"); + rs.next(); + int count = rs.getInt(1); + assertTrue("Found " + count + " rows", count == 1); + rs.close(); + } + + conn.close(); + prep.close(); + } + + private void testSetGetSchema() throws SQLException { + deleteDb("schemaSetGet"); + Connection conn = getConnection("schemaSetGet"); + Statement s = conn.createStatement(); + s.executeUpdate("create schema my_test_schema"); + s.executeUpdate("create table my_test_schema.my_test_table(id int, nave varchar) as values (1, 'a')"); + assertEquals("PUBLIC", conn.getSchema()); + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1, s, "select * from my_test_table"); + assertThrows(ErrorCode.SCHEMA_NOT_FOUND_1, conn).setSchema("my_test_table"); + conn.setSchema("MY_TEST_SCHEMA"); + assertEquals("MY_TEST_SCHEMA", conn.getSchema()); + try (ResultSet rs = s.executeQuery("select * from my_test_table")) { + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals("a", rs.getString(2)); + assertFalse(rs.next()); + } + assertThrows(ErrorCode.SCHEMA_NOT_FOUND_1, conn).setSchema("NON_EXISTING_SCHEMA"); + assertEquals("MY_TEST_SCHEMA", conn.getSchema()); + s.executeUpdate("create schema \"otheR_schEma\""); + s.executeUpdate("create table \"otheR_schEma\".my_test_table(id int, nave varchar) as values (2, 'b')"); + conn.setSchema("otheR_schEma"); + assertEquals("otheR_schEma", conn.getSchema()); + try (ResultSet rs = s.executeQuery("select * from my_test_table")) { + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertEquals("b", rs.getString(2)); + assertFalse(rs.next()); + } + s.execute("SET SCHEMA \"MY_TEST_SCHEMA\""); + assertEquals("MY_TEST_SCHEMA", conn.getSchema()); + s.close(); + conn.close(); + deleteDb("schemaSetGet"); + } + + private void testLockTimeout() throws SQLException { + deleteDb("lockTimeout"); + try (Connection conn1 = getConnection("lockTimeout"); + Connection conn2 = getConnection("lockTimeout;LOCK_TIMEOUT=6000")) { + conn1.setAutoCommit(false); + conn2.setAutoCommit(false); + Statement s1 = conn1.createStatement(); + Statement s2 = conn2.createStatement(); + s1.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, V INT) AS VALUES (1, 2)"); + conn1.commit(); + s2.execute("INSERT INTO TEST VALUES (2, 4)"); + s1.execute("UPDATE TEST SET V = 3 WHERE ID = 1"); + s2.execute("SET LOCK_TIMEOUT 50"); + long n = System.nanoTime(); + assertThrows(ErrorCode.LOCK_TIMEOUT_1, s2).execute("UPDATE TEST SET V = 4 WHERE ID = 1"); + if (System.nanoTime() - n > 5_000_000_000L) { + fail("LOCK_TIMEOUT wasn't set"); + } + } finally { + deleteDb("lockTimeout"); + } + } + + private void testIgnoreUnknownSettings() throws SQLException { + deleteDb("ignoreUnknownSettings"); + assertThrows(ErrorCode.UNSUPPORTED_SETTING_1, () -> getConnection("ignoreUnknownSettings;A=1")); + try (Connection c = getConnection("ignoreUnknownSettings;IGNORE_UNKNOWN_SETTINGS=TRUE;A=1")) { + } finally { + deleteDb("ignoreUnknownSettings"); + } + } + + private void testTimeZone() throws SQLException { + deleteDb("timeZone"); + String tz1 = "Europe/London", tz2 = "Europe/Paris", tz3 = "Asia/Tokyo"; + try (Connection c = getConnection("timeZone")) { + TimeZone tz = TimeZone.getDefault(); + try { + TimeZone.setDefault(TimeZone.getTimeZone(tz1)); + DateTimeUtils.resetCalendar(); + try (Connection c1 = getConnection("timeZone")) { + TimeZone.setDefault(TimeZone.getTimeZone(tz2)); + DateTimeUtils.resetCalendar(); + try (Connection c2 = getConnection("timeZone"); + Connection c3 = getConnection("timeZone;TIME ZONE=" + tz3)) { + checkTimeZone(tz1, c1); + checkTimeZone(tz2, c2); + checkTimeZone(tz3, c3); + } + } + } finally { + TimeZone.setDefault(tz); + DateTimeUtils.resetCalendar(); + } + } finally { + deleteDb("timeZone"); + } + } + + private void checkTimeZone(String expected, Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery( + "SELECT SETTING_VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME = 'TIME ZONE'"); + rs.next(); + assertEquals(expected, rs.getString(1)); + } + +} diff --git a/h2/src/test/org/h2/test/jdbc/TestDatabaseEventListener.java b/h2/src/test/org/h2/test/jdbc/TestDatabaseEventListener.java new file mode 100644 index 0000000..072fe14 --- /dev/null +++ b/h2/src/test/org/h2/test/jdbc/TestDatabaseEventListener.java @@ -0,0 +1,273 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbc; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Properties; + +import org.h2.Driver; +import org.h2.api.DatabaseEventListener; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests the DatabaseEventListener interface. + */ +public class TestDatabaseEventListener extends TestDb { + + /** + * A flag to mark that the given method was called. + */ + static boolean calledOpened, calledClosingDatabase, calledScan, + calledCreateIndex, calledStatementStart, calledStatementEnd, + calledStatementProgress; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + testInit(); + testIndexRebuiltOnce(); + testIndexNotRebuilt(); + testCalled(); + testCloseLog0(false); + testCloseLog0(true); + testCalledForStatement(); + deleteDb("databaseEventListener"); + } + + /** + * Initialize the database after opening. + */ + public static class Init implements DatabaseEventListener { + + private String databaseUrl; + + @Override + public void init(String url) { + databaseUrl = url; + } + + @Override + public void opened() { + try { + // using DriverManager.getConnection could result in a deadlock + // when using the server mode, but within the same process + Properties prop = new Properties(); + prop.setProperty("user", "sa"); + prop.setProperty("password", "sa"); + Connection conn = Driver.load().connect(databaseUrl, prop); + Statement stat = conn.createStatement(); + stat.execute("create table if not exists test(id int)"); + conn.close(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + } + + private void testInit() throws SQLException { + if (config.cipher != null || config.memory) { + return; + } + deleteDb("databaseEventListener"); + String url = getURL("databaseEventListener", true); + url += ";DATABASE_EVENT_LISTENER='" + Init.class.getName() + "'"; + Connection conn = DriverManager.getConnection(url, "sa", "sa"); + Statement stat = conn.createStatement(); + stat.execute("select * from test"); + conn.close(); + } + + private void testIndexRebuiltOnce() throws SQLException { + if (config.memory) { + return; + } + deleteDb("databaseEventListener"); + String url = getURL("databaseEventListener", true); + String user = getUser(), password = getPassword(); + Properties p = new Properties(); + p.setProperty("user", user); + p.setProperty("password", password); + Statement stat; + try (Connection conn = DriverManager.getConnection(url, p)) { + stat = conn.createStatement(); + // the old.id index head is at position 0 + stat.execute("create table old(id identity) as select 1"); + // the test.id index head is at position 1 + stat.execute("create table test(id identity) as select 1"); + } + try (Connection conn = DriverManager.getConnection(url, p)) { + stat = conn.createStatement(); + // free up space at position 0 + stat.execute("drop table old"); + stat.execute("insert into test values(2)"); + stat.execute("checkpoint sync"); + stat.execute("shutdown immediately"); + } + // now the index should be re-built + try (Connection conn = DriverManager.getConnection(url, p)) {/**/} + calledCreateIndex = false; + p.put("DATABASE_EVENT_LISTENER", + MyDatabaseEventListener.class.getName()); + try (Connection conn = org.h2.Driver.load().connect(url, p)) {/**/} + assertFalse(calledCreateIndex); + } + + private void testIndexNotRebuilt() throws SQLException { + if (config.memory) { + return; + } + deleteDb("databaseEventListener"); + String url = getURL("databaseEventListener", true); + String user = getUser(), password = getPassword(); + Properties p = new Properties(); + p.setProperty("user", user); + p.setProperty("password", password); + Connection conn = DriverManager.getConnection(url, p); + Statement stat = conn.createStatement(); + // the old.id index head is at position 0 + stat.execute("create table old(id identity) as select 1"); + // the test.id index head is at position 1 + stat.execute("create table test(id identity) as select 1"); + conn.close(); + conn = DriverManager.getConnection(url, p); + stat = conn.createStatement(); + // free up space at position 0 + stat.execute("drop table old"); + // truncate, relocating to position 0 + stat.execute("truncate table test"); + stat.execute("insert into test select 1"); + conn.close(); + calledCreateIndex = false; + p.put("DATABASE_EVENT_LISTENER", + MyDatabaseEventListener.class.getName()); + conn = org.h2.Driver.load().connect(url, p); + conn.close(); + assertFalse(calledCreateIndex); + } + + private void testCloseLog0(boolean shutdown) throws SQLException { + if (config.memory) { + return; + } + deleteDb("databaseEventListener"); + String url = getURL("databaseEventListener", true); + String user = getUser(), password = getPassword(); + Properties p = new Properties(); + p.setProperty("user", user); + p.setProperty("password", password); + Connection conn = DriverManager.getConnection(url, p); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name varchar)"); + stat.execute("insert into test select x, space(1000) " + + "from system_range(1,1000)"); + if (shutdown) { + stat.execute("shutdown"); + } + conn.close(); + + calledOpened = false; + calledScan = false; + p.put("DATABASE_EVENT_LISTENER", MyDatabaseEventListener.class.getName()); + conn = org.h2.Driver.load().connect(url, p); + conn.close(); + if (calledOpened) { + assertFalse(calledScan); + } + } + + private void testCalled() throws SQLException { + Properties p = new Properties(); + p.setProperty("user", "sa"); + p.setProperty("password", "sa"); + calledOpened = false; + calledClosingDatabase = false; + p.put("DATABASE_EVENT_LISTENER", MyDatabaseEventListener.class.getName()); + org.h2.Driver.load(); + String url = "jdbc:h2:mem:databaseEventListener"; + Connection conn = org.h2.Driver.load().connect(url, p); + conn.close(); + assertTrue(calledOpened); + assertTrue(calledClosingDatabase); + } + + private void testCalledForStatement() throws SQLException { + Properties p = new Properties(); + p.setProperty("user", "sa"); + p.setProperty("password", "sa"); + calledStatementStart = false; + calledStatementEnd = false; + calledStatementProgress = false; + p.put("DATABASE_EVENT_LISTENER", MyDatabaseEventListener.class.getName()); + org.h2.Driver.load(); + String url = "jdbc:h2:mem:databaseEventListener"; + Connection conn = org.h2.Driver.load().connect(url, p); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name varchar)"); + stat.execute("select * from test"); + conn.close(); + assertTrue(calledStatementStart); + assertTrue(calledStatementEnd); + assertTrue(calledStatementProgress); + } + + /** + * The database event listener for this test. + */ + public static final class MyDatabaseEventListener implements DatabaseEventListener { + + @Override + public void closingDatabase() { + calledClosingDatabase = true; + } + + @Override + public void opened() { + calledOpened = true; + } + + @Override + public void setProgress(int state, String name, long x, long max) { + if (state == DatabaseEventListener.STATE_SCAN_FILE) { + calledScan = true; + } + if (state == DatabaseEventListener.STATE_CREATE_INDEX) { + if (!name.startsWith("SYS:")) { + calledCreateIndex = true; + } + } + if (state == STATE_STATEMENT_START) { + if (name.equals("select * from test")) { + calledStatementStart = true; + } + } + if (state == STATE_STATEMENT_END) { + if (name.equals("select * from test")) { + calledStatementEnd = true; + } + } + if (state == STATE_STATEMENT_PROGRESS) { + if (name.equals("select * from test")) { + calledStatementProgress = true; + } + } + } + + } + +} diff --git a/h2/src/test/org/h2/test/jdbc/TestDriver.java b/h2/src/test/org/h2/test/jdbc/TestDriver.java new file mode 100644 index 0000000..64a7eb0 --- /dev/null +++ b/h2/src/test/org/h2/test/jdbc/TestDriver.java @@ -0,0 +1,72 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbc; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Properties; + +import org.h2.Driver; +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests the database driver. + */ +public class TestDriver extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testSettingsAsProperties(); + testDriverObject(); + testURLs(); + } + + private void testSettingsAsProperties() throws Exception { + Properties prop = new Properties(); + prop.put("user", getUser()); + prop.put("password", getPassword()); + prop.put("max_compact_time", "1234"); + prop.put("unknown", "1234"); + String url = getURL("jdbc:h2:mem:driver", true); + Connection conn = DriverManager.getConnection(url, prop); + ResultSet rs; + rs = conn.createStatement().executeQuery( + "SELECT SETTING_VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME = 'MAX_COMPACT_TIME'"); + rs.next(); + assertEquals(1234, rs.getInt(1)); + conn.close(); + } + + private void testDriverObject() throws Exception { + Driver instance = Driver.load(); + assertTrue(DriverManager.getDriver("jdbc:h2:~/test") == instance); + Driver.unload(); + assertThrows(SQLException.class, () -> DriverManager.getDriver("jdbc:h2:~/test")); + Driver.load(); + assertTrue(DriverManager.getDriver("jdbc:h2:~/test") == instance); + } + + private void testURLs() throws Exception { + java.sql.Driver instance = Driver.load(); + assertThrows(ErrorCode.URL_FORMAT_ERROR_2, instance).acceptsURL(null); + assertThrows(ErrorCode.URL_FORMAT_ERROR_2, instance).connect(null, null); + assertNull(instance.connect("jdbc:unknown", null)); + } + +} diff --git a/h2/src/test/org/h2/test/jdbc/TestGetGeneratedKeys.java b/h2/src/test/org/h2/test/jdbc/TestGetGeneratedKeys.java new file mode 100644 index 0000000..ebc3565 --- /dev/null +++ b/h2/src/test/org/h2/test/jdbc/TestGetGeneratedKeys.java @@ -0,0 +1,1751 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbc; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.UUID; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests for the {@link Statement#getGeneratedKeys()}. + */ +public class TestGetGeneratedKeys extends TestDb { + + /** + * Run just this test. + * + * @param a + * ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb("getGeneratedKeys"); + Connection conn = getConnection("getGeneratedKeys"); + testBatchAndMergeInto(conn); + testPrimaryKey(conn); + testInsertWithSelect(conn); + testUpdate(conn); + testMergeUsing(conn); + testWrongStatement(conn); + testMultithreaded(conn); + testNameCase(conn); + testColumnNotFound(conn); + + testPrepareStatement_Execute(conn); + testPrepareStatement_ExecuteBatch(conn); + testPrepareStatement_ExecuteLargeBatch(conn); + testPrepareStatement_ExecuteLargeUpdate(conn); + testPrepareStatement_ExecuteUpdate(conn); + testPrepareStatement_int_Execute(conn); + testPrepareStatement_int_ExecuteBatch(conn); + testPrepareStatement_int_ExecuteLargeBatch(conn); + testPrepareStatement_int_ExecuteLargeUpdate(conn); + testPrepareStatement_int_ExecuteUpdate(conn); + testPrepareStatement_intArray_Execute(conn); + testPrepareStatement_intArray_ExecuteBatch(conn); + testPrepareStatement_intArray_ExecuteLargeBatch(conn); + testPrepareStatement_intArray_ExecuteLargeUpdate(conn); + testPrepareStatement_intArray_ExecuteUpdate(conn); + testPrepareStatement_StringArray_Execute(conn); + testPrepareStatement_StringArray_ExecuteBatch(conn); + testPrepareStatement_StringArray_ExecuteLargeBatch(conn); + testPrepareStatement_StringArray_ExecuteLargeUpdate(conn); + testPrepareStatement_StringArray_ExecuteUpdate(conn); + + testStatementExecute(conn); + testStatementExecute_int(conn); + testStatementExecute_intArray(conn); + testStatementExecute_StringArray(conn); + testStatementExecuteLargeUpdate(conn); + testStatementExecuteLargeUpdate_int(conn); + testStatementExecuteLargeUpdate_intArray(conn); + testStatementExecuteLargeUpdate_StringArray(conn); + testStatementExecuteUpdate(conn); + testStatementExecuteUpdate_int(conn); + testStatementExecuteUpdate_intArray(conn); + testStatementExecuteUpdate_StringArray(conn); + + conn.close(); + deleteDb("getGeneratedKeys"); + } + + /** + * Test for batch updates and MERGE INTO operator. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testBatchAndMergeInto(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID BIGINT AUTO_INCREMENT, UID UUID DEFAULT RANDOM_UUID(), V INT)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (?), (?)", + Statement.RETURN_GENERATED_KEYS); + prep.setInt(1, 1); + prep.setInt(2, 2); + prep.addBatch(); + prep.setInt(1, 3); + prep.setInt(1, 4); + prep.addBatch(); + prep.executeBatch(); + ResultSet rs = prep.getGeneratedKeys(); + ResultSetMetaData meta = rs.getMetaData(); + assertEquals("BIGINT", meta.getColumnTypeName(1)); + assertEquals("UUID", meta.getColumnTypeName(2)); + rs.next(); + assertEquals(1L, rs.getLong(1)); + UUID u1 = (UUID) rs.getObject(2); + assertNotNull(u1); + rs.next(); + assertEquals(2L, rs.getLong(1)); + UUID u2 = (UUID) rs.getObject(2); + assertNotNull(u2); + rs.next(); + assertEquals(3L, rs.getLong(1)); + UUID u3 = (UUID) rs.getObject(2); + assertNotNull(u3); + rs.next(); + assertEquals(4L, rs.getLong(1)); + UUID u4 = (UUID) rs.getObject(2); + assertNotNull(u4); + assertFalse(rs.next()); + assertFalse(u1.equals(u2)); + assertFalse(u2.equals(u3)); + assertFalse(u3.equals(u4)); + prep = conn.prepareStatement("MERGE INTO TEST(ID, V) KEY(ID) VALUES (?, ?)", + Statement.RETURN_GENERATED_KEYS); + prep.setInt(1, 2); + prep.setInt(2, 10); + prep.execute(); + rs = prep.getGeneratedKeys(); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertEquals(u2, rs.getObject(2)); + assertFalse(rs.next()); + prep.setInt(1, 5); + prep.executeUpdate(); + rs = prep.getGeneratedKeys(); + rs.next(); + assertEquals(Long.class, rs.getObject(1).getClass()); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertFalse(rs.next()); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test for PRIMARY KEY columns. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testPrimaryKey(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID BIGINT PRIMARY KEY, V INT)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(ID, V) VALUES (?, ?)", + Statement.RETURN_GENERATED_KEYS); + prep.setLong(1, 10); + prep.setInt(2, 100); + prep.executeUpdate(); + ResultSet rs = prep.getGeneratedKeys(); + rs.next(); + assertEquals(10L, rs.getLong(1)); + assertFalse(rs.next()); + assertEquals(1, rs.getMetaData().getColumnCount()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for INSERT ... SELECT operator. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testInsertWithSelect(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT, V INT NOT NULL)"); + + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) SELECT 10", + Statement.RETURN_GENERATED_KEYS); + prep.executeUpdate(); + ResultSet rs = prep.getGeneratedKeys(); + assertTrue(rs.next()); + assertEquals(1, rs.getLong(1)); + rs.close(); + + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for UPDATE operator. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testUpdate(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT, V INT NOT NULL)"); + stat.execute("INSERT INTO TEST(V) VALUES 10"); + PreparedStatement prep = conn.prepareStatement("UPDATE TEST SET V = ? WHERE V = ?", + Statement.RETURN_GENERATED_KEYS); + prep.setInt(1, 20); + prep.setInt(2, 10); + assertEquals(1, prep.executeUpdate()); + ResultSet rs = prep.getGeneratedKeys(); + assertTrue(rs.next()); + assertEquals(1, rs.getLong(1)); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for MERGE USING operator. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testMergeUsing(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE SOURCE (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + " UID INT NOT NULL UNIQUE, V INT NOT NULL)"); + stat.execute("CREATE TABLE DESTINATION (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + " UID INT NOT NULL UNIQUE, V INT NOT NULL)"); + PreparedStatement ps = conn.prepareStatement("INSERT INTO SOURCE(UID, V) VALUES (?, ?)"); + for (int i = 1; i <= 100; i++) { + ps.setInt(1, i); + ps.setInt(2, i * 10 + 5); + ps.executeUpdate(); + } + // Insert first half of a rows with different values + ps = conn.prepareStatement("INSERT INTO DESTINATION(UID, V) VALUES (?, ?)"); + for (int i = 1; i <= 50; i++) { + ps.setInt(1, i); + ps.setInt(2, i * 10); + ps.executeUpdate(); + } + // And merge second half into it, first half will be updated with a new values + ps = conn.prepareStatement( + "MERGE INTO DESTINATION USING SOURCE ON (DESTINATION.UID = SOURCE.UID)" + + " WHEN MATCHED THEN UPDATE SET V = SOURCE.V" + + " WHEN NOT MATCHED THEN INSERT (UID, V) VALUES (SOURCE.UID, SOURCE.V)", + Statement.RETURN_GENERATED_KEYS); + // All rows should be either updated or inserted + assertEquals(100, ps.executeUpdate()); + ResultSet rs = ps.getGeneratedKeys(); + for (int i = 1; i <= 100; i++) { + assertTrue(rs.next()); + assertEquals(i, rs.getLong(1)); + } + assertFalse(rs.next()); + rs.close(); + // Check merged data + rs = stat.executeQuery("SELECT ID, UID, V FROM DESTINATION ORDER BY ID"); + for (int i = 1; i <= 100; i++) { + assertTrue(rs.next()); + assertEquals(i, rs.getLong(1)); + assertEquals(i, rs.getInt(2)); + assertEquals(i * 10 + 5, rs.getInt(3)); + } + assertFalse(rs.next()); + stat.execute("DROP TABLE SOURCE"); + stat.execute("DROP TABLE DESTINATION"); + } + + /** + * Test method for incompatible statements. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testWrongStatement(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT, V INT)"); + stat.execute("INSERT INTO TEST(V) VALUES 10, 20, 30"); + stat.execute("DELETE FROM TEST WHERE V = 10", Statement.RETURN_GENERATED_KEYS); + ResultSet rs = stat.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + stat.execute("TRUNCATE TABLE TEST", Statement.RETURN_GENERATED_KEYS); + rs = stat.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for shared connection between several statements in different + * threads. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testMultithreaded(final Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT, V INT NOT NULL)"); + final int count = 4, iterations = 10_000; + Thread[] threads = new Thread[count]; + final long[] keys = new long[count * iterations]; + for (int i = 0; i < count; i++) { + final int num = i; + threads[num] = new Thread("getGeneratedKeys-" + num) { + @Override + public void run() { + try { + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (?)", + Statement.RETURN_GENERATED_KEYS); + for (int i = 0; i < iterations; i++) { + int value = iterations * num + i; + prep.setInt(1, value); + prep.execute(); + ResultSet rs = prep.getGeneratedKeys(); + rs.next(); + keys[value] = rs.getLong(1); + rs.close(); + } + } catch (SQLException ex) { + ex.printStackTrace(); + } + } + }; + } + for (int i = 0; i < count; i++) { + threads[i].start(); + } + for (int i = 0; i < count; i++) { + threads[i].join(); + } + ResultSet rs = stat.executeQuery("SELECT V, ID FROM TEST ORDER BY V"); + for (int i = 0; i < keys.length; i++) { + assertTrue(rs.next()); + assertEquals(i, rs.getInt(1)); + assertEquals(keys[i], rs.getLong(2)); + } + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for case of letters in column names. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testNameCase(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "\"id\" UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + // Test columns with only difference in case + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (10)", + new String[] { "id", "ID" }); + prep.executeUpdate(); + ResultSet rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("id", rs.getMetaData().getColumnName(1)); + assertEquals("ID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertEquals(1L, rs.getLong(2)); + assertFalse(rs.next()); + rs.close(); + // Test lower case name of upper case column + stat.execute("ALTER TABLE TEST DROP COLUMN \"id\""); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (20)", new String[] { "id" }); + testNameCase1(prep, 2L, true); + // Test upper case name of lower case column + stat.execute("ALTER TABLE TEST ALTER COLUMN ID RENAME TO \"id\""); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (30)", new String[] { "ID" }); + testNameCase1(prep, 3L, false); + stat.execute("DROP TABLE TEST"); + } + + private void testNameCase1(PreparedStatement prep, long id, boolean upper) throws SQLException { + prep.executeUpdate(); + ResultSet rs = prep.getGeneratedKeys(); + assertEquals(1, rs.getMetaData().getColumnCount()); + assertEquals(upper ? "ID" : "id", rs.getMetaData().getColumnName(1)); + assertTrue(rs.next()); + assertEquals(id, rs.getLong(1)); + assertFalse(rs.next()); + rs.close(); + } + + /** + * Test method for column not found exception. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testColumnNotFound(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT, V INT NOT NULL)"); + assertThrows(ErrorCode.COLUMN_NOT_FOUND_1, stat).execute("INSERT INTO TEST(V) VALUES (1)", // + new int[] { 0 }); + assertThrows(ErrorCode.COLUMN_NOT_FOUND_1, stat).execute("INSERT INTO TEST(V) VALUES (1)", // + new int[] { 3 }); + assertThrows(ErrorCode.COLUMN_NOT_FOUND_1, stat).execute("INSERT INTO TEST(V) VALUES (1)", // + new String[] { "X" }); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for + * {@link Connection#prepareStatement(String)} + * .{@link PreparedStatement#execute()}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testPrepareStatement_Execute(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (10)"); + prep.execute(); + ResultSet rs = prep.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for + * {@link Connection#prepareStatement(String)} + * .{@link PreparedStatement#executeBatch()}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testPrepareStatement_ExecuteBatch(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (10)"); + prep.addBatch(); + prep.addBatch(); + prep.executeBatch(); + ResultSet rs = prep.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for + * {@link Connection#prepareStatement(String)} + * .{@link PreparedStatement#executeLargeBatch()}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testPrepareStatement_ExecuteLargeBatch(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (10)"); + prep.addBatch(); + prep.addBatch(); + prep.executeLargeBatch(); + ResultSet rs = prep.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for + * {@link Connection#prepareStatement(String)} + * .{@link PreparedStatement#executeLargeUpdate()}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testPrepareStatement_ExecuteLargeUpdate(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (10)"); + prep.executeLargeUpdate(); + ResultSet rs = prep.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for + * {@link Connection#prepareStatement(String)} + * .{@link PreparedStatement#executeUpdate()}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testPrepareStatement_ExecuteUpdate(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (10)"); + prep.executeUpdate(); + ResultSet rs = prep.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for + * {@link Connection#prepareStatement(String, int)} + * .{@link PreparedStatement#execute()}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testPrepareStatement_int_Execute(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL, OTHER INT DEFAULT 0)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (10)", + Statement.NO_GENERATED_KEYS); + prep.execute(); + ResultSet rs = prep.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (20)", Statement.RETURN_GENERATED_KEYS); + prep.execute(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(2L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for + * {@link Connection#prepareStatement(String, int)} + * .{@link PreparedStatement#executeBatch()}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testPrepareStatement_int_ExecuteBatch(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL, OTHER INT DEFAULT 0)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (10)", + Statement.NO_GENERATED_KEYS); + prep.addBatch(); + prep.addBatch(); + prep.executeBatch(); + ResultSet rs = prep.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (20)", Statement.RETURN_GENERATED_KEYS); + prep.addBatch(); + prep.addBatch(); + prep.executeBatch(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(3L, rs.getLong(1)); + assertEquals(3L, rs.getLong("ID")); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertEquals(UUID.class, rs.getObject("UID").getClass()); + assertEquals(UUID.class, rs.getObject("UID", UUID.class).getClass()); + assertTrue(rs.next()); + assertEquals(4L, rs.getLong(1)); + assertEquals(4L, rs.getLong("ID")); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertEquals(UUID.class, rs.getObject("UID").getClass()); + assertEquals(UUID.class, rs.getObject("UID", UUID.class).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for {@link Connection#prepareStatement(String, int)} + * .{@link PreparedStatement#executeLargeBatch()}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testPrepareStatement_int_ExecuteLargeBatch(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL, OTHER INT DEFAULT 0)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (10)", + Statement.NO_GENERATED_KEYS); + prep.addBatch(); + prep.addBatch(); + prep.executeLargeBatch(); + ResultSet rs = prep.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (20)", Statement.RETURN_GENERATED_KEYS); + prep.addBatch(); + prep.addBatch(); + prep.executeLargeBatch(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(3L, rs.getLong(1)); + assertEquals(3L, rs.getLong("ID")); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertEquals(UUID.class, rs.getObject("UID").getClass()); + assertEquals(UUID.class, rs.getObject("UID", UUID.class).getClass()); + assertTrue(rs.next()); + assertEquals(4L, rs.getLong(1)); + assertEquals(4L, rs.getLong("ID")); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertEquals(UUID.class, rs.getObject("UID").getClass()); + assertEquals(UUID.class, rs.getObject("UID", UUID.class).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for + * {@link Connection#prepareStatement(String, int)} + * .{@link PreparedStatement#executeLargeUpdate()}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testPrepareStatement_int_ExecuteLargeUpdate(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL, OTHER INT DEFAULT 0)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (10)", + Statement.NO_GENERATED_KEYS); + prep.executeLargeUpdate(); + ResultSet rs = prep.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (20)", Statement.RETURN_GENERATED_KEYS); + prep.executeLargeUpdate(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(2L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for + * {@link Connection#prepareStatement(String, int)} + * .{@link PreparedStatement#executeUpdate()}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testPrepareStatement_int_ExecuteUpdate(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL, OTHER INT DEFAULT 0)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (10)", + Statement.NO_GENERATED_KEYS); + prep.executeUpdate(); + ResultSet rs = prep.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (20)", Statement.RETURN_GENERATED_KEYS); + prep.executeUpdate(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(2L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for + * {@link Connection#prepareStatement(String, int[])} + * .{@link PreparedStatement#execute()}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testPrepareStatement_intArray_Execute(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (10)", new int[0]); + prep.execute(); + ResultSet rs = prep.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (20)", new int[] { 1, 2 }); + prep.execute(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(2L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (30)", new int[] { 2, 1 }); + prep.execute(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertEquals("ID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertEquals(3L, rs.getLong(2)); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (40)", new int[] { 2 }); + prep.execute(); + rs = prep.getGeneratedKeys(); + assertEquals(1, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for + * {@link Connection#prepareStatement(String, int[])} + * .{@link PreparedStatement#executeBatch()}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testPrepareStatement_intArray_ExecuteBatch(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (10)", new int[0]); + prep.addBatch(); + prep.addBatch(); + prep.executeBatch(); + ResultSet rs = prep.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (20)", new int[] { 1, 2 }); + prep.addBatch(); + prep.addBatch(); + prep.executeBatch(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(3L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertTrue(rs.next()); + assertEquals(4L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (30)", new int[] { 2, 1 }); + prep.addBatch(); + prep.addBatch(); + prep.executeBatch(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertEquals("ID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertEquals(5L, rs.getLong(2)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertEquals(6L, rs.getLong(2)); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (40)", new int[] { 2 }); + prep.addBatch(); + prep.addBatch(); + prep.executeBatch(); + rs = prep.getGeneratedKeys(); + assertEquals(1, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for + * {@link Connection#prepareStatement(String, int[])} + * .{@link PreparedStatement#executeLargeBatch()}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testPrepareStatement_intArray_ExecuteLargeBatch(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (10)", new int[0]); + prep.addBatch(); + prep.addBatch(); + prep.executeLargeBatch(); + ResultSet rs = prep.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (20)", new int[] { 1, 2 }); + prep.addBatch(); + prep.addBatch(); + prep.executeLargeBatch(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(3L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertTrue(rs.next()); + assertEquals(4L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (30)", new int[] { 2, 1 }); + prep.addBatch(); + prep.addBatch(); + prep.executeLargeBatch(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertEquals("ID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertEquals(5L, rs.getLong(2)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertEquals(6L, rs.getLong(2)); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (40)", new int[] { 2 }); + prep.addBatch(); + prep.addBatch(); + prep.executeLargeBatch(); + rs = prep.getGeneratedKeys(); + assertEquals(1, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for + * {@link Connection#prepareStatement(String, int[])} + * .{@link PreparedStatement#executeLargeUpdate()}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testPrepareStatement_intArray_ExecuteLargeUpdate(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (10)", new int[0]); + prep.executeLargeUpdate(); + ResultSet rs = prep.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (20)", new int[] { 1, 2 }); + prep.executeLargeUpdate(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(2L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (30)", new int[] { 2, 1 }); + prep.executeLargeUpdate(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertEquals("ID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertEquals(3L, rs.getLong(2)); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (40)", new int[] { 2 }); + prep.executeLargeUpdate(); + rs = prep.getGeneratedKeys(); + assertEquals(1, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for + * {@link Connection#prepareStatement(String, int[])} + * .{@link PreparedStatement#executeUpdate()}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testPrepareStatement_intArray_ExecuteUpdate(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (10)", new int[0]); + prep.executeUpdate(); + ResultSet rs = prep.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (20)", new int[] { 1, 2 }); + prep.executeUpdate(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(2L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (30)", new int[] { 2, 1 }); + prep.executeUpdate(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertEquals("ID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertEquals(3L, rs.getLong(2)); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (40)", new int[] { 2 }); + prep.executeUpdate(); + rs = prep.getGeneratedKeys(); + assertEquals(1, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for + * {@link Connection#prepareStatement(String, String[])} + * .{@link PreparedStatement#execute()}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testPrepareStatement_StringArray_Execute(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (10)", new String[0]); + prep.executeUpdate(); + ResultSet rs = prep.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (20)", new String[] { "ID", "UID" }); + prep.execute(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(2L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (30)", new String[] { "UID", "ID" }); + prep.execute(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertEquals("ID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertEquals(3L, rs.getLong(2)); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (40)", new String[] { "UID" }); + prep.execute(); + rs = prep.getGeneratedKeys(); + assertEquals(1, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for + * {@link Connection#prepareStatement(String, String[])} + * .{@link PreparedStatement#executeBatch()}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testPrepareStatement_StringArray_ExecuteBatch(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (10)", new String[0]); + prep.addBatch(); + prep.addBatch(); + prep.executeBatch(); + ResultSet rs = prep.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (20)", new String[] { "ID", "UID" }); + prep.addBatch(); + prep.addBatch(); + prep.executeBatch(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(3L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertTrue(rs.next()); + assertEquals(4L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (30)", new String[] { "UID", "ID" }); + prep.addBatch(); + prep.addBatch(); + prep.executeBatch(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertEquals("ID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertEquals(5L, rs.getLong(2)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertEquals(6L, rs.getLong(2)); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (40)", new String[] { "UID" }); + prep.addBatch(); + prep.addBatch(); + prep.executeBatch(); + rs = prep.getGeneratedKeys(); + assertEquals(1, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for + * {@link Connection#prepareStatement(String, String[])} + * .{@link PreparedStatement#executeLargeBatch()}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testPrepareStatement_StringArray_ExecuteLargeBatch(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (10)", new String[0]); + prep.addBatch(); + prep.addBatch(); + prep.executeLargeBatch(); + ResultSet rs = prep.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (20)", new String[] { "ID", "UID" }); + prep.addBatch(); + prep.addBatch(); + prep.executeLargeBatch(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(3L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertTrue(rs.next()); + assertEquals(4L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (30)", new String[] { "UID", "ID" }); + prep.addBatch(); + prep.addBatch(); + prep.executeLargeBatch(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertEquals("ID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertEquals(5L, rs.getLong(2)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertEquals(6L, rs.getLong(2)); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (40)", new String[] { "UID" }); + prep.addBatch(); + prep.addBatch(); + prep.executeLargeBatch(); + rs = prep.getGeneratedKeys(); + assertEquals(1, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for + * {@link Connection#prepareStatement(String, String[])} + * .{@link PreparedStatement#executeLargeUpdate()}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testPrepareStatement_StringArray_ExecuteLargeUpdate(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (10)", new String[0]); + prep.executeLargeUpdate(); + ResultSet rs = prep.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (20)", new String[] { "ID", "UID" }); + prep.executeLargeUpdate(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(2L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (30)", new String[] { "UID", "ID" }); + prep.executeLargeUpdate(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertEquals("ID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertEquals(3L, rs.getLong(2)); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (40)", new String[] { "UID" }); + prep.executeLargeUpdate(); + rs = prep.getGeneratedKeys(); + assertEquals(1, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for {@link Connection#prepareStatement(String, String[])} + * .{@link PreparedStatement#executeUpdate()}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testPrepareStatement_StringArray_ExecuteUpdate(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (10)", new String[0]); + prep.executeUpdate(); + ResultSet rs = prep.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (20)", new String[] { "ID", "UID" }); + prep.executeUpdate(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(2L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (30)", new String[] { "UID", "ID" }); + prep.executeUpdate(); + rs = prep.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertEquals("ID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertEquals(3L, rs.getLong(2)); + assertFalse(rs.next()); + rs.close(); + prep = conn.prepareStatement("INSERT INTO TEST(V) VALUES (40)", new String[] { "UID" }); + prep.executeUpdate(); + rs = prep.getGeneratedKeys(); + assertEquals(1, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for {@link Statement#execute(String)}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testStatementExecute(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + stat.execute("INSERT INTO TEST(V) VALUES (10)"); + ResultSet rs = stat.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for {@link Statement#execute(String, int)}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testStatementExecute_int(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL, OTHER INT DEFAULT 0)"); + stat.execute("INSERT INTO TEST(V) VALUES (10)", Statement.NO_GENERATED_KEYS); + ResultSet rs = stat.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + stat.execute("INSERT INTO TEST(V) VALUES (20)", Statement.RETURN_GENERATED_KEYS); + rs = stat.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(2L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for {@link Statement#execute(String, int[])}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testStatementExecute_intArray(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + stat.execute("INSERT INTO TEST(V) VALUES (10)", new int[0]); + ResultSet rs = stat.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + stat.execute("INSERT INTO TEST(V) VALUES (20)", new int[] { 1, 2 }); + rs = stat.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(2L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("INSERT INTO TEST(V) VALUES (30)", new int[] { 2, 1 }); + rs = stat.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertEquals("ID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertEquals(3L, rs.getLong(2)); + assertFalse(rs.next()); + rs.close(); + stat.execute("INSERT INTO TEST(V) VALUES (40)", new int[] { 2 }); + rs = stat.getGeneratedKeys(); + assertEquals(1, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for {@link Statement#executeUpdate(String, String[])}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testStatementExecute_StringArray(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + stat.execute("INSERT INTO TEST(V) VALUES (10)", new String[0]); + ResultSet rs = stat.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + stat.execute("INSERT INTO TEST(V) VALUES (20)", new String[] { "ID", "UID" }); + rs = stat.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(2L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("INSERT INTO TEST(V) VALUES (30)", new String[] { "UID", "ID" }); + rs = stat.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertEquals("ID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertEquals(3L, rs.getLong(2)); + assertFalse(rs.next()); + rs.close(); + stat.execute("INSERT INTO TEST(V) VALUES (40)", new String[] { "UID" }); + rs = stat.getGeneratedKeys(); + assertEquals(1, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for {@link Statement#executeLargeUpdate(String)}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testStatementExecuteLargeUpdate(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + stat.executeLargeUpdate("INSERT INTO TEST(V) VALUES (10)"); + ResultSet rs = stat.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for {@link Statement#executeLargeUpdate(String, int)}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testStatementExecuteLargeUpdate_int(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL, OTHER INT DEFAULT 0)"); + stat.executeLargeUpdate("INSERT INTO TEST(V) VALUES (10)", Statement.NO_GENERATED_KEYS); + ResultSet rs = stat.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + stat.executeLargeUpdate("INSERT INTO TEST(V) VALUES (20)", Statement.RETURN_GENERATED_KEYS); + rs = stat.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(2L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for {@link Statement#executeLargeUpdate(String, int[])}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testStatementExecuteLargeUpdate_intArray(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + stat.executeLargeUpdate("INSERT INTO TEST(V) VALUES (10)", new int[0]); + ResultSet rs = stat.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + stat.executeLargeUpdate("INSERT INTO TEST(V) VALUES (20)", new int[] { 1, 2 }); + rs = stat.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(2L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.executeLargeUpdate("INSERT INTO TEST(V) VALUES (30)", new int[] { 2, 1 }); + rs = stat.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertEquals("ID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertEquals(3L, rs.getLong(2)); + assertFalse(rs.next()); + rs.close(); + stat.executeLargeUpdate("INSERT INTO TEST(V) VALUES (40)", new int[] { 2 }); + rs = stat.getGeneratedKeys(); + assertEquals(1, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for {@link Statement#executeLargeUpdate(String, String[])}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testStatementExecuteLargeUpdate_StringArray(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + stat.executeLargeUpdate("INSERT INTO TEST(V) VALUES (10)", new String[0]); + ResultSet rs = stat.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + stat.executeLargeUpdate("INSERT INTO TEST(V) VALUES (20)", new String[] { "ID", "UID" }); + rs = stat.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(2L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.executeLargeUpdate("INSERT INTO TEST(V) VALUES (30)", new String[] { "UID", "ID" }); + rs = stat.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertEquals("ID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertEquals(3L, rs.getLong(2)); + assertFalse(rs.next()); + rs.close(); + stat.executeLargeUpdate("INSERT INTO TEST(V) VALUES (40)", new String[] { "UID" }); + rs = stat.getGeneratedKeys(); + assertEquals(1, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for {@link Statement#executeUpdate(String)}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testStatementExecuteUpdate(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + stat.executeUpdate("INSERT INTO TEST(V) VALUES (10)"); + ResultSet rs = stat.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for {@link Statement#executeUpdate(String, int)}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testStatementExecuteUpdate_int(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL, OTHER INT DEFAULT 0)"); + stat.executeUpdate("INSERT INTO TEST(V) VALUES (10)", Statement.NO_GENERATED_KEYS); + ResultSet rs = stat.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + stat.executeUpdate("INSERT INTO TEST(V) VALUES (20)", Statement.RETURN_GENERATED_KEYS); + rs = stat.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(2L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for {@link Statement#executeUpdate(String, int[])}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testStatementExecuteUpdate_intArray(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + stat.executeUpdate("INSERT INTO TEST(V) VALUES (10)", new int[0]); + ResultSet rs = stat.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + stat.executeUpdate("INSERT INTO TEST(V) VALUES (20)", new int[] { 1, 2 }); + rs = stat.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(2L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.executeUpdate("INSERT INTO TEST(V) VALUES (30)", new int[] { 2, 1 }); + rs = stat.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertEquals("ID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertEquals(3L, rs.getLong(2)); + assertFalse(rs.next()); + rs.close(); + stat.executeUpdate("INSERT INTO TEST(V) VALUES (40)", new int[] { 2 }); + rs = stat.getGeneratedKeys(); + assertEquals(1, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + + /** + * Test method for {@link Statement#executeUpdate(String, String[])}. + * + * @param conn + * connection + * @throws Exception + * on exception + */ + private void testStatementExecuteUpdate_StringArray(Connection conn) throws Exception { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST (ID BIGINT PRIMARY KEY AUTO_INCREMENT," + + "UID UUID NOT NULL DEFAULT RANDOM_UUID(), V INT NOT NULL)"); + stat.executeUpdate("INSERT INTO TEST(V) VALUES (10)", new String[0]); + ResultSet rs = stat.getGeneratedKeys(); + assertFalse(rs.next()); + rs.close(); + stat.executeUpdate("INSERT INTO TEST(V) VALUES (20)", new String[] { "ID", "UID" }); + rs = stat.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("ID", rs.getMetaData().getColumnName(1)); + assertEquals("UID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(2L, rs.getLong(1)); + assertEquals(UUID.class, rs.getObject(2).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.executeUpdate("INSERT INTO TEST(V) VALUES (30)", new String[] { "UID", "ID" }); + rs = stat.getGeneratedKeys(); + assertEquals(2, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertEquals("ID", rs.getMetaData().getColumnName(2)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertEquals(3L, rs.getLong(2)); + assertFalse(rs.next()); + rs.close(); + stat.executeUpdate("INSERT INTO TEST(V) VALUES (40)", new String[] { "UID" }); + rs = stat.getGeneratedKeys(); + assertEquals(1, rs.getMetaData().getColumnCount()); + assertEquals("UID", rs.getMetaData().getColumnName(1)); + assertTrue(rs.next()); + assertEquals(UUID.class, rs.getObject(1).getClass()); + assertFalse(rs.next()); + rs.close(); + stat.execute("DROP TABLE TEST"); + } + +} diff --git a/h2/src/test/org/h2/test/jdbc/TestJavaObjectSerializer.java b/h2/src/test/org/h2/test/jdbc/TestJavaObjectSerializer.java new file mode 100644 index 0000000..bb145a2 --- /dev/null +++ b/h2/src/test/org/h2/test/jdbc/TestJavaObjectSerializer.java @@ -0,0 +1,154 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbc; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; +import java.sql.Types; +import org.h2.api.JavaObjectSerializer; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.JdbcUtils; + +/** + * Tests {@link JavaObjectSerializer}. + * + * @author Sergi Vladykin + * @author Davide Cavestro + */ +public class TestJavaObjectSerializer extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = createCaller().init(); + test.config.traceTest = true; + test.config.memory = true; + test.config.networked = true; + test.testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb("javaSerializer"); + testStaticGlobalSerializer(); + testDbLevelJavaObjectSerializer(); + deleteDb("javaSerializer"); + } + + private void testStaticGlobalSerializer() throws Exception { + JdbcUtils.serializer = new JavaObjectSerializer() { + @Override + public byte[] serialize(Object obj) throws Exception { + assertEquals(100500, ((Integer) obj).intValue()); + + return new byte[] { 1, 2, 3 }; + } + + @Override + public Object deserialize(byte[] bytes) throws Exception { + assertEquals(new byte[] { 1, 2, 3 }, bytes); + + return 100500; + } + }; + + try { + deleteDb("javaSerializer"); + Connection conn = getConnection("javaSerializer"); + + Statement stat = conn.createStatement(); + stat.execute("create table t(id identity, val other)"); + + PreparedStatement ins = conn.prepareStatement("insert into t(val) values(?)"); + + ins.setObject(1, 100500, Types.JAVA_OBJECT); + assertEquals(1, ins.executeUpdate()); + + Statement s = conn.createStatement(); + ResultSet rs = s.executeQuery("select val from t"); + + assertTrue(rs.next()); + + assertEquals(100500, ((Integer) rs.getObject(1)).intValue()); + assertEquals(new byte[] { 1, 2, 3 }, rs.getBytes(1)); + + conn.close(); + deleteDb("javaSerializer"); + } finally { + JdbcUtils.serializer = null; + } + } + + /** + * Tests per-database serializer when set through the related SET command. + */ + public void testDbLevelJavaObjectSerializer() throws Exception { + + DbLevelJavaObjectSerializer.testBaseRef = this; + + try { + deleteDb("javaSerializer"); + Connection conn = getConnection("javaSerializer"); + + conn.createStatement().execute("SET JAVA_OBJECT_SERIALIZER '"+ + DbLevelJavaObjectSerializer.class.getName()+"'"); + + Statement stat = conn.createStatement(); + stat.execute("create table t1(id identity, val other)"); + + PreparedStatement ins = conn.prepareStatement("insert into t1(val) values(?)"); + + ins.setObject(1, 100500, Types.JAVA_OBJECT); + assertEquals(1, ins.executeUpdate()); + + Statement s = conn.createStatement(); + ResultSet rs = s.executeQuery("select val from t1"); + + assertTrue(rs.next()); + + assertEquals(100500, ((Integer) rs.getObject(1)).intValue()); + assertEquals(new byte[] { 1, 2, 3 }, rs.getBytes(1)); + + conn.close(); + deleteDb("javaSerializer"); + } finally { + DbLevelJavaObjectSerializer.testBaseRef = null; + } + } + + /** + * The serializer to use for this test. + */ + public static class DbLevelJavaObjectSerializer implements + JavaObjectSerializer { + + /** + * The test. + */ + static TestBase testBaseRef; + + @Override + public byte[] serialize(Object obj) throws Exception { + testBaseRef.assertEquals(100500, ((Integer) obj).intValue()); + + return new byte[] { 1, 2, 3 }; + } + + @Override + public Object deserialize(byte[] bytes) throws Exception { + testBaseRef.assertEquals(new byte[] { 1, 2, 3 }, bytes); + + return 100500; + } + + } +} diff --git a/h2/src/test/org/h2/test/jdbc/TestLobApi.java b/h2/src/test/org/h2/test/jdbc/TestLobApi.java new file mode 100644 index 0000000..cdb6f7d --- /dev/null +++ b/h2/src/test/org/h2/test/jdbc/TestLobApi.java @@ -0,0 +1,384 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbc; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.StringReader; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.NClob; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.Random; + +import org.h2.api.ErrorCode; +import org.h2.jdbc.JdbcConnection; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.test.utils.RandomDataUtils; +import org.h2.util.IOUtils; + +/** + * Test the Blob, Clob, and NClob implementations. + */ +public class TestLobApi extends TestDb { + + private JdbcConnection conn; + private Statement stat; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb(getTestName()); + testUnsupportedOperations(); + testLobStaysOpenUntilCommitted(); + testInputStreamThrowsException(true); + testInputStreamThrowsException(false); + conn = (JdbcConnection) getConnection(getTestName()); + stat = conn.createStatement(); + stat.execute("create table test(id int, x blob)"); + testBlob(0); + testBlob(1); + testBlob(100); + testBlob(100000); + stat.execute("drop table test"); + stat.execute("create table test(id int, x clob)"); + testClob(0); + testClob(1); + testClob(100); + testClob(100000); + stat.execute("drop table test"); + conn.close(); + } + + private void testUnsupportedOperations() throws Exception { + Connection conn = getConnection(getTestName()); + stat = conn.createStatement(); + stat.execute("create table test(id int, c clob, b blob)"); + stat.execute("insert into test values(1, 'x', x'00')"); + ResultSet rs = stat.executeQuery("select * from test order by id"); + rs.next(); + Clob clob = rs.getClob(2); + byte[] data = IOUtils.readBytesAndClose(clob.getAsciiStream(), -1); + assertEquals("x", new String(data, StandardCharsets.UTF_8)); + assertTrue(clob.toString().endsWith("'x'")); + clob.free(); + assertTrue(clob.toString().endsWith("")); + + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, clob). + truncate(0); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, clob). + setAsciiStream(1); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, clob). + position("", 0); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, clob). + position((Clob) null, 0); + + Blob blob = rs.getBlob(3); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, blob). + truncate(0); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, blob). + position(new byte[1], 0); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, blob). + position((Blob) null, 0); + assertTrue(blob.toString().endsWith("X'00'")); + blob.free(); + assertTrue(blob.toString().endsWith("")); + + stat.execute("drop table test"); + conn.close(); + } + + /** + * According to the JDBC spec, BLOB and CLOB objects must stay open even if + * the result set is closed (see ResultSet.close). + */ + private void testLobStaysOpenUntilCommitted() throws Exception { + Connection conn = getConnection(getTestName()); + stat = conn.createStatement(); + stat.execute("create table test(id identity, c clob, b blob)"); + PreparedStatement prep = conn.prepareStatement( + "insert into test(c, b) values(?, ?)"); + prep.setString(1, ""); + prep.setBytes(2, new byte[0]); + prep.execute(); + + Random r = new Random(1); + + char[] charsSmall = new char[20]; + RandomDataUtils.randomChars(r, charsSmall); + String dSmall = new String(charsSmall); + prep.setCharacterStream(1, new StringReader(dSmall), -1); + byte[] bytesSmall = new byte[20]; + r.nextBytes(bytesSmall); + prep.setBinaryStream(2, new ByteArrayInputStream(bytesSmall), -1); + prep.execute(); + + char[] chars = new char[100000]; + RandomDataUtils.randomChars(r, chars); + String d = new String(chars); + prep.setCharacterStream(1, new StringReader(d), -1); + byte[] bytes = new byte[100000]; + r.nextBytes(bytes); + prep.setBinaryStream(2, new ByteArrayInputStream(bytes), -1); + prep.execute(); + + conn.setAutoCommit(false); + ResultSet rs = stat.executeQuery("select * from test order by id"); + rs.next(); + Clob c1 = rs.getClob(2); + Blob b1 = rs.getBlob(3); + rs.next(); + Clob c2 = rs.getClob(2); + Blob b2 = rs.getBlob(3); + rs.next(); + Clob c3 = rs.getClob(2); + Blob b3 = rs.getBlob(3); + assertFalse(rs.next()); + // now close + rs.close(); + // but the LOBs must stay open + assertEquals(0, c1.length()); + assertEquals(0, b1.length()); + assertEquals("", c1.getSubString(1, 0)); + assertEquals(new byte[0], b1.getBytes(1, 0)); + + assertEquals(charsSmall.length, c2.length()); + assertEquals(bytesSmall.length, b2.length()); + assertEquals(dSmall, c2.getSubString(1, (int) c2.length())); + assertEquals(bytesSmall, b2.getBytes(1, (int) b2.length())); + + assertEquals(chars.length, c3.length()); + assertEquals(bytes.length, b3.length()); + assertEquals(d, c3.getSubString(1, (int) c3.length())); + assertEquals(bytes, b3.getBytes(1, (int) b3.length())); + stat.execute("drop table test"); + conn.close(); + } + + private void testInputStreamThrowsException(final boolean ioException) + throws Exception { + Connection conn = getConnection(getTestName()); + stat = conn.createStatement(); + stat.execute("create table test(id identity, c clob, b blob)"); + PreparedStatement prep = conn.prepareStatement( + "insert into test(c, b) values(?, ?)"); + + assertThrows(ErrorCode.IO_EXCEPTION_1, prep). + setCharacterStream(1, new Reader() { + int pos; + @Override + public int read(char[] buff, int off, int len) throws IOException { + pos += len; + if (pos > 100001) { + if (ioException) { + throw new IOException(""); + } + throw new IllegalStateException(); + } + return len; + } + @Override + public void close() throws IOException { + // nothing to do + } + }, -1); + + prep.setString(1, new String(new char[10000])); + prep.setBytes(2, new byte[0]); + prep.execute(); + prep.setString(1, ""); + + assertThrows(ErrorCode.IO_EXCEPTION_1, prep). + setBinaryStream(2, new InputStream() { + int pos; + @Override + public int read() throws IOException { + pos++; + if (pos > 100001) { + if (ioException) { + throw new IOException(""); + } + throw new IllegalStateException(); + } + return 0; + } + }, -1); + + prep.setBytes(2, new byte[10000]); + prep.execute(); + ResultSet rs = stat.executeQuery("select c, b from test order by id"); + rs.next(); + assertEquals(new String(new char[10000]), rs.getString(1)); + assertEquals(new byte[0], rs.getBytes(2)); + rs.next(); + assertEquals("", rs.getString(1)); + assertEquals(new byte[10000], rs.getBytes(2)); + stat.execute("drop table test"); + conn.close(); + } + + private void testBlob(int length) throws Exception { + Random r = new Random(length); + byte[] data = new byte[length]; + r.nextBytes(data); + Blob b = conn.createBlob(); + OutputStream out = b.setBinaryStream(1); + out.write(data, 0, data.length); + out.close(); + stat.execute("delete from test"); + + PreparedStatement prep = conn.prepareStatement("insert into test values(?, ?)"); + prep.setInt(1, 1); + prep.setBlob(2, b); + prep.execute(); + + prep.setInt(1, 2); + b = conn.createBlob(); + assertEquals(length, b.setBytes(1, data)); + prep.setBlob(2, b); + prep.execute(); + + prep.setInt(1, 3); + Blob b2 = conn.createBlob(); + byte[] xdata = new byte[length + 2]; + System.arraycopy(data, 0, xdata, 1, length); + assertEquals(length, b2.setBytes(1, xdata, 1, length)); + prep.setBlob(2, b2); + prep.execute(); + + prep.setInt(1, 4); + prep.setBlob(2, new ByteArrayInputStream(data)); + prep.execute(); + + prep.setInt(1, 5); + prep.setBlob(2, new ByteArrayInputStream(data), -1); + prep.execute(); + + ResultSet rs; + rs = stat.executeQuery("select * from test"); + rs.next(); + Blob b3 = rs.getBlob(2); + assertEquals(length, b3.length()); + byte[] bytes = b.getBytes(1, length); + byte[] bytes2 = b3.getBytes(1, length); + assertEquals(bytes, bytes2); + rs.next(); + b3 = rs.getBlob(2); + assertEquals(length, b3.length()); + bytes2 = b3.getBytes(1, length); + assertEquals(bytes, bytes2); + rs.next(); + b3 = rs.getBlob(2); + assertEquals(length, b3.length()); + bytes2 = b3.getBytes(1, length); + assertEquals(bytes, bytes2); + while (rs.next()) { + bytes2 = rs.getBytes(2); + assertEquals(bytes, bytes2); + } + } + + private void testClob(int length) throws Exception { + Random r = new Random(length); + char[] data = new char[length]; + + // Unicode problem: + // The UCS code values 0xd800-0xdfff (UTF-16 surrogates) + // as well as 0xfffe and 0xffff (UCS non-characters) + // should not appear in conforming UTF-8 streams. + // (String.getBytes("UTF-8") only returns 1 byte for 0xd800-0xdfff) + for (int i = 0; i < length; i++) { + char c; + do { + c = (char) r.nextInt(); + } while (c >= 0xd800 && c <= 0xdfff); + data[i] = c; + } + Clob c = conn.createClob(); + Writer out = c.setCharacterStream(1); + out.write(data, 0, data.length); + out.close(); + stat.execute("delete from test"); + PreparedStatement prep = conn.prepareStatement("insert into test values(?, ?)"); + + prep.setInt(1, 1); + prep.setClob(2, c); + prep.execute(); + + c = conn.createClob(); + c.setString(1, new String(data)); + prep.setInt(1, 2); + prep.setClob(2, c); + prep.execute(); + + prep.setInt(1, 3); + prep.setCharacterStream(2, new StringReader(new String(data))); + prep.execute(); + + prep.setInt(1, 4); + prep.setCharacterStream(2, new StringReader(new String(data)), -1); + prep.execute(); + + NClob nc; + nc = conn.createNClob(); + assertEquals(length, nc.setString(1, new String(data))); + prep.setInt(1, 5); + prep.setNClob(2, nc); + prep.execute(); + + nc = conn.createNClob(); + char[] xdata = new char[length + 2]; + System.arraycopy(data, 0, xdata, 1, length); + assertEquals(length, nc.setString(1, new String(xdata), 1, length)); + prep.setInt(1, 6); + prep.setNClob(2, nc); + prep.execute(); + + prep.setInt(1, 7); + prep.setNClob(2, new StringReader(new String(data))); + prep.execute(); + + prep.setInt(1, 8); + prep.setNClob(2, new StringReader(new String(data)), -1); + prep.execute(); + + prep.setInt(1, 9); + prep.setNString(2, new String(data)); + prep.execute(); + + ResultSet rs; + rs = stat.executeQuery("select * from test"); + rs.next(); + Clob c2 = rs.getClob(2); + assertEquals(length, c2.length()); + String s = c.getSubString(1, length); + String s2 = c2.getSubString(1, length); + while (rs.next()) { + c2 = rs.getClob(2); + assertEquals(length, c2.length()); + s2 = c2.getSubString(1, length); + assertEquals(s, s2); + } + } + +} diff --git a/h2/src/test/org/h2/test/jdbc/TestManyJdbcObjects.java b/h2/src/test/org/h2/test/jdbc/TestManyJdbcObjects.java new file mode 100644 index 0000000..d833c80 --- /dev/null +++ b/h2/src/test/org/h2/test/jdbc/TestManyJdbcObjects.java @@ -0,0 +1,117 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbc; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests the server by creating many JDBC objects (result sets and so on). + */ +public class TestManyJdbcObjects extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + testNestedResultSets(); + testManyConnections(); + testOneConnectionPrepare(); + deleteDb("manyObjects"); + } + + private void testNestedResultSets() throws SQLException { + if (!config.networked) { + return; + } + deleteDb("manyObjects"); + Connection conn = getConnection("manyObjects"); + DatabaseMetaData meta = conn.getMetaData(); + ResultSet rsTables = meta.getColumns(null, null, null, null); + while (rsTables.next()) { + meta.getExportedKeys(null, null, "TEST"); + meta.getImportedKeys(null, null, "TEST"); + } + conn.close(); + } + + private void testManyConnections() throws SQLException { + if (!config.networked || config.memory) { + return; + } + // SERVER_CACHED_OBJECTS = 1000: connections = 20 (1250) + // SERVER_CACHED_OBJECTS = 500: connections = 40 + // SERVER_CACHED_OBJECTS = 50: connections = 120 + deleteDb("manyObjects"); + int connCount = getSize(4, 40); + Connection[] conn = new Connection[connCount]; + for (int i = 0; i < connCount; i++) { + conn[i] = getConnection("manyObjects"); + } + int len = getSize(50, 500); + for (int j = 0; j < len; j++) { + if ((j % 10) == 0) { + trace("j=" + j); + } + for (int i = 0; i < connCount; i++) { + conn[i].getMetaData().getSchemas().close(); + } + } + for (int i = 0; i < connCount; i++) { + conn[i].close(); + } + } + + private void testOneConnectionPrepare() throws SQLException { + deleteDb("manyObjects"); + Connection conn = getConnection("manyObjects"); + PreparedStatement prep; + Statement stat; + int size = getSize(10, 1000); + for (int i = 0; i < size; i++) { + conn.getMetaData(); + } + for (int i = 0; i < size; i++) { + conn.createStatement(); + } + stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + stat.execute("INSERT INTO TEST VALUES(1, 'Hello')"); + for (int i = 0; i < size; i++) { + stat.executeQuery("SELECT * FROM TEST WHERE 1=0"); + } + for (int i = 0; i < size; i++) { + stat.executeQuery("SELECT * FROM TEST"); + } + for (int i = 0; i < size; i++) { + conn.prepareStatement("SELECT * FROM TEST"); + } + prep = conn.prepareStatement("SELECT * FROM TEST WHERE 1=0"); + for (int i = 0; i < size; i++) { + prep.executeQuery(); + } + prep = conn.prepareStatement("SELECT * FROM TEST"); + for (int i = 0; i < size; i++) { + prep.executeQuery(); + } + conn.close(); + } + +} diff --git a/h2/src/test/org/h2/test/jdbc/TestMetaData.java b/h2/src/test/org/h2/test/jdbc/TestMetaData.java new file mode 100644 index 0000000..ebf8879 --- /dev/null +++ b/h2/src/test/org/h2/test/jdbc/TestMetaData.java @@ -0,0 +1,1409 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbc; + +import static org.h2.engine.Constants.MAX_ARRAY_CARDINALITY; +import static org.h2.engine.Constants.MAX_NUMERIC_PRECISION; +import static org.h2.engine.Constants.MAX_STRING_LENGTH; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.Driver; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; + +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.mode.DefaultNullOrdering; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Test for the DatabaseMetaData implementation. + */ +public class TestMetaData extends TestDb { + + private static final String CATALOG = "METADATA"; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + deleteDb("metaData"); + testUnwrap(); + testUnsupportedOperations(); + testTempTable(); + testColumnLobMeta(); + testColumnMetaData(); + testColumnPrecision(); + testColumnDefault(); + testColumnGenerated(); + testHiddenColumn(); + testCrossReferences(); + testProcedureColumns(); + testTypeInfo(); + testUDTs(); + testStatic(); + testNullsAreSortedAt(); + testGeneral(); + testAllowLiteralsNone(); + testClientInfo(); + testQueryStatistics(); + testQueryStatisticsLimit(); + } + + private void testUnwrap() throws SQLException { + Connection conn = getConnection("metaData"); + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("select 1 as x from dual"); + ResultSetMetaData meta = rs.getMetaData(); + assertTrue(meta.isWrapperFor(Object.class)); + assertTrue(meta.isWrapperFor(ResultSetMetaData.class)); + assertTrue(meta.isWrapperFor(meta.getClass())); + assertTrue(meta == meta.unwrap(Object.class)); + assertTrue(meta == meta.unwrap(ResultSetMetaData.class)); + assertTrue(meta == meta.unwrap(meta.getClass())); + assertFalse(meta.isWrapperFor(Integer.class)); + assertThrows(ErrorCode.INVALID_VALUE_2, meta). + unwrap(Integer.class); + conn.close(); + } + + private void testUnsupportedOperations() throws SQLException { + Connection conn = getConnection("metaData"); + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("select 1 as x from dual"); + ResultSetMetaData meta = rs.getMetaData(); + assertThrows(ErrorCode.INVALID_VALUE_2, meta).getColumnLabel(0); + assertThrows(ErrorCode.INVALID_VALUE_2, meta).getColumnName(0); + assertThrows(ErrorCode.INVALID_VALUE_2, meta).getColumnType(0); + assertThrows(ErrorCode.INVALID_VALUE_2, meta).getColumnTypeName(0); + assertThrows(ErrorCode.INVALID_VALUE_2, meta).getSchemaName(0); + assertThrows(ErrorCode.INVALID_VALUE_2, meta).getTableName(0); + assertThrows(ErrorCode.INVALID_VALUE_2, meta).getCatalogName(0); + assertThrows(ErrorCode.INVALID_VALUE_2, meta).isAutoIncrement(0); + assertThrows(ErrorCode.INVALID_VALUE_2, meta).isCaseSensitive(0); + assertThrows(ErrorCode.INVALID_VALUE_2, meta).isSearchable(0); + assertThrows(ErrorCode.INVALID_VALUE_2, meta).isCurrency(0); + assertThrows(ErrorCode.INVALID_VALUE_2, meta).isNullable(0); + assertThrows(ErrorCode.INVALID_VALUE_2, meta).isSigned(0); + assertThrows(ErrorCode.INVALID_VALUE_2, meta).isReadOnly(0); + assertThrows(ErrorCode.INVALID_VALUE_2, meta).isWritable(0); + assertThrows(ErrorCode.INVALID_VALUE_2, meta).isDefinitelyWritable(0); + assertThrows(ErrorCode.INVALID_VALUE_2, meta).getColumnClassName(0); + assertThrows(ErrorCode.INVALID_VALUE_2, meta).getPrecision(0); + assertThrows(ErrorCode.INVALID_VALUE_2, meta).getScale(0); + assertThrows(ErrorCode.INVALID_VALUE_2, meta).getColumnDisplaySize(0); + conn.close(); + } + + private void testColumnLobMeta() throws SQLException { + Connection conn = getConnection("metaData"); + Statement stat = conn.createStatement(); + stat.executeUpdate("CREATE TABLE t (blob BLOB, clob CLOB)"); + stat.execute("INSERT INTO t VALUES('', '')"); + ResultSet rs = stat.executeQuery("SELECT blob,clob FROM t"); + ResultSetMetaData rsMeta = rs.getMetaData(); + assertEquals("java.sql.Blob", rsMeta.getColumnClassName(1)); + assertEquals("java.sql.Clob", rsMeta.getColumnClassName(2)); + rs.next(); + assertTrue(rs.getObject(1) instanceof java.sql.Blob); + assertTrue(rs.getObject(2) instanceof java.sql.Clob); + stat.executeUpdate("DROP TABLE t"); + conn.close(); + } + + private void testColumnMetaData() throws SQLException { + Connection conn = getConnection("metaData"); + String sql = "select substring('Hello',0,1)"; + ResultSet rs = conn.prepareStatement(sql).executeQuery(); + rs.next(); + int type = rs.getMetaData().getColumnType(1); + assertEquals(Types.VARCHAR, type); + rs = conn.createStatement().executeQuery("SELECT COUNT(*) C FROM DUAL"); + assertEquals("C", rs.getMetaData().getColumnName(1)); + + Statement stat = conn.createStatement(); + stat.execute("create table a(x int array)"); + stat.execute("insert into a values(ARRAY[1, 2])"); + rs = stat.executeQuery("SELECT x[1] FROM a"); + ResultSetMetaData rsMeta = rs.getMetaData(); + assertEquals(Types.INTEGER, rsMeta.getColumnType(1)); + rs.next(); + assertEquals(Integer.class.getName(), + rs.getObject(1).getClass().getName()); + stat.execute("drop table a"); + conn.close(); + } + + private void testColumnPrecision() throws SQLException { + Connection conn = getConnection("metaData"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE ONE(X NUMBER(12,2), Y FLOAT)"); + stat.execute("CREATE TABLE TWO AS SELECT * FROM ONE"); + ResultSet rs; + ResultSetMetaData rsMeta; + rs = stat.executeQuery("SELECT * FROM ONE"); + rsMeta = rs.getMetaData(); + assertEquals(12, rsMeta.getPrecision(1)); + assertEquals(53, rsMeta.getPrecision(2)); + assertEquals(Types.NUMERIC, rsMeta.getColumnType(1)); + assertEquals(Types.FLOAT, rsMeta.getColumnType(2)); + rs = stat.executeQuery("SELECT * FROM TWO"); + rsMeta = rs.getMetaData(); + assertEquals(12, rsMeta.getPrecision(1)); + assertEquals(53, rsMeta.getPrecision(2)); + assertEquals(Types.NUMERIC, rsMeta.getColumnType(1)); + assertEquals(Types.FLOAT, rsMeta.getColumnType(2)); + stat.execute("DROP TABLE ONE, TWO"); + conn.close(); + } + + private void testColumnDefault() throws SQLException { + Connection conn = getConnection("metaData"); + DatabaseMetaData meta = conn.getMetaData(); + ResultSet rs; + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(A INT, B INT DEFAULT NULL)"); + rs = meta.getColumns(null, null, "TEST", null); + rs.next(); + assertEquals("A", rs.getString("COLUMN_NAME")); + assertEquals(null, rs.getString("COLUMN_DEF")); + rs.next(); + assertEquals("B", rs.getString("COLUMN_NAME")); + assertEquals("NULL", rs.getString("COLUMN_DEF")); + assertFalse(rs.next()); + stat.execute("DROP TABLE TEST"); + conn.close(); + } + + private void testColumnGenerated() throws SQLException { + Connection conn = getConnection("metaData"); + DatabaseMetaData meta = conn.getMetaData(); + ResultSet rs; + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(A INT, B INT AS A + 1)"); + rs = meta.getColumns(null, null, "TEST", null); + rs.next(); + assertEquals("A", rs.getString("COLUMN_NAME")); + assertEquals("NO", rs.getString("IS_GENERATEDCOLUMN")); + rs.next(); + assertEquals("B", rs.getString("COLUMN_NAME")); + assertEquals("YES", rs.getString("IS_GENERATEDCOLUMN")); + assertFalse(rs.next()); + stat.execute("DROP TABLE TEST"); + conn.close(); + } + + private void testHiddenColumn() throws SQLException { + Connection conn = getConnection("metaData"); + DatabaseMetaData meta = conn.getMetaData(); + ResultSet rs; + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(A INT, B INT INVISIBLE)"); + rs = meta.getColumns(null, null, "TEST", null); + assertTrue(rs.next()); + assertEquals("A", rs.getString("COLUMN_NAME")); + assertFalse(rs.next()); + rs = meta.getPseudoColumns(null, null, "TEST", null); + assertTrue(rs.next()); + assertEquals("B", rs.getString("COLUMN_NAME")); + assertEquals("YES", rs.getString("IS_NULLABLE")); + assertTrue(rs.next()); + assertEquals("_ROWID_", rs.getString("COLUMN_NAME")); + assertEquals("NO", rs.getString("IS_NULLABLE")); + assertFalse(rs.next()); + stat.execute("DROP TABLE TEST"); + conn.close(); + } + + private void testProcedureColumns() throws SQLException { + Connection conn = getConnection("metaData"); + DatabaseMetaData meta = conn.getMetaData(); + ResultSet rs; + Statement stat = conn.createStatement(); + stat.execute("CREATE ALIAS PROP FOR 'java.lang.System.getProperty(java.lang.String)'"); + stat.execute("CREATE ALIAS EXIT FOR 'java.lang.System.exit'"); + rs = meta.getProcedures(null, null, "EX%"); + assertResultSetMeta(rs, 9, new String[] { "PROCEDURE_CAT", + "PROCEDURE_SCHEM", "PROCEDURE_NAME", "RESERVED1", + "RESERVED2", "RESERVED3", "REMARKS", + "PROCEDURE_TYPE", "SPECIFIC_NAME" }, new int[] { Types.VARCHAR, + Types.VARCHAR, Types.VARCHAR, Types.NULL, Types.NULL, + Types.NULL, Types.VARCHAR, Types.SMALLINT, Types.VARCHAR }, + null, null); + assertResultSetOrdered(rs, new String[][] { { CATALOG, + Constants.SCHEMA_MAIN, "EXIT", null, null, null, null, + "" + DatabaseMetaData.procedureNoResult, "EXIT_1" } }); + rs = meta.getProcedureColumns(null, null, null, null); + assertResultSetMeta(rs, 20, new String[] { "PROCEDURE_CAT", + "PROCEDURE_SCHEM", "PROCEDURE_NAME", "COLUMN_NAME", + "COLUMN_TYPE", "DATA_TYPE", "TYPE_NAME", "PRECISION", "LENGTH", + "SCALE", "RADIX", "NULLABLE", "REMARKS", "COLUMN_DEF", + "SQL_DATA_TYPE", "SQL_DATETIME_SUB", "CHAR_OCTET_LENGTH", + "ORDINAL_POSITION", "IS_NULLABLE", "SPECIFIC_NAME" }, + new int[] { Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, + Types.VARCHAR, Types.SMALLINT, Types.INTEGER, + Types.VARCHAR, Types.INTEGER, Types.INTEGER, + Types.SMALLINT, Types.SMALLINT, Types.SMALLINT, + Types.VARCHAR, Types.VARCHAR, Types.INTEGER, + Types.INTEGER, Types.INTEGER, Types.INTEGER, + Types.VARCHAR, Types.VARCHAR }, null, null); + assertResultSetOrdered(rs, new String[][] { + { CATALOG, Constants.SCHEMA_MAIN, "EXIT", "P1", + "" + DatabaseMetaData.procedureColumnIn, + "" + Types.INTEGER, "INTEGER", "32", "32", null, "2", + "" + DatabaseMetaData.procedureNoNulls, + null, null, null, null, null, "1", "", "EXIT_1" }, + { CATALOG, Constants.SCHEMA_MAIN, "PROP", "RESULT", + "" + DatabaseMetaData.procedureColumnReturn, + "" + Types.VARCHAR, "CHARACTER VARYING", "" + MAX_STRING_LENGTH, + "" + MAX_STRING_LENGTH, null, null, + "" + DatabaseMetaData.procedureNullableUnknown, + null, null, null, null, "" + MAX_STRING_LENGTH, "0", "", "PROP_1" }, + { CATALOG, Constants.SCHEMA_MAIN, "PROP", "P1", + "" + DatabaseMetaData.procedureColumnIn, + "" + Types.VARCHAR, "CHARACTER VARYING", "" + MAX_STRING_LENGTH, + "" + MAX_STRING_LENGTH, null, null, + "" + DatabaseMetaData.procedureNullableUnknown, + null, null, null, null, "" + MAX_STRING_LENGTH, "1", "", "PROP_1" }, }); + stat.execute("DROP ALIAS EXIT"); + stat.execute("DROP ALIAS PROP"); + conn.close(); + } + + private void testTypeInfo() throws SQLException { + Connection conn = getConnection("metaData"); + DatabaseMetaData meta = conn.getMetaData(); + ResultSet rs; + rs = meta.getTypeInfo(); + assertResultSetMeta(rs, 18, + new String[] { "TYPE_NAME", "DATA_TYPE", "PRECISION", "LITERAL_PREFIX", "LITERAL_SUFFIX", + "CREATE_PARAMS", "NULLABLE", "CASE_SENSITIVE", "SEARCHABLE", "UNSIGNED_ATTRIBUTE", + "FIXED_PREC_SCALE", "AUTO_INCREMENT", "LOCAL_TYPE_NAME", "MINIMUM_SCALE", "MAXIMUM_SCALE", + "SQL_DATA_TYPE", "SQL_DATETIME_SUB", "NUM_PREC_RADIX"}, + new int[] { Types.VARCHAR, Types.INTEGER, Types.INTEGER, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, + Types.SMALLINT, Types.BOOLEAN, Types.SMALLINT, Types.BOOLEAN, Types.BOOLEAN, Types.BOOLEAN, + Types.VARCHAR, Types.SMALLINT, Types.SMALLINT, Types.INTEGER, Types.INTEGER, Types.INTEGER }, + null, null); + testTypeInfo(rs, "TINYINT", Types.TINYINT, 8, null, null, null, false, false, (short) 0, (short) 0, 2); + testTypeInfo(rs, "BIGINT", Types.BIGINT, 64, null, null, null, false, false, (short) 0, (short) 0, 2); + testTypeInfo(rs, "BINARY VARYING", Types.VARBINARY, MAX_STRING_LENGTH, "X'", "'", "LENGTH", false, false, + (short) 0, (short) 0, 0); + testTypeInfo(rs, "BINARY", Types.BINARY, MAX_STRING_LENGTH, "X'", "'", "LENGTH", false, false, (short) 0, + (short) 0, 0); + testTypeInfo(rs, "UUID", Types.BINARY, 16, "'", "'", null, false, false, (short) 0, (short) 0, 0); + testTypeInfo(rs, "CHARACTER", Types.CHAR, MAX_STRING_LENGTH, "'", "'", "LENGTH", true, false, (short) 0, + (short) 0, 0); + testTypeInfo(rs, "NUMERIC", Types.NUMERIC, MAX_NUMERIC_PRECISION, null, null, "PRECISION,SCALE", false, true, + (short) 0, Short.MAX_VALUE, 10); + testTypeInfo(rs, "DECFLOAT", Types.NUMERIC, MAX_NUMERIC_PRECISION, null, null, "PRECISION", false, false, + (short) 0, (short) 0, 10); + testTypeInfo(rs, "INTEGER", Types.INTEGER, 32, null, null, null, false, false, (short) 0, + (short) 0, 2); + testTypeInfo(rs, "SMALLINT", Types.SMALLINT, 16, null, null, null, false, false, (short) 0, + (short) 0, 2); + testTypeInfo(rs, "REAL", Types.REAL, 24, null, null, null, false, false, (short) 0, (short) 0, 2); + testTypeInfo(rs, "DOUBLE PRECISION", Types.DOUBLE, 53, null, null, null, false, false, (short) 0, (short) 0, + 2); + testTypeInfo(rs, "CHARACTER VARYING", Types.VARCHAR, MAX_STRING_LENGTH, "'", "'", "LENGTH", true, false, + (short) 0, (short) 0, 0); + testTypeInfo(rs, "VARCHAR_IGNORECASE", Types.VARCHAR, MAX_STRING_LENGTH, "'", "'", "LENGTH", false, false, + (short) 0, (short) 0, 0); + testTypeInfo(rs, "BOOLEAN", Types.BOOLEAN, 1, null, null, null, false, false, (short) 0, + (short) 0, 0); + testTypeInfo(rs, "DATE", Types.DATE, 10, "DATE '", "'", null, false, false, (short) 0, (short) 0, 0); + testTypeInfo(rs, "TIME", Types.TIME, 18, "TIME '", "'", "SCALE", false, false, (short) 0, (short) 9, 0); + testTypeInfo(rs, "TIMESTAMP", Types.TIMESTAMP, 29, "TIMESTAMP '", "'", "SCALE", false, false, (short) 0, + (short) 9, 0); + testTypeInfo(rs, "INTERVAL YEAR", Types.OTHER, 18, "INTERVAL '", "' YEAR", "PRECISION", false, false, + (short) 0, (short) 0, 0); + testTypeInfo(rs, "INTERVAL MONTH", Types.OTHER, 18, "INTERVAL '", "' MONTH", "PRECISION", false, false, + (short) 0, (short) 0, 0); + testTypeInfo(rs, "INTERVAL DAY", Types.OTHER, 18, "INTERVAL '", "' DAY", "PRECISION", false, false, + (short) 0, (short) 0, 0); + testTypeInfo(rs, "INTERVAL HOUR", Types.OTHER, 18, "INTERVAL '", "' HOUR", "PRECISION", false, false, + (short) 0, (short) 0, 0); + testTypeInfo(rs, "INTERVAL MINUTE", Types.OTHER, 18, "INTERVAL '", "' MINUTE", "PRECISION", false, false, + (short) 0, (short) 0, 0); + testTypeInfo(rs, "INTERVAL SECOND", Types.OTHER, 18, "INTERVAL '", "' SECOND", "PRECISION,SCALE", false, false, + (short) 0, (short) 9, 0); + testTypeInfo(rs, "INTERVAL YEAR TO MONTH", Types.OTHER, 18, "INTERVAL '", "' YEAR TO MONTH", "PRECISION", + false, false, (short) 0, (short) 0, 0); + testTypeInfo(rs, "INTERVAL DAY TO HOUR", Types.OTHER, 18, "INTERVAL '", "' DAY TO HOUR", "PRECISION", + false, false, (short) 0, (short) 0, 0); + testTypeInfo(rs, "INTERVAL DAY TO MINUTE", Types.OTHER, 18, "INTERVAL '", "' DAY TO MINUTE", "PRECISION", + false, false, (short) 0, (short) 0, 0); + testTypeInfo(rs, "INTERVAL DAY TO SECOND", Types.OTHER, 18, "INTERVAL '", "' DAY TO SECOND", "PRECISION,SCALE", + false, false, (short) 0, (short) 9, 0); + testTypeInfo(rs, "INTERVAL HOUR TO MINUTE", Types.OTHER, 18, "INTERVAL '", "' HOUR TO MINUTE", "PRECISION", + false, false, (short) 0, (short) 0, 0); + testTypeInfo(rs, "INTERVAL HOUR TO SECOND", Types.OTHER, 18, "INTERVAL '", "' HOUR TO SECOND", + "PRECISION,SCALE", false, false, (short) 0, (short) 9, 0); + testTypeInfo(rs, "INTERVAL MINUTE TO SECOND", Types.OTHER, 18, "INTERVAL '", "' MINUTE TO SECOND", + "PRECISION,SCALE", false, false, (short) 0, (short) 9, 0); + testTypeInfo(rs, "ENUM", Types.OTHER, MAX_STRING_LENGTH, "'", "'", "ELEMENT [,...]", false, false, (short) 0, + (short) 0, 0); + testTypeInfo(rs, "GEOMETRY", Types.OTHER, Integer.MAX_VALUE, "'", "'", "TYPE,SRID", false, false, (short) 0, + (short) 0, 0); + testTypeInfo(rs, "JSON", Types.OTHER, MAX_STRING_LENGTH, "JSON '", "'", "LENGTH", true, false, (short) 0, + (short) 0, 0); + testTypeInfo(rs, "ROW", Types.OTHER, 0, "ROW(", ")", "NAME DATA_TYPE [,...]", false, false, (short) 0, + (short) 0, 0); + testTypeInfo(rs, "JAVA_OBJECT", Types.JAVA_OBJECT, MAX_STRING_LENGTH, "X'", "'", "LENGTH", false, false, + (short) 0, (short) 0, 0); + testTypeInfo(rs, "ARRAY", Types.ARRAY, MAX_ARRAY_CARDINALITY, "ARRAY[", "]", "CARDINALITY", false, false, + (short) 0, (short) 0, 0); + testTypeInfo(rs, "BINARY LARGE OBJECT", Types.BLOB, Integer.MAX_VALUE, "X'", "'", "LENGTH", false, false, + (short) 0, (short) 0, 0); + testTypeInfo(rs, "CHARACTER LARGE OBJECT", Types.CLOB, Integer.MAX_VALUE, "'", "'", "LENGTH", true, false, + (short) 0, (short) 0, 0); + testTypeInfo(rs, "TIME WITH TIME ZONE", Types.TIME_WITH_TIMEZONE, 24, "TIME WITH TIME ZONE '", "'", "SCALE", + false, false, (short) 0, (short) 9, 0); + testTypeInfo(rs, "TIMESTAMP WITH TIME ZONE", Types.TIMESTAMP_WITH_TIMEZONE, 35, "TIMESTAMP WITH TIME ZONE '", + "'", "SCALE", false, false, (short) 0, (short) 9, 0); + assertFalse(rs.next()); + conn.close(); + } + + private void testTypeInfo(ResultSet rs, String name, int type, long precision, String prefix, String suffix, + String params, boolean caseSensitive, boolean fixed, short minScale, short maxScale, int radix) + throws SQLException { + assertTrue(rs.next()); + assertEquals(name, rs.getString(1)); + assertEquals(type, rs.getInt(2)); + assertEquals(precision, rs.getLong(3)); + assertEquals(prefix, rs.getString(4)); + assertEquals(suffix, rs.getString(5)); + assertEquals(params, rs.getString(6)); + assertEquals(DatabaseMetaData.typeNullable, rs.getShort(7)); + assertEquals(caseSensitive, rs.getBoolean(8)); + assertEquals(DatabaseMetaData.typeSearchable, rs.getShort(9)); + assertFalse(rs.getBoolean(10)); + assertEquals(fixed, rs.getBoolean(11)); + assertFalse(rs.getBoolean(12)); + assertEquals(name, rs.getString(13)); + assertEquals(minScale, rs.getShort(14)); + assertEquals(maxScale, rs.getShort(15)); + rs.getInt(16); + assertTrue(rs.wasNull()); + rs.getInt(17); + assertTrue(rs.wasNull()); + if (radix != 0) { + assertEquals(radix, rs.getInt(18)); + } else { + rs.getInt(18); + assertTrue(rs.wasNull()); + } + } + + private void testUDTs() throws SQLException { + Connection conn = getConnection("metaData"); + DatabaseMetaData meta = conn.getMetaData(); + ResultSet rs; + rs = meta.getUDTs(null, null, null, null); + assertResultSetMeta(rs, 7, + new String[] { "TYPE_CAT", "TYPE_SCHEM", "TYPE_NAME", + "CLASS_NAME", "DATA_TYPE", "REMARKS", "BASE_TYPE" }, + new int[] { Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, + Types.VARCHAR, Types.INTEGER, Types.VARCHAR, + Types.SMALLINT }, null, null); + conn.close(); + } + + private void testCrossReferences() throws SQLException { + Connection conn = getConnection("metaData"); + DatabaseMetaData meta = conn.getMetaData(); + ResultSet rs; + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE PARENT(A INT, B INT, PRIMARY KEY(A, B))"); + stat.execute("CREATE TABLE CHILD(ID INT PRIMARY KEY, PA INT, PB INT, " + + "CONSTRAINT AB FOREIGN KEY(PA, PB) REFERENCES PARENT(A, B))"); + rs = meta.getCrossReference(null, "PUBLIC", "PARENT", null, "PUBLIC", "CHILD"); + checkCrossRef(rs); + rs = meta.getImportedKeys(null, "PUBLIC", "CHILD"); + checkCrossRef(rs); + rs = meta.getExportedKeys(null, "PUBLIC", "PARENT"); + checkCrossRef(rs); + stat.execute("DROP TABLE PARENT, CHILD"); + conn.close(); + } + + private void checkCrossRef(ResultSet rs) throws SQLException { + assertResultSetMeta(rs, 14, new String[] { "PKTABLE_CAT", + "PKTABLE_SCHEM", "PKTABLE_NAME", "PKCOLUMN_NAME", + "FKTABLE_CAT", "FKTABLE_SCHEM", "FKTABLE_NAME", + "FKCOLUMN_NAME", "KEY_SEQ", "UPDATE_RULE", "DELETE_RULE", + "FK_NAME", "PK_NAME", "DEFERRABILITY" }, new int[] { + Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, + Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, + Types.SMALLINT, Types.SMALLINT, Types.SMALLINT, Types.VARCHAR, + Types.VARCHAR, Types.SMALLINT }, null, null); + assertResultSetOrdered(rs, new String[][] { + { CATALOG, Constants.SCHEMA_MAIN, "PARENT", "A", CATALOG, + Constants.SCHEMA_MAIN, "CHILD", "PA", "1", + "" + DatabaseMetaData.importedKeyRestrict, + "" + DatabaseMetaData.importedKeyRestrict, "AB", + "CONSTRAINT_8", + "" + DatabaseMetaData.importedKeyNotDeferrable }, + { CATALOG, Constants.SCHEMA_MAIN, "PARENT", "B", CATALOG, + Constants.SCHEMA_MAIN, "CHILD", "PB", "2", + "" + DatabaseMetaData.importedKeyRestrict, + "" + DatabaseMetaData.importedKeyRestrict, "AB", + "CONSTRAINT_8", + "" + DatabaseMetaData.importedKeyNotDeferrable } }); + } + + private void testTempTable() throws SQLException { + Connection conn = getConnection("metaData"); + Statement stat = conn.createStatement(); + stat.execute("DROP TABLE IF EXISTS TEST_TEMP"); + stat.execute("CREATE TEMP TABLE TEST_TEMP" + + "(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + stat.execute("CREATE INDEX IDX_NAME ON TEST_TEMP(NAME)"); + stat.execute("ALTER TABLE TEST_TEMP ADD FOREIGN KEY(ID) REFERENCES(ID)"); + conn.close(); + + conn = getConnection("metaData"); + stat = conn.createStatement(); + stat.execute("CREATE TEMP TABLE TEST_TEMP" + + "(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + ResultSet rs = stat.executeQuery("SELECT STORAGE_TYPE FROM " + + "INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='TEST_TEMP'"); + rs.next(); + assertEquals("GLOBAL TEMPORARY", rs.getString("STORAGE_TYPE")); + stat.execute("DROP TABLE IF EXISTS TEST_TEMP"); + conn.close(); + } + + private void testStatic() throws SQLException { + Driver dr = org.h2.Driver.load(); + Connection conn = getConnection("metaData"); + DatabaseMetaData meta = conn.getMetaData(); + + assertEquals(dr.getMajorVersion(), meta.getDriverMajorVersion()); + assertEquals(dr.getMinorVersion(), meta.getDriverMinorVersion()); + assertTrue(dr.jdbcCompliant()); + + assertEquals(0, dr.getPropertyInfo(null, null).length); + assertNull(dr.connect("jdbc:test:false", null)); + + assertTrue(meta.getNumericFunctions().length() > 0); + assertTrue(meta.getStringFunctions().length() > 0); + assertTrue(meta.getSystemFunctions().length() > 0); + assertTrue(meta.getTimeDateFunctions().length() > 0); + + assertTrue(meta.allProceduresAreCallable()); + assertTrue(meta.allTablesAreSelectable()); + assertTrue(meta.dataDefinitionCausesTransactionCommit()); + assertFalse(meta.dataDefinitionIgnoredInTransactions()); + assertFalse(meta.deletesAreDetected(ResultSet.TYPE_FORWARD_ONLY)); + assertFalse(meta.deletesAreDetected(ResultSet.TYPE_SCROLL_INSENSITIVE)); + assertFalse(meta.deletesAreDetected(ResultSet.TYPE_SCROLL_SENSITIVE)); + assertFalse(meta.doesMaxRowSizeIncludeBlobs()); + assertEquals(".", meta.getCatalogSeparator()); + assertEquals("catalog", meta.getCatalogTerm()); + assertTrue(meta.getConnection() == conn); + String versionStart = meta.getDatabaseMajorVersion() + "." + + meta.getDatabaseMinorVersion(); + assertTrue(meta.getDatabaseProductVersion().startsWith(versionStart)); + assertEquals(meta.getDatabaseMajorVersion(), + meta.getDriverMajorVersion()); + assertEquals(meta.getDatabaseMinorVersion(), + meta.getDriverMinorVersion()); + int majorVersion = 4; + assertEquals(majorVersion, meta.getJDBCMajorVersion()); + assertEquals(2, meta.getJDBCMinorVersion()); + assertEquals("H2", meta.getDatabaseProductName()); + assertEquals(Connection.TRANSACTION_READ_COMMITTED, + meta.getDefaultTransactionIsolation()); + assertEquals("H2 JDBC Driver", meta.getDriverName()); + + versionStart = meta.getDriverMajorVersion() + "." + + meta.getDriverMinorVersion(); + assertTrue(meta.getDriverVersion().startsWith(versionStart)); + assertEquals("", meta.getExtraNameCharacters()); + assertEquals("\"", meta.getIdentifierQuoteString()); + assertEquals(0, meta.getMaxBinaryLiteralLength()); + assertEquals(0, meta.getMaxCatalogNameLength()); + assertEquals(0, meta.getMaxCharLiteralLength()); + assertEquals(0, meta.getMaxColumnNameLength()); + assertEquals(0, meta.getMaxColumnsInGroupBy()); + assertEquals(0, meta.getMaxColumnsInIndex()); + assertEquals(0, meta.getMaxColumnsInOrderBy()); + assertEquals(0, meta.getMaxColumnsInSelect()); + assertEquals(0, meta.getMaxColumnsInTable()); + assertEquals(0, meta.getMaxConnections()); + assertEquals(0, meta.getMaxCursorNameLength()); + assertEquals(0, meta.getMaxIndexLength()); + assertEquals(0, meta.getMaxProcedureNameLength()); + assertEquals(0, meta.getMaxRowSize()); + assertEquals(0, meta.getMaxSchemaNameLength()); + assertEquals(0, meta.getMaxStatementLength()); + assertEquals(0, meta.getMaxStatements()); + assertEquals(0, meta.getMaxTableNameLength()); + assertEquals(0, meta.getMaxTablesInSelect()); + assertEquals(0, meta.getMaxUserNameLength()); + assertEquals("procedure", meta.getProcedureTerm()); + + assertEquals(ResultSet.CLOSE_CURSORS_AT_COMMIT, + meta.getResultSetHoldability()); + assertEquals(DatabaseMetaData.sqlStateSQL, meta.getSQLStateType()); + assertFalse(meta.locatorsUpdateCopy()); + + assertEquals("schema", meta.getSchemaTerm()); + assertEquals("\\", meta.getSearchStringEscape()); + + assertTrue(meta.getURL().startsWith("jdbc:h2:")); + assertTrue(meta.getUserName().length() > 1); + assertFalse(meta.insertsAreDetected( + ResultSet.TYPE_FORWARD_ONLY)); + assertFalse(meta.insertsAreDetected( + ResultSet.TYPE_SCROLL_INSENSITIVE)); + assertFalse(meta.insertsAreDetected( + ResultSet.TYPE_SCROLL_SENSITIVE)); + assertTrue(meta.isCatalogAtStart()); + assertFalse(meta.isReadOnly()); + assertTrue(meta.nullPlusNonNullIsNull()); + assertFalse(meta.othersDeletesAreVisible( + ResultSet.TYPE_FORWARD_ONLY)); + assertFalse(meta.othersDeletesAreVisible( + ResultSet.TYPE_SCROLL_INSENSITIVE)); + assertFalse(meta.othersDeletesAreVisible( + ResultSet.TYPE_SCROLL_SENSITIVE)); + assertFalse(meta.othersInsertsAreVisible( + ResultSet.TYPE_FORWARD_ONLY)); + assertFalse(meta.othersInsertsAreVisible( + ResultSet.TYPE_SCROLL_INSENSITIVE)); + assertFalse(meta.othersInsertsAreVisible( + ResultSet.TYPE_SCROLL_SENSITIVE)); + assertFalse(meta.othersUpdatesAreVisible( + ResultSet.TYPE_FORWARD_ONLY)); + assertFalse(meta.othersUpdatesAreVisible( + ResultSet.TYPE_SCROLL_INSENSITIVE)); + assertFalse(meta.othersUpdatesAreVisible( + ResultSet.TYPE_SCROLL_SENSITIVE)); + assertFalse(meta.ownDeletesAreVisible( + ResultSet.TYPE_FORWARD_ONLY)); + assertFalse(meta.ownDeletesAreVisible( + ResultSet.TYPE_SCROLL_INSENSITIVE)); + assertFalse(meta.ownDeletesAreVisible( + ResultSet.TYPE_SCROLL_SENSITIVE)); + assertFalse(meta.ownInsertsAreVisible( + ResultSet.TYPE_FORWARD_ONLY)); + assertFalse(meta.ownInsertsAreVisible( + ResultSet.TYPE_SCROLL_INSENSITIVE)); + assertFalse(meta.ownInsertsAreVisible( + ResultSet.TYPE_SCROLL_SENSITIVE)); + assertTrue(meta.ownUpdatesAreVisible( + ResultSet.TYPE_FORWARD_ONLY)); + assertTrue(meta.ownUpdatesAreVisible( + ResultSet.TYPE_SCROLL_INSENSITIVE)); + assertTrue(meta.ownUpdatesAreVisible( + ResultSet.TYPE_SCROLL_SENSITIVE)); + assertFalse(meta.storesLowerCaseIdentifiers()); + assertFalse(meta.storesLowerCaseQuotedIdentifiers()); + assertFalse(meta.storesMixedCaseIdentifiers()); + assertFalse(meta.storesMixedCaseQuotedIdentifiers()); + assertTrue(meta.storesUpperCaseIdentifiers()); + assertFalse(meta.storesUpperCaseQuotedIdentifiers()); + assertTrue(meta.supportsAlterTableWithAddColumn()); + assertTrue(meta.supportsAlterTableWithDropColumn()); + assertTrue(meta.supportsANSI92EntryLevelSQL()); + assertFalse(meta.supportsANSI92IntermediateSQL()); + assertFalse(meta.supportsANSI92FullSQL()); + assertTrue(meta.supportsBatchUpdates()); + assertTrue(meta.supportsCatalogsInDataManipulation()); + assertTrue(meta.supportsCatalogsInIndexDefinitions()); + assertTrue(meta.supportsCatalogsInPrivilegeDefinitions()); + assertFalse(meta.supportsCatalogsInProcedureCalls()); + assertTrue(meta.supportsCatalogsInTableDefinitions()); + assertTrue(meta.supportsColumnAliasing()); + assertTrue(meta.supportsConvert()); + assertTrue(meta.supportsConvert(Types.INTEGER, Types.VARCHAR)); + assertTrue(meta.supportsCoreSQLGrammar()); + assertTrue(meta.supportsCorrelatedSubqueries()); + assertFalse(meta.supportsDataDefinitionAndDataManipulationTransactions()); + assertTrue(meta.supportsDataManipulationTransactionsOnly()); + assertFalse(meta.supportsDifferentTableCorrelationNames()); + assertTrue(meta.supportsExpressionsInOrderBy()); + assertFalse(meta.supportsExtendedSQLGrammar()); + assertFalse(meta.supportsFullOuterJoins()); + + assertTrue(meta.supportsGetGeneratedKeys()); + assertFalse(meta.supportsMultipleOpenResults()); + assertFalse(meta.supportsNamedParameters()); + + assertTrue(meta.supportsGroupBy()); + assertTrue(meta.supportsGroupByBeyondSelect()); + assertTrue(meta.supportsGroupByUnrelated()); + assertTrue(meta.supportsIntegrityEnhancementFacility()); + assertTrue(meta.supportsLikeEscapeClause()); + assertTrue(meta.supportsLimitedOuterJoins()); + assertTrue(meta.supportsMinimumSQLGrammar()); + assertFalse(meta.supportsMixedCaseIdentifiers()); + assertTrue(meta.supportsMixedCaseQuotedIdentifiers()); + assertFalse(meta.supportsMultipleResultSets()); + assertTrue(meta.supportsMultipleTransactions()); + assertTrue(meta.supportsNonNullableColumns()); + assertFalse(meta.supportsOpenCursorsAcrossCommit()); + assertFalse(meta.supportsOpenCursorsAcrossRollback()); + assertTrue(meta.supportsOpenStatementsAcrossCommit()); + assertTrue(meta.supportsOpenStatementsAcrossRollback()); + assertTrue(meta.supportsOrderByUnrelated()); + assertTrue(meta.supportsOuterJoins()); + assertFalse(meta.supportsPositionedDelete()); + assertFalse(meta.supportsPositionedUpdate()); + assertTrue(meta.supportsResultSetConcurrency( + ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)); + assertTrue(meta.supportsResultSetConcurrency( + ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE)); + assertTrue(meta.supportsResultSetConcurrency( + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)); + assertTrue(meta.supportsResultSetConcurrency( + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE)); + assertFalse(meta.supportsResultSetConcurrency( + ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY)); + assertFalse(meta.supportsResultSetConcurrency( + ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE)); + + assertFalse(meta.supportsResultSetHoldability( + ResultSet.HOLD_CURSORS_OVER_COMMIT)); + assertTrue(meta.supportsResultSetHoldability( + ResultSet.CLOSE_CURSORS_AT_COMMIT)); + assertTrue(meta.supportsSavepoints()); + assertFalse(meta.supportsStatementPooling()); + + assertTrue(meta.supportsResultSetType( + ResultSet.TYPE_FORWARD_ONLY)); + assertTrue(meta.supportsResultSetType( + ResultSet.TYPE_SCROLL_INSENSITIVE)); + assertFalse(meta.supportsResultSetType( + ResultSet.TYPE_SCROLL_SENSITIVE)); + assertTrue(meta.supportsSchemasInDataManipulation()); + assertTrue(meta.supportsSchemasInIndexDefinitions()); + assertTrue(meta.supportsSchemasInPrivilegeDefinitions()); + assertTrue(meta.supportsSchemasInProcedureCalls()); + assertTrue(meta.supportsSchemasInTableDefinitions()); + assertTrue(meta.supportsSelectForUpdate()); + assertFalse(meta.supportsStoredProcedures()); + assertTrue(meta.supportsSubqueriesInComparisons()); + assertTrue(meta.supportsSubqueriesInExists()); + assertTrue(meta.supportsSubqueriesInIns()); + assertTrue(meta.supportsSubqueriesInQuantifieds()); + assertTrue(meta.supportsTableCorrelationNames()); + assertTrue(meta.supportsTransactions()); + assertFalse(meta.supportsTransactionIsolationLevel(Connection.TRANSACTION_NONE)); + assertTrue(meta.supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_COMMITTED)); + assertTrue(meta.supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_UNCOMMITTED)); + assertTrue(meta.supportsTransactionIsolationLevel(Connection.TRANSACTION_REPEATABLE_READ)); + assertTrue(meta.supportsTransactionIsolationLevel(Constants.TRANSACTION_SNAPSHOT)); + assertTrue(meta.supportsTransactionIsolationLevel(Connection.TRANSACTION_SERIALIZABLE)); + assertTrue(meta.supportsUnion()); + assertTrue(meta.supportsUnionAll()); + assertFalse(meta.updatesAreDetected(ResultSet.TYPE_FORWARD_ONLY)); + assertFalse(meta.updatesAreDetected(ResultSet.TYPE_SCROLL_INSENSITIVE)); + assertFalse(meta.updatesAreDetected(ResultSet.TYPE_SCROLL_SENSITIVE)); + assertFalse(meta.usesLocalFilePerTable()); + assertTrue(meta.usesLocalFiles()); + conn.close(); + } + + private void testNullsAreSortedAt() throws SQLException { + Connection conn = getConnection("metaData"); + Statement stat = conn.createStatement(); + DatabaseMetaData meta = conn.getMetaData(); + testNullsAreSortedAt(meta, DefaultNullOrdering.LOW); + stat.execute("SET DEFAULT_NULL_ORDERING LOW"); + testNullsAreSortedAt(meta, DefaultNullOrdering.LOW); + stat.execute("SET DEFAULT_NULL_ORDERING HIGH"); + testNullsAreSortedAt(meta, DefaultNullOrdering.HIGH); + stat.execute("SET DEFAULT_NULL_ORDERING FIRST"); + testNullsAreSortedAt(meta, DefaultNullOrdering.FIRST); + stat.execute("SET DEFAULT_NULL_ORDERING LAST"); + testNullsAreSortedAt(meta, DefaultNullOrdering.LAST); + stat.execute("SET DEFAULT_NULL_ORDERING LOW"); + conn.close(); + } + + private void testNullsAreSortedAt(DatabaseMetaData meta, DefaultNullOrdering ordering) throws SQLException { + assertEquals(ordering == DefaultNullOrdering.HIGH, meta.nullsAreSortedHigh()); + assertEquals(ordering == DefaultNullOrdering.LOW, meta.nullsAreSortedLow()); + assertEquals(ordering == DefaultNullOrdering.FIRST, meta.nullsAreSortedAtStart()); + assertEquals(ordering == DefaultNullOrdering.LAST, meta.nullsAreSortedAtEnd()); + } + + private void testMore() throws SQLException { + Connection conn = getConnection("metaData"); + DatabaseMetaData meta = conn.getMetaData(); + Statement stat = conn.createStatement(); + ResultSet rs; + + conn.setReadOnly(true); + conn.setReadOnly(false); + assertFalse(conn.isReadOnly()); + assertTrue(conn.isReadOnly() == meta.isReadOnly()); + + assertTrue(conn == meta.getConnection()); + + // currently, setCatalog is ignored + conn.setCatalog("XYZ"); + trace(conn.getCatalog()); + + String product = meta.getDatabaseProductName(); + trace("meta.getDatabaseProductName:" + product); + + String version = meta.getDatabaseProductVersion(); + trace("meta.getDatabaseProductVersion:" + version); + + int major = meta.getDriverMajorVersion(); + trace("meta.getDriverMajorVersion:" + major); + + int minor = meta.getDriverMinorVersion(); + trace("meta.getDriverMinorVersion:" + minor); + + String driverName = meta.getDriverName(); + trace("meta.getDriverName:" + driverName); + + String driverVersion = meta.getDriverVersion(); + trace("meta.getDriverVersion:" + driverVersion); + + meta.getSearchStringEscape(); + + String url = meta.getURL(); + trace("meta.getURL:" + url); + + String user = meta.getUserName(); + trace("meta.getUserName:" + user); + + trace("meta.nullsAreSortedHigh:" + meta.nullsAreSortedHigh()); + trace("meta.nullsAreSortedLow:" + meta.nullsAreSortedLow()); + trace("meta.nullsAreSortedAtStart:" + meta.nullsAreSortedAtStart()); + trace("meta.nullsAreSortedAtEnd:" + meta.nullsAreSortedAtEnd()); + int count = (meta.nullsAreSortedHigh() ? 1 : 0) + + (meta.nullsAreSortedLow() ? 1 : 0) + + (meta.nullsAreSortedAtStart() ? 1 : 0) + + (meta.nullsAreSortedAtEnd() ? 1 : 0); + assertTrue(count == 1); + + trace("meta.allProceduresAreCallable:" + + meta.allProceduresAreCallable()); + assertTrue(meta.allProceduresAreCallable()); + + trace("meta.allTablesAreSelectable:" + meta.allTablesAreSelectable()); + assertTrue(meta.allTablesAreSelectable()); + + trace("getTables"); + rs = meta.getTables(null, Constants.SCHEMA_MAIN, null, + new String[] { "TABLE" }); + assertResultSetMeta(rs, 10, new String[] { "TABLE_CAT", "TABLE_SCHEM", + "TABLE_NAME", "TABLE_TYPE", "REMARKS", "TYPE_CAT", + "TYPE_SCHEM", "TYPE_NAME", "SELF_REFERENCING_COL_NAME", + "REF_GENERATION" }, new int[] { Types.VARCHAR, + Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, + Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, + Types.VARCHAR }, null, null); + if (rs.next()) { + fail("Database is not empty after dropping all tables"); + } + stat.executeUpdate("CREATE TABLE TEST(" + "ID INT PRIMARY KEY," + + "TEXT_V VARCHAR(120)," + "DEC_V DECIMAL(12,3)," + "NUM_V NUMERIC(12,3)," + + "DATE_V DATETIME," + "BLOB_V BLOB," + "CLOB_V CLOB" + ")"); + rs = meta.getTables(null, Constants.SCHEMA_MAIN, null, + new String[] { "TABLE" }); + assertResultSetOrdered(rs, new String[][] { { CATALOG, + Constants.SCHEMA_MAIN, "TEST", "BASE TABLE" } }); + trace("getColumns"); + rs = meta.getColumns(null, null, "TEST", null); + assertResultSetMeta(rs, 24, new String[] { "TABLE_CAT", "TABLE_SCHEM", + "TABLE_NAME", "COLUMN_NAME", "DATA_TYPE", "TYPE_NAME", + "COLUMN_SIZE", "BUFFER_LENGTH", "DECIMAL_DIGITS", + "NUM_PREC_RADIX", "NULLABLE", "REMARKS", "COLUMN_DEF", + "SQL_DATA_TYPE", "SQL_DATETIME_SUB", "CHAR_OCTET_LENGTH", + "ORDINAL_POSITION", "IS_NULLABLE", "SCOPE_CATALOG", + "SCOPE_SCHEMA", "SCOPE_TABLE", "SOURCE_DATA_TYPE", + "IS_AUTOINCREMENT", "IS_GENERATEDCOLUMN" }, new int[] { + Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, + Types.INTEGER, Types.VARCHAR, Types.INTEGER, Types.INTEGER, + Types.INTEGER, Types.INTEGER, Types.INTEGER, Types.VARCHAR, + Types.VARCHAR, Types.INTEGER, Types.INTEGER, Types.INTEGER, + Types.INTEGER, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, + Types.VARCHAR, Types.SMALLINT, Types.VARCHAR, Types.VARCHAR }, + null, null); + assertResultSetOrdered(rs, new String[][] { + { CATALOG, Constants.SCHEMA_MAIN, "TEST", "ID", + "" + Types.INTEGER, "INTEGER", "32", null, "0", "2", + "" + DatabaseMetaData.columnNoNulls, null, null, + null, null, "32", "1", "NO" }, + { CATALOG, Constants.SCHEMA_MAIN, "TEST", "TEXT_V", + "" + Types.VARCHAR, "CHARACTER VARYING", "120", null, "0", null, + "" + DatabaseMetaData.columnNullable, null, null, + null, null, "120", "2", "YES" }, + { CATALOG, Constants.SCHEMA_MAIN, "TEST", "DEC_V", + "" + Types.DECIMAL, "DECIMAL", "12", null, "3", "10", + "" + DatabaseMetaData.columnNullable, null, null, + null, null, "12", "3", "YES" }, + { CATALOG, Constants.SCHEMA_MAIN, "TEST", "NUM_V", + "" + Types.NUMERIC, "NUMERIC", "12", null, "3", "10", + "" + DatabaseMetaData.columnNullable, null, null, + null, null, "12", "4", "YES" }, + { CATALOG, Constants.SCHEMA_MAIN, "TEST", "DATE_V", + "" + Types.TIMESTAMP, "TIMESTAMP", "26", null, "6", null, + "" + DatabaseMetaData.columnNullable, null, null, + null, null, "26", "5", "YES" }, + { CATALOG, Constants.SCHEMA_MAIN, "TEST", "BLOB_V", + "" + Types.BLOB, "BINARY LARGE OBJECT", "" + Integer.MAX_VALUE, null, "0", null, + "" + DatabaseMetaData.columnNullable, null, null, + null, null, "" + Integer.MAX_VALUE, "6", + "YES" }, + { CATALOG, Constants.SCHEMA_MAIN, "TEST", "CLOB_V", + "" + Types.CLOB, "CHARACTER LARGE OBJECT", "" + Integer.MAX_VALUE, null, "0", null, + "" + DatabaseMetaData.columnNullable, null, null, + null, null, "" + Integer.MAX_VALUE, "7", + "YES" } }); + /* + * rs=meta.getColumns(null,null,"TEST",null); while(rs.next()) { int + * datatype=rs.getInt(5); } + */ + trace("getIndexInfo"); + stat.executeUpdate("CREATE INDEX IDX_TEXT_DEC ON TEST(TEXT_V,DEC_V)"); + stat.executeUpdate("CREATE UNIQUE INDEX IDX_DATE ON TEST(DATE_V)"); + rs = meta.getIndexInfo(null, null, "TEST", false, false); + assertResultSetMeta(rs, 13, new String[] { "TABLE_CAT", "TABLE_SCHEM", + "TABLE_NAME", "NON_UNIQUE", "INDEX_QUALIFIER", "INDEX_NAME", + "TYPE", "ORDINAL_POSITION", "COLUMN_NAME", "ASC_OR_DESC", + "CARDINALITY", "PAGES", "FILTER_CONDITION" }, + new int[] { Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, + Types.BOOLEAN, Types.VARCHAR, Types.VARCHAR, + Types.SMALLINT, Types.SMALLINT, Types.VARCHAR, + Types.VARCHAR, Types.BIGINT, Types.BIGINT, + Types.VARCHAR }, null, null); + assertResultSetOrdered(rs, new String[][] { + { CATALOG, Constants.SCHEMA_MAIN, "TEST", "FALSE", CATALOG, + "IDX_DATE", "" + DatabaseMetaData.tableIndexOther, "1", + "DATE_V", "A", "0", "0" }, + { CATALOG, Constants.SCHEMA_MAIN, "TEST", "FALSE", CATALOG, + "PRIMARY_KEY_2", "" + DatabaseMetaData.tableIndexOther, + "1", "ID", "A", "0", "0" }, + { CATALOG, Constants.SCHEMA_MAIN, "TEST", "TRUE", CATALOG, + "IDX_TEXT_DEC", "" + DatabaseMetaData.tableIndexOther, + "1", "TEXT_V", "A", "0", "0" }, + { CATALOG, Constants.SCHEMA_MAIN, "TEST", "TRUE", CATALOG, + "IDX_TEXT_DEC", "" + DatabaseMetaData.tableIndexOther, + "2", "DEC_V", "A", "0", "0" }, }, + new int[] { 11 }); + stat.executeUpdate("DROP INDEX IDX_TEXT_DEC"); + stat.executeUpdate("DROP INDEX IDX_DATE"); + rs = meta.getIndexInfo(null, null, "TEST", false, false); + assertResultSetMeta(rs, 13, new String[] { "TABLE_CAT", "TABLE_SCHEM", + "TABLE_NAME", "NON_UNIQUE", "INDEX_QUALIFIER", "INDEX_NAME", + "TYPE", "ORDINAL_POSITION", "COLUMN_NAME", "ASC_OR_DESC", + "CARDINALITY", "PAGES", "FILTER_CONDITION" }, + new int[] { Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, + Types.BOOLEAN, Types.VARCHAR, Types.VARCHAR, + Types.SMALLINT, Types.SMALLINT, Types.VARCHAR, + Types.VARCHAR, Types.BIGINT, Types.BIGINT, + Types.VARCHAR }, null, null); + assertResultSetOrdered(rs, new String[][] { { CATALOG, + Constants.SCHEMA_MAIN, "TEST", "FALSE", CATALOG, + "PRIMARY_KEY_2", "" + DatabaseMetaData.tableIndexOther, "1", + "ID", "A", "0", "0" } }, + new int[] { 11 }); + trace("getPrimaryKeys"); + rs = meta.getPrimaryKeys(null, null, "TEST"); + assertResultSetMeta(rs, 6, new String[] { "TABLE_CAT", "TABLE_SCHEM", + "TABLE_NAME", "COLUMN_NAME", "KEY_SEQ", "PK_NAME" }, new int[] { + Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, + Types.SMALLINT, Types.VARCHAR }, null, null); + assertResultSetOrdered(rs, new String[][] { { CATALOG, + Constants.SCHEMA_MAIN, "TEST", "ID", "1", "CONSTRAINT_2" }, }); + trace("getTables - using a wildcard"); + stat.executeUpdate( + "CREATE TABLE T_2(B INT,A VARCHAR(6),C INT,PRIMARY KEY(C,A,B))"); + stat.executeUpdate( + "CREATE TABLE TX2(B INT,A VARCHAR(6),C INT,PRIMARY KEY(C,A,B))"); + rs = meta.getTables(null, null, "T_2", null); + assertResultSetOrdered(rs, new String[][] { + { CATALOG, Constants.SCHEMA_MAIN, "TX2", "BASE TABLE" }, + { CATALOG, Constants.SCHEMA_MAIN, "T_2", "BASE TABLE" } }); + trace("getTables - using a quoted _ character"); + rs = meta.getTables(null, null, "T\\_2", null); + assertResultSetOrdered(rs, new String[][] { { CATALOG, + Constants.SCHEMA_MAIN, "T_2", "BASE TABLE" } }); + trace("getTables - using the % wildcard"); + rs = meta.getTables(null, Constants.SCHEMA_MAIN, "%", + new String[] { "TABLE" }); + assertResultSetOrdered(rs, new String[][] { + { CATALOG, Constants.SCHEMA_MAIN, "TEST", "BASE TABLE" }, + { CATALOG, Constants.SCHEMA_MAIN, "TX2", "BASE TABLE" }, + { CATALOG, Constants.SCHEMA_MAIN, "T_2", "BASE TABLE" } }); + stat.execute("DROP TABLE TEST"); + + trace("getColumns - using wildcards"); + rs = meta.getColumns(null, null, "___", "B%"); + assertResultSetOrdered(rs, new String[][] { + { CATALOG, Constants.SCHEMA_MAIN, "TX2", "B", + "" + Types.INTEGER, "INTEGER", "32" }, + { CATALOG, Constants.SCHEMA_MAIN, "T_2", "B", + "" + Types.INTEGER, "INTEGER", "32" }, }); + trace("getColumns - using wildcards"); + rs = meta.getColumns(null, null, "_\\__", "%"); + assertResultSetOrdered(rs, new String[][] { + { CATALOG, Constants.SCHEMA_MAIN, "T_2", "B", + "" + Types.INTEGER, "INTEGER", "32" }, + { CATALOG, Constants.SCHEMA_MAIN, "T_2", "A", + "" + Types.VARCHAR, "CHARACTER VARYING", "6" }, + { CATALOG, Constants.SCHEMA_MAIN, "T_2", "C", + "" + Types.INTEGER, "INTEGER", "32" }, }); + trace("getIndexInfo"); + stat.executeUpdate("CREATE UNIQUE INDEX A_INDEX ON TX2(B,C,A)"); + stat.executeUpdate("CREATE INDEX B_INDEX ON TX2(A,B,C)"); + rs = meta.getIndexInfo(null, null, "TX2", false, false); + assertResultSetOrdered(rs, new String[][] { + { CATALOG, Constants.SCHEMA_MAIN, "TX2", "FALSE", CATALOG, + "A_INDEX", "" + DatabaseMetaData.tableIndexOther, "1", + "B", "A" }, + { CATALOG, Constants.SCHEMA_MAIN, "TX2", "FALSE", CATALOG, + "A_INDEX", "" + DatabaseMetaData.tableIndexOther, "2", + "C", "A" }, + { CATALOG, Constants.SCHEMA_MAIN, "TX2", "FALSE", CATALOG, + "A_INDEX", "" + DatabaseMetaData.tableIndexOther, "3", + "A", "A" }, + { CATALOG, Constants.SCHEMA_MAIN, "TX2", "FALSE", CATALOG, + "PRIMARY_KEY_14", + "" + DatabaseMetaData.tableIndexOther, "1", "C", "A" }, + { CATALOG, Constants.SCHEMA_MAIN, "TX2", "FALSE", CATALOG, + "PRIMARY_KEY_14", + "" + DatabaseMetaData.tableIndexOther, "2", "A", "A" }, + { CATALOG, Constants.SCHEMA_MAIN, "TX2", "FALSE", CATALOG, + "PRIMARY_KEY_14", + "" + DatabaseMetaData.tableIndexOther, "3", "B", "A" }, + { CATALOG, Constants.SCHEMA_MAIN, "TX2", "TRUE", CATALOG, + "B_INDEX", "" + DatabaseMetaData.tableIndexOther, "1", + "A", "A" }, + { CATALOG, Constants.SCHEMA_MAIN, "TX2", "TRUE", CATALOG, + "B_INDEX", "" + DatabaseMetaData.tableIndexOther, "2", + "B", "A" }, + { CATALOG, Constants.SCHEMA_MAIN, "TX2", "TRUE", CATALOG, + "B_INDEX", "" + DatabaseMetaData.tableIndexOther, "3", + "C", "A" }, }, + new int[] { 11 }); + trace("getPrimaryKeys"); + rs = meta.getPrimaryKeys(null, null, "T_2"); + assertResultSetOrdered(rs, new String[][] { + { CATALOG, Constants.SCHEMA_MAIN, "T_2", "A", "2", "CONSTRAINT_1" }, + { CATALOG, Constants.SCHEMA_MAIN, "T_2", "B", "3", "CONSTRAINT_1" }, + { CATALOG, Constants.SCHEMA_MAIN, "T_2", "C", "1", "CONSTRAINT_1" }, }); + stat.executeUpdate("DROP TABLE TX2"); + stat.executeUpdate("DROP TABLE T_2"); + stat.executeUpdate("CREATE TABLE PARENT(ID INT PRIMARY KEY)"); + stat.executeUpdate("CREATE TABLE CHILD(P_ID INT,ID INT," + + "PRIMARY KEY(P_ID,ID),FOREIGN KEY(P_ID) REFERENCES PARENT(ID))"); + + trace("getImportedKeys"); + rs = meta.getImportedKeys(null, null, "CHILD"); + assertResultSetMeta(rs, 14, new String[] { "PKTABLE_CAT", + "PKTABLE_SCHEM", "PKTABLE_NAME", "PKCOLUMN_NAME", + "FKTABLE_CAT", "FKTABLE_SCHEM", "FKTABLE_NAME", + "FKCOLUMN_NAME", "KEY_SEQ", "UPDATE_RULE", "DELETE_RULE", + "FK_NAME", "PK_NAME", "DEFERRABILITY" }, new int[] { + Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, + Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, + Types.SMALLINT, Types.SMALLINT, Types.SMALLINT, Types.VARCHAR, + Types.VARCHAR, Types.SMALLINT }, null, null); + // TODO test + // testResultSetOrdered(rs, new String[][] { { null, null, "PARENT", + // "ID", + // null, null, "CHILD", "P_ID", "1", + // "" + DatabaseMetaData.importedKeyNoAction, + // "" + DatabaseMetaData.importedKeyNoAction, "FK_1", null, + // "" + DatabaseMetaData.importedKeyNotDeferrable}}); + + trace("getExportedKeys"); + rs = meta.getExportedKeys(null, null, "PARENT"); + assertResultSetMeta(rs, 14, new String[] { "PKTABLE_CAT", + "PKTABLE_SCHEM", "PKTABLE_NAME", "PKCOLUMN_NAME", + "FKTABLE_CAT", "FKTABLE_SCHEM", "FKTABLE_NAME", + "FKCOLUMN_NAME", "KEY_SEQ", "UPDATE_RULE", "DELETE_RULE", + "FK_NAME", "PK_NAME", "DEFERRABILITY" }, new int[] { + Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, + Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, + Types.SMALLINT, Types.SMALLINT, Types.SMALLINT, Types.VARCHAR, + Types.VARCHAR, Types.SMALLINT }, null, null); + // TODO test + /* + * testResultSetOrdered(rs, new String[][]{ { null,null,"PARENT","ID", + * null,null,"CHILD","P_ID", + * "1",""+DatabaseMetaData.importedKeyNoAction, + * ""+DatabaseMetaData.importedKeyNoAction, + * null,null,""+DatabaseMetaData.importedKeyNotDeferrable } } ); + */ + trace("getCrossReference"); + rs = meta.getCrossReference(null, null, "PARENT", null, null, "CHILD"); + assertResultSetMeta(rs, 14, new String[] { "PKTABLE_CAT", + "PKTABLE_SCHEM", "PKTABLE_NAME", "PKCOLUMN_NAME", + "FKTABLE_CAT", "FKTABLE_SCHEM", "FKTABLE_NAME", + "FKCOLUMN_NAME", "KEY_SEQ", "UPDATE_RULE", "DELETE_RULE", + "FK_NAME", "PK_NAME", "DEFERRABILITY" }, new int[] { + Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, + Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, + Types.SMALLINT, Types.SMALLINT, Types.SMALLINT, Types.VARCHAR, + Types.VARCHAR, Types.SMALLINT }, null, null); + // TODO test + /* + * testResultSetOrdered(rs, new String[][]{ { null,null,"PARENT","ID", + * null,null,"CHILD","P_ID", + * "1",""+DatabaseMetaData.importedKeyNoAction, + * ""+DatabaseMetaData.importedKeyNoAction, + * null,null,""+DatabaseMetaData.importedKeyNotDeferrable } } ); + */ + + rs = meta.getSchemas(); + assertResultSetMeta(rs, 2, new String[] { "TABLE_SCHEM", "TABLE_CATALOG" }, + new int[] { Types.VARCHAR, Types.VARCHAR }, null, null); + assertTrue(rs.next()); + assertEquals("INFORMATION_SCHEMA", rs.getString(1)); + assertTrue(rs.next()); + assertEquals("PUBLIC", rs.getString(1)); + assertFalse(rs.next()); + + rs = meta.getSchemas(null, null); + assertResultSetMeta(rs, 2, new String[] { "TABLE_SCHEM", "TABLE_CATALOG" }, + new int[] { Types.VARCHAR, Types.VARCHAR }, null, null); + assertTrue(rs.next()); + assertEquals("INFORMATION_SCHEMA", rs.getString(1)); + assertTrue(rs.next()); + assertEquals("PUBLIC", rs.getString(1)); + assertFalse(rs.next()); + + rs = meta.getCatalogs(); + assertResultSetMeta(rs, 1, new String[] { "TABLE_CAT" }, + new int[] { Types.VARCHAR }, null, null); + assertResultSetOrdered(rs, new String[][] { { CATALOG } }); + + rs = meta.getTableTypes(); + assertResultSetMeta(rs, 1, new String[] { "TABLE_TYPE" }, + new int[] { Types.VARCHAR }, null, null); + assertResultSetOrdered(rs, new String[][] { + { "BASE TABLE" }, { "GLOBAL TEMPORARY" }, + { "LOCAL TEMPORARY" }, { "SYNONYM" }, { "VIEW" } }); + + rs = meta.getTypeInfo(); + assertResultSetMeta(rs, 18, new String[] { "TYPE_NAME", "DATA_TYPE", + "PRECISION", "LITERAL_PREFIX", "LITERAL_SUFFIX", + "CREATE_PARAMS", "NULLABLE", "CASE_SENSITIVE", "SEARCHABLE", + "UNSIGNED_ATTRIBUTE", "FIXED_PREC_SCALE", "AUTO_INCREMENT", + "LOCAL_TYPE_NAME", "MINIMUM_SCALE", "MAXIMUM_SCALE", + "SQL_DATA_TYPE", "SQL_DATETIME_SUB", "NUM_PREC_RADIX" }, + new int[] { Types.VARCHAR, Types.INTEGER, Types.INTEGER, + Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, + Types.SMALLINT, Types.BOOLEAN, Types.SMALLINT, + Types.BOOLEAN, Types.BOOLEAN, Types.BOOLEAN, + Types.VARCHAR, Types.SMALLINT, Types.SMALLINT, + Types.INTEGER, Types.INTEGER, Types.INTEGER }, null, + null); + + rs = meta.getTablePrivileges(null, null, null); + assertResultSetMeta(rs, 7, + new String[] { "TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", + "GRANTOR", "GRANTEE", "PRIVILEGE", "IS_GRANTABLE" }, + new int[] { Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, + Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, + Types.VARCHAR, Types.VARCHAR }, null, null); + + rs = meta.getColumnPrivileges(null, null, "TEST", null); + assertResultSetMeta(rs, 8, new String[] { "TABLE_CAT", "TABLE_SCHEM", + "TABLE_NAME", "COLUMN_NAME", "GRANTOR", "GRANTEE", "PRIVILEGE", + "IS_GRANTABLE" }, new int[] { Types.VARCHAR, Types.VARCHAR, + Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, + Types.VARCHAR, Types.VARCHAR, Types.VARCHAR }, null, null); + + assertNull(conn.getWarnings()); + conn.clearWarnings(); + assertNull(conn.getWarnings()); + conn.close(); + } + + private void testGeneral() throws SQLException { + Connection conn = getConnection("metaData"); + DatabaseMetaData meta = conn.getMetaData(); + + Statement stat = conn.createStatement(); + + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + stat.execute("CREATE INDEX IDXNAME ON TEST(NAME)"); + + ResultSet rs; + + rs = meta.getCatalogs(); + rs.next(); + assertEquals(CATALOG, rs.getString(1)); + assertFalse(rs.next()); + + rs = meta.getSchemas(); + rs.next(); + assertEquals("INFORMATION_SCHEMA", rs.getString("TABLE_SCHEM")); + rs.next(); + assertEquals("PUBLIC", rs.getString("TABLE_SCHEM")); + assertFalse(rs.next()); + + rs = meta.getSchemas(null, null); + rs.next(); + assertEquals("INFORMATION_SCHEMA", rs.getString("TABLE_SCHEM")); + rs.next(); + assertEquals("PUBLIC", rs.getString("TABLE_SCHEM")); + assertFalse(rs.next()); + + rs = meta.getSchemas(null, "PUBLIC"); + rs.next(); + assertEquals("PUBLIC", rs.getString("TABLE_SCHEM")); + assertFalse(rs.next()); + + rs = meta.getTableTypes(); + rs.next(); + assertEquals("BASE TABLE", rs.getString("TABLE_TYPE")); + rs.next(); + assertEquals("GLOBAL TEMPORARY", rs.getString("TABLE_TYPE")); + rs.next(); + assertEquals("LOCAL TEMPORARY", rs.getString("TABLE_TYPE")); + rs.next(); + assertEquals("SYNONYM", rs.getString("TABLE_TYPE")); + rs.next(); + assertEquals("VIEW", rs.getString("TABLE_TYPE")); + assertFalse(rs.next()); + + rs = meta.getTables(null, Constants.SCHEMA_MAIN, + null, new String[] { "TABLE" }); + assertNull(rs.getStatement()); + rs.next(); + assertEquals("TEST", rs.getString("TABLE_NAME")); + assertFalse(rs.next()); + + rs = meta.getTables(null, "INFORMATION_SCHEMA", null, new String[] { "BASE TABLE", "VIEW" }); + for (String name : new String[] { "CONSTANTS", "ENUM_VALUES", + "INDEXES", "INDEX_COLUMNS", "INFORMATION_SCHEMA_CATALOG_NAME", "IN_DOUBT", "LOCKS", + "QUERY_STATISTICS", "RIGHTS", "ROLES", "SESSIONS", "SESSION_STATE", "SETTINGS", "SYNONYMS", + "USERS", "CHECK_CONSTRAINTS", "COLLATIONS", "COLUMNS", "COLUMN_PRIVILEGES", + "CONSTRAINT_COLUMN_USAGE", "DOMAINS", "DOMAIN_CONSTRAINTS", "ELEMENT_TYPES", "FIELDS", + "KEY_COLUMN_USAGE", "PARAMETERS", + "REFERENTIAL_CONSTRAINTS", "ROUTINES", "SCHEMATA", "SEQUENCES", "TABLES", "TABLE_CONSTRAINTS", + "TABLE_PRIVILEGES", "TRIGGERS", "VIEWS" }) { + rs.next(); + assertEquals(name, rs.getString("TABLE_NAME")); + } + assertFalse(rs.next()); + + rs = meta.getColumns(null, null, "TEST", null); + rs.next(); + assertEquals("ID", rs.getString("COLUMN_NAME")); + rs.next(); + assertEquals("NAME", rs.getString("COLUMN_NAME")); + assertFalse(rs.next()); + + rs = meta.getPrimaryKeys(null, null, "TEST"); + rs.next(); + assertEquals("ID", rs.getString("COLUMN_NAME")); + assertFalse(rs.next()); + + rs = meta.getBestRowIdentifier(null, null, "TEST", + DatabaseMetaData.bestRowSession, false); + rs.next(); + assertEquals("ID", rs.getString("COLUMN_NAME")); + assertFalse(rs.next()); + + rs = meta.getIndexInfo(null, null, "TEST", false, false); + rs.next(); + String index = rs.getString("INDEX_NAME"); + assertTrue(index.startsWith("PRIMARY_KEY")); + assertEquals("ID", rs.getString("COLUMN_NAME")); + rs.next(); + assertEquals("IDXNAME", rs.getString("INDEX_NAME")); + assertEquals("NAME", rs.getString("COLUMN_NAME")); + assertFalse(rs.next()); + + rs = meta.getIndexInfo(null, null, "TEST", true, false); + rs.next(); + index = rs.getString("INDEX_NAME"); + assertTrue(index.startsWith("PRIMARY_KEY")); + assertEquals("ID", rs.getString("COLUMN_NAME")); + assertFalse(rs.next()); + + rs = meta.getVersionColumns(null, null, "TEST"); + assertFalse(rs.next()); + + stat.execute("DROP TABLE TEST"); + + rs = stat.executeQuery("SELECT * FROM INFORMATION_SCHEMA.SETTINGS"); + int mvStoreSettingsCount = 0, pageStoreSettingsCount = 0; + while (rs.next()) { + String name = rs.getString("SETTING_NAME"); + trace(name + '=' + rs.getString("SETTING_VALUE")); + if ("COMPRESS".equals(name) || "REUSE_SPACE".equals(name)) { + mvStoreSettingsCount++; + } else if (name.startsWith("PAGE_STORE_")) { + pageStoreSettingsCount++; + } + } + assertEquals(2, mvStoreSettingsCount); + assertEquals(0, pageStoreSettingsCount); + + testMore(); + + // meta.getTablePrivileges() + + // meta.getAttributes() + // meta.getColumnPrivileges() + // meta.getSuperTables() + // meta.getSuperTypes() + // meta.getTypeInfo() + + conn.close(); + + deleteDb("metaData"); + } + + private void testAllowLiteralsNone() throws SQLException { + Connection conn = getConnection("metaData"); + Statement stat = conn.createStatement(); + stat.execute("SET ALLOW_LITERALS NONE"); + DatabaseMetaData meta = conn.getMetaData(); + // meta.getAttributes(null, null, null, null); + meta.getBestRowIdentifier(null, null, "TEST", 0, false); + meta.getCatalogs(); + // meta.getClientInfoProperties(); + meta.getColumnPrivileges(null, null, "TEST", null); + meta.getColumns(null, null, null, null); + meta.getCrossReference(null, null, "TEST", null, null, "TEST"); + meta.getExportedKeys(null, null, "TEST"); + // meta.getFunctionColumns(null, null, null, null); + // meta.getFunctions(null, null, null); + meta.getImportedKeys(null, null, "TEST"); + meta.getIndexInfo(null, null, "TEST", false, false); + meta.getPrimaryKeys(null, null, "TEST"); + meta.getProcedureColumns(null, null, null, null); + meta.getProcedures(null, null, null); + meta.getSchemas(); + meta.getSchemas(null, null); + meta.getSuperTables(null, null, null); + // meta.getSuperTypes(null, null, null); + meta.getTablePrivileges(null, null, null); + meta.getTables(null, null, null, null); + meta.getTableTypes(); + meta.getTypeInfo(); + meta.getUDTs(null, null, null, null); + meta.getVersionColumns(null, null, null); + conn.close(); + deleteDb("metaData"); + } + + private void testClientInfo() throws SQLException { + Connection conn = getConnection("metaData"); + assertNull(conn.getClientInfo("xxx")); + DatabaseMetaData meta = conn.getMetaData(); + ResultSet rs = meta.getClientInfoProperties(); + ResultSetMetaData rsMeta = rs.getMetaData(); + assertEquals("NAME", rsMeta.getColumnName(1)); + assertEquals("MAX_LEN", rsMeta.getColumnName(2)); + assertEquals("DEFAULT_VALUE", rsMeta.getColumnName(3)); + assertEquals("DESCRIPTION", rsMeta.getColumnName(4)); + assertEquals("VALUE", rsMeta.getColumnName(5)); + int count = 0; + while (rs.next()) { + count++; + } + if (config.networked) { + // server0, numServers + assertEquals(2, count); + } else { + // numServers + assertEquals(1, count); + } + rs.close(); + conn.close(); + deleteDb("metaData"); + } + + private void testQueryStatistics() throws SQLException { + Connection conn = getConnection("metaData"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name varchar) as " + + "select x, space(1000) from system_range(1, 2000)"); + + ResultSet rs = stat.executeQuery( + "select * from INFORMATION_SCHEMA.QUERY_STATISTICS"); + assertFalse(rs.next()); + rs.close(); + stat.execute("SET QUERY_STATISTICS TRUE"); + int count = 100; + for (int i = 0; i < count; i++) { + execute(stat, "select * from test limit 10"); + } + // The "order by" makes the result set more stable on windows, where the + // timer resolution is not that great + rs = stat.executeQuery( + "select * from INFORMATION_SCHEMA.QUERY_STATISTICS " + + "ORDER BY EXECUTION_COUNT desc"); + assertTrue(rs.next()); + assertEquals("select * from test limit 10", rs.getString("SQL_STATEMENT")); + assertEquals(count, rs.getInt("EXECUTION_COUNT")); + assertEquals(config.lazy ? 0 : 10 * count, rs.getInt("CUMULATIVE_ROW_COUNT")); + rs.close(); + conn.close(); + deleteDb("metaData"); + } + + private void testQueryStatisticsLimit() throws SQLException { + Connection conn = getConnection("metaData"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name varchar) as " + + "select x, space(1000) from system_range(1, 2000)"); + + ResultSet rs = stat.executeQuery( + "select * from INFORMATION_SCHEMA.QUERY_STATISTICS"); + assertFalse(rs.next()); + rs.close(); + + //first, test setting the limit before activating statistics + int statisticsMaxEntries = 200; + //prevent test limit being less than or equal to default limit + assertTrue(statisticsMaxEntries > Constants.QUERY_STATISTICS_MAX_ENTRIES); + stat.execute("SET QUERY_STATISTICS_MAX_ENTRIES " + statisticsMaxEntries); + stat.execute("SET QUERY_STATISTICS TRUE"); + for (int i = 0; i < statisticsMaxEntries * 2; i++) { + stat.execute("select * from test where id = " + i); + } + rs = stat.executeQuery("select count(*) from INFORMATION_SCHEMA.QUERY_STATISTICS"); + assertTrue(rs.next()); + assertEquals(statisticsMaxEntries, rs.getInt(1)); + rs.close(); + + //first, test changing the limit once statistics is activated + int statisticsMaxEntriesNew = 50; + //prevent new test limit being greater than or equal to default limit + assertTrue(statisticsMaxEntriesNew < Constants.QUERY_STATISTICS_MAX_ENTRIES); + stat.execute("SET QUERY_STATISTICS_MAX_ENTRIES " + statisticsMaxEntriesNew); + for (int i = 0; i < statisticsMaxEntriesNew * 2; i++) { + stat.execute("select * from test where id = " + i); + } + rs = stat.executeQuery("select count(*) from INFORMATION_SCHEMA.QUERY_STATISTICS"); + assertTrue(rs.next()); + assertEquals(statisticsMaxEntriesNew, rs.getInt(1)); + rs.close(); + + conn.close(); + deleteDb("metaData"); + } +} diff --git a/h2/src/test/org/h2/test/jdbc/TestNativeSQL.java b/h2/src/test/org/h2/test/jdbc/TestNativeSQL.java new file mode 100644 index 0000000..fd17319 --- /dev/null +++ b/h2/src/test/org/h2/test/jdbc/TestNativeSQL.java @@ -0,0 +1,264 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbc; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Random; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests the Connection.nativeSQL method. + */ +public class TestNativeSQL extends TestDb { + + private static final String[] PAIRS = { + "CREATE TABLE TEST(ID INT PRIMARY KEY)", + "CREATE TABLE TEST(ID INT PRIMARY KEY)", + + "INSERT INTO TEST VALUES(1)", + "INSERT INTO TEST VALUES(1)", + + "SELECT '{nothing}' FROM TEST", + "SELECT '{nothing}' FROM TEST", + + "SELECT '{fn ABS(1)}' FROM TEST", + "SELECT '{fn ABS(1)}' FROM TEST", + + "SELECT {d '2001-01-01'} FROM TEST", + "SELECT d '2001-01-01' FROM TEST", + + "SELECT {t '20:00:00'} FROM TEST", + "SELECT t '20:00:00' FROM TEST", + + "SELECT {ts '2001-01-01 20:00:00'} FROM TEST", + "SELECT ts '2001-01-01 20:00:00' FROM TEST", + + "SELECT {fn CONCAT('{fn x}','{oj}')} FROM TEST", + "SELECT CONCAT('{fn x}','{oj}') FROM TEST", + + "SELECT * FROM {oj TEST T1 LEFT OUTER JOIN TEST T2 ON T1.ID=T2.ID}", + "SELECT * FROM TEST T1 LEFT OUTER JOIN TEST T2 ON T1.ID=T2.ID ", + + "SELECT * FROM TEST WHERE '{' LIKE '{{' {escape '{'}", + "SELECT * FROM TEST WHERE '{' LIKE '{{' escape '{' ", + + "SELECT * FROM TEST WHERE '}' LIKE '}}' {escape '}'}", + "SELECT * FROM TEST WHERE '}' LIKE '}}' escape '}' ", + + "{call TEST('}')}", " call TEST('}') ", + + "{?= call TEST('}')}", " ?= call TEST('}') ", + + "{? = call TEST('}')}", " ? = call TEST('}') ", + + "{{{{this is a bug}", null, }; + + private Connection conn; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + deleteDb("nativeSql"); + conn = getConnection("nativeSql"); + testPairs(); + testCases(); + testRandom(); + testQuotes(); + conn.close(); + assertTrue(conn.isClosed()); + deleteDb("nativeSql"); + } + + private void testQuotes() throws SQLException { + Statement stat = conn.createStatement(); + Random random = new Random(1); + String s = "'\"$/-* \n"; + for (int i = 0; i < 200; i++) { + StringBuilder buffQuoted = new StringBuilder(); + StringBuilder buffRaw = new StringBuilder(); + if (random.nextBoolean()) { + buffQuoted.append("'"); + for (int j = 0; j < 10; j++) { + char c = s.charAt(random.nextInt(s.length())); + if (c == '\'') { + buffQuoted.append('\''); + } + buffQuoted.append(c); + buffRaw.append(c); + } + buffQuoted.append("'"); + } else { + buffQuoted.append("$$"); + for (int j = 0; j < 10; j++) { + char c = s.charAt(random.nextInt(s.length())); + buffQuoted.append(c); + buffRaw.append(c); + if (c == '$') { + buffQuoted.append(' '); + buffRaw.append(' '); + } + } + buffQuoted.append("$$"); + } + String sql = "CALL " + buffQuoted.toString(); + ResultSet rs = stat.executeQuery(sql); + rs.next(); + String raw = buffRaw.toString(); + assertEquals(raw, rs.getString(1)); + } + } + + private void testRandom() throws SQLException { + Random random = new Random(1); + for (int i = 0; i < 100; i++) { + StringBuilder buff = new StringBuilder("{oj }"); + String s = "{}\'\"-/*$ $-"; + for (int j = random.nextInt(30); j > 0; j--) { + buff.append(s.charAt(random.nextInt(s.length()))); + } + String sql = buff.toString(); + try { + conn.nativeSQL(sql); + } catch (SQLException e) { + assertKnownException(sql, e); + } + } + String smallest = null; + for (int i = 0; i < 1000; i++) { + StringBuilder buff = new StringBuilder("{oj }"); + for (int j = random.nextInt(10); j > 0; j--) { + String s; + switch (random.nextInt(7)) { + case 0: + buff.append(" $$"); + s = "{}\'\"-/* a\n"; + for (int k = random.nextInt(5); k > 0; k--) { + buff.append(s.charAt(random.nextInt(s.length()))); + } + buff.append("$$"); + break; + case 1: + buff.append("'"); + s = "{}\"-/*$ a\n"; + for (int k = random.nextInt(5); k > 0; k--) { + buff.append(s.charAt(random.nextInt(s.length()))); + } + buff.append("'"); + break; + case 2: + buff.append("\""); + s = "{}'-/*$ a\n"; + for (int k = random.nextInt(5); k > 0; k--) { + buff.append(s.charAt(random.nextInt(s.length()))); + } + buff.append("\""); + break; + case 3: + buff.append("/*"); + s = "{}'\"-/$ a\n"; + for (int k = random.nextInt(5); k > 0; k--) { + buff.append(s.charAt(random.nextInt(s.length()))); + } + buff.append("*/"); + break; + case 4: + buff.append("--"); + s = "{}'\"-/$ a"; + for (int k = random.nextInt(5); k > 0; k--) { + buff.append(s.charAt(random.nextInt(s.length()))); + } + buff.append("\n"); + break; + case 5: + buff.append("//"); + s = "{}'\"-/$ a"; + for (int k = random.nextInt(5); k > 0; k--) { + buff.append(s.charAt(random.nextInt(s.length()))); + } + buff.append("\n"); + break; + case 6: + s = " a\n"; + for (int k = random.nextInt(5); k > 0; k--) { + buff.append(s.charAt(random.nextInt(s.length()))); + } + break; + default: + } + } + String sql = buff.toString(); + try { + conn.nativeSQL(sql); + } catch (Exception e) { + if (smallest == null || sql.length() < smallest.length()) { + smallest = sql; + } + } + } + if (smallest != null) { + conn.nativeSQL(smallest); + } + } + + private void testPairs() { + for (int i = 0; i < PAIRS.length; i += 2) { + test(PAIRS[i], PAIRS[i + 1]); + } + } + + private void testCases() throws SQLException { + conn.nativeSQL("TEST"); + conn.nativeSQL("TEST--testing"); + conn.nativeSQL("TEST--testing{oj }"); + conn.nativeSQL("TEST/*{fn }*/"); + conn.nativeSQL("TEST//{fn }"); + conn.nativeSQL("TEST-TEST/TEST/*TEST*/TEST--\rTEST--{fn }"); + conn.nativeSQL("TEST-TEST//TEST"); + conn.nativeSQL("'{}' '' \"1\" \"\"\"\""); + conn.nativeSQL("{?= call HELLO{t '10'}}"); + conn.nativeSQL("TEST 'test'{OJ OUTER JOIN}'test'{oj OUTER JOIN}"); + conn.nativeSQL("{call {ts '2001-01-10'}}"); + conn.nativeSQL("call ? { 1: '}' };"); + conn.nativeSQL("TEST TEST TEST TEST TEST 'TEST' TEST \"TEST\""); + conn.nativeSQL("TEST TEST TEST 'TEST' TEST \"TEST\""); + Statement stat = conn.createStatement(); + stat.setEscapeProcessing(true); + stat.execute("CALL {d '2001-01-01'}"); + stat.setEscapeProcessing(false); + assertThrows(ErrorCode.SYNTAX_ERROR_2, stat). + execute("CALL {d '2001-01-01'} // this is a test"); + assertFalse(conn.isClosed()); + } + + private void test(String original, String expected) { + trace("original: <" + original + ">"); + trace("expected: <" + expected + ">"); + try { + String result = conn.nativeSQL(original); + trace("result: <" + result + ">"); + assertEquals(expected, result); + } catch (SQLException e) { + assertEquals(expected, null); + assertKnownException(e); + trace("got exception, good"); + } + } + +} diff --git a/h2/src/test/org/h2/test/jdbc/TestPreparedStatement.java b/h2/src/test/org/h2/test/jdbc/TestPreparedStatement.java new file mode 100644 index 0000000..7bbe402 --- /dev/null +++ b/h2/src/test/org/h2/test/jdbc/TestPreparedStatement.java @@ -0,0 +1,1759 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbc; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.net.URL; +import java.sql.Array; +import java.sql.Connection; +import java.sql.Date; +import java.sql.JDBCType; +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.Period; +import java.time.ZonedDateTime; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.TimeZone; +import java.util.UUID; + +import org.h2.api.ErrorCode; +import org.h2.api.H2Type; +import org.h2.api.Interval; +import org.h2.api.IntervalQualifier; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.Task; + +/** + * Tests for the PreparedStatement implementation. + */ +public class TestPreparedStatement extends TestDb { + + private static final int LOB_SIZE = 4000, LOB_SIZE_BIG = 512 * 1024; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb("preparedStatement"); + Connection conn = getConnection("preparedStatement"); + testUnwrap(conn); + testUnsupportedOperations(conn); + testChangeType(conn); + testCallTablePrepared(conn); + testValues(conn); + testToString(conn); + testExecuteUpdateCall(conn); + testPrepareExecute(conn); + testEnum(conn); + testUUID(conn); + testUUIDAsJavaObject(conn); + testLobTempFiles(conn); + testExecuteErrorTwice(conn); + testTempView(conn); + testInsertFunction(conn); + testPrepareRecompile(conn); + testMaxRowsChange(conn); + testUnknownDataType(conn); + testCancelReuse(conn); + testCoalesce(conn); + testPreparedStatementMetaData(conn); + testBigDecimal(conn); + testDate(conn); + testDate8(conn); + testTime8(conn); + testOffsetTime8(conn); + testDateTime8(conn); + testOffsetDateTime8(conn); + testZonedDateTime8(conn); + testInstant8(conn); + testInterval(conn); + testInterval8(conn); + testJson(conn); + testArray(conn); + testSetObject(conn); + testSetObject2(conn); + testPreparedSubquery(conn); + testLikeIndex(conn); + testCasewhen(conn); + testSubquery(conn); + testObject(conn); + testDataTypes(conn); + testGetMoreResults(conn); + testBlob(conn); + testClob(conn); + testParameterMetaData(conn); + testColumnMetaDataWithEquals(conn); + testColumnMetaDataWithIn(conn); + testMultipleStatements(conn); + testAfterRollback(conn); + conn.close(); + testPreparedStatementWithLiteralsNone(); + testPreparedStatementWithIndexedParameterAndLiteralsNone(); + testPreparedStatementWithAnyParameter(); + deleteDb("preparedStatement"); + } + + private void testUnwrap(Connection conn) throws SQLException { + assertTrue(conn.isWrapperFor(Object.class)); + assertTrue(conn.isWrapperFor(Connection.class)); + assertTrue(conn.isWrapperFor(conn.getClass())); + assertFalse(conn.isWrapperFor(String.class)); + assertTrue(conn == conn.unwrap(Object.class)); + assertTrue(conn == conn.unwrap(Connection.class)); + assertThrows(ErrorCode.INVALID_VALUE_2, conn). + unwrap(String.class); + } + + @SuppressWarnings("deprecation") + private void testUnsupportedOperations(Connection conn) throws Exception { + PreparedStatement prep = conn.prepareStatement("select ? from dual"); + assertThrows(ErrorCode.METHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT, prep). + addBatch("select 1"); + + assertThrows(ErrorCode.METHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT, prep). + executeUpdate("create table test(id int)"); + assertThrows(ErrorCode.METHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT, prep). + executeUpdate("create table test(id int)", new int[0]); + assertThrows(ErrorCode.METHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT, prep). + executeUpdate("create table test(id int)", new String[0]); + assertThrows(ErrorCode.METHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT, prep). + executeUpdate("create table test(id int)", Statement.RETURN_GENERATED_KEYS); + + assertThrows(ErrorCode.METHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT, prep). + execute("create table test(id int)"); + assertThrows(ErrorCode.METHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT, prep). + execute("create table test(id int)", new int[0]); + assertThrows(ErrorCode.METHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT, prep). + execute("create table test(id int)", new String[0]); + assertThrows(ErrorCode.METHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT, prep). + execute("create table test(id int)", Statement.RETURN_GENERATED_KEYS); + + assertThrows(ErrorCode.METHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT, prep). + executeQuery("select * from dual"); + + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, prep). + setURL(1, new URL("http://www.acme.com")); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, prep). + setRowId(1, (RowId) null); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, prep). + setUnicodeStream(1, (InputStream) null, 0); + + ParameterMetaData meta = prep.getParameterMetaData(); + assertTrue(meta.toString(), meta.toString().endsWith("parameterCount=1")); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, conn). + createStruct("Integer", new Object[0]); + } + + private static void testChangeType(Connection conn) throws SQLException { + PreparedStatement prep = conn.prepareStatement( + "select (? || ? || ?) from dual"); + prep.setString(1, "a"); + prep.setString(2, "b"); + prep.setString(3, "c"); + prep.executeQuery(); + prep.setInt(1, 1); + prep.setString(2, "ab"); + prep.setInt(3, 45); + prep.executeQuery(); + } + + private static void testCallTablePrepared(Connection conn) throws SQLException { + PreparedStatement prep = conn.prepareStatement("select * from table(x int = (1))"); + prep.executeQuery(); + prep.executeQuery(); + } + + private void testValues(Connection conn) throws SQLException { + PreparedStatement prep = conn.prepareStatement("values(?, ?)"); + prep.setInt(1, 1); + prep.setString(2, "Hello"); + ResultSet rs = prep.executeQuery(); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + + prep = conn.prepareStatement("select * from values(?, ?), (2, 'World!')"); + prep.setInt(1, 1); + prep.setString(2, "Hello"); + rs = prep.executeQuery(); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + rs.next(); + assertEquals(2, rs.getInt(1)); + assertEquals("World!", rs.getString(2)); + + prep = conn.prepareStatement("values 1, 2"); + rs = prep.executeQuery(); + rs.next(); + assertEquals(1, rs.getInt(1)); + rs.next(); + assertEquals(2, rs.getInt(1)); + } + + private void testToString(Connection conn) throws SQLException { + PreparedStatement prep = conn.prepareStatement("call 1"); + assertTrue(prep.toString().endsWith(": call 1")); + prep = conn.prepareStatement("call ?"); + assertTrue(prep.toString().endsWith(": call ?")); + prep.setString(1, "Hello World"); + assertTrue(prep.toString().endsWith(": call ? {1: 'Hello World'}")); + } + + private void testExecuteUpdateCall(Connection conn) throws SQLException { + assertThrows(ErrorCode.DATA_CONVERSION_ERROR_1, conn.createStatement()). + executeUpdate("CALL HASH('SHA256', STRINGTOUTF8('Password'), 1000)"); + } + + private void testPrepareExecute(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("prepare test(int, int) as select ?1*?2"); + ResultSet rs = stat.executeQuery("execute test(3, 2)"); + rs.next(); + assertEquals(6, rs.getInt(1)); + stat.execute("deallocate test"); + } + + private void testLobTempFiles(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, DATA CLOB)"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST VALUES(?, ?)"); + for (int i = 0; i < 5; i++) { + prep.setInt(1, i); + if (i % 2 == 0) { + prep.setCharacterStream(2, new StringReader(getString(i)), -1); + } + prep.execute(); + } + ResultSet rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + int check = 0; + for (int i = 0; i < 5; i++) { + assertTrue(rs.next()); + if (i % 2 == 0) { + check = i; + } + assertEquals(getString(check), rs.getString(2)); + } + assertFalse(rs.next()); + stat.execute("DELETE FROM TEST"); + for (int i = 0; i < 3; i++) { + prep.setInt(1, i); + prep.setCharacterStream(2, new StringReader(getString(i)), -1); + prep.addBatch(); + } + prep.executeBatch(); + rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + for (int i = 0; i < 3; i++) { + assertTrue(rs.next()); + assertEquals(getString(i), rs.getString(2)); + } + assertFalse(rs.next()); + stat.execute("DROP TABLE TEST"); + } + + private static String getString(int i) { + return new String(new char[100000]).replace('\0', (char) ('0' + i)); + } + + private void testExecuteErrorTwice(Connection conn) throws SQLException { + PreparedStatement prep = conn.prepareStatement( + "CREATE TABLE BAD AS SELECT A"); + assertThrows(ErrorCode.COLUMN_NOT_FOUND_1, prep).execute(); + assertThrows(ErrorCode.COLUMN_NOT_FOUND_1, prep).execute(); + } + + private void testTempView(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + PreparedStatement prep; + stat.execute("CREATE TABLE TEST(FIELD INT PRIMARY KEY)"); + stat.execute("INSERT INTO TEST VALUES(1)"); + stat.execute("INSERT INTO TEST VALUES(2)"); + prep = conn.prepareStatement("select FIELD FROM " + + "(select FIELD FROM (SELECT FIELD FROM TEST " + + "WHERE FIELD = ?) AS T2 " + + "WHERE T2.FIELD = ?) AS T3 WHERE T3.FIELD = ?"); + prep.setInt(1, 1); + prep.setInt(2, 1); + prep.setInt(3, 1); + ResultSet rs = prep.executeQuery(); + rs.next(); + assertEquals(1, rs.getInt(1)); + prep.setInt(1, 2); + prep.setInt(2, 2); + prep.setInt(3, 2); + rs = prep.executeQuery(); + rs.next(); + assertEquals(2, rs.getInt(1)); + stat.execute("DROP TABLE TEST"); + } + + private void testInsertFunction(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + PreparedStatement prep; + ResultSet rs; + + stat.execute("CREATE TABLE TEST(ID INT, H VARBINARY)"); + prep = conn.prepareStatement("INSERT INTO TEST " + + "VALUES(?, HASH('SHA256', STRINGTOUTF8(?), 5))"); + prep.setInt(1, 1); + prep.setString(2, "One"); + prep.execute(); + prep.setInt(1, 2); + prep.setString(2, "Two"); + prep.execute(); + rs = stat.executeQuery("SELECT COUNT(DISTINCT H) FROM TEST"); + rs.next(); + assertEquals(2, rs.getInt(1)); + + stat.execute("DROP TABLE TEST"); + } + + private void testPrepareRecompile(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + PreparedStatement prep; + ResultSet rs; + + prep = conn.prepareStatement("SELECT COUNT(*) " + + "FROM DUAL WHERE ? IS NULL"); + prep.setString(1, null); + prep.executeQuery(); + stat.execute("CREATE TABLE TEST(ID INT)"); + stat.execute("DROP TABLE TEST"); + prep.setString(1, null); + prep.executeQuery(); + prep.setString(1, "X"); + rs = prep.executeQuery(); + rs.next(); + assertEquals(0, rs.getInt(1)); + + stat.execute("CREATE TABLE t1 (c1 INT, c2 VARCHAR(10))"); + stat.execute("INSERT INTO t1 SELECT X, CONCAT('Test', X) " + + "FROM SYSTEM_RANGE(1, 5);"); + prep = conn.prepareStatement("SELECT c1, c2 FROM t1 WHERE c1 = ?"); + prep.setInt(1, 1); + prep.executeQuery(); + stat.execute("CREATE TABLE t2 (x int PRIMARY KEY)"); + prep.setInt(1, 2); + rs = prep.executeQuery(); + rs.next(); + assertEquals(2, rs.getInt(1)); + prep.setInt(1, 3); + rs = prep.executeQuery(); + rs.next(); + assertEquals(3, rs.getInt(1)); + stat.execute("DROP TABLE t1, t2"); + + } + + private void testMaxRowsChange(Connection conn) throws SQLException { + PreparedStatement prep = conn.prepareStatement( + "SELECT * FROM SYSTEM_RANGE(1, 100)"); + ResultSet rs; + for (int j = 1; j < 20; j++) { + prep.setMaxRows(j); + rs = prep.executeQuery(); + for (int i = 0; i < j; i++) { + assertTrue(rs.next()); + } + assertFalse(rs.next()); + } + } + + private void testUnknownDataType(Connection conn) throws SQLException { + assertThrows(ErrorCode.UNKNOWN_DATA_TYPE_1, conn). + prepareStatement("SELECT * FROM (SELECT ? FROM DUAL)"); + assertThrows(ErrorCode.UNKNOWN_DATA_TYPE_1, conn). + prepareStatement("VALUES BITAND(?, ?)"); + PreparedStatement prep = conn.prepareStatement("SELECT -?"); + prep.setInt(1, 1); + execute(prep); + prep = conn.prepareStatement("SELECT ?-?"); + prep.setInt(1, 1); + prep.setInt(2, 2); + execute(prep); + } + + private void testCancelReuse(Connection conn) throws Exception { + conn.createStatement().execute( + "CREATE ALIAS SLEEP FOR 'java.lang.Thread.sleep'"); + // sleep for 10 seconds + final PreparedStatement prep = conn.prepareStatement( + "SELECT SLEEP(?) FROM SYSTEM_RANGE(1, 10000) LIMIT ?"); + prep.setInt(1, 1); + prep.setInt(2, 10000); + Task t = new Task() { + @Override + public void call() throws SQLException { + TestPreparedStatement.this.execute(prep); + } + }; + t.execute(); + Thread.sleep(100); + prep.cancel(); + SQLException e = (SQLException) t.getException(); + assertNotNull(e); + assertEquals(ErrorCode.STATEMENT_WAS_CANCELED, e.getErrorCode()); + prep.setInt(1, 1); + prep.setInt(2, 1); + ResultSet rs = prep.executeQuery(); + assertTrue(rs.next()); + assertEquals(0, rs.getInt(1)); + assertFalse(rs.next()); + } + + private static void testCoalesce(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + stat.executeUpdate("create table test(tm timestamp)"); + stat.executeUpdate("insert into test values(current_timestamp)"); + PreparedStatement prep = conn.prepareStatement( + "update test set tm = coalesce(?,tm)"); + prep.setTimestamp(1, new java.sql.Timestamp(System.currentTimeMillis())); + prep.executeUpdate(); + stat.executeUpdate("drop table test"); + } + + private void testPreparedStatementMetaData(Connection conn) + throws SQLException { + PreparedStatement prep = conn.prepareStatement( + "select * from table(x int = ?, name varchar = ?)"); + ResultSetMetaData meta = prep.getMetaData(); + assertEquals(2, meta.getColumnCount()); + assertEquals("INTEGER", meta.getColumnTypeName(1)); + assertEquals("CHARACTER VARYING", meta.getColumnTypeName(2)); + prep = conn.prepareStatement("call 1"); + meta = prep.getMetaData(); + assertEquals(1, meta.getColumnCount()); + assertEquals("INTEGER", meta.getColumnTypeName(1)); + prep = conn.prepareStatement("SELECT * FROM UNNEST(ARRAY[1, 2])"); + meta = prep.getMetaData(); + assertEquals(1, meta.getColumnCount()); + assertEquals("INTEGER", meta.getColumnTypeName(1)); + } + + private void testArray(Connection conn) throws SQLException { + PreparedStatement prep = conn.prepareStatement( + "select * from table(x int = ?) order by x"); + prep.setObject(1, new Object[] { new BigDecimal("1"), "2" }); + ResultSet rs = prep.executeQuery(); + rs.next(); + assertEquals("1", rs.getString(1)); + rs.next(); + assertEquals("2", rs.getString(1)); + assertFalse(rs.next()); + } + + private void testEnum(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE test_enum(size ENUM('small', 'medium', 'large'))"); + + String[] badSizes = new String[]{"green", "smol", "0"}; + for (int i = 0; i < badSizes.length; i++) { + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO test_enum VALUES(?)"); + prep.setObject(1, badSizes[i]); + assertThrows(ErrorCode.ENUM_VALUE_NOT_PERMITTED, prep).execute(); + } + + String[] goodSizes = new String[]{"small", "medium", "large"}; + for (int i = 0; i < goodSizes.length; i++) { + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO test_enum VALUES(?)"); + prep.setObject(1, goodSizes[i]); + prep.execute(); + ResultSet rs = stat.executeQuery("SELECT * FROM test_enum"); + for (int j = 0; j <= i; j++) { + rs.next(); + } + assertEquals(goodSizes[i], rs.getString(1)); + assertEquals(i + 1, rs.getInt(1)); + Object o = rs.getObject(1); + assertEquals(String.class, o.getClass()); + } + + for (int i = 0; i < goodSizes.length; i++) { + PreparedStatement prep = conn.prepareStatement("SELECT * FROM test_enum WHERE size = ?"); + prep.setObject(1, goodSizes[i]); + ResultSet rs = prep.executeQuery(); + rs.next(); + String s = rs.getString(1); + assertTrue(s.equals(goodSizes[i])); + assertFalse(rs.next()); + } + + for (int i = 0; i < badSizes.length; i++) { + PreparedStatement prep = conn.prepareStatement("SELECT * FROM test_enum WHERE size = ?"); + prep.setObject(1, badSizes[i]); + if (config.lazy && !config.networked) { + ResultSet resultSet = prep.executeQuery(); + assertThrows(ErrorCode.ENUM_VALUE_NOT_PERMITTED, resultSet).next(); + } else { + assertThrows(ErrorCode.ENUM_VALUE_NOT_PERMITTED, prep).executeQuery(); + } + } + + stat.execute("DROP TABLE test_enum"); + } + + private void testUUID(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("create table test_uuid(id uuid primary key)"); + UUID uuid = new UUID(-2, -1); + PreparedStatement prep = conn.prepareStatement( + "insert into test_uuid values(?)"); + prep.setObject(1, uuid); + prep.execute(); + ResultSet rs = stat.executeQuery("select * from test_uuid"); + rs.next(); + assertEquals("ffffffff-ffff-fffe-ffff-ffffffffffff", rs.getString(1)); + Object o = rs.getObject(1); + assertEquals("java.util.UUID", o.getClass().getName()); + stat.execute("drop table test_uuid"); + } + + private void testUUIDAsJavaObject(Connection conn) throws SQLException { + String uuidStr = "12345678-1234-4321-8765-123456789012"; + + Statement stat = conn.createStatement(); + stat.execute("create table test_uuid(id uuid primary key)"); + UUID origUUID = UUID.fromString(uuidStr); + PreparedStatement prep = conn.prepareStatement("insert into test_uuid values(?)"); + prep.setObject(1, origUUID, java.sql.Types.JAVA_OBJECT); + prep.execute(); + + prep = conn.prepareStatement("select * from test_uuid where id=?"); + prep.setObject(1, origUUID, java.sql.Types.JAVA_OBJECT); + ResultSet rs = prep.executeQuery(); + rs.next(); + Object o = rs.getObject(1); + assertTrue(o instanceof UUID); + UUID selectedUUID = (UUID) o; + assertTrue(selectedUUID.toString().equals(uuidStr)); + assertTrue(selectedUUID.equals(origUUID)); + stat.execute("drop table test_uuid"); + } + + private void testSetObject(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(C CHAR(1))"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST VALUES(?)"); + prep.setObject(1, 'x'); + prep.execute(); + stat.execute("DROP TABLE TEST"); + stat.execute("CREATE TABLE TEST(ID INT, DATA VARBINARY, JAVA OTHER)"); + prep = conn.prepareStatement("INSERT INTO TEST VALUES(?, ?, ?)"); + prep.setInt(1, 1); + prep.setObject(2, 11); + prep.setObject(3, null); + prep.execute(); + prep.setInt(1, 2); + prep.setObject(2, 101, Types.JAVA_OBJECT); + prep.setObject(3, 103, Types.JAVA_OBJECT); + prep.execute(); + PreparedStatement p2 = conn.prepareStatement( + "SELECT * FROM TEST ORDER BY ID"); + ResultSet rs = p2.executeQuery(); + rs.next(); + Object o = rs.getObject(2); + assertTrue(o instanceof byte[]); + assertNull(rs.getObject(3)); + rs.next(); + o = rs.getObject(2); + assertTrue(o instanceof byte[]); + o = rs.getObject(3); + assertTrue(o instanceof Integer); + assertEquals(103, ((Integer) o).intValue()); + assertFalse(rs.next()); + stat.execute("DROP TABLE TEST"); + } + + private void testSetObject2(Connection conn) throws SQLException { + try (PreparedStatement prep = conn.prepareStatement("VALUES (?1, ?1 IS OF(INTEGER), ?1 IS OF(BIGINT))")) { + for (int i = 1; i <= 6; i++) { + testSetObject2SetObjectType(prep, i, (long) i); + try (ResultSet rs = prep.executeQuery()) { + rs.next(); + // Parameters are converted to VARCHAR by a query + assertEquals(Integer.toString(i), rs.getString(1)); + // Use the type predicate to check a real data type + if (i == 1) { + assertFalse(rs.getBoolean(2)); + assertTrue(rs.getBoolean(3)); + } else { + assertTrue(rs.getBoolean(2)); + assertFalse(rs.getBoolean(3)); + } + } + testSetObject2SetObjectType(prep, i, null); + try (ResultSet rs = prep.executeQuery()) { + rs.next(); + assertNull(rs.getObject(1)); + } + } + prep.setObject(1, 1); + } + } + + private static void testSetObject2SetObjectType(PreparedStatement prep, int method, Object value) + throws SQLException { + switch (method) { + case 1: + prep.setObject(1, value); + break; + case 2: + prep.setObject(1, value, Types.INTEGER); + break; + case 3: + prep.setObject(1, value, JDBCType.INTEGER); + break; + case 4: + prep.setObject(1, value, Types.INTEGER, 0); + break; + case 5: + prep.setObject(1, value, JDBCType.INTEGER, 0); + break; + case 6: + prep.setObject(1, value, H2Type.INTEGER, 0); + } + } + + private void testBigDecimal(Connection conn) throws SQLException { + PreparedStatement prep = conn.prepareStatement("SELECT ?, ?"); + BigDecimal bd = new BigDecimal("12300").setScale(-2, RoundingMode.UNNECESSARY); + prep.setBigDecimal(1, bd); + prep.setObject(2, bd); + ResultSet rs = prep.executeQuery(); + rs.next(); + bd = rs.getBigDecimal(1); + assertEquals(12300, bd.intValue()); + assertEquals(0, bd.scale()); + bd = rs.getBigDecimal(2); + assertEquals(12300, bd.intValue()); + assertEquals(0, bd.scale()); + } + + private void testDate(Connection conn) throws SQLException { + PreparedStatement prep = conn.prepareStatement("SELECT ?"); + Timestamp ts = Timestamp.valueOf("2001-02-03 04:05:06"); + prep.setObject(1, new java.util.Date(ts.getTime())); + ResultSet rs = prep.executeQuery(); + rs.next(); + Timestamp ts2 = rs.getTimestamp(1); + assertEquals(ts.toString(), ts2.toString()); + } + + private void testDate8(Connection conn) throws SQLException { + PreparedStatement prep = conn.prepareStatement("SELECT ?"); + LocalDate localDate = LocalDate.parse("2001-02-03"); + prep.setObject(1, localDate); + ResultSet rs = prep.executeQuery(); + rs.next(); + LocalDate localDate2 = rs.getObject(1, LocalDate.class); + assertEquals(localDate, localDate2); + rs.close(); + localDate = LocalDate.parse("-0509-01-01"); + prep.setObject(1, localDate); + rs = prep.executeQuery(); + rs.next(); + localDate2 = rs.getObject(1, LocalDate.class); + assertEquals(localDate, localDate2); + rs.close(); + prep.setString(1, "1500-02-28"); + rs = prep.executeQuery(); + rs.next(); + localDate2 = rs.getObject(1, LocalDate.class); + assertEquals(LocalDate.parse("1500-02-28"), localDate2); + rs.close(); + prep.setString(1, "-0100-02-28"); + rs = prep.executeQuery(); + rs.next(); + localDate2 = rs.getObject(1, LocalDate.class); + assertEquals(LocalDate.parse("-0100-02-28"), localDate2); + rs.close(); + /* + * Test dates during Julian to Gregorian transition. + * + * java.util.TimeZone doesn't support LMT, so perform this test with + * fixed time zone offset + */ + Statement stat = conn.createStatement(); + stat.execute("SET TIME ZONE '1'"); + TimeZone old = TimeZone.getDefault(); + TimeZone.setDefault(TimeZone.getTimeZone("GMT+01")); + try { + localDate = LocalDate.parse("1582-10-05"); + prep.setObject(1, localDate); + rs = prep.executeQuery(); + rs.next(); + localDate2 = rs.getObject(1, LocalDate.class); + assertEquals(localDate, localDate2); + assertEquals("1582-10-05", rs.getString(1)); + assertEquals(Date.valueOf("1582-09-25"), rs.getDate(1)); + GregorianCalendar gc = new GregorianCalendar(); + gc.setGregorianChange(new java.util.Date(Long.MIN_VALUE)); + gc.clear(); + gc.set(Calendar.YEAR, 1582); + gc.set(Calendar.MONTH, 9); + gc.set(Calendar.DAY_OF_MONTH, 5); + Date expected = new Date(gc.getTimeInMillis()); + gc.clear(); + assertEquals(expected, rs.getDate(1, gc)); + rs.close(); + } finally { + stat.execute("SET TIME ZONE LOCAL"); + TimeZone.setDefault(old); + } + } + + private void testTime8(Connection conn) throws SQLException { + PreparedStatement prep = conn.prepareStatement("SELECT ?"); + LocalTime localTime = LocalTime.parse("04:05:06"); + prep.setObject(1, localTime); + ResultSet rs = prep.executeQuery(); + rs.next(); + LocalTime localTime2 = rs.getObject(1, LocalTime.class); + assertEquals(localTime, localTime2); + rs.close(); + localTime = LocalTime.parse("04:05:06.123456789"); + prep.setObject(1, localTime); + rs = prep.executeQuery(); + rs.next(); + localTime2 = rs.getObject(1, LocalTime.class); + assertEquals(localTime, localTime2); + rs.close(); + } + + private void testOffsetTime8(Connection conn) throws SQLException { + PreparedStatement prep = conn.prepareStatement("SELECT ?"); + OffsetTime offsetTime = OffsetTime.parse("04:05:06+02:30"); + prep.setObject(1, offsetTime); + ResultSet rs = prep.executeQuery(); + rs.next(); + OffsetTime offsetTime2 = rs.getObject(1, OffsetTime.class); + assertEquals(offsetTime, offsetTime2); + assertFalse(rs.next()); + rs.close(); + + prep.setObject(1, offsetTime, Types.TIME_WITH_TIMEZONE); + rs = prep.executeQuery(); + rs.next(); + offsetTime2 = rs.getObject(1, OffsetTime.class); + assertEquals(offsetTime, offsetTime2); + assertFalse(rs.next()); + rs.close(); + } + + private void testDateTime8(Connection conn) throws SQLException { + PreparedStatement prep = conn.prepareStatement("SELECT ?"); + LocalDateTime localDateTime = LocalDateTime.parse("2001-02-03T04:05:06"); + prep.setObject(1, localDateTime); + ResultSet rs = prep.executeQuery(); + rs.next(); + LocalDateTime localDateTime2 = rs.getObject(1, LocalDateTime.class); + assertEquals(localDateTime, localDateTime2); + rs.close(); + } + + private void testOffsetDateTime8(Connection conn) throws SQLException { + PreparedStatement prep = conn.prepareStatement("SELECT ?"); + OffsetDateTime offsetDateTime = OffsetDateTime.parse("2001-02-03T04:05:06+02:30"); + prep.setObject(1, offsetDateTime); + ResultSet rs = prep.executeQuery(); + rs.next(); + OffsetDateTime offsetDateTime2 = rs.getObject(1, OffsetDateTime.class); + assertEquals(offsetDateTime, offsetDateTime2); + assertFalse(rs.next()); + rs.close(); + + prep.setObject(1, offsetDateTime, Types.TIMESTAMP_WITH_TIMEZONE); + rs = prep.executeQuery(); + rs.next(); + offsetDateTime2 = rs.getObject(1, OffsetDateTime.class); + assertEquals(offsetDateTime, offsetDateTime2); + // Check default mapping + rs.getObject(1); + assertFalse(rs.next()); + rs.close(); + } + + private void testZonedDateTime8(Connection conn) throws SQLException { + PreparedStatement prep = conn.prepareStatement("SELECT ?"); + ZonedDateTime zonedDateTime = ZonedDateTime.parse("2001-02-03T04:05:06+02:30"); + prep.setObject(1, zonedDateTime); + ResultSet rs = prep.executeQuery(); + rs.next(); + ZonedDateTime zonedDateTime2 = rs.getObject(1, ZonedDateTime.class); + assertEquals(zonedDateTime, zonedDateTime2); + assertFalse(rs.next()); + rs.close(); + + prep.setObject(1, zonedDateTime, Types.TIMESTAMP_WITH_TIMEZONE); + rs = prep.executeQuery(); + rs.next(); + zonedDateTime2 = rs.getObject(1, ZonedDateTime.class); + assertEquals(zonedDateTime, zonedDateTime2); + assertFalse(rs.next()); + rs.close(); + } + + private void testInstant8(Connection conn) throws Exception { + PreparedStatement prep = conn.prepareStatement("SELECT ?"); + testInstant8Impl(prep, Instant.now()); + testInstant8Impl(prep, Instant.parse("2000-01-15T12:13:14.123456789Z")); + testInstant8Impl(prep, Instant.parse("1500-09-10T23:22:11.123456789Z")); + } + + private void testInstant8Impl(PreparedStatement prep, Instant instant) throws SQLException { + prep.setObject(1, instant); + ResultSet rs = prep.executeQuery(); + rs.next(); + Instant instant2 = rs.getObject(1, Instant.class); + assertEquals(instant, instant2); + Timestamp ts = rs.getTimestamp(1); + assertEquals(instant, ts.toInstant()); + assertFalse(rs.next()); + rs.close(); + + prep.setTimestamp(1, ts); + rs = prep.executeQuery(); + rs.next(); + instant2 = rs.getObject(1, Instant.class); + assertEquals(instant, instant2); + assertFalse(rs.next()); + rs.close(); + } + + private void testInterval(Connection conn) throws SQLException { + PreparedStatement prep = conn.prepareStatement("SELECT ?"); + Interval interval = new Interval(IntervalQualifier.MINUTE, false, 100, 0); + prep.setObject(1, interval); + ResultSet rs = prep.executeQuery(); + rs.next(); + assertEquals("INTERVAL '100' MINUTE", rs.getString(1)); + assertEquals(interval, rs.getObject(1)); + assertEquals(interval, rs.getObject(1, Interval.class)); + } + + private void testInterval8(Connection conn) throws SQLException { + PreparedStatement prep = conn.prepareStatement("SELECT ?"); + testPeriod8(prep, 1, 2, "INTERVAL '1-2' YEAR TO MONTH"); + testPeriod8(prep, -1, -2, "INTERVAL '-1-2' YEAR TO MONTH"); + testPeriod8(prep, 1, -8, "INTERVAL '0-4' YEAR TO MONTH", 0, 4); + testPeriod8(prep, -1, 8, "INTERVAL '-0-4' YEAR TO MONTH", 0, -4); + testPeriod8(prep, 0, 0, "INTERVAL '0-0' YEAR TO MONTH"); + testPeriod8(prep, 100, 0, "INTERVAL '100' YEAR"); + testPeriod8(prep, -100, 0, "INTERVAL '-100' YEAR"); + testPeriod8(prep, 0, 100, "INTERVAL '100' MONTH"); + testPeriod8(prep, 0, -100, "INTERVAL '-100' MONTH"); + Period period = Period.of(0, 0, 1); + assertThrows(ErrorCode.INVALID_VALUE_2, prep).setObject(1, period); + Duration duration = Duration.ofSeconds(-4, 900_000_000); + prep.setObject(1, duration); + ResultSet rs = prep.executeQuery(); + rs.next(); + assertEquals("INTERVAL '-3.1' SECOND", rs.getString(1)); + assertEquals(duration, rs.getObject(1, Duration.class)); + } + + private void testPeriod8(PreparedStatement prep, int years, int months, String expectedString) + throws SQLException { + testPeriod8(prep, years, months, expectedString, years, months); + } + + private void testPeriod8(PreparedStatement prep, int years, int months, String expectedString, int expYears, + int expMonths) throws SQLException { + Period period = Period.of(years, months, 0); + Period expectedPeriod = Period.of(expYears, expMonths, 0); + prep.setObject(1, period); + ResultSet rs = prep.executeQuery(); + rs.next(); + assertEquals(expectedString, rs.getString(1)); + assertEquals(expectedPeriod, rs.getObject(1, Period.class)); + } + + private void testJson(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID BIGINT, J JSON)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST VALUES (?, ?)"); + prep.setInt(1, 1); + prep.setString(2, "[1]"); + prep.executeUpdate(); + prep = conn.prepareStatement("INSERT INTO TEST VALUES (?, ? FORMAT JSON)"); + prep.setInt(1, 2); + prep.setString(2, "[1]"); + prep.executeUpdate(); + try (ResultSet rs = stat.executeQuery("SELECT J FROM TEST ORDER BY ID")) { + assertTrue(rs.next()); + assertEquals("\"[1]\"", rs.getString(1)); + assertTrue(rs.next()); + assertEquals("[1]", rs.getString(1)); + assertFalse(rs.next()); + } + stat.execute("DROP TABLE TEST"); + } + + private void testPreparedSubquery(Connection conn) throws SQLException { + Statement s = conn.createStatement(); + s.executeUpdate("CREATE TABLE TEST(ID IDENTITY, FLAG BIT)"); + s.executeUpdate("INSERT INTO TEST(ID, FLAG) VALUES(0, FALSE)"); + s.executeUpdate("INSERT INTO TEST(ID, FLAG) VALUES(1, FALSE)"); + PreparedStatement u = conn.prepareStatement( + "SELECT ID, FLAG FROM TEST ORDER BY ID"); + PreparedStatement p = conn.prepareStatement( + "UPDATE TEST SET FLAG=true WHERE ID=(SELECT ?)"); + p.clearParameters(); + p.setLong(1, 0); + assertEquals(1, p.executeUpdate()); + p.clearParameters(); + p.setLong(1, 1); + assertEquals(1, p.executeUpdate()); + ResultSet rs = u.executeQuery(); + assertTrue(rs.next()); + assertEquals(0, rs.getInt(1)); + assertTrue(rs.getBoolean(2)); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertTrue(rs.getBoolean(2)); + + p = conn.prepareStatement("SELECT * FROM TEST " + + "WHERE EXISTS(SELECT * FROM TEST WHERE ID=?)"); + p.setInt(1, -1); + rs = p.executeQuery(); + assertFalse(rs.next()); + p.setInt(1, 1); + rs = p.executeQuery(); + assertTrue(rs.next()); + + s.executeUpdate("DROP TABLE IF EXISTS TEST"); + } + + private void testParameterMetaData(Connection conn) throws SQLException { + PreparedStatement prep = conn.prepareStatement("SELECT ?, ?, ? FROM DUAL"); + ParameterMetaData pm = prep.getParameterMetaData(); + assertEquals("java.lang.String", pm.getParameterClassName(1)); + assertEquals("CHARACTER VARYING", pm.getParameterTypeName(1)); + assertEquals(3, pm.getParameterCount()); + assertEquals(ParameterMetaData.parameterModeIn, pm.getParameterMode(1)); + assertEquals(Types.VARCHAR, pm.getParameterType(1)); + assertEquals(0, pm.getPrecision(1)); + assertEquals(0, pm.getScale(1)); + assertEquals(ResultSetMetaData.columnNullableUnknown, pm.isNullable(1)); + assertEquals(pm.isSigned(1), true); + assertThrows(ErrorCode.INVALID_VALUE_2, pm).getPrecision(0); + assertThrows(ErrorCode.INVALID_VALUE_2, pm).getPrecision(4); + prep.close(); + assertThrows(ErrorCode.OBJECT_CLOSED, pm).getPrecision(1); + + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST3(ID INT, " + + "NAME VARCHAR(255), DATA1 DECIMAL(10,2), DATA2 NUMERIC(10,2))"); + PreparedStatement prep1 = conn.prepareStatement( + "UPDATE TEST3 SET ID=?, NAME=?, DATA1=?, DATA2=?"); + PreparedStatement prep2 = conn.prepareStatement( + "INSERT INTO TEST3 VALUES(?, ?, ?, ?)"); + checkParameter(prep1, 1, "java.lang.Integer", 4, "INTEGER", 32, 0); + checkParameter(prep1, 2, "java.lang.String", 12, "CHARACTER VARYING", 255, 0); + checkParameter(prep1, 3, "java.math.BigDecimal", Types.DECIMAL, "DECIMAL", 10, 2); + checkParameter(prep1, 4, "java.math.BigDecimal", Types.NUMERIC, "NUMERIC", 10, 2); + checkParameter(prep2, 1, "java.lang.Integer", 4, "INTEGER", 32, 0); + checkParameter(prep2, 2, "java.lang.String", 12, "CHARACTER VARYING", 255, 0); + checkParameter(prep2, 3, "java.math.BigDecimal", Types.DECIMAL, "DECIMAL", 10, 2); + checkParameter(prep2, 4, "java.math.BigDecimal", Types.NUMERIC, "NUMERIC", 10, 2); + PreparedStatement prep3 = conn.prepareStatement( + "SELECT * FROM TEST3 WHERE ID=? AND NAME LIKE ? AND ?>DATA1 AND ?>DATA2"); + checkParameter(prep3, 1, "java.lang.Integer", 4, "INTEGER", 32, 0); + checkParameter(prep3, 2, "java.lang.String", 12, "CHARACTER VARYING", 0, 0); + checkParameter(prep3, 3, "java.math.BigDecimal", Types.DECIMAL, "DECIMAL", 10, 2); + checkParameter(prep3, 4, "java.math.BigDecimal", Types.NUMERIC, "NUMERIC", 10, 2); + stat.execute("DROP TABLE TEST3"); + } + + private void checkParameter(PreparedStatement prep, int index, + String className, int type, String typeName, int precision, + int scale) throws SQLException { + ParameterMetaData meta = prep.getParameterMetaData(); + assertEquals(className, meta.getParameterClassName(index)); + assertEquals(type, meta.getParameterType(index)); + assertEquals(typeName, meta.getParameterTypeName(index)); + assertEquals(precision, meta.getPrecision(index)); + assertEquals(scale, meta.getScale(index)); + } + + private void testLikeIndex(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, V INT, NAME VARCHAR(255))"); + stat.execute("INSERT INTO TEST VALUES(1, 2, 'Hello')"); + stat.execute("INSERT INTO TEST VALUES(2, 4, 'World')"); + stat.execute("create index idxname on test(name);"); + PreparedStatement prep, prepExe; + + prep = conn.prepareStatement( + "EXPLAIN SELECT * FROM TEST WHERE NAME LIKE ?"); + assertEquals(1, prep.getParameterMetaData().getParameterCount()); + prepExe = conn.prepareStatement( + "SELECT * FROM TEST WHERE NAME LIKE ?"); + prep.setString(1, "%orld"); + prepExe.setString(1, "%orld"); + ResultSet rs = prep.executeQuery(); + rs.next(); + String plan = rs.getString(1); + assertContains(plan, ".tableScan"); + rs = prepExe.executeQuery(); + rs.next(); + assertEquals("World", rs.getString(3)); + assertFalse(rs.next()); + + prep.setString(1, "H%"); + prepExe.setString(1, "H%"); + rs = prep.executeQuery(); + rs.next(); + String plan1 = rs.getString(1); + assertContains(plan1, "IDXNAME"); + rs = prepExe.executeQuery(); + rs.next(); + assertEquals("Hello", rs.getString(3)); + assertFalse(rs.next()); + + stat.execute("DROP TABLE IF EXISTS TEST"); + } + + private void testCasewhen(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT)"); + stat.execute("INSERT INTO TEST VALUES(1),(2),(3)"); + PreparedStatement prep; + ResultSet rs; + prep = conn.prepareStatement("EXPLAIN SELECT COUNT(*) FROM TEST " + + "WHERE CASEWHEN(ID=1, ID, ID)=? GROUP BY ID"); + prep.setInt(1, 1); + rs = prep.executeQuery(); + rs.next(); + String plan = rs.getString(1); + trace(plan); + rs.close(); + prep = conn.prepareStatement("EXPLAIN SELECT COUNT(*) FROM TEST " + + "WHERE CASE ID WHEN 1 THEN ID WHEN 2 THEN ID " + + "ELSE ID END=? GROUP BY ID"); + prep.setInt(1, 1); + rs = prep.executeQuery(); + rs.next(); + plan = rs.getString(1); + trace(plan); + + prep = conn.prepareStatement("SELECT COUNT(*) FROM TEST " + + "WHERE CASEWHEN(ID=1, ID, ID)=? GROUP BY ID"); + prep.setInt(1, 1); + rs = prep.executeQuery(); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertFalse(rs.next()); + + prep = conn.prepareStatement("SELECT COUNT(*) FROM TEST " + + "WHERE CASE ID WHEN 1 THEN ID WHEN 2 THEN ID " + + "ELSE ID END=? GROUP BY ID"); + prep.setInt(1, 1); + rs = prep.executeQuery(); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertFalse(rs.next()); + + prep = conn.prepareStatement("SELECT * FROM TEST WHERE ? IS NULL"); + prep.setString(1, "Hello"); + rs = prep.executeQuery(); + assertFalse(rs.next()); + assertThrows(ErrorCode.UNKNOWN_DATA_TYPE_1, conn). + prepareStatement("select ? from dual union select ? from dual"); + prep = conn.prepareStatement("select cast(? as varchar) " + + "from dual union select ? from dual"); + assertEquals(2, prep.getParameterMetaData().getParameterCount()); + prep.setString(1, "a"); + prep.setString(2, "a"); + rs = prep.executeQuery(); + rs.next(); + assertEquals("a", rs.getString(1)); + assertEquals("a", rs.getString(1)); + assertFalse(rs.next()); + + stat.execute("DROP TABLE TEST"); + } + + private void testSubquery(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT)"); + stat.execute("INSERT INTO TEST VALUES(1),(2),(3)"); + PreparedStatement prep = conn.prepareStatement("select x.id, ? from " + + "(select * from test where id in(?, ?)) x where x.id*2 <> ?"); + assertEquals(4, prep.getParameterMetaData().getParameterCount()); + prep.setInt(1, 0); + prep.setInt(2, 1); + prep.setInt(3, 2); + prep.setInt(4, 4); + ResultSet rs = prep.executeQuery(); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals(0, rs.getInt(2)); + assertFalse(rs.next()); + stat.execute("DROP TABLE TEST"); + } + + private void testDataTypes(Connection conn) throws SQLException { + conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_READ_ONLY); + conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, + ResultSet.CONCUR_UPDATABLE); + Statement stat = conn.createStatement(); + PreparedStatement prep; + ResultSet rs; + trace("Create tables"); + stat.execute("CREATE TABLE T_INT" + + "(ID INT PRIMARY KEY,V INT)"); + stat.execute("CREATE TABLE T_VARCHAR" + + "(ID INT PRIMARY KEY,V VARCHAR(255))"); + stat.execute("CREATE TABLE T_DECIMAL_0" + + "(ID INT PRIMARY KEY,V DECIMAL(30,0))"); + stat.execute("CREATE TABLE T_DECIMAL_10" + + "(ID INT PRIMARY KEY,V DECIMAL(20,10))"); + stat.execute("CREATE TABLE T_DATETIME" + + "(ID INT PRIMARY KEY,V DATETIME)"); + stat.execute("CREATE TABLE T_BIGINT" + + "(ID INT PRIMARY KEY,V DECIMAL(30,0))"); + prep = conn.prepareStatement("INSERT INTO T_INT VALUES(?,?)", + ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); + prep.setInt(1, 1); + prep.setInt(2, 0); + prep.executeUpdate(); + prep.setInt(1, 2); + prep.setInt(2, -1); + prep.executeUpdate(); + prep.setInt(1, 3); + prep.setInt(2, 3); + prep.executeUpdate(); + prep.setInt(1, 4); + prep.setNull(2, Types.INTEGER, "INTEGER"); + prep.setNull(2, Types.INTEGER); + prep.executeUpdate(); + prep.setInt(1, 5); + prep.setBigDecimal(2, new java.math.BigDecimal("0")); + prep.executeUpdate(); + prep.setInt(1, 6); + prep.setString(2, "-1"); + prep.executeUpdate(); + prep.setInt(1, 7); + prep.setObject(2, 3); + prep.executeUpdate(); + prep.setObject(1, "8"); + // should throw an exception + prep.setObject(2, null); + // some databases don't allow calling setObject with null (no data type) + prep.executeUpdate(); + prep.setInt(1, 9); + prep.setObject(2, -4, Types.VARCHAR); + prep.executeUpdate(); + prep.setInt(1, 10); + prep.setObject(2, "5", Types.INTEGER); + prep.executeUpdate(); + prep.setInt(1, 11); + prep.setObject(2, null, Types.INTEGER); + prep.executeUpdate(); + prep.setInt(1, 12); + prep.setBoolean(2, true); + prep.executeUpdate(); + prep.setInt(1, 13); + prep.setBoolean(2, false); + prep.executeUpdate(); + prep.setInt(1, 14); + prep.setByte(2, (byte) -20); + prep.executeUpdate(); + prep.setInt(1, 15); + prep.setByte(2, (byte) 100); + prep.executeUpdate(); + prep.setInt(1, 16); + prep.setShort(2, (short) 30000); + prep.executeUpdate(); + prep.setInt(1, 17); + prep.setShort(2, (short) (-30000)); + prep.executeUpdate(); + prep.setInt(1, 18); + prep.setLong(2, Integer.MAX_VALUE); + prep.executeUpdate(); + prep.setInt(1, 19); + prep.setLong(2, Integer.MIN_VALUE); + prep.executeUpdate(); + + assertTrue(stat.execute("SELECT * FROM T_INT ORDER BY ID")); + rs = stat.getResultSet(); + assertResultSetOrdered(rs, new String[][] { { "1", "0" }, + { "2", "-1" }, { "3", "3" }, { "4", null }, { "5", "0" }, + { "6", "-1" }, { "7", "3" }, { "8", null }, { "9", "-4" }, + { "10", "5" }, { "11", null }, { "12", "1" }, { "13", "0" }, + { "14", "-20" }, { "15", "100" }, { "16", "30000" }, + { "17", "-30000" }, { "18", "" + Integer.MAX_VALUE }, + { "19", "" + Integer.MIN_VALUE }, }); + + prep = conn.prepareStatement("INSERT INTO T_DECIMAL_0 VALUES(?,?)"); + prep.setInt(1, 1); + prep.setLong(2, Long.MAX_VALUE); + prep.executeUpdate(); + prep.setInt(1, 2); + prep.setLong(2, Long.MIN_VALUE); + prep.executeUpdate(); + prep.setInt(1, 3); + prep.setFloat(2, 10); + prep.executeUpdate(); + prep.setInt(1, 4); + prep.setFloat(2, -20); + prep.executeUpdate(); + prep.setInt(1, 5); + prep.setFloat(2, 30); + prep.executeUpdate(); + prep.setInt(1, 6); + prep.setFloat(2, -40); + prep.executeUpdate(); + + rs = stat.executeQuery("SELECT V FROM T_DECIMAL_0 ORDER BY ID"); + checkBigDecimal(rs, new String[] { "" + Long.MAX_VALUE, + "" + Long.MIN_VALUE, "10", "-20", "30", "-40" }); + prep = conn.prepareStatement("INSERT INTO T_BIGINT VALUES(?,?)"); + prep.setInt(1, 1); + prep.setObject(2, new BigInteger("" + Long.MAX_VALUE)); + prep.executeUpdate(); + prep.setInt(1, 2); + prep.setObject(2, Long.MIN_VALUE); + prep.executeUpdate(); + prep.setInt(1, 3); + prep.setObject(2, 10); + prep.executeUpdate(); + prep.setInt(1, 4); + prep.setObject(2, -20); + prep.executeUpdate(); + prep.setInt(1, 5); + prep.setObject(2, 30); + prep.executeUpdate(); + prep.setInt(1, 6); + prep.setObject(2, -40); + prep.executeUpdate(); + prep.setInt(1, 7); + prep.setObject(2, new BigInteger("-60")); + prep.executeUpdate(); + + rs = stat.executeQuery("SELECT V FROM T_BIGINT ORDER BY ID"); + checkBigDecimal(rs, new String[] { "" + Long.MAX_VALUE, + "" + Long.MIN_VALUE, "10", "-20", "30", "-40", "-60" }); + } + + private void testGetMoreResults(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + PreparedStatement prep; + ResultSet rs; + stat.execute("CREATE TABLE TEST(ID INT)"); + stat.execute("INSERT INTO TEST VALUES(1)"); + + prep = conn.prepareStatement("SELECT * FROM TEST"); + // just to check if it doesn't throw an exception - it may be null + prep.getMetaData(); + assertTrue(prep.execute()); + rs = prep.getResultSet(); + assertFalse(prep.getMoreResults()); + assertEquals(-1, prep.getUpdateCount()); + // supposed to be closed now + assertThrows(ErrorCode.OBJECT_CLOSED, rs).next(); + assertEquals(-1, prep.getUpdateCount()); + + prep = conn.prepareStatement("UPDATE TEST SET ID = 2"); + assertFalse(prep.execute()); + assertEquals(1, prep.getUpdateCount()); + assertFalse(prep.getMoreResults(Statement.CLOSE_CURRENT_RESULT)); + assertEquals(-1, prep.getUpdateCount()); + // supposed to be closed now + assertThrows(ErrorCode.OBJECT_CLOSED, rs).next(); + assertEquals(-1, prep.getUpdateCount()); + + prep = conn.prepareStatement("DELETE FROM TEST"); + prep.executeUpdate(); + assertFalse(prep.getMoreResults()); + assertEquals(-1, prep.getUpdateCount()); + stat.execute("DROP TABLE TEST"); + } + + private void testObject(Connection conn) throws SQLException { + Statement stat = conn.createStatement(); + ResultSet rs; + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + stat.execute("INSERT INTO TEST VALUES(1, 'Hello')"); + PreparedStatement prep = conn.prepareStatement( + "SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? FROM TEST"); + prep.setObject(1, Boolean.TRUE); + prep.setObject(2, "Abc"); + prep.setObject(3, new BigDecimal("10.2")); + prep.setObject(4, (byte) 0xff); + prep.setObject(5, Short.MAX_VALUE); + prep.setObject(6, Integer.MIN_VALUE); + prep.setObject(7, Long.MAX_VALUE); + prep.setObject(8, Float.MAX_VALUE); + prep.setObject(9, Double.MAX_VALUE); + prep.setObject(10, java.sql.Date.valueOf("2001-02-03")); + prep.setObject(11, java.sql.Time.valueOf("04:05:06")); + prep.setObject(12, java.sql.Timestamp.valueOf( + "2001-02-03 04:05:06.123456789")); + prep.setObject(13, new java.util.Date(java.sql.Date.valueOf( + "2001-02-03").getTime())); + prep.setObject(14, new byte[] { 10, 20, 30 }); + prep.setObject(15, 'a', Types.JAVA_OBJECT); + prep.setObject(16, "2001-01-02", Types.DATE); + // converting to null seems strange... + prep.setObject(17, "2001-01-02", Types.NULL); + prep.setObject(18, "3.725", Types.DOUBLE); + prep.setObject(19, "23:22:21", Types.TIME); + prep.setObject(20, new java.math.BigInteger("12345"), Types.JAVA_OBJECT); + prep.setArray(21, conn.createArrayOf("TINYINT", new Object[] {(byte) 1})); + prep.setArray(22, conn.createArrayOf("SMALLINT", new Object[] {(short) -2})); + rs = prep.executeQuery(); + rs.next(); + assertTrue(rs.getObject(1).equals(Boolean.TRUE)); + assertTrue(rs.getObject(2).equals("Abc")); + assertTrue(rs.getObject(3).equals(new BigDecimal("10.2"))); + assertTrue(rs.getObject(4).equals(Integer.valueOf(-1))); + assertTrue(rs.getObject(5).equals(Integer.valueOf(Short.MAX_VALUE))); + assertTrue(rs.getObject(6).equals(Integer.MIN_VALUE)); + assertTrue(rs.getObject(7).equals(Long.MAX_VALUE)); + assertTrue(rs.getObject(8).equals(Float.MAX_VALUE)); + assertTrue(rs.getObject(9).equals(Double.MAX_VALUE)); + assertTrue(rs.getObject(10).equals( + java.sql.Date.valueOf("2001-02-03"))); + assertEquals("04:05:06", rs.getObject(11).toString()); + assertTrue(rs.getObject(11).equals( + java.sql.Time.valueOf("04:05:06"))); + assertTrue(rs.getObject(12).equals( + java.sql.Timestamp.valueOf("2001-02-03 04:05:06.123456789"))); + assertTrue(rs.getObject(13).equals( + java.sql.Timestamp.valueOf("2001-02-03 00:00:00"))); + assertEquals(new byte[] { 10, 20, 30 }, (byte[]) rs.getObject(14)); + assertTrue(rs.getObject(15).equals('a')); + assertTrue(rs.getObject(16).equals( + java.sql.Date.valueOf("2001-01-02"))); + assertTrue(rs.getObject(17) == null && rs.wasNull()); + assertTrue(rs.getObject(18).equals(3.725d)); + assertTrue(rs.getObject(19).equals( + java.sql.Time.valueOf("23:22:21"))); + assertTrue(rs.getObject(20).equals( + new java.math.BigInteger("12345"))); + Object[] a = (Object[]) ((Array) rs.getObject(21)).getArray(); + assertEquals(a[0], Integer.valueOf(1)); + a = (Object[]) ((Array) rs.getObject(22)).getArray(); + assertEquals(a[0], Integer.valueOf(-2)); + + // } else if(x instanceof java.io.Reader) { + // return session.createLob(Value.CLOB, + // TypeConverter.getInputStream((java.io.Reader)x), 0); + // } else if(x instanceof java.io.InputStream) { + // return session.createLob(Value.BLOB, (java.io.InputStream)x, 0); + // } else { + // return ValueBytes.get(TypeConverter.serialize(x)); + + stat.execute("DROP TABLE TEST"); + + } + + private int getLength() { + return getSize(LOB_SIZE, LOB_SIZE_BIG); + } + + private void testBlob(Connection conn) throws SQLException { + trace("testBlob"); + Statement stat = conn.createStatement(); + PreparedStatement prep; + ResultSet rs; + stat.execute("CREATE TABLE T_BLOB(ID INT PRIMARY KEY,V1 BLOB,V2 BLOB)"); + trace("table created"); + prep = conn.prepareStatement("INSERT INTO T_BLOB VALUES(?,?,?)"); + + prep.setInt(1, 1); + prep.setBytes(2, null); + prep.setNull(3, Types.BINARY); + prep.executeUpdate(); + + prep.setInt(1, 2); + prep.setBinaryStream(2, null, 0); + prep.setNull(3, Types.BLOB); + prep.executeUpdate(); + + int length = getLength(); + byte[] big1 = new byte[length]; + byte[] big2 = new byte[length]; + for (int i = 0; i < big1.length; i++) { + big1[i] = (byte) ((i * 11) % 254); + big2[i] = (byte) ((i * 17) % 251); + } + + prep.setInt(1, 3); + prep.setBytes(2, big1); + prep.setBytes(3, big2); + prep.executeUpdate(); + + prep.setInt(1, 4); + ByteArrayInputStream buffer; + buffer = new ByteArrayInputStream(big2); + prep.setBinaryStream(2, buffer, big2.length); + buffer = new ByteArrayInputStream(big1); + prep.setBinaryStream(3, buffer, big1.length); + prep.executeUpdate(); + try { + buffer.close(); + trace("buffer not closed"); + } catch (IOException e) { + trace("buffer closed"); + } + + prep.setInt(1, 5); + buffer = new ByteArrayInputStream(big2); + prep.setObject(2, buffer, Types.BLOB, 0); + buffer = new ByteArrayInputStream(big1); + prep.setObject(3, buffer); + prep.executeUpdate(); + + rs = stat.executeQuery("SELECT ID, V1, V2 FROM T_BLOB ORDER BY ID"); + + rs.next(); + assertEquals(1, rs.getInt(1)); + assertTrue(rs.getBytes(2) == null && rs.wasNull()); + assertTrue(rs.getBytes(3) == null && rs.wasNull()); + + rs.next(); + assertEquals(2, rs.getInt(1)); + assertTrue(rs.getBytes(2) == null && rs.wasNull()); + assertTrue(rs.getBytes(3) == null && rs.wasNull()); + + rs.next(); + assertEquals(3, rs.getInt(1)); + assertEquals(big1, rs.getBytes(2)); + assertEquals(big2, rs.getBytes(3)); + + rs.next(); + assertEquals(4, rs.getInt(1)); + assertEquals(big2, rs.getBytes(2)); + assertEquals(big1, rs.getBytes(3)); + + rs.next(); + assertEquals(5, rs.getInt(1)); + assertEquals(big2, rs.getBytes(2)); + assertEquals(big1, rs.getBytes(3)); + + assertFalse(rs.next()); + } + + private void testClob(Connection conn) throws SQLException { + trace("testClob"); + Statement stat = conn.createStatement(); + PreparedStatement prep; + ResultSet rs; + stat.execute("CREATE TABLE T_CLOB(ID INT PRIMARY KEY,V1 CLOB,V2 CLOB)"); + StringBuilder asciiBuffer = new StringBuilder(); + int len = getLength(); + for (int i = 0; i < len; i++) { + asciiBuffer.append((char) ('a' + (i % 20))); + } + String ascii1 = asciiBuffer.toString(); + String ascii2 = "Number2 " + ascii1; + prep = conn.prepareStatement("INSERT INTO T_CLOB VALUES(?,?,?)"); + + prep.setInt(1, 1); + prep.setString(2, null); + prep.setNull(3, Types.CLOB); + prep.executeUpdate(); + + prep.clearParameters(); + prep.setInt(1, 2); + prep.setAsciiStream(2, null, 0); + prep.setCharacterStream(3, null, 0); + prep.executeUpdate(); + + prep.clearParameters(); + prep.setInt(1, 3); + prep.setCharacterStream(2, + new StringReader(ascii1), ascii1.length()); + prep.setCharacterStream(3, null, 0); + prep.setAsciiStream(3, + new ByteArrayInputStream(ascii2.getBytes()), ascii2.length()); + prep.executeUpdate(); + + prep.clearParameters(); + prep.setInt(1, 4); + prep.setNull(2, Types.CLOB); + prep.setString(2, ascii2); + prep.setCharacterStream(3, null, 0); + prep.setNull(3, Types.CLOB); + prep.setString(3, ascii1); + prep.executeUpdate(); + + prep.clearParameters(); + prep.setInt(1, 5); + prep.setObject(2, new StringReader(ascii1)); + prep.setObject(3, new StringReader(ascii2), Types.CLOB, 0); + prep.executeUpdate(); + + rs = stat.executeQuery("SELECT ID, V1, V2 FROM T_CLOB ORDER BY ID"); + + rs.next(); + assertEquals(1, rs.getInt(1)); + assertTrue(rs.getCharacterStream(2) == null && rs.wasNull()); + assertTrue(rs.getAsciiStream(3) == null && rs.wasNull()); + + rs.next(); + assertEquals(2, rs.getInt(1)); + assertTrue(rs.getString(2) == null && rs.wasNull()); + assertTrue(rs.getString(3) == null && rs.wasNull()); + + rs.next(); + assertEquals(3, rs.getInt(1)); + assertEquals(ascii1, rs.getString(2)); + assertEquals(ascii2, rs.getString(3)); + + rs.next(); + assertEquals(4, rs.getInt(1)); + assertEquals(ascii2, rs.getString(2)); + assertEquals(ascii1, rs.getString(3)); + + rs.next(); + assertEquals(5, rs.getInt(1)); + assertEquals(ascii1, rs.getString(2)); + assertEquals(ascii2, rs.getString(3)); + + assertFalse(rs.next()); + assertNull(prep.getWarnings()); + prep.clearWarnings(); + assertNull(prep.getWarnings()); + assertTrue(conn == prep.getConnection()); + } + + private void testPreparedStatementWithLiteralsNone() throws SQLException { + // make sure that when the analyze table kicks in, + // it works with ALLOW_LITERALS=NONE + deleteDb("preparedStatement"); + Connection conn = getConnection( + "preparedStatement;ANALYZE_AUTO=100"); + conn.createStatement().execute( + "SET ALLOW_LITERALS NONE"); + conn.prepareStatement("CREATE TABLE test (id INT)").execute(); + PreparedStatement ps = conn.prepareStatement( + "INSERT INTO test (id) VALUES (?)"); + for (int i = 0; i < 200; i++) { + ps.setInt(1, i); + ps.executeUpdate(); + } + conn.close(); + deleteDb("preparedStatement"); + } + + private void testPreparedStatementWithIndexedParameterAndLiteralsNone() throws SQLException { + // make sure that when the analyze table kicks in, + // it works with ALLOW_LITERALS=NONE + deleteDb("preparedStatement"); + Connection conn = getConnection( + "preparedStatement;ANALYZE_AUTO=100"); + conn.createStatement().execute( + "SET ALLOW_LITERALS NONE"); + conn.prepareStatement("CREATE TABLE test (id INT)").execute(); + PreparedStatement ps = conn.prepareStatement( + "INSERT INTO test (id) VALUES (?1)"); + + ps.setInt(1, 1); + ps.executeUpdate(); + + conn.close(); + deleteDb("preparedStatement"); + } + + private void testPreparedStatementWithAnyParameter() throws SQLException { + deleteDb("preparedStatement"); + Connection conn = getConnection("preparedStatement"); + conn.prepareStatement("CREATE TABLE TEST(ID INT PRIMARY KEY, V INT UNIQUE)").execute(); + PreparedStatement ps = conn.prepareStatement("INSERT INTO TEST(ID, V) VALUES (?, ?)"); + for (int i = 0; i < 10_000; i++) { + ps.setInt(1, i); + ps.setInt(2, i * 10); + ps.executeUpdate(); + } + Integer[] values = {-100, 10, 200, 3_000, 40_000, 500_000}; + int[] expected = {1, 20, 300, 4_000}; + // Ensure that other methods return the same results + ps = conn.prepareStatement("SELECT ID FROM TEST WHERE V IN (SELECT * FROM TABLE(X INT=?)) ORDER BY ID"); + anyParameterCheck(ps, values, expected); + ps = conn.prepareStatement("SELECT ID FROM TEST INNER JOIN TABLE(X INT=?) T ON TEST.V = T.X"); + anyParameterCheck(ps, values, expected); + // Test expression = ANY(?) + ps = conn.prepareStatement("SELECT ID FROM TEST WHERE V = ANY(?)"); + assertThrows(ErrorCode.PARAMETER_NOT_SET_1, ps).executeQuery(); + anyParameterCheck(ps, values, expected); + anyParameterCheck(ps, 300, new int[] {30}); + anyParameterCheck(ps, -5, new int[0]); + ps = conn.prepareStatement("SELECT V, CASE V WHEN = ANY(?) THEN 1 ELSE 2 END FROM" + + " (VALUES DATE '2000-01-01', DATE '2010-01-01') T(V) ORDER BY V"); + ps.setObject(1, new LocalDate[] { LocalDate.of(2000, 1, 1), LocalDate.of(2030, 1, 1) }); + try (ResultSet rs = ps.executeQuery()) { + assertTrue(rs.next()); + assertEquals(LocalDate.of(2000, 1, 1), rs.getObject(1, LocalDate.class)); + assertEquals(1, rs.getInt(2)); + assertTrue(rs.next()); + assertEquals(LocalDate.of(2010, 1, 1), rs.getObject(1, LocalDate.class)); + assertEquals(2, rs.getInt(2)); + assertFalse(rs.next()); + assertEquals("CASE V WHEN = ANY(?1) THEN 1 ELSE 2 END", rs.getMetaData().getColumnLabel(2)); + } + conn.close(); + deleteDb("preparedStatement"); + } + + private void anyParameterCheck(PreparedStatement ps, Object values, int[] expected) throws SQLException { + ps.setObject(1, values); + try (ResultSet rs = ps.executeQuery()) { + for (int exp : expected) { + assertTrue(rs.next()); + assertEquals(exp, rs.getInt(1)); + } + assertFalse(rs.next()); + } + } + + private void checkBigDecimal(ResultSet rs, String[] value) throws SQLException { + for (String v : value) { + assertTrue(rs.next()); + java.math.BigDecimal x = rs.getBigDecimal(1); + trace("v=" + v + " x=" + x); + if (v == null) { + assertNull(x); + } else { + assertTrue(x.compareTo(new java.math.BigDecimal(v)) == 0); + } + } + assertFalse(rs.next()); + } + + private void testColumnMetaDataWithEquals(Connection conn) + throws SQLException { + Statement stmt = conn.createStatement(); + stmt.execute("CREATE TABLE TEST( id INT, someColumn INT )"); + PreparedStatement ps = conn + .prepareStatement("INSERT INTO TEST VALUES(?,?)"); + ps.setInt(1, 0); + ps.setInt(2, 999); + ps.execute(); + ps = conn.prepareStatement("SELECT * FROM TEST WHERE someColumn = ?"); + assertEquals(Types.INTEGER, + ps.getParameterMetaData().getParameterType(1)); + stmt.execute("DROP TABLE TEST"); + } + + private void testColumnMetaDataWithIn(Connection conn) throws SQLException { + Statement stmt = conn.createStatement(); + stmt.execute("CREATE TABLE TEST( id INT, someColumn INT )"); + PreparedStatement ps = conn + .prepareStatement("INSERT INTO TEST VALUES( ? , ? )"); + ps.setInt(1, 0); + ps.setInt(2, 999); + ps.execute(); + ps = conn + .prepareStatement("SELECT * FROM TEST WHERE someColumn IN (?,?)"); + assertEquals(Types.INTEGER, + ps.getParameterMetaData().getParameterType(1)); + stmt.execute("DROP TABLE TEST"); + } + + private void testMultipleStatements(Connection conn) throws SQLException { + assertThrows(ErrorCode.CANNOT_MIX_INDEXED_AND_UNINDEXED_PARAMS, conn).prepareStatement("SELECT ?; SELECT ?1"); + assertThrows(ErrorCode.CANNOT_MIX_INDEXED_AND_UNINDEXED_PARAMS, conn).prepareStatement("SELECT ?1; SELECT ?"); + Statement stmt = conn.createStatement(); + stmt.execute("CREATE TABLE TEST (ID IDENTITY, V INT)"); + PreparedStatement ps = conn.prepareStatement("INSERT INTO TEST(V) VALUES ?; INSERT INTO TEST(V) VALUES ?"); + ps.setInt(1, 1); + ps.setInt(2, 2); + ps.executeUpdate(); + ps = conn.prepareStatement("INSERT INTO TEST(V) VALUES ?2; INSERT INTO TEST(V) VALUES ?1;"); + ps.setInt(1, 3); + ps.setInt(2, 4); + ps.executeUpdate(); + try (ResultSet rs = stmt.executeQuery("SELECT V FROM TEST ORDER BY ID")) { + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertTrue(rs.next()); + assertEquals(4, rs.getInt(1)); + assertTrue(rs.next()); + assertEquals(3, rs.getInt(1)); + assertFalse(rs.next()); + } + stmt.execute("DROP TABLE TEST"); + ps = conn.prepareStatement("CREATE TABLE A (C1 INT);" // + + "CREATE INDEX A_IDX ON A(C1);" // + + "ALTER TABLE A ADD (C2 INT);" // + + "CREATE TABLE B AS (SELECT C1 FROM A);"); + ps.executeUpdate(); + stmt.execute("DROP TABLE A, B"); + } + + private void testAfterRollback(Connection conn) throws SQLException { + try (Statement stat = conn.createStatement()) { + try { + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + conn.setAutoCommit(false); + + // insert something into test table + stat.execute("INSERT INTO TEST VALUES(1, 'Hello')"); + + // execute 'SELECT count(*)' with prepared-statements + PreparedStatement pstmt = conn.prepareStatement("SELECT count(*) FROM TEST"); + try (ResultSet rs = pstmt.executeQuery()) { + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + } + + // rollback the insert + conn.rollback(); + + // re-execute the pstmt. + try (ResultSet rs = pstmt.executeQuery()) { + assertTrue(rs.next()); + assertEquals(0, rs.getInt(1)); + } + } finally { + // cleanup + stat.execute("DROP TABLE IF EXISTS TEST"); + conn.setAutoCommit(true); + } + } + } + +} diff --git a/h2/src/test/org/h2/test/jdbc/TestResultSet.java b/h2/src/test/org/h2/test/jdbc/TestResultSet.java new file mode 100644 index 0000000..0b0141a --- /dev/null +++ b/h2/src/test/org/h2/test/jdbc/TestResultSet.java @@ -0,0 +1,2100 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbc; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Writer; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.Date; +import java.sql.NClob; +import java.sql.PreparedStatement; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.OffsetTime; +import java.time.Period; +import java.time.ZonedDateTime; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import org.h2.api.ErrorCode; +import org.h2.api.Interval; +import org.h2.api.IntervalQualifier; +import org.h2.engine.Constants; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.IOUtils; +import org.h2.util.MathUtils; +import org.h2.util.StringUtils; + +/** + * Tests for the ResultSet implementation. + */ +public class TestResultSet extends TestDb { + + private Connection conn; + private Statement stat; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb("resultSet"); + conn = getConnection("resultSet"); + + stat = conn.createStatement(); + + testUnwrap(); + testReuseSimpleResult(); + testUnsupportedOperations(); + testAmbiguousColumnNames(); + testInsertRowWithUpdatableResultSetDefault(); + testBeforeFirstAfterLast(); + testParseSpecialValues(); + testSubstringPrecision(); + testSubstringDataType(); + testColumnLabelColumnName(); + testAbsolute(); + testFetchSize(); + testOwnUpdates(); + testUpdatePrimaryKey(); + testFindColumn(); + testColumnLength(); + testArray(); + testRowValue(); + testEnum(); + testLimitMaxRows(); + + trace("max rows=" + stat.getMaxRows()); + stat.setMaxRows(6); + trace("max rows after set to 6=" + stat.getMaxRows()); + assertTrue(stat.getMaxRows() == 6); + + testInt(); + testSmallInt(); + testBigInt(); + testVarchar(); + testDecimal(); + testDoubleFloat(); + testDatetime(); + testDatetimeWithCalendar(); + testInterval(); + testInterval8(); + testBlob(); + testClob(); + testAutoIncrement(); + + conn.close(); + deleteDb("resultSet"); + + } + + private void testUnwrap() throws SQLException { + ResultSet rs = stat.executeQuery("select 1"); + assertTrue(rs.isWrapperFor(Object.class)); + assertTrue(rs.isWrapperFor(ResultSet.class)); + assertTrue(rs.isWrapperFor(rs.getClass())); + assertFalse(rs.isWrapperFor(Integer.class)); + assertTrue(rs == rs.unwrap(Object.class)); + assertTrue(rs == rs.unwrap(ResultSet.class)); + assertTrue(rs == rs.unwrap(rs.getClass())); + assertThrows(ErrorCode.INVALID_VALUE_2, rs). + unwrap(Integer.class); + } + + private void testReuseSimpleResult() throws SQLException { + ResultSet rs = stat.executeQuery("select * from table(x int array=((1)))"); + while (rs.next()) { + rs.getString(1); + } + rs.close(); + rs = stat.executeQuery("select * from table(x int array=((1)))"); + while (rs.next()) { + rs.getString(1); + } + rs.close(); + } + + @SuppressWarnings("deprecation") + private void testUnsupportedOperations() throws SQLException { + ResultSet rs = stat.executeQuery("select 1 as x from dual"); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, rs). + getUnicodeStream(1); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, rs). + getUnicodeStream("x"); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, rs). + getObject(1, Collections.emptyMap()); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, rs). + getObject("x", Collections.emptyMap()); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, rs). + getRef(1); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, rs). + getRef("x"); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, rs). + getURL(1); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, rs). + getURL("x"); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, rs). + getRowId(1); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, rs). + getRowId("x"); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, rs). + updateRef(1, (Ref) null); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, rs). + updateRef("x", (Ref) null); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, rs). + updateRowId(1, (RowId) null); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, rs). + updateRowId("x", (RowId) null); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, rs). + getCursorName(); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, rs). + setFetchDirection(ResultSet.FETCH_REVERSE); + } + + private void testAmbiguousColumnNames() throws SQLException { + stat.execute("create table test(id int)"); + stat.execute("insert into test values(1)"); + ResultSet rs = stat.executeQuery( + "select 1 x, 2 x, 3 x, 4 x, 5 x, 6 x from test"); + rs.next(); + assertEquals(1, rs.getInt("x")); + stat.execute("drop table test"); + } + + private void testInsertRowWithUpdatableResultSetDefault() throws Exception { + stat.execute("create table test(id int primary key, " + + "data varchar(255) default 'Hello')"); + PreparedStatement prep = conn.prepareStatement("select * from test", + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); + ResultSet rs = prep.executeQuery(); + int idx = 1; + rs.moveToInsertRow(); + rs.updateInt(1, idx++); + rs.insertRow(); + rs.close(); + rs = stat.executeQuery("select * from test"); + assertTrue(rs.next()); + assertEquals("Hello", rs.getString(2)); + assertEquals("Hello", rs.getString("data")); + assertEquals("Hello", rs.getNString(2)); + assertEquals("Hello", rs.getNString("data")); + assertEquals("Hello", IOUtils.readStringAndClose( + rs.getNCharacterStream(2), -1)); + assertEquals("Hello", IOUtils.readStringAndClose( + rs.getNCharacterStream("data"), -1)); + assertEquals("Hello", IOUtils.readStringAndClose( + rs.getNClob(2).getCharacterStream(), -1)); + assertEquals("Hello", IOUtils.readStringAndClose( + rs.getNClob("data").getCharacterStream(), -1)); + + rs = prep.executeQuery(); + + rs.moveToInsertRow(); + rs.updateInt(1, idx++); + rs.updateNString(2, "Hello"); + rs.insertRow(); + + rs.moveToInsertRow(); + rs.updateInt(1, idx++); + rs.updateNString("data", "Hello"); + rs.insertRow(); + + Clob c; + Writer w; + + rs.moveToInsertRow(); + rs.updateInt(1, idx++); + c = conn.createClob(); + w = c.setCharacterStream(1); + w.write("Hello"); + w.close(); + rs.updateClob(2, c); + rs.insertRow(); + + rs.moveToInsertRow(); + rs.updateInt(1, idx++); + c = conn.createClob(); + w = c.setCharacterStream(1); + w.write("Hello"); + w.close(); + rs.updateClob("data", c); + rs.insertRow(); + + NClob nc; + + rs.moveToInsertRow(); + rs.updateInt(1, idx++); + nc = conn.createNClob(); + w = nc.setCharacterStream(1); + w.write("Hello"); + w.close(); + rs.updateNClob(2, nc); + rs.insertRow(); + + rs.moveToInsertRow(); + rs.updateInt(1, idx++); + nc = conn.createNClob(); + w = nc.setCharacterStream(1); + w.write("Hello"); + w.close(); + rs.updateNClob("data", nc); + rs.insertRow(); + + InputStream in; + + rs.moveToInsertRow(); + rs.updateInt(1, idx++); + in = new ByteArrayInputStream("Hello".getBytes(StandardCharsets.UTF_8)); + rs.updateAsciiStream(2, in); + rs.insertRow(); + + rs.moveToInsertRow(); + rs.updateInt(1, idx++); + in = new ByteArrayInputStream("Hello".getBytes(StandardCharsets.UTF_8)); + rs.updateAsciiStream("data", in); + rs.insertRow(); + + rs.moveToInsertRow(); + rs.updateInt(1, idx++); + in = new ByteArrayInputStream("Hello-".getBytes(StandardCharsets.UTF_8)); + rs.updateAsciiStream(2, in, 5); + rs.insertRow(); + + rs.moveToInsertRow(); + rs.updateInt(1, idx++); + in = new ByteArrayInputStream("Hello-".getBytes(StandardCharsets.UTF_8)); + rs.updateAsciiStream("data", in, 5); + rs.insertRow(); + + rs.moveToInsertRow(); + rs.updateInt(1, idx++); + in = new ByteArrayInputStream("Hello-".getBytes(StandardCharsets.UTF_8)); + rs.updateAsciiStream(2, in, 5L); + rs.insertRow(); + + rs.moveToInsertRow(); + rs.updateInt(1, idx++); + in = new ByteArrayInputStream("Hello-".getBytes(StandardCharsets.UTF_8)); + rs.updateAsciiStream("data", in, 5L); + rs.insertRow(); + + rs = stat.executeQuery("select * from test"); + for (int i = 1; i < idx; i++) { + assertTrue(rs.next()); + assertEquals("Hello", rs.getString(2)); + } + assertFalse(rs.next()); + + stat.execute("drop table test"); + } + + private void testBeforeFirstAfterLast() throws SQLException { + stat.executeUpdate("create table test(id int)"); + stat.executeUpdate("insert into test values(1)"); + // With a result + ResultSet rs = stat.executeQuery("select * from test"); + assertTrue(rs.isBeforeFirst()); + assertFalse(rs.isAfterLast()); + rs.next(); + assertFalse(rs.isBeforeFirst()); + assertFalse(rs.isAfterLast()); + rs.next(); + assertFalse(rs.isBeforeFirst()); + assertTrue(rs.isAfterLast()); + rs.close(); + // With no result + rs = stat.executeQuery("select * from test where 1 = 2"); + assertFalse(rs.isBeforeFirst()); + assertFalse(rs.isAfterLast()); + rs.next(); + assertFalse(rs.isBeforeFirst()); + assertFalse(rs.isAfterLast()); + rs.close(); + stat.execute("drop table test"); + } + + private void testParseSpecialValues() throws SQLException { + for (int i = -10; i < 10; i++) { + testParseSpecialValue("" + ((long) Integer.MIN_VALUE + i)); + testParseSpecialValue("" + ((long) Integer.MAX_VALUE + i)); + BigInteger bi = BigInteger.valueOf(i); + testParseSpecialValue(bi.add(BigInteger.valueOf(Long.MIN_VALUE)).toString()); + testParseSpecialValue(bi.add(BigInteger.valueOf(Long.MAX_VALUE)).toString()); + } + } + + private void testParseSpecialValue(String x) throws SQLException { + Object expected; + expected = new BigDecimal(x); + try { + expected = Long.decode(x); + expected = Integer.decode(x); + } catch (Exception e) { + // ignore + } + ResultSet rs = stat.executeQuery("call " + x); + rs.next(); + Object o = rs.getObject(1); + assertEquals(expected.getClass().getName(), o.getClass().getName()); + assertTrue(expected.equals(o)); + } + + private void testSubstringDataType() throws SQLException { + ResultSet rs = stat.executeQuery("select substr(x, 1, 1) from system_range(1, 1)"); + rs.next(); + assertEquals(Types.VARCHAR, rs.getMetaData().getColumnType(1)); + } + + private void testColumnLabelColumnName() throws SQLException { + ResultSet rs = stat.executeQuery("select x as y from system_range(1, 1)"); + rs.next(); + rs.getString("x"); + rs.getString("y"); + rs.close(); + rs = conn.getMetaData().getColumns(null, null, null, null); + ResultSetMetaData meta = rs.getMetaData(); + int columnCount = meta.getColumnCount(); + String[] columnName = new String[columnCount]; + for (int i = 1; i <= columnCount; i++) { + // columnName[i - 1] = meta.getColumnLabel(i); + columnName[i - 1] = meta.getColumnName(i); + } + while (rs.next()) { + for (int i = 0; i < columnCount; i++) { + rs.getObject(columnName[i]); + } + } + } + + private void testAbsolute() throws SQLException { + // stat.execute("SET MAX_MEMORY_ROWS 90"); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY)"); + // there was a problem when more than MAX_MEMORY_ROWS where in the + // result set + stat.execute("INSERT INTO TEST SELECT X FROM SYSTEM_RANGE(1, 200)"); + Statement s2 = conn.createStatement( + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + ResultSet rs = s2.executeQuery("SELECT * FROM TEST ORDER BY ID"); + for (int i = 100; i > 0; i--) { + rs.absolute(i); + assertEquals(i, rs.getInt(1)); + } + stat.execute("DROP TABLE TEST"); + } + + private void testFetchSize() throws SQLException { + if (!config.networked || config.memory) { + return; + } + ResultSet rs = stat.executeQuery("SELECT * FROM SYSTEM_RANGE(1, 100)"); + int a = stat.getFetchSize(); + int b = rs.getFetchSize(); + assertEquals(a, b); + rs.setFetchDirection(ResultSet.FETCH_FORWARD); + rs.setFetchSize(b + 1); + b = rs.getFetchSize(); + assertEquals(a + 1, b); + } + + private void testOwnUpdates() throws SQLException { + DatabaseMetaData meta = conn.getMetaData(); + for (int i = 0; i < 3; i++) { + int type = i == 0 ? ResultSet.TYPE_FORWARD_ONLY : + i == 1 ? ResultSet.TYPE_SCROLL_INSENSITIVE : + ResultSet.TYPE_SCROLL_SENSITIVE; + assertTrue(meta.ownUpdatesAreVisible(type)); + assertFalse(meta.ownDeletesAreVisible(type)); + assertFalse(meta.ownInsertsAreVisible(type)); + } + stat = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, + ResultSet.CONCUR_UPDATABLE); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + stat.execute("INSERT INTO TEST VALUES(1, 'Hello')"); + stat.execute("INSERT INTO TEST VALUES(2, 'World')"); + ResultSet rs; + rs = stat.executeQuery("SELECT ID, NAME FROM TEST ORDER BY ID"); + rs.next(); + rs.next(); + rs.updateString(2, "Hallo"); + rs.updateRow(); + assertEquals("Hallo", rs.getString(2)); + stat.execute("DROP TABLE TEST"); + } + + private void testUpdatePrimaryKey() throws SQLException { + stat = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, + ResultSet.CONCUR_UPDATABLE); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + stat.execute("INSERT INTO TEST VALUES(1, 'Hello')"); + ResultSet rs = stat.executeQuery("SELECT * FROM TEST"); + rs.next(); + rs.updateInt(1, 2); + rs.updateRow(); + rs.updateInt(1, 3); + rs.updateRow(); + stat.execute("DROP TABLE TEST"); + } + + private void checkPrecision(int expected, String sql) throws SQLException { + ResultSetMetaData meta = stat.executeQuery(sql).getMetaData(); + assertEquals(expected, meta.getPrecision(1)); + } + + private void testSubstringPrecision() throws SQLException { + trace("testSubstringPrecision"); + stat.execute("CREATE TABLE TEST(ID INT, NAME VARCHAR(10))"); + stat.execute("INSERT INTO TEST VALUES(1, 'Hello'), (2, 'WorldPeace')"); + checkPrecision(1, "SELECT SUBSTR(NAME, 12, 4) FROM TEST"); + checkPrecision(9, "SELECT SUBSTR(NAME, 2) FROM TEST"); + checkPrecision(10, "SELECT SUBSTR(NAME, ID) FROM TEST"); + checkPrecision(4, "SELECT SUBSTR(NAME, 2, 4) FROM TEST"); + checkPrecision(3, "SELECT SUBSTR(NAME, 8, 4) FROM TEST"); + checkPrecision(4, "SELECT SUBSTR(NAME, 7, 4) FROM TEST"); + checkPrecision(8, "SELECT SUBSTR(NAME, 3, ID*0) FROM TEST"); + stat.execute("DROP TABLE TEST"); + } + + private void testFindColumn() throws SQLException { + trace("testFindColumn"); + ResultSet rs; + stat.execute("CREATE TABLE TEST(ID INT, NAME VARCHAR)"); + rs = stat.executeQuery("SELECT * FROM TEST"); + assertEquals(1, rs.findColumn("ID")); + assertEquals(2, rs.findColumn("NAME")); + assertEquals(1, rs.findColumn("id")); + assertEquals(2, rs.findColumn("name")); + assertEquals(1, rs.findColumn("Id")); + assertEquals(2, rs.findColumn("Name")); + assertEquals(1, rs.findColumn("TEST.ID")); + assertEquals(2, rs.findColumn("TEST.NAME")); + assertEquals(1, rs.findColumn("Test.Id")); + assertEquals(2, rs.findColumn("Test.Name")); + stat.execute("DROP TABLE TEST"); + + stat.execute("CREATE TABLE TEST(ID INT, NAME VARCHAR, DATA VARCHAR)"); + rs = stat.executeQuery("SELECT * FROM TEST"); + assertEquals(1, rs.findColumn("ID")); + assertEquals(2, rs.findColumn("NAME")); + assertEquals(3, rs.findColumn("DATA")); + assertEquals(1, rs.findColumn("id")); + assertEquals(2, rs.findColumn("name")); + assertEquals(3, rs.findColumn("data")); + assertEquals(1, rs.findColumn("Id")); + assertEquals(2, rs.findColumn("Name")); + assertEquals(3, rs.findColumn("Data")); + assertEquals(1, rs.findColumn("TEST.ID")); + assertEquals(2, rs.findColumn("TEST.NAME")); + assertEquals(3, rs.findColumn("TEST.DATA")); + assertEquals(1, rs.findColumn("Test.Id")); + assertEquals(2, rs.findColumn("Test.Name")); + assertEquals(3, rs.findColumn("Test.Data")); + stat.execute("DROP TABLE TEST"); + + } + + private void testColumnLength() throws SQLException { + trace("testColumnDisplayLength"); + ResultSet rs; + ResultSetMetaData meta; + + stat.execute("CREATE TABLE one (ID INT, NAME VARCHAR(255))"); + rs = stat.executeQuery("select * from one"); + meta = rs.getMetaData(); + assertEquals("ID", meta.getColumnLabel(1)); + assertEquals(11, meta.getColumnDisplaySize(1)); + assertEquals("NAME", meta.getColumnLabel(2)); + assertEquals(255, meta.getColumnDisplaySize(2)); + stat.execute("DROP TABLE one"); + + rs = stat.executeQuery("select 1, 'Hello' union select 2, 'Hello World!'"); + meta = rs.getMetaData(); + assertEquals(11, meta.getColumnDisplaySize(1)); + assertEquals(12, meta.getColumnDisplaySize(2)); + + rs = stat.executeQuery("explain select * from dual"); + meta = rs.getMetaData(); + assertEquals(Constants.MAX_STRING_LENGTH, meta.getColumnDisplaySize(1)); + assertEquals(Constants.MAX_STRING_LENGTH, meta.getPrecision(1)); + + rs = stat.executeQuery("script"); + meta = rs.getMetaData(); + assertEquals(Constants.MAX_STRING_LENGTH, meta.getColumnDisplaySize(1)); + assertEquals(Constants.MAX_STRING_LENGTH, meta.getPrecision(1)); + + rs = stat.executeQuery("select group_concat(table_name) " + + "from information_schema.tables"); + rs.next(); + meta = rs.getMetaData(); + assertEquals(Constants.MAX_STRING_LENGTH, meta.getColumnDisplaySize(1)); + assertEquals(Constants.MAX_STRING_LENGTH, meta.getPrecision(1)); + + } + + private void testLimitMaxRows() throws SQLException { + trace("Test LimitMaxRows"); + ResultSet rs; + stat.execute("CREATE TABLE one (C CHARACTER(10))"); + rs = stat.executeQuery("SELECT C || C FROM one;"); + ResultSetMetaData md = rs.getMetaData(); + assertEquals(20, md.getPrecision(1)); + rs = stat.executeQuery("SELECT CHAR(10), " + + "CONCAT(C,C,C), HEXTORAW(C), RAWTOHEX(C) FROM one"); + ResultSetMetaData meta = rs.getMetaData(); + assertEquals(1, meta.getPrecision(1)); + assertEquals(30, meta.getPrecision(2)); + assertEquals(2, meta.getPrecision(3)); + assertEquals(40, meta.getPrecision(4)); + stat.execute("DROP TABLE one"); + } + + private void testAutoIncrement() throws SQLException { + trace("Test AutoIncrement"); + stat.execute("DROP TABLE IF EXISTS TEST"); + ResultSet rs; + stat.execute("CREATE TABLE TEST(ID IDENTITY NOT NULL, NAME VARCHAR NULL)"); + + stat.execute("INSERT INTO TEST(NAME) VALUES('Hello')", + Statement.RETURN_GENERATED_KEYS); + rs = stat.getGeneratedKeys(); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + + stat.execute("INSERT INTO TEST(NAME) VALUES('World')", + Statement.RETURN_GENERATED_KEYS); + rs = stat.getGeneratedKeys(); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + + rs = stat.executeQuery("SELECT ID AS I, NAME AS N, ID+1 AS IP1 FROM TEST"); + ResultSetMetaData meta = rs.getMetaData(); + assertTrue(meta.isAutoIncrement(1)); + assertFalse(meta.isAutoIncrement(2)); + assertFalse(meta.isAutoIncrement(3)); + assertEquals(ResultSetMetaData.columnNoNulls, meta.isNullable(1)); + assertEquals(ResultSetMetaData.columnNullable, meta.isNullable(2)); + assertEquals(ResultSetMetaData.columnNullableUnknown, meta.isNullable(3)); + assertTrue(rs.next()); + assertTrue(rs.next()); + assertFalse(rs.next()); + + } + + private void testInt() throws SQLException { + trace("Test INT"); + ResultSet rs; + Object o; + + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY,\"VALUE\" INT)"); + stat.execute("INSERT INTO TEST VALUES(1,-1)"); + stat.execute("INSERT INTO TEST VALUES(2,0)"); + stat.execute("INSERT INTO TEST VALUES(3,1)"); + stat.execute("INSERT INTO TEST VALUES(4," + Integer.MAX_VALUE + ")"); + stat.execute("INSERT INTO TEST VALUES(5," + Integer.MIN_VALUE + ")"); + stat.execute("INSERT INTO TEST VALUES(6,NULL)"); + // this should not be read - maxrows=6 + stat.execute("INSERT INTO TEST VALUES(7,NULL)"); + + // MySQL compatibility (is this required?) + // rs=stat.executeQuery("SELECT * FROM TEST T ORDER BY ID"); + // check(rs.findColumn("T.ID"), 1); + // check(rs.findColumn("T.NAME"), 2); + + rs = stat.executeQuery("SELECT *, NULL AS N FROM TEST ORDER BY ID"); + + // MySQL compatibility + assertEquals(1, rs.findColumn("TEST.ID")); + assertEquals(2, rs.findColumn("TEST.VALUE")); + + ResultSetMetaData meta = rs.getMetaData(); + assertEquals(3, meta.getColumnCount()); + assertEquals("resultSet".toUpperCase(), meta.getCatalogName(1)); + assertTrue("PUBLIC".equals(meta.getSchemaName(2))); + assertTrue("TEST".equals(meta.getTableName(1))); + assertTrue("ID".equals(meta.getColumnName(1))); + assertTrue("VALUE".equals(meta.getColumnName(2))); + assertFalse(meta.isAutoIncrement(1)); + assertTrue(meta.isCaseSensitive(1)); + assertTrue(meta.isSearchable(1)); + assertFalse(meta.isCurrency(1)); + assertTrue(meta.getColumnDisplaySize(1) > 0); + assertTrue(meta.isSigned(1)); + assertTrue(meta.isSearchable(2)); + assertEquals(ResultSetMetaData.columnNoNulls, meta.isNullable(1)); + assertFalse(meta.isReadOnly(1)); + assertTrue(meta.isWritable(1)); + assertFalse(meta.isDefinitelyWritable(1)); + assertTrue(meta.getColumnDisplaySize(1) > 0); + assertTrue(meta.getColumnDisplaySize(2) > 0); + assertEquals(Void.class.getName(), meta.getColumnClassName(3)); + + assertTrue(rs.getRow() == 0); + assertResultSetMeta(rs, 3, new String[] { "ID", "VALUE", "N" }, + new int[] { Types.INTEGER, Types.INTEGER, + Types.NULL }, new int[] { 32, 32, 1 }, new int[] { 0, 0, 0 }); + rs.next(); + assertEquals(ResultSet.CONCUR_READ_ONLY, rs.getConcurrency()); + assertEquals(ResultSet.FETCH_FORWARD, rs.getFetchDirection()); + trace("default fetch size=" + rs.getFetchSize()); + // 0 should be an allowed value (but it's not defined what is actually + // means) + rs.setFetchSize(0); + assertThrows(ErrorCode.INVALID_VALUE_2, rs).setFetchSize(-1); + // fetch size 100 is bigger than maxrows - not allowed + assertThrows(ErrorCode.INVALID_VALUE_2, rs).setFetchSize(100); + rs.setFetchSize(6); + + assertTrue(rs.getRow() == 1); + assertEquals(2, rs.findColumn("VALUE")); + assertEquals(2, rs.findColumn("value")); + assertEquals(2, rs.findColumn("Value")); + assertEquals(2, rs.findColumn("Value")); + assertEquals(1, rs.findColumn("ID")); + assertEquals(1, rs.findColumn("id")); + assertEquals(1, rs.findColumn("Id")); + assertEquals(1, rs.findColumn("iD")); + assertTrue(rs.getInt(2) == -1 && !rs.wasNull()); + assertTrue(rs.getInt("VALUE") == -1 && !rs.wasNull()); + assertTrue(rs.getInt("value") == -1 && !rs.wasNull()); + assertTrue(rs.getInt("Value") == -1 && !rs.wasNull()); + assertTrue(rs.getString("Value").equals("-1") && !rs.wasNull()); + + o = rs.getObject("value"); + trace(o.getClass().getName()); + assertTrue(o instanceof Integer); + assertTrue((Integer) o == -1); + o = rs.getObject("value", Integer.class); + trace(o.getClass().getName()); + assertTrue(o instanceof Integer); + assertTrue((Integer) o == -1); + o = rs.getObject(2); + trace(o.getClass().getName()); + assertTrue(o instanceof Integer); + assertTrue((Integer) o == -1); + o = rs.getObject(2, Integer.class); + trace(o.getClass().getName()); + assertTrue(o instanceof Integer); + assertTrue((Integer) o == -1); + assertTrue(rs.getBoolean("Value")); + assertTrue(rs.getByte("Value") == (byte) -1); + assertTrue(rs.getShort("Value") == (short) -1); + assertTrue(rs.getLong("Value") == -1); + assertTrue(rs.getFloat("Value") == -1.0); + assertTrue(rs.getDouble("Value") == -1.0); + + assertTrue(rs.getString("Value").equals("-1") && !rs.wasNull()); + assertTrue(rs.getInt("ID") == 1 && !rs.wasNull()); + assertTrue(rs.getInt("id") == 1 && !rs.wasNull()); + assertTrue(rs.getInt("Id") == 1 && !rs.wasNull()); + assertTrue(rs.getInt(1) == 1 && !rs.wasNull()); + rs.next(); + assertTrue(rs.getRow() == 2); + assertTrue(rs.getInt(2) == 0 && !rs.wasNull()); + assertFalse(rs.getBoolean(2)); + assertTrue(rs.getByte(2) == 0); + assertTrue(rs.getShort(2) == 0); + assertTrue(rs.getLong(2) == 0); + assertTrue(rs.getFloat(2) == 0.0); + assertTrue(rs.getDouble(2) == 0.0); + assertTrue(rs.getString(2).equals("0") && !rs.wasNull()); + assertTrue(rs.getInt(1) == 2 && !rs.wasNull()); + rs.next(); + assertTrue(rs.getRow() == 3); + assertTrue(rs.getInt("ID") == 3 && !rs.wasNull()); + assertTrue(rs.getInt("VALUE") == 1 && !rs.wasNull()); + rs.next(); + assertTrue(rs.getRow() == 4); + assertTrue(rs.getInt("ID") == 4 && !rs.wasNull()); + assertTrue(rs.getInt("VALUE") == Integer.MAX_VALUE && !rs.wasNull()); + rs.next(); + assertTrue(rs.getRow() == 5); + assertTrue(rs.getInt("id") == 5 && !rs.wasNull()); + assertTrue(rs.getInt("value") == Integer.MIN_VALUE && !rs.wasNull()); + assertTrue(rs.getString(1).equals("5") && !rs.wasNull()); + rs.next(); + assertTrue(rs.getRow() == 6); + assertTrue(rs.getInt("id") == 6 && !rs.wasNull()); + assertTrue(rs.getInt("value") == 0 && rs.wasNull()); + assertTrue(rs.getInt(2) == 0 && rs.wasNull()); + assertTrue(rs.getInt(1) == 6 && !rs.wasNull()); + assertTrue(rs.getString(1).equals("6") && !rs.wasNull()); + assertTrue(rs.getString(2) == null && rs.wasNull()); + o = rs.getObject(2); + assertNull(o); + assertTrue(rs.wasNull()); + o = rs.getObject(2, Integer.class); + assertNull(o); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + assertEquals(0, rs.getRow()); + // there is one more row, but because of setMaxRows we don't get it + + stat.execute("DROP TABLE TEST"); + stat.setMaxRows(0); + } + + private void testSmallInt() throws SQLException { + trace("Test SMALLINT"); + ResultSet rs; + Object o; + + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY,\"VALUE\" SMALLINT)"); + stat.execute("INSERT INTO TEST VALUES(1,-1)"); + stat.execute("INSERT INTO TEST VALUES(2,0)"); + stat.execute("INSERT INTO TEST VALUES(3,1)"); + stat.execute("INSERT INTO TEST VALUES(4," + Short.MAX_VALUE + ")"); + stat.execute("INSERT INTO TEST VALUES(5," + Short.MIN_VALUE + ")"); + stat.execute("INSERT INTO TEST VALUES(6,NULL)"); + + // MySQL compatibility (is this required?) + // rs=stat.executeQuery("SELECT * FROM TEST T ORDER BY ID"); + // check(rs.findColumn("T.ID"), 1); + // check(rs.findColumn("T.NAME"), 2); + + rs = stat.executeQuery("SELECT *, NULL AS N FROM TEST ORDER BY ID"); + + // MySQL compatibility + assertEquals(1, rs.findColumn("TEST.ID")); + assertEquals(2, rs.findColumn("TEST.VALUE")); + + assertTrue(rs.getRow() == 0); + assertResultSetMeta(rs, 3, new String[] { "ID", "VALUE", "N" }, + new int[] { Types.INTEGER, Types.SMALLINT, + Types.NULL }, new int[] { 32, 16, 1 }, new int[] { 0, 0, 0 }); + rs.next(); + + assertTrue(rs.getRow() == 1); + assertEquals(2, rs.findColumn("VALUE")); + assertEquals(2, rs.findColumn("value")); + assertEquals(2, rs.findColumn("Value")); + assertEquals(2, rs.findColumn("Value")); + assertEquals(1, rs.findColumn("ID")); + assertEquals(1, rs.findColumn("id")); + assertEquals(1, rs.findColumn("Id")); + assertEquals(1, rs.findColumn("iD")); + assertTrue(rs.getShort(2) == -1 && !rs.wasNull()); + assertTrue(rs.getShort("VALUE") == -1 && !rs.wasNull()); + assertTrue(rs.getShort("value") == -1 && !rs.wasNull()); + assertTrue(rs.getShort("Value") == -1 && !rs.wasNull()); + assertTrue(rs.getString("Value").equals("-1") && !rs.wasNull()); + + o = rs.getObject("value"); + trace(o.getClass().getName()); + assertTrue(o.getClass() == Integer.class); + assertTrue(((Number) o).intValue() == -1); + o = rs.getObject("value", Short.class); + trace(o.getClass().getName()); + assertTrue(o instanceof Short); + assertTrue((Short) o == -1); + o = rs.getObject(2); + trace(o.getClass().getName()); + assertTrue(o.getClass() == Integer.class); + assertTrue(((Number) o).intValue() == -1); + o = rs.getObject(2, Short.class); + trace(o.getClass().getName()); + assertTrue(o instanceof Short); + assertTrue((Short) o == -1); + assertTrue(rs.getBoolean("Value")); + assertTrue(rs.getByte("Value") == (byte) -1); + assertTrue(rs.getInt("Value") == -1); + assertTrue(rs.getLong("Value") == -1); + assertTrue(rs.getFloat("Value") == -1.0); + assertTrue(rs.getDouble("Value") == -1.0); + + assertTrue(rs.getString("Value").equals("-1") && !rs.wasNull()); + assertTrue(rs.getShort("ID") == 1 && !rs.wasNull()); + assertTrue(rs.getShort("id") == 1 && !rs.wasNull()); + assertTrue(rs.getShort("Id") == 1 && !rs.wasNull()); + assertTrue(rs.getShort(1) == 1 && !rs.wasNull()); + rs.next(); + + assertTrue(rs.getRow() == 2); + assertTrue(rs.getShort(2) == 0 && !rs.wasNull()); + assertFalse(rs.getBoolean(2)); + assertTrue(rs.getByte(2) == 0); + assertTrue(rs.getInt(2) == 0); + assertTrue(rs.getLong(2) == 0); + assertTrue(rs.getFloat(2) == 0.0); + assertTrue(rs.getDouble(2) == 0.0); + assertTrue(rs.getString(2).equals("0") && !rs.wasNull()); + assertTrue(rs.getShort(1) == 2 && !rs.wasNull()); + rs.next(); + + assertTrue(rs.getRow() == 3); + assertTrue(rs.getShort("ID") == 3 && !rs.wasNull()); + assertTrue(rs.getShort("VALUE") == 1 && !rs.wasNull()); + rs.next(); + + assertTrue(rs.getRow() == 4); + assertTrue(rs.getShort("ID") == 4 && !rs.wasNull()); + assertTrue(rs.getShort("VALUE") == Short.MAX_VALUE && !rs.wasNull()); + rs.next(); + + assertTrue(rs.getRow() == 5); + assertTrue(rs.getShort("id") == 5 && !rs.wasNull()); + assertTrue(rs.getShort("value") == Short.MIN_VALUE && !rs.wasNull()); + assertTrue(rs.getString(1).equals("5") && !rs.wasNull()); + rs.next(); + + assertTrue(rs.getRow() == 6); + assertTrue(rs.getShort("id") == 6 && !rs.wasNull()); + assertTrue(rs.getShort("value") == 0 && rs.wasNull()); + assertTrue(rs.getShort(2) == 0 && rs.wasNull()); + assertTrue(rs.getShort(1) == 6 && !rs.wasNull()); + assertTrue(rs.getString(1).equals("6") && !rs.wasNull()); + assertTrue(rs.getString(2) == null && rs.wasNull()); + o = rs.getObject(2); + assertNull(o); + assertTrue(rs.wasNull()); + o = rs.getObject(2, Short.class); + assertNull(o); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + assertEquals(0, rs.getRow()); + + stat.execute("DROP TABLE TEST"); + stat.setMaxRows(0); + } + + private void testBigInt() throws SQLException { + trace("Test SMALLINT"); + ResultSet rs; + Object o; + + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY,\"VALUE\" BIGINT)"); + stat.execute("INSERT INTO TEST VALUES(1,-1)"); + stat.execute("INSERT INTO TEST VALUES(2,0)"); + stat.execute("INSERT INTO TEST VALUES(3,1)"); + stat.execute("INSERT INTO TEST VALUES(4," + Long.MAX_VALUE + ")"); + stat.execute("INSERT INTO TEST VALUES(5," + Long.MIN_VALUE + ")"); + stat.execute("INSERT INTO TEST VALUES(6,NULL)"); + + // MySQL compatibility (is this required?) + // rs=stat.executeQuery("SELECT * FROM TEST T ORDER BY ID"); + // check(rs.findColumn("T.ID"), 1); + // check(rs.findColumn("T.NAME"), 2); + + rs = stat.executeQuery("SELECT *, NULL AS N FROM TEST ORDER BY ID"); + + // MySQL compatibility + assertEquals(1, rs.findColumn("TEST.ID")); + assertEquals(2, rs.findColumn("TEST.VALUE")); + + assertTrue(rs.getRow() == 0); + assertResultSetMeta(rs, 3, new String[] { "ID", "VALUE", "N" }, + new int[] { Types.INTEGER, Types.BIGINT, + Types.NULL }, new int[] { 32, 64, 1 }, new int[] { 0, 0, 0 }); + rs.next(); + + assertTrue(rs.getRow() == 1); + assertEquals(2, rs.findColumn("VALUE")); + assertEquals(2, rs.findColumn("value")); + assertEquals(2, rs.findColumn("Value")); + assertEquals(2, rs.findColumn("Value")); + assertEquals(1, rs.findColumn("ID")); + assertEquals(1, rs.findColumn("id")); + assertEquals(1, rs.findColumn("Id")); + assertEquals(1, rs.findColumn("iD")); + assertTrue(rs.getLong(2) == -1 && !rs.wasNull()); + assertTrue(rs.getLong("VALUE") == -1 && !rs.wasNull()); + assertTrue(rs.getLong("value") == -1 && !rs.wasNull()); + assertTrue(rs.getLong("Value") == -1 && !rs.wasNull()); + assertTrue(rs.getString("Value").equals("-1") && !rs.wasNull()); + + o = rs.getObject("value"); + trace(o.getClass().getName()); + assertTrue(o instanceof Long); + assertTrue((Long) o == -1); + o = rs.getObject("value", Long.class); + trace(o.getClass().getName()); + assertTrue(o instanceof Long); + assertTrue((Long) o == -1); + o = rs.getObject("value", BigInteger.class); + trace(o.getClass().getName()); + assertTrue(o instanceof BigInteger); + assertTrue(((BigInteger) o).longValue() == -1); + o = rs.getObject(2); + trace(o.getClass().getName()); + assertTrue(o instanceof Long); + assertTrue((Long) o == -1); + o = rs.getObject(2, Long.class); + trace(o.getClass().getName()); + assertTrue(o instanceof Long); + assertTrue((Long) o == -1); + o = rs.getObject(2, BigInteger.class); + trace(o.getClass().getName()); + assertTrue(o instanceof BigInteger); + assertTrue(((BigInteger) o).longValue() == -1); + assertTrue(rs.getBoolean("Value")); + assertTrue(rs.getByte("Value") == (byte) -1); + assertTrue(rs.getShort("Value") == -1); + assertTrue(rs.getInt("Value") == -1); + assertTrue(rs.getFloat("Value") == -1.0); + assertTrue(rs.getDouble("Value") == -1.0); + + assertTrue(rs.getString("Value").equals("-1") && !rs.wasNull()); + assertTrue(rs.getLong("ID") == 1 && !rs.wasNull()); + assertTrue(rs.getLong("id") == 1 && !rs.wasNull()); + assertTrue(rs.getLong("Id") == 1 && !rs.wasNull()); + assertTrue(rs.getLong(1) == 1 && !rs.wasNull()); + rs.next(); + + assertTrue(rs.getRow() == 2); + assertTrue(rs.getLong(2) == 0 && !rs.wasNull()); + assertFalse(rs.getBoolean(2)); + assertTrue(rs.getByte(2) == 0); + assertTrue(rs.getShort(2) == 0); + assertTrue(rs.getInt(2) == 0); + assertTrue(rs.getFloat(2) == 0.0); + assertTrue(rs.getDouble(2) == 0.0); + assertTrue(rs.getString(2).equals("0") && !rs.wasNull()); + assertTrue(rs.getLong(1) == 2 && !rs.wasNull()); + rs.next(); + + assertTrue(rs.getRow() == 3); + assertTrue(rs.getLong("ID") == 3 && !rs.wasNull()); + assertTrue(rs.getLong("VALUE") == 1 && !rs.wasNull()); + rs.next(); + + assertTrue(rs.getRow() == 4); + assertTrue(rs.getLong("ID") == 4 && !rs.wasNull()); + assertTrue(rs.getLong("VALUE") == Long.MAX_VALUE && !rs.wasNull()); + rs.next(); + + assertTrue(rs.getRow() == 5); + assertTrue(rs.getLong("id") == 5 && !rs.wasNull()); + assertTrue(rs.getLong("value") == Long.MIN_VALUE && !rs.wasNull()); + assertTrue(rs.getString(1).equals("5") && !rs.wasNull()); + rs.next(); + + assertTrue(rs.getRow() == 6); + assertTrue(rs.getLong("id") == 6 && !rs.wasNull()); + assertTrue(rs.getLong("value") == 0 && rs.wasNull()); + assertTrue(rs.getLong(2) == 0 && rs.wasNull()); + assertTrue(rs.getLong(1) == 6 && !rs.wasNull()); + assertTrue(rs.getString(1).equals("6") && !rs.wasNull()); + assertTrue(rs.getString(2) == null && rs.wasNull()); + o = rs.getObject(2); + assertNull(o); + assertTrue(rs.wasNull()); + o = rs.getObject(2, Long.class); + assertNull(o); + assertTrue(rs.wasNull()); + assertFalse(rs.next()); + assertEquals(0, rs.getRow()); + + stat.execute("DROP TABLE TEST"); + stat.setMaxRows(0); + } + + private void testVarchar() throws SQLException { + trace("Test VARCHAR"); + ResultSet rs; + Object o; + + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY,\"VALUE\" VARCHAR(255))"); + stat.execute("INSERT INTO TEST VALUES(1,'')"); + stat.execute("INSERT INTO TEST VALUES(2,' ')"); + stat.execute("INSERT INTO TEST VALUES(3,' ')"); + stat.execute("INSERT INTO TEST VALUES(4,NULL)"); + stat.execute("INSERT INTO TEST VALUES(5,'Hi')"); + stat.execute("INSERT INTO TEST VALUES(6,' Hi ')"); + stat.execute("INSERT INTO TEST VALUES(7,'Joe''s')"); + stat.execute("INSERT INTO TEST VALUES(8,'{escape}')"); + stat.execute("INSERT INTO TEST VALUES(9,'\\n')"); + stat.execute("INSERT INTO TEST VALUES(10,'\\''')"); + stat.execute("INSERT INTO TEST VALUES(11,'\\%')"); + rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + assertResultSetMeta(rs, 2, new String[] { "ID", "VALUE" }, + new int[] { Types.INTEGER, Types.VARCHAR }, new int[] { + 32, 255 }, new int[] { 0, 0 }); + String value; + rs.next(); + value = rs.getString(2); + trace("Value: <" + value + "> (should be: <>)"); + assertTrue(value != null && value.equals("") && !rs.wasNull()); + assertTrue(rs.getInt(1) == 1 && !rs.wasNull()); + rs.next(); + value = rs.getString(2); + trace("Value: <" + value + "> (should be: < >)"); + assertTrue(rs.getString(2).equals(" ") && !rs.wasNull()); + assertTrue(rs.getInt(1) == 2 && !rs.wasNull()); + rs.next(); + value = rs.getString(2); + trace("Value: <" + value + "> (should be: < >)"); + assertTrue(rs.getString(2).equals(" ") && !rs.wasNull()); + assertTrue(rs.getInt(1) == 3 && !rs.wasNull()); + rs.next(); + value = rs.getString(2); + trace("Value: <" + value + "> (should be: )"); + assertTrue(rs.getString(2) == null && rs.wasNull()); + assertTrue(rs.getInt(1) == 4 && !rs.wasNull()); + rs.next(); + value = rs.getString(2); + trace("Value: <" + value + "> (should be: )"); + assertTrue(rs.getInt(1) == 5 && !rs.wasNull()); + assertTrue(rs.getString(2).equals("Hi") && !rs.wasNull()); + o = rs.getObject("value"); + trace(o.getClass().getName()); + assertTrue(o instanceof String); + assertTrue(o.toString().equals("Hi")); + o = rs.getObject("value", String.class); + trace(o.getClass().getName()); + assertTrue(o instanceof String); + assertTrue(o.equals("Hi")); + rs.next(); + value = rs.getString(2); + trace("Value: <" + value + "> (should be: < Hi >)"); + assertTrue(rs.getInt(1) == 6 && !rs.wasNull()); + assertTrue(rs.getString(2).equals(" Hi ") && !rs.wasNull()); + rs.next(); + value = rs.getString(2); + trace("Value: <" + value + "> (should be: )"); + assertTrue(rs.getInt(1) == 7 && !rs.wasNull()); + assertTrue(rs.getString(2).equals("Joe's") && !rs.wasNull()); + rs.next(); + value = rs.getString(2); + trace("Value: <" + value + "> (should be: <{escape}>)"); + assertTrue(rs.getInt(1) == 8 && !rs.wasNull()); + assertTrue(rs.getString(2).equals("{escape}") && !rs.wasNull()); + rs.next(); + value = rs.getString(2); + trace("Value: <" + value + "> (should be: <\\n>)"); + assertTrue(rs.getInt(1) == 9 && !rs.wasNull()); + assertTrue(rs.getString(2).equals("\\n") && !rs.wasNull()); + rs.next(); + value = rs.getString(2); + trace("Value: <" + value + "> (should be: <\\'>)"); + assertTrue(rs.getInt(1) == 10 && !rs.wasNull()); + assertTrue(rs.getString(2).equals("\\'") && !rs.wasNull()); + rs.next(); + value = rs.getString(2); + trace("Value: <" + value + "> (should be: <\\%>)"); + assertTrue(rs.getInt(1) == 11 && !rs.wasNull()); + assertTrue(rs.getString(2).equals("\\%") && !rs.wasNull()); + assertFalse(rs.next()); + stat.execute("DROP TABLE TEST"); + } + + private void testDecimal() throws SQLException { + trace("Test DECIMAL"); + ResultSet rs; + Object o; + + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY,\"VALUE\" DECIMAL(10,2))"); + stat.execute("INSERT INTO TEST VALUES(1,-1)"); + stat.execute("INSERT INTO TEST VALUES(2,.0)"); + stat.execute("INSERT INTO TEST VALUES(3,1.)"); + stat.execute("INSERT INTO TEST VALUES(4,12345678.89)"); + stat.execute("INSERT INTO TEST VALUES(6,99999998.99)"); + stat.execute("INSERT INTO TEST VALUES(7,-99999998.99)"); + stat.execute("INSERT INTO TEST VALUES(8,NULL)"); + rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + assertResultSetMeta(rs, 2, new String[] { "ID", "VALUE" }, + new int[] { Types.INTEGER, Types.DECIMAL }, new int[] { + 32, 10 }, new int[] { 0, 2 }); + BigDecimal bd; + + rs.next(); + assertTrue(rs.getInt(1) == 1); + assertFalse(rs.wasNull()); + assertTrue(rs.getInt(2) == -1); + assertFalse(rs.wasNull()); + bd = rs.getBigDecimal(2); + assertTrue(bd.compareTo(new BigDecimal("-1.00")) == 0); + assertFalse(rs.wasNull()); + o = rs.getObject(2); + trace(o.getClass().getName()); + assertTrue(o instanceof BigDecimal); + assertTrue(((BigDecimal) o).compareTo(new BigDecimal("-1.00")) == 0); + o = rs.getObject(2, BigDecimal.class); + trace(o.getClass().getName()); + assertTrue(o instanceof BigDecimal); + assertTrue(((BigDecimal) o).compareTo(new BigDecimal("-1.00")) == 0); + + rs.next(); + assertTrue(rs.getInt(1) == 2); + assertFalse(rs.wasNull()); + assertTrue(rs.getInt(2) == 0); + assertFalse(rs.wasNull()); + bd = rs.getBigDecimal(2); + assertTrue(bd.compareTo(new BigDecimal("0.00")) == 0); + assertFalse(rs.wasNull()); + + rs.next(); + checkColumnBigDecimal(rs, 2, 1, "1.00"); + + rs.next(); + checkColumnBigDecimal(rs, 2, 12345679, "12345678.89"); + + rs.next(); + checkColumnBigDecimal(rs, 2, 99999999, "99999998.99"); + + rs.next(); + checkColumnBigDecimal(rs, 2, -99999999, "-99999998.99"); + + rs.next(); + checkColumnBigDecimal(rs, 2, 0, null); + + assertFalse(rs.next()); + stat.execute("DROP TABLE TEST"); + + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY,\"VALUE\" DECIMAL(22,2))"); + stat.execute("INSERT INTO TEST VALUES(1,-12345678909876543210)"); + stat.execute("INSERT INTO TEST VALUES(2,12345678901234567890.12345)"); + rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + rs.next(); + assertEquals(new BigDecimal("-12345678909876543210.00"), rs.getBigDecimal(2)); + assertEquals(new BigInteger("-12345678909876543210"), rs.getObject(2, BigInteger.class)); + rs.next(); + assertEquals(new BigDecimal("12345678901234567890.12"), rs.getBigDecimal(2)); + assertEquals(new BigInteger("12345678901234567890"), rs.getObject(2, BigInteger.class)); + stat.execute("DROP TABLE TEST"); + } + + private void testDoubleFloat() throws SQLException { + trace("Test DOUBLE - FLOAT"); + ResultSet rs; + Object o; + + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, D DOUBLE, R REAL, F DECFLOAT)"); + stat.execute("INSERT INTO TEST VALUES(1, -1, -1, -1)"); + stat.execute("INSERT INTO TEST VALUES(2, .0, .0, .0)"); + stat.execute("INSERT INTO TEST VALUES(3, 1., 1., 1.)"); + stat.execute("INSERT INTO TEST VALUES(4, 12345678.89, 12345678.89, 12345678.89)"); + stat.execute("INSERT INTO TEST VALUES(6, 99999999.99, 99999999.99, 99999999.99)"); + stat.execute("INSERT INTO TEST VALUES(7, -99999999.99, -99999999.99, -99999999.99)"); + stat.execute("INSERT INTO TEST VALUES(8, NULL, NULL, NULL)"); + stat.execute("INSERT INTO TEST VALUES(9, '-Infinity', '-Infinity', '-Infinity')"); + stat.execute("INSERT INTO TEST VALUES(10, 'Infinity', 'Infinity', 'Infinity')"); + stat.execute("INSERT INTO TEST VALUES(11, 'NaN', 'NaN', 'NaN')"); + rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + assertResultSetMeta(rs, 4, new String[] { "ID", "D", "R", "F" }, + null, + new int[] { 32, 53, 24, 100_000 }, new int[] { 0, 0, 0, 0 }); + ResultSetMetaData md = rs.getMetaData(); + assertEquals("INTEGER", md.getColumnTypeName(1)); + assertEquals("DOUBLE PRECISION", md.getColumnTypeName(2)); + assertEquals("REAL", md.getColumnTypeName(3)); + assertEquals("DECFLOAT", md.getColumnTypeName(4)); + BigDecimal bd; + rs.next(); + assertTrue(rs.getInt(1) == 1); + assertFalse(rs.wasNull()); + assertTrue(rs.getInt(2) == -1); + assertTrue(rs.getInt(3) == -1); + assertFalse(rs.wasNull()); + bd = rs.getBigDecimal(2); + assertTrue(bd.compareTo(new BigDecimal("-1.00")) == 0); + assertFalse(rs.wasNull()); + o = rs.getObject(2); + trace(o.getClass().getName()); + assertTrue(o instanceof Double); + assertTrue(((Double) o).compareTo(-1d) == 0); + o = rs.getObject(2, Double.class); + trace(o.getClass().getName()); + assertTrue(o instanceof Double); + assertTrue(((Double) o).compareTo(-1d) == 0); + o = rs.getObject(3); + trace(o.getClass().getName()); + assertTrue(o instanceof Float); + assertTrue(((Float) o).compareTo(-1f) == 0); + o = rs.getObject(3, Float.class); + trace(o.getClass().getName()); + assertTrue(o instanceof Float); + assertTrue(((Float) o).compareTo(-1f) == 0); + o = rs.getObject(4); + trace(o.getClass().getName()); + assertTrue(o instanceof BigDecimal); + assertEquals(BigDecimal.valueOf(-1L, 0), o); + o = rs.getObject(4, BigDecimal.class); + trace(o.getClass().getName()); + assertTrue(o instanceof BigDecimal); + assertEquals(BigDecimal.valueOf(-1L, 0), o); + rs.next(); + assertTrue(rs.getInt(1) == 2); + assertFalse(rs.wasNull()); + assertTrue(rs.getInt(2) == 0); + assertFalse(rs.wasNull()); + assertTrue(rs.getInt(3) == 0); + assertFalse(rs.wasNull()); + assertTrue(rs.getInt(4) == 0); + assertFalse(rs.wasNull()); + bd = rs.getBigDecimal(2); + assertTrue(bd.compareTo(new BigDecimal("0.00")) == 0); + assertFalse(rs.wasNull()); + bd = rs.getBigDecimal(3); + assertTrue(bd.compareTo(new BigDecimal("0.00")) == 0); + assertFalse(rs.wasNull()); + bd = rs.getBigDecimal(4); + assertTrue(bd.compareTo(new BigDecimal("0.00")) == 0); + assertFalse(rs.wasNull()); + rs.next(); + assertEquals(1.0, rs.getDouble(2)); + assertEquals(1.0f, rs.getFloat(3)); + assertEquals(BigDecimal.ONE, rs.getBigDecimal(4)); + rs.next(); + assertEquals(12345678.89, rs.getDouble(2)); + assertEquals(12345678.89f, rs.getFloat(3)); + assertEquals(BigDecimal.valueOf(12_345_678_89L, 2), rs.getBigDecimal(4)); + rs.next(); + assertEquals(99999999.99, rs.getDouble(2)); + assertEquals(99999999.99f, rs.getFloat(3)); + assertEquals(BigDecimal.valueOf(99_999_999_99L, 2), rs.getBigDecimal(4)); + rs.next(); + assertEquals(-99999999.99, rs.getDouble(2)); + assertEquals(-99999999.99f, rs.getFloat(3)); + assertEquals(BigDecimal.valueOf(-99_999_999_99L, 2), rs.getBigDecimal(4)); + rs.next(); + checkColumnBigDecimal(rs, 2, 0, null); + checkColumnBigDecimal(rs, 3, 0, null); + checkColumnBigDecimal(rs, 4, 0, null); + rs.next(); + assertEquals(Float.NEGATIVE_INFINITY, rs.getFloat(2)); + assertEquals(Double.NEGATIVE_INFINITY, rs.getDouble(3)); + assertThrows(ErrorCode.DATA_CONVERSION_ERROR_1, rs).getBigDecimal(4); + assertThrows(ErrorCode.DATA_CONVERSION_ERROR_1, rs).getObject(4); + assertEquals(Double.NEGATIVE_INFINITY, rs.getDouble(4)); + assertEquals("-Infinity", rs.getString(4)); + rs.next(); + assertEquals(Float.POSITIVE_INFINITY, rs.getFloat(2)); + assertEquals(Double.POSITIVE_INFINITY, rs.getDouble(3)); + assertThrows(ErrorCode.DATA_CONVERSION_ERROR_1, rs).getBigDecimal(4); + assertThrows(ErrorCode.DATA_CONVERSION_ERROR_1, rs).getObject(4); + assertEquals(Double.POSITIVE_INFINITY, rs.getDouble(4)); + assertEquals("Infinity", rs.getString(4)); + rs.next(); + assertEquals(Float.NaN, rs.getFloat(2)); + assertEquals(Double.NaN, rs.getDouble(3)); + assertThrows(ErrorCode.DATA_CONVERSION_ERROR_1, rs).getBigDecimal(4); + assertThrows(ErrorCode.DATA_CONVERSION_ERROR_1, rs).getObject(4); + assertEquals(Double.NaN, rs.getDouble(4)); + assertEquals("NaN", rs.getString(4)); + assertFalse(rs.next()); + stat.execute("DROP TABLE TEST"); + } + + private void testDatetime() throws SQLException { + trace("Test DATETIME"); + ResultSet rs; + Object o; + + rs = stat.executeQuery("call date '99999-12-23'"); + rs.next(); + assertEquals("99999-12-23", rs.getString(1)); + rs = stat.executeQuery("call timestamp '99999-12-23 01:02:03.000'"); + rs.next(); + assertEquals("99999-12-23 01:02:03", rs.getString(1)); + rs = stat.executeQuery("call date '-99999-12-23'"); + rs.next(); + assertEquals("-99999-12-23", rs.getString(1)); + rs = stat.executeQuery("call timestamp '-99999-12-23 01:02:03.000'"); + rs.next(); + assertEquals("-99999-12-23 01:02:03", rs.getString(1)); + + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY,\"VALUE\" DATETIME)"); + stat.execute("INSERT INTO TEST VALUES(1,DATE '2011-11-11')"); + stat.execute("INSERT INTO TEST VALUES(2,TIMESTAMP '2002-02-02 02:02:02')"); + stat.execute("INSERT INTO TEST VALUES(3,TIMESTAMP '1800-1-1 0:0:0')"); + stat.execute("INSERT INTO TEST VALUES(4,TIMESTAMP '9999-12-31 23:59:59')"); + stat.execute("INSERT INTO TEST VALUES(5,NULL)"); + rs = stat.executeQuery("SELECT 0 ID, " + + "TIMESTAMP '9999-12-31 23:59:59' \"VALUE\" FROM TEST ORDER BY ID"); + assertResultSetMeta(rs, 2, new String[] { "ID", "VALUE" }, + new int[] { Types.INTEGER, Types.TIMESTAMP }, + new int[] { 32, 29 }, new int[] { 0, 9 }); + rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + assertResultSetMeta(rs, 2, new String[] { "ID", "VALUE" }, + new int[] { Types.INTEGER, Types.TIMESTAMP }, + new int[] { 32, 26 }, new int[] { 0, 6 }); + rs.next(); + java.sql.Date date; + java.sql.Time time; + java.sql.Timestamp ts; + date = rs.getDate(2); + assertFalse(rs.wasNull()); + time = rs.getTime(2); + assertFalse(rs.wasNull()); + ts = rs.getTimestamp(2); + assertFalse(rs.wasNull()); + trace("Date: " + date.toString() + " Time:" + time.toString() + + " Timestamp:" + ts.toString()); + trace("Date ms: " + date.getTime() + " Time ms:" + time.getTime() + + " Timestamp ms:" + ts.getTime()); + trace("1970 ms: " + java.sql.Timestamp.valueOf( + "1970-01-01 00:00:00.0").getTime()); + assertEquals(java.sql.Timestamp.valueOf( + "2011-11-11 00:00:00.0").getTime(), date.getTime()); + assertEquals(java.sql.Timestamp.valueOf( + "1970-01-01 00:00:00.0").getTime(), time.getTime()); + assertEquals(java.sql.Timestamp.valueOf( + "2011-11-11 00:00:00.0").getTime(), ts.getTime()); + assertTrue(date.equals( + java.sql.Date.valueOf("2011-11-11"))); + assertTrue(time.equals( + java.sql.Time.valueOf("00:00:00"))); + assertTrue(ts.equals( + java.sql.Timestamp.valueOf("2011-11-11 00:00:00.0"))); + assertFalse(rs.wasNull()); + o = rs.getObject(2); + trace(o.getClass().getName()); + assertTrue(o instanceof java.sql.Timestamp); + assertTrue(((java.sql.Timestamp) o).equals( + java.sql.Timestamp.valueOf("2011-11-11 00:00:00.0"))); + assertFalse(rs.wasNull()); + o = rs.getObject(2, java.sql.Timestamp.class); + trace(o.getClass().getName()); + assertTrue(o instanceof java.sql.Timestamp); + assertTrue(((java.sql.Timestamp) o).equals( + java.sql.Timestamp.valueOf("2011-11-11 00:00:00.0"))); + assertFalse(rs.wasNull()); + o = rs.getObject(2, java.util.Date.class); + assertTrue(o.getClass() == java.util.Date.class); + assertEquals(((java.util.Date) o).getTime(), + java.sql.Timestamp.valueOf("2011-11-11 00:00:00.0").getTime()); + o = rs.getObject(2, Calendar.class); + assertTrue(o instanceof Calendar); + assertEquals(((Calendar) o).getTimeInMillis(), + java.sql.Timestamp.valueOf("2011-11-11 00:00:00.0").getTime()); + rs.next(); + + date = rs.getDate("VALUE"); + assertFalse(rs.wasNull()); + time = rs.getTime("VALUE"); + assertFalse(rs.wasNull()); + ts = rs.getTimestamp("VALUE"); + assertFalse(rs.wasNull()); + trace("Date: " + date.toString() + + " Time:" + time.toString() + " Timestamp:" + ts.toString()); + assertEquals("2002-02-02", date.toString()); + assertEquals("02:02:02", time.toString()); + assertEquals("2002-02-02 02:02:02.0", ts.toString()); + rs.next(); + + assertEquals("1800-01-01", rs.getObject("value", LocalDate.class).toString()); + assertEquals("00:00:00", rs.getTime("value").toString()); + assertEquals("00:00", rs.getObject("value", LocalTime.class).toString()); + assertEquals("1800-01-01T00:00", rs.getObject("value", LocalDateTime.class).toString()); + rs.next(); + + assertEquals("9999-12-31", rs.getDate("Value").toString()); + assertEquals("9999-12-31", rs.getObject("Value", LocalDate.class).toString()); + assertEquals("23:59:59", rs.getTime("Value").toString()); + assertEquals("23:59:59", rs.getObject("Value", LocalTime.class).toString()); + assertEquals("9999-12-31 23:59:59.0", rs.getTimestamp("Value").toString()); + assertEquals("9999-12-31T23:59:59", rs.getObject("Value", LocalDateTime.class).toString()); + rs.next(); + + assertTrue(rs.getDate("Value") == null && rs.wasNull()); + assertTrue(rs.getTime("vALUe") == null && rs.wasNull()); + assertTrue(rs.getTimestamp(2) == null && rs.wasNull()); + assertTrue(rs.getObject(2, LocalDateTime.class) == null && rs.wasNull()); + assertFalse(rs.next()); + + rs = stat.executeQuery("SELECT DATE '2001-02-03' D, " + + "TIME '14:15:16', " + + "TIMESTAMP '2007-08-09 10:11:12.141516171' TS FROM TEST"); + rs.next(); + + date = (Date) rs.getObject(1); + time = (Time) rs.getObject(2); + ts = (Timestamp) rs.getObject(3); + assertEquals("2001-02-03", date.toString()); + assertEquals("14:15:16", time.toString()); + assertEquals("2007-08-09 10:11:12.141516171", ts.toString()); + date = rs.getObject(1, Date.class); + time = rs.getObject(2, Time.class); + ts = rs.getObject(3, Timestamp.class); + assertEquals("2001-02-03", date.toString()); + assertEquals("14:15:16", time.toString()); + assertEquals("2007-08-09 10:11:12.141516171", ts.toString()); + assertEquals("2001-02-03", rs.getObject(1, LocalDate.class).toString()); + assertEquals("14:15:16", rs.getObject(2, LocalTime.class).toString()); + assertEquals("2007-08-09T10:11:12.141516171", rs.getObject(3, LocalDateTime.class).toString()); + + stat.execute("DROP TABLE TEST"); + + rs = stat.executeQuery("SELECT LOCALTIME, CURRENT_TIME"); + rs.next(); + assertEquals(rs.getTime(1), rs.getTime(2)); + rs = stat.executeQuery("SELECT LOCALTIMESTAMP, CURRENT_TIMESTAMP"); + rs.next(); + assertEquals(rs.getTimestamp(1), rs.getTimestamp(2)); + + rs = stat.executeQuery("SELECT DATE '-1000000000-01-01', " + "DATE '1000000000-12-31'"); + rs.next(); + assertEquals("-999999999-01-01", rs.getObject(1, LocalDate.class).toString()); + assertEquals("+999999999-12-31", rs.getObject(2, LocalDate.class).toString()); + + rs = stat.executeQuery("SELECT TIMESTAMP '-1000000000-01-01 00:00:00', " + + "TIMESTAMP '1000000000-12-31 23:59:59.999999999'"); + rs.next(); + assertEquals("-999999999-01-01T00:00", rs.getObject(1, LocalDateTime.class).toString()); + assertEquals("+999999999-12-31T23:59:59.999999999", rs.getObject(2, LocalDateTime.class).toString()); + + rs = stat.executeQuery("SELECT TIMESTAMP WITH TIME ZONE '-1000000000-01-01 00:00:00Z', " + + "TIMESTAMP WITH TIME ZONE '1000000000-12-31 23:59:59.999999999Z', " + + "TIMESTAMP WITH TIME ZONE '-1000000000-01-01 00:00:00+18', " + + "TIMESTAMP WITH TIME ZONE '1000000000-12-31 23:59:59.999999999-18'"); + rs.next(); + assertEquals("-999999999-01-01T00:00Z", rs.getObject(1, OffsetDateTime.class).toString()); + assertEquals("+999999999-12-31T23:59:59.999999999Z", rs.getObject(2, OffsetDateTime.class).toString()); + assertEquals("-999999999-01-01T00:00+18:00", rs.getObject(3, OffsetDateTime.class).toString()); + assertEquals("+999999999-12-31T23:59:59.999999999-18:00", rs.getObject(4, OffsetDateTime.class).toString()); + assertEquals("-999999999-01-01T00:00Z", rs.getObject(1, ZonedDateTime.class).toString()); + assertEquals("+999999999-12-31T23:59:59.999999999Z", rs.getObject(2, ZonedDateTime.class).toString()); + assertEquals("-999999999-01-01T00:00+18:00", rs.getObject(3, ZonedDateTime.class).toString()); + assertEquals("+999999999-12-31T23:59:59.999999999-18:00", rs.getObject(4, ZonedDateTime.class).toString()); + assertEquals("-1000000000-01-01T00:00:00Z", rs.getObject(1, Instant.class).toString()); + assertEquals("+1000000000-12-31T23:59:59.999999999Z", rs.getObject(2, Instant.class).toString()); + assertEquals("-1000000000-01-01T00:00:00Z", rs.getObject(3, Instant.class).toString()); + assertEquals("+1000000000-12-31T23:59:59.999999999Z", rs.getObject(4, Instant.class).toString()); + + rs = stat.executeQuery("SELECT LOCALTIME, CURRENT_TIME"); + rs.next(); + assertEquals(rs.getObject(1, LocalTime.class), rs.getObject(2, LocalTime.class)); + assertEquals(rs.getObject(1, OffsetTime.class), rs.getObject(2, OffsetTime.class)); + rs = stat.executeQuery("SELECT LOCALTIMESTAMP, CURRENT_TIMESTAMP"); + rs.next(); + assertEquals(rs.getObject(1, LocalDateTime.class), rs.getObject(2, LocalDateTime.class)); + assertEquals(rs.getObject(1, OffsetDateTime.class), rs.getObject(2, OffsetDateTime.class)); + } + + private void testDatetimeWithCalendar() throws SQLException { + trace("Test DATETIME with Calendar"); + ResultSet rs; + + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, " + + "D DATE, T TIME, TS TIMESTAMP(9))"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST VALUES(?, ?, ?, ?)"); + GregorianCalendar regular = new GregorianCalendar(); + GregorianCalendar other = null; + // search a locale that has a _different_ raw offset + long testTime = java.sql.Date.valueOf("2001-02-03").getTime(); + for (String s : TimeZone.getAvailableIDs()) { + TimeZone zone = TimeZone.getTimeZone(s); + long rawOffsetDiff = regular.getTimeZone().getRawOffset() - + zone.getRawOffset(); + // must not be the same timezone (not 0 h and not 24 h difference + // as for Pacific/Auckland and Etc/GMT+12) + if (rawOffsetDiff != 0 && rawOffsetDiff != 1000 * 60 * 60 * 24) { + if (regular.getTimeZone().getOffset(testTime) != + zone.getOffset(testTime)) { + other = new GregorianCalendar(zone); + break; + } + } + } + + trace("regular offset = " + regular.getTimeZone().getRawOffset() + + " other = " + other.getTimeZone().getRawOffset()); + + prep.setInt(1, 0); + prep.setDate(2, null, regular); + prep.setTime(3, null, regular); + prep.setTimestamp(4, null, regular); + prep.execute(); + + prep.setInt(1, 1); + prep.setDate(2, null, other); + prep.setTime(3, null, other); + prep.setTimestamp(4, null, other); + prep.execute(); + + prep.setInt(1, 2); + prep.setDate(2, java.sql.Date.valueOf("2001-02-03"), regular); + prep.setTime(3, java.sql.Time.valueOf("04:05:06"), regular); + prep.setTimestamp(4, + java.sql.Timestamp.valueOf("2007-08-09 10:11:12.131415"), regular); + prep.execute(); + + prep.setInt(1, 3); + prep.setDate(2, java.sql.Date.valueOf("2101-02-03"), other); + prep.setTime(3, java.sql.Time.valueOf("14:05:06"), other); + prep.setTimestamp(4, + java.sql.Timestamp.valueOf("2107-08-09 10:11:12.131415"), other); + prep.execute(); + + prep.setInt(1, 4); + prep.setDate(2, java.sql.Date.valueOf("2101-02-03")); + prep.setTime(3, java.sql.Time.valueOf("14:05:06")); + prep.setTimestamp(4, + java.sql.Timestamp.valueOf("2107-08-09 10:11:12.131415")); + prep.execute(); + + prep.setInt(1, 5); + prep.setDate(2, java.sql.Date.valueOf("2101-02-03"), null); + prep.setTime(3, java.sql.Time.valueOf("14:05:06"), null); + prep.setTimestamp(4, java.sql.Timestamp.valueOf("2107-08-09 10:11:12.131415"), null); + prep.execute(); + + rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + assertResultSetMeta(rs, 4, + new String[] { "ID", "D", "T", "TS" }, + new int[] { Types.INTEGER, Types.DATE, + Types.TIME, Types.TIMESTAMP }, + new int[] { 32, 10, 8, 29 }, new int[] { 0, 0, 0, 9 }); + + rs.next(); + assertEquals(0, rs.getInt(1)); + assertTrue(rs.getDate(2, regular) == null && rs.wasNull()); + assertTrue(rs.getTime(3, regular) == null && rs.wasNull()); + assertTrue(rs.getTimestamp(3, regular) == null && rs.wasNull()); + + rs.next(); + assertEquals(1, rs.getInt(1)); + assertTrue(rs.getDate(2, other) == null && rs.wasNull()); + assertTrue(rs.getTime(3, other) == null && rs.wasNull()); + assertTrue(rs.getTimestamp(3, other) == null && rs.wasNull()); + + rs.next(); + assertEquals(2, rs.getInt(1)); + assertEquals("2001-02-03", rs.getDate(2, regular).toString()); + assertEquals("04:05:06", rs.getTime(3, regular).toString()); + assertFalse(rs.getTime(3, other).toString().equals("04:05:06")); + assertEquals("2007-08-09 10:11:12.131415", + rs.getTimestamp(4, regular).toString()); + assertFalse(rs.getTimestamp(4, other).toString(). + equals("2007-08-09 10:11:12.131415")); + + rs.next(); + assertEquals(3, rs.getInt("ID")); + assertFalse(rs.getTimestamp("TS", regular).toString(). + equals("2107-08-09 10:11:12.131415")); + assertEquals("2107-08-09 10:11:12.131415", + rs.getTimestamp("TS", other).toString()); + assertFalse(rs.getTime("T", regular).toString().equals("14:05:06")); + assertEquals("14:05:06", + rs.getTime("T", other).toString()); + // checkFalse(rs.getDate(2, regular).toString(), "2101-02-03"); + // check(rs.getDate("D", other).toString(), "2101-02-03"); + + rs.next(); + assertEquals(4, rs.getInt("ID")); + assertEquals("2107-08-09 10:11:12.131415", + rs.getTimestamp("TS").toString()); + assertEquals("14:05:06", rs.getTime("T").toString()); + assertEquals("2101-02-03", rs.getDate("D").toString()); + + rs.next(); + assertEquals(5, rs.getInt("ID")); + assertEquals("2107-08-09 10:11:12.131415", + rs.getTimestamp("TS").toString()); + assertEquals("14:05:06", rs.getTime("T").toString()); + assertEquals("2101-02-03", rs.getDate("D").toString()); + + assertFalse(rs.next()); + stat.execute("DROP TABLE TEST"); + } + + private void testInterval() throws SQLException { + trace("Test INTERVAL"); + ResultSet rs; + + rs = stat.executeQuery("CALL INTERVAL '10' YEAR"); + rs.next(); + assertEquals("INTERVAL '10' YEAR", rs.getString(1)); + Interval expected = new Interval(IntervalQualifier.YEAR, false, 10, 0); + assertEquals(expected, rs.getObject(1)); + assertEquals(expected, rs.getObject(1, Interval.class)); + ResultSetMetaData metaData = rs.getMetaData(); + assertEquals(Types.OTHER, metaData.getColumnType(1)); + assertEquals("INTERVAL YEAR", metaData.getColumnTypeName(1)); + assertEquals(Interval.class.getName(), metaData.getColumnClassName(1)); + assertEquals("INTERVAL '-111222333444555666' YEAR".length(), metaData.getColumnDisplaySize(1)); + // Intervals are not numbers + assertFalse(metaData.isSigned(1)); + } + + private void testInterval8() throws SQLException { + trace("Test INTERVAL 8"); + ResultSet rs; + + rs = stat.executeQuery("CALL INTERVAL '1-2' YEAR TO MONTH"); + rs.next(); + assertEquals("INTERVAL '1-2' YEAR TO MONTH", rs.getString(1)); + assertEquals(Period.of(1, 2, 0), rs.getObject(1, Period.class)); + assertThrows(ErrorCode.DATA_CONVERSION_ERROR_1, rs).getObject(1, Duration.class); + + rs = stat.executeQuery("CALL INTERVAL '-3.1' SECOND"); + rs.next(); + assertEquals("INTERVAL '-3.1' SECOND", rs.getString(1)); + assertEquals(Duration.ofSeconds(-4, 900_000_000), rs.getObject(1, Duration.class)); + assertThrows(ErrorCode.DATA_CONVERSION_ERROR_1, rs).getObject(1, Period.class); + } + + private void testBlob() throws SQLException { + trace("Test BLOB"); + ResultSet rs; + + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY,\"VALUE\" BLOB)"); + stat.execute("INSERT INTO TEST VALUES(1,X'01010101')"); + stat.execute("INSERT INTO TEST VALUES(2,X'02020202')"); + stat.execute("INSERT INTO TEST VALUES(3,X'00')"); + stat.execute("INSERT INTO TEST VALUES(4,X'ffffff')"); + stat.execute("INSERT INTO TEST VALUES(5,X'0bcec1')"); + stat.execute("INSERT INTO TEST VALUES(6,X'03030303')"); + stat.execute("INSERT INTO TEST VALUES(7,NULL)"); + byte[] random = new byte[0x10000]; + MathUtils.randomBytes(random); + stat.execute("INSERT INTO TEST VALUES(8, X'" + StringUtils.convertBytesToHex(random) + "')"); + rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + assertResultSetMeta(rs, 2, new String[] { "ID", "VALUE" }, + new int[] { Types.INTEGER, Types.BLOB }, new int[] { + 32, Integer.MAX_VALUE }, new int[] { 0, 0 }); + rs.next(); + + assertEqualsWithNull(new byte[] { (byte) 0x01, (byte) 0x01, + (byte) 0x01, (byte) 0x01 }, + rs.getBytes(2)); + assertFalse(rs.wasNull()); + assertEqualsWithNull(new byte[] { (byte) 0x01, (byte) 0x01, + (byte) 0x01, (byte) 0x01 }, + rs.getObject(2, byte[].class)); + assertFalse(rs.wasNull()); + rs.next(); + + assertEqualsWithNull(new byte[] { (byte) 0x02, (byte) 0x02, + (byte) 0x02, (byte) 0x02 }, + rs.getBytes("value")); + assertFalse(rs.wasNull()); + assertEqualsWithNull(new byte[] { (byte) 0x02, (byte) 0x02, + (byte) 0x02, (byte) 0x02 }, + rs.getObject("value", byte[].class)); + assertFalse(rs.wasNull()); + rs.next(); + + assertEqualsWithNull(new byte[] { (byte) 0x00 }, + readAllBytes(rs.getBinaryStream(2))); + assertFalse(rs.wasNull()); + rs.next(); + + assertEqualsWithNull(new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff }, + readAllBytes(rs.getBinaryStream("VaLuE"))); + assertFalse(rs.wasNull()); + rs.next(); + + InputStream in = rs.getBinaryStream("value"); + byte[] b = readAllBytes(in); + assertEqualsWithNull(new byte[] { (byte) 0x0b, (byte) 0xce, (byte) 0xc1 }, b); + Blob blob = rs.getObject("value", Blob.class); + try { + assertNotNull(blob); + assertEqualsWithNull(new byte[] { (byte) 0x0b, (byte) 0xce, (byte) 0xc1 }, + readAllBytes(blob.getBinaryStream())); + assertEqualsWithNull(new byte[] { (byte) 0xce, + (byte) 0xc1 }, readAllBytes(blob.getBinaryStream(2, 2))); + assertFalse(rs.wasNull()); + } finally { + blob.free(); + } + assertFalse(rs.wasNull()); + rs.next(); + + blob = rs.getObject("value", Blob.class); + try { + assertNotNull(blob); + assertEqualsWithNull(new byte[] { (byte) 0x03, (byte) 0x03, + (byte) 0x03, (byte) 0x03 }, readAllBytes(blob.getBinaryStream())); + assertEqualsWithNull(new byte[] { (byte) 0x03, + (byte) 0x03 }, readAllBytes(blob.getBinaryStream(2, 2))); + assertFalse(rs.wasNull()); + assertThrows(ErrorCode.INVALID_VALUE_2, blob).getBinaryStream(5, 1); + } finally { + blob.free(); + } + rs.next(); + + assertEqualsWithNull(null, readAllBytes(rs.getBinaryStream("VaLuE"))); + assertTrue(rs.wasNull()); + rs.next(); + + blob = rs.getObject("value", Blob.class); + try { + assertNotNull(blob); + assertEqualsWithNull(random, readAllBytes(blob.getBinaryStream())); + byte[] expected = Arrays.copyOfRange(random, 100, 50102); + byte[] got = readAllBytes(blob.getBinaryStream(101, 50002)); + assertEqualsWithNull(expected, got); + assertFalse(rs.wasNull()); + assertThrows(ErrorCode.INVALID_VALUE_2, blob).getBinaryStream(0x10001, 1); + assertThrows(ErrorCode.INVALID_VALUE_2, blob).getBinaryStream(0x10002, 0); + } finally { + blob.free(); + } + + assertFalse(rs.next()); + stat.execute("DROP TABLE TEST"); + } + + private void testClob() throws SQLException { + trace("Test CLOB"); + ResultSet rs; + String string; + stat = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_UPDATABLE); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY,\"VALUE\" CLOB)"); + stat.execute("INSERT INTO TEST VALUES(1,'Test')"); + stat.execute("INSERT INTO TEST VALUES(2,'Hello')"); + stat.execute("INSERT INTO TEST VALUES(3,'World!')"); + stat.execute("INSERT INTO TEST VALUES(4,'Hallo')"); + stat.execute("INSERT INTO TEST VALUES(5,'Welt!')"); + stat.execute("INSERT INTO TEST VALUES(6,'Test2')"); + stat.execute("INSERT INTO TEST VALUES(7,NULL)"); + stat.execute("INSERT INTO TEST VALUES(8,NULL)"); + rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + assertResultSetMeta(rs, 2, new String[] { "ID", "VALUE" }, + new int[] { Types.INTEGER, Types.CLOB }, new int[] { + 32, Integer.MAX_VALUE }, new int[] { 0, 0 }); + rs.next(); + Object obj = rs.getObject(2); + assertTrue(obj instanceof java.sql.Clob); + string = rs.getString(2); + assertTrue(string != null && string.equals("Test")); + assertFalse(rs.wasNull()); + rs.next(); + InputStreamReader reader = null; + try { + reader = new InputStreamReader(rs.getAsciiStream(2), StandardCharsets.ISO_8859_1); + } catch (Exception e) { + assertTrue(false); + } + string = readString(reader); + assertFalse(rs.wasNull()); + trace(string); + assertTrue(string != null && string.equals("Hello")); + rs.next(); + try { + reader = new InputStreamReader(rs.getAsciiStream("value"), StandardCharsets.ISO_8859_1); + } catch (Exception e) { + assertTrue(false); + } + string = readString(reader); + assertFalse(rs.wasNull()); + trace(string); + assertTrue(string != null && string.equals("World!")); + rs.next(); + + string = readString(rs.getCharacterStream(2)); + assertFalse(rs.wasNull()); + trace(string); + assertTrue(string != null && string.equals("Hallo")); + Clob clob = rs.getClob(2); + try { + assertEquals("all", readString(clob.getCharacterStream(2, 3))); + assertThrows(ErrorCode.INVALID_VALUE_2, clob).getCharacterStream(6, 1); + assertThrows(ErrorCode.INVALID_VALUE_2, clob).getCharacterStream(7, 0); + } finally { + clob.free(); + } + rs.next(); + + string = readString(rs.getCharacterStream("value")); + assertFalse(rs.wasNull()); + trace(string); + assertTrue(string != null && string.equals("Welt!")); + rs.next(); + + clob = rs.getObject("value", Clob.class); + try { + assertNotNull(clob); + string = readString(clob.getCharacterStream()); + assertTrue(string != null && string.equals("Test2")); + assertFalse(rs.wasNull()); + } finally { + clob.free(); + } + rs.next(); + + assertNull(rs.getCharacterStream(2)); + assertTrue(rs.wasNull()); + rs.next(); + + assertNull(rs.getAsciiStream("Value")); + assertTrue(rs.wasNull()); + + assertTrue(rs.getStatement() == stat); + assertNull(rs.getWarnings()); + rs.clearWarnings(); + assertNull(rs.getWarnings()); + assertEquals(ResultSet.FETCH_FORWARD, rs.getFetchDirection()); + assertEquals(ResultSet.CONCUR_UPDATABLE, rs.getConcurrency()); + rs.next(); + stat.execute("DROP TABLE TEST"); + } + + private void testArray() throws SQLException { + trace("Test ARRAY"); + ResultSet rs; + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, \"VALUE\" INTEGER ARRAY)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST VALUES(?, ?)"); + prep.setInt(1, 1); + prep.setObject(2, new Object[] { 1, 2 }); + prep.execute(); + prep.setInt(1, 2); + prep.setObject(2, new Object[] { 11, 12 }); + prep.execute(); + prep.setInt(1, 3); + prep.setObject(2, new Object[0]); + prep.execute(); + prep.close(); + rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + assertEquals("INTEGER ARRAY", rs.getMetaData().getColumnTypeName(2)); + rs.next(); + assertEquals(1, rs.getInt(1)); + Object[] list = (Object[]) ((Array) rs.getObject(2)).getArray(); + assertEquals(1, ((Integer) list[0]).intValue()); + assertEquals(2, ((Integer) list[1]).intValue()); + + Array array = rs.getArray(2); + Object[] list2 = (Object[]) array.getArray(); + assertEquals(1, ((Integer) list2[0]).intValue()); + assertEquals(2, ((Integer) list2[1]).intValue()); + list2 = (Object[]) array.getArray(2, 1); + assertEquals(2, ((Integer) list2[0]).intValue()); + + rs.next(); + assertEquals(2, rs.getInt(1)); + list = (Object[]) ((Array) rs.getObject(2)).getArray(); + assertEquals(11, ((Integer) list[0]).intValue()); + assertEquals(12, ((Integer) list[1]).intValue()); + + array = rs.getArray("VALUE"); + list2 = (Object[]) array.getArray(); + assertEquals(11, ((Integer) list2[0]).intValue()); + assertEquals(12, ((Integer) list2[1]).intValue()); + list2 = (Object[]) array.getArray(2, 1); + assertEquals(12, ((Integer) list2[0]).intValue()); + + list2 = (Object[]) array.getArray(Collections.emptyMap()); + assertEquals(11, ((Integer) list2[0]).intValue()); + + assertEquals(Types.INTEGER, array.getBaseType()); + assertEquals("INTEGER", array.getBaseTypeName()); + + assertTrue(array.toString().endsWith(": ARRAY [11, 12]")); + + rs.next(); + assertEquals(3, rs.getInt(1)); + list = (Object[]) ((Array) rs.getObject(2)).getArray(); + assertEquals(0, list.length); + + array = rs.getArray("VALUE"); + list2 = (Object[]) array.getArray(); + assertEquals(0, list2.length); + list2 = (Object[]) array.getArray(1, 0); + assertEquals(0, list2.length); + list2 = (Object[]) array.getArray(1, 1); + assertEquals(0, list2.length); + + list2 = (Object[]) array.getArray(Collections.emptyMap()); + assertEquals(0, list2.length); + + // TODO + // assertEquals(Types.INTEGER, array.getBaseType()); + // assertEquals("INTEGER", array.getBaseTypeName()); + + assertTrue(array.toString().endsWith(": ARRAY []")); + + // free + array.free(); + assertEquals("null", array.toString()); + assertThrows(ErrorCode.OBJECT_CLOSED, array).getBaseType(); + assertThrows(ErrorCode.OBJECT_CLOSED, array).getBaseTypeName(); + assertThrows(ErrorCode.OBJECT_CLOSED, array).getResultSet(); + + assertFalse(rs.next()); + + try (Statement s = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE)) { + rs = s.executeQuery("SELECT * FROM TEST ORDER BY ID"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + rs.updateArray(2, conn.createArrayOf("INT", new Object[] {10, 20})); + rs.updateRow(); + assertTrue(rs.next()); + rs.updateArray("VALUE", conn.createArrayOf("INT", new Object[] {11, 22})); + rs.updateRow(); + assertTrue(rs.next()); + assertFalse(rs.next()); + rs.moveToInsertRow(); + rs.updateInt(1, 4); + rs.updateArray(2, null); + rs.insertRow(); + } + + rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals(new Object[] {10, 20}, (Object[]) ((Array) rs.getObject(2)).getArray()); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertEquals(new Object[] {11, 22}, (Object[]) ((Array) rs.getObject(2)).getArray()); + assertTrue(rs.next()); + assertEquals(3, rs.getInt(1)); + assertEquals(new Object[0], (Object[]) ((Array) rs.getObject(2)).getArray()); + assertTrue(rs.next()); + assertEquals(4, rs.getInt(1)); + assertNull(rs.getObject(2)); + assertFalse(rs.next()); + + stat.execute("DROP TABLE TEST"); + } + + private void testRowValue() throws SQLException { + trace("Test ROW value"); + ResultSet rs; + rs = stat.executeQuery("SELECT (1, 'test')"); + assertEquals("ROW(\"C1\" INTEGER, \"C2\" CHARACTER VARYING(4))", rs.getMetaData().getColumnTypeName(1)); + rs.next(); + testRowValue((ResultSet) rs.getObject(1)); + ResultSet rowAsResultSet = rs.getObject(1, ResultSet.class); + testRowValue(rowAsResultSet); + } + + private void testRowValue(ResultSet rowAsResultSet) throws SQLException { + ResultSetMetaData md = rowAsResultSet.getMetaData(); + assertEquals(2, md.getColumnCount()); + assertEquals("C1", md.getColumnLabel(1)); + assertEquals("C1", md.getColumnName(1)); + assertEquals("C2", md.getColumnLabel(2)); + assertEquals("C2", md.getColumnName(2)); + assertEquals(Types.INTEGER, md.getColumnType(1)); + assertEquals(Types.VARCHAR, md.getColumnType(2)); + assertTrue(rowAsResultSet.next()); + assertEquals(1, rowAsResultSet.getInt(1)); + assertEquals("test", rowAsResultSet.getString(2)); + assertFalse(rowAsResultSet.next()); + } + + private void testEnum() throws SQLException { + trace("Test ENUM"); + + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, \"VALUE\" ENUM('A', 'B', 'C', 'D', 'E', 'F', 'G'))"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST VALUES(?, ?)"); + prep.setInt(1, 1); + prep.setString(2, "A"); + prep.executeUpdate(); + prep.setInt(1, 2); + prep.setObject(2, "B"); + prep.executeUpdate(); + prep.setInt(1, 3); + prep.setInt(2, 3); + prep.executeUpdate(); + prep.setInt(1, 4); + prep.setObject(2, "D", Types.VARCHAR); + prep.executeUpdate(); + prep.setInt(1, 5); + prep.setObject(2, "E", Types.OTHER); + prep.executeUpdate(); + prep.setInt(1, 6); + prep.setObject(2, 6, Types.OTHER); + prep.executeUpdate(); + prep.setInt(1, 7); + prep.setObject(2, 7, Types.INTEGER); + prep.executeUpdate(); + + ResultSet rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + assertEquals("ENUM('A', 'B', 'C', 'D', 'E', 'F', 'G')", rs.getMetaData().getColumnTypeName(2)); + testEnumResult(rs, 1, "A", 1); + testEnumResult(rs, 2, "B", 2); + testEnumResult(rs, 3, "C", 3); + testEnumResult(rs, 4, "D", 4); + testEnumResult(rs, 5, "E", 5); + testEnumResult(rs, 6, "F", 6); + testEnumResult(rs, 7, "G", 7); + assertFalse(rs.next()); + + stat.execute("DROP TABLE TEST"); + } + + private void testEnumResult(ResultSet rs, int id, String name, int ordinal) throws SQLException { + assertTrue(rs.next()); + assertEquals(id, rs.getInt(1)); + assertEquals(name, rs.getString(2)); + assertEquals(name, rs.getObject(2)); + assertEquals(name, rs.getObject(2, String.class)); + assertEquals(ordinal, rs.getInt(2)); + assertEquals((Integer) ordinal, rs.getObject(2, Integer.class)); + } + + private byte[] readAllBytes(InputStream in) { + if (in == null) { + return null; + } + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + while (true) { + int b = in.read(); + if (b == -1) { + break; + } + out.write(b); + } + return out.toByteArray(); + } catch (IOException e) { + assertTrue(false); + return null; + } + } + + private void assertEqualsWithNull(byte[] expected, byte[] got) { + if (got == null || expected == null) { + assertTrue(got == expected); + } else { + assertEquals(got, expected); + } + } + + private void checkColumnBigDecimal(ResultSet rs, int column, int i, + String bd) throws SQLException { + BigDecimal bd1 = rs.getBigDecimal(column); + int i1 = rs.getInt(column); + if (bd == null) { + trace("should be: null"); + assertTrue(rs.wasNull()); + } else { + trace("BigDecimal i=" + i + " bd=" + bd + " ; i1=" + i1 + " bd1=" + bd1); + assertFalse(rs.wasNull()); + assertTrue(i1 == i); + assertTrue(bd1.compareTo(new BigDecimal(bd)) == 0); + } + } + +} diff --git a/h2/src/test/org/h2/test/jdbc/TestSQLXML.java b/h2/src/test/org/h2/test/jdbc/TestSQLXML.java new file mode 100644 index 0000000..940b803 --- /dev/null +++ b/h2/src/test/org/h2/test/jdbc/TestSQLXML.java @@ -0,0 +1,217 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbc; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.StringReader; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLXML; +import java.sql.Statement; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.sax.SAXSource; +import javax.xml.transform.stax.StAXResult; +import javax.xml.transform.stax.StAXSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.h2.api.ErrorCode; +import org.h2.jdbc.JdbcConnection; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.IOUtils; +import org.w3c.dom.Node; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * Test the SQLXML implementation. + */ +public class TestSQLXML extends TestDb { + private static final String XML = "Text"; + + private JdbcConnection conn; + private Statement stat; + + /** + * Run just this test. + * + * @param a + * ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb(getTestName()); + conn = (JdbcConnection) getConnection(getTestName()); + stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, X CLOB)"); + stat.execute("INSERT INTO TEST VALUES (1, NULL)"); + testGetters(); + testSetters(); + conn.close(); + deleteDb(getTestName()); + } + + private void testGetters() throws SQLException, IOException, XMLStreamException { + ResultSet rs = stat.executeQuery("SELECT * FROM TEST"); + assertTrue(rs.next()); + assertNull(rs.getSQLXML(2)); + assertNull(rs.getSQLXML("X")); + assertEquals(1, stat.executeUpdate("UPDATE TEST SET X = '" + XML + '\'')); + rs = stat.executeQuery("SELECT * FROM TEST"); + assertTrue(rs.next()); + // ResultSet.getObject() + SQLXML sqlxml = rs.getObject(2, SQLXML.class); + assertEquals(XML, sqlxml.getString()); + + sqlxml = rs.getSQLXML(2); + // getBinaryStream() + assertEquals(XML, IOUtils.readStringAndClose(IOUtils.getReader(sqlxml.getBinaryStream()), -1)); + // getCharacterStream() + assertEquals(XML, IOUtils.readStringAndClose(sqlxml.getCharacterStream(), -1)); + // getString() + assertEquals(XML, sqlxml.getString()); + // DOMSource + DOMSource domSource = sqlxml.getSource(DOMSource.class); + Node n = domSource.getNode().getFirstChild(); + assertEquals("xml", n.getNodeName()); + assertEquals("v", n.getAttributes().getNamedItem("a").getNodeValue()); + assertEquals("Text", n.getFirstChild().getNodeValue()); + // SAXSource + SAXSource saxSource = sqlxml.getSource(SAXSource.class); + assertEquals(XML, + IOUtils.readStringAndClose(IOUtils.getReader(saxSource.getInputSource().getByteStream()), -1)); + // StAXSource + StAXSource staxSource = sqlxml.getSource(StAXSource.class); + XMLStreamReader stxReader = staxSource.getXMLStreamReader(); + assertEquals(XMLStreamReader.START_DOCUMENT, stxReader.getEventType()); + assertEquals(XMLStreamReader.START_ELEMENT, stxReader.next()); + assertEquals("xml", stxReader.getLocalName()); + assertEquals("a", stxReader.getAttributeLocalName(0)); + assertEquals("v", stxReader.getAttributeValue(0)); + assertEquals(XMLStreamReader.CHARACTERS, stxReader.next()); + assertEquals("Text", stxReader.getText()); + assertEquals(XMLStreamReader.END_ELEMENT, stxReader.next()); + assertEquals(XMLStreamReader.END_DOCUMENT, stxReader.next()); + // StreamSource + StreamSource streamSource = sqlxml.getSource(StreamSource.class); + assertEquals(XML, IOUtils.readStringAndClose(IOUtils.getReader(streamSource.getInputStream()), -1)); + // something illegal + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, sqlxml).getSource(Source.class); + } + + private void testSetters() throws SQLException, IOException, SAXException, ParserConfigurationException, + TransformerConfigurationException, TransformerException { + // setBinaryStream() + SQLXML sqlxml = conn.createSQLXML(); + try (OutputStream out = sqlxml.setBinaryStream()) { + out.write(XML.getBytes(StandardCharsets.UTF_8)); + } + testSettersImpl(sqlxml); + // setCharacterStream() + sqlxml = conn.createSQLXML(); + try (Writer out = sqlxml.setCharacterStream()) { + out.write(XML); + } + testSettersImpl(sqlxml); + // setString() + sqlxml = conn.createSQLXML(); + sqlxml.setString(XML); + testSettersImpl(sqlxml); + + TransformerFactory tf = TransformerFactory.newInstance(); + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DOMSource domSource = new DOMSource(dbf.newDocumentBuilder().parse(new InputSource(new StringReader(XML)))); + // DOMResult + sqlxml = conn.createSQLXML(); + tf.newTransformer().transform(domSource, sqlxml.setResult(DOMResult.class)); + testSettersImpl(sqlxml); + // SAXResult + sqlxml = conn.createSQLXML(); + tf.newTransformer().transform(domSource, sqlxml.setResult(SAXResult.class)); + testSettersImpl(sqlxml); + // StAXResult + sqlxml = conn.createSQLXML(); + tf.newTransformer().transform(domSource, sqlxml.setResult(StAXResult.class)); + testSettersImpl(sqlxml); + // StreamResult + sqlxml = conn.createSQLXML(); + tf.newTransformer().transform(domSource, sqlxml.setResult(StreamResult.class)); + testSettersImpl(sqlxml); + // something illegal + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, sqlxml).setResult(Result.class); + // null + testSettersImpl(null); + } + + private void assertXML(String actual) { + if (actual.startsWith("") + 2); + } + assertEquals(XML, actual); + } + + private void testSettersImplAssert(SQLXML sqlxml) throws SQLException { + ResultSet rs = stat.executeQuery("SELECT X FROM TEST"); + assertTrue(rs.next()); + SQLXML v = rs.getSQLXML(1); + if (sqlxml != null) { + assertXML(v.getString()); + } else { + assertNull(v); + } + } + + private void testSettersImpl(SQLXML sqlxml) throws SQLException { + PreparedStatement prep = conn.prepareStatement("UPDATE TEST SET X = ?"); + prep.setSQLXML(1, sqlxml); + assertEquals(1, prep.executeUpdate()); + testSettersImplAssert(sqlxml); + + prep.setObject(1, sqlxml); + assertEquals(1, prep.executeUpdate()); + testSettersImplAssert(sqlxml); + + Statement st = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE); + ResultSet rs = st.executeQuery("SELECT * FROM TEST FOR UPDATE"); + assertTrue(rs.next()); + rs.updateSQLXML(2, sqlxml); + rs.updateRow(); + testSettersImplAssert(sqlxml); + rs = st.executeQuery("SELECT * FROM TEST FOR UPDATE"); + assertTrue(rs.next()); + rs.updateSQLXML("X", sqlxml); + rs.updateRow(); + testSettersImplAssert(sqlxml); + + rs = st.executeQuery("SELECT * FROM TEST FOR UPDATE"); + assertTrue(rs.next()); + rs.updateObject(2, sqlxml); + rs.updateRow(); + testSettersImplAssert(sqlxml); + } + +} diff --git a/h2/src/test/org/h2/test/jdbc/TestStatement.java b/h2/src/test/org/h2/test/jdbc/TestStatement.java new file mode 100644 index 0000000..658b68f --- /dev/null +++ b/h2/src/test/org/h2/test/jdbc/TestStatement.java @@ -0,0 +1,530 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbc; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Savepoint; +import java.sql.Statement; +import java.util.Arrays; +import java.util.HashMap; + +import org.h2.api.ErrorCode; +import org.h2.engine.SysProperties; +import org.h2.jdbc.JdbcStatement; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests for the Statement implementation. + */ +public class TestStatement extends TestDb { + + private Connection conn; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb("statement"); + conn = getConnection("statement"); + testUnwrap(); + testUnsupportedOperations(); + testTraceError(); + testSavepoint(); + testConnectionRollback(); + testStatement(); + testPreparedStatement(); + testCloseOnCompletion(); + testIdentityMerge(); + conn.close(); + deleteDb("statement"); + testIdentifiers(); + deleteDb("statement"); + } + + private void testUnwrap() throws SQLException { + Statement stat = conn.createStatement(); + assertTrue(stat.isWrapperFor(Object.class)); + assertTrue(stat.isWrapperFor(Statement.class)); + assertTrue(stat.isWrapperFor(stat.getClass())); + assertFalse(stat.isWrapperFor(Integer.class)); + assertTrue(stat == stat.unwrap(Object.class)); + assertTrue(stat == stat.unwrap(Statement.class)); + assertTrue(stat == stat.unwrap(stat.getClass())); + assertThrows(ErrorCode.INVALID_VALUE_2, stat). + unwrap(Integer.class); + } + + private void testUnsupportedOperations() throws Exception { + conn.setTypeMap(null); + HashMap> map = new HashMap<>(); + conn.setTypeMap(map); + map.put("x", Object.class); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, conn). + setTypeMap(map); + } + + private void testTraceError() throws Exception { + if (config.memory || config.networked || config.traceLevelFile != 0) { + return; + } + Statement stat = conn.createStatement(); + String fileName = getBaseDir() + "/statement.trace.db"; + stat.execute("DROP TABLE TEST IF EXISTS"); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY)"); + stat.execute("INSERT INTO TEST VALUES(1)"); + try { + stat.execute("ERROR"); + } catch (SQLException e) { + // ignore + } + long lengthBefore = FileUtils.size(fileName); + try { + stat.execute("ERROR"); + } catch (SQLException e) { + // ignore + } + long error = FileUtils.size(fileName); + assertSmaller(lengthBefore, error); + lengthBefore = error; + try { + stat.execute("INSERT INTO TEST VALUES(1)"); + } catch (SQLException e) { + // ignore + } + error = FileUtils.size(fileName); + assertEquals(lengthBefore, error); + stat.execute("DROP TABLE TEST IF EXISTS"); + } + + private void testConnectionRollback() throws SQLException { + Statement stat = conn.createStatement(); + conn.setAutoCommit(false); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + stat.execute("INSERT INTO TEST VALUES(1, 'Hello')"); + conn.rollback(); + ResultSet rs = stat.executeQuery("SELECT * FROM TEST"); + assertFalse(rs.next()); + stat.execute("DROP TABLE TEST"); + conn.setAutoCommit(true); + } + + private void testSavepoint() throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + conn.setAutoCommit(false); + stat.execute("INSERT INTO TEST VALUES(0, 'Hi')"); + Savepoint savepoint1 = conn.setSavepoint(); + int id1 = savepoint1.getSavepointId(); + assertThrows(ErrorCode.SAVEPOINT_IS_UNNAMED, savepoint1). + getSavepointName(); + stat.execute("DELETE FROM TEST"); + conn.rollback(savepoint1); + stat.execute("UPDATE TEST SET NAME='Hello'"); + Savepoint savepoint2a = conn.setSavepoint(); + Savepoint savepoint2 = conn.setSavepoint(); + conn.releaseSavepoint(savepoint2a); + assertThrows(ErrorCode.SAVEPOINT_IS_INVALID_1, savepoint2a). + getSavepointId(); + int id2 = savepoint2.getSavepointId(); + assertTrue(id1 != id2); + stat.execute("UPDATE TEST SET NAME='Hallo' WHERE NAME='Hello'"); + Savepoint savepointTest = conn.setSavepoint("Joe's"); + assertTrue(savepointTest.toString().endsWith("name=Joe's")); + stat.execute("DELETE FROM TEST"); + assertEquals(savepointTest.getSavepointName(), "Joe's"); + assertThrows(ErrorCode.SAVEPOINT_IS_NAMED, savepointTest). + getSavepointId(); + conn.rollback(savepointTest); + conn.commit(); + ResultSet rs = stat.executeQuery("SELECT NAME FROM TEST"); + rs.next(); + String name = rs.getString(1); + assertEquals(name, "Hallo"); + assertFalse(rs.next()); + assertThrows(ErrorCode.SAVEPOINT_IS_INVALID_1, conn). + rollback(savepoint2); + stat.execute("DROP TABLE TEST"); + conn.setAutoCommit(true); + } + + private void testStatement() throws SQLException { + + Statement stat = conn.createStatement(); + + assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, + conn.getHoldability()); + conn.setHoldability(ResultSet.CLOSE_CURSORS_AT_COMMIT); + assertEquals(ResultSet.CLOSE_CURSORS_AT_COMMIT, + conn.getHoldability()); + + assertFalse(stat.isPoolable()); + stat.setPoolable(true); + assertFalse(stat.isPoolable()); + + // ignored + stat.setCursorName("x"); + // fixed return value + assertEquals(stat.getFetchDirection(), ResultSet.FETCH_FORWARD); + // ignored + stat.setFetchDirection(ResultSet.FETCH_REVERSE); + // ignored + stat.setMaxFieldSize(100); + + assertEquals(SysProperties.SERVER_RESULT_SET_FETCH_SIZE, + stat.getFetchSize()); + stat.setFetchSize(10); + assertEquals(10, stat.getFetchSize()); + stat.setFetchSize(0); + assertEquals(SysProperties.SERVER_RESULT_SET_FETCH_SIZE, + stat.getFetchSize()); + assertEquals(ResultSet.TYPE_FORWARD_ONLY, + stat.getResultSetType()); + Statement stat2 = conn.createStatement( + ResultSet.TYPE_SCROLL_SENSITIVE, + ResultSet.CONCUR_READ_ONLY, + ResultSet.HOLD_CURSORS_OVER_COMMIT); + assertEquals(ResultSet.TYPE_SCROLL_SENSITIVE, + stat2.getResultSetType()); + assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, + stat2.getResultSetHoldability()); + assertEquals(ResultSet.CONCUR_READ_ONLY, + stat2.getResultSetConcurrency()); + assertEquals(0, stat.getMaxFieldSize()); + assertFalse(stat2.isClosed()); + stat2.close(); + assertTrue(stat2.isClosed()); + + + ResultSet rs; + int count; + long largeCount; + boolean result; + + stat.execute("CREATE TABLE TEST(ID INT)"); + stat.execute("SELECT * FROM TEST"); + stat.execute("DROP TABLE TEST"); + + conn.getTypeMap(); + + // this method should not throw an exception - if not supported, this + // calls are ignored + + assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, + stat.getResultSetHoldability()); + assertEquals(ResultSet.CONCUR_READ_ONLY, + stat.getResultSetConcurrency()); + + stat.cancel(); + stat.setQueryTimeout(10); + assertTrue(stat.getQueryTimeout() == 10); + stat.setQueryTimeout(0); + assertTrue(stat.getQueryTimeout() == 0); + assertThrows(ErrorCode.INVALID_VALUE_2, stat).setQueryTimeout(-1); + assertTrue(stat.getQueryTimeout() == 0); + trace("executeUpdate"); + count = stat.executeUpdate( + "CREATE TABLE TEST(ID INT PRIMARY KEY,V VARCHAR(255))"); + assertEquals(0, count); + count = stat.executeUpdate( + "INSERT INTO TEST VALUES(1,'Hello')"); + assertEquals(1, count); + count = stat.executeUpdate( + "INSERT INTO TEST(V,ID) VALUES('JDBC',2)"); + assertEquals(1, count); + count = stat.executeUpdate( + "UPDATE TEST SET V='LDBC' WHERE ID=2 OR ID=1"); + assertEquals(2, count); + count = stat.executeUpdate( + "UPDATE TEST SET V='\\LDBC\\' WHERE V LIKE 'LDBC' "); + assertEquals(2, count); + count = stat.executeUpdate( + "UPDATE TEST SET V='LDBC' WHERE V LIKE '\\\\LDBC\\\\'"); + trace("count:" + count); + assertEquals(2, count); + count = stat.executeUpdate("DELETE FROM TEST WHERE ID=-1"); + assertEquals(0, count); + count = stat.executeUpdate("DELETE FROM TEST WHERE ID=2"); + assertEquals(1, count); + largeCount = stat.executeLargeUpdate("DELETE FROM TEST WHERE ID=-1"); + assertEquals(0, largeCount); + assertEquals(0, stat.getLargeUpdateCount()); + largeCount = stat.executeLargeUpdate("INSERT INTO TEST(V,ID) VALUES('JDBC',2)"); + assertEquals(1, largeCount); + assertEquals(1, stat.getLargeUpdateCount()); + largeCount = stat.executeLargeUpdate("DELETE FROM TEST WHERE ID=2"); + assertEquals(1, largeCount); + assertEquals(1, stat.getLargeUpdateCount()); + + assertThrows(ErrorCode.METHOD_NOT_ALLOWED_FOR_QUERY, stat). + executeUpdate("SELECT * FROM TEST"); + + count = stat.executeUpdate("DROP TABLE TEST"); + assertTrue(count == 0); + + trace("execute"); + result = stat.execute( + "CREATE TABLE TEST(ID INT PRIMARY KEY,V VARCHAR(255))"); + assertFalse(result); + result = stat.execute("INSERT INTO TEST VALUES(1,'Hello')"); + assertFalse(result); + result = stat.execute("INSERT INTO TEST(V,ID) VALUES('JDBC',2)"); + assertFalse(result); + result = stat.execute("UPDATE TEST SET V='LDBC' WHERE ID=2"); + assertFalse(result); + result = stat.execute("DELETE FROM TEST WHERE ID=3"); + assertFalse(result); + result = stat.execute("SELECT * FROM TEST"); + assertTrue(result); + result = stat.execute("DROP TABLE TEST"); + assertFalse(result); + + assertThrows(ErrorCode.METHOD_ONLY_ALLOWED_FOR_QUERY, stat). + executeQuery("CREATE TABLE TEST(ID INT PRIMARY KEY,V VARCHAR(255))"); + + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY,V VARCHAR(255))"); + + assertThrows(ErrorCode.METHOD_ONLY_ALLOWED_FOR_QUERY, stat). + executeQuery("INSERT INTO TEST VALUES(1,'Hello')"); + + assertThrows(ErrorCode.METHOD_ONLY_ALLOWED_FOR_QUERY, stat). + executeQuery("UPDATE TEST SET V='LDBC' WHERE ID=2"); + + assertThrows(ErrorCode.METHOD_ONLY_ALLOWED_FOR_QUERY, stat). + executeQuery("DELETE FROM TEST WHERE ID=3"); + + stat.executeQuery("SELECT * FROM TEST"); + + assertThrows(ErrorCode.METHOD_ONLY_ALLOWED_FOR_QUERY, stat). + executeQuery("DROP TABLE TEST"); + + // getMoreResults + rs = stat.executeQuery("SELECT * FROM TEST"); + assertFalse(stat.getMoreResults()); + assertThrows(ErrorCode.OBJECT_CLOSED, rs).next(); + assertTrue(stat.getUpdateCount() == -1); + count = stat.executeUpdate("DELETE FROM TEST"); + assertFalse(stat.getMoreResults()); + assertTrue(stat.getUpdateCount() == -1); + + stat.execute("DROP TABLE TEST"); + stat.executeUpdate("DROP TABLE IF EXISTS TEST"); + + assertNull(stat.getWarnings()); + stat.clearWarnings(); + assertNull(stat.getWarnings()); + assertTrue(conn == stat.getConnection()); + + stat.close(); + } + + private void testCloseOnCompletion() throws SQLException { + Statement stat = conn.createStatement(); + assertFalse(stat.isCloseOnCompletion()); + ResultSet rs = stat.executeQuery("VALUES 1"); + assertFalse(stat.isCloseOnCompletion()); + stat.closeOnCompletion(); + assertTrue(stat.isCloseOnCompletion()); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertFalse(rs.next()); + rs.close(); + assertTrue(stat.isClosed()); + assertThrows(ErrorCode.OBJECT_CLOSED, stat).isCloseOnCompletion(); + assertThrows(ErrorCode.OBJECT_CLOSED, stat).closeOnCompletion(); + stat = conn.createStatement(); + stat.closeOnCompletion(); + rs = stat.executeQuery("VALUES 1"); + ResultSet rs2 = stat.executeQuery("VALUES 2"); + rs.close(); + assertFalse(stat.isClosed()); + rs2.close(); + assertTrue(stat.isClosed()); + } + + private void testIdentityMerge() throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("drop table if exists test1"); + stat.execute("create table test1(id identity, x int)"); + stat.execute("drop table if exists test2"); + stat.execute("create table test2(id identity, x int)"); + stat.execute("merge into test1(x) key(x) values(5)", + Statement.RETURN_GENERATED_KEYS); + ResultSet keys; + keys = stat.getGeneratedKeys(); + keys.next(); + assertEquals(1, keys.getInt(1)); + stat.execute("insert into test2(x) values(10), (11), (12)"); + stat.execute("merge into test1(x) key(x) values(5)", + Statement.RETURN_GENERATED_KEYS); + keys = stat.getGeneratedKeys(); + keys.next(); + assertEquals(1, keys.getInt(1)); + assertFalse(keys.next()); + stat.execute("merge into test1(x) key(x) values(6)", + Statement.RETURN_GENERATED_KEYS); + keys = stat.getGeneratedKeys(); + keys.next(); + assertEquals(2, keys.getInt(1)); + stat.execute("drop table test1, test2"); + } + + private void testPreparedStatement() throws SQLException{ + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name varchar(255))"); + stat.execute("insert into test values(1, 'Hello')"); + stat.execute("insert into test values(2, 'World')"); + PreparedStatement ps = conn.prepareStatement( + "select name from test where id in (select id from test where name REGEXP ?)"); + ps.setString(1, "Hello"); + ResultSet rs = ps.executeQuery(); + assertTrue(rs.next()); + assertEquals("Hello", rs.getString("name")); + assertFalse(rs.next()); + ps.setString(1, "World"); + rs = ps.executeQuery(); + assertTrue(rs.next()); + assertEquals("World", rs.getString("name")); + assertFalse(rs.next()); + //Changes the table structure + stat.execute("create index t_id on test(name)"); + //Test the prepared statement again to check if the internal cache attributes were reset + ps.setString(1, "Hello"); + rs = ps.executeQuery(); + assertTrue(rs.next()); + assertEquals("Hello", rs.getString("name")); + assertFalse(rs.next()); + ps.setString(1, "World"); + rs = ps.executeQuery(); + assertTrue(rs.next()); + assertEquals("World", rs.getString("name")); + assertFalse(rs.next()); + ps = conn.prepareStatement("insert into test values(?, ?)"); + ps.setInt(1, 3); + ps.setString(2, "v3"); + ps.addBatch(); + ps.setInt(1, 4); + ps.setString(2, "v4"); + ps.addBatch(); + assertTrue(Arrays.equals(new int[] {1, 1}, ps.executeBatch())); + ps.setInt(1, 5); + ps.setString(2, "v5"); + ps.addBatch(); + ps.setInt(1, 6); + ps.setString(2, "v6"); + ps.addBatch(); + assertTrue(Arrays.equals(new long[] {1, 1}, ps.executeLargeBatch())); + ps.setInt(1, 7); + ps.setString(2, "v7"); + assertEquals(1, ps.executeUpdate()); + assertEquals(1, ps.getUpdateCount()); + ps.setInt(1, 8); + ps.setString(2, "v8"); + assertEquals(1, ps.executeLargeUpdate()); + assertEquals(1, ps.getLargeUpdateCount()); + stat.execute("drop table test"); + } + + private void testIdentifiers() throws SQLException { + Connection conn = getConnection("statement"); + + JdbcStatement stat = (JdbcStatement) conn.createStatement(); + assertEquals("SOME_ID", stat.enquoteIdentifier("SOME_ID", false)); + assertEquals("\"SOME ID\"", stat.enquoteIdentifier("SOME ID", false)); + assertEquals("\"SOME_ID\"", stat.enquoteIdentifier("SOME_ID", true)); + assertEquals("\"FROM\"", stat.enquoteIdentifier("FROM", false)); + assertEquals("\"Test\"", stat.enquoteIdentifier("Test", false)); + assertEquals("\"test\"", stat.enquoteIdentifier("test", false)); + assertEquals("\"TOP\"", stat.enquoteIdentifier("TOP", false)); + assertEquals("\"Test\"", stat.enquoteIdentifier("\"Test\"", false)); + assertEquals("\"Test\"", stat.enquoteIdentifier("\"Test\"", true)); + assertEquals("\"\"\"Test\"", stat.enquoteIdentifier("\"\"\"Test\"", true)); + assertEquals("\"\"", stat.enquoteIdentifier("", false)); + assertEquals("\"\"", stat.enquoteIdentifier("", true)); + assertEquals("U&\"\"", stat.enquoteIdentifier("U&\"\"", false)); + assertEquals("U&\"\"", stat.enquoteIdentifier("U&\"\"", true)); + assertEquals("U&\"\0100\"", stat.enquoteIdentifier("U&\"\0100\"", false)); + assertEquals("U&\"\0100\"", stat.enquoteIdentifier("U&\"\0100\"", true)); + assertThrows(NullPointerException.class, () -> stat.enquoteIdentifier(null, false)); + assertThrows(ErrorCode.INVALID_NAME_1, () -> stat.enquoteIdentifier("\"Test", true)); + assertThrows(ErrorCode.INVALID_NAME_1, () -> stat.enquoteIdentifier("\"a\"a\"", true)); + assertThrows(ErrorCode.INVALID_NAME_1, () -> stat.enquoteIdentifier("U&\"a\"a\"", true)); + assertThrows(ErrorCode.STRING_FORMAT_ERROR_1, () -> stat.enquoteIdentifier("U&\"\\111\"", true)); + assertEquals("U&\"\\02b0\"", stat.enquoteIdentifier("\u02B0", false)); + + assertTrue(stat.isSimpleIdentifier("SOME_ID_1")); + assertFalse(stat.isSimpleIdentifier("SOME ID")); + assertFalse(stat.isSimpleIdentifier("FROM")); + assertFalse(stat.isSimpleIdentifier("Test")); + assertFalse(stat.isSimpleIdentifier("test")); + assertFalse(stat.isSimpleIdentifier("TOP")); + assertFalse(stat.isSimpleIdentifier("_")); + assertFalse(stat.isSimpleIdentifier("_1")); + assertFalse(stat.isSimpleIdentifier("\u02B0")); + + conn.close(); + deleteDb("statement"); + conn = getConnection("statement;DATABASE_TO_LOWER=TRUE"); + + JdbcStatement stat2 = (JdbcStatement) conn.createStatement(); + assertEquals("some_id", stat2.enquoteIdentifier("some_id", false)); + assertEquals("\"some id\"", stat2.enquoteIdentifier("some id", false)); + assertEquals("\"some_id\"", stat2.enquoteIdentifier("some_id", true)); + assertEquals("\"from\"", stat2.enquoteIdentifier("from", false)); + assertEquals("\"Test\"", stat2.enquoteIdentifier("Test", false)); + assertEquals("\"TEST\"", stat2.enquoteIdentifier("TEST", false)); + assertEquals("\"top\"", stat2.enquoteIdentifier("top", false)); + + assertTrue(stat2.isSimpleIdentifier("some_id")); + assertFalse(stat2.isSimpleIdentifier("some id")); + assertFalse(stat2.isSimpleIdentifier("from")); + assertFalse(stat2.isSimpleIdentifier("Test")); + assertFalse(stat2.isSimpleIdentifier("TEST")); + assertFalse(stat2.isSimpleIdentifier("top")); + + conn.close(); + deleteDb("statement"); + conn = getConnection("statement;DATABASE_TO_UPPER=FALSE"); + + JdbcStatement stat3 = (JdbcStatement) conn.createStatement(); + assertEquals("SOME_ID", stat3.enquoteIdentifier("SOME_ID", false)); + assertEquals("some_id", stat3.enquoteIdentifier("some_id", false)); + assertEquals("\"SOME ID\"", stat3.enquoteIdentifier("SOME ID", false)); + assertEquals("\"some id\"", stat3.enquoteIdentifier("some id", false)); + assertEquals("\"SOME_ID\"", stat3.enquoteIdentifier("SOME_ID", true)); + assertEquals("\"some_id\"", stat3.enquoteIdentifier("some_id", true)); + assertEquals("\"FROM\"", stat3.enquoteIdentifier("FROM", false)); + assertEquals("\"from\"", stat3.enquoteIdentifier("from", false)); + assertEquals("Test", stat3.enquoteIdentifier("Test", false)); + assertEquals("\"TOP\"", stat3.enquoteIdentifier("TOP", false)); + assertEquals("\"top\"", stat3.enquoteIdentifier("top", false)); + + assertTrue(stat3.isSimpleIdentifier("SOME_ID")); + assertTrue(stat3.isSimpleIdentifier("some_id")); + assertFalse(stat3.isSimpleIdentifier("SOME ID")); + assertFalse(stat3.isSimpleIdentifier("some id")); + assertFalse(stat3.isSimpleIdentifier("FROM")); + assertFalse(stat3.isSimpleIdentifier("from")); + assertTrue(stat3.isSimpleIdentifier("Test")); + assertFalse(stat3.isSimpleIdentifier("TOP")); + assertFalse(stat3.isSimpleIdentifier("top")); + assertThrows(NullPointerException.class, () -> stat3.isSimpleIdentifier(null)); + + conn.close(); + } + +} diff --git a/h2/src/test/org/h2/test/jdbc/TestTransactionIsolation.java b/h2/src/test/org/h2/test/jdbc/TestTransactionIsolation.java new file mode 100644 index 0000000..234bad5 --- /dev/null +++ b/h2/src/test/org/h2/test/jdbc/TestTransactionIsolation.java @@ -0,0 +1,111 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbc; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Transaction isolation level tests. + */ +public class TestTransactionIsolation extends TestDb { + + private Connection conn1, conn2; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + testTableLevelLocking(); + } + + private void testTableLevelLocking() throws SQLException { + deleteDb("transactionIsolation"); + + conn1 = getConnection("transactionIsolation"); + conn1.setAutoCommit(false); + + conn2 = getConnection("transactionIsolation"); + conn2.setAutoCommit(false); + + assertEquals(Connection.TRANSACTION_READ_COMMITTED, conn1.getMetaData().getDefaultTransactionIsolation()); + assertEquals(Connection.TRANSACTION_READ_COMMITTED, conn1.getTransactionIsolation()); + + try (Connection conn = getConnection("transactionIsolation"); + Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE TEST(ID INT)"); + } + testIt(Connection.TRANSACTION_READ_UNCOMMITTED); + testIt(Connection.TRANSACTION_READ_COMMITTED); + testIt(Connection.TRANSACTION_REPEATABLE_READ); + testIt(Connection.TRANSACTION_SERIALIZABLE); + + try (Connection conn = getConnection("transactionIsolation"); + Statement stmt = conn.createStatement()) { + stmt.execute("DROP TABLE TEST"); + stmt.execute("CREATE TABLE TEST(ID INT UNIQUE)"); + } + testIt(Connection.TRANSACTION_READ_UNCOMMITTED); + testIt(Connection.TRANSACTION_READ_COMMITTED); + testIt(Connection.TRANSACTION_REPEATABLE_READ); + testIt(Connection.TRANSACTION_SERIALIZABLE); + + conn2.close(); + conn1.close(); + deleteDb("transactionIsolation"); + } + + private void testIt(int isolationLevel2) throws SQLException { + try (Connection conn = getConnection("transactionIsolation"); + Statement stmt = conn.createStatement()) { + stmt.execute("DELETE FROM TEST"); + stmt.execute("INSERT INTO TEST VALUES(1)"); + } + + conn2.setTransactionIsolation(isolationLevel2); + assertEquals(isolationLevel2, conn2.getTransactionIsolation()); + + testRowLocks(Connection.TRANSACTION_READ_UNCOMMITTED); + testRowLocks(Connection.TRANSACTION_READ_COMMITTED); + testRowLocks(Connection.TRANSACTION_REPEATABLE_READ); + testRowLocks(Connection.TRANSACTION_SERIALIZABLE); + + testDirtyRead(Connection.TRANSACTION_READ_UNCOMMITTED, 1, true, true); + testDirtyRead(Connection.TRANSACTION_READ_COMMITTED, 2, false, true); + testDirtyRead(Connection.TRANSACTION_REPEATABLE_READ, 3, false, false); + testDirtyRead(Connection.TRANSACTION_SERIALIZABLE, 4, false, false); + } + + private void testDirtyRead(int isolationLevel, int value, boolean dirtyVisible, boolean committedVisible) + throws SQLException { + conn1.setTransactionIsolation(isolationLevel); + assertSingleValue(conn1.createStatement(), "SELECT * FROM TEST", value); + int newValue = value + 1; + conn2.createStatement().executeUpdate("UPDATE TEST SET ID=" + newValue); + assertSingleValue(conn1.createStatement(), "SELECT * FROM TEST", dirtyVisible ? newValue : value); + conn2.commit(); + assertSingleValue(conn1.createStatement(), "SELECT * FROM TEST", committedVisible ? newValue : value); + } + + private void testRowLocks(int isolationLevel) throws SQLException { + conn1.setTransactionIsolation(isolationLevel); + assertSingleValue(conn1.createStatement(), "SELECT * FROM TEST", 1); + assertSingleValue(conn2.createStatement(), "SELECT * FROM TEST FOR UPDATE", 1); + assertThrows(ErrorCode.LOCK_TIMEOUT_1, conn1.createStatement()).executeUpdate("DELETE FROM TEST"); + conn2.commit(); + } +} diff --git a/h2/src/test/org/h2/test/jdbc/TestUpdatableResultSet.java b/h2/src/test/org/h2/test/jdbc/TestUpdatableResultSet.java new file mode 100644 index 0000000..217232d --- /dev/null +++ b/h2/src/test/org/h2/test/jdbc/TestUpdatableResultSet.java @@ -0,0 +1,828 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbc; + +import java.io.ByteArrayInputStream; +import java.io.OutputStream; +import java.io.StringReader; +import java.math.BigDecimal; +import java.sql.Blob; +import java.sql.Connection; +import java.sql.Date; +import java.sql.JDBCType; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; + +import org.h2.api.ErrorCode; +import org.h2.api.H2Type; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Updatable result set tests. + */ +public class TestUpdatableResultSet extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testDetectUpdatable(); + testUpdateLob(); + testScroll(); + testUpdateDeleteInsert(); + testUpdateDataType(); + testUpdateResetRead(); + testUpdateObject(); + deleteDb("updatableResultSet"); + } + + private void testDetectUpdatable() throws SQLException { + deleteDb("updatableResultSet"); + Connection conn = getConnection("updatableResultSet"); + Statement stat; + ResultSet rs; + stat = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, + ResultSet.CONCUR_UPDATABLE); + + stat.execute("create table test(id int primary key, name varchar)"); + rs = stat.executeQuery("select * from test"); + assertEquals(ResultSet.CONCUR_UPDATABLE, rs.getConcurrency()); + rs = stat.executeQuery("select name from test"); + assertEquals(ResultSet.CONCUR_READ_ONLY, rs.getConcurrency()); + stat.execute("drop table test"); + rs = stat.executeQuery("SELECT"); + assertEquals(ResultSet.CONCUR_READ_ONLY, rs.getConcurrency()); + + stat.execute("create table test(a int, b int, " + + "name varchar, primary key(a, b))"); + rs = stat.executeQuery("select * from test"); + assertEquals(ResultSet.CONCUR_UPDATABLE, rs.getConcurrency()); + rs = stat.executeQuery("select a, name from test"); + assertEquals(ResultSet.CONCUR_READ_ONLY, rs.getConcurrency()); + rs = stat.executeQuery("select b, name from test"); + assertEquals(ResultSet.CONCUR_READ_ONLY, rs.getConcurrency()); + rs = stat.executeQuery("select b, name, a from test"); + assertEquals(ResultSet.CONCUR_UPDATABLE, rs.getConcurrency()); + stat.execute("drop table test"); + + stat.execute("create table test(a int, b int, name varchar)"); + stat.execute("create unique index on test(b, a)"); + rs = stat.executeQuery("select * from test"); + assertEquals(ResultSet.CONCUR_UPDATABLE, rs.getConcurrency()); + rs = stat.executeQuery("select a, name from test"); + assertEquals(ResultSet.CONCUR_READ_ONLY, rs.getConcurrency()); + rs = stat.executeQuery("select b, name from test"); + assertEquals(ResultSet.CONCUR_READ_ONLY, rs.getConcurrency()); + rs = stat.executeQuery("select b, name, a from test"); + assertEquals(ResultSet.CONCUR_UPDATABLE, rs.getConcurrency()); + stat.execute("drop table test"); + + stat.execute("create table test(a int, b int, c int unique, " + + "name varchar, primary key(a, b))"); + rs = stat.executeQuery("select * from test"); + assertEquals(ResultSet.CONCUR_UPDATABLE, rs.getConcurrency()); + rs = stat.executeQuery("select a, name, c from test"); + assertEquals(ResultSet.CONCUR_UPDATABLE, rs.getConcurrency()); + rs = stat.executeQuery("select b, a, name, c from test"); + assertEquals(ResultSet.CONCUR_UPDATABLE, rs.getConcurrency()); + stat.execute("drop table test"); + + stat.execute("create table test(id int primary key, " + + "a int, b int, i int, j int, k int, name varchar)"); + stat.execute("create unique index on test(b, a)"); + stat.execute("create unique index on test(i, j)"); + stat.execute("create unique index on test(a, j)"); + rs = stat.executeQuery("select * from test"); + assertEquals(ResultSet.CONCUR_UPDATABLE, rs.getConcurrency()); + rs = stat.executeQuery("select a, name, b from test"); + assertEquals(ResultSet.CONCUR_UPDATABLE, rs.getConcurrency()); + rs = stat.executeQuery("select a, name, b from test"); + assertEquals(ResultSet.CONCUR_UPDATABLE, rs.getConcurrency()); + rs = stat.executeQuery("select i, b, k, name from test"); + assertEquals(ResultSet.CONCUR_READ_ONLY, rs.getConcurrency()); + rs = stat.executeQuery("select a, i, name from test"); + assertEquals(ResultSet.CONCUR_READ_ONLY, rs.getConcurrency()); + rs = stat.executeQuery("select b, i, k, name from test"); + assertEquals(ResultSet.CONCUR_READ_ONLY, rs.getConcurrency()); + rs = stat.executeQuery("select a, k, j, name from test"); + assertEquals(ResultSet.CONCUR_UPDATABLE, rs.getConcurrency()); + stat.execute("drop table test"); + + conn.close(); + } + + private void testUpdateLob() throws SQLException { + deleteDb("updatableResultSet"); + Connection conn = getConnection("updatableResultSet"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE object_index " + + "(id integer primary key, object other, number integer)"); + + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO object_index (id,object) VALUES (1,?)"); + prep.setObject(1, "hello", Types.JAVA_OBJECT); + prep.execute(); + + ResultSet rs = stat.executeQuery( + "SELECT object,id,number FROM object_index WHERE id =1"); + rs.next(); + assertEquals("hello", rs.getObject(1).toString()); + stat = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_UPDATABLE); + rs = stat.executeQuery("SELECT object,id,number FROM object_index WHERE id =1"); + rs.next(); + assertEquals("hello", rs.getObject(1).toString()); + rs.updateInt(2, 1); + rs.updateRow(); + rs.close(); + stat = conn.createStatement(); + rs = stat.executeQuery("SELECT object,id,number FROM object_index WHERE id =1"); + rs.next(); + assertEquals("hello", rs.getObject(1).toString()); + conn.close(); + } + + private void testUpdateResetRead() throws SQLException { + deleteDb("updatableResultSet"); + Connection conn = getConnection("updatableResultSet"); + Statement stat = conn.createStatement( + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + stat.execute("INSERT INTO TEST VALUES(1, 'Hello')"); + stat.execute("INSERT INTO TEST VALUES(2, 'World')"); + ResultSet rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + rs.next(); + rs.updateInt(1, 10); + rs.updateRow(); + rs.next(); + + rs.updateString(2, "Welt"); + rs.cancelRowUpdates(); + rs.updateString(2, "Welt"); + + rs.updateRow(); + rs.beforeFirst(); + rs.next(); + assertEquals(10, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + rs.next(); + assertEquals(2, rs.getInt(1)); + assertEquals("Welt", rs.getString(2)); + + assertFalse(rs.isClosed()); + rs.close(); + assertTrue(rs.isClosed()); + + conn.close(); + } + + private void testScroll() throws SQLException { + deleteDb("updatableResultSet"); + Connection conn = getConnection("updatableResultSet"); + Statement stat = conn.createStatement( + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + stat.execute("INSERT INTO TEST VALUES(1, 'Hello'), (2, 'World'), (3, 'Test')"); + + ResultSet rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + + assertTrue(rs.isBeforeFirst()); + assertFalse(rs.isAfterLast()); + assertEquals(0, rs.getRow()); + + rs.next(); + assertFalse(rs.isBeforeFirst()); + assertFalse(rs.isAfterLast()); + assertEquals(1, rs.getInt(1)); + assertEquals(1, rs.getRow()); + + rs.next(); + assertThrows(ErrorCode.RESULT_SET_READONLY, rs).insertRow(); + assertFalse(rs.isBeforeFirst()); + assertFalse(rs.isAfterLast()); + assertEquals(2, rs.getInt(1)); + assertEquals(2, rs.getRow()); + + rs.next(); + assertFalse(rs.isBeforeFirst()); + assertFalse(rs.isAfterLast()); + assertEquals(3, rs.getInt(1)); + assertEquals(3, rs.getRow()); + + assertFalse(rs.next()); + assertFalse(rs.isBeforeFirst()); + assertTrue(rs.isAfterLast()); + assertEquals(0, rs.getRow()); + + assertTrue(rs.first()); + assertEquals(1, rs.getInt(1)); + assertEquals(1, rs.getRow()); + + assertTrue(rs.last()); + assertEquals(3, rs.getInt(1)); + assertEquals(3, rs.getRow()); + + assertTrue(rs.relative(0)); + assertEquals(3, rs.getRow()); + + assertTrue(rs.relative(-1)); + assertEquals(2, rs.getRow()); + + assertTrue(rs.relative(1)); + assertEquals(3, rs.getRow()); + + assertFalse(rs.relative(100)); + assertTrue(rs.isAfterLast()); + + assertFalse(rs.absolute(0)); + assertEquals(0, rs.getRow()); + + assertTrue(rs.absolute(1)); + assertEquals(1, rs.getRow()); + + assertTrue(rs.absolute(2)); + assertEquals(2, rs.getRow()); + + assertTrue(rs.absolute(3)); + assertEquals(3, rs.getRow()); + + assertFalse(rs.absolute(4)); + assertEquals(0, rs.getRow()); + + // allowed for compatibility + assertFalse(rs.absolute(0)); + + assertTrue(rs.absolute(3)); + assertEquals(3, rs.getRow()); + + if (!config.lazy) { + assertTrue(rs.absolute(-1)); + assertEquals(3, rs.getRow()); + + assertTrue(rs.absolute(-2)); + assertEquals(2, rs.getRow()); + } + + assertFalse(rs.absolute(4)); + assertTrue(rs.isAfterLast()); + + assertFalse(rs.absolute(5)); + assertTrue(rs.isAfterLast()); + + assertTrue(rs.previous()); + assertEquals(3, rs.getRow()); + + assertTrue(rs.previous()); + assertEquals(2, rs.getRow()); + + conn.close(); + } + + private void testUpdateDataType() throws Exception { + deleteDb("updatableResultSet"); + Connection conn = getConnection("updatableResultSet"); + Statement stat = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_UPDATABLE); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255), " + + "DEC DECIMAL(10,2), BOO BIT, BYE TINYINT, BIN VARBINARY(100), " + + "D DATE, T TIME, TS TIMESTAMP(9), TSTZ TIMESTAMP(9) WITH TIME ZONE, DB DOUBLE, R REAL, L BIGINT, " + + "O_I INT, SH SMALLINT, CL CLOB, BL BLOB)"); + final int clobIndex = 16, blobIndex = 17; + ResultSet rs = stat.executeQuery("SELECT * FROM TEST"); + ResultSetMetaData meta = rs.getMetaData(); + int c = 0; + assertEquals("java.lang.Integer", meta.getColumnClassName(++c)); + assertEquals("java.lang.String", meta.getColumnClassName(++c)); + assertEquals("java.math.BigDecimal", meta.getColumnClassName(++c)); + assertEquals("java.lang.Boolean", meta.getColumnClassName(++c)); + assertEquals("java.lang.Integer", meta.getColumnClassName(++c)); + assertEquals("[B", meta.getColumnClassName(++c)); + assertEquals("java.sql.Date", meta.getColumnClassName(++c)); + assertEquals("java.sql.Time", meta.getColumnClassName(++c)); + assertEquals("java.sql.Timestamp", meta.getColumnClassName(++c)); + assertEquals("java.time.OffsetDateTime", meta.getColumnClassName(++c)); + assertEquals("java.lang.Double", meta.getColumnClassName(++c)); + assertEquals("java.lang.Float", meta.getColumnClassName(++c)); + assertEquals("java.lang.Long", meta.getColumnClassName(++c)); + assertEquals("java.lang.Integer", meta.getColumnClassName(++c)); + assertEquals("java.lang.Integer", meta.getColumnClassName(++c)); + assertEquals("java.sql.Clob", meta.getColumnClassName(++c)); + assertEquals("java.sql.Blob", meta.getColumnClassName(++c)); + rs.moveToInsertRow(); + rs.updateInt(1, 0); + rs.updateNull(2); + rs.updateNull("DEC"); + // 'not set' values are set to null + assertThrows(ErrorCode.NO_DATA_AVAILABLE, rs).cancelRowUpdates(); + rs.insertRow(); + + rs.moveToInsertRow(); + c = 0; + rs.updateInt(++c, 1); + rs.updateString(++c, null); + rs.updateBigDecimal(++c, null); + rs.updateBoolean(++c, false); + rs.updateByte(++c, (byte) 0); + rs.updateBytes(++c, null); + rs.updateDate(++c, null); + rs.updateTime(++c, null); + rs.updateTimestamp(++c, null); + rs.updateObject(++c, null); + rs.updateDouble(++c, 0.0); + rs.updateFloat(++c, 0.0f); + rs.updateLong(++c, 0L); + rs.updateObject(++c, null); + rs.updateShort(++c, (short) 0); + rs.updateCharacterStream(++c, new StringReader("test"), 0); + rs.updateBinaryStream(++c, + new ByteArrayInputStream(new byte[] { (byte) 0xff, 0x00 }), 0); + rs.insertRow(); + + rs.moveToInsertRow(); + rs.updateInt("ID", 2); + rs.updateString("NAME", "+"); + rs.updateBigDecimal("DEC", new BigDecimal("1.2")); + rs.updateBoolean("BOO", true); + rs.updateByte("BYE", (byte) 0xff); + rs.updateBytes("BIN", new byte[] { 0x00, (byte) 0xff }); + rs.updateDate("D", Date.valueOf("2005-09-21")); + rs.updateTime("T", Time.valueOf("21:46:28")); + rs.updateTimestamp("TS", + Timestamp.valueOf("2005-09-21 21:47:09.567890123")); + rs.updateObject("TSTZ", OffsetDateTime.of(LocalDate.of(2005, 9, 21), + LocalTime.ofNanoOfDay(81_189_123_456_789L), ZoneOffset.ofHours(1))); + rs.updateDouble("DB", 1.725); + rs.updateFloat("R", 2.5f); + rs.updateLong("L", Long.MAX_VALUE); + rs.updateObject("O_I", 10); + rs.updateShort("SH", Short.MIN_VALUE); + // auml, ouml, uuml + rs.updateCharacterStream("CL", new StringReader("\u00ef\u00f6\u00fc"), 0); + rs.updateBinaryStream("BL", + new ByteArrayInputStream(new byte[] { (byte) 0xab, 0x12 }), 0); + rs.insertRow(); + + rs.moveToInsertRow(); + rs.updateInt("ID", 3); + rs.updateCharacterStream("CL", new StringReader("\u00ef\u00f6\u00fc")); + rs.updateBinaryStream("BL", + new ByteArrayInputStream(new byte[] { (byte) 0xab, 0x12 })); + rs.insertRow(); + + rs.moveToInsertRow(); + rs.updateInt("ID", 4); + rs.updateCharacterStream(clobIndex, new StringReader("\u00ef\u00f6\u00fc")); + rs.updateBinaryStream(blobIndex, + new ByteArrayInputStream(new byte[] { (byte) 0xab, 0x12 })); + rs.insertRow(); + + rs.moveToInsertRow(); + rs.updateInt("ID", 5); + rs.updateClob("CL", new StringReader("\u00ef\u00f6\u00fc")); + rs.updateBlob("BL", + new ByteArrayInputStream(new byte[] { (byte) 0xab, 0x12 })); + rs.insertRow(); + + rs.moveToInsertRow(); + rs.updateInt("ID", 6); + rs.updateClob(clobIndex, new StringReader("\u00ef\u00f6\u00fc")); + rs.updateBlob(blobIndex, + new ByteArrayInputStream(new byte[] { (byte) 0xab, 0x12 })); + rs.insertRow(); + + rs.moveToInsertRow(); + rs.updateInt("ID", 7); + rs.updateNClob("CL", new StringReader("\u00ef\u00f6\u00fc")); + Blob b = conn.createBlob(); + OutputStream out = b.setBinaryStream(1); + out.write(new byte[] { (byte) 0xab, 0x12 }); + out.close(); + rs.updateBlob("BL", b); + rs.insertRow(); + + rs.moveToInsertRow(); + rs.updateInt("ID", 8); + rs.updateNClob(clobIndex, new StringReader("\u00ef\u00f6\u00fc")); + rs.updateBlob(blobIndex, b); + rs.insertRow(); + + rs.moveToInsertRow(); + rs.updateInt("ID", 9); + rs.updateNClob("CL", new StringReader("\u00ef\u00f6\u00fc"), -1); + rs.updateBlob("BL", b); + rs.insertRow(); + + rs.moveToInsertRow(); + rs.updateInt("ID", 10); + rs.updateNClob(clobIndex, new StringReader("\u00ef\u00f6\u00fc"), -1); + rs.updateBlob(blobIndex, b); + rs.insertRow(); + + rs.moveToInsertRow(); + rs.updateInt("ID", 11); + rs.updateNCharacterStream("CL", + new StringReader("\u00ef\u00f6\u00fc"), -1); + rs.updateBlob("BL", b); + rs.insertRow(); + + rs.moveToInsertRow(); + rs.updateInt("ID", 12); + rs.updateNCharacterStream(clobIndex, + new StringReader("\u00ef\u00f6\u00fc"), -1); + rs.updateBlob(blobIndex, b); + rs.insertRow(); + + rs.moveToInsertRow(); + rs.updateInt("ID", 13); + rs.updateNCharacterStream("CL", + new StringReader("\u00ef\u00f6\u00fc")); + rs.updateBlob("BL", b); + rs.insertRow(); + + rs.moveToInsertRow(); + rs.updateInt("ID", 14); + rs.updateNCharacterStream(clobIndex, + new StringReader("\u00ef\u00f6\u00fc")); + rs.updateBlob(blobIndex, b); + rs.insertRow(); + + rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID NULLS FIRST"); + rs.next(); + c = 0; + assertTrue(rs.getInt(++c) == 0); + assertTrue(rs.getString(++c) == null && rs.wasNull()); + assertTrue(rs.getBigDecimal(++c) == null && rs.wasNull()); + assertTrue(!rs.getBoolean(++c) && rs.wasNull()); + assertTrue(rs.getByte(++c) == 0 && rs.wasNull()); + assertTrue(rs.getBytes(++c) == null && rs.wasNull()); + assertTrue(rs.getDate(++c) == null && rs.wasNull()); + assertTrue(rs.getTime(++c) == null && rs.wasNull()); + assertTrue(rs.getTimestamp(++c) == null && rs.wasNull()); + assertTrue(rs.getDouble(++c) == 0.0 && rs.wasNull()); + assertTrue(rs.getFloat(++c) == 0.0 && rs.wasNull()); + assertTrue(rs.getLong(++c) == 0 && rs.wasNull()); + assertTrue(rs.getObject(++c) == null && rs.wasNull()); + assertTrue(rs.getShort(++c) == 0 && rs.wasNull()); + assertTrue(rs.getCharacterStream(++c) == null && rs.wasNull()); + assertTrue(rs.getBinaryStream(++c) == null && rs.wasNull()); + + rs.next(); + c = 0; + assertTrue(rs.getInt(++c) == 1); + assertTrue(rs.getString(++c) == null && rs.wasNull()); + assertTrue(rs.getBigDecimal(++c) == null && rs.wasNull()); + assertTrue(!rs.getBoolean(++c) && !rs.wasNull()); + assertTrue(rs.getByte(++c) == 0 && !rs.wasNull()); + assertTrue(rs.getBytes(++c) == null && rs.wasNull()); + assertTrue(rs.getDate(++c) == null && rs.wasNull()); + assertTrue(rs.getTime(++c) == null && rs.wasNull()); + assertTrue(rs.getTimestamp(++c) == null && rs.wasNull()); + assertTrue(rs.getObject(++c) == null && rs.wasNull()); + assertTrue(rs.getDouble(++c) == 0.0 && !rs.wasNull()); + assertTrue(rs.getFloat(++c) == 0.0 && !rs.wasNull()); + assertTrue(rs.getLong(++c) == 0 && !rs.wasNull()); + assertTrue(rs.getObject(++c) == null && rs.wasNull()); + assertTrue(rs.getShort(++c) == 0 && !rs.wasNull()); + assertEquals("test", rs.getString(++c)); + assertEquals(new byte[] { (byte) 0xff, 0x00 }, rs.getBytes(++c)); + + rs.next(); + c = 0; + assertTrue(rs.getInt(++c) == 2); + assertEquals("+", rs.getString(++c)); + assertEquals("1.20", rs.getBigDecimal(++c).toString()); + assertTrue(rs.getBoolean(++c)); + assertTrue((rs.getByte(++c) & 0xff) == 0xff); + assertEquals(new byte[] { 0x00, (byte) 0xff }, rs.getBytes(++c)); + assertEquals("2005-09-21", rs.getDate(++c).toString()); + assertEquals("21:46:28", rs.getTime(++c).toString()); + assertEquals("2005-09-21 21:47:09.567890123", rs.getTimestamp(++c).toString()); + assertEquals("2005-09-21T22:33:09.123456789+01:00", rs.getObject(++c).toString()); + assertTrue(rs.getDouble(++c) == 1.725); + assertTrue(rs.getFloat(++c) == 2.5f); + assertTrue(rs.getLong(++c) == Long.MAX_VALUE); + assertEquals(10, ((Integer) rs.getObject(++c)).intValue()); + assertTrue(rs.getShort(++c) == Short.MIN_VALUE); + // auml ouml uuml + assertEquals("\u00ef\u00f6\u00fc", rs.getString(++c)); + assertEquals(new byte[] { (byte) 0xab, 0x12 }, rs.getBytes(++c)); + c = 1; + rs.updateString(++c, "-"); + rs.updateBigDecimal(++c, new BigDecimal("1.30")); + rs.updateBoolean(++c, false); + rs.updateByte(++c, (byte) 0x55); + rs.updateBytes(++c, new byte[] { 0x01, (byte) 0xfe }); + rs.updateDate(++c, Date.valueOf("2005-09-22")); + rs.updateTime(++c, Time.valueOf("21:46:29")); + rs.updateTimestamp(++c, Timestamp.valueOf("2005-09-21 21:47:10.111222333")); + rs.updateObject(++c, OffsetDateTime.of(LocalDate.of(2005, 9, 22), LocalTime.ofNanoOfDay(10_111_222_333L), + ZoneOffset.ofHours(2))); + rs.updateDouble(++c, 2.25); + rs.updateFloat(++c, 3.5f); + rs.updateLong(++c, Long.MAX_VALUE - 1); + rs.updateInt(++c, 11); + rs.updateShort(++c, (short) -1_000); + rs.updateString(++c, "ABCD"); + rs.updateBytes(++c, new byte[] { 1, 2 }); + rs.updateRow(); + + for (int i = 3; i <= 14; i++) { + rs.next(); + assertEquals(i, rs.getInt(1)); + assertEquals("\u00ef\u00f6\u00fc", rs.getString(clobIndex)); + assertEquals(new byte[] { (byte) 0xab, 0x12 }, rs.getBytes(blobIndex)); + } + assertFalse(rs.next()); + + rs = stat.executeQuery("SELECT * FROM TEST WHERE ID = 2"); + rs.next(); + c = 0; + assertTrue(rs.getInt(++c) == 2); + assertEquals("-", rs.getString(++c)); + assertEquals("1.30", rs.getBigDecimal(++c).toString()); + assertFalse(rs.getBoolean(++c)); + assertTrue((rs.getByte(++c) & 0xff) == 0x55); + assertEquals(new byte[] { 0x01, (byte) 0xfe }, rs.getBytes(++c)); + assertEquals("2005-09-22", rs.getDate(++c).toString()); + assertEquals("21:46:29", rs.getTime(++c).toString()); + assertEquals("2005-09-21 21:47:10.111222333", rs.getTimestamp(++c).toString()); + assertEquals("2005-09-22T00:00:10.111222333+02:00", rs.getObject(++c).toString()); + assertTrue(rs.getDouble(++c) == 2.25); + assertTrue(rs.getFloat(++c) == 3.5f); + assertTrue(rs.getLong(++c) == Long.MAX_VALUE - 1); + assertEquals(11, ((Integer) rs.getObject(++c)).intValue()); + assertTrue(rs.getShort(++c) == -1_000); + assertEquals("ABCD", rs.getString(++c)); + assertEquals(new byte[] { 1, 2 }, rs.getBytes(++c)); + assertFalse(rs.next()); + + stat.execute("DROP TABLE TEST"); + conn.close(); + } + + private void testUpdateDeleteInsert() throws SQLException { + deleteDb("updatableResultSet"); + Connection c1 = getConnection("updatableResultSet"); + Connection c2 = getConnection("updatableResultSet"); + Statement stat = c1.createStatement(ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_UPDATABLE); + stat.execute("DROP TABLE IF EXISTS TEST"); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + int max = 8; + for (int i = 0; i < max; i++) { + stat.execute("INSERT INTO TEST VALUES(" + i + ", 'Hello" + i + "')"); + } + ResultSet rs; + rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + rs.next(); + assertEquals(0, rs.getInt(1)); + rs.moveToInsertRow(); + rs.updateInt(1, 100); + rs.moveToCurrentRow(); + assertEquals(0, rs.getInt(1)); + + rs = stat.executeQuery("SELECT * FROM TEST"); + int j = max; + while (rs.next()) { + int id = rs.getInt(1); + if (id % 2 == 0) { + Statement s2 = c2.createStatement(); + s2.execute("UPDATE TEST SET NAME = NAME || '+' WHERE ID = " + rs.getInt(1)); + if (id % 4 == 0) { + rs.refreshRow(); + } + rs.updateString(2, "Updated " + rs.getString(2)); + rs.updateRow(); + } else { + rs.deleteRow(); + } + // the driver does not detect it in any case + assertFalse(rs.rowUpdated()); + assertFalse(rs.rowInserted()); + assertFalse(rs.rowDeleted()); + + rs.moveToInsertRow(); + rs.updateString(2, "Inserted " + j); + rs.updateInt(1, j); + j += 2; + rs.insertRow(); + + // the driver does not detect it in any case + assertFalse(rs.rowUpdated()); + assertFalse(rs.rowInserted()); + assertFalse(rs.rowDeleted()); + + } + rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + while (rs.next()) { + int id = rs.getInt(1); + String name = rs.getString(2); + assertEquals(0, id % 2); + if (id >= max) { + assertEquals("Inserted " + id, rs.getString(2)); + } else { + if (id % 4 == 0) { + assertEquals("Updated Hello" + id + "+", rs.getString(2)); + } else { + assertEquals("Updated Hello" + id, rs.getString(2)); + } + } + trace("id=" + id + " name=" + name); + } + c2.close(); + c1.close(); + + // test scrollable result sets + Connection conn = getConnection("updatableResultSet"); + for (int i = 0; i < 5; i++) { + testScrollable(conn, i); + } + conn.close(); + } + + private void testScrollable(Connection conn, int rows) throws SQLException { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE IF NOT EXISTS TEST" + + "(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + stat.execute("DELETE FROM TEST"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST VALUES(?, ?)"); + for (int i = 0; i < rows; i++) { + prep.setInt(1, i); + prep.setString(2, "Data " + i); + prep.execute(); + } + Statement regular = conn.createStatement(); + testScrollResultSet(regular, ResultSet.TYPE_FORWARD_ONLY, rows); + Statement scroll = conn.createStatement( + ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + testScrollResultSet(scroll, ResultSet.TYPE_SCROLL_INSENSITIVE, rows); + } + + private void testScrollResultSet(Statement stat, int type, int rows) + throws SQLException { + boolean error = false; + if (type == ResultSet.TYPE_FORWARD_ONLY) { + error = true; + } + ResultSet rs = stat.executeQuery("SELECT * FROM TEST"); + assertEquals(type, rs.getType()); + + assertState(rs, rows > 0, false, false, false); + for (int i = 0; i < rows; i++) { + rs.next(); + assertState(rs, rows == 0, i == 0, i == rows - 1, rows == 0 || i == rows); + } + try { + rs.beforeFirst(); + assertState(rs, rows > 0, false, false, false); + } catch (SQLException e) { + if (!error) { + throw e; + } + } + try { + rs.afterLast(); + assertState(rs, false, false, false, rows > 0); + } catch (SQLException e) { + if (!error) { + throw e; + } + } + try { + boolean valid = rs.first(); + assertEquals(rows > 0, valid); + if (valid) { + assertState(rs, false, true, rows == 1, rows == 0); + } + } catch (SQLException e) { + if (!error) { + throw e; + } + } + try { + boolean valid = rs.last(); + assertEquals(rows > 0, valid); + if (valid) { + assertState(rs, false, rows == 1, true, rows == 0); + } + } catch (SQLException e) { + if (!error) { + throw e; + } + } + } + + private void testUpdateObject() throws SQLException { + deleteDb("updatableResultSet"); + Connection conn = getConnection("updatableResultSet"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, V INT)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST VALUES (?1, ?1)"); + for (int i = 1; i <= 12; i++) { + prep.setInt(1, i); + prep.executeUpdate(); + } + prep = conn.prepareStatement("TABLE TEST ORDER BY ID", ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_UPDATABLE); + try (ResultSet rs = prep.executeQuery()) { + for (int i = 1; i <= 12; i++) { + rs.next(); + assertEquals(i, rs.getInt(1)); + assertEquals(i, rs.getInt(2)); + testUpdateObjectUpdateRow(rs, i, i * 10); + rs.updateRow(); + } + assertFalse(rs.next()); + } + try (ResultSet rs = prep.executeQuery()) { + for (int i = 1; i <= 12; i++) { + assertTrue(rs.next()); + assertEquals(i, rs.getInt(1)); + assertEquals(i * 10, rs.getInt(2)); + testUpdateObjectUpdateRow(rs, i, null); + rs.updateRow(); + } + assertFalse(rs.next()); + } + try (ResultSet rs = prep.executeQuery()) { + for (int i = 1; i <= 12; i++) { + assertTrue(rs.next()); + assertEquals(i, rs.getInt(1)); + assertNull(rs.getObject(2)); + } + assertFalse(rs.next()); + } + conn.close(); + } + + private static void testUpdateObjectUpdateRow(ResultSet rs, int method, Object value) throws SQLException { + switch (method) { + case 1: + rs.updateObject(2, value); + break; + case 2: + rs.updateObject("V", value); + break; + case 3: + rs.updateObject(2, value, 0); + break; + case 4: + rs.updateObject(2, value, JDBCType.INTEGER); + break; + case 5: + rs.updateObject(2, value, H2Type.INTEGER); + break; + case 6: + rs.updateObject("V", value, 0); + break; + case 7: + rs.updateObject("V", value, JDBCType.INTEGER); + break; + case 8: + rs.updateObject("V", value, H2Type.INTEGER); + break; + case 9: + rs.updateObject(2, value, JDBCType.INTEGER, 0); + break; + case 10: + rs.updateObject(2, value, H2Type.INTEGER, 0); + break; + case 11: + rs.updateObject("V", value, JDBCType.INTEGER, 0); + break; + case 12: + rs.updateObject("V", value, H2Type.INTEGER, 0); + } + } + + private void assertState(ResultSet rs, boolean beforeFirst, + boolean first, boolean last, boolean afterLast) throws SQLException { + assertEquals(beforeFirst, rs.isBeforeFirst()); + assertEquals(first, rs.isFirst()); + assertEquals(last, rs.isLast()); + assertEquals(afterLast, rs.isAfterLast()); + } + +} diff --git a/h2/src/test/org/h2/test/jdbc/TestUrlJavaObjectSerializer.java b/h2/src/test/org/h2/test/jdbc/TestUrlJavaObjectSerializer.java new file mode 100644 index 0000000..b1e7634 --- /dev/null +++ b/h2/src/test/org/h2/test/jdbc/TestUrlJavaObjectSerializer.java @@ -0,0 +1,95 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbc; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; +import java.sql.Types; +import org.h2.api.JavaObjectSerializer; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests per-db {@link JavaObjectSerializer} when set through the JDBC URL. + * + * @author Davide Cavestro + */ +public class TestUrlJavaObjectSerializer extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = createCaller().init(); + test.config.traceTest = true; + test.config.memory = true; + test.config.networked = true; + test.testFromMain(); + } + + @Override + public void test() throws Exception { + FakeJavaObjectSerializer.testBaseRef = this; + try { + deleteDb("javaSerializer"); + String fqn = FakeJavaObjectSerializer.class.getName(); + Connection conn = getConnection( + "javaSerializer;JAVA_OBJECT_SERIALIZER='"+fqn+"'"); + + Statement stat = conn.createStatement(); + stat.execute("create table t1(id identity, val other)"); + + PreparedStatement ins = conn.prepareStatement( + "insert into t1(val) values(?)"); + + ins.setObject(1, 100500, Types.JAVA_OBJECT); + assertEquals(1, ins.executeUpdate()); + + Statement s = conn.createStatement(); + ResultSet rs = s.executeQuery("select val from t1"); + + assertTrue(rs.next()); + + assertEquals(100500, ((Integer) rs.getObject(1)).intValue()); + assertEquals(new byte[] { 1, 2, 3 }, rs.getBytes(1)); + + conn.close(); + deleteDb("javaSerializer"); + } finally { + FakeJavaObjectSerializer.testBaseRef = null; + } + } + + /** + * The serializer to use for this test. + */ + public static class FakeJavaObjectSerializer implements JavaObjectSerializer { + + /** + * The test. + */ + static TestBase testBaseRef; + + @Override + public byte[] serialize(Object obj) throws Exception { + testBaseRef.assertEquals(100500, ((Integer) obj).intValue()); + + return new byte[] { 1, 2, 3 }; + } + + @Override + public Object deserialize(byte[] bytes) throws Exception { + testBaseRef.assertEquals(new byte[] { 1, 2, 3 }, bytes); + + return 100500; + } + + } +} diff --git a/h2/src/test/org/h2/test/jdbc/TestZloty.java b/h2/src/test/org/h2/test/jdbc/TestZloty.java new file mode 100644 index 0000000..e915849 --- /dev/null +++ b/h2/src/test/org/h2/test/jdbc/TestZloty.java @@ -0,0 +1,121 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbc; + +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests a custom BigDecimal implementation, as well + * as direct modification of a byte in a byte array. + */ +public class TestZloty extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + testZloty(); + testModifyBytes(); + deleteDb("zloty"); + } + + /** + * This class overrides BigDecimal and implements some strange comparison + * method. + */ + private static class ZlotyBigDecimal extends BigDecimal { + + private static final long serialVersionUID = 1L; + + public ZlotyBigDecimal(String s) { + super(s); + } + + @Override + public int compareTo(BigDecimal bd) { + return -super.compareTo(bd); + } + + } + + private void testModifyBytes() throws SQLException { + deleteDb("zloty"); + Connection conn = getConnection("zloty"); + conn.createStatement().execute( + "CREATE TABLE TEST(ID INT, DATA BINARY)"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST VALUES(?, ?)"); + byte[] shared = { 0 }; + prep.setInt(1, 0); + prep.setBytes(2, shared); + prep.execute(); + shared[0] = 1; + prep.setInt(1, 1); + prep.setBytes(2, shared); + prep.execute(); + ResultSet rs = conn.createStatement().executeQuery( + "SELECT * FROM TEST ORDER BY ID"); + rs.next(); + assertEquals(0, rs.getInt(1)); + assertEquals(0, rs.getBytes(2)[0]); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals(1, rs.getBytes(2)[0]); + rs.getBytes(2)[0] = 2; + assertEquals(1, rs.getBytes(2)[0]); + assertFalse(rs.next()); + conn.close(); + } + + /** + * H2 destroyer application ;-> + * + * @author Maciej Wegorkiewicz + */ + private void testZloty() throws SQLException { + deleteDb("zloty"); + Connection conn = getConnection("zloty"); + conn.createStatement().execute("CREATE TABLE TEST(ID INT, AMOUNT DECIMAL)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST VALUES(?, ?)"); + prep.setInt(1, 1); + prep.setBigDecimal(2, new BigDecimal("10.0")); + prep.execute(); + prep.setInt(1, 2); + assertThrows(ErrorCode.INVALID_CLASS_2, prep). + setBigDecimal(2, new ZlotyBigDecimal("11.0")); + + prep.setInt(1, 3); + BigDecimal value = new BigDecimal("12.100000") { + + private static final long serialVersionUID = 1L; + + @Override + public String toString() { + return "12,100000 EURO"; + } + }; + assertThrows(ErrorCode.INVALID_CLASS_2, prep). + setBigDecimal(2, value); + + conn.close(); + } + +} diff --git a/h2/src/test/org/h2/test/jdbc/package.html b/h2/src/test/org/h2/test/jdbc/package.html new file mode 100644 index 0000000..bf78702 --- /dev/null +++ b/h2/src/test/org/h2/test/jdbc/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +JDBC API tests. + +

    \ No newline at end of file diff --git a/h2/src/test/org/h2/test/jdbcx/SimpleXid.java b/h2/src/test/org/h2/test/jdbcx/SimpleXid.java new file mode 100644 index 0000000..666239b --- /dev/null +++ b/h2/src/test/org/h2/test/jdbcx/SimpleXid.java @@ -0,0 +1,85 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbcx; + +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicInteger; +import javax.transaction.xa.Xid; +import org.h2.util.MathUtils; + +/** + * A simple Xid implementation. + */ +public class SimpleXid implements Xid { + + private static AtomicInteger next = new AtomicInteger(); + + private final int formatId; + private final byte[] branchQualifier; + private final byte[] globalTransactionId; + + private SimpleXid(int formatId, byte[] branchQualifier, + byte[] globalTransactionId) { + this.formatId = formatId; + this.branchQualifier = branchQualifier; + this.globalTransactionId = globalTransactionId; + } + + /** + * Create a new random xid. + * + * @return the new object + */ + public static SimpleXid createRandom() { + int formatId = next.getAndIncrement(); + byte[] bq = new byte[MAXBQUALSIZE]; + MathUtils.randomBytes(bq); + byte[] gt = new byte[MAXGTRIDSIZE]; + MathUtils.randomBytes(gt); + return new SimpleXid(formatId, bq, gt); + } + + @Override + public byte[] getBranchQualifier() { + return branchQualifier; + } + + @Override + public int getFormatId() { + return formatId; + } + + @Override + public byte[] getGlobalTransactionId() { + return globalTransactionId; + } + + @Override + public int hashCode() { + return formatId; + } + + @Override + public boolean equals(Object other) { + if (other instanceof Xid) { + Xid xid = (Xid) other; + if (xid.getFormatId() == formatId) { + if (Arrays.equals(branchQualifier, xid.getBranchQualifier())) { + if (Arrays.equals(globalTransactionId, xid.getGlobalTransactionId())) { + return true; + } + } + } + } + return false; + } + + @Override + public String toString() { + return "xid:" + formatId; + } + +} diff --git a/h2/src/test/org/h2/test/jdbcx/TestConnectionPool.java b/h2/src/test/org/h2/test/jdbcx/TestConnectionPool.java new file mode 100644 index 0000000..dab7d29 --- /dev/null +++ b/h2/src/test/org/h2/test/jdbcx/TestConnectionPool.java @@ -0,0 +1,269 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbcx; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.sql.DataSource; + +import org.h2.api.ErrorCode; +import org.h2.jdbcx.JdbcConnectionPool; +import org.h2.jdbcx.JdbcDataSource; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.Task; + +/** + * This class tests the JdbcConnectionPool. + */ +public class TestConnectionPool extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb("connectionPool"); + testShutdown(); + testWrongUrl(); + testTimeout(); + testUncommittedTransaction(); + testPerformance(); + testKeepOpen(); + testConnect(); + testThreads(); + testUnwrap(); + deleteDb("connectionPool"); + deleteDb("connectionPool2"); + } + + private void testShutdown() throws SQLException { + String url = getURL("connectionPool2", true), user = getUser(); + String password = getPassword(); + JdbcConnectionPool cp = JdbcConnectionPool.create(url, user, password); + StringWriter w = new StringWriter(); + cp.setLogWriter(new PrintWriter(w)); + Connection conn1 = cp.getConnection(); + Connection conn2 = cp.getConnection(); + conn1.close(); + conn2.createStatement().execute("shutdown immediately"); + cp.dispose(); + assertTrue(w.toString().length() == 0); + cp.dispose(); + } + + private void testWrongUrl() { + JdbcConnectionPool cp = JdbcConnectionPool.create( + "jdbc:wrong:url", "", ""); + try { + cp.getConnection(); + } catch (SQLException e) { + assertEquals(ErrorCode.URL_FORMAT_ERROR_2, e.getErrorCode()); + } + cp.dispose(); + } + + private void testTimeout() throws Exception { + String url = getURL("connectionPool", true), user = getUser(); + String password = getPassword(); + final JdbcConnectionPool man = JdbcConnectionPool.create(url, user, password); + man.setLoginTimeout(1); + assertThrows(IllegalArgumentException.class, () -> man.setMaxConnections(-1)); + man.setMaxConnections(2); + // connection 1 (of 2) + Connection conn = man.getConnection(); + Task t = new Task() { + @Override + public void call() { + while (!stop) { + // this calls notifyAll + man.setMaxConnections(1); + man.setMaxConnections(2); + } + } + }; + t.execute(); + long time = System.nanoTime(); + Connection conn2 = null; + try { + // connection 2 (of 1 or 2) may fail + conn2 = man.getConnection(); + // connection 3 (of 1 or 2) must fail + man.getConnection(); + fail(); + } catch (SQLException e) { + if (conn2 != null) { + conn2.close(); + } + assertContains(e.toString().toLowerCase(), "timeout"); + time = System.nanoTime() - time; + assertTrue("timeout after " + TimeUnit.NANOSECONDS.toMillis(time) + + " ms", time > TimeUnit.SECONDS.toNanos(1)); + } finally { + conn.close(); + t.get(); + } + + man.dispose(); + } + + private void testUncommittedTransaction() throws SQLException { + String url = getURL("connectionPool", true), user = getUser(); + String password = getPassword(); + JdbcConnectionPool man = JdbcConnectionPool.create(url, user, password); + + assertEquals(30, man.getLoginTimeout()); + man.setLoginTimeout(1); + assertEquals(1, man.getLoginTimeout()); + man.setLoginTimeout(0); + assertEquals(30, man.getLoginTimeout()); + assertEquals(10, man.getMaxConnections()); + + PrintWriter old = man.getLogWriter(); + PrintWriter pw = new PrintWriter(new StringWriter()); + man.setLogWriter(pw); + assertTrue(pw == man.getLogWriter()); + man.setLogWriter(old); + + Connection conn1 = man.getConnection(); + assertTrue(conn1.getAutoCommit()); + conn1.setAutoCommit(false); + conn1.close(); + assertTrue(conn1.isClosed()); + + Connection conn2 = man.getConnection(); + assertTrue(conn2.getAutoCommit()); + conn2.close(); + + man.dispose(); + } + + private void testPerformance() throws SQLException { + String url = getURL("connectionPool", true), user = getUser(); + String password = getPassword(); + JdbcConnectionPool man = JdbcConnectionPool.create(url, user, password); + Connection conn = man.getConnection(); + int len = 1000; + long time = System.nanoTime(); + for (int i = 0; i < len; i++) { + man.getConnection().close(); + } + man.dispose(); + trace((int) TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - time)); + time = System.nanoTime(); + for (int i = 0; i < len; i++) { + DriverManager.getConnection(url, user, password).close(); + } + trace((int) TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - time)); + conn.close(); + } + + private void testKeepOpen() throws Exception { + JdbcConnectionPool man = getConnectionPool(1); + Connection conn = man.getConnection(); + Statement stat = conn.createStatement(); + stat.execute("create local temporary table test(id int)"); + conn.close(); + conn = man.getConnection(); + stat = conn.createStatement(); + stat.execute("select * from test"); + conn.close(); + man.dispose(); + } + + private void testThreads() throws Exception { + final int len = getSize(4, 20); + final JdbcConnectionPool man = getConnectionPool(len - 2); + final AtomicBoolean stop = new AtomicBoolean(); + + /** + * This class gets and returns connections from the pool. + */ + class TestRunner implements Runnable { + @Override + public void run() { + try { + while (!stop.get()) { + Connection conn = man.getConnection(); + if (man.getActiveConnections() >= len + 1) { + throw new Exception("a: " + + man.getActiveConnections() + + " is not smaller than b: " + (len + 1)); + } + Statement stat = conn.createStatement(); + stat.execute("SELECT 1 FROM DUAL"); + conn.close(); + Thread.sleep(100); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + Thread[] threads = new Thread[len]; + for (int i = 0; i < len; i++) { + threads[i] = new Thread(new TestRunner()); + threads[i].start(); + } + Thread.sleep(1000); + stop.set(true); + for (int i = 0; i < len; i++) { + threads[i].join(); + } + assertEquals(0, man.getActiveConnections()); + man.dispose(); + } + + private JdbcConnectionPool getConnectionPool(int poolSize) { + JdbcDataSource ds = new JdbcDataSource(); + ds.setURL(getURL("connectionPool", true)); + ds.setUser(getUser()); + ds.setPassword(getPassword()); + JdbcConnectionPool pool = JdbcConnectionPool.create(ds); + pool.setMaxConnections(poolSize); + return pool; + } + + private void testConnect() throws SQLException { + JdbcConnectionPool pool = getConnectionPool(3); + for (int i = 0; i < 100; i++) { + Connection conn = pool.getConnection(); + conn.close(); + } + pool.dispose(); + DataSource ds = pool; + assertThrows(IllegalStateException.class, ds). + getConnection(); + assertThrows(UnsupportedOperationException.class, ds). + getConnection(null, null); + } + + private void testUnwrap() throws SQLException { + JdbcConnectionPool pool = JdbcConnectionPool.create(new JdbcDataSource()); + assertTrue(pool.isWrapperFor(Object.class)); + assertTrue(pool.isWrapperFor(DataSource.class)); + assertTrue(pool.isWrapperFor(pool.getClass())); + assertFalse(pool.isWrapperFor(Integer.class)); + assertTrue(pool == pool.unwrap(Object.class)); + assertTrue(pool == pool.unwrap(DataSource.class)); + assertTrue(pool == pool.unwrap(pool.getClass())); + assertThrows(ErrorCode.INVALID_VALUE_2, () -> pool.unwrap(Integer.class)); + } + +} diff --git a/h2/src/test/org/h2/test/jdbcx/TestDataSource.java b/h2/src/test/org/h2/test/jdbcx/TestDataSource.java new file mode 100644 index 0000000..20c9213 --- /dev/null +++ b/h2/src/test/org/h2/test/jdbcx/TestDataSource.java @@ -0,0 +1,210 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbcx; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import javax.naming.Reference; +import javax.naming.StringRefAddr; +import javax.naming.spi.ObjectFactory; +import javax.sql.ConnectionEvent; +import javax.sql.ConnectionEventListener; +import javax.sql.DataSource; +import javax.sql.XAConnection; +import javax.transaction.xa.XAResource; +import javax.transaction.xa.Xid; +import org.h2.api.ErrorCode; +import org.h2.jdbcx.JdbcDataSource; +import org.h2.jdbcx.JdbcDataSourceFactory; +import org.h2.jdbcx.JdbcXAConnection; +import org.h2.message.TraceSystem; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests DataSource and XAConnection. + */ +public class TestDataSource extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + +// public static void main(String... args) throws SQLException { +// +// // first, need to start on the command line: +// // rmiregistry 1099 +// +// // System.setProperty(Context.INITIAL_CONTEXT_FACTORY, +// "com.sun.jndi.ldap.LdapCtxFactory"); +// System.setProperty(Context.INITIAL_CONTEXT_FACTORY, +// "com.sun.jndi.rmi.registry.RegistryContextFactory"); +// System.setProperty(Context.PROVIDER_URL, "rmi://localhost:1099"); +// +// JdbcDataSource ds = new JdbcDataSource(); +// ds.setURL("jdbc:h2:./test"); +// ds.setUser("test"); +// ds.setPassword(""); +// +// Context ctx = new InitialContext(); +// ctx.bind("jdbc/test", ds); +// +// DataSource ds2 = (DataSource)ctx.lookup("jdbc/test"); +// Connection conn = ds2.getConnection(); +// conn.close(); +// } + + @Override + public void test() throws Exception { + if (config.traceLevelFile > 0) { + TraceSystem sys = JdbcDataSourceFactory.getTraceSystem(); + sys.setFileName(getBaseDir() + "/test/trace"); + sys.setLevelFile(3); + } + testDataSourceFactory(); + testDataSource(); + testUnwrap(); + testXAConnection(); + // otherwise we sometimes can't delete the trace file when the TestAll cleanup code runs + JdbcDataSourceFactory.getTraceSystem().close(); + deleteDb("dataSource"); + } + + private void testDataSourceFactory() throws Exception { + ObjectFactory factory = new JdbcDataSourceFactory(); + assertTrue(null == factory.getObjectInstance("test", null, null, null)); + Reference ref = new Reference("java.lang.String"); + assertTrue(null == factory.getObjectInstance(ref, null, null, null)); + ref = new Reference(JdbcDataSource.class.getName()); + ref.add(new StringRefAddr("url", "jdbc:h2:mem:")); + ref.add(new StringRefAddr("user", "u")); + ref.add(new StringRefAddr("password", "p")); + ref.add(new StringRefAddr("loginTimeout", "1")); + ref.add(new StringRefAddr("description", "test")); + JdbcDataSource ds = (JdbcDataSource) factory.getObjectInstance( + ref, null, null, null); + assertEquals(1, ds.getLoginTimeout()); + assertEquals("test", ds.getDescription()); + assertEquals("jdbc:h2:mem:", ds.getURL()); + assertEquals("u", ds.getUser()); + assertEquals("p", ds.getPassword()); + Reference ref2 = ds.getReference(); + assertEquals(ref.size(), ref2.size()); + assertEquals(ref.get("url").getContent().toString(), + ref2.get("url").getContent().toString()); + assertEquals(ref.get("user").getContent().toString(), + ref2.get("user").getContent().toString()); + assertEquals(ref.get("password").getContent().toString(), + ref2.get("password").getContent().toString()); + assertEquals(ref.get("loginTimeout").getContent().toString(), + ref2.get("loginTimeout").getContent().toString()); + assertEquals(ref.get("description").getContent().toString(), + ref2.get("description").getContent().toString()); + ds.setPasswordChars("abc".toCharArray()); + assertEquals("abc", ds.getPassword()); + } + + private void testXAConnection() throws Exception { + testXAConnection(false); + testXAConnection(true); + } + + private void testXAConnection(boolean userInDataSource) throws Exception { + deleteDb("dataSource"); + JdbcDataSource ds = new JdbcDataSource(); + String url = getURL("dataSource", true); + String user = getUser(); + ds.setURL(url); + if (userInDataSource) { + ds.setUser(user); + ds.setPassword(getPassword()); + } + if (userInDataSource) { + assertEquals("ds" + ds.getTraceId() + ": url=" + url + + " user=" + user, ds.toString()); + } else { + assertEquals("ds" + ds.getTraceId() + ": url=" + url + + " user=", ds.toString()); + } + XAConnection xaConn; + if (userInDataSource) { + xaConn = ds.getXAConnection(); + } else { + xaConn = ds.getXAConnection(user, getPassword()); + } + + int traceId = ((JdbcXAConnection) xaConn).getTraceId(); + assertTrue(xaConn.toString().startsWith("xads" + traceId + ": conn")); + + xaConn.addConnectionEventListener(new ConnectionEventListener() { + @Override + public void connectionClosed(ConnectionEvent event) { + // nothing to do + } + + @Override + public void connectionErrorOccurred(ConnectionEvent event) { + // nothing to do + } + }); + XAResource res = xaConn.getXAResource(); + + assertFalse(res.setTransactionTimeout(1)); + assertEquals(0, res.getTransactionTimeout()); + assertTrue(res.isSameRM(res)); + assertFalse(res.isSameRM(null)); + + Connection conn = xaConn.getConnection(); + assertEquals(user.toUpperCase(), conn.getMetaData().getUserName()); + Xid[] list = res.recover(XAResource.TMSTARTRSCAN); + assertEquals(0, list.length); + Statement stat = conn.createStatement(); + stat.execute("SELECT * FROM DUAL"); + conn.close(); + xaConn.close(); + } + + private void testDataSource() throws SQLException { + deleteDb("dataSource"); + JdbcDataSource ds = new JdbcDataSource(); + PrintWriter p = new PrintWriter(new StringWriter()); + ds.setLogWriter(p); + assertTrue(p == ds.getLogWriter()); + ds.setURL(getURL("dataSource", true)); + ds.setUser(getUser()); + ds.setPassword(getPassword()); + Connection conn; + conn = ds.getConnection(); + Statement stat; + stat = conn.createStatement(); + stat.execute("SELECT * FROM DUAL"); + conn.close(); + conn = ds.getConnection(getUser(), getPassword()); + stat = conn.createStatement(); + stat.execute("SELECT * FROM DUAL"); + conn.close(); + } + + private void testUnwrap() throws SQLException { + JdbcDataSource ds = new JdbcDataSource(); + assertTrue(ds.isWrapperFor(Object.class)); + assertTrue(ds.isWrapperFor(DataSource.class)); + assertTrue(ds.isWrapperFor(JdbcDataSource.class)); + assertFalse(ds.isWrapperFor(String.class)); + assertTrue(ds == ds.unwrap(Object.class)); + assertTrue(ds == ds.unwrap(DataSource.class)); + assertThrows(ErrorCode.INVALID_VALUE_2, () -> ds.unwrap(String.class)); + } + +} diff --git a/h2/src/test/org/h2/test/jdbcx/TestXA.java b/h2/src/test/org/h2/test/jdbcx/TestXA.java new file mode 100644 index 0000000..2914518 --- /dev/null +++ b/h2/src/test/org/h2/test/jdbcx/TestXA.java @@ -0,0 +1,424 @@ +/* + * 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: James Devenish + */ +package org.h2.test.jdbcx; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Random; +import javax.sql.XAConnection; +import javax.sql.XADataSource; +import javax.transaction.xa.XAResource; +import javax.transaction.xa.Xid; +import org.h2.jdbcx.JdbcDataSource; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.JdbcUtils; + +/** + * Basic XA tests. + */ +public class TestXA extends TestDb { + + private static final String DB_NAME1 = "xadb1"; + private static final String DB_NAME2 = "xadb2"; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testRollbackWithoutPrepare(); + testRollbackAfterPrepare(); + testXAAutoCommit(); + deleteDb("xa"); + testMixedXaNormal(); + testXA(true); + deleteDb(DB_NAME1); + deleteDb(DB_NAME2); + testXA(false); + deleteDb("xa"); + deleteDb(DB_NAME1); + deleteDb(DB_NAME2); + } + + private void testRollbackWithoutPrepare() throws Exception { + if (config.memory) { + return; + } + Xid xid = new Xid() { + @Override + public int getFormatId() { + return 3145; + } + @Override + public byte[] getGlobalTransactionId() { + return new byte[] { 1, 2, 3, 4, 5, 6, 6, 7, 8 }; + } + @Override + public byte[] getBranchQualifier() { + return new byte[] { 34, 43, 33, 3, 3, 3, 33, 33, 3 }; + } + }; + deleteDb("xa"); + JdbcDataSource ds = new JdbcDataSource(); + ds.setURL(getURL("xa", true)); + ds.setPassword(getPassword()); + Connection dm = ds.getConnection(); + Statement stat = dm.createStatement(); + stat.execute("CREATE TABLE IF NOT EXISTS TEST(ID INT PRIMARY KEY, VAL INT)"); + stat.execute("INSERT INTO TEST(ID,VAL) VALUES (1,1)"); + dm.close(); + XAConnection c = ds.getXAConnection(); + XAResource xa = c.getXAResource(); + Connection connection = c.getConnection(); + xa.start(xid, XAResource.TMJOIN); + PreparedStatement ps = connection.prepareStatement( + "UPDATE TEST SET VAL=? WHERE ID=?"); + ps.setInt(1, new Random().nextInt()); + ps.setInt(2, 1); + ps.close(); + xa.rollback(xid); + connection.close(); + c.close(); + deleteDb("xa"); + } + + private void testRollbackAfterPrepare() throws Exception { + if (config.memory) { + return; + } + Xid xid = new Xid() { + @Override + public int getFormatId() { + return 3145; + } + @Override + public byte[] getGlobalTransactionId() { + return new byte[] { 1, 2, 3, 4, 5, 6, 6, 7, 8 }; + } + @Override + public byte[] getBranchQualifier() { + return new byte[] { 34, 43, 33, 3, 3, 3, 33, 33, 3 }; + } + }; + deleteDb("xa"); + JdbcDataSource ds = new JdbcDataSource(); + ds.setURL(getURL("xa", true)); + ds.setPassword(getPassword()); + Connection dm = ds.getConnection(); + Statement stat = dm.createStatement(); + stat.execute("CREATE TABLE IF NOT EXISTS TEST(ID INT PRIMARY KEY, VAL INT)"); + stat.execute("INSERT INTO TEST(ID,VAL) VALUES (1,1)"); + dm.close(); + XAConnection c = ds.getXAConnection(); + XAResource xa = c.getXAResource(); + Connection connection = c.getConnection(); + xa.start(xid, XAResource.TMJOIN); + PreparedStatement ps = connection.prepareStatement("UPDATE TEST SET VAL=? WHERE ID=?"); + ps.setInt(1, new Random().nextInt()); + ps.setInt(2, 1); + ps.close(); + xa.prepare(xid); + xa.rollback(xid); + connection.close(); + c.close(); + deleteDb("xa"); + } + + + private void testMixedXaNormal() throws Exception { + JdbcDataSource ds = new JdbcDataSource(); + ds.setURL("jdbc:h2:mem:test"); + ds.setUser("sa"); + ds.setPassword(""); + XAConnection xa = ds.getXAConnection(); + Connection c = xa.getConnection(); + assertTrue(c.getAutoCommit()); + MyXid xid = new MyXid(); + XAResource res = xa.getXAResource(); + + res.start(xid, XAResource.TMNOFLAGS); + assertFalse(c.getAutoCommit()); + res.end(xid, XAResource.TMSUCCESS); + res.commit(xid, true); + assertTrue(c.getAutoCommit()); + + res.start(xid, XAResource.TMNOFLAGS); + assertFalse(c.getAutoCommit()); + res.end(xid, XAResource.TMFAIL); + res.rollback(xid); + assertTrue(c.getAutoCommit()); + + c.close(); + xa.close(); + } + + /** + * A simple Xid implementation. + */ + public static class MyXid implements Xid { + private final byte[] branchQualifier = { 0 }; + private final byte[] globalTransactionId = { 0 }; + @Override + public byte[] getBranchQualifier() { + return branchQualifier; + } + @Override + public int getFormatId() { + return 0; + } + @Override + public byte[] getGlobalTransactionId() { + return globalTransactionId; + } + } + + private void testXAAutoCommit() throws Exception { + JdbcDataSource ds = new JdbcDataSource(); + ds.setURL("jdbc:h2:mem:test"); + ds.setUser("sa"); + ds.setPassword(""); + XAConnection xa = ds.getXAConnection(); + MyXid xid = new MyXid(); + xa.getXAResource().start(xid, + XAResource.TMNOFLAGS); + Connection c = xa.getConnection(); + assertFalse(c.getAutoCommit()); + c.close(); + xa.close(); + } + + private void testXA(boolean useOneDatabase) throws SQLException { + String url1 = getURL(DB_NAME1, true); + String url2 = getURL(DB_NAME2, true); + + XAConnection xaConn1 = null; + XAConnection xaConn2 = null; + Connection conn1 = null; + Connection conn2 = null; + Statement stat1 = null; + Statement stat2 = null; + try { + trace("xads1 = createXADatasource1()"); + XADataSource xaDs1 = createXADatasource(useOneDatabase, url1); + trace("xads2 = createXADatasource2()"); + XADataSource xaDs2 = createXADatasource(useOneDatabase, url2); + + trace("xacon1 = xads1.getXAConnection()"); + xaConn1 = xaDs1.getXAConnection(); + trace("xacon2 = xads2.getXAConnection()"); + xaConn2 = xaDs2.getXAConnection(); + + trace("xares1 = xacon1.getXAResource()"); + XAResource xares1 = xaConn1.getXAResource(); + trace("xares2 = xacon2.getXAResource()"); + XAResource xares2 = xaConn2.getXAResource(); + + trace("xares1.recover(XAResource.TMSTARTRSCAN)"); + Xid[] xids1 = xares1.recover(XAResource.TMSTARTRSCAN); + if ((xids1 == null) || (xids1.length == 0)) { + trace("xares1.recover(XAResource.TMSTARTRSCAN): 0"); + } else { + trace("xares1.recover(XAResource.TMSTARTRSCAN): " + xids1.length); + } + + trace("xares2.recover(XAResource.TMSTARTRSCAN)"); + Xid[] xids2 = xares2.recover(XAResource.TMSTARTRSCAN); + if ((xids2 == null) || (xids2.length == 0)) { + trace("xares2.recover(XAResource.TMSTARTRSCAN): 0"); + } else { + trace("xares2.recover(XAResource.TMSTARTRSCAN): " + xids2.length); + } + + trace("con1 = xacon1.getConnection()"); + conn1 = xaConn1.getConnection(); + trace("stmt1 = con1.createStatement()"); + stat1 = conn1.createStatement(); + + trace("con2 = xacon2.getConnection()"); + conn2 = xaConn2.getConnection(); + trace("stmt2 = con2.createStatement()"); + stat2 = conn2.createStatement(); + + if (useOneDatabase) { + trace("stmt1.executeUpdate(\"DROP TABLE xatest1\")"); + try { + stat1.executeUpdate("DROP TABLE xatest1"); + } catch (SQLException e) { + // ignore + } + trace("stmt2.executeUpdate(\"DROP TABLE xatest2\")"); + try { + stat2.executeUpdate("DROP TABLE xatest2"); + } catch (SQLException e) { + // ignore + } + } else { + trace("stmt1.executeUpdate(\"DROP TABLE xatest\")"); + try { + stat1.executeUpdate("DROP TABLE xatest"); + } catch (SQLException e) { + // ignore + } + trace("stmt2.executeUpdate(\"DROP TABLE xatest\")"); + try { + stat2.executeUpdate("DROP TABLE xatest"); + } catch (SQLException e) { + // ignore + } + } + + if (useOneDatabase) { + trace("stmt1.executeUpdate(\"CREATE TABLE xatest1 " + + "(id INT PRIMARY KEY, value INT)\")"); + stat1.executeUpdate("CREATE TABLE xatest1 " + + "(id INT PRIMARY KEY, v INT)"); + trace("stmt2.executeUpdate(\"CREATE TABLE xatest2 " + + "(id INT PRIMARY KEY, v INT)\")"); + stat2.executeUpdate("CREATE TABLE xatest2 " + + "(id INT PRIMARY KEY, v INT)"); + } else { + trace("stmt1.executeUpdate(\"CREATE TABLE xatest " + + "(id INT PRIMARY KEY, value INT)\")"); + stat1.executeUpdate("CREATE TABLE xatest " + + "(id INT PRIMARY KEY, v INT)"); + trace("stmt2.executeUpdate(\"CREATE TABLE xatest " + + "(id INT PRIMARY KEY, v INT)\")"); + stat2.executeUpdate("CREATE TABLE xatest " + + "(id INT PRIMARY KEY, v INT)"); + } + + if (useOneDatabase) { + trace("stmt1.executeUpdate(\"INSERT INTO xatest1 " + + "VALUES (1, 0)\")"); + stat1.executeUpdate("INSERT INTO xatest1 VALUES (1, 0)"); + trace("stmt2.executeUpdate(\"INSERT INTO xatest2 " + + "VALUES (2, 0)\")"); + stat2.executeUpdate("INSERT INTO xatest2 " + + "VALUES (2, 0)"); + } else { + trace("stmt1.executeUpdate(\"INSERT INTO xatest " + + "VALUES (1, 0)\")"); + stat1.executeUpdate("INSERT INTO xatest " + + "VALUES (1, 0)"); + trace("stmt2.executeUpdate(\"INSERT INTO xatest " + + "VALUES (2, 0)\")"); + stat2.executeUpdate("INSERT INTO xatest " + + "VALUES (2, 0)"); + } + + Xid xid1 = null; + Xid xid2 = null; + + if (useOneDatabase) { + xid1 = SimpleXid.createRandom(); + xid2 = SimpleXid.createRandom(); + } else { + xid1 = SimpleXid.createRandom(); + xid2 = xid1; + } + + if (useOneDatabase) { + trace("xares1.start(xid1, XAResource.TMNOFLAGS)"); + xares1.start(xid1, XAResource.TMNOFLAGS); + trace("xares2.start(xid2, XAResource.TMJOIN)"); + xares2.start(xid2, XAResource.TMJOIN); + } else { + trace("xares1.start(xid1, XAResource.TMNOFLAGS)"); + xares1.start(xid1, XAResource.TMNOFLAGS); + trace("xares2.start(xid2, XAResource.TMNOFLAGS)"); + xares2.start(xid2, XAResource.TMNOFLAGS); + } + + if (useOneDatabase) { + trace("stmt1.executeUpdate(\"UPDATE xatest1 " + + "SET v=1 WHERE id=1\")"); + stat1.executeUpdate("UPDATE xatest1 " + + "SET v=1 WHERE id=1"); + trace("stmt2.executeUpdate(\"UPDATE xatest2 " + + "SET v=1 WHERE id=2\")"); + stat2.executeUpdate("UPDATE xatest2 " + + "SET v=1 WHERE id=2"); + } else { + trace("stmt1.executeUpdate(\"UPDATE xatest " + + "SET v=1 WHERE id=1\")"); + stat1.executeUpdate("UPDATE xatest " + + "SET v=1 WHERE id=1"); + trace("stmt2.executeUpdate(\"UPDATE xatest " + + "SET v=1 WHERE id=2\")"); + stat2.executeUpdate("UPDATE xatest " + + "SET v=1 WHERE id=2"); + } + + trace("xares1.end(xid1, XAResource.TMSUCCESS)"); + xares1.end(xid1, XAResource.TMSUCCESS); + trace("xares2.end(xid2, XAResource.TMSUCCESS)"); + xares2.end(xid2, XAResource.TMSUCCESS); + + int ret1; + int ret2; + + trace("ret1 = xares1.prepare(xid1)"); + ret1 = xares1.prepare(xid1); + trace("xares1.prepare(xid1): " + ret1); + trace("ret2 = xares2.prepare(xid2)"); + ret2 = xares2.prepare(xid2); + trace("xares2.prepare(xid2): " + ret2); + + if ((ret1 != XAResource.XA_OK) && (ret1 != XAResource.XA_RDONLY)) { + throw new IllegalStateException( + "xares1.prepare(xid1) must return XA_OK or XA_RDONLY"); + } + if ((ret2 != XAResource.XA_OK) && (ret2 != XAResource.XA_RDONLY)) { + throw new IllegalStateException( + "xares2.prepare(xid2) must return XA_OK or XA_RDONLY"); + } + + if (ret1 == XAResource.XA_OK) { + trace("xares1.commit(xid1, false)"); + xares1.commit(xid1, false); + } + if (ret2 == XAResource.XA_OK) { + trace("xares2.commit(xid2, false)"); + xares2.commit(xid2, false); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + JdbcUtils.closeSilently(stat1); + JdbcUtils.closeSilently(stat2); + JdbcUtils.closeSilently(conn1); + JdbcUtils.closeSilently(conn2); + if (xaConn1 != null) { + xaConn1.close(); + } + if (xaConn2 != null) { + xaConn2.close(); + } + } + } + + private XADataSource createXADatasource(boolean useOneDatabase, String url) { + JdbcDataSource ds = new JdbcDataSource(); + ds.setPassword(getPassword("")); + ds.setUser("sa"); + if (useOneDatabase) { + ds.setURL(getURL("xa", true)); + } else { + ds.setURL(url); + } + return ds; + } + +} diff --git a/h2/src/test/org/h2/test/jdbcx/TestXASimple.java b/h2/src/test/org/h2/test/jdbcx/TestXASimple.java new file mode 100644 index 0000000..16f68cd --- /dev/null +++ b/h2/src/test/org/h2/test/jdbcx/TestXASimple.java @@ -0,0 +1,161 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.jdbcx; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import javax.sql.XAConnection; +import javax.transaction.xa.XAResource; +import javax.transaction.xa.Xid; +import org.h2.jdbcx.JdbcDataSource; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.JdbcUtils; + +/** + * A simple XA test. + */ +public class TestXASimple extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testTwoPhase(); + testSimple(); + } + + private void testTwoPhase() throws Exception { + if (config.memory || config.networked) { + return; + } + // testTwoPhase(false, true); + // testTwoPhase(false, false); + testTwoPhase("xaSimple2a", true, true); + testTwoPhase("xaSimple2b", true, false); + + } + + private void testTwoPhase(String db, boolean shutdown, boolean commit) + throws Exception { + deleteDb(db); + JdbcDataSource ds = new JdbcDataSource(); + ds.setPassword(getPassword()); + ds.setUser("sa"); + // ds.setURL(getURL("xaSimple", true) + ";trace_level_system_out=3"); + ds.setURL(getURL(db, true)); + + XAConnection xa; + xa = ds.getXAConnection(); + Connection conn; + + conn = xa.getConnection(); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name varchar(255))"); + Xid xid = SimpleXid.createRandom(); + xa.getXAResource().start(xid, XAResource.TMNOFLAGS); + conn.setAutoCommit(false); + stat.execute("insert into test values(1, 'Hello')"); + xa.getXAResource().end(xid, XAResource.TMSUCCESS); + xa.getXAResource().prepare(xid); + if (shutdown) { + shutdown(ds); + } + + xa = ds.getXAConnection(); + Xid[] list = xa.getXAResource().recover(XAResource.TMSTARTRSCAN); + assertEquals(1, list.length); + assertTrue(xid.equals(list[0])); + if (commit) { + xa.getXAResource().commit(list[0], false); + } else { + xa.getXAResource().rollback(list[0]); + } + conn = xa.getConnection(); + conn.createStatement().executeQuery("select * from test"); + if (shutdown) { + shutdown(ds); + } + + xa = ds.getXAConnection(); + list = xa.getXAResource().recover(XAResource.TMSTARTRSCAN); + assertEquals(0, list.length); + conn = xa.getConnection(); + ResultSet rs; + rs = conn.createStatement().executeQuery("select * from test"); + if (commit) { + assertTrue(rs.next()); + } else { + assertFalse(rs.next()); + } + xa.close(); + } + + private static void shutdown(JdbcDataSource ds) throws SQLException { + Connection conn = ds.getConnection(); + conn.createStatement().execute("shutdown immediately"); + JdbcUtils.closeSilently(conn); + } + + private void testSimple() throws SQLException { + + deleteDb("xaSimple1"); + deleteDb("xaSimple2"); + org.h2.Driver.load(); + + // InitialContext context = new InitialContext(); + // context.rebind(USER_TRANSACTION_JNDI_NAME, j.getUserTransaction()); + + JdbcDataSource ds1 = new JdbcDataSource(); + ds1.setPassword(getPassword()); + ds1.setUser("sa"); + ds1.setURL(getURL("xaSimple1", true)); + + JdbcDataSource ds2 = new JdbcDataSource(); + ds2.setPassword(getPassword()); + ds2.setUser("sa"); + ds2.setURL(getURL("xaSimple2", true)); + + // UserTransaction ut = (UserTransaction) + // context.lookup("UserTransaction"); + // ut.begin(); + + XAConnection xa1 = ds1.getXAConnection(); + Connection c1 = xa1.getConnection(); + c1.setAutoCommit(false); + XAConnection xa2 = ds2.getXAConnection(); + Connection c2 = xa2.getConnection(); + c2.setAutoCommit(false); + + c1.createStatement().executeUpdate( + "create table test(id int, test varchar(255))"); + c2.createStatement().executeUpdate( + "create table test(id int, test varchar(255))"); + + // ut.rollback(); + c1.close(); + c2.close(); + + xa1.close(); + xa2.close(); + + // j.stop(); + // System.exit(0); + deleteDb("xaSimple1"); + deleteDb("xaSimple2"); + + } + +} diff --git a/h2/src/test/org/h2/test/jdbcx/package.html b/h2/src/test/org/h2/test/jdbcx/package.html new file mode 100644 index 0000000..41fa535 --- /dev/null +++ b/h2/src/test/org/h2/test/jdbcx/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +Tests related to distributed transactions. + +

    \ No newline at end of file diff --git a/h2/src/test/org/h2/test/mvcc/TestMvcc1.java b/h2/src/test/org/h2/test/mvcc/TestMvcc1.java new file mode 100644 index 0000000..954d27d --- /dev/null +++ b/h2/src/test/org/h2/test/mvcc/TestMvcc1.java @@ -0,0 +1,381 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.mvcc; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Random; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Basic MVCC (multi version concurrency) test cases. + */ +public class TestMvcc1 extends TestDb { + + private Connection c1, c2; + private Statement s1, s2; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); + test.testFromMain(); + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public void test() throws SQLException { + testCases(); + deleteDb("mvcc1"); + } + + private void testCases() throws SQLException { + ResultSet rs; + + // TODO Prio 1: document: exclusive table lock still used when altering + // tables, adding indexes, select ... for update; table level locks are + // checked + // TODO Prio 2: if MVCC is used, rows of transactions need to fit in + // memory + // TODO Prio 2: getFirst / getLast in MultiVersionIndex + // TODO Prio 2: snapshot isolation (currently read-committed, not + // repeatable read) + + // TODO test: one thread appends, the other + // selects new data (select * from test where id > ?) and deletes + + deleteDb("mvcc1"); + c1 = getConnection("mvcc1;LOCK_TIMEOUT=10"); + s1 = c1.createStatement(); + c2 = getConnection("mvcc1;LOCK_TIMEOUT=10"); + s2 = c2.createStatement(); + c1.setAutoCommit(false); + c2.setAutoCommit(false); + + // table rollback problem + assertThrows(ErrorCode.COLUMN_NOT_FOUND_1, s1). + execute("create table b(primary key(x))"); + s1.execute("create table a(id int as 1 unique)"); + s1.execute("drop table a"); + + // update same key problem + s1.execute("CREATE TABLE TEST(ID INT, NAME VARCHAR, PRIMARY KEY(ID))"); + s1.execute("INSERT INTO TEST VALUES(1, 'Hello')"); + c1.commit(); + assertResult("Hello", s2, "SELECT NAME FROM TEST WHERE ID=1"); + s1.execute("UPDATE TEST SET NAME = 'Hallo' WHERE ID=1"); + assertResult("Hello", s2, "SELECT NAME FROM TEST WHERE ID=1"); + assertResult("Hallo", s1, "SELECT NAME FROM TEST WHERE ID=1"); + s1.execute("DROP TABLE TEST"); + c1.commit(); + c2.commit(); + + // referential integrity problem + s1.execute("create table a (id integer generated by default as identity, " + + "code varchar(10) not null, primary key(id))"); + s1.execute("create table b (name varchar(100) not null, a integer, " + + "primary key(name), foreign key(a) references a(id))"); + s1.execute("insert into a(code) values('one')"); + assertThrows(ErrorCode.REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1, s2). + execute("insert into b values('un B', 1)"); + c2.commit(); + c1.rollback(); + s1.execute("drop table a, b"); + c2.commit(); + + // it should not be possible to drop a table + // when an uncommitted transaction changed something + s1.execute("create table test(id int primary key)"); + s1.execute("insert into test values(1)"); + assertThrows(ErrorCode.LOCK_TIMEOUT_1, s2). + execute("drop table test"); + c1.rollback(); + s2.execute("drop table test"); + c2.rollback(); + + // table scan problem + s1.execute("create table test(id int, name varchar)"); + s1.execute("insert into test values(1, 'A'), (2, 'B')"); + c1.commit(); + assertResult("2", s1, "select count(*) from test where name<>'C'"); + s2.execute("update test set name='B2' where id=2"); + assertResult("2", s1, "select count(*) from test where name<>'C'"); + c2.commit(); + s2.execute("drop table test"); + c2.rollback(); + + // select for update should do an exclusive lock, even with mvcc + s1.execute("create table test(id int primary key, name varchar(255))"); + s1.execute("insert into test values(1, 'y')"); + c1.commit(); + s2.execute("select * from test where id = 1 for update"); + assertThrows(ErrorCode.LOCK_TIMEOUT_1, s1). + execute("delete from test"); + c2.rollback(); + s1.execute("drop table test"); + c1.commit(); + c2.commit(); + + s1.execute("create table test(id int primary key, name varchar(255))"); + s2.execute("insert into test values(4, 'Hello')"); + c2.rollback(); + assertResult("0", s1, "select count(*) from test where name = 'Hello'"); + assertResult("0", s2, "select count(*) from test where name = 'Hello'"); + c1.commit(); + c2.commit(); + s1.execute("DROP TABLE TEST"); + + s1.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + s1.execute("INSERT INTO TEST VALUES(1, 'Test')"); + c1.commit(); + assertResult("1", s1, "select max(id) from test"); + s1.execute("INSERT INTO TEST VALUES(2, 'World')"); + c1.rollback(); + assertResult("1", s1, "select max(id) from test"); + c1.commit(); + c2.commit(); + s1.execute("DROP TABLE TEST"); + + + s1.execute("create table test as select * from table(id int=(1, 2))"); + s1.execute("update test set id=1 where id=1"); + s1.execute("select max(id) from test"); + assertResult("2", s1, "select max(id) from test"); + c1.commit(); + c2.commit(); + s1.execute("DROP TABLE TEST"); + + s1.execute("CREATE TABLE TEST(ID INT)"); + s1.execute("INSERT INTO TEST VALUES(1)"); + c1.commit(); + assertResult("1", s2, "SELECT COUNT(*) FROM TEST"); + s1.executeUpdate("DELETE FROM TEST"); + PreparedStatement p2 = c2.prepareStatement("select count(*) from test"); + rs = p2.executeQuery(); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertResult("1", s2, "SELECT COUNT(*) FROM TEST"); + assertResult("0", s1, "SELECT COUNT(*) FROM TEST"); + c1.commit(); + assertResult("0", s2, "SELECT COUNT(*) FROM TEST"); + rs = p2.executeQuery(); + rs.next(); + assertEquals(0, rs.getInt(1)); + c1.commit(); + c2.commit(); + s1.execute("DROP TABLE TEST"); + + s1.execute("CREATE TABLE TEST(ID INT)"); + s1.execute("INSERT INTO TEST VALUES(1)"); + c1.commit(); + s1.execute("DELETE FROM TEST"); + assertResult("0", s1, "SELECT COUNT(*) FROM TEST"); + c1.commit(); + assertResult("0", s1, "SELECT COUNT(*) FROM TEST"); + s1.execute("INSERT INTO TEST VALUES(1)"); + s1.execute("DELETE FROM TEST"); + c1.commit(); + assertResult("0", s1, "SELECT COUNT(*) FROM TEST"); + s1.execute("DROP TABLE TEST"); + + s1.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + s1.execute("INSERT INTO TEST VALUES(1, 'Hello'), (2, 'World')"); + assertResult("0", s2, "SELECT COUNT(*) FROM TEST"); + c1.commit(); + assertResult("2", s2, "SELECT COUNT(*) FROM TEST"); + s1.execute("INSERT INTO TEST VALUES(3, '!')"); + c1.rollback(); + assertResult("2", s2, "SELECT COUNT(*) FROM TEST"); + s1.execute("DROP TABLE TEST"); + c1.commit(); + + s1.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + s1.execute("INSERT INTO TEST VALUES(1, 'Hello')"); + s1.execute("DELETE FROM TEST"); + assertResult("0", s2, "SELECT COUNT(*) FROM TEST"); + c1.commit(); + assertResult("0", s2, "SELECT COUNT(*) FROM TEST"); + s1.execute("DROP TABLE TEST"); + c1.commit(); + + s1.execute("CREATE TABLE TEST(ID INT GENERATED BY DEFAULT AS IDENTITY, NAME VARCHAR)"); + s1.execute("INSERT INTO TEST(NAME) VALUES('Ruebezahl')"); + assertResult("0", s2, "SELECT COUNT(*) FROM TEST"); + assertResult("1", s1, "SELECT COUNT(*) FROM TEST"); + s1.execute("DROP TABLE TEST"); + c1.commit(); + + s1.execute("CREATE TABLE TEST(ID INT GENERATED BY DEFAULT AS IDENTITY, NAME VARCHAR)"); + s1.execute("INSERT INTO TEST(NAME) VALUES('Ruebezahl')"); + s1.execute("INSERT INTO TEST(NAME) VALUES('Ruebezahl')"); + s1.execute("DROP TABLE TEST"); + c1.commit(); + + s1.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + s1.execute("INSERT INTO TEST VALUES(1, 'Hello')"); + c1.commit(); + s1.execute("DELETE FROM TEST WHERE ID=1"); + c1.rollback(); + s1.execute("DROP TABLE TEST"); + c1.commit(); + + Random random = new Random(1); + s1.execute("CREATE TABLE TEST(ID INT GENERATED BY DEFAULT AS IDENTITY, NAME VARCHAR)"); + Statement s; + Connection c; + for (int i = 0; i < 1000; i++) { + if (random.nextBoolean()) { + s = s1; + c = c1; + } else { + s = s2; + c = c2; + } + switch (random.nextInt(5)) { + case 0: + s.execute("INSERT INTO TEST(NAME) VALUES('Hello')"); + break; + case 1: + s.execute("UPDATE TEST SET NAME=" + i + " WHERE ID=" + random.nextInt(i)); + break; + case 2: + s.execute("DELETE FROM TEST WHERE ID=" + random.nextInt(i)); + break; + case 3: + c.commit(); + break; + case 4: + c.rollback(); + break; + default: + } + s1.execute("SELECT * FROM TEST ORDER BY ID"); + s2.execute("SELECT * FROM TEST ORDER BY ID"); + } + c2.rollback(); + s1.execute("DROP TABLE TEST"); + c1.commit(); + c2.commit(); + + random = new Random(1); + s1.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + for (int i = 0; i < 1000; i++) { + if (random.nextBoolean()) { + s = s1; + c = c1; + } else { + s = s2; + c = c2; + } + switch (random.nextInt(5)) { + case 0: + s.execute("INSERT INTO TEST VALUES(" + i + ", 'Hello')"); + break; + case 1: + try { + s.execute("UPDATE TEST SET NAME=" + i + " WHERE ID=" + random.nextInt(i)); + } catch (SQLException e) { + assertEquals(ErrorCode.CONCURRENT_UPDATE_1, e.getErrorCode()); + } + break; + case 2: + s.execute("DELETE FROM TEST WHERE ID=" + random.nextInt(i)); + break; + case 3: + c.commit(); + break; + case 4: + c.rollback(); + break; + default: + } + s1.execute("SELECT * FROM TEST ORDER BY ID"); + s2.execute("SELECT * FROM TEST ORDER BY ID"); + } + c2.rollback(); + s1.execute("DROP TABLE TEST"); + c1.commit(); + c2.commit(); + + s1.execute("CREATE TABLE TEST(ID INT, NAME VARCHAR)"); + s1.execute("INSERT INTO TEST VALUES(1, 'Hello')"); + assertResult("0", s2, "SELECT COUNT(*) FROM TEST WHERE NAME!='X'"); + assertResult("1", s1, "SELECT COUNT(*) FROM TEST WHERE NAME!='X'"); + c1.commit(); + assertResult("1", s2, "SELECT COUNT(*) FROM TEST WHERE NAME!='X'"); + assertResult("1", s2, "SELECT COUNT(*) FROM TEST WHERE NAME!='X'"); + s1.execute("DROP TABLE TEST"); + c1.commit(); + c2.commit(); + + s1.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + s1.execute("INSERT INTO TEST VALUES(1, 'Hello')"); + assertResult("0", s2, "SELECT COUNT(*) FROM TEST WHERE ID<100"); + assertResult("1", s1, "SELECT COUNT(*) FROM TEST WHERE ID<100"); + c1.commit(); + assertResult("1", s2, "SELECT COUNT(*) FROM TEST WHERE ID<100"); + assertResult("1", s2, "SELECT COUNT(*) FROM TEST WHERE ID<100"); + s1.execute("DROP TABLE TEST"); + c1.commit(); + c2.commit(); + + s1.execute("CREATE TABLE TEST(ID INT, NAME VARCHAR, PRIMARY KEY(ID, NAME))"); + s1.execute("INSERT INTO TEST VALUES(1, 'Hello')"); + c1.commit(); + assertResult("Hello", s2, "SELECT NAME FROM TEST WHERE ID=1"); + s1.execute("UPDATE TEST SET NAME = 'Hallo' WHERE ID=1"); + assertResult("Hello", s2, "SELECT NAME FROM TEST WHERE ID=1"); + assertResult("Hallo", s1, "SELECT NAME FROM TEST WHERE ID=1"); + s1.execute("DROP TABLE TEST"); + c1.commit(); + c2.commit(); + + + s1.execute("create table test(id int primary key, name varchar(255))"); + s1.execute("insert into test values(1, 'Hello'), (2, 'World')"); + c1.commit(); + assertThrows(ErrorCode.DUPLICATE_KEY_1, s1). + execute("update test set id=2 where id=1"); + rs = s1.executeQuery("select * from test order by id"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertEquals("World", rs.getString(2)); + assertFalse(rs.next()); + + rs = s2.executeQuery("select * from test order by id"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertEquals("World", rs.getString(2)); + assertFalse(rs.next()); + s1.execute("drop table test"); + c1.commit(); + c2.commit(); + + c1.close(); + c2.close(); + + } + +} diff --git a/h2/src/test/org/h2/test/mvcc/TestMvcc2.java b/h2/src/test/org/h2/test/mvcc/TestMvcc2.java new file mode 100644 index 0000000..93ce063 --- /dev/null +++ b/h2/src/test/org/h2/test/mvcc/TestMvcc2.java @@ -0,0 +1,168 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.mvcc; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.Task; + +/** + * Additional MVCC (multi version concurrency) test cases. + */ +public class TestMvcc2 extends TestDb { + + private static final String DROP_TABLE = + "DROP TABLE IF EXISTS EMPLOYEE"; + private static final String CREATE_TABLE = + "CREATE TABLE EMPLOYEE (id BIGINT, version BIGINT, NAME VARCHAR(255))"; + private static final String INSERT = + "INSERT INTO EMPLOYEE (id, version, NAME) VALUES (1, 1, 'Jones')"; + private static final String UPDATE = + "UPDATE EMPLOYEE SET NAME = 'Miller' WHERE version = 1"; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); + test.testFromMain(); + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public void test() throws Exception { + deleteDb("mvcc2"); + testConcurrentInsert(); + testConcurrentUpdate(); + testSelectForUpdate(); + testInsertUpdateRollback(); + testInsertRollback(); + deleteDb("mvcc2"); + } + + private Connection getConnection() throws SQLException { + return getConnection("mvcc2"); + } + + private void testConcurrentInsert() throws Exception { + Connection conn = getConnection(); + final Connection conn2 = getConnection(); + Statement stat = conn.createStatement(); + final Statement stat2 = conn2.createStatement(); + stat2.execute("set lock_timeout 1000"); + stat.execute("create table test(id int primary key, name varchar)"); + conn.setAutoCommit(false); + Task t = new Task() { + @Override + public void call() { + try { + stat2.execute("insert into test values(0, 'Hallo')"); + fail(); + } catch (SQLException e) { + assertTrue(e.toString(), + e.getErrorCode() == ErrorCode.DUPLICATE_KEY_1 || + e.getErrorCode() == ErrorCode.CONCURRENT_UPDATE_1); + } + } + }; + stat.execute("insert into test values(0, 'Hello')"); + t.execute(); + conn.commit(); + t.get(); + ResultSet rs; + rs = stat.executeQuery("select name from test"); + assertTrue(rs.next()); + assertEquals("Hello", rs.getString(1)); + stat.execute("drop table test"); + conn2.close(); + conn.close(); + } + + private void testConcurrentUpdate() throws Exception { + Connection conn = getConnection(); + final Connection conn2 = getConnection(); + Statement stat = conn.createStatement(); + final Statement stat2 = conn2.createStatement(); + stat2.execute("set lock_timeout 1000"); + stat.execute("create table test(id int primary key, name varchar)"); + stat.execute("insert into test values(0, 'Hello')"); + conn.setAutoCommit(false); + Task t = new Task() { + @Override + public void call() throws SQLException { + stat2.execute("update test set name = 'Hallo'"); + assertEquals(1, stat2.getUpdateCount()); + } + }; + stat.execute("update test set name = 'Hi'"); + assertEquals(1, stat.getUpdateCount()); + t.execute(); + conn.commit(); + t.get(); + ResultSet rs; + rs = stat.executeQuery("select name from test"); + assertTrue(rs.next()); + assertEquals("Hallo", rs.getString(1)); + stat.execute("drop table test"); + conn2.close(); + conn.close(); + } + + private void testSelectForUpdate() throws SQLException { + Connection conn = getConnection("mvcc2"); + Connection conn2 = getConnection("mvcc2"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name varchar)"); + conn.setAutoCommit(false); + stat.execute("insert into test select x, 'Hello' from system_range(1, 10)"); + stat.execute("select * from test where id = 3 for update"); + conn.commit(); + stat.execute("select * from test where id = 3 for update"); + conn2.setAutoCommit(false); + conn2.createStatement().execute("select * from test where id = 4 for update"); + assertThrows(ErrorCode.LOCK_TIMEOUT_1, conn2.createStatement()). + execute("select * from test where id = 3 for update"); + conn.close(); + conn2.close(); + } + + private void testInsertUpdateRollback() throws SQLException { + Connection conn = getConnection(); + conn.setAutoCommit(false); + Statement stmt = conn.createStatement(); + stmt.execute(DROP_TABLE); + stmt.execute(CREATE_TABLE); + conn.commit(); + stmt.execute(INSERT); + stmt.execute(UPDATE); + conn.rollback(); + conn.close(); + } + + private void testInsertRollback() throws SQLException { + Connection conn = getConnection(); + conn.setAutoCommit(false); + Statement stmt = conn.createStatement(); + stmt.execute(DROP_TABLE); + stmt.execute(CREATE_TABLE); + conn.commit(); + stmt.execute(INSERT); + conn.rollback(); + conn.close(); + } + +} diff --git a/h2/src/test/org/h2/test/mvcc/TestMvcc3.java b/h2/src/test/org/h2/test/mvcc/TestMvcc3.java new file mode 100644 index 0000000..ebf6bfa --- /dev/null +++ b/h2/src/test/org/h2/test/mvcc/TestMvcc3.java @@ -0,0 +1,221 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.mvcc; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Savepoint; +import java.sql.Statement; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Additional MVCC (multi version concurrency) test cases. + */ +public class TestMvcc3 extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); + test.testFromMain(); + } + + @Override + public void test() throws SQLException { + testFailedUpdate(); + testConcurrentUpdate(); + testInsertUpdateRollback(); + testCreateTableAsSelect(); + testDisableAutoCommit(); + testRollback(); + deleteDb("mvcc3"); + } + + private void testFailedUpdate() throws SQLException { + deleteDb("mvcc3"); + Connection conn = getConnection("mvcc3"); + conn.setAutoCommit(false); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, a int unique, b int)"); + stat.execute("insert into test values(1, 1, 1)"); + stat.execute("insert into test values(2, 2, 2)"); + assertThrows(ErrorCode.DUPLICATE_KEY_1, stat). + execute("update test set a = 1 where id = 2"); + ResultSet rs; + rs = stat.executeQuery("select * from test where id = 2"); + assertTrue(rs.next()); + rs = stat.executeQuery("select * from test where a = 2"); + assertTrue(rs.next()); + rs = stat.executeQuery("select * from test where b = 2"); + assertTrue(rs.next()); + conn.close(); + } + + private void testConcurrentUpdate() throws SQLException { + deleteDb("mvcc3"); + Connection c1 = getConnection("mvcc3"); + c1.setAutoCommit(false); + Statement s1 = c1.createStatement(); + Connection c2 = getConnection("mvcc3"); + c2.setAutoCommit(false); + Statement s2 = c2.createStatement(); + + s1.execute("create table test(id int primary key, name varchar) as " + + "select x, x from system_range(1, 2)"); + s1.execute("create unique index on test(name)"); + s1.executeUpdate("update test set name = 100 where id = 1"); + + assertThrows(SQLException.class, s2).executeUpdate("update test set name = 100 where id = 2"); + + ResultSet rs = s1.executeQuery("select * from test order by id"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals("100", rs.getString(2)); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertEquals("2", rs.getString(2)); + assertFalse(rs.next()); + rs = s2.executeQuery("select * from test order by id"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals("1", rs.getString(2)); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertEquals("2", rs.getString(2)); + assertFalse(rs.next()); + c1.close(); + c2.close(); + } + + private void testInsertUpdateRollback() throws SQLException { + deleteDb("mvcc3"); + Connection c1 = getConnection("mvcc3"); + Statement s1 = c1.createStatement(); + Connection c2 = getConnection("mvcc3"); + Statement s2 = c2.createStatement(); + + s1.execute("create table test(id int primary key, name varchar) " + + "as select 0, 'Hello'"); + c1.setAutoCommit(false); + s1.executeUpdate("update test set name = 'World'"); + printRows("after update", s1, s2); + Savepoint sp1 = c1.setSavepoint(); + s1.executeUpdate("delete from test"); + printRows("after delete", s1, s2); + c1.rollback(sp1); + printRows("after rollback delete", s1, s2); + c1.rollback(); + printRows("after rollback all", s1, s2); + + ResultSet rs = s2.executeQuery("select * from test"); + assertTrue(rs.next()); + assertFalse(rs.next()); + c1.close(); + c2.close(); + } + + private void printRows(String s, Statement s1, Statement s2) + throws SQLException { + trace(s); + ResultSet rs; + rs = s1.executeQuery("select * from test"); + while (rs.next()) { + trace("s1: " + rs.getString(2)); + } + rs = s2.executeQuery("select * from test"); + while (rs.next()) { + trace("s2: " + rs.getString(2)); + } + } + + private void testCreateTableAsSelect() throws SQLException { + deleteDb("mvcc3"); + Connection c1 = getConnection("mvcc3"); + Statement s1 = c1.createStatement(); + s1.execute("CREATE TABLE TEST AS SELECT X ID, 'Hello' NAME " + + "FROM SYSTEM_RANGE(1, 3)"); + Connection c2 = getConnection("mvcc3"); + Statement s2 = c2.createStatement(); + ResultSet rs = s2.executeQuery("SELECT NAME FROM TEST WHERE ID=1"); + rs.next(); + assertEquals("Hello", rs.getString(1)); + c1.close(); + c2.close(); + } + + private void testRollback() throws SQLException { + deleteDb("mvcc3"); + Connection conn = getConnection("mvcc3"); + Statement stat = conn.createStatement(); + stat.executeUpdate("DROP TABLE IF EXISTS TEST"); + stat.executeUpdate("CREATE TABLE TEST (ID NUMBER(2) PRIMARY KEY, " + + "VAL VARCHAR(10))"); + stat.executeUpdate("INSERT INTO TEST (ID, VAL) VALUES (1, 'Value')"); + stat.executeUpdate("INSERT INTO TEST (ID, VAL) VALUES (2, 'Value')"); + if (!config.memory) { + conn.close(); + conn = getConnection("mvcc3"); + } + conn.setAutoCommit(false); + conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); + + Connection conn2 = getConnection("mvcc3"); + conn2.setAutoCommit(false); + conn2.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); + + conn.createStatement().executeUpdate( + "UPDATE TEST SET VAL='Updated' WHERE ID = 1"); + conn.rollback(); + + ResultSet rs = conn2.createStatement().executeQuery( + "SELECT * FROM TEST"); + assertTrue(rs.next()); + assertEquals("Value", rs.getString(2)); + assertTrue(rs.next()); + assertEquals("Value", rs.getString(2)); + assertFalse(rs.next()); + + conn.createStatement().executeUpdate( + "UPDATE TEST SET VAL='Updated' WHERE ID = 1"); + conn.commit(); + rs = conn2.createStatement().executeQuery( + "SELECT * FROM TEST ORDER BY ID"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals("Updated", rs.getString(2)); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertEquals("Value", rs.getString(2)); + assertFalse(rs.next()); + + conn.close(); + conn2.close(); + } + + private void testDisableAutoCommit() throws SQLException { + deleteDb("mvcc3"); + Connection conn = getConnection("mvcc3"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY)"); + stat.execute("INSERT INTO TEST VALUES(0)"); + conn.setAutoCommit(false); + stat.execute("INSERT INTO TEST VALUES(1)"); + ResultSet rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID"); + rs.next(); + assertEquals(0, rs.getInt(1)); + rs.next(); + assertEquals(1, rs.getInt(1)); + conn.close(); + } + +} diff --git a/h2/src/test/org/h2/test/mvcc/TestMvcc4.java b/h2/src/test/org/h2/test/mvcc/TestMvcc4.java new file mode 100644 index 0000000..b99637a --- /dev/null +++ b/h2/src/test/org/h2/test/mvcc/TestMvcc4.java @@ -0,0 +1,138 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.mvcc; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Timestamp; +import java.util.concurrent.CountDownLatch; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Additional MVCC (multi version concurrency) test cases. + */ +public class TestMvcc4 extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); + test.config.lockTimeout = 20000; + test.config.memory = true; + test.testFromMain(); + } + + @Override + public boolean isEnabled() { + if (config.networked) { + return false; + } + return true; + } + + @Override + public void test() throws SQLException { + testSelectForUpdateAndUpdateConcurrency(); + } + + private void testSelectForUpdateAndUpdateConcurrency() throws SQLException { + deleteDb("mvcc4"); + Connection setup = getConnection("mvcc4"); + setup.setAutoCommit(false); + + { + Statement s = setup.createStatement(); + s.executeUpdate("CREATE TABLE test (" + + "entity_id VARCHAR(100) NOT NULL PRIMARY KEY, " + + "lastUpdated TIMESTAMP NOT NULL)"); + + PreparedStatement ps = setup.prepareStatement( + "INSERT INTO test (entity_id, lastUpdated) VALUES (?, ?)"); + for (int i = 0; i < 2; i++) { + String id = "" + i; + ps.setString(1, id); + ps.setTimestamp(2, new Timestamp(System.currentTimeMillis())); + ps.executeUpdate(); + } + setup.commit(); + } + + //Create a connection from thread 1 + Connection c1 = getConnection("mvcc4;LOCK_TIMEOUT=10000"); + c1.setAutoCommit(false); + + //Fire off a concurrent update. + final CountDownLatch executedUpdate = new CountDownLatch(1); + new Thread() { + @Override + public void run() { + try { + Connection c2 = getConnection("mvcc4"); + c2.setAutoCommit(false); + + PreparedStatement ps = c2.prepareStatement( + "SELECT * FROM test WHERE entity_id = ? FOR UPDATE"); + ps.setString(1, "1"); + ps.executeQuery().next(); + + executedUpdate.countDown(); + // interrogate new "blocker_id" metatable field instead of + // relying on stacktraces!? to determine when session is blocking + PreparedStatement stmt = c2.prepareStatement( + "SELECT * FROM INFORMATION_SCHEMA.SESSIONS WHERE BLOCKER_ID = SESSION_ID()"); + ResultSet resultSet; + do { + resultSet = stmt.executeQuery(); + } while(!resultSet.next()); + + c2.commit(); + c2.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + }.start(); + + //Wait until the concurrent update has executed, but not yet committed + try { + executedUpdate.await(); + } catch (InterruptedException e) { + // ignore + } + + // Execute an update. This should initially fail, and enter the waiting + // for lock case. + PreparedStatement ps = c1.prepareStatement("UPDATE test SET lastUpdated = ?"); + ps.setTimestamp(1, new Timestamp(System.currentTimeMillis())); + assertEquals(2, ps.executeUpdate()); + + c1.commit(); + c1.close(); + + Connection verify = getConnection("mvcc4"); + + verify.setAutoCommit(false); + ps = verify.prepareStatement("SELECT COUNT(*) FROM test"); + ResultSet rs = ps.executeQuery(); + assertTrue(rs.next()); + assertEquals(2,rs.getInt(1)); + verify.commit(); + verify.close(); + + setup.close(); + } +} + + + + diff --git a/h2/src/test/org/h2/test/mvcc/TestMvccMultiThreaded.java b/h2/src/test/org/h2/test/mvcc/TestMvccMultiThreaded.java new file mode 100644 index 0000000..26f3ab3 --- /dev/null +++ b/h2/src/test/org/h2/test/mvcc/TestMvccMultiThreaded.java @@ -0,0 +1,179 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.mvcc; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.concurrent.CyclicBarrier; +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.Task; + +/** + * Multi-threaded MVCC (multi version concurrency) test cases. + */ +public class TestMvccMultiThreaded extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public void test() throws Exception { + testConcurrentSelectForUpdate(); + testMergeWithUniqueKeyViolation(); + testConcurrentMerge(); + testConcurrentUpdate(); + } + + private void testConcurrentSelectForUpdate() throws Exception { + deleteDb(getTestName()); + Connection conn = getConnection(getTestName()); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int not null primary key, updated int not null)"); + stat.execute("insert into test(id, updated) values(1, 100)"); + ArrayList tasks = new ArrayList<>(); + int count = 3; + for (int i = 0; i < count; i++) { + Task task = new Task() { + @Override + public void call() throws Exception { + try (Connection conn = getConnection(getTestName())) { + Statement stat = conn.createStatement(); + while (!stop) { + stat.execute("select * from test where id=1 for update"); + } + } + } + }.execute(); + tasks.add(task); + } + for (int i = 0; i < 10; i++) { + Thread.sleep(100); + ResultSet rs = stat.executeQuery("select * from test"); + assertTrue(rs.next()); + } + for (Task t : tasks) { + t.get(); + } + conn.close(); + deleteDb(getTestName()); + } + + private void testMergeWithUniqueKeyViolation() throws Exception { + deleteDb(getTestName()); + Connection conn = getConnection(getTestName()); + Statement stat = conn.createStatement(); + stat.execute("create table test(x int primary key, y int unique)"); + stat.execute("insert into test values(1, 1)"); + assertThrows(ErrorCode.DUPLICATE_KEY_1, stat). + execute("merge into test values(2, 1)"); + stat.execute("merge into test values(1, 2)"); + conn.close(); + + } + + private void testConcurrentMerge() throws Exception { + deleteDb(getTestName()); + int len = 3; + final Connection[] connList = new Connection[len]; + for (int i = 0; i < len; i++) { + Connection conn = getConnection( + getTestName() + ";LOCK_TIMEOUT=500"); + connList[i] = conn; + } + Connection conn = connList[0]; + conn.createStatement().execute( + "create table test(id int primary key, name varchar)"); + Task[] tasks = new Task[len]; + for (int i = 0; i < len; i++) { + final Connection c = connList[i]; + c.setAutoCommit(false); + tasks[i] = new Task() { + @Override + public void call() throws Exception { + while (!stop) { + c.createStatement().execute( + "merge into test values(1, 'x')"); + c.commit(); + } + } + }; + tasks[i].execute(); + } + Thread.sleep(1000); + for (int i = 0; i < len; i++) { + tasks[i].get(); + } + for (int i = 0; i < len; i++) { + connList[i].close(); + } + deleteDb(getTestName()); + } + + private void testConcurrentUpdate() throws Exception { + deleteDb(getTestName()); + int len = 2; + final Connection[] connList = new Connection[len]; + for (int i = 0; i < len; i++) { + connList[i] = getConnection(getTestName()); + } + Connection conn = connList[0]; + conn.createStatement().execute( + "create table test(id int primary key, v int)"); + conn.createStatement().execute( + "insert into test values(0, 0)"); + final int count = 1000; + Task[] tasks = new Task[len]; + + final CyclicBarrier barrier = new CyclicBarrier(len); + + for (int i = 0; i < len; i++) { + final int x = i; + // Recent changes exposed a race condition in this test itself. + // Without preliminary record locking, counter will be off. + connList[x].setAutoCommit(false); + tasks[i] = new Task() { + @Override + public void call() throws Exception { + for (int a = 0; a < count; a++) { + ResultSet rs = connList[x].createStatement().executeQuery( + "select v from test for update"); + assertTrue(rs.next()); + connList[x].createStatement().execute( + "update test set v=v+1"); + connList[x].commit(); + barrier.await(); + } + } + }; + tasks[i].execute(); + } + for (int i = 0; i < len; i++) { + tasks[i].get(); + } + ResultSet rs = conn.createStatement().executeQuery("select v from test"); + rs.next(); + assertEquals(count * len, rs.getInt(1)); + for (int i = 0; i < len; i++) { + connList[i].close(); + } + } + +} diff --git a/h2/src/test/org/h2/test/mvcc/TestMvccMultiThreaded2.java b/h2/src/test/org/h2/test/mvcc/TestMvccMultiThreaded2.java new file mode 100644 index 0000000..1f6231e --- /dev/null +++ b/h2/src/test/org/h2/test/mvcc/TestMvccMultiThreaded2.java @@ -0,0 +1,186 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.mvcc; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import org.h2.message.DbException; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.IOUtils; + +/** + * Additional MVCC (multi version concurrency) test cases. + */ +public class TestMvccMultiThreaded2 extends TestDb { + + private static final int TEST_THREAD_COUNT = 100; + private static final int TEST_TIME_SECONDS = 60; + private static final boolean DISPLAY_STATS = false; + + private static final String URL = ";LOCK_TIMEOUT=120000"; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); + test.config.lockTimeout = 120000; + test.config.memory = true; + test.testFromMain(); + } + + int getTestDuration() { + // to save some testing time + return config.big ? TEST_TIME_SECONDS : TEST_TIME_SECONDS / 10; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public void test() throws SQLException, InterruptedException { + testSelectForUpdateConcurrency(); + } + + private void testSelectForUpdateConcurrency() + throws SQLException, InterruptedException { + deleteDb(getTestName()); + Connection conn = getConnection(getTestName() + URL); + conn.setAutoCommit(false); + + String sql = "CREATE TABLE test (" + + "entity_id INTEGER NOT NULL PRIMARY KEY, " + + "lastUpdated INTEGER NOT NULL)"; + + Statement smtm = conn.createStatement(); + smtm.executeUpdate(sql); + + PreparedStatement ps = conn.prepareStatement( + "INSERT INTO test (entity_id, lastUpdated) VALUES (?, ?)"); + ps.setInt(1, 1); + ps.setInt(2, 100); + ps.executeUpdate(); + ps.setInt(1, 2); + ps.setInt(2, 200); + ps.executeUpdate(); + conn.commit(); + + CountDownLatch latch = new CountDownLatch(TEST_THREAD_COUNT + 1); + ArrayList threads = new ArrayList<>(); + for (int i = 0; i < TEST_THREAD_COUNT; i++) { + SelectForUpdate sfu = new SelectForUpdate(latch); + sfu.setName("Test SelectForUpdate Thread#"+i); + threads.add(sfu); + sfu.start(); + } + + latch.countDown(); + + // gather stats on threads after they finished + @SuppressWarnings("unused") + int minProcessed = Integer.MAX_VALUE, maxProcessed = 0, totalProcessed = 0; + + boolean allOk = true; + for (SelectForUpdate sfu : threads) { + // make sure all threads have stopped by joining with them + sfu.join(); + allOk &= sfu.ok; + totalProcessed += sfu.iterationsProcessed; + if (sfu.iterationsProcessed > maxProcessed) { + maxProcessed = sfu.iterationsProcessed; + } + if (sfu.iterationsProcessed < minProcessed) { + minProcessed = sfu.iterationsProcessed; + } + } + + if (DISPLAY_STATS) { + println(String.format( + "+ INFO: TestMvccMultiThreaded2 RUN STATS threads=%d, minProcessed=%d, maxProcessed=%d, " + + "totalProcessed=%d, averagePerThread=%d, averagePerThreadPerSecond=%d\n", + TEST_THREAD_COUNT, minProcessed, maxProcessed, totalProcessed, totalProcessed / TEST_THREAD_COUNT, + totalProcessed / (TEST_THREAD_COUNT * getTestDuration()))); + } + + IOUtils.closeSilently(conn); + deleteDb(getTestName()); + + assertTrue(allOk); + } + + /** + * Worker test thread selecting for update + */ + private class SelectForUpdate extends Thread + { + private final CountDownLatch latch; + public int iterationsProcessed; + + public boolean ok; + + SelectForUpdate(CountDownLatch latch) { + this.latch = latch; + } + + @Override + public void run() { + final long start = System.currentTimeMillis(); + boolean done = false; + try (Connection conn = getConnection(getTestName() + URL)) { + conn.setAutoCommit(false); + + // give the other threads a chance to start up before going into our work loop + latch.countDown(); + latch.await(); + + PreparedStatement ps = conn.prepareStatement( + "SELECT * FROM test WHERE entity_id = ? FOR UPDATE"); + while (!done) { + String id; + int value; + if ((iterationsProcessed & 1) == 0) { + id = "1"; + value = 100; + } else { + id = "2"; + value = 200; + } + ps.setString(1, id); + ResultSet rs = ps.executeQuery(); + + assertTrue(rs.next()); + assertTrue(rs.getInt(2) == value); + + conn.commit(); + iterationsProcessed++; + + long now = System.currentTimeMillis(); + if (now - start > 1000 * getTestDuration()) { + done = true; + } + } + ok = true; + } catch (InterruptedException ignore) { + } catch (SQLException e) { + TestBase.logError("SQL error from thread "+getName(), e); + throw DbException.convert(e); + } catch (Exception e) { + TestBase.logError("General error from thread "+getName(), e); + throw e; + } + } + } +} diff --git a/h2/src/test/org/h2/test/mvcc/package.html b/h2/src/test/org/h2/test/mvcc/package.html new file mode 100644 index 0000000..73ab19a --- /dev/null +++ b/h2/src/test/org/h2/test/mvcc/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +Multi version concurrency tests. + +

    \ No newline at end of file diff --git a/h2/src/test/org/h2/test/otherDatabases.txt b/h2/src/test/org/h2/test/otherDatabases.txt new file mode 100644 index 0000000..48a689b --- /dev/null +++ b/h2/src/test/org/h2/test/otherDatabases.txt @@ -0,0 +1,75 @@ +MySQL +-------------------------------------------------------------------------------------------------------- +Start: +sudo mysqld_safe --default-storage-engine=innodb +sudo mysqld_safe --default-storage-engine=innodb --wait_timeout=10 + +Stop: +sudo mysqladmin shutdown + +Configuration: +sudo cp /usr/local/mysql/support-files/my-medium.cnf /etc/my.cnf +sudo pico /etc/my.cnf +innodb_flush_log_at_trx_commit = 0 + +Initialization: +sudo mysql +create database test; +create user 'sa'@'localhost' identified by 'sa'; +use test; +grant all on * to 'sa'@'localhost' with grant option; + +'TRADITIONAL' is default; ANSI mode can be set using: +SET GLOBAL sql_mode='ANSI'; +SELECT @@global.sql_mode; + +Non-standard escape mechanism: +select 'Joe''s', 'Joe\'s'; + +Compare with NULL problem: +drop table test; +create table test(id int); +insert into test values(1); +insert into test values(null); +-- 2 rows even in ANSI mode (correct is 1 row): +select * from test where id=id and 1=1; + + +MS SQL Server 2005 +-------------------------------------------------------------------------------------------------------- +Problems when trying to select large objects (even if ResultSet.getBinaryStream is used). +The workaround responseBuffering=adaptive doesn't always seem to work +(jdbc:sqlserver://localhost:4220;DatabaseName=test;responseBuffering=adaptive) + + +PostgreSQL +-------------------------------------------------------------------------------------------------------- + +Non-standard escape mechanism: +select 'Joe''s', 'Joe\'s'; + +Configuration: +Mac OS: +/Library/PostgreSQL/8.4/data/postgresql.conf +fsync = off +commit_delay = 1000 + + +HSQLDB +-------------------------------------------------------------------------------------------------------- +To use the same default settings as H2, use: +jdbc:hsqldb:data/test;hsqldb.default_table_type=cached;sql.enforce_size=true +Also, you need to execute the following statement: +SET WRITE_DELAY 1 +No optimization for COUNT(*) + + +Derby +-------------------------------------------------------------------------------------------------------- +To call getFD().sync() (which results in the OS call fsync()), +set the system property derby.storage.fileSyncTransactionLog to true. +See +https://db.apache.org/derby/javadoc/engine/org/apache/derby/iapi/reference/Property.html#FILESYNC_TRANSACTION_LOG +Missing features: +LIMIT OFFSET is not supported. +No optimization for COUNT(*) diff --git a/h2/src/test/org/h2/test/package.html b/h2/src/test/org/h2/test/package.html new file mode 100644 index 0000000..b2fcea6 --- /dev/null +++ b/h2/src/test/org/h2/test/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +High level test classes. + +

    \ No newline at end of file diff --git a/h2/src/test/org/h2/test/poweroff/Listener.java b/h2/src/test/org/h2/test/poweroff/Listener.java new file mode 100644 index 0000000..2b49cac --- /dev/null +++ b/h2/src/test/org/h2/test/poweroff/Listener.java @@ -0,0 +1,83 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.poweroff; + +import java.io.DataInputStream; +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.concurrent.TimeUnit; + +/** + * The listener application for the power off test. + * The listener runs on a computer that stays on during the whole test. + */ +public class Listener implements Runnable { + + private volatile int maxValue; + + /** + * This method is called when executing this application from the command + * line. + * + * @param args the command line parameters + */ + public static void main(String... args) throws IOException { + new Listener().test(args); + } + + private void test(String... args) throws IOException { + int port = 9099; + for (int i = 0; i < args.length; i++) { + if (args[i].equals("-port")) { + port = Integer.parseInt(args[++i]); + } + } + listen(port); + } + + @Override + public void run() { + while (true) { + try { + Thread.sleep(10000); + } catch (Exception e) { + // ignore + } + System.out.println("Max=" + maxValue); + } + } + + private void listen(int port) throws IOException { + new Thread(this).start(); + ServerSocket serverSocket = new ServerSocket(port); + System.out.println("Listening on " + serverSocket.toString()); + long time; + maxValue = 0; + while (true) { + Socket socket = serverSocket.accept(); + DataInputStream in = new DataInputStream(socket.getInputStream()); + System.out.println("Connected"); + time = System.nanoTime(); + try { + while (true) { + int value = in.readInt(); + if (value < 0) { + break; + } + maxValue = Math.max(maxValue, value); + } + } catch (IOException e) { + System.out.println("Closed with Exception: " + e); + } + time = System.nanoTime() - time; + int operationsPerSecond = (int) (TimeUnit.SECONDS.toNanos(1) * maxValue / time); + System.out.println("Max=" + maxValue + + " operations/sec=" + operationsPerSecond); + } + } + +} diff --git a/h2/src/test/org/h2/test/poweroff/Test.java b/h2/src/test/org/h2/test/poweroff/Test.java new file mode 100644 index 0000000..2875236 --- /dev/null +++ b/h2/src/test/org/h2/test/poweroff/Test.java @@ -0,0 +1,169 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.poweroff; + +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.net.Socket; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * This application tests the durability / non-durability of file systems and + * databases. Two computers with network connection are required to run this + * test. Before starting this application, the Listener application must be + * started on another computer. + */ +public class Test { + + private String url; + private Connection conn; + private Statement stat; + private PreparedStatement prep; + + private Test() { + // nothing to do + } + + private Test(String driver, String url, String user, String password, + boolean writeDelay0) { + this.url = url; + try { + Class.forName(driver); + conn = DriverManager.getConnection(url, user, password); + stat = conn.createStatement(); + if (writeDelay0) { + stat.execute("SET WRITE_DELAY 0"); + } + System.out.println(url + " started"); + } catch (Exception e) { + System.out.println(url + ": " + e.toString()); + return; + } + try { + ResultSet rs = stat.executeQuery("SELECT MAX(ID) FROM TEST"); + rs.next(); + System.out.println(url + ": MAX(ID)=" + rs.getInt(1)); + stat.execute("DROP TABLE TEST"); + } catch (SQLException e) { + // ignore + } + try { + stat.execute("CREATE TABLE TEST" + + "(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + prep = conn.prepareStatement("INSERT INTO TEST VALUES(?, ?)"); + } catch (SQLException e) { + System.out.println(url + ": " + e.toString()); + } + } + + private void insert(int id) { + try { + if (prep != null) { + prep.setInt(1, id); + prep.setString(2, "World " + id); + prep.execute(); + } + } catch (SQLException e) { + System.out.println(url + ": " + e.toString()); + } + } + + /** + * This method is called when executing this application from the command + * line. + * + * @param args the command line parameters + */ + public static void main(String... args) throws Exception { + int port = 9099; + String connect = "192.168.0.3"; + boolean file = false; + for (int i = 0; i < args.length; i++) { + if (args[i].equals("-port")) { + port = Integer.parseInt(args[++i]); + } else if (args[i].equals("-connect")) { + connect = args[++i]; + } else if (args[i].equals("-file")) { + file = true; + } + } + test(connect, port, file); + } + + private static void test(String connect, int port, boolean file) + throws Exception { + Socket socket = new Socket(connect, port); + DataOutputStream out = new DataOutputStream(socket.getOutputStream()); + System.out.println("Connected to " + socket.toString()); + if (file) { + testFile(out); + } else { + testDatabases(out); + } + } + + private static void testFile(DataOutputStream out) throws IOException { + File file = new File("test.txt"); + if (file.exists()) { + file.delete(); + } + RandomAccessFile write = new RandomAccessFile(file, "rws"); + // RandomAccessFile write = new RandomAccessFile(file, "rwd"); + int fileSize = 10 * 1024 * 1024; + write.seek(fileSize - 1); + write.write(0); + write.seek(0); + int i = 0; + FileDescriptor fd = write.getFD(); + while (true) { + if (write.getFilePointer() >= fileSize) { + break; + } + write.writeBytes(i + "\r\n"); + fd.sync(); + out.writeInt(i); + out.flush(); + i++; + } + write.close(); + } + + private static void testDatabases(DataOutputStream out) throws Exception { + Test[] dbs = { + new Test("org.h2.Driver", + "jdbc:h2:./test1", "sa", "", true), + new Test("org.h2.Driver", + "jdbc:h2:./test2", "sa", "", false), + new Test("org.hsqldb.jdbcDriver", + "jdbc:hsqldb:test4", "sa", "", false), + // new Test("com.mysql.cj.jdbc.Driver", + // "jdbc:mysql://localhost/test", "sa", ""), + new Test("org.postgresql.Driver", + "jdbc:postgresql:test", "sa", "sa", false), + new Test("org.apache.derby.iapi.jdbc.AutoloadedDriver", + "jdbc:derby:test;create=true", "sa", "", false), + new Test("org.h2.Driver", + "jdbc:h2:./test5", "sa", "", true), + new Test("org.h2.Driver", + "jdbc:h2:./test6", "sa", "", false), }; + for (int i = 0;; i++) { + for (Test t : dbs) { + t.insert(i); + } + out.writeInt(i); + out.flush(); + } + } + +} diff --git a/h2/src/test/org/h2/test/poweroff/TestRecover.java b/h2/src/test/org/h2/test/poweroff/TestRecover.java new file mode 100644 index 0000000..922d43f --- /dev/null +++ b/h2/src/test/org/h2/test/poweroff/TestRecover.java @@ -0,0 +1,374 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.poweroff; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.security.SecureRandom; +import java.sql.Connection; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import org.h2.util.IOUtils; + +/** + * This standalone test checks if recovery of a database works after power + * failure. + */ +public class TestRecover { + + private static final int MAX_STRING_LENGTH = 10000; + private static final String NODE = System.getProperty("test.node", ""); + private static final String DIR = System.getProperty("test.dir", "/temp/db"); + + private static final String TEST_DIRECTORY = DIR + "/data" + NODE; + private static final String BACKUP_DIRECTORY = DIR + "/last"; + private static final String URL = System.getProperty( + "test.url", "jdbc:h2:" + TEST_DIRECTORY + "/test"); + private static final String DRIVER = System.getProperty( + "test.driver", "org.h2.Driver"); + + // private static final String DIR = + // System.getProperty("test.dir", "/temp/derby"); + // private static final String URL = + // System.getProperty("test.url", + // "jdbc:derby:/temp/derby/data/test;create=true"); + // private static final String DRIVER = + // System.getProperty("test.driver", + // "org.apache.derby.iapi.jdbc.AutoloadedDriver"); + + /** + * This method is called when executing this application from the command + * line. + * + * @param args the command line parameters + */ + public static void main(String... args) throws Exception { + System.out.println("URL=" + URL); + System.out.println("backup..."); + new File(TEST_DIRECTORY).mkdirs(); + File backup = backup(TEST_DIRECTORY, + BACKUP_DIRECTORY, "data", 10, NODE); + System.out.println("check consistency..."); + if (!testConsistency()) { + System.out.println("error! renaming file"); + backup.renameTo(new File(backup.getParentFile(), + "error-" + backup.getName())); + } + System.out.println("deleting old run..."); + deleteRecursive(new File(TEST_DIRECTORY)); + System.out.println("testing..."); + testLoop(); + } + + private static File backup(String sourcePath, String targetPath, + String basePath, int max, String node) throws IOException { + File root = new File(targetPath); + if (!root.exists()) { + root.mkdirs(); + } + while (true) { + File oldest = null; + int count = 0; + for (File f : root.listFiles()) { + String name = f.getName(); + if (f.isFile() && name.startsWith("backup") && name.endsWith(".zip")) { + count++; + if (oldest == null || f.lastModified() < oldest.lastModified()) { + oldest = f; + } + } + } + if (count < max) { + break; + } + oldest.delete(); + } + String date = DateTimeFormatter.ofPattern("yyMMdd-HHmmss").format(LocalDateTime.now()); + File zipFile = new File(root, "backup-" + date + "-" + node + ".zip"); + ArrayList list = new ArrayList<>(); + File base = new File(sourcePath); + listRecursive(list, base); + if (list.size() == 0) { + FileOutputStream out = new FileOutputStream(zipFile); + out.close(); + } else { + OutputStream out = null; + try { + out = new FileOutputStream(zipFile); + ZipOutputStream zipOut = new ZipOutputStream(out); + String baseName = base.getAbsolutePath(); + for (File f : list) { + String fileName = f.getAbsolutePath(); + String entryName = fileName; + if (fileName.startsWith(baseName)) { + entryName = entryName.substring(baseName.length()); + } + if (entryName.startsWith("\\")) { + entryName = entryName.substring(1); + } + if (!entryName.startsWith("/")) { + entryName = "/" + entryName; + } + ZipEntry entry = new ZipEntry(basePath + entryName); + zipOut.putNextEntry(entry); + + try (InputStream in = new FileInputStream(fileName)) { + IOUtils.copyAndCloseInput(in, zipOut); + } + zipOut.closeEntry(); + } + zipOut.closeEntry(); + zipOut.close(); + } finally { + IOUtils.closeSilently(out); + } + } + return zipFile; + } + + private static void listRecursive(List list, File file) + throws IOException { + File[] l = file.listFiles(); + for (int i = 0; l != null && i < l.length; i++) { + File f = l[i]; + if (f.isDirectory()) { + listRecursive(list, f); + } else { + list.add(f); + } + } + } + + private static void deleteRecursive(File file) throws IOException { + if (file.isDirectory()) { + for (File f : file.listFiles()) { + deleteRecursive(f); + } + } + if (file.exists() && !file.delete()) { + throw new IOException("Could not delete " + file.getAbsolutePath()); + } + } + + private static void testLoop() throws Exception { + Random random = new SecureRandom(); + while (true) { + runOneTest(random.nextInt()); + } + } + + private static Connection openConnection() throws Exception { + Class.forName(DRIVER); + Connection conn = DriverManager.getConnection(URL, "sa", "sa"); + Statement stat = conn.createStatement(); + try { + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, " + + "D INT, NAME VARCHAR("+MAX_STRING_LENGTH+"))"); + stat.execute("CREATE INDEX IDX_TEST_D ON TEST(D)"); + } catch (SQLException e) { + // ignore + } + return conn; + } + + private static void closeConnection(Connection conn) { + try { + conn.close(); + } catch (SQLException e) { + // ignore + } + if (DRIVER.startsWith("org.apache.derby")) { + try { + DriverManager.getConnection("jdbc:derby:;shutdown=true"); + } catch (SQLException e) { + // ignore + } + try { + Driver driver = (Driver) Class.forName(DRIVER).getDeclaredConstructor().newInstance(); + DriverManager.registerDriver(driver); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private static void runOneTest(int i) throws Exception { + Random random = new Random(i); + Connection conn = openConnection(); + PreparedStatement prepInsert = null; + PreparedStatement prepDelete = null; + conn.setAutoCommit(false); + for (int id = 0;; id++) { + boolean rollback = random.nextInt(10) == 1; + int len; + if (random.nextInt(10) == 1) { + len = random.nextInt(100) * 2; + } else { + len = random.nextInt(2) * 2; + } + if (rollback && random.nextBoolean()) { + // make the length odd + len++; + } + // byte[] data = new byte[len]; + // random.nextBytes(data); + int op = random.nextInt(); + if (op % 1000000 == 0) { + closeConnection(conn); + conn = openConnection(); + conn.setAutoCommit(false); + prepInsert = null; + prepDelete = null; + } +// if (random.nextBoolean()) { +// int test; +// if (random.nextBoolean()) { +// conn.createStatement().execute( +// "drop index if exists idx_2"); +// conn.createStatement().execute( +// "create table if not exists test2" + +// "(id int primary key) as select x " + +// "from system_range(1, 1000)"); +// } else { +// conn.createStatement().execute( +// "create index if not exists idx_2 " + +// "on test(d, name, id)"); +// conn.createStatement().execute( +// "drop table if exists test2"); +// } +// } + if (random.nextBoolean()) { + if (prepInsert == null) { + prepInsert = conn.prepareStatement( + "INSERT INTO TEST(ID, D, NAME) VALUES(?, ?, ?)"); + } + prepInsert.setInt(1, id); + prepInsert.setInt(2, random.nextInt(10000)); + StringBuilder buff = new StringBuilder(); + buff.append(len); + switch (random.nextInt(10)) { + case 0: + len = random.nextInt(MAX_STRING_LENGTH); + break; + case 1: + case 2: + case 3: + len = random.nextInt(MAX_STRING_LENGTH / 20); + break; + default: + len = 0; + } + len -= 10; + while (len > 0) { + buff.append('-'); + len--; + } + buff.append("->"); + String s = buff.toString(); + prepInsert.setString(3, s); + prepInsert.execute(); + } else { + ResultSet rs = conn.createStatement().executeQuery("SELECT COUNT(*) FROM TEST"); + rs.next(); + int count = rs.getInt(1); + rs.close(); + if (count > 1000) { + if (prepDelete == null) { + prepDelete = conn.prepareStatement("DELETE FROM TEST WHERE ROWNUM <= 4"); + } + prepDelete.execute(); + } + } + if (rollback) { + conn.rollback(); + } else { + conn.commit(); + } + } + } + + private static boolean testConsistency() { + PrintWriter p = null; + try { + p = new PrintWriter(new FileOutputStream(TEST_DIRECTORY + "/result.txt")); + p.println("Results"); + p.flush(); + } catch (Throwable t) { + t.printStackTrace(); + System.exit(0); + } + Connection conn = null; + try { + conn = openConnection(); + test(conn, ""); + test(conn, "ORDER BY D"); + closeConnection(conn); + return true; + } catch (Throwable t) { + t.printStackTrace(); + t.printStackTrace(p); + return false; + } finally { + if (conn != null) { + try { + closeConnection(conn); + } catch (Throwable t2) { + t2.printStackTrace(); + t2.printStackTrace(p); + } + } + if (p != null) { + p.flush(); + p.close(); + IOUtils.closeSilently(p); + } + } + } + + private static void test(Connection conn, String order) throws Exception { + ResultSet rs; + rs = conn.createStatement().executeQuery("SELECT * FROM TEST " + order); + int max = 0; + int count = 0; + while (rs.next()) { + count++; + int id = rs.getInt("ID"); + String name = rs.getString("NAME"); + if (!name.endsWith(">")) { + throw new Exception("unexpected entry " + id + " value " + name); + } + int idx = name.indexOf('-'); + if (idx < 0) { + throw new Exception("unexpected entry " + id + " value " + name); + } + int value = Integer.parseInt(name.substring(0, idx)); + if (value % 2 != 0) { + throw new Exception("unexpected odd entry " + id + " value " + value); + } + max = Math.max(max, id); + } + rs.close(); + System.out.println("max row id: " + max + " rows: " + count); + } + +} diff --git a/h2/src/test/org/h2/test/poweroff/TestRecoverKillLoop.java b/h2/src/test/org/h2/test/poweroff/TestRecoverKillLoop.java new file mode 100644 index 0000000..20c9a4d --- /dev/null +++ b/h2/src/test/org/h2/test/poweroff/TestRecoverKillLoop.java @@ -0,0 +1,67 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.poweroff; + +import java.io.InputStream; +import java.util.Random; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.synth.OutputCatcher; + +/** + * Run the TestRecover test case in a loop. The process is killed after 10 + * seconds. + */ +public class TestRecoverKillLoop extends TestBase { + + /** + * This method is called when executing this application from the command + * line. + * + * @param args the command line parameters + */ + public static void main(String... args) throws Exception { + new TestRecoverKillLoop().runTest(Integer.MAX_VALUE); + } + + @Override + public void test() throws Exception { + runTest(3); + } + + private void runTest(int count) throws Exception { + FileUtils.deleteRecursive("data/db", false); + Random random = new Random(1); + for (int i = 0; i < count; i++) { + String[] procDef = { + getJVM(), "-cp", getClassPath(), + "-Dtest.dir=data/db", + TestRecover.class.getName() + }; + Process p = Runtime.getRuntime().exec(procDef); + InputStream in = p.getInputStream(); + OutputCatcher catcher = new OutputCatcher(in); + catcher.start(); + while (true) { + String s = catcher.readLine(60 * 1000); + // System.out.println("> " + s); + if (s == null) { + fail("No reply from process"); + } else if (s.startsWith("testing...")) { + int sleep = random.nextInt(10000); + Thread.sleep(sleep); + printTime("killing"); + p.destroy(); + p.waitFor(); + break; + } else if (s.startsWith("error!")) { + fail("Failed: " + s); + } + } + } + } + +} diff --git a/h2/src/test/org/h2/test/poweroff/TestReorderWrites.java b/h2/src/test/org/h2/test/poweroff/TestReorderWrites.java new file mode 100644 index 0000000..a6bfba0 --- /dev/null +++ b/h2/src/test/org/h2/test/poweroff/TestReorderWrites.java @@ -0,0 +1,213 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.poweroff; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.Arrays; +import java.util.Map; +import java.util.Random; +import org.h2.mvstore.MVStore; +import org.h2.mvstore.MVStoreException; +import org.h2.mvstore.MVStoreTool; +import org.h2.store.fs.FilePath; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.utils.FilePathReorderWrites; + +/** + * Tests that the MVStore recovers from a power failure if the file system or + * disk re-ordered the write operations. + */ +public class TestReorderWrites extends TestBase { + + private static final boolean LOG = false; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testMVStore(false); + testMVStore(true); + testFileSystem(false); + testFileSystem(true); + } + + private void testMVStore(final boolean partialWrite) { + // Add partial write test + // @since 2019-07-31 little-pan + println(String.format("testMVStore(): %s partial write", partialWrite? "Enable": "Disable")); + FilePathReorderWrites.setPartialWrites(partialWrite); + + FilePathReorderWrites fs = FilePathReorderWrites.register(); + String fileName = "reorder:memFS:test.mv"; + try { + for (int i = 0; i < (config.big ? 1000 : 100); i++) { + log(i + " --------------------------------"); + // this test is not interested in power off failures during + // initial creation + fs.setPowerOffCountdown(0, 0); + // release the static data this test generates + FileUtils.delete("memFS:test.mv"); + FileUtils.delete("memFS:test.mv.copy"); + MVStore store = new MVStore.Builder(). + fileName(fileName). + autoCommitDisabled().open(); + // store.setRetentionTime(10); + Map map = store.openMap("data"); + map.put(-1, new byte[1]); + store.commit(); + store.getFileStore().sync(); + Random r = new Random(i); + int stop = 4 + r.nextInt(config.big ? 150 : 20); + log("countdown start"); + fs.setPowerOffCountdown(stop, i); + try { + for (int j = 1; j < 100; j++) { + Map newMap = store.openMap("d" + j); + newMap.put(j, j * 10); + int key = r.nextInt(10); + int len = 10 * r.nextInt(1000); + if (r.nextBoolean()) { + map.remove(key); + } else { + map.put(key, new byte[len]); + } + log("op " + j + ": "); + store.commit(); + switch (r.nextInt(10)) { + case 0: + log("op compact"); + store.compact(100, 10 * 1024); + break; + case 1: + log("op compactMoveChunks"); + store.compactMoveChunks(); + log("op compactMoveChunks done"); + break; + } + } + // write has to fail at some point + fail(); + } catch (MVStoreException e) { + log("stop " + e + ", cause: " + e.getCause()); + // expected + } + try { + store.close(); + } catch (MVStoreException e) { + // expected + store.closeImmediately(); + } + log("verify"); + fs.setPowerOffCountdown(100, 0); + if (LOG) { + MVStoreTool.dump(fileName, true); + } + store = new MVStore.Builder(). + fileName(fileName). + autoCommitDisabled().open(); + map = store.openMap("data"); + if (!map.containsKey(-1)) { + fail("key not found, size=" + map.size() + " i=" + i); + } else { + assertEquals("i=" + i, 1, map.get(-1).length); + } + for (int j = 0; j < 100; j++) { + Map newMap = store.openMap("d" + j); + newMap.get(j); + } + map.keySet(); + store.close(); + } + } finally { + // release the static data this test generates + FileUtils.delete("memFS:test.mv"); + FileUtils.delete("memFS:test.mv.copy"); + } + } + + private static void log(String message) { + if (LOG) { + System.out.println(message); + } + } + + private void testFileSystem(final boolean partialWrite) throws IOException { + FilePathReorderWrites fs = FilePathReorderWrites.register(); + // *disable this for now, still bug(s) in our code* + // Add partial write enable test + // @since 2019-07-31 little-pan + FilePathReorderWrites.setPartialWrites(partialWrite); + println(String.format("testFileSystem(): %s partial write", partialWrite? "Enable": "Disable")); + + String fileName = "reorder:memFS:test"; + final ByteBuffer empty = ByteBuffer.allocate(1024); + Random r = new Random(1); + long minSize = Long.MAX_VALUE; + long maxSize = 0; + int minWritten = Integer.MAX_VALUE; + int maxWritten = 0; + for (int i = 0; i < 100; i++) { + fs.setPowerOffCountdown(100, i); + FileUtils.delete(fileName); + FileChannel fc = FilePath.get(fileName).open("rw"); + for (int j = 0; j < 20; j++) { + fc.write(empty, j * 1024); + empty.flip(); + } + fs.setPowerOffCountdown(4 + r.nextInt(20), i); + int lastWritten = 0; + int lastTruncated = 0; + for (int j = 20; j >= 0; j--) { + try { + byte[] bytes = new byte[1024]; + Arrays.fill(bytes, (byte) j); + ByteBuffer data = ByteBuffer.wrap(bytes); + fc.write(data, 0); + lastWritten = j; + } catch (IOException e) { + // expected + break; + } + try { + fc.truncate(j * 1024); + lastTruncated = j * 1024; + } catch (IOException e) { + // expected + break; + } + } + if (lastTruncated <= 0 || lastWritten <= 0) { + fail(); + } + fs.setPowerOffCountdown(100, 0); + fc = FilePath.get(fileName).open("rw"); + ByteBuffer data = ByteBuffer.allocate(1024); + fc.read(data, 0); + data.flip(); + int got = data.get(); + long size = fc.size(); + minSize = Math.min(minSize, size); + maxSize = Math.max(minSize, size); + minWritten = Math.min(minWritten, got); + maxWritten = Math.max(maxWritten, got); + } + assertTrue(minSize < maxSize); + assertTrue(minWritten < maxWritten); + // release the static data this test generates + FileUtils.delete(fileName); + } + +} diff --git a/h2/src/test/org/h2/test/poweroff/TestWrite.java b/h2/src/test/org/h2/test/poweroff/TestWrite.java new file mode 100644 index 0000000..b7d75a0 --- /dev/null +++ b/h2/src/test/org/h2/test/poweroff/TestWrite.java @@ -0,0 +1,134 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.poweroff; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.RandomAccessFile; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.TimeUnit; + +/** + * This test shows the raw file access performance using various file modes. + * It also tests databases. + */ +public class TestWrite { + + private TestWrite() { + // utility class + } + + /** + * This method is called when executing this application from the command + * line. + * + * @param args the command line parameters + */ + public static void main(String... args) throws Exception { + testFile("rw", false); + testFile("rwd", false); + testFile("rws", false); + testFile("rw", true); + testFile("rwd", true); + testFile("rws", true); + testDatabase("org.h2.Driver", + "jdbc:h2:./test", "sa", ""); + testDatabase("org.hsqldb.jdbcDriver", + "jdbc:hsqldb:test4", "sa", ""); + testDatabase("org.apache.derby.iapi.jdbc.AutoloadedDriver", + "jdbc:derby:test;create=true", "sa", ""); + testDatabase("com.mysql.cj.jdbc.Driver", + "jdbc:mysql://localhost/test", "sa", "sa"); + testDatabase("org.postgresql.Driver", + "jdbc:postgresql:test", "sa", "sa"); + } + + private static void testFile(String mode, boolean flush) throws Exception { + System.out.println("Testing RandomAccessFile(.., \"" + mode + "\")..."); + if (flush) { + System.out.println(" with FileDescriptor.sync()"); + } + RandomAccessFile file = new RandomAccessFile("test.txt", mode); + file.setLength(0); + FileDescriptor fd = file.getFD(); + long start = System.nanoTime(); + byte[] data = { 0 }; + file.write(data); + int i = 0; + if (flush) { + for (;; i++) { + file.seek(0); + file.write(data); + fd.sync(); + if ((i & 15) == 0) { + long time = System.nanoTime() - start; + if (time > TimeUnit.SECONDS.toNanos(5)) { + break; + } + } + } + } else { + for (;; i++) { + file.seek(0); + file.write(data); + if ((i & 1023) == 0) { + long time = System.nanoTime() - start; + if (time > TimeUnit.SECONDS.toNanos(5)) { + break; + } + } + } + } + long time = System.nanoTime() - start; + System.out.println("Time: " + TimeUnit.NANOSECONDS.toMillis(time)); + System.out.println("Operations: " + i); + System.out.println("Operations/second: " + (i * TimeUnit.SECONDS.toNanos(1) / time)); + System.out.println(); + file.close(); + new File("test.txt").delete(); + } + + private static void testDatabase(String driver, String url, String user, + String password) throws Exception { + Class.forName(driver); + Connection conn = DriverManager.getConnection(url, user, password); + System.out.println("Testing Database, URL=" + url); + Statement stat = conn.createStatement(); + try { + stat.execute("DROP TABLE TEST"); + } catch (SQLException e) { + // ignore + } + stat.execute("CREATE TABLE TEST(ID INT)"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST VALUES(?)"); + long start = System.nanoTime(); + int i = 0; + for (;; i++) { + prep.setInt(1, i); + // autocommit is on by default, so this commits as well + prep.execute(); + if ((i & 15) == 0) { + long time = System.nanoTime() - start; + if (time > TimeUnit.SECONDS.toNanos(5)) { + break; + } + } + } + long time = System.nanoTime() - start; + System.out.println("Time: " + TimeUnit.NANOSECONDS.toMillis(time)); + System.out.println("Operations: " + i); + System.out.println("Operations/second: " + (i * TimeUnit.SECONDS.toNanos(1) / time)); + System.out.println(); + stat.execute("DROP TABLE TEST"); + conn.close(); + } + +} diff --git a/h2/src/test/org/h2/test/poweroff/package.html b/h2/src/test/org/h2/test/poweroff/package.html new file mode 100644 index 0000000..73ab19a --- /dev/null +++ b/h2/src/test/org/h2/test/poweroff/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +Multi version concurrency tests. + +

    \ No newline at end of file diff --git a/h2/src/test/org/h2/test/recover/RecoverLobTest.java b/h2/src/test/org/h2/test/recover/RecoverLobTest.java new file mode 100644 index 0000000..fb93f5b --- /dev/null +++ b/h2/src/test/org/h2/test/recover/RecoverLobTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.recover; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.DeleteDbFiles; +import org.h2.tools.Recover; + +/** + * Tests BLOB/CLOB recovery. + */ +public class RecoverLobTest extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + if (config.memory) { + return false; + } + return true; + } + + @Override + public void test() throws Exception { + testRecoverClob(); + } + + private void testRecoverClob() throws Exception { + DeleteDbFiles.execute(getBaseDir(), "recovery", true); + Connection conn = getConnection("recovery"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int, data clob)"); + stat.execute("insert into test values(1, space(10000))"); + stat.execute("insert into test values(2, space(20000))"); + stat.execute("insert into test values(3, space(30000))"); + stat.execute("insert into test values(4, space(40000))"); + stat.execute("insert into test values(5, space(50000))"); + stat.execute("insert into test values(6, space(60000))"); + stat.execute("insert into test values(7, space(70000))"); + stat.execute("insert into test values(8, space(80000))"); + + conn.close(); + Recover.main("-dir", getBaseDir(), "-db", "recovery"); + DeleteDbFiles.execute(getBaseDir(), "recovery", true); + conn = getConnection( + "recovery;init=runscript from '" + + getBaseDir() + "/recovery.h2.sql'"); + stat = conn.createStatement(); + + ResultSet rs = stat.executeQuery("select * from test"); + while(rs.next()){ + + int id = rs.getInt(1); + String data = rs.getString(2); + + assertNotNull(data); + assertTrue(data.length() == 10000 * id); + + } + rs.close(); + conn.close(); + } + + + +} diff --git a/h2/src/test/org/h2/test/recover/package.html b/h2/src/test/org/h2/test/recover/package.html new file mode 100644 index 0000000..05ddb3e --- /dev/null +++ b/h2/src/test/org/h2/test/recover/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +Recovery tests. + +

    \ No newline at end of file diff --git a/h2/src/test/org/h2/test/rowlock/TestRowLocks.java b/h2/src/test/org/h2/test/rowlock/TestRowLocks.java new file mode 100644 index 0000000..3c481d4 --- /dev/null +++ b/h2/src/test/org/h2/test/rowlock/TestRowLocks.java @@ -0,0 +1,103 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.rowlock; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.Task; + +/** + * Row level locking tests. + */ +public class TestRowLocks extends TestDb { + + /** + * The statements used in this test. + */ + Statement s1, s2; + + private Connection c1, c2; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testCases(); + deleteDb(getTestName()); + } + + private void testCases() throws Exception { + deleteDb(getTestName()); + c1 = getConnection(getTestName()); + s1 = c1.createStatement(); + s1.execute("SET LOCK_TIMEOUT 10000"); + s1.execute("CREATE TABLE TEST AS " + + "SELECT X ID, 'Hello' NAME FROM SYSTEM_RANGE(1, 3)"); + c1.commit(); + c1.setAutoCommit(false); + s1.execute("UPDATE TEST SET NAME='Hallo' WHERE ID=1"); + + c2 = getConnection(getTestName()); + c2.setAutoCommit(false); + s2 = c2.createStatement(); + + assertEquals("Hallo", getSingleValue(s1, + "SELECT NAME FROM TEST WHERE ID=1")); + assertEquals("Hello", getSingleValue(s2, + "SELECT NAME FROM TEST WHERE ID=1")); + + s2.execute("UPDATE TEST SET NAME='Hallo' WHERE ID=2"); + assertThrows(ErrorCode.LOCK_TIMEOUT_1, s2). + executeUpdate("UPDATE TEST SET NAME='Hi' WHERE ID=1"); + c1.commit(); + c2.commit(); + + assertEquals("Hallo", getSingleValue(s1, + "SELECT NAME FROM TEST WHERE ID=1")); + assertEquals("Hallo", getSingleValue(s2, + "SELECT NAME FROM TEST WHERE ID=1")); + + s2.execute("UPDATE TEST SET NAME='H1' WHERE ID=1"); + Task task = new Task() { + @Override + public void call() throws SQLException { + s1.execute("UPDATE TEST SET NAME='H2' WHERE ID=1"); + } + }; + task.execute(); + Thread.sleep(100); + c2.commit(); + task.get(); + c1.commit(); + assertEquals("H2", getSingleValue(s1, + "SELECT NAME FROM TEST WHERE ID=1")); + assertEquals("H2", getSingleValue(s2, + "SELECT NAME FROM TEST WHERE ID=1")); + + c1.close(); + c2.close(); + } + + private static String getSingleValue(Statement stat, String sql) + throws SQLException { + ResultSet rs = stat.executeQuery(sql); + return rs.next() ? rs.getString(1) : null; + } + +} diff --git a/h2/src/test/org/h2/test/rowlock/package.html b/h2/src/test/org/h2/test/rowlock/package.html new file mode 100644 index 0000000..ce78426 --- /dev/null +++ b/h2/src/test/org/h2/test/rowlock/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +Row level locking tests. + +

    \ No newline at end of file diff --git a/h2/src/test/org/h2/test/scripts/Aggregate1.java b/h2/src/test/org/h2/test/scripts/Aggregate1.java new file mode 100644 index 0000000..038a937 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/Aggregate1.java @@ -0,0 +1,32 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.scripts; + +import java.sql.SQLException; + +import org.h2.api.Aggregate; +import org.h2.api.H2Type; + +/** + * An aggregate function for tests. + */ +public class Aggregate1 implements Aggregate { + + @Override + public int getInternalType(int[] inputTypes) throws SQLException { + return H2Type.INTEGER.getVendorTypeNumber(); + } + + @Override + public void add(Object value) throws SQLException { + } + + @Override + public Object getResult() throws SQLException { + return 0; + } + +} diff --git a/h2/src/test/org/h2/test/scripts/TestScript.java b/h2/src/test/org/h2/test/scripts/TestScript.java new file mode 100644 index 0000000..0e7686b --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/TestScript.java @@ -0,0 +1,770 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.scripts; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.PrintStream; +import java.io.RandomAccessFile; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +import org.h2.api.ErrorCode; +import org.h2.command.CommandContainer; +import org.h2.command.CommandInterface; +import org.h2.command.Prepared; +import org.h2.command.dml.ScriptCommand; +import org.h2.command.query.Query; +import org.h2.engine.Mode.ModeEnum; +import org.h2.jdbc.JdbcConnection; +import org.h2.jdbc.JdbcPreparedStatement; +import org.h2.test.TestAll; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.StringUtils; +import org.h2.value.DataType; + +/** + * This test runs a SQL script file and compares the output with the expected + * output. + */ +public class TestScript extends TestDb { + + private static final String BASE_DIR = "org/h2/test/scripts/"; + + private static final boolean FIX_OUTPUT = false; + + private static final Field COMMAND; + + private static final Field PREPARED; + + private static boolean CHECK_ORDERING; + + /** If set to true, the test will exit at the first failure. */ + private boolean failFast; + /** If set to a value the test will add all executed statements to this list */ + private ArrayList statements; + + private boolean reconnectOften; + private Connection conn; + private Statement stat; + private String fileName; + private LineNumberReader in; + private PrintStream out; + private final ArrayList result = new ArrayList<>(); + private final ArrayDeque putBack = new ArrayDeque<>(); + private boolean foundErrors; + + private Random random = new Random(1); + + static { + try { + COMMAND = JdbcPreparedStatement.class.getDeclaredField("command"); + COMMAND.setAccessible(true); + PREPARED = CommandContainer.class.getDeclaredField("prepared"); + PREPARED.setAccessible(true); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + CHECK_ORDERING = true; + TestBase.createCaller().init().testFromMain(); + } + + /** + * Get all SQL statements of this file. + * + * @param conf the configuration + * @return the list of statements + */ + public ArrayList getAllStatements(TestAll conf) throws Exception { + config = conf; + ArrayList result = new ArrayList<>(4000); + try { + statements = result; + test(); + } finally { + this.statements = null; + } + return result; + } + + @Override + public boolean isEnabled() { + if (config.networked && config.big) { + return false; + } + return true; + } + + @Override + public void test() throws Exception { + reconnectOften = !config.memory && config.big; + + testScript("testScript.sql"); + if (!config.memory && !config.big && !config.networked) { + testScript("testSimple.sql"); + } + testScript("dual.sql"); + testScript("indexes.sql"); + testScript("information_schema.sql"); + testScript("range_table.sql"); + testScript("altertable-index-reuse.sql"); + testScript("altertable-fk.sql"); + testScript("default-and-on_update.sql"); + + for (String s : new String[] { "add_months", "compatibility", "group_by", "strict_and_legacy"}) { + testScript("compatibility/" + s + ".sql"); + } + for (String s : new String[] { "array", "bigint", "binary", "blob", + "boolean", "char", "clob", "date", "decfloat", "double_precision", "enum", + "geometry", "identity", "int", "interval", "java_object", "json", "numeric", "real", "row", "smallint", + "time-with-time-zone", "time", "timestamp-with-time-zone", "timestamp", "tinyint", + "uuid", "varbinary", "varchar", "varchar-ignorecase" }) { + testScript("datatypes/" + s + ".sql"); + } + for (String s : new String[] { "alterDomain", "alterTableAdd", "alterTableAlterColumn", "alterTableDropColumn", + "alterTableDropConstraint", + "alterTableRename", "alterTableRenameConstraint", + "analyze", "commentOn", "createAlias", "createConstant", "createDomain", + "createIndex", "createSchema", "createSequence", "createSynonym", + "createTable", "createTrigger", "createView", "dropAllObjects", "dropDomain", "dropIndex", + "dropSchema", "dropTable", "grant", "truncateTable" }) { + testScript("ddl/" + s + ".sql"); + } + for (String s : new String[] { "delete", "error_reporting", "execute_immediate", "insert", "insertIgnore", + "merge", "mergeUsing", "replace", "script", "show", "update", "with" }) { + testScript("dml/" + s + ".sql"); + } + for (String s : new String[] { "any", "array_agg", "avg", "bit_and_agg", "bit_or_agg", "bit_xor_agg", + "corr", + "count", + "covar_pop", "covar_samp", + "envelope", "every", "histogram", + "json_arrayagg", "json_objectagg", + "listagg", "max", "min", "mode", "percentile", "rank", + "regr_avgx", "regr_avgy", "regr_count", "regr_intercept", "regr_r2", "regr_slope", + "regr_sxx", "regr_sxy", "regr_syy", + "stddev_pop", "stddev_samp", "sum", "var_pop", "var_samp" }) { + testScript("functions/aggregate/" + s + ".sql"); + } + for (String s : new String[] { "json_array", "json_object" }) { + testScript("functions/json/" + s + ".sql"); + } + for (String s : new String[] { "abs", "acos", "asin", "atan", "atan2", + "bitand", "bitcount", "bitget", "bitnot", "bitor", "bitxor", "ceil", "compress", + "cos", "cosh", "cot", "decrypt", "degrees", "encrypt", "exp", + "expand", "floor", "hash", "length", "log", "lshift", "mod", "ora-hash", "pi", + "power", "radians", "rand", "random-uuid", "rotate", "round", + "roundmagic", "rshift", "secure-rand", "sign", "sin", "sinh", "sqrt", + "tan", "tanh", "truncate", "zero" }) { + testScript("functions/numeric/" + s + ".sql"); + } + for (String s : new String[] { "array-to-string", + "ascii", "bit-length", "char", "concat", + "concat-ws", "difference", "hextoraw", "insert", + "left", "length", "locate", "lower", "lpad", "ltrim", + "octet-length", "quote_ident", "rawtohex", "regexp-like", + "regex-replace", "regexp-substr", "repeat", "replace", "right", "rpad", "rtrim", + "soundex", "space", "stringdecode", "stringencode", + "stringtoutf8", "substring", "to-char", "translate", "trim", + "upper", "utf8tostring", "xmlattr", "xmlcdata", "xmlcomment", + "xmlnode", "xmlstartdoc", "xmltext" }) { + testScript("functions/string/" + s + ".sql"); + } + for (String s : new String[] { "array-cat", "array-contains", "array-get", + "array-slice", "autocommit", "cancel-session", "casewhen", + "cardinality", "cast", "coalesce", "convert", "csvread", "csvwrite", "current_catalog", + "current_schema", "current_user", "currval", "data_type_sql", + "database-path", "db_object", "decode", "disk-space-used", + "file-read", "file-write", "greatest", "h2version", "identity", + "ifnull", "last-insert-id", "least", "link-schema", "lock-mode", "lock-timeout", + "memory-free", "memory-used", "nextval", "nullif", "nvl2", + "readonly", "rownum", "session-id", + "table", "transaction-id", "trim_array", "truncate-value", "unnest" }) { + testScript("functions/system/" + s + ".sql"); + } + for (String s : new String[] { "current_date", "current_timestamp", + "current-time", "dateadd", "datediff", "dayname", + "day-of-month", "day-of-week", "day-of-year", "extract", + "formatdatetime", "hour", "minute", "month", "monthname", + "parsedatetime", "quarter", "second", "truncate", "week", "year", "date_trunc" }) { + testScript("functions/timeanddate/" + s + ".sql"); + } + for (String s : new String[] { "lead", "nth_value", "ntile", "ratio_to_report", "row_number" }) { + testScript("functions/window/" + s + ".sql"); + } + for (String s : new String[] { "at-time-zone", "boolean-test", "case", "concatenation", "conditions", + "data-change-delta-table", "field-reference", "help", "sequence", "set" }) { + testScript("other/" + s + ".sql"); + } + for (String s : new String[] { "comments", "identifiers" }) { + testScript("parser/" + s + ".sql"); + } + for (String s : new String[] { "between", "distinct", "in", "like", "null", "type", "unique" }) { + testScript("predicates/" + s + ".sql"); + } + for (String s : new String[] { "derived-column-names", "distinct", "joins", "query-optimisations", "select", + "table", "values", "window" }) { + testScript("queries/" + s + ".sql"); + } + testScript("other/two_phase_commit.sql"); + testScript("other/unique_include.sql"); + + deleteDb("script"); + System.out.flush(); + if (foundErrors) { + throw new Exception("errors in script found"); + } + } + + private void testScript(String scriptFileName) throws Exception { + deleteDb("script"); + + // Reset all the state in case there is anything left over from the previous file + // we processed. + conn = null; + stat = null; + fileName = null; + in = null; + out = null; + result.clear(); + putBack.clear(); + + String outFile; + if (FIX_OUTPUT) { + outFile = scriptFileName; + int idx = outFile.lastIndexOf('/'); + if (idx >= 0) { + outFile = outFile.substring(idx + 1); + } + } else { + outFile = "test.out.txt"; + } + conn = getConnection("script"); + stat = conn.createStatement(); + out = new PrintStream(new FileOutputStream(outFile)); + testFile(BASE_DIR + scriptFileName); + conn.close(); + out.close(); + if (FIX_OUTPUT) { + File file = new File(outFile); + // If there are two trailing newline characters remove one + try (RandomAccessFile r = new RandomAccessFile(file, "rw")) { + byte[] separator = System.lineSeparator().getBytes(StandardCharsets.ISO_8859_1); + int separatorLength = separator.length; + long length = r.length() - (separatorLength * 2); + truncate: if (length >= 0) { + r.seek(length); + for (int i = 0; i < 2; i++) { + for (int j = 0; j < separatorLength; j++) { + if (r.readByte() != separator[j]) { + break truncate; + } + } + } + r.setLength(length + separatorLength); + } + } + file.renameTo(new File("h2/src/test/org/h2/test/scripts/" + scriptFileName)); + return; + } + } + + private String readLine() throws IOException { + String s = putBack.pollFirst(); + return s != null ? s : readNextLine(); + } + + private String readNextLine() throws IOException { + String s; + boolean comment = false; + while ((s = in.readLine()) != null) { + if (s.startsWith("--")) { + write(s); + comment = true; + continue; + } + if (!FIX_OUTPUT) { + s = s.trim(); + } + if (!s.isEmpty()) { + break; + } + if (comment) { + write(""); + comment = false; + } + } + return s; + } + + private void putBack(String line) { + putBack.addLast(line); + } + + private void testFile(String inFile) throws Exception { + InputStream is = getClass().getClassLoader().getResourceAsStream(inFile); + if (is == null) { + throw new IOException("could not find " + inFile); + } + fileName = inFile; + in = new LineNumberReader(new InputStreamReader(is, StandardCharsets.UTF_8)); + StringBuilder buff = new StringBuilder(); + boolean allowReconnect = true; + for (String sql; (sql = readLine()) != null;) { + if (sql.startsWith("--")) { + write(sql); + } else if (sql.startsWith(">")) { + addWriteResultError("", sql); + } else if (sql.endsWith(";")) { + write(sql); + buff.append(sql, 0, sql.length() - 1); + sql = buff.toString(); + buff.setLength(0); + process(sql, allowReconnect); + } else if (sql.startsWith("@")) { + if (buff.length() > 0) { + addWriteResultError("", sql); + } else { + switch (sql) { + case "@reconnect": + write(sql); + write(""); + if (!config.memory) { + reconnect(conn.getAutoCommit()); + } + break; + case "@reconnect on": + write(sql); + write(""); + allowReconnect = true; + break; + case "@reconnect off": + write(sql); + write(""); + allowReconnect = false; + break; + case "@autocommit on": + conn.setAutoCommit(true); + break; + case "@autocommit off": + conn.setAutoCommit(false); + break; + default: + addWriteResultError("", sql); + } + } + } else { + write(sql); + buff.append(sql); + buff.append('\n'); + } + } + } + + private boolean containsTempTables() throws SQLException { + ResultSet rs = conn.getMetaData().getTables(null, null, null, + new String[] { "TABLE" }); + while (rs.next()) { + String sql = rs.getString("SQL"); + if (sql != null) { + if (sql.contains("TEMPORARY")) { + return true; + } + } + } + return false; + } + + private void process(String sql, boolean allowReconnect) throws Exception { + if (allowReconnect && reconnectOften) { + if (!containsTempTables() + && ((JdbcConnection) conn).getMode().getEnum() == ModeEnum.REGULAR + && conn.getSchema().equals("PUBLIC")) { + boolean autocommit = conn.getAutoCommit(); + if (autocommit && random.nextInt(10) < 1) { + // reconnect 10% of the time + reconnect(autocommit); + } + } + } + if (statements != null) { + statements.add(sql); + } + if (!hasParameters(sql)) { + processStatement(sql); + } else { + String param = readLine(); + write(param); + if (!param.equals("{")) { + throw new AssertionError("expected '{', got " + param + " in " + sql); + } + try { + PreparedStatement prep = conn.prepareStatement(sql); + int count = 0; + while (true) { + param = readLine(); + write(param); + if (param.startsWith("}")) { + break; + } + count += processPrepared(sql, prep, param); + } + writeResult(sql, "update count: " + count, null); + } catch (SQLException e) { + writeException(sql, e); + } + } + write(""); + } + + private static boolean hasParameters(String sql) { + int index = 0; + for (;;) { + index = sql.indexOf('?', index); + if (index < 0) { + return false; + } + int length = sql.length(); + if (++index == length || sql.charAt(index) != '?') { + return true; + } + index++; + } + } + + private void reconnect(boolean autocommit) throws SQLException { + conn.close(); + conn = getConnection("script"); + conn.setAutoCommit(autocommit); + stat = conn.createStatement(); + } + + private static void setParameter(PreparedStatement prep, int i, String param) + throws SQLException { + if (param.equalsIgnoreCase("null")) { + param = null; + } + prep.setString(i, param); + } + + private int processPrepared(String sql, PreparedStatement prep, String param) + throws Exception { + try { + StringBuilder buff = new StringBuilder(); + int index = 0; + for (int i = 0; i < param.length(); i++) { + char c = param.charAt(i); + if (c == ',') { + setParameter(prep, ++index, buff.toString()); + buff.setLength(0); + } else if (c == '"') { + while (true) { + c = param.charAt(++i); + if (c == '"') { + break; + } + buff.append(c); + } + } else if (c > ' ') { + buff.append(c); + } + } + if (buff.length() > 0) { + setParameter(prep, ++index, buff.toString()); + } + if (prep.execute()) { + writeResultSet(sql, prep.getResultSet()); + return 0; + } + return prep.getUpdateCount(); + } catch (SQLException e) { + writeException(sql, e); + return 0; + } + } + + private int processStatement(String sql) throws Exception { + try { + boolean res; + Statement s; + if (/* TestScript */ CHECK_ORDERING || /* TestAll */ config.memory && !config.lazy && !config.networked) { + PreparedStatement prep = conn.prepareStatement(sql); + res = prep.execute(); + s = prep; + } else { + res = stat.execute(sql); + s = stat; + } + if (res) { + writeResultSet(sql, s.getResultSet()); + } else { + int count = s.getUpdateCount(); + writeResult(sql, count < 1 ? "ok" : "update count: " + count, null); + } + } catch (SQLException e) { + writeException(sql, e); + } + return 0; + } + + private static String formatString(String s) { + if (s == null) { + return "null"; + } + s = StringUtils.replaceAll(s, "\r\n", "\n"); + s = s.replace('\n', ' '); + s = StringUtils.replaceAll(s, " ", " "); + while (true) { + String s2 = StringUtils.replaceAll(s, " ", " "); + if (s2.length() == s.length()) { + break; + } + s = s2; + } + return s; + } + + private static String formatBinary(byte[] b) { + if (b == null) { + return "null"; + } + return StringUtils.convertBytesToHex(new StringBuilder("X'"), b).append('\'').toString(); + } + + private void writeResultSet(String sql, ResultSet rs) throws Exception { + ResultSetMetaData meta = rs.getMetaData(); + int len = meta.getColumnCount(); + int[] max = new int[len]; + result.clear(); + while (rs.next()) { + String[] row = new String[len]; + for (int i = 0; i < len; i++) { + String data = readValue(rs, meta, i + 1); + if (max[i] < data.length()) { + max[i] = data.length(); + } + row[i] = data; + } + result.add(row); + } + String[] head = new String[len]; + for (int i = 0; i < len; i++) { + String label = formatString(meta.getColumnLabel(i + 1)); + if (max[i] < label.length()) { + max[i] = label.length(); + } + head[i] = label; + } + Boolean gotOrdered = null; + Statement st = rs.getStatement(); + if (st instanceof JdbcPreparedStatement) { + CommandInterface ci = (CommandInterface) COMMAND.get(st); + if (ci instanceof CommandContainer) { + Prepared p = (Prepared) PREPARED.get(ci); + if (p instanceof Query) { + gotOrdered = ((Query) p).hasOrder(); + } else if (p instanceof ScriptCommand) { + gotOrdered = true; + } + } + } + rs.close(); + String line = readLine(); + putBack(line); + if (line != null && line.startsWith(">> ")) { + switch (result.size()) { + case 0: + writeResult(sql, "", null, ">> "); + return; + case 1: + String[] row = result.get(0); + if (row.length == 1) { + writeResult(sql, row[0], null, ">> "); + } else { + writeResult(sql, "", null, ">> "); + } + return; + default: + writeResult(sql, "<" + result.size() + " rows>", null, ">> "); + return; + } + } + Boolean ordered; + for (;;) { + line = readNextLine(); + if (line == null) { + addWriteResultError("", ""); + return; + } + putBack(line); + if (line.startsWith("> rows: ")) { + ordered = false; + break; + } else if (line.startsWith("> rows (ordered): ")) { + ordered = true; + break; + } else if (line.startsWith("> rows (partially ordered): ")) { + ordered = null; + break; + } + } + if (gotOrdered != null) { + if (ordered == null || ordered) { + if (!gotOrdered) { + addWriteResultError("", ""); + } + } else { + if (gotOrdered) { + addWriteResultError("", ""); + } + } + } + writeResult(sql, format(head, max), null); + writeResult(sql, format(null, max), null); + String[] array = new String[result.size()]; + for (int i = 0; i < result.size(); i++) { + array[i] = format(result.get(i), max); + } + if (!Boolean.TRUE.equals(ordered)) { + sort(array); + } + int i = 0; + for (; i < array.length; i++) { + writeResult(sql, array[i], null); + } + writeResult(sql, + (ordered != null ? ordered ? "rows (ordered): " : "rows: " : "rows (partially ordered): ") + i, + null); + } + + private static String readValue(ResultSet rs, ResultSetMetaData meta, int column) throws SQLException { + return DataType.isBinaryColumn(meta, column) ? formatBinary(rs.getBytes(column)) + : formatString(rs.getString(column)); + } + + private static String format(String[] row, int[] max) { + int length = max.length; + StringBuilder buff = new StringBuilder(); + for (int i = 0; i < length; i++) { + if (i > 0) { + buff.append(' '); + } + if (row == null) { + for (int j = 0; j < max[i]; j++) { + buff.append('-'); + } + } else { + int len = row[i].length(); + buff.append(row[i]); + if (i < length - 1) { + for (int j = len; j < max[i]; j++) { + buff.append(' '); + } + } + } + } + return buff.toString(); + } + + /** Convert the error code to a symbolic name from ErrorCode. */ + private static final Map ERROR_CODE_TO_NAME = new HashMap<>(256); + static { + try { + for (Field field : ErrorCode.class.getDeclaredFields()) { + if (field.getModifiers() == (Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL) + && field.getAnnotation(Deprecated.class) == null) { + ERROR_CODE_TO_NAME.put(field.getInt(null), field.getName()); + } + } + } catch (IllegalAccessException ex) { + throw new RuntimeException(ex); + } + } + + private void writeException(String sql, SQLException ex) throws Exception { + writeResult(sql, "exception " + ERROR_CODE_TO_NAME.get(ex.getErrorCode()), ex); + } + + private void writeResult(String sql, String s, SQLException ex) throws Exception { + writeResult(sql, s, ex, "> "); + } + + private void writeResult(String sql, String s, SQLException ex, String prefix) throws Exception { + assertKnownException(sql, ex); + s = (prefix + s).trim(); + String compare = readLine(); + if (compare != null && compare.startsWith(">")) { + if (!compare.equals(s)) { + if (reconnectOften && sql.toUpperCase().startsWith("EXPLAIN")) { + return; + } + addWriteResultError(compare, s); + if (ex != null) { + TestBase.logError("script", ex); + } + if (failFast) { + conn.close(); + System.exit(1); + } + } + } else { + addWriteResultError("", s); + if (compare != null) { + putBack(compare); + } + } + write(s); + } + + private void addWriteResultError(String expected, String got) { + foundErrors = true; + final String msg = fileName + '\n' + // + "line: " + in.getLineNumber() + '\n' + // + "exp: " + expected + '\n' + // + "got: " + got + '\n'; + TestBase.logErrorMessage(msg); + } + + private void write(String s) { + out.println(s); + } + + private static void sort(String[] a) { + for (int i = 1, j, len = a.length; i < len; i++) { + String t = a[i]; + for (j = i - 1; j >= 0 && t.compareTo(a[j]) < 0; j--) { + a[j + 1] = a[j]; + } + a[j + 1] = t; + } + } + +} diff --git a/h2/src/test/org/h2/test/scripts/Trigger1.java b/h2/src/test/org/h2/test/scripts/Trigger1.java new file mode 100644 index 0000000..b110511 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/Trigger1.java @@ -0,0 +1,25 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.scripts; + +import java.sql.Connection; +import java.sql.SQLException; + +import org.h2.api.Trigger; + +/** + * A trigger for tests. + */ +public class Trigger1 implements Trigger { + + @Override + public void fire(Connection conn, Object[] oldRow, Object[] newRow) throws SQLException { + if (newRow != null) { + newRow[2] = ((int) newRow[2]) * 10; + } + } + +} diff --git a/h2/src/test/org/h2/test/scripts/Trigger2.java b/h2/src/test/org/h2/test/scripts/Trigger2.java new file mode 100644 index 0000000..ff77333 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/Trigger2.java @@ -0,0 +1,59 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.scripts; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.h2.api.Trigger; + +/** + * A trigger for tests. + */ +public class Trigger2 implements Trigger { + + @Override + public void fire(Connection conn, Object[] oldRow, Object[] newRow) throws SQLException { + if (oldRow == null && newRow != null) { + Long id = (Long) newRow[0]; + PreparedStatement prep; + int i = 0; + if (id == null) { + prep = conn.prepareStatement("SELECT * FROM FINAL TABLE (INSERT INTO TEST VALUES (DEFAULT, ?, ?))"); + } else { + prep = conn.prepareStatement("SELECT * FROM FINAL TABLE (INSERT INTO TEST VALUES (?, ?, ?))"); + prep.setLong(++i, id); + } + prep.setInt(++i, (int) newRow[1]); + prep.setInt(++i, (int) newRow[2]); + executeAndReadFinalTable(prep, newRow); + } else if (oldRow != null && newRow != null) { + PreparedStatement prep = conn.prepareStatement( + "SELECT * FROM FINAL TABLE (UPDATE TEST SET (ID, A, B) = (?, ?, ?) WHERE ID = ?)"); + prep.setLong(1, (long) newRow[0]); + prep.setInt(2, (int) newRow[1]); + prep.setInt(3, (int) newRow[2]); + prep.setLong(4, (long) oldRow[0]); + executeAndReadFinalTable(prep, newRow); + } else if (oldRow != null && newRow == null) { + PreparedStatement prep = conn.prepareStatement("DELETE FROM TEST WHERE ID = ?"); + prep.setLong(1, (long) oldRow[0]); + prep.executeUpdate(); + } + } + + private static void executeAndReadFinalTable(PreparedStatement prep, Object[] newRow) throws SQLException { + try (ResultSet rs = prep.executeQuery()) { + rs.next(); + newRow[0] = rs.getLong(1); + newRow[1] = rs.getInt(2); + newRow[2] = rs.getInt(3); + } + } + +} diff --git a/h2/src/test/org/h2/test/scripts/altertable-fk.sql b/h2/src/test/org/h2/test/scripts/altertable-fk.sql new file mode 100644 index 0000000..73adb9d --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/altertable-fk.sql @@ -0,0 +1,26 @@ +-- 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 +-- + +-- Check that constraints are properly renamed when we rename a column. + +CREATE TABLE user_group (ID decimal PRIMARY KEY NOT NULL); +> ok + +CREATE TABLE login_message (ID decimal PRIMARY KEY NOT NULL, user_group_id decimal); +> ok + +ALTER TABLE login_message ADD CONSTRAINT FK_LOGIN_MESSAGE +FOREIGN KEY (user_group_id) +REFERENCES user_group(id) ON DELETE CASCADE; +> ok + +ALTER TABLE login_message ALTER COLUMN user_group_id RENAME TO user_group_id2; +> ok + +INSERT INTO user_group (ID) VALUES (1); +> update count: 1 + +DELETE FROM user_group; +> update count: 1 diff --git a/h2/src/test/org/h2/test/scripts/altertable-index-reuse.sql b/h2/src/test/org/h2/test/scripts/altertable-index-reuse.sql new file mode 100644 index 0000000..f93f90e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/altertable-index-reuse.sql @@ -0,0 +1,33 @@ +-- 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 +-- + +CREATE TABLE "domains" ("id" bigint NOT NULL auto_increment PRIMARY KEY); +> ok + +CREATE TABLE "users" ("id" bigint NOT NULL auto_increment PRIMARY KEY,"username" varchar_ignorecase(255),"domain" bigint,"desc" varchar_ignorecase(255)); +> ok + +-- adds constraint on (domain,username) and generates unique index domainusername_key_INDEX_xxx +ALTER TABLE "users" ADD CONSTRAINT "domainusername_key" UNIQUE ("domain","username"); +> ok + +-- adds foreign key on domain - if domainusername_key didn't exist it would create unique index on domain, but it reuses the existing index +ALTER TABLE "users" ADD CONSTRAINT "udomain_fkey" FOREIGN KEY ("domain") REFERENCES "domains"("id") ON DELETE RESTRICT; +> ok + +-- now we drop the domainusername_key, but domainusername_key_INDEX_xxx is used by udomain_fkey and was not being dropped +-- this was an issue, because it's a unique index and still enforcing constraint on (domain,username) +ALTER TABLE "users" DROP CONSTRAINT "domainusername_key"; +> ok + +insert into "domains" ("id") VALUES (1); +> update count: 1 + +insert into "users" ("username","domain","desc") VALUES ('test',1,'first user'); +> update count: 1 + +-- should work,because we dropped domainusername_key, but failed: Unique index or primary key violation +INSERT INTO "users" ("username","domain","desc") VALUES ('test',1,'second user'); +> update count: 1 diff --git a/h2/src/test/org/h2/test/scripts/compatibility/add_months.sql b/h2/src/test/org/h2/test/scripts/compatibility/add_months.sql new file mode 100644 index 0000000..69e7100 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/compatibility/add_months.sql @@ -0,0 +1,23 @@ +-- 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 +-- + +SET MODE Oracle; +> ok + +-- 01-Aug-03 + 3 months = 01-Nov-03 +SELECT ADD_MONTHS('2003-08-01', 3); +>> 2003-11-01 00:00:00 + +-- 31-Jan-03 + 1 month = 28-Feb-2003 +SELECT ADD_MONTHS('2003-01-31', 1); +>> 2003-02-28 00:00:00 + +-- 21-Aug-2003 - 3 months = 21-May-2003 +SELECT ADD_MONTHS('2003-08-21', -3); +>> 2003-05-21 00:00:00 + +-- 21-Aug-2003 00:00:00.333 - 3 months = 21-May-2003 00:00:00.333 +SELECT ADD_MONTHS('2003-08-21 00:00:00.333', -3); +>> 2003-05-21 00:00:00.333 diff --git a/h2/src/test/org/h2/test/scripts/compatibility/compatibility.sql b/h2/src/test/org/h2/test/scripts/compatibility/compatibility.sql new file mode 100644 index 0000000..97db29e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/compatibility/compatibility.sql @@ -0,0 +1,757 @@ +-- Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, +-- and the EPL 1.0 (https://h2database.com/html/license.html). +-- Initial Developer: H2 Group +-- + +-- EXEC and EXECUTE in MSSQLServer mode + +CREATE ALIAS MY_NO_ARG AS 'int f() { return 1; }'; +> ok + +CREATE ALIAS MY_SQRT FOR "java.lang.Math.sqrt"; +> ok + +CREATE ALIAS MY_REMAINDER FOR "java.lang.Math.IEEEremainder"; +> ok + +EXEC MY_SQRT 4; +> exception SYNTAX_ERROR_2 + +-- PostgreSQL-style EXECUTE doesn't work with MSSQLServer-style arguments +EXECUTE MY_SQRT 4; +> exception FUNCTION_ALIAS_NOT_FOUND_1 + +SET MODE MSSQLServer; +> ok + +-- PostgreSQL-style PREPARE is not available in MSSQLServer mode +PREPARE TEST AS SELECT 1; +> exception SYNTAX_ERROR_2 + +-- PostgreSQL-style DEALLOCATE is not available in MSSQLServer mode +DEALLOCATE TEST; +> exception SYNTAX_ERROR_2 + +EXEC MY_NO_ARG; +>> 1 + +EXEC MY_SQRT 4; +>> 2.0 + +EXEC MY_REMAINDER 4, 3; +>> 1.0 + +EXECUTE MY_SQRT 4; +>> 2.0 + +EXEC PUBLIC.MY_SQRT 4; +>> 2.0 + +EXEC SCRIPT.PUBLIC.MY_SQRT 4; +>> 2.0 + +EXEC UNKNOWN_PROCEDURE; +> exception FUNCTION_NOT_FOUND_1 + +EXEC UNKNOWN_SCHEMA.MY_SQRT 4; +> exception SCHEMA_NOT_FOUND_1 + +EXEC UNKNOWN_DATABASE.PUBLIC.MY_SQRT 4; +> exception DATABASE_NOT_FOUND_1 + +SET MODE Regular; +> ok + +DROP ALIAS MY_NO_ARG; +> ok + +DROP ALIAS MY_SQRT; +> ok + +DROP ALIAS MY_REMAINDER; +> ok + +-- UPDATE TOP (n) in MSSQLServer mode + +CREATE TABLE TEST(A INT, B INT) AS VALUES (1, 2), (3, 4), (5, 6); +> ok + +UPDATE TOP (1) TEST SET B = 10; +> exception TABLE_OR_VIEW_NOT_FOUND_1 + +SET MODE MSSQLServer; +> ok + +UPDATE TOP (1) TEST SET B = 10; +> update count: 1 + +SELECT COUNT(*) FILTER (WHERE B = 10) N, COUNT(*) FILTER (WHERE B <> 10) O FROM TEST; +> N O +> - - +> 1 2 +> rows: 1 + +UPDATE TEST SET B = 10 WHERE B <> 10; +> update count: 2 + +UPDATE TOP (1) TEST SET B = 10 LIMIT 1; +> exception SYNTAX_ERROR_1 + +SET MODE Regular; +> ok + +DROP TABLE TEST; +> ok + +SET MODE MySQL; +> ok + +CREATE TABLE A (A INT PRIMARY KEY, X INT); +> ok + +ALTER TABLE A ADD INDEX A_IDX(X); +> ok + +ALTER TABLE A DROP INDEX A_IDX_1; +> exception CONSTRAINT_NOT_FOUND_1 + +ALTER TABLE A DROP INDEX IF EXISTS A_IDX_1; +> ok + +ALTER TABLE A DROP INDEX IF EXISTS A_IDX; +> ok + +ALTER TABLE A DROP INDEX A_IDX; +> exception CONSTRAINT_NOT_FOUND_1 + +CREATE TABLE B (B INT PRIMARY KEY, A INT); +> ok + +ALTER TABLE B ADD CONSTRAINT B_FK FOREIGN KEY (A) REFERENCES A(A); +> ok + +ALTER TABLE B DROP FOREIGN KEY B_FK_1; +> exception CONSTRAINT_NOT_FOUND_1 + +-- MariaDB compatibility +ALTER TABLE B DROP FOREIGN KEY IF EXISTS B_FK_1; +> ok + +ALTER TABLE B DROP FOREIGN KEY IF EXISTS B_FK; +> ok + +ALTER TABLE B DROP FOREIGN KEY B_FK; +> exception CONSTRAINT_NOT_FOUND_1 + +DROP TABLE A, B; +> ok + +SET MODE Regular; +> ok + +-- PostgreSQL-style CREATE INDEX ... USING +CREATE TABLE TEST(B1 INT, B2 INT, H INT, R GEOMETRY, T INT); +> ok + +CREATE INDEX TEST_BTREE_IDX ON TEST USING BTREE(B1, B2); +> ok + +CREATE INDEX TEST_HASH_IDX ON TEST USING HASH(H); +> ok + +CREATE INDEX TEST_RTREE_IDX ON TEST USING RTREE(R); +> ok + +SELECT INDEX_NAME, INDEX_TYPE_NAME FROM INFORMATION_SCHEMA.INDEXES WHERE TABLE_NAME = 'TEST'; +> INDEX_NAME INDEX_TYPE_NAME +> -------------- --------------- +> TEST_BTREE_IDX INDEX +> TEST_HASH_IDX HASH INDEX +> TEST_RTREE_IDX SPATIAL INDEX +> rows: 3 + +SELECT INDEX_NAME, COLUMN_NAME, ORDINAL_POSITION FROM INFORMATION_SCHEMA.INDEX_COLUMNS WHERE TABLE_NAME = 'TEST'; +> INDEX_NAME COLUMN_NAME ORDINAL_POSITION +> -------------- ----------- ---------------- +> TEST_BTREE_IDX B1 1 +> TEST_BTREE_IDX B2 2 +> TEST_HASH_IDX H 1 +> TEST_RTREE_IDX R 1 +> rows: 4 + +CREATE HASH INDEX TEST_BAD_IDX ON TEST USING HASH(T); +> exception SYNTAX_ERROR_2 + +CREATE SPATIAL INDEX TEST_BAD_IDX ON TEST USING RTREE(T); +> exception SYNTAX_ERROR_2 + +DROP TABLE TEST; +> ok + +SET MODE MySQL; +> ok + +CREATE TABLE test (id int(25) NOT NULL auto_increment, name varchar NOT NULL, PRIMARY KEY (id,name)); +> ok + +drop table test; +> ok + +create memory table word(word_id integer, name varchar); +> ok + +alter table word alter column word_id integer(10) auto_increment; +> ok + +insert into word(name) values('Hello'); +> update count: 1 + +alter table word alter column word_id restart with 30872; +> ok + +insert into word(name) values('World'); +> update count: 1 + +select * from word; +> WORD_ID NAME +> ------- ----- +> 1 Hello +> 30872 World +> rows: 2 + +drop table word; +> ok + +CREATE MEMORY TABLE TEST1(ID BIGINT(20) NOT NULL PRIMARY KEY COMMENT 'COMMENT1', FIELD_NAME VARCHAR(100) NOT NULL COMMENT 'COMMENT2'); +> ok + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION TABLE TEST1; +> SCRIPT +> ------------------------------------------------------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST1"( "ID" BIGINT COMMENT 'COMMENT1' NOT NULL, "FIELD_NAME" CHARACTER VARYING(100) COMMENT 'COMMENT2' NOT NULL ); +> ALTER TABLE "PUBLIC"."TEST1" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_4" PRIMARY KEY("ID"); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST1; +> rows (ordered): 4 + +CREATE TABLE TEST2(ID BIGINT(20) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'COMMENT1', FIELD_NAME VARCHAR(100) NOT NULL COMMENT 'COMMENT2' COMMENT 'COMMENT3'); +> exception SYNTAX_ERROR_2 + +CREATE TABLE TEST3(ID BIGINT(20) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'COMMENT1' CHECK(ID > 0), FIELD_NAME VARCHAR(100) NOT NULL COMMENT 'COMMENT2'); +> ok + +CREATE TABLE TEST4(ID BIGINT(20) NOT NULL AUTO_INCREMENT PRIMARY KEY CHECK(ID > 0) COMMENT 'COMMENT1', FIELD_NAME VARCHAR(100) NOT NULL COMMENT 'COMMENT2'); +> ok + +DROP TABLE TEST1, TEST3, TEST4; +> ok + +SET MODE Regular; +> ok + +-- Keywords as identifiers + +CREATE TABLE TEST(KEY INT, VALUE INT); +> exception SYNTAX_ERROR_2 + +@reconnect off + +SET NON_KEYWORDS KEY, VALUE, AS, SET, DAY; +> ok + +CREATE TABLE TEST(KEY INT, VALUE INT, AS INT, SET INT, DAY INT); +> ok + +INSERT INTO TEST(KEY, VALUE, AS, SET, DAY) VALUES (1, 2, 3, 4, 5), (6, 7, 8, 9, 10); +> update count: 2 + +SELECT KEY, VALUE, AS, SET, DAY FROM TEST WHERE KEY <> 6 AND VALUE <> 7 AND AS <> 8 AND SET <> 9 AND DAY <> 10; +> KEY VALUE AS SET DAY +> --- ----- -- --- --- +> 1 2 3 4 5 +> rows: 1 + +DROP TABLE TEST; +> ok + +SELECT SETTING_VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME = 'NON_KEYWORDS'; +>> AS,DAY,KEY,SET,VALUE + +SET NON_KEYWORDS; +> ok + +@reconnect on + +SELECT COUNT(*) FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME = 'NON_KEYWORDS'; +>> 0 + +CREATE TABLE TEST(KEY INT, VALUE INT); +> exception SYNTAX_ERROR_2 + +CREATE TABLE TEST1(C VARCHAR(1 CHAR)); +> exception SYNTAX_ERROR_2 + +CREATE TABLE TEST2(C VARCHAR(1 BYTE)); +> exception SYNTAX_ERROR_2 + +CREATE TABLE TEST3(C BINARY_FLOAT); +> exception UNKNOWN_DATA_TYPE_1 + +CREATE TABLE TEST4(C BINARY_DOUBLE); +> exception UNKNOWN_DATA_TYPE_1 + +SET MODE Oracle; +> ok + +CREATE TABLE TEST1(C VARCHAR(1 CHAR)); +> ok + +CREATE TABLE TEST2(C VARCHAR(1 BYTE)); +> ok + +CREATE TABLE TEST3(C BINARY_FLOAT); +> ok + +CREATE TABLE TEST4(C BINARY_DOUBLE); +> ok + +SELECT TABLE_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME IN ('TEST3', 'TEST4'); +> TABLE_NAME DATA_TYPE +> ---------- ---------------- +> TEST3 REAL +> TEST4 DOUBLE PRECISION +> rows: 2 + +DROP TABLE TEST1, TEST2, TEST3, TEST4; +> ok + +SET MODE PostgreSQL; +> ok + +EXPLAIN VALUES VERSION(); +>> VALUES (VERSION()) + +SET MODE Regular; +> ok + +CREATE TABLE TEST(A INT) AS VALUES 0; +> ok + +SELECT SIN(A), A+1, A FROM TEST; +> SIN(A) A + 1 A +> ------ ----- - +> 0.0 1 0 +> rows: 1 + +CREATE VIEW V AS SELECT SIN(A), A+1, (((((A + 1) * A + 1) * A + 1) * A + 1) * A + 1) * A + 1 FROM TEST; +> ok + +TABLE V; +> SIN(A) A + 1 ((((((((((A + 1) * A) + 1) * A) + 1) * A) + 1) * A) + 1) * A) + 1 +> ------ ----- ----------------------------------------------------------------- +> 0.0 1 1 +> rows: 1 + +DROP VIEW V; +> ok + +CREATE VIEW V AS SELECT SIN(0), COS(0); +> ok + +TABLE V; +> 0.0 1.0 +> --- --- +> 0.0 1.0 +> rows: 1 + +DROP VIEW V; +> ok + +SET MODE DB2; +> ok + +SELECT SIN(A), A+1, A FROM TEST; +> 1 2 A +> --- - - +> 0.0 1 0 +> rows: 1 + +CREATE VIEW V AS SELECT SIN(A), A+1, (((((A + 1) * A + 1) * A + 1) * A + 1) * A + 1) * A + 1 FROM TEST; +> exception COLUMN_ALIAS_IS_NOT_SPECIFIED_1 + +SET MODE Derby; +> ok + +SELECT SIN(A), A+1, A FROM TEST; +> 1 2 A +> --- - - +> 0.0 1 0 +> rows: 1 + +CREATE VIEW V AS SELECT SIN(A), A+1, (((((A + 1) * A + 1) * A + 1) * A + 1) * A + 1) * A + 1 FROM TEST; +> exception COLUMN_ALIAS_IS_NOT_SPECIFIED_1 + +SET MODE MSSQLServer; +> ok + +SELECT SIN(A), A+1, A FROM TEST; +> A +> --- - - +> 0.0 1 0 +> rows: 1 + +CREATE VIEW V AS SELECT SIN(A), A+1, (((((A + 1) * A + 1) * A + 1) * A + 1) * A + 1) * A + 1 FROM TEST; +> exception COLUMN_ALIAS_IS_NOT_SPECIFIED_1 + +SET MODE HSQLDB; +> ok + +SELECT SIN(A), A+1, A FROM TEST; +> C1 C2 A +> --- -- - +> 0.0 1 0 +> rows: 1 + +CREATE VIEW V AS SELECT SIN(A), A+1, (((((A + 1) * A + 1) * A + 1) * A + 1) * A + 1) * A + 1 FROM TEST; +> ok + +TABLE V; +> C1 C2 C3 +> --- -- -- +> 0.0 1 1 +> rows: 1 + +DROP VIEW V; +> ok + +SET MODE MySQL; +> ok + +SELECT SIN(A), A+1, A FROM TEST; +> SIN(A) A + 1 A +> ------ ----- - +> 0.0 1 0 +> rows: 1 + +CREATE VIEW V AS SELECT SIN(A), A+1, (((((A + 1) * A + 1) * A + 1) * A + 1) * A + 1) * A + 1 FROM TEST; +> ok + +TABLE V; +> SIN(A) A + 1 Name_exp_3 +> ------ ----- ---------- +> 0.0 1 1 +> rows: 1 + +DROP VIEW V; +> ok + +CREATE VIEW V AS SELECT SIN(0), COS(0); +> ok + +TABLE V; +> SIN(0) COS(0) +> ------ ------ +> 0.0 1.0 +> rows: 1 + +DROP VIEW V; +> ok + +SET MODE Oracle; +> ok + +SELECT SIN(A), A+1, A FROM TEST; +> SIN(A) A + 1 A +> ------ ----- - +> 0.0 1 0 +> rows: 1 + +SET MODE PostgreSQL; +> ok + +SELECT SIN(A), A+1, A FROM TEST; +> sin ?column? A +> --- -------- - +> 0.0 1 0 +> rows: 1 + +CREATE VIEW V AS SELECT SIN(A), A+1, (((((A + 1) * A + 1) * A + 1) * A + 1) * A + 1) * A + 1 FROM TEST; +> exception DUPLICATE_COLUMN_NAME_1 + +CREATE VIEW V AS SELECT SIN(0), COS(0); +> ok + +TABLE V; +> sin cos +> --- --- +> 0.0 1.0 +> rows: 1 + +DROP VIEW V; +> ok + +SET MODE Regular; +> ok + +DROP TABLE TEST; +> ok + +--- sequence with manual value ------------------ + +SET MODE MySQL; +> ok + +CREATE TABLE TEST(ID bigint generated by default as identity (start with 1), name varchar); +> ok + +SET AUTOCOMMIT FALSE; +> ok + +insert into test(name) values('Hello'); +> update count: 1 + +select id from final table (insert into test(name) values('World')); +>> 2 + +select id from final table (insert into test(id, name) values(1234567890123456, 'World')); +>> 1234567890123456 + +select id from final table (insert into test(name) values('World')); +>> 1234567890123457 + +select * from test order by id; +> ID NAME +> ---------------- ----- +> 1 Hello +> 2 World +> 1234567890123456 World +> 1234567890123457 World +> rows (ordered): 4 + +SET AUTOCOMMIT TRUE; +> ok + +drop table if exists test; +> ok + +CREATE TABLE TEST(ID bigint generated by default as identity (start with 1), name varchar); +> ok + +SET AUTOCOMMIT FALSE; +> ok + +insert into test(name) values('Hello'); +> update count: 1 + +select id from final table (insert into test(name) values('World')); +>> 2 + +select id from final table (insert into test(id, name) values(1234567890123456, 'World')); +>> 1234567890123456 + +select id from final table (insert into test(name) values('World')); +>> 1234567890123457 + +select * from test order by id; +> ID NAME +> ---------------- ----- +> 1 Hello +> 2 World +> 1234567890123456 World +> 1234567890123457 World +> rows (ordered): 4 + +SET AUTOCOMMIT TRUE; +> ok + +drop table test; +> ok + +SET MODE PostgreSQL; +> ok + +-- To reset last identity +DROP ALL OBJECTS; +> ok + +SELECT LASTVAL(); +> exception CURRENT_SEQUENCE_VALUE_IS_NOT_DEFINED_IN_SESSION_1 + +CREATE SEQUENCE SEQ START WITH 100; +> ok + +SELECT NEXT VALUE FOR SEQ; +>> 100 + +SELECT LASTVAL(); +>> 100 + +DROP SEQUENCE SEQ; +> ok + +SET MODE MSSQLServer; +> ok + +-- To reset last identity +DROP ALL OBJECTS; +> ok + +SELECT SCOPE_IDENTITY(); +>> null + +CREATE TABLE TEST(ID BIGINT IDENTITY, V INT); +> ok + +INSERT INTO TEST(V) VALUES (10); +> update count: 1 + +SELECT SCOPE_IDENTITY(); +>> 1 + +DROP TABLE TEST; +> ok + +SET MODE DB2; +> ok + +-- To reset last identity +DROP ALL OBJECTS; +> ok + +SELECT IDENTITY_VAL_LOCAL(); +>> null + +CREATE TABLE TEST(ID BIGINT GENERATED BY DEFAULT AS IDENTITY, V INT); +> ok + +INSERT INTO TEST(V) VALUES 10; +> update count: 1 + +SELECT IDENTITY_VAL_LOCAL(); +>> 1 + +DROP TABLE TEST; +> ok + +SET MODE Derby; +> ok + +-- To reset last identity +DROP ALL OBJECTS; +> ok + +SELECT IDENTITY_VAL_LOCAL(); +>> null + +CREATE TABLE TEST(ID BIGINT GENERATED BY DEFAULT AS IDENTITY, V INT); +> ok + +INSERT INTO TEST(V) VALUES 10; +> update count: 1 + +SELECT IDENTITY_VAL_LOCAL(); +>> 1 + +DROP TABLE TEST; +> ok + +SET MODE Regular; +> ok + +SET MODE MSSQLServer; +> ok + +CREATE TABLE TEST(ID BIGINT NOT NULL IDENTITY(10, 5), NAME VARCHAR); +> ok + +INSERT INTO TEST(NAME) VALUES('Hello'), ('World'); +> update count: 2 + +SELECT * FROM TEST; +> ID NAME +> -- ----- +> 10 Hello +> 15 World +> rows: 2 + +DROP TABLE TEST; +> ok + +SET MODE PostgreSQL; +> ok + +SELECT TO_DATE('24-12-2025','DD-MM-YYYY'); +>> 2025-12-24 + +SET TIME ZONE 'UTC'; +> ok + +SELECT TO_TIMESTAMP('24-12-2025 14:13:12','DD-MM-YYYY HH24:MI:SS'); +>> 2025-12-24 14:13:12+00 + +SET TIME ZONE LOCAL; +> ok + +SET MODE Regular; +> ok + +SELECT 1 = TRUE; +> exception TYPES_ARE_NOT_COMPARABLE_2 + +SET MODE MySQL; +> ok + +SELECT 1 = TRUE; +>> TRUE + +SELECT TRUE = 0; +>> FALSE + +SELECT 1 > TRUE; +> exception TYPES_ARE_NOT_COMPARABLE_2 + +CREATE TABLE TEST(ID BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, B BOOLEAN, I INTEGER); +> ok + +CREATE INDEX TEST_B_IDX ON TEST(B); +> ok + +CREATE INDEX TEST_I_IDX ON TEST(I); +> ok + +INSERT INTO TEST(B, I) VALUES (TRUE, 1), (TRUE, 1), (FALSE, 0), (TRUE, 1), (UNKNOWN, NULL); +> update count: 5 + +SELECT * FROM TEST WHERE B = 1; +> ID B I +> -- ---- - +> 1 TRUE 1 +> 2 TRUE 1 +> 4 TRUE 1 +> rows: 3 + +EXPLAIN SELECT * FROM TEST WHERE B = 1; +>> SELECT "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."B", "PUBLIC"."TEST"."I" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ WHERE "B" = 1 + +SELECT * FROM TEST WHERE I = TRUE; +> ID B I +> -- ---- - +> 1 TRUE 1 +> 2 TRUE 1 +> 4 TRUE 1 +> rows: 3 + +EXPLAIN SELECT * FROM TEST WHERE I = TRUE; +>> SELECT "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."B", "PUBLIC"."TEST"."I" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_I_IDX: I = 1 */ WHERE "I" = 1 + +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/compatibility/group_by.sql b/h2/src/test/org/h2/test/scripts/compatibility/group_by.sql new file mode 100644 index 0000000..f156ea5 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/compatibility/group_by.sql @@ -0,0 +1,57 @@ +-- 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 +-- + +-- GROUP BY column index for MySQL/MariaDB/PostgreSQL compatibility mode + +CREATE TABLE MYTAB(X INT , Y INT, Z INT) AS VALUES (1,123,2), (1,456,2), (3,789,4); +> ok + +SET MODE MySQL; +> ok + +SELECT SUM(Y) AS S , X + Z FROM MYTAB GROUP BY 2; +> S X + Z +> --- ----- +> 579 3 +> 789 7 +> rows: 2 + +EXPLAIN SELECT SUM(Y) AS S , X + Z FROM MYTAB GROUP BY 2; +> PLAN +> ------------------------------------------------------------------------------------------------------- +> SELECT SUM("Y") AS "S", "X" + "Z" FROM "PUBLIC"."MYTAB" /* PUBLIC.MYTAB.tableScan */ GROUP BY "X" + "Z" +> rows: 1 + +SELECT SUM(Y) AS S , X + Z FROM MYTAB GROUP BY 3; +> exception GROUP_BY_NOT_IN_THE_RESULT + +SELECT MYTAB.*, SUM(Y) AS S FROM MYTAB GROUP BY 1; +> exception SYNTAX_ERROR_2 + +SET MODE MariaDB; +> ok + +SELECT SUM(Y) AS S , X + Z FROM MYTAB GROUP BY 2; +> S X + Z +> --- ----- +> 579 3 +> 789 7 +> rows: 2 + +SET MODE PostgreSQL; +> ok + +SELECT SUM(Y) AS S , X + Z FROM MYTAB GROUP BY 2; +> S ?column? +> --- -------- +> 579 3 +> 789 7 +> rows: 2 + +SET MODE Oracle; +> ok + +SELECT SUM(Y) AS S , X FROM MYTAB GROUP BY 2; +> exception MUST_GROUP_BY_COLUMN_1 diff --git a/h2/src/test/org/h2/test/scripts/compatibility/strict_and_legacy.sql b/h2/src/test/org/h2/test/scripts/compatibility/strict_and_legacy.sql new file mode 100644 index 0000000..7fbc831 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/compatibility/strict_and_legacy.sql @@ -0,0 +1,101 @@ +-- 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 +-- + +SET MODE STRICT; +> ok + +VALUES 1 IN (); +> exception SYNTAX_ERROR_2 + +SELECT TOP 1 * FROM (VALUES 1, 2); +> exception SYNTAX_ERROR_1 + +SELECT * FROM (VALUES 1, 2) LIMIT 1; +> exception SYNTAX_ERROR_1 + +CREATE TABLE TEST(ID IDENTITY); +> exception UNKNOWN_DATA_TYPE_1 + +CREATE TABLE TEST(ID BIGINT AUTO_INCREMENT); +> exception SYNTAX_ERROR_2 + +SET MODE LEGACY; +> ok + +CREATE TABLE TEST(ID BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, V INTEGER NOT NULL); +> ok + +INSERT INTO TEST(ID, V) VALUES (10, 15); +> update count: 1 + +INSERT INTO TEST(V) VALUES 20; +> update count: 1 + +TABLE TEST; +> ID V +> -- -- +> 10 15 +> 11 20 +> rows: 2 + +UPDATE TOP(1) TEST SET V = V + 1; +> update count: 1 + +TABLE TEST; +> ID V +> -- -- +> 10 16 +> 11 20 +> rows: 2 + +MERGE INTO TEST T USING (VALUES (10, 17), (11, 30)) I(ID, V) ON T.ID = I.ID +WHEN MATCHED THEN UPDATE SET V = I.V WHERE T.ID > 10; +> update count: 1 + +TABLE TEST; +> ID V +> -- -- +> 10 16 +> 11 30 +> rows: 2 + +CREATE TABLE T2(ID BIGINT PRIMARY KEY, V INT REFERENCES TEST(V)); +> ok + +DROP TABLE T2, TEST; +> ok + +CREATE TABLE TEST(ID BIGINT IDENTITY(1, 10)); +> ok + +DROP TABLE TEST; +> ok + +CREATE SEQUENCE SEQ; +> ok + +SELECT SEQ.NEXTVAL; +>> 1 + +SELECT SEQ.CURRVAL; +>> 1 + +DROP SEQUENCE SEQ; +> ok + +SELECT 1 = TRUE; +>> TRUE + +SET MODE STRICT; +> ok + +CREATE TABLE TEST(LIMIT INTEGER, MINUS INTEGER); +> ok + +DROP TABLE TEST; +> ok + +SET MODE REGULAR; +> ok diff --git a/h2/src/test/org/h2/test/scripts/datatypes/array.sql b/h2/src/test/org/h2/test/scripts/datatypes/array.sql new file mode 100644 index 0000000..f083ce9 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/array.sql @@ -0,0 +1,270 @@ +-- 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 +-- + +SELECT (10, 20, 30)[1]; +> exception INVALID_VALUE_2 + +SELECT ARRAY[]; +>> [] + +SELECT ARRAY[10]; +>> [10] + +SELECT ARRAY[10, 20, 30]; +>> [10, 20, 30] + +SELECT ARRAY[10, 20, 30][1]; +>> 10 + +SELECT ARRAY[10, 20, 30][3]; +>> 30 + +SELECT ARRAY[10, 20, 30][0]; +> exception ARRAY_ELEMENT_ERROR_2 + +SELECT ARRAY[10, 20, 30][4]; +> exception ARRAY_ELEMENT_ERROR_2 + +SELECT ARRAY[1, NULL] IS NOT DISTINCT FROM ARRAY[1, NULL]; +>> TRUE + +SELECT ARRAY[1, NULL] IS DISTINCT FROM ARRAY[1, NULL]; +>> FALSE + +SELECT ARRAY[1, NULL] = ARRAY[1, NULL]; +>> null + +SELECT ARRAY[1, NULL] <> ARRAY[1, NULL]; +>> null + +SELECT ARRAY[NULL] = ARRAY[NULL, NULL]; +>> FALSE + +select ARRAY[1, NULL, 2] = ARRAY[1, NULL, 1]; +>> FALSE + +select ARRAY[1, NULL, 2] <> ARRAY[1, NULL, 1]; +>> TRUE + +SELECT ARRAY[1, NULL] > ARRAY[1, NULL]; +>> null + +SELECT ARRAY[1, 2] > ARRAY[1, NULL]; +>> null + +SELECT ARRAY[1, 2, NULL] > ARRAY[1, 1, NULL]; +>> TRUE + +SELECT ARRAY[1, 1, NULL] > ARRAY[1, 2, NULL]; +>> FALSE + +SELECT ARRAY[1, 2, NULL] < ARRAY[1, 1, NULL]; +>> FALSE + +SELECT ARRAY[1, 1, NULL] <= ARRAY[1, 1, NULL]; +>> null + +SELECT ARRAY[1, NULL] IN (ARRAY[1, NULL]); +>> null + +CREATE TABLE TEST(A ARRAY); +> exception SYNTAX_ERROR_2 + +CREATE TABLE TEST(A INTEGER ARRAY); +> ok + +INSERT INTO TEST VALUES (ARRAY[1, NULL]), (ARRAY[1, 2]); +> update count: 2 + +SELECT ARRAY[1, 2] IN (SELECT A FROM TEST); +>> TRUE + +SELECT ROW (ARRAY[1, 2]) IN (SELECT A FROM TEST); +>> TRUE + +SELECT ARRAY[1, NULL] IN (SELECT A FROM TEST); +>> null + +SELECT ROW (ARRAY[1, NULL]) IN (SELECT A FROM TEST); +>> null + +SELECT A FROM TEST WHERE A = (1, 2); +> exception TYPES_ARE_NOT_COMPARABLE_2 + +DROP TABLE TEST; +> ok + +SELECT ARRAY[1, 2] || 3; +>> [1, 2, 3] + +SELECT 1 || ARRAY[2, 3]; +>> [1, 2, 3] + +SELECT ARRAY[1, 2] || ARRAY[3]; +>> [1, 2, 3] + +SELECT ARRAY[1, 2] || ARRAY[3, 4]; +>> [1, 2, 3, 4] + +SELECT ARRAY[1, 2] || NULL; +>> null + +SELECT NULL::INT ARRAY || ARRAY[2]; +>> null + +CREATE TABLE TEST(ID INT, A1 INT ARRAY, A2 INT ARRAY[2]); +> ok + +SELECT COLUMN_NAME, DATA_TYPE, MAXIMUM_CARDINALITY + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DATA_TYPE MAXIMUM_CARDINALITY +> ----------- --------- ------------------- +> ID INTEGER null +> A1 ARRAY 65536 +> A2 ARRAY 2 +> rows (ordered): 3 + +INSERT INTO TEST VALUES (1, ARRAY[], ARRAY[]), (2, ARRAY[1, 2], ARRAY[1, 2]); +> update count: 2 + +INSERT INTO TEST VALUES (3, ARRAY[], ARRAY[1, 2, 3]); +> exception VALUE_TOO_LONG_2 + +TABLE TEST; +> ID A1 A2 +> -- ------ ------ +> 1 [] [] +> 2 [1, 2] [1, 2] +> rows: 2 + +DROP TABLE TEST; +> ok + +CREATE MEMORY TABLE TEST(A1 INT ARRAY, A2 INT ARRAY[2], A3 INT ARRAY[0]); +> ok + +SCRIPT NODATA NOPASSWORDS NOSETTINGS NOVERSION TABLE TEST; +> SCRIPT +> -------------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "A1" INTEGER ARRAY, "A2" INTEGER ARRAY[2], "A3" INTEGER ARRAY[0] ); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> rows (ordered): 3 + +INSERT INTO TEST(A3) VALUES ARRAY[NULL]; +> exception VALUE_TOO_LONG_2 + +DROP TABLE TEST; +> ok + +CREATE MEMORY TABLE TEST1(I INT ARRAY, I2 INT ARRAY[2]); +> ok + +INSERT INTO TEST1 VALUES (ARRAY[1, 2, 3.0], ARRAY[1, NULL]); +> update count: 1 + +@reconnect + +TABLE TEST1; +> I I2 +> --------- --------- +> [1, 2, 3] [1, null] +> rows: 1 + +INSERT INTO TEST1 VALUES (ARRAY[], ARRAY['abc']); +> exception DATA_CONVERSION_ERROR_1 + +CREATE MEMORY TABLE TEST2 AS (TABLE TEST1) WITH NO DATA; +> ok + +CREATE MEMORY TABLE TEST3(A TIME ARRAY[10] ARRAY[2]); +> ok + +INSERT INTO TEST3 VALUES ARRAY[ARRAY[TIME '10:00:00']]; +> update count: 1 + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> --------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST1"( "I" INTEGER ARRAY, "I2" INTEGER ARRAY[2] ); +> -- 1 +/- SELECT COUNT(*) FROM PUBLIC.TEST1; +> INSERT INTO "PUBLIC"."TEST1" VALUES (ARRAY [1, 2, 3], ARRAY [1, NULL]); +> CREATE MEMORY TABLE "PUBLIC"."TEST2"( "I" INTEGER ARRAY, "I2" INTEGER ARRAY[2] ); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST2; +> CREATE MEMORY TABLE "PUBLIC"."TEST3"( "A" TIME ARRAY[10] ARRAY[2] ); +> -- 1 +/- SELECT COUNT(*) FROM PUBLIC.TEST3; +> INSERT INTO "PUBLIC"."TEST3" VALUES (ARRAY [ARRAY [TIME '10:00:00']]); +> rows (ordered): 9 + +DROP TABLE TEST1, TEST2, TEST3; +> ok + +VALUES CAST(ARRAY['1', '2'] AS DOUBLE PRECISION ARRAY); +>> [1.0, 2.0] + +EXPLAIN VALUES CAST(ARRAY['1', '2'] AS DOUBLE PRECISION ARRAY); +>> VALUES (CAST(ARRAY [1.0, 2.0] AS DOUBLE PRECISION ARRAY)) + +CREATE TABLE TEST(A1 TIMESTAMP ARRAY, A2 TIMESTAMP ARRAY ARRAY); +> ok + +CREATE INDEX IDX3 ON TEST(A1); +> ok + +CREATE INDEX IDX4 ON TEST(A2); +> ok + +DROP TABLE TEST; +> ok + +VALUES CAST(ARRAY[ARRAY[1, 2], ARRAY[3, 4]] AS INT ARRAY[2] ARRAY[1]); +>> [[1, 2]] + +VALUES CAST(ARRAY[ARRAY[1, 2], ARRAY[3, 4]] AS INT ARRAY[1] ARRAY[2]); +>> [[1], [3]] + +VALUES CAST(ARRAY[1, 2] AS INT ARRAY[0]); +>> [] + +VALUES ARRAY??(1??); +>> [1] + +EXPLAIN VALUES ARRAY??(1, 2??); +>> VALUES (ARRAY [1, 2]) + +VALUES ARRAY(SELECT X FROM SYSTEM_RANGE(1, 10)); +>> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + +CREATE TABLE TEST AS VALUES ARRAY(SELECT X FROM SYSTEM_RANGE(1, 1) WHERE FALSE) WITH NO DATA; +> ok + +SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST'; +>> ARRAY + +SELECT DATA_TYPE FROM INFORMATION_SCHEMA.ELEMENT_TYPES WHERE OBJECT_NAME = 'TEST'; +>> BIGINT + +DROP TABLE TEST; +> ok + +VALUES ARRAY(SELECT); +> exception SUBQUERY_IS_NOT_SINGLE_COLUMN + +VALUES ARRAY(SELECT 1, 2); +> exception SUBQUERY_IS_NOT_SINGLE_COLUMN + +EXPLAIN VALUES ARRAY[NULL, 1, '3']; +>> VALUES (ARRAY [NULL, 1, 3]) + +CREATE TABLE TEST(A INTEGER ARRAY[65536]); +> ok + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INTEGER ARRAY[65537]); +> exception INVALID_VALUE_PRECISION diff --git a/h2/src/test/org/h2/test/scripts/datatypes/bigint.sql b/h2/src/test/org/h2/test/scripts/datatypes/bigint.sql new file mode 100644 index 0000000..3b2bacf --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/bigint.sql @@ -0,0 +1,68 @@ +-- 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 +-- + +-- Multiplication + +SELECT CAST(-4294967296 AS BIGINT) * CAST (2147483648 AS BIGINT); +>> -9223372036854775808 + +SELECT CAST(4294967296 AS BIGINT) * CAST (-2147483648 AS BIGINT); +>> -9223372036854775808 + +SELECT CAST(-2147483648 AS BIGINT) * CAST (4294967296 AS BIGINT); +>> -9223372036854775808 + +SELECT CAST(2147483648 AS BIGINT) * CAST (-4294967296 AS BIGINT); +>> -9223372036854775808 + +SELECT CAST(4294967296 AS BIGINT) * CAST (2147483648 AS BIGINT); +> exception NUMERIC_VALUE_OUT_OF_RANGE_1 + +SELECT CAST(-4294967296 AS BIGINT) * CAST (-2147483648 AS BIGINT); +> exception NUMERIC_VALUE_OUT_OF_RANGE_1 + +SELECT CAST(2147483648 AS BIGINT) * CAST (4294967296 AS BIGINT); +> exception NUMERIC_VALUE_OUT_OF_RANGE_1 + +SELECT CAST(-2147483648 AS BIGINT) * CAST (-4294967296 AS BIGINT); +> exception NUMERIC_VALUE_OUT_OF_RANGE_1 + +SELECT CAST(-9223372036854775808 AS BIGINT) * CAST(1 AS BIGINT); +>> -9223372036854775808 + +SELECT CAST(-9223372036854775808 AS BIGINT) * CAST(-1 AS BIGINT); +> exception NUMERIC_VALUE_OUT_OF_RANGE_1 + +SELECT CAST(1 AS BIGINT) * CAST(-9223372036854775808 AS BIGINT); +>> -9223372036854775808 + +SELECT CAST(-1 AS BIGINT) * CAST(-9223372036854775808 AS BIGINT); +> exception NUMERIC_VALUE_OUT_OF_RANGE_1 + +-- Division + +SELECT CAST(1 AS BIGINT) / CAST(0 AS BIGINT); +> exception DIVISION_BY_ZERO_1 + +SELECT CAST(-9223372036854775808 AS BIGINT) / CAST(1 AS BIGINT); +>> -9223372036854775808 + +SELECT CAST(-9223372036854775808 AS BIGINT) / CAST(-1 AS BIGINT); +> exception NUMERIC_VALUE_OUT_OF_RANGE_1 + +SELECT 0x1L; +> 1 +> - +> 1 +> rows: 1 + +SELECT 0x1234567890abL; +> 20015998341291 +> -------------- +> 20015998341291 +> rows: 1 + +EXPLAIN VALUES (1L, -2147483648L, 2147483647L, -2147483649L, 2147483648L); +>> VALUES (CAST(1 AS BIGINT), -2147483648, CAST(2147483647 AS BIGINT), -2147483649, 2147483648) diff --git a/h2/src/test/org/h2/test/scripts/datatypes/binary.sql b/h2/src/test/org/h2/test/scripts/datatypes/binary.sql new file mode 100644 index 0000000..49c31ea --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/binary.sql @@ -0,0 +1,58 @@ +-- 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 +-- + +CREATE TABLE TEST(B1 BINARY, B2 BINARY(10)); +> ok + +SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_OCTET_LENGTH FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DATA_TYPE CHARACTER_OCTET_LENGTH +> ----------- --------- ---------------------- +> B1 BINARY 1 +> B2 BINARY 10 +> rows (ordered): 2 + +DROP TABLE TEST; +> ok + +SELECT CAST(X'11' AS BINARY) || CAST(NULL AS BINARY); +>> null + +SELECT CAST(NULL AS BINARY) || CAST(X'11' AS BINARY); +>> null + +EXPLAIN VALUES CAST(X'01' AS BINARY); +>> VALUES (CAST(X'01' AS BINARY(1))) + +CREATE TABLE T(C BINARY(0)); +> exception INVALID_VALUE_2 + +VALUES CAST(X'0102' AS BINARY); +>> X'01' + +CREATE TABLE T1(A BINARY(1000000000)); +> ok + +CREATE TABLE T2(A BINARY(1000000001)); +> exception INVALID_VALUE_PRECISION + +SET TRUNCATE_LARGE_LENGTH TRUE; +> ok + +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 1000000000 +> T2 1000000000 +> rows: 2 + +SET TRUNCATE_LARGE_LENGTH FALSE; +> ok + +DROP TABLE T1, T2; +> ok diff --git a/h2/src/test/org/h2/test/scripts/datatypes/blob.sql b/h2/src/test/org/h2/test/scripts/datatypes/blob.sql new file mode 100644 index 0000000..05cc2eb --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/blob.sql @@ -0,0 +1,61 @@ +-- 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 +-- + +CREATE TABLE TEST(B1 BLOB, B2 BINARY LARGE OBJECT, B3 TINYBLOB, B4 MEDIUMBLOB, B5 LONGBLOB, B6 IMAGE); +> ok + +SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DATA_TYPE +> ----------- ------------------- +> B1 BINARY LARGE OBJECT +> B2 BINARY LARGE OBJECT +> B3 BINARY LARGE OBJECT +> B4 BINARY LARGE OBJECT +> B5 BINARY LARGE OBJECT +> B6 BINARY LARGE OBJECT +> rows (ordered): 6 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(B0 BLOB(10), B1 BLOB(10K), B2 BLOB(10M), B3 BLOB(10G), B4 BLOB(10T), B5 BLOB(10P)); +> ok + +SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DATA_TYPE CHARACTER_MAXIMUM_LENGTH +> ----------- ------------------- ------------------------ +> B0 BINARY LARGE OBJECT 10 +> B1 BINARY LARGE OBJECT 10240 +> B2 BINARY LARGE OBJECT 10485760 +> B3 BINARY LARGE OBJECT 10737418240 +> B4 BINARY LARGE OBJECT 10995116277760 +> B5 BINARY LARGE OBJECT 11258999068426240 +> rows (ordered): 6 + +INSERT INTO TEST(B0) VALUES (X'0102030405060708091011'); +> exception VALUE_TOO_LONG_2 + +INSERT INTO TEST(B0) VALUES (X'01020304050607080910'); +> update count: 1 + +SELECT B0 FROM TEST; +>> X'01020304050607080910' + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(B BLOB(8192P)); +> exception INVALID_VALUE_2 + +EXPLAIN VALUES CAST(X'00' AS BLOB(1)); +>> VALUES (CAST(X'00' AS BINARY LARGE OBJECT(1))) + +CREATE TABLE T(C BLOB(0)); +> exception INVALID_VALUE_2 + +CREATE TABLE TEST(C1 BLOB(1K CHARACTERS), C2 BLOB(1K OCTETS)); +> exception SYNTAX_ERROR_2 diff --git a/h2/src/test/org/h2/test/scripts/datatypes/boolean.sql b/h2/src/test/org/h2/test/scripts/datatypes/boolean.sql new file mode 100644 index 0000000..727d7aa --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/boolean.sql @@ -0,0 +1,51 @@ +-- 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 +-- + +CREATE TABLE TEST(B BOOLEAN) AS (VALUES TRUE, FALSE, UNKNOWN); +> ok + +SELECT * FROM TEST ORDER BY B; +> B +> ----- +> null +> FALSE +> TRUE +> rows (ordered): 3 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST AS (SELECT UNKNOWN B); +> ok + +SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST'; +>> BOOLEAN + +EXPLAIN SELECT CAST(NULL AS BOOLEAN); +>> SELECT UNKNOWN + +SELECT NOT TRUE A, NOT FALSE B, NOT NULL C, NOT UNKNOWN D; +> A B C D +> ----- ---- ---- ---- +> FALSE TRUE null null +> rows: 1 + +DROP TABLE TEST; +> ok + +EXPLAIN VALUES (TRUE, FALSE, UNKNOWN); +>> 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 new file mode 100644 index 0000000..45a4f89 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/char.sql @@ -0,0 +1,198 @@ +-- 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 +-- + +CREATE TABLE TEST(C1 CHAR, C2 CHARACTER, C3 NCHAR, C4 NATIONAL CHARACTER, C5 NATIONAL CHAR); +> ok + +SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DATA_TYPE +> ----------- --------- +> C1 CHARACTER +> C2 CHARACTER +> C3 CHARACTER +> C4 CHARACTER +> C5 CHARACTER +> rows (ordered): 5 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(C CHAR(2)); +> ok + +INSERT INTO TEST VALUES 'aa', 'b'; +> update count: 2 + +SELECT * FROM TEST WHERE C = 'b'; +>> b + +SELECT * FROM TEST WHERE C = 'b '; +>> b + +SELECT * FROM TEST WHERE C = 'b '; +>> b + +SELECT C || 'x' V FROM TEST; +> V +> --- +> aax +> b x +> rows: 2 + +DROP TABLE TEST; +> ok + +SET MODE PostgreSQL; +> ok + +CREATE TABLE TEST(C CHAR(2)); +> ok + +INSERT INTO TEST VALUES 'aa', 'b'; +> update count: 2 + +SELECT * FROM TEST WHERE C = 'b'; +>> b + +SELECT * FROM TEST WHERE C = 'b '; +>> b + +SELECT * FROM TEST WHERE C = 'b '; +>> b + +SELECT C || 'x' V FROM TEST; +> V +> --- +> aax +> bx +> rows: 2 + +DROP TABLE TEST; +> ok + +SET MODE Regular; +> ok + +EXPLAIN VALUES CAST('a' AS CHAR(1)); +>> VALUES (CAST('a' AS CHAR(1))) + +EXPLAIN VALUES CAST('' AS CHAR(1)); +>> VALUES (CAST(' ' AS CHAR(1))) + +CREATE TABLE T(C CHAR(0)); +> exception INVALID_VALUE_2 + +CREATE TABLE T(C1 CHAR(1 CHARACTERS), C2 CHAR(1 OCTETS)); +> ok + +DROP TABLE T; +> ok + +VALUES CAST('ab' AS CHAR); +>> a + +CREATE TABLE TEST(A CHAR(2) NOT NULL, B CHAR(3) NOT NULL); +> ok + +INSERT INTO TEST VALUES ('a', 'a'), ('aa', 'aaa'), ('bb ', 'bb'); +> update count: 3 + +INSERT INTO TEST VALUES ('a a', 'a a'); +> exception VALUE_TOO_LONG_2 + +VALUES CAST('a a' AS CHAR(2)) || '*'; +>> a * + +SELECT A || '*', B || '*', A || B || '*', CHAR_LENGTH(A), A = B FROM TEST; +> A || '*' B || '*' A || B || '*' CHAR_LENGTH(A) A = B +> -------- -------- ------------- -------------- ----- +> a * a * a a * 2 TRUE +> aa* aaa* aaaaa* 2 FALSE +> bb* bb * bbbb * 2 TRUE +> rows: 3 + +DROP TABLE TEST; +> ok + +SET MODE MySQL; +> ok + +CREATE TABLE TEST(A CHAR(2) NOT NULL, B CHAR(3) NOT NULL); +> ok + +INSERT INTO TEST VALUES ('a', 'a'), ('aa', 'aaa'), ('bb ', 'bb'); +> update count: 3 + +INSERT INTO TEST VALUES ('a a', 'a a'); +> exception VALUE_TOO_LONG_2 + +VALUES CAST('a a' AS CHAR(2)) || '*'; +>> a* + +SELECT A || '*', B || '*', A || B || '*', CHAR_LENGTH(A), A = B FROM TEST; +> A || '*' B || '*' A || B || '*' CHAR_LENGTH(A) A = B +> -------- -------- ------------- -------------- ----- +> a* a* aa* 1 TRUE +> aa* aaa* aaaaa* 2 FALSE +> bb* bb* bbbb* 2 TRUE +> rows: 3 + +DROP TABLE TEST; +> ok + +SET MODE PostgreSQL; +> ok + +CREATE TABLE TEST(A CHAR(2) NOT NULL, B CHAR(3) NOT NULL); +> ok + +INSERT INTO TEST VALUES ('a', 'a'), ('aa', 'aaa'), ('bb ', 'bb'); +> update count: 3 + +INSERT INTO TEST VALUES ('a a', 'a a'); +> exception VALUE_TOO_LONG_2 + +VALUES CAST('a a' AS CHAR(2)) || '*'; +>> a* + +SELECT A || '*', B || '*', A || B || '*', CHAR_LENGTH(A), A = B FROM TEST; +> ?column? ?column? ?column? char_length ?column? +> -------- -------- -------- ----------- -------- +> a* a* aa* 1 TRUE +> aa* aaa* aaaaa* 2 FALSE +> bb* bb* bbbb* 2 TRUE +> rows: 3 + +DROP TABLE TEST; +> ok + +SET MODE Regular; +> ok + +CREATE TABLE T1(A CHARACTER(1000000000)); +> ok + +CREATE TABLE T2(A CHARACTER(1000000001)); +> exception INVALID_VALUE_PRECISION + +SET TRUNCATE_LARGE_LENGTH TRUE; +> ok + +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 1000000000 +> T2 1000000000 +> rows: 2 + +SET TRUNCATE_LARGE_LENGTH FALSE; +> ok + +DROP TABLE T1, T2; +> ok diff --git a/h2/src/test/org/h2/test/scripts/datatypes/clob.sql b/h2/src/test/org/h2/test/scripts/datatypes/clob.sql new file mode 100644 index 0000000..20cb6db --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/clob.sql @@ -0,0 +1,70 @@ +-- 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 +-- + +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); +> ok + +SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DATA_TYPE +> ----------- ---------------------- +> C1 CHARACTER LARGE OBJECT +> C2 CHARACTER LARGE OBJECT +> C3 CHARACTER LARGE OBJECT +> 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 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(C0 CLOB(10), C1 CLOB(10K), C2 CLOB(10M CHARACTERS), C3 CLOB(10G OCTETS), C4 CLOB(10T), C5 CLOB(10P)); +> ok + +SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DATA_TYPE CHARACTER_MAXIMUM_LENGTH +> ----------- ---------------------- ------------------------ +> C0 CHARACTER LARGE OBJECT 10 +> C1 CHARACTER LARGE OBJECT 10240 +> C2 CHARACTER LARGE OBJECT 10485760 +> C3 CHARACTER LARGE OBJECT 10737418240 +> C4 CHARACTER LARGE OBJECT 10995116277760 +> C5 CHARACTER LARGE OBJECT 11258999068426240 +> rows (ordered): 6 + +INSERT INTO TEST(C0) VALUES ('12345678901'); +> exception VALUE_TOO_LONG_2 + +INSERT INTO TEST(C0) VALUES ('1234567890'); +> update count: 1 + +SELECT C0 FROM TEST; +>> 1234567890 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(C CLOB(8192P)); +> exception INVALID_VALUE_2 + +EXPLAIN VALUES CAST(' ' AS CLOB(1)); +>> VALUES (CAST(' ' AS CHARACTER LARGE OBJECT(1))) + +CREATE TABLE T(C CLOB(0)); +> exception INVALID_VALUE_2 + +CREATE TABLE TEST(C1 CLOB(1K CHARACTERS), C2 CLOB(1K OCTETS)); +> ok + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/datatypes/date.sql b/h2/src/test/org/h2/test/scripts/datatypes/date.sql new file mode 100644 index 0000000..9d48a4b --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/date.sql @@ -0,0 +1,60 @@ +-- 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 +-- + +CREATE TABLE TEST(D1 DATE); +> ok + +SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DATA_TYPE +> ----------- --------- +> D1 DATE +> rows (ordered): 1 + +DROP TABLE TEST; +> ok + +SELECT DATE '2000-01-02'; +>> 2000-01-02 + +SELECT DATE '20000102'; +>> 2000-01-02 + +SELECT DATE '-1000102'; +>> -0100-01-02 + +SELECT DATE '3001231'; +>> 0300-12-31 + +-- PostgreSQL returns 2020-12-31 +SELECT DATE '201231'; +> exception INVALID_DATETIME_CONSTANT_2 + +CALL DATE '-1000000000-01-01'; +>> -1000000000-01-01 + +CALL DATE '1000000000-12-31'; +>> 1000000000-12-31 + +CALL DATE '-1000000001-12-31'; +> exception INVALID_DATETIME_CONSTANT_2 + +CALL DATE '1000000001-01-01'; +> exception INVALID_DATETIME_CONSTANT_2 + +SELECT CAST (TIMESTAMP '1000000000-12-31 00:00:00' AS DATE); +>> 1000000000-12-31 + +SELECT CAST (DATE '1000000000-12-31' AS TIMESTAMP); +>> 1000000000-12-31 00:00:00 + +SELECT CAST (TIMESTAMP '-1000000000-01-01 00:00:00' AS DATE); +>> -1000000000-01-01 + +SELECT CAST (DATE '-1000000000-01-01' AS TIMESTAMP); +>> -1000000000-01-01 00:00:00 + +SELECT CAST (DATE '2000-01-01' AS TIME); +> exception DATA_CONVERSION_ERROR_1 diff --git a/h2/src/test/org/h2/test/scripts/datatypes/decfloat.sql b/h2/src/test/org/h2/test/scripts/datatypes/decfloat.sql new file mode 100644 index 0000000..f311f90 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/decfloat.sql @@ -0,0 +1,283 @@ +-- 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 +-- + +CREATE MEMORY TABLE TEST(D1 DECFLOAT, D2 DECFLOAT(5), D3 DECFLOAT(10), X NUMBER); +> ok + +INSERT INTO TEST VALUES(1, 1, 9999999999, 1.23); +> update count: 1 + +TABLE TEST; +> D1 D2 D3 X +> -- -- ---------- ---- +> 1 1 9999999999 1.23 +> rows: 1 + +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 +> ----------- --------- ----------------- ----------------------- ------------- ------------------ -------------------------- ---------------------- +> D1 DECFLOAT 100000 10 null DECFLOAT null null +> D2 DECFLOAT 5 10 null DECFLOAT 5 null +> D3 DECFLOAT 10 10 null DECFLOAT 10 null +> X DECFLOAT 40 10 null DECFLOAT 40 null +> rows (ordered): 4 + +SELECT D2 + D3 A, D2 - D3 S, D2 * D3 M, D2 / D3 D FROM TEST; +> A S M D +> ----- ----------- ---------- ---------------- +> 1E+10 -9999999998 9999999999 1.0000000001E-10 +> rows: 1 + +CREATE TABLE RESULT AS SELECT D2 + D3 A, D2 - D3 S, D2 * D3 M, D2 / D3 D FROM TEST; +> ok + +TABLE RESULT; +> A S M D +> ----- ----------- ---------- ---------------- +> 1E+10 -9999999998 9999999999 1.0000000001E-10 +> rows: 1 + +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 = 'RESULT' 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 11 10 null DECFLOAT 11 null +> S DECFLOAT 11 10 null DECFLOAT 11 null +> M DECFLOAT 15 10 null DECFLOAT 15 null +> D DECFLOAT 11 10 null DECFLOAT 11 null +> rows (ordered): 4 + +DROP TABLE TEST, RESULT; +> ok + +EXPLAIN VALUES (CAST(-9223372036854775808 AS DECFLOAT(19)), CAST(9223372036854775807 AS DECFLOAT(19)), 1.0, -9223372036854775809, + 9223372036854775808); +>> VALUES (CAST(-9223372036854775808 AS DECFLOAT), CAST(9223372036854775807 AS DECFLOAT), 1.0, -9223372036854775809, 9223372036854775808) + +CREATE TABLE T(C DECFLOAT(0)); +> exception INVALID_VALUE_2 + +SELECT CAST(11 AS DECFLOAT(1)); +>> 1E+1 + +SELECT 1E1 IS OF(DECFLOAT); +>> TRUE + +SELECT (CAST(1 AS REAL) + CAST(1 AS SMALLINT)) IS OF(REAL); +>> TRUE + +SELECT (CAST(1 AS REAL) + CAST(1 AS BIGINT)) IS OF(DECFLOAT); +>> TRUE + +SELECT (CAST(1 AS REAL) + CAST(1 AS NUMERIC)) IS OF(DECFLOAT); +>> TRUE + +SELECT MOD(CAST(5 AS DECFLOAT), CAST(2 AS DECFLOAT)); +>> 1 + +EXPLAIN SELECT 1.1E0, 1E1; +>> SELECT CAST(1.1 AS DECFLOAT), CAST(1E+1 AS DECFLOAT) + +CREATE MEMORY TABLE TEST(D DECFLOAT(8)) AS VALUES '-Infinity', '-1', '0', '1', '1.5', 'Infinity', 'NaN'; +> ok + +@reconnect + +SELECT D, -D, SIGN(D) FROM TEST ORDER BY D; +> D - D SIGN(D) +> --------- --------- ------- +> -Infinity Infinity -1 +> -1 1 -1 +> 0 0 0 +> 1 -1 1 +> 1.5 -1.5 1 +> Infinity -Infinity 1 +> NaN NaN 0 +> rows (ordered): 7 + +SELECT A.D, B.D, A.D + B.D, A.D - B.D, A.D * B.D FROM TEST A JOIN TEST B ORDER BY A.D, B.D; +> D D A.D + B.D A.D - B.D A.D * B.D +> --------- --------- --------- --------- --------- +> -Infinity -Infinity -Infinity NaN Infinity +> -Infinity -1 -Infinity -Infinity Infinity +> -Infinity 0 -Infinity -Infinity NaN +> -Infinity 1 -Infinity -Infinity -Infinity +> -Infinity 1.5 -Infinity -Infinity -Infinity +> -Infinity Infinity NaN -Infinity -Infinity +> -Infinity NaN NaN NaN NaN +> -1 -Infinity -Infinity Infinity Infinity +> -1 -1 -2 0 1 +> -1 0 -1 -1 0 +> -1 1 0 -2 -1 +> -1 1.5 0.5 -2.5 -1.5 +> -1 Infinity Infinity -Infinity -Infinity +> -1 NaN NaN NaN NaN +> 0 -Infinity -Infinity Infinity NaN +> 0 -1 -1 1 0 +> 0 0 0 0 0 +> 0 1 1 -1 0 +> 0 1.5 1.5 -1.5 0 +> 0 Infinity Infinity -Infinity NaN +> 0 NaN NaN NaN NaN +> 1 -Infinity -Infinity Infinity -Infinity +> 1 -1 0 2 -1 +> 1 0 1 1 0 +> 1 1 2 0 1 +> 1 1.5 2.5 -0.5 1.5 +> 1 Infinity Infinity -Infinity Infinity +> 1 NaN NaN NaN NaN +> 1.5 -Infinity -Infinity Infinity -Infinity +> 1.5 -1 0.5 2.5 -1.5 +> 1.5 0 1.5 1.5 0 +> 1.5 1 2.5 0.5 1.5 +> 1.5 1.5 3 0 2.25 +> 1.5 Infinity Infinity -Infinity Infinity +> 1.5 NaN NaN NaN NaN +> Infinity -Infinity NaN Infinity -Infinity +> Infinity -1 Infinity Infinity -Infinity +> Infinity 0 Infinity Infinity NaN +> Infinity 1 Infinity Infinity Infinity +> Infinity 1.5 Infinity Infinity Infinity +> Infinity Infinity Infinity NaN Infinity +> Infinity NaN NaN NaN NaN +> NaN -Infinity NaN NaN NaN +> NaN -1 NaN NaN NaN +> NaN 0 NaN NaN NaN +> NaN 1 NaN NaN NaN +> NaN 1.5 NaN NaN NaN +> NaN Infinity NaN NaN NaN +> NaN NaN NaN NaN NaN +> rows (ordered): 49 + +SELECT A.D, B.D, A.D / B.D, MOD(A.D, B.D) FROM TEST A JOIN TEST B WHERE B.D <> 0 ORDER BY A.D, B.D; +> D D A.D / B.D MOD(A.D, B.D) +> --------- --------- ------------ ------------- +> -Infinity -Infinity NaN NaN +> -Infinity -1 Infinity NaN +> -Infinity 1 -Infinity NaN +> -Infinity 1.5 -Infinity NaN +> -Infinity Infinity NaN NaN +> -Infinity NaN NaN NaN +> -1 -Infinity 0 -1 +> -1 -1 1 0 +> -1 1 -1 0 +> -1 1.5 -0.666666667 -1 +> -1 Infinity 0 -1 +> -1 NaN NaN NaN +> 0 -Infinity 0 0 +> 0 -1 0 0 +> 0 1 0 0 +> 0 1.5 0 0 +> 0 Infinity 0 0 +> 0 NaN NaN NaN +> 1 -Infinity 0 1 +> 1 -1 -1 0 +> 1 1 1 0 +> 1 1.5 0.666666667 1 +> 1 Infinity 0 1 +> 1 NaN NaN NaN +> 1.5 -Infinity 0 1.5 +> 1.5 -1 -1.5 0.5 +> 1.5 1 1.5 0.5 +> 1.5 1.5 1 0 +> 1.5 Infinity 0 1.5 +> 1.5 NaN NaN NaN +> Infinity -Infinity NaN NaN +> Infinity -1 -Infinity NaN +> Infinity 1 Infinity NaN +> Infinity 1.5 Infinity NaN +> Infinity Infinity NaN NaN +> Infinity NaN NaN NaN +> NaN -Infinity NaN NaN +> NaN -1 NaN NaN +> NaN 1 NaN NaN +> NaN 1.5 NaN NaN +> NaN Infinity NaN NaN +> NaN NaN NaN NaN +> rows (ordered): 42 + +SELECT A.D, B.D, A.D > B.D, A.D = B.D, A.D < B.D FROM TEST A JOIN TEST B ORDER BY A.D, B.D; +> D D A.D > B.D A.D = B.D A.D < B.D +> --------- --------- --------- --------- --------- +> -Infinity -Infinity FALSE TRUE FALSE +> -Infinity -1 FALSE FALSE TRUE +> -Infinity 0 FALSE FALSE TRUE +> -Infinity 1 FALSE FALSE TRUE +> -Infinity 1.5 FALSE FALSE TRUE +> -Infinity Infinity FALSE FALSE TRUE +> -Infinity NaN FALSE FALSE TRUE +> -1 -Infinity TRUE FALSE FALSE +> -1 -1 FALSE TRUE FALSE +> -1 0 FALSE FALSE TRUE +> -1 1 FALSE FALSE TRUE +> -1 1.5 FALSE FALSE TRUE +> -1 Infinity FALSE FALSE TRUE +> -1 NaN FALSE FALSE TRUE +> 0 -Infinity TRUE FALSE FALSE +> 0 -1 TRUE FALSE FALSE +> 0 0 FALSE TRUE FALSE +> 0 1 FALSE FALSE TRUE +> 0 1.5 FALSE FALSE TRUE +> 0 Infinity FALSE FALSE TRUE +> 0 NaN FALSE FALSE TRUE +> 1 -Infinity TRUE FALSE FALSE +> 1 -1 TRUE FALSE FALSE +> 1 0 TRUE FALSE FALSE +> 1 1 FALSE TRUE FALSE +> 1 1.5 FALSE FALSE TRUE +> 1 Infinity FALSE FALSE TRUE +> 1 NaN FALSE FALSE TRUE +> 1.5 -Infinity TRUE FALSE FALSE +> 1.5 -1 TRUE FALSE FALSE +> 1.5 0 TRUE FALSE FALSE +> 1.5 1 TRUE FALSE FALSE +> 1.5 1.5 FALSE TRUE FALSE +> 1.5 Infinity FALSE FALSE TRUE +> 1.5 NaN FALSE FALSE TRUE +> Infinity -Infinity TRUE FALSE FALSE +> Infinity -1 TRUE FALSE FALSE +> Infinity 0 TRUE FALSE FALSE +> Infinity 1 TRUE FALSE FALSE +> Infinity 1.5 TRUE FALSE FALSE +> Infinity Infinity FALSE TRUE FALSE +> Infinity NaN FALSE FALSE TRUE +> NaN -Infinity TRUE FALSE FALSE +> NaN -1 TRUE FALSE FALSE +> NaN 0 TRUE FALSE FALSE +> NaN 1 TRUE FALSE FALSE +> NaN 1.5 TRUE FALSE FALSE +> NaN Infinity TRUE FALSE FALSE +> NaN NaN FALSE TRUE FALSE +> rows (ordered): 49 + +SELECT D, CAST(D AS REAL) D1, CAST(D AS DOUBLE PRECISION) D2 FROM TEST ORDER BY D; +> D D1 D2 +> --------- --------- --------- +> -Infinity -Infinity -Infinity +> -1 -1.0 -1.0 +> 0 0.0 0.0 +> 1 1.0 1.0 +> 1.5 1.5 1.5 +> Infinity Infinity Infinity +> NaN NaN NaN +> rows (ordered): 7 + +EXPLAIN SELECT CAST('Infinity' AS DECFLOAT), CAST('-Infinity' AS DECFLOAT), CAST('NaN' AS DECFLOAT), CAST(0 AS DECFLOAT); +>> SELECT CAST('Infinity' AS DECFLOAT), CAST('-Infinity' AS DECFLOAT), CAST('NaN' AS DECFLOAT), CAST(0 AS DECFLOAT) + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION TABLE TEST; +> SCRIPT +> ----------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "D" DECFLOAT(8) ); +> -- 7 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> INSERT INTO "PUBLIC"."TEST" VALUES ('-Infinity'), (-1), (0), (1), (1.5), ('Infinity'), ('NaN'); +> rows (ordered): 4 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/datatypes/double_precision.sql b/h2/src/test/org/h2/test/scripts/datatypes/double_precision.sql new file mode 100644 index 0000000..3d86efd --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/double_precision.sql @@ -0,0 +1,233 @@ +-- 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 +-- + +CREATE MEMORY TABLE TEST(D1 DOUBLE, D2 DOUBLE PRECISION, D3 FLOAT, D4 FLOAT(25), D5 FLOAT(53)); +> ok + +ALTER TABLE TEST ADD COLUMN D6 FLOAT(54); +> exception INVALID_VALUE_PRECISION + +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 +> ----------- ---------------- ----------------- ----------------------- ------------- ------------------ -------------------------- ---------------------- +> D1 DOUBLE PRECISION 53 2 null DOUBLE PRECISION null null +> D2 DOUBLE PRECISION 53 2 null DOUBLE PRECISION null null +> D3 DOUBLE PRECISION 53 2 null FLOAT null null +> D4 DOUBLE PRECISION 53 2 null FLOAT 25 null +> D5 DOUBLE PRECISION 53 2 null FLOAT 53 null +> rows (ordered): 5 + +SCRIPT NODATA NOPASSWORDS NOSETTINGS NOVERSION TABLE TEST; +> SCRIPT +> -------------------------------------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "D1" DOUBLE PRECISION, "D2" DOUBLE PRECISION, "D3" FLOAT, "D4" FLOAT(25), "D5" FLOAT(53) ); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> rows (ordered): 3 + +DROP TABLE TEST; +> ok + +EXPLAIN VALUES CAST(0 AS DOUBLE); +>> VALUES (CAST(0.0 AS DOUBLE PRECISION)) + +CREATE MEMORY TABLE TEST(D DOUBLE PRECISION) AS VALUES '-Infinity', '-1', '0', '1', '1.5', 'Infinity', 'NaN'; +> ok + +SELECT D, -D, SIGN(D) FROM TEST ORDER BY D; +> D - D SIGN(D) +> --------- --------- ------- +> -Infinity Infinity -1 +> -1.0 1.0 -1 +> 0.0 0.0 0 +> 1.0 -1.0 1 +> 1.5 -1.5 1 +> Infinity -Infinity 1 +> NaN NaN 0 +> rows (ordered): 7 + +SELECT A.D, B.D, A.D + B.D, A.D - B.D, A.D * B.D FROM TEST A JOIN TEST B ORDER BY A.D, B.D; +> D D A.D + B.D A.D - B.D A.D * B.D +> --------- --------- --------- --------- --------- +> -Infinity -Infinity -Infinity NaN Infinity +> -Infinity -1.0 -Infinity -Infinity Infinity +> -Infinity 0.0 -Infinity -Infinity NaN +> -Infinity 1.0 -Infinity -Infinity -Infinity +> -Infinity 1.5 -Infinity -Infinity -Infinity +> -Infinity Infinity NaN -Infinity -Infinity +> -Infinity NaN NaN NaN NaN +> -1.0 -Infinity -Infinity Infinity Infinity +> -1.0 -1.0 -2.0 0.0 1.0 +> -1.0 0.0 -1.0 -1.0 0.0 +> -1.0 1.0 0.0 -2.0 -1.0 +> -1.0 1.5 0.5 -2.5 -1.5 +> -1.0 Infinity Infinity -Infinity -Infinity +> -1.0 NaN NaN NaN NaN +> 0.0 -Infinity -Infinity Infinity NaN +> 0.0 -1.0 -1.0 1.0 0.0 +> 0.0 0.0 0.0 0.0 0.0 +> 0.0 1.0 1.0 -1.0 0.0 +> 0.0 1.5 1.5 -1.5 0.0 +> 0.0 Infinity Infinity -Infinity NaN +> 0.0 NaN NaN NaN NaN +> 1.0 -Infinity -Infinity Infinity -Infinity +> 1.0 -1.0 0.0 2.0 -1.0 +> 1.0 0.0 1.0 1.0 0.0 +> 1.0 1.0 2.0 0.0 1.0 +> 1.0 1.5 2.5 -0.5 1.5 +> 1.0 Infinity Infinity -Infinity Infinity +> 1.0 NaN NaN NaN NaN +> 1.5 -Infinity -Infinity Infinity -Infinity +> 1.5 -1.0 0.5 2.5 -1.5 +> 1.5 0.0 1.5 1.5 0.0 +> 1.5 1.0 2.5 0.5 1.5 +> 1.5 1.5 3.0 0.0 2.25 +> 1.5 Infinity Infinity -Infinity Infinity +> 1.5 NaN NaN NaN NaN +> Infinity -Infinity NaN Infinity -Infinity +> Infinity -1.0 Infinity Infinity -Infinity +> Infinity 0.0 Infinity Infinity NaN +> Infinity 1.0 Infinity Infinity Infinity +> Infinity 1.5 Infinity Infinity Infinity +> Infinity Infinity Infinity NaN Infinity +> Infinity NaN NaN NaN NaN +> NaN -Infinity NaN NaN NaN +> NaN -1.0 NaN NaN NaN +> NaN 0.0 NaN NaN NaN +> NaN 1.0 NaN NaN NaN +> NaN 1.5 NaN NaN NaN +> NaN Infinity NaN NaN NaN +> NaN NaN NaN NaN NaN +> rows (ordered): 49 + +SELECT A.D, B.D, A.D / B.D, MOD(A.D, B.D) FROM TEST A JOIN TEST B WHERE B.D <> 0 ORDER BY A.D, B.D; +> D D A.D / B.D MOD(A.D, B.D) +> --------- --------- ------------------- ------------- +> -Infinity -Infinity NaN NaN +> -Infinity -1.0 Infinity NaN +> -Infinity 1.0 -Infinity NaN +> -Infinity 1.5 -Infinity NaN +> -Infinity Infinity NaN NaN +> -Infinity NaN NaN NaN +> -1.0 -Infinity 0.0 -1.0 +> -1.0 -1.0 1.0 0.0 +> -1.0 1.0 -1.0 0.0 +> -1.0 1.5 -0.6666666666666666 -1.0 +> -1.0 Infinity 0.0 -1.0 +> -1.0 NaN NaN NaN +> 0.0 -Infinity 0.0 0.0 +> 0.0 -1.0 0.0 0.0 +> 0.0 1.0 0.0 0.0 +> 0.0 1.5 0.0 0.0 +> 0.0 Infinity 0.0 0.0 +> 0.0 NaN NaN NaN +> 1.0 -Infinity 0.0 1.0 +> 1.0 -1.0 -1.0 0.0 +> 1.0 1.0 1.0 0.0 +> 1.0 1.5 0.6666666666666666 1.0 +> 1.0 Infinity 0.0 1.0 +> 1.0 NaN NaN NaN +> 1.5 -Infinity 0.0 1.5 +> 1.5 -1.0 -1.5 0.5 +> 1.5 1.0 1.5 0.5 +> 1.5 1.5 1.0 0.0 +> 1.5 Infinity 0.0 1.5 +> 1.5 NaN NaN NaN +> Infinity -Infinity NaN NaN +> Infinity -1.0 -Infinity NaN +> Infinity 1.0 Infinity NaN +> Infinity 1.5 Infinity NaN +> Infinity Infinity NaN NaN +> Infinity NaN NaN NaN +> NaN -Infinity NaN NaN +> NaN -1.0 NaN NaN +> NaN 1.0 NaN NaN +> NaN 1.5 NaN NaN +> NaN Infinity NaN NaN +> NaN NaN NaN NaN +> rows (ordered): 42 + +SELECT A.D, B.D, A.D > B.D, A.D = B.D, A.D < B.D FROM TEST A JOIN TEST B ORDER BY A.D, B.D; +> D D A.D > B.D A.D = B.D A.D < B.D +> --------- --------- --------- --------- --------- +> -Infinity -Infinity FALSE TRUE FALSE +> -Infinity -1.0 FALSE FALSE TRUE +> -Infinity 0.0 FALSE FALSE TRUE +> -Infinity 1.0 FALSE FALSE TRUE +> -Infinity 1.5 FALSE FALSE TRUE +> -Infinity Infinity FALSE FALSE TRUE +> -Infinity NaN FALSE FALSE TRUE +> -1.0 -Infinity TRUE FALSE FALSE +> -1.0 -1.0 FALSE TRUE FALSE +> -1.0 0.0 FALSE FALSE TRUE +> -1.0 1.0 FALSE FALSE TRUE +> -1.0 1.5 FALSE FALSE TRUE +> -1.0 Infinity FALSE FALSE TRUE +> -1.0 NaN FALSE FALSE TRUE +> 0.0 -Infinity TRUE FALSE FALSE +> 0.0 -1.0 TRUE FALSE FALSE +> 0.0 0.0 FALSE TRUE FALSE +> 0.0 1.0 FALSE FALSE TRUE +> 0.0 1.5 FALSE FALSE TRUE +> 0.0 Infinity FALSE FALSE TRUE +> 0.0 NaN FALSE FALSE TRUE +> 1.0 -Infinity TRUE FALSE FALSE +> 1.0 -1.0 TRUE FALSE FALSE +> 1.0 0.0 TRUE FALSE FALSE +> 1.0 1.0 FALSE TRUE FALSE +> 1.0 1.5 FALSE FALSE TRUE +> 1.0 Infinity FALSE FALSE TRUE +> 1.0 NaN FALSE FALSE TRUE +> 1.5 -Infinity TRUE FALSE FALSE +> 1.5 -1.0 TRUE FALSE FALSE +> 1.5 0.0 TRUE FALSE FALSE +> 1.5 1.0 TRUE FALSE FALSE +> 1.5 1.5 FALSE TRUE FALSE +> 1.5 Infinity FALSE FALSE TRUE +> 1.5 NaN FALSE FALSE TRUE +> Infinity -Infinity TRUE FALSE FALSE +> Infinity -1.0 TRUE FALSE FALSE +> Infinity 0.0 TRUE FALSE FALSE +> Infinity 1.0 TRUE FALSE FALSE +> Infinity 1.5 TRUE FALSE FALSE +> Infinity Infinity FALSE TRUE FALSE +> Infinity NaN FALSE FALSE TRUE +> NaN -Infinity TRUE FALSE FALSE +> NaN -1.0 TRUE FALSE FALSE +> NaN 0.0 TRUE FALSE FALSE +> NaN 1.0 TRUE FALSE FALSE +> NaN 1.5 TRUE FALSE FALSE +> NaN Infinity TRUE FALSE FALSE +> NaN NaN FALSE TRUE FALSE +> rows (ordered): 49 + +SELECT D, CAST(D AS REAL) D1, CAST(D AS DECFLOAT) D2 FROM TEST ORDER BY D; +> D D1 D2 +> --------- --------- --------- +> -Infinity -Infinity -Infinity +> -1.0 -1.0 -1 +> 0.0 0.0 0 +> 1.0 1.0 1 +> 1.5 1.5 1.5 +> Infinity Infinity Infinity +> NaN NaN NaN +> rows (ordered): 7 + +EXPLAIN SELECT CAST('Infinity' AS DOUBLE PRECISION), CAST('-Infinity' AS DOUBLE PRECISION), CAST('NaN' AS DOUBLE PRECISION), CAST(0 AS DOUBLE PRECISION); +>> SELECT CAST('Infinity' AS DOUBLE PRECISION), CAST('-Infinity' AS DOUBLE PRECISION), CAST('NaN' AS DOUBLE PRECISION), CAST(0.0 AS DOUBLE PRECISION) + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION TABLE TEST; +> SCRIPT +> ----------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "D" DOUBLE PRECISION ); +> -- 7 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> INSERT INTO "PUBLIC"."TEST" VALUES ('-Infinity'), (-1.0), (0.0), (1.0), (1.5), ('Infinity'), ('NaN'); +> rows (ordered): 4 + +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 new file mode 100644 index 0000000..5325fec --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/enum.sql @@ -0,0 +1,391 @@ +-- 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 +-- + +---------------- +--- ENUM support +---------------- + +--- ENUM basic operations + +create table card (rank int, suit enum('hearts', 'clubs', 'spades')); +> ok + +insert into card (rank, suit) values (0, 'clubs'), (3, 'hearts'), (4, NULL); +> update count: 3 + +alter table card alter column suit enum('hearts', 'clubs', 'spades', 'diamonds'); +> ok + +select * from card; +> RANK SUIT +> ---- ------ +> 0 clubs +> 3 hearts +> 4 null +> rows: 3 + +@reconnect + +select suit from card where rank = 0; +>> clubs + +alter table card alter column suit enum('a', 'b', 'c', 'd'); +> exception ENUM_VALUE_NOT_PERMITTED + +alter table card alter column suit enum('''none''', 'hearts', 'clubs', 'spades', 'diamonds'); +> ok + +select * from card order by suit; +> RANK SUIT +> ---- ------ +> 4 null +> 3 hearts +> 0 clubs +> rows (ordered): 3 + +insert into card (rank, suit) values (8, 'diamonds'), (10, 'clubs'), (7, 'hearts'); +> update count: 3 + +select suit, count(rank) from card group by suit order by suit, count(rank); +> SUIT COUNT(RANK) +> -------- ----------- +> null 1 +> hearts 2 +> clubs 2 +> 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 + +alter table card alter column suit enum('hearts', 'clubs', 'spades', 'diamonds'); +> ok + +alter table card alter column suit enum('hearts', 'clubs', 'spades', 'diamonds', 'long_enum_value_of_128_chars_00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'); +> ok + +insert into card (rank, suit) values (11, 'long_enum_value_of_128_chars_00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'); +> update count: 1 + +--- ENUM integer-based operations + +select rank from card where suit = 2; +> exception TYPES_ARE_NOT_COMPARABLE_2 + +select rank from card where cast(suit as integer) = 2; +> RANK +> ---- +> 0 +> 10 +> rows: 2 + +insert into card (rank, suit) values(5, 3); +> update count: 1 + +select * from card where cast(rank as integer) = 5; +> RANK SUIT +> ---- ------ +> 5 spades +> rows: 1 + +--- ENUM edge cases + +insert into card (rank, suit) values(6, ' '); +> exception ENUM_VALUE_NOT_PERMITTED + +alter table card alter column suit enum('hearts', 'clubs', 'spades', 'diamonds', 'clubs'); +> exception ENUM_DUPLICATE + +alter table card alter column suit enum('hearts', 'clubs', 'spades', 'diamonds', ''); +> exception ENUM_EMPTY + +drop table card; +> ok + +--- ENUM as custom user data type + +create type CARD_SUIT as enum('hearts', 'clubs', 'spades', 'diamonds'); +> ok + +create table card (rank int, suit CARD_SUIT); +> ok + +insert into card (rank, suit) values (0, 'clubs'), (3, 'hearts'); +> update count: 2 + +select * from card; +> RANK SUIT +> ---- ------ +> 0 clubs +> 3 hearts +> rows: 2 + +drop table card; +> ok + +drop type CARD_SUIT; +> ok + +--- ENUM in primary key with another column +create type CARD_SUIT as enum('hearts', 'clubs', 'spades', 'diamonds'); +> ok + +create table card (rank int, suit CARD_SUIT, primary key(rank, suit)); +> ok + +insert into card (rank, suit) values (0, 'clubs'), (3, 'hearts'), (1, 'clubs'); +> update count: 3 + +insert into card (rank, suit) values (0, 'clubs'); +> exception DUPLICATE_KEY_1 + +select rank from card where suit = 'clubs'; +> RANK +> ---- +> 0 +> 1 +> rows: 2 + +drop table card; +> ok + +drop type CARD_SUIT; +> ok + +--- ENUM with index +create type CARD_SUIT as enum('hearts', 'clubs', 'spades', 'diamonds'); +> ok + +create table card (rank int, suit CARD_SUIT, primary key(rank, suit)); +> ok + +insert into card (rank, suit) values (0, 'clubs'), (3, 'hearts'), (1, 'clubs'); +> update count: 3 + +create index idx_card_suite on card(`suit`); +> ok + +select rank from card where suit = 'clubs'; +> RANK +> ---- +> 0 +> 1 +> rows: 2 + +select rank from card where suit in ('clubs'); +> RANK +> ---- +> 0 +> 1 +> rows: 2 + +insert into card values (2, 'diamonds'); +> update count: 1 + +select rank from card where suit in ('clubs', 'hearts'); +> RANK +> ---- +> 0 +> 1 +> 3 +> rows: 3 + +select rank from card where suit in ('clubs', 'hearts') or suit = 'diamonds'; +> RANK +> ---- +> 0 +> 1 +> 2 +> 3 +> rows: 4 + +drop table card; +> ok + +drop type CARD_SUIT; +> ok + +CREATE TABLE TEST(ID INT, E1 ENUM('A', 'B') DEFAULT 'A', E2 ENUM('C', 'D') DEFAULT 'C' ON UPDATE 'D'); +> ok + +INSERT INTO TEST(ID) VALUES (1); +> update count: 1 + +SELECT * FROM TEST; +> ID E1 E2 +> -- -- -- +> 1 A C +> rows: 1 + +UPDATE TEST SET E1 = 'B'; +> update count: 1 + +SELECT * FROM TEST; +> ID E1 E2 +> -- -- -- +> 1 B D +> rows: 1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(E ENUM('A', 'B')); +> ok + +INSERT INTO TEST VALUES ('B'); +> update count: 1 + +CREATE VIEW V AS SELECT * FROM TEST; +> ok + +SELECT * FROM V; +>> B + +CREATE VIEW V1 AS SELECT E + 2 AS E FROM TEST; +> ok + +SELECT * FROM V1; +>> 4 + +CREATE VIEW V2 AS SELECT E + E AS E FROM TEST; +> ok + +SELECT * FROM V2; +>> 4 + +CREATE VIEW V3 AS SELECT -E AS E FROM TEST; +> ok + +SELECT * FROM V3; +>> -2 + +SELECT TABLE_NAME, DATA_TYPE + FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME = 'E' ORDER BY TABLE_NAME; +> TABLE_NAME DATA_TYPE +> ---------- --------- +> TEST ENUM +> V ENUM +> V1 INTEGER +> V2 INTEGER +> V3 INTEGER +> rows (ordered): 5 + +SELECT OBJECT_NAME, OBJECT_TYPE, ENUM_IDENTIFIER, VALUE_NAME, VALUE_ORDINAL FROM INFORMATION_SCHEMA.ENUM_VALUES + WHERE OBJECT_SCHEMA = 'PUBLIC'; +> OBJECT_NAME OBJECT_TYPE ENUM_IDENTIFIER VALUE_NAME VALUE_ORDINAL +> ----------- ----------- --------------- ---------- ------------- +> TEST TABLE 1 A 1 +> TEST TABLE 1 B 2 +> V TABLE 1 A 1 +> V TABLE 1 B 2 +> rows: 4 + +DROP VIEW V; +> ok + +DROP VIEW V1; +> ok + +DROP VIEW V2; +> ok + +DROP VIEW V3; +> ok + +DROP TABLE TEST; +> ok + +SELECT CAST (2 AS ENUM('a', 'b', 'c', 'd')); +>> b + +CREATE TABLE TEST(E ENUM('a', 'b')); +> ok + +EXPLAIN SELECT * FROM TEST WHERE E = 'a'; +>> SELECT "PUBLIC"."TEST"."E" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ WHERE "E" = CAST('a' AS ENUM('a', 'b')) + +INSERT INTO TEST VALUES ('a'); +> update count: 1 + +(SELECT * FROM TEST A) UNION ALL (SELECT * FROM TEST A); +> E +> - +> a +> a +> rows: 2 + +(SELECT * FROM TEST A) MINUS (SELECT * FROM TEST A); +> E +> - +> rows: 0 + +DROP TABLE TEST; +> ok + +EXPLAIN VALUES CAST('A' AS ENUM('A', 'B')); +>> VALUES (CAST('A' AS ENUM('A', 'B'))) + +CREATE TABLE TEST(E1 ENUM('a', 'b'), E2 ENUM('e', 'c') ARRAY, E3 ROW(E ENUM('x', 'y'))); +> ok + +SELECT COLUMN_NAME, DATA_TYPE, DTD_IDENTIFIER FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST'; +> COLUMN_NAME DATA_TYPE DTD_IDENTIFIER +> ----------- --------- -------------- +> E1 ENUM 1 +> E2 ARRAY 2 +> E3 ROW 3 +> rows: 3 + +SELECT COLLECTION_TYPE_IDENTIFIER, DATA_TYPE, DTD_IDENTIFIER FROM INFORMATION_SCHEMA.ELEMENT_TYPES WHERE OBJECT_NAME = 'TEST'; +> COLLECTION_TYPE_IDENTIFIER DATA_TYPE DTD_IDENTIFIER +> -------------------------- --------- -------------- +> 2 ENUM 2_ +> rows: 1 + +SELECT ROW_IDENTIFIER, FIELD_NAME, DATA_TYPE, DTD_IDENTIFIER FROM INFORMATION_SCHEMA.FIELDS WHERE OBJECT_NAME = 'TEST'; +> ROW_IDENTIFIER FIELD_NAME DATA_TYPE DTD_IDENTIFIER +> -------------- ---------- --------- -------------- +> 3 E ENUM 3_1 +> rows: 1 + +SELECT * FROM INFORMATION_SCHEMA.ENUM_VALUES WHERE OBJECT_NAME = 'TEST'; +> OBJECT_CATALOG OBJECT_SCHEMA OBJECT_NAME OBJECT_TYPE ENUM_IDENTIFIER VALUE_NAME VALUE_ORDINAL +> -------------- ------------- ----------- ----------- --------------- ---------- ------------- +> SCRIPT PUBLIC TEST TABLE 1 a 1 +> SCRIPT PUBLIC TEST TABLE 1 b 2 +> SCRIPT PUBLIC TEST TABLE 2_ c 2 +> SCRIPT PUBLIC TEST TABLE 2_ e 1 +> SCRIPT PUBLIC TEST TABLE 3_1 x 1 +> SCRIPT PUBLIC TEST TABLE 3_1 y 2 +> rows: 6 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A ENUM('A', 'B') ARRAY, B ROW(V ENUM('C', 'D'))); +> ok + +INSERT INTO TEST VALUES (ARRAY['A', 'B'], ROW('C')); +> update count: 1 + +TABLE TEST; +> A B +> ------ ------- +> [A, B] ROW (C) +> rows: 1 + +@reconnect + +TABLE TEST; +> A B +> ------ ------- +> [A, B] ROW (C) +> rows: 1 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/datatypes/geometry.sql b/h2/src/test/org/h2/test/scripts/datatypes/geometry.sql new file mode 100644 index 0000000..14a3522 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/geometry.sql @@ -0,0 +1,322 @@ +-- 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 +-- + +CREATE TABLE TEST(G GEOMETRY, G_S GEOMETRY(GEOMETRY, 1), P GEOMETRY(POINT), P_S GEOMETRY(POINT, 1), + PZ1 GEOMETRY(POINT Z), PZ2 GEOMETRY(POINTZ), PZ1_S GEOMETRY(POINT Z, 1), PZ2_S GEOMETRY(POINTZ, 1), + PM GEOMETRY(POINT M), PZM GEOMETRY(POINT ZM), PZM_S GEOMETRY(POINT ZM, -100), + LS GEOMETRY(LINESTRING), PG GEOMETRY(POLYGON), + MP GEOMETRY(MULTIPOINT), MLS GEOMETRY(MULTILINESTRING), MPG GEOMETRY(MULTIPOLYGON), + GC GEOMETRY(GEOMETRYCOLLECTION)); +> ok + +INSERT INTO TEST VALUES ('POINT EMPTY', 'SRID=1;POINT EMPTY', 'POINT EMPTY', 'SRID=1;POINT EMPTY', + 'POINT Z EMPTY', 'POINT Z EMPTY', 'SRID=1;POINT Z EMPTY', 'SRID=1;POINTZ EMPTY', + 'POINT M EMPTY', 'POINT ZM EMPTY', 'SRID=-100;POINT ZM EMPTY', + 'LINESTRING EMPTY', 'POLYGON EMPTY', + 'MULTIPOINT EMPTY', 'MULTILINESTRING EMPTY', 'MULTIPOLYGON EMPTY', + 'GEOMETRYCOLLECTION EMPTY'); +> update count: 1 + +SELECT COLUMN_NAME, DATA_TYPE, GEOMETRY_TYPE, GEOMETRY_SRID FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DATA_TYPE GEOMETRY_TYPE GEOMETRY_SRID +> ----------- --------- ------------------ ------------- +> G GEOMETRY null null +> G_S GEOMETRY null 1 +> P GEOMETRY POINT null +> P_S GEOMETRY POINT 1 +> PZ1 GEOMETRY POINT Z null +> PZ2 GEOMETRY POINT Z null +> PZ1_S GEOMETRY POINT Z 1 +> PZ2_S GEOMETRY POINT Z 1 +> PM GEOMETRY POINT M null +> PZM GEOMETRY POINT ZM null +> PZM_S GEOMETRY POINT ZM -100 +> LS GEOMETRY LINESTRING null +> PG GEOMETRY POLYGON null +> MP GEOMETRY MULTIPOINT null +> MLS GEOMETRY MULTILINESTRING null +> MPG GEOMETRY MULTIPOLYGON null +> GC GEOMETRY GEOMETRYCOLLECTION null +> rows (ordered): 17 + +UPDATE TEST SET G = 'SRID=10;LINESTRING EMPTY'; +> update count: 1 + +UPDATE TEST SET GC = 'SRID=8;GEOMETRYCOLLECTION(POINT (1 1))'; +> update count: 1 + +UPDATE TEST SET G_S = 'POINT (1 1)'; +> exception DATA_CONVERSION_ERROR_1 + +UPDATE TEST SET P = 'POINT Z EMPTY'; +> exception DATA_CONVERSION_ERROR_1 + +UPDATE TEST SET P = 'POLYGON EMPTY'; +> exception DATA_CONVERSION_ERROR_1 + +UPDATE TEST SET PZ1 = 'POINT EMPTY'; +> exception DATA_CONVERSION_ERROR_1 + +SELECT * FROM TEST; +> G G_S P P_S PZ1 PZ2 PZ1_S PZ2_S PM PZM PZM_S LS PG MP MLS MPG GC +> ------------------------ ------------------ ----------- ------------------ ------------- ------------- -------------------- -------------------- ------------- -------------- ------------------------ ---------------- ------------- ---------------- --------------------- ------------------ --------------------------------------- +> SRID=10;LINESTRING EMPTY SRID=1;POINT EMPTY POINT EMPTY SRID=1;POINT EMPTY POINT Z EMPTY POINT Z EMPTY SRID=1;POINT Z EMPTY SRID=1;POINT Z EMPTY POINT M EMPTY POINT ZM EMPTY SRID=-100;POINT ZM EMPTY LINESTRING EMPTY POLYGON EMPTY MULTIPOINT EMPTY MULTILINESTRING EMPTY MULTIPOLYGON EMPTY SRID=8;GEOMETRYCOLLECTION (POINT (1 1)) +> rows: 1 + +SELECT G FROM TEST WHERE P_S = 'SRID=1;POINT EMPTY'; +>> SRID=10;LINESTRING EMPTY + +SELECT G FROM TEST WHERE P_S = 'GEOMETRYCOLLECTION Z EMPTY'; +> exception DATA_CONVERSION_ERROR_1 + +CREATE SPATIAL INDEX IDX ON TEST(GC); +> ok + +SELECT P FROM TEST WHERE GC = 'SRID=8;GEOMETRYCOLLECTION (POINT (1 1))'; +>> POINT EMPTY + +SELECT P FROM TEST WHERE GC = 'SRID=8;GEOMETRYCOLLECTION Z (POINT (1 1 1))'; +> exception DATA_CONVERSION_ERROR_1 + +SELECT CAST('POINT EMPTY' AS GEOMETRY(POINT)); +>> POINT EMPTY + +SELECT CAST('POINT EMPTY' AS GEOMETRY(POINT Z)); +> exception DATA_CONVERSION_ERROR_1 + +SELECT CAST('POINT EMPTY' AS GEOMETRY(POINT, 0)); +>> POINT EMPTY + +SELECT CAST('POINT EMPTY' AS GEOMETRY(POINT, 1)); +> exception DATA_CONVERSION_ERROR_1 + +SELECT CAST('POINT EMPTY' AS GEOMETRY(POLYGON)); +> exception DATA_CONVERSION_ERROR_1 + +DROP TABLE TEST; +> ok + +SELECT CAST('POINT EMPTY'::GEOMETRY AS JSON); +>> null + +SELECT CAST('null' FORMAT JSON AS GEOMETRY); +>> POINT EMPTY + +SELECT CAST('POINT (1 2)'::GEOMETRY AS JSON); +>> {"type":"Point","coordinates":[1,2]} + +SELECT CAST('{"type":"Point","coordinates":[1,2]}' FORMAT JSON AS GEOMETRY); +>> POINT (1 2) + +SELECT CAST('POINT Z (1 2 3)'::GEOMETRY AS JSON); +>> {"type":"Point","coordinates":[1,2,3]} + +SELECT CAST('{"type":"Point","coordinates":[1,2,3]}' FORMAT JSON AS GEOMETRY); +>> POINT Z (1 2 3) + +SELECT CAST('POINT ZM (1 2 3 4)'::GEOMETRY AS JSON); +>> {"type":"Point","coordinates":[1,2,3,4]} + +SELECT CAST('{"type":"Point","coordinates":[1,2,3,4]}' FORMAT JSON AS GEOMETRY); +>> POINT ZM (1 2 3 4) + +SELECT CAST('POINT M (1 2 4)'::GEOMETRY AS JSON); +> exception DATA_CONVERSION_ERROR_1 + +SELECT CAST('SRID=4326;POINT (1 2)'::GEOMETRY AS JSON); +>> {"type":"Point","coordinates":[1,2]} + +SELECT CAST('{"type":"Point","coordinates":[1,2]}' FORMAT JSON AS GEOMETRY(POINT)); +>> POINT (1 2) + +SELECT CAST('{"type":"Point","coordinates":[1,2]}' FORMAT JSON AS GEOMETRY(GEOMETRY, 4326)); +>> SRID=4326;POINT (1 2) + +SELECT CAST('LINESTRING EMPTY'::GEOMETRY AS JSON); +>> {"type":"LineString","coordinates":[]} + +SELECT CAST('{"type":"LineString","coordinates":[]}' FORMAT JSON AS GEOMETRY); +>> LINESTRING EMPTY + +SELECT CAST('LINESTRING (1 2, 3 4)'::GEOMETRY AS JSON); +>> {"type":"LineString","coordinates":[[1,2],[3,4]]} + +SELECT CAST('{"type":"LineString","coordinates":[[1,2],[3,4]]}' FORMAT JSON AS GEOMETRY); +>> LINESTRING (1 2, 3 4) + +SELECT CAST('POLYGON EMPTY'::GEOMETRY AS JSON); +>> {"type":"Polygon","coordinates":[]} + +SELECT CAST('{"type":"Polygon","coordinates":[]}' FORMAT JSON AS GEOMETRY); +>> POLYGON EMPTY + +SELECT CAST('POLYGON ((-1 -2, 10 1, 2 20, -1 -2))'::GEOMETRY AS JSON); +>> {"type":"Polygon","coordinates":[[[-1,-2],[1E1,1],[2,2E1],[-1,-2]]]} + +SELECT CAST('{"type":"Polygon","coordinates":[[[-1,-2],[1E1,1],[2,2E1],[-1,-2]]]}' FORMAT JSON AS GEOMETRY); +>> POLYGON ((-1 -2, 10 1, 2 20, -1 -2)) + +SELECT CAST('POLYGON ((-1 -2, 10 1, 2 20, -1 -2), (0.5 0.5, 1 0.5, 1 1, 0.5 0.5), EMPTY)'::GEOMETRY AS JSON); +>> {"type":"Polygon","coordinates":[[[-1,-2],[1E1,1],[2,2E1],[-1,-2]],[[0.5,0.5],[1,0.5],[1,1],[0.5,0.5]],[]]} + +SELECT CAST('{"type":"Polygon","coordinates":[[[-1,-2],[1E1,1],[2,2E1],[-1,-2]],[[0.5,0.5],[1,0.5],[1,1],[0.5,0.5]],[]]}' FORMAT JSON AS GEOMETRY); +>> POLYGON ((-1 -2, 10 1, 2 20, -1 -2), (0.5 0.5, 1 0.5, 1 1, 0.5 0.5), EMPTY) + +SELECT CAST('MULTIPOINT EMPTY'::GEOMETRY AS JSON); +>> {"type":"MultiPoint","coordinates":[]} + +SELECT CAST('{"type":"MultiPoint","coordinates":[]}' FORMAT JSON AS GEOMETRY); +>> MULTIPOINT EMPTY + +SELECT CAST('MULTIPOINT ((1 2))'::GEOMETRY AS JSON); +>> {"type":"MultiPoint","coordinates":[[1,2]]} + +SELECT CAST('{"type":"MultiPoint","coordinates":[[1,2]]}' FORMAT JSON AS GEOMETRY); +>> MULTIPOINT ((1 2)) + +SELECT CAST('MULTIPOINT ((1 2), (3 4))'::GEOMETRY AS JSON); +>> {"type":"MultiPoint","coordinates":[[1,2],[3,4]]} + +SELECT CAST('{"type":"MultiPoint","coordinates":[[1,2],[3,4]]}' FORMAT JSON AS GEOMETRY); +>> MULTIPOINT ((1 2), (3 4)) + +SELECT CAST('MULTIPOINT ((1 0), EMPTY, EMPTY, (2 2))'::GEOMETRY AS JSON); +>> {"type":"MultiPoint","coordinates":[[1,0],null,null,[2,2]]} + +SELECT CAST('{"type":"MultiPoint","coordinates":[[1,0],null,null,[2,2]]}' FORMAT JSON AS GEOMETRY); +>> MULTIPOINT ((1 0), EMPTY, EMPTY, (2 2)) + +SELECT CAST('MULTILINESTRING EMPTY'::GEOMETRY AS JSON); +>> {"type":"MultiLineString","coordinates":[]} + +SELECT CAST('{"type":"MultiLineString","coordinates":[]}' FORMAT JSON AS GEOMETRY); +>> MULTILINESTRING EMPTY + +SELECT CAST('MULTILINESTRING ((1 2, 3 4, 5 7))'::GEOMETRY AS JSON); +>> {"type":"MultiLineString","coordinates":[[[1,2],[3,4],[5,7]]]} + +SELECT CAST('{"type":"MultiLineString","coordinates":[[[1,2],[3,4],[5,7]]]}' FORMAT JSON AS GEOMETRY); +>> MULTILINESTRING ((1 2, 3 4, 5 7)) + +SELECT CAST('MULTILINESTRING ((1 2, 3 4, 5 7), (-1 -1, 0 0, 2 2, 4 6.01), EMPTY)'::GEOMETRY AS JSON); +>> {"type":"MultiLineString","coordinates":[[[1,2],[3,4],[5,7]],[[-1,-1],[0,0],[2,2],[4,6.01]],[]]} + +SELECT CAST('{"type":"MultiLineString","coordinates":[[[1,2],[3,4],[5,7]],[[-1,-1],[0,0],[2,2],[4,6.01]],[]]}' FORMAT JSON AS GEOMETRY); +>> MULTILINESTRING ((1 2, 3 4, 5 7), (-1 -1, 0 0, 2 2, 4 6.01), EMPTY) + +SELECT CAST('MULTIPOLYGON EMPTY'::GEOMETRY AS JSON); +>> {"type":"MultiPolygon","coordinates":[]} + +SELECT CAST('{"type":"MultiPolygon","coordinates":[]}' FORMAT JSON AS GEOMETRY); +>> MULTIPOLYGON EMPTY + +SELECT CAST('MULTIPOLYGON (((-1 -2, 10 1, 2 20, -1 -2)))'::GEOMETRY AS JSON); +>> {"type":"MultiPolygon","coordinates":[[[[-1,-2],[1E1,1],[2,2E1],[-1,-2]]]]} + +SELECT CAST('{"type":"MultiPolygon","coordinates":[[[[-1,-2],[1E1,1],[2,2E1],[-1,-2]]]]}' FORMAT JSON AS GEOMETRY); +>> MULTIPOLYGON (((-1 -2, 10 1, 2 20, -1 -2))) + +SELECT CAST('MULTIPOLYGON (((-1 -2, 10 1, 2 20, -1 -2)), ((1 2, 2 2, 3 3, 1 2)))'::GEOMETRY AS JSON); +>> {"type":"MultiPolygon","coordinates":[[[[-1,-2],[1E1,1],[2,2E1],[-1,-2]]],[[[1,2],[2,2],[3,3],[1,2]]]]} + +SELECT CAST('{"type":"MultiPolygon","coordinates":[[[[-1,-2],[1E1,1],[2,2E1],[-1,-2]]],[[[1,2],[2,2],[3,3],[1,2]]]]}' FORMAT JSON AS GEOMETRY); +>> MULTIPOLYGON (((-1 -2, 10 1, 2 20, -1 -2)), ((1 2, 2 2, 3 3, 1 2))) + +SELECT CAST('MULTIPOLYGON (((-1 -2, 10 1, 2 20, -1 -2), (0.5 0.5, 1 0.5, 1 1, 0.5 0.5)))'::GEOMETRY AS JSON); +>> {"type":"MultiPolygon","coordinates":[[[[-1,-2],[1E1,1],[2,2E1],[-1,-2]],[[0.5,0.5],[1,0.5],[1,1],[0.5,0.5]]]]} + +SELECT CAST('{"type":"MultiPolygon","coordinates":[[[[-1,-2],[1E1,1],[2,2E1],[-1,-2]],[[0.5,0.5],[1,0.5],[1,1],[0.5,0.5]]]]}' FORMAT JSON AS GEOMETRY); +>> MULTIPOLYGON (((-1 -2, 10 1, 2 20, -1 -2), (0.5 0.5, 1 0.5, 1 1, 0.5 0.5))) + +SELECT CAST('GEOMETRYCOLLECTION EMPTY'::GEOMETRY AS JSON); +>> {"type":"GeometryCollection","geometries":[]} + +SELECT CAST('{"type":"GeometryCollection","geometries":[]}' FORMAT JSON AS GEOMETRY); +>> GEOMETRYCOLLECTION EMPTY + +SELECT CAST('GEOMETRYCOLLECTION (POINT (1 2))'::GEOMETRY AS JSON); +>> {"type":"GeometryCollection","geometries":[{"type":"Point","coordinates":[1,2]}]} + +SELECT CAST('{"type":"GeometryCollection","geometries":[{"type":"Point","coordinates":[1,2]}]}' FORMAT JSON AS GEOMETRY); +>> GEOMETRYCOLLECTION (POINT (1 2)) + +SELECT CAST('GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (POINT (1 3)), MULTIPOINT ((4 8)))'::GEOMETRY AS JSON); +>> {"type":"GeometryCollection","geometries":[{"type":"GeometryCollection","geometries":[{"type":"Point","coordinates":[1,3]}]},{"type":"MultiPoint","coordinates":[[4,8]]}]} + +SELECT CAST('{"type":"GeometryCollection","geometries":[{"type":"GeometryCollection","geometries":[{"type":"Point","coordinates":[1,3]}]},{"type":"MultiPoint","coordinates":[[4,8]]}]}' FORMAT JSON AS GEOMETRY); +>> GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (POINT (1 3)), MULTIPOINT ((4 8))) + +SELECT CAST('{"type":"Unknown","coordinates":[1,2]}' FORMAT JSON AS GEOMETRY); +> exception DATA_CONVERSION_ERROR_1 + +EXPLAIN VALUES GEOMETRY 'POINT EMPTY'; +>> VALUES (GEOMETRY 'POINT EMPTY') + +EXPLAIN VALUES GEOMETRY X'00000000017ff80000000000007ff8000000000000'; +>> VALUES (GEOMETRY 'POINT EMPTY') + +EXPLAIN VALUES CAST(CAST('POINT EMPTY' AS GEOMETRY) AS VARBINARY); +>> VALUES (CAST(X'00000000017ff80000000000007ff8000000000000' AS BINARY VARYING)) + +SELECT GEOMETRY X'000000000300000000'; +>> POLYGON EMPTY + +SELECT GEOMETRY X'00000000030000000100000000'; +>> POLYGON EMPTY + +SELECT CAST(GEOMETRY 'POLYGON EMPTY' AS VARBINARY); +>> X'000000000300000000' + +SELECT CAST(GEOMETRY X'00000000030000000100000000' AS VARBINARY); +>> X'000000000300000000' + +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 diff --git a/h2/src/test/org/h2/test/scripts/datatypes/identity.sql b/h2/src/test/org/h2/test/scripts/datatypes/identity.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/identity.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/datatypes/int.sql b/h2/src/test/org/h2/test/scripts/datatypes/int.sql new file mode 100644 index 0000000..266abcc --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/int.sql @@ -0,0 +1,18 @@ +-- 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 +-- + +-- Division + +SELECT CAST(1 AS INT) / CAST(0 AS INT); +> exception DIVISION_BY_ZERO_1 + +SELECT CAST(-2147483648 AS INT) / CAST(1 AS INT); +>> -2147483648 + +SELECT CAST(-2147483648 AS INT) / CAST(-1 AS INT); +> exception NUMERIC_VALUE_OUT_OF_RANGE_1 + +EXPLAIN VALUES 1; +>> VALUES (1) diff --git a/h2/src/test/org/h2/test/scripts/datatypes/interval.sql b/h2/src/test/org/h2/test/scripts/datatypes/interval.sql new file mode 100644 index 0000000..b08bc3c --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/interval.sql @@ -0,0 +1,1105 @@ +-- 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 +-- + +CREATE TABLE TEST(ID INT PRIMARY KEY, + I01 INTERVAL YEAR, I02 INTERVAL MONTH, I03 INTERVAL DAY, I04 INTERVAL HOUR, I05 INTERVAL MINUTE, + I06 INTERVAL SECOND, I07 INTERVAL YEAR TO MONTH, I08 INTERVAL DAY TO HOUR, I09 INTERVAL DAY TO MINUTE, + I10 INTERVAL DAY TO SECOND, I11 INTERVAL HOUR TO MINUTE, I12 INTERVAL HOUR TO SECOND, + I13 INTERVAL MINUTE TO SECOND, + J01 INTERVAL YEAR(5), J02 INTERVAL MONTH(5), J03 INTERVAL DAY(5), J04 INTERVAL HOUR(5), J05 INTERVAL MINUTE(5), + J06 INTERVAL SECOND(5, 9), J07 INTERVAL YEAR(5) TO MONTH, J08 INTERVAL DAY(5) TO HOUR, + J09 INTERVAL DAY(5) TO MINUTE, J10 INTERVAL DAY(5) TO SECOND(9), J11 INTERVAL HOUR(5) TO MINUTE, + J12 INTERVAL HOUR(5) TO SECOND(9), J13 INTERVAL MINUTE(5) TO SECOND(9)); +> ok + +SELECT COLUMN_NAME, DATA_TYPE, DATETIME_PRECISION, INTERVAL_TYPE, INTERVAL_PRECISION + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DATA_TYPE DATETIME_PRECISION INTERVAL_TYPE INTERVAL_PRECISION +> ----------- --------- ------------------ ---------------- ------------------ +> ID INTEGER null null null +> I01 INTERVAL 0 YEAR 2 +> I02 INTERVAL 0 MONTH 2 +> I03 INTERVAL 0 DAY 2 +> I04 INTERVAL 0 HOUR 2 +> I05 INTERVAL 0 MINUTE 2 +> I06 INTERVAL 6 SECOND 2 +> I07 INTERVAL 0 YEAR TO MONTH 2 +> I08 INTERVAL 0 DAY TO HOUR 2 +> I09 INTERVAL 0 DAY TO MINUTE 2 +> I10 INTERVAL 6 DAY TO SECOND 2 +> I11 INTERVAL 0 HOUR TO MINUTE 2 +> I12 INTERVAL 6 HOUR TO SECOND 2 +> I13 INTERVAL 6 MINUTE TO SECOND 2 +> J01 INTERVAL 0 YEAR 5 +> J02 INTERVAL 0 MONTH 5 +> J03 INTERVAL 0 DAY 5 +> J04 INTERVAL 0 HOUR 5 +> J05 INTERVAL 0 MINUTE 5 +> J06 INTERVAL 9 SECOND 5 +> J07 INTERVAL 0 YEAR TO MONTH 5 +> J08 INTERVAL 0 DAY TO HOUR 5 +> J09 INTERVAL 0 DAY TO MINUTE 5 +> J10 INTERVAL 9 DAY TO SECOND 5 +> J11 INTERVAL 0 HOUR TO MINUTE 5 +> J12 INTERVAL 9 HOUR TO SECOND 5 +> J13 INTERVAL 9 MINUTE TO SECOND 5 +> rows (ordered): 27 + +INSERT INTO TEST VALUES ( + 1, + INTERVAL '1' YEAR, INTERVAL '1' MONTH, INTERVAL '1' DAY, INTERVAL '1' HOUR, INTERVAL '1' MINUTE, + INTERVAL '1.123456789' SECOND, INTERVAL '1-2' YEAR TO MONTH, INTERVAL '1 2' DAY TO HOUR, + INTERVAL '1 2:3' DAY TO MINUTE, INTERVAL '1 2:3:4.123456789' DAY TO SECOND, INTERVAL '1:2' HOUR TO MINUTE, + INTERVAL '1:2:3.123456789' HOUR TO SECOND, INTERVAL '1:2.123456789' MINUTE TO SECOND, + INTERVAL '1' YEAR, INTERVAL '1' MONTH, INTERVAL '1' DAY, INTERVAL '1' HOUR, INTERVAL '1' MINUTE, + INTERVAL '1.123456789' SECOND, INTERVAL '1-2' YEAR TO MONTH, INTERVAL '1 2' DAY TO HOUR, + INTERVAL '1 2:3' DAY TO MINUTE, INTERVAL '1 2:3:4.123456789' DAY TO SECOND, INTERVAL '1:2' HOUR TO MINUTE, + INTERVAL '1:2:3.123456789' HOUR TO SECOND, INTERVAL '1:2.123456789' MINUTE TO SECOND + ), ( + 2, + INTERVAL '-1' YEAR, INTERVAL '-1' MONTH, INTERVAL '-1' DAY, INTERVAL '-1' HOUR, INTERVAL '-1' MINUTE, + INTERVAL '-1.123456789' SECOND, INTERVAL '-1-2' YEAR TO MONTH, INTERVAL '-1 2' DAY TO HOUR, + INTERVAL '-1 2:3' DAY TO MINUTE, INTERVAL '-1 2:3:4.123456789' DAY TO SECOND, INTERVAL '-1:2' HOUR TO MINUTE, + INTERVAL '-1:2:3.123456789' HOUR TO SECOND, INTERVAL '-1:2.123456789' MINUTE TO SECOND, + INTERVAL -'1' YEAR, INTERVAL -'1' MONTH, INTERVAL -'1' DAY, INTERVAL -'1' HOUR, INTERVAL -'1' MINUTE, + INTERVAL -'1.123456789' SECOND, INTERVAL -'1-2' YEAR TO MONTH, INTERVAL -'1 2' DAY TO HOUR, + INTERVAL -'1 2:3' DAY TO MINUTE, INTERVAL -'1 2:3:4.123456789' DAY TO SECOND, INTERVAL -'1:2' HOUR TO MINUTE, + INTERVAL -'1:2:3.123456789' HOUR TO SECOND, INTERVAL -'1:2.123456789' MINUTE TO SECOND); +> update count: 2 + +@reconnect + +SELECT I01, I02, I03, I04, I05, I06 FROM TEST ORDER BY ID; +> I01 I02 I03 I04 I05 I06 +> ------------------ ------------------- ----------------- ------------------ -------------------- --------------------------- +> INTERVAL '1' YEAR INTERVAL '1' MONTH INTERVAL '1' DAY INTERVAL '1' HOUR INTERVAL '1' MINUTE INTERVAL '1.123457' SECOND +> INTERVAL '-1' YEAR INTERVAL '-1' MONTH INTERVAL '-1' DAY INTERVAL '-1' HOUR INTERVAL '-1' MINUTE INTERVAL '-1.123457' SECOND +> rows (ordered): 2 + +SELECT I07, I08, I09, I10 FROM TEST ORDER BY ID; +> I07 I08 I09 I10 +> ----------------------------- ---------------------------- --------------------------------- ------------------------------------------- +> INTERVAL '1-2' YEAR TO MONTH INTERVAL '1 02' DAY TO HOUR INTERVAL '1 02:03' DAY TO MINUTE INTERVAL '1 02:03:04.123457' DAY TO SECOND +> INTERVAL '-1-2' YEAR TO MONTH INTERVAL '-1 02' DAY TO HOUR INTERVAL '-1 02:03' DAY TO MINUTE INTERVAL '-1 02:03:04.123457' DAY TO SECOND +> rows (ordered): 2 + +SELECT I11, I12, I12 FROM TEST ORDER BY ID; +> I11 I12 I12 +> ------------------------------- ----------------------------------------- ----------------------------------------- +> INTERVAL '1:02' HOUR TO MINUTE INTERVAL '1:02:03.123457' HOUR TO SECOND INTERVAL '1:02:03.123457' HOUR TO SECOND +> INTERVAL '-1:02' HOUR TO MINUTE INTERVAL '-1:02:03.123457' HOUR TO SECOND INTERVAL '-1:02:03.123457' HOUR TO SECOND +> rows (ordered): 2 + +SELECT J01, J02, J03, J04, J05, J06 FROM TEST ORDER BY ID; +> J01 J02 J03 J04 J05 J06 +> ------------------ ------------------- ----------------- ------------------ -------------------- ------------------------------ +> INTERVAL '1' YEAR INTERVAL '1' MONTH INTERVAL '1' DAY INTERVAL '1' HOUR INTERVAL '1' MINUTE INTERVAL '1.123456789' SECOND +> INTERVAL '-1' YEAR INTERVAL '-1' MONTH INTERVAL '-1' DAY INTERVAL '-1' HOUR INTERVAL '-1' MINUTE INTERVAL '-1.123456789' SECOND +> rows (ordered): 2 + +SELECT J07, J08, J09, J10 FROM TEST ORDER BY ID; +> J07 J08 J09 J10 +> ----------------------------- ---------------------------- --------------------------------- ---------------------------------------------- +> INTERVAL '1-2' YEAR TO MONTH INTERVAL '1 02' DAY TO HOUR INTERVAL '1 02:03' DAY TO MINUTE INTERVAL '1 02:03:04.123456789' DAY TO SECOND +> INTERVAL '-1-2' YEAR TO MONTH INTERVAL '-1 02' DAY TO HOUR INTERVAL '-1 02:03' DAY TO MINUTE INTERVAL '-1 02:03:04.123456789' DAY TO SECOND +> rows (ordered): 2 + +SELECT J11, J12, J12 FROM TEST ORDER BY ID; +> J11 J12 J12 +> ------------------------------- -------------------------------------------- -------------------------------------------- +> INTERVAL '1:02' HOUR TO MINUTE INTERVAL '1:02:03.123456789' HOUR TO SECOND INTERVAL '1:02:03.123456789' HOUR TO SECOND +> INTERVAL '-1:02' HOUR TO MINUTE INTERVAL '-1:02:03.123456789' HOUR TO SECOND INTERVAL '-1:02:03.123456789' HOUR TO SECOND +> rows (ordered): 2 + +DROP TABLE TEST; +> ok + +-- Year-month casts + +SELECT CAST(INTERVAL '-10' YEAR AS INTERVAL MONTH(3)); +>> INTERVAL '-120' MONTH + +SELECT CAST(INTERVAL '-10' YEAR AS INTERVAL YEAR TO MONTH); +>> INTERVAL '-10-0' YEAR TO MONTH + +SELECT CAST(INTERVAL '-20' MONTH AS INTERVAL YEAR); +>> INTERVAL '-1' YEAR + +SELECT CAST(INTERVAL '-20' MONTH AS INTERVAL YEAR TO MONTH); +>> INTERVAL '-1-8' YEAR TO MONTH + +SELECT CAST(INTERVAL '-20-10' YEAR TO MONTH AS INTERVAL YEAR); +>> INTERVAL '-20' YEAR + +SELECT CAST(INTERVAL '-20-10' YEAR TO MONTH AS INTERVAL MONTH(3)); +>> INTERVAL '-250' MONTH + +-- Day-time casts: DAY + +SELECT CAST(INTERVAL '-10' DAY AS INTERVAL HOUR(3)); +>> INTERVAL '-240' HOUR + +SELECT CAST(INTERVAL '-10' DAY AS INTERVAL MINUTE(5)); +>> INTERVAL '-14400' MINUTE + +SELECT CAST(INTERVAL '-10' DAY AS INTERVAL SECOND(6)); +>> INTERVAL '-864000' SECOND + +SELECT CAST(INTERVAL '-10' DAY AS INTERVAL DAY TO HOUR); +>> INTERVAL '-10 00' DAY TO HOUR + +SELECT CAST(INTERVAL '-10' DAY AS INTERVAL DAY TO MINUTE); +>> INTERVAL '-10 00:00' DAY TO MINUTE + +SELECT CAST(INTERVAL '-10' DAY AS INTERVAL DAY TO SECOND); +>> INTERVAL '-10 00:00:00' DAY TO SECOND + +SELECT CAST(INTERVAL '-10' DAY AS INTERVAL HOUR(3) TO MINUTE); +>> INTERVAL '-240:00' HOUR TO MINUTE + +SELECT CAST(INTERVAL '-10' DAY AS INTERVAL HOUR(3) TO SECOND); +>> INTERVAL '-240:00:00' HOUR TO SECOND + +SELECT CAST(INTERVAL '-10' DAY AS INTERVAL MINUTE(5) TO SECOND); +>> INTERVAL '-14400:00' MINUTE TO SECOND + +-- Day-time casts: HOUR + +SELECT CAST(INTERVAL '-30' HOUR AS INTERVAL DAY); +>> INTERVAL '-1' DAY + +SELECT CAST(INTERVAL '-30' HOUR AS INTERVAL MINUTE(4)); +>> INTERVAL '-1800' MINUTE + +SELECT CAST(INTERVAL '-30' HOUR AS INTERVAL SECOND(6)); +>> INTERVAL '-108000' SECOND + +SELECT CAST(INTERVAL '-30' HOUR AS INTERVAL DAY TO HOUR); +>> INTERVAL '-1 06' DAY TO HOUR + +SELECT CAST(INTERVAL '-30' HOUR AS INTERVAL DAY TO MINUTE); +>> INTERVAL '-1 06:00' DAY TO MINUTE + +SELECT CAST(INTERVAL '-30' HOUR AS INTERVAL DAY TO SECOND); +>> INTERVAL '-1 06:00:00' DAY TO SECOND + +SELECT CAST(INTERVAL '-30' HOUR AS INTERVAL HOUR TO MINUTE); +>> INTERVAL '-30:00' HOUR TO MINUTE + +SELECT CAST(INTERVAL '-30' HOUR AS INTERVAL HOUR TO SECOND); +>> INTERVAL '-30:00:00' HOUR TO SECOND + +SELECT CAST(INTERVAL '-30' HOUR AS INTERVAL MINUTE(4) TO SECOND); +>> INTERVAL '-1800:00' MINUTE TO SECOND + +-- Day-time casts: MINUTE + +SELECT CAST(INTERVAL '-1570' MINUTE AS INTERVAL DAY); +>> INTERVAL '-1' DAY + +SELECT CAST(INTERVAL '-1570' MINUTE AS INTERVAL HOUR); +>> INTERVAL '-26' HOUR + +SELECT CAST(INTERVAL '-1570' MINUTE AS INTERVAL SECOND(5)); +>> INTERVAL '-94200' SECOND + +SELECT CAST(INTERVAL '-1570' MINUTE AS INTERVAL DAY TO HOUR); +>> INTERVAL '-1 02' DAY TO HOUR + +SELECT CAST(INTERVAL '-1570' MINUTE AS INTERVAL DAY TO MINUTE); +>> INTERVAL '-1 02:10' DAY TO MINUTE + +SELECT CAST(INTERVAL '-1570' MINUTE AS INTERVAL DAY TO SECOND); +>> INTERVAL '-1 02:10:00' DAY TO SECOND + +SELECT CAST(INTERVAL '-1570' MINUTE AS INTERVAL HOUR TO MINUTE); +>> INTERVAL '-26:10' HOUR TO MINUTE + +SELECT CAST(INTERVAL '-1570' MINUTE AS INTERVAL HOUR TO SECOND); +>> INTERVAL '-26:10:00' HOUR TO SECOND + +SELECT CAST(INTERVAL '-1570' MINUTE AS INTERVAL MINUTE(4) TO SECOND); +>> INTERVAL '-1570:00' MINUTE TO SECOND + +-- Day-time casts: SECOND + +SELECT CAST(INTERVAL '-93784.123456789' SECOND AS INTERVAL DAY); +>> INTERVAL '-1' DAY + +SELECT CAST(INTERVAL '-93784.123456789' SECOND AS INTERVAL HOUR); +>> INTERVAL '-26' HOUR + +SELECT CAST(INTERVAL '-93784.123456789' SECOND AS INTERVAL MINUTE(4)); +>> INTERVAL '-1563' MINUTE + +SELECT CAST(INTERVAL '-93784.123456789' SECOND AS INTERVAL DAY TO HOUR); +>> INTERVAL '-1 02' DAY TO HOUR + +SELECT CAST(INTERVAL '-93784.123456789' SECOND AS INTERVAL DAY TO MINUTE); +>> INTERVAL '-1 02:03' DAY TO MINUTE + +SELECT CAST(INTERVAL '-93784.123456789' SECOND AS INTERVAL DAY TO SECOND); +>> INTERVAL '-1 02:03:04.123457' DAY TO SECOND + +SELECT CAST(INTERVAL '-93784.123456789' SECOND AS INTERVAL HOUR TO MINUTE); +>> INTERVAL '-26:03' HOUR TO MINUTE + +SELECT CAST(INTERVAL '-93784.123456789' SECOND AS INTERVAL HOUR TO SECOND); +>> INTERVAL '-26:03:04.123457' HOUR TO SECOND + +SELECT CAST(INTERVAL '-93784.123456789' SECOND AS INTERVAL MINUTE(4) TO SECOND); +>> INTERVAL '-1563:04.123457' MINUTE TO SECOND + +-- Day-time casts: DAY TO HOUR + +SELECT CAST(INTERVAL '-1 2' DAY TO HOUR AS INTERVAL DAY); +>> INTERVAL '-1' DAY + +SELECT CAST(INTERVAL '-1 2' DAY TO HOUR AS INTERVAL HOUR); +>> INTERVAL '-26' HOUR + +SELECT CAST(INTERVAL '-1 2' DAY TO HOUR AS INTERVAL MINUTE(4)); +>> INTERVAL '-1560' MINUTE + +SELECT CAST(INTERVAL '-1 2' DAY TO HOUR AS INTERVAL SECOND(5)); +>> INTERVAL '-93600' SECOND + +SELECT CAST(INTERVAL '-1 2' DAY TO HOUR AS INTERVAL DAY TO MINUTE); +>> INTERVAL '-1 02:00' DAY TO MINUTE + +SELECT CAST(INTERVAL '-1 2' DAY TO HOUR AS INTERVAL DAY TO SECOND); +>> INTERVAL '-1 02:00:00' DAY TO SECOND + +SELECT CAST(INTERVAL '-1 2' DAY TO HOUR AS INTERVAL HOUR TO MINUTE); +>> INTERVAL '-26:00' HOUR TO MINUTE + +SELECT CAST(INTERVAL '-1 2' DAY TO HOUR AS INTERVAL HOUR TO SECOND); +>> INTERVAL '-26:00:00' HOUR TO SECOND + +SELECT CAST(INTERVAL '-1 2' DAY TO HOUR AS INTERVAL MINUTE(4) TO SECOND); +>> INTERVAL '-1560:00' MINUTE TO SECOND + +-- Day-time casts: DAY TO MINUTE + +SELECT CAST(INTERVAL '-1 2:3' DAY TO MINUTE AS INTERVAL DAY); +>> INTERVAL '-1' DAY + +SELECT CAST(INTERVAL '-1 2:3' DAY TO MINUTE AS INTERVAL HOUR); +>> INTERVAL '-26' HOUR + +SELECT CAST(INTERVAL '-1 2:3' DAY TO MINUTE AS INTERVAL MINUTE(4)); +>> INTERVAL '-1563' MINUTE + +SELECT CAST(INTERVAL '-1 2:3' DAY TO MINUTE AS INTERVAL SECOND(5)); +>> INTERVAL '-93780' SECOND + +SELECT CAST(INTERVAL '-1 2:3' DAY TO MINUTE AS INTERVAL DAY TO HOUR); +>> INTERVAL '-1 02' DAY TO HOUR + +SELECT CAST(INTERVAL '-1 2:3' DAY TO MINUTE AS INTERVAL DAY TO SECOND); +>> INTERVAL '-1 02:03:00' DAY TO SECOND + +SELECT CAST(INTERVAL '-1 2:3' DAY TO MINUTE AS INTERVAL HOUR TO MINUTE); +>> INTERVAL '-26:03' HOUR TO MINUTE + +SELECT CAST(INTERVAL '-1 2:3' DAY TO MINUTE AS INTERVAL HOUR TO SECOND); +>> INTERVAL '-26:03:00' HOUR TO SECOND + +SELECT CAST(INTERVAL '-1 2:3' DAY TO MINUTE AS INTERVAL MINUTE(4) TO SECOND); +>> INTERVAL '-1563:00' MINUTE TO SECOND + +-- Day-time casts: DAY TO SECOND + +SELECT CAST(INTERVAL '-1 2:3:4.123456789' DAY TO SECOND AS INTERVAL DAY); +>> INTERVAL '-1' DAY + +SELECT CAST(INTERVAL '-1 2:3:4.123456789' DAY TO SECOND AS INTERVAL HOUR); +>> INTERVAL '-26' HOUR + +SELECT CAST(INTERVAL '-1 2:3:4.123456789' DAY TO SECOND AS INTERVAL MINUTE(4)); +>> INTERVAL '-1563' MINUTE + +SELECT CAST(INTERVAL '-1 2:3:4.123456789' DAY TO SECOND AS INTERVAL SECOND(5)); +>> INTERVAL '-93784.123457' SECOND + +SELECT CAST(INTERVAL '-1 2:3:4.123456789' DAY TO SECOND AS INTERVAL DAY TO HOUR); +>> INTERVAL '-1 02' DAY TO HOUR + +SELECT CAST(INTERVAL '-1 2:3:4.123456789' DAY TO SECOND AS INTERVAL DAY TO MINUTE); +>> INTERVAL '-1 02:03' DAY TO MINUTE + +SELECT CAST(INTERVAL '-1 2:3:4.123456789' DAY TO SECOND AS INTERVAL HOUR TO MINUTE); +>> INTERVAL '-26:03' HOUR TO MINUTE + +SELECT CAST(INTERVAL '-1 2:3:4.123456789' DAY TO SECOND AS INTERVAL HOUR TO SECOND); +>> INTERVAL '-26:03:04.123457' HOUR TO SECOND + +SELECT CAST(INTERVAL '-1 2:3:4.123456789' DAY TO SECOND AS INTERVAL MINUTE(4) TO SECOND); +>> INTERVAL '-1563:04.123457' MINUTE TO SECOND + +-- Day-time casts: HOUR TO MINUTE + +SELECT CAST(INTERVAL '-30:2' HOUR TO MINUTE AS INTERVAL DAY); +>> INTERVAL '-1' DAY + +SELECT CAST(INTERVAL '-30:2' HOUR TO MINUTE AS INTERVAL HOUR); +>> INTERVAL '-30' HOUR + +SELECT CAST(INTERVAL '-30:2' HOUR TO MINUTE AS INTERVAL MINUTE(4)); +>> INTERVAL '-1802' MINUTE + +SELECT CAST(INTERVAL '-30:2' HOUR TO MINUTE AS INTERVAL SECOND(6)); +>> INTERVAL '-108120' SECOND + +SELECT CAST(INTERVAL '-30:2' HOUR TO MINUTE AS INTERVAL DAY TO HOUR); +>> INTERVAL '-1 06' DAY TO HOUR + +SELECT CAST(INTERVAL '-30:2' HOUR TO MINUTE AS INTERVAL DAY TO MINUTE); +>> INTERVAL '-1 06:02' DAY TO MINUTE + +SELECT CAST(INTERVAL '-30:2' HOUR TO MINUTE AS INTERVAL DAY TO SECOND); +>> INTERVAL '-1 06:02:00' DAY TO SECOND + +SELECT CAST(INTERVAL '-30:2' HOUR TO MINUTE AS INTERVAL HOUR TO SECOND); +>> INTERVAL '-30:02:00' HOUR TO SECOND + +SELECT CAST(INTERVAL '-30:2' HOUR TO MINUTE AS INTERVAL MINUTE(4) TO SECOND); +>> INTERVAL '-1802:00' MINUTE TO SECOND + +-- Day-time casts: HOUR TO SECOND + +SELECT CAST(INTERVAL '-30:2:4.123456789' HOUR TO SECOND AS INTERVAL DAY); +>> INTERVAL '-1' DAY + +SELECT CAST(INTERVAL '-30:2:4.123456789' HOUR TO SECOND AS INTERVAL HOUR); +>> INTERVAL '-30' HOUR + +SELECT CAST(INTERVAL '-30:2:4.123456789' HOUR TO SECOND AS INTERVAL MINUTE(4)); +>> INTERVAL '-1802' MINUTE + +SELECT CAST(INTERVAL '-30:2:4.123456789' HOUR TO SECOND AS INTERVAL SECOND(6)); +>> INTERVAL '-108124.123457' SECOND + +SELECT CAST(INTERVAL '-30:2:4.123456789' HOUR TO SECOND AS INTERVAL DAY TO HOUR); +>> INTERVAL '-1 06' DAY TO HOUR + +SELECT CAST(INTERVAL '-30:2:4.123456789' HOUR TO SECOND AS INTERVAL DAY TO MINUTE); +>> INTERVAL '-1 06:02' DAY TO MINUTE + +SELECT CAST(INTERVAL '-30:2:4.123456789' HOUR TO SECOND AS INTERVAL DAY TO SECOND); +>> INTERVAL '-1 06:02:04.123457' DAY TO SECOND + +SELECT CAST(INTERVAL '-30:2:4.123456789' HOUR TO SECOND AS INTERVAL HOUR TO MINUTE); +>> INTERVAL '-30:02' HOUR TO MINUTE + +SELECT CAST(INTERVAL '-30:2:4.123456789' HOUR TO SECOND AS INTERVAL MINUTE(4) TO SECOND); +>> INTERVAL '-1802:04.123457' MINUTE TO SECOND + +-- Day-time casts: MINUTE TO SECOND + +SELECT CAST(INTERVAL '-1803:4.123456789' MINUTE TO SECOND AS INTERVAL DAY); +>> INTERVAL '-1' DAY + +SELECT CAST(INTERVAL '-1803:4.123456789' MINUTE TO SECOND AS INTERVAL HOUR); +>> INTERVAL '-30' HOUR + +SELECT CAST(INTERVAL '-1803:4.123456789' MINUTE TO SECOND AS INTERVAL MINUTE(4)); +>> INTERVAL '-1803' MINUTE + +SELECT CAST(INTERVAL '-1803:4.123456789' MINUTE TO SECOND AS INTERVAL SECOND(6)); +>> INTERVAL '-108184.123457' SECOND + +SELECT CAST(INTERVAL '-1803:4.123456789' MINUTE TO SECOND AS INTERVAL DAY TO HOUR); +>> INTERVAL '-1 06' DAY TO HOUR + +SELECT CAST(INTERVAL '-1803:4.123456789' MINUTE TO SECOND AS INTERVAL DAY TO MINUTE); +>> INTERVAL '-1 06:03' DAY TO MINUTE + +SELECT CAST(INTERVAL '-1803:4.123456789' MINUTE TO SECOND AS INTERVAL DAY TO SECOND); +>> INTERVAL '-1 06:03:04.123457' DAY TO SECOND + +SELECT CAST(INTERVAL '-1803:4.123456789' MINUTE TO SECOND AS INTERVAL HOUR TO MINUTE); +>> INTERVAL '-30:03' HOUR TO MINUTE + +SELECT CAST(INTERVAL '-1803:4.123456789' MINUTE TO SECOND AS INTERVAL HOUR TO SECOND); +>> INTERVAL '-30:03:04.123457' HOUR TO SECOND + +-- Cast with fractional seconds precision + +SELECT CAST(INTERVAL '10:11.123456789' MINUTE TO SECOND AS INTERVAL SECOND(3, 9)); +>> INTERVAL '611.123456789' SECOND + +-- Casts with strings + +SELECT CAST(INTERVAL '10' YEAR AS VARCHAR); +>> INTERVAL '10' YEAR + +SELECT CAST('INTERVAL ''10'' YEAR' AS INTERVAL YEAR); +>> INTERVAL '10' YEAR + +SELECT CAST('10' AS INTERVAL YEAR); +>> INTERVAL '10' YEAR + +SELECT CAST(INTERVAL '10' MONTH AS VARCHAR); +>> INTERVAL '10' MONTH + +SELECT CAST('INTERVAL ''10'' MONTH' AS INTERVAL MONTH); +>> INTERVAL '10' MONTH + +SELECT CAST('10' AS INTERVAL MONTH); +>> INTERVAL '10' MONTH + +SELECT CAST(INTERVAL '10' DAY AS VARCHAR); +>> INTERVAL '10' DAY + +SELECT CAST('INTERVAL ''10'' DAY' AS INTERVAL DAY); +>> INTERVAL '10' DAY + +SELECT CAST('10' AS INTERVAL DAY); +>> INTERVAL '10' DAY + +SELECT CAST(INTERVAL '10' HOUR AS VARCHAR); +>> INTERVAL '10' HOUR + +SELECT CAST('INTERVAL ''10'' HOUR' AS INTERVAL HOUR); +>> INTERVAL '10' HOUR + +SELECT CAST('10' AS INTERVAL HOUR); +>> INTERVAL '10' HOUR + +SELECT CAST(INTERVAL '10' MINUTE AS VARCHAR); +>> INTERVAL '10' MINUTE + +SELECT CAST('INTERVAL ''10'' MINUTE' AS INTERVAL MINUTE); +>> INTERVAL '10' MINUTE + +SELECT CAST('10' AS INTERVAL MINUTE); +>> INTERVAL '10' MINUTE + +SELECT CAST(INTERVAL '10.123456789' SECOND AS VARCHAR); +>> INTERVAL '10.123456789' SECOND + +SELECT CAST('INTERVAL ''10.123456789'' SECOND' AS INTERVAL SECOND(2, 9)); +>> INTERVAL '10.123456789' SECOND + +SELECT CAST('10.123456789' AS INTERVAL SECOND(2, 9)); +>> INTERVAL '10.123456789' SECOND + +SELECT CAST(INTERVAL '10-11' YEAR TO MONTH AS VARCHAR); +>> INTERVAL '10-11' YEAR TO MONTH + +SELECT CAST('INTERVAL ''10-11'' YEAR TO MONTH' AS INTERVAL YEAR TO MONTH); +>> INTERVAL '10-11' YEAR TO MONTH + +SELECT CAST('10-11' AS INTERVAL YEAR TO MONTH); +>> INTERVAL '10-11' YEAR TO MONTH + +SELECT CAST(INTERVAL '10 11' DAY TO HOUR AS VARCHAR); +>> INTERVAL '10 11' DAY TO HOUR + +SELECT CAST('INTERVAL ''10 11'' DAY TO HOUR' AS INTERVAL DAY TO HOUR); +>> INTERVAL '10 11' DAY TO HOUR + +SELECT CAST('10 11' AS INTERVAL DAY TO HOUR); +>> INTERVAL '10 11' DAY TO HOUR + +SELECT CAST(INTERVAL '10 11:12' DAY TO MINUTE AS VARCHAR); +>> INTERVAL '10 11:12' DAY TO MINUTE + +SELECT CAST('INTERVAL ''10 11:12'' DAY TO MINUTE' AS INTERVAL DAY TO MINUTE); +>> INTERVAL '10 11:12' DAY TO MINUTE + +SELECT CAST('10 11:12' AS INTERVAL DAY TO MINUTE); +>> INTERVAL '10 11:12' DAY TO MINUTE + +SELECT CAST(INTERVAL '10 11:12:13.123456789' DAY TO SECOND AS VARCHAR); +>> INTERVAL '10 11:12:13.123456789' DAY TO SECOND + +SELECT CAST('INTERVAL ''10 11:12:13.123456789'' DAY TO SECOND' AS INTERVAL DAY TO SECOND(9)); +>> INTERVAL '10 11:12:13.123456789' DAY TO SECOND + +SELECT CAST('10 11:12:13.123456789' AS INTERVAL DAY TO SECOND(9)); +>> INTERVAL '10 11:12:13.123456789' DAY TO SECOND + +SELECT CAST(INTERVAL '11:12' HOUR TO MINUTE AS VARCHAR); +>> INTERVAL '11:12' HOUR TO MINUTE + +SELECT CAST('INTERVAL ''11:12'' HOUR TO MINUTE' AS INTERVAL HOUR TO MINUTE); +>> INTERVAL '11:12' HOUR TO MINUTE + +SELECT CAST('11:12' AS INTERVAL HOUR TO MINUTE); +>> INTERVAL '11:12' HOUR TO MINUTE + +SELECT CAST(INTERVAL '11:12:13.123456789' HOUR TO SECOND AS VARCHAR); +>> INTERVAL '11:12:13.123456789' HOUR TO SECOND + +SELECT CAST('INTERVAL ''11:12:13.123456789'' HOUR TO SECOND' AS INTERVAL HOUR TO SECOND(9)); +>> INTERVAL '11:12:13.123456789' HOUR TO SECOND + +SELECT CAST('11:12:13.123456789' AS INTERVAL HOUR TO SECOND(9)); +>> INTERVAL '11:12:13.123456789' HOUR TO SECOND + +SELECT CAST(INTERVAL '12:13.123456789' MINUTE TO SECOND AS VARCHAR); +>> INTERVAL '12:13.123456789' MINUTE TO SECOND + +SELECT CAST('INTERVAL ''12:13.123456789'' MINUTE TO SECOND' AS INTERVAL MINUTE TO SECOND(9)); +>> INTERVAL '12:13.123456789' MINUTE TO SECOND + +SELECT CAST('12:13.123456789' AS INTERVAL MINUTE TO SECOND(9)); +>> INTERVAL '12:13.123456789' MINUTE TO SECOND + +-- More formats + +SELECT INTERVAL +'+10' SECOND; +>> INTERVAL '10' SECOND + +SELECT CAST('INTERVAL +''+10'' SECOND' AS INTERVAL SECOND); +>> INTERVAL '10' SECOND + +SELECT INTERVAL -'-10' HOUR; +>> INTERVAL '10' HOUR + +SELECT CAST('INTERVAL -''-10'' HOUR' AS INTERVAL HOUR); +>> INTERVAL '10' HOUR + +SELECT CAST('INTERVAL ''1'' MINUTE' AS INTERVAL SECOND); +>> INTERVAL '60' SECOND + +SELECT CAST(' interval + ''12-2'' Year To Month ' AS INTERVAL YEAR TO MONTH); +>> INTERVAL '12-2' YEAR TO MONTH + +SELECT CAST('INTERVAL''11:12''HOUR TO MINUTE' AS INTERVAL HOUR TO MINUTE); +>> INTERVAL '11:12' HOUR TO MINUTE + +SELECT INTERVAL '-0-1' YEAR TO MONTH; +>> INTERVAL '-0-1' YEAR TO MONTH + +SELECT INTERVAL '-0.1' SECOND; +>> INTERVAL '-0.1' SECOND + +SELECT INTERVAL -'0.1' SECOND; +>> INTERVAL '-0.1' SECOND + +-- Arithmetic + +SELECT INTERVAL '1000' SECOND + INTERVAL '10' MINUTE; +>> INTERVAL '26:40' MINUTE TO SECOND + +SELECT INTERVAL '1000' SECOND - INTERVAL '10' MINUTE; +>> INTERVAL '6:40' MINUTE TO SECOND + +SELECT INTERVAL '10' YEAR + INTERVAL '1' MONTH; +>> INTERVAL '10-1' YEAR TO MONTH + +SELECT INTERVAL '10' YEAR - INTERVAL '1' MONTH; +>> INTERVAL '9-11' YEAR TO MONTH + +SELECT INTERVAL '1000' SECOND * 2; +>> INTERVAL '2000' SECOND + +SELECT 2 * INTERVAL '1000' SECOND; +>> INTERVAL '2000' SECOND + +SELECT INTERVAL '1000' SECOND / 2; +>> INTERVAL '500' SECOND + +SELECT INTERVAL '10' YEAR * 2; +>> INTERVAL '20' YEAR + +SELECT 2 * INTERVAL '10' YEAR; +>> INTERVAL '20' YEAR + +SELECT INTERVAL '10' YEAR / 2; +>> INTERVAL '5' YEAR + +SELECT TIME '10:00:00' + INTERVAL '30' MINUTE; +>> 10:30:00 + +SELECT INTERVAL '30' MINUTE + TIME '10:00:00'; +>> 10:30:00 + +SELECT TIME '10:00:00' - INTERVAL '30' MINUTE; +>> 09:30:00 + +SELECT DATE '2000-01-10' + INTERVAL '30' HOUR; +>> 2000-01-11 + +SELECT INTERVAL '30' HOUR + DATE '2000-01-10'; +>> 2000-01-11 + +SELECT DATE '2000-01-10' - INTERVAL '30' HOUR; +>> 2000-01-09 + +SELECT DATE '2000-01-10' + INTERVAL '1-2' YEAR TO MONTH; +>> 2001-03-10 + +SELECT INTERVAL '1-2' YEAR TO MONTH + DATE '2000-01-10'; +>> 2001-03-10 + +SELECT DATE '2000-01-10' - INTERVAL '1-2' YEAR TO MONTH; +>> 1998-11-10 + +SELECT TIMESTAMP '2000-01-01 12:00:00' + INTERVAL '25 13' DAY TO HOUR; +>> 2000-01-27 01:00:00 + +SELECT INTERVAL '25 13' DAY TO HOUR + TIMESTAMP '2000-01-01 12:00:00'; +>> 2000-01-27 01:00:00 + +SELECT TIMESTAMP '2000-01-01 12:00:00' - INTERVAL '25 13' DAY TO HOUR; +>> 1999-12-06 23:00:00 + +SELECT TIMESTAMP '2000-01-01 12:00:00' + INTERVAL '1-2' YEAR TO MONTH; +>> 2001-03-01 12:00:00 + +SELECT INTERVAL '1-2' YEAR TO MONTH + TIMESTAMP '2000-01-01 12:00:00'; +>> 2001-03-01 12:00:00 + +SELECT TIMESTAMP '2000-01-01 12:00:00' - INTERVAL '1-2' YEAR TO MONTH; +>> 1998-11-01 12:00:00 + +SELECT TIMESTAMP WITH TIME ZONE '2000-01-01 12:00:00+01' + INTERVAL '25 13' DAY TO HOUR; +>> 2000-01-27 01:00:00+01 + +SELECT INTERVAL '25 13' DAY TO HOUR + TIMESTAMP WITH TIME ZONE '2000-01-01 12:00:00+01'; +>> 2000-01-27 01:00:00+01 + +SELECT TIMESTAMP WITH TIME ZONE '2000-01-01 12:00:00+01' - INTERVAL '25 13' DAY TO HOUR; +>> 1999-12-06 23:00:00+01 + +SELECT TIMESTAMP WITH TIME ZONE '2000-01-01 12:00:00+01' + INTERVAL '1-2' YEAR TO MONTH; +>> 2001-03-01 12:00:00+01 + +SELECT INTERVAL '1-2' YEAR TO MONTH + TIMESTAMP WITH TIME ZONE '2000-01-01 12:00:00+01'; +>> 2001-03-01 12:00:00+01 + +SELECT TIMESTAMP WITH TIME ZONE '2000-01-01 12:00:00+01' - INTERVAL '1-2' YEAR TO MONTH; +>> 1998-11-01 12:00:00+01 + +SELECT -INTERVAL '1' DAY; +>> INTERVAL '-1' DAY + +-- Date-time subtraction + +SELECT TIME '10:30:15.123456789' - TIME '11:00:00'; +>> INTERVAL '-0:29:44.876543211' HOUR TO SECOND + +SELECT DATE '2010-01-15' - DATE '2009-12-31'; +>> INTERVAL '15' DAY + +SELECT TIMESTAMP '2010-01-15 12:00:00.5' - TIMESTAMP '2010-01-13 01:30:00'; +>> INTERVAL '2 10:30:00.5' DAY TO SECOND + +SELECT TIMESTAMP WITH TIME ZONE '2010-01-15 12:00:00.5+01' - TIMESTAMP WITH TIME ZONE '2010-01-13 01:30:00+01'; +>> INTERVAL '2 10:30:00.5' DAY TO SECOND + +SELECT TIMESTAMP WITH TIME ZONE '2010-01-15 12:00:00.5+01' - TIMESTAMP WITH TIME ZONE '2010-01-13 01:30:00+02'; +>> INTERVAL '2 11:30:00.5' DAY TO SECOND + +SELECT TIMESTAMP '2010-01-15 12:00:00.5+01' - TIMESTAMP WITH TIME ZONE '2010-01-13 01:30:00+02'; +>> INTERVAL '2 11:30:00.5' DAY TO SECOND + +SELECT TIMESTAMP WITH TIME ZONE '2010-01-15 12:00:00.5+01' - TIMESTAMP '2010-01-13 01:30:00+02'; +>> INTERVAL '2 11:30:00.5' DAY TO SECOND + +CREATE TABLE TEST(I INTERVAL YEAR TO MONTH); +> ok + +INSERT INTO TEST VALUES ('-0-0'), ('-0-1'), ('-1-1'), ('1-0'), ('0-1'), ('1-1'), ('-1-0'); +> update count: 7 + +SELECT * FROM TEST ORDER BY I; +> I +> ----------------------------- +> INTERVAL '-1-1' YEAR TO MONTH +> INTERVAL '-1-0' YEAR TO MONTH +> INTERVAL '-0-1' YEAR TO MONTH +> INTERVAL '0-0' YEAR TO MONTH +> INTERVAL '0-1' YEAR TO MONTH +> INTERVAL '1-0' YEAR TO MONTH +> INTERVAL '1-1' YEAR TO MONTH +> rows (ordered): 7 + +DROP TABLE TEST; +> ok + +-- Some precision tests + +CREATE TABLE TEST(I INTERVAL DAY, IL INTERVAL DAY(5)); +> ok + +INSERT INTO TEST VALUES ('99', '99999'), ('-99', '-99999'); +> update count: 2 + +INSERT INTO TEST(I) VALUES ('100'); +> exception VALUE_TOO_LONG_2 + +INSERT INTO TEST(I) VALUES ('-100'); +> exception VALUE_TOO_LONG_2 + +INSERT INTO TEST(IL) VALUES ('100000'); +> exception VALUE_TOO_LONG_2 + +INSERT INTO TEST(IL) VALUES ('-100000'); +> exception VALUE_TOO_LONG_2 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(I INTERVAL DAY(0)); +> exception INVALID_VALUE_PRECISION + +CREATE TABLE TEST(I INTERVAL DAY(18)); +> ok + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(I INTERVAL DAY(19)); +> exception INVALID_VALUE_PRECISION + +CREATE TABLE TEST(I INTERVAL HOUR TO SECOND(0)); +> ok + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(I INTERVAL HOUR TO SECOND(9)); +> ok + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(I INTERVAL HOUR TO SECOND(10)); +> exception INVALID_VALUE_SCALE + +SELECT TIMESTAMP '2018-09-10 23:30:00' - TIMESTAMP '2014-09-11 23:30:00'; +>> INTERVAL '1460 00:00:00' DAY TO SECOND + +SELECT TIMESTAMP WITH TIME ZONE '2014-09-11 23:30:00Z' - TIMESTAMP WITH TIME ZONE '2018-09-10 23:30:00Z'; +>> INTERVAL '-1460 00:00:00' DAY TO SECOND + +SELECT DATE '2018-09-10' - DATE '2014-09-11'; +>> INTERVAL '1460' DAY + +SELECT INTERVAL -'1-2' YEAR TO MONTH / INTERVAL '1' MONTH; +>> -14.0000000000000000000000000000000000000000 + +SELECT INTERVAL '1 12:03:40.123456789' DAY TO SECOND / INTERVAL '1' SECOND; +>> 129820.1234567890000000000000000000000000000000000000000000000000000000 + +SELECT INTERVAL -'0.000000001' SECOND / INTERVAL '1' SECOND; +>> -0.0000000010000000000000000000000000000000000000000000000000000000 + +SELECT INTERVAL -'1-2' YEAR TO MONTH / INTERVAL '1' DAY; +> exception FEATURE_NOT_SUPPORTED_1 + +SELECT INTERVAL '1' DAY / INTERVAL '0' DAY; +> exception DIVISION_BY_ZERO_1 + +CALL CAST(INTERVAL '999999999999999998.999999999' SECOND AS INTERVAL SECOND(18)); +>> INTERVAL '999999999999999999' SECOND + +CALL CAST(INTERVAL '999999999999999999.999999999' SECOND AS INTERVAL SECOND(18)); +>> INTERVAL '999999999999999999.999999' SECOND + +CALL CAST(INTERVAL '999999999999999998 23:59:59.999999999' DAY TO SECOND AS INTERVAL DAY(18) TO SECOND); +>> INTERVAL '999999999999999999 00:00:00' DAY TO SECOND + +CALL CAST(INTERVAL '999999999999999999 23:59:59.999999999' DAY TO SECOND AS INTERVAL DAY(18) TO SECOND); +>> INTERVAL '999999999999999999 23:59:59.999999' DAY TO SECOND + +CALL CAST(INTERVAL '999999999999999998:59:59.999999999' HOUR TO SECOND AS INTERVAL HOUR(18) TO SECOND); +>> INTERVAL '999999999999999999:00:00' HOUR TO SECOND + +CALL CAST(INTERVAL '999999999999999999:59:59.999999999' HOUR TO SECOND AS INTERVAL HOUR(18) TO SECOND); +>> INTERVAL '999999999999999999:59:59.999999' HOUR TO SECOND + +CALL CAST(INTERVAL '999999999999999998:59.999999999' MINUTE TO SECOND AS INTERVAL MINUTE(18) TO SECOND); +>> INTERVAL '999999999999999999:00' MINUTE TO SECOND + +CALL CAST(INTERVAL '999999999999999999:59.999999999' MINUTE TO SECOND AS INTERVAL MINUTE(18) TO SECOND); +>> INTERVAL '999999999999999999:59.999999' MINUTE TO SECOND + +CALL CAST(INTERVAL '99' DAY AS INTERVAL DAY); +>> INTERVAL '99' DAY + +CALL CAST(INTERVAL '-99' DAY AS INTERVAL DAY); +>> INTERVAL '-99' DAY + +CALL CAST(INTERVAL '100' DAY AS INTERVAL DAY); +> exception VALUE_TOO_LONG_2 + +CALL CAST(INTERVAL '-100' DAY AS INTERVAL DAY); +> exception VALUE_TOO_LONG_2 + +SELECT (TIMESTAMP '2010-01-01 10:00:00' - TIMESTAMP '1990-05-06 00:30:00'); +>> INTERVAL '7180 09:30:00' DAY TO SECOND + +SELECT (TIMESTAMP '2010-01-01 10:00:00' - TIMESTAMP '1990-05-06 00:30:00') HOUR; +> exception VALUE_TOO_LONG_2 + +SELECT (TIMESTAMP '2010-01-01 10:00:00' - TIMESTAMP '1990-05-06 00:30:00') HOUR(6); +>> INTERVAL '172329' HOUR + +SELECT (TIMESTAMP '2010-01-01 10:00:00' - INTERVAL '1' YEAR) YEAR; +> exception SYNTAX_ERROR_2 + +SELECT (INTERVAL '10' HOUR - INTERVAL '1' HOUR) HOUR; +> exception SYNTAX_ERROR_2 + +SELECT (10 - 2) SECOND; +> exception SYNTAX_ERROR_2 + +SELECT (TIMESTAMP '2010-01-01 10:00:00' - TIMESTAMP '1990-05-06 00:30:00') HOUR TO SECOND; +> exception VALUE_TOO_LONG_2 + +SELECT (TIMESTAMP '2010-01-01 10:00:00' - TIMESTAMP '1990-05-06 00:30:00') HOUR(6) TO SECOND; +>> INTERVAL '172329:30:00' HOUR TO SECOND + +EXPLAIN SELECT (TIMESTAMP '2010-01-01 10:00:00' - TIMESTAMP '1990-05-06 00:30:00') HOUR TO SECOND; +>> SELECT (TIMESTAMP '2010-01-01 10:00:00' - TIMESTAMP '1990-05-06 00:30:00') HOUR TO SECOND + +EXPLAIN SELECT (TIMESTAMP '2010-01-01 10:00:00' - TIMESTAMP '1990-05-06 00:30:00') HOUR TO SECOND(9); +>> SELECT (TIMESTAMP '2010-01-01 10:00:00' - TIMESTAMP '1990-05-06 00:30:00') HOUR TO SECOND(9) + +CREATE TABLE TEST(S VARCHAR) AS VALUES '1'; +> ok + +SELECT S DAY FROM TEST; +>> INTERVAL '1' DAY + +EXPLAIN SELECT S DAY FROM TEST; +>> SELECT CAST("S" AS INTERVAL DAY) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +DROP TABLE TEST; +> ok + +SELECT CAST(10 AS INTERVAL YEAR); +>> INTERVAL '10' YEAR + +SELECT CAST(INTERVAL '10' YEAR AS INTEGER); +>> 10 + +SELECT CAST(-10 AS INTERVAL YEAR); +>> INTERVAL '-10' YEAR + +SELECT CAST(INTERVAL '-10' YEAR AS INTEGER); +>> -10 + +SELECT CAST(10::BIGINT AS INTERVAL YEAR); +>> INTERVAL '10' YEAR + +SELECT CAST(INTERVAL '10' YEAR AS BIGINT); +>> 10 + +SELECT CAST(INTERVAL '10' YEAR AS SMALLINT); +>> 10 + +SELECT CAST(INTERVAL '10' YEAR AS TINYINT); +>> 10 + +SELECT CAST(10::DOUBLE AS INTERVAL YEAR); +>> INTERVAL '10' YEAR + +SELECT CAST(INTERVAL '10' YEAR AS REAL); +>> 10.0 + +SELECT CAST(INTERVAL '10' YEAR AS DOUBLE); +>> 10.0 + +SELECT CAST(INTERVAL '10' YEAR AS NUMERIC); +>> 10 + +SELECT CAST(INTERVAL '-10' YEAR AS NUMERIC); +>> -10 + +SELECT CAST(10.123456789123456789 AS INTERVAL YEAR); +>> INTERVAL '10' YEAR + +SELECT CAST(10 AS INTERVAL MONTH); +>> INTERVAL '10' MONTH + +SELECT CAST(INTERVAL '10' MONTH AS NUMERIC); +>> 10 + +SELECT CAST(10.123456789123456789 AS INTERVAL MONTH); +>> INTERVAL '10' MONTH + +SELECT CAST(10 AS INTERVAL DAY); +>> INTERVAL '10' DAY + +SELECT CAST(INTERVAL '10' DAY AS NUMERIC); +>> 10 + +SELECT CAST(-10 AS INTERVAL DAY); +>> INTERVAL '-10' DAY + +SELECT CAST(10.123456789123456789 AS INTERVAL DAY); +>> INTERVAL '10' DAY + +SELECT CAST(10 AS INTERVAL HOUR); +>> INTERVAL '10' HOUR + +SELECT CAST(INTERVAL '10' HOUR AS NUMERIC); +>> 10 + +SELECT CAST(10::BIGINT AS INTERVAL HOUR); +>> INTERVAL '10' HOUR + +SELECT CAST(10::DOUBLE AS INTERVAL HOUR); +>> INTERVAL '10' HOUR + +SELECT CAST(10.123456789123456789 AS INTERVAL HOUR); +>> INTERVAL '10' HOUR + +SELECT CAST(10 AS INTERVAL MINUTE); +>> INTERVAL '10' MINUTE + +SELECT CAST(INTERVAL '10' MINUTE AS NUMERIC); +>> 10 + +SELECT CAST(10.123456789123456789 AS INTERVAL MINUTE); +>> INTERVAL '10' MINUTE + +SELECT CAST(10 AS INTERVAL SECOND); +>> INTERVAL '10' SECOND + +SELECT CAST(INTERVAL '10' SECOND AS NUMERIC); +>> 10 + +SELECT CAST(10.123456789123456789 AS INTERVAL SECOND); +>> INTERVAL '10.123457' SECOND + +SELECT CAST(INTERVAL '10.123457' SECOND AS INT); +>> 10 + +SELECT CAST(INTERVAL '10.123457' SECOND AS NUMERIC(8, 6)); +>> 10.123457 + +SELECT CAST(10 AS INTERVAL YEAR TO MONTH); +>> INTERVAL '10-0' YEAR TO MONTH + +SELECT CAST(10::DOUBLE AS INTERVAL YEAR TO MONTH); +>> INTERVAL '10-0' YEAR TO MONTH + +SELECT CAST(10.123456789123456789 AS INTERVAL YEAR TO MONTH); +>> INTERVAL '10-1' YEAR TO MONTH + +SELECT CAST(INTERVAL '10-1' YEAR TO MONTH AS NUMERIC(4, 2)); +>> 10.08 + +SELECT CAST(10 AS INTERVAL DAY TO HOUR); +>> INTERVAL '10 00' DAY TO HOUR + +SELECT CAST(10::DOUBLE AS INTERVAL DAY TO HOUR); +>> INTERVAL '10 00' DAY TO HOUR + +SELECT CAST(10.123456789123456789 AS INTERVAL DAY TO HOUR); +>> INTERVAL '10 02' DAY TO HOUR + +SELECT CAST(INTERVAL '10 02' DAY TO HOUR AS NUMERIC(4, 2)); +>> 10.08 + +SELECT CAST(INTERVAL '-10 02' DAY TO HOUR AS NUMERIC(4, 2)); +>> -10.08 + +SELECT CAST(10 AS INTERVAL DAY TO MINUTE); +>> INTERVAL '10 00:00' DAY TO MINUTE + +SELECT CAST(10.123456789123456789 AS INTERVAL DAY TO MINUTE); +>> INTERVAL '10 02:57' DAY TO MINUTE + +SELECT CAST(INTERVAL '10 02:57' DAY TO MINUTE AS NUMERIC(6, 4)); +>> 10.1229 + +SELECT CAST(10 AS INTERVAL DAY TO SECOND); +>> INTERVAL '10 00:00:00' DAY TO SECOND + +SELECT CAST(10.123456789123456789 AS INTERVAL DAY TO SECOND); +>> INTERVAL '10 02:57:46.66658' DAY TO SECOND + +SELECT CAST(INTERVAL '10 02:57:46.66658' DAY TO SECOND AS NUMERIC(16, 14)); +>> 10.12345678912037 + +SELECT CAST(10 AS INTERVAL HOUR TO MINUTE); +>> INTERVAL '10:00' HOUR TO MINUTE + +SELECT CAST(10.123456789123456789 AS INTERVAL HOUR TO MINUTE); +>> INTERVAL '10:07' HOUR TO MINUTE + +SELECT CAST(INTERVAL '10:07' HOUR TO MINUTE AS NUMERIC(4, 2)); +>> 10.12 + +SELECT CAST(10 AS INTERVAL HOUR TO SECOND); +>> INTERVAL '10:00:00' HOUR TO SECOND + +SELECT CAST(10.123456789123456789 AS INTERVAL HOUR TO SECOND); +>> INTERVAL '10:07:24.444441' HOUR TO SECOND + +SELECT CAST(INTERVAL '10:07:24.444441' HOUR TO SECOND AS NUMERIC(15, 13)); +>> 10.1234567891667 + +SELECT CAST(10 AS INTERVAL MINUTE TO SECOND); +>> INTERVAL '10:00' MINUTE TO SECOND + +SELECT CAST(10.123456789123456789 AS INTERVAL MINUTE TO SECOND); +>> INTERVAL '10:07.407407' MINUTE TO SECOND + +SELECT CAST(INTERVAL '10:07.407407' MINUTE TO SECOND AS NUMERIC(13, 11)); +>> 10.12345678333 + +-- H2 uses 1970-01-01 as start datetime + +SELECT TIMESTAMP '2001-01-05 10:30:00' - TIME '11:45:30.5'; +>> INTERVAL '11326 22:44:29.5' DAY TO SECOND + +SELECT TIME '11:45:30.5' - TIMESTAMP '2001-01-05 10:30:00'; +>> INTERVAL '-11326 22:44:29.5' DAY TO SECOND + +EXPLAIN VALUES INTERVAL '1' DAY; +>> VALUES (INTERVAL '1' DAY) + +SELECT CAST(INTERVAL '1000000000000000' MINUTE AS BIGINT); +>> 1000000000000000 + +SELECT CAST(INTERVAL '999999999999999999:30' HOUR TO SECOND AS NUMERIC); +>> 1000000000000000000 + +SELECT CAST(INTERVAL '999999999999999999:30' HOUR TO SECOND AS NUMERIC(20, 1)); +>> 999999999999999999.5 + +SELECT CAST(INTERVAL '999999999999999999:30' HOUR TO MINUTE AS BIGINT); +>> 1000000000000000000 + +SELECT D1, D2, (D1 - D2) YEAR TO MONTH, (D2 - D1) YEAR TO MONTH FROM (VALUES + (DATE '1999-05-12', DATE '2020-05-11'), + (DATE '1999-05-12', DATE '2020-05-12'), + (DATE '1999-05-12', DATE '2020-05-13') +) T(D1, D2); +> D1 D2 (D1 - D2) YEAR TO MONTH (D2 - D1) YEAR TO MONTH +> ---------- ---------- ------------------------------- ------------------------------ +> 1999-05-12 2020-05-11 INTERVAL '-20-11' YEAR TO MONTH INTERVAL '20-11' YEAR TO MONTH +> 1999-05-12 2020-05-12 INTERVAL '-21-0' YEAR TO MONTH INTERVAL '21-0' YEAR TO MONTH +> 1999-05-12 2020-05-13 INTERVAL '-21-0' YEAR TO MONTH INTERVAL '21-0' YEAR TO MONTH +> rows: 3 + +SELECT T1, T2, (T1 - T2) YEAR TO MONTH, (T2 - T1) YEAR TO MONTH FROM (VALUES + (TIMESTAMP '1999-05-12 12:00:00', TIMESTAMP '2020-05-12 11:00:00'), + (TIMESTAMP '1999-05-12 12:00:00', TIMESTAMP '2020-05-12 12:00:00'), + (TIMESTAMP '1999-05-12 12:00:00', TIMESTAMP '2020-05-12 13:00:00') +) T(T1, T2); +> T1 T2 (T1 - T2) YEAR TO MONTH (T2 - T1) YEAR TO MONTH +> ------------------- ------------------- ------------------------------- ------------------------------ +> 1999-05-12 12:00:00 2020-05-12 11:00:00 INTERVAL '-20-11' YEAR TO MONTH INTERVAL '20-11' YEAR TO MONTH +> 1999-05-12 12:00:00 2020-05-12 12:00:00 INTERVAL '-21-0' YEAR TO MONTH INTERVAL '21-0' YEAR TO MONTH +> 1999-05-12 12:00:00 2020-05-12 13:00:00 INTERVAL '-21-0' YEAR TO MONTH INTERVAL '21-0' YEAR TO MONTH +> rows: 3 + +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 new file mode 100644 index 0000000..cfa4678 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/java_object.sql @@ -0,0 +1,53 @@ +-- 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 +-- + +EXPLAIN VALUES CAST(X'' AS JAVA_OBJECT); +>> VALUES (CAST(X'' AS JAVA_OBJECT)) + +VALUES CAST(CAST(X'00' AS JAVA_OBJECT) AS VARCHAR(2)); +> exception DATA_CONVERSION_ERROR_1 + +VALUES CAST(CAST(X'00' AS JAVA_OBJECT) AS CHAR(2)); +> exception DATA_CONVERSION_ERROR_1 + +VALUES CAST('00' AS JAVA_OBJECT); +> exception DATA_CONVERSION_ERROR_1 + +VALUES CAST(CAST('00' AS CHAR(2)) AS JAVA_OBJECT); +> exception DATA_CONVERSION_ERROR_1 + +VALUES CAST(X'0000' AS JAVA_OBJECT(1)); +> exception VALUE_TOO_LONG_2 + +VALUES CAST(CAST (X'0000' AS JAVA_OBJECT) AS JAVA_OBJECT(1)); +> exception VALUE_TOO_LONG_2 + +CREATE TABLE T(C JAVA_OBJECT(0)); +> exception INVALID_VALUE_2 + +CREATE TABLE T1(A JAVA_OBJECT(1000000000)); +> ok + +CREATE TABLE T2(A JAVA_OBJECT(1000000001)); +> exception INVALID_VALUE_PRECISION + +SET TRUNCATE_LARGE_LENGTH TRUE; +> ok + +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 1000000000 +> T2 1000000000 +> rows: 2 + +SET TRUNCATE_LARGE_LENGTH FALSE; +> ok + +DROP TABLE T1, T2; +> ok diff --git a/h2/src/test/org/h2/test/scripts/datatypes/json.sql b/h2/src/test/org/h2/test/scripts/datatypes/json.sql new file mode 100644 index 0000000..0ad674c --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/json.sql @@ -0,0 +1,360 @@ +-- 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 +-- + +SELECT '{"tag1":"simple string"}' FORMAT JSON; +>> {"tag1":"simple string"} + +SELECT CAST('{"tag1":"simple string"}' FORMAT JSON AS JSON); +>> {"tag1":"simple string"} + +SELECT CAST('text' AS JSON); +>> "text" + +SELECT X'31' FORMAT JSON; +>> 1 + +SELECT 0::JSON; +>> 0 + +SELECT '0' FORMAT JSON; +>> 0 + +SELECT JSON '1', JSON X'31', JSON '1' IS OF (JSON), JSON X'31' IS OF (JSON); +> JSON '1' JSON '1' TRUE TRUE +> -------- -------- ---- ---- +> 1 1 TRUE TRUE +> rows: 1 + +SELECT JSON 'tr' 'ue', JSON X'7472' '7565', JSON 'tr' 'ue' IS OF (JSON), JSON X'7472' '7565' IS OF (JSON); +> JSON 'true' JSON 'true' TRUE TRUE +> ----------- ----------- ---- ---- +> true true TRUE TRUE +> rows: 1 + +SELECT 1::JSON; +>> 1 + +SELECT 1L::JSON; +>> 1 + +SELECT 1000000000000L::JSON; +>> 1000000000000 + +SELECT CAST(1e100::FLOAT AS JSON); +>> 1.0E100 + +SELECT CAST(1e100::DOUBLE AS JSON); +>> 1.0E100 + +SELECT CAST(1e100 AS JSON); +>> 1E100 + +SELECT CAST(TRUE AS JSON); +>> true + +SELECT CAST('true' FORMAT JSON AS JSON); +>> true + +SELECT CAST(FALSE AS JSON); +>> false + +SELECT CAST('false' FORMAT JSON AS JSON); +>> false + +SELECT CAST('null' FORMAT JSON AS JSON); +>> null + +SELECT CAST('10' FORMAT JSON AS VARBINARY); +>> X'3130' + +SELECT CAST('10' FORMAT JSON AS BLOB); +>> X'3130' + +CREATE TABLE TEST (ID INT, DATA JSON); +> ok + +INSERT INTO TEST VALUES +(1, '{"tag1":"simple string", "tag2": 333, "tag3":[1, 2, 3]}' format json), +(2, '{"tag1":"another string", "tag4":{"lvl1":"lvl2"}}' format json), +(3, '["string", 5555, {"arr":"yes"}]' format json), +(4, '{"1":"val1"}' format json); +> update count: 4 + +@reconnect + +SELECT ID, DATA FROM TEST; +> ID DATA +> -- -------------------------------------------------- +> 1 {"tag1":"simple string","tag2":333,"tag3":[1,2,3]} +> 2 {"tag1":"another string","tag4":{"lvl1":"lvl2"}} +> 3 ["string",5555,{"arr":"yes"}] +> 4 {"1":"val1"} +> rows: 4 + +INSERT INTO TEST VALUES (5, '}' FORMAT JSON); +> exception DATA_CONVERSION_ERROR_1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID INT, S VARCHAR, B VARBINARY, J JSON) AS VALUES + (1, '{"a":1,"a":2}', STRINGTOUTF8('{"a":1,"a":2}'), '{"a":1,"a":2}' FORMAT JSON), + (2, '{"a":1,"b":2}', STRINGTOUTF8('{"a":1,"b":2}'), '{"a":1,"b":2}' FORMAT JSON), + (3, '{"a":1,"b":2', STRINGTOUTF8('{"a":1,"b":2'), null), + (4, null, null, null); +> ok + +SELECT S IS JSON, B IS JSON WITHOUT UNIQUE, J IS JSON WITHOUT UNIQUE KEYS FROM TEST ORDER BY ID; +> S IS JSON B IS JSON J IS JSON +> --------- --------- --------- +> TRUE TRUE TRUE +> TRUE TRUE TRUE +> FALSE FALSE null +> null null null +> rows (ordered): 4 + +SELECT S IS NOT JSON, B IS NOT JSON WITHOUT UNIQUE, J IS NOT JSON WITHOUT UNIQUE KEYS FROM TEST ORDER BY ID; +> S IS NOT JSON B IS NOT JSON J IS NOT JSON +> ------------- ------------- ------------- +> FALSE FALSE FALSE +> FALSE FALSE FALSE +> TRUE TRUE null +> null null null +> rows (ordered): 4 + +SELECT S IS JSON WITH UNIQUE KEYS, B IS JSON WITH UNIQUE, J IS JSON WITH UNIQUE KEYS FROM TEST ORDER BY ID; +> S IS JSON WITH UNIQUE KEYS B IS JSON WITH UNIQUE KEYS J IS JSON WITH UNIQUE KEYS +> -------------------------- -------------------------- -------------------------- +> FALSE FALSE FALSE +> TRUE TRUE TRUE +> FALSE FALSE null +> null null null +> rows (ordered): 4 + +SELECT S IS NOT JSON WITH UNIQUE KEYS, B IS NOT JSON WITH UNIQUE, J IS NOT JSON WITH UNIQUE KEYS FROM TEST ORDER BY ID; +> S IS NOT JSON WITH UNIQUE KEYS B IS NOT JSON WITH UNIQUE KEYS J IS NOT JSON WITH UNIQUE KEYS +> ------------------------------ ------------------------------ ------------------------------ +> TRUE TRUE TRUE +> FALSE FALSE FALSE +> TRUE TRUE null +> null null null +> rows (ordered): 4 + +DROP TABLE TEST; +> ok + +SELECT 1 IS JSON; +>> FALSE + +SELECT 1 IS NOT JSON; +>> TRUE + +CREATE TABLE TEST(ID INT, S VARCHAR) AS VALUES + (1, '[{"a":1}]'), (2, '{"a":[3]}'), + (3, 'null'), (4, '{"a":1,"a":2}'), + (5, 'X'), (6, NULL); +> ok + +EXPLAIN SELECT S FORMAT JSON FORMAT JSON, (S FORMAT JSON) FORMAT JSON FROM TEST; +>> SELECT "S" FORMAT JSON, "S" FORMAT JSON FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +ALTER TABLE TEST ADD J JSON; +> ok + +UPDATE TEST SET J = S FORMAT JSON WHERE S IS JSON; +> update count: 4 + +SELECT S IS JSON, S IS JSON VALUE, S IS JSON ARRAY, S IS JSON OBJECT, S IS JSON SCALAR FROM TEST ORDER BY ID; +> S IS JSON S IS JSON S IS JSON ARRAY S IS JSON OBJECT S IS JSON SCALAR +> --------- --------- --------------- ---------------- ---------------- +> TRUE TRUE TRUE FALSE FALSE +> TRUE TRUE FALSE TRUE FALSE +> TRUE TRUE FALSE FALSE TRUE +> TRUE TRUE FALSE TRUE FALSE +> FALSE FALSE FALSE FALSE FALSE +> null null null null null +> rows (ordered): 6 + +SELECT J IS JSON, J IS JSON VALUE, J IS JSON ARRAY, J IS JSON OBJECT, J IS JSON SCALAR FROM TEST ORDER BY ID; +> J IS JSON J IS JSON J IS JSON ARRAY J IS JSON OBJECT J IS JSON SCALAR +> --------- --------- --------------- ---------------- ---------------- +> TRUE TRUE TRUE FALSE FALSE +> TRUE TRUE FALSE TRUE FALSE +> TRUE TRUE FALSE FALSE TRUE +> TRUE TRUE FALSE TRUE FALSE +> null null null null null +> null null null null null +> rows (ordered): 6 + +SELECT J IS JSON WITH UNIQUE KEYS, J IS JSON VALUE WITH UNIQUE KEYS, J IS JSON ARRAY WITH UNIQUE KEYS, + J IS JSON OBJECT WITH UNIQUE KEYS, J IS JSON SCALAR WITH UNIQUE KEYS FROM TEST ORDER BY ID; +> J IS JSON WITH UNIQUE KEYS J IS JSON WITH UNIQUE KEYS J IS JSON ARRAY WITH UNIQUE KEYS J IS JSON OBJECT WITH UNIQUE KEYS J IS JSON SCALAR WITH UNIQUE KEYS +> -------------------------- -------------------------- -------------------------------- --------------------------------- --------------------------------- +> TRUE TRUE TRUE FALSE FALSE +> TRUE TRUE FALSE TRUE FALSE +> TRUE TRUE FALSE FALSE TRUE +> FALSE FALSE FALSE FALSE FALSE +> null null null null null +> null null null null null +> rows (ordered): 6 + +SELECT S IS NOT JSON, S IS NOT JSON VALUE, S IS NOT JSON ARRAY, S IS NOT JSON OBJECT, S IS NOT JSON SCALAR + FROM TEST ORDER BY ID; +> S IS NOT JSON S IS NOT JSON S IS NOT JSON ARRAY S IS NOT JSON OBJECT S IS NOT JSON SCALAR +> ------------- ------------- ------------------- -------------------- -------------------- +> FALSE FALSE FALSE TRUE TRUE +> FALSE FALSE TRUE FALSE TRUE +> FALSE FALSE TRUE TRUE FALSE +> FALSE FALSE TRUE FALSE TRUE +> TRUE TRUE TRUE TRUE TRUE +> null null null null null +> rows (ordered): 6 + +SELECT NOT S IS NOT JSON, NOT S IS NOT JSON VALUE, NOT S IS NOT JSON ARRAY, NOT S IS NOT JSON OBJECT, + NOT S IS NOT JSON SCALAR FROM TEST ORDER BY ID; +> S IS JSON S IS JSON S IS JSON ARRAY S IS JSON OBJECT S IS JSON SCALAR +> --------- --------- --------------- ---------------- ---------------- +> TRUE TRUE TRUE FALSE FALSE +> TRUE TRUE FALSE TRUE FALSE +> TRUE TRUE FALSE FALSE TRUE +> TRUE TRUE FALSE TRUE FALSE +> FALSE FALSE FALSE FALSE FALSE +> null null null null null +> rows (ordered): 6 + +DROP TABLE TEST; +> ok + +SELECT NULL FORMAT JSON, (NULL FORMAT JSON) IS NULL; +> JSON 'null' FALSE +> ----------- ----- +> null FALSE +> rows: 1 + +CREATE MEMORY TABLE TEST(J JSON) AS VALUES ('["\u00A7''",{}]' FORMAT JSON); +> ok + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION TABLE TEST; +> SCRIPT +> ---------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "J" JSON ); +> -- 1 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> INSERT INTO "PUBLIC"."TEST" VALUES (JSON '["\u00a7\u0027",{}]'); +> rows (ordered): 4 + +DROP TABLE TEST; +> ok + +CREATE TABLE T(C JSON(0)); +> exception INVALID_VALUE_2 + +CREATE TABLE TEST(J JSON(3)); +> ok + +INSERT INTO TEST VALUES JSON '[1]'; +> update count: 1 + +INSERT INTO TEST VALUES JSON 'null'; +> exception VALUE_TOO_LONG_2 + +DROP TABLE TEST; +> ok + +SELECT CAST(JSON 'null' AS JSON(3)); +> exception VALUE_TOO_LONG_2 + +CREATE TABLE TEST(J JSONB); +> exception UNKNOWN_DATA_TYPE_1 + +SET MODE PostgreSQL; +> ok + +CREATE TABLE TEST(J JSONB); +> ok + +DROP TABLE TEST; +> ok + +SET MODE Regular; +> ok + +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(1000000000)); +> ok + +CREATE TABLE T2(A JSON(1000000001)); +> exception INVALID_VALUE_PRECISION + +SET TRUNCATE_LARGE_LENGTH TRUE; +> ok + +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 1000000000 +> T2 1000000000 +> rows: 2 + +SET TRUNCATE_LARGE_LENGTH FALSE; +> ok + +DROP TABLE T1, T2; +> ok + +SELECT JSON_OBJECT( + 'CHAR' : CAST('C' AS CHAR), + 'VARCHAR' : 'C', + 'CLOB' : CAST('C' AS CLOB), + 'IGNORECASE' : CAST('C' AS VARCHAR_IGNORECASE)); +>> {"CHAR":"C","VARCHAR":"C","CLOB":"C","IGNORECASE":"C"} + +SELECT JSON_OBJECT( + 'BINARY' : CAST(X'7b7d' AS BINARY(2)), + 'VARBINARY' : CAST(X'7b7d' AS VARBINARY), + 'BLOB' : CAST(X'7b7d' AS BLOB)); +>> {"BINARY":{},"VARBINARY":{},"BLOB":{}} + +SELECT CAST(TRUE AS JSON); +>> true + +SELECT JSON_OBJECT( + 'TINYINT' : CAST(1 AS TINYINT), + 'SMALLINT' : CAST(2 AS SMALLINT), + 'INTEGER' : 3, + 'BIGINT' : 4L, + 'NUMERIC' : 1.1, + 'REAL' : CAST(1.2 AS REAL), + 'DOUBLE' : CAST(1.3 AS DOUBLE), + 'DECFLOAT' : 1e-1); +>> {"TINYINT":1,"SMALLINT":2,"INTEGER":3,"BIGINT":4,"NUMERIC":1.1,"REAL":1.2,"DOUBLE":1.3,"DECFLOAT":0.1} + +SELECT JSON_OBJECT( + 'DATE' : DATE '2001-01-31', + 'TIME' : TIME '10:00:00.123456789', + 'TIME_TZ' : TIME WITH TIME ZONE '10:00:00.123456789+10:00'); +>> {"DATE":"2001-01-31","TIME":"10:00:00.123456789","TIME_TZ":"10:00:00.123456789+10"} + +SELECT JSON_OBJECT( + 'TIMESTAMP' : TIMESTAMP '2001-01-31 10:00:00.123456789', + 'TIMESTAMP_TZ' : TIMESTAMP WITH TIME ZONE '2001-01-31 10:00:00.123456789+10:00'); +>> {"TIMESTAMP":"2001-01-31T10:00:00.123456789","TIMESTAMP_TZ":"2001-01-31T10:00:00.123456789+10"} + +SELECT JSON_OBJECT( + 'GEOMETRY' : GEOMETRY 'POINT (1 2)', + 'JSON' : JSON '[]', + 'UUID' : UUID '01234567-89ab-cdef-fedc-ba9876543210'); +>> {"GEOMETRY":{"type":"Point","coordinates":[1,2]},"JSON":[],"UUID":"01234567-89ab-cdef-fedc-ba9876543210"} + +SELECT CAST(ARRAY[JSON '[]', JSON '{}'] AS JSON); +>> [[],{}] + +SELECT CAST(ARRAY[1, 2] AS JSON); +>> [1,2] diff --git a/h2/src/test/org/h2/test/scripts/datatypes/numeric.sql b/h2/src/test/org/h2/test/scripts/datatypes/numeric.sql new file mode 100644 index 0000000..43536ce --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/numeric.sql @@ -0,0 +1,188 @@ +-- 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 +-- + +CREATE MEMORY TABLE TEST( + N1 NUMERIC, N2 NUMERIC(10), N3 NUMERIC(10, 0), N4 NUMERIC(10, 2), + D1 DECIMAL, D2 DECIMAL(10), D3 DECIMAL(10, 0), D4 DECIMAL(10, 2), D5 DEC, + X1 NUMBER(10), X2 NUMBER(10, 2)); +> 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 +> ----------- --------- ----------------- ----------------------- ------------- ------------------ -------------------------- ---------------------- +> N1 NUMERIC 100000 10 0 NUMERIC null null +> N2 NUMERIC 10 10 0 NUMERIC 10 null +> N3 NUMERIC 10 10 0 NUMERIC 10 0 +> N4 NUMERIC 10 10 2 NUMERIC 10 2 +> D1 NUMERIC 100000 10 0 DECIMAL null null +> D2 NUMERIC 10 10 0 DECIMAL 10 null +> D3 NUMERIC 10 10 0 DECIMAL 10 0 +> D4 NUMERIC 10 10 2 DECIMAL 10 2 +> D5 NUMERIC 100000 10 0 DECIMAL null null +> X1 NUMERIC 10 10 0 NUMERIC 10 null +> X2 NUMERIC 10 10 2 NUMERIC 10 2 +> rows (ordered): 11 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(N NUMERIC(2, -1)); +> exception INVALID_VALUE_SCALE + +CREATE TABLE TEST(ID INT, X1 BIT, XT TINYINT, X_SM SMALLINT, XB BIGINT, XD DECIMAL(10,2), XD2 DOUBLE PRECISION, XR REAL); +> ok + +INSERT INTO TEST VALUES(?, ?, ?, ?, ?, ?, ?, ?); +{ +0,FALSE,0,0,0,0.0,0.0,0.0 +1,TRUE,1,1,1,1.0,1.0,1.0 +4,TRUE,4,4,4,4.0,4.0,4.0 +-1,FALSE,-1,-1,-1,-1.0,-1.0,-1.0 +NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL +}; +> update count: 5 + +SELECT ID, CAST(XT AS NUMBER(10,1)), +CAST(X_SM AS NUMBER(10,1)), CAST(XB AS NUMBER(10,1)), CAST(XD AS NUMBER(10,1)), +CAST(XD2 AS NUMBER(10,1)), CAST(XR AS NUMBER(10,1)) FROM TEST; +> ID CAST(XT AS NUMERIC(10, 1)) CAST(X_SM AS NUMERIC(10, 1)) CAST(XB AS NUMERIC(10, 1)) CAST(XD AS NUMERIC(10, 1)) CAST(XD2 AS NUMERIC(10, 1)) CAST(XR AS NUMERIC(10, 1)) +> ---- -------------------------- ---------------------------- -------------------------- -------------------------- --------------------------- -------------------------- +> -1 -1.0 -1.0 -1.0 -1.0 -1.0 -1.0 +> 0 0.0 0.0 0.0 0.0 0.0 0.0 +> 1 1.0 1.0 1.0 1.0 1.0 1.0 +> 4 4.0 4.0 4.0 4.0 4.0 4.0 +> null null null null null null null +> rows: 5 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(I NUMERIC(-1)); +> exception INVALID_VALUE_2 + +CREATE TABLE TEST(I NUMERIC(-1, -1)); +> exception INVALID_VALUE_2 + +CREATE TABLE TEST (N NUMERIC(3, 1)) AS VALUES (0), (0.0), (NULL); +> ok + +SELECT * FROM TEST; +> N +> ---- +> 0.0 +> 0.0 +> null +> rows: 3 + +DROP TABLE TEST; +> ok + +SELECT CAST(10000 AS NUMERIC(5)); +>> 10000 + +CREATE DOMAIN N AS NUMERIC(10, 1); +> ok + +CREATE TABLE TEST(V N); +> ok + +SELECT NUMERIC_SCALE FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME = 'V'; +>> 1 + +DROP TABLE TEST; +> ok + +DROP DOMAIN N; +> ok + +CREATE TABLE TEST(I INT PRIMARY KEY, V NUMERIC(1, 3)); +> ok + +INSERT INTO TEST VALUES (1, 1e-3), (2, 1.1e-3), (3, 1e-4); +> update count: 3 + +INSERT INTO TEST VALUES (4, 1e-2); +> exception VALUE_TOO_LONG_2 + +TABLE TEST; +> I V +> - ----- +> 1 0.001 +> 2 0.001 +> 3 0.000 +> rows: 3 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(I INT PRIMARY KEY, V NUMERIC(2)); +> ok + +INSERT INTO TEST VALUES (1, 1e-1), (2, 2e0), (3, 3e1); +> update count: 3 + +TABLE TEST; +> I V +> - -- +> 1 0 +> 2 2 +> 3 30 +> rows: 3 + +DROP TABLE TEST; +> ok + +EXPLAIN VALUES (CAST(-9223372036854775808 AS NUMERIC(19)), CAST(9223372036854775807 AS NUMERIC(19)), 1.0, -9223372036854775809, + 9223372036854775808); +>> VALUES (CAST(-9223372036854775808 AS NUMERIC(19)), CAST(9223372036854775807 AS NUMERIC(19)), 1.0, -9223372036854775809, 9223372036854775808) + +CREATE TABLE T(C NUMERIC(0)); +> exception INVALID_VALUE_2 + +CREATE TABLE T1(A NUMERIC(100000)); +> ok + +CREATE TABLE T2(A NUMERIC(100001)); +> exception INVALID_VALUE_PRECISION + +SET TRUNCATE_LARGE_LENGTH TRUE; +> ok + +CREATE TABLE T2(A NUMERIC(100001)); +> ok + +SELECT TABLE_NAME, NUMERIC_PRECISION, DECLARED_NUMERIC_PRECISION FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = 'PUBLIC'; +> TABLE_NAME NUMERIC_PRECISION DECLARED_NUMERIC_PRECISION +> ---------- ----------------- -------------------------- +> T1 100000 100000 +> T2 100000 100000 +> rows: 2 + +SET TRUNCATE_LARGE_LENGTH FALSE; +> ok + +DROP TABLE T1, T2; +> ok + +SET MODE Oracle; +> ok + +CREATE TABLE TEST(N NUMERIC(2, 1)); +> ok + +INSERT INTO TEST VALUES 20; +> exception VALUE_TOO_LONG_2 + +INSERT INTO TEST VALUES CAST(20 AS NUMERIC(2)); +> exception VALUE_TOO_LONG_2 + +DROP TABLE TEST; +> ok + +SET MODE Regular; +> ok diff --git a/h2/src/test/org/h2/test/scripts/datatypes/real.sql b/h2/src/test/org/h2/test/scripts/datatypes/real.sql new file mode 100644 index 0000000..d3e350e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/real.sql @@ -0,0 +1,247 @@ +-- 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 +-- + +CREATE MEMORY TABLE TEST(D1 REAL, D2 FLOAT4, D3 FLOAT(1), D4 FLOAT(24)); +> ok + +ALTER TABLE TEST ADD COLUMN D5 FLOAT(0); +> exception INVALID_VALUE_PRECISION + +ALTER TABLE TEST ADD COLUMN D5 FLOAT(-1); +> exception INVALID_VALUE_2 + +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 +> ----------- --------- ----------------- ----------------------- ------------- ------------------ -------------------------- ---------------------- +> D1 REAL 24 2 null REAL null null +> D2 REAL 24 2 null REAL null null +> D3 REAL 24 2 null FLOAT 1 null +> D4 REAL 24 2 null FLOAT 24 null +> rows (ordered): 4 + +SCRIPT NODATA NOPASSWORDS NOSETTINGS NOVERSION TABLE TEST; +> SCRIPT +> ------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "D1" REAL, "D2" REAL, "D3" FLOAT(1), "D4" FLOAT(24) ); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> rows (ordered): 3 + +DROP TABLE TEST; +> ok + +EXPLAIN VALUES CAST(0 AS REAL); +>> VALUES (CAST(0.0 AS REAL)) + +CREATE TABLE TEST(F REAL, I INT) AS VALUES (2000000000, 2000000001); +> ok + +SELECT F, I, F = I FROM TEST; +> F I F = I +> ----- ---------- ----- +> 2.0E9 2000000001 FALSE +> rows: 1 + +DROP TABLE TEST; +> ok + +CREATE MEMORY TABLE TEST(D REAL) AS VALUES '-Infinity', '-1', '0', '1', '1.5', 'Infinity', 'NaN'; +> ok + +SELECT D, -D, SIGN(D) FROM TEST ORDER BY D; +> D - D SIGN(D) +> --------- --------- ------- +> -Infinity Infinity -1 +> -1.0 1.0 -1 +> 0.0 0.0 0 +> 1.0 -1.0 1 +> 1.5 -1.5 1 +> Infinity -Infinity 1 +> NaN NaN 0 +> rows (ordered): 7 + +SELECT A.D, B.D, A.D + B.D, A.D - B.D, A.D * B.D FROM TEST A JOIN TEST B ORDER BY A.D, B.D; +> D D A.D + B.D A.D - B.D A.D * B.D +> --------- --------- --------- --------- --------- +> -Infinity -Infinity -Infinity NaN Infinity +> -Infinity -1.0 -Infinity -Infinity Infinity +> -Infinity 0.0 -Infinity -Infinity NaN +> -Infinity 1.0 -Infinity -Infinity -Infinity +> -Infinity 1.5 -Infinity -Infinity -Infinity +> -Infinity Infinity NaN -Infinity -Infinity +> -Infinity NaN NaN NaN NaN +> -1.0 -Infinity -Infinity Infinity Infinity +> -1.0 -1.0 -2.0 0.0 1.0 +> -1.0 0.0 -1.0 -1.0 0.0 +> -1.0 1.0 0.0 -2.0 -1.0 +> -1.0 1.5 0.5 -2.5 -1.5 +> -1.0 Infinity Infinity -Infinity -Infinity +> -1.0 NaN NaN NaN NaN +> 0.0 -Infinity -Infinity Infinity NaN +> 0.0 -1.0 -1.0 1.0 0.0 +> 0.0 0.0 0.0 0.0 0.0 +> 0.0 1.0 1.0 -1.0 0.0 +> 0.0 1.5 1.5 -1.5 0.0 +> 0.0 Infinity Infinity -Infinity NaN +> 0.0 NaN NaN NaN NaN +> 1.0 -Infinity -Infinity Infinity -Infinity +> 1.0 -1.0 0.0 2.0 -1.0 +> 1.0 0.0 1.0 1.0 0.0 +> 1.0 1.0 2.0 0.0 1.0 +> 1.0 1.5 2.5 -0.5 1.5 +> 1.0 Infinity Infinity -Infinity Infinity +> 1.0 NaN NaN NaN NaN +> 1.5 -Infinity -Infinity Infinity -Infinity +> 1.5 -1.0 0.5 2.5 -1.5 +> 1.5 0.0 1.5 1.5 0.0 +> 1.5 1.0 2.5 0.5 1.5 +> 1.5 1.5 3.0 0.0 2.25 +> 1.5 Infinity Infinity -Infinity Infinity +> 1.5 NaN NaN NaN NaN +> Infinity -Infinity NaN Infinity -Infinity +> Infinity -1.0 Infinity Infinity -Infinity +> Infinity 0.0 Infinity Infinity NaN +> Infinity 1.0 Infinity Infinity Infinity +> Infinity 1.5 Infinity Infinity Infinity +> Infinity Infinity Infinity NaN Infinity +> Infinity NaN NaN NaN NaN +> NaN -Infinity NaN NaN NaN +> NaN -1.0 NaN NaN NaN +> NaN 0.0 NaN NaN NaN +> NaN 1.0 NaN NaN NaN +> NaN 1.5 NaN NaN NaN +> NaN Infinity NaN NaN NaN +> NaN NaN NaN NaN NaN +> rows (ordered): 49 + +SELECT A.D, B.D, A.D / B.D, MOD(A.D, B.D) FROM TEST A JOIN TEST B WHERE B.D <> 0 ORDER BY A.D, B.D; +> D D A.D / B.D MOD(A.D, B.D) +> --------- --------- ---------- ------------- +> -Infinity -Infinity NaN NaN +> -Infinity -1.0 Infinity NaN +> -Infinity 1.0 -Infinity NaN +> -Infinity 1.5 -Infinity NaN +> -Infinity Infinity NaN NaN +> -Infinity NaN NaN NaN +> -1.0 -Infinity 0.0 -1.0 +> -1.0 -1.0 1.0 0.0 +> -1.0 1.0 -1.0 0.0 +> -1.0 1.5 -0.6666667 -1.0 +> -1.0 Infinity 0.0 -1.0 +> -1.0 NaN NaN NaN +> 0.0 -Infinity 0.0 0.0 +> 0.0 -1.0 0.0 0.0 +> 0.0 1.0 0.0 0.0 +> 0.0 1.5 0.0 0.0 +> 0.0 Infinity 0.0 0.0 +> 0.0 NaN NaN NaN +> 1.0 -Infinity 0.0 1.0 +> 1.0 -1.0 -1.0 0.0 +> 1.0 1.0 1.0 0.0 +> 1.0 1.5 0.6666667 1.0 +> 1.0 Infinity 0.0 1.0 +> 1.0 NaN NaN NaN +> 1.5 -Infinity 0.0 1.5 +> 1.5 -1.0 -1.5 0.5 +> 1.5 1.0 1.5 0.5 +> 1.5 1.5 1.0 0.0 +> 1.5 Infinity 0.0 1.5 +> 1.5 NaN NaN NaN +> Infinity -Infinity NaN NaN +> Infinity -1.0 -Infinity NaN +> Infinity 1.0 Infinity NaN +> Infinity 1.5 Infinity NaN +> Infinity Infinity NaN NaN +> Infinity NaN NaN NaN +> NaN -Infinity NaN NaN +> NaN -1.0 NaN NaN +> NaN 1.0 NaN NaN +> NaN 1.5 NaN NaN +> NaN Infinity NaN NaN +> NaN NaN NaN NaN +> rows (ordered): 42 + +SELECT A.D, B.D, A.D > B.D, A.D = B.D, A.D < B.D FROM TEST A JOIN TEST B ORDER BY A.D, B.D; +> D D A.D > B.D A.D = B.D A.D < B.D +> --------- --------- --------- --------- --------- +> -Infinity -Infinity FALSE TRUE FALSE +> -Infinity -1.0 FALSE FALSE TRUE +> -Infinity 0.0 FALSE FALSE TRUE +> -Infinity 1.0 FALSE FALSE TRUE +> -Infinity 1.5 FALSE FALSE TRUE +> -Infinity Infinity FALSE FALSE TRUE +> -Infinity NaN FALSE FALSE TRUE +> -1.0 -Infinity TRUE FALSE FALSE +> -1.0 -1.0 FALSE TRUE FALSE +> -1.0 0.0 FALSE FALSE TRUE +> -1.0 1.0 FALSE FALSE TRUE +> -1.0 1.5 FALSE FALSE TRUE +> -1.0 Infinity FALSE FALSE TRUE +> -1.0 NaN FALSE FALSE TRUE +> 0.0 -Infinity TRUE FALSE FALSE +> 0.0 -1.0 TRUE FALSE FALSE +> 0.0 0.0 FALSE TRUE FALSE +> 0.0 1.0 FALSE FALSE TRUE +> 0.0 1.5 FALSE FALSE TRUE +> 0.0 Infinity FALSE FALSE TRUE +> 0.0 NaN FALSE FALSE TRUE +> 1.0 -Infinity TRUE FALSE FALSE +> 1.0 -1.0 TRUE FALSE FALSE +> 1.0 0.0 TRUE FALSE FALSE +> 1.0 1.0 FALSE TRUE FALSE +> 1.0 1.5 FALSE FALSE TRUE +> 1.0 Infinity FALSE FALSE TRUE +> 1.0 NaN FALSE FALSE TRUE +> 1.5 -Infinity TRUE FALSE FALSE +> 1.5 -1.0 TRUE FALSE FALSE +> 1.5 0.0 TRUE FALSE FALSE +> 1.5 1.0 TRUE FALSE FALSE +> 1.5 1.5 FALSE TRUE FALSE +> 1.5 Infinity FALSE FALSE TRUE +> 1.5 NaN FALSE FALSE TRUE +> Infinity -Infinity TRUE FALSE FALSE +> Infinity -1.0 TRUE FALSE FALSE +> Infinity 0.0 TRUE FALSE FALSE +> Infinity 1.0 TRUE FALSE FALSE +> Infinity 1.5 TRUE FALSE FALSE +> Infinity Infinity FALSE TRUE FALSE +> Infinity NaN FALSE FALSE TRUE +> NaN -Infinity TRUE FALSE FALSE +> NaN -1.0 TRUE FALSE FALSE +> NaN 0.0 TRUE FALSE FALSE +> NaN 1.0 TRUE FALSE FALSE +> NaN 1.5 TRUE FALSE FALSE +> NaN Infinity TRUE FALSE FALSE +> NaN NaN FALSE TRUE FALSE +> rows (ordered): 49 + +SELECT D, CAST(D AS DOUBLE PRECISION) D1, CAST(D AS DECFLOAT) D2 FROM TEST ORDER BY D; +> D D1 D2 +> --------- --------- --------- +> -Infinity -Infinity -Infinity +> -1.0 -1.0 -1 +> 0.0 0.0 0 +> 1.0 1.0 1 +> 1.5 1.5 1.5 +> Infinity Infinity Infinity +> NaN NaN NaN +> rows (ordered): 7 + +EXPLAIN SELECT CAST('Infinity' AS REAL), CAST('-Infinity' AS REAL), CAST('NaN' AS REAL), CAST(0 AS REAL); +>> SELECT CAST('Infinity' AS REAL), CAST('-Infinity' AS REAL), CAST('NaN' AS REAL), CAST(0.0 AS REAL) + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION TABLE TEST; +> SCRIPT +> ----------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "D" REAL ); +> -- 7 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> INSERT INTO "PUBLIC"."TEST" VALUES ('-Infinity'), (-1.0), (0.0), (1.0), (1.5), ('Infinity'), ('NaN'); +> rows (ordered): 4 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/datatypes/row.sql b/h2/src/test/org/h2/test/scripts/datatypes/row.sql new file mode 100644 index 0000000..d1bd244 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/row.sql @@ -0,0 +1,220 @@ +-- 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 +-- + +SELECT (); +>> ROW () + +SELECT (1,); +> exception SYNTAX_ERROR_2 + +SELECT ROW (); +>> ROW () + +SELECT ROW (1,); +> exception SYNTAX_ERROR_2 + +SELECT ROW (10); +>> ROW (10) + +SELECT (10, 20, 30); +>> ROW (10, 20, 30) + +SELECT (1, NULL) IS NOT DISTINCT FROM (1, NULL); +>> TRUE + +SELECT (1, NULL) IS DISTINCT FROM ROW (1, NULL); +>> FALSE + +SELECT (1, NULL) = (1, NULL); +>> null + +SELECT (1, NULL) <> (1, NULL); +>> null + +SELECT ROW (NULL) = (NULL, NULL); +> exception COLUMN_COUNT_DOES_NOT_MATCH + +select (1, NULL, 2) = (1, NULL, 1); +>> FALSE + +select (1, NULL, 2) <> (1, NULL, 1); +>> TRUE + +SELECT (1, NULL) > (1, NULL); +>> null + +SELECT (1, 2) > (1, NULL); +>> null + +SELECT (1, 2, NULL) > (1, 1, NULL); +>> TRUE + +SELECT (1, 1, NULL) > (1, 2, NULL); +>> FALSE + +SELECT (1, 2, NULL) < (1, 1, NULL); +>> FALSE + +SELECT (1, 1, NULL) <= (1, 1, NULL); +>> null + +SELECT (1, 2) IN (SELECT 1, 2); +>> TRUE + +SELECT (1, 2) IN (SELECT * FROM VALUES (1, 2), (1, NULL)); +>> TRUE + +SELECT (1, 2) IN (SELECT * FROM VALUES (1, 1), (1, NULL)); +>> null + +SELECT (1, 2) IN (SELECT * FROM VALUES (1, 1), (1, 3)); +>> FALSE + +SELECT (1, NULL) IN (SELECT 1, NULL); +>> null + +SELECT (1, ARRAY[1]) IN (SELECT 1, ARRAY[1]); +>> TRUE + +SELECT (1, ARRAY[1]) IN (SELECT 1, ARRAY[2]); +>> FALSE + +SELECT (1, ARRAY[NULL]) IN (SELECT 1, ARRAY[NULL]); +>> null + +CREATE TABLE TEST (R ROW(A INT, B VARCHAR)); +> ok + +INSERT INTO TEST VALUES ((1, 2)); +> update count: 1 + +INSERT INTO TEST VALUES ((1, X'3341')); +> update count: 1 + +TABLE TEST; +> R +> ----------- +> ROW (1, 2) +> ROW (1, 3A) +> rows: 2 + +DROP TABLE TEST; +> ok + +SELECT CAST((1, 2.1) AS ROW(A INT, B INT)); +>> ROW (1, 2) + +SELECT CAST((1, 2.1) AS ROW(A INT, B INT, C INT)); +> exception DATA_CONVERSION_ERROR_1 + +SELECT CAST(1 AS ROW(V INT)); +>> ROW (1) + +SELECT CAST((1, 2) AS ROW(A INT, A INT)); +> exception DUPLICATE_COLUMN_NAME_1 + +CREATE DOMAIN D1 AS ROW(A INT); +> ok + +CREATE DOMAIN D2 AS BIGINT ARRAY; +> ok + +CREATE TABLE TEST(A ROW(A INT, B INT ARRAY[1]) ARRAY, B BIGINT ARRAY[2] ARRAY[3], C ROW(V BIGINT, A INT ARRAY), + D D1, E D2); +> ok + +SELECT COLUMN_NAME, DATA_TYPE, DOMAIN_NAME, MAXIMUM_CARDINALITY, DTD_IDENTIFIER FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = 'PUBLIC'; +> COLUMN_NAME DATA_TYPE DOMAIN_NAME MAXIMUM_CARDINALITY DTD_IDENTIFIER +> ----------- --------- ----------- ------------------- -------------- +> A ARRAY null 65536 1 +> B ARRAY null 3 2 +> C ROW null null 3 +> D ROW D1 null 4 +> E ARRAY D2 65536 5 +> rows: 5 + +SELECT OBJECT_NAME, OBJECT_TYPE, COLLECTION_TYPE_IDENTIFIER, DATA_TYPE, MAXIMUM_CARDINALITY, DTD_IDENTIFIER + FROM INFORMATION_SCHEMA.ELEMENT_TYPES; +> OBJECT_NAME OBJECT_TYPE COLLECTION_TYPE_IDENTIFIER DATA_TYPE MAXIMUM_CARDINALITY DTD_IDENTIFIER +> ----------- ----------- -------------------------- --------- ------------------- -------------- +> D2 DOMAIN TYPE BIGINT null TYPE_ +> TEST TABLE 1 ROW null 1_ +> TEST TABLE 1__2 INTEGER null 1__2_ +> TEST TABLE 2 ARRAY 2 2_ +> TEST TABLE 2_ BIGINT null 2__ +> TEST TABLE 3_2 INTEGER null 3_2_ +> TEST TABLE 5 BIGINT null 5_ +> rows: 7 + +SELECT OBJECT_NAME, OBJECT_TYPE, ROW_IDENTIFIER, FIELD_NAME, ORDINAL_POSITION, DATA_TYPE, MAXIMUM_CARDINALITY, + DTD_IDENTIFIER + FROM INFORMATION_SCHEMA.FIELDS; +> OBJECT_NAME OBJECT_TYPE ROW_IDENTIFIER FIELD_NAME ORDINAL_POSITION DATA_TYPE MAXIMUM_CARDINALITY DTD_IDENTIFIER +> ----------- ----------- -------------- ---------- ---------------- --------- ------------------- -------------- +> D1 DOMAIN TYPE A 1 INTEGER null TYPE_1 +> TEST TABLE 1_ A 1 INTEGER null 1__1 +> TEST TABLE 1_ B 2 ARRAY 1 1__2 +> TEST TABLE 3 A 2 ARRAY 65536 3_2 +> TEST TABLE 3 V 1 BIGINT null 3_1 +> TEST TABLE 4 A 1 INTEGER null 4_1 +> rows: 6 + +DROP TABLE TEST; +> ok + +DROP DOMAIN D1; +> ok + +DROP DOMAIN D2; +> ok + +@reconnect off + +CREATE LOCAL TEMPORARY TABLE TEST AS (SELECT ROW(1, 2) R); +> ok + +CREATE INDEX IDX ON TEST(R); +> ok + +DROP TABLE TEST; +> ok + +CREATE LOCAL TEMPORARY TABLE TEST(R ROW(C CLOB)); +> ok + +CREATE INDEX IDX ON TEST(R); +> exception FEATURE_NOT_SUPPORTED_1 + +DROP TABLE TEST; +> ok + +@reconnect on + +EXECUTE IMMEDIATE 'CREATE TABLE TEST AS SELECT (' || (SELECT LISTAGG('1') FROM SYSTEM_RANGE(1, 16384)) || ')'; +> ok + +DROP TABLE TEST; +> ok + +EXECUTE IMMEDIATE 'CREATE TABLE TEST AS SELECT (' || (SELECT LISTAGG('1') FROM SYSTEM_RANGE(1, 16385)) || ')'; +> exception TOO_MANY_COLUMNS_1 + +EXECUTE IMMEDIATE 'CREATE TABLE TEST(R ROW(' || (SELECT LISTAGG('C' || X || ' INTEGER') FROM SYSTEM_RANGE(1, 16384)) || '))'; +> ok + +DROP TABLE TEST; +> ok + +EXECUTE IMMEDIATE 'CREATE TABLE TEST(R ROW(' || (SELECT LISTAGG('C' || X || ' INTEGER') FROM SYSTEM_RANGE(1, 16385)) || '))'; +> exception TOO_MANY_COLUMNS_1 + +-- The next tests should be at the of this file + +SET MAX_MEMORY_ROWS = 2; +> ok + +SELECT (X, X) FROM SYSTEM_RANGE(1, 100000) ORDER BY -X FETCH FIRST ROW ONLY; +>> ROW (100000, 100000) diff --git a/h2/src/test/org/h2/test/scripts/datatypes/smallint.sql b/h2/src/test/org/h2/test/scripts/datatypes/smallint.sql new file mode 100644 index 0000000..53362fe --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/smallint.sql @@ -0,0 +1,30 @@ +-- 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 +-- + +-- Division + +SELECT CAST(1 AS SMALLINT) / CAST(0 AS SMALLINT); +> exception DIVISION_BY_ZERO_1 + +SELECT CAST(-32768 AS SMALLINT) / CAST(1 AS SMALLINT); +>> -32768 + +SELECT CAST(-32768 AS SMALLINT) / CAST(-1 AS SMALLINT); +> exception NUMERIC_VALUE_OUT_OF_RANGE_1 + +EXPLAIN VALUES CAST(1 AS SMALLINT); +>> VALUES (CAST(1 AS SMALLINT)) + +EXPLAIN VALUES CAST(1 AS YEAR); +> exception UNKNOWN_DATA_TYPE_1 + +SET MODE MySQL; +> ok + +EXPLAIN VALUES CAST(1 AS YEAR); +>> VALUES (CAST(1 AS SMALLINT)) + +SET MODE Regular; +> ok diff --git a/h2/src/test/org/h2/test/scripts/datatypes/time-with-time-zone.sql b/h2/src/test/org/h2/test/scripts/datatypes/time-with-time-zone.sql new file mode 100644 index 0000000..b400394 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/time-with-time-zone.sql @@ -0,0 +1,98 @@ +-- 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 +-- + +CREATE TABLE TEST(T1 TIME WITH TIME ZONE, T2 TIME WITH TIME ZONE); +> ok + +INSERT INTO TEST(T1, T2) VALUES (TIME WITH TIME ZONE '10:00:00+01', TIME WITH TIME ZONE '11:00:00+02'); +> update count: 1 + +SELECT T1, T2, T1 = T2 FROM TEST; +> T1 T2 T1 = T2 +> ----------- ----------- ------- +> 10:00:00+01 11:00:00+02 TRUE +> rows: 1 + +SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DATA_TYPE +> ----------- ------------------- +> T1 TIME WITH TIME ZONE +> T2 TIME WITH TIME ZONE +> rows (ordered): 2 + +ALTER TABLE TEST ADD (T3 TIME(0), T4 TIME(9) WITHOUT TIME ZONE); +> ok + +SELECT COLUMN_NAME, DATA_TYPE, DATETIME_PRECISION FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DATA_TYPE DATETIME_PRECISION +> ----------- ------------------- ------------------ +> T1 TIME WITH TIME ZONE 0 +> T2 TIME WITH TIME ZONE 0 +> T3 TIME 0 +> T4 TIME 9 +> rows (ordered): 4 + +ALTER TABLE TEST ADD T5 TIME(10); +> exception INVALID_VALUE_SCALE + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(T TIME WITH TIME ZONE, T0 TIME(0) WITH TIME ZONE, T1 TIME(1) WITH TIME ZONE, + T2 TIME(2) WITH TIME ZONE, T3 TIME(3) WITH TIME ZONE, T4 TIME(4) WITH TIME ZONE, T5 TIME(5) WITH TIME ZONE, + T6 TIME(6) WITH TIME ZONE, T7 TIME(7) WITH TIME ZONE, T8 TIME(8) WITH TIME ZONE, T9 TIME(9) WITH TIME ZONE); +> ok + +INSERT INTO TEST VALUES ('08:00:00.123456789-01', '08:00:00.123456789Z', '08:00:00.123456789+01:02:03', + '08:00:00.123456789-3:00', '08:00:00.123456789+4:30', '08:00:00.123456789Z', '08:00:00.123456789Z', + '08:00:00.123456789Z', '08:00:00.123456789Z', '08:00:00.123456789Z', '08:00:00.123456789Z'); +> update count: 1 + +SELECT * FROM TEST; +> T T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 +> ----------- ----------- ------------------- -------------- ------------------ ---------------- ----------------- ------------------ ------------------- -------------------- --------------------- +> 08:00:00-01 08:00:00+00 08:00:00.1+01:02:03 08:00:00.12-03 08:00:00.123+04:30 08:00:00.1235+00 08:00:00.12346+00 08:00:00.123457+00 08:00:00.1234568+00 08:00:00.12345679+00 08:00:00.123456789+00 +> rows: 1 + +DELETE FROM TEST; +> update count: 1 + +INSERT INTO TEST(T0, T8) VALUES ('23:59:59.999999999Z', '23:59:59.999999999Z'); +> update count: 1 + +SELECT T0 FROM TEST; +>> 23:59:59+00 + +SELECT T8 FROM TEST; +>> 23:59:59.99999999+00 + +DROP TABLE TEST; +> ok + +SELECT TIME WITH TIME ZONE '11:22:33'; +> exception INVALID_DATETIME_CONSTANT_2 + +SELECT TIME WITH TIME ZONE '11:22:33 Europe/London'; +> exception INVALID_DATETIME_CONSTANT_2 + +SELECT CAST (TIMESTAMP WITH TIME ZONE '1000000000-12-31 11:22:33.123456789+02' AS TIME WITH TIME ZONE); +>> 11:22:33+02 + +SELECT CAST (TIMESTAMP WITH TIME ZONE '1000000000-12-31 11:22:33.123456789+02' AS TIME(9) WITH TIME ZONE); +>> 11:22:33.123456789+02 + +SELECT CAST (TIMESTAMP WITH TIME ZONE '-1000000000-12-31 11:22:33.123456789+02' AS TIME(9) WITH TIME ZONE); +>> 11:22:33.123456789+02 + +SELECT CAST (TIME WITH TIME ZONE '10:00:00Z' AS DATE); +> exception DATA_CONVERSION_ERROR_1 + +SELECT TIME WITH TIME ZONE '23:00:00+01' - TIME WITH TIME ZONE '00:00:30-01'; +>> INTERVAL '20:59:30' HOUR TO SECOND + +SELECT TIME WITH TIME ZONE '10:00:00-10' + INTERVAL '30' MINUTE; +>> 10:30:00-10 diff --git a/h2/src/test/org/h2/test/scripts/datatypes/time.sql b/h2/src/test/org/h2/test/scripts/datatypes/time.sql new file mode 100644 index 0000000..a51b234 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/time.sql @@ -0,0 +1,128 @@ +-- 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 +-- + +CREATE TABLE TEST(T1 TIME, T2 TIME WITHOUT TIME ZONE); +> ok + +INSERT INTO TEST(T1, T2) VALUES (TIME '10:00:00', TIME WITHOUT TIME ZONE '10:00:00'); +> update count: 1 + +SELECT T1, T2, T1 = T2 FROM TEST; +> T1 T2 T1 = T2 +> -------- -------- ------- +> 10:00:00 10:00:00 TRUE +> rows: 1 + +SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DATA_TYPE +> ----------- --------- +> T1 TIME +> T2 TIME +> rows (ordered): 2 + +ALTER TABLE TEST ADD (T3 TIME(0), T4 TIME(9) WITHOUT TIME ZONE); +> ok + +SELECT COLUMN_NAME, DATA_TYPE, DATETIME_PRECISION FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DATA_TYPE DATETIME_PRECISION +> ----------- --------- ------------------ +> T1 TIME 0 +> T2 TIME 0 +> T3 TIME 0 +> T4 TIME 9 +> rows (ordered): 4 + +ALTER TABLE TEST ADD T5 TIME(10); +> exception INVALID_VALUE_SCALE + +DROP TABLE TEST; +> ok + +-- Check that TIME is allowed as a column name +CREATE TABLE TEST(TIME TIME); +> ok + +INSERT INTO TEST VALUES (TIME '08:00:00'); +> update count: 1 + +SELECT TIME FROM TEST; +>> 08:00:00 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(T TIME, T0 TIME(0), T1 TIME(1), T2 TIME(2), T3 TIME(3), T4 TIME(4), T5 TIME(5), T6 TIME(6), + T7 TIME(7), T8 TIME(8), T9 TIME(9)); +> ok + +INSERT INTO TEST VALUES ('08:00:00.123456789', '08:00:00.123456789', '08:00:00.123456789', '08:00:00.123456789', + '08:00:00.123456789', '08:00:00.123456789', '08:00:00.123456789', '08:00:00.123456789', '08:00:00.123456789', + '08:00:00.123456789', '08:00:00.123456789'); +> update count: 1 + +SELECT * FROM TEST; +> T T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 +> -------- -------- ---------- ----------- ------------ ------------- -------------- --------------- ---------------- ----------------- ------------------ +> 08:00:00 08:00:00 08:00:00.1 08:00:00.12 08:00:00.123 08:00:00.1235 08:00:00.12346 08:00:00.123457 08:00:00.1234568 08:00:00.12345679 08:00:00.123456789 +> rows: 1 + +DELETE FROM TEST; +> update count: 1 + +INSERT INTO TEST(T0, T8) VALUES ('23:59:59.999999999', '23:59:59.999999999'); +> update count: 1 + +SELECT T0 FROM TEST; +>> 23:59:59 + +SELECT T8 FROM TEST; +>> 23:59:59.99999999 + +DROP TABLE TEST; +> ok + +SELECT TIME '11:22:33'; +>> 11:22:33 + +SELECT TIME '11:22'; +>> 11:22:00 + +SELECT TIME '112233'; +>> 11:22:33 + +SELECT TIME '1122'; +>> 11:22:00 + +SELECT TIME '12233'; +> exception INVALID_DATETIME_CONSTANT_2 + +SELECT TIME '122'; +> exception INVALID_DATETIME_CONSTANT_2 + +SELECT TIME '11:22:33.1'; +>> 11:22:33.1 + +SELECT TIME '112233.1'; +>> 11:22:33.1 + +SELECT TIME '12233.1'; +> exception INVALID_DATETIME_CONSTANT_2 + +SELECT TIME '1122.1'; +> exception INVALID_DATETIME_CONSTANT_2 + +SELECT CAST (TIMESTAMP '1000000000-12-31 11:22:33.123456789' AS TIME); +>> 11:22:33 + +SELECT CAST (TIMESTAMP '1000000000-12-31 11:22:33.123456789' AS TIME(9)); +>> 11:22:33.123456789 + +SELECT CAST (TIMESTAMP '-1000000000-12-31 11:22:33.123456789' AS TIME(9)); +>> 11:22:33.123456789 + +SELECT CAST (TIME '10:00:00' AS DATE); +> exception DATA_CONVERSION_ERROR_1 diff --git a/h2/src/test/org/h2/test/scripts/datatypes/timestamp-with-time-zone.sql b/h2/src/test/org/h2/test/scripts/datatypes/timestamp-with-time-zone.sql new file mode 100644 index 0000000..290d975 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/timestamp-with-time-zone.sql @@ -0,0 +1,138 @@ +-- 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 +-- + +CREATE TABLE tab_with_timezone(x TIMESTAMP WITH TIME ZONE); +> ok + +INSERT INTO tab_with_timezone(x) VALUES ('2017-01-01'); +> update count: 1 + +SELECT CAST("Query".X AS TIMESTAMP) FROM (select * from tab_with_timezone where x > '2016-01-01') AS "Query"; +>> 2017-01-01 00:00:00 + +DELETE FROM tab_with_timezone; +> update count: 1 + +INSERT INTO tab_with_timezone VALUES ('2018-03-25 01:59:00 Europe/Berlin'), ('2018-03-25 03:00:00 Europe/Berlin'); +> update count: 2 + +SELECT * FROM tab_with_timezone ORDER BY X; +> X +> ---------------------- +> 2018-03-25 01:59:00+01 +> 2018-03-25 03:00:00+02 +> rows (ordered): 2 + +SELECT TIMESTAMP WITH TIME ZONE '2000-01-10 00:00:00 -02' AS A, + TIMESTAMP WITH TIME ZONE '2000-01-10 00:00:00.000000000 +02:00' AS B, + TIMESTAMP WITH TIME ZONE '2000-01-10 00:00:00.000000000+02:00' AS C, + TIMESTAMP WITH TIME ZONE '2000-01-10T00:00:00.000000000+09:00[Asia/Tokyo]' AS D; +> A B C D +> ---------------------- ---------------------- ---------------------- ---------------------- +> 2000-01-10 00:00:00-02 2000-01-10 00:00:00+02 2000-01-10 00:00:00+02 2000-01-10 00:00:00+09 +> rows: 1 + +CREATE TABLE TEST(T1 TIMESTAMP WITH TIME ZONE, T2 TIMESTAMP(0) WITH TIME ZONE, T3 TIMESTAMP(9) WITH TIME ZONE); +> ok + +SELECT COLUMN_NAME, DATA_TYPE, DATETIME_PRECISION FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DATA_TYPE DATETIME_PRECISION +> ----------- ------------------------ ------------------ +> T1 TIMESTAMP WITH TIME ZONE 6 +> T2 TIMESTAMP WITH TIME ZONE 0 +> T3 TIMESTAMP WITH TIME ZONE 9 +> rows (ordered): 3 + +ALTER TABLE TEST ADD T4 TIMESTAMP (10) WITH TIME ZONE; +> exception INVALID_VALUE_SCALE + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(T TIMESTAMP WITH TIME ZONE, T0 TIMESTAMP(0) WITH TIME ZONE, T1 TIMESTAMP(1) WITH TIME ZONE, + T2 TIMESTAMP(2) WITH TIME ZONE, T3 TIMESTAMP(3) WITH TIME ZONE, T4 TIMESTAMP(4) WITH TIME ZONE, + T5 TIMESTAMP(5) WITH TIME ZONE, T6 TIMESTAMP(6) WITH TIME ZONE, T7 TIMESTAMP(7) WITH TIME ZONE, + T8 TIMESTAMP(8) WITH TIME ZONE, T9 TIMESTAMP(9) WITH TIME ZONE); +> ok + +INSERT INTO TEST VALUES ('2000-01-01 08:00:00.123456789Z', '2000-01-01 08:00:00.123456789Z', + '2000-01-01 08:00:00.123456789Z', '2000-01-01 08:00:00.123456789Z', '2000-01-01 08:00:00.123456789Z', + '2000-01-01 08:00:00.123456789Z', '2000-01-01 08:00:00.123456789Z', '2000-01-01 08:00:00.123456789Z', + '2000-01-01 08:00:00.123456789Z', '2000-01-01 08:00:00.123456789Z', '2000-01-01 08:00:00.123456789Z'); +> update count: 1 + +SELECT T, T0, T1, T2 FROM TEST; +> T T0 T1 T2 +> ----------------------------- ---------------------- ------------------------ ------------------------- +> 2000-01-01 08:00:00.123457+00 2000-01-01 08:00:00+00 2000-01-01 08:00:00.1+00 2000-01-01 08:00:00.12+00 +> rows: 1 + +SELECT T3, T4, T5, T6 FROM TEST; +> T3 T4 T5 T6 +> -------------------------- --------------------------- ---------------------------- ----------------------------- +> 2000-01-01 08:00:00.123+00 2000-01-01 08:00:00.1235+00 2000-01-01 08:00:00.12346+00 2000-01-01 08:00:00.123457+00 +> rows: 1 + +SELECT T7, T8, T9 FROM TEST; +> T7 T8 T9 +> ------------------------------ ------------------------------- -------------------------------- +> 2000-01-01 08:00:00.1234568+00 2000-01-01 08:00:00.12345679+00 2000-01-01 08:00:00.123456789+00 +> rows: 1 + +DELETE FROM TEST; +> update count: 1 + +INSERT INTO TEST(T0) VALUES ('2000-01-01 23:59:59.999999999Z'); +> update count: 1 + +SELECT T0 FROM TEST; +>> 2000-01-02 00:00:00+00 + +DROP TABLE TEST; +> ok + +SELECT (LOCALTIMESTAMP + 1) = (CURRENT_TIMESTAMP + 1); +>> TRUE + +SELECT (TIMESTAMP WITH TIME ZONE '2010-01-01 10:00:00+01' + 1) A, + (1 + TIMESTAMP WITH TIME ZONE '2010-01-01 10:00:00+01') B; +> A B +> ---------------------- ---------------------- +> 2010-01-02 10:00:00+01 2010-01-02 10:00:00+01 +> rows: 1 + +SELECT (LOCALTIMESTAMP - 1) = (CURRENT_TIMESTAMP - 1); +>> TRUE + +SELECT (TIMESTAMP WITH TIME ZONE '2010-01-01 10:00:00+01' - 1) A; +> A +> ---------------------- +> 2009-12-31 10:00:00+01 +> rows: 1 + +CALL TIMESTAMP WITH TIME ZONE '-1000000000-01-01 00:00:00Z'; +>> -1000000000-01-01 00:00:00+00 + +CALL TIMESTAMP WITH TIME ZONE '1000000000-12-31 23:59:59.999999999Z'; +>> 1000000000-12-31 23:59:59.999999999+00 + +CALL TIMESTAMP WITH TIME ZONE '-1000000001-12-31 23:59:59.999999999Z'; +> exception INVALID_DATETIME_CONSTANT_2 + +CALL TIMESTAMP WITH TIME ZONE '1000000001-01-01 00:00:00Z'; +> exception INVALID_DATETIME_CONSTANT_2 + +SELECT CAST (TIMESTAMP WITH TIME ZONE '2000-01-01 23:59:59.999999999Z' AS TIMESTAMP WITH TIME ZONE); +>> 2000-01-02 00:00:00+00 + +SELECT CAST (TIMESTAMP WITH TIME ZONE '1000000000-12-31 23:59:59.999999999Z' AS TIMESTAMP WITH TIME ZONE); +>> 1000000000-12-31 23:59:59.999999+00 + +SELECT CAST (CAST (TIMESTAMP '1000000000-12-31 23:59:59.999999999' AS TIMESTAMP(9) WITH TIME ZONE) AS TIMESTAMP(9)); +>> 1000000000-12-31 23:59:59.999999999 + +SELECT CAST (CAST (TIMESTAMP '-1000000000-12-31 00:00:00' AS TIMESTAMP(9) WITH TIME ZONE) AS TIMESTAMP(9)); +>> -1000000000-12-31 00:00:00 diff --git a/h2/src/test/org/h2/test/scripts/datatypes/timestamp.sql b/h2/src/test/org/h2/test/scripts/datatypes/timestamp.sql new file mode 100644 index 0000000..b2bfa5f --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/timestamp.sql @@ -0,0 +1,173 @@ +-- 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 +-- + +CREATE TABLE TEST(T1 TIMESTAMP, T2 TIMESTAMP WITHOUT TIME ZONE); +> ok + +INSERT INTO TEST(T1, T2) VALUES (TIMESTAMP '2010-01-01 10:00:00', TIMESTAMP WITHOUT TIME ZONE '2010-01-01 10:00:00'); +> update count: 1 + +SELECT T1, T2, T1 = T2 FROM TEST; +> T1 T2 T1 = T2 +> ------------------- ------------------- ------- +> 2010-01-01 10:00:00 2010-01-01 10:00:00 TRUE +> rows: 1 + +ALTER TABLE TEST ADD (T3 TIMESTAMP(0), T4 TIMESTAMP(9) WITHOUT TIME ZONE, + DT1 DATETIME, DT2 DATETIME(0), DT3 DATETIME(9), + DT2_1 DATETIME2, DT2_2 DATETIME2(0), DT2_3 DATETIME2(7), + SDT1 SMALLDATETIME); +> ok + +SELECT COLUMN_NAME, DATA_TYPE, DATETIME_PRECISION FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DATA_TYPE DATETIME_PRECISION +> ----------- --------- ------------------ +> T1 TIMESTAMP 6 +> T2 TIMESTAMP 6 +> T3 TIMESTAMP 0 +> T4 TIMESTAMP 9 +> DT1 TIMESTAMP 6 +> DT2 TIMESTAMP 0 +> DT3 TIMESTAMP 9 +> DT2_1 TIMESTAMP 6 +> DT2_2 TIMESTAMP 0 +> DT2_3 TIMESTAMP 7 +> SDT1 TIMESTAMP 0 +> rows (ordered): 11 + +ALTER TABLE TEST ADD T5 TIMESTAMP(10); +> exception INVALID_VALUE_SCALE + +ALTER TABLE TEST ADD DT4 DATETIME(10); +> exception INVALID_VALUE_SCALE + +ALTER TABLE TEST ADD DT2_4 DATETIME2(10); +> exception INVALID_VALUE_SCALE + +ALTER TABLE TEST ADD STD2 SMALLDATETIME(1); +> exception SYNTAX_ERROR_1 + +DROP TABLE TEST; +> ok + +-- Check that TIMESTAMP is allowed as a column name +CREATE TABLE TEST(TIMESTAMP TIMESTAMP(0)); +> ok + +INSERT INTO TEST VALUES (TIMESTAMP '1999-12-31 08:00:00'); +> update count: 1 + +SELECT TIMESTAMP FROM TEST; +>> 1999-12-31 08:00:00 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(T TIMESTAMP, T0 TIMESTAMP(0), T1 TIMESTAMP(1), T2 TIMESTAMP(2), T3 TIMESTAMP(3), T4 TIMESTAMP(4), + T5 TIMESTAMP(5), T6 TIMESTAMP(6), T7 TIMESTAMP(7), T8 TIMESTAMP(8), T9 TIMESTAMP(9)); +> ok + +INSERT INTO TEST VALUES ('2000-01-01 08:00:00.123456789', '2000-01-01 08:00:00.123456789', + '2000-01-01 08:00:00.123456789', '2000-01-01 08:00:00.123456789', '2000-01-01 08:00:00.123456789', + '2000-01-01 08:00:00.123456789', '2000-01-01 08:00:00.123456789', '2000-01-01 08:00:00.123456789', + '2000-01-01 08:00:00.123456789', '2000-01-01 08:00:00.123456789', '2000-01-01 08:00:00.123456789'); +> update count: 1 + +SELECT T, T0, T1, T2 FROM TEST; +> T T0 T1 T2 +> -------------------------- ------------------- --------------------- ---------------------- +> 2000-01-01 08:00:00.123457 2000-01-01 08:00:00 2000-01-01 08:00:00.1 2000-01-01 08:00:00.12 +> rows: 1 + +SELECT T3, T4, T5, T6 FROM TEST; +> T3 T4 T5 T6 +> ----------------------- ------------------------ ------------------------- -------------------------- +> 2000-01-01 08:00:00.123 2000-01-01 08:00:00.1235 2000-01-01 08:00:00.12346 2000-01-01 08:00:00.123457 +> rows: 1 + +SELECT T7, T8, T9 FROM TEST; +> T7 T8 T9 +> --------------------------- ---------------------------- ----------------------------- +> 2000-01-01 08:00:00.1234568 2000-01-01 08:00:00.12345679 2000-01-01 08:00:00.123456789 +> rows: 1 + +DELETE FROM TEST; +> update count: 1 + +INSERT INTO TEST(T0) VALUES ('2000-01-01 23:59:59.999999999'); +> update count: 1 + +SELECT T0 FROM TEST; +>> 2000-01-02 00:00:00 + +DROP TABLE TEST; +> ok + +create table test(id int, d timestamp); +> ok + +insert into test values(1, '2006-01-01 12:00:00.000'); +> update count: 1 + +insert into test values(1, '1999-12-01 23:59:00.000'); +> update count: 1 + +select * from test where d= '1999-12-01 23:59:00.000'; +> ID D +> -- ------------------- +> 1 1999-12-01 23:59:00 +> rows: 1 + +select * from test where d= timestamp '2006-01-01 12:00:00.000'; +> ID D +> -- ------------------- +> 1 2006-01-01 12:00:00 +> rows: 1 + +drop table test; +> ok + +SELECT TIMESTAMP '2000-01-02 11:22:33'; +>> 2000-01-02 11:22:33 + +SELECT TIMESTAMP '2000-01-02T11:22:33'; +>> 2000-01-02 11:22:33 + +SELECT TIMESTAMP '20000102 11:22:33'; +>> 2000-01-02 11:22:33 + +SELECT TIMESTAMP '20000102T11:22:33'; +>> 2000-01-02 11:22:33 + +SELECT TIMESTAMP '2000-01-02 112233'; +>> 2000-01-02 11:22:33 + +SELECT TIMESTAMP '2000-01-02T112233'; +>> 2000-01-02 11:22:33 + +SELECT TIMESTAMP '20000102 112233'; +>> 2000-01-02 11:22:33 + +SELECT TIMESTAMP '20000102T112233'; +>> 2000-01-02 11:22:33 + +CALL TIMESTAMP '-1000000000-01-01 00:00:00'; +>> -1000000000-01-01 00:00:00 + +CALL TIMESTAMP '1000000000-12-31 23:59:59.999999999'; +>> 1000000000-12-31 23:59:59.999999999 + +CALL TIMESTAMP '-1000000001-12-31 23:59:59.999999999'; +> exception INVALID_DATETIME_CONSTANT_2 + +CALL TIMESTAMP '1000000001-01-01 00:00:00'; +> exception INVALID_DATETIME_CONSTANT_2 + +SELECT CAST (TIMESTAMP '2000-01-01 23:59:59.999999999' AS TIMESTAMP); +>> 2000-01-02 00:00:00 + +SELECT CAST (TIMESTAMP '1000000000-12-31 23:59:59.999999999' AS TIMESTAMP); +>> 1000000000-12-31 23:59:59.999999 diff --git a/h2/src/test/org/h2/test/scripts/datatypes/tinyint.sql b/h2/src/test/org/h2/test/scripts/datatypes/tinyint.sql new file mode 100644 index 0000000..c389b6e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/tinyint.sql @@ -0,0 +1,18 @@ +-- 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 +-- + +-- Division + +SELECT CAST(1 AS TINYINT) / CAST(0 AS TINYINT); +> exception DIVISION_BY_ZERO_1 + +SELECT CAST(-128 AS TINYINT) / CAST(1 AS TINYINT); +>> -128 + +SELECT CAST(-128 AS TINYINT) / CAST(-1 AS TINYINT); +> exception NUMERIC_VALUE_OUT_OF_RANGE_1 + +EXPLAIN VALUES CAST(1 AS TINYINT); +>> VALUES (CAST(1 AS TINYINT)) diff --git a/h2/src/test/org/h2/test/scripts/datatypes/uuid.sql b/h2/src/test/org/h2/test/scripts/datatypes/uuid.sql new file mode 100644 index 0000000..39686ca --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/uuid.sql @@ -0,0 +1,42 @@ +-- 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 +-- + +CREATE TABLE TEST(U UUID) AS (SELECT * FROM VALUES + ('00000000-0000-0000-0000-000000000000'), ('00000000-0000-0000-9000-000000000000'), + ('11111111-1111-1111-1111-111111111111'), ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa')); +> ok + +SELECT U FROM TEST ORDER BY U; +> U +> ------------------------------------ +> 00000000-0000-0000-0000-000000000000 +> 00000000-0000-0000-9000-000000000000 +> 11111111-1111-1111-1111-111111111111 +> aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa +> rows (ordered): 4 + +DROP TABLE TEST; +> ok + +EXPLAIN VALUES UUID '11111111-1111-1111-1111-111111111111'; +>> VALUES (UUID '11111111-1111-1111-1111-111111111111') + +VALUES CAST('01234567-89AB-CDEF-0123-456789ABCDE' AS UUID); +> exception DATA_CONVERSION_ERROR_1 + +VALUES CAST(X'0123456789ABCDEF0123456789ABCD' AS UUID); +> exception DATA_CONVERSION_ERROR_1 + +VALUES CAST('01234567-89AB-CDEF-0123-456789ABCDEF' AS UUID); +>> 01234567-89ab-cdef-0123-456789abcdef + +VALUES CAST(X'0123456789ABCDEF0123456789ABCDEF' AS UUID); +>> 01234567-89ab-cdef-0123-456789abcdef + +VALUES CAST('01234567-89AB-CDEF-0123-456789ABCDEF-0' AS UUID); +> exception DATA_CONVERSION_ERROR_1 + +VALUES CAST(X'0123456789ABCDEF0123456789ABCDEF01' AS UUID); +> exception DATA_CONVERSION_ERROR_1 diff --git a/h2/src/test/org/h2/test/scripts/datatypes/varbinary.sql b/h2/src/test/org/h2/test/scripts/datatypes/varbinary.sql new file mode 100644 index 0000000..4801af0 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/varbinary.sql @@ -0,0 +1,143 @@ +-- 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 +-- + +CREATE TABLE TEST(B1 VARBINARY, B2 BINARY VARYING, B3 RAW, B4 BYTEA, B5 LONG RAW, B6 LONGVARBINARY); +> ok + +SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DATA_TYPE +> ----------- -------------- +> B1 BINARY VARYING +> B2 BINARY VARYING +> B3 BINARY VARYING +> B4 BINARY VARYING +> B5 BINARY VARYING +> B6 BINARY VARYING +> rows (ordered): 6 + +DROP TABLE TEST; +> ok + +CREATE MEMORY TABLE TEST AS (VALUES X'11' || X'25'); +> ok + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION TABLE TEST; +> SCRIPT +> -------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "C1" BINARY VARYING(2) ); +> -- 1 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> INSERT INTO "PUBLIC"."TEST" VALUES (X'1125'); +> rows (ordered): 4 + +EXPLAIN SELECT C1 || X'10' FROM TEST; +>> SELECT "C1" || X'10' FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +DROP TABLE TEST; +> ok + +SELECT X'11' || CAST(NULL AS VARBINARY); +>> null + +SELECT CAST(NULL AS VARBINARY) || X'11'; +>> null + +SELECT X'1'; +> exception HEX_STRING_ODD_1 + +SELECT X'1' '1'; +> exception HEX_STRING_ODD_1 + +SELECT X' 1 2 3 4 '; +>> X'1234' + +SELECT X'1 2 3'; +> exception HEX_STRING_ODD_1 + +SELECT X'~'; +> exception HEX_STRING_WRONG_1 + +SELECT X'G'; +> exception HEX_STRING_WRONG_1 + +SELECT X'TT'; +> exception HEX_STRING_WRONG_1 + +SELECT X' TT'; +> exception HEX_STRING_WRONG_1 + +SELECT X'AB' 'CD'; +>> X'abcd' + +SELECT X'AB' /* comment*/ 'CD' 'EF'; +>> X'abcdef' + +SELECT X'AB' 'CX'; +> exception HEX_STRING_WRONG_1 + +SELECT 0xabcd; +>> 43981 + +SET MODE MSSQLServer; +> ok + +SELECT 0x, 0x12ab; +> +> --- ------- +> X'' X'12ab' +> rows: 1 + +SELECT 0xZ; +> exception HEX_STRING_WRONG_1 + +SET MODE MySQL; +> ok + +SELECT 0x, 0x12ab; +> X'' X'12ab' +> --- ------- +> X'' X'12ab' +> rows: 1 + +SELECT 0xZ; +> exception HEX_STRING_WRONG_1 + +SET MODE Regular; +> ok + +EXPLAIN VALUES X''; +>> VALUES (X'') + +CREATE TABLE T(C VARBINARY(0)); +> exception INVALID_VALUE_2 + +CREATE TABLE T1(A BINARY VARYING(1000000000)); +> ok + +CREATE TABLE T2(A BINARY VARYING(1000000001)); +> exception INVALID_VALUE_PRECISION + +SET TRUNCATE_LARGE_LENGTH TRUE; +> ok + +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 1000000000 +> T2 1000000000 +> rows: 2 + +SET TRUNCATE_LARGE_LENGTH FALSE; +> ok + +DROP TABLE T1, T2; +> ok + +SELECT X'ab''cd'; +> exception SYNTAX_ERROR_1 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 new file mode 100644 index 0000000..5878465 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/varchar-ignorecase.sql @@ -0,0 +1,191 @@ +-- 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 +-- + +CREATE TABLE TEST(C1 VARCHAR_IGNORECASE); +> ok + +SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DATA_TYPE +> ----------- ------------------ +> C1 VARCHAR_IGNORECASE +> rows (ordered): 1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST (N VARCHAR_IGNORECASE) AS VALUES 'A', 'a', NULL; +> ok + +SELECT DISTINCT * FROM TEST; +> N +> ---- +> A +> null +> rows: 2 + +SELECT * FROM TEST; +> N +> ---- +> A +> a +> null +> rows: 3 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST (N VARCHAR_IGNORECASE) AS VALUES 'A', 'a', 'C', NULL; +> ok + +CREATE INDEX TEST_IDX ON TEST(N); +> ok + +SELECT N FROM TEST WHERE N IN ('a', 'A', 'B'); +> N +> - +> A +> a +> rows: 2 + +EXPLAIN SELECT N FROM TEST WHERE N IN (SELECT DISTINCT ON(B) A FROM VALUES ('a', 1), ('A', 2), ('B', 3) T(A, B)); +>> SELECT "N" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_IDX */ WHERE "N" IN( SELECT DISTINCT ON("B") "A" FROM (VALUES ('a', 1), ('A', 2), ('B', 3)) "T"("A", "B") /* table scan */) + +SELECT N FROM TEST WHERE N IN (SELECT DISTINCT ON(B) A FROM VALUES ('a', 1), ('A', 2), ('B', 3) T(A, B)); +> N +> - +> A +> a +> rows: 2 + +SELECT N FROM TEST WHERE N IN (SELECT DISTINCT ON(B) A FROM VALUES ('a'::VARCHAR_IGNORECASE, 1), + ('A'::VARCHAR_IGNORECASE, 2), ('B'::VARCHAR_IGNORECASE, 3) T(A, B)); +> N +> - +> A +> a +> rows: 2 + +EXPLAIN SELECT N FROM TEST WHERE N IN (SELECT DISTINCT ON(B) A FROM VALUES ('a'::VARCHAR_IGNORECASE(1), 1), + ('A'::VARCHAR_IGNORECASE(1), 2), ('B'::VARCHAR_IGNORECASE(1), 3) T(A, B)); +>> SELECT "N" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_IDX: N IN(SELECT DISTINCT ON(B) A FROM (VALUES (CAST('a' AS VARCHAR_IGNORECASE(1)), 1), (CAST('A' AS VARCHAR_IGNORECASE(1)), 2), (CAST('B' AS VARCHAR_IGNORECASE(1)), 3)) T(A, B) /* table scan */) */ WHERE "N" IN( SELECT DISTINCT ON("B") "A" FROM (VALUES (CAST('a' AS VARCHAR_IGNORECASE(1)), 1), (CAST('A' AS VARCHAR_IGNORECASE(1)), 2), (CAST('B' AS VARCHAR_IGNORECASE(1)), 3)) "T"("A", "B") /* table scan */) + +DROP INDEX TEST_IDX; +> ok + +CREATE UNIQUE INDEX TEST_IDX ON TEST(N); +> exception DUPLICATE_KEY_1 + +DROP TABLE TEST; +> ok + +CREATE MEMORY TABLE TEST(N VARCHAR_IGNORECASE) AS VALUES ('A'), ('a'), ('C'), (NULL); +> ok + +CREATE HASH INDEX TEST_IDX ON TEST(N); +> ok + +SELECT N FROM TEST WHERE N = 'A'; +> N +> - +> A +> a +> rows: 2 + +DROP INDEX TEST_IDX; +> ok + +CREATE UNIQUE HASH INDEX TEST_IDX ON TEST(N); +> exception DUPLICATE_KEY_1 + +DELETE FROM TEST WHERE N = 'A' LIMIT 1; +> update count: 1 + +CREATE UNIQUE HASH INDEX TEST_IDX ON TEST(N); +> ok + +SELECT 1 FROM TEST WHERE N = 'A'; +>> 1 + +INSERT INTO TEST VALUES (NULL); +> update count: 1 + +SELECT N FROM TEST WHERE N IS NULL; +> N +> ---- +> null +> null +> rows: 2 + +DELETE FROM TEST WHERE N IS NULL LIMIT 1; +> update count: 1 + +SELECT N FROM TEST WHERE N IS NULL; +>> null + +DROP TABLE TEST; +> ok + +EXPLAIN VALUES CAST('a' AS VARCHAR_IGNORECASE(1)); +>> VALUES (CAST('a' AS VARCHAR_IGNORECASE(1))) + +CREATE TABLE T(C VARCHAR_IGNORECASE(0)); +> exception INVALID_VALUE_2 + +CREATE TABLE T(C1 VARCHAR_IGNORECASE(1 CHARACTERS), C2 VARCHAR_IGNORECASE(1 OCTETS)); +> ok + +DROP TABLE T; +> ok + +SELECT 'I' ILIKE CHAR(0x130); +>> TRUE + +SET COLLATION TURKISH STRENGTH IDENTICAL; +> ok + +CREATE TABLE TEST(V VARCHAR_IGNORECASE UNIQUE); +> ok + +INSERT INTO TEST VALUES 'I', 'i'; +> update count: 2 + +INSERT INTO TEST VALUES CHAR(0x0130); +> exception DUPLICATE_KEY_1 + +INSERT INTO TEST VALUES CHAR(0x0131); +> exception DUPLICATE_KEY_1 + +DROP TABLE TEST; +> ok + +SET COLLATION OFF; +> ok + + +CREATE TABLE T1(A VARCHAR_IGNORECASE(1000000000)); +> ok + +CREATE TABLE T2(A VARCHAR_IGNORECASE(1000000001)); +> exception INVALID_VALUE_PRECISION + +SET TRUNCATE_LARGE_LENGTH TRUE; +> ok + +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 1000000000 +> T2 1000000000 +> rows: 2 + +SET TRUNCATE_LARGE_LENGTH FALSE; +> ok + +DROP TABLE T1, T2; +> ok diff --git a/h2/src/test/org/h2/test/scripts/datatypes/varchar.sql b/h2/src/test/org/h2/test/scripts/datatypes/varchar.sql new file mode 100644 index 0000000..dd85699 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/datatypes/varchar.sql @@ -0,0 +1,126 @@ +-- 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 +-- + +SELECT 'A' 'b' + 'c'; +>> Abc + +SELECT N'A' 'b' + 'c'; +>> 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); +> ok + +SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DATA_TYPE +> ----------- ----------------- +> C1 CHARACTER VARYING +> C2 CHARACTER VARYING +> C3 CHARACTER VARYING +> C4 CHARACTER VARYING +> C5 CHARACTER VARYING +> C6 CHARACTER VARYING +> C7 CHARACTER VARYING +> C8 CHARACTER VARYING +> C9 CHARACTER VARYING +> C10 CHARACTER VARYING +> C11 CHARACTER VARYING +> C12 CHARACTER VARYING +> rows (ordered): 12 + +DROP TABLE TEST; +> ok + +CREATE TABLE T(C VARCHAR(0)); +> exception INVALID_VALUE_2 + +CREATE TABLE T(C VARCHAR(1K)); +> exception SYNTAX_ERROR_2 + +CREATE TABLE T(C1 VARCHAR(1 CHARACTERS), C2 VARCHAR(1 OCTETS)); +> ok + +DROP TABLE T; +> ok + + +CREATE TABLE T1(A CHARACTER VARYING(1000000000)); +> ok + +CREATE TABLE T2(A CHARACTER VARYING(1000000001)); +> exception INVALID_VALUE_PRECISION + +SET TRUNCATE_LARGE_LENGTH TRUE; +> ok + +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 1000000000 +> T2 1000000000 +> rows: 2 + +SET TRUNCATE_LARGE_LENGTH FALSE; +> ok + +DROP TABLE T1, T2; +> ok + +SELECT U&'a\0030a\+000025a'; +>> a0a%a + +SELECT U&'az0030az+000025a' UESCAPE 'z'; +>> a0a%a + +EXPLAIN SELECT U&'\fffd\+100000'; +>> SELECT U&'\fffd\+100000' + +SELECT U&'\'; +> exception STRING_FORMAT_ERROR_1 + +SELECT U&'\0'; +> exception STRING_FORMAT_ERROR_1 + +SELECT U&'\00'; +> exception STRING_FORMAT_ERROR_1 + +SELECT U&'\003'; +> exception STRING_FORMAT_ERROR_1 + +SELECT U&'\0030'; +>> 0 + +SELECT U&'\zzzz'; +> exception STRING_FORMAT_ERROR_1 + +SELECT U&'\+0'; +> exception STRING_FORMAT_ERROR_1 + +SELECT U&'\+00'; +> exception STRING_FORMAT_ERROR_1 + +SELECT U&'\+000'; +> exception STRING_FORMAT_ERROR_1 + +SELECT U&'\+0000'; +> exception STRING_FORMAT_ERROR_1 + +SELECT U&'\+00003'; +> exception STRING_FORMAT_ERROR_1 + +SELECT U&'\+000030'; +>> 0 + +SELECT U&'\+zzzzzz'; +> exception STRING_FORMAT_ERROR_1 + +EXPLAIN SELECT U&'''\\', U&'''\\\fffd'; +>> SELECT '''\', U&'''\\\fffd' diff --git a/h2/src/test/org/h2/test/scripts/ddl/alterDomain.sql b/h2/src/test/org/h2/test/scripts/ddl/alterDomain.sql new file mode 100644 index 0000000..94bc2ae --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/alterDomain.sql @@ -0,0 +1,346 @@ +-- 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 +-- + +CREATE DOMAIN D1 INT DEFAULT 1; +> ok + +CREATE DOMAIN D2 D1 DEFAULT 2; +> ok + +CREATE DOMAIN D3 D1; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, S1 D1, S2 D2, S3 D3, C1 D1 DEFAULT 4, C2 D2 DEFAULT 5, C3 D3 DEFAULT 6); +> ok + +INSERT INTO TEST(ID) VALUES 1; +> update count: 1 + +TABLE TEST; +> ID S1 S2 S3 C1 C2 C3 +> -- -- -- -- -- -- -- +> 1 1 2 1 4 5 6 +> rows: 1 + +ALTER DOMAIN D1 SET DEFAULT 3; +> ok + +INSERT INTO TEST(ID) VALUES 2; +> update count: 1 + +SELECT * FROM TEST WHERE ID = 2; +> ID S1 S2 S3 C1 C2 C3 +> -- -- -- -- -- -- -- +> 2 3 2 3 4 5 6 +> rows: 1 + +ALTER DOMAIN D1 DROP DEFAULT; +> ok + +SELECT DOMAIN_NAME, DOMAIN_DEFAULT FROM INFORMATION_SCHEMA.DOMAINS WHERE DOMAIN_SCHEMA = 'PUBLIC'; +> DOMAIN_NAME DOMAIN_DEFAULT +> ----------- -------------- +> D1 null +> D2 2 +> D3 3 +> rows: 3 + +SELECT COLUMN_NAME, COLUMN_DEFAULT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'PUBLIC'; +> COLUMN_NAME COLUMN_DEFAULT +> ----------- -------------- +> C1 4 +> C2 5 +> C3 6 +> ID null +> S1 3 +> S2 null +> S3 null +> rows: 7 + +ALTER DOMAIN D1 SET DEFAULT 3; +> ok + +ALTER DOMAIN D3 DROP DEFAULT; +> ok + +ALTER TABLE TEST ALTER COLUMN S1 DROP DEFAULT; +> ok + +SELECT DOMAIN_NAME, DOMAIN_DEFAULT FROM INFORMATION_SCHEMA.DOMAINS WHERE DOMAIN_SCHEMA = 'PUBLIC'; +> DOMAIN_NAME DOMAIN_DEFAULT +> ----------- -------------- +> D1 3 +> D2 2 +> D3 null +> rows: 3 + +SELECT COLUMN_NAME, COLUMN_DEFAULT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'PUBLIC'; +> COLUMN_NAME COLUMN_DEFAULT +> ----------- -------------- +> C1 4 +> C2 5 +> C3 6 +> ID null +> S1 null +> S2 null +> S3 null +> rows: 7 + +DROP DOMAIN D1 CASCADE; +> ok + +SELECT DOMAIN_NAME, DOMAIN_DEFAULT FROM INFORMATION_SCHEMA.DOMAINS WHERE DOMAIN_SCHEMA = 'PUBLIC'; +> DOMAIN_NAME DOMAIN_DEFAULT +> ----------- -------------- +> D2 2 +> D3 3 +> rows: 2 + +SELECT COLUMN_NAME, COLUMN_DEFAULT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'PUBLIC'; +> COLUMN_NAME COLUMN_DEFAULT +> ----------- -------------- +> C1 4 +> C2 5 +> C3 6 +> ID null +> S1 3 +> S2 null +> S3 null +> rows: 7 + +DROP TABLE TEST; +> ok + +DROP DOMAIN D2; +> ok + +DROP DOMAIN D3; +> ok + +CREATE DOMAIN D1 INT ON UPDATE 1; +> ok + +CREATE DOMAIN D2 D1 ON UPDATE 2; +> ok + +CREATE DOMAIN D3 D1; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, S1 D1, S2 D2, S3 D3, C1 D1 ON UPDATE 4, C2 D2 ON UPDATE 5, C3 D3 ON UPDATE 6); +> ok + +ALTER DOMAIN D1 SET ON UPDATE 3; +> ok + +ALTER DOMAIN D1 DROP ON UPDATE; +> ok + +SELECT DOMAIN_NAME, DOMAIN_ON_UPDATE FROM INFORMATION_SCHEMA.DOMAINS WHERE DOMAIN_SCHEMA = 'PUBLIC'; +> DOMAIN_NAME DOMAIN_ON_UPDATE +> ----------- ---------------- +> D1 null +> D2 2 +> D3 3 +> rows: 3 + +SELECT COLUMN_NAME, COLUMN_ON_UPDATE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'PUBLIC'; +> COLUMN_NAME COLUMN_ON_UPDATE +> ----------- ---------------- +> C1 4 +> C2 5 +> C3 6 +> ID null +> S1 3 +> S2 null +> S3 null +> rows: 7 + +ALTER DOMAIN D1 SET ON UPDATE 3; +> ok + +ALTER DOMAIN D3 DROP ON UPDATE; +> ok + +ALTER TABLE TEST ALTER COLUMN S1 DROP ON UPDATE; +> ok + +SELECT DOMAIN_NAME, DOMAIN_ON_UPDATE FROM INFORMATION_SCHEMA.DOMAINS WHERE DOMAIN_SCHEMA = 'PUBLIC'; +> DOMAIN_NAME DOMAIN_ON_UPDATE +> ----------- ---------------- +> D1 3 +> D2 2 +> D3 null +> rows: 3 + +SELECT COLUMN_NAME, COLUMN_ON_UPDATE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'PUBLIC'; +> COLUMN_NAME COLUMN_ON_UPDATE +> ----------- ---------------- +> C1 4 +> C2 5 +> C3 6 +> ID null +> S1 null +> S2 null +> S3 null +> rows: 7 + +DROP DOMAIN D1 CASCADE; +> ok + +SELECT DOMAIN_NAME, DOMAIN_ON_UPDATE FROM INFORMATION_SCHEMA.DOMAINS WHERE DOMAIN_SCHEMA = 'PUBLIC'; +> DOMAIN_NAME DOMAIN_ON_UPDATE +> ----------- ---------------- +> D2 2 +> D3 3 +> rows: 2 + +SELECT COLUMN_NAME, COLUMN_ON_UPDATE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'PUBLIC'; +> COLUMN_NAME COLUMN_ON_UPDATE +> ----------- ---------------- +> C1 4 +> C2 5 +> C3 6 +> ID null +> S1 3 +> S2 null +> S3 null +> rows: 7 + +DROP TABLE TEST; +> ok + +DROP DOMAIN D2; +> ok + +DROP DOMAIN D3; +> ok + +CREATE DOMAIN D1 AS INT; +> ok + +CREATE DOMAIN D2 AS D1; +> ok + +CREATE TABLE T(C1 D1, C2 D2, L BIGINT); +> ok + +ALTER DOMAIN D1 RENAME TO D3; +> ok + +SELECT DOMAIN_NAME, DATA_TYPE, PARENT_DOMAIN_NAME FROM INFORMATION_SCHEMA.DOMAINS; +> DOMAIN_NAME DATA_TYPE PARENT_DOMAIN_NAME +> ----------- --------- ------------------ +> D2 INTEGER D3 +> D3 INTEGER null +> rows: 2 + +SELECT COLUMN_NAME, DOMAIN_NAME FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'T' AND COLUMN_NAME LIKE 'C_'; +> COLUMN_NAME DOMAIN_NAME +> ----------- ----------- +> C1 D3 +> C2 D2 +> rows: 2 + +@reconnect + +SELECT DOMAIN_NAME, DATA_TYPE, PARENT_DOMAIN_NAME FROM INFORMATION_SCHEMA.DOMAINS; +> DOMAIN_NAME DATA_TYPE PARENT_DOMAIN_NAME +> ----------- --------- ------------------ +> D2 INTEGER D3 +> D3 INTEGER null +> rows: 2 + +SELECT COLUMN_NAME, DOMAIN_NAME FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'T' AND COLUMN_NAME LIKE 'C_'; +> COLUMN_NAME DOMAIN_NAME +> ----------- ----------- +> C1 D3 +> C2 D2 +> rows: 2 + +DROP TABLE T; +> ok + +DROP DOMAIN D2; +> ok + +DROP DOMAIN D3; +> ok + +CREATE DOMAIN D1 AS INT; +> ok + +CREATE DOMAIN D2 AS D1; +> ok + +CREATE TABLE TEST(A INT, C D2) AS VALUES (1, 1); +> ok + +ALTER TABLE TEST ADD CHECK (C > 1); +> exception CHECK_CONSTRAINT_VIOLATED_1 + +ALTER DOMAIN D2 ADD CHECK (VALUE > 1); +> exception CHECK_CONSTRAINT_VIOLATED_1 + +ALTER DOMAIN D1 ADD CHECK (VALUE > 1); +> exception CHECK_CONSTRAINT_VIOLATED_1 + +CREATE UNIQUE INDEX TEST_A_IDX ON TEST(A); +> ok + +ALTER TABLE TEST ADD CHECK (C > 1); +> exception CHECK_CONSTRAINT_VIOLATED_1 + +ALTER DOMAIN D2 ADD CHECK (VALUE > 1); +> exception CHECK_CONSTRAINT_VIOLATED_1 + +ALTER DOMAIN D1 ADD CHECK (VALUE > 1); +> exception CHECK_CONSTRAINT_VIOLATED_1 + +CREATE INDEX TEST_C_IDX ON TEST(C); +> ok + +ALTER TABLE TEST ADD CHECK (C > 1); +> exception CHECK_CONSTRAINT_VIOLATED_1 + +ALTER DOMAIN D2 ADD CHECK (VALUE > 1); +> exception CHECK_CONSTRAINT_VIOLATED_1 + +ALTER DOMAIN D1 ADD CHECK (VALUE > 1); +> exception CHECK_CONSTRAINT_VIOLATED_1 + +ALTER DOMAIN D1 ADD CHECK (VALUE > 1) NOCHECK; +> ok + +DROP TABLE TEST; +> ok + +ALTER DOMAIN D1 ADD CONSTRAINT T CHECK (VALUE < 100); +> ok + +ALTER DOMAIN D3 RENAME CONSTRAINT T TO T1; +> exception DOMAIN_NOT_FOUND_1 + +ALTER DOMAIN IF EXISTS D3 RENAME CONSTRAINT T TO T1; +> ok + +ALTER DOMAIN D2 RENAME CONSTRAINT T TO T2; +> exception CONSTRAINT_NOT_FOUND_1 + +ALTER DOMAIN D1 RENAME CONSTRAINT T TO T3; +> ok + +SELECT CONSTRAINT_NAME, DOMAIN_NAME FROM INFORMATION_SCHEMA.DOMAIN_CONSTRAINTS WHERE CONSTRAINT_NAME LIKE 'T%'; +> CONSTRAINT_NAME DOMAIN_NAME +> --------------- ----------- +> T3 D1 +> rows: 1 + +DROP DOMAIN D2; +> ok + +DROP DOMAIN D1; +> ok diff --git a/h2/src/test/org/h2/test/scripts/ddl/alterTableAdd.sql b/h2/src/test/org/h2/test/scripts/ddl/alterTableAdd.sql new file mode 100644 index 0000000..9f00abb --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/alterTableAdd.sql @@ -0,0 +1,395 @@ +-- 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 +-- + +CREATE TABLE TEST(B INT); +> ok + +ALTER TABLE TEST ADD C INT; +> ok + +ALTER TABLE TEST ADD COLUMN D INT; +> ok + +ALTER TABLE TEST ADD IF NOT EXISTS B INT; +> ok + +ALTER TABLE TEST ADD IF NOT EXISTS E INT; +> ok + +ALTER TABLE IF EXISTS TEST2 ADD COLUMN B INT; +> ok + +ALTER TABLE TEST ADD B1 INT AFTER B; +> ok + +ALTER TABLE TEST ADD B2 INT BEFORE C; +> ok + +ALTER TABLE TEST ADD (C1 INT, C2 INT) AFTER C; +> ok + +ALTER TABLE TEST ADD (C3 INT, C4 INT) BEFORE D; +> ok + +ALTER TABLE TEST ADD A2 INT FIRST; +> ok + +ALTER TABLE TEST ADD (A INT, A1 INT) FIRST; +> ok + +SELECT * FROM TEST; +> A A1 A2 B B1 B2 C C1 C2 C3 C4 D E +> - -- -- - -- -- - -- -- -- -- - - +> rows: 0 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INT NOT NULL, B INT); +> ok + +-- column B may be null +ALTER TABLE TEST ADD (CONSTRAINT PK_B PRIMARY KEY (B)); +> exception COLUMN_MUST_NOT_BE_NULLABLE_1 + +ALTER TABLE TEST ADD (CONSTRAINT PK_A PRIMARY KEY (A)); +> ok + +ALTER TABLE TEST ADD (C INT AUTO_INCREMENT UNIQUE, CONSTRAINT U_B UNIQUE (B), D INT UNIQUE); +> ok + +INSERT INTO TEST(A, B, D) VALUES (11, 12, 14); +> update count: 1 + +SELECT * FROM TEST; +> A B C D +> -- -- - -- +> 11 12 1 14 +> rows: 1 + +INSERT INTO TEST VALUES (11, 20, 30, 40); +> exception DUPLICATE_KEY_1 + +INSERT INTO TEST VALUES (10, 12, 30, 40); +> exception DUPLICATE_KEY_1 + +INSERT INTO TEST VALUES (10, 20, 1, 40); +> exception DUPLICATE_KEY_1 + +INSERT INTO TEST VALUES (10, 20, 30, 14); +> exception DUPLICATE_KEY_1 + +INSERT INTO TEST VALUES (10, 20, 30, 40); +> update count: 1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(); +> ok + +ALTER TABLE TEST ADD A INT CONSTRAINT PK_1 PRIMARY KEY; +> ok + +SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS; +> CONSTRAINT_NAME CONSTRAINT_TYPE +> --------------- --------------- +> PK_1 PRIMARY KEY +> rows: 1 + +DROP TABLE TEST; +> ok + +CREATE TABLE PARENT(ID INT); +> ok + +CREATE INDEX PARENT_ID_IDX ON PARENT(ID); +> ok + +CREATE TABLE CHILD(ID INT PRIMARY KEY, P INT); +> ok + +ALTER TABLE CHILD ADD CONSTRAINT CHILD_P_FK FOREIGN KEY (P) REFERENCES PARENT(ID); +> exception CONSTRAINT_NOT_FOUND_1 + +SET MODE MySQL; +> ok + +ALTER TABLE CHILD ADD CONSTRAINT CHILD_P_FK FOREIGN KEY (P) REFERENCES PARENT(ID); +> ok + +SET MODE Regular; +> ok + +INSERT INTO PARENT VALUES 1, 1; +> exception DUPLICATE_KEY_1 + +DROP TABLE CHILD, PARENT; +> ok + +CREATE TABLE PARENT(ID INT CONSTRAINT P1 PRIMARY KEY); +> ok + +CREATE TABLE CHILD(ID INT CONSTRAINT P2 PRIMARY KEY, CHILD INT CONSTRAINT C REFERENCES PARENT); +> ok + +ALTER TABLE PARENT DROP CONSTRAINT P1 RESTRICT; +> exception CONSTRAINT_IS_USED_BY_CONSTRAINT_2 + +ALTER TABLE PARENT DROP CONSTRAINT P1 RESTRICT; +> exception CONSTRAINT_IS_USED_BY_CONSTRAINT_2 + +ALTER TABLE PARENT DROP CONSTRAINT P1 CASCADE; +> ok + +DROP TABLE PARENT, CHILD; +> ok + +CREATE TABLE A(A TIMESTAMP PRIMARY KEY, B INT ARRAY UNIQUE, C TIME ARRAY UNIQUE); +> ok + +CREATE TABLE B(A TIMESTAMP WITH TIME ZONE, B DATE, C INT ARRAY, D TIME ARRAY, E TIME WITH TIME ZONE ARRAY); +> ok + +ALTER TABLE B ADD FOREIGN KEY(A) REFERENCES A(A); +> exception UNCOMPARABLE_REFERENCED_COLUMN_2 + +ALTER TABLE B ADD FOREIGN KEY(B) REFERENCES A(A); +> ok + +ALTER TABLE B ADD FOREIGN KEY(C) REFERENCES A(B); +> ok + +ALTER TABLE B ADD FOREIGN KEY(C) REFERENCES A(C); +> exception TYPES_ARE_NOT_COMPARABLE_2 + +ALTER TABLE B ADD FOREIGN KEY(D) REFERENCES A(B); +> exception UNCOMPARABLE_REFERENCED_COLUMN_2 + +ALTER TABLE B ADD FOREIGN KEY(D) REFERENCES A(C); +> ok + +ALTER TABLE B ADD FOREIGN KEY(E) REFERENCES A(B); +> exception UNCOMPARABLE_REFERENCED_COLUMN_2 + +ALTER TABLE B ADD FOREIGN KEY(E) REFERENCES A(C); +> exception UNCOMPARABLE_REFERENCED_COLUMN_2 + +DROP TABLE B, A; +> ok + +CREATE TABLE PARENT(ID INT PRIMARY KEY, K INT UNIQUE); +> ok + +CREATE TABLE CHILD(ID INT PRIMARY KEY, P INT GENERATED ALWAYS AS (ID)); +> ok + +ALTER TABLE CHILD ADD FOREIGN KEY(P) REFERENCES PARENT(K) ON DELETE CASCADE; +> ok + +ALTER TABLE CHILD ADD FOREIGN KEY(P) REFERENCES PARENT(K) ON DELETE RESTRICT; +> ok + +ALTER TABLE CHILD ADD FOREIGN KEY(P) REFERENCES PARENT(K) ON DELETE NO ACTION; +> ok + +ALTER TABLE CHILD ADD FOREIGN KEY(P) REFERENCES PARENT(K) ON DELETE SET DEFAULT; +> exception GENERATED_COLUMN_CANNOT_BE_UPDATABLE_BY_CONSTRAINT_2 + +ALTER TABLE CHILD ADD FOREIGN KEY(P) REFERENCES PARENT(K) ON DELETE SET NULL; +> exception GENERATED_COLUMN_CANNOT_BE_UPDATABLE_BY_CONSTRAINT_2 + +ALTER TABLE CHILD ADD FOREIGN KEY(P) REFERENCES PARENT(K) ON UPDATE CASCADE; +> exception GENERATED_COLUMN_CANNOT_BE_UPDATABLE_BY_CONSTRAINT_2 + +ALTER TABLE CHILD ADD FOREIGN KEY(P) REFERENCES PARENT(K) ON UPDATE RESTRICT; +> ok + +ALTER TABLE CHILD ADD FOREIGN KEY(P) REFERENCES PARENT(K) ON UPDATE NO ACTION; +> ok + +ALTER TABLE CHILD ADD FOREIGN KEY(P) REFERENCES PARENT(K) ON UPDATE SET DEFAULT; +> exception GENERATED_COLUMN_CANNOT_BE_UPDATABLE_BY_CONSTRAINT_2 + +ALTER TABLE CHILD ADD FOREIGN KEY(P) REFERENCES PARENT(K) ON UPDATE SET NULL; +> exception GENERATED_COLUMN_CANNOT_BE_UPDATABLE_BY_CONSTRAINT_2 + +DROP TABLE CHILD, PARENT; +> ok + +CREATE TABLE T1(B INT, G INT GENERATED ALWAYS AS (B + 1) UNIQUE); +> ok + +CREATE TABLE T2(A INT, G INT REFERENCES T1(G) ON UPDATE CASCADE); +> ok + +INSERT INTO T1(B) VALUES 1; +> update count: 1 + +INSERT INTO T2 VALUES (1, 2); +> update count: 1 + +TABLE T2; +> A G +> - - +> 1 2 +> rows: 1 + +UPDATE T1 SET B = 2; +> update count: 1 + +TABLE T2; +> A G +> - - +> 1 3 +> rows: 1 + +DROP TABLE T2, T1; +> ok + +CREATE SCHEMA S1; +> ok + +CREATE TABLE S1.T1(ID INT PRIMARY KEY); +> ok + +CREATE SCHEMA S2; +> ok + +CREATE TABLE S2.T2(ID INT, FK INT REFERENCES S1.T1(ID)); +> ok + +SELECT CONSTRAINT_SCHEMA, CONSTRAINT_TYPE, TABLE_SCHEMA, TABLE_NAME, INDEX_SCHEMA + FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA LIKE 'S%'; +> CONSTRAINT_SCHEMA CONSTRAINT_TYPE TABLE_SCHEMA TABLE_NAME INDEX_SCHEMA +> ----------------- --------------- ------------ ---------- ------------ +> S1 PRIMARY KEY S1 T1 S1 +> S2 FOREIGN KEY S2 T2 S2 +> rows: 2 + +SELECT INDEX_SCHEMA, TABLE_SCHEMA, TABLE_NAME, INDEX_TYPE_NAME, IS_GENERATED FROM INFORMATION_SCHEMA.INDEXES + WHERE TABLE_SCHEMA LIKE 'S%'; +> INDEX_SCHEMA TABLE_SCHEMA TABLE_NAME INDEX_TYPE_NAME IS_GENERATED +> ------------ ------------ ---------- --------------- ------------ +> S1 S1 T1 PRIMARY KEY TRUE +> S2 S2 T2 INDEX TRUE +> rows: 2 + +SELECT INDEX_SCHEMA, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME FROM INFORMATION_SCHEMA.INDEX_COLUMNS + WHERE TABLE_SCHEMA LIKE 'S%'; +> INDEX_SCHEMA TABLE_SCHEMA TABLE_NAME COLUMN_NAME +> ------------ ------------ ---------- ----------- +> S1 S1 T1 ID +> S2 S2 T2 FK +> rows: 2 + +@reconnect + +DROP SCHEMA S2 CASCADE; +> ok + +DROP SCHEMA S1 CASCADE; +> ok + +EXECUTE IMMEDIATE 'CREATE TABLE TEST(' || (SELECT LISTAGG('C' || X || ' INT') FROM SYSTEM_RANGE(1, 16384)) || ')'; +> ok + +ALTER TABLE TEST ADD COLUMN(X INTEGER); +> exception TOO_MANY_COLUMNS_1 + +DROP TABLE TEST; +> ok + +CREATE MEMORY TABLE TEST(ID BIGINT NOT NULL); +> ok + +ALTER TABLE TEST ADD PRIMARY KEY(ID); +> ok + +SELECT INDEX_TYPE_NAME, IS_GENERATED FROM INFORMATION_SCHEMA.INDEXES WHERE TABLE_NAME = 'TEST'; +> INDEX_TYPE_NAME IS_GENERATED +> --------------- ------------ +> PRIMARY KEY TRUE +> rows: 1 + +CALL DB_OBJECT_SQL('INDEX', 'PUBLIC', 'PRIMARY_KEY_2'); +>> CREATE PRIMARY KEY "PUBLIC"."PRIMARY_KEY_2" ON "PUBLIC"."TEST"("ID") + +SCRIPT NODATA NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> ------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "ID" BIGINT NOT NULL ); +> ALTER TABLE "PUBLIC"."TEST" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_2" PRIMARY KEY("ID"); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> rows (ordered): 4 + +@reconnect + +SELECT INDEX_TYPE_NAME, IS_GENERATED FROM INFORMATION_SCHEMA.INDEXES WHERE TABLE_NAME = 'TEST'; +> INDEX_TYPE_NAME IS_GENERATED +> --------------- ------------ +> PRIMARY KEY TRUE +> rows: 1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INT, B INT, C INT INVISIBLE, CONSTRAINT TEST_UNIQUE_2 UNIQUE(VALUE)); +> ok + +ALTER TABLE TEST ADD COLUMN D INT; +> ok + +ALTER TABLE TEST ADD CONSTRAINT TEST_UNIQUE_3 UNIQUE(VALUE); +> ok + +SELECT CONSTRAINT_NAME, COLUMN_NAME, ORDINAL_POSITION FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE + WHERE TABLE_NAME = 'TEST'; +> CONSTRAINT_NAME COLUMN_NAME ORDINAL_POSITION +> --------------- ----------- ---------------- +> TEST_UNIQUE_2 A 1 +> TEST_UNIQUE_2 B 2 +> TEST_UNIQUE_3 A 1 +> TEST_UNIQUE_3 B 2 +> TEST_UNIQUE_3 D 3 +> rows: 5 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(); +> ok + +ALTER TABLE TEST ADD UNIQUE (VALUE); +> exception SYNTAX_ERROR_1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INT, B INT) AS VALUES (3, 4); +> ok + +ALTER TABLE TEST ADD G INT GENERATED ALWAYS AS (A + B); +> ok + +ALTER TABLE TEST ADD ID BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY FIRST; +> ok + +ALTER TABLE TEST ADD C INT AFTER B; +> ok + +INSERT INTO TEST(A, B) VALUES (5, 6); +> update count: 1 + +TABLE TEST; +> ID A B C G +> -- - - ---- -- +> 1 3 4 null 7 +> 2 5 6 null 11 +> rows: 2 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/ddl/alterTableAlterColumn.sql b/h2/src/test/org/h2/test/scripts/ddl/alterTableAlterColumn.sql new file mode 100644 index 0000000..cda63ed --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/alterTableAlterColumn.sql @@ -0,0 +1,816 @@ +-- 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 +-- + +CREATE TABLE TEST(T INT); +> ok + +SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME = 'T'; +>> INTEGER + +-- SET DEFAULT +ALTER TABLE TEST ALTER COLUMN T SET DEFAULT 1; +> ok + +SELECT COLUMN_DEFAULT FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME = 'T'; +>> 1 + +-- DROP DEFAULT +ALTER TABLE TEST ALTER COLUMN T DROP DEFAULT; +> ok + +SELECT COLUMN_DEFAULT FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME = 'T'; +>> null + +-- SET NOT NULL +ALTER TABLE TEST ALTER COLUMN T SET NOT NULL; +> ok + +SELECT IS_NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME = 'T'; +>> NO + +-- DROP NOT NULL +ALTER TABLE TEST ALTER COLUMN T DROP NOT NULL; +> ok + +SELECT IS_NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME = 'T'; +>> YES + +ALTER TABLE TEST ALTER COLUMN T SET NOT NULL; +> ok + +-- SET NULL +ALTER TABLE TEST ALTER COLUMN T SET NULL; +> ok + +SELECT IS_NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME = 'T'; +>> YES + +-- SET DATA TYPE +ALTER TABLE TEST ALTER COLUMN T SET DATA TYPE BIGINT; +> ok + +SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME = 'T'; +>> BIGINT + +ALTER TABLE TEST ALTER COLUMN T INT INVISIBLE DEFAULT 1 ON UPDATE 2 NOT NULL COMMENT 'C'; +> ok + +SELECT DATA_TYPE, IS_VISIBLE, COLUMN_DEFAULT, COLUMN_ON_UPDATE, REMARKS, IS_NULLABLE + FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME = 'T'; +> DATA_TYPE IS_VISIBLE COLUMN_DEFAULT COLUMN_ON_UPDATE REMARKS IS_NULLABLE +> --------- ---------- -------------- ---------------- ------- ----------- +> INTEGER FALSE 1 2 C NO +> rows: 1 + +ALTER TABLE TEST ALTER COLUMN T SET DATA TYPE BIGINT; +> ok + +SELECT DATA_TYPE, IS_VISIBLE, COLUMN_DEFAULT, COLUMN_ON_UPDATE, REMARKS, IS_NULLABLE + FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME = 'T'; +> DATA_TYPE IS_VISIBLE COLUMN_DEFAULT COLUMN_ON_UPDATE REMARKS IS_NULLABLE +> --------- ---------- -------------- ---------------- ------- ----------- +> BIGINT FALSE 1 2 C NO +> rows: 1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID INT AUTO_INCREMENT PRIMARY KEY, V INT NOT NULL); +> ok + +ALTER TABLE TEST ALTER COLUMN ID RESTART WITH 100; +> ok + +INSERT INTO TEST(V) VALUES (1); +> update count: 1 + +ALTER TABLE TEST AUTO_INCREMENT = 200; +> exception SYNTAX_ERROR_2 + +SET MODE MySQL; +> ok + +ALTER TABLE TEST AUTO_INCREMENT = 200; +> ok + +INSERT INTO TEST(V) VALUES (2); +> update count: 1 + +ALTER TABLE TEST AUTO_INCREMENT 300; +> ok + +INSERT INTO TEST(V) VALUES (3); +> update count: 1 + +SELECT * FROM TEST ORDER BY ID; +> ID V +> --- - +> 100 1 +> 200 2 +> 300 3 +> rows (ordered): 3 + +ALTER TABLE TEST DROP PRIMARY KEY; +> ok + +ALTER TABLE TEST AUTO_INCREMENT = 400; +> exception COLUMN_NOT_FOUND_1 + +ALTER TABLE TEST ADD PRIMARY KEY(V); +> ok + +ALTER TABLE TEST AUTO_INCREMENT = 400; +> exception COLUMN_NOT_FOUND_1 + +SET MODE Regular; +> ok + +DROP TABLE TEST; +> ok + +-- Compatibility syntax + +SET MODE MySQL; +> ok + +create table test(id int primary key, name varchar); +> ok + +insert into test(id) values(1); +> update count: 1 + +alter table test change column id id2 int; +> ok + +select id2 from test; +> ID2 +> --- +> 1 +> rows: 1 + +drop table test; +> ok + +SET MODE Oracle; +> ok + +CREATE MEMORY TABLE TEST(V INT NOT NULL); +> ok + +ALTER TABLE TEST MODIFY COLUMN V BIGINT; +> ok + +SCRIPT NODATA NOPASSWORDS NOSETTINGS NOVERSION TABLE TEST; +> SCRIPT +> ----------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "V" BIGINT NOT NULL ); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> rows (ordered): 3 + +SET MODE MySQL; +> ok + +ALTER TABLE TEST MODIFY COLUMN V INT; +> ok + +SCRIPT NODATA NOPASSWORDS NOSETTINGS NOVERSION TABLE TEST; +> SCRIPT +> --------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "V" INTEGER ); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> rows (ordered): 3 + +ALTER TABLE TEST MODIFY COLUMN V BIGINT NOT NULL; +> ok + +SCRIPT NODATA NOPASSWORDS NOSETTINGS NOVERSION TABLE TEST; +> SCRIPT +> ----------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "V" BIGINT NOT NULL ); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> rows (ordered): 3 + +SET MODE Regular; +> ok + +DROP TABLE TEST; +> ok + +create table test(id int, name varchar); +> ok + +alter table test alter column id int as id+1; +> exception COLUMN_NOT_FOUND_1 + +drop table test; +> ok + +create table t(x varchar) as select 'x'; +> ok + +alter table t alter column x int; +> exception DATA_CONVERSION_ERROR_1 + +drop table t; +> ok + +create table t(id identity default on null, x varchar) as select null, 'x'; +> ok + +alter table t alter column x int; +> exception DATA_CONVERSION_ERROR_1 + +drop table t; +> ok + +-- ensure that increasing a VARCHAR columns length takes effect because we optimize this case +create table t(x varchar(2)) as select 'x'; +> ok + +alter table t alter column x varchar(20); +> ok + +insert into t values 'Hello'; +> update count: 1 + +drop table t; +> ok + +SET MODE MySQL; +> ok + +create table t(x int); +> ok + +alter table t modify column x varchar(20); +> ok + +insert into t values('Hello'); +> update count: 1 + +drop table t; +> ok + +-- This worked in v1.4.196 +create table T (C varchar not null); +> ok + +alter table T modify C int null; +> ok + +insert into T values(null); +> update count: 1 + +drop table T; +> ok + +-- This failed in v1.4.196 +create table T (C int not null); +> ok + +-- Silently corrupted column C +alter table T modify C null; +> ok + +insert into T values(null); +> update count: 1 + +drop table T; +> ok + +SET MODE Oracle; +> ok + +create table foo (bar varchar(255)); +> ok + +alter table foo modify (bar varchar(255) not null); +> ok + +insert into foo values(null); +> exception NULL_NOT_ALLOWED + +DROP TABLE FOO; +> ok + +SET MODE Regular; +> ok + +-- Tests a bug we used to have where altering the name of a column that had +-- a check constraint that referenced itself would result in not being able +-- to re-open the DB. +create table test(id int check(id in (1,2)) ); +> ok + +alter table test alter id rename to id2; +> ok + +@reconnect + +insert into test values 1; +> update count: 1 + +insert into test values 3; +> exception CHECK_CONSTRAINT_VIOLATED_1 + +drop table test; +> ok + +CREATE MEMORY TABLE TEST(C INT); +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS D RENAME TO E; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS C RENAME TO D; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS E SET NOT NULL; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS D SET NOT NULL; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS E SET DEFAULT 1; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS D SET DEFAULT 1; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS E SET ON UPDATE 2; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS D SET ON UPDATE 2; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS E SET DATA TYPE BIGINT; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS D SET DATA TYPE BIGINT; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS E SET INVISIBLE; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS D SET INVISIBLE; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS E SELECTIVITY 3; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS D SELECTIVITY 3; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS E RESTART WITH 4; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS D RESTART WITH 4 SET MAXVALUE 1000; +> ok + +SELECT COLUMN_NAME, IS_IDENTITY, IDENTITY_GENERATION, IDENTITY_START, IDENTITY_INCREMENT, IDENTITY_MAXIMUM, + IDENTITY_MINIMUM, IDENTITY_CYCLE, IDENTITY_BASE, IDENTITY_CACHE FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'TEST'; +> COLUMN_NAME IS_IDENTITY IDENTITY_GENERATION IDENTITY_START IDENTITY_INCREMENT IDENTITY_MAXIMUM IDENTITY_MINIMUM IDENTITY_CYCLE IDENTITY_BASE IDENTITY_CACHE +> ----------- ----------- ------------------- -------------- ------------------ ---------------- ---------------- -------------- ------------- -------------- +> D YES BY DEFAULT 1 1 1000 1 NO 4 32 +> rows: 1 + +ALTER TABLE TEST ALTER COLUMN D SET CYCLE; +> ok + +SELECT IDENTITY_CYCLE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST'; +>> YES + +ALTER TABLE TEST ALTER COLUMN D DROP IDENTITY; +> ok + +SELECT IS_IDENTITY FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST'; +>> NO + +ALTER TABLE TEST ALTER COLUMN D DROP IDENTITY; +> ok + +ALTER TABLE TEST ALTER COLUMN E DROP IDENTITY; +> exception COLUMN_NOT_FOUND_1 + +ALTER TABLE TEST ALTER COLUMN D SET GENERATED BY DEFAULT; +> ok + +ALTER TABLE TEST ALTER COLUMN D SET DEFAULT (1); +> ok + +SELECT COLUMN_DEFAULT, IS_IDENTITY FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST'; +> COLUMN_DEFAULT IS_IDENTITY +> -------------- ----------- +> null YES +> rows: 1 + +ALTER TABLE TEST ALTER COLUMN D DROP IDENTITY; +> ok + +ALTER TABLE TEST ALTER COLUMN D SET GENERATED ALWAYS; +> ok + +SELECT IS_IDENTITY FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST'; +>> YES + +ALTER TABLE TEST ALTER COLUMN IF EXISTS E DROP IDENTITY; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS E DROP NOT NULL; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS D DROP NOT NULL; +> exception COLUMN_MUST_NOT_BE_NULLABLE_1 + +ALTER TABLE TEST ALTER COLUMN IF EXISTS E DROP DEFAULT; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS D DROP DEFAULT; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS E DROP ON UPDATE; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS D DROP ON UPDATE; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS E INT; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS D INT; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS E SET VISIBLE; +> ok + +ALTER TABLE TEST ALTER COLUMN IF EXISTS D SET VISIBLE; +> ok + +SCRIPT NODATA NOPASSWORDS NOSETTINGS NOVERSION TABLE TEST; +> SCRIPT +> ------------------------------------------------------------ +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "D" INTEGER NOT NULL ); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> rows (ordered): 3 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID INT GENERATED ALWAYS AS IDENTITY (MINVALUE 1 MAXVALUE 10 INCREMENT BY -1), V INT); +> ok + +INSERT INTO TEST(V) VALUES 1; +> update count: 1 + +TABLE TEST; +> ID V +> -- - +> 10 1 +> rows: 1 + +DELETE FROM TEST; +> update count: 1 + +ALTER TABLE TEST ALTER COLUMN ID RESTART; +> ok + +INSERT INTO TEST(V) VALUES 1; +> update count: 1 + +TABLE TEST; +> ID V +> -- - +> 10 1 +> rows: 1 + +ALTER TABLE TEST ALTER COLUMN ID RESTART WITH 5; +> ok + +INSERT INTO TEST(V) VALUES 2; +> update count: 1 + +TABLE TEST; +> ID V +> -- - +> 10 1 +> 5 2 +> rows: 2 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INT) AS VALUES 1, 2, 3; +> ok + +ALTER TABLE TEST ALTER COLUMN A SET DATA TYPE BIGINT USING A * 10; +> ok + +TABLE TEST; +> A +> -- +> 10 +> 20 +> 30 +> rows: 3 + +ALTER TABLE TEST ADD COLUMN B INT NOT NULL USING A + 1; +> ok + +TABLE TEST; +> A B +> -- -- +> 10 11 +> 20 21 +> 30 31 +> rows: 3 + +ALTER TABLE TEST ADD COLUMN C VARCHAR(2) USING A; +> ok + +TABLE TEST; +> A B C +> -- -- -- +> 10 11 10 +> 20 21 20 +> 30 31 30 +> rows: 3 + +ALTER TABLE TEST ALTER COLUMN C SET DATA TYPE VARCHAR(3) USING C || '*'; +> ok + +TABLE TEST; +> A B C +> -- -- --- +> 10 11 10* +> 20 21 20* +> 30 31 30* +> rows: 3 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(B BINARY) AS VALUES X'00'; +> ok + +ALTER TABLE TEST ALTER COLUMN B SET DATA TYPE BINARY(2); +> ok + +TABLE TEST; +>> X'0000' + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(D INT DEFAULT 8, G INT GENERATED ALWAYS AS (D + 1), S INT GENERATED ALWAYS AS IDENTITY); +> ok + +SELECT COLUMN_NAME, COLUMN_DEFAULT, IS_IDENTITY, IS_GENERATED, GENERATION_EXPRESSION + FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST'; +> COLUMN_NAME COLUMN_DEFAULT IS_IDENTITY IS_GENERATED GENERATION_EXPRESSION +> ----------- -------------- ----------- ------------ --------------------- +> D 8 NO NEVER null +> G null NO ALWAYS "D" + 1 +> S null YES NEVER null +> rows: 3 + +ALTER TABLE TEST ALTER COLUMN D SET ON UPDATE 1; +> ok + +ALTER TABLE TEST ALTER COLUMN G SET ON UPDATE 1; +> ok + +ALTER TABLE TEST ALTER COLUMN S SET ON UPDATE 1; +> ok + +SELECT COLUMN_NAME, COLUMN_DEFAULT, IS_IDENTITY, IS_GENERATED, GENERATION_EXPRESSION, COLUMN_ON_UPDATE + FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST'; +> COLUMN_NAME COLUMN_DEFAULT IS_IDENTITY IS_GENERATED GENERATION_EXPRESSION COLUMN_ON_UPDATE +> ----------- -------------- ----------- ------------ --------------------- ---------------- +> D 8 NO NEVER null 1 +> G null NO ALWAYS "D" + 1 null +> S null YES NEVER null null +> rows: 3 + +ALTER TABLE TEST ALTER COLUMN D DROP ON UPDATE; +> ok + +ALTER TABLE TEST ALTER COLUMN G DROP ON UPDATE; +> ok + +ALTER TABLE TEST ALTER COLUMN S DROP ON UPDATE; +> ok + +SELECT COLUMN_NAME, COLUMN_DEFAULT, IS_IDENTITY, IS_GENERATED, GENERATION_EXPRESSION, COLUMN_ON_UPDATE + FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST'; +> COLUMN_NAME COLUMN_DEFAULT IS_IDENTITY IS_GENERATED GENERATION_EXPRESSION COLUMN_ON_UPDATE +> ----------- -------------- ----------- ------------ --------------------- ---------------- +> D 8 NO NEVER null null +> G null NO ALWAYS "D" + 1 null +> S null YES NEVER null null +> rows: 3 + +ALTER TABLE TEST ALTER COLUMN G DROP DEFAULT; +> ok + +ALTER TABLE TEST ALTER COLUMN S DROP DEFAULT; +> ok + +ALTER TABLE TEST ALTER COLUMN D DROP EXPRESSION; +> ok + +ALTER TABLE TEST ALTER COLUMN S DROP EXPRESSION; +> ok + +ALTER TABLE TEST ALTER COLUMN D DROP IDENTITY; +> ok + +ALTER TABLE TEST ALTER COLUMN G DROP IDENTITY; +> ok + +ALTER TABLE TEST ALTER COLUMN G SET DEFAULT ("D" + 2); +> ok + +SELECT COLUMN_NAME, COLUMN_DEFAULT, IS_IDENTITY, IS_GENERATED, GENERATION_EXPRESSION + FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST'; +> COLUMN_NAME COLUMN_DEFAULT IS_IDENTITY IS_GENERATED GENERATION_EXPRESSION +> ----------- -------------- ----------- ------------ --------------------- +> D 8 NO NEVER null +> G null NO ALWAYS "D" + 1 +> S null YES NEVER null +> rows: 3 + +ALTER TABLE TEST ALTER COLUMN D DROP DEFAULT; +> ok + +ALTER TABLE TEST ALTER COLUMN G DROP EXPRESSION; +> ok + +ALTER TABLE TEST ALTER COLUMN S DROP IDENTITY; +> ok + +SELECT COLUMN_NAME, COLUMN_DEFAULT, IS_IDENTITY, IS_GENERATED, GENERATION_EXPRESSION + FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST'; +> COLUMN_NAME COLUMN_DEFAULT IS_IDENTITY IS_GENERATED GENERATION_EXPRESSION +> ----------- -------------- ----------- ------------ --------------------- +> D null NO NEVER null +> G null NO NEVER null +> S null NO NEVER null +> rows: 3 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 10 MINVALUE 3 INCREMENT BY 2 CYCLE CACHE 16), V INT); +> ok + +INSERT INTO TEST(V) VALUES 1, 2; +> update count: 2 + +DELETE FROM TEST WHERE V = 2; +> update count: 1 + +SELECT COLUMN_NAME, DATA_TYPE, IS_IDENTITY, IDENTITY_START, IDENTITY_INCREMENT, IDENTITY_MAXIMUM, IDENTITY_MINIMUM, + IDENTITY_CYCLE, IDENTITY_BASE, IDENTITY_CACHE + FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST' AND COLUMN_NAME = 'ID'; +> COLUMN_NAME DATA_TYPE IS_IDENTITY IDENTITY_START IDENTITY_INCREMENT IDENTITY_MAXIMUM IDENTITY_MINIMUM IDENTITY_CYCLE IDENTITY_BASE IDENTITY_CACHE +> ----------- --------- ----------- -------------- ------------------ ------------------- ---------------- -------------- ------------- -------------- +> ID BIGINT YES 10 2 9223372036854775807 3 YES 14 16 +> rows: 1 + +ALTER TABLE TEST ALTER COLUMN ID SET DATA TYPE INTEGER; +> ok + +SELECT COLUMN_NAME, DATA_TYPE, IS_IDENTITY, IDENTITY_START, IDENTITY_INCREMENT, IDENTITY_MAXIMUM, IDENTITY_MINIMUM, + IDENTITY_CYCLE, IDENTITY_BASE, IDENTITY_CACHE + FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST' AND COLUMN_NAME = 'ID'; +> COLUMN_NAME DATA_TYPE IS_IDENTITY IDENTITY_START IDENTITY_INCREMENT IDENTITY_MAXIMUM IDENTITY_MINIMUM IDENTITY_CYCLE IDENTITY_BASE IDENTITY_CACHE +> ----------- --------- ----------- -------------- ------------------ ---------------- ---------------- -------------- ------------- -------------- +> ID INTEGER YES 10 2 2147483647 3 YES 14 16 +> rows: 1 + +DROP TABLE TEST; +> ok + +CREATE MEMORY TABLE TEST(ID BIGINT GENERATED ALWAYS AS IDENTITY, V INT); +> ok + +SELECT COLUMN_NAME, IS_IDENTITY, IDENTITY_GENERATION + FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST' AND COLUMN_NAME = 'ID'; +> COLUMN_NAME IS_IDENTITY IDENTITY_GENERATION +> ----------- ----------- ------------------- +> ID YES ALWAYS +> rows: 1 + +INSERT INTO TEST(V) VALUES 10; +> update count: 1 + +INSERT INTO TEST(ID, V) VALUES (2, 20); +> exception GENERATED_COLUMN_CANNOT_BE_ASSIGNED_1 + +UPDATE TEST SET ID = ID + 1; +> exception GENERATED_COLUMN_CANNOT_BE_ASSIGNED_1 + +MERGE INTO TEST(ID, V) KEY(V) VALUES (2, 10); +> exception GENERATED_COLUMN_CANNOT_BE_ASSIGNED_1 + +MERGE INTO TEST USING (VALUES (2, 20)) S(ID, V) ON TEST.ID = S.ID + WHEN NOT MATCHED THEN INSERT VALUES (S.ID, S.V); +> exception GENERATED_COLUMN_CANNOT_BE_ASSIGNED_1 + +@reconnect + +SELECT COLUMN_NAME, IS_IDENTITY, IDENTITY_GENERATION + FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST' AND COLUMN_NAME = 'ID'; +> COLUMN_NAME IS_IDENTITY IDENTITY_GENERATION +> ----------- ----------- ------------------- +> ID YES ALWAYS +> rows: 1 + +SCRIPT NODATA NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> ----------------------------------------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "ID" BIGINT GENERATED ALWAYS AS IDENTITY(START WITH 1 RESTART WITH 2) NOT NULL, "V" INTEGER ); +> -- 1 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> rows (ordered): 3 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID BIGINT, V INT); +> ok + +ALTER TABLE TEST ALTER COLUMN ID SET GENERATED ALWAYS; +> ok + +INSERT INTO TEST(V) VALUES 1; +> update count: 1 + +SELECT COLUMN_NAME, IS_IDENTITY, IDENTITY_GENERATION, IDENTITY_BASE + FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST' AND COLUMN_NAME = 'ID'; +> COLUMN_NAME IS_IDENTITY IDENTITY_GENERATION IDENTITY_BASE +> ----------- ----------- ------------------- ------------- +> ID YES ALWAYS 2 +> rows: 1 + +ALTER TABLE TEST ALTER COLUMN ID SET GENERATED BY DEFAULT; +> ok + +INSERT INTO TEST(V) VALUES 2; +> update count: 1 + +SELECT COLUMN_NAME, IS_IDENTITY, IDENTITY_GENERATION, IDENTITY_BASE + FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST' AND COLUMN_NAME = 'ID'; +> COLUMN_NAME IS_IDENTITY IDENTITY_GENERATION IDENTITY_BASE +> ----------- ----------- ------------------- ------------- +> ID YES BY DEFAULT 3 +> rows: 1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INT DEFAULT 1, B INT DEFAULT 2 DEFAULT ON NULL); +> ok + +SELECT COLUMN_NAME, COLUMN_DEFAULT, DEFAULT_ON_NULL FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST'; +> COLUMN_NAME COLUMN_DEFAULT DEFAULT_ON_NULL +> ----------- -------------- --------------- +> A 1 FALSE +> B 2 TRUE +> rows: 2 + +ALTER TABLE TEST ALTER COLUMN A SET DEFAULT ON NULL; +> ok + +ALTER TABLE TEST ALTER COLUMN B DROP DEFAULT ON NULL; +> ok + +SELECT COLUMN_NAME, COLUMN_DEFAULT, DEFAULT_ON_NULL FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST'; +> COLUMN_NAME COLUMN_DEFAULT DEFAULT_ON_NULL +> ----------- -------------- --------------- +> A 1 TRUE +> B 2 FALSE +> rows: 2 + +ALTER TABLE TEST ALTER COLUMN A SET DEFAULT ON NULL; +> ok + +ALTER TABLE TEST ALTER COLUMN B DROP DEFAULT ON NULL; +> ok + +SELECT COLUMN_NAME, COLUMN_DEFAULT, DEFAULT_ON_NULL FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST'; +> COLUMN_NAME COLUMN_DEFAULT DEFAULT_ON_NULL +> ----------- -------------- --------------- +> A 1 TRUE +> B 2 FALSE +> rows: 2 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/ddl/alterTableDropColumn.sql b/h2/src/test/org/h2/test/scripts/ddl/alterTableDropColumn.sql new file mode 100644 index 0000000..a7825a5 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/alterTableDropColumn.sql @@ -0,0 +1,98 @@ +-- 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 +-- + +CREATE TABLE TEST(A VARCHAR, B VARCHAR, C VARCHAR AS LOWER(A)); +> ok + +ALTER TABLE TEST DROP COLUMN B; +> ok + +DROP TABLE TEST; +> ok + +ALTER TABLE IF EXISTS TEST DROP COLUMN A; +> ok + +ALTER TABLE TEST DROP COLUMN A; +> exception TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1 + +CREATE TABLE TEST(A INT, B INT, C INT, D INT, E INT, F INT, G INT, H INT, I INT, J INT); +> ok + +ALTER TABLE TEST DROP COLUMN IF EXISTS J; +> ok + +ALTER TABLE TEST DROP COLUMN J; +> exception COLUMN_NOT_FOUND_1 + +ALTER TABLE TEST DROP COLUMN B; +> ok + +ALTER TABLE TEST DROP COLUMN IF EXISTS C; +> ok + +SELECT * FROM TEST; +> A D E F G H I +> - - - - - - - +> rows: 0 + +ALTER TABLE TEST DROP COLUMN B, D; +> exception COLUMN_NOT_FOUND_1 + +ALTER TABLE TEST DROP COLUMN IF EXISTS B, D; +> ok + +SELECT * FROM TEST; +> A E F G H I +> - - - - - - +> rows: 0 + +ALTER TABLE TEST DROP COLUMN E, F; +> ok + +SELECT * FROM TEST; +> A G H I +> - - - - +> rows: 0 + +ALTER TABLE TEST DROP COLUMN (B, H); +> exception COLUMN_NOT_FOUND_1 + +ALTER TABLE TEST DROP COLUMN IF EXISTS (B, H); +> ok + +SELECT * FROM TEST; +> A G I +> - - - +> rows: 0 + +ALTER TABLE TEST DROP COLUMN (G, I); +> ok + +SELECT * FROM TEST; +> A +> - +> rows: 0 + +DROP TABLE TEST; +> ok + +CREATE TABLE T1(ID INT PRIMARY KEY, C INT); +> ok + +CREATE VIEW V1 AS SELECT C FROM T1; +> ok + +ALTER TABLE T1 DROP COLUMN C; +> exception COLUMN_IS_REFERENCED_1 + +DROP VIEW V1; +> ok + +ALTER TABLE T1 DROP COLUMN C; +> ok + +DROP TABLE T1; +> ok diff --git a/h2/src/test/org/h2/test/scripts/ddl/alterTableDropConstraint.sql b/h2/src/test/org/h2/test/scripts/ddl/alterTableDropConstraint.sql new file mode 100644 index 0000000..2be6935 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/alterTableDropConstraint.sql @@ -0,0 +1,19 @@ +-- 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 +-- + +CREATE TABLE A(A INT PRIMARY KEY); +> ok + +CREATE TABLE B(B INT PRIMARY KEY, A INT CONSTRAINT C REFERENCES A(A)); +> ok + +ALTER TABLE A DROP CONSTRAINT C; +> exception CONSTRAINT_NOT_FOUND_1 + +ALTER TABLE B DROP CONSTRAINT C; +> ok + +DROP TABLE B, A; +> ok diff --git a/h2/src/test/org/h2/test/scripts/ddl/alterTableRename.sql b/h2/src/test/org/h2/test/scripts/ddl/alterTableRename.sql new file mode 100644 index 0000000..53683cb --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/alterTableRename.sql @@ -0,0 +1,61 @@ +-- 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 +-- + +-- Test for ALTER TABLE RENAME and ALTER VIEW RENAME + +CREATE TABLE TABLE1A(ID INT); +> ok + +INSERT INTO TABLE1A VALUES (1); +> update count: 1 + +-- ALTER TABLE RENAME + +ALTER TABLE TABLE1A RENAME TO TABLE1B; +> ok + +SELECT * FROM TABLE1B; +>> 1 + +ALTER TABLE IF EXISTS TABLE1B RENAME TO TABLE1C; +> ok + +SELECT * FROM TABLE1C; +>> 1 + +ALTER TABLE BAD RENAME TO SMTH; +> exception TABLE_OR_VIEW_NOT_FOUND_1 + +ALTER TABLE IF EXISTS BAD RENAME TO SMTH; +> ok + +-- ALTER VIEW RENAME + +CREATE VIEW VIEW1A AS SELECT * FROM TABLE1C; +> ok + +ALTER VIEW VIEW1A RENAME TO VIEW1B; +> ok + +SELECT * FROM VIEW1B; +>> 1 + +ALTER TABLE IF EXISTS VIEW1B RENAME TO VIEW1C; +> ok + +SELECT * FROM VIEW1C; +>> 1 + +ALTER VIEW BAD RENAME TO SMTH; +> exception VIEW_NOT_FOUND_1 + +ALTER VIEW IF EXISTS BAD RENAME TO SMTH; +> ok + +SELECT * FROM VIEW1C; +>> 1 + +DROP TABLE TABLE1C CASCADE; +> ok diff --git a/h2/src/test/org/h2/test/scripts/ddl/alterTableRenameConstraint.sql b/h2/src/test/org/h2/test/scripts/ddl/alterTableRenameConstraint.sql new file mode 100644 index 0000000..6c1dbdc --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/alterTableRenameConstraint.sql @@ -0,0 +1,19 @@ +-- 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 +-- + +CREATE TABLE A(A INT PRIMARY KEY); +> ok + +CREATE TABLE B(B INT PRIMARY KEY, A INT CONSTRAINT C REFERENCES A(A)); +> ok + +ALTER TABLE A RENAME CONSTRAINT C TO C1; +> exception CONSTRAINT_NOT_FOUND_1 + +ALTER TABLE B RENAME CONSTRAINT C TO C1; +> ok + +DROP TABLE B, A; +> ok diff --git a/h2/src/test/org/h2/test/scripts/ddl/analyze.sql b/h2/src/test/org/h2/test/scripts/ddl/analyze.sql new file mode 100644 index 0000000..706fe12 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/analyze.sql @@ -0,0 +1,67 @@ +-- 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 +-- + +CREATE TABLE TEST(X INT, B BLOB(1)); +> ok + +INSERT INTO TEST(X) VALUES 1, 2, 3, 3, NULL, NULL; +> update count: 6 + +ANALYZE TABLE TEST; +> ok + +SELECT SELECTIVITY FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST' AND COLUMN_NAME = 'X'; +>> 66 + +INSERT INTO TEST(X) VALUES 6, 7, 8, 9; +> update count: 4 + +ANALYZE TABLE TEST; +> ok + +SELECT SELECTIVITY FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST' AND COLUMN_NAME = 'X'; +>> 80 + +TRUNCATE TABLE TEST; +> update count: 10 + +INSERT INTO TEST(X) VALUES 1, 2, 3; +> update count: 3 + +ANALYZE TABLE TEST; +> ok + +SELECT SELECTIVITY FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST' AND COLUMN_NAME = 'X'; +>> 100 + +TRUNCATE TABLE TEST; +> update count: 3 + +INSERT INTO TEST(X) VALUES 1, 1, 1, 1; +> update count: 4 + +ANALYZE TABLE TEST; +> ok + +SELECT SELECTIVITY FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST' AND COLUMN_NAME = 'X'; +>> 25 + +ANALYZE TABLE TEST SAMPLE_SIZE 3; +> ok + +SELECT SELECTIVITY FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST' AND COLUMN_NAME = 'X'; +>> 33 + +TRUNCATE TABLE TEST; +> update count: 4 + +ANALYZE TABLE TEST; +> ok + +SELECT SELECTIVITY FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST' AND COLUMN_NAME = 'X'; +>> 50 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/ddl/commentOn.sql b/h2/src/test/org/h2/test/scripts/ddl/commentOn.sql new file mode 100644 index 0000000..ea9d89b --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/commentOn.sql @@ -0,0 +1,66 @@ +-- 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 +-- + +CREATE TABLE TEST(A INT COMMENT NULL, B INT COMMENT '', C INT COMMENT 'comment 1', D INT COMMENT 'comment 2'); +> ok + +SELECT COLUMN_NAME, REMARKS FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST'; +> COLUMN_NAME REMARKS +> ----------- --------- +> A null +> B null +> C comment 1 +> D comment 2 +> rows: 4 + +COMMENT ON COLUMN TEST.A IS 'comment 3'; +> ok + +COMMENT ON COLUMN TEST.B IS 'comment 4'; +> ok + +COMMENT ON COLUMN TEST.C IS NULL; +> ok + +COMMENT ON COLUMN TEST.D IS ''; +> ok + +SELECT COLUMN_NAME, REMARKS FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST'; +> COLUMN_NAME REMARKS +> ----------- --------- +> A comment 3 +> B comment 4 +> C null +> D null +> rows: 4 + +DROP TABLE TEST; +> ok + +CREATE USER U1 COMMENT NULL PASSWORD '1'; +> ok + +CREATE USER U2 COMMENT '' PASSWORD '1'; +> ok + +CREATE USER U3 COMMENT 'comment' PASSWORD '1'; +> ok + +SELECT USER_NAME, REMARKS FROM INFORMATION_SCHEMA.USERS WHERE USER_NAME LIKE 'U_'; +> USER_NAME REMARKS +> --------- ------- +> U1 null +> U2 null +> U3 comment +> rows: 3 + +DROP USER U1; +> ok + +DROP USER U2; +> ok + +DROP USER U3; +> ok diff --git a/h2/src/test/org/h2/test/scripts/ddl/createAlias.sql b/h2/src/test/org/h2/test/scripts/ddl/createAlias.sql new file mode 100644 index 0000000..4176fb7 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/createAlias.sql @@ -0,0 +1,156 @@ +-- 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 +-- + +create alias "MIN" for 'java.lang.Integer.parseInt(java.lang.String)'; +> exception FUNCTION_ALIAS_ALREADY_EXISTS_1 + +create alias "CAST" for 'java.lang.Integer.parseInt(java.lang.String)'; +> exception FUNCTION_ALIAS_ALREADY_EXISTS_1 + +@reconnect off + +--- function alias --------------------------------------------------------------------------------------------- +CREATE ALIAS MY_SQRT FOR 'java.lang.Math.sqrt'; +> ok + +SELECT MY_SQRT(2.0) MS, SQRT(2.0); +> MS 1.4142135623730951 +> ------------------ ------------------ +> 1.4142135623730951 1.4142135623730951 +> rows: 1 + +SELECT MY_SQRT(SUM(X)), SUM(X), MY_SQRT(55) FROM SYSTEM_RANGE(1, 10); +> PUBLIC.MY_SQRT(SUM(X)) SUM(X) PUBLIC.MY_SQRT(55) +> ---------------------- ------ ------------------ +> 7.416198487095663 55 7.416198487095663 +> rows: 1 + +SELECT MY_SQRT(-1.0) MS, SQRT(NULL) S; +> MS 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'; +> 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, ROUTINE_DEFINITION, + EXTERNAL_NAME, EXTERNAL_LANGUAGE, + IS_DETERMINISTIC, REMARKS FROM INFORMATION_SCHEMA.ROUTINES; +> 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 +> 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 + +CREATE ALIAS TRUNC FOR 'java.lang.Math.floor(double)'; +> exception FUNCTION_ALIAS_ALREADY_EXISTS_1 + +CREATE ALIAS PUBLIC.TRUNC FOR 'java.lang.Math.floor(double)'; +> exception FUNCTION_ALIAS_ALREADY_EXISTS_1 + +CREATE ALIAS TEST_SCHEMA.TRUNC FOR 'java.lang.Math.round(double)'; +> exception FUNCTION_ALIAS_ALREADY_EXISTS_1 + +SET BUILTIN_ALIAS_OVERRIDE=1; +> ok + +CREATE ALIAS TRUNC FOR 'java.lang.Math.floor(double)'; +> ok + +SELECT TRUNC(1.5); +>> 1.0 + +SELECT TRUNC(-1.5); +>> -2.0 + +DROP ALIAS TRUNC; +> ok + +-- Compatibility syntax with identifier +CREATE ALIAS TRUNC FOR "java.lang.Math.floor(double)"; +> ok + +SELECT TRUNC(-1.5); +>> -2.0 + +DROP ALIAS TRUNC; +> ok + +CREATE ALIAS PUBLIC.TRUNC FOR 'java.lang.Math.floor(double)'; +> ok + +CREATE ALIAS TEST_SCHEMA.TRUNC FOR 'java.lang.Math.round(double)'; +> ok + +SELECT PUBLIC.TRUNC(1.5); +>> 1.0 + +SELECT PUBLIC.TRUNC(-1.5); +>> -2.0 + +SELECT TEST_SCHEMA.TRUNC(1.5); +>> 2 + +SELECT TEST_SCHEMA.TRUNC(-1.5); +>> -1 + +DROP ALIAS PUBLIC.TRUNC; +> ok + +DROP ALIAS TEST_SCHEMA.TRUNC; +> ok + +SET BUILTIN_ALIAS_OVERRIDE=0; +> ok + +DROP SCHEMA TEST_SCHEMA RESTRICT; +> ok + +-- test for issue #1531 +CREATE TABLE TEST (ID BIGINT, VAL VARCHAR2(10)) AS SELECT x,'val'||x FROM SYSTEM_RANGE(1,2); +> ok + +CREATE ALIAS FTBL AS $$ ResultSet t(Connection c) throws SQLException {return c.prepareStatement("SELECT ID, VAL FROM TEST").executeQuery();} $$; +> ok + +CREATE OR REPLACE VIEW V_TEST (ID, VAL) AS (SELECT * FROM FTBL()); +> ok + +SELECT * FROM V_TEST; +> ID VAL +> -- ---- +> 1 val1 +> 2 val2 +> rows: 2 diff --git a/h2/src/test/org/h2/test/scripts/ddl/createConstant.sql b/h2/src/test/org/h2/test/scripts/ddl/createConstant.sql new file mode 100644 index 0000000..a2b941a --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/createConstant.sql @@ -0,0 +1,82 @@ +-- 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 +-- + +CREATE SCHEMA CONST; +> ok + +CREATE CONSTANT IF NOT EXISTS ONE VALUE 1; +> ok + +COMMENT ON CONSTANT ONE IS 'Eins'; +> ok + +CREATE CONSTANT IF NOT EXISTS ONE VALUE 1; +> ok + +CREATE CONSTANT CONST.ONE VALUE 1; +> ok + +SELECT CONSTANT_SCHEMA, CONSTANT_NAME, VALUE_DEFINITION, DATA_TYPE, NUMERIC_PRECISION, REMARKS FROM INFORMATION_SCHEMA.CONSTANTS; +> CONSTANT_SCHEMA CONSTANT_NAME VALUE_DEFINITION DATA_TYPE NUMERIC_PRECISION REMARKS +> --------------- ------------- ---------------- --------- ----------------- ------- +> CONST ONE 1 INTEGER 32 null +> PUBLIC ONE 1 INTEGER 32 Eins +> rows: 2 + +SELECT ONE, CONST.ONE; +> 1 1 +> - - +> 1 1 +> rows: 1 + +COMMENT ON CONSTANT ONE IS NULL; +> ok + +DROP SCHEMA CONST CASCADE; +> ok + +SELECT CONSTANT_SCHEMA, CONSTANT_NAME, VALUE_DEFINITION, DATA_TYPE, REMARKS FROM INFORMATION_SCHEMA.CONSTANTS; +> CONSTANT_SCHEMA CONSTANT_NAME VALUE_DEFINITION DATA_TYPE REMARKS +> --------------- ------------- ---------------- --------- ------- +> PUBLIC ONE 1 INTEGER null +> rows: 1 + +DROP CONSTANT ONE; +> ok + +DROP CONSTANT IF EXISTS ONE; +> ok + +create constant abc value 1; +> ok + +call abc; +> 1 +> - +> 1 +> rows: 1 + +drop all objects; +> ok + +call abc; +> exception COLUMN_NOT_FOUND_1 + +create constant abc value 1; +> ok + +comment on constant abc is 'One'; +> ok + +select remarks from information_schema.constants where constant_name = 'ABC'; +>> One + +@reconnect + +select remarks from information_schema.constants where constant_name = 'ABC'; +>> One + +drop constant abc; +> ok diff --git a/h2/src/test/org/h2/test/scripts/ddl/createDomain.sql b/h2/src/test/org/h2/test/scripts/ddl/createDomain.sql new file mode 100644 index 0000000..e0936e3 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/createDomain.sql @@ -0,0 +1,259 @@ +-- 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 +-- + +CREATE SCHEMA S1; +> ok + +CREATE SCHEMA S2; +> ok + +CREATE DOMAIN S1.D1 AS INT DEFAULT 1; +> ok + +CREATE DOMAIN S2.D2 AS TIMESTAMP WITH TIME ZONE ON UPDATE CURRENT_TIMESTAMP; +> ok + +CREATE TABLE TEST(C1 S1.D1, C2 S2.D2); +> ok + +SELECT COLUMN_NAME, DOMAIN_CATALOG, DOMAIN_SCHEMA, DOMAIN_NAME, COLUMN_DEFAULT, COLUMN_ON_UPDATE + FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DOMAIN_CATALOG DOMAIN_SCHEMA DOMAIN_NAME COLUMN_DEFAULT COLUMN_ON_UPDATE +> ----------- -------------- ------------- ----------- -------------- ---------------- +> C1 SCRIPT S1 D1 null null +> C2 SCRIPT S2 D2 null null +> rows (ordered): 2 + +SELECT DOMAIN_CATALOG, DOMAIN_SCHEMA, DOMAIN_NAME, DOMAIN_DEFAULT, DOMAIN_ON_UPDATE, DATA_TYPE FROM INFORMATION_SCHEMA.DOMAINS; +> DOMAIN_CATALOG DOMAIN_SCHEMA DOMAIN_NAME DOMAIN_DEFAULT DOMAIN_ON_UPDATE DATA_TYPE +> -------------- ------------- ----------- -------------- ----------------- ------------------------ +> SCRIPT S1 D1 1 null INTEGER +> SCRIPT S2 D2 null CURRENT_TIMESTAMP TIMESTAMP WITH TIME ZONE +> rows: 2 + +DROP TABLE TEST; +> ok + +DROP DOMAIN S1.D1; +> ok + +DROP SCHEMA S1 RESTRICT; +> ok + +DROP SCHEMA S2 RESTRICT; +> exception CANNOT_DROP_2 + +DROP SCHEMA S2 CASCADE; +> ok + +CREATE DOMAIN D INT; +> ok + +CREATE MEMORY TABLE TEST(C D); +> ok + +ALTER DOMAIN D ADD CHECK (VALUE <> 0); +> ok + +ALTER DOMAIN D ADD CONSTRAINT D1 CHECK (VALUE > 0); +> ok + +ALTER DOMAIN D ADD CONSTRAINT D1 CHECK (VALUE > 0); +> exception CONSTRAINT_ALREADY_EXISTS_1 + +ALTER DOMAIN D ADD CONSTRAINT IF NOT EXISTS D1 CHECK (VALUE > 0); +> ok + +ALTER DOMAIN X ADD CHECK (VALUE > 0); +> exception DOMAIN_NOT_FOUND_1 + +ALTER DOMAIN IF EXISTS X ADD CHECK (VALUE > 0); +> ok + +INSERT INTO TEST VALUES -1; +> exception CHECK_CONSTRAINT_VIOLATED_1 + +ALTER DOMAIN D DROP CONSTRAINT D1; +> ok + +ALTER DOMAIN D DROP CONSTRAINT D1; +> exception CONSTRAINT_NOT_FOUND_1 + +ALTER DOMAIN D DROP CONSTRAINT IF EXISTS D1; +> ok + +ALTER DOMAIN IF EXISTS X DROP CONSTRAINT D1; +> ok + +ALTER DOMAIN X DROP CONSTRAINT IF EXISTS D1; +> exception DOMAIN_NOT_FOUND_1 + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> ------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE DOMAIN "PUBLIC"."D" AS INTEGER; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "C" "PUBLIC"."D" ); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> ALTER DOMAIN "PUBLIC"."D" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_4" CHECK(VALUE <> 0) NOCHECK; +> rows (ordered): 5 + +SELECT CONSTRAINT_NAME, DOMAIN_NAME FROM INFORMATION_SCHEMA.DOMAIN_CONSTRAINTS; +> CONSTRAINT_NAME DOMAIN_NAME +> --------------- ----------- +> CONSTRAINT_4 D +> rows: 1 + +TABLE INFORMATION_SCHEMA.CHECK_CONSTRAINTS; +> CONSTRAINT_CATALOG CONSTRAINT_SCHEMA CONSTRAINT_NAME CHECK_CLAUSE +> ------------------ ----------------- --------------- ------------ +> SCRIPT PUBLIC CONSTRAINT_4 VALUE <> 0 +> rows: 1 + +SELECT COUNT(*) FROM INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE; +>> 0 + +INSERT INTO TEST VALUES -1; +> update count: 1 + +INSERT INTO TEST VALUES 0; +> exception CHECK_CONSTRAINT_VIOLATED_1 + +DROP DOMAIN D RESTRICT; +> exception CANNOT_DROP_2 + +DROP DOMAIN D CASCADE; +> ok + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> ------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "C" INTEGER ); +> -- 1 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> INSERT INTO "PUBLIC"."TEST" VALUES (-1); +> ALTER TABLE "PUBLIC"."TEST" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_2" CHECK("C" <> 0) NOCHECK; +> rows (ordered): 5 + +SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE, TABLE_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS; +> CONSTRAINT_NAME CONSTRAINT_TYPE TABLE_NAME +> --------------- --------------- ---------- +> CONSTRAINT_2 CHECK TEST +> rows: 1 + +TABLE INFORMATION_SCHEMA.CHECK_CONSTRAINTS; +> CONSTRAINT_CATALOG CONSTRAINT_SCHEMA CONSTRAINT_NAME CHECK_CLAUSE +> ------------------ ----------------- --------------- ------------ +> SCRIPT PUBLIC CONSTRAINT_2 "C" <> 0 +> rows: 1 + +TABLE INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE; +> TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME CONSTRAINT_CATALOG CONSTRAINT_SCHEMA CONSTRAINT_NAME +> ------------- ------------ ---------- ----------- ------------------ ----------------- --------------- +> SCRIPT PUBLIC TEST C SCRIPT PUBLIC CONSTRAINT_2 +> rows: 1 + +DROP TABLE TEST; +> ok + +CREATE DOMAIN D1 AS INT DEFAULT 1 CHECK (VALUE >= 1); +> ok + +CREATE DOMAIN D2 AS D1 DEFAULT 2; +> ok + +CREATE DOMAIN D3 AS D1 CHECK (VALUE >= 3); +> ok + +CREATE DOMAIN D4 AS D1 DEFAULT 4 CHECK (VALUE >= 4); +> ok + +SELECT DOMAIN_CATALOG, DOMAIN_SCHEMA, DOMAIN_NAME, DOMAIN_DEFAULT, DOMAIN_ON_UPDATE, DATA_TYPE, NUMERIC_PRECISION, + PARENT_DOMAIN_CATALOG, PARENT_DOMAIN_SCHEMA, PARENT_DOMAIN_NAME FROM INFORMATION_SCHEMA.DOMAINS WHERE DOMAIN_SCHEMA = 'PUBLIC'; +> DOMAIN_CATALOG DOMAIN_SCHEMA DOMAIN_NAME DOMAIN_DEFAULT DOMAIN_ON_UPDATE DATA_TYPE NUMERIC_PRECISION PARENT_DOMAIN_CATALOG PARENT_DOMAIN_SCHEMA PARENT_DOMAIN_NAME +> -------------- ------------- ----------- -------------- ---------------- --------- ----------------- --------------------- -------------------- ------------------ +> SCRIPT PUBLIC D1 1 null INTEGER 32 null null null +> SCRIPT PUBLIC D2 2 null INTEGER 32 SCRIPT PUBLIC D1 +> SCRIPT PUBLIC D3 null null INTEGER 32 SCRIPT PUBLIC D1 +> SCRIPT PUBLIC D4 4 null INTEGER 32 SCRIPT PUBLIC D1 +> rows: 4 + +SELECT DOMAIN_NAME, CHECK_CLAUSE FROM INFORMATION_SCHEMA.DOMAIN_CONSTRAINTS D JOIN INFORMATION_SCHEMA.CHECK_CONSTRAINTS C + ON D.CONSTRAINT_CATALOG = C.CONSTRAINT_CATALOG AND D.CONSTRAINT_SCHEMA = C.CONSTRAINT_SCHEMA AND D.CONSTRAINT_NAME = C.CONSTRAINT_NAME + WHERE C.CONSTRAINT_SCHEMA = 'PUBLIC'; +> DOMAIN_NAME CHECK_CLAUSE +> ----------- ------------ +> D1 VALUE >= 1 +> D3 VALUE >= 3 +> D4 VALUE >= 4 +> rows: 3 + +VALUES CAST(0 AS D2); +> exception CHECK_CONSTRAINT_VIOLATED_1 + +DROP DOMAIN D1; +> exception CANNOT_DROP_2 + +DROP DOMAIN D1 CASCADE; +> ok + +SELECT DOMAIN_CATALOG, DOMAIN_SCHEMA, DOMAIN_NAME, DOMAIN_DEFAULT, DOMAIN_ON_UPDATE, DATA_TYPE, NUMERIC_PRECISION, + PARENT_DOMAIN_CATALOG, PARENT_DOMAIN_SCHEMA, PARENT_DOMAIN_NAME FROM INFORMATION_SCHEMA.DOMAINS WHERE DOMAIN_SCHEMA = 'PUBLIC'; +> DOMAIN_CATALOG DOMAIN_SCHEMA DOMAIN_NAME DOMAIN_DEFAULT DOMAIN_ON_UPDATE DATA_TYPE NUMERIC_PRECISION PARENT_DOMAIN_CATALOG PARENT_DOMAIN_SCHEMA PARENT_DOMAIN_NAME +> -------------- ------------- ----------- -------------- ---------------- --------- ----------------- --------------------- -------------------- ------------------ +> SCRIPT PUBLIC D2 2 null INTEGER 32 null null null +> SCRIPT PUBLIC D3 1 null INTEGER 32 null null null +> SCRIPT PUBLIC D4 4 null INTEGER 32 null null null +> rows: 3 + +SELECT DOMAIN_NAME, CHECK_CLAUSE FROM INFORMATION_SCHEMA.DOMAIN_CONSTRAINTS D JOIN INFORMATION_SCHEMA.CHECK_CONSTRAINTS C + ON D.CONSTRAINT_CATALOG = C.CONSTRAINT_CATALOG AND D.CONSTRAINT_SCHEMA = C.CONSTRAINT_SCHEMA AND D.CONSTRAINT_NAME = C.CONSTRAINT_NAME + WHERE C.CONSTRAINT_SCHEMA = 'PUBLIC'; +> DOMAIN_NAME CHECK_CLAUSE +> ----------- ------------ +> D2 VALUE >= 1 +> D3 VALUE >= 1 +> D3 VALUE >= 3 +> D4 VALUE >= 1 +> D4 VALUE >= 4 +> rows: 5 + +DROP DOMAIN D2; +> ok + +DROP DOMAIN D3; +> ok + +DROP DOMAIN D4; +> ok + +CREATE DOMAIN D1 INT; +> ok + +CREATE DOMAIN D2 INT; +> ok + +DROP DOMAIN D1; +> ok + +CREATE DOMAIN D3 D2; +> ok + +@reconnect + +DROP DOMAIN D3; +> ok + +DROP DOMAIN D2; +> ok + +CREATE DOMAIN D AS CHARACTER VARYING CHECK (VALUE LIKE '%1%'); +> ok + +ALTER DOMAIN D ADD CHECK (VALUE ILIKE '%2%'); +> ok + +DROP DOMAIN D; +> ok diff --git a/h2/src/test/org/h2/test/scripts/ddl/createIndex.sql b/h2/src/test/org/h2/test/scripts/ddl/createIndex.sql new file mode 100644 index 0000000..4f99d98 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/createIndex.sql @@ -0,0 +1,34 @@ +-- 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 +-- + +CREATE TABLE TEST(G GEOMETRY); +> ok + +CREATE UNIQUE SPATIAL INDEX IDX ON TEST(G); +> exception SYNTAX_ERROR_2 + +CREATE HASH SPATIAL INDEX IDX ON TEST(G); +> exception SYNTAX_ERROR_2 + +CREATE UNIQUE HASH SPATIAL INDEX IDX ON TEST(G); +> exception SYNTAX_ERROR_2 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INT, B INT, C INT); +> ok + +CREATE INDEX TEST_IDX ON TEST(C) INCLUDE(B); +> exception SYNTAX_ERROR_1 + +CREATE UNIQUE INDEX TEST_IDX ON TEST(C) INCLUDE(B); +> ok + +DROP INDEX TEST_IDX; +> ok + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/ddl/createSchema.sql b/h2/src/test/org/h2/test/scripts/ddl/createSchema.sql new file mode 100644 index 0000000..e485831 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/createSchema.sql @@ -0,0 +1,64 @@ +-- 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 +-- + +CREATE USER TEST_USER PASSWORD 'test'; +> ok + +CREATE ROLE TEST_ROLE; +> ok + +CREATE SCHEMA S1; +> ok + +CREATE SCHEMA S2 AUTHORIZATION TEST_USER; +> ok + +CREATE SCHEMA S3 AUTHORIZATION TEST_ROLE; +> ok + +CREATE SCHEMA AUTHORIZATION TEST_USER; +> ok + +CREATE SCHEMA AUTHORIZATION TEST_ROLE; +> ok + +TABLE INFORMATION_SCHEMA.SCHEMATA; +> CATALOG_NAME SCHEMA_NAME SCHEMA_OWNER DEFAULT_CHARACTER_SET_CATALOG DEFAULT_CHARACTER_SET_SCHEMA DEFAULT_CHARACTER_SET_NAME SQL_PATH DEFAULT_COLLATION_NAME REMARKS +> ------------ ------------------ ------------ ----------------------------- ---------------------------- -------------------------- -------- ---------------------- ------- +> SCRIPT INFORMATION_SCHEMA SA SCRIPT PUBLIC Unicode null OFF null +> SCRIPT PUBLIC SA SCRIPT PUBLIC Unicode null OFF null +> SCRIPT S1 SA SCRIPT PUBLIC Unicode null OFF null +> SCRIPT S2 TEST_USER SCRIPT PUBLIC Unicode null OFF null +> SCRIPT S3 TEST_ROLE SCRIPT PUBLIC Unicode null OFF null +> SCRIPT TEST_ROLE TEST_ROLE SCRIPT PUBLIC Unicode null OFF null +> SCRIPT TEST_USER TEST_USER SCRIPT PUBLIC Unicode null OFF null +> rows: 7 + +DROP SCHEMA S1; +> ok + +DROP SCHEMA S2; +> ok + +DROP SCHEMA S3; +> ok + +DROP USER TEST_USER; +> exception CANNOT_DROP_2 + +DROP ROLE TEST_ROLE; +> exception CANNOT_DROP_2 + +DROP SCHEMA TEST_USER; +> ok + +DROP SCHEMA TEST_ROLE; +> ok + +DROP USER TEST_USER; +> ok + +DROP ROLE TEST_ROLE; +> ok diff --git a/h2/src/test/org/h2/test/scripts/ddl/createSequence.sql b/h2/src/test/org/h2/test/scripts/ddl/createSequence.sql new file mode 100644 index 0000000..e6f3cb8 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/createSequence.sql @@ -0,0 +1,196 @@ +-- 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 +-- + +CREATE SEQUENCE SEQ START WITH 0 INCREMENT BY 1 MINVALUE 0 MAXVALUE 1; +> ok + +DROP SEQUENCE SEQ; +> ok + +CREATE SEQUENCE SEQ START WITH 0 INCREMENT BY 1 MINVALUE 0 MAXVALUE 0; +> exception SEQUENCE_ATTRIBUTES_INVALID_7 + +CREATE SEQUENCE SEQ START WITH 1 INCREMENT BY 1 MINVALUE 1 MAXVALUE 0; +> exception SEQUENCE_ATTRIBUTES_INVALID_7 + +CREATE SEQUENCE SEQ START WITH 0 INCREMENT BY 0 MINVALUE 0 MAXVALUE 1; +> exception SEQUENCE_ATTRIBUTES_INVALID_7 + +CREATE SEQUENCE SEQ START WITH 1 INCREMENT BY 1 MINVALUE 2 MAXVALUE 10; +> exception SEQUENCE_ATTRIBUTES_INVALID_7 + +CREATE SEQUENCE SEQ START WITH 20 INCREMENT BY 1 MINVALUE 1 MAXVALUE 10; +> exception SEQUENCE_ATTRIBUTES_INVALID_7 + +CREATE SEQUENCE SEQ START WITH 0 INCREMENT BY 9223372036854775807 MINVALUE -9223372036854775808 MAXVALUE 9223372036854775807; +> ok + +DROP SEQUENCE SEQ; +> ok + +CREATE SEQUENCE SEQ START WITH 0 INCREMENT BY 9223372036854775807 MINVALUE -9223372036854775808 MAXVALUE 9223372036854775807 CACHE 2; +> exception SEQUENCE_ATTRIBUTES_INVALID_7 + +CREATE SEQUENCE SEQ START WITH 0 INCREMENT BY -9223372036854775808 MINVALUE -9223372036854775808 MAXVALUE 9223372036854775807; +> ok + +DROP SEQUENCE SEQ; +> ok + +CREATE SEQUENCE SEQ START WITH 0 INCREMENT BY -9223372036854775808 MINVALUE -9223372036854775808 MAXVALUE 9223372036854775807 CACHE 2; +> exception SEQUENCE_ATTRIBUTES_INVALID_7 + +CREATE SEQUENCE SEQ START WITH 0 INCREMENT BY -9223372036854775808 MINVALUE -1 MAXVALUE 9223372036854775807 NO CACHE; +> ok + +DROP SEQUENCE SEQ; +> ok + +CREATE SEQUENCE SEQ START WITH 0 INCREMENT BY -9223372036854775808 MINVALUE 0 MAXVALUE 9223372036854775807 NO CACHE; +> exception SEQUENCE_ATTRIBUTES_INVALID_7 + +CREATE SEQUENCE SEQ START WITH 0 INCREMENT BY -9223372036854775808 MINVALUE -1 MAXVALUE 9223372036854775807 CACHE 2; +> exception SEQUENCE_ATTRIBUTES_INVALID_7 + +CREATE SEQUENCE SEQ CACHE -1; +> exception SEQUENCE_ATTRIBUTES_INVALID_7 + +CREATE SEQUENCE SEQ MINVALUE 10 START WITH 9 RESTART WITH 10; +> exception SEQUENCE_ATTRIBUTES_INVALID_7 + +CREATE SEQUENCE SEQ MAXVALUE 10 START WITH 11 RESTART WITH 1; +> exception SEQUENCE_ATTRIBUTES_INVALID_7 + +CREATE SEQUENCE SEQ START WITH 0 MINVALUE -10 MAXVALUE 10; +> ok + +SELECT SEQUENCE_NAME, START_VALUE, MINIMUM_VALUE, MAXIMUM_VALUE, INCREMENT, CYCLE_OPTION, BASE_VALUE, CACHE + FROM INFORMATION_SCHEMA.SEQUENCES; +> SEQUENCE_NAME START_VALUE MINIMUM_VALUE MAXIMUM_VALUE INCREMENT CYCLE_OPTION BASE_VALUE CACHE +> ------------- ----------- ------------- ------------- --------- ------------ ---------- ----- +> SEQ 0 -10 10 1 NO 0 21 +> rows: 1 + +ALTER SEQUENCE SEQ NO MINVALUE NO MAXVALUE; +> ok + +SELECT SEQUENCE_NAME, START_VALUE, MINIMUM_VALUE, MAXIMUM_VALUE, INCREMENT, CYCLE_OPTION, BASE_VALUE, CACHE + FROM INFORMATION_SCHEMA.SEQUENCES; +> SEQUENCE_NAME START_VALUE MINIMUM_VALUE MAXIMUM_VALUE INCREMENT CYCLE_OPTION BASE_VALUE CACHE +> ------------- ----------- ------------- ------------------- --------- ------------ ---------- ----- +> SEQ 0 0 9223372036854775807 1 NO 0 21 +> rows: 1 + +ALTER SEQUENCE SEQ MINVALUE -100 MAXVALUE 100; +> ok + +SELECT SEQUENCE_NAME, START_VALUE, MINIMUM_VALUE, MAXIMUM_VALUE, INCREMENT, CYCLE_OPTION, BASE_VALUE, CACHE + FROM INFORMATION_SCHEMA.SEQUENCES; +> SEQUENCE_NAME START_VALUE MINIMUM_VALUE MAXIMUM_VALUE INCREMENT CYCLE_OPTION BASE_VALUE CACHE +> ------------- ----------- ------------- ------------- --------- ------------ ---------- ----- +> SEQ 0 -100 100 1 NO 0 21 +> rows: 1 + +VALUES NEXT VALUE FOR SEQ; +>> 0 + +ALTER SEQUENCE SEQ START WITH 10; +> ok + +SELECT SEQUENCE_NAME, START_VALUE, MINIMUM_VALUE, MAXIMUM_VALUE, INCREMENT, CYCLE_OPTION, BASE_VALUE, CACHE + FROM INFORMATION_SCHEMA.SEQUENCES; +> SEQUENCE_NAME START_VALUE MINIMUM_VALUE MAXIMUM_VALUE INCREMENT CYCLE_OPTION BASE_VALUE CACHE +> ------------- ----------- ------------- ------------- --------- ------------ ---------- ----- +> SEQ 10 -100 100 1 NO 1 21 +> rows: 1 + +VALUES NEXT VALUE FOR SEQ; +>> 1 + +ALTER SEQUENCE SEQ RESTART; +> ok + +VALUES NEXT VALUE FOR SEQ; +>> 10 + +ALTER SEQUENCE SEQ START WITH 5 RESTART WITH 20; +> ok + +VALUES NEXT VALUE FOR SEQ; +>> 20 + +@reconnect + +SELECT SEQUENCE_NAME, START_VALUE, MINIMUM_VALUE, MAXIMUM_VALUE, INCREMENT, CYCLE_OPTION, BASE_VALUE, CACHE + FROM INFORMATION_SCHEMA.SEQUENCES; +> SEQUENCE_NAME START_VALUE MINIMUM_VALUE MAXIMUM_VALUE INCREMENT CYCLE_OPTION BASE_VALUE CACHE +> ------------- ----------- ------------- ------------- --------- ------------ ---------- ----- +> SEQ 5 -100 100 1 NO 21 21 +> rows: 1 + +DROP SEQUENCE SEQ; +> ok + +CREATE SEQUENCE SEQ START WITH 10 RESTART WITH 20; +> ok + +VALUES NEXT VALUE FOR SEQ; +>> 20 + +DROP SEQUENCE SEQ; +> ok + +SET AUTOCOMMIT OFF; +> ok + +CREATE SEQUENCE SEQ; +> ok + +ALTER SEQUENCE SEQ RESTART WITH 1; +> ok + +SELECT NEXT VALUE FOR SEQ; +>> 1 + +DROP SEQUENCE SEQ; +> ok + +COMMIT; +> ok + +SET AUTOCOMMIT ON; +> ok + +CREATE SEQUENCE SEQ MINVALUE 1 MAXVALUE 10 INCREMENT BY -1; +> ok + +VALUES NEXT VALUE FOR SEQ, NEXT VALUE FOR SEQ; +> C1 +> -- +> 10 +> 9 +> rows: 2 + +ALTER SEQUENCE SEQ RESTART; +> ok + +VALUES NEXT VALUE FOR SEQ, NEXT VALUE FOR SEQ; +> C1 +> -- +> 10 +> 9 +> rows: 2 + +ALTER SEQUENCE SEQ RESTART WITH 1; +> ok + +VALUES NEXT VALUE FOR SEQ; +>> 1 + +VALUES NEXT VALUE FOR SEQ; +> exception SEQUENCE_EXHAUSTED + +DROP SEQUENCE SEQ; +> ok diff --git a/h2/src/test/org/h2/test/scripts/ddl/createSynonym.sql b/h2/src/test/org/h2/test/scripts/ddl/createSynonym.sql new file mode 100644 index 0000000..b359f38 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/createSynonym.sql @@ -0,0 +1,52 @@ +-- 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 +-- + +CREATE SCHEMA SCHEMA1; +> ok + +CREATE SCHEMA SCHEMA2; +> ok + +CREATE TABLE SCHEMA1.T1(K BIGINT PRIMARY KEY, V VARCHAR); +> ok + +CREATE SYNONYM SCHEMA1.T1 FOR SCHEMA1.T1; +> exception TABLE_OR_VIEW_ALREADY_EXISTS_1 + +CREATE SYNONYM SCHEMA2.T1 FOR SCHEMA1.T1; +> ok + +DROP SYNONYM SCHEMA2.T1; +> ok + +SET SCHEMA SCHEMA2; +> ok + +CREATE SYNONYM T1 FOR SCHEMA1.T1; +> ok + +DROP SYNONYM T1; +> ok + +SET SCHEMA SCHEMA1; +> ok + +CREATE SYNONYM T1 FOR T1; +> exception TABLE_OR_VIEW_ALREADY_EXISTS_1 + +CREATE SYNONYM SCHEMA2.T1 FOR T1; +> ok + +DROP SYNONYM SCHEMA2.T1; +> ok + +SET SCHEMA PUBLIC; +> ok + +DROP SCHEMA SCHEMA2 CASCADE; +> ok + +DROP SCHEMA SCHEMA1 CASCADE; +> 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 new file mode 100644 index 0000000..0be5385 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/createTable.sql @@ -0,0 +1,279 @@ +-- 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 +-- + +CREATE TABLE TEST(A INT CONSTRAINT PK_1 PRIMARY KEY); +> ok + +SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS; +> CONSTRAINT_NAME CONSTRAINT_TYPE +> --------------- --------------- +> PK_1 PRIMARY KEY +> rows: 1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID IDENTITY, CONSTRAINT PK_1 PRIMARY KEY(ID)); +> ok + +SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS; +> CONSTRAINT_NAME CONSTRAINT_TYPE +> --------------- --------------- +> PK_1 PRIMARY KEY +> rows: 1 + +DROP TABLE TEST; +> ok + +CREATE TABLE T1(ID INT PRIMARY KEY, COL2 INT); +> ok + +INSERT INTO T1 VALUES (1, 2), (11, 22); +> update count: 2 + +CREATE TABLE T2 AS SELECT * FROM T1; +> ok + +SELECT * FROM T2 ORDER BY ID; +> ID COL2 +> -- ---- +> 1 2 +> 11 22 +> rows (ordered): 2 + +DROP TABLE T2; +> ok + +CREATE TABLE T2 AS SELECT * FROM T1 WITH DATA; +> ok + +SELECT * FROM T2 ORDER BY ID; +> ID COL2 +> -- ---- +> 1 2 +> 11 22 +> rows (ordered): 2 + +DROP TABLE T2; +> ok + +CREATE TABLE T2 AS SELECT * FROM T1 WITH NO DATA; +> ok + +SELECT * FROM T2 ORDER BY ID; +> ID COL2 +> -- ---- +> rows (ordered): 0 + +DROP TABLE T2; +> ok + +DROP TABLE T1; +> ok + +CREATE TABLE TEST(A INT, B INT INVISIBLE); +> ok + +SELECT * FROM TEST; +> A +> - +> rows: 0 + +SELECT A, B FROM TEST; +> A B +> - - +> rows: 0 + +SELECT COLUMN_NAME, IS_VISIBLE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME IS_VISIBLE +> ----------- ---------- +> A TRUE +> B FALSE +> rows (ordered): 2 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST1(ID IDENTITY); +> ok + +CREATE TABLE TEST2(ID BIGINT GENERATED BY DEFAULT AS IDENTITY); +> ok + +SELECT CONSTRAINT_TYPE, TABLE_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_SCHEMA = 'PUBLIC'; +> CONSTRAINT_TYPE TABLE_NAME +> --------------- ---------- +> PRIMARY KEY TEST1 +> rows: 1 + +DROP TABLE TEST1, TEST2; +> ok + +CREATE TABLE TEST(A); +> exception UNKNOWN_DATA_TYPE_1 + +CREATE TABLE TEST(A, B, C) AS SELECT 1, 2, CAST ('A' AS VARCHAR); +> ok + +SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST'; +> COLUMN_NAME DATA_TYPE +> ----------- ----------------- +> A INTEGER +> B INTEGER +> C CHARACTER VARYING +> rows: 3 + +DROP TABLE TEST; +> ok + +CREATE MEMORY TABLE TEST(A INT, B INT GENERATED ALWAYS AS (1), C INT GENERATED ALWAYS AS (B + 1)); +> exception COLUMN_NOT_FOUND_1 + +CREATE MEMORY TABLE TEST(A INT, B INT GENERATED ALWAYS AS (1), C INT GENERATED ALWAYS AS (A + 1)); +> ok + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION TABLE TEST; +> SCRIPT +> ----------------------------------------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "A" INTEGER, "B" INTEGER GENERATED ALWAYS AS (1), "C" INTEGER GENERATED ALWAYS AS ("A" + 1) ); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> rows (ordered): 3 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INT GENERATED BY DEFAULT AS (1)); +> exception SYNTAX_ERROR_2 + +CREATE TABLE TEST(A IDENTITY GENERATED ALWAYS AS (1)); +> exception SYNTAX_ERROR_2 + +CREATE TABLE TEST(A IDENTITY AS (1)); +> exception SYNTAX_ERROR_2 + +CREATE TABLE TEST1(ID BIGINT GENERATED ALWAYS AS IDENTITY); +> ok + +CREATE TABLE TEST2(ID BIGINT GENERATED BY DEFAULT AS IDENTITY); +> ok + +CREATE TABLE TEST3(ID BIGINT NULL GENERATED ALWAYS AS IDENTITY); +> exception COLUMN_MUST_NOT_BE_NULLABLE_1 + +CREATE TABLE TEST3(ID BIGINT GENERATED BY DEFAULT AS IDENTITY NULL); +> exception COLUMN_MUST_NOT_BE_NULLABLE_1 + +SELECT COLUMN_NAME, IDENTITY_GENERATION, IS_NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'PUBLIC'; +> COLUMN_NAME IDENTITY_GENERATION IS_NULLABLE +> ----------- ------------------- ----------- +> ID ALWAYS NO +> ID BY DEFAULT NO +> rows: 2 + +DROP TABLE TEST1, TEST2; +> ok + +CREATE TABLE TEST(ID BIGINT GENERATED ALWAYS AS IDENTITY(MINVALUE 1 MAXVALUE 2), V INT); +> ok + +INSERT INTO TEST(V) VALUES 1; +> update count: 1 + +SELECT IDENTITY_BASE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST' AND COLUMN_NAME = 'ID'; +>> 2 + +INSERT INTO TEST(V) VALUES 2; +> update count: 1 + +SELECT IDENTITY_BASE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST' AND COLUMN_NAME = 'ID'; +>> null + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, V INT); +> ok + +INSERT INTO TEST(V) VALUES 1; +> update count: 1 + +INSERT INTO TEST VALUES (2, 2); +> update count: 1 + +INSERT INTO TEST(V) VALUES 3; +> exception DUPLICATE_KEY_1 + +TABLE TEST; +> ID V +> -- - +> 1 1 +> 2 2 +> rows: 2 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST1(R BIGINT GENERATED BY DEFAULT AS IDENTITY); +> ok + +SET MODE HSQLDB; +> ok + +CREATE TABLE TEST2(M BIGINT GENERATED BY DEFAULT AS IDENTITY); +> ok + +SET MODE MySQL; +> ok + +CREATE TABLE TEST3(H BIGINT GENERATED BY DEFAULT AS IDENTITY); +> ok + +SET MODE Regular; +> ok + +SELECT COLUMN_NAME, DEFAULT_ON_NULL FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'PUBLIC'; +> COLUMN_NAME DEFAULT_ON_NULL +> ----------- --------------- +> H TRUE +> M TRUE +> R FALSE +> rows: 3 + +DROP TABLE TEST1, TEST2, TEST3; +> ok + +EXECUTE IMMEDIATE 'CREATE TABLE TEST(' || (SELECT LISTAGG('C' || X || ' INT') FROM SYSTEM_RANGE(1, 16384)) || ')'; +> ok + +DROP TABLE TEST; +> ok + +EXECUTE IMMEDIATE 'CREATE TABLE TEST(' || (SELECT LISTAGG('C' || X || ' INT') FROM SYSTEM_RANGE(1, 16385)) || ')'; +> exception TOO_MANY_COLUMNS_1 + +CREATE TABLE TEST AS (SELECT REPEAT('A', 300)); +> ok + +TABLE TEST; +> C1 +> ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +> rows: 1 + +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/ddl/createTrigger.sql b/h2/src/test/org/h2/test/scripts/ddl/createTrigger.sql new file mode 100644 index 0000000..6722635 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/createTrigger.sql @@ -0,0 +1,230 @@ +-- 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 +-- + +CREATE TABLE COUNT(X INT); +> ok + +CREATE FORCE TRIGGER T_COUNT BEFORE INSERT ON COUNT CALL 'com.Unknown'; +> ok + +INSERT INTO COUNT VALUES(NULL); +> exception ERROR_CREATING_TRIGGER_OBJECT_3 + +DROP TRIGGER T_COUNT; +> ok + +CREATE TABLE ITEMS(ID INT CHECK ID < SELECT MAX(ID) FROM COUNT); +> ok + +insert into items values(DEFAULT); +> update count: 1 + +DROP TABLE COUNT; +> exception CANNOT_DROP_2 + +insert into items values(DEFAULT); +> update count: 1 + +drop table items, count; +> ok + +CREATE TABLE TEST(A VARCHAR, B VARCHAR, C VARCHAR); +> ok + +CREATE TRIGGER T1 BEFORE INSERT, UPDATE ON TEST FOR EACH ROW CALL 'org.h2.test.scripts.Trigger1'; +> ok + +INSERT INTO TEST VALUES ('a', 'b', 'c'); +> exception ERROR_EXECUTING_TRIGGER_3 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A VARCHAR, B VARCHAR, C INT); +> ok + +CREATE TRIGGER T1 BEFORE INSERT ON TEST FOR EACH ROW CALL 'org.h2.test.scripts.Trigger1'; +> ok + +INSERT INTO TEST VALUES ('1', 'a', 1); +> update count: 1 + +DROP TRIGGER T1; +> ok + +CREATE TRIGGER T1 BEFORE INSERT ON TEST FOR EACH STATEMENT CALL 'org.h2.test.scripts.Trigger1'; +> ok + +INSERT INTO TEST VALUES ('2', 'b', 2); +> update count: 1 + +DROP TRIGGER T1; +> ok + +TABLE TEST; +> A B C +> - - -- +> 1 a 10 +> 2 b 2 +> rows: 2 + +DROP TABLE TEST; +> ok + +-- --------------------------------------------------------------------------- +-- Checking multiple classes in trigger source +-- --------------------------------------------------------------------------- + +CREATE TABLE TEST(A VARCHAR, B VARCHAR, C VARCHAR); +> ok + +CREATE TRIGGER T1 BEFORE INSERT, UPDATE ON TEST FOR EACH ROW AS STRINGDECODE( +'org.h2.api.Trigger create() { + return new org.h2.api.Trigger() { + public void fire(Connection conn, Object[] oldRow, Object[] newRow) { + if (newRow != null) { + newRow[2] = newRow[2] + "1"\u003B + } + } + }\u003B +}'); +> ok + +INSERT INTO TEST VALUES ('a', 'b', 'c'); +> update count: 1 + +TABLE TEST; +> A B C +> - - -- +> a b c1 +> rows: 1 + +DROP TABLE TEST; +> ok + +-- --------------------------------------------------------------------------- +-- PostgreSQL syntax tests +-- --------------------------------------------------------------------------- + +set mode postgresql; +> ok + +CREATE TABLE COUNT(X INT); +> ok + +INSERT INTO COUNT VALUES(1); +> update count: 1 + +CREATE FORCE TRIGGER T_COUNT BEFORE INSERT OR UPDATE ON COUNT CALL 'com.Unknown'; +> ok + +INSERT INTO COUNT VALUES(NULL); +> exception ERROR_CREATING_TRIGGER_OBJECT_3 + +UPDATE COUNT SET X=2 WHERE X=1; +> exception ERROR_CREATING_TRIGGER_OBJECT_3 + +DROP TABLE COUNT; +> ok + +SET MODE Regular; +> ok + +CREATE MEMORY TABLE T(ID INT PRIMARY KEY, V INT); +> ok + +CREATE VIEW V1 AS TABLE T; +> ok + +CREATE VIEW V2 AS TABLE T; +> ok + +CREATE VIEW V3 AS TABLE T; +> ok + +CREATE TRIGGER T1 INSTEAD OF INSERT ON V1 FOR EACH ROW AS STRINGDECODE( +'org.h2.api.Trigger create() { + return new org.h2.api.Trigger() { + public void fire(Connection conn, Object[] oldRow, Object[] newRow) { + } + }\u003B +}'); +> ok + +CREATE TRIGGER T2 INSTEAD OF UPDATE ON V2 FOR EACH ROW AS STRINGDECODE( +'org.h2.api.Trigger create() { + return new org.h2.api.Trigger() { + public void fire(Connection conn, Object[] oldRow, Object[] newRow) { + } + }\u003B +}'); +> ok + +CREATE TRIGGER T3 INSTEAD OF DELETE ON V3 FOR EACH ROW AS STRINGDECODE( +'org.h2.api.Trigger create() { + return new org.h2.api.Trigger() { + public void fire(Connection conn, Object[] oldRow, Object[] newRow) { + } + }\u003B +}'); +> ok + +SELECT TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE, IS_INSERTABLE_INTO, COMMIT_ACTION + FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'PUBLIC'; +> TABLE_CATALOG TABLE_SCHEMA TABLE_NAME TABLE_TYPE IS_INSERTABLE_INTO COMMIT_ACTION +> ------------- ------------ ---------- ---------- ------------------ ------------- +> SCRIPT PUBLIC T BASE TABLE YES null +> SCRIPT PUBLIC V1 VIEW NO null +> SCRIPT PUBLIC V2 VIEW NO null +> SCRIPT PUBLIC V3 VIEW NO null +> rows: 4 + +SELECT TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, VIEW_DEFINITION, CHECK_OPTION, IS_UPDATABLE, INSERTABLE_INTO, + IS_TRIGGER_UPDATABLE, IS_TRIGGER_DELETABLE, IS_TRIGGER_INSERTABLE_INTO + FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = 'PUBLIC'; +> TABLE_CATALOG TABLE_SCHEMA TABLE_NAME VIEW_DEFINITION CHECK_OPTION IS_UPDATABLE INSERTABLE_INTO IS_TRIGGER_UPDATABLE IS_TRIGGER_DELETABLE IS_TRIGGER_INSERTABLE_INTO +> ------------- ------------ ---------- ------------------ ------------ ------------ --------------- -------------------- -------------------- -------------------------- +> SCRIPT PUBLIC V1 TABLE "PUBLIC"."T" NONE NO NO NO NO YES +> SCRIPT PUBLIC V2 TABLE "PUBLIC"."T" NONE NO NO YES NO NO +> SCRIPT PUBLIC V3 TABLE "PUBLIC"."T" NONE NO NO NO YES NO +> rows: 3 + +SELECT * FROM INFORMATION_SCHEMA.TRIGGERS; +> TRIGGER_CATALOG TRIGGER_SCHEMA TRIGGER_NAME EVENT_MANIPULATION EVENT_OBJECT_CATALOG EVENT_OBJECT_SCHEMA EVENT_OBJECT_TABLE ACTION_ORIENTATION ACTION_TIMING IS_ROLLBACK JAVA_CLASS QUEUE_SIZE NO_WAIT REMARKS +> --------------- -------------- ------------ ------------------ -------------------- ------------------- ------------------ ------------------ ------------- ----------- ---------- ---------- ------- ------- +> SCRIPT PUBLIC T1 INSERT SCRIPT PUBLIC V1 ROW INSTEAD OF FALSE null 1024 FALSE null +> SCRIPT PUBLIC T2 UPDATE SCRIPT PUBLIC V2 ROW INSTEAD OF FALSE null 1024 FALSE null +> SCRIPT PUBLIC T3 DELETE SCRIPT PUBLIC V3 ROW INSTEAD OF FALSE null 1024 FALSE null +> rows: 3 + +CREATE TRIGGER T4 BEFORE ROLLBACK ON TEST FOR EACH ROW AS STRINGDECODE( +'org.h2.api.Trigger create() { + return new org.h2.api.Trigger() { + public void fire(Connection conn, Object[] oldRow, Object[] newRow) { + } + }\u003B +}'); +> exception INVALID_TRIGGER_FLAGS_1 + +CREATE TRIGGER T4 BEFORE SELECT ON TEST FOR EACH ROW AS STRINGDECODE( +'org.h2.api.Trigger create() { + return new org.h2.api.Trigger() { + public void fire(Connection conn, Object[] oldRow, Object[] newRow) { + } + }\u003B +}'); +> exception INVALID_TRIGGER_FLAGS_1 + +CREATE TRIGGER T4 BEFORE SELECT, ROLLBACK ON TEST FOR EACH STATEMENT AS STRINGDECODE( +'org.h2.api.Trigger create() { + return new org.h2.api.Trigger() { + public void fire(Connection conn, Object[] oldRow, Object[] newRow) { + } + }\u003B +}'); +> exception INVALID_TRIGGER_FLAGS_1 + +DROP TABLE T CASCADE; +> ok diff --git a/h2/src/test/org/h2/test/scripts/ddl/createView.sql b/h2/src/test/org/h2/test/scripts/ddl/createView.sql new file mode 100644 index 0000000..b049555 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/createView.sql @@ -0,0 +1,54 @@ +-- 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 +-- + +CREATE VIEW TEST_VIEW(A) AS SELECT 'a'; +> ok + +CREATE OR REPLACE VIEW TEST_VIEW(B, C) AS SELECT 'b', 'c'; +> ok + +SELECT * FROM TEST_VIEW; +> B C +> - - +> b c +> rows: 1 + +SELECT TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, VIEW_DEFINITION, CHECK_OPTION, IS_UPDATABLE, STATUS, REMARKS + FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = 'TEST_VIEW'; +> TABLE_CATALOG TABLE_SCHEMA TABLE_NAME VIEW_DEFINITION CHECK_OPTION IS_UPDATABLE STATUS REMARKS +> ------------- ------------ ---------- --------------- ------------ ------------ ------ ------- +> SCRIPT PUBLIC TEST_VIEW SELECT 'b', 'c' NONE NO VALID null +> rows: 1 + +DROP VIEW TEST_VIEW; +> ok + +CREATE TABLE TEST(C1 INT) AS (VALUES 1, 2); +> ok + +CREATE OR REPLACE VIEW TEST_VIEW AS (SELECT C1 AS A FROM TEST); +> ok + +ALTER TABLE TEST ADD COLUMN C2 INT; +> ok + +UPDATE TEST SET C2 = C1 + 1; +> update count: 2 + +CREATE OR REPLACE VIEW TEST_VIEW AS (SELECT C1 AS A, C2 AS B FROM TEST); +> ok + +CREATE OR REPLACE VIEW TEST_VIEW AS (SELECT C2 AS B, C1 AS A FROM TEST); +> ok + +SELECT * FROM TEST_VIEW; +> B A +> - - +> 2 1 +> 3 2 +> rows: 2 + +DROP TABLE TEST CASCADE; +> ok diff --git a/h2/src/test/org/h2/test/scripts/ddl/dropAllObjects.sql b/h2/src/test/org/h2/test/scripts/ddl/dropAllObjects.sql new file mode 100644 index 0000000..2d570e5 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/dropAllObjects.sql @@ -0,0 +1,61 @@ +-- 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 +-- + +@reconnect off + +-- Test table depends on view + +create table a(x int); +> ok + +create view b as select * from a; +> ok + +create table c(y int check (select count(*) from b) = 0); +> ok + +drop all objects; +> ok + +-- Test inter-schema dependency + +create schema table_view; +> ok + +set schema table_view; +> ok + +create table test1 (id int, name varchar(20)); +> ok + +create view test_view_1 as (select * from test1); +> ok + +set schema public; +> ok + +create schema test_run; +> ok + +set schema test_run; +> ok + +create table test2 (id int, address varchar(20), constraint a_cons check (id in (select id from table_view.test1))); +> ok + +set schema public; +> ok + +drop all objects; +> ok + +CREATE DOMAIN D INT; +> ok + +DROP ALL OBJECTS; +> ok + +SELECT COUNT(*) FROM INFORMATION_SCHEMA.DOMAINS WHERE DOMAIN_SCHEMA = 'PUBLIC'; +>> 0 diff --git a/h2/src/test/org/h2/test/scripts/ddl/dropDomain.sql b/h2/src/test/org/h2/test/scripts/ddl/dropDomain.sql new file mode 100644 index 0000000..2fc644b --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/dropDomain.sql @@ -0,0 +1,76 @@ +-- 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 +-- + +CREATE DOMAIN E AS ENUM('A', 'B'); +> ok + +CREATE TABLE TEST(I INT PRIMARY KEY, E1 E, E2 E NOT NULL); +> ok + +INSERT INTO TEST VALUES (1, 'A', 'B'); +> update count: 1 + +SELECT COLUMN_NAME, DOMAIN_CATALOG, DOMAIN_SCHEMA, DOMAIN_NAME, IS_NULLABLE, DATA_TYPE + FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DOMAIN_CATALOG DOMAIN_SCHEMA DOMAIN_NAME IS_NULLABLE DATA_TYPE +> ----------- -------------- ------------- ----------- ----------- --------- +> I null null null NO INTEGER +> E1 SCRIPT PUBLIC E YES ENUM +> E2 SCRIPT PUBLIC E NO ENUM +> rows (ordered): 3 + +DROP DOMAIN E RESTRICT; +> exception CANNOT_DROP_2 + +DROP DOMAIN E CASCADE; +> ok + +SELECT COLUMN_NAME, DOMAIN_CATALOG, DOMAIN_SCHEMA, DOMAIN_NAME, IS_NULLABLE, DATA_TYPE + FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DOMAIN_CATALOG DOMAIN_SCHEMA DOMAIN_NAME IS_NULLABLE DATA_TYPE +> ----------- -------------- ------------- ----------- ----------- --------- +> I null null null NO INTEGER +> E1 null null null YES ENUM +> E2 null null null NO ENUM +> rows (ordered): 3 + +DROP TABLE TEST; +> ok + +CREATE DOMAIN D INT CHECK (VALUE > 0); +> ok + +CREATE MEMORY TABLE TEST(C D); +> ok + +DROP DOMAIN D CASCADE; +> ok + +INSERT INTO TEST VALUES 1; +> update count: 1 + +INSERT INTO TEST VALUES -1; +> exception CHECK_CONSTRAINT_VIOLATED_1 + +@reconnect + +INSERT INTO TEST VALUES 1; +> update count: 1 + +INSERT INTO TEST VALUES -1; +> exception CHECK_CONSTRAINT_VIOLATED_1 + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION TABLE TEST; +> SCRIPT +> ------------------------------------------------------------------------------------------ +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "C" INTEGER ); +> -- 2 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> INSERT INTO "PUBLIC"."TEST" VALUES (1), (1); +> ALTER TABLE "PUBLIC"."TEST" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_2" CHECK("C" > 0) NOCHECK; +> rows (ordered): 5 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/ddl/dropIndex.sql b/h2/src/test/org/h2/test/scripts/ddl/dropIndex.sql new file mode 100644 index 0000000..a933bb5 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/dropIndex.sql @@ -0,0 +1,75 @@ +-- 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 +-- + +CREATE SCHEMA TEST; +> ok + +CREATE TABLE TEST.TBL ( + NAME VARCHAR +); +> ok + +CREATE UNIQUE INDEX NAME_INDEX ON TEST.TBL(NAME); +> ok + +SET MODE MySQL; +> ok + +-- MySQL compatibility syntax +ALTER TABLE TEST.TBL DROP INDEX NAME_INDEX; +> ok + +CREATE UNIQUE INDEX NAME_INDEX ON TEST.TBL(NAME); +> ok + +-- MySQL compatibility syntax +ALTER TABLE TEST.TBL DROP INDEX TEST.NAME_INDEX; +> ok + +ALTER TABLE TEST.TBL ADD CONSTRAINT NAME_INDEX UNIQUE (NAME); +> ok + +-- MySQL compatibility syntax +ALTER TABLE TEST.TBL DROP INDEX NAME_INDEX; +> ok + +ALTER TABLE TEST.TBL ADD CONSTRAINT NAME_INDEX UNIQUE (NAME); +> ok + +-- MySQL compatibility syntax +ALTER TABLE TEST.TBL DROP INDEX TEST.NAME_INDEX; +> ok + +DROP SCHEMA TEST CASCADE; +> ok + +create table test(id int primary key, name varchar); +> ok + +alter table test alter column id int auto_increment; +> ok + +create table otherTest(id int primary key, name varchar); +> ok + +alter table otherTest add constraint fk foreign key(id) references test(id); +> ok + +-- MySQL compatibility syntax +alter table otherTest drop foreign key fk; +> ok + +create unique index idx on otherTest(name); +> ok + +-- MySQL compatibility syntax +alter table otherTest drop index idx; +> ok + +drop table test, otherTest; +> ok + +SET MODE Regular; +> ok diff --git a/h2/src/test/org/h2/test/scripts/ddl/dropSchema.sql b/h2/src/test/org/h2/test/scripts/ddl/dropSchema.sql new file mode 100644 index 0000000..4285f88 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/dropSchema.sql @@ -0,0 +1,149 @@ +-- 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 +-- + +@reconnect off + +CREATE SCHEMA TEST_SCHEMA; +> ok + +DROP SCHEMA TEST_SCHEMA RESTRICT; +> ok + +CREATE SCHEMA TEST_SCHEMA; +> ok + +CREATE TABLE TEST_SCHEMA.TEST(); +> ok + +DROP SCHEMA TEST_SCHEMA RESTRICT; +> exception CANNOT_DROP_2 + +DROP SCHEMA TEST_SCHEMA CASCADE; +> ok + +CREATE SCHEMA TEST_SCHEMA; +> ok + +CREATE VIEW TEST_SCHEMA.TEST AS SELECT 1; +> ok + +DROP SCHEMA TEST_SCHEMA RESTRICT; +> exception CANNOT_DROP_2 + +DROP SCHEMA TEST_SCHEMA CASCADE; +> ok + +CREATE TABLE PUBLIC.SRC(); +> ok + +CREATE SCHEMA TEST_SCHEMA; +> ok + +CREATE SYNONYM TEST_SCHEMA.TEST FOR PUBLIC.SRC; +> ok + +DROP SCHEMA TEST_SCHEMA RESTRICT; +> exception CANNOT_DROP_2 + +DROP SCHEMA TEST_SCHEMA CASCADE; +> ok + +DROP TABLE PUBLIC.SRC; +> ok + +CREATE SCHEMA TEST_SCHEMA; +> ok + +CREATE SEQUENCE TEST_SCHEMA.TEST; +> ok + +DROP SCHEMA TEST_SCHEMA RESTRICT; +> exception CANNOT_DROP_2 + +DROP SCHEMA TEST_SCHEMA CASCADE; +> ok + +CREATE SCHEMA TEST_SCHEMA; +> ok + +CREATE CONSTANT TEST_SCHEMA.TEST VALUE 1; +> ok + +DROP SCHEMA TEST_SCHEMA RESTRICT; +> exception CANNOT_DROP_2 + +DROP SCHEMA TEST_SCHEMA CASCADE; +> ok + +CREATE SCHEMA TEST_SCHEMA; +> ok + +CREATE ALIAS TEST_SCHEMA.TEST FOR "java.lang.System.currentTimeMillis"; +> ok + +DROP SCHEMA TEST_SCHEMA RESTRICT; +> exception CANNOT_DROP_2 + +DROP SCHEMA TEST_SCHEMA CASCADE; +> ok + +-- Test computed column dependency + +CREATE TABLE A (A INT); +> ok + +CREATE TABLE B (B INT AS SELECT A FROM A); +> ok + +DROP ALL OBJECTS; +> ok + +CREATE SCHEMA TEST_SCHEMA; +> ok + +CREATE TABLE TEST_SCHEMA.A (A INT); +> ok + +CREATE TABLE TEST_SCHEMA.B (B INT AS SELECT A FROM TEST_SCHEMA.A); +> ok + +DROP SCHEMA TEST_SCHEMA CASCADE; +> ok + +CREATE SCHEMA A; +> ok + +CREATE TABLE A.A1(ID INT); +> ok + +CREATE SCHEMA B; +> ok + +CREATE TABLE B.B1(ID INT, X INT DEFAULT (SELECT MAX(ID) FROM A.A1)); +> ok + +DROP SCHEMA A CASCADE; +> exception CANNOT_DROP_2 + +DROP SCHEMA B CASCADE; +> ok + +DROP SCHEMA A CASCADE; +> ok + +CREATE SCHEMA A; +> ok + +CREATE TABLE A.A1(ID INT, X INT); +> ok + +CREATE TABLE A.A2(ID INT, X INT DEFAULT (SELECT MAX(ID) FROM A.A1)); +> ok + +ALTER TABLE A.A1 ALTER COLUMN X SET DEFAULT (SELECT MAX(ID) FROM A.A2); +> ok + +DROP SCHEMA A CASCADE; +> ok diff --git a/h2/src/test/org/h2/test/scripts/ddl/dropTable.sql b/h2/src/test/org/h2/test/scripts/ddl/dropTable.sql new file mode 100644 index 0000000..05a606a --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/dropTable.sql @@ -0,0 +1,64 @@ +-- 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 +-- + +CREATE TABLE T1(ID1 INT PRIMARY KEY, ID2 INT); +> ok + +CREATE TABLE T2(ID2 INT, ID1 INT); +> ok + +ALTER TABLE T2 ADD CONSTRAINT C1 FOREIGN KEY(ID1) REFERENCES T1(ID1); +> ok + +DROP TABLE T1 RESTRICT; +> exception CANNOT_DROP_2 + +DROP TABLE T1 CASCADE; +> ok + +CREATE TABLE T1(ID1 INT PRIMARY KEY, ID2 INT); +> ok + +ALTER TABLE T2 ADD CONSTRAINT C1 FOREIGN KEY(ID1) REFERENCES T1(ID1); +> ok + +DROP TABLE T2 RESTRICT; +> ok + +CREATE VIEW V1 AS SELECT * FROM T1; +> ok + +DROP TABLE T1 RESTRICT; +> exception CANNOT_DROP_2 + +DROP TABLE T1 CASCADE; +> ok + +SELECT * FROM V1; +> exception TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1 + +CREATE TABLE T1(ID1 INT); +> ok + +ALTER TABLE T1 ADD CONSTRAINT C1 CHECK ID1 > 0; +> ok + +DROP TABLE T1 RESTRICT; +> ok + +CREATE TABLE T1(ID1 INT PRIMARY KEY, ID2 INT); +> ok + +CREATE TABLE T2(ID2 INT PRIMARY KEY, ID1 INT); +> ok + +ALTER TABLE T2 ADD CONSTRAINT C1 FOREIGN KEY(ID1) REFERENCES T1(ID1); +> ok + +ALTER TABLE T1 ADD CONSTRAINT C2 FOREIGN KEY(ID2) REFERENCES T2(ID2); +> ok + +DROP TABLE T1, T2 RESTRICT; +> ok diff --git a/h2/src/test/org/h2/test/scripts/ddl/grant.sql b/h2/src/test/org/h2/test/scripts/ddl/grant.sql new file mode 100644 index 0000000..e3b7e15 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/grant.sql @@ -0,0 +1,57 @@ +-- 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 +-- + +CREATE MEMORY TABLE TEST1(ID BIGINT PRIMARY KEY); +> ok + +CREATE MEMORY TABLE TEST2(ID BIGINT PRIMARY KEY); +> ok + +CREATE USER TEST_USER PASSWORD 'test'; +> ok + +GRANT SELECT, INSERT ON TEST1, TEST2 TO TEST_USER; +> ok + +SCRIPT NODATA NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> --------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE USER IF NOT EXISTS "TEST_USER" PASSWORD ''; +> CREATE MEMORY TABLE "PUBLIC"."TEST1"( "ID" BIGINT NOT NULL ); +> ALTER TABLE "PUBLIC"."TEST1" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_4" PRIMARY KEY("ID"); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST1; +> CREATE MEMORY TABLE "PUBLIC"."TEST2"( "ID" BIGINT NOT NULL ); +> ALTER TABLE "PUBLIC"."TEST2" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_4C" PRIMARY KEY("ID"); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST2; +> GRANT SELECT, INSERT ON "PUBLIC"."TEST1" TO "TEST_USER"; +> GRANT SELECT, INSERT ON "PUBLIC"."TEST2" TO "TEST_USER"; +> rows (ordered): 10 + +REVOKE INSERT ON TEST1 FROM TEST_USER; +> ok + +REVOKE ALL ON TEST2 FROM TEST_USER; +> ok + +SCRIPT NODATA NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> --------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE USER IF NOT EXISTS "TEST_USER" PASSWORD ''; +> CREATE MEMORY TABLE "PUBLIC"."TEST1"( "ID" BIGINT NOT NULL ); +> ALTER TABLE "PUBLIC"."TEST1" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_4" PRIMARY KEY("ID"); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST1; +> CREATE MEMORY TABLE "PUBLIC"."TEST2"( "ID" BIGINT NOT NULL ); +> ALTER TABLE "PUBLIC"."TEST2" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_4C" PRIMARY KEY("ID"); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST2; +> GRANT SELECT ON "PUBLIC"."TEST1" TO "TEST_USER"; +> rows (ordered): 9 + +DROP USER TEST_USER; +> ok + +DROP TABLE TEST1, TEST2; +> ok diff --git a/h2/src/test/org/h2/test/scripts/ddl/truncateTable.sql b/h2/src/test/org/h2/test/scripts/ddl/truncateTable.sql new file mode 100644 index 0000000..0ac0093 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/ddl/truncateTable.sql @@ -0,0 +1,189 @@ +-- 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 +-- + +create table FOO(id integer primary key); +> ok + +create table BAR(fooId integer); +> ok + +alter table bar add foreign key (fooId) references foo (id); +> ok + +truncate table bar; +> ok + +truncate table foo; +> exception CANNOT_TRUNCATE_1 + +drop table bar, foo; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR); +> ok + +INSERT INTO TEST VALUES(1, 'Hello'), (2, 'World'); +> update count: 2 + +TRUNCATE TABLE TEST; +> update count: 2 + +SELECT * FROM TEST; +> ID NAME +> -- ---- +> rows: 0 + +DROP TABLE TEST; +> ok + +CREATE TABLE PARENT(ID INT PRIMARY KEY, NAME VARCHAR); +> ok + +CREATE TABLE CHILD(PARENTID INT, FOREIGN KEY(PARENTID) REFERENCES PARENT(ID), NAME VARCHAR); +> ok + +TRUNCATE TABLE CHILD; +> ok + +TRUNCATE TABLE PARENT; +> exception CANNOT_TRUNCATE_1 + +DROP TABLE CHILD; +> ok + +DROP TABLE PARENT; +> ok + +CREATE SEQUENCE SEQ2; +> ok + +CREATE SEQUENCE SEQ3; +> ok + +CREATE TABLE TEST( + ID1 BIGINT AUTO_INCREMENT NOT NULL, + ID2 BIGINT NOT NULL DEFAULT NEXT VALUE FOR SEQ2 NULL_TO_DEFAULT SEQUENCE SEQ2, + ID3 BIGINT NOT NULL DEFAULT NEXT VALUE FOR SEQ3 NULL_TO_DEFAULT, + "VALUE" INT NOT NULL); +> ok + +INSERT INTO TEST("VALUE") VALUES (1), (2); +> update count: 2 + +SELECT * FROM TEST ORDER BY "VALUE"; +> ID1 ID2 ID3 VALUE +> --- --- --- ----- +> 1 1 1 1 +> 2 2 2 2 +> rows (ordered): 2 + +TRUNCATE TABLE TEST; +> update count: 2 + +INSERT INTO TEST("VALUE") VALUES (1), (2); +> update count: 2 + +SELECT * FROM TEST ORDER BY "VALUE"; +> ID1 ID2 ID3 VALUE +> --- --- --- ----- +> 3 3 3 1 +> 4 4 4 2 +> rows (ordered): 2 + +TRUNCATE TABLE TEST CONTINUE IDENTITY; +> update count: 2 + +INSERT INTO TEST("VALUE") VALUES (1), (2); +> update count: 2 + +SELECT * FROM TEST ORDER BY "VALUE"; +> ID1 ID2 ID3 VALUE +> --- --- --- ----- +> 5 5 5 1 +> 6 6 6 2 +> rows (ordered): 2 + +TRUNCATE TABLE TEST RESTART IDENTITY; +> update count: 2 + +INSERT INTO TEST("VALUE") VALUES (1), (2); +> update count: 2 + +SELECT * FROM TEST ORDER BY "VALUE"; +> ID1 ID2 ID3 VALUE +> --- --- --- ----- +> 1 1 7 1 +> 2 2 8 2 +> rows (ordered): 2 + +SET MODE MSSQLServer; +> ok + +TRUNCATE TABLE TEST; +> update count: 2 + +INSERT INTO TEST("VALUE") VALUES (1), (2); +> update count: 2 + +SELECT * FROM TEST ORDER BY "VALUE"; +> ID1 ID2 ID3 VALUE +> --- --- --- ----- +> 1 1 9 1 +> 2 2 10 2 +> rows (ordered): 2 + +SET MODE MySQL; +> ok + +TRUNCATE TABLE TEST; +> update count: 2 + +INSERT INTO TEST("VALUE") VALUES (1), (2); +> update count: 2 + +SELECT * FROM TEST ORDER BY "VALUE"; +> ID1 ID2 ID3 VALUE +> --- --- --- ----- +> 1 1 11 1 +> 2 2 12 2 +> rows (ordered): 2 + +SET MODE Regular; +> ok + +DROP TABLE TEST; +> ok + +DROP SEQUENCE SEQ3; +> ok + +CREATE TABLE TEST(ID INT GENERATED BY DEFAULT AS IDENTITY(MINVALUE 1 MAXVALUE 10 INCREMENT BY -1), V INT); +> ok + +INSERT INTO TEST(V) VALUES 1, 2; +> update count: 2 + +TABLE TEST; +> ID V +> -- - +> 10 1 +> 9 2 +> rows: 2 + +TRUNCATE TABLE TEST RESTART IDENTITY; +> update count: 2 + +INSERT INTO TEST(V) VALUES 1, 2; +> update count: 2 + +TABLE TEST; +> ID V +> -- - +> 10 1 +> 9 2 +> rows: 2 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/default-and-on_update.sql b/h2/src/test/org/h2/test/scripts/default-and-on_update.sql new file mode 100644 index 0000000..aeb2737 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/default-and-on_update.sql @@ -0,0 +1,111 @@ +-- 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 +-- + +CREATE SEQUENCE SEQ; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, V INT DEFAULT NEXT VALUE FOR SEQ ON UPDATE 1000 * NEXT VALUE FOR SEQ); +> ok + +INSERT INTO TEST(ID) VALUES (1), (2); +> update count: 2 + +SELECT * FROM TEST ORDER BY ID; +> ID V +> -- - +> 1 1 +> 2 2 +> rows (ordered): 2 + +UPDATE TEST SET ID = 3 WHERE ID = 2; +> update count: 1 + +SELECT * FROM TEST ORDER BY ID; +> ID V +> -- ---- +> 1 1 +> 3 3000 +> rows (ordered): 2 + +UPDATE TEST SET V = 3 WHERE ID = 3; +> update count: 1 + +SELECT * FROM TEST ORDER BY ID; +> ID V +> -- - +> 1 1 +> 3 3 +> rows (ordered): 2 + +ALTER TABLE TEST ADD V2 TIMESTAMP ON UPDATE CURRENT_TIMESTAMP; +> ok + +UPDATE TEST SET V = 4 WHERE ID = 3; +> update count: 1 + +SELECT ID, V, LENGTH(V2) > 18 AS L FROM TEST ORDER BY ID; +> ID V L +> -- - ---- +> 1 1 null +> 3 4 TRUE +> rows (ordered): 2 + +UPDATE TEST SET V = 1 WHERE V = 1; +> update count: 1 + +SELECT ID, V, LENGTH(V2) > 18 AS L FROM TEST ORDER BY ID; +> ID V L +> -- - ---- +> 1 1 null +> 3 4 TRUE +> rows (ordered): 2 + +MERGE INTO TEST(ID, V) KEY(ID) VALUES (1, 1); +> update count: 1 + +SELECT ID, V, LENGTH(V2) > 18 AS L FROM TEST ORDER BY ID; +> ID V L +> -- - ---- +> 1 1 null +> 3 4 TRUE +> rows (ordered): 2 + +MERGE INTO TEST(ID, V) KEY(ID) VALUES (1, 2); +> update count: 1 + +SELECT ID, V, LENGTH(V2) > 18 AS L FROM TEST ORDER BY ID; +> ID V L +> -- - ---- +> 1 2 TRUE +> 3 4 TRUE +> rows (ordered): 2 + +ALTER TABLE TEST ALTER COLUMN V SET ON UPDATE NULL; +> ok + +SELECT COLUMN_NAME, COLUMN_DEFAULT, COLUMN_ON_UPDATE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST' ORDER BY COLUMN_NAME; +> COLUMN_NAME COLUMN_DEFAULT COLUMN_ON_UPDATE +> ----------- ----------------------------- ----------------- +> ID null null +> V NEXT VALUE FOR "PUBLIC"."SEQ" NULL +> V2 null CURRENT_TIMESTAMP +> rows (ordered): 3 + +ALTER TABLE TEST ALTER COLUMN V DROP ON UPDATE; +> ok + +SELECT COLUMN_NAME, COLUMN_DEFAULT, COLUMN_ON_UPDATE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST' ORDER BY COLUMN_NAME; +> COLUMN_NAME COLUMN_DEFAULT COLUMN_ON_UPDATE +> ----------- ----------------------------- ----------------- +> ID null null +> V NEXT VALUE FOR "PUBLIC"."SEQ" null +> V2 null CURRENT_TIMESTAMP +> rows (ordered): 3 + +DROP TABLE TEST; +> ok + +DROP SEQUENCE SEQ; +> ok diff --git a/h2/src/test/org/h2/test/scripts/dml/delete.sql b/h2/src/test/org/h2/test/scripts/dml/delete.sql new file mode 100644 index 0000000..60a7f79 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/dml/delete.sql @@ -0,0 +1,101 @@ +-- 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 +-- + +CREATE TABLE TEST(ID INT); +> ok + +INSERT INTO TEST VALUES (1), (2), (3); +> update count: 3 + +DELETE FROM TEST WHERE EXISTS (SELECT X FROM SYSTEM_RANGE(1, 3) WHERE X = ID) AND ROWNUM() = 1; +> update count: 1 + +SELECT ID FROM TEST; +> ID +> -- +> 2 +> 3 +> rows: 2 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY) AS SELECT * FROM SYSTEM_RANGE(1, 13); +> ok + +DELETE FROM TEST WHERE ID <= 12 FETCH FIRST ROW ONLY; +> update count: 1 + +DELETE FROM TEST WHERE ID <= 12 FETCH FIRST ROWS ONLY; +> update count: 1 + +DELETE FROM TEST WHERE ID <= 12 FETCH NEXT ROW ONLY; +> update count: 1 + +DELETE FROM TEST WHERE ID <= 12 FETCH NEXT ROWS ONLY; +> update count: 1 + +DELETE FROM TEST WHERE ID <= 12 FETCH FIRST 2 ROW ONLY; +> update count: 2 + +DELETE FROM TEST WHERE ID <= 12 FETCH FIRST 2 ROWS ONLY; +> update count: 2 + +DELETE FROM TEST WHERE ID <= 12 FETCH NEXT 2 ROW ONLY; +> update count: 2 + +DELETE FROM TEST WHERE ID <= 12 FETCH NEXT 2 ROWS ONLY; +> update count: 2 + +EXPLAIN DELETE FROM TEST WHERE ID <= 12 FETCH FIRST 2 ROWS ONLY; +>> DELETE FROM "PUBLIC"."TEST" /* PUBLIC.PRIMARY_KEY_2: ID <= 12 */ WHERE "ID" <= 12 FETCH FIRST 2 ROWS ONLY + +EXPLAIN DELETE FROM TEST FETCH FIRST 1 ROW ONLY; +>> DELETE FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ FETCH FIRST ROW ONLY + +EXPLAIN DELETE FROM TEST; +>> DELETE FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +TABLE TEST; +>> 13 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(id int) AS SELECT x FROM system_range(1, 100); +> ok + +SET MODE MSSQLServer; +> ok + +DELETE TOP 10 FROM TEST; +> update count: 10 + +SET MODE Regular; +> ok + +SELECT COUNT(*) FROM TEST; +>> 90 + +DELETE FROM TEST LIMIT ((SELECT COUNT(*) FROM TEST) / 10); +> update count: 9 + +SELECT COUNT(*) FROM TEST; +>> 81 + +EXPLAIN DELETE FROM TEST LIMIT ((SELECT COUNT(*) FROM TEST) / 10); +>> DELETE FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ FETCH FIRST (SELECT COUNT(*) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ /* direct lookup */) / 10 ROWS ONLY + +DELETE FROM TEST LIMIT ?; +{ +10 +}; +> update count: 10 + +SELECT COUNT(*) FROM TEST; +>> 71 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/dml/error_reporting.sql b/h2/src/test/org/h2/test/scripts/dml/error_reporting.sql new file mode 100644 index 0000000..9da4297 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/dml/error_reporting.sql @@ -0,0 +1,43 @@ +-- 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 +-- + +SELECT 0x; +> exception SYNTAX_ERROR_2 + +SELECT 0xZ; +> exception SYNTAX_ERROR_2 + +SELECT 0xAAZ; +> exception SYNTAX_ERROR_2 + +SELECT 0x1LZ; +> exception SYNTAX_ERROR_2 + +SELECT 0x1234567890abZ; +> exception SYNTAX_ERROR_2 + +SELECT 0x1234567890abLZ; +> exception SYNTAX_ERROR_2 + +CREATE TABLE test (id INT NOT NULL, name VARCHAR); +> ok + +select * from test where id = ARRAY [1, 2]; +> exception TYPES_ARE_NOT_COMPARABLE_2 + +insert into test values (1, 't'); +> update count: 1 + +select * from test where id = (1, 2); +> exception TYPES_ARE_NOT_COMPARABLE_2 + +drop table test; +> ok + +SELECT 1 + 2 NOT; +> exception SYNTAX_ERROR_2 + +SELECT 1 NOT > 2; +> exception SYNTAX_ERROR_2 diff --git a/h2/src/test/org/h2/test/scripts/dml/execute_immediate.sql b/h2/src/test/org/h2/test/scripts/dml/execute_immediate.sql new file mode 100644 index 0000000..b3aa005 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/dml/execute_immediate.sql @@ -0,0 +1,33 @@ +-- 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 +-- + +CREATE MEMORY TABLE TEST(ID INT UNIQUE); +> ok + +EXECUTE IMMEDIATE 'INSERT INTO TEST VALUES ' || 1; +> update count: 1 + +EXECUTE IMMEDIATE 'INSERT INTO TEST2 VALUES 1'; +> exception TABLE_OR_VIEW_NOT_FOUND_1 + +EXECUTE IMMEDIATE 'SELECT 1'; +> exception SYNTAX_ERROR_2 + +EXECUTE IMMEDIATE 'ALTER TABLE TEST DROP CONSTRAINT ' || + QUOTE_IDENT((SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS + WHERE TABLE_SCHEMA = 'PUBLIC' AND TABLE_NAME = 'TEST' AND CONSTRAINT_TYPE = 'UNIQUE')); +> ok + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> ---------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "ID" INTEGER ); +> -- 1 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> INSERT INTO "PUBLIC"."TEST" VALUES (1); +> rows (ordered): 4 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/dml/insert.sql b/h2/src/test/org/h2/test/scripts/dml/insert.sql new file mode 100644 index 0000000..804fca8 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/dml/insert.sql @@ -0,0 +1,150 @@ +-- 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 +-- + +CREATE TABLE TEST(A INT, B INT); +> ok + +INSERT INTO TEST VALUES ROW (1, 2), (3, 4), ROW (5, 6); +> update count: 3 + +INSERT INTO TEST(a) VALUES 7; +> update count: 1 + +INSERT INTO TEST(a) VALUES 8, 9; +> update count: 2 + +TABLE TEST; +> A B +> - ---- +> 1 2 +> 3 4 +> 5 6 +> 7 null +> 8 null +> 9 null +> rows: 6 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID INT); +> ok + +-- TODO Do we need _ROWID_ support here? +INSERT INTO TEST(_ROWID_, ID) VALUES (2, 3); +> update count: 1 + +SELECT _ROWID_, ID FROM TEST; +> _ROWID_ ID +> ------- -- +> 2 3 +> rows: 1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INT, B INT DEFAULT 5); +> ok + +INSERT INTO TEST VALUES (1, DEFAULT); +> update count: 1 + +INSERT INTO TEST SET A = 2, B = DEFAULT; +> update count: 1 + +TABLE TEST; +> A B +> - - +> 1 5 +> 2 5 +> rows: 2 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INT, B INT GENERATED ALWAYS AS (A + 1)); +> ok + +INSERT INTO TEST VALUES (1, 1); +> exception GENERATED_COLUMN_CANNOT_BE_ASSIGNED_1 + +INSERT INTO TEST(B) VALUES 1; +> exception GENERATED_COLUMN_CANNOT_BE_ASSIGNED_1 + +INSERT INTO TEST VALUES (1, DEFAULT); +> update count: 1 + +INSERT INTO TEST DEFAULT VALUES; +> update count: 1 + +TABLE TEST; +> A B +> ---- ---- +> 1 2 +> null null +> rows: 2 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID NUMERIC(20) GENERATED BY DEFAULT AS IDENTITY, V INT); +> ok + +INSERT INTO TEST VALUES (12345678901234567890, 1); +> update count: 1 + +TABLE TEST; +> ID V +> -------------------- - +> 12345678901234567890 1 +> rows: 1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID BIGINT GENERATED BY DEFAULT AS IDENTITY, V INT); +> ok + +INSERT INTO TEST VALUES (10, 20); +> update count: 1 + +INSERT INTO TEST OVERRIDING USER VALUE VALUES (20, 30); +> update count: 1 + +INSERT INTO TEST OVERRIDING SYSTEM VALUE VALUES (30, 40); +> update count: 1 + +TABLE TEST; +> ID V +> -- -- +> 1 30 +> 10 20 +> 30 40 +> rows: 3 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID BIGINT GENERATED ALWAYS AS IDENTITY, V INT); +> ok + +INSERT INTO TEST VALUES (10, 20); +> exception GENERATED_COLUMN_CANNOT_BE_ASSIGNED_1 + +INSERT INTO TEST OVERRIDING USER VALUE VALUES (20, 30); +> update count: 1 + +INSERT INTO TEST OVERRIDING SYSTEM VALUE VALUES (30, 40); +> update count: 1 + +TABLE TEST; +> ID V +> -- -- +> 1 30 +> 30 40 +> rows: 2 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/dml/insertIgnore.sql b/h2/src/test/org/h2/test/scripts/dml/insertIgnore.sql new file mode 100644 index 0000000..bdbf726 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/dml/insertIgnore.sql @@ -0,0 +1,127 @@ +-- 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 +-- + +SET MODE MySQL; +> ok + +CREATE TABLE TEST(ID BIGINT PRIMARY KEY, `VALUE` INT NOT NULL); +> ok + +INSERT INTO TEST VALUES (1, 10), (2, 20), (3, 30), (4, 40); +> update count: 4 + +INSERT INTO TEST VALUES (3, 31), (5, 51); +> exception DUPLICATE_KEY_1 + +SELECT * FROM TEST ORDER BY ID; +> ID VALUE +> -- ----- +> 1 10 +> 2 20 +> 3 30 +> 4 40 +> rows (ordered): 4 + +INSERT IGNORE INTO TEST VALUES (3, 32), (5, 52); +> update count: 1 + +INSERT IGNORE INTO TEST VALUES (4, 43); +> ok + +SELECT * FROM TEST ORDER BY ID; +> ID VALUE +> -- ----- +> 1 10 +> 2 20 +> 3 30 +> 4 40 +> 5 52 +> rows (ordered): 5 + +CREATE TABLE TESTREF(ID BIGINT PRIMARY KEY, `VALUE` INT NOT NULL); +> ok + +INSERT INTO TESTREF VALUES (1, 11), (2, 21), (6, 61), (7, 71); +> update count: 4 + +INSERT INTO TEST (ID, `VALUE`) SELECT ID, `VALUE` FROM TESTREF; +> exception DUPLICATE_KEY_1 + +SELECT * FROM TEST ORDER BY ID; +> ID VALUE +> -- ----- +> 1 10 +> 2 20 +> 3 30 +> 4 40 +> 5 52 +> rows (ordered): 5 + +INSERT IGNORE INTO TEST (ID, `VALUE`) SELECT ID, `VALUE` FROM TESTREF; +> update count: 2 + +INSERT IGNORE INTO TEST (ID, `VALUE`) SELECT ID, `VALUE` FROM TESTREF; +> ok + +SELECT * FROM TEST ORDER BY ID; +> ID VALUE +> -- ----- +> 1 10 +> 2 20 +> 3 30 +> 4 40 +> 5 52 +> 6 61 +> 7 71 +> rows (ordered): 7 + +INSERT INTO TESTREF VALUES (8, 81), (9, 91); +> update count: 2 + +INSERT INTO TEST (ID, `VALUE`) SELECT ID, `VALUE` FROM TESTREF ON DUPLICATE KEY UPDATE `VALUE`=83; +> update count: 10 + +SELECT * FROM TEST ORDER BY ID; +> ID VALUE +> -- ----- +> 1 83 +> 2 83 +> 3 30 +> 4 40 +> 5 52 +> 6 83 +> 7 83 +> 8 81 +> 9 91 +> rows (ordered): 9 + +SET MODE Regular; +> ok + +INSERT INTO TEST (ID, `VALUE`) VALUES (9, 90), (10, 100); +> exception DUPLICATE_KEY_1 + +INSERT INTO TEST (ID, `VALUE`) VALUES (9, 90), (10, 100) ON CONFLICT DO NOTHING; +> exception SYNTAX_ERROR_1 + +SET MODE PostgreSQL; +> ok + +INSERT INTO TEST (ID, `VALUE`) VALUES (9, 90), (10, 100); +> exception DUPLICATE_KEY_1 + +INSERT INTO TEST (ID, `VALUE`) VALUES (9, 90), (10, 100) ON CONFLICT DO NOTHING; +> update count: 1 + +SELECT * FROM TEST WHERE ID >= 8 ORDER BY ID; +> ID VALUE +> -- ----- +> 8 81 +> 9 91 +> 10 100 +> rows (ordered): 3 + +SET MODE Regular; +> ok diff --git a/h2/src/test/org/h2/test/scripts/dml/merge.sql b/h2/src/test/org/h2/test/scripts/dml/merge.sql new file mode 100644 index 0000000..93509d4 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/dml/merge.sql @@ -0,0 +1,161 @@ +-- 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 +-- + +create table test(a int primary key, b int references(a)); +> ok + +merge into test values(1, 2); +> exception REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1 + +drop table test; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +EXPLAIN SELECT * FROM TEST WHERE ID=1; +>> SELECT "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."NAME" FROM "PUBLIC"."TEST" /* PUBLIC.PRIMARY_KEY_2: ID = 1 */ WHERE "ID" = 1 + +EXPLAIN MERGE INTO TEST VALUES(1, 'Hello'); +>> MERGE INTO "PUBLIC"."TEST"("ID", "NAME") KEY("ID") VALUES (1, 'Hello') + +MERGE INTO TEST VALUES(1, 'Hello'); +> update count: 1 + +MERGE INTO TEST VALUES(1, 'Hi'); +> update count: 1 + +MERGE INTO TEST VALUES(2, 'World'); +> update count: 1 + +MERGE INTO TEST VALUES(2, 'World!'); +> update count: 1 + +MERGE INTO TEST(ID, NAME) VALUES(3, 'How are you'); +> update count: 1 + +EXPLAIN MERGE INTO TEST(ID, NAME) VALUES(3, 'How are you'); +>> MERGE INTO "PUBLIC"."TEST"("ID", "NAME") KEY("ID") VALUES (3, 'How are you') + +MERGE INTO TEST(ID, NAME) KEY(ID) VALUES(3, 'How do you do'); +> update count: 1 + +EXPLAIN MERGE INTO TEST(ID, NAME) KEY(ID) VALUES(3, 'How do you do'); +>> MERGE INTO "PUBLIC"."TEST"("ID", "NAME") KEY("ID") VALUES (3, 'How do you do') + +MERGE INTO TEST(ID, NAME) KEY(NAME) VALUES(3, 'Fine'); +> exception DUPLICATE_KEY_1 + +MERGE INTO TEST(ID, NAME) KEY(NAME) VALUES(4, 'Fine!'); +> update count: 1 + +MERGE INTO TEST(ID, NAME) KEY(NAME) VALUES(4, 'Fine! And you'); +> exception DUPLICATE_KEY_1 + +MERGE INTO TEST(ID, NAME) KEY(NAME, ID) VALUES(5, 'I''m ok'); +> update count: 1 + +MERGE INTO TEST(ID, NAME) KEY(NAME, ID) VALUES(5, 'Oh, fine'); +> exception DUPLICATE_KEY_1 + +MERGE INTO TEST(ID, NAME) VALUES(6, 'Oh, fine.'); +> update count: 1 + +SELECT * FROM TEST; +> ID NAME +> -- ------------- +> 1 Hi +> 2 World! +> 3 How do you do +> 4 Fine! +> 5 I'm ok +> 6 Oh, fine. +> rows: 6 + +MERGE INTO TEST SELECT ID+4, NAME FROM TEST; +> update count: 6 + +SELECT * FROM TEST; +> ID NAME +> -- ------------- +> 1 Hi +> 10 Oh, fine. +> 2 World! +> 3 How do you do +> 4 Fine! +> 5 Hi +> 6 World! +> 7 How do you do +> 8 Fine! +> 9 I'm ok +> rows: 10 + +DROP TABLE TEST; +> ok + +-- Test for the index matching logic in org.h2.command.dml.Merge + +CREATE TABLE TEST(ID INT PRIMARY KEY, VALUE1 INT, VALUE2 INT, UNIQUE(VALUE1, VALUE2)); +> ok + +MERGE INTO TEST KEY (ID) VALUES (1, 2, 3), (2, 2, 3); +> exception DUPLICATE_KEY_1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INT, B INT DEFAULT 5); +> ok + +MERGE INTO TEST KEY(A) VALUES (1, DEFAULT); +> update count: 1 + +TABLE TEST; +> A B +> - - +> 1 5 +> rows: 1 + +UPDATE TEST SET B = 1 WHERE A = 1; +> update count: 1 + +SELECT B FROM TEST WHERE A = 1; +>> 1 + +MERGE INTO TEST KEY(A) VALUES (1, DEFAULT); +> update count: 1 + +SELECT B FROM TEST WHERE A = 1; +>> 5 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INT, B INT GENERATED ALWAYS AS (A + 1)); +> ok + +MERGE INTO TEST KEY(A) VALUES (1, 1); +> exception GENERATED_COLUMN_CANNOT_BE_ASSIGNED_1 + +MERGE INTO TEST KEY(A) VALUES (1, DEFAULT); +> update count: 1 + +MERGE INTO TEST KEY(A) VALUES (1, 1); +> exception GENERATED_COLUMN_CANNOT_BE_ASSIGNED_1 + +MERGE INTO TEST KEY(A) VALUES (1, DEFAULT); +> update count: 1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID INT, G INT GENERATED ALWAYS AS (ID + 1)); +> ok + +MERGE INTO TEST(G) KEY(ID) VALUES (1); +> exception SYNTAX_ERROR_2 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/dml/mergeUsing.sql b/h2/src/test/org/h2/test/scripts/dml/mergeUsing.sql new file mode 100644 index 0000000..0512416 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/dml/mergeUsing.sql @@ -0,0 +1,541 @@ +-- 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 +-- +CREATE TABLE PARENT(ID INT, NAME VARCHAR, PRIMARY KEY(ID) ); +> ok + +MERGE INTO PARENT AS P + USING (SELECT X AS ID, 'Coco'||X AS NAME FROM SYSTEM_RANGE(1,2) ) AS S + ON (P.ID = S.ID AND 1=1 AND S.ID = P.ID) + WHEN MATCHED THEN + UPDATE SET P.NAME = S.NAME WHERE 2 = 2; +> exception SYNTAX_ERROR_1 + +SET MODE Oracle; +> ok + +MERGE INTO PARENT AS P + USING (SELECT X AS ID, 'Coco'||X AS NAME FROM SYSTEM_RANGE(1,2) ) AS S + ON (P.ID = S.ID AND 1=1 AND S.ID = P.ID) + WHEN MATCHED THEN + UPDATE SET P.NAME = S.NAME WHERE 2 = 2 + WHEN NOT MATCHED THEN + INSERT (ID, NAME) VALUES (S.ID, S.NAME); +> update count: 2 + +SELECT * FROM PARENT; +> ID NAME +> -- ----- +> 1 Coco1 +> 2 Coco2 +> rows: 2 + +EXPLAIN PLAN + MERGE INTO PARENT AS P + USING (SELECT X AS ID, 'Coco'||X AS NAME FROM SYSTEM_RANGE(1,2) ) AS S + ON (P.ID = S.ID AND 1=1 AND S.ID = P.ID) + WHEN MATCHED THEN + UPDATE SET P.NAME = S.NAME WHERE 2 = 2 + WHEN NOT MATCHED THEN + INSERT (ID, NAME) VALUES (S.ID, S.NAME); +>> MERGE INTO "PUBLIC"."PARENT" "P" /* PUBLIC.PRIMARY_KEY_8: ID = S.ID AND ID = S.ID */ USING ( SELECT "X" AS "ID", CONCAT('Coco', "X") AS "NAME" FROM SYSTEM_RANGE(1, 2) ) "S" /* SELECT X AS ID, CONCAT('Coco', X) AS NAME FROM SYSTEM_RANGE(1, 2) /* range index */ */ WHEN MATCHED THEN UPDATE SET "NAME" = "S"."NAME" WHEN NOT MATCHED THEN INSERT ("ID", "NAME") VALUES ("S"."ID", "S"."NAME") + +SET MODE Regular; +> ok + +DROP TABLE PARENT; +> ok + +CREATE SCHEMA SOURCESCHEMA; +> ok + +CREATE TABLE SOURCESCHEMA.SOURCE(ID INT PRIMARY KEY, "VALUE" INT); +> ok + +INSERT INTO SOURCESCHEMA.SOURCE VALUES (1, 10), (3, 30), (5, 50); +> update count: 3 + +CREATE SCHEMA DESTSCHEMA; +> ok + +CREATE TABLE DESTSCHEMA.DESTINATION(ID INT PRIMARY KEY, "VALUE" INT); +> ok + +INSERT INTO DESTSCHEMA.DESTINATION VALUES (3, 300), (6, 600); +> update count: 2 + +MERGE INTO DESTSCHEMA.DESTINATION USING SOURCESCHEMA.SOURCE ON (DESTSCHEMA.DESTINATION.ID = SOURCESCHEMA.SOURCE.ID) + WHEN MATCHED THEN UPDATE SET "VALUE" = SOURCESCHEMA.SOURCE."VALUE" + WHEN NOT MATCHED THEN INSERT (ID, "VALUE") VALUES (SOURCESCHEMA.SOURCE.ID, SOURCESCHEMA.SOURCE."VALUE"); +> update count: 3 + +SELECT * FROM DESTSCHEMA.DESTINATION; +> ID VALUE +> -- ----- +> 1 10 +> 3 30 +> 5 50 +> 6 600 +> rows: 4 + +DROP SCHEMA SOURCESCHEMA CASCADE; +> ok + +DROP SCHEMA DESTSCHEMA CASCADE; +> ok + +CREATE TABLE SOURCE_TABLE(ID BIGINT PRIMARY KEY, C1 INT NOT NULL); +> ok + +INSERT INTO SOURCE_TABLE VALUES (1, 10), (2, 20), (3, 30); +> update count: 3 + +CREATE TABLE DEST_TABLE(ID BIGINT PRIMARY KEY, C1 INT NOT NULL, C2 INT NOT NULL); +> ok + +INSERT INTO DEST_TABLE VALUES (2, 200, 2000), (4, 400, 4000); +> update count: 2 + +MERGE INTO DEST_TABLE USING SOURCE_TABLE ON (DEST_TABLE.ID = SOURCE_TABLE.ID) + WHEN MATCHED THEN UPDATE SET DEST_TABLE.C1 = SOURCE_TABLE.C1, DEST_TABLE.C2 = 100; +> update count: 1 + +SELECT * FROM DEST_TABLE; +> ID C1 C2 +> -- --- ---- +> 2 20 100 +> 4 400 4000 +> rows: 2 + +MERGE INTO DEST_TABLE D USING SOURCE_TABLE S ON (D.ID = S.ID) + WHEN MATCHED THEN UPDATE SET D.C1 = S.C1, D.C2 = 100 + WHEN NOT MATCHED THEN INSERT (ID, C1, C2) VALUES (S.ID, S.C1, 1000); +> update count: 3 + +SELECT * FROM DEST_TABLE; +> ID C1 C2 +> -- --- ---- +> 1 10 1000 +> 2 20 100 +> 3 30 1000 +> 4 400 4000 +> rows: 4 + +DROP TABLE SOURCE_TABLE; +> ok + +DROP TABLE DEST_TABLE; +> ok + +CREATE TABLE TEST(C1 INT, C2 INT, C3 INT); +> ok + +MERGE INTO TEST USING DUAL ON C1 = 11 AND C2 = 21 + WHEN NOT MATCHED THEN INSERT (C1, C2, C3) VALUES (11, 21, 31) + WHEN MATCHED THEN UPDATE SET C3 = 31; +> update count: 1 + +MERGE INTO TEST USING DUAL ON (C1 = 11 AND C2 = 22) + WHEN NOT MATCHED THEN INSERT (C1, C2, C3) VALUES (11, 22, 32) + WHEN MATCHED THEN UPDATE SET C3 = 32; +> update count: 1 + +SELECT * FROM TEST ORDER BY C1, C2; +> C1 C2 C3 +> -- -- -- +> 11 21 31 +> 11 22 32 +> rows (ordered): 2 + +MERGE INTO TEST USING DUAL ON C1 = 11 AND C2 = 21 + WHEN NOT MATCHED THEN INSERT (C1, C2, C3) VALUES (11, 21, 33) + WHEN MATCHED THEN UPDATE SET C3 = 33; +> update count: 1 + +SELECT * FROM TEST ORDER BY C1, C2; +> C1 C2 C3 +> -- -- -- +> 11 21 33 +> 11 22 32 +> rows (ordered): 2 + +MERGE INTO TEST USING (SELECT 1 FROM DUAL) ON (C1 = 11 AND C2 = 21) + WHEN NOT MATCHED THEN INSERT (C1, C2, C3) VALUES (11, 21, 33) + WHEN MATCHED THEN UPDATE SET C3 = 34; +> update count: 1 + +SELECT * FROM TEST ORDER BY C1, C2; +> C1 C2 C3 +> -- -- -- +> 11 21 34 +> 11 22 32 +> rows (ordered): 2 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST (ID INT, "VALUE" INT); +> ok + +MERGE INTO TEST USING DUAL ON (ID = 1) + WHEN MATCHED THEN UPDATE SET "VALUE" = 1 + WHEN; +> exception SYNTAX_ERROR_2 + +MERGE INTO TEST USING DUAL ON (ID = 1) + WHEN MATCHED THEN UPDATE SET "VALUE" = 1 + WHEN NOT MATCHED THEN; +> exception SYNTAX_ERROR_2 + +MERGE INTO TEST USING DUAL ON (ID = 1) + WHEN NOT MATCHED THEN INSERT (ID, "VALUE") VALUES (1, 1) + WHEN; +> exception SYNTAX_ERROR_2 + +MERGE INTO TEST USING DUAL ON (ID = 1) + WHEN NOT MATCHED THEN INSERT (ID, "VALUE") VALUES (1, 1) + WHEN MATCHED THEN; +> exception SYNTAX_ERROR_2 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY); +> ok + +MERGE INTO TEST USING (SELECT CAST(? AS INT) ID FROM DUAL) S ON (TEST.ID = S.ID) + WHEN NOT MATCHED THEN INSERT (ID) VALUES (S.ID); +{ +10 +20 +30 +}; +> update count: 3 + +SELECT * FROM TEST; +> ID +> -- +> 10 +> 20 +> 30 +> rows: 3 + +MERGE INTO TEST USING (SELECT 40) ON UNKNOWN_COLUMN = 1 WHEN NOT MATCHED THEN INSERT (ID) VALUES (40); +> exception COLUMN_NOT_FOUND_1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, "VALUE" INT); +> ok + +INSERT INTO TEST VALUES (1, 10), (2, 20); +> update count: 2 + +MERGE INTO TEST USING (SELECT 1) ON (ID < 0) + WHEN MATCHED THEN UPDATE SET "VALUE" = 30 + WHEN NOT MATCHED THEN INSERT VALUES (3, 30); +> update count: 1 + +SELECT * FROM TEST; +> ID VALUE +> -- ----- +> 1 10 +> 2 20 +> 3 30 +> rows: 3 + +MERGE INTO TEST USING (SELECT 1) ON (ID = ID) + WHEN MATCHED THEN UPDATE SET "VALUE" = 40 + WHEN NOT MATCHED THEN INSERT VALUES (4, 40); +> update count: 3 + +SELECT * FROM TEST; +> ID VALUE +> -- ----- +> 1 40 +> 2 40 +> 3 40 +> rows: 3 + +MERGE INTO TEST USING (SELECT 1) ON (1 = 1) + WHEN MATCHED THEN UPDATE SET "VALUE" = 50 + WHEN NOT MATCHED THEN INSERT VALUES (5, 50); +> update count: 3 + +SELECT * FROM TEST; +> ID VALUE +> -- ----- +> 1 50 +> 2 50 +> 3 50 +> rows: 3 + +MERGE INTO TEST USING (SELECT 1) ON 1 = 1 + WHEN MATCHED THEN UPDATE SET "VALUE" = 60 WHERE ID = 3 DELETE WHERE ID = 2; +> exception SYNTAX_ERROR_1 + +MERGE INTO TEST USING (SELECT 1 A) ON 1 = 1 + WHEN MATCHED THEN DELETE WHERE ID = 2; +> exception SYNTAX_ERROR_1 + +SET MODE Oracle; +> ok + +MERGE INTO TEST USING (SELECT 1 A) ON 1 = 1 + WHEN MATCHED THEN DELETE WHERE ID = 2; +> update count: 1 + +SET MODE Regular; +> ok + +SELECT * FROM TEST; +> ID VALUE +> -- ----- +> 1 50 +> 3 50 +> rows: 2 + +DROP TABLE TEST; +> ok + +CREATE TABLE T(ID INT, F BOOLEAN, "VALUE" INT); +> ok + +INSERT INTO T VALUES (1, FALSE, 10), (2, TRUE, 20); +> update count: 2 + +CREATE TABLE S(S_ID INT, S_F BOOLEAN, S_VALUE INT); +> ok + +INSERT INTO S VALUES (1, FALSE, 100), (2, TRUE, 200), (3, FALSE, 300), (4, TRUE, 400); +> update count: 4 + +MERGE INTO T USING S ON ID = S_ID + WHEN MATCHED AND F THEN UPDATE SET "VALUE" = S_VALUE + WHEN MATCHED AND NOT F THEN DELETE + WHEN NOT MATCHED AND S_F THEN INSERT VALUES (S_ID, S_F, S_VALUE); +> update count: 3 + +SELECT * FROM T; +> ID F VALUE +> -- ---- ----- +> 2 TRUE 200 +> 4 TRUE 400 +> rows: 2 + +DROP TABLE T, S; +> ok + +CREATE TABLE T(ID INT, A INT, B INT) AS VALUES (1, 1, 1), (2, 1, 2); +> ok + +CREATE TABLE S(ID INT, A INT, B INT) AS VALUES (1, 1, 3), (2, 1, 4); +> ok + +MERGE INTO T USING S ON T.A = S.A WHEN MATCHED THEN UPDATE SET B = S.B; +> exception DUPLICATE_KEY_1 + +CREATE TABLE S2(ID INT, A INT, B INT) AS VALUES (3, 3, 3); +> ok + +MERGE INTO T USING (SELECT * FROM S UNION SELECT * FROM S2) S ON T.ID = S.ID + WHEN MATCHED THEN UPDATE SET A = S.A, B = S.B + WHEN NOT MATCHED THEN INSERT VALUES (S.ID, S.A, S.B); +> update count: 3 + +TABLE T; +> ID A B +> -- - - +> 1 1 3 +> 2 1 4 +> 3 3 3 +> rows: 3 + +MERGE INTO T USING (S) ON T.ID = S.ID + WHEN MATCHED THEN UPDATE SET B = S.B + 1; +> update count: 2 + +TABLE T; +> ID A B +> -- - - +> 1 1 4 +> 2 1 5 +> 3 3 3 +> rows: 3 + +DROP TABLE T, S, S2 CASCADE; +> ok + +CREATE TABLE TEST(ID INT, V INT); +> ok + +MERGE INTO TEST USING VALUES (1, 2) S ON TEST.ID = S.C1 WHEN NOT MATCHED THEN INSERT VALUES (1, 2), (3, 4); +> exception SYNTAX_ERROR_1 + +DROP TABLE TEST; +> ok + +CREATE TABLE T(A INT); +> ok + +MERGE INTO T USING (SELECT 1 A) S ON (TRUE) +WHEN NOT MATCHED AND S.X THEN INSERT VALUES (1); +> exception COLUMN_NOT_FOUND_1 + +DROP TABLE T; +> ok + +CREATE TABLE A(ID INT, V INT) AS VALUES (1, 1), (2, 2); +> ok + +CREATE TABLE B(ID INT, V INT) AS VALUES (2, 4), (3, 6); +> ok + +MERGE INTO A USING (SELECT * FROM B) S + ON A.ID = S.ID + WHEN MATCHED THEN UPDATE SET V = S.V; +> update count: 1 + +TABLE A; +> ID V +> -- - +> 1 1 +> 2 4 +> rows: 2 + +DROP TABLE A, B; +> ok + +CREATE TABLE TARGET(ID INT, V INT); +> ok + +MERGE INTO TARGET T USING (VALUES (1, 2)) S(ID, V) + ON T.ID = S.ID + WHEN NOT MATCHED THEN INSERT VALUES (S.ID, S.V); +> update count: 1 + +CREATE TABLE SOURCE(ID INT, V INT) AS VALUES (3, 4); +> ok + +MERGE INTO TARGET T USING SOURCE S(ID, V) + ON T.ID = S.ID + WHEN NOT MATCHED THEN INSERT VALUES (S.ID, S.V); +> update count: 1 + +TABLE TARGET; +> ID V +> -- - +> 1 2 +> 3 4 +> rows: 2 + +DROP TABLE SOURCE, TARGET; +> ok + +CREATE TABLE T(ID INT, V INT) AS VALUES (1, 1), (2, 2); +> ok + +MERGE INTO T USING (SELECT 1) ON (TRUE) + WHEN MATCHED THEN UPDATE SET V = 2 + WHEN MATCHED AND ID = 2 THEN UPDATE SET V = 3; +> update count: 2 + +TABLE T; +> ID V +> -- - +> 1 2 +> 2 2 +> rows: 2 + +TRUNCATE TABLE T; +> update count: 2 + +INSERT INTO T VALUES (1, 1); +> update count: 1 + +MERGE INTO T USING (SELECT 1) ON (ID = 1) + WHEN MATCHED THEN UPDATE SET V = 2 + WHEN MATCHED THEN UPDATE SET V = 3; +> update count: 1 + +TABLE T; +> ID V +> -- - +> 1 2 +> rows: 1 + +SELECT * FROM FINAL TABLE (MERGE INTO T USING (SELECT 1) ON (ID = 1) + WHEN MATCHED THEN UPDATE SET V = 4 + WHEN MATCHED THEN UPDATE SET V = 5); +> ID V +> -- - +> 1 4 +> rows: 1 + +EXPLAIN MERGE INTO T USING (VALUES (1, 2)) S(ID, V) ON T.ID = S.ID + WHEN NOT MATCHED AND T.ID = 1 THEN INSERT VALUES (S.ID, S.V) + WHEN NOT MATCHED THEN INSERT VALUES (S.ID, S.V + 1) + WHEN MATCHED AND T.ID = 2 THEN UPDATE SET V = S.ID + 2 + WHEN MATCHED THEN UPDATE SET V = S.ID + 3; +>> MERGE INTO "PUBLIC"."T" /* PUBLIC.T.tableScan */ USING (VALUES (1, 2)) "S"("ID", "V") /* table scan */ WHEN NOT MATCHED AND "T"."ID" = 1 THEN INSERT ("ID", "V") VALUES ("S"."ID", "S"."V") WHEN NOT MATCHED THEN INSERT ("ID", "V") VALUES ("S"."ID", "S"."V" + 1) WHEN MATCHED AND "T"."ID" = 2 THEN UPDATE SET "V" = "S"."ID" + 2 WHEN MATCHED THEN UPDATE SET "V" = "S"."ID" + 3 + +EXPLAIN MERGE INTO T USING (VALUES (1, 2)) S(ID, V) ON T.ID = S.ID + WHEN MATCHED AND T.ID = 1 THEN DELETE + WHEN MATCHED THEN DELETE; +>> MERGE INTO "PUBLIC"."T" /* PUBLIC.T.tableScan */ USING (VALUES (1, 2)) "S"("ID", "V") /* table scan */ WHEN MATCHED AND "T"."ID" = 1 THEN DELETE WHEN MATCHED THEN DELETE + +DROP TABLE T; +> ok + +CREATE TABLE TEST(ID BIGINT GENERATED BY DEFAULT AS IDENTITY, V INT); +> ok + +MERGE INTO TEST USING (VALUES (10, 20)) SOURCE(ID, V) ON TEST.ID = SOURCE.ID + WHEN NOT MATCHED THEN INSERT VALUES(SOURCE.ID, SOURCE.V); +> update count: 1 + +MERGE INTO TEST USING (VALUES (20, 30)) SOURCE(ID, V) ON TEST.ID = SOURCE.ID + WHEN NOT MATCHED THEN INSERT OVERRIDING USER VALUE VALUES(SOURCE.ID, SOURCE.V); +> update count: 1 + +MERGE INTO TEST USING (VALUES (30, 40)) SOURCE(ID, V) ON TEST.ID = SOURCE.ID + WHEN NOT MATCHED THEN INSERT OVERRIDING SYSTEM VALUE VALUES(SOURCE.ID, SOURCE.V); +> update count: 1 + +TABLE TEST; +> ID V +> -- -- +> 1 30 +> 10 20 +> 30 40 +> rows: 3 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID BIGINT GENERATED ALWAYS AS IDENTITY, V INT); +> ok + +MERGE INTO TEST USING (VALUES (10, 20)) SOURCE(ID, V) ON TEST.ID = SOURCE.ID + WHEN NOT MATCHED THEN INSERT VALUES(SOURCE.ID, SOURCE.V); +> exception GENERATED_COLUMN_CANNOT_BE_ASSIGNED_1 + +MERGE INTO TEST USING (VALUES (20, 30)) SOURCE(ID, V) ON TEST.ID = SOURCE.ID + WHEN NOT MATCHED THEN INSERT OVERRIDING USER VALUE VALUES(SOURCE.ID, SOURCE.V); +> update count: 1 + +MERGE INTO TEST USING (VALUES (30, 40)) SOURCE(ID, V) ON TEST.ID = SOURCE.ID + WHEN NOT MATCHED THEN INSERT OVERRIDING SYSTEM VALUE VALUES(SOURCE.ID, SOURCE.V); +> update count: 1 + +TABLE TEST; +> ID V +> -- -- +> 1 30 +> 30 40 +> rows: 2 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/dml/replace.sql b/h2/src/test/org/h2/test/scripts/dml/replace.sql new file mode 100644 index 0000000..cad90d6 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/dml/replace.sql @@ -0,0 +1,53 @@ +-- 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 +-- + +SET MODE MySQL; +> ok + +CREATE TABLE TABLE_WORD ( + WORD_ID int(11) NOT NULL AUTO_INCREMENT, + WORD varchar(128) NOT NULL, + PRIMARY KEY (WORD_ID) +); +> ok + +REPLACE INTO TABLE_WORD(WORD) VALUES ('aaaaaaaaaa'); +> update count: 1 + +REPLACE INTO TABLE_WORD(WORD) VALUES ('bbbbbbbbbb'); +> update count: 1 + +REPLACE INTO TABLE_WORD(WORD_ID, WORD) VALUES (3, 'cccccccccc'); +> update count: 1 + +SELECT WORD FROM TABLE_WORD where WORD_ID = 1; +>> aaaaaaaaaa + +REPLACE INTO TABLE_WORD(WORD_ID, WORD) VALUES (1, 'REPLACED'); +> update count: 2 + +SELECT WORD FROM TABLE_WORD where WORD_ID = 1; +>> REPLACED + +REPLACE INTO TABLE_WORD(WORD) SELECT 'dddddddddd'; +> update count: 1 + +SELECT WORD FROM TABLE_WORD where WORD_ID = 4; +>> dddddddddd + +REPLACE INTO TABLE_WORD(WORD_ID, WORD) SELECT 1, 'REPLACED2'; +> update count: 2 + +SELECT WORD FROM TABLE_WORD where WORD_ID = 1; +>> REPLACED2 + +SET MODE Regular; +> ok + +REPLACE INTO TABLE_WORD(WORD) VALUES ('aaaaaaaaaa'); +> exception SYNTAX_ERROR_2 + +DROP TABLE TABLE_WORD; +> ok diff --git a/h2/src/test/org/h2/test/scripts/dml/script.sql b/h2/src/test/org/h2/test/scripts/dml/script.sql new file mode 100644 index 0000000..b028913 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/dml/script.sql @@ -0,0 +1,142 @@ +-- 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 +-- + +create memory table test(id int primary key, name varchar(255)); +> ok + +INSERT INTO TEST VALUES(2, STRINGDECODE('abcsond\344rzeich\344 ') || char(22222) || STRINGDECODE(' \366\344\374\326\304\334\351\350\340\361!')); +> update count: 1 + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> ------------------------------------------------------------------------------------------------------------------------------ +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "ID" INTEGER NOT NULL, "NAME" CHARACTER VARYING(255) ); +> ALTER TABLE "PUBLIC"."TEST" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_2" PRIMARY KEY("ID"); +> -- 1 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> INSERT INTO "PUBLIC"."TEST" VALUES (2, U&'abcsond\00e4rzeich\00e4 \56ce \00f6\00e4\00fc\00d6\00c4\00dc\00e9\00e8\00e0\00f1!'); +> rows (ordered): 5 + +SCRIPT COLUMNS NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> -------------------------------------------------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "ID" INTEGER NOT NULL, "NAME" CHARACTER VARYING(255) ); +> ALTER TABLE "PUBLIC"."TEST" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_2" PRIMARY KEY("ID"); +> -- 1 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> INSERT INTO "PUBLIC"."TEST"("ID", "NAME") VALUES (2, U&'abcsond\00e4rzeich\00e4 \56ce \00f6\00e4\00fc\00d6\00c4\00dc\00e9\00e8\00e0\00f1!'); +> rows (ordered): 5 + +DROP TABLE TEST; +> ok + +CREATE MEMORY TABLE TEST(ID BIGINT GENERATED ALWAYS AS IDENTITY, V INT, G INT GENERATED ALWAYS AS (V + 1)); +> ok + +INSERT INTO TEST(V) VALUES 5; +> update count: 1 + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "ID" BIGINT GENERATED ALWAYS AS IDENTITY(START WITH 1 RESTART WITH 2) NOT NULL, "V" INTEGER, "G" INTEGER GENERATED ALWAYS AS ("V" + 1) ); +> -- 1 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> INSERT INTO "PUBLIC"."TEST"("ID", "V") OVERRIDING SYSTEM VALUE VALUES (1, 5); +> rows (ordered): 4 + +DROP TABLE TEST; +> ok + +CREATE DOMAIN C AS INT; +> ok + +CREATE DOMAIN B AS C; +> ok + +CREATE DOMAIN A AS B; +> ok + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> ------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE DOMAIN "PUBLIC"."C" AS INTEGER; +> CREATE DOMAIN "PUBLIC"."B" AS "PUBLIC"."C"; +> CREATE DOMAIN "PUBLIC"."A" AS "PUBLIC"."B"; +> rows (ordered): 4 + +DROP DOMAIN A; +> ok + +DROP DOMAIN B; +> ok + +DROP DOMAIN C; +> ok + +CREATE DOMAIN A AS INT; +> ok + +CREATE DOMAIN B AS A; +> ok + +CREATE DOMAIN X AS INT; +> ok + +CREATE DOMAIN Y AS X; +> ok + +CREATE DOMAIN Z AS Y; +> ok + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> ------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE DOMAIN "PUBLIC"."A" AS INTEGER; +> CREATE DOMAIN "PUBLIC"."X" AS INTEGER; +> CREATE DOMAIN "PUBLIC"."B" AS "PUBLIC"."A"; +> CREATE DOMAIN "PUBLIC"."Y" AS "PUBLIC"."X"; +> CREATE DOMAIN "PUBLIC"."Z" AS "PUBLIC"."Y"; +> rows (ordered): 6 + +DROP ALL OBJECTS; +> ok + +CREATE SCHEMA S1; +> ok + +CREATE SCHEMA S2; +> ok + +CREATE SCHEMA S3; +> ok + +CREATE DOMAIN S1.D1 AS INTEGER; +> ok + +CREATE DOMAIN S2.D2 AS S1.D1; +> ok + +CREATE DOMAIN S3.D3 AS S2.D2; +> ok + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION SCHEMA S3; +> SCRIPT +> ---------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE SCHEMA IF NOT EXISTS "S3" AUTHORIZATION "SA"; +> CREATE DOMAIN "S3"."D3" AS "S2"."D2"; +> rows (ordered): 3 + +DROP SCHEMA S3 CASCADE; +> ok + +DROP SCHEMA S2 CASCADE; +> ok + +DROP SCHEMA S1 CASCADE; +> ok diff --git a/h2/src/test/org/h2/test/scripts/dml/show.sql b/h2/src/test/org/h2/test/scripts/dml/show.sql new file mode 100644 index 0000000..a6c2c13 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/dml/show.sql @@ -0,0 +1,113 @@ +-- 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 +-- + +------------------------------ +-- PostgreSQL compatibility -- +------------------------------ + +SHOW CLIENT_ENCODING; +> CLIENT_ENCODING +> --------------- +> UNICODE +> rows: 1 + +SHOW DEFAULT_TRANSACTION_ISOLATION; +> DEFAULT_TRANSACTION_ISOLATION +> ----------------------------- +> read committed +> rows: 1 + +SHOW TRANSACTION ISOLATION LEVEL; +> TRANSACTION_ISOLATION +> --------------------- +> read committed +> rows: 1 + +SHOW DATESTYLE; +> DATESTYLE +> --------- +> ISO +> rows: 1 + +SHOW SERVER_VERSION; +> SERVER_VERSION +> -------------- +> 8.2.23 +> rows: 1 + +SHOW SERVER_ENCODING; +> SERVER_ENCODING +> --------------- +> UTF8 +> rows: 1 + +------------------------- +-- MySQL compatibility -- +------------------------- + +CREATE TABLE TEST_P(ID_P INT PRIMARY KEY, U_P VARCHAR(255) UNIQUE, N_P INT DEFAULT 1); +> ok + +CREATE SCHEMA SCH; +> ok + +CREATE TABLE SCH.TEST_S(ID_S INT PRIMARY KEY, U_S VARCHAR(255) UNIQUE, N_S INT DEFAULT 1); +> ok + +SHOW TABLES; +> TABLE_NAME TABLE_SCHEMA +> ---------- ------------ +> TEST_P PUBLIC +> rows (ordered): 1 + +SHOW TABLES FROM PUBLIC; +> TABLE_NAME TABLE_SCHEMA +> ---------- ------------ +> TEST_P PUBLIC +> rows (ordered): 1 + +SHOW TABLES FROM SCH; +> TABLE_NAME TABLE_SCHEMA +> ---------- ------------ +> TEST_S SCH +> rows (ordered): 1 + +SHOW COLUMNS FROM TEST_P; +> FIELD TYPE NULL KEY DEFAULT +> ----- ---------------------- ---- --- ------- +> ID_P INTEGER NO PRI NULL +> U_P CHARACTER VARYING(255) YES UNI NULL +> N_P INTEGER YES 1 +> rows (ordered): 3 + +SHOW COLUMNS FROM TEST_S FROM SCH; +> FIELD TYPE NULL KEY DEFAULT +> ----- ---------------------- ---- --- ------- +> ID_S INTEGER NO PRI NULL +> U_S CHARACTER VARYING(255) YES UNI NULL +> N_S INTEGER YES 1 +> rows (ordered): 3 + +SHOW DATABASES; +> SCHEMA_NAME +> ------------------ +> INFORMATION_SCHEMA +> PUBLIC +> SCH +> rows: 3 + +SHOW SCHEMAS; +> SCHEMA_NAME +> ------------------ +> INFORMATION_SCHEMA +> PUBLIC +> SCH +> rows: 3 + +DROP TABLE TEST_P; +> ok + +DROP SCHEMA SCH CASCADE; +> ok diff --git a/h2/src/test/org/h2/test/scripts/dml/update.sql b/h2/src/test/org/h2/test/scripts/dml/update.sql new file mode 100644 index 0000000..7f67503 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/dml/update.sql @@ -0,0 +1,345 @@ +-- 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 +-- + +CREATE TABLE TEST(A INT, B INT); +> ok + +INSERT INTO TEST VALUES (1, 2); +> update count: 1 + +UPDATE TEST SET (A, B) = (3, 4); +> update count: 1 + +SELECT * FROM TEST; +> A B +> - - +> 3 4 +> rows: 1 + +UPDATE TEST SET (B) = 5; +> update count: 1 + +SELECT B FROM TEST; +>> 5 + +UPDATE TEST SET (B) = ROW (6); +> update count: 1 + +SELECT B FROM TEST; +>> 6 + +UPDATE TEST SET (B) = (7); +> update count: 1 + +SELECT B FROM TEST; +>> 7 + +UPDATE TEST SET (B) = (2, 3); +> exception COLUMN_COUNT_DOES_NOT_MATCH + +-- TODO +-- UPDATE TEST SET (A, B) = ARRAY[3, 4]; +-- > exception COLUMN_COUNT_DOES_NOT_MATCH + +EXPLAIN UPDATE TEST SET (A) = ROW(3), B = 4; +>> UPDATE "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ SET "A" = 3, "B" = 4 + +EXPLAIN UPDATE TEST SET A = 3, (B) = 4; +>> UPDATE "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ SET "A" = 3, "B" = 4 + +UPDATE TEST SET (A, B) = (1, 2), (B, A) = (2, 1); +> exception DUPLICATE_COLUMN_NAME_1 + +UPDATE TEST SET (A) = A * 3; +> update count: 1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID INT) AS VALUES 100; +> ok + +-- _ROWID_ modifications are not allowed +UPDATE TEST SET _ROWID_ = 2 WHERE ID = 100; +> exception SYNTAX_ERROR_2 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INT, B INT GENERATED ALWAYS AS (A + 1)); +> ok + +INSERT INTO TEST(A) VALUES 1; +> update count: 1 + +UPDATE TEST SET A = 2, B = DEFAULT; +> update count: 1 + +TABLE TEST; +> A B +> - - +> 2 3 +> rows: 1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INT, B INT GENERATED ALWAYS AS (A + 1)); +> ok + +INSERT INTO TEST(A) VALUES 1; +> update count: 1 + +UPDATE TEST SET B = 1; +> exception GENERATED_COLUMN_CANNOT_BE_ASSIGNED_1 + +UPDATE TEST SET B = DEFAULT; +> update count: 1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, A INT, B INT, C INT, D INT, E INT, F INT) AS VALUES (1, 1, 1, 1, 1, 1, 1); +> ok + +EXPLAIN UPDATE TEST SET + (F, C, A) = (SELECT 2, 3, 4 FROM TEST FETCH FIRST ROW ONLY), + (B, E) = (SELECT 5, 6 FROM TEST FETCH FIRST ROW ONLY) + WHERE ID = 1; +>> UPDATE "PUBLIC"."TEST" /* PUBLIC.PRIMARY_KEY_2: ID = 1 */ SET ("F", "C", "A") = (SELECT 2, 3, 4 FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ FETCH FIRST ROW ONLY), ("B", "E") = (SELECT 5, 6 FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ FETCH FIRST ROW ONLY) WHERE "ID" = 1 + +UPDATE TEST SET + (F, C, A) = (SELECT 2, 3, 4 FROM TEST FETCH FIRST ROW ONLY), + (B, E) = (SELECT 5, 6 FROM TEST FETCH FIRST ROW ONLY) + WHERE ID = 1; +> update count: 1 + +TABLE TEST; +> ID A B C D E F +> -- - - - - - - +> 1 4 5 3 1 6 2 +> rows: 1 + +UPDATE TEST SET (C, C) = (SELECT 1, 2 FROM TEST); +> exception DUPLICATE_COLUMN_NAME_1 + +UPDATE TEST SET (A, B) = (SELECT 1, 2, 3 FROM TEST); +> exception COLUMN_COUNT_DOES_NOT_MATCH + +UPDATE TEST SET (D, E) = NULL; +> exception DATA_CONVERSION_ERROR_1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID BIGINT GENERATED ALWAYS AS IDENTITY, ID2 BIGINT GENERATED ALWAYS AS (ID + 1), + V INT, U INT ON UPDATE (5)); +> ok + +INSERT INTO TEST(V) VALUES 1; +> update count: 1 + +TABLE TEST; +> ID ID2 V U +> -- --- - ---- +> 1 2 1 null +> rows: 1 + +UPDATE TEST SET V = V + 1; +> update count: 1 + +UPDATE TEST SET V = V + 1, ID = DEFAULT, ID2 = DEFAULT; +> update count: 1 + +TABLE TEST; +> ID ID2 V U +> -- --- - - +> 1 2 3 5 +> rows: 1 + +MERGE INTO TEST USING (VALUES 1) T(X) ON TRUE WHEN MATCHED THEN UPDATE SET V = V + 1; +> update count: 1 + +MERGE INTO TEST USING (VALUES 1) T(X) ON TRUE WHEN MATCHED THEN UPDATE SET V = V + 1, ID = DEFAULT, ID2 = DEFAULT; +> update count: 1 + +TABLE TEST; +> ID ID2 V U +> -- --- - - +> 1 2 5 5 +> rows: 1 + +MERGE INTO TEST KEY(V) VALUES (DEFAULT, DEFAULT, 5, 1); +> update count: 1 + +TABLE TEST; +> ID ID2 V U +> -- --- - - +> 1 2 5 1 +> rows: 1 + +DROP TABLE TEST; +> ok + +CREATE DOMAIN D AS BIGINT DEFAULT 100 ON UPDATE 200; +> ok + +CREATE TABLE TEST(ID D GENERATED BY DEFAULT AS IDENTITY, V INT, G D GENERATED ALWAYS AS (V + 1)); +> ok + +INSERT INTO TEST(V) VALUES 1; +> update count: 1 + +TABLE TEST; +> ID V G +> -- - - +> 1 1 2 +> rows: 1 + +UPDATE TEST SET V = 2; +> update count: 1 + +TABLE TEST; +> ID V G +> -- - - +> 1 2 3 +> rows: 1 + +DROP TABLE TEST; +> ok + +DROP DOMAIN D; +> ok + +CREATE TABLE TEST(A INT, B INT, C INT) AS VALUES (0, 0, 1), (0, 0, 3); +> ok + +CREATE TABLE S1(A INT, B INT) AS VALUES (1, 2); +> ok + +CREATE TABLE S2(A INT, B INT) AS VALUES (3, 4); +> ok + +UPDATE TEST SET (A, B) = (SELECT * FROM S1 WHERE C = A UNION SELECT * FROM S2 WHERE C = A); +> update count: 2 + +TABLE TEST; +> A B C +> - - - +> 1 2 1 +> 3 4 3 +> rows: 2 + +DROP TABLE TEST, S1, S2; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, V INT) AS SELECT X, X FROM SYSTEM_RANGE(1, 13); +> ok + +UPDATE TEST SET V = V + 1 WHERE ID <= 12 FETCH FIRST ROW ONLY; +> update count: 1 + +UPDATE TEST SET V = V + 1 WHERE ID <= 12 FETCH FIRST ROWS ONLY; +> update count: 1 + +UPDATE TEST SET V = V + 1 WHERE ID <= 12 FETCH NEXT ROW ONLY; +> update count: 1 + +UPDATE TEST SET V = V + 1 WHERE ID <= 12 FETCH NEXT ROWS ONLY; +> update count: 1 + +UPDATE TEST SET V = V + 1 WHERE ID <= 12 FETCH FIRST 2 ROW ONLY; +> update count: 2 + +UPDATE TEST SET V = V + 1 WHERE ID <= 12 FETCH FIRST 2 ROWS ONLY; +> update count: 2 + +UPDATE TEST SET V = V + 1 WHERE ID <= 12 FETCH NEXT 2 ROW ONLY; +> update count: 2 + +UPDATE TEST SET V = V + 1 WHERE ID <= 12 FETCH NEXT 2 ROWS ONLY; +> update count: 2 + +EXPLAIN UPDATE TEST SET V = V + 1 WHERE ID <= 12 FETCH FIRST 2 ROWS ONLY; +>> UPDATE "PUBLIC"."TEST" /* PUBLIC.PRIMARY_KEY_2: ID <= 12 */ SET "V" = "V" + 1 WHERE "ID" <= 12 FETCH FIRST 2 ROWS ONLY + +EXPLAIN UPDATE TEST SET V = V + 1 FETCH FIRST 1 ROW ONLY; +>> UPDATE "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ SET "V" = "V" + 1 FETCH FIRST ROW ONLY + +EXPLAIN UPDATE TEST SET V = V + 1; +>> UPDATE "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ SET "V" = "V" + 1 + +SELECT SUM(V) FROM TEST; +>> 103 + +UPDATE TEST SET V = V + 1 FETCH FIRST 100 ROWS ONLY; +> update count: 13 + +SELECT SUM(V) FROM TEST; +>> 116 + +-- legacy syntax +EXPLAIN UPDATE TEST SET V = V + 1 LIMIT 2; +>> UPDATE "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ SET "V" = "V" + 1 FETCH FIRST 2 ROWS ONLY + +UPDATE TEST SET V = V + 1 LIMIT 2; +> update count: 2 + +SELECT SUM(V) FROM TEST; +>> 118 + +DROP TABLE TEST; +> ok + +CREATE TABLE FOO (ID INT, VAL VARCHAR) AS VALUES(1, 'foo1'), (2, 'foo2'), (3, 'foo3'); +> ok + +CREATE TABLE BAR (ID INT, VAL VARCHAR) AS VALUES(1, 'bar1'), (3, 'bar3'), (4, 'bar4'); +> ok + +SET MODE PostgreSQL; +> ok + +UPDATE FOO SET VAL = BAR.VAL FROM BAR WHERE FOO.ID = BAR.ID; +> update count: 2 + +TABLE FOO; +> ID VAL +> -- ---- +> 1 bar1 +> 2 foo2 +> 3 bar3 +> rows: 3 + +UPDATE FOO SET BAR.VAL = FOO.VAL FROM BAR WHERE FOO.ID = BAR.ID; +> exception TABLE_OR_VIEW_NOT_FOUND_1 + +SET MODE Regular; +> ok + +CREATE TABLE DEST(ID INT, X INT, Y INT); +> ok + +INSERT INTO DEST VALUES (1, 10, 11), (2, 20, 21); +> update count: 2 + +CREATE TABLE SRC(ID INT, X INT, Y INT); +> ok + +INSERT INTO SRC VALUES (1, 100, 101); +> update count: 1 + +UPDATE DEST SET (X, Y) = (SELECT X, Y FROM SRC WHERE SRC.ID = DEST.ID); +> update count: 2 + +TABLE DEST; +> ID X Y +> -- ---- ---- +> 1 100 101 +> 2 null null +> rows: 2 + +DROP TABLE SRC, DEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/dml/with.sql b/h2/src/test/org/h2/test/scripts/dml/with.sql new file mode 100644 index 0000000..758127e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/dml/with.sql @@ -0,0 +1,245 @@ +-- 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 +-- + +create table folder(id int primary key, name varchar(255), parent int); +> ok + +insert into folder values(1, null, null), (2, 'bin', 1), (3, 'docs', 1), (4, 'html', 3), (5, 'javadoc', 3), (6, 'ext', 1), (7, 'service', 1), (8, 'src', 1), (9, 'docsrc', 8), (10, 'installer', 8), (11, 'main', 8), (12, 'META-INF', 11), (13, 'org', 11), (14, 'h2', 13), (15, 'test', 8), (16, 'tools', 8); +> update count: 16 + +with link(id, name, level) as (select id, name, 0 from folder where parent is null union all select folder.id, ifnull(link.name || '/', '') || folder.name, level + 1 from link inner join folder on link.id = folder.parent) select name from link where name is not null order by cast(id as int); +> NAME +> ----------------- +> bin +> docs +> docs/html +> docs/javadoc +> ext +> service +> src +> src/docsrc +> src/installer +> src/main +> src/main/META-INF +> src/main/org +> src/main/org/h2 +> src/test +> src/tools +> rows (ordered): 15 + +drop table folder; +> ok + +explain with recursive r(n) as ( + (select 1) union all (select n+1 from r where n < 3) +) +select n from r; +>> WITH RECURSIVE "PUBLIC"."R"("N") AS ( (SELECT 1) UNION ALL (SELECT "N" + 1 FROM "PUBLIC"."R" /* PUBLIC.R.tableScan */ WHERE "N" < 3) ) SELECT "N" FROM "PUBLIC"."R" "R" /* null */ + +explain with recursive "r"(n) as ( + (select 1) union all (select n+1 from "r" where n < 3) +) +select n from "r"; +>> WITH RECURSIVE "PUBLIC"."r"("N") AS ( (SELECT 1) UNION ALL (SELECT "N" + 1 FROM "PUBLIC"."r" /* PUBLIC.r.tableScan */ WHERE "N" < 3) ) SELECT "N" FROM "PUBLIC"."r" "r" /* null */ + +select sum(n) from ( + with recursive r(n) as ( + (select 1) union all (select n+1 from r where n < 3) + ) + select n from r +); +>> 6 + +select sum(n) from ( + with recursive "r"(n) as ( + (select 1) union all (select n+1 from "r" where n < 3) + ) + select n from "r" +); +>> 6 + +select sum(n) from (select 0) join ( + with recursive r(n) as ( + (select 1) union all (select n+1 from r where n < 3) + ) + select n from r +) on 1=1; +>> 6 + +select 0 from ( + select 0 where 0 in ( + with recursive r(n) as ( + (select 1) union all (select n+1 from r where n < 3) + ) + select n from r + ) +); +> 0 +> - +> rows: 0 + +with + r0(n,k) as (select -1, 0), + r1(n,k) as ((select 1, 0) union all (select n+1,k+1 from r1 where n <= 3)), + r2(n,k) as ((select 10,0) union all (select n+1,k+1 from r2 where n <= 13)) + select r1.k, r0.n as N0, r1.n AS N1, r2.n AS n2 from r0 inner join r1 ON r1.k= r0.k inner join r2 ON r1.k= r2.k; +> K N0 N1 N2 +> - -- -- -- +> 0 -1 1 10 +> rows: 1 + +CREATE SCHEMA SCH; +> ok + +CREATE FORCE VIEW TABLE_EXPRESSION SCH.R1(N) AS +(SELECT 1) +UNION ALL +(SELECT (N + 1) FROM SCH.R1 WHERE N < 3); +> ok + +CREATE VIEW SCH.R2(N) AS +(SELECT 1) +UNION ALL +(SELECT (N + 1) FROM SCH.R1 WHERE N < 3); +> ok + +SELECT * FROM SCH.R2; +> N +> - +> 1 +> 2 +> 3 +> rows: 3 + +WITH CTE_TEST AS (SELECT 1, 2) SELECT * FROM CTE_TEST; +> 1 2 +> - - +> 1 2 +> rows: 1 + +WITH CTE_TEST AS (SELECT 1, 2) (SELECT * FROM CTE_TEST); +> 1 2 +> - - +> 1 2 +> rows: 1 + +WITH CTE_TEST AS (SELECT 1, 2) ((SELECT * FROM CTE_TEST)); +> 1 2 +> - - +> 1 2 +> rows: 1 + +CREATE TABLE TEST(A INT, B INT) AS SELECT 1, 2; +> ok + +WITH CTE_TEST AS (TABLE TEST) ((SELECT * FROM CTE_TEST)); +> A B +> - - +> 1 2 +> rows: 1 + +WITH CTE_TEST AS (TABLE TEST) ((TABLE CTE_TEST)); +> A B +> - - +> 1 2 +> rows: 1 + +WITH CTE_TEST AS (VALUES (1, 2)) ((SELECT * FROM CTE_TEST)); +> C1 C2 +> -- -- +> 1 2 +> rows: 1 + +WITH CTE_TEST AS (TABLE TEST) ((SELECT A, B FROM CTE_TEST2)); +> exception TABLE_OR_VIEW_NOT_FOUND_1 + +WITH CTE_TEST AS (TABLE TEST) ((SELECT A, B, C FROM CTE_TEST)); +> exception COLUMN_NOT_FOUND_1 + +DROP TABLE TEST; +> ok + +WITH RECURSIVE V(V1, V2) AS ( + SELECT 0 V1, 1 V2 + UNION ALL + SELECT V1 + 1, V2 + 1 FROM V WHERE V2 < 4 +) +SELECT V1, V2, COUNT(*) FROM V +LEFT JOIN (SELECT T1 / T2 R FROM (VALUES (10, 0)) T(T1, T2) WHERE T2*T2*T2*T2*T2*T2 <> 0) X ON X.R > V.V1 AND X.R < V.V2 +GROUP BY V1, V2; +> V1 V2 COUNT(*) +> -- -- -------- +> 0 1 1 +> 1 2 1 +> 2 3 1 +> 3 4 1 +> rows: 4 + +EXPLAIN WITH RECURSIVE V(V1, V2) AS ( + SELECT 0 V1, 1 V2 + UNION ALL + SELECT V1 + 1, V2 + 1 FROM V WHERE V2 < 10 +) +SELECT V1, V2, COUNT(*) FROM V +LEFT JOIN (SELECT T1 / T2 R FROM (VALUES (10, 0)) T(T1, T2) WHERE T2*T2*T2*T2*T2*T2 <> 0) X ON X.R > V.V1 AND X.R < V.V2 +GROUP BY V1, V2; +>> WITH RECURSIVE "PUBLIC"."V"("V1", "V2") AS ( (SELECT 0 AS "V1", 1 AS "V2") UNION ALL (SELECT "V1" + 1, "V2" + 1 FROM "PUBLIC"."V" /* PUBLIC.V.tableScan */ WHERE "V2" < 10) ) SELECT "V1", "V2", COUNT(*) FROM "PUBLIC"."V" "V" /* null */ LEFT OUTER JOIN ( SELECT "T1" / "T2" AS "R" FROM (VALUES (10, 0)) "T"("T1", "T2") WHERE ((((("T2" * "T2") * "T2") * "T2") * "T2") * "T2") <> 0 ) "X" /* SELECT T1 / T2 AS R FROM (VALUES (10, 0)) T(T1, T2) /* table scan */ WHERE ((((((T2 * T2) * T2) * T2) * T2) * T2) <> 0) _LOCAL_AND_GLOBAL_ (((T1 / T2) >= ?1) AND ((T1 / T2) <= ?2)): R > V.V1 AND R < V.V2 */ ON ("X"."R" > "V"."V1") AND ("X"."R" < "V"."V2") GROUP BY "V1", "V2" + +-- Data change delta tables in WITH +CREATE TABLE TEST("VALUE" INT NOT NULL PRIMARY KEY); +> ok + +WITH W AS (SELECT NULL FROM FINAL TABLE (INSERT INTO TEST VALUES 1, 2)) +SELECT COUNT (*) FROM W; +>> 2 + +WITH W AS (SELECT NULL FROM FINAL TABLE (UPDATE TEST SET "VALUE" = 3 WHERE "VALUE" = 2)) +SELECT COUNT (*) FROM W; +>> 1 + +WITH W AS (SELECT NULL FROM FINAL TABLE (MERGE INTO TEST VALUES 4, 5)) +SELECT COUNT (*) FROM W; +>> 2 + +WITH W AS (SELECT NULL FROM OLD TABLE (DELETE FROM TEST WHERE "VALUE" = 4)) +SELECT COUNT (*) FROM W; +>> 1 + +SET MODE MySQL; +> ok + +WITH W AS (SELECT NULL FROM FINAL TABLE (REPLACE INTO TEST VALUES 4, 5)) +SELECT COUNT (*) FROM W; +>> 2 + +SET MODE Regular; +> ok + +DROP TABLE TEST; +> ok + +CREATE TABLE T(C INT); +> ok + +INSERT INTO T WITH W(C) AS (VALUES 1) SELECT C FROM W; +> update count: 1 + +TABLE W; +> exception TABLE_OR_VIEW_NOT_FOUND_1 + +TABLE T; +>> 1 + +DROP TABLE T; +> ok + +WITH T(X) AS (SELECT 1) +(SELECT 2 Y) UNION (SELECT 3 Z) UNION (SELECT * FROM T); +> Y +> - +> 1 +> 2 +> 3 +> rows: 3 diff --git a/h2/src/test/org/h2/test/scripts/dual.sql b/h2/src/test/org/h2/test/scripts/dual.sql new file mode 100644 index 0000000..9df679a --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/dual.sql @@ -0,0 +1,58 @@ +-- 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 +-- + +SELECT * FROM DUAL; +> +> +> +> rows: 1 + +CREATE TABLE DUAL(A INT); +> ok + +INSERT INTO DUAL VALUES (2); +> update count: 1 + +SELECT A FROM DUAL; +>> 2 + +SELECT * FROM SYS.DUAL; +> +> +> +> rows: 1 + +DROP TABLE DUAL; +> ok + +SET MODE DB2; +> ok + +SELECT * FROM SYSDUMMY1; +> +> +> +> rows: 1 + +CREATE TABLE SYSDUMMY1(A INT); +> ok + +INSERT INTO SYSDUMMY1 VALUES (2); +> update count: 1 + +SELECT A FROM SYSDUMMY1; +>> 2 + +SELECT * FROM SYSIBM.SYSDUMMY1; +> +> +> +> rows: 1 + +DROP TABLE SYSDUMMY1; +> ok + +SET MODE Regular; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/any.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/any.sql new file mode 100644 index 0000000..41b27d5 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/any.sql @@ -0,0 +1,33 @@ +-- 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 +-- + +CREATE TABLE TEST(A INT, B INT); +> ok + +INSERT INTO TEST VALUES (1, 1), (1, 3), (2, 1), (2, 5), (3, 4); +> update count: 5 + +SELECT A, ANY(B < 2), SOME(B > 3), BOOL_OR(B = 1), ANY(B = 1) FILTER (WHERE A = 1) FROM TEST GROUP BY A; +> A ANY(B < 2) ANY(B > 3) ANY(B = 1) ANY(B = 1) FILTER (WHERE A = 1) +> - ---------- ---------- ---------- ------------------------------- +> 1 TRUE FALSE TRUE TRUE +> 2 TRUE TRUE TRUE null +> 3 FALSE TRUE FALSE null +> rows: 3 + +DROP TABLE TEST; +> ok + +SELECT TRUE = (ANY((SELECT X > 0 FROM SYSTEM_RANGE(1, 1)))); +> TRUE = (ANY((SELECT X > 0 FROM SYSTEM_RANGE(1, 1)))) +> ---------------------------------------------------- +> TRUE +> rows: 1 + +SELECT TRUE = (ANY((SELECT X < 0 FROM SYSTEM_RANGE(1, 1)))); +> TRUE = (ANY((SELECT X < 0 FROM SYSTEM_RANGE(1, 1)))) +> ---------------------------------------------------- +> FALSE +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/array_agg.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/array_agg.sql new file mode 100644 index 0000000..ab39ce4 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/array_agg.sql @@ -0,0 +1,678 @@ +-- 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: Alex Nordlund +-- + +-- with filter condition + +create table test(v varchar); +> ok + +insert into test values ('1'), ('2'), ('3'), ('4'), ('5'), ('6'), ('7'), ('8'), ('9'); +> update count: 9 + +select array_agg(v order by v asc), + array_agg(v order by v desc) filter (where v >= '4') + from test where v >= '2'; +> ARRAY_AGG(V ORDER BY V) ARRAY_AGG(V ORDER BY V DESC) FILTER (WHERE V >= '4') +> ------------------------ ---------------------------------------------------- +> [2, 3, 4, 5, 6, 7, 8, 9] [9, 8, 7, 6, 5, 4] +> rows: 1 + +create index test_idx on test(v); +> ok + +select ARRAY_AGG(v order by v asc), + ARRAY_AGG(v order by v desc) filter (where v >= '4') + from test where v >= '2'; +> ARRAY_AGG(V ORDER BY V) ARRAY_AGG(V ORDER BY V DESC) FILTER (WHERE V >= '4') +> ------------------------ ---------------------------------------------------- +> [2, 3, 4, 5, 6, 7, 8, 9] [9, 8, 7, 6, 5, 4] +> rows: 1 + +select ARRAY_AGG(v order by v asc), + ARRAY_AGG(v order by v desc) filter (where v >= '4') + from test; +> ARRAY_AGG(V ORDER BY V) ARRAY_AGG(V ORDER BY V DESC) FILTER (WHERE V >= '4') +> --------------------------- ---------------------------------------------------- +> [1, 2, 3, 4, 5, 6, 7, 8, 9] [9, 8, 7, 6, 5, 4] +> rows: 1 + +drop table test; +> ok + +create table test (id int auto_increment primary key, v int); +> ok + +insert into test(v) values (7), (2), (8), (3), (7), (3), (9), (-1); +> update count: 8 + +select array_agg(v) from test; +> ARRAY_AGG(V) +> ------------------------- +> [7, 2, 8, 3, 7, 3, 9, -1] +> rows: 1 + +select array_agg(distinct v) from test; +> ARRAY_AGG(DISTINCT V) +> --------------------- +> [-1, 2, 3, 7, 8, 9] +> rows: 1 + +select array_agg(distinct v order by v desc) from test; +> ARRAY_AGG(DISTINCT V ORDER BY V DESC) +> ------------------------------------- +> [9, 8, 7, 3, 2, -1] +> rows: 1 + +drop table test; +> ok + +CREATE TABLE TEST (ID INT PRIMARY KEY, NAME VARCHAR); +> ok + +INSERT INTO TEST VALUES (1, 'a'), (2, 'a'), (3, 'b'), (4, 'c'), (5, 'c'), (6, 'c'); +> update count: 6 + +SELECT ARRAY_AGG(ID), NAME FROM TEST; +> exception MUST_GROUP_BY_COLUMN_1 + +SELECT ARRAY_AGG(ID ORDER BY ID), NAME FROM TEST GROUP BY NAME; +> ARRAY_AGG(ID ORDER BY ID) NAME +> ------------------------- ---- +> [1, 2] a +> [3] b +> [4, 5, 6] c +> rows: 3 + +SELECT ARRAY_AGG(ID ORDER BY ID) OVER (), NAME FROM TEST; +> ARRAY_AGG(ID ORDER BY ID) OVER () NAME +> --------------------------------- ---- +> [1, 2, 3, 4, 5, 6] a +> [1, 2, 3, 4, 5, 6] a +> [1, 2, 3, 4, 5, 6] b +> [1, 2, 3, 4, 5, 6] c +> [1, 2, 3, 4, 5, 6] c +> [1, 2, 3, 4, 5, 6] c +> rows: 6 + +SELECT ARRAY_AGG(ID ORDER BY ID) OVER (PARTITION BY NAME), NAME FROM TEST; +> ARRAY_AGG(ID ORDER BY ID) OVER (PARTITION BY NAME) NAME +> -------------------------------------------------- ---- +> [1, 2] a +> [1, 2] a +> [3] b +> [4, 5, 6] c +> [4, 5, 6] c +> [4, 5, 6] c +> rows: 6 + +SELECT + ARRAY_AGG(ID ORDER BY ID) FILTER (WHERE ID < 3 OR ID > 4) OVER (PARTITION BY NAME) A, + ARRAY_AGG(ID ORDER BY ID) FILTER (WHERE ID < 3 OR ID > 4) OVER (PARTITION BY NAME ORDER BY ID) AO, + ID, NAME FROM TEST ORDER BY ID; +> A AO ID NAME +> ------ ------ -- ---- +> [1, 2] [1] 1 a +> [1, 2] [1, 2] 2 a +> null null 3 b +> [5, 6] null 4 c +> [5, 6] [5] 5 c +> [5, 6] [5, 6] 6 c +> rows (ordered): 6 + +SELECT + ARRAY_AGG(ID ORDER BY ID) FILTER (WHERE ID < 3 OR ID > 4) + OVER (ORDER BY ID ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) A, + ID FROM TEST ORDER BY ID; +> A ID +> ------ -- +> [1, 2] 1 +> [1, 2] 2 +> [2] 3 +> [5] 4 +> [5, 6] 5 +> [5, 6] 6 +> rows (ordered): 6 + +SELECT ARRAY_AGG(SUM(ID)) OVER () FROM TEST; +> ARRAY_AGG(SUM(ID)) OVER () +> -------------------------- +> [21] +> rows: 1 + +SELECT ARRAY_AGG(ID ORDER BY ID) OVER() FROM TEST GROUP BY ID ORDER BY ID; +> ARRAY_AGG(ID ORDER BY ID) OVER () +> --------------------------------- +> [1, 2, 3, 4, 5, 6] +> [1, 2, 3, 4, 5, 6] +> [1, 2, 3, 4, 5, 6] +> [1, 2, 3, 4, 5, 6] +> [1, 2, 3, 4, 5, 6] +> [1, 2, 3, 4, 5, 6] +> rows (ordered): 6 + +SELECT ARRAY_AGG(NAME) OVER(PARTITION BY NAME) FROM TEST GROUP BY NAME; +> ARRAY_AGG(NAME) OVER (PARTITION BY NAME) +> ---------------------------------------- +> [a] +> [b] +> [c] +> rows: 3 + +SELECT ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) OVER (PARTITION BY NAME), NAME FROM TEST GROUP BY NAME; +> ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) OVER (PARTITION BY NAME) NAME +> ------------------------------------------------------------- ---- +> [[1, 2]] a +> [[3]] b +> [[4, 5, 6]] c +> rows: 3 + +SELECT ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) OVER (PARTITION BY NAME), NAME FROM TEST + WHERE ID <> 5 + GROUP BY NAME HAVING ARRAY_AGG(ID ORDER BY ID)[1] > 1 + QUALIFY ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) OVER (PARTITION BY NAME) <> ARRAY[ARRAY[3]]; +> ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) OVER (PARTITION BY NAME) NAME +> ------------------------------------------------------------- ---- +> [[4, 6]] c +> rows: 1 + +EXPLAIN + SELECT ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) OVER (PARTITION BY NAME), NAME FROM TEST + WHERE ID <> 5 + GROUP BY NAME HAVING ARRAY_AGG(ID ORDER BY ID)[1] > 1 + QUALIFY ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) OVER (PARTITION BY NAME) <> ARRAY[ARRAY[3]]; +>> SELECT ARRAY_AGG(ARRAY_AGG("ID" ORDER BY "ID")) OVER (PARTITION BY "NAME"), "NAME" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ WHERE "ID" <> 5 GROUP BY "NAME" HAVING ARRAY_AGG("ID" ORDER BY "ID")[1] > 1 QUALIFY ARRAY_AGG(ARRAY_AGG("ID" ORDER BY "ID")) OVER (PARTITION BY "NAME") <> ARRAY [ARRAY [3]] + +SELECT ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) OVER (PARTITION BY NAME), NAME FROM TEST + GROUP BY NAME ORDER BY NAME OFFSET 1 ROW; +> ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) OVER (PARTITION BY NAME) NAME +> ------------------------------------------------------------- ---- +> [[3]] b +> [[4, 5, 6]] c +> rows (ordered): 2 + +SELECT ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) FILTER (WHERE NAME > 'b') OVER (PARTITION BY NAME), NAME FROM TEST + GROUP BY NAME ORDER BY NAME; +> ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) FILTER (WHERE NAME > 'b') OVER (PARTITION BY NAME) NAME +> --------------------------------------------------------------------------------------- ---- +> null a +> null b +> [[4, 5, 6]] c +> rows (ordered): 3 + +SELECT ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) FILTER (WHERE NAME > 'c') OVER (PARTITION BY NAME), NAME FROM TEST + GROUP BY NAME ORDER BY NAME; +> ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) FILTER (WHERE NAME > 'c') OVER (PARTITION BY NAME) NAME +> --------------------------------------------------------------------------------------- ---- +> null a +> null b +> null c +> rows (ordered): 3 + +SELECT ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) FILTER (WHERE NAME > 'b') OVER () FROM TEST GROUP BY NAME ORDER BY NAME; +> ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) FILTER (WHERE NAME > 'b') OVER () +> ---------------------------------------------------------------------- +> [[4, 5, 6]] +> [[4, 5, 6]] +> [[4, 5, 6]] +> rows (ordered): 3 + +SELECT ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) FILTER (WHERE NAME > 'c') OVER () FROM TEST GROUP BY NAME ORDER BY NAME; +> ARRAY_AGG(ARRAY_AGG(ID ORDER BY ID)) FILTER (WHERE NAME > 'c') OVER () +> ---------------------------------------------------------------------- +> null +> null +> null +> rows (ordered): 3 + +SELECT ARRAY_AGG(ID) OVER() FROM TEST GROUP BY NAME; +> exception MUST_GROUP_BY_COLUMN_1 + +SELECT ARRAY_AGG(ID) OVER(PARTITION BY NAME ORDER BY ID), NAME FROM TEST; +> ARRAY_AGG(ID) OVER (PARTITION BY NAME ORDER BY ID) NAME +> -------------------------------------------------- ---- +> [1, 2] a +> [1] a +> [3] b +> [4, 5, 6] c +> [4, 5] c +> [4] c +> rows: 6 + +SELECT ARRAY_AGG(ID) OVER(PARTITION BY NAME ORDER BY ID DESC), NAME FROM TEST; +> ARRAY_AGG(ID) OVER (PARTITION BY NAME ORDER BY ID DESC) NAME +> ------------------------------------------------------- ---- +> [2, 1] a +> [2] a +> [3] b +> [6, 5, 4] c +> [6, 5] c +> [6] c +> rows: 6 + +SELECT + ARRAY_AGG(ID ORDER BY ID) OVER(PARTITION BY NAME ORDER BY ID DESC) A, + ARRAY_AGG(ID) OVER(PARTITION BY NAME ORDER BY ID DESC) D, + NAME FROM TEST; +> A D NAME +> --------- --------- ---- +> [1, 2] [2, 1] a +> [2] [2] a +> [3] [3] b +> [4, 5, 6] [6, 5, 4] c +> [5, 6] [6, 5] c +> [6] [6] c +> rows: 6 + +SELECT ARRAY_AGG(SUM(ID)) OVER(ORDER BY ID) FROM TEST GROUP BY ID; +> ARRAY_AGG(SUM(ID)) OVER (ORDER BY ID) +> ------------------------------------- +> [1, 2, 3, 4, 5, 6] +> [1, 2, 3, 4, 5] +> [1, 2, 3, 4] +> [1, 2, 3] +> [1, 2] +> [1] +> rows: 6 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID INT, G INT); +> ok + +INSERT INTO TEST VALUES + (1, 1), + (2, 2), + (3, 2), + (4, 2), + (5, 3); +> update count: 5 + +SELECT + ARRAY_AGG(ID) OVER (ORDER BY G RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) D, + ARRAY_AGG(ID) OVER (ORDER BY G RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW) R, + ARRAY_AGG(ID) OVER (ORDER BY G RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE GROUP) G, + ARRAY_AGG(ID) OVER (ORDER BY G RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE TIES) T, + ARRAY_AGG(ID) OVER (ORDER BY G RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE NO OTHERS) N + FROM TEST; +> D R G T N +> --------------- ------------ ------------ --------------- --------------- +> [1, 2, 3, 4, 5] [1, 2, 3, 4] [1, 2, 3, 4] [1, 2, 3, 4, 5] [1, 2, 3, 4, 5] +> [1, 2, 3, 4, 5] [1, 2, 3, 5] [1, 5] [1, 4, 5] [1, 2, 3, 4, 5] +> [1, 2, 3, 4, 5] [1, 2, 4, 5] [1, 5] [1, 3, 5] [1, 2, 3, 4, 5] +> [1, 2, 3, 4, 5] [1, 3, 4, 5] [1, 5] [1, 2, 5] [1, 2, 3, 4, 5] +> [1, 2, 3, 4, 5] [2, 3, 4, 5] [2, 3, 4, 5] [1, 2, 3, 4, 5] [1, 2, 3, 4, 5] +> rows: 5 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID INT, "VALUE" INT); +> ok + +INSERT INTO TEST VALUES + (1, 1), + (2, 1), + (3, 5), + (4, 8), + (5, 8), + (6, 8), + (7, 9), + (8, 9); +> update count: 8 + +SELECT *, + ARRAY_AGG(ID) OVER (ORDER BY "VALUE" ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) R_ID, + ARRAY_AGG("VALUE") OVER (ORDER BY "VALUE" ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) R_V, + ARRAY_AGG(ID) OVER (ORDER BY "VALUE" RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING) V_ID, + ARRAY_AGG("VALUE") OVER (ORDER BY "VALUE" RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING) V_V, + ARRAY_AGG("VALUE") OVER (ORDER BY "VALUE" DESC RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING) V_V_R, + ARRAY_AGG(ID) OVER (ORDER BY "VALUE" GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) G_ID, + ARRAY_AGG("VALUE") OVER (ORDER BY "VALUE" GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) G_V + FROM TEST; +> ID VALUE R_ID R_V V_ID V_V V_V_R G_ID G_V +> -- ----- --------- --------- --------------- --------------- --------------- ------------------ ------------------ +> 1 1 [1, 2] [1, 1] [1, 2] [1, 1] [1, 1] [1, 2, 3] [1, 1, 5] +> 2 1 [1, 2, 3] [1, 1, 5] [1, 2] [1, 1] [1, 1] [1, 2, 3] [1, 1, 5] +> 3 5 [2, 3, 4] [1, 5, 8] [3] [5] [5] [1, 2, 3, 4, 5, 6] [1, 1, 5, 8, 8, 8] +> 4 8 [3, 4, 5] [5, 8, 8] [4, 5, 6, 7, 8] [8, 8, 8, 9, 9] [9, 9, 8, 8, 8] [3, 4, 5, 6, 7, 8] [5, 8, 8, 8, 9, 9] +> 5 8 [4, 5, 6] [8, 8, 8] [4, 5, 6, 7, 8] [8, 8, 8, 9, 9] [9, 9, 8, 8, 8] [3, 4, 5, 6, 7, 8] [5, 8, 8, 8, 9, 9] +> 6 8 [5, 6, 7] [8, 8, 9] [4, 5, 6, 7, 8] [8, 8, 8, 9, 9] [9, 9, 8, 8, 8] [3, 4, 5, 6, 7, 8] [5, 8, 8, 8, 9, 9] +> 7 9 [6, 7, 8] [8, 9, 9] [4, 5, 6, 7, 8] [8, 8, 8, 9, 9] [9, 9, 8, 8, 8] [4, 5, 6, 7, 8] [8, 8, 8, 9, 9] +> 8 9 [7, 8] [9, 9] [4, 5, 6, 7, 8] [8, 8, 8, 9, 9] [9, 9, 8, 8, 8] [4, 5, 6, 7, 8] [8, 8, 8, 9, 9] +> rows: 8 + +SELECT *, + ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY "VALUE" RANGE BETWEEN 1 PRECEDING AND UNBOUNDED FOLLOWING) A1, + ARRAY_AGG(ID) OVER (ORDER BY "VALUE" RANGE BETWEEN UNBOUNDED PRECEDING AND 1 FOLLOWING) A2 + FROM TEST; +> ID VALUE A1 A2 +> -- ----- ------------------------ ------------------------ +> 1 1 [1, 2, 3, 4, 5, 6, 7, 8] [1, 2] +> 2 1 [1, 2, 3, 4, 5, 6, 7, 8] [1, 2] +> 3 5 [3, 4, 5, 6, 7, 8] [1, 2, 3] +> 4 8 [4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6, 7, 8] +> 5 8 [4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6, 7, 8] +> 6 8 [4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6, 7, 8] +> 7 9 [4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6, 7, 8] +> 8 9 [4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6, 7, 8] +> rows: 8 + +SELECT *, ARRAY_AGG(ID) OVER (ORDER BY "VALUE" ROWS -1 PRECEDING) FROM TEST; +> exception INVALID_PRECEDING_OR_FOLLOWING_1 + +SELECT *, ARRAY_AGG(ID) OVER (ORDER BY ID ROWS BETWEEN 2 PRECEDING AND 1 PRECEDING) FROM TEST FETCH FIRST 4 ROWS ONLY; +> ID VALUE ARRAY_AGG(ID) OVER (ORDER BY ID ROWS BETWEEN 2 PRECEDING AND 1 PRECEDING) +> -- ----- ------------------------------------------------------------------------- +> 1 1 null +> 2 1 [1] +> 3 5 [1, 2] +> 4 8 [2, 3] +> rows: 4 + +SELECT *, ARRAY_AGG(ID) OVER (ORDER BY ID ROWS BETWEEN 1 FOLLOWING AND 2 FOLLOWING) FROM TEST OFFSET 4 ROWS; +> ID VALUE ARRAY_AGG(ID) OVER (ORDER BY ID ROWS BETWEEN 1 FOLLOWING AND 2 FOLLOWING) +> -- ----- ------------------------------------------------------------------------- +> 5 8 [6, 7] +> 6 8 [7, 8] +> 7 9 [8] +> 8 9 null +> rows: 4 + +SELECT *, ARRAY_AGG(ID) OVER (ORDER BY ID RANGE BETWEEN 2 PRECEDING AND 1 PRECEDING) FROM TEST FETCH FIRST 4 ROWS ONLY; +> ID VALUE ARRAY_AGG(ID) OVER (ORDER BY ID RANGE BETWEEN 2 PRECEDING AND 1 PRECEDING) +> -- ----- -------------------------------------------------------------------------- +> 1 1 null +> 2 1 [1] +> 3 5 [1, 2] +> 4 8 [2, 3] +> rows: 4 + +SELECT *, ARRAY_AGG(ID) OVER (ORDER BY ID RANGE BETWEEN 1 FOLLOWING AND 2 FOLLOWING) FROM TEST OFFSET 4 ROWS; +> ID VALUE ARRAY_AGG(ID) OVER (ORDER BY ID RANGE BETWEEN 1 FOLLOWING AND 2 FOLLOWING) +> -- ----- -------------------------------------------------------------------------- +> 5 8 [6, 7] +> 6 8 [7, 8] +> 7 9 [8] +> 8 9 null +> rows: 4 + +SELECT *, + ARRAY_AGG(ID) OVER (ORDER BY "VALUE" GROUPS BETWEEN 0 PRECEDING AND 0 FOLLOWING) N, + ARRAY_AGG(ID) OVER (ORDER BY "VALUE" GROUPS BETWEEN 0 PRECEDING AND 0 FOLLOWING EXCLUDE TIES) T, + ARRAY_AGG(ID) OVER (ORDER BY "VALUE" GROUPS BETWEEN 1 PRECEDING AND 0 FOLLOWING EXCLUDE TIES) T1 + FROM TEST; +> ID VALUE N T T1 +> -- ----- --------- --- ------------ +> 1 1 [1, 2] [1] [1] +> 2 1 [1, 2] [2] [2] +> 3 5 [3] [3] [1, 2, 3] +> 4 8 [4, 5, 6] [4] [3, 4] +> 5 8 [4, 5, 6] [5] [3, 5] +> 6 8 [4, 5, 6] [6] [3, 6] +> 7 9 [7, 8] [7] [4, 5, 6, 7] +> 8 9 [7, 8] [8] [4, 5, 6, 8] +> rows: 8 + +SELECT *, + ARRAY_AGG(ID) OVER (ORDER BY "VALUE" GROUPS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) U_P, + ARRAY_AGG(ID) OVER (ORDER BY "VALUE" GROUPS BETWEEN 2 PRECEDING AND 1 PRECEDING) P, + ARRAY_AGG(ID) OVER (ORDER BY "VALUE" GROUPS BETWEEN 1 FOLLOWING AND 2 FOLLOWING) F, + ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY "VALUE" GROUPS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING) U_F + FROM TEST; +> ID VALUE U_P P F U_F +> -- ----- ------------------ ------------ --------------- ------------------ +> 1 1 null null [3, 4, 5, 6] [3, 4, 5, 6, 7, 8] +> 2 1 null null [3, 4, 5, 6] [3, 4, 5, 6, 7, 8] +> 3 5 [1, 2] [1, 2] [4, 5, 6, 7, 8] [4, 5, 6, 7, 8] +> 4 8 [1, 2, 3] [1, 2, 3] [7, 8] [7, 8] +> 5 8 [1, 2, 3] [1, 2, 3] [7, 8] [7, 8] +> 6 8 [1, 2, 3] [1, 2, 3] [7, 8] [7, 8] +> 7 9 [1, 2, 3, 4, 5, 6] [3, 4, 5, 6] null null +> 8 9 [1, 2, 3, 4, 5, 6] [3, 4, 5, 6] null null +> rows: 8 + +SELECT *, + ARRAY_AGG(ID) OVER (ORDER BY "VALUE" GROUPS BETWEEN 1 PRECEDING AND 0 PRECEDING) P, + ARRAY_AGG(ID) OVER (ORDER BY "VALUE" GROUPS BETWEEN 0 FOLLOWING AND 1 FOLLOWING) F + FROM TEST; +> ID VALUE P F +> -- ----- --------------- --------------- +> 1 1 [1, 2] [1, 2, 3] +> 2 1 [1, 2] [1, 2, 3] +> 3 5 [1, 2, 3] [3, 4, 5, 6] +> 4 8 [3, 4, 5, 6] [4, 5, 6, 7, 8] +> 5 8 [3, 4, 5, 6] [4, 5, 6, 7, 8] +> 6 8 [3, 4, 5, 6] [4, 5, 6, 7, 8] +> 7 9 [4, 5, 6, 7, 8] [7, 8] +> 8 9 [4, 5, 6, 7, 8] [7, 8] +> rows: 8 + +SELECT ID, "VALUE", + ARRAY_AGG(ID) OVER (ORDER BY "VALUE" ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING EXCLUDE GROUP) G, + ARRAY_AGG(ID) OVER (ORDER BY "VALUE" ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING EXCLUDE TIES) T + FROM TEST; +> ID VALUE G T +> -- ----- ------------ --------------- +> 1 1 [3] [1, 3] +> 2 1 [3, 4] [2, 3, 4] +> 3 5 [1, 2, 4, 5] [1, 2, 3, 4, 5] +> 4 8 [2, 3] [2, 3, 4] +> 5 8 [3, 7] [3, 5, 7] +> 6 8 [7, 8] [6, 7, 8] +> 7 9 [5, 6] [5, 6, 7] +> 8 9 [6] [6, 8] +> rows: 8 + +SELECT ID, "VALUE", ARRAY_AGG(ID) OVER(ORDER BY "VALUE" ROWS BETWEEN 1 FOLLOWING AND 2 FOLLOWING EXCLUDE GROUP) G + FROM TEST ORDER BY ID FETCH FIRST 3 ROWS ONLY; +> ID VALUE G +> -- ----- ------ +> 1 1 [3] +> 2 1 [3, 4] +> 3 5 [4, 5] +> rows (ordered): 3 + +SELECT ID, "VALUE", ARRAY_AGG(ID) OVER(ORDER BY "VALUE" ROWS BETWEEN 2 PRECEDING AND 1 PRECEDING EXCLUDE GROUP) G + FROM TEST ORDER BY ID FETCH FIRST 3 ROWS ONLY; +> ID VALUE G +> -- ----- ------ +> 1 1 null +> 2 1 null +> 3 5 [1, 2] +> rows (ordered): 3 + +SELECT ID, "VALUE", ARRAY_AGG(ID) OVER (ORDER BY "VALUE" RANGE BETWEEN 2 PRECEDING AND 1 PRECEDING) A + FROM TEST; +> ID VALUE A +> -- ----- --------- +> 1 1 null +> 2 1 null +> 3 5 null +> 4 8 null +> 5 8 null +> 6 8 null +> 7 9 [4, 5, 6] +> 8 9 [4, 5, 6] +> rows: 8 + +SELECT ID, "VALUE", + ARRAY_AGG(ID) OVER (ORDER BY "VALUE" ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) CP, + ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY "VALUE" ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) CF, + ARRAY_AGG(ID) OVER (ORDER BY "VALUE" RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) RP, + ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY "VALUE" RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) RF, + ARRAY_AGG(ID) OVER (ORDER BY "VALUE" GROUPS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) GP, + ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY "VALUE" GROUPS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) GF + FROM TEST; +> ID VALUE CP CF RP RF GP GF +> -- ----- ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ +> 1 1 [1] [1, 2, 3, 4, 5, 6, 7, 8] [1, 2] [1, 2, 3, 4, 5, 6, 7, 8] [1, 2] [1, 2, 3, 4, 5, 6, 7, 8] +> 2 1 [1, 2] [2, 3, 4, 5, 6, 7, 8] [1, 2] [1, 2, 3, 4, 5, 6, 7, 8] [1, 2] [1, 2, 3, 4, 5, 6, 7, 8] +> 3 5 [1, 2, 3] [3, 4, 5, 6, 7, 8] [1, 2, 3] [3, 4, 5, 6, 7, 8] [1, 2, 3] [3, 4, 5, 6, 7, 8] +> 4 8 [1, 2, 3, 4] [4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6] [4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6] [4, 5, 6, 7, 8] +> 5 8 [1, 2, 3, 4, 5] [5, 6, 7, 8] [1, 2, 3, 4, 5, 6] [4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6] [4, 5, 6, 7, 8] +> 6 8 [1, 2, 3, 4, 5, 6] [6, 7, 8] [1, 2, 3, 4, 5, 6] [4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6] [4, 5, 6, 7, 8] +> 7 9 [1, 2, 3, 4, 5, 6, 7] [7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [7, 8] +> 8 9 [1, 2, 3, 4, 5, 6, 7, 8] [8] [1, 2, 3, 4, 5, 6, 7, 8] [7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [7, 8] +> rows: 8 + +SELECT *, ARRAY_AGG(ID) OVER (ORDER BY ID RANGE BETWEEN CURRENT ROW AND 1 PRECEDING) FROM TEST; +> exception SYNTAX_ERROR_1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST (ID INT, "VALUE" INT); +> ok + +INSERT INTO TEST VALUES + (1, 1), + (2, 1), + (3, 2), + (4, 2), + (5, 3), + (6, 3), + (7, 4), + (8, 4); +> update count: 8 + +SELECT *, ARRAY_AGG(ID) OVER (ORDER BY "VALUE" RANGE BETWEEN 2 PRECEDING AND 1 PRECEDING) FROM TEST; +> ID VALUE ARRAY_AGG(ID) OVER (ORDER BY "VALUE" RANGE BETWEEN 2 PRECEDING AND 1 PRECEDING) +> -- ----- ------------------------------------------------------------------------------- +> 1 1 null +> 2 1 null +> 3 2 [1, 2] +> 4 2 [1, 2] +> 5 3 [1, 2, 3, 4] +> 6 3 [1, 2, 3, 4] +> 7 4 [3, 4, 5, 6] +> 8 4 [3, 4, 5, 6] +> rows: 8 + +SELECT *, ARRAY_AGG(ID) OVER (ORDER BY "VALUE" RANGE BETWEEN 1 FOLLOWING AND 2 FOLLOWING) FROM TEST; +> ID VALUE ARRAY_AGG(ID) OVER (ORDER BY "VALUE" RANGE BETWEEN 1 FOLLOWING AND 2 FOLLOWING) +> -- ----- ------------------------------------------------------------------------------- +> 1 1 [3, 4, 5, 6] +> 2 1 [3, 4, 5, 6] +> 3 2 [5, 6, 7, 8] +> 4 2 [5, 6, 7, 8] +> 5 3 [7, 8] +> 6 3 [7, 8] +> 7 4 null +> 8 4 null +> rows: 8 + +SELECT ID, "VALUE", ARRAY_AGG(ID) OVER (ORDER BY "VALUE" RANGE BETWEEN 2 PRECEDING AND 1 PRECEDING EXCLUDE CURRENT ROW) A + FROM TEST; +> ID VALUE A +> -- ----- ------------ +> 1 1 null +> 2 1 null +> 3 2 [1, 2] +> 4 2 [1, 2] +> 5 3 [1, 2, 3, 4] +> 6 3 [1, 2, 3, 4] +> 7 4 [3, 4, 5, 6] +> 8 4 [3, 4, 5, 6] +> rows: 8 + +SELECT ID, "VALUE", ARRAY_AGG(ID) OVER (ORDER BY "VALUE" RANGE BETWEEN 1 FOLLOWING AND 1 FOLLOWING EXCLUDE CURRENT ROW) A + FROM TEST; +> ID VALUE A +> -- ----- ------ +> 1 1 [3, 4] +> 2 1 [3, 4] +> 3 2 [5, 6] +> 4 2 [5, 6] +> 5 3 [7, 8] +> 6 3 [7, 8] +> 7 4 null +> 8 4 null +> rows: 8 + +SELECT ID, "VALUE", + ARRAY_AGG(ID) OVER (ORDER BY "VALUE" ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) CP, + ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY "VALUE" ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) CF, + ARRAY_AGG(ID) OVER (ORDER BY "VALUE" RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) RP, + ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY "VALUE" RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) RF, + ARRAY_AGG(ID) OVER (ORDER BY "VALUE" GROUPS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) GP, + ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY "VALUE" GROUPS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) GF + FROM TEST; +> ID VALUE CP CF RP RF GP GF +> -- ----- ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ ------------------------ +> 1 1 [1] [1, 2, 3, 4, 5, 6, 7, 8] [1, 2] [1, 2, 3, 4, 5, 6, 7, 8] [1, 2] [1, 2, 3, 4, 5, 6, 7, 8] +> 2 1 [1, 2] [2, 3, 4, 5, 6, 7, 8] [1, 2] [1, 2, 3, 4, 5, 6, 7, 8] [1, 2] [1, 2, 3, 4, 5, 6, 7, 8] +> 3 2 [1, 2, 3] [3, 4, 5, 6, 7, 8] [1, 2, 3, 4] [3, 4, 5, 6, 7, 8] [1, 2, 3, 4] [3, 4, 5, 6, 7, 8] +> 4 2 [1, 2, 3, 4] [4, 5, 6, 7, 8] [1, 2, 3, 4] [3, 4, 5, 6, 7, 8] [1, 2, 3, 4] [3, 4, 5, 6, 7, 8] +> 5 3 [1, 2, 3, 4, 5] [5, 6, 7, 8] [1, 2, 3, 4, 5, 6] [5, 6, 7, 8] [1, 2, 3, 4, 5, 6] [5, 6, 7, 8] +> 6 3 [1, 2, 3, 4, 5, 6] [6, 7, 8] [1, 2, 3, 4, 5, 6] [5, 6, 7, 8] [1, 2, 3, 4, 5, 6] [5, 6, 7, 8] +> 7 4 [1, 2, 3, 4, 5, 6, 7] [7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [7, 8] +> 8 4 [1, 2, 3, 4, 5, 6, 7, 8] [8] [1, 2, 3, 4, 5, 6, 7, 8] [7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [7, 8] +> rows: 8 + +SELECT ID, "VALUE", + ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY ID RANGE BETWEEN UNBOUNDED PRECEDING AND "VALUE" FOLLOWING) RG, + ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY ID RANGE BETWEEN "VALUE" PRECEDING AND UNBOUNDED FOLLOWING) RGR, + ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND "VALUE" FOLLOWING) R, + ARRAY_AGG(ID ORDER BY ID) OVER (ORDER BY ID ROWS BETWEEN "VALUE" PRECEDING AND UNBOUNDED FOLLOWING) RR + FROM TEST; +> ID VALUE RG RGR R RR +> -- ----- ------------------------ ------------------------ ------------------------ ------------------------ +> 1 1 [1, 2] [1, 2, 3, 4, 5, 6, 7, 8] [1, 2] [1, 2, 3, 4, 5, 6, 7, 8] +> 2 1 [1, 2, 3] [1, 2, 3, 4, 5, 6, 7, 8] [1, 2, 3] [1, 2, 3, 4, 5, 6, 7, 8] +> 3 2 [1, 2, 3, 4, 5] [1, 2, 3, 4, 5, 6, 7, 8] [1, 2, 3, 4, 5] [1, 2, 3, 4, 5, 6, 7, 8] +> 4 2 [1, 2, 3, 4, 5, 6] [2, 3, 4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6] [2, 3, 4, 5, 6, 7, 8] +> 5 3 [1, 2, 3, 4, 5, 6, 7, 8] [2, 3, 4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [2, 3, 4, 5, 6, 7, 8] +> 6 3 [1, 2, 3, 4, 5, 6, 7, 8] [3, 4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [3, 4, 5, 6, 7, 8] +> 7 4 [1, 2, 3, 4, 5, 6, 7, 8] [3, 4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [3, 4, 5, 6, 7, 8] +> 8 4 [1, 2, 3, 4, 5, 6, 7, 8] [4, 5, 6, 7, 8] [1, 2, 3, 4, 5, 6, 7, 8] [4, 5, 6, 7, 8] +> rows: 8 + +SELECT ID, "VALUE", + ARRAY_AGG(ID ORDER BY ID) OVER + (PARTITION BY "VALUE" ORDER BY ID ROWS BETWEEN "VALUE" / 3 PRECEDING AND "VALUE" / 3 FOLLOWING) A, + ARRAY_AGG(ID ORDER BY ID) OVER + (PARTITION BY "VALUE" ORDER BY ID ROWS BETWEEN UNBOUNDED PRECEDING AND "VALUE" / 3 FOLLOWING) AP, + ARRAY_AGG(ID ORDER BY ID) OVER + (PARTITION BY "VALUE" ORDER BY ID ROWS BETWEEN "VALUE" / 3 PRECEDING AND UNBOUNDED FOLLOWING) AF + FROM TEST; +> ID VALUE A AP AF +> -- ----- ------ ------ ------ +> 1 1 [1] [1] [1, 2] +> 2 1 [2] [1, 2] [2] +> 3 2 [3] [3] [3, 4] +> 4 2 [4] [3, 4] [4] +> 5 3 [5, 6] [5, 6] [5, 6] +> 6 3 [5, 6] [5, 6] [5, 6] +> 7 4 [7, 8] [7, 8] [7, 8] +> 8 4 [7, 8] [7, 8] [7, 8] +> rows: 8 + +INSERT INTO TEST VALUES (9, NULL); +> update count: 1 + +SELECT ARRAY_AGG("VALUE") FROM TEST; +>> [1, 1, 2, 2, 3, 3, 4, 4, null] + +SELECT ARRAY_AGG("VALUE" ORDER BY ID) FROM TEST; +>> [1, 1, 2, 2, 3, 3, 4, 4, null] + +SELECT ARRAY_AGG("VALUE" ORDER BY ID) FILTER (WHERE "VALUE" IS NOT NULL) FROM TEST; +>> [1, 1, 2, 2, 3, 3, 4, 4] + +SELECT ARRAY_AGG("VALUE" ORDER BY "VALUE") FROM TEST; +>> [null, 1, 1, 2, 2, 3, 3, 4, 4] + +SELECT ARRAY_AGG("VALUE" ORDER BY "VALUE" NULLS LAST) FROM TEST; +>> [1, 1, 2, 2, 3, 3, 4, 4, null] + +DROP TABLE TEST; +> ok + +SELECT ARRAY_AGG(DISTINCT A ORDER BY B) FROM (VALUES (4, 3), (5, 1), (5, 2)) T(A, B); +>> [5, 4] + +EXPLAIN SELECT ARRAY_AGG(A ORDER BY 'a') FROM (VALUES 1, 2) T(A); +>> SELECT ARRAY_AGG("A") FROM (VALUES (1), (2)) "T"("A") /* table scan */ diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/avg.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/avg.sql new file mode 100644 index 0000000..1b70b6e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/avg.sql @@ -0,0 +1,136 @@ +-- 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 +-- + +select avg(cast(x as int)) from system_range(2147483547, 2147483637); +>> 2.147483592E9 + +select avg(x) from system_range(9223372036854775707, 9223372036854775797); +>> 9223372036854775752.0000000000 + +select avg(cast(100 as tinyint)) from system_range(1, 1000); +>> 100.0 + +select avg(cast(100 as smallint)) from system_range(1, 1000); +>> 100.0 + +-- with filter condition + +create table test(v int); +> ok + +insert into test values (10), (20), (30), (40), (50), (60), (70), (80), (90), (100), (110), (120); +> update count: 12 + +select avg(v), avg(v) filter (where v >= 40) from test where v <= 100; +> AVG(V) AVG(V) FILTER (WHERE V >= 40) +> ------ ----------------------------- +> 55.0 70.0 +> rows: 1 + +create index test_idx on test(v); +> ok + +select avg(v), avg(v) filter (where v >= 40) from test where v <= 100; +> AVG(V) AVG(V) FILTER (WHERE V >= 40) +> ------ ----------------------------- +> 55.0 70.0 +> rows: 1 + +drop table test; +> ok + +CREATE TABLE S( + N1 TINYINT, + N2 SMALLINT, + N4 INTEGER, + N8 BIGINT, + N NUMERIC(10, 2), + F4 REAL, + F8 DOUBLE PRECISION, + D DECFLOAT(10), + I1 INTERVAL YEAR(3), + I2 INTERVAL MONTH(3), + I3 INTERVAL DAY(3), + I4 INTERVAL HOUR(3), + I5 INTERVAL MINUTE(3), + I6 INTERVAL SECOND(2), + I7 INTERVAL YEAR(3) TO MONTH, + I8 INTERVAL DAY(3) TO HOUR, + I9 INTERVAL DAY(3) TO MINUTE, + I10 INTERVAL DAY(3) TO SECOND(2), + I11 INTERVAL HOUR(3) TO MINUTE, + I12 INTERVAL HOUR(3) TO SECOND(2), + I13 INTERVAL MINUTE(3) TO SECOND(2)); +> ok + +CREATE TABLE A AS SELECT + AVG(N1) N1, + AVG(N2) N2, + AVG(N4) N4, + AVG(N8) N8, + AVG(N) N, + AVG(F4) F4, + AVG(F8) F8, + AVG(D) D, + AVG(I1) I1, + AVG(I2) I2, + AVG(I3) I3, + AVG(I4) I4, + AVG(I5) I5, + AVG(I6) I6, + AVG(I7) I7, + AVG(I8) I8, + AVG(I9) I9, + AVG(I10) I10, + AVG(I11) I11, + AVG(I12) I12, + AVG(I13) I13 + FROM S; +> ok + +SELECT COLUMN_NAME, DATA_TYPE_SQL('PUBLIC', 'A', 'TABLE', DTD_IDENTIFIER) TYPE FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'A' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME TYPE +> ----------- ------------------------------- +> N1 DOUBLE PRECISION +> N2 DOUBLE PRECISION +> N4 DOUBLE PRECISION +> N8 NUMERIC(29, 10) +> N NUMERIC(20, 12) +> F4 DOUBLE PRECISION +> F8 DECFLOAT(27) +> D DECFLOAT(20) +> I1 INTERVAL YEAR(3) TO MONTH +> I2 INTERVAL MONTH(3) +> I3 INTERVAL DAY(3) TO SECOND(9) +> I4 INTERVAL HOUR(3) TO SECOND(9) +> I5 INTERVAL MINUTE(3) TO SECOND(9) +> I6 INTERVAL SECOND(2, 9) +> I7 INTERVAL YEAR(3) TO MONTH +> I8 INTERVAL DAY(3) TO SECOND(9) +> I9 INTERVAL DAY(3) TO SECOND(9) +> I10 INTERVAL DAY(3) TO SECOND(9) +> I11 INTERVAL HOUR(3) TO SECOND(9) +> I12 INTERVAL HOUR(3) TO SECOND(9) +> I13 INTERVAL MINUTE(3) TO SECOND(9) +> rows (ordered): 21 + +DROP TABLE S, A; +> ok + +SELECT AVG(X) FROM (VALUES INTERVAL '1' DAY, INTERVAL '2' DAY) T(X); +>> INTERVAL '1 12:00:00' DAY TO SECOND + +SELECT AVG(X) FROM (VALUES CAST(1 AS NUMERIC(1)), CAST(2 AS NUMERIC(1))) T(X); +>> 1.5000000000 + +SELECT AVG(I) FROM (VALUES 9e99999 - 1, 1e99999 + 1) T(I); +>> 5E+99999 + +SELECT AVG(I) = 5E99999 FROM (VALUES CAST(9e99999 - 1 AS NUMERIC(100000)), CAST(1e99999 + 1 AS NUMERIC(100000))) T(I); +>> TRUE + +SELECT AVG(I) FROM (VALUES INTERVAL '999999999999999999' SECOND, INTERVAL '1' SECOND) T(I); +>> INTERVAL '500000000000000000' SECOND 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 new file mode 100644 index 0000000..5221263 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/bit_and_agg.sql @@ -0,0 +1,48 @@ +-- 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 +-- + +-- with filter condition + +create table test(v bigint); +> ok + +insert into test values + (0xfffffffffff0), (0xffffffffff0f), (0xfffffffff0ff), (0xffffffff0fff), + (0xfffffff0ffff), (0xffffff0fffff), (0xfffff0ffffff), (0xffff0fffffff), + (0xfff0ffffffff), (0xff0fffffffff), (0xf0ffffffffff), (0x0fffffffffff); +> update count: 12 + +select BIT_AND_AGG(v), BIT_AND_AGG(v) filter (where v <= 0xffffffff0fff) from test where v >= 0xff0fffffffff; +> BIT_AND_AGG(V) BIT_AND_AGG(V) FILTER (WHERE V <= 281474976649215) +> --------------- -------------------------------------------------- +> 280375465082880 280375465086975 +> rows: 1 + +SELECT BIT_NAND_AGG(V), BIT_NAND_AGG(V) FILTER (WHERE V <= 0xffffffff0fff) FROM TEST WHERE V >= 0xff0fffffffff; +> BIT_NAND_AGG(V) BIT_NAND_AGG(V) FILTER (WHERE V <= 281474976649215) +> ---------------- --------------------------------------------------- +> -280375465082881 -280375465086976 +> rows: 1 + +create index test_idx on test(v); +> ok + +select BIT_AND_AGG(v), BIT_AND_AGG(v) filter (where v <= 0xffffffff0fff) from test where v >= 0xff0fffffffff; +> BIT_AND_AGG(V) BIT_AND_AGG(V) FILTER (WHERE V <= 281474976649215) +> --------------- -------------------------------------------------- +> 280375465082880 280375465086975 +> rows: 1 + +SELECT BIT_NAND_AGG(V), BIT_NAND_AGG(V) FILTER (WHERE V <= 0xffffffff0fff) FROM TEST WHERE V >= 0xff0fffffffff; +> BIT_NAND_AGG(V) BIT_NAND_AGG(V) FILTER (WHERE V <= 281474976649215) +> ---------------- --------------------------------------------------- +> -280375465082881 -280375465086976 +> rows: 1 + +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 */ + +drop table test; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/bit_or_agg.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/bit_or_agg.sql new file mode 100644 index 0000000..ba91746 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/bit_or_agg.sql @@ -0,0 +1,45 @@ +-- 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 +-- + +-- with filter condition + +create table test(v bigint); +> ok + +insert into test values (1), (2), (4), (8), (16), (32), (64), (128), (256), (512), (1024), (2048); +> update count: 12 + +select BIT_OR_AGG(v), BIT_OR_AGG(v) filter (where v >= 8) from test where v <= 512; +> BIT_OR_AGG(V) BIT_OR_AGG(V) FILTER (WHERE V >= 8) +> ------------- ----------------------------------- +> 1023 1016 +> rows: 1 + +SELECT BIT_NOR_AGG(V), BIT_NOR_AGG(V) FILTER (WHERE V >= 8) FROM TEST WHERE V <= 512; +> BIT_NOR_AGG(V) BIT_NOR_AGG(V) FILTER (WHERE V >= 8) +> -------------- ------------------------------------ +> -1024 -1017 +> rows: 1 + +create index test_idx on test(v); +> ok + +select BIT_OR_AGG(v), BIT_OR_AGG(v) filter (where v >= 8) from test where v <= 512; +> BIT_OR_AGG(V) BIT_OR_AGG(V) FILTER (WHERE V >= 8) +> ------------- ----------------------------------- +> 1023 1016 +> rows: 1 + +SELECT BIT_NOR_AGG(V), BIT_NOR_AGG(V) FILTER (WHERE V >= 8) FROM TEST WHERE V <= 512; +> BIT_NOR_AGG(V) BIT_NOR_AGG(V) FILTER (WHERE V >= 8) +> -------------- ------------------------------------ +> -1024 -1017 +> rows: 1 + +EXPLAIN SELECT BITNOT(BIT_OR_AGG(V)), BITNOT(BIT_NOR_AGG(V)) FROM TEST; +>> SELECT BIT_NOR_AGG("V"), BIT_OR_AGG("V") FROM "PUBLIC"."TEST" /* PUBLIC.TEST_IDX */ + +drop table test; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/bit_xor_agg.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/bit_xor_agg.sql new file mode 100644 index 0000000..1092a4d --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/bit_xor_agg.sql @@ -0,0 +1,25 @@ +-- 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 +-- + +SELECT BIT_XOR_AGG(V), BIT_XOR_AGG(DISTINCT V), BIT_XOR_AGG(V) FILTER (WHERE V <> 1) FROM (VALUES 1, 1, 2, 3, 4) T(V); +> BIT_XOR_AGG(V) BIT_XOR_AGG(DISTINCT V) BIT_XOR_AGG(V) FILTER (WHERE V <> 1) +> -------------- ----------------------- ------------------------------------ +> 5 4 5 +> rows: 1 + +SELECT BIT_XNOR_AGG(V), BIT_XNOR_AGG(DISTINCT V), BIT_XNOR_AGG(V) FILTER (WHERE V <> 1) FROM (VALUES 1, 1, 2, 3, 4) T(V); +> BIT_XNOR_AGG(V) BIT_XNOR_AGG(DISTINCT V) BIT_XNOR_AGG(V) FILTER (WHERE V <> 1) +> --------------- ------------------------ ------------------------------------- +> -6 -5 -6 +> rows: 1 + +CREATE TABLE TEST(V BIGINT); +> ok + +EXPLAIN SELECT BITNOT(BIT_XOR_AGG(V)), BITNOT(BIT_XNOR_AGG(V)) FROM TEST; +>> SELECT BIT_XNOR_AGG("V"), BIT_XOR_AGG("V") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/corr.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/corr.sql new file mode 100644 index 0000000..45a9fb3 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/corr.sql @@ -0,0 +1,28 @@ +-- 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 +-- + +SELECT CORR(Y, X) OVER (ORDER BY R) FROM (VALUES + (1, NULL, 1), + (2, 1, NULL), + (3, NULL, NULL), + (4, -3, -2), + (5, -3, -1), + (6, 10, 9), + (7, 10, 10), + (8, 10, 11), + (9, 11, 7) +) T(R, Y, X) ORDER BY R; +> CORR(Y, X) OVER (ORDER BY R) +> ---------------------------- +> null +> null +> null +> null +> null +> 0.9966158955401239 +> 0.9958932064677037 +> 0.9922153572367626 +> 0.9582302043304856 +> rows (ordered): 9 diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/count.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/count.sql new file mode 100644 index 0000000..1d151de --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/count.sql @@ -0,0 +1,235 @@ +-- 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 +-- + +-- with filter condition + +create table test(v int); +> ok + +insert into test values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11), (12), (null); +> update count: 13 + +select count(v), count(v) filter (where v >= 4) from test where v <= 10; +> COUNT(V) COUNT(V) FILTER (WHERE V >= 4) +> -------- ------------------------------ +> 10 7 +> rows: 1 + +select count(*), count(*) filter (where v >= 4) from test; +> COUNT(*) COUNT(*) FILTER (WHERE V >= 4) +> -------- ------------------------------ +> 13 9 +> rows: 1 + +select count(*), count(*) filter (where v >= 4) from test where v <= 10; +> COUNT(*) COUNT(*) FILTER (WHERE V >= 4) +> -------- ------------------------------ +> 10 7 +> rows: 1 + +create index test_idx on test(v); +> ok + +select count(v), count(v) filter (where v >= 4) from test where v <= 10; +> COUNT(V) COUNT(V) FILTER (WHERE V >= 4) +> -------- ------------------------------ +> 10 7 +> rows: 1 + +select count(v), count(v) filter (where v >= 4) from test; +> COUNT(V) COUNT(V) FILTER (WHERE V >= 4) +> -------- ------------------------------ +> 12 9 +> rows: 1 + +drop table test; +> ok + +CREATE TABLE TEST (ID INT PRIMARY KEY, NAME VARCHAR); +> ok + +INSERT INTO TEST VALUES (1, 'b'), (3, 'a'); +> update count: 2 + +SELECT COUNT(ID) OVER (ORDER BY NAME) AS NR, + A.ID AS ID FROM (SELECT ID, NAME FROM TEST ORDER BY NAME) AS A; +> NR ID +> -- -- +> 1 3 +> 2 1 +> rows: 2 + +SELECT NR FROM (SELECT COUNT(ID) OVER (ORDER BY NAME) AS NR, + A.ID AS ID FROM (SELECT ID, NAME FROM TEST ORDER BY NAME) AS A) + AS B WHERE B.ID = 1; +>> 2 + +DROP TABLE TEST; +> ok + +SELECT I, V, COUNT(V) OVER W C, COUNT(DISTINCT V) OVER W D FROM + VALUES (1, 1), (2, 1), (3, 1), (4, 1), (5, 2), (6, 2), (7, 3) T(I, V) + WINDOW W AS (ORDER BY I); +> I V C D +> - - - - +> 1 1 1 1 +> 2 1 2 1 +> 3 1 3 1 +> 4 1 4 1 +> 5 2 5 2 +> 6 2 6 2 +> 7 3 7 3 +> rows: 7 + +SELECT I, C, COUNT(I) OVER (PARTITION BY C) CNT FROM + VALUES (1, 1), (2, 1), (3, 2), (4, 2), (5, 2) T(I, C); +> I C CNT +> - - --- +> 1 1 2 +> 2 1 2 +> 3 2 3 +> 4 2 3 +> 5 2 3 +> rows: 5 + +SELECT X, COUNT(*) OVER (ORDER BY X) C FROM VALUES (1), (1), (2), (2), (3) V(X); +> X C +> - - +> 1 2 +> 1 2 +> 2 4 +> 2 4 +> 3 5 +> rows: 5 + +CREATE TABLE TEST (N NUMERIC) AS VALUES (0), (0.0), (NULL); +> ok + +SELECT COUNT(*) FROM TEST; +>> 3 + +SELECT COUNT(N) FROM TEST; +>> 2 + +SELECT COUNT(DISTINCT N) FROM TEST; +>> 1 + +SELECT COUNT(*) FROM TEST GROUP BY N; +> COUNT(*) +> -------- +> 1 +> 2 +> rows: 2 + +SELECT COUNT(N) OVER (PARTITION BY N) C FROM TEST; +> C +> - +> 0 +> 2 +> 2 +> rows: 3 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INT, B INT) AS (VALUES (1, NULL), (1, NULL), (2, NULL)); +> ok + +SELECT COUNT((A, B)) C, COUNT(DISTINCT (A, B)) CD FROM TEST; +> C CD +> - -- +> 3 2 +> rows: 1 + +SELECT COUNT(*) OVER (PARTITION BY A, B) C1, COUNT(*) OVER (PARTITION BY (A, B)) C2 FROM TEST; +> C1 C2 +> -- -- +> 1 1 +> 2 2 +> 2 2 +> rows: 3 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(X INT) AS (VALUES 1, 2, NULL); +> ok + +SELECT COUNT(*) FROM TEST; +>> 3 + +SELECT COUNT(1) FROM TEST; +>> 3 + +SELECT COUNT(DISTINCT 1) FROM TEST; +>> 1 + +SELECT COUNT(1) FROM TEST FILTER WHERE X <> 1; +>> 1 + +SELECT COUNT(1) OVER(PARTITION BY X IS NULL) FROM TEST; +> COUNT(*) OVER (PARTITION BY X IS NULL) +> -------------------------------------- +> 1 +> 2 +> 2 +> rows: 3 + +SELECT COUNT(NULL) FROM TEST; +>> 0 + +SELECT COUNT(DISTINCT NULL) FROM TEST; +>> 0 + +EXPLAIN SELECT COUNT(*) FROM TEST; +>> SELECT COUNT(*) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ /* direct lookup */ + +EXPLAIN SELECT COUNT(*) FILTER (WHERE TRUE) FROM TEST; +>> SELECT COUNT(*) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ /* direct lookup */ + +EXPLAIN SELECT COUNT(1) FROM TEST; +>> SELECT COUNT(*) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ /* direct lookup */ + +EXPLAIN SELECT COUNT(DISTINCT 1) FROM TEST; +>> SELECT COUNT(DISTINCT 1) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT COUNT(1) FROM TEST FILTER WHERE X <> 1; +>> SELECT COUNT(*) FROM "PUBLIC"."TEST" "FILTER" /* PUBLIC.TEST.tableScan */ WHERE "X" <> 1 + +EXPLAIN SELECT COUNT(1) OVER(PARTITION BY X IS NULL) FROM TEST; +>> SELECT COUNT(*) OVER (PARTITION BY "X" IS NULL) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT COUNT(NULL) FROM TEST; +>> SELECT CAST(0 AS BIGINT) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ GROUP BY () /* direct lookup */ + +EXPLAIN SELECT COUNT(DISTINCT NULL) FROM TEST; +>> SELECT CAST(0 AS BIGINT) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ GROUP BY () /* direct lookup */ + +SELECT COUNT(X) FROM TEST; +>> 2 + +EXPLAIN SELECT COUNT(X) FROM TEST; +>> SELECT COUNT("X") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +DELETE FROM TEST WHERE X IS NULL; +> update count: 1 + +ALTER TABLE TEST ALTER COLUMN X SET NOT NULL; +> ok + +SELECT COUNT(X) FROM TEST; +>> 2 + +EXPLAIN SELECT COUNT(X) FROM TEST; +>> SELECT COUNT("X") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ /* direct lookup */ + +SELECT COUNT(DISTINCT X) FROM TEST; +>> 2 + +EXPLAIN SELECT COUNT(DISTINCT X) FROM TEST; +>> SELECT COUNT(DISTINCT "X") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/covar_pop.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/covar_pop.sql new file mode 100644 index 0000000..2db8069 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/covar_pop.sql @@ -0,0 +1,28 @@ +-- 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 +-- + +SELECT COVAR_POP(Y, X) OVER (ORDER BY R) FROM (VALUES + (1, NULL, 1), + (2, 1, NULL), + (3, NULL, NULL), + (4, -3, -2), + (5, -3, -1), + (6, 10, 9), + (7, 10, 10), + (8, 10, 11), + (9, 11, 7) +) T(R, Y, X) ORDER BY R; +> COVAR_POP(Y, X) OVER (ORDER BY R) +> --------------------------------- +> null +> null +> null +> 0.0 +> 0.0 +> 30.333333333333332 +> 35.75 +> 35.88 +> 31.277777777777775 +> rows (ordered): 9 diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/covar_samp.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/covar_samp.sql new file mode 100644 index 0000000..8b09c45 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/covar_samp.sql @@ -0,0 +1,28 @@ +-- 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 +-- + +SELECT COVAR_SAMP(Y, X) OVER (ORDER BY R) FROM (VALUES + (1, NULL, 1), + (2, 1, NULL), + (3, NULL, NULL), + (4, -3, -2), + (5, -3, -1), + (6, 10, 9), + (7, 10, 10), + (8, 10, 11), + (9, 11, 7) +) T(R, Y, X) ORDER BY R; +> COVAR_SAMP(Y, X) OVER (ORDER BY R) +> ---------------------------------- +> null +> null +> null +> null +> 0.0 +> 45.5 +> 47.666666666666664 +> 44.85 +> 37.53333333333333 +> rows (ordered): 9 diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/envelope.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/envelope.sql new file mode 100644 index 0000000..9879b92 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/envelope.sql @@ -0,0 +1,132 @@ +-- 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 +-- + +CREATE TABLE TEST(V GEOMETRY); +> ok + +SELECT ENVELOPE(V) FROM TEST; +>> null + +INSERT INTO TEST VALUES ('POINT(1 1)'); +> update count: 1 + +SELECT ENVELOPE(V) FROM TEST; +>> POINT (1 1) + +INSERT INTO TEST VALUES ('POINT(1 2)'), (NULL), ('POINT(3 1)'); +> update count: 3 + +SELECT ENVELOPE(V), ENVELOPE(V) FILTER (WHERE V <> 'POINT(3 1)') FILTERED1, + ENVELOPE(V) FILTER (WHERE V <> 'POINT(1 2)') FILTERED2 FROM TEST; +> ENVELOPE(V) FILTERED1 FILTERED2 +> ----------------------------------- --------------------- --------------------- +> POLYGON ((1 1, 1 2, 3 2, 3 1, 1 1)) LINESTRING (1 1, 1 2) LINESTRING (1 1, 3 1) +> rows: 1 + +CREATE SPATIAL INDEX IDX ON TEST(V); +> ok + +-- Without index +SELECT ENVELOPE(N) FROM (SELECT V AS N FROM TEST); +>> POLYGON ((1 1, 1 2, 3 2, 3 1, 1 1)) + +-- With index +SELECT ENVELOPE(V) FROM TEST; +>> POLYGON ((1 1, 1 2, 3 2, 3 1, 1 1)) + +-- Without index +SELECT ENVELOPE(V) FILTER (WHERE V <> 'POINT(3 1)') FILTERED FROM TEST; +>> LINESTRING (1 1, 1 2) + +-- Without index +SELECT ENVELOPE(V) FROM TEST WHERE V <> 'POINT(3 1)'; +>> LINESTRING (1 1, 1 2) + +INSERT INTO TEST VALUES ('POINT(-1.0000000001 1)'); +> update count: 1 + +-- Without index +SELECT ENVELOPE(N) FROM (SELECT V AS N FROM TEST); +>> POLYGON ((-1.0000000001 1, -1.0000000001 2, 3 2, 3 1, -1.0000000001 1)) + +-- With index +SELECT ENVELOPE(V) FROM TEST; +>> POLYGON ((-1.0000000001 1, -1.0000000001 2, 3 2, 3 1, -1.0000000001 1)) + +TRUNCATE TABLE TEST; +> update count: 5 + +-- Without index +SELECT ENVELOPE(N) FROM (SELECT V AS N FROM TEST); +>> null + +-- With index +SELECT ENVELOPE(V) FROM TEST; +>> null + +SELECT ESTIMATED_ENVELOPE('TEST', 'V'); +>> null + +@reconnect off + +SELECT RAND(1000) * 0; +>> 0.0 + +INSERT INTO TEST SELECT CAST('POINT(' || CAST(RAND() * 100000 AS INT) || ' ' || CAST(RAND() * 100000 AS INT) || ')' AS GEOMETRY) FROM SYSTEM_RANGE(1, 1000); +> update count: 1000 + +@reconnect on + +-- Without index +SELECT ENVELOPE(N) FROM (SELECT V AS N FROM TEST); +>> POLYGON ((68 78, 68 99951, 99903 99951, 99903 78, 68 78)) + +-- With index +SELECT ENVELOPE(V) FROM TEST; +>> POLYGON ((68 78, 68 99951, 99903 99951, 99903 78, 68 78)) + +SELECT ESTIMATED_ENVELOPE('TEST', 'V'); +>> POLYGON ((68 78, 68 99951, 99903 99951, 99903 78, 68 78)) + +TRUNCATE TABLE TEST; +> update count: 1000 + +@reconnect off + +SELECT RAND(1000) * 0; +>> 0.0 + +INSERT INTO TEST SELECT CAST('POINT(' || (CAST(RAND() * 100000 AS INT) * 0.000000001 + 1) || ' ' + || (CAST(RAND() * 100000 AS INT) * 0.000000001 + 1) || ')' AS GEOMETRY) FROM SYSTEM_RANGE(1, 1000); +> update count: 1000 + +@reconnect on + +-- Without index +SELECT ENVELOPE(N) FROM (SELECT V AS N FROM TEST); +>> POLYGON ((1.000000068 1.000000078, 1.000000068 1.000099951, 1.000099903 1.000099951, 1.000099903 1.000000078, 1.000000068 1.000000078)) + +-- With index +SELECT ENVELOPE(V) FROM TEST; +>> POLYGON ((1.000000068 1.000000078, 1.000000068 1.000099951, 1.000099903 1.000099951, 1.000099903 1.000000078, 1.000000068 1.000000078)) + +DROP TABLE TEST; +> ok + +-- Test for index selection +CREATE TABLE TEST(G1 GEOMETRY, G2 GEOMETRY) AS (SELECT NULL, 'POINT (1 1)'::GEOMETRY); +> ok + +CREATE SPATIAL INDEX G1IDX ON TEST(G1); +> ok + +CREATE SPATIAL INDEX G2IDX ON TEST(G2); +> ok + +SELECT ENVELOPE(G2) FROM TEST; +>> POINT (1 1) + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/every.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/every.sql new file mode 100644 index 0000000..e603f5c --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/every.sql @@ -0,0 +1,21 @@ +-- 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 +-- + +CREATE TABLE TEST(A INT, B INT); +> ok + +INSERT INTO TEST VALUES (1, 1), (1, 3), (2, 1), (2, 5), (3, 4); +> update count: 5 + +SELECT A, EVERY(B < 5), BOOL_AND(B > 1), EVERY(B >= 1) FILTER (WHERE A = 1) FROM TEST GROUP BY A; +> A EVERY(B < 5) EVERY(B > 1) EVERY(B >= 1) FILTER (WHERE A = 1) +> - ------------ ------------ ---------------------------------- +> 1 TRUE FALSE TRUE +> 2 FALSE FALSE null +> 3 TRUE TRUE null +> rows: 3 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/histogram.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/histogram.sql new file mode 100644 index 0000000..396daab --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/histogram.sql @@ -0,0 +1,19 @@ +-- 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 +-- + +SELECT HISTOGRAM(X), FROM VALUES (1), (2), (3), (1), (2), (NULL), (5) T(X); +>> [ROW (null, 1), ROW (1, 2), ROW (2, 2), ROW (3, 1), ROW (5, 1)] + +SELECT HISTOGRAM(X) FILTER (WHERE X > 1) FROM VALUES (1), (2), (3), (1), (2), (NULL), (5) T(X); +>> [ROW (2, 2), ROW (3, 1), ROW (5, 1)] + +SELECT HISTOGRAM(X) FILTER (WHERE X > 0) FROM VALUES (0) T(X); +>> [] + +SELECT HISTOGRAM(DISTINCT X) FROM VALUES (0) T(X); +> exception SYNTAX_ERROR_2 + +SELECT HISTOGRAM(ALL X) FROM VALUES (0) T(X); +> exception SYNTAX_ERROR_2 diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/json_arrayagg.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/json_arrayagg.sql new file mode 100644 index 0000000..12429ec --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/json_arrayagg.sql @@ -0,0 +1,71 @@ +-- 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 +-- + +CREATE TABLE TEST(ID INT PRIMARY KEY, N VARCHAR, J JSON) AS VALUES + (1, 'Ten', JSON '10'), + (2, 'Null', NULL), + (3, 'False', JSON 'false'), + (4, 'False', JSON 'false'); +> ok + +SELECT JSON_ARRAYAGG(J NULL ON NULL) FROM TEST; +>> [10,null,false,false] + +SELECT JSON_ARRAYAGG(J) FROM TEST; +>> [10,false,false] + +SELECT JSON_ARRAYAGG(ALL J) FROM TEST; +>> [10,false,false] + +SELECT JSON_ARRAYAGG(DISTINCT J) FROM TEST; +>> [10,false] + +SELECT JSON_ARRAYAGG(J NULL ON NULL) FROM TEST; +>> [10,null,false,false] + +SELECT JSON_ARRAYAGG(J ABSENT ON NULL) FROM TEST; +>> [10,false,false] + +SELECT JSON_ARRAYAGG(J ORDER BY ID DESC NULL ON NULL) FROM TEST; +>> [false,false,null,10] + +SELECT JSON_ARRAY(NULL NULL ON NULL); +>> [null] + +EXPLAIN SELECT JSON_ARRAYAGG(J) FROM TEST; +>> SELECT JSON_ARRAYAGG("J") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT JSON_ARRAYAGG(J NULL ON NULL) FROM TEST; +>> SELECT JSON_ARRAYAGG("J" NULL ON NULL) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT JSON_ARRAYAGG(J ABSENT ON NULL) FROM TEST; +>> SELECT JSON_ARRAYAGG("J") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT JSON_ARRAYAGG(J FORMAT JSON ABSENT ON NULL) FROM TEST; +>> SELECT JSON_ARRAYAGG("J" FORMAT JSON) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT JSON_ARRAYAGG(DISTINCT J FORMAT JSON ORDER BY ID DESC ABSENT ON NULL) FROM TEST; +>> SELECT JSON_ARRAYAGG(DISTINCT "J" FORMAT JSON ORDER BY "ID" DESC) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +DELETE FROM TEST WHERE J IS NOT NULL; +> update count: 3 + +SELECT JSON_ARRAYAGG(J) FROM TEST; +>> [] + +SELECT JSON_ARRAYAGG(J NULL ON NULL) FROM TEST; +>> [null] + +DELETE FROM TEST; +> update count: 1 + +SELECT JSON_ARRAYAGG(J) FROM TEST; +>> null + +DROP TABLE TEST; +> ok + +EXPLAIN SELECT JSON_ARRAYAGG(A ORDER BY 'a') FROM (VALUES 1, 2) T(A); +>> SELECT JSON_ARRAYAGG("A") FROM (VALUES (1), (2)) "T"("A") /* table scan */ diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/json_objectagg.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/json_objectagg.sql new file mode 100644 index 0000000..de61a64 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/json_objectagg.sql @@ -0,0 +1,73 @@ +-- 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 +-- + +CREATE TABLE TEST(ID INT PRIMARY KEY, N VARCHAR, J JSON) AS VALUES + (1, 'Ten', '10' FORMAT JSON), + (2, 'Null', NULL), + (3, 'False', 'false' FORMAT JSON); +> ok + +SELECT JSON_OBJECTAGG(KEY N VALUE J) FROM TEST; +>> {"Ten":10,"Null":null,"False":false} + +SELECT JSON_OBJECTAGG(N VALUE J) FROM TEST; +>> {"Ten":10,"Null":null,"False":false} + +SELECT JSON_OBJECTAGG(N: J) FROM TEST; +>> {"Ten":10,"Null":null,"False":false} + +SELECT JSON_OBJECTAGG(N: J ABSENT ON NULL) FROM TEST; +>> {"Ten":10,"False":false} + +SELECT JSON_OBJECTAGG(N: J ABSENT ON NULL) FILTER (WHERE J IS NULL) FROM TEST; +>> {} + +SELECT JSON_OBJECTAGG(N: J) FILTER (WHERE FALSE) FROM TEST; +>> null + +SELECT JSON_OBJECTAGG(NULL: J) FROM TEST; +> exception INVALID_VALUE_2 + +INSERT INTO TEST VALUES (4, 'Ten', '-10' FORMAT JSON); +> update count: 1 + +SELECT JSON_OBJECTAGG(N: J) FROM TEST; +>> {"Ten":10,"Null":null,"False":false,"Ten":-10} + +SELECT JSON_OBJECTAGG(N: J WITHOUT UNIQUE KEYS) FROM TEST; +>> {"Ten":10,"Null":null,"False":false,"Ten":-10} + +SELECT JSON_OBJECTAGG(N: J WITH UNIQUE KEYS) FROM TEST; +> exception INVALID_VALUE_2 + +EXPLAIN SELECT JSON_OBJECTAGG(N: J) FROM TEST; +>> SELECT JSON_OBJECTAGG("N": "J") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT JSON_OBJECTAGG(N: J NULL ON NULL) FROM TEST; +>> SELECT JSON_OBJECTAGG("N": "J") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT JSON_OBJECTAGG(N: J ABSENT ON NULL) FROM TEST; +>> SELECT JSON_OBJECTAGG("N": "J" ABSENT ON NULL) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT JSON_OBJECTAGG(N: J WITH UNIQUE KEYS) FROM TEST; +>> SELECT JSON_OBJECTAGG("N": "J" WITH UNIQUE KEYS) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT JSON_OBJECTAGG(N: J NULL ON NULL WITH UNIQUE KEYS) FROM TEST; +>> SELECT JSON_OBJECTAGG("N": "J" WITH UNIQUE KEYS) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT JSON_OBJECTAGG(N: J ABSENT ON NULL WITH UNIQUE KEYS) FROM TEST; +>> SELECT JSON_OBJECTAGG("N": "J" ABSENT ON NULL WITH UNIQUE KEYS) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT JSON_OBJECTAGG(N: J WITHOUT UNIQUE KEYS) FROM TEST; +>> SELECT JSON_OBJECTAGG("N": "J") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT JSON_OBJECTAGG(N: J NULL ON NULL WITHOUT UNIQUE KEYS) FROM TEST; +>> SELECT JSON_OBJECTAGG("N": "J") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT JSON_OBJECTAGG(N: J ABSENT ON NULL WITHOUT UNIQUE KEYS) FROM TEST; +>> SELECT JSON_OBJECTAGG("N": "J" ABSENT ON NULL) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +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 new file mode 100644 index 0000000..073650b --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/listagg.sql @@ -0,0 +1,218 @@ +-- 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 +-- + +-- with filter condition + +create table test(v varchar); +> ok + +insert into test values ('1'), ('2'), ('3'), ('4'), ('5'), ('6'), ('7'), ('8'), ('9'); +> update count: 9 + +select listagg(v, '-') within group (order by v asc), + listagg(v, '-') within group (order by v desc) filter (where v >= '4') + from test where v >= '2'; +> LISTAGG(V, '-') WITHIN GROUP (ORDER BY V) LISTAGG(V, '-') WITHIN GROUP (ORDER BY V DESC) FILTER (WHERE V >= '4') +> ----------------------------------------- ---------------------------------------------------------------------- +> 2-3-4-5-6-7-8-9 9-8-7-6-5-4 +> rows: 1 + +select group_concat(v order by v asc separator '-'), + group_concat(v order by v desc separator '-') filter (where v >= '4') + from test where v >= '2'; +> LISTAGG(V, '-') WITHIN GROUP (ORDER BY V) LISTAGG(V, '-') WITHIN GROUP (ORDER BY V DESC) FILTER (WHERE V >= '4') +> ----------------------------------------- ---------------------------------------------------------------------- +> 2-3-4-5-6-7-8-9 9-8-7-6-5-4 +> rows: 1 + +create index test_idx on test(v); +> ok + +select group_concat(v order by v asc separator '-'), + group_concat(v order by v desc separator '-') filter (where v >= '4') + from test where v >= '2'; +> LISTAGG(V, '-') WITHIN GROUP (ORDER BY V) LISTAGG(V, '-') WITHIN GROUP (ORDER BY V DESC) FILTER (WHERE V >= '4') +> ----------------------------------------- ---------------------------------------------------------------------- +> 2-3-4-5-6-7-8-9 9-8-7-6-5-4 +> rows: 1 + +select group_concat(v order by v asc separator '-'), + group_concat(v order by v desc separator '-') filter (where v >= '4') + from test; +> LISTAGG(V, '-') WITHIN GROUP (ORDER BY V) LISTAGG(V, '-') WITHIN GROUP (ORDER BY V DESC) FILTER (WHERE V >= '4') +> ----------------------------------------- ---------------------------------------------------------------------- +> 1-2-3-4-5-6-7-8-9 9-8-7-6-5-4 +> rows: 1 + +drop table test; +> ok + +create table test (id int auto_increment primary key, v int); +> ok + +insert into test(v) values (7), (2), (8), (3), (7), (3), (9), (-1); +> update count: 8 + +select group_concat(v) from test; +> LISTAGG(V) WITHIN GROUP (ORDER BY NULL) +> --------------------------------------- +> 7,2,8,3,7,3,9,-1 +> rows: 1 + +select group_concat(distinct v) from test; +> LISTAGG(DISTINCT V) WITHIN GROUP (ORDER BY NULL) +> ------------------------------------------------ +> -1,2,3,7,8,9 +> rows: 1 + +select group_concat(distinct v order by v desc) from test; +> LISTAGG(DISTINCT V) WITHIN GROUP (ORDER BY V DESC) +> -------------------------------------------------- +> 9,8,7,3,2,-1 +> rows: 1 + +INSERT INTO TEST(V) VALUES NULL; +> update count: 1 + +SELECT LISTAGG(V, ',') WITHIN GROUP (ORDER BY ID) FROM TEST; +>> 7,2,8,3,7,3,9,-1 + +SELECT LISTAGG(COALESCE(CAST(V AS VARCHAR), 'null'), ',') WITHIN GROUP (ORDER BY ID) FROM TEST; +>> 7,2,8,3,7,3,9,-1,null + +SELECT LISTAGG(V, ',') WITHIN GROUP (ORDER BY V) FROM TEST; +>> -1,2,3,3,7,7,8,9 + +drop table test; +> ok + +create table test(g int, v int) as values (1, 1), (1, 2), (1, 3), (2, 4), (2, 5), (2, 6), (3, null); +> ok + +select g, listagg(v, '-') from test group by g; +> G LISTAGG(V, '-') WITHIN GROUP (ORDER BY NULL) +> - -------------------------------------------- +> 1 1-2-3 +> 2 4-5-6 +> 3 null +> rows: 3 + +select g, listagg(v, '-') over (partition by g) from test order by v; +> G LISTAGG(V, '-') WITHIN GROUP (ORDER BY NULL) OVER (PARTITION BY G) +> - ------------------------------------------------------------------ +> 3 null +> 1 1-2-3 +> 1 1-2-3 +> 1 1-2-3 +> 2 4-5-6 +> 2 4-5-6 +> 2 4-5-6 +> rows (ordered): 7 + +select g, listagg(v, '-' on overflow error) within group (order by v) filter (where v <> 2) over (partition by g) from test order by v; +> G LISTAGG(V, '-') WITHIN GROUP (ORDER BY V) FILTER (WHERE V <> 2) OVER (PARTITION BY G) +> - ------------------------------------------------------------------------------------- +> 3 null +> 1 1-3 +> 1 1-3 +> 1 1-3 +> 2 4-5-6 +> 2 4-5-6 +> 2 4-5-6 +> rows (ordered): 7 + +select listagg(distinct v, '-') from test; +> LISTAGG(DISTINCT V, '-') WITHIN GROUP (ORDER BY NULL) +> ----------------------------------------------------- +> 1-2-3-4-5-6 +> rows: 1 + +select g, group_concat(v separator v) from test group by g; +> exception SYNTAX_ERROR_2 + +drop table test; +> ok + +CREATE TABLE TEST(A INT, B INT, C INT); +> ok + +INSERT INTO TEST VALUES + (1, NULL, NULL), + (2, NULL, 1), + (3, 1, NULL), + (4, 1, 1), + (5, NULL, 2), + (6, 2, NULL), + (7, 2, 2); +> update count: 7 + +SELECT LISTAGG(A) WITHIN GROUP (ORDER BY B ASC NULLS FIRST, C ASC NULLS FIRST) FROM TEST; +>> 1,2,5,3,4,6,7 + +SELECT LISTAGG(A) WITHIN GROUP (ORDER BY B ASC NULLS LAST, C ASC NULLS LAST) FROM TEST; +>> 4,3,7,6,2,5,1 + +DROP TABLE TEST; +> ok + +SELECT LISTAGG(DISTINCT A, ' ') WITHIN GROUP (ORDER BY B) FROM (VALUES ('a', 2), ('a', 3), ('b', 1)) T(A, B); +>> b a + +CREATE TABLE TEST(A INT NOT NULL, B VARCHAR(50) NOT NULL) AS VALUES (1, '1'), (1, '2'), (1, '3'); +> ok + +SELECT STRING_AGG(B, ', ') FROM TEST GROUP BY A; +>> 1, 2, 3 + +SELECT STRING_AGG(B, ', ' ORDER BY B DESC) FROM TEST GROUP BY A; +>> 3, 2, 1 + +DROP TABLE TEST; +> ok + +EXPLAIN SELECT LISTAGG(A) WITHIN GROUP (ORDER BY 'a') FROM (VALUES 'a', 'b') T(A); +>> SELECT LISTAGG("A") WITHIN GROUP (ORDER BY NULL) FROM (VALUES ('a'), ('b')) "T"("A") /* table scan */ + +SET MODE Oracle; +> ok + +SELECT LISTAGG(V, '') WITHIN GROUP(ORDER BY V) FROM (VALUES 'a', 'b') T(V); +>> ab + +SET MODE Regular; +> ok + +CREATE TABLE TEST(ID INT, V VARCHAR) AS VALUES (1, 'b'), (2, 'a'); +> ok + +EXPLAIN SELECT LISTAGG(V) FROM TEST; +>> SELECT LISTAGG("V") WITHIN GROUP (ORDER BY NULL) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT LISTAGG(V) WITHIN GROUP (ORDER BY ID) FROM TEST; +>> SELECT LISTAGG("V") WITHIN GROUP (ORDER BY "ID") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT LISTAGG(V, ';') WITHIN GROUP (ORDER BY ID) FROM TEST; +>> SELECT LISTAGG("V", ';') WITHIN GROUP (ORDER BY "ID") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT LISTAGG(V ON OVERFLOW ERROR) WITHIN GROUP (ORDER BY ID) FROM TEST; +>> SELECT LISTAGG("V") WITHIN GROUP (ORDER BY "ID") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT LISTAGG(V, ';' ON OVERFLOW ERROR) WITHIN GROUP (ORDER BY ID) FROM TEST; +>> SELECT LISTAGG("V", ';') WITHIN GROUP (ORDER BY "ID") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT LISTAGG(V ON OVERFLOW TRUNCATE WITH COUNT) WITHIN GROUP (ORDER BY ID) FROM TEST; +>> SELECT LISTAGG("V" ON OVERFLOW TRUNCATE WITH COUNT) WITHIN GROUP (ORDER BY "ID") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT LISTAGG(V ON OVERFLOW TRUNCATE WITHOUT COUNT) WITHIN GROUP (ORDER BY ID) FROM TEST; +>> SELECT LISTAGG("V" ON OVERFLOW TRUNCATE WITHOUT COUNT) WITHIN GROUP (ORDER BY "ID") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT LISTAGG(V ON OVERFLOW TRUNCATE '..' WITH COUNT) WITHIN GROUP (ORDER BY ID) FROM TEST; +>> SELECT LISTAGG("V" ON OVERFLOW TRUNCATE '..' WITH COUNT) WITHIN GROUP (ORDER BY "ID") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT LISTAGG(V ON OVERFLOW TRUNCATE '..' WITHOUT COUNT) WITHIN GROUP (ORDER BY ID) FROM TEST; +>> SELECT LISTAGG("V" ON OVERFLOW TRUNCATE '..' WITHOUT COUNT) WITHIN GROUP (ORDER BY "ID") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/max.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/max.sql new file mode 100644 index 0000000..dfdf0c9 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/max.sql @@ -0,0 +1,69 @@ +-- 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 +-- + +-- with filter condition + +create table test(v int); +> ok + +insert into test values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11), (12); +> update count: 12 + +select max(v), max(v) filter (where v <= 8) from test where v <= 10; +> MAX(V) MAX(V) FILTER (WHERE V <= 8) +> ------ ---------------------------- +> 10 8 +> rows: 1 + +create index test_idx on test(v); +> ok + +select max(v), max(v) filter (where v <= 8) from test where v <= 10; +> MAX(V) MAX(V) FILTER (WHERE V <= 8) +> ------ ---------------------------- +> 10 8 +> rows: 1 + +select max(v), max(v) filter (where v <= 8) from test; +> MAX(V) MAX(V) FILTER (WHERE V <= 8) +> ------ ---------------------------- +> 12 8 +> rows: 1 + +drop table test; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, V INT) AS VALUES (1, 1), (2, NULL), (3, 5); +> ok + +CREATE INDEX TEST_IDX ON TEST(V NULLS LAST); +> ok + +EXPLAIN SELECT MAX(V) FROM TEST; +>> SELECT MAX("V") FROM "PUBLIC"."TEST" /* PUBLIC.TEST_IDX */ /* direct lookup */ + +SELECT MAX(V) FROM TEST; +>> 5 + +DROP TABLE TEST; +> ok + +EXPLAIN SELECT MAX(X) FROM SYSTEM_RANGE(1, 2); +>> SELECT MAX("X") FROM SYSTEM_RANGE(1, 2) /* range index */ /* direct lookup */ + +SELECT MAX(X) FROM SYSTEM_RANGE(1, 2, 0); +> exception STEP_SIZE_MUST_NOT_BE_ZERO + +SELECT MAX(X) FROM SYSTEM_RANGE(1, 2); +>> 2 + +SELECT MAX(X) FROM SYSTEM_RANGE(2, 1); +>> null + +SELECT MAX(X) FROM SYSTEM_RANGE(1, 2, -1); +>> null + +SELECT MAX(X) FROM SYSTEM_RANGE(2, 1, -1); +>> 2 diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/min.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/min.sql new file mode 100644 index 0000000..e8b4b50 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/min.sql @@ -0,0 +1,75 @@ +-- 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 +-- + +-- with filter condition + +create table test(v int); +> ok + +insert into test values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11), (12); +> update count: 12 + +select min(v), min(v) filter (where v >= 4) from test where v >= 2; +> MIN(V) MIN(V) FILTER (WHERE V >= 4) +> ------ ---------------------------- +> 2 4 +> rows: 1 + +create index test_idx on test(v); +> ok + +select min(v), min(v) filter (where v >= 4) from test where v >= 2; +> MIN(V) MIN(V) FILTER (WHERE V >= 4) +> ------ ---------------------------- +> 2 4 +> rows: 1 + +select min(v), min(v) filter (where v >= 4) from test; +> MIN(V) MIN(V) FILTER (WHERE V >= 4) +> ------ ---------------------------- +> 1 4 +> rows: 1 + +drop table test; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, V INT); +> ok + +CREATE INDEX TEST_IDX ON TEST(V NULLS FIRST); +> ok + +EXPLAIN SELECT MIN(V) FROM TEST; +>> SELECT MIN("V") FROM "PUBLIC"."TEST" /* PUBLIC.TEST_IDX */ /* direct lookup */ + +SELECT MIN(V) FROM TEST; +>> null + +INSERT INTO TEST VALUES (1, 1), (2, NULL), (3, 5); +> update count: 3 + +SELECT MIN(V) FROM TEST; +>> 1 + +DROP TABLE TEST; +> ok + +EXPLAIN SELECT MIN(X) FROM SYSTEM_RANGE(1, 2); +>> SELECT MIN("X") FROM SYSTEM_RANGE(1, 2) /* range index */ /* direct lookup */ + +SELECT MIN(X) FROM SYSTEM_RANGE(1, 2, 0); +> exception STEP_SIZE_MUST_NOT_BE_ZERO + +SELECT MIN(X) FROM SYSTEM_RANGE(1, 2); +>> 1 + +SELECT MIN(X) FROM SYSTEM_RANGE(2, 1); +>> null + +SELECT MIN(X) FROM SYSTEM_RANGE(1, 2, -1); +>> null + +SELECT MIN(X) FROM SYSTEM_RANGE(2, 1, -1); +>> 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/mode.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/mode.sql new file mode 100644 index 0000000..54b0dd7 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/mode.sql @@ -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 +-- + +CREATE TABLE TEST(V INT); +> ok + +SELECT MODE(V) FROM TEST; +>> null + +SELECT MODE(DISTINCT V) FROM TEST; +> exception SYNTAX_ERROR_2 + +INSERT INTO TEST VALUES (NULL); +> update count: 1 + +SELECT MODE(V) FROM TEST; +>> null + +INSERT INTO TEST VALUES (1), (2), (3), (1), (2), (1); +> update count: 6 + +SELECT MODE(V), MODE() WITHIN GROUP (ORDER BY V DESC) FROM TEST; +> MODE() WITHIN GROUP (ORDER BY V) MODE() WITHIN GROUP (ORDER BY V DESC) +> -------------------------------- ------------------------------------- +> 1 1 +> rows: 1 + +SELECT MODE(V) FILTER (WHERE (V > 1)), MODE(V) FILTER (WHERE (V < 0)) FROM TEST; +> MODE() WITHIN GROUP (ORDER BY V) FILTER (WHERE V > 1) MODE() WITHIN GROUP (ORDER BY V) FILTER (WHERE V < 0) +> ----------------------------------------------------- ----------------------------------------------------- +> 2 null +> rows: 1 + +-- Oracle compatibility +SELECT STATS_MODE(V) FROM TEST; +>> 1 + +INSERT INTO TEST VALUES (2), (3), (3); +> update count: 3 + +SELECT MODE(V ORDER BY V) FROM TEST; +>> 1 + +SELECT MODE(V ORDER BY V ASC) FROM TEST; +>> 1 + +SELECT MODE(V ORDER BY V DESC) FROM TEST; +>> 3 + +SELECT MODE(V ORDER BY V + 1) FROM TEST; +> exception IDENTICAL_EXPRESSIONS_SHOULD_BE_USED + +SELECT MODE() WITHIN GROUP (ORDER BY V) FROM TEST; +>> 1 + +SELECT MODE() WITHIN GROUP (ORDER BY V ASC) FROM TEST; +>> 1 + +SELECT MODE() WITHIN GROUP (ORDER BY V DESC) FROM TEST; +>> 3 + +SELECT + MODE() WITHIN GROUP (ORDER BY V) OVER () MA, + MODE() WITHIN GROUP (ORDER BY V DESC) OVER () MD, + MODE() WITHIN GROUP (ORDER BY V) OVER (ORDER BY V) MWA, + MODE() WITHIN GROUP (ORDER BY V DESC) OVER (ORDER BY V) MWD, + V FROM TEST; +> MA MD MWA MWD V +> -- -- ---- ---- ---- +> 1 3 1 1 1 +> 1 3 1 1 1 +> 1 3 1 1 1 +> 1 3 1 2 2 +> 1 3 1 2 2 +> 1 3 1 2 2 +> 1 3 1 3 3 +> 1 3 1 3 3 +> 1 3 1 3 3 +> 1 3 null null null +> rows: 10 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST (N NUMERIC) AS VALUES (0), (0.0), (NULL); +> ok + +SELECT MODE(N) FROM TEST; +>> 0 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/percentile.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/percentile.sql new file mode 100644 index 0000000..5ac0bed --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/percentile.sql @@ -0,0 +1,916 @@ +-- 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 +-- + +-- ASC +create table test(v tinyint); +> ok + +create index test_idx on test(v asc); +> ok + +insert into test values (20), (20), (10); +> update count: 3 + +select + percentile_disc(0.5) within group (order by v) d50a, + percentile_disc(0.5) within group (order by v desc) d50d, + median(v) m from test; +> D50A D50D M +> ---- ---- -- +> 20 20 20 +> rows: 1 + +insert into test values (null); +> update count: 1 + +select + percentile_disc(0.5) within group (order by v) d50a, + percentile_disc(0.5) within group (order by v desc) d50d, + median(v) m from test; +> D50A D50D M +> ---- ---- -- +> 20 20 20 +> rows: 1 + +select median(distinct v) from test; +>> 15.0 + +insert into test values (10); +> update count: 1 + +select + percentile_disc(0.5) within group (order by v) d50a, + percentile_disc(0.5) within group (order by v desc) d50d, + median(v) m from test; +> D50A D50D M +> ---- ---- ---- +> 10 20 15.0 +> rows: 1 + +drop table test; +> ok + +-- ASC NULLS FIRST +create table test(v tinyint); +> ok + +create index test_idx on test(v asc nulls first); +> ok + +insert into test values (20), (20), (10); +> update count: 3 + +select + percentile_disc(0.5) within group (order by v) d50a, + percentile_disc(0.5) within group (order by v desc) d50d, + median(v) m from test; +> D50A D50D M +> ---- ---- -- +> 20 20 20 +> rows: 1 + +insert into test values (null); +> update count: 1 + +select + percentile_disc(0.5) within group (order by v) d50a, + percentile_disc(0.5) within group (order by v desc) d50d, + median(v) m from test; +> D50A D50D M +> ---- ---- -- +> 20 20 20 +> rows: 1 + +select median(distinct v) from test; +>> 15.0 + +insert into test values (10); +> update count: 1 + +select + percentile_disc(0.5) within group (order by v) d50a, + percentile_disc(0.5) within group (order by v desc) d50d, + median(v) m from test; +> D50A D50D M +> ---- ---- ---- +> 10 20 15.0 +> rows: 1 + +drop table test; +> ok + +-- ASC NULLS LAST +create table test(v tinyint); +> ok + +create index test_idx on test(v asc nulls last); +> ok + +insert into test values (20), (20), (10); +> update count: 3 + +select + percentile_disc(0.5) within group (order by v) d50a, + percentile_disc(0.5) within group (order by v desc) d50d, + median(v) m from test; +> D50A D50D M +> ---- ---- -- +> 20 20 20 +> rows: 1 + +insert into test values (null); +> update count: 1 + +select + percentile_disc(0.5) within group (order by v) d50a, + percentile_disc(0.5) within group (order by v desc) d50d, + median(v) m from test; +> D50A D50D M +> ---- ---- -- +> 20 20 20 +> rows: 1 + +select median(distinct v) from test; +>> 15.0 + +insert into test values (10); +> update count: 1 + +select + percentile_disc(0.5) within group (order by v) d50a, + percentile_disc(0.5) within group (order by v desc) d50d, + median(v) m from test; +> D50A D50D M +> ---- ---- ---- +> 10 20 15.0 +> rows: 1 + +drop table test; +> ok + +-- DESC +create table test(v tinyint); +> ok + +create index test_idx on test(v desc); +> ok + +insert into test values (20), (20), (10); +> update count: 3 + +select + percentile_disc(0.5) within group (order by v) d50a, + percentile_disc(0.5) within group (order by v desc) d50d, + median(v) m from test; +> D50A D50D M +> ---- ---- -- +> 20 20 20 +> rows: 1 + +insert into test values (null); +> update count: 1 + +select + percentile_disc(0.5) within group (order by v) d50a, + percentile_disc(0.5) within group (order by v desc) d50d, + median(v) m from test; +> D50A D50D M +> ---- ---- -- +> 20 20 20 +> rows: 1 + +select median(distinct v) from test; +>> 15.0 + +insert into test values (10); +> update count: 1 + +select + percentile_disc(0.5) within group (order by v) d50a, + percentile_disc(0.5) within group (order by v desc) d50d, + median(v) m from test; +> D50A D50D M +> ---- ---- ---- +> 10 20 15.0 +> rows: 1 + +drop table test; +> ok + +-- DESC NULLS FIRST +create table test(v tinyint); +> ok + +create index test_idx on test(v desc nulls first); +> ok + +insert into test values (20), (20), (10); +> update count: 3 + +select + percentile_disc(0.5) within group (order by v) d50a, + percentile_disc(0.5) within group (order by v desc) d50d, + median(v) m from test; +> D50A D50D M +> ---- ---- -- +> 20 20 20 +> rows: 1 + +insert into test values (null); +> update count: 1 + +select + percentile_disc(0.5) within group (order by v) d50a, + percentile_disc(0.5) within group (order by v desc) d50d, + median(v) m from test; +> D50A D50D M +> ---- ---- -- +> 20 20 20 +> rows: 1 + +select median(distinct v) from test; +>> 15.0 + +insert into test values (10); +> update count: 1 + +select + percentile_disc(0.5) within group (order by v) d50a, + percentile_disc(0.5) within group (order by v desc) d50d, + median(v) m from test; +> D50A D50D M +> ---- ---- ---- +> 10 20 15.0 +> rows: 1 + +drop table test; +> ok + +-- DESC NULLS LAST +create table test(v tinyint); +> ok + +create index test_idx on test(v desc nulls last); +> ok + +insert into test values (20), (20), (10); +> update count: 3 + +select + percentile_disc(0.5) within group (order by v) d50a, + percentile_disc(0.5) within group (order by v desc) d50d, + median(v) m from test; +> D50A D50D M +> ---- ---- -- +> 20 20 20 +> rows: 1 + +insert into test values (null); +> update count: 1 + +select + percentile_disc(0.5) within group (order by v) d50a, + percentile_disc(0.5) within group (order by v desc) d50d, + median(v) m from test; +> D50A D50D M +> ---- ---- -- +> 20 20 20 +> rows: 1 + +select median(distinct v) from test; +>> 15.0 + +insert into test values (10); +> update count: 1 + +select + percentile_disc(0.5) within group (order by v) d50a, + percentile_disc(0.5) within group (order by v desc) d50d, + median(v) m from test; +> D50A D50D M +> ---- ---- ---- +> 10 20 15.0 +> rows: 1 + +drop table test; +> ok + +create table test(v tinyint); +> ok + +insert into test values (20), (20), (10); +> update count: 3 + +select median(v) from test; +>> 20 + +insert into test values (null); +> update count: 1 + +select median(v) from test; +>> 20 + +select median(distinct v) from test; +>> 15.0 + +insert into test values (10); +> update count: 1 + +select median(v) from test; +>> 15.0 + +drop table test; +> ok + +create table test(v smallint); +> ok + +insert into test values (20), (20), (10); +> update count: 3 + +select median(v) from test; +>> 20 + +insert into test values (null); +> update count: 1 + +select median(v) from test; +>> 20 + +select median(distinct v) from test; +>> 15.0 + +insert into test values (10); +> update count: 1 + +select median(v) from test; +>> 15.0 + +drop table test; +> ok + +create table test(v int); +> ok + +insert into test values (20), (20), (10); +> update count: 3 + +select median(v) from test; +>> 20 + +insert into test values (null); +> update count: 1 + +select median(v) from test; +>> 20 + +select median(distinct v) from test; +>> 15.0 + +insert into test values (10); +> update count: 1 + +select median(v) from test; +>> 15.0 + +drop table test; +> ok + +create table test(v bigint); +> ok + +insert into test values (20), (20), (10); +> update count: 3 + +select median(v) from test; +>> 20 + +insert into test values (null); +> update count: 1 + +select median(v) from test; +>> 20 + +select median(distinct v) from test; +>> 15.0 + +insert into test values (10); +> update count: 1 + +select median(v) from test; +>> 15.0 + +drop table test; +> ok + +create table test(v real); +> ok + +insert into test values (2), (2), (1); +> update count: 3 + +select median(v) from test; +>> 2.0 + +insert into test values (null); +> update count: 1 + +select median(v) from test; +>> 2.0 + +select median(distinct v) from test; +>> 1.50 + +insert into test values (1); +> update count: 1 + +select median(v) from test; +>> 1.50 + +drop table test; +> ok + +create table test(v double); +> ok + +insert into test values (2), (2), (1); +> update count: 3 + +select median(v) from test; +>> 2.0 + +insert into test values (null); +> update count: 1 + +select median(v) from test; +>> 2.0 + +select median(distinct v) from test; +>> 1.50 + +insert into test values (1); +> update count: 1 + +select median(v) from test; +>> 1.50 + +drop table test; +> ok + +create table test(v numeric(1)); +> ok + +insert into test values (2), (2), (1); +> update count: 3 + +select median(v) from test; +>> 2 + +insert into test values (null); +> update count: 1 + +select median(v) from test; +>> 2 + +select median(distinct v) from test; +>> 1.5 + +insert into test values (1); +> update count: 1 + +select median(v) from test; +>> 1.5 + +drop table test; +> ok + +create table test(v time); +> ok + +insert into test values ('20:00:00'), ('20:00:00'), ('10:00:00'); +> update count: 3 + +select median(v) from test; +>> 20:00:00 + +insert into test values (null); +> update count: 1 + +select median(v) from test; +>> 20:00:00 + +select median(distinct v) from test; +>> 15:00:00 + +insert into test values ('10:00:00'); +> update count: 1 + +select median(v) from test; +>> 15:00:00 + +drop table test; +> ok + +create table test(v date); +> ok + +insert into test values ('2000-01-20'), ('2000-01-20'), ('2000-01-10'); +> update count: 3 + +select median(v) from test; +>> 2000-01-20 + +insert into test values (null); +> update count: 1 + +select median(v) from test; +>> 2000-01-20 + +select median(distinct v) from test; +>> 2000-01-15 + +insert into test values ('2000-01-10'); +> update count: 1 + +select median(v) from test; +>> 2000-01-15 + +drop table test; +> ok + +create table test(v timestamp); +> ok + +insert into test values ('2000-01-20 20:00:00'), ('2000-01-20 20:00:00'), ('2000-01-10 10:00:00'); +> update count: 3 + +select median(v) from test; +>> 2000-01-20 20:00:00 + +insert into test values (null); +> update count: 1 + +select median(v) from test; +>> 2000-01-20 20:00:00 + +select median(distinct v) from test; +>> 2000-01-15 15:00:00 + +insert into test values ('2000-01-10 10:00:00'); +> update count: 1 + +select median(v) from test; +>> 2000-01-15 15:00:00 + +delete from test; +> update count: 5 + +insert into test values ('2000-01-20 20:00:00'), ('2000-01-21 20:00:00'); +> update count: 2 + +select median(v) from test; +>> 2000-01-21 08:00:00 + +insert into test values ('-2000-01-10 10:00:00'), ('-2000-01-10 10:00:01'); +> update count: 2 + +select percentile_cont(0.16) within group (order by v) from test; +>> -2000-01-10 10:00:00.48 + +drop table test; +> ok + +create table test(v timestamp with time zone); +> ok + +insert into test values ('2000-01-20 20:00:00+04'), ('2000-01-20 20:00:00+04'), ('2000-01-10 10:00:00+02'); +> update count: 3 + +select median(v) from test; +>> 2000-01-20 20:00:00+04 + +insert into test values (null); +> update count: 1 + +select median(v) from test; +>> 2000-01-20 20:00:00+04 + +select median(distinct v) from test; +>> 2000-01-15 15:00:00+03 + +insert into test values ('2000-01-10 10:00:00+02'); +> update count: 1 + +select median(v) from test; +>> 2000-01-15 15:00:00+03 + +delete from test; +> update count: 5 + +insert into test values ('2000-01-20 20:00:00+10:15:15'), ('2000-01-21 20:00:00-09'); +> update count: 2 + +select median(v) from test; +>> 2000-01-21 08:00:00.5+00:37:37 + +delete from test; +> update count: 2 + +insert into test values ('-2000-01-20 20:00:00+10:15:15'), ('-2000-01-21 20:00:00-09'); +> update count: 2 + +select median(v) from test; +>> -2000-01-21 08:00:00.5+00:37:37 + +drop table test; +> ok + +create table test(v interval day to second); +> ok + +insert into test values ('0 1'), ('0 2'), ('0 2'), ('0 2'), ('-0 1'), ('-0 1'); +> update count: 6 + +select median (v) from test; +>> INTERVAL '0 01:30:00' DAY TO SECOND + +drop table test; +> ok + +-- with group by +create table test(name varchar, "VALUE" int); +> ok + +insert into test values ('Group 2A', 10), ('Group 2A', 10), ('Group 2A', 20), + ('Group 1X', 40), ('Group 1X', 50), ('Group 3B', null); +> update count: 6 + +select name, median("VALUE") from test group by name order by name; +> NAME MEDIAN("VALUE") +> -------- --------------- +> Group 1X 45.0 +> Group 2A 10 +> Group 3B null +> rows (ordered): 3 + +drop table test; +> ok + +-- with filter +create table test(v int); +> ok + +insert into test values (20), (20), (10); +> update count: 3 + +select median(v) from test where v <> 20; +>> 10 + +create index test_idx on test(v asc); +> ok + +select median(v) from test where v <> 20; +>> 10 + +drop table test; +> ok + +-- two-column index +create table test(v int, v2 int); +> ok + +create index test_idx on test(v, v2); +> ok + +insert into test values (20, 1), (10, 2), (20, 3); +> update count: 3 + +select median(v) from test; +>> 20 + +drop table test; +> ok + +-- not null column +create table test (v int not null); +> ok + +create index test_idx on test(v desc); +> ok + +select median(v) from test; +>> null + +insert into test values (10), (20); +> update count: 2 + +select median(v) from test; +>> 15.0 + +insert into test values (20), (10), (20); +> update count: 3 + +select median(v) from test; +>> 20 + +drop table test; +> ok + +-- with filter condition + +create table test(v int); +> ok + +insert into test values (10), (20), (30), (40), (50), (60), (70), (80), (90), (100), (110), (120); +> update count: 12 + +select median(v), median(v) filter (where v >= 40) from test where v <= 100; +> MEDIAN(V) MEDIAN(V) FILTER (WHERE V >= 40) +> --------- -------------------------------- +> 55.0 70 +> rows: 1 + +create index test_idx on test(v); +> ok + +select median(v), median(v) filter (where v >= 40) from test where v <= 100; +> MEDIAN(V) MEDIAN(V) FILTER (WHERE V >= 40) +> --------- -------------------------------- +> 55.0 70 +> rows: 1 + +select median(v), median(v) filter (where v >= 40) from test; +> MEDIAN(V) MEDIAN(V) FILTER (WHERE V >= 40) +> --------- -------------------------------- +> 65.0 80 +> rows: 1 + +drop table test; +> ok + +-- with filter and group by + +create table test(dept varchar, amount int); +> ok + +insert into test values + ('First', 10), ('First', 10), ('First', 20), ('First', 30), ('First', 30), + ('Second', 5), ('Second', 4), ('Second', 20), ('Second', 22), ('Second', 300), + ('Third', 3), ('Third', 100), ('Third', 150), ('Third', 170), ('Third', 400); +> update count: 15 + +select dept, median(amount) from test group by dept order by dept; +> DEPT MEDIAN(AMOUNT) +> ------ -------------- +> First 20 +> Second 20 +> Third 150 +> rows (ordered): 3 + +select dept, median(amount) filter (where amount >= 20) from test group by dept order by dept; +> DEPT MEDIAN(AMOUNT) FILTER (WHERE AMOUNT >= 20) +> ------ ------------------------------------------ +> First 30 +> Second 22 +> Third 160.0 +> rows (ordered): 3 + +select dept, median(amount) filter (where amount >= 20) from test + where (amount < 200) group by dept order by dept; +> DEPT MEDIAN(AMOUNT) FILTER (WHERE AMOUNT >= 20) +> ------ ------------------------------------------ +> First 30 +> Second 21.0 +> Third 150 +> rows (ordered): 3 + +drop table test; +> ok + +create table test(g int, v int); +> ok + +insert into test values (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9), (1, 10), + (2, 10), (2, 20), (2, 30), (2, 100); +> update count: 14 + +select + percentile_cont(0.05) within group (order by v) c05a, + percentile_cont(0.05) within group (order by v desc) c05d, + percentile_cont(0.5) within group (order by v) c50, + percentile_cont(0.5) within group (order by v desc) c50d, + percentile_cont(0.95) within group (order by v) c95a, + percentile_cont(0.95) within group (order by v desc) c95d, + g from test group by g; +> C05A C05D C50 C50D C95A C95D G +> ----- ----- ---- ---- ----- ----- - +> 1.45 9.55 5.5 5.5 9.55 1.45 1 +> 11.50 89.50 25.0 25.0 89.50 11.50 2 +> rows: 2 + +select + percentile_disc(0.05) within group (order by v) d05a, + percentile_disc(0.05) within group (order by v desc) d05d, + percentile_disc(0.5) within group (order by v) d50, + percentile_disc(0.5) within group (order by v desc) d50d, + percentile_disc(0.95) within group (order by v) d95a, + percentile_disc(0.95) within group (order by v desc) d95d, + g from test group by g; +> D05A D05D D50 D50D D95A D95D G +> ---- ---- --- ---- ---- ---- - +> 1 10 5 6 10 1 1 +> 10 100 20 30 100 10 2 +> rows: 2 + +select + percentile_disc(0.05) within group (order by v) over (partition by g order by v) d05a, + percentile_disc(0.05) within group (order by v desc) over (partition by g order by v) d05d, + percentile_disc(0.5) within group (order by v) over (partition by g order by v) d50, + percentile_disc(0.5) within group (order by v desc) over (partition by g order by v) d50d, + percentile_disc(0.95) within group (order by v) over (partition by g order by v) d95a, + percentile_disc(0.95) within group (order by v desc) over (partition by g order by v) d95d, + g, v from test order by g, v; +> D05A D05D D50 D50D D95A D95D G V +> ---- ---- --- ---- ---- ---- - --- +> 1 1 1 1 1 1 1 1 +> 1 2 1 2 2 1 1 2 +> 1 3 2 2 3 1 1 3 +> 1 4 2 3 4 1 1 4 +> 1 5 3 3 5 1 1 5 +> 1 6 3 4 6 1 1 6 +> 1 7 4 4 7 1 1 7 +> 1 8 4 5 8 1 1 8 +> 1 9 5 5 9 1 1 9 +> 1 10 5 6 10 1 1 10 +> 10 10 10 10 10 10 2 10 +> 10 20 10 20 20 10 2 20 +> 10 30 20 20 30 10 2 30 +> 10 100 20 30 100 10 2 100 +> rows (ordered): 14 + +delete from test where g <> 1; +> update count: 4 + +create index test_idx on test(v); +> ok + +select + percentile_disc(0.05) within group (order by v) d05a, + percentile_disc(0.05) within group (order by v desc) d05d, + percentile_disc(0.5) within group (order by v) d50, + percentile_disc(0.5) within group (order by v desc) d50d, + percentile_disc(0.95) within group (order by v) d95a, + percentile_disc(0.95) within group (order by v desc) d95d + from test; +> D05A D05D D50 D50D D95A D95D +> ---- ---- --- ---- ---- ---- +> 1 10 5 6 10 1 +> rows: 1 + +SELECT percentile_disc(null) within group (order by v) from test; +>> null + +SELECT percentile_disc(-0.01) within group (order by v) from test; +> exception INVALID_VALUE_2 + +SELECT percentile_disc(1.01) within group (order by v) from test; +> exception INVALID_VALUE_2 + +SELECT percentile_disc(v) within group (order by v) from test; +> exception INVALID_VALUE_2 + +drop index test_idx; +> ok + +SELECT percentile_disc(null) within group (order by v) from test; +>> null + +SELECT percentile_disc(-0.01) within group (order by v) from test; +> exception INVALID_VALUE_2 + +SELECT percentile_disc(1.01) within group (order by v) from test; +> exception INVALID_VALUE_2 + +SELECT percentile_disc(v) within group (order by v) from test; +> exception INVALID_VALUE_2 + +drop table test; +> ok + +SELECT PERCENTILE_CONT(0.1) WITHIN GROUP (ORDER BY V) FROM (VALUES TIME WITH TIME ZONE '10:30:00Z', TIME WITH TIME ZONE '15:30:00+10') T(V); +>> 15:00:00+09 + +SELECT PERCENTILE_CONT(0.7) WITHIN GROUP (ORDER BY V) FROM (VALUES TIME WITH TIME ZONE '10:00:00Z', TIME WITH TIME ZONE '12:00:00+00:00:01') T(V); +>> 11:24:00.7+00 + +SELECT PERCENTILE_CONT(0.7) WITHIN GROUP (ORDER BY V) FROM (VALUES TIME WITH TIME ZONE '23:59:59.999999999Z', TIME WITH TIME ZONE '23:59:59.999999999+00:00:01') T(V); +>> 23:59:59.299999999-00:00:01 + +SELECT PERCENTILE_CONT(0.7) WITHIN GROUP (ORDER BY V) FROM (VALUES TIME WITH TIME ZONE '00:00:00Z', TIME WITH TIME ZONE '00:00:00-00:00:01') T(V); +>> 00:00:00.3+00:00:01 + +-- null ordering has no effect, but must be allowed +SELECT PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY V NULLS LAST) FROM (VALUES NULL, 1, 3) T(V); +>> 2.0 diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/rank.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/rank.sql new file mode 100644 index 0000000..739f1b0 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/rank.sql @@ -0,0 +1,150 @@ +-- 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 +-- + +CREATE TABLE TEST(V INT) AS VALUES 1, 2, 3, 3, 4, 5, 6; +> ok + +SELECT + RANK(1) WITHIN GROUP (ORDER BY V) R1, + RANK(3) WITHIN GROUP (ORDER BY V) R3, + RANK(7) WITHIN GROUP (ORDER BY V) R7 + FROM TEST; +> R1 R3 R7 +> -- -- -- +> 1 3 8 +> rows: 1 + +SELECT + DENSE_RANK(1) WITHIN GROUP (ORDER BY V) R1, + DENSE_RANK(3) WITHIN GROUP (ORDER BY V) R3, + DENSE_RANK(7) WITHIN GROUP (ORDER BY V) R7 + FROM TEST; +> R1 R3 R7 +> -- -- -- +> 1 3 7 +> rows: 1 + +SELECT + ROUND(PERCENT_RANK(1) WITHIN GROUP (ORDER BY V), 2) R1, + ROUND(PERCENT_RANK(3) WITHIN GROUP (ORDER BY V), 2) R3, + ROUND(PERCENT_RANK(7) WITHIN GROUP (ORDER BY V), 2) R7 + FROM TEST; +> R1 R3 R7 +> --- ---- --- +> 0.0 0.29 1.0 +> rows: 1 + +SELECT + ROUND(CUME_DIST(1) WITHIN GROUP (ORDER BY V), 2) R1, + ROUND(CUME_DIST(3) WITHIN GROUP (ORDER BY V), 2) R3, + ROUND(CUME_DIST(7) WITHIN GROUP (ORDER BY V), 2) R7 + FROM TEST; +> R1 R3 R7 +> ---- ---- --- +> 0.25 0.63 1.0 +> rows: 1 + +SELECT + RANK(1, 1) WITHIN GROUP (ORDER BY V, V + 1) R11, + RANK(1, 2) WITHIN GROUP (ORDER BY V, V + 1) R12, + RANK(1, 3) WITHIN GROUP (ORDER BY V, V + 1) R13 + FROM TEST; +> R11 R12 R13 +> --- --- --- +> 1 1 2 +> rows: 1 + +SELECT + RANK(1, 1) WITHIN GROUP (ORDER BY V, V + 1 DESC) R11, + RANK(1, 2) WITHIN GROUP (ORDER BY V, V + 1 DESC) R12, + RANK(1, 3) WITHIN GROUP (ORDER BY V, V + 1 DESC) R13 + FROM TEST; +> R11 R12 R13 +> --- --- --- +> 2 1 1 +> rows: 1 + +SELECT RANK(3) WITHIN GROUP (ORDER BY V) FILTER (WHERE V <> 2) FROM TEST; +>> 2 + +SELECT + RANK(1) WITHIN GROUP (ORDER BY V) OVER () R1, + RANK(3) WITHIN GROUP (ORDER BY V) OVER () R3, + RANK(7) WITHIN GROUP (ORDER BY V) OVER () R7, + V + FROM TEST ORDER BY V; +> R1 R3 R7 V +> -- -- -- - +> 1 3 8 1 +> 1 3 8 2 +> 1 3 8 3 +> 1 3 8 3 +> 1 3 8 4 +> 1 3 8 5 +> 1 3 8 6 +> rows (ordered): 7 + +SELECT + RANK(1) WITHIN GROUP (ORDER BY V) OVER (ORDER BY V) R1, + RANK(3) WITHIN GROUP (ORDER BY V) OVER (ORDER BY V) R3, + RANK(7) WITHIN GROUP (ORDER BY V) OVER (ORDER BY V) R7, + RANK(7) WITHIN GROUP (ORDER BY V) FILTER (WHERE V <> 2) OVER (ORDER BY V) F7, + V + FROM TEST ORDER BY V; +> R1 R3 R7 F7 V +> -- -- -- -- - +> 1 2 2 2 1 +> 1 3 3 2 2 +> 1 3 5 4 3 +> 1 3 5 4 3 +> 1 3 6 5 4 +> 1 3 7 6 5 +> 1 3 8 7 6 +> rows (ordered): 7 + +SELECT + RANK(1) WITHIN GROUP (ORDER BY V) FILTER (WHERE FALSE) R, + DENSE_RANK(1) WITHIN GROUP (ORDER BY V) FILTER (WHERE FALSE) D, + PERCENT_RANK(1) WITHIN GROUP (ORDER BY V) FILTER (WHERE FALSE) P, + CUME_DIST(1) WITHIN GROUP (ORDER BY V) FILTER (WHERE FALSE) C + FROM VALUES (1) T(V); +> R D P C +> - - --- --- +> 1 1 0.0 1.0 +> rows: 1 + +SELECT RANK(1) WITHIN GROUP (ORDER BY V, V) FROM TEST; +> exception SYNTAX_ERROR_2 + +SELECT RANK(1, 2) WITHIN GROUP (ORDER BY V) FROM TEST; +> exception SYNTAX_ERROR_2 + +SELECT RANK(V) WITHIN GROUP (ORDER BY V) FROM TEST; +> exception INVALID_VALUE_2 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INT, B INT, C INT); +> ok + +INSERT INTO TEST VALUES + (1, NULL, NULL), + (2, NULL, 1), + (3, 1, NULL), + (4, 1, 1), + (5, NULL, 3), + (6, 3, NULL), + (7, 3, 3); +> update count: 7 + +SELECT RANK(2, 2) WITHIN GROUP (ORDER BY B ASC NULLS FIRST, C ASC NULLS FIRST) FROM TEST; +>> 6 + +SELECT RANK(2, 2) WITHIN GROUP (ORDER BY B ASC NULLS LAST, C ASC NULLS LAST) FROM TEST; +>> 3 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_avgx.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_avgx.sql new file mode 100644 index 0000000..4211363 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_avgx.sql @@ -0,0 +1,28 @@ +-- 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 +-- + +SELECT REGR_AVGX(Y, X) OVER (ORDER BY R) FROM (VALUES + (1, NULL, 1), + (2, 1, NULL), + (3, NULL, NULL), + (4, -3, -2), + (5, -3, -1), + (6, 10, 9), + (7, 10, 10), + (8, 10, 11), + (9, 11, 7) +) T(R, Y, X) ORDER BY R; +> REGR_AVGX(Y, X) OVER (ORDER BY R) +> --------------------------------- +> null +> null +> null +> -2.0 +> -1.5 +> 2.0 +> 4.0 +> 5.4 +> 5.666666666666667 +> rows (ordered): 9 diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_avgy.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_avgy.sql new file mode 100644 index 0000000..377e441 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_avgy.sql @@ -0,0 +1,28 @@ +-- 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 +-- + +SELECT REGR_AVGY(Y, X) OVER (ORDER BY R) FROM (VALUES + (1, NULL, 1), + (2, 1, NULL), + (3, NULL, NULL), + (4, -3, -2), + (5, -3, -1), + (6, 10, 9), + (7, 10, 10), + (8, 10, 11), + (9, 11, 7) +) T(R, Y, X) ORDER BY R; +> REGR_AVGY(Y, X) OVER (ORDER BY R) +> --------------------------------- +> null +> null +> null +> -3.0 +> -3.0 +> 1.3333333333333333 +> 3.5 +> 4.8 +> 5.833333333333333 +> rows (ordered): 9 diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_count.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_count.sql new file mode 100644 index 0000000..e8e72f1 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_count.sql @@ -0,0 +1,28 @@ +-- 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 +-- + +SELECT REGR_COUNT(Y, X) OVER (ORDER BY R) FROM (VALUES + (1, NULL, 1), + (2, 1, NULL), + (3, NULL, NULL), + (4, -3, -2), + (5, -3, -1), + (6, 10, 9), + (7, 10, 10), + (8, 10, 11), + (9, 11, 7) +) T(R, Y, X) ORDER BY R; +> REGR_COUNT(Y, X) OVER (ORDER BY R) +> ---------------------------------- +> 0 +> 0 +> 0 +> 1 +> 2 +> 3 +> 4 +> 5 +> 6 +> rows (ordered): 9 diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_intercept.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_intercept.sql new file mode 100644 index 0000000..f1c22e3 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_intercept.sql @@ -0,0 +1,28 @@ +-- 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 +-- + +SELECT REGR_INTERCEPT(Y, X) OVER (ORDER BY R) FROM (VALUES + (1, NULL, 1), + (2, 1, NULL), + (3, NULL, NULL), + (4, -3, -2), + (5, -3, -1), + (6, 10, 9), + (7, 10, 10), + (8, 10, 11), + (9, 11, 7) +) T(R, Y, X) ORDER BY R; +> REGR_INTERCEPT(Y, X) OVER (ORDER BY R) +> -------------------------------------- +> null +> null +> null +> null +> -3.0 +> -1.1261261261261266 +> -1.1885245901639347 +> -1.2096774193548399 +> -0.6775510204081643 +> rows (ordered): 9 diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_r2.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_r2.sql new file mode 100644 index 0000000..67517a2 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_r2.sql @@ -0,0 +1,28 @@ +-- 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 +-- + +SELECT REGR_R2(Y, X) OVER (ORDER BY R) FROM (VALUES + (1, NULL, 1), + (2, 1, NULL), + (3, NULL, NULL), + (4, -3, -2), + (5, -3, -1), + (6, 10, 9), + (7, 10, 10), + (8, 10, 11), + (9, 11, 7) +) T(R, Y, X) ORDER BY R; +> REGR_R2(Y, X) OVER (ORDER BY R) +> ------------------------------- +> null +> null +> null +> null +> 1.0 +> 0.9932432432432432 +> 0.9918032786885245 +> 0.9844913151364764 +> 0.9182051244912443 +> rows (ordered): 9 diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_slope.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_slope.sql new file mode 100644 index 0000000..3f2c468 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_slope.sql @@ -0,0 +1,28 @@ +-- 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 +-- + +SELECT REGR_SLOPE(Y, X) OVER (ORDER BY R) FROM (VALUES + (1, NULL, 1), + (2, 1, NULL), + (3, NULL, NULL), + (4, -3, -2), + (5, -3, -1), + (6, 10, 9), + (7, 10, 10), + (8, 10, 11), + (9, 11, 7) +) T(R, Y, X) ORDER BY R; +> REGR_SLOPE(Y, X) OVER (ORDER BY R) +> ---------------------------------- +> null +> null +> null +> null +> 0.0 +> 1.2297297297297298 +> 1.1721311475409837 +> 1.1129032258064517 +> 1.1489795918367347 +> rows (ordered): 9 diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_sxx.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_sxx.sql new file mode 100644 index 0000000..963dfa5 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_sxx.sql @@ -0,0 +1,28 @@ +-- 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 +-- + +SELECT REGR_SXX(Y, X) OVER (ORDER BY R) FROM (VALUES + (1, NULL, 1), + (2, 1, NULL), + (3, NULL, NULL), + (4, -3, -2), + (5, -3, -1), + (6, 10, 9), + (7, 10, 10), + (8, 10, 11), + (9, 11, 7) +) T(R, Y, X) ORDER BY R; +> REGR_SXX(Y, X) OVER (ORDER BY R) +> -------------------------------- +> null +> null +> null +> 0.0 +> 0.5 +> 74.0 +> 122.0 +> 161.2 +> 163.33333333333331 +> rows (ordered): 9 diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_sxy.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_sxy.sql new file mode 100644 index 0000000..9d6aeca --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_sxy.sql @@ -0,0 +1,28 @@ +-- 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 +-- + +SELECT REGR_SXY(Y, X) OVER (ORDER BY R) FROM (VALUES + (1, NULL, 1), + (2, 1, NULL), + (3, NULL, NULL), + (4, -3, -2), + (5, -3, -1), + (6, 10, 9), + (7, 10, 10), + (8, 10, 11), + (9, 11, 7) +) T(R, Y, X) ORDER BY R; +> REGR_SXY(Y, X) OVER (ORDER BY R) +> -------------------------------- +> null +> null +> null +> 0.0 +> 0.0 +> 91.0 +> 143.0 +> 179.4 +> 187.66666666666666 +> rows (ordered): 9 diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_syy.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_syy.sql new file mode 100644 index 0000000..9478b4f --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/regr_syy.sql @@ -0,0 +1,28 @@ +-- 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 +-- + +SELECT REGR_SYY(Y, X) OVER (ORDER BY R) FROM (VALUES + (1, NULL, 1), + (2, 1, NULL), + (3, NULL, NULL), + (4, -3, -2), + (5, -3, -1), + (6, 10, 9), + (7, 10, 10), + (8, 10, 11), + (9, 11, 7) +) T(R, Y, X) ORDER BY R; +> REGR_SYY(Y, X) OVER (ORDER BY R) +> -------------------------------- +> null +> null +> null +> 0.0 +> 0.0 +> 112.66666666666669 +> 169.00000000000003 +> 202.80000000000004 +> 234.83333333333337 +> rows (ordered): 9 diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/stddev_pop.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/stddev_pop.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/stddev_pop.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/stddev_samp.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/stddev_samp.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/stddev_samp.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/sum.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/sum.sql new file mode 100644 index 0000000..f2d7940 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/sum.sql @@ -0,0 +1,232 @@ +-- 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 +-- + +select sum(cast(x as int)) from system_range(2147483547, 2147483637); +>> 195421006872 + +select sum(x) from system_range(9223372036854775707, 9223372036854775797); +>> 839326855353784593432 + +select sum(cast(100 as tinyint)) from system_range(1, 1000); +>> 100000 + +select sum(cast(100 as smallint)) from system_range(1, 1000); +>> 100000 + +-- with filter condition + +create table test(v int); +> ok + +insert into test values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11), (12); +> update count: 12 + +select sum(v), sum(v) filter (where v >= 4) from test where v <= 10; +> SUM(V) SUM(V) FILTER (WHERE V >= 4) +> ------ ---------------------------- +> 55 49 +> rows: 1 + +create index test_idx on test(v); +> ok + +select sum(v), sum(v) filter (where v >= 4) from test where v <= 10; +> SUM(V) SUM(V) FILTER (WHERE V >= 4) +> ------ ---------------------------- +> 55 49 +> rows: 1 + +insert into test values (1), (2), (8); +> update count: 3 + +select sum(v), sum(all v), sum(distinct v) from test; +> SUM(V) SUM(V) SUM(DISTINCT V) +> ------ ------ --------------- +> 89 89 78 +> rows: 1 + +drop table test; +> ok + +create table test(v interval day to second); +> ok + +insert into test values ('0 1'), ('0 2'), ('0 2'), ('0 2'), ('-0 1'), ('-0 1'); +> update count: 6 + +select sum(v) from test; +>> INTERVAL '0 05:00:00' DAY TO SECOND + +drop table test; +> ok + +SELECT X, COUNT(*), SUM(COUNT(*)) OVER() FROM VALUES (1), (1), (1), (1), (2), (2), (3) T(X) GROUP BY X; +> X COUNT(*) SUM(COUNT(*)) OVER () +> - -------- --------------------- +> 1 4 7 +> 2 2 7 +> 3 1 7 +> rows: 3 + +CREATE TABLE TEST(ID INT); +> ok + +SELECT SUM(ID) FROM TEST; +>> null + +SELECT SUM(ID) OVER () FROM TEST; +> SUM(ID) OVER () +> --------------- +> rows: 0 + +DROP TABLE TEST; +> ok + +SELECT + ID, + SUM(ID) OVER (ORDER BY ID) S, + SUM(ID) OVER (ORDER BY ID RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) S_U_C, + SUM(ID) OVER (ORDER BY ID RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) S_C_U, + SUM(ID) OVER (ORDER BY ID RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) S_U_U + FROM (SELECT X ID FROM SYSTEM_RANGE(1, 8)); +> ID S S_U_C S_C_U S_U_U +> -- -- ----- ----- ----- +> 1 1 1 36 36 +> 2 3 3 35 36 +> 3 6 6 33 36 +> 4 10 10 30 36 +> 5 15 15 26 36 +> 6 21 21 21 36 +> 7 28 28 15 36 +> 8 36 36 8 36 +> rows: 8 + +SELECT I, V, SUM(V) OVER W S, SUM(DISTINCT V) OVER W D FROM + VALUES (1, 1), (2, 1), (3, 1), (4, 1), (5, 2), (6, 2), (7, 3) T(I, V) + WINDOW W AS (ORDER BY I); +> I V S D +> - - -- - +> 1 1 1 1 +> 2 1 2 1 +> 3 1 3 1 +> 4 1 4 1 +> 5 2 6 3 +> 6 2 8 3 +> 7 3 11 6 +> rows: 7 + +SELECT * FROM (SELECT SUM(V) OVER (ORDER BY V ROWS BETWEEN CURRENT ROW AND CURRENT ROW) S FROM (VALUES 1, 2, 2) T(V)); +> S +> - +> 1 +> 2 +> 2 +> rows: 3 + +SELECT V, SUM(V) FILTER (WHERE V <> 1) OVER (ROWS CURRENT ROW) S FROM (VALUES 1, 2, 2) T(V); +> V S +> - ---- +> 1 null +> 2 2 +> 2 2 +> rows: 3 + +SELECT V, + SUM(V) FILTER (WHERE V <> 1) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) S, + SUM(V) FILTER (WHERE V <> 1) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND 1 FOLLOWING) T + FROM (VALUES 1, 2, 2) T(V); +> V S T +> - - - +> 1 4 2 +> 2 4 4 +> 2 4 4 +> rows: 3 + + + +CREATE TABLE S( + B BOOLEAN, + N1 TINYINT, + N2 SMALLINT, + N4 INTEGER, + N8 BIGINT, + N NUMERIC(10, 2), + F4 REAL, + F8 DOUBLE PRECISION, + D DECFLOAT(10), + I1 INTERVAL YEAR(3), + I2 INTERVAL MONTH(3), + I3 INTERVAL DAY(3), + I4 INTERVAL HOUR(3), + I5 INTERVAL MINUTE(3), + I6 INTERVAL SECOND(2), + I7 INTERVAL YEAR(3) TO MONTH, + I8 INTERVAL DAY(3) TO HOUR, + I9 INTERVAL DAY(3) TO MINUTE, + I10 INTERVAL DAY(3) TO SECOND(2), + I11 INTERVAL HOUR(3) TO MINUTE, + I12 INTERVAL HOUR(3) TO SECOND(2), + I13 INTERVAL MINUTE(3) TO SECOND(2)); +> ok + +CREATE TABLE A AS SELECT + SUM(B) B, + SUM(N1) N1, + SUM(N2) N2, + SUM(N4) N4, + SUM(N8) N8, + SUM(N) N, + SUM(F4) F4, + SUM(F8) F8, + SUM(D) D, + SUM(I1) I1, + SUM(I2) I2, + SUM(I3) I3, + SUM(I4) I4, + SUM(I5) I5, + SUM(I6) I6, + SUM(I7) I7, + SUM(I8) I8, + SUM(I9) I9, + SUM(I10) I10, + SUM(I11) I11, + SUM(I12) I12, + SUM(I13) I13 + FROM S; +> ok + +SELECT COLUMN_NAME, DATA_TYPE_SQL('PUBLIC', 'A', 'TABLE', DTD_IDENTIFIER) TYPE FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'A' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME TYPE +> ----------- -------------------------------- +> B BIGINT +> N1 BIGINT +> N2 BIGINT +> N4 BIGINT +> N8 NUMERIC(29) +> N NUMERIC(20, 2) +> F4 DOUBLE PRECISION +> F8 DECFLOAT(27) +> D DECFLOAT(20) +> I1 INTERVAL YEAR(18) +> I2 INTERVAL MONTH(18) +> I3 INTERVAL DAY(18) +> I4 INTERVAL HOUR(18) +> I5 INTERVAL MINUTE(18) +> I6 INTERVAL SECOND(18) +> I7 INTERVAL YEAR(18) TO MONTH +> I8 INTERVAL DAY(18) TO HOUR +> I9 INTERVAL DAY(18) TO MINUTE +> I10 INTERVAL DAY(18) TO SECOND(2) +> I11 INTERVAL HOUR(18) TO MINUTE +> I12 INTERVAL HOUR(18) TO SECOND(2) +> I13 INTERVAL MINUTE(18) TO SECOND(2) +> rows (ordered): 22 + +DROP TABLE S, A; +> ok + +SELECT SUM(I) FROM (VALUES INTERVAL '999999999999999999' SECOND, INTERVAL '1' SECOND) T(I); +> exception NUMERIC_VALUE_OUT_OF_RANGE_1 diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/var_pop.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/var_pop.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/var_pop.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/aggregate/var_samp.sql b/h2/src/test/org/h2/test/scripts/functions/aggregate/var_samp.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/aggregate/var_samp.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/json/json_array.sql b/h2/src/test/org/h2/test/scripts/functions/json/json_array.sql new file mode 100644 index 0000000..58d0c52 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/json/json_array.sql @@ -0,0 +1,58 @@ +-- 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 +-- + +SELECT JSON_ARRAY(10, TRUE, 'str', NULL, '[1,2,3]' FORMAT JSON); +>> [10,true,"str",[1,2,3]] + +SELECT JSON_ARRAY(10, TRUE, 'str', NULL, '[1,2,3]' FORMAT JSON ABSENT ON NULL); +>> [10,true,"str",[1,2,3]] + +SELECT JSON_ARRAY(10, TRUE, 'str', NULL, '[1,2,3]' FORMAT JSON NULL ON NULL); +>> [10,true,"str",null,[1,2,3]] + +SELECT JSON_ARRAY(); +>> [] + +SELECT JSON_ARRAY(NULL ON NULL); +>> [] + +SELECT JSON_ARRAY(NULL ABSENT ON NULL); +>> [] + +SELECT JSON_ARRAY(NULL NULL ON NULL); +>> [null] + +CREATE TABLE TEST(ID INT, V VARCHAR); +> ok + +EXPLAIN SELECT JSON_ARRAY(V) FROM TEST; +>> SELECT JSON_ARRAY("V") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT JSON_ARRAY(V NULL ON NULL) FROM TEST; +>> SELECT JSON_ARRAY("V" NULL ON NULL) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT JSON_ARRAY(V ABSENT ON NULL) FROM TEST; +>> SELECT JSON_ARRAY("V") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT JSON_ARRAY(V FORMAT JSON ABSENT ON NULL) FROM TEST; +>> SELECT JSON_ARRAY("V" FORMAT JSON) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +INSERT INTO TEST VALUES (1, 'null'), (2, '1'), (3, null); +> update count: 3 + +SELECT JSON_ARRAY((SELECT V FROM TEST ORDER BY ID)); +>> ["null","1"] + +SELECT JSON_ARRAY((SELECT V FROM TEST ORDER BY ID) ABSENT ON NULL); +>> ["null","1"] + +SELECT JSON_ARRAY((SELECT V FROM TEST ORDER BY ID) NULL ON NULL); +>> ["null","1",null] + +SELECT JSON_ARRAY((SELECT V FROM TEST ORDER BY ID) FORMAT JSON); +>> [null,1,null] + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/json/json_object.sql b/h2/src/test/org/h2/test/scripts/functions/json/json_object.sql new file mode 100644 index 0000000..d295f37 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/json/json_object.sql @@ -0,0 +1,58 @@ +-- 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 +-- + +SELECT JSON_OBJECT('key1' : 10, 'key2' VALUE TRUE, KEY 'key3' VALUE 'str', 'key4' : NULL, 'key5' : '[1,2,3]' FORMAT JSON); +>> {"key1":10,"key2":true,"key3":"str","key4":null,"key5":[1,2,3]} + +SELECT JSON_OBJECT('key1' : NULL ABSENT ON NULL); +>> {} + +SELECT JSON_OBJECT('key1' : NULL NULL ON NULL); +>> {"key1":null} + +SELECT JSON_OBJECT(); +>> {} + +SELECT JSON_OBJECT(NULL ON NULL); +>> {} + +SELECT JSON_OBJECT(WITHOUT UNIQUE KEYS); +>> {} + +SELECT JSON_OBJECT('key1' : NULL, 'key1' : 2 NULL ON NULL WITHOUT UNIQUE KEYS); +>> {"key1":null,"key1":2} + +SELECT JSON_OBJECT('key1' : 1, 'key1' : 2 WITH UNIQUE KEYS); +> exception INVALID_VALUE_2 + +SELECT JSON_OBJECT('key1' : 1, 'key1' : 2 NULL ON NULL WITH UNIQUE KEYS); +> exception INVALID_VALUE_2 + +SELECT JSON_OBJECT('key1' : TRUE WITH UNIQUE KEYS); +>> {"key1":true} + +SELECT JSON_OBJECT(NULL : 1); +> exception INVALID_VALUE_2 + +CREATE TABLE TEST(V VARCHAR, ABSENT VARCHAR, WITHOUT VARCHAR); +> ok + +EXPLAIN SELECT JSON_OBJECT('name' : V NULL ON NULL WITHOUT UNIQUE KEYS) FROM TEST; +>> SELECT JSON_OBJECT('name': "V") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT JSON_OBJECT('name' : V ABSENT ON NULL WITH UNIQUE KEYS) FROM TEST; +>> SELECT JSON_OBJECT('name': "V" ABSENT ON NULL WITH UNIQUE KEYS) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT JSON_OBJECT(ABSENT : 1) FROM TEST; +>> SELECT JSON_OBJECT("ABSENT": 1) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT JSON_OBJECT(WITHOUT : 1) FROM TEST; +>> SELECT JSON_OBJECT("WITHOUT": 1) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +SELECT JSON_OBJECT(NULL ON NULL WITHOUT); +> exception SYNTAX_ERROR_2 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/abs.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/abs.sql new file mode 100644 index 0000000..1e49b93 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/abs.sql @@ -0,0 +1,23 @@ +-- 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 +-- + +select abs(-1) r1, abs(1) r1b; +> R1 R1B +> -- --- +> 1 1 +> rows: 1 + +select abs(null) vn, abs(-1) r1, abs(1) r2, abs(0) r3, abs(-0.1) r4, abs(0.1) r5; +> VN R1 R2 R3 R4 R5 +> ---- -- -- -- --- --- +> null 1 1 0 0.1 0.1 +> rows: 1 + +select * from table(id int=(1, 2), name varchar=('Hello', 'World')) x order by id; +> ID NAME +> -- ----- +> 1 Hello +> 2 World +> rows (ordered): 2 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/acos.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/acos.sql new file mode 100644 index 0000000..d0f493d --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/acos.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +select acos(null) vn, acos(-1) r1; +> VN R1 +> ---- ----------------- +> null 3.141592653589793 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/asin.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/asin.sql new file mode 100644 index 0000000..d7fead3 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/asin.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +select asin(null) vn, asin(-1) r1; +> VN R1 +> ---- ------------------- +> null -1.5707963267948966 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/atan.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/atan.sql new file mode 100644 index 0000000..e8612f1 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/atan.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +select atan(null) vn, atan(-1) r1; +> VN R1 +> ---- ------------------- +> null -0.7853981633974483 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/atan2.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/atan2.sql new file mode 100644 index 0000000..b0b1172 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/atan2.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +select atan2(null, null) vn, atan2(10, 1) r1; +> VN R1 +> ---- ------------------ +> null 1.4711276743037347 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/bitand.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/bitand.sql new file mode 100644 index 0000000..da953e9 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/bitand.sql @@ -0,0 +1,79 @@ +-- 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 +-- + +select bitand(null, 1) vn, bitand(1, null) vn1, bitand(null, null) vn2, bitand(3, 6) e2; +> VN VN1 VN2 E2 +> ---- ---- ---- -- +> null null null 2 +> rows: 1 + +SELECT BITAND(10, 12); +>> 8 + +SELECT BITNAND(10, 12); +>> -9 + +CREATE TABLE TEST(A BIGINT, B BIGINT); +> ok + +EXPLAIN SELECT BITNOT(BITAND(A, B)), BITNOT(BITNAND(A, B)) FROM TEST; +>> SELECT BITNAND("A", "B"), BITAND("A", "B") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +DROP TABLE TEST; +> ok + +EXPLAIN SELECT + BITAND(CAST((0xC5 - 0x100) AS TINYINT), CAST(0x63 AS TINYINT)), + BITAND(CAST(0xC5 AS SMALLINT), CAST(0x63 AS SMALLINT)), + BITAND(CAST(0xC5 AS INTEGER), CAST(0x63 AS INTEGER)), + BITAND(CAST(0xC5 AS BIGINT), CAST(0x63 AS BIGINT)), + BITAND(CAST(X'C5' AS VARBINARY), CAST(X'63' AS VARBINARY)), + BITAND(CAST(X'C5' AS BINARY), CAST(X'63' AS BINARY)); +>> SELECT CAST(65 AS TINYINT), CAST(65 AS SMALLINT), 65, CAST(65 AS BIGINT), X'41', CAST(X'41' AS BINARY(1)) + +EXPLAIN SELECT + BITAND(CAST(X'C501' AS VARBINARY), CAST(X'63' AS VARBINARY)), + BITAND(CAST(X'63' AS VARBINARY), CAST(X'C501' AS VARBINARY)), + BITAND(CAST(X'C501' AS BINARY(2)), CAST(X'63' AS BINARY)), + BITAND(CAST(X'63' AS BINARY), CAST(X'C501' AS BINARY(2))); +>> SELECT X'4100', X'4100', CAST(X'4100' AS BINARY(2)), CAST(X'4100' AS BINARY(2)) + +EXPLAIN SELECT + BITAND(CAST(X'C501' AS VARBINARY), CAST(X'63' AS BINARY)), + BITAND(CAST(X'63' AS BINARY), CAST(X'C501' AS VARBINARY)); +>> SELECT CAST(X'41' AS BINARY(1)), CAST(X'41' AS BINARY(1)) + +EXPLAIN SELECT + BITNAND(CAST((0xC5 - 0x100) AS TINYINT), CAST(0x63 AS TINYINT)), + BITNAND(CAST(0xC5 AS SMALLINT), CAST(0x63 AS SMALLINT)), + BITNAND(CAST(0xC5 AS INTEGER), CAST(0x63 AS INTEGER)), + BITNAND(CAST(0xC5 AS BIGINT), CAST(0x63 AS BIGINT)), + BITNAND(CAST(X'C5' AS VARBINARY), CAST(X'63' AS VARBINARY)), + BITNAND(CAST(X'C5' AS BINARY), CAST(X'63' AS BINARY)); +>> SELECT CAST(-66 AS TINYINT), CAST(-66 AS SMALLINT), -66, CAST(-66 AS BIGINT), X'be', CAST(X'be' AS BINARY(1)) + +EXPLAIN SELECT + BITNAND(CAST(X'C501' AS VARBINARY), CAST(X'63' AS VARBINARY)), + BITNAND(CAST(X'63' AS VARBINARY), CAST(X'C501' AS VARBINARY)), + BITNAND(CAST(X'C501' AS BINARY(2)), CAST(X'63' AS BINARY)), + BITNAND(CAST(X'63' AS BINARY), CAST(X'C501' AS BINARY(2))); +>> SELECT X'beff', X'beff', CAST(X'beff' AS BINARY(2)), CAST(X'beff' AS BINARY(2)) + +EXPLAIN SELECT + BITNAND(CAST(X'C501' AS VARBINARY), CAST(X'63' AS BINARY)), + BITNAND(CAST(X'63' AS BINARY), CAST(X'C501' AS VARBINARY)); +>> SELECT CAST(X'be' AS BINARY(1)), CAST(X'be' AS BINARY(1)) + +SELECT BITAND('AA', 'BB'); +> exception INVALID_VALUE_2 + +SELECT BITAND(1, X'AA'); +> exception INVALID_VALUE_2 + +SELECT BITNAND('AA', 'BB'); +> exception INVALID_VALUE_2 + +SELECT BITNAND(1, X'AA'); +> exception INVALID_VALUE_2 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/bitcount.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/bitcount.sql new file mode 100644 index 0000000..235b433 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/bitcount.sql @@ -0,0 +1,27 @@ +-- 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 +-- + +SELECT V, BITCOUNT(V) C FROM (VALUES 0, 10, -1) T(V); +> V C +> -- -- +> -1 32 +> 0 0 +> 10 2 +> rows: 3 + +EXPLAIN SELECT + BITCOUNT(CAST((0xC5 - 0x100) AS TINYINT)), + BITCOUNT(CAST(0xC5 AS SMALLINT)), + BITCOUNT(CAST(0xC5 AS INTEGER)), + BITCOUNT(CAST(0xC5 AS BIGINT)), + BITCOUNT(CAST(X'C5' AS VARBINARY)), + BITCOUNT(CAST(X'C5' AS BINARY)); +>> SELECT CAST(4 AS BIGINT), CAST(4 AS BIGINT), CAST(4 AS BIGINT), CAST(4 AS BIGINT), CAST(4 AS BIGINT), CAST(4 AS BIGINT) + +SELECT BITCOUNT(X'0123456789ABCDEF'); +>> 32 + +SELECT BITCOUNT(X'0123456789ABCDEF33'); +>> 36 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/bitget.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/bitget.sql new file mode 100644 index 0000000..acea821 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/bitget.sql @@ -0,0 +1,30 @@ +-- 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 +-- + +SELECT I, + BITGET(CAST((0xC5 - 0x100) AS TINYINT), I), + BITGET(CAST(0xC5 AS SMALLINT), I), + BITGET(CAST(0xC5 AS INTEGER), I), + BITGET(CAST(0xC5 AS BIGINT), I), + BITGET(CAST(X'C5' AS VARBINARY), I), + BITGET(CAST(X'C5' AS BINARY), I) + FROM (VALUES -1, 0, 1, 4, 9, 99) T(I); +> I BITGET(-59, I) BITGET(197, I) BITGET(197, I) BITGET(197, I) BITGET(CAST(X'c5' AS BINARY VARYING), I) BITGET(X'c5', I) +> -- -------------- -------------- -------------- -------------- ---------------------------------------- ---------------- +> -1 FALSE FALSE FALSE FALSE FALSE FALSE +> 0 TRUE TRUE TRUE TRUE TRUE TRUE +> 1 FALSE FALSE FALSE FALSE FALSE FALSE +> 4 FALSE FALSE FALSE FALSE FALSE FALSE +> 9 FALSE FALSE FALSE FALSE FALSE FALSE +> 99 FALSE FALSE FALSE FALSE FALSE FALSE +> rows: 6 + +SELECT X, BITGET(X'1001', X) FROM SYSTEM_RANGE(7, 9); +> X BITGET(X'1001', X) +> - ------------------ +> 7 FALSE +> 8 TRUE +> 9 FALSE +> rows: 3 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/bitnot.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/bitnot.sql new file mode 100644 index 0000000..d4c80c2 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/bitnot.sql @@ -0,0 +1,31 @@ +-- 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: Joe Littlejohn +-- + +select bitnot(null) vn, bitnot(0) v1, bitnot(10) v2, bitnot(-10) v3; +> VN V1 V2 V3 +> ---- -- --- -- +> null -1 -11 9 +> rows: 1 + +CREATE TABLE TEST(A BIGINT); +> ok + +EXPLAIN SELECT BITNOT(BITNOT(A)), BITNOT(LSHIFT(A, 1)) FROM TEST; +>> SELECT "A", BITNOT(LSHIFT("A", 1)) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +DROP TABLE TEST; +> ok + +EXPLAIN SELECT + BITNOT(CAST((0xC5 - 0x100) AS TINYINT)), + BITNOT(CAST(0xC5 AS SMALLINT)), + BITNOT(CAST(0xC5 AS INTEGER)), + BITNOT(CAST(0xC5 AS BIGINT)), + BITNOT(CAST(X'C5' AS VARBINARY)), + BITNOT(CAST(X'C5' AS BINARY)); +>> SELECT CAST(58 AS TINYINT), CAST(-198 AS SMALLINT), -198, CAST(-198 AS BIGINT), X'3a', CAST(X'3a' AS BINARY(1)) + +SELECT BITNOT('AA'); +> exception INVALID_VALUE_2 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/bitor.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/bitor.sql new file mode 100644 index 0000000..9194848 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/bitor.sql @@ -0,0 +1,79 @@ +-- 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 +-- + +select bitor(null, 1) vn, bitor(1, null) vn1, bitor(null, null) vn2, bitor(3, 6) e7; +> VN VN1 VN2 E7 +> ---- ---- ---- -- +> null null null 7 +> rows: 1 + +SELECT BITOR(10, 12); +>> 14 + +SELECT BITNOR(10, 12); +>> -15 + +CREATE TABLE TEST(A BIGINT, B BIGINT); +> ok + +EXPLAIN SELECT BITNOT(BITOR(A, B)), BITNOT(BITNOR(A, B)) FROM TEST; +>> SELECT BITNOR("A", "B"), BITOR("A", "B") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +DROP TABLE TEST; +> ok + +EXPLAIN SELECT + BITOR(CAST((0xC5 - 0x100) AS TINYINT), CAST(0x63 AS TINYINT)), + BITOR(CAST(0xC5 AS SMALLINT), CAST(0x63 AS SMALLINT)), + BITOR(CAST(0xC5 AS INTEGER), CAST(0x63 AS INTEGER)), + BITOR(CAST(0xC5 AS BIGINT), CAST(0x63 AS BIGINT)), + BITOR(CAST(X'C5' AS VARBINARY), CAST(X'63' AS VARBINARY)), + BITOR(CAST(X'C5' AS BINARY), CAST(X'63' AS BINARY)); +>> SELECT CAST(-25 AS TINYINT), CAST(231 AS SMALLINT), 231, CAST(231 AS BIGINT), X'e7', CAST(X'e7' AS BINARY(1)) + +EXPLAIN SELECT + BITOR(CAST(X'C501' AS VARBINARY), CAST(X'63' AS VARBINARY)), + BITOR(CAST(X'63' AS VARBINARY), CAST(X'C501' AS VARBINARY)), + BITOR(CAST(X'C501' AS BINARY(2)), CAST(X'63' AS BINARY)), + BITOR(CAST(X'63' AS BINARY), CAST(X'C501' AS BINARY(2))); +>> SELECT X'e701', X'e701', CAST(X'e701' AS BINARY(2)), CAST(X'e701' AS BINARY(2)) + +EXPLAIN SELECT + BITOR(CAST(X'C501' AS VARBINARY), CAST(X'63' AS BINARY)), + BITOR(CAST(X'63' AS BINARY), CAST(X'C501' AS VARBINARY)); +>> SELECT CAST(X'e7' AS BINARY(1)), CAST(X'e7' AS BINARY(1)) + +EXPLAIN SELECT + BITNOR(CAST((0xC5 - 0x100) AS TINYINT), CAST(0x63 AS TINYINT)), + BITNOR(CAST(0xC5 AS SMALLINT), CAST(0x63 AS SMALLINT)), + BITNOR(CAST(0xC5 AS INTEGER), CAST(0x63 AS INTEGER)), + BITNOR(CAST(0xC5 AS BIGINT), CAST(0x63 AS BIGINT)), + BITNOR(CAST(X'C5' AS VARBINARY), CAST(X'63' AS VARBINARY)), + BITNOR(CAST(X'C5' AS BINARY), CAST(X'63' AS BINARY)); +>> SELECT CAST(24 AS TINYINT), CAST(-232 AS SMALLINT), -232, CAST(-232 AS BIGINT), X'18', CAST(X'18' AS BINARY(1)) + +EXPLAIN SELECT + BITNOR(CAST(X'C501' AS VARBINARY), CAST(X'63' AS VARBINARY)), + BITNOR(CAST(X'63' AS VARBINARY), CAST(X'C501' AS VARBINARY)), + BITNOR(CAST(X'C501' AS BINARY(2)), CAST(X'63' AS BINARY)), + BITNOR(CAST(X'63' AS BINARY), CAST(X'C501' AS BINARY(2))); +>> SELECT X'18fe', X'18fe', CAST(X'18fe' AS BINARY(2)), CAST(X'18fe' AS BINARY(2)) + +EXPLAIN SELECT + BITNOR(CAST(X'C501' AS VARBINARY), CAST(X'63' AS BINARY)), + BITNOR(CAST(X'63' AS BINARY), CAST(X'C501' AS VARBINARY)); +>> SELECT CAST(X'18' AS BINARY(1)), CAST(X'18' AS BINARY(1)) + +SELECT BITOR('AA', 'BB'); +> exception INVALID_VALUE_2 + +SELECT BITOR(1, X'AA'); +> exception INVALID_VALUE_2 + +SELECT BITNOR('AA', 'BB'); +> exception INVALID_VALUE_2 + +SELECT BITNOR(1, X'AA'); +> exception INVALID_VALUE_2 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/bitxor.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/bitxor.sql new file mode 100644 index 0000000..a26692f --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/bitxor.sql @@ -0,0 +1,79 @@ +-- 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 +-- + +select bitxor(null, 1) vn, bitxor(1, null) vn1, bitxor(null, null) vn2, bitxor(3, 6) e5; +> VN VN1 VN2 E5 +> ---- ---- ---- -- +> null null null 5 +> rows: 1 + +SELECT BITXOR(10, 12); +>> 6 + +SELECT BITXNOR(10, 12); +>> -7 + +CREATE TABLE TEST(A BIGINT, B BIGINT); +> ok + +EXPLAIN SELECT BITNOT(BITXOR(A, B)), BITNOT(BITXNOR(A, B)) FROM TEST; +>> SELECT BITXNOR("A", "B"), BITXOR("A", "B") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +DROP TABLE TEST; +> ok + +EXPLAIN SELECT + BITXOR(CAST((0xC5 - 0x100) AS TINYINT), CAST(0x63 AS TINYINT)), + BITXOR(CAST(0xC5 AS SMALLINT), CAST(0x63 AS SMALLINT)), + BITXOR(CAST(0xC5 AS INTEGER), CAST(0x63 AS INTEGER)), + BITXOR(CAST(0xC5 AS BIGINT), CAST(0x63 AS BIGINT)), + BITXOR(CAST(X'C5' AS VARBINARY), CAST(X'63' AS VARBINARY)), + BITXOR(CAST(X'C5' AS BINARY), CAST(X'63' AS BINARY)); +>> SELECT CAST(-90 AS TINYINT), CAST(166 AS SMALLINT), 166, CAST(166 AS BIGINT), X'a6', CAST(X'a6' AS BINARY(1)) + +EXPLAIN SELECT + BITXOR(CAST(X'C501' AS VARBINARY), CAST(X'63' AS VARBINARY)), + BITXOR(CAST(X'63' AS VARBINARY), CAST(X'C501' AS VARBINARY)), + BITXOR(CAST(X'C501' AS BINARY(2)), CAST(X'63' AS BINARY)), + BITXOR(CAST(X'63' AS BINARY), CAST(X'C501' AS BINARY(2))); +>> SELECT X'a601', X'a601', CAST(X'a601' AS BINARY(2)), CAST(X'a601' AS BINARY(2)) + +EXPLAIN SELECT + BITXOR(CAST(X'C501' AS VARBINARY), CAST(X'63' AS BINARY)), + BITXOR(CAST(X'63' AS BINARY), CAST(X'C501' AS VARBINARY)); +>> SELECT CAST(X'a6' AS BINARY(1)), CAST(X'a6' AS BINARY(1)) + +EXPLAIN SELECT + BITXNOR(CAST((0xC5 - 0x100) AS TINYINT), CAST(0x63 AS TINYINT)), + BITXNOR(CAST(0xC5 AS SMALLINT), CAST(0x63 AS SMALLINT)), + BITXNOR(CAST(0xC5 AS INTEGER), CAST(0x63 AS INTEGER)), + BITXNOR(CAST(0xC5 AS BIGINT), CAST(0x63 AS BIGINT)), + BITXNOR(CAST(X'C5' AS VARBINARY), CAST(X'63' AS VARBINARY)), + BITXNOR(CAST(X'C5' AS BINARY), CAST(X'63' AS BINARY)); +>> SELECT CAST(89 AS TINYINT), CAST(-167 AS SMALLINT), -167, CAST(-167 AS BIGINT), X'59', CAST(X'59' AS BINARY(1)) + +EXPLAIN SELECT + BITXNOR(CAST(X'C501' AS VARBINARY), CAST(X'63' AS VARBINARY)), + BITXNOR(CAST(X'63' AS VARBINARY), CAST(X'C501' AS VARBINARY)), + BITXNOR(CAST(X'C501' AS BINARY(2)), CAST(X'63' AS BINARY)), + BITXNOR(CAST(X'63' AS BINARY), CAST(X'C501' AS BINARY(2))); +>> SELECT X'59fe', X'59fe', CAST(X'59fe' AS BINARY(2)), CAST(X'59fe' AS BINARY(2)) + +EXPLAIN SELECT + BITXNOR(CAST(X'C501' AS VARBINARY), CAST(X'63' AS BINARY)), + BITXNOR(CAST(X'63' AS BINARY), CAST(X'C501' AS VARBINARY)); +>> SELECT CAST(X'59' AS BINARY(1)), CAST(X'59' AS BINARY(1)) + +SELECT BITXOR('AA', 'BB'); +> exception INVALID_VALUE_2 + +SELECT BITXOR(1, X'AA'); +> exception INVALID_VALUE_2 + +SELECT BITXNOR('AA', 'BB'); +> exception INVALID_VALUE_2 + +SELECT BITXNOR(1, X'AA'); +> exception INVALID_VALUE_2 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/ceil.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/ceil.sql new file mode 100644 index 0000000..7bcb48f --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/ceil.sql @@ -0,0 +1,46 @@ +-- 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 +-- + +select ceil(null) vn, ceil(1) v1, ceiling(1.1) v2, ceil(-1.1) v3, ceiling(1.9) v4, ceiling(-1.9) v5; +> VN V1 V2 V3 V4 V5 +> ---- -- -- -- -- -- +> null 1 2 -1 2 -1 +> rows: 1 + +SELECT CEIL(1.5), CEIL(-1.5), CEIL(1.5) IS OF (NUMERIC); +> 2 -1 TRUE +> - -- ---- +> 2 -1 TRUE +> rows: 1 + +SELECT CEIL(1.5::DOUBLE), CEIL(-1.5::DOUBLE), CEIL(1.5::DOUBLE) IS OF (DOUBLE); +> 2.0 -1.0 TRUE +> --- ---- ---- +> 2.0 -1.0 TRUE +> rows: 1 + +SELECT CEIL(1.5::REAL), CEIL(-1.5::REAL), CEIL(1.5::REAL) IS OF (REAL); +> 2.0 -1.0 TRUE +> --- ---- ---- +> 2.0 -1.0 TRUE +> rows: 1 + +SELECT CEIL('a'); +> exception INVALID_VALUE_2 + +CREATE TABLE S(N NUMERIC(5, 2)); +> ok + +CREATE TABLE T AS SELECT CEIL(N) C FROM S; +> ok + +SELECT DATA_TYPE, NUMERIC_PRECISION, NUMERIC_SCALE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'T'; +> DATA_TYPE NUMERIC_PRECISION NUMERIC_SCALE +> --------- ----------------- ------------- +> NUMERIC 4 0 +> rows: 1 + +DROP TABLE S, T; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/compress.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/compress.sql new file mode 100644 index 0000000..7b0ef7b --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/compress.sql @@ -0,0 +1,25 @@ +-- 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 +-- + +CALL COMPRESS(X'000000000000000000000000'); +>> X'010c010000c000010000' + +CALL COMPRESS(X'000000000000000000000000', 'NO'); +>> X'000c000000000000000000000000' + +CALL COMPRESS(X'000000000000000000000000', 'LZF'); +>> X'010c010000c000010000' + +CALL COMPRESS(X'000000000000000000000000', 'DEFLATE'); +>> X'020c789c6360400000000c0001' + +CALL COMPRESS(X'000000000000000000000000', 'UNKNOWN'); +> exception UNSUPPORTED_COMPRESSION_ALGORITHM_1 + +CALL COMPRESS(NULL); +>> null + +CALL COMPRESS(X'00', NULL); +>> null diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/cos.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/cos.sql new file mode 100644 index 0000000..fe64958 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/cos.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +select cos(null) vn, cos(-1) r1; +> VN R1 +> ---- ------------------ +> null 0.5403023058681398 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/cosh.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/cosh.sql new file mode 100644 index 0000000..0b7b614 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/cosh.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +CALL COSH(1); +>> 1.543080634815244 + +CALL COSH(50); +>> 2.592352764293536E21 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/cot.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/cot.sql new file mode 100644 index 0000000..74963e2 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/cot.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +select cot(null) vn, cot(-1) r1; +> VN R1 +> ---- ------------------- +> null -0.6420926159343306 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/decrypt.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/decrypt.sql new file mode 100644 index 0000000..b9eeb8f --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/decrypt.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +call utf8tostring(decrypt('AES', X'00000000000000000000000000000000', X'dbd42d55d4b923c4b03eba0396fac98e')); +>> Hello World Test + +call utf8tostring(decrypt('AES', hash('sha256', stringtoutf8('Hello'), 1000), encrypt('AES', hash('sha256', stringtoutf8('Hello'), 1000), stringtoutf8('Hello World Test')))); +>> Hello World Test diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/degrees.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/degrees.sql new file mode 100644 index 0000000..4b4a130 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/degrees.sql @@ -0,0 +1,14 @@ +-- 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 +-- + +-- Truncate least significant digits because implementations returns slightly +-- different results depending on Java version +select degrees(null) vn, truncate(degrees(1), 10) v1, truncate(degrees(1.1), 10) v2, + truncate(degrees(-1.1), 10) v3, truncate(degrees(1.9), 10) v4, + truncate(degrees(-1.9), 10) v5; +> VN V1 V2 V3 V4 V5 +> ---- ------------ ------------- -------------- -------------- --------------- +> null 57.295779513 63.0253574643 -63.0253574643 108.8619810748 -108.8619810748 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/encrypt.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/encrypt.sql new file mode 100644 index 0000000..00dff40 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/encrypt.sql @@ -0,0 +1,13 @@ +-- 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 +-- + +call encrypt('AES', X'00000000000000000000000000000000', stringtoutf8('Hello World Test')); +>> X'dbd42d55d4b923c4b03eba0396fac98e' + +CALL ENCRYPT('XTEA', X'00', STRINGTOUTF8('Test')); +>> X'8bc9a4601b3062692a72a5941072425f' + +call encrypt('XTEA', X'000102030405060708090a0b0c0d0e0f', X'4142434445464748'); +>> X'dea0b0b40966b0669fbae58ab503765f' diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/exp.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/exp.sql new file mode 100644 index 0000000..365c318 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/exp.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +select exp(null) vn, left(exp(1), 4) v1, left(exp(1.1), 4) v2, left(exp(-1.1), 4) v3, left(exp(1.9), 4) v4, left(exp(-1.9), 4) v5; +> VN V1 V2 V3 V4 V5 +> ---- ---- ---- ---- ---- ---- +> null 2.71 3.00 0.33 6.68 0.14 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/expand.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/expand.sql new file mode 100644 index 0000000..2b8416c --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/expand.sql @@ -0,0 +1,19 @@ +-- 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 +-- + +CALL EXPAND(X'000c000000000000000000000000'); +>> X'000000000000000000000000' + +CALL EXPAND(X'010c010000c000010000'); +>> X'000000000000000000000000' + +CALL EXPAND(X'020c789c6360400000000c0001'); +>> X'000000000000000000000000' + +CALL EXPAND(X''); +> exception COMPRESSION_ERROR + +CALL EXPAND(NULL); +>> null diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/floor.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/floor.sql new file mode 100644 index 0000000..c9e17ef --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/floor.sql @@ -0,0 +1,43 @@ +-- 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 +-- + +select floor(null) vn, floor(1) v1, floor(1.1) v2, floor(-1.1) v3, floor(1.9) v4, floor(-1.9) v5; +> VN V1 V2 V3 V4 V5 +> ---- -- -- -- -- -- +> null 1 1 -2 1 -2 +> rows: 1 + +SELECT FLOOR(1.5), FLOOR(-1.5), FLOOR(1.5) IS OF (NUMERIC); +> 1 -2 TRUE +> - -- ---- +> 1 -2 TRUE +> rows: 1 + +SELECT FLOOR(1.5::DOUBLE), FLOOR(-1.5::DOUBLE), FLOOR(1.5::DOUBLE) IS OF (DOUBLE); +> 1.0 -2.0 TRUE +> --- ---- ---- +> 1.0 -2.0 TRUE +> rows: 1 + +SELECT FLOOR(1.5::REAL), FLOOR(-1.5::REAL), FLOOR(1.5::REAL) IS OF (REAL); +> 1.0 -2.0 TRUE +> --- ---- ---- +> 1.0 -2.0 TRUE +> rows: 1 + +CREATE TABLE S(N NUMERIC(5, 2)); +> ok + +CREATE TABLE T AS SELECT FLOOR(N) F FROM S; +> ok + +SELECT DATA_TYPE, NUMERIC_PRECISION, NUMERIC_SCALE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'T'; +> DATA_TYPE NUMERIC_PRECISION NUMERIC_SCALE +> --------- ----------------- ------------- +> NUMERIC 4 0 +> rows: 1 + +DROP TABLE S, T; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/hash.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/hash.sql new file mode 100644 index 0000000..466d382 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/hash.sql @@ -0,0 +1,85 @@ +-- 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 +-- + +call hash('SHA256', 'Hello', 0); +> exception INVALID_VALUE_2 + +call hash('SHA256', 'Hello'); +>> X'185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969' + +call hash('SHA256', 'Hello', 1); +>> X'185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969' + +call hash('SHA256', stringtoutf8('Hello'), 1); +>> X'185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969' + +CALL HASH('SHA256', 'Password', 1000); +>> X'c644a176ce920bde361ac336089b06cc2f1514dfa95ba5aabfe33f9a22d577f0' + +CALL HASH('SHA256', STRINGTOUTF8('Password'), 1000); +>> X'c644a176ce920bde361ac336089b06cc2f1514dfa95ba5aabfe33f9a22d577f0' + +call hash('unknown', 'Hello', 1); +> exception INVALID_VALUE_2 + +CALL HASH('MD5', '****** Message digest test ******', 1); +>> X'ccd7ee53b52575b5b04fcadf1637fd30' + +CALL HASH('MD5', '****** Message digest test ******', 10); +>> X'b9e4b74ee3c41f646ee0ba42335efe20' + +CALL HASH('SHA-1', '****** Message digest test ******', 1); +>> X'b9f28134b8c9aef59e1257eca89e3e5101234694' + +CALL HASH('SHA-1', '****** Message digest test ******', 10); +>> X'e69a31beb996b59700aed3e6fbf9c29791efbc15' + +CALL HASH('SHA-224', '****** Message digest test ******', 1); +>> X'7bd9bf319961cfdb7fc9351debbcc8a80143d5d0909e8cbccd8b5f0f' + +CALL HASH('SHA-224', '****** Message digest test ******', 10); +>> X'6685a394158763e754332f0adec3ed43866dd0ba8f47624d0521fd1e' + +CALL HASH('SHA-256', '****** Message digest test ******', 1); +>> X'4e732bc9788b0958022403dbe42b4b79bfa270f05fbe914b4ecca074635f3f5c' + +CALL HASH('SHA-256', '****** Message digest test ******', 10); +>> X'93731025337904f6bc117ca5d3adc960ee2070c7a9666a5499af28546520da85' + +CALL HASH('SHA-384', '****** Message digest test ******', 1); +>> X'a37baa07c0cd5bc8dbb510b3fc3fa6f5ca539c847d8ee382d1d045b405a3d43dc4a898fcc31930cf7a80e2a79af82d4e' + +CALL HASH('SHA-384', '****** Message digest test ******', 10); +>> X'03cc3a769871ab13a64c387c44853efafe016180ab6ea70565924ccabe62c8884b2f2e1a53c1a79db184c112c9082bc2' + +CALL HASH('SHA-512', '****** Message digest test ******', 1); +>> X'88eb2488557eaf7e4da394b6f4ba08d4c781b9f2b9c9d150195ac7f7fbee7819923476b5139abc98f252b07649ade2471be46e2625b8003d0af5a8a50ca2915f' + +CALL HASH('SHA-512', '****** Message digest test ******', 10); +>> X'ab3bb7d9447f87a07379e9219c79da2e05122ff87bf25a5e553a7e44af7ac724ed91fb1fe5730d4bb584c367fc2232680f5c45b3863c6550fcf27b4473d05695' + +CALL HASH('SHA3-224', '****** Message digest test ******', 1); +>> X'cb91fec022d97ed63622d382e36e336b65a806888416a549fb4db390' + +CALL HASH('SHA3-224', '****** Message digest test ******', 10); +>> X'0d4dd581ed9b188341ec413988cb7c6bf15d178b151b543c91031ae6' + +CALL HASH('SHA3-256', '****** Message digest test ******', 1); +>> X'91db71f65f3c5b19370e0d9fd947da52695b28c9b440a1324d11e8076643c21f' + +CALL HASH('SHA3-256', '****** Message digest test ******', 10); +>> X'ed62484d8ac54550292241698dd5480de061fc23ab12e3e941a96ec7d3afd70f' + +CALL HASH('SHA3-384', '****** Message digest test ******', 1); +>> X'c2d5e516ea10a82a3d3a8c5fe8838ca77d402490f33ef813be9af168fd2cdf8f6daa7e9cf79565f3987f897d4087ce26' + +CALL HASH('SHA3-384', '****** Message digest test ******', 10); +>> X'9f5ac0eae232746826ea59196b455267e3aaa492047d5a2616c4a8aa325216f706dc7203fcbe71ee7e3357e0f3d93ee3' + +CALL HASH('SHA3-512', '****** Message digest test ******', 1); +>> X'08811cf7409957b59bb5ba090edbef9a35c3b7a4db5d5760f15f2b14453f9cacba30b9744d4248c742aa47f3d9943cf99e7d78d1700d4ccf5bc88b394bc00603' + +CALL HASH('SHA3-512', '****** Message digest test ******', 10); +>> X'37f2a9dbc6cd7a5122cc84383843566dd7195ed8d868b1c10aca2b706667c7bb0b4f00eab81d9e87b6f355e3afe0bccd57ba04aa121d0ef0c0bdea2ff8f95513' diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/length.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/length.sql new file mode 100644 index 0000000..67b6572 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/length.sql @@ -0,0 +1,34 @@ +-- 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 +-- + +select bit_length(null) en, bit_length('') e0, bit_length('ab') e32; +> EN E0 E32 +> ---- -- --- +> null 0 16 +> rows: 1 + +select length(null) en, length('') e0, length('ab') e2; +> EN E0 E2 +> ---- -- -- +> null 0 2 +> rows: 1 + +select char_length(null) en, char_length('') e0, char_length('ab') e2; +> EN E0 E2 +> ---- -- -- +> null 0 2 +> rows: 1 + +select character_length(null) en, character_length('') e0, character_length('ab') e2; +> EN E0 E2 +> ---- -- -- +> null 0 2 +> rows: 1 + +select octet_length(null) en, octet_length('') e0, octet_length('ab') e4; +> EN E0 E4 +> ---- -- -- +> null 0 2 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/log.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/log.sql new file mode 100644 index 0000000..baf60a6 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/log.sql @@ -0,0 +1,100 @@ +-- 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 +-- + +SELECT LN(NULL), LOG(NULL, NULL), LOG(NULL, 2); +> CAST(NULL AS DOUBLE PRECISION) CAST(NULL AS DOUBLE PRECISION) CAST(NULL AS DOUBLE PRECISION) +> ------------------------------ ------------------------------ ------------------------------ +> null null null +> rows: 1 + +SELECT LOG(2, NULL), LOG10(NULL), LOG(NULL); +> CAST(NULL AS DOUBLE PRECISION) CAST(NULL AS DOUBLE PRECISION) CAST(NULL AS DOUBLE PRECISION) +> ------------------------------ ------------------------------ ------------------------------ +> null null null +> rows: 1 + +SELECT LN(0); +> exception INVALID_VALUE_2 + +SELECT LN(-1); +> exception INVALID_VALUE_2 + +SELECT LOG(0, 2); +> exception INVALID_VALUE_2 + +SELECT LOG(-1, 2); +> exception INVALID_VALUE_2 + +SELECT LOG(1, 2); +> exception INVALID_VALUE_2 + +SELECT LOG(2, 0); +> exception INVALID_VALUE_2 + +SELECT LOG(2, -1); +> exception INVALID_VALUE_2 + +SELECT LOG(0); +> exception INVALID_VALUE_2 + +SELECT LOG(-1); +> exception INVALID_VALUE_2 + +SELECT LOG10(0); +> exception INVALID_VALUE_2 + +SELECT LOG10(-1); +> exception INVALID_VALUE_2 + +SELECT LN(0.5) VH, LN(1) V1, LN(2) V2, LN(3) V3, LN(10) V10; +> VH V1 V2 V3 V10 +> ------------------- --- ------------------ ------------------ ----------------- +> -0.6931471805599453 0.0 0.6931471805599453 1.0986122886681098 2.302585092994046 +> rows: 1 + +SELECT LOG(2, 0.5) VH, LOG(2, 1) V1, LOG(2, 2) V2, LOG(2, 3) V3, LOG(2, 10) V10, LOG(2, 64) V64; +> VH V1 V2 V3 V10 V64 +> ---- --- --- ------------------ ------------------ --- +> -1.0 0.0 1.0 1.5849625007211563 3.3219280948873626 6.0 +> rows: 1 + +SELECT LOG(2.7182818284590452, 10); +>> 2.302585092994046 + +SELECT LOG(10, 3); +>> 0.47712125471966244 + +SELECT LOG(0.5) VH, LOG(1) V1, LOG(2) V2, LOG(3) V3, LOG(10) V10; +> VH V1 V2 V3 V10 +> ------------------- --- ------------------ ------------------ ----------------- +> -0.6931471805599453 0.0 0.6931471805599453 1.0986122886681098 2.302585092994046 +> rows: 1 + +SELECT LOG10(0.5) VH, LOG10(1) V1, LOG10(2) V2, LOG10(3) V3, LOG10(10) V10, LOG10(100) V100; +> VH V1 V2 V3 V10 V100 +> ------------------- --- ------------------ ------------------- --- ---- +> -0.3010299956639812 0.0 0.3010299956639812 0.47712125471966244 1.0 2.0 +> rows: 1 + +SET MODE PostgreSQL; +> ok + +SELECT LOG(0.5) VH, LOG(1) V1, LOG(2) V2, LOG(3) V3, LOG(10) V10, LOG(100) V100; +> VH V1 V2 V3 V10 V100 +> ------------------- --- ------------------ ------------------- --- ---- +> -0.3010299956639812 0.0 0.3010299956639812 0.47712125471966244 1.0 2.0 +> rows: 1 + +SET MODE MSSQLServer; +> ok + +SELECT LOG(0.5, 2) VH, LOG(1, 2) V1, LOG(2, 2) V2, LOG(3, 2) V3, LOG(10, 2) V10, LOG(64, 2) V64; +> VH V1 V2 V3 V10 V64 +> ---- --- --- ------------------ ------------------ --- +> -1.0 0.0 1.0 1.5849625007211563 3.3219280948873626 6.0 +> rows: 1 + +SET MODE Regular; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/lshift.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/lshift.sql new file mode 100644 index 0000000..7bb7e44 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/lshift.sql @@ -0,0 +1,109 @@ +-- 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 +-- + +select lshift(null, 1) vn, lshift(1, null) vn1, lshift(null, null) vn2, lshift(3, 6) v1, lshift(3,0) v2; +> VN VN1 VN2 V1 V2 +> ---- ---- ---- --- -- +> null null null 192 3 +> rows: 1 + +SELECT I, + LSHIFT(CAST(-128 AS TINYINT), I), LSHIFT(CAST(1 AS TINYINT), I), + ULSHIFT(CAST(-128 AS TINYINT), I), ULSHIFT(CAST(1 AS TINYINT), I) + FROM + (VALUES -111, -8, -7, -1, 0, 1, 7, 8, 111) T(I) ORDER BY I; +> I LSHIFT(-128, I) LSHIFT(1, I) ULSHIFT(-128, I) ULSHIFT(1, I) +> ---- --------------- ------------ ---------------- ------------- +> -111 -1 0 0 0 +> -8 -1 0 0 0 +> -7 -1 0 1 0 +> -1 -64 0 64 0 +> 0 -128 1 -128 1 +> 1 0 2 0 2 +> 7 0 -128 0 -128 +> 8 0 0 0 0 +> 111 0 0 0 0 +> rows (ordered): 9 + +SELECT I, + LSHIFT(CAST(-32768 AS SMALLINT), I), LSHIFT(CAST(1 AS SMALLINT), I), + ULSHIFT(CAST(-32768 AS SMALLINT), I), ULSHIFT(CAST(1 AS SMALLINT), I) + FROM + (VALUES -111, -16, -15, -1, 0, 1, 15, 16, 111) T(I) ORDER BY I; +> I LSHIFT(-32768, I) LSHIFT(1, I) ULSHIFT(-32768, I) ULSHIFT(1, I) +> ---- ----------------- ------------ ------------------ ------------- +> -111 -1 0 0 0 +> -16 -1 0 0 0 +> -15 -1 0 1 0 +> -1 -16384 0 16384 0 +> 0 -32768 1 -32768 1 +> 1 0 2 0 2 +> 15 0 -32768 0 -32768 +> 16 0 0 0 0 +> 111 0 0 0 0 +> rows (ordered): 9 + +SELECT I, + LSHIFT(CAST(-2147483648 AS INTEGER), I), LSHIFT(CAST(1 AS INTEGER), I), + ULSHIFT(CAST(-2147483648 AS INTEGER), I), ULSHIFT(CAST(1 AS INTEGER), I) + FROM + (VALUES -111, -32, -31, -1, 0, 1, 31, 32, 111) T(I) ORDER BY I; +> I LSHIFT(-2147483648, I) LSHIFT(1, I) ULSHIFT(-2147483648, I) ULSHIFT(1, I) +> ---- ---------------------- ------------ ----------------------- ------------- +> -111 -1 0 0 0 +> -32 -1 0 0 0 +> -31 -1 0 1 0 +> -1 -1073741824 0 1073741824 0 +> 0 -2147483648 1 -2147483648 1 +> 1 0 2 0 2 +> 31 0 -2147483648 0 -2147483648 +> 32 0 0 0 0 +> 111 0 0 0 0 +> rows (ordered): 9 + +SELECT I, + LSHIFT(CAST(-9223372036854775808 AS BIGINT), I), LSHIFT(CAST(1 AS BIGINT), I), + ULSHIFT(CAST(-9223372036854775808 AS BIGINT), I), ULSHIFT(CAST(1 AS BIGINT), I) + FROM + (VALUES -111, -64, -63, -1, 0, 1, 63, 64, 111) T(I) ORDER BY I; +> I LSHIFT(-9223372036854775808, I) LSHIFT(1, I) ULSHIFT(-9223372036854775808, I) ULSHIFT(1, I) +> ---- ------------------------------- -------------------- -------------------------------- -------------------- +> -111 -1 0 0 0 +> -64 -1 0 0 0 +> -63 -1 0 1 0 +> -1 -4611686018427387904 0 4611686018427387904 0 +> 0 -9223372036854775808 1 -9223372036854775808 1 +> 1 0 2 0 2 +> 63 0 -9223372036854775808 0 -9223372036854775808 +> 64 0 0 0 0 +> 111 0 0 0 0 +> rows (ordered): 9 + +SELECT LSHIFT(X'', 1); +>> X'' + +SELECT LSHIFT(CAST(X'02' AS BINARY), 1); +>> X'04' + +SELECT I, LSHIFT(X'80ABCD09', I) FROM + (VALUES -33, -32, -31, -17, -16, -15, -1, 0, 1, 15, 16, 17, 31, 32, 33) T(I) ORDER BY I; +> I LSHIFT(X'80abcd09', I) +> --- ---------------------- +> -33 X'00000000' +> -32 X'00000000' +> -31 X'00000001' +> -17 X'00004055' +> -16 X'000080ab' +> -15 X'00010157' +> -1 X'4055e684' +> 0 X'80abcd09' +> 1 X'01579a12' +> 15 X'e6848000' +> 16 X'cd090000' +> 17 X'9a120000' +> 31 X'80000000' +> 32 X'00000000' +> 33 X'00000000' +> rows (ordered): 15 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/mod.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/mod.sql new file mode 100644 index 0000000..5d0b3e7 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/mod.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +select mod(null, 1) vn, mod(1, null) vn1, mod(null, null) vn2, mod(10, 2) e1; +> VN VN1 VN2 E1 +> ---- ---- ---- -- +> null null null 0 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/ora-hash.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/ora-hash.sql new file mode 100644 index 0000000..6df772c --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/ora-hash.sql @@ -0,0 +1,64 @@ +-- 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 +-- + +SELECT ORA_HASH(NULL); +>> null + +SELECT ORA_HASH(NULL, 0); +>> null + +SELECT ORA_HASH(NULL, 0, 0); +>> null + +SELECT ORA_HASH(1); +>> 3509391659 + +SELECT ORA_HASH(1, -1); +> exception INVALID_VALUE_2 + +SELECT ORA_HASH(1, 0); +>> 0 + +SELECT ORA_HASH(1, 4294967295); +>> 3509391659 + +SELECT ORA_HASH(1, 4294967296); +> exception INVALID_VALUE_2 + +SELECT ORA_HASH(1, 4294967295, -1); +> exception INVALID_VALUE_2 + +SELECT ORA_HASH(1, 4294967295, 0); +>> 3509391659 + +SELECT ORA_HASH(1, 4294967295, 10); +>> 2441322222 + +SELECT ORA_HASH(1, 4294967295, 4294967295); +>> 3501171530 + +SELECT ORA_HASH(1, 4294967295, 4294967296); +> exception INVALID_VALUE_2 + +CREATE TABLE TEST(I BINARY(3), B BLOB, S VARCHAR, C CLOB); +> ok + +INSERT INTO TEST VALUES (X'010203', X'010203', 'abc', 'abc'); +> update count: 1 + +SELECT ORA_HASH(I) FROM TEST; +>> 2562861693 + +SELECT ORA_HASH(B) FROM TEST; +>> 2562861693 + +SELECT ORA_HASH(S) FROM TEST; +>> 1191608682 + +SELECT ORA_HASH(C) FROM TEST; +>> 1191608682 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/pi.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/pi.sql new file mode 100644 index 0000000..0c283cb --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/pi.sql @@ -0,0 +1,7 @@ +-- 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 +-- + +select pi(); +>> 3.141592653589793 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/power.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/power.sql new file mode 100644 index 0000000..3dd455f --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/power.sql @@ -0,0 +1,13 @@ +-- 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 +-- + +select power(null, null) en, power(2, 3) e8, power(16, 0.5) e4; +> EN E8 E4 +> ---- --- --- +> null 8.0 4.0 +> rows: 1 + +SELECT POWER(10, 2) IS OF (DOUBLE); +>> TRUE diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/radians.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/radians.sql new file mode 100644 index 0000000..f22f493 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/radians.sql @@ -0,0 +1,17 @@ +-- 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 +-- + +-- Truncate least significant digits because implementations returns slightly +-- different results depending on Java version +select radians(null) vn, truncate(radians(1), 10) v1, truncate(radians(1.1), 10) v2, + truncate(radians(-1.1), 10) v3, truncate(radians(1.9), 10) v4, + truncate(radians(-1.9), 10) v5; +> VN V1 V2 V3 V4 V5 +> ---- ------------ ------------ ------------- ------------ ------------- +> null 0.0174532925 0.0191986217 -0.0191986217 0.0331612557 -0.0331612557 +> rows: 1 + +SELECT RADIANS(0) IS OF (DOUBLE); +>> TRUE diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/rand.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/rand.sql new file mode 100644 index 0000000..1d6c29b --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/rand.sql @@ -0,0 +1,15 @@ +-- 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 +-- + +@reconnect off + +select rand(1) e, random() f; +> E F +> ------------------ ------------------- +> 0.7308781907032909 0.41008081149220166 +> rows: 1 + +select rand(); +>> 0.20771484130971707 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/random-uuid.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/random-uuid.sql new file mode 100644 index 0000000..33a8bbe --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/random-uuid.sql @@ -0,0 +1,34 @@ +-- 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 +-- + +SELECT CHAR_LENGTH(CAST(RANDOM_UUID() AS VARCHAR)); +>> 36 + +SELECT RANDOM_UUID() = RANDOM_UUID(); +>> FALSE + +SELECT NEWID(); +> exception FUNCTION_NOT_FOUND_1 + +SELECT SYS_GUID(); +> exception FUNCTION_NOT_FOUND_1 + +SET MODE MSSQLServer; +> ok + +SELECT CHAR_LENGTH(CAST(NEWID() AS VARCHAR)); +>> 36 + +SET MODE Oracle; +> ok + +SELECT SYS_GUID() IS OF (RAW); +>> TRUE + +SELECT OCTET_LENGTH(SYS_GUID()); +>> 16 + +SET MODE Regular; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/rotate.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/rotate.sql new file mode 100644 index 0000000..5a20587 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/rotate.sql @@ -0,0 +1,103 @@ +-- 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 +-- + +SELECT I, ROTATELEFT(CAST(0x7d AS TINYINT), I) L, ROTATERIGHT(CAST(0x7d AS TINYINT), I) R + FROM (VALUES -8, -7, -2, -1, 0, 1, 2, 7, 8) T(I) ORDER BY I; +> I L R +> -- --- --- +> -8 125 125 +> -7 -6 -66 +> -2 95 -11 +> -1 -66 -6 +> 0 125 125 +> 1 -6 -66 +> 2 -11 95 +> 7 -66 -6 +> 8 125 125 +> rows (ordered): 9 + +SELECT I, ROTATELEFT(CAST(0x6d3f AS SMALLINT), I) L, ROTATERIGHT(CAST(0x6d3f AS SMALLINT), I) R + FROM (VALUES -16, -15, -2, -1, 0, 1, 2, 15, 16) T(I) ORDER BY I; +> I L R +> --- ------ ------ +> -16 27967 27967 +> -15 -9602 -18785 +> -2 -9393 -19203 +> -1 -18785 -9602 +> 0 27967 27967 +> 1 -9602 -18785 +> 2 -19203 -9393 +> 15 -18785 -9602 +> 16 27967 27967 +> rows (ordered): 9 + +SELECT I, ROTATELEFT(CAST(0x7d12e43c AS INTEGER), I) L, ROTATERIGHT(CAST(0x7d12e43c AS INTEGER), I) R + FROM (VALUES -32, -31, -2, -1, 0, 1, 2, 31, 32) T(I) ORDER BY I; +> I L R +> --- ---------- ---------- +> -32 2098390076 2098390076 +> -31 -98187144 1049195038 +> -2 524597519 -196374287 +> -1 1049195038 -98187144 +> 0 2098390076 2098390076 +> 1 -98187144 1049195038 +> 2 -196374287 524597519 +> 31 1049195038 -98187144 +> 32 2098390076 2098390076 +> rows (ordered): 9 + +SELECT I, ROTATELEFT(CAST(0x7302abe53d12e45f AS BIGINT), I) L, ROTATERIGHT(CAST(0x7302abe53d12e45f AS BIGINT), I) R + FROM (VALUES -64, -63, -2, -1, 0, 1, 2, 63, 64) T(I) ORDER BY I; +> I L R +> --- -------------------- -------------------- +> -64 8287375265375642719 8287375265375642719 +> -63 -1871993542958266178 -5079684404166954449 +> -2 -2539842202083477225 -3743987085916532355 +> -1 -5079684404166954449 -1871993542958266178 +> 0 8287375265375642719 8287375265375642719 +> 1 -1871993542958266178 -5079684404166954449 +> 2 -3743987085916532355 -2539842202083477225 +> 63 -5079684404166954449 -1871993542958266178 +> 64 8287375265375642719 8287375265375642719 +> rows (ordered): 9 + +SELECT I, ROTATELEFT(X'ABCD', I) L, ROTATERIGHT(X'ABCD', I) R + FROM (VALUES -16, -15, -8, -1, 0, 1, 8, 15, 16) T(I) ORDER BY I; +> I L R +> --- ------- ------- +> -16 X'abcd' X'abcd' +> -15 X'579b' X'd5e6' +> -8 X'cdab' X'cdab' +> -1 X'd5e6' X'579b' +> 0 X'abcd' X'abcd' +> 1 X'579b' X'd5e6' +> 8 X'cdab' X'cdab' +> 15 X'd5e6' X'579b' +> 16 X'abcd' X'abcd' +> rows (ordered): 9 + +SELECT I, ROTATELEFT(CAST(X'ABCD' AS BINARY(2)), I) L, ROTATERIGHT(CAST(X'ABCD' AS BINARY(2)), I) R + FROM (VALUES -16, -15, -8, -1, 0, 1, 8, 15, 16) T(I) ORDER BY I; +> I L R +> --- ------- ------- +> -16 X'abcd' X'abcd' +> -15 X'579b' X'd5e6' +> -8 X'cdab' X'cdab' +> -1 X'd5e6' X'579b' +> 0 X'abcd' X'abcd' +> 1 X'579b' X'd5e6' +> 8 X'cdab' X'cdab' +> 15 X'd5e6' X'579b' +> 16 X'abcd' X'abcd' +> rows (ordered): 9 + +SELECT ROTATELEFT(X'8000', 1); +>> X'0001' + +SELECT ROTATERIGHT(X'0001', 1); +>> X'8000' + +SELECT ROTATELEFT(X'', 1); +>> X'' diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/round.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/round.sql new file mode 100644 index 0000000..e925aa3 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/round.sql @@ -0,0 +1,111 @@ +-- 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 +-- + +SELECT ROUND(-1.2), ROUND(-1.5), ROUND(-1.6), ROUND(2), ROUND(1.5), ROUND(1.8), ROUND(1.1); +> -1 -2 -2 2 2 2 1 +> -- -- -- - - - - +> -1 -2 -2 2 2 2 1 +> rows: 1 + +select round(null, null) en, round(10.49, 0) e10, round(10.05, 1) e101; +> EN E10 E101 +> ---- --- ---- +> null 10 10.1 +> rows: 1 + +select round(null) en, round(0.6, null) en2, round(1.05) e1, round(-1.51) em2; +> EN EN2 E1 EM2 +> ---- ---- -- --- +> null null 1 -2 +> rows: 1 + +CALL ROUND(998.5::DOUBLE); +>> 999.0 + +CALL ROUND(998.5::REAL); +>> 999.0 + +SELECT + ROUND(4503599627370495.0::DOUBLE), ROUND(4503599627370495.5::DOUBLE), + ROUND(4503599627370496.0::DOUBLE), ROUND(4503599627370497.0::DOUBLE); +> 4.503599627370495E15 4.503599627370496E15 4.503599627370496E15 4.503599627370497E15 +> -------------------- -------------------- -------------------- -------------------- +> 4.503599627370495E15 4.503599627370496E15 4.503599627370496E15 4.503599627370497E15 +> rows: 1 + +SELECT + ROUND(450359962737049.50::DOUBLE, 1), ROUND(450359962737049.55::DOUBLE, 1), + ROUND(450359962737049.60::DOUBLE, 1), ROUND(450359962737049.70::DOUBLE, 1); +> 4.503599627370495E14 4.503599627370496E14 4.503599627370496E14 4.503599627370497E14 +> -------------------- -------------------- -------------------- -------------------- +> 4.503599627370495E14 4.503599627370496E14 4.503599627370496E14 4.503599627370497E14 +> rows: 1 + +CALL ROUND(0.285, 2); +>> 0.29 + +CALL ROUND(0.285::DOUBLE, 2); +>> 0.29 + +CALL ROUND(0.285::REAL, 2); +>> 0.29 + +CALL ROUND(1.285, 2); +>> 1.29 + +CALL ROUND(1.285::DOUBLE, 2); +>> 1.29 + +CALL ROUND(1.285::REAL, 2); +>> 1.29 + +CALL ROUND(1, 1) IS OF (INTEGER); +>> TRUE + +CALL ROUND(1::DOUBLE, 1) IS OF (DOUBLE); +>> TRUE + +CALL ROUND(1::REAL, 1) IS OF (REAL); +>> TRUE + +SELECT ROUND(1, 10000000); +>> 1 + +CREATE TABLE T1(N NUMERIC(10, 2), D DECFLOAT(10), I INTEGER) AS VALUES (99999999.99, 99999999.99, 10); +> ok + +SELECT ROUND(N, -1) NN, ROUND(N) N0, ROUND(N, 1) N1, ROUND(N, 2) N2, ROUND(N, 3) N3, ROUND(N, 10000000) NL, + ROUND(D) D0, ROUND(D, 2) D2, ROUND(D, 3) D3, + ROUND(I) I0, ROUND(I, 1) I1, ROUND(I, I) II FROM T1; +> NN N0 N1 N2 N3 NL D0 D2 D3 I0 I1 II +> --------- --------- ----------- ----------- ----------- ----------- ---- ----------- ----------- -- -- -- +> 100000000 100000000 100000000.0 99999999.99 99999999.99 99999999.99 1E+8 99999999.99 99999999.99 10 10 10 +> rows: 1 + +CREATE TABLE T2 AS SELECT ROUND(N, -1) NN, ROUND(N) N0, ROUND(N, 1) N1, ROUND(N, 2) N2, ROUND(N, 3) N3, ROUND(N, 10000000) NL, + ROUND(D) D0, ROUND(D, 2) D2, ROUND(D, 3) D3, + ROUND(I) I0, ROUND(I, 1) I1, ROUND(I, I) II FROM T1; +> ok + +SELECT COLUMN_NAME, DATA_TYPE, NUMERIC_PRECISION, NUMERIC_SCALE FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'T2' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DATA_TYPE NUMERIC_PRECISION NUMERIC_SCALE +> ----------- --------- ----------------- ------------- +> NN NUMERIC 9 0 +> N0 NUMERIC 9 0 +> N1 NUMERIC 10 1 +> N2 NUMERIC 10 2 +> N3 NUMERIC 10 2 +> NL NUMERIC 10 2 +> D0 DECFLOAT 10 null +> D2 DECFLOAT 10 null +> D3 DECFLOAT 10 null +> I0 INTEGER 32 0 +> I1 INTEGER 32 0 +> II INTEGER 32 0 +> rows (ordered): 12 + +DROP TABLE T1; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/roundmagic.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/roundmagic.sql new file mode 100644 index 0000000..5e42f18 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/roundmagic.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +select roundmagic(null) en, roundmagic(cast(3.11 as double) - 3.1) e001, roundmagic(3.11-3.1-0.01) e000, roundmagic(2000000000000) e20x; +> EN E001 E000 E20X +> ---- ---- ---- ------ +> null 0.01 0.0 2.0E12 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/rshift.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/rshift.sql new file mode 100644 index 0000000..47acc01 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/rshift.sql @@ -0,0 +1,115 @@ +-- 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 +-- + +select rshift(null, 1) vn, rshift(1, null) vn1, rshift(null, null) vn2, rshift(3, 6) v1, rshift(1024,3) v2; +> VN VN1 VN2 V1 V2 +> ---- ---- ---- -- --- +> null null null 0 128 +> rows: 1 + +SELECT I, + RSHIFT(CAST(-128 AS TINYINT), I), RSHIFT(CAST(1 AS TINYINT), I), + URSHIFT(CAST(-128 AS TINYINT), I), URSHIFT(CAST(1 AS TINYINT), I) + FROM + (VALUES -111, -8, -7, -1, 0, 1, 7, 8, 111) T(I) ORDER BY I; +> I RSHIFT(-128, I) RSHIFT(1, I) URSHIFT(-128, I) URSHIFT(1, I) +> ---- --------------- ------------ ---------------- ------------- +> -111 0 0 0 0 +> -8 0 0 0 0 +> -7 0 -128 0 -128 +> -1 0 2 0 2 +> 0 -128 1 -128 1 +> 1 -64 0 64 0 +> 7 -1 0 1 0 +> 8 -1 0 0 0 +> 111 -1 0 0 0 +> rows (ordered): 9 + +SELECT I, + RSHIFT(CAST(-32768 AS SMALLINT), I), RSHIFT(CAST(1 AS SMALLINT), I), + URSHIFT(CAST(-32768 AS SMALLINT), I), URSHIFT(CAST(1 AS SMALLINT), I) + FROM + (VALUES -111, -16, -15, -1, 0, 1, 15, 16, 111) T(I) ORDER BY I; +> I RSHIFT(-32768, I) RSHIFT(1, I) URSHIFT(-32768, I) URSHIFT(1, I) +> ---- ----------------- ------------ ------------------ ------------- +> -111 0 0 0 0 +> -16 0 0 0 0 +> -15 0 -32768 0 -32768 +> -1 0 2 0 2 +> 0 -32768 1 -32768 1 +> 1 -16384 0 16384 0 +> 15 -1 0 1 0 +> 16 -1 0 0 0 +> 111 -1 0 0 0 +> rows (ordered): 9 + +SELECT I, + RSHIFT(CAST(-2147483648 AS INTEGER), I), RSHIFT(CAST(1 AS INTEGER), I), + URSHIFT(CAST(-2147483648 AS INTEGER), I), URSHIFT(CAST(1 AS INTEGER), I) + FROM + (VALUES -111, -32, -31, -1, 0, 1, 31, 32, 111) T(I) ORDER BY I; +> I RSHIFT(-2147483648, I) RSHIFT(1, I) URSHIFT(-2147483648, I) URSHIFT(1, I) +> ---- ---------------------- ------------ ----------------------- ------------- +> -111 0 0 0 0 +> -32 0 0 0 0 +> -31 0 -2147483648 0 -2147483648 +> -1 0 2 0 2 +> 0 -2147483648 1 -2147483648 1 +> 1 -1073741824 0 1073741824 0 +> 31 -1 0 1 0 +> 32 -1 0 0 0 +> 111 -1 0 0 0 +> rows (ordered): 9 + +SELECT I, + RSHIFT(CAST(-9223372036854775808 AS BIGINT), I), RSHIFT(CAST(1 AS BIGINT), I), + URSHIFT(CAST(-9223372036854775808 AS BIGINT), I), URSHIFT(CAST(1 AS BIGINT), I) + FROM + (VALUES -111, -64, -63, -1, 0, 1, 63, 64, 111) T(I) ORDER BY I; +> I RSHIFT(-9223372036854775808, I) RSHIFT(1, I) URSHIFT(-9223372036854775808, I) URSHIFT(1, I) +> ---- ------------------------------- -------------------- -------------------------------- -------------------- +> -111 0 0 0 0 +> -64 0 0 0 0 +> -63 0 -9223372036854775808 0 -9223372036854775808 +> -1 0 2 0 2 +> 0 -9223372036854775808 1 -9223372036854775808 1 +> 1 -4611686018427387904 0 4611686018427387904 0 +> 63 -1 0 1 0 +> 64 -1 0 0 0 +> 111 -1 0 0 0 +> rows (ordered): 9 + +SELECT RSHIFT(X'', 1); +>> X'' + +SELECT RSHIFT(CAST(X'02' AS BINARY), 1); +>> X'01' + +SELECT I, RSHIFT(X'80ABCD09', I) FROM + (VALUES -33, -32, -31, -17, -16, -15, -1, 0, 1, 15, 16, 17, 31, 32, 33) T(I) ORDER BY I; +> I RSHIFT(X'80abcd09', I) +> --- ---------------------- +> -33 X'00000000' +> -32 X'00000000' +> -31 X'80000000' +> -17 X'9a120000' +> -16 X'cd090000' +> -15 X'e6848000' +> -1 X'01579a12' +> 0 X'80abcd09' +> 1 X'4055e684' +> 15 X'00010157' +> 16 X'000080ab' +> 17 X'00004055' +> 31 X'00000001' +> 32 X'00000000' +> 33 X'00000000' +> rows (ordered): 15 + +SELECT RSHIFT(-1, -9223372036854775808); +>> 0 + +SELECT URSHIFT(-1, -9223372036854775808); +>> 0 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/secure-rand.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/secure-rand.sql new file mode 100644 index 0000000..a083f92 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/secure-rand.sql @@ -0,0 +1,13 @@ +-- 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 +-- + +SELECT SECURE_RAND(NULL); +>> null + +SELECT OCTET_LENGTH(SECURE_RAND(0)); +>> 1 + +SELECT OCTET_LENGTH(SECURE_RAND(2)); +>> 2 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/sign.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/sign.sql new file mode 100644 index 0000000..2138f8f --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/sign.sql @@ -0,0 +1,16 @@ +-- 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 +-- + +select sign(null) en, sign(10) e1, sign(0) e0, sign(-0.1) em1; +> EN E1 E0 EM1 +> ---- -- -- --- +> null 1 0 -1 +> rows: 1 + +SELECT SIGN(INTERVAL '-0-1' YEAR TO MONTH) A, SIGN(INTERVAL '0' DAY) B, SIGN(INTERVAL '1' HOUR) C; +> A B C +> -- - - +> -1 0 1 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/sin.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/sin.sql new file mode 100644 index 0000000..f2f1146 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/sin.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +select sin(null) vn, sin(-1) r1; +> VN R1 +> ---- ------------------- +> null -0.8414709848078965 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/sinh.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/sinh.sql new file mode 100644 index 0000000..2186ea8 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/sinh.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +CALL SINH(1); +>> 1.1752011936438014 + +CALL SINH(50); +>> 2.592352764293536E21 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/sqrt.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/sqrt.sql new file mode 100644 index 0000000..4a96f3a --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/sqrt.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +select sqrt(null) vn, sqrt(0) e0, sqrt(1) e1, sqrt(4) e2, sqrt(100) e10, sqrt(0.25) e05; +> VN E0 E1 E2 E10 E05 +> ---- --- --- --- ---- --- +> null 0.0 1.0 2.0 10.0 0.5 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/tan.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/tan.sql new file mode 100644 index 0000000..13bcd44 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/tan.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +select tan(null) vn, tan(-1) r1; +> VN R1 +> ---- ------------------- +> null -1.5574077246549023 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/tanh.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/tanh.sql new file mode 100644 index 0000000..b6765cc --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/tanh.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +CALL TANH(1); +>> 0.7615941559557649 + +CALL TANH(50); +>> 1.0 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/truncate.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/truncate.sql new file mode 100644 index 0000000..0dbe8c9 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/truncate.sql @@ -0,0 +1,131 @@ +-- 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 +-- + +SELECT TRUNCATE(1.234, 2); +>> 1.23 + +SELECT TRUNCATE(DATE '2011-03-05'); +>> 2011-03-05 00:00:00 + +SELECT TRUNCATE(TIMESTAMP '2011-03-05 02:03:04'); +>> 2011-03-05 00:00:00 + +SELECT TRUNCATE(TIMESTAMP WITH TIME ZONE '2011-03-05 02:03:04+07'); +>> 2011-03-05 00:00:00+07 + +SELECT TRUNCATE(CURRENT_DATE, 1); +> exception INVALID_PARAMETER_COUNT_2 + +SELECT TRUNCATE(LOCALTIMESTAMP, 1); +> exception INVALID_PARAMETER_COUNT_2 + +SELECT TRUNCATE(CURRENT_TIMESTAMP, 1); +> exception INVALID_PARAMETER_COUNT_2 + +SELECT TRUNCATE('2011-03-05 02:03:04', 1); +> exception INVALID_PARAMETER_COUNT_2 + +SELECT TRUNCATE('bad'); +> exception INVALID_DATETIME_CONSTANT_2 + +SELECT TRUNCATE(1, 2, 3); +> exception SYNTAX_ERROR_2 + +select truncate(null, null) en, truncate(1.99, 0) e1, truncate(-10.9, 0) em10; +> EN E1 EM10 +> ---- -- ---- +> null 1 -10 +> rows: 1 + +select trunc(null, null) en, trunc(1.99, 0) e1, trunc(-10.9, 0) em10; +> EN E1 EM10 +> ---- -- ---- +> null 1 -10 +> rows: 1 + +select trunc(1.3); +>> 1 + +SELECT TRUNCATE(1.3) IS OF (NUMERIC); +>> TRUE + +SELECT TRUNCATE(CAST(1.3 AS DOUBLE)) IS OF (DOUBLE); +>> TRUE + +SELECT TRUNCATE(CAST(1.3 AS REAL)) IS OF (REAL); +>> TRUE + +SELECT TRUNCATE(1.99, 0), TRUNCATE(1.99, 1), TRUNCATE(-1.99, 0), TRUNCATE(-1.99, 1); +> 1 1.9 -1 -1.9 +> - --- -- ---- +> 1 1.9 -1 -1.9 +> rows: 1 + +SELECT TRUNCATE(1.99::DOUBLE, 0), TRUNCATE(1.99::DOUBLE, 1), TRUNCATE(-1.99::DOUBLE, 0), TRUNCATE(-1.99::DOUBLE, 1); +> 1.0 1.9 -1.0 -1.9 +> --- --- ---- ---- +> 1.0 1.9 -1.0 -1.9 +> rows: 1 + +SELECT TRUNCATE(1.99::REAL, 0), TRUNCATE(1.99::REAL, 1), TRUNCATE(-1.99::REAL, 0), TRUNCATE(-1.99::REAL, 1); +> 1.0 1.9 -1.0 -1.9 +> --- --- ---- ---- +> 1.0 1.9 -1.0 -1.9 +> rows: 1 + +SELECT TRUNCATE(V, S) FROM (VALUES (1.111, 1)) T(V, S); +>> 1.100 + +SELECT TRUNC(1, 10000000); +>> 1 + +CREATE TABLE T1(N NUMERIC(10, 2), D DECFLOAT(10), I INTEGER) AS VALUES (99999999.99, 99999999.99, 10); +> ok + +SELECT TRUNC(N, -1) NN, TRUNC(N) N0, TRUNC(N, 1) N1, TRUNC(N, 2) N2, TRUNC(N, 3) N3, TRUNC(N, 10000000) NL, + TRUNC(D) D0, TRUNC(D, 2) D2, TRUNC(D, 3) D3, + TRUNC(I) I0, TRUNC(I, 1) I1, TRUNC(I, I) II FROM T1; +> NN N0 N1 N2 N3 NL D0 D2 D3 I0 I1 II +> -------- -------- ---------- ----------- ----------- ----------- -------- ----------- ----------- -- -- -- +> 99999990 99999999 99999999.9 99999999.99 99999999.99 99999999.99 99999999 99999999.99 99999999.99 10 10 10 +> rows: 1 + +CREATE TABLE T2 AS SELECT TRUNC(N, -1) NN, TRUNC(N) N0, TRUNC(N, 1) N1, TRUNC(N, 2) N2, TRUNC(N, 3) N3, TRUNC(N, 10000000) NL, + TRUNC(D) D0, TRUNC(D, 2) D2, TRUNC(D, 3) D3, + TRUNC(I) I0, TRUNC(I, 1) I1, TRUNC(I, I) II FROM T1; +> ok + +SELECT COLUMN_NAME, DATA_TYPE, NUMERIC_PRECISION, NUMERIC_SCALE FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'T2' ORDER BY ORDINAL_POSITION; +> COLUMN_NAME DATA_TYPE NUMERIC_PRECISION NUMERIC_SCALE +> ----------- --------- ----------------- ------------- +> NN NUMERIC 8 0 +> N0 NUMERIC 8 0 +> N1 NUMERIC 9 1 +> N2 NUMERIC 10 2 +> N3 NUMERIC 10 2 +> NL NUMERIC 10 2 +> D0 DECFLOAT 10 null +> D2 DECFLOAT 10 null +> D3 DECFLOAT 10 null +> I0 INTEGER 32 0 +> I1 INTEGER 32 0 +> II INTEGER 32 0 +> rows (ordered): 12 + +DROP TABLE T1; +> ok + +SELECT TRUNC(11, -1) I, TRUNC(CAST(11 AS NUMERIC(2)), -1) N; +> I N +> -- -- +> 10 10 +> rows: 1 + +SELECT TRUNC(11, -2) I, TRUNC(CAST(11 AS NUMERIC(2)), -2) N; +> I N +> - - +> 0 0 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/numeric/zero.sql b/h2/src/test/org/h2/test/scripts/functions/numeric/zero.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/numeric/zero.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/string/array-to-string.sql b/h2/src/test/org/h2/test/scripts/functions/string/array-to-string.sql new file mode 100644 index 0000000..7ca0767 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/array-to-string.sql @@ -0,0 +1,34 @@ +-- 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 +-- + +set mode PostgreSQL; +> ok + +select array_to_string(array[null, 0, 1, null, 2], ','); +>> 0,1,2 + +select array_to_string(array['a', null, '', 'b', null], ',', null); +>> a,,b + +select array_to_string(array[null, 0, 1, null, 2], ',', '*'); +>> *,0,1,*,2 + +select array_to_string(array['a', null, '', 'b', null], ',', '*'); +>> a,*,,b,* + +select array_to_string(array[1, null, 3], 0, 2); +>> 10203 + +select array_to_string(null, 0, 2); +>> null + +select array_to_string(array[1, null, 3], null, 2); +>> null + +select array_to_string(0, ','); +> exception INVALID_VALUE_2 + +set mode Regular; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/string/ascii.sql b/h2/src/test/org/h2/test/scripts/functions/string/ascii.sql new file mode 100644 index 0000000..17fa38d --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/ascii.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +select ascii(null) en, ascii('') en, ascii('Abc') e65; +> EN EN E65 +> ---- ---- --- +> null null 65 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/string/bit-length.sql b/h2/src/test/org/h2/test/scripts/functions/string/bit-length.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/bit-length.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/string/char.sql b/h2/src/test/org/h2/test/scripts/functions/string/char.sql new file mode 100644 index 0000000..53bb3c5 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/char.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +select char(null) en, char(65) ea; +> EN EA +> ---- -- +> null A +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/string/concat-ws.sql b/h2/src/test/org/h2/test/scripts/functions/string/concat-ws.sql new file mode 100644 index 0000000..ec64776 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/concat-ws.sql @@ -0,0 +1,16 @@ +-- 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 +-- + +SELECT CONCAT_WS(NULL, NULL, 'a', NULL, 'b', NULL); +>> ab + +SELECT CONCAT_WS('*', NULL, 'a', NULL, 'b', NULL); +>> a*b + +SELECT CONCAT_WS('*', '', 'a', NULL, 'b', NULL); +>> *a*b + +SELECT '[' || CONCAT_WS('a', NULL, NULL) || ']'; +>> [] diff --git a/h2/src/test/org/h2/test/scripts/functions/string/concat.sql b/h2/src/test/org/h2/test/scripts/functions/string/concat.sql new file mode 100644 index 0000000..4b1b735 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/concat.sql @@ -0,0 +1,13 @@ +-- 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 +-- + +select concat(null, null) en, concat(null, 'a') ea, concat('b', null) eb, concat('ab', 'c') abc; +> EN EA EB ABC +> -- -- -- --- +> a b abc +> rows: 1 + +SELECT CONCAT('a', 'b', 'c', 'd'); +>> abcd diff --git a/h2/src/test/org/h2/test/scripts/functions/string/difference.sql b/h2/src/test/org/h2/test/scripts/functions/string/difference.sql new file mode 100644 index 0000000..4853dfe --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/difference.sql @@ -0,0 +1,16 @@ +-- 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 +-- + +select difference(null, null) en, difference('a', null) en1, difference(null, 'a') en2; +> EN EN1 EN2 +> ---- ---- ---- +> null null null +> rows: 1 + +select difference('abc', 'abc') e0, difference('Thomas', 'Tom') e1; +> E0 E1 +> -- -- +> 4 3 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/string/hextoraw.sql b/h2/src/test/org/h2/test/scripts/functions/string/hextoraw.sql new file mode 100644 index 0000000..95ea690 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/hextoraw.sql @@ -0,0 +1,25 @@ +-- 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 +-- + +select hextoraw(null) en, rawtohex(null) en1, hextoraw(rawtohex('abc')) abc; +> EN EN1 ABC +> ---- ---- --- +> null null abc +> rows: 1 + +SELECT HEXTORAW('0049'); +>> I + +SET MODE Oracle; +> ok + +SELECT HEXTORAW('0049'); +>> X'0049' + +SELECT HEXTORAW('0049') IS OF (RAW); +>> TRUE + +SET MODE Regular; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/string/insert.sql b/h2/src/test/org/h2/test/scripts/functions/string/insert.sql new file mode 100644 index 0000000..d24cb58 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/insert.sql @@ -0,0 +1,19 @@ +-- 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 +-- + +select insert(null, null, null, null) en, insert('Rund', 1, 0, 'o') e_round, insert(null, 1, 1, 'a') ea; +> EN E_ROUND EA +> ---- ------- -- +> null Rund a +> rows: 1 + +select insert('World', 2, 4, 'e') welt, insert('Hello', 2, 1, 'a') hallo; +> WELT HALLO +> ---- ----- +> We Hallo +> rows: 1 + +SELECT INSERT(NULL, 0, 0, NULL); +>> null diff --git a/h2/src/test/org/h2/test/scripts/functions/string/left.sql b/h2/src/test/org/h2/test/scripts/functions/string/left.sql new file mode 100644 index 0000000..fcf92c1 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/left.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +select left(null, 10) en, left('abc', null) en2, left('boat', 2) e_bo, left('', 1) ee, left('a', -1) ee2; +> EN EN2 E_BO EE EE2 +> ---- ---- ---- -- --- +> null null bo +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/string/length.sql b/h2/src/test/org/h2/test/scripts/functions/string/length.sql new file mode 100644 index 0000000..ebf2bae --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/length.sql @@ -0,0 +1,31 @@ +-- 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 +-- + +select length(null) en, length('This has 17 chars') e_17; +> EN E_17 +> ---- ---- +> null 17 +> rows: 1 + +SELECT LEN(NULL); +> exception FUNCTION_NOT_FOUND_1 + +SET MODE MSSQLServer; +> ok + +select len(null) en, len('MSSQLServer uses the len keyword') e_32; +> EN E_32 +> ---- ---- +> null 32 +> rows: 1 + +SELECT LEN('A '); +>> 2 + +SELECT LEN(CAST('A ' AS CHAR(2))); +>> 1 + +SET MODE Regular; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/string/locate.sql b/h2/src/test/org/h2/test/scripts/functions/string/locate.sql new file mode 100644 index 0000000..fe1bf6d --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/locate.sql @@ -0,0 +1,49 @@ +-- 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 +-- + +select locate(null, null) en, locate(null, null, null) en1; +> EN EN1 +> ---- ---- +> null null +> rows: 1 + +select locate('World', 'Hello World') e7, locate('hi', 'abchihihi', 2) e3; +> E7 E3 +> -- -- +> 7 4 +> rows: 1 + +SELECT CHARINDEX('test', 'test'); +> exception FUNCTION_NOT_FOUND_1 + +SET MODE MSSQLServer; +> ok + +select charindex('World', 'Hello World') e7, charindex('hi', 'abchihihi', 2) e3; +> E7 E3 +> -- -- +> 7 4 +> rows: 1 + +SET MODE Regular; +> ok + +select instr('Hello World', 'World') e7, instr('abchihihi', 'hi', 2) e3, instr('abcooo', 'o') e2; +> E7 E3 E2 +> -- -- -- +> 7 4 4 +> rows: 1 + +EXPLAIN SELECT INSTR(A, B) FROM (VALUES ('A', 'B')) T(A, B); +>> SELECT LOCATE("B", "A") FROM (VALUES ('A', 'B')) "T"("A", "B") /* table scan */ + +select position(null, null) en, position(null, 'abc') en1, position('World', 'Hello World') e7, position('hi', 'abchihihi') e1; +> EN EN1 E7 E1 +> ---- ---- -- -- +> null null 7 4 +> rows: 1 + +EXPLAIN SELECT POSITION((A > B), C) FROM (VALUES (1, 2, 3)) T(A, B, C); +>> SELECT LOCATE("A" > "B", "C") FROM (VALUES (1, 2, 3)) "T"("A", "B", "C") /* table scan */ diff --git a/h2/src/test/org/h2/test/scripts/functions/string/lower.sql b/h2/src/test/org/h2/test/scripts/functions/string/lower.sql new file mode 100644 index 0000000..73138cf --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/lower.sql @@ -0,0 +1,16 @@ +-- 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 +-- + +select lower(null) en, lower('Hello') hello, lower('ABC') abc; +> EN HELLO ABC +> ---- ----- --- +> null hello abc +> rows: 1 + +select lcase(null) en, lcase('Hello') hello, lcase('ABC') abc; +> EN HELLO ABC +> ---- ----- --- +> null hello abc +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/string/lpad.sql b/h2/src/test/org/h2/test/scripts/functions/string/lpad.sql new file mode 100644 index 0000000..41c69eb --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/lpad.sql @@ -0,0 +1,7 @@ +-- 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 +-- + +select lpad('string', 10, '+'); +>> ++++string 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 new file mode 100644 index 0000000..daf8e3e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/ltrim.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +select ltrim(null) en, '>' || ltrim('a') || '<' ea, '>' || ltrim(' a ') || '<' e_as; +> EN EA E_AS +> ---- --- ---- +> null >a< >a < +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/string/octet-length.sql b/h2/src/test/org/h2/test/scripts/functions/string/octet-length.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/octet-length.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/string/quote_ident.sql b/h2/src/test/org/h2/test/scripts/functions/string/quote_ident.sql new file mode 100644 index 0000000..8c8b946 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/quote_ident.sql @@ -0,0 +1,16 @@ +-- 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 +-- + +SELECT QUOTE_IDENT(NULL); +>> null + +SELECT QUOTE_IDENT(''); +>> "" + +SELECT QUOTE_IDENT('a'); +>> "a" + +SELECT QUOTE_IDENT('"a""A"'); +>> """a""""A""" diff --git a/h2/src/test/org/h2/test/scripts/functions/string/rawtohex.sql b/h2/src/test/org/h2/test/scripts/functions/string/rawtohex.sql new file mode 100644 index 0000000..05e418b --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/rawtohex.sql @@ -0,0 +1,28 @@ +-- 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 +-- + +SELECT RAWTOHEX('A'); +>> 0041 + +SELECT RAWTOHEX('Az'); +>> 0041007a + +SET MODE Oracle; +> ok + +SELECT RAWTOHEX('A'); +>> 41 + +SELECT RAWTOHEX('Az'); +>> 417a + +SET MODE Regular; +> ok + +SELECT RAWTOHEX(X'12fe'); +>> 12fe + +SELECT RAWTOHEX('12345678-9abc-def0-0123-456789abcdef'::UUID); +>> 123456789abcdef00123456789abcdef diff --git a/h2/src/test/org/h2/test/scripts/functions/string/regex-replace.sql b/h2/src/test/org/h2/test/scripts/functions/string/regex-replace.sql new file mode 100644 index 0000000..24a51ec --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/regex-replace.sql @@ -0,0 +1,76 @@ +-- 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 +-- + +call regexp_replace('x', 'x', '\'); +> exception LIKE_ESCAPE_ERROR_1 + +CALL REGEXP_REPLACE('abckaboooom', 'o+', 'o'); +>> abckabom + +select regexp_replace('Sylvain', 'S..', 'TOTO', 'mni'); +>> TOTOvain + +set mode oracle; +> ok + +select regexp_replace('.1.2.3.4', '[^0-9]', '', 1, 0); +>> 1234 + +select regexp_replace('.1.2.3.4', '[^0-9]', '', 1, 1); +>> 1.2.3.4 + +select regexp_replace('.1.2.3.4', '[^0-9]', '', 1, 2); +>> .12.3.4 + +select regexp_replace('.1.2.3.4', '[^0-9]', '', 3, 2); +>> .1.23.4 + +select regexp_replace('', '[^0-9]', '', 3, 2); +>> null + +select regexp_replace('ababab', '', '', 3, 2); +>> ababab + +select regexp_replace('ababab', '', '', 3, 2, ''); +>> ababab + +select regexp_replace('first last', '(\w+) (\w+)', '\2 \1'); +>> last first + +select regexp_replace('first last', '(\w+) (\w+)', '\\2 \1'); +>> \2 first + +select regexp_replace('first last', '(\w+) (\w+)', '\$2 \1'); +>> $2 first + +select regexp_replace('first last', '(\w+) (\w+)', '$2 $1'); +>> $2 $1 + +set mode regular; +> ok + +select regexp_replace('first last', '(\w+) (\w+)', '\2 \1'); +>> 2 1 + +select regexp_replace('first last', '(\w+) (\w+)', '$2 $1'); +>> last first + +select regexp_replace('AbcDef', '[^a-z]', '', 'g'); +> exception INVALID_VALUE_2 + +select regexp_replace('First and Second', '[A-Z]', ''); +>> irst and econd + +set mode PostgreSQL; +> ok + +select regexp_replace('AbcDef', '[^a-z]', '', 'g'); +>> bcef + +select regexp_replace('AbcDef123', '[a-z]', '!', 'gi'); +>> !!!!!!123 + +select regexp_replace('First Only', '[A-Z]', ''); +>> irst Only diff --git a/h2/src/test/org/h2/test/scripts/functions/string/regexp-like.sql b/h2/src/test/org/h2/test/scripts/functions/string/regexp-like.sql new file mode 100644 index 0000000..5f86d7f --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/regexp-like.sql @@ -0,0 +1,13 @@ +-- 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 +-- + +call select 1 from dual where regexp_like('x', 'x', '\'); +> exception INVALID_VALUE_2 + +CALL REGEXP_LIKE('A', '[a-z]', 'i'); +>> TRUE + +CALL REGEXP_LIKE('A', '[a-z]', 'c'); +>> FALSE diff --git a/h2/src/test/org/h2/test/scripts/functions/string/regexp-substr.sql b/h2/src/test/org/h2/test/scripts/functions/string/regexp-substr.sql new file mode 100644 index 0000000..b7c984a --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/regexp-substr.sql @@ -0,0 +1,83 @@ +-- 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 +-- + +-- case insensitive matches upper case +CALL REGEXP_SUBSTR('A', '[a-z]', 1, 1, 'i'); +>> A + +-- case sensitive does not match upper case +CALL REGEXP_SUBSTR('A', '[a-z]', 1, 1, 'c'); +>> null + +-- match string from position at string index 3 +CALL REGEXP_SUBSTR('help helpful', 'help.*', 3); +>> helpful + +-- match string from position at string index 6 +CALL REGEXP_SUBSTR('help helpful helping', 'help.*', 7); +>> helping + +-- should return first occurrence +CALL REGEXP_SUBSTR('helpful helping', 'help\w*', 1, 1); +>> helpful + +-- should return second occurrence +CALL REGEXP_SUBSTR('helpful helping', 'help\w*', 1, 2); +>> helping + +-- should return third occurrence +CALL REGEXP_SUBSTR('help helpful helping', 'help\w*', 1, 3); +>> helping + +-- should return first occurrence, after string at index 3 +CALL REGEXP_SUBSTR('help helpful helping', 'help\w*', 3, 1); +>> helpful + +-- should first matching group +CALL REGEXP_SUBSTR('help helpful helping', '(help\w*)', 1, 1, NULL, 1); +>> help + +-- should second occurrence of first group +CALL REGEXP_SUBSTR('help helpful helping', '(help\w*)', 1, 2, NULL, 1); +>> helpful + +-- should second group +CALL REGEXP_SUBSTR('2020-10-01', '(\d{4})-(\d{2})-(\d{2})', 1, 1, NULL, 2); +>> 10 + +-- should third group +CALL REGEXP_SUBSTR('2020-10-01', '(\d{4})-(\d{2})-(\d{2})', 1, 1, NULL, 3); +>> 01 + +CALL REGEXP_SUBSTR('2020-10-01', '\d{4}'); +>> 2020 + +-- Test variants of passing NULL, which should always result in NULL result +CALL REGEXP_SUBSTR('2020-10-01', NULL); +>> null + +CALL REGEXP_SUBSTR(NULL, '\d{4}'); +>> null + +CALL REGEXP_SUBSTR(NULL, NULL); +>> null + +CALL REGEXP_SUBSTR('2020-10-01', '\d{4}', NULL); +>> null + +CALL REGEXP_SUBSTR('2020-10-01', '\d{4}', 1, NULL); +>> null + +CALL REGEXP_SUBSTR('2020-10-01', '\d{4}', 1, 1, NULL, NULL); +>> null + +-- Index out of bounds +CALL REGEXP_SUBSTR('2020-10-01', '(\d{4})', 1, 1, NULL, 10); +>> null + +-- Illegal regexp pattern +CALL REGEXP_SUBSTR('2020-10-01', '\d{a}'); +> exception LIKE_ESCAPE_ERROR_1 + diff --git a/h2/src/test/org/h2/test/scripts/functions/string/repeat.sql b/h2/src/test/org/h2/test/scripts/functions/string/repeat.sql new file mode 100644 index 0000000..68b0622 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/repeat.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +select repeat(null, null) en, repeat('Ho', 2) abcehoho , repeat('abc', 0) ee; +> EN ABCEHOHO EE +> ---- -------- -- +> null HoHo +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/string/replace.sql b/h2/src/test/org/h2/test/scripts/functions/string/replace.sql new file mode 100644 index 0000000..19966c3 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/replace.sql @@ -0,0 +1,25 @@ +-- 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 +-- + +select replace(null, null) en, replace(null, null, null) en1; +> EN EN1 +> ---- ---- +> null null +> rows: 1 + +select replace('abchihihi', 'i', 'o') abcehohoho, replace('that is tom', 'i') abcethstom; +> ABCEHOHOHO ABCETHSTOM +> ---------- ---------- +> abchohoho that s tom +> rows: 1 + +set mode oracle; +> ok + +select replace('white space', ' ', '') x, replace('white space', ' ', null) y from dual; +> X Y +> ---------- ---------- +> whitespace whitespace +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/string/right.sql b/h2/src/test/org/h2/test/scripts/functions/string/right.sql new file mode 100644 index 0000000..c56fdca --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/right.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +select right(null, 10) en, right('abc', null) en2, right('boat-trip', 2) e_ip, right('', 1) ee, right('a', -1) ee2; +> EN EN2 E_IP EE EE2 +> ---- ---- ---- -- --- +> null null ip +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/string/rpad.sql b/h2/src/test/org/h2/test/scripts/functions/string/rpad.sql new file mode 100644 index 0000000..0d7e635 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/rpad.sql @@ -0,0 +1,7 @@ +-- 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 +-- + +select rpad('string', 10, '+'); +>> string++++ 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 new file mode 100644 index 0000000..a216fd6 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/rtrim.sql @@ -0,0 +1,13 @@ +-- 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 +-- + +select rtrim(null) en, '>' || rtrim('a') || '<' ea, '>' || rtrim(' a ') || '<' es; +> EN EA ES +> ---- --- ---- +> null >a< > a< +> rows: 1 + +select rtrim() from dual; +> exception SYNTAX_ERROR_2 diff --git a/h2/src/test/org/h2/test/scripts/functions/string/soundex.sql b/h2/src/test/org/h2/test/scripts/functions/string/soundex.sql new file mode 100644 index 0000000..fec64ae --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/soundex.sql @@ -0,0 +1,20 @@ +-- 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 +-- + +select soundex(null) en, soundex('tom') et; +> EN ET +> ---- ---- +> null t500 +> rows: 1 + +select +soundex('Washington') W252, soundex('Lee') L000, +soundex('Gutierrez') G362, soundex('Pfister') P236, +soundex('Jackson') J250, soundex('Tymczak') T522, +soundex('VanDeusen') V532, soundex('Ashcraft') A261; +> W252 L000 G362 P236 J250 T522 V532 A261 +> ---- ---- ---- ---- ---- ---- ---- ---- +> W252 L000 G362 P236 J250 T522 V532 A261 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/string/space.sql b/h2/src/test/org/h2/test/scripts/functions/string/space.sql new file mode 100644 index 0000000..867bd74 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/space.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +select space(null) en, '>' || space(1) || '<' es, '>' || space(3) || '<' e2; +> EN ES E2 +> ---- --- --- +> null > < > < +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/string/stringdecode.sql b/h2/src/test/org/h2/test/scripts/functions/string/stringdecode.sql new file mode 100644 index 0000000..3a2b439 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/stringdecode.sql @@ -0,0 +1,22 @@ +-- 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 +-- + +SELECT STRINGDECODE('\7'); +> exception STRING_FORMAT_ERROR_1 + +SELECT STRINGDECODE('\17'); +> exception STRING_FORMAT_ERROR_1 + +SELECT STRINGDECODE('\117'); +>> O + +SELECT STRINGDECODE('\178'); +> exception STRING_FORMAT_ERROR_1 + +SELECT STRINGDECODE('\u111'); +> exception STRING_FORMAT_ERROR_1 + +SELECT STRINGDECODE('\u0057'); +>> W diff --git a/h2/src/test/org/h2/test/scripts/functions/string/stringencode.sql b/h2/src/test/org/h2/test/scripts/functions/string/stringencode.sql new file mode 100644 index 0000000..72274a9 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/stringencode.sql @@ -0,0 +1,13 @@ +-- 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 +-- + +SELECT STRINGENCODE(STRINGDECODE('abcsond\344rzeich\344 ') || char(22222) || STRINGDECODE(' \366\344\374\326\304\334\351\350\340\361!')); +>> abcsond\u00e4rzeich\u00e4 \u56ce \u00f6\u00e4\u00fc\u00d6\u00c4\u00dc\u00e9\u00e8\u00e0\u00f1! + +call STRINGENCODE(STRINGDECODE('abcsond\344rzeich\344 \u56ce \366\344\374\326\304\334\351\350\340\361!')); +>> abcsond\u00e4rzeich\u00e4 \u56ce \u00f6\u00e4\u00fc\u00d6\u00c4\u00dc\u00e9\u00e8\u00e0\u00f1! + +CALL STRINGENCODE(STRINGDECODE('Lines 1\nLine 2')); +>> Lines 1\nLine 2 diff --git a/h2/src/test/org/h2/test/scripts/functions/string/stringtoutf8.sql b/h2/src/test/org/h2/test/scripts/functions/string/stringtoutf8.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/stringtoutf8.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/string/substring.sql b/h2/src/test/org/h2/test/scripts/functions/string/substring.sql new file mode 100644 index 0000000..624fc96 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/substring.sql @@ -0,0 +1,82 @@ +-- 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 +-- + +select substr(null, null) en, substr(null, null, null) e1, substr('bob', 2) e_ob, substr('bob', 2, 1) eo; +> EN E1 E_OB EO +> ---- ---- ---- -- +> null null ob o +> rows: 1 + +select substring(null, null) en, substring(null, null, null) e1, substring('bob', 2) e_ob, substring('bob', 2, 1) eo; +> EN E1 E_OB EO +> ---- ---- ---- -- +> null null ob o +> rows: 1 + +select substring(null from null) en, substring(null from null for null) e1, substring('bob' from 2) e_ob, substring('bob' from 2 for 1) eo; +> EN E1 E_OB EO +> ---- ---- ---- -- +> null null ob o +> rows: 1 + +select substr('[Hello]', 2, 5); +>> Hello + +-- Compatibility syntax +select substr('Hello World', -5); +>> World + +-- Compatibility +SELECT SUBSTRING('X', 0, 1); +>> X + +CREATE TABLE TEST(STR VARCHAR, START INT, LEN INT); +> ok + +EXPLAIN SELECT SUBSTRING(STR FROM START), SUBSTRING(STR FROM START FOR LEN) FROM TEST; +>> SELECT SUBSTRING("STR" FROM "START"), SUBSTRING("STR" FROM "START" FOR "LEN") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +DROP TABLE TEST; +> ok + +SELECT SUBSTRING('AAA' FROM 4 FOR 1); +> '' +> -- +> +> rows: 1 + +SELECT SUBSTRING(X'001122' FROM 1 FOR 3); +>> X'001122' + +SELECT SUBSTRING(X'001122' FROM 1 FOR 2); +>> X'0011' + +SELECT SUBSTRING(X'001122' FROM 2 FOR 2); +>> X'1122' + +SELECT SUBSTRING(X'001122' FROM 4 FOR 1); +>> X'' + +SELECT SUBSTRING(X'001122' FROM 2 FOR 1); +>> X'11' + +CREATE MEMORY TABLE TEST AS (VALUES SUBSTRING(X'0011' FROM 2)); +> ok + +-- Compatibility +SELECT SUBSTRING(X'00', 0, 1); +>> X'00' + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION TABLE TEST; +> SCRIPT +> -------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "C1" BINARY VARYING(1) ); +> -- 1 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> INSERT INTO "PUBLIC"."TEST" VALUES (X'11'); +> rows (ordered): 4 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/string/to-char.sql b/h2/src/test/org/h2/test/scripts/functions/string/to-char.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/to-char.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/string/translate.sql b/h2/src/test/org/h2/test/scripts/functions/string/translate.sql new file mode 100644 index 0000000..4e9207a --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/translate.sql @@ -0,0 +1,37 @@ +-- 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 +-- + +CREATE TABLE testTranslate(id BIGINT, txt1 VARCHAR); +> ok + +INSERT INTO testTranslate(id, txt1) values(1, 'test1'), (2, NULL), (3, ''), (4, 'caps'); +> update count: 4 + +SELECT TRANSLATE(txt1, 'p', 'r') FROM testTranslate ORDER BY id; +> TRANSLATE(TXT1, 'p', 'r') +> ------------------------- +> test1 +> null +> +> cars +> rows (ordered): 4 + +SET MODE DB2; +> ok + +SELECT TRANSLATE(txt1, 'p', 'r') FROM testTranslate WHERE txt1 = 'caps'; +>> caps + +SELECT TRANSLATE(txt1, 'r', 'p') FROM testTranslate WHERE txt1 = 'caps'; +>> cars + +SET MODE Regular; +> ok + +SELECT TRANSLATE(NULL, NULL, NULL); +>> null + +DROP TABLE testTranslate; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/string/trim.sql b/h2/src/test/org/h2/test/scripts/functions/string/trim.sql new file mode 100644 index 0000000..c4d1f53 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/trim.sql @@ -0,0 +1,31 @@ +-- 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 +-- + +CREATE TABLE TEST(ID INT PRIMARY KEY, A VARCHAR, B VARCHAR, C VARCHAR) AS VALUES (1, '__A__', ' B ', 'xAx'); +> ok + +SELECT TRIM(BOTH '_' FROM A), '|' || TRIM(LEADING FROM B) || '|', TRIM(TRAILING 'x' FROM C) FROM TEST; +> TRIM('_' FROM A) '|' || TRIM(LEADING FROM B) || '|' TRIM(TRAILING 'x' FROM C) +> ---------------- ---------------------------------- ------------------------- +> A |B | xA +> rows: 1 + +SELECT LENGTH(TRIM(B)), LENGTH(TRIM(FROM B)) FROM TEST; +> CHAR_LENGTH(TRIM(B)) CHAR_LENGTH(TRIM(B)) +> -------------------- -------------------- +> 1 1 +> rows: 1 + +SELECT TRIM(BOTH B) FROM TEST; +> exception SYNTAX_ERROR_2 + +DROP TABLE TEST; +> ok + +select TRIM(' ' FROM ' abc ') from dual; +> 'abc' +> ----- +> abc +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/string/upper.sql b/h2/src/test/org/h2/test/scripts/functions/string/upper.sql new file mode 100644 index 0000000..cbdaa1f --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/upper.sql @@ -0,0 +1,16 @@ +-- 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 +-- + +select ucase(null) en, ucase('Hello') hello, ucase('ABC') abc; +> EN HELLO ABC +> ---- ----- --- +> null HELLO ABC +> rows: 1 + +select upper(null) en, upper('Hello') hello, upper('ABC') abc; +> EN HELLO ABC +> ---- ----- --- +> null HELLO ABC +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/string/utf8tostring.sql b/h2/src/test/org/h2/test/scripts/functions/string/utf8tostring.sql new file mode 100644 index 0000000..16a4562 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/utf8tostring.sql @@ -0,0 +1,7 @@ +-- 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 +-- + +CALL UTF8TOSTRING(STRINGTOUTF8('This is a test')); +>> This is a test diff --git a/h2/src/test/org/h2/test/scripts/functions/string/xmlattr.sql b/h2/src/test/org/h2/test/scripts/functions/string/xmlattr.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/xmlattr.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/string/xmlcdata.sql b/h2/src/test/org/h2/test/scripts/functions/string/xmlcdata.sql new file mode 100644 index 0000000..2788160 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/xmlcdata.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +CALL XMLCDATA(''); +>> ]]> + +CALL XMLCDATA('special text ]]>'); +>> special text ]]> diff --git a/h2/src/test/org/h2/test/scripts/functions/string/xmlcomment.sql b/h2/src/test/org/h2/test/scripts/functions/string/xmlcomment.sql new file mode 100644 index 0000000..9e7721a --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/xmlcomment.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +CALL XMLCOMMENT('Test'); +>> + +CALL XMLCOMMENT('--- test ---'); +>> diff --git a/h2/src/test/org/h2/test/scripts/functions/string/xmlnode.sql b/h2/src/test/org/h2/test/scripts/functions/string/xmlnode.sql new file mode 100644 index 0000000..280b762 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/xmlnode.sql @@ -0,0 +1,19 @@ +-- 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 +-- + +CALL XMLNODE('a', XMLATTR('href', 'https://h2database.com')); +>> + +CALL XMLNODE('br'); +>>
    + +CALL XMLNODE('p', null, 'Hello World'); +>>

    Hello World

    + +SELECT XMLNODE('p', null, 'Hello' || chr(10) || 'World'); +>>

    Hello World

    + +SELECT XMLNODE('p', null, 'Hello' || chr(10) || 'World', false); +>>

    Hello World

    diff --git a/h2/src/test/org/h2/test/scripts/functions/string/xmlstartdoc.sql b/h2/src/test/org/h2/test/scripts/functions/string/xmlstartdoc.sql new file mode 100644 index 0000000..4f7d8df --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/xmlstartdoc.sql @@ -0,0 +1,7 @@ +-- 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 +-- + +CALL XMLSTARTDOC(); +>> diff --git a/h2/src/test/org/h2/test/scripts/functions/string/xmltext.sql b/h2/src/test/org/h2/test/scripts/functions/string/xmltext.sql new file mode 100644 index 0000000..9e2b422 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/string/xmltext.sql @@ -0,0 +1,16 @@ +-- 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 +-- + +CALL XMLTEXT('test'); +>> test + +CALL XMLTEXT(''); +>> <test> + +SELECT XMLTEXT('hello' || chr(10) || 'world'); +>> hello world + +CALL XMLTEXT('hello' || chr(10) || 'world', true); +>> hello world diff --git a/h2/src/test/org/h2/test/scripts/functions/system/array-cat.sql b/h2/src/test/org/h2/test/scripts/functions/system/array-cat.sql new file mode 100644 index 0000000..b979da1 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/array-cat.sql @@ -0,0 +1,22 @@ +-- 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 +-- + +select array_cat(ARRAY[1, 2], ARRAY[3, 4]) = ARRAY[1, 2, 3, 4]; +>> TRUE + +select array_cat(ARRAY[1, 2], null) is null; +>> TRUE + +select array_cat(null, ARRAY[1, 2]) is null; +>> TRUE + +select array_append(ARRAY[1, 2], 3) = ARRAY[1, 2, 3]; +>> TRUE + +select array_append(ARRAY[1, 2], null) is null; +>> TRUE + +select array_append(null, 3) is null; +>> TRUE diff --git a/h2/src/test/org/h2/test/scripts/functions/system/array-contains.sql b/h2/src/test/org/h2/test/scripts/functions/system/array-contains.sql new file mode 100644 index 0000000..897c242 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/array-contains.sql @@ -0,0 +1,56 @@ +-- 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 +-- + +select array_contains(ARRAY[4.0, 2.0, 2.0], 2.0); +>> TRUE + +select array_contains(ARRAY[4.0, 2.0, 2.0], 5.0); +>> FALSE + +select array_contains(ARRAY['one', 'two'], 'one'); +>> TRUE + +select array_contains(ARRAY['one', 'two'], 'xxx'); +>> FALSE + +select array_contains(ARRAY['one', 'two'], null); +>> FALSE + +select array_contains(ARRAY[null, 'two'], null); +>> TRUE + +select array_contains(null, 'one'); +>> null + +select array_contains(ARRAY[ARRAY[1, 2], ARRAY[3, 4]], ARRAY[1, 2]); +>> TRUE + +select array_contains(ARRAY[ARRAY[1, 2], ARRAY[3, 4]], ARRAY[5, 6]); +>> FALSE + +CREATE TABLE TEST (ID INT PRIMARY KEY AUTO_INCREMENT, A INT ARRAY); +> ok + +INSERT INTO TEST (A) VALUES (ARRAY[1L, 2L]), (ARRAY[3L, 4L]); +> update count: 2 + +SELECT ID, ARRAY_CONTAINS(A, 1L), ARRAY_CONTAINS(A, 2L), ARRAY_CONTAINS(A, 3L), ARRAY_CONTAINS(A, 4L) FROM TEST; +> ID ARRAY_CONTAINS(A, 1) ARRAY_CONTAINS(A, 2) ARRAY_CONTAINS(A, 3) ARRAY_CONTAINS(A, 4) +> -- -------------------- -------------------- -------------------- -------------------- +> 1 TRUE TRUE FALSE FALSE +> 2 FALSE FALSE TRUE TRUE +> rows: 2 + +SELECT * FROM ( + SELECT ID, ARRAY_CONTAINS(A, 1L), ARRAY_CONTAINS(A, 2L), ARRAY_CONTAINS(A, 3L), ARRAY_CONTAINS(A, 4L) FROM TEST +); +> ID ARRAY_CONTAINS(A, 1) ARRAY_CONTAINS(A, 2) ARRAY_CONTAINS(A, 3) ARRAY_CONTAINS(A, 4) +> -- -------------------- -------------------- -------------------- -------------------- +> 1 TRUE TRUE FALSE FALSE +> 2 FALSE FALSE TRUE TRUE +> rows: 2 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/system/array-get.sql b/h2/src/test/org/h2/test/scripts/functions/system/array-get.sql new file mode 100644 index 0000000..fe9e4b4 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/array-get.sql @@ -0,0 +1,17 @@ +-- 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 +-- + +CREATE TABLE TEST(A INTEGER ARRAY) AS VALUES ARRAY[NULL], ARRAY[1]; +> ok + +SELECT A, ARRAY_GET(A, 1), ARRAY_GET(A, 1) IS OF (INTEGER) FROM TEST; +> A A[1] A[1] IS OF (INTEGER) +> ------ ---- -------------------- +> [1] 1 TRUE +> [null] null null +> rows: 2 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/system/array-slice.sql b/h2/src/test/org/h2/test/scripts/functions/system/array-slice.sql new file mode 100644 index 0000000..09e0d76 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/array-slice.sql @@ -0,0 +1,46 @@ +-- 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 +-- + +select array_slice(ARRAY[1, 2, 3, 4], 1, 1) = ARRAY[1]; +>> TRUE + +select array_slice(ARRAY[1, 2, 3, 4], 1, 3) = ARRAY[1, 2, 3]; +>> TRUE + +-- test invalid indexes +select array_slice(ARRAY[1, 2, 3, 4], 3, 1) is null; +>> TRUE + +select array_slice(ARRAY[1, 2, 3, 4], 0, 3) is null; +>> TRUE + +select array_slice(ARRAY[1, 2, 3, 4], 1, 5) is null; +>> TRUE + +-- in PostgreSQL, indexes are corrected +SET MODE PostgreSQL; +> ok + +select array_slice(ARRAY[1, 2, 3, 4], 3, 1) = ARRAY[]; +>> TRUE + +select array_slice(ARRAY[1, 2, 3, 4], 0, 3) = ARRAY[1, 2, 3]; +>> TRUE + +select array_slice(ARRAY[1, 2, 3, 4], 1, 5) = ARRAY[1, 2, 3, 4]; +>> TRUE + +SET MODE Regular; +> ok + +-- null parameters +select array_slice(null, 1, 3) is null; +>> TRUE + +select array_slice(ARRAY[1, 2, 3, 4], null, 3) is null; +>> TRUE + +select array_slice(ARRAY[1, 2, 3, 4], 1, null) is null; +>> TRUE diff --git a/h2/src/test/org/h2/test/scripts/functions/system/autocommit.sql b/h2/src/test/org/h2/test/scripts/functions/system/autocommit.sql new file mode 100644 index 0000000..8065d08 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/autocommit.sql @@ -0,0 +1,7 @@ +-- 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 +-- + +select autocommit(); +>> TRUE diff --git a/h2/src/test/org/h2/test/scripts/functions/system/cancel-session.sql b/h2/src/test/org/h2/test/scripts/functions/system/cancel-session.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/cancel-session.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/system/cardinality.sql b/h2/src/test/org/h2/test/scripts/functions/system/cardinality.sql new file mode 100644 index 0000000..1d73e7f --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/cardinality.sql @@ -0,0 +1,41 @@ +-- 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 +-- + +SELECT CARDINALITY(NULL); +>> null + +SELECT CARDINALITY(ARRAY[]); +>> 0 + +SELECT CARDINALITY(ARRAY[1, 2, 5]); +>> 3 + +SELECT ARRAY_LENGTH(ARRAY[1, 2, 5]); +>> 3 + +CREATE TABLE TEST(ID INT, A INT ARRAY, B INT ARRAY[2]) AS VALUES (1, NULL, NULL), (2, ARRAY[1], ARRAY[1]); +> ok + +SELECT ID, ARRAY_MAX_CARDINALITY(A), ARRAY_MAX_CARDINALITY(B) FROM TEST; +> ID ARRAY_MAX_CARDINALITY(A) ARRAY_MAX_CARDINALITY(B) +> -- ------------------------ ------------------------ +> 1 65536 2 +> 2 65536 2 +> rows: 2 + +SELECT ARRAY_MAX_CARDINALITY(ARRAY_AGG(ID)) FROM TEST; +>> 65536 + +DROP TABLE TEST; +> ok + +SELECT ARRAY_MAX_CARDINALITY(ARRAY['a', 'b']); +>> 2 + +SELECT ARRAY_MAX_CARDINALITY(NULL); +> exception INVALID_VALUE_2 + +SELECT ARRAY_MAX_CARDINALITY(CAST(NULL AS INT ARRAY)); +>> 65536 diff --git a/h2/src/test/org/h2/test/scripts/functions/system/casewhen.sql b/h2/src/test/org/h2/test/scripts/functions/system/casewhen.sql new file mode 100644 index 0000000..f56f2b1 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/casewhen.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +select casewhen(null, '1', '2') xn, casewhen(1>0, 'n', 'y') xy, casewhen(0<1, 'a', 'b') xa; +> XN XY XA +> -- -- -- +> 2 n a +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/system/cast.sql b/h2/src/test/org/h2/test/scripts/functions/system/cast.sql new file mode 100644 index 0000000..4a343d3 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/cast.sql @@ -0,0 +1,203 @@ +-- 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 +-- + +select cast(null as varchar(255)) xn, cast(' 10' as int) x10, cast(' 20 ' as int) x20; +> XN X10 X20 +> ---- --- --- +> null 10 20 +> rows: 1 + +select cast(128 as varbinary); +>> X'00000080' + +select cast(65535 as varbinary); +>> X'0000ffff' + +select cast(X'ff' as tinyint); +>> -1 + +select cast(X'7f' as tinyint); +>> 127 + +select cast(X'00ff' as smallint); +>> 255 + +select cast(X'000000ff' as int); +>> 255 + +select cast(X'000000000000ffff' as long); +>> 65535 + +select cast(cast(65535 as long) as varbinary); +>> X'000000000000ffff' + +select cast(cast(-1 as tinyint) as varbinary); +>> X'ff' + +select cast(cast(-1 as smallint) as varbinary); +>> X'ffff' + +select cast(cast(-1 as int) as varbinary); +>> X'ffffffff' + +select cast(cast(-1 as long) as varbinary); +>> X'ffffffffffffffff' + +select cast(cast(1 as tinyint) as varbinary); +>> X'01' + +select cast(cast(1 as smallint) as varbinary); +>> X'0001' + +select cast(cast(1 as int) as varbinary); +>> X'00000001' + +select cast(cast(1 as long) as varbinary); +>> X'0000000000000001' + +select cast(X'ff' as tinyint); +>> -1 + +select cast(X'ffff' as smallint); +>> -1 + +select cast(X'ffffffff' as int); +>> -1 + +select cast(X'ffffffffffffffff' as long); +>> -1 + +select cast(' 011 ' as int); +>> 11 + +select cast(cast(0.1 as real) as decimal(1, 1)); +>> 0.1 + +select cast(cast(95605327.73 as float) as decimal(10, 8)); +> exception VALUE_TOO_LONG_2 + +select cast(cast('01020304-0506-0708-090a-0b0c0d0e0f00' as uuid) as varbinary); +>> X'0102030405060708090a0b0c0d0e0f00' + +call cast('null' as uuid); +> exception DATA_CONVERSION_ERROR_1 + +select cast('12345678123456781234567812345678' as uuid); +>> 12345678-1234-5678-1234-567812345678 + +select cast('000102030405060708090a0b0c0d0e0f' as uuid); +>> 00010203-0405-0607-0809-0a0b0c0d0e0f + +select -cast(0 as double); +>> 0.0 + +SELECT * FROM (SELECT CAST('11:11:11.123456789' AS TIME)); +>> 11:11:11 + +SELECT * FROM (SELECT CAST('11:11:11.123456789' AS TIME(0))); +>> 11:11:11 + +SELECT * FROM (SELECT CAST('11:11:11.123456789' AS TIME(9))); +>> 11:11:11.123456789 + +SELECT * FROM (SELECT CAST('2000-01-01 11:11:11.123456789' AS TIMESTAMP)); +>> 2000-01-01 11:11:11.123457 + +SELECT * FROM (SELECT CAST('2000-01-01 11:11:11.123456789' AS TIMESTAMP(0))); +>> 2000-01-01 11:11:11 + +SELECT * FROM (SELECT CAST('2000-01-01 11:11:11.123456789' AS TIMESTAMP(9))); +>> 2000-01-01 11:11:11.123456789 + +SELECT * FROM (SELECT CAST('2000-01-01 11:11:11.123456789Z' AS TIMESTAMP WITH TIME ZONE)); +>> 2000-01-01 11:11:11.123457+00 + +SELECT * FROM (SELECT CAST('2000-01-01 11:11:11.123456789Z' AS TIMESTAMP(0) WITH TIME ZONE)); +>> 2000-01-01 11:11:11+00 + +SELECT * FROM (SELECT CAST('2000-01-01 11:11:11.123456789Z' AS TIMESTAMP(9) WITH TIME ZONE)); +>> 2000-01-01 11:11:11.123456789+00 + +EXPLAIN SELECT CAST('A' AS VARCHAR(10)), CAST(NULL AS BOOLEAN), CAST(NULL AS VARCHAR), CAST(1 AS INT); +>> SELECT CAST('A' AS CHARACTER VARYING(10)), UNKNOWN, CAST(NULL AS CHARACTER VARYING), 1 + +SELECT CURRENT_TIMESTAMP(9) = CAST(CURRENT_TIME(9) AS TIMESTAMP(9) WITH TIME ZONE); +>> TRUE + +SELECT LOCALTIMESTAMP(9) = CAST(LOCALTIME(9) AS TIMESTAMP(9)); +>> TRUE + +CREATE TABLE TEST(I INTERVAL DAY TO SECOND(9), T TIME(9) WITH TIME ZONE); +> ok + +EXPLAIN SELECT CAST(I AS INTERVAL HOUR(4) TO SECOND), CAST(I AS INTERVAL HOUR(4) TO SECOND(6)), + CAST(I AS INTERVAL HOUR TO SECOND(9)), CAST(I AS INTERVAL HOUR(2) TO SECOND(9)) FROM TEST; +>> SELECT CAST("I" AS INTERVAL HOUR(4) TO SECOND), CAST("I" AS INTERVAL HOUR(4) TO SECOND(6)), CAST("I" AS INTERVAL HOUR TO SECOND(9)), CAST("I" AS INTERVAL HOUR(2) TO SECOND(9)) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT CAST(T AS TIME WITH TIME ZONE), CAST(T AS TIME(0) WITH TIME ZONE), CAST(T AS TIME(3) WITH TIME ZONE) FROM TEST; +>> SELECT CAST("T" AS TIME WITH TIME ZONE), CAST("T" AS TIME(0) WITH TIME ZONE), CAST("T" AS TIME(3) WITH TIME ZONE) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +DROP TABLE TEST; +> ok + +EXPLAIN SELECT + CAST(TIME '10:00:00' AS TIME(9)), + CAST(TIME '10:00:00' AS TIME(9) WITH TIME ZONE), + CAST(TIME '10:00:00' AS TIMESTAMP(9)), + CAST(TIME '10:00:00' AS TIMESTAMP(9) WITH TIME ZONE); +>> SELECT TIME '10:00:00', CAST(TIME '10:00:00' AS TIME(9) WITH TIME ZONE), CAST(TIME '10:00:00' AS TIMESTAMP(9)), CAST(TIME '10:00:00' AS TIMESTAMP(9) WITH TIME ZONE) + +EXPLAIN SELECT + CAST(TIME WITH TIME ZONE '10:00:00+10' AS TIME(9)), + CAST(TIME WITH TIME ZONE '10:00:00+10' AS TIME(9) WITH TIME ZONE), + CAST(TIME WITH TIME ZONE '10:00:00+10' AS TIMESTAMP(9)), + CAST(TIME WITH TIME ZONE '10:00:00+10' AS TIMESTAMP(9) WITH TIME ZONE); +>> SELECT CAST(TIME WITH TIME ZONE '10:00:00+10' AS TIME(9)), TIME WITH TIME ZONE '10:00:00+10', CAST(TIME WITH TIME ZONE '10:00:00+10' AS TIMESTAMP(9)), CAST(TIME WITH TIME ZONE '10:00:00+10' AS TIMESTAMP(9) WITH TIME ZONE) + +EXPLAIN SELECT + CAST(DATE '2000-01-01' AS DATE), + CAST(DATE '2000-01-01' AS TIMESTAMP(9)), + CAST(DATE '2000-01-01' AS TIMESTAMP(9) WITH TIME ZONE); +>> SELECT DATE '2000-01-01', TIMESTAMP '2000-01-01 00:00:00', CAST(DATE '2000-01-01' AS TIMESTAMP(9) WITH TIME ZONE) + +EXPLAIN SELECT + CAST(TIMESTAMP '2000-01-01 10:00:00' AS TIME(9)), + CAST(TIMESTAMP '2000-01-01 10:00:00' AS TIME(9) WITH TIME ZONE), + CAST(TIMESTAMP '2000-01-01 10:00:00' AS DATE), + CAST(TIMESTAMP '2000-01-01 10:00:00' AS TIMESTAMP(9)), + CAST(TIMESTAMP '2000-01-01 10:00:00' AS TIMESTAMP(9) WITH TIME ZONE); +>> SELECT TIME '10:00:00', CAST(TIMESTAMP '2000-01-01 10:00:00' AS TIME(9) WITH TIME ZONE), DATE '2000-01-01', TIMESTAMP '2000-01-01 10:00:00', CAST(TIMESTAMP '2000-01-01 10:00:00' AS TIMESTAMP(9) WITH TIME ZONE) + +EXPLAIN SELECT + CAST(TIMESTAMP WITH TIME ZONE '2000-01-01 10:00:00+10' AS TIME(9)), + CAST(TIMESTAMP WITH TIME ZONE '2000-01-01 10:00:00+10' AS TIME(9) WITH TIME ZONE), + CAST(TIMESTAMP WITH TIME ZONE '2000-01-01 10:00:00+10' AS DATE), + CAST(TIMESTAMP WITH TIME ZONE '2000-01-01 10:00:00+10' AS TIMESTAMP(9)), + CAST(TIMESTAMP WITH TIME ZONE '2000-01-01 10:00:00+10' AS TIMESTAMP(9) WITH TIME ZONE); +>> SELECT CAST(TIMESTAMP WITH TIME ZONE '2000-01-01 10:00:00+10' AS TIME(9)), TIME WITH TIME ZONE '10:00:00+10', CAST(TIMESTAMP WITH TIME ZONE '2000-01-01 10:00:00+10' AS DATE), CAST(TIMESTAMP WITH TIME ZONE '2000-01-01 10:00:00+10' AS TIMESTAMP(9)), TIMESTAMP WITH TIME ZONE '2000-01-01 10:00:00+10' + +CREATE DOMAIN D INT CHECK (VALUE > 10); +> ok + +VALUES CAST(11 AS D); +>> 11 + +VALUES CAST(10 AS D); +> exception CHECK_CONSTRAINT_VIOLATED_1 + +EXPLAIN SELECT CAST(X AS D) FROM SYSTEM_RANGE(20, 30); +>> SELECT CAST("X" AS "PUBLIC"."D") FROM SYSTEM_RANGE(20, 30) /* range index */ + +DROP DOMAIN D; +> ok + +EXPLAIN VALUES CAST('a' AS VARCHAR_IGNORECASE(10)); +>> VALUES (CAST('a' AS VARCHAR_IGNORECASE(10))) + +SELECT CAST('true ' AS BOOLEAN) V, CAST(CAST('true' AS CHAR(10)) AS BOOLEAN) F; +> V F +> ---- ---- +> TRUE TRUE +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/system/coalesce.sql b/h2/src/test/org/h2/test/scripts/functions/system/coalesce.sql new file mode 100644 index 0000000..c5fabf1 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/coalesce.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +select coalesce(null, null) xn, coalesce(null, 'a') xa, coalesce('1', '2') x1; +> XN XA X1 +> ---- -- -- +> null a 1 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/system/convert.sql b/h2/src/test/org/h2/test/scripts/functions/system/convert.sql new file mode 100644 index 0000000..da1a5fa --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/convert.sql @@ -0,0 +1,10 @@ +-- 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 +-- + +select convert(null, varchar(255)) xn, convert(' 10', int) x10, convert(' 20 ', int) x20; +> XN X10 X20 +> ---- --- --- +> null 10 20 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/system/csvread.sql b/h2/src/test/org/h2/test/scripts/functions/system/csvread.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/csvread.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/system/csvwrite.sql b/h2/src/test/org/h2/test/scripts/functions/system/csvwrite.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/csvwrite.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/system/current_catalog.sql b/h2/src/test/org/h2/test/scripts/functions/system/current_catalog.sql new file mode 100644 index 0000000..fbbce1f --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/current_catalog.sql @@ -0,0 +1,37 @@ +-- 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 +-- + +CALL CURRENT_CATALOG; +>> SCRIPT + +CALL DATABASE(); +>> SCRIPT + +SET CATALOG SCRIPT; +> ok + +SET CATALOG 'SCRIPT'; +> ok + +SET CATALOG 'SCR' || 'IPT'; +> ok + +SET CATALOG UNKNOWN_CATALOG; +> exception DATABASE_NOT_FOUND_1 + +SET CATALOG NULL; +> exception DATABASE_NOT_FOUND_1 + +CALL CURRENT_DATABASE(); +> exception FUNCTION_NOT_FOUND_1 + +SET MODE PostgreSQL; +> ok + +CALL CURRENT_DATABASE(); +>> SCRIPT + +SET MODE Regular; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/system/current_schema.sql b/h2/src/test/org/h2/test/scripts/functions/system/current_schema.sql new file mode 100644 index 0000000..d2f21bf --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/current_schema.sql @@ -0,0 +1,40 @@ +-- 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 +-- + +SELECT CURRENT_SCHEMA, SCHEMA(); +> CURRENT_SCHEMA CURRENT_SCHEMA +> -------------- -------------- +> PUBLIC PUBLIC +> rows: 1 + +CREATE SCHEMA S1; +> ok + +SET SCHEMA S1; +> ok + +CALL CURRENT_SCHEMA; +>> S1 + +SET SCHEMA 'PUBLIC'; +> ok + +CALL CURRENT_SCHEMA; +>> PUBLIC + +SET SCHEMA 'S' || 1; +> ok + +CALL CURRENT_SCHEMA; +>> S1 + +SET SCHEMA PUBLIC; +> ok + +SET SCHEMA NULL; +> exception SCHEMA_NOT_FOUND_1 + +DROP SCHEMA S1; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/system/current_user.sql b/h2/src/test/org/h2/test/scripts/functions/system/current_user.sql new file mode 100644 index 0000000..2881250 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/current_user.sql @@ -0,0 +1,25 @@ +-- 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 +-- + +select user() x_sa, current_user() x_sa2; +> X_SA X_SA2 +> ---- ----- +> SA SA +> rows: 1 + +SELECT CURRENT_USER; +>> SA + +SELECT SESSION_USER; +>> SA + +SELECT SYSTEM_USER; +>> SA + +SELECT CURRENT_ROLE; +>> PUBLIC + +EXPLAIN SELECT CURRENT_USER, SESSION_USER, SYSTEM_USER, USER, CURRENT_ROLE; +>> SELECT CURRENT_USER, SESSION_USER, SYSTEM_USER, CURRENT_USER, CURRENT_ROLE diff --git a/h2/src/test/org/h2/test/scripts/functions/system/currval.sql b/h2/src/test/org/h2/test/scripts/functions/system/currval.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/currval.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/system/data_type_sql.sql b/h2/src/test/org/h2/test/scripts/functions/system/data_type_sql.sql new file mode 100644 index 0000000..0f24fa4 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/data_type_sql.sql @@ -0,0 +1,121 @@ +-- 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 +-- +CREATE CONSTANT C VALUE 12; +> ok + +CREATE DOMAIN D AS CHAR(3); +> ok + +CREATE TABLE T (C VARCHAR(10)); +> ok + +CREATE ALIAS R FOR "java.lang.Math.max(long,long)"; +> ok + +SELECT ID, DATA_TYPE_SQL('PUBLIC', 'C', 'CONSTANT', ID) FROM (VALUES NULL, 'TYPE', 'X') T(ID); +> ID DATA_TYPE_SQL('PUBLIC', 'C', 'CONSTANT', ID) +> ---- -------------------------------------------- +> TYPE INTEGER +> X null +> null null +> rows: 3 + +SELECT ID, DATA_TYPE_SQL('PUBLIC', 'D', 'DOMAIN', ID) FROM (VALUES NULL, 'TYPE', 'X') T(ID); +> ID DATA_TYPE_SQL('PUBLIC', 'D', 'DOMAIN', ID) +> ---- ------------------------------------------ +> TYPE CHARACTER(3) +> X null +> null null +> rows: 3 + +SELECT ID, DATA_TYPE_SQL('PUBLIC', 'T', 'TABLE', ID) FROM (VALUES NULL, '0', '1', '2', 'X') T(ID); +> ID DATA_TYPE_SQL('PUBLIC', 'T', 'TABLE', ID) +> ---- ----------------------------------------- +> 0 null +> 1 CHARACTER VARYING(10) +> 2 null +> X null +> null null +> rows: 5 + +SELECT ID, DATA_TYPE_SQL('PUBLIC', 'R_1', 'ROUTINE', ID) FROM (VALUES NULL, 'RESULT', '0', '1', '2', '3', 'X') T(ID); +> ID DATA_TYPE_SQL('PUBLIC', 'R_1', 'ROUTINE', ID) +> ------ --------------------------------------------- +> 0 null +> 1 BIGINT +> 2 BIGINT +> 3 null +> RESULT BIGINT +> X null +> null null +> rows: 7 + +SELECT DATA_TYPE_SQL(S, O, T, I) FROM (VALUES + (NULL, 'C', 'CONSTANT', 'TYPE'), + ('X', 'C', 'CONSTANT', 'TYPE'), + ('PUBLIC', NULL, 'CONSTANT', 'TYPE'), + ('PUBLIC', 'X', 'CONSTANT', 'TYPE'), + ('PUBLIC', 'C', NULL, 'TYPE'), + (NULL, 'D', 'DOMAIN', 'TYPE'), + ('X', 'D', 'DOMAIN', 'TYPE'), + ('PUBLIC', NULL, 'DOMAIN', 'TYPE'), + ('PUBLIC', 'X', 'DOMAIN', 'TYPE'), + ('PUBLIC', 'D', NULL, 'TYPE'), + (NULL, 'T', 'TABLE', '1'), + ('X', 'T', 'TABLE', '1'), + ('PUBLIC', NULL, 'TABLE', '1'), + ('PUBLIC', 'X', 'TABLE', '1'), + ('PUBLIC', 'T', NULL, '1'), + (NULL, 'R_1', 'ROUTINE', '1'), + ('X', 'R_1', 'ROUTINE', '1'), + ('PUBLIC', NULL, 'ROUTINE', '1'), + ('PUBLIC', 'R_0', 'ROUTINE', '1'), + ('PUBLIC', 'R_2', 'ROUTINE', '1'), + ('PUBLIC', 'R_Z', 'ROUTINE', '1'), + ('PUBLIC', 'X', 'ROUTINE', '1'), + ('PUBLIC', 'X_1', 'ROUTINE', '1'), + ('PUBLIC', 'R_1', NULL, '1'), + ('PUBLIC', 'T', 'X', '1') + ) T(S, O, T, I); +> DATA_TYPE_SQL(S, O, T, I) +> ------------------------- +> null +> null +> null +> null +> null +> null +> null +> null +> null +> null +> null +> null +> null +> null +> null +> null +> null +> null +> null +> null +> null +> null +> null +> null +> null +> rows: 25 + +DROP CONSTANT C; +> ok + +DROP DOMAIN D; +> ok + +DROP TABLE T; +> ok + +DROP ALIAS R; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/system/database-path.sql b/h2/src/test/org/h2/test/scripts/functions/system/database-path.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/database-path.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/system/db_object.sql b/h2/src/test/org/h2/test/scripts/functions/system/db_object.sql new file mode 100644 index 0000000..d44d0fa --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/db_object.sql @@ -0,0 +1,284 @@ +-- 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 +-- + +CREATE ROLE A; +> ok + +CREATE ROLE B; +> ok + +SELECT ID_A <> ID_B, SQL_A, SQL_B FROM (VALUES ( + DB_OBJECT_ID('ROLE', 'A'), + DB_OBJECT_ID('ROLE', 'B'), + DB_OBJECT_SQL('ROLE', 'A'), + DB_OBJECT_SQL('ROLE', 'B') +)) T(ID_A, ID_B, SQL_A, SQL_B); +> ID_A <> ID_B SQL_A SQL_B +> ------------ --------------- --------------- +> TRUE CREATE ROLE "A" CREATE ROLE "B" +> rows: 1 + +DROP ROLE A; +> ok + +DROP ROLE B; +> ok + +CALL DB_OBJECT_ID('SETTING', 'CREATE_BUILD') IS NOT NULL; +>> TRUE + +CALL DB_OBJECT_SQL('SETTING', 'CREATE_BUILD') IS NOT NULL; +>> TRUE + +CREATE SCHEMA A; +> ok + +CREATE SCHEMA B; +> ok + +SELECT ID_A <> ID_B, SQL_A, SQL_B FROM (VALUES ( + DB_OBJECT_ID('SCHEMA', 'A'), + DB_OBJECT_ID('SCHEMA', 'B'), + DB_OBJECT_SQL('SCHEMA', 'A'), + DB_OBJECT_SQL('SCHEMA', 'B') +)) T(ID_A, ID_B, SQL_A, SQL_B); +> ID_A <> ID_B SQL_A SQL_B +> ------------ -------------------------------------------------- -------------------------------------------------- +> TRUE CREATE SCHEMA IF NOT EXISTS "A" AUTHORIZATION "SA" CREATE SCHEMA IF NOT EXISTS "B" AUTHORIZATION "SA" +> rows: 1 + +DROP SCHEMA A; +> ok + +DROP SCHEMA B; +> ok + +CREATE USER A SALT X'00' HASH X'00'; +> ok + +CREATE USER B SALT X'00' HASH X'00'; +> ok + +SELECT ID_A <> ID_B, SQL_A, SQL_B FROM (VALUES ( + DB_OBJECT_ID('USER', 'A'), + DB_OBJECT_ID('USER', 'B'), + DB_OBJECT_SQL('USER', 'A'), + DB_OBJECT_SQL('USER', 'B') +)) T(ID_A, ID_B, SQL_A, SQL_B); +> ID_A <> ID_B SQL_A SQL_B +> ------------ ------------------------------------------------- ------------------------------------------------- +> TRUE CREATE USER IF NOT EXISTS "A" SALT '00' HASH '00' CREATE USER IF NOT EXISTS "B" SALT '00' HASH '00' +> rows: 1 + +DROP USER A; +> ok + +DROP USER B; +> ok + +CREATE CONSTANT A VALUE 1; +> ok + +CREATE CONSTANT B VALUE 2; +> ok + +SELECT ID_A <> ID_B, SQL_A, SQL_B FROM (VALUES ( + DB_OBJECT_ID('CONSTANT', 'PUBLIC', 'A'), + DB_OBJECT_ID('CONSTANT', 'PUBLIC', 'B'), + DB_OBJECT_SQL('CONSTANT', 'PUBLIC', 'A'), + DB_OBJECT_SQL('CONSTANT', 'PUBLIC', 'B') +)) T(ID_A, ID_B, SQL_A, SQL_B); +> ID_A <> ID_B SQL_A SQL_B +> ------------ ------------------------------------ ------------------------------------ +> TRUE CREATE CONSTANT "PUBLIC"."A" VALUE 1 CREATE CONSTANT "PUBLIC"."B" VALUE 2 +> rows: 1 + +DROP CONSTANT A; +> ok + +DROP CONSTANT B; +> ok + +CREATE DOMAIN A AS CHAR; +> ok + +CREATE DOMAIN B AS CHAR; +> ok + +SELECT ID_A <> ID_B, SQL_A, SQL_B FROM (VALUES ( + DB_OBJECT_ID('DOMAIN', 'PUBLIC', 'A'), + DB_OBJECT_ID('DOMAIN', 'PUBLIC', 'B'), + DB_OBJECT_SQL('DOMAIN', 'PUBLIC', 'A'), + DB_OBJECT_SQL('DOMAIN', 'PUBLIC', 'B') +)) T(ID_A, ID_B, SQL_A, SQL_B); +> ID_A <> ID_B SQL_A SQL_B +> ------------ --------------------------------------- --------------------------------------- +> TRUE CREATE DOMAIN "PUBLIC"."A" AS CHARACTER CREATE DOMAIN "PUBLIC"."B" AS CHARACTER +> rows: 1 + +DROP DOMAIN A; +> ok + +DROP DOMAIN B; +> ok + +CREATE ALIAS A FOR 'java.lang.Math.sqrt'; +> ok + +CREATE AGGREGATE B FOR 'org.h2.test.scripts.Aggregate1'; +> ok + +SELECT ID_A <> ID_B, SQL_A, SQL_B FROM (VALUES ( + DB_OBJECT_ID('ROUTINE', 'PUBLIC', 'A'), + DB_OBJECT_ID('ROUTINE', 'PUBLIC', 'B'), + DB_OBJECT_SQL('ROUTINE', 'PUBLIC', 'A'), + DB_OBJECT_SQL('ROUTINE', 'PUBLIC', 'B') +)) T(ID_A, ID_B, SQL_A, SQL_B); +> ID_A <> ID_B SQL_A SQL_B +> ------------ --------------------------------------------------------- ------------------------------------------------------------------------ +> TRUE CREATE FORCE ALIAS "PUBLIC"."A" FOR 'java.lang.Math.sqrt' CREATE FORCE AGGREGATE "PUBLIC"."B" FOR 'org.h2.test.scripts.Aggregate1' +> rows: 1 + +DROP ALIAS A; +> ok + +DROP AGGREGATE B; +> ok + +CREATE SEQUENCE A; +> ok + +CREATE SEQUENCE B; +> ok + +SELECT ID_A <> ID_B, SQL_A, SQL_B FROM (VALUES ( + DB_OBJECT_ID('SEQUENCE', 'PUBLIC', 'A'), + DB_OBJECT_ID('SEQUENCE', 'PUBLIC', 'B'), + DB_OBJECT_SQL('SEQUENCE', 'PUBLIC', 'A'), + DB_OBJECT_SQL('SEQUENCE', 'PUBLIC', 'B') +)) T(ID_A, ID_B, SQL_A, SQL_B); +> ID_A <> ID_B SQL_A SQL_B +> ------------ ----------------------------------------- ----------------------------------------- +> TRUE CREATE SEQUENCE "PUBLIC"."A" START WITH 1 CREATE SEQUENCE "PUBLIC"."B" START WITH 1 +> rows: 1 + +DROP SEQUENCE A; +> ok + +DROP SEQUENCE B; +> ok + +CREATE MEMORY TABLE T_A(ID INT); +> ok + +CREATE UNIQUE INDEX I_A ON T_A(ID); +> ok + +ALTER TABLE T_A ADD CONSTRAINT C_A UNIQUE(ID); +> ok + +CREATE SYNONYM S_A FOR T_A; +> ok + +CREATE TRIGGER G_A BEFORE INSERT ON T_A FOR EACH ROW CALL 'org.h2.test.scripts.Trigger1'; +> ok + +CREATE MEMORY TABLE T_B(ID INT); +> ok + +CREATE UNIQUE INDEX I_B ON T_B(ID); +> ok + +ALTER TABLE T_B ADD CONSTRAINT C_B UNIQUE(ID); +> ok + +CREATE SYNONYM S_B FOR T_B; +> ok + +CREATE TRIGGER G_B BEFORE INSERT ON T_B FOR EACH ROW CALL 'org.h2.test.scripts.Trigger1'; +> ok + +SELECT T, ID_A <> ID_B, SQL_A, SQL_B FROM (VALUES +( + 'CONSTRAINT', + DB_OBJECT_ID('CONSTRAINT', 'PUBLIC', 'C_A'), + DB_OBJECT_ID('CONSTRAINT', 'PUBLIC', 'C_B'), + DB_OBJECT_SQL('CONSTRAINT', 'PUBLIC', 'C_A'), + DB_OBJECT_SQL('CONSTRAINT', 'PUBLIC', 'C_B') +), ( + 'INDEX', + DB_OBJECT_ID('INDEX', 'PUBLIC', 'I_A'), + DB_OBJECT_ID('INDEX', 'PUBLIC', 'I_B'), + DB_OBJECT_SQL('INDEX', 'PUBLIC', 'I_A'), + DB_OBJECT_SQL('INDEX', 'PUBLIC', 'I_B') +), ( + 'SYNONYM', + DB_OBJECT_ID('SYNONYM', 'PUBLIC', 'S_A'), + DB_OBJECT_ID('SYNONYM', 'PUBLIC', 'S_B'), + DB_OBJECT_SQL('SYNONYM', 'PUBLIC', 'S_A'), + DB_OBJECT_SQL('SYNONYM', 'PUBLIC', 'S_B') +), ( + 'TABLE', + DB_OBJECT_ID('TABLE', 'PUBLIC', 'T_A'), + DB_OBJECT_ID('TABLE', 'PUBLIC', 'T_B'), + DB_OBJECT_SQL('TABLE', 'PUBLIC', 'T_A'), + DB_OBJECT_SQL('TABLE', 'PUBLIC', 'T_B') +), ( + 'TRIGGER', + DB_OBJECT_ID('TRIGGER', 'PUBLIC', 'G_A'), + DB_OBJECT_ID('TRIGGER', 'PUBLIC', 'G_B'), + DB_OBJECT_SQL('TRIGGER', 'PUBLIC', 'G_A'), + DB_OBJECT_SQL('TRIGGER', 'PUBLIC', 'G_B') +)) T(T, ID_A, ID_B, SQL_A, SQL_B); +> T ID_A <> ID_B SQL_A SQL_B +> ---------- ------------ ------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------- +> CONSTRAINT TRUE ALTER TABLE "PUBLIC"."T_A" ADD CONSTRAINT "PUBLIC"."C_A" UNIQUE("ID") ALTER TABLE "PUBLIC"."T_B" ADD CONSTRAINT "PUBLIC"."C_B" UNIQUE("ID") +> INDEX TRUE CREATE UNIQUE INDEX "PUBLIC"."I_A" ON "PUBLIC"."T_A"("ID" NULLS FIRST) CREATE UNIQUE INDEX "PUBLIC"."I_B" ON "PUBLIC"."T_B"("ID" NULLS FIRST) +> SYNONYM TRUE CREATE SYNONYM "PUBLIC"."S_A" FOR "PUBLIC"."T_A" CREATE SYNONYM "PUBLIC"."S_B" FOR "PUBLIC"."T_B" +> TABLE TRUE CREATE MEMORY TABLE "PUBLIC"."T_A"( "ID" INTEGER ) CREATE MEMORY TABLE "PUBLIC"."T_B"( "ID" INTEGER ) +> TRIGGER TRUE CREATE FORCE TRIGGER "PUBLIC"."G_A" BEFORE INSERT ON "PUBLIC"."T_A" FOR EACH ROW QUEUE 1024 CALL 'org.h2.test.scripts.Trigger1' CREATE FORCE TRIGGER "PUBLIC"."G_B" BEFORE INSERT ON "PUBLIC"."T_B" FOR EACH ROW QUEUE 1024 CALL 'org.h2.test.scripts.Trigger1' +> rows: 5 + +DROP SYNONYM S_A; +> ok + +DROP SYNONYM S_B; +> ok + +DROP TABLE T_B, T_A; +> ok + +CALL DB_OBJECT_ID(NULL, NULL); +>> null + +CALL DB_OBJECT_ID(NULL, NULL, NULL); +>> null + +CALL DB_OBJECT_ID('UNKNOWN', NULL); +>> null + +CALL DB_OBJECT_ID('UNKNOWN', 'UNKNOWN'); +>> null + +CALL DB_OBJECT_ID('UNKNOWN', 'PUBLIC', 'UNKNOWN'); +>> null + +CALL DB_OBJECT_ID('UNKNOWN', 'UNKNOWN', 'UNKNOWN'); +>> null + +CALL DB_OBJECT_ID('TABLE', 'UNKNOWN', 'UNKNOWN'); +>> null + +CALL DB_OBJECT_ID('TABLE', 'PUBLIC', 'UNKNOWN'); +>> null + +CALL DB_OBJECT_ID('TABLE', 'PUBLIC', NULL); +>> null + +CALL DB_OBJECT_ID('TABLE', 'INFORMATION_SCHEMA', 'TABLES') IS NOT NULL; +>> TRUE + +CALL DB_OBJECT_SQL('TABLE', 'INFORMATION_SCHEMA', 'TABLES'); +>> null diff --git a/h2/src/test/org/h2/test/scripts/functions/system/decode.sql b/h2/src/test/org/h2/test/scripts/functions/system/decode.sql new file mode 100644 index 0000000..7c7c3ec --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/decode.sql @@ -0,0 +1,31 @@ +-- 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 +-- + +select select decode(null, null, 'a'); +>> a + +select select decode(1, 1, 'a'); +>> a + +select select decode(1, 2, 'a'); +>> null + +select select decode(1, 1, 'a', 'else'); +>> a + +select select decode(1, 2, 'a', 'else'); +>> else + +select decode(4.0, 2.0, 2.0, 3.0, 3.0); +>> null + +select decode('3', 2.0, 2.0, 3, 3.0); +>> 3.0 + +select decode(4.0, 2.0, 2.0, 3.0, 3.0, 4.0, 4.0, 9.0); +>> 4.0 + +select decode(1, 1, '1', 1, '11') from dual; +>> 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/system/disk-space-used.sql b/h2/src/test/org/h2/test/scripts/functions/system/disk-space-used.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/disk-space-used.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/system/file-read.sql b/h2/src/test/org/h2/test/scripts/functions/system/file-read.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/file-read.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/system/file-write.sql b/h2/src/test/org/h2/test/scripts/functions/system/file-write.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/file-write.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/system/greatest.sql b/h2/src/test/org/h2/test/scripts/functions/system/greatest.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/greatest.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/system/h2version.sql b/h2/src/test/org/h2/test/scripts/functions/system/h2version.sql new file mode 100644 index 0000000..ff8a311 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/h2version.sql @@ -0,0 +1,7 @@ +-- 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 +-- + +EXPLAIN VALUES H2VERSION(); +>> VALUES (H2VERSION()) diff --git a/h2/src/test/org/h2/test/scripts/functions/system/identity.sql b/h2/src/test/org/h2/test/scripts/functions/system/identity.sql new file mode 100644 index 0000000..4d692e6 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/identity.sql @@ -0,0 +1,34 @@ +-- 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 +-- + +CREATE TABLE TEST(ID BIGINT GENERATED BY DEFAULT AS IDENTITY, V INT); +> ok + +INSERT INTO TEST(V) VALUES 10; +> update count: 1 + +VALUES IDENTITY(); +> exception FUNCTION_NOT_FOUND_1 + +VALUES SCOPE_IDENTITY(); +> exception FUNCTION_NOT_FOUND_1 + +SET MODE LEGACY; +> ok + +INSERT INTO TEST(V) VALUES 20; +> update count: 1 + +VALUES IDENTITY(); +>> 2 + +VALUES SCOPE_IDENTITY(); +>> 2 + +SET MODE REGULAR; +> ok + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/system/ifnull.sql b/h2/src/test/org/h2/test/scripts/functions/system/ifnull.sql new file mode 100644 index 0000000..5aa7665 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/ifnull.sql @@ -0,0 +1,37 @@ +-- 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 +-- + +select ifnull(null, '1') x1, ifnull(null, null) xn, ifnull('a', 'b') xa; +> X1 XN XA +> -- ---- -- +> 1 null a +> rows: 1 + +SELECT ISNULL(NULL, '1'); +> exception FUNCTION_NOT_FOUND_1 + +SET MODE MSSQLServer; +> ok + +select isnull(null, '1') x1, isnull(null, null) xn, isnull('a', 'b') xa; +> X1 XN XA +> -- ---- -- +> 1 null a +> rows: 1 + +SET MODE Regular; +> ok + +CREATE MEMORY TABLE S(D DOUBLE) AS VALUES NULL; +> ok + +CREATE MEMORY TABLE T AS SELECT IFNULL(D, D) FROM S; +> ok + +SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'T'; +>> DOUBLE PRECISION + +DROP TABLE S, T; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/system/last-insert-id.sql b/h2/src/test/org/h2/test/scripts/functions/system/last-insert-id.sql new file mode 100644 index 0000000..b51d5cf --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/last-insert-id.sql @@ -0,0 +1,43 @@ +-- 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 +-- +SET MODE MySQL; +> ok + +create memory table sequence (id INT NOT NULL AUTO_INCREMENT, title varchar(255)); +> ok + +INSERT INTO sequence (title) VALUES ('test'); +> update count: 1 + +INSERT INTO sequence (title) VALUES ('test1'); +> update count: 1 + +SELECT LAST_INSERT_ID() AS L; +>> 2 + +SELECT LAST_INSERT_ID(100) AS L; +>> 100 + +SELECT LAST_INSERT_ID() AS L; +>> 100 + +INSERT INTO sequence (title) VALUES ('test2'); +> update count: 1 + +SELECT MAX(id) AS M FROM sequence; +>> 3 + +SELECT LAST_INSERT_ID() AS L; +>> 3 + +SELECT LAST_INSERT_ID(NULL) AS L; +>> null + +SELECT LAST_INSERT_ID() AS L; +>> 0 + + +DROP TABLE sequence; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/system/least.sql b/h2/src/test/org/h2/test/scripts/functions/system/least.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/least.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/system/link-schema.sql b/h2/src/test/org/h2/test/scripts/functions/system/link-schema.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/link-schema.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/system/lock-mode.sql b/h2/src/test/org/h2/test/scripts/functions/system/lock-mode.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/lock-mode.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/system/lock-timeout.sql b/h2/src/test/org/h2/test/scripts/functions/system/lock-timeout.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/lock-timeout.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/system/memory-free.sql b/h2/src/test/org/h2/test/scripts/functions/system/memory-free.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/memory-free.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/system/memory-used.sql b/h2/src/test/org/h2/test/scripts/functions/system/memory-used.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/memory-used.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/system/nextval.sql b/h2/src/test/org/h2/test/scripts/functions/system/nextval.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/nextval.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/system/nullif.sql b/h2/src/test/org/h2/test/scripts/functions/system/nullif.sql new file mode 100644 index 0000000..6042a0b --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/nullif.sql @@ -0,0 +1,27 @@ +-- 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 +-- + +select nullif(null, null) xn, nullif('a', 'a') xn, nullif('1', '2') x1; +> XN XN X1 +> ---- ---- -- +> null null 1 +> rows: 1 + +SELECT + A = B, + NULLIF(A, B), CASE WHEN A = B THEN NULL ELSE A END + FROM (VALUES + (1, (1, NULL), (1, NULL)), + (2, (1, NULL), (2, NULL)), + (3, (2, NULL), (1, NULL)), + (4, (1, 1), (1, 2)) + ) T(N, A, B) ORDER BY N; +> A = B NULLIF(A, B) CASE WHEN A = B THEN NULL ELSE A END +> ----- ------------- ------------------------------------ +> null ROW (1, null) ROW (1, null) +> FALSE ROW (1, null) ROW (1, null) +> FALSE ROW (2, null) ROW (2, null) +> FALSE ROW (1, 1) ROW (1, 1) +> rows (ordered): 4 diff --git a/h2/src/test/org/h2/test/scripts/functions/system/nvl2.sql b/h2/src/test/org/h2/test/scripts/functions/system/nvl2.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/nvl2.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/system/readonly.sql b/h2/src/test/org/h2/test/scripts/functions/system/readonly.sql new file mode 100644 index 0000000..14d9568 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/readonly.sql @@ -0,0 +1,7 @@ +-- 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 +-- + +select readonly(); +>> FALSE 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 new file mode 100644 index 0000000..44f26f9 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/rownum.sql @@ -0,0 +1,33 @@ +-- 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 +-- + +----- Issue#600 ----- +create table test as (select char(x) as str from system_range(48,90)); +> ok + +select rownum() as rnum, str from test where str = 'A'; +> RNUM STR +> ---- --- +> 1 A +> rows: 1 + +----- Issue#3353 ----- +SELECT str FROM FINAL TABLE (UPDATE test SET str = char(rownum + 48) WHERE str = '0'); +> STR +> --- +> 1 +> rows: 1 + +drop table test; +> ok + +SELECT * FROM (VALUES 1, 2) AS T1(X), (VALUES 1, 2) AS T2(X) WHERE ROWNUM = 1; +> X X +> - - +> 1 1 +> rows: 1 + +SELECT 1 ORDER BY ROWNUM; +>> 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/system/session-id.sql b/h2/src/test/org/h2/test/scripts/functions/system/session-id.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/session-id.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/system/table.sql b/h2/src/test/org/h2/test/scripts/functions/system/table.sql new file mode 100644 index 0000000..4df052a --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/table.sql @@ -0,0 +1,65 @@ +-- 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 +-- + +select * from table(a int=(1)), table(b int=2), table(c int=row(3)); +> A B C +> - - - +> 1 2 3 +> rows: 1 + +create table test as select * from table(id int=(1, 2, 3)); +> ok + +SELECT * FROM (SELECT * FROM TEST) ORDER BY id; +> ID +> -- +> 1 +> 2 +> 3 +> rows (ordered): 3 + +SELECT * FROM (SELECT * FROM TEST) x ORDER BY id; +> ID +> -- +> 1 +> 2 +> 3 +> rows (ordered): 3 + +drop table test; +> ok + +select * from table(id int = (1)); +> ID +> -- +> 1 +> rows: 1 + +-- compatibility syntax +call table(id int = (1)); +> ID +> -- +> 1 +> rows: 1 + +explain select * from table(id int = (1, 2), name varchar=('Hello', 'World')); +>> SELECT "TABLE"."ID", "TABLE"."NAME" FROM TABLE("ID" INTEGER=ROW (1, 2), "NAME" CHARACTER VARYING=ROW ('Hello', 'World')) /* function */ + +explain select * from table(id int = ARRAY[1, 2], name varchar=ARRAY['Hello', 'World']); +>> SELECT "TABLE"."ID", "TABLE"."NAME" FROM TABLE("ID" INTEGER=ARRAY [1, 2], "NAME" CHARACTER VARYING=ARRAY ['Hello', 'World']) /* function */ + +select * from table(id int=(1, 2), name varchar=('Hello', 'World')) x order by id; +> ID NAME +> -- ----- +> 1 Hello +> 2 World +> rows (ordered): 2 + +SELECT * FROM (TABLE(ID INT = (1, 2))); +> ID +> -- +> 1 +> 2 +> rows: 2 diff --git a/h2/src/test/org/h2/test/scripts/functions/system/transaction-id.sql b/h2/src/test/org/h2/test/scripts/functions/system/transaction-id.sql new file mode 100644 index 0000000..e04598e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/transaction-id.sql @@ -0,0 +1,4 @@ +-- 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 +-- diff --git a/h2/src/test/org/h2/test/scripts/functions/system/trim_array.sql b/h2/src/test/org/h2/test/scripts/functions/system/trim_array.sql new file mode 100644 index 0000000..ba5c743 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/trim_array.sql @@ -0,0 +1,28 @@ +-- 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 +-- + +SELECT TRIM_ARRAY(ARRAY[1, 2], -1); +> exception ARRAY_ELEMENT_ERROR_2 + +SELECT TRIM_ARRAY(ARRAY[1, 2], 0); +>> [1, 2] + +SELECT TRIM_ARRAY(ARRAY[1, 2], 1); +>> [1] + +SELECT TRIM_ARRAY(ARRAY[1, 2], 2); +>> [] + +SELECT TRIM_ARRAY(ARRAY[1, 2], 3); +> exception ARRAY_ELEMENT_ERROR_2 + +SELECT TRIM_ARRAY(NULL, 1); +>> null + +SELECT TRIM_ARRAY(NULL, -1); +> exception ARRAY_ELEMENT_ERROR_2 + +SELECT TRIM_ARRAY(ARRAY[1], NULL); +>> null diff --git a/h2/src/test/org/h2/test/scripts/functions/system/truncate-value.sql b/h2/src/test/org/h2/test/scripts/functions/system/truncate-value.sql new file mode 100644 index 0000000..5bca7ee --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/truncate-value.sql @@ -0,0 +1,19 @@ +-- 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 +-- + +CALL TRUNCATE_VALUE('Test 123', 4, FALSE); +>> Test + +CALL TRUNCATE_VALUE(1234567890.123456789, 4, FALSE); +>> 1235000000 + +CALL TRUNCATE_VALUE(1234567890.123456789, 4, TRUE); +>> 1235000000 + +CALL TRUNCATE_VALUE(CAST(1234567890.123456789 AS DOUBLE PRECISION), 4, FALSE); +>> 1.2345678901234567E9 + +CALL TRUNCATE_VALUE(CAST(1234567890.123456789 AS DOUBLE PRECISION), 4, TRUE); +>> 1.235E9 diff --git a/h2/src/test/org/h2/test/scripts/functions/system/unnest.sql b/h2/src/test/org/h2/test/scripts/functions/system/unnest.sql new file mode 100644 index 0000000..a5a52b0 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/system/unnest.sql @@ -0,0 +1,67 @@ +-- 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 +-- + +SELECT * FROM UNNEST(); +> exception INVALID_PARAMETER_COUNT_2 + +SELECT * FROM UNNEST(ARRAY[]); +> C1 +> -- +> rows: 0 + +SELECT * FROM UNNEST(ARRAY[1, 2, 3]); +> C1 +> -- +> 1 +> 2 +> 3 +> rows: 3 + +-- compatibility syntax +CALL UNNEST(ARRAY[1, 2, 3]); +> C1 +> -- +> 1 +> 2 +> 3 +> rows: 3 + +SELECT * FROM UNNEST(ARRAY[1], ARRAY[2, 3, 4], ARRAY[5, 6]); +> C1 C2 C3 +> ---- -- ---- +> 1 2 5 +> null 3 6 +> null 4 null +> rows: 3 + +SELECT * FROM UNNEST(ARRAY[1], ARRAY[2, 3, 4], ARRAY[5, 6]) WITH ORDINALITY; +> C1 C2 C3 NORD +> ---- -- ---- ---- +> 1 2 5 1 +> null 3 6 2 +> null 4 null 3 +> rows: 3 + +EXPLAIN SELECT * FROM UNNEST(ARRAY[1]); +>> SELECT "UNNEST"."C1" FROM UNNEST(ARRAY [1]) /* function */ + +EXPLAIN SELECT * FROM UNNEST(ARRAY[1]) WITH ORDINALITY; +>> SELECT "UNNEST"."C1", "UNNEST"."NORD" FROM UNNEST(ARRAY [1]) WITH ORDINALITY /* function */ + +SELECT 1 IN(SELECT * FROM UNNEST(ARRAY[1, 2, 3])); +>> TRUE + +SELECT 4 IN(SELECT * FROM UNNEST(ARRAY[1, 2, 3])); +>> FALSE + +SELECT X, X IN(SELECT * FROM UNNEST(ARRAY[2, 4])) FROM SYSTEM_RANGE(1, 5); +> X X IN( SELECT DISTINCT UNNEST.C1 FROM UNNEST(ARRAY [2, 4])) +> - ---------------------------------------------------------- +> 1 FALSE +> 2 TRUE +> 3 FALSE +> 4 TRUE +> 5 FALSE +> rows: 5 diff --git a/h2/src/test/org/h2/test/scripts/functions/timeanddate/current-time.sql b/h2/src/test/org/h2/test/scripts/functions/timeanddate/current-time.sql new file mode 100644 index 0000000..1d558ba --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/timeanddate/current-time.sql @@ -0,0 +1,43 @@ +-- 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 +-- + +SET TIME ZONE '-8:00'; +> ok + +SELECT CAST(CURRENT_TIME AS TIME(9)) = LOCALTIME; +>> TRUE + +SELECT CAST(CURRENT_TIME(0) AS TIME(9)) = LOCALTIME(0); +>> TRUE + +SELECT CAST(CURRENT_TIME(9) AS TIME(9)) = LOCALTIME(9); +>> TRUE + +SET TIME ZONE LOCAL; +> ok + +select length(curtime())>=8 c1, length(current_time())>=8 c2, substring(curtime(), 3, 1) c3; +> C1 C2 C3 +> ---- ---- -- +> TRUE TRUE : +> rows: 1 + +select length(now())>18 c1, length(current_timestamp())>18 c2, length(now(0))>18 c3, length(now(2))>18 c4; +> C1 C2 C3 C4 +> ---- ---- ---- ---- +> TRUE TRUE TRUE TRUE +> rows: 1 + +SELECT CAST(CURRENT_TIME AS TIME(9)) = LOCALTIME; +>> TRUE + +SELECT CAST(CURRENT_TIME(0) AS TIME(9)) = LOCALTIME(0); +>> TRUE + +SELECT CAST(CURRENT_TIME(9) AS TIME(9)) = LOCALTIME(9); +>> TRUE + +EXPLAIN SELECT CURRENT_TIME, LOCALTIME, CURRENT_TIME(9), LOCALTIME(9); +>> SELECT CURRENT_TIME, LOCALTIME, CURRENT_TIME(9), LOCALTIME(9) diff --git a/h2/src/test/org/h2/test/scripts/functions/timeanddate/current_date.sql b/h2/src/test/org/h2/test/scripts/functions/timeanddate/current_date.sql new file mode 100644 index 0000000..c5fe931 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/timeanddate/current_date.sql @@ -0,0 +1,13 @@ +-- 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 +-- + +select length(curdate()) c1, length(current_date()) c2, substring(curdate(), 5, 1) c3; +> C1 C2 C3 +> -- -- -- +> 10 10 - +> rows: 1 + +SELECT CURRENT_DATE IS OF (DATE); +>> TRUE diff --git a/h2/src/test/org/h2/test/scripts/functions/timeanddate/current_timestamp.sql b/h2/src/test/org/h2/test/scripts/functions/timeanddate/current_timestamp.sql new file mode 100644 index 0000000..38e6ef8 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/timeanddate/current_timestamp.sql @@ -0,0 +1,137 @@ +-- 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 +-- + +SET TIME ZONE '-8:00'; +> ok + +SELECT CAST(CURRENT_TIMESTAMP AS TIMESTAMP(9)) = LOCALTIMESTAMP; +>> TRUE + +SELECT CAST(CURRENT_TIMESTAMP(0) AS TIMESTAMP(9)) = LOCALTIMESTAMP(0); +>> TRUE + +SELECT CAST(CURRENT_TIMESTAMP(9) AS TIMESTAMP(9)) = LOCALTIMESTAMP(9); +>> TRUE + +VALUES EXTRACT(TIMEZONE_HOUR FROM CURRENT_TIMESTAMP); +>> -8 + +SET TIME ZONE '5:00'; +> ok + +VALUES EXTRACT(TIMEZONE_HOUR FROM CURRENT_TIMESTAMP); +>> 5 + +SET TIME ZONE LOCAL; +> ok + +@reconnect off + +SET AUTOCOMMIT OFF; +> ok + +CREATE ALIAS SLEEP FOR "java.lang.Thread.sleep(long)"; +> ok + +CREATE TABLE TEST(I IDENTITY PRIMARY KEY, T TIMESTAMP(9) WITH TIME ZONE); +> ok + +INSERT INTO TEST(T) VALUES (CURRENT_TIMESTAMP(9)), (CURRENT_TIMESTAMP(9)); +> update count: 2 + +CALL SLEEP(10); +>> null + +INSERT INTO TEST(T) VALUES (CURRENT_TIMESTAMP(9)); +> update count: 1 + +CALL SLEEP(10); +>> null + +COMMIT; +> ok + +INSERT INTO TEST(T) VALUES (CURRENT_TIMESTAMP(9)); +> update count: 1 + +CALL SLEEP(10); +>> null + +COMMIT; +> ok + +-- same statement +SELECT (SELECT T FROM TEST WHERE I = 1) = (SELECT T FROM TEST WHERE I = 2); +>> TRUE + +-- same transaction +SELECT (SELECT T FROM TEST WHERE I = 2) = (SELECT T FROM TEST WHERE I = 3); +>> TRUE + +-- another transaction +SELECT (SELECT T FROM TEST WHERE I = 3) = (SELECT T FROM TEST WHERE I = 4); +>> FALSE + +SET MODE MySQL; +> ok + +INSERT INTO TEST(T) VALUES (CURRENT_TIMESTAMP(9)), (CURRENT_TIMESTAMP(9)); +> update count: 2 + +CALL SLEEP(10); +>> null + +INSERT INTO TEST(T) VALUES (CURRENT_TIMESTAMP(9)); +> update count: 1 + +CALL SLEEP(10); +>> null + +COMMIT; +> ok + +INSERT INTO TEST(T) VALUES (CURRENT_TIMESTAMP(9)); +> update count: 1 + +COMMIT; +> ok + +-- same statement +SELECT (SELECT T FROM TEST WHERE I = 5) = (SELECT T FROM TEST WHERE I = 6); +>> TRUE + +-- same transaction +SELECT (SELECT T FROM TEST WHERE I = 6) = (SELECT T FROM TEST WHERE I = 7); +>> FALSE + +-- another transaction +SELECT (SELECT T FROM TEST WHERE I = 7) = (SELECT T FROM TEST WHERE I = 8); +>> FALSE + +SET MODE Regular; +> ok + +DROP TABLE TEST; +> ok + +DROP ALIAS SLEEP; +> ok + +SET AUTOCOMMIT ON; +> ok + +@reconnect on + +SELECT GETDATE(); +> exception FUNCTION_NOT_FOUND_1 + +SET MODE MSSQLServer; +> ok + +SELECT LOCALTIMESTAMP(3) = GETDATE(); +>> TRUE + +SET MODE Regular; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/timeanddate/date_trunc.sql b/h2/src/test/org/h2/test/scripts/functions/timeanddate/date_trunc.sql new file mode 100644 index 0000000..7d72d28 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/timeanddate/date_trunc.sql @@ -0,0 +1,925 @@ +-- 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 +-- + +@reconnect off + +SET TIME ZONE '01:00'; +> ok + +-- +-- Test time unit in 'MICROSECONDS' +-- +SELECT DATE_TRUNC('MICROSECONDS', time '00:00:00.000'); +>> 00:00:00 + +SELECT DATE_TRUNC('microseconds', time '00:00:00.000'); +>> 00:00:00 + +SELECT DATE_TRUNC(microseconds, time '00:00:00.000'); +>> 00:00:00 + +SELECT DATE_TRUNC('MICROSECONDS', time '15:00:00'); +>> 15:00:00 + +SELECT DATE_TRUNC('microseconds', time '15:00:00'); +>> 15:00:00 + +SELECT DATE_TRUNC('MICROSECONDS', time '15:14:13'); +>> 15:14:13 + +SELECT DATE_TRUNC('microseconds', time '15:14:13'); +>> 15:14:13 + +SELECT DATE_TRUNC('MICROSECONDS', time '15:14:13.123456789'); +>> 15:14:13.123456 + +SELECT DATE_TRUNC('microseconds', time '15:14:13.123456789'); +>> 15:14:13.123456 + +SELECT DATE_TRUNC('MICROSECONDS', date '2015-05-29'); +>> 2015-05-29 + +SELECT DATE_TRUNC('microseconds', date '2015-05-29'); +>> 2015-05-29 + +SELECT DATE_TRUNC('MICROSECONDS', date '1970-01-01'); +>> 1970-01-01 + +SELECT DATE_TRUNC('microseconds', date '1970-01-01'); +>> 1970-01-01 + +select DATE_TRUNC('MICROSECONDS', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2015-05-29 15:14:13+00 + +select DATE_TRUNC('microseconds', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2015-05-29 15:14:13+00 + +select DATE_TRUNC('MICROSECONDS', timestamp with time zone '2015-05-29 15:14:13.123456789+00'); +>> 2015-05-29 15:14:13.123456+00 + +select DATE_TRUNC('microseconds', timestamp with time zone '2015-05-29 15:14:13.123456789+00'); +>> 2015-05-29 15:14:13.123456+00 + +select DATE_TRUNC('MICROSECONDS', timestamp with time zone '2015-05-29 15:14:13-06'); +>> 2015-05-29 15:14:13-06 + +select DATE_TRUNC('microseconds', timestamp with time zone '2015-05-29 15:14:13-06'); +>> 2015-05-29 15:14:13-06 + +select DATE_TRUNC('MICROSECONDS', timestamp with time zone '2015-05-29 15:14:13.123456789-06'); +>> 2015-05-29 15:14:13.123456-06 + +select DATE_TRUNC('microseconds', timestamp with time zone '2015-05-29 15:14:13.123456789-06'); +>> 2015-05-29 15:14:13.123456-06 + +select DATE_TRUNC('MICROSECONDS', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2015-05-29 15:14:13+10 + +select DATE_TRUNC('microseconds', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2015-05-29 15:14:13+10 + +select DATE_TRUNC('MICROSECONDS', timestamp with time zone '2015-05-29 15:14:13.123456789+10'); +>> 2015-05-29 15:14:13.123456+10 + +select DATE_TRUNC('microseconds', timestamp with time zone '2015-05-29 15:14:13.123456789+10'); +>> 2015-05-29 15:14:13.123456+10 + +SELECT DATE_TRUNC('microseconds', timestamp '2015-05-29 15:14:13'); +>> 2015-05-29 15:14:13 + +SELECT DATE_TRUNC('MICROSECONDS', timestamp '2015-05-29 15:14:13'); +>> 2015-05-29 15:14:13 + +SELECT DATE_TRUNC('microseconds', timestamp '2015-05-29 15:14:13.123456789'); +>> 2015-05-29 15:14:13.123456 + +SELECT DATE_TRUNC('MICROSECONDS', timestamp '2015-05-29 15:14:13.123456789'); +>> 2015-05-29 15:14:13.123456 + +SELECT DATE_TRUNC('microseconds', timestamp '2015-05-29 15:00:00'); +>> 2015-05-29 15:00:00 + +SELECT DATE_TRUNC('MICROSECONDS', timestamp '2015-05-29 15:00:00'); +>> 2015-05-29 15:00:00 + +SELECT DATE_TRUNC('microseconds', timestamp '2015-05-29 00:00:00'); +>> 2015-05-29 00:00:00 + +SELECT DATE_TRUNC('MICROSECONDS', timestamp '2015-05-29 00:00:00'); +>> 2015-05-29 00:00:00 + +-- +-- Test time unit in 'MILLISECONDS' +-- +SELECT DATE_TRUNC('MILLISECONDS', time '00:00:00.000'); +>> 00:00:00 + +SELECT DATE_TRUNC('milliseconds', time '00:00:00.000'); +>> 00:00:00 + +SELECT DATE_TRUNC('MILLISECONDS', time '15:00:00'); +>> 15:00:00 + +SELECT DATE_TRUNC('milliseconds', time '15:00:00'); +>> 15:00:00 + +SELECT DATE_TRUNC('MILLISECONDS', time '15:14:13'); +>> 15:14:13 + +SELECT DATE_TRUNC('milliseconds', time '15:14:13'); +>> 15:14:13 + +SELECT DATE_TRUNC('MILLISECONDS', time '15:14:13.123456'); +>> 15:14:13.123 + +SELECT DATE_TRUNC('milliseconds', time '15:14:13.123456'); +>> 15:14:13.123 + +SELECT DATE_TRUNC('MILLISECONDS', date '2015-05-29'); +>> 2015-05-29 + +SELECT DATE_TRUNC('milliseconds', date '2015-05-29'); +>> 2015-05-29 + +SELECT DATE_TRUNC('MILLISECONDS', date '1970-01-01'); +>> 1970-01-01 + +SELECT DATE_TRUNC('milliseconds', date '1970-01-01'); +>> 1970-01-01 + +select DATE_TRUNC('MILLISECONDS', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2015-05-29 15:14:13+00 + +select DATE_TRUNC('milliseconds', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2015-05-29 15:14:13+00 + +select DATE_TRUNC('MILLISECONDS', timestamp with time zone '2015-05-29 15:14:13.123456+00'); +>> 2015-05-29 15:14:13.123+00 + +select DATE_TRUNC('milliseconds', timestamp with time zone '2015-05-29 15:14:13.123456+00'); +>> 2015-05-29 15:14:13.123+00 + +select DATE_TRUNC('MILLISECONDS', timestamp with time zone '2015-05-29 15:14:13-06'); +>> 2015-05-29 15:14:13-06 + +select DATE_TRUNC('milliseconds', timestamp with time zone '2015-05-29 15:14:13-06'); +>> 2015-05-29 15:14:13-06 + +select DATE_TRUNC('MILLISECONDS', timestamp with time zone '2015-05-29 15:14:13.123456-06'); +>> 2015-05-29 15:14:13.123-06 + +select DATE_TRUNC('milliseconds', timestamp with time zone '2015-05-29 15:14:13.123456-06'); +>> 2015-05-29 15:14:13.123-06 + +select DATE_TRUNC('MILLISECONDS', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2015-05-29 15:14:13+10 + +select DATE_TRUNC('milliseconds', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2015-05-29 15:14:13+10 + +select DATE_TRUNC('MILLISECONDS', timestamp with time zone '2015-05-29 15:14:13.123456+10'); +>> 2015-05-29 15:14:13.123+10 + +select DATE_TRUNC('milliseconds', timestamp with time zone '2015-05-29 15:14:13.123456+10'); +>> 2015-05-29 15:14:13.123+10 + +SELECT DATE_TRUNC('milliseconds', timestamp '2015-05-29 15:14:13'); +>> 2015-05-29 15:14:13 + +SELECT DATE_TRUNC('MILLISECONDS', timestamp '2015-05-29 15:14:13'); +>> 2015-05-29 15:14:13 + +SELECT DATE_TRUNC('milliseconds', timestamp '2015-05-29 15:14:13.123456'); +>> 2015-05-29 15:14:13.123 + +SELECT DATE_TRUNC('MILLISECONDS', timestamp '2015-05-29 15:14:13.123456'); +>> 2015-05-29 15:14:13.123 + +SELECT DATE_TRUNC('milliseconds', timestamp '2015-05-29 15:00:00'); +>> 2015-05-29 15:00:00 + +SELECT DATE_TRUNC('MILLISECONDS', timestamp '2015-05-29 15:00:00'); +>> 2015-05-29 15:00:00 + +SELECT DATE_TRUNC('milliseconds', timestamp '2015-05-29 00:00:00'); +>> 2015-05-29 00:00:00 + +SELECT DATE_TRUNC('MILLISECONDS', timestamp '2015-05-29 00:00:00'); +>> 2015-05-29 00:00:00 + +-- +-- Test time unit 'SECOND' +-- +SELECT DATE_TRUNC('SECOND', time '00:00:00.000'); +>> 00:00:00 + +SELECT DATE_TRUNC('second', time '00:00:00.000'); +>> 00:00:00 + +SELECT DATE_TRUNC('SECOND', time '15:00:00'); +>> 15:00:00 + +SELECT DATE_TRUNC('second', time '15:00:00'); +>> 15:00:00 + +SELECT DATE_TRUNC('SECOND', time '15:14:13'); +>> 15:14:13 + +SELECT DATE_TRUNC('second', time '15:14:13'); +>> 15:14:13 + +SELECT DATE_TRUNC('SECOND', time '15:14:13.123456'); +>> 15:14:13 + +SELECT DATE_TRUNC('second', time '15:14:13.123456'); +>> 15:14:13 + +SELECT DATE_TRUNC('SECOND', date '2015-05-29'); +>> 2015-05-29 + +SELECT DATE_TRUNC('second', date '2015-05-29'); +>> 2015-05-29 + +SELECT DATE_TRUNC('SECOND', date '1970-01-01'); +>> 1970-01-01 + +SELECT DATE_TRUNC('second', date '1970-01-01'); +>> 1970-01-01 + +select DATE_TRUNC('SECOND', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2015-05-29 15:14:13+00 + +select DATE_TRUNC('second', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2015-05-29 15:14:13+00 + +select DATE_TRUNC('SECOND', timestamp with time zone '2015-05-29 15:14:13.123456+00'); +>> 2015-05-29 15:14:13+00 + +select DATE_TRUNC('second', timestamp with time zone '2015-05-29 15:14:13.123456+00'); +>> 2015-05-29 15:14:13+00 + +select DATE_TRUNC('SECOND', timestamp with time zone '2015-05-29 15:14:13-06'); +>> 2015-05-29 15:14:13-06 + +select DATE_TRUNC('second', timestamp with time zone '2015-05-29 15:14:13-06'); +>> 2015-05-29 15:14:13-06 + +select DATE_TRUNC('SECOND', timestamp with time zone '2015-05-29 15:14:13.123456-06'); +>> 2015-05-29 15:14:13-06 + +select DATE_TRUNC('second', timestamp with time zone '2015-05-29 15:14:13.123456-06'); +>> 2015-05-29 15:14:13-06 + +select DATE_TRUNC('SECOND', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2015-05-29 15:14:13+10 + +select DATE_TRUNC('second', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2015-05-29 15:14:13+10 + +select DATE_TRUNC('SECOND', timestamp with time zone '2015-05-29 15:14:13.123456+10'); +>> 2015-05-29 15:14:13+10 + +select DATE_TRUNC('second', timestamp with time zone '2015-05-29 15:14:13.123456+10'); +>> 2015-05-29 15:14:13+10 + +SELECT DATE_TRUNC('second', timestamp '2015-05-29 15:14:13'); +>> 2015-05-29 15:14:13 + +SELECT DATE_TRUNC('SECOND', timestamp '2015-05-29 15:14:13'); +>> 2015-05-29 15:14:13 + +SELECT DATE_TRUNC('second', timestamp '2015-05-29 15:14:13.123456'); +>> 2015-05-29 15:14:13 + +SELECT DATE_TRUNC('SECOND', timestamp '2015-05-29 15:14:13.123456'); +>> 2015-05-29 15:14:13 + +SELECT DATE_TRUNC('second', timestamp '2015-05-29 15:00:00'); +>> 2015-05-29 15:00:00 + +SELECT DATE_TRUNC('SECOND', timestamp '2015-05-29 15:00:00'); +>> 2015-05-29 15:00:00 + +SELECT DATE_TRUNC('second', timestamp '2015-05-29 00:00:00'); +>> 2015-05-29 00:00:00 + +SELECT DATE_TRUNC('SECOND', timestamp '2015-05-29 00:00:00'); +>> 2015-05-29 00:00:00 + +-- +-- Test time unit 'MINUTE' +-- +SELECT DATE_TRUNC('MINUTE', time '00:00:00'); +>> 00:00:00 + +SELECT DATE_TRUNC('minute', time '00:00:00'); +>> 00:00:00 + +SELECT DATE_TRUNC('MINUTE', time '15:00:00'); +>> 15:00:00 + +SELECT DATE_TRUNC('minute', time '15:00:00'); +>> 15:00:00 + +SELECT DATE_TRUNC('MINUTE', time '15:14:13'); +>> 15:14:00 + +SELECT DATE_TRUNC('minute', time '15:14:13'); +>> 15:14:00 + +SELECT DATE_TRUNC('MINUTE', date '2015-05-29'); +>> 2015-05-29 + +SELECT DATE_TRUNC('minute', date '2015-05-29'); +>> 2015-05-29 + +SELECT DATE_TRUNC('MINUTE', date '1970-01-01'); +>> 1970-01-01 + +SELECT DATE_TRUNC('minute', date '1970-01-01'); +>> 1970-01-01 + +select DATE_TRUNC('MINUTE', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2015-05-29 15:14:00+00 + +select DATE_TRUNC('minute', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2015-05-29 15:14:00+00 + +select DATE_TRUNC('MINUTE', timestamp with time zone '2015-05-29 15:14:13-06'); +>> 2015-05-29 15:14:00-06 + +select DATE_TRUNC('minute', timestamp with time zone '2015-05-29 15:14:13-06'); +>> 2015-05-29 15:14:00-06 + +select DATE_TRUNC('MINUTE', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2015-05-29 15:14:00+10 + +select DATE_TRUNC('minute', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2015-05-29 15:14:00+10 + +SELECT DATE_TRUNC('MINUTE', timestamp '2015-05-29 15:14:13'); +>> 2015-05-29 15:14:00 + +SELECT DATE_TRUNC('MINUTE', timestamp '2015-05-29 15:00:00'); +>> 2015-05-29 15:00:00 + +SELECT DATE_TRUNC('MINUTE', timestamp '2015-05-29 00:00:00'); +>> 2015-05-29 00:00:00 + +-- +-- Test time unit 'HOUR' +-- +SELECT DATE_TRUNC('HOUR', time '00:00:00'); +>> 00:00:00 + +SELECT DATE_TRUNC('hour', time '00:00:00'); +>> 00:00:00 + +SELECT DATE_TRUNC('HOUR', time '15:00:00'); +>> 15:00:00 + +SELECT DATE_TRUNC('hour', time '15:00:00'); +>> 15:00:00 + +SELECT DATE_TRUNC('HOUR', time '15:14:13'); +>> 15:00:00 + +SELECT DATE_TRUNC('hour', time '15:14:13'); +>> 15:00:00 + +SELECT DATE_TRUNC('HOUR', date '2015-05-29'); +>> 2015-05-29 + +SELECT DATE_TRUNC('hour', date '2015-05-29'); +>> 2015-05-29 + +SELECT DATE_TRUNC('HOUR', date '1970-01-01'); +>> 1970-01-01 + +SELECT DATE_TRUNC('hour', date '1970-01-01'); +>> 1970-01-01 + +select DATE_TRUNC('HOUR', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2015-05-29 15:00:00+00 + +select DATE_TRUNC('hour', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2015-05-29 15:00:00+00 + +select DATE_TRUNC('HOUR', timestamp with time zone '2015-05-29 15:14:13-06'); +>> 2015-05-29 15:00:00-06 + +select DATE_TRUNC('hour', timestamp with time zone '2015-05-29 15:14:13-06'); +>> 2015-05-29 15:00:00-06 + +select DATE_TRUNC('HOUR', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2015-05-29 15:00:00+10 + +select DATE_TRUNC('hour', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2015-05-29 15:00:00+10 + +SELECT DATE_TRUNC('hour', timestamp '2015-05-29 15:14:13'); +>> 2015-05-29 15:00:00 + +SELECT DATE_TRUNC('HOUR', timestamp '2015-05-29 15:14:13'); +>> 2015-05-29 15:00:00 + +SELECT DATE_TRUNC('hour', timestamp '2015-05-29 15:00:00'); +>> 2015-05-29 15:00:00 + +SELECT DATE_TRUNC('HOUR', timestamp '2015-05-29 15:00:00'); +>> 2015-05-29 15:00:00 + +SELECT DATE_TRUNC('hour', timestamp '2015-05-29 00:00:00'); +>> 2015-05-29 00:00:00 + +SELECT DATE_TRUNC('HOUR', timestamp '2015-05-29 00:00:00'); +>> 2015-05-29 00:00:00 + +-- +-- Test time unit 'DAY' +-- +select DATE_TRUNC('day', time '00:00:00'); +>> 00:00:00 + +select DATE_TRUNC('DAY', time '00:00:00'); +>> 00:00:00 + +select DATE_TRUNC('day', time '15:14:13'); +>> 00:00:00 + +select DATE_TRUNC('DAY', time '15:14:13'); +>> 00:00:00 + +select DATE_TRUNC('day', date '2015-05-29'); +>> 2015-05-29 + +select DATE_TRUNC('DAY', date '2015-05-29'); +>> 2015-05-29 + +select DATE_TRUNC('day', timestamp '2015-05-29 15:14:13'); +>> 2015-05-29 00:00:00 + +select DATE_TRUNC('DAY', timestamp '2015-05-29 15:14:13'); +>> 2015-05-29 00:00:00 + +select DATE_TRUNC('day', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2015-05-29 00:00:00+00 + +select DATE_TRUNC('DAY', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2015-05-29 00:00:00+00 + +select DATE_TRUNC('day', timestamp with time zone '2015-05-29 05:14:13-06'); +>> 2015-05-29 00:00:00-06 + +select DATE_TRUNC('DAY', timestamp with time zone '2015-05-29 05:14:13-06'); +>> 2015-05-29 00:00:00-06 + +select DATE_TRUNC('day', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2015-05-29 00:00:00+10 + +select DATE_TRUNC('DAY', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2015-05-29 00:00:00+10 + +-- +-- Test time unit 'WEEK' +-- +select DATE_TRUNC('week', time '00:00:00'); +>> 00:00:00 + +select DATE_TRUNC('WEEK', time '00:00:00'); +>> 00:00:00 + +select DATE_TRUNC('week', time '15:14:13'); +>> 00:00:00 + +select DATE_TRUNC('WEEK', time '15:14:13'); +>> 00:00:00 + +-- ISO_WEEK + +SELECT DATE_TRUNC(ISO_WEEK, TIME '00:00:00'); +>> 00:00:00 + +SELECT DATE_TRUNC(ISO_WEEK, TIME '15:14:13'); +>> 00:00:00 + +SELECT DATE_TRUNC(ISO_WEEK, DATE '2015-05-28'); +>> 2015-05-25 + +SELECT DATE_TRUNC(ISO_WEEK, TIMESTAMP '2015-05-29 15:14:13'); +>> 2015-05-25 00:00:00 + +SELECT DATE_TRUNC(ISO_WEEK, TIMESTAMP '2018-03-14 00:00:00.000'); +>> 2018-03-12 00:00:00 + +SELECT DATE_TRUNC(ISO_WEEK, TIMESTAMP WITH TIME ZONE '2015-05-29 15:14:13+00'); +>> 2015-05-25 00:00:00+00 + +SELECT DATE_TRUNC(ISO_WEEK, TIMESTAMP WITH TIME ZONE '2015-05-29 05:14:13-06'); +>> 2015-05-25 00:00:00-06 + +SELECT DATE_TRUNC(ISO_WEEK, TIMESTAMP WITH TIME ZONE '2015-05-29 15:14:13+10'); +>> 2015-05-25 00:00:00+10 + +-- +-- Test time unit 'MONTH' +-- +select DATE_TRUNC('month', time '00:00:00'); +>> 00:00:00 + +select DATE_TRUNC('MONTH', time '00:00:00'); +>> 00:00:00 + +select DATE_TRUNC(MONTH, time '00:00:00'); +>> 00:00:00 + +select DATE_TRUNC('month', time '15:14:13'); +>> 00:00:00 + +select DATE_TRUNC('MONTH', time '15:14:13'); +>> 00:00:00 + +select DATE_TRUNC('month', date '2015-05-28'); +>> 2015-05-01 + +select DATE_TRUNC('MONTH', date '2015-05-28'); +>> 2015-05-01 + +select DATE_TRUNC('month', timestamp '2015-05-29 15:14:13'); +>> 2015-05-01 00:00:00 + +select DATE_TRUNC('MONTH', timestamp '2015-05-29 15:14:13'); +>> 2015-05-01 00:00:00 + +SELECT DATE_TRUNC('MONTH', timestamp '2018-03-14 00:00:00.000'); +>> 2018-03-01 00:00:00 + +select DATE_TRUNC('month', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2015-05-01 00:00:00+00 + +select DATE_TRUNC('MONTH', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2015-05-01 00:00:00+00 + +select DATE_TRUNC('month', timestamp with time zone '2015-05-29 05:14:13-06'); +>> 2015-05-01 00:00:00-06 + +select DATE_TRUNC('MONTH', timestamp with time zone '2015-05-29 05:14:13-06'); +>> 2015-05-01 00:00:00-06 + +select DATE_TRUNC('month', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2015-05-01 00:00:00+10 + +select DATE_TRUNC('MONTH', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2015-05-01 00:00:00+10 + +-- +-- Test time unit 'QUARTER' +-- +select DATE_TRUNC('quarter', time '00:00:00'); +>> 00:00:00 + +select DATE_TRUNC('QUARTER', time '00:00:00'); +>> 00:00:00 + +select DATE_TRUNC('quarter', time '15:14:13'); +>> 00:00:00 + +select DATE_TRUNC('QUARTER', time '15:14:13'); +>> 00:00:00 + +select DATE_TRUNC('quarter', date '2015-05-28'); +>> 2015-04-01 + +select DATE_TRUNC('QUARTER', date '2015-05-28'); +>> 2015-04-01 + +select DATE_TRUNC('quarter', timestamp '2015-05-29 15:14:13'); +>> 2015-04-01 00:00:00 + +select DATE_TRUNC('QUARTER', timestamp '2015-05-29 15:14:13'); +>> 2015-04-01 00:00:00 + +SELECT DATE_TRUNC('QUARTER', timestamp '2018-03-14 00:00:00.000'); +>> 2018-01-01 00:00:00 + +SELECT DATE_TRUNC('QUARTER', timestamp '2015-05-29 15:14:13'); +>> 2015-04-01 00:00:00 + +SELECT DATE_TRUNC('QUARTER', timestamp '2015-05-01 15:14:13'); +>> 2015-04-01 00:00:00 + +SELECT DATE_TRUNC('QUARTER', timestamp '2015-07-29 15:14:13'); +>> 2015-07-01 00:00:00 + +SELECT DATE_TRUNC('QUARTER', timestamp '2015-09-29 15:14:13'); +>> 2015-07-01 00:00:00 + +SELECT DATE_TRUNC('QUARTER', timestamp '2015-10-29 15:14:13'); +>> 2015-10-01 00:00:00 + +SELECT DATE_TRUNC('QUARTER', timestamp '2015-12-29 15:14:13'); +>> 2015-10-01 00:00:00 + +select DATE_TRUNC('quarter', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2015-04-01 00:00:00+00 + +select DATE_TRUNC('QUARTER', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2015-04-01 00:00:00+00 + +select DATE_TRUNC('quarter', timestamp with time zone '2015-05-29 05:14:13-06'); +>> 2015-04-01 00:00:00-06 + +select DATE_TRUNC('QUARTER', timestamp with time zone '2015-05-29 05:14:13-06'); +>> 2015-04-01 00:00:00-06 + +select DATE_TRUNC('quarter', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2015-04-01 00:00:00+10 + +select DATE_TRUNC('QUARTER', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2015-04-01 00:00:00+10 + +-- +-- Test time unit 'YEAR' +-- +select DATE_TRUNC('year', time '00:00:00'); +>> 00:00:00 + +select DATE_TRUNC('YEAR', time '00:00:00'); +>> 00:00:00 + +select DATE_TRUNC('year', time '15:14:13'); +>> 00:00:00 + +select DATE_TRUNC('YEAR', time '15:14:13'); +>> 00:00:00 + +select DATE_TRUNC('year', date '2015-05-28'); +>> 2015-01-01 + +select DATE_TRUNC('YEAR', date '2015-05-28'); +>> 2015-01-01 + +select DATE_TRUNC('year', timestamp '2015-05-29 15:14:13'); +>> 2015-01-01 00:00:00 + +select DATE_TRUNC('YEAR', timestamp '2015-05-29 15:14:13'); +>> 2015-01-01 00:00:00 + +select DATE_TRUNC('year', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2015-01-01 00:00:00+00 + +select DATE_TRUNC('YEAR', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2015-01-01 00:00:00+00 + +select DATE_TRUNC('year', timestamp with time zone '2015-05-29 05:14:13-06'); +>> 2015-01-01 00:00:00-06 + +select DATE_TRUNC('YEAR', timestamp with time zone '2015-05-29 05:14:13-06'); +>> 2015-01-01 00:00:00-06 + +select DATE_TRUNC('year', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2015-01-01 00:00:00+10 + +select DATE_TRUNC('YEAR', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2015-01-01 00:00:00+10 + +-- +-- Test time unit 'DECADE' +-- +select DATE_TRUNC('decade', time '00:00:00'); +>> 00:00:00 + +select DATE_TRUNC('DECADE', time '00:00:00'); +>> 00:00:00 + +select DATE_TRUNC('decade', time '15:14:13'); +>> 00:00:00 + +select DATE_TRUNC('DECADE', time '15:14:13'); +>> 00:00:00 + +select DATE_TRUNC('decade', date '2015-05-28'); +>> 2010-01-01 + +select DATE_TRUNC('DECADE', date '2015-05-28'); +>> 2010-01-01 + +select DATE_TRUNC('decade', timestamp '2015-05-29 15:14:13'); +>> 2010-01-01 00:00:00 + +select DATE_TRUNC('DECADE', timestamp '2015-05-29 15:14:13'); +>> 2010-01-01 00:00:00 + +SELECT DATE_TRUNC('decade', timestamp '2010-05-29 15:14:13'); +>> 2010-01-01 00:00:00 + +select DATE_TRUNC('decade', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2010-01-01 00:00:00+00 + +select DATE_TRUNC('DECADE', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2010-01-01 00:00:00+00 + +select DATE_TRUNC('decade', timestamp with time zone '2015-05-29 05:14:13-06'); +>> 2010-01-01 00:00:00-06 + +select DATE_TRUNC('DECADE', timestamp with time zone '2015-05-29 05:14:13-06'); +>> 2010-01-01 00:00:00-06 + +select DATE_TRUNC('decade', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2010-01-01 00:00:00+10 + +select DATE_TRUNC('DECADE', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2010-01-01 00:00:00+10 + +-- +-- Test time unit 'CENTURY' +-- +select DATE_TRUNC('century', time '00:00:00'); +>> 00:00:00 + +select DATE_TRUNC('CENTURY', time '00:00:00'); +>> 00:00:00 + +select DATE_TRUNC('century', time '15:14:13'); +>> 00:00:00 + +select DATE_TRUNC('CENTURY', time '15:14:13'); +>> 00:00:00 + +select DATE_TRUNC('century', date '2015-05-28'); +>> 2001-01-01 + +select DATE_TRUNC('CENTURY', date '2015-05-28'); +>> 2001-01-01 + +select DATE_TRUNC('century', timestamp '2015-05-29 15:14:13'); +>> 2001-01-01 00:00:00 + +select DATE_TRUNC('CENTURY', timestamp '2015-05-29 15:14:13'); +>> 2001-01-01 00:00:00 + +SELECT DATE_TRUNC('century', timestamp '2199-05-29 15:14:13'); +>> 2101-01-01 00:00:00 + +SELECT DATE_TRUNC('CENTURY', timestamp '2000-05-29 15:14:13'); +>> 1901-01-01 00:00:00 + +SELECT DATE_TRUNC('century', timestamp '2001-05-29 15:14:13'); +>> 2001-01-01 00:00:00 + +select DATE_TRUNC('century', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2001-01-01 00:00:00+00 + +select DATE_TRUNC('CENTURY', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2001-01-01 00:00:00+00 + +select DATE_TRUNC('century', timestamp with time zone '2015-05-29 05:14:13-06'); +>> 2001-01-01 00:00:00-06 + +select DATE_TRUNC('CENTURY', timestamp with time zone '2015-05-29 05:14:13-06'); +>> 2001-01-01 00:00:00-06 + +select DATE_TRUNC('century', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2001-01-01 00:00:00+10 + +select DATE_TRUNC('CENTURY', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2001-01-01 00:00:00+10 + +-- +-- Test time unit 'MILLENNIUM' +-- +select DATE_TRUNC('millennium', time '00:00:00'); +>> 00:00:00 + +select DATE_TRUNC('MILLENNIUM', time '00:00:00'); +>> 00:00:00 + +select DATE_TRUNC('millennium', time '15:14:13'); +>> 00:00:00 + +select DATE_TRUNC('MILLENNIUM', time '15:14:13'); +>> 00:00:00 + +select DATE_TRUNC('millennium', date '2015-05-28'); +>> 2001-01-01 + +select DATE_TRUNC('MILLENNIUM', date '2015-05-28'); +>> 2001-01-01 + +select DATE_TRUNC('millennium', timestamp '2015-05-29 15:14:13'); +>> 2001-01-01 00:00:00 + +select DATE_TRUNC('MILLENNIUM', timestamp '2015-05-29 15:14:13'); +>> 2001-01-01 00:00:00 + +SELECT DATE_TRUNC('millennium', timestamp '2000-05-29 15:14:13'); +>> 1001-01-01 00:00:00 + +select DATE_TRUNC('millennium', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2001-01-01 00:00:00+00 + +select DATE_TRUNC('MILLENNIUM', timestamp with time zone '2015-05-29 15:14:13+00'); +>> 2001-01-01 00:00:00+00 + +select DATE_TRUNC('millennium', timestamp with time zone '2015-05-29 05:14:13-06'); +>> 2001-01-01 00:00:00-06 + +select DATE_TRUNC('MILLENNIUM', timestamp with time zone '2015-05-29 05:14:13-06'); +>> 2001-01-01 00:00:00-06 + +select DATE_TRUNC('millennium', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2001-01-01 00:00:00+10 + +select DATE_TRUNC('MILLENNIUM', timestamp with time zone '2015-05-29 15:14:13+10'); +>> 2001-01-01 00:00:00+10 + +-- +-- Test unhandled time unit and bad date +-- +SELECT DATE_TRUNC('---', '2015-05-29 15:14:13'); +> exception INVALID_VALUE_2 + +SELECT DATE_TRUNC('', '2015-05-29 15:14:13'); +> exception INVALID_VALUE_2 + +SELECT DATE_TRUNC('', ''); +> exception INVALID_VALUE_2 + +SELECT DATE_TRUNC('YEAR', ''); +> exception INVALID_VALUE_2 + +SELECT DATE_TRUNC('microseconds', '2015-05-29 15:14:13'); +> exception INVALID_VALUE_2 + +SET MODE PostgreSQL; +> ok + +select DATE_TRUNC('YEAR', DATE '2015-05-28'); +>> 2015-01-01 00:00:00+01 + +SET MODE Regular; +> ok + +SELECT DATE_TRUNC(DECADE, DATE '0000-01-20'); +>> 0000-01-01 + +SELECT DATE_TRUNC(DECADE, DATE '-1-12-31'); +>> -0010-01-01 + +SELECT DATE_TRUNC(DECADE, DATE '-10-01-01'); +>> -0010-01-01 + +SELECT DATE_TRUNC(DECADE, DATE '-11-12-31'); +>> -0020-01-01 + +SELECT DATE_TRUNC(CENTURY, DATE '0001-01-20'); +>> 0001-01-01 + +SELECT DATE_TRUNC(CENTURY, DATE '0000-12-31'); +>> -0099-01-01 + +SELECT DATE_TRUNC(CENTURY, DATE '-1-12-31'); +>> -0099-01-01 + +SELECT DATE_TRUNC(CENTURY, DATE '-99-01-01'); +>> -0099-01-01 + +SELECT DATE_TRUNC(CENTURY, DATE '-100-12-31'); +>> -0199-01-01 + +SELECT DATE_TRUNC(MILLENNIUM, DATE '0001-01-20'); +>> 0001-01-01 + +SELECT DATE_TRUNC(MILLENNIUM, DATE '0000-12-31'); +>> -0999-01-01 + +SELECT DATE_TRUNC(MILLENNIUM, DATE '-1-12-31'); +>> -0999-01-01 + +SELECT DATE_TRUNC(MILLENNIUM, DATE '-999-01-01'); +>> -0999-01-01 + +SELECT DATE_TRUNC(MILLENNIUM, DATE '-1000-12-31'); +>> -1999-01-01 + +-- ISO_WEEK_YEAR + +SELECT DATE_TRUNC(ISO_WEEK_YEAR, DATE '2019-12-30'); +>> 2019-12-30 + +SELECT DATE_TRUNC(ISO_WEEK_YEAR, DATE '2020-01-01'); +>> 2019-12-30 + +SELECT DATE_TRUNC(ISO_WEEK_YEAR, DATE '2020-12-01'); +>> 2019-12-30 + +SELECT DATE_TRUNC(ISO_WEEK_YEAR, DATE '2020-12-31'); +>> 2019-12-30 + +SELECT DATE_TRUNC(ISO_WEEK_YEAR, DATE '2017-01-01'); +>> 2016-01-04 + +SELECT DATE_TRUNC(ISO_WEEK_YEAR, DATE '2017-01-02'); +>> 2017-01-02 diff --git a/h2/src/test/org/h2/test/scripts/functions/timeanddate/dateadd.sql b/h2/src/test/org/h2/test/scripts/functions/timeanddate/dateadd.sql new file mode 100644 index 0000000..6ce6d4d --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/timeanddate/dateadd.sql @@ -0,0 +1,142 @@ +-- 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 +-- + +select dateadd('month', 1, timestamp '2003-01-31 10:20:30.012345678'); +>> 2003-02-28 10:20:30.012345678 + +select dateadd('year', -1, timestamp '2000-02-29 10:20:30.012345678'); +>> 1999-02-28 10:20:30.012345678 + +create table test(d date, t time, ts timestamp); +> ok + +insert into test values(date '2001-01-01', time '01:00:00', timestamp '2010-01-01 00:00:00'); +> update count: 1 + +select ts + t from test; +>> 2010-01-01 01:00:00 + +select ts + t * 0.5 x from test; +>> 2010-01-01 00:30:00 + +select ts + 0.5 x from test; +>> 2010-01-01 12:00:00 + +select ts - 1.5 x from test; +>> 2009-12-30 12:00:00 + +select ts + t / 0.5 x from test; +>> 2010-01-01 02:00:00 + +VALUES TIME '04:00:00' + TIME '20:03:30.123'; +>> 00:03:30.123 + +VALUES TIME '04:00:00' + TIME WITH TIME ZONE '20:03:30.123+05'; +>> 00:03:30.123+05 + +VALUES TIME WITH TIME ZONE '04:00:00+08' + TIME '20:03:30.123'; +>> 00:03:30.123+08 + +VALUES TIME WITH TIME ZONE '04:00:00+08' + TIME WITH TIME ZONE '20:03:30.123+05'; +> exception FEATURE_NOT_SUPPORTED_1 + +VALUES DATE '2005-03-04' + TIME '20:03:30.123'; +>> 2005-03-04 20:03:30.123 + +VALUES DATE '2005-03-04' + TIME WITH TIME ZONE '20:03:30.123+05'; +>> 2005-03-04 20:03:30.123+05 + +VALUES TIMESTAMP '2005-03-04 04:00:00' + TIME '20:03:30.123'; +>> 2005-03-05 00:03:30.123 + +VALUES TIMESTAMP '2005-03-04 04:00:00' + TIME WITH TIME ZONE '20:03:30.123+05'; +>> 2005-03-05 00:03:30.123+05 + +VALUES TIMESTAMP WITH TIME ZONE '2005-03-04 04:00:00+08' + TIME '20:03:30.123'; +>> 2005-03-05 00:03:30.123+08 + +VALUES TIMESTAMP WITH TIME ZONE '2005-03-04 04:00:00+08' + TIME WITH TIME ZONE '20:03:30.123+05'; +> exception FEATURE_NOT_SUPPORTED_1 + +select 1 + d + 1, d - 1, 2 + ts + 2, ts - 2 from test; +> DATEADD(DAY, 1, DATEADD(DAY, 1, D)) DATEADD(DAY, -1, D) DATEADD(DAY, 2, DATEADD(DAY, 2, TS)) DATEADD(DAY, -2, TS) +> ----------------------------------- ------------------- ------------------------------------ -------------------- +> 2001-01-03 2000-12-31 2010-01-05 00:00:00 2009-12-30 00:00:00 +> rows: 1 + +select 1 + d + t + 1 from test; +>> 2001-01-03 01:00:00 + +drop table test; +> ok + +call dateadd('MS', 1, TIMESTAMP '2001-02-03 04:05:06.789001'); +>> 2001-02-03 04:05:06.790001 + +SELECT DATEADD('MICROSECOND', 1, TIME '10:00:01'), DATEADD('MCS', 1, TIMESTAMP '2010-10-20 10:00:01.1'); +> TIME '10:00:01.000001' TIMESTAMP '2010-10-20 10:00:01.100001' +> ---------------------- -------------------------------------- +> 10:00:01.000001 2010-10-20 10:00:01.100001 +> rows: 1 + +SELECT DATEADD('NANOSECOND', 1, TIME '10:00:01'), DATEADD('NS', 1, TIMESTAMP '2010-10-20 10:00:01.1'); +> TIME '10:00:01.000000001' TIMESTAMP '2010-10-20 10:00:01.100000001' +> ------------------------- ----------------------------------------- +> 10:00:01.000000001 2010-10-20 10:00:01.100000001 +> rows: 1 + +SELECT DATEADD('HOUR', 1, DATE '2010-01-20'); +>> 2010-01-20 01:00:00 + +SELECT DATEADD('MINUTE', 30, TIME '12:30:55'); +>> 13:00:55 + +SELECT DATEADD('DAY', 1, TIME '12:30:55'); +> exception INVALID_VALUE_2 + +SELECT DATEADD('QUARTER', 1, DATE '2010-11-16'); +>> 2011-02-16 + +SELECT DATEADD('DAY', 10, TIMESTAMP WITH TIME ZONE '2000-01-05 15:00:30.123456789-10'); +>> 2000-01-15 15:00:30.123456789-10 + +SELECT TIMESTAMPADD('DAY', 10, TIMESTAMP '2000-01-05 15:00:30.123456789'); +>> 2000-01-15 15:00:30.123456789 + +SELECT TIMESTAMPADD('TIMEZONE_HOUR', 1, TIMESTAMP WITH TIME ZONE '2010-01-01 10:00:00+07:30'); +>> 2010-01-01 10:00:00+08:30 + +SELECT TIMESTAMPADD('TIMEZONE_MINUTE', -45, TIMESTAMP WITH TIME ZONE '2010-01-01 10:00:00+07:30'); +>> 2010-01-01 10:00:00+06:45 + +SELECT TIMESTAMPADD('TIMEZONE_SECOND', -45, TIMESTAMP WITH TIME ZONE '2010-01-01 10:00:00+07:30'); +>> 2010-01-01 10:00:00+07:29:15 + +SELECT TIMESTAMPADD('TIMEZONE_HOUR', 1, TIME WITH TIME ZONE '10:00:00+07:30'); +>> 10:00:00+08:30 + +SELECT TIMESTAMPADD('TIMEZONE_MINUTE', -45, TIME WITH TIME ZONE '10:00:00+07:30'); +>> 10:00:00+06:45 + +SELECT DATEADD(HOUR, 1, TIME '23:00:00'); +>> 00:00:00 + +SELECT DATEADD(HOUR, 1, TIME WITH TIME ZONE '21:00:00+01'); +>> 22:00:00+01 + +SELECT DATEADD(HOUR, 1, TIME WITH TIME ZONE '23:00:00+01'); +>> 00:00:00+01 + +SELECT D FROM (SELECT '2010-01-01' D) WHERE D IN (SELECT D1 - 1 FROM (SELECT DATE '2010-01-02' D1)); +>> 2010-01-01 + +SELECT DATEADD(MILLENNIUM, 1, DATE '2000-02-29'); +>> 3000-02-28 + +SELECT DATEADD(CENTURY, 1, DATE '2000-02-29'); +>> 2100-02-28 + +SELECT DATEADD(DECADE, 1, DATE '2000-02-29'); +>> 2010-02-28 diff --git a/h2/src/test/org/h2/test/scripts/functions/timeanddate/datediff.sql b/h2/src/test/org/h2/test/scripts/functions/timeanddate/datediff.sql new file mode 100644 index 0000000..15b6052 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/timeanddate/datediff.sql @@ -0,0 +1,229 @@ +-- 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 +-- + +select datediff('yy', timestamp '2003-12-01 10:20:30.0', timestamp '2004-01-01 10:00:00.0'); +>> 1 + +select datediff('year', timestamp '2003-12-01 10:20:30.0', timestamp '2004-01-01 10:00:00.0'); +>> 1 + +select datediff('mm', timestamp '2003-11-01 10:20:30.0', timestamp '2004-01-01 10:00:00.0'); +>> 2 + +select datediff('month', timestamp '2003-11-01 10:20:30.0', timestamp '2004-01-01 10:00:00.0'); +>> 2 + +select datediff('dd', timestamp '2004-01-01 10:20:30.0', timestamp '2004-01-05 10:00:00.0'); +>> 4 + +select datediff('day', timestamp '2004-01-01 10:20:30.0', timestamp '2004-01-05 10:00:00.0'); +>> 4 + +select datediff('hh', timestamp '2004-01-01 10:20:30.0', timestamp '2004-01-02 10:00:00.0'); +>> 24 + +select datediff('hour', timestamp '2004-01-01 10:20:30.0', timestamp '2004-01-02 10:00:00.0'); +>> 24 + +select datediff('mi', timestamp '2004-01-01 10:20:30.0', timestamp '2004-01-01 10:00:00.0'); +>> -20 + +select datediff('minute', timestamp '2004-01-01 10:20:30.0', timestamp '2004-01-01 10:00:00.0'); +>> -20 + +select datediff('ss', timestamp '2004-01-01 10:00:00.5', timestamp '2004-01-01 10:00:01.0'); +>> 1 + +select datediff('second', timestamp '2004-01-01 10:00:00.5', timestamp '2004-01-01 10:00:01.0'); +>> 1 + +select datediff('ms', timestamp '2004-01-01 10:00:00.5', timestamp '2004-01-01 10:00:01.0'); +>> 500 + +select datediff('millisecond', timestamp '2004-01-01 10:00:00.5', timestamp '2004-01-01 10:00:01.0'); +>> 500 + +SELECT DATEDIFF('SECOND', '1900-01-01 00:00:00.001', '1900-01-01 00:00:00.002'), DATEDIFF('SECOND', '2000-01-01 00:00:00.001', '2000-01-01 00:00:00.002'); +> 0 0 +> - - +> 0 0 +> rows: 1 + +SELECT DATEDIFF('SECOND', '1900-01-01 00:00:00.000', '1900-01-01 00:00:00.001'), DATEDIFF('SECOND', '2000-01-01 00:00:00.000', '2000-01-01 00:00:00.001'); +> 0 0 +> - - +> 0 0 +> rows: 1 + +SELECT DATEDIFF('MINUTE', '1900-01-01 00:00:00.000', '1900-01-01 00:00:01.000'), DATEDIFF('MINUTE', '2000-01-01 00:00:00.000', '2000-01-01 00:00:01.000'); +> 0 0 +> - - +> 0 0 +> rows: 1 + +SELECT DATEDIFF('MINUTE', '1900-01-01 00:00:01.000', '1900-01-01 00:00:02.000'), DATEDIFF('MINUTE', '2000-01-01 00:00:01.000', '2000-01-01 00:00:02.000'); +> 0 0 +> - - +> 0 0 +> rows: 1 + +SELECT DATEDIFF('HOUR', '1900-01-01 00:00:00.000', '1900-01-01 00:00:01.000'), DATEDIFF('HOUR', '2000-01-01 00:00:00.000', '2000-01-01 00:00:01.000'); +> 0 0 +> - - +> 0 0 +> rows: 1 + +SELECT DATEDIFF('HOUR', '1900-01-01 00:00:00.001', '1900-01-01 00:00:01.000'), DATEDIFF('HOUR', '2000-01-01 00:00:00.001', '2000-01-01 00:00:01.000'); +> 0 0 +> - - +> 0 0 +> rows: 1 + +SELECT DATEDIFF('HOUR', '1900-01-01 01:00:00.000', '1900-01-01 01:00:01.000'), DATEDIFF('HOUR', '2000-01-01 01:00:00.000', '2000-01-01 01:00:01.000'); +> 0 0 +> - - +> 0 0 +> rows: 1 + +SELECT DATEDIFF('HOUR', '1900-01-01 01:00:00.001', '1900-01-01 01:00:01.000'), DATEDIFF('HOUR', '2000-01-01 01:00:00.001', '2000-01-01 01:00:01.000'); +> 0 0 +> - - +> 0 0 +> rows: 1 + +select datediff(day, '2015-12-09 23:59:00.0', '2016-01-16 23:59:00.0'), datediff(wk, '2015-12-09 23:59:00.0', '2016-01-16 23:59:00.0'); +> 38 5 +> -- - +> 38 5 +> rows: 1 + +call datediff('MS', TIMESTAMP '2001-02-03 04:05:06.789001', TIMESTAMP '2001-02-03 04:05:06.789002'); +> 0 +> - +> 0 +> rows: 1 + +call datediff('MS', TIMESTAMP '1900-01-01 00:00:01.000', TIMESTAMP '2008-01-01 00:00:00.000'); +>> 3408134399000 + +SELECT DATEDIFF('MICROSECOND', '2006-01-01 00:00:00.0000000', '2006-01-01 00:00:00.123456789'), + DATEDIFF('MCS', '2006-01-01 00:00:00.0000000', '2006-01-01 00:00:00.123456789'), + DATEDIFF('MCS', '2006-01-01 00:00:00.0000000', '2006-01-02 00:00:00.123456789'); +> 123456 123456 86400123456 +> ------ ------ ----------- +> 123456 123456 86400123456 +> rows: 1 + +SELECT DATEDIFF('NANOSECOND', '2006-01-01 00:00:00.0000000', '2006-01-01 00:00:00.123456789'), + DATEDIFF('NS', '2006-01-01 00:00:00.0000000', '2006-01-01 00:00:00.123456789'), + DATEDIFF('NS', '2006-01-01 00:00:00.0000000', '2006-01-02 00:00:00.123456789'); +> 123456789 123456789 86400123456789 +> --------- --------- -------------- +> 123456789 123456789 86400123456789 +> rows: 1 + +SELECT DATEDIFF('ISO_WEEK', DATE '2018-02-02', DATE '2018-02-03'); +>> 0 + +SELECT DATEDIFF('ISO_WEEK', DATE '2018-02-03', DATE '2018-02-04'); +>> 0 + +SELECT DATEDIFF('ISO_WEEK', DATE '2018-02-04', DATE '2018-02-05'); +>> 1 + +SELECT DATEDIFF('ISO_WEEK', DATE '2018-02-05', DATE '2018-02-06'); +>> 0 + +SELECT DATEDIFF('ISO_WEEK', DATE '1969-12-27', DATE '1969-12-28'); +>> 0 + +SELECT DATEDIFF('ISO_WEEK', DATE '1969-12-28', DATE '1969-12-29'); +>> 1 + +SELECT DATEDIFF('QUARTER', DATE '2009-12-30', DATE '2009-12-31'); +>> 0 + +SELECT DATEDIFF('QUARTER', DATE '2010-01-01', DATE '2009-12-31'); +>> -1 + +SELECT DATEDIFF('QUARTER', DATE '2010-01-01', DATE '2010-01-02'); +>> 0 + +SELECT DATEDIFF('QUARTER', DATE '2010-01-01', DATE '2010-03-31'); +>> 0 + +SELECT DATEDIFF('QUARTER', DATE '-1000-01-01', DATE '2000-01-01'); +>> 12000 + +SELECT DATEDIFF('TIMEZONE_HOUR', TIMESTAMP WITH TIME ZONE '2010-01-01 10:00:00+01', + TIMESTAMP WITH TIME ZONE '2012-02-02 12:00:00+02'); +>> 1 + +SELECT DATEDIFF('TIMEZONE_MINUTE', TIMESTAMP WITH TIME ZONE '2010-01-01 10:00:00+01:15', + TIMESTAMP WITH TIME ZONE '2012-02-02 12:00:00+02'); +>> 45 + +SELECT DATEDIFF('TIMEZONE_SECOND', TIMESTAMP WITH TIME ZONE '1880-01-01 10:00:00-07:52:58', + TIMESTAMP WITH TIME ZONE '1890-02-02 12:00:00-08'); +>> -422 + +SELECT DATEDIFF('TIMEZONE_HOUR', TIME WITH TIME ZONE '10:00:00+01', + TIME WITH TIME ZONE '12:00:00+02'); +>> 1 + +SELECT DATEDIFF('TIMEZONE_MINUTE', TIME WITH TIME ZONE '10:00:00+01:15', + TIME WITH TIME ZONE '12:00:00+02'); +>> 45 + +select datediff('HOUR', timestamp '2007-01-06 10:00:00Z', '2007-01-06 10:00:00Z'); +>> 0 + +select datediff('HOUR', timestamp '1234-05-06 10:00:00+01:00', '1234-05-06 10:00:00+02:00'); +>> -1 + +select datediff('HOUR', timestamp '1234-05-06 10:00:00+01:00', '1234-05-06 10:00:00-02:00'); +>> 3 + +select timestampdiff(month, '2003-02-01','2003-05-01'); +>> 3 + +select timestampdiff(YEAR,'2002-05-01','2001-01-01'); +>> -1 + +select timestampdiff(YEAR,'2017-01-01','2016-12-31 23:59:59'); +>> -1 + +select timestampdiff(YEAR,'2017-01-01','2017-12-31 23:59:59'); +>> 0 + +select timestampdiff(MINUTE,'2003-02-01','2003-05-01 12:05:55'); +>> 128885 + +SELECT DATEDIFF(MILLENNIUM, DATE '2000-12-31', DATE '2001-01-01'); +>> 1 + +SELECT DATEDIFF(MILLENNIUM, DATE '2001-01-01', DATE '3000-12-31'); +>> 0 + +SELECT DATEDIFF(MILLENNIUM, DATE '2001-01-01', DATE '3001-01-01'); +>> 1 + +SELECT DATEDIFF(CENTURY, DATE '2000-12-31', DATE '2001-01-01'); +>> 1 + +SELECT DATEDIFF(CENTURY, DATE '2001-01-01', DATE '2100-12-31'); +>> 0 + +SELECT DATEDIFF(CENTURY, DATE '2001-01-01', DATE '2101-01-01'); +>> 1 + +SELECT DATEDIFF(DECADE, DATE '2009-12-31', DATE '2010-01-01'); +>> 1 + +SELECT DATEDIFF(DECADE, DATE '2010-01-01', DATE '2019-12-31'); +>> 0 + +SELECT DATEDIFF(DECADE, DATE '2010-01-01', DATE '2020-01-01'); +>> 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/timeanddate/day-of-month.sql b/h2/src/test/org/h2/test/scripts/functions/timeanddate/day-of-month.sql new file mode 100644 index 0000000..609770c --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/timeanddate/day-of-month.sql @@ -0,0 +1,23 @@ +-- 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 +-- + +select dayofmonth(date '2005-09-12'); +>> 12 + +create table test(ts timestamp with time zone); +> ok + +insert into test(ts) values ('2010-05-11 00:00:00+10:00'), ('2010-05-11 00:00:00-10:00'); +> update count: 2 + +select dayofmonth(ts) d from test; +> D +> -- +> 11 +> 11 +> rows: 2 + +drop table test; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/timeanddate/day-of-week.sql b/h2/src/test/org/h2/test/scripts/functions/timeanddate/day-of-week.sql new file mode 100644 index 0000000..6e71c05 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/timeanddate/day-of-week.sql @@ -0,0 +1,7 @@ +-- 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 +-- + +SELECT DAYOFWEEK(DATE '2005-09-12') = EXTRACT(DAY_OF_WEEK FROM DATE '2005-09-12'); +>> TRUE diff --git a/h2/src/test/org/h2/test/scripts/functions/timeanddate/day-of-year.sql b/h2/src/test/org/h2/test/scripts/functions/timeanddate/day-of-year.sql new file mode 100644 index 0000000..3d7c68e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/timeanddate/day-of-year.sql @@ -0,0 +1,7 @@ +-- 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 +-- + +select dayofyear(date '2005-01-01') d1; +>> 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/timeanddate/dayname.sql b/h2/src/test/org/h2/test/scripts/functions/timeanddate/dayname.sql new file mode 100644 index 0000000..743867d --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/timeanddate/dayname.sql @@ -0,0 +1,7 @@ +-- 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 +-- + +select dayname(date '2005-09-12'); +>> Monday diff --git a/h2/src/test/org/h2/test/scripts/functions/timeanddate/extract.sql b/h2/src/test/org/h2/test/scripts/functions/timeanddate/extract.sql new file mode 100644 index 0000000..33918e9 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/timeanddate/extract.sql @@ -0,0 +1,275 @@ +-- 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 +-- + +SELECT EXTRACT(NANOSECOND FROM TIME '10:00:00.123456789') IS OF (INTEGER); +>> TRUE + +SELECT EXTRACT(EPOCH FROM TIME '01:00:00') IS OF (NUMERIC); +>> TRUE + +SELECT EXTRACT (MICROSECOND FROM TIME '10:00:00.123456789'), + EXTRACT (MCS FROM TIMESTAMP '2015-01-01 11:22:33.987654321'); +> 123456 987654 +> ------ ------ +> 123456 987654 +> rows: 1 + +SELECT EXTRACT (NANOSECOND FROM TIME '10:00:00.123456789'), + EXTRACT (NS FROM TIMESTAMP '2015-01-01 11:22:33.987654321'); +> 123456789 987654321 +> --------- --------- +> 123456789 987654321 +> rows: 1 + +select EXTRACT (EPOCH from time '00:00:00'); +>> 0 + +select EXTRACT (EPOCH from time '10:00:00'); +>> 36000 + +select EXTRACT (EPOCH from time '10:00:00.123456'); +>> 36000.123456 + +select EXTRACT (EPOCH from date '1970-01-01'); +>> 0 + +select EXTRACT (EPOCH from date '2000-01-03'); +>> 946857600 + +select EXTRACT (EPOCH from timestamp '1970-01-01 00:00:00'); +>> 0 + +select EXTRACT (EPOCH from timestamp '1970-01-03 12:00:00.123456'); +>> 216000.123456 + +select EXTRACT (EPOCH from timestamp '2000-01-03 12:00:00.123456'); +>> 946900800.123456 + +select EXTRACT (EPOCH from timestamp '2500-01-03 12:00:00.654321'); +>> 16725441600.654321 + +select EXTRACT (EPOCH from timestamp with time zone '1970-01-01 00:00:00+05'); +>> -18000 + +select EXTRACT (EPOCH from timestamp with time zone '1970-01-03 12:00:00.123456+05'); +>> 198000.123456 + +select EXTRACT (EPOCH from timestamp with time zone '2000-01-03 12:00:00.123456+05'); +>> 946882800.123456 + +select extract(EPOCH from '2001-02-03 14:15:16'); +>> 981209716 + +SELECT EXTRACT(EPOCH FROM INTERVAL '10.1' SECOND); +>> 10.1 + +SELECT EXTRACT(EPOCH FROM INTERVAL -'0.000001' SECOND); +>> -0.000001 + +SELECT EXTRACT(EPOCH FROM INTERVAL '0-1' YEAR TO MONTH); +>> 2592000 + +SELECT EXTRACT(EPOCH FROM INTERVAL '-0-1' YEAR TO MONTH); +>> -2592000 + +SELECT EXTRACT(EPOCH FROM INTERVAL '1-0' YEAR TO MONTH); +>> 31557600 + +SELECT EXTRACT(EPOCH FROM INTERVAL '-1-0' YEAR TO MONTH); +>> -31557600 + +SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2010-01-02 5:00:00+07:15'); +>> 7 + +SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2010-01-02 5:00:00-08:30'); +>> -8 + +SELECT EXTRACT(TIMEZONE_MINUTE FROM TIMESTAMP WITH TIME ZONE '2010-01-02 5:00:00+07:15'); +>> 15 + +SELECT EXTRACT(TIMEZONE_MINUTE FROM TIMESTAMP WITH TIME ZONE '2010-01-02 5:00:00-08:30'); +>> -30 + +SELECT EXTRACT(TIMEZONE_SECOND FROM TIMESTAMP WITH TIME ZONE '1880-01-01 10:00:00-07:52:58'); +>> -58 + +SELECT EXTRACT(TIMEZONE_HOUR FROM TIME WITH TIME ZONE '5:00:00+07:15'); +>> 7 + +SELECT EXTRACT(TIMEZONE_MINUTE FROM TIME WITH TIME ZONE '5:00:00+07:15'); +>> 15 + +select extract(hour from timestamp '2001-02-03 14:15:16'); +>> 14 + +select extract(hour from '2001-02-03 14:15:16'); +>> 14 + +SELECT EXTRACT(YEAR FROM INTERVAL '-1' YEAR); +>> -1 + +SELECT EXTRACT(YEAR FROM INTERVAL '1-2' YEAR TO MONTH); +>> 1 + +SELECT EXTRACT(MONTH FROM INTERVAL '-1-3' YEAR TO MONTH); +>> -3 + +SELECT EXTRACT(MONTH FROM INTERVAL '3' MONTH); +>> 3 + +SELECT EXTRACT(DAY FROM INTERVAL '1100' DAY); +>> 1100 + +SELECT EXTRACT(DAY FROM INTERVAL '10 23' DAY TO HOUR); +>> 10 + +SELECT EXTRACT(DAY FROM INTERVAL '10 23:15' DAY TO MINUTE); +>> 10 + +SELECT EXTRACT(DAY FROM INTERVAL '10 23:15:30' DAY TO SECOND); +>> 10 + +SELECT EXTRACT(HOUR FROM INTERVAL '15' HOUR); +>> 15 + +SELECT EXTRACT(HOUR FROM INTERVAL '2 15' DAY TO HOUR); +>> 15 + +SELECT EXTRACT(HOUR FROM INTERVAL '2 10:30' DAY TO MINUTE); +>> 10 + +SELECT EXTRACT(HOUR FROM INTERVAL '2 10:30:15' DAY TO SECOND); +>> 10 + +SELECT EXTRACT(HOUR FROM INTERVAL '20:10' HOUR TO MINUTE); +>> 20 + +SELECT EXTRACT(HOUR FROM INTERVAL '20:10:22' HOUR TO SECOND); +>> 20 + +SELECT EXTRACT(MINUTE FROM INTERVAL '-35' MINUTE); +>> -35 + +SELECT EXTRACT(MINUTE FROM INTERVAL '1 20:33' DAY TO MINUTE); +>> 33 + +SELECT EXTRACT(MINUTE FROM INTERVAL '1 20:33:10' DAY TO SECOND); +>> 33 + +SELECT EXTRACT(MINUTE FROM INTERVAL '20:34' HOUR TO MINUTE); +>> 34 + +SELECT EXTRACT(MINUTE FROM INTERVAL '20:34:10' HOUR TO SECOND); +>> 34 + +SELECT EXTRACT(MINUTE FROM INTERVAL '-34:10' MINUTE TO SECOND); +>> -34 + +SELECT EXTRACT(SECOND FROM INTERVAL '-100' SECOND); +>> -100 + +SELECT EXTRACT(SECOND FROM INTERVAL '10 11:22:33' DAY TO SECOND); +>> 33 + +SELECT EXTRACT(SECOND FROM INTERVAL '1:2:3' HOUR TO SECOND); +>> 3 + +SELECT EXTRACT(SECOND FROM INTERVAL '-2:43' MINUTE TO SECOND); +>> -43 + +SELECT EXTRACT(SECOND FROM INTERVAL '11.123456789' SECOND); +>> 11 + +SELECT EXTRACT(MILLISECOND FROM INTERVAL '11.123456789' SECOND); +>> 123 + +SELECT EXTRACT(MICROSECOND FROM INTERVAL '11.123456789' SECOND); +>> 123456 + +SELECT EXTRACT(NANOSECOND FROM INTERVAL '11.123456789' SECOND); +>> 123456789 + +SELECT D, ISO_YEAR(D) Y1, EXTRACT(ISO_WEEK_YEAR FROM D) Y2, EXTRACT(ISO_YEAR FROM D) Y3, EXTRACT(ISOYEAR FROM D) Y4 + FROM (VALUES DATE '2017-01-01', DATE '2017-01-02') V(D); +> D Y1 Y2 Y3 Y4 +> ---------- ---- ---- ---- ---- +> 2017-01-01 2016 2016 2016 2016 +> 2017-01-02 2017 2017 2017 2017 +> rows: 2 + +SELECT D, EXTRACT(ISO_DAY_OF_WEEK FROM D) D1, EXTRACT(ISODOW FROM D) D2 + FROM (VALUES DATE '2019-02-03', DATE '2019-02-04') V(D); +> D D1 D2 +> ---------- -- -- +> 2019-02-03 7 7 +> 2019-02-04 1 1 +> rows: 2 + +SET MODE PostgreSQL; +> ok + +SELECT D, EXTRACT(DOW FROM D) D3 FROM (VALUES DATE '2019-02-02', DATE '2019-02-03') V(D); +> D D3 +> ---------- -- +> 2019-02-02 6 +> 2019-02-03 0 +> rows: 2 + +SET MODE Regular; +> ok + +SELECT EXTRACT(MILLENNIUM FROM DATE '-1000-12-31'); +>> -1 + +SELECT EXTRACT(MILLENNIUM FROM DATE '-999-01-01'); +>> 0 + +SELECT EXTRACT(MILLENNIUM FROM DATE '0000-12-31'); +>> 0 + +SELECT EXTRACT(MILLENNIUM FROM DATE '0001-01-01'); +>> 1 + +SELECT EXTRACT(MILLENNIUM FROM DATE '1000-12-31'); +>> 1 + +SELECT EXTRACT(MILLENNIUM FROM DATE '1001-01-01'); +>> 2 + +SELECT EXTRACT(CENTURY FROM DATE '-100-12-31'); +>> -1 + +SELECT EXTRACT(CENTURY FROM DATE '-99-01-01'); +>> 0 + +SELECT EXTRACT(CENTURY FROM DATE '0000-12-31'); +>> 0 + +SELECT EXTRACT(CENTURY FROM DATE '0001-01-01'); +>> 1 + +SELECT EXTRACT(CENTURY FROM DATE '0100-12-31'); +>> 1 + +SELECT EXTRACT(CENTURY FROM DATE '0101-01-01'); +>> 2 + +SELECT EXTRACT(DECADE FROM DATE '-11-12-31'); +>> -2 + +SELECT EXTRACT(DECADE FROM DATE '-10-01-01'); +>> -1 + +SELECT EXTRACT(DECADE FROM DATE '-1-12-31'); +>> -1 + +SELECT EXTRACT(DECADE FROM DATE '0000-01-01'); +>> 0 + +SELECT EXTRACT(DECADE FROM DATE '0009-12-31'); +>> 0 + +SELECT EXTRACT(DECADE FROM DATE '0010-01-01'); +>> 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/timeanddate/formatdatetime.sql b/h2/src/test/org/h2/test/scripts/functions/timeanddate/formatdatetime.sql new file mode 100644 index 0000000..dd3e270 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/timeanddate/formatdatetime.sql @@ -0,0 +1,25 @@ +-- 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 +-- + +CALL FORMATDATETIME(PARSEDATETIME('2001-02-03 04:05:06 GMT', 'yyyy-MM-dd HH:mm:ss z', 'en', 'GMT'), 'EEE, d MMM yyyy HH:mm:ss z', 'en', 'GMT'); +>> Sat, 3 Feb 2001 04:05:06 GMT + +CALL FORMATDATETIME(TIMESTAMP '2001-02-03 04:05:06', 'yyyy-MM-dd HH:mm:ss'); +>> 2001-02-03 04:05:06 + +CALL FORMATDATETIME(TIMESTAMP '2001-02-03 04:05:06', 'MM/dd/yyyy HH:mm:ss'); +>> 02/03/2001 04:05:06 + +CALL FORMATDATETIME(TIMESTAMP '2001-02-03 04:05:06', 'd. MMMM yyyy', 'de'); +>> 3. Februar 2001 + +CALL FORMATDATETIME(PARSEDATETIME('Sat, 3 Feb 2001 04:05:06 GMT', 'EEE, d MMM yyyy HH:mm:ss z', 'en', 'GMT'), 'yyyy-MM-dd HH:mm:ss', 'en', 'GMT'); +>> 2001-02-03 04:05:06 + +SELECT FORMATDATETIME(TIMESTAMP WITH TIME ZONE '2010-05-06 07:08:09.123Z', 'yyyy-MM-dd HH:mm:ss.SSS z'); +>> 2010-05-06 07:08:09.123 UTC + +SELECT FORMATDATETIME(TIMESTAMP WITH TIME ZONE '2010-05-06 07:08:09.123+13:30', 'yyyy-MM-dd HH:mm:ss.SSS z'); +>> 2010-05-06 07:08:09.123 GMT+13:30 diff --git a/h2/src/test/org/h2/test/scripts/functions/timeanddate/hour.sql b/h2/src/test/org/h2/test/scripts/functions/timeanddate/hour.sql new file mode 100644 index 0000000..b008282 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/timeanddate/hour.sql @@ -0,0 +1,26 @@ +-- 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 +-- + +select hour(time '23:10:59'); +>> 23 + +create table test(ts timestamp with time zone); +> ok + +insert into test(ts) values ('2010-05-11 05:15:10+10:00'), ('2010-05-11 05:15:10-10:00'); +> update count: 2 + +select hour(ts) h from test; +> H +> - +> 5 +> 5 +> rows: 2 + +drop table test; +> ok + +select hour('2001-02-03 14:15:16'); +>> 14 diff --git a/h2/src/test/org/h2/test/scripts/functions/timeanddate/minute.sql b/h2/src/test/org/h2/test/scripts/functions/timeanddate/minute.sql new file mode 100644 index 0000000..8cf533c --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/timeanddate/minute.sql @@ -0,0 +1,7 @@ +-- 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 +-- + +select minute(timestamp '2005-01-01 23:10:59'); +>> 10 diff --git a/h2/src/test/org/h2/test/scripts/functions/timeanddate/month.sql b/h2/src/test/org/h2/test/scripts/functions/timeanddate/month.sql new file mode 100644 index 0000000..e85be36 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/timeanddate/month.sql @@ -0,0 +1,7 @@ +-- 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 +-- + +select month(date '2005-09-25'); +>> 9 diff --git a/h2/src/test/org/h2/test/scripts/functions/timeanddate/monthname.sql b/h2/src/test/org/h2/test/scripts/functions/timeanddate/monthname.sql new file mode 100644 index 0000000..a8e6637 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/timeanddate/monthname.sql @@ -0,0 +1,7 @@ +-- 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 +-- + +select monthname(date '2005-09-12'); +>> September diff --git a/h2/src/test/org/h2/test/scripts/functions/timeanddate/parsedatetime.sql b/h2/src/test/org/h2/test/scripts/functions/timeanddate/parsedatetime.sql new file mode 100644 index 0000000..4c31dc5 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/timeanddate/parsedatetime.sql @@ -0,0 +1,22 @@ +-- 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 +-- + +SET TIME ZONE '01:00'; +> ok + +CALL PARSEDATETIME('3. Februar 2001', 'd. MMMM yyyy', 'de'); +>> 2001-02-03 00:00:00+01 + +CALL PARSEDATETIME('02/03/2001 04:05:06', 'MM/dd/yyyy HH:mm:ss'); +>> 2001-02-03 04:05:06+01 + +CALL CAST(PARSEDATETIME('10:11:12', 'HH:mm:ss', 'en') AS TIME); +>> 10:11:12 + +CALL CAST(PARSEDATETIME('10:11:12', 'HH:mm:ss', 'en', 'GMT+2') AS TIME WITH TIME ZONE); +>> 10:11:12+02 + +SET TIME ZONE LOCAL; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/timeanddate/quarter.sql b/h2/src/test/org/h2/test/scripts/functions/timeanddate/quarter.sql new file mode 100644 index 0000000..b19ae40 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/timeanddate/quarter.sql @@ -0,0 +1,7 @@ +-- 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 +-- + +select quarter(date '2005-09-01'); +>> 3 diff --git a/h2/src/test/org/h2/test/scripts/functions/timeanddate/second.sql b/h2/src/test/org/h2/test/scripts/functions/timeanddate/second.sql new file mode 100644 index 0000000..01243ba --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/timeanddate/second.sql @@ -0,0 +1,7 @@ +-- 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 +-- + +select second(timestamp '2005-01-01 23:10:59'); +>> 59 diff --git a/h2/src/test/org/h2/test/scripts/functions/timeanddate/truncate.sql b/h2/src/test/org/h2/test/scripts/functions/timeanddate/truncate.sql new file mode 100644 index 0000000..3a28b9b --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/timeanddate/truncate.sql @@ -0,0 +1,16 @@ +-- 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 +-- + +select trunc('2015-05-29 15:00:00'); +>> 2015-05-29 00:00:00 + +select trunc('2015-05-29'); +>> 2015-05-29 00:00:00 + +select trunc(timestamp '2000-01-01 10:20:30.0'); +>> 2000-01-01 00:00:00 + +select trunc(timestamp '2001-01-01 14:00:00.0'); +>> 2001-01-01 00:00:00 diff --git a/h2/src/test/org/h2/test/scripts/functions/timeanddate/week.sql b/h2/src/test/org/h2/test/scripts/functions/timeanddate/week.sql new file mode 100644 index 0000000..3d902ea --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/timeanddate/week.sql @@ -0,0 +1,12 @@ +-- 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 +-- + +-- ISO_WEEK + +select iso_week('2006-12-31') w, iso_year('2007-12-31') y, iso_day_of_week('2007-12-31') w; +> W Y W +> -- ---- - +> 52 2008 1 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/functions/timeanddate/year.sql b/h2/src/test/org/h2/test/scripts/functions/timeanddate/year.sql new file mode 100644 index 0000000..25dea91 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/timeanddate/year.sql @@ -0,0 +1,7 @@ +-- 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 +-- + +select year(date '2005-01-01'); +>> 2005 diff --git a/h2/src/test/org/h2/test/scripts/functions/window/lead.sql b/h2/src/test/org/h2/test/scripts/functions/window/lead.sql new file mode 100644 index 0000000..947849a --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/window/lead.sql @@ -0,0 +1,181 @@ +-- 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 +-- + +CREATE TABLE TEST (ID INT PRIMARY KEY, "VALUE" INT); +> ok + +INSERT INTO TEST VALUES + (1, NULL), + (2, 12), + (3, NULL), + (4, 13), + (5, NULL), + (6, 21), + (7, 22), + (8, 33), + (9, NULL); +> update count: 9 + +SELECT *, + LEAD("VALUE") OVER (ORDER BY ID) LD, + LEAD("VALUE") RESPECT NULLS OVER (ORDER BY ID) LD_N, + LEAD("VALUE") IGNORE NULLS OVER (ORDER BY ID) LD_NN, + LAG("VALUE") OVER (ORDER BY ID) LG, + LAG("VALUE") RESPECT NULLS OVER (ORDER BY ID) LG_N, + LAG("VALUE") IGNORE NULLS OVER (ORDER BY ID) LG_NN + FROM TEST; +> ID VALUE LD LD_N LD_NN LG LG_N LG_NN +> -- ----- ---- ---- ----- ---- ---- ----- +> 1 null 12 12 12 null null null +> 2 12 null null 13 null null null +> 3 null 13 13 13 12 12 12 +> 4 13 null null 21 null null 12 +> 5 null 21 21 21 13 13 13 +> 6 21 22 22 22 null null 13 +> 7 22 33 33 33 21 21 21 +> 8 33 null null null 22 22 22 +> 9 null null null null 33 33 33 +> rows: 9 + +SELECT *, + LEAD("VALUE", 1) OVER (ORDER BY ID) LD, + LEAD("VALUE", 1) RESPECT NULLS OVER (ORDER BY ID) LD_N, + LEAD("VALUE", 1) IGNORE NULLS OVER (ORDER BY ID) LD_NN, + LAG("VALUE", 1) OVER (ORDER BY ID) LG, + LAG("VALUE", 1) RESPECT NULLS OVER (ORDER BY ID) LG_N, + LAG("VALUE", 1) IGNORE NULLS OVER (ORDER BY ID) LG_NN + FROM TEST; +> ID VALUE LD LD_N LD_NN LG LG_N LG_NN +> -- ----- ---- ---- ----- ---- ---- ----- +> 1 null 12 12 12 null null null +> 2 12 null null 13 null null null +> 3 null 13 13 13 12 12 12 +> 4 13 null null 21 null null 12 +> 5 null 21 21 21 13 13 13 +> 6 21 22 22 22 null null 13 +> 7 22 33 33 33 21 21 21 +> 8 33 null null null 22 22 22 +> 9 null null null null 33 33 33 +> rows: 9 + +SELECT *, + LEAD("VALUE", 0) OVER (ORDER BY ID) LD, + LEAD("VALUE", 0) RESPECT NULLS OVER (ORDER BY ID) LD_N, + LEAD("VALUE", 0) IGNORE NULLS OVER (ORDER BY ID) LD_NN, + LAG("VALUE", 0) OVER (ORDER BY ID) LG, + LAG("VALUE", 0) RESPECT NULLS OVER (ORDER BY ID) LG_N, + LAG("VALUE", 0) IGNORE NULLS OVER (ORDER BY ID) LG_NN + FROM TEST; +> ID VALUE LD LD_N LD_NN LG LG_N LG_NN +> -- ----- ---- ---- ----- ---- ---- ----- +> 1 null null null null null null null +> 2 12 12 12 12 12 12 12 +> 3 null null null null null null null +> 4 13 13 13 13 13 13 13 +> 5 null null null null null null null +> 6 21 21 21 21 21 21 21 +> 7 22 22 22 22 22 22 22 +> 8 33 33 33 33 33 33 33 +> 9 null null null null null null null +> rows: 9 + +SELECT *, + LEAD("VALUE", 2) OVER (ORDER BY ID) LD, + LEAD("VALUE", 2) RESPECT NULLS OVER (ORDER BY ID) LD_N, + LEAD("VALUE", 2) IGNORE NULLS OVER (ORDER BY ID) LD_NN, + LAG("VALUE", 2) OVER (ORDER BY ID) LG, + LAG("VALUE", 2) RESPECT NULLS OVER (ORDER BY ID) LG_N, + LAG("VALUE", 2) IGNORE NULLS OVER (ORDER BY ID) LG_NN + FROM TEST; +> ID VALUE LD LD_N LD_NN LG LG_N LG_NN +> -- ----- ---- ---- ----- ---- ---- ----- +> 1 null null null 13 null null null +> 2 12 13 13 21 null null null +> 3 null null null 21 null null null +> 4 13 21 21 22 12 12 null +> 5 null 22 22 22 null null 12 +> 6 21 33 33 33 13 13 12 +> 7 22 null null null null null 13 +> 8 33 null null null 21 21 21 +> 9 null null null null 22 22 22 +> rows: 9 + +SELECT *, + LEAD("VALUE", 2, 1111.0) OVER (ORDER BY ID) LD, + LEAD("VALUE", 2, 1111.0) RESPECT NULLS OVER (ORDER BY ID) LD_N, + LEAD("VALUE", 2, 1111.0) IGNORE NULLS OVER (ORDER BY ID) LD_NN, + LAG("VALUE", 2, 1111.0) OVER (ORDER BY ID) LG, + LAG("VALUE", 2, 1111.0) RESPECT NULLS OVER (ORDER BY ID) LG_N, + LAG("VALUE", 2, 1111.0) IGNORE NULLS OVER (ORDER BY ID) LG_NN + FROM TEST; +> ID VALUE LD LD_N LD_NN LG LG_N LG_NN +> -- ----- ---- ---- ----- ---- ---- ----- +> 1 null null null 13 1111 1111 1111 +> 2 12 13 13 21 1111 1111 1111 +> 3 null null null 21 null null 1111 +> 4 13 21 21 22 12 12 1111 +> 5 null 22 22 22 null null 12 +> 6 21 33 33 33 13 13 12 +> 7 22 null null 1111 null null 13 +> 8 33 1111 1111 1111 21 21 21 +> 9 null 1111 1111 1111 22 22 22 +> rows: 9 + +SELECT LEAD("VALUE", -1) OVER (ORDER BY ID) FROM TEST; +> exception INVALID_VALUE_2 + +SELECT LAG("VALUE", -1) OVER (ORDER BY ID) FROM TEST; +> exception INVALID_VALUE_2 + +SELECT LEAD("VALUE") OVER () FROM TEST; +> exception SYNTAX_ERROR_2 + +SELECT LAG("VALUE") OVER () FROM TEST; +> exception SYNTAX_ERROR_2 + +SELECT LEAD("VALUE") OVER (ORDER BY ID RANGE CURRENT ROW) FROM TEST; +> exception SYNTAX_ERROR_1 + +SELECT LAG("VALUE") OVER (ORDER BY ID RANGE CURRENT ROW) FROM TEST; +> exception SYNTAX_ERROR_1 + +DROP TABLE TEST; +> ok + +SELECT C, SUM(I) S, LEAD(SUM(I)) OVER (ORDER BY SUM(I)) L FROM + VALUES (1, 1), (2, 1), (4, 2), (8, 2) T(I, C) GROUP BY C; +> C S L +> - -- ---- +> 1 3 12 +> 2 12 null +> rows: 2 + +CREATE TABLE TEST(X INT) AS VALUES 1, 2, 3; +> ok + +EXPLAIN SELECT LEAD(X) OVER (ORDER BY 'a') FROM TEST; +>> SELECT LEAD("X") OVER (ORDER BY NULL) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +SELECT LEAD(X) OVER (ORDER BY 'a') FROM TEST; +> LEAD(X) OVER (ORDER BY NULL) +> ---------------------------- +> 2 +> 3 +> null +> rows: 3 + +EXPLAIN SELECT LAG(X) OVER (ORDER BY 'a') FROM TEST; +>> SELECT LAG("X") OVER (ORDER BY NULL) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +SELECT LAG(X) OVER (ORDER BY 'a') FROM TEST; +> LAG(X) OVER (ORDER BY NULL) +> --------------------------- +> 1 +> 2 +> null +> rows: 3 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/window/nth_value.sql b/h2/src/test/org/h2/test/scripts/functions/window/nth_value.sql new file mode 100644 index 0000000..57fea99 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/window/nth_value.sql @@ -0,0 +1,263 @@ +-- 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 +-- + +SELECT FIRST_VALUE(1) OVER (PARTITION BY ID); +> exception COLUMN_NOT_FOUND_1 + +SELECT FIRST_VALUE(1) OVER (ORDER BY ID); +> exception COLUMN_NOT_FOUND_1 + +CREATE TABLE TEST (ID INT PRIMARY KEY, CATEGORY INT, "VALUE" INT); +> ok + +INSERT INTO TEST VALUES + (1, 1, NULL), + (2, 1, 12), + (3, 1, NULL), + (4, 1, 13), + (5, 1, NULL), + (6, 1, 13), + (7, 2, 21), + (8, 2, 22), + (9, 3, 31), + (10, 3, 32), + (11, 3, 33), + (12, 4, 41), + (13, 4, NULL); +> update count: 13 + +SELECT *, + FIRST_VALUE("VALUE") OVER (ORDER BY ID) FIRST, + FIRST_VALUE("VALUE") RESPECT NULLS OVER (ORDER BY ID) FIRST_N, + FIRST_VALUE("VALUE") IGNORE NULLS OVER (ORDER BY ID) FIRST_NN, + LAST_VALUE("VALUE") OVER (ORDER BY ID) LAST, + LAST_VALUE("VALUE") RESPECT NULLS OVER (ORDER BY ID) LAST_N, + LAST_VALUE("VALUE") IGNORE NULLS OVER (ORDER BY ID) LAST_NN + FROM TEST FETCH FIRST 6 ROWS ONLY; +> ID CATEGORY VALUE FIRST FIRST_N FIRST_NN LAST LAST_N LAST_NN +> -- -------- ----- ----- ------- -------- ---- ------ ------- +> 1 1 null null null null null null null +> 2 1 12 null null 12 12 12 12 +> 3 1 null null null 12 null null 12 +> 4 1 13 null null 12 13 13 13 +> 5 1 null null null 12 null null 13 +> 6 1 13 null null 12 13 13 13 +> rows: 6 + +SELECT *, + FIRST_VALUE("VALUE") OVER (ORDER BY ID) FIRST, + FIRST_VALUE("VALUE") RESPECT NULLS OVER (ORDER BY ID) FIRST_N, + FIRST_VALUE("VALUE") IGNORE NULLS OVER (ORDER BY ID) FIRST_NN, + LAST_VALUE("VALUE") OVER (ORDER BY ID) LAST, + LAST_VALUE("VALUE") RESPECT NULLS OVER (ORDER BY ID) LAST_N, + LAST_VALUE("VALUE") IGNORE NULLS OVER (ORDER BY ID) LAST_NN + FROM TEST WHERE ID > 1 FETCH FIRST 3 ROWS ONLY; +> ID CATEGORY VALUE FIRST FIRST_N FIRST_NN LAST LAST_N LAST_NN +> -- -------- ----- ----- ------- -------- ---- ------ ------- +> 2 1 12 12 12 12 12 12 12 +> 3 1 null 12 12 12 null null 12 +> 4 1 13 12 12 12 13 13 13 +> rows: 3 + +SELECT *, + NTH_VALUE("VALUE", 2) OVER (ORDER BY ID) NTH, + NTH_VALUE("VALUE", 2) FROM FIRST OVER (ORDER BY ID) NTH_FF, + NTH_VALUE("VALUE", 2) FROM LAST OVER (ORDER BY ID) NTH_FL, + NTH_VALUE("VALUE", 2) RESPECT NULLS OVER (ORDER BY ID) NTH_N, + NTH_VALUE("VALUE", 2) FROM FIRST RESPECT NULLS OVER (ORDER BY ID) NTH_FF_N, + NTH_VALUE("VALUE", 2) FROM LAST RESPECT NULLS OVER (ORDER BY ID) NTH_FL_N, + NTH_VALUE("VALUE", 2) IGNORE NULLS OVER (ORDER BY ID) NTH_NN, + NTH_VALUE("VALUE", 2) FROM FIRST IGNORE NULLS OVER (ORDER BY ID) NTH_FF_NN, + NTH_VALUE("VALUE", 2) FROM LAST IGNORE NULLS OVER (ORDER BY ID) NTH_FL_NN + FROM TEST FETCH FIRST 6 ROWS ONLY; +> ID CATEGORY VALUE NTH NTH_FF NTH_FL NTH_N NTH_FF_N NTH_FL_N NTH_NN NTH_FF_NN NTH_FL_NN +> -- -------- ----- ---- ------ ------ ----- -------- -------- ------ --------- --------- +> 1 1 null null null null null null null null null null +> 2 1 12 12 12 null 12 12 null null null null +> 3 1 null 12 12 12 12 12 12 null null null +> 4 1 13 12 12 null 12 12 null 13 13 12 +> 5 1 null 12 12 13 12 12 13 13 13 12 +> 6 1 13 12 12 null 12 12 null 13 13 13 +> rows: 6 + +SELECT *, + NTH_VALUE("VALUE", 2) OVER(ORDER BY ID) F, + NTH_VALUE("VALUE", 2) OVER(ORDER BY ID RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) F_U_C, + NTH_VALUE("VALUE", 2) OVER(ORDER BY ID RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) F_C_U, + NTH_VALUE("VALUE", 2) OVER(ORDER BY ID RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) F_U_U, + NTH_VALUE("VALUE", 2) FROM LAST OVER(ORDER BY ID) L, + NTH_VALUE("VALUE", 2) FROM LAST OVER(ORDER BY ID RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) L_U_C, + NTH_VALUE("VALUE", 2) FROM LAST OVER(ORDER BY ID RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) L_C_U, + NTH_VALUE("VALUE", 2) FROM LAST OVER(ORDER BY ID RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) L_U_U + FROM TEST ORDER BY ID; +> ID CATEGORY VALUE F F_U_C F_C_U F_U_U L L_U_C L_C_U L_U_U +> -- -------- ----- ---- ----- ----- ----- ---- ----- ----- ----- +> 1 1 null null null 12 12 null null 41 41 +> 2 1 12 12 12 null 12 null null 41 41 +> 3 1 null 12 12 13 12 12 12 41 41 +> 4 1 13 12 12 null 12 null null 41 41 +> 5 1 null 12 12 13 12 13 13 41 41 +> 6 1 13 12 12 21 12 null null 41 41 +> 7 2 21 12 12 22 12 13 13 41 41 +> 8 2 22 12 12 31 12 21 21 41 41 +> 9 3 31 12 12 32 12 22 22 41 41 +> 10 3 32 12 12 33 12 31 31 41 41 +> 11 3 33 12 12 41 12 32 32 41 41 +> 12 4 41 12 12 null 12 33 33 41 41 +> 13 4 null 12 12 null 12 41 41 null 41 +> rows (ordered): 13 + +SELECT NTH_VALUE("VALUE", 0) OVER (ORDER BY ID) FROM TEST; +> exception INVALID_VALUE_2 + +SELECT *, + FIRST_VALUE("VALUE") OVER (PARTITION BY CATEGORY ORDER BY ID) FIRST, + LAST_VALUE("VALUE") OVER (PARTITION BY CATEGORY ORDER BY ID) LAST, + NTH_VALUE("VALUE", 2) OVER (PARTITION BY CATEGORY ORDER BY ID) NTH + FROM TEST ORDER BY ID; +> ID CATEGORY VALUE FIRST LAST NTH +> -- -------- ----- ----- ---- ---- +> 1 1 null null null null +> 2 1 12 null 12 12 +> 3 1 null null null 12 +> 4 1 13 null 13 12 +> 5 1 null null null 12 +> 6 1 13 null 13 12 +> 7 2 21 21 21 null +> 8 2 22 21 22 22 +> 9 3 31 31 31 null +> 10 3 32 31 32 32 +> 11 3 33 31 33 32 +> 12 4 41 41 41 null +> 13 4 null 41 null null +> rows (ordered): 13 + +SELECT ID, CATEGORY, + NTH_VALUE(CATEGORY, 2) OVER (ORDER BY CATEGORY ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) C, + NTH_VALUE(CATEGORY, 2) OVER (ORDER BY CATEGORY ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE CURRENT ROW) + FROM TEST FETCH FIRST 3 ROWS ONLY; +> ID CATEGORY C NTH_VALUE(CATEGORY, 2) OVER (ORDER BY CATEGORY ROWS UNBOUNDED PRECEDING EXCLUDE CURRENT ROW) +> -- -------- ---- -------------------------------------------------------------------------------------------- +> 1 1 null null +> 2 1 1 null +> 3 1 1 1 +> rows: 3 + +SELECT ID, CATEGORY, + NTH_VALUE(CATEGORY, 2) FROM LAST OVER (ORDER BY CATEGORY RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) C2, + NTH_VALUE(CATEGORY, 3) FROM LAST OVER (ORDER BY CATEGORY RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) C3, + NTH_VALUE(CATEGORY, 2) FROM LAST OVER (ORDER BY CATEGORY RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW) + FROM TEST OFFSET 10 ROWS; +> ID CATEGORY C2 C3 NTH_VALUE(CATEGORY, 2) FROM LAST OVER (ORDER BY CATEGORY RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW) +> -- -------- -- ---- ------------------------------------------------------------------------------------------------------------------------------- +> 11 3 4 3 4 +> 12 4 4 null null +> 13 4 4 null null +> rows: 3 + +SELECT ID, CATEGORY, + NTH_VALUE(CATEGORY, 2) FROM LAST OVER (ORDER BY CATEGORY RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW) C + FROM TEST OFFSET 10 ROWS; +> ID CATEGORY C +> -- -------- - +> 11 3 4 +> 12 4 3 +> 13 4 3 +> rows: 3 + +SELECT ID, CATEGORY, + NTH_VALUE(CATEGORY, 1) OVER (ORDER BY CATEGORY RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE GROUP) F1, + NTH_VALUE(CATEGORY, 2) OVER (ORDER BY CATEGORY RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE GROUP) F2, + NTH_VALUE(CATEGORY, 5) OVER (ORDER BY CATEGORY RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE GROUP) F5, + NTH_VALUE(CATEGORY, 5) FROM LAST OVER (ORDER BY CATEGORY RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE GROUP) L5, + NTH_VALUE(CATEGORY, 2) FROM LAST OVER (ORDER BY CATEGORY RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE GROUP) L2, + NTH_VALUE(CATEGORY, 1) FROM LAST OVER (ORDER BY CATEGORY RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE GROUP) L1 + FROM TEST ORDER BY ID; +> ID CATEGORY F1 F2 F5 L5 L2 L1 +> -- -------- -- -- -- -- -- -- +> 1 1 2 2 3 3 4 4 +> 2 1 2 2 3 3 4 4 +> 3 1 2 2 3 3 4 4 +> 4 1 2 2 3 3 4 4 +> 5 1 2 2 3 3 4 4 +> 6 1 2 2 3 3 4 4 +> 7 2 1 1 1 3 4 4 +> 8 2 1 1 1 3 4 4 +> 9 3 1 1 1 1 4 4 +> 10 3 1 1 1 1 4 4 +> 11 3 1 1 1 1 4 4 +> 12 4 1 1 1 2 3 3 +> 13 4 1 1 1 2 3 3 +> rows (ordered): 13 + +SELECT ID, CATEGORY, + NTH_VALUE(CATEGORY, 1) OVER (ORDER BY CATEGORY RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE TIES) F1, + NTH_VALUE(CATEGORY, 2) OVER (ORDER BY CATEGORY RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE TIES) F2, + NTH_VALUE(CATEGORY, 5) OVER (ORDER BY CATEGORY RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE TIES) F5, + NTH_VALUE(CATEGORY, 5) FROM LAST OVER (ORDER BY CATEGORY RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE TIES) L5, + NTH_VALUE(CATEGORY, 2) FROM LAST OVER (ORDER BY CATEGORY RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE TIES) L2, + NTH_VALUE(CATEGORY, 1) FROM LAST OVER (ORDER BY CATEGORY RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE TIES) L1 + FROM TEST ORDER BY ID; +> ID CATEGORY F1 F2 F5 L5 L2 L1 +> -- -------- -- -- -- -- -- -- +> 1 1 1 2 3 3 4 4 +> 2 1 1 2 3 3 4 4 +> 3 1 1 2 3 3 4 4 +> 4 1 1 2 3 3 4 4 +> 5 1 1 2 3 3 4 4 +> 6 1 1 2 3 3 4 4 +> 7 2 1 1 1 3 4 4 +> 8 2 1 1 1 3 4 4 +> 9 3 1 1 1 2 4 4 +> 10 3 1 1 1 2 4 4 +> 11 3 1 1 1 2 4 4 +> 12 4 1 1 1 2 3 4 +> 13 4 1 1 1 2 3 4 +> rows (ordered): 13 + +SELECT ID, CATEGORY, + FIRST_VALUE(ID) OVER (ORDER BY ID ROWS BETWEEN CATEGORY FOLLOWING AND UNBOUNDED FOLLOWING) F, + LAST_VALUE(ID) OVER (ORDER BY ID ROWS BETWEEN CURRENT ROW AND CATEGORY FOLLOWING) L, + NTH_VALUE(ID, 2) OVER (ORDER BY ID ROWS BETWEEN CATEGORY FOLLOWING AND UNBOUNDED FOLLOWING) N + FROM TEST ORDER BY ID; +> ID CATEGORY F L N +> -- -------- ---- -- ---- +> 1 1 2 2 3 +> 2 1 3 3 4 +> 3 1 4 4 5 +> 4 1 5 5 6 +> 5 1 6 6 7 +> 6 1 7 7 8 +> 7 2 9 9 10 +> 8 2 10 10 11 +> 9 3 12 12 13 +> 10 3 13 13 null +> 11 3 null 13 null +> 12 4 null 13 null +> 13 4 null 13 null +> rows (ordered): 13 + +DROP TABLE TEST; +> ok + +SELECT I, X, LAST_VALUE(I) OVER (ORDER BY X) L FROM VALUES (1, 1), (2, 1), (3, 2), (4, 2), (5, 3) V(I, X); +> I X L +> - - - +> 1 1 2 +> 2 1 2 +> 3 2 4 +> 4 2 4 +> 5 3 5 +> rows: 5 + +SELECT A, MAX(B) M, FIRST_VALUE(A) OVER (ORDER BY A ROWS BETWEEN MAX(B) - 1 FOLLOWING AND UNBOUNDED FOLLOWING) F + FROM VALUES (1, 1), (1, 1), (2, 1), (2, 2), (3, 1) V(A, B) + GROUP BY A; +> A M F +> - - - +> 1 1 1 +> 2 2 3 +> 3 1 3 +> rows: 3 diff --git a/h2/src/test/org/h2/test/scripts/functions/window/ntile.sql b/h2/src/test/org/h2/test/scripts/functions/window/ntile.sql new file mode 100644 index 0000000..6367c2d --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/window/ntile.sql @@ -0,0 +1,129 @@ +-- 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 +-- + +SELECT NTILE(1) OVER (ORDER BY X) FROM (SELECT * FROM SYSTEM_RANGE(1, 1)); +>> 1 + +SELECT NTILE(2) OVER (ORDER BY X) FROM (SELECT * FROM SYSTEM_RANGE(1, 1)); +>> 1 + +SELECT NTILE(3) OVER (ORDER BY X) FROM (SELECT * FROM SYSTEM_RANGE(1, 1)); +>> 1 + +SELECT NTILE(1) OVER (ORDER BY X) FROM (SELECT * FROM SYSTEM_RANGE(1, 2)); +> NTILE(1) OVER (ORDER BY X) +> -------------------------- +> 1 +> 1 +> rows: 2 + +SELECT NTILE(2) OVER (ORDER BY X) FROM (SELECT * FROM SYSTEM_RANGE(1, 2)) ORDER BY X; +> NTILE(2) OVER (ORDER BY X) +> -------------------------- +> 1 +> 2 +> rows (ordered): 2 + +SELECT NTILE(2) OVER (ORDER BY X) FROM (SELECT * FROM SYSTEM_RANGE(1, 3)) ORDER BY X; +> NTILE(2) OVER (ORDER BY X) +> -------------------------- +> 1 +> 1 +> 2 +> rows (ordered): 3 + +SELECT NTILE(2) OVER (ORDER BY X) FROM (SELECT * FROM SYSTEM_RANGE(1, 4)) ORDER BY X; +> NTILE(2) OVER (ORDER BY X) +> -------------------------- +> 1 +> 1 +> 2 +> 2 +> rows (ordered): 4 + +SELECT NTILE(2) OVER (ORDER BY X) FROM (SELECT * FROM SYSTEM_RANGE(1, 5)) ORDER BY X; +> NTILE(2) OVER (ORDER BY X) +> -------------------------- +> 1 +> 1 +> 1 +> 2 +> 2 +> rows (ordered): 5 + +SELECT NTILE(2) OVER (ORDER BY X) FROM (SELECT * FROM SYSTEM_RANGE(1, 6)) ORDER BY X; +> NTILE(2) OVER (ORDER BY X) +> -------------------------- +> 1 +> 1 +> 1 +> 2 +> 2 +> 2 +> rows (ordered): 6 + +SELECT NTILE(10) OVER (ORDER BY X) FROM (SELECT * FROM SYSTEM_RANGE(1, 3)) ORDER BY X; +> NTILE(10) OVER (ORDER BY X) +> --------------------------- +> 1 +> 2 +> 3 +> rows (ordered): 3 + +SELECT NTILE(10) OVER (ORDER BY X) FROM (SELECT * FROM SYSTEM_RANGE(1, 22)) ORDER BY X; +> NTILE(10) OVER (ORDER BY X) +> --------------------------- +> 1 +> 1 +> 1 +> 2 +> 2 +> 2 +> 3 +> 3 +> 4 +> 4 +> 5 +> 5 +> 6 +> 6 +> 7 +> 7 +> 8 +> 8 +> 9 +> 9 +> 10 +> 10 +> rows (ordered): 22 + +SELECT NTILE(0) OVER (ORDER BY X) FROM (SELECT * FROM SYSTEM_RANGE(1, 1)); +> exception INVALID_VALUE_2 + +SELECT NTILE(X) OVER (ORDER BY X) FROM (SELECT * FROM SYSTEM_RANGE(1, 6)) ORDER BY X; +> NTILE(X) OVER (ORDER BY X) +> -------------------------- +> 1 +> 1 +> 2 +> 2 +> 4 +> 6 +> rows (ordered): 6 + +SELECT NTILE(X) OVER () FROM (SELECT * FROM SYSTEM_RANGE(1, 1)); +> exception SYNTAX_ERROR_2 + +SELECT NTILE(X) OVER (ORDER BY X RANGE CURRENT ROW) FROM (SELECT * FROM SYSTEM_RANGE(1, 1)); +> exception SYNTAX_ERROR_1 + +SELECT NTILE(100000000000) OVER (ORDER BY X) FROM (SELECT * FROM SYSTEM_RANGE(1, 4)); +> NTILE(100000000000) OVER (ORDER BY X) +> ------------------------------------- +> 1 +> 2 +> 3 +> 4 +> rows: 4 diff --git a/h2/src/test/org/h2/test/scripts/functions/window/ratio_to_report.sql b/h2/src/test/org/h2/test/scripts/functions/window/ratio_to_report.sql new file mode 100644 index 0000000..6760ad7 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/window/ratio_to_report.sql @@ -0,0 +1,38 @@ +-- 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 +-- + +CREATE TABLE TEST(ID INT PRIMARY KEY, N NUMERIC); +> ok + +INSERT INTO TEST VALUES(1, 1), (2, 2), (3, NULL), (4, 5); +> update count: 4 + +SELECT ID, N, RATIO_TO_REPORT(N) OVER() R2R FROM TEST; +> ID N R2R +> -- ---- ----- +> 1 1 0.125 +> 2 2 0.25 +> 3 null null +> 4 5 0.625 +> rows: 4 + +INSERT INTO TEST VALUES (5, -8); +> update count: 1 + +SELECT ID, N, RATIO_TO_REPORT(N) OVER() R2R FROM TEST; +> ID N R2R +> -- ---- ---- +> 1 1 null +> 2 2 null +> 3 null null +> 4 5 null +> 5 -8 null +> rows: 5 + +SELECT RATIO_TO_REPORT(N) OVER (ORDER BY N) FROM TEST; +> exception SYNTAX_ERROR_1 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/functions/window/row_number.sql b/h2/src/test/org/h2/test/scripts/functions/window/row_number.sql new file mode 100644 index 0000000..90b99c3 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/functions/window/row_number.sql @@ -0,0 +1,245 @@ +-- 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 +-- + +CREATE TABLE TEST (ID INT PRIMARY KEY, CATEGORY INT, "VALUE" INT); +> ok + +INSERT INTO TEST VALUES + (1, 1, 11), + (2, 1, 12), + (3, 1, 13), + (4, 2, 21), + (5, 2, 22), + (6, 3, 31), + (7, 3, 32), + (8, 3, 33), + (9, 4, 41); +> update count: 9 + +SELECT *, + ROW_NUMBER() OVER () RN, + ROUND(PERCENT_RANK() OVER (), 2) PR, + ROUND(CUME_DIST() OVER (), 2) CD, + ROW_NUMBER() OVER (ORDER BY ID) RNO, + RANK() OVER (ORDER BY ID) RKO, + DENSE_RANK() OVER (ORDER BY ID) DRO, + ROUND(PERCENT_RANK() OVER (ORDER BY ID), 2) PRO, + ROUND(CUME_DIST() OVER (ORDER BY ID), 2) CDO + FROM TEST; +> ID CATEGORY VALUE RN PR CD RNO RKO DRO PRO CDO +> -- -------- ----- -- --- --- --- --- --- ---- ---- +> 1 1 11 1 0.0 1.0 1 1 1 0.0 0.11 +> 2 1 12 2 0.0 1.0 2 2 2 0.13 0.22 +> 3 1 13 3 0.0 1.0 3 3 3 0.25 0.33 +> 4 2 21 4 0.0 1.0 4 4 4 0.38 0.44 +> 5 2 22 5 0.0 1.0 5 5 5 0.5 0.56 +> 6 3 31 6 0.0 1.0 6 6 6 0.63 0.67 +> 7 3 32 7 0.0 1.0 7 7 7 0.75 0.78 +> 8 3 33 8 0.0 1.0 8 8 8 0.88 0.89 +> 9 4 41 9 0.0 1.0 9 9 9 1.0 1.0 +> rows: 9 + +SELECT *, + ROW_NUMBER() OVER (ORDER BY CATEGORY) RN, + RANK() OVER (ORDER BY CATEGORY) RK, + DENSE_RANK() OVER (ORDER BY CATEGORY) DR, + ROUND(PERCENT_RANK() OVER (ORDER BY CATEGORY), 2) PR, + ROUND(CUME_DIST() OVER (ORDER BY CATEGORY), 2) CD + FROM TEST; +> ID CATEGORY VALUE RN RK DR PR CD +> -- -------- ----- -- -- -- ---- ---- +> 1 1 11 1 1 1 0.0 0.33 +> 2 1 12 2 1 1 0.0 0.33 +> 3 1 13 3 1 1 0.0 0.33 +> 4 2 21 4 4 2 0.38 0.56 +> 5 2 22 5 4 2 0.38 0.56 +> 6 3 31 6 6 3 0.63 0.89 +> 7 3 32 7 6 3 0.63 0.89 +> 8 3 33 8 6 3 0.63 0.89 +> 9 4 41 9 9 4 1.0 1.0 +> rows: 9 + +SELECT *, + ROW_NUMBER() OVER (PARTITION BY CATEGORY ORDER BY ID) RN, + RANK() OVER (PARTITION BY CATEGORY ORDER BY ID) RK, + DENSE_RANK() OVER (PARTITION BY CATEGORY ORDER BY ID) DR, + ROUND(PERCENT_RANK() OVER (PARTITION BY CATEGORY ORDER BY ID), 2) PR, + ROUND(CUME_DIST() OVER (PARTITION BY CATEGORY ORDER BY ID), 2) CD + FROM TEST; +> ID CATEGORY VALUE RN RK DR PR CD +> -- -------- ----- -- -- -- --- ---- +> 1 1 11 1 1 1 0.0 0.33 +> 2 1 12 2 2 2 0.5 0.67 +> 3 1 13 3 3 3 1.0 1.0 +> 4 2 21 1 1 1 0.0 0.5 +> 5 2 22 2 2 2 1.0 1.0 +> 6 3 31 1 1 1 0.0 0.33 +> 7 3 32 2 2 2 0.5 0.67 +> 8 3 33 3 3 3 1.0 1.0 +> 9 4 41 1 1 1 0.0 1.0 +> rows: 9 + +SELECT *, + ROW_NUMBER() OVER W RN, + RANK() OVER W RK, + DENSE_RANK() OVER W DR, + ROUND(PERCENT_RANK() OVER W, 2) PR, + ROUND(CUME_DIST() OVER W, 2) CD + FROM TEST WINDOW W AS (PARTITION BY CATEGORY ORDER BY ID) QUALIFY ROW_NUMBER() OVER W = 2; +> ID CATEGORY VALUE RN RK DR PR CD +> -- -------- ----- -- -- -- --- ---- +> 2 1 12 2 2 2 0.5 0.67 +> 5 2 22 2 2 2 1.0 1.0 +> 7 3 32 2 2 2 0.5 0.67 +> rows: 3 + +SELECT *, + ROW_NUMBER() OVER (PARTITION BY CATEGORY ORDER BY ID) RN, + RANK() OVER (PARTITION BY CATEGORY ORDER BY ID) RK, + DENSE_RANK() OVER (PARTITION BY CATEGORY ORDER BY ID) DR, + ROUND(PERCENT_RANK() OVER (PARTITION BY CATEGORY ORDER BY ID), 2) PR, + ROUND(CUME_DIST() OVER (PARTITION BY CATEGORY ORDER BY ID), 2) CD + FROM TEST QUALIFY RN = 3; +> ID CATEGORY VALUE RN RK DR PR CD +> -- -------- ----- -- -- -- --- --- +> 3 1 13 3 3 3 1.0 1.0 +> 8 3 33 3 3 3 1.0 1.0 +> rows: 2 + +SELECT + ROW_NUMBER() OVER (ORDER BY CATEGORY) RN, + RANK() OVER (ORDER BY CATEGORY) RK, + DENSE_RANK() OVER (ORDER BY CATEGORY) DR, + PERCENT_RANK() OVER () PR, + CUME_DIST() OVER () CD, + CATEGORY C + FROM TEST GROUP BY CATEGORY ORDER BY RN; +> RN RK DR PR CD C +> -- -- -- --- --- - +> 1 1 1 0.0 1.0 1 +> 2 2 2 0.0 1.0 2 +> 3 3 3 0.0 1.0 3 +> 4 4 4 0.0 1.0 4 +> rows (ordered): 4 + +SELECT RANK() OVER () FROM TEST; +> exception SYNTAX_ERROR_2 + +SELECT DENSE_RANK() OVER () FROM TEST; +> exception SYNTAX_ERROR_2 + +SELECT ROW_NUMBER() OVER (ORDER BY ID RANGE CURRENT ROW) FROM TEST; +> exception SYNTAX_ERROR_1 + +SELECT RANK() OVER (ORDER BY ID RANGE CURRENT ROW) FROM TEST; +> exception SYNTAX_ERROR_1 + +SELECT DENSE_RANK() OVER (ORDER BY ID RANGE CURRENT ROW) FROM TEST; +> exception SYNTAX_ERROR_1 + +SELECT PERCENT_RANK() OVER (ORDER BY ID RANGE CURRENT ROW) FROM TEST; +> exception SYNTAX_ERROR_1 + +SELECT CUME_DIST() OVER (ORDER BY ID RANGE CURRENT ROW) FROM TEST; +> exception SYNTAX_ERROR_1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST (ID INT PRIMARY KEY, TYPE VARCHAR, CNT INT); +> ok + +INSERT INTO TEST VALUES + (1, 'a', 1), + (2, 'b', 2), + (3, 'c', 4), + (4, 'b', 8); +> update count: 4 + +SELECT ROW_NUMBER() OVER (ORDER BY TYPE) RN, TYPE, SUM(CNT) SUM FROM TEST GROUP BY TYPE; +> RN TYPE SUM +> -- ---- --- +> 1 a 1 +> 2 b 10 +> 3 c 4 +> rows: 3 + +SELECT A, B, C, ROW_NUMBER() OVER (PARTITION BY A, B) N FROM + VALUES (1, 1, 1), (1, 1, 2), (1, 2, 3), (2, 1, 4) T(A, B, C); +> A B C N +> - - - - +> 1 1 1 1 +> 1 1 2 2 +> 1 2 3 1 +> 2 1 4 1 +> rows: 4 + +SELECT RANK () OVER () FROM TEST; +> exception SYNTAX_ERROR_2 + +SELECT DENSE_RANK () OVER () FROM TEST; +> exception SYNTAX_ERROR_2 + +DROP TABLE TEST; +> ok + +SELECT ROW_NUMBER() OVER () FROM VALUES (1); +> ROW_NUMBER() OVER () +> -------------------- +> 1 +> rows: 1 + +CREATE TABLE TEST(X INT) AS VALUES 1, 2, 3; +> ok + +EXPLAIN SELECT ROW_NUMBER() OVER (ORDER BY 'a') FROM TEST; +>> SELECT ROW_NUMBER() OVER () FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT RANK() OVER (ORDER BY 'a') FROM TEST; +>> SELECT CAST(1 AS BIGINT) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +SELECT RANK() OVER (ORDER BY 'a') FROM TEST; +> 1 +> - +> 1 +> 1 +> 1 +> rows: 3 + +EXPLAIN SELECT DENSE_RANK() OVER (ORDER BY 'a') FROM TEST; +>> SELECT CAST(1 AS BIGINT) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +SELECT DENSE_RANK() OVER (ORDER BY 'a') FROM TEST; +> 1 +> - +> 1 +> 1 +> 1 +> rows: 3 + +EXPLAIN SELECT PERCENT_RANK() OVER (ORDER BY 'a') FROM TEST; +>> SELECT CAST(0.0 AS DOUBLE PRECISION) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +SELECT PERCENT_RANK() OVER (ORDER BY 'a') FROM TEST; +> 0.0 +> --- +> 0.0 +> 0.0 +> 0.0 +> rows: 3 + +EXPLAIN SELECT CUME_DIST() OVER (ORDER BY 'a') FROM TEST; +>> SELECT CAST(1.0 AS DOUBLE PRECISION) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +SELECT CUME_DIST() OVER (ORDER BY 'a') FROM TEST; +> 1.0 +> --- +> 1.0 +> 1.0 +> 1.0 +> rows: 3 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/indexes.sql b/h2/src/test/org/h2/test/scripts/indexes.sql new file mode 100644 index 0000000..4400a63 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/indexes.sql @@ -0,0 +1,412 @@ +-- 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 +-- + +-- Test all possible order modes + +CREATE TABLE TEST(A INT); +> ok + +INSERT INTO TEST VALUES (NULL), (0), (1); +> update count: 3 + +-- default + +SELECT A FROM TEST ORDER BY A; +> A +> ---- +> null +> 0 +> 1 +> rows (ordered): 3 + +CREATE INDEX A_IDX ON TEST(A); +> ok + +SELECT A FROM TEST ORDER BY A; +> A +> ---- +> null +> 0 +> 1 +> rows (ordered): 3 + +EXPLAIN SELECT A FROM TEST ORDER BY A; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.A_IDX */ ORDER BY 1 /* index sorted */ + +DROP INDEX A_IDX; +> ok + +-- ASC + +SELECT A FROM TEST ORDER BY A ASC; +> A +> ---- +> null +> 0 +> 1 +> rows (ordered): 3 + +CREATE INDEX A_IDX ON TEST(A ASC); +> ok + +SELECT A FROM TEST ORDER BY A ASC; +> A +> ---- +> null +> 0 +> 1 +> rows (ordered): 3 + +EXPLAIN SELECT A FROM TEST ORDER BY A ASC; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.A_IDX */ ORDER BY 1 /* index sorted */ + +DROP INDEX A_IDX; +> ok + +-- ASC NULLS FIRST + +SELECT A FROM TEST ORDER BY A ASC NULLS FIRST; +> A +> ---- +> null +> 0 +> 1 +> rows (ordered): 3 + +CREATE INDEX A_IDX ON TEST(A ASC NULLS FIRST); +> ok + +SELECT A FROM TEST ORDER BY A ASC NULLS FIRST; +> A +> ---- +> null +> 0 +> 1 +> rows (ordered): 3 + +EXPLAIN SELECT A FROM TEST ORDER BY A ASC NULLS FIRST; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.A_IDX */ ORDER BY 1 NULLS FIRST /* index sorted */ + +DROP INDEX A_IDX; +> ok + +-- ASC NULLS LAST + +SELECT A FROM TEST ORDER BY A ASC NULLS LAST; +> A +> ---- +> 0 +> 1 +> null +> rows (ordered): 3 + +CREATE INDEX A_IDX ON TEST(A ASC NULLS LAST); +> ok + +SELECT A FROM TEST ORDER BY A ASC NULLS LAST; +> A +> ---- +> 0 +> 1 +> null +> rows (ordered): 3 + +EXPLAIN SELECT A FROM TEST ORDER BY A ASC NULLS LAST; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.A_IDX */ ORDER BY 1 NULLS LAST /* index sorted */ + +DROP INDEX A_IDX; +> ok + +-- DESC + +SELECT A FROM TEST ORDER BY A DESC; +> A +> ---- +> 1 +> 0 +> null +> rows (ordered): 3 + +CREATE INDEX A_IDX ON TEST(A DESC); +> ok + +SELECT A FROM TEST ORDER BY A DESC; +> A +> ---- +> 1 +> 0 +> null +> rows (ordered): 3 + +EXPLAIN SELECT A FROM TEST ORDER BY A DESC; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.A_IDX */ ORDER BY 1 DESC /* index sorted */ + +DROP INDEX A_IDX; +> ok + +-- DESC NULLS FIRST + +SELECT A FROM TEST ORDER BY A DESC NULLS FIRST; +> A +> ---- +> null +> 1 +> 0 +> rows (ordered): 3 + +CREATE INDEX A_IDX ON TEST(A DESC NULLS FIRST); +> ok + +SELECT A FROM TEST ORDER BY A DESC NULLS FIRST; +> A +> ---- +> null +> 1 +> 0 +> rows (ordered): 3 + +EXPLAIN SELECT A FROM TEST ORDER BY A DESC NULLS FIRST; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.A_IDX */ ORDER BY 1 DESC NULLS FIRST /* index sorted */ + +DROP INDEX A_IDX; +> ok + +-- DESC NULLS LAST + +SELECT A FROM TEST ORDER BY A DESC NULLS LAST; +> A +> ---- +> 1 +> 0 +> null +> rows (ordered): 3 + +CREATE INDEX A_IDX ON TEST(A DESC NULLS LAST); +> ok + +SELECT A FROM TEST ORDER BY A DESC NULLS LAST; +> A +> ---- +> 1 +> 0 +> null +> rows (ordered): 3 + +EXPLAIN SELECT A FROM TEST ORDER BY A DESC NULLS LAST; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.A_IDX */ ORDER BY 1 DESC NULLS LAST /* index sorted */ + +DROP INDEX A_IDX; +> ok + +-- Index selection + +CREATE INDEX A_IDX_ASC ON TEST(A ASC); +> ok + +CREATE INDEX A_IDX_ASC_NL ON TEST(A ASC NULLS LAST); +> ok + +CREATE INDEX A_IDX_DESC ON TEST(A DESC); +> ok + +CREATE INDEX A_IDX_DESC_NF ON TEST(A DESC NULLS FIRST); +> ok + +EXPLAIN SELECT A FROM TEST ORDER BY A; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.A_IDX_ASC */ ORDER BY 1 /* index sorted */ + +EXPLAIN SELECT A FROM TEST ORDER BY A ASC; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.A_IDX_ASC */ ORDER BY 1 /* index sorted */ + +EXPLAIN SELECT A FROM TEST ORDER BY A NULLS FIRST; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.A_IDX_ASC */ ORDER BY 1 NULLS FIRST /* index sorted */ + +EXPLAIN SELECT A FROM TEST ORDER BY A NULLS LAST; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.A_IDX_ASC_NL */ ORDER BY 1 NULLS LAST /* index sorted */ + +EXPLAIN SELECT A FROM TEST ORDER BY A DESC; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.A_IDX_DESC */ ORDER BY 1 DESC /* index sorted */ + +EXPLAIN SELECT A FROM TEST ORDER BY A DESC NULLS FIRST; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.A_IDX_DESC_NF */ ORDER BY 1 DESC NULLS FIRST /* index sorted */ + +EXPLAIN SELECT A FROM TEST ORDER BY A DESC NULLS LAST; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.A_IDX_DESC */ ORDER BY 1 DESC NULLS LAST /* index sorted */ + +DROP INDEX A_IDX_ASC; +> ok + +DROP INDEX A_IDX_DESC; +> ok + +CREATE INDEX A_IDX_ASC_NF ON TEST(A ASC NULLS FIRST); +> ok + +CREATE INDEX A_IDX_DESC_NL ON TEST(A DESC NULLS LAST); +> ok + +EXPLAIN SELECT A FROM TEST ORDER BY A; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.A_IDX_ASC_NF */ ORDER BY 1 /* index sorted */ + +EXPLAIN SELECT A FROM TEST ORDER BY A ASC; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.A_IDX_ASC_NF */ ORDER BY 1 /* index sorted */ + +EXPLAIN SELECT A FROM TEST ORDER BY A NULLS FIRST; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.A_IDX_ASC_NF */ ORDER BY 1 NULLS FIRST /* index sorted */ + +EXPLAIN SELECT A FROM TEST ORDER BY A NULLS LAST; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.A_IDX_ASC_NL */ ORDER BY 1 NULLS LAST /* index sorted */ + +EXPLAIN SELECT A FROM TEST ORDER BY A DESC; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.A_IDX_DESC_NL */ ORDER BY 1 DESC /* index sorted */ + +EXPLAIN SELECT A FROM TEST ORDER BY A DESC NULLS FIRST; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.A_IDX_DESC_NF */ ORDER BY 1 DESC NULLS FIRST /* index sorted */ + +EXPLAIN SELECT A FROM TEST ORDER BY A DESC NULLS LAST; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.A_IDX_DESC_NL */ ORDER BY 1 DESC NULLS LAST /* index sorted */ + +DROP TABLE TEST; +> ok + +-- Other tests + +create table test(a int, b int); +> ok + +insert into test values(1, 1); +> update count: 1 + +create index on test(a, b desc); +> ok + +select * from test where a = 1; +> A B +> - - +> 1 1 +> rows: 1 + +drop table test; +> ok + +create table test(x int); +> ok + +create hash index on test(x); +> ok + +select 1 from test group by x; +> 1 +> - +> rows: 0 + +drop table test; +> ok + +CREATE TABLE TEST(A INT, B INT, C INT); +> ok + +CREATE INDEX T_A1 ON TEST(A); +> ok + +CREATE INDEX T_A_B ON TEST(A, B); +> ok + +CREATE INDEX T_A_C ON TEST(A, C); +> ok + +EXPLAIN SELECT * FROM TEST WHERE A = 0; +>> SELECT "PUBLIC"."TEST"."A", "PUBLIC"."TEST"."B", "PUBLIC"."TEST"."C" FROM "PUBLIC"."TEST" /* PUBLIC.T_A1: A = 0 */ WHERE "A" = 0 + +EXPLAIN SELECT * FROM TEST WHERE A = 0 AND B >= 0; +>> SELECT "PUBLIC"."TEST"."A", "PUBLIC"."TEST"."B", "PUBLIC"."TEST"."C" FROM "PUBLIC"."TEST" /* PUBLIC.T_A_B: A = 0 AND B >= 0 */ WHERE ("A" = 0) AND ("B" >= 0) + +EXPLAIN SELECT * FROM TEST WHERE A > 0 AND B >= 0; +>> SELECT "PUBLIC"."TEST"."A", "PUBLIC"."TEST"."B", "PUBLIC"."TEST"."C" FROM "PUBLIC"."TEST" /* PUBLIC.T_A_B: A > 0 AND B >= 0 */ WHERE ("A" > 0) AND ("B" >= 0) + +INSERT INTO TEST (SELECT X / 100, X, X FROM SYSTEM_RANGE(1, 3000)); +> update count: 3000 + +EXPLAIN SELECT * FROM TEST WHERE A = 0; +>> SELECT "PUBLIC"."TEST"."A", "PUBLIC"."TEST"."B", "PUBLIC"."TEST"."C" FROM "PUBLIC"."TEST" /* PUBLIC.T_A1: A = 0 */ WHERE "A" = 0 + +EXPLAIN SELECT * FROM TEST WHERE A = 0 AND B >= 0; +>> SELECT "PUBLIC"."TEST"."A", "PUBLIC"."TEST"."B", "PUBLIC"."TEST"."C" FROM "PUBLIC"."TEST" /* PUBLIC.T_A_B: A = 0 AND B >= 0 */ WHERE ("A" = 0) AND ("B" >= 0) + +EXPLAIN SELECT * FROM TEST WHERE A > 0 AND B >= 0; +>> SELECT "PUBLIC"."TEST"."A", "PUBLIC"."TEST"."B", "PUBLIC"."TEST"."C" FROM "PUBLIC"."TEST" /* PUBLIC.T_A_B: A > 0 AND B >= 0 */ WHERE ("A" > 0) AND ("B" >= 0) + +-- Test that creation order of indexes has no effect +CREATE INDEX T_A2 ON TEST(A); +> ok + +DROP INDEX T_A1; +> ok + +EXPLAIN SELECT * FROM TEST WHERE A = 0; +>> SELECT "PUBLIC"."TEST"."A", "PUBLIC"."TEST"."B", "PUBLIC"."TEST"."C" FROM "PUBLIC"."TEST" /* PUBLIC.T_A2: A = 0 */ WHERE "A" = 0 + +DROP TABLE TEST; +> ok + +CREATE TABLE T(A INT, B INT, C INT); +> ok + +CREATE INDEX T_B_IDX ON T(B); +> ok + +EXPLAIN SELECT * FROM T WHERE A = 1 AND B = A; +>> SELECT "PUBLIC"."T"."A", "PUBLIC"."T"."B", "PUBLIC"."T"."C" FROM "PUBLIC"."T" /* PUBLIC.T_B_IDX: B = 1 */ WHERE ("A" = 1) AND ("B" = "A") + +DROP TABLE T; +> ok + +-- _ROWID_ tests + +CREATE TABLE TEST(ID INT PRIMARY KEY); +> ok + +INSERT INTO TEST VALUES 1, 2, 3, 4; +> update count: 4 + +SELECT * FROM TEST WHERE ID >= 2 AND ID <= 3; +> ID +> -- +> 2 +> 3 +> rows: 2 + +SELECT * FROM TEST WHERE _ROWID_ >= 2 AND _ROWID_ <= 3; +> ID +> -- +> 2 +> 3 +> rows: 2 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID FLOAT PRIMARY KEY); +> ok + +INSERT INTO TEST VALUES 1.0, 2.0, 3.0, 4.0; +> update count: 4 + +SELECT * FROM TEST WHERE ID >= 2.0 AND ID <= 3.0; +> ID +> --- +> 2.0 +> 3.0 +> rows: 2 + +SELECT * FROM TEST WHERE _ROWID_ >= 2 AND _ROWID_ <= 3; +> ID +> --- +> 2.0 +> 3.0 +> rows: 2 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/information_schema.sql b/h2/src/test/org/h2/test/scripts/information_schema.sql new file mode 100644 index 0000000..aca6341 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/information_schema.sql @@ -0,0 +1,193 @@ +-- 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 +-- + +TABLE INFORMATION_SCHEMA.INFORMATION_SCHEMA_CATALOG_NAME; +> CATALOG_NAME +> ------------ +> SCRIPT +> rows: 1 + +CREATE TABLE T1(C1 INT NOT NULL, C2 INT NOT NULL, C3 INT, C4 INT); +> ok + +ALTER TABLE T1 ADD CONSTRAINT PK_1 PRIMARY KEY(C1, C2); +> ok + +ALTER TABLE T1 ADD CONSTRAINT U_1 UNIQUE(C3, C4); +> ok + +CREATE TABLE T2(C1 INT, C2 INT, C3 INT, C4 INT); +> ok + +ALTER TABLE T2 ADD CONSTRAINT FK_1 FOREIGN KEY (C3, C4) REFERENCES T1(C1, C3) ON DELETE SET NULL; +> exception CONSTRAINT_NOT_FOUND_1 + +SET MODE MySQL; +> ok + +ALTER TABLE T2 ADD CONSTRAINT FK_1 FOREIGN KEY (C3, C4) REFERENCES T1(C1, C3) ON DELETE SET NULL; +> ok + +ALTER TABLE T2 ADD CONSTRAINT FK_2 FOREIGN KEY (C3, C4) REFERENCES T1(C4, C3) ON UPDATE CASCADE ON DELETE SET DEFAULT; +> ok + +SET MODE Regular; +> ok + +ALTER TABLE T2 ADD CONSTRAINT CH_1 CHECK (C4 > 0 AND NOT EXISTS(SELECT 1 FROM T1 WHERE T1.C1 + T1.C2 = T2.C4)); +> ok + +SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS LIMIT 0; +> CONSTRAINT_CATALOG CONSTRAINT_SCHEMA CONSTRAINT_NAME CONSTRAINT_TYPE TABLE_CATALOG TABLE_SCHEMA TABLE_NAME IS_DEFERRABLE INITIALLY_DEFERRED ENFORCED INDEX_CATALOG INDEX_SCHEMA INDEX_NAME REMARKS +> ------------------ ----------------- --------------- --------------- ------------- ------------ ---------- ------------- ------------------ -------- ------------- ------------ ---------- ------- +> rows: 0 + +SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE, TABLE_NAME, IS_DEFERRABLE, INITIALLY_DEFERRED FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS + WHERE CONSTRAINT_CATALOG = DATABASE() AND CONSTRAINT_SCHEMA = SCHEMA() AND TABLE_CATALOG = DATABASE() AND TABLE_SCHEMA = SCHEMA() + ORDER BY TABLE_NAME, CONSTRAINT_NAME; +> CONSTRAINT_NAME CONSTRAINT_TYPE TABLE_NAME IS_DEFERRABLE INITIALLY_DEFERRED +> --------------- --------------- ---------- ------------- ------------------ +> CONSTRAINT_A UNIQUE T1 NO NO +> PK_1 PRIMARY KEY T1 NO NO +> U_1 UNIQUE T1 NO NO +> CH_1 CHECK T2 NO NO +> FK_1 FOREIGN KEY T2 NO NO +> FK_2 FOREIGN KEY T2 NO NO +> rows (ordered): 6 + +SELECT * FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE LIMIT 0; +> CONSTRAINT_CATALOG CONSTRAINT_SCHEMA CONSTRAINT_NAME TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME ORDINAL_POSITION POSITION_IN_UNIQUE_CONSTRAINT +> ------------------ ----------------- --------------- ------------- ------------ ---------- ----------- ---------------- ----------------------------- +> rows: 0 + +SELECT CONSTRAINT_NAME, TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, POSITION_IN_UNIQUE_CONSTRAINT FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE + WHERE CONSTRAINT_CATALOG = DATABASE() AND CONSTRAINT_SCHEMA = SCHEMA() AND TABLE_CATALOG = DATABASE() AND TABLE_SCHEMA = SCHEMA() + ORDER BY TABLE_NAME, CONSTRAINT_NAME, ORDINAL_POSITION; +> CONSTRAINT_NAME TABLE_NAME COLUMN_NAME ORDINAL_POSITION POSITION_IN_UNIQUE_CONSTRAINT +> --------------- ---------- ----------- ---------------- ----------------------------- +> CONSTRAINT_A T1 C1 1 null +> CONSTRAINT_A T1 C3 2 null +> PK_1 T1 C1 1 null +> PK_1 T1 C2 2 null +> U_1 T1 C3 1 null +> U_1 T1 C4 2 null +> FK_1 T2 C3 1 1 +> FK_1 T2 C4 2 2 +> FK_2 T2 C3 1 2 +> FK_2 T2 C4 2 1 +> rows (ordered): 10 + +SELECT * FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS LIMIT 0; +> CONSTRAINT_CATALOG CONSTRAINT_SCHEMA CONSTRAINT_NAME UNIQUE_CONSTRAINT_CATALOG UNIQUE_CONSTRAINT_SCHEMA UNIQUE_CONSTRAINT_NAME MATCH_OPTION UPDATE_RULE DELETE_RULE +> ------------------ ----------------- --------------- ------------------------- ------------------------ ---------------------- ------------ ----------- ----------- +> rows: 0 + +SELECT CONSTRAINT_NAME, UNIQUE_CONSTRAINT_NAME, MATCH_OPTION, UPDATE_RULE, DELETE_RULE FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS + WHERE CONSTRAINT_CATALOG = DATABASE() AND CONSTRAINT_SCHEMA = SCHEMA() AND UNIQUE_CONSTRAINT_CATALOG = DATABASE() AND UNIQUE_CONSTRAINT_SCHEMA = SCHEMA() + ORDER BY CONSTRAINT_NAME, UNIQUE_CONSTRAINT_NAME; +> CONSTRAINT_NAME UNIQUE_CONSTRAINT_NAME MATCH_OPTION UPDATE_RULE DELETE_RULE +> --------------- ---------------------- ------------ ----------- ----------- +> FK_1 CONSTRAINT_A NONE RESTRICT SET NULL +> FK_2 U_1 NONE CASCADE SET DEFAULT +> rows (ordered): 2 + +SELECT U1.TABLE_NAME T1, U1.COLUMN_NAME C1, U2.TABLE_NAME T2, U2.COLUMN_NAME C2 + FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE U1 JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS RC ON U1.CONSTRAINT_NAME = RC.CONSTRAINT_NAME + JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE U2 ON RC.UNIQUE_CONSTRAINT_NAME = U2.CONSTRAINT_NAME AND U1.POSITION_IN_UNIQUE_CONSTRAINT = U2.ORDINAL_POSITION + WHERE U1.CONSTRAINT_NAME = 'FK_2' ORDER BY U1.COLUMN_NAME; +> T1 C1 T2 C2 +> -- -- -- -- +> T2 C3 T1 C4 +> T2 C4 T1 C3 +> rows (ordered): 2 + +TABLE INFORMATION_SCHEMA.CHECK_CONSTRAINTS; +> CONSTRAINT_CATALOG CONSTRAINT_SCHEMA CONSTRAINT_NAME CHECK_CLAUSE +> ------------------ ----------------- --------------- --------------------------------------------------------------------------------------------------- +> SCRIPT PUBLIC CH_1 ("C4" > 0) AND (NOT EXISTS( SELECT 1 FROM "PUBLIC"."T1" WHERE ("T1"."C1" + "T1"."C2") = "T2"."C4")) +> rows: 1 + +TABLE INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE; +> TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME CONSTRAINT_CATALOG CONSTRAINT_SCHEMA CONSTRAINT_NAME +> ------------- ------------ ---------- ----------- ------------------ ----------------- --------------- +> SCRIPT PUBLIC T1 C1 SCRIPT PUBLIC CH_1 +> SCRIPT PUBLIC T1 C1 SCRIPT PUBLIC CONSTRAINT_A +> SCRIPT PUBLIC T1 C1 SCRIPT PUBLIC FK_1 +> SCRIPT PUBLIC T1 C1 SCRIPT PUBLIC PK_1 +> SCRIPT PUBLIC T1 C2 SCRIPT PUBLIC CH_1 +> SCRIPT PUBLIC T1 C2 SCRIPT PUBLIC PK_1 +> SCRIPT PUBLIC T1 C3 SCRIPT PUBLIC CONSTRAINT_A +> SCRIPT PUBLIC T1 C3 SCRIPT PUBLIC FK_1 +> SCRIPT PUBLIC T1 C3 SCRIPT PUBLIC FK_2 +> SCRIPT PUBLIC T1 C3 SCRIPT PUBLIC U_1 +> SCRIPT PUBLIC T1 C4 SCRIPT PUBLIC FK_2 +> SCRIPT PUBLIC T1 C4 SCRIPT PUBLIC U_1 +> SCRIPT PUBLIC T2 C3 SCRIPT PUBLIC FK_1 +> SCRIPT PUBLIC T2 C3 SCRIPT PUBLIC FK_2 +> SCRIPT PUBLIC T2 C4 SCRIPT PUBLIC CH_1 +> SCRIPT PUBLIC T2 C4 SCRIPT PUBLIC FK_1 +> SCRIPT PUBLIC T2 C4 SCRIPT PUBLIC FK_2 +> rows: 17 + +DROP TABLE T2; +> ok + +DROP TABLE T1; +> ok + +@reconnect off + +CREATE TABLE T1(C1 INT PRIMARY KEY); +> ok + +CREATE TABLE T2(C2 INT PRIMARY KEY REFERENCES T1); +> ok + +SELECT ENFORCED FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_TYPE = 'FOREIGN KEY'; +>> YES + +SET REFERENTIAL_INTEGRITY FALSE; +> ok + +SELECT ENFORCED FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_TYPE = 'FOREIGN KEY'; +>> NO + +SET REFERENTIAL_INTEGRITY TRUE; +> ok + +ALTER TABLE T1 SET REFERENTIAL_INTEGRITY FALSE; +> ok + +SELECT ENFORCED FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_TYPE = 'FOREIGN KEY'; +>> NO + +ALTER TABLE T1 SET REFERENTIAL_INTEGRITY TRUE; +> ok + +ALTER TABLE T2 SET REFERENTIAL_INTEGRITY FALSE; +> ok + +SELECT ENFORCED FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_TYPE = 'FOREIGN KEY'; +>> NO + +DROP TABLE T2, T1; +> ok + +@reconnect on + +SELECT TABLE_NAME, ROW_COUNT_ESTIMATE FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'INFORMATION_SCHEMA' + AND TABLE_NAME IN ('INFORMATION_SCHEMA_CATALOG_NAME', 'SCHEMATA', 'ROLES', 'SESSIONS', 'IN_DOUBT', 'USERS'); +> TABLE_NAME ROW_COUNT_ESTIMATE +> ------------------------------- ------------------ +> INFORMATION_SCHEMA_CATALOG_NAME 1 +> IN_DOUBT 0 +> ROLES 1 +> SCHEMATA 2 +> SESSIONS 1 +> USERS 1 +> rows: 6 + +EXPLAIN SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLLATIONS; +>> SELECT COUNT(*) FROM "INFORMATION_SCHEMA"."COLLATIONS" /* meta */ /* direct lookup */ diff --git a/h2/src/test/org/h2/test/scripts/other/at-time-zone.sql b/h2/src/test/org/h2/test/scripts/other/at-time-zone.sql new file mode 100644 index 0000000..c66ed8e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/other/at-time-zone.sql @@ -0,0 +1,134 @@ +-- 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 +-- + +CALL TIMESTAMP WITH TIME ZONE '2010-01-01 10:00:01.123456789+05' AT TIME ZONE '10'; +>> 2010-01-01 15:00:01.123456789+10 + +CALL TIMESTAMP WITH TIME ZONE '2010-01-01 10:00:01.123456789+05' AT TIME ZONE '10:00:30'; +>> 2010-01-01 15:00:31.123456789+10:00:30 + +CALL TIMESTAMP WITH TIME ZONE '2010-01-01 10:00:01.123456789+05' AT TIME ZONE '10:00:30.1'; +> exception INVALID_VALUE_2 + +CALL TIMESTAMP WITH TIME ZONE '2010-01-01 10:00:01.123456789+05' AT TIME ZONE INTERVAL '10:00' HOUR TO MINUTE; +>> 2010-01-01 15:00:01.123456789+10 + +CALL TIMESTAMP WITH TIME ZONE '2010-01-01 10:00:01.123456789+05' AT TIME ZONE INTERVAL '10:00:30' HOUR TO SECOND; +>> 2010-01-01 15:00:31.123456789+10:00:30 + +CALL TIMESTAMP WITH TIME ZONE '2010-01-01 10:00:01.123456789+05' AT TIME ZONE INTERVAL '10:00:30.1' HOUR TO SECOND; +> exception INVALID_VALUE_2 + +CALL TIMESTAMP WITH TIME ZONE '2010-01-01 20:00:01.123456789+05' AT TIME ZONE '18:00'; +>> 2010-01-02 09:00:01.123456789+18 + +CALL TIMESTAMP WITH TIME ZONE '2010-01-01 10:00:01.123456789+05' AT TIME ZONE '-18:00'; +>> 2009-12-31 11:00:01.123456789-18 + +CALL TIMESTAMP WITH TIME ZONE '2010-01-01 10:00:01.123456789+05' AT TIME ZONE '-18:01'; +> exception INVALID_VALUE_2 + +CALL TIMESTAMP WITH TIME ZONE '2010-01-01 10:00:01.123456789+05' AT TIME ZONE '+18:01'; +> exception INVALID_VALUE_2 + +CALL TIMESTAMP WITH TIME ZONE '2010-01-01 10:00:01.123456789+05' AT TIME ZONE '19:00'; +> exception INVALID_VALUE_2 + +CALL RIGHT(CAST(CURRENT_TIMESTAMP AT TIME ZONE '00:00' AS VARCHAR), 3); +>> +00 + +CALL CAST(CURRENT_TIMESTAMP AS VARCHAR) = CAST(CURRENT_TIMESTAMP AT LOCAL AS VARCHAR); +>> TRUE + +CALL CAST(CURRENT_TIMESTAMP AS VARCHAR) = CAST(LOCALTIMESTAMP AT LOCAL AS VARCHAR); +>> TRUE + +CALL TIME WITH TIME ZONE '10:00:01.123456789+05' AT TIME ZONE '10'; +>> 15:00:01.123456789+10 + +CALL RIGHT(CAST(CURRENT_TIME AT TIME ZONE '00:00' AS VARCHAR), 3); +>> +00 + +CALL CAST(CURRENT_TIME AS VARCHAR) = CAST(CURRENT_TIME AT LOCAL AS VARCHAR); +>> TRUE + +CALL CAST(CURRENT_TIME AS VARCHAR) = CAST(LOCALTIME AT LOCAL AS VARCHAR); +>> TRUE + +CALL CAST(NULL AS TIMESTAMP) AT LOCAL; +>> null + +CALL TIMESTAMP WITH TIME ZONE '2010-01-01 10:00:00Z' AT TIME ZONE NULL; +>> null + +CALL 1 AT LOCAL; +> exception SYNTAX_ERROR_2 + +CREATE TABLE TEST(A TIMESTAMP WITH TIME ZONE, B INTERVAL HOUR TO MINUTE) AS + (VALUES ('2010-01-01 10:00:00Z', '10:00')); +> ok + +EXPLAIN SELECT A AT TIME ZONE B, A AT LOCAL FROM TEST; +>> SELECT "A" AT TIME ZONE "B", "A" AT LOCAL FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +DROP TABLE TEST; +> ok + +CALL TIMESTAMP WITH TIME ZONE '2000-01-01 01:00:00+02' AT TIME ZONE 'Europe/London'; +>> 1999-12-31 23:00:00+00 + +CALL TIMESTAMP WITH TIME ZONE '2000-07-01 01:00:00+02' AT TIME ZONE 'Europe/London'; +>> 2000-07-01 00:00:00+01 + +CALL TIMESTAMP WITH TIME ZONE '2000-01-01 01:00:00+02' AT TIME ZONE 'Z'; +>> 1999-12-31 23:00:00+00 + +CALL TIMESTAMP WITH TIME ZONE '2000-01-01 01:00:00+02' AT TIME ZONE 'UTC'; +>> 1999-12-31 23:00:00+00 + +CALL TIMESTAMP WITH TIME ZONE '2000-01-01 01:00:00+02' AT TIME ZONE 'GMT'; +>> 1999-12-31 23:00:00+00 + +CALL TIMESTAMP WITH TIME ZONE '2000-01-01 01:00:00+02' AT TIME ZONE ''; +> exception INVALID_VALUE_2 + +CALL TIMESTAMP WITH TIME ZONE '2000-01-01 01:00:00+02' AT TIME ZONE 'GMT0'; +> exception INVALID_VALUE_2 + +CALL TIME WITH TIME ZONE '01:00:00+02' AT TIME ZONE 'Europe/London'; +> exception INVALID_VALUE_2 + +SET TIME ZONE '5'; +> ok + +SELECT SETTING_VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME = 'TIME ZONE'; +>> GMT+05:00 + +SET TIME ZONE INTERVAL '4:00' HOUR TO MINUTE; +> ok + +SET TIME ZONE NULL; +> exception INVALID_VALUE_2 + +SELECT SETTING_VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME = 'TIME ZONE'; +>> GMT+04:00 + +CREATE TABLE TEST(T TIMESTAMP) AS (VALUES '2010-01-01 10:00:00'); +> ok + +SELECT CAST(T AS TIMESTAMP WITH TIME ZONE) FROM TEST; +>> 2010-01-01 10:00:00+04 + +SELECT T AT LOCAL FROM TEST; +>> 2010-01-01 10:00:00+04 + +SELECT T AT TIME ZONE '8:00' FROM TEST; +>> 2010-01-01 14:00:00+08 + +SET TIME ZONE LOCAL; +> ok + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/other/boolean-test.sql b/h2/src/test/org/h2/test/scripts/other/boolean-test.sql new file mode 100644 index 0000000..37383d3 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/other/boolean-test.sql @@ -0,0 +1,135 @@ +-- 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 +-- + +SELECT + NULL IS UNKNOWN, FALSE IS UNKNOWN, TRUE IS UNKNOWN, + NULL IS FALSE, FALSE IS FALSE, TRUE IS FALSE, + NULL IS TRUE, FALSE IS TRUE, TRUE IS TRUE; +> TRUE FALSE FALSE FALSE TRUE FALSE FALSE FALSE TRUE +> ---- ----- ----- ----- ---- ----- ----- ----- ---- +> TRUE FALSE FALSE FALSE TRUE FALSE FALSE FALSE TRUE +> rows: 1 + +SELECT + NULL IS NOT UNKNOWN, FALSE IS NOT UNKNOWN, TRUE IS NOT UNKNOWN, + NULL IS NOT FALSE, FALSE IS NOT FALSE, TRUE IS NOT FALSE, + NULL IS NOT TRUE, FALSE IS NOT TRUE, TRUE IS NOT TRUE; +> FALSE TRUE TRUE TRUE FALSE TRUE TRUE TRUE FALSE +> ----- ---- ---- ---- ----- ---- ---- ---- ----- +> FALSE TRUE TRUE TRUE FALSE TRUE TRUE TRUE FALSE +> rows: 1 + +CREATE TABLE TEST(B BOOLEAN, N INT) AS VALUES (NULL, NULL), (FALSE, 0), (TRUE, 1); +> ok + +CREATE INDEX TEST_B_IDX ON TEST(B); +> ok + +CREATE INDEX TEST_N_IDX ON TEST(N); +> ok + +SELECT B, B IS UNKNOWN, N IS UNKNOWN, B IS FALSE, N IS FALSE, B IS TRUE, N IS TRUE FROM TEST; +> B B IS UNKNOWN N IS UNKNOWN B IS FALSE N IS FALSE B IS TRUE N IS TRUE +> ----- ------------ ------------ ---------- ---------- --------- --------- +> FALSE FALSE FALSE TRUE TRUE FALSE FALSE +> TRUE FALSE FALSE FALSE FALSE TRUE TRUE +> null TRUE TRUE FALSE FALSE FALSE FALSE +> rows: 3 + +SELECT B, B IS NOT UNKNOWN, N IS NOT UNKNOWN, B IS NOT FALSE, N IS NOT FALSE, B IS NOT TRUE, N IS NOT TRUE FROM TEST; +> B B IS NOT UNKNOWN N IS NOT UNKNOWN B IS NOT FALSE N IS NOT FALSE B IS NOT TRUE N IS NOT TRUE +> ----- ---------------- ---------------- -------------- -------------- ------------- ------------- +> FALSE TRUE TRUE FALSE FALSE TRUE TRUE +> TRUE TRUE TRUE TRUE TRUE FALSE FALSE +> null FALSE FALSE TRUE TRUE TRUE TRUE +> rows: 3 + +SELECT B, NOT B IS NOT UNKNOWN, NOT N IS NOT UNKNOWN, NOT B IS NOT FALSE, NOT N IS NOT FALSE, + NOT B IS NOT TRUE, NOT N IS NOT TRUE FROM TEST; +> B B IS UNKNOWN N IS UNKNOWN B IS FALSE N IS FALSE B IS TRUE N IS TRUE +> ----- ------------ ------------ ---------- ---------- --------- --------- +> FALSE FALSE FALSE TRUE TRUE FALSE FALSE +> TRUE FALSE FALSE FALSE FALSE TRUE TRUE +> null TRUE TRUE FALSE FALSE FALSE FALSE +> rows: 3 + +EXPLAIN SELECT B FROM TEST WHERE B IS UNKNOWN; +>> SELECT "B" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_B_IDX: B IS UNKNOWN */ WHERE "B" IS UNKNOWN + +SELECT B FROM TEST WHERE B IS UNKNOWN; +>> null + +EXPLAIN SELECT N FROM TEST WHERE N IS UNKNOWN; +>> SELECT "N" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_N_IDX */ WHERE "N" IS UNKNOWN + +EXPLAIN SELECT B FROM TEST WHERE B IS NOT UNKNOWN; +>> SELECT "B" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_B_IDX: B IN(FALSE, TRUE) */ WHERE "B" IS NOT UNKNOWN + +SELECT B FROM TEST WHERE B IS NOT UNKNOWN; +> B +> ----- +> FALSE +> TRUE +> rows: 2 + +EXPLAIN SELECT N FROM TEST WHERE N IS NOT UNKNOWN; +>> SELECT "N" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_N_IDX */ WHERE "N" IS NOT UNKNOWN + +EXPLAIN SELECT B FROM TEST WHERE B IS FALSE; +>> SELECT "B" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_B_IDX: B IS FALSE */ WHERE "B" IS FALSE + +SELECT B FROM TEST WHERE B IS FALSE; +>> FALSE + +EXPLAIN SELECT N FROM TEST WHERE N IS FALSE; +>> SELECT "N" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_N_IDX */ WHERE "N" IS FALSE + +EXPLAIN SELECT B FROM TEST WHERE B IS NOT FALSE; +>> SELECT "B" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_B_IDX */ WHERE "B" IS NOT FALSE + +SELECT B FROM TEST WHERE B IS NOT FALSE; +> B +> ---- +> TRUE +> null +> rows: 2 + +EXPLAIN SELECT N FROM TEST WHERE N IS NOT FALSE; +>> SELECT "N" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_N_IDX */ WHERE "N" IS NOT FALSE + +EXPLAIN SELECT B FROM TEST WHERE B IS TRUE; +>> SELECT "B" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_B_IDX: B IS TRUE */ WHERE "B" IS TRUE + +SELECT B FROM TEST WHERE B IS TRUE; +>> TRUE + +EXPLAIN SELECT N FROM TEST WHERE N IS TRUE; +>> SELECT "N" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_N_IDX */ WHERE "N" IS TRUE + +EXPLAIN SELECT B FROM TEST WHERE B IS NOT TRUE; +>> SELECT "B" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_B_IDX */ WHERE "B" IS NOT TRUE + +SELECT B FROM TEST WHERE B IS NOT TRUE; +> B +> ----- +> FALSE +> null +> rows: 2 + +EXPLAIN SELECT N FROM TEST WHERE N IS NOT TRUE; +>> SELECT "N" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_N_IDX */ WHERE "N" IS NOT TRUE + +DELETE FROM TEST WHERE B IS NULL; +> update count: 1 + +ALTER TABLE TEST ALTER COLUMN B SET NOT NULL; +> ok + +-- If column is NOT NULL index condition for IS NOT UNKNOWN shouldn't exist +EXPLAIN SELECT B FROM TEST WHERE B IS NOT UNKNOWN; +>> SELECT "B" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_B_IDX */ WHERE "B" IS NOT UNKNOWN + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/other/case.sql b/h2/src/test/org/h2/test/scripts/other/case.sql new file mode 100644 index 0000000..f2fdc6c --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/other/case.sql @@ -0,0 +1,133 @@ +-- 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 +-- + +select case when 1=null then 1 else 2 end; +>> 2 + +select case (1) when 1 then 1 else 2 end; +>> 1 + +select x, case when x=0 then 'zero' else 'not zero' end y from system_range(0, 2); +> X Y +> - -------- +> 0 zero +> 1 not zero +> 2 not zero +> rows: 3 + +select x, case when x=0 then 'zero' end y from system_range(0, 1); +> X Y +> - ---- +> 0 zero +> 1 null +> rows: 2 + +select x, case x when 0 then 'zero' else 'not zero' end y from system_range(0, 1); +> X Y +> - -------- +> 0 zero +> 1 not zero +> rows: 2 + +select x, case x when 0 then 'zero' when 1 then 'one' end y from system_range(0, 2); +> X Y +> - ---- +> 0 zero +> 1 one +> 2 null +> rows: 3 + +SELECT X, CASE X WHEN 1 THEN 10 WHEN 2, 3 THEN 25 WHEN 4, 5, 6 THEN 50 ELSE 90 END C FROM SYSTEM_RANGE(1, 7); +> X C +> - -- +> 1 10 +> 2 25 +> 3 25 +> 4 50 +> 5 50 +> 6 50 +> 7 90 +> rows: 7 + +SELECT CASE WHEN TRUE THEN 1 END CASE; +> exception SYNTAX_ERROR_1 + +SELECT S, CASE S + WHEN IS NULL THEN 1 + WHEN LOWER('A') THEN 2 + WHEN LIKE '%b' THEN 3 + WHEN ILIKE 'C' THEN 4 + WHEN REGEXP '[dQ]' THEN 5 + WHEN IS NOT DISTINCT FROM 'e' THEN 6 + WHEN IN ('x', 'f') THEN 7 + WHEN IN (VALUES 'g', 'z') THEN 8 + WHEN BETWEEN 'h' AND 'i' THEN 9 + WHEN = 'j' THEN 10 + WHEN < ANY(VALUES 'j', 'l') THEN 11 + WHEN NOT LIKE '%m%' THEN 12 + WHEN IS OF (VARCHAR) THEN 13 + ELSE 13 + END FROM (VALUES NULL, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm') T(S); +> S C2 +> ---- -- +> a 2 +> b 3 +> c 4 +> d 5 +> e 6 +> f 7 +> g 8 +> h 9 +> i 9 +> j 10 +> k 11 +> l 12 +> m 13 +> null 1 +> rows: 14 + +SELECT B, CASE B WHEN IS TRUE THEN 1 WHEN IS FALSE THEN 0 WHEN IS UNKNOWN THEN -1 END + FROM (VALUES TRUE, FALSE, UNKNOWN) T(B); +> B CASE B WHEN IS TRUE THEN 1 WHEN IS FALSE THEN 0 WHEN IS UNKNOWN THEN -1 END +> ----- --------------------------------------------------------------------------- +> FALSE 0 +> TRUE 1 +> null -1 +> rows: 3 + +SELECT J, CASE J WHEN IS JSON ARRAY THEN 1 WHEN IS NOT JSON OBJECT THEN 2 ELSE 3 END + FROM (VALUES JSON '[]', JSON 'true', JSON '{}') T(J); +> J CASE J WHEN IS JSON ARRAY THEN 1 WHEN IS NOT JSON OBJECT THEN 2 ELSE 3 END +> ---- -------------------------------------------------------------------------- +> [] 1 +> true 2 +> {} 3 +> rows: 3 + +SELECT V, CASE V + WHEN IN(CURRENT_DATE, DATE '2010-01-01') THEN 1 + ELSE 2 + END FROM (VALUES DATE '2000-01-01', DATE '2010-01-01', DATE '2020-02-01') T(V); +> V CASE V WHEN IN(CURRENT_DATE, DATE '2010-01-01') THEN 1 ELSE 2 END +> ---------- ----------------------------------------------------------------- +> 2000-01-01 2 +> 2010-01-01 1 +> 2020-02-01 2 +> rows: 3 + +SELECT CASE NULL WHEN IS NOT DISTINCT FROM NULL THEN TRUE ELSE FALSE END; +>> TRUE + +SELECT CASE TRUE WHEN CURRENT_DATE THEN 1 END; +> exception TYPES_ARE_NOT_COMPARABLE_2 + +SELECT * FROM (VALUES 0) D(X) JOIN (VALUES TRUE) T(C) WHERE (CASE C WHEN C THEN C END); +> X C +> - ---- +> 0 TRUE +> rows: 1 + +SELECT CASE TRUE WHEN NOT FALSE THEN 1 ELSE 0 END; +>> 1 diff --git a/h2/src/test/org/h2/test/scripts/other/concatenation.sql b/h2/src/test/org/h2/test/scripts/other/concatenation.sql new file mode 100644 index 0000000..f61452a --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/other/concatenation.sql @@ -0,0 +1,50 @@ +-- 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 +-- + +CREATE TABLE TEST(S VARCHAR(10), B VARBINARY(10), A VARCHAR(10) ARRAY) AS VALUES + ('a', X'49', ARRAY['b']), ('', X'', ARRAY[]), (NULL, NULL, NULL); +> ok + +EXPLAIN SELECT S || 'v' || '' || 'x' || S || (S || S), S || '', S || (B || X'50'), B || B || B FROM TEST; +>> SELECT "S" || 'vx' || "S" || "S" || "S", "S", "S" || ("B" || X'50'), "B" || "B" || "B" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +SELECT S || 'v' || '' || 'x' || S || (S || S), S || '', S || (B || X'50'), B || B || B FROM TEST; +> S || 'vx' || S || S || S S S || (B || X'50') B || B || B +> ------------------------ ---- ----------------- ----------- +> avxaaa a aIP X'494949' +> null null null null +> vx P X'' +> rows: 3 + +EXPLAIN SELECT S || A, ARRAY[] || A, S || CAST(ARRAY[] AS VARCHAR ARRAY), A || A || A FROM TEST; +>> SELECT "S" || "A", "A", CAST("S" AS CHARACTER VARYING ARRAY), "A" || "A" || "A" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +SELECT S || A, ARRAY[] || A, S || CAST(ARRAY[] AS VARCHAR ARRAY), A || A || A FROM TEST; +> S || A A CAST(S AS CHARACTER VARYING ARRAY) A || A || A +> ------ ---- ---------------------------------- ----------- +> [] [] [] [] +> [a, b] [b] [a] [b, b, b] +> null null null null +> rows: 3 + +EXPLAIN SELECT B || NULL, B || X'22' || NULL FROM TEST; +>> SELECT CAST(NULL AS BINARY VARYING(10)), CAST(NULL AS BINARY VARYING(11)) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +SELECT B || NULL, B || X'22' || NULL FROM TEST; +> CAST(NULL AS BINARY VARYING(10)) CAST(NULL AS BINARY VARYING(11)) +> -------------------------------- -------------------------------- +> null null +> null null +> null null +> rows: 3 + +EXPLAIN SELECT B || X'', A || ARRAY['a'] FROM TEST; +>> SELECT "B", "A" || ARRAY ['a'] FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT (S || S) || (B || B) FROM TEST; +>> SELECT "S" || "S" || ("B" || "B") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/other/conditions.sql b/h2/src/test/org/h2/test/scripts/other/conditions.sql new file mode 100644 index 0000000..ae1444f --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/other/conditions.sql @@ -0,0 +1,168 @@ +-- 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 +-- + +SELECT + NULL AND NULL, NULL AND FALSE, NULL AND TRUE, + FALSE AND NULL, FALSE AND FALSE, FALSE AND TRUE, + TRUE AND NULL, TRUE AND FALSE, TRUE AND TRUE; +> UNKNOWN FALSE UNKNOWN FALSE FALSE FALSE UNKNOWN FALSE TRUE +> ------- ----- ------- ----- ----- ----- ------- ----- ---- +> null FALSE null FALSE FALSE FALSE null FALSE TRUE +> rows: 1 + +SELECT + NULL OR NULL, NULL OR FALSE, NULL OR TRUE, + FALSE OR NULL, FALSE OR FALSE, FALSE OR TRUE, + TRUE OR NULL, TRUE OR FALSE, TRUE OR TRUE; +> UNKNOWN UNKNOWN TRUE UNKNOWN FALSE TRUE TRUE TRUE TRUE +> ------- ------- ---- ------- ----- ---- ---- ---- ---- +> null null TRUE null FALSE TRUE TRUE TRUE TRUE +> rows: 1 + +SELECT NOT NULL, NOT FALSE, NOT TRUE; +> UNKNOWN TRUE FALSE +> ------- ---- ----- +> null TRUE FALSE +> rows: 1 + +SELECT 0 AND TRUE; +>> FALSE + +SELECT TRUE AND 0; +>> FALSE + +SELECT 1 OR FALSE; +>> TRUE + +SELECT FALSE OR 1; +>> TRUE + +SELECT NOT 0; +>> TRUE + +SELECT NOT 1; +>> FALSE + +CREATE TABLE TEST(B BOOLEAN, Z INT) AS VALUES (NULL, 0); +> ok + +EXPLAIN SELECT NOT NOT B, NOT NOT Z FROM TEST; +>> SELECT "B", CAST("Z" AS BOOLEAN) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT TRUE AND B, B AND TRUE, TRUE AND Z, Z AND TRUE FROM TEST; +>> SELECT "B", "B", CAST("Z" AS BOOLEAN), CAST("Z" AS BOOLEAN) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT FALSE OR B, B OR FALSE, FALSE OR Z, Z OR FALSE FROM TEST; +>> SELECT "B", "B", CAST("Z" AS BOOLEAN), CAST("Z" AS BOOLEAN) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INT, B INT); +> ok + +EXPLAIN SELECT A FROM TEST WHERE (A, B) IS NOT DISTINCT FROM NULL; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ WHERE ROW ("A", "B") IS NOT DISTINCT FROM NULL + +EXPLAIN SELECT A FROM TEST WHERE (A, B) IS DISTINCT FROM NULL; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ WHERE ROW ("A", "B") IS DISTINCT FROM NULL + +EXPLAIN SELECT A IS DISTINCT FROM NULL, NULL IS DISTINCT FROM A FROM TEST; +>> SELECT "A" IS NOT NULL, "A" IS NOT NULL FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT A IS NOT DISTINCT FROM NULL, NULL IS NOT DISTINCT FROM A FROM TEST; +>> SELECT "A" IS NULL, "A" IS NULL FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A NULL); +> ok + +SELECT 1 IN (SELECT A FROM TEST); +>> FALSE + +INSERT INTO TEST VALUES NULL; +> update count: 1 + +SELECT 1 IN (SELECT A FROM TEST); +>> null + +DROP TABLE TEST; +> ok + +SELECT 1 IN (NULL); +>> null + +SELECT 1 IN (SELECT NULL); +>> null + +SELECT 1 IN (VALUES NULL); +>> null + +SELECT 1 IN (SELECT * FROM TABLE(X NULL=())); +>> FALSE + +SELECT (1, 1) IN (VALUES (1, NULL)); +>> null + +SELECT (1, 1) IN (VALUES (NULL, 1)); +>> null + +SELECT (1, 1) IN (SELECT * FROM TABLE(X INT=(), Y INT=())); +>> FALSE + +VALUES FALSE OR NULL OR FALSE; +>> null + +VALUES FALSE OR NULL OR TRUE; +>> TRUE + +VALUES TRUE AND NULL AND TRUE; +>> null + +VALUES TRUE AND NULL AND FALSE; +>> FALSE + +SELECT * FROM (VALUES 1) T(C) WHERE NOT NOT CASE C WHEN 1 THEN TRUE WHEN 2 THEN FALSE ELSE NULL END; +>> 1 + +SELECT C AND C, NOT(C AND C) FROM (VALUES 'F') T(C); +> C AND C (NOT C) OR (NOT C) +> ------- ------------------ +> FALSE TRUE +> rows: 1 + +SELECT C != 2 AND C, NOT (C != 2 AND C) FROM (VALUES TRUE) T(C); +> exception TYPES_ARE_NOT_COMPARABLE_2 + +SELECT ROW(1) = ROW(ROW(1)); +>> TRUE + +SELECT ROW(1) = ROW(ROW(2)); +>> FALSE + +SELECT ROW(1) = ROW(ROW(1, 2)); +> exception TYPES_ARE_NOT_COMPARABLE_2 + +SELECT ROW(1) = ROW(ROW(TIME '00:00:00')); +> exception TYPES_ARE_NOT_COMPARABLE_2 + +CREATE TABLE TEST(C1 BOOLEAN GENERATED ALWAYS AS (NOT C2), C2 BOOLEAN GENERATED ALWAYS AS (C1)); +> exception COLUMN_NOT_FOUND_1 + +CREATE TABLE TEST(A INTEGER, B INTEGER, C INTEGER, D INTEGER) AS VALUES (1, 2, 3, 4); +> ok + +EXPLAIN SELECT A = B OR A = C C1, B = A OR A = C C2, A = B OR C = A C3, B = A OR C = A C4 FROM TEST; +>> SELECT "A" IN("B", "C") AS "C1", "A" IN("B", "C") AS "C2", "A" IN("B", "C") AS "C3", "A" IN("B", "C") AS "C4" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT A = B OR A = C OR A = D C1, B = A OR A = C OR A = D C2, A = B OR C = A OR A = D C3, + B = A OR C = A OR A = D C4, A = B OR A = C OR D = A C5, B = A OR A = C OR D = A C6, A = B OR C = A OR D = A C7, + B = A OR C = A OR D = A C8 FROM TEST; +>> SELECT "A" IN("B", "C", "D") AS "C1", "A" IN("B", "C", "D") AS "C2", "A" IN("B", "C", "D") AS "C3", "A" IN("B", "C", "D") AS "C4", "A" IN("B", "C", "D") AS "C5", "A" IN("B", "C", "D") AS "C6", "A" IN("B", "C", "D") AS "C7", "A" IN("B", "C", "D") AS "C8" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/other/data-change-delta-table.sql b/h2/src/test/org/h2/test/scripts/other/data-change-delta-table.sql new file mode 100644 index 0000000..f804038 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/other/data-change-delta-table.sql @@ -0,0 +1,417 @@ +-- 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 +-- + +CREATE TABLE TEST(ID BIGINT AUTO_INCREMENT PRIMARY KEY, A INT, B INT); +> ok + +CREATE TRIGGER T1 BEFORE INSERT, UPDATE ON TEST FOR EACH ROW CALL "org.h2.test.scripts.Trigger1"; +> ok + +-- INSERT + +SELECT * FROM OLD TABLE (INSERT INTO TEST(A, B) VALUES (100, 100)); +> exception SYNTAX_ERROR_2 + +SELECT * FROM NEW TABLE (INSERT INTO TEST(A, B) VALUES (1, 2)); +> ID A B +> -- - - +> 1 1 2 +> rows: 1 + +SELECT * FROM FINAL TABLE (INSERT INTO TEST(A, B) VALUES (2, 3)); +> ID A B +> -- - -- +> 2 2 30 +> rows: 1 + +-- INSERT from SELECT + +SELECT * FROM NEW TABLE (INSERT INTO TEST(A, B) SELECT * FROM VALUES (3, 4), (4, 5)); +> ID A B +> -- - - +> 3 3 4 +> 4 4 5 +> rows: 2 + +SELECT * FROM FINAL TABLE (INSERT INTO TEST(A, B) SELECT * FROM VALUES (5, 6), (6, 7)); +> ID A B +> -- - -- +> 5 5 60 +> 6 6 70 +> rows: 2 + +-- UPDATE + +SELECT * FROM OLD TABLE (UPDATE TEST SET B = 3 WHERE ID = 1); +> ID A B +> -- - -- +> 1 1 20 +> rows: 1 + +SELECT * FROM NEW TABLE (UPDATE TEST SET B = 3 WHERE ID = 1); +> ID A B +> -- - - +> 1 1 3 +> rows: 1 + +SELECT * FROM FINAL TABLE (UPDATE TEST SET B = 3 WHERE ID = 1); +> ID A B +> -- - -- +> 1 1 30 +> rows: 1 + +-- DELETE + +SELECT * FROM OLD TABLE (DELETE FROM TEST WHERE ID = 1); +> ID A B +> -- - -- +> 1 1 30 +> rows: 1 + +SELECT * FROM OLD TABLE (DELETE FROM TEST WHERE ID = ?); +{ +2 +> ID A B +> -- - -- +> 2 2 30 +> rows: 1 +100 +> ID A B +> -- - - +> rows: 0 +}; +> update count: 0 + +SELECT * FROM NEW TABLE (DELETE FROM TEST); +> exception SYNTAX_ERROR_2 + +SELECT * FROM FINAL TABLE (DELETE FROM TEST); +> exception SYNTAX_ERROR_2 + +SELECT * FROM TEST TABLE (DELETE FROM TEST); +> exception SYNTAX_ERROR_2 + +-- MERGE INTO + +SELECT * FROM OLD TABLE (MERGE INTO TEST KEY(ID) VALUES (3, 3, 5), (7, 7, 8)); +> ID A B +> -- - -- +> 3 3 40 +> rows: 1 + +SELECT * FROM NEW TABLE (MERGE INTO TEST KEY(ID) VALUES (4, 4, 6), (8, 8, 9)); +> ID A B +> -- - - +> 4 4 6 +> 8 8 9 +> rows: 2 + +SELECT * FROM FINAL TABLE (MERGE INTO TEST KEY(ID) VALUES (5, 5, 7), (9, 9, 10)); +> ID A B +> -- - --- +> 5 5 70 +> 9 9 100 +> rows: 2 + +-- MERGE INTO from SELECT + +SELECT * FROM OLD TABLE (MERGE INTO TEST KEY(ID) SELECT * FROM VALUES (3, 3, 6), (10, 10, 11)); +> ID A B +> -- - -- +> 3 3 50 +> rows: 1 + +SELECT * FROM NEW TABLE (MERGE INTO TEST KEY(ID) SELECT * FROM VALUES (4, 4, 7), (11, 11, 12)); +> ID A B +> -- -- -- +> 11 11 12 +> 4 4 7 +> rows: 2 + +SELECT * FROM FINAL TABLE (MERGE INTO TEST KEY(ID) SELECT * FROM VALUES (5, 5, 8), (12, 12, 13)); +> ID A B +> -- -- --- +> 12 12 130 +> 5 5 80 +> rows: 2 + +-- MERGE USING + +SELECT * FROM OLD TABLE (MERGE INTO TEST USING + (VALUES (3, 3, 7), (10, 10, 12), (13, 13, 14)) S(ID, A, B) + ON TEST.ID = S.ID + WHEN MATCHED AND S.ID = 3 THEN UPDATE SET TEST.B = S.B + WHEN MATCHED AND S.ID <> 3 THEN DELETE + WHEN NOT MATCHED THEN INSERT VALUES (S.ID, S.A, S.B)); +> ID A B +> -- -- --- +> 10 10 110 +> 3 3 60 +> rows: 2 + +SELECT * FROM NEW TABLE (MERGE INTO TEST USING + (VALUES (4, 4, 8), (11, 11, 13), (14, 14, 15)) S(ID, A, B) + ON TEST.ID = S.ID + WHEN MATCHED AND S.ID = 4 THEN UPDATE SET TEST.B = S.B + WHEN MATCHED AND S.ID <> 4 THEN DELETE + WHEN NOT MATCHED THEN INSERT VALUES (S.ID, S.A, S.B)); +> ID A B +> -- -- -- +> 14 14 15 +> 4 4 8 +> rows: 2 + +SELECT * FROM FINAL TABLE (MERGE INTO TEST USING + (VALUES (5, 5, 9), (12, 12, 15), (15, 15, 16)) S(ID, A, B) + ON TEST.ID = S.ID + WHEN MATCHED AND S.ID = 5 THEN UPDATE SET TEST.B = S.B + WHEN MATCHED AND S.ID <> 5 THEN DELETE + WHEN NOT MATCHED THEN INSERT VALUES (S.ID, S.A, S.B)); +> ID A B +> -- -- --- +> 15 15 160 +> 5 5 90 +> rows: 2 + +-- REPLACE + +SELECT * FROM OLD TABLE (REPLACE INTO TEST VALUES (3, 3, 8), (16, 16, 17)); +> exception SYNTAX_ERROR_2 + +SELECT * FROM NEW TABLE (REPLACE INTO TEST VALUES (4, 4, 9), (17, 17, 18)); +> exception SYNTAX_ERROR_2 + +SELECT * FROM FINAL TABLE (REPLACE INTO TEST VALUES (5, 5, 10), (18, 18, 19)); +> exception SYNTAX_ERROR_2 + +SET MODE MySQL; +> ok + +SELECT * FROM OLD TABLE (REPLACE INTO TEST VALUES (3, 3, 8), (16, 16, 17)); +> ID A B +> -- - -- +> 3 3 70 +> rows: 1 + +SELECT * FROM NEW TABLE (REPLACE INTO TEST VALUES (4, 4, 9), (17, 17, 18)); +> ID A B +> -- -- -- +> 17 17 18 +> 4 4 9 +> rows: 2 + +SELECT * FROM FINAL TABLE (REPLACE INTO TEST VALUES (5, 5, 10), (18, 18, 19)); +> ID A B +> -- -- --- +> 18 18 190 +> 5 5 100 +> rows: 2 + +-- REPLACE from SELECT + +SELECT * FROM OLD TABLE (REPLACE INTO TEST SELECT * FROM VALUES (3, 3, 9), (19, 19, 20)); +> ID A B +> -- - -- +> 3 3 80 +> rows: 1 + +SELECT * FROM NEW TABLE (REPLACE INTO TEST SELECT * FROM VALUES (4, 4, 10), (20, 20, 21)); +> ID A B +> -- -- -- +> 20 20 21 +> 4 4 10 +> rows: 2 + +SELECT * FROM FINAL TABLE (REPLACE INTO TEST SELECT * FROM VALUES (5, 5, 11), (21, 21, 22)); +> ID A B +> -- -- --- +> 21 21 220 +> 5 5 110 +> rows: 2 + +SET MODE Regular; +> ok + +TRUNCATE TABLE TEST RESTART IDENTITY; +> update count: 16 + +CREATE VIEW TEST_VIEW AS SELECT * FROM TEST; +> ok + +CREATE TRIGGER T2 INSTEAD OF INSERT, UPDATE, DELETE ON TEST_VIEW FOR EACH ROW CALL "org.h2.test.scripts.Trigger2"; +> ok + +-- INSERT + +SELECT * FROM NEW TABLE (INSERT INTO TEST_VIEW(A, B) VALUES (1, 2)); +> ID A B +> ---- - - +> null 1 2 +> rows: 1 + +SELECT * FROM FINAL TABLE (INSERT INTO TEST_VIEW(A, B) VALUES (2, 3)); +> ID A B +> -- - -- +> 2 2 30 +> rows: 1 + +-- INSERT from SELECT + +SELECT * FROM NEW TABLE (INSERT INTO TEST_VIEW(A, B) SELECT * FROM VALUES (3, 4), (4, 5)); +> ID A B +> ---- - - +> null 3 4 +> null 4 5 +> rows: 2 + +SELECT * FROM FINAL TABLE (INSERT INTO TEST_VIEW(A, B) SELECT * FROM VALUES (5, 6), (6, 7)); +> ID A B +> -- - -- +> 5 5 60 +> 6 6 70 +> rows: 2 + +-- UPDATE + +SELECT * FROM OLD TABLE (UPDATE TEST_VIEW SET B = 3 WHERE ID = 1); +> ID A B +> -- - -- +> 1 1 20 +> rows: 1 + +SELECT * FROM NEW TABLE (UPDATE TEST_VIEW SET B = 3 WHERE ID = 1); +> ID A B +> -- - - +> 1 1 3 +> rows: 1 + +SELECT * FROM FINAL TABLE (UPDATE TEST_VIEW SET B = 3 WHERE ID = 1); +> ID A B +> -- - -- +> 1 1 30 +> rows: 1 + +-- DELETE + +SELECT * FROM OLD TABLE (DELETE FROM TEST_VIEW WHERE ID = 1); +> ID A B +> -- - -- +> 1 1 30 +> rows: 1 + +SELECT * FROM OLD TABLE (DELETE FROM TEST_VIEW WHERE ID = ?); +{ +2 +> ID A B +> -- - -- +> 2 2 30 +> rows: 1 +100 +> ID A B +> -- - - +> rows: 0 +}; +> update count: 0 + +-- MERGE INTO + +SELECT * FROM OLD TABLE (MERGE INTO TEST_VIEW KEY(ID) VALUES (3, 3, 5), (7, 7, 8)); +> ID A B +> -- - -- +> 3 3 40 +> rows: 1 + +SELECT * FROM NEW TABLE (MERGE INTO TEST_VIEW KEY(ID) VALUES (4, 4, 6), (8, 8, 9)); +> ID A B +> -- - - +> 4 4 6 +> 8 8 9 +> rows: 2 + +SELECT * FROM FINAL TABLE (MERGE INTO TEST_VIEW KEY(ID) VALUES (5, 5, 7), (9, 9, 10)); +> ID A B +> -- - --- +> 5 5 70 +> 9 9 100 +> rows: 2 + +-- MERGE INTO from SELECT + +SELECT * FROM OLD TABLE (MERGE INTO TEST_VIEW KEY(ID) SELECT * FROM VALUES (3, 3, 6), (10, 10, 11)); +> ID A B +> -- - -- +> 3 3 50 +> rows: 1 + +SELECT * FROM NEW TABLE (MERGE INTO TEST_VIEW KEY(ID) SELECT * FROM VALUES (4, 4, 7), (11, 11, 12)); +> ID A B +> -- -- -- +> 11 11 12 +> 4 4 7 +> rows: 2 + +SELECT * FROM FINAL TABLE (MERGE INTO TEST_VIEW KEY(ID) SELECT * FROM VALUES (5, 5, 8), (12, 12, 13)); +> ID A B +> -- -- --- +> 12 12 130 +> 5 5 80 +> rows: 2 + +-- MERGE USING + +SELECT * FROM OLD TABLE (MERGE INTO TEST_VIEW TEST USING + (VALUES (3, 3, 7), (10, 10, 12), (13, 13, 14)) S(ID, A, B) + ON TEST.ID = S.ID + WHEN MATCHED AND S.ID = 3 THEN UPDATE SET TEST.B = S.B + WHEN MATCHED AND S.ID <> 3 THEN DELETE + WHEN NOT MATCHED THEN INSERT VALUES (S.ID, S.A, S.B)); +> ID A B +> -- -- --- +> 10 10 110 +> 3 3 60 +> rows: 2 + +SELECT * FROM NEW TABLE (MERGE INTO TEST_VIEW TEST USING + (VALUES (4, 4, 8), (11, 11, 13), (14, 14, 15)) S(ID, A, B) + ON TEST.ID = S.ID + WHEN MATCHED AND S.ID = 4 THEN UPDATE SET TEST.B = S.B + WHEN MATCHED AND S.ID <> 4 THEN DELETE + WHEN NOT MATCHED THEN INSERT VALUES (S.ID, S.A, S.B)); +> ID A B +> -- -- -- +> 14 14 15 +> 4 4 8 +> rows: 2 + +DROP TABLE TEST CASCADE; +> ok + +CREATE TABLE TEST(ID BIGINT, DATA CHARACTER LARGE OBJECT); +> ok + +INSERT INTO TEST VALUES (1, REPEAT('A', 1000)); +> update count: 1 + +SELECT ID FROM FINAL TABLE (INSERT INTO TEST VALUES (2, REPEAT('B', 1000))); +>> 2 + +SELECT ID, SUBSTRING(DATA FROM 1 FOR 2) FROM TEST; +> ID SUBSTRING(DATA FROM 1 FOR 2) +> -- ---------------------------- +> 1 AA +> 2 BB +> rows: 2 + +@reconnect + +SELECT ID, SUBSTRING(DATA FROM 1 FOR 2) FROM TEST; +> ID SUBSTRING(DATA FROM 1 FOR 2) +> -- ---------------------------- +> 1 AA +> 2 BB +> rows: 2 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/other/field-reference.sql b/h2/src/test/org/h2/test/scripts/other/field-reference.sql new file mode 100644 index 0000000..203ea53 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/other/field-reference.sql @@ -0,0 +1,31 @@ +-- 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 +-- + +SELECT (R).A, (R).B FROM (VALUES CAST((1, 2) AS ROW(A INT, B INT))) T(R); +> (R).A (R).B +> ----- ----- +> 1 2 +> rows: 1 + +SELECT (R).C FROM (VALUES CAST((1, 2) AS ROW(A INT, B INT))) T(R); +> exception COLUMN_NOT_FOUND_1 + +SELECT (R).C1, (R).C2 FROM (VALUES ((1, 2))) T(R); +> (R).C1 (R).C2 +> ------ ------ +> 1 2 +> rows: 1 + +SELECT (1, 2).C2; +>> 2 + +SELECT (1, 2).C0; +> exception COLUMN_NOT_FOUND_1 + +SELECT (1, 2).C; +> exception COLUMN_NOT_FOUND_1 + +SELECT (1, 2).CX; +> exception COLUMN_NOT_FOUND_1 diff --git a/h2/src/test/org/h2/test/scripts/other/help.sql b/h2/src/test/org/h2/test/scripts/other/help.sql new file mode 100644 index 0000000..efd05de --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/other/help.sql @@ -0,0 +1,26 @@ +-- 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 +-- + +help abc; +> SECTION TOPIC SYNTAX TEXT +> ------- ----- ------ ---- +> rows: 0 + +HELP ABCDE EF_GH; +> SECTION TOPIC SYNTAX TEXT +> ------- ----- ------ ---- +> rows: 0 + +HELP HELP; +> SECTION TOPIC SYNTAX TEXT +> ---------------- ----- ----------------------- ---------------------------------------------------- +> Commands (Other) HELP HELP [ anything [...] ] Displays the help pages of SQL commands or keywords. +> rows: 1 + +HELP he lp; +> SECTION TOPIC SYNTAX TEXT +> ---------------- ----- ----------------------- ---------------------------------------------------- +> Commands (Other) HELP HELP [ anything [...] ] Displays the help pages of SQL commands or keywords. +> rows: 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 new file mode 100644 index 0000000..16c2e25 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/other/sequence.sql @@ -0,0 +1,481 @@ +-- 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 +-- + +CREATE SEQUENCE SEQ NO CACHE; +> ok + +CREATE TABLE TEST(NEXT INT, CURRENT INT) AS (VALUES (10, 11), (20, 21)); +> ok + +SELECT NEXT "VALUE", NEXT VALUE FOR SEQ, CURRENT "VALUE", CURRENT VALUE FOR SEQ FROM TEST; +> VALUE NEXT VALUE FOR PUBLIC.SEQ VALUE CURRENT VALUE FOR PUBLIC.SEQ +> ----- ------------------------- ----- ---------------------------- +> 10 1 11 1 +> 20 2 21 2 +> rows: 2 + +EXPLAIN SELECT NEXT "VALUE", NEXT VALUE FOR SEQ, CURRENT "VALUE", CURRENT VALUE FOR SEQ FROM TEST; +>> SELECT "NEXT" AS "VALUE", NEXT VALUE FOR "PUBLIC"."SEQ", "CURRENT" AS "VALUE", CURRENT VALUE FOR "PUBLIC"."SEQ" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +DROP TABLE TEST; +> ok + +DROP SEQUENCE SEQ; +> ok + +CREATE SEQUENCE S1 START WITH 11; +> ok + +CREATE SEQUENCE S2 START WITH 61; +> ok + +SELECT NEXT VALUE FOR S1 A, NEXT VALUE FOR S2 B, NEXT VALUE FOR S1 C, NEXT VALUE FOR S2 D FROM SYSTEM_RANGE(1, 2); +> A B C D +> -- -- -- -- +> 11 61 11 61 +> 12 62 12 62 +> rows: 2 + +CREATE TABLE TEST(A BIGINT, B BIGINT, C BIGINT, D BIGINT, V INT) AS + SELECT NEXT VALUE FOR S1, NEXT VALUE FOR S2, NEXT VALUE FOR S1, NEXT VALUE FOR S2, X FROM SYSTEM_RANGE(1, 2); +> ok + +INSERT INTO TEST + SELECT NEXT VALUE FOR S1, NEXT VALUE FOR S2, NEXT VALUE FOR S1, NEXT VALUE FOR S2, X FROM SYSTEM_RANGE(3, 4); +> update count: 2 + +INSERT INTO TEST VALUES + (NEXT VALUE FOR S1, NEXT VALUE FOR S2, NEXT VALUE FOR S1, NEXT VALUE FOR S2, 5), + (NEXT VALUE FOR S1, NEXT VALUE FOR S2, NEXT VALUE FOR S1, NEXT VALUE FOR S2, 6); +> update count: 2 + +TABLE TEST; +> A B C D V +> -- -- -- -- - +> 13 63 13 63 1 +> 14 64 14 64 2 +> 15 65 15 65 3 +> 16 66 16 66 4 +> 17 67 17 67 5 +> 18 68 18 68 6 +> rows: 6 + +UPDATE TEST SET A = NEXT VALUE FOR S1, B = NEXT VALUE FOR S2, C = NEXT VALUE FOR S1, D = NEXT VALUE FOR S2 + WHERE V BETWEEN 3 AND 4; +> update count: 2 + +TABLE TEST; +> A B C D V +> -- -- -- -- - +> 13 63 13 63 1 +> 14 64 14 64 2 +> 17 67 17 67 5 +> 18 68 18 68 6 +> 19 69 19 69 3 +> 20 70 20 70 4 +> rows: 6 + +MERGE INTO TEST D USING (VALUES 7, 8) S ON D.V = S.C1 + WHEN NOT MATCHED THEN INSERT VALUES + (NEXT VALUE FOR S1, NEXT VALUE FOR S2, NEXT VALUE FOR S1, NEXT VALUE FOR S2, S.C1); +> update count: 2 + +TABLE TEST; +> A B C D V +> -- -- -- -- - +> 13 63 13 63 1 +> 14 64 14 64 2 +> 17 67 17 67 5 +> 18 68 18 68 6 +> 19 69 19 69 3 +> 20 70 20 70 4 +> 21 71 21 71 7 +> 22 72 22 72 8 +> rows: 8 + +MERGE INTO TEST D USING (VALUES 7, 8) S ON D.V = S.C1 + WHEN MATCHED THEN UPDATE + SET A = NEXT VALUE FOR S1, B = NEXT VALUE FOR S2, C = NEXT VALUE FOR S1, D = NEXT VALUE FOR S2; +> update count: 2 + +TABLE TEST; +> A B C D V +> -- -- -- -- - +> 13 63 13 63 1 +> 14 64 14 64 2 +> 17 67 17 67 5 +> 18 68 18 68 6 +> 19 69 19 69 3 +> 20 70 20 70 4 +> 23 73 23 73 7 +> 24 74 24 74 8 +> rows: 8 + +DROP TABLE TEST; +> ok + +SET MODE MariaDB; +> ok + +SELECT NEXT VALUE FOR S1 A, NEXT VALUE FOR S2 B, NEXT VALUE FOR S1 C, NEXT VALUE FOR S2 D FROM SYSTEM_RANGE(1, 2); +> A B C D +> -- -- -- -- +> 25 75 26 76 +> 27 77 28 78 +> rows: 2 + +SET MODE Regular; +> ok + +DROP SEQUENCE S1; +> ok + +DROP SEQUENCE S2; +> ok + +CREATE SEQUENCE SEQ; +> ok + +SELECT SEQ.NEXTVAL; +> exception COLUMN_NOT_FOUND_1 + +SELECT SEQ.CURRVAL; +> exception COLUMN_NOT_FOUND_1 + +DROP SEQUENCE SEQ; +> ok + +SET MODE Oracle; +> ok + +create sequence seq; +> ok + +select case seq.nextval when 2 then 'two' when 3 then 'three' when 1 then 'one' else 'other' end result from dual; +> RESULT +> ------ +> one +> rows: 1 + +drop sequence seq; +> ok + +create schema s authorization sa; +> ok + +alter sequence if exists s.seq restart with 10; +> ok + +create sequence s.seq cache 0; +> ok + +alter sequence if exists s.seq restart with 3; +> ok + +select s.seq.nextval as x; +> X +> - +> 3 +> rows: 1 + +drop sequence s.seq; +> ok + +create sequence s.seq cache 0; +> ok + +alter sequence s.seq restart with 10; +> ok + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION DROP; +> SCRIPT +> ---------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE SCHEMA IF NOT EXISTS "S" AUTHORIZATION "SA"; +> DROP SEQUENCE IF EXISTS "S"."SEQ"; +> CREATE SEQUENCE "S"."SEQ" AS NUMERIC(19, 0) START WITH 1 RESTART WITH 10 NO CACHE; +> rows (ordered): 4 + +drop schema s cascade; +> ok + +create schema TEST_SCHEMA; +> ok + +create sequence TEST_SCHEMA.TEST_SEQ; +> ok + +select TEST_SCHEMA.TEST_SEQ.CURRVAL; +> exception CURRENT_SEQUENCE_VALUE_IS_NOT_DEFINED_IN_SESSION_1 + +select TEST_SCHEMA.TEST_SEQ.nextval; +>> 1 + +select TEST_SCHEMA.TEST_SEQ.CURRVAL; +>> 1 + +drop schema TEST_SCHEMA cascade; +> ok + +CREATE TABLE TEST(CURRVAL INT, NEXTVAL INT); +> ok + +INSERT INTO TEST VALUES (3, 4); +> update count: 1 + +SELECT TEST.CURRVAL, TEST.NEXTVAL FROM TEST; +> CURRVAL NEXTVAL +> ------- ------- +> 3 4 +> rows: 1 + +DROP TABLE TEST; +> ok + +SET MODE Regular; +> ok + +CREATE SEQUENCE SEQ01 AS TINYINT; +> ok + +CREATE SEQUENCE SEQ02 AS SMALLINT; +> ok + +CREATE SEQUENCE SEQ03 AS INTEGER; +> ok + +CREATE SEQUENCE SEQ04 AS BIGINT; +> ok + +CREATE SEQUENCE SEQ05 AS REAL; +> ok + +CREATE SEQUENCE SEQ06 AS DOUBLE PRECISION; +> ok + +CREATE SEQUENCE SEQ AS NUMERIC(10, 2); +> exception FEATURE_NOT_SUPPORTED_1 + +CREATE SEQUENCE SEQ AS NUMERIC(100, 20); +> exception FEATURE_NOT_SUPPORTED_1 + +CREATE SEQUENCE SEQ07 AS DECIMAL; +> ok + +CREATE SEQUENCE SEQ08 AS DECIMAL(10); +> ok + +CREATE SEQUENCE SEQ11 AS DECIMAL(10, 2); +> exception FEATURE_NOT_SUPPORTED_1 + +CREATE SEQUENCE SEQ09 AS FLOAT; +> ok + +CREATE SEQUENCE SEQ10 AS FLOAT(20); +> ok + +CREATE SEQUENCE SEQ11 AS DECFLOAT; +> ok + +CREATE SEQUENCE SEQ12 AS DECFLOAT(10); +> ok + +CREATE SEQUENCE SEQ13 AS DECFLOAT(20); +> ok + +SELECT SEQUENCE_NAME, DATA_TYPE, NUMERIC_PRECISION, NUMERIC_PRECISION_RADIX, NUMERIC_SCALE, MAXIMUM_VALUE, + DECLARED_DATA_TYPE, DECLARED_NUMERIC_PRECISION, DECLARED_NUMERIC_SCALE FROM INFORMATION_SCHEMA.SEQUENCES; +> SEQUENCE_NAME DATA_TYPE NUMERIC_PRECISION NUMERIC_PRECISION_RADIX NUMERIC_SCALE MAXIMUM_VALUE DECLARED_DATA_TYPE DECLARED_NUMERIC_PRECISION DECLARED_NUMERIC_SCALE +> ------------- ---------------- ----------------- ----------------------- ------------- ------------------- ------------------ -------------------------- ---------------------- +> SEQ01 TINYINT 8 2 0 127 TINYINT null null +> SEQ02 SMALLINT 16 2 0 32767 SMALLINT null null +> SEQ03 INTEGER 32 2 0 2147483647 INTEGER null null +> SEQ04 BIGINT 64 2 0 9223372036854775807 BIGINT null null +> SEQ05 REAL 24 2 null 16777216 REAL null null +> SEQ06 DOUBLE PRECISION 53 2 null 9007199254740992 DOUBLE PRECISION null null +> SEQ07 NUMERIC 19 10 0 9223372036854775807 DECIMAL null null +> SEQ08 NUMERIC 10 10 0 9999999999 DECIMAL 10 null +> SEQ09 DOUBLE PRECISION 53 2 null 9007199254740992 FLOAT null null +> SEQ10 REAL 24 2 null 16777216 FLOAT 20 null +> SEQ11 DECFLOAT 19 10 null 9223372036854775807 DECFLOAT null null +> SEQ12 DECFLOAT 10 10 null 10000000000 DECFLOAT 10 null +> SEQ13 DECFLOAT 19 10 null 9223372036854775807 DECFLOAT 20 null +> rows: 13 + +SELECT NEXT VALUE FOR SEQ01 IS OF (TINYINT); +>> TRUE + +DROP ALL OBJECTS; +> ok + +CREATE SEQUENCE SEQ AS NUMERIC(10, 20); +> exception FEATURE_NOT_SUPPORTED_1 + +CREATE SEQUENCE SEQ AS VARCHAR(10); +> exception FEATURE_NOT_SUPPORTED_1 + +CREATE SEQUENCE SEQ NO; +> exception SYNTAX_ERROR_2 + +CREATE TABLE TEST( + A BIGINT GENERATED ALWAYS AS (C + 1), + B BIGINT GENERATED ALWAYS AS (D + 1), + C BIGINT GENERATED ALWAYS AS IDENTITY, + D BIGINT DEFAULT 3, + E BIGINT); +> ok + +INSERT INTO TEST(E) VALUES 10; +> update count: 1 + +TABLE TEST; +> A B C D E +> - - - - -- +> 2 4 1 3 10 +> rows: 1 + +DROP TABLE TEST; +> ok + +CREATE SEQUENCE SEQ MINVALUE 1 MAXVALUE 2; +> ok + +SELECT NEXT VALUE FOR SEQ; +>> 1 + +SELECT NEXT VALUE FOR SEQ; +>> 2 + +SELECT CACHE FROM INFORMATION_SCHEMA.SEQUENCES WHERE SEQUENCE_NAME = 'SEQ'; +>> 2 + +SCRIPT NODATA NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> ----------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE SEQUENCE "PUBLIC"."SEQ" START WITH 1 MAXVALUE 2 EXHAUSTED; +> rows (ordered): 2 + +@reconnect + +SELECT NEXT VALUE FOR SEQ; +> exception SEQUENCE_EXHAUSTED + +ALTER SEQUENCE SEQ RESTART; +> ok + +SELECT NEXT VALUE FOR SEQ; +>> 1 + +ALTER SEQUENCE SEQ CYCLE; +> ok + +SELECT NEXT VALUE FOR SEQ; +>> 2 + +SELECT NEXT VALUE FOR SEQ; +>> 1 + +ALTER SEQUENCE SEQ INCREMENT BY -1; +> ok + +SELECT NEXT VALUE FOR SEQ; +>> 2 + +SELECT NEXT VALUE FOR SEQ; +>> 1 + +DROP SEQUENCE SEQ; +> ok + +CREATE SEQUENCE SEQ MINVALUE 9223372036854775806; +> ok + +SELECT NEXT VALUE FOR SEQ; +>> 9223372036854775806 + +SELECT NEXT VALUE FOR SEQ; +>> 9223372036854775807 + +SELECT NEXT VALUE FOR SEQ; +> exception SEQUENCE_EXHAUSTED + +ALTER SEQUENCE SEQ NO CACHE RESTART; +> ok + +SELECT NEXT VALUE FOR SEQ; +>> 9223372036854775806 + +SELECT NEXT VALUE FOR SEQ; +>> 9223372036854775807 + +SELECT NEXT VALUE FOR SEQ; +> exception SEQUENCE_EXHAUSTED + +ALTER SEQUENCE SEQ CACHE 2 MINVALUE 9223372036854775805 RESTART WITH 9223372036854775805; +> ok + +SELECT NEXT VALUE FOR SEQ; +>> 9223372036854775805 + +SELECT NEXT VALUE FOR SEQ; +>> 9223372036854775806 + +SELECT NEXT VALUE FOR SEQ; +>> 9223372036854775807 + +SELECT NEXT VALUE FOR SEQ; +> exception SEQUENCE_EXHAUSTED + +DROP SEQUENCE SEQ; +> ok + +CREATE SEQUENCE SEQ INCREMENT BY -1 MAXVALUE -9223372036854775807; +> ok + +SELECT NEXT VALUE FOR SEQ; +>> -9223372036854775807 + +SELECT NEXT VALUE FOR SEQ; +>> -9223372036854775808 + +SELECT NEXT VALUE FOR SEQ; +> exception SEQUENCE_EXHAUSTED + +ALTER SEQUENCE SEQ NO CACHE RESTART; +> ok + +SELECT NEXT VALUE FOR SEQ; +>> -9223372036854775807 + +SELECT NEXT VALUE FOR SEQ; +>> -9223372036854775808 + +SELECT NEXT VALUE FOR SEQ; +> exception SEQUENCE_EXHAUSTED + +ALTER SEQUENCE SEQ CACHE 2 MAXVALUE -9223372036854775806 RESTART WITH -9223372036854775806; +> ok + +SELECT NEXT VALUE FOR SEQ; +>> -9223372036854775806 + +SELECT NEXT VALUE FOR SEQ; +>> -9223372036854775807 + +SELECT BASE_VALUE FROM INFORMATION_SCHEMA.SEQUENCES WHERE SEQUENCE_NAME = 'SEQ'; +>> -9223372036854775808 + +SELECT NEXT VALUE FOR SEQ; +>> -9223372036854775808 + +SELECT BASE_VALUE FROM INFORMATION_SCHEMA.SEQUENCES WHERE SEQUENCE_NAME = 'SEQ'; +>> null + +SELECT NEXT VALUE FOR SEQ; +> exception SEQUENCE_EXHAUSTED + +DROP SEQUENCE SEQ; +> ok diff --git a/h2/src/test/org/h2/test/scripts/other/set.sql b/h2/src/test/org/h2/test/scripts/other/set.sql new file mode 100644 index 0000000..3529615 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/other/set.sql @@ -0,0 +1,244 @@ +-- 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 +-- + +@reconnect off + +SELECT ISOLATION_LEVEL FROM INFORMATION_SCHEMA.SESSIONS WHERE SESSION_ID = SESSION_ID(); +>> READ COMMITTED + +SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +> ok + +SELECT ISOLATION_LEVEL FROM INFORMATION_SCHEMA.SESSIONS WHERE SESSION_ID = SESSION_ID(); +>> READ UNCOMMITTED + +SET TRANSACTION ISOLATION LEVEL READ COMMITTED; +> ok + +SELECT ISOLATION_LEVEL FROM INFORMATION_SCHEMA.SESSIONS WHERE SESSION_ID = SESSION_ID(); +>> READ COMMITTED + +SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; +> ok + +SELECT ISOLATION_LEVEL FROM INFORMATION_SCHEMA.SESSIONS WHERE SESSION_ID = SESSION_ID(); +>> REPEATABLE READ + +SET TRANSACTION ISOLATION LEVEL SNAPSHOT; +> ok + +SELECT ISOLATION_LEVEL FROM INFORMATION_SCHEMA.SESSIONS WHERE SESSION_ID = SESSION_ID(); +>> SNAPSHOT + +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; +> ok + +SELECT ISOLATION_LEVEL FROM INFORMATION_SCHEMA.SESSIONS WHERE SESSION_ID = SESSION_ID(); +>> SERIALIZABLE + +SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; +> ok + +SELECT ISOLATION_LEVEL FROM INFORMATION_SCHEMA.SESSIONS WHERE SESSION_ID = SESSION_ID(); +>> READ UNCOMMITTED + +SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED; +> ok + +SELECT ISOLATION_LEVEL FROM INFORMATION_SCHEMA.SESSIONS WHERE SESSION_ID = SESSION_ID(); +>> READ COMMITTED + +SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL REPEATABLE READ; +> ok + +SELECT ISOLATION_LEVEL FROM INFORMATION_SCHEMA.SESSIONS WHERE SESSION_ID = SESSION_ID(); +>> REPEATABLE READ + +SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SNAPSHOT; +> ok + +SELECT ISOLATION_LEVEL FROM INFORMATION_SCHEMA.SESSIONS WHERE SESSION_ID = SESSION_ID(); +>> SNAPSHOT + +SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE; +> ok + +SELECT ISOLATION_LEVEL FROM INFORMATION_SCHEMA.SESSIONS WHERE SESSION_ID = SESSION_ID(); +>> SERIALIZABLE + +SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED; +> ok + +SELECT SETTING_VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME = 'VARIABLE_BINARY'; +>> FALSE + +CREATE MEMORY TABLE TEST(B BINARY); +> ok + +SCRIPT NODATA NOPASSWORDS NOSETTINGS NOVERSION TABLE TEST; +> SCRIPT +> -------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "B" BINARY ); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> rows (ordered): 3 + +DROP TABLE TEST; +> ok + +SET VARIABLE_BINARY TRUE; +> ok + +SELECT SETTING_VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME = 'VARIABLE_BINARY'; +>> TRUE + +CREATE MEMORY TABLE TEST(B BINARY); +> ok + +SCRIPT NODATA NOPASSWORDS NOSETTINGS NOVERSION TABLE TEST; +> SCRIPT +> ---------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "B" BINARY VARYING ); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> rows (ordered): 3 + +DROP TABLE TEST; +> ok + +SET VARIABLE_BINARY FALSE; +> ok + +SET LOCK_MODE 0; +> ok + +CALL LOCK_MODE(); +>> 0 + +SET LOCK_MODE 1; +> ok + +CALL LOCK_MODE(); +>> 3 + +SET LOCK_MODE 2; +> ok + +CALL LOCK_MODE(); +>> 3 + +SET LOCK_MODE 3; +> ok + +CALL LOCK_MODE(); +>> 3 + +@reconnect on + +SELECT CURRENT_PATH; +> CURRENT_PATH +> ------------ +> +> rows: 1 + +SET SCHEMA_SEARCH_PATH PUBLIC, INFORMATION_SCHEMA; +> ok + +SELECT CURRENT_PATH; +>> "PUBLIC","INFORMATION_SCHEMA" + +SET SCHEMA_SEARCH_PATH PUBLIC; +> ok + +CREATE TABLE TEST(C1 INT, C2 INT); +> ok + +CREATE INDEX IDX ON TEST(C1 ASC, C2 DESC); +> ok + +SELECT COLUMN_NAME, ORDERING_SPECIFICATION, NULL_ORDERING FROM INFORMATION_SCHEMA.INDEX_COLUMNS + WHERE INDEX_NAME = 'IDX'; +> COLUMN_NAME ORDERING_SPECIFICATION NULL_ORDERING +> ----------- ---------------------- ------------- +> C1 ASC FIRST +> C2 DESC LAST +> rows: 2 + +DROP INDEX IDX; +> ok + +SET DEFAULT_NULL_ORDERING LOW; +> ok + +CREATE INDEX IDX ON TEST(C1 ASC, C2 DESC); +> ok + +SELECT COLUMN_NAME, ORDERING_SPECIFICATION, NULL_ORDERING FROM INFORMATION_SCHEMA.INDEX_COLUMNS + WHERE INDEX_NAME = 'IDX'; +> COLUMN_NAME ORDERING_SPECIFICATION NULL_ORDERING +> ----------- ---------------------- ------------- +> C1 ASC FIRST +> C2 DESC LAST +> rows: 2 + +DROP INDEX IDX; +> ok + +SET DEFAULT_NULL_ORDERING HIGH; +> ok + +CREATE INDEX IDX ON TEST(C1 ASC, C2 DESC); +> ok + +SELECT COLUMN_NAME, ORDERING_SPECIFICATION, NULL_ORDERING FROM INFORMATION_SCHEMA.INDEX_COLUMNS + WHERE INDEX_NAME = 'IDX'; +> COLUMN_NAME ORDERING_SPECIFICATION NULL_ORDERING +> ----------- ---------------------- ------------- +> C1 ASC LAST +> C2 DESC FIRST +> rows: 2 + +DROP INDEX IDX; +> ok + +SET DEFAULT_NULL_ORDERING FIRST; +> ok + +CREATE INDEX IDX ON TEST(C1 ASC, C2 DESC); +> ok + +SELECT COLUMN_NAME, ORDERING_SPECIFICATION, NULL_ORDERING FROM INFORMATION_SCHEMA.INDEX_COLUMNS + WHERE INDEX_NAME = 'IDX'; +> COLUMN_NAME ORDERING_SPECIFICATION NULL_ORDERING +> ----------- ---------------------- ------------- +> C1 ASC FIRST +> C2 DESC FIRST +> rows: 2 + +DROP INDEX IDX; +> ok + +SET DEFAULT_NULL_ORDERING LAST; +> ok + +CREATE INDEX IDX ON TEST(C1 ASC, C2 DESC); +> ok + +SELECT COLUMN_NAME, ORDERING_SPECIFICATION, NULL_ORDERING FROM INFORMATION_SCHEMA.INDEX_COLUMNS + WHERE INDEX_NAME = 'IDX'; +> COLUMN_NAME ORDERING_SPECIFICATION NULL_ORDERING +> ----------- ---------------------- ------------- +> C1 ASC LAST +> C2 DESC LAST +> rows: 2 + +DROP INDEX IDX; +> ok + +SET DEFAULT_NULL_ORDERING LOW; +> ok + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/other/two_phase_commit.sql b/h2/src/test/org/h2/test/scripts/other/two_phase_commit.sql new file mode 100644 index 0000000..2cb8a7a --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/other/two_phase_commit.sql @@ -0,0 +1,28 @@ +-- 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 +-- + +-- issue #3033 +CREATE TABLE TEST(A BIGINT PRIMARY KEY, B BLOB); +> ok + +INSERT INTO TEST VALUES(1, REPEAT('010203040506070809101112',11)); +> update count: 1 + +@autocommit off + +DELETE FROM TEST WHERE A = 1; +> update count: 1 + +PREPARE COMMIT commit1; +> ok + +@reconnect + +ROLLBACK TRANSACTION commit1; +> ok + +SELECT B FROM TEST WHERE A = 1; +>> X'303130323033303430353036303730383039313031313132303130323033303430353036303730383039313031313132303130323033303430353036303730383039313031313132303130323033303430353036303730383039313031313132303130323033303430353036303730383039313031313132303130323033303430353036303730383039313031313132303130323033303430353036303730383039313031313132303130323033303430353036303730383039313031313132303130323033303430353036303730383039313031313132303130323033303430353036303730383039313031313132303130323033303430353036303730383039313031313132' + diff --git a/h2/src/test/org/h2/test/scripts/other/unique_include.sql b/h2/src/test/org/h2/test/scripts/other/unique_include.sql new file mode 100644 index 0000000..9f54280 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/other/unique_include.sql @@ -0,0 +1,76 @@ +-- 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 +-- + +CREATE TABLE TEST(A INT, B INT, C INT); +> ok + +CREATE UNIQUE INDEX TEST_IDX ON TEST(C) INCLUDE(B); +> ok + +INSERT INTO TEST VALUES (10, 20, 1), (11, 20, 2), (12, 21, 3); +> update count: 3 + +INSERT INTO TEST VALUES (13, 22, 1); +> exception DUPLICATE_KEY_1 + +SELECT INDEX_NAME, TABLE_NAME, INDEX_TYPE_NAME FROM INFORMATION_SCHEMA.INDEXES WHERE INDEX_NAME = 'TEST_IDX'; +> INDEX_NAME TABLE_NAME INDEX_TYPE_NAME +> ---------- ---------- --------------- +> TEST_IDX TEST UNIQUE INDEX +> rows: 1 + +SELECT INDEX_NAME, TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, IS_UNIQUE FROM INFORMATION_SCHEMA.INDEX_COLUMNS + WHERE INDEX_NAME = 'TEST_IDX' ORDER BY ORDINAL_POSITION; +> INDEX_NAME TABLE_NAME COLUMN_NAME ORDINAL_POSITION IS_UNIQUE +> ---------- ---------- ----------- ---------------- --------- +> TEST_IDX TEST C 1 TRUE +> TEST_IDX TEST B 2 FALSE +> rows (ordered): 2 + +SELECT DB_OBJECT_SQL('INDEX', 'PUBLIC', 'TEST_IDX'); +>> CREATE UNIQUE INDEX "PUBLIC"."TEST_IDX" ON "PUBLIC"."TEST"("C" NULLS FIRST) INCLUDE("B" NULLS FIRST) + +ALTER TABLE TEST ADD CONSTRAINT TEST_UNI_C UNIQUE(C); +> ok + +SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE, TABLE_NAME, INDEX_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_NAME = 'TEST'; +> CONSTRAINT_NAME CONSTRAINT_TYPE TABLE_NAME INDEX_NAME +> --------------- --------------- ---------- ---------- +> TEST_UNI_C UNIQUE TEST TEST_IDX +> rows: 1 + +SELECT CONSTRAINT_NAME, TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE + WHERE CONSTRAINT_NAME = 'TEST_UNI_C'; +> CONSTRAINT_NAME TABLE_NAME COLUMN_NAME ORDINAL_POSITION +> --------------- ---------- ----------- ---------------- +> TEST_UNI_C TEST C 1 +> rows: 1 + +EXPLAIN SELECT B, C FROM TEST ORDER BY C, B; +>> SELECT "B", "C" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_IDX */ ORDER BY 2, 1 /* index sorted */ + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INT, B INT, C INT); +> ok + +CREATE UNIQUE INDEX TEST_IDX_A_B ON TEST(A) INCLUDE (B); +> ok + +CREATE UNIQUE INDEX TEST_IDX_A ON TEST(A); +> ok + +CREATE UNIQUE INDEX TEST_IDX_A_B_C ON TEST(A) INCLUDE (B, C); +> ok + +ALTER TABLE TEST ADD CONSTRAINT UNI_TEST_A UNIQUE(A); +> ok + +SELECT INDEX_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE CONSTRAINT_NAME = 'UNI_TEST_A'; +>> TEST_IDX_A + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/package.html b/h2/src/test/org/h2/test/scripts/package.html new file mode 100644 index 0000000..cf8c836 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +Script test files. + +

    \ No newline at end of file diff --git a/h2/src/test/org/h2/test/scripts/parser/comments.sql b/h2/src/test/org/h2/test/scripts/parser/comments.sql new file mode 100644 index 0000000..aa4f6e6 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/parser/comments.sql @@ -0,0 +1,50 @@ +-- 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 +-- + +CALL 1 /* comment */ ;; +>> 1 + +CALL 1 /* comment */ ; +>> 1 + +call /* remark * / * /* ** // end */*/ 1; +>> 1 + +call /*/*/ */*/ 1; +>> 1 + +call /*1/*1*/1*/1; +>> 1 + +--- remarks/comments/syntax ---------------------------------------------------------------------------------------------- +CREATE TABLE TEST( +ID INT PRIMARY KEY, -- this is the primary key, type {integer} +NAME VARCHAR(255) -- this is a string +); +> ok + +INSERT INTO TEST VALUES( +1 /* ID */, +'Hello' // NAME +); +> update count: 1 + +SELECT * FROM TEST; +> ID NAME +> -- ----- +> 1 Hello +> rows: 1 + +DROP_ TABLE_ TEST_T; +> exception SYNTAX_ERROR_2 + +DROP TABLE TEST /*; +> exception SYNTAX_ERROR_1 + +call /* remark * / * /* ** // end */ 1; +> exception SYNTAX_ERROR_1 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/parser/identifiers.sql b/h2/src/test/org/h2/test/scripts/parser/identifiers.sql new file mode 100644 index 0000000..6d8bb49 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/parser/identifiers.sql @@ -0,0 +1,52 @@ +-- 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 +-- + +SELECT 1 "A""B""""C"""; +> A"B""C" +> ------- +> 1 +> rows: 1 + +SELECT 1 ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345; +> ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345 +> ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> 1 +> rows: 1 + +SELECT 1 ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456; +> exception NAME_TOO_LONG_2 + +SELECT 1 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"; +> ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345 +> ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> 1 +> rows: 1 + +SELECT 1 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456"; +> exception NAME_TOO_LONG_2 + +SELECT 1 "ABCDEFGHIJKLMNOPQRSTUVWXYZ01234""5ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"; +> exception NAME_TOO_LONG_2 + +SELECT 1 "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345""ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"; +> exception NAME_TOO_LONG_2 + +SELECT 3 U&"\0031", 4 U&"/0032" UESCAPE '/'; +> 1 2 +> - - +> 3 4 +> rows: 1 + +EXPLAIN SELECT 1 U&"!2030" UESCAPE '!'; +>> SELECT 1 AS U&"\2030" + +SELECT 1 U&"ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ01234\0035"; +> ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345 +> ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> 1 +> rows: 1 + +SELECT 1 U&"ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ012345ABCDEFGHIJKLMNOPQRSTUVWXYZ01234\00356"; +> exception NAME_TOO_LONG_2 diff --git a/h2/src/test/org/h2/test/scripts/predicates/between.sql b/h2/src/test/org/h2/test/scripts/predicates/between.sql new file mode 100644 index 0000000..0d4594f --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/predicates/between.sql @@ -0,0 +1,107 @@ +-- 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 +-- + +CREATE TABLE TEST(ID INT PRIMARY KEY, X INT, A INT, B INT) AS VALUES + (1, NULL, NULL, NULL), + (2, NULL, NULL, 1), + (3, NULL, 1, NULL), + (4, 1, NULL, NULL), + (5, NULL, 1, 1), + (6, NULL, 1, 2), + (7, NULL, 2, 1), + (8, 1, NULL, 1), + (9, 1, NULL, 2), + (10, 2, NULL, 1), + (11, 1, 1, NULL), + (12, 1, 2, NULL), + (13, 2, 1, NULL), + (14, 1, 1, 1), + (15, 1, 1, 2), + (16, 1, 2, 1), + (17, 2, 1, 1), + (18, 1, 2, 2), + (19, 2, 1, 2), + (20, 2, 2, 1), + (21, 1, 2, 3), + (22, 1, 3, 2), + (23, 2, 1, 3), + (24, 2, 3, 1), + (25, 3, 1, 2), + (26, 3, 2, 1); +> ok + +EXPLAIN SELECT X BETWEEN A AND B A1, X BETWEEN ASYMMETRIC A AND B A2 FROM TEST; +>> SELECT "X" BETWEEN "A" AND "B" AS "A1", "X" BETWEEN "A" AND "B" AS "A2" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT X BETWEEN SYMMETRIC A AND B S1 FROM TEST; +>> SELECT "X" BETWEEN SYMMETRIC "A" AND "B" AS "S1" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT X NOT BETWEEN A AND B NA1, X NOT BETWEEN ASYMMETRIC A AND B NA2 FROM TEST; +>> SELECT "X" NOT BETWEEN "A" AND "B" AS "NA1", "X" NOT BETWEEN "A" AND "B" AS "NA2" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT X NOT BETWEEN SYMMETRIC A AND B NS1 FROM TEST; +>> SELECT "X" NOT BETWEEN SYMMETRIC "A" AND "B" AS "NS1" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +SELECT X BETWEEN A AND B A1, X BETWEEN ASYMMETRIC A AND B A2, A <= X AND X <= B A3, + X BETWEEN SYMMETRIC A AND B S1, A <= X AND X <= B OR A >= X AND X >= B S2, + X NOT BETWEEN A AND B NA1, X NOT BETWEEN ASYMMETRIC A AND B NA2, NOT (A <= X AND X <= B) NA3, + X NOT BETWEEN SYMMETRIC A AND B NS1, NOT (A <= X AND X <= B OR A >= X AND X >= B) NS2 + FROM TEST ORDER BY ID; +> A1 A2 A3 S1 S2 NA1 NA2 NA3 NS1 NS2 +> ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- +> null null null null null null null null null null +> null null null null null null null null null null +> null null null null null null null null null null +> null null null null null null null null null null +> null null null null null null null null null null +> null null null null null null null null null null +> null null null null null null null null null null +> null null null null null null null null null null +> null null null null null null null null null null +> FALSE FALSE FALSE null null TRUE TRUE TRUE null null +> null null null null null null null null null null +> FALSE FALSE FALSE null null TRUE TRUE TRUE null null +> null null null null null null null null null null +> TRUE TRUE TRUE TRUE TRUE FALSE FALSE FALSE FALSE FALSE +> TRUE TRUE TRUE TRUE TRUE FALSE FALSE FALSE FALSE FALSE +> FALSE FALSE FALSE TRUE TRUE TRUE TRUE TRUE FALSE FALSE +> FALSE FALSE FALSE FALSE FALSE TRUE TRUE TRUE TRUE TRUE +> FALSE FALSE FALSE FALSE FALSE TRUE TRUE TRUE TRUE TRUE +> TRUE TRUE TRUE TRUE TRUE FALSE FALSE FALSE FALSE FALSE +> FALSE FALSE FALSE TRUE TRUE TRUE TRUE TRUE FALSE FALSE +> FALSE FALSE FALSE FALSE FALSE TRUE TRUE TRUE TRUE TRUE +> FALSE FALSE FALSE FALSE FALSE TRUE TRUE TRUE TRUE TRUE +> TRUE TRUE TRUE TRUE TRUE FALSE FALSE FALSE FALSE FALSE +> FALSE FALSE FALSE TRUE TRUE TRUE TRUE TRUE FALSE FALSE +> FALSE FALSE FALSE FALSE FALSE TRUE TRUE TRUE TRUE TRUE +> FALSE FALSE FALSE FALSE FALSE TRUE TRUE TRUE TRUE TRUE +> rows (ordered): 26 + +EXPLAIN SELECT * FROM TEST WHERE ID BETWEEN 1 AND 2; +>> SELECT "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."X", "PUBLIC"."TEST"."A", "PUBLIC"."TEST"."B" FROM "PUBLIC"."TEST" /* PUBLIC.PRIMARY_KEY_2: ID >= 1 AND ID <= 2 */ WHERE "ID" BETWEEN 1 AND 2 + +EXPLAIN SELECT * FROM TEST WHERE ID NOT BETWEEN 1 AND 2; +>> SELECT "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."X", "PUBLIC"."TEST"."A", "PUBLIC"."TEST"."B" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ WHERE "ID" NOT BETWEEN 1 AND 2 + +EXPLAIN SELECT NULL BETWEEN A AND B, X BETWEEN NULL AND NULL, X BETWEEN SYMMETRIC A AND NULL, X BETWEEN SYMMETRIC NULL AND B, X BETWEEN SYMMETRIC NULL AND NULL FROM TEST; +>> SELECT UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN, UNKNOWN FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT X BETWEEN 1 AND 1, X NOT BETWEEN 1 AND 1, 2 BETWEEN SYMMETRIC 3 AND 1 FROM TEST; +>> SELECT "X" = 1, "X" <> 1, TRUE FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT 2 BETWEEN 1 AND B, 2 BETWEEN A AND 3, 2 BETWEEN A AND B FROM TEST; +>> SELECT 2 BETWEEN 1 AND "B", 2 BETWEEN "A" AND 3, 2 BETWEEN "A" AND "B" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT X BETWEEN 1 AND NULL, X BETWEEN NULL AND 3 FROM TEST; +>> SELECT "X" BETWEEN 1 AND NULL, "X" BETWEEN NULL AND 3 FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT NOT (X BETWEEN A AND B), NOT (X NOT BETWEEN A AND B) FROM TEST; +>> SELECT "X" NOT BETWEEN "A" AND "B", "X" BETWEEN "A" AND "B" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +DROP TABLE TEST; +> ok + +SELECT CURRENT_TIME BETWEEN CURRENT_DATE AND (CURRENT_DATE + INTERVAL '1' DAY); +> exception TYPES_ARE_NOT_COMPARABLE_2 diff --git a/h2/src/test/org/h2/test/scripts/predicates/distinct.sql b/h2/src/test/org/h2/test/scripts/predicates/distinct.sql new file mode 100644 index 0000000..6fcd2e2 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/predicates/distinct.sql @@ -0,0 +1,66 @@ +-- 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 +-- + +-- Quantified distinct predicate + +SELECT 1 IS DISTINCT FROM ALL(VALUES 1, NULL, 2); +>> FALSE + +SELECT 1 IS DISTINCT FROM ALL(VALUES NULL, 2); +>> TRUE + +SELECT NULL IS DISTINCT FROM ALL(VALUES 1, NULL, 2); +>> FALSE + +SELECT NULL IS DISTINCT FROM ALL(VALUES 1, 2); +>> TRUE + +SELECT 1 IS NOT DISTINCT FROM ALL(VALUES 1, NULL, 2); +>> FALSE + +SELECT 1 IS NOT DISTINCT FROM ALL(VALUES 1, 1); +>> TRUE + +SELECT NULL IS NOT DISTINCT FROM ALL(VALUES 1, NULL, 2); +>> FALSE + +SELECT NULL IS NOT DISTINCT FROM ALL(VALUES NULL, NULL); +>> TRUE + +SELECT 1 IS DISTINCT FROM ANY(VALUES 1, NULL, 2); +>> TRUE + +SELECT 1 IS DISTINCT FROM ANY(VALUES 1, 1); +>> FALSE + +SELECT NULL IS DISTINCT FROM ANY(VALUES 1, NULL, 2); +>> TRUE + +SELECT NULL IS DISTINCT FROM ANY(VALUES NULL, NULL); +>> FALSE + +SELECT 1 IS NOT DISTINCT FROM ANY(VALUES 1, NULL, 2); +>> TRUE + +SELECT 1 IS NOT DISTINCT FROM ANY(VALUES NULL, 2); +>> FALSE + +SELECT NULL IS NOT DISTINCT FROM ANY(VALUES 1, NULL, 2); +>> TRUE + +SELECT NULL IS NOT DISTINCT FROM ANY(VALUES 1, 2); +>> FALSE + +SELECT NOT (NULL IS NOT DISTINCT FROM ANY(VALUES 1, 2)); +>> TRUE + +EXPLAIN SELECT NOT (NULL IS NOT DISTINCT FROM ANY(VALUES 1, 2)); +>> SELECT NOT (NULL IS NOT DISTINCT FROM ANY( VALUES (1), (2))) + +SELECT (1, NULL) IS NOT DISTINCT FROM ANY(VALUES (1, NULL), (2, NULL)); +>> TRUE + +SELECT (1, NULL) IS NOT DISTINCT FROM ANY(VALUES (2, NULL), (3, NULL)); +>> FALSE diff --git a/h2/src/test/org/h2/test/scripts/predicates/in.sql b/h2/src/test/org/h2/test/scripts/predicates/in.sql new file mode 100644 index 0000000..a57b38c --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/predicates/in.sql @@ -0,0 +1,428 @@ +-- 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 +-- + +create table test(id int) as select 1; +> ok + +select * from test where id in (select id from test order by 'x'); +> ID +> -- +> 1 +> rows: 1 + +drop table test; +> ok + +select x, x in(2, 3) i from system_range(1, 2) group by x; +> X I +> - ----- +> 1 FALSE +> 2 TRUE +> rows: 2 + +select * from system_range(1, 1) where x = x + 1 or x in(2, 0); +> X +> - +> rows: 0 + +select * from system_range(1, 1) where cast('a' || x as varchar_ignorecase) in ('A1', 'B1'); +> X +> - +> 1 +> rows: 1 + +create table test(x int) as select x from system_range(1, 2); +> ok + +select * from (select rownum r from test) where r in (1, 2); +> R +> - +> 1 +> 2 +> rows: 2 + +select * from (select rownum r from test) where r = 1 or r = 2; +> R +> - +> 1 +> 2 +> rows: 2 + +drop table test; +> ok + +select x from system_range(1, 1) where x in (select x from system_range(1, 1) group by x order by max(x)); +> X +> - +> 1 +> rows: 1 + +create table test(id int) as (values 1, 2, 4); +> ok + +select a.id, a.id in(select 4) x from test a, test b where a.id in (b.id, b.id - 1); +> ID X +> -- ----- +> 1 FALSE +> 1 FALSE +> 2 FALSE +> 4 TRUE +> rows: 4 + +select a.id, a.id in(select 4) x from test a, test b where a.id in (b.id, b.id - 1) group by a.id; +> ID X +> -- ----- +> 1 FALSE +> 2 FALSE +> 4 TRUE +> rows: 3 + +select a.id, 4 in(select a.id) x from test a, test b where a.id in (b.id, b.id - 1) group by a.id; +> ID X +> -- ----- +> 1 FALSE +> 2 FALSE +> 4 TRUE +> rows: 3 + +drop table test; +> ok + +create table test(id int primary key, d int) as (values (1, 1), (2, 1)); +> ok + +select id from test where id in (1, 2) and d = 1; +> ID +> -- +> 1 +> 2 +> rows: 2 + +drop table test; +> ok + +create table test(id int) as (values null, 1); +> ok + +select * from test where id not in (select id from test where 1=0); +> ID +> ---- +> 1 +> null +> rows: 2 + +select * from test where null not in (select id from test where 1=0); +> ID +> ---- +> 1 +> null +> rows: 2 + +select * from test where not (id in (select id from test where 1=0)); +> ID +> ---- +> 1 +> null +> rows: 2 + +select * from test where not (null in (select id from test where 1=0)); +> ID +> ---- +> 1 +> null +> rows: 2 + +drop table test; +> ok + +create table t1 (id int primary key) as (select x from system_range(1, 1000)); +> ok + +create table t2 (id int primary key) as (select x from system_range(1, 1000)); +> ok + +explain select count(*) from t1 where t1.id in ( select t2.id from t2 ); +>> SELECT COUNT(*) FROM "PUBLIC"."T1" /* PUBLIC.PRIMARY_KEY_A: ID IN(SELECT DISTINCT T2.ID FROM PUBLIC.T2 /* PUBLIC.T2.tableScan */) */ WHERE "T1"."ID" IN( SELECT DISTINCT "T2"."ID" FROM "PUBLIC"."T2" /* PUBLIC.T2.tableScan */) + +select count(*) from t1 where t1.id in ( select t2.id from t2 ); +> COUNT(*) +> -------- +> 1000 +> rows: 1 + +drop table t1, t2; +> ok + +select count(*) from system_range(1, 2) where x in(1, 1, 1); +> COUNT(*) +> -------- +> 1 +> rows: 1 + +create table test(id int primary key) as (values 1, 2, 3); +> ok + +explain select * from test where id in(1, 2, null); +>> SELECT "PUBLIC"."TEST"."ID" FROM "PUBLIC"."TEST" /* PUBLIC.PRIMARY_KEY_2: ID IN(1, 2, NULL) */ WHERE "ID" IN(1, 2, NULL) + +drop table test; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255)) AS (VALUES (1, 'Hello'), (2, 'World')); +> ok + +select * from test where id in (select id from test); +> ID NAME +> -- ----- +> 1 Hello +> 2 World +> rows: 2 + +select * from test where id in ((select id from test)); +> ID NAME +> -- ----- +> 1 Hello +> 2 World +> rows: 2 + +select * from test where id in (((select id from test))); +> ID NAME +> -- ----- +> 1 Hello +> 2 World +> rows: 2 + +DROP TABLE TEST; +> ok + +create table test(v boolean) as (values unknown, true, false); +> ok + +SELECT CASE WHEN NOT (false IN (null)) THEN false END; +> NULL +> ---- +> null +> rows: 1 + +select a.v as av, b.v as bv, a.v IN (b.v), not a.v IN (b.v) from test a, test b; +> AV BV A.V = B.V A.V <> B.V +> ----- ----- --------- ---------- +> FALSE FALSE TRUE FALSE +> FALSE TRUE FALSE TRUE +> FALSE null null null +> TRUE FALSE FALSE TRUE +> TRUE TRUE TRUE FALSE +> TRUE null null null +> null FALSE null null +> null TRUE null null +> null null null null +> rows: 9 + +select a.v as av, b.v as bv, a.v IN (b.v, null), not a.v IN (b.v, null) from test a, test b; +> AV BV A.V IN(B.V, NULL) A.V NOT IN(B.V, NULL) +> ----- ----- ----------------- --------------------- +> FALSE FALSE TRUE FALSE +> FALSE TRUE null null +> FALSE null null null +> TRUE FALSE null null +> TRUE TRUE TRUE FALSE +> TRUE null null null +> null FALSE null null +> null TRUE null null +> null null null null +> rows: 9 + +drop table test; +> ok + +SELECT CASE WHEN NOT (false IN (null)) THEN false END; +> NULL +> ---- +> null +> rows: 1 + +create table test(a int, b int) as select 2, 0; +> ok + +create index idx on test(b, a); +> ok + +select count(*) from test where a in(2, 10) and b in(0, null); +>> 1 + +drop table test; +> ok + +create table test(a int, b int) as select 1, 0; +> ok + +create index idx on test(b, a); +> ok + +select count(*) from test where b in(null, 0) and a in(1, null); +>> 1 + +drop table test; +> ok + +create table test(a int, b int, unique(a, b)); +> ok + +insert into test values(1,1), (1,2); +> update count: 2 + +select count(*) from test where a in(1,2) and b in(1,2); +>> 2 + +drop table test; +> ok + +SELECT * FROM SYSTEM_RANGE(1, 10) WHERE X IN ((SELECT 1), (SELECT 2)); +> X +> - +> 1 +> 2 +> rows: 2 + +EXPLAIN SELECT * FROM SYSTEM_RANGE(1, 10) WHERE X IN ((SELECT X FROM SYSTEM_RANGE(1, 1)), (SELECT X FROM SYSTEM_RANGE(2, 2))); +>> SELECT "SYSTEM_RANGE"."X" FROM SYSTEM_RANGE(1, 10) /* range index: X IN((SELECT X FROM SYSTEM_RANGE(1, 1) /* range index */), (SELECT X FROM SYSTEM_RANGE(2, 2) /* range index */)) */ WHERE "X" IN((SELECT "X" FROM SYSTEM_RANGE(1, 1) /* range index */), (SELECT "X" FROM SYSTEM_RANGE(2, 2) /* range index */)) + +-- Tests for IN predicate with an empty list + +SELECT 1 WHERE 1 IN (); +> 1 +> - +> rows: 0 + +SELECT 1 WHERE 1 NOT IN (); +>> 1 + +SELECT CASE 1 WHEN IN() THEN 1 ELSE 2 END; +> exception SYNTAX_ERROR_2 + +SET MODE DB2; +> ok + +SELECT 1 WHERE 1 IN (); +> exception SYNTAX_ERROR_2 + +SET MODE Derby; +> ok + +SELECT 1 WHERE 1 IN (); +> exception SYNTAX_ERROR_2 + +SET MODE MSSQLServer; +> ok + +SELECT 1 WHERE 1 IN (); +> exception SYNTAX_ERROR_2 + +SET MODE HSQLDB; +> ok + +SELECT 1 WHERE 1 IN (); +> exception SYNTAX_ERROR_2 + +SET MODE MySQL; +> ok + +SELECT 1 WHERE 1 IN (); +> exception SYNTAX_ERROR_2 + +SET MODE Oracle; +> ok + +SELECT 1 WHERE 1 IN (); +> exception SYNTAX_ERROR_2 + +SET MODE PostgreSQL; +> ok + +SELECT 1 WHERE 1 IN (); +> exception SYNTAX_ERROR_2 + +SET MODE Regular; +> ok + +CREATE TABLE TEST(A INT, B INT) AS (VALUES (1, 1), (1, 2), (2, 1), (2, NULL)); +> ok + +SELECT * FROM TEST WHERE (A, B) IN ((1, 1), (2, 1), (2, 2), (2, NULL)); +> A B +> - - +> 1 1 +> 2 1 +> rows: 2 + +DROP TABLE TEST; +> ok + +SELECT LOCALTIME IN(DATE '2000-01-01', DATE '2010-01-01'); +> exception TYPES_ARE_NOT_COMPARABLE_2 + +SELECT LOCALTIME IN ((VALUES DATE '2000-01-01', DATE '2010-01-01')); +> exception TYPES_ARE_NOT_COMPARABLE_2 + +CREATE TABLE TEST(V INT) AS VALUES 1, 2; +> ok + +SELECT V, V IN (1, 1000000000000) FROM TEST; +> V V IN(1, 1000000000000) +> - ---------------------- +> 1 TRUE +> 2 FALSE +> rows: 2 + +EXPLAIN SELECT V, V IN (1, 1000000000000) FROM TEST; +>> SELECT "V", "V" IN(1, 1000000000000) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +CREATE UNIQUE INDEX TEST_IDX ON TEST(V); +> ok + +SELECT V, V IN (1, 1000000000000) FROM TEST; +> V V IN(1, 1000000000000) +> - ---------------------- +> 1 TRUE +> 2 FALSE +> rows: 2 + +EXPLAIN SELECT V, V IN (1, 1000000000000) FROM TEST; +>> SELECT "V", "V" IN(1, 1000000000000) FROM "PUBLIC"."TEST" /* PUBLIC.TEST_IDX */ + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(C BIGINT PRIMARY KEY) AS VALUES 1, 1000000000000; +> ok + +SELECT V, V IN (SELECT * FROM TEST) FROM (VALUES 1, 2) T(V); +> V V IN( SELECT DISTINCT PUBLIC.TEST.C FROM PUBLIC.TEST) +> - ----------------------------------------------------- +> 1 TRUE +> 2 FALSE +> rows: 2 + +EXPLAIN SELECT V, V IN (SELECT * FROM TEST) FROM (VALUES 1, 2) T(V); +>> SELECT "V", "V" IN( SELECT DISTINCT "PUBLIC"."TEST"."C" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */) FROM (VALUES (1), (2)) "T"("V") /* table scan */ + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(C INTEGER PRIMARY KEY) AS VALUES 1, 2; +> ok + +SELECT V, V IN (SELECT * FROM TEST) FROM (VALUES 1, 1000000000000) T(V); +> V V IN( SELECT DISTINCT PUBLIC.TEST.C FROM PUBLIC.TEST) +> ------------- ----------------------------------------------------- +> 1 TRUE +> 1000000000000 FALSE +> rows: 2 + +EXPLAIN SELECT V, V IN (SELECT * FROM TEST) FROM (VALUES 1, 1000000000000) T(V); +>> SELECT "V", "V" IN( SELECT DISTINCT "PUBLIC"."TEST"."C" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */) FROM (VALUES (1), (1000000000000)) "T"("V") /* table scan */ + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/predicates/like.sql b/h2/src/test/org/h2/test/scripts/predicates/like.sql new file mode 100644 index 0000000..de01420 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/predicates/like.sql @@ -0,0 +1,214 @@ +-- 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 +-- + +create table Foo (A varchar(20), B integer); +> ok + +insert into Foo (A, B) values ('abcd', 1), ('abcd', 2); +> update count: 2 + +select * from Foo where A like 'abc%' escape '\' AND B=1; +> A B +> ---- - +> abcd 1 +> rows: 1 + +drop table Foo; +> ok + +--- test case for number like string --------------------------------------------------------------------------------------------- +CREATE TABLE test (one bigint primary key, two bigint, three bigint); +> ok + +CREATE INDEX two ON test(two); +> ok + +INSERT INTO TEST VALUES(1, 2, 3), (10, 20, 30), (100, 200, 300); +> update count: 3 + +INSERT INTO TEST VALUES(2, 6, 9), (20, 60, 90), (200, 600, 900); +> update count: 3 + +SELECT * FROM test WHERE one LIKE '2%'; +> ONE TWO THREE +> --- --- ----- +> 2 6 9 +> 20 60 90 +> 200 600 900 +> rows: 3 + +SELECT * FROM test WHERE two LIKE '2%'; +> ONE TWO THREE +> --- --- ----- +> 1 2 3 +> 10 20 30 +> 100 200 300 +> rows: 3 + +SELECT * FROM test WHERE three LIKE '2%'; +> ONE TWO THREE +> --- --- ----- +> rows: 0 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +INSERT INTO TEST VALUES(0, NULL), (1, 'Hello'), (2, 'World'), (3, 'Word'), (4, 'Wo%'); +> update count: 5 + +SELECT * FROM TEST WHERE NAME IS NULL; +> ID NAME +> -- ---- +> 0 null +> rows: 1 + +SELECT * FROM TEST WHERE NAME IS NOT NULL; +> ID NAME +> -- ----- +> 1 Hello +> 2 World +> 3 Word +> 4 Wo% +> rows: 4 + +SELECT * FROM TEST WHERE NAME BETWEEN 'H' AND 'Word'; +> ID NAME +> -- ----- +> 1 Hello +> 3 Word +> 4 Wo% +> rows: 3 + +SELECT * FROM TEST WHERE ID >= 2 AND ID <= 3 AND ID <> 2; +> ID NAME +> -- ---- +> 3 Word +> rows: 1 + +SELECT * FROM TEST WHERE ID>0 AND ID<4 AND ID!=2; +> ID NAME +> -- ----- +> 1 Hello +> 3 Word +> rows: 2 + +SELECT * FROM TEST WHERE 'Hello' LIKE '_el%'; +> ID NAME +> -- ----- +> 0 null +> 1 Hello +> 2 World +> 3 Word +> 4 Wo% +> rows: 5 + +SELECT * FROM TEST WHERE NAME LIKE 'Hello%'; +> ID NAME +> -- ----- +> 1 Hello +> rows: 1 + +SELECT * FROM TEST WHERE NAME ILIKE 'hello%'; +> ID NAME +> -- ----- +> 1 Hello +> rows: 1 + +SELECT * FROM TEST WHERE NAME ILIKE 'xxx%'; +> ID NAME +> -- ---- +> rows: 0 + +SELECT * FROM TEST WHERE NAME LIKE 'Wo%'; +> ID NAME +> -- ----- +> 2 World +> 3 Word +> 4 Wo% +> rows: 3 + +SELECT * FROM TEST WHERE NAME LIKE 'Wo\%'; +> ID NAME +> -- ---- +> 4 Wo% +> rows: 1 + +SELECT * FROM TEST WHERE NAME LIKE 'WoX%' ESCAPE 'X'; +> ID NAME +> -- ---- +> 4 Wo% +> rows: 1 + +SELECT * FROM TEST WHERE NAME LIKE 'Word_'; +> ID NAME +> -- ---- +> rows: 0 + +SELECT * FROM TEST WHERE NAME LIKE '%Hello%'; +> ID NAME +> -- ----- +> 1 Hello +> rows: 1 + +SELECT * FROM TEST WHERE 'Hello' LIKE NAME; +> ID NAME +> -- ----- +> 1 Hello +> rows: 1 + +SELECT T1.*, T2.* FROM TEST AS T1, TEST AS T2 WHERE T1.ID = T2.ID AND T1.NAME LIKE T2.NAME || '%'; +> ID NAME ID NAME +> -- ----- -- ----- +> 1 Hello 1 Hello +> 2 World 2 World +> 3 Word 3 Word +> 4 Wo% 4 Wo% +> rows: 4 + +SELECT ID, MAX(NAME) FROM TEST GROUP BY ID HAVING MAX(NAME) = 'World'; +> ID MAX(NAME) +> -- --------- +> 2 World +> rows: 1 + +SELECT ID, MAX(NAME) FROM TEST GROUP BY ID HAVING MAX(NAME) LIKE 'World%'; +> ID MAX(NAME) +> -- --------- +> 2 World +> rows: 1 + +EXPLAIN SELECT ID FROM TEST WHERE NAME ILIKE 'w%'; +>> SELECT "ID" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ WHERE "NAME" ILIKE 'w%' + +DROP TABLE TEST; +> ok + +SELECT S, S LIKE '%', S ILIKE '%', S REGEXP '%' FROM (VALUES NULL, '', '1') T(S); +> S CASE WHEN S IS NOT NULL THEN TRUE ELSE UNKNOWN END CASE WHEN S IS NOT NULL THEN TRUE ELSE UNKNOWN END S REGEXP '%' +> ---- -------------------------------------------------- -------------------------------------------------- ------------ +> TRUE TRUE FALSE +> 1 TRUE TRUE FALSE +> null null null null +> rows: 3 + +SELECT S, S NOT LIKE '%', S NOT ILIKE '%', S NOT REGEXP '%' FROM (VALUES NULL, '', '1') T(S); +> S CASE WHEN S IS NOT NULL THEN FALSE ELSE UNKNOWN END CASE WHEN S IS NOT NULL THEN FALSE ELSE UNKNOWN END S NOT REGEXP '%' +> ---- --------------------------------------------------- --------------------------------------------------- ---------------- +> FALSE FALSE TRUE +> 1 FALSE FALSE TRUE +> null null null null +> rows: 3 + +CREATE TABLE TEST(ID BIGINT PRIMARY KEY, V VARCHAR UNIQUE) AS VALUES (1, 'aa'), (2, 'bb'); +> ok + +SELECT ID FROM (SELECT * FROM TEST) WHERE V NOT LIKE 'a%'; +>> 2 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/predicates/null.sql b/h2/src/test/org/h2/test/scripts/predicates/null.sql new file mode 100644 index 0000000..68ed960 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/predicates/null.sql @@ -0,0 +1,200 @@ +-- 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 +-- + +SELECT NULL IS NULL; +>> TRUE + +SELECT NULL IS NOT NULL; +>> FALSE + +SELECT NOT NULL IS NULL; +>> FALSE + +SELECT NOT NULL IS NOT NULL; +>> TRUE + +SELECT 1 IS NULL; +>> FALSE + +SELECT 1 IS NOT NULL; +>> TRUE + +SELECT NOT 1 IS NULL; +>> TRUE + +SELECT NOT 1 IS NOT NULL; +>> FALSE + +SELECT () IS NULL; +>> TRUE + +SELECT () IS NOT NULL; +>> TRUE + +SELECT NOT () IS NULL; +>> FALSE + +SELECT NOT () IS NOT NULL; +>> FALSE + +SELECT (NULL, NULL) IS NULL; +>> TRUE + +SELECT (NULL, NULL) IS NOT NULL; +>> FALSE + +SELECT NOT (NULL, NULL) IS NULL; +>> FALSE + +SELECT NOT (NULL, NULL) IS NOT NULL; +>> TRUE + +SELECT (NULL, 1) IS NULL; +>> FALSE + +SELECT (NULL, 1) IS NOT NULL; +>> FALSE + +SELECT NOT (NULL, 1) IS NULL; +>> TRUE + +SELECT NOT (NULL, 1) IS NOT NULL; +>> TRUE + +SELECT (1, 2) IS NULL; +>> FALSE + +SELECT (1, 2) IS NOT NULL; +>> TRUE + +SELECT NOT (1, 2) IS NULL; +>> TRUE + +SELECT NOT (1, 2) IS NOT NULL; +>> FALSE + +CREATE TABLE TEST(A INT, B INT) AS VALUES (NULL, NULL), (1, NULL), (NULL, 2), (1, 2); +> ok + +CREATE INDEX TEST_A_IDX ON TEST(A); +> ok + +CREATE INDEX TEST_B_IDX ON TEST(B); +> ok + +CREATE INDEX TEST_A_B_IDX ON TEST(A, B); +> ok + +SELECT * FROM TEST T1 JOIN TEST T2 ON T1.A = T2.A WHERE T2.A IS NULL; +> A B A B +> - - - - +> rows: 0 + +EXPLAIN SELECT * FROM TEST T1 JOIN TEST T2 ON T1.A = T2.A WHERE T2.A IS NULL; +>> SELECT "T1"."A", "T1"."B", "T2"."A", "T2"."B" FROM "PUBLIC"."TEST" "T2" /* PUBLIC.TEST_A_B_IDX: A IS NULL */ /* WHERE T2.A IS NULL */ INNER JOIN "PUBLIC"."TEST" "T1" /* PUBLIC.TEST_A_B_IDX: A = T2.A */ ON 1=1 WHERE ("T2"."A" IS NULL) AND ("T1"."A" = "T2"."A") + +SELECT * FROM TEST T1 LEFT JOIN TEST T2 ON T1.A = T2.A WHERE T2.A IS NULL; +> A B A B +> ---- ---- ---- ---- +> null 2 null null +> null null null null +> rows: 2 + +EXPLAIN SELECT * FROM TEST T1 LEFT JOIN TEST T2 ON T1.A = T2.A WHERE T2.A IS NULL; +>> SELECT "T1"."A", "T1"."B", "T2"."A", "T2"."B" FROM "PUBLIC"."TEST" "T1" /* PUBLIC.TEST_A_B_IDX */ LEFT OUTER JOIN "PUBLIC"."TEST" "T2" /* PUBLIC.TEST_A_B_IDX: A = T1.A */ ON "T1"."A" = "T2"."A" WHERE "T2"."A" IS NULL + +SELECT * FROM TEST T1 JOIN TEST T2 ON T1.A = T2.A WHERE T2.A IS NOT NULL; +> A B A B +> - ---- - ---- +> 1 2 1 2 +> 1 2 1 null +> 1 null 1 2 +> 1 null 1 null +> rows: 4 + +EXPLAIN SELECT * FROM TEST T1 JOIN TEST T2 ON T1.A = T2.A WHERE T2.A IS NOT NULL; +>> SELECT "T1"."A", "T1"."B", "T2"."A", "T2"."B" FROM "PUBLIC"."TEST" "T1" /* PUBLIC.TEST_A_B_IDX */ INNER JOIN "PUBLIC"."TEST" "T2" /* PUBLIC.TEST_A_B_IDX: A = T1.A */ ON 1=1 WHERE ("T2"."A" IS NOT NULL) AND ("T1"."A" = "T2"."A") + +SELECT * FROM TEST T1 LEFT JOIN TEST T2 ON T1.A = T2.A WHERE T2.A IS NOT NULL; +> A B A B +> - ---- - ---- +> 1 2 1 2 +> 1 2 1 null +> 1 null 1 2 +> 1 null 1 null +> rows: 4 + +EXPLAIN SELECT * FROM TEST T1 LEFT JOIN TEST T2 ON T1.A = T2.A WHERE T2.A IS NOT NULL; +>> SELECT "T1"."A", "T1"."B", "T2"."A", "T2"."B" FROM "PUBLIC"."TEST" "T1" /* PUBLIC.TEST_A_B_IDX */ LEFT OUTER JOIN "PUBLIC"."TEST" "T2" /* PUBLIC.TEST_A_B_IDX: A = T1.A */ ON "T1"."A" = "T2"."A" WHERE "T2"."A" IS NOT NULL + +SELECT * FROM TEST T1 JOIN TEST T2 ON (T1.A, T1.B) = (T2.A, T2.B) WHERE (T2.A, T2.B) IS NULL; +> A B A B +> - - - - +> rows: 0 + +EXPLAIN SELECT * FROM TEST T1 JOIN TEST T2 ON (T1.A, T1.B) = (T2.A, T2.B) WHERE (T2.A, T2.B) IS NULL; +>> SELECT "T1"."A", "T1"."B", "T2"."A", "T2"."B" FROM "PUBLIC"."TEST" "T2" /* PUBLIC.TEST_A_B_IDX: A IS NULL AND B IS NULL */ /* WHERE ROW (T2.A, T2.B) IS NULL */ INNER JOIN "PUBLIC"."TEST" "T1" /* PUBLIC.TEST_A_B_IDX */ ON 1=1 WHERE (ROW ("T2"."A", "T2"."B") IS NULL) AND (ROW ("T1"."A", "T1"."B") = ROW ("T2"."A", "T2"."B")) + +SELECT * FROM TEST T1 LEFT JOIN TEST T2 ON (T1.A, T1.B) = (T2.A, T2.B) WHERE (T2.A, T2.B) IS NULL; +> A B A B +> ---- ---- ---- ---- +> 1 null null null +> null 2 null null +> null null null null +> rows: 3 + +EXPLAIN SELECT * FROM TEST T1 LEFT JOIN TEST T2 ON (T1.A, T1.B) = (T2.A, T2.B) WHERE (T2.A, T2.B) IS NULL; +>> SELECT "T1"."A", "T1"."B", "T2"."A", "T2"."B" FROM "PUBLIC"."TEST" "T1" /* PUBLIC.TEST_A_B_IDX */ LEFT OUTER JOIN "PUBLIC"."TEST" "T2" /* PUBLIC.TEST_A_B_IDX */ ON ROW ("T1"."A", "T1"."B") = ROW ("T2"."A", "T2"."B") WHERE ROW ("T2"."A", "T2"."B") IS NULL + +SELECT * FROM TEST T1 JOIN TEST T2 ON (T1.A, T1.B) = (T2.A, T2.B) WHERE (T2.A, T2.B) IS NOT NULL; +> A B A B +> - - - - +> 1 2 1 2 +> rows: 1 + +EXPLAIN SELECT * FROM TEST T1 JOIN TEST T2 ON (T1.A, T1.B) = (T2.A, T2.B) WHERE (T2.A, T2.B) IS NOT NULL; +>> SELECT "T1"."A", "T1"."B", "T2"."A", "T2"."B" FROM "PUBLIC"."TEST" "T1" /* PUBLIC.TEST_A_B_IDX */ INNER JOIN "PUBLIC"."TEST" "T2" /* PUBLIC.TEST_A_B_IDX */ ON 1=1 WHERE (ROW ("T2"."A", "T2"."B") IS NOT NULL) AND (ROW ("T1"."A", "T1"."B") = ROW ("T2"."A", "T2"."B")) + +SELECT * FROM TEST T1 LEFT JOIN TEST T2 ON (T1.A, T1.B) = (T2.A, T2.B) WHERE (T2.A, T2.B) IS NOT NULL; +> A B A B +> - - - - +> 1 2 1 2 +> rows: 1 + +EXPLAIN SELECT * FROM TEST T1 LEFT JOIN TEST T2 ON (T1.A, T1.B) = (T2.A, T2.B) WHERE (T2.A, T2.B) IS NOT NULL; +>> SELECT "T1"."A", "T1"."B", "T2"."A", "T2"."B" FROM "PUBLIC"."TEST" "T1" /* PUBLIC.TEST_A_B_IDX */ LEFT OUTER JOIN "PUBLIC"."TEST" "T2" /* PUBLIC.TEST_A_B_IDX */ ON ROW ("T1"."A", "T1"."B") = ROW ("T2"."A", "T2"."B") WHERE ROW ("T2"."A", "T2"."B") IS NOT NULL + +EXPLAIN SELECT A, B FROM TEST WHERE (A, NULL) IS NULL; +>> SELECT "A", "B" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_A_B_IDX: A IS NULL */ WHERE "A" IS NULL + +EXPLAIN SELECT A, B FROM TEST WHERE (A, NULL) IS NOT NULL; +>> SELECT "A", "B" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan: FALSE */ WHERE FALSE + +EXPLAIN SELECT A, B FROM TEST WHERE NOT (A, NULL) IS NULL; +>> SELECT "A", "B" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_A_B_IDX */ WHERE "A" IS NOT NULL + +EXPLAIN SELECT A, B FROM TEST WHERE NOT (A, NULL) IS NOT NULL; +>> SELECT "A", "B" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_A_B_IDX */ + +EXPLAIN SELECT A, B FROM TEST WHERE (A, NULL, B) IS NULL; +>> SELECT "A", "B" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_A_B_IDX: A IS NULL AND B IS NULL */ WHERE ROW ("A", "B") IS NULL + +EXPLAIN SELECT A, B FROM TEST WHERE (A, NULL, B, NULL) IS NULL; +>> SELECT "A", "B" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_A_B_IDX: A IS NULL AND B IS NULL */ WHERE ROW ("A", "B") IS NULL + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(I INTEGER) AS VALUES 1; +> ok + + +SELECT I FROM TEST WHERE _ROWID_ IS NULL; +> I +> - +> rows: 0 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/predicates/type.sql b/h2/src/test/org/h2/test/scripts/predicates/type.sql new file mode 100644 index 0000000..d555c80 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/predicates/type.sql @@ -0,0 +1,49 @@ +-- 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 +-- + +SELECT 1 IS OF (INT); +>> TRUE + +SELECT 1 IS NOT OF (INT); +>> FALSE + +SELECT NULL IS OF (INT); +>> null + +SELECT NULL IS NOT OF (INT); +>> null + +SELECT 1 IS OF (INT, BIGINT); +>> TRUE + +SELECT 1 IS NOT OF (INT, BIGINT); +>> FALSE + +SELECT TRUE IS OF (VARCHAR, TIME); +>> FALSE + +SELECT TRUE IS NOT OF (VARCHAR, TIME); +>> TRUE + +CREATE TABLE TEST(A INT NOT NULL, B INT); +> ok + +EXPLAIN SELECT + 'Test' IS OF (VARCHAR), 'Test' IS NOT OF (VARCHAR), + 10 IS OF (VARCHAR), 10 IS NOT OF (VARCHAR), + NULL IS OF (VARCHAR), NULL IS NOT OF (VARCHAR); +>> SELECT TRUE, FALSE, FALSE, TRUE, UNKNOWN, UNKNOWN + +EXPLAIN SELECT A IS OF (INT), A IS OF (BIGINT), A IS NOT OF (INT), NOT A IS OF (BIGINT) FROM TEST; +>> SELECT "A" IS OF (INTEGER), "A" IS OF (BIGINT), "A" IS NOT OF (INTEGER), "A" IS NOT OF (BIGINT) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT B IS OF (INT), B IS OF (BIGINT), B IS NOT OF (INT), NOT B IS OF (BIGINT) FROM TEST; +>> SELECT "B" IS OF (INTEGER), "B" IS OF (BIGINT), "B" IS NOT OF (INTEGER), "B" IS NOT OF (BIGINT) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT A IS NOT OF(INT) OR B IS OF (INT) FROM TEST; +>> SELECT ("A" IS NOT OF (INTEGER)) OR ("B" IS OF (INTEGER)) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/predicates/unique.sql b/h2/src/test/org/h2/test/scripts/predicates/unique.sql new file mode 100644 index 0000000..ffc26ea --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/predicates/unique.sql @@ -0,0 +1,54 @@ +-- 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 +-- + +CREATE TABLE TEST(ID INT PRIMARY KEY, GR INT, A INT, B INT, C INT) AS VALUES + (1, 1, NULL, NULL, NULL), + (2, 1, NULL, NULL, NULL), + (3, 1, NULL, 1, 1), + (4, 1, NULL, 1, 1), + (5, 1, 1, 1, 1), + (6, 1, 1, 1, 2), + (7, 2, 1, 2, 1); +> ok + +SELECT UNIQUE(SELECT A, B FROM TEST); +>> FALSE + +SELECT UNIQUE(TABLE TEST); +>> TRUE + +SELECT UNIQUE(SELECT A, B, C FROM TEST); +>> TRUE + +EXPLAIN SELECT UNIQUE(SELECT A, B FROM TEST); +>> SELECT UNIQUE( SELECT "A", "B" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */) + +SELECT UNIQUE(SELECT A, B FROM TEST); +>> FALSE + +EXPLAIN SELECT UNIQUE(SELECT DISTINCT A, B FROM TEST); +>> SELECT TRUE + +SELECT UNIQUE(SELECT DISTINCT A, B FROM TEST); +>> TRUE + +SELECT G, UNIQUE(SELECT A, B, C FROM TEST WHERE GR = G) FROM (VALUES 1, 2, 3) V(G); +> G UNIQUE( SELECT A, B, C FROM PUBLIC.TEST WHERE GR = G) +> - ----------------------------------------------------- +> 1 TRUE +> 2 TRUE +> 3 TRUE +> rows: 3 + +SELECT G, UNIQUE(SELECT A, B FROM TEST WHERE GR = G ORDER BY A + B) FROM (VALUES 1, 2, 3) V(G); +> G UNIQUE( SELECT A, B FROM PUBLIC.TEST WHERE GR = G ORDER BY A + B) +> - ----------------------------------------------------------------- +> 1 FALSE +> 2 TRUE +> 3 TRUE +> rows: 3 + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/queries/derived-column-names.sql b/h2/src/test/org/h2/test/scripts/queries/derived-column-names.sql new file mode 100644 index 0000000..1b36b3f --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/queries/derived-column-names.sql @@ -0,0 +1,88 @@ +-- 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 +-- + +SELECT * FROM (VALUES(1, 2)); +> C1 C2 +> -- -- +> 1 2 +> rows: 1 + +SELECT * FROM (VALUES(1, 2)) AS T; +> C1 C2 +> -- -- +> 1 2 +> rows: 1 + +SELECT * FROM (VALUES(1, 2)) AS T(A, B); +> A B +> - - +> 1 2 +> rows: 1 + +SELECT A AS A1, B AS B1 FROM (VALUES(1, 2)) AS T(A, B); +> A1 B1 +> -- -- +> 1 2 +> rows: 1 + +SELECT A AS A1, B AS B1 FROM (VALUES(1, 2)) AS T(A, B) WHERE A <> B; +> A1 B1 +> -- -- +> 1 2 +> rows: 1 + +SELECT A AS A1, B AS B1 FROM (VALUES(1, 2)) AS T(A, B) WHERE A1 <> B1; +> exception COLUMN_NOT_FOUND_1 + +SELECT * FROM (VALUES(1, 2)) AS T(A); +> exception COLUMN_COUNT_DOES_NOT_MATCH + +SELECT * FROM (VALUES(1, 2)) AS T(A, a); +> exception DUPLICATE_COLUMN_NAME_1 + +SELECT * FROM (VALUES(1, 2)) AS T(A, B, C); +> exception COLUMN_COUNT_DOES_NOT_MATCH + +SELECT V AS V1, A AS A1, B AS B1 FROM (VALUES (1)) T1(V) INNER JOIN (VALUES(1, 2)) T2(A, B) ON V = A; +> V1 A1 B1 +> -- -- -- +> 1 1 2 +> rows: 1 + +CREATE TABLE TEST(I INT, J INT); +> ok + +CREATE INDEX TEST_I_IDX ON TEST(I); +> ok + +INSERT INTO TEST VALUES (1, 2); +> update count: 1 + +SELECT * FROM (TEST) AS T(A, B); +> A B +> - - +> 1 2 +> rows: 1 + +SELECT * FROM TEST AS T(A, B); +> A B +> - - +> 1 2 +> rows: 1 + +SELECT * FROM TEST AS T(A, B) USE INDEX (TEST_I_IDX); +> A B +> - - +> 1 2 +> rows: 1 + +DROP TABLE TEST; +> ok + +SELECT * FROM (SELECT 1 A, 2 A) T(B, C); +> B C +> - - +> 1 2 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/queries/distinct.sql b/h2/src/test/org/h2/test/scripts/queries/distinct.sql new file mode 100644 index 0000000..7da7c9a --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/queries/distinct.sql @@ -0,0 +1,190 @@ +-- 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 +-- + +CREATE TABLE TEST(ID BIGINT, NAME VARCHAR); +> ok + +INSERT INTO TEST VALUES (1, 'a'), (2, 'B'), (3, 'c'), (1, 'a'); +> update count: 4 + +CREATE TABLE TEST2(ID2 BIGINT); +> ok + +INSERT INTO TEST2 VALUES (1), (2); +> update count: 2 + +SELECT DISTINCT NAME FROM TEST ORDER BY NAME; +> NAME +> ---- +> B +> a +> c +> rows (ordered): 3 + +SELECT DISTINCT NAME FROM TEST ORDER BY LOWER(NAME); +> NAME +> ---- +> a +> B +> c +> rows (ordered): 3 + +SELECT DISTINCT ID FROM TEST ORDER BY ID; +> ID +> -- +> 1 +> 2 +> 3 +> rows (ordered): 3 + +SELECT DISTINCT ID FROM TEST ORDER BY -ID - 1; +> ID +> -- +> 3 +> 2 +> 1 +> rows (ordered): 3 + +SELECT DISTINCT ID FROM TEST ORDER BY (-ID + 10) > 0 AND NOT (ID = 0), ID; +> ID +> -- +> 1 +> 2 +> 3 +> rows (ordered): 3 + +SELECT DISTINCT NAME, ID + 1 FROM TEST ORDER BY UPPER(NAME) || (ID + 1); +> NAME ID + 1 +> ---- ------ +> a 2 +> B 3 +> c 4 +> rows (ordered): 3 + +SELECT DISTINCT ID FROM TEST ORDER BY NAME; +> exception ORDER_BY_NOT_IN_RESULT + +SELECT DISTINCT ID FROM TEST ORDER BY UPPER(NAME); +> exception ORDER_BY_NOT_IN_RESULT + +SELECT DISTINCT ID FROM TEST ORDER BY CURRENT_TIMESTAMP; +> exception ORDER_BY_NOT_IN_RESULT + +SET MODE MySQL; +> ok + +SELECT DISTINCT ID FROM TEST ORDER BY NAME; +> ID +> -- +> 2 +> 1 +> 3 +> rows (ordered): 3 + +SELECT DISTINCT ID FROM TEST ORDER BY LOWER(NAME); +> ID +> -- +> 1 +> 2 +> 3 +> rows (ordered): 3 + +SELECT DISTINCT ID FROM TEST JOIN TEST2 ON ID = ID2 ORDER BY LOWER(NAME); +> ID +> -- +> 1 +> 2 +> rows (ordered): 2 + +SET MODE Regular; +> ok + +DROP TABLE TEST; +> ok + +DROP TABLE TEST2; +> ok + +CREATE TABLE TEST(C1 INT, C2 INT, C3 INT, C4 INT, C5 INT); +> ok + +INSERT INTO TEST VALUES(1, 2, 3, 4, 5), (1, 2, 3, 6, 7), (2, 1, 4, 8, 9), (3, 4, 5, 1, 1); +> update count: 4 + +SELECT DISTINCT ON(C1, C2) C1, C2, C3, C4, C5 FROM TEST; +> C1 C2 C3 C4 C5 +> -- -- -- -- -- +> 1 2 3 4 5 +> 2 1 4 8 9 +> 3 4 5 1 1 +> rows: 3 + +SELECT DISTINCT ON(C1 + C2) C1, C2, C3, C4, C5 FROM TEST; +> C1 C2 C3 C4 C5 +> -- -- -- -- -- +> 1 2 3 4 5 +> 3 4 5 1 1 +> rows: 2 + +SELECT DISTINCT ON(C1 + C2, C3) C1, C2, C3, C4, C5 FROM TEST; +> C1 C2 C3 C4 C5 +> -- -- -- -- -- +> 1 2 3 4 5 +> 2 1 4 8 9 +> 3 4 5 1 1 +> rows: 3 + +SELECT DISTINCT ON(C1) C2 FROM TEST ORDER BY C1; +> C2 +> -- +> 2 +> 1 +> 4 +> rows (ordered): 3 + +SELECT DISTINCT ON(C1) C1, C4, C5 FROM TEST ORDER BY C1, C5; +> C1 C4 C5 +> -- -- -- +> 1 4 5 +> 2 8 9 +> 3 1 1 +> rows (ordered): 3 + +SELECT DISTINCT ON(C1) C1, C4, C5 FROM TEST ORDER BY C1, C5 DESC; +> C1 C4 C5 +> -- -- -- +> 1 6 7 +> 2 8 9 +> 3 1 1 +> rows (ordered): 3 + +SELECT T1.C1, T2.C5 FROM TEST T1 JOIN ( + SELECT DISTINCT ON(C1) C1, C4, C5 FROM TEST ORDER BY C1, C5 +) T2 ON T1.C4 = T2.C4 ORDER BY T1.C1; +> C1 C5 +> -- -- +> 1 5 +> 2 9 +> 3 1 +> rows (ordered): 3 + +SELECT T1.C1, T2.C5 FROM TEST T1 JOIN ( + SELECT DISTINCT ON(C1) C1, C4, C5 FROM TEST ORDER BY C1, C5 DESC +) T2 ON T1.C4 = T2.C4 ORDER BY T1.C1; +> C1 C5 +> -- -- +> 1 7 +> 2 9 +> 3 1 +> rows (ordered): 3 + +EXPLAIN SELECT DISTINCT ON(C1) C2 FROM TEST ORDER BY C1; +>> SELECT DISTINCT ON("C1") "C2" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ ORDER BY "C1" + +SELECT DISTINCT ON(C1) C2 FROM TEST ORDER BY C3; +> exception ORDER_BY_NOT_IN_RESULT + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/queries/joins.sql b/h2/src/test/org/h2/test/scripts/queries/joins.sql new file mode 100644 index 0000000..57ccf2a --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/queries/joins.sql @@ -0,0 +1,1046 @@ +-- 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 +-- + +create table a(a int) as select 1; +> ok + +create table b(b int) as select 1; +> ok + +create table c(c int) as select x from system_range(1, 2); +> ok + +select * from a inner join b on a=b right outer join c on c=a; +> A B C +> ---- ---- - +> 1 1 1 +> null null 2 +> rows: 2 + +select * from c left outer join (a inner join b on b=a) on c=a; +> C A B +> - ---- ---- +> 1 1 1 +> 2 null null +> rows: 2 + +select * from c left outer join a on c=a inner join b on b=a; +> C A B +> - - - +> 1 1 1 +> rows: 1 + +drop table a, b, c; +> ok + +create table test(a int, b int) as select x, x from system_range(1, 100); +> ok + +-- the table t1 should be processed first +explain select * from test t2, test t1 where t1.a=1 and t1.b = t2.b; +>> SELECT "T2"."A", "T2"."B", "T1"."A", "T1"."B" FROM "PUBLIC"."TEST" "T1" /* PUBLIC.TEST.tableScan */ /* WHERE T1.A = 1 */ INNER JOIN "PUBLIC"."TEST" "T2" /* PUBLIC.TEST.tableScan */ ON 1=1 WHERE ("T1"."A" = 1) AND ("T1"."B" = "T2"."B") + +explain select * from test t1, test t2 where t1.a=1 and t1.b = t2.b; +>> SELECT "T1"."A", "T1"."B", "T2"."A", "T2"."B" FROM "PUBLIC"."TEST" "T1" /* PUBLIC.TEST.tableScan */ /* WHERE T1.A = 1 */ INNER JOIN "PUBLIC"."TEST" "T2" /* PUBLIC.TEST.tableScan */ ON 1=1 WHERE ("T1"."A" = 1) AND ("T1"."B" = "T2"."B") + +drop table test; +> ok + +create table test(id identity) as select x from system_range(1, 4); +> ok + +select a.id from test a inner join test b on a.id > b.id and b.id < 3 group by a.id; +> ID +> -- +> 2 +> 3 +> 4 +> rows: 3 + +drop table test; +> ok + +select * from system_range(1, 3) t1 inner join system_range(2, 3) t2 inner join system_range(1, 2) t3 on t3.x=t2.x on t1.x=t2.x; +> X X X +> - - - +> 2 2 2 +> rows: 1 + +CREATE TABLE PARENT(ID INT PRIMARY KEY); +> ok + +CREATE TABLE CHILD(ID INT PRIMARY KEY); +> ok + +INSERT INTO PARENT VALUES(1); +> update count: 1 + +SELECT * FROM PARENT P LEFT OUTER JOIN CHILD C ON C.PARENTID=P.ID; +> exception COLUMN_NOT_FOUND_1 + +DROP TABLE PARENT, CHILD; +> ok + +create table t1 (i int); +> ok + +create table t2 (i int); +> ok + +create table t3 (i int); +> ok + +select a.i from t1 a inner join (select a.i from t2 a inner join (select i from t3) b on a.i=b.i) b on a.i=b.i; +> I +> - +> rows: 0 + +insert into t1 values (1); +> update count: 1 + +insert into t2 values (1); +> update count: 1 + +insert into t3 values (1); +> update count: 1 + +select a.i from t1 a inner join (select a.i from t2 a inner join (select i from t3) b on a.i=b.i) b on a.i=b.i; +> I +> - +> 1 +> rows: 1 + +drop table t1, t2, t3; +> ok + +CREATE TABLE TESTA(ID IDENTITY); +> ok + +CREATE TABLE TESTB(ID IDENTITY); +> ok + +explain SELECT TESTA.ID A, TESTB.ID B FROM TESTA, TESTB ORDER BY TESTA.ID, TESTB.ID; +>> SELECT "TESTA"."ID" AS "A", "TESTB"."ID" AS "B" FROM "PUBLIC"."TESTA" /* PUBLIC.TESTA.tableScan */ INNER JOIN "PUBLIC"."TESTB" /* PUBLIC.TESTB.tableScan */ ON 1=1 ORDER BY 1, 2 + +DROP TABLE IF EXISTS TESTA, TESTB; +> ok + +create table one (id int primary key); +> ok + +create table two (id int primary key, val date); +> ok + +insert into one values(0); +> update count: 1 + +insert into one values(1); +> update count: 1 + +insert into one values(2); +> update count: 1 + +insert into two values(0, null); +> update count: 1 + +insert into two values(1, DATE'2006-01-01'); +> update count: 1 + +insert into two values(2, DATE'2006-07-01'); +> update count: 1 + +insert into two values(3, null); +> update count: 1 + +select * from one; +> ID +> -- +> 0 +> 1 +> 2 +> rows: 3 + +select * from two; +> ID VAL +> -- ---------- +> 0 null +> 1 2006-01-01 +> 2 2006-07-01 +> 3 null +> rows: 4 + +-- Query #1: should return one row +-- okay +select * from one natural join two left join two three on +one.id=three.id left join one four on two.id=four.id where three.val +is null; +> ID VAL ID VAL ID +> -- ---- -- ---- -- +> 0 null 0 null 0 +> rows: 1 + +-- Query #2: should return one row +-- okay +select * from one natural join two left join two three on +one.id=three.id left join one four on two.id=four.id where +three.val>=DATE'2006-07-01'; +> ID VAL ID VAL ID +> -- ---------- -- ---------- -- +> 2 2006-07-01 2 2006-07-01 2 +> rows: 1 + +-- Query #3: should return the union of #1 and #2 +select * from one natural join two left join two three on +one.id=three.id left join one four on two.id=four.id where three.val +is null or three.val>=DATE'2006-07-01'; +> ID VAL ID VAL ID +> -- ---------- -- ---------- -- +> 0 null 0 null 0 +> 2 2006-07-01 2 2006-07-01 2 +> rows: 2 + +explain select * from one natural join two left join two three on +one.id=three.id left join one four on two.id=four.id where three.val +is null or three.val>=DATE'2006-07-01'; +>> SELECT "PUBLIC"."ONE"."ID", "PUBLIC"."TWO"."VAL", "THREE"."ID", "THREE"."VAL", "FOUR"."ID" FROM "PUBLIC"."ONE" /* PUBLIC.ONE.tableScan */ INNER JOIN "PUBLIC"."TWO" /* PUBLIC.PRIMARY_KEY_14: ID = PUBLIC.ONE.ID */ ON 1=1 /* WHERE PUBLIC.ONE.ID = PUBLIC.TWO.ID */ LEFT OUTER JOIN "PUBLIC"."TWO" "THREE" /* PUBLIC.PRIMARY_KEY_14: ID = ONE.ID */ ON "ONE"."ID" = "THREE"."ID" LEFT OUTER JOIN "PUBLIC"."ONE" "FOUR" /* PUBLIC.PRIMARY_KEY_1: ID = TWO.ID */ ON "TWO"."ID" = "FOUR"."ID" WHERE ("PUBLIC"."ONE"."ID" = "PUBLIC"."TWO"."ID") AND (("THREE"."VAL" IS NULL) OR ("THREE"."VAL" >= DATE '2006-07-01')) + +-- Query #4: same as #3, but the joins have been manually re-ordered +-- Correct result set, same as expected for #3. +select * from one natural join two left join one four on +two.id=four.id left join two three on one.id=three.id where three.val +is null or three.val>=DATE'2006-07-01'; +> ID VAL ID ID VAL +> -- ---------- -- -- ---------- +> 0 null 0 0 null +> 2 2006-07-01 2 2 2006-07-01 +> rows: 2 + +drop table one; +> ok + +drop table two; +> ok + +create table test1 (id int primary key); +> ok + +create table test2 (id int primary key); +> ok + +create table test3 (id int primary key); +> ok + +insert into test1 values(1); +> update count: 1 + +insert into test2 values(1); +> update count: 1 + +insert into test3 values(1); +> update count: 1 + +select * from test1 +inner join test2 on test1.id=test2.id left +outer join test3 on test2.id=test3.id +where test3.id is null; +> ID ID ID +> -- -- -- +> rows: 0 + +explain select * from test1 +inner join test2 on test1.id=test2.id left +outer join test3 on test2.id=test3.id +where test3.id is null; +>> SELECT "PUBLIC"."TEST1"."ID", "PUBLIC"."TEST2"."ID", "PUBLIC"."TEST3"."ID" FROM "PUBLIC"."TEST1" /* PUBLIC.TEST1.tableScan */ INNER JOIN "PUBLIC"."TEST2" /* PUBLIC.PRIMARY_KEY_4C: ID = TEST1.ID */ ON 1=1 /* WHERE TEST1.ID = TEST2.ID */ LEFT OUTER JOIN "PUBLIC"."TEST3" /* PUBLIC.PRIMARY_KEY_4C0: ID = TEST2.ID */ ON "TEST2"."ID" = "TEST3"."ID" WHERE ("TEST3"."ID" IS NULL) AND ("TEST1"."ID" = "TEST2"."ID") + +insert into test1 select x from system_range(2, 1000); +> update count: 999 + +select * from test1 +inner join test2 on test1.id=test2.id +left outer join test3 on test2.id=test3.id +where test3.id is null; +> ID ID ID +> -- -- -- +> rows: 0 + +explain select * from test1 +inner join test2 on test1.id=test2.id +left outer join test3 on test2.id=test3.id +where test3.id is null; +>> SELECT "PUBLIC"."TEST1"."ID", "PUBLIC"."TEST2"."ID", "PUBLIC"."TEST3"."ID" FROM "PUBLIC"."TEST2" /* PUBLIC.TEST2.tableScan */ LEFT OUTER JOIN "PUBLIC"."TEST3" /* PUBLIC.PRIMARY_KEY_4C0: ID = TEST2.ID */ ON "TEST2"."ID" = "TEST3"."ID" INNER JOIN "PUBLIC"."TEST1" /* PUBLIC.PRIMARY_KEY_4: ID = TEST2.ID */ ON 1=1 WHERE ("TEST3"."ID" IS NULL) AND ("TEST1"."ID" = "TEST2"."ID") + +SELECT TEST1.ID, TEST2.ID, TEST3.ID +FROM TEST2 +LEFT OUTER JOIN TEST3 ON TEST2.ID = TEST3.ID +INNER JOIN TEST1 +WHERE TEST3.ID IS NULL AND TEST1.ID = TEST2.ID; +> ID ID ID +> -- -- -- +> rows: 0 + +drop table test1; +> ok + +drop table test2; +> ok + +drop table test3; +> ok + +create table left_hand (id int primary key); +> ok + +create table right_hand (id int primary key); +> ok + +insert into left_hand values(0); +> update count: 1 + +insert into left_hand values(1); +> update count: 1 + +insert into right_hand values(0); +> update count: 1 + +-- h2, postgresql, mysql, derby, hsqldb: 2 +select * from left_hand left outer join right_hand on left_hand.id=right_hand.id; +> ID ID +> -- ---- +> 0 0 +> 1 null +> rows: 2 + +-- h2, postgresql, mysql, derby, hsqldb: 2 +select * from left_hand left join right_hand on left_hand.id=right_hand.id; +> ID ID +> -- ---- +> 0 0 +> 1 null +> rows: 2 + +-- h2: 1 (2 cols); postgresql, mysql: 1 (1 col); derby, hsqldb: no natural join +select * from left_hand natural join right_hand; +> ID +> -- +> 0 +> rows: 1 + +-- h2, postgresql, mysql, derby, hsqldb: 1 +select * from left_hand left outer join right_hand on left_hand.id=right_hand.id where left_hand.id=1; +> ID ID +> -- ---- +> 1 null +> rows: 1 + +-- h2, postgresql, mysql, derby, hsqldb: 1 +select * from left_hand left join right_hand on left_hand.id=right_hand.id where left_hand.id=1; +> ID ID +> -- ---- +> 1 null +> rows: 1 + +-- h2: 0 (2 cols); postgresql, mysql: 0 (1 col); derby, hsqldb: no natural join +select * from left_hand natural join right_hand where left_hand.id=1; +> ID +> -- +> rows: 0 + +-- !!! h2: 1; postgresql, mysql, hsqldb: 0; derby: exception +select * from left_hand left outer join right_hand on left_hand.id=right_hand.id where left_hand.id=1 having right_hand.id=2; +> ID ID +> -- -- +> rows: 0 + +-- !!! h2: 1; postgresql, mysql, hsqldb: 0; derby: exception +select * from left_hand left join right_hand on left_hand.id=right_hand.id where left_hand.id=1 having right_hand.id=2; +> ID ID +> -- -- +> rows: 0 + +-- h2: 0 (2 cols); postgresql: 0 (1 col), mysql: exception; derby, hsqldb: no natural join +select * from left_hand natural join right_hand where left_hand.id=1 having right_hand.id=2; +> exception MUST_GROUP_BY_COLUMN_1 + +-- h2, mysql, hsqldb: 0 rows; postgresql, derby: exception +select * from left_hand left outer join right_hand on left_hand.id=right_hand.id where left_hand.id=1 group by left_hand.id having right_hand.id=2; +> ID ID +> -- -- +> rows: 0 + +-- h2, mysql, hsqldb: 0 rows; postgresql, derby: exception +select * from left_hand left join right_hand on left_hand.id=right_hand.id where left_hand.id=1 group by left_hand.id having right_hand.id=2; +> ID ID +> -- -- +> rows: 0 + +-- h2: 0 rows; postgresql, mysql: exception; derby, hsqldb: no natural join +select * from left_hand natural join right_hand where left_hand.id=1 group by left_hand.id having right_hand.id=2; +> ID +> -- +> rows: 0 + +drop table right_hand; +> ok + +drop table left_hand; +> ok + +--- complex join --------------------------------------------------------------------------------------------- +CREATE TABLE T1(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +CREATE TABLE T2(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +CREATE TABLE T3(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +INSERT INTO T1 VALUES(1, 'Hello'); +> update count: 1 + +INSERT INTO T1 VALUES(2, 'World'); +> update count: 1 + +INSERT INTO T1 VALUES(3, 'Peace'); +> update count: 1 + +INSERT INTO T2 VALUES(1, 'Hello'); +> update count: 1 + +INSERT INTO T2 VALUES(2, 'World'); +> update count: 1 + +INSERT INTO T3 VALUES(1, 'Hello'); +> update count: 1 + +SELECT * FROM t1 left outer join t2 on t1.id=t2.id; +> ID NAME ID NAME +> -- ----- ---- ----- +> 1 Hello 1 Hello +> 2 World 2 World +> 3 Peace null null +> rows: 3 + +SELECT * FROM t1 left outer join t2 on t1.id=t2.id left outer join t3 on t1.id=t3.id; +> ID NAME ID NAME ID NAME +> -- ----- ---- ----- ---- ----- +> 1 Hello 1 Hello 1 Hello +> 2 World 2 World null null +> 3 Peace null null null null +> rows: 3 + +SELECT * FROM t1 left outer join t2 on t1.id=t2.id inner join t3 on t1.id=t3.id; +> ID NAME ID NAME ID NAME +> -- ----- -- ----- -- ----- +> 1 Hello 1 Hello 1 Hello +> rows: 1 + +drop table t1; +> ok + +drop table t2; +> ok + +drop table t3; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, parent int, sid int); +> ok + +create index idx_p on test(sid); +> ok + +insert into test select x, x, x from system_range(0,20); +> update count: 21 + +select * from test l0 inner join test l1 on l0.sid=l1.sid, test l3 where l0.sid=l3.parent; +> ID PARENT SID ID PARENT SID ID PARENT SID +> -- ------ --- -- ------ --- -- ------ --- +> 0 0 0 0 0 0 0 0 0 +> 1 1 1 1 1 1 1 1 1 +> 10 10 10 10 10 10 10 10 10 +> 11 11 11 11 11 11 11 11 11 +> 12 12 12 12 12 12 12 12 12 +> 13 13 13 13 13 13 13 13 13 +> 14 14 14 14 14 14 14 14 14 +> 15 15 15 15 15 15 15 15 15 +> 16 16 16 16 16 16 16 16 16 +> 17 17 17 17 17 17 17 17 17 +> 18 18 18 18 18 18 18 18 18 +> 19 19 19 19 19 19 19 19 19 +> 2 2 2 2 2 2 2 2 2 +> 20 20 20 20 20 20 20 20 20 +> 3 3 3 3 3 3 3 3 3 +> 4 4 4 4 4 4 4 4 4 +> 5 5 5 5 5 5 5 5 5 +> 6 6 6 6 6 6 6 6 6 +> 7 7 7 7 7 7 7 7 7 +> 8 8 8 8 8 8 8 8 8 +> 9 9 9 9 9 9 9 9 9 +> rows: 21 + +select * from +test l0 +inner join test l1 on l0.sid=l1.sid +inner join test l2 on l0.sid=l2.id, +test l5 +inner join test l3 on l5.sid=l3.sid +inner join test l4 on l5.sid=l4.id +where l2.id is not null +and l0.sid=l5.parent; +> ID PARENT SID ID PARENT SID ID PARENT SID ID PARENT SID ID PARENT SID ID PARENT SID +> -- ------ --- -- ------ --- -- ------ --- -- ------ --- -- ------ --- -- ------ --- +> 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 +> 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +> 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 +> 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 11 +> 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 12 +> 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 13 +> 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 +> 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 15 +> 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 16 +> 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 +> 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 18 +> 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 +> 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 +> 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 +> 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 +> 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 4 +> 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 +> 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 +> 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 +> 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 +> 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 +> rows: 21 + +DROP TABLE IF EXISTS TEST; +> ok + +--- joins ---------------------------------------------------------------------------------------------------- +create table t1(id int, name varchar); +> ok + +insert into t1 values(1, 'hi'), (2, 'world'); +> update count: 2 + +create table t2(id int, name varchar); +> ok + +insert into t2 values(1, 'Hallo'), (3, 'Welt'); +> update count: 2 + +select * from t1 join t2 on t1.id=t2.id; +> ID NAME ID NAME +> -- ---- -- ----- +> 1 hi 1 Hallo +> rows: 1 + +select * from t1 left join t2 on t1.id=t2.id; +> ID NAME ID NAME +> -- ----- ---- ----- +> 1 hi 1 Hallo +> 2 world null null +> rows: 2 + +select * from t1 right join t2 on t1.id=t2.id; +> ID NAME ID NAME +> ---- ---- -- ----- +> 1 hi 1 Hallo +> null null 3 Welt +> rows: 2 + +select * from t1 cross join t2; +> ID NAME ID NAME +> -- ----- -- ----- +> 1 hi 1 Hallo +> 1 hi 3 Welt +> 2 world 1 Hallo +> 2 world 3 Welt +> rows: 4 + +select * from t1 natural join t2; +> ID NAME +> -- ---- +> rows: 0 + +explain select * from t1 natural join t2; +>> SELECT "PUBLIC"."T1"."ID", "PUBLIC"."T1"."NAME" FROM "PUBLIC"."T1" /* PUBLIC.T1.tableScan */ INNER JOIN "PUBLIC"."T2" /* PUBLIC.T2.tableScan */ ON 1=1 WHERE ("PUBLIC"."T1"."ID" = "PUBLIC"."T2"."ID") AND ("PUBLIC"."T1"."NAME" = "PUBLIC"."T2"."NAME") + +drop table t1; +> ok + +drop table t2; +> ok + +create table customer(customerid int, customer_name varchar); +> ok + +insert into customer values(0, 'Acme'); +> update count: 1 + +create table invoice(customerid int, invoiceid int, invoice_text varchar); +> ok + +insert into invoice values(0, 1, 'Soap'), (0, 2, 'More Soap'); +> update count: 2 + +create table INVOICE_LINE(line_id int, invoiceid int, customerid int, line_text varchar); +> ok + +insert into INVOICE_LINE values(10, 1, 0, 'Super Soap'), (20, 1, 0, 'Regular Soap'); +> update count: 2 + +select * from customer c natural join invoice i natural join INVOICE_LINE l; +> CUSTOMERID CUSTOMER_NAME INVOICEID INVOICE_TEXT LINE_ID LINE_TEXT +> ---------- ------------- --------- ------------ ------- ------------ +> 0 Acme 1 Soap 10 Super Soap +> 0 Acme 1 Soap 20 Regular Soap +> rows: 2 + +explain select * from customer c natural join invoice i natural join INVOICE_LINE l; +>> SELECT "C"."CUSTOMERID", "C"."CUSTOMER_NAME", "I"."INVOICEID", "I"."INVOICE_TEXT", "L"."LINE_ID", "L"."LINE_TEXT" FROM "PUBLIC"."INVOICE" "I" /* PUBLIC.INVOICE.tableScan */ INNER JOIN "PUBLIC"."INVOICE_LINE" "L" /* PUBLIC.INVOICE_LINE.tableScan */ ON 1=1 /* WHERE (I.CUSTOMERID = L.CUSTOMERID) AND (I.INVOICEID = L.INVOICEID) */ INNER JOIN "PUBLIC"."CUSTOMER" "C" /* PUBLIC.CUSTOMER.tableScan */ ON 1=1 WHERE ("C"."CUSTOMERID" = "I"."CUSTOMERID") AND ("I"."CUSTOMERID" = "L"."CUSTOMERID") AND ("I"."INVOICEID" = "L"."INVOICEID") + +select c.*, i.*, l.* from customer c natural join invoice i natural join INVOICE_LINE l; +> CUSTOMERID CUSTOMER_NAME CUSTOMERID INVOICEID INVOICE_TEXT LINE_ID INVOICEID CUSTOMERID LINE_TEXT +> ---------- ------------- ---------- --------- ------------ ------- --------- ---------- ------------ +> 0 Acme 0 1 Soap 10 1 0 Super Soap +> 0 Acme 0 1 Soap 20 1 0 Regular Soap +> rows: 2 + +explain select c.*, i.*, l.* from customer c natural join invoice i natural join INVOICE_LINE l; +>> SELECT "C"."CUSTOMERID", "C"."CUSTOMER_NAME", "I"."CUSTOMERID", "I"."INVOICEID", "I"."INVOICE_TEXT", "L"."LINE_ID", "L"."INVOICEID", "L"."CUSTOMERID", "L"."LINE_TEXT" FROM "PUBLIC"."INVOICE" "I" /* PUBLIC.INVOICE.tableScan */ INNER JOIN "PUBLIC"."INVOICE_LINE" "L" /* PUBLIC.INVOICE_LINE.tableScan */ ON 1=1 /* WHERE (I.CUSTOMERID = L.CUSTOMERID) AND (I.INVOICEID = L.INVOICEID) */ INNER JOIN "PUBLIC"."CUSTOMER" "C" /* PUBLIC.CUSTOMER.tableScan */ ON 1=1 WHERE ("C"."CUSTOMERID" = "I"."CUSTOMERID") AND ("I"."CUSTOMERID" = "L"."CUSTOMERID") AND ("I"."INVOICEID" = "L"."INVOICEID") + +drop table customer; +> ok + +drop table invoice; +> ok + +drop table INVOICE_LINE; +> ok + +--- outer joins ---------------------------------------------------------------------------------------------- +CREATE TABLE PARENT(ID INT, NAME VARCHAR(20)); +> ok + +CREATE TABLE CHILD(ID INT, PARENTID INT, NAME VARCHAR(20)); +> ok + +INSERT INTO PARENT VALUES(1, 'Sue'); +> update count: 1 + +INSERT INTO PARENT VALUES(2, 'Joe'); +> update count: 1 + +INSERT INTO CHILD VALUES(100, 1, 'Simon'); +> update count: 1 + +INSERT INTO CHILD VALUES(101, 1, 'Sabine'); +> update count: 1 + +SELECT * FROM PARENT P INNER JOIN CHILD C ON P.ID = C.PARENTID; +> ID NAME ID PARENTID NAME +> -- ---- --- -------- ------ +> 1 Sue 100 1 Simon +> 1 Sue 101 1 Sabine +> rows: 2 + +SELECT * FROM PARENT P LEFT OUTER JOIN CHILD C ON P.ID = C.PARENTID; +> ID NAME ID PARENTID NAME +> -- ---- ---- -------- ------ +> 1 Sue 100 1 Simon +> 1 Sue 101 1 Sabine +> 2 Joe null null null +> rows: 3 + +SELECT * FROM CHILD C RIGHT OUTER JOIN PARENT P ON P.ID = C.PARENTID; +> ID PARENTID NAME ID NAME +> ---- -------- ------ -- ---- +> 100 1 Simon 1 Sue +> 101 1 Sabine 1 Sue +> null null null 2 Joe +> rows: 3 + +DROP TABLE PARENT; +> ok + +DROP TABLE CHILD; +> ok + +CREATE TABLE A(A1 INT, A2 INT); +> ok + +INSERT INTO A VALUES (1, 2); +> update count: 1 + +CREATE TABLE B(B1 INT, B2 INT); +> ok + +INSERT INTO B VALUES (1, 2); +> update count: 1 + +CREATE TABLE C(B1 INT, C1 INT); +> ok + +INSERT INTO C VALUES (1, 2); +> update count: 1 + +SELECT * FROM A LEFT JOIN B ON TRUE; +> A1 A2 B1 B2 +> -- -- -- -- +> 1 2 1 2 +> rows: 1 + +SELECT A.A1, A.A2, B.B1, B.B2 FROM A RIGHT JOIN B ON TRUE; +> A1 A2 B1 B2 +> -- -- -- -- +> 1 2 1 2 +> rows: 1 + +-- this syntax without ON or USING in not standard +SELECT * FROM A LEFT JOIN B; +> A1 A2 B1 B2 +> -- -- -- -- +> 1 2 1 2 +> rows: 1 + +-- this syntax without ON or USING in not standard +SELECT A.A1, A.A2, B.B1, B.B2 FROM A RIGHT JOIN B; +> A1 A2 B1 B2 +> -- -- -- -- +> 1 2 1 2 +> rows: 1 + +SELECT * FROM A LEFT JOIN B ON TRUE NATURAL JOIN C; +> A1 A2 B1 B2 C1 +> -- -- -- -- -- +> 1 2 1 2 2 +> rows: 1 + +SELECT A.A1, A.A2, B.B1, B.B2, C.C1 FROM A RIGHT JOIN B ON TRUE NATURAL JOIN C; +> A1 A2 B1 B2 C1 +> -- -- -- -- -- +> 1 2 1 2 2 +> rows: 1 + +-- this syntax without ON or USING in not standard +SELECT * FROM A LEFT JOIN B NATURAL JOIN C; +> A1 A2 B1 B2 C1 +> -- -- -- -- -- +> 1 2 1 2 2 +> rows: 1 + +-- this syntax without ON or USING in not standard +SELECT A.A1, A.A2, B.B1, B.B2, C.C1 FROM A RIGHT JOIN B NATURAL JOIN C; +> A1 A2 B1 B2 C1 +> -- -- -- -- -- +> 1 2 1 2 2 +> rows: 1 + +DROP TABLE A; +> ok + +DROP TABLE B; +> ok + +DROP TABLE C; +> ok + +CREATE TABLE T1(X1 INT); +> ok + +CREATE TABLE T2(X2 INT); +> ok + +CREATE TABLE T3(X3 INT); +> ok + +CREATE TABLE T4(X4 INT); +> ok + +CREATE TABLE T5(X5 INT); +> ok + +INSERT INTO T1 VALUES (1); +> update count: 1 + +INSERT INTO T1 VALUES (NULL); +> update count: 1 + +INSERT INTO T2 VALUES (1); +> update count: 1 + +INSERT INTO T2 VALUES (NULL); +> update count: 1 + +INSERT INTO T3 VALUES (1); +> update count: 1 + +INSERT INTO T3 VALUES (NULL); +> update count: 1 + +INSERT INTO T4 VALUES (1); +> update count: 1 + +INSERT INTO T4 VALUES (NULL); +> update count: 1 + +INSERT INTO T5 VALUES (1); +> update count: 1 + +INSERT INTO T5 VALUES (NULL); +> update count: 1 + +SELECT T1.X1, T2.X2, T3.X3, T4.X4, T5.X5 FROM ( + T1 INNER JOIN ( + T2 LEFT OUTER JOIN ( + T3 INNER JOIN T4 ON T3.X3 = T4.X4 + ) ON T2.X2 = T4.X4 + ) ON T1.X1 = T2.X2 +) INNER JOIN T5 ON T2.X2 = T5.X5; +> X1 X2 X3 X4 X5 +> -- -- -- -- -- +> 1 1 1 1 1 +> rows: 1 + +DROP TABLE T1, T2, T3, T4, T5; +> ok + +CREATE TABLE A(X INT); +> ok + +CREATE TABLE B(Y INT); +> ok + +CREATE TABLE C(Z INT); +> ok + +SELECT A.X FROM A JOIN B ON A.X = B.Y AND B.Y >= COALESCE((SELECT Z FROM C FETCH FIRST ROW ONLY), 0); +> X +> - +> rows: 0 + +DROP TABLE A, B, C; +> ok + +CREATE TABLE TEST(A INT PRIMARY KEY); +> ok + +SELECT * FROM TEST X LEFT OUTER JOIN TEST Y ON Y.A = X.A || '1'; +> A A +> - - +> rows: 0 + +DROP TABLE TEST; +> ok + +CREATE TABLE T1(A INT, B INT) AS VALUES (1, 10), (2, 20), (4, 40), (6, 6), (7, 7); +> ok + +CREATE TABLE T2(A INT, B INT) AS VALUES (1, 100), (2, 200), (5, 500), (6, 6), (8, 7); +> ok + +SELECT T1.B, T2.B FROM T1 INNER JOIN T2 USING (A); +> B B +> -- --- +> 10 100 +> 20 200 +> 6 6 +> rows: 3 + +SELECT * FROM T1 INNER JOIN T2 USING (A); +> A B B +> - -- --- +> 1 10 100 +> 2 20 200 +> 6 6 6 +> rows: 3 + +SELECT * FROM T1 INNER JOIN T2 USING (B); +> B A A +> - - - +> 6 6 6 +> 7 7 8 +> rows: 2 + +SELECT T1.B, T2.B FROM T1 INNER JOIN T2 USING (A, B); +> B B +> - - +> 6 6 +> rows: 1 + +SELECT * FROM T1 INNER JOIN T2 USING (B, A); +> B A +> - - +> 6 6 +> rows: 1 + +DROP TABLE T1, T2; +> ok + +SELECT * + FROM (VALUES(1, 'A'), (2, 'B')) T1(A, B) + JOIN (VALUES(2, 'C'), (3, 'D')) T2(A, C) USING (A); +> A B C +> - - - +> 2 B C +> rows: 1 + +SELECT * + FROM (VALUES(1, 'A'), (2, 'B')) T1(A, B) + LEFT JOIN (VALUES(2, 'C'), (3, 'D')) T2(A, C) USING (A); +> A B C +> - - ---- +> 1 A null +> 2 B C +> rows: 2 + +SELECT * + FROM (VALUES(1, 'A'), (2, 'B')) T1(A, B) + RIGHT JOIN (VALUES(2, 'C'), (3, 'D')) T2(A, C) USING (A); +> A B C +> - ---- - +> 2 B C +> 3 null D +> rows: 2 + +SELECT T1.*, T2.* + FROM (VALUES(1, 'A'), (2, 'B')) T1(A, B) + RIGHT JOIN (VALUES(2, 'C'), (3, 'D')) T2(A, C) USING (A); +> A B A C +> ---- ---- - - +> 2 B 2 C +> null null 3 D +> rows: 2 + +SELECT * + FROM (VALUES(1, 'A'), (2, 'B')) T1(A, B) + NATURAL JOIN (VALUES(2, 'C'), (3, 'D')) T2(A, C); +> A B C +> - - - +> 2 B C +> rows: 1 + +CREATE TABLE T1(A VARCHAR_IGNORECASE PRIMARY KEY, B VARCHAR) AS (VALUES ('a', 'A'), ('b', 'B')); +> ok + +CREATE TABLE T2(A VARCHAR_IGNORECASE PRIMARY KEY, C VARCHAR) AS (VALUES ('B', 'C'), ('C', 'D')); +> ok + +SELECT * FROM T1 RIGHT JOIN T2 USING (A); +> A B C +> - ---- - +> C null D +> b B C +> rows: 2 + +EXPLAIN SELECT * FROM T1 RIGHT JOIN T2 USING (A); +>> SELECT COALESCE("PUBLIC"."T1"."A", "PUBLIC"."T2"."A") AS "A", "PUBLIC"."T1"."B", "PUBLIC"."T2"."C" FROM "PUBLIC"."T2" /* PUBLIC.T2.tableScan */ LEFT OUTER JOIN "PUBLIC"."T1" /* PUBLIC.PRIMARY_KEY_A: A = PUBLIC.T2.A */ ON "PUBLIC"."T1"."A" = "PUBLIC"."T2"."A" + +DROP TABLE T1, T2; +> ok + +CREATE TABLE T1(A INT PRIMARY KEY, B VARCHAR) AS (VALUES (1, 'A'), (2, 'B')); +> ok + +CREATE TABLE T2(A INT PRIMARY KEY, C VARCHAR) AS (VALUES (2, 'C'), (3, 'D')); +> ok + +SELECT * FROM T1 RIGHT JOIN T2 USING (A); +> A B C +> - ---- - +> 2 B C +> 3 null D +> rows: 2 + +EXPLAIN SELECT * FROM T1 RIGHT JOIN T2 USING (A); +>> SELECT "PUBLIC"."T2"."A", "PUBLIC"."T1"."B", "PUBLIC"."T2"."C" FROM "PUBLIC"."T2" /* PUBLIC.T2.tableScan */ LEFT OUTER JOIN "PUBLIC"."T1" /* PUBLIC.PRIMARY_KEY_A: A = PUBLIC.T2.A */ ON "PUBLIC"."T1"."A" = "PUBLIC"."T2"."A" + +SELECT * EXCEPT (T1.A) FROM T1 RIGHT JOIN T2 USING (A); +> B C +> ---- - +> B C +> null D +> rows: 2 + +SELECT * EXCEPT (T2.A) FROM T1 RIGHT JOIN T2 USING (A); +> B C +> ---- - +> B C +> null D +> rows: 2 + +DROP TABLE T1, T2; +> ok + +CREATE SCHEMA S1; +> ok + +CREATE SCHEMA S2; +> ok + +CREATE TABLE S1.T(A VARCHAR_IGNORECASE, B INT) AS (VALUES ('a', 2)); +> ok + +CREATE TABLE S2.T(A VARCHAR_IGNORECASE, B INT) AS (VALUES ('A', 3)); +> ok + +SELECT * FROM S1.T RIGHT JOIN S2.T USING(A); +> A B B +> - - - +> a 2 3 +> rows: 1 + +EXPLAIN SELECT * FROM S1.T RIGHT JOIN S2.T USING(A); +>> SELECT COALESCE("S1"."T"."A", "S2"."T"."A") AS "A", "S1"."T"."B", "S2"."T"."B" FROM "S2"."T" /* S2.T.tableScan */ LEFT OUTER JOIN "S1"."T" /* S1.T.tableScan */ ON "S1"."T"."A" = "S2"."T"."A" + +DROP SCHEMA S1 CASCADE; +> ok + +DROP SCHEMA S2 CASCADE; +> ok + +CREATE TABLE T1(C1 INTEGER) AS VALUES 1, 2, 4; +> ok + +CREATE TABLE T2(C2 INTEGER) AS VALUES 1, 3, 4; +> ok + +CREATE TABLE T3(C3 INTEGER) AS VALUES 2, 3, 4; +> ok + +SELECT * FROM T1 JOIN T2 LEFT JOIN T3 ON T2.C2 = T3.C3 ON T1.C1 = T2.C2; +> C1 C2 C3 +> -- -- ---- +> 1 1 null +> 4 4 4 +> rows: 2 + +EXPLAIN SELECT * FROM T1 JOIN T2 LEFT JOIN T3 ON T2.C2 = T3.C3 ON T1.C1 = T2.C2; +>> SELECT "PUBLIC"."T1"."C1", "PUBLIC"."T2"."C2", "PUBLIC"."T3"."C3" FROM ( "PUBLIC"."T2" /* PUBLIC.T2.tableScan */ LEFT OUTER JOIN "PUBLIC"."T3" /* PUBLIC.T3.tableScan */ ON "T2"."C2" = "T3"."C3" ) INNER JOIN "PUBLIC"."T1" /* PUBLIC.T1.tableScan */ ON 1=1 WHERE "T1"."C1" = "T2"."C2" + +SELECT * FROM T1 RIGHT JOIN T2 LEFT JOIN T3 ON T2.C2 = T3.C3 ON T1.C1 = T2.C2; +> C1 C2 C3 +> ---- -- ---- +> 1 1 null +> 4 4 4 +> null 3 3 +> rows: 3 + +EXPLAIN SELECT * FROM T1 RIGHT JOIN T2 LEFT JOIN T3 ON T2.C2 = T3.C3 ON T1.C1 = T2.C2; +>> SELECT "PUBLIC"."T1"."C1", "PUBLIC"."T2"."C2", "PUBLIC"."T3"."C3" FROM "PUBLIC"."T2" /* PUBLIC.T2.tableScan */ LEFT OUTER JOIN "PUBLIC"."T3" /* PUBLIC.T3.tableScan */ ON "T2"."C2" = "T3"."C3" LEFT OUTER JOIN "PUBLIC"."T1" /* PUBLIC.T1.tableScan */ ON "T1"."C1" = "T2"."C2" + +DROP TABLE T1, T2, T3; +> ok + +SELECT X.A, Y.B, Z.C +FROM (SELECT 1 A) X JOIN ( + (SELECT 1 B) Y JOIN (SELECT 1 C) Z ON Z.C = Y.B +) ON Y.B = X.A; +> A B C +> - - - +> 1 1 1 +> rows: 1 diff --git a/h2/src/test/org/h2/test/scripts/queries/query-optimisations.sql b/h2/src/test/org/h2/test/scripts/queries/query-optimisations.sql new file mode 100644 index 0000000..16f09f0 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/queries/query-optimisations.sql @@ -0,0 +1,210 @@ +-- 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 +-- + +create table person(firstname varchar, lastname varchar); +> ok + +create index person_1 on person(firstname, lastname); +> ok + +insert into person select convert(x,varchar) as firstname, (convert(x,varchar) || ' last') as lastname from system_range(1,100); +> update count: 100 + +-- Issue #643: verify that when using an index, we use the IN part of the query, if that part of the query +-- can directly use the index. +-- +explain analyze SELECT * FROM person WHERE firstname IN ('FirstName1', 'FirstName2') AND lastname='LastName1'; +>> SELECT "PUBLIC"."PERSON"."FIRSTNAME", "PUBLIC"."PERSON"."LASTNAME" FROM "PUBLIC"."PERSON" /* PUBLIC.PERSON_1: FIRSTNAME IN('FirstName1', 'FirstName2') AND LASTNAME = 'LastName1' */ /* scanCount: 1 */ WHERE ("FIRSTNAME" IN('FirstName1', 'FirstName2')) AND ("LASTNAME" = 'LastName1') + +CREATE TABLE TEST(A SMALLINT PRIMARY KEY, B SMALLINT); +> ok + +CREATE INDEX TEST_IDX_1 ON TEST(B); +> ok + +CREATE INDEX TEST_IDX_2 ON TEST(B, A); +> ok + +INSERT INTO TEST VALUES (1, 2), (3, 4); +> update count: 2 + +EXPLAIN SELECT _ROWID_ FROM TEST WHERE B = 4; +>> SELECT _ROWID_ FROM "PUBLIC"."TEST" /* PUBLIC.TEST_IDX_1: B = 4 */ WHERE "B" = 4 + +EXPLAIN SELECT _ROWID_, A FROM TEST WHERE B = 4; +>> SELECT _ROWID_, "A" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_IDX_1: B = 4 */ WHERE "B" = 4 + +EXPLAIN SELECT A FROM TEST WHERE B = 4; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_IDX_1: B = 4 */ WHERE "B" = 4 + +SELECT _ROWID_, A FROM TEST WHERE B = 4; +> _ROWID_ A +> ------- - +> 3 3 +> rows: 1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A TINYINT PRIMARY KEY, B TINYINT); +> ok + +CREATE INDEX TEST_IDX_1 ON TEST(B); +> ok + +CREATE INDEX TEST_IDX_2 ON TEST(B, A); +> ok + +INSERT INTO TEST VALUES (1, 2), (3, 4); +> update count: 2 + +EXPLAIN SELECT _ROWID_ FROM TEST WHERE B = 4; +>> SELECT _ROWID_ FROM "PUBLIC"."TEST" /* PUBLIC.TEST_IDX_1: B = 4 */ WHERE "B" = 4 + +EXPLAIN SELECT _ROWID_, A FROM TEST WHERE B = 4; +>> SELECT _ROWID_, "A" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_IDX_1: B = 4 */ WHERE "B" = 4 + +EXPLAIN SELECT A FROM TEST WHERE B = 4; +>> SELECT "A" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_IDX_1: B = 4 */ WHERE "B" = 4 + +SELECT _ROWID_, A FROM TEST WHERE B = 4; +> _ROWID_ A +> ------- - +> 3 3 +> rows: 1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(V VARCHAR(2)) AS VALUES -1, -2; +> ok + +CREATE INDEX TEST_INDEX ON TEST(V); +> ok + +SELECT * FROM TEST WHERE V >= -1; +>> -1 + +-- H2 may use the index for a table scan, but may not create index conditions due to incompatible type +EXPLAIN SELECT * FROM TEST WHERE V >= -1; +>> SELECT "PUBLIC"."TEST"."V" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_INDEX */ WHERE "V" >= -1 + +EXPLAIN SELECT * FROM TEST WHERE V IN (-1, -3); +>> SELECT "PUBLIC"."TEST"."V" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_INDEX */ WHERE "V" IN(-1, -3) + +SELECT * FROM TEST WHERE V < -1; +>> -2 + +DROP TABLE TEST; +> ok + +CREATE TABLE T(ID INT, V INT) AS VALUES (1, 1), (1, 2), (2, 1), (2, 2); +> ok + +SELECT T1.ID, T2.V AS LV FROM (SELECT ID, MAX(V) AS LV FROM T GROUP BY ID) AS T1 + INNER JOIN T AS T2 ON T2.ID = T1.ID AND T2.V = T1.LV + WHERE T1.ID IN (1, 2) ORDER BY ID; +> ID LV +> -- -- +> 1 2 +> 2 2 +> rows (ordered): 2 + +EXPLAIN SELECT T1.ID, T2.V AS LV FROM (SELECT ID, MAX(V) AS LV FROM T GROUP BY ID) AS T1 + INNER JOIN T AS T2 ON T2.ID = T1.ID AND T2.V = T1.LV + WHERE T1.ID IN (1, 2) ORDER BY ID; +>> SELECT "T1"."ID", "T2"."V" AS "LV" FROM "PUBLIC"."T" "T2" /* PUBLIC.T.tableScan */ INNER JOIN ( SELECT "ID", MAX("V") AS "LV" FROM "PUBLIC"."T" GROUP BY "ID" ) "T1" /* SELECT ID, MAX(V) AS LV FROM PUBLIC.T /* PUBLIC.T.tableScan */ WHERE ID IS NOT DISTINCT FROM ?1 GROUP BY ID HAVING MAX(V) IS NOT DISTINCT FROM ?2: ID = T2.ID AND LV = T2.V */ ON 1=1 WHERE ("T1"."ID" IN(1, 2)) AND ("T2"."ID" = "T1"."ID") AND ("T2"."V" = "T1"."LV") ORDER BY 1 + +DROP TABLE T; +> ok + +SELECT (SELECT ROWNUM) R FROM VALUES 1, 2, 3; +> R +> - +> 1 +> 1 +> 1 +> rows: 3 + +CREATE TABLE TEST(A INT, B INT, C INT) AS VALUES (1, 1, 1); +> ok + +SELECT T1.A FROM TEST T1 LEFT OUTER JOIN TEST T2 ON T1.B = T2.A WHERE (SELECT T2.C) IS NOT NULL ORDER BY T1.A; +>> 1 + +EXPLAIN SELECT T1.A FROM TEST T1 LEFT OUTER JOIN TEST T2 ON T1.B = T2.A WHERE (SELECT T2.C) IS NOT NULL ORDER BY T1.A; +>> SELECT "T1"."A" FROM "PUBLIC"."TEST" "T1" /* PUBLIC.TEST.tableScan */ LEFT OUTER JOIN "PUBLIC"."TEST" "T2" /* PUBLIC.TEST.tableScan */ ON "T1"."B" = "T2"."A" WHERE "T2"."C" IS NOT NULL ORDER BY 1 + +SELECT X, (SELECT X IN (SELECT B FROM TEST)) FROM SYSTEM_RANGE(1, 2); +> X X IN( SELECT DISTINCT B FROM PUBLIC.TEST) +> - ----------------------------------------- +> 1 TRUE +> 2 FALSE +> rows: 2 + +SELECT T1.A FROM TEST T1 LEFT OUTER JOIN TEST T2 ON T1.B = T2.A WHERE (SELECT T2.C + ROWNUM) IS NOT NULL ORDER BY T1.A; +>> 1 + +EXPLAIN SELECT T1.A FROM TEST T1 LEFT OUTER JOIN TEST T2 ON T1.B = T2.A WHERE (SELECT T2.C + ROWNUM) IS NOT NULL ORDER BY T1.A; +>> SELECT "T1"."A" FROM "PUBLIC"."TEST" "T1" /* PUBLIC.TEST.tableScan */ LEFT OUTER JOIN "PUBLIC"."TEST" "T2" /* PUBLIC.TEST.tableScan */ ON "T1"."B" = "T2"."A" WHERE ("T2"."C" + CAST(1 AS BIGINT)) IS NOT NULL ORDER BY 1 + +DROP TABLE TEST; +> ok + +CREATE TABLE A(T TIMESTAMP WITH TIME ZONE UNIQUE) AS VALUES + TIMESTAMP WITH TIME ZONE '2020-01-01 00:01:02+02', + TIMESTAMP WITH TIME ZONE '2020-01-01 00:01:02+01'; +> ok + +CREATE TABLE B(D DATE) AS VALUES DATE '2020-01-01'; +> ok + +SET TIME ZONE '01:00'; +> ok + +SELECT T FROM A JOIN B ON T >= D; +>> 2020-01-01 00:01:02+01 + +EXPLAIN SELECT T FROM A JOIN B ON T >= D; +>> SELECT "T" FROM "PUBLIC"."B" /* PUBLIC.B.tableScan */ INNER JOIN "PUBLIC"."A" /* PUBLIC.CONSTRAINT_INDEX_4: T >= D */ ON 1=1 WHERE "T" >= "D" + +SET TIME ZONE LOCAL; +> ok + +DROP TABLE A, B; +> ok + +CREATE TABLE TEST(T TIMESTAMP WITH TIME ZONE) AS VALUES + NULL, + TIMESTAMP WITH TIME ZONE '2020-01-01 00:00:00+00', + TIMESTAMP WITH TIME ZONE '2020-01-01 01:00:00+01', + TIMESTAMP WITH TIME ZONE '2020-01-01 02:00:00+01', + NULL; +> ok + +SELECT T AT TIME ZONE 'UTC' FROM TEST GROUP BY T; +> T AT TIME ZONE 'UTC' +> ---------------------- +> 2020-01-01 00:00:00+00 +> 2020-01-01 01:00:00+00 +> null +> rows: 3 + +CREATE INDEX TEST_T_IDX ON TEST(T); +> ok + +SELECT T AT TIME ZONE 'UTC' FROM TEST GROUP BY T; +> T AT TIME ZONE 'UTC' +> ---------------------- +> 2020-01-01 00:00:00+00 +> 2020-01-01 01:00:00+00 +> null +> rows: 3 + +EXPLAIN SELECT T AT TIME ZONE 'UTC' FROM TEST GROUP BY T; +>> SELECT "T" AT TIME ZONE 'UTC' FROM "PUBLIC"."TEST" /* PUBLIC.TEST_T_IDX */ GROUP BY "T" /* group sorted */ + +DROP TABLE TEST; +> 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 new file mode 100644 index 0000000..92689a6 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/queries/select.sql @@ -0,0 +1,1213 @@ +-- 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 +-- + +CREATE TABLE TEST(A INT, B INT, C INT); +> ok + +INSERT INTO TEST VALUES (1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 2, 1), (1, 2, 2), (1, 2, 3), + (2, 1, 1), (2, 1, 2), (2, 1, 3), (2, 2, 1), (2, 2, 2), (2, 2, 3); +> update count: 12 + +SELECT * FROM TEST ORDER BY A, B; +> A B C +> - - - +> 1 1 1 +> 1 1 2 +> 1 1 3 +> 1 2 1 +> 1 2 2 +> 1 2 3 +> 2 1 1 +> 2 1 2 +> 2 1 3 +> 2 2 1 +> 2 2 2 +> 2 2 3 +> rows (partially ordered): 12 + +SELECT * FROM TEST ORDER BY A, B, C FETCH FIRST 4 ROWS ONLY; +> A B C +> - - - +> 1 1 1 +> 1 1 2 +> 1 1 3 +> 1 2 1 +> rows (ordered): 4 + +SELECT * FROM TEST ORDER BY A, B, C FETCH FIRST 4 ROWS WITH TIES; +> A B C +> - - - +> 1 1 1 +> 1 1 2 +> 1 1 3 +> 1 2 1 +> rows (ordered): 4 + +SELECT * FROM TEST ORDER BY A, B FETCH FIRST 4 ROWS WITH TIES; +> A B C +> - - - +> 1 1 1 +> 1 1 2 +> 1 1 3 +> 1 2 1 +> 1 2 2 +> 1 2 3 +> rows (partially ordered): 6 + +SELECT * FROM TEST ORDER BY A FETCH FIRST ROW WITH TIES; +> A B C +> - - - +> 1 1 1 +> 1 1 2 +> 1 1 3 +> 1 2 1 +> 1 2 2 +> 1 2 3 +> rows (partially ordered): 6 + +SELECT TOP (1) WITH TIES * FROM TEST ORDER BY A; +> A B C +> - - - +> 1 1 1 +> 1 1 2 +> 1 1 3 +> 1 2 1 +> 1 2 2 +> 1 2 3 +> rows (partially ordered): 6 + +SELECT TOP 1 PERCENT WITH TIES * FROM TEST ORDER BY A; +> A B C +> - - - +> 1 1 1 +> 1 1 2 +> 1 1 3 +> 1 2 1 +> 1 2 2 +> 1 2 3 +> rows (partially ordered): 6 + +SELECT TOP 51 PERCENT WITH TIES * FROM TEST ORDER BY A, B; +> A B C +> - - - +> 1 1 1 +> 1 1 2 +> 1 1 3 +> 1 2 1 +> 1 2 2 +> 1 2 3 +> 2 1 1 +> 2 1 2 +> 2 1 3 +> rows (partially ordered): 9 + +SELECT * FROM TEST ORDER BY A, B OFFSET 3 ROWS FETCH NEXT 1 ROW WITH TIES; +> A B C +> - - - +> 1 2 1 +> 1 2 2 +> 1 2 3 +> rows (partially ordered): 3 + +SELECT * FROM TEST FETCH NEXT ROWS ONLY; +> A B C +> - - - +> 1 1 1 +> rows: 1 + +SELECT * FROM TEST FETCH FIRST 101 PERCENT ROWS ONLY; +> exception INVALID_VALUE_2 + +SELECT * FROM TEST FETCH FIRST -1 PERCENT ROWS ONLY; +> exception INVALID_VALUE_2 + +SELECT * FROM TEST FETCH FIRST 0 PERCENT ROWS ONLY; +> A B C +> - - - +> rows: 0 + +SELECT * FROM TEST FETCH FIRST 1 PERCENT ROWS ONLY; +> A B C +> - - - +> 1 1 1 +> rows: 1 + +SELECT * FROM TEST FETCH FIRST 10 PERCENT ROWS ONLY; +> A B C +> - - - +> 1 1 1 +> 1 1 2 +> rows: 2 + +SELECT * FROM TEST OFFSET 2 ROWS FETCH NEXT 10 PERCENT ROWS ONLY; +> A B C +> - - - +> 1 1 3 +> 1 2 1 +> rows: 2 + +CREATE INDEX TEST_A_IDX ON TEST(A); +> ok + +CREATE INDEX TEST_A_B_IDX ON TEST(A, B); +> ok + +SELECT * FROM TEST ORDER BY A FETCH FIRST 1 ROW WITH TIES; +> A B C +> - - - +> 1 1 1 +> 1 1 2 +> 1 1 3 +> 1 2 1 +> 1 2 2 +> 1 2 3 +> rows (partially ordered): 6 + +SELECT * FROM TEST ORDER BY A, B OFFSET 3 ROWS FETCH NEXT 1 ROW WITH TIES; +> A B C +> - - - +> 1 2 1 +> 1 2 2 +> 1 2 3 +> rows (partially ordered): 3 + +SELECT * FROM TEST FETCH FIRST 1 ROW WITH TIES; +> exception WITH_TIES_WITHOUT_ORDER_BY + +(SELECT * FROM TEST) UNION (SELECT 1, 2, 4) ORDER BY A, B OFFSET 3 ROWS FETCH NEXT 1 ROW WITH TIES; +> A B C +> - - - +> 1 2 1 +> 1 2 2 +> 1 2 3 +> 1 2 4 +> rows (partially ordered): 4 + +(SELECT * FROM TEST) UNION (SELECT 1, 2, 4) ORDER BY A, B OFFSET 3 ROWS FETCH NEXT 50 PERCENT ROWS ONLY; +> A B C +> - - - +> 1 2 1 +> 1 2 2 +> 1 2 3 +> 1 2 4 +> 2 1 1 +> 2 1 2 +> 2 1 3 +> rows (partially ordered): 7 + +(SELECT * FROM TEST) UNION (SELECT 1, 2, 4) ORDER BY A, B OFFSET 3 ROWS FETCH NEXT 40 PERCENT ROWS WITH TIES; +> A B C +> - - - +> 1 2 1 +> 1 2 2 +> 1 2 3 +> 1 2 4 +> 2 1 1 +> 2 1 2 +> 2 1 3 +> rows (partially ordered): 7 + +(SELECT * FROM TEST) UNION (SELECT 1, 2, 4) FETCH NEXT 1 ROW WITH TIES; +> exception WITH_TIES_WITHOUT_ORDER_BY + +EXPLAIN SELECT * FROM TEST ORDER BY A, B OFFSET 3 ROWS FETCH NEXT 1 ROW WITH TIES; +>> SELECT "PUBLIC"."TEST"."A", "PUBLIC"."TEST"."B", "PUBLIC"."TEST"."C" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_A_B_IDX */ ORDER BY 1, 2 OFFSET 3 ROWS FETCH NEXT ROW WITH TIES /* index sorted */ + +EXPLAIN SELECT * FROM TEST ORDER BY A, B OFFSET 3 ROWS FETCH NEXT 1 PERCENT ROWS WITH TIES; +>> SELECT "PUBLIC"."TEST"."A", "PUBLIC"."TEST"."B", "PUBLIC"."TEST"."C" FROM "PUBLIC"."TEST" /* PUBLIC.TEST_A_B_IDX */ ORDER BY 1, 2 OFFSET 3 ROWS FETCH NEXT 1 PERCENT ROWS WITH TIES /* index sorted */ + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A VARCHAR_IGNORECASE, B VARCHAR_IGNORECASE); +> ok + +INSERT INTO TEST VALUES ('A', 1), ('a', 2), ('A', 3), ('B', 4); +> update count: 4 + +SELECT A, B FROM TEST ORDER BY A FETCH FIRST 1 ROW WITH TIES; +> A B +> - - +> A 1 +> A 3 +> a 2 +> rows (partially ordered): 3 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INT, B INT); +> ok + +INSERT INTO TEST VALUES (1, 1), (1, 2), (2, 1), (2, 2), (2, 3); +> update count: 5 + +SELECT A, COUNT(B) FROM TEST GROUP BY A ORDER BY A OFFSET 1; +> A COUNT(B) +> - -------- +> 2 3 +> rows (ordered): 1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, "VALUE" VARCHAR) AS VALUES (1, 'A'), (2, 'B'), (3, 'C'); +> ok + +SELECT * FROM TEST ORDER BY ID DESC OFFSET 2 ROWS FETCH FIRST 2147483646 ROWS ONLY; +> ID VALUE +> -- ----- +> 1 A +> rows (ordered): 1 + +SELECT * FROM TEST ORDER BY ID DESC OFFSET 2 ROWS FETCH FIRST 2147483647 ROWS ONLY; +> ID VALUE +> -- ----- +> 1 A +> rows (ordered): 1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST1(A INT, B INT, C INT) AS SELECT 1, 2, 3; +> ok + +CREATE TABLE TEST2(A INT, B INT, C INT) AS SELECT 4, 5, 6; +> ok + +SELECT A, B FROM TEST1 UNION SELECT A, B FROM TEST2 ORDER BY TEST1.C; +> exception ORDER_BY_NOT_IN_RESULT + +DROP TABLE TEST1; +> ok + +DROP TABLE TEST2; +> ok + +-- Disallowed mixed OFFSET/FETCH/LIMIT/TOP clauses +CREATE TABLE TEST (ID BIGINT); +> ok + +SELECT TOP 1 ID FROM TEST OFFSET 1 ROW; +> exception SYNTAX_ERROR_1 + +SELECT TOP 1 ID FROM TEST FETCH NEXT ROW ONLY; +> exception SYNTAX_ERROR_1 + +SELECT TOP 1 ID FROM TEST LIMIT 1; +> exception SYNTAX_ERROR_1 + +SELECT ID FROM TEST OFFSET 1 ROW LIMIT 1; +> exception SYNTAX_ERROR_1 + +SELECT ID FROM TEST FETCH NEXT ROW ONLY LIMIT 1; +> exception SYNTAX_ERROR_1 + +DROP TABLE TEST; +> ok + +-- ORDER BY with parameter +CREATE TABLE TEST(A INT, B INT); +> ok + +INSERT INTO TEST VALUES (1, 1), (1, 2), (2, 1), (2, 2); +> update count: 4 + +SELECT * FROM TEST ORDER BY ?, ? FETCH FIRST ROW ONLY; +{ +1, 2 +> A B +> - - +> 1 1 +> rows (ordered): 1 +-1, 2 +> A B +> - - +> 2 1 +> rows (ordered): 1 +1, -2 +> A B +> - - +> 1 2 +> rows (ordered): 1 +-1, -2 +> A B +> - - +> 2 2 +> rows (ordered): 1 +2, -1 +> A B +> - - +> 2 1 +> rows (ordered): 1 +} +> update count: 0 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST1(A INT, B INT, C INT) AS SELECT 1, 2, 3; +> ok + +CREATE TABLE TEST2(A INT, D INT) AS SELECT 4, 5; +> ok + +SELECT * FROM TEST1, TEST2; +> A B C A D +> - - - - - +> 1 2 3 4 5 +> rows: 1 + +SELECT * EXCEPT (A) FROM TEST1; +> B C +> - - +> 2 3 +> rows: 1 + +SELECT * EXCEPT (TEST1.A) FROM TEST1; +> B C +> - - +> 2 3 +> rows: 1 + +SELECT * EXCEPT (PUBLIC.TEST1.A) FROM TEST1; +> B C +> - - +> 2 3 +> rows: 1 + +SELECT * EXCEPT (SCRIPT.PUBLIC.TEST1.A) FROM TEST1; +> B C +> - - +> 2 3 +> rows: 1 + +SELECT * EXCEPT (Z) FROM TEST1; +> exception COLUMN_NOT_FOUND_1 + +SELECT * EXCEPT (B, TEST1.B) FROM TEST1; +> exception DUPLICATE_COLUMN_NAME_1 + +SELECT * EXCEPT (A) FROM TEST1, TEST2; +> exception AMBIGUOUS_COLUMN_NAME_1 + +SELECT * EXCEPT (TEST1.A, B, TEST2.D) FROM TEST1, TEST2; +> C A +> - - +> 3 4 +> rows: 1 + +SELECT TEST1.*, TEST2.* FROM TEST1, TEST2; +> A B C A D +> - - - - - +> 1 2 3 4 5 +> rows: 1 + +SELECT TEST1.* EXCEPT (A), TEST2.* EXCEPT (A) FROM TEST1, TEST2; +> B C D +> - - - +> 2 3 5 +> rows: 1 + +SELECT TEST1.* EXCEPT (A), TEST2.* EXCEPT (D) FROM TEST1, TEST2; +> B C A +> - - - +> 2 3 4 +> rows: 1 + +SELECT * EXCEPT (T1.A, T2.D) FROM TEST1 T1, TEST2 T2; +> B C A +> - - - +> 2 3 4 +> rows: 1 + +DROP TABLE TEST1, TEST2; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, "VALUE" INT NOT NULL); +> ok + +INSERT INTO TEST VALUES (1, 1), (2, 1), (3, 2); +> update count: 3 + +SELECT ID, "VALUE" FROM TEST FOR UPDATE; +> ID VALUE +> -- ----- +> 1 1 +> 2 1 +> 3 2 +> rows: 3 + +-- Check that NULL row is returned from SELECT FOR UPDATE +CREATE TABLE T1(A INT PRIMARY KEY) AS VALUES 1, 2; +> ok + +CREATE TABLE T2(B INT PRIMARY KEY) AS VALUES 1; +> ok + +SELECT * FROM T1 LEFT JOIN T2 ON A = B FOR UPDATE; +> A B +> - ---- +> 1 1 +> 2 null +> rows: 2 + +DROP TABLE T1, T2; +> ok + +SELECT DISTINCT "VALUE" FROM TEST FOR UPDATE; +> exception FOR_UPDATE_IS_NOT_ALLOWED_IN_DISTINCT_OR_GROUPED_SELECT + +SELECT DISTINCT ON("VALUE") ID, "VALUE" FROM TEST FOR UPDATE; +> exception FOR_UPDATE_IS_NOT_ALLOWED_IN_DISTINCT_OR_GROUPED_SELECT + +SELECT SUM("VALUE") FROM TEST FOR UPDATE; +> exception FOR_UPDATE_IS_NOT_ALLOWED_IN_DISTINCT_OR_GROUPED_SELECT + +SELECT ID FROM TEST GROUP BY "VALUE" FOR UPDATE; +> exception FOR_UPDATE_IS_NOT_ALLOWED_IN_DISTINCT_OR_GROUPED_SELECT + +SELECT 1 FROM TEST HAVING TRUE FOR UPDATE; +> exception FOR_UPDATE_IS_NOT_ALLOWED_IN_DISTINCT_OR_GROUPED_SELECT + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, V INT) AS SELECT X, X + 1 FROM SYSTEM_RANGE(1, 3); +> ok + +SELECT ID FROM TEST WHERE ID != ALL (SELECT ID FROM TEST WHERE ID IN(1, 3)); +> ID +> -- +> 2 +> rows: 1 + +SELECT (1, 3) > ANY (SELECT ID, V FROM TEST); +>> TRUE + +SELECT (1, 2) > ANY (SELECT ID, V FROM TEST); +>> FALSE + +SELECT (2, 3) = ANY (SELECT ID, V FROM TEST); +>> TRUE + +SELECT (3, 4) > ALL (SELECT ID, V FROM TEST); +>> FALSE + +DROP TABLE TEST; +> ok + +SELECT 1 = ALL (SELECT * FROM VALUES (NULL), (1), (2), (NULL) ORDER BY 1); +>> FALSE + +CREATE TABLE TEST(G INT, V INT); +> ok + +INSERT INTO TEST VALUES (10, 1), (11, 2), (20, 4); +> update count: 3 + +SELECT G / 10 G1, G / 10 G2, SUM(T.V) S FROM TEST T GROUP BY G / 10, G / 10; +> G1 G2 S +> -- -- - +> 1 1 3 +> 2 2 4 +> rows: 2 + +SELECT G / 10 G1, G / 10 G2, SUM(T.V) S FROM TEST T GROUP BY G2; +> G1 G2 S +> -- -- - +> 1 1 3 +> 2 2 4 +> rows: 2 + +DROP TABLE TEST; +> ok + +@reconnect off + +CALL RAND(0); +>> 0.730967787376657 + +SELECT RAND(), RAND() + 1, RAND() + 1, RAND() GROUP BY RAND() + 1; +> RAND() RAND() + 1 RAND() + 1 RAND() +> ------------------ ------------------ ------------------ ------------------ +> 0.6374174253501083 1.2405364156714858 1.2405364156714858 0.5504370051176339 +> rows: 1 + +SELECT RAND() A, RAND() + 1 B, RAND() + 1 C, RAND() D, RAND() + 2 E, RAND() + 3 F GROUP BY B, C, E, F; +> A B C D E F +> ------------------ ------------------ ------------------ ------------------ ------------------ ------------------ +> 0.8791825178724801 1.3332183994766498 1.3332183994766498 0.9412491794821144 2.3851891847407183 3.9848415401998087 +> rows: 1 + +@reconnect on + +CREATE TABLE TEST (A INT, B INT, C INT); +> ok + +INSERT INTO TEST VALUES (11, 12, 13), (21, 22, 23), (31, 32, 33); +> update count: 3 + +SELECT * FROM TEST WHERE (A, B) IN (VALUES (11, 12), (21, 22), (41, 42)); +> A B C +> -- -- -- +> 11 12 13 +> 21 22 23 +> rows: 2 + +SELECT * FROM TEST WHERE (A, B) = (VALUES (11, 12)); +> A B C +> -- -- -- +> 11 12 13 +> rows: 1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A BIGINT, B INT) AS VALUES (1::BIGINT, 2); +> ok + +SELECT * FROM TEST WHERE (A, B) IN ((1, 2), (3, 4)); +> A B +> - - +> 1 2 +> rows: 1 + +UPDATE TEST SET A = 1000000000000; +> update count: 1 + +SELECT * FROM TEST WHERE (A, B) IN ((1, 2), (3, 4)); +> A B +> - - +> rows: 0 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A BIGINT, B INT) AS VALUES (1, 2); +> ok + +SELECT * FROM TEST WHERE (A, B) IN ((1::BIGINT, 2), (3, 4)); +> A B +> - - +> 1 2 +> rows: 1 + +SELECT * FROM TEST WHERE (A, B) IN ((1000000000000, 2), (3, 4)); +> A B +> - - +> rows: 0 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(I) AS VALUES 1, 2, 3; +> ok + +SELECT COUNT(*) C FROM TEST HAVING C < 1; +> C +> - +> rows: 0 + +SELECT COUNT(*) C FROM TEST QUALIFY C < 1; +> C +> - +> rows: 0 + +DROP TABLE TEST; +> ok + +SELECT A, ROW_NUMBER() OVER (ORDER BY B) R +FROM (VALUES (1, 2), (2, 1), (3, 3)) T(A, B); +> A R +> - - +> 1 2 +> 2 1 +> 3 3 +> rows: 3 + +SELECT X, A, ROW_NUMBER() OVER (ORDER BY B) R +FROM (SELECT 1 X), (VALUES (1, 2), (2, 1), (3, 3)) T(A, B); +> X A R +> - - - +> 1 1 2 +> 1 2 1 +> 1 3 3 +> rows: 3 + +SELECT A, SUM(S) OVER (ORDER BY S) FROM + (SELECT A, SUM(B) FROM (VALUES (1, 2), (1, 3), (3, 5), (3, 10)) V(A, B) GROUP BY A) S(A, S); +> A SUM(S) OVER (ORDER BY S) +> - ------------------------ +> 1 5 +> 3 20 +> rows: 2 + +SELECT A, SUM(A) OVER W SUM FROM (VALUES 1, 2) T(A) WINDOW W AS (ORDER BY A); +> A SUM +> - --- +> 1 1 +> 2 3 +> rows: 2 + +SELECT A, B, C FROM (SELECT A, B, C FROM (VALUES (1, 2, 3)) V(A, B, C)); +> A B C +> - - - +> 1 2 3 +> rows: 1 + +SELECT * FROM (SELECT * FROM (VALUES (1, 2, 3)) V(A, B, C)); +> A B C +> - - - +> 1 2 3 +> rows: 1 + +SELECT * FROM + (SELECT X * X, Y FROM + (SELECT A + 5, B FROM + (VALUES (1, 2)) V(A, B) + ) T(X, Y) + ); +> X * X Y +> ----- - +> 36 2 +> rows: 1 + +CREATE TABLE TEST("_ROWID_" INT) AS VALUES 2; +> ok + +SELECT _ROWID_ S1, TEST._ROWID_ S2, PUBLIC.TEST._ROWID_ S3, SCRIPT.PUBLIC.TEST._ROWID_ S4, + "_ROWID_" U1, TEST."_ROWID_" U2, PUBLIC.TEST."_ROWID_" U3, SCRIPT.PUBLIC.TEST."_ROWID_" U4 + FROM TEST; +> S1 S2 S3 S4 U1 U2 U3 U4 +> -- -- -- -- -- -- -- -- +> 1 1 1 1 2 2 2 2 +> rows: 1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID BIGINT PRIMARY KEY); +> ok + +SELECT X.ID FROM TEST X JOIN TEST Y ON Y.ID IN (SELECT 1); +> ID +> -- +> rows: 0 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INT, B INT) AS VALUES (1, 10), (2, 20), (4, 40); +> ok + +SELECT T1.A, T2.ARR FROM TEST T1 JOIN ( + SELECT A, ARRAY_AGG(B) OVER (ORDER BY B ROWS BETWEEN 1 FOLLOWING AND 2 FOLLOWING) ARR FROM TEST +) T2 ON T1.A = T2.A; +> A ARR +> - -------- +> 1 [20, 40] +> 2 [40] +> 4 null +> rows: 3 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, V INT UNIQUE); +> ok + +EXPLAIN SELECT * FROM TEST ORDER BY ID FOR UPDATE; +>> SELECT "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."V" FROM "PUBLIC"."TEST" /* PUBLIC.PRIMARY_KEY_2 */ ORDER BY 1 FOR UPDATE /* index sorted */ + +EXPLAIN SELECT * FROM TEST ORDER BY V; +>> SELECT "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."V" FROM "PUBLIC"."TEST" /* PUBLIC.CONSTRAINT_INDEX_2 */ ORDER BY 2 /* index sorted */ + +EXPLAIN SELECT * FROM TEST ORDER BY V FOR UPDATE; +>> SELECT "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."V" FROM "PUBLIC"."TEST" /* PUBLIC.CONSTRAINT_INDEX_2 */ ORDER BY 2 FOR UPDATE + +DROP TABLE TEST; +> ok + +-- The next tests should be at the of this file + +SET MAX_MEMORY_ROWS = 1; +> ok + +CREATE TABLE TEST(I INT) AS SELECT * FROM SYSTEM_RANGE(1, 10); +> ok + +SELECT COUNT(*) FROM (SELECT I, SUM(I) S, COUNT(I) C FROM TEST GROUP BY I HAVING S + C <= 9 ORDER BY I); +>> 8 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INT, B INT); +> ok + +EXPLAIN SELECT * FROM TEST WHERE A = 1 AND B = 1 OR A = 2 AND B = 2; +>> SELECT "PUBLIC"."TEST"."A", "PUBLIC"."TEST"."B" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ WHERE (("A" = 1) AND ("B" = 1)) OR (("A" = 2) AND ("B" = 2)) + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INT, B INT) AS VALUES (1, 2), (1, 3), (5, 5); +> ok + +SELECT (SELECT A, B FROM TEST ORDER BY A + B FETCH FIRST ROW ONLY); +>> ROW (1, 2) + +SELECT * FROM TEST UNION ALL SELECT * FROM TEST OFFSET 2 ROWS; +> A B +> - - +> 1 2 +> 1 3 +> 5 5 +> 5 5 +> rows: 4 + +SELECT (1, 2) IN (SELECT * FROM TEST UNION ALL SELECT * FROM TEST OFFSET 2 ROWS); +>> TRUE + +SELECT * FROM TEST UNION ALL SELECT * FROM TEST ORDER BY A DESC, B DESC OFFSET 2 ROWS; +> A B +> - - +> 1 3 +> 1 3 +> 1 2 +> 1 2 +> rows (ordered): 4 + +SELECT (1, 2) IN (SELECT * FROM TEST UNION ALL SELECT * FROM TEST ORDER BY A DESC, B DESC OFFSET 2 ROWS); +>> TRUE + +SELECT (1, 2) IN (SELECT * FROM TEST UNION ALL SELECT * FROM TEST ORDER BY A DESC, B DESC OFFSET 2 ROWS FETCH NEXT 1 ROW ONLY); +>> FALSE + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID INT, NAME VARCHAR, DATA VARCHAR); +> ok + +-- This ORDER BY condition is currently forbidden +SELECT DISTINCT DATA FROM TEST ORDER BY (CASE WHEN EXISTS(SELECT * FROM TEST T WHERE T.NAME = 'A') THEN 1 ELSE 2 END); +> exception ORDER_BY_NOT_IN_RESULT + +SELECT DISTINCT DATA FROM TEST X ORDER BY (CASE WHEN EXISTS(SELECT * FROM TEST T WHERE T.ID = X.ID + 1) THEN 1 ELSE 2 END); +> exception ORDER_BY_NOT_IN_RESULT + +DROP TABLE TEST; +> ok + +-- Additional GROUP BY tests + +CREATE TABLE TEST(A INT, B INT, C INT) AS (VALUES + (NULL, NULL, NULL), (NULL, NULL, 1), (NULL, NULL, 2), + (NULL, 1, NULL), (NULL, 1, 1), (NULL, 1, 2), + (NULL, 2, NULL), (NULL, 2, 1), (NULL, 2, 2), + (1, NULL, NULL), (1, NULL, 1), (1, NULL, 2), + (1, 1, NULL), (1, 1, 1), (1, 1, 2), + (1, 2, NULL), (1, 2, 1), (1, 2, 2), + (2, NULL, NULL), (2, NULL, 1), (2, NULL, 2), + (2, 1, NULL), (2, 1, 1), (2, 1, 2), + (2, 2, NULL), (2, 2, 1), (2, 2, 2)); +> ok + +SELECT SUM(A), B, C FROM TEST GROUP BY B, C; +> SUM(A) B C +> ------ ---- ---- +> 3 1 1 +> 3 1 2 +> 3 1 null +> 3 2 1 +> 3 2 2 +> 3 2 null +> 3 null 1 +> 3 null 2 +> 3 null null +> rows: 9 + +EXPLAIN SELECT SUM(A), B, C FROM TEST GROUP BY B, C; +>> SELECT SUM("A"), "B", "C" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ GROUP BY "B", "C" + +SELECT SUM(A), B, C FROM TEST GROUP BY (B), C, (); +> SUM(A) B C +> ------ ---- ---- +> 3 1 1 +> 3 1 2 +> 3 1 null +> 3 2 1 +> 3 2 2 +> 3 2 null +> 3 null 1 +> 3 null 2 +> 3 null null +> rows: 9 + +EXPLAIN SELECT SUM(A), B, C FROM TEST GROUP BY (B), C, (); +>> SELECT SUM("A"), "B", "C" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ GROUP BY "B", "C" + +SELECT SUM(A), B, C FROM TEST GROUP BY (B, C); +> SUM(A) B C +> ------ ---- ---- +> 3 1 1 +> 3 1 2 +> 3 1 null +> 3 2 1 +> 3 2 2 +> 3 2 null +> 3 null 1 +> 3 null 2 +> 3 null null +> rows: 9 + +EXPLAIN SELECT SUM(A), B, C FROM TEST GROUP BY (B, C); +>> SELECT SUM("A"), "B", "C" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ GROUP BY "B", "C" + +SELECT COUNT(*) FROM TEST; +>> 27 + +EXPLAIN SELECT COUNT(*) FROM TEST; +>> SELECT COUNT(*) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ /* direct lookup */ + +SELECT COUNT(*) FROM TEST GROUP BY (); +>> 27 + +EXPLAIN SELECT COUNT(*) FROM TEST GROUP BY (); +>> SELECT COUNT(*) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ /* direct lookup */ + +SELECT COUNT(*) FROM TEST WHERE FALSE; +>> 0 + +EXPLAIN SELECT COUNT(*) FROM TEST WHERE FALSE; +>> SELECT COUNT(*) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan: FALSE */ WHERE FALSE + +SELECT COUNT(*) FROM TEST WHERE FALSE GROUP BY (); +>> 0 + +EXPLAIN SELECT COUNT(*) FROM TEST WHERE FALSE GROUP BY (); +>> SELECT COUNT(*) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan: FALSE */ WHERE FALSE + +SELECT COUNT(*) FROM TEST WHERE FALSE GROUP BY (), (); +>> 0 + +EXPLAIN SELECT COUNT(*) FROM TEST WHERE FALSE GROUP BY (), (); +>> SELECT COUNT(*) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan: FALSE */ WHERE FALSE + +SELECT 1 FROM TEST GROUP BY (); +>> 1 + +EXPLAIN SELECT 1 FROM TEST GROUP BY (); +>> SELECT 1 FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ GROUP BY () /* direct lookup */ + +EXPLAIN SELECT FALSE AND MAX(A) > 0 FROM TEST; +>> SELECT FALSE FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ GROUP BY () /* direct lookup */ + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INT PRIMARY KEY) AS (VALUES 1, 2, 3); +> ok + +SELECT A AS A1, A AS A2 FROM TEST GROUP BY A; +> A1 A2 +> -- -- +> 1 1 +> 2 2 +> 3 3 +> rows: 3 + +DROP TABLE TEST; +> ok + +-- Tests for SELECT without columns + +EXPLAIN SELECT *; +>> SELECT + +SELECT; +> +> +> +> rows: 1 + +SELECT FROM DUAL; +> +> +> +> rows: 1 + +SELECT * FROM DUAL JOIN (SELECT * FROM DUAL) ON 1 = 1; +> +> +> +> rows: 1 + +EXPLAIN SELECT * FROM DUAL JOIN (SELECT * FROM DUAL) ON 1 = 1; +>> SELECT FROM DUAL /* dual index */ INNER JOIN ( SELECT ) "_7" /* SELECT */ ON 1=1 + +SELECT WHERE FALSE; +> +> +> rows: 0 + +SELECT GROUP BY (); +> +> +> +> rows: 1 + +SELECT HAVING FALSE; +> +> +> rows: 0 + +SELECT QUALIFY FALSE; +> +> +> rows: 0 + +SELECT ORDER BY (SELECT 1); +> +> +> +> rows: 1 + +SELECT OFFSET 0 ROWS; +> +> +> +> rows: 1 + +SELECT FETCH FIRST 0 ROWS ONLY; +> +> +> rows: 0 + +CREATE TABLE TEST(A INT, B INT, C INT, D INT); +> ok + +EXPLAIN SELECT SUM(D) FROM TEST GROUP BY (A + B) + C; +>> SELECT SUM("D") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ GROUP BY ("A" + "B") + "C" + +EXPLAIN SELECT SUM(D) FROM TEST GROUP BY (A + B); +>> SELECT SUM("D") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ GROUP BY "A" + "B" + +EXPLAIN SELECT 1 FROM (SELECT SUM(D) FROM TEST GROUP BY (A + B)) T; +>> SELECT 1 FROM ( SELECT SUM("D") FROM "PUBLIC"."TEST" GROUP BY "A" + "B" ) "T" /* SELECT SUM(D) FROM PUBLIC.TEST /* PUBLIC.TEST.tableScan */ GROUP BY A + B */ + +EXPLAIN SELECT SUM(D) FROM TEST GROUP BY (A + B), C; +>> SELECT SUM("D") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ GROUP BY "A" + "B", "C" + +EXPLAIN SELECT SUM(D) FROM TEST GROUP BY (A + B) HAVING TRUE; +>> SELECT SUM("D") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ GROUP BY "A" + "B" HAVING TRUE + +EXPLAIN SELECT SUM(D) FROM TEST GROUP BY (A + B) WINDOW W AS (); +>> SELECT SUM("D") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ GROUP BY "A" + "B" + +EXPLAIN SELECT SUM(D) FROM TEST GROUP BY (A + B) QUALIFY TRUE; +>> SELECT SUM("D") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ GROUP BY "A" + "B" QUALIFY TRUE + +EXPLAIN SELECT SUM(D) FROM TEST GROUP BY (A + B) UNION VALUES 1; +>> (SELECT SUM("D") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ GROUP BY "A" + "B") UNION (VALUES (1)) + +EXPLAIN SELECT SUM(D) FROM TEST GROUP BY (A + B) EXCEPT VALUES 1; +>> (SELECT SUM("D") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ GROUP BY "A" + "B") EXCEPT (VALUES (1)) + +EXPLAIN SELECT SUM(D) FROM TEST GROUP BY (A + B) MINUS VALUES 1; +>> (SELECT SUM("D") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ GROUP BY "A" + "B") EXCEPT (VALUES (1)) + +EXPLAIN SELECT SUM(D) FROM TEST GROUP BY (A + B) INTERSECT VALUES 1; +>> (SELECT SUM("D") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ GROUP BY "A" + "B") INTERSECT (VALUES (1)) + +EXPLAIN SELECT SUM(D) FROM TEST GROUP BY (A + B) ORDER BY SUM(D); +>> SELECT SUM("D") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ GROUP BY "A" + "B" ORDER BY 1 + +EXPLAIN SELECT SUM(D) FROM TEST GROUP BY (A + B) OFFSET 0 ROWS; +>> SELECT SUM("D") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ GROUP BY "A" + "B" OFFSET 0 ROWS + +EXPLAIN SELECT SUM(D) FROM TEST GROUP BY (A + B) FETCH FIRST ROW ONLY; +>> SELECT SUM("D") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ GROUP BY "A" + "B" FETCH FIRST ROW ONLY + +EXPLAIN SELECT SUM(D) FROM TEST GROUP BY (A + B) LIMIT 1; +>> SELECT SUM("D") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ GROUP BY "A" + "B" FETCH FIRST ROW ONLY + +EXPLAIN SELECT SUM(D) FROM TEST GROUP BY (A + B) FOR UPDATE; +> exception FOR_UPDATE_IS_NOT_ALLOWED_IN_DISTINCT_OR_GROUPED_SELECT + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(A INT) AS VALUES 1, 2; +> ok + +SELECT A, A FROM TEST GROUP BY A HAVING SUM(A) > 0; +> A A +> - - +> 1 1 +> 2 2 +> rows: 2 + +DROP TABLE TEST; +> ok + +EXPLAIN SELECT X FROM SYSTEM_RANGE(1, 10) A ORDER BY (SELECT X FROM SYSTEM_RANGE(1, 20) B WHERE A.X = B.X); +>> SELECT "X" FROM SYSTEM_RANGE(1, 10) "A" /* range index */ ORDER BY (SELECT "X" FROM SYSTEM_RANGE(1, 20) "B" /* range index: X = A.X */ WHERE "A"."X" = "B"."X") + +EXPLAIN SELECT X FROM SYSTEM_RANGE(1, 10) ORDER BY 'a'; +>> SELECT "X" FROM SYSTEM_RANGE(1, 10) /* range index */ + +EXPLAIN SELECT (SELECT 1); +>> SELECT 1 + +EXPLAIN SELECT (SELECT DISTINCT 1); +>> SELECT 1 + +EXPLAIN SELECT (SELECT DISTINCT ON(RAND()) 1); +>> SELECT 1 + +EXPLAIN SELECT (SELECT 1 WHERE TRUE); +>> SELECT 1 + +EXPLAIN SELECT (SELECT 1 HAVING TRUE); +>> SELECT (SELECT 1 HAVING TRUE) + +EXPLAIN SELECT (SELECT 1 QUALIFY TRUE); +>> SELECT (SELECT 1 QUALIFY TRUE) + +EXPLAIN SELECT (VALUES 1, 2 OFFSET 1 ROW); +>> SELECT 2 + +EXPLAIN SELECT (VALUES 1, 2 OFFSET RAND() ROWS); +>> SELECT (VALUES (1), (2) OFFSET RAND() ROWS) + +EXPLAIN SELECT (VALUES 1 FETCH FIRST 2 ROWS ONLY); +>> SELECT 1 + +EXPLAIN SELECT (VALUES 1, 2 FETCH FIRST RAND() ROWS ONLY); +>> SELECT (VALUES (1), (2) FETCH FIRST RAND() ROWS ONLY) + +EXPLAIN SELECT X FROM SYSTEM_RANGE(1, 10) ORDER BY (SELECT 1); +>> SELECT "X" FROM SYSTEM_RANGE(1, 10) /* range index */ + +EXPLAIN SELECT X FROM SYSTEM_RANGE(1, 10) ORDER BY (SELECT RAND()); +>> SELECT "X" FROM SYSTEM_RANGE(1, 10) /* range index */ ORDER BY RAND() + +EXPLAIN SELECT (SELECT 1, RAND()); +>> SELECT ROW (1, RAND()) + +EXPLAIN SELECT (VALUES (1, RAND())); +>> SELECT ROW (1, RAND()) + +EXPLAIN SELECT (VALUES 1, RAND()); +>> SELECT (VALUES (1), (RAND())) + +EXPLAIN SELECT X FROM SYSTEM_RANGE(1, 10) ORDER BY X, (1+1), -X; +>> SELECT "X" FROM SYSTEM_RANGE(1, 10) /* range index */ ORDER BY 1, - "X" + + +CREATE TABLE T1 ( + T1_ID BIGINT PRIMARY KEY +); +> ok + +INSERT INTO T1 VALUES 1, 2, 3; +> update count: 3 + +CREATE TABLE T2 ( + T2_ID BIGINT PRIMARY KEY, + T1_ID BIGINT NOT NULL REFERENCES T1 +); +> ok + +INSERT INTO T2 VALUES (1, 1), (2, 1), (3, 2), (4, 3); +> update count: 4 + +SELECT * FROM (SELECT * FROM T1 FETCH FIRST 2 ROWS ONLY) T1 JOIN T2 USING (T1_ID); +> T1_ID T2_ID +> ----- ----- +> 1 1 +> 1 2 +> 2 3 +> rows: 3 + + +DROP TABLE T2, T1; +> ok + +EXECUTE IMMEDIATE 'CREATE TABLE TEST AS SELECT C1 FROM (SELECT ' || (SELECT LISTAGG('1 C' || X) FROM SYSTEM_RANGE(1, 16384)) || ')'; +> ok + +DROP TABLE TEST; +> ok + +EXECUTE IMMEDIATE 'CREATE TABLE TEST AS SELECT C1 FROM (SELECT ' || (SELECT LISTAGG('1 C' || X) FROM SYSTEM_RANGE(1, 16385)) || ')'; +> exception TOO_MANY_COLUMNS_1 + +CREATE TABLE TEST(A INT, B INT); +> ok + +CREATE INDEX TEST_IDX ON TEST(A, B); +> ok + +INSERT INTO TEST VALUES (1, 1), (1, 2), (2, 1), (2, 2); +> update count: 4 + +SELECT A, 1 AS X, B FROM TEST ORDER BY A, X, B DESC; +> A X B +> - - - +> 1 1 2 +> 1 1 1 +> 2 1 2 +> 2 1 1 +> rows (ordered): 4 + +EXPLAIN SELECT A, 1 AS X, B FROM TEST ORDER BY A, X, B DESC; +>> SELECT "A", 1 AS "X", "B" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ ORDER BY 1, 2, 3 DESC + +DROP TABLE TEST; +> ok + +SELECT X FROM SYSTEM_RANGE(1, 2) ORDER BY X DESC FETCH FIRST 0xFFFFFFFF ROWS ONLY; +> X +> - +> 2 +> 1 +> rows (ordered): 2 + +SELECT ((SELECT 1 X) EXCEPT (SELECT 1 Y)) T; +> 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/queries/table.sql b/h2/src/test/org/h2/test/scripts/queries/table.sql new file mode 100644 index 0000000..a4d2347 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/queries/table.sql @@ -0,0 +1,64 @@ +-- 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 +-- + +CREATE TABLE TEST(A INT, B INT, C INT); +> ok + +INSERT INTO TEST VALUES (1, 1, 1), (1, 1, 2), (1, 1, 3), (1, 2, 1), (1, 2, 2), (1, 2, 3), + (2, 1, 1), (2, 1, 2), (2, 1, 3), (2, 2, 1), (2, 2, 2), (2, 2, 3); +> update count: 12 + +TABLE TEST ORDER BY A, B; +> A B C +> - - - +> 1 1 1 +> 1 1 2 +> 1 1 3 +> 1 2 1 +> 1 2 2 +> 1 2 3 +> 2 1 1 +> 2 1 2 +> 2 1 3 +> 2 2 1 +> 2 2 2 +> 2 2 3 +> rows (partially ordered): 12 + +TABLE TEST ORDER BY A, B, C FETCH FIRST 4 ROWS ONLY; +> A B C +> - - - +> 1 1 1 +> 1 1 2 +> 1 1 3 +> 1 2 1 +> rows (ordered): 4 + +SELECT * FROM (TABLE TEST) ORDER BY A, B, C FETCH FIRST ROW ONLY; +> A B C +> - - - +> 1 1 1 +> rows (ordered): 1 + +SELECT (1, 2, 3) IN (TABLE TEST); +>> TRUE + +SELECT (TABLE TEST FETCH FIRST ROW ONLY) "ROW"; +> ROW +> ------------- +> ROW (1, 1, 1) +> rows: 1 + +EXPLAIN TABLE TEST ORDER BY A; +>> TABLE "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ ORDER BY 1 + +CREATE INDEX TEST_A_INDEX ON TEST(A); +> ok + +EXPLAIN TABLE TEST ORDER BY A; +>> TABLE "PUBLIC"."TEST" /* PUBLIC.TEST_A_INDEX */ ORDER BY 1 /* index sorted */ + +DROP TABLE TEST; +> ok diff --git a/h2/src/test/org/h2/test/scripts/queries/values.sql b/h2/src/test/org/h2/test/scripts/queries/values.sql new file mode 100644 index 0000000..410945e --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/queries/values.sql @@ -0,0 +1,115 @@ +-- 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 +-- + +VALUES (1, 2); +> C1 C2 +> -- -- +> 1 2 +> rows: 1 + +VALUES ROW (1, 2); +> C1 C2 +> -- -- +> 1 2 +> rows: 1 + +VALUES 1, 2; +> C1 +> -- +> 1 +> 2 +> rows: 2 + +VALUES 4, 3, 1, 2 ORDER BY 1 FETCH FIRST 75 PERCENT ROWS ONLY; +> C1 +> -- +> 1 +> 2 +> 3 +> rows (ordered): 3 + +SELECT * FROM (VALUES (1::BIGINT, 2)) T (A, B) WHERE (A, B) IN (VALUES(1, 2)); +> A B +> - - +> 1 2 +> rows: 1 + +SELECT * FROM (VALUES (1000000000000, 2)) T (A, B) WHERE (A, B) IN (VALUES(1, 2)); +> A B +> - - +> rows: 0 + +SELECT * FROM (VALUES (1, 2)) T (A, B) WHERE (A, B) IN (VALUES(1::BIGINT, 2)); +> A B +> - - +> 1 2 +> rows: 1 + +SELECT * FROM (VALUES (1, 2)) T (A, B) WHERE (A, B) IN (VALUES(1000000000000, 2)); +> A B +> - - +> rows: 0 + +EXPLAIN VALUES 1, (2), ROW(3); +>> VALUES (1), (2), (3) + +EXPLAIN VALUES (1, 2), (3, 4); +>> VALUES (1, 2), (3, 4) + +EXPLAIN SELECT * FROM (VALUES 1, 2) T(V); +>> SELECT "T"."V" FROM (VALUES (1), (2)) "T"("V") /* table scan */ + +EXPLAIN SELECT * FROM (VALUES 1, 2); +>> SELECT "_0"."C1" FROM (VALUES (1), (2)) "_0" /* table scan */ + +EXPLAIN SELECT * FROM (VALUES 1, 2 ORDER BY 1 DESC); +>> SELECT "_1"."C1" FROM ( VALUES (1), (2) ORDER BY 1 DESC ) "_1" /* VALUES (1), (2) ORDER BY 1 DESC */ + +-- Non-standard syntax +EXPLAIN SELECT * FROM VALUES 1, 2; +>> SELECT "_2"."C1" FROM (VALUES (1), (2)) "_2" /* table scan */ + +VALUES (1, 2), (3, 4), (5, 1) ORDER BY C1 + C2; +> C1 C2 +> -- -- +> 1 2 +> 5 1 +> 3 4 +> rows (ordered): 3 + +VALUES (1, 2), (3, 4), (5, 1) ORDER BY C1 + C2, C1 * C2; +> C1 C2 +> -- -- +> 1 2 +> 5 1 +> 3 4 +> rows (ordered): 3 + +VALUES (1, 2), (3, 4), (5, 1) ORDER BY C1 + C2, C1 * C2 OFFSET 1 ROW FETCH FIRST 1 ROW ONLY; +> C1 C2 +> -- -- +> 5 1 +> rows (ordered): 1 + +EXPLAIN VALUES (1, 2), (3, 4), (5, 1) ORDER BY C1 + C2, C1 * C2 OFFSET 1 ROW FETCH FIRST 1 ROW ONLY; +>> VALUES (1, 2), (3, 4), (5, 1) ORDER BY "C1" + "C2", "C1" * "C2" OFFSET 1 ROW FETCH NEXT ROW ONLY + +EXECUTE IMMEDIATE 'CREATE TABLE TEST AS SELECT C1 FROM (VALUES (' || (SELECT LISTAGG('1') FROM SYSTEM_RANGE(1, 16384)) || '))'; +> ok + +DROP TABLE TEST; +> ok + +EXECUTE IMMEDIATE 'CREATE TABLE TEST AS SELECT C1 FROM (VALUES (' || (SELECT LISTAGG('1') FROM SYSTEM_RANGE(1, 16385)) || '))'; +> exception TOO_MANY_COLUMNS_1 + +VALUES (1), (1, 2); +> exception COLUMN_COUNT_DOES_NOT_MATCH + +EXPLAIN SELECT C1, 2 FROM (VALUES 1, 2, 3) T ORDER BY 1; +>> SELECT "C1", 2 FROM (VALUES (1), (2), (3)) "T" /* table scan */ ORDER BY 1 + +EXPLAIN SELECT C1, 2 FROM (VALUES 1, 2, 3) T ORDER BY (1); +>> SELECT "C1", 2 FROM (VALUES (1), (2), (3)) "T" /* table scan */ diff --git a/h2/src/test/org/h2/test/scripts/queries/window.sql b/h2/src/test/org/h2/test/scripts/queries/window.sql new file mode 100644 index 0000000..7e1e856 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/queries/window.sql @@ -0,0 +1,232 @@ +-- 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 +-- + +CREATE TABLE TEST(ID INT, R INT, CATEGORY INT); +> ok + +INSERT INTO TEST VALUES + (1, 4, 1), + (2, 3, 1), + (3, 2, 2), + (4, 1, 2); +> update count: 4 + +SELECT *, ROW_NUMBER() OVER W FROM TEST; +> exception WINDOW_NOT_FOUND_1 + +SELECT * FROM TEST WINDOW W AS W1, W1 AS (); +> exception SYNTAX_ERROR_2 + +SELECT *, ROW_NUMBER() OVER W1, ROW_NUMBER() OVER W2 FROM TEST + WINDOW W1 AS (W2 ORDER BY ID), W2 AS (PARTITION BY CATEGORY ORDER BY ID DESC); +> ID R CATEGORY ROW_NUMBER() OVER (PARTITION BY CATEGORY ORDER BY ID) ROW_NUMBER() OVER (PARTITION BY CATEGORY ORDER BY ID DESC) +> -- - -------- ----------------------------------------------------- ---------------------------------------------------------- +> 1 4 1 1 2 +> 2 3 1 2 1 +> 3 2 2 1 2 +> 4 1 2 2 1 +> rows: 4 + +SELECT *, LAST_VALUE(ID) OVER W FROM TEST + WINDOW W AS (PARTITION BY CATEGORY ORDER BY ID RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW); +> ID R CATEGORY LAST_VALUE(ID) OVER (PARTITION BY CATEGORY ORDER BY ID RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE CURRENT ROW) +> -- - -------- ------------------------------------------------------------------------------------------------------------------------------------- +> 1 4 1 2 +> 2 3 1 1 +> 3 2 2 4 +> 4 1 2 3 +> rows: 4 + +DROP TABLE TEST; +> ok + +SELECT MAX(MAX(X) OVER ()) OVER () FROM VALUES (1); +> exception INVALID_USE_OF_AGGREGATE_FUNCTION_1 + +SELECT MAX(MAX(X) OVER ()) FROM VALUES (1); +> exception INVALID_USE_OF_AGGREGATE_FUNCTION_1 + +SELECT MAX(MAX(X)) FROM VALUES (1); +> exception INVALID_USE_OF_AGGREGATE_FUNCTION_1 + +CREATE TABLE TEST(ID INT, CATEGORY INT); +> ok + +INSERT INTO TEST VALUES + (1, 1), + (2, 1), + (4, 2), + (8, 2), + (16, 3), + (32, 3); +> update count: 6 + +SELECT ROW_NUMBER() OVER (ORDER /**/ BY CATEGORY), SUM(ID) FROM TEST GROUP BY CATEGORY HAVING SUM(ID) = 12; +> ROW_NUMBER() OVER (ORDER BY CATEGORY) SUM(ID) +> ------------------------------------- ------- +> 1 12 +> rows: 1 + +SELECT ROW_NUMBER() OVER (ORDER /**/ BY CATEGORY), SUM(ID) FROM TEST GROUP BY CATEGORY HAVING CATEGORY = 2; +> ROW_NUMBER() OVER (ORDER BY CATEGORY) SUM(ID) +> ------------------------------------- ------- +> 1 12 +> rows: 1 + +SELECT ROW_NUMBER() OVER (ORDER BY CATEGORY), SUM(ID) FROM TEST GROUP BY CATEGORY HAVING CATEGORY > 1; +> ROW_NUMBER() OVER (ORDER BY CATEGORY) SUM(ID) +> ------------------------------------- ------- +> 1 12 +> 2 48 +> rows: 2 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID INT, CATEGORY BOOLEAN); +> ok + +INSERT INTO TEST VALUES + (1, FALSE), + (2, FALSE), + (4, TRUE), + (8, TRUE), + (16, FALSE), + (32, FALSE); +> update count: 6 + +SELECT ROW_NUMBER() OVER (ORDER BY CATEGORY), SUM(ID) FROM TEST GROUP BY CATEGORY HAVING SUM(ID) = 12; +> ROW_NUMBER() OVER (ORDER BY CATEGORY) SUM(ID) +> ------------------------------------- ------- +> 1 12 +> rows: 1 + +SELECT ROW_NUMBER() OVER (ORDER BY CATEGORY), SUM(ID) FROM TEST GROUP BY CATEGORY HAVING CATEGORY; +> ROW_NUMBER() OVER (ORDER BY CATEGORY) SUM(ID) +> ------------------------------------- ------- +> 1 12 +> rows: 1 + +SELECT SUM(ID) OVER (ORDER BY ID ROWS NULL PRECEDING) P FROM TEST; +> exception INVALID_PRECEDING_OR_FOLLOWING_1 + +SELECT SUM(ID) OVER (ORDER BY ID RANGE NULL PRECEDING) P FROM TEST; +> exception INVALID_PRECEDING_OR_FOLLOWING_1 + +SELECT ARRAY_AGG(ID) OVER (ORDER BY V NULLS FIRST RANGE BETWEEN 1 PRECEDING AND UNBOUNDED FOLLOWING) A, + ID, V FROM VALUES (1, 1), (2, NULL), (3, 2) T(ID, V) ORDER BY V NULLS FIRST; +> A ID V +> --------- -- ---- +> [3, 1, 2] 2 null +> [3, 1] 1 1 +> [3, 1] 3 2 +> rows (ordered): 3 + +SELECT ARRAY_AGG(ID) OVER (ORDER BY V NULLS LAST RANGE BETWEEN 1 PRECEDING AND UNBOUNDED FOLLOWING) A, + ID, V FROM VALUES (1, 1), (2, NULL), (3, 2) T(ID, V) ORDER BY V NULLS LAST; +> A ID V +> --------- -- ---- +> [2, 3, 1] 1 1 +> [2, 3, 1] 3 2 +> [2] 2 null +> rows (ordered): 3 + +SELECT ARRAY_AGG(ID) OVER (ORDER BY V NULLS FIRST RANGE BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING) A, + ID, V FROM VALUES (1, 1), (2, NULL), (3, 2) T(ID, V) ORDER BY V NULLS FIRST; +> A ID V +> --------- -- ---- +> [3, 1, 2] 2 null +> [3] 1 1 +> null 3 2 +> rows (ordered): 3 + +SELECT ARRAY_AGG(ID) OVER (ORDER BY V NULLS LAST RANGE BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING) A, + ID, V FROM VALUES (1, 1), (2, NULL), (3, 2) T(ID, V) ORDER BY V NULLS LAST; +> A ID V +> ------ -- ---- +> [2, 3] 1 1 +> [2] 3 2 +> [2] 2 null +> rows (ordered): 3 + +SELECT ARRAY_AGG(ID) OVER (ORDER BY V NULLS FIRST RANGE BETWEEN UNBOUNDED PRECEDING AND 1 FOLLOWING) A, + ID, V FROM VALUES (1, 1), (2, NULL), (3, 2) T(ID, V) ORDER BY V NULLS FIRST; +> A ID V +> --------- -- ---- +> [2] 2 null +> [2, 1, 3] 1 1 +> [2, 1, 3] 3 2 +> rows (ordered): 3 + +SELECT ARRAY_AGG(ID) OVER (ORDER BY V NULLS LAST RANGE BETWEEN UNBOUNDED PRECEDING AND 1 FOLLOWING) A, + ID, V FROM VALUES (1, 1), (2, NULL), (3, 2) T(ID, V) ORDER BY V NULLS LAST; +> A ID V +> --------- -- ---- +> [1, 3] 1 1 +> [1, 3] 3 2 +> [1, 3, 2] 2 null +> rows (ordered): 3 + +SELECT ARRAY_AGG(ID) OVER (ORDER BY V NULLS FIRST RANGE BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) A, + ID, V FROM VALUES (1, 1), (2, NULL), (3, 2) T(ID, V) ORDER BY V NULLS FIRST; +> A ID V +> ------ -- ---- +> [2] 2 null +> [2] 1 1 +> [2, 1] 3 2 +> rows (ordered): 3 + +SELECT ARRAY_AGG(ID) OVER (ORDER BY V NULLS LAST RANGE BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) A, + ID, V FROM VALUES (1, 1), (2, NULL), (3, 2) T(ID, V) ORDER BY V NULLS LAST; +> A ID V +> --------- -- ---- +> null 1 1 +> [1] 3 2 +> [1, 3, 2] 2 null +> rows (ordered): 3 + +SELECT SUM(V) OVER (ORDER BY V RANGE BETWEEN 1 PRECEDING AND 1 FOLLOWING) FROM VALUES (TRUE) T(V); +> exception INVALID_VALUE_2 + +SELECT + SUM(ID) OVER (ORDER BY ID RANGE BETWEEN 10000000000 PRECEDING AND CURRENT ROW) P, + SUM(ID) OVER (ORDER BY ID RANGE BETWEEN 10000000001 PRECEDING AND 10000000000 PRECEDING) P2, + SUM(ID) OVER (ORDER BY ID RANGE BETWEEN CURRENT ROW AND 2147483647 FOLLOWING) F, + SUM(ID) OVER (ORDER BY ID RANGE BETWEEN 2147483647 FOLLOWING AND 2147483648 FOLLOWING) F2, + ID FROM TEST ORDER BY ID; +> P P2 F F2 ID +> -- ---- -- ---- -- +> 1 null 63 null 1 +> 3 null 62 null 2 +> 7 null 60 null 4 +> 15 null 56 null 8 +> 31 null 48 null 16 +> 63 null 32 null 32 +> rows (ordered): 6 + +DROP TABLE TEST; +> ok + +SELECT + ARRAY_AGG(T) OVER (ORDER BY T RANGE BETWEEN INTERVAL '1' DAY PRECEDING AND CURRENT ROW) C, + ARRAY_AGG(T) OVER (ORDER BY T RANGE BETWEEN INTERVAL '2' HOUR PRECEDING AND INTERVAL '1' HOUR PRECEDING) P, + T FROM VALUES (TIME '00:00:00'), (TIME '01:30:00') TEST(T) ORDER BY T; +> C P T +> -------------------- ---------- -------- +> [00:00:00] null 00:00:00 +> [00:00:00, 01:30:00] [00:00:00] 01:30:00 +> rows (ordered): 2 + +SELECT SUM(A) OVER (ORDER BY A, B RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) S FROM VALUES (1, 2) T(A, B); +>> 1 + +SELECT SUM(A) OVER (ORDER BY A, B RANGE BETWEEN 1 PRECEDING AND UNBOUNDED FOLLOWING) S FROM VALUES (1, 2) T(A, B); +> exception SYNTAX_ERROR_2 + +SELECT SUM(A) OVER (ORDER BY A, B RANGE BETWEEN UNBOUNDED PRECEDING AND 1 FOLLOWING) S FROM VALUES (1, 2) T(A, B); +> exception SYNTAX_ERROR_2 + +SELECT SUM(A) OVER (GROUPS BETWEEN UNBOUNDED PRECEDING AND 1 FOLLOWING) S FROM VALUES (1, 2) T(A, B); +> exception SYNTAX_ERROR_2 diff --git a/h2/src/test/org/h2/test/scripts/range_table.sql b/h2/src/test/org/h2/test/scripts/range_table.sql new file mode 100644 index 0000000..b3b758b --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/range_table.sql @@ -0,0 +1,235 @@ +-- 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 +-- + +explain select * from system_range(1, 2) where x=x+1 and x=1; +>> SELECT "SYSTEM_RANGE"."X" FROM SYSTEM_RANGE(1, 2) /* range index: X = CAST(1 AS BIGINT) */ WHERE ("X" = CAST(1 AS BIGINT)) AND ("X" = ("X" + 1)) + +explain select * from system_range(1, 2) where not (x = 1 and x*2 = 2); +>> SELECT "SYSTEM_RANGE"."X" FROM SYSTEM_RANGE(1, 2) /* range index */ WHERE ("X" <> CAST(1 AS BIGINT)) OR (("X" * 2) <> 2) + +explain select * from system_range(1, 10) where (NOT x >= 5); +>> SELECT "SYSTEM_RANGE"."X" FROM SYSTEM_RANGE(1, 10) /* range index: X < CAST(5 AS BIGINT) */ WHERE "X" < CAST(5 AS BIGINT) + +select (select t1.x from system_range(1,1) t2) from system_range(1,1) t1; +> (SELECT T1.X FROM SYSTEM_RANGE(1, 1) T2) +> ---------------------------------------- +> 1 +> rows: 1 + +EXPLAIN PLAN FOR SELECT * FROM SYSTEM_RANGE(1, 20); +>> SELECT "SYSTEM_RANGE"."X" FROM SYSTEM_RANGE(1, 20) /* range index */ + +select sum(x) from system_range(2, 1000) r where +not exists(select * from system_range(2, 32) r2 where r.x>r2.x and mod(r.x, r2.x)=0); +>> 76127 + +SELECT COUNT(*) FROM SYSTEM_RANGE(0, 2111222333); +>> 2111222334 + +select * from system_range(2, 100) r where +not exists(select * from system_range(2, 11) r2 where r.x>r2.x and mod(r.x, r2.x)=0); +> X +> -- +> 11 +> 13 +> 17 +> 19 +> 2 +> 23 +> 29 +> 3 +> 31 +> 37 +> 41 +> 43 +> 47 +> 5 +> 53 +> 59 +> 61 +> 67 +> 7 +> 71 +> 73 +> 79 +> 83 +> 89 +> 97 +> rows: 25 + +SELECT * FROM SYSTEM_RANGE(1, 10) ORDER BY 1; +> X +> -- +> 1 +> 2 +> 3 +> 4 +> 5 +> 6 +> 7 +> 8 +> 9 +> 10 +> rows (ordered): 10 + +SELECT COUNT(*) FROM SYSTEM_RANGE(1, 10); +>> 10 + +SELECT * FROM SYSTEM_RANGE(1, 10, 2) ORDER BY 1; +> X +> - +> 1 +> 3 +> 5 +> 7 +> 9 +> rows (ordered): 5 + +SELECT COUNT(*) FROM SYSTEM_RANGE(1, 10, 2); +>> 5 + +SELECT * FROM SYSTEM_RANGE(1, 9, 2) ORDER BY 1; +> X +> - +> 1 +> 3 +> 5 +> 7 +> 9 +> rows (ordered): 5 + +SELECT COUNT(*) FROM SYSTEM_RANGE(1, 9, 2); +>> 5 + +SELECT * FROM SYSTEM_RANGE(10, 1, -2) ORDER BY 1 DESC; +> X +> -- +> 10 +> 8 +> 6 +> 4 +> 2 +> rows (ordered): 5 + +SELECT COUNT(*) FROM SYSTEM_RANGE(10, 1, -2); +>> 5 + +SELECT * FROM SYSTEM_RANGE(10, 2, -2) ORDER BY 1 DESC; +> X +> -- +> 10 +> 8 +> 6 +> 4 +> 2 +> rows (ordered): 5 + +SELECT COUNT(*) FROM SYSTEM_RANGE(10, 2, -2); +>> 5 + +SELECT * FROM SYSTEM_RANGE(1, 1); +>> 1 + +SELECT COUNT(*) FROM SYSTEM_RANGE(1, 1); +>> 1 + +SELECT * FROM SYSTEM_RANGE(1, 1, -1); +>> 1 + +SELECT COUNT(*) FROM SYSTEM_RANGE(1, 1, -1); +>> 1 + +SELECT * FROM SYSTEM_RANGE(2, 1); +> X +> - +> rows: 0 + +SELECT COUNT(*) FROM SYSTEM_RANGE(2, 1); +>> 0 + +SELECT * FROM SYSTEM_RANGE(2, 1, 2); +> X +> - +> rows: 0 + +SELECT COUNT(*) FROM SYSTEM_RANGE(2, 1, 2); +>> 0 + +SELECT * FROM SYSTEM_RANGE(1, 2, 0); +> exception STEP_SIZE_MUST_NOT_BE_ZERO + +SELECT COUNT(*) FROM SYSTEM_RANGE(1, 2, 0); +> exception STEP_SIZE_MUST_NOT_BE_ZERO + +SELECT * FROM SYSTEM_RANGE(2, 1, 0); +> exception STEP_SIZE_MUST_NOT_BE_ZERO + +SELECT COUNT(*) FROM SYSTEM_RANGE(2, 1, 0); +> exception STEP_SIZE_MUST_NOT_BE_ZERO + +SELECT * FROM SYSTEM_RANGE(1, 8, 2); +> X +> - +> 1 +> 3 +> 5 +> 7 +> rows: 4 + +SELECT * FROM SYSTEM_RANGE(1, 8, 2) WHERE X = 2; +> X +> - +> rows: 0 + +SELECT COUNT(*) FROM SYSTEM_RANGE(1, 8, 2) WHERE X = 2; +>> 0 + +SELECT * FROM SYSTEM_RANGE(1, 8, 2) WHERE X BETWEEN 2 AND 6; +> X +> - +> 3 +> 5 +> rows: 2 + +SELECT COUNT(*) FROM SYSTEM_RANGE(1, 8, 2) WHERE X BETWEEN 2 AND 6; +>> 2 + +SELECT * FROM SYSTEM_RANGE(8, 1, -2) ORDER BY X DESC; +> X +> - +> 8 +> 6 +> 4 +> 2 +> rows (ordered): 4 + +SELECT * FROM SYSTEM_RANGE(8, 1, -2) WHERE X = 3; +> X +> - +> rows: 0 + +SELECT COUNT(*) FROM SYSTEM_RANGE(8, 1, -2) WHERE X = 3; +>> 0 + +SELECT * FROM SYSTEM_RANGE(8, 1, -2) WHERE X BETWEEN 3 AND 7 ORDER BY 1 DESC; +> X +> - +> 6 +> 4 +> rows (ordered): 2 + +SELECT COUNT(*) FROM SYSTEM_RANGE(8, 1, -2) WHERE X BETWEEN 3 AND 7; +>> 2 + +SELECT X FROM SYSTEM_RANGE(1, 2, ?); +{ +1 +> X +> - +> 1 +> 2 +> rows: 2 +}; +> update count: 0 diff --git a/h2/src/test/org/h2/test/scripts/testScript.sql b/h2/src/test/org/h2/test/scripts/testScript.sql new file mode 100644 index 0000000..5415e3b --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/testScript.sql @@ -0,0 +1,7076 @@ +-- 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 +-- +CREATE TABLE TEST(A INT, B INT) AS VALUES (1, 2), (3, 4), (5, 6); +> ok + +UPDATE TOP (1) TEST SET B = 10; +> exception TABLE_OR_VIEW_NOT_FOUND_1 + +SET MODE MSSQLServer; +> ok + +UPDATE TOP (1) TEST SET B = 10; +> update count: 1 + +SELECT COUNT(*) FILTER (WHERE B = 10) N, COUNT(*) FILTER (WHERE B <> 10) O FROM TEST; +> N O +> - - +> 1 2 +> rows: 1 + +UPDATE TEST SET B = 10 WHERE B <> 10; +> update count: 2 + +UPDATE TOP (1) TEST SET B = 10 LIMIT 1; +> exception SYNTAX_ERROR_1 + +SET MODE Regular; +> ok + +DROP TABLE TEST; +> ok + +--- special grammar and test cases --------------------------------------------------------------------------------------------- +select 0 as x from system_range(1, 2) d group by d.x; +> X +> - +> 0 +> 0 +> rows: 2 + +select 1 "a", count(*) from dual group by "a" order by "a"; +> a COUNT(*) +> - -------- +> 1 1 +> rows (ordered): 1 + +create table results(eventId int, points int, studentId int); +> ok + +insert into results values(1, 10, 1), (2, 20, 1), (3, 5, 1); +> update count: 3 + +insert into results values(1, 10, 2), (2, 20, 2), (3, 5, 2); +> update count: 3 + +insert into results values(1, 10, 3), (2, 20, 3), (3, 5, 3); +> update count: 3 + +SELECT SUM(points) FROM RESULTS +WHERE eventID IN +(SELECT eventID FROM RESULTS +WHERE studentID = 2 +ORDER BY points DESC +LIMIT 2 ) +AND studentID = 2; +> SUM(POINTS) +> ----------- +> 30 +> rows: 1 + +SELECT eventID X FROM RESULTS +WHERE studentID = 2 +ORDER BY points DESC +LIMIT 2; +> X +> - +> 2 +> 1 +> rows (ordered): 2 + +SELECT SUM(r.points) FROM RESULTS r, +(SELECT eventID FROM RESULTS +WHERE studentID = 2 +ORDER BY points DESC +LIMIT 2 ) r2 +WHERE r2.eventID = r.eventId +AND studentID = 2; +> SUM(R.POINTS) +> ------------- +> 30 +> rows: 1 + +drop table results; +> ok + +create table test(id int, name varchar) as select 1, 'a'; +> ok + +(select id from test order by id) union (select id from test order by name); +> ID +> -- +> 1 +> rows: 1 + +drop table test; +> ok + +select * from system_range(1,1) order by x limit 3 offset 3; +> X +> - +> rows (ordered): 0 + +create sequence seq start with 65 increment by 1; +> ok + +select char(nextval('seq')) as x; +> X +> - +> A +> rows: 1 + +select char(nextval('seq')) as x; +> X +> - +> B +> rows: 1 + +drop sequence seq; +> ok + +create table test(id int, name varchar); +> ok + +insert into test values(5, 'b'), (5, 'b'), (20, 'a'); +> update count: 3 + +select id from test where name in(null, null); +> ID +> -- +> rows: 0 + +select * from (select * from test order by name limit 1) where id < 10; +> ID NAME +> -- ---- +> rows: 0 + +drop table test; +> ok + +create table test (id int primary key, pid int); +> ok + +alter table test add constraint fk_test foreign key (pid) +references test (id) index idx_test_pid; +> ok + +insert into test values (2, null); +> update count: 1 + +update test set pid = 1 where id = 2; +> exception REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1 + +drop table test; +> ok + +create table test(name varchar(255)); +> ok + +select * from test union select * from test order by test.name; +> exception ORDER_BY_NOT_IN_RESULT + +insert into test values('a'), ('b'), ('c'); +> update count: 3 + +select name from test where name > all(select name from test where name<'b'); +> NAME +> ---- +> b +> c +> rows: 2 + +select count(*) from (select name from test where name > all(select name from test where name<'b')) x; +> COUNT(*) +> -------- +> 2 +> rows: 1 + +drop table test; +> ok + +create table test(id int) as select 1; +> ok + +select * from test where id >= all(select id from test where 1=0); +> ID +> -- +> 1 +> rows: 1 + +select * from test where id = all(select id from test where 1=0); +> ID +> -- +> 1 +> rows: 1 + +select * from test where id = all(select id from test union all select id from test); +> ID +> -- +> 1 +> rows: 1 + +select * from test where null >= all(select id from test where 1=0); +> ID +> -- +> 1 +> rows: 1 + +select * from test where null = all(select id from test where 1=0); +> ID +> -- +> 1 +> rows: 1 + +select * from test where null = all(select id from test union all select id from test); +> ID +> -- +> rows: 0 + +select * from test where id >= all(select cast(null as int) from test); +> ID +> -- +> rows: 0 + +select * from test where id = all(select null from test union all select id from test); +> ID +> -- +> rows: 0 + +select * from test where null >= all(select cast(null as int) from test); +> ID +> -- +> rows: 0 + +select * from test where null = all(select null from test union all select id from test); +> ID +> -- +> rows: 0 + +drop table test; +> ok + +select x from dual order by y.x; +> exception COLUMN_NOT_FOUND_1 + +create table test(id int primary key, name varchar(255), row_number int); +> ok + +insert into test values(1, 'hello', 10), (2, 'world', 20); +> update count: 2 + +select rownum(), id, name from test order by id; +> ROWNUM() ID NAME +> -------- -- ----- +> 1 1 hello +> 2 2 world +> rows (ordered): 2 + +select rownum(), id, name from test order by name; +> ROWNUM() ID NAME +> -------- -- ----- +> 1 1 hello +> 2 2 world +> rows (ordered): 2 + +select rownum(), id, name from test order by name desc; +> ROWNUM() ID NAME +> -------- -- ----- +> 2 2 world +> 1 1 hello +> rows (ordered): 2 + +update test set (id)=(id); +> update count: 2 + +drop table test; +> ok + +select 2^2; +> exception SYNTAX_ERROR_1 + +select * from dual where cast('xx' as varchar_ignorecase(1)) = 'X' and cast('x x ' as char(2)) = 'x'; +> +> +> +> rows: 1 + +explain select -cast(0 as real), -cast(0 as double); +>> SELECT CAST(0.0 AS REAL), CAST(0.0 AS DOUBLE PRECISION) + +select (1) one; +> ONE +> --- +> 1 +> rows: 1 + +create table test(id int); +> ok + +insert into test values(1), (2), (4); +> update count: 3 + +select * from test order by id limit -1; +> exception INVALID_VALUE_2 + +select * from test order by id limit 0; +> ID +> -- +> rows (ordered): 0 + +select * from test order by id limit 1; +> ID +> -- +> 1 +> rows (ordered): 1 + +select * from test order by id limit 1+1; +> ID +> -- +> 1 +> 2 +> rows (ordered): 2 + +select * from test order by id limit null; +> exception INVALID_VALUE_2 + +delete from test limit 0; +> ok + +delete from test limit 1; +> update count: 1 + +delete from test limit -1; +> exception INVALID_VALUE_2 + +drop table test; +> ok + +create table test(id int primary key); +> ok + +insert into test(id) direct sorted select x from system_range(1, 100); +> update count: 100 + +explain insert into test(id) direct sorted select x from system_range(1, 100); +>> INSERT INTO "PUBLIC"."TEST"("ID") DIRECT SELECT "X" FROM SYSTEM_RANGE(1, 100) /* range index */ + +explain select * from test limit 10; +>> SELECT "PUBLIC"."TEST"."ID" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ FETCH FIRST 10 ROWS ONLY + +drop table test; +> ok + +create table test(id int primary key); +> ok + +insert into test values(1), (2), (3), (4); +> update count: 4 + +explain analyze select * from test where id is null; +>> SELECT "PUBLIC"."TEST"."ID" FROM "PUBLIC"."TEST" /* PUBLIC.PRIMARY_KEY_2: ID IS NULL */ /* scanCount: 1 */ WHERE "ID" IS NULL + +drop table test; +> ok + +explain analyze select 1; +>> SELECT 1 + +create table test(id int); +> ok + +create view x as select * from test; +> ok + +drop table test restrict; +> exception CANNOT_DROP_2 + +drop table test cascade; +> ok + +select 1, 2 from (select * from dual) union all select 3, 4 from dual; +> 1 2 +> - - +> 1 2 +> 3 4 +> rows: 2 + +select 3 from (select * from dual) union all select 2 from dual; +> 3 +> - +> 2 +> 3 +> rows: 2 + +create table a(x int, y int); +> ok + +alter table a add constraint a_xy unique(x, y); +> ok + +create table b(x int, y int, foreign key(x, y) references a(x, y)); +> ok + +insert into a values(null, null), (null, 0), (0, null), (0, 0); +> update count: 4 + +insert into b values(null, null), (null, 0), (0, null), (0, 0); +> update count: 4 + +delete from a where x is null and y is null; +> update count: 1 + +delete from a where x is null and y = 0; +> update count: 1 + +delete from a where x = 0 and y is null; +> update count: 1 + +delete from a where x = 0 and y = 0; +> exception REFERENTIAL_INTEGRITY_VIOLATED_CHILD_EXISTS_1 + +drop table b; +> ok + +drop table a; +> ok + +select * from (select null as x) where x=1; +> X +> - +> rows: 0 + +create table test(id decimal(10, 2) primary key) as select 0; +> ok + +select * from test where id = 0.00; +> ID +> ---- +> 0.00 +> rows: 1 + +select * from test where id = 0.0; +> ID +> ---- +> 0.00 +> rows: 1 + +drop table test; +> ok + +select count(*) from (select 1 union (select 2 intersect select 2)) x; +> COUNT(*) +> -------- +> 2 +> rows: 1 + +create table test(id varchar(1) primary key) as select 'X'; +> ok + +select count(*) from (select 1 from dual where 1 in ((select 1 union select 1))) a; +> COUNT(*) +> -------- +> 1 +> rows: 1 + +insert into test ((select 1 union select 2) union select 3); +> update count: 3 + +select count(*) from test where id = 'X1'; +> COUNT(*) +> -------- +> 0 +> rows: 1 + +drop table test; +> ok + +create table test(id int, constraint pk primary key(id), constraint x unique(id)); +> ok + +SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_NAME = 'TEST'; +> CONSTRAINT_NAME +> --------------- +> PK +> X +> rows: 2 + +drop table test; +> ok + +create table parent(id int primary key); +> ok + +create table child(id int, parent_id int, constraint child_parent foreign key (parent_id) references parent(id)); +> ok + +SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_NAME = 'CHILD'; +> CONSTRAINT_NAME +> --------------- +> CHILD_PARENT +> rows: 1 + +drop table parent, child; +> ok + +create table test(id int, name varchar(max)); +> ok + +alter table test alter column id identity; +> ok + +drop table test; +> ok + +create table test(id identity); +> ok + +set password test; +> exception COLUMN_NOT_FOUND_1 + +alter user sa set password test; +> exception COLUMN_NOT_FOUND_1 + +comment on table test is test; +> exception COLUMN_NOT_FOUND_1 + +select 1 from test a where 1 in(select 1 from test b where b.id in(select 1 from test c where c.id=a.id)); +> 1 +> - +> rows: 0 + +drop table test; +> ok + +select @n := case when x = 1 then 1 else @n * x end f from system_range(1, 4); +> F +> -- +> 1 +> 2 +> 24 +> 6 +> rows: 4 + +select * from (select "x" from dual); +> exception COLUMN_NOT_FOUND_1 + +select * from(select 1 from system_range(1, 2) group by sin(x) order by sin(x)); +> 1 +> - +> 1 +> 1 +> rows: 2 + +create table parent(id int primary key, x int) as select 1 id, 2 x; +> ok + +create table child(id int references parent(id)) as select 1; +> ok + +delete from parent; +> exception REFERENTIAL_INTEGRITY_VIOLATED_CHILD_EXISTS_1 + +drop table parent, child; +> ok + +create domain integer as varchar; +> exception DOMAIN_ALREADY_EXISTS_1 + +create domain int as varchar; +> ok + +create memory table test(id int); +> ok + +script nodata nopasswords nosettings noversion; +> SCRIPT +> ----------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE DOMAIN "PUBLIC"."INT" AS CHARACTER VARYING; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "ID" "PUBLIC"."INT" ); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> rows (ordered): 4 + +SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST'; +>> CHARACTER VARYING + +drop table test; +> ok + +drop domain int; +> ok + +create table test(id identity, parent bigint, foreign key(parent) references(id)); +> ok + +insert into test values(0, 0), (1, NULL), (2, 1), (3, 3), (4, 3); +> update count: 5 + +delete from test where id = 3; +> exception REFERENTIAL_INTEGRITY_VIOLATED_CHILD_EXISTS_1 + +delete from test where id = 0; +> update count: 1 + +delete from test where id = 1; +> exception REFERENTIAL_INTEGRITY_VIOLATED_CHILD_EXISTS_1 + +drop table test; +> ok + +create schema a; +> ok + +set autocommit false; +> ok + +set schema a; +> ok + +create table t1 ( k int, v varchar(10) ); +> ok + +insert into t1 values ( 1, 't1' ); +> update count: 1 + +create table t2 ( k int, v varchar(10) ); +> ok + +insert into t2 values ( 2, 't2' ); +> update count: 1 + +create view v_test(a, b, c, d) as select t1.*, t2.* from t1 join t2 on ( t1.k = t2.k ); +> ok + +select * from v_test; +> A B C D +> - - - - +> rows: 0 + +set schema public; +> ok + +drop schema a cascade; +> ok + +set autocommit true; +> ok + +select x/3 as a, count(*) c from system_range(1, 10) group by a having c>2; +> A C +> - - +> 1 3 +> 2 3 +> rows: 2 + +create table test(id int); +> ok + +insert into test values(1), (2); +> update count: 2 + +select id+1 as x, count(*) from test group by x; +> X COUNT(*) +> - -------- +> 2 1 +> 3 1 +> rows: 2 + +select 1 as id, id as b, count(*) from test group by id; +> ID B COUNT(*) +> -- - -------- +> 1 1 1 +> 1 2 1 +> rows: 2 + +select id+1 as x, count(*) from test group by -x; +> exception COLUMN_NOT_FOUND_1 + +select id+1 as x, count(*) from test group by x having x>2; +> exception MUST_GROUP_BY_COLUMN_1 + +select id+1 as x, count(*) from test group by 1; +> exception MUST_GROUP_BY_COLUMN_1 + +drop table test; +> ok + +create table test(t0 timestamp(0), t1 timestamp(1), t4 timestamp(4)); +> ok + +select column_name, datetime_precision from information_schema.columns c where c.table_name = 'TEST' order by column_name; +> COLUMN_NAME DATETIME_PRECISION +> ----------- ------------------ +> T0 0 +> T1 1 +> T4 4 +> rows (ordered): 3 + +drop table test; +> ok + +create table test(a int); +> ok + +insert into test values(1), (2); +> update count: 2 + +select -test.a a from test order by test.a; +> A +> -- +> -1 +> -2 +> rows (ordered): 2 + +select -test.a from test order by test.a; +> - TEST.A +> -------- +> -1 +> -2 +> rows (ordered): 2 + +select -test.a aa from test order by a; +> AA +> -- +> -1 +> -2 +> rows (ordered): 2 + +select -test.a aa from test order by aa; +> AA +> -- +> -2 +> -1 +> rows (ordered): 2 + +select -test.a a from test order by a; +> A +> -- +> -2 +> -1 +> rows (ordered): 2 + +drop table test; +> ok + +CREATE TABLE table_a(a_id INT PRIMARY KEY, left_id INT, right_id INT); +> ok + +CREATE TABLE table_b(b_id INT PRIMARY KEY, a_id INT); +> ok + +CREATE TABLE table_c(left_id INT, right_id INT, center_id INT); +> ok + +CREATE VIEW view_a AS +SELECT table_c.center_id, table_a.a_id, table_b.b_id +FROM table_c +INNER JOIN table_a ON table_c.left_id = table_a.left_id +AND table_c.right_id = table_a.right_id +LEFT JOIN table_b ON table_b.a_id = table_a.a_id; +> ok + +SELECT * FROM table_c INNER JOIN view_a +ON table_c.center_id = view_a.center_id; +> LEFT_ID RIGHT_ID CENTER_ID CENTER_ID A_ID B_ID +> ------- -------- --------- --------- ---- ---- +> rows: 0 + +drop view view_a; +> ok + +drop table table_a, table_b, table_c; +> ok + +create table t (pk int primary key, attr int); +> ok + +insert into t values (1, 5), (5, 1); +> update count: 2 + +select t1.pk from t t1, t t2 where t1.pk = t2.attr order by t1.pk; +> PK +> -- +> 1 +> 5 +> rows (ordered): 2 + +drop table t; +> ok + +CREATE ROLE TEST_A; +> ok + +GRANT TEST_A TO TEST_A; +> exception ROLE_ALREADY_GRANTED_1 + +CREATE ROLE TEST_B; +> ok + +GRANT TEST_A TO TEST_B; +> ok + +GRANT TEST_B TO TEST_A; +> exception ROLE_ALREADY_GRANTED_1 + +DROP ROLE TEST_A; +> ok + +DROP ROLE TEST_B; +> ok + +CREATE ROLE PUBLIC2; +> ok + +GRANT PUBLIC2 TO SA; +> ok + +GRANT PUBLIC2 TO SA; +> ok + +REVOKE PUBLIC2 FROM SA; +> ok + +REVOKE PUBLIC2 FROM SA; +> ok + +DROP ROLE PUBLIC2; +> ok + +create table test(id int primary key, lastname varchar, firstname varchar, parent int references(id)); +> ok + +alter table test add constraint name unique (lastname, firstname); +> ok + +SELECT CONSTRAINT_NAME, INDEX_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS; +> CONSTRAINT_NAME INDEX_NAME +> --------------- ------------------ +> CONSTRAINT_2 PRIMARY_KEY_2 +> CONSTRAINT_27 CONSTRAINT_INDEX_2 +> NAME NAME_INDEX_2 +> rows: 3 + +SELECT CONSTRAINT_NAME, COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE; +> CONSTRAINT_NAME COLUMN_NAME +> --------------- ----------- +> CONSTRAINT_2 ID +> CONSTRAINT_27 PARENT +> NAME FIRSTNAME +> NAME LASTNAME +> rows: 4 + +drop table test; +> ok + +ALTER TABLE INFORMATION_SCHEMA.INFORMATION_SCHEMA_CATALOG_NAME RENAME TO INFORMATION_SCHEMA.CAT; +> exception FEATURE_NOT_SUPPORTED_1 + +CREATE TABLE test (id bigserial NOT NULL primary key); +> ok + +drop table test; +> ok + +CREATE TABLE test (id serial NOT NULL primary key); +> ok + +drop table test; +> ok + +CREATE MEMORY TABLE TEST(ID INT, D DOUBLE, F FLOAT); +> ok + +insert into test values(0, POWER(0, -1), POWER(0, -1)), (1, -POWER(0, -1), -POWER(0, -1)), (2, SQRT(-1), SQRT(-1)); +> update count: 3 + +select * from test order by id; +> ID D F +> -- --------- --------- +> 0 Infinity Infinity +> 1 -Infinity -Infinity +> 2 NaN NaN +> rows (ordered): 3 + +script nopasswords nosettings noversion; +> SCRIPT +> ----------------------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "ID" INTEGER, "D" DOUBLE PRECISION, "F" FLOAT ); +> -- 3 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> INSERT INTO "PUBLIC"."TEST" VALUES (0, 'Infinity', 'Infinity'), (1, '-Infinity', '-Infinity'), (2, 'NaN', 'NaN'); +> rows (ordered): 4 + +DROP TABLE TEST; +> ok + +create schema a; +> ok + +create table a.x(ax int); +> ok + +create schema b; +> ok + +create table b.x(bx int); +> ok + +select * from a.x, b.x; +> AX BX +> -- -- +> rows: 0 + +drop schema a cascade; +> ok + +drop schema b cascade; +> ok + +CREATE TABLE p(d date); +> ok + +INSERT INTO p VALUES('-1-01-01'), ('0-01-01'), ('0001-01-01'); +> update count: 3 + +select d, year(d), extract(year from d), cast(d as timestamp) from p; +> D EXTRACT(YEAR FROM D) EXTRACT(YEAR FROM D) CAST(D AS TIMESTAMP) +> ----------- -------------------- -------------------- -------------------- +> -0001-01-01 -1 -1 -0001-01-01 00:00:00 +> 0000-01-01 0 0 0000-01-01 00:00:00 +> 0001-01-01 1 1 0001-01-01 00:00:00 +> rows: 3 + +drop table p; +> ok + +create table test(a int, b int default 1); +> ok + +insert into test values(1, default), (2, 2), (3, null); +> update count: 3 + +select * from test; +> A B +> - ---- +> 1 1 +> 2 2 +> 3 null +> rows: 3 + +update test set b = default where a = 2; +> update count: 1 + +explain update test set b = default where a = 2; +>> UPDATE "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ SET "B" = DEFAULT WHERE "A" = 2 + +select * from test; +> A B +> - ---- +> 1 1 +> 2 1 +> 3 null +> rows: 3 + +update test set a=default; +> update count: 3 + +drop table test; +> ok + +CREATE ROLE X; +> ok + +GRANT X TO X; +> exception ROLE_ALREADY_GRANTED_1 + +CREATE ROLE Y; +> ok + +GRANT Y TO X; +> ok + +DROP ROLE Y; +> ok + +DROP ROLE X; +> ok + +select top sum(1) 0 from dual; +> exception SYNTAX_ERROR_1 + +create table test(id int primary key, name varchar) as select 1, 'Hello World'; +> ok + +select * from test; +> ID NAME +> -- ----------- +> 1 Hello World +> rows: 1 + +drop table test; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, LABEL CHAR(20), LOOKUP CHAR(30)); +> ok + +INSERT INTO TEST VALUES (1, 'Mouse', 'MOUSE'), (2, 'MOUSE', 'Mouse'); +> update count: 2 + +SELECT * FROM TEST; +> ID LABEL LOOKUP +> -- ------ ------ +> 1 Mouse MOUSE +> 2 MOUSE Mouse +> rows: 2 + +DROP TABLE TEST; +> ok + +call 'a' regexp 'Ho.*\'; +> exception LIKE_ESCAPE_ERROR_1 + +set @t = 0; +> ok + +call set(1, 2); +> exception CAN_ONLY_ASSIGN_TO_VARIABLE_1 + +select x, set(@t, ifnull(@t, 0) + x) from system_range(1, 3); +> X SET(@T, COALESCE(@T, 0) + X) +> - ---------------------------- +> 1 1 +> 2 3 +> 3 6 +> rows: 3 + +select * from system_range(1, 2) a, +(select * from system_range(1, 2) union select * from system_range(1, 2) +union select * from system_range(1, 1)) v where a.x = v.x; +> X X +> - - +> 1 1 +> 2 2 +> rows: 2 + +create table test(id int); +> ok + +select * from ((select * from test) union (select * from test)) where id = 0; +> ID +> -- +> rows: 0 + +select * from ((test d1 inner join test d2 on d1.id = d2.id) inner join test d3 on d1.id = d3.id) inner join test d4 on d4.id = d1.id; +> ID ID ID ID +> -- -- -- -- +> rows: 0 + +drop table test; +> ok + +create table person(id bigint auto_increment, name varchar(100)); +> ok + +insert into person(name) values ('a'), ('b'), ('c'); +> update count: 3 + +select * from person order by id; +> ID NAME +> -- ---- +> 1 a +> 2 b +> 3 c +> rows (ordered): 3 + +select * from person order by id limit 2; +> ID NAME +> -- ---- +> 1 a +> 2 b +> rows (ordered): 2 + +select * from person order by id limit 2 offset 1; +> ID NAME +> -- ---- +> 2 b +> 3 c +> rows (ordered): 2 + +select * from person order by id limit 2147483647 offset 1; +> ID NAME +> -- ---- +> 2 b +> 3 c +> rows (ordered): 2 + +select * from person order by id limit 2147483647-1 offset 1; +> ID NAME +> -- ---- +> 2 b +> 3 c +> rows (ordered): 2 + +select * from person order by id limit 2147483647-1 offset 2; +> ID NAME +> -- ---- +> 3 c +> rows (ordered): 1 + +select * from person order by id limit 2147483647-2 offset 2; +> ID NAME +> -- ---- +> 3 c +> rows (ordered): 1 + +drop table person; +> ok + +CREATE TABLE TEST(ID INTEGER NOT NULL, ID2 INTEGER DEFAULT 0); +> ok + +ALTER TABLE test ALTER COLUMN ID2 RENAME TO ID; +> exception DUPLICATE_COLUMN_NAME_1 + +drop table test; +> ok + +CREATE TABLE FOO (A CHAR(10)); +> ok + +CREATE TABLE BAR AS SELECT * FROM FOO; +> ok + +select table_name, character_maximum_length from information_schema.columns where column_name = 'A'; +> TABLE_NAME CHARACTER_MAXIMUM_LENGTH +> ---------- ------------------------ +> BAR 10 +> FOO 10 +> rows: 2 + +DROP TABLE FOO, BAR; +> ok + +create table multi_pages(dir_num int, bh_id int); +> ok + +insert into multi_pages values(1, 1), (2, 2), (3, 3); +> update count: 3 + +create table b_holding(id int primary key, site varchar(255)); +> ok + +insert into b_holding values(1, 'Hello'), (2, 'Hello'), (3, 'Hello'); +> update count: 3 + +select * from (select dir_num, count(*) as cnt from multi_pages t, b_holding bh +where t.bh_id=bh.id and bh.site='Hello' group by dir_num) as x +where cnt < 1000 order by dir_num asc; +> DIR_NUM CNT +> ------- --- +> 1 1 +> 2 1 +> 3 1 +> rows (ordered): 3 + +explain select * from (select dir_num, count(*) as cnt from multi_pages t, b_holding bh +where t.bh_id=bh.id and bh.site='Hello' group by dir_num) as x +where cnt < 1000 order by dir_num asc; +>> SELECT "X"."DIR_NUM", "X"."CNT" FROM ( SELECT "DIR_NUM", COUNT(*) AS "CNT" FROM "PUBLIC"."MULTI_PAGES" "T" INNER JOIN "PUBLIC"."B_HOLDING" "BH" ON 1=1 WHERE ("BH"."SITE" = 'Hello') AND ("T"."BH_ID" = "BH"."ID") GROUP BY "DIR_NUM" ) "X" /* SELECT DIR_NUM, COUNT(*) AS CNT FROM PUBLIC.MULTI_PAGES T /* PUBLIC.MULTI_PAGES.tableScan */ INNER JOIN PUBLIC.B_HOLDING BH /* PUBLIC.PRIMARY_KEY_3: ID = T.BH_ID */ ON 1=1 WHERE (BH.SITE = 'Hello') AND (T.BH_ID = BH.ID) GROUP BY DIR_NUM HAVING COUNT(*) <= ?1: CNT < CAST(1000 AS BIGINT) */ WHERE "CNT" < CAST(1000 AS BIGINT) ORDER BY 1 + +select dir_num, count(*) as cnt from multi_pages t, b_holding bh +where t.bh_id=bh.id and bh.site='Hello' group by dir_num +having count(*) < 1000 order by dir_num asc; +> DIR_NUM CNT +> ------- --- +> 1 1 +> 2 1 +> 3 1 +> rows (ordered): 3 + +drop table multi_pages, b_holding; +> ok + +create table test(id smallint primary key); +> ok + +insert into test values(1), (2), (3); +> update count: 3 + +explain select * from test where id = 1; +>> SELECT "PUBLIC"."TEST"."ID" FROM "PUBLIC"."TEST" /* PUBLIC.PRIMARY_KEY_2: ID = 1 */ WHERE "ID" = 1 + +EXPLAIN SELECT * FROM TEST WHERE ID = (SELECT MAX(ID) FROM TEST); +>> SELECT "PUBLIC"."TEST"."ID" FROM "PUBLIC"."TEST" /* PUBLIC.PRIMARY_KEY_2: ID = (SELECT MAX(ID) FROM PUBLIC.TEST /* PUBLIC.TEST.tableScan */ /* direct lookup */) */ WHERE "ID" = (SELECT MAX("ID") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ /* direct lookup */) + +drop table test; +> ok + +create table test(id tinyint primary key); +> ok + +insert into test values(1), (2), (3); +> update count: 3 + +explain select * from test where id = 3; +>> SELECT "PUBLIC"."TEST"."ID" FROM "PUBLIC"."TEST" /* PUBLIC.PRIMARY_KEY_2: ID = 3 */ WHERE "ID" = 3 + +explain select * from test where id = 255; +>> SELECT "PUBLIC"."TEST"."ID" FROM "PUBLIC"."TEST" /* PUBLIC.PRIMARY_KEY_2: ID = 255 */ WHERE "ID" = 255 + +drop table test; +> ok + +CREATE TABLE PARENT(A INT, B INT, PRIMARY KEY(A, B)); +> ok + +CREATE TABLE CHILD(A INT, B INT, CONSTRAINT CP FOREIGN KEY(A, B) REFERENCES PARENT(A, B)); +> ok + +INSERT INTO PARENT VALUES(1, 2); +> update count: 1 + +INSERT INTO CHILD VALUES(2, NULL), (NULL, 3), (NULL, NULL), (1, 2); +> update count: 4 + +set autocommit false; +> ok + +ALTER TABLE CHILD SET REFERENTIAL_INTEGRITY FALSE; +> ok + +ALTER TABLE CHILD SET REFERENTIAL_INTEGRITY TRUE CHECK; +> ok + +set autocommit true; +> ok + +DROP TABLE CHILD, PARENT; +> ok + +CREATE TABLE TEST(BIRTH TIMESTAMP); +> ok + +INSERT INTO TEST VALUES('2006-04-03 10:20:30'), ('2006-04-03 10:20:31'), ('2006-05-05 00:00:00'), ('2006-07-03 22:30:00'), ('2006-07-03 22:31:00'); +> update count: 5 + +SELECT * FROM (SELECT CAST(BIRTH AS DATE) B +FROM TEST GROUP BY CAST(BIRTH AS DATE)) A +WHERE A.B >= '2006-05-05'; +> B +> ---------- +> 2006-05-05 +> 2006-07-03 +> rows: 2 + +DROP TABLE TEST; +> ok + +CREATE TABLE Parent(ID INT PRIMARY KEY, Name VARCHAR); +> ok + +CREATE TABLE Child(ID INT); +> ok + +ALTER TABLE Child ADD FOREIGN KEY(ID) REFERENCES Parent(ID); +> ok + +INSERT INTO Parent VALUES(1, '0'), (2, '0'), (3, '0'); +> update count: 3 + +INSERT INTO Child VALUES(1); +> update count: 1 + +ALTER TABLE Parent ALTER COLUMN Name BOOLEAN NULL; +> ok + +DELETE FROM Parent WHERE ID=3; +> update count: 1 + +DROP TABLE Parent, Child; +> ok + +set autocommit false; +> ok + +CREATE TABLE A(ID INT PRIMARY KEY, SK INT); +> ok + +ALTER TABLE A ADD CONSTRAINT AC FOREIGN KEY(SK) REFERENCES A(ID); +> ok + +INSERT INTO A VALUES(1, 1); +> update count: 1 + +INSERT INTO A VALUES(-2, NULL); +> update count: 1 + +ALTER TABLE A SET REFERENTIAL_INTEGRITY FALSE; +> ok + +ALTER TABLE A SET REFERENTIAL_INTEGRITY TRUE CHECK; +> ok + +ALTER TABLE A SET REFERENTIAL_INTEGRITY FALSE; +> ok + +INSERT INTO A VALUES(2, 3); +> update count: 1 + +ALTER TABLE A SET REFERENTIAL_INTEGRITY TRUE; +> ok + +ALTER TABLE A SET REFERENTIAL_INTEGRITY FALSE; +> ok + +ALTER TABLE A SET REFERENTIAL_INTEGRITY TRUE CHECK; +> exception REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1 + +DROP TABLE A; +> ok + +set autocommit true; +> ok + +CREATE TABLE PARENT(ID INT PRIMARY KEY); +> ok + +CREATE TABLE CHILD(PID INT); +> ok + +INSERT INTO PARENT VALUES(1); +> update count: 1 + +INSERT INTO CHILD VALUES(2); +> update count: 1 + +ALTER TABLE CHILD ADD CONSTRAINT CP FOREIGN KEY(PID) REFERENCES PARENT(ID); +> exception REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1 + +UPDATE CHILD SET PID=1; +> update count: 1 + +ALTER TABLE CHILD ADD CONSTRAINT CP FOREIGN KEY(PID) REFERENCES PARENT(ID); +> ok + +DROP TABLE CHILD, PARENT; +> ok + +CREATE TABLE A(ID INT PRIMARY KEY, SK INT); +> ok + +INSERT INTO A VALUES(1, 2); +> update count: 1 + +ALTER TABLE A ADD CONSTRAINT AC FOREIGN KEY(SK) REFERENCES A(ID); +> exception REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1 + +DROP TABLE A; +> ok + +CREATE TABLE TEST(ID INT); +> ok + +INSERT INTO TEST VALUES(0), (1), (100); +> update count: 3 + +ALTER TABLE TEST ADD CONSTRAINT T CHECK ID<100; +> exception CHECK_CONSTRAINT_VIOLATED_1 + +UPDATE TEST SET ID=20 WHERE ID=100; +> update count: 1 + +ALTER TABLE TEST ADD CONSTRAINT T CHECK ID<100; +> ok + +DROP TABLE TEST; +> ok + +create table test(id int); +> ok + +set autocommit false; +> ok + +insert into test values(1); +> update count: 1 + +prepare commit tx1; +> ok + +commit transaction tx1; +> ok + +rollback; +> ok + +select * from test; +> ID +> -- +> 1 +> rows: 1 + +drop table test; +> ok + +set autocommit true; +> ok + +SELECT 'Hello' ~ 'He.*' T1, 'HELLO' ~ 'He.*' F2, CAST('HELLO' AS VARCHAR_IGNORECASE) ~ 'He.*' T3; +> T1 F2 T3 +> ---- ----- ---- +> TRUE FALSE TRUE +> rows: 1 + +SELECT 'Hello' ~* 'He.*' T1, 'HELLO' ~* 'He.*' T2, 'hallo' ~* 'He.*' F3; +> T1 T2 F3 +> ---- ---- ----- +> TRUE TRUE FALSE +> rows: 1 + +SELECT 'Hello' !~* 'Ho.*' T1, 'HELLO' !~* 'He.*' F2, 'hallo' !~* 'Ha.*' F3; +> T1 F2 F3 +> ---- ----- ----- +> TRUE FALSE FALSE +> rows: 1 + +create table test(parent int primary key, child int, foreign key(child) references (parent)); +> ok + +insert into test values(1, 1); +> update count: 1 + +insert into test values(2, 3); +> exception REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1 + +set autocommit false; +> ok + +set referential_integrity false; +> ok + +insert into test values(4, 4); +> update count: 1 + +insert into test values(5, 6); +> update count: 1 + +set referential_integrity true; +> ok + +insert into test values(7, 7), (8, 9); +> exception REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1 + +set autocommit true; +> ok + +drop table test; +> ok + +create table test as select 1, space(10) from dual where 1=0 union all select x, cast(space(100) as varchar(101)) d from system_range(1, 100); +> ok + +drop table test; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +INSERT INTO TEST VALUES(1, 'Hello'), (-1, '-1'); +> update count: 2 + +select * from test where name = -1 and name = id; +> ID NAME +> -- ---- +> -1 -1 +> rows: 1 + +explain select * from test where name = -1 and name = id; +>> SELECT "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."NAME" FROM "PUBLIC"."TEST" /* PUBLIC.PRIMARY_KEY_2: ID = -1 */ WHERE ("NAME" = -1) AND ("NAME" = "ID") + +DROP TABLE TEST; +> ok + +select * from system_range(1, 2) where x=x+1 and x=1; +> X +> - +> rows: 0 + +CREATE TABLE A as select 6 a; +> ok + +CREATE TABLE B(B INT PRIMARY KEY); +> ok + +CREATE VIEW V(V) AS (SELECT A FROM A UNION SELECT B FROM B); +> ok + +create table C as select * from table(c int = (0,6)); +> ok + +select * from V, C where V.V = C.C; +> V C +> - - +> 6 6 +> rows: 1 + +drop table A, B, C, V cascade; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, FLAG BOOLEAN, NAME VARCHAR); +> ok + +CREATE INDEX IDX_FLAG ON TEST(FLAG, NAME); +> ok + +INSERT INTO TEST VALUES(1, TRUE, 'Hello'), (2, FALSE, 'World'); +> update count: 2 + +EXPLAIN SELECT * FROM TEST WHERE FLAG; +>> SELECT "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."FLAG", "PUBLIC"."TEST"."NAME" FROM "PUBLIC"."TEST" /* PUBLIC.IDX_FLAG: FLAG = TRUE */ WHERE "FLAG" + +EXPLAIN SELECT * FROM TEST WHERE FLAG AND NAME>'I'; +>> SELECT "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."FLAG", "PUBLIC"."TEST"."NAME" FROM "PUBLIC"."TEST" /* PUBLIC.IDX_FLAG: FLAG = TRUE AND NAME > 'I' */ WHERE "FLAG" AND ("NAME" > 'I') + +DROP TABLE TEST; +> ok + +CREATE TABLE test_table (first_col varchar(20), second_col integer); +> ok + +insert into test_table values('a', 10), ('a', 4), ('b', 30), ('b', 3); +> update count: 4 + +CREATE VIEW test_view AS SELECT first_col AS renamed_col, MIN(second_col) AS also_renamed FROM test_table GROUP BY first_col; +> ok + +SELECT * FROM test_view WHERE renamed_col = 'a'; +> RENAMED_COL ALSO_RENAMED +> ----------- ------------ +> a 4 +> rows: 1 + +drop view test_view; +> ok + +drop table test_table; +> ok + +create table test(id int); +> ok + +explain select id+1 a from test group by id+1; +>> SELECT "ID" + 1 AS "A" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ GROUP BY "ID" + 1 + +drop table test; +> ok + +set autocommit off; +> ok + +set schema_search_path = public, information_schema; +> ok + +select table_name from tables where 1=0; +> TABLE_NAME +> ---------- +> rows: 0 + +set schema_search_path = public; +> ok + +set autocommit on; +> ok + +create table script.public.x(a int); +> ok + +select * from script.PUBLIC.x; +> A +> - +> rows: 0 + +create index script.public.idx on script.public.x(a); +> ok + +drop table script.public.x; +> ok + +create table d(d double, r real); +> ok + +insert into d(d, d, r) values(1.1234567890123456789, 1.1234567890123456789, 3); +> exception DUPLICATE_COLUMN_NAME_1 + +insert into d values(1.1234567890123456789, 1.1234567890123456789); +> update count: 1 + +select r+d, r+r, d+d from d; +> R + D R + R D + D +> ----------------- --------- ------------------ +> 2.246913624759111 2.2469137 2.2469135780246914 +> rows: 1 + +drop table d; +> ok + +create table test(id int, c char(5), v varchar(5)); +> ok + +insert into test set id = 1, c = 'a', v = 'a'; +> update count: 1 + +insert into test set id = 2, c = 'a ', v = 'a '; +> update count: 1 + +insert into test set id = 3, c = 'abcde ', v = 'abcde'; +> update count: 1 + +select distinct length(c) from test order by length(c); +> CHAR_LENGTH(C) +> -------------- +> 5 +> rows (ordered): 1 + +select id, c, v, length(c), length(v) from test order by id; +> ID C V CHAR_LENGTH(C) CHAR_LENGTH(V) +> -- ----- ----- -------------- -------------- +> 1 a a 5 1 +> 2 a a 5 2 +> 3 abcde abcde 5 5 +> rows (ordered): 3 + +select id from test where c='a' order by id; +> ID +> -- +> 1 +> 2 +> rows (ordered): 2 + +select id from test where c='a ' order by id; +> ID +> -- +> 1 +> 2 +> rows (ordered): 2 + +select id from test where c=v order by id; +> ID +> -- +> 1 +> 2 +> 3 +> rows (ordered): 3 + +drop table test; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255), C INT); +> ok + +INSERT INTO TEST VALUES(1, '10', NULL), (2, '0', NULL); +> update count: 2 + +SELECT LEAST(ID, C, NAME), GREATEST(ID, C, NAME), LEAST(NULL, C), GREATEST(NULL, NULL), ID FROM TEST ORDER BY ID; +> LEAST(ID, C, NAME) GREATEST(ID, C, NAME) LEAST(NULL, C) CAST(NULL AS CHARACTER VARYING) ID +> ------------------ --------------------- -------------- ------------------------------- -- +> 1 10 null null 1 +> 0 2 null null 2 +> rows (ordered): 2 + +DROP TABLE TEST; +> ok + +create table people (family varchar(1) not null, person varchar(1) not null); +> ok + +create table cars (family varchar(1) not null, car varchar(1) not null); +> ok + +insert into people values(1, 1), (2, 1), (2, 2), (3, 1), (5, 1); +> update count: 5 + +insert into cars values(2, 1), (2, 2), (3, 1), (3, 2), (3, 3), (4, 1); +> update count: 6 + +select family, (select count(car) from cars where cars.family = people.family) as x +from people group by family order by family; +> FAMILY X +> ------ - +> 1 0 +> 2 2 +> 3 3 +> 5 0 +> rows (ordered): 4 + +drop table people, cars; +> ok + +select (1, 2); +> ROW (1, 2) +> ---------- +> ROW (1, 2) +> rows: 1 + +select * from (select 1), (select 2); +> 1 2 +> - - +> 1 2 +> rows: 1 + +create table t1(c1 int, c2 int); +> ok + +create table t2(c1 int, c2 int); +> ok + +insert into t1 values(1, null), (2, 2), (3, 3); +> update count: 3 + +insert into t2 values(1, 1), (1, 2), (2, null), (3, 3); +> update count: 4 + +select * from t2 where c1 not in(select c2 from t1); +> C1 C2 +> -- -- +> rows: 0 + +select * from t2 where c1 not in(null, 2, 3); +> C1 C2 +> -- -- +> rows: 0 + +select * from t1 where c2 not in(select c1 from t2); +> C1 C2 +> -- -- +> rows: 0 + +select * from t1 where not exists(select * from t2 where t1.c2=t2.c1); +> C1 C2 +> -- ---- +> 1 null +> rows: 1 + +drop table t1; +> ok + +drop table t2; +> ok + +CREATE TABLE test (family_name VARCHAR_IGNORECASE(63) NOT NULL); +> ok + +INSERT INTO test VALUES('Smith'), ('de Smith'), ('el Smith'), ('von Smith'); +> update count: 4 + +SELECT * FROM test WHERE family_name IN ('de Smith', 'Smith'); +> FAMILY_NAME +> ----------- +> Smith +> de Smith +> rows: 2 + +SELECT * FROM test WHERE family_name BETWEEN 'D' AND 'T'; +> FAMILY_NAME +> ----------- +> Smith +> de Smith +> el Smith +> rows: 3 + +CREATE INDEX family_name ON test(family_name); +> ok + +SELECT * FROM test WHERE family_name IN ('de Smith', 'Smith'); +> FAMILY_NAME +> ----------- +> Smith +> de Smith +> rows: 2 + +drop table test; +> ok + +create memory table test(id int primary key, data clob); +> ok + +insert into test values(1, 'abc' || space(20)); +> update count: 1 + +script nopasswords nosettings noversion blocksize 10; +> SCRIPT +> ---------------------------------------------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "ID" INTEGER NOT NULL, "DATA" CHARACTER LARGE OBJECT ); +> ALTER TABLE "PUBLIC"."TEST" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_2" PRIMARY KEY("ID"); +> -- 1 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> CREATE CACHED LOCAL TEMPORARY TABLE IF NOT EXISTS SYSTEM_LOB_STREAM(ID INT NOT NULL, PART INT NOT NULL, CDATA VARCHAR, BDATA VARBINARY); +> ALTER TABLE SYSTEM_LOB_STREAM ADD CONSTRAINT SYSTEM_LOB_STREAM_PRIMARY_KEY PRIMARY KEY(ID, PART); +> CREATE ALIAS IF NOT EXISTS SYSTEM_COMBINE_CLOB FOR 'org.h2.command.dml.ScriptCommand.combineClob'; +> CREATE ALIAS IF NOT EXISTS SYSTEM_COMBINE_BLOB FOR 'org.h2.command.dml.ScriptCommand.combineBlob'; +> INSERT INTO SYSTEM_LOB_STREAM VALUES(0, 0, 'abc ', NULL); +> INSERT INTO SYSTEM_LOB_STREAM VALUES(0, 1, ' ', NULL); +> INSERT INTO SYSTEM_LOB_STREAM VALUES(0, 2, ' ', NULL); +> INSERT INTO "PUBLIC"."TEST" VALUES (1, SYSTEM_COMBINE_CLOB(0)); +> DROP TABLE IF EXISTS SYSTEM_LOB_STREAM; +> DROP ALIAS IF EXISTS SYSTEM_COMBINE_CLOB; +> DROP ALIAS IF EXISTS SYSTEM_COMBINE_BLOB; +> rows (ordered): 15 + +drop table test; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +INSERT INTO TEST VALUES(1, 'Hello'), (2, 'World'); +> update count: 2 + +SELECT DISTINCT * FROM TEST ORDER BY ID; +> ID NAME +> -- ----- +> 1 Hello +> 2 World +> rows (ordered): 2 + +DROP TABLE TEST; +> ok + +create sequence main_seq; +> ok + +create schema "TestSchema"; +> ok + +create sequence "TestSchema"."TestSeq"; +> ok + +create sequence "TestSchema"."ABC"; +> ok + +select currval('main_seq'), currval('TestSchema', 'TestSeq'); +> exception CURRENT_SEQUENCE_VALUE_IS_NOT_DEFINED_IN_SESSION_1 + +select nextval('TestSchema', 'ABC'); +>> 1 + +set autocommit off; +> ok + +set schema "TestSchema"; +> ok + +select nextval('abc'), currval('Abc'), nextval('TestSchema', 'ABC'); +> NEXTVAL('abc') CURRVAL('Abc') NEXTVAL('TestSchema', 'ABC') +> -------------- -------------- ---------------------------- +> 2 2 3 +> rows: 1 + +set schema public; +> ok + +drop schema "TestSchema" cascade; +> ok + +drop sequence main_seq; +> ok + +create sequence "test"; +> ok + +select nextval('test'); +> NEXTVAL('test') +> --------------- +> 1 +> rows: 1 + +drop sequence "test"; +> ok + +set autocommit on; +> ok + +CREATE TABLE parent(id int PRIMARY KEY); +> ok + +CREATE TABLE child(parentid int REFERENCES parent); +> ok + +TABLE INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS; +> CONSTRAINT_CATALOG CONSTRAINT_SCHEMA CONSTRAINT_NAME UNIQUE_CONSTRAINT_CATALOG UNIQUE_CONSTRAINT_SCHEMA UNIQUE_CONSTRAINT_NAME MATCH_OPTION UPDATE_RULE DELETE_RULE +> ------------------ ----------------- --------------- ------------------------- ------------------------ ---------------------- ------------ ----------- ----------- +> SCRIPT PUBLIC CONSTRAINT_3 SCRIPT PUBLIC CONSTRAINT_8 NONE RESTRICT RESTRICT +> rows: 1 + +ALTER TABLE parent ADD COLUMN name varchar; +> ok + +TABLE INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS; +> CONSTRAINT_CATALOG CONSTRAINT_SCHEMA CONSTRAINT_NAME UNIQUE_CONSTRAINT_CATALOG UNIQUE_CONSTRAINT_SCHEMA UNIQUE_CONSTRAINT_NAME MATCH_OPTION UPDATE_RULE DELETE_RULE +> ------------------ ----------------- --------------- ------------------------- ------------------------ ---------------------- ------------ ----------- ----------- +> SCRIPT PUBLIC CONSTRAINT_3 SCRIPT PUBLIC CONSTRAINT_8 NONE RESTRICT RESTRICT +> rows: 1 + +drop table parent, child; +> ok + +create table test(id int); +> ok + +create schema TEST_SCHEMA; +> ok + +set autocommit false; +> ok + +set schema TEST_SCHEMA; +> ok + +create table test(id int, name varchar); +> ok + +explain select * from test; +>> SELECT "TEST_SCHEMA"."TEST"."ID", "TEST_SCHEMA"."TEST"."NAME" FROM "TEST_SCHEMA"."TEST" /* TEST_SCHEMA.TEST.tableScan */ + +explain select * from public.test; +>> SELECT "PUBLIC"."TEST"."ID" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +drop schema TEST_SCHEMA cascade; +> ok + +set autocommit true; +> ok + +set schema public; +> ok + +select * from test; +> ID +> -- +> rows: 0 + +drop table test; +> ok + +create table content(thread_id int, parent_id int); +> ok + +alter table content add constraint content_parent_id check (parent_id = thread_id) or (parent_id is null) or ( parent_id in (select thread_id from content)); +> ok + +create index content_thread_id ON content(thread_id); +> ok + +insert into content values(0, 0), (0, 0); +> update count: 2 + +insert into content values(0, 1); +> exception CHECK_CONSTRAINT_VIOLATED_1 + +insert into content values(1, 1), (2, 2); +> update count: 2 + +insert into content values(2, 1); +> update count: 1 + +insert into content values(2, 3); +> exception CHECK_CONSTRAINT_VIOLATED_1 + +drop table content; +> ok + +select x/10 y from system_range(1, 100) group by x/10; +> Y +> -- +> 0 +> 1 +> 10 +> 2 +> 3 +> 4 +> 5 +> 6 +> 7 +> 8 +> 9 +> rows: 11 + +select timestamp '2001-02-03T10:30:33'; +> TIMESTAMP '2001-02-03 10:30:33' +> ------------------------------- +> 2001-02-03 10:30:33 +> rows: 1 + +create table test(id int); +> ok + +insert into test (select x from system_range(1, 100)); +> update count: 100 + +select id/1000 from test group by id/1000; +> ID / 1000 +> --------- +> 0 +> rows: 1 + +select id/(10*100) from test group by id/(10*100); +> ID / 1000 +> --------- +> 0 +> rows: 1 + +select id/1000 from test group by id/100; +> exception MUST_GROUP_BY_COLUMN_1 + +drop table test; +> ok + +select (x/10000) from system_range(10, 20) group by (x/10000); +> X / 10000 +> --------- +> 0 +> rows: 1 + +select sum(x), (x/10) from system_range(10, 100) group by (x/10); +> SUM(X) X / 10 +> ------ ------ +> 100 10 +> 145 1 +> 245 2 +> 345 3 +> 445 4 +> 545 5 +> 645 6 +> 745 7 +> 845 8 +> 945 9 +> rows: 10 + +CREATE FORCE VIEW ADDRESS_VIEW AS SELECT * FROM ADDRESS; +> ok + +CREATE memory TABLE ADDRESS(ID INT); +> ok + +alter view address_view recompile; +> ok + +alter view if exists address_view recompile; +> ok + +alter view if exists does_not_exist recompile; +> ok + +select * from ADDRESS_VIEW; +> ID +> -- +> rows: 0 + +drop view address_view; +> ok + +drop table address; +> ok + +CREATE ALIAS PARSE_INT2 FOR "java.lang.Integer.parseInt(java.lang.String, int)"; +> ok + +select min(SUBSTRING(random_uuid(), 15,1)='4') from system_range(1, 10); +> MIN(SUBSTRING(RANDOM_UUID() FROM 15 FOR 1) = '4') +> ------------------------------------------------- +> TRUE +> rows: 1 + +select min(8=bitand(12, PARSE_INT2(SUBSTRING(random_uuid(), 20,1), 16))) from system_range(1, 10); +> MIN(8 = BITAND(12, PUBLIC.PARSE_INT2(SUBSTRING(RANDOM_UUID() FROM 20 FOR 1), 16))) +> ---------------------------------------------------------------------------------- +> TRUE +> rows: 1 + +select BITGET(x, 0) AS IS_SET from system_range(1, 2); +> IS_SET +> ------ +> FALSE +> TRUE +> rows: 2 + +drop alias PARSE_INT2; +> ok + +create memory table test(name varchar check(name = upper(name))); +> ok + +insert into test values(null); +> update count: 1 + +insert into test values('aa'); +> exception CHECK_CONSTRAINT_VIOLATED_1 + +insert into test values('AA'); +> update count: 1 + +script nodata nopasswords nosettings noversion; +> SCRIPT +> --------------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "NAME" CHARACTER VARYING ); +> -- 2 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> ALTER TABLE "PUBLIC"."TEST" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_2" CHECK("NAME" = UPPER("NAME")) NOCHECK; +> rows (ordered): 4 + +drop table test; +> ok + +create domain email as varchar(200) check (position('@' in value) > 1); +> ok + +create domain gmail as email default '@gmail.com' check (position('gmail' in value) > 1); +> ok + +create memory table address(id int primary key, name email, name2 gmail); +> ok + +insert into address(id, name, name2) values(1, 'test@abc', 'test@gmail.com'); +> update count: 1 + +insert into address(id, name, name2) values(2, 'test@abc', 'test@acme'); +> exception CHECK_CONSTRAINT_VIOLATED_1 + +@reconnect + +insert into address(id, name, name2) values(3, 'test_abc', 'test@gmail'); +> exception CHECK_CONSTRAINT_VIOLATED_1 + +insert into address2(name) values('test@abc'); +> exception TABLE_OR_VIEW_NOT_FOUND_1 + +CREATE DOMAIN STRING AS VARCHAR(255) DEFAULT ''; +> ok + +CREATE DOMAIN IF NOT EXISTS STRING AS VARCHAR(255) DEFAULT ''; +> ok + +CREATE DOMAIN STRING1 AS VARCHAR; +> ok + +CREATE DOMAIN STRING2 AS VARCHAR DEFAULT ''; +> ok + +create domain string_x as string2; +> ok + +create memory table test(a string, b string1, c string2); +> ok + +insert into test(b) values('x'); +> update count: 1 + +select * from test; +> A B C +> - - ------- +> x +> rows: 1 + +select DOMAIN_NAME, DOMAIN_DEFAULT, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, PARENT_DOMAIN_NAME, REMARKS from information_schema.domains; +> DOMAIN_NAME DOMAIN_DEFAULT DATA_TYPE CHARACTER_MAXIMUM_LENGTH PARENT_DOMAIN_NAME REMARKS +> ----------- -------------- ----------------- ------------------------ ------------------ ------- +> 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 1000000000 null null +> STRING2 '' CHARACTER VARYING 1000000000 null null +> STRING_X null CHARACTER VARYING 1000000000 STRING2 null +> rows: 6 + +script nodata nopasswords nosettings noversion; +> SCRIPT +> ------------------------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE DOMAIN "PUBLIC"."EMAIL" AS CHARACTER VARYING(200); +> CREATE DOMAIN "PUBLIC"."STRING" AS CHARACTER VARYING(255) DEFAULT ''; +> CREATE DOMAIN "PUBLIC"."STRING1" AS CHARACTER VARYING; +> CREATE DOMAIN "PUBLIC"."STRING2" AS CHARACTER VARYING DEFAULT ''; +> CREATE DOMAIN "PUBLIC"."GMAIL" AS "PUBLIC"."EMAIL" DEFAULT '@gmail.com'; +> CREATE DOMAIN "PUBLIC"."STRING_X" AS "PUBLIC"."STRING2"; +> CREATE MEMORY TABLE "PUBLIC"."ADDRESS"( "ID" INTEGER NOT NULL, "NAME" "PUBLIC"."EMAIL", "NAME2" "PUBLIC"."GMAIL" ); +> ALTER TABLE "PUBLIC"."ADDRESS" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_E" PRIMARY KEY("ID"); +> -- 1 +/- SELECT COUNT(*) FROM PUBLIC.ADDRESS; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "A" "PUBLIC"."STRING", "B" "PUBLIC"."STRING1", "C" "PUBLIC"."STRING2" ); +> -- 1 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> ALTER DOMAIN "PUBLIC"."EMAIL" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_3" CHECK(LOCATE('@', VALUE) > 1) NOCHECK; +> ALTER DOMAIN "PUBLIC"."GMAIL" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_4" CHECK(LOCATE('gmail', VALUE) > 1) NOCHECK; +> rows (ordered): 14 + +drop table test; +> ok + +drop domain string; +> ok + +drop domain string1; +> ok + +drop domain string2 cascade; +> ok + +drop domain string_x; +> ok + +drop table address; +> ok + +drop domain email cascade; +> ok + +drop domain gmail; +> ok + +create force view address_view as select * from address; +> ok + +create table address(id identity, name varchar check instr(value, '@') > 1); +> exception SYNTAX_ERROR_2 + +create table address(id identity, name varchar check instr(name, '@') > 1); +> ok + +drop view if exists address_view; +> ok + +drop table address; +> ok + +create memory table a(k10 blob(10k), m20 blob(20m), g30 clob(30g)); +> ok + +SCRIPT NODATA NOPASSWORDS NOSETTINGS NOVERSION DROP; +> SCRIPT +> ----------------------------------------------------------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> DROP TABLE IF EXISTS "PUBLIC"."A" CASCADE; +> CREATE MEMORY TABLE "PUBLIC"."A"( "K10" BINARY LARGE OBJECT(10240), "M20" BINARY LARGE OBJECT(20971520), "G30" CHARACTER LARGE OBJECT(32212254720) ); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.A; +> rows (ordered): 4 + +create table b(); +> ok + +create table c(); +> ok + +drop table information_schema.columns; +> exception CANNOT_DROP_TABLE_1 + +create table columns as select * from information_schema.columns; +> ok + +create table tables as select * from information_schema.tables where false; +> ok + +create table dual2 as select 1 from dual; +> ok + +select * from dual2; +> 1 +> - +> 1 +> rows: 1 + +drop table dual2, columns, tables; +> ok + +drop table a, a; +> ok + +drop table b, c; +> ok + +CREATE TABLE A (ID_A int primary key); +> ok + +CREATE TABLE B (ID_B int primary key); +> ok + +CREATE TABLE C (ID_C int primary key); +> ok + +insert into A values (1); +> update count: 1 + +insert into A values (2); +> update count: 1 + +insert into B values (1); +> update count: 1 + +insert into C values (1); +> update count: 1 + +SELECT * FROM C WHERE NOT EXISTS ((SELECT ID_A FROM A) EXCEPT (SELECT ID_B FROM B)); +> ID_C +> ---- +> rows: 0 + +(SELECT ID_A FROM A) EXCEPT (SELECT ID_B FROM B); +> ID_A +> ---- +> 2 +> rows: 1 + +drop table a; +> ok + +drop table b; +> ok + +drop table c; +> ok + +CREATE TABLE X (ID INTEGER PRIMARY KEY); +> ok + +insert into x values(0), (1), (10); +> update count: 3 + +SELECT t1.ID, (SELECT t1.id || ':' || AVG(t2.ID) FROM X t2) AS col2 FROM X t1; +> ID COL2 +> -- --------------------- +> 0 0:3.6666666666666665 +> 1 1:3.6666666666666665 +> 10 10:3.6666666666666665 +> rows: 3 + +drop table x; +> ok + +create table test(id int primary key, name varchar); +> ok + +insert into test values(rownum, '11'), (rownum, '22'), (rownum, '33'); +> update count: 3 + +select * from test order by id; +> ID NAME +> -- ---- +> 1 11 +> 2 22 +> 3 33 +> rows (ordered): 3 + +select rownum, (select count(*) from test) as col2, rownum from test; +> ROWNUM() COL2 ROWNUM() +> -------- ---- -------- +> 1 3 1 +> 2 3 2 +> 3 3 3 +> rows: 3 + +delete from test t0 where rownum<2; +> update count: 1 + +select rownum, * from (select * from test where id>1 order by id desc); +> ROWNUM() ID NAME +> -------- -- ---- +> 1 3 33 +> 2 2 22 +> rows: 2 + +update test set name='x' where rownum<2; +> update count: 1 + +select * from test; +> ID NAME +> -- ---- +> 2 x +> 3 33 +> rows: 2 + +merge into test values(2, 'r' || rownum), (10, rownum), (11, rownum); +> update count: 3 + +select * from test; +> ID NAME +> -- ---- +> 10 2 +> 11 3 +> 2 r1 +> 3 33 +> rows: 4 + +call rownum; +> ROWNUM() +> -------- +> 1 +> rows: 1 + +drop table test; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +create index idx_test_name on test(name); +> ok + +INSERT INTO TEST VALUES(1, 'Hello'); +> update count: 1 + +INSERT INTO TEST VALUES(2, 'World'); +> update count: 1 + +set ignorecase true; +> ok + +CREATE TABLE TEST2(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +create unique index idx_test2_name on test2(name); +> ok + +INSERT INTO TEST2 VALUES(1, 'hElLo'); +> update count: 1 + +INSERT INTO TEST2 VALUES(2, 'World'); +> update count: 1 + +INSERT INTO TEST2 VALUES(3, 'WoRlD'); +> exception DUPLICATE_KEY_1 + +drop index idx_test2_name; +> ok + +select * from test where name='HELLO'; +> ID NAME +> -- ---- +> rows: 0 + +select * from test2 where name='HELLO'; +> ID NAME +> -- ----- +> 1 hElLo +> rows: 1 + +select * from test where name like 'HELLO'; +> ID NAME +> -- ---- +> rows: 0 + +select * from test2 where name like 'HELLO'; +> ID NAME +> -- ----- +> 1 hElLo +> rows: 1 + +explain plan for select * from test2, test where test2.name = test.name; +>> SELECT "PUBLIC"."TEST2"."ID", "PUBLIC"."TEST2"."NAME", "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."NAME" FROM "PUBLIC"."TEST2" /* PUBLIC.TEST2.tableScan */ INNER JOIN "PUBLIC"."TEST" /* PUBLIC.IDX_TEST_NAME */ ON 1=1 WHERE "TEST2"."NAME" = "TEST"."NAME" + +select * from test2, test where test2.name = test.name; +> ID NAME ID NAME +> -- ----- -- ----- +> 1 hElLo 1 Hello +> 2 World 2 World +> rows: 2 + +explain plan for select * from test, test2 where test2.name = test.name; +>> SELECT "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."NAME", "PUBLIC"."TEST2"."ID", "PUBLIC"."TEST2"."NAME" FROM "PUBLIC"."TEST2" /* PUBLIC.TEST2.tableScan */ INNER JOIN "PUBLIC"."TEST" /* PUBLIC.IDX_TEST_NAME */ ON 1=1 WHERE "TEST2"."NAME" = "TEST"."NAME" + +select * from test, test2 where test2.name = test.name; +> ID NAME ID NAME +> -- ----- -- ----- +> 1 Hello 1 hElLo +> 2 World 2 World +> rows: 2 + +create index idx_test2_name on test2(name); +> ok + +explain plan for select * from test2, test where test2.name = test.name; +>> SELECT "PUBLIC"."TEST2"."ID", "PUBLIC"."TEST2"."NAME", "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."NAME" FROM "PUBLIC"."TEST" /* PUBLIC.IDX_TEST_NAME */ INNER JOIN "PUBLIC"."TEST2" /* PUBLIC.IDX_TEST2_NAME: NAME = TEST.NAME */ ON 1=1 WHERE "TEST2"."NAME" = "TEST"."NAME" + +select * from test2, test where test2.name = test.name; +> ID NAME ID NAME +> -- ----- -- ----- +> 1 hElLo 1 Hello +> 2 World 2 World +> rows: 2 + +explain plan for select * from test, test2 where test2.name = test.name; +>> SELECT "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."NAME", "PUBLIC"."TEST2"."ID", "PUBLIC"."TEST2"."NAME" FROM "PUBLIC"."TEST" /* PUBLIC.IDX_TEST_NAME */ INNER JOIN "PUBLIC"."TEST2" /* PUBLIC.IDX_TEST2_NAME: NAME = TEST.NAME */ ON 1=1 WHERE "TEST2"."NAME" = "TEST"."NAME" + +select * from test, test2 where test2.name = test.name; +> ID NAME ID NAME +> -- ----- -- ----- +> 1 Hello 1 hElLo +> 2 World 2 World +> rows: 2 + +DROP TABLE IF EXISTS TEST; +> ok + +DROP TABLE IF EXISTS TEST2; +> ok + +set ignorecase false; +> ok + +create table test(f1 varchar, f2 varchar); +> ok + +insert into test values('abc','222'); +> update count: 1 + +insert into test values('abc','111'); +> update count: 1 + +insert into test values('abc','333'); +> update count: 1 + +SELECT t.f1, t.f2 FROM test t ORDER BY t.f2; +> F1 F2 +> --- --- +> abc 111 +> abc 222 +> abc 333 +> rows (ordered): 3 + +SELECT t1.f1, t1.f2, t2.f1, t2.f2 FROM test t1, test t2 ORDER BY t2.f2, t1.f2; +> F1 F2 F1 F2 +> --- --- --- --- +> abc 111 abc 111 +> abc 222 abc 111 +> abc 333 abc 111 +> abc 111 abc 222 +> abc 222 abc 222 +> abc 333 abc 222 +> abc 111 abc 333 +> abc 222 abc 333 +> abc 333 abc 333 +> rows (ordered): 9 + +drop table if exists test; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +INSERT INTO TEST VALUES(1, 'Hello'); +> update count: 1 + +explain select t0.id, t1.id from test t0, test t1 order by t0.id, t1.id; +>> SELECT "T0"."ID", "T1"."ID" FROM "PUBLIC"."TEST" "T0" /* PUBLIC.TEST.tableScan */ INNER JOIN "PUBLIC"."TEST" "T1" /* PUBLIC.TEST.tableScan */ ON 1=1 ORDER BY 1, 2 + +INSERT INTO TEST VALUES(2, 'World'); +> update count: 1 + +SELECT id, sum(id) FROM test GROUP BY id ORDER BY id*sum(id); +> ID SUM(ID) +> -- ------- +> 1 1 +> 2 2 +> rows (ordered): 2 + +select * +from test t1 +inner join test t2 on t2.id=t1.id +inner join test t3 on t3.id=t2.id +where exists (select 1 from test t4 where t2.id=t4.id); +> ID NAME ID NAME ID NAME +> -- ----- -- ----- -- ----- +> 1 Hello 1 Hello 1 Hello +> 2 World 2 World 2 World +> rows: 2 + +explain select * from test t1 where id in(select id from test t2 where t1.id=t2.id); +>> SELECT "T1"."ID", "T1"."NAME" FROM "PUBLIC"."TEST" "T1" /* PUBLIC.TEST.tableScan */ WHERE "ID" IN( SELECT DISTINCT "ID" FROM "PUBLIC"."TEST" "T2" /* PUBLIC.PRIMARY_KEY_2: ID = T1.ID */ WHERE "T1"."ID" = "T2"."ID") + +select * from test t1 where id in(select id from test t2 where t1.id=t2.id); +> ID NAME +> -- ----- +> 1 Hello +> 2 World +> rows: 2 + +explain select * from test t1 where id in(id, id+1); +>> SELECT "T1"."ID", "T1"."NAME" FROM "PUBLIC"."TEST" "T1" /* PUBLIC.TEST.tableScan */ WHERE "ID" IN("ID", "ID" + 1) + +select * from test t1 where id in(id, id+1); +> ID NAME +> -- ----- +> 1 Hello +> 2 World +> rows: 2 + +explain select * from test t1 where id in(id); +>> SELECT "T1"."ID", "T1"."NAME" FROM "PUBLIC"."TEST" "T1" /* PUBLIC.TEST.tableScan */ WHERE "ID" = "ID" + +select * from test t1 where id in(id); +> ID NAME +> -- ----- +> 1 Hello +> 2 World +> rows: 2 + +explain select * from test t1 where id in(select id from test); +>> SELECT "T1"."ID", "T1"."NAME" FROM "PUBLIC"."TEST" "T1" /* PUBLIC.PRIMARY_KEY_2: ID IN(SELECT DISTINCT ID FROM PUBLIC.TEST /* PUBLIC.TEST.tableScan */) */ WHERE "ID" IN( SELECT DISTINCT "ID" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */) + +select * from test t1 where id in(select id from test); +> ID NAME +> -- ----- +> 1 Hello +> 2 World +> rows: 2 + +explain select * from test t1 where id in(1, select max(id) from test); +>> SELECT "T1"."ID", "T1"."NAME" FROM "PUBLIC"."TEST" "T1" /* PUBLIC.PRIMARY_KEY_2: ID IN(1, (SELECT MAX(ID) FROM PUBLIC.TEST /* PUBLIC.TEST.tableScan */ /* direct lookup */)) */ WHERE "ID" IN(1, (SELECT MAX("ID") FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ /* direct lookup */)) + +select * from test t1 where id in(1, select max(id) from test); +> ID NAME +> -- ----- +> 1 Hello +> 2 World +> rows: 2 + +explain select * from test t1 where id in(1, select max(id) from test t2 where t1.id=t2.id); +>> SELECT "T1"."ID", "T1"."NAME" FROM "PUBLIC"."TEST" "T1" /* PUBLIC.TEST.tableScan */ WHERE "ID" IN(1, (SELECT MAX("ID") FROM "PUBLIC"."TEST" "T2" /* PUBLIC.PRIMARY_KEY_2: ID = T1.ID */ WHERE "T1"."ID" = "T2"."ID")) + +select * from test t1 where id in(1, select max(id) from test t2 where t1.id=t2.id); +> ID NAME +> -- ----- +> 1 Hello +> 2 World +> rows: 2 + +DROP TABLE TEST; +> ok + +create force view t1 as select * from t1; +> ok + +select * from t1; +> exception VIEW_IS_INVALID_2 + +drop table t1; +> ok + +CREATE TABLE TEST(id INT PRIMARY KEY, foo BIGINT); +> ok + +INSERT INTO TEST VALUES(1, 100); +> update count: 1 + +INSERT INTO TEST VALUES(2, 123456789012345678); +> update count: 1 + +SELECT * FROM TEST WHERE foo = 123456789014567; +> ID FOO +> -- --- +> rows: 0 + +DROP TABLE IF EXISTS TEST; +> ok + +create table test(id int); +> ok + +insert into test values(1), (2), (3), (4); +> update count: 4 + +(select * from test a, test b) minus (select * from test a, test b); +> ID ID +> -- -- +> rows: 0 + +drop table test; +> ok + +call select 1.0/3.0*3.0, 100.0/2.0, -25.0/100.0, 0.0/3.0, 6.9/2.0, 0.72179425150347250912311550800000 / 5314251955.21; +> ROW (0.99990, 50.0000, -0.25000000, 0.0000, 3.4500, 0.000000000135822361752313607260107721120531135706133162) +> ------------------------------------------------------------------------------------------------------------- +> ROW (0.99990, 50.0000, -0.25000000, 0.0000, 3.4500, 0.000000000135822361752313607260107721120531135706133162) +> rows: 1 + +create sequence test_seq; +> ok + +create table test(id int primary key, parent int); +> ok + +create index ni on test(parent); +> ok + +alter table test add constraint nu unique(parent); +> ok + +alter table test add constraint fk foreign key(parent) references(id); +> ok + +SELECT TABLE_NAME, INDEX_NAME, INDEX_TYPE_NAME FROM INFORMATION_SCHEMA.INDEXES; +> TABLE_NAME INDEX_NAME INDEX_TYPE_NAME +> ---------- ------------- --------------- +> TEST NI INDEX +> TEST NU_INDEX_2 UNIQUE INDEX +> TEST PRIMARY_KEY_2 PRIMARY KEY +> rows: 3 + +SELECT TABLE_NAME, INDEX_NAME, ORDINAL_POSITION, COLUMN_NAME FROM INFORMATION_SCHEMA.INDEX_COLUMNS; +> TABLE_NAME INDEX_NAME ORDINAL_POSITION COLUMN_NAME +> ---------- ------------- ---------------- ----------- +> TEST NI 1 PARENT +> TEST NU_INDEX_2 1 PARENT +> TEST PRIMARY_KEY_2 1 ID +> rows: 3 + +select SEQUENCE_NAME, BASE_VALUE, INCREMENT, REMARKS from INFORMATION_SCHEMA.SEQUENCES; +> SEQUENCE_NAME BASE_VALUE INCREMENT REMARKS +> ------------- ---------- --------- ------- +> TEST_SEQ 1 1 null +> rows: 1 + +drop table test; +> ok + +drop sequence test_seq; +> ok + +create table test(id int); +> ok + +insert into test values(1), (2); +> update count: 2 + +select count(*) from test where id in ((select id from test where 1=0)); +> COUNT(*) +> -------- +> 0 +> rows: 1 + +select count(*) from test where id = ((select id from test where 1=0)+1); +> COUNT(*) +> -------- +> 0 +> rows: 1 + +select count(*) from test where id = (select id from test where 1=0); +> COUNT(*) +> -------- +> 0 +> rows: 1 + +select count(*) from test where id in ((select id from test)); +> COUNT(*) +> -------- +> 2 +> rows: 1 + +select count(*) from test where id = ((select id from test)); +> exception SCALAR_SUBQUERY_CONTAINS_MORE_THAN_ONE_ROW + +select count(*) from test where id = ARRAY [(select id from test), 1]; +> exception TYPES_ARE_NOT_COMPARABLE_2 + +select count(*) from test where id = ((select id from test fetch first row only), 1); +> exception TYPES_ARE_NOT_COMPARABLE_2 + +select (select id from test where 1=0) from test; +> (SELECT ID FROM PUBLIC.TEST WHERE FALSE) +> ---------------------------------------- +> null +> null +> rows: 2 + +drop table test; +> ok + +create table test(id int primary key, a boolean); +> ok + +insert into test values(1, 'Y'); +> update count: 1 + +call select a from test order by id; +> (SELECT A FROM PUBLIC.TEST ORDER BY ID) +> --------------------------------------- +> TRUE +> rows (ordered): 1 + +select select a from test order by id; +> (SELECT A FROM PUBLIC.TEST ORDER BY ID) +> --------------------------------------- +> TRUE +> rows: 1 + +insert into test values(2, 'N'); +> update count: 1 + +insert into test values(3, '1'); +> update count: 1 + +insert into test values(4, '0'); +> update count: 1 + +insert into test values(5, 'T'); +> update count: 1 + +insert into test values(6, 'F'); +> update count: 1 + +select max(id) from test where id = max(id) group by id; +> exception INVALID_USE_OF_AGGREGATE_FUNCTION_1 + +select * from test where a=TRUE=a; +> ID A +> -- ----- +> 1 TRUE +> 2 FALSE +> 3 TRUE +> 4 FALSE +> 5 TRUE +> 6 FALSE +> rows: 6 + +drop table test; +> ok + +CREATE memory TABLE TEST(ID INT PRIMARY KEY, PARENT INT REFERENCES TEST); +> ok + +CREATE memory TABLE s(S_NO VARCHAR(5) PRIMARY KEY, name VARCHAR(16), city VARCHAR(16)); +> ok + +CREATE memory TABLE p(p_no VARCHAR(5) PRIMARY KEY, descr VARCHAR(16), color VARCHAR(8)); +> ok + +CREATE memory TABLE sp1(S_NO VARCHAR(5) REFERENCES s, p_no VARCHAR(5) REFERENCES p, qty INT, PRIMARY KEY (S_NO, p_no)); +> ok + +CREATE memory TABLE sp2(S_NO VARCHAR(5), p_no VARCHAR(5), qty INT, constraint c1 FOREIGN KEY (S_NO) references s, PRIMARY KEY (S_NO, p_no)); +> ok + +script NOPASSWORDS NOSETTINGS noversion; +> SCRIPT +> -------------------------------------------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "ID" INTEGER NOT NULL, "PARENT" INTEGER ); +> ALTER TABLE "PUBLIC"."TEST" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_2" PRIMARY KEY("ID"); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> CREATE MEMORY TABLE "PUBLIC"."S"( "S_NO" CHARACTER VARYING(5) NOT NULL, "NAME" CHARACTER VARYING(16), "CITY" CHARACTER VARYING(16) ); +> ALTER TABLE "PUBLIC"."S" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_5" PRIMARY KEY("S_NO"); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.S; +> CREATE MEMORY TABLE "PUBLIC"."P"( "P_NO" CHARACTER VARYING(5) NOT NULL, "DESCR" CHARACTER VARYING(16), "COLOR" CHARACTER VARYING(8) ); +> ALTER TABLE "PUBLIC"."P" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_50" PRIMARY KEY("P_NO"); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.P; +> CREATE MEMORY TABLE "PUBLIC"."SP1"( "S_NO" CHARACTER VARYING(5) NOT NULL, "P_NO" CHARACTER VARYING(5) NOT NULL, "QTY" INTEGER ); +> ALTER TABLE "PUBLIC"."SP1" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_141" PRIMARY KEY("S_NO", "P_NO"); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.SP1; +> CREATE MEMORY TABLE "PUBLIC"."SP2"( "S_NO" CHARACTER VARYING(5) NOT NULL, "P_NO" CHARACTER VARYING(5) NOT NULL, "QTY" INTEGER ); +> ALTER TABLE "PUBLIC"."SP2" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_1417" PRIMARY KEY("S_NO", "P_NO"); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.SP2; +> ALTER TABLE "PUBLIC"."SP1" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_1" FOREIGN KEY("S_NO") REFERENCES "PUBLIC"."S"("S_NO") NOCHECK; +> ALTER TABLE "PUBLIC"."SP1" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_14" FOREIGN KEY("P_NO") REFERENCES "PUBLIC"."P"("P_NO") NOCHECK; +> ALTER TABLE "PUBLIC"."TEST" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_27" FOREIGN KEY("PARENT") REFERENCES "PUBLIC"."TEST"("ID") NOCHECK; +> ALTER TABLE "PUBLIC"."SP2" ADD CONSTRAINT "PUBLIC"."C1" FOREIGN KEY("S_NO") REFERENCES "PUBLIC"."S"("S_NO") NOCHECK; +> rows (ordered): 20 + +drop table test; +> ok + +drop table sp1; +> ok + +drop table sp2; +> ok + +drop table s; +> ok + +drop table p; +> ok + +create table test (id identity, "VALUE" int not null); +> ok + +alter table test add primary key(id); +> exception SECOND_PRIMARY_KEY + +alter table test drop primary key; +> ok + +alter table test drop primary key; +> exception INDEX_NOT_FOUND_1 + +alter table test add primary key(id, id, id); +> ok + +alter table test drop primary key; +> ok + +drop table test; +> ok + +set autocommit off; +> ok + +create local temporary table test (id identity, b int, foreign key(b) references(id)); +> ok + +drop table test; +> ok + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION DROP; +> SCRIPT +> ------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> rows (ordered): 1 + +create local temporary table test1 (id identity); +> ok + +create local temporary table test2 (id identity); +> ok + +alter table test2 add constraint test2_test1 foreign key (id) references test1; +> ok + +drop table test1, test2; +> ok + +create local temporary table test1 (id identity); +> ok + +create local temporary table test2 (id identity); +> ok + +alter table test2 add constraint test2_test1 foreign key (id) references test1; +> ok + +drop table test1, test2; +> ok + +set autocommit on; +> ok + +create table test(id int primary key, ref int, foreign key(ref) references(id)); +> ok + +insert into test values(1, 1), (2, 2); +> update count: 2 + +update test set ref=3-ref; +> update count: 2 + +alter table test add column dummy int; +> ok + +insert into test values(4, 4, null); +> update count: 1 + +drop table test; +> ok + +create table test(id int primary key); +> ok + +-- Column A.ID cannot be referenced here +explain select * from test a inner join test b left outer join test c on c.id = a.id; +> exception COLUMN_NOT_FOUND_1 + +SELECT T.ID FROM TEST "T"; +> ID +> -- +> rows: 0 + +SELECT T."ID" FROM TEST "T"; +> ID +> -- +> rows: 0 + +SELECT "T".ID FROM TEST "T"; +> ID +> -- +> rows: 0 + +SELECT "T"."ID" FROM TEST "T"; +> ID +> -- +> rows: 0 + +SELECT T.ID FROM "TEST" T; +> ID +> -- +> rows: 0 + +SELECT T."ID" FROM "TEST" T; +> ID +> -- +> rows: 0 + +SELECT "T".ID FROM "TEST" T; +> ID +> -- +> rows: 0 + +SELECT "T"."ID" FROM "TEST" T; +> ID +> -- +> rows: 0 + +SELECT T.ID FROM "TEST" "T"; +> ID +> -- +> rows: 0 + +SELECT T."ID" FROM "TEST" "T"; +> ID +> -- +> rows: 0 + +SELECT "T".ID FROM "TEST" "T"; +> ID +> -- +> rows: 0 + +SELECT "T"."ID" FROM "TEST" "T"; +> ID +> -- +> rows: 0 + +select "TEST".id from test; +> ID +> -- +> rows: 0 + +select test."ID" from test; +> ID +> -- +> rows: 0 + +select test."id" from test; +> exception COLUMN_NOT_FOUND_1 + +select "TEST"."ID" from test; +> ID +> -- +> rows: 0 + +select "test"."ID" from test; +> exception COLUMN_NOT_FOUND_1 + +select public."TEST".id from test; +> ID +> -- +> rows: 0 + +select public.test."ID" from test; +> ID +> -- +> rows: 0 + +select public."TEST"."ID" from test; +> ID +> -- +> rows: 0 + +select public."test"."ID" from test; +> exception COLUMN_NOT_FOUND_1 + +select "PUBLIC"."TEST".id from test; +> ID +> -- +> rows: 0 + +select "PUBLIC".test."ID" from test; +> ID +> -- +> rows: 0 + +select public."TEST"."ID" from test; +> ID +> -- +> rows: 0 + +select "public"."TEST"."ID" from test; +> exception COLUMN_NOT_FOUND_1 + +drop table test; +> ok + +create schema s authorization sa; +> ok + +create memory table s.test(id int); +> ok + +create index if not exists idx_id on s.test(id); +> ok + +create index if not exists idx_id on s.test(id); +> ok + +alter index s.idx_id rename to s.x; +> ok + +alter index if exists s.idx_id rename to s.x; +> ok + +alter index if exists s.x rename to s.index_id; +> ok + +alter table s.test add constraint cu_id unique(id); +> ok + +alter table s.test add name varchar; +> ok + +alter table s.test drop column name; +> ok + +alter table s.test drop constraint cu_id; +> ok + +alter table s.test rename to testtab; +> ok + +alter table s.testtab rename to test; +> ok + +create trigger test_trigger before insert on s.test call 'org.h2.test.db.TestTriggersConstraints'; +> ok + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION DROP; +> SCRIPT +> ----------------------------------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE SCHEMA IF NOT EXISTS "S" AUTHORIZATION "SA"; +> DROP TABLE IF EXISTS "S"."TEST" CASCADE; +> CREATE MEMORY TABLE "S"."TEST"( "ID" INTEGER ); +> -- 0 +/- SELECT COUNT(*) FROM S.TEST; +> CREATE INDEX "S"."INDEX_ID" ON "S"."TEST"("ID" NULLS FIRST); +> CREATE FORCE TRIGGER "S"."TEST_TRIGGER" BEFORE INSERT ON "S"."TEST" QUEUE 1024 CALL 'org.h2.test.db.TestTriggersConstraints'; +> rows (ordered): 7 + +drop trigger s.test_trigger; +> ok + +drop schema s cascade; +> ok + +CREATE MEMORY TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255), y int as id+1); +> ok + +INSERT INTO TEST(id, name) VALUES(1, 'Hello'); +> update count: 1 + +create index idx_n_id on test(name, id); +> ok + +alter table test add constraint abc foreign key(id) references (id); +> ok + +alter table test rename column id to i; +> ok + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION DROP; +> SCRIPT +> -------------------------------------------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> DROP TABLE IF EXISTS "PUBLIC"."TEST" CASCADE; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "I" INTEGER NOT NULL, "NAME" CHARACTER VARYING(255), "Y" INTEGER GENERATED ALWAYS AS ("I" + 1) ); +> ALTER TABLE "PUBLIC"."TEST" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_2" PRIMARY KEY("I"); +> -- 1 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> INSERT INTO "PUBLIC"."TEST"("I", "NAME") VALUES (1, 'Hello'); +> CREATE INDEX "PUBLIC"."IDX_N_ID" ON "PUBLIC"."TEST"("NAME" NULLS FIRST, "I" NULLS FIRST); +> ALTER TABLE "PUBLIC"."TEST" ADD CONSTRAINT "PUBLIC"."ABC" FOREIGN KEY("I") REFERENCES "PUBLIC"."TEST"("I") NOCHECK; +> rows (ordered): 8 + +INSERT INTO TEST(i, name) VALUES(2, 'World'); +> update count: 1 + +SELECT * FROM TEST ORDER BY I; +> I NAME Y +> - ----- - +> 1 Hello 2 +> 2 World 3 +> rows (ordered): 2 + +UPDATE TEST SET NAME='Hi' WHERE I=1; +> update count: 1 + +DELETE FROM TEST t0 WHERE t0.I=2; +> update count: 1 + +drop table test; +> ok + +create table test(current int); +> ok + +select current from test; +> CURRENT +> ------- +> rows: 0 + +drop table test; +> ok + +CREATE table my_table(my_int integer, my_char varchar); +> ok + +INSERT INTO my_table VALUES(1, 'Testing'); +> update count: 1 + +ALTER TABLE my_table ALTER COLUMN my_int RENAME to my_new_int; +> ok + +SELECT my_new_int FROM my_table; +> MY_NEW_INT +> ---------- +> 1 +> rows: 1 + +UPDATE my_table SET my_new_int = 33; +> update count: 1 + +SELECT * FROM my_table; +> MY_NEW_INT MY_CHAR +> ---------- ------- +> 33 Testing +> rows: 1 + +DROP TABLE my_table; +> ok + +create sequence seq1; +> ok + +create table test(ID INT default next value for seq1); +> ok + +drop sequence seq1; +> exception CANNOT_DROP_2 + +alter table test add column name varchar; +> ok + +insert into test(name) values('Hello'); +> update count: 1 + +select * from test; +> ID NAME +> -- ----- +> 1 Hello +> rows: 1 + +drop table test; +> ok + +drop sequence seq1; +> ok + +create table test(a int primary key, b int, c int); +> ok + +alter table test add constraint unique_ba unique(b, a); +> ok + +alter table test add constraint abc foreign key(c, a) references test(b, a); +> ok + +insert into test values(1, 1, null); +> update count: 1 + +drop table test; +> ok + +create table ADDRESS (ADDRESS_ID int primary key, ADDRESS_TYPE int not null, SERVER_ID int not null); +> ok + +alter table address add constraint unique_a unique(ADDRESS_TYPE, SERVER_ID); +> ok + +create table SERVER (SERVER_ID int primary key, SERVER_TYPE int not null, ADDRESS_TYPE int); +> ok + +alter table ADDRESS add constraint addr foreign key (SERVER_ID) references SERVER; +> ok + +alter table SERVER add constraint server_const foreign key (ADDRESS_TYPE, SERVER_ID) references ADDRESS (ADDRESS_TYPE, SERVER_ID); +> ok + +insert into SERVER (SERVER_ID, SERVER_TYPE) values (1, 1); +> update count: 1 + +drop table address, server; +> ok + +CREATE TABLE PlanElements(id int primary key, name varchar, parent_id int, foreign key(parent_id) references(id) on delete cascade); +> ok + +INSERT INTO PlanElements(id,name,parent_id) VALUES(1, '#1', null), (2, '#1-A', 1), (3, '#1-A-1', 2), (4, '#1-A-2', 2); +> update count: 4 + +INSERT INTO PlanElements(id,name,parent_id) VALUES(5, '#1-B', 1), (6, '#1-B-1', 5), (7, '#1-B-2', 5); +> update count: 3 + +INSERT INTO PlanElements(id,name,parent_id) VALUES(8, '#1-C', 1), (9, '#1-C-1', 8), (10, '#1-C-2', 8); +> update count: 3 + +INSERT INTO PlanElements(id,name,parent_id) VALUES(11, '#1-D', 1), (12, '#1-D-1', 11), (13, '#1-D-2', 11), (14, '#1-D-3', 11); +> update count: 4 + +INSERT INTO PlanElements(id,name,parent_id) VALUES(15, '#1-E', 1), (16, '#1-E-1', 15), (17, '#1-E-2', 15), (18, '#1-E-3', 15), (19, '#1-E-4', 15); +> update count: 5 + +DELETE FROM PlanElements WHERE id = 1; +> update count: 1 + +SELECT * FROM PlanElements; +> ID NAME PARENT_ID +> -- ---- --------- +> rows: 0 + +DROP TABLE PlanElements; +> ok + +CREATE TABLE PARENT(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +CREATE TABLE CHILD(ID INT PRIMARY KEY, NAME VARCHAR(255), FOREIGN KEY(NAME) REFERENCES PARENT(ID)); +> ok + +INSERT INTO PARENT VALUES(1, '1'); +> update count: 1 + +INSERT INTO CHILD VALUES(1, '1'); +> update count: 1 + +INSERT INTO CHILD VALUES(2, 'Hello'); +> exception DATA_CONVERSION_ERROR_1 + +DROP TABLE IF EXISTS CHILD; +> ok + +DROP TABLE IF EXISTS PARENT; +> ok + +DECLARE GLOBAL TEMPORARY TABLE TEST(ID INT PRIMARY KEY); +> ok + +SELECT * FROM TEST; +> ID +> -- +> rows: 0 + +SELECT GROUP_CONCAT(ID) FROM TEST; +> LISTAGG(ID) WITHIN GROUP (ORDER BY NULL) +> ---------------------------------------- +> null +> rows: 1 + +SELECT * FROM SESSION.TEST; +> ID +> -- +> rows: 0 + +DROP TABLE TEST; +> ok + +VALUES(1, 2); +> C1 C2 +> -- -- +> 1 2 +> rows: 1 + +DROP TABLE IF EXISTS TEST; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +INSERT INTO TEST VALUES(1, 'Hello'); +> update count: 1 + +INSERT INTO TEST VALUES(2, 'World'); +> update count: 1 + +SELECT group_concat(name) FROM TEST group by id; +> LISTAGG(NAME) WITHIN GROUP (ORDER BY NULL) +> ------------------------------------------ +> Hello +> World +> rows: 2 + +drop table test; +> ok + +create table test(a int primary key, b int invisible, c int); +> ok + +select * from test; +> A C +> - - +> rows: 0 + +select a, b, c from test; +> A B C +> - - - +> rows: 0 + +drop table test; +> ok + +--- script drop --------------------------------------------------------------------------------------------- +create memory table test (id int primary key, im_ie varchar(10)); +> ok + +create sequence test_seq; +> ok + +SCRIPT NODATA NOPASSWORDS NOSETTINGS NOVERSION DROP; +> SCRIPT +> -------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> DROP TABLE IF EXISTS "PUBLIC"."TEST" CASCADE; +> DROP SEQUENCE IF EXISTS "PUBLIC"."TEST_SEQ"; +> CREATE SEQUENCE "PUBLIC"."TEST_SEQ" START WITH 1; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "ID" INTEGER NOT NULL, "IM_IE" CHARACTER VARYING(10) ); +> ALTER TABLE "PUBLIC"."TEST" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_2" PRIMARY KEY("ID"); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> rows (ordered): 7 + +drop sequence test_seq; +> ok + +drop table test; +> ok + +--- constraints --------------------------------------------------------------------------------------------- +CREATE MEMORY TABLE TEST(ID IDENTITY(100, 10), NAME VARCHAR); +> ok + +INSERT INTO TEST(NAME) VALUES('Hello'), ('World'); +> update count: 2 + +SELECT * FROM TEST; +> ID NAME +> --- ----- +> 100 Hello +> 110 World +> rows: 2 + +DROP TABLE TEST; +> ok + +CREATE CACHED TABLE account( +id INTEGER GENERATED BY DEFAULT AS IDENTITY, +name VARCHAR NOT NULL, +mail_address VARCHAR NOT NULL, +UNIQUE(name), +PRIMARY KEY(id) +); +> ok + +CREATE CACHED TABLE label( +id INTEGER GENERATED BY DEFAULT AS IDENTITY, +parent_id INTEGER NOT NULL, +account_id INTEGER NOT NULL, +name VARCHAR NOT NULL, +PRIMARY KEY(id), +UNIQUE(parent_id, name), +UNIQUE(id, account_id), +FOREIGN KEY(account_id) REFERENCES account (id), +FOREIGN KEY(parent_id, account_id) REFERENCES label (id, account_id) +); +> ok + +INSERT INTO account VALUES (0, 'example', 'example@example.com'); +> update count: 1 + +INSERT INTO label VALUES ( 0, 0, 0, 'TEST'); +> update count: 1 + +INSERT INTO label VALUES ( 1, 0, 0, 'TEST'); +> exception DUPLICATE_KEY_1 + +INSERT INTO label VALUES ( 1, 0, 0, 'TEST1'); +> update count: 1 + +INSERT INTO label VALUES ( 2, 2, 1, 'TEST'); +> exception REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1 + +drop table label; +> ok + +drop table account; +> ok + +--- constraints and alter table add column --------------------------------------------------------------------------------------------- +CREATE TABLE TEST(ID INT PRIMARY KEY, PARENTID INT, FOREIGN KEY(PARENTID) REFERENCES(ID)); +> ok + +INSERT INTO TEST VALUES(0, 0); +> update count: 1 + +ALTER TABLE TEST ADD COLUMN CHILD_ID INT; +> ok + +ALTER TABLE TEST ALTER COLUMN CHILD_ID VARCHAR; +> ok + +ALTER TABLE TEST ALTER COLUMN PARENTID VARCHAR; +> ok + +ALTER TABLE TEST DROP COLUMN PARENTID; +> ok + +ALTER TABLE TEST DROP COLUMN CHILD_ID; +> ok + +SELECT * FROM TEST; +> ID +> -- +> 0 +> rows: 1 + +DROP TABLE TEST; +> ok + +CREATE MEMORY TABLE A(X INT PRIMARY KEY); +> ok + +CREATE MEMORY TABLE B(XX INT, CONSTRAINT B2A FOREIGN KEY(XX) REFERENCES A(X)); +> ok + +CREATE MEMORY TABLE C(X_MASTER INT PRIMARY KEY); +> ok + +ALTER TABLE A ADD CONSTRAINT A2C FOREIGN KEY(X) REFERENCES C(X_MASTER); +> ok + +insert into c values(1); +> update count: 1 + +insert into a values(1); +> update count: 1 + +insert into b values(1); +> update count: 1 + +ALTER TABLE A ADD COLUMN Y INT; +> ok + +insert into c values(2); +> update count: 1 + +insert into a values(2, 2); +> update count: 1 + +insert into b values(2); +> update count: 1 + +DROP TABLE IF EXISTS A, B, C; +> ok + +--- quoted keywords --------------------------------------------------------------------------------------------- +CREATE TABLE "CREATE"("SELECT" INT, "PRIMARY" INT, "KEY" INT, "INDEX" INT, "ROWNUM" INT, "NEXTVAL" INT, "FROM" INT); +> ok + +INSERT INTO "CREATE" default values; +> update count: 1 + +INSERT INTO "CREATE" default values; +> update count: 1 + +SELECT "ROWNUM", ROWNUM, "SELECT" "AS", "PRIMARY" AS "X", "KEY", "NEXTVAL", "INDEX", "SELECT" "FROM" FROM "CREATE"; +> ROWNUM ROWNUM() AS X KEY NEXTVAL INDEX FROM +> ------ -------- ---- ---- ---- ------- ----- ---- +> null 1 null null null null null null +> null 2 null null null null null null +> rows: 2 + +DROP TABLE "CREATE"; +> ok + +CREATE TABLE PARENT(ID INT PRIMARY KEY, NAME VARCHAR); +> ok + +CREATE TABLE CHILD(ID INT, PARENTID INT, FOREIGN KEY(PARENTID) REFERENCES PARENT(ID)); +> ok + +INSERT INTO PARENT VALUES(1, 'Mary'), (2, 'John'); +> update count: 2 + +INSERT INTO CHILD VALUES(10, 1), (11, 1), (20, 2), (21, 2); +> update count: 4 + +MERGE INTO PARENT KEY(ID) VALUES(1, 'Marcy'); +> update count: 1 + +SELECT * FROM PARENT; +> ID NAME +> -- ----- +> 1 Marcy +> 2 John +> rows: 2 + +SELECT * FROM CHILD; +> ID PARENTID +> -- -------- +> 10 1 +> 11 1 +> 20 2 +> 21 2 +> rows: 4 + +DROP TABLE PARENT, CHILD; +> ok + +--- +create table STRING_TEST(label varchar(31), label2 varchar(255)); +> ok + +create table STRING_TEST_ic(label varchar_ignorecase(31), label2 +varchar_ignorecase(255)); +> ok + +insert into STRING_TEST values('HELLO','Bye'); +> update count: 1 + +insert into STRING_TEST values('HELLO','Hello'); +> update count: 1 + +insert into STRING_TEST_ic select * from STRING_TEST; +> update count: 2 + +-- Expect rows of STRING_TEST_ic and STRING_TEST to be identical +select * from STRING_TEST; +> LABEL LABEL2 +> ----- ------ +> HELLO Bye +> HELLO Hello +> rows: 2 + +-- correct +select * from STRING_TEST_ic; +> LABEL LABEL2 +> ----- ------ +> HELLO Bye +> HELLO Hello +> rows: 2 + +drop table STRING_TEST; +> ok + +drop table STRING_TEST_ic; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR_IGNORECASE); +> ok + +INSERT INTO TEST VALUES(1, 'Hello'), (2, 'World'), (3, 'hallo'), (4, 'hoi'); +> update count: 4 + +SELECT * FROM TEST WHERE NAME = 'HELLO'; +> ID NAME +> -- ----- +> 1 Hello +> rows: 1 + +SELECT * FROM TEST WHERE NAME = 'HE11O'; +> ID NAME +> -- ---- +> rows: 0 + +SELECT * FROM TEST ORDER BY NAME; +> ID NAME +> -- ----- +> 3 hallo +> 1 Hello +> 4 hoi +> 2 World +> rows (ordered): 4 + +DROP TABLE IF EXISTS TEST; +> ok + +--- update with list --------------------------------------------------------------------------------------------- +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +INSERT INTO TEST VALUES(1, 'Hello'); +> update count: 1 + +INSERT INTO TEST VALUES(2, 'World'); +> update count: 1 + +SELECT * FROM TEST ORDER BY ID; +> ID NAME +> -- ----- +> 1 Hello +> 2 World +> rows (ordered): 2 + +UPDATE TEST t0 SET t0.NAME='Hi' WHERE t0.ID=1; +> update count: 1 + +update test set (id, name)=(id+1, name || 'Hi'); +> update count: 2 + +update test set (id, name)=(select id+1, name || 'Ho' from test t1 where test.id=t1.id); +> update count: 2 + +explain update test set (id, name)=(id+1, name || 'Hi'); +>> UPDATE "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ SET "ID" = "ID" + 1, "NAME" = "NAME" || 'Hi' + +explain update test set (id, name)=(select id+1, name || 'Ho' from test t1 where test.id=t1.id); +>> UPDATE "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ SET ("ID", "NAME") = (SELECT "ID" + 1, "NAME" || 'Ho' FROM "PUBLIC"."TEST" "T1" /* PUBLIC.PRIMARY_KEY_2: ID = TEST.ID */ WHERE "TEST"."ID" = "T1"."ID") + +select * from test; +> ID NAME +> -- --------- +> 3 HiHiHo +> 4 WorldHiHo +> rows: 2 + +DROP TABLE IF EXISTS TEST; +> ok + +--- script --------------------------------------------------------------------------------------------- +create memory table test(id int primary key, c clob, b blob); +> ok + +insert into test values(0, null, null); +> update count: 1 + +insert into test values(1, '', ''); +> update count: 1 + +insert into test values(2, 'Cafe', X'cafe'); +> update count: 1 + +script simple nopasswords nosettings noversion; +> SCRIPT +> ------------------------------------------------------------------------------------------------------------------ +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "ID" INTEGER NOT NULL, "C" CHARACTER LARGE OBJECT, "B" BINARY LARGE OBJECT ); +> ALTER TABLE "PUBLIC"."TEST" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_2" PRIMARY KEY("ID"); +> -- 3 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> INSERT INTO "PUBLIC"."TEST" VALUES(0, NULL, NULL); +> INSERT INTO "PUBLIC"."TEST" VALUES(1, '', X''); +> INSERT INTO "PUBLIC"."TEST" VALUES(2, 'Cafe', X'cafe'); +> rows (ordered): 7 + +drop table test; +> ok + +--- optimizer --------------------------------------------------------------------------------------------- +create table b(id int primary key, p int); +> ok + +create index bp on b(p); +> ok + +insert into b values(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8), (9, 9); +> update count: 10 + +insert into b select id+10, p+10 from b; +> update count: 10 + +explain select * from b b0, b b1, b b2 where b1.p = b0.id and b2.p = b1.id and b0.id=10; +>> SELECT "B0"."ID", "B0"."P", "B1"."ID", "B1"."P", "B2"."ID", "B2"."P" FROM "PUBLIC"."B" "B0" /* PUBLIC.PRIMARY_KEY_4: ID = 10 */ /* WHERE B0.ID = 10 */ INNER JOIN "PUBLIC"."B" "B1" /* PUBLIC.BP: P = B0.ID */ ON 1=1 /* WHERE B1.P = B0.ID */ INNER JOIN "PUBLIC"."B" "B2" /* PUBLIC.BP: P = B1.ID */ ON 1=1 WHERE ("B0"."ID" = 10) AND ("B1"."P" = "B0"."ID") AND ("B2"."P" = "B1"."ID") + +explain select * from b b0, b b1, b b2, b b3 where b1.p = b0.id and b2.p = b1.id and b3.p = b2.id and b0.id=10; +>> SELECT "B0"."ID", "B0"."P", "B1"."ID", "B1"."P", "B2"."ID", "B2"."P", "B3"."ID", "B3"."P" FROM "PUBLIC"."B" "B0" /* PUBLIC.PRIMARY_KEY_4: ID = 10 */ /* WHERE B0.ID = 10 */ INNER JOIN "PUBLIC"."B" "B1" /* PUBLIC.BP: P = B0.ID */ ON 1=1 /* WHERE B1.P = B0.ID */ INNER JOIN "PUBLIC"."B" "B2" /* PUBLIC.BP: P = B1.ID */ ON 1=1 /* WHERE B2.P = B1.ID */ INNER JOIN "PUBLIC"."B" "B3" /* PUBLIC.BP: P = B2.ID */ ON 1=1 WHERE ("B0"."ID" = 10) AND ("B3"."P" = "B2"."ID") AND ("B1"."P" = "B0"."ID") AND ("B2"."P" = "B1"."ID") + +explain select * from b b0, b b1, b b2, b b3, b b4 where b1.p = b0.id and b2.p = b1.id and b3.p = b2.id and b4.p = b3.id and b0.id=10; +>> SELECT "B0"."ID", "B0"."P", "B1"."ID", "B1"."P", "B2"."ID", "B2"."P", "B3"."ID", "B3"."P", "B4"."ID", "B4"."P" FROM "PUBLIC"."B" "B0" /* PUBLIC.PRIMARY_KEY_4: ID = 10 */ /* WHERE B0.ID = 10 */ INNER JOIN "PUBLIC"."B" "B1" /* PUBLIC.BP: P = B0.ID */ ON 1=1 /* WHERE B1.P = B0.ID */ INNER JOIN "PUBLIC"."B" "B2" /* PUBLIC.BP: P = B1.ID */ ON 1=1 /* WHERE B2.P = B1.ID */ INNER JOIN "PUBLIC"."B" "B3" /* PUBLIC.BP: P = B2.ID */ ON 1=1 /* WHERE B3.P = B2.ID */ INNER JOIN "PUBLIC"."B" "B4" /* PUBLIC.BP: P = B3.ID */ ON 1=1 WHERE ("B0"."ID" = 10) AND ("B3"."P" = "B2"."ID") AND ("B4"."P" = "B3"."ID") AND ("B1"."P" = "B0"."ID") AND ("B2"."P" = "B1"."ID") + +analyze; +> ok + +explain select * from b b0, b b1, b b2, b b3, b b4 where b1.p = b0.id and b2.p = b1.id and b3.p = b2.id and b4.p = b3.id and b0.id=10; +>> SELECT "B0"."ID", "B0"."P", "B1"."ID", "B1"."P", "B2"."ID", "B2"."P", "B3"."ID", "B3"."P", "B4"."ID", "B4"."P" FROM "PUBLIC"."B" "B0" /* PUBLIC.PRIMARY_KEY_4: ID = 10 */ /* WHERE B0.ID = 10 */ INNER JOIN "PUBLIC"."B" "B1" /* PUBLIC.BP: P = B0.ID */ ON 1=1 /* WHERE B1.P = B0.ID */ INNER JOIN "PUBLIC"."B" "B2" /* PUBLIC.BP: P = B1.ID */ ON 1=1 /* WHERE B2.P = B1.ID */ INNER JOIN "PUBLIC"."B" "B3" /* PUBLIC.BP: P = B2.ID */ ON 1=1 /* WHERE B3.P = B2.ID */ INNER JOIN "PUBLIC"."B" "B4" /* PUBLIC.BP: P = B3.ID */ ON 1=1 WHERE ("B0"."ID" = 10) AND ("B3"."P" = "B2"."ID") AND ("B4"."P" = "B3"."ID") AND ("B1"."P" = "B0"."ID") AND ("B2"."P" = "B1"."ID") + +drop table if exists b; +> ok + +create table test(id int primary key, first_name varchar, name varchar, state int); +> ok + +create index idx_first_name on test(first_name); +> ok + +create index idx_name on test(name); +> ok + +create index idx_state on test(state); +> ok + +insert into test values +(0, 'Anne', 'Smith', 0), (1, 'Tom', 'Smith', 0), +(2, 'Tom', 'Jones', 0), (3, 'Steve', 'Johnson', 0), +(4, 'Steve', 'Martin', 0), (5, 'Jon', 'Jones', 0), +(6, 'Marc', 'Scott', 0), (7, 'Marc', 'Miller', 0), +(8, 'Susan', 'Wood', 0), (9, 'Jon', 'Bennet', 0); +> update count: 10 + +EXPLAIN SELECT * FROM TEST WHERE ID = 3; +>> SELECT "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."FIRST_NAME", "PUBLIC"."TEST"."NAME", "PUBLIC"."TEST"."STATE" FROM "PUBLIC"."TEST" /* PUBLIC.PRIMARY_KEY_2: ID = 3 */ WHERE "ID" = 3 + +explain select * from test where name='Smith' and first_name='Tom' and state=0; +>> SELECT "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."FIRST_NAME", "PUBLIC"."TEST"."NAME", "PUBLIC"."TEST"."STATE" FROM "PUBLIC"."TEST" /* PUBLIC.IDX_FIRST_NAME: FIRST_NAME = 'Tom' */ WHERE ("STATE" = 0) AND ("NAME" = 'Smith') AND ("FIRST_NAME" = 'Tom') + +alter table test alter column name selectivity 100; +> ok + +explain select * from test where name='Smith' and first_name='Tom' and state=0; +>> SELECT "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."FIRST_NAME", "PUBLIC"."TEST"."NAME", "PUBLIC"."TEST"."STATE" FROM "PUBLIC"."TEST" /* PUBLIC.IDX_NAME: NAME = 'Smith' */ WHERE ("STATE" = 0) AND ("NAME" = 'Smith') AND ("FIRST_NAME" = 'Tom') + +drop table test; +> ok + +CREATE TABLE O(X INT PRIMARY KEY, Y INT); +> ok + +INSERT INTO O SELECT X, X+1 FROM SYSTEM_RANGE(1, 1000); +> update count: 1000 + +EXPLAIN SELECT A.X FROM O B, O A, O F, O D, O C, O E, O G, O H, O I, O J +WHERE 1=J.X and J.Y=I.X AND I.Y=H.X AND H.Y=G.X AND G.Y=F.X AND F.Y=E.X +AND E.Y=D.X AND D.Y=C.X AND C.Y=B.X AND B.Y=A.X; +>> SELECT "A"."X" FROM "PUBLIC"."O" "J" /* PUBLIC.PRIMARY_KEY_4: X = 1 */ /* WHERE J.X = 1 */ INNER JOIN "PUBLIC"."O" "I" /* PUBLIC.PRIMARY_KEY_4: X = J.Y */ ON 1=1 /* WHERE J.Y = I.X */ INNER JOIN "PUBLIC"."O" "H" /* PUBLIC.PRIMARY_KEY_4: X = I.Y */ ON 1=1 /* WHERE I.Y = H.X */ INNER JOIN "PUBLIC"."O" "G" /* PUBLIC.PRIMARY_KEY_4: X = H.Y */ ON 1=1 /* WHERE H.Y = G.X */ INNER JOIN "PUBLIC"."O" "F" /* PUBLIC.PRIMARY_KEY_4: X = G.Y */ ON 1=1 /* WHERE G.Y = F.X */ INNER JOIN "PUBLIC"."O" "E" /* PUBLIC.PRIMARY_KEY_4: X = F.Y */ ON 1=1 /* WHERE F.Y = E.X */ INNER JOIN "PUBLIC"."O" "D" /* PUBLIC.PRIMARY_KEY_4: X = E.Y */ ON 1=1 /* WHERE E.Y = D.X */ INNER JOIN "PUBLIC"."O" "C" /* PUBLIC.PRIMARY_KEY_4: X = D.Y */ ON 1=1 /* WHERE D.Y = C.X */ INNER JOIN "PUBLIC"."O" "B" /* PUBLIC.PRIMARY_KEY_4: X = C.Y */ ON 1=1 /* WHERE C.Y = B.X */ INNER JOIN "PUBLIC"."O" "A" /* PUBLIC.PRIMARY_KEY_4: X = B.Y */ ON 1=1 WHERE ("J"."X" = 1) AND ("I"."Y" = "H"."X") AND ("H"."Y" = "G"."X") AND ("G"."Y" = "F"."X") AND ("F"."Y" = "E"."X") AND ("E"."Y" = "D"."X") AND ("D"."Y" = "C"."X") AND ("C"."Y" = "B"."X") AND ("B"."Y" = "A"."X") AND ("J"."Y" = "I"."X") + +DROP TABLE O; +> ok + +CREATE TABLE PARENT(ID INT PRIMARY KEY, AID INT, BID INT, CID INT, DID INT, EID INT, FID INT, GID INT, HID INT); +> ok + +CREATE TABLE CHILD(ID INT PRIMARY KEY); +> ok + +INSERT INTO PARENT SELECT X, 1, 2, 1, 2, 1, 2, 1, 2 FROM SYSTEM_RANGE(0, 1000); +> update count: 1001 + +INSERT INTO CHILD SELECT X FROM SYSTEM_RANGE(0, 1000); +> update count: 1001 + +SELECT COUNT(*) FROM PARENT, CHILD A, CHILD B, CHILD C, CHILD D, CHILD E, CHILD F, CHILD G, CHILD H +WHERE AID=A.ID AND BID=B.ID AND CID=C.ID +AND DID=D.ID AND EID=E.ID AND FID=F.ID AND GID=G.ID AND HID=H.ID; +> COUNT(*) +> -------- +> 1001 +> rows: 1 + +EXPLAIN SELECT COUNT(*) FROM PARENT, CHILD A, CHILD B, CHILD C, CHILD D, CHILD E, CHILD F, CHILD G, CHILD H +WHERE AID=A.ID AND BID=B.ID AND CID=C.ID +AND DID=D.ID AND EID=E.ID AND FID=F.ID AND GID=G.ID AND HID=H.ID; +>> SELECT COUNT(*) FROM "PUBLIC"."PARENT" /* PUBLIC.PARENT.tableScan */ INNER JOIN "PUBLIC"."CHILD" "A" /* PUBLIC.PRIMARY_KEY_3: ID = AID */ ON 1=1 /* WHERE AID = A.ID */ INNER JOIN "PUBLIC"."CHILD" "B" /* PUBLIC.PRIMARY_KEY_3: ID = BID */ ON 1=1 /* WHERE BID = B.ID */ INNER JOIN "PUBLIC"."CHILD" "C" /* PUBLIC.PRIMARY_KEY_3: ID = CID */ ON 1=1 /* WHERE CID = C.ID */ INNER JOIN "PUBLIC"."CHILD" "D" /* PUBLIC.PRIMARY_KEY_3: ID = DID */ ON 1=1 /* WHERE DID = D.ID */ INNER JOIN "PUBLIC"."CHILD" "E" /* PUBLIC.PRIMARY_KEY_3: ID = EID */ ON 1=1 /* WHERE EID = E.ID */ INNER JOIN "PUBLIC"."CHILD" "F" /* PUBLIC.PRIMARY_KEY_3: ID = FID */ ON 1=1 /* WHERE FID = F.ID */ INNER JOIN "PUBLIC"."CHILD" "G" /* PUBLIC.PRIMARY_KEY_3: ID = GID */ ON 1=1 /* WHERE GID = G.ID */ INNER JOIN "PUBLIC"."CHILD" "H" /* PUBLIC.PRIMARY_KEY_3: ID = HID */ ON 1=1 WHERE ("CID" = "C"."ID") AND ("DID" = "D"."ID") AND ("EID" = "E"."ID") AND ("FID" = "F"."ID") AND ("GID" = "G"."ID") AND ("HID" = "H"."ID") AND ("AID" = "A"."ID") AND ("BID" = "B"."ID") + +CREATE TABLE FAMILY(ID INT PRIMARY KEY, PARENTID INT); +> ok + +INSERT INTO FAMILY SELECT X, X-1 FROM SYSTEM_RANGE(0, 1000); +> update count: 1001 + +EXPLAIN SELECT COUNT(*) FROM CHILD A, CHILD B, FAMILY, CHILD C, CHILD D, PARENT, CHILD E, CHILD F, CHILD G +WHERE FAMILY.ID=1 AND FAMILY.PARENTID=PARENT.ID +AND AID=A.ID AND BID=B.ID AND CID=C.ID AND DID=D.ID AND EID=E.ID AND FID=F.ID AND GID=G.ID; +>> SELECT COUNT(*) FROM "PUBLIC"."FAMILY" /* PUBLIC.PRIMARY_KEY_7: ID = 1 */ /* WHERE FAMILY.ID = 1 */ INNER JOIN "PUBLIC"."PARENT" /* PUBLIC.PRIMARY_KEY_8: ID = FAMILY.PARENTID */ ON 1=1 /* WHERE FAMILY.PARENTID = PARENT.ID */ INNER JOIN "PUBLIC"."CHILD" "A" /* PUBLIC.PRIMARY_KEY_3: ID = AID */ ON 1=1 /* WHERE AID = A.ID */ INNER JOIN "PUBLIC"."CHILD" "B" /* PUBLIC.PRIMARY_KEY_3: ID = BID */ ON 1=1 /* WHERE BID = B.ID */ INNER JOIN "PUBLIC"."CHILD" "C" /* PUBLIC.PRIMARY_KEY_3: ID = CID */ ON 1=1 /* WHERE CID = C.ID */ INNER JOIN "PUBLIC"."CHILD" "D" /* PUBLIC.PRIMARY_KEY_3: ID = DID */ ON 1=1 /* WHERE DID = D.ID */ INNER JOIN "PUBLIC"."CHILD" "E" /* PUBLIC.PRIMARY_KEY_3: ID = EID */ ON 1=1 /* WHERE EID = E.ID */ INNER JOIN "PUBLIC"."CHILD" "F" /* PUBLIC.PRIMARY_KEY_3: ID = FID */ ON 1=1 /* WHERE FID = F.ID */ INNER JOIN "PUBLIC"."CHILD" "G" /* PUBLIC.PRIMARY_KEY_3: ID = GID */ ON 1=1 WHERE ("FAMILY"."ID" = 1) AND ("AID" = "A"."ID") AND ("BID" = "B"."ID") AND ("CID" = "C"."ID") AND ("DID" = "D"."ID") AND ("EID" = "E"."ID") AND ("FID" = "F"."ID") AND ("GID" = "G"."ID") AND ("FAMILY"."PARENTID" = "PARENT"."ID") + +DROP TABLE FAMILY; +> ok + +DROP TABLE PARENT; +> ok + +DROP TABLE CHILD; +> ok + +--- is null / not is null --------------------------------------------------------------------------------------------- +CREATE TABLE TEST(ID INT UNIQUE, NAME VARCHAR CHECK LENGTH(NAME)>3); +> ok + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID INT, NAME VARCHAR(255), B INT); +> ok + +CREATE UNIQUE INDEX IDXNAME ON TEST(NAME); +> ok + +CREATE UNIQUE INDEX IDX_NAME_B ON TEST(NAME, B); +> ok + +INSERT INTO TEST(ID, NAME, B) VALUES (0, NULL, NULL); +> update count: 1 + +INSERT INTO TEST(ID, NAME, B) VALUES (1, 'Hello', NULL); +> update count: 1 + +INSERT INTO TEST(ID, NAME, B) VALUES (2, NULL, NULL); +> update count: 1 + +INSERT INTO TEST(ID, NAME, B) VALUES (3, 'World', NULL); +> update count: 1 + +select * from test; +> ID NAME B +> -- ----- ---- +> 0 null null +> 1 Hello null +> 2 null null +> 3 World null +> rows: 4 + +UPDATE test SET name='Hi'; +> exception DUPLICATE_KEY_1 + +select * from test; +> ID NAME B +> -- ----- ---- +> 0 null null +> 1 Hello null +> 2 null null +> 3 World null +> rows: 4 + +UPDATE test SET name=NULL; +> update count: 4 + +UPDATE test SET B=1; +> update count: 4 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID INT, NAME VARCHAR); +> ok + +INSERT INTO TEST VALUES(NULL, NULL), (0, 'Hello'), (1, 'World'); +> update count: 3 + +SELECT * FROM TEST WHERE NOT (1=1); +> ID NAME +> -- ---- +> rows: 0 + +DROP TABLE TEST; +> ok + +create table test_null(a int, b int); +> ok + +insert into test_null values(0, 0); +> update count: 1 + +insert into test_null values(0, null); +> update count: 1 + +insert into test_null values(null, null); +> update count: 1 + +insert into test_null values(null, 0); +> update count: 1 + +select * from test_null where a=0; +> A B +> - ---- +> 0 0 +> 0 null +> rows: 2 + +select * from test_null where not a=0; +> A B +> - - +> rows: 0 + +select * from test_null where (a=0 or b=0); +> A B +> ---- ---- +> 0 0 +> 0 null +> null 0 +> rows: 3 + +select * from test_null where not (a=0 or b=0); +> A B +> - - +> rows: 0 + +select * from test_null where (a=1 or b=0); +> A B +> ---- - +> 0 0 +> null 0 +> rows: 2 + +select * from test_null where not( a=1 or b=0); +> A B +> - - +> rows: 0 + +select * from test_null where not(not( a=1 or b=0)); +> A B +> ---- - +> 0 0 +> null 0 +> rows: 2 + +select * from test_null where a=0 or b=0; +> A B +> ---- ---- +> 0 0 +> 0 null +> null 0 +> rows: 3 + +SELECT count(*) FROM test_null WHERE not ('X'=null and 1=0); +> COUNT(*) +> -------- +> 4 +> rows: 1 + +drop table if exists test_null; +> ok + +--- schema ---------------------------------------------------------------------------------------------- +SELECT DISTINCT TABLE_SCHEMA, TABLE_CATALOG FROM INFORMATION_SCHEMA.TABLES ORDER BY TABLE_SCHEMA; +> TABLE_SCHEMA TABLE_CATALOG +> ------------------ ------------- +> INFORMATION_SCHEMA SCRIPT +> rows (ordered): 1 + +SELECT * FROM INFORMATION_SCHEMA.SCHEMATA; +> CATALOG_NAME SCHEMA_NAME SCHEMA_OWNER DEFAULT_CHARACTER_SET_CATALOG DEFAULT_CHARACTER_SET_SCHEMA DEFAULT_CHARACTER_SET_NAME SQL_PATH DEFAULT_COLLATION_NAME REMARKS +> ------------ ------------------ ------------ ----------------------------- ---------------------------- -------------------------- -------- ---------------------- ------- +> SCRIPT INFORMATION_SCHEMA SA SCRIPT PUBLIC Unicode null OFF null +> SCRIPT PUBLIC SA SCRIPT PUBLIC Unicode null OFF null +> rows: 2 + +SELECT * FROM INFORMATION_SCHEMA.INFORMATION_SCHEMA_CATALOG_NAME; +> CATALOG_NAME +> ------------ +> SCRIPT +> rows: 1 + +SELECT INFORMATION_SCHEMA.SCHEMATA.SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA; +> SCHEMA_NAME +> ------------------ +> INFORMATION_SCHEMA +> PUBLIC +> rows: 2 + +SELECT INFORMATION_SCHEMA.SCHEMATA.* FROM INFORMATION_SCHEMA.SCHEMATA; +> CATALOG_NAME SCHEMA_NAME SCHEMA_OWNER DEFAULT_CHARACTER_SET_CATALOG DEFAULT_CHARACTER_SET_SCHEMA DEFAULT_CHARACTER_SET_NAME SQL_PATH DEFAULT_COLLATION_NAME REMARKS +> ------------ ------------------ ------------ ----------------------------- ---------------------------- -------------------------- -------- ---------------------- ------- +> SCRIPT INFORMATION_SCHEMA SA SCRIPT PUBLIC Unicode null OFF null +> SCRIPT PUBLIC SA SCRIPT PUBLIC Unicode null OFF null +> rows: 2 + +CREATE SCHEMA TEST_SCHEMA AUTHORIZATION SA; +> ok + +DROP SCHEMA TEST_SCHEMA RESTRICT; +> ok + +create schema Contact_Schema AUTHORIZATION SA; +> ok + +CREATE TABLE Contact_Schema.Address ( +address_id BIGINT NOT NULL +CONSTRAINT address_id_check +CHECK (address_id > 0), +address_type VARCHAR(20) NOT NULL +CONSTRAINT address_type +CHECK (address_type in ('postal','email','web')), +CONSTRAINT X_PKAddress +PRIMARY KEY (address_id) +); +> ok + +create schema ClientServer_Schema AUTHORIZATION SA; +> ok + +CREATE TABLE ClientServer_Schema.PrimaryKey_Seq ( +sequence_name VARCHAR(100) NOT NULL, +seq_number BIGINT NOT NULL UNIQUE, +CONSTRAINT X_PKPrimaryKey_Seq +PRIMARY KEY (sequence_name) +); +> ok + +alter table Contact_Schema.Address add constraint abc foreign key(address_id) +references ClientServer_Schema.PrimaryKey_Seq(seq_number); +> ok + +drop table ClientServer_Schema.PrimaryKey_Seq, Contact_Schema.Address; +> ok + +drop schema Contact_Schema restrict; +> ok + +drop schema ClientServer_Schema restrict; +> ok + +--- alter table add / drop / rename column ---------------------------------------------------------------------------------------------- +CREATE MEMORY TABLE TEST(ID INT PRIMARY KEY); +> ok + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> ------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "ID" INTEGER NOT NULL ); +> ALTER TABLE "PUBLIC"."TEST" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_2" PRIMARY KEY("ID"); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> rows (ordered): 4 + +ALTER TABLE TEST ADD CREATEDATE VARCHAR(255) DEFAULT '2001-01-01' NOT NULL; +> ok + +ALTER TABLE TEST ADD NAME VARCHAR(255) NULL BEFORE CREATEDATE; +> ok + +CREATE INDEX IDXNAME ON TEST(NAME); +> ok + +INSERT INTO TEST(ID, NAME) VALUES(1, 'Hi'); +> update count: 1 + +ALTER TABLE TEST ALTER COLUMN NAME SET NOT NULL; +> ok + +ALTER TABLE TEST ALTER COLUMN NAME SET NOT NULL; +> ok + +ALTER TABLE TEST ALTER COLUMN NAME SET NULL; +> ok + +ALTER TABLE TEST ALTER COLUMN NAME SET NULL; +> ok + +ALTER TABLE TEST ALTER COLUMN NAME SET DEFAULT 1; +> ok + +SELECT * FROM TEST; +> ID NAME CREATEDATE +> -- ---- ---------- +> 1 Hi 2001-01-01 +> rows: 1 + +ALTER TABLE TEST ADD MODIFY_DATE TIMESTAMP; +> ok + +CREATE MEMORY TABLE TEST_SEQ(ID INT, NAME VARCHAR); +> ok + +INSERT INTO TEST_SEQ VALUES(-1, '-1'); +> update count: 1 + +ALTER TABLE TEST_SEQ ALTER COLUMN ID IDENTITY; +> ok + +INSERT INTO TEST_SEQ VALUES(NULL, '1'); +> exception NULL_NOT_ALLOWED + +INSERT INTO TEST_SEQ VALUES(DEFAULT, '1'); +> update count: 1 + +ALTER TABLE TEST_SEQ ALTER COLUMN ID RESTART WITH 10; +> ok + +INSERT INTO TEST_SEQ VALUES(DEFAULT, '10'); +> update count: 1 + +alter table test_seq drop primary key; +> ok + +ALTER TABLE TEST_SEQ ALTER COLUMN ID INT DEFAULT 20; +> ok + +INSERT INTO TEST_SEQ VALUES(DEFAULT, '20'); +> update count: 1 + +ALTER TABLE TEST_SEQ ALTER COLUMN NAME RENAME TO DATA; +> ok + +SELECT * FROM TEST_SEQ ORDER BY ID; +> ID DATA +> -- ---- +> -1 -1 +> 1 1 +> 10 10 +> 20 20 +> rows (ordered): 4 + +SCRIPT SIMPLE NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST_SEQ"( "ID" INTEGER DEFAULT 20 NOT NULL, "DATA" CHARACTER VARYING ); +> -- 4 +/- SELECT COUNT(*) FROM PUBLIC.TEST_SEQ; +> INSERT INTO "PUBLIC"."TEST_SEQ" VALUES(-1, '-1'); +> INSERT INTO "PUBLIC"."TEST_SEQ" VALUES(1, '1'); +> INSERT INTO "PUBLIC"."TEST_SEQ" VALUES(10, '10'); +> INSERT INTO "PUBLIC"."TEST_SEQ" VALUES(20, '20'); +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "ID" INTEGER NOT NULL, "NAME" CHARACTER VARYING(255) DEFAULT 1, "CREATEDATE" CHARACTER VARYING(255) DEFAULT '2001-01-01' NOT NULL, "MODIFY_DATE" TIMESTAMP ); +> ALTER TABLE "PUBLIC"."TEST" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_2" PRIMARY KEY("ID"); +> -- 1 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> INSERT INTO "PUBLIC"."TEST" VALUES(1, 'Hi', '2001-01-01', NULL); +> CREATE INDEX "PUBLIC"."IDXNAME" ON "PUBLIC"."TEST"("NAME" NULLS FIRST); +> rows (ordered): 12 + +CREATE UNIQUE INDEX IDX_NAME_ID ON TEST(ID, NAME); +> ok + +ALTER TABLE TEST DROP COLUMN NAME; +> exception COLUMN_IS_REFERENCED_1 + +DROP INDEX IDX_NAME_ID; +> ok + +DROP INDEX IDX_NAME_ID IF EXISTS; +> ok + +ALTER TABLE TEST DROP NAME; +> ok + +DROP TABLE TEST_SEQ; +> ok + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> --------------------------------------------------------------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "ID" INTEGER NOT NULL, "CREATEDATE" CHARACTER VARYING(255) DEFAULT '2001-01-01' NOT NULL, "MODIFY_DATE" TIMESTAMP ); +> ALTER TABLE "PUBLIC"."TEST" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_2" PRIMARY KEY("ID"); +> -- 1 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> INSERT INTO "PUBLIC"."TEST" VALUES (1, '2001-01-01', NULL); +> rows (ordered): 5 + +ALTER TABLE TEST ADD NAME VARCHAR(255) NULL BEFORE CREATEDATE; +> ok + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "ID" INTEGER NOT NULL, "NAME" CHARACTER VARYING(255), "CREATEDATE" CHARACTER VARYING(255) DEFAULT '2001-01-01' NOT NULL, "MODIFY_DATE" TIMESTAMP ); +> ALTER TABLE "PUBLIC"."TEST" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_2" PRIMARY KEY("ID"); +> -- 1 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> INSERT INTO "PUBLIC"."TEST" VALUES (1, NULL, '2001-01-01', NULL); +> rows (ordered): 5 + +UPDATE TEST SET NAME = 'Hi'; +> update count: 1 + +INSERT INTO TEST VALUES(2, 'Hello', DEFAULT, DEFAULT); +> update count: 1 + +SELECT * FROM TEST; +> ID NAME CREATEDATE MODIFY_DATE +> -- ----- ---------- ----------- +> 1 Hi 2001-01-01 null +> 2 Hello 2001-01-01 null +> rows: 2 + +DROP TABLE TEST; +> ok + +create table test(id int, name varchar invisible); +> ok + +select * from test; +> ID +> -- +> rows: 0 + +alter table test alter column name set visible; +> ok + +select * from test; +> ID NAME +> -- ---- +> rows: 0 + +alter table test add modify_date timestamp invisible before name; +> ok + +select * from test; +> ID NAME +> -- ---- +> rows: 0 + +alter table test alter column modify_date timestamp visible; +> ok + +select * from test; +> ID MODIFY_DATE NAME +> -- ----------- ---- +> rows: 0 + +alter table test alter column modify_date set invisible; +> ok + +select * from test; +> ID NAME +> -- ---- +> rows: 0 + +drop table test; +> ok + +CREATE MEMORY TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR); +> ok + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> --------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "ID" INTEGER NOT NULL, "NAME" CHARACTER VARYING ); +> ALTER TABLE "PUBLIC"."TEST" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_2" PRIMARY KEY("ID"); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> rows (ordered): 4 + +INSERT INTO TEST(ID, NAME) VALUES(1, 'Hi'), (2, 'World'); +> update count: 2 + +SELECT * FROM TEST; +> ID NAME +> -- ----- +> 1 Hi +> 2 World +> rows: 2 + +SELECT * FROM TEST WHERE ? IS NULL; +{ +Hello +> ID NAME +> -- ---- +> rows: 0 +}; +> update count: 0 + +DROP TABLE TEST; +> ok + +--- limit/offset ---------------------------------------------------------------------------------------------- +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR); +> ok + +INSERT INTO TEST VALUES(1, 'Hello'), (2, 'World'), (3, 'with'), (4, 'limited'), (5, 'resources'); +> update count: 5 + +SELECT TOP 2 * FROM TEST ORDER BY ID; +> ID NAME +> -- ----- +> 1 Hello +> 2 World +> rows (ordered): 2 + +SELECT * FROM TEST ORDER BY ID LIMIT 2+0 OFFSET 1+0; +> ID NAME +> -- ----- +> 2 World +> 3 with +> rows (ordered): 2 + +SELECT * FROM TEST UNION ALL SELECT * FROM TEST ORDER BY ID LIMIT 2+0 OFFSET 1+0; +> ID NAME +> -- ----- +> 1 Hello +> 2 World +> rows (ordered): 2 + +SELECT * FROM TEST ORDER BY ID OFFSET 4; +> ID NAME +> -- --------- +> 5 resources +> rows (ordered): 1 + +SELECT ID FROM TEST GROUP BY ID UNION ALL SELECT ID FROM TEST GROUP BY ID; +> ID +> -- +> 1 +> 1 +> 2 +> 2 +> 3 +> 3 +> 4 +> 4 +> 5 +> 5 +> rows: 10 + +SELECT * FROM (SELECT ID FROM TEST GROUP BY ID); +> ID +> -- +> 1 +> 2 +> 3 +> 4 +> 5 +> rows: 5 + +EXPLAIN SELECT * FROM TEST UNION ALL SELECT * FROM TEST ORDER BY ID LIMIT 2+0 OFFSET 1+0; +>> (SELECT "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."NAME" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */) UNION ALL (SELECT "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."NAME" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */) ORDER BY 1 OFFSET 1 ROW FETCH NEXT 2 ROWS ONLY + +EXPLAIN DELETE FROM TEST WHERE ID=1; +>> DELETE FROM "PUBLIC"."TEST" /* PUBLIC.PRIMARY_KEY_2: ID = 1 */ WHERE "ID" = 1 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST2COL(A INT, B INT, C VARCHAR(255), PRIMARY KEY(A, B)); +> ok + +INSERT INTO TEST2COL VALUES(0, 0, 'Hallo'), (0, 1, 'Welt'), (1, 0, 'Hello'), (1, 1, 'World'); +> update count: 4 + +SELECT * FROM TEST2COL WHERE A=0 AND B=0; +> A B C +> - - ----- +> 0 0 Hallo +> rows: 1 + +EXPLAIN SELECT * FROM TEST2COL WHERE A=0 AND B=0; +>> SELECT "PUBLIC"."TEST2COL"."A", "PUBLIC"."TEST2COL"."B", "PUBLIC"."TEST2COL"."C" FROM "PUBLIC"."TEST2COL" /* PUBLIC.PRIMARY_KEY_E: A = 0 AND B = 0 */ WHERE ("A" = 0) AND ("B" = 0) + +SELECT * FROM TEST2COL WHERE A=0; +> A B C +> - - ----- +> 0 0 Hallo +> 0 1 Welt +> rows: 2 + +EXPLAIN SELECT * FROM TEST2COL WHERE A=0; +>> SELECT "PUBLIC"."TEST2COL"."A", "PUBLIC"."TEST2COL"."B", "PUBLIC"."TEST2COL"."C" FROM "PUBLIC"."TEST2COL" /* PUBLIC.PRIMARY_KEY_E: A = 0 */ WHERE "A" = 0 + +SELECT * FROM TEST2COL WHERE B=0; +> A B C +> - - ----- +> 0 0 Hallo +> 1 0 Hello +> rows: 2 + +EXPLAIN SELECT * FROM TEST2COL WHERE B=0; +>> SELECT "PUBLIC"."TEST2COL"."A", "PUBLIC"."TEST2COL"."B", "PUBLIC"."TEST2COL"."C" FROM "PUBLIC"."TEST2COL" /* PUBLIC.TEST2COL.tableScan */ WHERE "B" = 0 + +DROP TABLE TEST2COL; +> ok + +--- testCases ---------------------------------------------------------------------------------------------- +CREATE TABLE t_1 (ch CHARACTER(10), dec DECIMAL(10,2), do DOUBLE, lo BIGINT, "IN" INTEGER, sm SMALLINT, ty TINYINT, +da DATE DEFAULT CURRENT_DATE, ti TIME DEFAULT CURRENT_TIME, ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); +> ok + +INSERT INTO T_1 (ch, dec, do) VALUES ('name', 10.23, 0); +> update count: 1 + +SELECT COUNT(*) FROM T_1; +> COUNT(*) +> -------- +> 1 +> rows: 1 + +DROP TABLE T_1; +> ok + +--- rights ---------------------------------------------------------------------------------------------- +CREATE USER TEST_USER PASSWORD '123'; +> ok + +CREATE TABLE TEST(ID INT); +> ok + +CREATE ROLE TEST_ROLE; +> ok + +CREATE ROLE IF NOT EXISTS TEST_ROLE; +> ok + +GRANT SELECT, INSERT ON TEST TO TEST_USER; +> ok + +GRANT UPDATE ON TEST TO TEST_ROLE; +> ok + +GRANT TEST_ROLE TO TEST_USER; +> ok + +SELECT ROLE_NAME FROM INFORMATION_SCHEMA.ROLES; +> ROLE_NAME +> --------- +> PUBLIC +> TEST_ROLE +> rows: 2 + +SELECT GRANTEE, GRANTEETYPE, GRANTEDROLE, RIGHTS, TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.RIGHTS; +> GRANTEE GRANTEETYPE GRANTEDROLE RIGHTS TABLE_SCHEMA TABLE_NAME +> --------- ----------- ----------- -------------- ------------ ---------- +> TEST_ROLE ROLE null UPDATE PUBLIC TEST +> TEST_USER USER TEST_ROLE null null null +> TEST_USER USER null SELECT, INSERT PUBLIC TEST +> rows: 3 + +SELECT * FROM INFORMATION_SCHEMA.TABLE_PRIVILEGES; +> GRANTOR GRANTEE TABLE_CATALOG TABLE_SCHEMA TABLE_NAME PRIVILEGE_TYPE IS_GRANTABLE WITH_HIERARCHY +> ------- --------- ------------- ------------ ---------- -------------- ------------ -------------- +> null TEST_ROLE SCRIPT PUBLIC TEST UPDATE NO NO +> null TEST_USER SCRIPT PUBLIC TEST INSERT NO NO +> null TEST_USER SCRIPT PUBLIC TEST SELECT NO NO +> rows: 3 + +SELECT * FROM INFORMATION_SCHEMA.COLUMN_PRIVILEGES; +> GRANTOR GRANTEE TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME PRIVILEGE_TYPE IS_GRANTABLE +> ------- --------- ------------- ------------ ---------- ----------- -------------- ------------ +> null TEST_ROLE SCRIPT PUBLIC TEST ID UPDATE NO +> null TEST_USER SCRIPT PUBLIC TEST ID INSERT NO +> null TEST_USER SCRIPT PUBLIC TEST ID SELECT NO +> rows: 3 + +REVOKE INSERT ON TEST FROM TEST_USER; +> ok + +REVOKE TEST_ROLE FROM TEST_USER; +> ok + +SELECT GRANTEE, GRANTEETYPE, GRANTEDROLE, RIGHTS, TABLE_NAME FROM INFORMATION_SCHEMA.RIGHTS; +> GRANTEE GRANTEETYPE GRANTEDROLE RIGHTS TABLE_NAME +> --------- ----------- ----------- ------ ---------- +> TEST_ROLE ROLE null UPDATE TEST +> TEST_USER USER null SELECT TEST +> rows: 2 + +SELECT * FROM INFORMATION_SCHEMA.TABLE_PRIVILEGES; +> GRANTOR GRANTEE TABLE_CATALOG TABLE_SCHEMA TABLE_NAME PRIVILEGE_TYPE IS_GRANTABLE WITH_HIERARCHY +> ------- --------- ------------- ------------ ---------- -------------- ------------ -------------- +> null TEST_ROLE SCRIPT PUBLIC TEST UPDATE NO NO +> null TEST_USER SCRIPT PUBLIC TEST SELECT NO NO +> rows: 2 + +DROP USER TEST_USER; +> ok + +DROP TABLE TEST; +> ok + +DROP ROLE TEST_ROLE; +> ok + +SELECT * FROM INFORMATION_SCHEMA.ROLES; +> ROLE_NAME REMARKS +> --------- ------- +> PUBLIC null +> rows: 1 + +SELECT * FROM INFORMATION_SCHEMA.RIGHTS; +> GRANTEE GRANTEETYPE GRANTEDROLE RIGHTS TABLE_SCHEMA TABLE_NAME +> ------- ----------- ----------- ------ ------------ ---------- +> rows: 0 + +--- plan ---------------------------------------------------------------------------------------------- +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +INSERT INTO TEST VALUES(?, ?); +{ +1, Hello +2, World +3, Peace +}; +> update count: 3 + +EXPLAIN INSERT INTO TEST VALUES(1, 'Test'); +>> INSERT INTO "PUBLIC"."TEST"("ID", "NAME") VALUES (1, 'Test') + +EXPLAIN INSERT INTO TEST VALUES(1, 'Test'), (2, 'World'); +>> INSERT INTO "PUBLIC"."TEST"("ID", "NAME") VALUES (1, 'Test'), (2, 'World') + +EXPLAIN INSERT INTO TEST SELECT DISTINCT ID+1, NAME FROM TEST; +>> INSERT INTO "PUBLIC"."TEST"("ID", "NAME") SELECT DISTINCT "ID" + 1, "NAME" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT DISTINCT ID + 1, NAME FROM TEST; +>> SELECT DISTINCT "ID" + 1, "NAME" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN SELECT * FROM TEST WHERE 1=0; +>> SELECT "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."NAME" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan: FALSE */ WHERE FALSE + +EXPLAIN SELECT TOP 1 * FROM TEST FOR UPDATE; +>> SELECT "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."NAME" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ FETCH FIRST ROW ONLY FOR UPDATE + +EXPLAIN SELECT COUNT(NAME) FROM TEST WHERE ID=1; +>> SELECT COUNT("NAME") FROM "PUBLIC"."TEST" /* PUBLIC.PRIMARY_KEY_2: ID = 1 */ WHERE "ID" = 1 + +EXPLAIN SELECT * FROM TEST WHERE (ID>=1 AND ID<=2) OR (ID>0 AND ID<3) AND (ID<>6) ORDER BY NAME NULLS FIRST, 1 NULLS LAST, (1+1) DESC; +>> SELECT "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."NAME" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ WHERE (("ID" >= 1) AND ("ID" <= 2)) OR (("ID" <> 6) AND ("ID" > 0) AND ("ID" < 3)) ORDER BY 2 NULLS FIRST, 1 NULLS LAST + +EXPLAIN SELECT * FROM TEST WHERE ID=1 GROUP BY NAME, ID; +>> SELECT "PUBLIC"."TEST"."ID", "PUBLIC"."TEST"."NAME" FROM "PUBLIC"."TEST" /* PUBLIC.PRIMARY_KEY_2: ID = 1 */ WHERE "ID" = 1 GROUP BY "NAME", "ID" + +EXPLAIN PLAN FOR UPDATE TEST SET NAME='Hello', ID=1 WHERE NAME LIKE 'T%' ESCAPE 'x'; +>> UPDATE "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ SET "ID" = 1, "NAME" = 'Hello' WHERE "NAME" LIKE 'T%' ESCAPE 'x' + +EXPLAIN PLAN FOR DELETE FROM TEST; +>> DELETE FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN PLAN FOR SELECT NAME, COUNT(*) FROM TEST GROUP BY NAME HAVING COUNT(*) > 1; +>> SELECT "NAME", COUNT(*) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ GROUP BY "NAME" HAVING COUNT(*) > 1 + +EXPLAIN PLAN FOR SELECT * FROM test t1 inner join test t2 on t1.id=t2.id and t2.name is not null where t1.id=1; +>> SELECT "T1"."ID", "T1"."NAME", "T2"."ID", "T2"."NAME" FROM "PUBLIC"."TEST" "T1" /* PUBLIC.PRIMARY_KEY_2: ID = 1 */ /* WHERE T1.ID = 1 */ INNER JOIN "PUBLIC"."TEST" "T2" /* PUBLIC.PRIMARY_KEY_2: ID = T1.ID */ ON 1=1 WHERE ("T1"."ID" = 1) AND ("T2"."NAME" IS NOT NULL) AND ("T1"."ID" = "T2"."ID") + +EXPLAIN PLAN FOR SELECT * FROM test t1 left outer join test t2 on t1.id=t2.id and t2.name is not null where t1.id=1; +>> SELECT "T1"."ID", "T1"."NAME", "T2"."ID", "T2"."NAME" FROM "PUBLIC"."TEST" "T1" /* PUBLIC.PRIMARY_KEY_2: ID = 1 */ /* WHERE T1.ID = 1 */ LEFT OUTER JOIN "PUBLIC"."TEST" "T2" /* PUBLIC.PRIMARY_KEY_2: ID = T1.ID */ ON ("T2"."NAME" IS NOT NULL) AND ("T1"."ID" = "T2"."ID") WHERE "T1"."ID" = 1 + +EXPLAIN PLAN FOR SELECT * FROM test t1 left outer join test t2 on t1.id=t2.id and t2.name is null where t1.id=1; +>> SELECT "T1"."ID", "T1"."NAME", "T2"."ID", "T2"."NAME" FROM "PUBLIC"."TEST" "T1" /* PUBLIC.PRIMARY_KEY_2: ID = 1 */ /* WHERE T1.ID = 1 */ LEFT OUTER JOIN "PUBLIC"."TEST" "T2" /* PUBLIC.PRIMARY_KEY_2: ID = T1.ID */ ON ("T2"."NAME" IS NULL) AND ("T1"."ID" = "T2"."ID") WHERE "T1"."ID" = 1 + +EXPLAIN PLAN FOR SELECT * FROM TEST T1 WHERE EXISTS(SELECT * FROM TEST T2 WHERE T1.ID-1 = T2.ID); +>> SELECT "T1"."ID", "T1"."NAME" FROM "PUBLIC"."TEST" "T1" /* PUBLIC.TEST.tableScan */ WHERE EXISTS( SELECT "T2"."ID", "T2"."NAME" FROM "PUBLIC"."TEST" "T2" /* PUBLIC.PRIMARY_KEY_2: ID = (T1.ID - 1) */ WHERE ("T1"."ID" - 1) = "T2"."ID") + +EXPLAIN PLAN FOR SELECT * FROM TEST T1 WHERE ID IN(1, 2); +>> SELECT "T1"."ID", "T1"."NAME" FROM "PUBLIC"."TEST" "T1" /* PUBLIC.PRIMARY_KEY_2: ID IN(1, 2) */ WHERE "ID" IN(1, 2) + +EXPLAIN PLAN FOR SELECT * FROM TEST T1 WHERE ID IN(SELECT ID FROM TEST); +>> SELECT "T1"."ID", "T1"."NAME" FROM "PUBLIC"."TEST" "T1" /* PUBLIC.PRIMARY_KEY_2: ID IN(SELECT DISTINCT ID FROM PUBLIC.TEST /* PUBLIC.TEST.tableScan */) */ WHERE "ID" IN( SELECT DISTINCT "ID" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */) + +EXPLAIN PLAN FOR SELECT * FROM TEST T1 WHERE ID NOT IN(SELECT ID FROM TEST); +>> SELECT "T1"."ID", "T1"."NAME" FROM "PUBLIC"."TEST" "T1" /* PUBLIC.TEST.tableScan */ WHERE "ID" NOT IN( SELECT DISTINCT "ID" FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */) + +EXPLAIN PLAN FOR SELECT CAST(ID AS VARCHAR(255)) FROM TEST; +>> SELECT CAST("ID" AS CHARACTER VARYING(255)) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +EXPLAIN PLAN FOR SELECT LEFT(NAME, 2) FROM TEST; +>> SELECT LEFT("NAME", 2) FROM "PUBLIC"."TEST" /* PUBLIC.TEST.tableScan */ + +SELECT * FROM test t1 inner join test t2 on t1.id=t2.id and t2.name is not null where t1.id=1; +> ID NAME ID NAME +> -- ----- -- ----- +> 1 Hello 1 Hello +> rows: 1 + +SELECT * FROM test t1 left outer join test t2 on t1.id=t2.id and t2.name is not null where t1.id=1; +> ID NAME ID NAME +> -- ----- -- ----- +> 1 Hello 1 Hello +> rows: 1 + +SELECT * FROM test t1 left outer join test t2 on t1.id=t2.id and t2.name is null where t1.id=1; +> ID NAME ID NAME +> -- ----- ---- ---- +> 1 Hello null null +> rows: 1 + +DROP TABLE TEST; +> ok + +--- union ---------------------------------------------------------------------------------------------- +SELECT * FROM SYSTEM_RANGE(1,2) UNION ALL SELECT * FROM SYSTEM_RANGE(1,2) ORDER BY 1; +> X +> - +> 1 +> 1 +> 2 +> 2 +> rows (ordered): 4 + +EXPLAIN (SELECT * FROM SYSTEM_RANGE(1,2) UNION ALL SELECT * FROM SYSTEM_RANGE(1,2) ORDER BY 1); +>> (SELECT "SYSTEM_RANGE"."X" FROM SYSTEM_RANGE(1, 2) /* range index */) UNION ALL (SELECT "SYSTEM_RANGE"."X" FROM SYSTEM_RANGE(1, 2) /* range index */) ORDER BY 1 + +CREATE TABLE CHILDREN(ID INT PRIMARY KEY, NAME VARCHAR(255), CLASS INT); +> ok + +CREATE TABLE CLASSES(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +INSERT INTO CHILDREN VALUES(?, ?, ?); +{ +0, Joe, 0 +1, Anne, 1 +2, Joerg, 1 +3, Petra, 2 +}; +> update count: 4 + +INSERT INTO CLASSES VALUES(?, ?); +{ +0, Kindergarden +1, Class 1 +2, Class 2 +3, Class 3 +4, Class 4 +}; +> update count: 5 + +SELECT * FROM CHILDREN UNION ALL SELECT * FROM CHILDREN ORDER BY ID, NAME FOR UPDATE; +> ID NAME CLASS +> -- ----- ----- +> 0 Joe 0 +> 0 Joe 0 +> 1 Anne 1 +> 1 Anne 1 +> 2 Joerg 1 +> 2 Joerg 1 +> 3 Petra 2 +> 3 Petra 2 +> rows (ordered): 8 + +EXPLAIN SELECT * FROM CHILDREN UNION ALL SELECT * FROM CHILDREN ORDER BY ID, NAME FOR UPDATE; +>> (SELECT "PUBLIC"."CHILDREN"."ID", "PUBLIC"."CHILDREN"."NAME", "PUBLIC"."CHILDREN"."CLASS" FROM "PUBLIC"."CHILDREN" /* PUBLIC.CHILDREN.tableScan */ FOR UPDATE) UNION ALL (SELECT "PUBLIC"."CHILDREN"."ID", "PUBLIC"."CHILDREN"."NAME", "PUBLIC"."CHILDREN"."CLASS" FROM "PUBLIC"."CHILDREN" /* PUBLIC.CHILDREN.tableScan */ FOR UPDATE) ORDER BY 1, 2 FOR UPDATE + +SELECT 'Child', ID, NAME FROM CHILDREN UNION SELECT 'Class', ID, NAME FROM CLASSES; +> 'Child' ID NAME +> ------- -- ------------ +> Child 0 Joe +> Child 1 Anne +> Child 2 Joerg +> Child 3 Petra +> Class 0 Kindergarden +> Class 1 Class1 +> Class 2 Class2 +> Class 3 Class3 +> Class 4 Class4 +> rows: 9 + +EXPLAIN SELECT 'Child', ID, NAME FROM CHILDREN UNION SELECT 'Class', ID, NAME FROM CLASSES; +>> (SELECT 'Child', "ID", "NAME" FROM "PUBLIC"."CHILDREN" /* PUBLIC.CHILDREN.tableScan */) UNION (SELECT 'Class', "ID", "NAME" FROM "PUBLIC"."CLASSES" /* PUBLIC.CLASSES.tableScan */) + +SELECT * FROM CHILDREN EXCEPT SELECT * FROM CHILDREN WHERE CLASS=0; +> ID NAME CLASS +> -- ----- ----- +> 1 Anne 1 +> 2 Joerg 1 +> 3 Petra 2 +> rows: 3 + +EXPLAIN SELECT * FROM CHILDREN EXCEPT SELECT * FROM CHILDREN WHERE CLASS=0; +>> (SELECT "PUBLIC"."CHILDREN"."ID", "PUBLIC"."CHILDREN"."NAME", "PUBLIC"."CHILDREN"."CLASS" FROM "PUBLIC"."CHILDREN" /* PUBLIC.CHILDREN.tableScan */) EXCEPT (SELECT "PUBLIC"."CHILDREN"."ID", "PUBLIC"."CHILDREN"."NAME", "PUBLIC"."CHILDREN"."CLASS" FROM "PUBLIC"."CHILDREN" /* PUBLIC.CHILDREN.tableScan */ WHERE "CLASS" = 0) + +EXPLAIN SELECT CLASS FROM CHILDREN INTERSECT SELECT ID FROM CLASSES; +>> (SELECT "CLASS" FROM "PUBLIC"."CHILDREN" /* PUBLIC.CHILDREN.tableScan */) INTERSECT (SELECT "ID" FROM "PUBLIC"."CLASSES" /* PUBLIC.CLASSES.tableScan */) + +SELECT CLASS FROM CHILDREN INTERSECT SELECT ID FROM CLASSES; +> CLASS +> ----- +> 0 +> 1 +> 2 +> rows: 3 + +EXPLAIN SELECT * FROM CHILDREN EXCEPT SELECT * FROM CHILDREN WHERE CLASS=0; +>> (SELECT "PUBLIC"."CHILDREN"."ID", "PUBLIC"."CHILDREN"."NAME", "PUBLIC"."CHILDREN"."CLASS" FROM "PUBLIC"."CHILDREN" /* PUBLIC.CHILDREN.tableScan */) EXCEPT (SELECT "PUBLIC"."CHILDREN"."ID", "PUBLIC"."CHILDREN"."NAME", "PUBLIC"."CHILDREN"."CLASS" FROM "PUBLIC"."CHILDREN" /* PUBLIC.CHILDREN.tableScan */ WHERE "CLASS" = 0) + +SELECT * FROM CHILDREN CH, CLASSES CL WHERE CH.CLASS = CL.ID; +> ID NAME CLASS ID NAME +> -- ----- ----- -- ------------ +> 0 Joe 0 0 Kindergarden +> 1 Anne 1 1 Class1 +> 2 Joerg 1 1 Class1 +> 3 Petra 2 2 Class2 +> rows: 4 + +SELECT CH.ID CH_ID, CH.NAME CH_NAME, CL.ID CL_ID, CL.NAME CL_NAME FROM CHILDREN CH, CLASSES CL WHERE CH.CLASS = CL.ID; +> CH_ID CH_NAME CL_ID CL_NAME +> ----- ------- ----- ------------ +> 0 Joe 0 Kindergarden +> 1 Anne 1 Class1 +> 2 Joerg 1 Class1 +> 3 Petra 2 Class2 +> rows: 4 + +CREATE VIEW CHILDREN_CLASSES(CH_ID, CH_NAME, CL_ID, CL_NAME) AS +SELECT CH.ID CH_ID1, CH.NAME CH_NAME2, CL.ID CL_ID3, CL.NAME CL_NAME4 +FROM CHILDREN CH, CLASSES CL WHERE CH.CLASS = CL.ID; +> ok + +SELECT * FROM CHILDREN_CLASSES WHERE CH_NAME <> 'X'; +> CH_ID CH_NAME CL_ID CL_NAME +> ----- ------- ----- ------------ +> 0 Joe 0 Kindergarden +> 1 Anne 1 Class1 +> 2 Joerg 1 Class1 +> 3 Petra 2 Class2 +> rows: 4 + +CREATE VIEW CHILDREN_CLASS1 AS SELECT * FROM CHILDREN_CLASSES WHERE CL_ID=1; +> ok + +SELECT * FROM CHILDREN_CLASS1; +> CH_ID CH_NAME CL_ID CL_NAME +> ----- ------- ----- ------- +> 1 Anne 1 Class1 +> 2 Joerg 1 Class1 +> rows: 2 + +CREATE VIEW CHILDREN_CLASS2 AS SELECT * FROM CHILDREN_CLASSES WHERE CL_ID=2; +> ok + +SELECT * FROM CHILDREN_CLASS2; +> CH_ID CH_NAME CL_ID CL_NAME +> ----- ------- ----- ------- +> 3 Petra 2 Class2 +> rows: 1 + +CREATE VIEW CHILDREN_CLASS12 AS SELECT * FROM CHILDREN_CLASS1 UNION ALL SELECT * FROM CHILDREN_CLASS1; +> ok + +SELECT * FROM CHILDREN_CLASS12; +> CH_ID CH_NAME CL_ID CL_NAME +> ----- ------- ----- ------- +> 1 Anne 1 Class1 +> 1 Anne 1 Class1 +> 2 Joerg 1 Class1 +> 2 Joerg 1 Class1 +> rows: 4 + +DROP VIEW CHILDREN_CLASS2; +> ok + +DROP VIEW CHILDREN_CLASS1 cascade; +> ok + +DROP VIEW CHILDREN_CLASSES; +> ok + +DROP VIEW CHILDREN_CLASS12; +> exception VIEW_NOT_FOUND_1 + +CREATE VIEW V_UNION AS SELECT * FROM CHILDREN UNION ALL SELECT * FROM CHILDREN; +> ok + +SELECT * FROM V_UNION WHERE ID=1; +> ID NAME CLASS +> -- ---- ----- +> 1 Anne 1 +> 1 Anne 1 +> rows: 2 + +EXPLAIN SELECT * FROM V_UNION WHERE ID=1; +>> SELECT "PUBLIC"."V_UNION"."ID", "PUBLIC"."V_UNION"."NAME", "PUBLIC"."V_UNION"."CLASS" FROM "PUBLIC"."V_UNION" /* (SELECT PUBLIC.CHILDREN.ID, PUBLIC.CHILDREN.NAME, PUBLIC.CHILDREN.CLASS FROM PUBLIC.CHILDREN /* PUBLIC.PRIMARY_KEY_9: ID IS NOT DISTINCT FROM ?1 */ /* scanCount: 2 */ WHERE PUBLIC.CHILDREN.ID IS NOT DISTINCT FROM ?1) UNION ALL (SELECT PUBLIC.CHILDREN.ID, PUBLIC.CHILDREN.NAME, PUBLIC.CHILDREN.CLASS FROM PUBLIC.CHILDREN /* PUBLIC.PRIMARY_KEY_9: ID IS NOT DISTINCT FROM ?1 */ /* scanCount: 2 */ WHERE PUBLIC.CHILDREN.ID IS NOT DISTINCT FROM ?1): ID = 1 */ WHERE "ID" = 1 + +CREATE VIEW V_EXCEPT AS SELECT * FROM CHILDREN EXCEPT SELECT * FROM CHILDREN WHERE ID=2; +> ok + +SELECT * FROM V_EXCEPT WHERE ID=1; +> ID NAME CLASS +> -- ---- ----- +> 1 Anne 1 +> rows: 1 + +EXPLAIN SELECT * FROM V_EXCEPT WHERE ID=1; +>> SELECT "PUBLIC"."V_EXCEPT"."ID", "PUBLIC"."V_EXCEPT"."NAME", "PUBLIC"."V_EXCEPT"."CLASS" FROM "PUBLIC"."V_EXCEPT" /* (SELECT DISTINCT PUBLIC.CHILDREN.ID, PUBLIC.CHILDREN.NAME, PUBLIC.CHILDREN.CLASS FROM PUBLIC.CHILDREN /* PUBLIC.PRIMARY_KEY_9: ID IS NOT DISTINCT FROM ?1 */ /* scanCount: 2 */ WHERE PUBLIC.CHILDREN.ID IS NOT DISTINCT FROM ?1) EXCEPT (SELECT DISTINCT PUBLIC.CHILDREN.ID, PUBLIC.CHILDREN.NAME, PUBLIC.CHILDREN.CLASS FROM PUBLIC.CHILDREN /* PUBLIC.PRIMARY_KEY_9: ID = 2 */ /* scanCount: 2 */ WHERE ID = 2): ID = 1 */ WHERE "ID" = 1 + +CREATE VIEW V_INTERSECT AS SELECT ID, NAME FROM CHILDREN INTERSECT SELECT * FROM CLASSES; +> ok + +SELECT * FROM V_INTERSECT WHERE ID=1; +> ID NAME +> -- ---- +> rows: 0 + +EXPLAIN SELECT * FROM V_INTERSECT WHERE ID=1; +>> SELECT "PUBLIC"."V_INTERSECT"."ID", "PUBLIC"."V_INTERSECT"."NAME" FROM "PUBLIC"."V_INTERSECT" /* (SELECT DISTINCT ID, NAME FROM PUBLIC.CHILDREN /* PUBLIC.PRIMARY_KEY_9: ID IS NOT DISTINCT FROM ?1 */ /* scanCount: 2 */ WHERE ID IS NOT DISTINCT FROM ?1) INTERSECT (SELECT DISTINCT PUBLIC.CLASSES.ID, PUBLIC.CLASSES.NAME FROM PUBLIC.CLASSES /* PUBLIC.PRIMARY_KEY_5: ID IS NOT DISTINCT FROM ?1 */ /* scanCount: 2 */ WHERE PUBLIC.CLASSES.ID IS NOT DISTINCT FROM ?1): ID = 1 */ WHERE "ID" = 1 + +DROP VIEW V_UNION; +> ok + +DROP VIEW V_EXCEPT; +> ok + +DROP VIEW V_INTERSECT; +> ok + +DROP TABLE CHILDREN; +> ok + +DROP TABLE CLASSES; +> ok + +--- view ---------------------------------------------------------------------------------------------- +CREATE CACHED TABLE TEST_A(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +CREATE CACHED TABLE TEST_B(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +SELECT A.ID AID, A.NAME A_NAME, B.ID BID, B.NAME B_NAME FROM TEST_A A INNER JOIN TEST_B B WHERE A.ID = B.ID; +> AID A_NAME BID B_NAME +> --- ------ --- ------ +> rows: 0 + +INSERT INTO TEST_B VALUES(1, 'Hallo'), (2, 'Welt'), (3, 'Rekord'); +> update count: 3 + +CREATE VIEW IF NOT EXISTS TEST_ALL AS SELECT A.ID AID, A.NAME A_NAME, B.ID BID, B.NAME B_NAME FROM TEST_A A, TEST_B B WHERE A.ID = B.ID; +> ok + +SELECT COUNT(*) FROM TEST_ALL; +> COUNT(*) +> -------- +> 0 +> rows: 1 + +CREATE VIEW IF NOT EXISTS TEST_ALL AS +SELECT * FROM TEST_A; +> ok + +INSERT INTO TEST_A VALUES(1, 'Hello'), (2, 'World'), (3, 'Record'); +> update count: 3 + +SELECT * FROM TEST_ALL; +> AID A_NAME BID B_NAME +> --- ------ --- ------ +> 1 Hello 1 Hallo +> 2 World 2 Welt +> 3 Record 3 Rekord +> rows: 3 + +SELECT * FROM TEST_ALL WHERE AID=1; +> AID A_NAME BID B_NAME +> --- ------ --- ------ +> 1 Hello 1 Hallo +> rows: 1 + +SELECT * FROM TEST_ALL WHERE AID>0; +> AID A_NAME BID B_NAME +> --- ------ --- ------ +> 1 Hello 1 Hallo +> 2 World 2 Welt +> 3 Record 3 Rekord +> rows: 3 + +SELECT * FROM TEST_ALL WHERE AID<2; +> AID A_NAME BID B_NAME +> --- ------ --- ------ +> 1 Hello 1 Hallo +> rows: 1 + +SELECT * FROM TEST_ALL WHERE AID<=2; +> AID A_NAME BID B_NAME +> --- ------ --- ------ +> 1 Hello 1 Hallo +> 2 World 2 Welt +> rows: 2 + +SELECT * FROM TEST_ALL WHERE AID>=2; +> AID A_NAME BID B_NAME +> --- ------ --- ------ +> 2 World 2 Welt +> 3 Record 3 Rekord +> rows: 2 + +CREATE VIEW TEST_A_SUB AS SELECT * FROM TEST_A WHERE ID < 2; +> ok + +SELECT TABLE_NAME, VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = 'PUBLIC'; +> TABLE_NAME VIEW_DEFINITION +> ---------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> TEST_ALL SELECT "A"."ID" AS "AID", "A"."NAME" AS "A_NAME", "B"."ID" AS "BID", "B"."NAME" AS "B_NAME" FROM "PUBLIC"."TEST_A" "A" INNER JOIN "PUBLIC"."TEST_B" "B" ON 1=1 WHERE "A"."ID" = "B"."ID" +> TEST_A_SUB SELECT "PUBLIC"."TEST_A"."ID", "PUBLIC"."TEST_A"."NAME" FROM "PUBLIC"."TEST_A" WHERE "ID" < 2 +> rows: 2 + +SELECT * FROM TEST_A_SUB WHERE NAME IS NOT NULL; +> ID NAME +> -- ----- +> 1 Hello +> rows: 1 + +DROP VIEW TEST_A_SUB; +> ok + +DROP TABLE TEST_A cascade; +> ok + +DROP TABLE TEST_B cascade; +> ok + +DROP VIEW TEST_ALL; +> exception VIEW_NOT_FOUND_1 + +DROP VIEW IF EXISTS TEST_ALL; +> ok + +--- commit/rollback ---------------------------------------------------------------------------------------------- +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +SET AUTOCOMMIT FALSE; +> ok + +INSERT INTO TEST VALUES(1, 'Test'); +> update count: 1 + +ROLLBACK; +> ok + +SELECT * FROM TEST; +> ID NAME +> -- ---- +> rows: 0 + +INSERT INTO TEST VALUES(1, 'Test2'); +> update count: 1 + +SAVEPOINT TEST; +> ok + +INSERT INTO TEST VALUES(2, 'World'); +> update count: 1 + +ROLLBACK TO SAVEPOINT NOT_EXISTING; +> exception SAVEPOINT_IS_INVALID_1 + +ROLLBACK TO SAVEPOINT TEST; +> ok + +SELECT * FROM TEST; +> ID NAME +> -- ----- +> 1 Test2 +> rows: 1 + +ROLLBACK WORK; +> ok + +SELECT * FROM TEST; +> ID NAME +> -- ---- +> rows: 0 + +INSERT INTO TEST VALUES(1, 'Test3'); +> update count: 1 + +SAVEPOINT TEST3; +> ok + +INSERT INTO TEST VALUES(2, 'World2'); +> update count: 1 + +ROLLBACK TO SAVEPOINT TEST3; +> ok + +COMMIT WORK; +> ok + +SELECT * FROM TEST; +> ID NAME +> -- ----- +> 1 Test3 +> rows: 1 + +SET AUTOCOMMIT TRUE; +> ok + +DROP TABLE TEST; +> ok + +--- insert..select ---------------------------------------------------------------------------------------------- +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +INSERT INTO TEST VALUES(0, 'Hello'); +> update count: 1 + +INSERT INTO TEST SELECT ID+1, NAME||'+' FROM TEST; +> update count: 1 + +INSERT INTO TEST SELECT ID+2, NAME||'+' FROM TEST; +> update count: 2 + +INSERT INTO TEST SELECT ID+4, NAME||'+' FROM TEST; +> update count: 4 + +SELECT * FROM TEST; +> ID NAME +> -- -------- +> 0 Hello +> 1 Hello+ +> 2 Hello+ +> 3 Hello++ +> 4 Hello+ +> 5 Hello++ +> 6 Hello++ +> 7 Hello+++ +> rows: 8 + +DROP TABLE TEST; +> ok + +--- syntax errors ---------------------------------------------------------------------------------------------- +CREATE SOMETHING STRANGE; +> exception SYNTAX_ERROR_2 + +SELECT T1.* T2; +> exception SYNTAX_ERROR_1 + +select replace('abchihihi', 'i', 'o') abcehohoho, replace('this is tom', 'i') 1e_th_st_om from test; +> exception SYNTAX_ERROR_1 + +select monthname(date )'005-0E9-12') d_set fm test; +> exception SYNTAX_ERROR_1 + +call substring('bob', 2, -1); +> '' +> -- +> +> rows: 1 + +--- exists ---------------------------------------------------------------------------------------------- +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +INSERT INTO TEST VALUES(0, NULL); +> update count: 1 + +INSERT INTO TEST VALUES(1, 'Hello'); +> update count: 1 + +INSERT INTO TEST VALUES(2, 'World'); +> update count: 1 + +SELECT * FROM TEST T WHERE NOT EXISTS( +SELECT * FROM TEST T2 WHERE T.ID > T2.ID); +> ID NAME +> -- ---- +> 0 null +> rows: 1 + +DROP TABLE TEST; +> ok + +--- subquery ---------------------------------------------------------------------------------------------- +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +INSERT INTO TEST VALUES(0, NULL); +> update count: 1 + +INSERT INTO TEST VALUES(1, 'Hello'); +> update count: 1 + +select * from test where (select max(t1.id) from test t1) between 0 and 100; +> ID NAME +> -- ----- +> 0 null +> 1 Hello +> rows: 2 + +INSERT INTO TEST VALUES(2, 'World'); +> update count: 1 + +SELECT * FROM TEST T WHERE T.ID = (SELECT T2.ID FROM TEST T2 WHERE T2.ID=T.ID); +> ID NAME +> -- ----- +> 0 null +> 1 Hello +> 2 World +> rows: 3 + +SELECT (SELECT T2.NAME FROM TEST T2 WHERE T2.ID=T.ID), T.NAME FROM TEST T; +> (SELECT T2.NAME FROM PUBLIC.TEST T2 WHERE T2.ID = T.ID) NAME +> ------------------------------------------------------- ----- +> Hello Hello +> World World +> null null +> rows: 3 + +SELECT (SELECT SUM(T2.ID) FROM TEST T2 WHERE T2.ID>T.ID), T.ID FROM TEST T; +> (SELECT SUM(T2.ID) FROM PUBLIC.TEST T2 WHERE T2.ID > T.ID) ID +> ---------------------------------------------------------- -- +> 2 1 +> 3 0 +> null 2 +> rows: 3 + +select * from test t where t.id+1 in (select id from test); +> ID NAME +> -- ----- +> 0 null +> 1 Hello +> rows: 2 + +select * from test t where t.id in (select id from test where id=t.id); +> ID NAME +> -- ----- +> 0 null +> 1 Hello +> 2 World +> rows: 3 + +select 1 from test, test where 1 in (select 1 from test where id=1); +> 1 +> - +> 1 +> 1 +> 1 +> 1 +> 1 +> 1 +> 1 +> 1 +> 1 +> rows: 9 + +select * from test, test where id=id; +> exception AMBIGUOUS_COLUMN_NAME_1 + +select 1 from test, test where id=id; +> exception AMBIGUOUS_COLUMN_NAME_1 + +select 1 from test where id in (select id from test, test); +> exception AMBIGUOUS_COLUMN_NAME_1 + +DROP TABLE TEST; +> ok + +--- group by ---------------------------------------------------------------------------------------------- +CREATE TABLE TEST(A INT, B INT, "VALUE" INT, UNIQUE(A, B)); +> ok + +INSERT INTO TEST VALUES(?, ?, ?); +{ +NULL, NULL, NULL +NULL, 0, 0 +NULL, 1, 10 +0, 0, -1 +0, 1, 100 +1, 0, 200 +1, 1, 300 +}; +> update count: 7 + +SELECT A, B, COUNT(*) CAL, COUNT(A) CA, COUNT(B) CB, MIN("VALUE") MI, MAX("VALUE") MA, SUM("VALUE") S FROM TEST GROUP BY A, B; +> A B CAL CA CB MI MA S +> ---- ---- --- -- -- ---- ---- ---- +> 0 0 1 1 1 -1 -1 -1 +> 0 1 1 1 1 100 100 100 +> 1 0 1 1 1 200 200 200 +> 1 1 1 1 1 300 300 300 +> null 0 1 0 1 0 0 0 +> null 1 1 0 1 10 10 10 +> null null 1 0 0 null null null +> rows: 7 + +DROP TABLE TEST; +> ok + +--- data types (blob, clob, varchar_ignorecase) ---------------------------------------------------------------------------------------------- +CREATE TABLE TEST(ID INT, XB BINARY(3), XBL BLOB, XO OTHER, XCL CLOB, XVI VARCHAR_IGNORECASE); +> ok + +INSERT INTO TEST VALUES(0, X'', X'', X'', '', ''); +> update count: 1 + +INSERT INTO TEST VALUES(1, X'0101', X'0101', X'0101', 'abc', 'aa'); +> update count: 1 + +INSERT INTO TEST VALUES(2, X'0AFF', X'08FE', X'F0F1', 'AbCdEfG', 'ZzAaBb'); +> update count: 1 + +INSERT INTO TEST VALUES(3, + X'112233', + X'112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff', + X'112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff', + 'AbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYz', + 'AbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYz'); +> update count: 1 + +INSERT INTO TEST VALUES(4, NULL, NULL, NULL, NULL, NULL); +> update count: 1 + +SELECT ID, XB, XBL, XO, XCL, XVI FROM TEST; +> ID XB XBL XO XCL XVI +> -- --------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> 0 X'000000' X'' X'' +> 1 X'010100' X'0101' X'0101' abc aa +> 2 X'0aff00' X'08fe' X'f0f1' AbCdEfG ZzAaBb +> 3 X'112233' X'112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff' X'112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff112233445566778899aabbccddeeff' AbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYz AbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYz +> 4 null null null null null +> rows: 5 + +SELECT ID FROM TEST WHERE XCL = XCL; +> ID +> -- +> 0 +> 1 +> 2 +> 3 +> rows: 4 + +SELECT ID FROM TEST WHERE XCL LIKE 'abc%'; +> ID +> -- +> 1 +> rows: 1 + +SELECT ID FROM TEST WHERE XVI LIKE 'abc%'; +> ID +> -- +> 3 +> rows: 1 + +SELECT 'abc', 'Papa Joe''s', CAST(-1 AS SMALLINT), CAST(2 AS BIGINT), CAST(0 AS DOUBLE), CAST('0a0f' AS BINARY(4)) B, CAST(125 AS TINYINT), TRUE, FALSE FROM TEST WHERE ID=1; +> 'abc' 'Papa Joe''s' -1 2 0.0 B 125 TRUE FALSE +> ----- ------------- -- - --- ----------- --- ---- ----- +> abc Papa Joe's -1 2 0.0 X'30613066' 125 TRUE FALSE +> rows: 1 + +-- ' This apostrophe is here to fix syntax highlighting in the text editors. + +SELECT CAST('abcd' AS VARCHAR(255)) C1, CAST('ef_gh' AS VARCHAR(3)) C2; +> C1 C2 +> ---- --- +> abcd ef_ +> rows: 1 + +DROP TABLE TEST; +> ok + +--- data types (date and time) ---------------------------------------------------------------------------------------------- +CREATE MEMORY TABLE TEST(ID INT, XT TIME, XD DATE, XTS TIMESTAMP(9)); +> ok + +INSERT INTO TEST VALUES(0, '0:0:0','1-2-3','2-3-4 0:0:0'); +> update count: 1 + +INSERT INTO TEST VALUES(1, '01:02:03','2001-02-03','2001-02-29 0:0:0'); +> exception INVALID_DATETIME_CONSTANT_2 + +INSERT INTO TEST VALUES(1, '24:62:03','2001-02-03','2001-02-01 0:0:0'); +> exception INVALID_DATETIME_CONSTANT_2 + +INSERT INTO TEST VALUES(1, '23:02:03','2001-04-31','2001-02-01 0:0:0'); +> exception INVALID_DATETIME_CONSTANT_2 + +INSERT INTO TEST VALUES(1,'1:2:3','4-5-6','7-8-9 0:1:2'); +> update count: 1 + +INSERT INTO TEST VALUES(2,'23:59:59','1999-12-31','1999-12-31 23:59:59.123456789'); +> update count: 1 + +INSERT INTO TEST VALUES(NULL,NULL,NULL,NULL); +> update count: 1 + +SELECT * FROM TEST; +> ID XT XD XTS +> ---- -------- ---------- ----------------------------- +> 0 00:00:00 0001-02-03 0002-03-04 00:00:00 +> 1 01:02:03 0004-05-06 0007-08-09 00:01:02 +> 2 23:59:59 1999-12-31 1999-12-31 23:59:59.123456789 +> null null null null +> rows: 4 + +SELECT XD+1, XD-1, XD-XD FROM TEST; +> DATEADD(DAY, 1, XD) DATEADD(DAY, -1, XD) XD - XD +> ------------------- -------------------- ---------------- +> 0001-02-04 0001-02-02 INTERVAL '0' DAY +> 0004-05-07 0004-05-05 INTERVAL '0' DAY +> 2000-01-01 1999-12-30 INTERVAL '0' DAY +> null null null +> rows: 4 + +SELECT ID, CAST(XTS AS DATE) TS2D, +CAST(XTS AS TIME(9)) TS2T, +CAST(XD AS TIMESTAMP) D2TS FROM TEST; +> ID TS2D TS2T D2TS +> ---- ---------- ------------------ ------------------- +> 0 0002-03-04 00:00:00 0001-02-03 00:00:00 +> 1 0007-08-09 00:01:02 0004-05-06 00:00:00 +> 2 1999-12-31 23:59:59.123456789 1999-12-31 00:00:00 +> null null null null +> rows: 4 + +SCRIPT SIMPLE NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> --------------------------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "ID" INTEGER, "XT" TIME, "XD" DATE, "XTS" TIMESTAMP(9) ); +> -- 4 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> INSERT INTO "PUBLIC"."TEST" VALUES(0, TIME '00:00:00', DATE '0001-02-03', TIMESTAMP '0002-03-04 00:00:00'); +> INSERT INTO "PUBLIC"."TEST" VALUES(1, TIME '01:02:03', DATE '0004-05-06', TIMESTAMP '0007-08-09 00:01:02'); +> INSERT INTO "PUBLIC"."TEST" VALUES(2, TIME '23:59:59', DATE '1999-12-31', TIMESTAMP '1999-12-31 23:59:59.123456789'); +> INSERT INTO "PUBLIC"."TEST" VALUES(NULL, NULL, NULL, NULL); +> rows (ordered): 7 + +DROP TABLE TEST; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, t0 timestamp(23, 0), t1 timestamp(23, 1), t2 timestamp(23, 2), t5 timestamp(23, 5)); +> ok + +INSERT INTO TEST VALUES(1, '2001-01-01 12:34:56.789123', '2001-01-01 12:34:56.789123', '2001-01-01 12:34:56.789123', '2001-01-01 12:34:56.789123'); +> update count: 1 + +select * from test; +> ID T0 T1 T2 T5 +> -- ------------------- --------------------- ---------------------- ------------------------- +> 1 2001-01-01 12:34:57 2001-01-01 12:34:56.8 2001-01-01 12:34:56.79 2001-01-01 12:34:56.78912 +> rows: 1 + +DROP TABLE IF EXISTS TEST; +> ok + +--- data types (decimal) ---------------------------------------------------------------------------------------------- +CALL 1.2E10+1; +> 12000000001 +> ----------- +> 12000000001 +> rows: 1 + +CALL -1.2E-10-1; +> -1.00000000012 +> -------------- +> -1.00000000012 +> rows: 1 + +CALL 1E-1; +> 0.1 +> --- +> 0.1 +> rows: 1 + +CREATE TABLE TEST(ID INT, X1 BIT, XT TINYINT, X_SM SMALLINT, XB BIGINT, XD DECIMAL(10,2), XD2 DOUBLE PRECISION, XR REAL); +> ok + +INSERT INTO TEST VALUES(?, ?, ?, ?, ?, ?, ?, ?); +{ +0,FALSE,0,0,0,0.0,0.0,0.0 +1,TRUE,1,1,1,1.0,1.0,1.0 +4,TRUE,4,4,4,4.0,4.0,4.0 +-1,FALSE,-1,-1,-1,-1.0,-1.0,-1.0 +NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL +}; +> update count: 5 + +SELECT *, 0xFF, -0x1234567890abcd FROM TEST; +> ID X1 XT X_SM XB XD XD2 XR 255 -5124095575370701 +> ---- ----- ---- ---- ---- ----- ---- ---- --- ----------------- +> -1 FALSE -1 -1 -1 -1.00 -1.0 -1.0 255 -5124095575370701 +> 0 FALSE 0 0 0 0.00 0.0 0.0 255 -5124095575370701 +> 1 TRUE 1 1 1 1.00 1.0 1.0 255 -5124095575370701 +> 4 TRUE 4 4 4 4.00 4.0 4.0 255 -5124095575370701 +> null null null null null null null null 255 -5124095575370701 +> rows: 5 + +SELECT XD, CAST(XD AS DECIMAL(10,1)) D2DE, CAST(XD2 AS DECIMAL(4, 3)) DO2DE, CAST(XR AS DECIMAL(20,3)) R2DE FROM TEST; +> XD D2DE DO2DE R2DE +> ----- ---- ------ ------ +> -1.00 -1.0 -1.000 -1.000 +> 0.00 0.0 0.000 0.000 +> 1.00 1.0 1.000 1.000 +> 4.00 4.0 4.000 4.000 +> null null null null +> rows: 5 + +SELECT ID, CAST(XB AS DOUBLE) L2D, CAST(X_SM AS DOUBLE) S2D, CAST(XT AS DOUBLE) X2D FROM TEST; +> ID L2D S2D X2D +> ---- ---- ---- ---- +> -1 -1.0 -1.0 -1.0 +> 0 0.0 0.0 0.0 +> 1 1.0 1.0 1.0 +> 4 4.0 4.0 4.0 +> null null null null +> rows: 5 + +SELECT ID, CAST(XB AS REAL) L2D, CAST(X_SM AS REAL) S2D, CAST(XT AS REAL) T2R FROM TEST; +> ID L2D S2D T2R +> ---- ---- ---- ---- +> -1 -1.0 -1.0 -1.0 +> 0 0.0 0.0 0.0 +> 1 1.0 1.0 1.0 +> 4 4.0 4.0 4.0 +> null null null null +> rows: 5 + +SELECT ID, CAST(X_SM AS BIGINT) S2L, CAST(XT AS BIGINT) B2L, CAST(XD2 AS BIGINT) D2L, CAST(XR AS BIGINT) R2L FROM TEST; +> ID S2L B2L D2L R2L +> ---- ---- ---- ---- ---- +> -1 -1 -1 -1 -1 +> 0 0 0 0 0 +> 1 1 1 1 1 +> 4 4 4 4 4 +> null null null null null +> rows: 5 + +SELECT ID, CAST(XB AS INT) L2I, CAST(XD2 AS INT) D2I, CAST(XD2 AS SMALLINT) DO2I, CAST(XR AS SMALLINT) R2I FROM TEST; +> ID L2I D2I DO2I R2I +> ---- ---- ---- ---- ---- +> -1 -1 -1 -1 -1 +> 0 0 0 0 0 +> 1 1 1 1 1 +> 4 4 4 4 4 +> null null null null null +> rows: 5 + +SELECT ID, CAST(XD AS SMALLINT) D2S, CAST(XB AS SMALLINT) L2S, CAST(XT AS SMALLINT) B2S FROM TEST; +> ID D2S L2S B2S +> ---- ---- ---- ---- +> -1 -1 -1 -1 +> 0 0 0 0 +> 1 1 1 1 +> 4 4 4 4 +> null null null null +> rows: 5 + +SELECT ID, CAST(XD2 AS TINYINT) D2B, CAST(XD AS TINYINT) DE2B, CAST(XB AS TINYINT) L2B, CAST(X_SM AS TINYINT) S2B FROM TEST; +> ID D2B DE2B L2B S2B +> ---- ---- ---- ---- ---- +> -1 -1 -1 -1 -1 +> 0 0 0 0 0 +> 1 1 1 1 1 +> 4 4 4 4 4 +> null null null null null +> rows: 5 + +SELECT ID, CAST(XD2 AS BIT) D2B, CAST(XD AS BIT) DE2B, CAST(XB AS BIT) L2B, CAST(X_SM AS BIT) S2B FROM TEST; +> ID D2B DE2B L2B S2B +> ---- ----- ----- ----- ----- +> -1 TRUE TRUE TRUE TRUE +> 0 FALSE FALSE FALSE FALSE +> 1 TRUE TRUE TRUE TRUE +> 4 TRUE TRUE TRUE TRUE +> null null null null null +> rows: 5 + +SELECT CAST('TRUE' AS BIT) NT, CAST('1.0' AS BIT) N1, CAST('0.0' AS BIT) N0; +> NT N1 N0 +> ---- ---- ----- +> TRUE TRUE FALSE +> rows: 1 + +SELECT ID, ID+X1, ID+XT, ID+X_SM, ID+XB, ID+XD, ID+XD2, ID+XR FROM TEST; +> ID ID + X1 ID + XT ID + X_SM ID + XB ID + XD ID + XD2 ID + XR +> ---- ------- ------- --------- ------- ------- -------- ------- +> -1 -1 -2 -2 -2 -2.00 -2.0 -2.0 +> 0 0 0 0 0 0.00 0.0 0.0 +> 1 2 2 2 2 2.00 2.0 2.0 +> 4 5 8 8 8 8.00 8.0 8.0 +> null null null null null null null null +> rows: 5 + +SELECT ID, 10-X1, 10-XT, 10-X_SM, 10-XB, 10-XD, 10-XD2, 10-XR FROM TEST; +> ID 10 - X1 10 - XT 10 - X_SM 10 - XB 10 - XD 10 - XD2 10 - XR +> ---- ------- ------- --------- ------- ------- -------- ------- +> -1 10 11 11 11 11.00 11.0 11.0 +> 0 10 10 10 10 10.00 10.0 10.0 +> 1 9 9 9 9 9.00 9.0 9.0 +> 4 9 6 6 6 6.00 6.0 6.0 +> null null null null null null null null +> rows: 5 + +SELECT ID, 10*X1, 10*XT, 10*X_SM, 10*XB, 10*XD, 10*XD2, 10*XR FROM TEST; +> ID 10 * X1 10 * XT 10 * X_SM 10 * XB 10 * XD 10 * XD2 10 * XR +> ---- ------- ------- --------- ------- ------- -------- ------- +> -1 0 -10 -10 -10 -10.00 -10.0 -10.0 +> 0 0 0 0 0 0.00 0.0 0.0 +> 1 10 10 10 10 10.00 10.0 10.0 +> 4 10 40 40 40 40.00 40.0 40.0 +> null null null null null null null null +> rows: 5 + +SELECT ID, SIGN(XT), SIGN(X_SM), SIGN(XB), SIGN(XD), SIGN(XD2), SIGN(XR) FROM TEST; +> ID SIGN(XT) SIGN(X_SM) SIGN(XB) SIGN(XD) SIGN(XD2) SIGN(XR) +> ---- -------- ---------- -------- -------- --------- -------- +> -1 -1 -1 -1 -1 -1 -1 +> 0 0 0 0 0 0 0 +> 1 1 1 1 1 1 1 +> 4 1 1 1 1 1 1 +> null null null null null null null +> rows: 5 + +SELECT ID, XT-XT-XT, X_SM-X_SM-X_SM, XB-XB-XB, XD-XD-XD, XD2-XD2-XD2, XR-XR-XR FROM TEST; +> ID (XT - XT) - XT (X_SM - X_SM) - X_SM (XB - XB) - XB (XD - XD) - XD (XD2 - XD2) - XD2 (XR - XR) - XR +> ---- -------------- -------------------- -------------- -------------- ----------------- -------------- +> -1 1 1 1 1.00 1.0 1.0 +> 0 0 0 0 0.00 0.0 0.0 +> 1 -1 -1 -1 -1.00 -1.0 -1.0 +> 4 -4 -4 -4 -4.00 -4.0 -4.0 +> null null null null null null null +> rows: 5 + +SELECT ID, XT+XT, X_SM+X_SM, XB+XB, XD+XD, XD2+XD2, XR+XR FROM TEST; +> ID XT + XT X_SM + X_SM XB + XB XD + XD XD2 + XD2 XR + XR +> ---- ------- ----------- ------- ------- --------- ------- +> -1 -2 -2 -2 -2.00 -2.0 -2.0 +> 0 0 0 0 0.00 0.0 0.0 +> 1 2 2 2 2.00 2.0 2.0 +> 4 8 8 8 8.00 8.0 8.0 +> null null null null null null null +> rows: 5 + +SELECT ID, XT*XT, X_SM*X_SM, XB*XB, XD*XD, XD2*XD2, XR*XR FROM TEST; +> ID XT * XT X_SM * X_SM XB * XB XD * XD XD2 * XD2 XR * XR +> ---- ------- ----------- ------- ------- --------- ------- +> -1 1 1 1 1.0000 1.0 1.0 +> 0 0 0 0 0.0000 0.0 0.0 +> 1 1 1 1 1.0000 1.0 1.0 +> 4 16 16 16 16.0000 16.0 16.0 +> null null null null null null null +> rows: 5 + +SELECT 2/3 FROM TEST WHERE ID=1; +> 0 +> - +> 0 +> rows: 1 + +SELECT ID/ID FROM TEST; +> exception DIVISION_BY_ZERO_1 + +SELECT XT/XT FROM TEST; +> exception DIVISION_BY_ZERO_1 + +SELECT X_SM/X_SM FROM TEST; +> exception DIVISION_BY_ZERO_1 + +SELECT XB/XB FROM TEST; +> exception DIVISION_BY_ZERO_1 + +SELECT XD/XD FROM TEST; +> exception DIVISION_BY_ZERO_1 + +SELECT XD2/XD2 FROM TEST; +> exception DIVISION_BY_ZERO_1 + +SELECT XR/XR FROM TEST; +> exception DIVISION_BY_ZERO_1 + +SELECT ID++0, -X1, -XT, -X_SM, -XB, -XD, -XD2, -XR FROM TEST; +> ID + 0 - X1 - XT - X_SM - XB - XD - XD2 - XR +> ------ ----- ---- ------ ---- ----- ----- ---- +> -1 TRUE 1 1 1 1.00 1.0 1.0 +> 0 TRUE 0 0 0 0.00 0.0 0.0 +> 1 FALSE -1 -1 -1 -1.00 -1.0 -1.0 +> 4 FALSE -4 -4 -4 -4.00 -4.0 -4.0 +> null null null null null null null null +> rows: 5 + +SELECT ID, X1||'!', XT||'!', X_SM||'!', XB||'!', XD||'!', XD2||'!', XR||'!' FROM TEST; +> ID X1 || '!' XT || '!' X_SM || '!' XB || '!' XD || '!' XD2 || '!' XR || '!' +> ---- --------- --------- ----------- --------- --------- ---------- --------- +> -1 FALSE! -1! -1! -1! -1.00! -1.0! -1.0! +> 0 FALSE! 0! 0! 0! 0.00! 0.0! 0.0! +> 1 TRUE! 1! 1! 1! 1.00! 1.0! 1.0! +> 4 TRUE! 4! 4! 4! 4.00! 4.0! 4.0! +> null null null null null null null null +> rows: 5 + +DROP TABLE TEST; +> ok + +--- in ---------------------------------------------------------------------------------------------- +CREATE TABLE CUSTOMER(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +CREATE TABLE INVOICE(ID INT, CUSTOMER_ID INT, PRIMARY KEY(CUSTOMER_ID, ID), "VALUE" DECIMAL(10,2)); +> ok + +INSERT INTO CUSTOMER VALUES(?, ?); +{ +1,Lehmann +2,Meier +3,Scott +4,NULL +}; +> update count: 4 + +INSERT INTO INVOICE VALUES(?, ?, ?); +{ +10,1,100.10 +11,1,10.01 +12,1,1.001 +20,2,22.2 +21,2,200.02 +}; +> update count: 5 + +SELECT * FROM CUSTOMER WHERE ID IN(1,2,4,-1); +> ID NAME +> -- ------- +> 1 Lehmann +> 2 Meier +> 4 null +> rows: 3 + +SELECT * FROM CUSTOMER WHERE ID NOT IN(3,4,5,'1'); +> ID NAME +> -- ----- +> 2 Meier +> rows: 1 + +SELECT * FROM CUSTOMER WHERE ID NOT IN(SELECT CUSTOMER_ID FROM INVOICE); +> ID NAME +> -- ----- +> 3 Scott +> 4 null +> rows: 2 + +SELECT * FROM INVOICE WHERE CUSTOMER_ID IN(SELECT C.ID FROM CUSTOMER C); +> ID CUSTOMER_ID VALUE +> -- ----------- ------ +> 10 1 100.10 +> 11 1 10.01 +> 12 1 1.00 +> 20 2 22.20 +> 21 2 200.02 +> rows: 5 + +SELECT * FROM CUSTOMER WHERE NAME IN('Lehmann', 20); +> exception DATA_CONVERSION_ERROR_1 + +SELECT * FROM CUSTOMER WHERE NAME NOT IN('Scott'); +> ID NAME +> -- ------- +> 1 Lehmann +> 2 Meier +> rows: 2 + +SELECT * FROM CUSTOMER WHERE NAME IN(SELECT NAME FROM CUSTOMER); +> ID NAME +> -- ------- +> 1 Lehmann +> 2 Meier +> 3 Scott +> rows: 3 + +SELECT * FROM CUSTOMER WHERE NAME NOT IN(SELECT NAME FROM CUSTOMER); +> ID NAME +> -- ---- +> rows: 0 + +SELECT * FROM CUSTOMER WHERE NAME = ANY(SELECT NAME FROM CUSTOMER); +> ID NAME +> -- ------- +> 1 Lehmann +> 2 Meier +> 3 Scott +> rows: 3 + +SELECT * FROM CUSTOMER WHERE NAME = ALL(SELECT NAME FROM CUSTOMER); +> ID NAME +> -- ---- +> rows: 0 + +SELECT * FROM CUSTOMER WHERE NAME > ALL(SELECT NAME FROM CUSTOMER); +> ID NAME +> -- ---- +> rows: 0 + +SELECT * FROM CUSTOMER WHERE NAME > ANY(SELECT NAME FROM CUSTOMER); +> ID NAME +> -- ----- +> 2 Meier +> 3 Scott +> rows: 2 + +SELECT * FROM CUSTOMER WHERE NAME < ANY(SELECT NAME FROM CUSTOMER); +> ID NAME +> -- ------- +> 1 Lehmann +> 2 Meier +> rows: 2 + +DROP TABLE INVOICE; +> ok + +DROP TABLE CUSTOMER; +> ok + +--- aggregates ---------------------------------------------------------------------------------------------- +drop table if exists t; +> ok + +create table t(x double precision, y double precision); +> ok + +create view s as +select stddev_pop(x) s_px, stddev_samp(x) s_sx, var_pop(x) v_px, var_samp(x) v_sx, +stddev_pop(y) s_py, stddev_samp(y) s_sy, var_pop(y) v_py, var_samp(y) v_sy from t; +> ok + +select var(100000000.1) z from system_range(1, 1000000); +> Z +> --- +> 0.0 +> rows: 1 + +select * from s; +> S_PX S_SX V_PX V_SX S_PY S_SY V_PY V_SY +> ---- ---- ---- ---- ---- ---- ---- ---- +> null null null null null null null null +> rows: 1 + +select some(y>10), every(y>10), min(y), max(y) from t; +> ANY(Y > 10.0) EVERY(Y > 10.0) MIN(Y) MAX(Y) +> ------------- --------------- ------ ------ +> null null null null +> rows: 1 + +insert into t values(1000000004, 4); +> update count: 1 + +select * from s; +> S_PX S_SX V_PX V_SX S_PY S_SY V_PY V_SY +> ---- ---- ---- ---- ---- ---- ---- ---- +> 0.0 null 0.0 null 0.0 null 0.0 null +> rows: 1 + +insert into t values(1000000007, 7); +> update count: 1 + +select * from s; +> S_PX S_SX V_PX V_SX S_PY S_SY V_PY V_SY +> ---- ------------------ ---- ---- ---- ------------------ ---- ---- +> 1.5 2.1213203435596424 2.25 4.5 1.5 2.1213203435596424 2.25 4.5 +> rows: 1 + +insert into t values(1000000013, 13); +> update count: 1 + +select * from s; +> S_PX S_SX V_PX V_SX S_PY S_SY V_PY V_SY +> ------------------ ---------------- ---- ---- ------------------ ---------------- ---- ---- +> 3.7416573867739413 4.58257569495584 14.0 21.0 3.7416573867739413 4.58257569495584 14.0 21.0 +> rows: 1 + +insert into t values(1000000016, 16); +> update count: 1 + +select * from s; +> S_PX S_SX V_PX V_SX S_PY S_SY V_PY V_SY +> ----------------- ----------------- ---- ---- ----------------- ----------------- ---- ---- +> 4.743416490252569 5.477225575051661 22.5 30.0 4.743416490252569 5.477225575051661 22.5 30.0 +> rows: 1 + +insert into t values(1000000016, 16); +> update count: 1 + +select * from s; +> S_PX S_SX V_PX V_SX S_PY S_SY V_PY V_SY +> ----------------- ----------------- ----------------- ------------------ ----------------- ----------------- ----- ------------------ +> 4.874423036912116 5.449770630813229 23.75999994277954 29.699999928474426 4.874423042781577 5.449770637375485 23.76 29.700000000000003 +> rows: 1 + +select stddev_pop(distinct x) s_px, stddev_samp(distinct x) s_sx, var_pop(distinct x) v_px, var_samp(distinct x) v_sx, +stddev_pop(distinct y) s_py, stddev_samp(distinct y) s_sy, var_pop(distinct y) v_py, var_samp(distinct y) V_SY from t; +> S_PX S_SX V_PX V_SX S_PY S_SY V_PY V_SY +> ----------------- ----------------- ---- ---- ----------------- ----------------- ---- ---- +> 4.743416490252569 5.477225575051661 22.5 30.0 4.743416490252569 5.477225575051661 22.5 30.0 +> rows: 1 + +select some(y>10), every(y>10), min(y), max(y) from t; +> ANY(Y > 10.0) EVERY(Y > 10.0) MIN(Y) MAX(Y) +> ------------- --------------- ------ ------ +> TRUE FALSE 4.0 16.0 +> rows: 1 + +drop view s; +> ok + +drop table t; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255), "VALUE" DECIMAL(10,2)); +> ok + +INSERT INTO TEST VALUES(?, ?, ?); +{ +1,Apples,1.20 +2,Oranges,2.05 +3,Cherries,5.10 +4,Apples,1.50 +5,Apples,1.10 +6,Oranges,1.80 +7,Bananas,2.50 +8,NULL,3.10 +9,NULL,-10.0 +}; +> update count: 9 + +SELECT IFNULL(NAME, '') || ': ' || GROUP_CONCAT("VALUE" ORDER BY NAME, "VALUE" DESC SEPARATOR ', ') FROM TEST GROUP BY NAME ORDER BY 1; +> COALESCE(NAME, '') || ': ' || LISTAGG("VALUE", ', ') WITHIN GROUP (ORDER BY NAME, "VALUE" DESC) +> ----------------------------------------------------------------------------------------------- +> : 3.10, -10.00 +> Apples: 1.50, 1.20, 1.10 +> Bananas: 2.50 +> Cherries: 5.10 +> Oranges: 2.05, 1.80 +> rows (ordered): 5 + +SELECT GROUP_CONCAT(ID ORDER BY ID) FROM TEST; +> LISTAGG(ID) WITHIN GROUP (ORDER BY ID) +> -------------------------------------- +> 1,2,3,4,5,6,7,8,9 +> rows: 1 + +SELECT STRING_AGG(ID,';') FROM TEST; +> LISTAGG(ID, ';') WITHIN GROUP (ORDER BY NULL) +> --------------------------------------------- +> 1;2;3;4;5;6;7;8;9 +> rows: 1 + +SELECT DISTINCT NAME FROM TEST; +> NAME +> -------- +> Apples +> Bananas +> Cherries +> Oranges +> null +> rows: 5 + +SELECT DISTINCT NAME FROM TEST ORDER BY NAME DESC NULLS LAST; +> NAME +> -------- +> Oranges +> Cherries +> Bananas +> Apples +> null +> rows (ordered): 5 + +SELECT DISTINCT NAME FROM TEST ORDER BY NAME DESC NULLS LAST LIMIT 2 OFFSET 1; +> NAME +> -------- +> Cherries +> Bananas +> rows (ordered): 2 + +SELECT NAME, COUNT(*), SUM("VALUE"), MAX("VALUE"), MIN("VALUE"), AVG("VALUE"), COUNT(DISTINCT "VALUE") FROM TEST GROUP BY NAME; +> NAME COUNT(*) SUM("VALUE") MAX("VALUE") MIN("VALUE") AVG("VALUE") COUNT(DISTINCT "VALUE") +> -------- -------- ------------ ------------ ------------ --------------- ----------------------- +> Apples 3 3.80 1.50 1.10 1.266666666667 3 +> Bananas 1 2.50 2.50 2.50 2.500000000000 1 +> Cherries 1 5.10 5.10 5.10 5.100000000000 1 +> Oranges 2 3.85 2.05 1.80 1.925000000000 2 +> null 2 -6.90 3.10 -10.00 -3.450000000000 2 +> rows: 5 + +SELECT NAME, MAX("VALUE"), MIN("VALUE"), MAX("VALUE"+1)*MIN("VALUE"+1) FROM TEST GROUP BY NAME; +> NAME MAX("VALUE") MIN("VALUE") MAX("VALUE" + 1) * MIN("VALUE" + 1) +> -------- ------------ ------------ ----------------------------------- +> Apples 1.50 1.10 5.2500 +> Bananas 2.50 2.50 12.2500 +> Cherries 5.10 5.10 37.2100 +> Oranges 2.05 1.80 8.5400 +> null 3.10 -10.00 -36.9000 +> rows: 5 + +DROP TABLE TEST; +> ok + +--- order by ---------------------------------------------------------------------------------------------- +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +CREATE UNIQUE INDEX IDXNAME ON TEST(NAME); +> ok + +INSERT INTO TEST VALUES(1, 'Hello'); +> update count: 1 + +INSERT INTO TEST VALUES(2, 'World'); +> update count: 1 + +INSERT INTO TEST VALUES(3, NULL); +> update count: 1 + +SELECT * FROM TEST ORDER BY NAME; +> ID NAME +> -- ----- +> 3 null +> 1 Hello +> 2 World +> rows (ordered): 3 + +SELECT * FROM TEST ORDER BY NAME DESC; +> ID NAME +> -- ----- +> 2 World +> 1 Hello +> 3 null +> rows (ordered): 3 + +SELECT * FROM TEST ORDER BY NAME NULLS FIRST; +> ID NAME +> -- ----- +> 3 null +> 1 Hello +> 2 World +> rows (ordered): 3 + +SELECT * FROM TEST ORDER BY NAME DESC NULLS FIRST; +> ID NAME +> -- ----- +> 3 null +> 2 World +> 1 Hello +> rows (ordered): 3 + +SELECT * FROM TEST ORDER BY NAME NULLS LAST; +> ID NAME +> -- ----- +> 1 Hello +> 2 World +> 3 null +> rows (ordered): 3 + +SELECT * FROM TEST ORDER BY NAME DESC NULLS LAST; +> ID NAME +> -- ----- +> 2 World +> 1 Hello +> 3 null +> rows (ordered): 3 + +SELECT ID, '=', NAME FROM TEST ORDER BY 2 FOR UPDATE; +> ID '=' NAME +> -- --- ----- +> 1 = Hello +> 2 = World +> 3 = null +> rows: 3 + +DROP TABLE TEST; +> ok + +--- having ---------------------------------------------------------------------------------------------- +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +CREATE INDEX IDXNAME ON TEST(NAME); +> ok + +INSERT INTO TEST VALUES(1, 'Hello'); +> update count: 1 + +INSERT INTO TEST VALUES(2, 'Hello'); +> update count: 1 + +INSERT INTO TEST VALUES(3, 'World'); +> update count: 1 + +INSERT INTO TEST VALUES(4, 'World'); +> update count: 1 + +INSERT INTO TEST VALUES(5, 'Orange'); +> update count: 1 + +SELECT NAME, SUM(ID) FROM TEST GROUP BY NAME HAVING COUNT(*)>1 ORDER BY NAME; +> NAME SUM(ID) +> ----- ------- +> Hello 3 +> World 7 +> rows (ordered): 2 + +DROP INDEX IF EXISTS IDXNAME; +> ok + +DROP TABLE TEST; +> ok + +--- sequence ---------------------------------------------------------------------------------------------- +CREATE CACHED TABLE TEST(ID INT PRIMARY KEY); +> ok + +CREATE CACHED TABLE IF NOT EXISTS TEST(ID INT PRIMARY KEY); +> ok + +CREATE SEQUENCE IF NOT EXISTS TEST_SEQ START WITH 10; +> ok + +CREATE SEQUENCE IF NOT EXISTS TEST_SEQ START WITH 20; +> ok + +INSERT INTO TEST VALUES(NEXT VALUE FOR TEST_SEQ); +> update count: 1 + +CALL CURRVAL('test_seq'); +> CURRVAL('test_seq') +> ------------------- +> 10 +> rows: 1 + +INSERT INTO TEST VALUES(NEXT VALUE FOR TEST_SEQ); +> update count: 1 + +CALL NEXT VALUE FOR TEST_SEQ; +> NEXT VALUE FOR PUBLIC.TEST_SEQ +> ------------------------------ +> 12 +> rows: 1 + +INSERT INTO TEST VALUES(NEXT VALUE FOR TEST_SEQ); +> update count: 1 + +SELECT * FROM TEST; +> ID +> -- +> 10 +> 11 +> 13 +> rows: 3 + +SELECT TOP 2 * FROM TEST; +> ID +> -- +> 10 +> 11 +> rows: 2 + +SELECT TOP 2 * FROM TEST ORDER BY ID DESC; +> ID +> -- +> 13 +> 11 +> rows (ordered): 2 + +ALTER SEQUENCE TEST_SEQ RESTART WITH 20 INCREMENT BY -1; +> ok + +INSERT INTO TEST VALUES(NEXT VALUE FOR TEST_SEQ); +> update count: 1 + +INSERT INTO TEST VALUES(NEXT VALUE FOR TEST_SEQ); +> update count: 1 + +SELECT * FROM TEST ORDER BY ID ASC; +> ID +> -- +> 10 +> 11 +> 13 +> 19 +> 20 +> rows (ordered): 5 + +CALL NEXTVAL('test_seq'); +> NEXTVAL('test_seq') +> ------------------- +> 18 +> rows: 1 + +DROP SEQUENCE IF EXISTS TEST_SEQ; +> ok + +DROP SEQUENCE IF EXISTS TEST_SEQ; +> ok + +CREATE SEQUENCE TEST_LONG START WITH 90123456789012345 MAXVALUE 90123456789012345 INCREMENT BY -1; +> ok + +SET AUTOCOMMIT FALSE; +> ok + +CALL NEXT VALUE FOR TEST_LONG; +> NEXT VALUE FOR PUBLIC.TEST_LONG +> ------------------------------- +> 90123456789012345 +> rows: 1 + +SELECT SEQUENCE_NAME, BASE_VALUE, INCREMENT FROM INFORMATION_SCHEMA.SEQUENCES; +> SEQUENCE_NAME BASE_VALUE INCREMENT +> ------------- ----------------- --------- +> TEST_LONG 90123456789012344 -1 +> rows: 1 + +SET AUTOCOMMIT TRUE; +> ok + +DROP SEQUENCE TEST_LONG; +> ok + +DROP TABLE TEST; +> ok + +--- call ---------------------------------------------------------------------------------------------- +CALL PI(); +> 3.141592653589793 +> ----------------- +> 3.141592653589793 +> rows: 1 + +CALL 1+1; +> 2 +> - +> 2 +> rows: 1 + +--- constraints ---------------------------------------------------------------------------------------------- +CREATE TABLE PARENT(A INT, B INT, PRIMARY KEY(A, B)); +> ok + +CREATE TABLE CHILD(ID INT PRIMARY KEY, PA INT, PB INT, CONSTRAINT AB FOREIGN KEY(PA, PB) REFERENCES PARENT(A, B)); +> ok + +TABLE INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS; +> CONSTRAINT_CATALOG CONSTRAINT_SCHEMA CONSTRAINT_NAME UNIQUE_CONSTRAINT_CATALOG UNIQUE_CONSTRAINT_SCHEMA UNIQUE_CONSTRAINT_NAME MATCH_OPTION UPDATE_RULE DELETE_RULE +> ------------------ ----------------- --------------- ------------------------- ------------------------ ---------------------- ------------ ----------- ----------- +> SCRIPT PUBLIC AB SCRIPT PUBLIC CONSTRAINT_8 NONE RESTRICT RESTRICT +> rows: 1 + +TABLE INFORMATION_SCHEMA.KEY_COLUMN_USAGE; +> CONSTRAINT_CATALOG CONSTRAINT_SCHEMA CONSTRAINT_NAME TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME ORDINAL_POSITION POSITION_IN_UNIQUE_CONSTRAINT +> ------------------ ----------------- --------------- ------------- ------------ ---------- ----------- ---------------- ----------------------------- +> SCRIPT PUBLIC AB SCRIPT PUBLIC CHILD PA 1 1 +> SCRIPT PUBLIC AB SCRIPT PUBLIC CHILD PB 2 2 +> SCRIPT PUBLIC CONSTRAINT_3 SCRIPT PUBLIC CHILD ID 1 null +> SCRIPT PUBLIC CONSTRAINT_8 SCRIPT PUBLIC PARENT A 1 null +> SCRIPT PUBLIC CONSTRAINT_8 SCRIPT PUBLIC PARENT B 2 null +> rows: 5 + +DROP TABLE PARENT, CHILD; +> ok + +drop table if exists test; +> ok + +create table test(id int primary key, parent int unique, foreign key(id) references test(parent)); +> ok + +insert into test values(1, 1); +> update count: 1 + +delete from test; +> update count: 1 + +drop table test; +> ok + +drop table if exists child; +> ok + +drop table if exists parent; +> ok + +create table child(a int, id int); +> ok + +create table parent(id int primary key); +> ok + +alter table child add foreign key(id) references parent; +> ok + +insert into parent values(1); +> update count: 1 + +delete from parent; +> update count: 1 + +drop table if exists child; +> ok + +drop table if exists parent; +> ok + +CREATE MEMORY TABLE PARENT(ID INT PRIMARY KEY); +> ok + +CREATE MEMORY TABLE CHILD(ID INT, PARENT_ID INT, FOREIGN KEY(PARENT_ID) REFERENCES PARENT); +> ok + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> ---------------------------------------------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."PARENT"( "ID" INTEGER NOT NULL ); +> ALTER TABLE "PUBLIC"."PARENT" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_8" PRIMARY KEY("ID"); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.PARENT; +> CREATE MEMORY TABLE "PUBLIC"."CHILD"( "ID" INTEGER, "PARENT_ID" INTEGER ); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.CHILD; +> ALTER TABLE "PUBLIC"."CHILD" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_3" FOREIGN KEY("PARENT_ID") REFERENCES "PUBLIC"."PARENT"("ID") NOCHECK; +> rows (ordered): 7 + +DROP TABLE PARENT, CHILD; +> ok + +CREATE TABLE TEST(ID INT, CONSTRAINT PK PRIMARY KEY(ID), NAME VARCHAR, PARENT INT, CONSTRAINT P FOREIGN KEY(PARENT) REFERENCES(ID)); +> ok + +ALTER TABLE TEST DROP PRIMARY KEY; +> exception INDEX_BELONGS_TO_CONSTRAINT_2 + +ALTER TABLE TEST DROP CONSTRAINT PK; +> exception CONSTRAINT_IS_USED_BY_CONSTRAINT_2 + +INSERT INTO TEST VALUES(1, 'Frank', 1); +> update count: 1 + +INSERT INTO TEST VALUES(2, 'Sue', 1); +> update count: 1 + +INSERT INTO TEST VALUES(3, 'Karin', 2); +> update count: 1 + +INSERT INTO TEST VALUES(4, 'Joe', 5); +> exception REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1 + +INSERT INTO TEST VALUES(4, 'Joe', 3); +> update count: 1 + +DROP TABLE TEST; +> ok + +CREATE MEMORY TABLE TEST(A_INT INT NOT NULL, B_INT INT NOT NULL, PRIMARY KEY(A_INT, B_INT), CONSTRAINT U_B UNIQUE(B_INT)); +> ok + +ALTER TABLE TEST ADD CONSTRAINT A_UNIQUE UNIQUE(A_INT); +> ok + +ALTER TABLE TEST DROP PRIMARY KEY; +> ok + +ALTER TABLE TEST DROP PRIMARY KEY; +> exception INDEX_NOT_FOUND_1 + +ALTER TABLE TEST DROP CONSTRAINT A_UNIQUE; +> ok + +ALTER TABLE TEST ADD CONSTRAINT C1 FOREIGN KEY(A_INT) REFERENCES TEST(B_INT); +> ok + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> -------------------------------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "A_INT" INTEGER NOT NULL, "B_INT" INTEGER NOT NULL ); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> ALTER TABLE "PUBLIC"."TEST" ADD CONSTRAINT "PUBLIC"."U_B" UNIQUE("B_INT"); +> ALTER TABLE "PUBLIC"."TEST" ADD CONSTRAINT "PUBLIC"."C1" FOREIGN KEY("A_INT") REFERENCES "PUBLIC"."TEST"("B_INT") NOCHECK; +> rows (ordered): 5 + +ALTER TABLE TEST DROP CONSTRAINT C1; +> ok + +ALTER TABLE TEST DROP CONSTRAINT C1; +> exception CONSTRAINT_NOT_FOUND_1 + +DROP TABLE TEST; +> ok + +CREATE MEMORY TABLE A_TEST(A_INT INT NOT NULL, A_VARCHAR VARCHAR(255) DEFAULT 'x', A_DATE DATE, A_DECIMAL DECIMAL(10,2)); +> ok + +ALTER TABLE A_TEST ADD PRIMARY KEY(A_INT); +> ok + +ALTER TABLE A_TEST ADD CONSTRAINT MIN_LENGTH CHECK LENGTH(A_VARCHAR)>1; +> ok + +ALTER TABLE A_TEST ADD CONSTRAINT DATE_UNIQUE UNIQUE(A_DATE); +> ok + +ALTER TABLE A_TEST ADD CONSTRAINT DATE_UNIQUE_2 UNIQUE(A_DATE); +> ok + +INSERT INTO A_TEST VALUES(NULL, NULL, NULL, NULL); +> exception NULL_NOT_ALLOWED + +INSERT INTO A_TEST VALUES(1, 'A', NULL, NULL); +> exception CHECK_CONSTRAINT_VIOLATED_1 + +INSERT INTO A_TEST VALUES(1, 'AB', NULL, NULL); +> update count: 1 + +INSERT INTO A_TEST VALUES(1, 'AB', NULL, NULL); +> exception DUPLICATE_KEY_1 + +INSERT INTO A_TEST VALUES(2, 'AB', NULL, NULL); +> update count: 1 + +INSERT INTO A_TEST VALUES(3, 'AB', '2004-01-01', NULL); +> update count: 1 + +INSERT INTO A_TEST VALUES(4, 'AB', '2004-01-01', NULL); +> exception DUPLICATE_KEY_1 + +INSERT INTO A_TEST VALUES(5, 'ABC', '2004-01-02', NULL); +> update count: 1 + +CREATE MEMORY TABLE B_TEST(B_INT INT DEFAULT -1 NOT NULL , B_VARCHAR VARCHAR(255) DEFAULT NULL NULL, CONSTRAINT B_UNIQUE UNIQUE(B_INT)); +> ok + +ALTER TABLE B_TEST ADD CHECK LENGTH(B_VARCHAR)>1; +> ok + +ALTER TABLE B_TEST ADD CONSTRAINT C1 FOREIGN KEY(B_INT) REFERENCES A_TEST(A_INT) ON DELETE CASCADE ON UPDATE CASCADE; +> ok + +ALTER TABLE B_TEST ADD PRIMARY KEY(B_INT); +> ok + +INSERT INTO B_TEST VALUES(10, 'X'); +> exception CHECK_CONSTRAINT_VIOLATED_1 + +INSERT INTO B_TEST VALUES(1, 'X'); +> exception CHECK_CONSTRAINT_VIOLATED_1 + +INSERT INTO B_TEST VALUES(1, 'XX'); +> update count: 1 + +SELECT * FROM B_TEST; +> B_INT B_VARCHAR +> ----- --------- +> 1 XX +> rows: 1 + +UPDATE A_TEST SET A_INT = A_INT*10; +> update count: 4 + +SELECT * FROM B_TEST; +> B_INT B_VARCHAR +> ----- --------- +> 10 XX +> rows: 1 + +ALTER TABLE B_TEST DROP CONSTRAINT C1; +> ok + +ALTER TABLE B_TEST ADD CONSTRAINT C2 FOREIGN KEY(B_INT) REFERENCES A_TEST(A_INT) ON DELETE SET NULL ON UPDATE SET NULL; +> ok + +UPDATE A_TEST SET A_INT = A_INT*10; +> exception NULL_NOT_ALLOWED + +SELECT * FROM B_TEST; +> B_INT B_VARCHAR +> ----- --------- +> 10 XX +> rows: 1 + +ALTER TABLE B_TEST DROP CONSTRAINT C2; +> ok + +UPDATE B_TEST SET B_INT = 20; +> update count: 1 + +SELECT A_INT FROM A_TEST; +> A_INT +> ----- +> 10 +> 20 +> 30 +> 50 +> rows: 4 + +ALTER TABLE B_TEST ADD CONSTRAINT C3 FOREIGN KEY(B_INT) REFERENCES A_TEST(A_INT) ON DELETE SET DEFAULT ON UPDATE SET DEFAULT; +> ok + +UPDATE A_TEST SET A_INT = A_INT*10; +> update count: 4 + +SELECT * FROM B_TEST; +> B_INT B_VARCHAR +> ----- --------- +> -1 XX +> rows: 1 + +DELETE FROM A_TEST; +> update count: 4 + +SELECT * FROM B_TEST; +> B_INT B_VARCHAR +> ----- --------- +> -1 XX +> rows: 1 + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."A_TEST"( "A_INT" INTEGER NOT NULL, "A_VARCHAR" CHARACTER VARYING(255) DEFAULT 'x', "A_DATE" DATE, "A_DECIMAL" DECIMAL(10, 2) ); +> ALTER TABLE "PUBLIC"."A_TEST" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_7" PRIMARY KEY("A_INT"); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.A_TEST; +> CREATE MEMORY TABLE "PUBLIC"."B_TEST"( "B_INT" INTEGER DEFAULT -1 NOT NULL, "B_VARCHAR" CHARACTER VARYING(255) DEFAULT NULL ); +> ALTER TABLE "PUBLIC"."B_TEST" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_760" PRIMARY KEY("B_INT"); +> -- 1 +/- SELECT COUNT(*) FROM PUBLIC.B_TEST; +> INSERT INTO "PUBLIC"."B_TEST" VALUES (-1, 'XX'); +> ALTER TABLE "PUBLIC"."A_TEST" ADD CONSTRAINT "PUBLIC"."MIN_LENGTH" CHECK(CHAR_LENGTH("A_VARCHAR") > 1) NOCHECK; +> ALTER TABLE "PUBLIC"."B_TEST" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_76" CHECK(CHAR_LENGTH("B_VARCHAR") > 1) NOCHECK; +> ALTER TABLE "PUBLIC"."A_TEST" ADD CONSTRAINT "PUBLIC"."DATE_UNIQUE" UNIQUE("A_DATE"); +> ALTER TABLE "PUBLIC"."A_TEST" ADD CONSTRAINT "PUBLIC"."DATE_UNIQUE_2" UNIQUE("A_DATE"); +> ALTER TABLE "PUBLIC"."B_TEST" ADD CONSTRAINT "PUBLIC"."B_UNIQUE" UNIQUE("B_INT"); +> ALTER TABLE "PUBLIC"."B_TEST" ADD CONSTRAINT "PUBLIC"."C3" FOREIGN KEY("B_INT") REFERENCES "PUBLIC"."A_TEST"("A_INT") ON DELETE SET DEFAULT ON UPDATE SET DEFAULT NOCHECK; +> rows (ordered): 14 + +DROP TABLE A_TEST, B_TEST; +> ok + +CREATE MEMORY TABLE FAMILY(ID INT PRIMARY KEY, NAME VARCHAR(20)); +> ok + +CREATE INDEX FAMILY_ID_NAME ON FAMILY(ID, NAME); +> ok + +CREATE MEMORY TABLE PARENT(ID INT, FAMILY_ID INT, NAME VARCHAR(20), UNIQUE(ID, FAMILY_ID)); +> ok + +ALTER TABLE PARENT ADD CONSTRAINT PARENT_FAMILY FOREIGN KEY(FAMILY_ID) +REFERENCES FAMILY(ID); +> ok + +CREATE MEMORY TABLE CHILD( +ID INT, +PARENTID INT, +FAMILY_ID INT, +UNIQUE(ID, PARENTID), +CONSTRAINT PARENT_CHILD FOREIGN KEY(PARENTID, FAMILY_ID) +REFERENCES PARENT(ID, FAMILY_ID) +ON UPDATE CASCADE +ON DELETE SET NULL, +NAME VARCHAR(20)); +> ok + +INSERT INTO FAMILY VALUES(1, 'Capone'); +> update count: 1 + +INSERT INTO CHILD VALUES(100, 1, 1, 'early'); +> exception REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1 + +INSERT INTO PARENT VALUES(1, 1, 'Sue'); +> update count: 1 + +INSERT INTO PARENT VALUES(2, 1, 'Joe'); +> update count: 1 + +INSERT INTO CHILD VALUES(100, 1, 1, 'Simon'); +> update count: 1 + +INSERT INTO CHILD VALUES(101, 1, 1, 'Sabine'); +> update count: 1 + +INSERT INTO CHILD VALUES(200, 2, 1, 'Jim'); +> update count: 1 + +INSERT INTO CHILD VALUES(201, 2, 1, 'Johann'); +> update count: 1 + +UPDATE PARENT SET ID=3 WHERE ID=1; +> update count: 1 + +SELECT * FROM CHILD; +> ID PARENTID FAMILY_ID NAME +> --- -------- --------- ------ +> 100 3 1 Simon +> 101 3 1 Sabine +> 200 2 1 Jim +> 201 2 1 Johann +> rows: 4 + +UPDATE CHILD SET PARENTID=-1 WHERE PARENTID IS NOT NULL; +> exception REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1 + +DELETE FROM PARENT WHERE ID=2; +> update count: 1 + +SELECT * FROM CHILD; +> ID PARENTID FAMILY_ID NAME +> --- -------- --------- ------ +> 100 3 1 Simon +> 101 3 1 Sabine +> 200 null null Jim +> 201 null null Johann +> rows: 4 + +SCRIPT SIMPLE NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."FAMILY"( "ID" INTEGER NOT NULL, "NAME" CHARACTER VARYING(20) ); +> ALTER TABLE "PUBLIC"."FAMILY" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_7" PRIMARY KEY("ID"); +> -- 1 +/- SELECT COUNT(*) FROM PUBLIC.FAMILY; +> INSERT INTO "PUBLIC"."FAMILY" VALUES(1, 'Capone'); +> CREATE INDEX "PUBLIC"."FAMILY_ID_NAME" ON "PUBLIC"."FAMILY"("ID" NULLS FIRST, "NAME" NULLS FIRST); +> CREATE MEMORY TABLE "PUBLIC"."PARENT"( "ID" INTEGER, "FAMILY_ID" INTEGER, "NAME" CHARACTER VARYING(20) ); +> -- 1 +/- SELECT COUNT(*) FROM PUBLIC.PARENT; +> INSERT INTO "PUBLIC"."PARENT" VALUES(3, 1, 'Sue'); +> CREATE MEMORY TABLE "PUBLIC"."CHILD"( "ID" INTEGER, "PARENTID" INTEGER, "FAMILY_ID" INTEGER, "NAME" CHARACTER VARYING(20) ); +> -- 4 +/- SELECT COUNT(*) FROM PUBLIC.CHILD; +> INSERT INTO "PUBLIC"."CHILD" VALUES(100, 3, 1, 'Simon'); +> INSERT INTO "PUBLIC"."CHILD" VALUES(101, 3, 1, 'Sabine'); +> INSERT INTO "PUBLIC"."CHILD" VALUES(200, NULL, NULL, 'Jim'); +> INSERT INTO "PUBLIC"."CHILD" VALUES(201, NULL, NULL, 'Johann'); +> ALTER TABLE "PUBLIC"."CHILD" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_3" UNIQUE("ID", "PARENTID"); +> ALTER TABLE "PUBLIC"."PARENT" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_8" UNIQUE("ID", "FAMILY_ID"); +> ALTER TABLE "PUBLIC"."CHILD" ADD CONSTRAINT "PUBLIC"."PARENT_CHILD" FOREIGN KEY("PARENTID", "FAMILY_ID") REFERENCES "PUBLIC"."PARENT"("ID", "FAMILY_ID") ON DELETE SET NULL ON UPDATE CASCADE NOCHECK; +> ALTER TABLE "PUBLIC"."PARENT" ADD CONSTRAINT "PUBLIC"."PARENT_FAMILY" FOREIGN KEY("FAMILY_ID") REFERENCES "PUBLIC"."FAMILY"("ID") NOCHECK; +> rows (ordered): 19 + +ALTER TABLE CHILD DROP CONSTRAINT PARENT_CHILD; +> ok + +SCRIPT SIMPLE NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> ------------------------------------------------------------------------------------------------------------------------------------------ +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."FAMILY"( "ID" INTEGER NOT NULL, "NAME" CHARACTER VARYING(20) ); +> ALTER TABLE "PUBLIC"."FAMILY" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_7" PRIMARY KEY("ID"); +> -- 1 +/- SELECT COUNT(*) FROM PUBLIC.FAMILY; +> INSERT INTO "PUBLIC"."FAMILY" VALUES(1, 'Capone'); +> CREATE INDEX "PUBLIC"."FAMILY_ID_NAME" ON "PUBLIC"."FAMILY"("ID" NULLS FIRST, "NAME" NULLS FIRST); +> CREATE MEMORY TABLE "PUBLIC"."PARENT"( "ID" INTEGER, "FAMILY_ID" INTEGER, "NAME" CHARACTER VARYING(20) ); +> -- 1 +/- SELECT COUNT(*) FROM PUBLIC.PARENT; +> INSERT INTO "PUBLIC"."PARENT" VALUES(3, 1, 'Sue'); +> CREATE MEMORY TABLE "PUBLIC"."CHILD"( "ID" INTEGER, "PARENTID" INTEGER, "FAMILY_ID" INTEGER, "NAME" CHARACTER VARYING(20) ); +> -- 4 +/- SELECT COUNT(*) FROM PUBLIC.CHILD; +> INSERT INTO "PUBLIC"."CHILD" VALUES(100, 3, 1, 'Simon'); +> INSERT INTO "PUBLIC"."CHILD" VALUES(101, 3, 1, 'Sabine'); +> INSERT INTO "PUBLIC"."CHILD" VALUES(200, NULL, NULL, 'Jim'); +> INSERT INTO "PUBLIC"."CHILD" VALUES(201, NULL, NULL, 'Johann'); +> ALTER TABLE "PUBLIC"."CHILD" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_3" UNIQUE("ID", "PARENTID"); +> ALTER TABLE "PUBLIC"."PARENT" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_8" UNIQUE("ID", "FAMILY_ID"); +> ALTER TABLE "PUBLIC"."PARENT" ADD CONSTRAINT "PUBLIC"."PARENT_FAMILY" FOREIGN KEY("FAMILY_ID") REFERENCES "PUBLIC"."FAMILY"("ID") NOCHECK; +> rows (ordered): 18 + +DELETE FROM PARENT; +> update count: 1 + +SELECT * FROM CHILD; +> ID PARENTID FAMILY_ID NAME +> --- -------- --------- ------ +> 100 3 1 Simon +> 101 3 1 Sabine +> 200 null null Jim +> 201 null null Johann +> rows: 4 + +DROP TABLE PARENT; +> ok + +DROP TABLE CHILD; +> ok + +DROP TABLE FAMILY; +> ok + +CREATE TABLE INVOICE(CUSTOMER_ID INT, ID INT, TOTAL_AMOUNT DECIMAL(10,2), PRIMARY KEY(CUSTOMER_ID, ID)); +> ok + +CREATE TABLE INVOICE_LINE(CUSTOMER_ID INT, INVOICE_ID INT, LINE_ID INT, TEXT VARCHAR, AMOUNT DECIMAL(10,2)); +> ok + +CREATE INDEX ON INVOICE_LINE(CUSTOMER_ID); +> ok + +ALTER TABLE INVOICE_LINE ADD FOREIGN KEY(CUSTOMER_ID, INVOICE_ID) REFERENCES INVOICE(CUSTOMER_ID, ID) ON DELETE CASCADE; +> ok + +INSERT INTO INVOICE VALUES(1, 100, NULL), (1, 101, NULL); +> update count: 2 + +INSERT INTO INVOICE_LINE VALUES(1, 100, 10, 'Apples', 20.35), (1, 100, 20, 'Paper', 10.05), (1, 101, 10, 'Pencil', 1.10), (1, 101, 20, 'Chair', 540.40); +> update count: 4 + +INSERT INTO INVOICE_LINE VALUES(1, 102, 20, 'Nothing', 30.00); +> exception REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1 + +DELETE FROM INVOICE WHERE ID = 100; +> update count: 1 + +SELECT * FROM INVOICE_LINE; +> CUSTOMER_ID INVOICE_ID LINE_ID TEXT AMOUNT +> ----------- ---------- ------- ------ ------ +> 1 101 10 Pencil 1.10 +> 1 101 20 Chair 540.40 +> rows: 2 + +DROP TABLE INVOICE, INVOICE_LINE; +> ok + +CREATE MEMORY TABLE TEST(A INT PRIMARY KEY, B INT, FOREIGN KEY (B) REFERENCES(A) ON UPDATE RESTRICT ON DELETE NO ACTION); +> ok + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> ----------------------------------------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "A" INTEGER NOT NULL, "B" INTEGER ); +> ALTER TABLE "PUBLIC"."TEST" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_2" PRIMARY KEY("A"); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> ALTER TABLE "PUBLIC"."TEST" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_27" FOREIGN KEY("B") REFERENCES "PUBLIC"."TEST"("A") NOCHECK; +> rows (ordered): 5 + +DROP TABLE TEST; +> ok + +--- users ---------------------------------------------------------------------------------------------- +CREATE USER TEST PASSWORD 'abc'; +> ok + +CREATE USER TEST_ADMIN_X PASSWORD 'def' ADMIN; +> ok + +ALTER USER TEST_ADMIN_X RENAME TO TEST_ADMIN; +> ok + +ALTER USER TEST_ADMIN ADMIN TRUE; +> ok + +CREATE USER TEST2 PASSWORD '123' ADMIN; +> ok + +ALTER USER TEST2 SET PASSWORD 'abc'; +> ok + +ALTER USER TEST2 ADMIN FALSE; +> ok + +CREATE MEMORY TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +CREATE MEMORY TABLE TEST2_X(ID INT); +> ok + +CREATE INDEX IDX_ID ON TEST2_X(ID); +> ok + +ALTER TABLE TEST2_X RENAME TO TEST2; +> ok + +ALTER INDEX IDX_ID RENAME TO IDX_ID2; +> ok + +SCRIPT NOPASSWORDS NOSETTINGS NOVERSION; +> SCRIPT +> -------------------------------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" PASSWORD '' ADMIN; +> CREATE USER IF NOT EXISTS "TEST_ADMIN" PASSWORD '' ADMIN; +> CREATE USER IF NOT EXISTS "TEST" PASSWORD ''; +> CREATE USER IF NOT EXISTS "TEST2" PASSWORD ''; +> CREATE MEMORY TABLE "PUBLIC"."TEST"( "ID" INTEGER NOT NULL, "NAME" CHARACTER VARYING(255) ); +> ALTER TABLE "PUBLIC"."TEST" ADD CONSTRAINT "PUBLIC"."CONSTRAINT_2" PRIMARY KEY("ID"); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST; +> CREATE MEMORY TABLE "PUBLIC"."TEST2"( "ID" INTEGER ); +> -- 0 +/- SELECT COUNT(*) FROM PUBLIC.TEST2; +> CREATE INDEX "PUBLIC"."IDX_ID2" ON "PUBLIC"."TEST2"("ID" NULLS FIRST); +> rows (ordered): 10 + +SELECT USER_NAME, IS_ADMIN FROM INFORMATION_SCHEMA.USERS; +> USER_NAME IS_ADMIN +> ---------- -------- +> SA TRUE +> TEST FALSE +> TEST2 FALSE +> TEST_ADMIN TRUE +> rows: 4 + +DROP TABLE TEST2; +> ok + +DROP TABLE TEST; +> ok + +DROP USER TEST; +> ok + +DROP USER IF EXISTS TEST; +> ok + +DROP USER IF EXISTS TEST2; +> ok + +DROP USER TEST_ADMIN; +> ok + +SET AUTOCOMMIT FALSE; +> ok + +SET SALT '' HASH ''; +> ok + +CREATE USER SECURE SALT '001122' HASH '1122334455'; +> ok + +ALTER USER SECURE SET SALT '112233' HASH '2233445566'; +> ok + +SCRIPT NOSETTINGS NOVERSION; +> SCRIPT +> ------------------------------------------------------------------- +> CREATE USER IF NOT EXISTS "SA" SALT '' HASH '' ADMIN; +> CREATE USER IF NOT EXISTS "SECURE" SALT '112233' HASH '2233445566'; +> rows (ordered): 2 + +SET PASSWORD '123'; +> ok + +SET AUTOCOMMIT TRUE; +> ok + +DROP USER SECURE; +> ok + +--- test cases --------------------------------------------------------------------------------------------- +create table test(id int, name varchar); +> ok + +insert into test values(5, 'b'), (5, 'b'), (20, 'a'); +> update count: 3 + +drop table test; +> ok + +select 0 from (( +select 0 as f from dual u1 where null in (?, ?, ?, ?, ?) +) union all ( +select u2.f from ( +select 0 as f from ( +select 0 from dual u2f1f1 where now() = ? +) u2f1 +) u2 +)) where f = 12345; +{ +11, 22, 33, 44, 55, null +> 0 +> - +> rows: 0 +}; +> update count: 0 + +create table x(id int not null); +> ok + +alter table if exists y add column a varchar; +> ok + +alter table if exists x add column a varchar; +> ok + +alter table if exists x add column a varchar; +> exception DUPLICATE_COLUMN_NAME_1 + +alter table if exists y alter column a rename to b; +> ok + +alter table if exists x alter column a rename to b; +> ok + +alter table if exists x alter column a rename to b; +> exception COLUMN_NOT_FOUND_1 + +alter table if exists y alter column b set default 'a'; +> ok + +alter table if exists x alter column b set default 'a'; +> ok + +insert into x(id) values(1); +> update count: 1 + +select b from x; +>> a + +delete from x; +> update count: 1 + +alter table if exists y alter column b drop default; +> ok + +alter table if exists x alter column b drop default; +> ok + +alter table if exists y alter column b set not null; +> ok + +alter table if exists x alter column b set not null; +> ok + +insert into x(id) values(1); +> exception NULL_NOT_ALLOWED + +alter table if exists y alter column b drop not null; +> ok + +alter table if exists x alter column b drop not null; +> ok + +insert into x(id) values(1); +> update count: 1 + +select b from x; +>> null + +delete from x; +> update count: 1 + +alter table if exists y add constraint x_pk primary key (id); +> ok + +alter table if exists x add constraint x_pk primary key (id); +> ok + +alter table if exists x add constraint x_pk primary key (id); +> exception CONSTRAINT_ALREADY_EXISTS_1 + +insert into x(id) values(1); +> update count: 1 + +insert into x(id) values(1); +> exception DUPLICATE_KEY_1 + +delete from x; +> update count: 1 + +alter table if exists y add constraint x_check check (b = 'a'); +> ok + +alter table if exists x add constraint x_check check (b = 'a'); +> ok + +alter table if exists x add constraint x_check check (b = 'a'); +> exception CONSTRAINT_ALREADY_EXISTS_1 + +insert into x(id, b) values(1, 'b'); +> exception CHECK_CONSTRAINT_VIOLATED_1 + +alter table if exists y rename constraint x_check to x_check1; +> ok + +alter table if exists x rename constraint x_check to x_check1; +> ok + +alter table if exists x rename constraint x_check to x_check1; +> exception CONSTRAINT_NOT_FOUND_1 + +alter table if exists y drop constraint x_check1; +> ok + +alter table if exists x drop constraint x_check1; +> ok + +alter table if exists y rename to z; +> ok + +alter table if exists x rename to z; +> ok + +alter table if exists x rename to z; +> ok + +insert into z(id, b) values(1, 'b'); +> update count: 1 + +delete from z; +> update count: 1 + +alter table if exists y add constraint z_uk unique (b); +> ok + +alter table if exists z add constraint z_uk unique (b); +> ok + +alter table if exists z add constraint z_uk unique (b); +> exception CONSTRAINT_ALREADY_EXISTS_1 + +insert into z(id, b) values(1, 'b'); +> update count: 1 + +insert into z(id, b) values(1, 'b'); +> exception DUPLICATE_KEY_1 + +delete from z; +> update count: 1 + +alter table if exists y drop column b; +> ok + +alter table if exists z drop column b; +> ok + +alter table if exists z drop column b; +> exception COLUMN_NOT_FOUND_1 + +alter table if exists y drop primary key; +> ok + +alter table if exists z drop primary key; +> ok + +alter table if exists z drop primary key; +> exception INDEX_NOT_FOUND_1 + +create table x (id int not null primary key); +> ok + +alter table if exists y add constraint z_fk foreign key (id) references x (id); +> ok + +alter table if exists z add constraint z_fk foreign key (id) references x (id); +> ok + +alter table if exists z add constraint z_fk foreign key (id) references x (id); +> exception CONSTRAINT_ALREADY_EXISTS_1 + +insert into z (id) values (1); +> exception REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1 + +SET MODE MySQL; +> ok + +alter table if exists y drop foreign key z_fk; +> ok + +alter table if exists z drop foreign key z_fk; +> ok + +alter table if exists z drop foreign key z_fk; +> exception CONSTRAINT_NOT_FOUND_1 + +SET MODE Regular; +> ok + +insert into z (id) values (1); +> update count: 1 + +delete from z; +> update count: 1 + +drop table x; +> ok + +drop table z; +> ok + +create schema x; +> ok + +alter schema if exists y rename to z; +> ok + +alter schema if exists x rename to z; +> ok + +alter schema if exists x rename to z; +> ok + +create table z.z (id int); +> ok + +drop schema z cascade; +> ok + +----- Issue#493 ----- +create table test ("YEAR" int, action varchar(10)); +> ok + +insert into test values (2015, 'order'), (2016, 'order'), (2014, 'order'); +> update count: 3 + +insert into test values (2014, 'execution'), (2015, 'execution'), (2016, 'execution'); +> update count: 3 + +select * from test where "YEAR" in (select distinct "YEAR" from test order by "YEAR" desc limit 1 offset 0); +> YEAR ACTION +> ---- --------- +> 2016 execution +> 2016 order +> rows: 2 + +drop table test; +> ok diff --git a/h2/src/test/org/h2/test/scripts/testSimple.sql b/h2/src/test/org/h2/test/scripts/testSimple.sql new file mode 100644 index 0000000..ae5fc89 --- /dev/null +++ b/h2/src/test/org/h2/test/scripts/testSimple.sql @@ -0,0 +1,1259 @@ +-- 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 +-- +select 1000L / 10; +>> 100 + +select * from (select 1 as y from dual order by y); +>> 1 + +select 1 from(select 2 from(select 1) a right join dual b) c; +>> 1 + +select 1.00 / 3 * 0.00; +>> 0.000000000000000000000000 + +select 1.00000 / 3 * 0.0000; +>> 0.00000000000000000000000000000 + +select 1.0000000 / 3 * 0.00000; +>> 0.00000000000000000000000000000000 + +select 1.0000000 / 3 * 0.000000; +>> 0.000000000000000000000000000000000 + +create table test(id null); +> ok + +drop table test; +> ok + +select * from (select group_concat(distinct 1) from system_range(1, 3)); +>> 1 + +select sum(mod(x, 2) = 1) from system_range(1, 10); +>> 5 + +create table a(x int); +> ok + +create table b(x int); +> ok + +select count(*) from (select b.x from a left join b); +>> 0 + +drop table a, b; +> ok + +select count(distinct now()) c from system_range(1, 100), system_range(1, 1000); +>> 1 + +select {fn TIMESTAMPADD(SQL_TSI_DAY, 1, {ts '2011-10-20 20:30:40.001'})}; +>> 2011-10-21 20:30:40.001 + +select {fn TIMESTAMPADD(SQL_TSI_SECOND, 1, cast('2011-10-20 20:30:40.001' as timestamp))}; +>> 2011-10-20 20:30:41.001 + +select N'test'; +>> test + +select E'test\\test'; +>> test\test + +create table a(id int unique) as select null; +> ok + +create table b(id int references a(id)) as select null; +> ok + +delete from a; +> update count: 1 + +drop table a, b; +> ok + +create cached temp table test(id identity) not persistent; +> ok + +drop table test; +> ok + +create table test(id int); +> ok + +alter table test alter column id set default 'x'; +> ok + +select column_default from information_schema.columns c where c.table_name = 'TEST' and c.column_name = 'ID'; +>> 'x' + +alter table test alter column id set not null; +> ok + +select is_nullable from information_schema.columns c where c.table_name = 'TEST' and c.column_name = 'ID'; +>> NO + +alter table test alter column id set data type varchar; +> ok + +select data_type from information_schema.columns c where c.table_name = 'TEST' and c.column_name = 'ID'; +>> CHARACTER VARYING + +alter table test alter column id type int; +> ok + +select data_type from information_schema.columns c where c.table_name = 'TEST' and c.column_name = 'ID'; +>> INTEGER + +alter table test alter column id drop default; +> ok + +select column_default from information_schema.columns c where c.table_name = 'TEST' and c.column_name = 'ID'; +>> null + +alter table test alter column id drop not null; +> ok + +select is_nullable from information_schema.columns c where c.table_name = 'TEST' and c.column_name = 'ID'; +>> YES + +drop table test; +> ok + +select x from (select *, rownum as r from system_range(1, 3)) where r=2; +>> 2 + +create table test(name varchar(255)) as select 'Hello+World+'; +> ok + +select count(*) from test where name like 'Hello++World++' escape '+'; +>> 1 + +select count(*) from test where name like '+H+e+l+l+o++World++' escape '+'; +>> 1 + +select count(*) from test where name like 'Hello+World++' escape '+'; +>> 0 + +select count(*) from test where name like 'Hello++World+' escape '+'; +>> 0 + +drop table test; +> ok + +select count(*) from system_range(1, 1); +>> 1 + +select count(*) from system_range(1, -1); +>> 0 + +select 1 from dual where '\' like '\' escape ''; +>> 1 + +select left(timestamp '2001-02-03 08:20:31+04', 4); +>> 2001 + +create table t1$2(id int); +> ok + +drop table t1$2; +> ok + +create table test(id int primary key) as select x from system_range(1, 200); +> ok + +delete from test; +> update count: 200 + +insert into test(id) values(1); +> update count: 1 + +select * from test order by id; +>> 1 + +drop table test; +> ok + +create memory table test(id int) not persistent as select 1 from dual; +> ok + +insert into test values(1); +> update count: 1 + +select count(1) from test; +>> 2 + +@reconnect + +select count(1) from test; +>> 0 + +drop table test; +> ok + +create table test(t clob) as select 1; +> ok + +select distinct t from test; +>> 1 + +drop table test; +> ok + +create table test(id int unique not null); +> ok + +drop table test; +> ok + +create table test(id int not null unique); +> ok + +drop table test; +> ok + +select count(*)from((select 1 from dual limit 1)union(select 2 from dual limit 1)); +>> 2 + +select datediff(yyyy, now(), now()); +>> 0 + +create table t(d date) as select '2008-11-01' union select '2008-11-02'; +> ok + +select 1 from t group by year(d) order by year(d); +>> 1 + +drop table t; +> ok + +create table t(d int) as select 2001 union select 2002; +> ok + +select 1 from t group by d/10 order by d/10; +>> 1 + +drop table t; +> ok + +create schema test; +> ok + +create sequence test.report_id_seq; +> ok + +select nextval('"test".REPORT_ID_SEQ'); +>> 1 + +select nextval('"test"."report_id_seq"'); +>> 2 + +select nextval('test.report_id_seq'); +>> 3 + +drop schema test cascade; +> ok + +create table master(id int primary key); +> ok + +create table detail(id int primary key, x bigint, foreign key(x) references master(id) on delete cascade); +> ok + +alter table detail alter column x bigint; +> ok + +insert into master values(0); +> update count: 1 + +insert into detail values(0,0); +> update count: 1 + +delete from master; +> update count: 1 + +drop table master, detail; +> ok + +drop all objects; +> ok + +create table test(id int primary key, parent int references test(id) on delete cascade); +> ok + +insert into test values(0, 0); +> update count: 1 + +alter table test rename to test2; +> ok + +delete from test2; +> update count: 1 + +drop table test2; +> ok + +create view test_view(id) as select * from dual; +> ok + +drop view test_view; +> ok + +SET MODE DB2; +> ok + +SELECT * FROM SYSTEM_RANGE(1, 100) OFFSET 99 ROWS; +>> 100 + +SELECT * FROM SYSTEM_RANGE(1, 100) OFFSET 50 ROWS FETCH FIRST 1 ROW ONLY; +>> 51 + +SELECT * FROM SYSTEM_RANGE(1, 100) FETCH FIRST 1 ROWS ONLY; +>> 1 + +SELECT * FROM SYSTEM_RANGE(1, 100) FETCH FIRST ROW ONLY; +>> 1 + +SET MODE REGULAR; +> ok + +create domain email as varchar comment 'e-mail'; +> ok + +create table test(e email); +> ok + +select remarks from INFORMATION_SCHEMA.COLUMNS where table_name='TEST'; +>> e-mail + +drop table test; +> ok + +drop domain email; +> ok + +create table test$test(id int); +> ok + +drop table test$test; +> ok + +create table test$$test(id int); +> ok + +drop table test$$test; +> ok + +create table test (id varchar(36) as random_uuid() primary key); +> ok + +insert into test() values(); +> update count: 1 + +delete from test where id = select id from test; +> update count: 1 + +drop table test; +> ok + +create table test (id varchar(36) as now() primary key); +> ok + +insert into test() values(); +> update count: 1 + +delete from test where id = select id from test; +> update count: 1 + +drop table test; +> ok + +SELECT SOME(X>4) FROM SYSTEM_RANGE(1,6); +>> TRUE + +SELECT EVERY(X>4) FROM SYSTEM_RANGE(1,6); +>> FALSE + +SELECT BOOL_OR(X>4) FROM SYSTEM_RANGE(1,6); +>> TRUE + +SELECT BOOL_AND(X>4) FROM SYSTEM_RANGE(1,6); +>> FALSE + +SELECT BIT_OR(X) FROM SYSTEM_RANGE(1,6); +>> 7 + +SELECT BIT_AND(X) FROM SYSTEM_RANGE(1,6); +>> 0 + +SELECT BIT_AND(X) FROM SYSTEM_RANGE(1,1); +>> 1 + +CREATE TABLE TEST(ID IDENTITY); +> ok + +ALTER TABLE TEST ALTER COLUMN ID RESTART WITH ?; +{ +10 +}; +> update count: 0 + +INSERT INTO TEST VALUES(DEFAULT); +> update count: 1 + +SELECT * FROM TEST; +>> 10 + +DROP TABLE TEST; +> ok + +CREATE SEQUENCE TEST_SEQ; +> ok + +ALTER SEQUENCE TEST_SEQ RESTART WITH ? INCREMENT BY ?; +{ +20, 3 +}; +> update count: 0 + +SELECT NEXT VALUE FOR TEST_SEQ; +>> 20 + +SELECT NEXT VALUE FOR TEST_SEQ; +>> 23 + +DROP SEQUENCE TEST_SEQ; +> ok + +create schema Contact; +> ok + +CREATE TABLE Account (id BIGINT PRIMARY KEY); +> ok + +CREATE TABLE Person (id BIGINT PRIMARY KEY, FOREIGN KEY (id) REFERENCES Account(id)); +> ok + +CREATE TABLE Contact.Contact (id BIGINT, FOREIGN KEY (id) REFERENCES public.Person(id)); +> ok + +drop schema contact cascade; +> ok + +drop table account, person; +> ok + +create schema Contact; +> ok + +CREATE TABLE Account (id BIGINT primary key); +> ok + +CREATE TABLE Person (id BIGINT primary key, FOREIGN KEY (id) REFERENCES Account); +> ok + +CREATE TABLE Contact.Contact (id BIGINT primary key, FOREIGN KEY (id) REFERENCES public.Person); +> ok + +drop schema contact cascade; +> ok + +drop table account, person; +> ok + +CREATE TABLE TEST(A int NOT NULL, B int NOT NULL, C int) ; +> ok + +ALTER TABLE TEST ADD CONSTRAINT CON UNIQUE(A,B); +> ok + +ALTER TABLE TEST DROP C; +> ok + +ALTER TABLE TEST DROP CONSTRAINT CON; +> ok + +ALTER TABLE TEST DROP B; +> ok + +DROP TABLE TEST; +> ok + +create table test(id int); +> ok + +select count(*) from (select * from ((select * from test) union (select * from test)) a) b where id = 0; +>> 0 + +select count(*) from (select * from ((select * from test) union select * from test) a) b where id = 0; +>> 0 + +select count(*) from (select * from (select * from test union select * from test) a) b where id = 0; +>> 0 + +select 1 from ((test d1 inner join test d2 on d1.id = d2.id) inner join test d3 on d1.id = d3.id) inner join test d4 on d4.id = d1.id; +> 1 +> - +> rows: 0 + +drop table test; +> ok + +select replace(lpad('string', 10), ' ', '*'); +>> ****string + +select instr('abcisj','s', -1) from dual; +>> 5 + +CREATE TABLE TEST(ID INT); +> ok + +INSERT INTO TEST VALUES(1), (2), (3); +> update count: 3 + +create index idx_desc on test(id desc); +> ok + +select * from test where id between 0 and 1; +>> 1 + +select * from test where id between 3 and 4; +>> 3 + +drop table test; +> ok + +CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255)); +> ok + +INSERT INTO TEST VALUES(1, 'Hello'), (2, 'HelloWorld'), (3, 'HelloWorldWorld'); +> update count: 3 + +SELECT COUNT(*) FROM TEST WHERE NAME REGEXP 'World'; +>> 2 + +SELECT NAME FROM TEST WHERE NAME REGEXP 'WorldW'; +>> HelloWorldWorld + +drop table test; +> ok + +create table test(id int); +> ok + +insert into script.public.test(id) values(1), (2); +> update count: 2 + +update test t set t.id=t.id+1; +> update count: 2 + +update public.test set public.test.id=1; +> update count: 2 + +select count(script.public.test.id) from script.public.test; +>> 2 + +update script.public.test set script.public.test.id=1; +> update count: 2 + +drop table script.public.test; +> ok + +select year(timestamp '2007-07-26T18:44:26.109000+02:00'); +>> 2007 + +create table test(id int primary key); +> ok + +begin; +> ok + +insert into test values(1); +> update count: 1 + +rollback; +> ok + +insert into test values(2); +> update count: 1 + +rollback; +> ok + +begin; +> ok + +insert into test values(3); +> update count: 1 + +commit; +> ok + +insert into test values(4); +> update count: 1 + +rollback; +> ok + +select group_concat(id order by id) from test; +>> 2,3,4 + +drop table test; +> ok + +create table test(); +> ok + +insert into test values(); +> update count: 1 + +ALTER TABLE TEST ADD ID INTEGER; +> ok + +select count(*) from test; +>> 1 + +drop table test; +> ok + +select * from dual where 'a_z' like '%=_%' escape '='; +> +> +> +> rows: 1 + +create table test as select 1 from dual union all select 2 from dual; +> ok + +drop table test; +> ok + +create table test_table(column_a integer); +> ok + +insert into test_table values(1); +> update count: 1 + +create view test_view AS SELECT * FROM (SELECT DISTINCT * FROM test_table) AS subquery; +> ok + +select * FROM test_view; +>> 1 + +drop view test_view; +> ok + +drop table test_table; +> ok + +CREATE TABLE TEST(ID INT); +> ok + +INSERT INTO TEST VALUES(1); +> update count: 1 + +CREATE VIEW TEST_VIEW AS SELECT COUNT(ID) X FROM TEST; +> ok + +explain SELECT * FROM TEST_VIEW WHERE X>1; +>> SELECT "PUBLIC"."TEST_VIEW"."X" FROM "PUBLIC"."TEST_VIEW" /* SELECT COUNT(ID) AS X FROM PUBLIC.TEST /* PUBLIC.TEST.tableScan */ HAVING COUNT(ID) >= ?1: X > CAST(1 AS BIGINT) */ WHERE "X" > CAST(1 AS BIGINT) + +DROP VIEW TEST_VIEW; +> ok + +DROP TABLE TEST; +> ok + +create table test1(id int); +> ok + +insert into test1 values(1), (1), (2), (3); +> update count: 4 + +select sum(C0) from (select count(*) AS C0 from (select distinct * from test1) as temp); +>> 3 + +drop table test1; +> ok + +create table test(id int primary key check id>1); +> ok + +drop table test; +> ok + +create table table1(f1 int not null primary key); +> ok + +create table table2(f2 int not null references table1(f1) on delete cascade); +> ok + +drop table table2; +> ok + +drop table table1; +> ok + +create table table1(f1 int not null primary key); +> ok + +create table table2(f2 int not null primary key references table1(f1)); +> ok + +drop table table1, table2; +> ok + +create table test(id int); +> ok + +insert into test values(1); +> update count: 1 + +select distinct id from test a order by a.id; +>> 1 + +drop table test; +> ok + +create table FOO (ID int, A number(18, 2)); +> ok + +insert into FOO (ID, A) values (1, 10.0), (2, 20.0); +> update count: 2 + +select SUM (CASE when ID=1 then 0 ELSE A END) col0 from Foo; +>> 20.00 + +drop table FOO; +> ok + +select (SELECT true)+1 GROUP BY 1; +>> 2 + +create table FOO (ID int, A number(18, 2)); +> ok + +insert into FOO (ID, A) values (1, 10.0), (2, 20.0); +> update count: 2 + +select SUM (CASE when ID=1 then A ELSE 0 END) col0 from Foo; +>> 10.00 + +drop table FOO; +> ok + +create table A ( ID integer, a1 varchar(20) ); +> ok + +create table B ( ID integer, AID integer, b1 varchar(20)); +> ok + +create table C ( ID integer, BId integer, c1 varchar(20)); +> ok + +insert into A (ID, a1) values (1, 'a1'); +> update count: 1 + +insert into A (ID, a1) values (2, 'a2'); +> update count: 1 + +select count(*) from A left outer join (B inner join C on C.BID=B.ID ) on B.AID=A.ID where A.id=1; +>> 1 + +select count(*) from A left outer join (B left join C on C.BID=B.ID ) on B.AID=A.ID where A.id=1; +>> 1 + +select count(*) from A left outer join B on B.AID=A.ID inner join C on C.BID=B.ID where A.id=1; +>> 0 + +select count(*) from (A left outer join B on B.AID=A.ID) inner join C on C.BID=B.ID where A.id=1; +>> 0 + +drop table a, b, c; +> ok + +create schema a; +> ok + +create table a.test(id int); +> ok + +insert into a.test values(1); +> update count: 1 + +create schema b; +> ok + +create table b.test(id int); +> ok + +insert into b.test values(2); +> update count: 1 + +select a.test.id + b.test.id from a.test, b.test; +>> 3 + +drop schema a cascade; +> ok + +drop schema b cascade; +> ok + +select date '+0011-01-01'; +>> 0011-01-01 + +select date'-0010-01-01'; +>> -0010-01-01 + +create table test(id int); +> ok + +create trigger TEST_TRIGGER before insert on test call "org.h2.test.db.TestTriggersConstraints"; +> ok + +comment on trigger TEST_TRIGGER is 'just testing'; +> ok + +select remarks from information_schema.triggers where trigger_name = 'TEST_TRIGGER'; +>> just testing + +@reconnect + +select remarks from information_schema.triggers where trigger_name = 'TEST_TRIGGER'; +>> just testing + +drop trigger TEST_TRIGGER; +> ok + +@reconnect + +create alias parse_long for "java.lang.Long.parseLong(java.lang.String)"; +> ok + +comment on alias parse_long is 'Parse a long with base'; +> ok + +select remarks from information_schema.routines where routine_name = 'PARSE_LONG'; +>> Parse a long with base + +@reconnect + +select remarks from information_schema.routines where routine_name = 'PARSE_LONG'; +>> Parse a long with base + +drop alias parse_long; +> ok + +@reconnect + +create role hr; +> ok + +comment on role hr is 'Human Resources'; +> ok + +select remarks from information_schema.roles where role_name = 'HR'; +>> Human Resources + +@reconnect + +select remarks from information_schema.roles where role_name = 'HR'; +>> Human Resources + +create user abc password 'x'; +> ok + +grant hr to abc; +> ok + +drop role hr; +> ok + +@reconnect + +drop user abc; +> ok + +create domain email as varchar(100) check instr(value, '@') > 0; +> ok + +comment on domain email is 'must contain @'; +> ok + +select remarks from information_schema.domains where domain_name = 'EMAIL'; +>> must contain @ + +@reconnect + +select remarks from information_schema.domains where domain_name = 'EMAIL'; +>> must contain @ + +drop domain email; +> ok + +@reconnect + +create schema tests; +> ok + +set schema tests; +> ok + +create sequence walk; +> ok + +comment on schema tests is 'Test Schema'; +> ok + +comment on sequence walk is 'Walker'; +> ok + +select remarks from information_schema.schemata where schema_name = 'TESTS'; +>> Test Schema + +select remarks from information_schema.sequences where sequence_name = 'WALK'; +>> Walker + +@reconnect + +select remarks from information_schema.schemata where schema_name = 'TESTS'; +>> Test Schema + +select remarks from information_schema.sequences where sequence_name = 'WALK'; +>> Walker + +drop schema tests cascade; +> ok + +@reconnect + +drop table test; +> ok + +@reconnect + +create table test(id int); +> ok + +alter table test add constraint const1 unique(id); +> ok + +create index IDX_ID on test(id); +> ok + +comment on constraint const1 is 'unique id'; +> ok + +comment on index IDX_ID is 'id_index'; +> ok + +select remarks from information_schema.table_constraints where constraint_name = 'CONST1'; +>> unique id + +select remarks from information_schema.indexes where index_name = 'IDX_ID'; +>> id_index + +@reconnect + +select remarks from information_schema.table_constraints where constraint_name = 'CONST1'; +>> unique id + +select remarks from information_schema.indexes where index_name = 'IDX_ID'; +>> id_index + +drop table test; +> ok + +@reconnect + +create user sales password '1'; +> ok + +comment on user sales is 'mr. money'; +> ok + +select remarks from information_schema.users where user_name = 'SALES'; +>> mr. money + +@reconnect + +select remarks from information_schema.users where user_name = 'SALES'; +>> mr. money + +alter user sales rename to SALES_USER; +> ok + +select remarks from information_schema.users where user_name = 'SALES_USER'; +>> mr. money + +@reconnect + +select remarks from information_schema.users where user_name = 'SALES_USER'; +>> mr. money + +create table test(id int); +> ok + +create linked table test_link('org.h2.Driver', 'jdbc:h2:mem:', 'sa', 'sa', 'DUAL'); +> ok + +comment on table test_link is '123'; +> ok + +select remarks from information_schema.tables where table_name = 'TEST_LINK'; +>> 123 + +@reconnect + +select remarks from information_schema.tables where table_name = 'TEST_LINK'; +>> 123 + +comment on table test_link is 'xyz'; +> ok + +select remarks from information_schema.tables where table_name = 'TEST_LINK'; +>> xyz + +alter table test_link rename to test_l; +> ok + +select remarks from information_schema.tables where table_name = 'TEST_L'; +>> xyz + +@reconnect + +select remarks from information_schema.tables where table_name = 'TEST_L'; +>> xyz + +drop table test; +> ok + +@reconnect + +create table test(id int); +> ok + +create view test_v as select * from test; +> ok + +comment on table test_v is 'abc'; +> ok + +select remarks from information_schema.tables where table_name = 'TEST_V'; +>> abc + +@reconnect + +select remarks from information_schema.tables where table_name = 'TEST_V'; +>> abc + +alter table test_v rename to TEST_VIEW; +> ok + +select remarks from information_schema.tables where table_name = 'TEST_VIEW'; +>> abc + +@reconnect + +select remarks from information_schema.tables where table_name = 'TEST_VIEW'; +>> abc + +drop table test cascade; +> ok + +@reconnect + +create table test(a int); +> ok + +comment on table test is 'hi'; +> ok + +select remarks from information_schema.tables where table_name = 'TEST'; +>> hi + +alter table test add column b int; +> ok + +select remarks from information_schema.tables where table_name = 'TEST'; +>> hi + +alter table test rename to test1; +> ok + +select remarks from information_schema.tables where table_name = 'TEST1'; +>> hi + +@reconnect + +select remarks from information_schema.tables where table_name = 'TEST1'; +>> hi + +comment on table test1 is 'ho'; +> ok + +@reconnect + +select remarks from information_schema.tables where table_name = 'TEST1'; +>> ho + +drop table test1; +> ok + +create table test(a int, b int); +> ok + +comment on column test.b is 'test'; +> ok + +select remarks from information_schema.columns where table_name = 'TEST' and column_name = 'B'; +>> test + +@reconnect + +select remarks from information_schema.columns where table_name = 'TEST' and column_name = 'B'; +>> test + +alter table test drop column b; +> ok + +@reconnect + +comment on column test.a is 'ho'; +> ok + +select remarks from information_schema.columns where table_name = 'TEST' and column_name = 'A'; +>> ho + +@reconnect + +select remarks from information_schema.columns where table_name = 'TEST' and column_name = 'A'; +>> ho + +drop table test; +> ok + +@reconnect + +create table test(a int); +> ok + +comment on column test.a is 'test'; +> ok + +alter table test rename to test2; +> ok + +@reconnect + +select remarks from information_schema.columns where table_name = 'TEST2'; +>> test + +@reconnect + +select remarks from information_schema.columns where table_name = 'TEST2'; +>> test + +drop table test2; +> ok + +@reconnect + +create table test1 (a varchar(10)); +> ok + +create hash index x1 on test1(a); +> ok + +insert into test1 values ('abcaaaa'),('abcbbbb'),('abccccc'),('abcdddd'); +> update count: 4 + +insert into test1 values ('abcaaaa'),('abcbbbb'),('abccccc'),('abcdddd'); +> update count: 4 + +insert into test1 values ('abcaaaa'),('abcbbbb'),('abccccc'),('abcdddd'); +> update count: 4 + +insert into test1 values ('abcaaaa'),('abcbbbb'),('abccccc'),('abcdddd'); +> update count: 4 + +select count(*) from test1 where a='abcaaaa'; +>> 4 + +select count(*) from test1 where a='abcbbbb'; +>> 4 + +@reconnect + +select count(*) from test1 where a='abccccc'; +>> 4 + +select count(*) from test1 where a='abcdddd'; +>> 4 + +update test1 set a='abccccc' where a='abcdddd'; +> update count: 4 + +select count(*) from test1 where a='abccccc'; +>> 8 + +select count(*) from test1 where a='abcdddd'; +>> 0 + +delete from test1 where a='abccccc'; +> update count: 8 + +select count(*) from test1 where a='abccccc'; +>> 0 + +truncate table test1; +> update count: 8 + +insert into test1 values ('abcaaaa'); +> update count: 1 + +insert into test1 values ('abcaaaa'); +> update count: 1 + +delete from test1; +> update count: 2 + +drop table test1; +> ok + +@reconnect + +drop table if exists test; +> ok + +create table if not exists test(col1 int primary key); +> ok + +insert into test values(1); +> update count: 1 + +insert into test values(2); +> update count: 1 + +insert into test values(3); +> update count: 1 + +select count(*) from test; +>> 3 + +select max(col1) from test; +>> 3 + +update test set col1 = col1 + 1 order by col1 asc limit 100; +> update count: 3 + +select count(*) from test; +>> 3 + +select max(col1) from test; +>> 4 + +drop table if exists test; +> ok diff --git a/h2/src/test/org/h2/test/server/TestAutoServer.java b/h2/src/test/org/h2/test/server/TestAutoServer.java new file mode 100644 index 0000000..72090a0 --- /dev/null +++ b/h2/src/test/org/h2/test/server/TestAutoServer.java @@ -0,0 +1,241 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.server; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.SortedProperties; + +/** + * Tests automatic embedded/server mode. + */ +public class TestAutoServer extends TestDb { + + /** + * The number of iterations. + */ + static final int ITERATIONS = 30; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testUnsupportedCombinations(); + testAutoServer(false); + testSocketReadTimeout(false); + if (!config.big) { + testAutoServer(true); + } + testLinkedLocalTablesWithAutoServerReconnect(); + } + + private void testUnsupportedCombinations() { + String[] urls = { + "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" + }; + for (String url : urls) { + assertThrows(SQLException.class, () -> getConnection(url)); + try { + getConnection(url); + fail(url); + } catch (SQLException e) { + assertKnownException(e); + } + } + } + + private void testAutoServer(boolean port) throws Exception { + if (config.memory || config.networked) { + return; + } + deleteDb(getTestName()); + String url = getURL(getTestName() + ";AUTO_SERVER=TRUE", true); + if (port) { + url += ";AUTO_SERVER_PORT=11111"; + } + String user = getUser(), password = getPassword(); + try (Connection connServer = getConnection(url + ";OPEN_NEW=TRUE", user, password)) { + int i = ITERATIONS; + for (; i > 0; i--) { + Thread.sleep(100); + SortedProperties prop = SortedProperties.loadProperties( + getBaseDir() + "/" + getTestName() + ".lock.db"); + String key = prop.getProperty("id"); + String server = prop.getProperty("server"); + if (server != null) { + String u2 = url.substring(url.indexOf(';')); + u2 = "jdbc:h2:tcp://" + server + "/" + key + u2; + Connection conn = DriverManager.getConnection(u2, user, password); + conn.close(); + int gotPort = Integer.parseInt(server.substring(server.lastIndexOf(':') + 1)); + if (port) { + assertEquals(11111, gotPort); + } + break; + } + } + if (i <= 0) { + fail(); + } + try (Connection conn = getConnection(url + ";OPEN_NEW=TRUE")) { + Statement stat = conn.createStatement(); + if (config.big) { + try { + stat.execute("SHUTDOWN"); + } catch (SQLException e) { + assertKnownException(e); + // the connection is closed + } + } + } + } + deleteDb("autoServer"); + } + + + private void testSocketReadTimeout(boolean port) throws Exception { + if (config.memory || config.networked) { + return; + } + deleteDb(getTestName()); + String url = getURL(getTestName() + ";AUTO_SERVER=TRUE", true); + if (port) { + url += ";AUTO_SERVER_PORT=11111"; + } + String user = getUser(), password = getPassword(); + Connection connServer = getConnection(url + ";OPEN_NEW=TRUE", + user, password); + try { + SortedProperties prop = SortedProperties.loadProperties( + getBaseDir() + "/" + getTestName() + ".lock.db"); + String key = prop.getProperty("id"); + String server = prop.getProperty("server"); + if (server != null) { + String u2 = url.substring(url.indexOf(';')); + //todo java.net.SocketTimeoutException: Read timed out + u2 = "jdbc:h2:tcp://" + server + "/" + key + u2 + ";NETWORK_TIMEOUT=100"; + Connection conn = DriverManager.getConnection(u2, user, password); + Statement stat = conn.createStatement(); + assertThrows(ErrorCode.CONNECTION_BROKEN_1, stat). + executeQuery("SELECT MAX(RAND()) FROM SYSTEM_RANGE(1, 100000000)"); + conn.close(); + int gotPort = Integer.parseInt(server.substring(server.lastIndexOf(':') + 1)); + if (port) { + assertEquals(11111, gotPort); + } + } + Connection conn = getConnection(url + ";OPEN_NEW=TRUE"); + Statement stat = conn.createStatement(); + if (config.big) { + try { + stat.execute("SHUTDOWN"); + } catch (SQLException e) { + assertKnownException(e); + // the connection is closed + } + } + conn.close(); + } finally { + try { + connServer.createStatement().execute("SHUTDOWN"); + if (config.big) { + fail("server should be down already"); + } + } catch (SQLException e) { + assertTrue(config.big); + assertKnownException(e); + } + try { + connServer.close(); + } catch (SQLException ignore) {} + } + + deleteDb("autoServer"); + } + + /** + * Tests recreation of temporary linked tables on reconnect + */ + private void testLinkedLocalTablesWithAutoServerReconnect() + throws SQLException { + if (config.memory || config.networked) { + return; + } + deleteDb(getTestName() + "1"); + deleteDb(getTestName() + "2"); + String url = getURL(getTestName() + "1;AUTO_SERVER=TRUE", true); + String urlLinked = getURL(getTestName() + "2", true); + String user = getUser(), password = getPassword(); + + Connection connLinked = getConnection(urlLinked, user, password); + Statement statLinked = connLinked.createStatement(); + statLinked.execute("CREATE TABLE TEST(ID VARCHAR)"); + + // Server is connection 1 + Connection connAutoServer1 = getConnection( + url + ";OPEN_NEW=TRUE", user, password); + Statement statAutoServer1 = connAutoServer1.createStatement(); + statAutoServer1.execute("CREATE LOCAL TEMPORARY LINKED TABLE T('', '" + + urlLinked + "', '" + user + "', '" + password + "', 'TEST')"); + + // Connection 2 connects + Connection connAutoServer2 = getConnection( + url + ";OPEN_NEW=TRUE", user, password); + Statement statAutoServer2 = connAutoServer2.createStatement(); + statAutoServer2.execute("CREATE LOCAL TEMPORARY LINKED TABLE T('', '" + + urlLinked + "', '" + user + "', '" + password + "', 'TEST')"); + + // Server 1 closes the connection => connection 2 will be the server + // => the "force create local temporary linked..." must be reissued + statAutoServer1.execute("shutdown immediately"); + try { + connAutoServer1.close(); + } catch (SQLException e) { + // ignore + } + + // Now test insert + statAutoServer2.execute("INSERT INTO T (ID) VALUES('abc')"); + statAutoServer2.execute("drop table t"); + connAutoServer2.close(); + + // this will also close the linked connection from statAutoServer1 + connLinked.createStatement().execute("shutdown immediately"); + try { + connLinked.close(); + } catch (SQLException e) { + // ignore + } + + deleteDb(getTestName() + "1"); + deleteDb(getTestName() + "2"); + } + + /** + * This method is called via reflection from the database. + * + * @param exitValue the exit value + */ + public static void halt(int exitValue) { + Runtime.getRuntime().halt(exitValue); + } + +} diff --git a/h2/src/test/org/h2/test/server/TestInit.java b/h2/src/test/org/h2/test/server/TestInit.java new file mode 100644 index 0000000..49a90f0 --- /dev/null +++ b/h2/src/test/org/h2/test/server/TestInit.java @@ -0,0 +1,80 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.server; + +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Writer; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests INIT command within embedded/server mode. + */ +public class TestInit extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String[] a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + + String init1 = getBaseDir() + "/test-init-1.sql"; + String init2 = getBaseDir() + "/test-init-2.sql"; + + // Create two scripts that we will run via "INIT" + FileUtils.createDirectories(FileUtils.getParent(init1)); + + Writer w = new OutputStreamWriter(FileUtils.newOutputStream(init1, false)); + + PrintWriter writer = new PrintWriter(w); + writer.println("create table test(id int generated by default as identity, name varchar);"); + writer.println("insert into test(name) values('cat');"); + writer.close(); + + w = new OutputStreamWriter(FileUtils.newOutputStream(init2, false)); + writer = new PrintWriter(w); + writer.println("insert into test(name) values('dog');"); + writer.close(); + + // Make the database connection, and run the two scripts + deleteDb("initDb"); + Connection conn = getConnection("initDb;" + + "INIT=" + + "RUNSCRIPT FROM '" + init1 + "'\\;" + + "RUNSCRIPT FROM '" + init2 + "'"); + + Statement stat = conn.createStatement(); + + // Confirm our scripts have run by loading the data they inserted + ResultSet rs = stat.executeQuery("select name from test order by name"); + + assertTrue(rs.next()); + assertEquals("cat", rs.getString(1)); + + assertTrue(rs.next()); + assertEquals("dog", rs.getString(1)); + + assertFalse(rs.next()); + + conn.close(); + deleteDb("initDb"); + + FileUtils.delete(init1); + FileUtils.delete(init2); + } + +} diff --git a/h2/src/test/org/h2/test/server/TestJakartaWeb.java b/h2/src/test/org/h2/test/server/TestJakartaWeb.java new file mode 100644 index 0000000..7d24757 --- /dev/null +++ b/h2/src/test/org/h2/test/server/TestJakartaWeb.java @@ -0,0 +1,698 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.server; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.security.Principal; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Vector; + +import jakarta.servlet.AsyncContext; +import jakarta.servlet.DispatcherType; +import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.ServletConfig; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.WriteListener; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import jakarta.servlet.http.HttpUpgradeHandler; +import jakarta.servlet.http.Part; + +import org.h2.server.web.JakartaWebServlet; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.Utils10; + +/** + * Tests the Jakarta Web Servlet for the H2 Console. + */ +public class TestJakartaWeb extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testServlet(); + } + + private void testServlet() throws Exception { + JakartaWebServlet servlet = new JakartaWebServlet(); + final HashMap configMap = new HashMap<>(); + configMap.put("ifExists", ""); + configMap.put("", ""); + ServletConfig config = new ServletConfig() { + + @Override + public String getServletName() { + return "H2Console"; + } + + @Override + public Enumeration getInitParameterNames() { + return new Vector<>(configMap.keySet()).elements(); + } + + @Override + public String getInitParameter(String name) { + return configMap.get(name); + } + + @Override + public ServletContext getServletContext() { + return null; + } + + }; + servlet.init(config); + + + TestHttpServletRequest request = new TestHttpServletRequest(); + request.setPathInfo("/"); + TestHttpServletResponse response = new TestHttpServletResponse(); + TestServletOutputStream out = new TestServletOutputStream(); + response.setServletOutputStream(out); + servlet.doGet(request, response); + assertContains(out.toString(), "location.href = 'login.jsp"); + servlet.destroy(); + } + + /** + * A HTTP servlet request for testing. + */ + static class TestHttpServletRequest implements HttpServletRequest { + + private String pathInfo; + + void setPathInfo(String pathInfo) { + this.pathInfo = pathInfo; + } + + @Override + public Object getAttribute(String name) { + return null; + } + + @Override + public Enumeration getAttributeNames() { + return new Vector().elements(); + } + + @Override + public String getCharacterEncoding() { + return null; + } + + @Override + public int getContentLength() { + return 0; + } + + @Override + public String getContentType() { + return null; + } + + @Override + public ServletInputStream getInputStream() throws IOException { + return null; + } + + @Override + public String getLocalAddr() { + return null; + } + + @Override + public String getLocalName() { + return null; + } + + @Override + public int getLocalPort() { + return 0; + } + + @Override + public Locale getLocale() { + return null; + } + + @Override + public Enumeration getLocales() { + return null; + } + + @Override + public String getParameter(String name) { + return null; + } + + @Override + public Map getParameterMap() { + return null; + } + + @Override + public Enumeration getParameterNames() { + return new Vector().elements(); + } + + @Override + public String[] getParameterValues(String name) { + return null; + } + + @Override + public String getProtocol() { + return null; + } + + @Override + public BufferedReader getReader() throws IOException { + return null; + } + + @Override + @Deprecated + public String getRealPath(String path) { + return null; + } + + @Override + public String getRemoteAddr() { + return null; + } + + @Override + public String getRemoteHost() { + return null; + } + + @Override + public int getRemotePort() { + return 0; + } + + @Override + public RequestDispatcher getRequestDispatcher(String name) { + return null; + } + + @Override + public String getScheme() { + return "http"; + } + + @Override + public String getServerName() { + return null; + } + + @Override + public int getServerPort() { + return 80; + } + + @Override + public boolean isSecure() { + return false; + } + + @Override + public void removeAttribute(String name) { + // ignore + } + + @Override + public void setAttribute(String name, Object value) { + // ignore + } + + @Override + public void setCharacterEncoding(String encoding) + throws UnsupportedEncodingException { + // ignore + } + + @Override + public String getAuthType() { + return null; + } + + @Override + public String getContextPath() { + return null; + } + + @Override + public Cookie[] getCookies() { + return null; + } + + @Override + public long getDateHeader(String x) { + return 0; + } + + @Override + public String getHeader(String name) { + return null; + } + + @Override + public Enumeration getHeaderNames() { + return null; + } + + @Override + public Enumeration getHeaders(String name) { + return null; + } + + @Override + public int getIntHeader(String name) { + return 0; + } + + @Override + public String getMethod() { + return null; + } + + @Override + public String getPathInfo() { + return pathInfo; + } + + @Override + public String getPathTranslated() { + return null; + } + + @Override + public String getQueryString() { + return null; + } + + @Override + public String getRemoteUser() { + return null; + } + + @Override + public String getRequestURI() { + return null; + } + + @Override + public StringBuffer getRequestURL() { + return null; + } + + @Override + public String getRequestedSessionId() { + return null; + } + + @Override + public String getServletPath() { + return null; + } + + @Override + public HttpSession getSession() { + return null; + } + + @Override + public HttpSession getSession(boolean x) { + return null; + } + + @Override + public Principal getUserPrincipal() { + return null; + } + + @Override + public boolean isRequestedSessionIdFromCookie() { + return false; + } + + @Override + public boolean isRequestedSessionIdFromURL() { + return false; + } + + @Override + @Deprecated + public boolean isRequestedSessionIdFromUrl() { + return false; + } + + @Override + public boolean isRequestedSessionIdValid() { + return false; + } + + @Override + public boolean isUserInRole(String x) { + return false; + } + + @Override + public java.util.Collection getParts() { + return null; + } + + @Override + public Part getPart(String name) { + return null; + } + + @Override + public boolean authenticate(HttpServletResponse response) { + return false; + } + + @Override + public void login(String username, String password) { + // ignore + } + + @Override + public void logout() { + // ignore + } + + @Override + public ServletContext getServletContext() { + return null; + } + + @Override + public AsyncContext startAsync() { + return null; + } + + @Override + public AsyncContext startAsync( + ServletRequest servletRequest, + ServletResponse servletResponse) { + return null; + } + + @Override + public boolean isAsyncStarted() { + return false; + } + + @Override + public boolean isAsyncSupported() { + return false; + } + + @Override + public AsyncContext getAsyncContext() { + return null; + } + + @Override + public DispatcherType getDispatcherType() { + return null; + } + + @Override + public long getContentLengthLong() { + return 0; + } + + @Override + public String changeSessionId() { + return null; + } + + @Override + public T upgrade(Class handlerClass) + throws IOException, ServletException { + return null; + } + + } + + /** + * A HTTP servlet response for testing. + */ + static class TestHttpServletResponse implements HttpServletResponse { + + ServletOutputStream servletOutputStream; + + void setServletOutputStream(ServletOutputStream servletOutputStream) { + this.servletOutputStream = servletOutputStream; + } + + @Override + public void flushBuffer() throws IOException { + // ignore + } + + @Override + public int getBufferSize() { + return 0; + } + + @Override + public String getCharacterEncoding() { + return null; + } + + @Override + public String getContentType() { + return null; + } + + @Override + public Locale getLocale() { + return null; + } + + @Override + public ServletOutputStream getOutputStream() throws IOException { + return servletOutputStream; + } + + @Override + public PrintWriter getWriter() throws IOException { + return null; + } + + @Override + public boolean isCommitted() { + return false; + } + + @Override + public void reset() { + // ignore + } + + @Override + public void resetBuffer() { + // ignore + } + + @Override + public void setBufferSize(int arg0) { + // ignore + } + + @Override + public void setCharacterEncoding(String arg0) { + // ignore + } + + @Override + public void setContentLength(int arg0) { + // ignore + } + + @Override + public void setContentLengthLong(long arg0) { + // ignore + } + + @Override + public void setContentType(String arg0) { + // ignore + } + + @Override + public void setLocale(Locale arg0) { + // ignore + } + + @Override + public void addCookie(Cookie arg0) { + // ignore + } + + @Override + public void addDateHeader(String arg0, long arg1) { + // ignore + } + + @Override + public void addHeader(String arg0, String arg1) { + // ignore + } + + @Override + public void addIntHeader(String arg0, int arg1) { + // ignore + } + + @Override + public boolean containsHeader(String arg0) { + return false; + } + + @Override + public String encodeRedirectURL(String arg0) { + return null; + } + + @Override + @Deprecated + public String encodeRedirectUrl(String arg0) { + return null; + } + + @Override + public String encodeURL(String arg0) { + return null; + } + + @Override + @Deprecated + public String encodeUrl(String arg0) { + return null; + } + + @Override + public void sendError(int arg0) throws IOException { + // ignore + } + + @Override + public void sendError(int arg0, String arg1) throws IOException { + // ignore + } + + @Override + public void sendRedirect(String arg0) throws IOException { + // ignore + } + + @Override + public void setDateHeader(String arg0, long arg1) { + // ignore + } + + @Override + public void setHeader(String arg0, String arg1) { + // ignore + } + + @Override + public void setIntHeader(String arg0, int arg1) { + // ignore + } + + @Override + public void setStatus(int arg0) { + // ignore + } + + @Override + @Deprecated + public void setStatus(int arg0, String arg1) { + // ignore + } + + @Override + public int getStatus() { + return 0; + } + + @Override + public String getHeader(String name) { + return null; + } + + @Override + public java.util.Collection getHeaders(String name) { + return null; + } + + @Override + public java.util.Collection getHeaderNames() { + return null; + } + + } + + /** + * A servlet output stream for testing. + */ + static class TestServletOutputStream extends ServletOutputStream { + + private final ByteArrayOutputStream buff = new ByteArrayOutputStream(); + + @Override + public void write(int b) throws IOException { + buff.write(b); + } + + @Override + public String toString() { + return Utils10.byteArrayOutputStreamToString(buff, StandardCharsets.UTF_8); + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setWriteListener(WriteListener writeListener) { + // ignore + } + + } + +} diff --git a/h2/src/test/org/h2/test/server/TestNestedLoop.java b/h2/src/test/org/h2/test/server/TestNestedLoop.java new file mode 100644 index 0000000..e085efe --- /dev/null +++ b/h2/src/test/org/h2/test/server/TestNestedLoop.java @@ -0,0 +1,68 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.server; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests remote JDBC access with nested loops. + * This is not allowed in some databases. + */ +public class TestNestedLoop extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + deleteDb("nestedLoop"); + Connection conn = getConnection("nestedLoop"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int generated by default as identity, name varchar)"); + int len = getSize(1010, 10000); + for (int i = 0; i < len; i++) { + stat.execute("insert into test(name) values('Hello World')"); + } + ResultSet rs = stat.executeQuery("select id from test"); + stat.executeQuery("select id from test"); + assertThrows(ErrorCode.OBJECT_CLOSED, rs).next(); + rs = stat.executeQuery("select id from test"); + stat.close(); + assertThrows(ErrorCode.OBJECT_CLOSED, rs).next(); + stat = conn.createStatement(); + rs = stat.executeQuery("select id from test"); + Statement stat2 = conn.createStatement(); + while (rs.next()) { + int id = rs.getInt(1); + ResultSet rs2 = stat2.executeQuery("select * from test where id=" + id); + while (rs2.next()) { + assertEquals(id, rs2.getInt(1)); + assertEquals("Hello World", rs2.getString(2)); + } + rs2 = stat2.executeQuery("select * from test where id=" + id); + while (rs2.next()) { + assertEquals(id, rs2.getInt(1)); + assertEquals("Hello World", rs2.getString(2)); + } + } + conn.close(); + deleteDb("nestedLoop"); + } + +} diff --git a/h2/src/test/org/h2/test/server/TestWeb.java b/h2/src/test/org/h2/test/server/TestWeb.java new file mode 100644 index 0000000..f7cac62 --- /dev/null +++ b/h2/src/test/org/h2/test/server/TestWeb.java @@ -0,0 +1,1187 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.server; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.net.ConnectException; +import java.nio.charset.StandardCharsets; +import java.security.Principal; +import java.sql.Connection; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Vector; + +import javax.servlet.AsyncContext; +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; +import javax.servlet.ServletOutputStream; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.WriteListener; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpUpgradeHandler; +import javax.servlet.http.Part; + +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.engine.SysProperties; +import org.h2.server.web.WebServlet; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.Server; +import org.h2.util.StringUtils; +import org.h2.util.Task; +import org.h2.util.Utils10; + +/** + * Tests the H2 Console application. + */ +public class TestWeb extends TestDb { + + private static volatile String lastUrl; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testServlet(); + testWrongParameters(); + testTools(); + testAlreadyRunning(); + testStartWebServerWithConnection(); + testServer(); + testWebApp(); + testIfExists(); + } + + private void testServlet() throws Exception { + WebServlet servlet = new WebServlet(); + final HashMap configMap = new HashMap<>(); + configMap.put("ifExists", ""); + configMap.put("", ""); + ServletConfig config = new ServletConfig() { + + @Override + public String getServletName() { + return "H2Console"; + } + + @Override + public Enumeration getInitParameterNames() { + return new Vector<>(configMap.keySet()).elements(); + } + + @Override + public String getInitParameter(String name) { + return configMap.get(name); + } + + @Override + public ServletContext getServletContext() { + return null; + } + + }; + servlet.init(config); + + + TestHttpServletRequest request = new TestHttpServletRequest(); + request.setPathInfo("/"); + TestHttpServletResponse response = new TestHttpServletResponse(); + TestServletOutputStream out = new TestServletOutputStream(); + response.setServletOutputStream(out); + servlet.doGet(request, response); + assertContains(out.toString(), "location.href = 'login.jsp"); + servlet.destroy(); + } + + private void testWrongParameters() { + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, () -> Server.createPgServer("-pgPort 8182")); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, () -> Server.createTcpServer("-tcpPort 8182")); + assertThrows(ErrorCode.FEATURE_NOT_SUPPORTED_1, () -> Server.createWebServer("-webPort=8182")); + } + + private void testAlreadyRunning() throws Exception { + Server server = Server.createWebServer( + "-webPort", "8182", "-properties", "null"); + server.start(); + assertContains(server.getStatus(), "server running"); + Server server2 = Server.createWebServer( + "-webPort", "8182", "-properties", "null"); + assertEquals("Not started", server2.getStatus()); + try { + server2.start(); + fail(); + } catch (Exception e) { + assertContains(e.toString(), "port may be in use"); + assertContains(server2.getStatus(), + "could not be started"); + } + server.stop(); + } + + private void testTools() throws Exception { + if (config.memory || config.cipher != null) { + return; + } + deleteDb(getTestName()); + Connection conn = getConnection(getTestName()); + conn.createStatement().execute( + "create table test(id int) as select 1"); + conn.close(); + Server server = new Server(); + server.setOut(new PrintStream(new ByteArrayOutputStream())); + server.runTool("-web", "-webPort", "8182", + "-properties", "null", "-tcp", "-tcpPort", "9101", "-webAdminPassword", "123"); + try { + String url = "http://localhost:8182"; + WebClient client; + String result; + client = new WebClient(); + result = client.get(url); + client.readSessionId(result); + result = client.get(url, "adminLogin.do?password=123"); + result = client.get(url, "tools.jsp"); + FileUtils.delete(getBaseDir() + "/backup.zip"); + result = client.get(url, "tools.do?tool=Backup&args=-dir," + + getBaseDir() + ",-db," + getTestName() + ",-file," + + getBaseDir() + "/backup.zip"); + deleteDb(getTestName()); + assertTrue(FileUtils.exists(getBaseDir() + "/backup.zip")); + result = client.get(url, + "tools.do?tool=DeleteDbFiles&args=-dir," + + getBaseDir() + ",-db," + getTestName()); + String fn = getBaseDir() + "/" + getTestName() + Constants.SUFFIX_MV_FILE; + assertFalse(FileUtils.exists(fn)); + result = client.get(url, "tools.do?tool=Restore&args=-dir," + + getBaseDir() + ",-db," + getTestName() +",-file," + getBaseDir() + + "/backup.zip"); + assertTrue(FileUtils.exists(fn)); + FileUtils.delete(getBaseDir() + "/web.h2.sql"); + FileUtils.delete(getBaseDir() + "/backup.zip"); + result = client.get(url, "tools.do?tool=Recover&args=-dir," + + getBaseDir() + ",-db," + getTestName()); + assertTrue(FileUtils.exists(getBaseDir() + "/" + getTestName() + ".h2.sql")); + FileUtils.delete(getBaseDir() + "/web.h2.sql"); + result = client.get(url, "tools.do?tool=RunScript&args=-script," + + getBaseDir() + "/" + getTestName() + ".h2.sql,-url," + + getURL(getTestName(), true) + + ",-user," + getUser() + ",-password," + getPassword()); + FileUtils.delete(getBaseDir() + "/" + getTestName() + ".h2.sql"); + assertTrue(FileUtils.exists(fn)); + deleteDb(getTestName()); + } finally { + server.shutdown(); + } + } + + private void testServer() throws Exception { + Server server = new Server(); + server.setOut(new PrintStream(new ByteArrayOutputStream())); + server.runTool("-web", "-webPort", "8182", "-properties", + "null", "-tcp", "-tcpPort", "9101"); + try { + String url = "http://localhost:8182"; + WebClient client; + String result; + client = new WebClient(); + client.setAcceptLanguage("de-de,de;q=0.5"); + result = client.get(url); + client.readSessionId(result); + result = client.get(url, "login.jsp"); + assertEquals("text/html", client.getContentType()); + assertContains(result, "Einstellung"); + client.get(url, "favicon.ico"); + assertEquals("image/x-icon", client.getContentType()); + client.get(url, "ico_ok.gif"); + assertEquals("image/gif", client.getContentType()); + client.get(url, "tree.js"); + assertEquals("text/javascript", client.getContentType()); + client.get(url, "stylesheet.css"); + assertEquals("text/css", client.getContentType()); + client.get(url, "admin.do"); + try { + client.get(url, "adminShutdown.do"); + } catch (IOException e) { + // expected + Thread.sleep(1000); + } + } finally { + server.shutdown(); + } + // it should be stopped now + server = Server.createTcpServer("-tcpPort", "9101"); + server.start(); + server.stop(); + } + + private void testIfExists() throws Exception { + Connection conn = getConnection("jdbc:h2:mem:" + getTestName(), + getUser(), getPassword()); + Server server = new Server(); + server.setOut(new PrintStream(new ByteArrayOutputStream())); + // -ifExists is the default + server.runTool("-web", "-webPort", "8182", + "-properties", "null", "-tcp", "-tcpPort", "9101"); + try { + String url = "http://localhost:8182"; + WebClient client; + String result; + client = new WebClient(); + result = client.get(url); + client.readSessionId(result); + result = client.get(url, "login.jsp"); + result = client.get(url, "test.do?driver=org.h2.Driver" + + "&url=jdbc:h2:mem:" + getTestName() + + "&user=" + getUser() + "&password=" + + getPassword() + "&name=_test_"); + assertTrue(result.indexOf("Exception") < 0); + result = client.get(url, "test.do?driver=org.h2.Driver" + + "&url=jdbc:h2:mem:" + getTestName() + "Wrong" + + "&user=" + getUser() + "&password=" + + getPassword() + "&name=_test_"); + assertContains(result, "Exception"); + } finally { + server.shutdown(); + conn.close(); + } + + } + + private void testWebApp() throws Exception { + Server server = new Server(); + server.setOut(new PrintStream(new ByteArrayOutputStream())); + server.runTool("-ifNotExists", "-web", "-webPort", "8182", + "-properties", "null", "-tcp", "-tcpPort", "9101"); + try { + String url = "http://localhost:8182"; + WebClient client; + String result; + client = new WebClient(); + result = client.get(url); + client.readSessionId(result); + client.get(url, "login.jsp"); + client.get(url, "adminSave.do"); + result = client.get(url, "index.do?language=de"); + result = client.get(url, "login.jsp"); + assertContains(result, "Einstellung"); + result = client.get(url, "index.do?language=en"); + result = client.get(url, "login.jsp"); + assertTrue(result.indexOf("Einstellung") < 0); + result = client.get(url, "test.do?driver=abc" + + "&url=jdbc:abc:mem: " + getTestName() + + "&user=sa&password=sa&name=_test_"); + assertContains(result, "Exception"); + result = client.get(url, "test.do?driver=org.h2.Driver" + + "&url=jdbc:h2:mem:" + getTestName() + + "&user=sa&password=sa&name=_test_"); + assertTrue(result.indexOf("Exception") < 0); + result = client.get(url, "login.do?driver=org.h2.Driver" + + "&url=jdbc:h2:mem:" + getTestName() + + "&user=sa&password=sa&name=_test_"); + result = client.get(url, "header.jsp"); + result = client.get(url, "query.do?sql=" + + "create table test(id int primary key, name varchar);" + + "insert into test values(1, 'Hello')"); + result = client.get(url, "query.do?sql=create sequence test_sequence"); + result = client.get(url, "query.do?sql=create schema test_schema"); + result = client.get(url, "query.do?sql=" + + "create view test_view as select * from test"); + result = client.get(url, "tables.do"); + result = client.get(url, "query.jsp"); + result = client.get(url, "query.do?sql=select * from test"); + assertContains(result, "Hello"); + result = client.get(url, "query.do?sql=select * from test"); + result = client.get(url, "query.do?sql=@META select * from test"); + assertContains(result, "typeName"); + result = client.get(url, "query.do?sql=delete from test"); + result = client.get(url, "query.do?sql=@LOOP 1000 " + + "insert into test values(?, 'Hello ' || ?/*RND*/)"); + assertContains(result, "1000 * (Prepared)"); + result = client.get(url, "query.do?sql=select * from test"); + result = client.get(url, "query.do?sql=@list select * from test"); + assertContains(result, "Row #"); + result = client.get(url, "query.do?sql=@parameter_meta " + + "select * from test where id = ?"); + assertContains(result, "INTEGER"); + result = client.get(url, "query.do?sql=@edit select * from test"); + assertContains(result, "editResult.do"); + result = client.get(url, "query.do?sql=" + + StringUtils.urlEncode("select space(100001) a, 1 b")); + assertContains(result, "..."); + result = client.get(url, "query.do?sql=" + + StringUtils.urlEncode("call '<&>'")); + assertContains(result, "<&>"); + result = client.get(url, "query.do?sql=@HISTORY"); + result = client.get(url, "getHistory.do?id=4"); + assertContains(result, "select * from test"); + result = client.get(url, "query.do?sql=delete from test"); + // op 1 (row -1: insert, otherwise update): ok, + // 2: delete 3: cancel, + result = client.get(url, "editResult.do?sql=@edit " + + "select * from test&op=1&row=-1&r-1c1=1&r-1c2=Hello"); + assertContains(result, "1"); + assertContains(result, "Hello"); + result = client.get(url, "editResult.do?sql=@edit " + + "select * from test&op=1&row=1&r1c1=1&r1c2=Hallo"); + assertContains(result, "1"); + assertContains(result, "Hallo"); + result = client.get(url, "query.do?sql=select * from test"); + assertContains(result, "1"); + assertContains(result, "Hallo"); + result = client.get(url, "editResult.do?sql=@edit " + + "select * from test&op=2&row=1"); + result = client.get(url, "query.do?sql=select * from test"); + assertContains(result, "no rows"); + + // autoComplete + result = client.get(url, "autoCompleteList.do?query=select 'abc"); + assertContains(StringUtils.urlDecode(result), "'"); + result = client.get(url, "autoCompleteList.do?query=select 'abc''"); + assertContains(StringUtils.urlDecode(result), "'"); + result = client.get(url, "autoCompleteList.do?query=select 'abc' "); + assertContains(StringUtils.urlDecode(result), "||"); + result = client.get(url, "autoCompleteList.do?query=select 'abc' |"); + assertContains(StringUtils.urlDecode(result), "|"); + result = client.get(url, "autoCompleteList.do?query=select 'abc' || "); + assertContains(StringUtils.urlDecode(result), "'"); + result = client.get(url, "autoCompleteList.do?query=call timestamp '2"); + assertContains(result, "20"); + result = client.get(url, "autoCompleteList.do?query=call time '1"); + assertContains(StringUtils.urlDecode(result), "12:00:00"); + result = client.get(url, "autoCompleteList.do?query=" + + "call timestamp '2001-01-01 12:00:00."); + assertContains(result, "nanoseconds"); + result = client.get(url, "autoCompleteList.do?query=" + + "call timestamp '2001-01-01 12:00:00.00"); + assertContains(result, "nanoseconds"); + result = client.get(url, "autoCompleteList.do?query=" + + "call $$ hello world"); + assertContains(StringUtils.urlDecode(result), "$$"); + result = client.get(url, "autoCompleteList.do?query=alter index "); + assertContains(StringUtils.urlDecode(result), "character"); + result = client.get(url, "autoCompleteList.do?query=alter index idx"); + assertContains(StringUtils.urlDecode(result), "character"); + result = client.get(url, "autoCompleteList.do?query=alter index \"IDX_"); + assertContains(StringUtils.urlDecode(result), "\""); + result = client.get(url, "autoCompleteList.do?query=alter index \"IDX_\"\""); + assertContains(StringUtils.urlDecode(result), "\""); + result = client.get(url, "autoCompleteList.do?query=help "); + assertContains(result, "anything"); + result = client.get(url, "autoCompleteList.do?query=help select"); + assertContains(result, "anything"); + result = client.get(url, "autoCompleteList.do?query=call "); + assertContains(result, "0x"); + result = client.get(url, "autoCompleteList.do?query=call 0"); + assertContains(result, "."); + result = client.get(url, "autoCompleteList.do?query=se"); + assertContains(result, "select"); + assertContains(result, "set"); + result = client.get(url, "tables.do"); + assertContains(result, "TEST"); + result = client.get(url, "autoCompleteList.do?query=" + + "select * from "); + assertContains(result, "test"); + result = client.get(url, "autoCompleteList.do?query=" + + "select * from test t where t."); + assertContains(result, "id"); + result = client.get(url, "autoCompleteList.do?query=" + + "select id x from test te where t"); + assertContains(result, "te"); + result = client.get(url, "autoCompleteList.do?query=" + + "select * from test where name = '"); + assertContains(StringUtils.urlDecode(result), "'"); + result = client.get(url, "autoCompleteList.do?query=" + + "select * from information_schema.columns where columns."); + assertContains(result, "column_name"); + + result = client.get(url, "query.do?sql=delete from test"); + + // special commands + result = client.get(url, "query.do?sql=@autocommit_true"); + assertContains(result, "Auto commit is now ON"); + result = client.get(url, "query.do?sql=@autocommit_false"); + assertContains(result, "Auto commit is now OFF"); + result = client.get(url, "query.do?sql=@cancel"); + assertContains(result, "There is currently no running statement"); + result = client.get(url, + "query.do?sql=@generated insert into test(id) values(next value for test_sequence)"); + assertContains(result, "ID1"); + result = client.get(url, + "query.do?sql=@generated(1) insert into test(id) values(next value for test_sequence)"); + assertContains(result, "ID2"); + result = client.get(url, + "query.do?sql=@generated(1, 1) insert into test(id) values(next value for test_sequence)"); + assertContains(result, "IDID33"); + result = client.get(url, + "query.do?sql=@generated(id) insert into test(id) values(next value for test_sequence)"); + assertContains(result, "ID4"); + result = client.get(url, + "query.do?sql=@generated(id, id) insert into test(id) values(next value for test_sequence)"); + assertContains(result, "IDID55"); + result = client.get(url, + "query.do?sql=@generated() insert into test(id) values(next value for test_sequence)"); + assertContains(result, "
    "); + result = client.get(url, "query.do?sql=@maxrows 2000"); + assertContains(result, "Max rowcount is set"); + result = client.get(url, "query.do?sql=@password_hash user password"); + assertContains(result, + "501cf5c163c184c26e62e76d25d441979f8f25dfd7a683484995b4a43a112fdf"); + result = client.get(url, "query.do?sql=@sleep 1"); + assertContains(result, "Ok"); + result = client.get(url, "query.do?sql=@catalogs"); + assertContains(result, "PUBLIC"); + result = client.get(url, "query.do?sql=@column_privileges null null TEST null"); + assertContains(result, "PRIVILEGE"); + result = client.get(url, "query.do?sql=@cross_references null null TEST null null TEST"); + assertContains(result, "PKTABLE_NAME"); + result = client.get(url, "query.do?sql=@exported_keys null null TEST"); + assertContains(result, "PKTABLE_NAME"); + result = client.get(url, "query.do?sql=@imported_keys null null TEST"); + assertContains(result, "PKTABLE_NAME"); + result = client.get(url, "query.do?sql=@primary_keys null null TEST"); + assertContains(result, "PK_NAME"); + result = client.get(url, "query.do?sql=@procedures null null null"); + assertContains(result, "PROCEDURE_NAME"); + result = client.get(url, "query.do?sql=@procedure_columns"); + assertContains(result, "PROCEDURE_NAME"); + result = client.get(url, "query.do?sql=@schemas"); + assertContains(result, "PUBLIC"); + result = client.get(url, "query.do?sql=@table_privileges"); + assertContains(result, "PRIVILEGE"); + result = client.get(url, "query.do?sql=@table_types"); + assertContains(result, "BASE TABLE"); + result = client.get(url, "query.do?sql=@type_info"); + assertContains(result, "CHARACTER LARGE OBJECT"); + result = client.get(url, "query.do?sql=@version_columns"); + assertContains(result, "PSEUDO_COLUMN"); + result = client.get(url, "query.do?sql=@attributes"); + assertContains(result, "ATTR_NAME"); + result = client.get(url, "query.do?sql=@super_tables"); + assertContains(result, "SUPERTABLE_NAME"); + result = client.get(url, "query.do?sql=@super_types"); + assertContains(result, "SUPERTYPE_NAME"); + result = client.get(url, "query.do?sql=@prof_start"); + assertContains(result, "Ok"); + result = client.get(url, "query.do?sql=@prof_stop"); + assertContains(result, "Top Stack Trace(s)"); + result = client.get(url, "query.do?sql=@best_row_identifier null null TEST"); + assertContains(result, "SCOPE"); + assertContains(result, "COLUMN_NAME"); + assertContains(result, "ID"); + result = client.get(url, "query.do?sql=@udts"); + assertContains(result, "CLASS_NAME"); + result = client.get(url, "query.do?sql=@udts null null null 1,2,3"); + assertContains(result, "CLASS_NAME"); + result = client.get(url, "query.do?sql=@LOOP 10 " + + "@STATEMENT insert into test values(?, 'Hello')"); + result = client.get(url, "query.do?sql=select * from test"); + assertContains(result, "8"); + result = client.get(url, "query.do?sql=@EDIT select * from test"); + assertContains(result, "editRow"); + + result = client.get(url, "query.do?sql=@AUTOCOMMIT TRUE"); + result = client.get(url, "query.do?sql=@AUTOCOMMIT FALSE"); + result = client.get(url, "query.do?sql=@TRANSACTION_ISOLATION"); + result = client.get(url, "query.do?sql=@SET MAXROWS 1"); + result = client.get(url, "query.do?sql=select * from test order by id"); + result = client.get(url, "query.do?sql=@SET MAXROWS 1000"); + result = client.get(url, "query.do?sql=@TABLES"); + assertContains(result, "TEST"); + result = client.get(url, "query.do?sql=@COLUMNS null null TEST"); + assertContains(result, "ID"); + result = client.get(url, "query.do?sql=@INDEX_INFO null null TEST"); + assertContains(result, "PRIMARY"); + result = client.get(url, "query.do?sql=@CATALOG"); + assertContains(result, "PUBLIC"); + result = client.get(url, "query.do?sql=@MEMORY"); + assertContains(result, "Used"); + + result = client.get(url, "query.do?sql=@INFO"); + assertContains(result, "getCatalog"); + + result = client.get(url, "logout.do"); + result = client.get(url, "login.do?driver=org.h2.Driver&" + + "url=jdbc:h2:mem:" + getTestName() + + "&user=sa&password=sa&name=_test_"); + + result = client.get(url, "logout.do"); + result = client.get(url, "settingRemove.do?name=_test_"); + + client.get(url, "admin.do"); + } finally { + server.shutdown(); + } + } + + private void testStartWebServerWithConnection() throws Exception { + String old = System.getProperty(SysProperties.H2_BROWSER); + try { + System.setProperty(SysProperties.H2_BROWSER, + "call:" + TestWeb.class.getName() + ".openBrowser"); + Server.openBrowser("testUrl"); + assertEquals("testUrl", lastUrl); + String oldUrl = lastUrl; + final Connection conn = getConnection(getTestName()); + Task t = new Task() { + @Override + public void call() throws Exception { + Server.startWebServer(conn, true); + } + }; + t.execute(); + for (int i = 0; lastUrl == oldUrl; i++) { + if (i > 100) { + throw new Exception("Browser not started"); + } + Thread.sleep(100); + } + String url = lastUrl; + WebClient client = new WebClient(); + client.readSessionId(url); + url = client.getBaseUrl(url); + try { + client.get(url, "logout.do"); + } catch (ConnectException e) { + // the server stops on logout + } + t.get(); + conn.close(); + } finally { + if (old != null) { + System.setProperty(SysProperties.H2_BROWSER, old); + } else { + System.clearProperty(SysProperties.H2_BROWSER); + } + } + } + + /** + * This method is called via reflection. + * + * @param url the browser url + */ + public static void openBrowser(String url) { + lastUrl = url; + } + + /** + * A HTTP servlet request for testing. + */ + static class TestHttpServletRequest implements HttpServletRequest { + + private String pathInfo; + + void setPathInfo(String pathInfo) { + this.pathInfo = pathInfo; + } + + @Override + public Object getAttribute(String name) { + return null; + } + + @Override + public Enumeration getAttributeNames() { + return new Vector().elements(); + } + + @Override + public String getCharacterEncoding() { + return null; + } + + @Override + public int getContentLength() { + return 0; + } + + @Override + public String getContentType() { + return null; + } + + @Override + public ServletInputStream getInputStream() throws IOException { + return null; + } + + @Override + public String getLocalAddr() { + return null; + } + + @Override + public String getLocalName() { + return null; + } + + @Override + public int getLocalPort() { + return 0; + } + + @Override + public Locale getLocale() { + return null; + } + + @Override + public Enumeration getLocales() { + return null; + } + + @Override + public String getParameter(String name) { + return null; + } + + @Override + public Map getParameterMap() { + return null; + } + + @Override + public Enumeration getParameterNames() { + return new Vector().elements(); + } + + @Override + public String[] getParameterValues(String name) { + return null; + } + + @Override + public String getProtocol() { + return null; + } + + @Override + public BufferedReader getReader() throws IOException { + return null; + } + + @Override + @Deprecated + public String getRealPath(String path) { + return null; + } + + @Override + public String getRemoteAddr() { + return null; + } + + @Override + public String getRemoteHost() { + return null; + } + + @Override + public int getRemotePort() { + return 0; + } + + @Override + public RequestDispatcher getRequestDispatcher(String name) { + return null; + } + + @Override + public String getScheme() { + return "http"; + } + + @Override + public String getServerName() { + return null; + } + + @Override + public int getServerPort() { + return 80; + } + + @Override + public boolean isSecure() { + return false; + } + + @Override + public void removeAttribute(String name) { + // ignore + } + + @Override + public void setAttribute(String name, Object value) { + // ignore + } + + @Override + public void setCharacterEncoding(String encoding) + throws UnsupportedEncodingException { + // ignore + } + + @Override + public String getAuthType() { + return null; + } + + @Override + public String getContextPath() { + return null; + } + + @Override + public Cookie[] getCookies() { + return null; + } + + @Override + public long getDateHeader(String x) { + return 0; + } + + @Override + public String getHeader(String name) { + return null; + } + + @Override + public Enumeration getHeaderNames() { + return null; + } + + @Override + public Enumeration getHeaders(String name) { + return null; + } + + @Override + public int getIntHeader(String name) { + return 0; + } + + @Override + public String getMethod() { + return null; + } + + @Override + public String getPathInfo() { + return pathInfo; + } + + @Override + public String getPathTranslated() { + return null; + } + + @Override + public String getQueryString() { + return null; + } + + @Override + public String getRemoteUser() { + return null; + } + + @Override + public String getRequestURI() { + return null; + } + + @Override + public StringBuffer getRequestURL() { + return null; + } + + @Override + public String getRequestedSessionId() { + return null; + } + + @Override + public String getServletPath() { + return null; + } + + @Override + public HttpSession getSession() { + return null; + } + + @Override + public HttpSession getSession(boolean x) { + return null; + } + + @Override + public Principal getUserPrincipal() { + return null; + } + + @Override + public boolean isRequestedSessionIdFromCookie() { + return false; + } + + @Override + public boolean isRequestedSessionIdFromURL() { + return false; + } + + @Override + @Deprecated + public boolean isRequestedSessionIdFromUrl() { + return false; + } + + @Override + public boolean isRequestedSessionIdValid() { + return false; + } + + @Override + public boolean isUserInRole(String x) { + return false; + } + + @Override + public java.util.Collection getParts() { + return null; + } + + @Override + public Part getPart(String name) { + return null; + } + + @Override + public boolean authenticate(HttpServletResponse response) { + return false; + } + + @Override + public void login(String username, String password) { + // ignore + } + + @Override + public void logout() { + // ignore + } + + @Override + public ServletContext getServletContext() { + return null; + } + + @Override + public AsyncContext startAsync() { + return null; + } + + @Override + public AsyncContext startAsync( + ServletRequest servletRequest, + ServletResponse servletResponse) { + return null; + } + + @Override + public boolean isAsyncStarted() { + return false; + } + + @Override + public boolean isAsyncSupported() { + return false; + } + + @Override + public AsyncContext getAsyncContext() { + return null; + } + + @Override + public DispatcherType getDispatcherType() { + return null; + } + + @Override + public long getContentLengthLong() { + return 0; + } + + @Override + public String changeSessionId() { + return null; + } + + @Override + public T upgrade(Class handlerClass) + throws IOException, ServletException { + return null; + } + + } + + /** + * A HTTP servlet response for testing. + */ + static class TestHttpServletResponse implements HttpServletResponse { + + ServletOutputStream servletOutputStream; + + void setServletOutputStream(ServletOutputStream servletOutputStream) { + this.servletOutputStream = servletOutputStream; + } + + @Override + public void flushBuffer() throws IOException { + // ignore + } + + @Override + public int getBufferSize() { + return 0; + } + + @Override + public String getCharacterEncoding() { + return null; + } + + @Override + public String getContentType() { + return null; + } + + @Override + public Locale getLocale() { + return null; + } + + @Override + public ServletOutputStream getOutputStream() throws IOException { + return servletOutputStream; + } + + @Override + public PrintWriter getWriter() throws IOException { + return null; + } + + @Override + public boolean isCommitted() { + return false; + } + + @Override + public void reset() { + // ignore + } + + @Override + public void resetBuffer() { + // ignore + } + + @Override + public void setBufferSize(int arg0) { + // ignore + } + + @Override + public void setCharacterEncoding(String arg0) { + // ignore + } + + @Override + public void setContentLength(int arg0) { + // ignore + } + + @Override + public void setContentLengthLong(long arg0) { + // ignore + } + + @Override + public void setContentType(String arg0) { + // ignore + } + + @Override + public void setLocale(Locale arg0) { + // ignore + } + + @Override + public void addCookie(Cookie arg0) { + // ignore + } + + @Override + public void addDateHeader(String arg0, long arg1) { + // ignore + } + + @Override + public void addHeader(String arg0, String arg1) { + // ignore + } + + @Override + public void addIntHeader(String arg0, int arg1) { + // ignore + } + + @Override + public boolean containsHeader(String arg0) { + return false; + } + + @Override + public String encodeRedirectURL(String arg0) { + return null; + } + + @Override + @Deprecated + public String encodeRedirectUrl(String arg0) { + return null; + } + + @Override + public String encodeURL(String arg0) { + return null; + } + + @Override + @Deprecated + public String encodeUrl(String arg0) { + return null; + } + + @Override + public void sendError(int arg0) throws IOException { + // ignore + } + + @Override + public void sendError(int arg0, String arg1) throws IOException { + // ignore + } + + @Override + public void sendRedirect(String arg0) throws IOException { + // ignore + } + + @Override + public void setDateHeader(String arg0, long arg1) { + // ignore + } + + @Override + public void setHeader(String arg0, String arg1) { + // ignore + } + + @Override + public void setIntHeader(String arg0, int arg1) { + // ignore + } + + @Override + public void setStatus(int arg0) { + // ignore + } + + @Override + @Deprecated + public void setStatus(int arg0, String arg1) { + // ignore + } + + @Override + public int getStatus() { + return 0; + } + + @Override + public String getHeader(String name) { + return null; + } + + @Override + public java.util.Collection getHeaders(String name) { + return null; + } + + @Override + public java.util.Collection getHeaderNames() { + return null; + } + + } + + /** + * A servlet output stream for testing. + */ + static class TestServletOutputStream extends ServletOutputStream { + + private final ByteArrayOutputStream buff = new ByteArrayOutputStream(); + + @Override + public void write(int b) throws IOException { + buff.write(b); + } + + @Override + public String toString() { + return Utils10.byteArrayOutputStreamToString(buff, StandardCharsets.UTF_8); + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setWriteListener(WriteListener writeListener) { + // ignore + } + + } + +} diff --git a/h2/src/test/org/h2/test/server/WebClient.java b/h2/src/test/org/h2/test/server/WebClient.java new file mode 100644 index 0000000..a24d10a --- /dev/null +++ b/h2/src/test/org/h2/test/server/WebClient.java @@ -0,0 +1,153 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.server; + +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.UUID; +import org.h2.util.IOUtils; + +/** + * A simple web browser simulator. + */ +public class WebClient { + + private String sessionId; + private String acceptLanguage; + private String contentType; + + /** + * Open a URL and get the HTML data. + * + * @param url the HTTP URL + * @return the HTML as a string + */ + String get(String url) throws IOException { + HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); + conn.setRequestMethod("GET"); + conn.setInstanceFollowRedirects(true); + if (acceptLanguage != null) { + conn.setRequestProperty("accept-language", acceptLanguage); + } + conn.connect(); + int code = conn.getResponseCode(); + contentType = conn.getContentType(); + if (code != HttpURLConnection.HTTP_OK) { + throw new IOException("Result code: " + code); + } + InputStream in = conn.getInputStream(); + String result = IOUtils.readStringAndClose(new InputStreamReader(in), -1); + conn.disconnect(); + return result; + } + + /** + * Upload a file. + * + * @param url the target URL + * @param fileName the file name to post + * @param in the input stream + * @return the result + */ + String upload(String url, String fileName, InputStream in) throws IOException { + HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); + conn.setDoOutput(true); + conn.setDoInput(true); + conn.setUseCaches(false); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Connection", "Keep-Alive"); + String boundary = UUID.randomUUID().toString(); + conn.setRequestProperty("Content-Type", + "multipart/form-data;boundary="+boundary); + conn.connect(); + DataOutputStream out = new DataOutputStream(conn.getOutputStream()); + out.writeBytes("--" + boundary + "--\r\n"); + out.writeBytes("Content-Disposition: form-data; name=\"upload\";" + + " filename=\"" + fileName +"\"\r\n\r\n"); + IOUtils.copyAndCloseInput(in, out); + out.writeBytes("\r\n--" + boundary + "--\r\n"); + out.close(); + int code = conn.getResponseCode(); + if (code != HttpURLConnection.HTTP_OK) { + throw new IOException("Result code: " + code); + } + in = conn.getInputStream(); + String result = IOUtils.readStringAndClose(new InputStreamReader(in), -1); + conn.disconnect(); + return result; + } + + void setAcceptLanguage(String acceptLanguage) { + this.acceptLanguage = acceptLanguage; + } + + String getContentType() { + return contentType; + } + + /** + * Read the session ID from a URL. + * + * @param url the URL + * @return the session id + */ + String readSessionId(String url) { + int idx = url.indexOf("jsessionid="); + String id = url.substring(idx + "jsessionid=".length()); + for (int i = 0; i < id.length(); i++) { + char ch = id.charAt(i); + if (!Character.isLetterOrDigit(ch)) { + id = id.substring(0, i); + break; + } + } + this.sessionId = id; + return id; + } + + /** + * Read the specified HTML page. + * + * @param url the base URL + * @param page the page to read + * @return the HTML page + */ + String get(String url, String page) throws IOException { + if (sessionId != null) { + if (page.indexOf('?') < 0) { + page += "?"; + } else { + page += "&"; + } + page += "jsessionid=" + sessionId; + } + if (!url.endsWith("/")) { + url += "/"; + } + url += page; + return get(url); + } + + /** + * Get the base URL (the host name and port). + * + * @param url the complete URL + * @return the host name and port + */ + String getBaseUrl(String url) { + int idx = url.indexOf("//"); + idx = url.indexOf('/', idx + 2); + if (idx >= 0) { + return url.substring(0, idx); + } + return url; + } + +} diff --git a/h2/src/test/org/h2/test/server/package.html b/h2/src/test/org/h2/test/server/package.html new file mode 100644 index 0000000..75974b6 --- /dev/null +++ b/h2/src/test/org/h2/test/server/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +This package contains server tests. + +

    \ No newline at end of file diff --git a/h2/src/test/org/h2/test/store/CalculateHashConstant.java b/h2/src/test/org/h2/test/store/CalculateHashConstant.java new file mode 100644 index 0000000..9399768 --- /dev/null +++ b/h2/src/test/org/h2/test/store/CalculateHashConstant.java @@ -0,0 +1,554 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.io.File; +import java.io.FileOutputStream; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collections; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import org.h2.security.AES; + +/** + * Calculate the constant for the secondary / supplemental hash function, so + * that the hash function mixes the input bits as much as possible. + */ +public class CalculateHashConstant implements Runnable { + + private static BitSet primeNumbers = new BitSet(); + private static int[] randomValues; + private static AtomicInteger high = new AtomicInteger(0x20); + private static Set candidates = + Collections.synchronizedSet(new HashSet()); + + private int constant; + private int[] fromTo = new int[32 * 32]; + + private final AES aes = new AES(); + { + aes.setKey("Hello Welt Hallo Welt".getBytes()); + } + private final byte[] data = new byte[16]; + + /** + * Run just this test. + * + * @param args ignored + */ + public static void main(String... args) throws Exception { + for (int i = 0x0; i < 0x10000; i++) { + if (BigInteger.valueOf(i).isProbablePrime(20)) { + primeNumbers.set(i); + } + } + randomValues = getRandomValues(1000, 1); + Random r = new Random(1); + for (int i = 0; i < randomValues.length; i++) { + randomValues[i] = r.nextInt(); + } + Thread[] threads = new Thread[8]; + for (int i = 0; i < 8; i++) { + threads[i] = new Thread(new CalculateHashConstant()); + threads[i].start(); + } + for (int i = 0; i < 8; i++) { + threads[i].join(); + } + int finalCount = 10000; + int[] randomValues = getRandomValues(finalCount, 10); + + System.out.println(); + System.out.println("AES:"); + CalculateHashConstant test = new CalculateHashConstant(); + int[] minMax; + int av = 0; + test = new CalculateHashConstant() { + @Override + public int hash(int x) { + return secureHash(x); + } + }; + minMax = test.getDependencies(test, randomValues); + System.out.println("Dependencies: " + minMax[0] + ".." + minMax[1]); + av = 0; + for (int j = 0; j < 100; j++) { + av += test.getAvalanche(test, randomValues[j]); + } + System.out.println("AvalancheSum: " + av); + minMax = test.getEffect(test, finalCount * 10, 11); + System.out.println("Effect: " + minMax[0] + ".." + minMax[1]); + + test = new CalculateHashConstant(); + int best = 0; + int dist = Integer.MAX_VALUE; + for (int i : new int[] { + 0x10b5383, 0x10b65f3, 0x1170d8b, 0x118e97b, + 0x1190d8b, 0x11ab37d, 0x11c65f3, 0x1228357, 0x122a837, + 0x122a907, 0x12b24f7, 0x12c4d05, 0x131a907, 0x131afa3, + 0x131b683, 0x132927d, 0x13298fb, 0x134a837, 0x13698fb, + 0x136afa3, 0x138da0b, 0x138f563, 0x13957c5, 0x1470b1b, + 0x148e97b, 0x14b5827, 0x150a837, 0x151c97d, 0x151ed3d, + 0x1525707, 0x1534b07, 0x1570b9b, 0x158d283, 0x15933eb, + 0x15947cb, 0x15b5f33, 0x166da7d, 0x16af4a3, 0x16b0b47, + 0x16ca907, 0x16ee585, 0x17609a3, 0x1770b23, 0x17a2507, + 0x1855383, 0x18b5383, 0x18d9f3b, 0x18db37d, 0x1922507, + 0x1960d3d, 0x1990d4f, 0x1991a7b, 0x19b4b07, 0x19b5383, + 0x19d9f3b, 0x1a2b683, 0x1a40d8b, 0x1a4b08d, 0x1a698fb, + 0x1a6cf9b, 0x1a714bd, 0x1a89283, 0x1aa86c3, 0x1b14e0b, + 0x1b196fb, 0x1b1f2bd, 0x1b30b1b, 0x1b32507, 0x1b44f75, + 0x1b50ce3, 0x1b5927d, 0x1b6afa3, 0x1b8a6f7, 0x1baa907, + 0x1bb2507, 0x1c2a6f7, 0x1c48357, 0x1c957c5, 0x1cb8357, + 0x1cc4d05, 0x1cd9283, 0x1ce2683, 0x1d198fb, 0x1d45383, + 0x1d4ed3d, 0x1d598fb, 0x1d8d283, 0x1d95f3b, 0x1db24f7, + 0x1db5383, 0x1db997d, 0x1e465f3, 0x1f198fb, 0x1f2b683, + 0x1f4a837, 0x20998fb, 0x20eb683, 0x214a907, 0x2152ec3, + 0x2169f3b, 0x216ed3d, 0x218a6f7, 0x2194b07, 0x21c5707, + 0x22158f9, 0x2250d4f, 0x2252507, 0x2297d63, 0x22aed3d, + 0x22b5383, 0x22ca7d7, 0x23596fb, 0x23633eb, 0x23957c5, + 0x23b24f7, 0x2476e7b, 0x24a57c5, 0x24b5383, 0x252ebb7, + 0x254f547, 0x258d283, 0x2595707, 0x25957c5, 0x25c5707, + 0x262a837, 0x2638357, 0x2645827, 0x265f2bd, 0x266f2bd, + 0x268a6f7, 0x26a0b23, 0x26cce7b, 0x2730b47, 0x2750b23, + 0x275b683, 0x28465f3, 0x2850d4f, 0x28d0b47, 0x291c97d, + 0x2922507, 0x2941a7b, 0x294a907, 0x294b683, 0x295f2bd, + 0x2969f3b, 0x296cfb5, 0x2976e7b, 0x2989f3b, 0x29933eb, + 0x29a907, 0x29b2683, 0x29c8357, 0x29d9f3b, 0x2a1a907, + 0x2a45383, 0x2a52f75, 0x2a85383, 0x2aacf9b, 0x2ac5f33, + 0x2ad1a7b, 0x2ad2507, 0x2b2b683, 0x2b3af8b, 0x2b63e65, + 0x2b8da0b, 0x2b9416b, 0x2bb24f7, 0x2c4b37d, 0x2c6cf9b, + 0x2ca0d13, 0x2cb2507, 0x2cb2983, 0x2cce97b, 0x2ce305b, + 0x2ceb683, 0x2d14e0b, 0x2d1a7b, 0x2d2507, 0x2d2af8b, 0x2d41a7b, + 0x2d7467b, 0x2d8d283, 0x2d960bb, 0x2dab683, 0x2db5f33, + 0x2dd5f3b, 0x2e2ebb7, 0x2e32e85, 0x2e6a7c9, 0x2e85383, + 0x2e8e585, 0x2e960bb, 0x2f3afa3, 0x2f62fa5, 0x30e8639, + 0x3132983, 0x3150d4f, 0x315cf9b, 0x3162fa5, 0x316ce7b, + 0x31914bd, 0x31a927d, 0x31ea83b, 0x31f1a7b, 0x3285383, + 0x3289f3b, 0x32933eb, 0x329afa3, 0x32a5707, 0x32a6f7, + 0x32ae585, 0x32b1a7b, 0x32b4b07, 0x32b5383, 0x32b5827, + 0x32ee97b, 0x330be5b, 0x3314e0b, 0x33317a5, 0x333af8b, + 0x335afa3, 0x335b37d, 0x3371a7b, 0x3393e65, 0x339a907, + 0x33d1a7b, 0x3425707, 0x34606d3, 0x347b37d, 0x349305b, + 0x34b2683, 0x34b683, 0x34dafa3, 0x34ec97d, 0x3512683, + 0x3515383, 0x3515707, 0x352e585, 0x353af75, 0x354b683, + 0x355ed3d, 0x3562fa5, 0x356f2bd, 0x3574135, 0x359afa3, + 0x35ad283, 0x35b2683, 0x35d9f3b, 0x361ed3d, 0x3671a7b, + 0x3672fa5, 0x36cbe5b, 0x37598fb, 0x375c97d, 0x37ca907, + 0x389a907, 0x38c65f3, 0x38cf547, 0x38e33eb, 0x3931a7b, + 0x39598fb, 0x3979283, 0x398ce7b, 0x39933eb, 0x39960bb, + 0x39b1a87, 0x39b5f33, 0x39c8333, 0x39d2507, 0x3a55827, + 0x3a89f3b, 0x3a9f2bd, 0x3ab2983, 0x3aba7d7, 0x3adafa3, + 0x3b196fb, 0x3b29f3b, 0x3b32e85, 0x3b4e97b, 0x3b9260b, + 0x3bb5383, 0x3c4a907, 0x3c4c97d, 0x3c6cf9b, 0x3c95707, + 0x3ca57c5, 0x3caa907, 0x3cb2683, 0x3cb2983, 0x3ce305b, + 0x3d158f9, 0x3d15f65, 0x3d2c685, 0x3d34b07, 0x3d76e7b, + 0x3d8d283, 0x3d8f563, 0x3dae585, 0x3dd60bb, 0x3e5c97d, + 0x3eb2683, 0x3eb467b, 0x3f5927d, 0x3f596fb, 0x414e585, + 0x424b37d, 0x425afa3, 0x42e5383, 0x4315f65, 0x4325707, + 0x434b683, 0x4485383, 0x448e97b, 0x44996fb, 0x44b3e65, + 0x44eafa3, 0x4515707, 0x4532983, 0x4533e65, 0x453e585, + 0x454b683, 0x454e97b, 0x45598fb, 0x455a907, 0x456f2bd, + 0x4595f3b, 0x45d9f3b, 0x461a907, 0x462a837, 0x4645827, + 0x46a2fa5, 0x46aa213, 0x46acf9b, 0x46b2507, 0x46ba837, + 0x4715707, 0x472f547, 0x475c97d, 0x4799283, 0x47a0d3d, + 0x47ca907, 0x482a907, 0x4835707, 0x4844b07, 0x48933eb, + 0x48ab683, 0x48b5827, 0x491d0e7, 0x4922507, 0x4929f3b, + 0x492e97b, 0x4933eb, 0x4934b07, 0x49598fb, 0x495f2bd, + 0x4962fa5, 0x496b683, 0x499da7d, 0x499ed3d, 0x49ab683, + 0x4a2f547, 0x4a32e85, 0x4a5f2bd, 0x4a696fb, 0x4aacf9b, + 0x4ab5383, 0x4aba837, 0x4aeb683, 0x4aeda7d, 0x4b0f69b, + 0x4b12fa5, 0x4b13e65, 0x4b5a907, 0x4b5afa3, 0x4b8afa3, + 0x4b8e585, 0x4b8f58d, 0x4ba5707, 0x4bc5707, 0x4c2a837, + 0x4c5b37d, 0x4c5ed3d, 0x4c67d8d, 0x4c8f58d, 0x4c933eb, + 0x4caec5d, 0x4cd17a5, 0x4ce305b, 0x4cf179b, 0x4cfc979, + 0x4d0be5b, 0x4d1ed3d, 0x4d2507, 0x4d30d8b, 0x4d32983, + 0x4d40ae5, 0x4d40d8b, 0x4d4f697, 0x4d8e97b, 0x4d9f2bd, + 0x4da0d3d, 0x4dce585, 0x4dd1a7b, 0x4df467b, 0x4e25707, + 0x4e4c97d, 0x4e63833, 0x4eb1a7b, 0x4eb2683, 0x4eb683, + 0x4ece97b, 0x4eee585, 0x4f13e65, 0x4f6afa3, 0x504b37d, + 0x50e2683, 0x50eb683, 0x5132e85, 0x514b08d, 0x516e97b, + 0x5198fb, 0x519f2bd, 0x51ab37d, 0x51b2683, 0x522a837, + 0x5232983, 0x52465f3, 0x52660bb, 0x528b3ed, 0x5294193, + 0x5294e0b, 0x52a57c5, 0x52eaf8b, 0x52eda7d, 0x52ee585, + 0x530be93, 0x5314e0b, 0x532e97b, 0x5340b1b, 0x535279d, + 0x53598fb, 0x5429283, 0x54a0b23, 0x54b24f7, 0x54d17a5, + 0x54eb683, 0x55198fb, 0x5523e65, 0x55357c5, 0x553e585, + 0x555cf9b, 0x55a24f7, 0x55d2507, 0x55eda7d, 0x5645f33, + 0x567ecdd, 0x56a0d3d, 0x571305b, 0x5714e0b, 0x574a837, + 0x57a5707, 0x57b65f3, 0x57c65f3, 0x5879283, 0x58ba837, + 0x58ded3d, 0x59598fb, 0x5989f3b, 0x598a83b, 0x59957c5, + 0x599f2bd, 0x59bd37b, 0x59ca907, 0x59d9f3b, 0x59e60bb, + 0x5a4b37d, 0x5a64133, 0x5a6cf9b, 0x5a89f3b, 0x5a927d, + 0x5a94165, 0x5a94193, 0x5a958f9, 0x5a960bb, 0x5aac3d3, + 0x5ad98fb, 0x5ae98fb, 0x5b198fb, 0x5b2e97b, 0x5b40b5d, + 0x5b5c97d, 0x5b75f33, 0x5b8afa3, 0x5b94e0b, 0x5ba24f7, + 0x5cab683, 0x5cb0b47, 0x5cb0ce5, 0x5cba7d7, 0x5d0be93, + 0x5d12683, 0x5d20cc7, 0x5d3e585, 0x5e2a907, 0x5e3467b, + 0x5e5c97d, 0x5e89f3b, 0x5eb2683, 0x5f598fb, 0x5f5a907, + 0x676e7b, 0x695705, 0x6b2983, 0x8998fb, 0x8ab683, 0x94a837, + 0x95f2bd, 0x991a7b, 0x995705, 0xa714bd, 0xa90a63, 0xa933eb, + 0xad98fb, 0xb365f3, 0xb4a907, 0xb598fb, 0xb5c97d, 0xb5ed3d, + 0xb698fb, 0xbd279d, 0xc55383, 0xc7b37d, 0xc8da0b, 0xca0b23, + 0xca96fb, 0xcacf9b, 0xcb2683, 0xcd1a7b, 0xd45383, 0xd4e585, + 0xd6afa3, 0xd94e0b, 0xdaf547, 0xdb1a7b, 0xdca907, 0xdd2e85, + 0xe6da7d, 0xe94e0b, 0xe9a907, 0xeca7d7, 0xf4a837 + }) { + // for(int i : candidates) { + test.constant = i; + System.out.println(); + System.out.println("Constant: 0x" + Integer.toHexString(i)); + minMax = test.getDependencies(test, randomValues); + System.out.println("Dependencies: " + minMax[0] + ".." + minMax[1]); + int d = minMax[1] - minMax[0]; + av = 0; + for (int j = 0; j < 100; j++) { + av += test.getAvalanche(test, randomValues[j]); + } + System.out.println("AvalancheSum: " + av); + minMax = test.getEffect(test, finalCount * 10, 11); + System.out.println("Effect: " + minMax[0] + ".." + minMax[1]); + d += minMax[1] - minMax[0]; + if (d < dist) { + dist = d; + best = i; + } + } + System.out.println(); + System.out.println("Best constant: 0x" + Integer.toHexString(best)); + test.constant = best; + long collisions = test.getCollisionCount(); + System.out.println("Collisions: " + collisions); + } + + /** + * Calculate the multiplicative inverse of a value (int). + * + * @param a the value + * @return the multiplicative inverse + */ + static long calcMultiplicativeInverse(long a) { + return BigInteger.valueOf(a).modPow( + BigInteger.valueOf((1 << 31) - 1), BigInteger.valueOf(1L << 32)).longValue(); + } + + /** + * Calculate the multiplicative inverse of a value (long). + * + * @param a the value + * @return the multiplicative inverse + */ + static long calcMultiplicativeInverseLong(long a) { + BigInteger oneShift64 = BigInteger.valueOf(1).shiftLeft(64); + BigInteger oneShift63 = BigInteger.valueOf(1).shiftLeft(63); + return BigInteger.valueOf(a).modPow( + oneShift63.subtract(BigInteger.ONE), + oneShift64).longValue(); + } + /** + * Store a random file to be analyzed by the Diehard test. + */ + void storeRandomFile() throws Exception { + File f = new File(System.getProperty("user.home") + "/temp/rand.txt"); + FileOutputStream out = new FileOutputStream(f); + CalculateHashConstant test = new CalculateHashConstant(); + // Random r = new Random(1); + byte[] buff = new byte[4]; + // tt.constant = 0x29a907; + for (int i = 0; i < 10000000 / 8; i++) { + int y = test.hash(i); + // int y = r.nextInt(); + writeInt(buff, 0, y); + out.write(buff); + } + out.close(); + } + + private static int[] getRandomValues(int count, int seed) { + int[] values = new int[count]; + Random r = new Random(seed); + for (int i = 0; i < count; i++) { + values[i] = r.nextInt(); + } + return values; + } + + @Override + public void run() { + while (true) { + int currentHigh = high.getAndIncrement(); + // if (currentHigh > 0x2d) { + if (currentHigh > 0xffff) { + break; + } + System.out.println("testing " + Integer.toHexString(currentHigh) + "...."); + addCandidates(currentHigh); + } + } + + private void addCandidates(int currentHigh) { + for (int low = 0; low <= 0xffff; low++) { + // the lower 16 bits don't have to be a prime number + // but it seems that's a good restriction + if (!primeNumbers.get(low)) { + continue; + } + int i = (currentHigh << 16) | low; + constant = i; + // after one bit changes in the input, + // on average 16 bits of the output change + int av = getAvalanche(this, 0); + if (Math.abs(av - 16000) > 130) { + continue; + } + av = getAvalanche(this, 0xffffffff); + if (Math.abs(av - 16000) > 130) { + continue; + } + long es = getEffectSquare(this, randomValues); + if (es > 259000) { + continue; + } + int[] minMax = getEffect(this, 10000, 1); + if (!isWithin(4800, 5200, minMax)) { + continue; + } + minMax = getDependencies(this, randomValues); + // aes: 7788..8183 + if (!isWithin(7720, 8240, minMax)) { + continue; + } + minMax = getEffect(this, 100000, 3); + if (!isWithin(49000, 51000, minMax)) { + continue; + } + System.out.println(Integer.toHexString(i) + + " hit av:" + av + " bits:" + Integer.bitCount(i) + " es:" + es + " prime:" + + BigInteger.valueOf(i).isProbablePrime(15)); + candidates.add(i); + } + } + + long getCollisionCount() { + BitSet set = new BitSet(); + BitSet neg = new BitSet(); + long collisions = 0; + long t = System.nanoTime(); + for (int i = Integer.MIN_VALUE; i != Integer.MAX_VALUE; i++) { + int x = hash(i); + if (x >= 0) { + if (set.get(x)) { + collisions++; + } else { + set.set(x); + } + } else { + x = -(x + 1); + if (neg.get(x)) { + collisions++; + } else { + neg.set(x); + } + } + if ((i & 0xfffff) == 0) { + long n = System.nanoTime(); + if (n - t > TimeUnit.SECONDS.toNanos(5)) { + System.out.println(Integer.toHexString(constant) + " " + + Integer.toHexString(i) + " collisions: " + collisions); + t = n; + } + } + } + return collisions; + } + + private static boolean isWithin(int min, int max, int[] range) { + return range[0] >= min && range[1] <= max; + } + + /** + * Calculate how much the bit changes (output bits that change if an input + * bit is changed) are independent of each other. + * + * @param h the hash object + * @param values the values to test with + * @return the minimum and maximum number of output bits that are changed in + * combination with another output bit + */ + int[] getDependencies(CalculateHashConstant h, int[] values) { + Arrays.fill(fromTo, 0); + for (int x : values) { + for (int shift = 0; shift < 32; shift++) { + int x1 = h.hash(x); + int x2 = h.hash(x ^ (1 << shift)); + int x3 = x1 ^ x2; + for (int s = 0; s < 32; s++) { + if ((x3 & (1 << s)) != 0) { + for (int s2 = 0; s2 < 32; s2++) { + if (s == s2) { + continue; + } + if ((x3 & (1 << s2)) != 0) { + fromTo[s * 32 + s2]++; + } + } + } + } + } + } + int a = Integer.MAX_VALUE, b = Integer.MIN_VALUE; + for (int x : fromTo) { + if (x == 0) { + continue; + } + if (x < a) { + a = x; + } + if (x > b) { + b = x; + } + } + return new int[] {a, b}; + } + + /** + * Calculate the number of bits that change if a single bit is changed + * multiplied by 1000 (expected: 16000 +/- 5%). + * + * @param h the hash object + * @param value the base value + * @return the number of bit changes multiplied by 1000 + */ + int getAvalanche(CalculateHashConstant h, int value) { + int changedBitsSum = 0; + for (int i = 0; i < 32; i++) { + int x = value ^ (1 << i); + for (int shift = 0; shift < 32; shift++) { + int x1 = h.hash(x); + int x2 = h.hash(x ^ (1 << shift)); + int x3 = x1 ^ x2; + changedBitsSum += Integer.bitCount(x3); + } + } + return changedBitsSum * 1000 / 32 / 32; + } + + /** + * Calculate the sum of the square of the distance to the expected + * probability that an output bit changes if an input bit is changed. The + * lower the value, the better. + * + * @param h the hash object + * @param values the values to test with + * @return sum(distance^2) + */ + long getEffectSquare(CalculateHashConstant h, int[] values) { + Arrays.fill(fromTo, 0); + int total = 0; + for (int x : values) { + for (int shift = 0; shift < 32; shift++) { + int x1 = h.hash(x); + int x2 = h.hash(x ^ (1 << shift)); + int x3 = x1 ^ x2; + for (int s = 0; s < 32; s++) { + if ((x3 & (1 << s)) != 0) { + fromTo[shift * 32 + s]++; + total++; + } + } + } + } + long sqDist = 0; + int expected = total / 32 / 32; + for (int x : fromTo) { + int dist = Math.abs(x - expected); + sqDist += dist * dist; + } + return sqDist; + } + + /** + * Calculate if the all bit changes (that an output bit changes if an input + * bit is changed) are within a certain range. + * + * @param h the hash object + * @param count the number of values to test + * @param seed the random seed + * @return the minimum and maximum value of all input-to-output bit changes + */ + int[] getEffect(CalculateHashConstant h, int count, int seed) { + Random r = new Random(); + r.setSeed(seed); + Arrays.fill(fromTo, 0); + for (int i = 0; i < count; i++) { + int x = r.nextInt(); + for (int shift = 0; shift < 32; shift++) { + int x1 = h.hash(x); + int x2 = h.hash(x ^ (1 << shift)); + int x3 = x1 ^ x2; + for (int s = 0; s < 32; s++) { + if ((x3 & (1 << s)) != 0) { + fromTo[shift * 32 + s]++; + } + } + } + } + int a = Integer.MAX_VALUE, b = Integer.MIN_VALUE; + for (int x : fromTo) { + if (x < a) { + a = x; + } + if (x > b) { + b = x; + } + } + return new int[] {a, b}; + } + + /** + * The hash method. + * + * @param x the input + * @return the output + */ + int hash(int x) { + // return secureHash(x); + x = ((x >>> 16) ^ x) * constant; + x = ((x >>> 16) ^ x) * constant; + x = (x >>> 16) ^ x; + return x; + } + + /** + * Calculate a hash using AES. + * + * @param x the input + * @return the output + */ + int secureHash(int x) { + Arrays.fill(data, (byte) 0); + writeInt(data, 0, x); + aes.encrypt(data, 0, 16); + return readInt(data, 0); + } + + private static void writeInt(byte[] buff, int pos, int x) { + buff[pos++] = (byte) (x >> 24); + buff[pos++] = (byte) (x >> 16); + buff[pos++] = (byte) (x >> 8); + buff[pos++] = (byte) x; + } + + private static int readInt(byte[] buff, int pos) { + return (buff[pos++] << 24) + ((buff[pos++] & 0xff) << 16) + + ((buff[pos++] & 0xff) << 8) + (buff[pos] & 0xff); + } + +} diff --git a/h2/src/test/org/h2/test/store/CalculateHashConstantLong.java b/h2/src/test/org/h2/test/store/CalculateHashConstantLong.java new file mode 100644 index 0000000..6dd2aba --- /dev/null +++ b/h2/src/test/org/h2/test/store/CalculateHashConstantLong.java @@ -0,0 +1,435 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.io.File; +import java.io.FileOutputStream; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collections; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import org.h2.security.AES; + +/** + * Calculate the constant for the secondary hash function, so that the hash + * function mixes the input bits as much as possible. + */ +public class CalculateHashConstantLong implements Runnable { + + private static BitSet primeNumbers = new BitSet(); + private static long[] randomValues; + private static AtomicInteger high = new AtomicInteger(0x20); + private static Set candidates = + Collections.synchronizedSet(new HashSet()); + + private long constant; + private int[] fromTo = new int[64 * 64]; + + private final AES aes = new AES(); + { + aes.setKey("Hello World Hallo Welt".getBytes()); + } + private final byte[] data = new byte[16]; + + /** + * Run just this test. + * + * @param args ignored + */ + public static void main(String... args) throws Exception { + for (int i = 0x0; i < 0x10000; i++) { + if (BigInteger.valueOf(i).isProbablePrime(20)) { + primeNumbers.set(i); + } + } + randomValues = getRandomValues(1000, 1); + Random r = new Random(1); + for (int i = 0; i < randomValues.length; i++) { + randomValues[i] = r.nextInt(); + } + printQuality(new CalculateHashConstantLong() { + @Override + public long hash(long x) { + return secureHash(x); + } + @Override + public String toString() { + return "AES"; + } + }, randomValues); + // Quality of AES + // Dependencies: 15715..16364 + // Avalanche: 31998 + // AvalancheSum: 3199841 + // Effect: 49456..50584 + + printQuality(new CalculateHashConstantLong() { + @Override + public long hash(long x) { + x = (x ^ (x >>> 30)) * 0xbf58476d1ce4e5b9L; + x = (x ^ (x >>> 27)) * 0x94d049bb133111ebL; + return x ^ (x >>> 31); + } + @Override + public String toString() { + return "Test"; + } + }, randomValues); + // Quality of Test + // Dependencies: 14693..16502 + // Avalanche: 31996 + // AvalancheSum: 3199679 + // Effect: 49437..50537 + + Thread[] threads = new Thread[8]; + for (int i = 0; i < 8; i++) { + threads[i] = new Thread(new CalculateHashConstantLong()); + threads[i].start(); + } + for (int i = 0; i < 8; i++) { + threads[i].join(); + } + + int finalCount = 10000; + long[] randomValues = getRandomValues(finalCount, 10); + + CalculateHashConstantLong test; + int[] minMax; + test = new CalculateHashConstantLong(); + long best = 0; + int dist = Integer.MAX_VALUE; + for (long i : candidates) { + test.constant = i; + System.out.println(); + System.out.println("Constant: 0x" + Long.toHexString(i)); + minMax = test.getDependencies(test, randomValues); + System.out.println("Dependencies: " + minMax[0] + ".." + minMax[1]); + int d = minMax[1] - minMax[0]; + int av = 0; + for (int j = 0; j < 100; j++) { + av += test.getAvalanche(test, randomValues[j]); + } + System.out.println("AvalancheSum: " + av); + minMax = test.getEffect(test, finalCount * 10, 11); + System.out.println("Effect: " + minMax[0] + ".." + minMax[1]); + d += minMax[1] - minMax[0]; + if (d < dist) { + dist = d; + best = i; + } + } + System.out.println(); + System.out.println("Best constant: 0x" + Long.toHexString(best)); + test.constant = best; + long collisions = test.getCollisionCount(); + System.out.println("Collisions: " + collisions); + } + + private static void printQuality(CalculateHashConstantLong test, long[] randomValues) { + int finalCount = randomValues.length * 10; + System.out.println("Quality of " + test); + int[] minMax; + int av = 0; + minMax = test.getDependencies(test, randomValues); + System.out.println("Dependencies: " + minMax[0] + ".." + minMax[1]); + av = 0; + for (int j = 0; j < 100; j++) { + av += test.getAvalanche(test, randomValues[j]); + } + System.out.println("Avalanche: " + (av / 100)); + System.out.println("AvalancheSum: " + av); + minMax = test.getEffect(test, finalCount * 10, 11); + System.out.println("Effect: " + minMax[0] + ".." + minMax[1]); + System.out.println("ok=" + test.testCandidate()); + } + + /** + * Store a random file to be analyzed by the Diehard test. + */ + void storeRandomFile() throws Exception { + File f = new File(System.getProperty("user.home") + "/temp/rand.txt"); + FileOutputStream out = new FileOutputStream(f); + CalculateHashConstantLong test = new CalculateHashConstantLong(); + // Random r = new Random(1); + byte[] buff = new byte[8]; + // tt.constant = 0x29a907; + for (int i = 0; i < 10000000 / 8; i++) { + long y = test.hash(i); + // int y = r.nextInt(); + writeLong(buff, 0, y); + out.write(buff); + } + out.close(); + } + + private static long[] getRandomValues(int count, int seed) { + long[] values = new long[count]; + Random r = new Random(seed); + for (int i = 0; i < count; i++) { + values[i] = r.nextLong(); + } + return values; + } + + @Override + public void run() { + while (true) { + int currentHigh = high.getAndIncrement(); + // if (currentHigh > 0x2d) { + if (currentHigh > 0xffff) { + break; + } + System.out.println("testing " + Integer.toHexString(currentHigh) + "...."); + addCandidates(currentHigh); + } + } + + private void addCandidates(long currentHigh) { + for (int low = 0; low <= 0xffff; low++) { + // the lower 16 bits don't have to be a prime number + // but it seems that's a good restriction + if (!primeNumbers.get(low)) { + continue; + } + long i = (currentHigh << 48) | ((long) low << 32) | (currentHigh << 16) | low; + constant = i; + if (!testCandidate()) { + continue; + } + System.out.println(Long.toHexString(i) + + " hit " + i); + candidates.add(i); + } + } + + private boolean testCandidate() { + // after one bit changes in the input, + // on average 32 bits of the output change + int av = getAvalanche(this, 0); + if (Math.abs(av - 32000) > 1000) { + return false; + } + av = getAvalanche(this, 0xffffffffffffffffL); + if (Math.abs(av - 32000) > 1000) { + return false; + } + long es = getEffectSquare(this, randomValues); + if (es > 1100000) { + System.out.println("fail at a " + es); + return false; + } + int[] minMax = getEffect(this, 10000, 1); + if (!isWithin(4700, 5300, minMax)) { + System.out.println("fail at b " + minMax[0] + " " + minMax[1]); + return false; + } + minMax = getDependencies(this, randomValues); + if (!isWithin(14500, 17000, minMax)) { + System.out.println("fail at c " + minMax[0] + " " + minMax[1]); + return false; + } + return true; + } + + long getCollisionCount() { + // TODO need a way to check this + return 0; + } + + private static boolean isWithin(int min, int max, int[] range) { + return range[0] >= min && range[1] <= max; + } + + /** + * Calculate how much the bit changes (output bits that change if an input + * bit is changed) are independent of each other. + * + * @param h the hash object + * @param values the values to test with + * @return the minimum and maximum number of output bits that are changed in + * combination with another output bit + */ + int[] getDependencies(CalculateHashConstantLong h, long[] values) { + Arrays.fill(fromTo, 0); + for (long x : values) { + for (int shift = 0; shift < 64; shift++) { + long x1 = h.hash(x); + long x2 = h.hash(x ^ (1L << shift)); + long x3 = x1 ^ x2; + for (int s = 0; s < 64; s++) { + if ((x3 & (1L << s)) != 0) { + for (int s2 = 0; s2 < 64; s2++) { + if (s == s2) { + continue; + } + if ((x3 & (1L << s2)) != 0) { + fromTo[s * 64 + s2]++; + } + } + } + } + } + } + int a = Integer.MAX_VALUE, b = Integer.MIN_VALUE; + for (int x : fromTo) { + if (x == 0) { + continue; + } + if (x < a) { + a = x; + } + if (x > b) { + b = x; + } + } + return new int[] {a, b}; + } + + /** + * Calculate the number of bits that change if a single bit is changed + * multiplied by 1000 (expected: 16000 +/- 5%). + * + * @param h the hash object + * @param value the base value + * @return the number of bit changes multiplied by 1000 + */ + int getAvalanche(CalculateHashConstantLong h, long value) { + int changedBitsSum = 0; + for (int i = 0; i < 64; i++) { + long x = value ^ (1L << i); + for (int shift = 0; shift < 64; shift++) { + long x1 = h.hash(x); + long x2 = h.hash(x ^ (1L << shift)); + long x3 = x1 ^ x2; + changedBitsSum += Long.bitCount(x3); + } + } + return changedBitsSum * 1000 / 64 / 64; + } + + /** + * Calculate the sum of the square of the distance to the expected + * probability that an output bit changes if an input bit is changed. The + * lower the value, the better. + * + * @param h the hash object + * @param values the values to test with + * @return sum(distance^2) + */ + long getEffectSquare(CalculateHashConstantLong h, long[] values) { + Arrays.fill(fromTo, 0); + int total = 0; + for (long x : values) { + for (int shift = 0; shift < 64; shift++) { + long x1 = h.hash(x); + long x2 = h.hash(x ^ (1L << shift)); + long x3 = x1 ^ x2; + for (int s = 0; s < 64; s++) { + if ((x3 & (1L << s)) != 0) { + fromTo[shift * 64 + s]++; + total++; + } + } + } + } + long sqDist = 0; + int expected = total / 64 / 64; + for (int x : fromTo) { + int dist = Math.abs(x - expected); + sqDist += dist * dist; + } + return sqDist; + } + + /** + * Calculate if the bit changes (that an output bit changes if an input + * bit is changed) are within a certain range. + * + * @param h the hash object + * @param count the number of values to test + * @param seed the random seed + * @return the minimum and maximum value of all input-to-output bit changes + */ + int[] getEffect(CalculateHashConstantLong h, int count, int seed) { + Random r = new Random(); + r.setSeed(seed); + Arrays.fill(fromTo, 0); + for (int i = 0; i < count; i++) { + long x = r.nextLong(); + for (int shift = 0; shift < 64; shift++) { + long x1 = h.hash(x); + long x2 = h.hash(x ^ (1L << shift)); + long x3 = x1 ^ x2; + for (int s = 0; s < 64; s++) { + if ((x3 & (1L << s)) != 0) { + fromTo[shift * 64 + s]++; + } + } + } + } + int a = Integer.MAX_VALUE, b = Integer.MIN_VALUE; + for (int x : fromTo) { + if (x < a) { + a = x; + } + if (x > b) { + b = x; + } + } + return new int[] {a, b}; + } + + /** + * The hash method. + * + * @param x the input + * @return the output + */ + long hash(long x) { + x = ((x >>> 32) ^ x) * constant; + x = ((x >>> 32) ^ x) * constant; + x = (x >>> 32) ^ x; + return x; + } + + /** + * Calculate a hash using AES. + * + * @param x the input + * @return the output + */ + long secureHash(long x) { + writeLong(data, 0, x); + aes.encrypt(data, 0, 16); + return readLong(data, 0); + } + + private static void writeLong(byte[] buff, int pos, long x) { + writeInt(buff, pos, (int) (x >>> 32)); + writeInt(buff, pos + 4, (int) x); + } + + private static void writeInt(byte[] buff, int pos, int x) { + buff[pos++] = (byte) (x >> 24); + buff[pos++] = (byte) (x >> 16); + buff[pos++] = (byte) (x >> 8); + buff[pos++] = (byte) x; + } + + private static long readLong(byte[] buff, int pos) { + return (((long) readInt(buff, pos)) << 32) | (readInt(buff, pos + 4) & 0xffffffffL); + } + + private static int readInt(byte[] buff, int pos) { + return (buff[pos++] << 24) + ((buff[pos++] & 0xff) << 16) + + ((buff[pos++] & 0xff) << 8) + (buff[pos] & 0xff); + } + +} diff --git a/h2/src/test/org/h2/test/store/FreeSpaceList.java b/h2/src/test/org/h2/test/store/FreeSpaceList.java new file mode 100644 index 0000000..b6cb3e9 --- /dev/null +++ b/h2/src/test/org/h2/test/store/FreeSpaceList.java @@ -0,0 +1,217 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.util.ArrayList; +import java.util.List; + +import org.h2.mvstore.DataUtils; +import org.h2.util.MathUtils; + +/** + * A list that maintains ranges of free space (in blocks). + */ +public class FreeSpaceList { + + /** + * The first usable block. + */ + private final int firstFreeBlock; + + /** + * The block size in bytes. + */ + private final int blockSize; + + private List freeSpaceList = new ArrayList<>(); + + public FreeSpaceList(int firstFreeBlock, int blockSize) { + this.firstFreeBlock = firstFreeBlock; + if (Integer.bitCount(blockSize) != 1) { + throw DataUtils.newIllegalArgumentException("Block size is not a power of 2"); + } + this.blockSize = blockSize; + clear(); + } + + /** + * Reset the list. + */ + public synchronized void clear() { + freeSpaceList.clear(); + freeSpaceList.add(new BlockRange(firstFreeBlock, + Integer.MAX_VALUE - firstFreeBlock)); + } + + /** + * Allocate a number of blocks and mark them as used. + * + * @param length the number of bytes to allocate + * @return the start position in bytes + */ + public synchronized long allocate(int length) { + int required = getBlockCount(length); + for (BlockRange pr : freeSpaceList) { + if (pr.length >= required) { + int result = pr.start; + this.markUsed(pr.start * blockSize, length); + return result * blockSize; + } + } + throw DataUtils.newMVStoreException( + DataUtils.ERROR_INTERNAL, + "Could not find a free page to allocate"); + } + + /** + * Mark the space as in use. + * + * @param pos the position in bytes + * @param length the number of bytes + */ + public synchronized void markUsed(long pos, int length) { + int start = (int) (pos / blockSize); + int required = getBlockCount(length); + BlockRange found = null; + int i = 0; + for (BlockRange pr : freeSpaceList) { + if (start >= pr.start && start < (pr.start + pr.length)) { + found = pr; + break; + } + i++; + } + if (found == null) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_INTERNAL, + "Cannot find spot to mark as used in free list"); + } + if (start + required > found.start + found.length) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_INTERNAL, + "Runs over edge of free space"); + } + if (found.start == start) { + // if the used space is at the beginning of a free-space-range + found.start += required; + found.length -= required; + if (found.length == 0) { + // if the free-space-range is now empty, remove it + freeSpaceList.remove(i); + } + } else if (found.start + found.length == start + required) { + // if the used space is at the end of a free-space-range + found.length -= required; + } else { + // it's in the middle, so split the existing entry + int length1 = start - found.start; + int start2 = start + required; + int length2 = found.start + found.length - start - required; + + found.length = length1; + BlockRange newRange = new BlockRange(start2, length2); + freeSpaceList.add(i + 1, newRange); + } + } + + /** + * Mark the space as free. + * + * @param pos the position in bytes + * @param length the number of bytes + */ + public synchronized void free(long pos, int length) { + int start = (int) (pos / blockSize); + int required = getBlockCount(length); + BlockRange found = null; + int i = 0; + for (BlockRange pr : freeSpaceList) { + if (pr.start > start) { + found = pr; + break; + } + i++; + } + if (found == null) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_INTERNAL, + "Cannot find spot to mark as unused in free list"); + } + if (start + required == found.start) { + // if the used space is adjacent to the beginning of a + // free-space-range + found.start = start; + found.length += required; + // compact: merge the previous entry into this one if + // they are now adjacent + if (i > 0) { + BlockRange previous = freeSpaceList.get(i - 1); + if (previous.start + previous.length == found.start) { + previous.length += found.length; + freeSpaceList.remove(i); + } + } + return; + } + if (i > 0) { + // if the used space is adjacent to the end of a free-space-range + BlockRange previous = freeSpaceList.get(i - 1); + if (previous.start + previous.length == start) { + previous.length += required; + return; + } + } + + // it is between 2 entries, so add a new one + BlockRange newRange = new BlockRange(start, required); + freeSpaceList.add(i, newRange); + } + + private int getBlockCount(int length) { + if (length <= 0) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_INTERNAL, "Free space invalid length"); + } + return MathUtils.roundUpInt(length, blockSize) / blockSize; + } + + @Override + public String toString() { + return freeSpaceList.toString(); + } + + /** + * A range of free blocks. + */ + private static final class BlockRange { + + /** + * The starting point, in blocks. + */ + int start; + + /** + * The length, in blocks. + */ + int length; + + public BlockRange(int start, int length) { + this.start = start; + this.length = length; + } + + @Override + public String toString() { + if (start + length == Integer.MAX_VALUE) { + return Integer.toHexString(start) + "-"; + } + return Integer.toHexString(start) + "-" + + Integer.toHexString(start + length - 1); + } + + } + +} diff --git a/h2/src/test/org/h2/test/store/FreeSpaceTree.java b/h2/src/test/org/h2/test/store/FreeSpaceTree.java new file mode 100644 index 0000000..07931a9 --- /dev/null +++ b/h2/src/test/org/h2/test/store/FreeSpaceTree.java @@ -0,0 +1,206 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.util.TreeSet; + +import org.h2.mvstore.DataUtils; +import org.h2.util.MathUtils; + +/** + * A list that maintains ranges of free space (in blocks) in a file. + */ +public class FreeSpaceTree { + + /** + * The first usable block. + */ + private final int firstFreeBlock; + + /** + * The block size in bytes. + */ + private final int blockSize; + + /** + * The list of free space. + */ + private TreeSet freeSpace = new TreeSet<>(); + + public FreeSpaceTree(int firstFreeBlock, int blockSize) { + this.firstFreeBlock = firstFreeBlock; + if (Integer.bitCount(blockSize) != 1) { + throw DataUtils.newIllegalArgumentException("Block size is not a power of 2"); + } + this.blockSize = blockSize; + clear(); + } + + /** + * Reset the list. + */ + public synchronized void clear() { + freeSpace.clear(); + freeSpace.add(new BlockRange(firstFreeBlock, + Integer.MAX_VALUE - firstFreeBlock)); + } + + /** + * Allocate a number of blocks and mark them as used. + * + * @param length the number of bytes to allocate + * @return the start position in bytes + */ + public synchronized long allocate(int length) { + int blocks = getBlockCount(length); + BlockRange x = null; + for (BlockRange b : freeSpace) { + if (b.blocks >= blocks) { + x = b; + break; + } + } + long pos = getPos(x.start); + if (x.blocks == blocks) { + freeSpace.remove(x); + } else { + x.start += blocks; + x.blocks -= blocks; + } + return pos; + } + + /** + * Mark the space as in use. + * + * @param pos the position in bytes + * @param length the number of bytes + */ + public synchronized void markUsed(long pos, int length) { + int start = getBlock(pos); + int blocks = getBlockCount(length); + BlockRange x = new BlockRange(start, blocks); + BlockRange prev = freeSpace.floor(x); + if (prev == null) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_INTERNAL, "Free space already marked"); + } + if (prev.start == start) { + if (prev.blocks == blocks) { + // match + freeSpace.remove(prev); + } else { + // cut the front + prev.start += blocks; + prev.blocks -= blocks; + } + } else if (prev.start + prev.blocks == start + blocks) { + // cut the end + prev.blocks -= blocks; + } else { + // insert an entry + x.start = start + blocks; + x.blocks = prev.start + prev.blocks - x.start; + freeSpace.add(x); + prev.blocks = start - prev.start; + } + } + + /** + * Mark the space as free. + * + * @param pos the position in bytes + * @param length the number of bytes + */ + public synchronized void free(long pos, int length) { + int start = getBlock(pos); + int blocks = getBlockCount(length); + BlockRange x = new BlockRange(start, blocks); + BlockRange next = freeSpace.ceiling(x); + if (next == null) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_INTERNAL, "Free space sentinel is missing"); + } + BlockRange prev = freeSpace.lower(x); + if (prev != null) { + if (prev.start + prev.blocks == start) { + // extend the previous entry + prev.blocks += blocks; + if (prev.start + prev.blocks == next.start) { + // merge with the next entry + prev.blocks += next.blocks; + freeSpace.remove(next); + } + return; + } + } + if (start + blocks == next.start) { + // extend the next entry + next.start -= blocks; + next.blocks += blocks; + return; + } + freeSpace.add(x); + } + + private long getPos(int block) { + return (long) block * (long) blockSize; + } + + private int getBlock(long pos) { + return (int) (pos / blockSize); + } + + private int getBlockCount(int length) { + if (length <= 0) { + throw DataUtils.newMVStoreException( + DataUtils.ERROR_INTERNAL, "Free space invalid length"); + } + return MathUtils.roundUpInt(length, blockSize) / blockSize; + } + + @Override + public String toString() { + return freeSpace.toString(); + } + + /** + * A range of free blocks. + */ + private static final class BlockRange implements Comparable { + + /** + * The starting point (the block number). + */ + public int start; + + /** + * The length, in blocks. + */ + public int blocks; + + public BlockRange(int start, int blocks) { + this.start = start; + this.blocks = blocks; + } + + @Override + public int compareTo(BlockRange o) { + return Integer.compare(start, o.start); + } + + @Override + public String toString() { + if (blocks + start == Integer.MAX_VALUE) { + return Integer.toHexString(start) + "-"; + } + return Integer.toHexString(start) + "-" + + Integer.toHexString(start + blocks - 1); + } + + } + +} diff --git a/h2/src/test/org/h2/test/store/RowDataType.java b/h2/src/test/org/h2/test/store/RowDataType.java new file mode 100644 index 0000000..ac4611f --- /dev/null +++ b/h2/src/test/org/h2/test/store/RowDataType.java @@ -0,0 +1,81 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.nio.ByteBuffer; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.WriteBuffer; +import org.h2.mvstore.type.BasicDataType; +import org.h2.mvstore.type.DataType; + +/** + * A row type. + */ +public class RowDataType extends BasicDataType { + + private final DataType[] types; + + @SuppressWarnings("unchecked") + RowDataType(DataType[] types) { + this.types = types; + } + + @Override + public Object[][] createStorage(int size) { + return new Object[size][]; + } + + @Override + public int compare(Object[] ax, Object[] bx) { + if (ax == bx) { + return 0; + } + int al = ax.length; + int bl = bx.length; + int len = Math.min(al, bl); + for (int i = 0; i < len; i++) { + int comp = types[i].compare(ax[i], bx[i]); + if (comp != 0) { + return comp; + } + } + if (len < al) { + return -1; + } else if (len < bl) { + return 1; + } + return 0; + } + + @Override + public int getMemory(Object[] x) { + int len = x.length; + int memory = 0; + for (int i = 0; i < len; i++) { + memory += types[i].getMemory(x[i]); + } + return memory; + } + + @Override + public Object[] read(ByteBuffer buff) { + int len = DataUtils.readVarInt(buff); + Object[] x = new Object[len]; + for (int i = 0; i < len; i++) { + x[i] = types[i].read(buff); + } + return x; + } + + @Override + public void write(WriteBuffer buff, Object[] x) { + int len = x.length; + buff.putVarInt(len); + for (int i = 0; i < len; i++) { + types[i].write(buff, x[i]); + } + } +} diff --git a/h2/src/test/org/h2/test/store/SequenceMap.java b/h2/src/test/org/h2/test/store/SequenceMap.java new file mode 100644 index 0000000..aa94a5f --- /dev/null +++ b/h2/src/test/org/h2/test/store/SequenceMap.java @@ -0,0 +1,74 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.util.AbstractSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.type.DataType; + +/** + * A custom map returning the keys and values 1 .. 10. + */ +public class SequenceMap extends MVMap { + + /** + * The minimum value. + */ + int min = 1; + + /** + * The maximum value. + */ + int max = 10; + + public SequenceMap(Map config, DataType keyType, DataType valueType) { + super(config, keyType, valueType); + } + + @Override + public Set keySet() { + return new AbstractSet() { + + @Override + public Iterator iterator() { + return new Iterator() { + + long x = min; + + @Override + public boolean hasNext() { + return x <= max; + } + + @Override + public Long next() { + return Long.valueOf(x++); + } + + }; + } + + @Override + public int size() { + return max - min + 1; + } + }; + } + + /** + * A builder for this class. + */ + public static class Builder extends MVMap.Builder { + @Override + public SequenceMap create(Map config) { + return new SequenceMap(config, getKeyType(), getValueType()); + } + + } +} diff --git a/h2/src/test/org/h2/test/store/TestBenchmark.java b/h2/src/test/org/h2/test/store/TestBenchmark.java new file mode 100644 index 0000000..1f72047 --- /dev/null +++ b/h2/src/test/org/h2/test/store/TestBenchmark.java @@ -0,0 +1,273 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.Statement; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVStore; +import org.h2.store.FileLister; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.Task; + +/** + * Tests performance and helps analyze bottlenecks. + */ +public class TestBenchmark extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testConcurrency(); + + // TODO this test is currently disabled + + test(true); + test(false); + test(true); + test(false); + test(true); + test(false); + } + + private void testConcurrency() throws Exception { + // String fileName = getBaseDir() + "/" + getTestName(); + String fileName = "nioMemFS:/" + getTestName(); + FileUtils.delete(fileName); + MVStore store = new MVStore.Builder().cacheSize(16). + fileName(fileName).open(); + MVMap map = store.openMap("test"); + byte[] data = new byte[1024]; + int count = 1000000; + for (int i = 0; i < count; i++) { + map.put(i, data); + } + store.close(); + for (int concurrency = 1024; concurrency > 0; concurrency /= 2) { + testConcurrency(fileName, concurrency, count); + testConcurrency(fileName, concurrency, count); + testConcurrency(fileName, concurrency, count); + } + FileUtils.delete(fileName); + } + + private void testConcurrency(String fileName, + int concurrency, final int count) throws Exception { + Thread.sleep(1000); + final MVStore store = new MVStore.Builder().cacheSize(256). + cacheConcurrency(concurrency). + fileName(fileName).open(); + int threadCount = 128; + final CountDownLatch wait = new CountDownLatch(1); + final AtomicInteger counter = new AtomicInteger(); + final AtomicBoolean stopped = new AtomicBoolean(); + Task[] tasks = new Task[threadCount]; + // Profiler prof = new Profiler().startCollecting(); + for (int i = 0; i < threadCount; i++) { + final int x = i; + Task t = new Task() { + @Override + public void call() throws Exception { + MVMap map = store.openMap("test"); + Random random = new Random(x); + wait.await(); + while (!stopped.get()) { + int key = random.nextInt(count); + byte[] data = map.get(key); + if (data.length > 1) { + counter.incrementAndGet(); + } + } + } + }; + t.execute("t" + i); + tasks[i] = t; + } + wait.countDown(); + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + stopped.set(true); + for (Task t : tasks) { + t.get(); + } + // System.out.println(prof.getTop(5)); + String msg = "concurrency " + concurrency + + " threads " + threadCount + " requests: " + counter; + System.out.println(msg); + trace(msg); + store.close(); + } + + private void test(boolean mvStore) throws Exception { + // testInsertSelect(mvStore); + // testBinary(mvStore); + testCreateIndex(mvStore); + } + + private void testCreateIndex(boolean mvStore) throws Exception { + FileUtils.deleteRecursive(getBaseDir(), true); + Connection conn; + Statement stat; + String url = "mvstore"; + if (mvStore) { + // ;COMPRESS=TRUE"; + url += ";MV_STORE=TRUE"; + } + + url = getURL(url, true); + conn = getConnection(url); + stat = conn.createStatement(); + stat.execute("create table test(id bigint primary key, data bigint)"); + conn.setAutoCommit(false); + PreparedStatement prep = conn + .prepareStatement("insert into test values(?, ?)"); + +// int rowCount = 10000000; + int rowCount = 1000000; + + Random r = new Random(1); + + for (int i = 0; i < rowCount; i++) { + prep.setInt(1, i); + // prep.setInt(2, i); + prep.setInt(2, r.nextInt()); + prep.execute(); + if (i % 10000 == 0) { + conn.commit(); + } + } + + long start = System.nanoTime(); + // Profiler prof = new Profiler().startCollecting(); + stat.execute("create index on test(data)"); + // System.out.println(prof.getTop(5)); + + System.out.println(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start) + " " + + (mvStore ? "mvstore" : "default")); + conn.createStatement().execute("shutdown compact"); + conn.close(); + for (String f : FileLister.getDatabaseFiles(getBaseDir(), "mvstore", true)) { + System.out.println(" " + f + " " + FileUtils.size(f)); + } + } + + private void testBinary(boolean mvStore) throws Exception { + FileUtils.deleteRecursive(getBaseDir(), true); + Connection conn; + Statement stat; + String url = "mvstore"; + if (mvStore) { + url += ";MV_STORE=TRUE"; + } + + url = getURL(url, true); + conn = getConnection(url); + stat = conn.createStatement(); + stat.execute("create table test(id bigint primary key, data blob)"); + conn.setAutoCommit(false); + PreparedStatement prep = conn + .prepareStatement("insert into test values(?, ?)"); + byte[] data = new byte[1024 * 1024]; + + int rowCount = 100; + int readCount = 20 * rowCount; + + long start = System.nanoTime(); + + for (int i = 0; i < rowCount; i++) { + prep.setInt(1, i); + randomize(data, i); + prep.setBytes(2, data); + prep.execute(); + if (i % 100 == 0) { + conn.commit(); + } + } + + prep = conn.prepareStatement("select * from test where id = ?"); + for (int i = 0; i < readCount; i++) { + prep.setInt(1, i % rowCount); + prep.executeQuery(); + } + + System.out.println(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start) + " " + + (mvStore ? "mvstore" : "default")); + conn.close(); + } + + private static void randomize(byte[] data, int i) { + Random r = new Random(i); + r.nextBytes(data); + } + + private void testInsertSelect(boolean mvStore) throws Exception { + + FileUtils.deleteRecursive(getBaseDir(), true); + Connection conn; + Statement stat; + String url = "mvstore"; + if (mvStore) { + url += ";MV_STORE=TRUE;LOG=0;COMPRESS=TRUE"; + } + + url = getURL(url, true); + conn = getConnection(url); + stat = conn.createStatement(); + stat.execute("create table test(id bigint primary key, name varchar)"); + conn.setAutoCommit(false); + PreparedStatement prep = conn + .prepareStatement("insert into test values(?, ?)"); + String data = "Hello World"; + + int rowCount = 100000; + int readCount = 20 * rowCount; + + for (int i = 0; i < rowCount; i++) { + prep.setInt(1, i); + prep.setString(2, data); + prep.execute(); + if (i % 100 == 0) { + conn.commit(); + } + } + long start = System.nanoTime(); + + prep = conn.prepareStatement("select * from test where id = ?"); + for (int i = 0; i < readCount; i++) { + prep.setInt(1, i % rowCount); + prep.executeQuery(); + } + + System.out.println(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start) + " " + + (mvStore ? "mvstore" : "default")); + conn.createStatement().execute("shutdown compact"); + conn.close(); + for (String f : FileLister.getDatabaseFiles(getBaseDir(), "mvstore", true)) { + System.out.println(" " + f + " " + FileUtils.size(f)); + } + + } + +} diff --git a/h2/src/test/org/h2/test/store/TestCacheConcurrentLIRS.java b/h2/src/test/org/h2/test/store/TestCacheConcurrentLIRS.java new file mode 100644 index 0000000..4c4f409 --- /dev/null +++ b/h2/src/test/org/h2/test/store/TestCacheConcurrentLIRS.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.test.store; + +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import org.h2.mvstore.cache.CacheLongKeyLIRS; +import org.h2.test.TestBase; +import org.h2.util.Task; + +/** + * Tests the cache algorithm. + */ +public class TestCacheConcurrentLIRS extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testConcurrent(); + } + + private void testConcurrent() { + CacheLongKeyLIRS.Config cc = new CacheLongKeyLIRS.Config(); + cc.maxMemory = 100; + final CacheLongKeyLIRS test = new CacheLongKeyLIRS<>(cc); + int threadCount = 8; + final CountDownLatch wait = new CountDownLatch(1); + final AtomicBoolean stopped = new AtomicBoolean(); + Task[] tasks = new Task[threadCount]; + final int[] getCounts = new int[threadCount]; + final int offset = 1000000; + for (int i = 0; i < 100; i++) { + test.put(offset + i, i); + } + final int[] keys = new int[1000]; + Random random = new Random(1); + for (int i = 0; i < keys.length; i++) { + int key; + do { + key = (int) Math.abs(random.nextGaussian() * 50); + } while (key > 100); + keys[i] = key; + } + for (int i = 0; i < threadCount; i++) { + final int x = i; + Task t = new Task() { + @Override + public void call() throws Exception { + Random random = new Random(x); + wait.await(); + int i = 0; + for (; !stopped.get(); i++) { + int key = keys[random.nextInt(keys.length)]; + test.get(offset + key); + if ((i & 127) == 0) { + test.put(offset + random.nextInt(100), random.nextInt()); + } + } + getCounts[x] = i; + } + }; + t.execute("t" + i); + tasks[i] = t; + } + wait.countDown(); + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + stopped.set(true); + for (Task t : tasks) { + t.get(); + } + int totalCount = 0; + for (int x : getCounts) { + totalCount += x; + } + trace("requests: " + totalCount); + } + +} diff --git a/h2/src/test/org/h2/test/store/TestCacheLIRS.java b/h2/src/test/org/h2/test/store/TestCacheLIRS.java new file mode 100644 index 0000000..95b9c16 --- /dev/null +++ b/h2/src/test/org/h2/test/store/TestCacheLIRS.java @@ -0,0 +1,559 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map.Entry; +import java.util.Random; +import org.h2.dev.cache.CacheLIRS; +import org.h2.test.TestBase; + +/** + * Tests the cache algorithm. + */ +public class TestCacheLIRS extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testCache(); + } + + private void testCache() { + testRandomSmallCache(); + testEdgeCases(); + testSize(); + testClear(); + testGetPutPeekRemove(); + testPruneStack(); + testLimitHot(); + testLimitNonResident(); + testBadHashMethod(); + testLimitMemory(); + testScanResistance(); + testRandomOperations(); + } + + private static void testRandomSmallCache() { + Random r = new Random(1); + for (int i = 0; i < 10000; i++) { + int j = 0; + StringBuilder buff = new StringBuilder(); + CacheLIRS test = createCache(1 + r.nextInt(10)); + for (; j < 30; j++) { + int key = r.nextInt(5); + switch (r.nextInt(3)) { + case 0: + int memory = r.nextInt(5) + 1; + buff.append("add ").append(key).append(' '). + append(memory).append('\n'); + test.put(key, j, memory); + break; + case 1: + buff.append("remove ").append(key).append('\n'); + test.remove(key); + break; + case 2: + buff.append("get ").append(key).append('\n'); + test.get(key); + } + } + } + } + + private void testEdgeCases() { + CacheLIRS test = createCache(1); + test.put(1, 10, 100); + assertEquals(0, test.size()); + assertThrows(NullPointerException.class, () -> test.put(null, 10, 100)); + assertThrows(NullPointerException.class, () -> test.put(1, null, 100)); + assertThrows(IllegalArgumentException.class, () -> test.setMaxMemory(0)); + } + + private void testSize() { + verifyMapSize(7, 16); + verifyMapSize(13, 32); + verifyMapSize(25, 64); + verifyMapSize(49, 128); + verifyMapSize(97, 256); + verifyMapSize(193, 512); + verifyMapSize(385, 1024); + verifyMapSize(769, 2048); + + CacheLIRS test; + + test = createCache(1000); + for (int j = 0; j < 2000; j++) { + test.put(j, j); + } + // for a cache of size 1000, + // there are 62 cold entries (about 6.25%). + assertEquals(62, test.size() - test.sizeHot()); + // at most as many non-resident elements + // as there are entries in the stack + assertEquals(968, test.sizeNonResident()); + } + + private void verifyMapSize(int elements, int expectedMapSize) { + CacheLIRS test; + test = createCache(elements - 1); + for (int i = 0; i < elements - 1; i++) { + test.put(i, i * 10); + } + assertTrue(test.sizeMapArray() + "<" + expectedMapSize, + test.sizeMapArray() < expectedMapSize); + test = createCache(elements); + for (int i = 0; i < elements + 1; i++) { + test.put(i, i * 10); + } + assertEquals(expectedMapSize, test.sizeMapArray()); + test = createCache(elements * 2); + for (int i = 0; i < elements * 2; i++) { + test.put(i, i * 10); + } + assertTrue(test.sizeMapArray() + ">" + expectedMapSize, + test.sizeMapArray() > expectedMapSize); + } + + private void testGetPutPeekRemove() { + CacheLIRS test = createCache(4); + test.put(1, 10); + test.put(2, 20); + test.put(3, 30); + assertNull(test.peek(4)); + assertNull(test.get(4)); + test.put(4, 40); + verify(test, "mem: 4 stack: 4 3 2 1 cold: non-resident:"); + // move middle to front + assertEquals(30, test.get(3).intValue()); + assertEquals(20, test.get(2).intValue()); + assertEquals(20, test.peek(2).intValue()); + // already on (an optimization) + assertEquals(20, test.get(2).intValue()); + assertEquals(10, test.peek(1).intValue()); + assertEquals(10, test.get(1).intValue()); + verify(test, "mem: 4 stack: 1 2 3 4 cold: non-resident:"); + test.put(3, 30); + verify(test, "mem: 4 stack: 3 1 2 4 cold: non-resident:"); + // 5 is cold; will make 4 non-resident + test.put(5, 50); + verify(test, "mem: 4 stack: 5 3 1 2 cold: 5 non-resident: 4"); + assertEquals(1, test.getMemory(1)); + assertEquals(1, test.getMemory(5)); + assertEquals(0, test.getMemory(4)); + assertEquals(0, test.getMemory(100)); + assertNull(test.peek(4)); + assertNull(test.get(4)); + assertEquals(10, test.get(1).intValue()); + assertEquals(20, test.get(2).intValue()); + assertEquals(30, test.get(3).intValue()); + verify(test, "mem: 4 stack: 3 2 1 cold: 5 non-resident: 4"); + assertEquals(50, test.get(5).intValue()); + verify(test, "mem: 4 stack: 5 3 2 1 cold: 5 non-resident: 4"); + assertEquals(50, test.get(5).intValue()); + verify(test, "mem: 4 stack: 5 3 2 cold: 1 non-resident: 4"); + + // remove + assertEquals(50, test.remove(5).intValue()); + assertNull(test.remove(5)); + verify(test, "mem: 3 stack: 3 2 1 cold: non-resident: 4"); + assertNull(test.remove(4)); + verify(test, "mem: 3 stack: 3 2 1 cold: non-resident:"); + assertNull(test.remove(4)); + verify(test, "mem: 3 stack: 3 2 1 cold: non-resident:"); + test.put(4, 40); + test.put(5, 50); + verify(test, "mem: 4 stack: 5 4 3 2 cold: 5 non-resident: 1"); + test.get(5); + test.get(2); + test.get(3); + test.get(4); + verify(test, "mem: 4 stack: 4 3 2 5 cold: 2 non-resident: 1"); + assertEquals(50, test.remove(5).intValue()); + verify(test, "mem: 3 stack: 4 3 2 cold: non-resident: 1"); + assertEquals(20, test.remove(2).intValue()); + assertFalse(test.containsKey(1)); + assertNull(test.remove(1)); + assertFalse(test.containsKey(1)); + verify(test, "mem: 2 stack: 4 3 cold: non-resident:"); + test.put(1, 10); + test.put(2, 20); + verify(test, "mem: 4 stack: 2 1 4 3 cold: non-resident:"); + test.get(1); + test.get(3); + test.get(4); + verify(test, "mem: 4 stack: 4 3 1 2 cold: non-resident:"); + assertEquals(10, test.remove(1).intValue()); + verify(test, "mem: 3 stack: 4 3 2 cold: non-resident:"); + test.remove(2); + test.remove(3); + test.remove(4); + + // test clear + test.clear(); + verify(test, "mem: 0 stack: cold: non-resident:"); + + // strange situation where there is only a non-resident entry + test.put(1, 10); + test.put(2, 20); + test.put(3, 30); + test.put(4, 40); + test.put(5, 50); + assertTrue(test.containsValue(50)); + verify(test, "mem: 4 stack: 5 4 3 2 cold: 5 non-resident: 1"); + test.put(1, 10); + verify(test, "mem: 4 stack: 1 5 4 3 2 cold: 1 non-resident: 5"); + assertFalse(test.containsValue(50)); + test.remove(2); + test.remove(3); + test.remove(4); + verify(test, "mem: 1 stack: 1 cold: non-resident: 5"); + assertTrue(test.containsKey(1)); + test.remove(1); + assertFalse(test.containsKey(1)); + verify(test, "mem: 0 stack: cold: non-resident: 5"); + assertFalse(test.containsKey(5)); + assertTrue(test.isEmpty()); + + // verify that converting a hot to cold entry will prune the stack + test.clear(); + test.put(1, 10); + test.put(2, 20); + test.put(3, 30); + test.put(4, 40); + test.put(5, 50); + test.get(4); + test.get(3); + verify(test, "mem: 4 stack: 3 4 5 2 cold: 5 non-resident: 1"); + test.put(6, 60); + verify(test, "mem: 4 stack: 6 3 4 5 2 cold: 6 non-resident: 5 1"); + // this will prune the stack (remove entry 5 as entry 2 becomes cold) + test.get(6); + verify(test, "mem: 4 stack: 6 3 4 cold: 2 non-resident: 5 1"); + } + + private void testPruneStack() { + CacheLIRS test = createCache(5); + for (int i = 0; i < 7; i++) { + test.put(i, i * 10); + } + verify(test, "mem: 5 stack: 6 5 4 3 2 1 cold: 6 non-resident: 5 0"); + test.get(4); + test.get(3); + test.get(2); + verify(test, "mem: 5 stack: 2 3 4 6 5 1 cold: 6 non-resident: 5 0"); + // this call needs to prune the stack + test.remove(1); + verify(test, "mem: 4 stack: 2 3 4 6 cold: non-resident: 5 0"); + test.put(0, 0); + test.put(1, 10); + // the stack was not pruned, the following will fail + verify(test, "mem: 5 stack: 1 0 2 3 4 cold: 1 non-resident: 6 5"); + } + + private void testClear() { + CacheLIRS test = createCache(40); + for (int i = 0; i < 5; i++) { + test.put(i, 10 * i, 9); + } + verify(test, "mem: 36 stack: 4 3 2 1 cold: 4 non-resident: 0"); + for (Entry e : test.entrySet()) { + assertTrue(e.getKey() >= 1 && e.getKey() <= 4); + assertTrue(e.getValue() >= 10 && e.getValue() <= 40); + } + for (int x : test.values()) { + assertTrue(x >= 10 && x <= 40); + } + for (int x : test.keySet()) { + assertTrue(x >= 1 && x <= 4); + } + assertEquals(40, test.getMaxMemory()); + assertEquals(36, test.getUsedMemory()); + assertEquals(4, test.size()); + assertEquals(3, test.sizeHot()); + assertEquals(1, test.sizeNonResident()); + assertFalse(test.isEmpty()); + + // changing the limit is not supposed to modify the map + test.setMaxMemory(10); + assertEquals(10, test.getMaxMemory()); + test.setMaxMemory(40); + verify(test, "mem: 36 stack: 4 3 2 1 cold: 4 non-resident: 0"); + + test.putAll(test); + verify(test, "mem: 4 stack: 4 3 2 1 cold: non-resident: 0"); + + test.clear(); + verify(test, "mem: 0 stack: cold: non-resident:"); + + assertEquals(40, test.getMaxMemory()); + assertEquals(0, test.getUsedMemory()); + assertEquals(0, test.size()); + assertEquals(0, test.sizeHot()); + assertEquals(0, test.sizeNonResident()); + assertTrue(test.isEmpty()); + } + + private void testLimitHot() { + CacheLIRS test = createCache(100); + for (int i = 0; i < 300; i++) { + test.put(i, 10 * i); + } + assertEquals(100, test.size()); + assertEquals(99, test.sizeNonResident()); + assertEquals(93, test.sizeHot()); + } + + private void testLimitNonResident() { + CacheLIRS test = createCache(4); + for (int i = 0; i < 20; i++) { + test.put(i, 10 * i); + } + verify(test, "mem: 4 stack: 19 18 17 16 3 2 1 " + + "cold: 19 non-resident: 18 17 16"); + } + + private void testBadHashMethod() { + // ensure an 2^n cache size + final int size = 4; + + /** + * A class with a bad hashCode implementation. + */ + class BadHash { + int x; + + BadHash(int x) { + this.x = x; + } + + @Override + public int hashCode() { + return (x & 1) * size * 2; + } + + @Override + public boolean equals(Object o) { + return ((BadHash) o).x == x; + } + + @Override + public String toString() { + return "" + x; + } + + } + + CacheLIRS test = createCache(size * 2); + for (int i = 0; i < size; i++) { + test.put(new BadHash(i), i); + } + for (int i = 0; i < size; i++) { + if (i % 3 == 0) { + assertEquals(i, test.remove(new BadHash(i)).intValue()); + assertNull(test.remove(new BadHash(i))); + } + } + for (int i = 0; i < size; i++) { + if (i % 3 == 0) { + assertNull(test.get(new BadHash(i))); + } else { + assertEquals(i, test.get(new BadHash(i)).intValue()); + } + } + for (int i = 0; i < size; i++) { + test.put(new BadHash(i), i); + } + for (int i = 0; i < size; i++) { + if (i % 3 == 0) { + assertEquals(i, test.remove(new BadHash(i)).intValue()); + assertNull(test.remove(new BadHash(i))); + } + } + for (int i = 0; i < size; i++) { + if (i % 3 == 0) { + assertNull(test.get(new BadHash(i))); + } else { + assertEquals(i, test.get(new BadHash(i)).intValue()); + } + } + } + + private void testLimitMemory() { + CacheLIRS test = createCache(4); + for (int i = 0; i < 5; i++) { + test.put(i, 10 * i, 1); + } + verify(test, "mem: 4 stack: 4 3 2 1 cold: 4 non-resident: 0"); + assertTrue("" + test.getUsedMemory(), test.getUsedMemory() <= 4); + test.put(6, 60, 3); + verify(test, "mem: 4 stack: 6 3 cold: 6 non-resident:"); + assertTrue("" + test.getUsedMemory(), test.getUsedMemory() <= 4); + test.put(7, 70, 3); + verify(test, "mem: 4 stack: 7 6 3 cold: 7 non-resident: 6"); + assertTrue("" + test.getUsedMemory(), test.getUsedMemory() <= 4); + test.put(8, 80, 4); + verify(test, "mem: 4 stack: 8 cold: non-resident:"); + assertTrue("" + test.getUsedMemory(), test.getUsedMemory() <= 4); + } + + private void testScanResistance() { + boolean log = false; + int size = 20; + // cache size 11 (10 hot, 1 cold) + CacheLIRS test = createCache(size / 2 + 1); + // init the cache with some dummy entries + for (int i = 0; i < size; i++) { + test.put(-i, -i * 10); + } + verify(test, null); + // init with 0..9, ensure those are hot entries + for (int i = 0; i < size / 2; i++) { + test.put(i, i * 10); + test.get(i); + if (log) { + System.out.println("get " + i + " -> " + test); + } + } + verify(test, null); + // read 0..9, add 10..19 (cold) + for (int i = 0; i < size; i++) { + Integer x = test.get(i); + Integer y = test.peek(i); + if (i < size / 2) { + assertNotNull("i: " + i, x); + assertNotNull("i: " + i, y); + assertEquals(i * 10, x.intValue()); + assertEquals(i * 10, y.intValue()); + } else { + assertNull(x); + assertNull(y); + test.put(i, i * 10); + // peek should have no effect + assertEquals(i * 10, test.peek(i).intValue()); + } + if (log) { + System.out.println("get " + i + " -> " + test); + } + verify(test, null); + } + // ensure 0..9 are hot, 10..18 are not resident, 19 is cold + for (int i = 0; i < size; i++) { + Integer x = test.get(i); + if (i < size / 2 || i == size - 1) { + assertNotNull("i: " + i, x); + assertEquals(i * 10, x.intValue()); + } else { + assertNull(x); + } + verify(test, null); + } + } + + private void testRandomOperations() { + boolean log = false; + int size = 10; + Random r = new Random(1); + for (int j = 0; j < 100; j++) { + CacheLIRS test = createCache(size / 2); + HashMap good = new HashMap<>(); + for (int i = 0; i < 10000; i++) { + int key = r.nextInt(size); + int value = r.nextInt(); + switch (r.nextInt(3)) { + case 0: + if (log) { + System.out.println(i + " put " + key + " " + value); + } + good.put(key, value); + test.put(key, value); + break; + case 1: + if (log) { + System.out.println(i + " get " + key); + } + Integer a = good.get(key); + Integer b = test.get(key); + if (a == null) { + assertNull(b); + } else if (b != null) { + assertEquals(a, b); + } + break; + case 2: + if (log) { + System.out.println(i + " remove " + key); + } + good.remove(key); + test.remove(key); + break; + } + if (log) { + System.out.println(" -> " + toString(test)); + } + } + verify(test, null); + } + } + + private static String toString(CacheLIRS cache) { + StringBuilder buff = new StringBuilder(); + buff.append("mem: " + cache.getUsedMemory()); + buff.append(" stack:"); + for (K k : cache.keys(false, false)) { + buff.append(' ').append(k); + } + buff.append(" cold:"); + for (K k : cache.keys(true, false)) { + buff.append(' ').append(k); + } + buff.append(" non-resident:"); + for (K k : cache.keys(true, true)) { + buff.append(' ').append(k); + } + return buff.toString(); + } + + private void verify(CacheLIRS cache, String expected) { + if (expected != null) { + String got = toString(cache); + assertEquals(expected, got); + } + int mem = 0; + for (K k : cache.keySet()) { + mem += cache.getMemory(k); + } + assertEquals(mem, cache.getUsedMemory()); + List stack = cache.keys(false, false); + List cold = cache.keys(true, false); + List nonResident = cache.keys(true, true); + assertEquals(nonResident.size(), cache.sizeNonResident()); + HashSet hot = new HashSet<>(stack); + hot.removeAll(cold); + hot.removeAll(nonResident); + assertEquals(hot.size(), cache.sizeHot()); + assertEquals(hot.size() + cold.size(), cache.size()); + if (stack.size() > 0) { + K lastStack = stack.get(stack.size() - 1); + assertTrue(hot.contains(lastStack)); + } + } + + private static CacheLIRS createCache(int maxSize) { + return new CacheLIRS<>(maxSize, 1, 0); + } + +} diff --git a/h2/src/test/org/h2/test/store/TestCacheLongKeyLIRS.java b/h2/src/test/org/h2/test/store/TestCacheLongKeyLIRS.java new file mode 100644 index 0000000..487f0d6 --- /dev/null +++ b/h2/src/test/org/h2/test/store/TestCacheLongKeyLIRS.java @@ -0,0 +1,505 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map.Entry; +import java.util.Random; +import org.h2.mvstore.cache.CacheLongKeyLIRS; +import org.h2.test.TestBase; + +/** + * Tests the cache algorithm. + */ +public class TestCacheLongKeyLIRS extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testCache(); + } + + private void testCache() { + testRandomSmallCache(); + testEdgeCases(); + testSize(); + testClear(); + testGetPutPeekRemove(); + testPruneStack(); + testLimitHot(); + testLimitNonResident(); + testLimitMemory(); + testScanResistance(); + testRandomOperations(); + } + + private void testRandomSmallCache() { + Random r = new Random(1); + for (int i = 0; i < 10000; i++) { + int j = 0; + StringBuilder buff = new StringBuilder(); + int maxSize = 1 + r.nextInt(10); + buff.append("size:").append(maxSize).append('\n'); + CacheLongKeyLIRS test = createCache(maxSize); + for (; j < 30; j++) { + String lastState = toString(test); + try { + int key = r.nextInt(5); + switch (r.nextInt(3)) { + case 0: + int memory = r.nextInt(5) + 1; + buff.append("add ").append(key).append(' '). + append(memory).append('\n'); + test.put(key, j, memory); + break; + case 1: + buff.append("remove ").append(key).append('\n'); + test.remove(key); + break; + case 2: + buff.append("get ").append(key).append('\n'); + test.get(key); + } + verify(test, null); + } catch (Throwable ex) { + println(i + "\n" + buff + "\n" + lastState + "\n" + toString(test)); + throw ex; + } + } + } + } + + private void testEdgeCases() { + CacheLongKeyLIRS test = createCache(1); + test.put(1, 10, 100); + assertEquals(0, test.size()); + assertThrows(IllegalArgumentException.class, () -> test.put(1, null, 100)); + assertThrows(IllegalArgumentException.class, () -> test.setMaxMemory(0)); + } + + private void testSize() { + verifyMapSize(7, 16); + verifyMapSize(13, 32); + verifyMapSize(25, 64); + verifyMapSize(49, 128); + verifyMapSize(97, 256); + verifyMapSize(193, 512); + verifyMapSize(385, 1024); + verifyMapSize(769, 2048); + + CacheLongKeyLIRS test; + + test = createCache(1000); + for (int j = 0; j < 2000; j++) { + test.put(j, j); + } + // for a cache of size 1000, + // there are 32 cold entries (about 1/32). + assertEquals(32, test.size() - test.sizeHot()); + // at most as many non-resident elements + // as there are entries in the stack + assertEquals(1000, test.size()); + assertEquals(1000, test.sizeNonResident()); + } + + private void verifyMapSize(int elements, int expectedMapSize) { + CacheLongKeyLIRS test; + test = createCache(elements - 1); + for (int i = 0; i < elements - 1; i++) { + test.put(i, i * 10); + } + assertTrue(test.sizeMapArray() + "<" + expectedMapSize, + test.sizeMapArray() < expectedMapSize); + test = createCache(elements); + for (int i = 0; i < elements + 1; i++) { + test.put(i, i * 10); + } + assertEquals(expectedMapSize, test.sizeMapArray()); + test = createCache(elements * 2); + for (int i = 0; i < elements * 2; i++) { + test.put(i, i * 10); + } + assertTrue(test.sizeMapArray() + ">" + expectedMapSize, + test.sizeMapArray() > expectedMapSize); + } + + private void testGetPutPeekRemove() { + CacheLongKeyLIRS test = createCache(4); + test.put(1, 10); + test.put(2, 20); + test.put(3, 30); + assertNull(test.peek(4)); + assertNull(test.get(4)); + test.put(4, 40); + verify(test, "mem: 4 stack: 4 3 2 1 cold: non-resident:"); + // move middle to front + assertEquals(30, test.get(3).intValue()); + assertEquals(20, test.get(2).intValue()); + assertEquals(20, test.peek(2).intValue()); + // already on (an optimization) + assertEquals(20, test.get(2).intValue()); + assertEquals(10, test.peek(1).intValue()); + assertEquals(10, test.get(1).intValue()); + verify(test, "mem: 4 stack: 1 2 3 4 cold: non-resident:"); + test.put(3, 30); + verify(test, "mem: 4 stack: 3 1 2 4 cold: non-resident:"); + // 5 is cold; will make 4 non-resident + test.put(5, 50); + verify(test, "mem: 4 stack: 5 3 1 2 cold: 5 non-resident: 4"); + assertEquals(1, test.getMemory(1)); + assertEquals(1, test.getMemory(5)); + assertEquals(0, test.getMemory(4)); + assertEquals(0, test.getMemory(100)); + assertNotNull(test.peek(4)); + assertNotNull(test.get(4)); + assertEquals(10, test.get(1).intValue()); + assertEquals(20, test.get(2).intValue()); + assertEquals(30, test.get(3).intValue()); + verify(test, "mem: 5 stack: 3 2 1 cold: 4 5 non-resident:"); + assertEquals(50, test.get(5).intValue()); + verify(test, "mem: 5 stack: 5 3 2 1 cold: 5 4 non-resident:"); + assertEquals(50, test.get(5).intValue()); + verify(test, "mem: 5 stack: 5 3 2 cold: 1 4 non-resident:"); + + // remove + assertEquals(50, test.remove(5).intValue()); + assertNull(test.remove(5)); + verify(test, "mem: 4 stack: 3 2 1 cold: 4 non-resident:"); + assertNotNull(test.remove(4)); + verify(test, "mem: 3 stack: 3 2 1 cold: non-resident:"); + assertNull(test.remove(4)); + verify(test, "mem: 3 stack: 3 2 1 cold: non-resident:"); + test.put(4, 40); + test.put(5, 50); + verify(test, "mem: 4 stack: 5 4 3 2 cold: 5 non-resident: 1"); + test.get(5); + test.get(2); + test.get(3); + test.get(4); + verify(test, "mem: 4 stack: 4 3 2 5 cold: 2 non-resident: 1"); + assertEquals(50, test.remove(5).intValue()); + verify(test, "mem: 3 stack: 4 3 2 cold: non-resident: 1"); + assertEquals(20, test.remove(2).intValue()); + assertFalse(test.containsKey(1)); + assertEquals(10, test.remove(1).intValue()); + assertFalse(test.containsKey(1)); + verify(test, "mem: 2 stack: 4 3 cold: non-resident:"); + test.put(1, 10); + test.put(2, 20); + verify(test, "mem: 4 stack: 2 1 4 3 cold: non-resident:"); + test.get(1); + test.get(3); + test.get(4); + verify(test, "mem: 4 stack: 4 3 1 2 cold: non-resident:"); + assertEquals(10, test.remove(1).intValue()); + verify(test, "mem: 3 stack: 4 3 2 cold: non-resident:"); + test.remove(2); + test.remove(3); + test.remove(4); + + // test clear + test.clear(); + verify(test, "mem: 0 stack: cold: non-resident:"); + + // strange situation where there is only a non-resident entry + test.put(1, 10); + test.put(2, 20); + test.put(3, 30); + test.put(4, 40); + test.put(5, 50); + assertTrue(test.containsValue(50)); + verify(test, "mem: 4 stack: 5 4 3 2 cold: 5 non-resident: 1"); + // 1 was non-resident, so this should make it hot + test.put(1, 10); + verify(test, "mem: 4 stack: 1 5 4 3 cold: 2 non-resident: 5"); + assertTrue(test.containsValue(50)); + test.remove(2); + test.remove(3); + test.remove(4); + verify(test, "mem: 1 stack: 1 cold: non-resident: 5"); + assertTrue(test.containsKey(1)); + test.remove(1); + assertFalse(test.containsKey(1)); + verify(test, "mem: 0 stack: cold: non-resident: 5"); + assertFalse(test.containsKey(5)); + assertTrue(test.isEmpty()); + + // verify that converting a hot to cold entry will prune the stack + test.clear(); + test.put(1, 10); + test.put(2, 20); + test.put(3, 30); + test.put(4, 40); + test.put(5, 50); + test.get(4); + test.get(3); + verify(test, "mem: 4 stack: 3 4 5 2 cold: 5 non-resident: 1"); + test.put(6, 60); + verify(test, "mem: 4 stack: 6 3 4 5 2 cold: 6 non-resident: 5 1"); + // this will prune the stack (remove entry 5 as entry 2 becomes cold) + test.get(6); + verify(test, "mem: 4 stack: 6 3 4 cold: 2 non-resident: 5 1"); + } + + private void testPruneStack() { + CacheLongKeyLIRS test = createCache(5); + for (int i = 0; i < 7; i++) { + test.put(i, i * 10); + } + verify(test, "mem: 5 stack: 6 5 4 3 2 1 cold: 6 non-resident: 5 0"); + test.get(4); + test.get(3); + test.get(2); + verify(test, "mem: 5 stack: 2 3 4 6 5 1 cold: 6 non-resident: 5 0"); + // this call needs to prune the stack + test.remove(1); + verify(test, "mem: 4 stack: 2 3 4 6 cold: non-resident: 5 0"); + test.put(0, 0); + test.put(1, 10); + // the stack was not pruned, the following will fail + verify(test, "mem: 5 stack: 1 0 2 3 4 cold: 1 non-resident: 6 5"); + } + + private void testClear() { + CacheLongKeyLIRS test = createCache(40); + for (int i = 0; i < 5; i++) { + test.put(i, 10 * i, 9); + } + verify(test, "mem: 36 stack: 4 3 2 1 cold: 4 non-resident: 0"); + for (Entry e : test.entrySet()) { + assertTrue(e.getKey() >= 1 && e.getKey() <= 4); + assertTrue(e.getValue() >= 10 && e.getValue() <= 40); + } + for (int x : test.values()) { + assertTrue(x >= 10 && x <= 40); + } + for (long x : test.keySet()) { + assertTrue(x >= 1 && x <= 4); + } + assertEquals(40, test.getMaxMemory()); + assertEquals(36, test.getUsedMemory()); + assertEquals(4, test.size()); + assertEquals(3, test.sizeHot()); + assertEquals(1, test.sizeNonResident()); + assertFalse(test.isEmpty()); + + // changing the limit is not supposed to modify the map + test.setMaxMemory(10); + assertEquals(10, test.getMaxMemory()); + test.setMaxMemory(40); + verify(test, "mem: 36 stack: 4 3 2 1 cold: 4 non-resident: 0"); + + test.putAll(test.getMap()); + verify(test, "mem: 4 stack: 4 3 2 1 cold: non-resident: 0"); + + test.clear(); + verify(test, "mem: 0 stack: cold: non-resident:"); + + assertEquals(40, test.getMaxMemory()); + assertEquals(0, test.getUsedMemory()); + assertEquals(0, test.size()); + assertEquals(0, test.sizeHot()); + assertEquals(0, test.sizeNonResident()); + assertTrue(test.isEmpty()); + } + + private void testLimitHot() { + CacheLongKeyLIRS test = createCache(100); + for (int i = 0; i < 300; i++) { + test.put(i, 10 * i); + } + assertEquals(100, test.size()); + assertEquals(200, test.sizeNonResident()); + assertEquals(96, test.sizeHot()); + } + + private void testLimitNonResident() { + CacheLongKeyLIRS test = createCache(4); + for (int i = 0; i < 20; i++) { + test.put(i, 10 * i); + } + verify(test, "mem: 4 stack: 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 " + + "cold: 19 non-resident: 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 0"); + } + + private void testLimitMemory() { + CacheLongKeyLIRS test = createCache(4); + for (int i = 0; i < 5; i++) { + test.put(i, 10 * i, 1); + } + verify(test, "mem: 4 stack: 4 3 2 1 cold: 4 non-resident: 0"); + assertTrue("" + test.getUsedMemory(), test.getUsedMemory() <= 4); + test.put(6, 60, 3); + verify(test, "mem: 4 stack: 6 4 3 cold: 6 non-resident: 2 1 4 0"); + assertTrue("" + test.getUsedMemory(), test.getUsedMemory() <= 4); + test.put(7, 70, 3); + verify(test, "mem: 4 stack: 7 6 4 3 cold: 7 non-resident: 6 2 1 4 0"); + assertTrue("" + test.getUsedMemory(), test.getUsedMemory() <= 4); + test.put(8, 80, 4); + verify(test, "mem: 4 stack: 8 cold: non-resident:"); + assertTrue("" + test.getUsedMemory(), test.getUsedMemory() <= 4); + } + + private void testScanResistance() { + boolean log = false; + int size = 20; + // cache size 11 (10 hot, 2 cold) + CacheLongKeyLIRS test = createCache(size / 2 + 2); + // init the cache with some dummy entries + for (int i = 0; i < size; i++) { + test.put(-i, -i * 10); + } + verify(test, null); + // init with 0..9, ensure those are hot entries + for (int i = 0; i < size / 2; i++) { + test.put(i, i * 10); + test.get(i); + if (log) { + println("get " + i + " -> " + test); + } + } + verify(test, null); + // read 0..9, add 10..19 (cold) + for (int i = 0; i < size; i++) { + Integer x = test.get(i); + Integer y = test.peek(i); + if (i < size / 2) { + assertNotNull("i: " + i, x); + assertNotNull("i: " + i, y); + assertEquals(i * 10, x.intValue()); + assertEquals(i * 10, y.intValue()); + } else { + assertNull(x); + assertNull(y); + test.put(i, i * 10); + // peek should have no effect + assertEquals(i * 10, test.peek(i).intValue()); + } + if (log) { + System.out.println("get " + i + " -> " + test); + } + verify(test, null); + } + + // ensure 0..9 are hot, 10..17 are not resident, 18..19 are cold + for (int i = 0; i < size; i++) { + Integer x = test.get(i); + if (i < size / 2 || i == size - 1 || i == size - 2) { + assertNotNull("i: " + i, x); + assertEquals(i * 10, x.intValue()); + } + verify(test, null); + } + } + + private void testRandomOperations() { + boolean log = false; + int size = 10; + Random r = new Random(1); + for (int j = 0; j < 100; j++) { + CacheLongKeyLIRS test = createCache(size / 2); + HashMap good = new HashMap<>(); + for (int i = 0; i < 10000; i++) { + int key = r.nextInt(size); + int value = r.nextInt(); + switch (r.nextInt(3)) { + case 0: + if (log) { + System.out.println(i + " put " + key + " " + value); + } + good.put(key, value); + test.put(key, value); + break; + case 1: + if (log) { + System.out.println(i + " get " + key); + } + Integer a = good.get(key); + Integer b = test.get(key); + if (a == null) { + assertNull(b); + } else if (b != null) { + assertEquals(a, b); + } + break; + case 2: + if (log) { + System.out.println(i + " remove " + key); + } + good.remove(key); + test.remove(key); + break; + } + if (log) { + System.out.println(" -> " + toString(test)); + } + } + verify(test, null); + } + } + + private static String toString(CacheLongKeyLIRS cache) { + StringBuilder buff = new StringBuilder(); + buff.append("mem: " + cache.getUsedMemory()); + buff.append(" stack:"); + for (long k : cache.keys(false, false)) { + buff.append(' ').append(k); + } + buff.append(" cold:"); + for (long k : cache.keys(true, false)) { + buff.append(' ').append(k); + } + buff.append(" non-resident:"); + for (long k : cache.keys(true, true)) { + buff.append(' ').append(k); + } + return buff.toString(); + } + + private void verify(CacheLongKeyLIRS cache, String expected) { + if (expected != null) { + String got = toString(cache); + assertEquals(expected, got); + } + int mem = 0; + for (long k : cache.keySet()) { + mem += cache.getMemory(k); + } + assertEquals(mem, cache.getUsedMemory()); + List stack = cache.keys(false, false); + List cold = cache.keys(true, false); + List nonResident = cache.keys(true, true); + assertEquals(nonResident.size(), cache.sizeNonResident()); + HashSet hot = new HashSet<>(stack); + hot.removeAll(cold); + hot.removeAll(nonResident); + assertEquals(hot.size(), cache.sizeHot()); + assertEquals(hot.size() + cold.size(), cache.size()); + if (stack.size() > 0) { + long lastStack = stack.get(stack.size() - 1); + assertTrue(hot.contains(lastStack)); + } + } + + private static CacheLongKeyLIRS createCache(int maxSize) { + CacheLongKeyLIRS.Config cc = new CacheLongKeyLIRS.Config(); + cc.maxMemory = maxSize; + cc.segmentCount = 1; + cc.stackMoveDistance = 0; + return new CacheLongKeyLIRS<>(cc); + } + +} diff --git a/h2/src/test/org/h2/test/store/TestDataUtils.java b/h2/src/test/org/h2/test/store/TestDataUtils.java new file mode 100644 index 0000000..e6b2c4a --- /dev/null +++ b/h2/src/test/org/h2/test/store/TestDataUtils.java @@ -0,0 +1,360 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Random; +import org.h2.mvstore.Chunk; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.MVStoreException; +import org.h2.mvstore.WriteBuffer; +import org.h2.test.TestBase; + +/** + * Test utility classes. + */ +public class TestDataUtils extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() { + testParse(); + testWriteBuffer(); + testEncodeLength(); + testFletcher(); + testMap(); + testMapRandomized(); + testMaxShortVarIntVarLong(); + testVarIntVarLong(); + testCheckValue(); + testPagePos(); + } + + private static void testWriteBuffer() { + WriteBuffer buff = new WriteBuffer(); + buff.put(new byte[1500000]); + buff.put(new byte[1900000]); + } + + private void testFletcher() { + byte[] data = new byte[10000]; + for (int i = 0; i < 10000; i += 1000) { + assertEquals(-1, DataUtils.getFletcher32(data, 0, i)); + } + Arrays.fill(data, (byte) 255); + for (int i = 0; i < 10000; i += 1000) { + assertEquals(-1, DataUtils.getFletcher32(data, 0, i)); + } + for (int i = 0; i < 1000; i++) { + for (int j = 0; j < 255; j++) { + Arrays.fill(data, 0, i, (byte) j); + data[i] = 0; + int a = DataUtils.getFletcher32(data, 0, i); + if (i % 2 == 1) { + // add length: same as appending a 0 + int b = DataUtils.getFletcher32(data, 0, i + 1); + assertEquals(a, b); + } + data[i] = 10; + int c = DataUtils.getFletcher32(data, 0, i); + assertEquals(a, c); + } + } + long last = 0; + for (int i = 1; i < 255; i++) { + Arrays.fill(data, (byte) i); + for (int j = 0; j < 10; j += 2) { + int x = DataUtils.getFletcher32(data, 0, j); + assertTrue(x != last); + last = x; + } + } + Arrays.fill(data, (byte) 10); + assertEquals(0x1e1e1414, + DataUtils.getFletcher32(data, 0, 10000)); + assertEquals(0x1e3fa7ed, + DataUtils.getFletcher32("Fletcher32".getBytes(), 0, 10)); + assertEquals(0x1e3fa7ed, + DataUtils.getFletcher32("XFletcher32".getBytes(), 1, 10)); + } + + private void testMap() { + StringBuilder buff = new StringBuilder(); + DataUtils.appendMap(buff, "", ""); + DataUtils.appendMap(buff, "a", "1"); + DataUtils.appendMap(buff, "b", ","); + DataUtils.appendMap(buff, "c", "1,2"); + DataUtils.appendMap(buff, "d", "\"test\""); + DataUtils.appendMap(buff, "e", "}"); + DataUtils.appendMap(buff, "name", "1:1\","); + String encoded = buff.toString(); + assertEquals(":,a:1,b:\",\",c:\"1,2\",d:\"\\\"test\\\"\",e:},name:\"1:1\\\",\"", encoded); + + HashMap m = DataUtils.parseMap(encoded); + assertEquals(7, m.size()); + assertEquals("", m.get("")); + assertEquals("1", m.get("a")); + assertEquals(",", m.get("b")); + assertEquals("1,2", m.get("c")); + assertEquals("\"test\"", m.get("d")); + assertEquals("}", m.get("e")); + assertEquals("1:1\",", m.get("name")); + assertEquals("1:1\",", DataUtils.getMapName(encoded)); + + buff.setLength(0); + DataUtils.appendMap(buff, "1", "1"); + DataUtils.appendMap(buff, "name", "2"); + DataUtils.appendMap(buff, "3", "3"); + encoded = buff.toString(); + assertEquals("2", DataUtils.parseMap(encoded).get("name")); + assertEquals("2", DataUtils.getMapName(encoded)); + + buff.setLength(0); + DataUtils.appendMap(buff, "name", "xx"); + encoded = buff.toString(); + assertEquals("xx", DataUtils.parseMap(encoded).get("name")); + assertEquals("xx", DataUtils.getMapName(encoded)); + } + + private void testMapRandomized() { + Random r = new Random(1); + String chars = "a_1,\\\":"; + for (int i = 0; i < 1000; i++) { + StringBuilder buff = new StringBuilder(); + for (int j = 0; j < 20; j++) { + buff.append(chars.charAt(r.nextInt(chars.length()))); + } + try { + HashMap map = DataUtils.parseMap(buff.toString()); + assertNotNull(map); + // ok + } catch (MVStoreException e) { + // ok - but not another exception + } + } + } + + private void testMaxShortVarIntVarLong() { + ByteBuffer buff = ByteBuffer.allocate(100); + DataUtils.writeVarInt(buff, DataUtils.COMPRESSED_VAR_INT_MAX); + assertEquals(3, buff.position()); + buff.rewind(); + DataUtils.writeVarInt(buff, DataUtils.COMPRESSED_VAR_INT_MAX + 1); + assertEquals(4, buff.position()); + buff.rewind(); + DataUtils.writeVarLong(buff, DataUtils.COMPRESSED_VAR_LONG_MAX); + assertEquals(7, buff.position()); + buff.rewind(); + DataUtils.writeVarLong(buff, DataUtils.COMPRESSED_VAR_LONG_MAX + 1); + assertEquals(8, buff.position()); + buff.rewind(); + } + + private void testVarIntVarLong() { + ByteBuffer buff = ByteBuffer.allocate(100); + for (long x = 0; x < 1000; x++) { + testVarIntVarLong(buff, x); + testVarIntVarLong(buff, -x); + } + for (long x = Long.MIN_VALUE, i = 0; i < 1000; x++, i++) { + testVarIntVarLong(buff, x); + } + for (long x = Long.MAX_VALUE, i = 0; i < 1000; x--, i++) { + testVarIntVarLong(buff, x); + } + for (int shift = 0; shift < 64; shift++) { + for (long x = 250; x < 260; x++) { + testVarIntVarLong(buff, x << shift); + testVarIntVarLong(buff, -(x << shift)); + } + } + // invalid varInt / varLong + // should work, but not read far too much + for (int i = 0; i < 50; i++) { + buff.put((byte) 255); + } + buff.flip(); + assertEquals(-1, DataUtils.readVarInt(buff)); + assertEquals(5, buff.position()); + buff.rewind(); + assertEquals(-1, DataUtils.readVarLong(buff)); + assertEquals(10, buff.position()); + + buff.clear(); + testVarIntVarLong(buff, DataUtils.COMPRESSED_VAR_INT_MAX); + testVarIntVarLong(buff, DataUtils.COMPRESSED_VAR_INT_MAX + 1); + testVarIntVarLong(buff, DataUtils.COMPRESSED_VAR_LONG_MAX); + testVarIntVarLong(buff, DataUtils.COMPRESSED_VAR_LONG_MAX + 1); + } + + private void testVarIntVarLong(ByteBuffer buff, long x) { + int len; + byte[] data; + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + DataUtils.writeVarLong(out, x); + data = out.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); + } + DataUtils.writeVarLong(buff, x); + len = buff.position(); + assertEquals(data.length, len); + byte[] data2 = new byte[len]; + buff.position(0); + buff.get(data2); + assertEquals(data2, data); + buff.flip(); + long y = DataUtils.readVarLong(buff); + assertEquals(y, x); + assertEquals(len, buff.position()); + assertEquals(len, DataUtils.getVarLongLen(x)); + buff.clear(); + + int intX = (int) x; + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + DataUtils.writeVarInt(out, intX); + data = out.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); + } + DataUtils.writeVarInt(buff, intX); + len = buff.position(); + assertEquals(data.length, len); + data2 = new byte[len]; + buff.position(0); + buff.get(data2); + assertEquals(data2, data); + buff.flip(); + int intY = DataUtils.readVarInt(buff); + assertEquals(intY, intX); + assertEquals(len, buff.position()); + assertEquals(len, DataUtils.getVarIntLen(intX)); + buff.clear(); + } + + private void testCheckValue() { + // 0 xor 0 = 0 + assertEquals(0, DataUtils.getCheckValue(0)); + // 1111... xor 1111... = 0 + assertEquals(0, DataUtils.getCheckValue(-1)); + // 0 xor 1111... = 1111... + assertEquals((short) -1, DataUtils.getCheckValue(-1 >>> 16)); + // 1111... xor 0 = 1111... + assertEquals((short) -1, DataUtils.getCheckValue(-1 << 16)); + // 0 xor 1000... = 1000... + assertEquals((short) (1 << 15), DataUtils.getCheckValue(1 << 15)); + // 1000... xor 0 = 1000... + assertEquals((short) (1 << 15), DataUtils.getCheckValue(1 << 31)); + } + + private void testParse() { + for (long i = -1; i != 0; i >>>= 1) { + String x = Long.toHexString(i); + assertEquals(i, DataUtils.parseHexLong(x)); + x = Long.toHexString(-i); + assertEquals(-i, DataUtils.parseHexLong(x)); + int j = (int) i; + x = Integer.toHexString(j); + assertEquals(j, DataUtils.parseHexInt(x)); + j = (int) -i; + x = Integer.toHexString(j); + assertEquals(j, DataUtils.parseHexInt(x)); + } + } + + private void testPagePos() { + assertEquals(0, DataUtils.PAGE_TYPE_LEAF); + assertEquals(1, DataUtils.PAGE_TYPE_NODE); + + long max = DataUtils.getPagePos(Chunk.MAX_ID, Integer.MAX_VALUE, + Integer.MAX_VALUE, DataUtils.PAGE_TYPE_NODE); + String hex = Long.toHexString(max); + assertEquals(max, DataUtils.parseHexLong(hex)); + assertEquals(Chunk.MAX_ID, DataUtils.getPageChunkId(max)); + assertEquals(Integer.MAX_VALUE, DataUtils.getPageOffset(max)); + assertEquals(DataUtils.PAGE_LARGE, DataUtils.getPageMaxLength(max)); + assertEquals(DataUtils.PAGE_TYPE_NODE, DataUtils.getPageType(max)); + + long overflow = DataUtils.getPagePos(Chunk.MAX_ID + 1, + Integer.MAX_VALUE, Integer.MAX_VALUE, DataUtils.PAGE_TYPE_NODE); + assertTrue(Chunk.MAX_ID + 1 != DataUtils.getPageChunkId(overflow)); + + for (int i = 0; i < Chunk.MAX_ID; i++) { + long pos = DataUtils.getPagePos(i, 3, 128, 1); + assertEquals(i, DataUtils.getPageChunkId(pos)); + assertEquals(3, DataUtils.getPageOffset(pos)); + assertEquals(128, DataUtils.getPageMaxLength(pos)); + assertEquals(1, DataUtils.getPageType(pos)); + } + for (int type = 0; type <= 1; type++) { + for (int chunkId = 0; chunkId < Chunk.MAX_ID; + chunkId += Chunk.MAX_ID / 100) { + for (long offset = 0; offset < Integer.MAX_VALUE; + offset += Integer.MAX_VALUE / 100) { + for (int length = 0; length < 2000000; length += 200000) { + long pos = DataUtils.getPagePos( + chunkId, (int) offset, length, type); + assertEquals(chunkId, DataUtils.getPageChunkId(pos)); + assertEquals(offset, DataUtils.getPageOffset(pos)); + assertTrue(DataUtils.getPageMaxLength(pos) >= length); + assertTrue(DataUtils.getPageType(pos) == type); + } + } + } + } + } + + private void testEncodeLength() { + int lastCode = 0; + assertEquals(0, DataUtils.encodeLength(32)); + assertEquals(1, DataUtils.encodeLength(33)); + assertEquals(1, DataUtils.encodeLength(48)); + assertEquals(2, DataUtils.encodeLength(49)); + assertEquals(2, DataUtils.encodeLength(64)); + assertEquals(3, DataUtils.encodeLength(65)); + assertEquals(30, DataUtils.encodeLength(1024 * 1024)); + assertEquals(31, DataUtils.encodeLength(1024 * 1024 + 1)); + assertEquals(31, DataUtils.encodeLength(Integer.MAX_VALUE)); + int[] maxLengthForIndex = {32, 48, 64, 96, 128, 192, 256, + 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 6144, + 8192, 12288, 16384, 24576, 32768, 49152, 65536, + 98304, 131072, 196608, 262144, 393216, 524288, + 786432, 1048576}; + for (int i = 0; i < maxLengthForIndex.length; i++) { + assertEquals(i, DataUtils.encodeLength(maxLengthForIndex[i])); + assertEquals(i + 1, DataUtils.encodeLength(maxLengthForIndex[i] + 1)); + } + for (int i = 1024 * 1024 + 1; i < 100 * 1024 * 1024; i += 1024) { + int code = DataUtils.encodeLength(i); + assertEquals(31, code); + } + for (int i = 0; i < 2 * 1024 * 1024; i++) { + int code = DataUtils.encodeLength(i); + assertTrue(code <= 31 && code >= 0); + assertTrue(code >= lastCode); + if (code > lastCode) { + lastCode = code; + } + int max = DataUtils.getPageMaxLength(code << 1); + assertTrue(max >= i && max >= 32); + } + } + +} diff --git a/h2/src/test/org/h2/test/store/TestDefrag.java b/h2/src/test/org/h2/test/store/TestDefrag.java new file mode 100644 index 0000000..b78bab5 --- /dev/null +++ b/h2/src/test/org/h2/test/store/TestDefrag.java @@ -0,0 +1,79 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import static org.h2.engine.Constants.SUFFIX_MV_FILE; + +import java.io.File; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; +import java.text.NumberFormat; + +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Test off-line compaction procedure used by SHUTDOWN DEFRAG command + * + * @author Andrei Tokar + */ +public class TestDefrag extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + return !config.memory && config.big && !config.ci; + } + + @Override + public void test() throws Exception { + String dbName = getTestName(); + deleteDb(dbName); + File dbFile = new File(getBaseDir(), dbName + SUFFIX_MV_FILE); + NumberFormat nf = NumberFormat.getInstance(); + try (Connection c = getConnection(dbName)) { + try (Statement st = c.createStatement()) { + st.execute("CREATE TABLE IF NOT EXISTS test (id INT PRIMARY KEY, txt varchar)" + + " AS SELECT x, x || SPACE(200) FROM SYSTEM_RANGE(1,10000000)"); + st.execute("checkpoint"); + } + long origSize = dbFile.length(); + String message = "before defrag: " + nf.format(origSize); + trace(message); + assertTrue(message, origSize > 4_000_000_000L); + try (Statement st = c.createStatement()) { + st.execute("shutdown defrag"); + } + } + long compactedSize = dbFile.length(); + String message = "after defrag: " + nf.format(compactedSize); + trace(message); + assertTrue(message, compactedSize < 400_000_000L); + + try (Connection c = getConnection(dbName + ";LAZY_QUERY_EXECUTION=1")) { + try (Statement st = c.createStatement()) { + ResultSet rs = st.executeQuery("SELECT * FROM test"); + int count = 0; + while (rs.next()) { + ++count; + assertEquals(count, rs.getInt(1)); + assertTrue(rs.getString(2).startsWith(count + " ")); + } + assertEquals(10_000_000, count); + } + } + deleteDb(dbName); + } +} diff --git a/h2/src/test/org/h2/test/store/TestFreeSpace.java b/h2/src/test/org/h2/test/store/TestFreeSpace.java new file mode 100644 index 0000000..c4867a4 --- /dev/null +++ b/h2/src/test/org/h2/test/store/TestFreeSpace.java @@ -0,0 +1,145 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import org.h2.mvstore.FreeSpaceBitSet; +import org.h2.test.TestBase; +import org.h2.util.Utils; + +/** + * Tests the free space list. + */ +public class TestFreeSpace extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + testMemoryUsage(); + testPerformance(); + } + + @Override + public void test() throws Exception { + testSimple(); + testRandomized(); + } + + private static void testPerformance() { + for (int i = 0; i < 10; i++) { + long t = System.nanoTime(); + + FreeSpaceBitSet f = new FreeSpaceBitSet(0, 4096); + // 75 ms + + // FreeSpaceList f = new FreeSpaceList(0, 4096); + // 13868 ms + + // FreeSpaceTree f = new FreeSpaceTree(0, 4096); + // 56 ms + + for (int j = 0; j < 100000; j++) { + f.markUsed(j * 2 * 4096, 4096); + } + for (int j = 0; j < 100000; j++) { + f.free(j * 2 * 4096, 4096); + } + for (int j = 0; j < 100000; j++) { + f.allocate(4096 * 2); + } + System.out.println(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t)); + } + } + + private static void testMemoryUsage() { + + // 16 GB file size + long size = 16L * 1024 * 1024 * 1024; + System.gc(); + System.gc(); + long first = Utils.getMemoryUsed(); + + FreeSpaceBitSet f = new FreeSpaceBitSet(0, 4096); + // 512 KB + + // FreeSpaceTree f = new FreeSpaceTree(0, 4096); + // 64 MB + + // FreeSpaceList f = new FreeSpaceList(0, 4096); + // too slow + + for (long j = size; j > 0; j -= 4 * 4096) { + f.markUsed(j, 4096); + } + + System.gc(); + System.gc(); + long mem = Utils.getMemoryUsed() - first; + System.out.println("Memory used: " + mem); + System.out.println("f: " + f.toString().length()); + + } + + private void testSimple() { + FreeSpaceBitSet f1 = new FreeSpaceBitSet(2, 1024); + FreeSpaceList f2 = new FreeSpaceList(2, 1024); + FreeSpaceTree f3 = new FreeSpaceTree(2, 1024); + assertEquals(f1.toString(), f2.toString()); + assertEquals(f1.toString(), f3.toString()); + assertEquals(2 * 1024, f1.allocate(10240)); + assertEquals(2 * 1024, f2.allocate(10240)); + assertEquals(2 * 1024, f3.allocate(10240)); + assertEquals(f1.toString(), f2.toString()); + assertEquals(f1.toString(), f3.toString()); + f1.markUsed(20480, 1024); + f2.markUsed(20480, 1024); + f3.markUsed(20480, 1024); + assertEquals(f1.toString(), f2.toString()); + assertEquals(f1.toString(), f3.toString()); + } + + private void testRandomized() { + FreeSpaceBitSet f1 = new FreeSpaceBitSet(2, 8); + FreeSpaceList f2 = new FreeSpaceList(2, 8); + Random r = new Random(1); + StringBuilder log = new StringBuilder(); + for (int i = 0; i < 100000; i++) { + long pos = r.nextInt(1024); + int length = 1 + r.nextInt(8 * 128); + switch (r.nextInt(3)) { + case 0: { + log.append("allocate(" + length + ");\n"); + long a = f1.allocate(length); + long b = f2.allocate(length); + assertEquals(a, b); + break; + } + case 1: + if (f1.isUsed(pos, length)) { + log.append("free(" + pos + ", " + length + ");\n"); + f1.free(pos, length); + f2.free(pos, length); + } + break; + case 2: + if (f1.isFree(pos, length)) { + log.append("markUsed(" + pos + ", " + length + ");\n"); + f1.markUsed(pos, length); + f2.markUsed(pos, length); + } + break; + } + assertEquals(f1.toString(), f2.toString()); + } + } + +} diff --git a/h2/src/test/org/h2/test/store/TestImmutableArray.java b/h2/src/test/org/h2/test/store/TestImmutableArray.java new file mode 100644 index 0000000..9b40fdf --- /dev/null +++ b/h2/src/test/org/h2/test/store/TestImmutableArray.java @@ -0,0 +1,162 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import org.h2.dev.util.ImmutableArray2; +import org.h2.test.TestBase; + +/** + * Test the concurrent linked list. + */ +public class TestImmutableArray extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestImmutableArray test = (TestImmutableArray) TestBase.createCaller().init(); + test.test(); + testPerformance(); + } + + @Override + public void test() throws Exception { + testRandomized(); + } + + private static void testPerformance() { + testPerformance(true); + testPerformance(false); + testPerformance(true); + testPerformance(false); + testPerformance(true); + testPerformance(false); + } + + private static void testPerformance(final boolean immutable) { +// immutable time 2068 dummy: 60000000 +// immutable time 1140 dummy: 60000000 +// ArrayList time 361 dummy: 60000000 + + System.out.print(immutable ? "immutable" : "ArrayList"); + long start = System.nanoTime(); + int count = 20000000; + Integer x = 1; + int sum = 0; + if (immutable) { + ImmutableArray2 test = ImmutableArray2.empty(); + for (int i = 0; i < count; i++) { + if (i % 10 != 0) { + test = test.insert(test.length(), x); + } else { + test = test.insert(i % 30, x); + } + if (test.length() > 100) { + while (test.length() > 30) { + if (i % 10 != 0) { + test = test.remove(test.length() - 1); + } else { + test = test.remove(0); + } + } + } + sum += test.get(0); + sum += test.get(test.length() - 1); + sum += test.get(test.length() / 2); + if (i % 10 == 0) { + test = test.set(0, x); + } + } + } else { + ArrayList test = new ArrayList<>(); + for (int i = 0; i < count; i++) { + if (i % 10 != 0) { + test.add(test.size(), x); + } else { + test.add(i % 30, x); + } + if (test.size() > 100) { + while (test.size() > 30) { + if (i % 2 == 0) { + test.remove(test.size() - 1); + } else { + test.remove(0); + } + } + } + sum += test.get(0); + sum += test.get(test.size() - 1); + sum += test.get(test.size() / 2); + if (i % 10 == 0) { + test.set(0, x); + } + } + } + System.out.println(" time " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start) + + " dummy: " + sum); + } + + private void testRandomized() { + Random r = new Random(0); + for (int i = 0; i < 100; i++) { + ImmutableArray2 test = ImmutableArray2.empty(); + // ConcurrentRing test = new ConcurrentRing(); + ArrayList x = new ArrayList<>(); + StringBuilder buff = new StringBuilder(); + for (int j = 0; j < 1000; j++) { + buff.append("[" + j + "] "); + int opType = r.nextInt(3); + switch (opType) { + case 0: { + int index = test.length() == 0 ? 0 : r.nextInt(test.length()); + int value = r.nextInt(100); + buff.append("insert " + index + " " + value + "\n"); + test = test.insert(index, value); + x.add(index, value); + break; + } + case 1: { + if (test.length() > 0) { + int index = r.nextInt(test.length()); + int value = r.nextInt(100); + buff.append("set " + index + " " + value + "\n"); + x.set(index, value); + test = test.set(index, value); + } + break; + } + case 2: { + if (test.length() > 0) { + int index = r.nextInt(test.length()); + buff.append("remove " + index + "\n"); + x.remove(index); + test = test.remove(index); + } + break; + } + } + assertEquals(x.size(), test.length()); + assertEquals(toString(x.iterator()), toString(test.iterator())); + } + } + } + + private static String toString(Iterator it) { + StringBuilder buff = new StringBuilder(); + while (it.hasNext()) { + buff.append(' ').append(it.next()); + } + return buff.toString(); + } + +} diff --git a/h2/src/test/org/h2/test/store/TestKillProcessWhileWriting.java b/h2/src/test/org/h2/test/store/TestKillProcessWhileWriting.java new file mode 100644 index 0000000..802949a --- /dev/null +++ b/h2/src/test/org/h2/test/store/TestKillProcessWhileWriting.java @@ -0,0 +1,155 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.util.Random; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVStore; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.utils.FilePathUnstable; + +/** + * Tests the MVStore. + */ +public class TestKillProcessWhileWriting extends TestBase { + + private String fileName; + private int seed; + private FilePathUnstable fs; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); + test.config.big = true; + test.testFromMain(); + } + + @Override + public void test() throws Exception { + fs = FilePathUnstable.register(); + fs.setPartialWrites(false); + test("unstable:memFS:killProcess.h3"); + + if (config.big) { + try { + fs.setPartialWrites(true); + test("unstable:memFS:killProcess.h3"); + } finally { + fs.setPartialWrites(false); + } + } + FileUtils.delete("unstable:memFS:killProcess.h3"); + } + + private void test(String fileName) throws Exception { + for (seed = 0; seed < 10; seed++) { + this.fileName = fileName; + FileUtils.delete(fileName); + test(Integer.MAX_VALUE, seed); + int max = Integer.MAX_VALUE - fs.getDiskFullCount() + 10; + assertTrue("" + (max - 10), max > 0); + for (int i = 0; i < max; i++) { + test(i, seed); + } + } + } + + private void test(int x, int seed) throws Exception { + FileUtils.delete(fileName); + fs.setDiskFullCount(x, seed); + try { + write(); + verify(); + } catch (Exception e) { + if (x == Integer.MAX_VALUE) { + throw e; + } + fs.setDiskFullCount(0, seed); + verify(); + } + } + + private int write() { + MVStore s; + MVMap m; + + s = new MVStore.Builder(). + fileName(fileName). + pageSplitSize(50). + autoCommitDisabled(). + open(); + m = s.openMap("data"); + Random r = new Random(seed); + int op = 0; + try { + for (; op < 100; op++) { + int k = r.nextInt(100); + byte[] v = new byte[r.nextInt(100) * 100]; + int type = r.nextInt(10); + switch (type) { + case 0: + case 1: + case 2: + case 3: + m.put(k, v); + break; + case 4: + case 5: + m.remove(k); + break; + case 6: + s.commit(); + break; + case 7: + s.compact(80, 1024); + break; + case 8: + m.clear(); + break; + case 9: + s.close(); + s = new MVStore.Builder(). + fileName(fileName). + pageSplitSize(50). + autoCommitDisabled(). + open(); + m = s.openMap("data"); + break; + } + } + s.close(); + return 0; + } catch (Exception e) { + s.closeImmediately(); + return op; + } + } + + private void verify() { + + MVStore s; + MVMap m; + + FileUtils.delete(fileName); + s = new MVStore.Builder(). + fileName(fileName).open(); + m = s.openMap("data"); + for (int i = 0; i < 100; i++) { + byte[] x = m.get(i); + if (x == null) { + break; + } + assertEquals(i * 100, x.length); + } + s.close(); + } + +} diff --git a/h2/src/test/org/h2/test/store/TestMVRTree.java b/h2/src/test/org/h2/test/store/TestMVRTree.java new file mode 100644 index 0000000..4af6001 --- /dev/null +++ b/h2/src/test/org/h2/test/store/TestMVRTree.java @@ -0,0 +1,433 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Objects; +import java.util.Random; + +import javax.imageio.ImageIO; +import javax.imageio.ImageWriter; +import javax.imageio.stream.FileImageOutputStream; + +import org.h2.mvstore.MVStore; +import org.h2.mvstore.rtree.MVRTreeMap; +import org.h2.mvstore.rtree.Spatial; +import org.h2.mvstore.db.SpatialKey; +import org.h2.mvstore.type.StringDataType; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; + +/** + * Tests the r-tree. + */ +public class TestMVRTree extends TestMVStore { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() { + testRemoveAll(); + testRandomInsert(); + testSpatialKey(); + testExample(); + testMany(); + testSimple(); + testRandom(); + testRandomFind(); + } + + private void testRemoveAll() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = new MVStore.Builder().fileName(fileName).pageSplitSize(100).open()) { + MVRTreeMap map = s.openMap("data", new MVRTreeMap.Builder<>()); + Random r = new Random(1); + for (int i = 0; i < 1000; i++) { + float x = r.nextFloat() * 50, y = r.nextFloat() * 50; + Spatial k = new SpatialKey(i % 100, x, x + 2, y, y + 1); + map.put(k, "i:" + i); + } + s.commit(); + map.clear(); + } + } + + private void testRandomInsert() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = new MVStore.Builder().fileName(fileName). + pageSplitSize(100).open()) { + MVRTreeMap map = s.openMap("data", new MVRTreeMap.Builder<>()); + Random r = new Random(1); + for (int i = 0; i < 1000; i++) { + if (i % 100 == 0) { + r.setSeed(1); + } + float x = r.nextFloat() * 50, y = r.nextFloat() * 50; + Spatial k = new SpatialKey(i % 100, x, x + 2, y, y + 1); + map.put(k, "i:" + i); + if (i % 10 == 0) { + s.commit(); + } + } + } + } + + private void testSpatialKey() { + Spatial a0 = new SpatialKey(0, 1, 2, 3, 4); + Spatial a1 = new SpatialKey(0, 1, 2, 3, 4); + Spatial b0 = new SpatialKey(1, 1, 2, 3, 4); + Spatial c0 = new SpatialKey(1, 1.1f, 2.2f, 3.3f, 4.4f); + assertEquals(0, a0.hashCode()); + assertEquals(1, b0.hashCode()); + assertTrue(a0.equals(a0)); + assertTrue(a0.equals(a1)); + assertFalse(a0.equals(b0)); + assertTrue(a0.equalsIgnoringId(b0)); + assertFalse(b0.equals(c0)); + assertFalse(b0.equalsIgnoringId(c0)); + assertEquals("0: (1.0/2.0, 3.0/4.0)", a0.toString()); + assertEquals("1: (1.0/2.0, 3.0/4.0)", b0.toString()); + assertEquals("1: (1.1/2.2, 3.3/4.4)", c0.toString()); + } + + private void testExample() { + // create an in-memory store + try (MVStore s = MVStore.open(null)) { + + // open an R-tree map + MVRTreeMap r = s.openMap("data", new MVRTreeMap.Builder<>()); + + // add two key-value pairs + // the first value is the key id (to make the key unique) + // then the min x, max x, min y, max y + r.add(new SpatialKey(0, -3f, -2f, 2f, 3f), "left"); + r.add(new SpatialKey(1, 3f, 4f, 4f, 5f), "right"); + + // iterate over the intersecting keys + Iterator it = r.findIntersectingKeys( + new SpatialKey(0, 0f, 9f, 3f, 6f)); + for (Spatial k; it.hasNext(); ) { + k = it.next(); + // System.out.println(k + ": " + r.get(k)); + assertNotNull(k); + } + } + } + + private void testMany() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + int len = 1000; + try (MVStore s = openStore(fileName)) { + // s.setMaxPageSize(50); + MVRTreeMap r = s.openMap("data", + new MVRTreeMap.Builder().dimensions(2). + valueType(StringDataType.INSTANCE)); + // r.setQuadraticSplit(true); + Random rand = new Random(1); + // long t = System.nanoTime(); + // Profiler prof = new Profiler(); + // prof.startCollecting(); + for (int i = 0; i < len; i++) { + float x = rand.nextFloat(), y = rand.nextFloat(); + float p = (float) (rand.nextFloat() * 0.000001); + Spatial k = new SpatialKey(i, x - p, x + p, y - p, y + p); + r.add(k, "" + i); + if (i > 0 && (i % len / 10) == 0) { + s.commit(); + } + if (i > 0 && (i % 10000) == 0) { + render(r, getBaseDir() + "/test.png"); + } + } + } + try (MVStore s = openStore(fileName)) { + MVRTreeMap r = s.openMap("data", + new MVRTreeMap.Builder().dimensions(2). + valueType(StringDataType.INSTANCE)); + Random rand = new Random(1); + for (int i = 0; i < len; i++) { + float x = rand.nextFloat(), y = rand.nextFloat(); + float p = (float) (rand.nextFloat() * 0.000001); + Spatial k = new SpatialKey(i, x - p, x + p, y - p, y + p); + assertEquals("" + i, r.get(k)); + } + assertEquals(len, r.size()); + int count = 0; + for (Spatial k : r.keySet()) { + assertNotNull(r.get(k)); + count++; + } + assertEquals(len, count); + rand = new Random(1); + for (int i = 0; i < len; i++) { + float x = rand.nextFloat(), y = rand.nextFloat(); + float p = (float) (rand.nextFloat() * 0.000001); + Spatial k = new SpatialKey(i, x - p, x + p, y - p, y + p); + r.remove(k); + } + assertEquals(0, r.size()); + } + } + + private void testSimple() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = openStore(fileName)) { + MVRTreeMap r = s.openMap("data", + new MVRTreeMap.Builder().dimensions(2). + valueType(StringDataType.INSTANCE)); + + add(r, "Bern", key(0, 46.57, 7.27, 124381)); + add(r, "Basel", key(1, 47.34, 7.36, 170903)); + add(r, "Zurich", key(2, 47.22, 8.33, 376008)); + add(r, "Lucerne", key(3, 47.03, 8.18, 77491)); + add(r, "Geneva", key(4, 46.12, 6.09, 191803)); + add(r, "Lausanne", key(5, 46.31, 6.38, 127821)); + add(r, "Winterthur", key(6, 47.30, 8.45, 102966)); + add(r, "St. Gallen", key(7, 47.25, 9.22, 73500)); + add(r, "Biel/Bienne", key(8, 47.08, 7.15, 51203)); + add(r, "Lugano", key(9, 46.00, 8.57, 54667)); + add(r, "Thun", key(10, 46.46, 7.38, 42623)); + add(r, "Bellinzona", key(11, 46.12, 9.01, 17373)); + add(r, "Chur", key(12, 46.51, 9.32, 33756)); + // render(r, getBaseDir() + "/test.png"); + ArrayList list = new ArrayList<>(r.size()); + for (Spatial x : r.keySet()) { + list.add(r.get(x)); + } + Collections.sort(list); + assertEquals("[Basel, Bellinzona, Bern, Biel/Bienne, Chur, Geneva, " + + "Lausanne, Lucerne, Lugano, St. Gallen, Thun, Winterthur, Zurich]", + list.toString()); + + + // intersection + list.clear(); + Spatial k = key(0, 47.34, 7.36, 0); + for (Iterator it = r.findIntersectingKeys(k); it.hasNext(); ) { + list.add(r.get(it.next())); + } + Collections.sort(list); + assertEquals("[Basel]", list.toString()); + + // contains + list.clear(); + k = key(0, 47.34, 7.36, 0); + for (Iterator it = r.findContainedKeys(k); it.hasNext(); ) { + list.add(r.get(it.next())); + } + assertEquals(0, list.size()); + k = key(0, 47.34, 7.36, 171000); + for (Iterator it = r.findContainedKeys(k); it.hasNext(); ) { + list.add(r.get(it.next())); + } + assertEquals("[Basel]", list.toString()); + } + } + + private static void add(MVRTreeMap r, String name, Spatial k) { + r.put(k, name); + } + + private static Spatial key(int id, double y, double x, int population) { + float a = (float) ((int) x + (x - (int) x) * 5 / 3); + float b = 50 - (float) ((int) y + (y - (int) y) * 5 / 3); + float s = (float) Math.sqrt(population / 10000000.); + Spatial k = new SpatialKey(id, a - s, a + s, b - s, b + s); + return k; + } + + private static void render(MVRTreeMap r, String fileName) { + int width = 1000, height = 500; + BufferedImage img = new BufferedImage(width, height, + BufferedImage.TYPE_INT_ARGB); + Graphics2D g2d = (Graphics2D) img.getGraphics(); + g2d.setBackground(Color.WHITE); + g2d.setColor(Color.WHITE); + g2d.fillRect(0, 0, width, height); + g2d.setComposite(AlphaComposite.SrcOver.derive(0.5f)); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setColor(Color.BLACK); + SpatialKey b = new SpatialKey(0, Float.MAX_VALUE, Float.MIN_VALUE, + Float.MAX_VALUE, Float.MIN_VALUE); + for (Spatial x : r.keySet()) { + b.setMin(0, Math.min(b.min(0), x.min(0))); + b.setMin(1, Math.min(b.min(1), x.min(1))); + b.setMax(0, Math.max(b.max(0), x.max(0))); + b.setMax(1, Math.max(b.max(1), x.max(1))); + } + // System.out.println(b); + for (Spatial x : r.keySet()) { + int[] rect = scale(b, x, width, height); + g2d.drawRect(rect[0], rect[1], rect[2] - rect[0], rect[3] - rect[1]); + String s = r.get(x); + g2d.drawChars(s.toCharArray(), 0, s.length(), rect[0], rect[1] - 4); + } + g2d.setColor(Color.red); + ArrayList list = new ArrayList<>(); + r.addNodeKeys(list, r.getRootPage()); + for (Spatial x : list) { + int[] rect = scale(b, x, width, height); + g2d.drawRect(rect[0], rect[1], rect[2] - rect[0], rect[3] - rect[1]); + } + ImageWriter out = ImageIO.getImageWritersByFormatName("png").next(); + try { + out.setOutput(new FileImageOutputStream(new File(fileName))); + out.write(img); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static int[] scale(Spatial b, Spatial x, int width, int height) { + int[] rect = { + (int) ((x.min(0) - b.min(0)) * (width * 0.9) / + (b.max(0) - b.min(0)) + width * 0.05), + (int) ((x.min(1) - b.min(1)) * (height * 0.9) / + (b.max(1) - b.min(1)) + height * 0.05), + (int) ((x.max(0) - b.min(0)) * (width * 0.9) / + (b.max(0) - b.min(0)) + width * 0.05), + (int) ((x.max(1) - b.min(1)) * (height * 0.9) / + (b.max(1) - b.min(1)) + height * 0.05), + }; + return rect; + } + + private void testRandom() { + testRandom(true); + testRandom(false); + } + + private void testRandomFind() { + try (MVStore s = openStore(null)) { + MVRTreeMap m = s.openMap("data", new MVRTreeMap.Builder<>()); + int max = 100; + for (int x = 0; x < max; x++) { + for (int y = 0; y < max; y++) { + int id = x * max + y; + Spatial k = new SpatialKey(id, x, x, y, y); + m.put(k, id); + } + } + Random rand = new Random(1); + int operationCount = 1000; + for (int i = 0; i < operationCount; i++) { + int x1 = rand.nextInt(max), y1 = rand.nextInt(10); + int x2 = rand.nextInt(10), y2 = rand.nextInt(10); + int intersecting = Math.max(0, x2 - x1 + 1) * Math.max(0, y2 - y1 + 1); + int contained = Math.max(0, x2 - x1 - 1) * Math.max(0, y2 - y1 - 1); + Spatial k = new SpatialKey(0, x1, x2, y1, y2); + Iterator it = m.findContainedKeys(k); + int count = 0; + while (it.hasNext()) { + Spatial t = it.next(); + assertTrue(t.min(0) > x1); + assertTrue(t.min(1) > y1); + assertTrue(t.max(0) < x2); + assertTrue(t.max(1) < y2); + count++; + } + assertEquals(contained, count); + it = m.findIntersectingKeys(k); + count = 0; + while (it.hasNext()) { + Spatial t = it.next(); + assertTrue(t.min(0) >= x1); + assertTrue(t.min(1) >= y1); + assertTrue(t.max(0) <= x2); + assertTrue(t.max(1) <= y2); + count++; + } + assertEquals(intersecting, count); + } + } + } + + private void testRandom(boolean quadraticSplit) { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = openStore(fileName)) { + MVRTreeMap m = s.openMap("data", + new MVRTreeMap.Builder<>()); + + m.setQuadraticSplit(quadraticSplit); + HashMap map = new HashMap<>(); + Random rand = new Random(1); + int operationCount = 10000; + int maxValue = 300; + for (int i = 0; i < operationCount; i++) { + int key = rand.nextInt(maxValue); + Random rk = new Random(key); + float x = rk.nextFloat(), y = rk.nextFloat(); + float p = (float) (rk.nextFloat() * 0.000001); + Spatial k = new SpatialKey(key, x - p, x + p, y - p, y + p); + String v = "" + rand.nextInt(); + Iterator it; + switch (rand.nextInt(5)) { + case 0: + log(i + ": put " + k + " = " + v + " " + m.size()); + m.put(k, v); + map.put(k, v); + break; + case 1: + log(i + ": remove " + k + " " + m.size()); + m.remove(k); + map.remove(k); + break; + case 2: { + p = (float) (rk.nextFloat() * 0.01); + k = new SpatialKey(key, x - p, x + p, y - p, y + p); + it = m.findIntersectingKeys(k); + while (it.hasNext()) { + Spatial n = it.next(); + String a = map.get(n); + assertNotNull(a); + } + break; + } + case 3: { + p = (float) (rk.nextFloat() * 0.01); + k = new SpatialKey(key, x - p, x + p, y - p, y + p); + it = m.findContainedKeys(k); + while (it.hasNext()) { + Spatial n = it.next(); + String a = map.get(n); + assertNotNull(a); + } + break; + } + default: + String a = map.get(k); + String b = m.get(k); + assertTrue(Objects.equals(a, b)); + break; + } + assertEquals(map.size(), m.size()); + } + } + } +} diff --git a/h2/src/test/org/h2/test/store/TestMVStore.java b/h2/src/test/org/h2/test/store/TestMVStore.java new file mode 100644 index 0000000..3d5072b --- /dev/null +++ b/h2/src/test/org/h2/test/store/TestMVStore.java @@ -0,0 +1,2085 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.Random; +import java.util.TreeMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import org.h2.mvstore.Chunk; +import org.h2.mvstore.Cursor; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.FileStore; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVStore; +import org.h2.mvstore.MVStoreException; +import org.h2.mvstore.OffHeapStore; +import org.h2.mvstore.type.DataType; +import org.h2.mvstore.type.ObjectDataType; +import org.h2.mvstore.type.StringDataType; +import org.h2.store.fs.FilePath; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.util.Utils; + +/** + * Tests the MVStore. + */ +public class TestMVStore extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); + test.config.traceTest = true; + test.config.big = true; + test.testFromMain(); + } + + @Override + public void test() throws Exception { + testRemoveMapRollback(); + testProvidedFileStoreNotOpenedAndClosed(); + testVolatileMap(); + testEntrySet(); + testCompressEmptyPage(); + testCompressed(); + testFileFormatExample(); + testMaxChunkLength(); + testCacheInfo(); + testRollback(); + testVersionsToKeep(); + testVersionsToKeep2(); + testRemoveMap(); + testIsEmpty(); + testOffHeapStorage(); + testNewerWriteVersion(); + testCompactFully(); + testBackgroundExceptionListener(); + testOldVersion(); + testAtomicOperations(); + testWriteBuffer(); + testWriteDelay(); + testEncryptedFile(); + testFileFormatChange(); + testRecreateMap(); + testRenameMapRollback(); + testCustomMapType(); + testCacheSize(); + testConcurrentOpen(); + testFileHeader(); + testFileHeaderCorruption(); + testIndexSkip(); + testIndexSkipReverse(); + testMinMaxNextKey(); + testStoreVersion(); + testIterateOldVersion(); + testObjects(); + testExample(); + testExampleMvcc(); + testOpenStoreCloseLoop(); + testVersion(); + testTruncateFile(); + testFastDelete(); + testRollbackInMemory(); + testRollbackStored(); + testMeta(); + testInMemory(); + testLargeImport(); + testBtreeStore(); + testCompact(); + testCompactMapNotOpen(); + testReuseSpace(); + testRandom(); + testKeyValueClasses(); + testIterate(); + testIterateReverse(); + testCloseTwice(); + testSimple(); + testInvalidSettings(); + + // longer running tests + testLargerThan2G(); + } + + private void testRemoveMapRollback() { + try (MVStore store = new MVStore.Builder(). + open()) { + MVMap map = store.openMap("test"); + map.put("1", "Hello"); + store.commit(); + store.removeMap(map); + store.rollback(); + assertTrue(store.hasMap("test")); + map = store.openMap("test"); + assertEquals("Hello", map.get("1")); + } + + FileUtils.createDirectories(getTestDir("")); + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore store = new MVStore.Builder(). + autoCommitDisabled(). + fileName(fileName). + open()) { + MVMap map = store.openMap("test"); + map.put("1", "Hello"); + store.commit(); + store.removeMap(map); + store.rollback(); + assertTrue(store.hasMap("test")); + map = store.openMap("test"); + // the data will get back alive + assertEquals("Hello", map.get("1")); + } + } + + private void testProvidedFileStoreNotOpenedAndClosed() { + final AtomicInteger openClose = new AtomicInteger(); + FileStore fileStore = new OffHeapStore() { + + @Override + public void open(String fileName, boolean readOnly, char[] encryptionKey) { + openClose.incrementAndGet(); + super.open(fileName, readOnly, encryptionKey); + } + + @Override + public void close() { + openClose.incrementAndGet(); + super.close(); + } + }; + MVStore store = new MVStore.Builder(). + fileStore(fileStore). + open(); + store.close(); + assertEquals(0, openClose.get()); + } + + private void testVolatileMap() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore store = new MVStore.Builder(). + fileName(fileName). + open()) { + MVMap map = store.openMap("test"); + assertFalse(map.isVolatile()); + map.setVolatile(true); + assertTrue(map.isVolatile()); + map.put("1", "Hello"); + assertEquals("Hello", map.get("1")); + assertEquals(1, map.size()); + } + try (MVStore store = new MVStore.Builder(). + fileName(fileName). + open()) { + assertTrue(store.hasMap("test")); + MVMap map = store.openMap("test"); + assertEquals(0, map.size()); + } + } + + private void testEntrySet() { + try (MVStore s = new MVStore.Builder().open()) { + MVMap map = s.openMap("data"); + for (int i = 0; i < 20; i++) { + map.put(i, i * 10); + } + int next = 0; + for (Entry e : map.entrySet()) { + assertEquals(next, e.getKey().intValue()); + assertEquals(next * 10, e.getValue().intValue()); + next++; + } + } + } + + private void testCompressEmptyPage() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + MVStore store = new MVStore.Builder(). + cacheSize(100).fileName(fileName). + compress(). + autoCommitBufferSize(10 * 1024). + open(); + MVMap map = store.openMap("test"); + store.removeMap(map); + store.commit(); + store.close(); + store = new MVStore.Builder(). + compress(). + open(); + store.close(); + } + + private void testCompressed() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + String data = new String(new char[1000]).replace((char) 0, 'x'); + long lastSize = 0; + for (int level = 0; level <= 2; level++) { + FileUtils.delete(fileName); + MVStore.Builder builder = new MVStore.Builder().fileName(fileName); + if (level == 1) { + builder.compress(); + } else if (level == 2) { + builder.compressHigh(); + } + try (MVStore s = builder.open()) { + MVMap map = s.openMap("data"); + for (int i = 0; i < 400; i++) { + map.put(data + i, data); + } + } + long size = FileUtils.size(fileName); + if (level > 0) { + assertTrue(size < lastSize); + } + lastSize = size; + try (MVStore s = new MVStore.Builder().fileName(fileName).open()) { + MVMap map = s.openMap("data"); + for (int i = 0; i < 400; i++) { + assertEquals(data, map.get(data + i)); + } + } + } + } + + private void testFileFormatExample() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = MVStore.open(fileName)) { + MVMap map = s.openMap("data"); + for (int i = 0; i < 400; i++) { + map.put(i, "Hello"); + } + s.commit(); + for (int i = 0; i < 100; i++) { + map.put(0, "Hi"); + } + s.commit(); + } + // ;MVStoreTool.dump(fileName); + } + + private void testMaxChunkLength() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = new MVStore.Builder().fileName(fileName).open()) { + MVMap map = s.openMap("data"); + map.put(0, new byte[2 * 1024 * 1024]); + s.commit(); + map.put(1, new byte[10 * 1024]); + s.commit(); + MVMap layout = s.getLayoutMap(); + Chunk c = Chunk.fromString(layout.get(DataUtils.META_CHUNK + "1")); + assertTrue(c.maxLen < Integer.MAX_VALUE); + assertTrue(c.maxLenLive < Integer.MAX_VALUE); + } + } + + private void testCacheInfo() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = new MVStore.Builder().fileName(fileName).cacheSize(2).open()) { + assertEquals(2, s.getCacheSize()); + MVMap map; + map = s.openMap("data"); + byte[] data = new byte[1024]; + for (int i = 0; i < 1000; i++) { + map.put(i, data); + s.commit(); + if (i < 50) { + assertEquals(0, s.getCacheSizeUsed()); + } else if (i > 300) { + assertTrue(s.getCacheSizeUsed() >= 1); + } + } + } + try (MVStore s = new MVStore.Builder().open()) { + assertEquals(0, s.getCacheSize()); + assertEquals(0, s.getCacheSizeUsed()); + } + } + + private void testVersionsToKeep() { + try (MVStore s = new MVStore.Builder().open()) { + assertEquals(5, s.getVersionsToKeep()); + MVMap map = s.openMap("data"); + for (int i = 0; i < 20; i++) { + map.put(i, i); + s.commit(); + long version = s.getCurrentVersion(); + if (version >= 6) { + map.openVersion(version - 5); + assertThrows(IllegalArgumentException.class, () -> map.openVersion(version - 6)); + } + } + } + } + + private void testVersionsToKeep2() { + try (MVStore s = new MVStore.Builder().autoCommitDisabled().open()) { + s.setVersionsToKeep(2); + final MVMap m = s.openMap("data"); + s.commit(); + assertEquals(1, s.getCurrentVersion()); + m.put(1, "version 1"); + s.commit(); + assertEquals(2, s.getCurrentVersion()); + m.put(1, "version 2"); + s.commit(); + assertEquals(3, s.getCurrentVersion()); + m.put(1, "version 3"); + s.commit(); + m.put(1, "version 4"); + assertEquals("version 4", m.openVersion(4).get(1)); + assertEquals("version 3", m.openVersion(3).get(1)); + assertEquals("version 2", m.openVersion(2).get(1)); + assertThrows(IllegalArgumentException.class, () -> m.openVersion(1)); + } + } + + private void testRemoveMap() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = new MVStore.Builder(). + fileName(fileName). + open()) { + MVMap map = s.openMap("data"); + map.put(1, 1); + assertEquals(1, map.get(1).intValue()); + s.commit(); + + s.removeMap(map); + s.commit(); + + map = s.openMap("data"); + assertTrue(map.isEmpty()); + map.put(2, 2); + } + } + + private void testIsEmpty() { + try (MVStore s = new MVStore.Builder(). + pageSplitSize(50). + open()) { + Map m = s.openMap("data"); + m.put(1, new byte[50]); + m.put(2, new byte[50]); + m.put(3, new byte[50]); + m.remove(1); + m.remove(2); + m.remove(3); + assertEquals(0, m.size()); + assertTrue(m.isEmpty()); + } + } + + private void testOffHeapStorage() { + OffHeapStore offHeap = new OffHeapStore(); + int count = 1000; + try (MVStore s = new MVStore.Builder(). + fileStore(offHeap). + open()) { + Map map = s.openMap("data"); + for (int i = 0; i < count; i++) { + map.put(i, "Hello " + i); + s.commit(); + } + assertTrue(offHeap.getWriteCount() > count); + } + + try (MVStore s = new MVStore.Builder(). + fileStore(offHeap). + open()) { + Map map = s.openMap("data"); + for (int i = 0; i < count; i++) { + assertEquals("Hello " + i, map.get(i)); + } + } + } + + private void testNewerWriteVersion() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + MVStore s = new MVStore.Builder(). + encryptionKey("007".toCharArray()). + fileName(fileName). + open(); + s.setRetentionTime(Integer.MAX_VALUE); + Map header = s.getStoreHeader(); + assertEquals("2", header.get("format").toString()); + header.put("formatRead", "2"); + header.put("format", "3"); + forceWriteStoreHeader(s); + MVMap m = s.openMap("data"); + forceWriteStoreHeader(s); + m.put(0, "Hello World"); + s.close(); + try { + s = new MVStore.Builder(). + encryptionKey("007".toCharArray()). + fileName(fileName). + open(); + header = s.getStoreHeader(); + fail(header.toString()); + } catch (MVStoreException e) { + assertEquals(DataUtils.ERROR_UNSUPPORTED_FORMAT, + e.getErrorCode()); + } + s = new MVStore.Builder(). + encryptionKey("007".toCharArray()). + readOnly(). + fileName(fileName). + open(); + assertTrue(s.getFileStore().isReadOnly()); + m = s.openMap("data"); + assertEquals("Hello World", m.get(0)); + s.close(); + + FileUtils.setReadOnly(fileName); + s = new MVStore.Builder(). + encryptionKey("007".toCharArray()). + fileName(fileName). + open(); + assertTrue(s.getFileStore().isReadOnly()); + m = s.openMap("data"); + assertEquals("Hello World", m.get(0)); + s.close(); + + } + + private void testCompactFully() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + MVStore s = new MVStore.Builder(). + fileName(fileName). + autoCommitDisabled(). + open(); + s.setRetentionTime(0); + s.setVersionsToKeep(0); + MVMap m; + for (int i = 0; i < 100; i++) { + m = s.openMap("data" + i); + m.put(0, "Hello World"); + s.commit(); + } + for (int i = 0; i < 100; i += 2) { + m = s.openMap("data" + i); + s.removeMap(m); + s.commit(); + } + long sizeOld = s.getFileStore().size(); + s.compactMoveChunks(); + s.close(); + long sizeNew = s.getFileStore().size(); + assertTrue("old: " + sizeOld + " new: " + sizeNew, sizeNew < sizeOld); + } + + private void testBackgroundExceptionListener() throws Exception { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + AtomicReference exRef = new AtomicReference<>(); + MVStore s = new MVStore.Builder(). + fileName(fileName). + backgroundExceptionHandler((t, e) -> exRef.set(e)). + open(); + s.setAutoCommitDelay(10); + MVMap m = s.openMap("data"); + s.getFileStore().getFile().close(); + try { + m.put(1, "Hello"); + for (int i = 0; i < 200; i++) { + if (exRef.get() != null) { + break; + } + sleep(10); + } + Throwable e = exRef.get(); + assertNotNull(e); + checkErrorCode(DataUtils.ERROR_WRITING_FAILED, e); + } catch (MVStoreException e) { + // sometimes it is detected right away + assertEquals(DataUtils.ERROR_CLOSED, e.getErrorCode()); + } + + s.closeImmediately(); + FileUtils.delete(fileName); + } + + private void testAtomicOperations() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = new MVStore.Builder(). + fileName(fileName). + open()) { + MVMap m = s.openMap("data"); + + // putIfAbsent + assertNull(m.putIfAbsent(1, new byte[1])); + assertEquals(1, m.putIfAbsent(1, new byte[2]).length); + assertEquals(1, m.get(1).length); + + // replace + assertNull(m.replace(2, new byte[2])); + assertNull(m.get(2)); + assertEquals(1, m.replace(1, new byte[2]).length); + assertEquals(2, m.replace(1, new byte[3]).length); + assertEquals(3, m.replace(1, new byte[1]).length); + + // replace with oldValue + assertFalse(m.replace(1, new byte[2], new byte[10])); + assertTrue(m.replace(1, new byte[1], new byte[2])); + assertTrue(m.replace(1, new byte[2], new byte[1])); + + // remove + assertFalse(m.remove(1, new byte[2])); + assertTrue(m.remove(1, new byte[1])); + } + FileUtils.delete(fileName); + } + + private void testWriteBuffer() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + MVStore s; + MVMap m; + byte[] data = new byte[1000]; + long lastSize = 0; + int len = 1000; + for (int bs = 0; bs <= 1; bs++) { + s = new MVStore.Builder(). + fileName(fileName). + autoCommitBufferSize(bs). + open(); + m = s.openMap("data"); + for (int i = 0; i < len; i++) { + m.put(i, data); + } + long size = s.getFileStore().size(); + assertTrue("last:" + lastSize + " now: " + size, size > lastSize); + lastSize = size; + s.close(); + } + + s = new MVStore.Builder(). + fileName(fileName). + open(); + m = s.openMap("data"); + assertTrue(m.containsKey(1)); + + m.put(-1, data); + s.commit(); + m.put(-2, data); + s.close(); + + s = new MVStore.Builder(). + fileName(fileName). + open(); + m = s.openMap("data"); + assertTrue(m.containsKey(-1)); + assertTrue(m.containsKey(-2)); + + s.close(); + FileUtils.delete(fileName); + } + + private void testWriteDelay() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + MVStore s; + MVMap m; + + FileUtils.delete(fileName); + s = new MVStore.Builder(). + autoCommitDisabled(). + fileName(fileName).open(); + m = s.openMap("data"); + m.put(1, "1"); + s.commit(); + s.close(); + s = new MVStore.Builder(). + autoCommitDisabled(). + fileName(fileName).open(); + m = s.openMap("data"); + assertEquals(1, m.size()); + s.close(); + + FileUtils.delete(fileName); + s = new MVStore.Builder(). + fileName(fileName). + open(); + m = s.openMap("data"); + m.put(1, "Hello"); + m.put(2, "World."); + s.commit(); + s.close(); + + s = new MVStore.Builder(). + fileName(fileName). + open(); + s.setAutoCommitDelay(2); + m = s.openMap("data"); + assertEquals("World.", m.get(2)); + m.put(2, "World"); + s.commit(); + long v = s.getCurrentVersion(); + long time = System.nanoTime(); + m.put(3, "!"); + + for (int i = 200; i > 0; i--) { + if (s.getCurrentVersion() > v) { + break; + } + long diff = System.nanoTime() - time; + if (diff > TimeUnit.SECONDS.toNanos(1)) { + fail("diff=" + TimeUnit.NANOSECONDS.toMillis(diff)); + } + sleep(10); + } + s.closeImmediately(); + + s = new MVStore.Builder(). + fileName(fileName). + open(); + m = s.openMap("data"); + assertEquals("Hello", m.get(1)); + assertEquals("World", m.get(2)); + assertEquals("!", m.get(3)); + s.close(); + + FileUtils.delete(fileName); + } + + private void testEncryptedFile() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + + char[] passwordChars = "007".toCharArray(); + try (MVStore s = new MVStore.Builder().fileName(fileName).encryptionKey(passwordChars).open()) { + assertPasswordErased(passwordChars); + assertTrue(FileUtils.exists(fileName)); + MVMap m = s.openMap("test"); + m.put(1, "Hello"); + assertEquals("Hello", m.get(1)); + } + + char[] passwordChars2 = "008".toCharArray(); + assertThrows(DataUtils.ERROR_FILE_CORRUPT, + () -> new MVStore.Builder().fileName(fileName).encryptionKey(passwordChars2).open()); + assertPasswordErased(passwordChars2); + + passwordChars = "007".toCharArray(); + try (MVStore s = new MVStore.Builder().fileName(fileName).encryptionKey(passwordChars).open()) { + assertPasswordErased(passwordChars); + MVMap m = s.openMap("test"); + assertEquals("Hello", m.get(1)); + } + + FileUtils.setReadOnly(fileName); + passwordChars = "007".toCharArray(); + try (MVStore s = new MVStore.Builder().fileName(fileName).encryptionKey(passwordChars).open()) { + assertTrue(s.getFileStore().isReadOnly()); + } + + FileUtils.delete(fileName); + assertFalse(FileUtils.exists(fileName)); + } + + private void assertPasswordErased(char[] passwordChars) { + assertEquals(0, passwordChars[0]); + assertEquals(0, passwordChars[1]); + assertEquals(0, passwordChars[2]); + } + + private void testFileFormatChange() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = openStore(fileName)) { + s.setRetentionTime(Integer.MAX_VALUE); + MVMap m = s.openMap("test"); + m.put(1, 1); + Map header = s.getStoreHeader(); + int format = Integer.parseInt(header.get("format").toString()); + assertEquals(2, format); + header.put("format", Integer.toString(format + 1)); + forceWriteStoreHeader(s); + } + assertThrows(DataUtils.ERROR_UNSUPPORTED_FORMAT, () -> openStore(fileName).close()); + FileUtils.delete(fileName); + } + + private void testRecreateMap() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = openStore(fileName)) { + MVMap m = s.openMap("test"); + m.put(1, 1); + s.commit(); + s.removeMap(m); + } + try (MVStore s = openStore(fileName)) { + MVMap m = s.openMap("test"); + assertNull(m.get(1)); + } + } + + private void testRenameMapRollback() { + try (MVStore s = openStore(null)) { + MVMap map = s.openMap("hello"); + map.put(1, 10); + long old = s.commit(); + s.renameMap(map, "world"); + map.put(2, 20); + assertEquals("world", map.getName()); + s.rollbackTo(old); + assertEquals("hello", map.getName()); + s.rollbackTo(0); + assertTrue(map.isClosed()); + } + } + + private void testCustomMapType() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = openStore(fileName)) { + Map seq = s.openMap("data", new SequenceMap.Builder()); + StringBuilder buff = new StringBuilder(); + for (long x : seq.keySet()) { + buff.append(x).append(';'); + } + assertEquals("1;2;3;4;5;6;7;8;9;10;", buff.toString()); + } + } + + private void testCacheSize() { + if (config.memory) { + return; + } + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = new MVStore.Builder(). + fileName(fileName). + autoCommitDisabled(). + compress().open()) { + s.setReuseSpace(false); // disable free space scanning + MVMap map = s.openMap("test"); + // add 10 MB of data + for (int i = 0; i < 1024; i++) { + map.put(i, new String(new char[10240])); + } + } + int[] expectedReadsForCacheSize = { + 1880, 490, 476, 501, 476, 476, 541 // compressed +// 1887, 1775, 1599, 1355, 1035, 732, 507 // uncompressed + }; + for (int cacheSize = 0; cacheSize <= 6; cacheSize += 1) { + int cacheMB = 1 + 3 * cacheSize; + Utils.collectGarbage(); + try (MVStore s = new MVStore.Builder(). + fileName(fileName). + autoCommitDisabled(). + cacheSize(cacheMB).open()) { + assertEquals(cacheMB, s.getCacheSize()); + MVMap map = s.openMap("test"); + for (int i = 0; i < 1024; i += 128) { + for (int j = 0; j < i; j++) { + String x = map.get(j); + assertEquals(10240, x.length()); + } + } + long readCount = s.getFileStore().getReadCount(); + int expected = expectedReadsForCacheSize[cacheSize]; + assertTrue("Cache " + cacheMB + "Mb, reads: " + readCount + " expected: " + expected + + " size: " + s.getFileStore().getReadBytes() + + " cache used: " + s.getCacheSizeUsed() + + " cache hits: " + s.getCache().getHits() + + " cache misses: " + s.getCache().getMisses() + + " cache requests: " + (s.getCache().getHits() + s.getCache().getMisses()) + + "", + Math.abs(100 - (100 * expected / readCount)) < 15); + } + } + } + + private void testConcurrentOpen() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = new MVStore.Builder().fileName(fileName).open()) { + assertThrows(MVStoreException.class, () -> new MVStore.Builder().fileName(fileName).open().close()); + assertThrows(MVStoreException.class, + () -> new MVStore.Builder().fileName(fileName).readOnly().open().close()); + assertFalse(s.getFileStore().isReadOnly()); + } + try (MVStore s = new MVStore.Builder().fileName(fileName).readOnly().open()) { + assertTrue(s.getFileStore().isReadOnly()); + } + } + + private void testFileHeader() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = openStore(fileName)) { + s.setRetentionTime(Integer.MAX_VALUE); + long time = System.currentTimeMillis(); + Map m = s.getStoreHeader(); + assertEquals("2", m.get("format").toString()); + long creationTime = (Long) m.get("created"); + assertTrue(Math.abs(time - creationTime) < 100); + m.put("test", "123"); + forceWriteStoreHeader(s); + } + + try (MVStore s = openStore(fileName)) { + Object test = s.getStoreHeader().get("test"); + assertNotNull(test); + assertEquals("123", test.toString()); + } + } + + private static void forceWriteStoreHeader(MVStore s) { + MVMap map = s.openMap("dummy"); + map.put(10, 100); + // this is to ensure the file header is overwritten + // the header is written at least every 20 commits + for (int i = 0; i < 30; i++) { + if (i > 5) { + s.setRetentionTime(0); + // ensure that the next save time is different, + // so that blocks can be reclaimed + // (on Windows, resolution is 10 ms) + sleep(1); + } + map.put(10, 110); + s.commit(); + } + s.removeMap(map); + s.commit(); + } + + private static void sleep(long ms) { + // on Windows, need to sleep in some cases, + // mainly because the milliseconds resolution of + // System.currentTimeMillis is 10 ms. + try { + Thread.sleep(ms); + } catch (InterruptedException e) { + // ignore + } + } + + private void testFileHeaderCorruption() throws Exception { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + MVStore.Builder builder = new MVStore.Builder(). + fileName(fileName).pageSplitSize(1000).autoCommitDisabled(); + try (MVStore s = builder.open()) { + s.setRetentionTime(0); + MVMap map = s.openMap("test"); + map.put(0, new byte[100]); + for (int i = 0; i < 10; i++) { + map = s.openMap("test" + i); + map.put(0, new byte[1000]); + s.commit(); + } + FileStore fs = s.getFileStore(); + long size = fs.getFile().size(); + for (int i = 0; i < 100; i++) { + map = s.openMap("test" + i); + s.removeMap(map); + s.commit(); + s.compact(100, 1); + if (fs.getFile().size() <= size) { + break; + } + } + // the last chunk is at the end + s.setReuseSpace(false); + map = s.openMap("test2"); + map.put(1, new byte[1000]); + } + + FilePath f = FilePath.get(fileName); + int blockSize = 4 * 1024; + // test corrupt file headers + for (int i = 0; i <= blockSize; i += blockSize) { + try (FileChannel fc = f.open("rw")) { + if (i == 0) { + // corrupt the last block (the end header) + fc.write(ByteBuffer.allocate(256), fc.size() - 256); + } + ByteBuffer buff = ByteBuffer.allocate(4 * 1024); + fc.read(buff, i); + String h = new String(buff.array(), StandardCharsets.UTF_8).trim(); + int idx = h.indexOf("fletcher:"); + int old = Character.digit(h.charAt(idx + "fletcher:".length()), 16); + int bad = (old + 1) & 15; + buff.put(idx + "fletcher:".length(), + (byte) Character.forDigit(bad, 16)); + + // now intentionally corrupt first or both headers + // note that headers may be overwritten upon successfull opening + for (int b = 0; b <= i; b += blockSize) { + buff.rewind(); + fc.write(buff, b); + } + } + + if (i == 0) { + // if the first header is corrupt, the second + // header should be used + try (MVStore s = openStore(fileName)) { + MVMap map = s.openMap("test"); + assertEquals(100, map.get(0).length); + map = s.openMap("test2"); + assertFalse(map.containsKey(1)); + } + } else { + // both headers are corrupt + assertThrows(Exception.class, () -> openStore(fileName)); + } + } + } + + private void testIndexSkip() { + MVStore s = openStore(null, 4); + MVMap map = s.openMap("test"); + for (int i = 0; i < 100; i += 2) { + map.put(i, 10 * i); + } + + Cursor c = map.cursor(50); + // skip must reset the root of the cursor + c.skip(10); + for (int i = 70; i < 100; i += 2) { + assertTrue(c.hasNext()); + assertEquals(i, c.next().intValue()); + } + assertFalse(c.hasNext()); + + for (int i = -1; i < 100; i++) { + long index = map.getKeyIndex(i); + if (i < 0 || (i % 2) != 0) { + assertEquals(i < 0 ? -1 : -(i / 2) - 2, index); + } else { + assertEquals(i / 2, index); + } + } + for (int i = -1; i < 60; i++) { + Integer k = map.getKey(i); + if (i < 0 || i >= 50) { + assertNull(k); + } else { + assertEquals(i * 2, k.intValue()); + } + } + // skip + c = map.cursor(0); + assertTrue(c.hasNext()); + assertEquals(0, c.next().intValue()); + c.skip(0); + assertEquals(2, c.next().intValue()); + c.skip(1); + assertEquals(6, c.next().intValue()); + c.skip(20); + assertEquals(48, c.next().intValue()); + + c = map.cursor(0); + c.skip(20); + assertEquals(40, c.next().intValue()); + + c = map.cursor(0); + assertEquals(0, c.next().intValue()); + + assertEquals(12, map.keyList().indexOf(24)); + assertEquals(24, map.keyList().get(12).intValue()); + assertEquals(-14, map.keyList().indexOf(25)); + assertEquals(map.size(), map.keyList().size()); + } + + private void testIndexSkipReverse() { + MVStore s = openStore(null, 4); + MVMap map = s.openMap("test"); + for (int i = 0; i < 100; i += 2) { + map.put(i, 10 * i); + } + + Cursor c = map.cursor(50, null, true); + // skip must reset the root of the cursor + c.skip(10); + for (int i = 30; i >= 0; i -= 2) { + assertTrue(c.hasNext()); + assertEquals(i, c.next().intValue()); + } + assertFalse(c.hasNext()); + } + + private void testMinMaxNextKey() { + try (MVStore s = openStore(null)) { + MVMap map = s.openMap("test"); + map.put(10, 100); + map.put(20, 200); + + assertEquals(10, map.firstKey().intValue()); + assertEquals(20, map.lastKey().intValue()); + + assertEquals(20, map.ceilingKey(15).intValue()); + assertEquals(20, map.ceilingKey(20).intValue()); + assertEquals(10, map.floorKey(15).intValue()); + assertEquals(10, map.floorKey(10).intValue()); + assertEquals(20, map.higherKey(10).intValue()); + assertEquals(10, map.lowerKey(20).intValue()); + + assertEquals(10, map.ceilingKey(null).intValue()); + assertEquals(10, map.higherKey(null).intValue()); + assertNull(map.lowerKey(null)); + assertNull(map.floorKey(null)); + } + + for (int i = 3; i < 20; i++) { + try (MVStore s = openStore(null, 4)) { + MVMap map = s.openMap("test"); + for (int j = 3; j < i; j++) { + map.put(j * 2, j * 20); + } + if (i == 3) { + assertNull(map.firstKey()); + assertNull(map.lastKey()); + } else { + assertEquals(6, map.firstKey().intValue()); + int max = (i - 1) * 2; + assertEquals(max, map.lastKey().intValue()); + + for (int j = 0; j < i * 2 + 2; j++) { + if (j > max) { + assertNull(map.ceilingKey(j)); + } else { + int ceiling = Math.max((j + 1) / 2 * 2, 6); + assertEquals(ceiling, map.ceilingKey(j).intValue()); + } + + int floor = Math.min(max, Math.max(j / 2 * 2, 4)); + if (floor < 6) { + assertNull(map.floorKey(j)); + } else { + map.floorKey(j); + } + + int lower = Math.min(max, Math.max((j - 1) / 2 * 2, 4)); + if (lower < 6) { + assertNull(map.lowerKey(j)); + } else { + assertEquals(lower, map.lowerKey(j).intValue()); + } + + int higher = Math.max((j + 2) / 2 * 2, 6); + if (higher > max) { + assertNull(map.higherKey(j)); + } else { + assertEquals(higher, map.higherKey(j).intValue()); + } + } + } + } + } + } + + private void testStoreVersion() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + MVStore store = MVStore.open(fileName); + assertEquals(0, store.getCurrentVersion()); + assertEquals(0, store.getStoreVersion()); + store.setStoreVersion(0); + store.commit(); + store.setStoreVersion(1); + store.closeImmediately(); + + try (MVStore s = MVStore.open(fileName)) { + assertEquals(1, s.getCurrentVersion()); + assertEquals(0, s.getStoreVersion()); + s.setStoreVersion(1); + } + + try (MVStore s = MVStore.open(fileName)) { + assertEquals(2, s.getCurrentVersion()); + assertEquals(1, s.getStoreVersion()); + } + } + + private void testIterateOldVersion() { + try (MVStore s = new MVStore.Builder().open()) { + Map map = s.openMap("test"); + int len = 100; + for (int i = 0; i < len; i++) { + map.put(i, 10 * i); + } + int count = 0; + MVStore.TxCounter txCounter = s.registerVersionUsage(); + try { + Iterator it = map.keySet().iterator(); + s.commit(); + for (int i = 0; i < len; i += 2) { + map.remove(i); + } + while (it.hasNext()) { + it.next(); + count++; + } + } finally { + s.deregisterVersionUsage(txCounter); + } + assertEquals(len, count); + } + } + + private void testObjects() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = new MVStore.Builder().fileName(fileName).open()) { + Map map = s.openMap("test"); + map.put(1, "Hello"); + map.put("2", 200); + map.put(new Object[1], new Object[]{1, "2"}); + } + + try (MVStore s = new MVStore.Builder().fileName(fileName).open()) { + Map map = s.openMap("test"); + assertEquals("Hello", map.get(1).toString()); + assertEquals(200, ((Integer) map.get("2")).intValue()); + Object[] x = (Object[]) map.get(new Object[1]); + assertEquals(2, x.length); + assertEquals(1, ((Integer) x[0]).intValue()); + assertEquals("2", (String) x[1]); + } + } + + private void testExample() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + + // open the store (in-memory if fileName is null) + try (MVStore s = MVStore.open(fileName)) { + + // create/get the map named "data" + MVMap map = s.openMap("data"); + + // add and read some data + map.put(1, "Hello World"); + // System.out.println(map.get(1)); + } + try (MVStore s = MVStore.open(fileName)) { + MVMap map = s.openMap("data"); + assertEquals("Hello World", map.get(1)); + } + } + + private void testExampleMvcc() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + + // open the store (in-memory if fileName is null) + try (MVStore s = MVStore.open(fileName)) { + + // create/get the map named "data" + MVMap map = s.openMap("data"); + + // add some data + map.put(1, "Hello"); + map.put(2, "World"); + + // get the current version, for later use + long oldVersion = s.getCurrentVersion(); + + // from now on, the old version is read-only + s.commit(); + + // more changes, in the new version + // changes can be rolled back if required + // changes always go into "head" (the newest version) + map.put(1, "Hi"); + map.remove(2); + + // access the old data (before the commit) + MVMap oldMap = + map.openVersion(oldVersion); + + // print the old version (can be done + // concurrently with further modifications) + // this will print "Hello" and "World": + // System.out.println(oldMap.get(1)); + assertEquals("Hello", oldMap.get(1)); + // System.out.println(oldMap.get(2)); + assertEquals("World", oldMap.get(2)); + + // print the newest version ("Hi") + // System.out.println(map.get(1)); + assertEquals("Hi", map.get(1)); + } + } + + private void testOpenStoreCloseLoop() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + for (int k = 0; k < 1; k++) { + // long t = System.nanoTime(); + for (int j = 0; j < 3; j++) { + try (MVStore s = openStore(fileName)) { + Map m = s.openMap("data"); + for (int i = 0; i < 3; i++) { + Integer x = m.get("value"); + m.put("value", x == null ? 0 : x + 1); + s.commit(); + } + } + } + // System.out.println("open/close: " + + // TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t)); + // System.out.println("size: " + FileUtils.size(fileName)); + } + } + + private void testOldVersion() { + for (int op = 0; op <= 1; op++) { + for (int i = 0; i < 5; i++) { + try (MVStore s = openStore(null)) { + s.setVersionsToKeep(Integer.MAX_VALUE); + MVMap m; + m = s.openMap("data"); + for (int j = 0; j < 5; j++) { + if (op == 1) { + m.put("1", "" + s.getCurrentVersion()); + } + s.commit(); + } + for (int j = 0; j < s.getCurrentVersion(); j++) { + MVMap old = m.openVersion(j); + if (op == 1) { + assertEquals("" + j, old.get("1")); + } + } + } + } + } + } + + private void testVersion() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = openStore(fileName)) { + s.setVersionsToKeep(100); + s.setAutoCommitDelay(0); + s.setRetentionTime(Integer.MAX_VALUE); + MVMap m = s.openMap("data"); + s.commit(); + long first = s.getCurrentVersion(); + assertEquals(1, first); + m.put("0", "test"); + s.commit(); + m.put("1", "Hello"); + m.put("2", "World"); + for (int i = 10; i < 20; i++) { + m.put("" + i, "data"); + } + long old = s.getCurrentVersion(); + s.commit(); + m.put("1", "Hallo"); + m.put("2", "Welt"); + MVMap mFirst; + mFirst = m.openVersion(first); + // openVersion() should restore map at last known state of the version specified + // not at the first known state, as it was before + assertEquals(1, mFirst.size()); + MVMap mOld; + assertEquals("Hallo", m.get("1")); + assertEquals("Welt", m.get("2")); + mOld = m.openVersion(old); + assertEquals("Hello", mOld.get("1")); + assertEquals("World", mOld.get("2")); + assertTrue(mOld.isReadOnly()); + long old3 = s.getCurrentVersion(); + assertEquals(3, old3); + s.commit(); + + // the old version is still available + assertEquals("Hello", mOld.get("1")); + assertEquals("World", mOld.get("2")); + + mOld = m.openVersion(old3); + assertEquals("Hallo", mOld.get("1")); + assertEquals("Welt", mOld.get("2")); + + m.put("1", "Hi"); + assertEquals("Welt", m.remove("2")); + } + + try (MVStore s = openStore(fileName)) { + MVMap m = s.openMap("data"); + assertEquals("Hi", m.get("1")); + assertEquals(null, m.get("2")); + assertThrows(IllegalArgumentException.class, () -> m.openVersion(-3)); + } + } + + private void testTruncateFile() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = openStore(fileName)) { + MVMap m = s.openMap("data"); + String data = new String(new char[10000]).replace((char) 0, 'x'); + for (int i = 1; i < 10; i++) { + m.put(i, data); + s.commit(); + } + } + long len = FileUtils.size(fileName); + try (MVStore s = openStore(fileName)) { + s.setRetentionTime(0); + // remove 75% + MVMap m = s.openMap("data"); + for (int i = 0; i < 10; i++) { + if (i % 4 != 0) { + sleep(2); + m.remove(i); + s.commit(); + } + } + assertTrue(s.compact(100, 50 * 1024)); + // compaction alone will not guarantee file size reduction + s.compactMoveChunks(); + } + long len2 = FileUtils.size(fileName); + assertTrue("len2: " + len2 + " len: " + len, len2 < len); + } + + private void testFastDelete() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = openStore(fileName, 700)) { + MVMap m = s.openMap("data"); + for (int i = 0; i < 1000; i++) { + m.put(i, "Hello World"); + assertEquals(i + 1, m.size()); + } + assertEquals(1000, m.size()); + // memory calculations were adjusted, so as this out-of-the-thin-air number + assertEquals(93832, s.getUnsavedMemory()); + s.commit(); + assertEquals(2, s.getFileStore().getWriteCount()); + } + + try (MVStore s = openStore(fileName)) { + MVMap m = s.openMap("data"); + m.clear(); + assertEquals(0, m.size()); + s.commit(); + // ensure only nodes are read, but not leaves + assertEquals(7, s.getFileStore().getReadCount()); + assertTrue(s.getFileStore().getWriteCount() < 5); + } + } + + private void testRollback() { + try (MVStore s = MVStore.open(null)) { + MVMap m = s.openMap("m"); + m.put(1, -1); + s.commit(); + for (int i = 0; i < 10; i++) { + m.put(1, i); + s.rollback(); + assertEquals(i - 1, m.get(1).intValue()); + m.put(1, i); + s.commit(); + } + } + } + + private void testRollbackStored() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + long v2; + try (MVStore s = openStore(fileName)) { + assertEquals(45000, s.getRetentionTime()); + s.setRetentionTime(0); + assertEquals(0, s.getRetentionTime()); + s.setRetentionTime(45000); + assertEquals(45000, s.getRetentionTime()); + assertEquals(0, s.getCurrentVersion()); + assertFalse(s.hasUnsavedChanges()); + MVMap m = s.openMap("data"); + assertTrue(s.hasUnsavedChanges()); + MVMap m0 = s.openMap("data0"); + m.put("1", "Hello"); + assertEquals(1, s.commit()); + s.rollbackTo(1); + assertEquals(1, s.getCurrentVersion()); + assertEquals("Hello", m.get("1")); + // so a new version is created + m.put("1", "Hello"); + + v2 = s.commit(); + assertEquals(2, v2); + assertEquals(2, s.getCurrentVersion()); + assertFalse(s.hasUnsavedChanges()); + assertEquals("Hello", m.get("1")); + } + + try (MVStore s = openStore(fileName)) { + s.setRetentionTime(45000); + assertEquals(2, s.getCurrentVersion()); + MVMap meta = s.getMetaMap(); + MVMap m = s.openMap("data"); + assertFalse(s.hasUnsavedChanges()); + assertEquals("Hello", m.get("1")); + MVMap m0 = s.openMap("data0"); + MVMap m1 = s.openMap("data1"); + m.put("1", "Hallo"); + m0.put("1", "Hallo"); + m1.put("1", "Hallo"); + assertEquals("Hallo", m.get("1")); + assertEquals("Hallo", m1.get("1")); + assertTrue(s.hasUnsavedChanges()); + s.rollbackTo(v2); + assertFalse(s.hasUnsavedChanges()); + assertNull(meta.get(DataUtils.META_NAME + "data1")); + assertNull(m0.get("1")); + assertEquals("Hello", m.get("1")); + // no changes - no real commit here + assertEquals(2, s.commit()); + } + + long v3; + try (MVStore s = openStore(fileName)) { + s.setRetentionTime(45000); + assertEquals(2, s.getCurrentVersion()); + MVMap meta = s.getMetaMap(); + assertNotNull(meta.get(DataUtils.META_NAME + "data")); + assertNotNull(meta.get(DataUtils.META_NAME + "data0")); + assertNull(meta.get(DataUtils.META_NAME + "data1")); + MVMap m = s.openMap("data"); + MVMap m0 = s.openMap("data0"); + assertNull(m0.get("1")); + assertEquals("Hello", m.get("1")); + assertFalse(m0.isReadOnly()); + m.put("1", "Hallo"); + s.commit(); + v3 = s.getCurrentVersion(); + assertEquals(3, v3); + } + + try (MVStore s = openStore(fileName)) { + s.setRetentionTime(45000); + assertEquals(3, s.getCurrentVersion()); + MVMap m = s.openMap("data"); + m.put("1", "Hi"); + } + + try (MVStore s = openStore(fileName)) { + s.setRetentionTime(45000); + MVMap m = s.openMap("data"); + assertEquals("Hi", m.get("1")); + s.rollbackTo(v3); + assertEquals("Hallo", m.get("1")); + } + + try (MVStore s = openStore(fileName)) { + s.setRetentionTime(45000); + MVMap m = s.openMap("data"); + assertEquals("Hallo", m.get("1")); + } + } + + private void testRollbackInMemory() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = openStore(fileName, 5)) { + s.setAutoCommitDelay(0); + assertEquals(0, s.getCurrentVersion()); + MVMap m = s.openMap("data"); + s.rollbackTo(0); + assertTrue(m.isClosed()); + assertEquals(0, s.getCurrentVersion()); + m = s.openMap("data"); + + MVMap m0 = s.openMap("data0"); + MVMap m2 = s.openMap("data2"); + m.put("1", "Hello"); + for (int i = 0; i < 10; i++) { + m2.put("" + i, "Test"); + } + long v1 = s.commit(); + assertEquals(1, v1); + assertEquals(1, s.getCurrentVersion()); + MVMap m1 = s.openMap("data1"); + assertEquals("Test", m2.get("1")); + m.put("1", "Hallo"); + m0.put("1", "Hallo"); + m1.put("1", "Hallo"); + m2.clear(); + assertEquals("Hallo", m.get("1")); + assertEquals("Hallo", m1.get("1")); + s.rollbackTo(v1); + assertEquals(1, s.getCurrentVersion()); + for (int i = 0; i < 10; i++) { + assertEquals("Test", m2.get("" + i)); + } + assertEquals("Hello", m.get("1")); + assertNull(m0.get("1")); + assertTrue(m1.isClosed()); + assertFalse(m0.isReadOnly()); + } + } + + private void testMeta() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = openStore(fileName)) { + s.setRetentionTime(Integer.MAX_VALUE); + MVMap m = s.getMetaMap(); + assertEquals("[]", s.getMapNames().toString()); + MVMap data = s.openMap("data"); + data.put("1", "Hello"); + data.put("2", "World"); + s.commit(); + assertEquals(1, s.getCurrentVersion()); + + assertEquals("[data]", s.getMapNames().toString()); + assertEquals("data", s.getMapName(data.getId())); + assertNull(s.getMapName(s.getMetaMap().getId())); + assertNull(s.getMapName(data.getId() + 1)); + + String id = s.getMetaMap().get(DataUtils.META_NAME + "data"); + assertEquals("name:data", m.get(DataUtils.META_MAP + id)); + assertEquals("Hello", data.put("1", "Hallo")); + s.commit(); + assertEquals("name:data", m.get(DataUtils.META_MAP + id)); + m = s.getLayoutMap(); + assertTrue(m.get(DataUtils.META_ROOT + id).length() > 0); + assertTrue(m.containsKey(DataUtils.META_CHUNK + "1")); + + assertEquals(2, s.getCurrentVersion()); + + s.rollbackTo(1); + assertEquals("Hello", data.get("1")); + assertEquals("World", data.get("2")); + } + } + + private void testInMemory() { + for (int j = 0; j < 1; j++) { + try (MVStore s = openStore(null)) { + // s.setMaxPageSize(10); + int len = 100; + // TreeMap m = new TreeMap(); + // HashMap m = New.hashMap(); + MVMap m = s.openMap("data"); + for (int i = 0; i < len; i++) { + assertNull(m.put(i, "Hello World")); + } + for (int i = 0; i < len; i++) { + assertEquals("Hello World", m.get(i)); + } + for (int i = 0; i < len; i++) { + assertEquals("Hello World", m.remove(i)); + } + assertEquals(null, m.get(0)); + assertEquals(0, m.size()); + } + } + } + + private void testLargeImport() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + int len = 1000; + for (int j = 0; j < 5; j++) { + FileUtils.delete(fileName); + try (MVStore s = openStore(fileName, 40)) { + MVMap m = s.openMap("data", + new MVMap.Builder() + .valueType(new RowDataType(new DataType[]{ + new ObjectDataType(), + StringDataType.INSTANCE, + StringDataType.INSTANCE}))); + + // Profiler prof = new Profiler(); + // prof.startCollecting(); + // long t = System.nanoTime(); + for (int i = 0; i < len; ) { + Object[] o = new Object[3]; + o[0] = i; + o[1] = "Hello World"; + o[2] = "World"; + m.put(i, o); + i++; + if (i % 10000 == 0) { + s.commit(); + } + } + } + // System.out.println(prof.getTop(5)); + // System.out.println("store time " + + // TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t)); + // System.out.println("store size " + + // FileUtils.size(fileName)); + } + } + + private void testBtreeStore() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + MVStore store = openStore(fileName); + store.close(); + + int count = 2000; + try (MVStore s = openStore(fileName)) { + MVMap m = s.openMap("data"); + for (int i = 0; i < count; i++) { + assertNull(m.put(i, "hello " + i)); + assertEquals("hello " + i, m.get(i)); + } + s.commit(); + assertEquals("hello 0", m.remove(0)); + assertNull(m.get(0)); + for (int i = 1; i < count; i++) { + assertEquals("hello " + i, m.get(i)); + } + } + + try (MVStore s = openStore(fileName)) { + MVMap m = s.openMap("data"); + assertNull(m.get(0)); + for (int i = 1; i < count; i++) { + assertEquals("hello " + i, m.get(i)); + } + for (int i = 1; i < count; i++) { + m.remove(i); + } + s.commit(); + assertNull(m.get(0)); + for (int i = 0; i < count; i++) { + assertNull(m.get(i)); + } + } + } + + private void testCompactMapNotOpen() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + int factor = 100; + try (MVStore s = openStore(fileName, 1000)) { + s.setAutoCommitDelay(0); + MVMap m = s.openMap("data"); + for (int j = 0; j < 10; j++) { + for (int i = j * factor; i < 10 * factor; i++) { + m.put(i, "Hello" + j); + } + s.commit(); + } + } + + try (MVStore s = openStore(fileName)) { + s.setAutoCommitDelay(0); + s.setRetentionTime(0); + + Map layout = s.getLayoutMap(); + int chunkCount1 = getChunkCount(layout); + s.compact(80, 1); + s.compact(80, 1); + + int chunkCount2 = getChunkCount(layout); + assertTrue(chunkCount2 >= chunkCount1); + + MVMap m = s.openMap("data"); + for (int i = 0; i < 10; i++) { + sleep(1); + boolean result = s.compact(50, 50 * 1024); + s.commit(); + if (!result) { + break; + } + } + assertFalse(s.compact(50, 1024)); + + int chunkCount3 = getChunkCount(layout); + + assertTrue(chunkCount1 + ">" + chunkCount2 + ">" + chunkCount3, + chunkCount3 < chunkCount1); + + for (int i = 0; i < 10 * factor; i++) { + assertEquals("x" + i, "Hello" + (i / factor), m.get(i)); + } + } + } + + private static int getChunkCount(Map layout) { + int chunkCount = 0; + for (String k : layout.keySet()) { + if (k.startsWith(DataUtils.META_CHUNK)) { + chunkCount++; + } + } + return chunkCount; + } + + private void testCompact() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + long initialLength = 0; + for (int j = 0; j < 20; j++) { + sleep(2); + try (MVStore s = openStore(fileName)) { + s.setRetentionTime(0); + s.setVersionsToKeep(0); + MVMap m = s.openMap("data"); + for (int i = 0; i < 100; i++) { + m.put(j + i, "Hello " + j); + } + trace("Before - fill rate: " + s.getFillRate() + "%, chunks fill rate: " + + s.getChunksFillRate() + ", len: " + FileUtils.size(fileName)); + s.compact(80, 2048); + s.compactMoveChunks(); + trace("After - fill rate: " + s.getFillRate() + "%, chunks fill rate: " + + s.getChunksFillRate() + ", len: " + FileUtils.size(fileName)); + } + long len = FileUtils.size(fileName); + // System.out.println(" len:" + len); + if (initialLength == 0) { + initialLength = len; + } else { + assertTrue("initial: " + initialLength + " len: " + len, + len <= initialLength * 3); + } + } + // long len = FileUtils.size(fileName); + // System.out.println("len0: " + len); + try (MVStore s = openStore(fileName)) { + MVMap m = s.openMap("data"); + for (int i = 0; i < 100; i++) { + m.remove(i); + } + s.compact(80, 1024); + } + + // len = FileUtils.size(fileName); + // System.out.println("len1: " + len); + try (MVStore s = openStore(fileName)) { + MVMap m = s.openMap("data"); + s.compact(80, 1024); + } + // len = FileUtils.size(fileName); + // System.out.println("len2: " + len); + } + + private void testReuseSpace() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + long initialLength = 0; + for (int j = 0; j < 20; j++) { + sleep(2); + try (MVStore s = openStore(fileName)) { + s.setRetentionTime(0); + s.setVersionsToKeep(0); + MVMap m = s.openMap("data"); + for (int i = 0; i < 10; i++) { + m.put(i, "Hello"); + } + s.commit(); + for (int i = 0; i < 10; i++) { + assertEquals("Hello", m.get(i)); + assertEquals("Hello", m.remove(i)); + } + } + long len = FileUtils.size(fileName); + if (initialLength == 0) { + initialLength = len; + } else { + assertTrue("len: " + len + " initial: " + initialLength + " j: " + j, + len <= initialLength * 3); + } + } + } + + private void testRandom() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = openStore(fileName)) { + MVMap m = s.openMap("data"); + TreeMap map = new TreeMap<>(); + Random r = new Random(1); + int operationCount = 1000; + int maxValue = 30; + Integer expected, got; + for (int i = 0; i < operationCount; i++) { + int k = r.nextInt(maxValue); + int v = r.nextInt(); + boolean compareAll; + switch (r.nextInt(3)) { + case 0: + log(i + ": put " + k + " = " + v); + expected = map.put(k, v); + got = m.put(k, v); + if (expected == null) { + assertNull(got); + } else { + assertEquals(expected, got); + } + compareAll = true; + break; + case 1: + log(i + ": remove " + k); + expected = map.remove(k); + got = m.remove(k); + if (expected == null) { + assertNull(got); + } else { + assertEquals(expected, got); + } + compareAll = true; + break; + default: + Integer a = map.get(k); + Integer b = m.get(k); + if (a == null || b == null) { + assertTrue(a == b); + } else { + assertEquals(a.intValue(), b.intValue()); + } + compareAll = false; + break; + } + if (compareAll) { + Iterator it = m.keyIterator(null); + for (Integer integer : map.keySet()) { + assertTrue(it.hasNext()); + expected = integer; + got = it.next(); + assertEquals(expected, got); + } + assertFalse(it.hasNext()); + } + } + } + } + + private void testKeyValueClasses() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = openStore(fileName)) { + MVMap is = s.openMap("intString"); + is.put(1, "Hello"); + MVMap ii = s.openMap("intInt"); + ii.put(1, 10); + MVMap si = s.openMap("stringInt"); + si.put("Test", 10); + MVMap ss = s.openMap("stringString"); + ss.put("Hello", "World"); + } + + try (MVStore s = openStore(fileName)) { + MVMap is = s.openMap("intString"); + assertEquals("Hello", is.get(1)); + MVMap ii = s.openMap("intInt"); + assertEquals(10, ii.get(1).intValue()); + MVMap si = s.openMap("stringInt"); + assertEquals(10, si.get("Test").intValue()); + MVMap ss = s.openMap("stringString"); + assertEquals("World", ss.get("Hello")); + } + } + + private void testIterate() { + int size = config.big ? 1000 : 10; + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = openStore(fileName)) { + MVMap m = s.openMap("data"); + Iterator it = m.keyIterator(null); + assertFalse(it.hasNext()); + for (int i = 0; i < size; i++) { + m.put(i, "hello " + i); + } + s.commit(); + it = m.keyIterator(null); + it.next(); + assertThrows(UnsupportedOperationException.class, it).remove(); + + it = m.keyIterator(null); + for (int i = 0; i < size; i++) { + assertTrue(it.hasNext()); + assertEquals(i, it.next().intValue()); + } + assertFalse(it.hasNext()); + assertThrows(NoSuchElementException.class, it).next(); + for (int j = 0; j < size; j++) { + it = m.keyIterator(j); + for (int i = j; i < size; i++) { + assertTrue(it.hasNext()); + assertEquals(i, it.next().intValue()); + } + assertFalse(it.hasNext()); + } + } + } + + private void testIterateReverse() { + int size = config.big ? 1000 : 10; + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = openStore(fileName)) { + MVMap m = s.openMap("data"); + for (int i = 0; i < size; i++) { + m.put(i, "hello " + i); + } + s.commit(); + Iterator it = m.keyIteratorReverse(null); + it.next(); + assertThrows(UnsupportedOperationException.class, it).remove(); + + it = m.keyIteratorReverse(null); + for (int i = size - 1; i >= 0; i--) { + assertTrue(it.hasNext()); + assertEquals(i, it.next().intValue()); + } + assertFalse(it.hasNext()); + assertThrows(NoSuchElementException.class, it).next(); + for (int j = 0; j < size; j++) { + it = m.keyIteratorReverse(j); + for (int i = j; i >= 0; i--) { + assertTrue(it.hasNext()); + assertEquals(i, it.next().intValue()); + } + assertFalse(it.hasNext()); + } + } + } + + private void testCloseTwice() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + MVStore s = openStore(fileName); + MVMap m = s.openMap("data"); + for (int i = 0; i < 3; i++) { + m.put(i, "hello " + i); + } + // closing twice should be fine + s.close(); + s.close(); + } + + private void testSimple() { + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = openStore(fileName)) { + MVMap m = s.openMap("data"); + for (int i = 0; i < 3; i++) { + m.put(i, "hello " + i); + } + s.commit(); + assertEquals("hello 0", m.remove(0)); + + assertNull(m.get(0)); + for (int i = 1; i < 3; i++) { + assertEquals("hello " + i, m.get(i)); + } + } + + try (MVStore s = openStore(fileName)) { + MVMap m = s.openMap("data"); + assertNull(m.get(0)); + for (int i = 1; i < 3; i++) { + assertEquals("hello " + i, m.get(i)); + } + } + } + + private void testInvalidSettings() { + assertThrows(IllegalArgumentException.class, + () -> new MVStore.Builder().fileName("test").fileStore(new OffHeapStore()).open()); + } + + private void testLargerThan2G() { + if (!config.big) { + return; + } + String fileName = getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + MVStore store = new MVStore.Builder().cacheSize(16). + fileName(fileName).open(); + try { + MVMap map = store.openMap("test"); + long last = System.nanoTime(); + String data = new String(new char[2500]).replace((char) 0, 'x'); + for (int i = 0;; i++) { + map.put(i, data); + if (i % 10000 == 0) { + store.commit(); + long time = System.nanoTime(); + if (time - last > TimeUnit.SECONDS.toNanos(2)) { + long mb = store.getFileStore().size() / 1024 / 1024; + trace(mb + "/4500"); + if (mb > 4500) { + break; + } + last = time; + } + } + } + store.commit(); + store.close(); + } finally { + store.closeImmediately(); + } + FileUtils.delete(fileName); + } + + /** + * Open a store for the given file name, using a small page size. + * + * @param fileName the file name (null for in-memory) + * @return the store + */ + protected static MVStore openStore(String fileName) { + return openStore(fileName, 1000); + } + + /** + * Open a store for the given file name, using a small page size. + * + * @param fileName the file name (null for in-memory) + * @param pageSplitSize the page split size + * @return the store + */ + protected static MVStore openStore(String fileName, int pageSplitSize) { + MVStore store = new MVStore.Builder(). + fileName(fileName).pageSplitSize(pageSplitSize).open(); + return store; + } + + /** + * Log the message. + * + * @param msg the message + */ + @SuppressWarnings("unused") + protected static void log(String msg) { + // System.out.println(msg); + } + +} diff --git a/h2/src/test/org/h2/test/store/TestMVStoreBenchmark.java b/h2/src/test/org/h2/test/store/TestMVStoreBenchmark.java new file mode 100644 index 0000000..fc587d2 --- /dev/null +++ b/h2/src/test/org/h2/test/store/TestMVStoreBenchmark.java @@ -0,0 +1,194 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.TimeUnit; + +import org.h2.mvstore.MVStore; +import org.h2.test.TestBase; + +/** + * Tests the performance and memory usage claims in the documentation. + */ +public class TestMVStoreBenchmark extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); + test.config.traceTest = true; + test.config.big = true; + test.test(); + } + + @Override + public boolean isEnabled() { + if (!config.big) { + return false; + } + if (config.codeCoverage) { + // run only when _not_ using a code coverage tool, + // because the tool might instrument our code but not + // java.util.* + return false; + } + return true; + } + + @Override + public void test() throws Exception { + testPerformanceComparison(); + testMemoryUsageComparison(); + } + + private void testMemoryUsageComparison() { + long[] mem; + long hash, tree, mv; + String msg; + + mem = getMemoryUsed(10000, 10); + hash = mem[0]; + tree = mem[1]; + mv = mem[2]; + msg = Arrays.toString(mem); + assertTrue(msg, hash < mv); + assertTrue(msg, tree < mv); + + mem = getMemoryUsed(10000, 30); + hash = mem[0]; + tree = mem[1]; + mv = mem[2]; + msg = Arrays.toString(mem); + assertTrue(msg, mv < hash); + assertTrue(msg, mv < tree); + + } + + private long[] getMemoryUsed(int count, int size) { + long hash, tree, mv; + ArrayList> mapList; + long mem; + + mapList = new ArrayList<>(count); + mem = getMemory(); + for (int i = 0; i < count; i++) { + mapList.add(new ConcurrentHashMap(size)); + } + addEntries(mapList, size); + hash = getMemory() - mem; + mapList.size(); + + mapList.clear(); + mem = getMemory(); + for (int i = 0; i < count; i++) { + mapList.add(new ConcurrentSkipListMap()); + } + addEntries(mapList, size); + tree = getMemory() - mem; + mapList.size(); + + mapList.clear(); + mem = getMemory(); + MVStore store = MVStore.open(null); + for (int i = 0; i < count; i++) { + Map map = store.openMap("t" + i); + mapList.add(map); + } + addEntries(mapList, size); + mv = getMemory() - mem; + mapList.size(); + + trace("hash: " + hash / 1024 / 1024 + " mb"); + trace("tree: " + tree / 1024 / 1024 + " mb"); + trace("mv: " + mv / 1024 / 1024 + " mb"); + + return new long[]{hash, tree, mv}; + } + + private static void addEntries(List> mapList, int size) { + for (Map map : mapList) { + for (int i = 0; i < size; i++) { + map.put(i, "Hello World"); + } + } + } + + static long getMemory() { + for (int i = 0; i < 16; i++) { + System.gc(); + try { + Thread.sleep(10); + } catch (InterruptedException e) { + // ignore + } + } + return getMemoryUsedBytes(); + } + + private void testPerformanceComparison() { + if (!config.big) { + return; + } + // -mx12g -agentlib:hprof=heap=sites,depth=8 + // int size = 1000; + int size = 1000000; + long hash = 0, tree = 0, mv = 0; + for (int i = 0; i < 5; i++) { + Map map; + MVStore store = MVStore.open(null); + map = store.openMap("test"); + mv = testPerformance(map, size); + store.close(); + map = new ConcurrentHashMap<>(size); + hash = testPerformance(map, size); + map = new ConcurrentSkipListMap<>(); + tree = testPerformance(map, size); + if (hash < tree && mv < tree * 1.5) { + break; + } + } + String msg = "mv " + mv + " tree " + tree + " hash " + hash; + assertTrue(msg, hash < tree); + // assertTrue(msg, hash < mv); + assertTrue(msg, mv < tree * 2); + } + + private long testPerformance(Map map, int size) { + System.gc(); + long time = 0; + for (int t = 0; t < 3; t++) { + time = System.nanoTime(); + for (int b = 0; b < 3; b++) { + for (int i = 0; i < size; i++) { + map.put(i, "Hello World"); + } + for (int a = 0; a < 5; a++) { + for (int i = 0; i < size; i++) { + String x = map.get(i); + assertNotNull(x); + } + } + for (int i = 0; i < size; i++) { + map.remove(i); + } + assertEquals(0, map.size()); + } + time = System.nanoTime() - time; + } + trace(map.getClass().getName() + ": " + TimeUnit.NANOSECONDS.toMillis(time)); + return time; + } + +} diff --git a/h2/src/test/org/h2/test/store/TestMVStoreCachePerformance.java b/h2/src/test/org/h2/test/store/TestMVStoreCachePerformance.java new file mode 100644 index 0000000..1576724 --- /dev/null +++ b/h2/src/test/org/h2/test/store/TestMVStoreCachePerformance.java @@ -0,0 +1,97 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; + +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVStore; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.util.Task; + +/** + * Tests the MVStore cache. + */ +public class TestMVStoreCachePerformance extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); + test.test(); + } + + @Override + public void test() throws Exception { + testCache(1, ""); + testCache(1, "cache:"); + testCache(10, ""); + testCache(10, "cache:"); + testCache(100, ""); + testCache(100, "cache:"); + } + + private void testCache(int threadCount, String fileNamePrefix) { + String fileName = getBaseDir() + "/" + getTestName(); + fileName = fileNamePrefix + fileName; + FileUtils.delete(fileName); + MVStore store = new MVStore.Builder(). + fileName(fileName). + // cacheSize(1024). + open(); + final MVMap map = store.openMap("test"); + final AtomicInteger counter = new AtomicInteger(); + byte[] data = new byte[8 * 1024]; + final int count = 10000; + for (int i = 0; i < count; i++) { + map.put(i, data); + store.commit(); + if (i % 1000 == 0) { + // System.out.println("add " + i); + } + } + Task[] tasks = new Task[threadCount]; + for (int i = 0; i < threadCount; i++) { + tasks[i] = new Task() { + + @Override + public void call() throws Exception { + Random r = new Random(); + do { + int id = r.nextInt(count); + map.get(id); + counter.incrementAndGet(); + } while (!stop); + } + + }; + tasks[i].execute(); + } + for (int i = 0; i < 4; i++) { + // Profiler prof = new Profiler().startCollecting(); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // ignore + } + // System.out.println(prof.getTop(5)); + // System.out.println(" " + counter.get() / (i + 1) + " op/s"); + } + // long time = System.nanoTime(); + for (Task t : tasks) { + t.get(); + } + store.close(); + System.out.println(counter.get() / 10000 + " ops/ms; " + + threadCount + " thread(s); " + fileNamePrefix); + } + +} diff --git a/h2/src/test/org/h2/test/store/TestMVStoreConcurrent.java b/h2/src/test/org/h2/test/store/TestMVStoreConcurrent.java new file mode 100644 index 0000000..e05fcb8 --- /dev/null +++ b/h2/src/test/org/h2/test/store/TestMVStoreConcurrent.java @@ -0,0 +1,804 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; +import org.h2.mvstore.Chunk; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVStore; +import org.h2.mvstore.MVStoreException; +import org.h2.mvstore.WriteBuffer; +import org.h2.mvstore.type.ObjectDataType; +import org.h2.store.fs.FileChannelInputStream; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.util.Task; + +/** + * Tests concurrently accessing a tree map store. + */ +public class TestMVStoreConcurrent extends TestMVStore { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + FileUtils.createDirectories(getBaseDir()); + testInterruptReopenAsync(); + testInterruptReopenRetryNIO(); + testConcurrentSaveCompact(); + testConcurrentDataType(); + testConcurrentAutoCommitAndChange(); + testConcurrentReplaceAndRead(); + testConcurrentChangeAndCompact(); + testConcurrentChangeAndGetVersion(); + testConcurrentFree(); + testConcurrentStoreAndRemoveMap(); + testConcurrentStoreAndClose(); + testConcurrentOnlineBackup(); + testConcurrentMap(); + testConcurrentIterate(); + testConcurrentWrite(); + testConcurrentRead(); + } + + private void testInterruptReopenAsync() { + testInterruptReopen("async:"); + } + + private void testInterruptReopenRetryNIO() { + testInterruptReopen("retry:"); + } + + private void testInterruptReopen(String prefix) { + String fileName = prefix + getBaseDir() + "/" + getTestName(); + FileUtils.delete(fileName); + final MVStore s = new MVStore.Builder(). + fileName(fileName). + cacheSize(0). + open(); + final Thread mainThread = Thread.currentThread(); + Task task = new Task() { + @Override + public void call() throws Exception { + while (!stop) { + mainThread.interrupt(); + Thread.sleep(10); + } + } + }; + try { + MVMap map = s.openMap("data"); + task.execute(); + for (int i = 0; i < 1000 && !task.isFinished(); i++) { + map.get(i % 1000); + map.put(i % 1000, new byte[1024]); + s.commit(); + } + } finally { + task.get(); + s.close(); + } + } + + private void testConcurrentSaveCompact() { + String fileName = "memFS:" + getTestName(); + FileUtils.delete(fileName); + MVStore.Builder builder = new MVStore.Builder(). + fileName(fileName). + cacheSize(0); + try (final MVStore s = builder.open()) { + s.setRetentionTime(0); + final MVMap dataMap = s.openMap("data"); + Task task = new Task() { + @Override + public void call() { + int i = 0; + while (!stop) { + s.compact(100, 1024 * 1024); + MVStore.TxCounter token = s.registerVersionUsage(); + try { + dataMap.put(i % 1000, i * 10); + } finally { + s.deregisterVersionUsage(token); + } + s.commit(); + i++; + } + } + }; + task.execute(); + for (int i = 0; i < 1000 && !task.isFinished(); i++) { + s.compact(100, 1024 * 1024); + MVStore.TxCounter token = s.registerVersionUsage(); + try { + dataMap.put(i % 1000, i * 10); + } finally { + s.deregisterVersionUsage(token); + } + s.commit(); + } + task.get(); + } + } + + private void testConcurrentDataType() throws InterruptedException { + final ObjectDataType type = new ObjectDataType(); + final Object[] data = new Object[]{ + null, + -1, + 1, + 10, + "Hello", + new Object[]{ new byte[]{(byte) -1, (byte) 1}, null}, + new Object[]{ new byte[]{(byte) 1, (byte) -1}, 10}, + new Object[]{ new byte[]{(byte) -1, (byte) 1}, 20L}, + new Object[]{ new byte[]{(byte) 1, (byte) -1}, 5}, + }; + Arrays.sort(data, type::compare); + Task[] tasks = new Task[2]; + for (int i = 0; i < tasks.length; i++) { + tasks[i] = new Task() { + @Override + public void call() { + Random r = new Random(); + WriteBuffer buff = new WriteBuffer(); + while (!stop) { + int a = r.nextInt(data.length); + int b = r.nextInt(data.length); + int comp; + if (r.nextBoolean()) { + comp = type.compare(a, b); + } else { + comp = -type.compare(b, a); + } + buff.clear(); + type.write(buff, a); + buff.clear(); + type.write(buff, b); + if (a == b) { + assertEquals(0, comp); + } else { + assertEquals(a > b ? 1 : -1, comp); + } + } + } + }; + tasks[i].execute(); + } + try { + Thread.sleep(100); + } finally { + for (Task t : tasks) { + t.get(); + } + } + } + + private void testConcurrentAutoCommitAndChange() throws InterruptedException { + String fileName = "memFS:" + getTestName(); + FileUtils.delete(fileName); + MVStore.Builder builder = new MVStore.Builder() + .fileName(fileName) + .pageSplitSize(1000); + try (MVStore s = builder.open()) { + s.setRetentionTime(1000); + s.setAutoCommitDelay(1); + final CountDownLatch latch = new CountDownLatch(2); + Task task = new Task() { + @Override + public void call() { + latch.countDown(); + while (!stop) { + s.compact(100, 1024 * 1024); + } + } + }; + final MVMap dataMap = s.openMap("data"); + final MVMap dataSmallMap = s.openMap("dataSmall"); + s.openMap("emptyMap"); + final AtomicInteger counter = new AtomicInteger(); + Task task2 = new Task() { + @Override + public void call() { + latch.countDown(); + while (!stop) { + int i = counter.getAndIncrement(); + dataMap.put(i, i * 10); + dataSmallMap.put(i % 100, i * 10); + if (i % 100 == 0) { + dataSmallMap.clear(); + } + } + } + }; + task.execute(); + task2.execute(); + latch.await(); + for (int i = 0; !task.isFinished() && !task2.isFinished() && i < 1000; i++) { + MVMap map = s.openMap("d" + (i % 3)); + map.put(0, i); + s.commit(); + } + task.get(); + task2.get(); + for (int i = 0; i < counter.get(); i++) { + assertEquals(10 * i, dataMap.get(i).intValue()); + } + } + } + + private void testConcurrentReplaceAndRead() throws InterruptedException { + final MVStore s = new MVStore.Builder().open(); + final MVMap map = s.openMap("data"); + for (int i = 0; i < 100; i++) { + map.put(i, i % 100); + } + Task task = new Task() { + @Override + public void call() { + int i = 0; + while (!stop) { + map.put(i % 100, i % 100); + i++; + if (i % 1000 == 0) { + s.commit(); + } + } + } + }; + task.execute(); + try { + Thread.sleep(1); + for (int i = 0; !task.isFinished() && i < 1000000; i++) { + assertEquals(i % 100, map.get(i % 100).intValue()); + } + } finally { + task.get(); + } + s.close(); + } + + private void testConcurrentChangeAndCompact() throws InterruptedException { + String fileName = "memFS:" + getTestName(); + FileUtils.delete(fileName); + final MVStore s = new MVStore.Builder().fileName( + fileName). + pageSplitSize(10). + autoCommitDisabled().open(); + s.setRetentionTime(10000); + try { + Task task = new Task() { + @Override + public void call() { + while (!stop) { + s.compact(100, 1024 * 1024); + } + } + }; + task.execute(); + Task task2 = new Task() { + @Override + public void call() { + while (!stop) { + s.compact(100, 1024 * 1024); + } + } + }; + task2.execute(); + Thread.sleep(1); + for (int i = 0; !task.isFinished() && !task2.isFinished() && i < 1000; i++) { + MVMap map = s.openMap("d" + (i % 3)); + // MVMap map = s.openMap("d" + (i % 3), + // new MVMapConcurrent.Builder()); + map.put(0, i); + map.get(0); + s.commit(); + } + task.get(); + task2.get(); + } finally { + s.close(); + } + } + + private static void testConcurrentChangeAndGetVersion() throws InterruptedException { + for (int test = 0; test < 10; test++) { + try (final MVStore s = new MVStore.Builder().autoCommitDisabled().open()) { + s.setVersionsToKeep(10); + final MVMap m = s.openMap("data"); + m.put(1, 1); + Task task = new Task() { + @Override + public void call() { + while (!stop) { + m.put(1, 1); + s.commit(); + } + } + }; + task.execute(); + Thread.sleep(1); + for (int i = 0; i < 10000; i++) { + if (task.isFinished()) { + break; + } + for (int j = 0; j < 20; j++) { + m.put(1, 1); + s.commit(); + } + s.setVersionsToKeep(15); + long version = s.getCurrentVersion() - 1; + try { + m.openVersion(version); + } catch (IllegalArgumentException e) { + // ignore + } + s.setVersionsToKeep(20); + } + task.get(); + s.commit(); + } + } + } + + private void testConcurrentFree() throws InterruptedException { + String fileName = "memFS:" + getTestName(); + for (int test = 0; test < 10; test++) { + FileUtils.delete(fileName); + final MVStore s1 = new MVStore.Builder(). + fileName(fileName).autoCommitDisabled().open(); + s1.setRetentionTime(0); + final int count = 200; + for (int i = 0; i < count; i++) { + MVMap m = s1.openMap("d" + i); + m.put(1, 1); + if (i % 2 == 0) { + s1.commit(); + } + } + s1.close(); + MVStore.Builder builder = new MVStore.Builder(). + fileName(fileName).autoCommitDisabled(); + try (final MVStore s = builder.open()) { + s.setRetentionTime(0); + s.setVersionsToKeep(0); + final ArrayList> list = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + MVMap m = s.openMap("d" + i); + list.add(m); + } + + final AtomicInteger counter = new AtomicInteger(); + Task task = new Task() { + @Override + public void call() { + while (!stop) { + int x = counter.getAndIncrement(); + if (x >= count) { + break; + } + MVMap m = list.get(x); + m.clear(); + s.removeMap(m); + } + } + }; + task.execute(); + Thread.sleep(1); + while (true) { + int x = counter.getAndIncrement(); + if (x >= count) { + break; + } + MVMap m = list.get(x); + m.clear(); + s.removeMap(m); + if (x % 5 == 0) { + s.commit(); + } + } + task.get(); + // this will mark old chunks as unused, + // but not remove (and overwrite) them yet + MVMap m = s.openMap("dummy"); + m.put(0, 0); + s.commit(); + // this will remove them, so we end up with + // one unused one, and one active one + m.put(1, 1); + s.commit(); + m.put(2, 2); + s.commit(); + + MVMap layoutMap = s.getLayoutMap(); + int chunkCount = 0; + for (String k : layoutMap.keyList()) { + if (k.startsWith(DataUtils.META_CHUNK)) { + // dead chunks may stay around for a little while + // discount them + Chunk chunk = Chunk.fromString(layoutMap.get(k)); + if (chunk.maxLenLive > 0) { + chunkCount++; + } + } + } + assertTrue("" + chunkCount, chunkCount < 3); + } + } + } + + private void testConcurrentStoreAndRemoveMap() throws InterruptedException { + String fileName = "memFS:" + getTestName(); + FileUtils.delete(fileName); + try (MVStore s = openStore(fileName)) { + int count = 200; + for (int i = 0; i < count; i++) { + MVMap m = s.openMap("d" + i); + m.put(1, 1); + } + final AtomicInteger counter = new AtomicInteger(); + Task task = new Task() { + @Override + public void call() { + while (!stop) { + counter.incrementAndGet(); + s.commit(); + } + } + }; + task.execute(); + Thread.sleep(1); + for (int i = 0; i < count || counter.get() < count; i++) { + MVMap m = s.openMap("d" + i); + m.put(1, 10); + s.removeMap(m); + if (task.isFinished()) { + break; + } + } + task.get(); + } + } + + private void testConcurrentStoreAndClose() throws InterruptedException { + String fileName = "memFS:" + getTestName(); + for (int i = 0; i < 10; i++) { + FileUtils.delete(fileName); + try (MVStore s = openStore(fileName)) { + final AtomicInteger counter = new AtomicInteger(); + Task task = new Task() { + @Override + public void call() { + while (!stop) { + s.setStoreVersion(counter.incrementAndGet()); + s.commit(); + } + } + }; + task.execute(); + while (counter.get() < 5) { + Thread.sleep(1); + } + try { + s.close(); + // sometimes closing works, in which case + // storing must fail at some point (not necessarily + // immediately) + for (int x = counter.get(), y = x + 2; x <= y; x++) { + Thread.sleep(1); + } + Exception e = task.getException(); + if (e != null) { + checkErrorCode(DataUtils.ERROR_CLOSED, e); + } + } catch (MVStoreException e) { + // sometimes storing works, in which case + // closing must fail + assertEquals(DataUtils.ERROR_WRITING_FAILED, e.getErrorCode()); + task.get(); + } + } + } + } + + /** + * Test the concurrent map implementation. + */ + private static void testConcurrentMap() throws InterruptedException { + try (MVStore s = openStore(null)) { + final MVMap m = s.openMap("data"); + final int size = 20; + final Random rand = new Random(1); + Task task = new Task() { + @Override + public void call() { + try { + while (!stop) { + if (rand.nextBoolean()) { + m.put(rand.nextInt(size), 1); + } else { + m.remove(rand.nextInt(size)); + } + m.get(rand.nextInt(size)); + m.firstKey(); + m.lastKey(); + m.ceilingKey(5); + m.floorKey(5); + m.higherKey(5); + m.lowerKey(5); + for (Iterator it = m.keyIterator(null); + it.hasNext();) { + it.next(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + }; + task.execute(); + Thread.sleep(1); + for (int j = 0; j < 100; j++) { + for (int i = 0; i < 100; i++) { + if (rand.nextBoolean()) { + m.put(rand.nextInt(size), 2); + } else { + m.remove(rand.nextInt(size)); + } + m.get(rand.nextInt(size)); + } + s.commit(); + Thread.sleep(1); + } + task.get(); + } + } + + private void testConcurrentOnlineBackup() throws Exception { + String fileName = getBaseDir() + "/" + getTestName(); + String fileNameRestore = getBaseDir() + "/" + getTestName() + "2"; + try (final MVStore s = openStore(fileName)) { + final MVMap map = s.openMap("test"); + final Random r = new Random(); + Task task = new Task() { + @Override + public void call() throws Exception { + while (!stop) { + for (int i = 0; i < 10; i++) { + map.put(i, new byte[100 * r.nextInt(100)]); + } + s.commit(); + map.clear(); + s.commit(); + long len = s.getFileStore().size(); + if (len > 1024 * 1024) { + // slow down writing a lot + Thread.sleep(200); + } else if (len > 20 * 1024) { + // slow down writing + Thread.sleep(20); + } + } + } + }; + task.execute(); + try { + for (int i = 0; i < 10; i++) { + // System.out.println("test " + i); + s.setReuseSpace(false); + OutputStream out = new BufferedOutputStream( + new FileOutputStream(fileNameRestore)); + long len = s.getFileStore().size(); + copyFileSlowly(s.getFileStore().getFile(), + len, out); + out.close(); + s.setReuseSpace(true); + MVStore s2 = openStore(fileNameRestore); + MVMap test = s2.openMap("test"); + for (Integer k : test.keySet()) { + test.get(k); + } + s2.close(); + // let it compact + Thread.sleep(10); + } + } finally { + task.get(); + } + } + } + + private static void copyFileSlowly(FileChannel file, long length, OutputStream out) + throws Exception { + file.position(0); + try (InputStream in = new BufferedInputStream(new FileChannelInputStream( + file, false))) { + for (int j = 0; j < length; j++) { + int x = in.read(); + if (x < 0) { + break; + } + out.write(x); + } + } + } + + private static void testConcurrentIterate() { + try (MVStore s = new MVStore.Builder().pageSplitSize(3).open()) { + s.setVersionsToKeep(100); + final MVMap map = s.openMap("test"); + final int len = 10; + final Random r = new Random(); + Task task = new Task() { + @Override + public void call() { + while (!stop) { + int x = r.nextInt(len); + if (r.nextBoolean()) { + map.remove(x); + } else { + map.put(x, r.nextInt(100)); + } + } + } + }; + task.execute(); + try { + for (int k = 0; k < 10000; k++) { + Iterator it = map.keyIterator(r.nextInt(len)); + long old = map.getVersion(); + s.commit(); + while (map.getVersion() == old) { + Thread.yield(); + } + while (it.hasNext()) { + it.next(); + } + } + } finally { + task.get(); + } + } + } + + + /** + * Test what happens on concurrent write. Concurrent write may corrupt the + * map, so that keys and values may become null. + */ + private void testConcurrentWrite() throws InterruptedException { + final AtomicInteger detected = new AtomicInteger(); + final AtomicInteger notDetected = new AtomicInteger(); + for (int i = 0; i < 10; i++) { + testConcurrentWrite(detected, notDetected); + } + // in most cases, it should be detected + assertTrue(notDetected.get() * 10 <= detected.get()); + } + + private static void testConcurrentWrite(final AtomicInteger detected, + final AtomicInteger notDetected) throws InterruptedException { + try (final MVStore s = openStore(null)) { + final MVMap m = s.openMap("data"); + final int size = 20; + final Random rand = new Random(1); + Task task = new Task() { + @Override + public void call() { + while (!stop) { + try { + if (rand.nextBoolean()) { + m.put(rand.nextInt(size), 1); + } else { + m.remove(rand.nextInt(size)); + } + m.get(rand.nextInt(size)); + } catch (ConcurrentModificationException e) { + detected.incrementAndGet(); + } catch (NegativeArraySizeException + | ArrayIndexOutOfBoundsException + | IllegalArgumentException + | NullPointerException e) { + notDetected.incrementAndGet(); + } + } + } + }; + task.execute(); + try { + Thread.sleep(1); + for (int j = 0; j < 10; j++) { + for (int i = 0; i < 10; i++) { + try { + if (rand.nextBoolean()) { + m.put(rand.nextInt(size), 2); + } else { + m.remove(rand.nextInt(size)); + } + m.get(rand.nextInt(size)); + } catch (ConcurrentModificationException e) { + detected.incrementAndGet(); + } catch (NegativeArraySizeException + | ArrayIndexOutOfBoundsException + | NullPointerException + | IllegalArgumentException e) { + notDetected.incrementAndGet(); + } + } + s.commit(); + Thread.sleep(1); + } + } finally { + task.get(); + } + } + } + + private static void testConcurrentRead() throws InterruptedException { + try (final MVStore s = openStore(null)) { + s.setVersionsToKeep(100); + final MVMap m = s.openMap("data"); + final int size = 3; + int x = (int) s.getCurrentVersion(); + for (int i = 0; i < size; i++) { + m.put(i, x); + } + s.commit(); + Task task = new Task() { + @Override + public void call() { + while (!stop) { + long v = s.getCurrentVersion() - 1; + Map old = m.openVersion(v); + for (int i = 0; i < size; i++) { + Integer x = old.get(i); + if (x == null || (int) v != x) { + Map old2 = m.openVersion(v); + throw new AssertionError(x + "<>" + v + " at " + i + " " + old2); + } + } + } + } + }; + task.execute(); + try { + Thread.sleep(1); + for (int j = 0; j < 100; j++) { + x = (int) s.getCurrentVersion(); + for (int i = 0; i < size; i++) { + m.put(i, x); + } + s.commit(); + Thread.sleep(1); + } + } finally { + task.get(); + } + } + } +} diff --git a/h2/src/test/org/h2/test/store/TestMVStoreStopCompact.java b/h2/src/test/org/h2/test/store/TestMVStoreStopCompact.java new file mode 100644 index 0000000..b4c7a88 --- /dev/null +++ b/h2/src/test/org/h2/test/store/TestMVStoreStopCompact.java @@ -0,0 +1,80 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.util.Random; + +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVStore; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; + +/** + * Test that the MVStore eventually stops optimizing (does not excessively opti + */ +public class TestMVStoreStopCompact extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); + test.config.big = true; + test.test(); + } + + @Override + public void test() throws Exception { + for(int retentionTime = 10; retentionTime < 1000; retentionTime *= 10) { + for(int timeout = 100; timeout <= 1000; timeout *= 10) { + testStopCompact(retentionTime, timeout); + } + } + } + + private void testStopCompact(int retentionTime, int timeout) throws InterruptedException { + String fileName = getBaseDir() + "/testStopCompact.h3"; + FileUtils.createDirectories(getBaseDir()); + FileUtils.delete(fileName); + // store with a very small page size, to make sure + // there are many leaf pages + MVStore.Builder builder = new MVStore.Builder().fileName(fileName); + try (MVStore s = builder.open()) { + s.setRetentionTime(retentionTime); + s.setVersionsToKeep(0); + MVMap map = s.openMap("data"); + long start = System.currentTimeMillis(); + Random r = new Random(1); + for (int i = 0; i < 4_000_000; i++) { + long time = System.currentTimeMillis() - start; + if (time > timeout) { + break; + } + int x = r.nextInt(10_000_000); + map.put(x, "Hello World " + i * 10); + } + s.setAutoCommitDelay(100); + long oldWriteCount = s.getFileStore().getWriteCount(); + long totalWrites = 0; + // expect background write to stop after a few seconds + for (int i = 0; i < 50; i++) { + Thread.sleep(200); + long newWriteCount = s.getFileStore().getWriteCount(); + long delta = newWriteCount - oldWriteCount; + if (delta == 0) { + break; + } + totalWrites += delta; + oldWriteCount = newWriteCount; + } + // expect that compaction didn't cause many writes + assertTrue("writeCount diff: " + retentionTime + "/" + timeout + " " + totalWrites, + totalWrites < 90); + } + } +} diff --git a/h2/src/test/org/h2/test/store/TestMVStoreTool.java b/h2/src/test/org/h2/test/store/TestMVStoreTool.java new file mode 100644 index 0000000..a63d85a --- /dev/null +++ b/h2/src/test/org/h2/test/store/TestMVStoreTool.java @@ -0,0 +1,151 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.util.Map.Entry; +import java.util.Random; + +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVStore; +import org.h2.mvstore.MVStoreTool; +import org.h2.mvstore.rtree.MVRTreeMap; +import org.h2.mvstore.rtree.Spatial; +import org.h2.mvstore.db.SpatialKey; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; + +/** + * Tests the MVStoreTool class. + */ +public class TestMVStoreTool extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); + test.config.traceTest = true; + test.config.big = true; + test.testFromMain(); + } + + @Override + public void test() throws Exception { + testCompact(); + } + + private void testCompact() { + String fileName = getBaseDir() + "/testCompact.h3"; + String fileNameNew = fileName + ".new"; + String fileNameCompressed = fileNameNew + ".compress"; + + FileUtils.createDirectories(getBaseDir()); + FileUtils.delete(fileName); + // store with a very small page size, to make sure + // there are many leaf pages + MVStore s = new MVStore.Builder(). + pageSplitSize(1000). + fileName(fileName).autoCommitDisabled().open(); + s.setRetentionTime(0); + long start = System.currentTimeMillis(); + MVMap map = s.openMap("data"); + int size = config.big ? 2_000_000 : 20_000; + for (int i = 0; i < size; i++) { + map.put(i, "Hello World " + i * 10); + if (i % 10000 == 0) { + s.commit(); + } + } + for (int i = 0; i < size; i += 2) { + map.remove(i); + if (i % 10000 == 0) { + s.commit(); + } + } + for (int i = 0; i < 20; i++) { + map = s.openMap("data" + i); + for (int j = 0; j < i * i; j++) { + map.put(j, "Hello World " + j * 10); + } + s.commit(); + } + MVRTreeMap rTreeMap = s.openMap("rtree", new MVRTreeMap.Builder()); + Random r = new Random(1); + for (int i = 0; i < 10; i++) { + float x = r.nextFloat(); + float y = r.nextFloat(); + float width = r.nextFloat() / 10; + float height = r.nextFloat() / 10; + SpatialKey k = new SpatialKey(i, x, x + width, y, y + height); + rTreeMap.put(k, "Hello World " + i * 10); + if (i % 3 == 0) { + s.commit(); + } + } + s.close(); + trace("Created in " + (System.currentTimeMillis() - start) + " ms."); + + start = System.currentTimeMillis(); + MVStoreTool.compact(fileName, fileNameNew, false); + MVStoreTool.compact(fileName, fileNameCompressed, true); + trace("Compacted in " + (System.currentTimeMillis() - start) + " ms."); + long size1 = FileUtils.size(fileName); + long size2 = FileUtils.size(fileNameNew); + long size3 = FileUtils.size(fileNameCompressed); + assertTrue("size1: " + size1 + " size2: " + size2 + " size3: " + size3, + size2 < size1 && size3 < size2); + + start = System.currentTimeMillis(); + MVStoreTool.compact(fileNameNew, false); + assertTrue(100L * Math.abs(size2 - FileUtils.size(fileNameNew)) / size2 < 1); + MVStoreTool.compact(fileNameCompressed, true); + assertEquals(size3, FileUtils.size(fileNameCompressed)); + trace("Re-compacted in " + (System.currentTimeMillis() - start) + " ms."); + + start = System.currentTimeMillis(); + MVStore s1 = new MVStore.Builder(). + fileName(fileName).readOnly().open(); + MVStore s2 = new MVStore.Builder(). + fileName(fileNameNew).readOnly().open(); + MVStore s3 = new MVStore.Builder(). + fileName(fileNameCompressed).readOnly().open(); + assertEquals(s1, s2); + assertEquals(s1, s3); + s1.close(); + s2.close(); + s3.close(); + trace("Verified in " + (System.currentTimeMillis() - start) + " ms."); + } + + private void assertEquals(MVStore a, MVStore b) { + assertEquals(a.getMapNames().size(), b.getMapNames().size()); + for (String mapName : a.getMapNames()) { + if (mapName.startsWith("rtree")) { + MVRTreeMap ma = a.openMap( + mapName, new MVRTreeMap.Builder()); + MVRTreeMap mb = b.openMap( + mapName, new MVRTreeMap.Builder()); + assertEquals(ma.sizeAsLong(), mb.sizeAsLong()); + for (Entry e : ma.entrySet()) { + Object x = mb.get(e.getKey()); + assertEquals(e.getValue(), x.toString()); + } + + } else { + MVMap ma = a.openMap(mapName); + MVMap mb = a.openMap(mapName); + assertEquals(ma.sizeAsLong(), mb.sizeAsLong()); + for (Entry e : ma.entrySet()) { + Object x = mb.get(e.getKey()); + assertEquals(e.getValue().toString(), x.toString()); + } + } + } + } + +} diff --git a/h2/src/test/org/h2/test/store/TestMVTableEngine.java b/h2/src/test/org/h2/test/store/TestMVTableEngine.java new file mode 100644 index 0000000..3c2d421 --- /dev/null +++ b/h2/src/test/org/h2/test/store/TestMVTableEngine.java @@ -0,0 +1,1406 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.math.BigDecimal; +import java.nio.channels.FileChannel; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Savepoint; +import java.sql.Statement; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.engine.Database; +import org.h2.jdbc.JdbcConnection; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVStore; +import org.h2.mvstore.db.LobStorageMap; +import org.h2.mvstore.tx.TransactionStore; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.Recover; +import org.h2.tools.Restore; +import org.h2.util.IOUtils; +import org.h2.util.JdbcUtils; +import org.h2.util.Task; +import org.h2.value.Value; + +/** + * Tests the MVStore in a database. + */ +public class TestMVTableEngine extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public void test() throws Exception { +/* + testLobCopy(); + testLobReuse(); + testShutdownDuringLobCreation(); + testLobCreationThenShutdown(); + testManyTransactions(); + testAppendOnly(); + testNoRetentionTime(); + testOldAndNew(); + testTemporaryTables(); + testUniqueIndex(); + testSecondaryIndex(); + testGarbageCollectionForLOB(); + testSpatial(); + testCount(); + testMinMaxWithNull(); + testTimeout(); + testExplainAnalyze(); + if (!config.memory) { + testTransactionLogEmptyAfterCommit(); + } + testShrinkDatabaseFile(); + testTwoPhaseCommit(); + testRecover(); + testSeparateKey(); + testRollback(); + testRollbackAfterCrash(); + testReferentialIntegrity(); + testWriteDelay(); + testAutoCommit(); + testReopen(); + testBlob(); + testEncryption(); + testReadOnly(); + testReuseDiskSpace(); +*/ + testDataTypes(); +// testSimple(); +// if (!config.travis) { +// testReverseDeletePerformance(); +// } + } + + private void testLobCopy() throws Exception { + deleteDb(getTestName()); + Connection conn = getConnection(getTestName()); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, data clob)"); + stat = conn.createStatement(); + stat.execute("insert into test(id, data) values(2, space(300))"); + stat.execute("insert into test(id, data) values(1, space(300))"); + stat.execute("alter table test add column x int"); + if (!config.memory) { + conn.close(); + conn = getConnection(getTestName()); + } + stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("select data from test"); + while (rs.next()) { + rs.getString(1); + } + conn.close(); + } + + private void testLobReuse() throws Exception { + deleteDb(getTestName()); + try (Connection conn1 = getConnection(getTestName())) { + Statement stat = conn1.createStatement(); + stat.execute("create table test(id identity primary key, lob clob)"); + byte[] buffer = new byte[8192]; + for (int i = 0; i < 20; i++) { + try (Connection conn2 = getConnection(getTestName())) { + stat = conn2.createStatement(); + stat.execute("insert into test(lob) select space(1025) from system_range(1, 10)"); + stat.execute("delete from test where random() > 0.5"); + ResultSet rs = conn2.createStatement().executeQuery( + "select lob from test"); + while (rs.next()) { + InputStream is = rs.getBinaryStream(1); + while (is.read(buffer) != -1) { + // ignore + } + } + } + } + } + } + + private void testShutdownDuringLobCreation() throws Exception { + if (config.memory) { + return; + } + deleteDb(getTestName()); + try (Connection conn = getConnection(getTestName())) { + Statement stat = conn.createStatement(); + stat.execute("create table test(data clob) as select space(10000)"); + final PreparedStatement prep = conn + .prepareStatement("set @lob = ?"); + final AtomicBoolean end = new AtomicBoolean(); + Task t = new Task() { + + @Override + public void call() throws Exception { + prep.setBinaryStream(1, new InputStream() { + + int len; + + @Override + public int read() throws IOException { + if (len++ < 1024 * 1024 * 4) { + return 0; + } + end.set(true); + while (!stop) { + try { + Thread.sleep(1); + } catch (InterruptedException e) { + // ignore + } + } + return -1; + } + }, -1); + } + }; + t.execute(); + while (!end.get()) { + Thread.sleep(1); + } + stat.execute("checkpoint"); + stat.execute("shutdown immediately"); + Exception ex = t.getException(); + assertNotNull(ex); + IOUtils.closeSilently(conn); + } + try (Connection conn = getConnection(getTestName())) { + Statement stat = conn.createStatement(); + stat.execute("shutdown defrag"); + } + try (Connection conn = getConnection(getTestName())) { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("select * " + + "from information_schema.settings " + + "where setting_name = 'info.PAGE_COUNT'"); + rs.next(); + int pages = rs.getInt(2); + // only one lob should remain (but it is small and compressed) + assertTrue("p:" + pages, pages <= 7); + } + } + + private void testLobCreationThenShutdown() throws Exception { + if (config.memory) { + return; + } + deleteDb(getTestName()); + try (Connection conn = getConnection(getTestName())) { + Statement stat = conn.createStatement(); + stat.execute("create table test(id identity, data clob)"); + PreparedStatement prep = conn + .prepareStatement("insert into test values(?, ?)"); + for (int i = 0; i < 9; i++) { + prep.setInt(1, i); + int size = i * i * i * i * 1024; + prep.setCharacterStream(2, new StringReader(new String( + new char[size]))); + prep.execute(); + } + stat.execute("shutdown immediately"); + IOUtils.closeSilently(conn); + } + try (Connection conn = getConnection(getTestName())) { + Statement stat = conn.createStatement(); + stat.execute("drop all objects"); + stat.execute("shutdown defrag"); + } + try (Connection conn = getConnection(getTestName())) { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("select * " + + "from information_schema.settings " + + "where setting_name = 'info.PAGE_COUNT'"); + rs.next(); + int pages = rs.getInt(2); + // no lobs should remain + assertTrue("p:" + pages, pages < 4); + } + } + + private void testManyTransactions() throws Exception { + deleteDb(getTestName()); + try (Connection conn = getConnection(getTestName())) { + Statement stat = conn.createStatement(); + stat.execute("create table test()"); + conn.setAutoCommit(false); + stat.execute("insert into test values()"); + + try (Connection conn2 = getConnection(getTestName())) { + Statement stat2 = conn2.createStatement(); + for (long i = 0; i < 100000; i++) { + stat2.execute("insert into test values()"); + } + } + } + } + + private void testAppendOnly() throws Exception { + if (config.memory) { + return; + } + deleteDb(getTestName()); + try (Connection conn = getConnection(getTestName())) { + Statement stat = conn.createStatement(); + stat.execute("set retention_time 0"); + for (int i = 0; i < 10; i++) { + stat.execute("create table dummy" + i + + " as select x, space(100) from system_range(1, 1000)"); + stat.execute("checkpoint"); + } + stat.execute("create table test as select x from system_range(1, 1000)"); + } + + String fileName = getBaseDir() + "/" + getTestName() + Constants.SUFFIX_MV_FILE; + long fileSize = FileUtils.size(fileName); + + try (Connection conn = getConnection(getTestName() + ";reuse_space=false")) { + Statement stat = conn.createStatement(); + stat.execute("set retention_time 0"); + for (int i = 0; i < 10; i++) { + stat.execute("drop table dummy" + i); + stat.execute("checkpoint"); + } + stat.execute("alter table test alter column x rename to y"); + stat.execute("select y from test where 1 = 0"); + stat.execute("create table test2 as select x from system_range(1, 1000)"); + } + + try (FileChannel fc = FileUtils.open(fileName, "rw")) { + // undo all changes + fc.truncate(fileSize); + } + + try (Connection conn = getConnection(getTestName())) { + Statement stat = conn.createStatement(); + stat.execute("select * from dummy0 where 1 = 0"); + stat.execute("select * from dummy9 where 1 = 0"); + stat.execute("select x from test where 1 = 0"); + } + } + + private void testNoRetentionTime() throws SQLException { + deleteDb(getTestName()); + try (Connection conn = getConnection(getTestName() + ";RETENTION_TIME=0;WRITE_DELAY=10")) { + Statement stat = conn.createStatement(); + try (Connection conn2 = getConnection(getTestName())) { + Statement stat2 = conn2.createStatement(); + stat.execute("create alias sleep as " + + "$$void sleep(int ms) throws Exception { Thread.sleep(ms); }$$"); + stat.execute("create table test(id identity, name varchar) " + + "as select x, 'Init' from system_range(0, 1999)"); + for (int i = 0; i < 10; i++) { + stat.execute("insert into test values(null, 'Hello')"); + // create and delete a large table: this will force compaction + stat.execute("create table temp(id identity, name varchar) as " + + "select x, space(1000000) from system_range(0, 10)"); + stat.execute("drop table temp"); + } + ResultSet rs = stat2 + .executeQuery("select *, sleep(1) from test order by id"); + for (int i = 0; i < 2000 + 10; i++) { + assertTrue(rs.next()); + assertEquals(i, rs.getInt(1)); + } + assertFalse(rs.next()); + } + } + } + + private void testOldAndNew() throws SQLException { + if (config.memory) { + return; + } + deleteDb(getTestName()); + String urlOld = getURL(getTestName() + ";MV_STORE=FALSE", true); + String urlNew = getURL(getTestName() + ";MV_STORE=TRUE", true); + String url = getURL(getTestName(), true); + + try (Connection conn = getConnection(urlOld)) { + conn.createStatement().execute("create table test_old(id int)"); + } + try (Connection conn = getConnection(url)) { + conn.createStatement().execute("select * from test_old"); + } + try (Connection conn = getConnection(urlNew)) { + conn.createStatement().execute("create table test_new(id int)"); + } + try (Connection conn = getConnection(url)) { + conn.createStatement().execute("select * from test_new"); + } + try (Connection conn = getConnection(urlOld)) { + conn.createStatement().execute("select * from test_old"); + } + try (Connection conn = getConnection(urlNew)) { + conn.createStatement().execute("select * from test_new"); + } + } + + private void testTemporaryTables() throws SQLException { + deleteDb(getTestName()); + String url = getTestName() + ";MV_STORE=TRUE"; + url = getURL(url, true); + try (Connection conn = getConnection(url)) { + Statement stat = conn.createStatement(); + stat.execute("set max_memory_rows 100"); + stat.execute("create table t1 as select x from system_range(1, 200)"); + stat.execute("create table t2 as select x from system_range(1, 200)"); + for (int i = 0; i < 20; i++) { + // this will create temporary results that + // internally use temporary tables, which are not all closed + stat.execute("select count(*) from t1 where t1.x in (select t2.x from t2)"); + } + } + try (Connection conn = getConnection(url)) { + Statement stat = conn.createStatement(); + for (int i = 0; i < 20; i++) { + stat.execute("create table a" + i + "(id int primary key)"); + ResultSet rs = stat.executeQuery("select count(*) from a" + i); + rs.next(); + assertEquals(0, rs.getInt(1)); + } + } + } + + private void testUniqueIndex() throws SQLException { + deleteDb(getTestName()); + String url = getTestName() + ";MV_STORE=TRUE"; + url = getURL(url, true); + try (Connection conn = getConnection(url)) { + Statement stat = conn.createStatement(); + stat.execute("create table test as select x, 0 from system_range(1, 5000)"); + stat.execute("create unique index on test(x)"); + ResultSet rs = stat.executeQuery("select * from test where x=1"); + assertTrue(rs.next()); + assertFalse(rs.next()); + } + } + + private void testSecondaryIndex() throws SQLException { + deleteDb(getTestName()); + String url = getTestName() + ";MV_STORE=TRUE"; + url = getURL(url, true); + try (Connection conn = getConnection(url)) { + Statement stat = conn.createStatement(); + stat.execute("create table test(id int)"); + int size = 8 * 1024; + stat.execute("insert into test select mod(x * 111, " + size + ") " + + "from system_range(1, " + size + ")"); + stat.execute("create index on test(id)"); + ResultSet rs = stat.executeQuery( + "select count(*) from test inner join " + + "system_range(1, " + size + ") where " + + "id = mod(x * 111, " + size + ")"); + rs.next(); + assertEquals(size, rs.getInt(1)); + } + } + + private void testGarbageCollectionForLOB() throws SQLException { + if (config.memory) { + return; + } + deleteDb(getTestName()); + String url = getTestName() + ";MV_STORE=TRUE"; + url = getURL(url, true); + try (Connection conn = getConnection(url)) { + Statement stat = conn.createStatement(); + stat.execute("create table test(id int, data blob)"); + stat.execute("insert into test select x, repeat('0', 10000) " + + "from system_range(1, 10)"); + stat.execute("drop table test"); + stat.execute("create table test2(id int, data blob)"); + PreparedStatement prep = conn.prepareStatement( + "insert into test2 values(?, ?)"); + prep.setInt(1, 1); + assertThrows(ErrorCode.IO_EXCEPTION_1, prep). + setBinaryStream(1, createFailingStream(new IOException())); + prep.setInt(1, 2); + assertThrows(ErrorCode.IO_EXCEPTION_1, prep). + setBinaryStream(1, createFailingStream(new IllegalStateException())); + } + try (MVStore s = MVStore.open(getBaseDir()+ "/" + getTestName() + ".mv.db")) { + assertTrue(s.hasMap("lobData")); + MVMap lobData = s.openMap("lobData"); + assertEquals(0, lobData.sizeAsLong()); + assertTrue(s.hasMap("lobMap")); + MVMap lobMap = s.openMap("lobMap"); + assertEquals(0, lobMap.sizeAsLong()); + assertTrue(s.hasMap("lobRef")); + MVMap lobRef = s.openMap("lobRef"); + assertEquals(0, lobRef.sizeAsLong()); + } + } + + private void testSpatial() throws SQLException { + Statement stat; + deleteDb(getTestName()); + String url = getTestName() + ";MV_STORE=TRUE"; + url = getURL(url, true); + try (Connection conn = getConnection(url)) { + stat = conn.createStatement(); + stat.execute("call rand(1)"); + stat.execute("create table coordinates as select rand()*50 x, " + + "rand()*50 y from system_range(1, 5000)"); + stat.execute("create table test(id identity, data geometry)"); + stat.execute("create spatial index on test(data)"); + stat.execute("insert into test(data) select 'polygon(('||" + + "(1+x)||' '||(1+y)||', '||(2+x)||' '||(2+y)||', " + + "'||(3+x)||' '||(1+y)||', '||(1+x)||' '||(1+y)||'))' from coordinates;"); + } + } + + private void testCount() throws Exception { + if (config.memory) { + return; + } + + Statement stat; + Statement stat2; + deleteDb(getTestName()); + String url = getTestName() + ";MV_STORE=TRUE"; + url = getURL(url, true); + try (Connection conn = getConnection(url)) { + stat = conn.createStatement(); + stat.execute("create table test(id int)"); + stat.execute("create table test2(id int)"); + stat.execute("insert into test select x from system_range(1, 10000)"); + } + + String plan; + + ResultSet rs; + try (Connection conn2 = getConnection(url)) { + stat2 = conn2.createStatement(); + rs = stat2.executeQuery("explain analyze select count(*) from test"); + rs.next(); + plan = rs.getString(1); + assertTrue(plan, !plan.contains("reads:")); + try (Connection conn = getConnection(url)) { + stat = conn.createStatement(); + conn.setAutoCommit(false); + stat.execute("insert into test select x from system_range(1, 1000)"); + rs = stat.executeQuery("select count(*) from test"); + rs.next(); + assertEquals(11000, rs.getInt(1)); + + // not yet committed + rs = stat2.executeQuery("explain analyze select count(*) from test"); + rs.next(); + plan = rs.getString(1); + // transaction log is small, so no need to read the table + assertTrue(plan, !plan.contains("reads:")); + rs = stat2.executeQuery("select count(*) from test"); + rs.next(); + assertEquals(10000, rs.getInt(1)); + + stat2.execute("set cache_size 1024"); // causes cache to be cleared, so reads will occur + + stat.execute("insert into test2 select x from system_range(1, 11000)"); + rs = stat2.executeQuery("explain analyze select count(*) from test"); + rs.next(); + plan = rs.getString(1); + // transaction log is larger than the table, so read the table + assertContains(plan, "reads:"); + rs = stat2.executeQuery("select count(*) from test"); + rs.next(); + assertEquals(10000, rs.getInt(1)); + } + } + + } + + private void testMinMaxWithNull() throws Exception { + Statement stat; + Statement stat2; + deleteDb(getTestName()); + String url = getTestName() + ";MV_STORE=TRUE"; + url = getURL(url, true); + try (Connection conn = getConnection(url)) { + stat = conn.createStatement(); + stat.execute("create table test(data int)"); + stat.execute("create index on test(data)"); + stat.execute("insert into test values(null), (2)"); + try (Connection conn2 = getConnection(url)) { + stat2 = conn2.createStatement(); + conn.setAutoCommit(false); + conn2.setAutoCommit(false); + stat.execute("insert into test values(1)"); + ResultSet rs; + rs = stat.executeQuery("select min(data) from test"); + rs.next(); + assertEquals(1, rs.getInt(1)); + rs = stat2.executeQuery("select min(data) from test"); + rs.next(); + // not yet committed + assertEquals(2, rs.getInt(1)); + } + } + } + + private void testTimeout() throws Exception { + Statement stat; + Statement stat2; + deleteDb(getTestName()); + String url = getTestName() + ";MV_STORE=TRUE"; + url = getURL(url, true); + try (Connection conn = getConnection(url)) { + stat = conn.createStatement(); + stat.execute("create table test(id identity, name varchar)"); + try (Connection conn2 = getConnection(url)) { + stat2 = conn2.createStatement(); + conn.setAutoCommit(false); + conn2.setAutoCommit(false); + stat.execute("insert into test values(1, 'Hello')"); + assertThrows(ErrorCode.LOCK_TIMEOUT_1, stat2). + execute("insert into test values(1, 'Hello')"); + } + } + } + + private void testExplainAnalyze() throws Exception { + if (config.memory) { + return; + } + Statement stat; + deleteDb(getTestName()); + String url = getTestName() + ";MV_STORE=TRUE;WRITE_DELAY=0"; + url = getURL(url, true); + try (Connection conn = getConnection(url)) { + stat = conn.createStatement(); + stat.execute("create table test(id identity, name varchar) as " + + "select x, space(1000) from system_range(1, 1000)"); + } + try (Connection conn = getConnection(url)) { + stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("explain analyze select * from test"); + rs.next(); + String plan = rs.getString(1); + // expect about 1000 reads + String readCount = plan.substring(plan.indexOf("reads: ")); + readCount = readCount.substring("reads: ".length(), readCount.indexOf('\n')); + int rc = Integer.parseInt(readCount); + assertTrue(plan, rc >= 60 && rc <= 80); + } + } + + private void testTransactionLogEmptyAfterCommit() throws Exception { + Statement stat; + deleteDb(getTestName()); + String url = getTestName() + ";MV_STORE=TRUE"; + url = getURL(url, true); + try (Connection conn = getConnection(url)) { + stat = conn.createStatement(); + stat.execute("create table test(id identity, name varchar)"); + stat.execute("set write_delay 0"); + conn.setAutoCommit(false); + PreparedStatement prep = conn.prepareStatement( + "insert into test(name) values(space(10000))"); + for (int j = 0; j < 100; j++) { + for (int i = 0; i < 100; i++) { + prep.execute(); + } + conn.commit(); + } + stat.execute("shutdown immediately"); + } catch (Exception ignore) {/**/} + + String file = getBaseDir() + "/" + getTestName() + Constants.SUFFIX_MV_FILE; + assertTrue(new File(file).exists()); + try (MVStore store = MVStore.open(file)) { + TransactionStore t = new TransactionStore(store); + t.init(); + int openTransactions = t.getOpenTransactions().size(); + if (openTransactions != 0) { + fail("transaction log was not empty"); + } + } + } + + private void testShrinkDatabaseFile() throws Exception { + if (config.memory) { + return; + } + deleteDb(getTestName()); + // set WRITE_DELAY=0 so the free-unused-space runs on commit + String dbName = getTestName() + ";MV_STORE=TRUE;WRITE_DELAY=0"; + Connection conn; + Statement stat; + long maxSize = 0; + // by default, the database does not shrink for 45 seconds + int retentionTime = 45000; + for (int i = 0; i < 20; i++) { + // the first 10 times, keep the default retention time + // then switch to 0, at which point the database file + // should stop to grow + conn = getConnection(dbName); + stat = conn.createStatement(); + if (i == 10) { + stat.execute("set retention_time 0"); + retentionTime = 0; + } + ResultSet rs = stat.executeQuery( + "select setting_value from information_schema.settings " + + "where setting_name='RETENTION_TIME'"); + assertTrue(rs.next()); + assertEquals(retentionTime, rs.getInt(1)); + stat.execute("create table test(id int primary key, data varchar)"); + stat.execute("insert into test select x, space(100) " + + "from system_range(1, 1000)"); + // this table is kept + if (i < 10) { + stat.execute("create table test" + i + + "(id int primary key, data varchar) " + + "as select x, space(10) from system_range(1, 100)"); + } + // force writing the chunk + stat.execute("checkpoint"); + // drop the table - but the chunk is still used + stat.execute("drop table test"); + stat.execute("checkpoint"); + stat.execute("shutdown immediately"); + try { + conn.close(); + } catch (Exception e) { + // ignore + } + String fileName = getBaseDir() + "/" + getTestName() + + Constants.SUFFIX_MV_FILE; + long size = FileUtils.size(fileName); + if (i < 10) { + maxSize = (int) Math.max(size * 1.2, maxSize); + } else if (size > maxSize) { + fail(i + " size: " + size + " max: " + maxSize); + } + } + long sizeOld = FileUtils.size(getBaseDir() + "/" + getTestName() + + Constants.SUFFIX_MV_FILE); + conn = getConnection(dbName); + stat = conn.createStatement(); + stat.execute("shutdown compact"); + conn.close(); + long sizeNew = FileUtils.size(getBaseDir() + "/" + getTestName() + + Constants.SUFFIX_MV_FILE); + assertTrue("new: " + sizeNew + " old: " + sizeOld, sizeNew < sizeOld); + } + + private void testTwoPhaseCommit() throws Exception { + if (config.memory) { + return; + } + Connection conn; + Statement stat; + deleteDb(getTestName()); + String url = getTestName() + ";MV_STORE=TRUE"; + url = getURL(url, true); + conn = getConnection(url); + stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name varchar)"); + stat.execute("set write_delay 0"); + conn.setAutoCommit(false); + stat.execute("insert into test values(1, 'Hello')"); + stat.execute("prepare commit test_tx"); + stat.execute("shutdown immediately"); + JdbcUtils.closeSilently(conn); + + conn = getConnection(url); + stat = conn.createStatement(); + ResultSet rs; + rs = stat.executeQuery("select * from information_schema.in_doubt"); + assertTrue(rs.next()); + stat.execute("commit transaction test_tx"); + rs = stat.executeQuery("select * from test"); + assertTrue(rs.next()); + conn.close(); + } + + private void testRecover() throws Exception { + if (config.memory) { + return; + } + Connection conn; + Statement stat; + deleteDb(getTestName()); + String url = getTestName() + ";MV_STORE=TRUE"; + url = getURL(url, true); + conn = getConnection(url); + stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name varchar)"); + stat.execute("insert into test values(1, 'Hello')"); + stat.execute("create table test2(name varchar)"); + stat.execute("insert into test2 values('Hello World')"); + conn.close(); + + Recover.execute(getBaseDir(), getTestName()); + deleteDb(getTestName()); + conn = getConnection(url); + stat = conn.createStatement(); + stat.execute("runscript from '" + getBaseDir() + "/" + getTestName()+ ".h2.sql'"); + ResultSet rs; + rs = stat.executeQuery("select * from test"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + rs = stat.executeQuery("select * from test2"); + assertTrue(rs.next()); + assertEquals("Hello World", rs.getString(1)); + conn.close(); + } + + private void testRollback() throws Exception { + Connection conn; + Statement stat; + deleteDb(getTestName()); + String url = getTestName() + ";MV_STORE=TRUE"; + conn = getConnection(url); + stat = conn.createStatement(); + stat.execute("create table test(id identity)"); + conn.setAutoCommit(false); + stat.execute("insert into test values(1)"); + stat.execute("delete from test"); + conn.rollback(); + conn.close(); + } + + private void testSeparateKey() throws Exception { + if (config.memory) { + return; + } + Connection conn; + Statement stat; + deleteDb(getTestName()); + String url = getTestName() + ";MV_STORE=TRUE"; + + conn = getConnection(url); + stat = conn.createStatement(); + stat.execute("create table a(id int)"); + stat.execute("insert into a values(1)"); + stat.execute("insert into a values(1)"); + + stat.execute("create table test(id int not null) as select 100"); + stat.execute("create primary key on test(id)"); + ResultSet rs = stat.executeQuery("select * from test where id = 100"); + assertTrue(rs.next()); + conn.close(); + + conn = getConnection(url); + stat = conn.createStatement(); + rs = stat.executeQuery("select * from test where id = 100"); + assertTrue(rs.next()); + conn.close(); + } + + private void testRollbackAfterCrash() throws Exception { + if (config.memory) { + return; + } + Connection conn; + Statement stat; + deleteDb(getTestName()); + String url = getTestName() + ";MV_STORE=TRUE"; + String url2 = getTestName() + "2;MV_STORE=TRUE"; + + conn = getConnection(url); + stat = conn.createStatement(); + stat.execute("create table test(id int)"); + stat.execute("insert into test values(0)"); + stat.execute("set write_delay 0"); + conn.setAutoCommit(false); + stat.execute("insert into test values(1)"); + stat.execute("shutdown immediately"); + JdbcUtils.closeSilently(conn); + + conn = getConnection(url); + stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("select row_count_estimate " + + "from information_schema.tables where table_name='TEST'"); + rs.next(); + assertEquals(1, rs.getLong(1)); + stat.execute("drop table test"); + + stat.execute("create table test(id int primary key, data clob)"); + stat.execute("insert into test values(1, space(10000))"); + conn.setAutoCommit(false); + stat.execute("delete from test"); + stat.execute("checkpoint"); + stat.execute("shutdown immediately"); + JdbcUtils.closeSilently(conn); + + conn = getConnection(url); + stat = conn.createStatement(); + rs = stat.executeQuery("select * from test"); + assertTrue(rs.next()); + stat.execute("drop all objects delete files"); + conn.close(); + + conn = getConnection(url); + stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name varchar)"); + stat.execute("create index idx_name on test(name, id)"); + stat.execute("insert into test select x, x || space(200 * x) " + + "from system_range(1, 10)"); + conn.setAutoCommit(false); + stat.execute("delete from test where id > 5"); + stat.execute("backup to '" + getBaseDir() + "/" + getTestName() + ".zip'"); + conn.rollback(); + Restore.execute(getBaseDir() + "/" +getTestName() + ".zip", + getBaseDir(), getTestName() + "2"); + Connection conn2; + conn2 = getConnection(url2); + conn.close(); + conn2.close(); + + } + + private void testReferentialIntegrity() throws Exception { + Connection conn; + Statement stat; + deleteDb(getTestName()); + conn = getConnection(getTestName() + ";MV_STORE=TRUE"); + + stat = conn.createStatement(); + stat.execute("create table test(id int, parent int " + + "references test(id) on delete cascade)"); + stat.execute("insert into test values(0, 0)"); + stat.execute("delete from test"); + stat.execute("drop table test"); + + stat.execute("create table parent(id int, name varchar)"); + stat.execute("create table child(id int, parentid int, " + + "foreign key(parentid) references parent(id))"); + stat.execute("insert into parent values(1, 'mary'), (2, 'john')"); + stat.execute("insert into child values(10, 1), (11, 1), (20, 2), (21, 2)"); + stat.execute("update parent set name = 'marc' where id = 1"); + stat.execute("merge into parent key(id) values(1, 'marcy')"); + stat.execute("drop table parent, child"); + + stat.execute("create table test(id identity, parent bigint, " + + "foreign key(parent) references(id))"); + stat.execute("insert into test values(0, 0), (1, NULL), " + + "(2, 1), (3, 3), (4, 3)"); + stat.execute("drop table test"); + + stat.execute("create table parent(id int)"); + stat.execute("create table child(pid int)"); + stat.execute("insert into parent values(1)"); + stat.execute("insert into child values(2)"); + assertThrows(ErrorCode.REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1, stat).execute( + "alter table child add constraint cp foreign key(pid) references parent(id)"); + stat.execute("update child set pid=1"); + stat.execute("drop table child, parent"); + + stat.execute("create table parent(id int)"); + stat.execute("create table child(pid int)"); + stat.execute("insert into parent values(1)"); + stat.execute("insert into child values(2)"); + assertThrows(ErrorCode.REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1, stat).execute( + "alter table child add constraint cp foreign key(pid) references parent(id)"); + stat.execute("drop table child, parent"); + + stat.execute("create table test(id identity, parent bigint, " + + "foreign key(parent) references(id))"); + stat.execute("insert into test values(0, 0), (1, NULL), " + + "(2, 1), (3, 3), (4, 3)"); + stat.execute("drop table test"); + + stat.execute("create table parent(id int, x int)"); + stat.execute("insert into parent values(1, 2)"); + stat.execute("create table child(id int references parent(id)) as select 1"); + + conn.close(); + } + + private void testWriteDelay() throws Exception { + if (config.memory) { + return; + } + Connection conn; + Statement stat; + ResultSet rs; + deleteDb(getTestName()); + conn = getConnection(getTestName() + ";MV_STORE=TRUE"); + stat = conn.createStatement(); + stat.execute("create table test(id int)"); + stat.execute("set write_delay 0"); + stat.execute("insert into test values(1)"); + stat.execute("shutdown immediately"); + try { + conn.close(); + } catch (Exception e) { + // ignore + } + conn = getConnection(getTestName() + ";MV_STORE=TRUE"); + stat = conn.createStatement(); + rs = stat.executeQuery("select * from test"); + assertTrue(rs.next()); + conn.close(); + } + + private void testAutoCommit() throws SQLException { + Connection conn; + Statement stat; + ResultSet rs; + deleteDb(getTestName()); + conn = getConnection(getTestName() + ";MV_STORE=TRUE"); + for (int i = 0; i < 2; i++) { + stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name varchar)"); + stat.execute("create index on test(name)"); + conn.setAutoCommit(false); + stat.execute("insert into test values(1, 'Hello')"); + stat.execute("insert into test values(2, 'World')"); + rs = stat.executeQuery("select count(*) from test"); + rs.next(); + assertEquals(2, rs.getInt(1)); + conn.rollback(); + rs = stat.executeQuery("select count(*) from test"); + rs.next(); + assertEquals(0, rs.getInt(1)); + + stat.execute("insert into test values(1, 'Hello')"); + Savepoint sp = conn.setSavepoint(); + stat.execute("insert into test values(2, 'World')"); + conn.rollback(sp); + rs = stat.executeQuery("select count(*) from test"); + rs.next(); + assertEquals(1, rs.getInt(1)); + stat.execute("drop table test"); + } + + conn.close(); + } + + private void testReopen() throws SQLException { + if (config.memory) { + return; + } + Connection conn; + Statement stat; + deleteDb(getTestName()); + conn = getConnection(getTestName() + ";MV_STORE=TRUE"); + stat = conn.createStatement(); + stat.execute("create table test(id int, name varchar)"); + conn.close(); + conn = getConnection(getTestName() + ";MV_STORE=TRUE"); + stat = conn.createStatement(); + stat.execute("drop table test"); + conn.close(); + } + + private void testBlob() throws SQLException, IOException { + if (config.memory) { + return; + } + deleteDb(getTestName()); + String dbName = getTestName() + ";MV_STORE=TRUE"; + Connection conn; + Statement stat; + conn = getConnection(dbName); + stat = conn.createStatement(); + stat.execute("create table test(id int, name blob)"); + PreparedStatement prep = conn.prepareStatement( + "insert into test values(1, ?)"); + prep.setBinaryStream(1, new ByteArrayInputStream(new byte[129])); + prep.execute(); + conn.close(); + conn = getConnection(dbName); + stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("select * from test"); + while (rs.next()) { + InputStream in = rs.getBinaryStream(2); + int len = 0; + while (in.read() >= 0) { + len++; + } + assertEquals(129, len); + } + conn.close(); + } + + private void testEncryption() throws Exception { + if (config.memory) { + return; + } + deleteDb(getTestName()); + String dbName = getTestName() + ";MV_STORE=TRUE"; + Connection conn; + Statement stat; + String url = getURL(dbName + ";CIPHER=AES", true); + String user = "sa"; + String password = "123 123"; + conn = DriverManager.getConnection(url, user, password); + stat = conn.createStatement(); + stat.execute("create table test(id int primary key)"); + conn.close(); + conn = DriverManager.getConnection(url, user, password); + stat = conn.createStatement(); + stat.execute("select * from test"); + stat.execute("drop table test"); + conn.close(); + } + + private void testReadOnly() throws Exception { + if (config.memory) { + return; + } + deleteDb(getTestName()); + String dbName = getTestName() + ";MV_STORE=TRUE"; + Connection conn; + Statement stat; + conn = getConnection(dbName); + stat = conn.createStatement(); + stat.execute("create table test(id int)"); + conn.close(); + FileUtils.setReadOnly(getBaseDir() + "/" + getTestName() + + Constants.SUFFIX_MV_FILE); + conn = getConnection(dbName); + Database db = (Database) ((JdbcConnection) conn).getSession() + .getDataHandler(); + assertTrue(db.getStore().getMvStore().getFileStore().isReadOnly()); + conn.close(); + } + + private void testReuseDiskSpace() throws Exception { + deleteDb(getTestName()); + // set WRITE_DELAY=0 so the free-unused-space runs on commit + String dbName = getTestName() + ";MV_STORE=TRUE;WRITE_DELAY=0;RETENTION_TIME=0"; + Connection conn; + Statement stat; + long maxSize = 0; + for (int i = 0; i < 20; i++) { + conn = getConnection(dbName); + stat = conn.createStatement(); + stat.execute("create table test(id int primary key, data varchar)"); + stat.execute("insert into test select x, space(1000) " + + "from system_range(1, 1000)"); + stat.execute("drop table test"); + conn.close(); + long size = FileUtils.size(getBaseDir() + "/" + getTestName() + + Constants.SUFFIX_MV_FILE); +// trace("Pass #" + i + ": size=" + size); + if (i < 10) { + maxSize = (int) (Math.max(size * 1.1, maxSize)); + } else if (size > maxSize) { + fail(i + " size: " + size + " max: " + maxSize); + } + } + } + + private void testDataTypes() throws Exception { + deleteDb(getTestName()); + String dbName = getTestName() + ";MV_STORE=TRUE"; + Connection conn = getConnection(dbName); + Statement stat = conn.createStatement(); + + stat.execute("create table test(id int primary key, " + + "vc varchar," + + "ch char(10)," + + "bo boolean," + + "by tinyint," + + "sm smallint," + + "bi bigint," + + "de decimal(5, 2)," + + "re real,"+ + "do double," + + "ti time," + + "da date," + + "ts timestamp," + + "bin varbinary," + + "uu uuid," + + "bl blob," + + "cl clob)"); + stat.execute("insert into test values(1000, '', '', null, 0, 0, 0, " + + "9, 2, 3, '10:00:00', '2001-01-01', " + + "'2010-10-10 10:10:10', x'00', '01234567-89AB-CDEF-0123-456789ABCDEF', x'b1', 'clob')"); + stat.execute("insert into test values(1, 'vc', 'ch', true, 8, 16, 64, " + + "123.00, 64.0, 32.0, '10:00:00', '2001-01-01', " + + "'2010-10-10 10:10:10', x'00', '01234567-89AB-CDEF-0123-456789ABCDEF', x'b1', 'clob')"); + stat.execute("insert into test values(-1, " + + "'quite a long string \u1234 \u00ff', 'ch', false, -8, -16, -64, " + + "0, 0, 0, '10:00:00', '2001-01-01', " + + "'2010-10-10 10:10:10', SECURE_RAND(100), RANDOM_UUID(), x'b1', 'clob')"); + stat.execute("insert into test values(-1000, space(1000), 'ch', " + + "false, -8, -16, -64, " + + "1, 1, 1, '10:00:00', '2001-01-01', " + + "'2010-10-10 10:10:10', SECURE_RAND(100), RANDOM_UUID(), x'b1', 'clob')"); + if (!config.memory) { + conn.close(); + conn = getConnection(dbName); + stat = conn.createStatement(); + } + ResultSet rs; + rs = stat.executeQuery("select * from test order by id desc"); + rs.next(); + assertEquals(1000, rs.getInt(1)); + assertEquals("", rs.getString(2)); + assertEquals(" ", rs.getString(3)); + assertFalse(rs.getBoolean(4)); + assertEquals(0, rs.getByte(5)); + assertEquals(0, rs.getShort(6)); + assertEquals(0, rs.getLong(7)); + assertEquals("9.00", rs.getBigDecimal(8).toString()); + assertEquals(2d, rs.getDouble(9)); + assertEquals(3d, rs.getFloat(10)); + assertEquals("10:00:00", rs.getString(11)); + assertEquals("2001-01-01", rs.getString(12)); + assertEquals("2010-10-10 10:10:10", rs.getString(13)); + assertEquals(1, rs.getBytes(14).length); + assertEquals(UUID.fromString("01234567-89AB-CDEF-0123-456789ABCDEF"), rs.getObject(15)); + assertEquals(1, rs.getBytes(16).length); + assertEquals("clob", rs.getString(17)); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals("vc", rs.getString(2)); + assertEquals("ch ", rs.getString(3)); + assertTrue(rs.getBoolean(4)); + assertEquals(8, rs.getByte(5)); + assertEquals(16, rs.getShort(6)); + assertEquals(64, rs.getLong(7)); + assertEquals("123.00", rs.getBigDecimal(8).toString()); + assertEquals(64d, rs.getDouble(9)); + assertEquals(32d, rs.getFloat(10)); + assertEquals("10:00:00", rs.getString(11)); + assertEquals("2001-01-01", rs.getString(12)); + assertEquals("2010-10-10 10:10:10", rs.getString(13)); + assertEquals(1, rs.getBytes(14).length); + assertEquals(UUID.fromString("01234567-89AB-CDEF-0123-456789ABCDEF"), rs.getObject(15)); + assertEquals(1, rs.getBytes(16).length); + assertEquals("clob", rs.getString(17)); + rs.next(); + assertEquals(-1, rs.getInt(1)); + assertEquals("quite a long string \u1234 \u00ff", + rs.getString(2)); + assertEquals("ch ", rs.getString(3)); + assertFalse(rs.getBoolean(4)); + assertEquals(-8, rs.getByte(5)); + assertEquals(-16, rs.getShort(6)); + assertEquals(-64, rs.getLong(7)); + assertEquals("0.00", rs.getBigDecimal(8).toString()); + assertEquals(0.0d, rs.getDouble(9)); + assertEquals(0.0d, rs.getFloat(10)); + assertEquals("10:00:00", rs.getString(11)); + assertEquals("2001-01-01", rs.getString(12)); + assertEquals("2010-10-10 10:10:10", rs.getString(13)); + assertEquals(100, rs.getBytes(14).length); + assertEquals(2, rs.getObject(15, UUID.class).variant()); + assertEquals(1, rs.getBytes(16).length); + assertEquals("clob", rs.getString(17)); + rs.next(); + assertEquals(-1000, rs.getInt(1)); + assertEquals(1000, rs.getString(2).length()); + assertEquals("ch ", rs.getString(3)); + assertFalse(rs.getBoolean(4)); + assertEquals(-8, rs.getByte(5)); + assertEquals(-16, rs.getShort(6)); + assertEquals(-64, rs.getLong(7)); + assertEquals("1.00", rs.getBigDecimal(8).toString()); + assertEquals(1.0d, rs.getDouble(9)); + assertEquals(1.0d, rs.getFloat(10)); + assertEquals("10:00:00", rs.getString(11)); + assertEquals("2001-01-01", rs.getString(12)); + assertEquals("2010-10-10 10:10:10", rs.getString(13)); + assertEquals(100, rs.getBytes(14).length); + assertEquals(2, rs.getObject(15, UUID.class).variant()); + assertEquals(1, rs.getBytes(16).length); + assertEquals("clob", rs.getString(17)); + + stat.execute("drop table test"); + + stat.execute("create table test(id int, obj object, " + + "rs row(a int), arr1 int array, arr2 numeric(1000) array, ig varchar_ignorecase)"); + PreparedStatement prep = conn.prepareStatement( + "insert into test values(?, ?, ?, ?, ?, ?)"); + prep.setInt(1, 1); + prep.setObject(2, new java.lang.AssertionError()); + prep.setObject(3, stat.executeQuery("select 1 from dual")); + prep.setObject(4, new Object[]{1, 2}); + prep.setObject(5, new Object[0]); + prep.setObject(6, "test"); + prep.execute(); + prep.setInt(1, 1); + prep.setObject(2, new java.lang.AssertionError()); + prep.setObject(3, stat.executeQuery("select 1 from dual")); + prep.setObject(4, new Object[0]); + prep.setObject(5, new Object[]{ + new BigDecimal(new String( + new char[1000]).replace((char) 0, '1'))}); + prep.setObject(6, "test"); + prep.execute(); + if (!config.memory) { + conn.close(); + conn = getConnection(dbName); + stat = conn.createStatement(); + } + stat.execute("select * from test"); + + rs = stat.executeQuery("script"); + int count = 0; + while (rs.next()) { + count++; + } + assertTrue(count < 10); + + stat.execute("drop table test"); + conn.close(); + } + + private void testSimple() throws Exception { + deleteDb(getTestName()); + String dbName = getTestName() + ";MV_STORE=TRUE"; + Connection conn = getConnection(dbName); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name varchar)"); + stat.execute("insert into test values(1, 'Hello'), (2, 'World')"); + ResultSet rs = stat.executeQuery("select *, _rowid_ from test"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + assertEquals(1, rs.getInt(3)); + + stat.execute("update test set name = 'Hello' where id = 1"); + + if (!config.memory) { + conn.close(); + conn = getConnection(dbName); + stat = conn.createStatement(); + } + + rs = stat.executeQuery("select * from test order by id"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + assertEquals("World", rs.getString(2)); + assertFalse(rs.next()); + + stat.execute("create unique index idx_name on test(name)"); + rs = stat.executeQuery("select * from test " + + "where name = 'Hello' order by name"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + assertFalse(rs.next()); + + assertThrows(ErrorCode.DUPLICATE_KEY_1, stat).execute("insert into test(id, name) values(10, 'Hello')"); + + rs = stat.executeQuery("select min(id), max(id), " + + "min(name), max(name) from test"); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals(2, rs.getInt(2)); + assertEquals("Hello", rs.getString(3)); + assertEquals("World", rs.getString(4)); + assertFalse(rs.next()); + + stat.execute("delete from test where id = 2"); + rs = stat.executeQuery("select * from test order by id"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + assertFalse(rs.next()); + + stat.execute("alter table test add column firstName varchar"); + rs = stat.executeQuery("select * from test where name = 'Hello'"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + assertFalse(rs.next()); + + if (!config.memory) { + conn.close(); + conn = getConnection(dbName); + stat = conn.createStatement(); + } + + rs = stat.executeQuery("select * from test order by id"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + assertFalse(rs.next()); + + stat.execute("truncate table test"); + rs = stat.executeQuery("select * from test order by id"); + assertFalse(rs.next()); + + rs = stat.executeQuery("select count(*) from test"); + rs.next(); + assertEquals(0, rs.getInt(1)); + stat.execute("insert into test(id) select x from system_range(1, 3000)"); + rs = stat.executeQuery("select count(*) from test"); + rs.next(); + assertEquals(3000, rs.getInt(1)); + assertThrows(ErrorCode.DUPLICATE_KEY_1, stat).execute("insert into test(id) values(1)"); + stat.execute("delete from test"); + stat.execute("insert into test(id, name) values(-1, 'Hello')"); + rs = stat.executeQuery("select count(*) from test where id = -1"); + rs.next(); + assertEquals(1, rs.getInt(1)); + rs = stat.executeQuery("select count(*) from test where name = 'Hello'"); + rs.next(); + assertEquals(1, rs.getInt(1)); + conn.close(); + } + + private void testReverseDeletePerformance() throws Exception { + long direct = 0; + long reverse = 0; + for (int i = 0; i < 5; i++) { + reverse += testReverseDeletePerformance(true); + direct += testReverseDeletePerformance(false); + } + assertTrue("direct: " + direct + ", reverse: " + reverse, + 3 * Math.abs(reverse - direct) < 2 * (reverse + direct)); + } + + private long testReverseDeletePerformance(boolean reverse) throws Exception { + deleteDb(getTestName()); + String dbName = getTestName() + ";MV_STORE=TRUE"; + try (Connection conn = getConnection(dbName)) { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE test(id INT PRIMARY KEY, name VARCHAR) AS " + + "SELECT x, x || space(1024) || x FROM system_range(1, 1000)"); + conn.setAutoCommit(false); + PreparedStatement prep = conn.prepareStatement("DELETE FROM test WHERE id = ?"); + long start = System.nanoTime(); + for (int i = 0; i < 1000; i++) { + prep.setInt(1, reverse ? 1000 - i : i); + prep.execute(); + } + long end = System.nanoTime(); + conn.commit(); + return TimeUnit.NANOSECONDS.toMillis(end - start); + } + } +} diff --git a/h2/src/test/org/h2/test/store/TestObjectDataType.java b/h2/src/test/org/h2/test/store/TestObjectDataType.java new file mode 100644 index 0000000..8b4cc3a --- /dev/null +++ b/h2/src/test/org/h2/test/store/TestObjectDataType.java @@ -0,0 +1,185 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.sql.Timestamp; +import java.util.Arrays; +import java.util.Random; +import java.util.UUID; + +import org.h2.mvstore.WriteBuffer; +import org.h2.mvstore.type.ObjectDataType; +import org.h2.test.TestBase; + +/** + * Test the ObjectType class. + */ +public class TestObjectDataType extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() { + testCommonValues(); + } + + private void testCommonValues() { + BigInteger largeBigInt = BigInteger.probablePrime(200, new Random(1)); + ObjectDataType ot = new ObjectDataType(); + Object[] array = { + false, true, + Byte.MIN_VALUE, (byte) -1, (byte) 0, (byte) 1, Byte.MAX_VALUE, + Short.MIN_VALUE, (short) -1, (short) 0, (short) 1, Short.MAX_VALUE, + Integer.MIN_VALUE, Integer.MIN_VALUE + 1, + -1000, -100, -1, 0, 1, 2, 14, + 15, 16, 17, 100, Integer.MAX_VALUE - 1, Integer.MAX_VALUE, + Long.MIN_VALUE, Long.MIN_VALUE + 1, -1000L, -1L, 0L, 1L, 2L, 14L, + 15L, 16L, 17L, 100L, Long.MAX_VALUE - 1, Long.MAX_VALUE, + largeBigInt.negate(), BigInteger.valueOf(-1), BigInteger.ZERO, + BigInteger.ONE, BigInteger.TEN, largeBigInt, + Float.NEGATIVE_INFINITY, -Float.MAX_VALUE, -1f, -0f, 0f, + Float.MIN_VALUE, 1f, Float.MAX_VALUE, + Float.POSITIVE_INFINITY, Float.NaN, + Double.NEGATIVE_INFINITY, -Double.MAX_VALUE, -1d, -0d, 0d, + Double.MIN_VALUE, 1d, Double.MAX_VALUE, + Double.POSITIVE_INFINITY, Double.NaN, + BigDecimal.valueOf(Double.MAX_VALUE).negate(), + new BigDecimal(largeBigInt).negate(), + BigDecimal.valueOf(-100.0), BigDecimal.ZERO, BigDecimal.ONE, + BigDecimal.TEN, BigDecimal.valueOf(Long.MAX_VALUE), + new BigDecimal(largeBigInt), + BigDecimal.valueOf(Double.MAX_VALUE), + Character.MIN_VALUE, '0', 'a', Character.MAX_VALUE, + "", " ", " ", "123456789012345", "1234567890123456", + new String(new char[100]).replace((char) 0, 'x'), + new String(new char[100000]).replace((char) 0, 'x'), "y", + "\u1234", "\u2345", "\u6789", "\uffff", + new UUID(Long.MIN_VALUE, Long.MIN_VALUE), + new UUID(Long.MIN_VALUE, 0), new UUID(0, 0), + new UUID(Long.MAX_VALUE, Long.MAX_VALUE), + new java.util.Date(0), new java.util.Date(1000), + new java.util.Date(4000), new java.util.Date(5000), + new boolean[0], new boolean[] { false, false }, + new boolean[] { true }, + new byte[0], new byte[1], new byte[15], new byte[16], + new byte[10000], new byte[] { (byte) 1 }, + new byte[] { (byte) 0xff }, + new short[0], new short[] { -1 }, new short[] { 1 }, + new char[0], new char[1], new char[10000], + new char[] { (char) 1 }, + new int[0], new int[1], new int[15], new int[16], + new int[10000], new int[] { (byte) 1 }, + new long[0], new long[1], new long[15], new long[16], + new long[10000], new long[] { (byte) 1 }, + new float[0], new float[]{Float.NEGATIVE_INFINITY}, + new float[1], new float[]{Float.POSITIVE_INFINITY}, + new double[0], new double[]{Double.NEGATIVE_INFINITY}, + new double[1], new double[]{Double.POSITIVE_INFINITY}, + new Object[0], + new Object[100], + new Object[] { 1 }, + new Object[] { 0.0, "Hello", null, Double.NaN }, + new String[] { "Hello", null }, + new String[] { "World" }, + new java.sql.Date[] { }, + new Timestamp[] { }, + new Timestamp[] { null }, + new Timestamp(2000), new Timestamp(3000), + }; + Object otherType = false; + Object last = null; + for (Object x : array) { + test(otherType, x); + if (last != null) { + int comp = ot.compare(x, last); + if (comp <= 0) { + fail(x.getClass().getSimpleName() + ": " + + x.toString() + " " + comp); + } + assertTrue(x.toString(), ot.compare(last, x) < 0); + } + if (last != null && last.getClass() != x.getClass()) { + otherType = last; + } + last = x; + } + Random r = new Random(1); + for (int i = 0; i < 1000; i++) { + Object x = array[r.nextInt(array.length)]; + Object y = array[r.nextInt(array.length)]; + int comp = ot.compare(x, y); + if (comp != 0) { + assertEquals("x:" + x + " y:" + y, -comp, ot.compare(y, x)); + } + } + } + + private void test(Object last, Object x) { + ObjectDataType ot = new ObjectDataType(); + + // switch to the last type before every operation, + // to test switching types + ot.getMemory(last); + assertTrue(ot.getMemory(x) >= 0); + + ot.getMemory(last); + assertTrue(ot.getMemory(x) >= 0); + + ot.getMemory(last); + assertEquals(0, ot.compare(x, x)); + WriteBuffer buff = new WriteBuffer(); + + ot.getMemory(last); + ot.write(buff, x); + buff.put((byte) 123); + ByteBuffer bb = buff.getBuffer(); + bb.flip(); + + ot.getMemory(last); + Object y = ot.read(bb); + assertEquals(123, bb.get()); + assertEquals(0, bb.remaining()); + assertEquals(x.getClass().getName(), y.getClass().getName()); + + ot.getMemory(last); + assertEquals(0, ot.compare(x, y)); + if (x.getClass().isArray()) { + if (x instanceof byte[]) { + assertTrue(Arrays.equals((byte[]) x, (byte[]) y)); + } else if (x instanceof boolean[]) { + assertTrue(Arrays.equals((boolean[]) x, (boolean[]) y)); + } else if (x instanceof short[]) { + assertTrue(Arrays.equals((short[]) x, (short[]) y)); + } else if (x instanceof float[]) { + assertTrue(Arrays.equals((float[]) x, (float[]) y)); + } else if (x instanceof double[]) { + assertTrue(Arrays.equals((double[]) x, (double[]) y)); + } else if (x instanceof char[]) { + assertTrue(Arrays.equals((char[]) x, (char[]) y)); + } else if (x instanceof int[]) { + assertTrue(Arrays.equals((int[]) x, (int[]) y)); + } else if (x instanceof long[]) { + assertTrue(Arrays.equals((long[]) x, (long[]) y)); + } else { + assertTrue(Arrays.equals((Object[]) x, (Object[]) y)); + } + } else { + assertEquals(x.hashCode(), y.hashCode()); + assertTrue(x.equals(y)); + } + } + +} diff --git a/h2/src/test/org/h2/test/store/TestRandomMapOps.java b/h2/src/test/org/h2/test/store/TestRandomMapOps.java new file mode 100644 index 0000000..b3f75b4 --- /dev/null +++ b/h2/src/test/org/h2/test/store/TestRandomMapOps.java @@ -0,0 +1,285 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.TreeMap; +import org.h2.mvstore.Cursor; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVStore; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestAll; +import org.h2.test.TestBase; + +/** + * Tests the MVStore. + */ +public class TestRandomMapOps extends TestBase { + + private static final boolean LOG = false; + private final Random r = new Random(); + private int op; + + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); + TestAll config = test.config; + config.big = true; +// config.memory = true; + + test.println(config.toString()); + for (int i = 0; i < 10; i++) { + test.testFromMain(); + test.println("Done pass #" + i); + } + } + + @Override + public void test() throws Exception { + if (config.memory) { + testMap(null); + } else { + String fileName = "memFS:" + getTestName(); + testMap(fileName); + } + } + + private void testMap(String fileName) { + int size = getSize(500, 3000); + long seed = 0; +// seed = System.currentTimeMillis(); +// seed = -3407210256209708616L; + for (int cnt = 0; cnt < 100; cnt++) { + try { + testOps(fileName, size, seed); + } catch (Exception | AssertionError ex) { + println("seed:" + seed + " op:" + op + " " + ex); + throw ex; + } finally { + if (fileName != null) { + FileUtils.delete(fileName); + } + } + seed = r.nextLong(); + } + } + + private void testOps(String fileName, int loopCount, long seed) { + r.setSeed(seed); + op = 0; + MVStore s = openStore(fileName); + int keysPerPage = s.getKeysPerPage(); + int keyRange = 2000; + MVMap m = s.openMap("data"); + TreeMap map = new TreeMap<>(); + int[] recentKeys = new int[2 * keysPerPage]; + for (; op < loopCount; op++) { + int k = r.nextInt(3 * keyRange / 2); + if (k >= keyRange) { + k = recentKeys[k % recentKeys.length]; + } else { + recentKeys[op % recentKeys.length] = k; + } + String v = k + "_Value_" + op; + int type = r.nextInt(15); + switch (type) { + case 0: + case 1: + case 2: + case 3: + log(op, k, v, "m.put({0}, {1})"); + m.put(k, v); + map.put(k, v); + break; + case 4: + case 5: + log(op, k, v, "m.remove({0})"); + m.remove(k); + map.remove(k); + break; + case 6: + log(op, k, v, "s.compact(90, 1024)"); + s.compact(90, 1024); + break; + case 7: + if (op % 64 == 0) { + log(op, k, v, "m.clear()"); + m.clear(); + map.clear(); + } + break; + case 8: + log(op, k, v, "s.commit()"); + s.commit(); + break; + case 9: + if (fileName != null) { + log(op, k, v, "s.commit()"); + s.commit(); + log(op, k, v, "s.close()"); + s.close(); + log(op, k, v, "s = openStore(fileName)"); + s = openStore(fileName); + log(op, k, v, "m = s.openMap(\"data\")"); + m = s.openMap("data"); + } + break; + case 10: + log(op, k, v, "s.commit()"); + s.commit(); + log(op, k, v, "s.compactMoveChunks()"); + s.compactMoveChunks(); + break; + case 11: { + int rangeSize = r.nextInt(2 * keysPerPage); + int step = r.nextBoolean() ? 1 : -1; + for (int i = 0; i < rangeSize; i++) { + log(op, k, v, "m.put({0}, {1})"); + m.put(k, v); + map.put(k, v); + k += step; + v = k + "_Value_" + op; + } + break; + } + case 12: { + int rangeSize = r.nextInt(2 * keysPerPage); + int step = r.nextBoolean() ? 1 : -1; + for (int i = 0; i < rangeSize; i++) { + log(op, k, v, "m.remove({0})"); + m.remove(k); + map.remove(k); + k += step; + } + break; + } + default: + log(op, k, v, "m.getKeyIndex({0})"); + ArrayList keyList = new ArrayList<>(map.keySet()); + int index = Collections.binarySearch(keyList, k, null); + int index2 = (int) m.getKeyIndex(k); + assertEquals(index, index2); + if (index >= 0) { + int k2 = m.getKey(index); + assertEquals(k2, k); + } + break; + } + assertEquals(map.get(k), m.get(k)); + assertEquals(map.ceilingKey(k), m.ceilingKey(k)); + assertEquals(map.floorKey(k), m.floorKey(k)); + assertEquals(map.higherKey(k), m.higherKey(k)); + assertEquals(map.lowerKey(k), m.lowerKey(k)); + assertEquals(map.isEmpty(), m.isEmpty()); + assertEquals(map.size(), m.size()); + if (!map.isEmpty()) { + assertEquals(map.firstKey(), m.firstKey()); + assertEquals(map.lastKey(), m.lastKey()); + } + + int from = r.nextBoolean() ? r.nextInt(keyRange) : k + r.nextInt(2 * keysPerPage) - keysPerPage; + int to = r.nextBoolean() ? r.nextInt(keyRange) : from + r.nextInt(2 * keysPerPage) - keysPerPage; + + Cursor cursor; + Collection> entrySet; + String msg; + if (from <= to) { + msg = "(" + from + ", null)"; + cursor = m.cursor(from, null, false); + entrySet = map.tailMap(from).entrySet(); + assertEquals(msg, entrySet, cursor); + + msg = "(null, " + from + ")"; + cursor = m.cursor(null, from, false); + entrySet = map.headMap(from + 1).entrySet(); + assertEquals(msg, entrySet, cursor); + + msg = "(" + from + ", " + to + ")"; + cursor = m.cursor(from, to, false); + entrySet = map.subMap(from, to + 1).entrySet(); + assertEquals(msg, entrySet, cursor); + } + + if (from >= to) { + msg = "rev (" + from + ", null)"; + cursor = m.cursor(from, null, true); + entrySet = reverse(map.headMap(from + 1).entrySet()); + assertEquals(msg, entrySet, cursor); + + msg = "rev (null, "+from+")"; + cursor = m.cursor(null, from, true); + entrySet = reverse(map.tailMap(from).entrySet()); + assertEquals(msg, entrySet, cursor); + + msg = "rev (" + from + ", " + to + ")"; + cursor = m.cursor(from, to, true); + entrySet = reverse(map.subMap(to, from + 1).entrySet()); + assertEquals(msg, entrySet, cursor); + } + } + s.close(); + } + + private static Collection> reverse(Collection> entrySet) { + ArrayList> list = new ArrayList<>(entrySet); + Collections.reverse(list); + entrySet = list; + return entrySet; + } + + private void assertEquals(String msg, Iterable> entrySet, Cursor cursor) { + int cnt = 0; + for (Map.Entry entry : entrySet) { + String message = msg + " " + cnt; + assertTrue(message, cursor.hasNext()); + assertEquals(message, entry.getKey(), cursor.next()); + assertEquals(message, entry.getKey(), cursor.getKey()); + assertEquals(message, entry.getValue(), cursor.getValue()); + ++cnt; + } + assertFalse(msg, cursor.hasNext()); + } + + public void assertEquals(String message, Object expected, Object actual) { + if (!Objects.equals(expected, actual)) { + fail(message + " expected: " + expected + " actual: " + actual); + } + } + + private static MVStore openStore(String fileName) { + MVStore s = new MVStore.Builder().fileName(fileName) + .keysPerPage(7).autoCommitDisabled().open(); + s.setRetentionTime(1000); + return s; + } + + /** + * Log the operation + * + * @param op the operation id + * @param k the key + * @param v the value + * @param msg the message + */ + private static void log(int op, int k, String v, String msg) { + if (LOG) { + msg = MessageFormat.format(msg, k, v); + System.out.println(msg + "; // op " + op); + } + } + +} diff --git a/h2/src/test/org/h2/test/store/TestShardedMap.java b/h2/src/test/org/h2/test/store/TestShardedMap.java new file mode 100644 index 0000000..6934560 --- /dev/null +++ b/h2/src/test/org/h2/test/store/TestShardedMap.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.test.store; + +import java.util.TreeMap; + +import org.h2.dev.cluster.ShardedMap; +import org.h2.test.TestBase; + +/** + * Test sharded maps. + */ +public class TestShardedMap extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() { + testLinearSplit(); + testReplication(); + testOverlap(); + } + + private void testLinearSplit() { + ShardedMap map = new ShardedMap<>(); + TreeMap a = new TreeMap<>(); + TreeMap b = new TreeMap<>(); + map.addMap(a, null, 5); + map.addMap(b, 5, null); + for (int i = 0; i < 10; i++) { + map.put(i, i * 10); + } + assertEquals(10, map.size()); + for (int i = 0; i < 10; i++) { + assertEquals(i * 10, map.get(i).intValue()); + } + assertEquals("[0, 1, 2, 3, 4]", + a.keySet().toString()); + assertEquals("[5, 6, 7, 8, 9]", + b.keySet().toString()); + assertEquals("[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]", + map.keySet().toString()); + assertEquals(10, map.sizeAsLong()); + } + + private void testReplication() { + ShardedMap map = new ShardedMap<>(); + TreeMap a = new TreeMap<>(); + TreeMap b = new TreeMap<>(); + map.addMap(a, null, null); + map.addMap(b, null, null); + for (int i = 0; i < 10; i++) { + map.put(i, i * 10); + } + assertEquals(10, map.size()); + for (int i = 0; i < 10; i++) { + assertEquals(i * 10, map.get(i).intValue()); + } + assertEquals("[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]", + a.keySet().toString()); + assertEquals("[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]", + b.keySet().toString()); + assertEquals("[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]", + map.keySet().toString()); + assertEquals(10, map.sizeAsLong()); + } + + private void testOverlap() { + ShardedMap map = new ShardedMap<>(); + TreeMap a = new TreeMap<>(); + TreeMap b = new TreeMap<>(); + map.addMap(a, null, 10); + map.addMap(b, 5, null); + for (int i = 0; i < 20; i++) { + map.put(i, i * 10); + } + // overlap: size is unknown + assertEquals(-1, map.size()); + for (int i = 0; i < 20; i++) { + assertEquals(i * 10, map.get(i).intValue()); + } + assertEquals("[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]", + a.keySet().toString()); + assertEquals("[5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]", + b.keySet().toString()); + assertEquals(-1, map.sizeAsLong()); + } + +} \ No newline at end of file diff --git a/h2/src/test/org/h2/test/store/TestSpinLock.java b/h2/src/test/org/h2/test/store/TestSpinLock.java new file mode 100644 index 0000000..693d6ab --- /dev/null +++ b/h2/src/test/org/h2/test/store/TestSpinLock.java @@ -0,0 +1,155 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import org.h2.test.TestBase; + +/** + * Test using volatile fields to ensure we don't read from a version that is + * concurrently written to. + */ +public class TestSpinLock extends TestBase { + + /** + * The version to use for writing. + */ + volatile int writeVersion; + + /** + * The current data object. + */ + volatile Data data = new Data(0, null); + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + final TestSpinLock obj = new TestSpinLock(); + Thread t = new Thread() { + @Override + public void run() { + while (!isInterrupted()) { + for (int i = 0; i < 10000; i++) { + Data d = obj.copyOnWrite(); + obj.data = d; + d.write(i); + d.writing = false; + } + } + } + }; + t.start(); + try { + for (int i = 0; i < 100000; i++) { + Data d = obj.getImmutable(); + int z = d.x + d.y; + if (z != 0) { + String error = i + " result: " + z + " now: " + d.x + " " + + d.y; + System.out.println(error); + throw new Exception(error); + } + } + } finally { + t.interrupt(); + t.join(); + } + } + + /** + * Clone the data object if necessary (if the write version is newer than + * the current version). + * + * @return the data object + */ + Data copyOnWrite() { + Data d = data; + d.writing = true; + int w = writeVersion; + if (w <= data.version) { + return d; + } + Data d2 = new Data(w, data); + d2.writing = true; + d.writing = false; + return d2; + } + + /** + * Get an immutable copy of the data object. + * + * @return the immutable object + */ + private Data getImmutable() { + Data d = data; + ++writeVersion; + // wait until writing is done, + // but only for the current write operation: + // a bit like a spin lock + while (d.writing) { + // Thread.yield() is not required, specially + // if there are multiple cores + // but getImmutable() doesn't + // need to be that fast actually + Thread.yield(); + } + return d; + } + + /** + * The data class - represents the root page. + */ + static class Data { + + /** + * The version. + */ + final int version; + + /** + * The values. + */ + int x, y; + + /** + * Whether a write operation is in progress. + */ + volatile boolean writing; + + /** + * Create a copy of the data. + * + * @param version the new version + * @param old the old data or null + */ + Data(int version, Data old) { + this.version = version; + if (old != null) { + this.x = old.x; + this.y = old.y; + } + } + + /** + * Write to the fields in an unsynchronized way. + * + * @param value the new value + */ + void write(int value) { + this.x = value; + this.y = -value; + } + + } + +} diff --git a/h2/src/test/org/h2/test/store/TestStreamStore.java b/h2/src/test/org/h2/test/store/TestStreamStore.java new file mode 100644 index 0000000..1704fda --- /dev/null +++ b/h2/src/test/org/h2/test/store/TestStreamStore.java @@ -0,0 +1,466 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicInteger; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVStore; +import org.h2.mvstore.StreamStore; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.util.IOUtils; +import org.h2.util.StringUtils; + +/** + * Test the stream store. + */ +public class TestStreamStore extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws IOException { + FileUtils.createDirectories(getBaseDir()); + testMaxBlockKey(); + testIOException(); + testSaveCount(); + testExceptionDuringStore(); + testReadCount(); + testLarge(); + testDetectIllegalId(); + testTreeStructure(); + testFormat(); + testWithExistingData(); + testWithFullMap(); + testLoop(); + } + + private void testMaxBlockKey() throws IOException { + TreeMap map = new TreeMap<>(); + StreamStore s = new StreamStore(map); + s.setMaxBlockSize(128); + s.setMinBlockSize(64); + map.clear(); + for (int len = 1; len < 1024 * 1024; len *= 2) { + byte[] id = s.put(new ByteArrayInputStream(new byte[len])); + long max = s.getMaxBlockKey(id); + if (max == -1) { + assertTrue(map.isEmpty()); + } else { + assertEquals(map.lastKey(), (Long) max); + } + } + } + + private void testIOException() throws IOException { + HashMap map = new HashMap<>(); + StreamStore s = new StreamStore(map); + byte[] id = s.put(new ByteArrayInputStream(new byte[1024 * 1024])); + InputStream in = s.get(id); + map.clear(); + try { + while (true) { + if (in.read() < 0) { + break; + } + } + fail(); + } catch (IOException e) { + checkErrorCode(DataUtils.ERROR_BLOCK_NOT_FOUND, e.getCause()); + } + } + + private void testSaveCount() throws IOException { + String fileName = getBaseDir() + "/testSaveCount.h3"; + FileUtils.delete(fileName); + MVStore s = new MVStore.Builder(). + fileName(fileName). + open(); + MVMap map = s.openMap("data"); + StreamStore streamStore = new StreamStore(map); + int blockSize = 256 * 1024; + assertEquals(blockSize, streamStore.getMaxBlockSize()); + for (int i = 0; i < 8 * 16; i++) { + streamStore.put(new RandomStream(blockSize, i)); + } + s.close(); + long writeCount = s.getFileStore().getWriteCount(); + assertTrue(writeCount > 5); + } + + private void testExceptionDuringStore() throws IOException { + // test that if there is an IOException while storing + // the data, the entries in the map are "rolled back" + HashMap map = new HashMap<>(); + StreamStore s = new StreamStore(map); + s.setMaxBlockSize(1024); + assertThrows(IOException.class, () -> s.put(createFailingStream(new IOException()))); + assertEquals(0, map.size()); + // the runtime exception is converted to an IOException + assertThrows(IOException.class, () -> s.put(createFailingStream(new IllegalStateException()))); + assertEquals(0, map.size()); + } + + private void testReadCount() throws IOException { + String fileName = getBaseDir() + "/testReadCount.h3"; + FileUtils.delete(fileName); + MVStore s = new MVStore.Builder(). + fileName(fileName). + open(); + s.setCacheSize(1); + StreamStore streamStore = getAutoCommitStreamStore(s); + long size = s.getPageSplitSize() * 2; + for (int i = 0; i < 100; i++) { + streamStore.put(new RandomStream(size, i)); + } + s.commit(); + MVMap map = s.openMap("data"); + assertTrue("size: " + map.size(), map.sizeAsLong() >= 100); + s.close(); + + s = new MVStore.Builder(). + fileName(fileName). + open(); + streamStore = getAutoCommitStreamStore(s); + for (int i = 0; i < 100; i++) { + streamStore.put(new RandomStream(size, -i)); + } + s.commit(); + long readCount = s.getFileStore().getReadCount(); + // the read count should be low because new blocks + // are appended at the end (not between existing blocks) + assertTrue("rc: " + readCount, readCount <= 20); + map = s.openMap("data"); + assertTrue("size: " + map.size(), map.sizeAsLong() >= 200); + s.close(); + } + + private static StreamStore getAutoCommitStreamStore(final MVStore s) { + MVMap map = s.openMap("data"); + return new StreamStore(map) { + @Override + protected void onStore(int len) { + if (s.getUnsavedMemory() > s.getAutoCommitMemory() / 2) { + s.commit(); + } + } + }; + } + + private void testLarge() throws IOException { + String fileName = getBaseDir() + "/testVeryLarge.h3"; + FileUtils.delete(fileName); + final MVStore s = new MVStore.Builder(). + fileName(fileName). + open(); + MVMap map = s.openMap("data"); + final AtomicInteger count = new AtomicInteger(); + StreamStore streamStore = new StreamStore(map) { + @Override + protected void onStore(int len) { + count.incrementAndGet(); + s.commit(); + } + }; + long size = 1 * 1024 * 1024; + streamStore.put(new RandomStream(size, 0)); + s.close(); + assertEquals(4, count.get()); + } + + /** + * A stream of incompressible data. + */ + static class RandomStream extends InputStream { + + private long pos, size; + private int seed; + + RandomStream(long size, int seed) { + this.size = size; + this.seed = seed; + } + + @Override + public int read() { + byte[] data = new byte[1]; + int len = read(data, 0, 1); + return len <= 0 ? len : data[0] & 255; + } + + @Override + public int read(byte[] b, int off, int len) { + if (pos >= size) { + return -1; + } + len = (int) Math.min(size - pos, len); + int x = seed, end = off + len; + // a fast and very simple pseudo-random number generator + // with a period length of 4 GB + // also good: x * 9 + 1, shift 6; x * 11 + 1, shift 7 + while (off < end) { + x = (x << 4) + x + 1; + b[off++] = (byte) (x >> 8); + } + seed = x; + pos += len; + return len; + } + + } + + private void testDetectIllegalId() { + Map map = new HashMap<>(); + StreamStore store = new StreamStore(map); + assertThrows(IllegalArgumentException.class, () -> store.length(new byte[]{3, 0, 0})); + assertThrows(IllegalArgumentException.class, () -> store.remove(new byte[]{3, 0, 0})); + map.put(0L, new byte[]{3, 0, 0}); + InputStream in = store.get(new byte[]{2, 1, 0}); + assertThrows(IllegalArgumentException.class, () -> in.read()); + } + + private void testTreeStructure() throws IOException { + + final AtomicInteger reads = new AtomicInteger(); + Map map = new HashMap() { + + private static final long serialVersionUID = 1L; + + @Override + public byte[] get(Object k) { + reads.incrementAndGet(); + return super.get(k); + } + + }; + + StreamStore store = new StreamStore(map); + store.setMinBlockSize(10); + store.setMaxBlockSize(100); + byte[] id = store.put(new ByteArrayInputStream(new byte[10000])); + InputStream in = store.get(id); + assertEquals(0, in.read(new byte[0])); + assertEquals(0, in.read()); + assertEquals(3, reads.get()); + } + + private void testFormat() throws IOException { + Map map = new HashMap<>(); + StreamStore store = new StreamStore(map); + store.setMinBlockSize(10); + store.setMaxBlockSize(20); + store.setNextKey(123); + + byte[] id; + + id = store.put(new ByteArrayInputStream(new byte[200])); + assertEquals(200, store.length(id)); + assertEquals("02c8018801", StringUtils.convertBytesToHex(id)); + + id = store.put(new ByteArrayInputStream(new byte[0])); + assertEquals("", StringUtils.convertBytesToHex(id)); + + id = store.put(new ByteArrayInputStream(new byte[1])); + assertEquals("000100", StringUtils.convertBytesToHex(id)); + + id = store.put(new ByteArrayInputStream(new byte[3])); + assertEquals("0003000000", StringUtils.convertBytesToHex(id)); + + id = store.put(new ByteArrayInputStream(new byte[10])); + assertEquals("010a8901", StringUtils.convertBytesToHex(id)); + + byte[] combined = StringUtils.convertHexToBytes("0001aa0002bbcc"); + assertEquals(3, store.length(combined)); + InputStream in = store.get(combined); + assertEquals(1, in.skip(1)); + assertEquals(0xbb, in.read()); + assertEquals(1, in.skip(1)); + } + + private void testWithExistingData() throws IOException { + + final AtomicInteger tests = new AtomicInteger(); + Map map = new HashMap() { + + private static final long serialVersionUID = 1L; + + @Override + public boolean containsKey(Object k) { + tests.incrementAndGet(); + return super.containsKey(k); + } + + }; + StreamStore store = new StreamStore(map); + store.setMinBlockSize(10); + store.setMaxBlockSize(20); + store.setNextKey(0); + for (int i = 0; i < 10; i++) { + store.put(new ByteArrayInputStream(new byte[20])); + } + assertEquals(10, map.size()); + assertEquals(10, tests.get()); + for (int i = 0; i < 10; i++) { + map.containsKey((long)i); + } + assertEquals(20, tests.get()); + store = new StreamStore(map); + store.setMinBlockSize(10); + store.setMaxBlockSize(20); + store.setNextKey(0); + assertEquals(0, store.getNextKey()); + for (int i = 0; i < 5; i++) { + store.put(new ByteArrayInputStream(new byte[20])); + } + assertEquals(88, tests.get()); + assertEquals(15, store.getNextKey()); + assertEquals(15, map.size()); + for (int i = 0; i < 15; i++) { + map.containsKey((long)i); + } + } + + private void testWithFullMap() throws IOException { + final AtomicInteger tests = new AtomicInteger(); + Map map = new HashMap() { + + private static final long serialVersionUID = 1L; + + @Override + public boolean containsKey(Object k) { + tests.incrementAndGet(); + if (((Long) k) < Long.MAX_VALUE / 2) { + // simulate a *very* full map + return true; + } + return super.containsKey(k); + } + + }; + StreamStore store = new StreamStore(map); + store.setMinBlockSize(20); + store.setMaxBlockSize(100); + store.setNextKey(0); + store.put(new ByteArrayInputStream(new byte[100])); + assertEquals(1, map.size()); + assertEquals(64, tests.get()); + assertEquals(Long.MAX_VALUE / 2 + 1, store.getNextKey()); + } + + private void testLoop() throws IOException { + Map map = new HashMap<>(); + StreamStore store = new StreamStore(map); + assertEquals(256 * 1024, store.getMaxBlockSize()); + assertEquals(256, store.getMinBlockSize()); + store.setNextKey(0); + assertEquals(0, store.getNextKey()); + test(store, 10, 20, 1000); + for (int i = 0; i < 20; i++) { + test(store, 0, 128, i); + test(store, 10, 128, i); + } + for (int i = 20; i < 200; i += 10) { + test(store, 0, 128, i); + test(store, 10, 128, i); + } + } + + private void test(StreamStore store, int minBlockSize, int maxBlockSize, + int length) throws IOException { + store.setMinBlockSize(minBlockSize); + assertEquals(minBlockSize, store.getMinBlockSize()); + store.setMaxBlockSize(maxBlockSize); + assertEquals(maxBlockSize, store.getMaxBlockSize()); + long next = store.getNextKey(); + Random r = new Random(length); + byte[] data = new byte[length]; + r.nextBytes(data); + byte[] id = store.put(new ByteArrayInputStream(data)); + if (length > 0 && length >= minBlockSize) { + assertFalse(store.isInPlace(id)); + } else { + assertTrue(store.isInPlace(id)); + } + long next2 = store.getNextKey(); + if (length > 0 && length >= minBlockSize) { + assertTrue(next2 > next); + } else { + assertEquals(next, next2); + } + if (length == 0) { + assertEquals(0, id.length); + } + + assertEquals(length, store.length(id)); + + InputStream in = store.get(id); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + IOUtils.copy(in, out); + assertTrue(Arrays.equals(data, out.toByteArray())); + + in = store.get(id); + in.close(); + assertEquals(-1, in.read()); + + in = store.get(id); + assertEquals(0, in.skip(0)); + if (length > 0) { + assertEquals(1, in.skip(1)); + if (length > 1) { + assertEquals(data[1] & 255, in.read()); + if (length > 2) { + assertEquals(1, in.skip(1)); + if (length > 3) { + assertEquals(data[3] & 255, in.read()); + } + } else { + assertEquals(0, in.skip(1)); + } + } else { + assertEquals(-1, in.read()); + } + } else { + assertEquals(0, in.skip(1)); + } + + if (length > 12) { + in = store.get(id); + assertEquals(12, in.skip(12)); + assertEquals(data[12] & 255, in.read()); + long skipped = 0; + while (true) { + long s = in.skip(Integer.MAX_VALUE); + if (s == 0) { + break; + } + skipped += s; + } + assertEquals(length - 13, skipped); + assertEquals(-1, in.read()); + } + + store.remove(id); + assertEquals(0, store.getMap().size()); + } + +} diff --git a/h2/src/test/org/h2/test/store/TestTransactionStore.java b/h2/src/test/org/h2/test/store/TestTransactionStore.java new file mode 100644 index 0000000..07fee70 --- /dev/null +++ b/h2/src/test/org/h2/test/store/TestTransactionStore.java @@ -0,0 +1,1009 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.store; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.MVStore; +import org.h2.mvstore.MVStoreException; +import org.h2.mvstore.tx.Transaction; +import org.h2.mvstore.tx.TransactionMap; +import org.h2.mvstore.tx.TransactionStore; +import org.h2.mvstore.tx.TransactionStore.Change; +import org.h2.mvstore.type.LongDataType; +import org.h2.mvstore.type.MetaType; +import org.h2.mvstore.type.ObjectDataType; +import org.h2.mvstore.type.StringDataType; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.util.Task; + +/** + * Test concurrent transactions. + */ +public class TestTransactionStore extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + FileUtils.createDirectories(getBaseDir()); + testHCLFKey(); + testConcurrentAddRemove(); + testConcurrentAdd(); + testCountWithOpenTransactions(); + testConcurrentUpdate(); + testRepeatedChange(); + testTransactionAge(); + testGetModifiedMaps(); + testKeyIterator(); + testTwoPhaseCommit(); + testSavepoint(); + testConcurrentTransactionsReadCommitted(); + testSingleConnection(); + testCompareWithPostgreSQL(); + testStoreMultiThreadedReads(); + testCommitAfterMapRemoval(); + testDeadLock(); + } + + private void testHCLFKey() { + try (MVStore s = MVStore.open(null)) { + TransactionStore ts = new TransactionStore(s); + ts.init(); + Transaction t = ts.begin(); + LongDataType keyType = LongDataType.INSTANCE; + TransactionMap map = t.openMap("test", keyType, keyType); + // firstEntry() & firstKey() + assertNull(map.firstEntry()); + assertNull(map.firstKey()); + // lastEntry() & lastKey() + assertNull(map.lastEntry()); + assertNull(map.lastKey()); + map.put(10L, 100L); + map.put(20L, 200L); + map.put(30L, 300L); + map.put(40L, 400L); + t.commit(); + t = ts.begin(); + map = t.openMap("test", keyType, keyType); + map.put(15L, 150L); + // The same transaction + assertEquals(new SimpleImmutableEntry<>(15L, 150L), map.higherEntry(10L)); + assertEquals((Object) 15L, map.higherKey(10L)); + t = ts.begin(); + map = t.openMap("test", keyType, keyType); + // Another transaction + // firstEntry() & firstKey() + assertEquals(new SimpleImmutableEntry<>(10L, 100L), map.firstEntry()); + assertEquals((Object) 10L, map.firstKey()); + // lastEntry() & lastKey() + assertEquals(new SimpleImmutableEntry<>(40L, 400L),map.lastEntry()); + assertEquals((Object) 40L, map.lastKey()); + // higherEntry() & higherKey() + assertEquals(new SimpleImmutableEntry<>(20L, 200L), map.higherEntry(10L)); + assertEquals((Object) 20L, map.higherKey(10L)); + assertEquals(new SimpleImmutableEntry<>(20L, 200L), map.higherEntry(15L)); + assertEquals((Object) 20L, map.higherKey(15L)); + assertNull(map.higherEntry(40L)); + assertNull(map.higherKey(40L)); + // ceilingEntry() & ceilingKey() + assertEquals(new SimpleImmutableEntry<>(10L, 100L), map.ceilingEntry(10L)); + assertEquals((Object) 10L, map.ceilingKey(10L)); + assertEquals(new SimpleImmutableEntry<>(20L, 200L), map.ceilingEntry(15L)); + assertEquals((Object) 20L, map.ceilingKey(15L)); + assertEquals(new SimpleImmutableEntry<>(40L, 400L), map.ceilingEntry(40L)); + assertEquals((Object) 40L, map.ceilingKey(40L)); + assertNull(map.higherEntry(45L)); + assertNull(map.higherKey(45L)); + // lowerEntry() & lowerKey() + assertNull(map.lowerEntry(10L)); + assertNull(map.lowerKey(10L)); + assertEquals(new SimpleImmutableEntry<>(10L, 100L), map.lowerEntry(15L)); + assertEquals((Object) 10L, map.lowerKey(15L)); + assertEquals(new SimpleImmutableEntry<>(10L, 100L), map.lowerEntry(20L)); + assertEquals((Object) 10L, map.lowerKey(20L)); + assertEquals(new SimpleImmutableEntry<>(20L, 200L), map.lowerEntry(25L)); + assertEquals((Object) 20L, map.lowerKey(25L)); + // floorEntry() & floorKey() + assertNull(map.floorEntry(5L)); + assertNull(map.floorKey(5L)); + assertEquals(new SimpleImmutableEntry<>(10L, 100L), map.floorEntry(10L)); + assertEquals((Object) 10L, map.floorKey(10L)); + assertEquals(new SimpleImmutableEntry<>(10L, 100L), map.floorEntry(15L)); + assertEquals((Object) 10L, map.floorKey(15L)); + assertEquals(new SimpleImmutableEntry<>(30L, 300L), map.floorEntry(35L)); + assertEquals((Object) 30L, map.floorKey(35L)); + } + } + + private static void testConcurrentAddRemove() throws InterruptedException { + try (MVStore s = MVStore.open(null)) { + int threadCount = 3; + int keyCount = 2; + TransactionStore ts = new TransactionStore(s); + ts.init(); + + final Random r = new Random(1); + + Task[] tasks = new Task[threadCount]; + for (int i = 0; i < threadCount; i++) { + Task task = new Task() { + @Override + public void call() { + while (!stop) { + Transaction tx = ts.begin(); + TransactionMap map = tx.openMap("data"); + int k = r.nextInt(keyCount); + try { + map.remove(k); + map.put(k, r.nextInt()); + } catch (MVStoreException e) { + // ignore and retry + } + tx.commit(); + } + } + }; + task.execute(); + tasks[i] = task; + } + Thread.sleep(1000); + for (Task t : tasks) { + t.get(); + } + } + } + + private void testConcurrentAdd() { + try (MVStore s = MVStore.open(null)) { + TransactionStore ts = new TransactionStore(s); + ts.init(); + + Random r = new Random(1); + + AtomicInteger key = new AtomicInteger(); + AtomicInteger failCount = new AtomicInteger(); + + Task task = new Task() { + + @Override + public void call() { + while (!stop) { + int k = key.get(); + Transaction tx = ts.begin(); + TransactionMap map = tx.openMap("data"); + try { + map.put(k, r.nextInt()); + } catch (MVStoreException e) { + failCount.incrementAndGet(); + // ignore and retry + } + tx.commit(); + } + } + + }; + task.execute(); + int count = 100000; + for (int i = 0; i < count; i++) { + key.set(i); + Transaction tx = ts.begin(); + TransactionMap map = tx.openMap("data"); + try { + map.put(i, r.nextInt()); + } catch (MVStoreException e) { + failCount.incrementAndGet(); + // ignore and retry + } + tx.commit(); + if (failCount.get() > 0 && i > 4000) { + // stop earlier, if possible + count = i; + break; + } + } + task.get(); + // we expect at least 10% the operations were successful + assertTrue(failCount + " >= " + (count * 0.9), + failCount.get() < count * 0.9); + // we expect at least a few failures + assertTrue(failCount.toString(), failCount.get() > 0); + } + } + + private void testCountWithOpenTransactions() { + try (MVStore s = MVStore.open(null)) { + TransactionStore ts = new TransactionStore(s); + ts.init(); + + Transaction tx1 = ts.begin(); + TransactionMap map1 = tx1.openMap("data"); + int size = 150; + for (int i = 0; i < size; i++) { + map1.put(i, i * 10); + } + tx1.commit(); + tx1 = ts.begin(); + map1 = tx1.openMap("data"); + + Transaction tx2 = ts.begin(); + TransactionMap map2 = tx2.openMap("data"); + + Random r = new Random(1); + for (int i = 0; i < size * 3; i++) { + assertEquals("op: " + i, size, map1.size()); + assertEquals("op: " + i, size, (int) map1.sizeAsLong()); + // keep the first 10%, and add 10% + int k = size / 10 + r.nextInt(size); + if (r.nextBoolean()) { + map2.remove(k); + } else { + map2.put(k, i); + } + } + } + } + + private void testConcurrentUpdate() { + try (MVStore s = MVStore.open(null)) { + TransactionStore ts = new TransactionStore(s); + ts.init(); + + Transaction tx1 = ts.begin(); + TransactionMap map1 = tx1.openMap("data"); + map1.put(1, 10); + + Transaction tx2 = ts.begin(); + TransactionMap map2 = tx2.openMap("data"); + assertThrows(DataUtils.ERROR_TRANSACTION_LOCKED, () -> map2.put(1, 20)); + assertEquals(10, map1.get(1).intValue()); + assertNull(map2.get(1)); + tx1.commit(); + assertEquals(10, map2.get(1).intValue()); + } + } + + private void testRepeatedChange() { + try (MVStore s = MVStore.open(null)) { + TransactionStore ts = new TransactionStore(s); + ts.init(); + + Transaction tx0 = ts.begin(); + TransactionMap map0 = tx0.openMap("data"); + map0.put(1, -1); + tx0.commit(); + + Transaction tx = ts.begin(); + TransactionMap map = tx.openMap("data"); + for (int i = 0; i < 2000; i++) { + map.put(1, i); + } + + Transaction tx2 = ts.begin(); + TransactionMap map2 = tx2.openMap("data"); + assertEquals(-1, map2.get(1).intValue()); + } + } + + private void testTransactionAge() { + MVStore s; + TransactionStore ts; + s = MVStore.open(null); + ts = new TransactionStore(s); + ts.init(); + ts.setMaxTransactionId(16); + ArrayList openList = new ArrayList<>(); + for (int i = 0, j = 1; i < 64; i++) { + Transaction t = ts.begin(); + openList.add(t); + assertEquals(j, t.getId()); + j++; + if (j > 16) { + j = 1; + } + if (openList.size() >= 16) { + t = openList.remove(0); + t.commit(); + } + } + + s = MVStore.open(null); + TransactionStore ts2 = new TransactionStore(s); + ts2.init(); + ts2.setMaxTransactionId(16); + ArrayList fifo = new ArrayList<>(); + int open = 0; + for (int i = 0; i < 64; i++) { + if (open >= 16) { + assertThrows(MVStoreException.class, () -> ts2.begin()); + Transaction first = fifo.remove(0); + first.commit(); + open--; + } + Transaction t = ts2.begin(); + t.openMap("data").put(i, i); + fifo.add(t); + open++; + } + s.close(); + } + + private void testGetModifiedMaps() { + try (MVStore s = MVStore.open(null)) { + TransactionStore ts = new TransactionStore(s); + ts.init(); + + Transaction tx = ts.begin(); + tx.openMap("m1"); + tx.openMap("m2"); + tx.openMap("m3"); + assertFalse(tx.getChanges(0).hasNext()); + tx.commit(); + + tx = ts.begin(); + TransactionMap m1 = tx.openMap("m1"); + TransactionMap m2 = tx.openMap("m2"); + TransactionMap m3 = tx.openMap("m3"); + m1.put("1", "100"); + long sp = tx.setSavepoint(); + m2.put("1", "100"); + m3.put("1", "100"); + Iterator it = tx.getChanges(sp); + assertTrue(it.hasNext()); + Change c = it.next(); + assertEquals("m3", c.mapName); + assertEquals("1", c.key.toString()); + assertNull(c.value); + assertTrue(it.hasNext()); + c = it.next(); + assertEquals("m2", c.mapName); + assertEquals("1", c.key.toString()); + assertNull(c.value); + assertFalse(it.hasNext()); + + it = tx.getChanges(0); + assertTrue(it.hasNext()); + c = it.next(); + assertEquals("m3", c.mapName); + assertEquals("1", c.key.toString()); + assertNull(c.value); + assertTrue(it.hasNext()); + c = it.next(); + assertEquals("m2", c.mapName); + assertEquals("1", c.key.toString()); + assertNull(c.value); + assertTrue(it.hasNext()); + c = it.next(); + assertEquals("m1", c.mapName); + assertEquals("1", c.key.toString()); + assertNull(c.value); + assertFalse(it.hasNext()); + + tx.rollbackToSavepoint(sp); + + it = tx.getChanges(0); + assertTrue(it.hasNext()); + c = it.next(); + assertEquals("m1", c.mapName); + assertEquals("1", c.key.toString()); + assertNull(c.value); + assertFalse(it.hasNext()); + + tx.commit(); + } + } + + private void testKeyIterator() { + try (MVStore s = MVStore.open(null)) { + TransactionStore ts = new TransactionStore(s); + ts.init(); + + Transaction tx = ts.begin(); + TransactionMap m = tx.openMap("test"); + m.put("1", "Hello"); + m.put("2", "World"); + m.put("3", "."); + tx.commit(); + + Transaction tx2 = ts.begin(); + TransactionMap m2 = tx2.openMap("test"); + m2.remove("2"); + m2.put("3", "!"); + m2.put("4", "?"); + + tx = ts.begin(); + m = tx.openMap("test"); + Iterator it = m.keyIterator(null); + assertTrue(it.hasNext()); + assertEquals("1", it.next()); + assertTrue(it.hasNext()); + assertEquals("2", it.next()); + assertTrue(it.hasNext()); + assertEquals("3", it.next()); + assertFalse(it.hasNext()); + + Iterator> entryIt = m.entrySet().iterator(); + assertTrue(entryIt.hasNext()); + assertEquals("1", entryIt.next().getKey()); + assertTrue(entryIt.hasNext()); + assertEquals("2", entryIt.next().getKey()); + assertTrue(entryIt.hasNext()); + assertEquals("3", entryIt.next().getKey()); + assertFalse(entryIt.hasNext()); + + Iterator it2 = m2.keyIterator(null); + assertTrue(it2.hasNext()); + assertEquals("1", it2.next()); + assertTrue(it2.hasNext()); + assertEquals("3", it2.next()); + assertTrue(it2.hasNext()); + assertEquals("4", it2.next()); + assertFalse(it2.hasNext()); + } + } + + private void testTwoPhaseCommit() { + String fileName = getBaseDir() + "/testTwoPhaseCommit.h3"; + FileUtils.delete(fileName); + + TransactionMap m; + + try (MVStore s = MVStore.open(fileName)) { + TransactionStore ts = new TransactionStore(s); + ts.init(); + Transaction tx = ts.begin(); + assertEquals(null, tx.getName()); + tx.setName("first transaction"); + assertEquals("first transaction", tx.getName()); + assertEquals(1, tx.getId()); + assertEquals(Transaction.STATUS_OPEN, tx.getStatus()); + m = tx.openMap("test"); + m.put("1", "Hello"); + List list = ts.getOpenTransactions(); + assertEquals(1, list.size()); + Transaction txOld = list.get(0); + assertTrue(tx.getId() == txOld.getId()); + assertEquals("first transaction", txOld.getName()); + s.commit(); + ts.close(); + } + + try (MVStore s = MVStore.open(fileName)) { + TransactionStore ts = new TransactionStore(s); + ts.init(); + Transaction tx = ts.begin(); + assertEquals(2, tx.getId()); + m = tx.openMap("test"); + assertEquals(null, m.get("1")); + m.put("2", "Hello"); + List list = ts.getOpenTransactions(); + assertEquals(2, list.size()); + Transaction txOld = list.get(0); + assertEquals(1, txOld.getId()); + assertEquals(Transaction.STATUS_OPEN, txOld.getStatus()); + assertEquals("first transaction", txOld.getName()); + txOld.prepare(); + assertEquals(Transaction.STATUS_PREPARED, txOld.getStatus()); + txOld = list.get(1); + txOld.commit(); + s.commit(); + } + + try (MVStore s = MVStore.open(fileName)) { + TransactionStore ts = new TransactionStore(s); + ts.init(); + Transaction tx = ts.begin(); + m = tx.openMap("test"); + m.put("3", "Test"); + assertEquals(2, tx.getId()); + List list = ts.getOpenTransactions(); + assertEquals(2, list.size()); + Transaction txOld = list.get(1); + assertEquals(2, txOld.getId()); + assertEquals(Transaction.STATUS_OPEN, txOld.getStatus()); + assertEquals(null, txOld.getName()); + txOld.rollback(); + txOld = list.get(0); + assertEquals(1, txOld.getId()); + assertEquals(Transaction.STATUS_PREPARED, txOld.getStatus()); + assertEquals("first transaction", txOld.getName()); + txOld.commit(); + assertEquals("Hello", m.get("1")); + } + + FileUtils.delete(fileName); + } + + private void testSavepoint() { + try (MVStore s = MVStore.open(null)) { + TransactionStore ts = new TransactionStore(s); + ts.init(); + + Transaction tx = ts.begin(); + TransactionMap m = tx.openMap("test"); + m.put("1", "Hello"); + m.put("2", "World"); + m.put("1", "Hallo"); + m.remove("2"); + m.put("3", "!"); + long logId = tx.setSavepoint(); + m.put("1", "Hi"); + m.put("2", "."); + m.remove("3"); + tx.rollbackToSavepoint(logId); + assertEquals("Hallo", m.get("1")); + assertNull(m.get("2")); + assertEquals("!", m.get("3")); + tx.rollback(); + + tx = ts.begin(); + m = tx.openMap("test"); + assertNull(m.get("1")); + assertNull(m.get("2")); + assertNull(m.get("3")); + + ts.close(); + } + } + + private void testCompareWithPostgreSQL() throws Exception { + ArrayList statements = new ArrayList<>(); + ArrayList transactions = new ArrayList<>(); + ArrayList> maps = new ArrayList<>(); + int connectionCount = 3, opCount = 1000, rowCount = 10; + try { + Class.forName("org.postgresql.Driver"); + for (int i = 0; i < connectionCount; i++) { + Connection conn = DriverManager.getConnection( + "jdbc:postgresql:test?loggerLevel=OFF", "sa", "sa"); + statements.add(conn.createStatement()); + } + } catch (Exception e) { + // database not installed - ok + return; + } + statements.get(0).execute( + "drop table if exists test cascade"); + statements.get(0).execute( + "create table test(id int primary key, name varchar(255))"); + + try (MVStore s = MVStore.open(null)) { + TransactionStore ts = new TransactionStore(s); + ts.init(); + for (int i = 0; i < connectionCount; i++) { + Statement stat = statements.get(i); + // 100 ms to avoid blocking (the test is single threaded) + stat.execute("set statement_timeout to 100"); + Connection c = stat.getConnection(); + c.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); + c.setAutoCommit(false); + Transaction transaction = ts.begin(); + transactions.add(transaction); + TransactionMap map; + map = transaction.openMap("test"); + maps.add(map); + } + StringBuilder buff = new StringBuilder(); + + Random r = new Random(1); + try { + for (int i = 0; i < opCount; i++) { + int connIndex = r.nextInt(connectionCount); + Statement stat = statements.get(connIndex); + Transaction transaction = transactions.get(connIndex); + TransactionMap map = maps.get(connIndex); + if (transaction == null) { + transaction = ts.begin(); + map = transaction.openMap("test"); + transactions.set(connIndex, transaction); + maps.set(connIndex, map); + + // read all data, to get a snapshot + ResultSet rs = stat.executeQuery( + "select * from test order by id"); + buff.append(i).append(": [" + connIndex + "]="); + int size = 0; + while (rs.next()) { + buff.append(' '); + int k = rs.getInt(1); + String v = rs.getString(2); + buff.append(k).append(':').append(v); + assertEquals(v, map.get(k)); + size++; + } + buff.append('\n'); + if (size != map.sizeAsLong()) { + assertEquals(size, map.sizeAsLong()); + } + } + int x = r.nextInt(rowCount); + int y = r.nextInt(rowCount); + buff.append(i).append(": [" + connIndex + "]: "); + ResultSet rs = null; + switch (r.nextInt(7)) { + case 0: + buff.append("commit"); + stat.getConnection().commit(); + transaction.commit(); + transactions.set(connIndex, null); + break; + case 1: + buff.append("rollback"); + stat.getConnection().rollback(); + transaction.rollback(); + transactions.set(connIndex, null); + break; + case 2: + // insert or update + String old = map.get(x); + if (old == null) { + buff.append("insert " + x + "=" + y); + if (map.tryPut(x, "" + y)) { + stat.execute("insert into test values(" + x + ", '" + y + "')"); + } else { + buff.append(" -> row was locked"); + // the statement would time out in PostgreSQL + // TODO test sometimes if timeout occurs + } + } else { + buff.append("update " + x + "=" + y + " (old:" + old + ")"); + if (map.tryPut(x, "" + y)) { + int c = stat.executeUpdate("update test set name = '" + y + + "' where id = " + x); + assertEquals(1, c); + } else { + buff.append(" -> row was locked"); + // the statement would time out in PostgreSQL + // TODO test sometimes if timeout occurs + } + } + break; + case 3: + buff.append("delete " + x); + try { + int c = stat.executeUpdate("delete from test where id = " + x); + if (c == 1) { + map.remove(x); + } else { + assertNull(map.get(x)); + } + } catch (SQLException e) { + assertNotNull(map.get(x)); + assertFalse(map.tryRemove(x)); + // PostgreSQL needs to rollback + buff.append(" -> rollback"); + stat.getConnection().rollback(); + transaction.rollback(); + transactions.set(connIndex, null); + } + break; + case 4: + case 5: + case 6: + rs = stat.executeQuery("select * from test where id = " + x); + String expected = rs.next() ? rs.getString(2) : null; + buff.append("select " + x + "=" + expected); + assertEquals("i:" + i, expected, map.get(x)); + break; + } + buff.append('\n'); + } + } catch (Exception e) { + e.printStackTrace(); + fail(buff.toString()); + } + for (Statement stat : statements) { + stat.getConnection().close(); + } + ts.close(); + } + } + + private void testConcurrentTransactionsReadCommitted() { + try (MVStore s = MVStore.open(null)) { + TransactionStore ts = new TransactionStore(s); + ts.init(); + + + Transaction tx1 = ts.begin(); + TransactionMap m1 = tx1.openMap("test"); + m1.put("1", "Hi"); + m1.put("3", "."); + tx1.commit(); + + tx1 = ts.begin(); + m1 = tx1.openMap("test"); + m1.put("1", "Hello"); + m1.put("2", "World"); + m1.remove("3"); + tx1.commit(); + + // start new transaction to read old data + Transaction tx2 = ts.begin(); + TransactionMap m2 = tx2.openMap("test"); + + // start transaction tx1, update/delete/add + tx1 = ts.begin(); + m1 = tx1.openMap("test"); + m1.put("1", "Hallo"); + m1.remove("2"); + m1.put("3", "!"); + + assertEquals("Hello", m2.get("1")); + assertEquals("World", m2.get("2")); + assertNull(m2.get("3")); + + tx1.commit(); + + assertEquals("Hallo", m2.get("1")); + assertNull(m2.get("2")); + assertEquals("!", m2.get("3")); + + tx1 = ts.begin(); + m1 = tx1.openMap("test"); + m1.put("2", "World"); + + assertNull(m2.get("2")); + assertFalse(m2.tryRemove("2")); + assertFalse(m2.tryPut("2", "Welt")); + + tx2 = ts.begin(); + m2 = tx2.openMap("test"); + assertNull(m2.get("2")); + m1.remove("2"); + assertNull(m2.get("2")); + tx1.commit(); + + tx1 = ts.begin(); + m1 = tx1.openMap("test"); + assertNull(m1.get("2")); + m1.put("2", "World"); + m1.put("2", "Welt"); + tx1.rollback(); + + tx1 = ts.begin(); + m1 = tx1.openMap("test"); + assertNull(m1.get("2")); + + ts.close(); + } + } + + private void testSingleConnection() { + try (MVStore s = MVStore.open(null)) { + TransactionStore ts = new TransactionStore(s); + ts.init(); + + // add, rollback + Transaction tx = ts.begin(); + TransactionMap m = tx.openMap("test"); + m.put("1", "Hello"); + assertEquals("Hello", m.get("1")); + m.put("2", "World"); + assertEquals("World", m.get("2")); + tx.rollback(); + tx = ts.begin(); + m = tx.openMap("test"); + assertNull(m.get("1")); + assertNull(m.get("2")); + + // add, commit + tx = ts.begin(); + m = tx.openMap("test"); + m.put("1", "Hello"); + m.put("2", "World"); + assertEquals("Hello", m.get("1")); + assertEquals("World", m.get("2")); + tx.commit(); + tx = ts.begin(); + m = tx.openMap("test"); + assertEquals("Hello", m.get("1")); + assertEquals("World", m.get("2")); + + // update+delete+insert, rollback + tx = ts.begin(); + m = tx.openMap("test"); + m.put("1", "Hallo"); + m.remove("2"); + m.put("3", "!"); + assertEquals("Hallo", m.get("1")); + assertNull(m.get("2")); + assertEquals("!", m.get("3")); + tx.rollback(); + tx = ts.begin(); + m = tx.openMap("test"); + assertEquals("Hello", m.get("1")); + assertEquals("World", m.get("2")); + assertNull(m.get("3")); + + // update+delete+insert, commit + tx = ts.begin(); + m = tx.openMap("test"); + m.put("1", "Hallo"); + m.remove("2"); + m.put("3", "!"); + assertEquals("Hallo", m.get("1")); + assertNull(m.get("2")); + assertEquals("!", m.get("3")); + tx.commit(); + tx = ts.begin(); + m = tx.openMap("test"); + assertEquals("Hallo", m.get("1")); + assertNull(m.get("2")); + assertEquals("!", m.get("3")); + + ts.close(); + } + } + + private static void testStoreMultiThreadedReads() { + try (MVStore s = MVStore.open(null)) { + TransactionStore ts = new TransactionStore(s); + ts.init(); + Transaction t = ts.begin(); + TransactionMap mapA = t.openMap("a"); + mapA.put(1, 0); + t.commit(); + + Task task = new Task() { + @Override + public void call() { + for (int i = 0; !stop; i++) { + Transaction tx = ts.begin(); + TransactionMap mapA = tx.openMap("a"); + while (!mapA.tryPut(1, i)) { + // repeat + } + tx.commit(); + + // map B transaction + // the other thread will get a map A uncommitted value, + // but by the time it tries to walk back to the committed + // value, the undoLog has changed + tx = ts.begin(); + TransactionMap mapB = tx.openMap("b"); + // put a new value to the map; this will cause a map B + // undoLog entry to be created with a null pre-image value + mapB.tryPut(i, -i); + // this is where the real race condition occurs: + // some other thread might get the B log entry + // for this transaction rather than the uncommitted A log + // entry it is expecting + tx.commit(); + } + } + }; + task.execute(); + try { + for (int i = 0; i < 10000; i++) { + Transaction tx = ts.begin(); + mapA = tx.openMap("a"); + if (mapA.get(1) == null) { + throw new AssertionError("key not found"); + } + tx.commit(); + } + } finally { + task.get(); + } + ts.close(); + } + } + + private void testCommitAfterMapRemoval() { + try (MVStore s = MVStore.open(null)) { + TransactionStore ts = new TransactionStore(s); + ts.init(); + Transaction t = ts.begin(); + TransactionMap map = t.openMap("test", LongDataType.INSTANCE, StringDataType.INSTANCE); + map.put(1L, "A"); + s.removeMap("test"); + try { + t.commit(); + } finally { + // commit should not fail, but even if it does + // transaction should be cleanly removed and store remains operational + assertTrue(ts.getOpenTransactions().isEmpty()); + assertFalse(ts.hasMap("test")); + t = ts.begin(); + map = t.openMap("test", LongDataType.INSTANCE, StringDataType.INSTANCE); + assertTrue(map.isEmpty()); + map.put(2L, "B"); + } + } + } + + private void testDeadLock() { + int threadCount = 2; + for (int i = 1; i < threadCount; i++) { + testDeadLock(threadCount, i); + } + } + + private void testDeadLock(int threadCount, int stepCount) { + try (MVStore s = MVStore.open(null)) { + s.setAutoCommitDelay(0); + TransactionStore ts = new TransactionStore(s, + new MetaType<>(null, s.backgroundExceptionHandler), new ObjectDataType(), 10000); + ts.init(); + Transaction t = ts.begin(); + TransactionMap m = t.openMap("test", LongDataType.INSTANCE, LongDataType.INSTANCE); + for (int i = 0; i < threadCount; i++) { + m.put((long)i, 0L); + } + t.commit(); + + CountDownLatch latch = new CountDownLatch(threadCount); + Task[] tasks = new Task[threadCount]; + for (int i = 0; i < threadCount; i++) { + long initialKey = i; + tasks[i] = new Task() { + @Override + public void call() throws Exception { + Transaction tx = ts.begin(); + try { + TransactionMap map = tx.openMap("test", LongDataType.INSTANCE, + LongDataType.INSTANCE); + long key = initialKey; + map.computeIfPresent(key, (k, v) -> v + 1); + latch.countDown(); + latch.await(); + for (int j = 0; j < stepCount; j++) { + key = (key + 1) % threadCount; + map.lock(key); + map.put(key, map.get(key) + 1); + } + tx.commit(); + } catch (Throwable e) { + tx.rollback(); + throw e; + } + } + }.execute(); + } + int failureCount = 0; + for (Task task : tasks) { + Exception exception = task.getException(); + if (exception != null) { + ++failureCount; + assertEquals(MVStoreException.class, exception.getClass()); + checkErrorCode(DataUtils.ERROR_TRANSACTIONS_DEADLOCK, exception); + } + } + assertEquals(" "+stepCount, stepCount, failureCount); + t = ts.begin(); + m = t.openMap("test", LongDataType.INSTANCE, LongDataType.INSTANCE); + int count = 0; + for (int i = 0; i < threadCount; i++) { + Long value = m.get((long) i); + assertNotNull("Key " + i, value); + count += value; + } + t.commit(); + assertEquals(" "+stepCount, (stepCount+1) * (threadCount - failureCount), count); + } + } +} diff --git a/h2/src/test/org/h2/test/store/package.html b/h2/src/test/org/h2/test/store/package.html new file mode 100644 index 0000000..f71790e --- /dev/null +++ b/h2/src/test/org/h2/test/store/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +This package contains tests for the map store. + +

    \ No newline at end of file diff --git a/h2/src/test/org/h2/test/synth/BnfRandom.java b/h2/src/test/org/h2/test/synth/BnfRandom.java new file mode 100644 index 0000000..cc35923 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/BnfRandom.java @@ -0,0 +1,223 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.util.ArrayList; +import java.util.Random; + +import org.h2.bnf.Bnf; +import org.h2.bnf.BnfVisitor; +import org.h2.bnf.Rule; +import org.h2.bnf.RuleFixed; +import org.h2.bnf.RuleHead; + +/** + * A BNF visitor that generates a random SQL statement. + */ +public class BnfRandom implements BnfVisitor { + + private static final boolean SHOW_SYNTAX = false; + + private final Random random = new Random(); + private final ArrayList statements = new ArrayList<>(); + + private int level; + private String sql; + + public BnfRandom() throws Exception { + Bnf config = Bnf.getInstance(null); + config.addAlias("procedure", "@func@"); + config.linkStatements(); + + ArrayList all = config.getStatements(); + + // go backwards so we can append at the end + for (int i = all.size() - 1; i >= 0; i--) { + RuleHead r = all.get(i); + String topic = r.getTopic().toLowerCase(); + int weight = 0; + if (topic.equals("select")) { + weight = 10; + } else if (topic.equals("create table")) { + weight = 20; + } else if (topic.equals("insert")) { + weight = 5; + } else if (topic.startsWith("update")) { + weight = 3; + } else if (topic.startsWith("delete")) { + weight = 3; + } else if (topic.startsWith("drop")) { + weight = 2; + } + if (SHOW_SYNTAX) { + System.out.println(r.getTopic()); + } + for (int j = 0; j < weight; j++) { + statements.add(r); + } + } + } + + public String getRandomSQL() { + int sid = random.nextInt(statements.size()); + + RuleHead r = statements.get(sid); + level = 0; + r.getRule().accept(this); + sql = sql.trim(); + + if (sql.length() > 0) { + if (sql.indexOf("TRACE_LEVEL_") < 0 + && sql.indexOf("COLLATION") < 0 + && sql.indexOf("SCRIPT ") < 0 + && sql.indexOf("CSVWRITE") < 0 + && sql.indexOf("BACKUP") < 0 + && sql.indexOf("DB_CLOSE_DELAY") < 0) { + if (SHOW_SYNTAX) { + System.out.println(" " + sql); + } + return sql; + } + } + return null; + } + + @Override + public void visitRuleElement(boolean keyword, String name, Rule link) { + if (keyword) { + if (name.startsWith(";")) { + sql = ""; + } else { + sql = name.length() > 1 ? " " + name + " " : name; + } + } else if (link != null) { + level++; + link.accept(this); + level--; + } else { + throw new AssertionError(name); + } + } + + @Override + public void visitRuleFixed(int type) { + sql = getRandomFixed(type); + } + + private String getRandomFixed(int type) { + Random r = random; + switch (type) { + case RuleFixed.YMD: + return (1800 + r.nextInt(200)) + "-" + + (1 + r.nextInt(12)) + "-" + (1 + r.nextInt(31)); + case RuleFixed.HMS: + return (r.nextInt(24)) + "-" + (r.nextInt(60)) + "-" + (r.nextInt(60)); + case RuleFixed.NANOS: + return "" + (r.nextInt(100000) + r.nextInt(10000)); + case RuleFixed.ANY_UNTIL_EOL: + case RuleFixed.ANY_EXCEPT_SINGLE_QUOTE: + case RuleFixed.ANY_EXCEPT_DOUBLE_QUOTE: + case RuleFixed.ANY_WORD: + case RuleFixed.ANY_EXCEPT_2_DOLLAR: + case RuleFixed.ANY_UNTIL_END: { + StringBuilder buff = new StringBuilder(); + int len = r.nextBoolean() ? 1 : r.nextInt(5); + for (int i = 0; i < len; i++) { + buff.append((char) ('A' + r.nextInt('C' - 'A'))); + } + return buff.toString(); + } + case RuleFixed.HEX_START: + return "0x"; + case RuleFixed.CONCAT: + return "||"; + case RuleFixed.AZ_UNDERSCORE: + return "" + (char) ('A' + r.nextInt('C' - 'A')); + case RuleFixed.AF: + return "" + (char) ('A' + r.nextInt('F' - 'A')); + case RuleFixed.DIGIT: + return "" + (char) ('0' + r.nextInt(10)); + case RuleFixed.OPEN_BRACKET: + return "["; + case RuleFixed.CLOSE_BRACKET: + return "]"; + default: + throw new AssertionError("type="+type); + } + } + + @Override + public void visitRuleList(boolean or, ArrayList list) { + if (or) { + visitOr(list); + return; + } + StringBuilder buff = new StringBuilder(); + level++; + for (Rule r : list) { + r.accept(this); + buff.append(sql); + } + level--; + sql = buff.toString(); + } + + @Override + public void visitRuleOptional(Rule rule) { + if (level > 10 ? random.nextInt(level) == 1 : random.nextInt(4) == 1) { + level++; + rule.accept(this); + level--; + return; + } + sql = ""; + } + + @Override + public void visitRuleOptional(ArrayList list) { + if (level > 10 ? random.nextInt(level) == 1 : random.nextInt(4) == 1) { + level++; + visitOr(list); + level--; + return; + } + sql = ""; + } + + private void visitOr(ArrayList list) throws AssertionError { + if (level > 10) { + if (level > 1000) { + // better than stack overflow + throw new AssertionError(); + } + list.get(0).accept(this); + return; + } + int idx = random.nextInt(list.size()); + level++; + list.get(idx).accept(this); + level--; + } + + @Override + public void visitRuleRepeat(boolean comma, Rule rule) { + rule.accept(this); + } + + @Override + public void visitRuleExtension(Rule rule, boolean compatibility) { + rule.accept(this); + } + + public void setSeed(int seed) { + random.setSeed(seed); + } + + public int getStatementCount() { + return statements.size(); + } + +} diff --git a/h2/src/test/org/h2/test/synth/OutputCatcher.java b/h2/src/test/org/h2/test/synth/OutputCatcher.java new file mode 100644 index 0000000..2ab3413 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/OutputCatcher.java @@ -0,0 +1,88 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.io.IOException; +import java.io.InputStream; +import java.util.LinkedList; +import java.util.concurrent.TimeUnit; +import org.h2.util.IOUtils; + +/** + * Catches the output of another process. + */ +public class OutputCatcher extends Thread { + private final InputStream in; + private final LinkedList list = new LinkedList<>(); + + public OutputCatcher(InputStream in) { + this.in = in; + } + + /** + * Read a line from the output. + * + * @param wait the maximum number of milliseconds to wait + * @return the line + */ + public String readLine(long wait) { + long start = System.nanoTime(); + while (true) { + synchronized (list) { + if (list.size() > 0) { + return list.removeFirst(); + } + try { + list.wait(wait); + } catch (InterruptedException e) { + // ignore + } + long time = System.nanoTime() - start; + if (time >= TimeUnit.MILLISECONDS.toNanos(wait)) { + return null; + } + } + } + } + + @Override + public void run() { + final StringBuilder buff = new StringBuilder(); + try { + while (true) { + try { + int x = in.read(); + if (x < 0) { + break; + } + if (x < ' ') { + if (buff.length() > 0) { + String s = buff.toString(); + buff.setLength(0); + synchronized (list) { + list.add(s); + list.notifyAll(); + } + } + } else { + buff.append((char) x); + } + } catch (IOException e) { + break; + } + } + IOUtils.closeSilently(in); + } finally { + // just in case something goes wrong, make sure we store any partial output we got + if (buff.length() > 0) { + synchronized (list) { + list.add(buff.toString()); + list.notifyAll(); + } + } + } + } +} diff --git a/h2/src/test/org/h2/test/synth/TestBtreeIndex.java b/h2/src/test/org/h2/test/synth/TestBtreeIndex.java new file mode 100644 index 0000000..42dfae5 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestBtreeIndex.java @@ -0,0 +1,201 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Random; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.DeleteDbFiles; + +/** + * A b-tree index test. + */ +public class TestBtreeIndex extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); + test.config.big = true; + test.test(); + test.test(); + test.test(); + } + + @Override + public void test() throws SQLException { + Random random = new Random(); + for (int i = 0; i < getSize(1, 4); i++) { + testAddDelete(); + int seed = random.nextInt(); + testCase(seed); + } + } + + private void testAddDelete() throws SQLException { + deleteDb(getTestName()); + Connection conn = getConnection(getTestName()); + try { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID bigint primary key)"); + int count = 1000; + stat.execute( + "insert into test select x from system_range(1, " + + count + ")"); + if (!config.memory) { + conn.close(); + conn = getConnection(getTestName()); + stat = conn.createStatement(); + } + for (int i = 1; i < count; i++) { + ResultSet rs = stat.executeQuery("select * from test order by id"); + for (int j = i; rs.next(); j++) { + assertEquals(j, rs.getInt(1)); + } + stat.execute("delete from test where id =" + i); + } + stat.execute("drop all objects delete files"); + } finally { + conn.close(); + } + deleteDb(getTestName()); + } + + private void testCase(int seed) throws SQLException { + testOne(seed); + } + + private void testOne(int seed) throws SQLException { + org.h2.Driver.load(); + deleteDb(getTestName()); + printTime("testIndex " + seed); + Random random = new Random(seed); + int distinct, prefixLength; + if (random.nextBoolean()) { + distinct = random.nextInt(8000) + 1; + prefixLength = random.nextInt(8000) + 1; + } else if (random.nextBoolean()) { + distinct = random.nextInt(16000) + 1; + prefixLength = random.nextInt(100) + 1; + } else { + distinct = random.nextInt(10) + 1; + prefixLength = random.nextInt(10) + 1; + } + boolean delete = random.nextBoolean(); + StringBuilder buff = new StringBuilder(); + for (int j = 0; j < prefixLength; j++) { + buff.append("x"); + if (buff.length() % 10 == 0) { + buff.append(buff.length()); + } + } + String prefix = buff.toString().substring(0, prefixLength); + DeleteDbFiles.execute(getBaseDir() + "/" + getTestName(), null, true); + try (Connection conn = getConnection(getTestName())) { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE a(text VARCHAR PRIMARY KEY)"); + PreparedStatement prepInsert = conn.prepareStatement( + "INSERT INTO a VALUES(?)"); + PreparedStatement prepDelete = conn.prepareStatement( + "DELETE FROM a WHERE text=?"); + PreparedStatement prepDeleteAllButOne = conn.prepareStatement( + "DELETE FROM a WHERE text <> ?"); + int count = 0; + for (int i = 0; i < 1000; i++) { + int y = random.nextInt(distinct); + try { + prepInsert.setString(1, prefix + y); + prepInsert.executeUpdate(); + count++; + } catch (SQLException e) { + if (e.getSQLState().equals("23505")) { + // ignore + } else { + TestBase.logError("error", e); + break; + } + } + if (delete && random.nextInt(10) == 1) { + if (random.nextInt(4) == 1) { + try { + prepDeleteAllButOne.setString(1, prefix + y); + int deleted = prepDeleteAllButOne.executeUpdate(); + if (deleted < count - 1) { + printError(seed, "deleted:" + deleted + " i:" + i); + } + count -= deleted; + } catch (SQLException e) { + TestBase.logError("error", e); + break; + } + } else { + try { + prepDelete.setString(1, prefix + y); + int deleted = prepDelete.executeUpdate(); + if (deleted > 1) { + printError(seed, "deleted:" + deleted + " i:" + i); + } + count -= deleted; + } catch (SQLException e) { + TestBase.logError("error", e); + break; + } + } + } + } + int testCount; + testCount = 0; + ResultSet rs = stat.executeQuery( + "SELECT text FROM a ORDER BY text"); + ResultSet rs2 = conn.createStatement().executeQuery( + "SELECT text FROM a ORDER BY 'x' || text"); + +//System.out.println("-----------"); +//while(rs.next()) { +// System.out.println(rs.getString(1)); +//} +//System.out.println("-----------"); +//while(rs2.next()) { +// System.out.println(rs2.getString(1)); +//} +//if (true) throw new AssertionError("stop"); +// + testCount = 0; + while (rs.next() && rs2.next()) { + if (!rs.getString(1).equals(rs2.getString(1))) { + assertEquals("" + testCount, rs.getString(1), rs2.getString(1)); + } + testCount++; + } + assertFalse(rs.next()); + assertFalse(rs2.next()); + if (testCount != count) { + printError(seed, "count:" + count + " testCount:" + testCount); + } + rs = stat.executeQuery("SELECT text, count(*) FROM a " + + "GROUP BY text HAVING COUNT(*)>1"); + if (rs.next()) { + printError(seed, "testCount:" + testCount + " " + rs.getString(1)); + } + } + deleteDb(getTestName()); + } + + private void printError(int seed, String message) { + TestBase.logError("new TestBtreeIndex().init(test).testCase(" + + seed + "); // " + message, null); + fail(message); + } + +} diff --git a/h2/src/test/org/h2/test/synth/TestConcurrentUpdate.java b/h2/src/test/org/h2/test/synth/TestConcurrentUpdate.java new file mode 100644 index 0000000..072029b --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestConcurrentUpdate.java @@ -0,0 +1,174 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.Task; + +/** + * A concurrent test. + */ +public class TestConcurrentUpdate extends TestDb { + + private static final int THREADS = 10; + private static final int ROW_COUNT = 3; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + org.h2.test.TestAll config = new org.h2.test.TestAll(); +// config.memory = true; +// config.mvStore = false; + System.out.println(config); + TestBase test = createCaller().init(config); + for (int i = 0; i < 10; i++) { + System.out.println("Pass #" + i); + test.testFromMain(); + } + } + + @Override + public void test() throws Exception { + testConcurrent(); + testConcurrentShutdown(); + } + + private void testConcurrent() throws Exception { + deleteDb("concurrent"); + final String url = getURL("concurrent;LOCK_TIMEOUT=2000", true); + try (Connection conn = getConnection(url)) { + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name varchar)"); + + Task[] tasks = new Task[THREADS]; + for (int i = 0; i < THREADS; i++) { + final int threadId = i; + Task t = new Task() { + @Override + public void call() throws Exception { + Random r = new Random(threadId); + try (Connection conn = getConnection(url)) { + PreparedStatement insert = conn.prepareStatement( + "merge into test values(?, ?)"); + PreparedStatement update = conn.prepareStatement( + "update test set name = ? where id = ?"); + PreparedStatement delete = conn.prepareStatement( + "delete from test where id = ?"); + PreparedStatement select = conn.prepareStatement( + "select * from test where id = ?"); + while (!stop) { + int x = r.nextInt(ROW_COUNT); + String data = "x" + r.nextInt(ROW_COUNT); + switch (r.nextInt(4)) { + case 0: + insert.setInt(1, x); + insert.setString(2, data); + insert.execute(); + break; + case 1: + update.setString(1, data); + update.setInt(2, x); + update.execute(); + break; + case 2: + delete.setInt(1, x); + delete.execute(); + break; + case 3: + select.setInt(1, x); + ResultSet rs = select.executeQuery(); + while (rs.next()) { + rs.getString(2); + } + break; + } + } + } + } + }; + tasks[i] = t; + t.execute(); + } + // test 2 seconds + Thread.sleep(2000); + boolean success = true; + for (Task t : tasks) { + t.join(); + Throwable exception = t.getException(); + if (exception != null) { + logError("", exception); + success = false; + } + } + assert success; + } + } + + private void testConcurrentShutdown() throws SQLException { + if (config.memory) { + return; + } + deleteDb(getTestName()); + final String url = getURL(getTestName(), true); + try (Connection connection = getConnection(url)) { + connection.createStatement().execute("create table test(id int primary key, v int)"); + connection.createStatement().execute("insert into test values(0, 0)"); + } + int len = 2; + final CountDownLatch latch = new CountDownLatch(len + 1); + Collection tasks = new ArrayList<>(); + + tasks.add(new Task() { + @Override + public void call() throws Exception { + try (Connection c = getConnection(url)) { + c.setAutoCommit(false); + c.createStatement().execute("insert into test values(1, 1)"); + latch.countDown(); + latch.await(); + } + } + }); + + for (int i = 0; i < len; i++) { + tasks.add(new Task() { + @Override + public void call() throws Exception { + try (Connection c = getConnection(url)) { + Statement stmt = c.createStatement(); + latch.countDown(); + latch.await(); + stmt.execute("shutdown"); + } + } + }); + } + for (Task task : tasks) { + task.execute(); + } + for (Task task : tasks) { + task.getException(); + } + try (Connection connection = getConnection(getTestName())) { + ResultSet rs = connection.createStatement().executeQuery("select count(*) from test"); + rs.next(); + assertEquals(1, rs.getInt(1)); + } + } +} diff --git a/h2/src/test/org/h2/test/synth/TestCrashAPI.java b/h2/src/test/org/h2/test/synth/TestCrashAPI.java new file mode 100644 index 0000000..f88c384 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestCrashAPI.java @@ -0,0 +1,535 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.sql.BatchUpdateException; +import java.sql.Blob; +import java.sql.CallableStatement; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.Savepoint; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Comparator; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Map; + +import org.h2.api.ErrorCode; +import org.h2.store.FileLister; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.test.scripts.TestScript; +import org.h2.test.synth.sql.RandomGen; +import org.h2.tools.Backup; +import org.h2.tools.DeleteDbFiles; +import org.h2.tools.Restore; +import org.h2.util.MathUtils; + +/** + * A test that calls random methods with random parameters from JDBC objects. + * This is sometimes called 'Fuzz Testing'. + */ +public class TestCrashAPI extends TestDb implements Runnable { + + private static final boolean RECOVER_ALL = false; + + private static final Class[] INTERFACES = { Connection.class, + PreparedStatement.class, Statement.class, ResultSet.class, + ResultSetMetaData.class, Savepoint.class, ParameterMetaData.class, + Clob.class, Blob.class, Array.class, CallableStatement.class }; + + private static final String DIR = "synth"; + + private final ArrayList objects = new ArrayList<>(); + private final HashMap, ArrayList> classMethods = + new HashMap<>(); + private RandomGen random = new RandomGen(); + private ArrayList statements; + private int openCount; + private long callCount; + private volatile long maxWait = 60; + private volatile boolean stopped; + private volatile boolean running; + private Thread mainThread; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + System.setProperty("h2.delayWrongPasswordMin", "0"); + System.setProperty("h2.delayWrongPasswordMax", "0"); + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void run() { + while (--maxWait > 0) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + maxWait++; + // ignore + } + if (maxWait > 0 && maxWait <= 10) { + println("stopping..."); + stopped = true; + } + } + if (maxWait == 0 && running) { + objects.clear(); + if (running) { + println("stopping (trying to interrupt)..."); + for (StackTraceElement e : mainThread.getStackTrace()) { + System.out.println(e.toString()); + } + mainThread.interrupt(); + } + } + } + + private static void recoverAll() { + org.h2.Driver.load(); + File[] files = new File("temp/backup").listFiles(); + Arrays.sort(files, Comparator.comparing(File::getName)); + for (File f : files) { + if (!f.getName().startsWith("db-")) { + continue; + } + DeleteDbFiles.execute("data", null, true); + try { + Restore.execute(f.getAbsolutePath(), "data", null); + } catch (Exception e) { + System.out.println(f.getName() + " restore error " + e); + // ignore + } + ArrayList dbFiles = FileLister.getDatabaseFiles("data", null, false); + for (String name: dbFiles) { + if (!name.endsWith(".h2.db")) { + continue; + } + name = name.substring(0, name.length() - 6); + try { + DriverManager.getConnection("jdbc:h2:data/" + name, "sa", "").close(); + System.out.println(f.getName() + " OK"); + } catch (SQLException e) { + System.out.println(f.getName() + " " + e); + } + } + } + } + + @Override + public boolean isEnabled() { + if (config.networked) { + return false; + } + return true; + } + + @Override + public void test() throws Exception { + if (RECOVER_ALL) { + recoverAll(); + return; + } + + if (config.networked) { + return; + } + + TestScript script = new TestScript(); + statements = script.getAllStatements(config); + initMethods(); + + int len = getSize(2, 6); + Thread t = new Thread(this); + try { + mainThread = Thread.currentThread(); + t.start(); + running = true; + for (int i = 0; i < len && !stopped; i++) { + int seed = MathUtils.randomInt(Integer.MAX_VALUE); + testCase(seed); + deleteDb(); + } + } finally { + running = false; + deleteDb(); + maxWait = -1; + t.join(); + } + } + + private void deleteDb() { + try { + deleteDb(getBaseDir() + "/" + DIR, null); + } catch (Exception e) { + // ignore + } + } + + private Connection getConnection(int seed, boolean delete) throws SQLException { + openCount++; + if (delete) { + deleteDb(); + } + // can not use FILE_LOCK=NO, otherwise something could be written into + // the database in the finalize method + + String add = ";MAX_QUERY_TIMEOUT=10000"; + +// int testing; +// if(openCount >= 32) { +// int test; +// Runtime.getRuntime().halt(0); +// System.exit(1); +// } + // System.out.println("now open " + openCount); + // add += ";TRACE_LEVEL_FILE=3"; + // config.logMode = 2; + // } + + String dbName = "crashApi" + seed; + String url = getURL(DIR + "/" + dbName, true) + add; + +// int test; +// url += ";DB_CLOSE_ON_EXIT=FALSE"; +// int test; +// url += ";TRACE_LEVEL_FILE=3"; + + Connection conn = null; + String fileName = "temp/backup/db-" + uniqueId++ + ".zip"; + Backup.execute(fileName, getBaseDir() + "/" + DIR, dbName, true); + // close databases earlier + System.gc(); + try { + conn = DriverManager.getConnection(url, "sa", getPassword("")); + // delete the backup if opening was successful + FileUtils.delete(fileName); + } catch (SQLException e) { + if (e.getErrorCode() == ErrorCode.WRONG_USER_OR_PASSWORD) { + // delete if the password changed + FileUtils.delete(fileName); + } + throw e; + } + int len = random.getInt(50); + int first = random.getInt(statements.size() - len); + int end = first + len; + Statement stat = conn.createStatement(); + stat.execute("SET LOCK_TIMEOUT 10"); + stat.execute("SET WRITE_DELAY 0"); + if (random.nextBoolean()) { + if (random.nextBoolean()) { + double g = random.nextGaussian(); + int size = (int) Math.abs(10000 * g * g); + stat.execute("SET CACHE_SIZE " + size); + } else { + stat.execute("SET CACHE_SIZE 0"); + } + } + stat.execute("SCRIPT NOPASSWORDS NOSETTINGS"); + for (int i = first; i < end && i < statements.size() && !stopped; i++) { + try { + stat.execute("SELECT * FROM TEST WHERE ID=1"); + } catch (Throwable t) { + printIfBad(seed, -i, -1, t); + } + try { + stat.execute("SELECT * FROM TEST WHERE ID=1 OR ID=1"); + } catch (Throwable t) { + printIfBad(seed, -i, -1, t); + } + + String sql = statements.get(i); + try { +// if(openCount == 32) { +// int test; +// System.out.println("stop!"); +// } + stat.execute(sql); + } catch (Throwable t) { + printIfBad(seed, -i, -1, t); + } + } + if (random.nextBoolean()) { + try { + conn.commit(); + } catch (Throwable t) { + printIfBad(seed, 0, -1, t); + } + } + return conn; + } + + private void testCase(int seed) throws SQLException { + printTime("seed: " + seed); + callCount = 0; + openCount = 0; + random = new RandomGen(); + random.setSeed(seed); + Connection c1 = getConnection(seed, true); + Connection conn = null; + for (int i = 0; i < 2000 && !stopped; i++) { + // if(i % 10 == 0) { + // for(int j=0; j in = getJdbcInterface(o); + ArrayList methods = classMethods.get(in); + Method m = methods.get(random.getInt(methods.size())); + Object o2 = callRandom(seed, i, objectId, o, m); + if (o2 != null) { + objects.add(o2); + } + } + try { + if (conn != null) { + conn.close(); + } + c1.close(); + } catch (Throwable t) { + printIfBad(seed, -101010, -1, t); + try { + deleteDb(); + } catch (Throwable t2) { + printIfBad(seed, -101010, -1, t2); + } + } + objects.clear(); + } + + private void printError(int seed, int id, Throwable t) { + StringWriter writer = new StringWriter(); + t.printStackTrace(new PrintWriter(writer)); + String s = writer.toString(); + TestBase.logError("new TestCrashAPI().init(test).testCase(" + + seed + "); // Bug " + s.hashCode() + " id=" + id + + " callCount=" + callCount + " openCount=" + openCount + + " " + t.getMessage(), t); + throw new RuntimeException(t); + } + + private Object callRandom(int seed, int id, int objectId, Object o, Method m) { + // TODO m.isDefault() can be used on Java 8 + boolean isDefault = + (m.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC + && m.getDeclaringClass().isInterface(); + boolean allowNPE = isDefault || o instanceof Blob && "setBytes".equals(m.getName()); + Class[] paramClasses = m.getParameterTypes(); + Object[] params = new Object[paramClasses.length]; + for (int i = 0; i < params.length; i++) { + params[i] = getRandomParam(paramClasses[i]); + } + Object result = null; + try { + callCount++; + result = m.invoke(o, params); + } catch (IllegalArgumentException | IllegalAccessException e) { + TestBase.logError("error", e); + } catch (InvocationTargetException e) { + Throwable t = e.getTargetException(); + printIfBad(seed, id, objectId, t, allowNPE); + } + if (result == null) { + return null; + } + Class in = getJdbcInterface(result); + if (in == null) { + return null; + } + return result; + } + + private void printIfBad(int seed, int id, int objectId, Throwable t) { + printIfBad(seed, id, objectId, t, false); + } + + private void printIfBad(int seed, int id, int objectId, Throwable t, boolean allowNPE) { + if (t instanceof BatchUpdateException) { + // do nothing + } else if (t.getClass().getName().contains("SQLClientInfoException")) { + // do nothing + } else if (t instanceof UnsupportedOperationException) { + // do nothing - new Java8/9 stuff + } else if (t instanceof SQLFeatureNotSupportedException) { + // do nothing + } else if (t instanceof SQLException) { + SQLException s = (SQLException) t; + int errorCode = s.getErrorCode(); + if (errorCode == 0) { + printError(seed, id, s); + } else if (errorCode == ErrorCode.OBJECT_CLOSED) { + if (objectId >= 0 && objects.size() > 0) { + // TODO at least call a few more times after close - maybe + // there is still an error + objects.remove(objectId); + } + } else if (errorCode == ErrorCode.GENERAL_ERROR_1) { + // General error [HY000] + printError(seed, id, s); + } + } else if (allowNPE && t instanceof NullPointerException) { + // do nothing, this methods may throw this exception + } else { + printError(seed, id, t); + } + } + + private Object getRandomParam(Class type) { + if (type == int.class) { + return random.getRandomInt(); + } else if (type == byte.class) { + return (byte) random.getRandomInt(); + } else if (type == short.class) { + return (short) random.getRandomInt(); + } else if (type == long.class) { + return random.getRandomLong(); + } else if (type == float.class) { + return (float) random.getRandomDouble(); + } else if (type == boolean.class) { + return random.nextBoolean(); + } else if (type == double.class) { + return random.getRandomDouble(); + } else if (type == String.class) { + if (random.getInt(10) == 0) { + return null; + } + int randomId = random.getInt(statements.size()); + String sql = statements.get(randomId); + if (random.getInt(10) == 0) { + sql = random.modify(sql); + } + return sql; + } else if (type == int[].class) { + // TODO test with 'shared' arrays (make sure database creates a + // copy) + return random.getIntArray(); + } else if (type == java.io.Reader.class) { + return null; + } else if (type == java.sql.Array.class) { + return null; + } else if (type == byte[].class) { + // TODO test with 'shared' arrays (make sure database creates a + // copy) + return random.getByteArray(); + } else if (type == Map.class) { + return null; + } else if (type == Object.class) { + return null; + } else if (type == java.sql.Date.class) { + return random.randomDate(); + } else if (type == java.sql.Time.class) { + return random.randomTime(); + } else if (type == java.sql.Timestamp.class) { + return random.randomTimestamp(); + } else if (type == java.io.InputStream.class) { + return null; + } else if (type == String[].class) { + return null; + } else if (type == java.sql.Clob.class) { + return null; + } else if (type == java.sql.Blob.class) { + return null; + } else if (type == Savepoint.class) { + // TODO should use generated savepoints + return null; + } else if (type == Calendar.class) { + return new GregorianCalendar(); + } else if (type == java.net.URL.class) { + return null; + } else if (type == java.math.BigDecimal.class) { + return new java.math.BigDecimal("" + random.getRandomDouble()); + } else if (type == java.sql.Ref.class) { + return null; + } + return null; + } + + private Class getJdbcInterface(Object o) { + for (Class in : o.getClass().getInterfaces()) { + if (classMethods.get(in) != null) { + return in; + } + } + return null; + } + + private void initMethods() { + for (Class inter : INTERFACES) { + ArrayList list = new ArrayList<>(); + for (Method m : inter.getMethods()) { + list.add(m); + } + classMethods.put(inter, list); + } + } + +} diff --git a/h2/src/test/org/h2/test/synth/TestDiskFull.java b/h2/src/test/org/h2/test/synth/TestDiskFull.java new file mode 100644 index 0000000..16e4a0a --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestDiskFull.java @@ -0,0 +1,135 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import org.h2.api.ErrorCode; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.test.utils.FilePathUnstable; + +/** + * Test simulated disk full problems. + */ +public class TestDiskFull extends TestDb { + + private FilePathUnstable fs; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + fs = FilePathUnstable.register(); + fs.setPartialWrites(true); + try { + test(Integer.MAX_VALUE); + int max = Integer.MAX_VALUE - fs.getDiskFullCount() + 10; + for (int i = 0; i < max; i++) { + test(i); + } + } finally { + fs.setPartialWrites(false); + } + } + + private boolean test(int x) throws SQLException { + deleteDb("memFS:", null); + fs.setDiskFullCount(x, 0); + String url = "jdbc:h2:unstable:memFS:diskFull" + x + + ";FILE_LOCK=NO;TRACE_LEVEL_FILE=0;WRITE_DELAY=10;" + + "LOCK_TIMEOUT=100;CACHE_SIZE=4096;MAX_COMPACT_TIME=10"; + url = getURL(url, true); + Connection conn = null; + Statement stat = null; + boolean opened = false; + try { + conn = DriverManager.getConnection(url); + stat = conn.createStatement(); + opened = true; + for (int j = 0; j < 5; j++) { + stat.execute("create table test(id int primary key, name varchar)"); + stat.execute("insert into test values(1, 'Hello')"); + stat.execute("create index idx_name on test(name)"); + stat.execute("insert into test values(2, 'World')"); + stat.execute("update test set name='Hallo' where id=1"); + stat.execute("delete from test where id=2"); + stat.execute("checkpoint"); + stat.execute("insert into test values(3, space(10000))"); + stat.execute("update test set name='Hallo' where id=3"); + stat.execute("drop table test"); + } + conn.close(); + conn = null; + return fs.getDiskFullCount() > 0; + } catch (SQLException e) { + if (e.getErrorCode() == ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1) { + throw e; + } + if (stat != null) { + try { + fs.setDiskFullCount(0, 0); + stat.execute("create table if not exists test" + + "(id int primary key, name varchar)"); + stat.execute("insert into test values(4, space(10000))"); + stat.execute("update test set name='Hallo' where id=3"); + conn.close(); + } catch (SQLException e2) { + if (e2.getErrorCode() != ErrorCode.IO_EXCEPTION_1 + && e2.getErrorCode() != ErrorCode.IO_EXCEPTION_2 + && e2.getErrorCode() != ErrorCode.DATABASE_IS_CLOSED + && e2.getErrorCode() != ErrorCode.OBJECT_CLOSED) { + throw e2; + } + } + } + } finally { + if (conn != null) { + try { + if (stat != null) { + stat.execute("shutdown immediately"); + } + } catch (Exception e2) { + // ignore + } + try { + conn.close(); + } catch (Exception e2) { + // ignore + } + } + } + fs.setDiskFullCount(0, 0); + try { + conn = null; + conn = DriverManager.getConnection(url); + } catch (SQLException e) { + if (!opened) { + return false; + } + throw e; + } + stat = conn.createStatement(); + stat.execute("script to 'memFS:test.sql'"); + conn.close(); + + deleteDb("memFS:", null); + FileUtils.delete("memFS:test.sql"); + + return false; + } + +} diff --git a/h2/src/test/org/h2/test/synth/TestFuzzOptimizations.java b/h2/src/test/org/h2/test/synth/TestFuzzOptimizations.java new file mode 100644 index 0000000..f029e1f --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestFuzzOptimizations.java @@ -0,0 +1,274 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Random; + +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.test.db.Db; +import org.h2.test.db.Db.Prepared; + +/** + * This test executes random SQL statements to test if optimizations are working + * correctly. + */ +public class TestFuzzOptimizations extends TestDb { + + private Connection conn; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb(getTestName()); + conn = getConnection(getTestName()); + if (!config.diskResult) { + testIn(); + } + testGroupSorted(); + testInSelect(); + conn.close(); + deleteDb(getTestName()); + } + + /* + drop table test0; + drop table test1; + create table test0(a int, b int, c int); + create index idx_1 on test0(a); + create index idx_2 on test0(b, a); + create table test1(a int, b int, c int); + insert into test0 select x / 100, + mod(x / 10, 10), mod(x, 10) + from system_range(0, 999); + update test0 set a = null where a = 9; + update test0 set b = null where b = 9; + update test0 set c = null where c = 9; + insert into test1 select * from test0; + + select * from test0 where + b in(null, 0) and a in(2, null, null) + order by 1, 2, 3; + + select * from test1 where + b in(null, 0) and a in(2, null, null) + order by 1, 2, 3; + */ + private void testIn() throws SQLException { + Db db = new Db(conn); + db.execute("create table test0(a int, b int, c int)"); + db.execute("create index idx_1 on test0(a)"); + db.execute("create index idx_2 on test0(b, a)"); + db.execute("create table test1(a int, b int, c int)"); + db.execute("insert into test0 select x / 100, " + + "mod(x / 10, 10), mod(x, 10) from system_range(0, 999)"); + db.execute("update test0 set a = null where a = 9"); + db.execute("update test0 set b = null where b = 9"); + db.execute("update test0 set c = null where c = 9"); + db.execute("insert into test1 select * from test0"); + + // this failed at some point + Prepared p = db.prepare("select * from test0 where b in(" + + "select a from test1 where a a) " + + "and c in(0, 10) and c in(10, 0, 0) order by 1, 2, 3"); + p.set(1); + p.execute(); + + Random seedGenerator = new Random(); + String[] columns = new String[] { "a", "b", "c" }; + String[] values = new String[] { null, "0", "0", "1", "2", "10", "a", "?" }; + String[] compares = new String[] { "in(", "not in(", "=", "=", ">", + "<", ">=", "<=", "<>", "in(select", "not in(select" }; + int size = getSize(100, 1000); + for (int i = 0; i < size; i++) { + long seed = seedGenerator.nextLong(); + println("testIn() seed: " + seed); + Random random = new Random(seed); + ArrayList params = new ArrayList<>(); + String condition = getRandomCondition(random, params, columns, + compares, values); + String message = "testIn() seed: " + seed + " " + condition; + executeAndCompare(condition, params, message); + if (params.size() > 0) { + for (int j = 0; j < params.size(); j++) { + String value = values[random.nextInt(values.length - 2)]; + params.set(j, value); + } + executeAndCompare(condition, params, message); + } + } + executeAndCompare("a >=0 and b in(?, 2) and a in(1, ?, null)", Arrays.asList("10", "2"), + "testIn() seed=-6191135606105920350L"); + db.execute("drop table test0, test1"); + } + + private void executeAndCompare(String condition, List params, String message) throws SQLException { + PreparedStatement prep0 = conn.prepareStatement( + "select * from test0 where " + condition + + " order by 1, 2, 3"); + PreparedStatement prep1 = conn.prepareStatement( + "select * from test1 where " + condition + + " order by 1, 2, 3"); + for (int j = 0; j < params.size(); j++) { + prep0.setString(j + 1, params.get(j)); + prep1.setString(j + 1, params.get(j)); + } + ResultSet rs0 = prep0.executeQuery(); + ResultSet rs1 = prep1.executeQuery(); + assertEquals(message, rs0, rs1); + } + + private String getRandomCondition(Random random, ArrayList params, + String[] columns, String[] compares, String[] values) { + int comp = 1 + random.nextInt(4); + StringBuilder buff = new StringBuilder(); + for (int j = 0; j < comp; j++) { + if (j > 0) { + buff.append(random.nextBoolean() ? " and " : " or "); + } + String column = columns[random.nextInt(columns.length)]; + String compare = compares[random.nextInt(compares.length)]; + buff.append(column).append(' ').append(compare); + if (compare.endsWith("in(")) { + int len = 1+random.nextInt(3); + for (int k = 0; k < len; k++) { + if (k > 0) { + buff.append(", "); + } + String value = values[random.nextInt(values.length)]; + buff.append(value); + if ("?".equals(value)) { + value = values[random.nextInt(values.length - 2)]; + params.add(value); + } + } + buff.append(")"); + } else if (compare.endsWith("(select")) { + String col = columns[random.nextInt(columns.length)]; + buff.append(" ").append(col).append(" from test1 where "); + String condition = getRandomCondition( + random, params, columns, compares, values); + buff.append(condition); + buff.append(")"); + } else { + String value = values[random.nextInt(values.length)]; + buff.append(value); + if ("?".equals(value)) { + value = values[random.nextInt(values.length - 2)]; + params.add(value); + } + } + } + return buff.toString(); + } + + private void testInSelect() { + Db db = new Db(conn); + db.execute("CREATE TABLE TEST(A INT, B INT)"); + db.execute("CREATE INDEX IDX ON TEST(A)"); + db.execute("INSERT INTO TEST SELECT X/4, MOD(X, 4) " + + "FROM SYSTEM_RANGE(1, 16)"); + db.execute("UPDATE TEST SET A = NULL WHERE A = 0"); + db.execute("UPDATE TEST SET B = NULL WHERE B = 0"); + Random random = new Random(); + long seed = random.nextLong(); + println("testInSelect() seed: " + seed); + for (int i = 0; i < 100; i++) { + String column = random.nextBoolean() ? "A" : "B"; + String value = new String[] { "NULL", "0", "A", "B" }[random.nextInt(4)]; + String compare = random.nextBoolean() ? "A" : "B"; + int x = random.nextInt(3); + String sql1 = "SELECT * FROM TEST T WHERE " + column + "+0 " + + "IN(SELECT " + value + + " FROM TEST I WHERE I." + compare + "=?) ORDER BY 1, 2"; + String sql2 = "SELECT * FROM TEST T WHERE " + column + " " + + "IN(SELECT " + value + + " FROM TEST I WHERE I." + compare + "=?) ORDER BY 1, 2"; + List> a = db.prepare(sql1).set(x).query(); + List> b = db.prepare(sql2).set(x).query(); + assertTrue("testInSelect() seed: " + seed + " sql: " + sql1 + + " a: " + a + " b: " + b, a.equals(b)); + } + db.execute("DROP TABLE TEST"); + } + + private void testGroupSorted() { + Db db = new Db(conn); + db.execute("CREATE TABLE TEST(A INT, B INT, C INT)"); + Random random = new Random(); + long seed = random.nextLong(); + println("testGroupSorted() seed: " + seed); + for (int i = 0; i < 100; i++) { + Prepared p = db.prepare("INSERT INTO TEST VALUES(?, ?, ?)"); + p.set(new String[] { null, "0", "1", "2" }[random.nextInt(4)]); + p.set(new String[] { null, "0", "1", "2" }[random.nextInt(4)]); + p.set(new String[] { null, "0", "1", "2" }[random.nextInt(4)]); + p.execute(); + } + int len = getSize(1000, 3000); + for (int i = 0; i < len / 10; i++) { + db.execute("CREATE TABLE TEST_INDEXED AS SELECT * FROM TEST"); + int jLen = 1 + random.nextInt(2); + for (int j = 0; j < jLen; j++) { + String x = "CREATE INDEX IDX" + j + " ON TEST_INDEXED("; + int kLen = 1 + random.nextInt(2); + for (int k = 0; k < kLen; k++) { + if (k > 0) { + x += ","; + } + x += new String[] { "A", "B", "C" }[random.nextInt(3)]; + } + db.execute(x + ")"); + } + for (int j = 0; j < 10; j++) { + String x = "SELECT "; + for (int k = 0; k < 3; k++) { + if (k > 0) { + x += ","; + } + x += new String[] { "SUM(A)", "MAX(B)", "AVG(C)", + "COUNT(B)" }[random.nextInt(4)]; + x += " S" + k; + } + x += " FROM "; + String group = " GROUP BY "; + int kLen = 1 + random.nextInt(2); + for (int k = 0; k < kLen; k++) { + if (k > 0) { + group += ","; + } + group += new String[] { "A", "B", "C" }[random.nextInt(3)]; + } + group += " ORDER BY 1, 2, 3"; + List> a = db.query(x + "TEST" + group); + List> b = db.query(x + "TEST_INDEXED" + group); + assertEquals(a.toString(), b.toString()); + assertTrue(a.equals(b)); + } + db.execute("DROP TABLE TEST_INDEXED"); + } + db.execute("DROP TABLE TEST"); + } + +} diff --git a/h2/src/test/org/h2/test/synth/TestHalt.java b/h2/src/test/org/h2/test/synth/TestHalt.java new file mode 100644 index 0000000..f6fd68f --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestHalt.java @@ -0,0 +1,353 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Random; +import org.h2.test.TestAll; +import org.h2.test.TestBase; +import org.h2.test.utils.SelfDestructor; +import org.h2.tools.Backup; +import org.h2.tools.DeleteDbFiles; +import org.h2.util.StringUtils; + +/** + * Tests database recovery by destroying a process that writes to the database. + */ +public abstract class TestHalt extends TestBase { + + /** + * This bit flag means insert operations should be performed. + */ + protected static final int OP_INSERT = 1; + + /** + * This bit flag means delete operations should be performed. + */ + protected static final int OP_DELETE = 2; + + /** + * This bit flag means update operations should be performed. + */ + protected static final int OP_UPDATE = 4; + + /** + * This bit flag means select operations should be performed. + */ + protected static final int OP_SELECT = 8; + + /** + * This bit flag means operations should be written to the transaction log + * immediately. + */ + protected static final int FLAG_NO_DELAY = 1; + + /** + * This bit flag means the test should use LOB values. + */ + protected static final int FLAG_LOBS = 2; + + private static final String DATABASE_NAME = "halt"; + private static final String TRACE_FILE_NAME = "haltTrace.trace.db"; + + /** + * The current operations bit mask. + */ + protected int operations; + + /** + * The current flags bit mask. + */ + protected int flags; + + /** + * The current test value, for example the number of rows. + */ + protected int value; + + /** + * The database connection. + */ + protected Connection conn; + + /** + * The pseudo random number generator used for this test. + */ + protected Random random = new Random(); + + private final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("MM-dd HH:mm:ss"); + private int errorId; + private int sequenceId; + + /** + * Initialize the test. + */ + abstract void controllerInit() throws SQLException; + + /** + * Check if the database is consistent after a simulated database crash. + */ + abstract void controllerCheckAfterCrash() throws SQLException; + + /** + * Wait for some time after the application has been started. + */ + abstract void controllerWaitAfterAppStart() throws Exception; + + /** + * Start the application. + */ + abstract void processAppStart() throws SQLException; + + /** + * Run the application. + */ + abstract void processAppRun() throws SQLException; + + @Override + public void test() { + for (int i = 0;; i++) { + operations = OP_INSERT | i; + flags = i >> 4; + // flags |= FLAG_NO_DELAY; // | FLAG_LOBS; + try { + controllerTest(); + } catch (Throwable t) { + System.out.println("Error: " + t); + t.printStackTrace(); + } + } + } + + Connection getConnection() throws SQLException { + org.h2.Driver.load(); + String url = "jdbc:h2:" + getBaseDir() + "/halt"; + // String url = "jdbc:h2:" + baseDir + "/halt;TRACE_LEVEL_FILE=3"; + return DriverManager.getConnection(url, "sa", "sa"); + } + + /** + * The second process starts the application and executes random operations. + */ + void processRunRandom() throws SQLException { + connect(); + try { + traceOperation("connected, operations:" + + operations + " flags:" + flags + " value:" + value); + processAppStart(); + System.out.println("READY"); + System.out.println("READY"); + System.out.println("READY"); + processAppRun(); + traceOperation("done"); + } catch (Exception e) { + traceOperation("run", e); + } + disconnect(); + } + + private void connect() throws SQLException { + try { + traceOperation("connecting"); + conn = getConnection(); + } catch (SQLException e) { + traceOperation("connect", e); + e.printStackTrace(); + throw e; + } + } + + /** + * Print a trace message to the trace file. + * + * @param s the message + */ + protected void traceOperation(String s) { + traceOperation(s, null); + } + + /** + * Print a trace message to the trace file. + * + * @param s the message + * @param e the exception or null + */ + protected void traceOperation(String s, Exception e) { + File f = new File(getBaseDir() + "/" + TRACE_FILE_NAME); + f.getParentFile().mkdirs(); + try (FileWriter writer = new FileWriter(f, true)) { + PrintWriter w = new PrintWriter(writer); + s = dateFormat.format(LocalDateTime.now()) + " : " + s; + w.println(s); + if (e != null) { + e.printStackTrace(w); + } + } catch (IOException e2) { + e2.printStackTrace(); + } + } + + /** + * Run one test. The controller starts the process, waits, kills the + * process, and checks if everything is ok. + */ + void controllerTest() throws Exception { + traceOperation("delete database -----------------------------"); + DeleteDbFiles.execute(getBaseDir(), DATABASE_NAME, true); + new File(getBaseDir() + "/" + TRACE_FILE_NAME).delete(); + + connect(); + controllerInit(); + disconnect(); + for (int i = 0; i < 10; i++) { + traceOperation("backing up " + sequenceId); + Backup.execute(getBaseDir() + "/haltSeq" + + sequenceId + ".zip", getBaseDir(), null, true); + sequenceId++; + // int operations = OP_INSERT; + // OP_DELETE = 1, OP_UPDATE = 2, OP_SELECT = 4; + // int flags = FLAG_NODELAY; + // FLAG_NO_DELAY = 1, FLAG_AUTO_COMMIT = 2, FLAG_SMALL_CACHE = 4; + int testValue = random.nextInt(1000); + // for Derby and HSQLDB + // String classPath = "-cp + // .;D:/data/java/hsqldb.jar;D:/data/java/derby.jar"; + String selfDestruct = SelfDestructor.getPropertyString(60); + String[] procDef = { getJVM(), selfDestruct, + "-cp", getClassPath(), + getClass().getName(), "" + operations, "" + flags, "" + testValue}; + traceOperation("start: " + StringUtils.arrayCombine(procDef, ' ')); + Process p = Runtime.getRuntime().exec(procDef); + InputStream in = p.getInputStream(); + OutputCatcher catcher = new OutputCatcher(in); + catcher.start(); + String s = catcher.readLine(5 * 60 * 1000); + if (s == null) { + throw new IOException( + "No reply from process, command: " + + StringUtils.arrayCombine(procDef, ' ')); + } else if (s.startsWith("READY")) { + traceOperation("got reply: " + s); + } + controllerWaitAfterAppStart(); + p.destroy(); + p.waitFor(); + try { + traceOperation("backing up " + sequenceId); + Backup.execute(getBaseDir() + "/haltSeq" + + sequenceId + ".zip", getBaseDir(), null, true); + // new File(BASE_DIR + "/haltSeq" + (sequenceId-20) + + // ".zip").delete(); + connect(); + controllerCheckAfterCrash(); + } catch (Exception e) { + File zip = new File(getBaseDir() + "/haltSeq" + + sequenceId + ".zip"); + File zipId = new File(getBaseDir() + "/haltSeq" + + sequenceId + "-" + errorId + ".zip"); + zip.renameTo(zipId); + printTime("ERROR: " + sequenceId + " " + errorId + " " + e.toString()); + e.printStackTrace(); + errorId++; + } finally { + sequenceId++; + disconnect(); + } + } + } + + /** + * Close the database connection normally. + */ + protected void disconnect() { + try { + traceOperation("disconnect"); + conn.close(); + } catch (Exception e) { + traceOperation("disconnect", e); + } + } + +// public Connection getConnectionHSQLDB() throws SQLException { +// File lock = new File("test.lck"); +// while (lock.exists()) { +// lock.delete(); +// System.gc(); +// } +// Class.forName("org.hsqldb.jdbcDriver"); +// return DriverManager.getConnection("jdbc:hsqldb:test", "sa", ""); +// } + +// public Connection getConnectionDerby() throws SQLException { +// File lock = new File("test3/db.lck"); +// while (lock.exists()) { +// lock.delete(); +// System.gc(); +// } +// Class.forName("org.apache.derby.iapi.jdbc.AutoloadedDriver").newInstance(); +// try { +// return DriverManager.getConnection( +// "jdbc:derby:test3;create=true", "sa", "sa"); +// } catch (SQLException e) { +// Exception e2 = e; +// do { +// e.printStackTrace(); +// e = e.getNextException(); +// } while (e != null); +// throw e2; +// } +// } + +// void disconnectHSQLDB() { +// try { +// conn.createStatement().execute("SHUTDOWN"); +// } catch (Exception e) { +// // ignore +// } +// // super.disconnect(); +// } + +// void disconnectDerby() { +// // super.disconnect(); +// try { +// Class.forName("org.apache.derby.iapi.jdbc.AutoloadedDriver"); +// DriverManager.getConnection( +// "jdbc:derby:;shutdown=true", "sa", "sa"); +// } catch (Exception e) { +// // ignore +// } +// } + + /** + * Create a random string with the specified length. + * + * @param len the number of characters + * @return the random string + */ + protected String getRandomString(int len) { + StringBuilder buff = new StringBuilder(); + for (int i = 0; i < len; i++) { + buff.append('a' + random.nextInt(20)); + } + return buff.toString(); + } + + @Override + public TestBase init(TestAll conf) throws Exception { + super.init(conf); + return this; + } + +} diff --git a/h2/src/test/org/h2/test/synth/TestHaltApp.java b/h2/src/test/org/h2/test/synth/TestHaltApp.java new file mode 100644 index 0000000..22b5d90 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestHaltApp.java @@ -0,0 +1,183 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import org.h2.test.utils.SelfDestructor; + +/** + * The application code for the {@link TestHalt} application. + */ +public class TestHaltApp extends TestHalt { + + private int rowCount; + + /** + * This method is called when executing this application from the command + * line. + * + * @param args the command line parameters + */ + public static void main(String... args) throws Exception { + SelfDestructor.startCountdown(60); + TestHaltApp app = new TestHaltApp(); + if (args.length == 0) { + app.controllerTest(); + } else { + app.operations = Integer.parseInt(args[0]); + app.flags = Integer.parseInt(args[1]); + app.value = Integer.parseInt(args[2]); + app.processRunRandom(); + } + } + + @Override + protected void execute(Statement stat, String sql) throws SQLException { + traceOperation("execute: " + sql); + super.execute(stat, sql); + } + + /** + * Initialize the database. + */ + @Override + protected void controllerInit() throws SQLException { + Statement stat = conn.createStatement(); + // stat.execute("CREATE TABLE TEST(ID IDENTITY, NAME VARCHAR(255))"); + for (int i = 0; i < 20; i++) { + execute(stat, "DROP TABLE IF EXISTS TEST" + i); + execute(stat, "CREATE TABLE TEST" + i + + "(ID INT PRIMARY KEY, NAME VARCHAR(255))"); + } + for (int i = 0; i < 20; i += 2) { + execute(stat, "DROP TABLE TEST" + i); + } + execute(stat, "DROP TABLE IF EXISTS TEST"); + execute(stat, "CREATE TABLE TEST" + + "(ID BIGINT GENERATED BY DEFAULT AS IDENTITY, " + + "NAME VARCHAR(255), DATA CLOB)"); + } + + /** + * Wait after the application has been started. + */ + @Override + protected void controllerWaitAfterAppStart() throws Exception { + int sleep = 10 + random.nextInt(300); + if ((flags & FLAG_NO_DELAY) == 0) { + sleep += 1000; + } + Thread.sleep(sleep); + } + + /** + * This method is called after a simulated crash. The method should check if + * the data is transactionally consistent and throw an exception if not. + * + * @throws SQLException if the data is not consistent. + */ + @Override + protected void controllerCheckAfterCrash() throws SQLException { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("SELECT COUNT(*) FROM TEST"); + rs.next(); + int count = rs.getInt(1); + System.out.println("count: " + count); + if (count % 2 != 0) { + traceOperation("row count: " + count); + throw new SQLException("Unexpected odd row count: " + count); + } + } + + /** + * Initialize the application. + */ + @Override + protected void processAppStart() throws SQLException { + Statement stat = conn.createStatement(); + if ((flags & FLAG_NO_DELAY) != 0) { + execute(stat, "SET WRITE_DELAY 0"); + execute(stat, "SET MAX_LOG_SIZE 1"); + } + ResultSet rs = stat.executeQuery("SELECT COUNT(*) FROM TEST"); + rs.next(); + rowCount = rs.getInt(1); + traceOperation("rows: " + rowCount, null); + } + + /** + * Run the application code. + */ + @Override + protected void processAppRun() throws SQLException { + conn.setAutoCommit(false); + traceOperation("setAutoCommit false"); + int rows = 10000 + value; + PreparedStatement prepInsert = conn.prepareStatement( + "INSERT INTO TEST(NAME, DATA) VALUES('Hello World', ?)"); + PreparedStatement prepUpdate = conn.prepareStatement( + "UPDATE TEST SET NAME = 'Hallo Welt', DATA = ? WHERE ID = ?"); + for (int i = 0; i < rows; i++) { + Statement stat = conn.createStatement(); + if ((operations & OP_INSERT) != 0) { + if ((flags & FLAG_LOBS) != 0) { + String s = getRandomString(random.nextInt(200)); + prepInsert.setString(1, s); + traceOperation("insert " + s); + prepInsert.execute(); + } else { + execute(stat, "INSERT INTO TEST(NAME) " + + "VALUES('Hello World')"); + } + ResultSet rs = stat.getGeneratedKeys(); + rs.next(); + int key = rs.getInt(1); + traceOperation("inserted key: " + key); + rowCount++; + } + if ((operations & OP_UPDATE) != 0) { + if ((flags & FLAG_LOBS) != 0) { + String s = getRandomString(random.nextInt(200)); + prepUpdate.setString(1, s); + int x = random.nextInt(rowCount + 1); + prepUpdate.setInt(2, x); + traceOperation("update " + s + " " + x); + prepUpdate.execute(); + } else { + int x = random.nextInt(rowCount + 1); + execute(stat, "UPDATE TEST SET VALUE = 'Hallo Welt' " + + "WHERE ID = " + x); + } + } + if ((operations & OP_DELETE) != 0) { + int x = random.nextInt(rowCount + 1); + traceOperation("deleting " + x); + int uc = stat.executeUpdate("DELETE FROM TEST " + + "WHERE ID = " + x); + traceOperation("updated: " + uc); + rowCount -= uc; + } + traceOperation("rowCount " + rowCount); + traceOperation("rows now: " + rowCount, null); + if (rowCount % 2 == 0) { + traceOperation("commit " + rowCount); + conn.commit(); + traceOperation("committed: " + rowCount, null); + } + if ((flags & FLAG_NO_DELAY) != 0) { + if (random.nextInt(10) == 0 && (rowCount % 2 == 0)) { + execute(stat, "CHECKPOINT"); + } + } + } + traceOperation("rollback"); + conn.rollback(); + } + +} diff --git a/h2/src/test/org/h2/test/synth/TestJoin.java b/h2/src/test/org/h2/test/synth/TestJoin.java new file mode 100644 index 0000000..ca45c1a --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestJoin.java @@ -0,0 +1,309 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.StringUtils; + +/** + * A test that runs random join statements against two databases and compares + * the results. + */ +public class TestJoin extends TestDb { + + private final ArrayList connections = new ArrayList<>(); + private Random random; + private int paramCount; + private StringBuilder buff; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testJoin(); + } + + private void testJoin() throws Exception { + deleteDb("join"); + String shortestFailed = null; + + Connection c1 = getConnection("join"); + connections.add(c1); + + Class.forName("org.postgresql.Driver"); + Connection c2 = DriverManager.getConnection("jdbc:postgresql:test", "sa", "sa"); + connections.add(c2); + + // Class.forName("com.mysql.cj.jdbc.Driver"); + // Connection c2 = + // DriverManager.getConnection("jdbc:mysql://localhost/test", "sa", + // "sa"); + // connections.add(c2); + + // Class.forName("org.hsqldb.jdbcDriver"); + // Connection c2 = DriverManager.getConnection("jdbc:hsqldb:join", "sa", + // ""); + // connections.add(c2); + + /* + DROP TABLE ONE; + DROP TABLE TWO; + CREATE TABLE ONE(A INT PRIMARY KEY, B INT); + INSERT INTO ONE VALUES(0, NULL); + INSERT INTO ONE VALUES(1, 0); + INSERT INTO ONE VALUES(2, 1); + INSERT INTO ONE VALUES(3, 4); + CREATE TABLE TWO(A INT PRIMARY KEY, B INT); + INSERT INTO TWO VALUES(0, NULL); + INSERT INTO TWO VALUES(1, 0); + INSERT INTO TWO VALUES(2, 2); + INSERT INTO TWO VALUES(3, 3); + INSERT INTO TWO VALUES(4, NULL); + */ + + execute("DROP TABLE ONE", null, true); + execute("DROP TABLE TWO", null, true); + execute("CREATE TABLE ONE(A INT PRIMARY KEY, B INT)", null); + execute("INSERT INTO ONE VALUES(0, NULL)", null); + execute("INSERT INTO ONE VALUES(1, 0)", null); + execute("INSERT INTO ONE VALUES(2, 1)", null); + execute("INSERT INTO ONE VALUES(3, 4)", null); + execute("CREATE TABLE TWO(A INT PRIMARY KEY, B INT)", null); + execute("INSERT INTO TWO VALUES(0, NULL)", null); + execute("INSERT INTO TWO VALUES(1, 0)", null); + execute("INSERT INTO TWO VALUES(2, 2)", null); + execute("INSERT INTO TWO VALUES(3, 3)", null); + execute("INSERT INTO TWO VALUES(4, NULL)", null); + random = new Random(); + long startTime = System.nanoTime(); + for (int i = 0;; i++) { + paramCount = 0; + buff = new StringBuilder(); + long time = System.nanoTime(); + if (time - startTime > TimeUnit.SECONDS.toNanos(5)) { + printTime("i:" + i); + startTime = time; + } + buff.append("SELECT "); + int tables = 1 + random.nextInt(5); + for (int j = 0; j < tables; j++) { + if (j > 0) { + buff.append(", "); + } + buff.append("T" + (char) ('0' + j) + ".A"); + } + buff.append(" FROM "); + appendRandomTable(); + buff.append(" T0 "); + for (int j = 1; j < tables; j++) { + if (random.nextBoolean()) { + buff.append("INNER"); + } else { + // if(random.nextInt(4)==1) { + // buff.append("RIGHT"); + // } else { + buff.append("LEFT"); + // } + } + buff.append(" JOIN "); + appendRandomTable(); + buff.append(" T"); + buff.append((char) ('0' + j)); + buff.append(" ON "); + appendRandomCondition(j); + } + if (random.nextBoolean()) { + buff.append("WHERE "); + appendRandomCondition(tables - 1); + } + String sql = buff.toString(); + Object[] params = new Object[paramCount]; + for (int j = 0; j < paramCount; j++) { + params[j] = random.nextInt(4) == 1 ? null : random.nextInt(10) - 3; + } + try { + execute(sql, params); + } catch (Exception e) { + if (shortestFailed == null || shortestFailed.length() > sql.length()) { + TestBase.logError("/*SHORT*/ " + sql, null); + shortestFailed = sql; + } + } + } + // c1.close(); + // c2.close(); + } + + private void appendRandomTable() { + if (random.nextBoolean()) { + buff.append("ONE"); + } else { + buff.append("TWO"); + } + } + + private void appendRandomCondition(int j) { + if (random.nextInt(10) == 1) { + buff.append("NOT "); + appendRandomCondition(j); + } else if (random.nextInt(5) == 1) { + buff.append("("); + appendRandomCondition(j); + if (random.nextBoolean()) { + buff.append(") OR ("); + } else { + buff.append(") AND ("); + } + appendRandomCondition(j); + buff.append(")"); + } else { + if (j > 0 && random.nextBoolean()) { + buff.append("T" + (char) ('0' + j - 1) + ".A=T" + (char) ('0' + j) + ".A "); + } else { + appendRandomConditionPart(j); + } + } + } + + private void appendRandomConditionPart(int j) { + int t1 = j <= 1 ? 0 : random.nextInt(j + 1); + int t2 = j <= 1 ? 0 : random.nextInt(j + 1); + String c1 = random.nextBoolean() ? "A" : "B"; + String c2 = random.nextBoolean() ? "A" : "B"; + buff.append("T" + (char) ('0' + t1)); + buff.append("." + c1); + if (random.nextInt(4) == 1) { + if (random.nextInt(5) == 1) { + buff.append(" IS NOT NULL"); + } else { + buff.append(" IS NULL"); + } + } else { + if (random.nextInt(5) == 1) { + switch (random.nextInt(5)) { + case 0: + buff.append(">"); + break; + case 1: + buff.append("<"); + break; + case 2: + buff.append("<="); + break; + case 3: + buff.append(">="); + break; + case 4: + buff.append("<>"); + break; + default: + } + } else { + buff.append("="); + } + if (random.nextBoolean()) { + buff.append("T" + (char) ('0' + t2)); + buff.append("." + c2); + } else { + buff.append(random.nextInt(5) - 1); + } + } + buff.append(" "); + } + + private void execute(String sql, Object[] params) { + execute(sql, params, false); + } + + private void execute(String sql, Object[] params, boolean ignoreDifference) { + String first = null; + for (int i = 0; i < connections.size(); i++) { + Connection conn = connections.get(i); + String s; + try { + Statement stat; + boolean result; + if (params == null || params.length == 0) { + stat = conn.createStatement(); + result = stat.execute(sql); + } else { + PreparedStatement prep = conn.prepareStatement(sql); + stat = prep; + for (int j = 0; j < params.length; j++) { + prep.setObject(j + 1, params[j]); + } + result = prep.execute(); + } + if (result) { + ResultSet rs = stat.getResultSet(); + s = "rs: " + readResult(rs); + } else { + s = "updateCount: " + stat.getUpdateCount(); + } + } catch (SQLException e) { + s = "exception"; + } + if (i == 0) { + first = s; + } else { + if (!ignoreDifference && !s.equals(first)) { + fail("FAIL s:" + s + " first:" + first + " sql:" + sql); + } + } + } + } + + private static String readResult(ResultSet rs) throws SQLException { + StringBuilder b = new StringBuilder(); + ResultSetMetaData meta = rs.getMetaData(); + int columnCount = meta.getColumnCount(); + for (int i = 0; i < columnCount; i++) { + if (i > 0) { + b.append(","); + } + b.append(StringUtils.toUpperEnglish(meta.getColumnLabel(i + 1))); + } + b.append(":\n"); + String result = b.toString(); + ArrayList list = new ArrayList<>(); + while (rs.next()) { + b = new StringBuilder(); + for (int i = 0; i < columnCount; i++) { + if (i > 0) { + b.append(","); + } + b.append(rs.getString(i + 1)); + } + list.add(b.toString()); + } + Collections.sort(list); + for (int i = 0; i < list.size(); i++) { + result += list.get(i) + "\n"; + } + return result; + } + +} diff --git a/h2/src/test/org/h2/test/synth/TestKill.java b/h2/src/test/org/h2/test/synth/TestKill.java new file mode 100644 index 0000000..52baf41 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestKill.java @@ -0,0 +1,166 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Random; + +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.test.utils.SelfDestructor; + +/** + * A random recovery test. This test starts a process that executes random + * operations against a database, then kills this process. Afterwards recovery + * is tested. + */ +public class TestKill extends TestDb { + + private static final String DIR = TestBase.getTestDir("kill"); + + private static final int ACCOUNTS = 10; + + private Connection conn; + private final Random random = new Random(1); + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + String connect = ""; + + connect = ";MAX_LOG_SIZE=10;THROTTLE=80"; + + String url = getURL(DIR + "/kill" + connect, true); + String user = getUser(); + String password = getPassword(); + String selfDestruct = SelfDestructor.getPropertyString(60); + String[] procDef = { + getJVM(), selfDestruct, + "-cp", getClassPath(), + "org.h2.test.synth.TestKillProcess", url, user, + password, getBaseDir(), "" + ACCOUNTS }; + + for (int i = 0;; i++) { + printTime("TestKill " + i); + if (i % 10 == 0) { + trace("deleting db..."); + deleteDb("kill"); + } + conn = getConnection(url); + createTables(); + checkData(); + initData(); + conn.close(); + Process proc = Runtime.getRuntime().exec(procDef); + // while(true) { + // int ch = proc.getErrorStream().read(); + // if(ch < 0) { + // break; + // } + // System.out.print((char)ch); + // } + int runtime = random.nextInt(10000); + trace("running..."); + Thread.sleep(runtime); + trace("stopping..."); + proc.destroy(); + proc.waitFor(); + trace("stopped"); + } + } + + private void createTables() throws SQLException { + trace("createTables..."); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE IF NOT EXISTS ACCOUNT" + + "(ID INT PRIMARY KEY, SUM INT)"); + stat.execute("CREATE TABLE IF NOT EXISTS LOG(" + + "ID IDENTITY, ACCOUNTID INT, AMOUNT INT, " + + "FOREIGN KEY(ACCOUNTID) REFERENCES ACCOUNT(ID))"); + stat.execute("CREATE TABLE IF NOT EXISTS TEST_A" + + "(ID INT PRIMARY KEY, DATA VARCHAR)"); + stat.execute("CREATE TABLE IF NOT EXISTS TEST_B" + + "(ID INT PRIMARY KEY, DATA VARCHAR)"); + } + + private void initData() throws SQLException { + trace("initData..."); + conn.createStatement().execute("DROP TABLE LOG"); + conn.createStatement().execute("DROP TABLE ACCOUNT"); + conn.createStatement().execute("DROP TABLE TEST_A"); + conn.createStatement().execute("DROP TABLE TEST_B"); + createTables(); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO ACCOUNT VALUES(?, 0)"); + for (int i = 0; i < ACCOUNTS; i++) { + prep.setInt(1, i); + prep.execute(); + } + PreparedStatement p1 = conn.prepareStatement( + "INSERT INTO TEST_A VALUES(?, '')"); + PreparedStatement p2 = conn.prepareStatement( + "INSERT INTO TEST_B VALUES(?, '')"); + for (int i = 0; i < ACCOUNTS; i++) { + p1.setInt(1, i); + p2.setInt(1, i); + p1.execute(); + p2.execute(); + } + } + + private void checkData() throws SQLException { + trace("checkData..."); + ResultSet rs = conn.createStatement().executeQuery( + "SELECT * FROM ACCOUNT ORDER BY ID"); + PreparedStatement prep = conn.prepareStatement( + "SELECT SUM(AMOUNT) FROM LOG WHERE ACCOUNTID=?"); + while (rs.next()) { + int account = rs.getInt(1); + int sum = rs.getInt(2); + prep.setInt(1, account); + ResultSet rs2 = prep.executeQuery(); + rs2.next(); + int sumLog = rs2.getInt(1); + assertEquals(sum, sumLog); + trace("account=" + account + " sum=" + sum); + } + PreparedStatement p1 = conn.prepareStatement( + "SELECT * FROM TEST_A WHERE ID=?"); + PreparedStatement p2 = conn.prepareStatement( + "SELECT * FROM TEST_B WHERE ID=?"); + for (int i = 0; i < ACCOUNTS; i++) { + p1.setInt(1, i); + p2.setInt(1, i); + ResultSet r1 = p1.executeQuery(); + ResultSet r2 = p2.executeQuery(); + boolean hasData = r1.next(); + assertEquals(r2.next(), hasData); + if (hasData) { + String d1 = r1.getString("DATA"); + String d2 = r2.getString("DATA"); + assertEquals(d1, d2); + assertFalse(r1.next()); + assertFalse(r2.next()); + trace("test: data=" + d1); + } else { + trace("test: empty"); + } + } + } + +} diff --git a/h2/src/test/org/h2/test/synth/TestKillProcess.java b/h2/src/test/org/h2/test/synth/TestKillProcess.java new file mode 100644 index 0000000..b432222 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestKillProcess.java @@ -0,0 +1,92 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.util.ArrayList; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import org.h2.store.FileLister; +import org.h2.test.TestBase; +import org.h2.test.utils.SelfDestructor; + +/** + * Test application for TestKill. + */ +public class TestKillProcess { + + private TestKillProcess() { + // utility class + } + + /** + * This method is called when executing this application. + * + * @param args the command line parameters + */ + public static void main(String... args) { + SelfDestructor.startCountdown(60); + try { + Class.forName("org.h2.Driver"); + String url = args[0], user = args[1], password = args[2]; + String baseDir = args[3]; + int accounts = Integer.parseInt(args[4]); + + Random random = new Random(); + Connection conn1 = DriverManager.getConnection( + url, user, password); + + PreparedStatement prep1a = conn1.prepareStatement( + "INSERT INTO LOG(ACCOUNTID, AMOUNT) VALUES(?, ?)"); + PreparedStatement prep1b = conn1.prepareStatement( + "UPDATE ACCOUNT SET SUM=SUM+? WHERE ID=?"); + conn1.setAutoCommit(false); + long time = System.nanoTime(); + String d = null; + for (int i = 0;; i++) { + long t = System.nanoTime(); + if (t > time + TimeUnit.SECONDS.toNanos(1)) { + ArrayList list = FileLister.getDatabaseFiles( + baseDir, "kill", true); + System.out.println("inserting... i:" + i + " d:" + d + + " files:" + list.size()); + time = t; + } + if (i > 10000) { + // System.out.println("halt"); + // Runtime.getRuntime().halt(0); + // conn.createStatement().execute("SHUTDOWN IMMEDIATELY"); + // System.exit(0); + } + int account = random.nextInt(accounts); + int value = random.nextInt(100); + prep1a.setInt(1, account); + prep1a.setInt(2, value); + prep1a.execute(); + prep1b.setInt(1, value); + prep1b.setInt(2, account); + prep1b.execute(); + conn1.commit(); + if (random.nextInt(100) < 2) { + d = "D" + random.nextInt(1000); + account = random.nextInt(accounts); + conn1.createStatement().execute( + "UPDATE TEST_A SET DATA='" + d + + "' WHERE ID=" + account); + conn1.createStatement().execute( + "UPDATE TEST_B SET DATA='" + d + + "' WHERE ID=" + account); + } + } + } catch (Throwable e) { + TestBase.logError("error", e); + } + } + +} diff --git a/h2/src/test/org/h2/test/synth/TestKillRestart.java b/h2/src/test/org/h2/test/synth/TestKillRestart.java new file mode 100644 index 0000000..d9ed492 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestKillRestart.java @@ -0,0 +1,233 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import org.h2.test.TestDb; +import org.h2.test.utils.SelfDestructor; + +/** + * Standalone recovery test. A new process is started and then killed while it + * executes random statements. + */ +public class TestKillRestart extends TestDb { + + @Override + public boolean isEnabled() { + if (config.networked) { + return false; + } + if (getBaseDir().indexOf(':') > 0) { + return false; + } + return true; + } + + @Override + public void test() throws Exception { + deleteDb("killRestart"); + String url = getURL("killRestart", true); + // String url = getURL( + // "killRestart;CACHE_SIZE=2048;WRITE_DELAY=0", true); + String user = getUser(), password = getPassword(); + String selfDestruct = SelfDestructor.getPropertyString(60); + String[] procDef = { getJVM(), selfDestruct, + "-cp", getClassPath(), "-ea", + getClass().getName(), "-url", url, "-user", user, + "-password", password }; + + int len = getSize(2, 15); + for (int i = 0; i < len; i++) { + Process p = new ProcessBuilder().redirectErrorStream(true).command(procDef).start(); + InputStream in = p.getInputStream(); + OutputCatcher catcher = new OutputCatcher(in); + catcher.start(); + while (true) { + String s = catcher.readLine(60 * 1000); + // System.out.println("> " + s); + if (s == null) { + fail("No reply from process"); + } else if (!s.startsWith("#")) { + // System.out.println(s); + fail("Expected: #..., got: " + s); + } else if (s.startsWith("#Running")) { + Thread.sleep(100); + printTime("killing: " + i); + p.destroy(); + waitForTimeout(p); + break; + } else if (s.startsWith("#Fail")) { + fail("Failed: " + s); + } + } + } + deleteDb("killRestart"); + } + + /** + * Wait for a subprocess with timeout. + */ + private static void waitForTimeout(final Process p) + throws InterruptedException, IOException { + final long pid = getPidOfProcess(p); + if (pid == -1) { + p.waitFor(); + } + // when we hit Java8 we can use the waitFor(1,TimeUnit.MINUTES) method + final CountDownLatch latch = new CountDownLatch(1); + new Thread("waitForTimeout") { + @Override + public void run() { + try { + p.waitFor(); + latch.countDown(); + } catch (InterruptedException ex) { + ex.printStackTrace(); + } + } + }.start(); + if (!latch.await(2, TimeUnit.MINUTES)) { + String[] procDef = { "jstack", "-F", "-m", "-l", "" + pid }; + new ProcessBuilder().redirectErrorStream(true).command(procDef) + .start(); + OutputCatcher catcher = new OutputCatcher(p.getInputStream()); + catcher.start(); + Thread.sleep(500); + throw new IOException("timed out waiting for subprocess to die"); + } + } + + /** + * Get the PID of a subprocess. Only works on Linux and OSX. + */ + private static long getPidOfProcess(Process p) { + // When we hit Java9 we can call getPid() on Process. + long pid = -1; + try { + if (p.getClass().getName().equals("java.lang.UNIXProcess")) { + Field f = p.getClass().getDeclaredField("pid"); + f.setAccessible(true); + pid = f.getLong(p); + f.setAccessible(false); + } + } catch (Exception e) { + pid = -1; + } + return pid; + } + + /** + * This method is called when executing this application from the command + * line. + * + * @param args the command line parameters + */ + public static void main(String... args) { + SelfDestructor.startCountdown(60); + String driver = "org.h2.Driver"; + String url = "jdbc:h2:mem:test", user = "sa", password = "sa"; + for (int i = 0; i < args.length; i++) { + if ("-url".equals(args[i])) { + url = args[++i]; + } else if ("-driver".equals(args[i])) { + driver = args[++i]; + } else if ("-user".equals(args[i])) { + user = args[++i]; + } else if ("-password".equals(args[i])) { + password = args[++i]; + } + } + System.out.println("#Started; driver: " + driver + " url: " + url + + " user: " + user + " password: " + password); + try { + Class.forName(driver); + System.out.println("#Opening..."); + Connection conn = DriverManager.getConnection(url, user, password); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE IF NOT EXISTS TEST" + + "(ID IDENTITY, NAME VARCHAR)"); + stat.execute("CREATE TABLE IF NOT EXISTS TEST2" + + "(ID IDENTITY, NAME VARCHAR)"); + ResultSet rs = stat.executeQuery("SELECT * FROM TEST"); + while (rs.next()) { + rs.getLong("ID"); + rs.getString("NAME"); + } + rs = stat.executeQuery("SELECT * FROM TEST2"); + while (rs.next()) { + rs.getLong("ID"); + rs.getString("NAME"); + } + stat.execute("DROP ALL OBJECTS DELETE FILES"); + System.out.println("#Closing with delete..."); + conn.close(); + System.out.println("#Starting..."); + conn = DriverManager.getConnection(url, user, password); + stat = conn.createStatement(); + stat.execute("DROP ALL OBJECTS"); + stat.execute("CREATE TABLE TEST(ID IDENTITY, NAME VARCHAR)"); + stat.execute("CREATE TABLE TEST2(ID IDENTITY, NAME VARCHAR)"); + stat.execute("CREATE TABLE TEST_META(ID INT)"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST(NAME) VALUES(?)"); + PreparedStatement prep2 = conn.prepareStatement( + "INSERT INTO TEST2(NAME) VALUES(?)"); + Random r = new Random(0); +// Runnable stopper = new Runnable() { +// public void run() { +// try { +// Thread.sleep(500); +// } catch (InterruptedException e) { +// } +// System.out.println("#Halt..."); +// Runtime.getRuntime().halt(0); +// } +// }; +// new Thread(stopper).start(); + for (int i = 0; i < 2000; i++) { + if (i == 100) { + System.out.println("#Running..."); + } + if (r.nextInt(100) < 10) { + conn.createStatement().execute( + "ALTER TABLE TEST_META " + + "ALTER COLUMN ID INT DEFAULT 10"); + } + if (r.nextBoolean()) { + if (r.nextBoolean()) { + prep.setString(1, new String(new char[r.nextInt(30) * 10])); + prep.execute(); + } else { + prep2.setString(1, new String(new char[r.nextInt(30) * 10])); + prep2.execute(); + } + } else { + if (r.nextBoolean()) { + conn.createStatement().execute( + "UPDATE TEST SET NAME = NULL"); + } else { + conn.createStatement().execute( + "UPDATE TEST2 SET NAME = NULL"); + } + } + } + } catch (Throwable e) { + e.printStackTrace(System.out); + System.out.println("#Fail: " + e.toString()); + } + } + +} diff --git a/h2/src/test/org/h2/test/synth/TestKillRestartMulti.java b/h2/src/test/org/h2/test/synth/TestKillRestartMulti.java new file mode 100644 index 0000000..a8858e1 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestKillRestartMulti.java @@ -0,0 +1,331 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.io.InputStream; +import java.lang.ProcessBuilder.Redirect; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Random; + +import org.h2.api.ErrorCode; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.test.utils.SelfDestructor; +import org.h2.tools.Backup; + +/** + * Standalone recovery test. A new process is started and then killed while it + * executes random statements using multiple connection. + */ +public class TestKillRestartMulti extends TestDb { + + /** + * We want self-destruct to occur before the read times out and we kill the + * child process. + */ + private static final int CHILD_READ_TIMEOUT_MS = 7 * 60 * 1000; // 7 minutes + private static final int CHILD_SELFDESTRUCT_TIMEOUT_MINS = 5; + + private String driver = "org.h2.Driver"; + private String url; + private String user = "sa"; + private String password = "sa"; + private final ArrayList connections = new ArrayList<>(); + private final ArrayList tables = new ArrayList<>(); + private int openCount; + + + /** + * This method is called when executing this application from the command + * line. + * + * Note that this entry can be used in two different ways, either + * (a) running just this test + * (b) or when this test invokes itself in a child process + * + * @param args the command line parameters + */ + public static void main(String... args) throws Exception { + if (args != null && args.length > 0) { + // the child process case + SelfDestructor.startCountdown(CHILD_SELFDESTRUCT_TIMEOUT_MINS); + new TestKillRestartMulti().test(args); + } else { + // the standalone test case + TestBase.createCaller().init().testFromMain(); + } + } + + @Override + public boolean isEnabled() { + if (config.networked) { + return false; + } + if (getBaseDir().indexOf(':') > 0) { + return false; + } + return true; + } + + @Override + public void test() throws Exception { + deleteDb("killRestartMulti"); + url = getURL("killRestartMulti;RETENTION_TIME=0", true); + user = getUser(); + password = getPassword(); + String selfDestruct = SelfDestructor.getPropertyString(60); + // Inherit error so that the stacktraces reported from SelfDestructor + // show up in our log. + ProcessBuilder pb = new ProcessBuilder().redirectError(Redirect.INHERIT) + .command(getJVM(), selfDestruct, "-cp", getClassPath(), + "-ea", + getClass().getName(), "-url", url, "-user", user, + "-password", password); + deleteDb("killRestartMulti"); + int len = getSize(3, 10); + Random random = new Random(); + for (int i = 0; i < len; i++) { + Process p = pb.start(); + InputStream in = p.getInputStream(); + OutputCatcher catcher = new OutputCatcher(in); + catcher.start(); + while (true) { + String s = catcher.readLine(CHILD_READ_TIMEOUT_MS); + // System.out.println("> " + s); + if (s == null) { + fail("No reply from process"); + } else if (!s.startsWith("#")) { + // System.out.println(s); + fail("Expected: #..., got: " + s); + } else if (s.startsWith("#Running")) { + int sleep = 10 + random.nextInt(100); + Thread.sleep(sleep); + printTime("killing: " + i); + p.destroy(); + printTime("killing, waiting for: " + i); + p.waitFor(); + printTime("killing, dead: " + i); + break; + } else if (s.startsWith("#Info")) { + // System.out.println("info: " + s); + } else if (s.startsWith("#Fail")) { + System.err.println(s); + while (true) { + String a = catcher.readLine(CHILD_READ_TIMEOUT_MS); + if (a == null || "#End".endsWith(a)) { + break; + } + System.err.println(" " + a); + } + fail("Failed: " + s); + } + } + String backup = getBaseDir() + "/killRestartMulti-" + + System.currentTimeMillis() + ".zip"; + try { + Backup.execute(backup, getBaseDir(), "killRestartMulti", true); + Connection conn = null; + for (int j = 0;; j++) { + try { + conn = openConnection(); + break; + } catch (SQLException e2) { + if (e2.getErrorCode() == ErrorCode.DATABASE_ALREADY_OPEN_1 + && j < 3) { + Thread.sleep(100); + } else { + throw e2; + } + } + } + testConsistent(conn); + Statement stat = conn.createStatement(); + stat.execute("DROP ALL OBJECTS"); + conn.close(); + conn = openConnection(); + conn.close(); + FileUtils.delete(backup); + } catch (SQLException e) { + FileUtils.move(backup, backup + ".error"); + throw e; + } + } + deleteDb("killRestartMulti"); + } + + private void test(String... args) { + for (int i = 0; i < args.length; i++) { + if ("-url".equals(args[i])) { + url = args[++i]; + } else if ("-driver".equals(args[i])) { + driver = args[++i]; + } else if ("-user".equals(args[i])) { + user = args[++i]; + } else if ("-password".equals(args[i])) { + password = args[++i]; + } + } + System.out.println("#Started; driver: " + driver + " url: " + url + + " user: " + user + " password: " + password); + try { + System.out.println("#Starting..."); + Random random = new Random(); + boolean wasRunning = false; + for (int i = 0; i < 3000; i++) { + if (i > 1000 && connections.size() > 1 && tables.size() > 1) { + System.out.println("#Running connections: " + + connections.size() + " tables: " + tables.size()); + wasRunning = true; + } + if (connections.size() < 1) { + openConnection(); + } + if (tables.size() < 1) { + createTable(random); + } + int p = random.nextInt(100); + if ((p -= 2) <= 0) { + // 2%: open new connection + if (connections.size() < 5) { + openConnection(); + } + } else if ((p -= 1) <= 0) { + // 1%: close connection + if (connections.size() > 1) { + Connection conn = connections.remove( + random.nextInt(connections.size())); + if (random.nextBoolean()) { + conn.close(); + } + } + } else if ((p -= 10) <= 0) { + // 10% create table + createTable(random); + } else if ((p -= 20) <= 0) { + // 20% large insert, delete, or update + if (tables.size() > 0) { + Connection conn = connections.get( + random.nextInt(connections.size())); + Statement stat = conn.createStatement(); + String table = tables.get(random.nextInt(tables.size())); + if (random.nextBoolean()) { + // 10% insert + stat.execute("INSERT INTO " + table + + "(NAME) SELECT 'Hello ' || X FROM SYSTEM_RANGE(0, 20)"); + } else if (random.nextBoolean()) { + // 5% update + stat.execute("UPDATE " + table + " SET NAME='Hallo Welt'"); + } else { + // 5% delete + stat.execute("DELETE FROM " + table); + } + } + } else if ((p -= 5) < 0) { + // 5% truncate or drop table + if (tables.size() > 0) { + Connection conn = connections.get(random.nextInt(connections.size())); + Statement stat = conn.createStatement(); + String table = tables.get(random.nextInt(tables.size())); + if (random.nextBoolean()) { + stat.execute("TRUNCATE TABLE " + table); + } else { + stat.execute("DROP TABLE " + table); + System.out.println("#Info table dropped: " + table); + tables.remove(table); + } + } + } else if ((p -= 30) <= 0) { + // 30% insert + if (tables.size() > 0) { + Connection conn = connections.get(random.nextInt(connections.size())); + Statement stat = conn.createStatement(); + String table = tables.get(random.nextInt(tables.size())); + stat.execute("INSERT INTO " + table + "(NAME) VALUES('Hello World')"); + } + } else { + // 32% delete + if (tables.size() > 0) { + Connection conn = connections.get(random.nextInt(connections.size())); + Statement stat = conn.createStatement(); + String table = tables.get(random.nextInt(tables.size())); + stat.execute("DELETE FROM " + table + + " WHERE ID = SELECT MIN(ID) FROM " + table); + } + } + } + System.out.println("#Fail: end " + wasRunning); + System.out.println("#End"); + } catch (Throwable e) { + System.out.println("#Fail: openCount=" + + openCount + " url=" + url + " " + e.toString()); + e.printStackTrace(System.out); + System.out.println("#End"); + } + } + + private Connection openConnection() throws Exception { + Class.forName(driver); + openCount++; + Connection conn = DriverManager.getConnection(url, user, password); + connections.add(conn); + return conn; + } + + private void createTable(Random random) throws SQLException { + Connection conn = connections.get(random.nextInt(connections.size())); + Statement stat = conn.createStatement(); + String table = "TEST" + random.nextInt(10); + try { + stat.execute("CREATE TABLE " + table + "(ID IDENTITY, NAME VARCHAR)"); + System.out.println("#Info table created: " + table); + tables.add(table); + } catch (SQLException e) { + if (e.getErrorCode() == ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1) { + System.out.println("#Info table already exists: " + table); + if (!tables.contains(table)) { + tables.add(table); + } + // ok + } else { + throw e; + } + } + } + + private static void testConsistent(Connection conn) throws SQLException { + for (int i = 0; i < 20; i++) { + Statement stat = conn.createStatement(); + try { + ResultSet rs = stat.executeQuery("SELECT * FROM TEST" + i); + while (rs.next()) { + rs.getLong("ID"); + rs.getString("NAME"); + } + rs = stat.executeQuery("SELECT * FROM TEST" + i + " ORDER BY ID"); + while (rs.next()) { + rs.getLong("ID"); + rs.getString("NAME"); + } + } catch (SQLException e) { + if (e.getErrorCode() == ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1 || + e.getErrorCode() == ErrorCode.TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1 || + e.getErrorCode() == ErrorCode.TABLE_OR_VIEW_NOT_FOUND_WITH_CANDIDATES_2 + ) { + // ok + } else { + throw e; + } + } + } + } + +} diff --git a/h2/src/test/org/h2/test/synth/TestLimit.java b/h2/src/test/org/h2/test/synth/TestLimit.java new file mode 100644 index 0000000..5a063b0 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestLimit.java @@ -0,0 +1,95 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * The LIMIT, OFFSET, maxRows. + */ +public class TestLimit extends TestDb { + + private Statement stat; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); + // test.config.traceTest = true; + test.testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb("limit"); + Connection conn = getConnection("limit"); + stat = conn.createStatement(); + stat.execute("create table test(id int) as " + + "select x from system_range(1, 10)"); + for (int maxRows = 0; maxRows < 12; maxRows++) { + stat.setMaxRows(maxRows); + for (int limit = -1; limit < 12; limit++) { + for (int offset = -1; offset < 12; offset++) { + int l = limit < 0 ? 10 : Math.min(10, limit); + for (int d = 0; d < 2; d++) { + int m = maxRows <= 0 ? 10 : Math.min(10, maxRows); + int expected = Math.min(m, l); + if (offset > 0) { + expected = Math.max(0, Math.min(10 - offset, expected)); + } + String s = "select " + (d == 1 ? "distinct " : "") + "* from test" + + (offset >= 0 ? " offset " + offset + " rows" : "") + + (limit >= 0 ? " fetch next " + limit + " rows only" : ""); + assertRow(expected, s); + String union = "(" + s + ") union (" + s + ")"; + assertRow(expected, union); + m = maxRows <= 0 ? 20 : Math.min(20, maxRows); + if (offset > 0) { + l = Math.max(0, Math.min(10 - offset, l)); + } + expected = Math.min(m, l * 2); + union = "(" + s + ") union all (" + s + ")"; + assertRow(expected, union); + for (int unionLimit = -1; unionLimit < 5; unionLimit++) { + int e = unionLimit < 0 ? 20 : Math.min(20, unionLimit); + e = Math.min(expected, e); + String u = union; + if (unionLimit >= 0) { + u += " fetch first " + unionLimit + " rows only"; + } + assertRow(e, u); + } + } + } + } + } + assertEquals(0, stat.executeUpdate("delete from test limit 0")); + assertEquals(1, stat.executeUpdate("delete from test limit 1")); + assertEquals(2, stat.executeUpdate("delete from test limit 2")); + assertThrows(ErrorCode.INVALID_VALUE_2, stat).executeUpdate("delete from test limit null"); + conn.close(); + deleteDb("limit"); + } + + private void assertRow(int expected, String sql) throws SQLException { + try { + assertResultRowCount(expected, stat.executeQuery(sql)); + } catch (AssertionError e) { + stat.executeQuery(sql + " -- cache killer"); + throw e; + } + } + +} diff --git a/h2/src/test/org/h2/test/synth/TestMultiThreaded.java b/h2/src/test/org/h2/test/synth/TestMultiThreaded.java new file mode 100644 index 0000000..4e1fc87 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestMultiThreaded.java @@ -0,0 +1,189 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Random; + +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests the multi-threaded mode. + */ +public class TestMultiThreaded extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + org.h2.test.TestAll config = new org.h2.test.TestAll(); + config.memory = true; + config.big = true; + System.out.println(config); + TestBase test = createCaller().init(config); + for (int i = 0; i < 100; i++) { + System.out.println("Pass #" + i); + test.testFromMain(); + } + } + + /** + * Processes random operations. + */ + private class Processor extends Thread { + private final int id; + private final Statement stat; + private final Random random; + private volatile Throwable exception; + private boolean stop; + + Processor(Connection conn, int id) throws SQLException { + this.id = id; + stat = conn.createStatement(); + random = new Random(id); + } + public Throwable getException() { + return exception; + } + @Override + public void run() { + int count = 0; + ResultSet rs; + try { + while (!stop) { + switch (random.nextInt(6)) { + case 0: + // insert a row for this connection + traceThread("insert " + id + " count: " + count); + stat.execute("INSERT INTO TEST(NAME) VALUES('"+ id +"')"); + traceThread("insert done"); + count++; + break; + case 1: + // delete a row for this connection + if (count > 0) { + traceThread("delete " + id + " count: " + count); + int updateCount = stat.executeUpdate( + "DELETE FROM TEST " + + "WHERE NAME = '"+ id +"' AND ROWNUM()<2"); + traceThread("delete done"); + if (updateCount != 1) { + throw new AssertionError( + "Expected: 1 Deleted: " + updateCount); + } + count--; + } + break; + case 2: + // select the number of rows of this connection + traceThread("select " + id + " count: " + count); + rs = stat.executeQuery("SELECT COUNT(*) " + + "FROM TEST WHERE NAME = '"+ id +"'"); + traceThread("select done"); + rs.next(); + int got = rs.getInt(1); + if (got != count) { + throw new AssertionError("Expected: " + count + " got: " + got); + } + break; + case 3: + traceThread("insert"); + stat.execute("INSERT INTO TEST(NAME) VALUES(NULL)"); + traceThread("insert done"); + break; + case 4: + traceThread("delete"); + stat.execute("DELETE FROM TEST WHERE NAME IS NULL"); + traceThread("delete done"); + break; + case 5: + traceThread("select"); + rs = stat.executeQuery("SELECT * FROM TEST WHERE NAME IS NULL"); + traceThread("select done"); + while (rs.next()) { + rs.getString(1); + } + break; + } + } + } catch (Throwable e) { + exception = e; + } + } + + private void traceThread(String s) { + if (config.traceTest) { + trace(id + " " + s); + } + } + public void stopNow() { + this.stop = true; + } + } + + @Override + public void test() throws Exception { + deleteDb("multiThreaded"); + int size = getSize(2, 20); + Connection[] connList = new Connection[size]; + for (int i = 0; i < size; i++) { + connList[i] = getConnection("multiThreaded"); + } + Connection conn = connList[0]; + Statement stat = conn.createStatement(); + stat.execute("CREATE SEQUENCE TEST_SEQ"); + stat.execute("CREATE TABLE TEST" + + "(ID BIGINT DEFAULT NEXT VALUE FOR TEST_SEQ, NAME VARCHAR)"); + // stat.execute("CREATE TABLE TEST(ID IDENTITY, NAME VARCHAR)"); + // stat.execute("CREATE INDEX IDX_TEST_NAME ON TEST(NAME)"); + trace("init done"); + Processor[] processors = new Processor[size]; + for (int i = 0; i < size; i++) { + conn = connList[i]; + conn.createStatement().execute("SET LOCK_TIMEOUT 1000"); + processors[i] = new Processor(conn, i); + processors[i].start(); + trace("started " + i); + Thread.sleep(100); + } + try { + Thread.sleep(2000); + } finally { + trace("stopping"); + for (int i = 0; i < size; i++) { + Processor p = processors[i]; + p.stopNow(); + } + for (int i = 0; i < size; i++) { + Processor p = processors[i]; + p.join(1000); + } + trace("close"); + for (int i = 0; i < size; i++) { + connList[i].close(); + } + deleteDb("multiThreaded"); + } + + boolean success = true; + for (int i = 0; i < size; i++) { + Processor p = processors[i]; + p.join(10000); + Throwable exception = p.getException(); + if (exception != null) { + logError("", exception); + success = false; + } + } + assert success; + } +} diff --git a/h2/src/test/org/h2/test/synth/TestNestedJoins.java b/h2/src/test/org/h2/test/synth/TestNestedJoins.java new file mode 100644 index 0000000..d72fde3 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestNestedJoins.java @@ -0,0 +1,658 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.io.File; +import java.io.StringReader; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Random; + +import org.h2.api.ErrorCode; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.ScriptReader; + +/** + * Tests nested joins and right outer joins. + */ +public class TestNestedJoins extends TestDb { + + private final ArrayList dbs = new ArrayList<>(); + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); + // test.config.traceTest = true; + test.testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb("nestedJoins"); + // testCases2(); + testCases(); + testRandom(); + deleteDb("nestedJoins"); + } + + private void testRandom() throws Exception { + Connection conn = getConnection("nestedJoins"); + dbs.add(conn.createStatement()); + + try { + Class.forName("org.postgresql.Driver"); + Connection c2 = DriverManager.getConnection("jdbc:postgresql:test?loggerLevel=OFF", "sa", "sa"); + dbs.add(c2.createStatement()); + } catch (Exception e) { + // database not installed - ok + } + + // Derby doesn't work currently + deleteDerby(); + try { + Class.forName("org.apache.derby.iapi.jdbc.AutoloadedDriver"); + Connection c2 = DriverManager.getConnection( + "jdbc:derby:" + getBaseDir() + + "/derby/test;create=true", "sa", "sa"); + dbs.add(c2.createStatement()); + } catch (Throwable e) { + // database not installed - ok + } + String shortest = null; + Throwable shortestEx = null; + for (int i = 0; i < 10; i++) { + try { + execute("drop table t" + i); + } catch (Exception e) { + // ignore + } + String sql = "create table t" + i + "(x int)"; + trace(sql + ";"); + execute(sql); + if (i >= 4) { + for (int j = 0; j < i; j++) { + sql = "insert into t" + i + " values(" + j + ")"; + trace(sql + ";"); + execute(sql); + } + } + } + // the first 4 tables: all combinations + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 4; j++) { + if ((i & (1 << j)) != 0) { + String sql = "insert into t" + j + " values(" + i + ")"; + trace(sql + ";"); + execute(sql); + } + } + } + Random random = new Random(1); + int size = getSize(1000, 10000); + for (int i = 0; i < size; i++) { + StringBuilder buff = new StringBuilder(); + int t = 1 + random.nextInt(9); + buff.append("select "); + for (int j = 0; j < t; j++) { + if (j > 0) { + buff.append(", "); + } + buff.append("t" + j + ".x "); + } + buff.append("from "); + appendRandomJoin(random, buff, 0, t - 1); + String sql = buff.toString(); + try { + execute(sql); + } catch (Throwable e) { + if (e instanceof SQLException) { + trace(sql); + fail(sql); + // SQLException se = (SQLException) e; + // System.out.println(se); + // System.out.println(" " + sql); + } + if (shortest == null || sql.length() < shortest.length()) { + shortest = sql; + shortestEx = e; + } + } + } + if (shortest != null) { + shortestEx.printStackTrace(); + fail(shortest + " " + shortestEx); + } + for (int i = 0; i < 10; i++) { + try { + execute("drop table t" + i); + } catch (Exception e) { + // ignore + } + } + for (Statement s : dbs) { + s.getConnection().close(); + } + deleteDerby(); + deleteDb("nestedJoins"); + } + + private void deleteDerby() { + try { + new File("derby.log").delete(); + try { + DriverManager.getConnection("jdbc:derby:" + + getBaseDir() + "/derby/test;shutdown=true", "sa", "sa"); + } catch (Exception e) { + // ignore + } + FileUtils.deleteRecursive(getBaseDir() + "/derby", false); + } catch (Exception e) { + e.printStackTrace(); + // database not installed - ok + } + } + + private void appendRandomJoin(Random random, StringBuilder buff, int min, + int max) { + if (min == max) { + buff.append("t" + min); + return; + } + buff.append("("); + int m = min + random.nextInt(max - min); + int left = min + (m == min ? 0 : random.nextInt(m - min)); + appendRandomJoin(random, buff, min, m); + switch (random.nextInt(3)) { + case 0: + buff.append(" inner join "); + break; + case 1: + buff.append(" left outer join "); + break; + case 2: + buff.append(" right outer join "); + break; + } + m++; + int right = m + (m == max ? 0 : random.nextInt(max - m)); + appendRandomJoin(random, buff, m, max); + buff.append(" on t" + left + ".x = t" + right + ".x "); + buff.append(")"); + } + + private void execute(String sql) throws SQLException { + String expected = null; + SQLException e = null; + for (Statement s : dbs) { + try { + boolean result = s.execute(sql); + if (result) { + String data = getResult(s.getResultSet()); + if (expected == null) { + expected = data; + } else { + assertEquals(sql, expected, data); + } + } + } catch (SQLException e2) { + // ignore now, throw at the end + e = e2; + } + } + if (e != null) { + throw e; + } + } + + private static String getResult(ResultSet rs) throws SQLException { + ArrayList list = new ArrayList<>(); + while (rs.next()) { + StringBuilder buff = new StringBuilder(); + for (int i = 0; i < rs.getMetaData().getColumnCount(); i++) { + if (i > 0) { + buff.append(" "); + } + buff.append(rs.getString(i + 1)); + } + list.add(buff.toString()); + } + Collections.sort(list); + return list.toString(); + } + + private void testCases() throws Exception { + + Connection conn = getConnection("nestedJoins"); + Statement stat = conn.createStatement(); + ResultSet rs; + String sql; + + // issue 288 + assertThrows(ErrorCode.COLUMN_NOT_FOUND_1, stat). + execute("select 1 from dual a right outer join " + + "(select b.x from dual b) c on unknown_table.x = c.x, dual d"); + + // issue 288 + stat.execute("create table test(id int primary key)"); + stat.execute("insert into test values(1)"); + // this threw the exception Column "T.ID" must be in the GROUP BY list + stat.execute("select * from test t right outer join " + + "(select t2.id, count(*) c from test t2 group by t2.id) x on x.id = t.id " + + "where t.id = 1"); + + // the query plan of queries with subqueries + // that contain nested joins was wrong + stat.execute("select 1 from (select 2 from ((test t1 inner join test t2 " + + "on t1.id=t2.id) inner join test t3 on t3.id=t1.id)) x"); + + stat.execute("drop table test"); + + // issue 288 + /* + create table test(id int); + select 1 from test a right outer join test b on a.id = 1, test c; + drop table test; + */ + stat.execute("create table test(id int)"); + stat.execute("select 1 from test a " + + "right outer join test b on a.id = 1, test c"); + stat.execute("drop table test"); + + /* + create table a(id int); + create table b(id int); + create table c(id int); + select * from a inner join b inner join c on c.id = b.id on b.id = a.id; + drop table a, b, c; + */ + stat.execute("create table a(id int)"); + stat.execute("create table b(id int)"); + stat.execute("create table c(id int)"); + rs = stat.executeQuery("explain select * from a inner join b " + + "inner join c on c.id = b.id on b.id = a.id"); + assertTrue(rs.next()); + sql = rs.getString(1); + assertContains(sql, "("); + stat.execute("drop table a, b, c"); + + /* + create table test(id int primary key, x int) + as select x, x from system_range(1, 10); + create index on test(x); + create table o(id int primary key) + as select x from system_range(1, 10); + explain select * from test a inner join test b + on a.id=b.id left outer join o on o.id=a.id where b.x=1; + -- expected: no tableScan + explain select * from test a inner join test b + on a.id=b.id inner join o on o.id=a.id where b.x=1; + -- expected: no tableScan + drop table test; + drop table o; + */ + stat.execute("create table test(id int primary key, x int) " + + "as select x, x from system_range(1, 10)"); + stat.execute("create index on test(x)"); + stat.execute("create table o(id int primary key) " + + "as select x from system_range(1, 10)"); + rs = stat.executeQuery("explain select * from test a inner join " + + "test b on a.id=b.id inner join o on o.id=a.id where b.x=1"); + assertTrue(rs.next()); + sql = rs.getString(1); + assertTrue("using table scan", sql.indexOf("tableScan") < 0); + rs = stat.executeQuery("explain select * from test a inner join " + + "test b on a.id=b.id left outer join o on o.id=a.id where b.x=1"); + assertTrue(rs.next()); + sql = rs.getString(1); + // TODO support optimizing queries with both inner and outer joins + // assertTrue("using table scan", sql.indexOf("tableScan") < 0); + stat.execute("drop table test"); + stat.execute("drop table o"); + + /* + create table test(id int primary key); + insert into test values(1); + select b.id from test a left outer join test b on a.id = b.id + and not exists (select * from test c where c.id = b.id); + -- expected: null + */ + stat.execute("create table test(id int primary key)"); + stat.execute("insert into test values(1)"); + rs = stat.executeQuery("select b.id from test a left outer join " + + "test b on a.id = b.id and not exists " + + "(select * from test c where c.id = b.id)"); + assertTrue(rs.next()); + sql = rs.getString(1); + assertEquals(null, sql); + stat.execute("drop table test"); + + /* + create table test(id int primary key); + explain select * from test a left outer join (test c) on a.id = c.id; + -- expected: uses the primary key index + */ + stat.execute("create table test(id int primary key)"); + rs = stat.executeQuery("explain select * from test a " + + "left outer join (test c) on a.id = c.id"); + assertTrue(rs.next()); + sql = rs.getString(1); + assertContains(sql, "PRIMARY_KEY"); + stat.execute("drop table test"); + + /* + create table t1(a int, b int); + create table t2(a int, b int); + create table t3(a int, b int); + create table t4(a int, b int); + insert into t1 values(1,1), (2,2), (3,3); + insert into t2 values(1,1), (2,2); + insert into t3 values(1,1), (3,3); + insert into t4 values(1,1), (2,2), (3,3), (4,4); + select distinct t1.a, t2.a, t3.a from t1 + right outer join t3 on t1.b=t3.a right outer join t2 on t2.b=t1.a; + drop table t1, t2, t3, t4; + */ + stat.execute("create table t1(a int, b int)"); + stat.execute("create table t2(a int, b int)"); + stat.execute("create table t3(a int, b int)"); + stat.execute("create table t4(a int, b int)"); + stat.execute("insert into t1 values(1,1), (2,2), (3,3)"); + stat.execute("insert into t2 values(1,1), (2,2)"); + stat.execute("insert into t3 values(1,1), (3,3)"); + stat.execute("insert into t4 values(1,1), (2,2), (3,3), (4,4)"); + rs = stat.executeQuery( + "explain select distinct t1.a, t2.a, t3.a from t1 " + + "right outer join t3 on t1.b=t3.a right outer join t2 on t2.b=t1.a"); + assertTrue(rs.next()); + sql = cleanRemarks(rs.getString(1)); + assertEquals("SELECT DISTINCT \"T1\".\"A\", \"T2\".\"A\", \"T3\".\"A\" FROM \"PUBLIC\".\"T2\" " + + "LEFT OUTER JOIN ( \"PUBLIC\".\"T3\" LEFT OUTER JOIN \"PUBLIC\".\"T1\" " + + "ON \"T1\".\"B\" = \"T3\".\"A\" ) ON \"T2\".\"B\" = \"T1\".\"A\"", sql); + rs = stat.executeQuery("select distinct t1.a, t2.a, t3.a from t1 " + + "right outer join t3 on t1.b=t3.a " + + "right outer join t2 on t2.b=t1.a"); + // expected: + // null 2 null + // 1 1 1 + assertTrue(rs.next()); + assertEquals(null, rs.getString(1)); + assertEquals("2", rs.getString(2)); + assertEquals(null, rs.getString(3)); + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals("1", rs.getString(2)); + assertEquals("1", rs.getString(3)); + assertFalse(rs.next()); + stat.execute("drop table t1, t2, t3, t4"); + + /* + create table a(x int); + create table b(x int); + create table c(x int); + insert into a values(1); + insert into b values(1); + insert into c values(1), (2); + select a.x, b.x, c.x from a inner join b on a.x = b.x + right outer join c on c.x = a.x; + drop table a, b, c; + */ + stat.execute("create table a(x int)"); + stat.execute("create table b(x int)"); + stat.execute("create table c(x int)"); + stat.execute("insert into a values(1)"); + stat.execute("insert into b values(1)"); + stat.execute("insert into c values(1), (2)"); + rs = stat.executeQuery("explain select a.x, b.x, c.x from a " + + "inner join b on a.x = b.x right outer join c on c.x = a.x"); + assertTrue(rs.next()); + sql = cleanRemarks(rs.getString(1)); + assertEquals("SELECT \"A\".\"X\", \"B\".\"X\", \"C\".\"X\" FROM \"PUBLIC\".\"C\" LEFT OUTER JOIN " + + "( \"PUBLIC\".\"A\" INNER JOIN \"PUBLIC\".\"B\" " + + "ON \"A\".\"X\" = \"B\".\"X\" ) ON \"C\".\"X\" = \"A\".\"X\"", sql); + rs = stat.executeQuery("select a.x, b.x, c.x from a " + + "inner join b on a.x = b.x " + + "right outer join c on c.x = a.x"); + // expected result: 1 1 1; null null 2 + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals("1", rs.getString(2)); + assertEquals("1", rs.getString(3)); + assertTrue(rs.next()); + assertEquals(null, rs.getString(1)); + assertEquals(null, rs.getString(2)); + assertEquals("2", rs.getString(3)); + assertFalse(rs.next()); + stat.execute("drop table a, b, c"); + + /* + drop table a, b, c; + create table a(x int); + create table b(x int); + create table c(x int, y int); + insert into a values(1), (2); + insert into b values(3); + insert into c values(1, 3); + insert into c values(4, 5); + explain select * from a left outer join + (b left outer join c on b.x = c.y) on a.x = c.x; + select * from a left outer join + (b left outer join c on b.x = c.y) on a.x = c.x; + */ + stat.execute("create table a(x int)"); + stat.execute("create table b(x int)"); + stat.execute("create table c(x int, y int)"); + stat.execute("insert into a values(1), (2)"); + stat.execute("insert into b values(3)"); + stat.execute("insert into c values(1, 3)"); + stat.execute("insert into c values(4, 5)"); + rs = stat.executeQuery("explain select * from a " + + "left outer join (b " + + "left outer join c " + + "on b.x = c.y) " + + "on a.x = c.x"); + assertTrue(rs.next()); + sql = cleanRemarks(rs.getString(1)); + assertEquals("SELECT \"PUBLIC\".\"A\".\"X\", \"PUBLIC\".\"B\".\"X\", " + + "\"PUBLIC\".\"C\".\"X\", \"PUBLIC\".\"C\".\"Y\" FROM \"PUBLIC\".\"A\" " + + "LEFT OUTER JOIN ( \"PUBLIC\".\"B\" " + + "LEFT OUTER JOIN \"PUBLIC\".\"C\" " + + "ON \"B\".\"X\" = \"C\".\"Y\" ) " + + "ON \"A\".\"X\" = \"C\".\"X\"", sql); + rs = stat.executeQuery("select * from a " + + "left outer join (b " + + "left outer join c " + + "on b.x = c.y) " + + "on a.x = c.x"); + // expected result: 1 3 1 3; 2 null null null + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals("3", rs.getString(2)); + assertEquals("1", rs.getString(3)); + assertEquals("3", rs.getString(4)); + assertTrue(rs.next()); + assertEquals("2", rs.getString(1)); + assertEquals(null, rs.getString(2)); + assertEquals(null, rs.getString(3)); + assertEquals(null, rs.getString(4)); + assertFalse(rs.next()); + stat.execute("drop table a, b, c"); + + stat.execute("create table a(x int primary key)"); + stat.execute("insert into a values(0), (1)"); + stat.execute("create table b(x int primary key)"); + stat.execute("insert into b values(0)"); + stat.execute("create table c(x int primary key)"); + rs = stat.executeQuery("select a.*, b.*, c.* from a " + + "left outer join (b " + + "inner join c " + + "on b.x = c.x) " + + "on a.x = b.x"); + // expected result: 0, null, null; 1, null, null + assertTrue(rs.next()); + assertEquals("0", rs.getString(1)); + assertEquals(null, rs.getString(2)); + assertEquals(null, rs.getString(3)); + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals(null, rs.getString(2)); + assertEquals(null, rs.getString(3)); + assertFalse(rs.next()); + rs = stat.executeQuery("select * from a " + + "left outer join b on a.x = b.x " + + "inner join c on b.x = c.x"); + // expected result: - + assertFalse(rs.next()); + rs = stat.executeQuery("select * from a " + + "left outer join b on a.x = b.x " + + "left outer join c on b.x = c.x"); + // expected result: 0 0 null; 1 null null + assertTrue(rs.next()); + assertEquals("0", rs.getString(1)); + assertEquals("0", rs.getString(2)); + assertEquals(null, rs.getString(3)); + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals(null, rs.getString(2)); + assertEquals(null, rs.getString(3)); + assertFalse(rs.next()); + + rs = stat.executeQuery("select * from a " + + "left outer join (b " + + "inner join c on b.x = c.x) on a.x = b.x"); + // expected result: 0 null null; 1 null null + assertTrue(rs.next()); + assertEquals("0", rs.getString(1)); + assertEquals(null, rs.getString(2)); + assertEquals(null, rs.getString(3)); + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals(null, rs.getString(2)); + assertEquals(null, rs.getString(3)); + assertFalse(rs.next()); + rs = stat.executeQuery("explain select * from a " + + "left outer join (b " + + "inner join c on c.x = 1) on a.x = b.x"); + assertTrue(rs.next()); + sql = cleanRemarks(rs.getString(1)); + assertEquals("SELECT \"PUBLIC\".\"A\".\"X\", \"PUBLIC\".\"B\".\"X\", \"PUBLIC\".\"C\".\"X\" " + + "FROM \"PUBLIC\".\"A\" " + + "LEFT OUTER JOIN ( \"PUBLIC\".\"B\" " + + "INNER JOIN \"PUBLIC\".\"C\" ON \"C\".\"X\" = 1 ) ON \"A\".\"X\" = \"B\".\"X\"", sql); + stat.execute("drop table a, b, c"); + + stat.execute("create table test(id int primary key)"); + stat.execute("insert into test values(0), (1), (2)"); + rs = stat.executeQuery("select * from test a " + + "left outer join (test b " + + "inner join test c on b.id = c.id - 2) on a.id = b.id + 1"); + // drop table test; + // create table test(id int primary key); + // insert into test values(0), (1), (2); + // select * from test a left outer join + // (test b inner join test c on b.id = c.id - 2) on a.id = b.id + 1; + // expected result: 0 null null; 1 0 2; 2 null null + assertTrue(rs.next()); + assertEquals("0", rs.getString(1)); + assertEquals(null, rs.getString(2)); + assertEquals(null, rs.getString(3)); + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals("0", rs.getString(2)); + assertEquals("2", rs.getString(3)); + assertTrue(rs.next()); + assertEquals("2", rs.getString(1)); + assertEquals(null, rs.getString(2)); + assertEquals(null, rs.getString(3)); + assertFalse(rs.next()); + stat.execute("drop table test"); + + stat.execute("create table a(pk int, val varchar(255))"); + stat.execute("create table b(pk int, val varchar(255))"); + stat.execute("create table base(pk int, deleted int)"); + stat.execute("insert into base values(1, 0)"); + stat.execute("insert into base values(2, 1)"); + stat.execute("insert into base values(3, 0)"); + stat.execute("insert into a values(1, 'a')"); + stat.execute("insert into b values(2, 'a')"); + stat.execute("insert into b values(3, 'a')"); + rs = stat.executeQuery( + "explain select a.pk, a_base.pk, b.pk, b_base.pk " + + "from a " + + "inner join base a_base on a.pk = a_base.pk " + + "left outer join (b inner join base b_base " + + "on b.pk = b_base.pk and b_base.deleted = 0) on 1=1"); + assertTrue(rs.next()); + sql = cleanRemarks(rs.getString(1)); + assertEquals("SELECT \"A\".\"PK\", \"A_BASE\".\"PK\", \"B\".\"PK\", \"B_BASE\".\"PK\" " + + "FROM \"PUBLIC\".\"BASE\" \"A_BASE\" " + + "LEFT OUTER JOIN ( \"PUBLIC\".\"B\" " + + "INNER JOIN \"PUBLIC\".\"BASE\" \"B_BASE\" " + + "ON (\"B_BASE\".\"DELETED\" = 0) AND (\"B\".\"PK\" = \"B_BASE\".\"PK\") ) " + + "ON 1=1 INNER JOIN \"PUBLIC\".\"A\" ON 1=1 " + + "WHERE \"A\".\"PK\" = \"A_BASE\".\"PK\"", sql); + rs = stat.executeQuery( + "select a.pk, a_base.pk, b.pk, b_base.pk from a " + + "inner join base a_base on a.pk = a_base.pk " + + "left outer join (b inner join base b_base " + + "on b.pk = b_base.pk and b_base.deleted = 0) on 1=1"); + // expected: 1 1 3 3 + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals("1", rs.getString(2)); + assertEquals("3", rs.getString(3)); + assertEquals("3", rs.getString(3)); + assertFalse(rs.next()); + stat.execute("drop table a, b, base"); + + // while (rs.next()) { + // for (int i = 0; i < rs.getMetaData().getColumnCount(); i++) { + // System.out.print(rs.getString(i + 1) + " "); + // } + // System.out.println(); + // } + + conn.close(); + deleteDb("nestedJoins"); + } + + private static String cleanRemarks(String sql) { + ScriptReader r = new ScriptReader(new StringReader(sql)); + r.setSkipRemarks(true); + sql = r.readStatement(); + sql = sql.replaceAll("\\n", " "); + while (sql.contains(" ")) { + sql = sql.replaceAll(" ", " "); + } + return sql; + } + + private void testCases2() throws Exception { + Connection conn = getConnection("nestedJoins"); + Statement stat = conn.createStatement(); + stat.execute("create table a(id int primary key)"); + stat.execute("create table b(id int primary key)"); + stat.execute("create table c(id int primary key)"); + stat.execute("insert into a(id) values(1)"); + stat.execute("insert into c(id) values(1)"); + stat.execute("insert into b(id) values(1)"); + stat.executeQuery("select 1 from a left outer join " + + "(a t0 join b t1 on 1 = 1) on t1.id = 1, c"); + conn.close(); + deleteDb("nestedJoins"); + } + +} diff --git a/h2/src/test/org/h2/test/synth/TestOuterJoins.java b/h2/src/test/org/h2/test/synth/TestOuterJoins.java new file mode 100644 index 0000000..41e97bb --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestOuterJoins.java @@ -0,0 +1,594 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.io.File; +import java.io.StringReader; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Random; + +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.ScriptReader; + +/** + * Tests nested joins and right outer joins. + */ +public class TestOuterJoins extends TestDb { + + private final ArrayList dbs = new ArrayList<>(); + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); + test.config.traceTest = true; + test.testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb("outerJoins"); + testCases(); + testRandom(); + deleteDb("outerJoins"); + } + + private void testRandom() throws Exception { + Connection conn = getConnection("outerJoins"); + dbs.add(conn.createStatement()); + + try { + Class.forName("org.postgresql.Driver"); + Connection c2 = DriverManager.getConnection( + "jdbc:postgresql:test?loggerLevel=OFF", "sa", "sa"); + dbs.add(c2.createStatement()); + } catch (Exception e) { + // database not installed - ok + } + deleteDerby(); + try { + Class.forName("org.apache.derby.iapi.jdbc.AutoloadedDriver"); + Connection c2 = DriverManager.getConnection( + "jdbc:derby:" + getBaseDir() + + "/derby/test;create=true", "sa", "sa"); + dbs.add(c2.createStatement()); + } catch (Throwable e) { + // database not installed - ok + } + String shortest = null; + Throwable shortestEx = null; + for (int i = 0; i < 4; i++) { + try { + executeAndLog("drop table t" + i); + } catch (Exception e) { + // ignore + } + } + executeAndLog("create table t0(x int primary key)"); + executeAndLog("create table t1(x int)"); + // for H2, this will ensure it's not using a clustered index + executeAndLog("create table t2(x real primary key)"); + executeAndLog("create table t3(x int)"); + executeAndLog("create index idx_t3_x on t3(x)"); + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 4; j++) { + if ((i & (1 << j)) != 0) { + executeAndLog("insert into t" + j + " values(" + i + ")"); + } + } + } + Random random = new Random(); + int len = getSize(500, 5000); + for (int i = 0; i < len; i++) { + StringBuilder buff = new StringBuilder(); + int t = 1 + random.nextInt(3); + buff.append("select "); + for (int j = 0; j < t; j++) { + if (j > 0) { + buff.append(", "); + } + buff.append("t" + j + ".x "); + } + buff.append("from "); + appendRandomJoin(random, buff, 0, t - 1); + appendRandomCondition(random, buff, t); + String sql = buff.toString(); + try { + execute(sql); + } catch (Throwable e) { + if (e instanceof SQLException) { + trace(sql); + fail(sql); + // SQLException se = (SQLException) e; + // System.out.println(se); + } + if (shortest == null || sql.length() < shortest.length()) { + shortest = sql; + shortestEx = e; + } + } + } + if (shortest != null) { + shortestEx.printStackTrace(); + fail(shortest + " " + shortestEx); + } + for (int i = 0; i < 4; i++) { + try { + execute("drop table t" + i); + } catch (Exception e) { + // ignore + } + } + for (Statement s : dbs) { + s.getConnection().close(); + } + deleteDerby(); + deleteDb("outerJoins"); + } + + private void deleteDerby() { + try { + new File("derby.log").delete(); + try { + DriverManager.getConnection("jdbc:derby:" + + getBaseDir() + "/derby/test;shutdown=true", "sa", "sa"); + } catch (Exception e) { + // ignore + } + FileUtils.deleteRecursive(getBaseDir() + "/derby", false); + } catch (Exception e) { + e.printStackTrace(); + // database not installed - ok + } + } + + private void appendRandomJoin(Random random, StringBuilder buff, int min, + int max) { + if (min == max) { + buff.append("t" + min); + return; + } + buff.append("("); + int m = min + random.nextInt(max - min); + int left = min + (m == min ? 0 : random.nextInt(m - min)); + appendRandomJoin(random, buff, min, m); + switch (random.nextInt(3)) { + case 0: + buff.append(" inner join "); + break; + case 1: + buff.append(" left outer join "); + break; + case 2: + buff.append(" right outer join "); + break; + } + m++; + int right = m + (m == max ? 0 : random.nextInt(max - m)); + appendRandomJoin(random, buff, m, max); + buff.append(" on t" + left + ".x = t" + right + ".x "); + buff.append(")"); + } + + private static void appendRandomCondition(Random random, + StringBuilder buff, int max) { + if (max > 0 && random.nextInt(4) == 0) { + return; + } + buff.append(" where "); + int count = 1 + random.nextInt(3); + for (int i = 0; i < count; i++) { + if (i > 0) { + buff.append(random.nextBoolean() ? " and " : " or "); + } + buff.append("t" + random.nextInt(max) + ".x"); + switch (random.nextInt(8)) { + case 0: + buff.append("="); + appendRandomValueOrColumn(random, buff, max); + break; + case 1: + buff.append(">="); + appendRandomValueOrColumn(random, buff, max); + break; + case 2: + buff.append("<="); + appendRandomValueOrColumn(random, buff, max); + break; + case 3: + buff.append("<"); + appendRandomValueOrColumn(random, buff, max); + break; + case 4: + buff.append(">"); + appendRandomValueOrColumn(random, buff, max); + break; + case 5: + buff.append("<>"); + appendRandomValueOrColumn(random, buff, max); + break; + case 6: + buff.append(" is not null"); + break; + case 7: + buff.append(" is null"); + break; + } + } + } + + private static void appendRandomValueOrColumn(Random random, + StringBuilder buff, int max) { + if (random.nextBoolean()) { + buff.append(random.nextInt(8) - 2); + } else { + buff.append("t" + random.nextInt(max) + ".x"); + } + } + + private void executeAndLog(String sql) throws SQLException { + trace(sql + ";"); + execute(sql); + } + + private void execute(String sql) throws SQLException { + String expected = null; + SQLException e = null; + for (Statement s : dbs) { + try { + boolean result = s.execute(sql); + if (result) { + String data = getResult(s.getResultSet()); + if (expected == null) { + expected = data; + } else { + assertEquals(sql, expected, data); + } + } + } catch (AssertionError e2) { + e = new SQLException(e2.getMessage()); + } catch (SQLException e2) { + // ignore now, throw at the end + e = e2; + } + } + if (e != null) { + throw e; + } + } + + private static String getResult(ResultSet rs) throws SQLException { + ArrayList list = new ArrayList<>(); + while (rs.next()) { + StringBuilder buff = new StringBuilder(); + for (int i = 0; i < rs.getMetaData().getColumnCount(); i++) { + if (i > 0) { + buff.append(" "); + } + int x = rs.getInt(i + 1); + buff.append(rs.wasNull() ? "null" : x); + } + list.add(buff.toString()); + } + Collections.sort(list); + return list.toString(); + } + + private void testCases() throws Exception { + + Connection conn = getConnection("outerJoins"); + Statement stat = conn.createStatement(); + ResultSet rs; + String sql; + + /* + create table test(id int primary key); + explain select * from test a left outer join (test c) on a.id = c.id; + -- expected: uses the primary key index + */ + stat.execute("create table test(id int primary key)"); + rs = stat.executeQuery("explain select * from test a " + + "left outer join (test c) on a.id = c.id"); + assertTrue(rs.next()); + sql = rs.getString(1); + assertContains(sql, "PRIMARY_KEY"); + stat.execute("drop table test"); + + /* + create table t1(a int, b int); + create table t2(a int, b int); + create table t3(a int, b int); + create table t4(a int, b int); + insert into t1 values(1,1), (2,2), (3,3); + insert into t2 values(1,1), (2,2); + insert into t3 values(1,1), (3,3); + insert into t4 values(1,1), (2,2), (3,3), (4,4); + select distinct t1.a, t2.a, t3.a from t1 + right outer join t3 on t1.b=t3.a right outer join t2 on t2.b=t1.a; + drop table t1, t2, t3, t4; + */ + stat.execute("create table t1(a int, b int)"); + stat.execute("create table t2(a int, b int)"); + stat.execute("create table t3(a int, b int)"); + stat.execute("create table t4(a int, b int)"); + stat.execute("insert into t1 values(1,1), (2,2), (3,3)"); + stat.execute("insert into t2 values(1,1), (2,2)"); + stat.execute("insert into t3 values(1,1), (3,3)"); + stat.execute("insert into t4 values(1,1), (2,2), (3,3), (4,4)"); + rs = stat.executeQuery("explain select distinct t1.a, t2.a, t3.a from t1 " + + "right outer join t3 on t1.b=t3.a right outer join t2 on t2.b=t1.a"); + assertTrue(rs.next()); + sql = cleanRemarks(rs.getString(1)); + assertEquals("SELECT DISTINCT \"T1\".\"A\", \"T2\".\"A\", \"T3\".\"A\" FROM \"PUBLIC\".\"T2\" " + + "LEFT OUTER JOIN ( \"PUBLIC\".\"T3\" " + + "LEFT OUTER JOIN \"PUBLIC\".\"T1\" ON \"T1\".\"B\" = \"T3\".\"A\" ) " + + "ON \"T2\".\"B\" = \"T1\".\"A\"", sql); + rs = stat.executeQuery("select distinct t1.a, t2.a, t3.a from t1 " + + "right outer join t3 on t1.b=t3.a right outer join t2 on t2.b=t1.a"); + // expected: + // null 2 null + // 1 1 1 + assertTrue(rs.next()); + assertEquals(null, rs.getString(1)); + assertEquals("2", rs.getString(2)); + assertEquals(null, rs.getString(3)); + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals("1", rs.getString(2)); + assertEquals("1", rs.getString(3)); + assertFalse(rs.next()); + stat.execute("drop table t1, t2, t3, t4"); + + /* + create table a(x int); + create table b(x int); + create table c(x int); + insert into a values(1); + insert into b values(1); + insert into c values(1), (2); + select a.x, b.x, c.x from a inner join b on a.x = b.x + right outer join c on c.x = a.x; + drop table a, b, c; + */ + stat.execute("create table a(x int)"); + stat.execute("create table b(x int)"); + stat.execute("create table c(x int)"); + stat.execute("insert into a values(1)"); + stat.execute("insert into b values(1)"); + stat.execute("insert into c values(1), (2)"); + rs = stat.executeQuery("explain select a.x, b.x, c.x from a " + + "inner join b on a.x = b.x right outer join c on c.x = a.x"); + assertTrue(rs.next()); + sql = cleanRemarks(rs.getString(1)); + assertEquals("SELECT \"A\".\"X\", \"B\".\"X\", \"C\".\"X\" FROM \"PUBLIC\".\"C\" LEFT OUTER JOIN " + + "( \"PUBLIC\".\"A\" INNER JOIN \"PUBLIC\".\"B\" " + + "ON \"A\".\"X\" = \"B\".\"X\" ) ON \"C\".\"X\" = \"A\".\"X\"", sql); + rs = stat.executeQuery("select a.x, b.x, c.x from a " + + "inner join b on a.x = b.x " + + "right outer join c on c.x = a.x"); + // expected result: 1 1 1; null null 2 + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals("1", rs.getString(2)); + assertEquals("1", rs.getString(3)); + assertTrue(rs.next()); + assertEquals(null, rs.getString(1)); + assertEquals(null, rs.getString(2)); + assertEquals("2", rs.getString(3)); + assertFalse(rs.next()); + stat.execute("drop table a, b, c"); + + /* + drop table a, b, c; + create table a(x int); + create table b(x int); + create table c(x int, y int); + insert into a values(1), (2); + insert into b values(3); + insert into c values(1, 3); + insert into c values(4, 5); + explain select * from a left outer join + (b left outer join c on b.x = c.y) on a.x = c.x; + select * from a left outer join + (b left outer join c on b.x = c.y) on a.x = c.x; + */ + stat.execute("create table a(x int)"); + stat.execute("create table b(x int)"); + stat.execute("create table c(x int, y int)"); + stat.execute("insert into a values(1), (2)"); + stat.execute("insert into b values(3)"); + stat.execute("insert into c values(1, 3)"); + stat.execute("insert into c values(4, 5)"); + rs = stat.executeQuery("explain select * from a " + + "left outer join (b " + + "left outer join c " + + "on b.x = c.y) " + + "on a.x = c.x"); + assertTrue(rs.next()); + sql = cleanRemarks(rs.getString(1)); + assertEquals("SELECT \"PUBLIC\".\"A\".\"X\", \"PUBLIC\".\"B\".\"X\", " + + "\"PUBLIC\".\"C\".\"X\", \"PUBLIC\".\"C\".\"Y\" FROM \"PUBLIC\".\"A\" " + + "LEFT OUTER JOIN ( \"PUBLIC\".\"B\" " + + "LEFT OUTER JOIN \"PUBLIC\".\"C\" " + + "ON \"B\".\"X\" = \"C\".\"Y\" ) " + + "ON \"A\".\"X\" = \"C\".\"X\"", sql); + rs = stat.executeQuery("select * from a " + + "left outer join (b " + + "left outer join c " + + "on b.x = c.y) " + + "on a.x = c.x"); + // expected result: 1 3 1 3; 2 null null null + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals("3", rs.getString(2)); + assertEquals("1", rs.getString(3)); + assertEquals("3", rs.getString(4)); + assertTrue(rs.next()); + assertEquals("2", rs.getString(1)); + assertEquals(null, rs.getString(2)); + assertEquals(null, rs.getString(3)); + assertEquals(null, rs.getString(4)); + assertFalse(rs.next()); + stat.execute("drop table a, b, c"); + + stat.execute("create table a(x int primary key)"); + stat.execute("insert into a values(0), (1)"); + stat.execute("create table b(x int primary key)"); + stat.execute("insert into b values(0)"); + stat.execute("create table c(x int primary key)"); + rs = stat.executeQuery("select a.*, b.*, c.* from a " + + "left outer join (b " + + "inner join c " + + "on b.x = c.x) " + + "on a.x = b.x"); + // expected result: 0, null, null; 1, null, null + assertTrue(rs.next()); + assertEquals("0", rs.getString(1)); + assertEquals(null, rs.getString(2)); + assertEquals(null, rs.getString(3)); + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals(null, rs.getString(2)); + assertEquals(null, rs.getString(3)); + assertFalse(rs.next()); + rs = stat.executeQuery("select * from a " + + "left outer join b on a.x = b.x " + + "inner join c on b.x = c.x"); + // expected result: - + assertFalse(rs.next()); + rs = stat.executeQuery("select * from a " + + "left outer join b on a.x = b.x " + + "left outer join c on b.x = c.x"); + // expected result: 0 0 null; 1 null null + assertTrue(rs.next()); + assertEquals("0", rs.getString(1)); + assertEquals("0", rs.getString(2)); + assertEquals(null, rs.getString(3)); + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals(null, rs.getString(2)); + assertEquals(null, rs.getString(3)); + assertFalse(rs.next()); + + rs = stat.executeQuery("select * from a " + + "left outer join (b " + + "inner join c on b.x = c.x) on a.x = b.x"); + // expected result: 0 null null; 1 null null + assertTrue(rs.next()); + assertEquals("0", rs.getString(1)); + assertEquals(null, rs.getString(2)); + assertEquals(null, rs.getString(3)); + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals(null, rs.getString(2)); + assertEquals(null, rs.getString(3)); + assertFalse(rs.next()); + rs = stat.executeQuery("explain select * from a " + + "left outer join (b " + + "inner join c on c.x = 1) on a.x = b.x"); + assertTrue(rs.next()); + sql = cleanRemarks(rs.getString(1)); + assertEquals("SELECT \"PUBLIC\".\"A\".\"X\", \"PUBLIC\".\"B\".\"X\", \"PUBLIC\".\"C\".\"X\" " + + "FROM \"PUBLIC\".\"A\" " + + "LEFT OUTER JOIN ( \"PUBLIC\".\"B\" " + + "INNER JOIN \"PUBLIC\".\"C\" ON \"C\".\"X\" = 1 ) ON \"A\".\"X\" = \"B\".\"X\"", sql); + stat.execute("drop table a, b, c"); + + stat.execute("create table test(id int primary key)"); + stat.execute("insert into test values(0), (1), (2)"); + rs = stat.executeQuery("select * from test a " + + "left outer join (test b " + + "inner join test c on b.id = c.id - 2) on a.id = b.id + 1"); + // drop table test; + // create table test(id int primary key); + // insert into test values(0), (1), (2); + // select * from test a left outer join + // (test b inner join test c on b.id = c.id - 2) on a.id = b.id + 1; + // expected result: 0 null null; 1 0 2; 2 null null + assertTrue(rs.next()); + assertEquals("0", rs.getString(1)); + assertEquals(null, rs.getString(2)); + assertEquals(null, rs.getString(3)); + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals("0", rs.getString(2)); + assertEquals("2", rs.getString(3)); + assertTrue(rs.next()); + assertEquals("2", rs.getString(1)); + assertEquals(null, rs.getString(2)); + assertEquals(null, rs.getString(3)); + assertFalse(rs.next()); + stat.execute("drop table test"); + + stat.execute("create table a(pk int, val varchar(255))"); + stat.execute("create table b(pk int, val varchar(255))"); + stat.execute("create table base(pk int, deleted int)"); + stat.execute("insert into base values(1, 0)"); + stat.execute("insert into base values(2, 1)"); + stat.execute("insert into base values(3, 0)"); + stat.execute("insert into a values(1, 'a')"); + stat.execute("insert into b values(2, 'a')"); + stat.execute("insert into b values(3, 'a')"); + rs = stat.executeQuery("explain select a.pk, a_base.pk, b.pk, b_base.pk " + + "from a " + + "inner join base a_base on a.pk = a_base.pk " + + "left outer join (b inner join base b_base " + + "on b.pk = b_base.pk and b_base.deleted = 0) on 1=1"); + assertTrue(rs.next()); + sql = cleanRemarks(rs.getString(1)); + assertEquals("SELECT \"A\".\"PK\", \"A_BASE\".\"PK\", \"B\".\"PK\", \"B_BASE\".\"PK\" " + + "FROM \"PUBLIC\".\"BASE\" \"A_BASE\" " + + "LEFT OUTER JOIN ( \"PUBLIC\".\"B\" " + + "INNER JOIN \"PUBLIC\".\"BASE\" \"B_BASE\" " + + "ON (\"B_BASE\".\"DELETED\" = 0) AND (\"B\".\"PK\" = \"B_BASE\".\"PK\") ) " + + "ON 1=1 INNER JOIN \"PUBLIC\".\"A\" ON 1=1 WHERE \"A\".\"PK\" = \"A_BASE\".\"PK\"", sql); + rs = stat.executeQuery("select a.pk, a_base.pk, b.pk, b_base.pk from a " + + "inner join base a_base on a.pk = a_base.pk " + + "left outer join (b inner join base b_base " + + "on b.pk = b_base.pk and b_base.deleted = 0) on 1=1"); + // expected: 1 1 3 3 + assertTrue(rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals("1", rs.getString(2)); + assertEquals("3", rs.getString(3)); + assertEquals("3", rs.getString(3)); + assertFalse(rs.next()); + stat.execute("drop table a, b, base"); + + // while (rs.next()) { + // for (int i = 0; i < rs.getMetaData().getColumnCount(); i++) { + // System.out.print(rs.getString(i + 1) + " "); + // } + // System.out.println(); + // } + + conn.close(); + deleteDb("outerJoins"); + } + + private static String cleanRemarks(String sql) { + ScriptReader r = new ScriptReader(new StringReader(sql)); + r.setSkipRemarks(true); + sql = r.readStatement(); + sql = sql.replaceAll("\\n", " "); + while (sql.contains(" ")) { + sql = sql.replaceAll(" ", " "); + } + return sql; + } + +} diff --git a/h2/src/test/org/h2/test/synth/TestPowerOffFs.java b/h2/src/test/org/h2/test/synth/TestPowerOffFs.java new file mode 100644 index 0000000..443b784 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestPowerOffFs.java @@ -0,0 +1,102 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import org.h2.api.ErrorCode; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.test.utils.FilePathDebug; + +/** + * Tests that use the debug file system to simulate power failure. + */ +public class TestPowerOffFs extends TestDb { + + private FilePathDebug fs; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + fs = FilePathDebug.register(); + test(Integer.MAX_VALUE); + System.out.println(Integer.MAX_VALUE - fs.getPowerOffCount()); + System.out.println("done"); + for (int i = 0;; i++) { + boolean end = test(i); + if (end) { + break; + } + } + deleteDb("memFS:", null); + } + + private boolean test(int x) throws SQLException { + deleteDb("memFS:", null); + fs.setPowerOffCount(x); + String url = "jdbc:h2:debug:memFS:powerOffFs;" + + "FILE_LOCK=NO;TRACE_LEVEL_FILE=0;" + + "WRITE_DELAY=0;CACHE_SIZE=4096"; + Connection conn = null; + Statement stat = null; + try { + conn = DriverManager.getConnection(url); + stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name varchar)"); + stat.execute("insert into test values(1, 'Hello')"); + stat.execute("create index idx_name on test(name)"); + stat.execute("insert into test values(2, 'World')"); + stat.execute("update test set name='Hallo' where id=1"); + stat.execute("delete from test where name=2"); + stat.execute("insert into test values(3, space(10000))"); + stat.execute("update test set name='Hallo' where id=3"); + stat.execute("drop table test"); + conn.close(); + conn = null; + return fs.getPowerOffCount() > 0; + } catch (SQLException e) { + if (e.getErrorCode() == ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1) { + throw e; + } + // ignore + } finally { + if (conn != null) { + try { + if (stat != null) { + stat.execute("shutdown immediately"); + } + } catch (Exception e2) { + // ignore + } + try { + conn.close(); + } catch (Exception e2) { + // ignore + } + } + } + fs.setPowerOffCount(0); + conn = DriverManager.getConnection(url); + stat = conn.createStatement(); + stat.execute("script to 'memFS:test.sql'"); + conn.close(); + FileUtils.delete("memFS:test.sql"); + return false; + } + +} diff --git a/h2/src/test/org/h2/test/synth/TestPowerOffFs2.java b/h2/src/test/org/h2/test/synth/TestPowerOffFs2.java new file mode 100644 index 0000000..1799b86 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestPowerOffFs2.java @@ -0,0 +1,232 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Random; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.test.utils.FilePathDebug; + +/** + * Tests that use the debug file system to simulate power failure. + * This test runs many random operations and stops after some time. + */ +public class TestPowerOffFs2 extends TestDb { + + private static final String USER = "sa"; + private static final String PASSWORD = "sa"; + + private FilePathDebug fs; + + private String url; + private final ArrayList connections = new ArrayList<>(); + private final ArrayList tables = new ArrayList<>(); + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + fs = FilePathDebug.register(); + url = "jdbc:h2:debug:memFS:powerOffFs;FILE_LOCK=NO;" + + "TRACE_LEVEL_FILE=0;WRITE_DELAY=0;CACHE_SIZE=32"; + for (int i = 0;; i++) { + test(i); + } + } + + private void test(int x) throws SQLException { + System.out.println("x:" + x); + deleteDb("memFS:", null); + try { + testCrash(x); + fail(); + } catch (SQLException e) { + if (e.toString().indexOf("Simulated") < 0) { + throw e; + } + for (Connection c : connections) { + try { + Statement stat = c.createStatement(); + stat.execute("shutdown immediately"); + } catch (Exception e2) { + // ignore + } + try { + c.close(); + } catch (Exception e2) { + // ignore + } + } + } + fs.setPowerOffCount(0); + Connection conn; + conn = openConnection(); + testConsistent(conn); + conn.close(); + } + + private void testCrash(int x) throws SQLException { + connections.clear(); + tables.clear(); + Random random = new Random(x); + for (int i = 0;; i++) { + if (i > 200 && connections.size() > 1 && tables.size() > 1) { + fs.setPowerOffCount(100); + } + if (connections.size() < 1) { + openConnection(); + } + if (tables.size() < 1) { + createTable(random); + } + int p = random.nextInt(100); + if ((p -= 2) <= 0) { + // 2%: open new connection + if (connections.size() < 5) { + openConnection(); + } + } else if ((p -= 1) <= 0) { + // 1%: close connection + if (connections.size() > 1) { + Connection conn = connections.remove( + random.nextInt(connections.size())); + conn.close(); + } + } else if ((p -= 10) <= 0) { + // 10% create table + createTable(random); + } else if ((p -= 20) <= 0) { + // 20% large insert, delete, or update + if (tables.size() > 0) { + Connection conn = connections.get( + random.nextInt(connections.size())); + Statement stat = conn.createStatement(); + String table = tables.get(random.nextInt(tables.size())); + if (random.nextBoolean()) { + // 10% insert + stat.execute("INSERT INTO " + table + + "(NAME) SELECT 'Hello ' || X FROM SYSTEM_RANGE(0, 20)"); + } else if (random.nextBoolean()) { + // 5% update + stat.execute("UPDATE " + table + " SET NAME='Hallo Welt'"); + } else { + // 5% delete + stat.execute("DELETE FROM " + table); + } + } + } else if ((p -= 5) < 0) { + // 5% truncate or drop table + if (tables.size() > 0) { + Connection conn = connections.get( + random.nextInt(connections.size())); + Statement stat = conn.createStatement(); + String table = tables.get(random.nextInt(tables.size())); + if (random.nextBoolean()) { + stat.execute("TRUNCATE TABLE " + table); + } else { + stat.execute("DROP TABLE " + table); + tables.remove(table); + } + } + } else if ((p -= 30) <= 0) { + // 30% insert + if (tables.size() > 0) { + Connection conn = connections.get( + random.nextInt(connections.size())); + Statement stat = conn.createStatement(); + String table = tables.get(random.nextInt(tables.size())); + int spaces = random.nextInt(4) * 30; + if (random.nextInt(15) == 2) { + spaces *= 100; + } + int name = random.nextInt(20); + stat.execute("INSERT INTO " + table + + "(NAME) VALUES('" + name + "' || space( " + spaces + " ))"); + } + } else { + // 32% delete + if (tables.size() > 0) { + Connection conn = connections.get(random.nextInt(connections.size())); + Statement stat = conn.createStatement(); + String table = tables.get(random.nextInt(tables.size())); + stat.execute("DELETE FROM " + table + + " WHERE ID = SELECT MIN(ID) FROM " + table); + } + } + } + } + + private Connection openConnection() throws SQLException { + Connection conn = DriverManager.getConnection(url, USER, PASSWORD); + connections.add(conn); + return conn; + } + + private void createTable(Random random) throws SQLException { + Connection conn = connections.get(random.nextInt(connections.size())); + Statement stat = conn.createStatement(); + String table = "TEST" + random.nextInt(10); + try { + stat.execute("CREATE TABLE " + table + "(ID IDENTITY, NAME VARCHAR)"); + if (random.nextBoolean()) { + stat.execute("CREATE INDEX IDX_" + table + " ON " + table + "(NAME)"); + } + tables.add(table); + } catch (SQLException e) { + if (e.getErrorCode() == ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1) { + if (!tables.contains(table)) { + tables.add(table); + } + // ok + } else { + throw e; + } + } + } + + private static void testConsistent(Connection conn) throws SQLException { + for (int i = 0; i < 20; i++) { + Statement stat = conn.createStatement(); + try { + ResultSet rs = stat.executeQuery("SELECT * FROM TEST" + i); + while (rs.next()) { + rs.getLong("ID"); + rs.getString("NAME"); + } + rs = stat.executeQuery("SELECT * FROM TEST" + i + " ORDER BY ID"); + while (rs.next()) { + rs.getLong("ID"); + rs.getString("NAME"); + } + } catch (SQLException e) { + if (e.getErrorCode() == ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1 || + e.getErrorCode() == ErrorCode.TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1 || + e.getErrorCode() == ErrorCode.TABLE_OR_VIEW_NOT_FOUND_WITH_CANDIDATES_2 + ) { + // ok + } else { + throw e; + } + } + } + } + +} diff --git a/h2/src/test/org/h2/test/synth/TestRandomCompare.java b/h2/src/test/org/h2/test/synth/TestRandomCompare.java new file mode 100644 index 0000000..7cf7657 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestRandomCompare.java @@ -0,0 +1,308 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Random; + +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests random compare operations. + */ +public class TestRandomCompare extends TestDb { + + private final ArrayList dbs = new ArrayList<>(); + private int aliasId; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); + test.config.traceTest = true; + test.testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb("randomCompare"); + testCases(); + testRandom(); + deleteDb("randomCompare"); + } + + private void testRandom() throws Exception { + Connection conn = getConnection("randomCompare"); + dbs.add(conn.createStatement()); + + try { + Class.forName("org.postgresql.Driver"); + Connection c2 = DriverManager.getConnection( + "jdbc:postgresql:test?loggerLevel=OFF", "sa", "sa"); + dbs.add(c2.createStatement()); + } catch (Exception e) { + // database not installed - ok + } + + String shortest = null; + Throwable shortestEx = null; + /* + drop table test; + create table test(x0 int, x1 int); + create index idx_test_x0 on test(x0); + insert into test values(null, null); + insert into test values(null, 1); + insert into test values(null, 2); + insert into test values(1, null); + insert into test values(1, 1); + insert into test values(1, 2); + insert into test values(2, null); + insert into test values(2, 1); + insert into test values(2, 2); + */ + try { + execute("drop table test"); + } catch (Exception e) { + // ignore + } + try { + execute("drop table test cascade"); + } catch (Exception e) { + // ignore + } + String sql = "create table test(x0 int, x1 int)"; + trace(sql + ";"); + execute(sql); + sql = "create index idx_test_x0 on test(x0)"; + trace(sql + ";"); + execute(sql); + for (int x0 = 0; x0 < 3; x0++) { + for (int x1 = 0; x1 < 3; x1++) { + sql = "insert into test values(" + (x0 == 0 ? "null" : x0) + + ", " + (x1 == 0 ? "null" : x1) + ")"; + trace(sql + ";"); + execute(sql); + } + } + Random random = new Random(1); + for (int i = 0; i < 1000; i++) { + StringBuilder buff = new StringBuilder(); + appendRandomCompare(random, buff); + sql = buff.toString(); + try { + execute(sql); + } catch (Throwable e) { + if (e instanceof SQLException) { + trace(sql); + fail(sql); + // SQLException se = (SQLException) e; + // System.out.println(se); + // System.out.println(" " + sql); + } + if (shortest == null || sql.length() < shortest.length()) { + shortest = sql; + shortestEx = e; + } + } + } + if (shortest != null) { + shortestEx.printStackTrace(); + fail(shortest + " " + shortestEx); + } + for (int i = 0; i < 10; i++) { + try { + execute("drop table t" + i); + } catch (Exception e) { + // ignore + } + } + for (Statement s : dbs) { + s.getConnection().close(); + } + deleteDb("randomCompare"); + } + + private void appendRandomCompare(Random random, StringBuilder buff) { + buff.append("select * from "); + int alias = aliasId++; + if (random.nextBoolean()) { + buff.append("("); + appendRandomCompare(random, buff); + buff.append(")"); + } else { + buff.append("test"); + } + buff.append(" as t").append(alias); + if (random.nextInt(10) == 0) { + return; + } + buff.append(" where "); + int count = 1 + random.nextInt(3); + for (int i = 0; i < count; i++) { + if (i > 0) { + buff.append(random.nextBoolean() ? " or " : " and "); + } + if (random.nextInt(10) == 0) { + buff.append("not "); + } + appendRandomValue(random, buff); + switch (random.nextInt(8)) { + case 0: + buff.append("="); + appendRandomValue(random, buff); + break; + case 1: + buff.append("<"); + appendRandomValue(random, buff); + break; + case 2: + buff.append(">"); + appendRandomValue(random, buff); + break; + case 3: + buff.append("<="); + appendRandomValue(random, buff); + break; + case 4: + buff.append(">="); + appendRandomValue(random, buff); + break; + case 5: + buff.append("<>"); + appendRandomValue(random, buff); + break; + case 6: + buff.append(" is distinct from "); + appendRandomValue(random, buff); + break; + case 7: + buff.append(" is not distinct from "); + appendRandomValue(random, buff); + break; + } + } + } + + private static void appendRandomValue(Random random, StringBuilder buff) { + switch (random.nextInt(7)) { + case 0: + buff.append("null"); + break; + case 1: + buff.append(1); + break; + case 2: + buff.append(2); + break; + case 3: + buff.append(3); + break; + case 4: + buff.append(-1); + break; + case 5: + buff.append("x0"); + break; + case 6: + buff.append("x1"); + break; + } + } + + private void execute(String sql) throws SQLException { + String expected = null; + SQLException e = null; + for (Statement s : dbs) { + try { + boolean result = s.execute(sql); + if (result) { + String data = getResult(s.getResultSet()); + if (expected == null) { + expected = data; + } else { + assertEquals(sql, expected, data); + } + } + } catch (SQLException e2) { + // ignore now, throw at the end + e = e2; + } + } + if (e != null) { + throw e; + } + } + + private static String getResult(ResultSet rs) throws SQLException { + ArrayList list = new ArrayList<>(); + while (rs.next()) { + StringBuilder buff = new StringBuilder(); + for (int i = 0; i < rs.getMetaData().getColumnCount(); i++) { + if (i > 0) { + buff.append(" "); + } + buff.append(rs.getString(i + 1)); + } + list.add(buff.toString()); + } + Collections.sort(list); + return list.toString(); + } + + private void testCases() throws Exception { + + Connection conn = getConnection("randomCompare"); + Statement stat = conn.createStatement(); + ResultSet rs; + + /* + create table test(x int); + insert into test values(null); + select * from (select x from test + union all select x from test) where x is null; + select * from (select x from test) where x is null; + */ + stat.execute("create table test(x int)"); + stat.execute("insert into test values(null)"); + rs = stat.executeQuery("select * from (select x from test " + + "union all select x from test) where x is null"); + assertTrue(rs.next()); + rs = stat.executeQuery( + "select * from (select x from test) where x is null"); + assertTrue(rs.next()); + rs = stat.executeQuery("select * from (select x from test " + + "union all select x from test) where x is null"); + assertTrue(rs.next()); + assertTrue(rs.next()); + + Connection conn2 = DriverManager.getConnection("jdbc:h2:mem:temp"); + conn2.createStatement().execute("create table test(x int) as select null"); + stat.execute("drop table test"); + stat.execute("create linked table test" + + "(null, 'jdbc:h2:mem:temp', null, null, 'TEST')"); + rs = stat.executeQuery("select * from (select x from test) where x is null"); + assertTrue(rs.next()); + rs = stat.executeQuery("select * from (select x from test " + + "union all select x from test) where x is null"); + assertTrue(rs.next()); + assertTrue(rs.next()); + conn2.close(); + + conn.close(); + deleteDb("randomCompare"); + } + +} diff --git a/h2/src/test/org/h2/test/synth/TestRandomSQL.java b/h2/src/test/org/h2/test/synth/TestRandomSQL.java new file mode 100644 index 0000000..9223e60 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestRandomSQL.java @@ -0,0 +1,122 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import org.h2.engine.SysProperties; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.MathUtils; + +/** + * This test executes random SQL statements generated using the BNF tool. + */ +public class TestRandomSQL extends TestDb { + + private int success, total; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + if (config.networked) { + return false; + } + return true; + } + + @Override + public void test() throws Exception { + int len = getSize(2, 6); + for (int a = 0; a < len; a++) { + int s = MathUtils.randomInt(Integer.MAX_VALUE); + testCase(s); + } + } + + private void testWithSeed(int seed) throws Exception { + Connection conn = null; + try { + conn = getConnection(getDatabaseName(seed)); + } catch (SQLException e) { + if (e.getSQLState().equals("HY000")) { + TestBase.logError("new TestRandomSQL().init(test).testCase(" + seed + "); " + + "// FAIL: " + e.toString() + " sql: " + "connect", e); + } + conn = getConnection(getDatabaseName(seed)); + } + Statement stat = conn.createStatement(); + + BnfRandom bnfRandom = new BnfRandom(); + bnfRandom.setSeed(seed); + for (int i = 0; i < bnfRandom.getStatementCount(); i++) { + String sql = bnfRandom.getRandomSQL(); + if (sql != null) { + try { + Thread.yield(); + total++; + if (total % 100 == 0) { + printTime("total: " + total + " success: " + + (100 * success / total) + "%"); + } + stat.execute(sql); + success++; + } catch (SQLException e) { + if (e.getSQLState().equals("HY000")) { + TestBase.logError( + "new TestRandomSQL().init(test).testCase(" + + seed + "); " + "// FAIL: " + + e.toString() + " sql: " + sql, e); + } + } + } + } + try { + conn.close(); + conn = getConnection(getDatabaseName(seed)); + conn.createStatement().execute("shutdown immediately"); + conn.close(); + } catch (SQLException e) { + if (e.getSQLState().equals("HY000")) { + TestBase.logError("new TestRandomSQL().init(test).testCase(" + seed + "); " + + "// FAIL: " + e.toString() + " sql: " + "conn.close", e); + } + } + } + + private void testCase(int seed) throws Exception { + String old = SysProperties.getScriptDirectory(); + try { + System.setProperty(SysProperties.H2_SCRIPT_DIRECTORY, + getBaseDir() + "/" + getTestName()); + printTime("seed: " + seed); + deleteDb(seed); + testWithSeed(seed); + } finally { + System.setProperty(SysProperties.H2_SCRIPT_DIRECTORY, old); + } + deleteDb(seed); + } + + private String getDatabaseName(int seed) { + return getTestName() + "/db" + seed; + } + + private void deleteDb(int seed) { + FileUtils.delete(getDatabaseName(seed)); + } + +} diff --git a/h2/src/test/org/h2/test/synth/TestReleaseSelectLock.java b/h2/src/test/org/h2/test/synth/TestReleaseSelectLock.java new file mode 100644 index 0000000..42907fe --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestReleaseSelectLock.java @@ -0,0 +1,79 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.concurrent.CountDownLatch; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests lock releasing for concurrent select statements + */ +public class TestReleaseSelectLock extends TestDb { + + private static final String TEST_DB_NAME = "releaseSelectLock"; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); + test.testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb(TEST_DB_NAME); + + Connection conn = getConnection(TEST_DB_NAME); + final Statement statement = conn.createStatement(); + statement.execute("create table test(id int primary key)"); + + runConcurrentSelects(); + + // check that all locks have been released by dropping the test table + statement.execute("drop table test"); + + statement.close(); + conn.close(); + deleteDb(TEST_DB_NAME); + } + + private void runConcurrentSelects() throws InterruptedException { + int tryCount = 500; + int threadsCount = getSize(2, 4); + for (int tryNumber = 0; tryNumber < tryCount; tryNumber++) { + CountDownLatch allFinished = new CountDownLatch(threadsCount); + + for (int i = 0; i < threadsCount; i++) { + new Thread(() -> { + try { + Connection conn = getConnection(TEST_DB_NAME); + PreparedStatement stmt = conn.prepareStatement("select id from test"); + ResultSet rs = stmt.executeQuery(); + while (rs.next()) { + rs.getInt(1); + } + stmt.close(); + conn.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + allFinished.countDown(); + } + }).start(); + } + + allFinished.await(); + } + } +} diff --git a/h2/src/test/org/h2/test/synth/TestSimpleIndex.java b/h2/src/test/org/h2/test/synth/TestSimpleIndex.java new file mode 100644 index 0000000..4a0337d --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestSimpleIndex.java @@ -0,0 +1,177 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.test.synth.sql.RandomGen; + +/** + * A test that runs random operations against a table to test the various index + * implementations. + */ +public class TestSimpleIndex extends TestDb { + + private Connection conn; + private Statement stat; + private RandomGen random; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + deleteDb("simpleIndex"); + conn = getConnection("simpleIndex"); + random = new RandomGen(); + stat = conn.createStatement(); + for (int i = 0; i < 10000; i++) { + testIndex(i); + } + } + + private void testIndex(int seed) throws SQLException { + random.setSeed(seed); + String unique = random.nextBoolean() ? "UNIQUE " : ""; + int len = random.getInt(2) + 1; + StringBuilder buff = new StringBuilder(); + for (int i = 0; i < len; i++) { + if (i > 0) { + buff.append(", "); + } + buff.append((char) ('A' + random.getInt(3))); + } + String cols = buff.toString(); + execute("CREATE MEMORY TABLE TEST_M(A INT, B INT, C INT, DATA VARCHAR(255))"); + execute("CREATE CACHED TABLE TEST_D(A INT, B INT, C INT, DATA VARCHAR(255))"); + execute("CREATE MEMORY TABLE TEST_MI(A INT, B INT, C INT, DATA VARCHAR(255))"); + execute("CREATE CACHED TABLE TEST_DI(A INT, B INT, C INT, DATA VARCHAR(255))"); + execute("CREATE " + unique + "INDEX M ON TEST_MI(" + cols + ")"); + execute("CREATE " + unique + "INDEX D ON TEST_DI(" + cols + ")"); + for (int i = 0; i < 100; i++) { + println("i=" + i); + testRows(); + } + execute("DROP INDEX M"); + execute("DROP INDEX D"); + execute("DROP TABLE TEST_M"); + execute("DROP TABLE TEST_D"); + execute("DROP TABLE TEST_MI"); + execute("DROP TABLE TEST_DI"); + } + + private void testRows() throws SQLException { + String a = randomValue(), b = randomValue(), c = randomValue(); + String data = a + "/" + b + "/" + c; + String sql = "VALUES(" + a + ", " + b + ", " + c + ", '" + data + "')"; + boolean em, ed; + // if(id==73) { + // print("halt"); + // } + try { + execute("INSERT INTO TEST_MI " + sql); + em = false; + } catch (SQLException e) { + em = true; + } + try { + execute("INSERT INTO TEST_DI " + sql); + ed = false; + } catch (SQLException e) { + ed = true; + } + if (em != ed) { + fail("different result: "); + } + if (!em) { + execute("INSERT INTO TEST_M " + sql); + execute("INSERT INTO TEST_D " + sql); + } + StringBuilder buff = new StringBuilder("WHERE 1=1"); + int len = random.getLog(10); + for (int i = 0; i < len; i++) { + buff.append(" AND "); + buff.append('A' + random.getInt(3)); + switch (random.getInt(10)) { + case 0: + buff.append("<"); + buff.append(random.getInt(100) - 50); + break; + case 1: + buff.append("<="); + buff.append(random.getInt(100) - 50); + break; + case 2: + buff.append(">"); + buff.append(random.getInt(100) - 50); + break; + case 3: + buff.append(">="); + buff.append(random.getInt(100) - 50); + break; + case 4: + buff.append("<>"); + buff.append(random.getInt(100) - 50); + break; + case 5: + buff.append(" IS NULL"); + break; + case 6: + buff.append(" IS NOT NULL"); + break; + default: + buff.append("="); + buff.append(random.getInt(100) - 50); + } + } + String where = buff.toString(); + String r1 = getResult("SELECT DATA FROM TEST_M " + where + " ORDER BY DATA"); + String r2 = getResult("SELECT DATA FROM TEST_D " + where + " ORDER BY DATA"); + String r3 = getResult("SELECT DATA FROM TEST_MI " + where + " ORDER BY DATA"); + String r4 = getResult("SELECT DATA FROM TEST_DI " + where + " ORDER BY DATA"); + assertEquals(r1, r2); + assertEquals(r1, r3); + assertEquals(r1, r4); + } + + private String getResult(String sql) throws SQLException { + ResultSet rs = stat.executeQuery(sql); + StringBuilder buff = new StringBuilder(); + while (rs.next()) { + buff.append(rs.getString(1)); + buff.append("; "); + } + rs.close(); + return buff.toString(); + } + + private String randomValue() { + return random.getInt(10) == 0 ? "NULL" : "" + (random.getInt(100) - 50); + } + + private void execute(String sql) throws SQLException { + try { + println(sql + ";"); + stat.execute(sql); + println("> update count: 1"); + } catch (SQLException e) { + println("> exception"); + throw e; + } + } + +} diff --git a/h2/src/test/org/h2/test/synth/TestThreads.java b/h2/src/test/org/h2/test/synth/TestThreads.java new file mode 100644 index 0000000..f88049e --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestThreads.java @@ -0,0 +1,175 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Random; + +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * This test starts multiple threads and executes random operations in each + * thread. + */ +public class TestThreads extends TestDb implements Runnable { + + private static final int INSERT = 0, UPDATE = 1, DELETE = 2; + private static final int SELECT_ONE = 3, SELECT_ALL = 4; + private static final int CHECKPOINT = 5, RECONNECT = 6; + private static final int OP_TYPES = RECONNECT + 1; + + private int maxId = 1; + + private volatile boolean stop; + private TestThreads master; + private int type; + private String table; + private final Random random = new Random(); + + public TestThreads() { + // nothing to do + } + + TestThreads(TestThreads master, int type, String table) { + this.master = master; + this.type = type; + this.table = table; + } + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb("threads"); + Connection conn = getConnection("threads;MAX_LOG_SIZE=1"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST_A(ID INT PRIMARY KEY, NAME VARCHAR)"); + stat.execute("CREATE TABLE TEST_B(ID INT PRIMARY KEY, NAME VARCHAR)"); + stat.execute("CREATE TABLE TEST_C(ID INT PRIMARY KEY, NAME VARCHAR)"); + int len = 1000; + insertRows(conn, "TEST_A", len); + insertRows(conn, "TEST_B", len); + insertRows(conn, "TEST_C", len); + maxId = len; + int threadCount = 4; + Thread[] threads = new Thread[threadCount]; + for (int i = 0; i < threadCount; i++) { + String t = random.nextBoolean() ? null : getRandomTable(); + int op = random.nextInt(OP_TYPES); + op = i % 2 == 0 ? RECONNECT : CHECKPOINT; + threads[i] = new Thread(new TestThreads(this, op, t)); + } + for (int i = 0; i < threadCount; i++) { + threads[i].start(); + } + Thread.sleep(10000); + stop = true; + for (int i = 0; i < threadCount; i++) { + threads[i].join(); + } + conn.close(); + conn = getConnection("threads"); + checkTable(conn, "TEST_A"); + checkTable(conn, "TEST_B"); + checkTable(conn, "TEST_C"); + conn.close(); + } + + private static void insertRows(Connection conn, String tableName, int len) + throws SQLException { + PreparedStatement prep = conn.prepareStatement("INSERT INTO " + + tableName + " VALUES(?, 'Hi')"); + for (int i = 0; i < len; i++) { + prep.setInt(1, i); + prep.execute(); + } + } + + private static void checkTable(Connection conn, String tableName) + throws SQLException { + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("SELECT * FROM " + tableName + " ORDER BY ID"); + while (rs.next()) { + int id = rs.getInt(1); + String name = rs.getString(2); + System.out.println("id=" + id + " name=" + name); + } + } + + private int getMaxId() { + return maxId; + } + + private synchronized int incrementMaxId() { + return maxId++; + } + + private String getRandomTable() { + return "TEST_" + (char) ('A' + random.nextInt(3)); + } + + @Override + public void run() { + try { + String t = table == null ? getRandomTable() : table; + Connection conn = master.getConnection("threads"); + Statement stat = conn.createStatement(); + ResultSet rs; + int max = master.getMaxId(); + int rid = random.nextInt(max); + while (!master.stop) { + switch (type) { + case INSERT: + max = master.incrementMaxId(); + stat.execute("INSERT INTO " + t + "(ID, NAME) VALUES(" + max + ", 'Hello')"); + break; + case UPDATE: + stat.execute("UPDATE " + t + " SET NAME='World " + rid + "' WHERE ID=" + rid); + break; + case DELETE: + stat.execute("DELETE FROM " + t + " WHERE ID=" + rid); + break; + case SELECT_ALL: + rs = stat.executeQuery("SELECT * FROM " + t + " ORDER BY ID"); + while (rs.next()) { + // nothing + } + break; + case SELECT_ONE: + rs = stat.executeQuery("SELECT * FROM " + t + " WHERE ID=" + rid); + while (rs.next()) { + // nothing + } + break; + case CHECKPOINT: + stat.execute("CHECKPOINT"); + break; + case RECONNECT: + conn.close(); + conn = master.getConnection("threads"); + break; + default: + } + } + conn.close(); + } catch (Exception e) { + TestBase.logError("error", e); + } + } + +} diff --git a/h2/src/test/org/h2/test/synth/TestTimer.java b/h2/src/test/org/h2/test/synth/TestTimer.java new file mode 100644 index 0000000..aae2e40 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/TestTimer.java @@ -0,0 +1,145 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth; + +import java.io.File; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.Backup; +import org.h2.tools.DeleteDbFiles; + +/** + * A recovery test that checks the consistency of a database (if it exists), + * then deletes everything and runs in an endless loop executing random + * operations. This loop is usually stopped by switching off the computer. + */ +public class TestTimer extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + validateOld(); + DeleteDbFiles.execute(getBaseDir(), "timer", true); + loop(); + } + + private void loop() throws SQLException { + println("loop"); + Connection conn = getConnection("timer"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID IDENTITY, NAME VARCHAR)"); + Random random = new Random(); + int max = 0; + int count = 0; + long startTime = System.nanoTime(); + while (true) { + int action = random.nextInt(10); + int x = max == 0 ? 0 : random.nextInt(max); + switch (action) { + case 0: + case 1: + case 2: + stat.execute("INSERT INTO TEST VALUES(NULL, 'Hello')"); + ResultSet rs = stat.getGeneratedKeys(); + rs.next(); + int i = rs.getInt(1); + max = i; + count++; + break; + case 3: + case 4: + if (count == 0) { + break; + } + stat.execute("UPDATE TEST SET NAME=NAME||'+' WHERE ID=" + x); + break; + case 5: + case 6: + if (count == 0) { + break; + } + count -= stat.executeUpdate("DELETE FROM TEST WHERE ID=" + x); + break; + case 7: + rs = stat.executeQuery("SELECT COUNT(*) FROM TEST"); + rs.next(); + int c = rs.getInt(1); + assertEquals(count, c); + long time = System.nanoTime(); + if (time > startTime + TimeUnit.SECONDS.toNanos(5)) { + println("rows: " + count); + startTime = time; + } + break; + default: + } + } + } + + private void validateOld() { + println("validate"); + try { + Connection conn = getConnection("timer"); + // TODO validate transactions + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE IF NOT EXISTS TEST(ID IDENTITY, NAME VARCHAR)"); + ResultSet rs = stat.executeQuery("SELECT COUNT(*) FROM TEST"); + rs.next(); + int count = rs.getInt(1); + println("row count: " + count); + int real = 0; + rs = stat.executeQuery("SELECT * FROM TEST"); + while (rs.next()) { + real++; + } + if (real != count) { + println("real count: " + real); + throw new AssertionError("COUNT(*)=" + count + " SELECT=" + real); + } + rs = stat.executeQuery("SCRIPT"); + while (rs.next()) { + rs.getString(1); + } + conn.close(); + } catch (Throwable e) { + logError("validate", e); + backup(); + } + } + + private void backup() { + println("backup"); + for (int i = 0;; i++) { + String s = "timer." + i + ".zip"; + File f = new File(s); + if (f.exists()) { + continue; + } + try { + Backup.execute(s, getBaseDir(), "timer", true); + } catch (SQLException e) { + logError("backup", e); + } + break; + } + } + +} diff --git a/h2/src/test/org/h2/test/synth/package.html b/h2/src/test/org/h2/test/synth/package.html new file mode 100644 index 0000000..31abc88 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +Synthetic tests using random operations or statements. + +

    \ No newline at end of file diff --git a/h2/src/test/org/h2/test/synth/sql/Column.java b/h2/src/test/org/h2/test/synth/sql/Column.java new file mode 100644 index 0000000..e797507 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/sql/Column.java @@ -0,0 +1,224 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth.sql; + +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Types; + +/** + * A column of a table. + */ +class Column { + + private static final int[] TYPES = { Types.INTEGER, Types.VARCHAR, + Types.DECIMAL, Types.DATE, Types.TIME, Types.TIMESTAMP, + Types.BOOLEAN, Types.BINARY, Types.VARBINARY, Types.CLOB, + Types.BLOB, Types.DOUBLE, Types.BIGINT, Types.TIMESTAMP, Types.BIT, }; + + private TestSynth config; + private String name; + private int type; + private int precision; + private int scale; + private boolean isNullable; + private boolean isPrimaryKey; + // TODO test isAutoincrement; + + private Column(TestSynth config) { + this.config = config; + } + + Column(ResultSetMetaData meta, int index) throws SQLException { + name = meta.getColumnLabel(index); + type = meta.getColumnType(index); + switch (type) { + case Types.DECIMAL: + precision = meta.getPrecision(index); + scale = meta.getScale(index); + break; + case Types.BLOB: + case Types.BINARY: + case Types.VARBINARY: + case Types.CLOB: + case Types.LONGVARCHAR: + case Types.DATE: + case Types.TIME: + case Types.INTEGER: + case Types.VARCHAR: + case Types.CHAR: + case Types.BIGINT: + case Types.NUMERIC: + case Types.TIMESTAMP: + case Types.NULL: + case Types.LONGVARBINARY: + case Types.DOUBLE: + case Types.REAL: + case Types.OTHER: + case Types.BIT: + case Types.BOOLEAN: + break; + default: + throw new AssertionError("type=" + type); + } + } + + /** + * Check if this data type supports comparisons for this database. + * + * @param config the configuration + * @param type the SQL type + * @return true if the value can be used in conditions + */ + static boolean isConditionType(TestSynth config, int type) { + switch (config.getMode()) { + case TestSynth.H2: + case TestSynth.H2_MEM: + return true; + case TestSynth.MYSQL: + case TestSynth.HSQLDB: + case TestSynth.POSTGRESQL: + switch (type) { + case Types.INTEGER: + case Types.VARCHAR: + case Types.DECIMAL: + case Types.DATE: + case Types.TIME: + case Types.TIMESTAMP: + case Types.DOUBLE: + case Types.BIGINT: + case Types.BOOLEAN: + case Types.BIT: + return true; + case Types.BINARY: + case Types.VARBINARY: + case Types.BLOB: + case Types.CLOB: + case Types.LONGVARCHAR: + case Types.LONGVARBINARY: + return false; + default: + throw new AssertionError("type=" + type); + } + default: + throw new AssertionError("type=" + type); + } + } + + private String getTypeName() { + switch (type) { + case Types.INTEGER: + return "INT"; + case Types.VARCHAR: + return "VARCHAR(" + precision + ")"; + case Types.DECIMAL: + return "NUMERIC(" + precision + ", " + scale + ")"; + case Types.DATE: + return "DATE"; + case Types.TIME: + return "TIME"; + case Types.TIMESTAMP: + return "TIMESTAMP"; + case Types.BINARY: + case Types.VARBINARY: + if (config.is(TestSynth.POSTGRESQL)) { + return "BYTEA"; + } + return "BINARY(" + precision + ")"; + case Types.CLOB: { + if (config.is(TestSynth.HSQLDB)) { + return "LONGVARCHAR"; + } else if (config.is(TestSynth.POSTGRESQL)) { + return "TEXT"; + } + return "CLOB"; + } + case Types.BLOB: { + if (config.is(TestSynth.HSQLDB)) { + return "LONGVARBINARY"; + } + return "BLOB"; + } + case Types.DOUBLE: + if (config.is(TestSynth.POSTGRESQL)) { + return "DOUBLE PRECISION"; + } + return "DOUBLE"; + case Types.BIGINT: + return "BIGINT"; + case Types.BOOLEAN: + case Types.BIT: + return "BOOLEAN"; + default: + throw new AssertionError("type=" + type); + } + } + + String getCreateSQL() { + String sql = name + " " + getTypeName(); + if (!isNullable) { + sql += " NOT NULL"; + } + return sql; + } + + String getName() { + return name; + } + + Value getRandomValue() { + return Value.getRandom(config, type, precision, scale, isNullable); + } + + /** + * Generate a random column. + * + * @param config the configuration + * @return the column + */ + static Column getRandomColumn(TestSynth config) { + Column column = new Column(config); + column.name = "C_" + config.randomIdentifier(); + int randomType; + while (true) { + randomType = TYPES[config.random().getLog(TYPES.length)]; + if (config.is(TestSynth.POSTGRESQL) + && (randomType == Types.BINARY || + randomType == Types.VARBINARY || + randomType == Types.BLOB)) { + continue; + } + break; + } + column.type = randomType; + column.precision = config.random().getInt(20) + 2; + column.scale = config.random().getInt(column.precision); + column.isNullable = config.random().getBoolean(50); + return column; + } + + boolean getPrimaryKey() { + return isPrimaryKey; + } + + void setPrimaryKey(boolean b) { + isPrimaryKey = b; + } + + void setNullable(boolean b) { + isNullable = b; + } + + /** + * The the column type. + * + * @return the type + */ + int getType() { + return type; + } + +} diff --git a/h2/src/test/org/h2/test/synth/sql/Command.java b/h2/src/test/org/h2/test/synth/sql/Command.java new file mode 100644 index 0000000..00997cc --- /dev/null +++ b/h2/src/test/org/h2/test/synth/sql/Command.java @@ -0,0 +1,423 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth.sql; + +import java.sql.SQLException; +import java.util.HashMap; + +/** + * Represents a statement. + */ +class Command { + private static final int CONNECT = 0, RESET = 1, DISCONNECT = 2, + CREATE_TABLE = 3, INSERT = 4, DROP_TABLE = 5, SELECT = 6, + DELETE = 7, UPDATE = 8, COMMIT = 9, ROLLBACK = 10, + AUTOCOMMIT_ON = 11, AUTOCOMMIT_OFF = 12, + CREATE_INDEX = 13, DROP_INDEX = 14, END = 15; + + /** + * The select list. + */ + String[] selectList; + + private TestSynth config; + private final int type; + private Table table; + private HashMap tables; + private Index index; + private Column[] columns; + private Value[] values; + private String condition; + // private int nextAlias; + private String order; + private final String join = ""; + private Result result; + + private Command(TestSynth config, int type) { + this.config = config; + this.type = type; + } + + private Command(TestSynth config, int type, Table table) { + this.config = config; + this.type = type; + this.table = table; + } + + private Command(TestSynth config, int type, Table table, String alias) { + this.config = config; + this.type = type; + this.table = table; + this.tables = new HashMap<>(); + this.tables.put(alias, table); + } + + private Command(TestSynth config, int type, Index index) { + this.config = config; + this.type = type; + this.index = index; + } + + Command(int type, String alias, Table table) { + this.type = type; + if (alias == null) { + alias = table.getName(); + } + addSubqueryTable(alias, table); + this.table = table; + } + +// static Command getDropTable(TestSynth config, Table table) { +// return new Command(config, Command.DROP_TABLE, table); +// } + +// Command getCommit(TestSynth config) { +// return new Command(config, Command.COMMIT); +// } + +// Command getRollback(TestSynth config) { +// return new Command(config, Command.ROLLBACK); +// } + +// Command getSetAutoCommit(TestSynth config, boolean auto) { +// int type = auto ? Command.AUTOCOMMIT_ON : Command.AUTOCOMMIT_OFF; +// return new Command(config, type); +// } + + /** + * Create a connect command. + * + * @param config the configuration + * @return the command + */ + static Command getConnect(TestSynth config) { + return new Command(config, CONNECT); + } + + /** + * Create a reset command. + * + * @param config the configuration + * @return the command + */ + static Command getReset(TestSynth config) { + return new Command(config, RESET); + } + + /** + * Create a disconnect command. + * + * @param config the configuration + * @return the command + */ + static Command getDisconnect(TestSynth config) { + return new Command(config, DISCONNECT); + } + + /** + * Create an end command. + * + * @param config the configuration + * @return the command + */ + static Command getEnd(TestSynth config) { + return new Command(config, END); + } + + /** + * Create a create table command. + * + * @param config the configuration + * @param table the table + * @return the command + */ + static Command getCreateTable(TestSynth config, Table table) { + return new Command(config, CREATE_TABLE, table); + } + + /** + * Create a create index command. + * + * @param config the configuration + * @param index the index + * @return the command + */ + static Command getCreateIndex(TestSynth config, Index index) { + return new Command(config, CREATE_INDEX, index); + } + + /** + * Create a random select command. + * + * @param config the configuration + * @param table the table + * @return the command + */ + static Command getRandomSelect(TestSynth config, Table table) { + Command command = new Command(config, Command.SELECT, table, "M"); + command.selectList = Expression.getRandomSelectList(config, command); + // TODO group by, having, joins + command.condition = Expression.getRandomCondition(config, command).getSQL(); + command.order = Expression.getRandomOrder(config, command); + return command; + } + +// static Command getRandomSelectJoin(TestSynth config, Table table) { +// Command command = new Command(config, Command.SELECT, table, "M"); +// int len = config.random().getLog(5) + 1; +// String globalJoinCondition = ""; +// for (int i = 0; i < len; i++) { +// Table t2 = config.randomTable(); +// String alias = "J" + i; +// command.addSubqueryTable(alias, t2); +// Expression joinOn = +// Expression.getRandomJoinOn(config, command, alias); +// if (config.random().getBoolean(50)) { +// // regular join +// if (globalJoinCondition.length() > 0) { +// globalJoinCondition += " AND "; +// +// } +// globalJoinCondition += " (" + joinOn.getSQL() + ") "; +// command.addJoin(", " + t2.getName() + " " + alias); +// } else { +// String join = " JOIN " + t2.getName() + +// " " + alias + " ON " + joinOn.getSQL(); +// if (config.random().getBoolean(20)) { +// command.addJoin(" LEFT OUTER" + join); +// } else { +// command.addJoin(" INNER" + join); +// } +// } +// } +// command.selectList = +// Expression.getRandomSelectList(config, command); +// // TODO group by, having +// String cond = Expression.getRandomCondition(config, command).getSQL(); +// if (globalJoinCondition.length() > 0) { +// if (cond != null) { +// cond = "(" + globalJoinCondition + " ) AND (" + cond + ")"; +// } else { +// cond = globalJoinCondition; +// } +// } +// command.condition = cond; +// command.order = Expression.getRandomOrder(config, command); +// return command; +// } + + /** + * Create a random delete command. + * + * @param config the configuration + * @param table the table + * @return the command + */ + static Command getRandomDelete(TestSynth config, Table table) { + Command command = new Command(config, Command.DELETE, table); + command.condition = Expression.getRandomCondition(config, command).getSQL(); + return command; + } + + /** + * Create a random update command. + * + * @param config the configuration + * @param table the table + * @return the command + */ + static Command getRandomUpdate(TestSynth config, Table table) { + Command command = new Command(config, Command.UPDATE, table); + command.prepareUpdate(); + return command; + } + + /** + * Create a random insert command. + * + * @param config the configuration + * @param table the table + * @return the command + */ + static Command getRandomInsert(TestSynth config, Table table) { + Command command = new Command(config, Command.INSERT, table); + command.prepareInsert(); + return command; + } + + /** + * Add a subquery table to the command. + * + * @param alias the table alias + * @param t the table + */ + void addSubqueryTable(String alias, Table t) { + tables.put(alias, t); + } + +// void removeSubqueryTable(String alias) { +// tables.remove(alias); +// } + + private void prepareInsert() { + Column[] c; + if (config.random().getBoolean(70)) { + c = table.getColumns(); + } else { + int len = config.random().getInt(table.getColumnCount() - 1) + 1; + c = columns = table.getRandomColumns(len); + } + values = new Value[c.length]; + for (int i = 0; i < c.length; i++) { + values[i] = c[i].getRandomValue(); + } + } + + private void prepareUpdate() { + int len = config.random().getLog(table.getColumnCount() - 1) + 1; + Column[] c = columns = table.getRandomColumns(len); + values = new Value[c.length]; + for (int i = 0; i < c.length; i++) { + values[i] = c[i].getRandomValue(); + } + condition = Expression.getRandomCondition(config, this).getSQL(); + } + + private Result select(DbInterface db) throws SQLException { + StringBuilder builder = new StringBuilder("SELECT "); + for (int i = 0, length = selectList.length; i < length; i++) { + if (i > 0) { + builder.append(", "); + } + builder.append(selectList[i]); + } + builder.append(" FROM ").append(table.getName()).append(" M").append(' ').append(join); + if (condition != null) { + builder.append(" WHERE ").append(condition); + } + if (order.trim().length() > 0) { + builder.append(" ORDER BY ").append(order); + } + return db.select(builder.toString()); + } + + /** + * Run the command against the specified database. + * + * @param db the database + * @return the result + */ + Result run(DbInterface db) throws Exception { + try { + switch (type) { + case CONNECT: + db.connect(); + result = new Result("connect"); + break; + case RESET: + db.reset(); + result = new Result("reset"); + break; + case DISCONNECT: + db.disconnect(); + result = new Result("disconnect"); + break; + case END: + db.end(); + result = new Result("disconnect"); + break; + case CREATE_TABLE: + db.createTable(table); + result = new Result("createTable"); + break; + case DROP_TABLE: + db.dropTable(table); + result = new Result("dropTable"); + break; + case CREATE_INDEX: + db.createIndex(index); + result = new Result("createIndex"); + break; + case DROP_INDEX: + db.dropIndex(index); + result = new Result("dropIndex"); + break; + case INSERT: + result = db.insert(table, columns, values); + break; + case SELECT: + result = select(db); + break; + case DELETE: + result = db.delete(table, condition); + break; + case UPDATE: + result = db.update(table, columns, values, condition); + break; + case AUTOCOMMIT_ON: + db.setAutoCommit(true); + result = new Result("setAutoCommit true"); + break; + case AUTOCOMMIT_OFF: + db.setAutoCommit(false); + result = new Result("setAutoCommit false"); + break; + case COMMIT: + db.commit(); + result = new Result("commit"); + break; + case ROLLBACK: + db.rollback(); + result = new Result("rollback"); + break; + default: + throw new AssertionError("type=" + type); + } + } catch (SQLException e) { + result = new Result("", e); + } + return result; + } + + /** + * Get a random table alias name. + * + * @return the alias name + */ + String getRandomTableAlias() { + if (tables == null) { + return null; + } + Object[] list = tables.keySet().toArray(); + int i = config.random().getInt(list.length); + return (String) list[i]; + } + + /** + * Get the table with the specified alias. + * + * @param alias the alias or null if there is only one table + * @return the table + */ + Table getTable(String alias) { + if (alias == null) { + return table; + } + return tables.get(alias); + } + +// public void addJoin(String string) { +// join += string; +// } + +// static Command getSelectAll(TestSynth config, Table table) { +// Command command = new Command(config, Command.SELECT, table, "M"); +// command.selectList = new String[] { "*" }; +// command.order = ""; +// return command; +// } + +} diff --git a/h2/src/test/org/h2/test/synth/sql/DbConnection.java b/h2/src/test/org/h2/test/synth/sql/DbConnection.java new file mode 100644 index 0000000..803fc28 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/sql/DbConnection.java @@ -0,0 +1,209 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth.sql; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; + +/** + * Represents a connection to a real database. + */ +class DbConnection implements DbInterface { + private final TestSynth config; + private final int id; + private final String driver; + private final String url; + private final String user; + private final String password; + private Connection conn; + private Connection sentinel; + private final boolean useSentinel; + + DbConnection(TestSynth config, String driver, String url, String user, + String password, int id, boolean useSentinel) { + this.config = config; + this.driver = driver; + this.url = url; + this.user = user; + this.password = password; + this.id = id; + this.useSentinel = useSentinel; + log("url=" + url); + } + + @Override + public void reset() throws SQLException { + log("reset;"); + DatabaseMetaData meta = conn.getMetaData(); + Statement stat = conn.createStatement(); + ArrayList tables = new ArrayList<>(); + ResultSet rs = meta.getTables(null, null, null, new String[] { "TABLE" }); + while (rs.next()) { + String schemaName = rs.getString("TABLE_SCHEM"); + if (!"INFORMATION_SCHEMA".equals(schemaName)) { + tables.add(rs.getString("TABLE_NAME")); + } + } + while (tables.size() > 0) { + int dropped = 0; + for (int i = 0; i < tables.size(); i++) { + try { + String table = tables.get(i); + stat.execute("DROP TABLE " + table); + dropped++; + tables.remove(i); + i--; + } catch (SQLException e) { + // maybe a referential integrity + } + } + // could not drop any table and still tables to drop + if (dropped == 0 && tables.size() > 0) { + throw new AssertionError("Cannot drop " + tables); + } + } + } + + @Override + public void connect() throws Exception { + if (useSentinel && sentinel == null) { + sentinel = getConnection(); + } + log("connect to " + url + ";"); + conn = getConnection(); + } + + private Connection getConnection() throws Exception { + log("(getConnection to " + url + ");"); + if (driver == null) { + return config.getConnection("synth"); + } + Class.forName(driver); + return DriverManager.getConnection(url, user, password); + } + + @Override + public void disconnect() throws SQLException { + log("disconnect " + url + ";"); + conn.close(); + } + + @Override + public void end() throws SQLException { + log("end " + url + ";"); + if (sentinel != null) { + sentinel.close(); + sentinel = null; + } + } + + @Override + public void createTable(Table table) throws SQLException { + execute(table.getCreateSQL()); + } + + @Override + public void dropTable(Table table) throws SQLException { + execute(table.getDropSQL()); + } + + @Override + public void createIndex(Index index) throws SQLException { + execute(index.getCreateSQL()); + index.getTable().addIndex(index); + } + + @Override + public void dropIndex(Index index) throws SQLException { + execute(index.getDropSQL()); + index.getTable().removeIndex(index); + } + + @Override + public Result insert(Table table, Column[] c, Value[] v) + throws SQLException { + String sql = table.getInsertSQL(c, v); + execute(sql); + return new Result(sql, 1); + } + + private void execute(String sql) throws SQLException { + log(sql + ";"); + conn.createStatement().execute(sql); + } + + @Override + public Result select(String sql) throws SQLException { + log(sql + ";"); + Statement stat = conn.createStatement(); + Result result = new Result(config, sql, stat.executeQuery(sql)); + return result; + } + + @Override + public Result delete(Table table, String condition) throws SQLException { + String sql = "DELETE FROM " + table.getName(); + if (condition != null) { + sql += " WHERE " + condition; + } + log(sql + ";"); + Statement stat = conn.createStatement(); + Result result = new Result(sql, stat.executeUpdate(sql)); + return result; + } + + @Override + public Result update(Table table, Column[] columns, Value[] values, + String condition) throws SQLException { + String sql = "UPDATE " + table.getName() + " SET "; + for (int i = 0; i < columns.length; i++) { + if (i > 0) { + sql += ", "; + } + sql += columns[i].getName() + "=" + values[i].getSQL(); + } + if (condition != null) { + sql += " WHERE " + condition; + } + log(sql + ";"); + Statement stat = conn.createStatement(); + Result result = new Result(sql, stat.executeUpdate(sql)); + return result; + } + + @Override + public void setAutoCommit(boolean b) throws SQLException { + log("set autoCommit " + b + ";"); + conn.setAutoCommit(b); + } + + @Override + public void commit() throws SQLException { + log("commit;"); + conn.commit(); + } + + @Override + public void rollback() throws SQLException { + log("rollback;"); + conn.rollback(); + } + + private void log(String s) { + config.log(id, s); + } + + @Override + public String toString() { + return url; + } + +} diff --git a/h2/src/test/org/h2/test/synth/sql/DbInterface.java b/h2/src/test/org/h2/test/synth/sql/DbInterface.java new file mode 100644 index 0000000..118b703 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/sql/DbInterface.java @@ -0,0 +1,118 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth.sql; + +import java.sql.SQLException; + +/** + * Represents a connection to a (real or simulated) database. + */ +public interface DbInterface { + + /** + * Drop all objects in the database. + */ + void reset() throws SQLException; + + /** + * Connect to the database. + */ + void connect() throws Exception; + + /** + * Disconnect from the database. + */ + void disconnect() throws SQLException; + + /** + * Close the connection and the database. + */ + void end() throws SQLException; + + /** + * Create the specified table. + * + * @param table the table to create + */ + void createTable(Table table) throws SQLException; + + /** + * Drop the specified table. + * + * @param table the table to drop + */ + void dropTable(Table table) throws SQLException; + + /** + * Create an index. + * + * @param index the index to create + */ + void createIndex(Index index) throws SQLException; + + /** + * Drop an index. + * + * @param index the index to drop + */ + void dropIndex(Index index) throws SQLException; + + /** + * Insert a row into a table. + * + * @param table the table + * @param c the column list + * @param v the values + * @return the result + */ + Result insert(Table table, Column[] c, Value[] v) throws SQLException; + + /** + * Execute a query. + * + * @param sql the SQL statement + * @return the result + */ + Result select(String sql) throws SQLException; + + /** + * Delete a number of rows. + * + * @param table the table + * @param condition the condition + * @return the result + */ + Result delete(Table table, String condition) throws SQLException; + + /** + * Update the given table with the new values. + * + * @param table the table + * @param columns the columns to update + * @param values the new values + * @param condition the condition + * @return the result of the update + */ + Result update(Table table, Column[] columns, Value[] values, + String condition) throws SQLException; + + /** + * Enable or disable autocommit. + * + * @param b the new value + */ + void setAutoCommit(boolean b) throws SQLException; + + /** + * Commit a pending transaction. + */ + void commit() throws SQLException; + + /** + * Roll back a pending transaction. + */ + void rollback() throws SQLException; +} diff --git a/h2/src/test/org/h2/test/synth/sql/DbState.java b/h2/src/test/org/h2/test/synth/sql/DbState.java new file mode 100644 index 0000000..0ecee56 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/sql/DbState.java @@ -0,0 +1,120 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth.sql; + +import java.util.ArrayList; + +/** + * Represents a connection to a simulated database. + */ +public class DbState implements DbInterface { + + private boolean connected; + private boolean autoCommit; + private final TestSynth config; + private ArrayList tables = new ArrayList<>(); + private ArrayList indexes = new ArrayList<>(); + + DbState(TestSynth config) { + this.config = config; + } + + @Override + public void reset() { + tables = new ArrayList<>(); + indexes = new ArrayList<>(); + } + + @Override + public void connect() { + connected = true; + } + + @Override + public void disconnect() { + connected = false; + } + + @Override + public void createTable(Table table) { + tables.add(table); + } + + @Override + public void dropTable(Table table) { + tables.remove(table); + } + + @Override + public void createIndex(Index index) { + indexes.add(index); + } + + @Override + public void dropIndex(Index index) { + indexes.remove(index); + } + + @Override + public Result insert(Table table, Column[] c, Value[] v) { + return null; + } + + @Override + public Result select(String sql) { + return null; + } + + @Override + public Result delete(Table table, String condition) { + return null; + } + + @Override + public Result update(Table table, Column[] columns, Value[] values, + String condition) { + return null; + } + + @Override + public void setAutoCommit(boolean b) { + autoCommit = b; + } + + @Override + public void commit() { + // nothing to do + } + + @Override + public void rollback() { + // nothing to do + } + + /** + * Get a random table. + * + * @return the table + */ + Table randomTable() { + if (tables.size() == 0) { + return null; + } + int i = config.random().getInt(tables.size()); + return tables.get(i); + } + + @Override + public void end() { + // nothing to do + } + + @Override + public String toString() { + return "autocommit: " + autoCommit + " connected: " + connected; + } + +} diff --git a/h2/src/test/org/h2/test/synth/sql/Expression.java b/h2/src/test/org/h2/test/synth/sql/Expression.java new file mode 100644 index 0000000..50d6154 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/sql/Expression.java @@ -0,0 +1,379 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth.sql; + +import java.sql.Types; +import java.util.ArrayList; + +/** + * Represents an expression. + */ +public class Expression { + + private String sql; + private final TestSynth config; + private final Command command; + + private Expression(TestSynth config, Command command) { + this.config = config; + this.command = command; + sql = ""; + } + + /** + * Create a random select list. + * + * @param config the configuration + * @param command the command + * @return the select list + */ + static String[] getRandomSelectList(TestSynth config, Command command) { + if (config.random().getBoolean(30)) { + return new String[] { "*" }; + } + ArrayList exp = new ArrayList<>(); + String sql = ""; + if (config.random().getBoolean(10)) { + sql += "DISTINCT "; + } + int len = config.random().getLog(8) + 1; + for (int i = 0; i < len; i++) { + sql += getRandomExpression(config, command).getSQL(); + sql += " AS A" + i + " "; + exp.add(sql); + sql = ""; + } + return exp.toArray(new String[0]); + } + + /** + * Generate a random condition. + * + * @param config the configuration + * @param command the command + * @return the random condition expression + */ + static Expression getRandomCondition(TestSynth config, Command command) { + Expression condition = new Expression(config, command); + if (config.random().getBoolean(50)) { + condition.create(); + } + return condition; + } + + private static Expression getRandomExpression(TestSynth config, + Command command) { + Expression expression = new Expression(config, command); + String alias = command.getRandomTableAlias(); + Column column = command.getTable(alias).getRandomConditionColumn(); + if (column == null) { + expression.createValue(); + } else { + expression.createExpression(alias, column); + } + return expression; + } + + private void createValue() { + Value v = Column.getRandomColumn(config).getRandomValue(); + sql = v.getSQL(); + } + + /** + * Generate a random join condition. + * + * @param config the configuration + * @param command the command + * @param alias the alias name + * @return the join condition + */ + static Expression getRandomJoinOn(TestSynth config, Command command, + String alias) { + Expression expression = new Expression(config, command); + expression.createJoinComparison(alias); + return expression; + } + + /** + * Generate a random sort order list. + * + * @param config the configuration + * @param command the command + * @return the ORDER BY list + */ + static String getRandomOrder(TestSynth config, Command command) { + int len = config.random().getLog(6); + String sql = ""; + for (int i = 0; i < len; i++) { + if (i > 0) { + sql += ", "; + } + int max = command.selectList.length; + int idx = config.random().getInt(max); + // sql += getRandomExpression(command).getSQL(); + // if (max > 1 && config.random().getBoolean(50)) { + sql += "A" + idx; + // } else { + // sql += String.valueOf(idx + 1); + // } + if (config.random().getBoolean(50)) { + if (config.random().getBoolean(10)) { + sql += " ASC"; + } else { + sql += " DESC"; + } + } + } + return sql; + } + + /** + * Get the SQL snippet of this expression. + * + * @return the SQL snippet + */ + String getSQL() { + return sql.trim().length() == 0 ? null : sql.trim(); + } + + private boolean is(int percent) { + return config.random().getBoolean(percent); + } + + private String oneOf(String[] list) { + int i = config.random().getInt(list.length); + if (!sql.endsWith(" ")) { + sql += " "; + } + sql += list[i] + " "; + return list[i]; + } + + private static String getColumnName(String alias, Column column) { + if (alias == null) { + return column.getName(); + } + return alias + "." + column.getName(); + } + + private void createJoinComparison(String alias) { + int len = config.random().getLog(5) + 1; + for (int i = 0; i < len; i++) { + if (i > 0) { + sql += "AND "; + } + Column column = command.getTable(alias).getRandomConditionColumn(); + if (column == null) { + sql += "1=1"; + return; + } + sql += getColumnName(alias, column); + sql += "="; + String a2; + do { + a2 = command.getRandomTableAlias(); + } while (a2.equals(alias)); + Table t2 = command.getTable(a2); + Column c2 = t2.getRandomColumnOfType(column.getType()); + if (c2 == null) { + sql += column.getRandomValue().getSQL(); + } else { + sql += getColumnName(a2, c2); + } + sql += " "; + } + } + + private void create() { + createComparison(); + while (is(50)) { + oneOf(new String[] { "AND", "OR" }); + createComparison(); + } + } + + // private void createSubquery() { + // // String alias = command.getRandomTableAlias(); + // // Table t1 = command.getTable(alias); + // Database db = command.getDatabase(); + // Table t2 = db.getRandomTable(); + // String a2 = command.getNextTableAlias(); + // sql += "SELECT * FROM " + t2.getName() + " " + a2 + " WHERE "; + // command.addSubqueryTable(a2, t2); + // createComparison(); + // command.removeSubqueryTable(a2); + // } + + private void createComparison() { + if (is(5)) { + sql += " NOT( "; + createComparisonSub(); + sql += ")"; + } else { + createComparisonSub(); + } + } + + private void createComparisonSub() { + /* + * if (is(10)) { sql += " EXISTS("; createSubquery(); sql += ")"; + * return; } else + */ + if (is(10)) { + sql += "("; + create(); + sql += ")"; + return; + } + String alias = command.getRandomTableAlias(); + Column column = command.getTable(alias).getRandomConditionColumn(); + if (column == null) { + if (is(50)) { + sql += "1=1"; + } else { + sql += "1=0"; + } + return; + } + boolean columnFirst = is(90); + if (columnFirst) { + sql += getColumnName(alias, column); + } else { + Value v = column.getRandomValue(); + sql += v.getSQL(); + } + if (is(10)) { + oneOf(new String[] { "IS NULL", "IS NOT NULL" }); + } else if (is(10)) { + oneOf(new String[] { "BETWEEN", "NOT BETWEEN" }); + Value v = column.getRandomValue(); + sql += v.getSQL(); + sql += " AND "; + v = column.getRandomValue(); + sql += v.getSQL(); + // } else if (is(10)) { + // // oneOf(new String[] { "IN", "NOT IN" }); + // sql += " IN "; + // sql += "("; + // int len = config.random().getInt(8) + 1; + // for (int i = 0; i < len; i++) { + // if (i > 0) { + // sql += ", "; + // } + // sql += column.getRandomValueNotNull().getSQL(); + // } + // sql += ")"; + } else { + if (column.getType() == Types.VARCHAR) { + oneOf(new String[] { "=", "=", "=", "<", ">", + "<=", ">=", "<>", "LIKE", "NOT LIKE" }); + } else { + oneOf(new String[] { "=", "=", "=", "<", ">", + "<=", ">=", "<>" }); + } + if (columnFirst) { + Value v = column.getRandomValue(); + sql += v.getSQL(); + } else { + sql += getColumnName(alias, column); + } + } + } + + private void createExpression(String alias, Column type) { + boolean op = is(20); + // no null values if there is an operation + boolean allowNull = !op; + // boolean allowNull =true; + + createTerm(alias, type, true); + if (op) { + switch (type.getType()) { + case Types.INTEGER: + if (config.is(TestSynth.POSTGRESQL)) { + oneOf(new String[] { "+", "-", "/" }); + } else { + oneOf(new String[] { "+", "-", "*", "/" }); + } + createTerm(alias, type, allowNull); + break; + case Types.DECIMAL: + oneOf(new String[] { "+", "-", "*" }); + createTerm(alias, type, allowNull); + break; + case Types.VARCHAR: + sql += " || "; + createTerm(alias, type, allowNull); + break; + case Types.BLOB: + case Types.CLOB: + case Types.DATE: + break; + default: + } + } + } + + private void createTerm(String alias, Column type, boolean allowNull) { + int dt = type.getType(); + if (is(5) && (dt == Types.INTEGER) || (dt == Types.DECIMAL)) { + sql += " - "; + allowNull = false; + } + if (is(10)) { + sql += "("; + createTerm(alias, type, allowNull); + sql += ")"; + return; + } + if (is(20)) { + // if (is(10)) { + // sql += "CAST("; + // // TODO cast + // Column c = Column.getRandomColumn(config); + // createTerm(alias, c, allowNull); + // sql += " AS "; + // sql += type.getTypeName(); + // sql += ")"; + // return; + // } + switch (dt) { + // case Types.INTEGER: + // String function = oneOf(new String[] { "LENGTH" /*, "MOD" */ }); + // sql += "("; + // createTerm(alias, type, allowNull); + // sql += ")"; + // break; + case Types.VARCHAR: + oneOf(new String[] { "LOWER", "UPPER" }); + sql += "("; + createTerm(alias, type, allowNull); + sql += ")"; + break; + default: + createTerm(alias, type, allowNull); + } + return; + } + if (is(60)) { + String a2 = command.getRandomTableAlias(); + Column column = command.getTable(a2).getRandomColumnOfType(dt); + if (column != null) { + sql += getColumnName(a2, column); + return; + } + } + + Value v = Value.getRandom(config, dt, 20, 2, allowNull); + sql += v.getSQL(); + } + + @Override + public String toString() { + throw new AssertionError(); + } + +} diff --git a/h2/src/test/org/h2/test/synth/sql/Index.java b/h2/src/test/org/h2/test/synth/sql/Index.java new file mode 100644 index 0000000..544a847 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/sql/Index.java @@ -0,0 +1,52 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth.sql; + +/** + * Represents an index. + */ +public class Index { + private final Table table; + private final String name; + private final Column[] columns; + private final boolean unique; + + Index(Table table, String name, Column[] columns, boolean unique) { + this.table = table; + this.name = name; + this.columns = columns; + this.unique = unique; + } + + String getName() { + return name; + } + + String getCreateSQL() { + String sql = "CREATE "; + if (unique) { + sql += "UNIQUE "; + } + sql += "INDEX " + name + " ON " + table.getName() + "("; + for (int i = 0; i < columns.length; i++) { + if (i > 0) { + sql += ", "; + } + sql += columns[i].getName(); + } + sql += ")"; + return sql; + } + + String getDropSQL() { + return "DROP INDEX " + name; + } + + Table getTable() { + return table; + } + +} diff --git a/h2/src/test/org/h2/test/synth/sql/RandomGen.java b/h2/src/test/org/h2/test/synth/sql/RandomGen.java new file mode 100644 index 0000000..50ce674 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/sql/RandomGen.java @@ -0,0 +1,345 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth.sql; + +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Random; + +/** + * A random data generator class. + */ +public class RandomGen { + private final Random random = new Random(); + + /** + * Create a new random instance with a fixed seed value. + */ + public RandomGen() { + random.setSeed(12); + } + + /** + * Get the next integer that is smaller than max. + * + * @param max the upper limit (exclusive) + * @return the random value + */ + public int getInt(int max) { + return max == 0 ? 0 : random.nextInt(max); + } + + /** + * Get the next gaussian value. + * + * @return the value + */ + public double nextGaussian() { + return random.nextGaussian(); + } + + /** + * Get the next random value that is at most max but probably much lower. + * + * @param max the maximum value + * @return the value + */ + public int getLog(int max) { + if (max == 0) { + return 0; + } + while (true) { + int d = Math.abs((int) (random.nextGaussian() / 2. * max)); + if (d < max) { + return d; + } + } + } + + /** + * Get a number of random bytes. + * + * @param data the target buffer + */ + public void getBytes(byte[] data) { + random.nextBytes(data); + } + + /** + * Get a boolean that is true with the given probability in percent. + * + * @param percent the probability + * @return the boolean value + */ + public boolean getBoolean(int percent) { + return random.nextInt(100) <= percent; + } + + /** + * Get a random string with the given length. + * + * @param len the length + * @return the string + */ + public String randomString(int len) { + StringBuilder buff = new StringBuilder(); + for (int i = 0; i < len; i++) { + String from = (i % 2 == 0) ? "bdfghklmnpqrst" : "aeiou"; + buff.append(from.charAt(getInt(from.length()))); + } + return buff.toString(); + } + + /** + * Get a random integer. In 10% of the cases, Integer.MAX_VALUE is returned, + * in 10% of the cases Integer.MIN_VALUE. Also in the 10% of the cases, a + * random value between those extremes is returned. In 20% of the cases, + * this method returns 0. In 10% of the cases, a gaussian value in the range + * -200 and +1800 is returned. In all other cases, a gaussian value in the + * range -5 and +20 is returned. + * + * @return the random value + */ + public int getRandomInt() { + switch (random.nextInt(10)) { + case 0: + return Integer.MAX_VALUE; + case 1: + return Integer.MIN_VALUE; + case 2: + return random.nextInt(); + case 3: + case 4: + return 0; + case 5: + return (int) (random.nextGaussian() * 2000) - 200; + default: + return (int) (random.nextGaussian() * 20) - 5; + } + } + + /** + * Get a random long. The algorithm is similar to a random int. + * + * @return the random value + */ + public long getRandomLong() { + switch (random.nextInt(10)) { + case 0: + return Long.MAX_VALUE; + case 1: + return Long.MIN_VALUE; + case 2: + return random.nextLong(); + case 3: + case 4: + return 0; + case 5: + return (int) (random.nextGaussian() * 20000) - 2000; + default: + return (int) (random.nextGaussian() * 200) - 50; + } + } + + /** + * Get a random double. The algorithm is similar to a random int. + * + * @return the random value + */ + public double getRandomDouble() { + switch (random.nextInt(10)) { + case 0: + return Double.MIN_VALUE; + case 1: + return Double.MAX_VALUE; + case 2: + return Float.MIN_VALUE; + case 3: + return Float.MAX_VALUE; + case 4: + return random.nextDouble(); + case 5: + case 6: + return 0; + case 7: + return random.nextGaussian() * 20000. - 2000.; + default: + return random.nextGaussian() * 200. - 50.; + } + } + + /** + * Get a random boolean. + * + * @return the random value + */ + public boolean nextBoolean() { + return random.nextBoolean(); + } + + /** + * Get a random integer array. In 10% of the cases, null is returned. + * + * @return the array + */ + public int[] getIntArray() { + switch (random.nextInt(10)) { + case 0: + return null; + default: + int len = getInt(100); + int[] list = new int[len]; + for (int i = 0; i < len; i++) { + list[i] = getRandomInt(); + } + return list; + } + } + + /** + * Get a random byte array. In 10% of the cases, null is returned. + * + * @return the array + */ + public byte[] getByteArray() { + switch (random.nextInt(10)) { + case 0: + return null; + default: + int len = getInt(100); + byte[] list = new byte[len]; + random.nextBytes(list); + return list; + } + } + + /** + * Get a random time value. In 10% of the cases, null is returned. + * + * @return the value + */ + public Time randomTime() { + if (random.nextInt(10) == 0) { + return null; + } + StringBuilder buff = new StringBuilder(); + buff.append(getInt(24)); + buff.append(':'); + buff.append(getInt(24)); + buff.append(':'); + buff.append(getInt(24)); + return Time.valueOf(buff.toString()); + + } + + /** + * Get a random timestamp value. In 10% of the cases, null is returned. + * + * @return the value + */ + public Timestamp randomTimestamp() { + if (random.nextInt(10) == 0) { + return null; + } + StringBuilder buff = new StringBuilder(); + buff.append(getInt(10) + 2000); + buff.append('-'); + int month = getInt(12) + 1; + if (month < 10) { + buff.append('0'); + } + buff.append(month); + buff.append('-'); + int day = getInt(28) + 1; + if (day < 10) { + buff.append('0'); + } + buff.append(day); + buff.append(' '); + int hour = getInt(24); + if (hour < 10) { + buff.append('0'); + } + buff.append(hour); + buff.append(':'); + int minute = getInt(60); + if (minute < 10) { + buff.append('0'); + } + buff.append(minute); + buff.append(':'); + int second = getInt(60); + if (second < 10) { + buff.append('0'); + } + buff.append(second); + // TODO test timestamp nanos + return Timestamp.valueOf(buff.toString()); + } + + /** + * Get a random date value. In 10% of the cases, null is returned. + * + * @return the value + */ + public Date randomDate() { + if (random.nextInt(10) == 0) { + return null; + } + StringBuilder buff = new StringBuilder(); + buff.append(getInt(10) + 2000); + buff.append('-'); + int month = getInt(12) + 1; + if (month < 10) { + buff.append('0'); + } + buff.append(month); + buff.append('-'); + int day = getInt(28) + 1; + if (day < 10) { + buff.append('0'); + } + buff.append(day); + return Date.valueOf(buff.toString()); + } + + /** + * Randomly modify a SQL statement. + * + * @param sql the original SQL statement + * @return the modified statement + */ + public String modify(String sql) { + int len = getLog(10); + for (int i = 0; i < len; i++) { + int pos = getInt(sql.length()); + if (getBoolean(50)) { + String badChars = "abcABCDEF\u00ef\u00f6\u00fcC1230=<>+\"\\*%&/()=?$_-.:,;{}[]"; + // auml, ouml, uuml + char bad = badChars.charAt(getInt(badChars.length())); + sql = sql.substring(0, pos) + bad + sql.substring(pos); + } else { + if (pos >= sql.length()) { + sql = sql.substring(0, pos); + } else { + sql = sql.substring(0, pos) + sql.substring(pos + 1); + } + } + } + return sql; + } + + /** + * Set the seed value. + * + * @param seed the new seed value + */ + public void setSeed(int seed) { + random.setSeed(seed); + } + +} diff --git a/h2/src/test/org/h2/test/synth/sql/Result.java b/h2/src/test/org/h2/test/synth/sql/Result.java new file mode 100644 index 0000000..556bf8c --- /dev/null +++ b/h2/src/test/org/h2/test/synth/sql/Result.java @@ -0,0 +1,154 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth.sql; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; + +import org.h2.test.TestBase; + +/** + * Represents an in-memory result. + */ +class Result implements Comparable { + static final int SUCCESS = 0, BOOLEAN = 1, INT = 2, EXCEPTION = 3, + RESULT_SET = 4; + + String sql; + + private final int type; + private boolean bool; + private int intValue; + private SQLException exception; + private ArrayList rows; + private ArrayList header; + + Result(String sql) { + this.sql = sql; + type = SUCCESS; + } + + Result(String sql, SQLException e) { + this.sql = sql; + type = EXCEPTION; + exception = e; + } + + Result(String sql, boolean b) { + this.sql = sql; + type = BOOLEAN; + this.bool = b; + } + + Result(String sql, int i) { + this.sql = sql; + type = INT; + this.intValue = i; + } + + Result(TestSynth config, String sql, ResultSet rs) { + this.sql = sql; + type = RESULT_SET; + try { + rows = new ArrayList<>(); + header = new ArrayList<>(); + ResultSetMetaData meta = rs.getMetaData(); + int len = meta.getColumnCount(); + Column[] cols = new Column[len]; + for (int i = 0; i < len; i++) { + cols[i] = new Column(meta, i + 1); + } + while (rs.next()) { + Row row = new Row(config, rs, len); + rows.add(row); + } + Collections.sort(rows); + } catch (SQLException e) { + // type = EXCEPTION; + // exception = e; + TestBase.logError("error reading result set", e); + } + } + + @Override + public String toString() { + switch (type) { + case SUCCESS: + return "success"; + case BOOLEAN: + return "boolean: " + this.bool; + case INT: + return "int: " + this.intValue; + case EXCEPTION: { + StringWriter w = new StringWriter(); + exception.printStackTrace(new PrintWriter(w)); + return "exception: " + exception.getSQLState() + ": " + + exception.getMessage() + "\r\n" + w.toString(); + } + case RESULT_SET: + String result = "ResultSet { // size=" + rows.size() + "\r\n "; + for (Column column : header) { + result += column.toString() + "; "; + } + result += "} = {\r\n"; + for (Row row : rows) { + result += " { " + row.toString() + "};\r\n"; + } + return result + "}"; + default: + throw new AssertionError("type=" + type); + } + } + + @Override + public int compareTo(Result r) { + switch (type) { + case EXCEPTION: + if (r.type != EXCEPTION) { + return 1; + } + return 0; + // return + // exception.getSQLState().compareTo(r.exception.getSQLState()); + case BOOLEAN: + case INT: + case SUCCESS: + case RESULT_SET: + return toString().compareTo(r.toString()); + default: + throw new AssertionError("type=" + type); + } + } + +// public void log() { +// switch (type) { +// case SUCCESS: +// System.out.println("> ok"); +// break; +// case EXCEPTION: +// System.out.println("> exception"); +// break; +// case INT: +// if (intValue == 0) { +// System.out.println("> ok"); +// } else { +// System.out.println("> update count: " + intValue); +// } +// break; +// case RESULT_SET: +// System.out.println("> rs " + rows.size()); +// break; +// default: +// } +// System.out.println(); +// } + +} diff --git a/h2/src/test/org/h2/test/synth/sql/Row.java b/h2/src/test/org/h2/test/synth/sql/Row.java new file mode 100644 index 0000000..e60988b --- /dev/null +++ b/h2/src/test/org/h2/test/synth/sql/Row.java @@ -0,0 +1,51 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth.sql; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Represents a row. + */ +class Row implements Comparable { + private final Value[] data; + + public Row(TestSynth config, ResultSet rs, int len) throws SQLException { + data = new Value[len]; + for (int i = 0; i < len; i++) { + data[i] = Value.read(config, rs, i + 1); + } + } + + @Override + public String toString() { + String s = ""; + for (Object o : data) { + s += o == null ? "NULL" : o.toString(); + s += "; "; + } + return s; + } + + @Override + public int compareTo(Row r2) { + int result = 0; + for (int i = 0; i < data.length && result == 0; i++) { + Object o1 = data[i]; + Object o2 = r2.data[i]; + if (o1 == null) { + result = (o2 == null) ? 0 : -1; + } else if (o2 == null) { + result = 1; + } else { + result = o1.toString().compareTo(o2.toString()); + } + } + return result; + } + +} diff --git a/h2/src/test/org/h2/test/synth/sql/Table.java b/h2/src/test/org/h2/test/synth/sql/Table.java new file mode 100644 index 0000000..abf1092 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/sql/Table.java @@ -0,0 +1,265 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth.sql; + +import java.util.ArrayList; + +/** + * Represents a table. + */ +class Table { + + private final TestSynth config; + private String name; + private boolean temporary; + private boolean globalTemporary; + private Column[] columns; + private Column[] primaryKeys; + private final ArrayList indexes = new ArrayList<>(); + + Table(TestSynth config) { + this.config = config; + } + + /** + * Create a new random table. + * + * @param config the configuration + * @return the table + */ + static Table newRandomTable(TestSynth config) { + Table table = new Table(config); + table.name = "T_" + config.randomIdentifier(); + + // there is a difference between local temp tables for persistent and + // in-memory mode + // table.temporary = config.random().getBoolean(10); + // if(table.temporary) { + // if(config.getMode() == TestSynth.H2_MEM) { + // table.globalTemporary = false; + // } else { + // table.globalTemporary = config.random().getBoolean(50); + // } + // } + + int len = config.random().getLog(10) + 1; + table.columns = new Column[len]; + for (int i = 0; i < len; i++) { + Column col = Column.getRandomColumn(config); + table.columns[i] = col; + } + if (config.random().getBoolean(90)) { + int pkLen = config.random().getLog(len); + table.primaryKeys = new Column[pkLen]; + for (int i = 0; i < pkLen; i++) { + Column pk = null; + do { + pk = table.columns[config.random().getInt(len)]; + } while (pk.getPrimaryKey()); + table.primaryKeys[i] = pk; + pk.setPrimaryKey(true); + pk.setNullable(false); + } + } + return table; + } + + /** + * Create a new random index. + * + * @return the index + */ + Index newRandomIndex() { + String indexName = "I_" + config.randomIdentifier(); + int len = config.random().getLog(getColumnCount() - 1) + 1; + boolean unique = config.random().getBoolean(50); + Column[] cols = getRandomColumns(len); + Index index = new Index(this, indexName, cols, unique); + return index; + } + + /** + * Get the DROP TABLE statement for this table. + * + * @return the SQL statement + */ + String getDropSQL() { + return "DROP TABLE " + name; + } + + /** + * Get the CREATE TABLE statement for this table. + * + * @return the SQL statement + */ + String getCreateSQL() { + String sql = "CREATE "; + if (temporary) { + if (globalTemporary) { + sql += "GLOBAL "; + } else { + sql += "LOCAL "; + } + sql += "TEMPORARY "; + } + sql += "TABLE " + name + "("; + for (int i = 0; i < columns.length; i++) { + if (i > 0) { + sql += ", "; + } + Column column = columns[i]; + sql += column.getCreateSQL(); + if (primaryKeys != null && primaryKeys.length == 1 && + primaryKeys[0] == column) { + sql += " PRIMARY KEY"; + } + } + if (primaryKeys != null && primaryKeys.length > 1) { + sql += ", "; + sql += "PRIMARY KEY("; + for (int i = 0; i < primaryKeys.length; i++) { + if (i > 0) { + sql += ", "; + } + Column column = primaryKeys[i]; + sql += column.getName(); + } + sql += ")"; + } + sql += ")"; + return sql; + } + + /** + * Get the INSERT statement for this table. + * + * @param c the column list + * @param v the value list + * @return the SQL statement + */ + String getInsertSQL(Column[] c, Value[] v) { + String sql = "INSERT INTO " + name; + if (c != null) { + sql += "("; + for (int i = 0; i < c.length; i++) { + if (i > 0) { + sql += ", "; + } + sql += c[i].getName(); + } + sql += ")"; + } + sql += " VALUES("; + for (int i = 0; i < v.length; i++) { + if (i > 0) { + sql += ", "; + } + sql += v[i].getSQL(); + } + sql += ")"; + return sql; + } + + /** + * Get the table name. + * + * @return the name + */ + String getName() { + return name; + } + + /** + * Get a random column that can be used in a condition. + * + * @return the column + */ + Column getRandomConditionColumn() { + ArrayList list = new ArrayList<>(); + for (Column col : columns) { + if (Column.isConditionType(config, col.getType())) { + list.add(col); + } + } + if (list.size() == 0) { + return null; + } + return list.get(config.random().getInt(list.size())); + } + + Column getRandomColumn() { + return columns[config.random().getInt(columns.length)]; + } + + int getColumnCount() { + return columns.length; + } + + /** + * Get a random column of the specified type. + * + * @param type the type + * @return the column or null if no such column was found + */ + Column getRandomColumnOfType(int type) { + ArrayList list = new ArrayList<>(); + for (Column col : columns) { + if (col.getType() == type) { + list.add(col); + } + } + if (list.size() == 0) { + return null; + } + return list.get(config.random().getInt(list.size())); + } + + /** + * Get a number of random column from this table. + * + * @param len the column count + * @return the columns + */ + Column[] getRandomColumns(int len) { + int[] index = new int[columns.length]; + for (int i = 0; i < columns.length; i++) { + index[i] = i; + } + for (int i = 0; i < columns.length; i++) { + int temp = index[i]; + int r = index[config.random().getInt(columns.length)]; + index[i] = index[r]; + index[r] = temp; + } + Column[] c = new Column[len]; + for (int i = 0; i < len; i++) { + c[i] = columns[index[i]]; + } + return c; + } + + Column[] getColumns() { + return columns; + } + + /** + * Add this index to the table. + * + * @param index the index to add + */ + void addIndex(Index index) { + indexes.add(index); + } + + /** + * Remove an index from the table. + * + * @param index the index to remove + */ + void removeIndex(Index index) { + indexes.remove(index); + } +} diff --git a/h2/src/test/org/h2/test/synth/sql/TestSynth.java b/h2/src/test/org/h2/test/synth/sql/TestSynth.java new file mode 100644 index 0000000..389a914 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/sql/TestSynth.java @@ -0,0 +1,346 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth.sql; + +import java.util.ArrayList; + +import org.h2.test.TestAll; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.MathUtils; + +/** + * A test that generates random SQL statements against a number of databases + * and compares the results. + */ +public class TestSynth extends TestDb { + + // TODO hsqldb: call 1||null should return 1 but returns null + // TODO hsqldb: call mod(1) should return invalid parameter count + + /** + * A H2 database connection. + */ + static final int H2 = 0; + + /** + * An in-memory H2 database connection. + */ + static final int H2_MEM = 1; + + /** + * An HSQLDB database connection. + */ + static final int HSQLDB = 2; + + /** + * A MySQL database connection. + */ + static final int MYSQL = 3; + + /** + * A PostgreSQL database connection. + */ + static final int POSTGRESQL = 4; + + private final DbState dbState = new DbState(this); + private ArrayList databases; + private ArrayList commands; + private final RandomGen random = new RandomGen(); + private boolean showError, showLog; + private boolean stopImmediately; + private int mode; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + /** + * Check whether this database is of the specified type. + * + * @param isType the database type + * @return true if it is + */ + boolean is(int isType) { + return mode == isType; + } + + /** + * Get the random number generator. + * + * @return the random number generator + */ + RandomGen random() { + return random; + } + + /** + * Get a random identifier. + * + * @return the random identifier + */ + String randomIdentifier() { + int len = random.getLog(8) + 2; + while (true) { + return random.randomString(len); + } + } + + private void add(Command command) throws Exception { + command.run(dbState); + commands.add(command); + } + + private void addRandomCommands() throws Exception { + switch (random.getInt(20)) { + case 0: { + add(Command.getDisconnect(this)); + add(Command.getConnect(this)); + break; + } + case 1: { + Table table = Table.newRandomTable(this); + add(Command.getCreateTable(this, table)); + break; + } + case 2: { + Table table = randomTable(); + add(Command.getCreateIndex(this, table.newRandomIndex())); + break; + } + case 3: + case 4: + case 5: { + Table table = randomTable(); + add(Command.getRandomInsert(this, table)); + break; + } + case 6: + case 7: + case 8: { + Table table = randomTable(); + add(Command.getRandomUpdate(this, table)); + break; + } + case 9: + case 10: { + Table table = randomTable(); + add(Command.getRandomDelete(this, table)); + break; + } + default: { + Table table = randomTable(); + add(Command.getRandomSelect(this, table)); + } + } + } + + private void testRun(int seed) throws Exception { + random.setSeed(seed); + commands = new ArrayList<>(); + add(Command.getConnect(this)); + add(Command.getReset(this)); + + for (int i = 0; i < 1; i++) { + Table table = Table.newRandomTable(this); + add(Command.getCreateTable(this, table)); + add(Command.getCreateIndex(this, table.newRandomIndex())); + } + for (int i = 0; i < 2000; i++) { + addRandomCommands(); + } + // for (int i = 0; i < 20; i++) { + // Table table = randomTable(); + // add(Command.getRandomInsert(this, table)); + // } + // for (int i = 0; i < 100; i++) { + // Table table = randomTable(); + // add(Command.getRandomSelect(this, table)); + // } + // for (int i = 0; i < 10; i++) { + // Table table = randomTable(); + // add(Command.getRandomUpdate(this, table)); + // } + // for (int i = 0; i < 30; i++) { + // Table table = randomTable(); + // add(Command.getRandomSelect(this, table)); + // } + // for (int i = 0; i < 50; i++) { + // Table table = randomTable(); + // add(Command.getRandomDelete(this, table)); + // } + // for (int i = 0; i < 10; i++) { + // Table table = randomTable(); + // add(Command.getRandomSelect(this, table)); + // } + // while(true) { + // Table table = randomTable(); + // if(table == null) { + // break; + // } + // add(Command.getDropTable(this, table)); + // } + add(Command.getDisconnect(this)); + add(Command.getEnd(this)); + + for (int i = 0; i < commands.size(); i++) { + Command command = commands.get(i); + boolean stop = process(seed, i, command); + if (stop) { + break; + } + } + } + + private boolean process(int seed, int id, Command command) throws Exception { + try { + ArrayList results = new ArrayList<>(); + for (int i = 0; i < databases.size(); i++) { + DbInterface db = databases.get(i); + Result result = command.run(db); + results.add(result); + if (showError && i == 0) { + // result.log(); + } + } + compareResults(results); + + } catch (Error e) { + if (showError) { + TestBase.logError("synth", e); + } + System.out.println("new TestSynth().init(test).testCase(" + seed + + "); // id=" + id + " " + e.toString()); + if (stopImmediately) { + System.exit(0); + } + return true; + } + return false; + } + + private void compareResults(ArrayList results) { + Result original = results.get(0); + for (int i = 1; i < results.size(); i++) { + Result copy = results.get(i); + if (original.compareTo(copy) != 0) { + if (showError) { + throw new AssertionError( + "Results don't match: original (0): \r\n" + + original + "\r\n" + "other:\r\n" + copy); + } + throw new AssertionError("Results don't match"); + } + } + } + + /** + * Get a random table. + * + * @return the table + */ + Table randomTable() { + return dbState.randomTable(); + } + + /** + * Print this message if the log is enabled. + * + * @param id the id + * @param s the message + */ + void log(int id, String s) { + if (showLog && id == 0) { + System.out.println(s); + } + } + + int getMode() { + return mode; + } + + private void addDatabase(String className, String url, String user, + String password, boolean useSentinel) { + DbConnection db = new DbConnection(this, className, url, user, + password, databases.size(), useSentinel); + databases.add(db); + } + + @Override + public TestBase init(TestAll conf) throws Exception { + super.init(conf); + deleteDb("synth/synth"); + databases = new ArrayList<>(); + + // mode = HSQLDB; + // addDatabase("org.hsqldb.jdbcDriver", "jdbc:hsqldb:test", "sa", "" ); + // addDatabase("org.h2.Driver", "jdbc:h2:synth;mode=hsqldb", "sa", ""); + + // mode = POSTGRESQL; + // addDatabase("org.postgresql.Driver", "jdbc:postgresql:test", "sa", + // "sa"); + // addDatabase("org.h2.Driver", "jdbc:h2:synth;mode=postgresql", "sa", + // ""); + + mode = H2_MEM; + org.h2.Driver.load(); + addDatabase("org.h2.Driver", "jdbc:h2:mem:synth", "sa", "", true); + addDatabase("org.h2.Driver", "jdbc:h2:" + + getBaseDir() + "/synth/synth", "sa", "", false); + + // addDatabase("com.mysql.cj.jdbc.Driver", "jdbc:mysql://localhost/test", + // "sa", ""); + // addDatabase("org.h2.Driver", "jdbc:h2:synth;mode=mysql", "sa", ""); + + // addDatabase("com.mysql.cj.jdbc.Driver", "jdbc:mysql://localhost/test", + // "sa", ""); + // addDatabase("org.ldbc.jdbc.jdbcDriver", + // "jdbc:ldbc:mysql://localhost/test", "sa", ""); + // addDatabase("org.h2.Driver", "jdbc:h2:memFS:synth", "sa", ""); + + // MySQL: NOT is bound to column: NOT ID = 1 means (NOT ID) = 1 instead + // of NOT (ID=1) + for (int i = 0; i < databases.size(); i++) { + DbConnection conn = (DbConnection) databases.get(i); + System.out.println(i + " = " + conn.toString()); + } + showError = true; + showLog = false; + + // stopImmediately = true; + // showLog = true; + // testRun(110600); // id=27 java.lang.Error: results don't match: + // original (0): + // System.exit(0); + + return this; + } + + private void testCase(int seed) throws Exception { + deleteDb("synth/synth"); + try { + printTime("TestSynth " + seed); + testRun(seed); + } catch (Error e) { + TestBase.logError("error", e); + System.exit(0); + } + } + + @Override + public void test() throws Exception { + while (true) { + int seed = MathUtils.randomInt(Integer.MAX_VALUE); + testCase(seed); + } + } + +} diff --git a/h2/src/test/org/h2/test/synth/sql/Value.java b/h2/src/test/org/h2/test/synth/sql/Value.java new file mode 100644 index 0000000..6707fee --- /dev/null +++ b/h2/src/test/org/h2/test/synth/sql/Value.java @@ -0,0 +1,338 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth.sql; + +import java.math.BigDecimal; +import java.sql.Date; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; + +/** + * Represents a simple value. + */ +public class Value { + private final int type; + private final Object data; + private final TestSynth config; + + private Value(TestSynth config, int type, Object data) { + this.config = config; + this.type = type; + this.data = data; + } + + /** + * Convert the value to a SQL string. + * + * @return the SQL string + */ + String getSQL() { + if (data == null) { + return "NULL"; + } + switch (type) { + case Types.DECIMAL: + case Types.NUMERIC: + case Types.BIGINT: + case Types.INTEGER: + case Types.DOUBLE: + case Types.REAL: + return data.toString(); + case Types.CLOB: + case Types.VARCHAR: + case Types.CHAR: + case Types.OTHER: + case Types.LONGVARCHAR: + return "'" + data.toString() + "'"; + case Types.BLOB: + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + return getBlobSQL(); + case Types.DATE: + return getDateSQL((Date) data); + case Types.TIME: + return getTimeSQL((Time) data); + case Types.TIMESTAMP: + return getTimestampSQL((Timestamp) data); + case Types.BOOLEAN: + case Types.BIT: + return (String) data; + default: + throw new AssertionError("type=" + type); + } + } + + private static Date randomDate(TestSynth config) { + return config.random().randomDate(); + + } + + private static Double randomDouble(TestSynth config) { + return config.random().getInt(100) / 10.; + } + + private static Long randomLong(TestSynth config) { + return Long.valueOf(config.random().getInt(1000)); + } + + private static Time randomTime(TestSynth config) { + return config.random().randomTime(); + } + + private static Timestamp randomTimestamp(TestSynth config) { + return config.random().randomTimestamp(); + } + + private String getTimestampSQL(Timestamp ts) { + String s = "'" + ts.toString() + "'"; + if (config.getMode() != TestSynth.HSQLDB) { + s = "TIMESTAMP " + s; + } + return s; + } + + private String getDateSQL(Date date) { + String s = "'" + date.toString() + "'"; + if (config.getMode() != TestSynth.HSQLDB) { + s = "DATE " + s; + } + return s; + } + + private String getTimeSQL(Time time) { + String s = "'" + time.toString() + "'"; + if (config.getMode() != TestSynth.HSQLDB) { + s = "TIME " + s; + } + return s; + } + + private String getBlobSQL() { + byte[] bytes = (byte[]) data; + // StringBuilder buff = new StringBuilder("X'"); + StringBuilder buff = new StringBuilder("'"); + for (byte b : bytes) { + int c = b & 0xff; + buff.append(Integer.toHexString(c >> 4 & 0xf)); + buff.append(Integer.toHexString(c & 0xf)); + + } + buff.append("'"); + return buff.toString(); + } + + /** + * Read a value from a result set. + * + * @param config the configuration + * @param rs the result set + * @param index the column index + * @return the value + */ + static Value read(TestSynth config, ResultSet rs, int index) + throws SQLException { + ResultSetMetaData meta = rs.getMetaData(); + Object data; + int type = meta.getColumnType(index); + switch (type) { + case Types.REAL: + case Types.DOUBLE: + data = rs.getDouble(index); + break; + case Types.BIGINT: + data = rs.getLong(index); + break; + case Types.DECIMAL: + case Types.NUMERIC: + data = rs.getBigDecimal(index); + break; + case Types.BLOB: + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + data = rs.getBytes(index); + break; + case Types.OTHER: + case Types.CLOB: + case Types.VARCHAR: + case Types.LONGVARCHAR: + case Types.CHAR: + data = rs.getString(index); + break; + case Types.DATE: + data = rs.getDate(index); + break; + case Types.TIME: + data = rs.getTime(index); + break; + case Types.TIMESTAMP: + data = rs.getTimestamp(index); + break; + case Types.INTEGER: + data = rs.getInt(index); + break; + case Types.NULL: + data = null; + break; + case Types.BOOLEAN: + case Types.BIT: + data = rs.getBoolean(index) ? "TRUE" : "FALSE"; + break; + default: + throw new AssertionError("type=" + type); + } + if (rs.wasNull()) { + data = null; + } + return new Value(config, type, data); + } + + /** + * Generate a random value. + * + * @param config the configuration + * @param type the value type + * @param precision the precision + * @param scale the scale + * @param mayBeNull if the value may be null or not + * @return the value + */ + static Value getRandom(TestSynth config, int type, int precision, + int scale, boolean mayBeNull) { + Object data; + if (mayBeNull && config.random().getBoolean(20)) { + return new Value(config, type, null); + } + switch (type) { + case Types.BIGINT: + data = randomLong(config); + break; + case Types.DOUBLE: + data = randomDouble(config); + break; + case Types.DECIMAL: + data = randomDecimal(config, precision, scale); + break; + case Types.VARBINARY: + case Types.BINARY: + case Types.BLOB: + data = randomBytes(config, precision); + break; + case Types.CLOB: + case Types.VARCHAR: + data = config.random().randomString(config.random().getInt(precision)); + break; + case Types.DATE: + data = randomDate(config); + break; + case Types.TIME: + data = randomTime(config); + break; + case Types.TIMESTAMP: + data = randomTimestamp(config); + break; + case Types.INTEGER: + data = randomInt(config); + break; + case Types.BOOLEAN: + case Types.BIT: + data = config.random().getBoolean(50) ? "TRUE" : "FALSE"; + break; + default: + throw new AssertionError("type=" + type); + } + return new Value(config, type, data); + } + + private static Object randomInt(TestSynth config) { + int value; + if (config.is(TestSynth.POSTGRESQL)) { + value = config.random().getInt(1000000); + } else { + value = config.random().getRandomInt(); + } + return value; + } + + private static byte[] randomBytes(TestSynth config, int max) { + int len = config.random().getLog(max); + byte[] data = new byte[len]; + config.random().getBytes(data); + return data; + } + + private static BigDecimal randomDecimal(TestSynth config, int precision, + int scale) { + int len = config.random().getLog(precision - scale) + scale; + if (len == 0) { + len++; + } + StringBuilder buff = new StringBuilder(); + for (int i = 0; i < len; i++) { + buff.append((char) ('0' + config.random().getInt(10))); + } + buff.insert(len - scale, '.'); + if (config.random().getBoolean(20)) { + buff.insert(0, '-'); + } + return new BigDecimal(buff.toString()); + } + +// private int compareTo(Object o) { +// Value v = (Value) o; +// if (type != v.type) { +// throw new AssertionError("compare " + type + +// " " + v.type + " " + data + " " + v.data); +// } +// if (data == null) { +// return (v.data == null) ? 0 : -1; +// } else if (v.data == null) { +// return 1; +// } +// switch (type) { +// case Types.DECIMAL: +// return ((BigDecimal) data).compareTo((BigDecimal) v.data); +// case Types.BLOB: +// case Types.VARBINARY: +// case Types.BINARY: +// return compareBytes((byte[]) data, (byte[]) v.data); +// case Types.CLOB: +// case Types.VARCHAR: +// return data.toString().compareTo(v.data.toString()); +// case Types.DATE: +// return ((Date) data).compareTo((Date) v.data); +// case Types.INTEGER: +// return ((Integer) data).compareTo((Integer) v.data); +// default: +// throw new AssertionError("type=" + type); +// } +// } + +// private static int compareBytes(byte[] a, byte[] b) { +// int al = a.length, bl = b.length; +// int len = Math.min(al, bl); +// for (int i = 0; i < len; i++) { +// int x = a[i] & 0xff; +// int y = b[i] & 0xff; +// if (x == y) { +// continue; +// } +// return x > y ? 1 : -1; +// } +// return al == bl ? 0 : al > bl ? 1 : -1; +// } + + @Override + public String toString() { + return getSQL(); + } + +} diff --git a/h2/src/test/org/h2/test/synth/sql/package.html b/h2/src/test/org/h2/test/synth/sql/package.html new file mode 100644 index 0000000..6826f68 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/sql/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +A synthetic test using random SQL statements executed against multiple databases. + +

    \ No newline at end of file diff --git a/h2/src/test/org/h2/test/synth/thread/TestMulti.java b/h2/src/test/org/h2/test/synth/thread/TestMulti.java new file mode 100644 index 0000000..e7e16b7 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/thread/TestMulti.java @@ -0,0 +1,64 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth.thread; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Starts multiple threads and performs random operations on each thread. + */ +public class TestMulti extends TestDb { + + /** + * If set, the test should stop. + */ + public volatile boolean stop; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + org.h2.Driver.load(); + deleteDb("openClose"); + + // int len = getSize(5, 100); + int len = 10; + TestMultiThread[] threads = new TestMultiThread[len]; + for (int i = 0; i < len; i++) { + threads[i] = new TestMultiNews(this); + } + threads[0].first(); + for (int i = 0; i < len; i++) { + threads[i].start(); + } + Thread.sleep(10000); + this.stop = true; + for (int i = 0; i < len; i++) { + threads[i].join(); + } + threads[0].finalTest(); + } + + Connection getConnection() throws SQLException { + final String url = "jdbc:h2:" + getBaseDir() + + "/openClose;LOCK_MODE=3;DB_CLOSE_DELAY=-1"; + Connection conn = DriverManager.getConnection(url, "sa", ""); + return conn; + } + +} diff --git a/h2/src/test/org/h2/test/synth/thread/TestMultiNews.java b/h2/src/test/org/h2/test/synth/thread/TestMultiNews.java new file mode 100644 index 0000000..4c2921f --- /dev/null +++ b/h2/src/test/org/h2/test/synth/thread/TestMultiNews.java @@ -0,0 +1,134 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth.thread; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * The operation part of {@link TestMulti}. + * Queries and updates a table. + */ +public class TestMultiNews extends TestMultiThread { + + private static final String PREFIX_URL = + "http://feeds.wizbangblog.com/WizbangFullFeed?m="; + + private static final int LEN = 10000; + private Connection conn; + + TestMultiNews(TestMulti base) throws SQLException { + super(base); + conn = base.getConnection(); + } + + @Override + void operation() throws SQLException { + if (random.nextInt(10) == 0) { + conn.close(); + conn = base.getConnection(); + } else if (random.nextInt(10) == 0) { + if (random.nextBoolean()) { + conn.commit(); + } else { + conn.rollback(); + } + } else if (random.nextInt(10) == 0) { + conn.setAutoCommit(random.nextBoolean()); + } else { + if (random.nextBoolean()) { + PreparedStatement prep; + if (random.nextBoolean()) { + prep = conn.prepareStatement( + "SELECT * FROM NEWS WHERE LINK = ?"); + } else { + prep = conn.prepareStatement( + "SELECT * FROM NEWS WHERE VALUE = ?"); + } + prep.setString(1, PREFIX_URL + random.nextInt(LEN)); + ResultSet rs = prep.executeQuery(); + if (!rs.next()) { + throw new SQLException("expected one row, got none"); + } + if (rs.next()) { + throw new SQLException("expected one row, got more"); + } + } else { + PreparedStatement prep = conn.prepareStatement( + "UPDATE NEWS SET STATE = ? WHERE FID = ?"); + prep.setInt(1, random.nextInt(100)); + prep.setInt(2, random.nextInt(LEN)); + int count = prep.executeUpdate(); + if (count != 1) { + throw new SQLException("expected one row, got " + count); + } + } + } + } + + @Override + void begin() { + // nothing to do + } + + @Override + void end() throws SQLException { + conn.close(); + } + + @Override + void finalTest() { + // nothing to do + } + + @Override + void first() throws SQLException { + Connection c = base.getConnection(); + Statement stat = c.createStatement(); + stat.execute("CREATE TABLE TEST (ID IDENTITY, NAME VARCHAR)"); + stat.execute("CREATE TABLE NEWS" + + "(FID NUMERIC(19) PRIMARY KEY, COMMENTS LONGVARCHAR, " + + "LINK VARCHAR(255), STATE INTEGER, VALUE VARCHAR(255))"); + stat.execute("CREATE INDEX IF NOT EXISTS " + + "NEWS_GUID_VALUE_INDEX ON NEWS(VALUE)"); + stat.execute("CREATE INDEX IF NOT EXISTS " + + "NEWS_LINK_INDEX ON NEWS(LINK)"); + stat.execute("CREATE INDEX IF NOT EXISTS " + + "NEWS_STATE_INDEX ON NEWS(STATE)"); + PreparedStatement prep = c.prepareStatement( + "INSERT INTO NEWS (FID, COMMENTS, LINK, STATE, VALUE) " + + "VALUES (?, ?, ?, ?, ?) "); + PreparedStatement prep2 = c.prepareStatement( + "INSERT INTO TEST (NAME) VALUES (?)"); + for (int i = 0; i < LEN; i++) { + int x = random.nextInt(10) * 128; + StringBuilder buff = new StringBuilder(); + while (buff.length() < x) { + buff.append("Test "); + buff.append(buff.length()); + buff.append(' '); + } + String comment = buff.toString(); + // FID + prep.setInt(1, i); + // COMMENTS + prep.setString(2, comment); + // LINK + prep.setString(3, PREFIX_URL + i); + // STATE + prep.setInt(4, 0); + // VALUE + prep.setString(5, PREFIX_URL + i); + prep.execute(); + prep2.setString(1, comment); + prep2.execute(); + } + } + +} diff --git a/h2/src/test/org/h2/test/synth/thread/TestMultiNewsSimple.java b/h2/src/test/org/h2/test/synth/thread/TestMultiNewsSimple.java new file mode 100644 index 0000000..fc04310 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/thread/TestMultiNewsSimple.java @@ -0,0 +1,95 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth.thread; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * The operation part of {@link TestMulti}. + * Executes simple queries and updates in a table. + */ +public class TestMultiNewsSimple extends TestMultiThread { + + private static int newsCount = 10000; + + private final Connection conn; + + TestMultiNewsSimple(TestMulti base) throws SQLException { + super(base); + conn = base.getConnection(); + } + + private static int getNewsCount() { + return newsCount; + } + + @Override + void first() throws SQLException { + Connection c = base.getConnection(); + c.createStatement().execute("create table news" + + "(id identity, state int default 0, text varchar default '')"); + PreparedStatement prep = c.prepareStatement( + "insert into news() values()"); + for (int i = 0; i < newsCount; i++) { + prep.executeUpdate(); + } + c.createStatement().execute("update news set text = 'Text' || id"); + c.close(); + } + + @Override + void begin() { + // nothing to do + } + + @Override + void end() throws SQLException { + conn.close(); + } + + @Override + void operation() throws SQLException { + if (random.nextInt(10) == 0) { + conn.setAutoCommit(random.nextBoolean()); + } else if (random.nextInt(10) == 0) { + if (random.nextBoolean()) { + conn.commit(); + } else { + // conn.rollback(); + } + } else { + if (random.nextBoolean()) { + PreparedStatement prep = conn.prepareStatement( + "update news set state = ? where id = ?"); + prep.setInt(1, random.nextInt(getNewsCount())); + prep.setInt(2, random.nextInt(10)); + prep.execute(); + } else { + PreparedStatement prep = conn.prepareStatement( + "select * from news where id = ?"); + prep.setInt(1, random.nextInt(getNewsCount())); + ResultSet rs = prep.executeQuery(); + if (!rs.next()) { + System.out.println("No row found"); + // throw new AssertionError("No row found"); + } + if (rs.next()) { + System.out.println("Multiple rows found"); + // throw new AssertionError("Multiple rows found"); + } + } + } + } + + @Override + void finalTest() { + // nothing to do + } + +} diff --git a/h2/src/test/org/h2/test/synth/thread/TestMultiOrder.java b/h2/src/test/org/h2/test/synth/thread/TestMultiOrder.java new file mode 100644 index 0000000..c10fec4 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/thread/TestMultiOrder.java @@ -0,0 +1,167 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth.thread; + +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Random; + +/** + * The operation part of {@link TestMulti}. + * Queries and updates two tables. + */ +public class TestMultiOrder extends TestMultiThread { + + private static int customerCount; + private static int orderCount; + private static int orderLineCount; + + private static final String[] ITEMS = { "Apples", "Oranges", + "Bananas", "Coffee" }; + + private Connection conn; + private PreparedStatement insertLine; + + TestMultiOrder(TestMulti base) throws SQLException { + super(base); + conn = base.getConnection(); + } + + @Override + void begin() throws SQLException { + insertLine = conn.prepareStatement("insert into orderLine" + + "(order_id, line_id, text, amount) values(?, ?, ?, ?)"); + insertCustomer(); + } + + @Override + void end() throws SQLException { + conn.close(); + } + + @Override + void operation() throws SQLException { + if (random.nextInt(10) == 0) { + insertCustomer(); + } else { + insertOrder(); + } + } + + private void insertOrder() throws SQLException { + PreparedStatement prep = conn.prepareStatement( + "insert into orders(customer_id , total) values(?, ?)"); + prep.setInt(1, random.nextInt(getCustomerCount())); + BigDecimal total = new BigDecimal("0"); + prep.setBigDecimal(2, total); + prep.executeUpdate(); + ResultSet rs = prep.getGeneratedKeys(); + rs.next(); + int orderId = rs.getInt(1); + int lines = random.nextInt(20); + for (int i = 0; i < lines; i++) { + insertLine.setInt(1, orderId); + insertLine.setInt(2, i); + insertLine.setString(3, ITEMS[random.nextInt(ITEMS.length)]); + BigDecimal amount = new BigDecimal( + random.nextInt(100) + "." + random.nextInt(10)); + insertLine.setBigDecimal(4, amount); + total = total.add(amount); + insertLine.addBatch(); + } + insertLine.executeBatch(); + increaseOrderLines(lines); + prep = conn.prepareStatement( + "update orders set total = ? where id = ?"); + prep.setBigDecimal(1, total); + prep.setInt(2, orderId); + increaseOrders(); + prep.execute(); + } + + private void insertCustomer() throws SQLException { + PreparedStatement prep = conn.prepareStatement( + "insert into customer(id, name) values(?, ?)"); + int customerId = getNextCustomerId(); + prep.setInt(1, customerId); + prep.setString(2, getString(customerId)); + prep.execute(); + } + + private static String getString(int id) { + StringBuilder buff = new StringBuilder(); + Random rnd = new Random(id); + int len = rnd.nextInt(40); + for (int i = 0; i < len; i++) { + String s = "bcdfghklmnprstwz"; + char c = s.charAt(rnd.nextInt(s.length())); + buff.append(i == 0 ? Character.toUpperCase(c) : c); + s = "aeiou "; + + buff.append(s.charAt(rnd.nextInt(s.length()))); + } + return buff.toString(); + } + + private static synchronized int getNextCustomerId() { + return customerCount++; + } + + private static synchronized int increaseOrders() { + return orderCount++; + } + + private static synchronized int increaseOrderLines(int count) { + return orderLineCount += count; + } + + private static int getCustomerCount() { + return customerCount; + } + + @Override + void first() throws SQLException { + Connection c = base.getConnection(); + c.createStatement().execute("drop table customer if exists"); + c.createStatement().execute("drop table orders if exists"); + c.createStatement().execute("drop table orderLine if exists"); + c.createStatement().execute("create table customer(" + + "id int primary key, name varchar, account decimal)"); + c.createStatement().execute("create table orders(" + + "id int generated by default as identity primary key, customer_id int, total decimal)"); + c.createStatement().execute("create table orderLine(" + + "order_id int, line_id int, text varchar, " + + "amount decimal, primary key(order_id, line_id))"); + c.close(); + } + + @Override + void finalTest() throws SQLException { + conn = base.getConnection(); + ResultSet rs = conn.createStatement().executeQuery( + "select count(*) from customer"); + rs.next(); + base.assertEquals(customerCount, rs.getInt(1)); + // System.out.println("customers: " + rs.getInt(1)); + + rs = conn.createStatement().executeQuery( + "select count(*) from orders"); + rs.next(); + base.assertEquals(orderCount, rs.getInt(1)); + // System.out.println("orders: " + rs.getInt(1)); + + rs = conn.createStatement().executeQuery( + "select count(*) from orderLine"); + rs.next(); + base.assertEquals(orderLineCount, rs.getInt(1)); + // System.out.println("orderLines: " + rs.getInt(1)); + + conn.close(); + } +} diff --git a/h2/src/test/org/h2/test/synth/thread/TestMultiThread.java b/h2/src/test/org/h2/test/synth/thread/TestMultiThread.java new file mode 100644 index 0000000..7ed64a1 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/thread/TestMultiThread.java @@ -0,0 +1,72 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.synth.thread; + +import java.sql.SQLException; +import java.util.Random; + +import org.h2.test.TestBase; + +/** + * The is an abstract operation for {@link TestMulti}. + */ +abstract class TestMultiThread extends Thread { + + /** + * The base object. + */ + TestMulti base; + + /** + * The random number generator. + */ + Random random = new Random(); + + TestMultiThread(TestMulti base) { + this.base = base; + } + + /** + * Execute statements that need to be executed before starting the thread. + * This includes CREATE TABLE statements. + */ + abstract void first() throws SQLException; + + /** + * The main operation to perform. This method is called in a loop. + */ + abstract void operation() throws SQLException; + + /** + * Execute statements before entering the loop, but after starting the + * thread. + */ + abstract void begin() throws SQLException; + + /** + * This method is called once after the test is stopped. + */ + abstract void end() throws SQLException; + + /** + * This method is called once after all threads have been stopped. + */ + abstract void finalTest() throws SQLException; + + @Override + public void run() { + try { + begin(); + while (!base.stop) { + operation(); + } + end(); + } catch (Throwable e) { + TestBase.logError("error", e); + } + } + +} diff --git a/h2/src/test/org/h2/test/synth/thread/package.html b/h2/src/test/org/h2/test/synth/thread/package.html new file mode 100644 index 0000000..6adf5e5 --- /dev/null +++ b/h2/src/test/org/h2/test/synth/thread/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +Synthetic tests using random operations in multiple threads. + +

    \ No newline at end of file diff --git a/h2/src/test/org/h2/test/todo/TestDiskSpaceLeak.java b/h2/src/test/org/h2/test/todo/TestDiskSpaceLeak.java new file mode 100644 index 0000000..51aff90 --- /dev/null +++ b/h2/src/test/org/h2/test/todo/TestDiskSpaceLeak.java @@ -0,0 +1,80 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.todo; + +import java.io.File; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import org.h2.test.TestBase; +import org.h2.tools.DeleteDbFiles; +import org.h2.tools.Recover; +import org.h2.util.JdbcUtils; + +/** + * A test to detect disk space leaks when killing a process. + */ +public class TestDiskSpaceLeak { + + /** + * Run just this test. + * + * @param args ignored + */ + public static void main(String... args) throws Exception { + DeleteDbFiles.execute("data", null, true); + Class.forName("org.h2.Driver"); + Connection conn; + long before = 0; + for (int i = 0; i < 10; i++) { + conn = DriverManager.getConnection("jdbc:h2:data/test"); + ResultSet rs; + rs = conn.createStatement().executeQuery( + "select count(*) from information_schema.lobs"); + rs.next(); + System.out.println("lobs: " + rs.getInt(1)); + rs = conn.createStatement().executeQuery( + "select count(*) from information_schema.lob_map"); + rs.next(); + System.out.println("lob_map: " + rs.getInt(1)); + rs = conn.createStatement().executeQuery( + "select count(*) from information_schema.lob_data"); + rs.next(); + System.out.println("lob_data: " + rs.getInt(1)); + conn.close(); + Recover.execute("data", "test"); + new File("data/test.h2.sql").renameTo(new File("data/test." + i + ".sql")); + conn = DriverManager.getConnection("jdbc:h2:data/test"); + // TestBase.setPowerOffCount(conn, i); + TestBase.setPowerOffCount(conn, 28); + String last = "connect"; + try { + conn.createStatement().execute("drop table test if exists"); + last = "drop"; + conn.createStatement().execute("create table test(id identity, b blob)"); + last = "create"; + conn.createStatement().execute("insert into test values(1, space(10000))"); + last = "insert"; + conn.createStatement().execute("delete from test"); + last = "delete"; + conn.createStatement().execute("insert into test values(1, space(10000))"); + last = "insert2"; + conn.createStatement().execute("delete from test"); + last = "delete2"; + } catch (SQLException e) { + // ignore + } finally { + JdbcUtils.closeSilently(conn); + } + long now = new File("data/test.h2.db").length(); + long diff = now - before; + before = now; + System.out.println(now + " " + diff + " " + i + " " + last); + } + } + +} diff --git a/h2/src/test/org/h2/test/todo/TestDropTableLarge.java b/h2/src/test/org/h2/test/todo/TestDropTableLarge.java new file mode 100644 index 0000000..3a05064 --- /dev/null +++ b/h2/src/test/org/h2/test/todo/TestDropTableLarge.java @@ -0,0 +1,58 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.todo; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; +import org.h2.tools.DeleteDbFiles; +import org.h2.util.Profiler; + +/** + * Test the performance of dropping large tables + */ +public class TestDropTableLarge { + + /** + * Run just this test. + * + * @param args ignored + */ + public static void main(String... args) throws Exception { + // System.setProperty("h2.largeTransactions", "true"); + TestDropTableLarge.test(); + } + + private static void test() throws SQLException { + DeleteDbFiles.execute("data", "test", true); + Connection conn = DriverManager.getConnection("jdbc:h2:data/test"); + Statement stat = conn.createStatement(); + stat.execute("create table test1(id identity, name varchar)"); + stat.execute("create table test2(id identity, name varchar)"); + conn.setAutoCommit(true); + // use two tables to make sure the data stored on disk is not too simple + PreparedStatement prep1 = conn.prepareStatement( + "insert into test1(name) values(space(255))"); + PreparedStatement prep2 = conn.prepareStatement( + "insert into test2(name) values(space(255))"); + for (int i = 0; i < 50000; i++) { + if (i % 7 != 0) { + prep1.execute(); + } else { + prep2.execute(); + } + } + Profiler prof = new Profiler(); + prof.startCollecting(); + stat.execute("DROP TABLE test1"); + prof.stopCollecting(); + System.out.println(prof.getTop(3)); + conn.close(); + } + +} diff --git a/h2/src/test/org/h2/test/todo/TestLinkedTableFullCondition.java b/h2/src/test/org/h2/test/todo/TestLinkedTableFullCondition.java new file mode 100644 index 0000000..9770cf6 --- /dev/null +++ b/h2/src/test/org/h2/test/todo/TestLinkedTableFullCondition.java @@ -0,0 +1,44 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.todo; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.Statement; +import org.h2.tools.DeleteDbFiles; + +/** + * The complete condition should be sent to a linked table, not just the index + * condition. + */ +public class TestLinkedTableFullCondition { + + /** + * Run just this test. + * + * @param args ignored + */ + public static void main(String... args) throws Exception { + DeleteDbFiles.execute("data", null, true); + Class.forName("org.h2.Driver"); + Connection conn; + conn = DriverManager.getConnection("jdbc:h2:data/test"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name varchar)"); + stat.execute("insert into test values(1, 'Hello')"); + stat.execute("insert into test values(2, 'World')"); + stat.execute("create linked table test_link" + + "('', 'jdbc:h2:data/test', '', '', 'TEST')"); + stat.execute("set trace_level_system_out 2"); + // the query sent to the linked database is + // SELECT * FROM PUBLIC.TEST T WHERE ID>=? AND ID<=? {1: 1, 2: 1}; + // it should also include AND NAME='Hello' + stat.execute("select * from test_link " + + "where id = 1 and name = 'Hello'"); + conn.close(); + } + +} diff --git a/h2/src/test/org/h2/test/todo/TestTempTableCrash.java b/h2/src/test/org/h2/test/todo/TestTempTableCrash.java new file mode 100644 index 0000000..8a4e452 --- /dev/null +++ b/h2/src/test/org/h2/test/todo/TestTempTableCrash.java @@ -0,0 +1,81 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.todo; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.Statement; +import java.util.Random; +import java.util.concurrent.TimeUnit; +import org.h2.store.fs.rec.FilePathRec; +import org.h2.test.unit.TestReopen; +import org.h2.tools.DeleteDbFiles; + +/** + * Test crashing a database by creating a lot of temporary tables. + */ +public class TestTempTableCrash { + + /** + * Run just this test. + * + * @param args ignored + */ + public static void main(String[] args) throws Exception { + TestTempTableCrash.test(); + } + + private static void test() throws Exception { + Connection conn; + Statement stat; + + System.setProperty("h2.delayWrongPasswordMin", "0"); + FilePathRec.register(); + System.setProperty("reopenShift", "4"); + TestReopen reopen = new TestReopen(); + FilePathRec.setRecorder(reopen); + + String url = "jdbc:h2:rec:memFS:data;PAGE_SIZE=64;ANALYZE_AUTO=100"; + // String url = "jdbc:h2:" + RecordingFileSystem.PREFIX + + // "data/test;PAGE_SIZE=64"; + + Class.forName("org.h2.Driver"); + DeleteDbFiles.execute("data", "test", true); + conn = DriverManager.getConnection(url, "sa", "sa"); + stat = conn.createStatement(); + + Random random = new Random(1); + long start = System.nanoTime(); + for (int i = 0; i < 10000; i++) { + long now = System.nanoTime(); + if (now > start + TimeUnit.SECONDS.toNanos(1)) { + System.out.println("i: " + i); + start = now; + } + int x; + x = random.nextInt(100); + stat.execute("drop table if exists test" + x); + String type = random.nextBoolean() ? "temp" : ""; + // String type = ""; + stat.execute("create " + type + " table test" + x + + "(id int primary key, name varchar)"); + if (random.nextBoolean()) { + stat.execute("create index idx_" + x + " on test" + x + "(name, id)"); + } + if (random.nextBoolean()) { + stat.execute("insert into test" + x + " select x, x " + + "from system_range(1, " + random.nextInt(100) + ")"); + } + if (random.nextInt(10) == 1) { + conn.close(); + conn = DriverManager.getConnection(url, "sa", "sa"); + stat = conn.createStatement(); + } + } + conn.close(); + } + +} diff --git a/h2/src/test/org/h2/test/todo/TestUndoLogLarge.java b/h2/src/test/org/h2/test/todo/TestUndoLogLarge.java new file mode 100644 index 0000000..41a463f --- /dev/null +++ b/h2/src/test/org/h2/test/todo/TestUndoLogLarge.java @@ -0,0 +1,55 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.todo; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.TimeUnit; + +import org.h2.tools.DeleteDbFiles; + +/** + * A test with an undo log size of 2 GB. + */ +public class TestUndoLogLarge { + + /** + * Run just this test. + * + * @param args ignored + */ + public static void main(String... args) throws Exception { + // System.setProperty("h2.largeTransactions", "true"); + TestUndoLogLarge.test(); + } + + private static void test() throws SQLException { + DeleteDbFiles.execute("data", "test", true); + Connection conn = DriverManager.getConnection("jdbc:h2:data/test"); + Statement stat = conn.createStatement(); + stat.execute("set max_operation_memory 100"); + stat.execute("set max_memory_undo 100"); + stat.execute("create table test(id identity, name varchar)"); + conn.setAutoCommit(false); + PreparedStatement prep = conn.prepareStatement( + "insert into test(name) values(space(1024*1024))"); + long time = System.nanoTime(); + for (int i = 0; i < 2500; i++) { + prep.execute(); + long now = System.nanoTime(); + if (now > time + TimeUnit.SECONDS.toNanos(5)) { + System.out.println(i); + time = now + TimeUnit.SECONDS.toNanos(5); + } + } + conn.rollback(); + conn.close(); + } + +} diff --git a/h2/src/test/org/h2/test/todo/columnAlias.txt b/h2/src/test/org/h2/test/todo/columnAlias.txt new file mode 100644 index 0000000..0c66332 --- /dev/null +++ b/h2/src/test/org/h2/test/todo/columnAlias.txt @@ -0,0 +1,31 @@ +DROP TABLE TEST; +CREATE TABLE TEST(ID INT); +INSERT INTO TEST VALUES(1); +INSERT INTO TEST VALUES(2); + +SELECT ID AS A FROM TEST WHERE A>0; +-- Yes: HSQLDB +-- Fail: Oracle, MS SQL Server, PostgreSQL, MySQL, H2, Derby + +SELECT ID AS A FROM TEST ORDER BY A; +-- Yes: Oracle, MS SQL Server, PostgreSQL, MySQL, H2, Derby, HSQLDB + +SELECT ID AS A FROM TEST ORDER BY -A; +-- Yes: Oracle, MySQL, HSQLDB +-- Fail: MS SQL Server, PostgreSQL, H2, Derby + +SELECT ID AS A FROM TEST GROUP BY A; +-- Yes: PostgreSQL, MySQL, HSQLDB +-- Fail: Oracle, MS SQL Server, H2, Derby + +SELECT ID AS A FROM TEST GROUP BY -A; +-- Yes: MySQL, HSQLDB +-- Fail: Oracle, MS SQL Server, PostgreSQL, H2, Derby + +SELECT ID AS A FROM TEST GROUP BY ID HAVING A>0; +-- Yes: MySQL, HSQLDB +-- Fail: Oracle, MS SQL Server, PostgreSQL, H2, Derby + +SELECT COUNT(*) AS A FROM TEST GROUP BY ID HAVING A>0; +-- Yes: MySQL, HSQLDB +-- Fail: Oracle, MS SQL Server, PostgreSQL, H2, Derby diff --git a/h2/src/test/org/h2/test/todo/dateFunctions.txt b/h2/src/test/org/h2/test/todo/dateFunctions.txt new file mode 100644 index 0000000..4f13eab --- /dev/null +++ b/h2/src/test/org/h2/test/todo/dateFunctions.txt @@ -0,0 +1,10 @@ +h2 +update FOO set a = dateadd('second', 4320000, a); +ms sql server +update FOO set a = dateadd(s, 4320000, a); +mysql +update FOO set a = date_add(a, interval 4320000 second); +postgresql +update FOO set a = a + interval '4320000 s'; +oracle +update FOO set a = a + INTERVAL '4320000' SECOND; diff --git a/h2/src/test/org/h2/test/todo/package.html b/h2/src/test/org/h2/test/todo/package.html new file mode 100644 index 0000000..a99d84e --- /dev/null +++ b/h2/src/test/org/h2/test/todo/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +Documentation and tests for open issues. + +

    \ No newline at end of file diff --git a/h2/src/test/org/h2/test/todo/recursiveQueries.txt b/h2/src/test/org/h2/test/todo/recursiveQueries.txt new file mode 100644 index 0000000..da21c32 --- /dev/null +++ b/h2/src/test/org/h2/test/todo/recursiveQueries.txt @@ -0,0 +1,130 @@ +WITH DirectReports (ManagerID, EmployeeID, Title, DeptID, Level) +AS +( +-- Anchor member definition + SELECT e.ManagerID, e.EmployeeID, e.Title, edh.DepartmentID, + 0 AS Level + FROM Employee AS e + INNER JOIN EmployeeDepartmentHistory AS edh + ON e.EmployeeID = edh.EmployeeID AND edh.EndDate IS NULL + WHERE ManagerID IS NULL + UNION ALL +-- Recursive member definition + SELECT e.ManagerID, e.EmployeeID, e.Title, edh.DepartmentID, + Level + 1 + FROM Employee AS e + INNER JOIN EmployeeDepartmentHistory AS edh + ON e.EmployeeID = edh.EmployeeID AND edh.EndDate IS NULL + INNER JOIN DirectReports AS d + ON e.ManagerID = d.EmployeeID +) +-- Statement that executes the CTE +SELECT ManagerID, EmployeeID, Title, Level +FROM DirectReports +INNER JOIN Department AS dp + ON DirectReports.DeptID = dp.DepartmentID +WHERE dp.GroupName = N'Research and Development' OR Level = 0; +GO + + DROP VIEW IF EXISTS TEST_REC; + DROP VIEW IF EXISTS TEST_2; + DROP TABLE IF EXISTS TEST; + + CREATE TABLE TEST(ID INT PRIMARY KEY, PARENT INT, NAME VARCHAR(255)); + INSERT INTO TEST VALUES(1, NULL, 'Root'); + INSERT INTO TEST VALUES(2, 1, 'Plant'); + INSERT INTO TEST VALUES(3, 1, 'Animal'); + INSERT INTO TEST VALUES(4, 2, 'Tree'); + INSERT INTO TEST VALUES(5, 2, 'Flower'); + INSERT INTO TEST VALUES(6, 3, 'Elephant'); + INSERT INTO TEST VALUES(7, 3, 'Dog'); + + CREATE FORCE VIEW TEST_2(ID, PARENT, NAME) AS SELECT ID, PARENT, NAME FROM TEST_REC; + + CREATE FORCE VIEW TEST_REC(ID, PARENT, NAME) AS + SELECT ID, PARENT, NAME FROM TEST T + WHERE PARENT IS NULL + UNION ALL + SELECT T.ID, T.PARENT, T.NAME + FROM TEST T, TEST_2 R + WHERE T.PARENT=R.ID; + + SELECT * FROM TEST_REC; + +------------ + + DROP VIEW IF EXISTS TEST_REC; + DROP VIEW IF EXISTS TEST_2; + DROP TABLE IF EXISTS TEST; + + CREATE TABLE TEST(ID INT PRIMARY KEY, PARENT INT, NAME VARCHAR(255)); + INSERT INTO TEST VALUES(1, NULL, 'Root'); + INSERT INTO TEST VALUES(2, 1, 'Plant'); + INSERT INTO TEST VALUES(3, 1, 'Animal'); + INSERT INTO TEST VALUES(4, 2, 'Tree'); + INSERT INTO TEST VALUES(5, 2, 'Flower'); + INSERT INTO TEST VALUES(6, 3, 'Elephant'); + INSERT INTO TEST VALUES(7, 3, 'Dog'); + + CREATE VIEW RECURSIVE TEST_REC(ID, PARENT, NAME) AS + SELECT ID, PARENT, NAME FROM TEST T + WHERE PARENT IS NULL + UNION ALL + SELECT T.ID, T.PARENT, T.NAME + FROM TEST T, TEST_REC R + WHERE T.PARENT=R.ID; + + SELECT * FROM TEST_REC; + +---------------- + + +CREATE LOCAL TEMPORARY TABLE test (family_name VARCHAR_IGNORECASE(63) NOT NULL); +INSERT INTO test VALUES('Smith'); +INSERT INTO test VALUES('de Smith'); +INSERT INTO test VALUES('el Smith'); +INSERT INTO test VALUES('von Smith'); +SELECT * FROM test WHERE family_name IN ('de Smith', 'Smith'); +-- okay IN(...) with TABLE_SCAN + +SELECT * FROM test WHERE family_name BETWEEN 'd' AND 'T'; +-- okay, ignorecase honoured + +SELECT * FROM test WHERE family_name BETWEEN 'D' AND 'T'; +-- okay, ignorecase honoured + +CREATE INDEX family_name ON test(family_name); +SELECT * FROM test WHERE family_name IN ('de Smith', 'Smith'); +-- OOPS, the comparison's operands are sorted incorrectly for ignorecase! + +EXPLAIN SELECT * FROM test WHERE family_name IN ('de Smith', 'Smith'); + + +------------------- + + +complete recursive views: + +drop all objects; +create table parent(id int primary key, parent int); +insert into parent values(1, null), (2, 1), (3, 1); + +with test_view(id, parent) as +select id, parent from parent where id = ? +union all +select parent.id, parent.parent from test_view, parent +where parent.parent = test_view.id +select * from test_view {1: 1}; + +drop view test_view; + +with test_view(id, parent) as +select id, parent from parent where id = 1 +union all +select parent.id, parent.parent from test_view, parent +where parent.parent = test_view.id +select * from test_view; + +drop view test_view; + +drop table parent; diff --git a/h2/src/test/org/h2/test/todo/supportTemplates.txt b/h2/src/test/org/h2/test/todo/supportTemplates.txt new file mode 100644 index 0000000..f79ebc4 --- /dev/null +++ b/h2/src/test/org/h2/test/todo/supportTemplates.txt @@ -0,0 +1,251 @@ +Old issue tracking + +Please send a question to the H2 Google Group or StackOverflow first, +and only then, once you are completely sure it is an issue, submit it here. +The reason is that only very few people actively monitor the issue tracker. + +Before submitting a bug, please also check the FAQ: +https://h2database.com/html/faq.html + +What steps will reproduce the problem? +(simple SQL scripts or simple standalone applications are preferred) +1. +2. +3. + +What is the expected output? What do you see instead? + + +What version of the product are you using? On what operating system, file +system, and virtual machine? + + +Do you know a workaround? + +What is your use case, meaning why do you need this feature? + +How important/urgent is the problem for you? + +Please provide any additional information below. + +------------------ +Benchmark: + +Hi, + +If you are running benchmarks, could you profile them please? To do that use java -Xrunhprof:cpu=samples,depth=8 ... - and then upload/post the file java.hprof.txt + +If the benchmark application is not a self contained application, another way is to generate 'full thread dumps' (maybe 30 or so) while running the benchmark, and upload them? To get a full thread dump, you can use "kill -QUIT ", or (Windows) press Ctrl+Pause, or (Java 1.6) use jps -l to get the , and then use jstack . + +Regards, +Thomas + +----------------- + +Can not reproduce: + +Hi, + +Sorry I can not reproduce this problem. Could you post a simple, standalone test case that reproduces the problem? It would be great if the test case does not have any dependencies except the H2 jar file (that is, a simple SQL script that can be run in the H2 Console, or a Java class uses the JDBC API and is run using a static main method). If the test case does have dependencies it would help a lot if you upload them somewhere (for example Rapidshare.com). Please include any initialization code (CREATE TABLE, INSERT and so on) in the Java class or in a .sql script file. + +Regards, +Thomas + +----------------- + +Defect report: + +What steps will reproduce the problem? +(simple SQL scripts or simple standalone applications are preferred) +1. +2. +3. + +What is the expected output? What do you see instead? + + +What version of the product are you using? On what operating system, file system, and virtual machine? + + +Do you know a workaround? + +How important/urgent is the problem for you? + +In your view, is this a defect or a feature request? + +Please provide any additional information below. + +----------------- +Hi, + +This looks like a corrupt database. To recover the data, use the tool org.h2.tools.Recover to create the SQL script file, and then re-create the database using this script. Does it work when you do this? + +With version 1.3.171 and older: when using local temporary tables and not dropping them manually before closing the session, and then killing the process could result in a database that couldn't be opened. + +With version 1.3.162 and older: on out of disk space, the database can get corrupt sometimes, if later write operations succeed. The same problem happens on other kinds of I/O exceptions (where one or some of the writes fail, but subsequent writes succeed). Now the file is closed on the first unsuccessful write operation, so that later requests fail consistently. + +Important corruption problems were fixed in version 1.2.135 and version 1.2.140 (see the change log). Known causes for corrupt databases are: if the database was created or used with a version older than 1.2.135, and the process was killed while the database was closing or writing a checkpoint. Using the transaction isolation level READ_UNCOMMITTED (LOCK_MODE 0) while at the same time using multiple connections. Disabling database file protection using (setting FILE_LOCK to NO in the database URL). Some other areas that are not fully tested are: Platforms other than Windows XP, Linux, Mac OS X, or JVMs other than Sun 1.5 or 1.6; the feature MULTI_THREADED; the features AUTO_SERVER and AUTO_RECONNECT; the file locking method 'Serialized'. + +I am very interested in analyzing and solving this problem. Corruption problems have top priority for me. I have a few questions: + +- What is your database URL? +- Did you use LOG=0 or LOG=1? Did you read the FAQ about it? +- Did the system ever run out of disk space? +- Could you send the full stack trace of the exception including message text? +- Did you use SHUTDOWN DEFRAG or the database setting DEFRAG_ALWAYS with H2 version 1.3.159 or older? +- How many connections does your application use concurrently? +- Do you use temporary tables? +- With which version of H2 was this database created? + You can find it out using: + select * from information_schema.settings where name='CREATE_BUILD' + or have a look in the SQL script created by the recover tool. +- Did the application run out of memory (once, or multiple times)? +- Do you use any settings or special features (for example cache settings, + two phase commit, linked tables)? +- Do you use any H2-specific system properties? +- Is the application multi-threaded? +- What operating system, file system, and virtual machine + (java -version) do you use? +- How did you start the Java process (java -Xmx... and so on)? +- Is it (or was it at some point) a networked file system? +- How big is the database (file sizes)? +- How much heap memory does the Java process have? +- Is the database usually closed normally, or is process terminated + forcefully or the computer switched off? +- Is it possible to reproduce this problem using a fresh database + (sometimes, or always)? +- Are there any other exceptions (maybe in the .trace.db file)? + Could you send them please? +- Do you still have any .trace.db files, and if yes could you send them? +- Could you send the .h2.db file where this exception occurs? + +Regards, +Thomas + +----------------- + + +This looks like a serious problem. I have a few questions: + +- Could you send the full stack trace of the exception including message text? +- What is your database URL? +- Did you use multiple connections? +- Do you use temporary tables? +- A workarounds is: use the tool org.h2.tools.Recover to create + the SQL script file, and then re-create the database using this script. + Does it work when you do this? +- With which version of H2 was this database created? + You can find it out using: + select * from information_schema.settings where name='CREATE_BUILD' + or have a look in the SQL script created by the recover tool. +- Did the application run out of memory (once, or multiple times)? +- Do you use any settings or special features (for example cache settings, + two phase commit, linked tables)? +- Do you use any H2-specific system properties? +- Is the application multi-threaded? +- What operating system, file system, and virtual machine + (java -version) do you use? +- How did you start the Java process (java -Xmx... and so on)? +- Is it (or was it at some point) a networked file system? +- How big is the database (file sizes)? +- How much heap memory does the Java process have? +- Is the database usually closed normally, or is process terminated + forcefully or the computer switched off? +- Is it possible to reproduce this problem using a fresh database + (sometimes, or always)? +- Are there any other exceptions (maybe in the .trace.db file)? + Could you send them please? +- Do you still have any .trace.db files, and if yes could you send them? +- Could you send the .h2.db file where this exception occurs? + +----------------- + + +Hi, + +I have a few questions: + +- The database URL? +- With which version of H2 was this database created? + You can find it out using: + select * from information_schema.settings where name='CREATE_BUILD' +- Did the application run out of memory (once, or multiple times)? +- Did you use multiple connections? +- Do you use any settings or special features (for example, the setting + LOG=0, or two phase commit, linked tables, cache settings)? +- Is the application multi-threaded? +- What operating system, file system, and virtual machine + (java -version) do you use? +- How big is the database (file sizes)? +- Is the database usually closed normally, or is process terminated + forcefully or the computer switched off? +- Are there any other exceptions (maybe in the .trace.db file)? + Could you send them please? +- Do you still have any .trace.db files, and if yes could you send them? +- Could you send the .h2.db file where this exception occurs? + + + + +Corrupted database + +I am sorry to say that, but it looks like a corruption problem. I am very interested in analyzing and solving this problem. Corruption problems have top priority for me. I have a few questions: + +- Could you send the full stack trace of the exception including message text? +- What is your database URL? +- Do you use Tomcat or another web server? + Do you unload or reload the web application? +- You can find out if the database is corrupted when running + SCRIPT TO 'test.sql' +- What version H2 are you using? +- Did you use multiple connections? +- The first workarounds is: append ;RECOVER=1 to the database URL. + Does it work when you do this? +- The second workarounds is: delete the index.db file (it is re-created + automatically) and try again. Does it work when you do this? +- The third workarounds is: use the tool org.h2.tools.Recover to create + the SQL script file, and then re-create the database using this script. + Does it work when you do this? +- With which version of H2 was this database created? + You can find it out using: + select * from information_schema.settings where name='CREATE_BUILD' +- Did the application run out of memory (once, or multiple times)? +- Do you use any settings or special features (for example, the setting + LOG=0, or two phase commit, linked tables, cache settings)? +- Is the application multi-threaded? +- What operating system, file system, and virtual machine + (java -version) do you use? +- Is it (or was it at some point) a networked file system? +- How big is the database (file sizes)? +- Is the database usually closed normally, or is process terminated + forcefully or the computer switched off? +- Is it possible to reproduce this problem using a fresh database + (sometimes, or always)? +- Are there any other exceptions (maybe in the .trace.db file)? + Could you send them please? +- Do you still have any .trace.db files, and if yes could you send them? +- Could you send the .data.db file where this exception occurs? + + + +Hi, + +I have a few questions: + +- The database URL? +- With which version of H2 was this database created? + You can find it out using: + select * from information_schema.settings where name='CREATE_BUILD' +- Did you use multiple connections? +- Do you use any settings or special features (for example, the setting + LOG=0, or two phase commit, linked tables, cache settings)? +- Is the application multi-threaded? +- What operating system, file system, and virtual machine + (java -version) do you use? +- How big is the database (file sizes)? +- Is the database usually closed normally, or is process terminated + forcefully or the computer switched off? +- Are there any other exceptions (maybe in the .trace.db file)? + Could you send them please? +- Do you still have any .trace.db files, and if yes could you send them? +- Could you send the .h2.db file where this exception occurs? diff --git a/h2/src/test/org/h2/test/todo/todo.txt b/h2/src/test/org/h2/test/todo/todo.txt new file mode 100644 index 0000000..f306fd7 --- /dev/null +++ b/h2/src/test/org/h2/test/todo/todo.txt @@ -0,0 +1,94 @@ +TIMESTAMPDIFF +----------------- +Test cases: +select timestampdiff(year, '2000-01-01', '2001-01-01'); +select timestampdiff(year, '2000-03-01', '2001-03-01'); +select timestampdiff(day, '2000-03-01', '2001-03-01'); +select timestampdiff(year, '2000-02-29', '2001-02-28'); +select timestampdiff(day, '2000-02-29', '2001-02-28'); +select timestampdiff(month, '2000-02-29', '2001-02-28'); +select timestampadd(year, 1, '2000-02-29'); + + +Auto Upgrade +----------------- +file conversion should be done automatically when the new engine connects. + +auto-upgrade application: +check if new version is available +(option: digital signature) +if yes download new version +(option: http, https, ftp) +backup database to SQL script +(option: list of databases, use recovery mechanism) +install new version + +ftp client +task to download new version from another HTTP / HTTPS / FTP server +multi-task + + +Direct Lookup +----------------- +drop table test; +create table test(id int, version int, idx int); +@LOOP 1000 insert into test values(1, 1, ?); +@LOOP 1000 insert into test values(1, 2, ?); +@LOOP 1000 insert into test values(2, 1, ?); +create index idx_test on test(id, version, idx); +@LOOP 1000 select max(id)+1 from test; +@LOOP 1000 select max(idx)+1 from test where id=1 and version=2; +@LOOP 1000 select max(id)+1 from test; +@LOOP 1000 select max(idx)+1 from test where id=1 and version=2; +@LOOP 1000 select max(id)+1 from test; +@LOOP 1000 select max(idx)+1 from test where id=1 and version=2; +-- should be direct query + + +Update Multiple Tables with Merge +----------------- +drop table statisticlog; +create table statisticlog(id int primary key, datatext varchar, moment int); +@LOOP 20000 insert into statisticlog values(?, ?, ?); +merge into statisticlog(id, datatext) key(id) +select id, 'data1' from statisticlog order by moment limit 5; +select * from statisticlog where id < 10; +UPDATE statisticlog SET datatext = 'data2' +WHERE id IN (SELECT id FROM statisticlog ORDER BY moment LIMIT 5); +select * from statisticlog where id < 10; + +Auto-Reconnect +----------------- +Implemented: +- auto_server includes auto_reconnect +- works with server mode +- works with auto_server mode +- keep temporary linked tables, variables on client +- statements +- prepared statements +- small result sets (up to fetch size) +- throws an error when autocommit is false +- an error is thrown when the connection is lost + while looping over large result sets (larger than fetch size) +Not implemented / not tested: +- batch updates +- ignored in embedded mode +- keep temporary tables (including data) on client +- keep identity, getGeneratedKeys on client +- throw error when in cluster mode + +Support Model +----------------- +Check JBoss and Spring support models +http://wiki.bonita.ow2.org/xwiki/bin/view/Main/BullOffer +- starting 2500 euros / year +- unlimited support requests +- 2 named contacts +- optional half days of technical aid by remote services + +Durability +----------------- +Improve documentation. +You can't make a system that will not lose data, you can only make +a system that knows the last save point of 100% integrity. There are +too many variables and too much randomness on a cold hard power failure. diff --git a/h2/src/test/org/h2/test/todo/tools.sql b/h2/src/test/org/h2/test/todo/tools.sql new file mode 100644 index 0000000..bd61c7a --- /dev/null +++ b/h2/src/test/org/h2/test/todo/tools.sql @@ -0,0 +1,83 @@ +/* + * 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 + */ + +-- TO_DATE +create alias TO_DATE as $$ +java.util.Date toDate(String s) throws Exception { + return new java.text.SimpleDateFormat("yyyy.MM.dd").parse(s); +} +$$; +call TO_DATE('1990.02.03') + +-- TO_CHAR +drop alias if exists TO_CHAR; +create alias TO_CHAR as $$ +String toChar(BigDecimal x, String pattern) throws Exception { + return new java.text.DecimalFormat(pattern).format(x); +} +$$; +call TO_CHAR(123456789.12, '###,###,###,###.##'); + +-- update all rows in all tables +select 'update ' || table_schema || '.' || table_name || ' set ' || column_name || '=' || column_name || ';' +from information_schema.columns where ORDINAL_POSITION = 1 and table_schema <> 'INFORMATION_SCHEMA'; + +-- read the first few bytes from a BLOB +drop table test; +drop alias first_bytes; +create alias first_bytes as $$ +import java.io.*; +@CODE +byte[] firstBytes(InputStream in, int len) throws IOException { + try { + byte[] data = new byte[len]; + DataInputStream dIn = new DataInputStream(in); + dIn.readFully(data, 0, len); + return data; + } finally { + in.close(); + } +} +$$; +create table test(data blob); +insert into test values('010203040506070809'); +select first_bytes(data, 3) from test; + +-- encrypt and decrypt strings +CALL CAST(ENCRYPT('AES', HASH('SHA256', STRINGTOUTF8('key'), 1), STRINGTOUTF8('Hello')) AS VARCHAR); +CALL TRIM(CHAR(0) FROM UTF8TOSTRING(DECRYPT('AES', HASH('SHA256', STRINGTOUTF8('key'), 1), '16e44604230717eec9f5fa6058e77e83'))); +DROP ALIAS ENC; +DROP ALIAS DEC; +CREATE ALIAS ENC AS $$ +import org.h2.security.*; +import org.h2.util.*; +@CODE +String encrypt(String data, String key) throws Exception { + byte[] k = new SHA256().getHash(key.getBytes("UTF-8"), false); + byte[] b1 = data.getBytes("UTF-8"); + byte[] buff = new byte[(b1.length + 15) / 16 * 16]; + System.arraycopy(b1, 0, buff, 0, b1.length); + BlockCipher bc = CipherFactory.getBlockCipher("AES"); + bc.setKey(k); + bc.encrypt(buff, 0, buff.length); + return ByteUtils.convertBytesToString(buff); +} +$$; +CREATE ALIAS DEC AS $$ +import org.h2.security.*; +import org.h2.util.*; +@CODE +String decrypt(String data, String key) throws Exception { + byte[] k = new SHA256().getHash(key.getBytes("UTF-8"), false); + byte[] buff = ByteUtils.convertStringToBytes(data); + BlockCipher bc = CipherFactory.getBlockCipher("AES"); + bc.setKey(k); + bc.decrypt(buff, 0, buff.length); + return StringUtils.trim(new String(buff, "UTF-8"), false, true, "\u0000"); +} +$$; +CALL ENC('Hello', 'key'); +CALL DEC(ENC('Hello', 'key'), 'key'); diff --git a/h2/src/test/org/h2/test/todo/versionlist.txt b/h2/src/test/org/h2/test/todo/versionlist.txt new file mode 100644 index 0000000..b8db342 --- /dev/null +++ b/h2/src/test/org/h2/test/todo/versionlist.txt @@ -0,0 +1,65 @@ +1.3.162 2011-11-26 +1.3.161 2011-10-28 +1.3.160 2011-09-11 +1.3.159 2011-08-13 +1.3.158 2011-07-17 +1.3.157 2011-06-25 +1.3.156 2011-06-17 +1.3.155 2011-05-27 +1.3.154 2011-04-04 +1.3.153 2011-03-14 +1.3.152 2011-03-01 Beta +1.3.151 2011-02-12 Beta +1.3.150 2011-01-28 Beta +1.3.149 2011-01-07 Beta +1.3.148 2010-12-12 Beta +1.2.147 2010-11-21 +1.3.146 2010-11-08 Beta +1.2.145 2010-11-02 + +1.0.78 2008-08-28 +1.0.77 2008-08-16 +1.0.76 2008-07-27 +1.0.75 2008-07-14 +1.0.74 2008-06-21 +1.0.73 2008-05-31 +1.0.72 2008-05-10 +1.0.71 2008-04-25 +1.0.70 2008-04-20 +1.0.69 2008-03-29 +1.0.68 2008-03-15 +1.0.67 2008-02-22 +1.0.66 2008-02-02 +1.0.65 2008-01-18 +1.0.64 2007-12-27 +1.0.63 2007-12-02 +1.0.62 2007-11-25 +1.0.61 2007-11-10 +1.0.60 2007-10-20 +1.0.59 2007-10-03 +1.0.58 2007-09-15 +1.0.57 2007-08-25 + +1.0 2007-08-02 +1.0 2007-07-12 + +build 50 2007-06-17 +build 46 2007-04-29 +build 41 2007-01-30 +build 2007-01-17 +build 34 2006-12-17 +build 2006-12-03 +build 2006-11-20 +build 2006-11-03 +build 2006-10-10 +build 2006-09-24 +build 2006-09-10 +build 2006-08-31 +build 2006-08-28 +build 2006-08-23 +build 2006-08-14 +build 2006-07-29 +build 2006-07-14 +build 2006-07-01 +build 10 2006-06-02 +alpha 2005-11-17 \ No newline at end of file diff --git a/h2/src/test/org/h2/test/trace/Arg.java b/h2/src/test/org/h2/test/trace/Arg.java new file mode 100644 index 0000000..5503856 --- /dev/null +++ b/h2/src/test/org/h2/test/trace/Arg.java @@ -0,0 +1,88 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + */ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.h2.test.trace; + +import java.math.BigDecimal; + +import org.h2.util.StringUtils; + +/** + * An argument of a statement. + */ +class Arg { + private Class clazz; + private Object obj; + private Statement stat; + + Arg(Class clazz, Object obj) { + this.clazz = clazz; + this.obj = obj; + } + + Arg(Statement stat) { + this.stat = stat; + } + + @Override + public String toString() { + if (stat != null) { + return stat.toString(); + } + return quote(clazz, getValue()); + } + + /** + * Calculate the value if this is a statement. + */ + void execute() throws Exception { + if (stat != null) { + obj = stat.execute(); + clazz = stat.getReturnClass(); + stat = null; + } + } + + Class getValueClass() { + return clazz; + } + + Object getValue() { + return obj; + } + + private static String quote(Class valueClass, Object value) { + if (value == null) { + return null; + } else if (valueClass == String.class) { + return StringUtils.quoteJavaString(value.toString()); + } else if (valueClass == BigDecimal.class) { + return "new BigDecimal(\"" + value.toString() + "\")"; + } else if (valueClass.isArray()) { + if (valueClass == String[].class) { + return StringUtils.quoteJavaStringArray((String[]) value); + } else if (valueClass == int[].class) { + return StringUtils.quoteJavaIntArray((int[]) value); + } + } + return value.toString(); + } + +} diff --git a/h2/src/test/org/h2/test/trace/Parser.java b/h2/src/test/org/h2/test/trace/Parser.java new file mode 100644 index 0000000..86e995e --- /dev/null +++ b/h2/src/test/org/h2/test/trace/Parser.java @@ -0,0 +1,279 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + */ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.h2.test.trace; + +import java.math.BigDecimal; +import java.util.ArrayList; + +import org.h2.util.StringUtils; + +/** + * Parses an entry in a Java-style log file. + */ +class Parser { + private static final int STRING = 0, NAME = 1, NUMBER = 2, SPECIAL = 3; + private final Player player; + private Statement stat; + private final String line; + private String token; + private int tokenType; + private int pos; + + private Parser(Player player, String line) { + this.player = player; + this.line = line; + read(); + } + + /** + * Parse a Java statement. + * + * @param player the player + * @param line the statement text + * @return the statement + */ + static Statement parseStatement(Player player, String line) { + Parser p = new Parser(player, line); + p.parseStatement(); + return p.stat; + } + + private Statement parseStatement() { + stat = new Statement(player); + String name = readToken(); + Object o = player.getObject(name); + if (o == null) { + if (readIf(".")) { + // example: java.lang.System.exit(0); + parseStaticCall(name); + } else { + // example: Statement s1 = ... + stat.setAssign(name, readToken()); + read("="); + name = readToken(); + o = player.getObject(name); + if (o != null) { + // example: ... = s1.executeQuery(); + read("."); + parseCall(name, o, readToken()); + } else if (readIf(".")) { + // ... = x.y.z("..."); + parseStaticCall(name); + } + } + } else { + // example: s1.execute() + read("."); + String methodName = readToken(); + parseCall(name, o, methodName); + } + return stat; + } + + private void read() { + while (line.charAt(pos) == ' ') { + pos++; + } + int start = pos; + char ch = line.charAt(pos); + switch (ch) { + case '\"': + tokenType = STRING; + pos++; + while (pos < line.length()) { + ch = line.charAt(pos); + if (ch == '\\') { + pos += 2; + } else if (ch == '\"') { + pos++; + break; + } else { + pos++; + } + } + break; + case '.': + case ',': + case '(': + case ')': + case ';': + case '{': + case '}': + case '[': + case ']': + case '=': + tokenType = SPECIAL; + pos++; + break; + default: + if (Character.isLetter(ch) || ch == '_') { + tokenType = NAME; + pos++; + while (true) { + ch = line.charAt(pos); + if (Character.isLetterOrDigit(ch) || ch == '_') { + pos++; + } else { + break; + } + } + } else if (ch == '-' || Character.isDigit(ch)) { + tokenType = NUMBER; + pos++; + while (true) { + ch = line.charAt(pos); + if (Character.isDigit(ch) + || ".+-eElLxabcdefABCDEF".indexOf(ch) >= 0) { + pos++; + } else { + break; + } + } + } + } + token = line.substring(start, pos); + } + + private boolean readIf(String s) { + if (token.equals(s)) { + read(); + return true; + } + return false; + } + + private String readToken() { + String s = token; + read(); + return s; + } + + private void read(String s) { + if (!readIf(s)) { + throw new RuntimeException("Expected: " + s + " got: " + token + " in " + + line); + } + } + + private Arg parseValue() { + if (tokenType == STRING) { + String s = readToken(); + s = StringUtils.javaDecode(s.substring(1, s.length() - 1)); + return new Arg(String.class, s); + } else if (tokenType == NUMBER) { + String number = readToken().toLowerCase(); + if (number.endsWith("f")) { + Float v = Float.parseFloat(number); + return new Arg(float.class, v); + } else if (number.endsWith("d") || + number.indexOf('e') >= 0 || + number.indexOf('.') >= 0) { + Double v = Double.parseDouble(number); + return new Arg(double.class, v); + } else if (number.endsWith("l")) { + Long v = Long.parseLong(number.substring(0, number.length() - 1)); + return new Arg(long.class, v); + } else { + Integer v = Integer.parseInt(number); + return new Arg(int.class, v); + } + } else if (tokenType == NAME) { + if (readIf("true")) { + return new Arg(boolean.class, Boolean.TRUE); + } else if (readIf("false")) { + return new Arg(boolean.class, Boolean.FALSE); + } else if (readIf("null")) { + throw new RuntimeException( + "Null: class not specified. Example: (java.lang.String)null"); + } else if (readIf("new")) { + if (readIf("String")) { + read("["); + read("]"); + read("{"); + ArrayList values = new ArrayList<>(); + do { + values.add(parseValue().getValue()); + } while (readIf(",")); + read("}"); + String[] list = values.toArray(new String[0]); + return new Arg(String[].class, list); + } else if (readIf("BigDecimal")) { + read("("); + BigDecimal value = new BigDecimal((String) parseValue().getValue()); + read(")"); + return new Arg(BigDecimal.class, value); + } else { + throw new RuntimeException("Unsupported constructor: " + readToken()); + } + } + String name = readToken(); + Object obj = player.getObject(name); + if (obj != null) { + return new Arg(obj.getClass(), obj); + } + read("."); + Statement outer = stat; + stat = new Statement(player); + parseStaticCall(name); + Arg s = new Arg(stat); + stat = outer; + return s; + } else if (readIf("(")) { + read("short"); + read(")"); + String number = readToken(); + return new Arg(short.class, Short.parseShort(number)); + } else { + throw new RuntimeException("Value expected, got: " + readToken() + " in " + + line); + } + } + + private void parseCall(String objectName, Object o, String methodName) { + stat.setMethodCall(objectName, o, methodName); + ArrayList args = new ArrayList<>(); + read("("); + while (true) { + if (readIf(")")) { + break; + } + Arg p = parseValue(); + args.add(p); + if (readIf(")")) { + break; + } + read(","); + } + stat.setArgs(args); + } + + private void parseStaticCall(String clazz) { + String last = readToken(); + while (readIf(".")) { + clazz += last == null ? "" : "." + last; + last = readToken(); + } + String methodName = last; + stat.setStaticCall(clazz); + parseCall(null, null, methodName); + } + +} diff --git a/h2/src/test/org/h2/test/trace/Player.java b/h2/src/test/org/h2/test/trace/Player.java new file mode 100644 index 0000000..cf0a750 --- /dev/null +++ b/h2/src/test/org/h2/test/trace/Player.java @@ -0,0 +1,179 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + */ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.h2.test.trace; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.util.HashMap; +import org.h2.store.fs.FileUtils; + +/** + * This tool can re-run Java style log files. There is no size limit. + */ +public class Player { + + // TODO support InputStream; + // TODO support Reader; + // TODO support int[]; + // TODO support Blob and Clob; + // TODO support Calendar + // TODO support Object + // TODO support Object[] + // TODO support URL + // TODO support Array + // TODO support Ref + // TODO support SQLInput, SQLOutput + // TODO support Properties + // TODO support Map + // TODO support SQLXML + + private static final String[] IMPORTED_PACKAGES = { + "", "java.lang.", "java.sql.", "javax.sql." }; + private boolean trace; + private final HashMap objects = new HashMap<>(); + + /** + * Execute a trace file using the command line. The log file name to execute + * (replayed) must be specified as the last parameter. The following + * optional command line parameters are supported: + *
      + *
    • -log to enable logging the executed statement to + * System.out + *
    + * + * @param args the arguments of the application + */ + public static void main(String... args) throws IOException { + new Player().run(args); + } + + /** + * Execute a trace file. + * + * @param fileName the file name + * @param trace print debug information + */ + public static void execute(String fileName, boolean trace) throws IOException { + Player player = new Player(); + player.trace = trace; + player.runFile(fileName); + } + + private void run(String... args) throws IOException { + String fileName = "test.log.db"; + try { + fileName = args[args.length - 1]; + for (int i = 0; i < args.length - 1; i++) { + if ("-trace".equals(args[i])) { + trace = true; + } else { + throw new RuntimeException("Unknown setting: " + args[i]); + } + } + } catch (Exception e) { + e.printStackTrace(); + System.out.println("Usage: java " + getClass().getName() + + " [-trace] "); + return; + } + runFile(fileName); + } + + private void runFile(String fileName) throws IOException { + LineNumberReader reader = new LineNumberReader(new BufferedReader( + new InputStreamReader(FileUtils.newInputStream(fileName)))); + while (true) { + String line = reader.readLine(); + if (line == null) { + break; + } + runLine(line.trim()); + } + reader.close(); + } + + /** + * Write trace information if trace is enabled. + * + * @param s the message to write + */ + void trace(String s) { + if (trace) { + System.out.println(s); + } + } + + private void runLine(String line) { + if (!line.startsWith("/**/")) { + return; + } + line = line.substring("/**/".length()) + ";"; + Statement s = Parser.parseStatement(this, line); + trace("> " + s.toString()); + try { + s.execute(); + } catch (Exception e) { + e.printStackTrace(); + trace("error: " + e.toString()); + } + } + + /** + * Get the class for the given class name. + * Only a limited set of classes is supported. + * + * @param className the class name + * @return the class + */ + static Class getClass(String className) { + for (String s : IMPORTED_PACKAGES) { + try { + return Class.forName(s + className); + } catch (ClassNotFoundException e) { + // ignore + } + } + throw new RuntimeException("Class not found: " + className); + } + + /** + * Assign an object to a variable. + * + * @param variableName the variable name + * @param obj the object + */ + void assign(String variableName, Object obj) { + objects.put(variableName, obj); + } + + /** + * Get an object. + * + * @param name the variable name + * @return the object + */ + Object getObject(String name) { + return objects.get(name); + } + +} diff --git a/h2/src/test/org/h2/test/trace/Statement.java b/h2/src/test/org/h2/test/trace/Statement.java new file mode 100644 index 0000000..6fcca9d --- /dev/null +++ b/h2/src/test/org/h2/test/trace/Statement.java @@ -0,0 +1,165 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + */ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.h2.test.trace; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; + +/** + * A statement in a Java-style log file. + */ +class Statement { + private final Player player; + private boolean assignment; + private boolean staticCall; + private String assignClass; + private String assignVariable; + private String staticCallClass; + private String objectName; + private Object object; + private String methodName; + private Arg[] args; + private Class returnClass; + + Statement(Player player) { + this.player = player; + } + + /** + * Execute the statement. + * + * @return the object returned if this was a method call + */ + Object execute() throws Exception { + if (object == player) { + // there was an exception previously + player.trace("> " + assignVariable + " not set"); + if (assignment) { + player.assign(assignVariable, player); + } + return null; + } + Class clazz; + if (staticCall) { + clazz = Player.getClass(staticCallClass); + } else { + clazz = object.getClass(); + } + Class[] parameterTypes = new Class[args.length]; + Object[] parameters = new Object[args.length]; + for (int i = 0; i < args.length; i++) { + Arg arg = args[i]; + arg.execute(); + parameterTypes[i] = arg.getValueClass(); + parameters[i] = arg.getValue(); + } + Method method = clazz.getMethod(methodName, parameterTypes); + returnClass = method.getReturnType(); + try { + Object obj = method.invoke(object, parameters); + if (assignment) { + player.assign(assignVariable, obj); + } + return obj; + } catch (IllegalArgumentException | IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + Throwable t = e.getTargetException(); + player.trace("> " + t.toString()); + if (assignment) { + player.assign(assignVariable, player); + } + } + return null; + } + + @Override + public String toString() { + StringBuilder buff = new StringBuilder(); + if (assignment) { + buff.append(assignClass); + buff.append(' '); + buff.append(assignVariable); + buff.append('='); + } + if (staticCall) { + buff.append(staticCallClass); + } else { + buff.append(objectName); + } + buff.append('.'); + buff.append(methodName); + buff.append('('); + for (int i = 0; args != null && i < args.length; i++) { + if (i > 0) { + buff.append(", "); + } + buff.append(args[i].toString()); + } + buff.append(");"); + return buff.toString(); + } + + Class getReturnClass() { + return returnClass; + } + + /** + * This statement is an assignment. + * + * @param className the class of the variable + * @param variableName the variable name + */ + void setAssign(String className, String variableName) { + this.assignment = true; + this.assignClass = className; + this.assignVariable = variableName; + } + + /** + * This statement is a static method call. + * + * @param className the class name + */ + void setStaticCall(String className) { + this.staticCall = true; + this.staticCallClass = className; + } + + /** + * This statement is a method call, and the result is assigned to a + * variable. + * + * @param variableName the variable name + * @param object the object + * @param methodName the method name + */ + void setMethodCall(String variableName, Object object, String methodName) { + this.objectName = variableName; + this.object = object; + this.methodName = methodName; + } + + public void setArgs(ArrayList list) { + args = list.toArray(new Arg[0]); + } +} diff --git a/h2/src/test/org/h2/test/trace/package.html b/h2/src/test/org/h2/test/trace/package.html new file mode 100644 index 0000000..5b4b294 --- /dev/null +++ b/h2/src/test/org/h2/test/trace/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +A player to interpret and execute Java statements in a trace file. + +

    \ No newline at end of file diff --git a/h2/src/test/org/h2/test/unit/TestAnsCompression.java b/h2/src/test/org/h2/test/unit/TestAnsCompression.java new file mode 100644 index 0000000..32daf07 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestAnsCompression.java @@ -0,0 +1,110 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.Random; + +import org.h2.dev.util.AnsCompression; +import org.h2.dev.util.BinaryArithmeticStream; +import org.h2.dev.util.BitStream; +import org.h2.test.TestBase; + +/** + * Tests the ANS (Asymmetric Numeral Systems) compression tool. + */ +public class TestAnsCompression extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testScaleFrequencies(); + testRandomized(); + testCompressionRate(); + } + + private void testCompressionRate() throws IOException { + byte[] data = new byte[1024 * 1024]; + Random r = new Random(1); + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (r.nextInt(4) * r.nextInt(4)); + } + int[] freq = new int[256]; + AnsCompression.countFrequencies(freq, data); + int lenAns = AnsCompression.encode(freq, data).length; + BitStream.Huffman huff = new BitStream.Huffman(freq); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + BitStream.Out o = new BitStream.Out(out); + for (byte x : data) { + huff.write(o, x & 255); + } + o.flush(); + int lenHuff = out.toByteArray().length; + BinaryArithmeticStream.Huffman aHuff = new BinaryArithmeticStream.Huffman( + freq); + out = new ByteArrayOutputStream(); + BinaryArithmeticStream.Out o2 = new BinaryArithmeticStream.Out(out); + for (byte x : data) { + aHuff.write(o2, x & 255); + } + o2.flush(); + int lenArithmetic = out.toByteArray().length; + + assertTrue(lenAns < lenArithmetic); + assertTrue(lenArithmetic < lenHuff); + assertTrue(lenHuff < data.length); + } + + private void testScaleFrequencies() { + Random r = new Random(1); + for (int j = 0; j < 100; j++) { + int symbolCount = r.nextInt(200) + 1; + int[] freq = new int[symbolCount]; + for (int total = symbolCount * 2; total < 10000; total *= 2) { + for (int i = 0; i < freq.length; i++) { + freq[i] = r.nextInt(1000) + 1; + } + AnsCompression.scaleFrequencies(freq, total); + } + } + int[] freq = new int[]{0, 1, 1, 1000}; + AnsCompression.scaleFrequencies(freq, 100); + assertEquals("[0, 1, 1, 98]", Arrays.toString(freq)); + } + + private void testRandomized() { + Random r = new Random(1); + int symbolCount = r.nextInt(200) + 1; + int[] freq = new int[symbolCount]; + for (int i = 0; i < freq.length; i++) { + freq[i] = r.nextInt(1000) + 1; + } + int seed = r.nextInt(); + r.setSeed(seed); + int len = 10000; + byte[] data = new byte[len]; + r.nextBytes(data); + freq = new int[256]; + AnsCompression.countFrequencies(freq, data); + byte[] encoded = AnsCompression.encode(freq, data); + byte[] decoded = AnsCompression.decode(freq, encoded, data.length); + for (int i = 0; i < len; i++) { + int expected = data[i]; + assertEquals(expected, decoded[i]); + } + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestAutoReconnect.java b/h2/src/test/org/h2/test/unit/TestAutoReconnect.java new file mode 100644 index 0000000..e275d3e --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestAutoReconnect.java @@ -0,0 +1,189 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import org.h2.api.DatabaseEventListener; +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.Server; + +/** + * Tests automatic embedded/server mode. + */ +public class TestAutoReconnect extends TestDb { + + private String url; + private boolean autoServer; + private Server server; + private Connection connServer; + private Connection conn; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + private void restart() throws SQLException, InterruptedException { + if (autoServer) { + if (connServer != null) { + connServer.createStatement().execute("SHUTDOWN"); + connServer.close(); + } + org.h2.Driver.load(); + connServer = getConnection(url); + } else { + server.stop(); + Thread.sleep(100); // try to prevent "port may be in use" error + server.start(); + } + } + + @Override + public void test() throws Exception { + testWrongUrl(); + autoServer = true; + testReconnect(); + autoServer = false; + testReconnect(); + deleteDb(getTestName()); + } + + private void testWrongUrl() throws Exception { + deleteDb(getTestName()); + Server tcp = Server.createTcpServer().start(); + try { + conn = getConnection("jdbc:h2:" + getBaseDir() + '/' + getTestName() + ";AUTO_SERVER=TRUE"); + assertThrows(ErrorCode.DATABASE_ALREADY_OPEN_1, + () -> getConnection("jdbc:h2:" + getBaseDir() + '/' + getTestName() + ";OPEN_NEW=TRUE")); + assertThrows(ErrorCode.DATABASE_ALREADY_OPEN_1, + () -> getConnection("jdbc:h2:" + getBaseDir() + '/' + getTestName() + ";OPEN_NEW=TRUE")); + conn.close(); + + conn = getConnection("jdbc:h2:tcp://localhost:" + tcp.getPort() + '/' + getBaseDir() + '/' // + + getTestName()); + assertThrows(ErrorCode.DATABASE_ALREADY_OPEN_1, () -> getConnection( + "jdbc:h2:" + getBaseDir() + '/' + getTestName() + ";AUTO_SERVER=TRUE;OPEN_NEW=TRUE")); + conn.close(); + } finally { + tcp.stop(); + } + } + + private void testReconnect() throws Exception { + deleteDb(getTestName()); + if (autoServer) { + url = "jdbc:h2:" + getBaseDir() + "/" + getTestName() + ";" + + "FILE_LOCK=SOCKET;" + + "AUTO_SERVER=TRUE;OPEN_NEW=TRUE"; + restart(); + } else { + server = Server.createTcpServer("-ifNotExists").start(); + int port = server.getPort(); + url = "jdbc:h2:tcp://localhost:" + port + "/" + getBaseDir() + "/" + getTestName() + ";" + + "FILE_LOCK=SOCKET;AUTO_RECONNECT=TRUE"; + } + + // test the database event listener + conn = getConnection(url + ";DATABASE_EVENT_LISTENER='" + + MyDatabaseEventListener.class.getName() + "'"); + conn.close(); + + Statement stat; + + conn = getConnection(url); + restart(); + stat = conn.createStatement(); + restart(); + stat.execute("create table test(id identity, name varchar)"); + restart(); + PreparedStatement prep = conn.prepareStatement( + "insert into test(name) values(?)"); + restart(); + prep.setString(1, "Hello"); + restart(); + prep.execute(); + restart(); + prep.setString(1, "World"); + restart(); + prep.execute(); + restart(); + ResultSet rs = stat.executeQuery("select * from test order by id"); + restart(); + assertTrue(rs.next()); + restart(); + assertEquals(1, rs.getInt(1)); + restart(); + assertEquals("Hello", rs.getString(2)); + restart(); + assertTrue(rs.next()); + restart(); + assertEquals(2, rs.getInt(1)); + restart(); + assertEquals("World", rs.getString(2)); + restart(); + assertFalse(rs.next()); + restart(); + stat.execute("SET @TEST 10"); + restart(); + rs = stat.executeQuery("CALL @TEST"); + rs.next(); + assertEquals(10, rs.getInt(1)); + stat.setFetchSize(10); + restart(); + rs = stat.executeQuery("select * from system_range(1, 20)"); + restart(); + for (int i = 0;; i++) { + try { + boolean more = rs.next(); + if (!more) { + assertEquals(i, 20); + break; + } + restart(); + int x = rs.getInt(1); + assertEquals(x, i + 1); + if (i > 10) { + fail(); + } + } catch (SQLException e) { + if (i < 10) { + throw e; + } + break; + } + } + restart(); + rs.close(); + + conn.setAutoCommit(false); + restart(); + assertThrows(ErrorCode.CONNECTION_BROKEN_1, conn.createStatement()). + execute("select * from test"); + + conn.close(); + if (autoServer) { + connServer.close(); + } else { + server.stop(); + } + } + + /** + * A database event listener used in this test. + */ + public static final class MyDatabaseEventListener implements DatabaseEventListener { + } +} diff --git a/h2/src/test/org/h2/test/unit/TestBinaryArithmeticStream.java b/h2/src/test/org/h2/test/unit/TestBinaryArithmeticStream.java new file mode 100644 index 0000000..173691d --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestBinaryArithmeticStream.java @@ -0,0 +1,172 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Random; + +import org.h2.dev.util.BinaryArithmeticStream; +import org.h2.dev.util.BinaryArithmeticStream.Huffman; +import org.h2.dev.util.BinaryArithmeticStream.In; +import org.h2.dev.util.BinaryArithmeticStream.Out; +import org.h2.dev.util.BitStream; +import org.h2.test.TestBase; + +/** + * Test the binary arithmetic stream utility. + */ +public class TestBinaryArithmeticStream extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testCompareWithHuffman(); + testHuffmanRandomized(); + testCompressionRatio(); + testRandomized(); + testPerformance(); + } + + private void testCompareWithHuffman() throws IOException { + Random r = new Random(1); + for (int test = 0; test < 10; test++) { + int[] freq = new int[4]; + for (int i = 0; i < freq.length; i++) { + freq[i] = 0 + r.nextInt(1000); + } + BinaryArithmeticStream.Huffman ah = new BinaryArithmeticStream.Huffman( + freq); + BitStream.Huffman hh = new BitStream.Huffman(freq); + ByteArrayOutputStream hbOut = new ByteArrayOutputStream(); + ByteArrayOutputStream abOut = new ByteArrayOutputStream(); + BitStream.Out bOut = new BitStream.Out(hbOut); + BinaryArithmeticStream.Out aOut = new BinaryArithmeticStream.Out(abOut); + for (int i = 0; i < freq.length; i++) { + for (int j = 0; j < freq[i]; j++) { + int x = i; + hh.write(bOut, x); + ah.write(aOut, x); + } + } + assertTrue(hbOut.toByteArray().length >= abOut.toByteArray().length); + } + } + + private void testHuffmanRandomized() throws IOException { + Random r = new Random(1); + int[] freq = new int[r.nextInt(200) + 1]; + for (int i = 0; i < freq.length; i++) { + freq[i] = r.nextInt(1000) + 1; + } + int seed = r.nextInt(); + r.setSeed(seed); + Huffman huff = new Huffman(freq); + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + Out out = new Out(byteOut); + for (int i = 0; i < 10000; i++) { + huff.write(out, r.nextInt(freq.length)); + } + out.flush(); + In in = new In(new ByteArrayInputStream(byteOut.toByteArray())); + r.setSeed(seed); + for (int i = 0; i < 10000; i++) { + int expected = r.nextInt(freq.length); + int got = huff.read(in); + assertEquals(expected, got); + } + } + + private void testPerformance() throws IOException { + Random r = new Random(); + // long time = System.nanoTime(); + // Profiler prof = new Profiler().startCollecting(); + for (int seed = 0; seed < 10000; seed++) { + r.setSeed(seed); + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + Out out = new Out(byteOut); + int len = 100; + for (int i = 0; i < len; i++) { + boolean v = r.nextBoolean(); + int prob = r.nextInt(BinaryArithmeticStream.MAX_PROBABILITY); + out.writeBit(v, prob); + } + out.flush(); + r.setSeed(seed); + ByteArrayInputStream byteIn = new ByteArrayInputStream( + byteOut.toByteArray()); + In in = new In(byteIn); + for (int i = 0; i < len; i++) { + boolean expected = r.nextBoolean(); + int prob = r.nextInt(BinaryArithmeticStream.MAX_PROBABILITY); + assertEquals(expected, in.readBit(prob)); + } + } + // time = System.nanoTime() - time; + // System.out.println("time: " + TimeUnit.NANOSECONDS.toMillis(time)); + // System.out.println(prof.getTop(5)); + } + + private void testCompressionRatio() throws IOException { + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + Out out = new Out(byteOut); + int prob = 1000; + int len = 1024; + for (int i = 0; i < len; i++) { + out.writeBit(true, prob); + } + out.flush(); + ByteArrayInputStream byteIn = new ByteArrayInputStream( + byteOut.toByteArray()); + In in = new In(byteIn); + for (int i = 0; i < len; i++) { + assertTrue(in.readBit(prob)); + } + // System.out.println(len / 8 + " comp: " + + // byteOut.toByteArray().length); + } + + private void testRandomized() throws IOException { + for (int i = 0; i < 10000; i = (int) ((i + 10) * 1.1)) { + testRandomized(i); + } + } + + private void testRandomized(int len) throws IOException { + Random r = new Random(); + int seed = r.nextInt(); + r.setSeed(seed); + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + Out out = new Out(byteOut); + for (int i = 0; i < len; i++) { + int prob = r.nextInt(BinaryArithmeticStream.MAX_PROBABILITY); + out.writeBit(r.nextBoolean(), prob); + } + out.flush(); + byteOut.write(r.nextInt(255)); + ByteArrayInputStream byteIn = new ByteArrayInputStream( + byteOut.toByteArray()); + In in = new In(byteIn); + r.setSeed(seed); + for (int i = 0; i < len; i++) { + int prob = r.nextInt(BinaryArithmeticStream.MAX_PROBABILITY); + boolean expected = r.nextBoolean(); + boolean got = in.readBit(prob); + assertEquals(expected, got); + } + assertEquals(r.nextInt(255), byteIn.read()); + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestBinaryOperation.java b/h2/src/test/org/h2/test/unit/TestBinaryOperation.java new file mode 100644 index 0000000..606d728 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestBinaryOperation.java @@ -0,0 +1,109 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import org.h2.engine.SessionLocal; +import org.h2.expression.BinaryOperation; +import org.h2.expression.ExpressionVisitor; +import org.h2.expression.Operation0; +import org.h2.message.DbException; +import org.h2.test.TestBase; +import org.h2.value.TypeInfo; +import org.h2.value.Value; + +/** + * Test the binary operation. + */ +public class TestBinaryOperation extends TestBase { + + /** + * Run just this test. + * + * @param a + * ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testPlusMinus(BinaryOperation.OpType.PLUS); + testPlusMinus(BinaryOperation.OpType.MINUS); + testMultiply(); + testDivide(); + } + + private void testPlusMinus(BinaryOperation.OpType type) { + assertPrecisionScale(2, 0, 2, type, 1, 0, 1, 0); + assertPrecisionScale(3, 1, 2, type, 1, 1, 1, 0); + assertPrecisionScale(3, 1, 2, type, 1, 0, 1, 1); + } + + private void testMultiply() { + assertPrecisionScale(2, 0, 2, BinaryOperation.OpType.MULTIPLY, 1, 0, 1, 0); + assertPrecisionScale(2, 1, 2, BinaryOperation.OpType.MULTIPLY, 1, 1, 1, 0); + assertPrecisionScale(2, 1, 2, BinaryOperation.OpType.MULTIPLY, 1, 0, 1, 1); + } + + private void testDivide() { + assertPrecisionScale(3, 2, 2, BinaryOperation.OpType.DIVIDE, 1, 0, 1, 0); + assertPrecisionScale(3, 3, 2, BinaryOperation.OpType.DIVIDE, 1, 1, 1, 0); + assertPrecisionScale(3, 1, 2, BinaryOperation.OpType.DIVIDE, 1, 0, 1, 1); + assertPrecisionScale(25, 0, 10, BinaryOperation.OpType.DIVIDE, 1, 3, 9, 27); + } + + private void assertPrecisionScale(int expectedPrecision, int expectedScale, int expectedDecfloatPrecision, + BinaryOperation.OpType type, int precision1, int scale1, int precision2, int scale2) { + TestExpression left = new TestExpression(TypeInfo.getTypeInfo(Value.NUMERIC, precision1, scale1, null)); + TestExpression right = new TestExpression(TypeInfo.getTypeInfo(Value.NUMERIC, precision2, scale2, null)); + TypeInfo typeInfo = new BinaryOperation(type, left, right).optimize(null).getType(); + assertEquals(Value.NUMERIC, typeInfo.getValueType()); + assertEquals(expectedPrecision, typeInfo.getPrecision()); + assertEquals(expectedScale, typeInfo.getScale()); + left = new TestExpression(TypeInfo.getTypeInfo(Value.DECFLOAT, precision1, 0, null)); + right = new TestExpression(TypeInfo.getTypeInfo(Value.DECFLOAT, precision2, 0, null)); + typeInfo = new BinaryOperation(type, left, right).optimize(null).getType(); + assertEquals(Value.DECFLOAT, typeInfo.getValueType()); + assertEquals(expectedDecfloatPrecision, typeInfo.getPrecision()); + } + + private static final class TestExpression extends Operation0 { + + private final TypeInfo type; + + TestExpression(TypeInfo type) { + this.type = type; + } + + @Override + public Value getValue(SessionLocal session) { + throw DbException.getUnsupportedException(""); + } + + @Override + public TypeInfo getType() { + return type; + } + + @Override + public StringBuilder getUnenclosedSQL(StringBuilder builder, int sqlFlags) { + throw DbException.getUnsupportedException(""); + } + + @Override + public boolean isEverything(ExpressionVisitor visitor) { + return false; + } + + @Override + public int getCost() { + return 0; + } + + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestBitStream.java b/h2/src/test/org/h2/test/unit/TestBitStream.java new file mode 100644 index 0000000..dd53cc5 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestBitStream.java @@ -0,0 +1,160 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.Random; + +import org.h2.dev.util.BitStream; +import org.h2.dev.util.BitStream.In; +import org.h2.dev.util.BitStream.Out; +import org.h2.test.TestBase; + +/** + * Test the bit stream (Golomb code and Huffman code) utility. + */ +public class TestBitStream extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testHuffmanRandomized(); + testHuffman(); + testBitStream(); + testGolomb("11110010", 10, 42); + testGolomb("00", 3, 0); + testGolomb("010", 3, 1); + testGolomb("011", 3, 2); + testGolomb("100", 3, 3); + testGolomb("1010", 3, 4); + testGolombRandomized(); + } + + private void testHuffmanRandomized() { + Random r = new Random(1); + int[] freq = new int[r.nextInt(200) + 1]; + for (int i = 0; i < freq.length; i++) { + freq[i] = r.nextInt(1000) + 1; + } + int seed = r.nextInt(); + r.setSeed(seed); + BitStream.Huffman huff = new BitStream.Huffman(freq); + ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); + BitStream.Out out = new BitStream.Out(byteOut); + for (int i = 0; i < 10000; i++) { + huff.write(out, r.nextInt(freq.length)); + } + out.close(); + BitStream.In in = new BitStream.In(new ByteArrayInputStream(byteOut.toByteArray())); + r.setSeed(seed); + for (int i = 0; i < 10000; i++) { + int expected = r.nextInt(freq.length); + assertEquals(expected, huff.read(in)); + } + } + + private void testHuffman() { + int[] freq = { 36, 18, 12, 9, 7, 6, 5, 4 }; + BitStream.Huffman huff = new BitStream.Huffman(freq); + final StringBuilder buff = new StringBuilder(); + Out o = new Out(null) { + @Override + public void writeBit(int bit) { + buff.append(bit == 0 ? '0' : '1'); + } + }; + for (int i = 0; i < freq.length; i++) { + buff.append(i + ": "); + huff.write(o, i); + buff.append("\n"); + } + assertEquals( + "0: 0\n" + + "1: 110\n" + + "2: 100\n" + + "3: 1110\n" + + "4: 1011\n" + + "5: 1010\n" + + "6: 11111\n" + + "7: 11110\n", buff.toString()); + } + + private void testGolomb(String expected, int div, int value) { + final StringBuilder buff = new StringBuilder(); + Out o = new Out(null) { + @Override + public void writeBit(int bit) { + buff.append(bit == 0 ? '0' : '1'); + } + }; + o.writeGolomb(div, value); + int size = Out.getGolombSize(div, value); + String got = buff.toString(); + assertEquals(size, got.length()); + assertEquals(expected, got); + } + + private void testGolombRandomized() { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Out bitOut = new Out(out); + Random r = new Random(1); + int len = 1000; + for (int i = 0; i < len; i++) { + int div = r.nextInt(100) + 1; + int value = r.nextInt(1000000); + bitOut.writeGolomb(div, value); + } + bitOut.flush(); + bitOut.close(); + byte[] data = out.toByteArray(); + ByteArrayInputStream in = new ByteArrayInputStream(data); + In bitIn = new In(in); + r.setSeed(1); + for (int i = 0; i < len; i++) { + int div = r.nextInt(100) + 1; + int value = r.nextInt(1000000); + int v = bitIn.readGolomb(div); + assertEquals("i=" + i + " div=" + div, value, v); + } + } + + private void testBitStream() { + Random r = new Random(); + for (int test = 0; test < 10000; test++) { + ByteArrayOutputStream buff = new ByteArrayOutputStream(); + int len = r.nextInt(40); + Out out = new Out(buff); + long seed = r.nextLong(); + Random r2 = new Random(seed); + for (int i = 0; i < len; i++) { + out.writeBit(r2.nextBoolean() ? 1 : 0); + } + out.close(); + In in = new In(new ByteArrayInputStream( + buff.toByteArray())); + r2 = new Random(seed); + int i = 0; + for (; i < len; i++) { + int expected = r2.nextBoolean() ? 1 : 0; + assertEquals(expected, in.readBit()); + } + for (; i % 8 != 0; i++) { + assertEquals(0, in.readBit()); + } + assertEquals(-1, in.readBit()); + } + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestBnf.java b/h2/src/test/org/h2/test/unit/TestBnf.java new file mode 100644 index 0000000..71f9113 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestBnf.java @@ -0,0 +1,177 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.sql.Connection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.h2.bnf.Bnf; +import org.h2.bnf.context.DbContents; +import org.h2.bnf.context.DbContextRule; +import org.h2.bnf.context.DbProcedure; +import org.h2.bnf.context.DbSchema; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Test Bnf Sql parser + * @author Nicolas Fortin + */ +public class TestBnf extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb("bnf"); + try (Connection conn = getConnection("bnf")) { + testModes(conn); + testProcedures(conn, false); + } + deleteDb("bnf"); + try (Connection conn = getConnection("bnf;mode=mysql;database_to_lower=true")) { + testProcedures(conn, true); + } + } + + private void testModes(Connection conn) throws Exception { + DbContents dbContents; + dbContents = new DbContents(); + dbContents.readContents("jdbc:h2:./test", conn); + assertTrue(dbContents.isH2()); + dbContents = new DbContents(); + dbContents.readContents("jdbc:derby:test", conn); + assertTrue(dbContents.isDerby()); + dbContents = new DbContents(); + dbContents.readContents("jdbc:firebirdsql:test", conn); + assertTrue(dbContents.isFirebird()); + dbContents = new DbContents(); + dbContents.readContents("jdbc:sqlserver:test", conn); + assertTrue(dbContents.isMSSQLServer()); + dbContents = new DbContents(); + dbContents.readContents("jdbc:mysql:test", conn); + assertTrue(dbContents.isMySQL()); + dbContents = new DbContents(); + dbContents.readContents("jdbc:oracle:test", conn); + assertTrue(dbContents.isOracle()); + dbContents = new DbContents(); + dbContents.readContents("jdbc:postgresql:test", conn); + assertTrue(dbContents.isPostgreSQL()); + dbContents = new DbContents(); + dbContents.readContents("jdbc:sqlite:test", conn); + assertTrue(dbContents.isSQLite()); + } + + private void testProcedures(Connection conn, boolean isMySQLMode) + throws Exception { + // Register a procedure and check if it is present in DbContents + conn.createStatement().execute( + "DROP ALIAS IF EXISTS CUSTOM_PRINT"); + conn.createStatement().execute( + "CREATE ALIAS CUSTOM_PRINT " + + "AS $$ void print(String s) { System.out.println(s); } $$"); + conn.createStatement().execute( + "DROP TABLE IF EXISTS " + + "TABLE_WITH_STRING_FIELD"); + conn.createStatement().execute( + "CREATE TABLE " + + "TABLE_WITH_STRING_FIELD (STRING_FIELD VARCHAR(50), INT_FIELD integer)"); + DbContents dbContents = new DbContents(); + dbContents.readContents("jdbc:h2:./test", conn); + assertTrue(dbContents.isH2()); + assertFalse(dbContents.isDerby()); + assertFalse(dbContents.isFirebird()); + assertEquals(null, dbContents.quoteIdentifier(null)); + if (isMySQLMode) { + assertEquals("\"TEST\"", dbContents.quoteIdentifier("TEST")); + assertEquals("\"Test\"", dbContents.quoteIdentifier("Test")); + assertEquals("test", dbContents.quoteIdentifier("test")); + } else { + assertEquals("TEST", dbContents.quoteIdentifier("TEST")); + assertEquals("\"Test\"", dbContents.quoteIdentifier("Test")); + assertEquals("\"test\"", dbContents.quoteIdentifier("test")); + } + assertFalse(dbContents.isMSSQLServer()); + assertFalse(dbContents.isMySQL()); + assertFalse(dbContents.isOracle()); + assertFalse(dbContents.isPostgreSQL()); + assertFalse(dbContents.isSQLite()); + DbSchema defaultSchema = dbContents.getDefaultSchema(); + DbProcedure[] procedures = defaultSchema.getProcedures(); + Set procedureName = new HashSet<>(procedures.length); + for (DbProcedure procedure : procedures) { + assertTrue(defaultSchema == procedure.getSchema()); + procedureName.add(procedure.getName()); + } + if (isMySQLMode) { + assertTrue(procedureName.contains("custom_print")); + } else { + assertTrue(procedureName.contains("CUSTOM_PRINT")); + } + + if (isMySQLMode) { + return; + } + + // Test completion + Bnf bnf = Bnf.getInstance(null); + DbContextRule columnRule = new + DbContextRule(dbContents, DbContextRule.COLUMN); + bnf.updateTopic("column_name", columnRule); + bnf.updateTopic("user_defined_function_name", new + DbContextRule(dbContents, DbContextRule.PROCEDURE)); + bnf.linkStatements(); + // Test partial + Map tokens; + tokens = bnf.getNextTokenList("SELECT CUSTOM_PR"); + assertTrue(tokens.values().contains("INT")); + + // Test identifiers are working + tokens = bnf.getNextTokenList("create table \"test\" as s" + "el"); + assertTrue(tokens.values().contains("E" + "CT")); + + tokens = bnf.getNextTokenList("create table test as s" + "el"); + assertTrue(tokens.values().contains("E" + "CT")); + + // Test || with and without spaces + tokens = bnf.getNextTokenList("select 1||f"); + assertFalse(tokens.values().contains("R" + "OM")); + tokens = bnf.getNextTokenList("select 1 || f"); + assertFalse(tokens.values().contains("R" + "OM")); + tokens = bnf.getNextTokenList("select 1 || 2 "); + assertTrue(tokens.values().contains("FROM")); + tokens = bnf.getNextTokenList("select 1||2"); + assertTrue(tokens.values().contains("FROM")); + tokens = bnf.getNextTokenList("select 1 || 2"); + assertTrue(tokens.values().contains("FROM")); + + // Test keyword + tokens = bnf.getNextTokenList("SELECT LE" + "AS"); + assertTrue(tokens.values().contains("T")); + + // Test parameters + tokens = bnf.getNextTokenList("SELECT CUSTOM_PRINT("); + assertTrue(tokens.values().contains("STRING_FIELD")); + assertFalse(tokens.values().contains("INT_FIELD")); + + // Test parameters with spaces + tokens = bnf.getNextTokenList("SELECT CUSTOM_PRINT ( "); + assertTrue(tokens.values().contains("STRING_FIELD")); + assertFalse(tokens.values().contains("INT_FIELD")); + + // Test parameters with close bracket + tokens = bnf.getNextTokenList("SELECT CUSTOM_PRINT ( STRING_FIELD"); + assertTrue(tokens.values().contains(")")); + } +} \ No newline at end of file diff --git a/h2/src/test/org/h2/test/unit/TestCache.java b/h2/src/test/org/h2/test/unit/TestCache.java new file mode 100644 index 0000000..4f71f0d --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestCache.java @@ -0,0 +1,202 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Random; +import org.h2.message.Trace; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.Cache; +import org.h2.util.CacheLRU; +import org.h2.util.CacheObject; +import org.h2.util.CacheWriter; +import org.h2.util.StringUtils; +import org.h2.util.Utils; +import org.h2.value.Value; + +/** + * Tests the cache. + */ +public class TestCache extends TestDb implements CacheWriter { + + private String out; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); +// test.config.traceTest = true; + test.testFromMain(); + } + + @Override + public void test() throws Exception { + testMemoryUsage(); + testCache(); + testCacheDb(false); + testCacheDb(true); + } + + private void testMemoryUsage() throws SQLException { + if (!config.traceTest) { + return; + } + if (config.memory) { + return; + } + deleteDb("cache"); + Connection conn; + Statement stat; + ResultSet rs; + conn = getConnection("cache;CACHE_SIZE=16384"); + stat = conn.createStatement(); + // test DataOverflow + stat.execute("create table test(id int primary key, data varchar)"); + stat.execute("set max_memory_undo 10000"); + conn.close(); + stat = null; + conn = null; + long before = getRealMemory(); + + conn = getConnection("cache;CACHE_SIZE=16384;DB_CLOSE_ON_EXIT=FALSE"); + stat = conn.createStatement(); + + // -XX:+HeapDumpOnOutOfMemoryError + + stat.execute( + "insert into test select x, random_uuid() || space(1) " + + "from system_range(1, 10000)"); + + // stat.execute("create index idx_test_n on test(data)"); + // stat.execute("select data from test where data >= ''"); + + rs = stat.executeQuery( + "SELECT SETTING_VALUE FROM INFORMATION_SCHEMA.SETTINGS WHERE SETTING_NAME = 'info.CACHE_SIZE'"); + rs.next(); + int calculated = rs.getInt(1); + rs = null; + long afterInsert = getRealMemory(); + + conn.close(); + stat = null; + conn = null; + long afterClose = getRealMemory(); + trace("Used memory: " + (afterInsert - afterClose) + + " calculated cache size: " + calculated); + trace("Before: " + before + " after: " + afterInsert + + " after closing: " + afterClose); + } + + private static long getRealMemory() { + StringUtils.clearCache(); + Value.clearCache(); + return Utils.getMemoryUsed(); + } + + private void testCache() { + out = ""; + Cache c = CacheLRU.getCache(this, "LRU", 16); + for (int i = 0; i < 20; i++) { + c.put(new Obj(i)); + } + assertEquals("flush 0 flush 1 flush 2 flush 3 ", out); + } + + /** + * A simple cache object + */ + static class Obj extends CacheObject { + + Obj(int pos) { + setPos(pos); + } + + @Override + public int getMemory() { + return 1024; + } + + @Override + public boolean canRemove() { + return true; + } + + @Override + public boolean isChanged() { + return true; + } + + @Override + public String toString() { + return "[" + getPos() + "]"; + } + + } + + @Override + public void flushLog() { + out += "flush "; + } + + @Override + public Trace getTrace() { + return null; + } + + @Override + public void writeBack(CacheObject entry) { + out += entry.getPos() + " "; + } + + private void testCacheDb(boolean lru) throws SQLException { + if (config.memory) { + return; + } + deleteDb("cache"); + Connection conn = getConnection( + "cache;CACHE_TYPE=" + (lru ? "LRU" : "SOFT_LRU")); + Statement stat = conn.createStatement(); + stat.execute("SET CACHE_SIZE 1024"); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + stat.execute("CREATE TABLE MAIN(ID INT PRIMARY KEY, NAME VARCHAR)"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST VALUES(?, ?)"); + PreparedStatement prep2 = conn.prepareStatement( + "INSERT INTO MAIN VALUES(?, ?)"); + int max = 10000; + for (int i = 0; i < max; i++) { + prep.setInt(1, i); + prep.setString(2, "Hello " + i); + prep.execute(); + prep2.setInt(1, i); + prep2.setString(2, "World " + i); + prep2.execute(); + } + conn.close(); + conn = getConnection("cache"); + stat = conn.createStatement(); + stat.execute("SET CACHE_SIZE 1024"); + Random random = new Random(1); + for (int i = 0; i < 100; i++) { + stat.executeQuery("SELECT * FROM MAIN WHERE ID BETWEEN 40 AND 50"); + stat.executeQuery("SELECT * FROM MAIN WHERE ID = " + random.nextInt(max)); + if ((i % 10) == 0) { + stat.executeQuery("SELECT * FROM TEST"); + } + } + conn.close(); + deleteDb("cache"); + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestCharsetCollator.java b/h2/src/test/org/h2/test/unit/TestCharsetCollator.java new file mode 100644 index 0000000..e1fb1d1 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestCharsetCollator.java @@ -0,0 +1,73 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.charset.UnsupportedCharsetException; +import java.text.Collator; +import org.h2.test.TestBase; +import org.h2.value.CharsetCollator; +import org.h2.value.CompareMode; + +/** + * Unittest for org.h2.value.CharsetCollator + */ +public class TestCharsetCollator extends TestBase { + private CharsetCollator cp500Collator = new CharsetCollator(Charset.forName("cp500")); + private CharsetCollator utf8Collator = new CharsetCollator(StandardCharsets.UTF_8); + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + + @Override + public void test() throws Exception { + testBasicComparison(); + testNumberToCharacterComparison(); + testLengthComparison(); + testCreationFromCompareMode(); + testCreationFromCompareModeWithInvalidCharset(); + testCaseInsensitive(); + } + + private void testCreationFromCompareModeWithInvalidCharset() { + assertThrows(UnsupportedCharsetException.class, () -> CompareMode.getCollator("CHARSET_INVALID")); + } + + private void testCreationFromCompareMode() { + Collator utf8Col = CompareMode.getCollator("CHARSET_UTF-8"); + assertTrue(utf8Col instanceof CharsetCollator); + assertEquals(((CharsetCollator) utf8Col).getCharset(), StandardCharsets.UTF_8); + } + + private void testBasicComparison() { + assertTrue(cp500Collator.compare("A", "B") < 0); + assertTrue(cp500Collator.compare("AA", "AB") < 0); + } + + private void testLengthComparison() { + assertTrue(utf8Collator.compare("AA", "A") > 0); + } + + private void testNumberToCharacterComparison() { + assertTrue(cp500Collator.compare("A", "1") < 0); + assertTrue(utf8Collator.compare("A", "1") > 0); + } + + private void testCaseInsensitive() { + CharsetCollator c = new CharsetCollator(StandardCharsets.UTF_8); + c.setStrength(Collator.SECONDARY); + assertEquals(0, c.compare("a", "A")); + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestClassLoaderLeak.java b/h2/src/test/org/h2/test/unit/TestClassLoaderLeak.java new file mode 100644 index 0000000..1a6b4f4 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestClassLoaderLeak.java @@ -0,0 +1,128 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.lang.ref.WeakReference; +import java.lang.reflect.Method; +import java.net.URLClassLoader; +import java.sql.Connection; +import java.sql.Driver; +import java.sql.DriverManager; +import java.util.ArrayList; + +import org.h2.test.TestBase; + +/** + * Test that static references within the database engine don't reference the + * class itself. For example, there is a leak if a class contains a static + * reference to a stack trace. This was the case using the following + * declaration: static EOFException EOF = new EOFException(). The way to solve + * the problem is to not use such references, or to not fill in the stack trace + * (which indirectly references the class loader). + * + * @author Erik Karlsson + * @author Thomas Mueller + */ +public class TestClassLoaderLeak extends TestBase { + + /** + * The name of this class (used by reflection). + */ + static final String CLASS_NAME = TestClassLoaderLeak.class.getName(); + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + WeakReference ref = createClassLoader(); + for (int i = 0; i < 10; i++) { + System.gc(); + Thread.sleep(10); + } + ClassLoader cl = ref.get(); + assertNull(cl); + // fill the memory, so a heap dump is created + // using -XX:+HeapDumpOnOutOfMemoryError + // which can be analyzed using EclipseMAT + // (check incoming references to TestClassLoader) + boolean fillMemory = false; + if (fillMemory) { + ArrayList memory = new ArrayList<>(); + for (int i = 0; i < Integer.MAX_VALUE; i++) { + memory.add(new byte[1024]); + } + } + DriverManager.registerDriver((Driver) + Class.forName("org.h2.Driver").getDeclaredConstructor().newInstance()); + DriverManager.registerDriver((Driver) + Class.forName("org.h2.upgrade.v1_1.Driver").getDeclaredConstructor().newInstance()); + } + + private static WeakReference createClassLoader() throws Exception { + ClassLoader cl = new TestClassLoader(); + Class h2ConnectionTestClass = Class.forName(CLASS_NAME, true, cl); + Method testMethod = h2ConnectionTestClass.getDeclaredMethod("runTest"); + testMethod.setAccessible(true); + testMethod.invoke(null); + return new WeakReference<>(cl); + } + + /** + * This method is called using reflection. + */ + static void runTest() throws Exception { + Class.forName("org.h2.Driver"); + Class.forName("org.h2.upgrade.v1_1.Driver"); + Driver d1 = DriverManager.getDriver("jdbc:h2:mem:test"); + Driver d2 = DriverManager.getDriver("jdbc:h2v1_1:mem:test"); + Connection connection; + connection = DriverManager.getConnection("jdbc:h2:mem:test"); + DriverManager.deregisterDriver(d1); + DriverManager.deregisterDriver(d2); + connection.close(); + connection = null; + } + + /** + * The application class loader. + */ + private static class TestClassLoader extends URLClassLoader { + + public TestClassLoader() { + super(((URLClassLoader) TestClassLoader.class.getClassLoader()) + .getURLs(), ClassLoader.getSystemClassLoader()); + } + + // allows delegation of H2 to the AppClassLoader + @Override + public synchronized Class loadClass(String name, boolean resolve) + throws ClassNotFoundException { + if (!name.contains(CLASS_NAME) && !name.startsWith("org.h2.")) { + return super.loadClass(name, resolve); + } + Class c = findLoadedClass(name); + if (c == null) { + try { + c = findClass(name); + } catch (SecurityException | ClassNotFoundException e) { + return super.loadClass(name, resolve); + } + if (resolve) { + resolveClass(c); + } + } + return c; + } + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestCollation.java b/h2/src/test/org/h2/test/unit/TestCollation.java new file mode 100644 index 0000000..7e0a9b1 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestCollation.java @@ -0,0 +1,53 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.sql.Connection; +import java.sql.Statement; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Test the ICU4J collator. + */ +public class TestCollation extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb("collation"); + Connection conn = getConnection("collation"); + Statement stat = conn.createStatement(); + assertThrows(ErrorCode.INVALID_VALUE_2, stat). + execute("set collation xyz"); + stat.execute("set collation en"); + stat.execute("set collation default_en"); + assertThrows(ErrorCode.CLASS_NOT_FOUND_1, stat). + execute("set collation icu4j_en"); + + stat.execute("set collation ge"); + stat.execute("create table test(id int)"); + // the same as the current - ok + stat.execute("set collation ge"); + // not allowed to change now + assertThrows(ErrorCode.COLLATION_CHANGE_WITH_DATA_TABLE_1, stat). + execute("set collation en"); + + conn.close(); + deleteDb("collation"); + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestCompress.java b/h2/src/test/org/h2/test/unit/TestCompress.java new file mode 100644 index 0000000..7aacdf6 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestCompress.java @@ -0,0 +1,329 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import org.h2.compress.CompressLZF; +import org.h2.compress.Compressor; +import org.h2.engine.Constants; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.CompressTool; +import org.h2.util.IOUtils; +import org.h2.util.Task; + +/** + * Data compression tests. + */ +public class TestCompress extends TestDb { + + private boolean testPerformance; + private final byte[] buff = new byte[10]; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + if (testPerformance) { + testDatabase(); + System.exit(0); + return; + } + testVariableSizeInt(); + testMultiThreaded(); + if (config.big) { + for (int i = 0; i < 100; i++) { + test(i); + } + for (int i = 100; i < 10000; i += i + i + 1) { + test(i); + } + } else { + test(0); + test(1); + test(7); + test(50); + test(200); + } + test(4000000); + testVariableEnd(); + } + + private void testVariableSizeInt() { + assertEquals(1, CompressTool.getVariableIntLength(0)); + assertEquals(2, CompressTool.getVariableIntLength(0x80)); + assertEquals(3, CompressTool.getVariableIntLength(0x4000)); + assertEquals(4, CompressTool.getVariableIntLength(0x200000)); + assertEquals(5, CompressTool.getVariableIntLength(0x10000000)); + assertEquals(5, CompressTool.getVariableIntLength(-1)); + for (int x = 0; x < 0x20000; x++) { + testVar(x); + testVar(Integer.MIN_VALUE + x); + testVar(Integer.MAX_VALUE - x); + testVar(0x200000 + x - 100); + testVar(0x10000000 + x - 100); + } + } + + private void testVar(int x) { + int len = CompressTool.getVariableIntLength(x); + int l2 = CompressTool.writeVariableInt(buff, 0, x); + assertEquals(len, l2); + int x2 = CompressTool.readVariableInt(buff, 0); + assertEquals(x2, x); + } + + private void testMultiThreaded() throws Exception { + Task[] tasks = new Task[3]; + for (int i = 0; i < tasks.length; i++) { + Task t = new Task() { + @Override + public void call() { + CompressTool tool = CompressTool.getInstance(); + byte[] b = new byte[1024]; + Random r = new Random(); + while (!stop) { + r.nextBytes(b); + byte[] test = tool.expand(tool.compress(b, "LZF")); + assertEquals(b, test); + } + } + }; + tasks[i] = t; + t.execute(); + } + Thread.sleep(1000); + for (Task t : tasks) { + t.get(); + } + } + + private void testVariableEnd() { + CompressTool utils = CompressTool.getInstance(); + StringBuilder b = new StringBuilder(); + for (int i = 0; i < 90; i++) { + b.append('0'); + } + String prefix = b.toString(); + for (int i = 0; i < 100; i++) { + b = new StringBuilder(prefix); + for (int j = 0; j < i; j++) { + b.append((char) ('1' + j)); + } + String test = b.toString(); + byte[] in = test.getBytes(); + assertEquals(in, utils.expand(utils.compress(in, "LZF"))); + } + } + + private void testDatabase() throws Exception { + deleteDb("memFS:compress"); + Connection conn = getConnection("memFS:compress"); + Statement stat = conn.createStatement(); + ResultSet rs; + rs = stat.executeQuery("select table_name from information_schema.tables"); + Statement stat2 = conn.createStatement(); + while (rs.next()) { + String table = rs.getString(1); + if (!"COLLATIONS".equals(table)) { + stat2.execute("create table " + table + + " as select * from information_schema." + table); + } + } + conn.close(); + Compressor compress = new CompressLZF(); + int pageSize = Constants.DEFAULT_PAGE_SIZE; + byte[] buff2 = new byte[pageSize]; + byte[] test = new byte[2 * pageSize]; + compress.compress(buff2, 0, pageSize, test, 0); + for (int j = 0; j < 4; j++) { + long time = System.nanoTime(); + for (int i = 0; i < 1000; i++) { + InputStream in = FileUtils.newInputStream("memFS:compress.h2.db"); + while (true) { + int len = in.read(buff2); + if (len < 0) { + break; + } + compress.compress(buff2, 0, pageSize, test, 0); + } + in.close(); + } + System.out.println("compress: " + + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - time) + + " ms"); + } + + for (int j = 0; j < 4; j++) { + ArrayList comp = new ArrayList<>(); + InputStream in = FileUtils.newInputStream("memFS:compress.h2.db"); + while (true) { + int len = in.read(buff2); + if (len < 0) { + break; + } + int b = compress.compress(buff2, 0, pageSize, test, 0); + byte[] data = Arrays.copyOf(test, b); + comp.add(data); + } + in.close(); + byte[] result = new byte[pageSize]; + long time = System.nanoTime(); + for (int i = 0; i < 1000; i++) { + for (int k = 0; k < comp.size(); k++) { + byte[] data = comp.get(k); + compress.expand(data, 0, data.length, result, 0, pageSize); + } + } + System.out.println("expand: " + + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - time) + + " ms"); + } + } + + private void test(int len) throws IOException { + testByteArray(len); + testByteBuffer(len); + } + + private void testByteArray(int len) throws IOException { + Random r = new Random(len); + for (int pattern = 0; pattern < 4; pattern++) { + byte[] b = new byte[len]; + switch (pattern) { + case 0: + // leave empty + break; + case 1: { + r.nextBytes(b); + break; + } + case 2: { + for (int x = 0; x < len; x++) { + b[x] = (byte) (x & 10); + } + break; + } + case 3: { + for (int x = 0; x < len; x++) { + b[x] = (byte) (x / 10); + } + break; + } + default: + } + if (r.nextInt(2) < 1) { + for (int x = 0; x < len; x++) { + if (r.nextInt(20) < 1) { + b[x] = (byte) (r.nextInt(255)); + } + } + } + CompressTool utils = CompressTool.getInstance(); + // level 9 is highest, strategy 2 is huffman only + for (String a : new String[] { "LZF", "No", + "Deflate", "Deflate level 9 strategy 2" }) { + long time = System.nanoTime(); + byte[] out = utils.compress(b, a); + byte[] test = utils.expand(out); + if (testPerformance) { + System.out.println("p:" + + pattern + + " len: " + + out.length + + " time: " + + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - + time) + " " + a); + } + assertEquals(b.length, test.length); + assertEquals(b, test); + Arrays.fill(test, (byte) 0); + CompressTool.expand(out, test, 0); + assertEquals(b, test); + } + for (String a : new String[] { null, "LZF", "DEFLATE", "ZIP", "GZIP" }) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + OutputStream out2 = CompressTool.wrapOutputStream(out, a, "test"); + IOUtils.copy(new ByteArrayInputStream(b), out2); + out2.close(); + InputStream in = new ByteArrayInputStream(out.toByteArray()); + in = CompressTool.wrapInputStream(in, a, "test"); + out.reset(); + IOUtils.copy(in, out); + assertEquals(b, out.toByteArray()); + } + } + } + + private void testByteBuffer(int len) { + if (len < 4) { + return; + } + Random r = new Random(len); + CompressLZF comp = new CompressLZF(); + for (int pattern = 0; pattern < 4; pattern++) { + byte[] b = new byte[len]; + switch (pattern) { + case 0: + // leave empty + break; + case 1: { + r.nextBytes(b); + break; + } + case 2: { + for (int x = 0; x < len; x++) { + b[x] = (byte) (x & 10); + } + break; + } + case 3: { + for (int x = 0; x < len; x++) { + b[x] = (byte) (x / 10); + } + break; + } + default: + } + if (r.nextInt(2) < 1) { + for (int x = 0; x < len; x++) { + if (r.nextInt(20) < 1) { + b[x] = (byte) (r.nextInt(255)); + } + } + } + ByteBuffer buff = ByteBuffer.wrap(b); + byte[] temp = new byte[100 + b.length * 2]; + int compLen = comp.compress(buff, 0, temp, 0); + ByteBuffer test = ByteBuffer.wrap(temp, 0, compLen); + byte[] exp = new byte[b.length]; + CompressLZF.expand(test, ByteBuffer.wrap(exp)); + assertEquals(b, exp); + } + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestConcurrentJdbc.java b/h2/src/test/org/h2/test/unit/TestConcurrentJdbc.java new file mode 100644 index 0000000..bf75f5f --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestConcurrentJdbc.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.test.unit; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.concurrent.CountDownLatch; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.util.Task; + +/** + * Test concurrent access to JDBC objects. + */ +public class TestConcurrentJdbc extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + String url = "jdbc:h2:mem:"; + for (int i = 0; i < 50; i++) { + final int x = i % 4; + final Connection conn = DriverManager.getConnection(url); + final Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key)"); + String sql = ""; + switch (x % 6) { + case 0: + sql = "select 1"; + break; + case 1: + case 2: + sql = "delete from test"; + break; + } + final PreparedStatement prep = conn.prepareStatement(sql); + final CountDownLatch executedUpdate = new CountDownLatch(1); + Task t = new Task() { + @Override + public void call() throws SQLException { + while (!conn.isClosed()) { + executedUpdate.countDown(); + switch (x % 6) { + case 0: + prep.executeQuery(); + break; + case 1: + prep.execute(); + break; + case 2: + prep.executeUpdate(); + break; + case 3: + stat.executeQuery("select 1"); + break; + case 4: + stat.execute("select 1"); + break; + case 5: + stat.execute("delete from test"); + break; + } + } + } + }; + t.execute(); + //Wait until the concurrent task has started + try { + executedUpdate.await(); + } catch (InterruptedException e) { + // ignore + } + conn.close(); + SQLException e = (SQLException) t.getException(); + if (e != null) { + if (ErrorCode.OBJECT_CLOSED != e.getErrorCode() && + ErrorCode.STATEMENT_WAS_CANCELED != e.getErrorCode() && + ErrorCode.DATABASE_CALLED_AT_SHUTDOWN != e.getErrorCode()) { + throw e; + } + } + } + } +} diff --git a/h2/src/test/org/h2/test/unit/TestConnectionInfo.java b/h2/src/test/org/h2/test/unit/TestConnectionInfo.java new file mode 100644 index 0000000..22405b1 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestConnectionInfo.java @@ -0,0 +1,84 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.io.File; + +import org.h2.api.ErrorCode; +import org.h2.engine.ConnectionInfo; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.DeleteDbFiles; + +/** + * Test the ConnectionInfo class. + * + * @author Kerry Sainsbury + * @author Thomas Mueller Graf + */ +public class TestConnectionInfo extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String[] a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testImplicitRelativePath(); + testConnectInitError(); + testConnectionInfo(); + testName(); + } + + private void testImplicitRelativePath() throws Exception { + assertThrows(ErrorCode.URL_RELATIVE_TO_CWD, () -> getConnection("jdbc:h2:" + getTestName())); + assertThrows(ErrorCode.URL_RELATIVE_TO_CWD, () -> getConnection("jdbc:h2:data/" + getTestName())); + + getConnection("jdbc:h2:./data/" + getTestName()).close(); + DeleteDbFiles.execute("data", getTestName(), true); + } + + private void testConnectInitError() throws Exception { + assertThrows(ErrorCode.SYNTAX_ERROR_2, () -> getConnection("jdbc:h2:mem:;init=error")); + assertThrows(ErrorCode.IO_EXCEPTION_2, () -> getConnection("jdbc:h2:mem:;init=runscript from 'wrong.file'")); + } + + private void testConnectionInfo() { + ConnectionInfo connectionInfo = new ConnectionInfo( + "jdbc:h2:mem:" + getTestName() + + ";ACCESS_MODE_DATA=rws" + + ";INIT=CREATE this...\\;INSERT that..." + + ";IFEXISTS=TRUE", + null, null, null); + + assertEquals("jdbc:h2:mem:" + getTestName(), + connectionInfo.getURL()); + + assertEquals("rws", + connectionInfo.getProperty("ACCESS_MODE_DATA", "")); + assertEquals("CREATE this...;INSERT that...", + connectionInfo.getProperty("INIT", "")); + assertEquals("TRUE", + connectionInfo.getProperty("IFEXISTS", "")); + assertEquals("undefined", + connectionInfo.getProperty("CACHE_TYPE", "undefined")); + } + + private void testName() throws Exception { + char differentFileSeparator = File.separatorChar == '/' ? '\\' : '/'; + ConnectionInfo connectionInfo = new ConnectionInfo("./test" + + differentFileSeparator + "subDir"); + File file = new File("test" + File.separatorChar + "subDir"); + assertEquals(file.getCanonicalPath().replace('\\', '/'), + connectionInfo.getName()); + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestDate.java b/h2/src/test/org/h2/test/unit/TestDate.java new file mode 100644 index 0000000..739c6b1 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestDate.java @@ -0,0 +1,460 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.sql.Date; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import org.h2.api.ErrorCode; +import org.h2.api.JavaObjectSerializer; +import org.h2.engine.CastDataProvider; +import org.h2.engine.Mode; +import org.h2.test.TestBase; +import org.h2.util.DateTimeUtils; +import org.h2.util.LegacyDateTimeUtils; +import org.h2.util.TimeZoneProvider; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueDate; +import org.h2.value.ValueDouble; +import org.h2.value.ValueTime; +import org.h2.value.ValueTimestamp; +import org.h2.value.ValueTimestampTimeZone; + +/** + * Tests the date parsing. The problem is that some dates are not allowed + * because of the summer time change. Most countries change at 2 o'clock in the + * morning to 3 o'clock, but some (for example Chile) change at midnight. + * Non-lenient parsing would not work in this case. + */ +public class TestDate extends TestBase { + + static class SimpleCastDataProvider implements CastDataProvider { + + TimeZoneProvider currentTimeZone = DateTimeUtils.getTimeZone(); + + ValueTimestampTimeZone currentTimestamp = DateTimeUtils.currentTimestamp(currentTimeZone); + + @Override + public Mode getMode() { + return Mode.getRegular(); + } + + @Override + public ValueTimestampTimeZone currentTimestamp() { + return currentTimestamp; + } + + @Override + public TimeZoneProvider currentTimeZone() { + return currentTimeZone; + } + + @Override + public JavaObjectSerializer getJavaObjectSerializer() { + return null; + } + + @Override + public boolean zeroBasedEnums() { + return false; + } + + } + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + testValueDate(); + testValueTime(); + testValueTimestamp(); + testValueTimestampWithTimezone(); + testValidDate(); + testAbsoluteDay(); + testCalculateLocalMillis(); + testDateTimeUtils(); + } + + private void testValueDate() { + assertEquals("2000-01-01", + LegacyDateTimeUtils.fromDate(null, null, Date.valueOf("2000-01-01")).getString()); + assertEquals("0000-00-00", + ValueDate.fromDateValue(0).getString()); + assertEquals("9999-12-31", + ValueDate.parse("9999-12-31").getString()); + assertEquals("-9999-12-31", + ValueDate.parse("-9999-12-31").getString()); + ValueDate d1 = ValueDate.parse("2001-01-01"); + assertEquals("2001-01-01", LegacyDateTimeUtils.toDate(null, null, d1).toString()); + assertEquals("DATE '2001-01-01'", d1.getTraceSQL()); + assertEquals("DATE '2001-01-01'", d1.toString()); + assertEquals(Value.DATE, d1.getValueType()); + long dv = d1.getDateValue(); + assertEquals((int) ((dv >>> 32) ^ dv), d1.hashCode()); + TypeInfo type = d1.getType(); + assertEquals(d1.getString().length(), type.getDisplaySize()); + assertEquals(ValueDate.PRECISION, type.getPrecision()); + ValueDate d1b = ValueDate.parse("2001-01-01"); + assertTrue(d1 == d1b); + Value.clearCache(); + d1b = ValueDate.parse("2001-01-01"); + assertFalse(d1 == d1b); + assertTrue(d1.equals(d1)); + assertTrue(d1.equals(d1b)); + assertTrue(d1b.equals(d1)); + assertEquals(0, d1.compareTo(d1b, null, null)); + assertEquals(0, d1b.compareTo(d1, null, null)); + ValueDate d2 = ValueDate.parse("2002-02-02"); + assertFalse(d1.equals(d2)); + assertFalse(d2.equals(d1)); + assertEquals(-1, d1.compareTo(d2, null, null)); + assertEquals(1, d2.compareTo(d1, null, null)); + } + + private void testValueTime() { + assertEquals("10:20:30", LegacyDateTimeUtils.fromTime(null, null, Time.valueOf("10:20:30")).getString()); + assertEquals("00:00:00", ValueTime.fromNanos(0).getString()); + assertEquals("23:59:59", ValueTime.parse("23:59:59").getString()); + assertEquals("11:22:33.444555666", ValueTime.parse("11:22:33.444555666").getString()); + assertThrows(ErrorCode.INVALID_DATETIME_CONSTANT_2, () -> ValueTime.parse("-00:00:00.000000001")); + assertThrows(ErrorCode.INVALID_DATETIME_CONSTANT_2, () -> ValueTime.parse("24:00:00")); + ValueTime t1 = ValueTime.parse("11:11:11"); + assertEquals("11:11:11", LegacyDateTimeUtils.toTime(null, null, t1).toString()); + assertEquals("TIME '11:11:11'", t1.getTraceSQL()); + assertEquals("TIME '11:11:11'", t1.toString()); + assertEquals("05:35:35.5", t1.multiply(ValueDouble.get(0.5)).getString()); + assertEquals("22:22:22", t1.divide(ValueDouble.get(0.5), TypeInfo.TYPE_TIME).getString()); + assertEquals(Value.TIME, t1.getValueType()); + long nanos = t1.getNanos(); + assertEquals((int) ((nanos >>> 32) ^ nanos), t1.hashCode()); + // Literals return maximum precision + TypeInfo type = t1.getType(); + assertEquals(ValueTime.MAXIMUM_PRECISION, type.getDisplaySize()); + assertEquals(ValueTime.MAXIMUM_PRECISION, type.getPrecision()); + ValueTime t1b = ValueTime.parse("11:11:11"); + assertTrue(t1 == t1b); + Value.clearCache(); + t1b = ValueTime.parse("11:11:11"); + assertFalse(t1 == t1b); + assertTrue(t1.equals(t1)); + assertTrue(t1.equals(t1b)); + assertTrue(t1b.equals(t1)); + assertEquals(0, t1.compareTo(t1b, null, null)); + assertEquals(0, t1b.compareTo(t1, null, null)); + ValueTime t2 = ValueTime.parse("22:22:22"); + assertFalse(t1.equals(t2)); + assertFalse(t2.equals(t1)); + assertEquals(-1, t1.compareTo(t2, null, null)); + assertEquals(1, t2.compareTo(t1, null, null)); + } + + private void testValueTimestampWithTimezone() { + for (int m = 1; m <= 12; m++) { + for (int d = 1; d <= 28; d++) { + for (int h = 0; h <= 23; h++) { + String s = "2011-" + (m < 10 ? "0" : "") + m + + "-" + (d < 10 ? "0" : "") + d + " " + + (h < 10 ? "0" : "") + h + ":00:00"; + ValueTimestamp ts = ValueTimestamp.parse(s + "Z", null); + String s2 = ts.getString(); + ValueTimestamp ts2 = ValueTimestamp.parse(s2, null); + assertEquals(ts.getString(), ts2.getString()); + } + } + } + } + + @SuppressWarnings("unlikely-arg-type") + private void testValueTimestamp() { + assertEquals( + "2001-02-03 04:05:06", + LegacyDateTimeUtils.fromTimestamp(null, null, Timestamp.valueOf("2001-02-03 04:05:06")).getString()); + assertEquals( + "2001-02-03 04:05:06.001002003", + LegacyDateTimeUtils.fromTimestamp(null, null, Timestamp.valueOf("2001-02-03 04:05:06.001002003")) + .getString()); + assertEquals( + "0000-00-00 00:00:00", ValueTimestamp.fromDateValueAndNanos(0, 0).getString()); + assertEquals( + "9999-12-31 23:59:59", + ValueTimestamp.parse("9999-12-31 23:59:59", null).getString()); + + ValueTimestamp t1 = ValueTimestamp.parse("2001-01-01 01:01:01.111", null); + assertEquals("2001-01-01 01:01:01.111", LegacyDateTimeUtils.toTimestamp(null, null, t1).toString()); + assertEquals("2001-01-01", LegacyDateTimeUtils.toDate(null, null, t1).toString()); + assertEquals("01:01:01", LegacyDateTimeUtils.toTime(null, null, t1).toString()); + assertEquals("TIMESTAMP '2001-01-01 01:01:01.111'", t1.getTraceSQL()); + assertEquals("TIMESTAMP '2001-01-01 01:01:01.111'", t1.toString()); + assertEquals(Value.TIMESTAMP, t1.getValueType()); + long dateValue = t1.getDateValue(); + long nanos = t1.getTimeNanos(); + assertEquals((int) ((dateValue >>> 32) ^ dateValue ^ + (nanos >>> 32) ^ nanos), + t1.hashCode()); + // Literals return maximum precision + TypeInfo type = t1.getType(); + assertEquals(ValueTimestamp.MAXIMUM_PRECISION, type.getDisplaySize()); + assertEquals(ValueTimestamp.MAXIMUM_PRECISION, type.getPrecision()); + assertEquals(9, type.getScale()); + ValueTimestamp t1b = ValueTimestamp.parse("2001-01-01 01:01:01.111", null); + assertTrue(t1 == t1b); + Value.clearCache(); + t1b = ValueTimestamp.parse("2001-01-01 01:01:01.111", null); + assertFalse(t1 == t1b); + assertTrue(t1.equals(t1)); + assertTrue(t1.equals(t1b)); + assertTrue(t1b.equals(t1)); + assertEquals(0, t1.compareTo(t1b, null, null)); + assertEquals(0, t1b.compareTo(t1, null, null)); + ValueTimestamp t2 = ValueTimestamp.parse("2002-02-02 02:02:02.222", null); + assertFalse(t1.equals(t2)); + assertFalse(t2.equals(t1)); + assertEquals(-1, t1.compareTo(t2, null, null)); + assertEquals(1, t2.compareTo(t1, null, null)); + SimpleCastDataProvider provider = new SimpleCastDataProvider(); + t1 = ValueTimestamp.parse("2001-01-01 01:01:01.123456789", null); + assertEquals("2001-01-01 01:01:01.123456789", + t1.getString()); + assertEquals("2001-01-01 01:01:01.123456789", + t1.castTo(TypeInfo.getTypeInfo(Value.TIMESTAMP, 0L, 9, null), provider).getString()); + assertEquals("2001-01-01 01:01:01.12345679", + t1.castTo(TypeInfo.getTypeInfo(Value.TIMESTAMP, 0L, 8, null), provider).getString()); + assertEquals("2001-01-01 01:01:01.1234568", + t1.castTo(TypeInfo.getTypeInfo(Value.TIMESTAMP, 0L, 7, null), provider).getString()); + assertEquals("2001-01-01 01:01:01.123457", + t1.castTo(TypeInfo.getTypeInfo(Value.TIMESTAMP, 0L, 6, null), provider).getString()); + assertEquals("2001-01-01 01:01:01.12346", + t1.castTo(TypeInfo.getTypeInfo(Value.TIMESTAMP, 0L, 5, null), provider).getString()); + assertEquals("2001-01-01 01:01:01.1235", + t1.castTo(TypeInfo.getTypeInfo(Value.TIMESTAMP, 0L, 4, null), provider).getString()); + assertEquals("2001-01-01 01:01:01.123", + t1.castTo(TypeInfo.getTypeInfo(Value.TIMESTAMP, 0L, 3, null), provider).getString()); + assertEquals("2001-01-01 01:01:01.12", + t1.castTo(TypeInfo.getTypeInfo(Value.TIMESTAMP, 0L, 2, null), provider).getString()); + assertEquals("2001-01-01 01:01:01.1", + t1.castTo(TypeInfo.getTypeInfo(Value.TIMESTAMP, 0L, 1, null), provider).getString()); + assertEquals("2001-01-01 01:01:01", + t1.castTo(TypeInfo.getTypeInfo(Value.TIMESTAMP, 0L, 0, null), provider).getString()); + t1 = ValueTimestamp.parse("-2001-01-01 01:01:01.123456789", null); + assertEquals("-2001-01-01 01:01:01.123457", + t1.castTo(TypeInfo.getTypeInfo(Value.TIMESTAMP, 0L, 6, null), provider).getString()); + // classes do not match + assertFalse(ValueTimestamp.parse("2001-01-01", null). + equals(ValueDate.parse("2001-01-01"))); + + provider.currentTimestamp = ValueTimestampTimeZone.fromDateValueAndNanos(DateTimeUtils.EPOCH_DATE_VALUE, 0, + provider.currentTimeZone.getTimeZoneOffsetUTC(0L)); + assertEquals("2001-01-01 01:01:01", + ValueTimestamp.parse("2001-01-01", null).add( + ValueTime.parse("01:01:01").convertTo(TypeInfo.TYPE_TIMESTAMP, provider)).getString()); + assertEquals("1010-10-10 00:00:00", + ValueTimestamp.parse("1010-10-10 10:10:10", null).subtract( + ValueTime.parse("10:10:10").convertTo(TypeInfo.TYPE_TIMESTAMP, provider)).getString()); + assertEquals("-2001-01-01 01:01:01", + ValueTimestamp.parse("-2001-01-01", null).add( + ValueTime.parse("01:01:01").convertTo(TypeInfo.TYPE_TIMESTAMP, provider)).getString()); + assertEquals("-1010-10-10 00:00:00", + ValueTimestamp.parse("-1010-10-10 10:10:10", null).subtract( + ValueTime.parse("10:10:10").convertTo(TypeInfo.TYPE_TIMESTAMP, provider)).getString()); + + assertEquals(0, DateTimeUtils.absoluteDayFromDateValue( + ValueTimestamp.parse("1970-01-01", null).getDateValue())); + assertEquals(0, ValueTimestamp.parse("1970-01-01", null).getTimeNanos()); + assertEquals(0, LegacyDateTimeUtils.toTimestamp(null, null, + ValueTimestamp.parse("1970-01-01 00:00:00.000 UTC", null)).getTime()); + assertEquals(0, LegacyDateTimeUtils.toTimestamp(null, null, + ValueTimestamp.parse("+1970-01-01T00:00:00.000Z", null)).getTime()); + assertEquals(0, LegacyDateTimeUtils.toTimestamp(null, null, + ValueTimestamp.parse("1970-01-01T00:00:00.000+00:00", null)).getTime()); + assertEquals(0, LegacyDateTimeUtils.toTimestamp(null, null, + ValueTimestamp.parse("1970-01-01T00:00:00.000-00:00", null)).getTime()); + assertThrows(ErrorCode.INVALID_DATETIME_CONSTANT_2, + () -> ValueTimestamp.parse("1970-01-01 00:00:00.000 ABC", null)); + assertThrows(ErrorCode.INVALID_DATETIME_CONSTANT_2, + () -> ValueTimestamp.parse("1970-01-01T00:00:00.000+ABC", null)); + } + + private void testAbsoluteDay() { + long next = Long.MIN_VALUE; + for (int y = -2000; y < 3000; y++) { + for (int m = -3; m <= 14; m++) { + for (int d = -2; d <= 35; d++) { + if (!DateTimeUtils.isValidDate(y, m, d)) { + continue; + } + long date = DateTimeUtils.dateValue(y, m, d); + long abs = DateTimeUtils.absoluteDayFromDateValue(date); + if (abs != next && next != Long.MIN_VALUE) { + assertEquals(abs, next); + } + if (m == 1 && d == 1) { + assertEquals(abs, DateTimeUtils.absoluteDayFromYear(y)); + } + next = abs + 1; + long d2 = DateTimeUtils.dateValueFromAbsoluteDay(abs); + assertEquals(date, d2); + assertEquals(y, DateTimeUtils.yearFromDateValue(date)); + assertEquals(m, DateTimeUtils.monthFromDateValue(date)); + assertEquals(d, DateTimeUtils.dayFromDateValue(date)); + long nextDateValue = DateTimeUtils.dateValueFromAbsoluteDay(next); + assertEquals(nextDateValue, DateTimeUtils.incrementDateValue(date)); + assertEquals(date, DateTimeUtils.decrementDateValue(nextDateValue)); + } + } + } + } + + private void testValidDate() { + Calendar c = TestDateTimeUtils.createGregorianCalendar(LegacyDateTimeUtils.UTC); + c.setLenient(false); + for (int y = -2000; y < 3000; y++) { + for (int m = -3; m <= 14; m++) { + for (int d = -2; d <= 35; d++) { + boolean valid = DateTimeUtils.isValidDate(y, m, d); + if (m < 1 || m > 12) { + assertFalse(valid); + } else if (d < 1 || d > 31) { + assertFalse(valid); + } else if (d <= 27) { + assertTrue(valid); + } else { + if (y <= 0) { + c.set(Calendar.ERA, GregorianCalendar.BC); + c.set(Calendar.YEAR, 1 - y); + } else { + c.set(Calendar.ERA, GregorianCalendar.AD); + c.set(Calendar.YEAR, y); + } + c.set(Calendar.MONTH, m - 1); + c.set(Calendar.DAY_OF_MONTH, d); + boolean expected = true; + try { + c.getTimeInMillis(); + } catch (Exception e) { + expected = false; + } + if (expected != valid) { + fail(y + "-" + m + "-" + d + + " expected: " + expected + " got: " + valid); + } + } + } + } + } + } + + private static void testCalculateLocalMillis() { + TimeZone defaultTimeZone = TimeZone.getDefault(); + try { + for (TimeZone tz : TestDate.getDistinctTimeZones()) { + TimeZone.setDefault(tz); + for (int y = 1900; y < 2039; y += 10) { + if (y == 1993) { + // timezone change in Kwajalein + } else if (y == 1995) { + // timezone change in Enderbury and Kiritimati + } + for (int m = 1; m <= 12; m++) { + if (m != 3 && m != 4 && m != 10 && m != 11) { + // only test daylight saving time transitions + continue; + } + for (int day = 1; day < 29; day++) { + testDate(y, m, day); + } + } + } + } + } finally { + TimeZone.setDefault(defaultTimeZone); + } + } + + private static void testDate(int y, int m, int day) { + long millis = LegacyDateTimeUtils.getMillis(null, TimeZone.getDefault(), DateTimeUtils.dateValue(y, m, day), + 0); + String st = new java.sql.Date(millis).toString(); + int y2 = Integer.parseInt(st.substring(0, 4)); + int m2 = Integer.parseInt(st.substring(5, 7)); + int d2 = Integer.parseInt(st.substring(8, 10)); + if (y != y2 || m != m2 || day != d2) { + String s = y + "-" + (m < 10 ? "0" + m : m) + + "-" + (day < 10 ? "0" + day : day); + System.out.println(s + "<>" + st + " " + TimeZone.getDefault().getID()); + } + } + + /** + * Get the list of timezones with distinct rules. + * + * @return the list + */ + public static ArrayList getDistinctTimeZones() { + ArrayList distinct = new ArrayList<>(); + for (String id : TimeZone.getAvailableIDs()) { + TimeZone t = TimeZone.getTimeZone(id); + for (TimeZone d : distinct) { + if (t.hasSameRules(d)) { + t = null; + break; + } + } + if (t != null) { + distinct.add(t); + } + } + return distinct; + } + + private void testDateTimeUtils() { + TimeZone old = TimeZone.getDefault(); + /* + * java.util.TimeZone doesn't support LMT, so perform this test with + * fixed time zone offset + */ + TimeZone.setDefault(TimeZone.getTimeZone("GMT+01")); + DateTimeUtils.resetCalendar(); + try { + ValueTimestamp ts1 = ValueTimestamp.parse("-999-08-07 13:14:15.16", null); + ValueTimestamp ts2 = ValueTimestamp.parse("19999-08-07 13:14:15.16", null); + ValueTime t1 = (ValueTime) ts1.convertTo(TypeInfo.TYPE_TIME); + ValueTime t2 = (ValueTime) ts2.convertTo(TypeInfo.TYPE_TIME); + ValueDate d1 = ts1.convertToDate(null); + ValueDate d2 = ts2.convertToDate(null); + assertEquals("-0999-08-07 13:14:15.16", ts1.getString()); + assertEquals("-0999-08-07", d1.getString()); + assertEquals("13:14:15.16", t1.getString()); + assertEquals("19999-08-07 13:14:15.16", ts2.getString()); + assertEquals("19999-08-07", d2.getString()); + assertEquals("13:14:15.16", t2.getString()); + TimeZone timeZone = TimeZone.getDefault(); + ValueTimestamp ts1a = LegacyDateTimeUtils.fromTimestamp(null, timeZone, + LegacyDateTimeUtils.toTimestamp(null, null, ts1)); + ValueTimestamp ts2a = LegacyDateTimeUtils.fromTimestamp(null, timeZone, + LegacyDateTimeUtils.toTimestamp(null, null, ts2)); + assertEquals("-0999-08-07 13:14:15.16", ts1a.getString()); + assertEquals("19999-08-07 13:14:15.16", ts2a.getString()); + } finally { + TimeZone.setDefault(old); + DateTimeUtils.resetCalendar(); + } + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestDateIso8601.java b/h2/src/test/org/h2/test/unit/TestDateIso8601.java new file mode 100644 index 0000000..b3cfe3f --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestDateIso8601.java @@ -0,0 +1,282 @@ +/* + * 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: Robert Rathsack (firstName dot lastName at gmx dot de) + */ +package org.h2.test.unit; + +import static org.h2.util.DateTimeUtils.getIsoDayOfWeek; +import static org.h2.util.DateTimeUtils.getIsoWeekOfYear; +import static org.h2.util.DateTimeUtils.getIsoWeekYear; + +import org.h2.test.TestBase; +import org.h2.value.ValueDate; +import org.h2.value.ValueTimestamp; +import org.h2.value.ValueTimestampTimeZone; + +/** + * Test cases for DateTimeIso8601Utils. + */ +public class TestDateIso8601 extends TestBase { + + private enum Type { + DATE, TIMESTAMP, TIMESTAMP_TIMEZONE_0, TIMESTAMP_TIMEZONE_PLUS_18, TIMESTAMP_TIMEZONE_MINUS_18; + } + + private static Type type; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + private static long parse(String s) { + if (type == null) { + throw new IllegalStateException(); + } + switch (type) { + case DATE: + return ValueDate.parse(s).getDateValue(); + case TIMESTAMP: + return ValueTimestamp.parse(s, null).getDateValue(); + case TIMESTAMP_TIMEZONE_0: + return ValueTimestampTimeZone.parse(s + " 00:00:00.0Z", null).getDateValue(); + case TIMESTAMP_TIMEZONE_PLUS_18: + return ValueTimestampTimeZone.parse(s + " 00:00:00+18:00", null).getDateValue(); + case TIMESTAMP_TIMEZONE_MINUS_18: + return ValueTimestampTimeZone.parse(s + " 00:00:00-18:00", null).getDateValue(); + default: + throw new IllegalStateException(); + } + } + + @Override + public void test() throws Exception { + type = Type.DATE; + doTest(); + type = Type.TIMESTAMP; + doTest(); + type = Type.TIMESTAMP_TIMEZONE_0; + doTest(); + type = Type.TIMESTAMP_TIMEZONE_PLUS_18; + doTest(); + type = Type.TIMESTAMP_TIMEZONE_MINUS_18; + doTest(); + } + + private void doTest() throws Exception { + testIsoDayOfWeek(); + testIsoWeekJanuary1thMonday(); + testIsoWeekJanuary1thTuesday(); + testIsoWeekJanuary1thWednesday(); + testIsoWeekJanuary1thThursday(); + testIsoWeekJanuary1thFriday(); + testIsoWeekJanuary1thSaturday(); + testIsoWeekJanuary1thSunday(); + testIsoYearJanuary1thMonday(); + testIsoYearJanuary1thTuesday(); + testIsoYearJanuary1thWednesday(); + testIsoYearJanuary1thThursday(); + testIsoYearJanuary1thFriday(); + testIsoYearJanuary1thSaturday(); + testIsoYearJanuary1thSunday(); + } + + /** + * Test if day of week is returned as Monday = 1 to Sunday = 7. + */ + private void testIsoDayOfWeek() throws Exception { + assertEquals(1, getIsoDayOfWeek(parse("2008-09-29"))); + assertEquals(2, getIsoDayOfWeek(parse("2008-09-30"))); + assertEquals(3, getIsoDayOfWeek(parse("2008-10-01"))); + assertEquals(4, getIsoDayOfWeek(parse("2008-10-02"))); + assertEquals(5, getIsoDayOfWeek(parse("2008-10-03"))); + assertEquals(6, getIsoDayOfWeek(parse("2008-10-04"))); + assertEquals(7, getIsoDayOfWeek(parse("2008-10-05"))); + } + + /** + * January 1st is a Monday therefore the week belongs to the next year. + */ + private void testIsoWeekJanuary1thMonday() throws Exception { + assertEquals(52, getIsoWeekOfYear(parse("2006-12-31"))); + assertEquals(1, getIsoWeekOfYear(parse("2007-01-01"))); + assertEquals(1, getIsoWeekOfYear(parse("2007-01-07"))); + assertEquals(2, getIsoWeekOfYear(parse("2007-01-08"))); + } + + /** + * January 1st is a Tuesday therefore the week belongs to the next year. + */ + private void testIsoWeekJanuary1thTuesday() throws Exception { + assertEquals(52, getIsoWeekOfYear(parse("2007-12-30"))); + assertEquals(1, getIsoWeekOfYear(parse("2007-12-31"))); + assertEquals(1, getIsoWeekOfYear(parse("2008-01-01"))); + assertEquals(1, getIsoWeekOfYear(parse("2008-01-06"))); + assertEquals(2, getIsoWeekOfYear(parse("2008-01-07"))); + } + + /** + * January1th is a Wednesday therefore the week belongs to the next year. + */ + private void testIsoWeekJanuary1thWednesday() throws Exception { + assertEquals(52, getIsoWeekOfYear(parse("2002-12-28"))); + assertEquals(52, getIsoWeekOfYear(parse("2002-12-29"))); + assertEquals(1, getIsoWeekOfYear(parse("2002-12-30"))); + assertEquals(1, getIsoWeekOfYear(parse("2002-12-31"))); + assertEquals(1, getIsoWeekOfYear(parse("2003-01-01"))); + assertEquals(1, getIsoWeekOfYear(parse("2003-01-05"))); + assertEquals(2, getIsoWeekOfYear(parse("2003-01-06"))); + } + + /** + * January 1st is a Thursday therefore the week belongs to the next year. + */ + private void testIsoWeekJanuary1thThursday() throws Exception { + assertEquals(52, getIsoWeekOfYear(parse("2008-12-28"))); + assertEquals(1, getIsoWeekOfYear(parse("2008-12-29"))); + assertEquals(1, getIsoWeekOfYear(parse("2008-12-30"))); + assertEquals(1, getIsoWeekOfYear(parse("2008-12-31"))); + assertEquals(1, getIsoWeekOfYear(parse("2009-01-01"))); + assertEquals(1, getIsoWeekOfYear(parse("2009-01-04"))); + assertEquals(2, getIsoWeekOfYear(parse("2009-01-09"))); + } + + /** + * January 1st is a Friday therefore the week belongs to the previous year. + */ + private void testIsoWeekJanuary1thFriday() throws Exception { + assertEquals(53, getIsoWeekOfYear(parse("2009-12-31"))); + assertEquals(53, getIsoWeekOfYear(parse("2010-01-01"))); + assertEquals(53, getIsoWeekOfYear(parse("2010-01-03"))); + assertEquals(1, getIsoWeekOfYear(parse("2010-01-04"))); + } + + /** + * January 1st is a Saturday therefore the week belongs to the previous + * year. + */ + private void testIsoWeekJanuary1thSaturday() throws Exception { + assertEquals(52, getIsoWeekOfYear(parse("2010-12-31"))); + assertEquals(52, getIsoWeekOfYear(parse("2011-01-01"))); + assertEquals(52, getIsoWeekOfYear(parse("2011-01-02"))); + assertEquals(1, getIsoWeekOfYear(parse("2011-01-03"))); + } + + /** + * January 1st is a Sunday therefore the week belongs to the previous year. + */ + private void testIsoWeekJanuary1thSunday() throws Exception { + assertEquals(52, getIsoWeekOfYear(parse("2011-12-31"))); + assertEquals(52, getIsoWeekOfYear(parse("2012-01-01"))); + assertEquals(1, getIsoWeekOfYear(parse("2012-01-02"))); + assertEquals(1, getIsoWeekOfYear(parse("2012-01-08"))); + assertEquals(2, getIsoWeekOfYear(parse("2012-01-09"))); + } + + /** + * January 1st is a Monday therefore year is equal to isoYear. + */ + private void testIsoYearJanuary1thMonday() throws Exception { + assertEquals(2006, getIsoWeekYear(parse("2006-12-28"))); + assertEquals(2006, getIsoWeekYear(parse("2006-12-29"))); + assertEquals(2006, getIsoWeekYear(parse("2006-12-30"))); + assertEquals(2006, getIsoWeekYear(parse("2006-12-31"))); + assertEquals(2007, getIsoWeekYear(parse("2007-01-01"))); + assertEquals(2007, getIsoWeekYear(parse("2007-01-02"))); + assertEquals(2007, getIsoWeekYear(parse("2007-01-03"))); + } + + /** + * January 1st is a Tuesday therefore 31th of December belong to the next + * year. + */ + private void testIsoYearJanuary1thTuesday() throws Exception { + assertEquals(2007, getIsoWeekYear(parse("2007-12-28"))); + assertEquals(2007, getIsoWeekYear(parse("2007-12-29"))); + assertEquals(2007, getIsoWeekYear(parse("2007-12-30"))); + assertEquals(2008, getIsoWeekYear(parse("2007-12-31"))); + assertEquals(2008, getIsoWeekYear(parse("2008-01-01"))); + assertEquals(2008, getIsoWeekYear(parse("2008-01-02"))); + assertEquals(2008, getIsoWeekYear(parse("2008-01-03"))); + assertEquals(2008, getIsoWeekYear(parse("2008-01-04"))); + } + + /** + * January 1st is a Wednesday therefore 30th and 31th of December belong to + * the next year. + */ + private void testIsoYearJanuary1thWednesday() throws Exception { + assertEquals(2002, getIsoWeekYear(parse("2002-12-28"))); + assertEquals(2002, getIsoWeekYear(parse("2002-12-29"))); + assertEquals(2003, getIsoWeekYear(parse("2002-12-30"))); + assertEquals(2003, getIsoWeekYear(parse("2002-12-31"))); + assertEquals(2003, getIsoWeekYear(parse("2003-01-01"))); + assertEquals(2003, getIsoWeekYear(parse("2003-01-02"))); + assertEquals(2003, getIsoWeekYear(parse("2003-12-02"))); + } + + /** + * January 1st is a Thursday therefore 29th - 31th of December belong to the + * next year. + */ + private void testIsoYearJanuary1thThursday() throws Exception { + assertEquals(2008, getIsoWeekYear(parse("2008-12-28"))); + assertEquals(2009, getIsoWeekYear(parse("2008-12-29"))); + assertEquals(2009, getIsoWeekYear(parse("2008-12-30"))); + assertEquals(2009, getIsoWeekYear(parse("2008-12-31"))); + assertEquals(2009, getIsoWeekYear(parse("2009-01-01"))); + assertEquals(2009, getIsoWeekYear(parse("2009-01-02"))); + assertEquals(2009, getIsoWeekYear(parse("2009-01-03"))); + assertEquals(2009, getIsoWeekYear(parse("2009-01-04"))); + } + + /** + * January 1st is a Friday therefore 1st - 3rd of January belong to the + * previous year. + */ + private void testIsoYearJanuary1thFriday() throws Exception { + assertEquals(2009, getIsoWeekYear(parse("2009-12-28"))); + assertEquals(2009, getIsoWeekYear(parse("2009-12-29"))); + assertEquals(2009, getIsoWeekYear(parse("2009-12-30"))); + assertEquals(2009, getIsoWeekYear(parse("2009-12-31"))); + assertEquals(2009, getIsoWeekYear(parse("2010-01-01"))); + assertEquals(2009, getIsoWeekYear(parse("2010-01-02"))); + assertEquals(2009, getIsoWeekYear(parse("2010-01-03"))); + assertEquals(2010, getIsoWeekYear(parse("2010-01-04"))); + } + + /** + * January 1st is a Saturday therefore 1st and 2nd of January belong to the + * previous year. + */ + private void testIsoYearJanuary1thSaturday() throws Exception { + assertEquals(2010, getIsoWeekYear(parse("2010-12-28"))); + assertEquals(2010, getIsoWeekYear(parse("2010-12-29"))); + assertEquals(2010, getIsoWeekYear(parse("2010-12-30"))); + assertEquals(2010, getIsoWeekYear(parse("2010-12-31"))); + assertEquals(2010, getIsoWeekYear(parse("2011-01-01"))); + assertEquals(2010, getIsoWeekYear(parse("2011-01-02"))); + assertEquals(2011, getIsoWeekYear(parse("2011-01-03"))); + assertEquals(2011, getIsoWeekYear(parse("2011-01-04"))); + } + + /** + * January 1st is a Sunday therefore this day belong to the previous year. + */ + private void testIsoYearJanuary1thSunday() throws Exception { + assertEquals(2011, getIsoWeekYear(parse("2011-12-28"))); + assertEquals(2011, getIsoWeekYear(parse("2011-12-29"))); + assertEquals(2011, getIsoWeekYear(parse("2011-12-30"))); + assertEquals(2011, getIsoWeekYear(parse("2011-12-31"))); + assertEquals(2011, getIsoWeekYear(parse("2012-01-01"))); + assertEquals(2012, getIsoWeekYear(parse("2012-01-02"))); + assertEquals(2012, getIsoWeekYear(parse("2012-01-03"))); + assertEquals(2012, getIsoWeekYear(parse("2012-01-04"))); + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestDateTimeUtils.java b/h2/src/test/org/h2/test/unit/TestDateTimeUtils.java new file mode 100644 index 0000000..e3aa5bf --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestDateTimeUtils.java @@ -0,0 +1,327 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import static org.h2.util.DateTimeUtils.dateValue; + +import java.sql.Timestamp; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import org.h2.api.IntervalQualifier; +import org.h2.test.TestBase; +import org.h2.util.DateTimeUtils; +import org.h2.util.IntervalUtils; +import org.h2.util.LegacyDateTimeUtils; +import org.h2.value.ValueInterval; +import org.h2.value.ValueTimestamp; + +/** + * Unit tests for the DateTimeUtils and IntervalUtils classes. + */ +public class TestDateTimeUtils extends TestBase { + + /** + * Creates a proleptic Gregorian calendar for the given timezone using the + * default locale. + * + * @param tz timezone for the calendar, is never null + * @return a new calendar instance. + */ + public static GregorianCalendar createGregorianCalendar(TimeZone tz) { + GregorianCalendar c = new GregorianCalendar(tz); + c.setGregorianChange(LegacyDateTimeUtils.PROLEPTIC_GREGORIAN_CHANGE); + return c; + } + + /** + * Run just this test. + * + * @param a + * if {@code "testUtc2Value"} only {@link #testUTC2Value(boolean)} + * will be executed with all time zones (slow). Otherwise all tests + * in this test unit will be executed with local time zone. + */ + public static void main(String... a) throws Exception { + if (a.length == 1) { + if ("testUtc2Value".equals(a[0])) { + new TestDateTimeUtils().testUTC2Value(true); + return; + } + } + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testParseTimeNanosDB2Format(); + testDayOfWeek(); + testWeekOfYear(); + testDateValueFromDenormalizedDate(); + testUTC2Value(false); + testConvertScale(); + testParseInterval(); + testGetTimeZoneOffset(); + } + + private void testParseTimeNanosDB2Format() { + assertEquals(3723004000000L, DateTimeUtils.parseTimeNanos("01:02:03.004", 0, 12)); + assertEquals(3723004000000L, DateTimeUtils.parseTimeNanos("01.02.03.004", 0, 12)); + + assertEquals(3723000000000L, DateTimeUtils.parseTimeNanos("01:02:03", 0, 8)); + assertEquals(3723000000000L, DateTimeUtils.parseTimeNanos("01.02.03", 0, 8)); + } + + /** + * Test for {@link DateTimeUtils#getSundayDayOfWeek(long)} and + * {@link DateTimeUtils#getIsoDayOfWeek(long)}. + */ + private void testDayOfWeek() { + GregorianCalendar gc = createGregorianCalendar(LegacyDateTimeUtils.UTC); + for (int i = -1_000_000; i <= 1_000_000; i++) { + gc.clear(); + gc.setTimeInMillis(i * 86400000L); + int year = gc.get(Calendar.YEAR); + if (gc.get(Calendar.ERA) == GregorianCalendar.BC) { + year = 1 - year; + } + long expectedDateValue = dateValue(year, gc.get(Calendar.MONTH) + 1, + gc.get(Calendar.DAY_OF_MONTH)); + long dateValue = DateTimeUtils.dateValueFromAbsoluteDay(i); + assertEquals(expectedDateValue, dateValue); + assertEquals(i, DateTimeUtils.absoluteDayFromDateValue(dateValue)); + int dow = gc.get(Calendar.DAY_OF_WEEK); + assertEquals(dow, DateTimeUtils.getSundayDayOfWeek(dateValue)); + int isoDow = (dow + 5) % 7 + 1; + assertEquals(isoDow, DateTimeUtils.getIsoDayOfWeek(dateValue)); + assertEquals(gc.get(Calendar.WEEK_OF_YEAR), + DateTimeUtils.getWeekOfYear(dateValue, gc.getFirstDayOfWeek() - 1, + gc.getMinimalDaysInFirstWeek())); + } + } + + /** + * Test for {@link DateTimeUtils#getDayOfYear(long)}, + * {@link DateTimeUtils#getWeekOfYear(long, int, int)} and + * {@link DateTimeUtils#getWeekYear(long, int, int)}. + */ + private void testWeekOfYear() { + GregorianCalendar gc = new GregorianCalendar(LegacyDateTimeUtils.UTC); + for (int firstDay = 1; firstDay <= 7; firstDay++) { + gc.setFirstDayOfWeek(firstDay); + for (int minimalDays = 1; minimalDays <= 7; minimalDays++) { + gc.setMinimalDaysInFirstWeek(minimalDays); + for (int i = 0; i < 150_000; i++) { + long dateValue = DateTimeUtils.dateValueFromAbsoluteDay(i); + gc.clear(); + gc.setTimeInMillis(i * 86400000L); + assertEquals(gc.get(Calendar.DAY_OF_YEAR), DateTimeUtils.getDayOfYear(dateValue)); + assertEquals(gc.get(Calendar.WEEK_OF_YEAR), + DateTimeUtils.getWeekOfYear(dateValue, firstDay - 1, minimalDays)); + assertEquals(gc.getWeekYear(), DateTimeUtils.getWeekYear(dateValue, firstDay - 1, minimalDays)); + } + } + } + } + + /** + * Test for {@link DateTimeUtils#dateValueFromDenormalizedDate(long, long, int)}. + */ + private void testDateValueFromDenormalizedDate() { + assertEquals(dateValue(2017, 1, 1), DateTimeUtils.dateValueFromDenormalizedDate(2018, -11, 0)); + assertEquals(dateValue(2001, 2, 28), DateTimeUtils.dateValueFromDenormalizedDate(2000, 14, 29)); + assertEquals(dateValue(1999, 8, 1), DateTimeUtils.dateValueFromDenormalizedDate(2000, -4, -100)); + assertEquals(dateValue(2100, 12, 31), DateTimeUtils.dateValueFromDenormalizedDate(2100, 12, 2000)); + assertEquals(dateValue(-100, 2, 28), DateTimeUtils.dateValueFromDenormalizedDate(-100, 2, 30)); + } + + private void testUTC2Value(boolean allTimeZones) { + TimeZone def = TimeZone.getDefault(); + GregorianCalendar gc = new GregorianCalendar(); + String[] ids = allTimeZones ? TimeZone.getAvailableIDs() + : new String[] { def.getID(), "+10", + // Any time zone with DST in the future (JDK-8073446) + "America/New_York" }; + try { + for (String id : ids) { + if (allTimeZones) { + System.out.println(id); + } + TimeZone tz = TimeZone.getTimeZone(id); + TimeZone.setDefault(tz); + DateTimeUtils.resetCalendar(); + testUTC2ValueImpl(tz, gc); + } + } finally { + TimeZone.setDefault(def); + DateTimeUtils.resetCalendar(); + } + } + + private void testUTC2ValueImpl(TimeZone tz, GregorianCalendar gc) { + gc.setTimeZone(tz); + gc.set(Calendar.MILLISECOND, 0); + long absoluteStart = DateTimeUtils.absoluteDayFromDateValue(DateTimeUtils.dateValue(1950, 01, 01)); + long absoluteEnd = DateTimeUtils.absoluteDayFromDateValue(DateTimeUtils.dateValue(2050, 01, 01)); + for (long i = absoluteStart; i < absoluteEnd; i++) { + long dateValue = DateTimeUtils.dateValueFromAbsoluteDay(i); + int year = DateTimeUtils.yearFromDateValue(dateValue); + int month = DateTimeUtils.monthFromDateValue(dateValue); + int day = DateTimeUtils.dayFromDateValue(dateValue); + for (int j = 0; j < 48; j++) { + gc.set(year, month - 1, day, j / 2, (j & 1) * 30, 0); + long timeMillis = gc.getTimeInMillis(); + ValueTimestamp ts = LegacyDateTimeUtils.fromTimestamp(null, null, new Timestamp(timeMillis)); + timeMillis += LegacyDateTimeUtils.getTimeZoneOffsetMillis(null, timeMillis); + assertEquals(ts.getDateValue(), LegacyDateTimeUtils.dateValueFromLocalMillis(timeMillis)); + assertEquals(ts.getTimeNanos(), LegacyDateTimeUtils.nanosFromLocalMillis(timeMillis)); + } + } + } + + private void testConvertScale() { + assertEquals(555_555_555_555L, DateTimeUtils.convertScale(555_555_555_555L, 9, Long.MAX_VALUE)); + assertEquals(555_555_555_550L, DateTimeUtils.convertScale(555_555_555_554L, 8, Long.MAX_VALUE)); + assertEquals(555_555_555_500L, DateTimeUtils.convertScale(555_555_555_549L, 7, Long.MAX_VALUE)); + assertEquals(555_555_555_000L, DateTimeUtils.convertScale(555_555_555_499L, 6, Long.MAX_VALUE)); + assertEquals(555_555_550_000L, DateTimeUtils.convertScale(555_555_554_999L, 5, Long.MAX_VALUE)); + assertEquals(555_555_500_000L, DateTimeUtils.convertScale(555_555_549_999L, 4, Long.MAX_VALUE)); + assertEquals(555_555_000_000L, DateTimeUtils.convertScale(555_555_499_999L, 3, Long.MAX_VALUE)); + assertEquals(555_550_000_000L, DateTimeUtils.convertScale(555_554_999_999L, 2, Long.MAX_VALUE)); + assertEquals(555_500_000_000L, DateTimeUtils.convertScale(555_549_999_999L, 1, Long.MAX_VALUE)); + assertEquals(555_000_000_000L, DateTimeUtils.convertScale(555_499_999_999L, 0, Long.MAX_VALUE)); + assertEquals(555_555_555_555L, DateTimeUtils.convertScale(555_555_555_555L, 9, Long.MAX_VALUE)); + assertEquals(555_555_555_560L, DateTimeUtils.convertScale(555_555_555_555L, 8, Long.MAX_VALUE)); + assertEquals(555_555_555_600L, DateTimeUtils.convertScale(555_555_555_550L, 7, Long.MAX_VALUE)); + assertEquals(555_555_556_000L, DateTimeUtils.convertScale(555_555_555_500L, 6, Long.MAX_VALUE)); + assertEquals(555_555_560_000L, DateTimeUtils.convertScale(555_555_555_000L, 5, Long.MAX_VALUE)); + assertEquals(555_555_600_000L, DateTimeUtils.convertScale(555_555_550_000L, 4, Long.MAX_VALUE)); + assertEquals(555_556_000_000L, DateTimeUtils.convertScale(555_555_500_000L, 3, Long.MAX_VALUE)); + assertEquals(555_560_000_000L, DateTimeUtils.convertScale(555_555_000_000L, 2, Long.MAX_VALUE)); + assertEquals(555_600_000_000L, DateTimeUtils.convertScale(555_550_000_000L, 1, Long.MAX_VALUE)); + assertEquals(556_000_000_000L, DateTimeUtils.convertScale(555_500_000_000L, 0, Long.MAX_VALUE)); + assertEquals(100_999_999_999L, DateTimeUtils.convertScale(100_999_999_999L, 9, Long.MAX_VALUE)); + assertEquals(100_999_999_999L, DateTimeUtils.convertScale(100_999_999_999L, 9, DateTimeUtils.NANOS_PER_DAY)); + assertEquals(86_399_999_999_999L, DateTimeUtils.convertScale(86_399_999_999_999L, 9, Long.MAX_VALUE)); + for (int i = 8; i >= 0; i--) { + assertEquals(101_000_000_000L, DateTimeUtils.convertScale(100_999_999_999L, i, Long.MAX_VALUE)); + assertEquals(101_000_000_000L, + DateTimeUtils.convertScale(100_999_999_999L, i, DateTimeUtils.NANOS_PER_DAY)); + assertEquals(86_400_000_000_000L, DateTimeUtils.convertScale(86_399_999_999_999L, i, Long.MAX_VALUE)); + } + assertEquals(86_399_999_999_999L, + DateTimeUtils.convertScale(86_399_999_999_999L, 9, DateTimeUtils.NANOS_PER_DAY)); + assertEquals(86_399_999_999_990L, + DateTimeUtils.convertScale(86_399_999_999_999L, 8, DateTimeUtils.NANOS_PER_DAY)); + assertEquals(86_399_999_999_900L, + DateTimeUtils.convertScale(86_399_999_999_999L, 7, DateTimeUtils.NANOS_PER_DAY)); + assertEquals(86_399_999_999_000L, + DateTimeUtils.convertScale(86_399_999_999_999L, 6, DateTimeUtils.NANOS_PER_DAY)); + assertEquals(86_399_999_990_000L, + DateTimeUtils.convertScale(86_399_999_999_999L, 5, DateTimeUtils.NANOS_PER_DAY)); + assertEquals(86_399_999_900_000L, + DateTimeUtils.convertScale(86_399_999_999_999L, 4, DateTimeUtils.NANOS_PER_DAY)); + assertEquals(86_399_999_000_000L, + DateTimeUtils.convertScale(86_399_999_999_999L, 3, DateTimeUtils.NANOS_PER_DAY)); + assertEquals(86_399_990_000_000L, + DateTimeUtils.convertScale(86_399_999_999_999L, 2, DateTimeUtils.NANOS_PER_DAY)); + assertEquals(86_399_900_000_000L, + DateTimeUtils.convertScale(86_399_999_999_999L, 1, DateTimeUtils.NANOS_PER_DAY)); + assertEquals(86_399_000_000_000L, + DateTimeUtils.convertScale(86_399_999_999_999L, 0, DateTimeUtils.NANOS_PER_DAY)); + } + + private void testParseInterval() { + testParseIntervalSimple(IntervalQualifier.YEAR); + testParseIntervalSimple(IntervalQualifier.MONTH); + testParseIntervalSimple(IntervalQualifier.DAY); + testParseIntervalSimple(IntervalQualifier.HOUR); + testParseIntervalSimple(IntervalQualifier.MINUTE); + testParseIntervalSimple(IntervalQualifier.SECOND); + + testParseInterval(IntervalQualifier.YEAR_TO_MONTH, 10, 0, "10", "10-0"); + testParseInterval(IntervalQualifier.YEAR_TO_MONTH, 10, 11, "10-11"); + + testParseInterval(IntervalQualifier.DAY_TO_HOUR, 10, 0, "10", "10 00"); + testParseInterval(IntervalQualifier.DAY_TO_HOUR, 10, 11, "10 11"); + + testParseInterval(IntervalQualifier.DAY_TO_MINUTE, 10, 0, "10", "10 00:00"); + testParseInterval(IntervalQualifier.DAY_TO_MINUTE, 10, 11 * 60, "10 11", "10 11:00"); + testParseInterval(IntervalQualifier.DAY_TO_MINUTE, 10, 11 * 60 + 12, "10 11:12"); + + testParseInterval(IntervalQualifier.DAY_TO_SECOND, 10, 0, "10 00:00:00"); + testParseInterval(IntervalQualifier.DAY_TO_SECOND, 10, 11 * 3_600_000_000_000L, "10 11", "10 11:00:00"); + testParseInterval(IntervalQualifier.DAY_TO_SECOND, 10, 11 * 3_600_000_000_000L + 12 * 60_000_000_000L, + "10 11:12", "10 11:12:00"); + testParseInterval(IntervalQualifier.DAY_TO_SECOND, + 10, 11 * 3_600_000_000_000L + 12 * 60_000_000_000L + 13_000_000_000L, + "10 11:12:13"); + testParseInterval(IntervalQualifier.DAY_TO_SECOND, + 10, 11 * 3_600_000_000_000L + 12 * 60_000_000_000L + 13_123_456_789L, + "10 11:12:13.123456789"); + + testParseInterval(IntervalQualifier.HOUR_TO_MINUTE, 10, 0, "10", "10:00"); + testParseInterval(IntervalQualifier.HOUR_TO_MINUTE, 10, 11, "10:11"); + + testParseInterval(IntervalQualifier.HOUR_TO_SECOND, 10, 0, "10", "10:00:00"); + testParseInterval(IntervalQualifier.HOUR_TO_SECOND, 10, 11 * 60_000_000_000L, "10:11", "10:11:00"); + testParseInterval(IntervalQualifier.HOUR_TO_SECOND, 10, 11 * 60_000_000_000L + 12_000_000_000L, + "10:11:12"); + testParseInterval(IntervalQualifier.HOUR_TO_SECOND, 10, 11 * 60_000_000_000L + 12_123_456_789L, + "10:11:12.123456789"); + + testParseInterval(IntervalQualifier.MINUTE_TO_SECOND, 10, 0, "10", "10:00"); + testParseInterval(IntervalQualifier.MINUTE_TO_SECOND, 10, 11_000_000_000L, "10:11", "10:11"); + testParseInterval(IntervalQualifier.MINUTE_TO_SECOND, 10, 11_123_456_789L, "10:11.123456789"); + } + + private void testParseIntervalSimple(IntervalQualifier qualifier) { + testParseInterval(qualifier, 10, 0, "10"); + } + + private void testParseInterval(IntervalQualifier qualifier, long leading, long remaining, String s) { + testParseInterval(qualifier, leading, remaining, s, s); + } + + private void testParseInterval(IntervalQualifier qualifier, long leading, long remaining, String s, String full) { + testParseIntervalImpl(qualifier, false, leading, remaining, s, full); + testParseIntervalImpl(qualifier, true, leading, remaining, s, full); + } + + private void testParseIntervalImpl(IntervalQualifier qualifier, boolean negative, long leading, long remaining, + String s, String full) { + ValueInterval expected = ValueInterval.from(qualifier, negative, leading, remaining); + assertEquals(expected, IntervalUtils.parseInterval(qualifier, negative, s)); + StringBuilder b = new StringBuilder(); + b.append("INTERVAL ").append('\''); + if (negative) { + b.append('-'); + } + b.append(full).append("' ").append(qualifier); + assertEquals(b.toString(), expected.getString()); + } + + private void testGetTimeZoneOffset() { + TimeZone old = TimeZone.getDefault(); + TimeZone timeZone = TimeZone.getTimeZone("Europe/Paris"); + TimeZone.setDefault(timeZone); + DateTimeUtils.resetCalendar(); + try { + long n = -1111971600; + assertEquals(3_600, DateTimeUtils.getTimeZone().getTimeZoneOffsetUTC(n - 1)); + assertEquals(3_600_000, LegacyDateTimeUtils.getTimeZoneOffsetMillis(null, n * 1_000 - 1)); + assertEquals(0, DateTimeUtils.getTimeZone().getTimeZoneOffsetUTC(n)); + assertEquals(0, LegacyDateTimeUtils.getTimeZoneOffsetMillis(null, n * 1_000)); + assertEquals(0, DateTimeUtils.getTimeZone().getTimeZoneOffsetUTC(n + 1)); + assertEquals(0, LegacyDateTimeUtils.getTimeZoneOffsetMillis(null, n * 1_000 + 1)); + } finally { + TimeZone.setDefault(old); + DateTimeUtils.resetCalendar(); + } + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestDbException.java b/h2/src/test/org/h2/test/unit/TestDbException.java new file mode 100644 index 0000000..014b3d6 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestDbException.java @@ -0,0 +1,55 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.sql.SQLException; + +import org.h2.api.ErrorCode; +import org.h2.jdbc.JdbcException; +import org.h2.jdbc.JdbcSQLException; +import org.h2.message.DbException; +import org.h2.test.TestBase; + +/** + * Tests DbException class. + */ +public class TestDbException extends TestBase { + + /** + * Run just this test. + * + * @param a + * ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testGetJdbcSQLException(); + } + + private void testGetJdbcSQLException() throws Exception { + for (Field field : ErrorCode.class.getDeclaredFields()) { + if (field.getModifiers() == (Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL)) { + int errorCode = field.getInt(null); + SQLException exception = DbException.getJdbcSQLException(errorCode); + if (exception instanceof JdbcSQLException) { + fail("Custom exception expected for " + ErrorCode.class.getName() + '.' + field.getName() + " (" + + errorCode + ')'); + } + if (!(exception instanceof JdbcException)) { + fail("Custom exception for " + ErrorCode.class.getName() + '.' + field.getName() + " (" + errorCode + + ") should implement JdbcException"); + } + } + } + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestExit.java b/h2/src/test/org/h2/test/unit/TestExit.java new file mode 100644 index 0000000..472a627 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestExit.java @@ -0,0 +1,149 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.io.File; +import java.io.IOException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +import org.h2.api.DatabaseEventListener; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.test.utils.SelfDestructor; + +/** + * Tests the flag db_close_on_exit. A new process is started. + */ +public class TestExit extends TestDb { + + private static Connection conn; + + private static final int OPEN_WITH_CLOSE_ON_EXIT = 1, + OPEN_WITHOUT_CLOSE_ON_EXIT = 2; + + @Override + public boolean isEnabled() { + if (config.codeCoverage || config.networked) { + return false; + } + if (getBaseDir().indexOf(':') > 0) { + return false; + } + return true; + } + + @Override + public void test() throws Exception { + deleteDb("exit"); + String url = getURL(OPEN_WITH_CLOSE_ON_EXIT); + String selfDestruct = SelfDestructor.getPropertyString(60); + String[] procDef = { getJVM(), selfDestruct, "-cp", getClassPath(), + getClass().getName(), url }; + Process proc = Runtime.getRuntime().exec(procDef); + while (true) { + int ch = proc.getErrorStream().read(); + if (ch < 0) { + break; + } + System.out.print((char) ch); + } + while (true) { + int ch = proc.getInputStream().read(); + if (ch < 0) { + break; + } + System.out.print((char) ch); + } + proc.waitFor(); + Thread.sleep(100); + if (!getClosedFile().exists()) { + fail("did not close database"); + } + url = getURL(OPEN_WITHOUT_CLOSE_ON_EXIT); + procDef = new String[] { getJVM(), "-cp", getClassPath(), + getClass().getName(), url }; + proc = Runtime.getRuntime().exec(procDef); + proc.waitFor(); + Thread.sleep(100); + if (getClosedFile().exists()) { + fail("closed database"); + } + deleteDb("exit"); + } + + private String getURL(int action) { + String url = ""; + switch (action) { + case OPEN_WITH_CLOSE_ON_EXIT: + url = "jdbc:h2:" + getBaseDir() + + "/exit;database_event_listener='" + + MyDatabaseEventListener.class.getName() + + "';db_close_on_exit=true"; + break; + case OPEN_WITHOUT_CLOSE_ON_EXIT: + url = "jdbc:h2:" + getBaseDir() + + "/exit;database_event_listener='" + + MyDatabaseEventListener.class.getName() + + "';db_close_on_exit=false"; + break; + default: + } + url = getURL(url, true); + return url; + } + + /** + * This method is called when executing this application from the command + * line. + * + * @param args the command line parameters + */ + public static void main(String... args) throws SQLException { + SelfDestructor.startCountdown(60); + if (args.length == 0) { + System.exit(1); + } + String url = args[0]; + TestExit.execute(url); + } + + private static void execute(String url) throws SQLException { + org.h2.Driver.load(); + conn = open(url); + Connection conn2 = open(url); + conn2.close(); + // do not close + conn.isClosed(); + } + + private static Connection open(String url) throws SQLException { + getClosedFile().delete(); + return DriverManager.getConnection(url, "sa", ""); + } + + static File getClosedFile() { + return new File(TestBase.BASE_TEST_DIR + "/closed.txt"); + } + + /** + * A database event listener used in this test. + */ + public static final class MyDatabaseEventListener implements DatabaseEventListener { + + @Override + public void closingDatabase() { + try { + getClosedFile().createNewFile(); + } catch (IOException e) { + TestBase.logError("error", e); + } + } + + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestFile.java b/h2/src/test/org/h2/test/unit/TestFile.java new file mode 100644 index 0000000..107d5e4 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestFile.java @@ -0,0 +1,193 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.util.Random; +import org.h2.store.DataHandler; +import org.h2.store.FileStore; +import org.h2.store.LobStorageInterface; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.util.SmallLRUCache; +import org.h2.util.TempFileDeleter; +import org.h2.value.CompareMode; + +/** + * Tests the in-memory file store. + */ +public class TestFile extends TestBase implements DataHandler { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + doTest(false, false); + doTest(false, true); + doTest(true, false); + doTest(true, true); + } + + private void doTest(boolean nioMem, boolean compress) { + int len = getSize(1000, 10000); + Random random = new Random(); + FileStore mem = null, file = null; + byte[] buffMem = null; + byte[] buffFile = null; + String prefix = nioMem ? (compress ? "nioMemLZF:" : "nioMemFS:") + : (compress ? "memLZF:" : "memFS:"); + FileUtils.delete(prefix + "test"); + FileUtils.delete("~/testFile"); + + // config.traceTest = true; + + for (int i = 0; i < len; i++) { + if (buffMem == null) { + int l = 1 + random.nextInt(1000); + buffMem = new byte[l]; + buffFile = new byte[l]; + } + if (file == null) { + mem = FileStore.open(this, prefix + "test", "rw"); + file = FileStore.open(this, "~/testFile", "rw"); + } + assertEquals(file.getFilePointer(), mem.getFilePointer()); + assertEquals(file.length(), mem.length()); + int x = random.nextInt(100); + if ((x -= 20) < 0) { + if (file.length() > 0) { + long pos = random.nextInt((int) (file.length() / 16)) * 16; + trace("seek " + pos); + mem.seek(pos); + file.seek(pos); + } + } else if ((x -= 20) < 0) { + trace("close"); + mem.close(); + file.close(); + mem = null; + file = null; + } else if ((x -= 20) < 0) { + if (buffFile.length > 16) { + random.nextBytes(buffFile); + System.arraycopy(buffFile, 0, buffMem, 0, buffFile.length); + int off = random.nextInt(buffFile.length - 16); + int l = random.nextInt((buffFile.length - off) / 16) * 16; + trace("write " + off + " " + l); + mem.write(buffMem, off, l); + file.write(buffFile, off, l); + } + } else if ((x -= 20) < 0) { + if (buffFile.length > 16) { + int off = random.nextInt(buffFile.length - 16); + int l = random.nextInt((buffFile.length - off) / 16) * 16; + l = (int) Math + .min(l, file.length() - file.getFilePointer()); + trace("read " + off + " " + l); + Exception a = null, b = null; + try { + file.readFully(buffFile, off, l); + } catch (Exception e) { + a = e; + } + try { + mem.readFully(buffMem, off, l); + } catch (Exception e) { + b = e; + } + if (a != b) { + if (a == null || b == null) { + fail("only one threw an exception"); + } + } + assertEquals(buffMem, buffFile); + } + } else if ((x -= 10) < 0) { + trace("reset buffers"); + buffMem = null; + buffFile = null; + } else { + int l = random.nextInt(10000) * 16; + long p = file.getFilePointer(); + file.setLength(l); + mem.setLength(l); + trace("setLength " + l); + if (p > l) { + file.seek(l); + mem.seek(l); + } + } + } + if (mem != null) { + mem.close(); + file.close(); + } + FileUtils.delete(prefix + "test"); + FileUtils.delete("~/testFile"); + } + + @Override + public void checkPowerOff() { + // nothing to do + } + + @Override + public void checkWritingAllowed() { + // nothing to do + } + + @Override + public String getDatabasePath() { + return null; + } + + @Override + public Object getLobSyncObject() { + return null; + } + + @Override + public int getMaxLengthInplaceLob() { + return 0; + } + + @Override + public FileStore openFile(String name, String mode, boolean mustExist) { + return null; + } + + @Override + public SmallLRUCache getLobFileListCache() { + return null; + } + + @Override + public TempFileDeleter getTempFileDeleter() { + return TempFileDeleter.getInstance(); + } + + @Override + public LobStorageInterface getLobStorage() { + return null; + } + + @Override + public int readLob(long lobId, byte[] hmac, long offset, byte[] buff, + int off, int length) { + return -1; + } + + @Override + public CompareMode getCompareMode() { + return CompareMode.getInstance(null, 0); + } +} diff --git a/h2/src/test/org/h2/test/unit/TestFileLock.java b/h2/src/test/org/h2/test/unit/TestFileLock.java new file mode 100644 index 0000000..716c5b1 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestFileLock.java @@ -0,0 +1,157 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.io.File; +import java.sql.Connection; +import org.h2.api.ErrorCode; +import org.h2.engine.Constants; +import org.h2.message.TraceSystem; +import org.h2.store.FileLock; +import org.h2.store.FileLockMethod; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests the database file locking facility. Both lock files and sockets locking + * is tested. + */ +public class TestFileLock extends TestDb implements Runnable { + + private static volatile int locks; + private static volatile boolean stop; + private TestBase base; + private int wait; + private boolean allowSockets; + + public TestFileLock() { + // nothing to do + } + + TestFileLock(TestBase base, boolean allowSockets) { + this.base = base; + this.allowSockets = allowSockets; + } + + private String getFile() { + return getBaseDir() + "/test.lock"; + } + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + if (!getFile().startsWith(TestBase.BASE_TEST_DIR)) { + return false; + } + return true; + } + + @Override + public void test() throws Exception { + testFsFileLock(); + testFutureModificationDate(); + testSimple(); + test(false); + test(true); + } + + private void testFsFileLock() throws Exception { + deleteDb("fileLock"); + String url = "jdbc:h2:" + getBaseDir() + + "/fileLock;FILE_LOCK=FS;OPEN_NEW=TRUE"; + Connection conn = getConnection(url); + assertThrows(ErrorCode.DATABASE_ALREADY_OPEN_1, () -> getConnection(url)); + conn.close(); + } + + private void testFutureModificationDate() throws Exception { + File f = new File(getFile()); + f.delete(); + assertTrue(f.createNewFile()); + f.setLastModified(System.currentTimeMillis() + 10000); + FileLock lock = new FileLock(new TraceSystem(null), getFile(), + Constants.LOCK_SLEEP); + lock.lock(FileLockMethod.FILE); + lock.unlock(); + } + + private void testSimple() { + FileLock lock1 = new FileLock(new TraceSystem(null), getFile(), Constants.LOCK_SLEEP); + FileLock lock2 = new FileLock(new TraceSystem(null), getFile(), Constants.LOCK_SLEEP); + lock1.lock(FileLockMethod.FILE); + assertThrows(ErrorCode.DATABASE_ALREADY_OPEN_1, () -> lock2.lock(FileLockMethod.FILE)); + lock1.unlock(); + FileLock lock3 = new FileLock(new TraceSystem(null), getFile(), Constants.LOCK_SLEEP); + lock3.lock(FileLockMethod.FILE); + lock3.unlock(); + } + + private void test(boolean allowSocketsLock) throws Exception { + int threadCount = getSize(3, 5); + wait = getSize(20, 200); + Thread[] threads = new Thread[threadCount]; + new File(getFile()).delete(); + for (int i = 0; i < threadCount; i++) { + threads[i] = new Thread(new TestFileLock(this, allowSocketsLock)); + threads[i].start(); + Thread.sleep(wait + (int) (Math.random() * wait)); + } + trace("wait"); + Thread.sleep(500); + stop = true; + trace("STOP file"); + for (int i = 0; i < threadCount; i++) { + threads[i].join(); + } + assertEquals(0, locks); + } + + @Override + public void run() { + FileLock lock = null; + while (!stop) { + lock = new FileLock(new TraceSystem(null), getFile(), 100); + try { + lock.lock(allowSockets ? FileLockMethod.SOCKET + : FileLockMethod.FILE); + base.trace(lock + " locked"); + locks++; + if (locks > 1) { + System.err.println("ERROR! LOCKS=" + locks + " sockets=" + + allowSockets); + stop = true; + } + Thread.sleep(wait + (int) (Math.random() * wait)); + locks--; + base.trace(lock + " unlock"); + lock.unlock(); + if (locks < 0) { + System.err.println("ERROR! LOCKS=" + locks); + stop = true; + } + } catch (Exception e) { + // log(id+" cannot lock: " + e); + } + try { + Thread.sleep(wait + (int) (Math.random() * wait)); + } catch (InterruptedException e1) { + // ignore + } + } + if (lock != null) { + lock.unlock(); + } + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestFileLockProcess.java b/h2/src/test/org/h2/test/unit/TestFileLockProcess.java new file mode 100644 index 0000000..b69846f --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestFileLockProcess.java @@ -0,0 +1,127 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.util.ArrayList; + +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.test.utils.SelfDestructor; + +/** + * Tests database file locking. + * A new process is started. + */ +public class TestFileLockProcess extends TestDb { + + /** + * This method is called when executing this application from the command + * line. + * + * @param args the command line parameters + */ + public static void main(String... args) throws Exception { + SelfDestructor.startCountdown(60); + if (args.length == 0) { + TestBase.createCaller().init().testFromMain(); + return; + } + String url = args[0]; + execute(url); + } + + private static void execute(String url) { + org.h2.Driver.load(); + try { + Class.forName("org.h2.Driver"); + Connection conn = DriverManager.getConnection(url); + System.out.println("!"); + conn.close(); + } catch (Exception e) { + // failed - expected + } + } + + @Override + public boolean isEnabled() { + if (config.codeCoverage || config.networked) { + return false; + } + if (getBaseDir().indexOf(':') > 0) { + return false; + } + return true; + } + + @Override + public void test() throws Exception { + deleteDb("lock"); + String url = "jdbc:h2:"+getBaseDir()+"/lock"; + + println("socket"); + test(4, url + ";file_lock=socket"); + + println("fs"); + test(4, url + ";file_lock=fs"); + + println("default"); + test(50, url); + + deleteDb("lock"); + } + + private void test(int count, String url) throws Exception { + url = getURL(url, true); + Connection conn = getConnection(url); + String selfDestruct = SelfDestructor.getPropertyString(60); + String[] procDef = { getJVM(), selfDestruct, + "-cp", getClassPath(), + getClass().getName(), url }; + ArrayList processes = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + Thread.sleep(100); + if (i % 10 == 0) { + println(i + "/" + count); + } + Process proc = Runtime.getRuntime().exec(procDef); + processes.add(proc); + } + for (int i = 0; i < count; i++) { + Process proc = processes.get(i); + StringBuilder buff = new StringBuilder(); + while (true) { + int ch = proc.getErrorStream().read(); + if (ch < 0) { + break; + } + System.out.print((char) ch); + buff.append((char) ch); + } + while (true) { + int ch = proc.getInputStream().read(); + if (ch < 0) { + break; + } + System.out.print((char) ch); + buff.append((char) ch); + } + proc.waitFor(); + + // The travis build somehow generates messages like this from javac. + // No idea where it is coming from. + String processOutput = buff.toString(); + processOutput = processOutput.replaceAll("Picked up _JAVA_OPTIONS: -Xmx2048m -Xms512m", "").trim(); + + assertEquals(0, proc.exitValue()); + assertTrue(i + ": " + buff.toString(), processOutput.isEmpty()); + } + Thread.sleep(100); + conn.close(); + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestFileSystem.java b/h2/src/test/org/h2/test/unit/TestFileSystem.java new file mode 100644 index 0000000..8bd7dc1 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestFileSystem.java @@ -0,0 +1,802 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.channels.NonWritableChannelException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.List; +import java.util.Random; +import java.util.concurrent.atomic.AtomicIntegerArray; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; +import org.h2.dev.fs.FilePathZip2; +import org.h2.message.DbException; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.cache.FilePathCache; +import org.h2.store.fs.FilePath; +import org.h2.store.fs.FileUtils; +import org.h2.store.fs.encrypt.FilePathEncrypt; +import org.h2.store.fs.rec.FilePathRec; +import org.h2.test.TestBase; +import org.h2.test.utils.FilePathDebug; +import org.h2.tools.Backup; +import org.h2.tools.DeleteDbFiles; +import org.h2.util.IOUtils; +import org.h2.util.Task; + +/** + * Tests various file system. + */ +public class TestFileSystem extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); + // test.config.traceTest = true; + test.testFromMain(); + } + + @Override + public void test() throws Exception { + testFileSystem(getBaseDir() + "/fs"); + + testAbsoluteRelative(); + testDirectories(getBaseDir()); + testMoveTo(getBaseDir()); + FilePathZip2.register(); + FilePath.register(new FilePathCache()); + FilePathRec.register(); + testZipFileSystem("zip:"); + testZipFileSystem("cache:zip:"); + testZipFileSystem("zip2:"); + testZipFileSystem("cache:zip2:"); + testMemFsDir(); + testClasspath(); + FilePathDebug.register().setTrace(true); + FilePathEncrypt.register(); + testSimpleExpandTruncateSize(); + testSplitDatabaseInZip(); + testDatabaseInMemFileSys(); + testDatabaseInJar(); + // set default part size to 1 << 10 + String f = "split:10:" + getBaseDir() + "/fs"; + FileUtils.toRealPath(f); + testFileSystem(getBaseDir() + "/fs"); + testFileSystem("async:" + getBaseDir() + "/fs"); + testFileSystem("memFS:"); + testFileSystem("memLZF:"); + testFileSystem("nioMemFS:"); + testFileSystem("nioMemLZF:1:"); + // 12% compressLaterCache + testFileSystem("nioMemLZF:12:"); + testFileSystem("rec:memFS:"); + testUserHome(); + try { + testFileSystem("cache:" + getBaseDir() + "/fs"); + testFileSystem("nioMapped:" + getBaseDir() + "/fs"); + testFileSystem("encrypt:0007:" + getBaseDir() + "/fs"); + testFileSystem("cache:encrypt:0007:" + getBaseDir() + "/fs"); + if (!config.splitFileSystem) { + testFileSystem("split:" + getBaseDir() + "/fs"); + testFileSystem("split:nioMapped:" + getBaseDir() + "/fs"); + } + } catch (Exception | Error e) { + e.printStackTrace(); + throw e; + } finally { + FileUtils.delete(getBaseDir() + "/fs"); + } + } + + private void testZipFileSystem(String prefix) throws IOException { + Random r = new Random(1); + for (int i = 0; i < 5; i++) { + testZipFileSystem(prefix, r); + } + } + + private void testZipFileSystem(String prefix, Random r) throws IOException { + byte[] data = new byte[r.nextInt(16 * 1024)]; + long x = r.nextLong(); + FilePath file = FilePath.get(getBaseDir() + "/fs/readonly" + x + ".zip"); + r.nextBytes(data); + OutputStream out = file.newOutputStream(false); + ZipOutputStream zipOut = new ZipOutputStream(out); + ZipEntry entry = new ZipEntry("data"); + zipOut.putNextEntry(entry); + zipOut.write(data); + zipOut.closeEntry(); + zipOut.close(); + out.close(); + FilePath fp = FilePath.get( + prefix + getBaseDir() + "/fs/readonly" + x + ".zip!data"); + FileChannel fc = fp.open("r"); + StringBuilder buff = new StringBuilder(); + try { + int pos = 0; + for (int i = 0; i < 100; i++) { + trace("op " + i); + switch (r.nextInt(5)) { + case 0: { + int p = r.nextInt(data.length); + trace("seek " + p); + buff.append("seek " + p + "\n"); + fc.position(p); + pos = p; + break; + } + case 1: { + int len = r.nextInt(1000); + int offset = r.nextInt(100); + int arrayLen = len + offset; + len = Math.min(len, data.length - pos); + byte[] b1 = new byte[arrayLen]; + byte[] b2 = new byte[arrayLen]; + trace("readFully " + len); + buff.append("readFully " + len + "\n"); + System.arraycopy(data, pos, b1, offset, len); + ByteBuffer byteBuff = createSlicedBuffer(b2, offset, len); + FileUtils.readFully(fc, byteBuff); + assertEquals(b1, b2); + pos += len; + break; + } + case 2: { + int len = r.nextInt(1000); + int offset = r.nextInt(100); + int arrayLen = len + offset; + int p = r.nextInt(data.length); + len = Math.min(len, data.length - p); + byte[] b1 = new byte[arrayLen]; + byte[] b2 = new byte[arrayLen]; + trace("readFully " + p + " " + len); + buff.append("readFully " + p + " " + len + "\n"); + System.arraycopy(data, p, b1, offset, len); + ByteBuffer byteBuff = createSlicedBuffer(b2, offset, len); + DataUtils.readFully(fc, p, byteBuff); + assertEquals(b1, b2); + break; + } + case 3: { + trace("getFilePointer"); + buff.append("getFilePointer\n"); + assertEquals(pos, fc.position()); + break; + } + case 4: { + trace("length " + data.length); + buff.append("length " + data.length + "\n"); + assertEquals(data.length, fc.size()); + break; + } + default: + } + } + fc.close(); + file.delete(); + } catch (Throwable e) { + e.printStackTrace(); + fail("Exception: " + e + "\n"+ buff.toString()); + } + } + + private void testAbsoluteRelative() { + assertFalse(FileUtils.isAbsolute("test/abc")); + assertFalse(FileUtils.isAbsolute("./test/abc")); + assertTrue(FileUtils.isAbsolute("~/test/abc")); + assertTrue(FileUtils.isAbsolute("/test/abc")); + } + + private void testMemFsDir() throws IOException { + FileUtils.newOutputStream("memFS:data/test/a.txt", false).close(); + assertEquals(FileUtils.newDirectoryStream("memFS:data/test").toString(), + 1, FileUtils.newDirectoryStream("memFS:data/test").size()); + FileUtils.deleteRecursive("memFS:", false); + } + + private void testClasspath() throws IOException { + String resource = "org/h2/test/scripts/testSimple.sql"; + InputStream in; + in = getClass().getResourceAsStream("/" + resource); + assertNotNull(in); + in.close(); + in = getClass().getClassLoader().getResourceAsStream(resource); + assertNotNull(in); + in.close(); + in = FileUtils.newInputStream("classpath:" + resource); + assertNotNull(in); + in.close(); + in = FileUtils.newInputStream("classpath:/" + resource); + assertNotNull(in); + in.close(); + } + + private void testSimpleExpandTruncateSize() throws Exception { + String f = "memFS:" + getBaseDir() + "/fs/test.data"; + FileUtils.createDirectories("memFS:" + getBaseDir() + "/fs"); + FileChannel c = FileUtils.open(f, "rw"); + c.position(4000); + c.write(ByteBuffer.wrap(new byte[1])); + FileLock lock = c.tryLock(); + c.truncate(0); + if (lock != null) { + lock.release(); + } + c.close(); + FileUtils.deleteRecursive("memFS:", false); + } + + private void testSplitDatabaseInZip() throws SQLException { + String dir = getBaseDir() + "/fs"; + FileUtils.deleteRecursive(dir, false); + Connection conn; + Statement stat; + conn = DriverManager.getConnection("jdbc:h2:split:18:"+dir+"/test"); + stat = conn.createStatement(); + stat.execute( + "create table test(id int primary key, name varchar) " + + "as select x, space(10000) from system_range(1, 100)"); + // stat.execute("shutdown defrag"); + conn.close(); + Backup.execute(dir + "/test.zip", dir, "", true); + DeleteDbFiles.execute("split:" + dir, "test", true); + conn = DriverManager.getConnection( + "jdbc:h2:split:zip:"+dir+"/test.zip!/test"); + conn.createStatement().execute("select * from test where id=1"); + conn.close(); + FileUtils.deleteRecursive(dir, false); + } + + private void testDatabaseInMemFileSys() throws SQLException { + org.h2.Driver.load(); + String dir = getBaseDir() + "/fsMem"; + FileUtils.deleteRecursive(dir, false); + String url = "jdbc:h2:" + dir + "/fsMem"; + Connection conn = DriverManager.getConnection(url, "sa", "sa"); + conn.createStatement().execute( + "CREATE TABLE TEST AS SELECT * FROM DUAL"); + conn.createStatement().execute( + "BACKUP TO '" + getBaseDir() + "/fsMem.zip'"); + conn.close(); + org.h2.tools.Restore.main("-file", getBaseDir() + "/fsMem.zip", "-dir", "memFS:"); + conn = DriverManager.getConnection("jdbc:h2:memFS:fsMem", "sa", "sa"); + ResultSet rs = conn.createStatement() + .executeQuery("SELECT * FROM TEST"); + rs.close(); + conn.close(); + FileUtils.deleteRecursive(dir, false); + FileUtils.delete(getBaseDir() + "/fsMem.zip"); + FileUtils.delete("memFS:fsMem.mv.db"); + } + + private void testDatabaseInJar() throws Exception { + if (getBaseDir().indexOf(':') > 0) { + return; + } + if (config.networked) { + return; + } + org.h2.Driver.load(); + String dir = getBaseDir() + "/fsJar"; + String url = "jdbc:h2:" + dir + "/fsJar"; + Connection conn = DriverManager.getConnection(url, "sa", "sa"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, " + + "name varchar, b blob, c clob)"); + stat.execute("insert into test values(1, 'Hello', " + + "SECURE_RAND(2000), space(2000))"); + ResultSet rs; + rs = stat.executeQuery("select * from test"); + rs.next(); + byte[] b1 = rs.getBytes(3); + String s1 = rs.getString(4); + conn.close(); + conn = DriverManager.getConnection(url, "sa", "sa"); + stat = conn.createStatement(); + stat.execute("backup to '" + getBaseDir() + "/fsJar.zip'"); + conn.close(); + + FileUtils.deleteRecursive(dir, false); + for (String f : FileUtils.newDirectoryStream( + "zip:" + getBaseDir() + "/fsJar.zip")) { + assertFalse(FileUtils.isAbsolute(f)); + assertFalse(FileUtils.isDirectory(f)); + assertTrue(FileUtils.size(f) > 0); + assertTrue(f.endsWith(FileUtils.getName(f))); + assertEquals(0, FileUtils.lastModified(f)); + FileUtils.setReadOnly(f); + assertFalse(FileUtils.canWrite(f)); + InputStream in = FileUtils.newInputStream(f); + int len = 0; + while (in.read() >= 0) { + len++; + } + assertEquals(len, FileUtils.size(f)); + testReadOnly(f); + } + String urlJar = "jdbc:h2:zip:" + getBaseDir() + "/fsJar.zip!/fsJar"; + conn = DriverManager.getConnection(urlJar, "sa", "sa"); + stat = conn.createStatement(); + rs = stat.executeQuery("select * from test"); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + byte[] b2 = rs.getBytes(3); + String s2 = rs.getString(4); + assertEquals(2000, b2.length); + assertEquals(2000, s2.length()); + assertEquals(b1, b2); + assertEquals(s1, s2); + assertFalse(rs.next()); + conn.close(); + FileUtils.delete(getBaseDir() + "/fsJar.zip"); + } + + private void testReadOnly(final String f) throws IOException { + assertThrows(IOException.class, () -> FileUtils.newOutputStream(f, false)); + assertThrows(DbException.class, () -> FileUtils.move(f, f)); + assertThrows(DbException.class, () -> FileUtils.move(f, f)); + assertThrows(IOException.class, () -> FileUtils.createTempFile(f, ".tmp", false)); + final FileChannel channel = FileUtils.open(f, "r"); + assertThrows(NonWritableChannelException.class, () -> channel.write(ByteBuffer.allocate(1))); + assertThrows(IOException.class, () -> channel.truncate(0)); + assertNull(channel.tryLock()); + channel.force(false); + channel.close(); + } + + private void testUserHome() { + String userDir = System.getProperty("user.home").replace('\\', '/'); + assertTrue(FileUtils.toRealPath("~/test").startsWith(userDir)); + assertTrue(FileUtils.toRealPath("file:~/test").startsWith(userDir)); + } + + private void testFileSystem(String fsBase) throws Exception { + testConcurrent(fsBase); + testRootExists(fsBase); + testPositionedReadWrite(fsBase); + testSetReadOnly(fsBase); + testParentEventuallyReturnsNull(fsBase); + testSimple(fsBase); + testTempFile(fsBase); + testRandomAccess(fsBase); + } + + private void testRootExists(String fsBase) { + String fileName = fsBase + "/testFile"; + FilePath p = FilePath.get(fileName); + while (p.getParent() != null) { + p = p.getParent(); + } + assertTrue(p.exists()); + } + + private void testSetReadOnly(String fsBase) { + String fileName = fsBase + "/testFile"; + if (FileUtils.exists(fileName)) { + FileUtils.delete(fileName); + } + if (FileUtils.createFile(fileName)) { + FileUtils.setReadOnly(fileName); + assertFalse(FileUtils.canWrite(fileName)); + FileUtils.delete(fileName); + } + } + + private void testDirectories(String fsBase) { + final String fileName = fsBase + "/testFile"; + if (FileUtils.exists(fileName)) { + FileUtils.delete(fileName); + } + if (FileUtils.createFile(fileName)) { + assertThrows(DbException.class, () -> FileUtils.createDirectory(fileName)); + assertThrows(DbException.class, () -> FileUtils.createDirectories(fileName + "/test")); + FileUtils.delete(fileName); + } + } + + private void testMoveTo(String fsBase) { + final String fileName = fsBase + "/testFile"; + final String fileName2 = fsBase + "/testFile2"; + if (FileUtils.exists(fileName)) { + FileUtils.delete(fileName); + } + if (FileUtils.createFile(fileName)) { + FileUtils.move(fileName, fileName2); + FileUtils.createFile(fileName); + assertThrows(DbException.class, () -> FileUtils.move(fileName2, fileName)); + FileUtils.delete(fileName); + FileUtils.delete(fileName2); + assertThrows(DbException.class, () -> FileUtils.move(fileName, fileName2)); + } + } + + private void testParentEventuallyReturnsNull(String fsBase) { + FilePath p = FilePath.get(fsBase + "/testFile"); + assertTrue(p.getScheme().length() > 0); + for (int i = 0; i < 100; i++) { + if (p == null) { + return; + } + p = p.getParent(); + } + fail("Parent is not null: " + p); + String path = fsBase + "/testFile"; + for (int i = 0; i < 100; i++) { + if (path == null) { + return; + } + path = FileUtils.getParent(path); + } + fail("Parent is not null: " + path); + } + + private void testSimple(final String fsBase) throws Exception { + long time = System.currentTimeMillis(); + for (String s : FileUtils.newDirectoryStream(fsBase)) { + FileUtils.delete(s); + } + FileUtils.createDirectories(fsBase + "/test"); + assertTrue(FileUtils.exists(fsBase)); + FileUtils.delete(fsBase + "/test"); + FileUtils.delete(fsBase + "/test2"); + assertTrue(FileUtils.createFile(fsBase + "/test")); + List p = FilePath.get(fsBase).newDirectoryStream(); + assertEquals(1, p.size()); + String can = FilePath.get(fsBase + "/test").toRealPath().toString(); + assertEquals(can, p.get(0).toString()); + assertTrue(FileUtils.canWrite(fsBase + "/test")); + FileChannel channel = FileUtils.open(fsBase + "/test", "rw"); + byte[] buffer = new byte[10000]; + Random random = new Random(1); + random.nextBytes(buffer); + channel.write(ByteBuffer.wrap(buffer)); + assertEquals(10000, channel.size()); + channel.position(20000); + assertEquals(20000, channel.position()); + assertEquals(-1, channel.read(ByteBuffer.wrap(buffer, 0, 1))); + String path = fsBase + "/test"; + assertEquals("test", FileUtils.getName(path)); + can = FilePath.get(fsBase).toRealPath().toString(); + String can2 = FileUtils.toRealPath(FileUtils.getParent(path)); + assertEquals(can, can2); + FileLock lock = channel.tryLock(); + if (lock != null) { + lock.release(); + } + assertEquals(10000, channel.size()); + channel.close(); + assertEquals(10000, FileUtils.size(fsBase + "/test")); + channel = FileUtils.open(fsBase + "/test", "r"); + final byte[] test = new byte[10000]; + FileUtils.readFully(channel, ByteBuffer.wrap(test, 0, 10000)); + assertEquals(buffer, test); + final FileChannel fc = channel; + assertThrows(NonWritableChannelException.class, () -> fc.write(ByteBuffer.wrap(test, 0, 10))); + assertThrows(NonWritableChannelException.class, () -> fc.truncate(10)); + channel.close(); + long lastMod = FileUtils.lastModified(fsBase + "/test"); + if (lastMod < time - 1999) { + // at most 2 seconds difference + assertEquals(time, lastMod); + } + assertEquals(10000, FileUtils.size(fsBase + "/test")); + List list = FileUtils.newDirectoryStream(fsBase); + assertEquals(1, list.size()); + assertTrue(list.get(0).endsWith("test")); + IOUtils.copyFiles(fsBase + "/test", fsBase + "/test3"); + FileUtils.move(fsBase + "/test3", fsBase + "/test2"); + FileUtils.move(fsBase + "/test2", fsBase + "/test2"); + assertFalse(FileUtils.exists(fsBase + "/test3")); + assertTrue(FileUtils.exists(fsBase + "/test2")); + assertEquals(10000, FileUtils.size(fsBase + "/test2")); + byte[] buffer2 = new byte[10000]; + InputStream in = FileUtils.newInputStream(fsBase + "/test2"); + int pos = 0; + while (true) { + int l = in.read(buffer2, pos, Math.min(10000 - pos, 1000)); + if (l <= 0) { + break; + } + pos += l; + } + in.close(); + assertEquals(10000, pos); + assertEquals(buffer, buffer2); + + assertTrue(FileUtils.tryDelete(fsBase + "/test2")); + FileUtils.delete(fsBase + "/test"); + if (fsBase.indexOf("memFS:") < 0 && fsBase.indexOf("memLZF:") < 0 + && fsBase.indexOf("nioMemFS:") < 0 && fsBase.indexOf("nioMemLZF:") < 0) { + FileUtils.createDirectories(fsBase + "/testDir"); + assertTrue(FileUtils.isDirectory(fsBase + "/testDir")); + if (!fsBase.startsWith("jdbc:")) { + FileUtils.deleteRecursive(fsBase + "/testDir", false); + assertFalse(FileUtils.exists(fsBase + "/testDir")); + } + } + } + + private void testPositionedReadWrite(String fsBase) throws IOException { + FileUtils.deleteRecursive(fsBase + "/testFile", false); + FileUtils.delete(fsBase + "/testFile"); + FileUtils.createDirectories(fsBase); + assertTrue(FileUtils.createFile(fsBase + "/testFile")); + FileChannel fc = FilePath.get(fsBase + "/testFile").open("rw"); + ByteBuffer buff = ByteBuffer.allocate(4000); + for (int i = 0; i < 4000; i++) { + buff.put((byte) i); + } + buff.flip(); + fc.write(buff, 96); + assertEquals(0, fc.position()); + assertEquals(4096, fc.size()); + buff = ByteBuffer.allocate(4000); + assertEquals(4000, fc.read(buff, 96)); + assertEquals(0, fc.position()); + buff.flip(); + for (int i = 0; i < 4000; i++) { + assertEquals((byte) i, buff.get()); + } + buff = ByteBuffer.allocate(0); + assertTrue(fc.read(buff, 8000) <= 0); + assertEquals(0, fc.position()); + assertTrue(fc.read(buff, 4000) <= 0); + assertEquals(0, fc.position()); + assertTrue(fc.read(buff, 2000) <= 0); + assertEquals(0, fc.position()); + buff = ByteBuffer.allocate(1); + assertEquals(-1, fc.read(buff, 8000)); + assertEquals(1, fc.read(buff, 4000)); + buff.flip(); + assertEquals(1, fc.read(buff, 2000)); + fc.close(); + } + + private void testRandomAccess(String fsBase) throws Exception { + testRandomAccess(fsBase, 1); + } + + private void testRandomAccess(String fsBase, int seed) throws Exception { + StringBuilder buff = new StringBuilder(); + String s = FileUtils.createTempFile(fsBase + "/tmp", ".tmp", false); + File file = new File(TestBase.BASE_TEST_DIR + "/tmp"); + file.getParentFile().mkdirs(); + file.delete(); + RandomAccessFile ra = new RandomAccessFile(file, "rw"); + FileUtils.delete(s); + FileChannel f = FileUtils.open(s, "rw"); + assertEquals(-1, f.read(ByteBuffer.wrap(new byte[1]))); + f.force(true); + Random random = new Random(seed); + int size = getSize(100, 500); + try { + for (int i = 0; i < size; i++) { + trace("op " + i); + int pos = random.nextInt(10000); + switch (random.nextInt(7)) { + case 0: { + pos = (int) Math.min(pos, ra.length()); + trace("seek " + pos); + buff.append("seek " + pos + "\n"); + f.position(pos); + ra.seek(pos); + break; + } + case 1: { + int arrayLen = random.nextInt(1000); + int offset = arrayLen / 10; + offset = offset == 0 ? 0 : random.nextInt(offset); + int len = arrayLen == 0 ? 0 : random.nextInt(arrayLen - offset); + byte[] buffer = new byte[arrayLen]; + ByteBuffer byteBuff = createSlicedBuffer(buffer, offset, len); + random.nextBytes(buffer); + trace("write " + offset + " len " + len); + buff.append("write " + offset + " " + len + "\n"); + f.write(byteBuff); + ra.write(buffer, offset, len); + break; + } + case 2: { + trace("truncate " + pos); + buff.append("truncate " + pos + "\n"); + f.truncate(pos); + if (pos < ra.length()) { + // truncate is supposed to have no effect if the + // position is larger than the current size + ra.setLength(pos); + } + assertEquals(ra.getFilePointer(), f.position()); + break; + } + case 3: { + int len = random.nextInt(1000); + int offset = random.nextInt(100); + int arrayLen = len + offset; + len = (int) Math.min(len, ra.length() - ra.getFilePointer()); + byte[] b1 = new byte[arrayLen]; + byte[] b2 = new byte[arrayLen]; + trace("readFully " + len); + buff.append("readFully " + len + "\n"); + ra.readFully(b1, offset, len); + ByteBuffer byteBuff = createSlicedBuffer(b2, offset, len); + FileUtils.readFully(f, byteBuff); + assertEquals(b1, b2); + break; + } + case 4: { + trace("getFilePointer"); + buff.append("getFilePointer\n"); + assertEquals(ra.getFilePointer(), f.position()); + break; + } + case 5: { + trace("length " + ra.length()); + buff.append("length " + ra.length() + "\n"); + assertEquals(ra.length(), f.size()); + break; + } + case 6: { + trace("reopen"); + buff.append("reopen\n"); + f.close(); + ra.close(); + ra = new RandomAccessFile(file, "rw"); + f = FileUtils.open(s, "rw"); + assertEquals(ra.length(), f.size()); + break; + } + default: + } + } + } catch (Throwable e) { + e.printStackTrace(); + fail("Exception: " + e + "\n"+ buff.toString()); + } finally { + f.close(); + ra.close(); + file.delete(); + FileUtils.delete(s); + } + } + + private static ByteBuffer createSlicedBuffer(byte[] buffer, int offset, + int len) { + ByteBuffer byteBuff = ByteBuffer.wrap(buffer); + byteBuff.position(offset); + // force the arrayOffset to be non-0 + byteBuff = byteBuff.slice(); + byteBuff.limit(len); + return byteBuff; + } + + private void testTempFile(String fsBase) throws Exception { + int len = 10000; + String s = FileUtils.createTempFile(fsBase + "/tmp", ".tmp", false); + OutputStream out = FileUtils.newOutputStream(s, false); + byte[] buffer = new byte[len]; + out.write(buffer); + out.close(); + out = FileUtils.newOutputStream(s, true); + out.write(1); + out.close(); + InputStream in = FileUtils.newInputStream(s); + for (int i = 0; i < len; i++) { + assertEquals(0, in.read()); + } + assertEquals(1, in.read()); + assertEquals(-1, in.read()); + in.close(); + out.close(); + FileUtils.delete(s); + } + + private void testConcurrent(String fsBase) throws Exception { + String s = FileUtils.createTempFile(fsBase + "/tmp", ".tmp", false); + File file = new File(TestBase.BASE_TEST_DIR + "/tmp"); + file.getParentFile().mkdirs(); + file.delete(); + RandomAccessFile ra = new RandomAccessFile(file, "rw"); + FileUtils.delete(s); + final FileChannel f = FileUtils.open(s, "rw"); + final int size = getSize(10, 50); + f.write(ByteBuffer.allocate(size * 64 * 1024)); + AtomicIntegerArray locks = new AtomicIntegerArray(size); + AtomicIntegerArray expected = new AtomicIntegerArray(size); + Random random = new Random(1); + System.gc(); + Task task = new Task() { + @Override + public void call() throws Exception { + ByteBuffer byteBuff = ByteBuffer.allocate(16); + while (!stop) { + for (int pos = 0; pos < size; pos++) { + byteBuff.clear(); + int e; + while (!locks.compareAndSet(pos, 0, 1)) { + } + try { + e = expected.get(pos); + f.read(byteBuff, pos * 64 * 1024); + } finally { + locks.set(pos, 0); + } + byteBuff.position(0); + int x = byteBuff.getInt(); + int y = byteBuff.getInt(); + assertEquals(e, x); + assertEquals(e, y); + Thread.yield(); + } + } + } + }; + task.execute(); + try { + ByteBuffer byteBuff = ByteBuffer.allocate(16); + int operations = 10000; + for (int i = 0; i < operations; i++) { + byteBuff.position(0); + byteBuff.putInt(i); + byteBuff.putInt(i); + byteBuff.flip(); + int pos = random.nextInt(size); + while (!locks.compareAndSet(pos, 0, 1)) { + } + try { + f.write(byteBuff, pos * 64 * 1024); + expected.set(pos, i); + } finally { + locks.set(pos, 0); + } + pos = random.nextInt(size); + byteBuff.clear(); + int e; + while (!locks.compareAndSet(pos, 0, 1)) { + } + try { + e = expected.get(pos); + f.read(byteBuff, pos * 64 * 1024); + } finally { + locks.set(pos, 0); + } + byteBuff.limit(16); + byteBuff.position(0); + int x = byteBuff.getInt(); + int y = byteBuff.getInt(); + assertEquals(e, x); + assertEquals(e, y); + } + } catch (Throwable e) { + e.printStackTrace(); + fail("Exception: " + e); + } finally { + task.get(); + f.close(); + ra.close(); + file.delete(); + FileUtils.delete(s); + System.gc(); + } + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestFtp.java b/h2/src/test/org/h2/test/unit/TestFtp.java new file mode 100644 index 0000000..53ba7d2 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestFtp.java @@ -0,0 +1,82 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import org.h2.dev.ftp.FtpClient; +import org.h2.dev.ftp.server.FtpEvent; +import org.h2.dev.ftp.server.FtpEventListener; +import org.h2.dev.ftp.server.FtpServer; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.tools.Server; + +/** + * Tests the FTP server tool. + */ +public class TestFtp extends TestBase implements FtpEventListener { + + private FtpEvent lastEvent; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + if (getBaseDir().indexOf(':') > 0) { + return false; + } + return true; + } + + @Override + public void test() throws Exception { + FileUtils.delete(getBaseDir() + "/ftp"); + test(getBaseDir()); + FileUtils.delete(getBaseDir() + "/ftp"); + } + + private void test(String dir) throws Exception { + Server server = FtpServer.createFtpServer( + "-ftpDir", dir, "-ftpPort", "8121").start(); + FtpServer ftp = (FtpServer) server.getService(); + ftp.setEventListener(this); + FtpClient client = FtpClient.open("localhost:8121"); + client.login("sa", "sa"); + client.makeDirectory("ftp"); + client.changeWorkingDirectory("ftp"); + assertEquals("CWD", lastEvent.getCommand()); + client.makeDirectory("hello"); + client.changeWorkingDirectory("hello"); + client.changeDirectoryUp(); + assertEquals("CDUP", lastEvent.getCommand()); + client.nameList("hello"); + client.removeDirectory("hello"); + client.close(); + server.stop(); + } + + @Override + public void beforeCommand(FtpEvent event) { + lastEvent = event; + } + + @Override + public void afterCommand(FtpEvent event) { + lastEvent = event; + } + + @Override + public void onUnsupportedCommand(FtpEvent event) { + lastEvent = event; + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestGeometryUtils.java b/h2/src/test/org/h2/test/unit/TestGeometryUtils.java new file mode 100644 index 0000000..6b8f1b5 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestGeometryUtils.java @@ -0,0 +1,531 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XY; +import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYM; +import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYZ; +import static org.h2.util.geometry.GeometryUtils.DIMENSION_SYSTEM_XYZM; +import static org.h2.util.geometry.GeometryUtils.GEOMETRY_COLLECTION; +import static org.h2.util.geometry.GeometryUtils.M; +import static org.h2.util.geometry.GeometryUtils.MAX_X; +import static org.h2.util.geometry.GeometryUtils.MAX_Y; +import static org.h2.util.geometry.GeometryUtils.MIN_X; +import static org.h2.util.geometry.GeometryUtils.MIN_Y; +import static org.h2.util.geometry.GeometryUtils.X; +import static org.h2.util.geometry.GeometryUtils.Y; +import static org.h2.util.geometry.GeometryUtils.Z; + +import java.io.ByteArrayOutputStream; +import java.util.Random; + +import org.h2.test.TestBase; +import org.h2.util.StringUtils; +import org.h2.util.geometry.EWKBUtils; +import org.h2.util.geometry.EWKBUtils.EWKBTarget; +import org.h2.util.geometry.EWKTUtils; +import org.h2.util.geometry.EWKTUtils.EWKTTarget; +import org.h2.util.geometry.GeometryUtils; +import org.h2.util.geometry.GeometryUtils.DimensionSystemTarget; +import org.h2.util.geometry.GeometryUtils.EnvelopeTarget; +import org.h2.util.geometry.GeometryUtils.Target; +import org.h2.util.geometry.JTSUtils; +import org.h2.util.geometry.JTSUtils.GeometryTarget; +import org.h2.value.ValueGeometry; +import org.locationtech.jts.geom.CoordinateSequence; +import org.locationtech.jts.geom.Envelope; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryCollection; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.Point; +import org.locationtech.jts.io.ParseException; +import org.locationtech.jts.io.WKBWriter; +import org.locationtech.jts.io.WKTReader; +import org.locationtech.jts.io.WKTWriter; + +/** + * Tests the classes from org.h2.util.geometry package. + */ +public class TestGeometryUtils extends TestBase { + + private static final byte[][] NON_FINITE = { // + // XY + StringUtils.convertHexToBytes("0000000001" // + + "0000000000000000" // + + "7ff8000000000000"), // + // XY + StringUtils.convertHexToBytes("0000000001" // + + "7ff8000000000000" // + + "0000000000000000"), // + // XYZ + StringUtils.convertHexToBytes("0080000001" // + + "0000000000000000" // + + "0000000000000000" // + + "7ff8000000000000"), // + // XYM + StringUtils.convertHexToBytes("0040000001" // + + "0000000000000000" // + + "0000000000000000" // + + "7ff8000000000000") }; + + private static final int[] NON_FINITE_DIMENSIONS = { // + DIMENSION_SYSTEM_XY, // + DIMENSION_SYSTEM_XY, // + DIMENSION_SYSTEM_XYZ, // + DIMENSION_SYSTEM_XYM }; + + private static final String MIXED_WKT = "LINESTRING (1 2, 3 4 5)"; + + private static final byte[] MIXED_WKB = StringUtils.convertHexToBytes("" + // BOM (BigEndian) + + "00" + // Z | LINESTRING + + "80000002" + // 2 items + + "00000002" + // 1.0 + + "3ff0000000000000" + // 2.0 + + "4000000000000000" + // NaN + + "7ff8000000000000" + // 3.0 + + "4008000000000000" + // 4.0 + + "4010000000000000" + // 5.0 + + "4014000000000000"); + + /** + * Run just this test. + * + * @param a + * ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testPoint(); + testLineString(); + testPolygon(); + testMultiPoint(); + testMultiLineString(); + testMultiPolygon(); + testGeometryCollection(); + testEmptyPoint(); + testDimensionXY(); + testDimensionZ(); + testDimensionM(); + testDimensionZM(); + testFiniteOnly(); + testSRID(); + testIntersectionAndUnion(); + testMixedGeometries(); + } + + private void testPoint() throws Exception { + testGeometry("POINT (1 2)", 2); + testGeometry("POINT (-1.3 15)", 2); + testGeometry("POINT (-1E32 1.000001)", "POINT (-1E32 1.000001)", + "POINT (-100000000000000000000000000000000 1.000001)", 2, true); + testGeometry("POINT Z (2.7 -3 34)", 3); + assertEquals("POINT Z (1 2 3)", EWKTUtils.ewkb2ewkt(EWKTUtils.ewkt2ewkb("POINTZ(1 2 3)"))); + assertEquals("POINT Z (1 2 3)", EWKTUtils.ewkb2ewkt(EWKTUtils.ewkt2ewkb("pointz(1 2 3)"))); + } + + private void testLineString() throws Exception { + testGeometry("LINESTRING (-1 -2, 10 1, 2 20)", 2); + testGeometry("LINESTRING (1 2, 1 3)", 2); + testGeometry("LINESTRING (1 2, 2 2)", 2); + testGeometry("LINESTRING EMPTY", 2); + testGeometry("LINESTRING Z (-1 -2 -3, 10 15.7 3)", 3); + } + + private void testPolygon() throws Exception { + testGeometry("POLYGON ((-1 -2, 10 1, 2 20, -1 -2))", 2); + testGeometry("POLYGON EMPTY", "POLYGON EMPTY", "POLYGON EMPTY", 2, false); + testGeometry("POLYGON ((-1 -2, 10 1, 2 20, -1 -2), (0.5 0.5, 1 0.5, 1 1, 0.5 0.5))", 2); + // TODO is EMPTY inner ring valid? + testGeometry("POLYGON ((-1 -2, 10 1, 2 20, -1 -2), EMPTY)", 2); + testGeometry("POLYGON Z ((-1 -2 7, 10 1 7, 2 20 7, -1 -2 7), (0.5 0.5 7, 1 0.5 7, 1 1 7, 0.5 0.5 7))", 3); + } + + private void testMultiPoint() throws Exception { + testGeometry("MULTIPOINT ((1 2), (3 4))", 2); + // Alternative syntax + testGeometry("MULTIPOINT (1 2, 3 4)", "MULTIPOINT ((1 2), (3 4))", "MULTIPOINT ((1 2), (3 4))", 2, true); + testGeometry("MULTIPOINT (1 2)", "MULTIPOINT ((1 2))", "MULTIPOINT ((1 2))", 2, true); + testGeometry("MULTIPOINT EMPTY", 2); + testGeometry("MULTIPOINT Z ((1 2 0.5), (3 4 -3))", 3); + } + + private void testMultiLineString() throws Exception { + testGeometry("MULTILINESTRING ((1 2, 3 4, 5 7))", 2); + testGeometry("MULTILINESTRING ((1 2, 3 4, 5 7), (-1 -1, 0 0, 2 2, 4 6.01))", 2); + testGeometry("MULTILINESTRING EMPTY", 2); + testGeometry("MULTILINESTRING Z ((1 2 0.5, 3 4 -3, 5 7 10))", 3); + } + + private void testMultiPolygon() throws Exception { + testGeometry("MULTIPOLYGON (((-1 -2, 10 1, 2 20, -1 -2)))", 2); + testGeometry("MULTIPOLYGON (((-1 -2, 10 1, 2 20, -1 -2)), ((1 2, 2 2, 3 3, 1 2)))", 2); + testGeometry("MULTIPOLYGON EMPTY", 2); + testGeometry("MULTIPOLYGON (((-1 -2, 10 1, 2 20, -1 -2), (0.5 0.5, 1 0.5, 1 1, 0.5 0.5)))", 2); + testGeometry("MULTIPOLYGON Z (((-1 -2 7, 10 1 7, 2 20 7, -1 -2 7), (0.5 1 7, 1 0.5 7, 1 1 7, 0.5 1 7)))", 3); + } + + private void testGeometryCollection() throws Exception { + testGeometry("GEOMETRYCOLLECTION (POINT (1 2))", 2); + testGeometry("GEOMETRYCOLLECTION (POINT (1 2), " // + + "MULTILINESTRING ((1 2, 3 4, 5 7), (-1 -1, 0 0, 2 2, 4 6.01)), " // + + "POINT (100 130))", 2); + testGeometry("GEOMETRYCOLLECTION EMPTY", 2); + testGeometry( + "GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (POINT (1 3)), MULTIPOINT ((4 8)), GEOMETRYCOLLECTION EMPTY)", + 2); + testGeometry("GEOMETRYCOLLECTION Z (POINT Z (1 2 3))", 3); + } + + private void testGeometry(String wkt, int numOfDimensions) throws Exception { + testGeometry(wkt, wkt, wkt, numOfDimensions, true); + } + + private void testGeometry(String wkt, String h2Wkt, String jtsWkt, int numOfDimensions, boolean withEWKB) + throws Exception { + Geometry geometryFromJTS = readWKT(wkt); + byte[] wkbFromJTS = new WKBWriter(numOfDimensions).write(geometryFromJTS); + + // Test WKB->WKT conversion + assertEquals(h2Wkt, EWKTUtils.ewkb2ewkt(wkbFromJTS)); + + if (withEWKB) { + // Test WKT->WKB conversion + assertEquals(wkbFromJTS, EWKTUtils.ewkt2ewkb(wkt)); + + // Test WKB->WKB no-op normalization + assertEquals(wkbFromJTS, EWKBUtils.ewkb2ewkb(wkbFromJTS)); + } + + // Test WKB->Geometry conversion + Geometry geometryFromH2 = JTSUtils.ewkb2geometry(wkbFromJTS); + String got = new WKTWriter(numOfDimensions).write(geometryFromH2); + if (!jtsWkt.equals(got)) { + assertEquals(jtsWkt.replaceAll(" Z ", " Z"), got); + } + + if (withEWKB) { + // Test Geometry->WKB conversion + assertEquals(wkbFromJTS, JTSUtils.geometry2ewkb(geometryFromJTS)); + } + + // Test Envelope + Envelope envelopeFromJTS = geometryFromJTS.getEnvelopeInternal(); + testEnvelope(envelopeFromJTS, GeometryUtils.getEnvelope(wkbFromJTS)); + EnvelopeTarget target = new EnvelopeTarget(); + EWKBUtils.parseEWKB(wkbFromJTS, target); + testEnvelope(envelopeFromJTS, target.getEnvelope()); + + // Test dimensions + int expectedDimensionSystem = numOfDimensions > 2 ? GeometryUtils.DIMENSION_SYSTEM_XYZ + : GeometryUtils.DIMENSION_SYSTEM_XY; + testDimensions(expectedDimensionSystem, wkbFromJTS); + + testValueGeometryProperties(wkbFromJTS); + } + + private void testEnvelope(Envelope envelopeFromJTS, double[] envelopeFromH2) { + if (envelopeFromJTS.isNull()) { + assertNull(envelopeFromH2); + assertNull(EWKBUtils.envelope2wkb(envelopeFromH2)); + } else { + assertEquals(envelopeFromJTS.getMinX(), envelopeFromH2[0]); + assertEquals(envelopeFromJTS.getMaxX(), envelopeFromH2[1]); + assertEquals(envelopeFromJTS.getMinY(), envelopeFromH2[2]); + assertEquals(envelopeFromJTS.getMaxY(), envelopeFromH2[3]); + assertEquals(new WKBWriter(2).write(new GeometryFactory().toGeometry(envelopeFromJTS)), + EWKBUtils.envelope2wkb(envelopeFromH2)); + } + } + + private void testEmptyPoint() { + String ewkt = "POINT EMPTY"; + byte[] ewkb = EWKTUtils.ewkt2ewkb(ewkt); + assertEquals(StringUtils.convertHexToBytes("00000000017ff80000000000007ff8000000000000"), ewkb); + assertEquals(ewkt, EWKTUtils.ewkb2ewkt(ewkb)); + assertNull(GeometryUtils.getEnvelope(ewkb)); + Point p = (Point) JTSUtils.ewkb2geometry(ewkb); + assertTrue(p.isEmpty()); + assertEquals(ewkt, new WKTWriter().write(p)); + assertEquals(ewkb, JTSUtils.geometry2ewkb(p)); + } + + private void testDimensionXY() throws Exception { + byte[] ewkb = EWKTUtils.ewkt2ewkb("POINT (1 2)"); + assertEquals("POINT (1 2)", EWKTUtils.ewkb2ewkt(ewkb)); + Point p = (Point) JTSUtils.ewkb2geometry(ewkb); + CoordinateSequence cs = p.getCoordinateSequence(); + testDimensionXYCheckPoint(cs); + assertEquals(ewkb, JTSUtils.geometry2ewkb(p)); + testDimensions(GeometryUtils.DIMENSION_SYSTEM_XY, ewkb); + testValueGeometryProperties(ewkb); + + p = (Point) readWKT("POINT (1 2)"); + cs = p.getCoordinateSequence(); + testDimensionXYCheckPoint(cs); + ewkb = JTSUtils.geometry2ewkb(p); + assertEquals("POINT (1 2)", EWKTUtils.ewkb2ewkt(ewkb)); + p = (Point) JTSUtils.ewkb2geometry(ewkb); + cs = p.getCoordinateSequence(); + testDimensionXYCheckPoint(cs); + } + + private void testDimensionXYCheckPoint(CoordinateSequence cs) { + assertEquals(2, cs.getDimension()); + assertEquals(0, cs.getMeasures()); + assertEquals(1, cs.getOrdinate(0, X)); + assertEquals(2, cs.getOrdinate(0, Y)); + assertEquals(Double.NaN, cs.getZ(0)); + } + + private void testDimensionZ() throws Exception { + byte[] ewkb = EWKTUtils.ewkt2ewkb("POINT Z (1 2 3)"); + assertEquals("POINT Z (1 2 3)", EWKTUtils.ewkb2ewkt(ewkb)); + assertEquals("POINT Z (1 2 3)", EWKTUtils.ewkb2ewkt(EWKTUtils.ewkt2ewkb("POINTZ(1 2 3)"))); + assertEquals("POINT Z (1 2 3)", EWKTUtils.ewkb2ewkt(EWKTUtils.ewkt2ewkb("pointz(1 2 3)"))); + Point p = (Point) JTSUtils.ewkb2geometry(ewkb); + CoordinateSequence cs = p.getCoordinateSequence(); + testDimensionZCheckPoint(cs); + assertEquals(ewkb, JTSUtils.geometry2ewkb(p)); + testDimensions(GeometryUtils.DIMENSION_SYSTEM_XYZ, ewkb); + testValueGeometryProperties(ewkb); + + p = (Point) readWKT("POINT Z (1 2 3)"); + cs = p.getCoordinateSequence(); + testDimensionZCheckPoint(cs); + ewkb = JTSUtils.geometry2ewkb(p); + assertEquals("POINT Z (1 2 3)", EWKTUtils.ewkb2ewkt(ewkb)); + p = (Point) JTSUtils.ewkb2geometry(ewkb); + cs = p.getCoordinateSequence(); + testDimensionZCheckPoint(cs); + } + + private void testDimensionZCheckPoint(CoordinateSequence cs) { + assertEquals(3, cs.getDimension()); + assertEquals(0, cs.getMeasures()); + assertEquals(1, cs.getOrdinate(0, X)); + assertEquals(2, cs.getOrdinate(0, Y)); + assertEquals(3, cs.getOrdinate(0, Z)); + assertEquals(3, cs.getZ(0)); + } + + private void testDimensionM() throws Exception { + byte[] ewkb = EWKTUtils.ewkt2ewkb("POINT M (1 2 3)"); + assertEquals("POINT M (1 2 3)", EWKTUtils.ewkb2ewkt(ewkb)); + assertEquals("POINT M (1 2 3)", EWKTUtils.ewkb2ewkt(EWKTUtils.ewkt2ewkb("POINTM(1 2 3)"))); + assertEquals("POINT M (1 2 3)", EWKTUtils.ewkb2ewkt(EWKTUtils.ewkt2ewkb("pointm(1 2 3)"))); + Point p = (Point) JTSUtils.ewkb2geometry(ewkb); + CoordinateSequence cs = p.getCoordinateSequence(); + testDimensionMCheckPoint(cs); + assertEquals(ewkb, JTSUtils.geometry2ewkb(p)); + testDimensions(GeometryUtils.DIMENSION_SYSTEM_XYM, ewkb); + testValueGeometryProperties(ewkb); + + p = (Point) readWKT("POINT M (1 2 3)"); + cs = p.getCoordinateSequence(); + testDimensionMCheckPoint(cs); + ewkb = JTSUtils.geometry2ewkb(p); + assertEquals("POINT M (1 2 3)", EWKTUtils.ewkb2ewkt(ewkb)); + p = (Point) JTSUtils.ewkb2geometry(ewkb); + cs = p.getCoordinateSequence(); + testDimensionMCheckPoint(cs); + } + + private void testDimensionMCheckPoint(CoordinateSequence cs) { + assertEquals(3, cs.getDimension()); + assertEquals(1, cs.getMeasures()); + assertEquals(1, cs.getOrdinate(0, X)); + assertEquals(2, cs.getOrdinate(0, Y)); + assertEquals(3, cs.getOrdinate(0, 2)); + assertEquals(3, cs.getM(0)); + } + + private void testDimensionZM() throws Exception { + byte[] ewkb = EWKTUtils.ewkt2ewkb("POINT ZM (1 2 3 4)"); + assertEquals("POINT ZM (1 2 3 4)", EWKTUtils.ewkb2ewkt(ewkb)); + assertEquals("POINT ZM (1 2 3 4)", EWKTUtils.ewkb2ewkt(EWKTUtils.ewkt2ewkb("POINTZM(1 2 3 4)"))); + assertEquals("POINT ZM (1 2 3 4)", EWKTUtils.ewkb2ewkt(EWKTUtils.ewkt2ewkb("pointzm(1 2 3 4)"))); + Point p = (Point) JTSUtils.ewkb2geometry(ewkb); + CoordinateSequence cs = p.getCoordinateSequence(); + testDimensionZMCheckPoint(cs); + assertEquals(ewkb, JTSUtils.geometry2ewkb(p)); + testDimensions(GeometryUtils.DIMENSION_SYSTEM_XYZM, ewkb); + testValueGeometryProperties(ewkb); + + p = (Point) readWKT("POINT ZM (1 2 3 4)"); + cs = p.getCoordinateSequence(); + testDimensionZMCheckPoint(cs); + ewkb = JTSUtils.geometry2ewkb(p); + assertEquals("POINT ZM (1 2 3 4)", EWKTUtils.ewkb2ewkt(ewkb)); + p = (Point) JTSUtils.ewkb2geometry(ewkb); + cs = p.getCoordinateSequence(); + testDimensionZMCheckPoint(cs); + } + + private void testDimensionZMCheckPoint(CoordinateSequence cs) { + assertEquals(4, cs.getDimension()); + assertEquals(1, cs.getMeasures()); + assertEquals(1, cs.getOrdinate(0, X)); + assertEquals(2, cs.getOrdinate(0, Y)); + assertEquals(3, cs.getOrdinate(0, Z)); + assertEquals(3, cs.getZ(0)); + assertEquals(4, cs.getOrdinate(0, M)); + assertEquals(4, cs.getM(0)); + } + + private void testValueGeometryProperties(byte[] ewkb) { + ValueGeometry vg = ValueGeometry.getFromEWKB(ewkb); + DimensionSystemTarget target = new DimensionSystemTarget(); + EWKBUtils.parseEWKB(ewkb, target); + int dimensionSystem = target.getDimensionSystem(); + assertEquals(dimensionSystem, vg.getDimensionSystem()); + String formattedType = EWKTUtils + .formatGeometryTypeAndDimensionSystem(new StringBuilder(), vg.getTypeAndDimensionSystem()).toString(); + assertTrue(EWKTUtils.ewkb2ewkt(ewkb).startsWith(formattedType)); + switch (dimensionSystem) { + case DIMENSION_SYSTEM_XY: + assertTrue(formattedType.indexOf(' ') < 0); + break; + case DIMENSION_SYSTEM_XYZ: + assertTrue(formattedType.endsWith(" Z")); + break; + case DIMENSION_SYSTEM_XYM: + assertTrue(formattedType.endsWith(" M")); + break; + case DIMENSION_SYSTEM_XYZM: + assertTrue(formattedType.endsWith(" ZM")); + break; + } + assertEquals(vg.getTypeAndDimensionSystem(), vg.getGeometryType() + vg.getDimensionSystem() * 1_000); + assertEquals(0, vg.getSRID()); + } + + private void testFiniteOnly() { + for (int i = 0; i < NON_FINITE.length; i++) { + testFiniteOnly(NON_FINITE[i], new EWKBTarget(new ByteArrayOutputStream(), NON_FINITE_DIMENSIONS[i])); + } + for (int i = 0; i < NON_FINITE.length; i++) { + testFiniteOnly(NON_FINITE[i], new EWKTTarget(new StringBuilder(), NON_FINITE_DIMENSIONS[i])); + } + for (int i = 0; i < NON_FINITE.length; i++) { + testFiniteOnly(NON_FINITE[i], new GeometryTarget(NON_FINITE_DIMENSIONS[i])); + } + } + + private void testFiniteOnly(byte[] ewkb, Target target) { + assertThrows(IllegalArgumentException.class, () -> EWKBUtils.parseEWKB(ewkb, target)); + } + + private void testSRID() throws Exception { + byte[] ewkb = EWKTUtils.ewkt2ewkb("SRID=10;GEOMETRYCOLLECTION (POINT (1 2))"); + assertEquals(StringUtils.convertHexToBytes("" + // ******** Geometry collection ******** + // BOM (BigEndian) + + "00" + // Only top-level object has a SRID + // type (SRID | POINT) + + "20000007" + // SRID = 10 + + "0000000a" + // 1 item + + "00000001" + // ******** Point ******** + // BOM (BigEndian) + + "00" + // type (POINT) + + "00000001" + // 1.0 + + "3ff0000000000000" + // 2.0 + + "4000000000000000"), ewkb); + assertEquals("SRID=10;GEOMETRYCOLLECTION (POINT (1 2))", EWKTUtils.ewkb2ewkt(ewkb)); + GeometryCollection gc = (GeometryCollection) JTSUtils.ewkb2geometry(ewkb); + assertEquals(10, gc.getSRID()); + assertEquals(10, gc.getGeometryN(0).getSRID()); + assertEquals(ewkb, JTSUtils.geometry2ewkb(gc)); + ValueGeometry vg = ValueGeometry.getFromEWKB(ewkb); + assertEquals(10, vg.getSRID()); + assertEquals(GEOMETRY_COLLECTION, vg.getTypeAndDimensionSystem()); + assertEquals("SRID=-1;POINT EMPTY", EWKTUtils.ewkb2ewkt(EWKTUtils.ewkt2ewkb(" srid=-1 ; POINT EMPTY "))); + } + + private void testDimensions(int expected, byte[] ewkb) { + DimensionSystemTarget dst = new DimensionSystemTarget(); + EWKBUtils.parseEWKB(ewkb, dst); + assertEquals(expected, dst.getDimensionSystem()); + } + + private void testIntersectionAndUnion() { + double[] zero = new double[4]; + assertFalse(GeometryUtils.intersects(null, null)); + assertFalse(GeometryUtils.intersects(null, zero)); + assertFalse(GeometryUtils.intersects(zero, null)); + assertNull(GeometryUtils.union(null, null)); + assertEquals(zero, GeometryUtils.union(null, zero)); + assertEquals(zero, GeometryUtils.union(zero, null)); + // These 30 values with fixed seed 0 are enough to cover all remaining + // cases + Random r = new Random(0); + for (int i = 0; i < 30; i++) { + double[] envelope1 = getEnvelope(r); + double[] envelope2 = getEnvelope(r); + Envelope e1 = convert(envelope1); + Envelope e2 = convert(envelope2); + assertEquals(e1.intersects(e2), GeometryUtils.intersects(envelope1, envelope2)); + e1.expandToInclude(e2); + assertEquals(e1, convert(GeometryUtils.union(envelope1, envelope2))); + } + } + + private static Envelope convert(double[] envelope) { + return new Envelope(envelope[MIN_X], envelope[MAX_X], envelope[MIN_Y], envelope[MAX_Y]); + } + + private static double[] getEnvelope(Random r) { + double minX = r.nextDouble(); + double maxX = r.nextDouble(); + if (minX > maxX) { + double t = minX; + minX = maxX; + maxX = t; + } + double minY = r.nextDouble(); + double maxY = r.nextDouble(); + if (minY > maxY) { + double t = minY; + minY = maxY; + maxY = t; + } + return new double[] { minX, maxX, minY, maxY }; + } + + private void testMixedGeometries() throws Exception { + assertThrows(IllegalArgumentException.class, () -> EWKTUtils.ewkt2ewkb(MIXED_WKT)); + assertThrows(IllegalArgumentException.class, () -> EWKTUtils.ewkb2ewkt(MIXED_WKB)); + assertThrows(IllegalArgumentException.class, () -> JTSUtils.ewkb2geometry(MIXED_WKB)); + Geometry g = new WKTReader().read(MIXED_WKT); + assertThrows(IllegalArgumentException.class, () -> JTSUtils.geometry2ewkb(g)); + } + + private static Geometry readWKT(String text) throws ParseException { + WKTReader reader = new WKTReader(); + reader.setIsOldJtsCoordinateSyntaxAllowed(false); + return reader.read(text); + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestIntArray.java b/h2/src/test/org/h2/test/unit/TestIntArray.java new file mode 100644 index 0000000..04ab6f9 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestIntArray.java @@ -0,0 +1,112 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.util.Random; +import org.h2.test.TestBase; +import org.h2.util.IntArray; + +/** + * Tests the IntArray class. + */ +public class TestIntArray extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() { + testInit(); + testRandom(); + testRemoveRange(); + } + + private void testRemoveRange() { + IntArray array = new IntArray(new int[] {1, 2, 3, 4, 5}); + array.removeRange(1, 3); + assertEquals(3, array.size()); + assertEquals(1, array.get(0)); + assertEquals(4, array.get(1)); + assertEquals(5, array.get(2)); + } + + private static void testInit() { + IntArray array = new IntArray(new int[0]); + array.add(10); + } + + private void testRandom() { + IntArray array = new IntArray(); + int[] test = {}; + Random random = new Random(1); + for (int i = 0; i < 10000; i++) { + int idx = test.length == 0 ? 0 : random.nextInt(test.length); + int v = random.nextInt(100); + int op = random.nextInt(4); + switch (op) { + case 0: + array.add(v); + test = add(test, v); + break; + case 1: + if (test.length > idx) { + assertEquals(get(test, idx), array.get(idx)); + } + break; + case 2: + if (test.length > 0) { + array.remove(idx); + test = remove(test, idx); + } + break; + case 3: + assertEquals(test.length, array.size()); + break; + default: + } + assertEquals(test.length, array.size()); + for (int j = 0; j < test.length; j++) { + assertEquals(test[j], array.get(j)); + } + + } + } + + private static int[] add(int[] array, int i, int value) { + int[] a2 = new int[array.length + 1]; + System.arraycopy(array, 0, a2, 0, array.length); + if (i < array.length) { + System.arraycopy(a2, i, a2, i + 1, a2.length - i - 1); + } + array = a2; + array[i] = value; + return array; + } + + private static int[] add(int[] array, int value) { + return add(array, array.length, value); + } + + private static int get(int[] array, int i) { + return array[i]; + } + + private static int[] remove(int[] array, int i) { + int[] a2 = new int[array.length - 1]; + System.arraycopy(array, 0, a2, 0, i); + if (i < a2.length) { + System.arraycopy(array, i + 1, a2, i, array.length - i - 1); + } + return a2; + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestIntPerfectHash.java b/h2/src/test/org/h2/test/unit/TestIntPerfectHash.java new file mode 100644 index 0000000..1aa4209 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestIntPerfectHash.java @@ -0,0 +1,109 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.h2.dev.hash.IntPerfectHash; +import org.h2.dev.hash.IntPerfectHash.BitArray; +import org.h2.test.TestBase; + +/** + * Tests the perfect hash tool. + */ +public class TestIntPerfectHash extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestIntPerfectHash test = (TestIntPerfectHash) TestBase.createCaller().init(); + test.measure(); + test.test(); + test.measure(); + } + + /** + * Measure the hash functions. + */ + public void measure() { + int size = 10000; + test(size / 10); + int s; + long time = System.nanoTime(); + s = test(size); + time = System.nanoTime() - time; + System.out.println((double) s / size + " bits/key in " + + TimeUnit.NANOSECONDS.toMillis(time) + " ms"); + + } + + @Override + public void test() { + testBitArray(); + for (int i = 0; i < 100; i++) { + test(i); + } + for (int i = 100; i <= 10000; i *= 10) { + test(i); + } + } + + private void testBitArray() { + byte[] data = new byte[0]; + BitSet set = new BitSet(); + for (int i = 100; i >= 0; i--) { + data = BitArray.setBit(data, i, true); + set.set(i); + } + Random r = new Random(1); + for (int i = 0; i < 10000; i++) { + int pos = r.nextInt(100); + boolean s = r.nextBoolean(); + data = BitArray.setBit(data, pos, s); + set.set(pos, s); + pos = r.nextInt(100); + assertTrue(BitArray.getBit(data, pos) == set.get(pos)); + } + assertTrue(BitArray.countBits(data) == set.cardinality()); + } + + private int test(int size) { + Random r = new Random(size); + HashSet set = new HashSet<>(); + while (set.size() < size) { + set.add(r.nextInt()); + } + ArrayList list = new ArrayList<>(set); + byte[] desc = IntPerfectHash.generate(list); + int max = test(desc, set); + assertEquals(size - 1, max); + return desc.length * 8; + } + + private int test(byte[] desc, Set set) { + int max = -1; + HashSet test = new HashSet<>(); + IntPerfectHash hash = new IntPerfectHash(desc); + for (int x : set) { + int h = hash.get(x); + assertTrue(h >= 0); + assertTrue(h <= set.size() * 3); + max = Math.max(max, h); + assertFalse(test.contains(h)); + test.add(h); + } + return max; + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestInterval.java b/h2/src/test/org/h2/test/unit/TestInterval.java new file mode 100644 index 0000000..ddbf276 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestInterval.java @@ -0,0 +1,547 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import static org.h2.util.DateTimeUtils.NANOS_PER_SECOND; + +import org.h2.api.Interval; +import org.h2.test.TestBase; +import org.h2.util.StringUtils; + +/** + * Test cases for Interval. + */ +public class TestInterval extends TestBase { + + private static final long MAX = 999_999_999_999_999_999L; + + private static final long MIN = -999_999_999_999_999_999L; + + /** + * Run just this test. + * + * @param a + * ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testOfYears(); + testOfMonths(); + testOfDays(); + testOfHours(); + testOfMinutes(); + testOfSeconds(); + testOfSeconds2(); + testOfNanos(); + testOfYearsMonths(); + testOfDaysHours(); + testOfDaysHoursMinutes(); + testOfDaysHoursMinutesSeconds(); + testOfHoursMinutes(); + testOfHoursMinutesSeconds(); + testOfMinutesSeconds(); + } + + private void testOfYears() { + testOfYearsGood(0); + testOfYearsGood(100); + testOfYearsGood(-100); + testOfYearsGood(MAX); + testOfYearsGood(MIN); + testOfYearsBad(MAX + 1); + testOfYearsBad(MIN - 1); + testOfYearsBad(Long.MAX_VALUE); + testOfYearsBad(Long.MIN_VALUE); + } + + private void testOfYearsGood(long years) { + Interval i = Interval.ofYears(years); + assertEquals(years, i.getYears()); + assertEquals("INTERVAL '" + years + "' YEAR", i.toString()); + } + + private void testOfYearsBad(long years) { + assertThrows(IllegalArgumentException.class, () -> Interval.ofYears(years)); + } + + private void testOfMonths() { + testOfMonthsGood(0); + testOfMonthsGood(100); + testOfMonthsGood(-100); + testOfMonthsGood(MAX); + testOfMonthsGood(MIN); + testOfMonthsBad(MAX + 1); + testOfMonthsBad(MIN - 1); + testOfMonthsBad(Long.MAX_VALUE); + testOfMonthsBad(Long.MIN_VALUE); + } + + private void testOfMonthsGood(long months) { + Interval i = Interval.ofMonths(months); + assertEquals(months, i.getMonths()); + assertEquals("INTERVAL '" + months + "' MONTH", i.toString()); + } + + private void testOfMonthsBad(long months) { + assertThrows(IllegalArgumentException.class, () -> Interval.ofMonths(months)); + } + + private void testOfDays() { + testOfDaysGood(0); + testOfDaysGood(100); + testOfDaysGood(-100); + testOfDaysGood(MAX); + testOfDaysGood(MIN); + testOfDaysBad(MAX + 1); + testOfDaysBad(MIN - 1); + testOfDaysBad(Long.MAX_VALUE); + testOfDaysBad(Long.MIN_VALUE); + } + + private void testOfDaysGood(long days) { + Interval i = Interval.ofDays(days); + assertEquals(days, i.getDays()); + assertEquals("INTERVAL '" + days + "' DAY", i.toString()); + } + + private void testOfDaysBad(long days) { + assertThrows(IllegalArgumentException.class, () -> Interval.ofDays(days)); + } + + private void testOfHours() { + testOfHoursGood(0); + testOfHoursGood(100); + testOfHoursGood(-100); + testOfHoursGood(MAX); + testOfHoursGood(MIN); + testOfHoursBad(MAX + 1); + testOfHoursBad(MIN - 1); + testOfHoursBad(Long.MAX_VALUE); + testOfHoursBad(Long.MIN_VALUE); + } + + private void testOfHoursGood(long hours) { + Interval i = Interval.ofHours(hours); + assertEquals(hours, i.getHours()); + assertEquals("INTERVAL '" + hours + "' HOUR", i.toString()); + } + + private void testOfHoursBad(long hours) { + assertThrows(IllegalArgumentException.class, () -> Interval.ofHours(hours)); + } + + private void testOfMinutes() { + testOfMinutesGood(0); + testOfMinutesGood(100); + testOfMinutesGood(-100); + testOfMinutesGood(MAX); + testOfMinutesGood(MIN); + testOfMinutesBad(MAX + 1); + testOfMinutesBad(MIN - 1); + testOfMinutesBad(Long.MAX_VALUE); + testOfMinutesBad(Long.MIN_VALUE); + } + + private void testOfMinutesGood(long minutes) { + Interval i = Interval.ofMinutes(minutes); + assertEquals(minutes, i.getMinutes()); + assertEquals("INTERVAL '" + minutes + "' MINUTE", i.toString()); + } + + private void testOfMinutesBad(long minutes) { + assertThrows(IllegalArgumentException.class, () -> Interval.ofMinutes(minutes)); + } + + private void testOfSeconds() { + testOfSecondsGood(0); + testOfSecondsGood(100); + testOfSecondsGood(-100); + testOfSecondsGood(MAX); + testOfSecondsGood(MIN); + testOfSecondsBad(MAX + 1); + testOfSecondsBad(MIN - 1); + testOfSecondsBad(Long.MAX_VALUE); + testOfSecondsBad(Long.MIN_VALUE); + } + + private void testOfSecondsGood(long seconds) { + Interval i = Interval.ofSeconds(seconds); + assertEquals(seconds, i.getSeconds()); + assertEquals("INTERVAL '" + seconds + "' SECOND", i.toString()); + } + + private void testOfSecondsBad(long seconds) { + assertThrows(IllegalArgumentException.class, () -> Interval.ofSeconds(seconds)); + } + + private void testOfSeconds2() { + testOfSeconds2Good(0, 0); + testOfSeconds2Good(0, -2); + testOfSeconds2Good(100, 5); + testOfSeconds2Good(-100, -1); + testOfSeconds2Good(MAX, 999_999_999); + testOfSeconds2Good(MIN, -999_999_999); + testOfSeconds2Bad(0, 1_000_000_000); + testOfSeconds2Bad(0, -1_000_000_000); + testOfSeconds2Bad(MAX + 1, 0); + testOfSeconds2Bad(MIN - 1, 0); + testOfSeconds2Bad(Long.MAX_VALUE, 0); + testOfSeconds2Bad(Long.MIN_VALUE, 0); + testOfSeconds2Bad(0, Integer.MAX_VALUE); + testOfSeconds2Bad(0, Integer.MIN_VALUE); + } + + private void testOfSeconds2Good(long seconds, int nanos) { + Interval i = Interval.ofSeconds(seconds, nanos); + assertEquals(seconds, i.getSeconds()); + assertEquals(nanos, i.getNanosOfSecond()); + if (Math.abs(seconds) < 9_000_000_000L) { + assertEquals(seconds * NANOS_PER_SECOND + nanos, i.getSecondsAndNanos()); + } + StringBuilder b = new StringBuilder("INTERVAL '"); + if (seconds < 0 || nanos < 0) { + b.append('-'); + } + b.append(Math.abs(seconds)); + if (nanos != 0) { + b.append('.'); + StringUtils.appendZeroPadded(b, 9, Math.abs(nanos)); + stripTrailingZeroes(b); + } + b.append("' SECOND"); + assertEquals(b.toString(), i.toString()); + } + + private void testOfSeconds2Bad(long seconds, int nanos) { + assertThrows(IllegalArgumentException.class, () -> Interval.ofSeconds(seconds, nanos)); + } + + private void testOfNanos() { + testOfNanosGood(0); + testOfNanosGood(100); + testOfNanosGood(-100); + testOfNanosGood(Long.MAX_VALUE); + testOfNanosGood(Long.MIN_VALUE); + } + + private void testOfNanosGood(long nanos) { + Interval i = Interval.ofNanos(nanos); + long seconds = nanos / NANOS_PER_SECOND; + long nanosOfSecond = nanos % NANOS_PER_SECOND; + assertEquals(seconds, i.getSeconds()); + assertEquals(nanosOfSecond, i.getNanosOfSecond()); + assertEquals(nanos, i.getSecondsAndNanos()); + StringBuilder b = new StringBuilder("INTERVAL '"); + if (nanos < 0) { + b.append('-'); + } + b.append(Math.abs(seconds)); + if (nanosOfSecond != 0) { + b.append('.'); + StringUtils.appendZeroPadded(b, 9, Math.abs(nanosOfSecond)); + stripTrailingZeroes(b); + } + b.append("' SECOND"); + assertEquals(b.toString(), i.toString()); + } + + private void testOfYearsMonths() { + testOfYearsMonthsGood(0, 0); + testOfYearsMonthsGood(0, -2); + testOfYearsMonthsGood(100, 5); + testOfYearsMonthsGood(-100, -1); + testOfYearsMonthsGood(MAX, 11); + testOfYearsMonthsGood(MIN, -11); + testOfYearsMonthsBad(0, 12); + testOfYearsMonthsBad(0, -12); + testOfYearsMonthsBad(MAX + 1, 0); + testOfYearsMonthsBad(MIN - 1, 0); + testOfYearsMonthsBad(Long.MAX_VALUE, 0); + testOfYearsMonthsBad(Long.MIN_VALUE, 0); + testOfYearsMonthsBad(0, Integer.MAX_VALUE); + testOfYearsMonthsBad(0, Integer.MIN_VALUE); + } + + private void testOfYearsMonthsGood(long years, int months) { + Interval i = Interval.ofYearsMonths(years, months); + assertEquals(years, i.getYears()); + assertEquals(months, i.getMonths()); + StringBuilder b = new StringBuilder("INTERVAL '"); + if (years < 0 || months < 0) { + b.append('-'); + } + b.append(Math.abs(years)).append('-').append(Math.abs(months)).append("' YEAR TO MONTH"); + assertEquals(b.toString(), i.toString()); + } + + private void testOfYearsMonthsBad(long years, int months) { + assertThrows(IllegalArgumentException.class, () -> Interval.ofYearsMonths(years, months)); + } + + private void testOfDaysHours() { + testOfDaysHoursGood(0, 0); + testOfDaysHoursGood(0, -2); + testOfDaysHoursGood(100, 5); + testOfDaysHoursGood(-100, -1); + testOfDaysHoursGood(MAX, 23); + testOfDaysHoursGood(MIN, -23); + testOfDaysHoursBad(0, 24); + testOfDaysHoursBad(0, -24); + testOfDaysHoursBad(MAX + 1, 0); + testOfDaysHoursBad(MIN - 1, 0); + testOfDaysHoursBad(Long.MAX_VALUE, 0); + testOfDaysHoursBad(Long.MIN_VALUE, 0); + testOfDaysHoursBad(0, Integer.MAX_VALUE); + testOfDaysHoursBad(0, Integer.MIN_VALUE); + } + + private void testOfDaysHoursGood(long days, int hours) { + Interval i = Interval.ofDaysHours(days, hours); + assertEquals(days, i.getDays()); + assertEquals(hours, i.getHours()); + StringBuilder b = new StringBuilder("INTERVAL '"); + if (days < 0 || hours < 0) { + b.append('-'); + } + b.append(Math.abs(days)).append(' '); + StringUtils.appendTwoDigits(b, Math.abs(hours)); + b.append("' DAY TO HOUR"); + assertEquals(b.toString(), i.toString()); + } + + private void testOfDaysHoursBad(long days, int hours) { + assertThrows(IllegalArgumentException.class, () -> Interval.ofDaysHours(days, hours)); + } + + private void testOfDaysHoursMinutes() { + testOfDaysHoursMinutesGood(0, 0, 0); + testOfDaysHoursMinutesGood(0, -2, 0); + testOfDaysHoursMinutesGood(0, 0, -2); + testOfDaysHoursMinutesGood(100, 5, 3); + testOfDaysHoursMinutesGood(-100, -1, -3); + testOfDaysHoursMinutesGood(MAX, 23, 59); + testOfDaysHoursMinutesGood(MIN, -23, -59); + testOfDaysHoursMinutesBad(0, 24, 0); + testOfDaysHoursMinutesBad(0, -24, 0); + testOfDaysHoursMinutesBad(0, 0, 60); + testOfDaysHoursMinutesBad(0, 0, -60); + testOfDaysHoursMinutesBad(MAX + 1, 0, 0); + testOfDaysHoursMinutesBad(MIN - 1, 0, 0); + testOfDaysHoursMinutesBad(Long.MAX_VALUE, 0, 0); + testOfDaysHoursMinutesBad(Long.MIN_VALUE, 0, 0); + testOfDaysHoursMinutesBad(0, Integer.MAX_VALUE, 0); + testOfDaysHoursMinutesBad(0, Integer.MIN_VALUE, 0); + testOfDaysHoursMinutesBad(0, 0, Integer.MAX_VALUE); + testOfDaysHoursMinutesBad(0, 0, Integer.MIN_VALUE); + } + + private void testOfDaysHoursMinutesGood(long days, int hours, int minutes) { + Interval i = Interval.ofDaysHoursMinutes(days, hours, minutes); + assertEquals(days, i.getDays()); + assertEquals(hours, i.getHours()); + assertEquals(minutes, i.getMinutes()); + StringBuilder b = new StringBuilder("INTERVAL '"); + if (days < 0 || hours < 0 || minutes < 0) { + b.append('-'); + } + b.append(Math.abs(days)).append(' '); + StringUtils.appendTwoDigits(b, Math.abs(hours)); + b.append(':'); + StringUtils.appendTwoDigits(b, Math.abs(minutes)); + b.append("' DAY TO MINUTE"); + assertEquals(b.toString(), i.toString()); + } + + private void testOfDaysHoursMinutesBad(long days, int hours, int minutes) { + assertThrows(IllegalArgumentException.class, () -> Interval.ofDaysHoursMinutes(days, hours, minutes)); + } + + private void testOfDaysHoursMinutesSeconds() { + testOfDaysHoursMinutesSecondsGood(0, 0, 0, 0); + testOfDaysHoursMinutesSecondsGood(0, -2, 0, 0); + testOfDaysHoursMinutesSecondsGood(0, 0, -2, 0); + testOfDaysHoursMinutesSecondsGood(0, 0, 0, -2); + testOfDaysHoursMinutesSecondsGood(100, 5, 3, 4); + testOfDaysHoursMinutesSecondsGood(-100, -1, -3, -4); + testOfDaysHoursMinutesSecondsGood(MAX, 23, 59, 59); + testOfDaysHoursMinutesSecondsGood(MIN, -23, -59, -59); + testOfDaysHoursMinutesSecondsBad(0, 24, 0, 0); + testOfDaysHoursMinutesSecondsBad(0, -24, 0, 0); + testOfDaysHoursMinutesSecondsBad(0, 0, 60, 0); + testOfDaysHoursMinutesSecondsBad(0, 0, -60, 0); + testOfDaysHoursMinutesSecondsBad(0, 0, 0, 60); + testOfDaysHoursMinutesSecondsBad(0, 0, 0, -60); + testOfDaysHoursMinutesSecondsBad(MAX + 1, 0, 0, 0); + testOfDaysHoursMinutesSecondsBad(MIN - 1, 0, 0, 0); + testOfDaysHoursMinutesSecondsBad(Long.MAX_VALUE, 0, 0, 0); + testOfDaysHoursMinutesSecondsBad(Long.MIN_VALUE, 0, 0, 0); + testOfDaysHoursMinutesSecondsBad(0, Integer.MAX_VALUE, 0, 0); + testOfDaysHoursMinutesSecondsBad(0, Integer.MIN_VALUE, 0, 0); + testOfDaysHoursMinutesSecondsBad(0, 0, Integer.MAX_VALUE, 0); + testOfDaysHoursMinutesSecondsBad(0, 0, Integer.MIN_VALUE, 0); + testOfDaysHoursMinutesSecondsBad(0, 0, 0, Integer.MAX_VALUE); + testOfDaysHoursMinutesSecondsBad(0, 0, 0, Integer.MIN_VALUE); + } + + private void testOfDaysHoursMinutesSecondsGood(long days, int hours, int minutes, int seconds) { + Interval i = Interval.ofDaysHoursMinutesSeconds(days, hours, minutes, seconds); + assertEquals(days, i.getDays()); + assertEquals(hours, i.getHours()); + assertEquals(minutes, i.getMinutes()); + assertEquals(seconds, i.getSeconds()); + assertEquals(0, i.getNanosOfSecond()); + assertEquals(seconds * NANOS_PER_SECOND, i.getSecondsAndNanos()); + StringBuilder b = new StringBuilder("INTERVAL '"); + if (days < 0 || hours < 0 || minutes < 0 || seconds < 0) { + b.append('-'); + } + b.append(Math.abs(days)).append(' '); + StringUtils.appendTwoDigits(b, Math.abs(hours)); + b.append(':'); + StringUtils.appendTwoDigits(b, Math.abs(minutes)); + b.append(':'); + StringUtils.appendTwoDigits(b, Math.abs(seconds)); + b.append("' DAY TO SECOND"); + assertEquals(b.toString(), i.toString()); + } + + private void testOfDaysHoursMinutesSecondsBad(long days, int hours, int minutes, int seconds) { + assertThrows(IllegalArgumentException.class, + () -> Interval.ofDaysHoursMinutesSeconds(days, hours, minutes, seconds)); + } + + private void testOfHoursMinutes() { + testOfHoursMinutesGood(0, 0); + testOfHoursMinutesGood(0, -2); + testOfHoursMinutesGood(100, 5); + testOfHoursMinutesGood(-100, -1); + testOfHoursMinutesGood(MAX, 59); + testOfHoursMinutesGood(MIN, -59); + testOfHoursMinutesBad(0, 60); + testOfHoursMinutesBad(0, -60); + testOfHoursMinutesBad(MAX + 1, 0); + testOfHoursMinutesBad(MIN - 1, 0); + testOfHoursMinutesBad(Long.MAX_VALUE, 0); + testOfHoursMinutesBad(Long.MIN_VALUE, 0); + testOfHoursMinutesBad(0, Integer.MAX_VALUE); + testOfHoursMinutesBad(0, Integer.MIN_VALUE); + } + + private void testOfHoursMinutesGood(long hours, int minutes) { + Interval i = Interval.ofHoursMinutes(hours, minutes); + assertEquals(hours, i.getHours()); + assertEquals(minutes, i.getMinutes()); + StringBuilder b = new StringBuilder("INTERVAL '"); + if (hours < 0 || minutes < 0) { + b.append('-'); + } + b.append(Math.abs(hours)).append(':'); + StringUtils.appendTwoDigits(b, Math.abs(minutes)); + b.append("' HOUR TO MINUTE"); + assertEquals(b.toString(), i.toString()); + } + + private void testOfHoursMinutesBad(long hours, int minutes) { + assertThrows(IllegalArgumentException.class, () -> Interval.ofHoursMinutes(hours, minutes)); + } + + private void testOfHoursMinutesSeconds() { + testOfHoursMinutesSecondsGood(0, 0, 0); + testOfHoursMinutesSecondsGood(0, -2, 0); + testOfHoursMinutesSecondsGood(0, 0, -2); + testOfHoursMinutesSecondsGood(100, 5, 3); + testOfHoursMinutesSecondsGood(-100, -1, -3); + testOfHoursMinutesSecondsGood(MAX, 59, 59); + testOfHoursMinutesSecondsGood(MIN, -59, -59); + testOfHoursMinutesSecondsBad(0, 60, 0); + testOfHoursMinutesSecondsBad(0, -60, 0); + testOfHoursMinutesSecondsBad(0, 0, 60); + testOfHoursMinutesSecondsBad(0, 0, -60); + testOfHoursMinutesSecondsBad(MAX + 1, 0, 0); + testOfHoursMinutesSecondsBad(MIN - 1, 0, 0); + testOfHoursMinutesSecondsBad(Long.MAX_VALUE, 0, 0); + testOfHoursMinutesSecondsBad(Long.MIN_VALUE, 0, 0); + testOfHoursMinutesSecondsBad(0, Integer.MAX_VALUE, 0); + testOfHoursMinutesSecondsBad(0, Integer.MIN_VALUE, 0); + testOfHoursMinutesSecondsBad(0, 0, Integer.MAX_VALUE); + testOfHoursMinutesSecondsBad(0, 0, Integer.MIN_VALUE); + } + + private void testOfHoursMinutesSecondsGood(long hours, int minutes, int seconds) { + Interval i = Interval.ofHoursMinutesSeconds(hours, minutes, seconds); + assertEquals(hours, i.getHours()); + assertEquals(minutes, i.getMinutes()); + assertEquals(seconds, i.getSeconds()); + assertEquals(0, i.getNanosOfSecond()); + assertEquals(seconds * NANOS_PER_SECOND, i.getSecondsAndNanos()); + StringBuilder b = new StringBuilder("INTERVAL '"); + if (hours < 0 || minutes < 0 || seconds < 0) { + b.append('-'); + } + b.append(Math.abs(hours)).append(':'); + StringUtils.appendTwoDigits(b, Math.abs(minutes)); + b.append(':'); + StringUtils.appendTwoDigits(b, Math.abs(seconds)); + b.append("' HOUR TO SECOND"); + assertEquals(b.toString(), i.toString()); + } + + private void testOfHoursMinutesSecondsBad(long hours, int minutes, int seconds) { + assertThrows(IllegalArgumentException.class, () -> Interval.ofHoursMinutesSeconds(hours, minutes, seconds)); + } + + private void testOfMinutesSeconds() { + testOfMinutesSecondsGood(0, 0); + testOfMinutesSecondsGood(0, -2); + testOfMinutesSecondsGood(100, 5); + testOfMinutesSecondsGood(-100, -1); + testOfMinutesSecondsGood(MAX, 59); + testOfMinutesSecondsGood(MIN, -59); + testOfMinutesSecondsBad(0, 60); + testOfMinutesSecondsBad(0, -60); + testOfMinutesSecondsBad(MAX + 1, 0); + testOfMinutesSecondsBad(MIN - 1, 0); + testOfMinutesSecondsBad(Long.MAX_VALUE, 0); + testOfMinutesSecondsBad(Long.MIN_VALUE, 0); + testOfMinutesSecondsBad(0, Integer.MAX_VALUE); + testOfMinutesSecondsBad(0, Integer.MIN_VALUE); + } + + private void testOfMinutesSecondsGood(long minutes, int seconds) { + Interval i = Interval.ofMinutesSeconds(minutes, seconds); + assertEquals(minutes, i.getMinutes()); + assertEquals(seconds, i.getSeconds()); + assertEquals(0, i.getNanosOfSecond()); + assertEquals(seconds * NANOS_PER_SECOND, i.getSecondsAndNanos()); + StringBuilder b = new StringBuilder("INTERVAL '"); + if (minutes < 0 || seconds < 0) { + b.append('-'); + } + b.append(Math.abs(minutes)).append(':'); + StringUtils.appendTwoDigits(b, Math.abs(seconds)); + b.append("' MINUTE TO SECOND"); + assertEquals(b.toString(), i.toString()); + } + + private void testOfMinutesSecondsBad(long minutes, int seconds) { + assertThrows(IllegalArgumentException.class, () -> Interval.ofMinutesSeconds(minutes, seconds)); + } + + private static void stripTrailingZeroes(StringBuilder b) { + int i = b.length() - 1; + if (b.charAt(i) == '0') { + while (b.charAt(--i) == '0') { + // do nothing + } + b.setLength(i + 1); + } + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestJakartaServlet.java b/h2/src/test/org/h2/test/unit/TestJakartaServlet.java new file mode 100644 index 0000000..6f6cb83 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestJakartaServlet.java @@ -0,0 +1,437 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.io.InputStream; +import java.net.URL; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Enumeration; +import java.util.EventListener; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterRegistration; +import jakarta.servlet.FilterRegistration.Dynamic; +import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.Servlet; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletContextEvent; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRegistration; +import jakarta.servlet.SessionCookieConfig; +import jakarta.servlet.SessionTrackingMode; +import jakarta.servlet.descriptor.JspConfigDescriptor; +import org.h2.api.ErrorCode; +import org.h2.server.web.JakartaDbStarter; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests the JakartaDbStarter servlet. + * This test simulates a minimum servlet container environment. + */ +public class TestJakartaServlet extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + /** + * Minimum ServletContext implementation. + * Most methods are not implemented. + */ + static class TestServletContext implements ServletContext { + + private final Properties initParams = new Properties(); + private final HashMap attributes = new HashMap<>(); + + @Override + public void setAttribute(String key, Object value) { + attributes.put(key, value); + } + + @Override + public Object getAttribute(String key) { + return attributes.get(key); + } + + @Override + public boolean setInitParameter(String key, String value) { + initParams.setProperty(key, value); + return true; + } + + @Override + public String getInitParameter(String key) { + return initParams.getProperty(key); + } + + @Override + public Enumeration getAttributeNames() { + throw new UnsupportedOperationException(); + } + + @Override + public ServletContext getContext(String string) { + throw new UnsupportedOperationException(); + } + + @Override + public Enumeration getInitParameterNames() { + throw new UnsupportedOperationException(); + } + + @Override + public int getMajorVersion() { + throw new UnsupportedOperationException(); + } + + @Override + public String getMimeType(String string) { + throw new UnsupportedOperationException(); + } + + @Override + public int getMinorVersion() { + throw new UnsupportedOperationException(); + } + + @Override + public RequestDispatcher getNamedDispatcher(String string) { + throw new UnsupportedOperationException(); + } + + @Override + public String getRealPath(String string) { + throw new UnsupportedOperationException(); + } + + @Override + public RequestDispatcher getRequestDispatcher(String string) { + throw new UnsupportedOperationException(); + } + + @Override + public URL getResource(String string) { + throw new UnsupportedOperationException(); + } + + @Override + public InputStream getResourceAsStream(String string) { + throw new UnsupportedOperationException(); + } + + @Override + public Set getResourcePaths(String string) { + throw new UnsupportedOperationException(); + } + + @Override + public String getServerInfo() { + throw new UnsupportedOperationException(); + } + + /** + * @deprecated as of servlet API 2.1 + */ + @Override + @Deprecated + public Servlet getServlet(String string) { + throw new UnsupportedOperationException(); + } + + @Override + public String getServletContextName() { + throw new UnsupportedOperationException(); + } + + /** + * @deprecated as of servlet API 2.1 + */ + @Deprecated + @Override + public Enumeration getServletNames() { + throw new UnsupportedOperationException(); + } + + /** + * @deprecated as of servlet API 2.0 + */ + @Deprecated + @Override + public Enumeration getServlets() { + throw new UnsupportedOperationException(); + } + + @Override + public void log(String string) { + throw new UnsupportedOperationException(); + } + + /** + * @deprecated as of servlet API 2.1 + */ + @Deprecated + @Override + public void log(Exception exception, String string) { + throw new UnsupportedOperationException(); + } + + @Override + public void log(String string, Throwable throwable) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeAttribute(String string) { + throw new UnsupportedOperationException(); + } + + @Override + public Dynamic addFilter(String arg0, String arg1) { + throw new UnsupportedOperationException(); + } + + @Override + public Dynamic addFilter(String arg0, Filter arg1) { + throw new UnsupportedOperationException(); + } + + @Override + public Dynamic addFilter(String arg0, Class arg1) { + throw new UnsupportedOperationException(); + } + + @Override + public void addListener(String arg0) { + throw new UnsupportedOperationException(); + } + + @Override + public void addListener(T arg0) { + throw new UnsupportedOperationException(); + } + + @Override + public void addListener(Class arg0) { + throw new UnsupportedOperationException(); + } + + @Override + public jakarta.servlet.ServletRegistration.Dynamic addServlet( + String arg0, String arg1) { + throw new UnsupportedOperationException(); + } + + @Override + public jakarta.servlet.ServletRegistration.Dynamic addServlet( + String arg0, Servlet arg1) { + throw new UnsupportedOperationException(); + } + + @Override + public jakarta.servlet.ServletRegistration.Dynamic addServlet( + String arg0, Class arg1) { + throw new UnsupportedOperationException(); + } + + @Override + public T createFilter(Class arg0) + throws ServletException { + throw new UnsupportedOperationException(); + } + + @Override + public T createListener(Class arg0) + throws ServletException { + throw new UnsupportedOperationException(); + } + + @Override + public T createServlet(Class arg0) + throws ServletException { + throw new UnsupportedOperationException(); + } + + @Override + public void declareRoles(String... arg0) { + throw new UnsupportedOperationException(); + } + + @Override + public ClassLoader getClassLoader() { + throw new UnsupportedOperationException(); + } + + @Override + public String getContextPath() { + throw new UnsupportedOperationException(); + } + + @Override + public Set getDefaultSessionTrackingModes() { + throw new UnsupportedOperationException(); + } + + @Override + public int getEffectiveMajorVersion() { + throw new UnsupportedOperationException(); + } + + @Override + public int getEffectiveMinorVersion() { + throw new UnsupportedOperationException(); + } + + @Override + public Set getEffectiveSessionTrackingModes() { + throw new UnsupportedOperationException(); + } + + @Override + public FilterRegistration getFilterRegistration(String arg0) { + throw new UnsupportedOperationException(); + } + + @Override + public Map getFilterRegistrations() { + throw new UnsupportedOperationException(); + } + + @Override + public JspConfigDescriptor getJspConfigDescriptor() { + throw new UnsupportedOperationException(); + } + + @Override + public ServletRegistration getServletRegistration(String arg0) { + throw new UnsupportedOperationException(); + } + + @Override + public Map getServletRegistrations() { + throw new UnsupportedOperationException(); + } + + @Override + public SessionCookieConfig getSessionCookieConfig() { + throw new UnsupportedOperationException(); + } + + + @Override + public void setSessionTrackingModes(Set arg0) { + throw new UnsupportedOperationException(); + } + + @Override + public String getVirtualServerName() { + throw new UnsupportedOperationException(); + } + + @Override + public ServletRegistration.Dynamic addJspFile(String servletName, String jspFile) { + throw new UnsupportedOperationException(); + } + + @Override + public int getSessionTimeout() { + throw new UnsupportedOperationException(); + } + + @Override + public void setSessionTimeout(int sessionTimeout) { + throw new UnsupportedOperationException(); + } + + @Override + public String getRequestCharacterEncoding() { + throw new UnsupportedOperationException(); + } + + @Override + public void setRequestCharacterEncoding(String encoding) { + throw new UnsupportedOperationException(); + } + + @Override + public String getResponseCharacterEncoding() { + throw new UnsupportedOperationException(); + } + + @Override + public void setResponseCharacterEncoding(String encoding) { + throw new UnsupportedOperationException(); + } + + } + + @Override + public boolean isEnabled() { + if (config.networked || config.memory) { + return false; + } + return true; + } + + @Override + public void test() throws SQLException { + JakartaDbStarter listener = new JakartaDbStarter(); + + TestServletContext context = new TestServletContext(); + String url = getURL("servlet", true); + context.setInitParameter("db.url", url); + context.setInitParameter("db.user", getUser()); + context.setInitParameter("db.password", getPassword()); + context.setInitParameter("db.tcpServer", "-tcpPort 8888"); + + ServletContextEvent event = new ServletContextEvent(context); + listener.contextInitialized(event); + + Connection conn1 = listener.getConnection(); + Connection conn1a = (Connection) context.getAttribute("connection"); + assertTrue(conn1 == conn1a); + Statement stat1 = conn1.createStatement(); + stat1.execute("CREATE TABLE T(ID INT)"); + + String u2 = url.substring(url.indexOf("servlet")); + u2 = "jdbc:h2:tcp://localhost:8888/" + getBaseDir() + "/" + u2; + Connection conn2 = DriverManager.getConnection( + u2, getUser(), getPassword()); + Statement stat2 = conn2.createStatement(); + stat2.execute("SELECT * FROM T"); + stat2.execute("DROP TABLE T"); + + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1, stat1). + execute("SELECT * FROM T"); + conn2.close(); + + listener.contextDestroyed(event); + + // listener must be stopped + assertThrows(ErrorCode.CONNECTION_BROKEN_1, + () -> getConnection("jdbc:h2:tcp://localhost:8888/" + getBaseDir() + "/servlet", getUser(), + getPassword())); + + // connection must be closed + assertThrows(ErrorCode.OBJECT_CLOSED, stat1). + execute("SELECT * FROM DUAL"); + + deleteDb("servlet"); + + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestJmx.java b/h2/src/test/org/h2/test/unit/TestJmx.java new file mode 100644 index 0000000..20f6aea --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestJmx.java @@ -0,0 +1,144 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.lang.management.ManagementFactory; +import java.sql.Connection; +import java.sql.Statement; +import java.util.HashMap; +import java.util.Set; +import javax.management.Attribute; +import javax.management.MBeanAttributeInfo; +import javax.management.MBeanInfo; +import javax.management.MBeanOperationInfo; +import javax.management.MBeanServer; +import javax.management.ObjectName; +import org.h2.engine.Constants; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests the JMX feature. + */ +public class TestJmx extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase base = TestBase.createCaller().init(); + base.testFromMain(); + } + + @Override + public void test() throws Exception { + HashMap attrMap; + HashMap opMap; + String result; + MBeanInfo info; + ObjectName name; + Connection conn; + Statement stat; + + MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); + + conn = getConnection("mem:jmx;jmx=true"); + stat = conn.createStatement(); + + name = new ObjectName("org.h2:name=JMX,path=mem_jmx"); + info = mbeanServer.getMBeanInfo(name); + assertEquals("0", mbeanServer. + getAttribute(name, "CacheSizeMax").toString()); + // cache size is ignored for in-memory databases + mbeanServer.setAttribute(name, new Attribute("CacheSizeMax", 1)); + assertEquals("0", mbeanServer. + getAttribute(name, "CacheSizeMax").toString()); + assertEquals("0", mbeanServer. + getAttribute(name, "CacheSize").toString()); + assertEquals("false", mbeanServer. + getAttribute(name, "Exclusive").toString()); + assertEquals("0", mbeanServer. + getAttribute(name, "FileSize").toString()); + assertEquals("0", mbeanServer. + getAttribute(name, "FileReadCount").toString()); + assertEquals("0", mbeanServer. + getAttribute(name, "FileWriteCount").toString()); + assertEquals("REGULAR", mbeanServer. + getAttribute(name, "Mode").toString()); + assertEquals("false", mbeanServer. + getAttribute(name, "ReadOnly").toString()); + assertEquals("1", mbeanServer. + getAttribute(name, "TraceLevel").toString()); + mbeanServer.setAttribute(name, new Attribute("TraceLevel", 0)); + assertEquals("0", mbeanServer. + getAttribute(name, "TraceLevel").toString()); + assertEquals(Constants.FULL_VERSION, mbeanServer.getAttribute(name, "Version").toString()); + assertEquals(10, info.getAttributes().length); + result = mbeanServer.invoke(name, "listSettings", null, null).toString(); + assertContains(result, "ANALYZE_AUTO"); + + conn.setAutoCommit(false); + stat.execute("create table test(id int)"); + stat.execute("insert into test values(1)"); + + result = mbeanServer.invoke(name, "listSessions", null, null).toString(); + assertContains(result, "session id"); + assertContains(result, "read lock"); + + assertEquals(2, info.getOperations().length); + assertContains(info.getDescription(), "database"); + attrMap = new HashMap<>(); + for (MBeanAttributeInfo a : info.getAttributes()) { + attrMap.put(a.getName(), a); + } + assertContains(attrMap.get("CacheSize").getDescription(), "KB"); + opMap = new HashMap<>(); + for (MBeanOperationInfo o : info.getOperations()) { + opMap.put(o.getName(), o); + } + assertContains(opMap.get("listSessions").getDescription(), "lock"); + assertEquals(MBeanOperationInfo.INFO, opMap.get("listSessions").getImpact()); + + conn.close(); + + conn = getConnection("jmx;jmx=true"); + conn.close(); + conn = getConnection("jmx;jmx=true"); + + name = new ObjectName("org.h2:name=JMX,*"); + @SuppressWarnings("rawtypes") + Set set = mbeanServer.queryNames(name, null); + name = (ObjectName) set.iterator().next(); + + if (config.memory) { + assertEquals("0", mbeanServer. + getAttribute(name, "CacheSizeMax").toString()); + } else { + assertEquals("16384", mbeanServer. + getAttribute(name, "CacheSizeMax").toString()); + } + mbeanServer.setAttribute(name, new Attribute("CacheSizeMax", 1)); + if (config.memory) { + assertEquals("0", mbeanServer. + getAttribute(name, "CacheSizeMax").toString()); + } else { + assertEquals("1024", mbeanServer. + getAttribute(name, "CacheSizeMax").toString()); + assertEquals("0", mbeanServer. + getAttribute(name, "CacheSize").toString()); + assertTrue(0 < (Long) mbeanServer. + getAttribute(name, "FileReadCount")); + // FileWriteCount can be not yet updated and may return 0 + assertTrue(0 <= (Long) mbeanServer.getAttribute(name, "FileWriteCount")); + } + + conn.close(); + + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestJsonUtils.java b/h2/src/test/org/h2/test/unit/TestJsonUtils.java new file mode 100644 index 0000000..35b3bae --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestJsonUtils.java @@ -0,0 +1,340 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.math.BigDecimal; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.Callable; + +import org.h2.test.TestBase; +import org.h2.util.json.JSONByteArrayTarget; +import org.h2.util.json.JSONBytesSource; +import org.h2.util.json.JSONItemType; +import org.h2.util.json.JSONStringSource; +import org.h2.util.json.JSONStringTarget; +import org.h2.util.json.JSONTarget; +import org.h2.util.json.JSONValidationTargetWithUniqueKeys; +import org.h2.util.json.JSONValidationTargetWithoutUniqueKeys; +import org.h2.util.json.JSONValueTarget; + +/** + * Tests the classes from org.h2.util.json package. + */ +public class TestJsonUtils extends TestBase { + + private static final Charset[] CHARSETS = { StandardCharsets.UTF_8, StandardCharsets.UTF_16BE, + StandardCharsets.UTF_16LE, Charset.forName("UTF-32BE"), Charset.forName("UTF-32LE") }; + + private static final Callable> STRING_TARGET = () -> new JSONStringTarget(); + + private static final Callable> BYTES_TARGET = () -> new JSONByteArrayTarget(); + + private static final Callable> VALUE_TARGET = () -> new JSONValueTarget(); + + private static final Callable> JSON_VALIDATION_TARGET_WITHOUT_UNIQUE_KEYS = // + () -> new JSONValidationTargetWithoutUniqueKeys(); + + private static final Callable> JSON_VALIDATION_TARGET_WITH_UNIQUE_KEYS = // + () -> new JSONValidationTargetWithUniqueKeys(); + + /** + * Run just this test. + * + * @param a + * ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testTargetErrorDetection(); + testSourcesAndTargets(); + testUtfError(); + testLongNesting(); + testEncodeString(); + } + + private void testTargetErrorDetection() throws Exception { + testTargetErrorDetection(STRING_TARGET); + testTargetErrorDetection(BYTES_TARGET); + testTargetErrorDetection(VALUE_TARGET); + testTargetErrorDetection(JSON_VALIDATION_TARGET_WITHOUT_UNIQUE_KEYS); + testTargetErrorDetection(JSON_VALIDATION_TARGET_WITH_UNIQUE_KEYS); + } + + private void testTargetErrorDetection(Callable> constructor) throws Exception { + assertThrows(RuntimeException.class, () -> constructor.call().endObject()); + assertThrows(RuntimeException.class, () -> constructor.call().endArray()); + // Unexpected member without object + assertThrows(RuntimeException.class, () -> constructor.call().member("1")); + // Unexpected member inside array + JSONTarget target1 = constructor.call(); + target1.startArray(); + assertThrows(RuntimeException.class, () -> target1.member("1")); + // Unexpected member without value + JSONTarget target2 = constructor.call(); + target2.startObject(); + target2.member("1"); + assertThrows(RuntimeException.class, () -> target2.member("2")); + JSONTarget target3 = constructor.call(); + target3.startObject(); + target3.member("1"); + assertThrows(RuntimeException.class, () -> target3.endObject()); + // Unexpected value without member name + testJsonStringTargetErrorDetectionAllValues(() -> { + JSONTarget target = constructor.call(); + target.startObject(); + return target; + }); + // Unexpected second value + testJsonStringTargetErrorDetectionAllValues(() -> { + JSONTarget target = constructor.call(); + target.valueNull(); + return target; + }); + // No value + assertIncomplete(constructor.call()); + // Unclosed object + JSONTarget target = constructor.call(); + target.startObject(); + assertIncomplete(target); + // Unclosed array + target = constructor.call(); + target.startObject(); + assertIncomplete(target); + // End of array after start of object or vice versa + JSONTarget target6 = constructor.call(); + target6.startObject(); + assertThrows(RuntimeException.class, () -> target6.endArray()); + JSONTarget target7 = constructor.call(); + target7.startArray(); + assertThrows(RuntimeException.class, () -> target7.endObject()); + } + + private void assertIncomplete(JSONTarget target) { + assertThrows(RuntimeException.class, () -> target.getResult()); + } + + private void testJsonStringTargetErrorDetectionAllValues(Callable> initializer) throws Exception { + assertThrows(RuntimeException.class, () -> initializer.call().valueNull()); + assertThrows(RuntimeException.class, () -> initializer.call().valueFalse()); + assertThrows(RuntimeException.class, () -> initializer.call().valueTrue()); + assertThrows(RuntimeException.class, () -> initializer.call().valueNumber(BigDecimal.ONE)); + assertThrows(RuntimeException.class, () -> initializer.call().valueString("string")); + } + + private void testSourcesAndTargets() throws Exception { + testSourcesAndTargets("1", "1"); + testSourcesAndTargets("\uFEFF0", "0"); + testSourcesAndTargets("\uFEFF-1", "-1"); + testSourcesAndTargets("null", "null"); + testSourcesAndTargets("true", "true"); + testSourcesAndTargets("false", "false"); + testSourcesAndTargets("1.2", "1.2"); + testSourcesAndTargets("1.2e+1", "12"); + testSourcesAndTargets("10000.0", "10000.0"); + testSourcesAndTargets("\t\r\n 1.2E-1 ", "0.12"); + testSourcesAndTargets("9.99e99", "9.99E99"); + testSourcesAndTargets("\"\"", "\"\""); + testSourcesAndTargets("\"\\b\\f\\t\\r\\n\\\"\\/\\\\\\u0019\\u0020\"", "\"\\b\\f\\t\\r\\n\\\"/\\\\\\u0019 \""); + testSourcesAndTargets("{ }", "{}"); + testSourcesAndTargets("{\"a\" : 1}", "{\"a\":1}"); + testSourcesAndTargets("{\"a\" : 1, \"b\":[], \"c\":{}}", "{\"a\":1,\"b\":[],\"c\":{}}"); + testSourcesAndTargets("{\"a\" : 1, \"b\":[1,null, true,false,{}]}", "{\"a\":1,\"b\":[1,null,true,false,{}]}"); + testSourcesAndTargets("{\"1\" : [[[[[[[[[[11.1e-100]]]], null]]], {\n\r}]]]}", + "{\"1\":[[[[[[[[[[1.11E-99]]]],null]]],{}]]]}"); + testSourcesAndTargets("{\"b\":false,\"a\":1,\"a\":null}", "{\"b\":false,\"a\":1,\"a\":null}", true); + testSourcesAndTargets("[[{\"b\":false,\"a\":1,\"a\":null}]]", "[[{\"b\":false,\"a\":1,\"a\":null}]]", true); + testSourcesAndTargets("\"\uD800\uDFFF\"", "\"\uD800\uDFFF\""); + testSourcesAndTargets("\"\\uD800\\uDFFF\"", "\"\uD800\uDFFF\""); + testSourcesAndTargets("\"\u0700\"", "\"\u0700\""); + testSourcesAndTargets("\"\\u0700\"", "\"\u0700\""); + StringBuilder builder = new StringBuilder().append('"'); + for (int cp = 0x80; cp < Character.MIN_SURROGATE; cp++) { + builder.appendCodePoint(cp); + } + for (int cp = Character.MAX_SURROGATE + 1; cp < 0xfffe; cp++) { + builder.appendCodePoint(cp); + } + for (int cp = 0xffff; cp <= Character.MAX_CODE_POINT; cp++) { + builder.appendCodePoint(cp); + } + String s = builder.append('"').toString(); + testSourcesAndTargets(s, s); + testSourcesAndTargetsError("", true); + testSourcesAndTargetsError("\"", true); + testSourcesAndTargetsError("\"\\u", true); + testSourcesAndTargetsError("\u0080", true); + testSourcesAndTargetsError(".1", true); + testSourcesAndTargetsError("1.", true); + testSourcesAndTargetsError("1.1e", true); + testSourcesAndTargetsError("1.1e+", true); + testSourcesAndTargetsError("1.1e-", true); + testSourcesAndTargetsError("\b1", true); + testSourcesAndTargetsError("\"\\u", true); + testSourcesAndTargetsError("\"\\u0", true); + testSourcesAndTargetsError("\"\\u00", true); + testSourcesAndTargetsError("\"\\u000", true); + testSourcesAndTargetsError("\"\\u0000", true); + testSourcesAndTargetsError("{,}", true); + testSourcesAndTargetsError("{,,}", true); + testSourcesAndTargetsError("{}}", true); + testSourcesAndTargetsError("{\"a\":\"\":\"\"}", true); + testSourcesAndTargetsError("[]]", true); + testSourcesAndTargetsError("\"\\uZZZZ\"", true); + testSourcesAndTargetsError("\"\\x\"", true); + testSourcesAndTargetsError("\"\\", true); + testSourcesAndTargetsError("[1,", true); + testSourcesAndTargetsError("[1,,2]", true); + testSourcesAndTargetsError("[1,]", true); + testSourcesAndTargetsError("{\"a\":1,]", true); + testSourcesAndTargetsError("[1 2]", true); + testSourcesAndTargetsError("{\"a\"-1}", true); + testSourcesAndTargetsError("[1;2]", true); + testSourcesAndTargetsError("{\"a\":1,b:2}", true); + testSourcesAndTargetsError("{\"a\":1;\"b\":2}", true); + testSourcesAndTargetsError("fals", true); + testSourcesAndTargetsError("falsE", true); + testSourcesAndTargetsError("False", true); + testSourcesAndTargetsError("nul", true); + testSourcesAndTargetsError("nulL", true); + testSourcesAndTargetsError("Null", true); + testSourcesAndTargetsError("tru", true); + testSourcesAndTargetsError("truE", true); + testSourcesAndTargetsError("True", true); + testSourcesAndTargetsError("\"\uD800\"", false); + testSourcesAndTargetsError("\"\\uD800\"", true); + testSourcesAndTargetsError("\"\uDC00\"", false); + testSourcesAndTargetsError("\"\\uDC00\"", true); + testSourcesAndTargetsError("\"\uDBFF \"", false); + testSourcesAndTargetsError("\"\\uDBFF \"", true); + testSourcesAndTargetsError("\"\uDBFF\\\"", true); + testSourcesAndTargetsError("\"\\uDBFF\\\"", true); + testSourcesAndTargetsError("\"\uDFFF\uD800\"", false); + testSourcesAndTargetsError("\"\\uDFFF\\uD800\"", true); + } + + private void testSourcesAndTargets(String src, String expected) throws Exception { + testSourcesAndTargets(src, expected, false); + } + + private void testSourcesAndTargets(String src, String expected, boolean hasNonUniqueKeys) throws Exception { + JSONItemType itemType; + switch (expected.charAt(0)) { + case '[': + itemType = JSONItemType.ARRAY; + break; + case '{': + itemType = JSONItemType.OBJECT; + break; + default: + itemType = JSONItemType.SCALAR; + } + assertEquals(expected, JSONStringSource.parse(src, new JSONStringTarget())); + assertEquals(expected.getBytes(StandardCharsets.UTF_8), // + JSONStringSource.parse(src, new JSONByteArrayTarget())); + assertEquals(expected, JSONStringSource.parse(src, new JSONValueTarget()).toString()); + assertEquals(itemType, JSONStringSource.parse(src, new JSONValidationTargetWithoutUniqueKeys())); + if (hasNonUniqueKeys) { + testSourcesAndTargetsError(src, JSON_VALIDATION_TARGET_WITH_UNIQUE_KEYS, true); + } else { + assertEquals(itemType, JSONStringSource.parse(src, new JSONValidationTargetWithUniqueKeys())); + } + for (Charset charset : CHARSETS) { + assertEquals(expected, JSONBytesSource.parse(src.getBytes(charset), new JSONStringTarget())); + } + } + + private void testSourcesAndTargetsError(String src, boolean testBytes) throws Exception { + testSourcesAndTargetsError(src, STRING_TARGET, testBytes); + testSourcesAndTargetsError(src, BYTES_TARGET, testBytes); + testSourcesAndTargetsError(src, VALUE_TARGET, testBytes); + testSourcesAndTargetsError(src, JSON_VALIDATION_TARGET_WITHOUT_UNIQUE_KEYS, testBytes); + testSourcesAndTargetsError(src, JSON_VALIDATION_TARGET_WITH_UNIQUE_KEYS, testBytes); + } + + private void testSourcesAndTargetsError(String src, Callable> constructor, boolean testBytes) + throws Exception { + check: { + JSONTarget target = constructor.call(); + try { + JSONStringSource.parse(src, target); + } catch (IllegalArgumentException | IllegalStateException expected) { + // Expected + break check; + } + fail(); + } + /* + * String.getBytes() replaces invalid characters, so some tests are + * disabled. + */ + if (testBytes) { + JSONTarget target = constructor.call(); + try { + JSONBytesSource.parse(src.getBytes(StandardCharsets.UTF_8), target); + } catch (IllegalArgumentException | IllegalStateException expected) { + // Expected + return; + } + fail(); + } + } + + private void testUtfError() { + // 2 bytes + testUtfError(new byte[] { '"', (byte) 0xc2, (byte) 0xc0, '"' }); + testUtfError(new byte[] { '"', (byte) 0xc1, (byte) 0xbf, '"' }); + testUtfError(new byte[] { '"', (byte) 0xc2 }); + // 3 bytes + testUtfError(new byte[] { '"', (byte) 0xe1, (byte) 0xc0, (byte) 0x80, '"' }); + testUtfError(new byte[] { '"', (byte) 0xe1, (byte) 0x80, (byte) 0xc0, '"' }); + testUtfError(new byte[] { '"', (byte) 0xe0, (byte) 0x9f, (byte) 0xbf, '"' }); + testUtfError(new byte[] { '"', (byte) 0xe1, (byte) 0x80 }); + // 4 bytes + testUtfError(new byte[] { '"', (byte) 0xf1, (byte) 0xc0, (byte) 0x80, (byte) 0x80, '"' }); + testUtfError(new byte[] { '"', (byte) 0xf1, (byte) 0x80, (byte) 0xc0, (byte) 0x80, '"' }); + testUtfError(new byte[] { '"', (byte) 0xf1, (byte) 0x80, (byte) 0x80, (byte) 0xc0, '"' }); + testUtfError(new byte[] { '"', (byte) 0xf0, (byte) 0x8f, (byte) 0xbf, (byte) 0xbf, '"' }); + testUtfError(new byte[] { '"', (byte) 0xf4, (byte) 0x90, (byte) 0x80, (byte) 0x80, '"' }); + testUtfError(new byte[] { '"', (byte) 0xf1, (byte) 0x80, (byte) 0x80 }); + } + + private void testUtfError(byte[] bytes) { + assertThrows(IllegalArgumentException.class, + () -> JSONBytesSource.parse(bytes, new JSONValidationTargetWithoutUniqueKeys())); + } + + private void testLongNesting() { + final int halfLevel = 2048; + StringBuilder builder = new StringBuilder(halfLevel * 8); + for (int i = 0; i < halfLevel; i++) { + builder.append("{\"a\":["); + } + for (int i = 0; i < halfLevel; i++) { + builder.append("]}"); + } + String string = builder.toString(); + assertEquals(string, JSONStringSource.parse(string, new JSONStringTarget())); + byte[] bytes = string.getBytes(StandardCharsets.ISO_8859_1); + assertEquals(bytes, JSONBytesSource.normalize(bytes)); + } + + private void testEncodeString() { + testEncodeString("abc \"\u0001\u007f\u0080\u1000\uabcd\n'\t", + "\"abc \\\"\\u0001\u007f\u0080\u1000\uabcd\\n'\\t\"", + "\"abc \\\"\\u0001\u007f\\u0080\\u1000\\uabcd\\n\\u0027\\t\""); + } + + private void testEncodeString(String source, String expected, String expectedPrintable) { + assertEquals(expected, JSONStringTarget.encodeString(new StringBuilder(), source, false).toString()); + assertEquals(expectedPrintable, JSONStringTarget.encodeString(new StringBuilder(), source, true).toString()); + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestKeywords.java b/h2/src/test/org/h2/test/unit/TestKeywords.java new file mode 100644 index 0000000..b006b5d --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestKeywords.java @@ -0,0 +1,754 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.Statement; +import java.time.Duration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.TreeSet; + +import org.h2.command.Parser; +import org.h2.command.Token; +import org.h2.command.Tokenizer; +import org.h2.message.DbException; +import org.h2.test.TestBase; +import org.h2.util.ParserUtil; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** + * Tests keywords. + */ +public class TestKeywords extends TestBase { + + private enum TokenType { + IDENTIFIER, + + KEYWORD, + + CONTEXT_SENSITIVE_KEYWORD; + } + + private static final HashSet SQL92_RESERVED_WORDS = toSet(new String[] { + + "ABSOLUTE", "ACTION", "ADD", "ALL", "ALLOCATE", "ALTER", "AND", "ANY", "ARE", "AS", "ASC", "ASSERTION", + "AT", "AUTHORIZATION", "AVG", + + "BEGIN", "BETWEEN", "BIT", "BIT_LENGTH", "BOTH", "BY", + + "CASCADE", "CASCADED", "CASE", "CAST", "CATALOG", "CHAR", "CHARACTER", "CHAR_LENGTH", "CHARACTER_LENGTH", + "CHECK", "CLOSE", "COALESCE", "COLLATE", "COLLATION", "COLUMN", "COMMIT", "CONNECT", "CONNECTION", + "CONSTRAINT", "CONSTRAINTS", "CONTINUE", "CONVERT", "CORRESPONDING", "COUNT", "CREATE", "CROSS", "CURRENT", + "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_USER", "CURSOR", + + "DATE", "DAY", "DEALLOCATE", "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DEFERRABLE", "DEFERRED", "DELETE", + "DESC", "DESCRIBE", "DESCRIPTOR", "DIAGNOSTICS", "DISCONNECT", "DISTINCT", "DOMAIN", "DOUBLE", "DROP", + + "ELSE", "END", "END-EXEC", "ESCAPE", "EXCEPT", "EXCEPTION", "EXEC", "EXECUTE", "EXISTS", "EXTERNAL", + "EXTRACT", + + "FALSE", "FETCH", "FIRST", "FLOAT", "FOR", "FOREIGN", "FOUND", "FROM", "FULL", + + "GET", "GLOBAL", "GO", "GOTO", "GRANT", "GROUP", + + "HAVING", "HOUR", + + "IDENTITY", "IMMEDIATE", "IN", "INDICATOR", "INITIALLY", "INNER", "INPUT", "INSENSITIVE", "INSERT", "INT", + "INTEGER", "INTERSECT", "INTERVAL", "INTO", "IS", "ISOLATION", + + "JOIN", + + "KEY", + + "LANGUAGE", "LAST", "LEADING", "LEFT", "LEVEL", "LIKE", "LOCAL", "LOWER", + + "MATCH", "MAX", "MIN", "MINUTE", "MODULE", "MONTH", + + "NAMES", "NATIONAL", "NATURAL", "NCHAR", "NEXT", "NO", "NOT", "NULL", "NULLIF", "NUMERIC", + + "OCTET_LENGTH", "OF", "ON", "ONLY", "OPEN", "OPTION", "OR", "ORDER", "OUTER", "OUTPUT", "OVERLAPS", + + "PAD", "PARTIAL", "POSITION", "PRECISION", "PREPARE", "PRESERVE", "PRIMARY", "PRIOR", "PRIVILEGES", + "PROCEDURE", "PUBLIC", + + "READ", "REAL", "REFERENCES", "RELATIVE", "RESTRICT", "REVOKE", "RIGHT", "ROLLBACK", "ROWS", + + "SCHEMA", "SCROLL", "SECOND", "SECTION", "SELECT", "SESSION", "SESSION_USER", "SET", "SIZE", "SMALLINT", + "SOME", "SPACE", "SQL", "SQLCODE", "SQLERROR", "SQLSTATE", "SUBSTRING", "SUM", "SYSTEM_USER", + + "TABLE", "TEMPORARY", "THEN", "TIME", "TIMESTAMP", "TIMEZONE_HOUR", "TIMEZONE_MINUTE", "TO", "TRAILING", + "TRANSACTION", "TRANSLATE", "TRANSLATION", "TRIM", "TRUE", + + "UNION", "UNIQUE", "UNKNOWN", "UPDATE", "UPPER", "USAGE", "USER", "USING", + + "VALUE", "VALUES", "VARCHAR", "VARYING", "VIEW", + + "WHEN", "WHENEVER", "WHERE", "WITH", "WORK", "WRITE", + + "YEAR", + + "ZONE", + + }); + + private static final HashSet SQL1999_RESERVED_WORDS = toSet(new String[] { + + "ABSOLUTE", "ACTION", "ADD", "ADMIN", "AFTER", "AGGREGATE", "ALIAS", "ALL", "ALLOCATE", "ALTER", "AND", + "ANY", "ARE", "ARRAY", "AS", "ASC", "ASSERTION", "AT", "AUTHORIZATION", + + "BEFORE", "BEGIN", "BINARY", "BIT", "BLOB", "BOOLEAN", "BOTH", "BREADTH", "BY", + + "CALL", "CASCADE", "CASCADED", "CASE", "CAST", "CATALOG", "CHAR", "CHARACTER", "CHECK", "CLASS", "CLOB", + "CLOSE", "COLLATE", "COLLATION", "COLUMN", "COMMIT", "COMPLETION", "CONNECT", "CONNECTION", "CONSTRAINT", + "CONSTRAINTS", "CONSTRUCTOR", "CONTINUE", "CORRESPONDING", "CREATE", "CROSS", "CUBE", "CURRENT", + "CURRENT_DATE", "CURRENT_PATH", "CURRENT_ROLE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_USER", + "CURSOR", "CYCLE", + + "DATA", "DATE", "DAY", "DEALLOCATE", "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DEFERRABLE", "DEFERRED", + "DELETE", "DEPTH", "DEREF", "DESC", "DESCRIBE", "DESCRIPTOR", "DESTROY", "DESTRUCTOR", "DETERMINISTIC", + "DICTIONARY", "DIAGNOSTICS", "DISCONNECT", "DISTINCT", "DOMAIN", "DOUBLE", "DROP", "DYNAMIC", + + "EACH", "ELSE", "END", "END-EXEC", "EQUALS", "ESCAPE", "EVERY", "EXCEPT", "EXCEPTION", "EXEC", "EXECUTE", + "EXTERNAL", + + "FALSE", "FETCH", "FIRST", "FLOAT", "FOR", "FOREIGN", "FOUND", "FROM", "FREE", "FULL", "FUNCTION", + + "GENERAL", "GET", "GLOBAL", "GO", "GOTO", "GRANT", "GROUP", "GROUPING", + + "HAVING", "HOST", "HOUR", + + "IDENTITY", "IGNORE", "IMMEDIATE", "IN", "INDICATOR", "INITIALIZE", "INITIALLY", "INNER", "INOUT", "INPUT", + "INSERT", "INT", "INTEGER", "INTERSECT", "INTERVAL", "INTO", "IS", "ISOLATION", "ITERATE", + + "JOIN", + + "KEY", + + "LANGUAGE", "LARGE", "LAST", "LATERAL", "LEADING", "LEFT", "LESS", "LEVEL", "LIKE", "LIMIT", "LOCAL", + "LOCALTIME", "LOCALTIMESTAMP", "LOCATOR", + + "MAP", "MATCH", "MINUTE", "MODIFIES", "MODIFY", "MODULE", "MONTH", + + "NAMES", "NATIONAL", "NATURAL", "NCHAR", "NCLOB", "NEW", "NEXT", "NO", "NONE", "NOT", "NULL", "NUMERIC", + + "OBJECT", "OF", "OFF", "OLD", "ON", "ONLY", "OPEN", "OPERATION", "OPTION", "OR", "ORDER", "ORDINALITY", + "OUT", "OUTER", "OUTPUT", + + "PAD", "PARAMETER", "PARAMETERS", "PARTIAL", "PATH", "POSTFIX", "PRECISION", "PREFIX", "PREORDER", + "PREPARE", "PRESERVE", "PRIMARY", "PRIOR", "PRIVILEGES", "PROCEDURE", "PUBLIC", + + "READ", "READS", "REAL", "RECURSIVE", "REF", "REFERENCES", "REFERENCING", "RELATIVE", "RESTRICT", "RESULT", + "RETURN", "RETURNS", "REVOKE", "RIGHT", "ROLE", "ROLLBACK", "ROLLUP", "ROUTINE", "ROW", "ROWS", + + "SAVEPOINT", "SCHEMA", "SCROLL", "SCOPE", "SEARCH", "SECOND", "SECTION", "SELECT", "SEQUENCE", "SESSION", + "SESSION_USER", "SET", "SETS", "SIZE", "SMALLINT", "SOME", "SPACE", "SPECIFIC", "SPECIFICTYPE", "SQL", + "SQLEXCEPTION", "SQLSTATE", "SQLWARNING", "START", "STATE", "STATEMENT", "STATIC", "STRUCTURE", + "SYSTEM_USER", + + "TABLE", "TEMPORARY", "TERMINATE", "THAN", "THEN", "TIME", "TIMESTAMP", "TIMEZONE_HOUR", "TIMEZONE_MINUTE", + "TO", "TRAILING", "TRANSACTION", "TRANSLATION", "TREAT", "TRIGGER", "TRUE", + + "UNDER", "UNION", "UNIQUE", "UNKNOWN", "UNNEST", "UPDATE", "USAGE", "USER", "USING", + + "VALUE", "VALUES", "VARCHAR", "VARIABLE", "VARYING", "VIEW", + + "WHEN", "WHENEVER", "WHERE", "WITH", "WITHOUT", "WORK", "WRITE", + + "YEAR", "ZONE", + + }); + + private static final HashSet SQL2003_RESERVED_WORDS = toSet(new String[] { + + "ABS", "ALL", "ALLOCATE", "ALTER", "AND", "ANY", "ARE", "ARRAY", "AS", "ASENSITIVE", "ASYMMETRIC", "AT", + "ATOMIC", "AUTHORIZATION", "AVG", + + "BEGIN", "BETWEEN", "BIGINT", "BINARY", "BLOB", "BOOLEAN", "BOTH", "BY", + + "CALL", "CALLED", "CARDINALITY", "CASCADED", "CASE", "CAST", "CEIL", "CEILING", "CHAR", "CHAR_LENGTH", + "CHARACTER", "CHARACTER_LENGTH", "CHECK", "CLOB", "CLOSE", "COALESCE", "COLLATE", "COLLECT", "COLUMN", + "COMMIT", "CONDITION", "CONNECT", "CONSTRAINT", "CONVERT", "CORR", "CORRESPONDING", "COUNT", "COVAR_POP", + "COVAR_SAMP", "CREATE", "CROSS", "CUBE", "CUME_DIST", "CURRENT", "CURRENT_DATE", + "CURRENT_DEFAULT_TRANSFORM_GROUP", "CURRENT_PATH", "CURRENT_ROLE", "CURRENT_TIME", "CURRENT_TIMESTAMP", + "CURRENT_TRANSFORM_GROUP_FOR_TYPE", "CURRENT_USER", "CURSOR", "CYCLE", + + "DATE", "DAY", "DEALLOCATE", "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DELETE", "DENSE_RANK", "DEREF", + "DESCRIBE", "DETERMINISTIC", "DISCONNECT", "DISTINCT", "DOUBLE", "DROP", "DYNAMIC", + + "EACH", "ELEMENT", "ELSE", "END", "END-EXEC", "ESCAPE", "EVERY", "EXCEPT", "EXEC", "EXECUTE", "EXISTS", + "EXP", "EXTERNAL", "EXTRACT", + + "FALSE", "FETCH", "FILTER", "FLOAT", "FLOOR", "FOR", "FOREIGN", "FREE", "FROM", "FULL", "FUNCTION", + "FUSION", + + "GET", "GLOBAL", "GRANT", "GROUP", "GROUPING", + + "HAVING", "HOLD", "HOUR", "IDENTITY", "IN", "INDICATOR", "INNER", "INOUT", "INSENSITIVE", + + "INSERT", "INT", "INTEGER", "INTERSECT", "INTERSECTION", "INTERVAL", "INTO", "IS", + + "JOIN", + + "LANGUAGE", "LARGE", "LATERAL", "LEADING", "LEFT", "LIKE", "LN", "LOCAL", "LOCALTIME", "LOCALTIMESTAMP", + "LOWER", + + "MATCH", "MAX", "MEMBER", "MERGE", "METHOD", "MIN", "MINUTE", "MOD", "MODIFIES", "MODULE", "MONTH", + "MULTISET", + + "NATIONAL", "NATURAL", "NCHAR", "NCLOB", "NEW", "NO", "NONE", "NORMALIZE", "NOT", "NULL", "NULLIF", + "NUMERIC", + + "OCTET_LENGTH", "OF", "OLD", "ON", "ONLY", "OPEN", "OR", "ORDER", "OUT", "OUTER", "OVER", "OVERLAPS", + "OVERLAY", + + "PARAMETER", "PARTITION", "PERCENT_RANK", "PERCENTILE_CONT", "PERCENTILE_DISC", "POSITION", "POWER", + "PRECISION", "PREPARE", "PRIMARY", "PROCEDURE", + + "RANGE", "RANK", "READS", "REAL", "RECURSIVE", "REF", "REFERENCES", "REFERENCING", "REGR_AVGX", // + "REGR_AVGY", "REGR_COUNT", "REGR_INTERCEPT", "REGR_R2", "REGR_SLOPE", "REGR_SXX", "REGR_SXY", "REGR_SYY", + "RELEASE", "RESULT", "RETURN", "RETURNS", "REVOKE", "RIGHT", "ROLLBACK", "ROLLUP", "ROW", "ROW_NUMBER", + "ROWS", + + "SAVEPOINT", "SCOPE", "SCROLL", "SEARCH", "SECOND", "SELECT", "SENSITIVE", "SESSION_USER", "SET", // + "SIMILAR", "SMALLINT", "SOME", "SPECIFIC", "SPECIFICTYPE", "SQL", "SQLEXCEPTION", "SQLSTATE", "SQLWARNING", + "SQRT", "START", "STATIC", "STDDEV_POP", "STDDEV_SAMP", "SUBMULTISET", "SUBSTRING", "SUM", "SYMMETRIC", + "SYSTEM", "SYSTEM_USER", + + "TABLE", "TABLESAMPLE", "THEN", "TIME", "TIMESTAMP", "TIMEZONE_HOUR", "TIMEZONE_MINUTE", "TO", "TRAILING", + "TRANSLATE", "TRANSLATION", "TREAT", "TRIGGER", "TRIM", "TRUE", + + "UESCAPE", "UNION", "UNIQUE", "UNKNOWN", "UNNEST", "UPDATE", "UPPER", "USER", "USING", + + "VALUE", "VALUES", "VAR_POP", "VAR_SAMP", "VARCHAR", "VARYING", + + "WHEN", "WHENEVER", "WHERE", "WIDTH_BUCKET", "WINDOW", "WITH", "WITHIN", "WITHOUT", + + "YEAR", + + }); + + private static final HashSet SQL2008_RESERVED_WORDS = toSet(new String[] { + + "ABS", "ALL", "ALLOCATE", "ALTER", "AND", "ANY", "ARE", "ARRAY", "AS", "ASENSITIVE", "ASYMMETRIC", "AT", + "ATOMIC", "AUTHORIZATION", "AVG", + + "BEGIN", "BETWEEN", "BIGINT", "BINARY", "BLOB", "BOOLEAN", "BOTH", "BY", + + "CALL", "CALLED", "CARDINALITY", "CASCADED", "CASE", "CAST", "CEIL", "CEILING", "CHAR", "CHAR_LENGTH", + "CHARACTER", "CHARACTER_LENGTH", "CHECK", "CLOB", "CLOSE", "COALESCE", "COLLATE", "COLLECT", "COLUMN", + "COMMIT", "CONDITION", "CONNECT", "CONSTRAINT", "CONVERT", "CORR", "CORRESPONDING", "COUNT", "COVAR_POP", + "COVAR_SAMP", "CREATE", "CROSS", "CUBE", "CUME_DIST", "CURRENT", "CURRENT_CATALOG", "CURRENT_DATE", + "CURRENT_DEFAULT_TRANSFORM_GROUP", "CURRENT_PATH", "CURRENT_ROLE", "CURRENT_SCHEMA", "CURRENT_TIME", + "CURRENT_TIMESTAMP", "CURRENT_TRANSFORM_GROUP_FOR_TYPE", "CURRENT_USER", "CURSOR", "CYCLE", + + "DATE", "DAY", "DEALLOCATE", "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DELETE", "DENSE_RANK", "DEREF", + "DESCRIBE", "DETERMINISTIC", "DISCONNECT", "DISTINCT", "DOUBLE", "DROP", "DYNAMIC", + + "EACH", "ELEMENT", "ELSE", "END", "END-EXEC", "ESCAPE", "EVERY", "EXCEPT", "EXEC", "EXECUTE", "EXISTS", + "EXP", "EXTERNAL", "EXTRACT", + + "FALSE", "FETCH", "FILTER", "FLOAT", "FLOOR", "FOR", "FOREIGN", "FREE", "FROM", "FULL", "FUNCTION", + "FUSION", + + "GET", "GLOBAL", "GRANT", "GROUP", "GROUPING", + + "HAVING", "HOLD", "HOUR", + + "IDENTITY", "IN", "INDICATOR", "INNER", "INOUT", "INSENSITIVE", "INSERT", "INT", "INTEGER", "INTERSECT", + "INTERSECTION", "INTERVAL", "INTO", "IS", + + "JOIN", + + "LANGUAGE", "LARGE", "LATERAL", "LEADING", "LEFT", "LIKE", "LIKE_REGEX", "LN", "LOCAL", "LOCALTIME", + "LOCALTIMESTAMP", "LOWER", + + "MATCH", "MAX", "MEMBER", "MERGE", "METHOD", "MIN", "MINUTE", "MOD", "MODIFIES", "MODULE", "MONTH", + "MULTISET", + + "NATIONAL", "NATURAL", "NCHAR", "NCLOB", "NEW", "NO", "NONE", "NORMALIZE", "NOT", "NULL", "NULLIF", + "NUMERIC", + + "OCTET_LENGTH", "OCCURRENCES_REGEX", "OF", "OLD", "ON", "ONLY", "OPEN", "OR", "ORDER", "OUT", "OUTER", + "OVER", "OVERLAPS", "OVERLAY", + + "PARAMETER", "PARTITION", "PERCENT_RANK", "PERCENTILE_CONT", "PERCENTILE_DISC", "POSITION", + "POSITION_REGEX", "POWER", "PRECISION", "PREPARE", "PRIMARY", "PROCEDURE", + + "RANGE", "RANK", "READS", "REAL", "RECURSIVE", "REF", "REFERENCES", "REFERENCING", "REGR_AVGX", // + "REGR_AVGY", "REGR_COUNT", "REGR_INTERCEPT", "REGR_R2", "REGR_SLOPE", "REGR_SXX", "REGR_SXY", "REGR_SYY", + "RELEASE", "RESULT", "RETURN", "RETURNS", "REVOKE", "RIGHT", "ROLLBACK", "ROLLUP", "ROW", "ROW_NUMBER", + "ROWS", + + "SAVEPOINT", "SCOPE", "SCROLL", "SEARCH", "SECOND", "SELECT", "SENSITIVE", "SESSION_USER", "SET", // + "SIMILAR", "SMALLINT", "SOME", "SPECIFIC", "SPECIFICTYPE", "SQL", "SQLEXCEPTION", "SQLSTATE", "SQLWARNING", + "SQRT", "START", "STATIC", "STDDEV_POP", "STDDEV_SAMP", "SUBMULTISET", "SUBSTRING", "SUBSTRING_REGEX", + "SUM", "SYMMETRIC", "SYSTEM", "SYSTEM_USER", + + "TABLE", "TABLESAMPLE", "THEN", "TIME", "TIMESTAMP", "TIMEZONE_HOUR", "TIMEZONE_MINUTE", "TO", "TRAILING", + "TRANSLATE", "TRANSLATE_REGEX", "TRANSLATION", "TREAT", "TRIGGER", "TRIM", "TRUE", + + "UESCAPE", "UNION", "UNIQUE", "UNKNOWN", "UNNEST", "UPDATE", "UPPER", "USER", "USING", + + "VALUE", "VALUES", "VAR_POP", "VAR_SAMP", "VARBINARY", "VARCHAR", "VARYING", + + "WHEN", "WHENEVER", "WHERE", "WIDTH_BUCKET", "WINDOW", "WITH", "WITHIN", "WITHOUT", + + "YEAR", + + }); + + private static final HashSet SQL2011_RESERVED_WORDS = toSet(new String[] { + + "ABS", "ALL", "ALLOCATE", "ALTER", "AND", "ANY", "ARE", "ARRAY", "ARRAY_AGG", "ARRAY_MAX_CARDINALITY", // + "AS", "ASENSITIVE", "ASYMMETRIC", "AT", "ATOMIC", "AUTHORIZATION", "AVG", + + "BEGIN", "BEGIN_FRAME", "BEGIN_PARTITION", "BETWEEN", "BIGINT", "BINARY", "BLOB", "BOOLEAN", "BOTH", "BY", + + "CALL", "CALLED", "CARDINALITY", "CASCADED", "CASE", "CAST", "CEIL", "CEILING", "CHAR", "CHAR_LENGTH", + "CHARACTER", "CHARACTER_LENGTH", "CHECK", "CLOB", "CLOSE", "COALESCE", "COLLATE", "COLLECT", "COLUMN", + "COMMIT", "CONDITION", "CONNECT", "CONSTRAINT", "CONTAINS", "CONVERT", "CORR", "CORRESPONDING", "COUNT", + "COVAR_POP", "COVAR_SAMP", "CREATE", "CROSS", "CUBE", "CUME_DIST", "CURRENT", "CURRENT_CATALOG", + "CURRENT_DATE", "CURRENT_DEFAULT_TRANSFORM_GROUP", "CURRENT_PATH", "CURRENT_ROLE", "CURRENT_ROW", + "CURRENT_SCHEMA", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_TRANSFORM_GROUP_FOR_TYPE", "CURRENT_USER", + "CURSOR", "CYCLE", + + "DATE", "DAY", "DEALLOCATE", "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DELETE", "DENSE_RANK", "DEREF", + "DESCRIBE", "DETERMINISTIC", "DISCONNECT", "DISTINCT", "DOUBLE", "DROP", "DYNAMIC", + + "EACH", "ELEMENT", "ELSE", "END", "END_FRAME", "END_PARTITION", "END-EXEC", "EQUALS", "ESCAPE", "EVERY", + "EXCEPT", "EXEC", "EXECUTE", "EXISTS", "EXP", "EXTERNAL", "EXTRACT", + + "FALSE", "FETCH", "FILTER", "FIRST_VALUE", "FLOAT", "FLOOR", "FOR", "FOREIGN", "FRAME_ROW", "FREE", "FROM", + "FULL", "FUNCTION", "FUSION", + + "GET", "GLOBAL", "GRANT", "GROUP", "GROUPING", "GROUPS", + + "HAVING", "HOLD", "HOUR", + + "IDENTITY", "IN", "INDICATOR", "INNER", "INOUT", "INSENSITIVE", "INSERT", "INT", "INTEGER", "INTERSECT", + "INTERSECTION", "INTERVAL", "INTO", "IS", + + "JOIN", + + "LAG", "LANGUAGE", "LARGE", "LAST_VALUE", "LATERAL", "LEAD", "LEADING", "LEFT", "LIKE", "LIKE_REGEX", "LN", + "LOCAL", "LOCALTIME", "LOCALTIMESTAMP", "LOWER", + + "MATCH", "MAX", "MEMBER", "MERGE", "METHOD", "MIN", "MINUTE", "MOD", "MODIFIES", "MODULE", "MONTH", + "MULTISET", + + "NATIONAL", "NATURAL", "NCHAR", "NCLOB", "NEW", "NO", "NONE", "NORMALIZE", "NOT", "NTH_VALUE", "NTILE", + "NULL", "NULLIF", "NUMERIC", + + "OCTET_LENGTH", "OCCURRENCES_REGEX", "OF", "OFFSET", "OLD", "ON", "ONLY", "OPEN", "OR", "ORDER", "OUT", + "OUTER", "OVER", "OVERLAPS", "OVERLAY", + + "PARAMETER", "PARTITION", "PERCENT", "PERCENT_RANK", "PERCENTILE_CONT", "PERCENTILE_DISC", "PERIOD", + "PORTION", "POSITION", "POSITION_REGEX", "POWER", "PRECEDES", "PRECISION", "PREPARE", "PRIMARY", + "PROCEDURE", + + "RANGE", "RANK", "READS", "REAL", "RECURSIVE", "REF", "REFERENCES", "REFERENCING", "REGR_AVGX", // + "REGR_AVGY", "REGR_COUNT", "REGR_INTERCEPT", "REGR_R2", "REGR_SLOPE", "REGR_SXX", "REGR_SXY", "REGR_SYY", + "RELEASE", "RESULT", "RETURN", "RETURNS", "REVOKE", "RIGHT", "ROLLBACK", "ROLLUP", "ROW", "ROW_NUMBER", + "ROWS", + + "SAVEPOINT", "SCOPE", "SCROLL", "SEARCH", "SECOND", "SELECT", "SENSITIVE", "SESSION_USER", "SET", // + "SIMILAR", "SMALLINT", "SOME", "SPECIFIC", "SPECIFICTYPE", "SQL", "SQLEXCEPTION", "SQLSTATE", "SQLWARNING", + "SQRT", "START", "STATIC", "STDDEV_POP", "STDDEV_SAMP", "SUBMULTISET", "SUBSTRING", "SUBSTRING_REGEX", + "SUCCEEDS", "SUM", "SYMMETRIC", "SYSTEM", "SYSTEM_TIME", "SYSTEM_USER", + + "TABLE", "TABLESAMPLE", "THEN", "TIME", "TIMESTAMP", "TIMEZONE_HOUR", "TIMEZONE_MINUTE", "TO", "TRAILING", + "TRANSLATE", "TRANSLATE_REGEX", "TRANSLATION", "TREAT", "TRIGGER", "TRUNCATE", "TRIM", "TRIM_ARRAY", // + "TRUE", + + "UESCAPE", "UNION", "UNIQUE", "UNKNOWN", "UNNEST", "UPDATE", "UPPER", "USER", "USING", + + "VALUE", "VALUES", "VALUE_OF", "VAR_POP", "VAR_SAMP", "VARBINARY", "VARCHAR", "VARYING", "VERSIONING", + + "WHEN", "WHENEVER", "WHERE", "WIDTH_BUCKET", "WINDOW", "WITH", "WITHIN", "WITHOUT", + + "YEAR", + + }); + + private static final HashSet SQL2016_RESERVED_WORDS = toSet(new String[] { + + "ABS", "ACOS", "ALL", "ALLOCATE", "ALTER", "AND", "ANY", "ARE", "ARRAY", "ARRAY_AGG", + "ARRAY_MAX_CARDINALITY", "AS", "ASENSITIVE", "ASIN", "ASYMMETRIC", "AT", "ATAN", "ATOMIC", "AUTHORIZATION", + "AVG", + + "BEGIN", "BEGIN_FRAME", "BEGIN_PARTITION", "BETWEEN", "BIGINT", "BINARY", "BLOB", "BOOLEAN", "BOTH", "BY", + + "CALL", "CALLED", "CARDINALITY", "CASCADED", "CASE", "CAST", "CEIL", "CEILING", "CHAR", "CHAR_LENGTH", + "CHARACTER", "CHARACTER_LENGTH", "CHECK", "CLASSIFIER", "CLOB", "CLOSE", "COALESCE", "COLLATE", "COLLECT", + "COLUMN", "COMMIT", "CONDITION", "CONNECT", "CONSTRAINT", "CONTAINS", "CONVERT", "COPY", "CORR", + "CORRESPONDING", "COS", "COSH", "COUNT", "COVAR_POP", "COVAR_SAMP", "CREATE", "CROSS", "CUBE", "CUME_DIST", + "CURRENT", "CURRENT_CATALOG", "CURRENT_DATE", "CURRENT_DEFAULT_TRANSFORM_GROUP", "CURRENT_PATH", + "CURRENT_ROLE", "CURRENT_ROW", "CURRENT_SCHEMA", "CURRENT_TIME", "CURRENT_TIMESTAMP", + "CURRENT_TRANSFORM_GROUP_FOR_TYPE", "CURRENT_USER", "CURSOR", "CYCLE", + + "DATE", "DAY", "DEALLOCATE", "DEC", "DECIMAL", "DECFLOAT", "DECLARE", "DEFAULT", "DEFINE", "DELETE", + "DENSE_RANK", "DEREF", "DESCRIBE", "DETERMINISTIC", "DISCONNECT", "DISTINCT", "DOUBLE", "DROP", "DYNAMIC", + + "EACH", "ELEMENT", "ELSE", "EMPTY", "END", "END_FRAME", "END_PARTITION", "END-EXEC", "EQUALS", "ESCAPE", + "EVERY", "EXCEPT", "EXEC", "EXECUTE", "EXISTS", "EXP", "EXTERNAL", "EXTRACT", + + "FALSE", "FETCH", "FILTER", "FIRST_VALUE", "FLOAT", "FLOOR", "FOR", "FOREIGN", "FRAME_ROW", "FREE", "FROM", + "FULL", "FUNCTION", "FUSION", + + "GET", "GLOBAL", "GRANT", "GROUP", "GROUPING", "GROUPS", + + "HAVING", "HOLD", "HOUR", + + "IDENTITY", "IN", "INDICATOR", "INITIAL", "INNER", "INOUT", "INSENSITIVE", "INSERT", "INT", "INTEGER", + "INTERSECT", "INTERSECTION", "INTERVAL", "INTO", "IS", + + "JOIN", "JSON_ARRAY", "JSON_ARRAYAGG", "JSON_EXISTS", "JSON_OBJECT", "JSON_OBJECTAGG", "JSON_QUERY", + "JSON_TABLE", "JSON_TABLE_PRIMITIVE", "JSON_VALUE", + + "LAG", "LANGUAGE", "LARGE", "LAST_VALUE", "LATERAL", "LEAD", "LEADING", "LEFT", "LIKE", "LIKE_REGEX", + "LISTAGG", "LN", "LOCAL", "LOCALTIME", "LOCALTIMESTAMP", "LOG", "LOG10", "LOWER", + + "MATCH", "MATCH_NUMBER", "MATCH_RECOGNIZE", "MATCHES", "MAX", "MEMBER", "MERGE", "METHOD", "MIN", "MINUTE", + "MOD", "MODIFIES", "MODULE", "MONTH", "MULTISET", + + "NATIONAL", "NATURAL", "NCHAR", "NCLOB", "NEW", "NO", "NONE", "NORMALIZE", "NOT", "NTH_VALUE", "NTILE", + "NULL", "NULLIF", "NUMERIC", + + "OCTET_LENGTH", "OCCURRENCES_REGEX", "OF", "OFFSET", "OLD", "OMIT", "ON", "ONE", "ONLY", "OPEN", "OR", + "ORDER", "OUT", "OUTER", "OVER", "OVERLAPS", "OVERLAY", + + "PARAMETER", "PARTITION", "PATTERN", "PER", "PERCENT", "PERCENT_RANK", "PERCENTILE_CONT", // + "PERCENTILE_DISC", "PERIOD", "PORTION", "POSITION", "POSITION_REGEX", "POWER", "PRECEDES", "PRECISION", + "PREPARE", "PRIMARY", "PROCEDURE", "PTF", + + "RANGE", "RANK", "READS", "REAL", "RECURSIVE", "REF", "REFERENCES", "REFERENCING", "REGR_AVGX", // + "REGR_AVGY", "REGR_COUNT", "REGR_INTERCEPT", "REGR_R2", "REGR_SLOPE", "REGR_SXX", "REGR_SXY", "REGR_SYY", + "RELEASE", "RESULT", "RETURN", "RETURNS", "REVOKE", "RIGHT", "ROLLBACK", "ROLLUP", "ROW", "ROW_NUMBER", + "ROWS", "RUNNING", + + "SAVEPOINT", "SCOPE", "SCROLL", "SEARCH", "SECOND", "SEEK", "SELECT", "SENSITIVE", "SESSION_USER", "SET", + "SHOW", "SIMILAR", "SIN", "SINH", "SKIP", "SMALLINT", "SOME", "SPECIFIC", "SPECIFICTYPE", "SQL", + "SQLEXCEPTION", "SQLSTATE", "SQLWARNING", "SQRT", "START", "STATIC", "STDDEV_POP", "STDDEV_SAMP", + "SUBMULTISET", "SUBSET", "SUBSTRING", "SUBSTRING_REGEX", "SUCCEEDS", "SUM", "SYMMETRIC", "SYSTEM", + "SYSTEM_TIME", "SYSTEM_USER", + + "TABLE", "TABLESAMPLE", "TAN", "TANH", "THEN", "TIME", "TIMESTAMP", "TIMEZONE_HOUR", "TIMEZONE_MINUTE", + "TO", "TRAILING", "TRANSLATE", "TRANSLATE_REGEX", "TRANSLATION", "TREAT", "TRIGGER", "TRIM", "TRIM_ARRAY", + "TRUE", "TRUNCATE", + + "UESCAPE", "UNION", "UNIQUE", "UNKNOWN", "UNNEST", "UPDATE", "UPPER", "USER", "USING", + + "VALUE", "VALUES", "VALUE_OF", "VAR_POP", "VAR_SAMP", "VARBINARY", "VARCHAR", "VARYING", "VERSIONING", + + "WHEN", "WHENEVER", "WHERE", "WIDTH_BUCKET", "WINDOW", "WITH", "WITHIN", "WITHOUT", + + "YEAR", + + }); + + private static final HashSet STRICT_MODE_NON_KEYWORDS = toSet(new String[] { "LIMIT", "MINUS", "TOP" }); + + private static final HashSet ALL_RESEVED_WORDS; + + private static final HashMap TOKENS; + + static { + HashSet set = new HashSet<>(1024); + set.addAll(SQL92_RESERVED_WORDS); + set.addAll(SQL1999_RESERVED_WORDS); + set.addAll(SQL2003_RESERVED_WORDS); + set.addAll(SQL2008_RESERVED_WORDS); + set.addAll(SQL2011_RESERVED_WORDS); + set.addAll(SQL2016_RESERVED_WORDS); + ALL_RESEVED_WORDS = set; + HashMap tokens = new HashMap<>(); + processClass(Parser.class, tokens); + processClass(ParserUtil.class, tokens); + processClass(Token.class, tokens); + processClass(Tokenizer.class, tokens); + TOKENS = tokens; + } + + private static void processClass(Class clazz, HashMap tokens) { + ClassReader r; + try { + r = new ClassReader(clazz.getResourceAsStream(clazz.getSimpleName() + ".class")); + } catch (IOException e) { + throw DbException.convert(e); + } + r.accept(new ClassVisitor(Opcodes.ASM8) { + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, // + Object value) { + add(value); + return null; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, + String[] exceptions) { + return new MethodVisitor(Opcodes.ASM8) { + @Override + public void visitLdcInsn(Object value) { + add(value); + } + }; + } + + void add(Object value) { + if (!(value instanceof String)) { + return; + } + String s = (String) value; + int l = s.length(); + if (l == 0) { + return; + } + for (int i = 0; i < l; i++) { + char ch = s.charAt(i); + if ((ch < 'A' || ch > 'Z') && ch != '_') { + return; + } + } + final TokenType type; + switch (ParserUtil.getTokenType(s, false, true)) { + case ParserUtil.IDENTIFIER: + type = TokenType.IDENTIFIER; + break; + case ParserUtil.KEYWORD: + type = TokenType.CONTEXT_SENSITIVE_KEYWORD; + break; + default: + type = TokenType.KEYWORD; + } + tokens.put(s, type); + } + }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + } + + private static HashSet toSet(String[] array) { + HashSet set = new HashSet<>((int) Math.ceil(array.length / .75)); + for (String reservedWord : array) { + if (!set.add(reservedWord)) { + throw new AssertionError(reservedWord); + } + } + return set; + } + + /** + * Run just this test. + * + * @param a + * ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testParser(); + testInformationSchema(); + testMetaData(); + } + + private void testParser() throws Exception { + testParser(false); + testParser(true); + } + + private void testParser(boolean strictMode) throws Exception { + try (Connection conn = DriverManager + .getConnection("jdbc:h2:mem:keywords;MODE=" + (strictMode ? "STRICT" : "REGULAR"))) { + Statement stat = conn.createStatement(); + for (Entry entry : TOKENS.entrySet()) { + String s = entry.getKey(); + TokenType type = entry.getValue(); + if (strictMode && STRICT_MODE_NON_KEYWORDS.contains(s)) { + type = TokenType.IDENTIFIER; + } + Throwable exception1 = null, exception2 = null; + try { + stat.execute("CREATE TABLE " + s + '(' + s + " INT)"); + stat.execute("INSERT INTO " + s + '(' + s + ") VALUES (10)"); + } catch (Throwable t) { + exception1 = t; + } + if (exception1 == null) { + try { + try (ResultSet rs = stat.executeQuery("SELECT " + s + " FROM " + s)) { + assertTrue(rs.next()); + assertEquals(10, rs.getInt(1)); + assertFalse(rs.next()); + } + try (ResultSet rs = stat.executeQuery("SELECT SUM(" + s + ") " + s + " FROM " + s + ' ' + s)) { + assertTrue(rs.next()); + assertEquals(10, rs.getInt(1)); + assertFalse(rs.next()); + assertEquals(s, rs.getMetaData().getColumnLabel(1)); + } + try (ResultSet rs = stat.executeQuery("SELECT CASE " + s + " WHEN 10 THEN 1 END FROM " + s)) { + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertFalse(rs.next()); + } + stat.execute("DROP TABLE " + s); + stat.execute("CREATE TABLE TEST(" + s + " VARCHAR) AS VALUES '-'"); + String str; + try (ResultSet rs = stat.executeQuery("SELECT TRIM(" + s + " FROM '--a--') FROM TEST")) { + assertTrue(rs.next()); + str = rs.getString(1); + } + stat.execute("DROP TABLE TEST"); + stat.execute("CREATE TABLE TEST(" + s + " INT) AS (VALUES 10)"); + try (ResultSet rs = stat.executeQuery("SELECT " + s + " V FROM TEST")) { + assertTrue(rs.next()); + assertEquals(10, rs.getInt(1)); + } + try (ResultSet rs = stat.executeQuery("SELECT TEST." + s + " FROM TEST")) { + assertTrue(rs.next()); + assertEquals(10, rs.getInt(1)); + } + stat.execute("DROP TABLE TEST"); + stat.execute("CREATE TABLE TEST(" + s + " INT, _VALUE_ INT DEFAULT 1) AS VALUES (2, 2)"); + stat.execute("UPDATE TEST SET _VALUE_ = " + s); + try (ResultSet rs = stat.executeQuery("SELECT _VALUE_ FROM TEST")) { + assertTrue(rs.next()); + assertEquals(2, rs.getInt(1)); + } + stat.execute("DROP TABLE TEST"); + try (ResultSet rs = stat.executeQuery("SELECT 1 DAY " + s)) { + assertEquals(s, rs.getMetaData().getColumnLabel(1)); + assertTrue(rs.next()); + assertEquals(Duration.ofDays(1L), rs.getObject(1, Duration.class)); + } + try (ResultSet rs = stat.executeQuery("SELECT 1 = " + s + " FROM (VALUES 1) T(" + s + ')')) { + rs.next(); + assertTrue(rs.getBoolean(1)); + } + try (ResultSet rs = stat + .executeQuery("SELECT ROW_NUMBER() OVER(" + s + ") WINDOW " + s + " AS ()")) { + } + if (!"a".equals(str)) { + exception2 = new AssertionError(); + } + } catch (Throwable t) { + exception2 = t; + stat.execute("DROP TABLE IF EXISTS TEST"); + } + } + switch (type) { + case IDENTIFIER: + if (exception1 != null) { + throw new AssertionError(s + " must be a keyword.", exception1); + } + if (exception2 != null) { + throw new AssertionError(s + " must be a context-sensitive keyword.", exception2); + } + break; + case KEYWORD: + if (exception1 == null && exception2 == null) { + throw new AssertionError(s + " may be removed from a list of keywords."); + } + if (exception1 == null) { + throw new AssertionError(s + " may be a context-sensitive keyword."); + } + break; + case CONTEXT_SENSITIVE_KEYWORD: + if (exception1 != null) { + throw new AssertionError(s + " must be a keyword.", exception1); + } + if (exception2 == null) { + throw new AssertionError(s + " may be removed from a list of context-sensitive keywords."); + } + break; + default: + fail(); + } + } + } + } + + private void testInformationSchema() throws Exception { + try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:")) { + Statement stat = conn.createStatement(); + try (ResultSet rs = stat.executeQuery("SELECT TABLE_NAME, COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS")) { + while (rs.next()) { + String table = rs.getString(1); + if (isKeyword(table) && !table.equals("PARAMETERS")) { + fail("Table INFORMATION_SCHEMA.\"" + table + + "\" uses a keyword or SQL reserved word as its name."); + } + String column = rs.getString(2); + if (isKeyword(column)) { + fail("Column INFORMATION_SCHEMA." + table + ".\"" + column + + "\" uses a keyword or SQL reserved word as its name."); + } + } + } + } + } + + private static boolean isKeyword(String identifier) { + return ALL_RESEVED_WORDS.contains(identifier) || ParserUtil.isKeyword(identifier, false); + } + + @SuppressWarnings("incomplete-switch") + private void testMetaData() throws Exception { + TreeSet set = new TreeSet<>(); + for (Entry entry : TOKENS.entrySet()) { + switch (entry.getValue()) { + case KEYWORD: + case CONTEXT_SENSITIVE_KEYWORD: { + String s = entry.getKey(); + if (!SQL2003_RESERVED_WORDS.contains(s)) { + set.add(s); + } + } + } + } + try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:")) { + assertEquals(setToString(set), conn.getMetaData().getSQLKeywords()); + } + try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:;MODE=STRICT")) { + TreeSet set2 = new TreeSet<>(set); + set2.removeAll(STRICT_MODE_NON_KEYWORDS); + assertEquals(setToString(set2), conn.getMetaData().getSQLKeywords()); + } + set.add("INTERSECTS"); + set.add("SYSDATE"); + set.add("SYSTIME"); + set.add("SYSTIMESTAMP"); + set.add("TODAY"); + try (Connection conn = DriverManager.getConnection("jdbc:h2:mem:;OLD_INFORMATION_SCHEMA=TRUE")) { + assertEquals(setToString(set), conn.getMetaData().getSQLKeywords()); + } + } + + private static String setToString(TreeSet set) { + Iterator i = set.iterator(); + if (i.hasNext()) { + StringBuilder builder = new StringBuilder(i.next()); + while (i.hasNext()) { + builder.append(',').append(i.next()); + } + return builder.toString(); + } + return ""; + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestLocale.java b/h2/src/test/org/h2/test/unit/TestLocale.java new file mode 100644 index 0000000..0c91b9f --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestLocale.java @@ -0,0 +1,88 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Locale; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests that change the default locale. + */ +public class TestLocale extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + testSpecialLocale(); + testDatesInJapanLocale(); + } + + private void testSpecialLocale() throws SQLException { + deleteDb(getTestName()); + Connection conn = getConnection(getTestName()); + Statement stat = conn.createStatement(); + Locale old = Locale.getDefault(); + try { + // when using Turkish as the default locale, "i".toUpperCase() is + // not "I" + Locale.setDefault(new Locale("tr")); + stat.execute("create table test(I1 int, i2 int, b int, c int, d int) " + + "as select 1, 1, 1, 1, 1"); + ResultSet rs = stat.executeQuery("select * from test"); + rs.next(); + rs.getString("I1"); + rs.getString("i1"); + rs.getString("I2"); + rs.getString("i2"); + stat.execute("drop table test"); + } finally { + Locale.setDefault(old); + } + conn.close(); + } + + private void testDatesInJapanLocale() throws SQLException { + deleteDb(getTestName()); + Connection conn = getConnection(getTestName()); + Statement stat = conn.createStatement(); + Locale old = Locale.getDefault(); + try { + // when using Japanese as the default locale, the default calendar is + // the imperial japanese calendar + Locale.setDefault(new Locale("ja", "JP", "JP")); + stat.execute("CREATE TABLE test(d TIMESTAMP, dz TIMESTAMP WITH TIME ZONE) " + + "as select '2017-12-03T00:00:00Z', '2017-12-03T00:00:00Z'"); + ResultSet rs = stat.executeQuery("select YEAR(d) y, YEAR(dz) yz from test"); + rs.next(); + assertEquals(2017, rs.getInt("y")); + assertEquals(2017, rs.getInt("yz")); + stat.execute("drop table test"); + + rs = stat.executeQuery( + "CALL FORMATDATETIME(TIMESTAMP '2001-02-03 04:05:06', 'yyyy-MM-dd HH:mm:ss', 'en')"); + rs.next(); + assertEquals("2001-02-03 04:05:06", rs.getString(1)); + + } finally { + Locale.setDefault(old); + } + conn.close(); + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestMVTempResult.java b/h2/src/test/org/h2/test/unit/TestMVTempResult.java new file mode 100644 index 0000000..3dacb86 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestMVTempResult.java @@ -0,0 +1,81 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.lang.ProcessBuilder.Redirect; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.BitSet; + +import org.h2.test.TestBase; +import org.h2.tools.DeleteDbFiles; + +/** + * Tests that MVTempResult implementations do not produce OOME. + */ +public class TestMVTempResult extends TestBase { + + private static final int MEMORY = 64; + + private static final int ROWS = 1_000_000; + + /** + * May be used to run only this test and may be launched by this test in a + * subprocess. + * + * @param a + * if empty run this test, if not empty run the subprocess + */ + public static void main(String... a) throws Exception { + TestMVTempResult test = (TestMVTempResult) TestBase.createCaller().init(); + if (a.length == 0) { + test.test(); + } else { + test.runTest(); + } + } + + @Override + public void test() throws Exception { + ProcessBuilder pb = new ProcessBuilder().redirectError(Redirect.INHERIT); + pb.command(getJVM(), "-Xmx" + MEMORY + "M", "-cp", getClassPath(), "-ea", getClass().getName(), "dummy"); + assertEquals(0, pb.start().waitFor()); + } + + private void runTest() throws SQLException { + String dir = getBaseDir(); + String name = "testResultExternal"; + DeleteDbFiles.execute(dir, name, true); + try (Connection c = DriverManager.getConnection("jdbc:h2:" + dir + '/' + name)) { + Statement s = c.createStatement(); + s.execute("CREATE TABLE TEST(I BIGINT, E ENUM('a', 'b'))" // + + " AS SELECT X, 'a' FROM SYSTEM_RANGE(1, " + ROWS + ')'); + try (ResultSet rs = s.executeQuery("SELECT I, E FROM TEST ORDER BY I DESC")) { + for (int i = ROWS; i > 0; i--) { + assertTrue(rs.next()); + assertEquals(i, rs.getLong(1)); + assertEquals("a", rs.getString(2)); + } + assertFalse(rs.next()); + } + BitSet set = new BitSet(ROWS); + try (ResultSet rs = s.executeQuery("SELECT I, E FROM TEST")) { + for (int i = 1; i <= ROWS; i++) { + assertTrue(rs.next()); + set.set((int) rs.getLong(1)); + assertEquals("a", rs.getString(2)); + } + assertFalse(rs.next()); + assertEquals(ROWS, set.cardinality()); + } + } + DeleteDbFiles.execute(dir, name, true); + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestMathUtils.java b/h2/src/test/org/h2/test/unit/TestMathUtils.java new file mode 100644 index 0000000..80b2e74 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestMathUtils.java @@ -0,0 +1,69 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import org.h2.test.TestBase; +import org.h2.util.MathUtils; + +/** + * Tests math utility methods. + */ +public class TestMathUtils extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() { + testRandom(); + testNextPowerOf2Int(); + } + + private void testRandom() { + int bits = 0; + for (int i = 0; i < 1000; i++) { + bits |= 1 << MathUtils.randomInt(8); + } + assertEquals(255, bits); + bits = 0; + for (int i = 0; i < 1000; i++) { + bits |= 1 << MathUtils.secureRandomInt(8); + } + assertEquals(255, bits); + bits = 0; + for (int i = 0; i < 1000; i++) { + bits |= 1 << (MathUtils.secureRandomLong() & 7); + } + assertEquals(255, bits); + // just verify the method doesn't throw an exception + byte[] data = MathUtils.generateAlternativeSeed(); + assertTrue(data.length > 10); + } + + private void testNextPowerOf2Int() { + // the largest power of two that fits into an integer + final int largestPower2 = 0x40000000; + int[] testValues = { 0, 1, 2, 3, 4, 12, 17, 500, 1023, + largestPower2 - 500, largestPower2 }; + int[] resultValues = { 1, 1, 2, 4, 4, 16, 32, 512, 1024, + largestPower2, largestPower2 }; + + for (int i = 0; i < testValues.length; i++) { + assertEquals(resultValues[i], MathUtils.nextPowerOf2(testValues[i])); + } + testValues = new int[] { Integer.MIN_VALUE, -1, largestPower2 + 1, Integer.MAX_VALUE }; + for (int v : testValues) { + assertThrows(IllegalArgumentException.class, () -> MathUtils.nextPowerOf2(v)); + } + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestMemoryEstimator.java b/h2/src/test/org/h2/test/unit/TestMemoryEstimator.java new file mode 100644 index 0000000..31e0e4d --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestMemoryEstimator.java @@ -0,0 +1,120 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.nio.ByteBuffer; +import java.util.Random; +import java.util.concurrent.atomic.AtomicLong; +import org.h2.mvstore.WriteBuffer; +import org.h2.mvstore.type.BasicDataType; +import org.h2.test.TestBase; +import org.h2.util.MemoryEstimator; + +/** + * Class TestMemoryEstimator. + *
      + *
    • 12/7/19 10:38 PM initial creation + *
    + * + * @author Andrei Tokar + */ +public class TestMemoryEstimator extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() { + testEstimator(); + testPageEstimator(); + } + + private void testEstimator() { + Random random = new Random(); + AtomicLong stat = new AtomicLong(); + TestDataType dataType = new TestDataType(); + int sum = 0; + int sum2 = 0; + int err2 = 0; + int size = 10000; + for (int i = 0; i < size; i++) { + int x = (int)Math.abs(100 + random.nextGaussian() * 30); + int y = MemoryEstimator.estimateMemory(stat, dataType, x); + sum += x; + sum2 += x * x; + err2 += (x - y) * (x - y); + } + int avg = sum / size; + double err = Math.sqrt(1.0 * err2 / sum2); + int pct = MemoryEstimator.samplingPct(stat); + String msg = "Avg=" + avg + ", err=" + err + ", pct=" + pct + " " + (dataType.getCount() * 100 / size); + assertTrue(msg, err < 0.3); + assertTrue(msg, pct <= 7); + } + + private void testPageEstimator() { + Random random = new Random(); + AtomicLong stat = new AtomicLong(); + TestDataType dataType = new TestDataType(); + long sum = 0; + long sum2 = 0; + long err2 = 0; + int size = 10000; + int pageSz; + for (int i = 0; i < size; i+=pageSz) { + pageSz = random.nextInt(48) + 1; + Integer[] storage = dataType.createStorage(pageSz); + int x = 0; + for (int k = 0; k < pageSz; k++) { + storage[k] = (int)Math.abs(100 + random.nextGaussian() * 30); + x += storage[k]; + } + int y = MemoryEstimator.estimateMemory(stat, dataType, storage, pageSz); + sum += x; + sum2 += x * x; + err2 += (x - y) * (x - y); + } + long avg = sum / size; + double err = Math.sqrt(1.0 * err2 / sum2); + int pct = MemoryEstimator.samplingPct(stat); + String msg = "Avg=" + avg + ", err=" + err + ", pct=" + pct + " " + (dataType.getCount() * 100 / size); + assertTrue(msg, err < 0.12); + assertTrue(msg, pct <= 4); + } + + private static class TestDataType extends BasicDataType { + private int count; + + TestDataType() { + } + + public int getCount() { + return count; + } + + @Override + public int getMemory(Integer obj) { + ++count; + return obj; + } + + @Override + public void write(WriteBuffer buff, Integer obj) {} + + @Override + public Integer read(ByteBuffer buff) { return null; } + + @Override + public Integer[] createStorage(int size) { return new Integer[size]; } + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestMemoryUnmapper.java b/h2/src/test/org/h2/test/unit/TestMemoryUnmapper.java new file mode 100644 index 0000000..c2d320c --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestMemoryUnmapper.java @@ -0,0 +1,51 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.lang.ProcessBuilder.Redirect; +import java.nio.ByteBuffer; + +import org.h2.test.TestBase; +import org.h2.util.MemoryUnmapper; + +/** + * Tests memory unmapper. + */ +public class TestMemoryUnmapper extends TestBase { + private static final int OK = 0, /* EXCEPTION = 1, */ UNAVAILABLE = 2; + + /** + * May be used to run only this test and may be launched by this test in a + * subprocess. + * + * @param a + * if empty run this test only + */ + public static void main(String... a) throws Exception { + if (a.length == 0) { + TestBase.createCaller().init().testFromMain(); + } else { + ByteBuffer buffer = ByteBuffer.allocateDirect(10); + System.exit(MemoryUnmapper.unmap(buffer) ? OK : UNAVAILABLE); + } + } + + @Override + public void test() throws Exception { + ProcessBuilder pb = new ProcessBuilder().redirectError(Redirect.INHERIT); + // Test that unsafe unmapping is disabled by default + pb.command(getJVM(), "-cp", getClassPath(), "-ea", getClass().getName(), "dummy"); + assertEquals(UNAVAILABLE, pb.start().waitFor()); + // Test that it can be enabled + pb.command(getJVM(), "-cp", getClassPath(), "-ea", "-Dh2.nioCleanerHack=true", getClass().getName(), "dummy"); + assertEquals(OK, pb.start().waitFor()); + // Test that it will not be enabled with a security manager + pb.command(getJVM(), "-cp", getClassPath(), "-ea", "-Djava.security.manager", "-Dh2.nioCleanerHack=true", + getClass().getName(), "dummy"); + assertEquals(UNAVAILABLE, pb.start().waitFor()); + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestMode.java b/h2/src/test/org/h2/test/unit/TestMode.java new file mode 100644 index 0000000..e8dd8a9 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestMode.java @@ -0,0 +1,90 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import org.h2.engine.Mode; +import org.h2.test.TestBase; + +/** + * Unit test for the Mode class. + */ +public class TestMode extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String[] a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testDb2ClientInfo(); + testDerbyClientInfo(); + testHsqlDbClientInfo(); + testMsSqlServerClientInfo(); + testMySqlClientInfo(); + testOracleClientInfo(); + testPostgresqlClientInfo(); + } + + private void testDb2ClientInfo() { + Mode db2Mode = Mode.getInstance("DB2"); + + assertTrue(db2Mode.supportedClientInfoPropertiesRegEx.matcher( + "ApplicationName").matches()); + assertTrue(db2Mode.supportedClientInfoPropertiesRegEx.matcher( + "ClientAccountingInformation").matches()); + assertTrue(db2Mode.supportedClientInfoPropertiesRegEx.matcher( + "ClientUser").matches()); + assertTrue(db2Mode.supportedClientInfoPropertiesRegEx.matcher( + "ClientCorrelationToken").matches()); + + assertFalse(db2Mode.supportedClientInfoPropertiesRegEx.matcher( + "AnyOtherValue").matches()); + } + + private void testDerbyClientInfo() { + Mode derbyMode = Mode.getInstance("Derby"); + assertNull(derbyMode.supportedClientInfoPropertiesRegEx); + } + + private void testHsqlDbClientInfo() { + Mode hsqlMode = Mode.getInstance("HSQLDB"); + assertNull(hsqlMode.supportedClientInfoPropertiesRegEx); + } + + private void testMsSqlServerClientInfo() { + Mode msSqlMode = Mode.getInstance("MSSQLServer"); + assertNull(msSqlMode.supportedClientInfoPropertiesRegEx); + } + + private void testMySqlClientInfo() { + Mode mySqlMode = Mode.getInstance("MySQL"); + assertTrue(mySqlMode.supportedClientInfoPropertiesRegEx.matcher( + "AnyString").matches()); + } + + private void testOracleClientInfo() { + Mode oracleMode = Mode.getInstance("Oracle"); + assertTrue(oracleMode.supportedClientInfoPropertiesRegEx.matcher( + "anythingContaining.aDot").matches()); + assertFalse(oracleMode.supportedClientInfoPropertiesRegEx.matcher( + "anythingContainingNoDot").matches()); + } + + + private void testPostgresqlClientInfo() { + Mode postgresqlMode = Mode.getInstance("PostgreSQL"); + assertTrue(postgresqlMode.supportedClientInfoPropertiesRegEx.matcher( + "ApplicationName").matches()); + assertFalse(postgresqlMode.supportedClientInfoPropertiesRegEx.matcher( + "AnyOtherValue").matches()); + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestMultiThreadedKernel.java b/h2/src/test/org/h2/test/unit/TestMultiThreadedKernel.java new file mode 100644 index 0000000..658bf5d --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestMultiThreadedKernel.java @@ -0,0 +1,90 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests the multi-threaded kernel feature. + */ +public class TestMultiThreadedKernel extends TestDb implements Runnable { + + private String url, user, password; + private int id; + private TestMultiThreadedKernel master; + private volatile boolean stop; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + if (config.networked) { + return false; + } + return true; + } + + @Override + public void test() throws Exception { + deleteDb("multiThreadedKernel"); + int count = getSize(2, 5); + Thread[] list = new Thread[count]; + for (int i = 0; i < count; i++) { + TestMultiThreadedKernel r = new TestMultiThreadedKernel(); + r.url = getURL("multiThreadedKernel", true); + r.user = getUser(); + r.password = getPassword(); + r.master = this; + r.id = i; + Thread thread = new Thread(r); + thread.setName("Thread " + i); + thread.start(); + list[i] = thread; + } + Thread.sleep(getSize(2000, 5000)); + stop = true; + for (int i = 0; i < count; i++) { + list[i].join(); + } + deleteDb("multiThreadedKernel"); + } + + @Override + public void run() { + try { + org.h2.Driver.load(); + Connection conn = DriverManager.getConnection(url + + ";LOCK_MODE=3;WRITE_DELAY=0", + user, password); + conn.createStatement().execute( + "CREATE TABLE TEST" + id + + "(COL1 BIGINT AUTO_INCREMENT PRIMARY KEY, COL2 BIGINT)"); + PreparedStatement prep = conn.prepareStatement( + "insert into TEST" + id + "(col2) values (?)"); + for (int i = 0; !master.stop; i++) { + prep.setLong(1, i); + prep.execute(); + } + conn.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestNetUtils.java b/h2/src/test/org/h2/test/unit/TestNetUtils.java new file mode 100644 index 0000000..30bf100 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestNetUtils.java @@ -0,0 +1,334 @@ +/* + * 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: Sergi Vladykin + */ +package org.h2.test.unit; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import org.h2.build.BuildBase; +import org.h2.engine.SysProperties; +import org.h2.test.TestBase; +import org.h2.util.NetUtils; +import org.h2.util.Task; +import org.h2.util.Utils10; + +/** + * Test the network utilities from {@link NetUtils}. + * + * @author Sergi Vladykin + * @author Tomas Pospichal + */ +public class TestNetUtils extends TestBase { + + private static final int WORKER_COUNT = 10; + private static final int PORT = 9111; + private static final int WAIT_MILLIS = 100; + private static final int WAIT_LONGER_MILLIS = 2 * WAIT_MILLIS; + private static final String TASK_PREFIX = "ServerSocketThread-"; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testAnonymousTlsSession(); + testTlsSessionWithServerSideAnonymousDisabled(); + testFrequentConnections(true, 100); + testFrequentConnections(false, 1000); + testIpToShortForm(); + testTcpQuickack(); + } + + /** + * With default settings, H2 client SSL socket should be able to connect + * to an H2 server SSL socket using an anonymous cipher suite + * (no SSL certificate is needed). + */ + private void testAnonymousTlsSession() throws Exception { + if (config.ci || BuildBase.getJavaVersion() >= 11) { + // Issue #1303 + return; + } + assertTrue("Failed assumption: the default value of ENABLE_ANONYMOUS_TLS" + + " property should be true", SysProperties.ENABLE_ANONYMOUS_TLS); + boolean ssl = true; + Task task = null; + ServerSocket serverSocket = null; + Socket socket = null; + + try { + serverSocket = NetUtils.createServerSocket(PORT, ssl); + serverSocket.setSoTimeout(WAIT_LONGER_MILLIS); + task = createServerSocketTask(serverSocket); + task.execute(TASK_PREFIX + "AnonEnabled"); + Thread.sleep(WAIT_MILLIS); + socket = NetUtils.createLoopbackSocket(PORT, ssl); + assertTrue("loopback anon socket should be connected", socket.isConnected()); + SSLSession session = ((SSLSocket) socket).getSession(); + assertTrue("TLS session should be valid when anonymous TLS is enabled", + session.isValid()); + // in case of handshake failure: + // the cipher suite is the pre-handshake SSL_NULL_WITH_NULL_NULL + assertContains(session.getCipherSuite(), "_anon_"); + } finally { + closeSilently(socket); + closeSilently(serverSocket); + if (task != null) { + // SSL server socket should succeed using an anonymous cipher + // suite, and not throw javax.net.ssl.SSLHandshakeException + assertNull(task.getException()); + task.join(); + } + } + } + + /** + * TLS connections (without trusted certificates) should fail if the server + * does not allow anonymous TLS. + * The global property ENABLE_ANONYMOUS_TLS cannot be modified for the test; + * instead, the server socket is altered. + */ + private void testTlsSessionWithServerSideAnonymousDisabled() throws Exception { + if (config.ci) { + // Issue #1303 + return; + } + boolean ssl = true; + Task task = null; + ServerSocket serverSocket = null; + Socket socket = null; + try { + serverSocket = NetUtils.createServerSocket(PORT, ssl); + serverSocket.setSoTimeout(WAIT_LONGER_MILLIS); + // emulate the situation ENABLE_ANONYMOUS_TLS=false on server side + String[] defaultCipherSuites = SSLContext.getDefault().getServerSocketFactory() + .getDefaultCipherSuites(); + ((SSLServerSocket) serverSocket).setEnabledCipherSuites(defaultCipherSuites); + task = createServerSocketTask(serverSocket); + task.execute(TASK_PREFIX + "AnonDisabled"); + Thread.sleep(WAIT_MILLIS); + socket = NetUtils.createLoopbackSocket(PORT, ssl); + assertTrue("loopback socket should be connected", socket.isConnected()); + // Java 6 API does not have getHandshakeSession() which could + // reveal the actual cipher selected in the attempted handshake + SSLSession session = ((SSLSocket) socket).getSession(); + assertFalse("TLS session should be invalid when the server" + + "disables anonymous TLS", session.isValid()); + // the SSL handshake should fail, because non-anon ciphers require + // a trusted certificate + assertEquals("SSL_NULL_WITH_NULL_NULL", session.getCipherSuite()); + } finally { + closeSilently(socket); + closeSilently(serverSocket); + if (task != null) { + assertNotNull(task.getException()); + assertEquals(javax.net.ssl.SSLHandshakeException.class.getName(), + task.getException().getClass().getName()); + assertContains(task.getException().getMessage(), "certificate_unknown"); + task.join(); + } + } + } + + private Task createServerSocketTask(final ServerSocket serverSocket) { + Task task = new Task() { + + @Override + public void call() throws Exception { + Socket ss = null; + try { + ss = serverSocket.accept(); + ss.getOutputStream().write(123); + } finally { + closeSilently(ss); + } + } + }; + return task; + } + + /** + * Close a socket, ignoring errors + * + * @param socket the socket + */ + void closeSilently(Socket socket) { + try { + if (socket != null) { + socket.close(); + } + } catch (Exception e) { + // ignore + } + } + + /** + * Close a server socket, ignoring errors + * + * @param socket the server socket + */ + void closeSilently(ServerSocket socket) { + try { + socket.close(); + } catch (Exception e) { + // ignore + } + } + + private static void testFrequentConnections(boolean ssl, int count) throws Exception { + final ServerSocket serverSocket = NetUtils.createServerSocket(PORT, ssl); + final AtomicInteger counter = new AtomicInteger(count); + Task serverThread = new Task() { + @Override + public void call() { + while (!stop) { + try { + Socket socket = serverSocket.accept(); + // System.out.println("opened " + counter); + socket.close(); + } catch (Exception e) { + // ignore + } + } + // System.out.println("stopped "); + + } + }; + serverThread.execute(); + try { + Set workers = new HashSet<>(); + for (int i = 0; i < WORKER_COUNT; i++) { + workers.add(new ConnectWorker(ssl, counter)); + } + // ensure the server is started + Thread.sleep(100); + for (ConnectWorker worker : workers) { + worker.start(); + } + for (ConnectWorker worker : workers) { + worker.join(); + Exception e = worker.getException(); + if (e != null) { + e.printStackTrace(); + } + } + } finally { + try { + serverSocket.close(); + } catch (Exception e) { + // ignore + } + serverThread.get(); + } + } + + /** + * A worker thread to test connecting. + */ + private static class ConnectWorker extends Thread { + + private final boolean ssl; + private final AtomicInteger counter; + private Exception exception; + + ConnectWorker(boolean ssl, AtomicInteger counter) { + this.ssl = ssl; + this.counter = counter; + } + + @Override + public void run() { + try { + while (!isInterrupted() && counter.decrementAndGet() > 0) { + Socket socket = NetUtils.createLoopbackSocket(PORT, ssl); + try { + socket.close(); + } catch (IOException e) { + // ignore + } + } + } catch (Exception e) { + exception = new Exception("count: " + counter, e); + } + } + + public Exception getException() { + return exception; + } + + } + + private void testIpToShortForm() throws Exception { + testIpToShortForm("1.2.3.4", "1.2.3.4"); + testIpToShortForm("1:2:3:4:a:b:c:d", "1:2:3:4:a:b:c:d"); + testIpToShortForm("::1", "::1"); + testIpToShortForm("1::", "1::"); + testIpToShortForm("c1c1:0:0:2::fffe", "c1c1:0:0:2:0:0:0:fffe"); + } + + private void testIpToShortForm(String expected, String source) throws Exception { + byte[] addr = InetAddress.getByName(source).getAddress(); + testIpToShortForm(expected, addr, false); + if (expected.indexOf(':') >= 0) { + expected = '[' + expected + ']'; + } + testIpToShortForm(expected, addr, true); + } + + private void testIpToShortForm(String expected, byte[] addr, boolean addBrackets) { + assertEquals(expected, NetUtils.ipToShortForm(null, addr, addBrackets).toString()); + assertEquals(expected, NetUtils.ipToShortForm(new StringBuilder(), addr, addBrackets).toString()); + assertEquals(expected, + NetUtils.ipToShortForm(new StringBuilder("*"), addr, addBrackets).deleteCharAt(0).toString()); + } + + private void testTcpQuickack() { + final boolean ssl = !config.ci && BuildBase.getJavaVersion() < 11; + try (ServerSocket serverSocket = NetUtils.createServerSocket(PORT, ssl)) { + Thread thread = new Thread() { + @Override + public void run() { + try (Socket s = NetUtils.createLoopbackSocket(PORT, ssl)) { + s.getInputStream().read(); + } catch (IOException e) { + } + } + }; + thread.start(); + try (Socket socket = serverSocket.accept()) { + boolean supported = Utils10.setTcpQuickack(socket, true); + if (supported) { + assertTrue(Utils10.getTcpQuickack(socket)); + Utils10.setTcpQuickack(socket, false); + assertFalse(Utils10.getTcpQuickack(socket)); + } + socket.getOutputStream().write(1); + } finally { + try { + thread.join(); + } catch (InterruptedException e) { + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestObjectDeserialization.java b/h2/src/test/org/h2/test/unit/TestObjectDeserialization.java new file mode 100644 index 0000000..47274f0 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestObjectDeserialization.java @@ -0,0 +1,72 @@ +/* + * 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: Noah Fontes + */ +package org.h2.test.unit; + +import org.h2.api.ErrorCode; +import org.h2.test.TestBase; +import org.h2.util.JdbcUtils; +import org.h2.util.StringUtils; + +/** + * Tests the ability to deserialize objects that are not part of the system + * class-loading scope. + */ +public class TestObjectDeserialization extends TestBase { + + private static final String CLAZZ = "org.h2.test.unit.SampleObject"; + private static final String OBJECT = + "aced00057372001d6f72672e68322e746573742e756" + + "e69742e53616d706c654f626a65637400000000000000010200007870"; + + /** + * The thread context class loader was used. + */ + protected boolean usesThreadContextClassLoader; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + System.setProperty("h2.useThreadContextClassLoader", "true"); + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() { + testThreadContextClassLoader(); + } + + private void testThreadContextClassLoader() { + usesThreadContextClassLoader = false; + Thread.currentThread().setContextClassLoader(new TestClassLoader()); + assertThrows(ErrorCode.DESERIALIZATION_FAILED_1, + () -> JdbcUtils.deserialize(StringUtils.convertHexToBytes(OBJECT), null)); + assertTrue(usesThreadContextClassLoader); + } + + /** + * A special class loader. + */ + private class TestClassLoader extends ClassLoader { + + public TestClassLoader() { + super(); + } + + @Override + protected synchronized Class loadClass(String name, boolean resolve) + throws ClassNotFoundException { + if (name.equals(CLAZZ)) { + usesThreadContextClassLoader = true; + } + return super.loadClass(name, resolve); + } + + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestOverflow.java b/h2/src/test/org/h2/test/unit/TestOverflow.java new file mode 100644 index 0000000..c23d34c --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestOverflow.java @@ -0,0 +1,130 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Random; + +import org.h2.test.TestBase; +import org.h2.value.Value; +import org.h2.value.ValueVarchar; + +/** + * Tests numeric overflow on various data types. + * Other than in Java, overflow is detected and an exception is thrown. + */ +public class TestOverflow extends TestBase { + + private ArrayList values; + private int dataType; + private BigInteger min, max; + private boolean successExpected; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() { + test(Value.TINYINT, Byte.MIN_VALUE, Byte.MAX_VALUE); + test(Value.INTEGER, Integer.MIN_VALUE, Integer.MAX_VALUE); + test(Value.BIGINT, Long.MIN_VALUE, Long.MAX_VALUE); + test(Value.SMALLINT, Short.MIN_VALUE, Short.MAX_VALUE); + } + + private void test(int type, long minValue, long maxValue) { + values = new ArrayList<>(); + this.dataType = type; + this.min = new BigInteger("" + minValue); + this.max = new BigInteger("" + maxValue); + add(0); + add(minValue); + add(maxValue); + add(maxValue - 1); + add(minValue + 1); + add(1); + add(-1); + Random random = new Random(1); + for (int i = 0; i < 40; i++) { + if (maxValue > Integer.MAX_VALUE) { + add(random.nextLong()); + } else { + add((random.nextBoolean() ? 1 : -1) * random.nextInt((int) maxValue)); + } + } + for (Value va : values) { + for (Value vb : values) { + testValues(va, vb); + } + } + } + + private void checkIfExpected(String a, String b) { + if (successExpected) { + assertEquals(a, b); + } + } + + private void onSuccess() { + if (!successExpected) { + fail(); + } + } + + private void onError() { + if (successExpected) { + fail(); + } + } + + private void testValues(Value va, Value vb) { + BigInteger a = new BigInteger(va.getString()); + BigInteger b = new BigInteger(vb.getString()); + successExpected = inRange(a.negate()); + try { + checkIfExpected(va.negate().getString(), a.negate().toString()); + onSuccess(); + } catch (Exception e) { + onError(); + } + successExpected = inRange(a.add(b)); + try { + checkIfExpected(va.add(vb).getString(), a.add(b).toString()); + onSuccess(); + } catch (Exception e) { + onError(); + } + successExpected = inRange(a.subtract(b)); + try { + checkIfExpected(va.subtract(vb).getString(), a.subtract(b).toString()); + onSuccess(); + } catch (Exception e) { + onError(); + } + successExpected = inRange(a.multiply(b)); + try { + checkIfExpected(va.multiply(vb).getString(), a.multiply(b).toString()); + onSuccess(); + } catch (Exception e) { + onError(); + } + } + + private boolean inRange(BigInteger v) { + return v.compareTo(min) >= 0 && v.compareTo(max) <= 0; + } + + private void add(long l) { + values.add(ValueVarchar.get("" + l).convertTo(dataType)); + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestPageStoreCoverage.java b/h2/src/test/org/h2/test/unit/TestPageStoreCoverage.java new file mode 100644 index 0000000..6cbf7a5 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestPageStoreCoverage.java @@ -0,0 +1,243 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.Restore; + +/** + * Test the page store. + */ +public class TestPageStoreCoverage extends TestDb { + + private static final String URL = "pageStoreCoverage;" + + "PAGE_SIZE=64;CACHE_SIZE=16;MAX_LOG_SIZE=1"; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + if (config.memory) { + return false; + } + return true; + } + + @Override + public void test() throws Exception { + // TODO mvcc, 2-phase commit + deleteDb("pageStoreCoverage"); + testMoveRoot(); + testBasic(); + testReadOnly(); + testBackupRestore(); + testTrim(); + testLongTransaction(); + testRecoverTemp(); + deleteDb("pageStoreCoverage"); + } + + private void testMoveRoot() throws SQLException { + Connection conn; + + conn = getConnection(URL); + Statement stat = conn.createStatement(); + stat.execute("create memory table test(id int primary key) " + + "as select x from system_range(1, 20)"); + for (int i = 0; i < 10; i++) { + stat.execute("create memory table test" + i + + "(id int primary key) as select x from system_range(1, 2)"); + } + stat.execute("drop table test"); + conn.close(); + + conn = getConnection(URL); + stat = conn.createStatement(); + stat.execute("drop all objects delete files"); + conn.close(); + + conn = getConnection(URL); + stat = conn.createStatement(); + stat.execute("create table test(id int primary key) " + + "as select x from system_range(1, 100)"); + for (int i = 0; i < 10; i++) { + stat.execute("create table test" + i + "(id int primary key) " + + "as select x from system_range(1, 2)"); + } + stat.execute("drop table test"); + conn.close(); + + conn = getConnection(URL); + stat = conn.createStatement(); + for (int i = 0; i < 10; i++) { + ResultSet rs = stat.executeQuery("select * from test" + i); + while (rs.next()) { + // ignore + } + } + stat.execute("drop all objects delete files"); + conn.close(); + } + + private void testRecoverTemp() throws SQLException { + try (Connection conn = getConnection(URL)) { + Statement stat = conn.createStatement(); + stat.execute("create cached temporary table test(id identity, name varchar)"); + stat.execute("create index idx_test_name on test(name)"); + stat.execute("create index idx_test_name2 on test(name, id)"); + stat.execute("create table test2(id identity, name varchar)"); + stat.execute("create index idx_test2_name on test2(name desc)"); + stat.execute("create index idx_test2_name2 on test2(name, id)"); + stat.execute("insert into test2(name) " + + "select space(10) from system_range(1, 10)"); + stat.execute("create table test3(id identity, name varchar)"); + stat.execute("checkpoint"); + conn.setAutoCommit(false); + stat.execute("create table test4(id identity, name varchar)"); + stat.execute("create index idx_test4_name2 on test(name, id)"); + stat.execute("insert into test(name) " + + "select space(10) from system_range(1, 10)"); + stat.execute("insert into test3(name) " + + "select space(10) from system_range(1, 10)"); + stat.execute("insert into test4(name) " + + "select space(10) from system_range(1, 10)"); + stat.execute("truncate table test2"); + stat.execute("drop index idx_test_name"); + stat.execute("drop index idx_test2_name"); + stat.execute("drop table test2"); + stat.execute("insert into test(name) " + + "select space(10) from system_range(1, 10)"); + stat.execute("shutdown immediately"); + } + try (Connection conn = getConnection(URL)) { + Statement stat = conn.createStatement(); + stat.execute("drop all objects"); + // re-allocate index root pages + for (int i = 0; i < 10; i++) { + stat.execute("create table test" + i + "(id identity, name varchar)"); + } + stat.execute("checkpoint"); + for (int i = 0; i < 10; i++) { + stat.execute("drop table test" + i); + } + for (int i = 0; i < 10; i++) { + stat.execute("create table test" + i + "(id identity, name varchar)"); + } + stat.execute("shutdown immediately"); + } + try (Connection conn = getConnection(URL)) { + conn.createStatement().execute("drop all objects"); + } + } + + private void testLongTransaction() throws SQLException { + Connection conn; + conn = getConnection(URL); + Statement stat = conn.createStatement(); + stat.execute("create table test(id identity, name varchar)"); + conn.setAutoCommit(false); + stat.execute("insert into test(name) " + + "select space(10) from system_range(1, 10)"); + Connection conn2; + conn2 = getConnection(URL); + Statement stat2 = conn2.createStatement(); + stat2.execute("checkpoint"); + // large transaction + stat2.execute("create table test2(id identity, name varchar)"); + stat2.execute("create index idx_test2_name on test2(name)"); + stat2.execute("insert into test2(name) " + + "select x || space(10000) from system_range(1, 100)"); + stat2.execute("drop table test2"); + conn2.close(); + stat.execute("drop table test"); + conn.close(); + } + + private void testTrim() throws SQLException { + Connection conn; + conn = getConnection(URL); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, name varchar)"); + stat.execute("create index idx_name on test(name, id)"); + stat.execute("insert into test " + + "select x, x || space(10) from system_range(1, 20)"); + stat.execute("create table test2(id int primary key, name varchar)"); + stat.execute("create index idx_test2_name on test2(name, id)"); + stat.execute("insert into test2 " + + "select x, x || space(10) from system_range(1, 20)"); + stat.execute("create table test3(id int primary key, name varchar)"); + stat.execute("create index idx_test3_name on test3(name, id)"); + stat.execute("insert into test3 " + + "select x, x || space(3) from system_range(1, 3)"); + stat.execute("delete from test"); + stat.execute("checkpoint"); + stat.execute("checkpoint sync"); + stat.execute("shutdown compact"); + conn.close(); + conn = getConnection(URL); + conn.createStatement().execute("drop all objects"); + conn.close(); + } + + private void testBasic() throws Exception { + Connection conn; + conn = getConnection(URL); + conn.close(); + conn = getConnection(URL); + conn.close(); + + } + + private void testReadOnly() throws Exception { + Connection conn; + conn = getConnection(URL); + conn.createStatement().execute("shutdown compact"); + conn.close(); + conn = getConnection(URL + ";access_mode_data=r"); + conn.close(); + } + + private void testBackupRestore() throws Exception { + Connection conn; + conn = getConnection(URL); + Statement stat = conn.createStatement(); + stat.execute( + "create table test(id int primary key, name varchar)"); + stat.execute( + "create index idx_name on test(name, id)"); + stat.execute( + "insert into test select x, x || space(200 * x) from system_range(1, 10)"); + conn.setAutoCommit(false); + stat.execute("delete from test where id > 5"); + stat.execute("backup to '" + getBaseDir() + "/backup.zip'"); + conn.rollback(); + Restore.execute(getBaseDir() + "/backup.zip", getBaseDir(), + "pageStore2"); + Connection conn2; + conn2 = getConnection("pageStore2"); + Statement stat2 = conn2.createStatement(); + assertEqualDatabases(stat, stat2); + conn.createStatement().execute("drop table test"); + conn2.close(); + conn.close(); + FileUtils.delete(getBaseDir() + "/backup.zip"); + deleteDb("pageStore2"); + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestPattern.java b/h2/src/test/org/h2/test/unit/TestPattern.java new file mode 100644 index 0000000..4a56deb --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestPattern.java @@ -0,0 +1,124 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.text.Collator; +import org.h2.expression.condition.CompareLike; +import org.h2.test.TestBase; +import org.h2.value.CompareMode; + +/** + * Tests LIKE pattern matching. + */ +public class TestPattern extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() { + testCompareModeReuse(); + testPattern(); + } + + private void testCompareModeReuse() { + CompareMode mode1, mode2; + mode1 = CompareMode.getInstance(null, 0); + mode2 = CompareMode.getInstance(null, 0); + assertTrue(mode1 == mode2); + + mode1 = CompareMode.getInstance("DE", Collator.SECONDARY); + assertFalse(mode1 == mode2); + mode2 = CompareMode.getInstance("DE", Collator.SECONDARY); + assertTrue(mode1 == mode2); + } + + private void testPattern() { + CompareMode mode = CompareMode.getInstance(null, 0); + CompareLike comp = new CompareLike(mode, "\\", null, false, false, null, null, CompareLike.LikeType.LIKE); + test(comp, "B", "%_"); + test(comp, "A", "A%"); + test(comp, "A", "A%%"); + test(comp, "A_A", "%\\_%"); + + for (int i = 0; i < 10000; i++) { + String pattern = getRandomPattern(); + String value = getRandomValue(); + test(comp, value, pattern); + } + } + + private void test(CompareLike comp, String value, String pattern) { + String regexp = initPatternRegexp(pattern, '\\'); + boolean resultRegexp = value.matches(regexp); + boolean result = comp.test(pattern, value, '\\'); + if (result != resultRegexp) { + fail("Error: >" + value + "< LIKE >" + pattern + "< result=" + + result + " resultReg=" + resultRegexp); + } + } + + private static String getRandomValue() { + StringBuilder buff = new StringBuilder(); + int len = (int) (Math.random() * 10); + String s = "AB_%\\"; + for (int i = 0; i < len; i++) { + buff.append(s.charAt((int) (Math.random() * s.length()))); + } + return buff.toString(); + } + + private static String getRandomPattern() { + StringBuilder buff = new StringBuilder(); + int len = (int) (Math.random() * 4); + String s = "A%_\\"; + for (int i = 0; i < len; i++) { + char c = s.charAt((int) (Math.random() * s.length())); + if ((c == '_' || c == '%') && Math.random() > 0.5) { + buff.append('\\'); + } else if (c == '\\') { + buff.append(c); + } + buff.append(c); + } + return buff.toString(); + } + + private String initPatternRegexp(String pattern, char escape) { + int len = pattern.length(); + StringBuilder buff = new StringBuilder(); + for (int i = 0; i < len; i++) { + char c = pattern.charAt(i); + if (escape == c) { + if (i >= len - 1) { + fail("escape can't be last char"); + } + c = pattern.charAt(++i); + buff.append('\\'); + buff.append(c); + } else if (c == '%') { + buff.append(".*"); + } else if (c == '_') { + buff.append('.'); + } else if (c == '\\') { + buff.append("\\\\"); + } else { + buff.append(c); + } + // TODO regexp: there are other chars that need escaping + } + String regexp = buff.toString(); + // System.out.println("regexp = " + regexp); + return regexp; + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestPerfectHash.java b/h2/src/test/org/h2/test/unit/TestPerfectHash.java new file mode 100644 index 0000000..bc8cac7 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestPerfectHash.java @@ -0,0 +1,317 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.BitSet; +import java.util.HashSet; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import org.h2.dev.hash.MinimalPerfectHash; +import org.h2.dev.hash.MinimalPerfectHash.LongHash; +import org.h2.dev.hash.MinimalPerfectHash.StringHash; +import org.h2.dev.hash.MinimalPerfectHash.UniversalHash; +import org.h2.dev.hash.PerfectHash; +import org.h2.test.TestBase; + +/** + * Tests the perfect hash tool. + */ +public class TestPerfectHash extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestPerfectHash test = (TestPerfectHash) TestBase.createCaller().init(); + test.measure(); + largeFile(); + test.test(); + test.measure(); + } + + private static void largeFile() throws IOException { + largeFile("sequence.txt"); + for (int i = 1; i <= 4; i++) { + largeFile("unique" + i + ".txt"); + } + largeFile("enwiki-20140811-all-titles.txt"); + } + + private static void largeFile(String s) throws IOException { + String fileName = System.getProperty("user.home") + "/temp/" + s; + if (!new File(fileName).exists()) { + System.out.println("not found: " + fileName); + return; + } + RandomAccessFile f = new RandomAccessFile(fileName, "r"); + byte[] data = new byte[(int) f.length()]; + f.readFully(data); + UniversalHash hf = Text::hashCode; + f.close(); + HashSet set = new HashSet<>(); + Text t = new Text(data, 0); + while (true) { + set.add(t); + int end = t.getEnd(); + if (end >= data.length - 1) { + break; + } + t = new Text(data, end + 1); + if (set.size() % 1000000 == 0) { + System.out.println("size: " + set.size()); + } + } + System.out.println("file: " + s); + System.out.println("size: " + set.size()); + long time = System.nanoTime(); + byte[] desc = MinimalPerfectHash.generate(set, hf); + time = System.nanoTime() - time; + System.out.println("millis: " + TimeUnit.NANOSECONDS.toMillis(time)); + System.out.println("len: " + desc.length); + int bits = desc.length * 8; + System.out.println(((double) bits / set.size()) + " bits/key"); + } + + /** + * Measure the hash functions. + */ + public void measure() { + int size = 1000000; + testMinimal(size / 10); + int s; + long time = System.nanoTime(); + s = testMinimal(size); + time = System.nanoTime() - time; + System.out.println((double) s / size + " bits/key (minimal) in " + + TimeUnit.NANOSECONDS.toMillis(time) + " ms"); + + time = System.nanoTime(); + s = testMinimalWithString(size); + time = System.nanoTime() - time; + System.out.println((double) s / size + + " bits/key (minimal; String keys) in " + + TimeUnit.NANOSECONDS.toMillis(time) + " ms"); + + time = System.nanoTime(); + s = test(size, true); + time = System.nanoTime() - time; + System.out.println((double) s / size + " bits/key (minimal old) in " + + TimeUnit.NANOSECONDS.toMillis(time) + " ms"); + time = System.nanoTime(); + s = test(size, false); + time = System.nanoTime() - time; + System.out.println((double) s / size + " bits/key (not minimal) in " + + TimeUnit.NANOSECONDS.toMillis(time) + " ms"); + } + + @Override + public void test() { + testBrokenHashFunction(); + for (int i = 0; i < 100; i++) { + testMinimal(i); + } + for (int i = 100; i <= 100000; i *= 10) { + testMinimal(i); + } + for (int i = 0; i < 100; i++) { + test(i, true); + test(i, false); + } + for (int i = 100; i <= 100000; i *= 10) { + test(i, true); + test(i, false); + } + } + + private void testBrokenHashFunction() { + int size = 10000; + Random r = new Random(10000); + HashSet set = new HashSet<>(size); + while (set.size() < size) { + set.add("x " + r.nextDouble()); + } + for (int test = 1; test < 10; test++) { + final int badUntilLevel = test; + UniversalHash badHash = (o, index, seed) -> { + if (index < badUntilLevel) { + return 0; + } + return StringHash.getFastHash(o, index, seed); + }; + byte[] desc = MinimalPerfectHash.generate(set, badHash); + testMinimal(desc, set, badHash); + } + } + + private int test(int size, boolean minimal) { + Random r = new Random(size); + HashSet set = new HashSet<>(); + while (set.size() < size) { + set.add(r.nextInt()); + } + byte[] desc = PerfectHash.generate(set, minimal); + int max = test(desc, set); + if (minimal) { + assertEquals(size - 1, max); + } else { + if (size > 10) { + assertTrue(max < 1.5 * size); + } + } + return desc.length * 8; + } + + private int test(byte[] desc, Set set) { + int max = -1; + HashSet test = new HashSet<>(); + PerfectHash hash = new PerfectHash(desc); + for (int x : set) { + int h = hash.get(x); + assertTrue(h >= 0); + assertTrue(h <= set.size() * 3); + max = Math.max(max, h); + assertFalse(test.contains(h)); + test.add(h); + } + return max; + } + + private int testMinimal(int size) { + Random r = new Random(size); + HashSet set = new HashSet<>(size); + while (set.size() < size) { + set.add((long) r.nextInt()); + } + LongHash hf = new LongHash(); + byte[] desc = MinimalPerfectHash.generate(set, hf); + int max = testMinimal(desc, set, hf); + assertEquals(size - 1, max); + return desc.length * 8; + } + + private int testMinimalWithString(int size) { + Random r = new Random(size); + HashSet set = new HashSet<>(size); + while (set.size() < size) { + set.add("x " + r.nextDouble()); + } + StringHash hf = new StringHash(); + byte[] desc = MinimalPerfectHash.generate(set, hf); + int max = testMinimal(desc, set, hf); + assertEquals(size - 1, max); + return desc.length * 8; + } + + private int testMinimal(byte[] desc, Set set, UniversalHash hf) { + int max = -1; + BitSet test = new BitSet(); + MinimalPerfectHash hash = new MinimalPerfectHash<>(desc, hf); + for (K x : set) { + int h = hash.get(x); + assertTrue(h >= 0); + assertTrue(h <= set.size() * 3); + max = Math.max(max, h); + assertFalse(test.get(h)); + test.set(h); + } + return max; + } + + /** + * A text. + */ + static class Text { + + /** + * The byte data (may be shared, so must not be modified). + */ + final byte[] data; + + /** + * The start location. + */ + final int start; + + Text(byte[] data, int start) { + this.data = data; + this.start = start; + } + + /** + * The hash code (using a universal hash function). + * + * @param index the hash function index + * @param seed the random seed + * @return the hash code + */ + public int hashCode(int index, int seed) { + if (index < 8) { + int x = (index * 0x9f3b) ^ seed; + int result = seed; + int p = start; + while (true) { + int c = data[p++] & 255; + if (c == '\n') { + break; + } + x = 31 + x * 0x9f3b; + result ^= x * (1 + c); + } + return result; + } + int end = getEnd(); + return StringHash.getSipHash24(data, start, end, index, seed); + } + + int getEnd() { + int end = start; + while (data[end] != '\n') { + end++; + } + return end; + } + + @Override + public int hashCode() { + return hashCode(0, 0); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } else if (!(other instanceof Text)) { + return false; + } + Text o = (Text) other; + int end = getEnd(); + int s2 = o.start; + int e2 = o.getEnd(); + if (e2 - s2 != end - start) { + return false; + } + for (int s1 = start; s1 < end; s1++, s2++) { + if (data[s1] != o.data[s2]) { + return false; + } + } + return true; + } + + @Override + public String toString() { + return new String(data, start, getEnd() - start); + } + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestPgServer.java b/h2/src/test/org/h2/test/unit/TestPgServer.java new file mode 100644 index 0000000..4a0a474 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestPgServer.java @@ -0,0 +1,913 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.lang.reflect.Field; +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.Date; +import java.sql.DriverManager; +import java.sql.ParameterMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.Properties; +import java.util.Set; +import java.util.TimeZone; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.h2.api.ErrorCode; +import org.h2.server.pg.PgServer; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.Server; +import org.h2.util.DateTimeUtils; + +/** + * Tests the PostgreSQL server protocol compliant implementation. + */ +public class TestPgServer extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase test = TestBase.createCaller().init(); + test.config.memory = true; + test.testFromMain(); + } + + @Override + public boolean isEnabled() { + if (!config.memory) { + return false; + } + return true; + } + + @Override + public void test() throws Exception { + // testPgAdapter() starts server by itself without a wait so run it first + testPgAdapter(); + testKeyAlias(); + testCancelQuery(); + testTextualAndBinaryTypes(); + testBinaryNumeric(); + testDateTime(); + testPrepareWithUnspecifiedType(); + testOtherPgClients(); + testArray(); + } + + private boolean getPgJdbcDriver() { + try { + Class.forName("org.postgresql.Driver"); + return true; + } catch (ClassNotFoundException e) { + println("PostgreSQL JDBC driver not found - PgServer not tested"); + return false; + } + } + + private Server createPgServer(String... args) throws SQLException { + Server server = Server.createPgServer(args); + int failures = 0; + for (;;) { + try { + server.start(); + return server; + } catch (SQLException e) { + // the sleeps are too mitigate "port in use" exceptions on Jenkins + if (e.getErrorCode() != ErrorCode.EXCEPTION_OPENING_PORT_2 || ++failures > 10) { + throw e; + } + println("Sleeping"); + try { + Thread.sleep(100); + } catch (InterruptedException e2) { + throw new RuntimeException(e2); + } + } + } + } + + private void testPgAdapter() throws SQLException { + deleteDb("pgserver"); + Server server = Server.createPgServer( + "-ifNotExists", "-baseDir", getBaseDir(), "-pgPort", "5535", "-pgDaemon"); + assertEquals(5535, server.getPort()); + assertEquals("Not started", server.getStatus()); + server.start(); + assertStartsWith(server.getStatus(), "PG server running at pg://"); + try { + if (getPgJdbcDriver()) { + testPgClient(); + testPgClientSimple(); + } + } finally { + server.stop(); + } + } + + private void testCancelQuery() throws Exception { + if (!getPgJdbcDriver()) { + return; + } + + Server server = createPgServer( + "-ifNotExists", "-pgPort", "5535", "-pgDaemon", "-key", "pgserver", "mem:pgserver"); + + ExecutorService executor = Executors.newSingleThreadExecutor(); + try { + Connection conn = DriverManager.getConnection( + "jdbc:postgresql://localhost:5535/pgserver", "sa", "sa"); + Statement stat = conn.createStatement(); + stat.execute("create alias sleep for 'java.lang.Thread.sleep'"); + + // create a table with 200 rows (cancel interval is 127) + stat.execute("create table test(id int)"); + for (int i = 0; i < 200; i++) { + stat.execute("insert into test (id) values (rand())"); + } + + Future future = executor.submit(() -> stat.execute("select id, sleep(5) from test")); + + // give it a little time to start and then cancel it + Thread.sleep(100); + stat.cancel(); + + try { + future.get(); + throw new IllegalStateException(); + } catch (ExecutionException e) { + assertStartsWith(e.getCause().getMessage(), + "ERROR: canceling statement due to user request"); + } finally { + conn.close(); + } + } finally { + server.stop(); + executor.shutdown(); + } + deleteDb("pgserver"); + } + + private void testPgClient() throws SQLException { + Connection conn = DriverManager.getConnection( + "jdbc:postgresql://localhost:5535/pgserver", "sa", "sa"); + Statement stat = conn.createStatement(); + assertThrows(SQLException.class, stat). + execute("select ***"); + stat.execute("create user test password 'test'"); + stat.execute("create table test(id int primary key, name varchar)"); + stat.execute("create index idx_test_name on test(name, id)"); + stat.execute("grant all on test to test"); + int userId; + try (ResultSet rs = stat.executeQuery("call db_object_id('USER', 'test')")) { + rs.next(); + userId = rs.getInt(1); + } + int indexId; + try (ResultSet rs = stat.executeQuery("call db_object_id('INDEX', 'public', 'idx_test_name')")) { + rs.next(); + indexId = rs.getInt(1); + } + stat.close(); + conn.close(); + + conn = DriverManager.getConnection( + "jdbc:postgresql://localhost:5535/pgserver", "test", "test"); + stat = conn.createStatement(); + ResultSet rs; + + stat.execute("prepare test(int, int) as select ?1*?2"); + rs = stat.executeQuery("execute test(3, 2)"); + rs.next(); + assertEquals(6, rs.getInt(1)); + stat.execute("deallocate test"); + + PreparedStatement prep; + prep = conn.prepareStatement("select * from test where name = ?"); + prep.setNull(1, Types.VARCHAR); + rs = prep.executeQuery(); + assertFalse(rs.next()); + + prep = conn.prepareStatement("insert into test values(?, ?)"); + ParameterMetaData meta = prep.getParameterMetaData(); + assertEquals(2, meta.getParameterCount()); + prep.setInt(1, 1); + prep.setString(2, "Hello"); + prep.execute(); + rs = stat.executeQuery("select *, null nul from test"); + rs.next(); + + ResultSetMetaData rsMeta = rs.getMetaData(); + assertEquals(Types.INTEGER, rsMeta.getColumnType(1)); + assertEquals(Types.VARCHAR, rsMeta.getColumnType(2)); + assertEquals(Types.VARCHAR, rsMeta.getColumnType(3)); + assertEquals("test", rsMeta.getTableName(1)); + + prep.close(); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + assertFalse(rs.next()); + prep = conn.prepareStatement( + "select * from test " + + "where id = ? and name = ?"); + prep.setInt(1, 1); + prep.setString(2, "Hello"); + rs = prep.executeQuery(); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + assertFalse(rs.next()); + rs.close(); + DatabaseMetaData dbMeta = conn.getMetaData(); + rs = dbMeta.getTables(null, null, "TEST", null); + assertFalse(rs.next()); + rs = dbMeta.getTables(null, null, "test", null); + assertTrue(rs.next()); + assertEquals("test", rs.getString("TABLE_NAME")); + assertFalse(rs.next()); + rs = dbMeta.getColumns(null, null, "test", null); + rs.next(); + assertEquals("id", rs.getString("COLUMN_NAME")); + rs.next(); + assertEquals("name", rs.getString("COLUMN_NAME")); + assertFalse(rs.next()); + rs = dbMeta.getIndexInfo(null, null, "TEST", false, false); + // index info is currently disabled + // rs.next(); + // assertEquals("TEST", rs.getString("TABLE_NAME")); + // rs.next(); + // assertEquals("TEST", rs.getString("TABLE_NAME")); + assertFalse(rs.next()); + rs = stat.executeQuery( + "select version(), pg_postmaster_start_time(), current_schema()"); + rs.next(); + String s = rs.getString(1); + assertContains(s, "H2"); + assertContains(s, "PostgreSQL"); + s = rs.getString(2); + s = rs.getString(3); + assertEquals(s, "public"); + assertFalse(rs.next()); + + conn.setAutoCommit(false); + stat.execute("delete from test"); + conn.rollback(); + stat.execute("update test set name = 'Hallo'"); + conn.commit(); + rs = stat.executeQuery("select * from test order by id"); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals("Hallo", rs.getString(2)); + assertFalse(rs.next()); + + rs = stat.executeQuery("select pg_get_userbyid(" + userId + ')'); + rs.next(); + assertEquals("test", rs.getString(1)); + rs.close(); + + rs = stat.executeQuery("select currTid2('x', 1)"); + rs.next(); + assertEquals(1, rs.getInt(1)); + + rs = stat.executeQuery("select has_table_privilege('TEST', 'READ')"); + rs.next(); + assertTrue(rs.getBoolean(1)); + + rs = stat.executeQuery("select has_schema_privilege(1, 'READ')"); + rs.next(); + assertTrue(rs.getBoolean(1)); + + rs = stat.executeQuery("select has_database_privilege(1, 'READ')"); + rs.next(); + assertTrue(rs.getBoolean(1)); + + + rs = stat.executeQuery("select pg_get_userbyid(1000000000)"); + rs.next(); + assertEquals("unknown (OID=1000000000)", rs.getString(1)); + + rs = stat.executeQuery("select pg_encoding_to_char(0)"); + rs.next(); + assertEquals("SQL_ASCII", rs.getString(1)); + + rs = stat.executeQuery("select pg_encoding_to_char(6)"); + rs.next(); + assertEquals("UTF8", rs.getString(1)); + + rs = stat.executeQuery("select pg_encoding_to_char(8)"); + rs.next(); + assertEquals("LATIN1", rs.getString(1)); + + rs = stat.executeQuery("select pg_encoding_to_char(20)"); + rs.next(); + assertEquals("UTF8", rs.getString(1)); + + rs = stat.executeQuery("select pg_encoding_to_char(40)"); + rs.next(); + assertEquals("", rs.getString(1)); + + rs = stat.executeQuery("select 0::regclass"); + rs.next(); + assertEquals(0, rs.getInt(1)); + + rs = stat.executeQuery("select pg_get_indexdef(0, 0, false)"); + rs.next(); + assertNull(rs.getString(1)); + + rs = stat.executeQuery("select pg_get_indexdef("+indexId+", 0, false)"); + rs.next(); + assertEquals("CREATE INDEX \"public\".\"idx_test_name\" ON \"public\".\"test\"" + + "(\"name\" NULLS LAST, \"id\" NULLS LAST)", + rs.getString(1)); + rs = stat.executeQuery("select pg_get_indexdef("+indexId+", null, false)"); + rs.next(); + assertNull(rs.getString(1)); + rs = stat.executeQuery("select pg_get_indexdef("+indexId+", 1, false)"); + rs.next(); + assertEquals("name", rs.getString(1)); + rs = stat.executeQuery("select pg_get_indexdef("+indexId+", 2, false)"); + rs.next(); + assertEquals("id", rs.getString(1)); + + rs = stat.executeQuery("select * from pg_type where oid = " + PgServer.PG_TYPE_VARCHAR_ARRAY); + rs.next(); + assertEquals("_varchar", rs.getString("typname")); + assertEquals("_varchar", rs.getObject("typname")); + assertEquals("b", rs.getString("typtype")); + assertEquals(",", rs.getString("typdelim")); + assertEquals(PgServer.PG_TYPE_VARCHAR, rs.getInt("typelem")); + + stat.setMaxRows(10); + rs = stat.executeQuery("select * from generate_series(0, 10)"); + assertNRows(rs, 10); + stat.setMaxRows(0); + + stat.setFetchSize(2); + rs = stat.executeQuery("select * from generate_series(0, 4)"); + assertNRows(rs, 5); + rs = stat.executeQuery("select * from generate_series(0, 1)"); + assertNRows(rs, 2); + stat.setFetchSize(0); + + conn.close(); + } + + private void assertNRows(ResultSet rs, int n) throws SQLException { + for (int i = 0; i < n; i++) { + assertTrue(rs.next()); + assertEquals(i, rs.getInt(1)); + } + assertFalse(rs.next()); + } + + private void testPgClientSimple() throws SQLException { + Connection conn = DriverManager.getConnection( + "jdbc:postgresql://localhost:5535/pgserver?preferQueryMode=simple", "sa", "sa"); + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("select 1"); + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertFalse(rs.next()); + stat.setMaxRows(0); + stat.execute("create table test2(int integer)"); + stat.execute("drop table test2"); + assertThrows(SQLException.class, stat).execute("drop table test2"); + conn.close(); + } + + private void testKeyAlias() throws SQLException { + if (!getPgJdbcDriver()) { + return; + } + Server server = createPgServer( + "-ifNotExists", "-pgPort", "5535", "-pgDaemon", "-key", "pgserver", "mem:pgserver"); + try { + Connection conn = DriverManager.getConnection( + "jdbc:postgresql://localhost:5535/pgserver", "sa", "sa"); + Statement stat = conn.createStatement(); + + // confirm that we've got the in memory implementation + // by creating a table and checking flags + stat.execute("create table test(id int primary key, name varchar)"); + ResultSet rs = stat.executeQuery( + "select storage_type from information_schema.tables " + + "where table_name = 'test'"); + assertTrue(rs.next()); + assertEquals("MEMORY", rs.getString(1)); + + conn.close(); + } finally { + server.stop(); + } + } + + private static Set supportedBinaryOids; + + static { + try { + supportedBinaryOids = getSupportedBinaryOids(); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + @SuppressWarnings("unchecked") + private static Set getSupportedBinaryOids() throws ReflectiveOperationException { + Field supportedBinaryOidsField = Class + .forName("org.postgresql.jdbc.PgConnection") + .getDeclaredField("SUPPORTED_BINARY_OIDS"); + supportedBinaryOidsField.setAccessible(true); + return (Set) supportedBinaryOidsField.get(null); + } + + private void testTextualAndBinaryTypes() throws SQLException { + testTextualAndBinaryTypes(false); + testTextualAndBinaryTypes(true); + // additional support of NUMERIC for Npgsql + supportedBinaryOids.add(1700); + testTextualAndBinaryTypes(true); + supportedBinaryOids.remove(1700); + } + + private void testTextualAndBinaryTypes(boolean binary) throws SQLException { + if (!getPgJdbcDriver()) { + return; + } + + Server server = createPgServer( + "-ifNotExists", "-pgPort", "5535", "-pgDaemon", "-key", "pgserver", "mem:pgserver"); + try { + Properties props = new Properties(); + props.setProperty("user", "sa"); + props.setProperty("password", "sa"); + + // force binary + if (binary) { + props.setProperty("prepareThreshold", "-1"); + } + + Connection conn = DriverManager.getConnection( + "jdbc:postgresql://localhost:5535/pgserver", props); + Statement stat = conn.createStatement(); + + stat.execute( + "create table test(x1 varchar, x2 int, " + + "x3 smallint, x4 bigint, x5 double precision, x6 float, " + + "x7 real, x8 boolean, x9 char(3), x10 bytea, " + + "x11 date, x12 time, x13 timestamp, x14 numeric(25, 5)," + + "x15 time with time zone, x16 timestamp with time zone)"); + + PreparedStatement ps = conn.prepareStatement( + "insert into test values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); + ps.setString(1, "test"); + ps.setInt(2, 12345678); + ps.setShort(3, (short) 12345); + ps.setLong(4, 1234567890123L); + ps.setDouble(5, 123.456); + ps.setFloat(6, 123.456f); + ps.setFloat(7, 123.456f); + ps.setBoolean(8, true); + ps.setByte(9, (byte) 0xfe); + ps.setBytes(10, new byte[] { 'a', (byte) 0xfe, '\127', 0, 127, '\\' }); + ps.setDate(11, Date.valueOf("2015-01-31")); + ps.setTime(12, Time.valueOf("20:11:15")); + ps.setTimestamp(13, Timestamp.valueOf("2001-10-30 14:16:10.111")); + ps.setBigDecimal(14, new BigDecimal("12345678901234567890.12345")); + ps.setTime(15, Time.valueOf("20:11:15")); + ps.setTimestamp(16, Timestamp.valueOf("2001-10-30 14:16:10.111")); + ps.execute(); + for (int i = 1; i <= 16; i++) { + ps.setNull(i, Types.NULL); + } + ps.execute(); + + ResultSet rs = stat.executeQuery("select * from test"); + assertTrue(rs.next()); + assertEquals("test", rs.getString(1)); + assertEquals(12345678, rs.getInt(2)); + assertEquals((short) 12345, rs.getShort(3)); + assertEquals(1234567890123L, rs.getLong(4)); + assertEquals(123.456, rs.getDouble(5)); + assertEquals(123.456f, rs.getFloat(6)); + assertEquals(123.456f, rs.getFloat(7)); + assertEquals(true, rs.getBoolean(8)); + assertEquals((byte) 0xfe, rs.getByte(9)); + assertEquals(new byte[] { 'a', (byte) 0xfe, '\127', 0, 127, '\\' }, + rs.getBytes(10)); + assertEquals(Date.valueOf("2015-01-31"), rs.getDate(11)); + assertEquals(Time.valueOf("20:11:15"), rs.getTime(12)); + assertEquals(Timestamp.valueOf("2001-10-30 14:16:10.111"), rs.getTimestamp(13)); + assertEquals(new BigDecimal("12345678901234567890.12345"), rs.getBigDecimal(14)); + assertEquals(Time.valueOf("20:11:15"), rs.getTime(15)); + assertEquals(Timestamp.valueOf("2001-10-30 14:16:10.111"), rs.getTimestamp(16)); + assertTrue(rs.next()); + for (int i = 1; i <= 16; i++) { + assertNull(rs.getObject(i)); + } + assertFalse(rs.next()); + + conn.close(); + } finally { + server.stop(); + } + } + + private void testBinaryNumeric() throws SQLException { + if (!getPgJdbcDriver()) { + return; + } + Server server = createPgServer( + "-ifNotExists", "-pgPort", "5535", "-pgDaemon", "-key", "pgserver", "mem:pgserver"); + supportedBinaryOids.add(1700); + try { + Properties props = new Properties(); + props.setProperty("user", "sa"); + props.setProperty("password", "sa"); + // force binary + props.setProperty("prepareThreshold", "-1"); + + Connection conn = DriverManager.getConnection( + "jdbc:postgresql://localhost:5535/pgserver", props); + Statement stat = conn.createStatement(); + + try (ResultSet rs = stat.executeQuery("SELECT 1E-16383, 1E+1, 1E+89, 1E-16384")) { + rs.next(); + assertEquals(new BigDecimal("1E-16383"), rs.getBigDecimal(1)); + assertEquals(new BigDecimal("10"), rs.getBigDecimal(2)); + assertEquals(new BigDecimal("10").pow(89), rs.getBigDecimal(3)); + // TODO `SELECT 1E+90, 1E+131071` fails due to PgJDBC issue 1935 + try { + rs.getBigDecimal(4); + fail(); + } catch (IllegalArgumentException e) { + // PgJDBC doesn't support scale greater than 16383 + } + } + try (ResultSet rs = stat.executeQuery("SELECT 1E-32768")) { + fail(); + } catch (SQLException e) { + assertEquals("22003", e.getSQLState()); + } + try (ResultSet rs = stat.executeQuery("SELECT 1E+131072")) { + fail(); + } catch (SQLException e) { + assertEquals("22003", e.getSQLState()); + } + + conn.close(); + } finally { + supportedBinaryOids.remove(1700); + server.stop(); + } + } + + private void testDateTime() throws SQLException { + if (!getPgJdbcDriver()) { + return; + } + TimeZone old = TimeZone.getDefault(); + /* + * java.util.TimeZone doesn't support LMT, so perform this test with + * fixed time zone offset + */ + TimeZone.setDefault(TimeZone.getTimeZone("GMT+01")); + DateTimeUtils.resetCalendar(); + try { + Server server = createPgServer( + "-ifNotExists", "-pgPort", "5535", "-pgDaemon", "-key", "pgserver", "mem:pgserver"); + try { + Properties props = new Properties(); + props.setProperty("user", "sa"); + props.setProperty("password", "sa"); + // force binary + props.setProperty("prepareThreshold", "-1"); + + Connection conn = DriverManager.getConnection( + "jdbc:postgresql://localhost:5535/pgserver", props); + Statement stat = conn.createStatement(); + + stat.execute( + "create table test(x1 date, x2 time, x3 timestamp)"); + + Date[] dates = { null, Date.valueOf("2017-02-20"), + Date.valueOf("1970-01-01"), Date.valueOf("1969-12-31"), + Date.valueOf("1940-01-10"), Date.valueOf("1950-11-10"), + Date.valueOf("1500-01-01")}; + Time[] times = { null, Time.valueOf("14:15:16"), + Time.valueOf("00:00:00"), Time.valueOf("23:59:59"), + Time.valueOf("00:10:59"), Time.valueOf("08:30:42"), + Time.valueOf("10:00:00")}; + Timestamp[] timestamps = { null, Timestamp.valueOf("2017-02-20 14:15:16.763"), + Timestamp.valueOf("1970-01-01 00:00:00"), Timestamp.valueOf("1969-12-31 23:59:59"), + Timestamp.valueOf("1940-01-10 00:10:59"), Timestamp.valueOf("1950-11-10 08:30:42.12"), + Timestamp.valueOf("1500-01-01 10:00:10")}; + int count = dates.length; + + PreparedStatement ps = conn.prepareStatement( + "insert into test values (?,?,?)"); + for (int i = 0; i < count; i++) { + ps.setDate(1, dates[i]); + ps.setTime(2, times[i]); + ps.setTimestamp(3, timestamps[i]); + ps.execute(); + } + + ResultSet rs = stat.executeQuery("select * from test"); + for (int i = 0; i < count; i++) { + assertTrue(rs.next()); + assertEquals(dates[i], rs.getDate(1)); + assertEquals(times[i], rs.getTime(2)); + assertEquals(timestamps[i], rs.getTimestamp(3)); + } + assertFalse(rs.next()); + + conn.close(); + } finally { + server.stop(); + } + } finally { + TimeZone.setDefault(old); + DateTimeUtils.resetCalendar(); + } + } + + private void testPrepareWithUnspecifiedType() throws Exception { + if (!getPgJdbcDriver()) { + return; + } + + Server server = createPgServer( + "-ifNotExists", "-pgPort", "5535", "-pgDaemon", "-key", "pgserver", "mem:pgserver"); + try { + Properties props = new Properties(); + + props.setProperty("user", "sa"); + props.setProperty("password", "sa"); + // force server side prepare + props.setProperty("prepareThreshold", "1"); + + Connection conn = DriverManager.getConnection( + "jdbc:postgresql://localhost:5535/pgserver", props); + + Statement stmt = conn.createStatement(); + stmt.executeUpdate("create table t1 (id integer, v timestamp)"); + stmt.close(); + + PreparedStatement pstmt = conn.prepareStatement("insert into t1 values(100500, ?)"); + // assertTrue(((PGStatement) pstmt).isUseServerPrepare()); + assertEquals(Types.TIMESTAMP, pstmt.getParameterMetaData().getParameterType(1)); + + Timestamp t = new Timestamp(System.currentTimeMillis()); + pstmt.setObject(1, t); + assertEquals(1, pstmt.executeUpdate()); + pstmt.close(); + + pstmt = conn.prepareStatement("SELECT * FROM t1 WHERE v = ?"); + assertEquals(Types.TIMESTAMP, pstmt.getParameterMetaData().getParameterType(1)); + + pstmt.setObject(1, t); + ResultSet rs = pstmt.executeQuery(); + assertTrue(rs.next()); + assertEquals(100500, rs.getInt(1)); + rs.close(); + pstmt.close(); + + conn.close(); + } finally { + server.stop(); + } + } + + private void testOtherPgClients() throws SQLException { + if (!getPgJdbcDriver()) { + return; + } + + Server server = createPgServer( + "-ifNotExists", "-pgPort", "5535", "-pgDaemon", "-key", "pgserver", "mem:pgserver"); + try ( + Connection conn = DriverManager.getConnection( + "jdbc:postgresql://localhost:5535/pgserver", "sa", "sa"); + Statement stat = conn.createStatement(); + ) { + stat.execute( + "create table test(id serial primary key, x1 integer)"); + + // pgAdmin + stat.execute("SET client_min_messages=notice"); + try (ResultSet rs = stat.executeQuery("SELECT set_config('bytea_output','escape',false) " + + "FROM pg_settings WHERE name = 'bytea_output'")) { + assertFalse(rs.next()); + } + stat.execute("SET client_encoding='UNICODE'"); + try (ResultSet rs = stat.executeQuery("SELECT version()")) { + assertTrue(rs.next()); + assertNotNull(rs.getString("version")); + } + try (ResultSet rs = stat.executeQuery("SELECT " + + "db.oid as did, db.datname, db.datallowconn, " + + "pg_encoding_to_char(db.encoding) AS serverencoding, " + + "has_database_privilege(db.oid, 'CREATE') as cancreate, datlastsysoid " + + "FROM pg_database db WHERE db.datname = current_database()")) { + assertTrue(rs.next()); + assertEquals("pgserver", rs.getString("datname")); + assertFalse(rs.next()); + } + try (ResultSet rs = stat.executeQuery("SELECT " + + "oid as id, rolname as name, rolsuper as is_superuser, " + + "CASE WHEN rolsuper THEN true ELSE rolcreaterole END as can_create_role, " + + "CASE WHEN rolsuper THEN true ELSE rolcreatedb END as can_create_db " + + "FROM pg_catalog.pg_roles WHERE rolname = current_user")) { + assertTrue(rs.next()); + assertEquals("sa", rs.getString("name")); + assertFalse(rs.next()); + } + try (ResultSet rs = stat.executeQuery("SELECT " + + "db.oid as did, db.datname as name, ta.spcname as spcname, db.datallowconn, " + + "has_database_privilege(db.oid, 'CREATE') as cancreate, datdba as owner " + + "FROM pg_database db LEFT OUTER JOIN pg_tablespace ta ON db.dattablespace = ta.oid " + + "WHERE db.oid > 100000::OID")) { + assertTrue(rs.next()); + assertEquals("pgserver", rs.getString("name")); + assertFalse(rs.next()); + } + try (ResultSet rs = stat.executeQuery("SELECT nsp.oid, nsp.nspname as name, " + + "has_schema_privilege(nsp.oid, 'CREATE') as can_create, " + + "has_schema_privilege(nsp.oid, 'USAGE') as has_usage " + + "FROM pg_namespace nsp WHERE nspname NOT LIKE 'pg\\_%' AND NOT (" + + "(nsp.nspname = 'pg_catalog' AND EXISTS (SELECT 1 FROM pg_class " + + "WHERE relname = 'pg_class' AND relnamespace = nsp.oid LIMIT 1)) OR " + + "(nsp.nspname = 'pgagent' AND EXISTS (SELECT 1 FROM pg_class " + + "WHERE relname = 'pga_job' AND relnamespace = nsp.oid LIMIT 1)) OR " + + "(nsp.nspname = 'information_schema' AND EXISTS (SELECT 1 FROM pg_class " + + "WHERE relname = 'tables' AND relnamespace = nsp.oid LIMIT 1))" + + ") ORDER BY nspname")) { + assertTrue(rs.next()); + assertEquals("public", rs.getString("name")); + assertFalse(rs.next()); + } + try (ResultSet rs = stat.executeQuery("SELECT format_type(23, NULL)")) { + assertTrue(rs.next()); + assertEquals("INTEGER", rs.getString(1)); + assertFalse(rs.next()); + } + // pgAdmin sends `SET LOCAL join_collapse_limit=8`, but `LOCAL` is not supported yet + stat.execute("SET join_collapse_limit=8"); + + // HeidiSQL + try (ResultSet rs = stat.executeQuery("SHOW ssl")) { + assertTrue(rs.next()); + assertEquals("off", rs.getString(1)); + } + stat.execute("SET search_path TO 'public', '$user'"); + try (ResultSet rs = stat.executeQuery("SELECT *, NULL AS data_length, " + + "pg_relation_size(QUOTE_IDENT(t.TABLE_SCHEMA) || '.' || QUOTE_IDENT(t.TABLE_NAME))::bigint " + + "AS index_length, " + + "c.reltuples, obj_description(c.oid) AS comment " + + "FROM \"information_schema\".\"tables\" AS t " + + "LEFT JOIN \"pg_namespace\" n ON t.table_schema = n.nspname " + + "LEFT JOIN \"pg_class\" c ON n.oid = c.relnamespace AND c.relname=t.table_name " + + "WHERE t.\"table_schema\"='public'")) { + assertTrue(rs.next()); + assertEquals("test", rs.getString("table_name")); + assertTrue(rs.getLong("index_length") >= 0L); // test pg_relation_size() + assertNull(rs.getString("comment")); // test obj_description() + } + try (ResultSet rs = stat.executeQuery("SELECT \"p\".\"proname\", \"p\".\"proargtypes\" " + + "FROM \"pg_catalog\".\"pg_namespace\" AS \"n\" " + + "JOIN \"pg_catalog\".\"pg_proc\" AS \"p\" ON \"p\".\"pronamespace\" = \"n\".\"oid\" " + + "WHERE \"n\".\"nspname\"='public'")) { + assertFalse(rs.next()); // "pg_proc" always empty + } + try (ResultSet rs = stat.executeQuery("SELECT DISTINCT a.attname AS column_name, " + + "a.attnum, a.atttypid, FORMAT_TYPE(a.atttypid, a.atttypmod) AS data_type, " + + "CASE a.attnotnull WHEN false THEN 'YES' ELSE 'NO' END AS IS_NULLABLE, " + + "com.description AS column_comment, pg_get_expr(def.adbin, def.adrelid) AS column_default, " + + "NULL AS character_maximum_length FROM pg_attribute AS a " + + "JOIN pg_class AS pgc ON pgc.oid = a.attrelid " + + "LEFT JOIN pg_description AS com ON (pgc.oid = com.objoid AND a.attnum = com.objsubid) " + + "LEFT JOIN pg_attrdef AS def ON (a.attrelid = def.adrelid AND a.attnum = def.adnum) " + + "WHERE a.attnum > 0 AND pgc.oid = a.attrelid AND pg_table_is_visible(pgc.oid) " + + "AND NOT a.attisdropped AND pgc.relname = 'test' ORDER BY a.attnum")) { + assertTrue(rs.next()); + assertEquals("id", rs.getString("column_name")); + assertTrue(rs.next()); + assertEquals("x1", rs.getString("column_name")); + assertFalse(rs.next()); + } + try (ResultSet rs = stat.executeQuery("SHOW ALL")) { + ResultSetMetaData rsMeta = rs.getMetaData(); + assertEquals("name", rsMeta.getColumnName(1)); + assertEquals("setting", rsMeta.getColumnName(2)); + } + + // DBeaver + try (ResultSet rs = stat.executeQuery("SELECT t.oid,t.*,c.relkind FROM pg_catalog.pg_type t " + + "LEFT OUTER JOIN pg_class c ON c.oid=t.typrelid WHERE typnamespace=-1000")) { + // just no exception + } + stat.execute("SET search_path TO 'ab', 'c\"d', 'e''f'"); + try (ResultSet rs = stat.executeQuery("SHOW search_path")) { + assertTrue(rs.next()); + assertEquals("pg_catalog, ab, \"c\"\"d\", \"e'f\"", rs.getString("search_path")); + } + stat.execute("SET search_path TO ab, \"c\"\"d\", \"e'f\""); + try (ResultSet rs = stat.executeQuery("SHOW search_path")) { + assertTrue(rs.next()); + assertEquals("pg_catalog, ab, \"c\"\"d\", \"e'f\"", rs.getString("search_path")); + } + int oid; + try (ResultSet rs = stat.executeQuery("SELECT oid FROM pg_class WHERE relname = 'test'")) { + rs.next(); + oid = rs.getInt("oid"); + } + try (ResultSet rs = stat.executeQuery("SELECT i.*,i.indkey as keys," + + "c.relname,c.relnamespace,c.relam,c.reltablespace," + + "tc.relname as tabrelname,dsc.description," + + "pg_catalog.pg_get_expr(i.indpred, i.indrelid) as pred_expr," + + "pg_catalog.pg_get_expr(i.indexprs, i.indrelid, true) as expr," + + "pg_catalog.pg_relation_size(i.indexrelid) as index_rel_size," + + "pg_catalog.pg_stat_get_numscans(i.indexrelid) as index_num_scans " + + "FROM pg_catalog.pg_index i " + + "INNER JOIN pg_catalog.pg_class c ON c.oid=i.indexrelid " + + "INNER JOIN pg_catalog.pg_class tc ON tc.oid=i.indrelid " + + "LEFT OUTER JOIN pg_catalog.pg_description dsc ON i.indexrelid=dsc.objoid " + + "WHERE i.indrelid=" + oid + " ORDER BY c.relname")) { + // pg_index is empty + assertFalse(rs.next()); + } + try (ResultSet rs = stat.executeQuery("SELECT c.oid,c.*," + + "t.relname as tabrelname,rt.relnamespace as refnamespace,d.description " + + "FROM pg_catalog.pg_constraint c " + + "INNER JOIN pg_catalog.pg_class t ON t.oid=c.conrelid " + + "LEFT OUTER JOIN pg_catalog.pg_class rt ON rt.oid=c.confrelid " + + "LEFT OUTER JOIN pg_catalog.pg_description d ON d.objoid=c.oid " + + "AND d.objsubid=0 AND d.classoid='pg_constraint'::regclass WHERE c.conrelid=" + oid)) { + assertTrue(rs.next()); + assertEquals("test", rs.getString("tabrelname")); + assertEquals("p", rs.getString("contype")); + assertEquals(Short.valueOf((short) 1), ((Object[]) rs.getArray("conkey").getArray())[0]); + } + } finally { + server.stop(); + } + } + + private void testArray() throws Exception { + if (!getPgJdbcDriver()) { + return; + } + + Server server = createPgServer( + "-ifNotExists", "-pgPort", "5535", "-pgDaemon", "-key", "pgserver", "mem:pgserver"); + try ( + Connection conn = DriverManager.getConnection( + "jdbc:postgresql://localhost:5535/pgserver", "sa", "sa"); + Statement stat = conn.createStatement(); + ) { + stat.execute("CREATE TABLE test (id int primary key, x1 varchar array)"); + stat.execute("INSERT INTO test (id, x1) VALUES (1, ARRAY['abc', 'd\\\"e', '{,}'])"); + try (ResultSet rs = stat.executeQuery( + "SELECT x1 FROM test WHERE id = 1")) { + assertTrue(rs.next()); + Object[] arr = (Object[]) rs.getArray(1).getArray(); + assertEquals("abc", arr[0]); + assertEquals("d\\\"e", arr[1]); + assertEquals("{,}", arr[2]); + } + try (ResultSet rs = stat.executeQuery( + "SELECT data_type FROM information_schema.columns WHERE table_schema = 'pg_catalog' " + + "AND table_name = 'pg_database' AND column_name = 'datacl'")) { + assertTrue(rs.next()); + assertEquals("array", rs.getString(1)); + } + try (ResultSet rs = stat.executeQuery( + "SELECT data_type FROM information_schema.columns WHERE table_schema = 'pg_catalog' " + + "AND table_name = 'pg_tablespace' AND column_name = 'spcacl'")) { + assertTrue(rs.next()); + assertEquals("array", rs.getString(1)); + } + } finally { + server.stop(); + } + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestReader.java b/h2/src/test/org/h2/test/unit/TestReader.java new file mode 100644 index 0000000..2ddb8fc --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestReader.java @@ -0,0 +1,43 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; + +import org.h2.dev.util.ReaderInputStream; +import org.h2.test.TestBase; +import org.h2.util.IOUtils; + +/** + * Tests the stream to UTF-8 reader conversion. + */ +public class TestReader extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + String s = "\u00ef\u00f6\u00fc"; + StringReader r = new StringReader(s); + InputStream in = new ReaderInputStream(r); + byte[] buff = IOUtils.readBytesAndClose(in, 0); + InputStream in2 = new ByteArrayInputStream(buff); + Reader r2 = IOUtils.getReader(in2); + String s2 = IOUtils.readStringAndClose(r2, Integer.MAX_VALUE); + assertEquals(s, s2); + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestRecovery.java b/h2/src/test/org/h2/test/unit/TestRecovery.java new file mode 100644 index 0000000..3db1cc1 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestRecovery.java @@ -0,0 +1,217 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.DeleteDbFiles; +import org.h2.tools.Recover; +import org.h2.util.Utils10; + +/** + * Tests database recovery. + */ +public class TestRecovery extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + if (config.memory) { + return false; + } + return true; + } + + @Override + public void test() throws Exception { + testRecoverClob(); + testRecoverFulltext(); + testCompressedAndUncompressed(); + testRunScript(); + testRunScript2(); + } + + private void testRecoverClob() throws Exception { + DeleteDbFiles.execute(getBaseDir(), "recovery", true); + Connection conn = getConnection("recovery"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int, data clob)"); + stat.execute("insert into test values(1, space(100000))"); + conn.close(); + Recover.main("-dir", getBaseDir(), "-db", "recovery"); + DeleteDbFiles.execute(getBaseDir(), "recovery", true); + conn = getConnection( + "recovery;init=runscript from '" + + getBaseDir() + "/recovery.h2.sql'"); + stat = conn.createStatement(); + stat.execute("select * from test"); + conn.close(); + } + + private void testRecoverFulltext() throws Exception { + DeleteDbFiles.execute(getBaseDir(), "recovery", true); + Connection conn = getConnection("recovery"); + Statement stat = conn.createStatement(); + stat.execute("CREATE ALIAS IF NOT EXISTS FTL_INIT FOR 'org.h2.fulltext.FullTextLucene.init'"); + stat.execute("CALL FTL_INIT()"); + stat.execute("create table test(id int primary key, name varchar) as " + + "select 1, 'Hello'"); + stat.execute("CALL FTL_CREATE_INDEX('PUBLIC', 'TEST', 'NAME')"); + conn.close(); + Recover.main("-dir", getBaseDir(), "-db", "recovery"); + DeleteDbFiles.execute(getBaseDir(), "recovery", true); + conn = getConnection( + "recovery;init=runscript from '" + + getBaseDir() + "/recovery.h2.sql'"); + conn.close(); + } + + + private void testCompressedAndUncompressed() throws SQLException { + DeleteDbFiles.execute(getBaseDir(), "recovery", true); + DeleteDbFiles.execute(getBaseDir(), "recovery2", true); + org.h2.Driver.load(); + Connection conn = getConnection("recovery"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, data clob)"); + stat.execute("insert into test values(1, space(10000))"); + stat.execute("insert into test values(2, space(10000))"); + conn.close(); + Recover rec = new Recover(); + rec.runTool("-dir", getBaseDir(), "-db", "recovery"); + Connection conn2 = getConnection("recovery2"); + Statement stat2 = conn2.createStatement(); + String name = "recovery.h2.sql"; + stat2.execute("runscript from '" + getBaseDir() + "/" + name + "'"); + stat2.execute("select * from test"); + conn2.close(); + + conn = getConnection("recovery"); + stat = conn.createStatement(); + conn2 = getConnection("recovery2"); + stat2 = conn2.createStatement(); + + assertEqualDatabases(stat, stat2); + conn.close(); + conn2.close(); + DeleteDbFiles.execute(getBaseDir(), "recovery", true); + DeleteDbFiles.execute(getBaseDir(), "recovery2", true); + } + + private void testRunScript() throws Exception { + DeleteDbFiles.execute(getBaseDir(), "recovery", true); + DeleteDbFiles.execute(getBaseDir(), "recovery2", true); + org.h2.Driver.load(); + Connection conn = getConnection("recovery"); + Statement stat = conn.createStatement(); + stat.execute("create table \"Joe\"\"s Table\" as " + + "select 1"); + stat.execute("create table test as " + + "select * from system_range(1, 100)"); + stat.execute("create view \"TEST VIEW OF TABLE TEST\" as " + + "select * from test"); + stat.execute("create table a(id int primary key) as " + + "select * from system_range(1, 100)"); + stat.execute("create table b(id int primary key references a(id)) as " + + "select * from system_range(1, 100)"); + stat.execute("create table lob(c clob, b blob) as " + + "select space(10000) || 'end', SECURE_RAND(10000)"); + stat.execute("create table d(d varchar) as " + + "select space(10000) || 'end'"); + stat.execute("alter table a add foreign key(id) references b(id)"); + // all rows have the same value - so that SCRIPT can't re-order the rows + stat.execute("create table e(id varchar) as " + + "select space(10) from system_range(1, 1000)"); + stat.execute("create index idx_e_id on e(id)"); + conn.close(); + + Recover rec = new Recover(); + ByteArrayOutputStream buff = new ByteArrayOutputStream(); + rec.setOut(new PrintStream(buff, false, "UTF-8")); + rec.runTool("-dir", getBaseDir(), "-db", "recovery", "-trace"); + String out = Utils10.byteArrayOutputStreamToString(buff, StandardCharsets.UTF_8); + assertContains(out, "Created file"); + + Connection conn2 = getConnection("recovery2"); + Statement stat2 = conn2.createStatement(); + String name = "recovery.h2.sql"; + + stat2.execute("runscript from '" + getBaseDir() + "/" + name + "'"); + stat2.execute("select * from test"); + conn2.close(); + + conn = getConnection("recovery"); + stat = conn.createStatement(); + conn2 = getConnection("recovery2"); + stat2 = conn2.createStatement(); + + assertEqualDatabases(stat, stat2); + conn.close(); + conn2.close(); + + Recover.execute(getBaseDir(), "recovery"); + + deleteDb("recovery"); + deleteDb("recovery2"); + FileUtils.delete(getBaseDir() + "/recovery.h2.sql"); + String dir = getBaseDir() + "/recovery.lobs.db"; + FileUtils.deleteRecursive(dir, false); + } + + private void testRunScript2() throws Exception { + DeleteDbFiles.execute(getBaseDir(), "recovery", true); + DeleteDbFiles.execute(getBaseDir(), "recovery2", true); + org.h2.Driver.load(); + Connection conn = getConnection("recovery"); + Statement stat = conn.createStatement(); + stat.execute("SET COLLATION EN"); + stat.execute("CREATE TABLE TEST(A VARCHAR)"); + conn.close(); + + final Recover recover = new Recover(); + final ByteArrayOutputStream buff = new ByteArrayOutputStream(); // capture the console output + recover.setOut(new PrintStream(buff, false, "UTF-8")); + recover.runTool("-dir", getBaseDir(), "-db", "recovery", "-trace"); + String consoleOut = Utils10.byteArrayOutputStreamToString(buff, StandardCharsets.UTF_8); + assertContains(consoleOut, "Created file"); + + Connection conn2 = getConnection("recovery2"); + Statement stat2 = conn2.createStatement(); + + stat2.execute("runscript from '" + getBaseDir() + "/recovery.h2.sql'"); + stat2.execute("select * from test"); + conn2.close(); + + conn = getConnection("recovery"); + stat = conn.createStatement(); + conn2 = getConnection("recovery2"); + stat2 = conn2.createStatement(); + assertEqualDatabases(stat, stat2); + conn.close(); + conn2.close(); + + deleteDb("recovery"); + deleteDb("recovery2"); + FileUtils.delete(getBaseDir() + "/recovery.h2.sql"); + String dir = getBaseDir() + "/recovery.lobs.db"; + FileUtils.deleteRecursive(dir, false); + } +} diff --git a/h2/src/test/org/h2/test/unit/TestReopen.java b/h2/src/test/org/h2/test/unit/TestReopen.java new file mode 100644 index 0000000..babf456 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestReopen.java @@ -0,0 +1,184 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.sql.SQLException; +import java.util.HashSet; +import java.util.concurrent.TimeUnit; + +import org.h2.api.ErrorCode; +import org.h2.engine.ConnectionInfo; +import org.h2.engine.Constants; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.message.DbException; +import org.h2.store.fs.FileUtils; +import org.h2.store.fs.Recorder; +import org.h2.store.fs.rec.FilePathRec; +import org.h2.test.TestBase; +import org.h2.tools.Recover; +import org.h2.util.IOUtils; +import org.h2.util.Profiler; +import org.h2.util.Utils; + +/** + * A test that calls another test, and after each write operation to the + * database file, it copies the file, and tries to reopen it. + */ +public class TestReopen extends TestBase implements Recorder { + + // TODO this is largely a copy of org.h2.util.RecoverTester + + private String testDatabase = "memFS:" + TestBase.BASE_TEST_DIR + "/reopen"; + private int writeCount = Utils.getProperty("h2.reopenOffset", 0); + private final int testEvery = 1 << Utils.getProperty("h2.reopenShift", 6); + private final long maxFileSize = Utils.getProperty("h2.reopenMaxFileSize", + Integer.MAX_VALUE) * 1024L * 1024; + private int verifyCount; + private final HashSet knownErrors = new HashSet<>(); + private volatile boolean testing; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + System.setProperty("h2.delayWrongPasswordMin", "0"); + FilePathRec.register(); + FilePathRec.setRecorder(this); + config.reopen = true; + + long time = System.nanoTime(); + Profiler p = new Profiler(); + p.startCollecting(); + new TestPageStoreCoverage().init(config).test(); + System.out.println(p.getTop(3)); + System.out.println(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - time)); + System.out.println("counter: " + writeCount); + } + + @Override + public void log(int op, String fileName, byte[] data, long x) { + if (op != Recorder.WRITE && op != Recorder.TRUNCATE) { + return; + } + if (!fileName.endsWith(Constants.SUFFIX_MV_FILE)) { + return; + } + if (testing) { + // avoid deadlocks + return; + } + testing = true; + try { + logDb(fileName); + } finally { + testing = false; + } + } + + private synchronized void logDb(String fileName) { + writeCount++; + if ((writeCount & (testEvery - 1)) != 0) { + return; + } + if (FileUtils.size(fileName) > maxFileSize) { + // System.out.println(fileName + " " + IOUtils.length(fileName)); + return; + } + System.out.println("+ write #" + writeCount + " verify #" + verifyCount); + + try { + IOUtils.copyFiles(fileName, testDatabase + + Constants.SUFFIX_MV_FILE); + verifyCount++; + // avoid using the Engine class to avoid deadlocks + String url = "jdbc:h2:" + testDatabase + + ";FILE_LOCK=NO;TRACE_LEVEL_FILE=0"; + ConnectionInfo ci = new ConnectionInfo(url, null, getUser(), getPassword()); + Database database = new Database(ci, null); + // close the database + SessionLocal session = database.getSystemSession(); + session.prepare("script to '" + testDatabase + ".sql'").query(0); + session.prepare("shutdown immediately").update(); + database.removeSession(null); + // everything OK - return + return; + } catch (DbException e) { + SQLException e2 = DbException.toSQLException(e); + int errorCode = e2.getErrorCode(); + if (errorCode == ErrorCode.WRONG_USER_OR_PASSWORD) { + return; + } else if (errorCode == ErrorCode.FILE_ENCRYPTION_ERROR_1) { + return; + } + e.printStackTrace(System.out); + throw e; + } catch (Exception e) { + // failed + int errorCode = 0; + if (e instanceof SQLException) { + errorCode = ((SQLException) e).getErrorCode(); + } + if (errorCode == ErrorCode.WRONG_USER_OR_PASSWORD) { + return; + } else if (errorCode == ErrorCode.FILE_ENCRYPTION_ERROR_1) { + return; + } + e.printStackTrace(System.out); + } + System.out.println( + "begin ------------------------------ " + writeCount); + try { + Recover.execute(fileName.substring(0, fileName.lastIndexOf('/')), null); + } catch (SQLException e) { + // ignore + } + testDatabase += "X"; + try { + IOUtils.copyFiles(fileName, testDatabase + + Constants.SUFFIX_MV_FILE); + // avoid using the Engine class to avoid deadlocks + String url = "jdbc:h2:" + testDatabase + ";FILE_LOCK=NO"; + ConnectionInfo ci = new ConnectionInfo(url, null, null, null); + Database database = new Database(ci, null); + // close the database + database.removeSession(null); + } catch (Exception e) { + int errorCode = 0; + if (e instanceof DbException) { + e = ((DbException) e).getSQLException(); + errorCode = ((SQLException) e).getErrorCode(); + } + if (errorCode == ErrorCode.WRONG_USER_OR_PASSWORD) { + return; + } else if (errorCode == ErrorCode.FILE_ENCRYPTION_ERROR_1) { + return; + } + StringBuilder buff = new StringBuilder(); + StackTraceElement[] list = e.getStackTrace(); + for (int i = 0; i < 10 && i < list.length; i++) { + buff.append(list[i].toString()).append('\n'); + } + String s = buff.toString(); + if (!knownErrors.contains(s)) { + System.out.println(writeCount + " code: " + errorCode + " " + + e.toString()); + e.printStackTrace(System.out); + knownErrors.add(s); + } else { + System.out.println(writeCount + " code: " + errorCode); + } + } + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestSampleApps.java b/h2/src/test/org/h2/test/unit/TestSampleApps.java new file mode 100644 index 0000000..2bcafae --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestSampleApps.java @@ -0,0 +1,154 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.PrintStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.tools.DeleteDbFiles; +import org.h2.util.IOUtils; +import org.h2.util.StringUtils; +import org.h2.util.Utils10; + +/** + * Tests the sample apps. + */ +public class TestSampleApps extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + if (!getBaseDir().startsWith(TestBase.BASE_TEST_DIR)) { + return false; + } + return true; + } + + @Override + public void test() throws Exception { + deleteDb(getTestName()); + InputStream in = getClass().getClassLoader().getResourceAsStream( + "org/h2/samples/optimizations.sql"); + new File(getBaseDir()).mkdirs(); + FileOutputStream out = new FileOutputStream(getBaseDir() + + "/optimizations.sql"); + IOUtils.copyAndClose(in, out); + String url = "jdbc:h2:" + getBaseDir() + "/" + getTestName(); + testApp("", org.h2.tools.RunScript.class, "-url", url, "-user", "sa", + "-password", "sa", "-script", getBaseDir() + + "/optimizations.sql", "-checkResults"); + deleteDb(getTestName()); + testApp("Compacting...\nDone.", org.h2.samples.Compact.class); + testApp("NAME: Bob Meier\n" + + "EMAIL: bob.meier@abcde.abc\n" + + "PHONE: +41123456789\n\n" + + "NAME: John Jones\n" + + "EMAIL: john.jones@abcde.abc\n" + + "PHONE: +41976543210\n", + org.h2.samples.CsvSample.class); + testApp("", + org.h2.samples.CachedPreparedStatements.class); + testApp("2 is prime\n" + + "3 is prime\n" + + "5 is prime\n" + + "7 is prime\n" + + "11 is prime\n" + + "13 is prime\n" + + "17 is prime\n" + + "19 is prime\n" + + "30\n" + + "20\n" + + "0/0\n" + + "0/1\n" + + "1/0\n" + + "1/1\n" + + "10", + org.h2.samples.Function.class); + // Not compatible with PostgreSQL JDBC driver (throws a + // NullPointerException): + // testApp(org.h2.samples.SecurePassword.class, null, "Joe"); + // TODO test ShowProgress (percent numbers are hardware specific) + // TODO test ShutdownServer (server needs to be started in a separate + // process) + testApp("The sum is 20.00", org.h2.samples.TriggerSample.class); + testApp("Hello: 1\nWorld: 2", org.h2.samples.TriggerPassData.class); + testApp("Key 1 was generated\n" + + "Key 2 was generated\n\n" + + "TEST_TABLE:\n" + + "1 Hallo\n\n" + + "TEST_VIEW:\n" + + "1 Hallo", + org.h2.samples.UpdatableView.class); + testApp( + "adding test data...\n" + + "defrag to reduce random access...\n" + + "create the zip file...\n" + + "open the database from the zip file...", + org.h2.samples.ReadOnlyDatabaseInZip.class); + testApp( + "a: 1/Hello!\n" + + "b: 1/Hallo!\n" + + "1/A/Hello!\n" + + "1/B/Hallo!", + org.h2.samples.RowAccessRights.class); + + // tools + testApp("Allows changing the database file encryption password or algorithm*", + org.h2.tools.ChangeFileEncryption.class, "-help"); + testApp("Deletes all files belonging to a database.*", + org.h2.tools.DeleteDbFiles.class, "-help"); + FileUtils.delete(getBaseDir() + "/optimizations.sql"); + } + + private void testApp(String expected, Class clazz, String... args) + throws Exception { + DeleteDbFiles.execute("data", "test", true); + Method m = clazz.getMethod("main", String[].class); + PrintStream oldOut = System.out, oldErr = System.err; + ByteArrayOutputStream buff = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(buff, false, "UTF-8"); + System.setOut(out); + System.setErr(out); + try { + m.invoke(null, new Object[] { args }); + } catch (InvocationTargetException e) { + TestBase.logError("error", e.getTargetException()); + } catch (Throwable e) { + TestBase.logError("error", e); + } + out.flush(); + System.setOut(oldOut); + System.setErr(oldErr); + String s = Utils10.byteArrayOutputStreamToString(buff, StandardCharsets.UTF_8); + s = StringUtils.replaceAll(s, "\r\n", "\n"); + s = s.trim(); + expected = expected.trim(); + if (expected.endsWith("*")) { + expected = expected.substring(0, expected.length() - 1); + if (!s.startsWith(expected)) { + assertEquals(expected.trim(), s.trim()); + } + } else { + assertEquals(expected.trim(), s.trim()); + } + } +} diff --git a/h2/src/test/org/h2/test/unit/TestScriptReader.java b/h2/src/test/org/h2/test/unit/TestScriptReader.java new file mode 100644 index 0000000..6c430e9 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestScriptReader.java @@ -0,0 +1,244 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.io.StringReader; +import java.util.Random; +import org.h2.test.TestBase; +import org.h2.util.ScriptReader; + +/** + * Tests the script reader tool that breaks up SQL scripts in statements. + */ +public class TestScriptReader extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() { + testCommon(); + testRandom(); + } + + private void testRandom() { + int len = getSize(1000, 10000); + Random random = new Random(10); + for (int i = 0; i < len; i++) { + int l = random.nextInt(10); + String[] sql = new String[l]; + StringBuilder buff = new StringBuilder(); + for (int j = 0; j < l; j++) { + sql[j] = randomStatement(random); + buff.append(sql[j]); + if (j < l - 1) { + buff.append(";"); + } + } + String s = buff.toString(); + StringReader reader = new StringReader(s); + try (ScriptReader source = new ScriptReader(reader)) { + for (int j = 0; j < l; j++) { + String e = source.readStatement(); + String c = sql[j]; + if (c.length() == 0 && j == l - 1) { + c = null; + } + assertEquals(c, e); + } + assertEquals(null, source.readStatement()); + } + } + } + + private static String randomStatement(Random random) { + StringBuilder buff = new StringBuilder(); + int len = random.nextInt(5); + for (int i = 0; i < len; i++) { + switch (random.nextInt(10)) { + case 0: { + int l = random.nextInt(4); + String[] ch = { "\n", "\r", " ", "*", "a", "0", "$ " }; + for (int j = 0; j < l; j++) { + buff.append(ch[random.nextInt(ch.length)]); + } + break; + } + case 1: { + buff.append('\''); + int l = random.nextInt(4); + String[] ch = { ";", "\n", "\r", "--", "//", "/", "-", "*", + "/*", "*/", "\"", "$ " }; + for (int j = 0; j < l; j++) { + buff.append(ch[random.nextInt(ch.length)]); + } + buff.append('\''); + break; + } + case 2: { + buff.append('"'); + int l = random.nextInt(4); + String[] ch = { ";", "\n", "\r", "--", "//", "/", "-", "*", + "/*", "*/", "\'", "$" }; + for (int j = 0; j < l; j++) { + buff.append(ch[random.nextInt(ch.length)]); + } + buff.append('"'); + break; + } + case 3: { + buff.append('-'); + if (random.nextBoolean()) { + String[] ch = { "\n", "\r", "*", "a", " ", "$ " }; + int l = 1 + random.nextInt(4); + for (int j = 0; j < l; j++) { + buff.append(ch[random.nextInt(ch.length)]); + } + } else { + buff.append('-'); + String[] ch = { ";", "-", "//", "/*", "*/", "a", "$" }; + int l = random.nextInt(4); + for (int j = 0; j < l; j++) { + buff.append(ch[random.nextInt(ch.length)]); + } + buff.append('\n'); + } + break; + } + case 4: { + buff.append('/'); + if (random.nextBoolean()) { + String[] ch = { "\n", "\r", "a", " ", "- ", "$ " }; + int l = 1 + random.nextInt(4); + for (int j = 0; j < l; j++) { + buff.append(ch[random.nextInt(ch.length)]); + } + } else { + buff.append('*'); + String[] ch = { ";", "-", "//", "/* ", "--", "\n", "\r", "a", "$" }; + int l = random.nextInt(4); + int comments = 0; + for (int j = 0; j < l; j++) { + String s = ch[random.nextInt(ch.length)]; + buff.append(s); + if (s.equals("/* ")) { + comments++; + } + } + while (comments-- >= 0) { + buff.append("*/"); + } + } + break; + } + case 5: { + if (buff.length() > 0) { + buff.append(" "); + } + buff.append("$"); + if (random.nextBoolean()) { + String[] ch = { "\n", "\r", "a", " ", "- ", "/ " }; + int l = 1 + random.nextInt(4); + for (int j = 0; j < l; j++) { + buff.append(ch[random.nextInt(ch.length)]); + } + } else { + buff.append("$"); + String[] ch = { ";", "-", "//", "/* ", "--", "\n", "\r", "a", "$ " }; + int l = random.nextInt(4); + for (int j = 0; j < l; j++) { + buff.append(ch[random.nextInt(ch.length)]); + } + buff.append("$$"); + } + break; + } + default: + } + } + return buff.toString(); + } + + private void testCommon() { + String s; + ScriptReader source; + + s = "$$;$$;"; + source = new ScriptReader(new StringReader(s)); + assertEquals("$$;$$", source.readStatement()); + assertEquals(null, source.readStatement()); + source.close(); + + s = "a;';';\";\";--;\n;/*;\n*/;//;\na;"; + source = new ScriptReader(new StringReader(s)); + assertEquals("a", source.readStatement()); + assertEquals("';'", source.readStatement()); + assertEquals("\";\"", source.readStatement()); + assertEquals("--;\n", source.readStatement()); + assertEquals("/*;\n*/", source.readStatement()); + assertEquals("//;\na", source.readStatement()); + assertEquals(null, source.readStatement()); + source.close(); + + s = "/\n$ \n\n $';$$a$$ $\n;'"; + source = new ScriptReader(new StringReader(s)); + assertEquals("/\n$ \n\n $';$$a$$ $\n;'", source.readStatement()); + assertEquals(null, source.readStatement()); + source.close(); + + s = "//"; + source = new ScriptReader(new StringReader(s)); + assertEquals("//", source.readStatement()); + assertTrue(source.isInsideRemark()); + assertFalse(source.isBlockRemark()); + source.close(); + + // check handling of unclosed block comments + s = "/*xxx"; + source = new ScriptReader(new StringReader(s)); + assertEquals("/*xxx", source.readStatement()); + assertTrue(source.isBlockRemark()); + source.close(); + + s = "/*xxx*"; + source = new ScriptReader(new StringReader(s)); + assertEquals("/*xxx*", source.readStatement()); + assertTrue(source.isBlockRemark()); + source.close(); + + s = "/*xxx* "; + source = new ScriptReader(new StringReader(s)); + assertEquals("/*xxx* ", source.readStatement()); + assertTrue(source.isBlockRemark()); + source.close(); + + s = "/*xxx/"; + source = new ScriptReader(new StringReader(s)); + assertEquals("/*xxx/", source.readStatement()); + assertTrue(source.isBlockRemark()); + source.close(); + + // nested comments + s = "/*/**/SCRIPT;*/"; + source = new ScriptReader(new StringReader(s)); + assertEquals("/*/**/SCRIPT;*/", source.readStatement()); + assertTrue(source.isBlockRemark()); + source.close(); + + s = "/* /* */ SCRIPT; */"; + source = new ScriptReader(new StringReader(s)); + assertEquals("/* /* */ SCRIPT; */", source.readStatement()); + assertTrue(source.isBlockRemark()); + source.close(); + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestSecurity.java b/h2/src/test/org/h2/test/unit/TestSecurity.java new file mode 100644 index 0000000..7f3c970 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestSecurity.java @@ -0,0 +1,333 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Random; + +import org.h2.security.BlockCipher; +import org.h2.security.CipherFactory; +import org.h2.security.SHA256; +import org.h2.security.SHA3; +import org.h2.test.TestBase; +import org.h2.util.StringUtils; + +/** + * Tests various security primitives. + */ +public class TestSecurity extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + testConnectWithHash(); + testSHA(); + testSHA3(); + testAES(); + testBlockCiphers(); + testRemoveAnonFromLegacyAlgorithms(); + // testResetLegacyAlgorithms(); + } + + private static void testConnectWithHash() throws SQLException { + Connection conn = DriverManager.getConnection( + "jdbc:h2:mem:test", "sa", "sa"); + String pwd = StringUtils.convertBytesToHex( + SHA256.getKeyPasswordHash("SA", "sa".toCharArray())); + Connection conn2 = DriverManager.getConnection( + "jdbc:h2:mem:test;PASSWORD_HASH=TRUE", "sa", pwd); + conn.close(); + conn2.close(); + } + + private void testSHA() { + testPBKDF2(); + testHMAC(); + testOneSHA(); + } + + private void testPBKDF2() { + // test vectors from StackOverflow (PBKDF2-HMAC-SHA2) + assertEquals( + "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b", + StringUtils.convertBytesToHex( + SHA256.getPBKDF2( + "password".getBytes(), + "salt".getBytes(), 1, 32))); + assertEquals( + "ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43", + StringUtils.convertBytesToHex( + SHA256.getPBKDF2( + "password".getBytes(), + "salt".getBytes(), 2, 32))); + assertEquals( + "c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a", + StringUtils.convertBytesToHex( + SHA256.getPBKDF2( + "password".getBytes(), + "salt".getBytes(), 4096, 32))); + // take a very long time to calculate + // assertEquals( + // "cf81c66fe8cfc04d1f31ecb65dab4089f7f179e" + + // "89b3b0bcb17ad10e3ac6eba46", + // StringUtils.convertBytesToHex( + // SHA256.getPBKDF2( + // "password".getBytes(), + // "salt".getBytes(), 16777216, 32))); + assertEquals( + "348c89dbcbd32b2f32d814b8116e84cf2b17347e" + + "bc1800181c4e2a1fb8dd53e1c635518c7dac47e9", + StringUtils.convertBytesToHex( + SHA256.getPBKDF2( + ("password" + "PASSWORD" + "password").getBytes(), + ("salt"+ "SALT"+ "salt"+ "SALT"+ "salt"+ + "SALT"+ "salt"+ "SALT"+ "salt").getBytes(), 4096, 40))); + assertEquals( + "89b69d0516f829893c696226650a8687", + StringUtils.convertBytesToHex( + SHA256.getPBKDF2( + "pass\0word".getBytes(), + "sa\0lt".getBytes(), 4096, 16))); + + // the password is filled with zeroes + byte[] password = "Test".getBytes(); + SHA256.getPBKDF2(password, "".getBytes(), 1, 16); + assertEquals(new byte[4], password); + } + + private void testHMAC() { + // from Wikipedia + assertEquals( + "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad", + StringUtils.convertBytesToHex( + SHA256.getHMAC(new byte[0], new byte[0]))); + assertEquals( + "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8", + StringUtils.convertBytesToHex( + SHA256.getHMAC( + "key".getBytes(), + "The quick brown fox jumps over the lazy dog".getBytes()))); + } + + private String getHashString(byte[] data) { + byte[] result = SHA256.getHash(data, true); + if (data.length > 0) { + assertEquals(0, data[0]); + } + return StringUtils.convertBytesToHex(result); + } + + private void testOneSHA() { + assertEquals( + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + getHashString(new byte[] {})); + assertEquals( + "68aa2e2ee5dff96e3355e6c7ee373e3d6a4e17f75f9518d843709c0c9bc3e3d4", + getHashString(new byte[] { 0x19 })); + assertEquals( + "175ee69b02ba9b58e2b0a5fd13819cea573f3940a94f825128cf4209beabb4e8", + getHashString( + new byte[] { (byte) 0xe3, (byte) 0xd7, 0x25, + 0x70, (byte) 0xdc, (byte) 0xdd, 0x78, 0x7c, + (byte) 0xe3, (byte) 0x88, 0x7a, (byte) 0xb2, + (byte) 0xcd, 0x68, 0x46, 0x52 })); + checkSHA256( + "", + "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"); + checkSHA256( + "a", + "CA978112CA1BBDCAFAC231B39A23DC4DA786EFF8147C4E72B9807785AFEE48BB"); + checkSHA256( + "abc", + "BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD"); + checkSHA256( + "message digest", + "F7846F55CF23E14EEBEAB5B4E1550CAD5B509E3348FBC4EFA3A1413D393CB650"); + checkSHA256( + "abcdefghijklmnopqrstuvwxyz", + "71C480DF93D6AE2F1EFAD1447C66C9525E316218CF51FC8D9ED832F2DAF18B73"); + checkSHA256( + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "248D6A61D20638B8E5C026930C3E6039A33CE45964FF2167F6ECEDD419DB06C1"); + checkSHA256( + "123456789012345678901234567890" + + "12345678901234567890" + + "123456789012345678901234567890", + "F371BC4A311F2B009EEF952DD83CA80E2B60026C8E935592D0F9C308453C813E"); + StringBuilder buff = new StringBuilder(1000000); + buff.append('a'); + checkSHA256(buff.toString(), + "CA978112CA1BBDCAFAC231B39A23DC4DA786EFF8147C4E72B9807785AFEE48BB"); + } + + private void checkSHA256(String message, String expected) { + String hash = StringUtils.convertBytesToHex( + SHA256.getHash(message.getBytes(), true)).toUpperCase(); + assertEquals(expected, hash); + } + + private void testSHA3() { + try { + MessageDigest md = MessageDigest.getInstance("SHA3-224"); + Random r = new Random(); + byte[] bytes1 = new byte[r.nextInt(1025)]; + byte[] bytes2 = new byte[256]; + r.nextBytes(bytes1); + r.nextBytes(bytes2); + testSHA3(md, SHA3.getSha3_224(), bytes1, bytes2); + testSHA3(MessageDigest.getInstance("SHA3-256"), SHA3.getSha3_256(), bytes1, bytes2); + testSHA3(MessageDigest.getInstance("SHA3-384"), SHA3.getSha3_384(), bytes1, bytes2); + testSHA3(MessageDigest.getInstance("SHA3-512"), SHA3.getSha3_512(), bytes1, bytes2); + } catch (NoSuchAlgorithmException e) { + // Java 8 doesn't support SHA-3 + } + } + + private void testSHA3(MessageDigest md1, SHA3 md2, byte[] bytes1, byte[] bytes2) { + md1.update(bytes1); + md2.update(bytes1); + md1.update(bytes2, 0, 1); + md2.update(bytes2, 0, 1); + md1.update(bytes2, 1, 33); + md2.update(bytes2, 1, 33); + md1.update(bytes2, 34, 222); + md2.update(bytes2, 34, 222); + assertEquals(md1.digest(), md2.digest()); + md1.update(bytes2, 1, 1); + md2.update(bytes2, 1, 1); + assertEquals(md1.digest(), md2.digest()); + } + + private void testBlockCiphers() { + for (String algorithm : new String[] { "AES", "FOG" }) { + byte[] test = new byte[4096]; + BlockCipher cipher = CipherFactory.getBlockCipher(algorithm); + cipher.setKey("abcdefghijklmnop".getBytes()); + for (int i = 0; i < 10; i++) { + cipher.encrypt(test, 0, test.length); + } + assertFalse(isCompressible(test)); + for (int i = 0; i < 10; i++) { + cipher.decrypt(test, 0, test.length); + } + assertEquals(new byte[test.length], test); + assertTrue(isCompressible(test)); + } + } + + private void testAES() { + BlockCipher test = CipherFactory.getBlockCipher("AES"); + + String r; + byte[] data; + + // test vector from + // http://csrc.nist.gov/groups/STM/cavp/documents/aes/KAT_AES.zip + // ECBVarTxt128e.txt + // COUNT = 0 + test.setKey(StringUtils.convertHexToBytes("00000000000000000000000000000000")); + data = StringUtils.convertHexToBytes("80000000000000000000000000000000"); + test.encrypt(data, 0, data.length); + r = StringUtils.convertBytesToHex(data); + assertEquals("3ad78e726c1ec02b7ebfe92b23d9ec34", r); + + // COUNT = 127 + test.setKey(StringUtils.convertHexToBytes("00000000000000000000000000000000")); + data = StringUtils.convertHexToBytes("ffffffffffffffffffffffffffffffff"); + test.encrypt(data, 0, data.length); + r = StringUtils.convertBytesToHex(data); + assertEquals("3f5b8cc9ea855a0afa7347d23e8d664e", r); + + // test vector + test.setKey(StringUtils.convertHexToBytes("2b7e151628aed2a6abf7158809cf4f3c")); + data = StringUtils.convertHexToBytes("6bc1bee22e409f96e93d7e117393172a"); + test.encrypt(data, 0, data.length); + r = StringUtils.convertBytesToHex(data); + assertEquals("3ad77bb40d7a3660a89ecaf32466ef97", r); + + test.setKey(StringUtils.convertHexToBytes("000102030405060708090A0B0C0D0E0F")); + byte[] in = new byte[128]; + byte[] enc = new byte[128]; + test.encrypt(enc, 0, 128); + test.decrypt(enc, 0, 128); + if (!Arrays.equals(in, enc)) { + throw new AssertionError(); + } + + for (int i = 0; i < 10; i++) { + test.encrypt(in, 0, 128); + test.decrypt(enc, 0, 128); + } + } + + private static boolean isCompressible(byte[] data) { + int len = data.length; + int[] sum = new int[16]; + for (int i = 0; i < len; i++) { + int x = (data[i] & 255) >> 4; + sum[x]++; + } + int r = 0; + for (int x : sum) { + long v = ((long) x << 32) / len; + r += 63 - Long.numberOfLeadingZeros(v + 1); + } + return len * r < len * 120; + } + + private void testRemoveAnonFromLegacyAlgorithms() { + String legacyAlgorithms = "K_NULL, C_NULL, M_NULL, DHE_DSS_EXPORT" + + ", DHE_RSA_EXPORT, DH_anon_EXPORT, DH_DSS_EXPORT, DH_RSA_EXPORT, RSA_EXPORT" + + ", DH_anon, ECDH_anon, RC4_128, RC4_40, DES_CBC, DES40_CBC"; + String expectedLegacyWithoutDhAnon = "K_NULL, C_NULL, M_NULL, DHE_DSS_EXPORT" + + ", DHE_RSA_EXPORT, DH_anon_EXPORT, DH_DSS_EXPORT, DH_RSA_EXPORT, RSA_EXPORT" + + ", RC4_128, RC4_40, DES_CBC, DES40_CBC"; + assertEquals(expectedLegacyWithoutDhAnon, + CipherFactory.removeDhAnonFromCommaSeparatedList(legacyAlgorithms)); + + legacyAlgorithms = "ECDH_anon, DH_anon_EXPORT, DH_anon"; + expectedLegacyWithoutDhAnon = "DH_anon_EXPORT"; + assertEquals(expectedLegacyWithoutDhAnon, + CipherFactory.removeDhAnonFromCommaSeparatedList(legacyAlgorithms)); + + legacyAlgorithms = null; + assertNull(CipherFactory.removeDhAnonFromCommaSeparatedList(legacyAlgorithms)); + } + + /** + * This test is meaningful when run in isolation. However, tests of server + * sockets or ssl connections may modify the global state given by the + * jdk.tls.legacyAlgorithms security property (for a good reason). + * It is best to avoid running it in test suites, as it could itself lead + * to a modification of the global state with hard-to-track consequences. + */ + @SuppressWarnings("unused") + private void testResetLegacyAlgorithms() { + String legacyAlgorithmsBefore = CipherFactory.getLegacyAlgorithmsSilently(); + assertEquals("Failed assumption: jdk.tls.legacyAlgorithms" + + " has been modified from its initial setting", + CipherFactory.DEFAULT_LEGACY_ALGORITHMS, legacyAlgorithmsBefore); + CipherFactory.removeAnonFromLegacyAlgorithms(); + CipherFactory.resetDefaultLegacyAlgorithms(); + String legacyAlgorithmsAfter = CipherFactory.getLegacyAlgorithmsSilently(); + assertEquals(CipherFactory.DEFAULT_LEGACY_ALGORITHMS, legacyAlgorithmsAfter); + } + + +} diff --git a/h2/src/test/org/h2/test/unit/TestServlet.java b/h2/src/test/org/h2/test/unit/TestServlet.java new file mode 100644 index 0000000..8dd911c --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestServlet.java @@ -0,0 +1,437 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.io.InputStream; +import java.net.URL; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Enumeration; +import java.util.EventListener; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import javax.servlet.Filter; +import javax.servlet.FilterRegistration; +import javax.servlet.FilterRegistration.Dynamic; +import javax.servlet.RequestDispatcher; +import javax.servlet.Servlet; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletException; +import javax.servlet.ServletRegistration; +import javax.servlet.SessionCookieConfig; +import javax.servlet.SessionTrackingMode; +import javax.servlet.descriptor.JspConfigDescriptor; +import org.h2.api.ErrorCode; +import org.h2.server.web.DbStarter; +import org.h2.test.TestBase; +import org.h2.test.TestDb; + +/** + * Tests the DbStarter servlet. + * This test simulates a minimum servlet container environment. + */ +public class TestServlet extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + /** + * Minimum ServletContext implementation. + * Most methods are not implemented. + */ + static class TestServletContext implements ServletContext { + + private final Properties initParams = new Properties(); + private final HashMap attributes = new HashMap<>(); + + @Override + public void setAttribute(String key, Object value) { + attributes.put(key, value); + } + + @Override + public Object getAttribute(String key) { + return attributes.get(key); + } + + @Override + public boolean setInitParameter(String key, String value) { + initParams.setProperty(key, value); + return true; + } + + @Override + public String getInitParameter(String key) { + return initParams.getProperty(key); + } + + @Override + public Enumeration getAttributeNames() { + throw new UnsupportedOperationException(); + } + + @Override + public ServletContext getContext(String string) { + throw new UnsupportedOperationException(); + } + + @Override + public Enumeration getInitParameterNames() { + throw new UnsupportedOperationException(); + } + + @Override + public int getMajorVersion() { + throw new UnsupportedOperationException(); + } + + @Override + public String getMimeType(String string) { + throw new UnsupportedOperationException(); + } + + @Override + public int getMinorVersion() { + throw new UnsupportedOperationException(); + } + + @Override + public RequestDispatcher getNamedDispatcher(String string) { + throw new UnsupportedOperationException(); + } + + @Override + public String getRealPath(String string) { + throw new UnsupportedOperationException(); + } + + @Override + public RequestDispatcher getRequestDispatcher(String string) { + throw new UnsupportedOperationException(); + } + + @Override + public URL getResource(String string) { + throw new UnsupportedOperationException(); + } + + @Override + public InputStream getResourceAsStream(String string) { + throw new UnsupportedOperationException(); + } + + @Override + public Set getResourcePaths(String string) { + throw new UnsupportedOperationException(); + } + + @Override + public String getServerInfo() { + throw new UnsupportedOperationException(); + } + + /** + * @deprecated as of servlet API 2.1 + */ + @Override + @Deprecated + public Servlet getServlet(String string) { + throw new UnsupportedOperationException(); + } + + @Override + public String getServletContextName() { + throw new UnsupportedOperationException(); + } + + /** + * @deprecated as of servlet API 2.1 + */ + @Deprecated + @Override + public Enumeration getServletNames() { + throw new UnsupportedOperationException(); + } + + /** + * @deprecated as of servlet API 2.0 + */ + @Deprecated + @Override + public Enumeration getServlets() { + throw new UnsupportedOperationException(); + } + + @Override + public void log(String string) { + throw new UnsupportedOperationException(); + } + + /** + * @deprecated as of servlet API 2.1 + */ + @Deprecated + @Override + public void log(Exception exception, String string) { + throw new UnsupportedOperationException(); + } + + @Override + public void log(String string, Throwable throwable) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeAttribute(String string) { + throw new UnsupportedOperationException(); + } + + @Override + public Dynamic addFilter(String arg0, String arg1) { + throw new UnsupportedOperationException(); + } + + @Override + public Dynamic addFilter(String arg0, Filter arg1) { + throw new UnsupportedOperationException(); + } + + @Override + public Dynamic addFilter(String arg0, Class arg1) { + throw new UnsupportedOperationException(); + } + + @Override + public void addListener(String arg0) { + throw new UnsupportedOperationException(); + } + + @Override + public void addListener(T arg0) { + throw new UnsupportedOperationException(); + } + + @Override + public void addListener(Class arg0) { + throw new UnsupportedOperationException(); + } + + @Override + public javax.servlet.ServletRegistration.Dynamic addServlet( + String arg0, String arg1) { + throw new UnsupportedOperationException(); + } + + @Override + public javax.servlet.ServletRegistration.Dynamic addServlet( + String arg0, Servlet arg1) { + throw new UnsupportedOperationException(); + } + + @Override + public javax.servlet.ServletRegistration.Dynamic addServlet( + String arg0, Class arg1) { + throw new UnsupportedOperationException(); + } + + @Override + public T createFilter(Class arg0) + throws ServletException { + throw new UnsupportedOperationException(); + } + + @Override + public T createListener(Class arg0) + throws ServletException { + throw new UnsupportedOperationException(); + } + + @Override + public T createServlet(Class arg0) + throws ServletException { + throw new UnsupportedOperationException(); + } + + @Override + public void declareRoles(String... arg0) { + throw new UnsupportedOperationException(); + } + + @Override + public ClassLoader getClassLoader() { + throw new UnsupportedOperationException(); + } + + @Override + public String getContextPath() { + throw new UnsupportedOperationException(); + } + + @Override + public Set getDefaultSessionTrackingModes() { + throw new UnsupportedOperationException(); + } + + @Override + public int getEffectiveMajorVersion() { + throw new UnsupportedOperationException(); + } + + @Override + public int getEffectiveMinorVersion() { + throw new UnsupportedOperationException(); + } + + @Override + public Set getEffectiveSessionTrackingModes() { + throw new UnsupportedOperationException(); + } + + @Override + public FilterRegistration getFilterRegistration(String arg0) { + throw new UnsupportedOperationException(); + } + + @Override + public Map getFilterRegistrations() { + throw new UnsupportedOperationException(); + } + + @Override + public JspConfigDescriptor getJspConfigDescriptor() { + throw new UnsupportedOperationException(); + } + + @Override + public ServletRegistration getServletRegistration(String arg0) { + throw new UnsupportedOperationException(); + } + + @Override + public Map getServletRegistrations() { + throw new UnsupportedOperationException(); + } + + @Override + public SessionCookieConfig getSessionCookieConfig() { + throw new UnsupportedOperationException(); + } + + + @Override + public void setSessionTrackingModes(Set arg0) { + throw new UnsupportedOperationException(); + } + + @Override + public String getVirtualServerName() { + throw new UnsupportedOperationException(); + } + + @Override + public ServletRegistration.Dynamic addJspFile(String servletName, String jspFile) { + throw new UnsupportedOperationException(); + } + + @Override + public int getSessionTimeout() { + throw new UnsupportedOperationException(); + } + + @Override + public void setSessionTimeout(int sessionTimeout) { + throw new UnsupportedOperationException(); + } + + @Override + public String getRequestCharacterEncoding() { + throw new UnsupportedOperationException(); + } + + @Override + public void setRequestCharacterEncoding(String encoding) { + throw new UnsupportedOperationException(); + } + + @Override + public String getResponseCharacterEncoding() { + throw new UnsupportedOperationException(); + } + + @Override + public void setResponseCharacterEncoding(String encoding) { + throw new UnsupportedOperationException(); + } + + } + + @Override + public boolean isEnabled() { + if (config.networked || config.memory) { + return false; + } + return true; + } + + @Override + public void test() throws SQLException { + DbStarter listener = new DbStarter(); + + TestServletContext context = new TestServletContext(); + String url = getURL("servlet", true); + context.setInitParameter("db.url", url); + context.setInitParameter("db.user", getUser()); + context.setInitParameter("db.password", getPassword()); + context.setInitParameter("db.tcpServer", "-tcpPort 8888"); + + ServletContextEvent event = new ServletContextEvent(context); + listener.contextInitialized(event); + + Connection conn1 = listener.getConnection(); + Connection conn1a = (Connection) context.getAttribute("connection"); + assertTrue(conn1 == conn1a); + Statement stat1 = conn1.createStatement(); + stat1.execute("CREATE TABLE T(ID INT)"); + + String u2 = url.substring(url.indexOf("servlet")); + u2 = "jdbc:h2:tcp://localhost:8888/" + getBaseDir() + "/" + u2; + Connection conn2 = DriverManager.getConnection( + u2, getUser(), getPassword()); + Statement stat2 = conn2.createStatement(); + stat2.execute("SELECT * FROM T"); + stat2.execute("DROP TABLE T"); + + assertThrows(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_DATABASE_EMPTY_1, stat1). + execute("SELECT * FROM T"); + conn2.close(); + + listener.contextDestroyed(event); + + // listener must be stopped + assertThrows(ErrorCode.CONNECTION_BROKEN_1, + () -> getConnection("jdbc:h2:tcp://localhost:8888/" + getBaseDir() + "/servlet", getUser(), + getPassword())); + + // connection must be closed + assertThrows(ErrorCode.OBJECT_CLOSED, stat1). + execute("SELECT * FROM DUAL"); + + deleteDb("servlet"); + + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestShell.java b/h2/src/test/org/h2/test/unit/TestShell.java new file mode 100644 index 0000000..36d9373 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestShell.java @@ -0,0 +1,241 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import org.h2.test.TestBase; +import org.h2.tools.Shell; +import org.h2.util.Task; +import org.h2.util.Utils10; + +/** + * Test the shell tool. + */ +public class TestShell extends TestBase { + + /** + * The output stream of the tool. + */ + PrintStream toolOut; + + /** + * The input stream of the tool. + */ + InputStream toolIn; + + private LineNumberReader lineReader; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + Shell shell = new Shell(); + ByteArrayOutputStream buff = new ByteArrayOutputStream(); + shell.setOut(new PrintStream(buff, false, "UTF-8")); + shell.runTool("-url", "jdbc:h2:mem:", "-driver", "org.h2.Driver", + "-user", "sa", "-password", "sa", "-properties", "null", + "-sql", "select 'Hello ' || 'World' as hi"); + String s = Utils10.byteArrayOutputStreamToString(buff, StandardCharsets.UTF_8); + assertContains(s, "HI"); + assertContains(s, "Hello World"); + assertContains(s, "(1 row, "); + + shell = new Shell(); + buff = new ByteArrayOutputStream(); + shell.setOut(new PrintStream(buff, false, "UTF-8")); + shell.runTool("-help"); + s = Utils10.byteArrayOutputStreamToString(buff, StandardCharsets.UTF_8); + assertContains(s, + "Interactive command line tool to access a database using JDBC."); + + test(true); + test(false); + } + + private void test(final boolean commandLineArgs) throws IOException { + PipedInputStream testIn = new PipedInputStream(); + PipedOutputStream out = new PipedOutputStream(testIn); + toolOut = new PrintStream(out, true); + out = new PipedOutputStream(); + PrintStream testOut = new PrintStream(out, true); + toolIn = new PipedInputStream(out); + Task task = new Task() { + @Override + public void call() throws Exception { + try { + Shell shell = new Shell(); + shell.setIn(toolIn); + shell.setOut(toolOut); + shell.setErr(toolOut); + if (commandLineArgs) { + shell.runTool("-url", "jdbc:h2:mem:", + "-user", "sa", "-password", "sa"); + } else { + shell.runTool(); + } + } finally { + toolOut.close(); + } + } + }; + task.execute(); + InputStreamReader reader = new InputStreamReader(testIn); + lineReader = new LineNumberReader(reader); + read(""); + read("Welcome to H2 Shell"); + read("Exit with"); + if (!commandLineArgs) { + read("[Enter]"); + testOut.println("jdbc:h2:mem:"); + read("URL"); + testOut.println(""); + read("Driver"); + testOut.println("sa"); + testOut.println("sa"); + testOut.println("sa"); + read("User"); + read("Password"); + } + read("Commands are case insensitive"); + read("help or ?"); + read("list"); + read("maxwidth"); + read("autocommit"); + read("history"); + read("quit or exit"); + read(""); + testOut.println("history"); + read("sql> No history"); + testOut.println("1"); + read("sql> Not found"); + testOut.println("select 1 a;"); + read("sql> A"); + read("1"); + read("(1 row,"); + testOut.println("history"); + read("sql> #1: select 1 a"); + read("To re-run a statement, type the number and press and enter"); + testOut.println("1"); + read("sql> select 1 a"); + read("A"); + read("1"); + read("(1 row,"); + + testOut.println("select 'x' || space(1000) large, 'y' small;"); + read("sql> LARGE"); + read("x"); + read("(data is partially truncated)"); + read("(1 row,"); + + testOut.println("select x, 's' s from system_range(0, 10001);"); + read("sql> X | S"); + for (int i = 0; i < 10000; i++) { + read((i + " ").substring(0, 4) + " | s"); + } + for (int i = 10000; i <= 10001; i++) { + read((i + " ").substring(0, 5) + " | s"); + } + read("(10002 rows,"); + + testOut.println("select error;"); + read("sql> Error:"); + if (read("").startsWith("Column \"ERROR\" not found")) { + read(""); + } + testOut.println("create table test(id int primary key, name varchar)\n;"); + read("sql> ...>"); + testOut.println("insert into test values(1, 'Hello');"); + read("sql>"); + testOut.println("select null n, * from test;"); + read("sql> N | ID | NAME"); + read("null | 1 | Hello"); + read("(1 row,"); + + // test history + for (int i = 0; i < 30; i++) { + testOut.println("select " + i + " ID from test;"); + read("sql> ID"); + read("" + i); + read("(1 row,"); + } + testOut.println("20"); + read("sql> select 10 ID from test"); + read("ID"); + read("10"); + read("(1 row,"); + + testOut.println("maxwidth"); + read("sql> Usage: maxwidth "); + read("Maximum column width is now 100"); + testOut.println("maxwidth 80"); + read("sql> Maximum column width is now 80"); + testOut.println("autocommit"); + read("sql> Usage: autocommit [true|false]"); + read("Autocommit is now true"); + testOut.println("autocommit false"); + read("sql> Autocommit is now false"); + testOut.println("autocommit true"); + read("sql> Autocommit is now true"); + testOut.println("list"); + read("sql> Result list mode is now on"); + + testOut.println("select 1 first, 2 `second`;"); + read("sql> FIRST : 1"); + read("SECOND: 2"); + read("(1 row, "); + + testOut.println("select x from system_range(1, 3);"); + read("sql> X: 1"); + read(""); + read("X: 2"); + read(""); + read("X: 3"); + read("(3 rows, "); + + testOut.println("select x, 2 as y from system_range(1, 3) where 1 = 0;"); + read("sql> X"); + read("Y"); + read("(0 rows, "); + + testOut.println("list"); + read("sql> Result list mode is now off"); + testOut.println("help"); + read("sql> Commands are case insensitive"); + read("help or ?"); + read("list"); + read("maxwidth"); + read("autocommit"); + read("history"); + read("quit or exit"); + read(""); + testOut.println("exit"); + read("sql>"); + task.get(); + } + + private String read(String expectedStart) throws IOException { + String line = lineReader.readLine(); + // System.out.println(": " + line); + assertStartsWith(line, expectedStart); + return line; + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestSort.java b/h2/src/test/org/h2/test/unit/TestSort.java new file mode 100644 index 0000000..ab7efe8 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestSort.java @@ -0,0 +1,163 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.Random; +import java.util.concurrent.atomic.AtomicInteger; +import org.h2.dev.sort.InPlaceStableMergeSort; +import org.h2.dev.sort.InPlaceStableQuicksort; +import org.h2.test.TestBase; + +/** + * Tests the stable in-place sorting implementations. + */ +public class TestSort extends TestBase { + + /** + * The number of times the compare method was called. + */ + private AtomicInteger compareCount = new AtomicInteger(); + + /** + * The comparison object used in this test. + */ + Comparator comp = (o1, o2) -> { + compareCount.incrementAndGet(); + return Long.compare(o1 >> 32, o2 >> 32); + }; + + private final Long[] array = new Long[100000]; + private Class clazz; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + test(InPlaceStableMergeSort.class); + test(InPlaceStableQuicksort.class); + test(Arrays.class); + } + + private void test(Class c) throws Exception { + this.clazz = c; + ordered(array); + shuffle(array); + stabilize(array); + test("random"); + ordered(array); + stabilize(array); + test("ordered"); + ordered(array); + reverse(array); + stabilize(array); + test("reverse"); + ordered(array); + stretch(array); + shuffle(array); + stabilize(array); + test("few random"); + ordered(array); + stretch(array); + stabilize(array); + test("few ordered"); + ordered(array); + reverse(array); + stretch(array); + stabilize(array); + test("few reverse"); + // System.out.println(); + } + + /** + * Sort the array and verify the result. + * + * @param type the type of data + */ + private void test(@SuppressWarnings("unused") String type) throws Exception { + compareCount.set(0); + + // long t = System.nanoTime(); + + clazz.getMethod("sort", Object[].class, Comparator.class).invoke(null, + array, comp); + + // System.out.printf( + // "%4d ms; %10d comparisons order: %s data: %s\n", + // TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t), + // compareCount.get(), clazz, type); + + verify(array); + + } + + private static void verify(Long[] array) { + long last = Long.MIN_VALUE; + int len = array.length; + for (int i = 0; i < len; i++) { + long x = array[i]; + long x1 = x >> 32, x2 = x - (x1 << 32); + long last1 = last >> 32, last2 = last - (last1 << 32); + if (x1 < last1) { + if (array.length < 1000) { + System.out.println(Arrays.toString(array)); + } + throw new RuntimeException("" + x); + } else if (x1 == last1 && x2 < last2) { + if (array.length < 1000) { + System.out.println(Arrays.toString(array)); + } + throw new RuntimeException("" + x); + } + last = x; + } + } + + private static void ordered(Long[] array) { + for (int i = 0; i < array.length; i++) { + array[i] = (long) i; + } + } + + private static void stretch(Long[] array) { + for (int i = array.length - 1; i >= 0; i--) { + array[i] = array[i / 4]; + } + } + + private static void reverse(Long[] array) { + for (int i = 0; i < array.length / 2; i++) { + long temp = array[i]; + array[i] = array[array.length - i - 1]; + array[array.length - i - 1] = temp; + } + } + + private static void shuffle(Long[] array) { + Random r = new Random(1); + for (int i = 0; i < array.length; i++) { + long temp = array[i]; + int j = r.nextInt(array.length); + array[j] = array[i]; + array[i] = temp; + } + } + + private static void stabilize(Long[] array) { + for (int i = 0; i < array.length; i++) { + array[i] = (array[i] << 32) + i; + } + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestStreams.java b/h2/src/test/org/h2/test/unit/TestStreams.java new file mode 100644 index 0000000..73a3c7c --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestStreams.java @@ -0,0 +1,121 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Random; +import org.h2.compress.LZFInputStream; +import org.h2.compress.LZFOutputStream; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; + +/** + * Tests the LZF stream. + */ +public class TestStreams extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws IOException { + testLZFStreams(); + testLZFStreamClose(); + } + + private static byte[] getRandomBytes(Random random) { + int[] sizes = { 0, 1, random.nextInt(1000), random.nextInt(100000), + random.nextInt(1000000) }; + int size = sizes[random.nextInt(sizes.length)]; + byte[] buffer = new byte[size]; + if (random.nextInt(5) == 1) { + random.nextBytes(buffer); + } else if (random.nextBoolean()) { + int patternLen = random.nextInt(100) + 1; + for (int j = 0; j < size; j++) { + buffer[j] = (byte) (j % patternLen); + } + } + return buffer; + } + + private void testLZFStreamClose() throws IOException { + String fileName = getBaseDir() + "/temp"; + FileUtils.createDirectories(FileUtils.getParent(fileName)); + OutputStream fo = FileUtils.newOutputStream(fileName, false); + LZFOutputStream out = new LZFOutputStream(fo); + out.write("Hello".getBytes()); + out.close(); + InputStream fi = FileUtils.newInputStream(fileName); + LZFInputStream in = new LZFInputStream(fi); + byte[] buff = new byte[100]; + assertEquals(5, in.read(buff)); + in.read(); + in.close(); + FileUtils.delete(getBaseDir() + "/temp"); + } + + private void testLZFStreams() throws IOException { + Random random = new Random(1); + int max = getSize(100, 1000); + for (int i = 0; i < max; i += 3) { + byte[] buffer = getRandomBytes(random); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + LZFOutputStream comp = new LZFOutputStream(out); + if (random.nextInt(10) == 1) { + comp.write(buffer); + } else { + for (int j = 0; j < buffer.length;) { + int[] sizes = { 0, 1, random.nextInt(100), random.nextInt(100000) }; + int size = sizes[random.nextInt(sizes.length)]; + size = Math.min(size, buffer.length - j); + if (size == 1) { + comp.write(buffer[j]); + } else { + comp.write(buffer, j, size); + } + j += size; + } + } + comp.close(); + byte[] compressed = out.toByteArray(); + ByteArrayInputStream in = new ByteArrayInputStream(compressed); + LZFInputStream decompress = new LZFInputStream(in); + byte[] test = new byte[buffer.length]; + for (int j = 0; j < buffer.length;) { + int[] sizes = { 0, 1, random.nextInt(100), random.nextInt(100000) }; + int size = sizes[random.nextInt(sizes.length)]; + if (size == 1) { + int x = decompress.read(); + if (x < 0) { + break; + } + test[j++] = (byte) x; + } else { + size = Math.min(size, test.length - j); + int l = decompress.read(test, j, size); + if (l < 0) { + break; + } + j += l; + } + } + decompress.close(); + assertEquals(buffer, test); + } + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestStringCache.java b/h2/src/test/org/h2/test/unit/TestStringCache.java new file mode 100644 index 0000000..ccfa2a1 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestStringCache.java @@ -0,0 +1,177 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.util.Locale; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import org.h2.test.TestBase; +import org.h2.util.StringUtils; + +/** + * Tests the string cache facility. + */ +public class TestStringCache extends TestBase { + + /** + * Flag to indicate the test should stop. + */ + volatile boolean stop; + private final Random random = new Random(1); + private final String[] some = { null, "", "ABC", + "this is a medium sized string", "1", "2" }; + private boolean useIntern; + + /** + * This method is called when executing this application from the command + * line. + * + * @param args the command line parameters + */ + public static void main(String... args) throws Exception { + TestBase.createCaller().init().testFromMain(); + new TestStringCache().runBenchmark(); + } + + @Override + public void test() throws InterruptedException { + testToUpperToLower(); + StringUtils.clearCache(); + testSingleThread(getSize(5000, 20000)); + testMultiThreads(); + } + + private void testToUpperCache() { + Random r = new Random(); + String[] test = new String[50]; + for (int i = 0; i < test.length; i++) { + StringBuilder buff = new StringBuilder(); + for (int a = 0; a < 50; a++) { + buff.append((char) r.nextInt()); + } + String a = buff.toString(); + test[i] = a; + } + int repeat = 100000; + int testLen = 0; + long time = System.nanoTime(); + for (int a = 0; a < repeat; a++) { + for (String x : test) { + String y = StringUtils.toUpperEnglish(x); + testLen += y.length(); + } + } + time = System.nanoTime() - time; + System.out.println("cache " + TimeUnit.NANOSECONDS.toMillis(time)); + time = System.nanoTime(); + for (int a = 0; a < repeat; a++) { + for (String x : test) { + String y = x.toUpperCase(Locale.ENGLISH); + testLen -= y.length(); + } + } + time = System.nanoTime() - time; + System.out.println("toUpperCase " + TimeUnit.NANOSECONDS.toMillis(time)); + assertEquals(0, testLen); + } + + private void testToUpperToLower() { + Random r = new Random(); + for (int i = 0; i < 1000; i++) { + StringBuilder buff = new StringBuilder(); + for (int a = 0; a < 100; a++) { + buff.append((char) r.nextInt()); + } + String a = buff.toString(); + String b = StringUtils.toUpperEnglish(a); + String c = a.toUpperCase(Locale.ENGLISH); + assertEquals(c, b); + String d = StringUtils.toLowerEnglish(a); + String e = a.toLowerCase(Locale.ENGLISH); + assertEquals(e, d); + } + } + + private void runBenchmark() { + testToUpperCache(); + testToUpperCache(); + testToUpperCache(); + for (int i = 0; i < 6; i++) { + useIntern = (i % 2) == 0; + long time = System.nanoTime(); + testSingleThread(100000); + time = System.nanoTime() - time; + System.out.println(TimeUnit.NANOSECONDS.toMillis(time) + + " ms (useIntern=" + useIntern + ")"); + } + + } + + private String randomString() { + if (random.nextBoolean()) { + String s = some[random.nextInt(some.length)]; + if (s != null && random.nextBoolean()) { + s = new String(s); + } + return s; + } + int len = random.nextBoolean() ? random.nextInt(1000) + : random.nextInt(10); + StringBuilder buff = new StringBuilder(len); + for (int i = 0; i < len; i++) { + buff.append(random.nextInt(0xfff)); + } + return buff.toString(); + } + + /** + * Test one string operation using the string cache. + */ + void testString() { + String a = randomString(); + String b; + if (useIntern) { + b = a == null ? null : a.intern(); + } else { + b = StringUtils.cache(a); + } + try { + assertEquals(a, b); + } catch (Exception e) { + TestBase.logError("error", e); + } + } + + private void testSingleThread(int len) { + for (int i = 0; i < len; i++) { + testString(); + } + } + + private void testMultiThreads() throws InterruptedException { + int threadCount = getSize(3, 100); + Thread[] threads = new Thread[threadCount]; + for (int i = 0; i < threadCount; i++) { + Thread t = new Thread(() -> { + while (!stop) { + testString(); + } + }); + threads[i] = t; + } + for (int i = 0; i < threadCount; i++) { + threads[i].start(); + } + int wait = getSize(200, 2000); + Thread.sleep(wait); + stop = true; + for (int i = 0; i < threadCount; i++) { + threads[i].join(); + } + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestStringUtils.java b/h2/src/test/org/h2/test/unit/TestStringUtils.java new file mode 100644 index 0000000..5115c4c --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestStringUtils.java @@ -0,0 +1,300 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.Random; + +import org.h2.expression.function.DateTimeFormatFunction; +import org.h2.message.DbException; +import org.h2.test.TestBase; +import org.h2.util.StringUtils; +import org.h2.value.ValueTimestampTimeZone; + +/** + * Tests string utility methods. + */ +public class TestStringUtils extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testParseUInt31(); + testHex(); + testXML(); + testSplit(); + testJavaString(); + testURL(); + testPad(); + testReplaceAll(); + testTrim(); + testTrimSubstring(); + testTruncateString(); + } + + private void testParseUInt31() { + assertEquals(0, StringUtils.parseUInt31("101", 1, 2)); + assertEquals(11, StringUtils.parseUInt31("11", 0, 2)); + assertEquals(0, StringUtils.parseUInt31("000", 0, 3)); + assertEquals(1, StringUtils.parseUInt31("01", 0, 2)); + assertEquals(999999999, StringUtils.parseUInt31("X999999999", 1, 10)); + assertEquals(2147483647, StringUtils.parseUInt31("2147483647", 0, 10)); + testParseUInt31Bad(null, 0, 1); + testParseUInt31Bad("1", -1, 1); + testParseUInt31Bad("1", 0, 0); + testParseUInt31Bad("12", 1, 0); + testParseUInt31Bad("-0", 0, 2); + testParseUInt31Bad("+0", 0, 2); + testParseUInt31Bad("2147483648", 0, 10); + testParseUInt31Bad("21474836470", 0, 11); + } + + private void testParseUInt31Bad(String s, int start, int end) { + try { + StringUtils.parseUInt31(s, start, end); + } catch (NullPointerException | IndexOutOfBoundsException | NumberFormatException e) { + return; + } + fail(); + } + + private void testHex() { + assertEquals("face", + StringUtils.convertBytesToHex(new byte[] + { (byte) 0xfa, (byte) 0xce })); + assertEquals(new byte[] { (byte) 0xfa, (byte) 0xce }, + StringUtils.convertHexToBytes("face")); + assertEquals(new byte[] { (byte) 0xfa, (byte) 0xce }, + StringUtils.convertHexToBytes("fAcE")); + assertEquals(new byte[] { (byte) 0xfa, (byte) 0xce }, + StringUtils.convertHexToBytes("FaCe")); + assertThrows(DbException.class, () -> StringUtils.convertHexToBytes("120")); + assertThrows(DbException.class, () -> StringUtils.convertHexToBytes("fast")); + assertThrows(DbException.class, () -> StringUtils.convertHexToBytes("012=abcf")); + } + + private void testPad() { + assertEquals("large", StringUtils.pad("larger text", 5, null, true)); + assertEquals("large", StringUtils.pad("larger text", 5, null, false)); + assertEquals("short+++++", StringUtils.pad("short", 10, "+", true)); + assertEquals("+++++short", StringUtils.pad("short", 10, "+", false)); + } + + private void testXML() { + assertEquals("\n", + StringUtils.xmlComment("------abc------")); + assertEquals("\n", + StringUtils.xmlNode("test", null, null)); + assertEquals("Grübel\n", + StringUtils.xmlNode("test", null, + StringUtils.xmlText("Gr\u00fcbel"))); + assertEquals("Rand&Blue", + StringUtils.xmlText("Rand&Blue")); + assertEquals("<<[[[]]]>>", + StringUtils.xmlCData("<<[[[]]]>>")); + ValueTimestampTimeZone dt = DateTimeFormatFunction.parseDateTime(null, + "2001-02-03 04:05:06 GMT", + "yyyy-MM-dd HH:mm:ss z", "en", "GMT"); + String s = StringUtils.xmlStartDoc() + + StringUtils.xmlComment("Test Comment") + + StringUtils.xmlNode("rss", + StringUtils.xmlAttr("version", "2.0"), + StringUtils.xmlComment("Test Comment\nZeile2") + + StringUtils.xmlNode("channel", null, + StringUtils.xmlNode("title", null, "H2 Database Engine") + + StringUtils.xmlNode("link", null, "https://h2database.com") + + StringUtils.xmlNode("description", null, "H2 Database Engine") + + StringUtils.xmlNode("language", null, "en-us") + + StringUtils.xmlNode("pubDate", null, + DateTimeFormatFunction.formatDateTime(null, dt, + "EEE, d MMM yyyy HH:mm:ss z", "en", "GMT")) + + StringUtils.xmlNode("lastBuildDate", null, + DateTimeFormatFunction.formatDateTime(null, dt, + "EEE, d MMM yyyy HH:mm:ss z", "en", "GMT")) + + StringUtils.xmlNode("item", null, + StringUtils.xmlNode("title", null, + "New Version 0.9.9.9.9") + + StringUtils.xmlNode("link", null, "https://h2database.com") + + StringUtils.xmlNode("description", null, + StringUtils.xmlCData("\nNew Features\nTest\n"))))); + assertEquals( + s, + "\n" + + "\n" + + "\n" + + " \n" + + " \n" + + " H2 Database Engine\n" + + " https://h2database.com\n" + + " H2 Database Engine\n" + + " en-us\n" + + " Sat, 3 Feb 2001 04:05:06 GMT\n" + + " Sat, 3 Feb 2001 04:05:06 GMT\n" + + " \n" + + " New Version 0.9.9.9.9\n" + + " https://h2database.com\n" + + " \n" + + " \n" + + " \n" + " \n" + + " \n" + "\n"); + } + + private void testURL() throws UnsupportedEncodingException { + Random random = new Random(1); + for (int i = 0; i < 100; i++) { + int len = random.nextInt(10); + StringBuilder buff = new StringBuilder(); + for (int j = 0; j < len; j++) { + if (random.nextBoolean()) { + buff.append((char) random.nextInt(0x3000)); + } else { + buff.append((char) random.nextInt(255)); + } + } + String a = buff.toString(); + String b = URLEncoder.encode(a, "UTF-8"); + String c = URLDecoder.decode(b, "UTF-8"); + assertEquals(a, c); + String d = StringUtils.urlDecode(b); + assertEquals(d, c); + } + } + + private void testJavaString() { + assertEquals("a\"b", StringUtils.javaDecode("a\"b")); + Random random = new Random(1); + for (int i = 0; i < 1000; i++) { + int len = random.nextInt(10); + StringBuilder buff = new StringBuilder(); + for (int j = 0; j < len; j++) { + if (random.nextBoolean()) { + buff.append((char) random.nextInt(0x3000)); + } else { + buff.append((char) random.nextInt(255)); + } + } + String a = buff.toString(); + String b = StringUtils.javaEncode(a); + String c = StringUtils.javaDecode(b); + assertEquals(a, c); + } + } + + private void testSplit() { + assertEquals(3, + StringUtils.arraySplit("ABC,DEF,G\\,HI", ',', false).length); + assertEquals( + StringUtils.arrayCombine(new String[] { "", " ", "," }, ','), + ", ,\\,"); + Random random = new Random(1); + for (int i = 0; i < 100; i++) { + int len = random.nextInt(10); + StringBuilder buff = new StringBuilder(); + String select = "abcd,"; + for (int j = 0; j < len; j++) { + char c = select.charAt(random.nextInt(select.length())); + if (c == 'a') { + buff.append("\\\\"); + } else if (c == 'b') { + buff.append("\\,"); + } else { + buff.append(c); + } + } + String a = buff.toString(); + String[] b = StringUtils.arraySplit(a, ',', false); + String c = StringUtils.arrayCombine(b, ','); + assertEquals(a, c); + } + } + + private void testReplaceAll() { + assertEquals("def", + StringUtils.replaceAll("abc def", "abc ", "")); + assertEquals("af", + StringUtils.replaceAll("abc def", "bc de", "")); + assertEquals("abc def", + StringUtils.replaceAll("abc def", "bc ", "bc ")); + assertEquals("abc ", + StringUtils.replaceAll("abc def", "def", "")); + assertEquals(" ", + StringUtils.replaceAll("abc abc", "abc", "")); + assertEquals("xyz xyz", + StringUtils.replaceAll("abc abc", "abc", "xyz")); + assertEquals("abc def", + StringUtils.replaceAll("abc def", "xyz", "abc")); + assertEquals("", + StringUtils.replaceAll("abcabcabc", "abc", "")); + assertEquals("abcabcabc", + StringUtils.replaceAll("abcabcabc", "aBc", "")); + assertEquals("abcabcabc", + StringUtils.replaceAll("abcabcabc", "", "abc")); + } + + private void testTrim() { + assertEquals("a a", + StringUtils.trim("a a", true, true, null)); + assertEquals(" a a ", + StringUtils.trim(" a a ", false, false, null)); + assertEquals(" a a", + StringUtils.trim(" a a ", false, true, null)); + assertEquals("a a ", + StringUtils.trim(" a a ", true, false, null)); + assertEquals("a a", + StringUtils.trim(" a a ", true, true, null)); + assertEquals("a a", + StringUtils.trim(" a a ", true, true, "")); + assertEquals("zzbbzz", + StringUtils.trim("zzbbzz", false, false, "z")); + assertEquals("zzbb", + StringUtils.trim("zzbbzz", false, true, "z")); + assertEquals("bbzz", + StringUtils.trim("zzbbzz", true, false, "z")); + assertEquals("bb", + StringUtils.trim("zzbbzz", true, true, "z")); + } + + private void testTrimSubstring() { + testTrimSubstringImpl("", "", 0, 0); + testTrimSubstringImpl("", " ", 0, 0); + testTrimSubstringImpl("", " ", 4, 4); + testTrimSubstringImpl("select", " select from", 1, 7); + testTrimSubstringImpl("a b", " a b ", 1, 4); + testTrimSubstringImpl("a b", " a b ", 1, 5); + testTrimSubstringImpl("b", " a b ", 2, 5); + assertThrows(StringIndexOutOfBoundsException.class, () -> StringUtils.trimSubstring(" with (", 1, 8)); + } + + private void testTrimSubstringImpl(String expected, String string, int startIndex, int endIndex) { + assertEquals(expected, StringUtils.trimSubstring(string, startIndex, endIndex)); + assertEquals(expected, StringUtils + .trimSubstring(new StringBuilder(endIndex - startIndex), string, startIndex, endIndex).toString()); + } + + private void testTruncateString() { + assertEquals("", StringUtils.truncateString("", 1)); + assertEquals("", StringUtils.truncateString("a", 0)); + assertEquals("_\ud83d\ude00", StringUtils.truncateString("_\ud83d\ude00", 3)); + assertEquals("_", StringUtils.truncateString("_\ud83d\ude00", 2)); + assertEquals("_\ud83d", StringUtils.truncateString("_\ud83d_", 2)); + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestTimeStampWithTimeZone.java b/h2/src/test/org/h2/test/unit/TestTimeStampWithTimeZone.java new file mode 100644 index 0000000..5d29fce --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestTimeStampWithTimeZone.java @@ -0,0 +1,231 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.time.OffsetDateTime; +import java.util.TimeZone; + +import org.h2.engine.CastDataProvider; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.DateTimeUtils; +import org.h2.util.JSR310Utils; +import org.h2.util.LegacyDateTimeUtils; +import org.h2.util.TimeZoneProvider; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueDate; +import org.h2.value.ValueTime; +import org.h2.value.ValueTimestamp; +import org.h2.value.ValueTimestampTimeZone; + +/** + */ +public class TestTimeStampWithTimeZone extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + deleteDb(getTestName()); + test1(); + test2(); + test3(); + test4(); + test5(); + testOrder(); + testConversions(); + deleteDb(getTestName()); + } + + private void test1() throws SQLException { + Connection conn = getConnection(getTestName()); + Statement stat = conn.createStatement(); + stat.execute("create table test(id identity, t1 timestamp(9) with time zone)"); + stat.execute("insert into test(t1) values('1970-01-01 12:00:00.00+00:15')"); + // verify NanosSinceMidnight is in local time and not UTC + stat.execute("insert into test(t1) values('2016-09-24 00:00:00.000000001+00:01')"); + stat.execute("insert into test(t1) values('2016-09-24 00:00:00.000000001-00:01')"); + // verify year month day is in local time and not UTC + stat.execute("insert into test(t1) values('2016-01-01 05:00:00.00+10:00')"); + stat.execute("insert into test(t1) values('2015-12-31 19:00:00.00-10:00')"); + ResultSet rs = stat.executeQuery("select t1 from test"); + rs.next(); + assertEquals("1970-01-01 12:00:00+00:15", rs.getString(1)); + OffsetDateTime ts = (OffsetDateTime) rs.getObject(1); + assertEquals(1970, ts.getYear()); + assertEquals(1, ts.getMonthValue()); + assertEquals(1, ts.getDayOfMonth()); + assertEquals(15 * 60, ts.getOffset().getTotalSeconds()); + OffsetDateTime expected = OffsetDateTime.parse("1970-01-01T12:00+00:15"); + assertEquals(expected, ts); + assertEquals("1970-01-01T12:00+00:15", rs.getObject(1, OffsetDateTime.class).toString()); + rs.next(); + ts = (OffsetDateTime) rs.getObject(1); + assertEquals(2016, ts.getYear()); + assertEquals(9, ts.getMonthValue()); + assertEquals(24, ts.getDayOfMonth()); + assertEquals(1L, ts.toLocalTime().toNanoOfDay()); + assertEquals(60, ts.getOffset().getTotalSeconds()); + assertEquals("2016-09-24T00:00:00.000000001+00:01", rs.getObject(1, OffsetDateTime.class).toString()); + rs.next(); + ts = (OffsetDateTime) rs.getObject(1); + assertEquals(2016, ts.getYear()); + assertEquals(9, ts.getMonthValue()); + assertEquals(24, ts.getDayOfMonth()); + assertEquals(1L, ts.toLocalTime().toNanoOfDay()); + assertEquals(-60, ts.getOffset().getTotalSeconds()); + assertEquals("2016-09-24T00:00:00.000000001-00:01", rs.getObject(1, OffsetDateTime.class).toString()); + rs.next(); + ts = (OffsetDateTime) rs.getObject(1); + assertEquals(2016, ts.getYear()); + assertEquals(1, ts.getMonthValue()); + assertEquals(1, ts.getDayOfMonth()); + assertEquals("2016-01-01T05:00+10:00", rs.getObject(1, OffsetDateTime.class).toString()); + rs.next(); + ts = (OffsetDateTime) rs.getObject(1); + assertEquals(2015, ts.getYear()); + assertEquals(12, ts.getMonthValue()); + assertEquals(31, ts.getDayOfMonth()); + assertEquals("2015-12-31T19:00-10:00", rs.getObject(1, OffsetDateTime.class).toString()); + + ResultSetMetaData metaData = rs.getMetaData(); + int columnType = metaData.getColumnType(1); + assertEquals(Types.TIMESTAMP_WITH_TIMEZONE, columnType); + assertEquals("java.time.OffsetDateTime", metaData.getColumnClassName(1)); + + rs.close(); + + rs = stat.executeQuery("select cast(t1 as varchar) from test"); + assertTrue(rs.next()); + assertEquals(expected, rs.getObject(1, OffsetDateTime.class)); + + stat.close(); + conn.close(); + } + + private void test2() { + ValueTimestampTimeZone a = ValueTimestampTimeZone.parse("1970-01-01 12:00:00.00+00:15", null); + ValueTimestampTimeZone b = ValueTimestampTimeZone.parse("1970-01-01 12:00:01.00+01:15", null); + int c = a.compareTo(b, null, null); + assertEquals(1, c); + c = b.compareTo(a, null, null); + assertEquals(-1, c); + } + + private void test3() { + ValueTimestampTimeZone a = ValueTimestampTimeZone.parse("1970-01-02 00:00:02.00+01:15", null); + ValueTimestampTimeZone b = ValueTimestampTimeZone.parse("1970-01-01 23:00:01.00+00:15", null); + int c = a.compareTo(b, null, null); + assertEquals(1, c); + c = b.compareTo(a, null, null); + assertEquals(-1, c); + } + + private void test4() { + ValueTimestampTimeZone a = ValueTimestampTimeZone.parse("1970-01-02 00:00:01.00+01:15", null); + ValueTimestampTimeZone b = ValueTimestampTimeZone.parse("1970-01-01 23:00:01.00+00:15", null); + int c = a.compareTo(b, null, null); + assertEquals(0, c); + c = b.compareTo(a, null, null); + assertEquals(0, c); + } + + private void test5() throws SQLException { + Connection conn = getConnection(getTestName()); + Statement stat = conn.createStatement(); + stat.execute("create table test5(id identity, t1 timestamp with time zone)"); + stat.execute("insert into test5(t1) values('2016-09-24 00:00:00.000000001+00:01')"); + stat.execute("insert into test5(t1) values('2017-04-20 00:00:00.000000001+00:01')"); + + PreparedStatement preparedStatement = conn.prepareStatement("select id" + + " from test5" + + " where (t1 < ?)"); + Value value = ValueTimestampTimeZone.parse("2016-12-24 00:00:00.000000001+00:01", null); + preparedStatement.setObject(1, JSR310Utils.valueToOffsetDateTime(value, null)); + + ResultSet rs = preparedStatement.executeQuery(); + + assertTrue(rs.next()); + assertEquals(1, rs.getInt(1)); + assertFalse(rs.next()); + + rs.close(); + preparedStatement.close(); + stat.close(); + conn.close(); + } + + private void testOrder() throws SQLException { + Connection conn = getConnection(getTestName()); + Statement stat = conn.createStatement(); + stat.execute("create table test_order(id identity, t1 timestamp with time zone)"); + stat.execute("insert into test_order(t1) values('1970-01-01 12:00:00.00+00:15')"); + stat.execute("insert into test_order(t1) values('1970-01-01 12:00:01.00+01:15')"); + ResultSet rs = stat.executeQuery("select t1 from test_order order by t1"); + rs.next(); + assertEquals("1970-01-01 12:00:01+01:15", rs.getString(1)); + conn.close(); + } + + private void testConversionsImpl(String timeStr, boolean testReverse, CastDataProvider provider) { + ValueTimestamp ts = ValueTimestamp.parse(timeStr, null); + ValueDate d = ts.convertToDate(provider); + ValueTime t = (ValueTime) ts.convertTo(TypeInfo.TYPE_TIME, provider); + ValueTimestampTimeZone tstz = ValueTimestampTimeZone.parse(timeStr, null); + assertEquals(ts, tstz.convertTo(TypeInfo.TYPE_TIMESTAMP, provider)); + assertEquals(d, tstz.convertToDate(provider)); + assertEquals(t, tstz.convertTo(TypeInfo.TYPE_TIME, provider)); + assertEquals(LegacyDateTimeUtils.toTimestamp(provider, null, ts), + LegacyDateTimeUtils.toTimestamp(provider, null, tstz)); + if (testReverse) { + assertEquals(0, tstz.compareTo(ts.convertTo(TypeInfo.TYPE_TIMESTAMP_TZ, provider), null, null)); + assertEquals(d.convertTo(TypeInfo.TYPE_TIMESTAMP, provider) + .convertTo(TypeInfo.TYPE_TIMESTAMP_TZ, provider), + d.convertTo(TypeInfo.TYPE_TIMESTAMP_TZ, provider)); + assertEquals(t.convertTo(TypeInfo.TYPE_TIMESTAMP, provider) + .convertTo(TypeInfo.TYPE_TIMESTAMP_TZ, provider), + t.convertTo(TypeInfo.TYPE_TIMESTAMP_TZ, provider)); + } + } + + private void testConversions() { + TestDate.SimpleCastDataProvider provider = new TestDate.SimpleCastDataProvider(); + TimeZone current = TimeZone.getDefault(); + try { + for (String id : TimeZone.getAvailableIDs()) { + if (id.equals("GMT0")) { + continue; + } + TimeZone.setDefault(TimeZone.getTimeZone(id)); + provider.currentTimeZone = TimeZoneProvider.ofId(id); + DateTimeUtils.resetCalendar(); + testConversionsImpl("2017-12-05 23:59:30.987654321-12:00", true, provider); + testConversionsImpl("2000-01-02 10:20:30.123456789+07:30", true, provider); + boolean testReverse = !"Africa/Monrovia".equals(id); + testConversionsImpl("1960-04-06 12:13:14.777666555+12:00", testReverse, provider); + } + } finally { + TimeZone.setDefault(current); + DateTimeUtils.resetCalendar(); + } + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestTools.java b/h2/src/test/org/h2/test/unit/TestTools.java new file mode 100644 index 0000000..69b8c9a --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestTools.java @@ -0,0 +1,1303 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.awt.Button; +import java.awt.HeadlessException; +import java.awt.event.ActionEvent; +import java.awt.event.MouseEvent; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.Reader; +import java.io.Writer; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.UUID; +import org.h2.api.ErrorCode; +import org.h2.engine.SysProperties; +import org.h2.store.FileLister; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.test.trace.Player; +import org.h2.tools.Backup; +import org.h2.tools.ChangeFileEncryption; +import org.h2.tools.Console; +import org.h2.tools.ConvertTraceFile; +import org.h2.tools.DeleteDbFiles; +import org.h2.tools.GUIConsole; +import org.h2.tools.Recover; +import org.h2.tools.Restore; +import org.h2.tools.RunScript; +import org.h2.tools.Script; +import org.h2.tools.Server; +import org.h2.tools.SimpleResultSet; +import org.h2.tools.SimpleResultSet.SimpleArray; +import org.h2.util.JdbcUtils; +import org.h2.util.Task; +import org.h2.util.Utils10; +import org.h2.value.ValueUuid; + +/** + * Tests the database tools. + */ +public class TestTools extends TestDb { + + private static String lastUrl; + private Server server; + private List remainingServers = new ArrayList<>(3); + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public boolean isEnabled() { + if (config.networked) { + return false; + } + return true; + } + + @Override + public void test() throws Exception { + DeleteDbFiles.execute(getBaseDir(), null, true); + org.h2.Driver.load(); + testSimpleResultSet(); + testTcpServerWithoutPort(); + testConsole(); + testJdbcDriverUtils(); + testWrongServer(); + testDeleteFiles(); + testScriptRunscriptLob(); + testServerMain(); + testConvertTraceFile(); + testManagementDb(); + testChangeFileEncryption(false); + if (!config.splitFileSystem) { + testChangeFileEncryption(true); + } + testChangeFileEncryptionWithWrongPassword(); + testServer(); + testScriptRunscript(); + testBackupRestore(); + testRecover(); + FileUtils.delete(getBaseDir() + "/b2.sql"); + FileUtils.delete(getBaseDir() + "/b2.sql.txt"); + FileUtils.delete(getBaseDir() + "/b2.zip"); + } + + private void testTcpServerWithoutPort() throws Exception { + Server s1 = Server.createTcpServer().start(); + Server s2 = Server.createTcpServer().start(); + assertTrue(s1.getPort() != s2.getPort()); + s1.stop(); + s2.stop(); + s1 = Server.createTcpServer("-tcpPort", "9123").start(); + assertEquals(9123, s1.getPort()); + assertThrows(ErrorCode.EXCEPTION_OPENING_PORT_2, () -> Server.createTcpServer("-tcpPort", "9123").start()); + s1.stop(); + } + + private void testConsole() throws Exception { + String old = System.getProperty(SysProperties.H2_BROWSER); + GUIConsole c = new GUIConsole(); + c.setOut(new PrintStream(new ByteArrayOutputStream())); + try { + + // start including browser + lastUrl = "-"; + System.setProperty(SysProperties.H2_BROWSER, "call:" + + TestTools.class.getName() + ".openBrowser"); + c.runTool("-web", "-webPort", "9002", "-tool", "-browser", "-tcp", + "-tcpPort", "9003", "-pg", "-pgPort", "9004"); + assertContains(lastUrl, ":9002"); + shutdownConsole(c); + + // check if starting the browser works + c.runTool("-web", "-webPort", "9002", "-tool"); + lastUrl = "-"; + c.actionPerformed(new ActionEvent(this, 0, "console")); + assertContains(lastUrl, ":9002"); + lastUrl = "-"; + // double-click prevention is 100 ms + Thread.sleep(200); + try { + MouseEvent me = new MouseEvent(new Button(), 0, 0, 0, 0, 0, 0, + false, MouseEvent.BUTTON1); + c.mouseClicked(me); + assertContains(lastUrl, ":9002"); + lastUrl = "-"; + // no delay - ignore because it looks like a double click + c.mouseClicked(me); + assertEquals("-", lastUrl); + // open the window + c.actionPerformed(new ActionEvent(this, 0, "status")); + c.actionPerformed(new ActionEvent(this, 0, "exit")); + + // check if the service was stopped + c.runTool("-webPort", "9002"); + + } catch (HeadlessException e) { + // ignore + } + + shutdownConsole(c); + + // trying to use the same port for two services should fail, + // but also stop the first service + assertThrows(ErrorCode.EXCEPTION_OPENING_PORT_2, + () -> c.runTool("-web", "-webPort", "9002", "-tcp", "-tcpPort", "9002")); + c.runTool("-web", "-webPort", "9002"); + + } finally { + if (old != null) { + System.setProperty(SysProperties.H2_BROWSER, old); + } else { + System.clearProperty(SysProperties.H2_BROWSER); + } + shutdownConsole(c); + } + } + + private static void shutdownConsole(Console c) { + c.shutdown(); + if (Thread.currentThread().isInterrupted()) { + // Clear interrupted state so test can continue its work safely + try { + Thread.sleep(1); + } catch (InterruptedException e) { + // Ignore + } + } + } + + /** + * This method is called via reflection. + * + * @param url the browser url + */ + public static void openBrowser(String url) { + lastUrl = url; + } + + private void testSimpleResultSet() throws Exception { + SimpleResultSet rs; + rs = new SimpleResultSet(); + rs.addColumn(null, 0, 0, 0); + rs.addRow(1); + SimpleResultSet r = rs; + assertThrows(IllegalStateException.class, () -> r.addColumn(null, 0, 0, 0)); + assertEquals(ResultSet.TYPE_FORWARD_ONLY, rs.getType()); + + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals("1", rs.getString(1)); + assertEquals("1", rs.getString("C1")); + assertFalse(rs.wasNull()); + assertEquals("C1", rs.getMetaData().getColumnLabel(1)); + assertEquals("C1", rs.getColumnName(1)); + assertEquals( + ResultSetMetaData.columnNullableUnknown, + rs.getMetaData().isNullable(1)); + assertFalse(rs.getMetaData().isAutoIncrement(1)); + assertTrue(rs.getMetaData().isCaseSensitive(1)); + assertFalse(rs.getMetaData().isCurrency(1)); + assertFalse(rs.getMetaData().isDefinitelyWritable(1)); + assertTrue(rs.getMetaData().isReadOnly(1)); + assertTrue(rs.getMetaData().isSearchable(1)); + assertTrue(rs.getMetaData().isSigned(1)); + assertFalse(rs.getMetaData().isWritable(1)); + assertEquals("", rs.getMetaData().getCatalogName(1)); + assertEquals(Void.class.getName(), rs.getMetaData().getColumnClassName(1)); + assertEquals("NULL", rs.getMetaData().getColumnTypeName(1)); + assertEquals("", rs.getMetaData().getSchemaName(1)); + assertEquals("", rs.getMetaData().getTableName(1)); + assertEquals(ResultSet.HOLD_CURSORS_OVER_COMMIT, rs.getHoldability()); + assertEquals(1, rs.getColumnCount()); + + rs = new SimpleResultSet(); + rs.setAutoClose(false); + + rs.addColumn("a", Types.BIGINT, 0, 0); + rs.addColumn("b", Types.BINARY, 0, 0); + rs.addColumn("c", Types.BOOLEAN, 0, 0); + rs.addColumn("d", Types.DATE, 0, 0); + rs.addColumn("e", Types.DECIMAL, 0, 0); + rs.addColumn("f", Types.FLOAT, 0, 0); + rs.addColumn("g", Types.VARCHAR, 0, 0); + rs.addColumn("h", Types.ARRAY, 0, 0); + rs.addColumn("i", Types.TIME, 0, 0); + rs.addColumn("j", Types.TIMESTAMP, 0, 0); + rs.addColumn("k", Types.CLOB, 0, 0); + rs.addColumn("l", Types.BLOB, 0, 0); + + Date d = Date.valueOf("2001-02-03"); + byte[] b = {(byte) 0xab}; + Object[] a = {1, 2}; + Time t = Time.valueOf("10:20:30"); + Timestamp ts = Timestamp.valueOf("2002-03-04 10:20:30"); + Clob clob = new SimpleClob("Hello World"); + Blob blob = new SimpleBlob(new byte[]{(byte) 1, (byte) 2}); + rs.addRow(1, b, true, d, "10.3", Math.PI, "-3", a, t, ts, clob, blob); + rs.addRow(BigInteger.ONE, null, true, null, BigDecimal.ONE, 1d, null, null, null, null, null); + rs.addRow(BigInteger.ZERO, null, false, null, BigDecimal.ZERO, 0d, null, null, null, null, null); + rs.addRow(null, null, null, null, null, null, null, null, null, null, null); + rs.addRow(null, null, true, null, null, null, null, null, null, null, null); + + rs.next(); + + assertEquals(1, rs.getLong(1)); + assertEquals((byte) 1, rs.getByte(1)); + assertEquals((short) 1, rs.getShort(1)); + assertEquals(1, rs.getLong("a")); + assertEquals((byte) 1, rs.getByte("a")); + assertEquals(1, rs.getInt("a")); + assertEquals((short) 1, rs.getShort("a")); + assertTrue(rs.getObject(1).getClass() == Integer.class); + assertTrue(rs.getObject("a").getClass() == Integer.class); + assertTrue(rs.getBoolean(1)); + + assertEquals(b, rs.getBytes(2)); + assertEquals(b, rs.getBytes("b")); + + assertTrue(rs.getBoolean(3)); + assertTrue(rs.getBoolean("c")); + assertEquals(d.getTime(), rs.getDate(4).getTime()); + assertEquals(d.getTime(), rs.getDate("d").getTime()); + + assertTrue(new BigDecimal("10.3").equals(rs.getBigDecimal(5))); + assertTrue(new BigDecimal("10.3").equals(rs.getBigDecimal("e"))); + assertEquals(10.3, rs.getDouble(5)); + assertEquals((float) 10.3, rs.getFloat(5)); + + assertTrue(Math.PI == rs.getDouble(6)); + assertTrue(Math.PI == rs.getDouble("f")); + assertTrue((float) Math.PI == rs.getFloat(6)); + assertTrue((float) Math.PI == rs.getFloat("f")); + assertTrue(rs.getBoolean(6)); + + assertEquals(-3, rs.getInt(7)); + assertEquals(-3, rs.getByte(7)); + assertEquals(-3, rs.getShort(7)); + assertEquals(-3, rs.getLong(7)); + + Object[] a2 = (Object[]) rs.getArray(8).getArray(); + assertEquals(2, a2.length); + assertTrue(a == a2); + SimpleArray array = (SimpleArray) rs.getArray("h"); + assertEquals(Types.NULL, array.getBaseType()); + assertEquals("NULL", array.getBaseTypeName()); + a2 = (Object[]) array.getArray(); + array.free(); + assertEquals(2, a2.length); + assertTrue(a == a2); + + assertTrue(t == rs.getTime("i")); + assertTrue(t == rs.getTime(9)); + + assertTrue(ts == rs.getTimestamp("j")); + assertTrue(ts == rs.getTimestamp(10)); + + assertTrue(clob == rs.getClob("k")); + assertTrue(clob == rs.getClob(11)); + assertEquals("Hello World", rs.getString("k")); + assertEquals("Hello World", rs.getString(11)); + + assertTrue(blob == rs.getBlob("l")); + assertTrue(blob == rs.getBlob(12)); + + assertThrows(ErrorCode.INVALID_VALUE_2, (ResultSet) rs). + getString(13); + assertThrows(ErrorCode.COLUMN_NOT_FOUND_1, (ResultSet) rs). + getString("NOT_FOUND"); + + rs.next(); + + assertTrue(rs.getBoolean(1)); + assertTrue(rs.getBoolean(3)); + assertTrue(rs.getBoolean(5)); + assertTrue(rs.getBoolean(6)); + + rs.next(); + + assertFalse(rs.getBoolean(1)); + assertFalse(rs.getBoolean(3)); + assertFalse(rs.getBoolean(5)); + assertFalse(rs.getBoolean(6)); + + rs.next(); + + assertEquals(0, rs.getLong(1)); + assertTrue(rs.wasNull()); + assertEquals(null, rs.getBytes(2)); + assertTrue(rs.wasNull()); + assertFalse(rs.getBoolean(3)); + assertTrue(rs.wasNull()); + assertNull(rs.getDate(4)); + assertTrue(rs.wasNull()); + assertNull(rs.getBigDecimal(5)); + assertTrue(rs.wasNull()); + assertEquals(0.0, rs.getDouble(5)); + assertTrue(rs.wasNull()); + assertEquals(0.0, rs.getDouble(6)); + assertTrue(rs.wasNull()); + assertEquals(0.0, rs.getFloat(6)); + assertTrue(rs.wasNull()); + assertEquals(0, rs.getInt(7)); + assertTrue(rs.wasNull()); + assertNull(rs.getArray(8)); + assertTrue(rs.wasNull()); + assertNull(rs.getTime(9)); + assertTrue(rs.wasNull()); + assertNull(rs.getTimestamp(10)); + assertTrue(rs.wasNull()); + assertNull(rs.getClob(11)); + assertTrue(rs.wasNull()); + assertNull(rs.getCharacterStream(11)); + assertTrue(rs.wasNull()); + assertNull(rs.getBlob(12)); + assertTrue(rs.wasNull()); + assertNull(rs.getBinaryStream(12)); + assertTrue(rs.wasNull()); + + assertTrue(rs.next()); + assertTrue(rs.getBoolean(3)); + assertFalse(rs.wasNull()); + assertNull(rs.getObject(6, Float.class)); + assertTrue(rs.wasNull()); + + // all updateX methods + for (Method m: rs.getClass().getMethods()) { + if (m.getName().startsWith("update")) { + if (m.getName().equals("updateRow")) { + continue; + } + int len = m.getParameterTypes().length; + if (m.getName().equals("updateObject") && m.getParameterTypes().length > 2) { + Class p3 = m.getParameterTypes()[2]; + if (p3.toString().indexOf("SQLType") >= 0) { + continue; + } + } + Object[] params = new Object[len]; + int i = 0; + String expectedValue = null; + for (Class type : m.getParameterTypes()) { + Object o; + String e = null; + if (type == int.class) { + o = 1; + e = "1"; + } else if (type == byte.class) { + o = (byte) 2; + e = "2"; + } else if (type == double.class) { + o = (double) 3; + e = "3.0"; + } else if (type == float.class) { + o = (float) 4; + e = "4.0"; + } else if (type == long.class) { + o = (long) 5; + e = "5"; + } else if (type == short.class) { + o = (short) 6; + e = "6"; + } else if (type == boolean.class) { + o = false; + e = "false"; + } else if (type == String.class) { + // columnName or value + o = "a"; + e = "a"; + } else { + o = null; + } + if (i == 1) { + expectedValue = e; + } + params[i] = o; + i++; + } + m.invoke(rs, params); + if (params.length == 1) { + // updateNull + assertEquals(null, rs.getString(1)); + } else { + assertEquals(expectedValue, rs.getString(1)); + } + // invalid column name / index + Object invalidColumn; + if (m.getParameterTypes()[0] == String.class) { + invalidColumn = "x"; + } else { + invalidColumn = 0; + } + params[0] = invalidColumn; + try { + m.invoke(rs, params); + fail(); + } catch (InvocationTargetException e) { + SQLException e2 = (SQLException) e.getTargetException(); + if (invalidColumn instanceof String) { + assertEquals(ErrorCode.COLUMN_NOT_FOUND_1, + e2.getErrorCode()); + } else { + assertEquals(ErrorCode.INVALID_VALUE_2, + e2.getErrorCode()); + } + } + } + } + assertEquals(ResultSet.FETCH_FORWARD, rs.getFetchDirection()); + assertEquals(0, rs.getFetchSize()); + assertEquals(ResultSet.TYPE_SCROLL_INSENSITIVE, rs.getType()); + assertNull(rs.getStatement()); + assertFalse(rs.isClosed()); + + rs.beforeFirst(); + assertEquals(0, rs.getRow()); + assertTrue(rs.next()); + assertFalse(rs.isClosed()); + assertEquals(1, rs.getRow()); + assertTrue(rs.next()); + assertTrue(rs.next()); + assertTrue(rs.next()); + assertTrue(rs.next()); + assertFalse(rs.next()); + assertThrows(ErrorCode.NO_DATA_AVAILABLE, (ResultSet) rs). + getInt(1); + assertEquals(0, rs.getRow()); + assertFalse(rs.isClosed()); + rs.close(); + assertTrue(rs.isClosed()); + rs = new SimpleResultSet(); + rs.addColumn("TEST", Types.BINARY, 0, 0); + UUID uuid = UUID.randomUUID(); + rs.addRow(uuid); + rs.next(); + assertEquals(uuid, rs.getObject(1)); + assertEquals(uuid, ValueUuid.get(rs.getBytes(1)).getUuid()); + + assertTrue(rs.isWrapperFor(Object.class)); + assertTrue(rs.isWrapperFor(ResultSet.class)); + assertTrue(rs.isWrapperFor(rs.getClass())); + assertFalse(rs.isWrapperFor(Integer.class)); + assertTrue(rs == rs.unwrap(Object.class)); + assertTrue(rs == rs.unwrap(ResultSet.class)); + assertTrue(rs == rs.unwrap(rs.getClass())); + SimpleResultSet rs2 = rs; + assertThrows(ErrorCode.INVALID_VALUE_2, () -> rs2.unwrap(Integer.class)); + } + + private void testJdbcDriverUtils() { + assertEquals("org.h2.Driver", JdbcUtils.getDriver("jdbc:h2:~/test")); + assertEquals("org.postgresql.Driver", JdbcUtils.getDriver("jdbc:postgresql:test")); + assertEquals(null, JdbcUtils.getDriver("jdbc:unknown:test")); + try { + JdbcUtils.getConnection("org.h2.Driver", "jdbc:h2x:test", "sa", ""); + fail("Expected SQLException: 08001"); + } catch (SQLException e) { + assertEquals("08001", e.getSQLState()); + } + try { + JdbcUtils.getConnection("javax.naming.InitialContext", "ldap://localhost/ds", "sa", ""); + fail("Expected SQLException: 08001"); + } catch (SQLException e) { + assertEquals("08001", e.getSQLState()); + assertEquals("Only java scheme is supported for JNDI lookups", e.getMessage()); + } + try { + JdbcUtils.getConnection("org.h2.Driver", "jdbc:h2:mem:", "sa", "", null, true); + fail("Expected SQLException: " + ErrorCode.REMOTE_DATABASE_NOT_FOUND_1); + } catch (SQLException e) { + assertEquals(ErrorCode.REMOTE_DATABASE_NOT_FOUND_1, e.getErrorCode()); + } + } + + private void testWrongServer() throws Exception { + // try to connect when the server is not running + assertThrows(ErrorCode.CONNECTION_BROKEN_1, () -> getConnection("jdbc:h2:tcp://localhost:9001/test")); + final ServerSocket serverSocket = new ServerSocket(9001); + Task task = new Task() { + @Override + public void call() throws Exception { + while (!stop) { + Socket socket = serverSocket.accept(); + byte[] data = new byte[1024]; + data[0] = 'x'; + OutputStream out = socket.getOutputStream(); + out.write(data); + out.close(); + socket.close(); + } + } + }; + try { + task.execute(); + Thread.sleep(100); + assertThrows(ErrorCode.CONNECTION_BROKEN_1, () -> getConnection("jdbc:h2:tcp://localhost:9001/test")); + } finally { + serverSocket.close(); + task.getException(); + } + } + + private void testDeleteFiles() throws SQLException { + if (config.memory) { + return; + } + deleteDb("testDeleteFiles"); + Connection conn = getConnection("testDeleteFiles"); + Statement stat = conn.createStatement(); + stat.execute("create table test(c clob) as select space(10000) from dual"); + conn.close(); + // the name starts with the same string, but does not match it + DeleteDbFiles.execute(getBaseDir(), "testDelete", true); + conn = getConnection("testDeleteFiles"); + stat = conn.createStatement(); + ResultSet rs; + rs = stat.executeQuery("select * from test"); + rs.next(); + rs.getString(1); + conn.close(); + deleteDb("testDeleteFiles"); + } + + private void testServerMain() throws Exception { + testNonSSL(); + if (!config.ci) { + testSSL(); + } + } + + private void testNonSSL() throws Exception { + String result; + Connection conn; + + try { + result = runServer(0, new String[]{"-?"}); + assertContains(result, "Starts the H2 Console"); + assertTrue(result.indexOf("Unknown option") < 0); + + result = runServer(1, new String[]{"-xy"}); + assertContains(result, "Starts the H2 Console"); + assertContains(result, "Feature not supported"); + result = runServer(0, new String[]{"-ifNotExists", "-tcp", + "-tcpPort", "9001", "-tcpPassword", "abc"}); + assertContains(result, "tcp://"); + assertContains(result, ":9001"); + assertContains(result, "only local"); + assertTrue(result.indexOf("Starts the H2 Console") < 0); + conn = getConnection("jdbc:h2:tcp://localhost:9001/mem:", "sa", "sa"); + conn.close(); + result = runServer(0, new String[]{"-tcpShutdown", + "tcp://localhost:9001", "-tcpPassword", "abc", "-tcpShutdownForce"}); + assertContains(result, "Shutting down"); + } finally { + shutdownServers(); + } + } + + private void testSSL() throws Exception { + String result; + Connection conn; + + try { + result = runServer(0, new String[]{"-ifNotExists", "-tcp", + "-tcpAllowOthers", "-tcpPort", "9001", "-tcpPassword", "abcdef", "-tcpSSL"}); + assertContains(result, "ssl://"); + assertContains(result, ":9001"); + assertContains(result, "others can"); + assertTrue(result.indexOf("Starts the H2 Console") < 0); + conn = getConnection("jdbc:h2:ssl://localhost:9001/mem:", "sa", "sa"); + conn.close(); + + result = runServer(0, new String[]{"-tcpShutdown", + "ssl://localhost:9001", "-tcpPassword", "abcdef"}); + assertContains(result, "Shutting down"); + assertThrows(ErrorCode.CONNECTION_BROKEN_1, + () -> getConnection("jdbc:h2:ssl://localhost:9001/mem:", "sa", "sa")); + + result = runServer(0, new String[]{ + "-ifNotExists", "-web", "-webPort", "9002", "-webAllowOthers", "-webSSL", + "-pg", "-pgAllowOthers", "-pgPort", "9003", + "-tcp", "-tcpAllowOthers", "-tcpPort", "9006", "-tcpPassword", "abc"}); + Server stop = server; + assertContains(result, "https://"); + assertContains(result, ":9002"); + assertContains(result, "pg://"); + assertContains(result, ":9003"); + assertContains(result, "others can"); + assertTrue(result.indexOf("only local") < 0); + assertContains(result, "tcp://"); + assertContains(result, ":9006"); + + conn = getConnection("jdbc:h2:tcp://localhost:9006/mem:", "sa", "sa"); + conn.close(); + + result = runServer(0, new String[]{"-tcpShutdown", + "tcp://localhost:9006", "-tcpPassword", "abc", "-tcpShutdownForce"}); + assertContains(result, "Shutting down"); + stop.shutdown(); + assertThrows(ErrorCode.CONNECTION_BROKEN_1, + () -> getConnection("jdbc:h2:tcp://localhost:9006/mem:", "sa", "sa")); + } finally { + shutdownServers(); + } + } + + private String runServer(int exitCode, String... args) throws Exception { + ByteArrayOutputStream buff = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(buff, false, "UTF-8"); + if (server != null) { + remainingServers.add(server); + } + server = new Server(); + server.setOut(ps); + int result = 0; + try { + server.runTool(args); + } catch (SQLException e) { + result = 1; + e.printStackTrace(ps); + } + assertEquals(exitCode, result); + ps.flush(); + return Utils10.byteArrayOutputStreamToString(buff, StandardCharsets.UTF_8); + } + + private void shutdownServers() { + for (Server remainingServer : remainingServers) { + if (remainingServer != null) { + remainingServer.shutdown(); + } + } + remainingServers.clear(); + if (server != null) { + server.shutdown(); + } + } + + private void testConvertTraceFile() throws Exception { + deleteDb("toolsConvertTraceFile"); + org.h2.Driver.load(); + String url = "jdbc:h2:" + getBaseDir() + "/toolsConvertTraceFile"; + url = getURL(url, true); + Connection conn = getConnection(url + ";TRACE_LEVEL_FILE=3", "sa", "sa"); + Statement stat = conn.createStatement(); + stat.execute( + "create table test(id int primary key, name varchar, amount decimal(4, 2))"); + PreparedStatement prep = conn.prepareStatement( + "insert into test values(?, ?, ?)"); + prep.setInt(1, 1); + prep.setString(2, "Hello \\'Joe\n\\'"); + prep.setBigDecimal(3, new BigDecimal("10.20")); + prep.executeUpdate(); + stat.execute("create table test2(id int primary key,\n" + + "a real, b double, c bigint,\n" + + "d smallint, e boolean, f varbinary, g date, h time, i timestamp)", + Statement.NO_GENERATED_KEYS); + prep = conn.prepareStatement( + "insert into test2 values(1, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + prep.setFloat(1, Float.MIN_VALUE); + prep.setDouble(2, Double.MIN_VALUE); + prep.setLong(3, Long.MIN_VALUE); + prep.setShort(4, Short.MIN_VALUE); + prep.setBoolean(5, false); + prep.setBytes(6, new byte[] { (byte) 10, (byte) 20 }); + prep.setDate(7, java.sql.Date.valueOf("2007-12-31")); + prep.setTime(8, java.sql.Time.valueOf("23:59:59")); + prep.setTimestamp(9, java.sql.Timestamp.valueOf("2007-12-31 23:59:59")); + prep.executeUpdate(); + conn.close(); + + ConvertTraceFile.main("-traceFile", getBaseDir() + + "/toolsConvertTraceFile.trace.db", "-javaClass", getBaseDir() + + "/Test", "-script", getBaseDir() + "/test.sql"); + FileUtils.delete(getBaseDir() + "/Test.java"); + + String trace = getBaseDir() + "/toolsConvertTraceFile.trace.db"; + assertTrue(FileUtils.exists(trace)); + String newTrace = getBaseDir() + "/test.trace.db"; + FileUtils.delete(newTrace); + assertFalse(FileUtils.exists(newTrace)); + FileUtils.move(trace, newTrace); + deleteDb("toolsConvertTraceFile"); + Player.main(getBaseDir() + "/test.trace.db"); + testTraceFile(url); + + deleteDb("toolsConvertTraceFile"); + RunScript.main("-url", url, "-user", "sa", "-script", getBaseDir() + + "/test.sql"); + testTraceFile(url); + + deleteDb("toolsConvertTraceFile"); + FileUtils.delete(getBaseDir() + "/toolsConvertTraceFile.h2.sql"); + FileUtils.delete(getBaseDir() + "/test.sql"); + } + + private void testTraceFile(String url) throws SQLException { + Connection conn; + Recover.main("-dir", getBaseDir(), "-db", "toolsConvertTraceFile"); + conn = getConnection(url, "sa", ""); + Statement stat = conn.createStatement(); + ResultSet rs; + rs = stat.executeQuery("select * from test"); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello \\'Joe\n\\'", rs.getString(2)); + assertEquals("10.20", rs.getBigDecimal(3).toString()); + assertFalse(rs.next()); + rs = stat.executeQuery("select * from test2"); + rs.next(); + assertEquals(Float.MIN_VALUE, rs.getFloat("a")); + assertEquals(Double.MIN_VALUE, rs.getDouble("b")); + assertEquals(Long.MIN_VALUE, rs.getLong("c")); + assertEquals(Short.MIN_VALUE, rs.getShort("d")); + assertFalse(rs.getBoolean("e")); + assertEquals(new byte[] { (byte) 10, (byte) 20 }, rs.getBytes("f")); + assertEquals("2007-12-31", rs.getString("g")); + assertEquals("23:59:59", rs.getString("h")); + assertEquals("2007-12-31 23:59:59", rs.getString("i")); + assertFalse(rs.next()); + conn.close(); + } + + private void testRecover() throws SQLException { + if (config.memory) { + return; + } + deleteDb("toolsRecover"); + org.h2.Driver.load(); + String url = getURL("toolsRecover", true); + Connection conn = getConnection(url, "sa", "sa"); + Statement stat = conn.createStatement(); + stat.execute("create table test(id int primary key, " + + "name varchar, b blob, c clob)"); + stat.execute("create table \"test 2\"(id int primary key, name varchar)"); + stat.execute("comment on table test is ';-)'"); + stat.execute("insert into test values" + + "(1, 'Hello', SECURE_RAND(4100), '\u00e4' || space(4100))"); + ResultSet rs; + rs = stat.executeQuery("select * from test"); + rs.next(); + byte[] b1 = rs.getBytes(3); + String s1 = rs.getString(4); + + conn.close(); + Recover.main("-dir", getBaseDir(), "-db", "toolsRecover"); + + // deleteDb would delete the .lob.db directory as well + // deleteDb("toolsRecover"); + ArrayList list = FileLister.getDatabaseFiles(getBaseDir(), + "toolsRecover", true); + for (String fileName : list) { + if (!FileUtils.isDirectory(fileName)) { + FileUtils.delete(fileName); + } + } + + conn = getConnection(url); + stat = conn.createStatement(); + String suffix = ".h2.sql"; + stat.execute("runscript from '" + getBaseDir() + "/toolsRecover" + + suffix + "'"); + rs = stat.executeQuery("select * from \"test 2\""); + assertFalse(rs.next()); + rs = stat.executeQuery("select * from test"); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertEquals("Hello", rs.getString(2)); + byte[] b2 = rs.getBytes(3); + String s2 = rs.getString(4); + assertEquals("\u00e4 ", s2.substring(0, 2)); + assertEquals(4100, b2.length); + assertEquals(4101, s2.length()); + assertEquals(b1, b2); + assertEquals(s1, s2); + assertFalse(rs.next()); + conn.close(); + deleteDb("toolsRecover"); + FileUtils.delete(getBaseDir() + "/toolsRecover.h2.sql"); + String dir = getBaseDir() + "/toolsRecover.lobs.db"; + FileUtils.deleteRecursive(dir, false); + } + + private void testManagementDb() throws SQLException { + int count = getSize(2, 10); + for (int i = 0; i < count; i++) { + Server tcpServer = Server. + createTcpServer().start(); + tcpServer.stop(); + tcpServer = Server.createTcpServer("-tcpPassword", "abc").start(); + tcpServer.stop(); + } + } + + private void testScriptRunscriptLob() throws Exception { + String url = getURL("jdbc:h2:" + getBaseDir() + + "/testScriptRunscriptLob", true); + String user = "sa", password = "abc"; + String fileName = getBaseDir() + "/b2.sql"; + Connection conn = getConnection(url, user, password); + conn.createStatement().execute( + "CREATE TABLE TEST(ID INT PRIMARY KEY, BDATA BLOB, CDATA CLOB)"); + PreparedStatement prep = conn.prepareStatement( + "INSERT INTO TEST VALUES(?, ?, ?)"); + + prep.setInt(1, 1); + prep.setNull(2, Types.BLOB); + prep.setNull(3, Types.CLOB); + prep.execute(); + + prep.setInt(1, 2); + prep.setString(2, "face"); + prep.setString(3, "face"); + prep.execute(); + + Random random = new Random(1); + prep.setInt(1, 3); + byte[] large = new byte[getSize(10 * 1024, 100 * 1024)]; + random.nextBytes(large); + prep.setBytes(2, large); + String largeText = new String(large, StandardCharsets.ISO_8859_1); + prep.setString(3, largeText); + prep.execute(); + + for (int i = 0; i < 2; i++) { + ResultSet rs = conn.createStatement().executeQuery( + "SELECT * FROM TEST ORDER BY ID"); + rs.next(); + assertEquals(1, rs.getInt(1)); + assertNull(rs.getString(2)); + assertNull(rs.getString(3)); + rs.next(); + assertEquals(2, rs.getInt(1)); + assertEquals("face", rs.getString(2)); + assertEquals("face", rs.getString(3)); + rs.next(); + assertEquals(3, rs.getInt(1)); + assertEquals(large, rs.getBytes(2)); + assertEquals(largeText, rs.getString(3)); + assertFalse(rs.next()); + + conn.close(); + Script.main("-url", url, "-user", user, "-password", password, + "-script", fileName); + DeleteDbFiles.main("-dir", getBaseDir(), "-db", + "testScriptRunscriptLob", "-quiet"); + RunScript.main("-url", url, "-user", user, "-password", password, + "-script", fileName); + conn = getConnection("jdbc:h2:" + getBaseDir() + + "/testScriptRunscriptLob", "sa", "abc"); + } + conn.close(); + + } + + private void testScriptRunscript() throws Exception { + String url = getURL("jdbc:h2:" + getBaseDir() + "/testScriptRunscript", + true); + String user = "sa", password = "abc"; + String fileName = getBaseDir() + "/b2.sql"; + DeleteDbFiles.main("-dir", getBaseDir(), "-db", "testScriptRunscript", + "-quiet"); + Connection conn = getConnection(url, user, password); + conn.createStatement().execute("CREATE TABLE \u00f6()"); + conn.createStatement().execute( + "CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + conn.createStatement().execute("INSERT INTO TEST VALUES(1, 'Hello')"); + conn.close(); + Script.main("-url", url, "-user", user, "-password", password, + "-script", fileName, "-options", "nodata", "compression", + "lzf", "cipher", "aes", "password", "'123'", "charset", + "'utf-8'"); + Script.main("-url", url, "-user", user, "-password", password, + "-script", fileName + ".txt"); + DeleteDbFiles.main("-dir", getBaseDir(), "-db", "testScriptRunscript", + "-quiet"); + RunScript.main("-url", url, "-user", user, "-password", password, + "-script", fileName, "-options", "compression", "lzf", + "cipher", "aes", "password", "'123'", "charset", "'utf-8'"); + conn = getConnection( + "jdbc:h2:" + getBaseDir() + "/testScriptRunscript", "sa", "abc"); + ResultSet rs = conn.createStatement() + .executeQuery("SELECT * FROM TEST"); + assertFalse(rs.next()); + rs = conn.createStatement().executeQuery("SELECT * FROM \u00f6"); + assertFalse(rs.next()); + conn.close(); + + DeleteDbFiles.main("-dir", getBaseDir(), "-db", "testScriptRunscript", + "-quiet"); + RunScript tool = new RunScript(); + ByteArrayOutputStream buff = new ByteArrayOutputStream(); + tool.setOut(new PrintStream(buff, false, "UTF-8")); + tool.runTool("-url", url, "-user", user, "-password", password, + "-script", fileName + ".txt", "-showResults"); + assertContains(Utils10.byteArrayOutputStreamToString(buff, StandardCharsets.UTF_8), "Hello"); + + + // test parsing of BLOCKSIZE option + DeleteDbFiles.main("-dir", getBaseDir(), "-db", "testScriptRunscript", + "-quiet"); + conn = getConnection(url, user, password); + conn.createStatement().execute( + "CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + conn.close(); + Script.main("-url", url, "-user", user, "-password", password, + "-script", fileName, "-options", "simple", "blocksize", + "8192"); + } + + private void testBackupRestore() throws SQLException { + org.h2.Driver.load(); + String url = "jdbc:h2:" + getBaseDir() + "/testBackupRestore"; + String user = "sa", password = "abc"; + final String fileName = getBaseDir() + "/b2.zip"; + DeleteDbFiles.main("-dir", getBaseDir(), "-db", "testBackupRestore", + "-quiet"); + Connection conn = getConnection(url, user, password); + conn.createStatement().execute( + "CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); + conn.createStatement().execute("INSERT INTO TEST VALUES(1, 'Hello')"); + conn.close(); + Backup.main("-file", fileName, "-dir", getBaseDir(), "-db", + "testBackupRestore", "-quiet"); + DeleteDbFiles.main("-dir", getBaseDir(), "-db", "testBackupRestore", + "-quiet"); + Restore.main("-file", fileName, "-dir", getBaseDir(), "-db", + "testBackupRestore", "-quiet"); + conn = getConnection("jdbc:h2:" + getBaseDir() + "/testBackupRestore", + "sa", "abc"); + ResultSet rs = conn.createStatement() + .executeQuery("SELECT * FROM TEST"); + assertTrue(rs.next()); + assertFalse(rs.next()); + // must fail when the database is in use + assertThrows(ErrorCode.CANNOT_CHANGE_SETTING_WHEN_OPEN_1, + () -> Backup.main("-file", fileName, "-dir", getBaseDir(), "-db", "testBackupRestore")); + conn.close(); + DeleteDbFiles.main("-dir", getBaseDir(), "-db", "testBackupRestore", + "-quiet"); + } + + private void testChangeFileEncryption(boolean split) throws SQLException { + org.h2.Driver.load(); + final String dir = (split ? "split:19:" : "") + getBaseDir(); + String url = "jdbc:h2:" + dir + "/testChangeFileEncryption;CIPHER=AES"; + DeleteDbFiles.execute(dir, "testChangeFileEncryption", true); + Connection conn = getConnection(url, "sa", "abc 123"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, DATA CLOB) " + + "AS SELECT X, SPACE(3000) FROM SYSTEM_RANGE(1, 300)"); + conn.close(); + String[] args = { "-dir", dir, "-db", "testChangeFileEncryption", + "-cipher", "AES", "-decrypt", "abc", "-quiet" }; + new ChangeFileEncryption().runTool(args); + args = new String[] { "-dir", dir, "-db", "testChangeFileEncryption", + "-cipher", "AES", "-encrypt", "def", "-quiet" }; + new ChangeFileEncryption().runTool(args); + conn = getConnection(url, "sa", "def 123"); + stat = conn.createStatement(); + stat.execute("SELECT * FROM TEST"); + assertThrows(ErrorCode.CANNOT_CHANGE_SETTING_WHEN_OPEN_1, + () -> new ChangeFileEncryption().runTool(new String[] { "-dir", dir, "-db", "testChangeFileEncryption", + "-cipher", "AES", "-decrypt", "def", "-quiet" })); + conn.close(); + args = new String[] { "-dir", dir, "-db", "testChangeFileEncryption", + "-quiet" }; + DeleteDbFiles.main(args); + } + + private void testChangeFileEncryptionWithWrongPassword() throws SQLException { + org.h2.Driver.load(); + final String dir = getBaseDir(); + String url = "jdbc:h2:" + dir + "/testChangeFileEncryption;CIPHER=AES"; + DeleteDbFiles.execute(dir, "testChangeFileEncryption", true); + Connection conn = getConnection(url, "sa", "abc 123"); + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, DATA CLOB) " + + "AS SELECT X, SPACE(3000) FROM SYSTEM_RANGE(1, 300)"); + conn.close(); + // try with wrong password, this used to have a bug where it kept the + // file handle open + assertThrows(SQLException.class, () -> ChangeFileEncryption.execute(dir, "testChangeFileEncryption", "AES", + "wrong".toCharArray(), "def".toCharArray(), true)); + ChangeFileEncryption.execute(dir, "testChangeFileEncryption", + "AES", "abc".toCharArray(), "def".toCharArray(), + true); + + conn = getConnection(url, "sa", "def 123"); + stat = conn.createStatement(); + stat.execute("SELECT * FROM TEST"); + conn.close(); + String[] args = new String[] { "-dir", dir, "-db", "testChangeFileEncryption", "-quiet" }; + DeleteDbFiles.main(args); + } + + private void testServer() throws SQLException { + Connection conn; + try { + deleteDb("test"); + Server tcpServer = Server.createTcpServer("-ifNotExists", + "-baseDir", getBaseDir(), + "-tcpAllowOthers").start(); + remainingServers.add(tcpServer); + final int port = tcpServer.getPort(); + conn = getConnection("jdbc:h2:tcp://localhost:" + port + "/test", "sa", ""); + conn.close(); + // must not be able to use a different base dir + assertThrows(ErrorCode.IO_EXCEPTION_1, + () -> getConnection("jdbc:h2:tcp://localhost:" + port + "/../test", "sa", "")); + assertThrows(ErrorCode.IO_EXCEPTION_1, + () -> getConnection("jdbc:h2:tcp://localhost:" + port + "/../test2/test", "sa", "")); + assertThrows(ErrorCode.WRONG_USER_OR_PASSWORD, + () -> Server.shutdownTcpServer("tcp://localhost:" + port, "", true, false)); + tcpServer.stop(); + Server tcpServerWithPassword = Server.createTcpServer( + "-ifExists", + "-tcpPassword", "abc", + "-baseDir", getBaseDir()).start(); + final int prt = tcpServerWithPassword.getPort(); + remainingServers.add(tcpServerWithPassword); + // must not be able to create new db + assertThrows(ErrorCode.REMOTE_DATABASE_NOT_FOUND_1, + () -> getConnection("jdbc:h2:tcp://localhost:" + prt + "/test2", "sa", "")); + assertThrows(ErrorCode.REMOTE_DATABASE_NOT_FOUND_1, + () -> getConnection("jdbc:h2:tcp://localhost:" + prt + "/test2;ifexists=false", "sa", "")); + conn = getConnection("jdbc:h2:tcp://localhost:" + prt + "/test", "sa", ""); + conn.close(); + assertThrows(ErrorCode.WRONG_USER_OR_PASSWORD, + () -> Server.shutdownTcpServer("tcp://localhost:" + prt, "", true, false)); + conn = getConnection("jdbc:h2:tcp://localhost:" + prt + "/test", "sa", ""); + // conn.close(); + Server.shutdownTcpServer("tcp://localhost:" + prt, "abc", true, false); + // check that the database is closed + deleteDb("test"); + // server must have been closed + assertThrows(ErrorCode.CONNECTION_BROKEN_1, + () -> getConnection("jdbc:h2:tcp://localhost:" + prt + "/test", "sa", "")); + JdbcUtils.closeSilently(conn); + // Test filesystem prefix and escape from baseDir + deleteDb("testSplit"); + server = Server.createTcpServer("-ifNotExists", + "-baseDir", getBaseDir(), + "-tcpAllowOthers").start(); + final int p = server.getPort(); + conn = getConnection("jdbc:h2:tcp://localhost:" + p + "/split:testSplit", "sa", ""); + conn.close(); + + assertThrows(ErrorCode.IO_EXCEPTION_1, + () -> getConnection("jdbc:h2:tcp://localhost:" + p + "/../test", "sa", "")); + + server.stop(); + deleteDb("testSplit"); + } finally { + shutdownServers(); + } + } + + /** + * A simple Clob implementation. + */ + static class SimpleClob implements Clob { + + private final String data; + + SimpleClob(String data) { + this.data = data; + } + + /** + * Free the clob. + */ + @Override + public void free() throws SQLException { + // ignore + } + + @Override + public InputStream getAsciiStream() throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public Reader getCharacterStream() throws SQLException { + throw new UnsupportedOperationException(); + } + + /** + * Get the reader. + * + * @param pos the position + * @param length the length + * @return the reader + */ + @Override + public Reader getCharacterStream(long pos, long length) + throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public String getSubString(long pos, int length) throws SQLException { + return data; + } + + @Override + public long length() throws SQLException { + return data.length(); + } + + @Override + public long position(String search, long start) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public long position(Clob search, long start) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public OutputStream setAsciiStream(long pos) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public Writer setCharacterStream(long pos) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public int setString(long pos, String str) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public int setString(long pos, String str, int offset, int len) + throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public void truncate(long len) throws SQLException { + throw new UnsupportedOperationException(); + } + + } + + /** + * A simple Blob implementation. + */ + static class SimpleBlob implements Blob { + + private final byte[] data; + + SimpleBlob(byte[] data) { + this.data = data; + } + + /** + * Free the blob. + */ + @Override + public void free() throws SQLException { + // ignore + } + + @Override + public InputStream getBinaryStream() throws SQLException { + throw new UnsupportedOperationException(); + } + + /** + * Get the binary stream. + * + * @param pos the position + * @param length the length + * @return the input stream + */ + @Override + public InputStream getBinaryStream(long pos, long length) + throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public byte[] getBytes(long pos, int length) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public long length() throws SQLException { + return data.length; + } + + @Override + public long position(byte[] pattern, long start) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public long position(Blob pattern, long start) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public OutputStream setBinaryStream(long pos) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public int setBytes(long pos, byte[] bytes) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public int setBytes(long pos, byte[] bytes, int offset, int len) + throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public void truncate(long len) throws SQLException { + throw new UnsupportedOperationException(); + } + + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestTraceSystem.java b/h2/src/test/org/h2/test/unit/TestTraceSystem.java new file mode 100644 index 0000000..1c6c1e6 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestTraceSystem.java @@ -0,0 +1,78 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import org.h2.message.TraceSystem; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.util.Utils10; + +/** + * Tests the trace system + */ +public class TestTraceSystem extends TestBase { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testTraceDebug(); + testReadOnly(); + testAdapter(); + } + + private void testAdapter() { + TraceSystem ts = new TraceSystem(null); + ts.setName("test"); + ts.setLevelFile(TraceSystem.ADAPTER); + ts.getTrace("test").debug("test"); + ts.getTrace("test").info("test"); + ts.getTrace("test").error(new Exception(), "test"); + + // The used SLF4J-nop logger has all log levels disabled, + // so this should be reflected in the trace system. + assertFalse(ts.isEnabled(TraceSystem.INFO)); + assertFalse(ts.getTrace("test").isInfoEnabled()); + + ts.close(); + } + + private void testTraceDebug() throws Exception { + TraceSystem ts = new TraceSystem(null); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ts.setSysOut(new PrintStream(out, false, "UTF-8")); + ts.setLevelSystemOut(TraceSystem.DEBUG); + ts.getTrace("test").debug(new Exception("error"), "test"); + ts.close(); + String outString = Utils10.byteArrayOutputStreamToString(out, StandardCharsets.UTF_8); + assertContains(outString, "error"); + assertContains(outString, "Exception"); + assertContains(outString, "test"); + } + + private void testReadOnly() throws Exception { + String readOnlyFile = getBaseDir() + "/readOnly.log"; + FileUtils.delete(readOnlyFile); + FileUtils.newOutputStream(readOnlyFile, false).close(); + FileUtils.setReadOnly(readOnlyFile); + TraceSystem ts = new TraceSystem(readOnlyFile); + ts.setLevelFile(TraceSystem.INFO); + ts.getTrace("test").info("test"); + FileUtils.delete(readOnlyFile); + ts.close(); + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestUpgrade.java b/h2/src/test/org/h2/test/unit/TestUpgrade.java new file mode 100644 index 0000000..b448560 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestUpgrade.java @@ -0,0 +1,112 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.Properties; +import java.util.Random; + +import org.h2.engine.Constants; +import org.h2.store.fs.FilePath; +import org.h2.store.fs.FileUtils; +import org.h2.test.TestBase; +import org.h2.tools.Upgrade; + +/** + * Tests upgrade utility. + */ +public class TestUpgrade extends TestBase { + + /** + * Run just this test. + * + * @param a + * ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + deleteDb(); + testUpgrade(1, 2, 120); + testUpgrade(1, 4, 200); + } + + private void testUpgrade(int major, int minor, int build) throws Exception { + String baseDir = getBaseDir(); + String url = "jdbc:h2:" + baseDir + "/testUpgrade"; + Properties p = new Properties(); + p.put("user", "sa"); + p.put("password", "password"); + Random r = new Random(); + byte[] bytes = new byte[10_000]; + r.nextBytes(bytes); + String s = new String(bytes, StandardCharsets.ISO_8859_1); + java.sql.Driver driver = Upgrade.loadH2(build); + try { + assertEquals(major, driver.getMajorVersion()); + assertEquals(minor, driver.getMinorVersion()); + try (Connection conn = driver.connect(url, p)) { + Statement stat = conn.createStatement(); + stat.execute("CREATE TABLE TEST(ID BIGINT AUTO_INCREMENT PRIMARY KEY, B BINARY, L BLOB, C CLOB)"); + PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST(B, L, C) VALUES (?, ?, ?)"); + prep.setBytes(1, bytes); + prep.setBytes(2, bytes); + prep.setString(3, s); + prep.execute(); + } + } finally { + Upgrade.unloadH2(driver); + } + assertTrue(Upgrade.upgrade(url, p, build)); + try (Connection conn = DriverManager.getConnection(url, p)) { + Statement stat = conn.createStatement(); + try (ResultSet rs = stat.executeQuery("TABLE TEST")) { + assertTrue(rs.next()); + assertEquals(bytes, rs.getBytes(2)); + assertEquals(bytes, rs.getBytes(3)); + assertEquals(s, rs.getString(4)); + assertFalse(rs.next()); + } + try (ResultSet rs = stat.executeQuery("SELECT COLUMN_NAME, DATA_TYPE, CHARACTER_OCTET_LENGTH" + + " FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'TEST' ORDER BY ORDINAL_POSITION")) { + assertTrue(rs.next()); + assertEquals("ID", rs.getString(1)); + assertEquals("BIGINT", rs.getString(2)); + assertTrue(rs.next()); + assertEquals("B", rs.getString(1)); + assertEquals("BINARY VARYING", rs.getString(2)); + assertEquals(Constants.MAX_STRING_LENGTH, rs.getLong(3)); + assertTrue(rs.next()); + assertEquals("L", rs.getString(1)); + assertEquals("BINARY LARGE OBJECT", rs.getString(2)); + assertEquals(Long.MAX_VALUE, rs.getLong(3)); + assertTrue(rs.next()); + assertEquals("C", rs.getString(1)); + assertEquals("CHARACTER LARGE OBJECT", rs.getString(2)); + assertEquals(Long.MAX_VALUE, rs.getLong(3)); + assertFalse(rs.next()); + } + } + deleteDb(); + } + + private void deleteDb() { + for (FilePath p : FilePath.get(getBaseDir()).newDirectoryStream()) { + if (p.getName().startsWith("testUpgrade")) { + FileUtils.deleteRecursive(p.toString(), false); + } + } + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestUtils.java b/h2/src/test/org/h2/test/unit/TestUtils.java new file mode 100644 index 0000000..29fbefa --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestUtils.java @@ -0,0 +1,277 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Random; +import org.h2.test.TestBase; +import org.h2.util.Bits; +import org.h2.util.IOUtils; +import org.h2.util.Utils; + +/** + * Tests reflection utilities. + */ +public class TestUtils extends TestBase { + + /** + * Dummy field + */ + public final String testField = "abc"; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws Exception { + testIOUtils(); + testSortTopN(); + testSortTopNRandom(); + testWriteReadInt(); + testWriteReadLong(); + testGetNonPrimitiveClass(); + testGetNonPrimitiveClass(); + testGetNonPrimitiveClass(); + testReflectionUtils(); + testParseBoolean(); + } + + private void testIOUtils() throws IOException { + for (int i = 0; i < 20; i++) { + byte[] data = new byte[i]; + InputStream in = new ByteArrayInputStream(data); + byte[] buffer = new byte[i]; + assertEquals(0, IOUtils.readFully(in, buffer, -2)); + assertEquals(0, IOUtils.readFully(in, buffer, -1)); + assertEquals(0, IOUtils.readFully(in, buffer, 0)); + for (int j = 1, off = 0;; j += 1) { + int read = Math.max(0, Math.min(i - off, j)); + int l = IOUtils.readFully(in, buffer, j); + assertEquals(read, l); + off += l; + if (l == 0) { + break; + } + } + assertEquals(0, IOUtils.readFully(in, buffer, 1)); + } + for (int i = 0; i < 10; i++) { + char[] data = new char[i]; + Reader in = new StringReader(new String(data)); + char[] buffer = new char[i]; + assertEquals(0, IOUtils.readFully(in, buffer, -2)); + assertEquals(0, IOUtils.readFully(in, buffer, -1)); + assertEquals(0, IOUtils.readFully(in, buffer, 0)); + for (int j = 1, off = 0;; j += 1) { + int read = Math.max(0, Math.min(i - off, j)); + int l = IOUtils.readFully(in, buffer, j); + assertEquals(read, l); + off += l; + if (l == 0) { + break; + } + } + assertEquals(0, IOUtils.readFully(in, buffer, 1)); + } + } + + private void testWriteReadInt() { + byte[] buff = new byte[4]; + for (int x : new int[]{Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 1, -1, + Short.MIN_VALUE, Short.MAX_VALUE}) { + testIntImpl1(buff, x); + } + Random r = new Random(1); + for (int i = 0; i < 1000; i++) { + testIntImpl1(buff, r.nextInt()); + } + } + + private void testIntImpl1(byte[] buff, int x) { + int r = Integer.reverseBytes(x); + Bits.writeInt(buff, 0, x); + testIntImpl2(buff, x, r); + Bits.writeIntLE(buff, 0, x); + testIntImpl2(buff, r, x); + } + + private void testIntImpl2(byte[] buff, int x, int r) { + assertEquals(x, Bits.readInt(buff, 0)); + assertEquals(r, Bits.readIntLE(buff, 0)); + } + + private void testWriteReadLong() { + byte[] buff = new byte[8]; + for (long x : new long[]{Long.MIN_VALUE, Long.MAX_VALUE, 0, 1, -1, + Integer.MIN_VALUE, Integer.MAX_VALUE}) { + testLongImpl1(buff, x); + } + Random r = new Random(1); + for (int i = 0; i < 1000; i++) { + testLongImpl1(buff, r.nextLong()); + } + } + + private void testLongImpl1(byte[] buff, long x) { + long r = Long.reverseBytes(x); + Bits.writeLong(buff, 0, x); + testLongImpl2(buff, x, r); + Bits.writeLongLE(buff, 0, x); + testLongImpl2(buff, r, x); + Bits.writeDouble(buff, 0, Double.longBitsToDouble(x)); + testLongImpl2(buff, x, r); + Bits.writeDoubleLE(buff, 0, Double.longBitsToDouble(x)); + testLongImpl2(buff, r, x); + } + + private void testLongImpl2(byte[] buff, long x, long r) { + assertEquals(x, Bits.readLong(buff, 0)); + assertEquals(r, Bits.readLongLE(buff, 0)); + assertEquals(Double.longBitsToDouble(x), Bits.readDouble(buff, 0)); + assertEquals(Double.longBitsToDouble(r), Bits.readDoubleLE(buff, 0)); + } + + private void testSortTopN() { + Comparator comp = Comparator.naturalOrder(); + Integer[] arr = new Integer[] {}; + Utils.sortTopN(arr, 0, 0, comp); + + arr = new Integer[] { 1 }; + Utils.sortTopN(arr, 0, 1, comp); + + arr = new Integer[] { 3, 5, 1, 4, 2 }; + Utils.sortTopN(arr, 0, 2, comp); + assertEquals(arr[0].intValue(), 1); + assertEquals(arr[1].intValue(), 2); + } + + private void testSortTopNRandom() { + Random rnd = new Random(); + Comparator comp = Comparator.naturalOrder(); + for (int z = 0; z < 10000; z++) { + int length = 1 + rnd.nextInt(500); + Integer[] arr = new Integer[length]; + for (int i = 0; i < length; i++) { + arr[i] = rnd.nextInt(50); + } + Integer[] arr2 = Arrays.copyOf(arr, length); + int offset = rnd.nextInt(length); + int limit = rnd.nextInt(length - offset + 1); + Utils.sortTopN(arr, offset, offset + limit, comp); + Arrays.sort(arr2, comp); + for (int i = offset, end = offset + limit; i < end; i++) { + if (!arr[i].equals(arr2[i])) { + fail(offset + " " + end + "\n" + Arrays.toString(arr) + + "\n" + Arrays.toString(arr2)); + } + } + } + } + + private void testGetNonPrimitiveClass() { + testGetNonPrimitiveClass(BigInteger.class, BigInteger.class); + testGetNonPrimitiveClass(Boolean.class, boolean.class); + testGetNonPrimitiveClass(Byte.class, byte.class); + testGetNonPrimitiveClass(Character.class, char.class); + testGetNonPrimitiveClass(Byte.class, byte.class); + testGetNonPrimitiveClass(Double.class, double.class); + testGetNonPrimitiveClass(Float.class, float.class); + testGetNonPrimitiveClass(Integer.class, int.class); + testGetNonPrimitiveClass(Long.class, long.class); + testGetNonPrimitiveClass(Short.class, short.class); + testGetNonPrimitiveClass(Void.class, void.class); + } + + private void testGetNonPrimitiveClass(Class expected, Class p) { + assertEquals(expected.getName(), Utils.getNonPrimitiveClass(p).getName()); + } + + private void testReflectionUtils() throws Exception { + // Static method call + long currentTimeNanos1 = System.nanoTime(); + long currentTimeNanos2 = (Long) Utils.callStaticMethod( + "java.lang.System.nanoTime"); + assertTrue(currentTimeNanos1 <= currentTimeNanos2); + // New Instance + Object instance = Utils.newInstance("java.lang.StringBuilder"); + // New Instance with int parameter + instance = Utils.newInstance("java.lang.StringBuilder", 10); + // StringBuilder.append or length don't work on JDK 5 due to + // http://bugs.sun.com/view_bug.do?bug_id=4283544 + instance = Utils.newInstance("java.lang.Integer", 10); + // Instance methods + long x = (Long) Utils.callMethod(instance, "longValue"); + assertEquals(10, x); + // Instance fields + Utils.callStaticMethod("java.lang.String.valueOf", "a"); + Utils.callStaticMethod("java.awt.AWTKeyStroke.getAWTKeyStroke", + 'x', java.awt.event.InputEvent.SHIFT_DOWN_MASK); + } + + private void testParseBooleanCheckFalse(String value) { + assertFalse(Utils.parseBoolean(value, false, false)); + assertFalse(Utils.parseBoolean(value, false, true)); + assertFalse(Utils.parseBoolean(value, true, false)); + assertFalse(Utils.parseBoolean(value, true, true)); + } + + private void testParseBooleanCheckTrue(String value) { + assertTrue(Utils.parseBoolean(value, false, false)); + assertTrue(Utils.parseBoolean(value, false, true)); + assertTrue(Utils.parseBoolean(value, true, false)); + assertTrue(Utils.parseBoolean(value, true, true)); + } + + private void testParseBoolean() { + // Test for default value in case of null + assertFalse(Utils.parseBoolean(null, false, false)); + assertFalse(Utils.parseBoolean(null, false, true)); + assertTrue(Utils.parseBoolean(null, true, false)); + assertTrue(Utils.parseBoolean(null, true, true)); + // Test assorted valid strings + testParseBooleanCheckFalse("0"); + testParseBooleanCheckFalse("f"); + testParseBooleanCheckFalse("F"); + testParseBooleanCheckFalse("n"); + testParseBooleanCheckFalse("N"); + testParseBooleanCheckFalse("no"); + testParseBooleanCheckFalse("No"); + testParseBooleanCheckFalse("NO"); + testParseBooleanCheckFalse("false"); + testParseBooleanCheckFalse("False"); + testParseBooleanCheckFalse("FALSE"); + testParseBooleanCheckTrue("1"); + testParseBooleanCheckTrue("t"); + testParseBooleanCheckTrue("T"); + testParseBooleanCheckTrue("y"); + testParseBooleanCheckTrue("Y"); + testParseBooleanCheckTrue("yes"); + testParseBooleanCheckTrue("Yes"); + testParseBooleanCheckTrue("YES"); + testParseBooleanCheckTrue("true"); + testParseBooleanCheckTrue("True"); + testParseBooleanCheckTrue("TRUE"); + // Test other values + assertFalse(Utils.parseBoolean("BAD", false, false)); + assertTrue(Utils.parseBoolean("BAD", true, false)); + assertThrows(IllegalArgumentException.class, () -> Utils.parseBoolean("BAD", false, true)); + assertThrows(IllegalArgumentException.class, () -> Utils.parseBoolean("BAD", true, true)); + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestValue.java b/h2/src/test/org/h2/test/unit/TestValue.java new file mode 100644 index 0000000..d04d2e1 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestValue.java @@ -0,0 +1,552 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import static org.h2.engine.Constants.MAX_ARRAY_CARDINALITY; +import static org.h2.engine.Constants.MAX_NUMERIC_PRECISION; +import static org.h2.engine.Constants.MAX_STRING_LENGTH; + +import java.io.ByteArrayInputStream; +import java.io.InputStreamReader; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.Date; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Arrays; +import java.util.Calendar; +import java.util.TimeZone; +import java.util.UUID; +import org.h2.api.ErrorCode; +import org.h2.api.H2Type; +import org.h2.engine.Database; +import org.h2.engine.SessionLocal; +import org.h2.jdbc.JdbcConnection; +import org.h2.store.DataHandler; +import org.h2.test.TestBase; +import org.h2.test.TestDb; +import org.h2.util.Bits; +import org.h2.util.JdbcUtils; +import org.h2.util.LegacyDateTimeUtils; +import org.h2.value.TypeInfo; +import org.h2.value.Value; +import org.h2.value.ValueArray; +import org.h2.value.ValueBlob; +import org.h2.value.ValueClob; +import org.h2.value.ValueDouble; +import org.h2.value.ValueInterval; +import org.h2.value.ValueJavaObject; +import org.h2.value.ValueNumeric; +import org.h2.value.ValueReal; +import org.h2.value.ValueTimestamp; +import org.h2.value.ValueToObjectConverter2; +import org.h2.value.ValueUuid; +import org.h2.value.ValueVarbinary; +import org.h2.value.ValueVarchar; + +/** + * Tests features of values. + */ +public class TestValue extends TestDb { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + TestBase.createCaller().init().testFromMain(); + } + + @Override + public void test() throws SQLException { + testBinaryAndUuid(); + testCastTrim(); + testDataType(); + testArray(); + testUUID(); + testDouble(false); + testDouble(true); + testTimestamp(); + testModulusDouble(); + testModulusDecimal(); + testModulusOperator(); + testLobComparison(); + testTypeInfo(); + testH2Type(); + testHigherType(); + } + + private void testBinaryAndUuid() throws SQLException { + try (Connection conn = getConnection("binaryAndUuid")) { + UUID uuid = UUID.randomUUID(); + PreparedStatement prep; + ResultSet rs; + // Check conversion to byte[] + prep = conn.prepareStatement("SELECT * FROM TABLE(X BINARY(16)=?)"); + prep.setObject(1, new Object[] { uuid }); + rs = prep.executeQuery(); + rs.next(); + assertTrue(Arrays.equals(Bits.uuidToBytes(uuid), (byte[]) rs.getObject(1))); + // Check conversion to byte[] + prep = conn.prepareStatement("SELECT * FROM TABLE(X VARBINARY=?)"); + prep.setObject(1, new Object[] { uuid }); + rs = prep.executeQuery(); + rs.next(); + assertTrue(Arrays.equals(Bits.uuidToBytes(uuid), (byte[]) rs.getObject(1))); + // Check that type is not changed + prep = conn.prepareStatement("SELECT * FROM TABLE(X UUID=?)"); + prep.setObject(1, new Object[] { uuid }); + rs = prep.executeQuery(); + rs.next(); + assertEquals(uuid, rs.getObject(1)); + } finally { + deleteDb("binaryAndUuid"); + } + } + + private void testCastTrim() { + Value v; + String spaces = new String(new char[100]).replace((char) 0, ' '); + + v = ValueArray.get(new Value[] { ValueVarchar.get("hello"), ValueVarchar.get("world") }, null); + TypeInfo typeInfo = TypeInfo.getTypeInfo(Value.ARRAY, 1L, 0, TypeInfo.TYPE_VARCHAR); + assertEquals(2, v.getType().getPrecision()); + assertEquals(1, v.castTo(typeInfo, null).getType().getPrecision()); + v = ValueArray.get(new Value[]{ValueVarchar.get(""), ValueVarchar.get("")}, null); + assertEquals(2, v.getType().getPrecision()); + assertEquals("ARRAY ['']", v.castTo(typeInfo, null).toString()); + + v = ValueVarbinary.get(spaces.getBytes()); + typeInfo = TypeInfo.getTypeInfo(Value.VARBINARY, 10L, 0, null); + assertEquals(100, v.getType().getPrecision()); + assertEquals(10, v.castTo(typeInfo, null).getType().getPrecision()); + assertEquals(10, v.castTo(typeInfo, null).getBytes().length); + assertEquals(32, v.castTo(typeInfo, null).getBytes()[9]); + assertEquals(10, v.castTo(typeInfo, null).getType().getPrecision()); + + v = ValueClob.createSmall(spaces.getBytes(), 100); + typeInfo = TypeInfo.getTypeInfo(Value.CLOB, 10L, 0, null); + assertEquals(100, v.getType().getPrecision()); + assertEquals(10, v.castTo(typeInfo, null).getType().getPrecision()); + assertEquals(10, v.castTo(typeInfo, null).getString().length()); + assertEquals(" ", v.castTo(typeInfo, null).getString()); + assertEquals(10, v.castTo(typeInfo, null).getType().getPrecision()); + + v = ValueBlob.createSmall(spaces.getBytes()); + typeInfo = TypeInfo.getTypeInfo(Value.BLOB, 10L, 0, null); + assertEquals(100, v.getType().getPrecision()); + assertEquals(10, v.castTo(typeInfo, null).getType().getPrecision()); + assertEquals(10, v.castTo(typeInfo, null).getBytes().length); + assertEquals(32, v.castTo(typeInfo, null).getBytes()[9]); + assertEquals(10, v.castTo(typeInfo, null).getType().getPrecision()); + + v = ValueVarchar.get(spaces); + typeInfo = TypeInfo.getTypeInfo(Value.VARCHAR, 10L, 0, null); + assertEquals(100, v.getType().getPrecision()); + assertEquals(10, v.castTo(typeInfo, null).getType().getPrecision()); + assertEquals(" ", v.castTo(typeInfo, null).getString()); + assertEquals(" ", v.castTo(typeInfo, null).getString()); + + } + + private void testDataType() { + testDataType(TypeInfo.TYPE_NULL, null); + testDataType(TypeInfo.TYPE_NULL, Void.class); + testDataType(TypeInfo.TYPE_NULL, void.class); + testDataType(TypeInfo.getTypeInfo(Value.ARRAY, Integer.MAX_VALUE, 0, TypeInfo.TYPE_VARCHAR), String[].class); + testDataType(TypeInfo.TYPE_VARCHAR, String.class); + testDataType(TypeInfo.TYPE_INTEGER, Integer.class); + testDataType(TypeInfo.TYPE_BIGINT, Long.class); + testDataType(TypeInfo.TYPE_BOOLEAN, Boolean.class); + testDataType(TypeInfo.TYPE_DOUBLE, Double.class); + testDataType(TypeInfo.TYPE_TINYINT, Byte.class); + testDataType(TypeInfo.TYPE_SMALLINT, Short.class); + testDataType(TypeInfo.TYPE_REAL, Float.class); + testDataType(TypeInfo.TYPE_VARBINARY, byte[].class); + testDataType(TypeInfo.TYPE_UUID, UUID.class); + testDataType(TypeInfo.TYPE_NULL, Void.class); + testDataType(TypeInfo.TYPE_NUMERIC_FLOATING_POINT, BigDecimal.class); + testDataType(TypeInfo.TYPE_DATE, Date.class); + testDataType(TypeInfo.TYPE_TIME, Time.class); + testDataType(TypeInfo.TYPE_TIMESTAMP, Timestamp.class); + testDataType(TypeInfo.TYPE_TIMESTAMP, java.util.Date.class); + testDataType(TypeInfo.TYPE_CLOB, java.io.Reader.class); + testDataType(TypeInfo.TYPE_CLOB, java.sql.Clob.class); + testDataType(TypeInfo.TYPE_BLOB, java.io.InputStream.class); + testDataType(TypeInfo.TYPE_BLOB, java.sql.Blob.class); + testDataType(TypeInfo.getTypeInfo(Value.ARRAY, Integer.MAX_VALUE, 0, TypeInfo.TYPE_JAVA_OBJECT), + Object[].class); + testDataType(TypeInfo.TYPE_JAVA_OBJECT, StringBuffer.class); + } + + private void testDataType(TypeInfo type, Class clazz) { + assertEquals(type, ValueToObjectConverter2.classToType(clazz)); + } + + private void testDouble(boolean useFloat) { + double[] d = { + Double.NEGATIVE_INFINITY, + -1, + 0, + 1, + Double.POSITIVE_INFINITY, + Double.NaN + }; + int[] signum = { + -1, + -1, + 0, + 1, + 1, + 0 + }; + Value[] values = new Value[d.length]; + for (int i = 0; i < d.length; i++) { + Value v = useFloat ? (Value) ValueReal.get((float) d[i]) + : (Value) ValueDouble.get(d[i]); + values[i] = v; + assertTrue(values[i].compareTypeSafe(values[i], null, null) == 0); + assertTrue(v.equals(v)); + assertEquals(signum[i], v.getSignum()); + } + for (int i = 0; i < d.length - 1; i++) { + assertTrue(values[i].compareTypeSafe(values[i+1], null, null) < 0); + assertTrue(values[i + 1].compareTypeSafe(values[i], null, null) > 0); + assertFalse(values[i].equals(values[i+1])); + } + } + + private void testTimestamp() { + ValueTimestamp valueTs = ValueTimestamp.parse("2000-01-15 10:20:30.333222111", null); + Timestamp ts = Timestamp.valueOf("2000-01-15 10:20:30.333222111"); + assertEquals(ts.toString(), valueTs.getString()); + assertEquals(ts, LegacyDateTimeUtils.toTimestamp(null, null, valueTs)); + Calendar c = Calendar.getInstance(TimeZone.getTimeZone("Europe/Berlin")); + c.set(2018, 02, 25, 1, 59, 00); + c.set(Calendar.MILLISECOND, 123); + long expected = c.getTimeInMillis(); + ts = LegacyDateTimeUtils.toTimestamp(null, null, + ValueTimestamp.parse("2018-03-25 01:59:00.123123123 Europe/Berlin", null)); + assertEquals(expected, ts.getTime()); + assertEquals(123123123, ts.getNanos()); + ts = LegacyDateTimeUtils.toTimestamp(null, null, + ValueTimestamp.parse("2018-03-25 01:59:00.123123123+01", null)); + assertEquals(expected, ts.getTime()); + assertEquals(123123123, ts.getNanos()); + expected += 60000; // 1 minute + ts = LegacyDateTimeUtils.toTimestamp(null, null, + ValueTimestamp.parse("2018-03-25 03:00:00.123123123 Europe/Berlin", null)); + assertEquals(expected, ts.getTime()); + assertEquals(123123123, ts.getNanos()); + ts = LegacyDateTimeUtils.toTimestamp(null, null, + ValueTimestamp.parse("2018-03-25 03:00:00.123123123+02", null)); + assertEquals(expected, ts.getTime()); + assertEquals(123123123, ts.getNanos()); + } + + private void testArray() { + ValueArray src = ValueArray.get( + new Value[] {ValueVarchar.get("1"), ValueVarchar.get("22"), ValueVarchar.get("333")}, null); + assertEquals(3, src.getType().getPrecision()); + assertSame(src, src.castTo(TypeInfo.getTypeInfo(Value.ARRAY, 3L, 0, TypeInfo.TYPE_VARCHAR), null)); + ValueArray exp = ValueArray.get( + new Value[] {ValueVarchar.get("1"), ValueVarchar.get("22")}, null); + Value got = src.castTo(TypeInfo.getTypeInfo(Value.ARRAY, 2L, 0, TypeInfo.TYPE_VARCHAR), null); + assertEquals(exp, got); + assertEquals(Value.VARCHAR, ((ValueArray) got).getComponentType().getValueType()); + exp = ValueArray.get(TypeInfo.TYPE_VARCHAR, new Value[0], null); + got = src.castTo(TypeInfo.getTypeInfo(Value.ARRAY, 0L, 0, TypeInfo.TYPE_VARCHAR), null); + assertEquals(exp, got); + assertEquals(Value.VARCHAR, ((ValueArray) got).getComponentType().getValueType()); + } + + private void testUUID() { + long maxHigh = 0, maxLow = 0, minHigh = -1L, minLow = -1L; + for (int i = 0; i < 100; i++) { + ValueUuid uuid = ValueUuid.getNewRandom(); + maxHigh |= uuid.getHigh(); + maxLow |= uuid.getLow(); + minHigh &= uuid.getHigh(); + minLow &= uuid.getLow(); + } + ValueUuid max = ValueUuid.get(maxHigh, maxLow); + assertEquals("ffffffff-ffff-4fff-bfff-ffffffffffff", max.getString()); + ValueUuid min = ValueUuid.get(minHigh, minLow); + assertEquals("00000000-0000-4000-8000-000000000000", min.getString()); + + // Test conversion from ValueJavaObject to ValueUuid + String uuidStr = "12345678-1234-4321-8765-123456789012"; + + UUID origUUID = UUID.fromString(uuidStr); + ValueJavaObject valObj = ValueJavaObject.getNoCopy(JdbcUtils.serialize(origUUID, null)); + ValueUuid valUUID = valObj.convertToUuid(); + assertEquals(uuidStr, valUUID.getString()); + assertEquals(origUUID, valUUID.getUuid()); + + ValueJavaObject voString = ValueJavaObject.getNoCopy(JdbcUtils.serialize( + new String("This is not a ValueUuid object"), null)); + assertThrows(ErrorCode.DESERIALIZATION_FAILED_1, () -> voString.convertToUuid()); + } + + private void testModulusDouble() { + final ValueDouble vd1 = ValueDouble.get(12); + assertThrows(ErrorCode.DIVISION_BY_ZERO_1, () -> vd1.modulus(ValueDouble.ZERO)); + ValueDouble vd2 = ValueDouble.get(10); + ValueDouble vd3 = vd1.modulus(vd2); + assertEquals(2, vd3.getDouble()); + } + + private void testModulusDecimal() { + final ValueNumeric vd1 = ValueNumeric.get(new BigDecimal(12)); + assertThrows(ErrorCode.DIVISION_BY_ZERO_1, () -> vd1.modulus(ValueNumeric.ZERO)); + ValueNumeric vd2 = ValueNumeric.get(new BigDecimal(10)); + Value vd3 = vd1.modulus(vd2); + assertEquals(2, vd3.getDouble()); + } + + private void testModulusOperator() throws SQLException { + try (Connection conn = getConnection("modulus")) { + ResultSet rs = conn.createStatement().executeQuery("CALL 12 % 10"); + rs.next(); + assertEquals(2, rs.getInt(1)); + } finally { + deleteDb("modulus"); + } + } + + private void testLobComparison() throws SQLException { + assertEquals(0, testLobComparisonImpl(null, Value.BLOB, 0, 0, 0, 0)); + assertEquals(0, testLobComparisonImpl(null, Value.CLOB, 0, 0, 0, 0)); + assertEquals(-1, testLobComparisonImpl(null, Value.BLOB, 1, 1, 200, 210)); + assertEquals(-1, testLobComparisonImpl(null, Value.CLOB, 1, 1, 'a', 'b')); + assertEquals(1, testLobComparisonImpl(null, Value.BLOB, 512, 512, 210, 200)); + assertEquals(1, testLobComparisonImpl(null, Value.CLOB, 512, 512, 'B', 'A')); + try (Connection c = DriverManager.getConnection("jdbc:h2:mem:testValue")) { + Database dh = ((SessionLocal) ((JdbcConnection) c).getSession()).getDatabase(); + assertEquals(1, testLobComparisonImpl(dh, Value.BLOB, 1_024, 1_024, 210, 200)); + assertEquals(1, testLobComparisonImpl(dh, Value.CLOB, 1_024, 1_024, 'B', 'A')); + assertEquals(-1, testLobComparisonImpl(dh, Value.BLOB, 10_000, 10_000, 200, 210)); + assertEquals(-1, testLobComparisonImpl(dh, Value.CLOB, 10_000, 10_000, 'a', 'b')); + assertEquals(0, testLobComparisonImpl(dh, Value.BLOB, 10_000, 10_000, 0, 0)); + assertEquals(0, testLobComparisonImpl(dh, Value.CLOB, 10_000, 10_000, 0, 0)); + assertEquals(-1, testLobComparisonImpl(dh, Value.BLOB, 1_000, 10_000, 0, 0)); + assertEquals(-1, testLobComparisonImpl(dh, Value.CLOB, 1_000, 10_000, 0, 0)); + assertEquals(1, testLobComparisonImpl(dh, Value.BLOB, 10_000, 1_000, 0, 0)); + assertEquals(1, testLobComparisonImpl(dh, Value.CLOB, 10_000, 1_000, 0, 0)); + } + } + + private static int testLobComparisonImpl(DataHandler dh, int type, int size1, int size2, int suffix1, + int suffix2) { + byte[] bytes1 = new byte[size1]; + byte[] bytes2 = new byte[size2]; + if (size1 > 0) { + bytes1[size1 - 1] = (byte) suffix1; + } + if (size2 > 0) { + bytes2[size2 - 1] = (byte) suffix2; + } + Value lob1 = createLob(dh, type, bytes1); + Value lob2 = createLob(dh, type, bytes2); + return lob1.compareTypeSafe(lob2, null, null); + } + + private static Value createLob(DataHandler dh, int type, byte[] bytes) { + if (dh == null) { + return type == Value.BLOB ? ValueBlob.createSmall(bytes) : ValueClob.createSmall(bytes); + } + ByteArrayInputStream in = new ByteArrayInputStream(bytes); + if (type == Value.BLOB) { + return dh.getLobStorage().createBlob(in, -1); + } else { + return dh.getLobStorage().createClob(new InputStreamReader(in, StandardCharsets.UTF_8), -1); + } + } + + private void testTypeInfo() { + testTypeInfoCheck(Value.UNKNOWN, -1, -1, -1, TypeInfo.TYPE_UNKNOWN); + assertThrows(ErrorCode.UNKNOWN_DATA_TYPE_1, () -> TypeInfo.getTypeInfo(Value.UNKNOWN)); + + testTypeInfoCheck(Value.NULL, 1, 0, 4, TypeInfo.TYPE_NULL, TypeInfo.getTypeInfo(Value.NULL)); + + testTypeInfoCheck(Value.BOOLEAN, 1, 0, 5, TypeInfo.TYPE_BOOLEAN, TypeInfo.getTypeInfo(Value.BOOLEAN)); + + testTypeInfoCheck(Value.TINYINT, 8, 0, 4, TypeInfo.TYPE_TINYINT, TypeInfo.getTypeInfo(Value.TINYINT)); + testTypeInfoCheck(Value.SMALLINT, 16, 0, 6, TypeInfo.TYPE_SMALLINT, TypeInfo.getTypeInfo(Value.SMALLINT)); + testTypeInfoCheck(Value.INTEGER, 32, 0, 11, TypeInfo.TYPE_INTEGER, TypeInfo.getTypeInfo(Value.INTEGER)); + testTypeInfoCheck(Value.BIGINT, 64, 0, 20, TypeInfo.TYPE_BIGINT, TypeInfo.getTypeInfo(Value.BIGINT)); + + testTypeInfoCheck(Value.REAL, 24, 0, 15, TypeInfo.TYPE_REAL, TypeInfo.getTypeInfo(Value.REAL)); + testTypeInfoCheck(Value.DOUBLE, 53, 0, 24, TypeInfo.TYPE_DOUBLE, TypeInfo.getTypeInfo(Value.DOUBLE)); + testTypeInfoCheck(Value.NUMERIC, MAX_NUMERIC_PRECISION, MAX_NUMERIC_PRECISION / 2, MAX_NUMERIC_PRECISION + 2, + TypeInfo.TYPE_NUMERIC_FLOATING_POINT); + + testTypeInfoCheck(Value.TIME, 18, 9, 18, TypeInfo.TYPE_TIME, TypeInfo.getTypeInfo(Value.TIME)); + for (int s = 0; s <= 9; s++) { + int d = s > 0 ? s + 9 : 8; + testTypeInfoCheck(Value.TIME, d, s, d, TypeInfo.getTypeInfo(Value.TIME, 0, s, null)); + } + testTypeInfoCheck(Value.DATE, 10, 0, 10, TypeInfo.TYPE_DATE, TypeInfo.getTypeInfo(Value.DATE)); + testTypeInfoCheck(Value.TIMESTAMP, 29, 9, 29, TypeInfo.TYPE_TIMESTAMP, TypeInfo.getTypeInfo(Value.TIMESTAMP)); + for (int s = 0; s <= 9; s++) { + int d = s > 0 ? s + 20 : 19; + testTypeInfoCheck(Value.TIMESTAMP, d, s, d, TypeInfo.getTypeInfo(Value.TIMESTAMP, 0, s, null)); + } + testTypeInfoCheck(Value.TIMESTAMP_TZ, 35, 9, 35, TypeInfo.TYPE_TIMESTAMP_TZ, + TypeInfo.getTypeInfo(Value.TIMESTAMP_TZ)); + for (int s = 0; s <= 9; s++) { + int d = s > 0 ? s + 26 : 25; + testTypeInfoCheck(Value.TIMESTAMP_TZ, d, s, d, TypeInfo.getTypeInfo(Value.TIMESTAMP_TZ, 0, s, null)); + } + + testTypeInfoCheck(Value.BINARY, 1, 0, 2, TypeInfo.getTypeInfo(Value.BINARY)); + testTypeInfoCheck(Value.VARBINARY, MAX_STRING_LENGTH, 0, MAX_STRING_LENGTH * 2, + TypeInfo.getTypeInfo(Value.VARBINARY)); + testTypeInfoCheck(Value.BLOB, Long.MAX_VALUE, 0, Integer.MAX_VALUE, TypeInfo.getTypeInfo(Value.BLOB)); + testTypeInfoCheck(Value.CLOB, Long.MAX_VALUE, 0, Integer.MAX_VALUE, TypeInfo.getTypeInfo(Value.CLOB)); + + testTypeInfoCheck(Value.VARCHAR, MAX_STRING_LENGTH, 0, MAX_STRING_LENGTH, TypeInfo.TYPE_VARCHAR, + TypeInfo.getTypeInfo(Value.VARCHAR)); + testTypeInfoCheck(Value.CHAR, 1, 0, 1, TypeInfo.getTypeInfo(Value.CHAR)); + testTypeInfoCheck(Value.VARCHAR_IGNORECASE, MAX_STRING_LENGTH, 0, MAX_STRING_LENGTH, + TypeInfo.getTypeInfo(Value.VARCHAR_IGNORECASE)); + + testTypeInfoCheck(Value.ARRAY, MAX_ARRAY_CARDINALITY, 0, Integer.MAX_VALUE, TypeInfo.TYPE_ARRAY_UNKNOWN, + TypeInfo.getTypeInfo(Value.ARRAY)); + testTypeInfoCheck(Value.ROW, Integer.MAX_VALUE, 0, Integer.MAX_VALUE, TypeInfo.TYPE_ROW_EMPTY, + TypeInfo.getTypeInfo(Value.ROW)); + + testTypeInfoCheck(Value.JAVA_OBJECT, MAX_STRING_LENGTH, 0, MAX_STRING_LENGTH * 2, TypeInfo.TYPE_JAVA_OBJECT, + TypeInfo.getTypeInfo(Value.JAVA_OBJECT)); + testTypeInfoCheck(Value.UUID, 16, 0, 36, TypeInfo.TYPE_UUID, TypeInfo.getTypeInfo(Value.UUID)); + testTypeInfoCheck(Value.GEOMETRY, MAX_STRING_LENGTH, 0, Integer.MAX_VALUE, TypeInfo.TYPE_GEOMETRY, + TypeInfo.getTypeInfo(Value.GEOMETRY)); + testTypeInfoCheck(Value.ENUM, MAX_STRING_LENGTH, 0, MAX_STRING_LENGTH, TypeInfo.TYPE_ENUM_UNDEFINED, + TypeInfo.getTypeInfo(Value.ENUM)); + + testTypeInfoInterval1(Value.INTERVAL_YEAR); + testTypeInfoInterval1(Value.INTERVAL_MONTH); + testTypeInfoInterval1(Value.INTERVAL_DAY); + testTypeInfoInterval1(Value.INTERVAL_HOUR); + testTypeInfoInterval1(Value.INTERVAL_MINUTE); + testTypeInfoInterval2(Value.INTERVAL_SECOND); + testTypeInfoInterval1(Value.INTERVAL_YEAR_TO_MONTH); + testTypeInfoInterval1(Value.INTERVAL_DAY_TO_HOUR); + testTypeInfoInterval1(Value.INTERVAL_DAY_TO_MINUTE); + testTypeInfoInterval2(Value.INTERVAL_DAY_TO_SECOND); + testTypeInfoInterval1(Value.INTERVAL_HOUR_TO_MINUTE); + testTypeInfoInterval2(Value.INTERVAL_HOUR_TO_SECOND); + testTypeInfoInterval2(Value.INTERVAL_MINUTE_TO_SECOND); + + testTypeInfoCheck(Value.JSON, MAX_STRING_LENGTH, 0, MAX_STRING_LENGTH, TypeInfo.TYPE_JSON, + TypeInfo.getTypeInfo(Value.JSON)); + } + + private void testTypeInfoInterval1(int type) { + testTypeInfoCheck(type, 18, 0, ValueInterval.getDisplaySize(type, 18, 0), TypeInfo.getTypeInfo(type)); + for (int p = 1; p <= 18; p++) { + testTypeInfoCheck(type, p, 0, ValueInterval.getDisplaySize(type, p, 0), + TypeInfo.getTypeInfo(type, p, 0, null)); + } + } + + private void testTypeInfoInterval2(int type) { + testTypeInfoCheck(type, 18, 9, ValueInterval.getDisplaySize(type, 18, 9), TypeInfo.getTypeInfo(type)); + for (int p = 1; p <= 18; p++) { + for (int s = 0; s <= 9; s++) { + testTypeInfoCheck(type, p, s, ValueInterval.getDisplaySize(type, p, s), + TypeInfo.getTypeInfo(type, p, s, null)); + } + } + } + + private void testTypeInfoCheck(int valueType, long precision, int scale, int displaySize, TypeInfo... typeInfos) { + for (TypeInfo typeInfo : typeInfos) { + testTypeInfoCheck(valueType, precision, scale, displaySize, typeInfo); + } + } + + private void testTypeInfoCheck(int valueType, long precision, int scale, int displaySize, TypeInfo typeInfo) { + assertEquals(valueType, typeInfo.getValueType()); + assertEquals(precision, typeInfo.getPrecision()); + assertEquals(scale, typeInfo.getScale()); + assertEquals(displaySize, typeInfo.getDisplaySize()); + } + + private void testH2Type() { + assertEquals(Value.CHAR, (int) H2Type.CHAR.getVendorTypeNumber()); + assertEquals(Value.VARCHAR, (int) H2Type.VARCHAR.getVendorTypeNumber()); + assertEquals(Value.CLOB, (int) H2Type.CLOB.getVendorTypeNumber()); + assertEquals(Value.VARCHAR_IGNORECASE, (int) H2Type.VARCHAR_IGNORECASE.getVendorTypeNumber()); + assertEquals(Value.BINARY, (int) H2Type.BINARY.getVendorTypeNumber()); + assertEquals(Value.VARBINARY, (int) H2Type.VARBINARY.getVendorTypeNumber()); + assertEquals(Value.BLOB, (int) H2Type.BLOB.getVendorTypeNumber()); + assertEquals(Value.BOOLEAN, (int) H2Type.BOOLEAN.getVendorTypeNumber()); + assertEquals(Value.TINYINT, (int) H2Type.TINYINT.getVendorTypeNumber()); + assertEquals(Value.SMALLINT, (int) H2Type.SMALLINT.getVendorTypeNumber()); + assertEquals(Value.INTEGER, (int) H2Type.INTEGER.getVendorTypeNumber()); + assertEquals(Value.BIGINT, (int) H2Type.BIGINT.getVendorTypeNumber()); + assertEquals(Value.NUMERIC, (int) H2Type.NUMERIC.getVendorTypeNumber()); + assertEquals(Value.REAL, (int) H2Type.REAL.getVendorTypeNumber()); + assertEquals(Value.DOUBLE, (int) H2Type.DOUBLE_PRECISION.getVendorTypeNumber()); + assertEquals(Value.DECFLOAT, (int) H2Type.DECFLOAT.getVendorTypeNumber()); + assertEquals(Value.DATE, (int) H2Type.DATE.getVendorTypeNumber()); + assertEquals(Value.TIME, (int) H2Type.TIME.getVendorTypeNumber()); + assertEquals(Value.TIME_TZ, (int) H2Type.TIME_WITH_TIME_ZONE.getVendorTypeNumber()); + assertEquals(Value.TIMESTAMP, (int) H2Type.TIMESTAMP.getVendorTypeNumber()); + assertEquals(Value.TIMESTAMP_TZ, (int) H2Type.TIMESTAMP_WITH_TIME_ZONE.getVendorTypeNumber()); + assertEquals(Value.INTERVAL_YEAR, (int) H2Type.INTERVAL_YEAR.getVendorTypeNumber()); + assertEquals(Value.INTERVAL_MONTH, (int) H2Type.INTERVAL_MONTH.getVendorTypeNumber()); + assertEquals(Value.INTERVAL_DAY, (int) H2Type.INTERVAL_DAY.getVendorTypeNumber()); + assertEquals(Value.INTERVAL_HOUR, (int) H2Type.INTERVAL_HOUR.getVendorTypeNumber()); + assertEquals(Value.INTERVAL_MINUTE, (int) H2Type.INTERVAL_MINUTE.getVendorTypeNumber()); + assertEquals(Value.INTERVAL_SECOND, (int) H2Type.INTERVAL_SECOND.getVendorTypeNumber()); + assertEquals(Value.INTERVAL_YEAR_TO_MONTH, (int) H2Type.INTERVAL_YEAR_TO_MONTH.getVendorTypeNumber()); + assertEquals(Value.INTERVAL_DAY_TO_HOUR, (int) H2Type.INTERVAL_DAY_TO_HOUR.getVendorTypeNumber()); + assertEquals(Value.INTERVAL_DAY_TO_MINUTE, (int) H2Type.INTERVAL_DAY_TO_MINUTE.getVendorTypeNumber()); + assertEquals(Value.INTERVAL_DAY_TO_SECOND, (int) H2Type.INTERVAL_DAY_TO_SECOND.getVendorTypeNumber()); + assertEquals(Value.INTERVAL_HOUR_TO_MINUTE, (int) H2Type.INTERVAL_HOUR_TO_MINUTE.getVendorTypeNumber()); + assertEquals(Value.INTERVAL_HOUR_TO_SECOND, (int) H2Type.INTERVAL_HOUR_TO_SECOND.getVendorTypeNumber()); + assertEquals(Value.INTERVAL_MINUTE_TO_SECOND, (int) H2Type.INTERVAL_MINUTE_TO_SECOND.getVendorTypeNumber()); + assertEquals(Value.JAVA_OBJECT, (int) H2Type.JAVA_OBJECT.getVendorTypeNumber()); + assertEquals(Value.ENUM, (int) H2Type.ENUM.getVendorTypeNumber()); + assertEquals(Value.GEOMETRY, (int) H2Type.GEOMETRY.getVendorTypeNumber()); + assertEquals(Value.JSON, (int) H2Type.JSON.getVendorTypeNumber()); + assertEquals(Value.UUID, (int) H2Type.UUID.getVendorTypeNumber()); + assertEquals(Value.ARRAY, (int) H2Type.array(H2Type.VARCHAR).getVendorTypeNumber()); + assertEquals(Value.ROW, (int) H2Type.row(H2Type.VARCHAR).getVendorTypeNumber()); + } + + private void testHigherType() { + testHigherTypeNumeric(15L, 6, 10L, 1, 5L, 6); + testHigherTypeNumeric(15L, 6, 5L, 6, 10L, 1); + TypeInfo intArray10 = TypeInfo.getTypeInfo(Value.ARRAY, 10, 0, TypeInfo.TYPE_INTEGER); + TypeInfo bigintArray1 = TypeInfo.getTypeInfo(Value.ARRAY, 1, 0, TypeInfo.TYPE_BIGINT); + TypeInfo bigintArray10 = TypeInfo.getTypeInfo(Value.ARRAY, 10, 0, TypeInfo.TYPE_BIGINT); + assertEquals(bigintArray10, TypeInfo.getHigherType(intArray10, bigintArray1)); + TypeInfo intArray10Array1 = TypeInfo.getTypeInfo(Value.ARRAY, 1, 0, intArray10); + TypeInfo bigintArray1Array10 = TypeInfo.getTypeInfo(Value.ARRAY, 10, 0, bigintArray1); + TypeInfo bigintArray10Array10 = TypeInfo.getTypeInfo(Value.ARRAY, 10, 0, bigintArray10); + assertEquals(bigintArray10Array10, TypeInfo.getHigherType(intArray10Array1, bigintArray1Array10)); + assertEquals(bigintArray10Array10, TypeInfo.getHigherType(intArray10, bigintArray1Array10)); + TypeInfo bigintArray10Array1 = TypeInfo.getTypeInfo(Value.ARRAY, 1, 0, bigintArray10); + assertEquals(bigintArray10Array1, TypeInfo.getHigherType(intArray10Array1, bigintArray1)); + } + + private void testHigherTypeNumeric(long expectedPrecision, int expectedScale, long precision1, int scale1, + long precision2, int scale2) { + assertEquals(TypeInfo.getTypeInfo(Value.NUMERIC, expectedPrecision, expectedScale, null), + TypeInfo.getHigherType(TypeInfo.getTypeInfo(Value.NUMERIC, precision1, scale1, null), + TypeInfo.getTypeInfo(Value.NUMERIC, precision2, scale2, null))); + } + +} diff --git a/h2/src/test/org/h2/test/unit/TestValueMemory.java b/h2/src/test/org/h2/test/unit/TestValueMemory.java new file mode 100644 index 0000000..96ac632 --- /dev/null +++ b/h2/src/test/org/h2/test/unit/TestValueMemory.java @@ -0,0 +1,425 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.unit; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.math.BigDecimal; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.Random; +import org.h2.api.IntervalQualifier; +import org.h2.engine.Constants; +import org.h2.store.DataHandler; +import org.h2.store.FileStore; +import org.h2.store.LobStorageInterface; +import org.h2.test.TestBase; +import org.h2.test.utils.MemoryFootprint; +import org.h2.util.DateTimeUtils; +import org.h2.util.SmallLRUCache; +import org.h2.util.TempFileDeleter; +import org.h2.util.Utils; +import org.h2.value.CompareMode; +import org.h2.value.Value; +import org.h2.value.ValueArray; +import org.h2.value.ValueBigint; +import org.h2.value.ValueBinary; +import org.h2.value.ValueBlob; +import org.h2.value.ValueBoolean; +import org.h2.value.ValueChar; +import org.h2.value.ValueClob; +import org.h2.value.ValueDate; +import org.h2.value.ValueDecfloat; +import org.h2.value.ValueDouble; +import org.h2.value.ValueGeometry; +import org.h2.value.ValueInteger; +import org.h2.value.ValueInterval; +import org.h2.value.ValueJavaObject; +import org.h2.value.ValueJson; +import org.h2.value.ValueLob; +import org.h2.value.ValueNull; +import org.h2.value.ValueNumeric; +import org.h2.value.ValueReal; +import org.h2.value.ValueRow; +import org.h2.value.ValueSmallint; +import org.h2.value.ValueTime; +import org.h2.value.ValueTimeTimeZone; +import org.h2.value.ValueTimestamp; +import org.h2.value.ValueTimestampTimeZone; +import org.h2.value.ValueTinyint; +import org.h2.value.ValueUuid; +import org.h2.value.ValueVarbinary; +import org.h2.value.ValueVarchar; +import org.h2.value.ValueVarcharIgnoreCase; + +/** + * Tests the memory consumption of values. Values can estimate how much memory + * they occupy, and this tests if this estimation is correct. + */ +public class TestValueMemory extends TestBase implements DataHandler { + + private static final long MIN_ABSOLUTE_DAY = DateTimeUtils.absoluteDayFromDateValue(DateTimeUtils.MIN_DATE_VALUE); + + private static final long MAX_ABSOLUTE_DAY = DateTimeUtils.absoluteDayFromDateValue(DateTimeUtils.MAX_DATE_VALUE); + + private final Random random = new Random(1); + private final SmallLRUCache lobFileListCache = SmallLRUCache + .newInstance(128); + private LobStorageTest lobStorage; + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) throws Exception { + // run using -javaagent:ext/h2-1.2.139.jar + TestBase test = TestBase.createCaller().init(); + test.config.traceTest = true; + test.testFromMain(); + } + + @Override + public void test() throws SQLException { + testCompare(); + for (int i = 0; i < Value.TYPE_COUNT; i++) { + if (i == 23) { + // this used to be "TIMESTAMP UTC", which was a short-lived + // experiment + continue; + } + if (i == Value.ENUM) { + // TODO ENUM + continue; + } + Value v = create(i); + String s = "type: " + v.getValueType() + + " calculated: " + v.getMemory() + + " real: " + MemoryFootprint.getObjectSize(v) + " " + + v.getClass().getName() + ": " + v.toString(); + trace(s); + } + for (int i = 0; i < Value.TYPE_COUNT; i++) { + if (i == 23) { + // this used to be "TIMESTAMP UTC", which was a short-lived + // experiment + continue; + } + if (i == Value.ENUM) { + // TODO ENUM + continue; + } + Value v = create(i); + if (v == ValueNull.INSTANCE && i == Value.GEOMETRY) { + // jts not in the classpath, OK + continue; + } + assertEquals(i, v.getValueType()); + testType(i); + } + } + + private void testCompare() { + ValueNumeric a = ValueNumeric.get(new BigDecimal("0.0")); + ValueNumeric b = ValueNumeric.get(new BigDecimal("-0.00")); + assertTrue(a.hashCode() != b.hashCode()); + assertFalse(a.equals(b)); + } + + private void testType(int type) throws SQLException { + System.gc(); + System.gc(); + long first = Utils.getMemoryUsed(); + ArrayList list = new ArrayList<>(); + long memory = 0; + while (memory < 1000000) { + Value v = create(type); + memory += v.getMemory() + Constants.MEMORY_POINTER; + list.add(v); + } + Object[] array = list.toArray(); + IdentityHashMap map = new IdentityHashMap<>(); + for (Object a : array) { + map.put(a, a); + } + int size = map.size(); + map.clear(); + map = null; + list = null; + System.gc(); + System.gc(); + long used = Utils.getMemoryUsed() - first; + memory /= 1024; + if (config.traceTest || used > memory * 3) { + String msg = "Type: " + type + " Used memory: " + used + + " calculated: " + memory + " length: " + array.length + " size: " + size; + if (config.traceTest) { + trace(msg); + } + if (used > memory * 3) { + fail(msg); + } + } + } + private Value create(int type) throws SQLException { + switch (type) { + case Value.NULL: + return ValueNull.INSTANCE; + case Value.BOOLEAN: + return ValueBoolean.FALSE; + case Value.TINYINT: + return ValueTinyint.get((byte) random.nextInt()); + case Value.SMALLINT: + return ValueSmallint.get((short) random.nextInt()); + case Value.INTEGER: + return ValueInteger.get(random.nextInt()); + case Value.BIGINT: + return ValueBigint.get(random.nextLong()); + case Value.NUMERIC: + return ValueNumeric.get(new BigDecimal(random.nextInt())); + // + "12123344563456345634565234523451312312" + case Value.DOUBLE: + return ValueDouble.get(random.nextDouble()); + case Value.REAL: + return ValueReal.get(random.nextFloat()); + case Value.DECFLOAT: + return ValueDecfloat.get(new BigDecimal(random.nextInt())); + case Value.TIME: + return ValueTime.fromNanos(randomTimeNanos()); + case Value.TIME_TZ: + return ValueTimeTimeZone.fromNanos(randomTimeNanos(), randomZoneOffset()); + case Value.DATE: + return ValueDate.fromDateValue(randomDateValue()); + case Value.TIMESTAMP: + return ValueTimestamp.fromDateValueAndNanos(randomDateValue(), randomTimeNanos()); + case Value.TIMESTAMP_TZ: + return ValueTimestampTimeZone.fromDateValueAndNanos( + randomDateValue(), randomTimeNanos(), randomZoneOffset()); + case Value.VARBINARY: + return ValueVarbinary.get(randomBytes(random.nextInt(1000))); + case Value.VARCHAR: + return ValueVarchar.get(randomString(random.nextInt(100))); + case Value.VARCHAR_IGNORECASE: + return ValueVarcharIgnoreCase.get(randomString(random.nextInt(100))); + case Value.BLOB: { + int len = (int) Math.abs(random.nextGaussian() * 10); + byte[] data = randomBytes(len); + return getLobStorage().createBlob(new ByteArrayInputStream(data), len); + } + case Value.CLOB: { + int len = (int) Math.abs(random.nextGaussian() * 10); + String s = randomString(len); + return getLobStorage().createClob(new StringReader(s), len); + } + case Value.ARRAY: + return ValueArray.get(createArray(), null); + case Value.ROW: + return ValueRow.get(createArray()); + case Value.JAVA_OBJECT: + return ValueJavaObject.getNoCopy(randomBytes(random.nextInt(100))); + case Value.UUID: + return ValueUuid.get(random.nextLong(), random.nextLong()); + case Value.CHAR: + return ValueChar.get(randomString(random.nextInt(100))); + case Value.GEOMETRY: + return ValueGeometry.get("POINT (" + random.nextInt(100) + ' ' + random.nextInt(100) + ')'); + case Value.INTERVAL_YEAR: + case Value.INTERVAL_MONTH: + case Value.INTERVAL_DAY: + case Value.INTERVAL_HOUR: + case Value.INTERVAL_MINUTE: + return ValueInterval.from(IntervalQualifier.valueOf(type - Value.INTERVAL_YEAR), + random.nextBoolean(), random.nextInt(Integer.MAX_VALUE), 0); + case Value.INTERVAL_SECOND: + case Value.INTERVAL_DAY_TO_SECOND: + case Value.INTERVAL_HOUR_TO_SECOND: + case Value.INTERVAL_MINUTE_TO_SECOND: + return ValueInterval.from(IntervalQualifier.valueOf(type - Value.INTERVAL_YEAR), + random.nextBoolean(), random.nextInt(Integer.MAX_VALUE), random.nextInt(1_000_000_000)); + case Value.INTERVAL_YEAR_TO_MONTH: + case Value.INTERVAL_DAY_TO_HOUR: + case Value.INTERVAL_DAY_TO_MINUTE: + case Value.INTERVAL_HOUR_TO_MINUTE: + return ValueInterval.from(IntervalQualifier.valueOf(type - Value.INTERVAL_YEAR), + random.nextBoolean(), random.nextInt(Integer.MAX_VALUE), random.nextInt(12)); + case Value.JSON: + return ValueJson.fromJson("{\"key\":\"value\"}"); + case Value.BINARY: + return ValueBinary.get(randomBytes(random.nextInt(1000))); + default: + throw new AssertionError("type=" + type); + } + } + + private long randomDateValue() { + return DateTimeUtils.dateValueFromAbsoluteDay( + (random.nextLong() & Long.MAX_VALUE) % (MAX_ABSOLUTE_DAY - MIN_ABSOLUTE_DAY + 1) + MIN_ABSOLUTE_DAY); + } + + private long randomTimeNanos() { + return (random.nextLong() & Long.MAX_VALUE) % DateTimeUtils.NANOS_PER_DAY; + } + + private short randomZoneOffset() { + return (short) (random.nextInt() % (18 * 60)); + } + + private Value[] createArray() throws SQLException { + int len = random.nextInt(20); + Value[] list = new Value[len]; + for (int i = 0; i < list.length; i++) { + list[i] = create(Value.VARCHAR); + } + return list; + } + + private byte[] randomBytes(int len) { + byte[] data = new byte[len]; + if (random.nextBoolean()) { + // don't initialize always (compression) + random.nextBytes(data); + } + return data; + } + + private String randomString(int len) { + char[] chars = new char[len]; + if (random.nextBoolean()) { + // don't initialize always (compression) + for (int i = 0; i < chars.length; i++) { + chars[i] = (char) (random.nextGaussian() * 100); + } + } + return new String(chars); + } + + @Override + public void checkPowerOff() { + // nothing to do + } + + @Override + public void checkWritingAllowed() { + // nothing to do + } + + @Override + public String getDatabasePath() { + return getBaseDir() + "/valueMemory"; + } + + @Override + public Object getLobSyncObject() { + return this; + } + + @Override + public int getMaxLengthInplaceLob() { + return 100; + } + + @Override + public FileStore openFile(String name, String mode, boolean mustExist) { + return FileStore.open(this, name, mode); + } + + @Override + public SmallLRUCache getLobFileListCache() { + return lobFileListCache; + } + + @Override + public TempFileDeleter getTempFileDeleter() { + return TempFileDeleter.getInstance(); + } + + @Override + public LobStorageInterface getLobStorage() { + if (lobStorage == null) { + lobStorage = new LobStorageTest(); + } + return lobStorage; + } + + @Override + public int readLob(long lobId, byte[] hmac, long offset, byte[] buff, + int off, int length) { + return -1; + } + + @Override + public CompareMode getCompareMode() { + return CompareMode.getInstance(null, 0); + } + + + private class LobStorageTest implements LobStorageInterface { + + LobStorageTest() { + } + + @Override + public void removeLob(ValueLob lob) { + // not stored in the database + } + + @Override + public InputStream getInputStream(long lobId, + long byteCount) throws IOException { + // this method is only implemented on the server side of a TCP connection + throw new IllegalStateException(); + } + + @Override + public InputStream getInputStream(long lobId, int tableId, + long byteCount) throws IOException { + // this method is only implemented on the server side of a TCP connection + throw new IllegalStateException(); + } + + @Override + public boolean isReadOnly() { + return false; + } + + @Override + public ValueLob copyLob(ValueLob old, int tableId) { + throw new UnsupportedOperationException(); + } + + @Override + public void removeAllForTable(int tableId) { + throw new UnsupportedOperationException(); + } + + @Override + public ValueBlob createBlob(InputStream in, long maxLength) { + // need to use a temp file, because the input stream could come from + // the same database, which would create a weird situation (trying + // to read a block while writing something) + return ValueBlob.createTempBlob(in, maxLength, TestValueMemory.this); + } + + /** + * Create a CLOB object. + * + * @param reader the reader + * @param maxLength the maximum length (-1 if not known) + * @return the LOB + */ + @Override + public ValueClob createClob(Reader reader, long maxLength) { + // need to use a temp file, because the input stream could come from + // the same database, which would create a weird situation (trying + // to read a block while writing something) + return ValueClob.createTempClob(reader, maxLength, TestValueMemory.this); + } + } +} diff --git a/h2/src/test/org/h2/test/unit/package.html b/h2/src/test/org/h2/test/unit/package.html new file mode 100644 index 0000000..f87035f --- /dev/null +++ b/h2/src/test/org/h2/test/unit/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +Unit tests that don't start the database (in most cases). + +

    \ No newline at end of file diff --git a/h2/src/test/org/h2/test/utils/FilePathDebug.java b/h2/src/test/org/h2/test/utils/FilePathDebug.java new file mode 100644 index 0000000..90e1a57 --- /dev/null +++ b/h2/src/test/org/h2/test/utils/FilePathDebug.java @@ -0,0 +1,342 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.utils; + +import java.io.FilterInputStream; +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 org.h2.store.fs.FileBase; +import org.h2.store.fs.FilePath; +import org.h2.store.fs.FilePathWrapper; + +/** + * A debugging file system that logs all operations. + */ +public class FilePathDebug extends FilePathWrapper { + + private static final FilePathDebug INSTANCE = new FilePathDebug(); + + private static final IOException POWER_OFF = new IOException( + "Simulated power failure"); + + private int powerOffCount; + private boolean trace; + + /** + * Register the file system. + * + * @return the instance + */ + public static FilePathDebug register() { + FilePath.register(INSTANCE); + return INSTANCE; + } + + /** + * Check if the simulated power failure occurred. + * This call will decrement the countdown. + * + * @throws IOException if the simulated power failure occurred + */ + void checkPowerOff() throws IOException { + if (powerOffCount == 0) { + return; + } + if (powerOffCount > 1) { + powerOffCount--; + return; + } + powerOffCount = -1; + // throw new IOException("Simulated power failure"); + throw POWER_OFF; + } + + @Override + public void createDirectory() { + trace(name, "createDirectory"); + super.createDirectory(); + } + + @Override + public boolean createFile() { + trace(name, "createFile"); + return super.createFile(); + } + + @Override + public void delete() { + trace(name, "fileName"); + super.delete(); + } + + @Override + public boolean exists() { + trace(name, "exists"); + return super.exists(); + } + + @Override + public String getName() { + trace(name, "getName"); + return super.getName(); + } + + @Override + public long lastModified() { + trace(name, "lastModified"); + return super.lastModified(); + } + + @Override + public FilePath getParent() { + trace(name, "getParent"); + return super.getParent(); + } + + @Override + public boolean isAbsolute() { + trace(name, "isAbsolute"); + return super.isAbsolute(); + } + + @Override + public boolean isDirectory() { + trace(name, "isDirectory"); + return super.isDirectory(); + } + + @Override + public boolean isRegularFile() { + trace(name, "isRegularFile"); + return super.isRegularFile(); + } + + @Override + public boolean canWrite() { + trace(name, "canWrite"); + return super.canWrite(); + } + + @Override + public boolean setReadOnly() { + trace(name, "setReadOnly"); + return super.setReadOnly(); + } + + @Override + public long size() { + trace(name, "size"); + return super.size(); + } + + @Override + public List newDirectoryStream() { + trace(name, "newDirectoryStream"); + return super.newDirectoryStream(); + } + + @Override + public FilePath toRealPath() { + trace(name, "toRealPath"); + return super.toRealPath(); + } + + @Override + public InputStream newInputStream() throws IOException { + trace(name, "newInputStream"); + InputStream in = super.newInputStream(); + if (!isTrace()) { + return in; + } + final String fileName = name; + return new FilterInputStream(in) { + @Override + public int read(byte[] b) throws IOException { + trace(fileName, "in.read(b)"); + return super.read(b); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + trace(fileName, "in.read(b)", "in.read(b, " + off + ", " + len + ")"); + return super.read(b, off, len); + } + + @Override + public long skip(long n) throws IOException { + trace(fileName, "in.read(b)", "in.skip(" + n + ")"); + return super.skip(n); + } + }; + } + + @Override + public FileChannel open(String mode) throws IOException { + trace(name, "open", mode); + return new FileDebug(this, super.open(mode), name); + } + + @Override + public OutputStream newOutputStream(boolean append) throws IOException { + trace(name, "newOutputStream", append); + return super.newOutputStream(append); + } + + @Override + public void moveTo(FilePath newName, boolean atomicReplace) { + trace(name, "moveTo", unwrap(((FilePathDebug) newName).name)); + super.moveTo(newName, atomicReplace); + } + + @Override + public FilePath createTempFile(String suffix, boolean inTempDir) throws IOException { + trace(name, "createTempFile", suffix, inTempDir); + return super.createTempFile(suffix, inTempDir); + } + + /** + * Print a debug message. + * + * @param fileName the (wrapped) file name + * @param method the method name + * @param params parameters if any + */ + void trace(String fileName, String method, Object... params) { + if (isTrace()) { + StringBuilder buff = new StringBuilder(" "); + buff.append(unwrap(fileName)).append(' ').append(method); + for (Object s : params) { + buff.append(' ').append(s); + } + System.out.println(buff); + } + } + + public void setPowerOffCount(int count) { + this.powerOffCount = count; + } + + public int getPowerOffCount() { + return powerOffCount; + } + + public boolean isTrace() { + return INSTANCE.trace; + } + + public void setTrace(boolean trace) { + INSTANCE.trace = trace; + } + + @Override + public String getScheme() { + return "debug"; + } + +} + +/** + * A debugging file that logs all operations. + */ +class FileDebug extends FileBase { + + private final FilePathDebug debug; + private final FileChannel channel; + private final String name; + + FileDebug(FilePathDebug debug, FileChannel channel, String name) { + this.debug = debug; + this.channel = channel; + this.name = name; + } + + @Override + public void implCloseChannel() throws IOException { + debug("close"); + channel.close(); + } + + @Override + public long position() throws IOException { + debug("getFilePointer"); + return channel.position(); + } + + @Override + public long size() throws IOException { + debug("length"); + return channel.size(); + } + + @Override + public int read(ByteBuffer dst) throws IOException { + debug("read", channel.position(), dst.position(), dst.remaining()); + return channel.read(dst); + } + + @Override + public FileChannel position(long pos) throws IOException { + debug("seek", pos); + channel.position(pos); + return this; + } + + @Override + public FileChannel truncate(long newLength) throws IOException { + checkPowerOff(); + debug("truncate", newLength); + channel.truncate(newLength); + return this; + } + + @Override + public void force(boolean metaData) throws IOException { + debug("force"); + channel.force(metaData); + } + + @Override + public int write(ByteBuffer src) throws IOException { + checkPowerOff(); + debug("write", channel.position(), src.position(), src.remaining()); + return channel.write(src); + } + + private void debug(String method, Object... params) { + debug.trace(name, method, params); + } + + private void checkPowerOff() throws IOException { + try { + debug.checkPowerOff(); + } catch (IOException e) { + try { + channel.close(); + } catch (IOException e2) { + // ignore + } + throw e; + } + } + + @Override + public synchronized FileLock tryLock(long position, long size, + boolean shared) throws IOException { + debug("tryLock"); + return channel.tryLock(position, size, shared); + } + + @Override + public String toString() { + return name; + } + +} diff --git a/h2/src/test/org/h2/test/utils/FilePathReorderWrites.java b/h2/src/test/org/h2/test/utils/FilePathReorderWrites.java new file mode 100644 index 0000000..a8d9c72 --- /dev/null +++ b/h2/src/test/org/h2/test/utils/FilePathReorderWrites.java @@ -0,0 +1,379 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.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.ArrayList; +import java.util.Random; +import org.h2.store.fs.FileBaseDefault; +import org.h2.store.fs.FilePath; +import org.h2.store.fs.FilePathWrapper; +import org.h2.util.IOUtils; + +/** + * An unstable file system. It is used to simulate file system problems (for + * example out of disk space). + */ +public class FilePathReorderWrites extends FilePathWrapper { + + /** + * Whether trace output of all method calls is enabled. + */ + static final boolean TRACE = false; + + private static final FilePathReorderWrites INSTANCE = new FilePathReorderWrites(); + + private static final IOException POWER_FAILURE = new IOException("Power Failure"); + + private static int powerFailureCountdown; + + private static boolean partialWrites; + + private static Random random = new Random(1); + + /** + * Register the file system. + * + * @return the instance + */ + public static FilePathReorderWrites register() { + FilePath.register(INSTANCE); + return INSTANCE; + } + + /** + * Set the number of write operations before a simulated power failure, and + * the random seed (for partial writes). + * + * @param count the number of write operations (0 to never fail, + * Integer.MAX_VALUE to count the operations) + * @param seed the new seed + */ + public void setPowerOffCountdown(int count, int seed) { + powerFailureCountdown = count; + random.setSeed(seed); + } + + public int getPowerOffCountdown() { + return powerFailureCountdown; + } + + /** + * Whether partial writes are possible (writing only part of the data). + * + * @param b true to enable + */ + public static void setPartialWrites(boolean b) { + partialWrites = b; + } + + static boolean isPartialWrites() { + return partialWrites; + } + + /** + * Get a buffer with a subset (the head) of the data of the source buffer. + * + * @param src the source buffer + * @return a buffer with a subset of the data + */ + ByteBuffer getRandomSubset(ByteBuffer src) { + int len = src.remaining(); + len = Math.min(4096, Math.min(len, 1 + random.nextInt(len))); + ByteBuffer temp = ByteBuffer.allocate(len); + src.get(temp.array()); + return temp; + } + + Random getRandom() { + return random; + } + + /** + * Check if the simulated problem occurred. + * This call will decrement the countdown. + * + * @throws IOException if the simulated power failure occurred + */ + void checkError() throws IOException { + if (powerFailureCountdown == 0) { + return; + } + if (powerFailureCountdown < 0) { + throw POWER_FAILURE; + } + powerFailureCountdown--; + if (powerFailureCountdown == 0) { + powerFailureCountdown--; + throw POWER_FAILURE; + } + } + + @Override + public FileChannel open(String mode) throws IOException { + InputStream in = newInputStream(); + FilePath copy = FilePath.get(getBase().toString() + ".copy"); + OutputStream out = copy.newOutputStream(false); + IOUtils.copy(in, out); + in.close(); + out.close(); + FileChannel base = getBase().open(mode); + FileChannel readBase = copy.open(mode); + return new FileReorderWrites(this, base, readBase); + } + + @Override + public String getScheme() { + return "reorder"; + } + + public long getMaxAge() { + // TODO implement, configurable + return 45000; + } + + @Override + public void delete() { + super.delete(); + FilePath.get(getBase().toString() + ".copy").delete(); + } +} + +/** + * A write-reordering file implementation. + */ +class FileReorderWrites extends FileBaseDefault { + + private final FilePathReorderWrites file; + /** + * The base channel, where not all operations are immediately applied. + */ + private final FileChannel base; + + /** + * The base channel that is used for reading, where all operations are + * immediately applied to get a consistent view before a power failure. + */ + private final FileChannel readBase; + + private boolean closed; + + /** + * The list of not yet applied to the base channel. It is sorted by time. + */ + private ArrayList notAppliedList = new ArrayList<>(); + + private int id; + + FileReorderWrites(FilePathReorderWrites file, FileChannel base, FileChannel readBase) { + this.file = file; + this.base = base; + this.readBase = readBase; + } + + @Override + public void implCloseChannel() throws IOException { + base.close(); + readBase.close(); + closed = true; + } + + @Override + public long size() throws IOException { + return readBase.size(); + } + + @Override + public int read(ByteBuffer dst, long pos) throws IOException { + return readBase.read(dst, pos); + } + + @Override + protected void implTruncate(long newSize) throws IOException { + long oldSize = readBase.size(); + if (oldSize <= newSize) { + return; + } + addOperation(new FileWriteOperation(id++, newSize, null)); + } + + private int addOperation(FileWriteOperation op) throws IOException { + trace("op " + op); + checkError(); + notAppliedList.add(op); + long now = op.getTime(); + for (int i = 0; i < notAppliedList.size() - 1; i++) { + FileWriteOperation old = notAppliedList.get(i); + boolean applyOld = false; + // String reason = ""; + if (old.getTime() + 45000 < now) { + // reason = "old"; + applyOld = true; + } else if (old.overlaps(op)) { + // reason = "overlap"; + applyOld = true; + } else if (file.getRandom().nextInt(100) < 10) { + // reason = "random"; + applyOld = true; + } + if (applyOld) { + trace("op apply " + op); + old.apply(base); + notAppliedList.remove(i); + i--; + } + } + return op.apply(readBase); + } + + private void applyAll() throws IOException { + trace("applyAll"); + for (FileWriteOperation op : notAppliedList) { + op.apply(base); + } + notAppliedList.clear(); + } + + @Override + public void force(boolean metaData) throws IOException { + checkError(); + readBase.force(metaData); + applyAll(); + } + + @Override + public int write(ByteBuffer src, long position) throws IOException { + if (FilePathReorderWrites.isPartialWrites() && src.remaining() > 2) { + ByteBuffer buf1 = src.slice(); + ByteBuffer buf2 = src.slice(); + int len1 = src.remaining() / 2; + int len2 = src.remaining() - len1; + buf1.limit(buf1.limit() - len2); + buf2.position(buf2.position() + len1); + int x = addOperation(new FileWriteOperation(id++, position, buf1)); + x += addOperation( + new FileWriteOperation(id++, position + len1, buf2)); + src.position( src.position() + x ); + return x; + } + return addOperation(new FileWriteOperation(id++, position, src)); + } + + private void checkError() throws IOException { + if (closed) { + throw new IOException("Closed"); + } + file.checkError(); + } + + @Override + public synchronized FileLock tryLock(long position, long size, + boolean shared) throws IOException { + return readBase.tryLock(position, size, shared); + } + + @Override + public String toString() { + return file.getScheme() + ":" + file.toString(); + } + + private static void trace(String message) { + if (FilePathReorderWrites.TRACE) { + System.out.println(message); + } + } + + /** + * A file operation (that might be re-ordered with other operations, or not + * be applied on power failure). + */ + static class FileWriteOperation { + private final int id; + private final long time; + private final ByteBuffer buffer; + private final long position; + + FileWriteOperation(int id, long position, ByteBuffer src) { + this.id = id; + this.time = System.currentTimeMillis(); + if (src == null) { + buffer = null; + } else { + int len = src.limit() - src.position(); + this.buffer = ByteBuffer.allocate(len); + buffer.put(src); + buffer.flip(); + } + this.position = position; + } + + public long getTime() { + return time; + } + + /** + * Check whether the file region of this operation overlaps with + * another operation. + * + * @param other the other operation + * @return if there is an overlap + */ + boolean overlaps(FileWriteOperation other) { + if (isTruncate() && other.isTruncate()) { + // we just keep the latest truncate operation + return true; + } + if (isTruncate()) { + return position < other.getEndPosition(); + } else if (other.isTruncate()) { + return getEndPosition() > other.position; + } + return position < other.getEndPosition() && + getEndPosition() > other.position; + } + + private boolean isTruncate() { + return buffer == null; + } + + private long getEndPosition() { + return position + getLength(); + } + + private int getLength() { + return buffer == null ? 0 : buffer.limit() - buffer.position(); + } + + /** + * Apply the operation to the channel. + * + * @param channel the channel + * @return the return value of the operation + */ + int apply(FileChannel channel) throws IOException { + if (isTruncate()) { + channel.truncate(position); + return -1; + } + int len = channel.write(buffer, position); + buffer.flip(); + return len; + } + + @Override + public String toString() { + String s = "[" + id + "]: @" + position + ( + isTruncate() ? "-truncate" : ("+" + getLength())); + return s; + } + } + +} \ No newline at end of file diff --git a/h2/src/test/org/h2/test/utils/FilePathUnstable.java b/h2/src/test/org/h2/test/utils/FilePathUnstable.java new file mode 100644 index 0000000..2a40401 --- /dev/null +++ b/h2/src/test/org/h2/test/utils/FilePathUnstable.java @@ -0,0 +1,214 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.utils; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.util.Random; + +import org.h2.store.fs.FileBase; +import org.h2.store.fs.FilePath; +import org.h2.store.fs.FilePathWrapper; + +/** + * An unstable file system. It is used to simulate file system problems (for + * example out of disk space). + */ +public class FilePathUnstable extends FilePathWrapper { + + private static final FilePathUnstable INSTANCE = new FilePathUnstable(); + + private static final IOException DISK_FULL = new IOException("Disk full"); + + private static int diskFullOffCount; + + private static boolean partialWrites; + + private static Random random = new Random(1); + + /** + * Register the file system. + * + * @return the instance + */ + public static FilePathUnstable register() { + FilePath.register(INSTANCE); + return INSTANCE; + } + + /** + * Set the number of write operations before the disk is full, and the + * random seed (for partial writes). + * + * @param count the number of write operations (0 to never fail, + * Integer.MAX_VALUE to count the operations) + * @param seed the new seed + */ + public void setDiskFullCount(int count, int seed) { + diskFullOffCount = count; + random.setSeed(seed); + } + + public int getDiskFullCount() { + return diskFullOffCount; + } + + /** + * Whether partial writes are possible (writing only part of the data). + * + * @param partialWrites true to enable + */ + public void setPartialWrites(boolean partialWrites) { + FilePathUnstable.partialWrites = partialWrites; + } + + boolean getPartialWrites() { + return partialWrites; + } + + /** + * Get a buffer with a subset (the head) of the data of the source buffer. + * + * @param src the source buffer + * @return a buffer with a subset of the data + */ + ByteBuffer getRandomSubset(ByteBuffer src) { + int len = src.remaining(); + len = Math.min(4096, Math.min(len, 1 + random.nextInt(len))); + ByteBuffer temp = ByteBuffer.allocate(len); + src.get(temp.array()); + return temp; + } + + /** + * Check if the simulated problem occurred. + * This call will decrement the countdown. + * + * @throws IOException if the simulated power failure occurred + */ + void checkError() throws IOException { + if (diskFullOffCount == 0) { + return; + } + if (--diskFullOffCount > 0) { + return; + } + if (diskFullOffCount >= -1) { + diskFullOffCount--; + throw DISK_FULL; + } + } + + @Override + public FileChannel open(String mode) throws IOException { + return new FileUnstable(this, super.open(mode)); + } + + @Override + public String getScheme() { + return "unstable"; + } + +} + +/** + * An file that checks for errors before each write operation. + */ +class FileUnstable extends FileBase { + + private final FilePathUnstable file; + private final FileChannel channel; + private boolean closed; + + FileUnstable(FilePathUnstable file, FileChannel channel) { + this.file = file; + this.channel = channel; + } + + @Override + public void implCloseChannel() throws IOException { + channel.close(); + closed = true; + } + + @Override + public long position() throws IOException { + return channel.position(); + } + + @Override + public long size() throws IOException { + return channel.size(); + } + + @Override + public int read(ByteBuffer dst) throws IOException { + return channel.read(dst); + } + + @Override + public int read(ByteBuffer dst, long pos) throws IOException { + return channel.read(dst, pos); + } + + @Override + public FileChannel position(long pos) throws IOException { + channel.position(pos); + return this; + } + + @Override + public FileChannel truncate(long newLength) throws IOException { + checkError(); + channel.truncate(newLength); + return this; + } + + @Override + public void force(boolean metaData) throws IOException { + checkError(); + channel.force(metaData); + } + + @Override + public int write(ByteBuffer src) throws IOException { + checkError(); + if (file.getPartialWrites()) { + return channel.write(file.getRandomSubset(src)); + } + return channel.write(src); + } + + @Override + public int write(ByteBuffer src, long position) throws IOException { + checkError(); + if (file.getPartialWrites()) { + return channel.write(file.getRandomSubset(src), position); + } + return channel.write(src, position); + } + + private void checkError() throws IOException { + if (closed) { + throw new IOException("Closed"); + } + file.checkError(); + } + + @Override + public synchronized FileLock tryLock(long position, long size, + boolean shared) throws IOException { + return channel.tryLock(position, size, shared); + } + + @Override + public String toString() { + return "unstable:" + file.toString(); + } + +} \ No newline at end of file diff --git a/h2/src/test/org/h2/test/utils/MemoryFootprint.java b/h2/src/test/org/h2/test/utils/MemoryFootprint.java new file mode 100644 index 0000000..ecfe077 --- /dev/null +++ b/h2/src/test/org/h2/test/utils/MemoryFootprint.java @@ -0,0 +1,84 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.utils; + +import java.lang.instrument.Instrumentation; +import java.math.BigDecimal; +import java.math.BigInteger; +import org.h2.engine.Constants; +import org.h2.result.Row; +import org.h2.store.Data; +import org.h2.util.Profiler; +import org.h2.value.Value; + +/** + * Calculate the memory footprint of various objects. + */ +public class MemoryFootprint { + + /** + * Run just this test. + * + * @param a ignored + */ + public static void main(String... a) { + // System.getProperties().store(System.out, ""); + print("Object", new Object()); + print("Timestamp", new java.sql.Timestamp(0)); + print("Date", new java.sql.Date(0)); + print("Time", new java.sql.Time(0)); + print("BigDecimal", new BigDecimal("0")); + print("BigInteger", new BigInteger("0")); + print("String", new String("Hello")); + print("Data", Data.create(10)); + print("Row", Row.get(new Value[0], 0)); + System.out.println(); + for (int i = 1; i < 128; i += i) { + + System.out.println(getArraySize(1, i) + " bytes per p1[]"); + print("boolean[" + i +"]", new boolean[i]); + + System.out.println(getArraySize(2, i) + " bytes per p2[]"); + print("char[" + i +"]", new char[i]); + print("short[" + i +"]", new short[i]); + + System.out.println(getArraySize(4, i) + " bytes per p4[]"); + print("int[" + i +"]", new int[i]); + print("float[" + i +"]", new float[i]); + + System.out.println(getArraySize(8, i) + " bytes per p8[]"); + print("long[" + i +"]", new long[i]); + print("double[" + i +"]", new double[i]); + + System.out.println(getArraySize(Constants.MEMORY_POINTER, i) + + " bytes per obj[]"); + print("Object[" + i +"]", new Object[i]); + + System.out.println(); + } + } + + private static int getArraySize(int type, int length) { + return ((Constants.MEMORY_OBJECT + length * type) + 7) / 8 * 8; + } + + private static void print(String type, Object o) { + System.out.println(getObjectSize(o) + " bytes per " + type); + } + + /** + * Get the number of bytes required for the given object. + * This method only works if the agent is set. + * + * @param o the object + * @return the number of bytes required + */ + public static long getObjectSize(Object o) { + Instrumentation inst = Profiler.getInstrumentation(); + return inst == null ? 0 : inst.getObjectSize(o); + } + +} diff --git a/h2/src/test/org/h2/test/utils/OutputCatcher.java b/h2/src/test/org/h2/test/utils/OutputCatcher.java new file mode 100644 index 0000000..ef93621 --- /dev/null +++ b/h2/src/test/org/h2/test/utils/OutputCatcher.java @@ -0,0 +1,218 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.utils; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.PrintWriter; + +/** + * A tool to capture the output of System.out and System.err. The regular output + * still occurs, but it is additionally available as a String. + */ +public class OutputCatcher { + + /** + * The HTML text will contain this string if something was written to + * System.err. + */ + public static final String START_ERROR = ""; + + private final ByteArrayOutputStream buff = new ByteArrayOutputStream(); + private final DualOutputStream out, err; + private String output; + + private OutputCatcher() { + HtmlOutputStream html = new HtmlOutputStream(buff); + out = new DualOutputStream(html, System.out, false); + err = new DualOutputStream(html, System.err, true); + System.setOut(new PrintStream(out, true)); + System.setErr(new PrintStream(err, true)); + } + + /** + * Stop catching output. + */ + public void stop() { + System.out.flush(); + System.setOut(out.print); + System.err.flush(); + System.setErr(err.print); + output = buff.toString(); + } + + /** + * Write the output to a HTML file. + * + * @param title the title + * @param fileName the file name + */ + public void writeTo(String title, String fileName) throws IOException { + File file = new File(fileName); + file.getParentFile().mkdirs(); + PrintWriter writer = new PrintWriter(new FileOutputStream(file)); + writer.write("\n"); + writer.write("\n"); + writer.write("\n"); + writer.print(title); + writer.print("\n"); + writer.print("\n"); + writer.print("

    " + title + "


    \n"); + writer.print(output); + writer.write("\n"); + writer.close(); + } + + /** + * Create a new output catcher and start it. + * + * @return the output catcher + */ + public static OutputCatcher start() { + return new OutputCatcher(); + } + + /** + * An output stream that writes to both a HTML stream and a print stream. + */ + static class DualOutputStream extends FilterOutputStream { + + /** + * The original print stream. + */ + final PrintStream print; + + private final HtmlOutputStream htmlOut; + private final boolean error; + + DualOutputStream(HtmlOutputStream out, PrintStream print, boolean error) { + super(out); + this.htmlOut = out; + this.print = print; + this.error = error; + } + + @Override + public void close() throws IOException { + print.close(); + super.close(); + } + + @Override + public void flush() throws IOException { + print.flush(); + super.flush(); + } + + @Override + public void write(int b) throws IOException { + print.write(b); + htmlOut.write(error, b); + } + } + + /** + * An output stream that has two modes: error mode and regular mode. + */ + static class HtmlOutputStream extends FilterOutputStream { + + private static final byte[] START = START_ERROR.getBytes(); + private static final byte[] END = "
    ".getBytes(); + private static final byte[] BR = "
    \n".getBytes(); + private static final byte[] NBSP = " ".getBytes(); + private static final byte[] LT = "<".getBytes(); + private static final byte[] GT = ">".getBytes(); + private static final byte[] AMP = "&".getBytes(); + private boolean error; + private boolean hasError; + private boolean convertSpace; + + HtmlOutputStream(OutputStream out) { + super(out); + } + + /** + * Check if the error mode was used. + * + * @return true if it was + */ + boolean hasError() { + return hasError; + } + + /** + * Enable or disable the error mode. + * + * @param error the flag + */ + void setError(boolean error) throws IOException { + if (error != this.error) { + if (error) { + hasError = true; + super.write(START); + } else { + super.write(END); + } + this.error = error; + } + } + + /** + * Write a character. + * + * @param errorStream if the character comes from the error stream + * @param b the character + */ + void write(boolean errorStream, int b) throws IOException { + setError(errorStream); + switch (b) { + case '\n': + super.write(BR); + convertSpace = true; + break; + case '\t': + super.write(NBSP); + super.write(NBSP); + break; + case ' ': + if (convertSpace) { + super.write(NBSP); + } else { + super.write(b); + } + break; + case '<': + super.write(LT); + break; + case '>': + super.write(GT); + break; + case '&': + super.write(AMP); + break; + default: + if (b >= 128) { + super.write(("&#" + b + ";").getBytes()); + } else { + super.write(b); + } + convertSpace = false; + } + } + + } + +} diff --git a/h2/src/test/org/h2/test/utils/RandomDataUtils.java b/h2/src/test/org/h2/test/utils/RandomDataUtils.java new file mode 100644 index 0000000..36b15e5 --- /dev/null +++ b/h2/src/test/org/h2/test/utils/RandomDataUtils.java @@ -0,0 +1,62 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.utils; + +import java.util.Random; + +/** + * Utilities for random data generation. + */ +public final class RandomDataUtils { + + /** + * Fills the specified character array with random printable code points + * from the limited set of Unicode code points with different length in + * UTF-8 representation. + * + *

    + * Debuggers can have performance problems on some systems when displayed + * values have characters from many different blocks, because too many large + * separate fonts with different sets of glyphs can be needed. + *

    + * + * @param r + * the source of random data + * @param chars + * the character array to fill + */ + public static void randomChars(Random r, char[] chars) { + for (int i = 0, l = chars.length; i < l;) { + int from, to; + switch (r.nextInt(4)) { + case 3: + if (i + 1 < l) { + from = 0x1F030; + to = 0x1F093; + break; + } + //$FALL-THROUGH$ + default: + from = ' '; + to = '~'; + break; + case 1: + from = 0xA0; + to = 0x24F; + break; + case 2: + from = 0x2800; + to = 0x28FF; + break; + } + i += Character.toChars(from + r.nextInt(to - from + 1), chars, i); + } + } + + private RandomDataUtils() { + } + +} diff --git a/h2/src/test/org/h2/test/utils/ResultVerifier.java b/h2/src/test/org/h2/test/utils/ResultVerifier.java new file mode 100644 index 0000000..ed5d73c --- /dev/null +++ b/h2/src/test/org/h2/test/utils/ResultVerifier.java @@ -0,0 +1,26 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.utils; + +import java.lang.reflect.Method; + +/** + * This handler is called after a method returned. + */ +public interface ResultVerifier { + + /** + * Verify the result or exception. + * + * @param returnValue the returned value or null + * @param t the exception / error or null if the method returned normally + * @param m the method or null if unknown + * @param args the arguments or null if unknown + * @return true if the method should be called again + */ + boolean verify(Object returnValue, Throwable t, Method m, Object... args); + +} diff --git a/h2/src/test/org/h2/test/utils/SelfDestructor.java b/h2/src/test/org/h2/test/utils/SelfDestructor.java new file mode 100644 index 0000000..6f11ffa --- /dev/null +++ b/h2/src/test/org/h2/test/utils/SelfDestructor.java @@ -0,0 +1,108 @@ +/* + * Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0, + * and the EPL 1.0 (https://h2database.com/html/license.html). + * Initial Developer: H2 Group + */ +package org.h2.test.utils; + +import java.sql.Timestamp; +import org.h2.util.ThreadDeadlockDetector; + +/** + * This is a self-destructor class to kill a long running process automatically + * after a pre-defined time. The class reads the number of minutes from the + * system property 'h2.selfDestruct' and starts a countdown thread to kill the + * virtual machine if it still runs then. + */ +public class SelfDestructor { + private static final String PROPERTY_NAME = "h2.selfDestruct"; + + /** + * Start the countdown. If the self-destruct system property is set, this + * value is used, otherwise the given default value is used. + * + * @param defaultMinutes the default number of minutes after which the + * current process is killed. + */ + public static void startCountdown(int defaultMinutes) { + final int minutes = Integer.parseInt( + System.getProperty(PROPERTY_NAME, "" + defaultMinutes)); + if (minutes == 0) { + return; + } + Thread thread = new Thread() { + @Override + public void run() { + for (int i = minutes; i >= 0; i--) { + while (true) { + try { + String name = "SelfDestructor " + i + " min"; + setName(name); + break; + } catch (OutOfMemoryError e) { + // ignore + } + } + try { + Thread.sleep(60 * 1000); + } catch (InterruptedException e) { + // ignore + } + } + try { + String time = new Timestamp( + System.currentTimeMillis()).toString(); + System.out.println(time + " Killing the process after " + + minutes + " minute(s)"); + try { + ThreadDeadlockDetector.dumpAllThreadsAndLocks( + "SelfDestructor timed out", System.err); + try { + Thread.sleep(1000); + } catch (Exception e) { + // ignore + } + int activeCount = Thread.activeCount(); + Thread[] threads = new Thread[activeCount + 100]; + int len = Thread.enumerate(threads); + for (int i = 0; i < len; i++) { + Thread t = threads[i]; + if (t != Thread.currentThread()) { + t.interrupt(); + } + } + } catch (Throwable t) { + t.printStackTrace(); + // ignore + } + try { + Thread.sleep(1000); + } catch (Exception e) { + // ignore + } + System.out.println("Killing the process now"); + } catch (Throwable t) { + try { + t.printStackTrace(System.out); + } catch (Throwable t2) { + // ignore (out of memory) + } + } + Runtime.getRuntime().halt(1); + } + }; + thread.setDaemon(true); + thread.start(); + } + + /** + * Get the string to be added when starting the Java process. + * + * @param minutes the countdown time in minutes + * @return the setting + */ + public static String getPropertyString(int minutes) { + return "-D" + PROPERTY_NAME + "=" + minutes; + } + +} diff --git a/h2/src/test/org/h2/test/utils/package.html b/h2/src/test/org/h2/test/utils/package.html new file mode 100644 index 0000000..c2468ca --- /dev/null +++ b/h2/src/test/org/h2/test/utils/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +Utility classes used by the tests. + +

    \ No newline at end of file diff --git a/h2/src/tools/WEB-INF/console.html b/h2/src/tools/WEB-INF/console.html new file mode 100644 index 0000000..2ae76ab --- /dev/null +++ b/h2/src/tools/WEB-INF/console.html @@ -0,0 +1,15 @@ + + + + + H2 Console + + + + H2 Console + + diff --git a/h2/src/tools/WEB-INF/web.xml b/h2/src/tools/WEB-INF/web.xml new file mode 100644 index 0000000..b1b067f --- /dev/null +++ b/h2/src/tools/WEB-INF/web.xml @@ -0,0 +1,67 @@ + + + + + H2 Console Web Application + + A web application that includes the H2 Console servlet. + + + + H2Console + org.h2.server.web.WebServlet + + 1 + + + + H2Console + /console/* + + + + /console.html + + + + + + + diff --git a/h2/src/tools/org/h2/dev/cache/CacheLIRS.java b/h2/src/tools/org/h2/dev/cache/CacheLIRS.java new file mode 100644 index 0000000..7667cb3 --- /dev/null +++ b/h2/src/tools/org/h2/dev/cache/CacheLIRS.java @@ -0,0 +1,1081 @@ +/* + * 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.dev.cache; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * A scan resistant cache. It is meant to cache objects that are relatively + * costly to acquire, for example file content. + *

    + * This implementation is multi-threading safe and supports concurrent access. + * Null keys or null values are not allowed. The map fill factor is at most 75%. + *

    + * Each entry is assigned a distinct memory size, and the cache will try to use + * at most the specified amount of memory. The memory unit is not relevant, + * however it is suggested to use bytes as the unit. + *

    + * This class implements an approximation of the LIRS replacement algorithm + * invented by Xiaodong Zhang and Song Jiang as described in + * https://web.cse.ohio-state.edu/~zhang.574/lirs-sigmetrics-02.html with a few + * smaller changes: An additional queue for non-resident entries is used, to + * prevent unbound memory usage. The maximum size of this queue is at most the + * size of the rest of the stack. About 6.25% of the mapped entries are cold. + *

    + * Internally, the cache is split into a number of segments, and each segment is + * an individual LIRS cache. + *

    + * Accessed entries are only moved to the top of the stack if at least a number + * of other entries have been moved to the front (8 per segment by default). + * Write access and moving entries to the top of the stack is synchronized per + * segment. + * + * @author Thomas Mueller + * @param the key type + * @param the value type + */ +public class CacheLIRS extends AbstractMap { + + /** + * The maximum memory this cache should use. + */ + private long maxMemory; + + private final Segment[] segments; + + private final int segmentCount; + private final int segmentShift; + private final int segmentMask; + private final int stackMoveDistance; + + + /** + * Create a new cache with the given number of entries, and the default + * settings (16 segments, and stack move distance of 8. + * + * @param maxMemory the maximum memory to use (1 or larger) + */ + public CacheLIRS(long maxMemory) { + this(maxMemory, 16, 8); + } + + /** + * Create a new cache with the given memory size. + * + * @param maxMemory the maximum memory to use (1 or larger) + * @param segmentCount the number of cache segments (must be a power of 2) + * @param stackMoveDistance how many other item are to be moved to the top + * of the stack before the current item is moved + */ + @SuppressWarnings("unchecked") + public CacheLIRS(long maxMemory, int segmentCount, + int stackMoveDistance) { + setMaxMemory(maxMemory); + if (Integer.bitCount(segmentCount) != 1) { + throw new IllegalArgumentException( + "The segment count must be a power of 2, is " + + segmentCount); + } + this.segmentCount = segmentCount; + this.segmentMask = segmentCount - 1; + this.stackMoveDistance = stackMoveDistance; + segments = new Segment[segmentCount]; + clear(); + // use the high bits for the segment + this.segmentShift = 32 - Integer.bitCount(segmentMask); + } + + /** + * Remove all entries. + */ + @Override + public void clear() { + long max = Math.max(1, maxMemory / segmentCount); + for (int i = 0; i < segmentCount; i++) { + segments[i] = new Segment<>( + this, max, stackMoveDistance, 8); + } + } + + private Entry find(Object key) { + int hash = getHash(key); + return getSegment(hash).find(key, hash); + } + + /** + * Check whether there is a resident entry for the given key. This + * method does not adjust the internal state of the cache. + * + * @param key the key (may not be null) + * @return true if there is a resident entry + */ + @Override + public boolean containsKey(Object key) { + int hash = getHash(key); + return getSegment(hash).containsKey(key, hash); + } + + /** + * Get the value for the given key if the entry is cached. This method does + * not modify the internal state. + * + * @param key the key (may not be null) + * @return the value, or null if there is no resident entry + */ + public V peek(K key) { + Entry e = find(key); + return e == null ? null : e.value; + } + + /** + * Add an entry to the cache. The entry may or may not exist in the + * cache yet. This method will usually mark unknown entries as cold and + * known entries as hot. + * + * @param key the key (may not be null) + * @param value the value (may not be null) + * @param memory the memory used for the given entry + * @return the old value, or null if there was no resident entry + */ + public V put(K key, V value, int memory) { + int hash = getHash(key); + int segmentIndex = getSegmentIndex(hash); + Segment s = segments[segmentIndex]; + // check whether resize is required: synchronize on s, to avoid + // concurrent resizes (concurrent reads read + // from the old segment) + synchronized (s) { + s = resizeIfNeeded(s, segmentIndex); + return s.put(key, hash, value, memory); + } + } + + private Segment resizeIfNeeded(Segment s, int segmentIndex) { + int newLen = s.getNewMapLen(); + if (newLen == 0) { + return s; + } + // another thread might have resized + // (as we retrieved the segment before synchronizing on it) + Segment s2 = segments[segmentIndex]; + if (s == s2) { + // no other thread resized, so we do + s = new Segment<>(s, newLen); + segments[segmentIndex] = s; + } + return s; + } + + /** + * Add an entry to the cache using a memory size of 1. + * + * @param key the key (may not be null) + * @param value the value (may not be null) + * @return the old value, or null if there was no resident entry + */ + @Override + public V put(K key, V value) { + return put(key, value, sizeOf(key, value)); + } + + /** + * Get the size of the given value. The default implementation returns 1. + * + * @param key the key + * @param value the value + * @return the size + */ + @SuppressWarnings("unused") + protected int sizeOf(K key, V value) { + return 1; + } + + /** + * This method is called after the value for the given key was removed. + * It is not called on clear or put when replacing a value. + * + * @param key the key + */ + protected void onRemove(@SuppressWarnings("unused") K key) { + // do nothing + } + + /** + * Remove an entry. Both resident and non-resident entries can be + * removed. + * + * @param key the key (may not be null) + * @return the old value, or null if there was no resident entry + */ + @Override + public V remove(Object key) { + int hash = getHash(key); + int segmentIndex = getSegmentIndex(hash); + Segment s = segments[segmentIndex]; + // check whether resize is required: synchronize on s, to avoid + // concurrent resizes (concurrent reads read + // from the old segment) + synchronized (s) { + s = resizeIfNeeded(s, segmentIndex); + return s.remove(key, hash); + } + } + + /** + * Get the memory used for the given key. + * + * @param key the key (may not be null) + * @return the memory, or 0 if there is no resident entry + */ + public int getMemory(K key) { + int hash = getHash(key); + return getSegment(hash).getMemory(key, hash); + } + + /** + * Get the value for the given key if the entry is cached. This method + * adjusts the internal state of the cache sometimes, to ensure commonly + * used entries stay in the cache. + * + * @param key the key (may not be null) + * @return the value, or null if there is no resident entry + */ + @Override + public V get(Object key) { + int hash = getHash(key); + return getSegment(hash).get(key, hash); + } + + private Segment getSegment(int hash) { + return segments[getSegmentIndex(hash)]; + } + + private int getSegmentIndex(int hash) { + return (hash >>> segmentShift) & segmentMask; + } + + /** + * Get the hash code for the given key. The hash code is + * further enhanced to spread the values more evenly. + * + * @param key the key + * @return the hash code + */ + static int getHash(Object key) { + int hash = key.hashCode(); + // a supplemental secondary hash function + // to protect against hash codes that don't differ much + hash = ((hash >>> 16) ^ hash) * 0x45d9f3b; + hash = ((hash >>> 16) ^ hash) * 0x45d9f3b; + hash = (hash >>> 16) ^ hash; + return hash; + } + + /** + * Get the currently used memory. + * + * @return the used memory + */ + public long getUsedMemory() { + long x = 0; + for (Segment s : segments) { + x += s.usedMemory; + } + return x; + } + + /** + * Set the maximum memory this cache should use. This will not + * immediately cause entries to get removed however; it will only change + * the limit. To resize the internal array, call the clear method. + * + * @param maxMemory the maximum size (1 or larger) + */ + public void setMaxMemory(long maxMemory) { + if (maxMemory <= 0) { + throw new IllegalArgumentException("Max memory must be larger than 0"); + } + this.maxMemory = maxMemory; + if (segments != null) { + long max = 1 + maxMemory / segments.length; + for (Segment s : segments) { + s.setMaxMemory(max); + } + } + } + + /** + * Get the maximum memory to use. + * + * @return the maximum memory + */ + public long getMaxMemory() { + return maxMemory; + } + + /** + * Get the entry set for all resident entries. + * + * @return the entry set + */ + @Override + public Set> entrySet() { + HashMap map = new HashMap<>(); + for (K k : keySet()) { + map.put(k, find(k).value); + } + return map.entrySet(); + } + + /** + * Get the set of keys for resident entries. + * + * @return the set of keys + */ + @Override + public Set keySet() { + HashSet set = new HashSet<>(); + for (Segment s : segments) { + set.addAll(s.keySet()); + } + return set; + } + + /** + * Get the number of non-resident entries in the cache. + * + * @return the number of non-resident entries + */ + public int sizeNonResident() { + int x = 0; + for (Segment s : segments) { + x += s.queue2Size; + } + return x; + } + + /** + * Get the length of the internal map array. + * + * @return the size of the array + */ + public int sizeMapArray() { + int x = 0; + for (Segment s : segments) { + x += s.entries.length; + } + return x; + } + + /** + * Get the number of hot entries in the cache. + * + * @return the number of hot entries + */ + public int sizeHot() { + int x = 0; + for (Segment s : segments) { + x += s.mapSize - s.queueSize - s.queue2Size; + } + return x; + } + + /** + * Get the number of resident entries. + * + * @return the number of entries + */ + @Override + public int size() { + int x = 0; + for (Segment s : segments) { + x += s.mapSize - s.queue2Size; + } + return x; + } + + /** + * Get the list of keys. This method allows to read the internal state of + * the cache. + * + * @param cold if true, only keys for the cold entries are returned + * @param nonResident true for non-resident entries + * @return the key list + */ + public List keys(boolean cold, boolean nonResident) { + ArrayList keys = new ArrayList<>(); + for (Segment s : segments) { + keys.addAll(s.keys(cold, nonResident)); + } + return keys; + } + + /** + * A cache segment + * + * @param the key type + * @param the value type + */ + private static class Segment { + + /** + * The number of (hot, cold, and non-resident) entries in the map. + */ + int mapSize; + + /** + * The size of the LIRS queue for resident cold entries. + */ + int queueSize; + + /** + * The size of the LIRS queue for non-resident cold entries. + */ + int queue2Size; + + /** + * The map array. The size is always a power of 2. + */ + final Entry[] entries; + + /** + * The currently used memory. + */ + long usedMemory; + + private final CacheLIRS cache; + + /** + * How many other item are to be moved to the top of the stack before + * the current item is moved. + */ + private final int stackMoveDistance; + + /** + * The maximum memory this cache should use. + */ + private long maxMemory; + + /** + * The bit mask that is applied to the key hash code to get the index in + * the map array. The mask is the length of the array minus one. + */ + private final int mask; + + /** + * The stack of recently referenced elements. This includes all hot + * entries, and the recently referenced cold entries. Resident cold + * entries that were not recently referenced, as well as non-resident + * cold entries, are not in the stack. + *

    + * There is always at least one entry: the head entry. + */ + private final Entry stack; + + /** + * The number of entries in the stack. + */ + private int stackSize; + + /** + * The queue of resident cold entries. + *

    + * There is always at least one entry: the head entry. + */ + private final Entry queue; + + /** + * The queue of non-resident cold entries. + *

    + * There is always at least one entry: the head entry. + */ + private final Entry queue2; + + /** + * The number of times any item was moved to the top of the stack. + */ + private int stackMoveCounter; + + /** + * Create a new cache segment. + * + * @param cache the cache + * @param maxMemory the maximum memory to use + * @param stackMoveDistance the number of other entries to be moved to + * the top of the stack before moving an entry to the top + * @param len the number of hash table buckets (must be a power of 2) + */ + Segment(CacheLIRS cache, long maxMemory, + int stackMoveDistance, int len) { + this.cache = cache; + setMaxMemory(maxMemory); + this.stackMoveDistance = stackMoveDistance; + + // the bit mask has all bits set + mask = len - 1; + + // initialize the stack and queue heads + stack = new Entry<>(); + stack.stackPrev = stack.stackNext = stack; + queue = new Entry<>(); + queue.queuePrev = queue.queueNext = queue; + queue2 = new Entry<>(); + queue2.queuePrev = queue2.queueNext = queue2; + + @SuppressWarnings("unchecked") + Entry[] e = new Entry[len]; + entries = e; + } + + /** + * Create a new cache segment from an existing one. + * The caller must synchronize on the old segment, to avoid + * concurrent modifications. + * + * @param old the old segment + * @param len the number of hash table buckets (must be a power of 2) + */ + Segment(Segment old, int len) { + this(old.cache, old.maxMemory, old.stackMoveDistance, len); + Entry s = old.stack.stackPrev; + while (s != old.stack) { + Entry e = copy(s); + addToMap(e); + addToStack(e); + s = s.stackPrev; + } + s = old.queue.queuePrev; + while (s != old.queue) { + Entry e = find(s.key, getHash(s.key)); + if (e == null) { + e = copy(s); + addToMap(e); + } + addToQueue(queue, e); + s = s.queuePrev; + } + s = old.queue2.queuePrev; + while (s != old.queue2) { + Entry e = find(s.key, getHash(s.key)); + if (e == null) { + e = copy(s); + addToMap(e); + } + addToQueue(queue2, e); + s = s.queuePrev; + } + } + + /** + * Calculate the new number of hash table buckets if the internal map + * should be re-sized. + * + * @return 0 if no resizing is needed, or the new length + */ + int getNewMapLen() { + int len = mask + 1; + if (len * 3 < mapSize * 4 && len < (1 << 28)) { + // more than 75% usage + return len * 2; + } else if (len > 32 && len / 8 > mapSize) { + // less than 12% usage + return len / 2; + } + return 0; + } + + private void addToMap(Entry e) { + int index = getHash(e.key) & mask; + e.mapNext = entries[index]; + entries[index] = e; + usedMemory += e.memory; + mapSize++; + } + + private static Entry copy(Entry old) { + Entry e = new Entry<>(); + e.key = old.key; + e.value = old.value; + e.memory = old.memory; + e.topMove = old.topMove; + return e; + } + + /** + * Get the memory used for the given key. + * + * @param key the key (may not be null) + * @param hash the hash + * @return the memory, or 0 if there is no resident entry + */ + int getMemory(K key, int hash) { + Entry e = find(key, hash); + return e == null ? 0 : e.memory; + } + + /** + * Get the value for the given key if the entry is cached. This method + * adjusts the internal state of the cache sometimes, to ensure commonly + * used entries stay in the cache. + * + * @param key the key (may not be null) + * @param hash the hash + * @return the value, or null if there is no resident entry + */ + V get(Object key, int hash) { + Entry e = find(key, hash); + if (e == null) { + // the entry was not found + return null; + } + V value = e.value; + if (value == null) { + // it was a non-resident entry + return null; + } + if (e.isHot()) { + if (e != stack.stackNext) { + if (stackMoveDistance == 0 || + stackMoveCounter - e.topMove > stackMoveDistance) { + access(key, hash); + } + } + } else { + access(key, hash); + } + return value; + } + + /** + * Access an item, moving the entry to the top of the stack or front of + * the queue if found. + * + * @param key the key + */ + private synchronized void access(Object key, int hash) { + Entry e = find(key, hash); + if (e == null || e.value == null) { + return; + } + if (e.isHot()) { + if (e != stack.stackNext) { + if (stackMoveDistance == 0 || + stackMoveCounter - e.topMove > stackMoveDistance) { + // move a hot entry to the top of the stack + // unless it is already there + boolean wasEnd = e == stack.stackPrev; + removeFromStack(e); + if (wasEnd) { + // if moving the last entry, the last entry + // could now be cold, which is not allowed + pruneStack(); + } + addToStack(e); + } + } + } else { + removeFromQueue(e); + if (e.stackNext != null) { + // resident cold entries become hot + // if they are on the stack + removeFromStack(e); + // which means a hot entry needs to become cold + // (this entry is cold, that means there is at least one + // more entry in the stack, which must be hot) + convertOldestHotToCold(); + } else { + // cold entries that are not on the stack + // move to the front of the queue + addToQueue(queue, e); + } + // in any case, the cold entry is moved to the top of the stack + addToStack(e); + } + } + + /** + * Add an entry to the cache. The entry may or may not exist in the + * cache yet. This method will usually mark unknown entries as cold and + * known entries as hot. + * + * @param key the key (may not be null) + * @param hash the hash + * @param value the value (may not be null) + * @param memory the memory used for the given entry + * @return the old value, or null if there was no resident entry + */ + synchronized V put(K key, int hash, V value, int memory) { + if (value == null) { + throw new NullPointerException("The value may not be null"); + } + V old; + Entry e = find(key, hash); + if (e == null) { + old = null; + } else { + old = e.value; + remove(key, hash); + } + if (memory > maxMemory) { + // the new entry is too big to fit + return old; + } + e = new Entry<>(); + e.key = key; + e.value = value; + e.memory = memory; + int index = hash & mask; + e.mapNext = entries[index]; + entries[index] = e; + usedMemory += memory; + if (usedMemory > maxMemory) { + // old entries needs to be removed + evict(); + // if the cache is full, the new entry is + // cold if possible + if (stackSize > 0) { + // the new cold entry is at the top of the queue + addToQueue(queue, e); + } + } + mapSize++; + // added entries are always added to the stack + addToStack(e); + return old; + } + + /** + * Remove an entry. Both resident and non-resident entries can be + * removed. + * + * @param key the key (may not be null) + * @param hash the hash + * @return the old value, or null if there was no resident entry + */ + synchronized V remove(Object key, int hash) { + int index = hash & mask; + Entry e = entries[index]; + if (e == null) { + return null; + } + V old; + if (e.key.equals(key)) { + old = e.value; + entries[index] = e.mapNext; + } else { + Entry last; + do { + last = e; + e = e.mapNext; + if (e == null) { + return null; + } + } while (!e.key.equals(key)); + old = e.value; + last.mapNext = e.mapNext; + } + mapSize--; + usedMemory -= e.memory; + if (e.stackNext != null) { + removeFromStack(e); + } + if (e.isHot()) { + // when removing a hot entry, the newest cold entry gets hot, + // so the number of hot entries does not change + e = queue.queueNext; + if (e != queue) { + removeFromQueue(e); + if (e.stackNext == null) { + addToStackBottom(e); + } + } + } else { + removeFromQueue(e); + } + pruneStack(); + return old; + } + + /** + * Evict cold entries (resident and non-resident) until the memory limit + * is reached. The new entry is added as a cold entry, except if it is + * the only entry. + */ + private void evict() { + do { + evictBlock(); + } while (usedMemory > maxMemory); + } + + private void evictBlock() { + // ensure there are not too many hot entries: right shift of 5 is + // division by 32, that means if there are only 1/32 (3.125%) or + // less cold entries, a hot entry needs to become cold + while (queueSize <= (mapSize >>> 5) && stackSize > 0) { + convertOldestHotToCold(); + } + // the oldest resident cold entries become non-resident + while (usedMemory > maxMemory && queueSize > 0) { + Entry e = queue.queuePrev; + usedMemory -= e.memory; + removeFromQueue(e); + cache.onRemove(e.key); + e.value = null; + e.memory = 0; + addToQueue(queue2, e); + // the size of the non-resident-cold entries needs to be limited + while (queue2Size + queue2Size > stackSize) { + e = queue2.queuePrev; + int hash = getHash(e.key); + remove(e.key, hash); + } + } + } + + private void convertOldestHotToCold() { + // the last entry of the stack is known to be hot + Entry last = stack.stackPrev; + if (last == stack) { + // never remove the stack head itself (this would mean the + // internal structure of the cache is corrupt) + throw new IllegalStateException(); + } + // remove from stack - which is done anyway in the stack pruning, + // but we can do it here as well + removeFromStack(last); + // adding an entry to the queue will make it cold + addToQueue(queue, last); + pruneStack(); + } + + /** + * Ensure the last entry of the stack is cold. + */ + private void pruneStack() { + while (true) { + Entry last = stack.stackPrev; + // must stop at a hot entry or the stack head, + // but the stack head itself is also hot, so we + // don't have to test it + if (last.isHot()) { + break; + } + // the cold entry is still in the queue + removeFromStack(last); + } + } + + /** + * Try to find an entry in the map. + * + * @param key the key + * @param hash the hash + * @return the entry (might be a non-resident) + */ + Entry find(Object key, int hash) { + int index = hash & mask; + Entry e = entries[index]; + while (e != null && !e.key.equals(key)) { + e = e.mapNext; + } + return e; + } + + private void addToStack(Entry e) { + e.stackPrev = stack; + e.stackNext = stack.stackNext; + e.stackNext.stackPrev = e; + stack.stackNext = e; + stackSize++; + e.topMove = stackMoveCounter++; + } + + private void addToStackBottom(Entry e) { + e.stackNext = stack; + e.stackPrev = stack.stackPrev; + e.stackPrev.stackNext = e; + stack.stackPrev = e; + stackSize++; + } + + /** + * Remove the entry from the stack. The head itself must not be removed. + * + * @param e the entry + */ + private void removeFromStack(Entry e) { + e.stackPrev.stackNext = e.stackNext; + e.stackNext.stackPrev = e.stackPrev; + e.stackPrev = e.stackNext = null; + stackSize--; + } + + private void addToQueue(Entry q, Entry e) { + e.queuePrev = q; + e.queueNext = q.queueNext; + e.queueNext.queuePrev = e; + q.queueNext = e; + if (e.value != null) { + queueSize++; + } else { + queue2Size++; + } + } + + private void removeFromQueue(Entry e) { + e.queuePrev.queueNext = e.queueNext; + e.queueNext.queuePrev = e.queuePrev; + e.queuePrev = e.queueNext = null; + if (e.value != null) { + queueSize--; + } else { + queue2Size--; + } + } + + /** + * Get the list of keys. This method allows to read the internal state + * of the cache. + * + * @param cold if true, only keys for the cold entries are returned + * @param nonResident true for non-resident entries + * @return the key list + */ + synchronized List keys(boolean cold, boolean nonResident) { + ArrayList keys = new ArrayList<>(); + if (cold) { + Entry start = nonResident ? queue2 : queue; + for (Entry e = start.queueNext; e != start; + e = e.queueNext) { + keys.add(e.key); + } + } else { + for (Entry e = stack.stackNext; e != stack; + e = e.stackNext) { + keys.add(e.key); + } + } + return keys; + } + + /** + * Check whether there is a resident entry for the given key. This + * method does not adjust the internal state of the cache. + * + * @param key the key (may not be null) + * @param hash the hash + * @return true if there is a resident entry + */ + boolean containsKey(Object key, int hash) { + Entry e = find(key, hash); + return e != null && e.value != null; + } + + /** + * Get the set of keys for resident entries. + * + * @return the set of keys + */ + synchronized Set keySet() { + HashSet set = new HashSet<>(); + for (Entry e = stack.stackNext; e != stack; e = e.stackNext) { + set.add(e.key); + } + for (Entry e = queue.queueNext; e != queue; e = e.queueNext) { + set.add(e.key); + } + return set; + } + + /** + * Set the maximum memory this cache should use. This will not + * immediately cause entries to get removed however; it will only change + * the limit. To resize the internal array, call the clear method. + * + * @param maxMemory the maximum size (1 or larger) + */ + void setMaxMemory(long maxMemory) { + this.maxMemory = maxMemory; + } + + } + + /** + * A cache entry. Each entry is either hot (low inter-reference recency; + * LIR), cold (high inter-reference recency; HIR), or non-resident-cold. Hot + * entries are in the stack only. Cold entries are in the queue, and may be + * in the stack. Non-resident-cold entries have their value set to null and + * are in the stack and in the non-resident queue. + * + * @param the key type + * @param the value type + */ + static class Entry { + + /** + * The key. + */ + K key; + + /** + * The value. Set to null for non-resident-cold entries. + */ + V value; + + /** + * The estimated memory used. + */ + int memory; + + /** + * When the item was last moved to the top of the stack. + */ + int topMove; + + /** + * The next entry in the stack. + */ + Entry stackNext; + + /** + * The previous entry in the stack. + */ + Entry stackPrev; + + /** + * The next entry in the queue (either the resident queue or the + * non-resident queue). + */ + Entry queueNext; + + /** + * The previous entry in the queue. + */ + Entry queuePrev; + + /** + * The next entry in the map (the chained entry). + */ + Entry mapNext; + + /** + * Whether this entry is hot. Cold entries are in one of the two queues. + * + * @return whether the entry is hot + */ + boolean isHot() { + return queueNext == null; + } + + } + +} diff --git a/h2/src/tools/org/h2/dev/cache/package.html b/h2/src/tools/org/h2/dev/cache/package.html new file mode 100644 index 0000000..b72f46d --- /dev/null +++ b/h2/src/tools/org/h2/dev/cache/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +A LIRS cache implementation. + +

    \ No newline at end of file diff --git a/h2/src/tools/org/h2/dev/cluster/ShardedMap.java b/h2/src/tools/org/h2/dev/cluster/ShardedMap.java new file mode 100644 index 0000000..2ac17eb --- /dev/null +++ b/h2/src/tools/org/h2/dev/cluster/ShardedMap.java @@ -0,0 +1,330 @@ +/* + * 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.dev.cluster; + +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.type.DataType; +import org.h2.mvstore.type.ObjectDataType; + +/** + * A sharded map. It is typically split into multiple sub-maps that don't have + * overlapping keys. + * + * @param the key type + * @param the value type + */ +public class ShardedMap extends AbstractMap { + + private final DataType keyType; + + /** + * The shards. Each shard has a minimum and a maximum key (null for no + * limit). Key ranges are ascending but can overlap, in which case entries + * may be stored in multiple maps. If that is the case, for read operations, + * the entry in the first map is used, and for write operations, the changes + * are applied to all maps within the range. + */ + private Shard[] shards; + + public ShardedMap() { + this(new ObjectDataType()); + } + + @SuppressWarnings("unchecked") + public ShardedMap(DataType keyType) { + this.keyType = keyType; + shards = new Shard[0]; + } + + /** + * Get the size of the map. + * + * @param map the map + * @return the size + */ + static long getSize(Map map) { + if (map instanceof LargeMap) { + return ((LargeMap) map).sizeAsLong(); + } + return map.size(); + } + + /** + * Add the given shard. + * + * @param map the map + * @param min the lowest key, or null if no limit + * @param max the highest key, or null if no limit + */ + public void addMap(Map map, K min, K max) { + if (min != null && max != null && keyType.compare(min, max) > 0) { + DataUtils.newIllegalArgumentException("Invalid range: {0} .. {1}", min, max); + } + int len = shards.length + 1; + Shard[] newShards = Arrays.copyOf(shards, len); + Shard newShard = new Shard<>(); + newShard.map = map; + newShard.minIncluding = min; + newShard.maxExcluding = max; + newShards[len - 1] = newShard; + shards = newShards; + } + + private boolean isInRange(K key, Shard shard) { + if (shard.minIncluding != null) { + if (keyType.compare(key, shard.minIncluding) < 0) { + return false; + } + } + if (shard.maxExcluding != null) { + if (keyType.compare(key, shard.maxExcluding) >= 0) { + return false; + } + } + return true; + } + + @Override + public int size() { + long size = sizeAsLong(); + return size > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) size; + } + + /** + * The size of the map. + * + * @return the size + */ + public long sizeAsLong() { + Shard[] copy = shards; + for (Shard s : copy) { + if (s.minIncluding == null && s.maxExcluding == null) { + return getSize(s.map); + } + } + if (isSimpleSplit(copy)) { + long size = 0; + for (Shard s : copy) { + size += getSize(s.map); + } + return size; + } + return -1; + } + + private boolean isSimpleSplit(Shard[] shards) { + K last = null; + for (int i = 0; i < shards.length; i++) { + Shard s = shards[i]; + if (last == null) { + if (s.minIncluding != null) { + return false; + } + } else if (keyType.compare(last, s.minIncluding) != 0) { + return false; + } + if (s.maxExcluding == null) { + return i == shards.length - 1; + } + last = s.maxExcluding; + } + return last == null; + } + + @Override + public V put(K key, V value) { + V result = null; + Shard[] copy = shards; + for (Shard s : copy) { + if (isInRange(key, s)) { + V r = s.map.put(key, value); + if (result == null) { + result = r; + } + } + } + return result; + } + + @Override + public V get(Object key) { + @SuppressWarnings("unchecked") + K k = (K) key; + Shard[] copy = shards; + for (Shard s : copy) { + if (isInRange(k, s)) { + return s.map.get(k); + } + } + return null; + } + + @Override + public Set> entrySet() { + Shard[] copy = shards; + for (Shard s : copy) { + if (s.minIncluding == null && s.maxExcluding == null) { + return s.map.entrySet(); + } + } + if (isSimpleSplit(copy)) { + return new CombinedSet<>(size(), copy); + } + return null; + } + + /** + * A subset of a map. + * + * @param the key type + * @param the value type + */ + static class Shard { + + /** + * The lowest key, or null if no limit. + */ + K minIncluding; + + /** + * A key higher than the highest key, or null if no limit. + */ + K maxExcluding; + + /** + * The backing map. + */ + Map map; + + @Override + public String toString() { + StringBuilder buff = new StringBuilder(); + if (minIncluding != null) { + buff.append('[').append(minIncluding); + } + buff.append(".."); + if (maxExcluding != null) { + buff.append(maxExcluding).append(')'); + } + return buff.toString(); + } + + } + + /** + * A combination of multiple sets. + * + * @param the key type + * @param the value type + */ + private static class CombinedSet extends AbstractSet> { + + final int size; + final Shard[] shards; + + CombinedSet(int size, Shard[] shards) { + this.size = size; + this.shards = shards; + } + + @Override + public Iterator> iterator() { + return new Iterator>() { + + boolean init; + Entry current; + Iterator> currentIterator; + int shardIndex; + + private void fetchNext() { + while (currentIterator == null || !currentIterator.hasNext()) { + if (shardIndex >= shards.length) { + current = null; + return; + } + currentIterator = shards[shardIndex++].map.entrySet().iterator(); + } + current = currentIterator.next(); + } + + @Override + public boolean hasNext() { + if (!init) { + fetchNext(); + init = true; + } + return current != null; + } + + @Override + public Entry next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + Entry e = current; + fetchNext(); + return e; + } + + }; + } + + @Override + public int size() { + return size; + } + + } + + /** + * A large map. + */ + public interface LargeMap { + + /** + * The size of the map. + * + * @return the size + */ + long sizeAsLong(); + } + + /** + * A map that can efficiently return the index of a key, and the key at a + * given index. + */ + public interface CountedMap { + + /** + * Get the key at the given index. + * + * @param index the index + * @return the key + */ + K getKey(long index); + + /** + * Get the index of the given key in the map. + *

    + * If the key was found, the returned value is the index in the key + * array. If not found, the returned value is negative, where -1 means + * the provided key is smaller than any keys. See also + * Arrays.binarySearch. + * + * @param key the key + * @return the index + */ + long getKeyIndex(K key); + } + +} diff --git a/h2/src/tools/org/h2/dev/cluster/package.html b/h2/src/tools/org/h2/dev/cluster/package.html new file mode 100644 index 0000000..5e941c9 --- /dev/null +++ b/h2/src/tools/org/h2/dev/cluster/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +A clustering implementation. + +

    \ No newline at end of file diff --git a/h2/src/tools/org/h2/dev/fs/ArchiveTool.java b/h2/src/tools/org/h2/dev/fs/ArchiveTool.java new file mode 100644 index 0000000..08128e9 --- /dev/null +++ b/h2/src/tools/org/h2/dev/fs/ArchiveTool.java @@ -0,0 +1,1195 @@ +/* + * 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.dev.fs; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; + +/** + * A standalone archive tool to compress directories. It does not have any + * dependencies except for the Java libraries. + *

    + * Unlike other compression tools, it splits the data into chunks and sorts the + * chunks, so that large directories or files that contain duplicate data are + * compressed much better. + */ +public class ArchiveTool { + + /** + * The file header. + */ + private static final byte[] HEADER = {'H', '2', 'A', '1'}; + + /** + * The number of bytes per megabyte (used for the output). + */ + private static final int MB = 1000 * 1000; + + /** + * Run the tool. + * + * @param args the command line arguments + */ + public static void main(String... args) throws Exception { + Log log = new Log(); + int level = Integer.getInteger("level", Deflater.BEST_SPEED); + if (args.length == 1) { + File f = new File(args[0]); + if (f.exists()) { + if (f.isDirectory()) { + String fromDir = f.getAbsolutePath(); + String toFile = fromDir + ".at"; + compress(fromDir, toFile, level); + return; + } + String fromFile = f.getAbsolutePath(); + int dot = fromFile.lastIndexOf('.'); + if (dot > 0 && dot > fromFile.replace('\\', '/').lastIndexOf('/')) { + String toDir = fromFile.substring(0, dot); + extract(fromFile, toDir); + return; + } + } + } + String arg = args.length != 3 ? null : args[0]; + if ("-compress".equals(arg)) { + String toFile = args[1]; + String fromDir = args[2]; + compress(fromDir, toFile, level); + } else if ("-extract".equals(arg)) { + String fromFile = args[1]; + String toDir = args[2]; + extract(fromFile, toDir); + } else { + log.println("An archive tool to efficiently compress large directories"); + log.println("Command line options:"); + log.println(""); + log.println(""); + log.println("-compress "); + log.println("-extract "); + } + } + + private static void compress(String fromDir, String toFile, int level) throws IOException { + final Log log = new Log(); + final long start = System.nanoTime(); + final long startMs = System.currentTimeMillis(); + final AtomicBoolean title = new AtomicBoolean(); + long size = getSize(new File(fromDir), new Runnable() { + int count; + long lastTime = start; + @Override + public void run() { + count++; + if (count % 1000 == 0) { + long now = System.nanoTime(); + if (now - lastTime > TimeUnit.SECONDS.toNanos(3)) { + if (!title.getAndSet(true)) { + log.println("Counting files"); + } + log.print(count + " "); + lastTime = now; + } + } + } + }); + if (title.get()) { + log.println(); + } + log.println("Compressing " + size / MB + " MB at " + + new java.sql.Time(startMs).toString()); + InputStream in = getDirectoryInputStream(fromDir); + String temp = toFile + ".temp"; + OutputStream out = + new BufferedOutputStream( + new FileOutputStream(toFile), 1024 * 1024); + Deflater def = new Deflater(); + def.setLevel(level); + out = new BufferedOutputStream( + new DeflaterOutputStream(out, def), 1024 * 1024); + sort(log, in, out, temp, size); + in.close(); + out.close(); + log.println(); + log.println("Compressed to " + + new File(toFile).length() / MB + " MB in " + + TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) + + " seconds"); + log.println(); + } + + private static void extract(String fromFile, String toDir) throws IOException { + Log log = new Log(); + long start = System.nanoTime(); + long startMs = System.currentTimeMillis(); + long size = new File(fromFile).length(); + log.println("Extracting " + size / MB + " MB at " + new java.sql.Time(startMs).toString()); + InputStream in = + new BufferedInputStream( + new FileInputStream(fromFile), 1024 * 1024); + String temp = fromFile + ".temp"; + Inflater inflater = new Inflater(); + in = new InflaterInputStream(in, inflater, 1024 * 1024); + OutputStream out = getDirectoryOutputStream(toDir); + combine(log, in, out, temp); + inflater.end(); + in.close(); + out.close(); + log.println(); + log.println("Extracted in " + + TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) + + " seconds"); + } + + private static long getSize(File f, Runnable r) { + // assume a metadata entry is 40 bytes + long size = 40; + if (f.isDirectory()) { + File[] list = f.listFiles(); + if (list != null) { + for (File c : list) { + size += getSize(c, r); + } + } + } else { + size += f.length(); + } + r.run(); + return size; + } + + private static InputStream getDirectoryInputStream(final String dir) { + + File f = new File(dir); + if (!f.isDirectory() || !f.exists()) { + throw new IllegalArgumentException("Not an existing directory: " + dir); + } + + return new InputStream() { + + private final String baseDir; + private final ArrayList files = new ArrayList<>(); + private String current; + private ByteArrayInputStream meta; + private DataInputStream fileIn; + private long remaining; + + { + File f = new File(dir); + baseDir = f.getAbsolutePath(); + addDirectory(f); + } + + private void addDirectory(File f) { + File[] list = f.listFiles(); + if (list != null) { + // first all directories, then all files + for (File c : list) { + if (c.isDirectory()) { + files.add(c.getAbsolutePath()); + } + } + for (File c : list) { + if (c.isFile()) { + files.add(c.getAbsolutePath()); + } + } + } + } + + // int: metadata length + // byte: 0: directory, 1: file + // varLong: lastModified + // byte: 0: read-write, 1: read-only + // (file only) varLong: file length + // utf-8: file name + + @Override + public int read() throws IOException { + if (meta != null) { + // read from the metadata + int x = meta.read(); + if (x >= 0) { + return x; + } + meta = null; + } + if (fileIn != null) { + if (remaining > 0) { + // read from the file + int x = fileIn.read(); + remaining--; + if (x < 0) { + throw new EOFException(); + } + return x; + } + fileIn.close(); + fileIn = null; + } + if (files.isEmpty()) { + // EOF + return -1; + } + // breadth-first traversal + // first all files, then all directories + current = files.remove(files.size() - 1); + File f = new File(current); + if (f.isDirectory()) { + addDirectory(f); + } + ByteArrayOutputStream metaOut = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(metaOut); + boolean isFile = f.isFile(); + out.writeInt(0); + out.write(isFile ? 1 : 0); + out.write(!f.canWrite() ? 1 : 0); + writeVarLong(out, f.lastModified()); + if (isFile) { + remaining = f.length(); + writeVarLong(out, remaining); + fileIn = new DataInputStream(new BufferedInputStream( + new FileInputStream(current), 1024 * 1024)); + } + if (!current.startsWith(baseDir)) { + throw new IOException("File " + current + " does not start with " + baseDir); + } + String n = current.substring(baseDir.length() + 1); + out.writeUTF(n); + out.writeInt(metaOut.size()); + out.flush(); + byte[] bytes = metaOut.toByteArray(); + // copy metadata length to beginning + System.arraycopy(bytes, bytes.length - 4, bytes, 0, 4); + // cut the length + bytes = Arrays.copyOf(bytes, bytes.length - 4); + meta = new ByteArrayInputStream(bytes); + return meta.read(); + } + + @Override + public int read(byte[] buff, int offset, int length) throws IOException { + if (meta != null || fileIn == null || remaining == 0) { + return super.read(buff, offset, length); + } + int l = (int) Math.min(length, remaining); + fileIn.readFully(buff, offset, l); + remaining -= l; + return l; + } + + }; + + } + + private static OutputStream getDirectoryOutputStream(final String dir) { + new File(dir).mkdirs(); + return new OutputStream() { + + private ByteArrayOutputStream meta = new ByteArrayOutputStream(); + private OutputStream fileOut; + private File file; + private long remaining = 4; + private long modified; + private boolean readOnly; + + @Override + public void write(byte[] buff, int offset, int length) throws IOException { + while (length > 0) { + if (fileOut == null || remaining <= 1) { + write(buff[offset] & 255); + offset++; + length--; + } else { + int l = (int) Math.min(length, remaining - 1); + fileOut.write(buff, offset, l); + remaining -= l; + offset += l; + length -= l; + } + } + } + + @Override + public void write(int b) throws IOException { + if (fileOut != null) { + fileOut.write(b); + if (--remaining > 0) { + return; + } + // this can be slow, but I don't know a way to avoid it + fileOut.close(); + fileOut = null; + file.setLastModified(modified); + if (readOnly) { + file.setReadOnly(); + } + remaining = 4; + return; + } + meta.write(b); + if (--remaining > 0) { + return; + } + DataInputStream in = new DataInputStream( + new ByteArrayInputStream(meta.toByteArray())); + if (meta.size() == 4) { + // metadata is next + remaining = in.readInt() - 4; + if (remaining > 16 * 1024) { + throw new IOException("Illegal directory stream"); + } + return; + } + // read and ignore the length + in.readInt(); + boolean isFile = in.read() == 1; + readOnly = in.read() == 1; + modified = readVarLong(in); + if (isFile) { + remaining = readVarLong(in); + } else { + remaining = 4; + } + String name = dir + "/" + in.readUTF(); + file = new File(name); + if (isFile) { + if (remaining == 0) { + new File(name).createNewFile(); + remaining = 4; + } else { + fileOut = new BufferedOutputStream( + new FileOutputStream(name), 1024 * 1024); + } + } else { + file.mkdirs(); + file.setLastModified(modified); + if (readOnly) { + file.setReadOnly(); + } + } + meta.reset(); + } + }; + } + + private static void sort(Log log, InputStream in, OutputStream out, + String tempFileName, long size) throws IOException { + int bufferSize = 32 * 1024 * 1024; + DataOutputStream tempOut = new DataOutputStream(new BufferedOutputStream( + new FileOutputStream(tempFileName), 1024 * 1024)); + byte[] bytes = new byte[bufferSize]; + List segmentStart = new ArrayList<>(); + long outPos = 0; + long id = 1; + + // Temp file: segment* 0 + // Segment: chunk* 0 + // Chunk: pos* 0 sortKey data + + log.setRange(0, 30, size); + while (true) { + int len = readFully(in, bytes, bytes.length); + if (len == 0) { + break; + } + log.printProgress(len); + TreeMap map = new TreeMap<>(); + for (int pos = 0; pos < len;) { + int[] key = getKey(bytes, pos, len); + int l = key[3]; + byte[] buff = Arrays.copyOfRange(bytes, pos, pos + l); + pos += l; + Chunk c = new Chunk(null, key, buff); + Chunk old = map.get(c); + if (old == null) { + // new entry + c.idList = new ArrayList<>(); + c.idList.add(id); + map.put(c, c); + } else { + old.idList.add(id); + } + id++; + } + segmentStart.add(outPos); + for (Chunk c : map.keySet()) { + outPos += c.write(tempOut, true); + } + // end of segment + outPos += writeVarLong(tempOut, 0); + } + tempOut.close(); + long tempSize = new File(tempFileName).length(); + + // merge blocks if needed + int blockSize = 64; + boolean merge = false; + while (segmentStart.size() > blockSize) { + merge = true; + log.setRange(30, 50, tempSize); + log.println(); + log.println("Merging " + segmentStart.size() + " segments " + blockSize + ":1"); + ArrayList segmentStart2 = new ArrayList<>(); + outPos = 0; + DataOutputStream tempOut2 = new DataOutputStream(new BufferedOutputStream( + new FileOutputStream(tempFileName + ".b"), 1024 * 1024)); + while (segmentStart.size() > 0) { + segmentStart2.add(outPos); + int s = Math.min(segmentStart.size(), blockSize); + List start = segmentStart.subList(0, s); + TreeSet segmentIn = new TreeSet<>(); + long read = openSegments(start, segmentIn, tempFileName, true); + log.printProgress(read); + Chunk last = null; + Iterator it = merge(segmentIn, log); + while (it.hasNext()) { + Chunk c = it.next(); + if (last == null) { + last = c; + } else if (last.compareTo(c) == 0) { + last.idList.addAll(c.idList); + } else { + outPos += last.write(tempOut2, true); + last = c; + } + } + if (last != null) { + outPos += last.write(tempOut2, true); + } + // end of segment + outPos += writeVarLong(tempOut2, 0); + segmentStart = segmentStart.subList(s, segmentStart.size()); + } + segmentStart = segmentStart2; + tempOut2.close(); + tempSize = new File(tempFileName).length(); + new File(tempFileName).delete(); + tempFileName += ".b"; + } + if (merge) { + log.println(); + log.println("Combining " + segmentStart.size() + " segments"); + } + + TreeSet segmentIn = new TreeSet<>(); + long read = openSegments(segmentStart, segmentIn, tempFileName, true); + log.printProgress(read); + + DataOutputStream dataOut = new DataOutputStream(out); + dataOut.write(HEADER); + writeVarLong(dataOut, size); + + // File: header length chunk* 0 + // chunk: pos* 0 data + log.setRange(50, 100, tempSize); + Chunk last = null; + Iterator it = merge(segmentIn, log); + while (it.hasNext()) { + Chunk c = it.next(); + if (last == null) { + last = c; + } else if (last.compareTo(c) == 0) { + last.idList.addAll(c.idList); + } else { + last.write(dataOut, false); + last = c; + } + } + if (last != null) { + last.write(dataOut, false); + } + new File(tempFileName).delete(); + writeVarLong(dataOut, 0); + dataOut.flush(); + } + + private static long openSegments(List segmentStart, TreeSet segmentIn, + String tempFileName, boolean readKey) throws IOException { + long inPos = 0; + int bufferTotal = 64 * 1024 * 1024; + int bufferPerStream = bufferTotal / segmentStart.size(); + // FileChannel fc = new RandomAccessFile(tempFileName, "r"). + // getChannel(); + for (int i = 0; i < segmentStart.size(); i++) { + // long end = i < segmentStart.size() - 1 ? + // segmentStart.get(i+1) : fc.size(); + // InputStream in = + // new SharedInputStream(fc, segmentStart.get(i), end); + InputStream in = new FileInputStream(tempFileName); + in.skip(segmentStart.get(i)); + ChunkStream s = new ChunkStream(i); + s.readKey = readKey; + s.in = new DataInputStream(new BufferedInputStream(in, bufferPerStream)); + inPos += s.readNext(); + if (s.current != null) { + segmentIn.add(s); + } + } + return inPos; + } + + private static Iterator merge(final TreeSet segmentIn, final Log log) { + return new Iterator() { + + @Override + public boolean hasNext() { + return !segmentIn.isEmpty(); + } + + @Override + public Chunk next() { + ChunkStream s = segmentIn.first(); + segmentIn.remove(s); + Chunk c = s.current; + int len = s.readNext(); + log.printProgress(len); + if (s.current != null) { + segmentIn.add(s); + } + return c; + } + + }; + } + + /** + * Read a number of bytes. This method repeats reading until + * either the bytes have been read, or EOF. + * + * @param in the input stream + * @param buffer the target buffer + * @param max the number of bytes to read + * @return the number of bytes read (max unless EOF has been reached) + */ + private static int readFully(InputStream in, byte[] buffer, int max) + throws IOException { + int result = 0, len = Math.min(max, buffer.length); + while (len > 0) { + int l = in.read(buffer, result, len); + if (l < 0) { + break; + } + result += l; + len -= l; + } + return result; + } + + /** + * Get the sort key and length of a chunk. + */ + private static int[] getKey(byte[] data, int start, int maxPos) { + int minLen = 4 * 1024; + int mask = 4 * 1024 - 1; + long min = Long.MAX_VALUE; + int pos = start; + for (int j = 0; pos < maxPos; pos++, j++) { + if (pos <= start + 10) { + continue; + } + long hash = getSipHash24(data, pos - 10, pos, 111, 11224); + if (hash < min) { + min = hash; + } + if (j > minLen) { + if ((hash & mask) == 1) { + break; + } + if (j > minLen * 4 && (hash & (mask >> 1)) == 1) { + break; + } + if (j > minLen * 16) { + break; + } + } + } + int len = pos - start; + int[] counts = new int[8]; + for (int i = start; i < pos; i++) { + int x = data[i] & 0xff; + counts[x >> 5]++; + } + int cs = 0; + for (int i = 0; i < 8; i++) { + cs *= 2; + if (counts[i] > (len / 32)) { + cs += 1; + } + } + int[] key = new int[4]; + // TODO test if cs makes a difference + key[0] = (int) (min >>> 32); + key[1] = (int) min; + key[2] = cs; + key[3] = len; + return key; + } + + private static long getSipHash24(byte[] b, int start, int end, long k0, + long k1) { + long v0 = k0 ^ 0x736f6d6570736575L; + long v1 = k1 ^ 0x646f72616e646f6dL; + long v2 = k0 ^ 0x6c7967656e657261L; + long v3 = k1 ^ 0x7465646279746573L; + int repeat; + for (int off = start; off <= end + 8; off += 8) { + long m; + if (off <= end) { + m = 0; + int i = 0; + for (; i < 8 && off + i < end; i++) { + m |= ((long) b[off + i] & 255) << (8 * i); + } + if (i < 8) { + m |= ((long) end - start) << 56; + } + v3 ^= m; + repeat = 2; + } else { + m = 0; + v2 ^= 0xff; + repeat = 4; + } + for (int i = 0; i < repeat; i++) { + v0 += v1; + v2 += v3; + v1 = Long.rotateLeft(v1, 13); + v3 = Long.rotateLeft(v3, 16); + v1 ^= v0; + v3 ^= v2; + v0 = Long.rotateLeft(v0, 32); + v2 += v1; + v0 += v3; + v1 = Long.rotateLeft(v1, 17); + v3 = Long.rotateLeft(v3, 21); + v1 ^= v2; + v3 ^= v0; + v2 = Long.rotateLeft(v2, 32); + } + v0 ^= m; + } + return v0 ^ v1 ^ v2 ^ v3; + } + + /** + * Get the sort key and length of a chunk. + */ + private static int[] getKeyOld(byte[] data, int start, int maxPos) { + int minLen = 4 * 1024; + int mask = 4 * 1024 - 1; + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + int pos = start; + long bytes = 0; + for (int j = 0; pos < maxPos; pos++, j++) { + bytes = (bytes << 8) | (data[pos] & 255); + int hash = getHash(bytes); + if (hash < min) { + min = hash; + } + if (hash > max) { + max = hash; + } + if (j > minLen) { + if ((hash & mask) == 1) { + break; + } + if (j > minLen * 4 && (hash & (mask >> 1)) == 1) { + break; + } + if (j > minLen * 16) { + break; + } + } + } + int len = pos - start; + int[] counts = new int[8]; + for (int i = start; i < pos; i++) { + int x = data[i] & 0xff; + counts[x >> 5]++; + } + int cs = 0; + for (int i = 0; i < 8; i++) { + cs *= 2; + if (counts[i] > (len / 32)) { + cs += 1; + } + } + int[] key = new int[4]; + key[0] = cs; + key[1] = min; + key[2] = max; + key[3] = len; + return key; + } + + private static int getHash(long key) { + int hash = (int) ((key >>> 32) ^ key); + hash = ((hash >>> 16) ^ hash) * 0x45d9f3b; + hash = ((hash >>> 16) ^ hash) * 0x45d9f3b; + hash = (hash >>> 16) ^ hash; + return hash; + } + + private static void combine(Log log, InputStream in, OutputStream out, + String tempFileName) throws IOException { + int bufferSize = 16 * 1024 * 1024; + DataOutputStream tempOut = + new DataOutputStream( + new BufferedOutputStream( + new FileOutputStream(tempFileName), 1024 * 1024)); + + // File: header length chunk* 0 + // chunk: pos* 0 data + + DataInputStream dataIn = new DataInputStream(in); + byte[] header = new byte[4]; + dataIn.readFully(header); + if (!Arrays.equals(header, HEADER)) { + tempOut.close(); + throw new IOException("Invalid header"); + } + long size = readVarLong(dataIn); + long outPos = 0; + List segmentStart = new ArrayList<>(); + boolean end = false; + + // Temp file: segment* 0 + // Segment: chunk* 0 + // Chunk: pos* 0 data + log.setRange(0, 30, size); + while (!end) { + int segmentSize = 0; + TreeMap map = new TreeMap<>(); + while (segmentSize < bufferSize) { + Chunk c = Chunk.read(dataIn, false); + if (c == null) { + end = true; + break; + } + int length = c.value.length; + log.printProgress(length); + segmentSize += length; + for (long x : c.idList) { + map.put(x, c.value); + } + } + if (map.size() == 0) { + break; + } + segmentStart.add(outPos); + for (Long x : map.keySet()) { + outPos += writeVarLong(tempOut, x); + outPos += writeVarLong(tempOut, 0); + byte[] v = map.get(x); + outPos += writeVarLong(tempOut, v.length); + tempOut.write(v); + outPos += v.length; + } + outPos += writeVarLong(tempOut, 0); + } + tempOut.close(); + long tempSize = new File(tempFileName).length(); + size = outPos; + + // merge blocks if needed + int blockSize = 64; + boolean merge = false; + while (segmentStart.size() > blockSize) { + merge = true; + log.setRange(30, 50, tempSize); + log.println(); + log.println("Merging " + segmentStart.size() + " segments " + blockSize + ":1"); + ArrayList segmentStart2 = new ArrayList<>(); + outPos = 0; + DataOutputStream tempOut2 = new DataOutputStream(new BufferedOutputStream( + new FileOutputStream(tempFileName + ".b"), 1024 * 1024)); + while (segmentStart.size() > 0) { + segmentStart2.add(outPos); + int s = Math.min(segmentStart.size(), blockSize); + List start = segmentStart.subList(0, s); + TreeSet segmentIn = new TreeSet<>(); + long read = openSegments(start, segmentIn, tempFileName, false); + log.printProgress(read); + + Iterator it = merge(segmentIn, log); + while (it.hasNext()) { + Chunk c = it.next(); + outPos += writeVarLong(tempOut2, c.idList.get(0)); + outPos += writeVarLong(tempOut2, 0); + outPos += writeVarLong(tempOut2, c.value.length); + tempOut2.write(c.value); + outPos += c.value.length; + } + outPos += writeVarLong(tempOut2, 0); + + segmentStart = segmentStart.subList(s, segmentStart.size()); + } + segmentStart = segmentStart2; + tempOut2.close(); + tempSize = new File(tempFileName).length(); + new File(tempFileName).delete(); + tempFileName += ".b"; + } + if (merge) { + log.println(); + log.println("Combining " + segmentStart.size() + " segments"); + } + + TreeSet segmentIn = new TreeSet<>(); + DataOutputStream dataOut = new DataOutputStream(out); + log.setRange(50, 100, size); + + long read = openSegments(segmentStart, segmentIn, tempFileName, false); + log.printProgress(read); + + Iterator it = merge(segmentIn, log); + while (it.hasNext()) { + dataOut.write(it.next().value); + } + new File(tempFileName).delete(); + dataOut.flush(); + } + + /** + * A stream of chunks. + */ + static class ChunkStream implements Comparable { + final int id; + Chunk current; + DataInputStream in; + boolean readKey; + + ChunkStream(int id) { + this.id = id; + } + + /** + * Read the next chunk. + * + * @return the number of bytes read + */ + int readNext() { + current = null; + current = Chunk.read(in, readKey); + if (current == null) { + return 0; + } + return current.value.length; + } + + @Override + public int compareTo(ChunkStream o) { + int comp = current.compareTo(o.current); + if (comp != 0) { + return comp; + } + return Integer.signum(id - o.id); + } + } + + /** + * A chunk of data. + */ + static class Chunk implements Comparable { + ArrayList idList; + final byte[] value; + private final int[] sortKey; + + Chunk(ArrayList idList, int[] sortKey, byte[] value) { + this.idList = idList; + this.sortKey = sortKey; + this.value = value; + } + + /** + * Read a chunk. + * + * @param in the input stream + * @param readKey whether to read the sort key + * @return the chunk, or null if 0 has been read + */ + public static Chunk read(DataInputStream in, boolean readKey) { + try { + ArrayList idList = new ArrayList<>(); + while (true) { + long x = readVarLong(in); + if (x == 0) { + break; + } + idList.add(x); + } + if (idList.isEmpty()) { + // eof + in.close(); + return null; + } + int[] key = null; + if (readKey) { + key = new int[4]; + for (int i = 0; i < key.length; i++) { + key[i] = in.readInt(); + } + } + int len = (int) readVarLong(in); + byte[] value = new byte[len]; + in.readFully(value); + return new Chunk(idList, key, value); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Write a chunk. + * + * @param out the output stream + * @param writeKey whether to write the sort key + * @return the number of bytes written + */ + int write(DataOutputStream out, boolean writeKey) throws IOException { + int len = 0; + for (long x : idList) { + len += writeVarLong(out, x); + } + len += writeVarLong(out, 0); + if (writeKey) { + for (int i = 0; i < sortKey.length; i++) { + out.writeInt(sortKey[i]); + len += 4; + } + } + len += writeVarLong(out, value.length); + out.write(value); + len += value.length; + return len; + } + + @Override + public int compareTo(Chunk o) { + if (sortKey == null) { + // sort by id + long a = idList.get(0); + long b = o.idList.get(0); + if (a < b) { + return -1; + } else if (a > b) { + return 1; + } + return 0; + } + for (int i = 0; i < sortKey.length; i++) { + if (sortKey[i] < o.sortKey[i]) { + return -1; + } else if (sortKey[i] > o.sortKey[i]) { + return 1; + } + } + if (value.length < o.value.length) { + return -1; + } else if (value.length > o.value.length) { + return 1; + } + for (int i = 0; i < value.length; i++) { + int a = value[i] & 255; + int b = o.value[i] & 255; + if (a < b) { + return -1; + } else if (a > b) { + return 1; + } + } + return 0; + } + } + + /** + * A logger, including context. + */ + static class Log { + + private long lastTime; + private long current; + private int pos; + private int low; + private int high; + private long total; + + /** + * Print an empty line. + */ + void println() { + System.out.println(); + pos = 0; + } + + /** + * Print a message. + * + * @param msg the message + */ + void print(String msg) { + System.out.print(msg); + } + + /** + * Print a message. + * + * @param msg the message + */ + void println(String msg) { + System.out.println(msg); + pos = 0; + } + + /** + * Set the range. + * + * @param low the percent value if current = 0 + * @param high the percent value if current = total + * @param total the maximum value + */ + void setRange(int low, int high, long total) { + this.low = low; + this.high = high; + this.current = 0; + this.total = total; + } + + /** + * Print the progress. + * + * @param offset the offset since the last operation + */ + void printProgress(long offset) { + current += offset; + long now = System.nanoTime(); + if (now - lastTime > TimeUnit.SECONDS.toNanos(3)) { + String msg = (low + (high - low) * current / total) + "% "; + if (pos > 80) { + System.out.println(); + pos = 0; + } + System.out.print(msg); + pos += msg.length(); + lastTime = now; + } + } + + } + + /** + * Write a variable size long value. + * + * @param out the output stream + * @param x the value + * @return the number of bytes written + */ + static int writeVarLong(OutputStream out, long x) + throws IOException { + int len = 0; + while ((x & ~0x7f) != 0) { + out.write((byte) (0x80 | (x & 0x7f))); + x >>>= 7; + len++; + } + out.write((byte) x); + return ++len; + } + + /** + * Read a variable size long value. + * + * @param in the input stream + * @return the value + */ + static long readVarLong(InputStream in) throws IOException { + long x = in.read(); + if (x < 0) { + throw new EOFException(); + } + x = (byte) x; + if (x >= 0) { + return x; + } + x &= 0x7f; + for (int s = 7; s < 64; s += 7) { + long b = in.read(); + if (b < 0) { + throw new EOFException(); + } + b = (byte) b; + x |= (b & 0x7f) << s; + if (b >= 0) { + break; + } + } + return x; + } + + /** + * An input stream that uses a shared file channel. + */ + static class SharedInputStream extends InputStream { + private final FileChannel channel; + private final long endPosition; + private long position; + + SharedInputStream(FileChannel channel, long position, long endPosition) { + this.channel = channel; + this.position = position; + this.endPosition = endPosition; + } + + @Override + public int read() { + throw new UnsupportedOperationException(); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (len == 0) { + return 0; + } + len = (int) Math.min(len, endPosition - position); + if (len <= 0) { + return -1; + } + ByteBuffer buff = ByteBuffer.wrap(b, off, len); + len = channel.read(buff, position); + position += len; + return len; + } + + } + +} diff --git a/h2/src/tools/org/h2/dev/fs/ArchiveToolStore.java b/h2/src/tools/org/h2/dev/fs/ArchiveToolStore.java new file mode 100644 index 0000000..6324d2f --- /dev/null +++ b/h2/src/tools/org/h2/dev/fs/ArchiveToolStore.java @@ -0,0 +1,518 @@ +/* + * 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.dev.fs; + +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Map.Entry; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import org.h2.mvstore.Cursor; +import org.h2.mvstore.DataUtils; +import org.h2.mvstore.MVMap; +import org.h2.mvstore.MVStore; +import org.h2.store.fs.FileUtils; + +/** + * An archive tool to compress directories, using the MVStore backend. + */ +public class ArchiveToolStore { + + private static final int[] RANDOM = new int[256]; + private static final int MB = 1000 * 1000; + private long lastTime; + private long start; + private int bucket; + private String fileName; + + static { + Random r = new Random(1); + for (int i = 0; i < RANDOM.length; i++) { + RANDOM[i] = r.nextInt(); + } + } + + /** + * Run the tool. + * + * @param args the command line arguments + */ + public static void main(String... args) throws Exception { + ArchiveToolStore app = new ArchiveToolStore(); + String arg = args.length != 3 ? null : args[0]; + if ("-compress".equals(arg)) { + app.fileName = args[1]; + app.compress(args[2]); + } else if ("-extract".equals(arg)) { + app.fileName = args[1]; + app.expand(args[2]); + } else { + System.out.println("Command line options:"); + System.out.println("-compress "); + System.out.println("-extract "); + } + } + + private void compress(String sourceDir) throws Exception { + start(); + long tempSize = 8 * 1024 * 1024; + String tempFileName = fileName + ".temp"; + ArrayList fileNames = new ArrayList<>(); + + System.out.println("Reading the file list"); + long totalSize = addFiles(sourceDir, fileNames); + System.out.println("Compressing " + totalSize / MB + " MB"); + + FileUtils.delete(tempFileName); + FileUtils.delete(fileName); + MVStore storeTemp = new MVStore.Builder(). + fileName(tempFileName). + autoCommitDisabled(). + open(); + final MVStore store = new MVStore.Builder(). + fileName(fileName). + pageSplitSize(2 * 1024 * 1024). + compressHigh(). + autoCommitDisabled(). + open(); + MVMap filesTemp = storeTemp.openMap("files"); + long currentSize = 0; + int segmentId = 1; + int segmentLength = 0; + ByteBuffer buff = ByteBuffer.allocate(1024 * 1024); + for (String s : fileNames) { + String name = s.substring(sourceDir.length() + 1); + if (FileUtils.isDirectory(s)) { + // directory + filesTemp.put(name, new int[1]); + continue; + } + buff.clear(); + buff.flip(); + ArrayList posList = new ArrayList<>(); + try (FileChannel fc = FileUtils.open(s, "r")) { + boolean eof = false; + while (true) { + while (!eof && buff.remaining() < 512 * 1024) { + int remaining = buff.remaining(); + buff.compact(); + buff.position(remaining); + int l = fc.read(buff); + if (l < 0) { + eof = true; + } + buff.flip(); + } + if (buff.remaining() == 0) { + break; + } + int position = buff.position(); + int c = getChunkLength(buff.array(), position, + buff.limit()) - position; + byte[] bytes = Arrays.copyOfRange(buff.array(), position, position + c); + buff.position(position + c); + int[] key = getKey(bucket, bytes); + key[3] = segmentId; + while (true) { + MVMap data = storeTemp. + openMap("data" + segmentId); + byte[] old = data.get(key); + if (old == null) { + // new + data.put(key, bytes); + break; + } + if (Arrays.equals(old, bytes)) { + // duplicate + break; + } + // same checksum: change checksum + key[2]++; + } + for (int i = 0; i < key.length; i++) { + posList.add(key[i]); + } + segmentLength += c; + currentSize += c; + if (segmentLength > tempSize) { + storeTemp.commit(); + segmentId++; + segmentLength = 0; + } + printProgress(0, 50, currentSize, totalSize); + } + } + int[] posArray = new int[posList.size()]; + for (int i = 0; i < posList.size(); i++) { + posArray[i] = posList.get(i); + } + filesTemp.put(name, posArray); + } + storeTemp.commit(); + ArrayList> list = new ArrayList<>(segmentId-1); + totalSize = 0; + for (int i = 1; i <= segmentId; i++) { + MVMap data = storeTemp.openMap("data" + i); + totalSize += data.sizeAsLong(); + Cursor c = data.cursor(null); + if (c.hasNext()) { + c.next(); + list.add(c); + } + } + segmentId = 1; + segmentLength = 0; + currentSize = 0; + MVMap data = store.openMap("data" + segmentId); + MVMap keepSegment = storeTemp.openMap("keep"); + while (list.size() > 0) { + list.sort((o1, o2) -> { + int[] k1 = o1.getKey(); + int[] k2 = o2.getKey(); + int comp = 0; + for (int i = 0; i < k1.length - 1; i++) { + long x1 = k1[i]; + long x2 = k2[i]; + if (x1 > x2) { + comp = 1; + break; + } else if (x1 < x2) { + comp = -1; + break; + } + } + return comp; + }); + Cursor top = list.get(0); + int[] key = top.getKey(); + byte[] bytes = top.getValue(); + int[] k2 = Arrays.copyOf(key, key.length); + k2[key.length - 1] = 0; + // TODO this lookup can be avoided + // if we remember the last entry with k[..] = 0 + byte[] old = data.get(k2); + if (old == null) { + if (segmentLength > tempSize) { + // switch only for new entries + // where segmentId is 0, + // so that entries with the same + // key but different segmentId + // are in the same segment + store.commit(); + segmentLength = 0; + segmentId++; + data = store.openMap("data" + segmentId); + } + key = k2; + // new entry + data.put(key, bytes); + segmentLength += bytes.length; + } else if (Arrays.equals(old, bytes)) { + // duplicate + } else { + // almost a duplicate: + // keep segment id + keepSegment.put(key, Boolean.TRUE); + data.put(key, bytes); + segmentLength += bytes.length; + } + if (!top.hasNext()) { + list.remove(0); + } else { + top.next(); + } + currentSize++; + printProgress(50, 100, currentSize, totalSize); + } + MVMap files = store.openMap("files"); + for (Entry e : filesTemp.entrySet()) { + String k = e.getKey(); + int[] ids = e.getValue(); + if (ids.length == 1) { + // directory + files.put(k, ids); + continue; + } + int[] newIds = Arrays.copyOf(ids, ids.length); + for (int i = 0; i < ids.length; i += 4) { + int[] id = new int[4]; + id[0] = ids[i]; + id[1] = ids[i + 1]; + id[2] = ids[i + 2]; + id[3] = ids[i + 3]; + if (!keepSegment.containsKey(id)) { + newIds[i + 3] = 0; + } + } + files.put(k, newIds); + } + store.commit(); + storeTemp.close(); + FileUtils.delete(tempFileName); + store.close(); + System.out.println(); + System.out.println("Compressed to " + + FileUtils.size(fileName) / MB + " MB"); + printDone(); + } + + private void start() { + this.start = System.nanoTime(); + this.lastTime = start; + } + + private void printProgress(int low, int high, long current, long total) { + long now = System.nanoTime(); + if (now - lastTime > TimeUnit.SECONDS.toNanos(5)) { + System.out.print((low + (high - low) * current / total) + "% "); + lastTime = now; + } + } + + private void printDone() { + System.out.println("Done in " + + TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start) + + " seconds"); + } + + private static long addFiles(String dir, ArrayList list) { + long size = 0; + for (String s : FileUtils.newDirectoryStream(dir)) { + if (FileUtils.isDirectory(s)) { + size += addFiles(s, list); + } else { + size += FileUtils.size(s); + } + list.add(s); + } + return size; + } + + private void expand(String targetDir) throws Exception { + start(); + long tempSize = 8 * 1024 * 1024; + String tempFileName = fileName + ".temp"; + FileUtils.createDirectories(targetDir); + MVStore store = new MVStore.Builder(). + fileName(fileName).open(); + MVMap files = store.openMap("files"); + System.out.println("Extracting " + files.size() + " files"); + MVStore storeTemp = null; + FileUtils.delete(tempFileName); + long totalSize = 0; + int lastSegment = 0; + for (int i = 1;; i++) { + if (!store.hasMap("data" + i)) { + lastSegment = i - 1; + break; + } + } + + storeTemp = new MVStore.Builder(). + fileName(tempFileName). + autoCommitDisabled(). + open(); + + MVMap fileNames = storeTemp.openMap("fileNames"); + + MVMap filesTemp = storeTemp.openMap("files"); + int fileId = 0; + for (Entry e : files.entrySet()) { + fileNames.put(fileId++, e.getKey()); + filesTemp.put(e.getKey(), e.getValue()); + totalSize += e.getValue().length / 4; + } + storeTemp.commit(); + + files = filesTemp; + long currentSize = 0; + int chunkSize = 0; + for (int s = 1; s <= lastSegment; s++) { + MVMap segmentData = store.openMap("data" + s); + // key: fileId, blockId; value: data + MVMap fileData = storeTemp.openMap("fileData" + s); + fileId = 0; + for (Entry e : files.entrySet()) { + int[] keys = e.getValue(); + if (keys.length == 1) { + fileId++; + continue; + } + for (int i = 0; i < keys.length; i += 4) { + int[] dk = new int[4]; + dk[0] = keys[i]; + dk[1] = keys[i + 1]; + dk[2] = keys[i + 2]; + dk[3] = keys[i + 3]; + byte[] bytes = segmentData.get(dk); + if (bytes != null) { + int[] k = new int[] { fileId, i / 4 }; + fileData.put(k, bytes); + chunkSize += bytes.length; + if (chunkSize > tempSize) { + storeTemp.commit(); + chunkSize = 0; + } + currentSize++; + printProgress(0, 50, currentSize, totalSize); + } + } + fileId++; + } + storeTemp.commit(); + } + + ArrayList> list = new ArrayList<>(lastSegment-1); + totalSize = 0; + currentSize = 0; + for (int i = 1; i <= lastSegment; i++) { + MVMap fileData = storeTemp.openMap("fileData" + i); + totalSize += fileData.sizeAsLong(); + Cursor c = fileData.cursor(null); + if (c.hasNext()) { + c.next(); + list.add(c); + } + } + String lastFileName = null; + OutputStream file = null; + int[] lastKey = null; + while (list.size() > 0) { + list.sort((o1, o2) -> { + int[] k1 = o1.getKey(); + int[] k2 = o2.getKey(); + int comp = 0; + for (int i = 0; i < k1.length; i++) { + long x1 = k1[i]; + long x2 = k2[i]; + if (x1 > x2) { + comp = 1; + break; + } else if (x1 < x2) { + comp = -1; + break; + } + } + return comp; + }); + Cursor top = list.get(0); + int[] key = top.getKey(); + byte[] bytes = top.getValue(); + String f = targetDir + "/" + fileNames.get(key[0]); + if (!f.equals(lastFileName)) { + if (file != null) { + file.close(); + } + String p = FileUtils.getParent(f); + if (p != null) { + FileUtils.createDirectories(p); + } + file = new BufferedOutputStream(new FileOutputStream(f)); + lastFileName = f; + } else { + if (key[0] != lastKey[0] || key[1] != lastKey[1] + 1) { + System.out.println("missing entry after " + Arrays.toString(lastKey)); + } + } + lastKey = key; + file.write(bytes); + if (!top.hasNext()) { + list.remove(0); + } else { + top.next(); + } + currentSize++; + printProgress(50, 100, currentSize, totalSize); + } + for (Entry e : files.entrySet()) { + String f = targetDir + "/" + e.getKey(); + int[] keys = e.getValue(); + if (keys.length == 1) { + FileUtils.createDirectories(f); + } else if (keys.length == 0) { + // empty file + String p = FileUtils.getParent(f); + if (p != null) { + FileUtils.createDirectories(p); + } + new FileOutputStream(f).close(); + } + } + if (file != null) { + file.close(); + } + store.close(); + + storeTemp.close(); + FileUtils.delete(tempFileName); + + System.out.println(); + printDone(); + } + + private int getChunkLength(byte[] data, int start, int maxPos) { + int minLen = 4 * 1024; + int mask = 4 * 1024 - 1; + int factor = 31; + int hash = 0, mul = 1, offset = 8; + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + int i = start; + int[] rand = RANDOM; + for (int j = 0; i < maxPos; i++, j++) { + hash = hash * factor + rand[data[i] & 255]; + if (j >= offset) { + hash -= mul * rand[data[i - offset] & 255]; + } else { + mul *= factor; + } + if (hash < min) { + min = hash; + } + if (hash > max) { + max = hash; + } + if (j > minLen) { + if (j > minLen * 4) { + break; + } + if ((hash & mask) == 1) { + break; + } + } + } + bucket = min; + return i; + } + + private static int[] getKey(int bucket, byte[] buff) { + int[] key = new int[4]; + int[] counts = new int[8]; + int len = buff.length; + for (int i = 0; i < len; i++) { + int x = buff[i] & 0xff; + counts[x >> 5]++; + } + int cs = 0; + for (int i = 0; i < 8; i++) { + cs *= 2; + if (counts[i] > (len / 32)) { + cs += 1; + } + } + key[0] = cs; + key[1] = bucket; + key[2] = DataUtils.getFletcher32(buff, 0, buff.length); + return key; + } + +} diff --git a/h2/src/tools/org/h2/dev/fs/FilePathZip2.java b/h2/src/tools/org/h2/dev/fs/FilePathZip2.java new file mode 100644 index 0000000..9b0acae --- /dev/null +++ b/h2/src/tools/org/h2/dev/fs/FilePathZip2.java @@ -0,0 +1,456 @@ +/* + * 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.dev.fs; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.util.ArrayList; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import org.h2.engine.Constants; +import org.h2.message.DbException; +import org.h2.store.fs.FakeFileChannel; +import org.h2.store.fs.FileBase; +import org.h2.store.fs.FilePath; +import org.h2.store.fs.FileUtils; +import org.h2.store.fs.disk.FilePathDisk; +import org.h2.util.IOUtils; + +/** + * This is a read-only file system that allows to access databases stored in a + * .zip or .jar file. The problem of this file system is that data is always + * accessed as a stream. This implementation allows to stack file systems. + */ +public class FilePathZip2 extends FilePath { + + /** + * Register the file system. + * + * @return the instance + */ + public static FilePathZip2 register() { + FilePathZip2 instance = new FilePathZip2(); + FilePath.register(instance); + return instance; + } + + @Override + public FilePathZip2 getPath(String path) { + FilePathZip2 p = new FilePathZip2(); + p.name = path; + return p; + } + + @Override + public void createDirectory() { + // ignore + } + + @Override + public boolean createFile() { + throw DbException.getUnsupportedException("write"); + } + + @Override + public FilePath createTempFile(String suffix, boolean inTempDir) throws IOException { + if (!inTempDir) { + throw new IOException("File system is read-only"); + } + return new FilePathDisk().getPath(name).createTempFile(suffix, true); + } + + @Override + public void delete() { + throw DbException.getUnsupportedException("write"); + } + + @Override + public boolean exists() { + try { + String entryName = getEntryName(); + if (entryName.length() == 0) { + return true; + } + ZipInputStream file = openZip(); + boolean result = false; + while (true) { + ZipEntry entry = file.getNextEntry(); + if (entry == null) { + break; + } + if (entry.getName().equals(entryName)) { + result = true; + break; + } + file.closeEntry(); + } + file.close(); + return result; + } catch (IOException e) { + return false; + } + } + + @Override + public long lastModified() { + return 0; + } + + @Override + public FilePath getParent() { + int idx = name.lastIndexOf('/'); + return idx < 0 ? null : getPath(name.substring(0, idx)); + } + + @Override + public boolean isAbsolute() { + String fileName = translateFileName(name); + return FilePath.get(fileName).isAbsolute(); + } + + @Override + public FilePath unwrap() { + return FilePath.get(name.substring(getScheme().length() + 1)); + } + + @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 directory; + } + ZipInputStream file = openZip(); + boolean result = false; + while (true) { + ZipEntry entry = file.getNextEntry(); + if (entry == null) { + break; + } + String n = entry.getName(); + if (n.equals(entryName)) { + result = entry.isDirectory() == directory; + break; + } else if (n.startsWith(entryName)) { + if (n.length() == entryName.length() + 1) { + if (n.equals(entryName + "/")) { + result = directory; + break; + } + } + } + file.closeEntry(); + } + file.close(); + return result; + } catch (IOException e) { + return false; + } + } + + @Override + public boolean canWrite() { + return false; + } + + @Override + public boolean setReadOnly() { + return true; + } + + @Override + public long size() { + try { + String entryName = getEntryName(); + ZipInputStream file = openZip(); + long result = 0; + while (true) { + ZipEntry entry = file.getNextEntry(); + if (entry == null) { + break; + } + if (entry.getName().equals(entryName)) { + result = entry.getSize(); + if (result == -1) { + result = 0; + while (true) { + long x = file.skip(16 * Constants.IO_BUFFER_SIZE); + if (x == 0) { + break; + } + result += x; + } + } + break; + } + file.closeEntry(); + } + file.close(); + return result; + } catch (IOException e) { + return 0; + } + } + + @Override + public ArrayList newDirectoryStream() { + String path = name; + try { + if (path.indexOf('!') < 0) { + path += "!"; + } + if (!path.endsWith("/")) { + path += "/"; + } + ZipInputStream file = openZip(); + String dirName = getEntryName(); + String prefix = path.substring(0, path.length() - dirName.length()); + ArrayList list = new ArrayList<>(); + while (true) { + ZipEntry entry = file.getNextEntry(); + if (entry == null) { + break; + } + String entryName = entry.getName(); + if (entryName.startsWith(dirName) && entryName.length() > dirName.length()) { + int idx = entryName.indexOf('/', dirName.length()); + if (idx < 0 || idx >= entryName.length() - 1) { + list.add(getPath(prefix + entryName)); + } + } + file.closeEntry(); + } + file.close(); + return list; + } catch (IOException e) { + throw DbException.convertIOException(e, "listFiles " + path); + } + } + + @Override + public FilePath toRealPath() { + return this; + } + + @Override + public InputStream newInputStream() throws IOException { + return Channels.newInputStream(open("r")); + } + + @Override + public FileChannel open(String mode) throws IOException { + String entryName = getEntryName(); + if (entryName.length() == 0) { + throw new FileNotFoundException(); + } + ZipInputStream in = openZip(); + while (true) { + ZipEntry entry = in.getNextEntry(); + if (entry == null) { + break; + } + if (entry.getName().equals(entryName)) { + return new FileZip2(name, entryName, in, size()); + } + in.closeEntry(); + } + in.close(); + throw new FileNotFoundException(name); + } + + @Override + public OutputStream newOutputStream(boolean append) { + throw DbException.getUnsupportedException("write"); + } + + @Override + public void moveTo(FilePath newName, boolean atomicReplace) { + throw DbException.getUnsupportedException("write"); + } + + private String getEntryName() { + int idx = name.indexOf('!'); + String fileName; + if (idx <= 0) { + fileName = ""; + } else { + fileName = name.substring(idx + 1); + } + fileName = fileName.replace('\\', '/'); + if (fileName.startsWith("/")) { + fileName = fileName.substring(1); + } + return fileName; + } + + private ZipInputStream openZip() throws IOException { + String fileName = translateFileName(name); + return new ZipInputStream(FileUtils.newInputStream(fileName)); + } + + private static String translateFileName(String fileName) { + if (fileName.startsWith("zip2:")) { + fileName = fileName.substring("zip2:".length()); + } + int idx = fileName.indexOf('!'); + if (idx >= 0) { + fileName = fileName.substring(0, idx); + } + return FilePathDisk.expandUserHomeDirectory(fileName); + } + + @Override + public String getScheme() { + return "zip2"; + } + +} + +/** + * The file is read from a stream. When reading from start to end, the same + * input stream is re-used, however when reading from end to start, a new input + * stream is opened for each request. + */ +class FileZip2 extends FileBase { + + private static final byte[] SKIP_BUFFER = new byte[1024]; + + private final String fullName; + private final String name; + private final long length; + private long pos; + private InputStream in; + private long inPos; + private boolean skipUsingRead; + + FileZip2(String fullName, String name, ZipInputStream in, long length) { + this.fullName = fullName; + this.name = name; + this.length = length; + this.in = in; + } + + @Override + public void implCloseChannel() throws IOException { + in.close(); + } + + @Override + public long position() { + return pos; + } + + @Override + public long size() { + return length; + } + + @Override + public int read(ByteBuffer dst) throws IOException { + seek(); + int len = in.read(dst.array(), dst.arrayOffset() + dst.position(), + dst.remaining()); + if (len > 0) { + dst.position(dst.position() + len); + pos += len; + inPos += len; + } + return len; + } + + private void seek() throws IOException { + if (inPos > pos) { + if (in != null) { + in.close(); + } + in = null; + } + if (in == null) { + in = FileUtils.newInputStream(fullName); + inPos = 0; + } + if (inPos < pos) { + long skip = pos - inPos; + if (!skipUsingRead) { + try { + IOUtils.skipFully(in, skip); + } catch (NullPointerException e) { + // workaround for Android + skipUsingRead = true; + } + } + if (skipUsingRead) { + while (skip > 0) { + int s = (int) Math.min(SKIP_BUFFER.length, skip); + s = in.read(SKIP_BUFFER, 0, s); + skip -= s; + } + } + inPos = pos; + } + } + + @Override + public FileChannel position(long newPos) { + this.pos = newPos; + return this; + } + + @Override + public FileChannel truncate(long newLength) throws IOException { + throw new IOException("File is read-only"); + } + + @Override + public void force(boolean metaData) throws IOException { + // nothing to do + } + + @Override + public int write(ByteBuffer src) throws IOException { + throw new IOException("File is read-only"); + } + + @Override + public synchronized FileLock tryLock(long position, long size, + boolean shared) throws IOException { + if (shared) { + return new FileLock(FakeFileChannel.INSTANCE, position, size, shared) { + + @Override + public boolean isValid() { + return true; + } + + @Override + public void release() throws IOException { + // ignore + }}; + } + return null; + } + + @Override + public String toString() { + return "zip2:" + name; + } + +} \ No newline at end of file diff --git a/h2/src/tools/org/h2/dev/fs/FileShell.java b/h2/src/tools/org/h2/dev/fs/FileShell.java new file mode 100644 index 0000000..be7ce88 --- /dev/null +++ b/h2/src/tools/org/h2/dev/fs/FileShell.java @@ -0,0 +1,512 @@ +/* + * 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.dev.fs; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintStream; +import java.nio.channels.FileChannel; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import org.h2.command.dml.BackupCommand; +import org.h2.engine.Constants; +import org.h2.message.DbException; +import org.h2.store.fs.FileUtils; +import org.h2.util.IOUtils; +import org.h2.util.StringUtils; +import org.h2.util.Tool; + +/** + * A shell tool that allows to list and manipulate files. + */ +public class FileShell extends Tool { + + private boolean verbose; + private BufferedReader reader; + private PrintStream err = System.err; + private InputStream in = System.in; + private String currentWorkingDirectory; + + /** + * Options are case sensitive. + *

    + * + * + * + * + * + * + * + *
    Supported options
    [-help] or [-?]Print the list of options
    [-verbose]Print stack traces
    [-run ...]Execute the given commands and exit
    + * Multiple commands may be executed if separated by ; + * + * @param args the command line arguments + * @throws SQLException on failure + */ + public static void main(String... args) throws SQLException { + new FileShell().runTool(args); + } + + /** + * Sets the standard error stream. + * + * @param err the new standard error stream + */ + public void setErr(PrintStream err) { + this.err = err; + } + + /** + * Redirects the standard input. By default, System.in is used. + * + * @param in the input stream to use + */ + public void setIn(InputStream in) { + this.in = in; + } + + /** + * Redirects the standard input. By default, System.in is used. + * + * @param reader the input stream reader to use + */ + public void setInReader(BufferedReader reader) { + this.reader = reader; + } + + /** + * Run the shell tool with the given command line settings. + * + * @param args the command line settings + */ + @Override + public void runTool(String... args) throws SQLException { + try { + currentWorkingDirectory = new File(".").getCanonicalPath(); + } catch (IOException e) { + throw DbException.convertIOException(e, "cwd"); + } + for (int i = 0; args != null && i < args.length; i++) { + String arg = args[i]; + if (arg.equals("-run")) { + try { + execute(args[++i]); + } catch (Exception e) { + throw DbException.convert(e); + } + } else if (arg.equals("-verbose")) { + verbose = true; + } else if (arg.equals("-help") || arg.equals("-?")) { + showUsage(); + return; + } else { + showUsageAndThrowUnsupportedOption(arg); + } + } + promptLoop(); + } + + private void promptLoop() { + println(""); + println("Welcome to H2 File Shell " + Constants.FULL_VERSION); + println("Exit with Ctrl+C"); + showHelp(); + if (reader == null) { + reader = new BufferedReader(new InputStreamReader(in)); + } + println(FileUtils.toRealPath(currentWorkingDirectory)); + while (true) { + try { + print("> "); + String line = readLine(); + if (line == null) { + break; + } + line = line.trim(); + if (line.length() == 0) { + continue; + } + try { + execute(line); + } catch (Exception e) { + error(e); + } + } catch (Exception e) { + error(e); + break; + } + } + } + + private void execute(String line) throws IOException { + String[] commands = StringUtils.arraySplit(line, ';', true); + for (String command : commands) { + String[] list = StringUtils.arraySplit(command, ' ', true); + if (!execute(list)) { + break; + } + } + } + + private boolean execute(String[] list) throws IOException { + // TODO unit tests for everything (multiple commands, errors, ...) + // TODO less (support large files) + // TODO hex dump + int i = 0; + String c = list[i++]; + if ("exit".equals(c) || "quit".equals(c)) { + end(list, i); + return false; + } else if ("help".equals(c) || "?".equals(c)) { + showHelp(); + end(list, i); + } else if ("cat".equals(c)) { + String file = getFile(list[i++]); + end(list, i); + cat(file, Long.MAX_VALUE); + } else if ("cd".equals(c)) { + String dir = getFile(list[i++]); + end(list, i); + if (FileUtils.isDirectory(dir)) { + currentWorkingDirectory = dir; + println(dir); + } else { + println("Not a directory: " + dir); + } + } else if ("chmod".equals(c)) { + String mode = list[i++]; + String file = getFile(list[i++]); + end(list, i); + if ("-w".equals(mode)) { + boolean success = FileUtils.setReadOnly(file); + println(success ? "Success" : "Failed"); + } else { + println("Unsupported mode: " + mode); + } + } else if ("cp".equals(c)) { + String source = getFile(list[i++]); + String target = getFile(list[i++]); + end(list, i); + IOUtils.copyFiles(source, target); + } else if ("head".equals(c)) { + String file = getFile(list[i++]); + end(list, i); + cat(file, 1024); + } else if ("ls".equals(c)) { + String dir = currentWorkingDirectory; + if (i < list.length) { + dir = getFile(list[i++]); + } + end(list, i); + println(dir); + for (String file : FileUtils.newDirectoryStream(dir)) { + StringBuilder buff = new StringBuilder(); + buff.append(FileUtils.isDirectory(file) ? "d" : "-"); + buff.append(FileUtils.canWrite(file) ? "rw" : "r-"); + buff.append(' '); + buff.append(String.format("%10d", FileUtils.size(file))); + buff.append(' '); + long lastMod = FileUtils.lastModified(file); + buff.append(new Timestamp(lastMod).toString()); + buff.append(' '); + buff.append(FileUtils.getName(file)); + println(buff.toString()); + } + } else if ("mkdir".equals(c)) { + String dir = getFile(list[i++]); + end(list, i); + FileUtils.createDirectories(dir); + } else if ("mv".equals(c)) { + String source = getFile(list[i++]); + String target = getFile(list[i++]); + end(list, i); + FileUtils.move(source, target); + } else if ("pwd".equals(c)) { + end(list, i); + println(FileUtils.toRealPath(currentWorkingDirectory)); + } else if ("rm".equals(c)) { + if ("-r".equals(list[i])) { + i++; + String dir = getFile(list[i++]); + end(list, i); + FileUtils.deleteRecursive(dir, true); + } else if ("-rf".equals(list[i])) { + i++; + String dir = getFile(list[i++]); + end(list, i); + FileUtils.deleteRecursive(dir, false); + } else { + String file = getFile(list[i++]); + end(list, i); + FileUtils.delete(file); + } + } else if ("touch".equals(c)) { + String file = getFile(list[i++]); + end(list, i); + truncate(file, FileUtils.size(file)); + } else if ("truncate".equals(c)) { + if ("-s".equals(list[i])) { + i++; + long length = Long.decode(list[i++]); + String file = getFile(list[i++]); + end(list, i); + truncate(file, length); + } else { + println("Unsupported option"); + } + } else if ("unzip".equals(c)) { + String file = getFile(list[i++]); + end(list, i); + unzip(file, currentWorkingDirectory); + } else if ("zip".equals(c)) { + boolean recursive = false; + if ("-r".equals(list[i])) { + i++; + recursive = true; + } + String target = getFile(list[i++]); + ArrayList source = new ArrayList<>(); + readFileList(list, i, source, recursive); + zip(target, currentWorkingDirectory, source); + } + return true; + } + + private static void end(String[] list, int index) throws IOException { + if (list.length != index) { + throw new IOException("End of command expected, got: " + list[index]); + } + } + + private void cat(String fileName, long length) { + if (!FileUtils.exists(fileName)) { + print("No such file: " + fileName); + } + if (FileUtils.isDirectory(fileName)) { + print("Is a directory: " + fileName); + } + InputStream inFile = null; + try { + inFile = FileUtils.newInputStream(fileName); + IOUtils.copy(inFile, out, length); + } catch (IOException e) { + error(e); + } finally { + IOUtils.closeSilently(inFile); + } + println(""); + } + + private void truncate(String fileName, long length) { + FileChannel f = null; + try { + f = FileUtils.open(fileName, "rw"); + f.truncate(length); + } catch (IOException e) { + error(e); + } finally { + try { + f.close(); + } catch (IOException e) { + error(e); + } + } + } + + private void error(Exception e) { + println("Exception: " + e.getMessage()); + if (verbose) { + e.printStackTrace(err); + } + } + + private static void zip(String zipFileName, String base, + ArrayList source) { + FileUtils.delete(zipFileName); + OutputStream fileOut = null; + try { + fileOut = FileUtils.newOutputStream(zipFileName, false); + ZipOutputStream zipOut = new ZipOutputStream(fileOut); + for (String fileName : source) { + String f = FileUtils.toRealPath(fileName); + if (!f.startsWith(base)) { + throw DbException.getInternalError(f + " does not start with " + base); + } + if (f.endsWith(zipFileName)) { + continue; + } + if (FileUtils.isDirectory(fileName)) { + continue; + } + f = f.substring(base.length()); + f = BackupCommand.correctFileName(f); + ZipEntry entry = new ZipEntry(f); + zipOut.putNextEntry(entry); + InputStream in = null; + try { + in = FileUtils.newInputStream(fileName); + IOUtils.copyAndCloseInput(in, zipOut); + } catch (FileNotFoundException e) { + // the file could have been deleted in the meantime + // ignore this (in this case an empty file is created) + } finally { + IOUtils.closeSilently(in); + } + zipOut.closeEntry(); + } + zipOut.closeEntry(); + zipOut.close(); + } catch (IOException e) { + throw DbException.convertIOException(e, zipFileName); + } finally { + IOUtils.closeSilently(fileOut); + } + } + + private void unzip(String zipFileName, String targetDir) { + InputStream inFile = null; + try { + inFile = FileUtils.newInputStream(zipFileName); + ZipInputStream zipIn = new ZipInputStream(inFile); + while (true) { + ZipEntry entry = zipIn.getNextEntry(); + if (entry == null) { + break; + } + String fileName = entry.getName(); + // restoring windows backups on linux and vice versa + fileName = IOUtils.nameSeparatorsToNative(fileName); + if (fileName.startsWith(File.separator)) { + fileName = fileName.substring(1); + } + OutputStream o = null; + try { + o = FileUtils.newOutputStream(targetDir + File.separatorChar + fileName, false); + IOUtils.copy(zipIn, o); + o.close(); + } finally { + IOUtils.closeSilently(o); + } + zipIn.closeEntry(); + } + zipIn.closeEntry(); + zipIn.close(); + } catch (IOException e) { + error(e); + } finally { + IOUtils.closeSilently(inFile); + } + } + + private int readFileList(String[] list, int i, ArrayList target, + boolean recursive) throws IOException { + while (i < list.length) { + String c = list[i++]; + if (";".equals(c)) { + break; + } + c = getFile(c); + if (!FileUtils.exists(c)) { + throw new IOException("File not found: " + c); + } + if (recursive) { + addFilesRecursive(c, target); + } else { + target.add(c); + } + } + return i; + } + + private void addFilesRecursive(String f, ArrayList target) { + if (FileUtils.isDirectory(f)) { + for (String c : FileUtils.newDirectoryStream(f)) { + addFilesRecursive(c, target); + } + } else { + target.add(getFile(f)); + } + } + + private String getFile(String f) { + if (FileUtils.isAbsolute(f)) { + return f; + } + String unwrapped = FileUtils.unwrap(f); + String prefix = f.substring(0, f.length() - unwrapped.length()); + f = prefix + currentWorkingDirectory + File.separatorChar + unwrapped; + return FileUtils.toRealPath(f); + } + + private void showHelp() { + println("Commands are case sensitive"); + println("? or help " + + "Display this help"); + println("cat " + + "Print the contents of the file"); + println("cd " + + "Change the directory"); + println("chmod -w " + + "Make the file read-only"); + println("cp " + + "Copy a file"); + println("head " + + "Print the first few lines of the contents"); + println("ls [] " + + "Print the directory contents"); + println("mkdir " + + "Create a directory (including parent directories)"); + println("mv " + + "Rename a file or directory"); + println("pwd " + + "Print the current working directory"); + println("rm " + + "Remove a file"); + println("rm -r " + + "Remove a directory, recursively"); + println("rm -rf " + + "Remove a directory, recursively; force"); + println("touch " + + "Update the last modified date (creates the file)"); + println("truncate -s " + + "Set the file length"); + println("unzip " + + "Extract all files from the zip file"); + println("zip [-r] " + + "Create a zip file (-r to recurse directories)"); + println("exit Exit"); + println(""); + } + + private String readLine() throws IOException { + String line = reader.readLine(); + if (line == null) { + throw new IOException("Aborted"); + } + return line; + } + + private void print(String s) { + out.print(s); + out.flush(); + } + + private void println(String s) { + out.println(s); + out.flush(); + } + +} diff --git a/h2/src/tools/org/h2/dev/fs/package.html b/h2/src/tools/org/h2/dev/fs/package.html new file mode 100644 index 0000000..e541d95 --- /dev/null +++ b/h2/src/tools/org/h2/dev/fs/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +An encrypting file system. + +

    \ No newline at end of file diff --git a/h2/src/tools/org/h2/dev/ftp/FtpClient.java b/h2/src/tools/org/h2/dev/ftp/FtpClient.java new file mode 100644 index 0000000..faf1f36 --- /dev/null +++ b/h2/src/tools/org/h2/dev/ftp/FtpClient.java @@ -0,0 +1,475 @@ +/* + * 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.dev.ftp; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.net.Socket; +import java.nio.charset.StandardCharsets; + +import org.h2.util.IOUtils; +import org.h2.util.NetUtils; +import org.h2.util.StringUtils; + +/** + * A simple standalone FTP client. + */ +public class FtpClient { + private Socket socket; + private BufferedReader reader; + private PrintWriter writer; + private int code; + private String message; + private InputStream inData; + private OutputStream outData; + + private FtpClient() { + // don't allow construction + } + + /** + * Open an FTP connection. + * + * @param url the FTP URL + * @return the ftp client object + */ + public static FtpClient open(String url) throws IOException { + FtpClient client = new FtpClient(); + client.connect(url); + return client; + } + + private void connect(String url) throws IOException { + socket = NetUtils.createSocket(url, 21, false); + InputStream in = socket.getInputStream(); + OutputStream out = socket.getOutputStream(); + reader = new BufferedReader(new InputStreamReader(in)); + writer = new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)); + readCode(220); + } + + private void readLine() throws IOException { + while (true) { + message = reader.readLine(); + if (message != null) { + int idxSpace = message.indexOf(' '); + int idxMinus = message.indexOf('-'); + int idx = idxSpace < 0 ? idxMinus : idxMinus < 0 ? idxSpace + : Math.min(idxSpace, idxMinus); + if (idx < 0) { + code = 0; + } else { + code = Integer.parseInt(message.substring(0, idx)); + message = message.substring(idx + 1); + } + } + break; + } + } + + private void readCode(int optional, int expected) throws IOException { + readLine(); + if (code == optional) { + readLine(); + } + if (code != expected) { + throw new IOException("Expected: " + expected + " got: " + code + + " " + message); + } + } + + private void readCode(int expected) throws IOException { + readCode(-1, expected); + } + + private void send(String command) { + writer.println(command); + writer.flush(); + } + + /** + * Login to this FTP server (USER, PASS, SYST, SITE, STRU F, TYPE I). + * + * @param userName the user name + * @param password the password + */ + public void login(String userName, String password) throws IOException { + send("USER " + userName); + readCode(331); + send("PASS " + password); + readCode(230); + send("SYST"); + readCode(215); + send("SITE"); + readCode(500); + send("STRU F"); + readCode(200); + send("TYPE I"); + readCode(200); + } + + /** + * Close the connection (QUIT). + */ + public void close() throws IOException { + if (socket != null) { + send("QUIT"); + readCode(221); + socket.close(); + } + } + + /** + * Change the working directory (CWD). + * + * @param dir the new directory + */ + public void changeWorkingDirectory(String dir) throws IOException { + send("CWD " + dir); + readCode(250); + } + + /** + * Change to the parent directory (CDUP). + */ + public void changeDirectoryUp() throws IOException { + send("CDUP"); + readCode(250); + } + + /** + * Delete a file (DELE). + * + * @param fileName the name of the file to delete + */ + void delete(String fileName) throws IOException { + send("DELE " + fileName); + readCode(226, 250); + } + + /** + * Create a directory (MKD). + * + * @param dir the directory to create + */ + public void makeDirectory(String dir) throws IOException { + send("MKD " + dir); + readCode(226, 257); + } + + /** + * Change the transfer mode (MODE). + * + * @param mode the mode + */ + void mode(String mode) throws IOException { + send("MODE " + mode); + readCode(200); + } + + /** + * Change the modified time of a file (MDTM). + * + * @param fileName the file name + */ + void modificationTime(String fileName) throws IOException { + send("MDTM " + fileName); + readCode(213); + } + + /** + * Issue a no-operation statement (NOOP). + */ + void noOperation() throws IOException { + send("NOOP"); + readCode(200); + } + + /** + * Print the working directory (PWD). + * + * @return the working directory + */ + String printWorkingDirectory() throws IOException { + send("PWD"); + readCode(257); + return removeQuotes(); + } + + private String removeQuotes() { + int first = message.indexOf('"') + 1; + int last = message.lastIndexOf('"'); + StringBuilder buff = new StringBuilder(); + for (int i = first; i < last; i++) { + char ch = message.charAt(i); + buff.append(ch); + if (ch == '\"') { + i++; + } + } + return buff.toString(); + } + + private void passive() throws IOException { + send("PASV"); + readCode(226, 227); + int first = message.indexOf('(') + 1; + int last = message.indexOf(')'); + String[] address = StringUtils.arraySplit( + message.substring(first, last), ',', true); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < 4; i++) { + if (i > 0) { + builder.append('.'); + } + builder.append(address[i]); + } + String ip = builder.toString(); + InetAddress addr = InetAddress.getByName(ip); + int port = (Integer.parseInt(address[4]) << 8) | Integer.parseInt(address[5]); + Socket socketData = NetUtils.createSocket(addr, port, false); + inData = socketData.getInputStream(); + outData = socketData.getOutputStream(); + } + + /** + * Rename a file (RNFR / RNTO). + * + * @param fromFileName the old file name + * @param toFileName the new file name + */ + void rename(String fromFileName, String toFileName) throws IOException { + send("RNFR " + fromFileName); + readCode(350); + send("RNTO " + toFileName); + readCode(250); + } + + /** + * Read a file. + * + * @param fileName the file name + * @return the content, null if the file doesn't exist + */ + public byte[] retrieve(String fileName) throws IOException { + ByteArrayOutputStream buff = new ByteArrayOutputStream(); + retrieve(fileName, buff, 0); + return buff.toByteArray(); + } + + /** + * Read a file ([REST] RETR). + * + * @param fileName the file name + * @param out the output stream + * @param restartAt restart at the given position (0 if no restart is + * required). + */ + void retrieve(String fileName, OutputStream out, long restartAt) + throws IOException { + passive(); + if (restartAt > 0) { + send("REST " + restartAt); + readCode(350); + } + send("RETR " + fileName); + IOUtils.copyAndClose(inData, out); + readCode(150, 226); + } + + /** + * Remove a directory (RMD). + * + * @param dir the directory to remove + */ + public void removeDirectory(String dir) throws IOException { + send("RMD " + dir); + readCode(226, 250); + } + + /** + * Remove all files and directory in a directory, and then delete the + * directory itself. + * + * @param dir the directory to remove + */ + public void removeDirectoryRecursive(String dir) throws IOException { + for (File f : listFiles(dir)) { + String name = f.getName(); + if (f.isDirectory()) { + if (!name.equals(".") && !name.equals("..")) { + removeDirectoryRecursive(dir + "/" + name); + } + } else { + delete(dir + "/" + name); + } + } + removeDirectory(dir); + } + + /** + * Get the size of a file (SIZE). + * + * @param fileName the file name + * @return the size + */ + long size(String fileName) throws IOException { + send("SIZE " + fileName); + readCode(250); + long size = Long.parseLong(message); + return size; + } + + /** + * Store a file (STOR). + * + * @param fileName the file name + * @param in the input stream + */ + public void store(String fileName, InputStream in) throws IOException { + passive(); + send("STOR " + fileName); + readCode(150); + IOUtils.copyAndClose(in, outData); + readCode(226); + } + + /** + * Copy a local file or directory to the FTP server, recursively. + * + * @param file the file to copy + */ + public void storeRecursive(File file) throws IOException { + if (file.isDirectory()) { + makeDirectory(file.getName()); + changeWorkingDirectory(file.getName()); + for (File f : file.listFiles()) { + storeRecursive(f); + } + changeWorkingDirectory(".."); + } else { + InputStream in = new FileInputStream(file); + store(file.getName(), in); + } + } + + /** + * Get the directory listing (NLST). + * + * @param dir the directory + * @return the listing + */ + public String nameList(String dir) throws IOException { + passive(); + send("NLST " + dir); + readCode(150); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + IOUtils.copyAndClose(inData, out); + readCode(226); + return out.toString(); + } + + /** + * Get the directory listing (LIST). + * + * @param dir the directory + * @return the listing + */ + public String list(String dir) throws IOException { + passive(); + send("LIST " + dir); + readCode(150); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + IOUtils.copyAndClose(inData, out); + readCode(226); + return out.toString(); + } + + /** + * A file on an FTP server. + */ + static class FtpFile extends File { + private static final long serialVersionUID = 1L; + private final boolean dir; + private final long length; + FtpFile(String name, boolean dir, long length) { + super(name); + this.dir = dir; + this.length = length; + } + @Override + public long length() { + return length; + } + @Override + public boolean isFile() { + return !dir; + } + @Override + public boolean isDirectory() { + return dir; + } + @Override + public boolean exists() { + return true; + } + } + + /** + * Check if a file exists on the FTP server. + * + * @param dir the directory + * @param name the directory or file name + * @return true if it exists + */ + public boolean exists(String dir, String name) throws IOException { + for (File f : listFiles(dir)) { + if (f.getName().equals(name)) { + return true; + } + } + return false; + } + + /** + * List the files on the FTP server. + * + * @param dir the directory + * @return the list of files + */ + public File[] listFiles(String dir) throws IOException { + String content = list(dir); + String[] list = StringUtils.arraySplit(content.trim(), '\n', true); + File[] files = new File[list.length]; + for (int i = 0; i < files.length; i++) { + String s = list[i]; + while (true) { + String s2 = StringUtils.replaceAll(s, " ", " "); + if (s2.equals(s)) { + break; + } + s = s2; + } + String[] tokens = StringUtils.arraySplit(s, ' ', true); + boolean directory = tokens[0].charAt(0) == 'd'; + long length = Long.parseLong(tokens[4]); + String name = tokens[8]; + File f = new FtpFile(name, directory, length); + files[i] = f; + } + return files; + } + +} diff --git a/h2/src/tools/org/h2/dev/ftp/package.html b/h2/src/tools/org/h2/dev/ftp/package.html new file mode 100644 index 0000000..fcfd171 --- /dev/null +++ b/h2/src/tools/org/h2/dev/ftp/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +A simple FTP client. + +

    \ No newline at end of file diff --git a/h2/src/tools/org/h2/dev/ftp/server/FtpControl.java b/h2/src/tools/org/h2/dev/ftp/server/FtpControl.java new file mode 100644 index 0000000..7e0a42e --- /dev/null +++ b/h2/src/tools/org/h2/dev/ftp/server/FtpControl.java @@ -0,0 +1,420 @@ +/* + * 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.dev.ftp.server; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.charset.StandardCharsets; + +import org.h2.store.fs.FileUtils; +import org.h2.util.StringUtils; + +/** + * The implementation of the control channel of the FTP server. + */ +public class FtpControl extends Thread { + + private static final String SERVER_NAME = "Small FTP Server"; + + private final FtpServer server; + private final Socket control; + private FtpData data; + private PrintWriter output; + private String userName; + private boolean connected, readonly; + private String currentDir = "/"; + private String serverIpAddress; + private boolean stop; + private String renameFrom; + private boolean replied; + private long restart; + + FtpControl(Socket control, FtpServer server, boolean stop) { + this.server = server; + this.control = control; + this.stop = stop; + } + + @Override + public void run() { + try { + output = new PrintWriter(new OutputStreamWriter( + control.getOutputStream(), StandardCharsets.UTF_8)); + if (stop) { + reply(421, "Too many users"); + } else { + reply(220, SERVER_NAME); + // TODO need option to configure the serverIpAddress? + serverIpAddress = control.getLocalAddress().getHostAddress().replace('.', ','); + BufferedReader input = new BufferedReader( + new InputStreamReader(control.getInputStream())); + while (!stop) { + String command = null; + try { + command = input.readLine(); + } catch (IOException e) { + // ignore + } + if (command == null) { + break; + } + process(command); + } + if (data != null) { + data.close(); + } + } + } catch (Throwable t) { + server.traceError(t); + } + server.closeConnection(); + } + + private void process(String command) throws IOException { + int idx = command.indexOf(' '); + String param = ""; + if (idx >= 0) { + param = command.substring(idx).trim(); + command = command.substring(0, idx); + } + command = StringUtils.toUpperEnglish(command); + if (command.length() == 0) { + reply(506, "No command"); + return; + } + server.trace(">" + command); + FtpEventListener listener = server.getEventListener(); + FtpEvent event = null; + if (listener != null) { + event = new FtpEvent(this, command, param); + listener.beforeCommand(event); + } + replied = false; + if (connected) { + processConnected(command, param); + } + if (!replied) { + if ("USER".equals(command)) { + userName = param; + reply(331, "Need password"); + } else if ("QUIT".equals(command)) { + reply(221, "Bye"); + stop = true; + } else if ("PASS".equals(command)) { + if (userName == null) { + reply(332, "Need username"); + } else if (server.checkUserPasswordWrite(userName, param)) { + reply(230, "Ok"); + readonly = false; + connected = true; + } else if (server.checkUserPasswordReadOnly(userName)) { + reply(230, "Ok, readonly"); + readonly = true; + connected = true; + } else { + reply(431, "Wrong user/password"); + } + } else if ("REIN".equals(command)) { + userName = null; + connected = false; + currentDir = "/"; + reply(200, "Ok"); + } else if ("HELP".equals(command)) { + reply(214, SERVER_NAME); + } + } + if (!replied) { + if (listener != null) { + listener.onUnsupportedCommand(event); + } + reply(506, "Invalid command"); + } + if (listener != null) { + listener.afterCommand(event); + } + } + + private void processConnected(String command, String param) throws IOException { + switch (command.charAt(0)) { + case 'C': + if ("CWD".equals(command)) { + String path = getPath(param); + String fileName = getFileName(path); + if (FileUtils.exists(fileName) && FileUtils.isDirectory(fileName)) { + if (!path.endsWith("/")) { + path += "/"; + } + currentDir = path; + reply(250, "Ok"); + } else { + reply(550, "Failed"); + } + } else if ("CDUP".equals(command)) { + if (currentDir.length() > 1) { + int idx = currentDir.lastIndexOf('/', currentDir.length() - 2); + currentDir = currentDir.substring(0, idx + 1); + reply(250, "Ok"); + } else { + reply(550, "Failed"); + } + } + break; + case 'D': + if ("DELE".equals(command)) { + String fileName = getFileName(param); + if (!readonly && FileUtils.exists(fileName) + && !FileUtils.isDirectory(fileName) + && FileUtils.tryDelete(fileName)) { + if (server.getAllowTask() && fileName.endsWith(FtpServer.TASK_SUFFIX)) { + server.stopTask(fileName); + } + reply(250, "Ok"); + } else { + reply(500, "Delete failed"); + } + } + break; + case 'L': + if ("LIST".equals(command)) { + processList(param, true); + } + break; + case 'M': + if ("MKD".equals(command)) { + processMakeDir(param); + } else if ("MODE".equals(command)) { + if ("S".equals(StringUtils.toUpperEnglish(param))) { + reply(200, "Ok"); + } else { + reply(504, "Invalid"); + } + } else if ("MDTM".equals(command)) { + String fileName = getFileName(param); + if (FileUtils.exists(fileName) && !FileUtils.isDirectory(fileName)) { + reply(213, server.formatLastModified(fileName)); + } else { + reply(550, "Failed"); + } + } + break; + case 'N': + if ("NLST".equals(command)) { + processList(param, false); + } else if ("NOOP".equals(command)) { + reply(200, "Ok"); + } + break; + case 'P': + if ("PWD".equals(command)) { + reply(257, StringUtils.quoteIdentifier(currentDir) + " directory"); + } else if ("PASV".equals(command)) { + ServerSocket dataSocket = FtpServer.createDataSocket(); + data = new FtpData(server, control.getInetAddress(), dataSocket); + data.start(); + int port = dataSocket.getLocalPort(); + reply(227, "Passive Mode (" + serverIpAddress + "," + + (port >> 8) + "," + (port & 255) + ")"); + } else if ("PORT".equals(command)) { + String[] list = StringUtils.arraySplit(param, ',', true); + String host = list[0] + "." + list[1] + "." + list[2] + "." + list[3]; + int port = (Integer.parseInt(list[4]) << 8) | Integer.parseInt(list[5]); + InetAddress address = InetAddress.getByName(host); + if (address.equals(control.getInetAddress())) { + data = new FtpData(server, address, port); + reply(200, "Ok"); + } else { + server.trace("Port REJECTED:" + address + " expected:" + + control.getInetAddress()); + reply(550, "Failed"); + } + } + break; + case 'R': + if ("RNFR".equals(command)) { + String fileName = getFileName(param); + if (FileUtils.exists(fileName)) { + renameFrom = fileName; + reply(350, "Ok"); + } else { + reply(450, "Not found"); + } + } else if ("RNTO".equals(command)) { + if (renameFrom == null) { + reply(503, "RNFR required"); + } else { + String fileOld = renameFrom; + String fileNew = getFileName(param); + boolean ok = false; + if (!readonly) { + try { + FileUtils.move(fileOld, fileNew); + reply(250, "Ok"); + ok = true; + } catch (Exception e) { + server.traceError(e); + } + } + if (!ok) { + reply(550, "Failed"); + } + } + } else if ("RETR".equals(command)) { + String fileName = getFileName(param); + if (FileUtils.exists(fileName) && !FileUtils.isDirectory(fileName)) { + reply(150, "Starting transfer"); + try { + data.send(fileName, restart); + reply(226, "Ok"); + } catch (IOException e) { + server.traceError(e); + reply(426, "Failed"); + } + restart = 0; + } else { + // Firefox compatibility + // (still not good) + processList(param, true); + // reply(426, "Not a file"); + } + } else if ("RMD".equals(command)) { + processRemoveDir(param); + } else if ("REST".equals(command)) { + try { + restart = Integer.parseInt(param); + reply(350, "Ok"); + } catch (NumberFormatException e) { + reply(500, "Invalid"); + } + } + break; + case 'S': + if ("SYST".equals(command)) { + reply(215, "UNIX Type: L8"); + } else if ("SITE".equals(command)) { + reply(500, "Not understood"); + } else if ("SIZE".equals(command)) { + param = getFileName(param); + if (FileUtils.exists(param) && !FileUtils.isDirectory(param)) { + reply(250, Long.toString(FileUtils.size(param))); + } else { + reply(500, "Failed"); + } + } else if ("STOR".equals(command)) { + String fileName = getFileName(param); + if (!readonly && !FileUtils.exists(fileName) + || !FileUtils.isDirectory(fileName)) { + reply(150, "Starting transfer"); + try { + data.receive(fileName); + if (server.getAllowTask() && param.endsWith(FtpServer.TASK_SUFFIX)) { + server.startTask(fileName); + } + reply(226, "Ok"); + } catch (Exception e) { + server.traceError(e); + reply(426, "Failed"); + } + } else { + reply(550, "Failed"); + } + } else if ("STRU".equals(command)) { + if ("F".equals(StringUtils.toUpperEnglish(param))) { + reply(200, "Ok"); + } else { + reply(504, "Invalid"); + } + } + break; + case 'T': + if ("TYPE".equals(command)) { + param = StringUtils.toUpperEnglish(param); + if ("A".equals(param) || "A N".equals(param)) { + reply(200, "Ok"); + } else if ("I".equals(param) || "L 8".equals(param)) { + reply(200, "Ok"); + } else { + reply(500, "Invalid"); + } + } + break; + case 'X': + if ("XMKD".equals(command)) { + processMakeDir(param); + } else if ("XRMD".equals(command)) { + processRemoveDir(param); + } + break; + } + } + + private void processMakeDir(String param) { + String fileName = getFileName(param); + boolean ok = false; + if (!readonly) { + try { + FileUtils.createDirectories(fileName); + reply(257, StringUtils.quoteIdentifier(param) + " directory"); + ok = true; + } catch (Exception e) { + server.traceError(e); + } + } + if (!ok) { + reply(500, "Failed"); + } + } + + private void processRemoveDir(String param) { + String fileName = getFileName(param); + if (!readonly && FileUtils.exists(fileName) + && FileUtils.isDirectory(fileName) + && FileUtils.tryDelete(fileName)) { + reply(250, "Ok"); + } else { + reply(500, "Failed"); + } + } + + private String getFileName(String file) { + return server.getFileName(file.startsWith("/") ? file : currentDir + file); + } + + private String getPath(String path) { + return path.startsWith("/") ? path : currentDir + path; + } + + private void processList(String param, boolean directories) throws IOException { + String directory = getFileName(param); + if (!FileUtils.exists(directory)) { + reply(450, "Directory does not exist"); + return; + } else if (!FileUtils.isDirectory(directory)) { + reply(450, "Not a directory"); + return; + } + String list = server.getDirectoryListing(directory, directories); + reply(150, "Starting transfer"); + server.trace(list); + // need to use the current locale (UTF-8 would be wrong for the Windows + // Explorer) + data.send(list.getBytes()); + reply(226, "Done"); + } + + private void reply(int code, String message) { + server.trace(code + " " + message); + output.print(code + " " + message + "\r\n"); + output.flush(); + replied = true; + } + +} diff --git a/h2/src/tools/org/h2/dev/ftp/server/FtpData.java b/h2/src/tools/org/h2/dev/ftp/server/FtpData.java new file mode 100644 index 0000000..6faf765 --- /dev/null +++ b/h2/src/tools/org/h2/dev/ftp/server/FtpData.java @@ -0,0 +1,145 @@ +/* + * 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.dev.ftp.server; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import org.h2.store.fs.FileUtils; +import org.h2.util.IOUtils; + +/** + * The implementation of the data channel of the FTP server. + */ +public class FtpData extends Thread { + + private final FtpServer server; + private final InetAddress address; + private ServerSocket serverSocket; + private volatile Socket socket; + private final boolean active; + private final int port; + + FtpData(FtpServer server, InetAddress address, ServerSocket serverSocket) { + this.server = server; + this.address = address; + this.serverSocket = serverSocket; + this.port = 0; + this.active = false; + } + + FtpData(FtpServer server, InetAddress address, int port) { + this.server = server; + this.address = address; + this.port = port; + this.active = true; + } + + @Override + public void run() { + try { + synchronized (this) { + Socket s = serverSocket.accept(); + if (s.getInetAddress().equals(address)) { + server.trace("Data connected:" + s.getInetAddress() + " expected:" + address); + socket = s; + notifyAll(); + } else { + server.trace("Data REJECTED:" + s.getInetAddress() + " expected:" + address); + close(); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void connect() throws IOException { + if (active) { + socket = new Socket(address, port); + } else { + waitUntilConnected(); + } + } + + private void waitUntilConnected() { + while (serverSocket != null && socket == null) { + try { + wait(); + } catch (InterruptedException e) { + // ignore + } + } + server.trace("connected"); + } + + /** + * Close the socket. + */ + void close() { + serverSocket = null; + socket = null; + } + + /** + * Read a file from a client. + * + * @param fileName the target file name + */ + synchronized void receive(String fileName) throws IOException { + connect(); + try { + InputStream in = socket.getInputStream(); + OutputStream out = FileUtils.newOutputStream(fileName, false); + IOUtils.copy(in, out); + out.close(); + } finally { + socket.close(); + } + server.trace("closed"); + } + + /** + * Send a file to the client. This method waits until the client has + * connected. + * + * @param fileName the source file name + * @param skip the number of bytes to skip + */ + synchronized void send(String fileName, long skip) throws IOException { + connect(); + try { + OutputStream out = socket.getOutputStream(); + InputStream in = FileUtils.newInputStream(fileName); + IOUtils.skipFully(in, skip); + IOUtils.copy(in, out); + in.close(); + } finally { + socket.close(); + } + server.trace("closed"); + } + + /** + * Wait until the client has connected, and then send the data to him. + * + * @param data the data to send + */ + synchronized void send(byte[] data) throws IOException { + connect(); + try { + OutputStream out = socket.getOutputStream(); + out.write(data); + } finally { + socket.close(); + } + server.trace("closed"); + } + +} diff --git a/h2/src/tools/org/h2/dev/ftp/server/FtpEvent.java b/h2/src/tools/org/h2/dev/ftp/server/FtpEvent.java new file mode 100644 index 0000000..55f91f8 --- /dev/null +++ b/h2/src/tools/org/h2/dev/ftp/server/FtpEvent.java @@ -0,0 +1,48 @@ +/* + * 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.dev.ftp.server; + +/** + * Describes an FTP event. This class is used by the FtpEventListener. + */ +public class FtpEvent { + private final FtpControl control; + private final String command; + private final String param; + + FtpEvent(FtpControl control, String command, String param) { + this.control = control; + this.command = command; + this.param = param; + } + + /** + * Get the FTP command. Example: RETR + * + * @return the command + */ + public String getCommand() { + return command; + } + + /** + * Get the FTP control object. + * + * @return the control object + */ + public FtpControl getControl() { + return control; + } + + /** + * Get the parameter of the FTP command (if any). + * + * @return the parameter + */ + public String getParam() { + return param; + } +} diff --git a/h2/src/tools/org/h2/dev/ftp/server/FtpEventListener.java b/h2/src/tools/org/h2/dev/ftp/server/FtpEventListener.java new file mode 100644 index 0000000..e01a19a --- /dev/null +++ b/h2/src/tools/org/h2/dev/ftp/server/FtpEventListener.java @@ -0,0 +1,34 @@ +/* + * 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.dev.ftp.server; + +/** + * Event listener for the FTP Server. + */ +public interface FtpEventListener { + + /** + * Called before the given command is processed. + * + * @param event the event + */ + void beforeCommand(FtpEvent event); + + /** + * Called after the command has been processed. + * + * @param event the event + */ + void afterCommand(FtpEvent event); + + /** + * Called when an unsupported command is processed. + * This method is called after beforeCommand. + * + * @param event the event + */ + void onUnsupportedCommand(FtpEvent event); +} diff --git a/h2/src/tools/org/h2/dev/ftp/server/FtpServer.java b/h2/src/tools/org/h2/dev/ftp/server/FtpServer.java new file mode 100644 index 0000000..176e5f1 --- /dev/null +++ b/h2/src/tools/org/h2/dev/ftp/server/FtpServer.java @@ -0,0 +1,572 @@ +/* + * 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.dev.ftp.server; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.net.Socket; +import java.sql.SQLException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.Properties; +import org.h2.server.Service; +import org.h2.store.fs.FileUtils; +import org.h2.tools.Server; +import org.h2.util.IOUtils; +import org.h2.util.NetUtils; +import org.h2.util.SortedProperties; +import org.h2.util.Tool; + +/** + * Small FTP Server. Intended for ad-hoc networks in a secure environment. + * Remote connections are possible. + * See also https://cr.yp.to/ftp.html http://www.ftpguide.com/ + */ +public class FtpServer extends Tool implements Service { + + /** + * The default port to use for the FTP server. + * This value is also in the documentation and in the Server javadoc. + */ + public static final int DEFAULT_PORT = 8021; + + /** + * The default root directory name used by the FTP server. + * This value is also in the documentation and in the Server javadoc. + */ + public static final String DEFAULT_ROOT = "ftp"; + + /** + * The default user name that is allowed to read data. + * This value is also in the documentation and in the Server javadoc. + */ + public static final String DEFAULT_READ = "guest"; + + /** + * The default user name that is allowed to read and write data. + * This value is also in the documentation and in the Server javadoc. + */ + public static final String DEFAULT_WRITE = "sa"; + + /** + * The default password of the user that is allowed to read and write data. + * This value is also in the documentation and in the Server javadoc. + */ + public static final String DEFAULT_WRITE_PASSWORD = "sa"; + + static final String TASK_SUFFIX = ".task"; + + private static final int MAX_CONNECTION_COUNT = 100; + + private ServerSocket serverSocket; + private int port = DEFAULT_PORT; + private int openConnectionCount; + + private final SimpleDateFormat dateFormatNew = new SimpleDateFormat( + "MMM dd HH:mm", Locale.ENGLISH); + private final SimpleDateFormat dateFormatOld = new SimpleDateFormat( + "MMM dd yyyy", Locale.ENGLISH); + private final SimpleDateFormat dateFormat = new SimpleDateFormat( + "yyyyMMddHHmmss"); + + private String root = DEFAULT_ROOT; + private String writeUserName = DEFAULT_WRITE, + writePassword = DEFAULT_WRITE_PASSWORD; + private String readUserName = DEFAULT_READ; + private final HashMap tasks = new HashMap<>(); + + private boolean trace; + private boolean allowTask; + + private FtpEventListener eventListener; + + + /** + * When running without options, -tcp, -web, -browser, + * and -pg are started. + * Options are case sensitive. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Supported options
    [-help] or [-?]Print the list of options
    [-web]Start the web server with the H2 Console
    [-webAllowOthers]Allow other computers to connect
    [-webPort <port>]The port (default: 8082)
    [-webSSL]Use encrypted (HTTPS) connections
    [-browser]Start a browser and open a page to login to the web server
    [-tcp]Start the TCP server
    [-tcpAllowOthers]Allow other computers to connect
    [-tcpPort <port>]The port (default: 9092)
    [-tcpSSL]Use encrypted (SSL) connections
    [-tcpPassword <pwd>]The password for shutting down a TCP server
    [-tcpShutdown "<url>"]Stop the TCP server; example: tcp://localhost:9094
    [-tcpShutdownForce]Do not wait until all connections are closed
    [-pg]Start the PG server
    [-pgAllowOthers]Allow other computers to connect
    [-pgPort <port>]The port (default: 5435)
    [-ftp]Start the FTP server
    [-ftpPort <port>]The port (default: 8021)
    [-ftpDir <dir>]The base directory (default: ftp)
    [-ftpRead <user>]The user name for reading (default: guest)
    [-ftpWrite <user>]The user name for writing (default: sa)
    [-ftpWritePassword <p>]The write password (default: sa)
    [-baseDir <dir>]The base directory for H2 databases; for all servers
    [-ifExists]Only existing databases may be opened; for all servers
    [-trace]Print additional trace information; for all servers
    + * + * @param args the command line arguments + */ + public static void main(String... args) throws SQLException { + new FtpServer().runTool(args); + } + + @Override + public void runTool(String... args) throws SQLException { + for (int i = 0; args != null && i < args.length; i++) { + String arg = args[i]; + if (arg == null) { + continue; + } else if ("-?".equals(arg) || "-help".equals(arg)) { + showUsage(); + return; + } else if (arg.startsWith("-ftp")) { + if ("-ftpPort".equals(arg)) { + i++; + } else if ("-ftpDir".equals(arg)) { + i++; + } else if ("-ftpRead".equals(arg)) { + i++; + } else if ("-ftpWrite".equals(arg)) { + i++; + } else if ("-ftpWritePassword".equals(arg)) { + i++; + } else if ("-ftpTask".equals(arg)) { + // no parameters + } else { + showUsageAndThrowUnsupportedOption(arg); + } + } else if ("-trace".equals(arg)) { + // no parameters + } else { + showUsageAndThrowUnsupportedOption(arg); + } + } + Server server = new Server(this, args); + server.start(); + out.println(server.getStatus()); + } + + @Override + public void listen() { + try { + while (serverSocket != null) { + Socket s = serverSocket.accept(); + boolean stop; + synchronized (this) { + openConnectionCount++; + stop = openConnectionCount > MAX_CONNECTION_COUNT; + } + FtpControl c = new FtpControl(s, this, stop); + c.start(); + } + } catch (Exception e) { + traceError(e); + } + } + + /** + * Close a connection. The open connection count will be decremented. + */ + void closeConnection() { + synchronized (this) { + openConnectionCount--; + } + } + + /** + * Create a socket to listen for incoming data connections. + * + * @return the server socket + */ + static ServerSocket createDataSocket() { + return NetUtils.createServerSocket(0, false); + } + + private void appendFile(StringBuilder buff, String fileName) { + buff.append(FileUtils.isDirectory(fileName) ? 'd' : '-'); + buff.append('r'); + buff.append(FileUtils.canWrite(fileName) ? 'w' : '-'); + buff.append("------- 1 owner group "); + String size = Long.toString(FileUtils.size(fileName)); + for (int i = size.length(); i < 15; i++) { + buff.append(' '); + } + buff.append(size); + buff.append(' '); + Date now = new Date(), mod = new Date(FileUtils.lastModified(fileName)); + String date; + if (mod.after(now) + || Math.abs((now.getTime() - mod.getTime()) / + 1000 / 60 / 60 / 24) > 180) { + synchronized (dateFormatOld) { + date = dateFormatOld.format(mod); + } + } else { + synchronized (dateFormatNew) { + date = dateFormatNew.format(mod); + } + } + buff.append(date); + buff.append(' '); + buff.append(FileUtils.getName(fileName)); + buff.append("\r\n"); + } + + /** + * Get the last modified date of a date and format it as required by the FTP + * protocol. + * + * @param fileName the file name + * @return the last modified date of this file + */ + String formatLastModified(String fileName) { + synchronized (dateFormat) { + return dateFormat.format(new Date(FileUtils.lastModified(fileName))); + } + } + + /** + * Get the full file name of this relative path. + * + * @param path the relative path + * @return the file name + */ + String getFileName(String path) { + return root + getPath(path); + } + + private String getPath(String path) { + if (path.indexOf("..") > 0) { + path = "/"; + } + while (path.startsWith("/") && root.endsWith("/")) { + path = path.substring(1); + } + while (path.endsWith("/")) { + path = path.substring(0, path.length() - 1); + } + trace("path: " + path); + return path; + } + + /** + * Get the directory listing for this directory. + * + * @param directory the directory to list + * @param listDirectories if sub-directories should be listed + * @return the list + */ + String getDirectoryListing(String directory, boolean listDirectories) { + StringBuilder buff = new StringBuilder(); + for (String fileName : FileUtils.newDirectoryStream(directory)) { + if (!FileUtils.isDirectory(fileName) + || (FileUtils.isDirectory(fileName) && listDirectories)) { + appendFile(buff, fileName); + } + } + return buff.toString(); + } + + /** + * Check if this user name is allowed to write. + * + * @param userName the user name + * @param password the password + * @return true if this user may write + */ + boolean checkUserPasswordWrite(String userName, String password) { + return userName.equals(this.writeUserName) + && password.equals(this.writePassword); + } + + /** + * Check if this user name is allowed to read. + * + * @param userName the user name + * @return true if this user may read + */ + boolean checkUserPasswordReadOnly(String userName) { + return userName.equals(this.readUserName); + } + + @Override + public void init(String... args) { + for (int i = 0; args != null && i < args.length; i++) { + String a = args[i]; + if ("-ftpPort".equals(a)) { + port = Integer.decode(args[++i]); + } else if ("-ftpDir".equals(a)) { + root = FileUtils.toRealPath(args[++i]); + } else if ("-ftpRead".equals(a)) { + readUserName = args[++i]; + } else if ("-ftpWrite".equals(a)) { + writeUserName = args[++i]; + } else if ("-ftpWritePassword".equals(a)) { + writePassword = args[++i]; + } else if ("-trace".equals(a)) { + trace = true; + } else if ("-ftpTask".equals(a)) { + allowTask = true; + } + } + } + + @Override + public String getURL() { + return "ftp://" + NetUtils.getLocalAddress() + ":" + port; + } + + @Override + public int getPort() { + return port; + } + + @Override + public void start() { + root = FileUtils.toRealPath(root); + FileUtils.createDirectories(root); + serverSocket = NetUtils.createServerSocket(port, false); + port = serverSocket.getLocalPort(); + } + + @Override + public void stop() { + if (serverSocket == null) { + return; + } + try { + serverSocket.close(); + } catch (IOException e) { + traceError(e); + } + serverSocket = null; + } + + @Override + public boolean isRunning(boolean traceError) { + if (serverSocket == null) { + return false; + } + try { + Socket s = NetUtils.createLoopbackSocket(port, false); + s.close(); + return true; + } catch (IOException e) { + if (traceError) { + traceError(e); + } + return false; + } + } + + @Override + public boolean getAllowOthers() { + return true; + } + + @Override + public String getType() { + return "FTP"; + } + + @Override + public String getName() { + return "H2 FTP Server"; + } + + /** + * Write trace information if trace is enabled. + * + * @param s the message to write + */ + void trace(String s) { + if (trace) { + System.out.println(s); + } + } + + /** + * Write the stack trace if trace is enabled. + * + * @param e the exception + */ + void traceError(Throwable e) { + if (trace) { + e.printStackTrace(); + } + } + + boolean getAllowTask() { + return allowTask; + } + + /** + * Start a task. + * + * @param path the name of the task file + */ + void startTask(String path) throws IOException { + stopTask(path); + if (path.endsWith(".zip.task")) { + trace("expand: " + path); + Process p = Runtime.getRuntime().exec("jar -xf " + path, null, new File(root)); + new StreamRedirect(path, p.getInputStream(), null).start(); + return; + } + Properties prop = SortedProperties.loadProperties(path); + String command = prop.getProperty("command"); + String outFile = path.substring(0, path.length() - TASK_SUFFIX.length()); + String errorFile = root + "/" + + prop.getProperty("error", outFile + ".err.txt"); + String outputFile = root + "/" + + prop.getProperty("output", outFile + ".out.txt"); + trace("start process: " + path + " / " + command); + Process p = Runtime.getRuntime().exec(command, null, new File(root)); + new StreamRedirect(path, p.getErrorStream(), errorFile).start(); + new StreamRedirect(path, p.getInputStream(), outputFile).start(); + tasks.put(path, p); + } + + /** + * This class re-directs an input stream to a file. + */ + private static class StreamRedirect extends Thread { + private final InputStream in; + private OutputStream out; + private String outFile; + private final String processFile; + + StreamRedirect(String processFile, InputStream in, String outFile) { + this.processFile = processFile; + this.in = in; + this.outFile = outFile; + } + + private void openOutput() { + if (outFile != null) { + try { + this.out = FileUtils.newOutputStream(outFile, false); + } catch (Exception e) { + // ignore + } + outFile = null; + } + } + + @Override + public void run() { + while (true) { + try { + int x = in.read(); + if (x < 0) { + break; + } + openOutput(); + if (out != null) { + out.write(x); + } + } catch (IOException e) { + // ignore + } + } + IOUtils.closeSilently(out); + IOUtils.closeSilently(in); + new File(processFile).delete(); + } + } + + /** + * Stop a running task. + * + * @param processName the task name + */ + void stopTask(String processName) { + trace("kill process: " + processName); + Process p = tasks.remove(processName); + if (p == null) { + return; + } + p.destroy(); + } + + /** + * Set the event listener. Only one listener can be registered. + * + * @param eventListener the new listener, or null to de-register + */ + public void setEventListener(FtpEventListener eventListener) { + this.eventListener = eventListener; + } + + /** + * Get the registered event listener. + * + * @return the event listener, or null if non is registered + */ + FtpEventListener getEventListener() { + return eventListener; + } + + /** + * Create a new FTP server, but does not start it yet. Example: + * + *
    +     * Server server = FtpServer.createFtpServer(null).start();
    +     * 
    + * + * @param args the argument list + * @return the server + */ + public static Server createFtpServer(String... args) throws SQLException { + return new Server(new FtpServer(), args); + } + + @Override + public boolean isDaemon() { + return false; + } + +} diff --git a/h2/src/tools/org/h2/dev/ftp/server/package.html b/h2/src/tools/org/h2/dev/ftp/server/package.html new file mode 100644 index 0000000..29801cd --- /dev/null +++ b/h2/src/tools/org/h2/dev/ftp/server/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +A simple FTP server. + +

    \ No newline at end of file diff --git a/h2/src/tools/org/h2/dev/hash/IntPerfectHash.java b/h2/src/tools/org/h2/dev/hash/IntPerfectHash.java new file mode 100644 index 0000000..58db01f --- /dev/null +++ b/h2/src/tools/org/h2/dev/hash/IntPerfectHash.java @@ -0,0 +1,409 @@ +/* + * 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.dev.hash; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * A minimum perfect hash function tool. It needs about 2.2 bits per key. + */ +public class IntPerfectHash { + + /** + * Large buckets are typically divided into buckets of this size. + */ + private static final int DIVIDE = 6; + + /** + * The maximum size of a small bucket (one that is not further split if + * possible). + */ + private static final int MAX_SIZE = 12; + + /** + * The maximum offset for hash functions of small buckets. At most that many + * hash functions are tried for the given size. + */ + private static final int[] MAX_OFFSETS = { 0, 0, 8, 18, 47, 123, 319, 831, 2162, + 5622, 14617, 38006, 98815 }; + + /** + * The output value to split the bucket into many (more than 2) smaller + * buckets. + */ + private static final int SPLIT_MANY = 3; + + /** + * The minimum output value for a small bucket of a given size. + */ + private static final int[] SIZE_OFFSETS = new int[MAX_OFFSETS.length + 1]; + + static { + int last = SPLIT_MANY + 1; + for (int i = 0; i < MAX_OFFSETS.length; i++) { + SIZE_OFFSETS[i] = last; + last += MAX_OFFSETS[i]; + } + SIZE_OFFSETS[SIZE_OFFSETS.length - 1] = last; + } + + /** + * The description of the hash function. Used for calculating the hash of a + * key. + */ + private final byte[] data; + + /** + * Create a hash object to convert keys to hashes. + * + * @param data the data returned by the generate method + */ + public IntPerfectHash(byte[] data) { + this.data = data; + } + + /** + * Get the hash function description. + * + * @return the data + */ + public byte[] getData() { + return data; + } + + /** + * Calculate the hash value for the given key. + * + * @param x the key + * @return the hash value + */ + public int get(int x) { + return get(0, x, 0); + } + + /** + * Get the hash value for the given key, starting at a certain position and + * level. + * + * @param pos the start position + * @param x the key + * @param level the level + * @return the hash value + */ + private int get(int pos, int x, int level) { + int n = readVarInt(data, pos); + if (n < 2) { + return 0; + } else if (n > SPLIT_MANY) { + int size = getSize(n); + int offset = getOffset(n, size); + return hash(x, level, offset, size); + } + pos++; + int split; + if (n == SPLIT_MANY) { + split = readVarInt(data, pos); + pos += getVarIntLength(data, pos); + } else { + split = n; + } + int h = hash(x, level, 0, split); + int s; + int start = pos; + for (int i = 0; i < h; i++) { + pos = getNextPos(pos); + } + s = getSizeSum(start, pos); + return s + get(pos, x, level + 1); + } + + /** + * Get the position of the next sibling. + * + * @param pos the position of this branch + * @return the position of the next sibling + */ + private int getNextPos(int pos) { + int n = readVarInt(data, pos); + pos += getVarIntLength(data, pos); + if (n < 2 || n > SPLIT_MANY) { + return pos; + } + int split; + if (n == SPLIT_MANY) { + split = readVarInt(data, pos); + pos += getVarIntLength(data, pos); + } else { + split = n; + } + for (int i = 0; i < split; i++) { + pos = getNextPos(pos); + } + return pos; + } + + /** + * The sum of the sizes between the start and end position. + * + * @param start the start position + * @param end the end position (excluding) + * @return the sizes + */ + private int getSizeSum(int start, int end) { + int s = 0; + for (int pos = start; pos < end;) { + int n = readVarInt(data, pos); + pos += getVarIntLength(data, pos); + if (n < 2) { + s += n; + } else if (n > SPLIT_MANY) { + s += getSize(n); + } else if (n == SPLIT_MANY) { + pos += getVarIntLength(data, pos); + } + } + return s; + } + + private static void writeSizeOffset(ByteStream out, int size, + int offset) { + writeVarInt(out, SIZE_OFFSETS[size] + offset); + } + + private static int getOffset(int n, int size) { + return n - SIZE_OFFSETS[size]; + } + + private static int getSize(int n) { + for (int i = 0; i < SIZE_OFFSETS.length; i++) { + if (n < SIZE_OFFSETS[i]) { + return i - 1; + } + } + return 0; + } + + /** + * Generate the minimal perfect hash function data from the given list. + * + * @param list the data + * @return the hash function description + */ + public static byte[] generate(ArrayList list) { + ByteStream out = new ByteStream(); + generate(list, 0, out); + return out.toByteArray(); + } + + private static void generate(ArrayList list, int level, ByteStream out) { + int size = list.size(); + if (size <= 1) { + out.write((byte) size); + return; + } + if (level > 32) { + throw new IllegalStateException("Too many recursions; " + + " incorrect universal hash function?"); + } + if (size <= MAX_SIZE) { + int maxOffset = MAX_OFFSETS[size]; + int testSize = size; + nextOffset: + for (int offset = 0; offset < maxOffset; offset++) { + int bits = 0; + for (int i = 0; i < size; i++) { + int x = list.get(i); + int h = hash(x, level, offset, testSize); + if ((bits & (1 << h)) != 0) { + continue nextOffset; + } + bits |= 1 << h; + } + writeSizeOffset(out, size, offset); + return; + } + } + int split; + if (size > 57 * DIVIDE) { + split = size / (36 * DIVIDE); + } else { + split = (size - 47) / DIVIDE; + } + split = Math.max(2, split); + ArrayList> lists = new ArrayList<>(split); + for (int i = 0; i < split; i++) { + lists.add(new ArrayList(size / split)); + } + for (int x : list) { + ArrayList l = lists.get(hash(x, level, 0, split)); + l.add(x); + } + if (split >= SPLIT_MANY) { + out.write((byte) SPLIT_MANY); + } + writeVarInt(out, split); + list.clear(); + list.trimToSize(); + for (ArrayList s2 : lists) { + generate(s2, level + 1, out); + } + } + + private static int hash(int x, int level, int offset, int size) { + x += level + offset * 32; + x = ((x >>> 16) ^ x) * 0x45d9f3b; + x = ((x >>> 16) ^ x) * 0x45d9f3b; + x = (x >>> 16) ^ x; + return Math.abs(x % size); + } + + private static int writeVarInt(ByteStream out, int x) { + int len = 0; + while ((x & ~0x7f) != 0) { + out.write((byte) (0x80 | (x & 0x7f))); + x >>>= 7; + len++; + } + out.write((byte) x); + return ++len; + } + + private static int readVarInt(byte[] d, int pos) { + int x = d[pos++]; + if (x >= 0) { + return x; + } + x &= 0x7f; + for (int s = 7; s < 64; s += 7) { + int b = d[pos++]; + x |= (b & 0x7f) << s; + if (b >= 0) { + break; + } + } + return x; + } + + private static int getVarIntLength(byte[] d, int pos) { + int x = d[pos++]; + if (x >= 0) { + return 1; + } + int len = 2; + for (int s = 7; s < 64; s += 7) { + int b = d[pos++]; + if (b >= 0) { + break; + } + len++; + } + return len; + } + + /** + * A stream of bytes. + */ + static class ByteStream { + + private byte[] data; + private int pos; + + ByteStream() { + this.data = new byte[16]; + } + + ByteStream(byte[] data) { + this.data = data; + } + + /** + * Read a byte. + * + * @return the byte, or -1. + */ + int read() { + return pos < data.length ? (data[pos++] & 255) : -1; + } + + /** + * Write a byte. + * + * @param value the byte + */ + void write(byte value) { + if (pos >= data.length) { + data = Arrays.copyOf(data, data.length * 2); + } + data[pos++] = value; + } + + /** + * Get the byte array. + * + * @return the byte array + */ + byte[] toByteArray() { + return Arrays.copyOf(data, pos); + } + + } + + /** + * A helper class for bit arrays. + */ + public static class BitArray { + + /** + * Set a bit in the array. + * + * @param data the array + * @param x the bit index + * @param value the new value + * @return the bit array (if the passed one was too small) + */ + public static byte[] setBit(byte[] data, int x, boolean value) { + int pos = x / 8; + if (pos >= data.length) { + data = Arrays.copyOf(data, pos + 1); + } + if (value) { + data[pos] |= 1 << (x & 7); + } else { + data[pos] &= 255 - (1 << (x & 7)); + } + return data; + } + + /** + * Get a bit in a bit array. + * + * @param data the array + * @param x the bit index + * @return the value + */ + public static boolean getBit(byte[] data, int x) { + return (data[x / 8] & (1 << (x & 7))) != 0; + } + + /** + * Count the number of set bits. + * + * @param data the array + * @return the number of set bits + */ + public static int countBits(byte[] data) { + int count = 0; + for (byte x : data) { + count += Integer.bitCount(x & 255); + } + return count; + } + + + } + +} diff --git a/h2/src/tools/org/h2/dev/hash/MinimalPerfectHash.java b/h2/src/tools/org/h2/dev/hash/MinimalPerfectHash.java new file mode 100644 index 0000000..3019f11 --- /dev/null +++ b/h2/src/tools/org/h2/dev/hash/MinimalPerfectHash.java @@ -0,0 +1,783 @@ +/* + * 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.dev.hash; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +/** + * A minimal perfect hash function tool. It needs about 1.98 bits per key. + *

    + * The algorithm is recursive: sets that contain no or only one entry are not + * processed as no conflicts are possible. For sets that contain between 2 and + * 12 entries, a number of hash functions are tested to check if they can store + * the data without conflict. If no function was found, and for larger sets, the + * set is split into a (possibly high) number of smaller set, which are + * processed recursively. The average size of a top-level bucket is about 216 + * entries, and the maximum recursion level is typically 5. + *

    + * At the end of the generation process, the data is compressed using a general + * purpose compression tool (Deflate / Huffman coding) down to 2.0 bits per key. + * The uncompressed data is around 2.2 bits per key. With arithmetic coding, + * about 1.9 bits per key are needed. Generating the hash function takes about + * 2.5 seconds per million keys with 8 cores (multithreaded). The algorithm + * automatically scales with the number of available CPUs (using as many threads + * as there are processors). At the expense of processing time, a lower number + * of bits per key would be possible (for example 1.84 bits per key with 100000 + * keys, using 32 seconds generation time, with Huffman coding). + *

    + * The memory usage to efficiently calculate hash values is around 2.5 bits per + * key (the space needed for the uncompressed description, plus 8 bytes for + * every top-level bucket). + *

    + * At each level, only one user defined hash function per object is called + * (about 3 hash functions per key). The result is further processed using a + * supplemental hash function, so that the default user defined hash function + * doesn't need to be sophisticated (it doesn't need to be non-linear, have a + * good avalanche effect, or generate random looking data; it just should + * produce few conflicts if possible). + *

    + * To protect against hash flooding and similar attacks, a secure random seed + * per hash table is used. For further protection, cryptographically secure + * functions such as SipHash or SHA-256 can be used. However, such (slower) + * functions only need to be used if regular hash functions produce too many + * conflicts. This case is detected when generating the perfect hash function, + * by checking if there are too many conflicts (more than 2160 entries in one + * top-level bucket). In this case, the next hash function is used. That way, in + * the normal case, where no attack is happening, only fast, but less secure, + * hash functions are called. It is fine to use the regular hashCode method as + * the level 0 hash function. However, just relying on the regular hashCode + * method does not work if the key has more than 32 bits, because the risk of + * collisions is too high. Incorrect universal hash functions are detected (an + * exception is thrown if there are more than 32 recursion levels). + *

    + * In-place updating of the hash table is not implemented but possible in + * theory, by patching the hash function description. With a small change, + * non-minimal perfect hash functions can be calculated (for example 1.22 bits + * per key at a fill rate of 81%). + * + * @param the key type + */ +public class MinimalPerfectHash { + + /** + * Large buckets are typically divided into buckets of this size. + */ + private static final int DIVIDE = 6; + + /** + * For sets larger than this, instead of trying to map then uniquely to a + * set of the same size, the size of the set is incremented by one. This + * reduces the time to find a mapping, but the index of the hole also needs + * to be stored, which increases the space usage. + */ + private static final int SPEEDUP = 11; + + /** + * The maximum size of a small bucket (one that is not further split if + * possible). + */ + private static final int MAX_SIZE = 14; + + /** + * The maximum offset for hash functions of small buckets. At most that many + * hash functions are tried for the given size. + */ + private static final int[] MAX_OFFSETS = { 0, 0, 8, 18, 47, 123, 319, 831, 2162, + 5622, 14617, 38006, 98815, 256920, 667993 }; + + /** + * The output value to split the bucket into many (more than 2) smaller + * buckets. + */ + private static final int SPLIT_MANY = 3; + + /** + * The minimum output value for a small bucket of a given size. + */ + private static final int[] SIZE_OFFSETS = new int[MAX_OFFSETS.length + 1]; + + /** + * A secure random generator. + */ + private static final SecureRandom RANDOM = new SecureRandom(); + + static { + for (int i = SPEEDUP; i < MAX_OFFSETS.length; i++) { + MAX_OFFSETS[i] = (int) (MAX_OFFSETS[i] * 2.5); + } + int last = SPLIT_MANY + 1; + for (int i = 0; i < MAX_OFFSETS.length; i++) { + SIZE_OFFSETS[i] = last; + last += MAX_OFFSETS[i]; + } + SIZE_OFFSETS[SIZE_OFFSETS.length - 1] = last; + } + + /** + * The universal hash function. + */ + private final UniversalHash hash; + + /** + * The description of the hash function. Used for calculating the hash of a + * key. + */ + private final byte[] data; + + /** + * The random seed. + */ + private final int seed; + + /** + * The size up to the given root-level bucket in the data array. Used to + * speed up calculating the hash of a key. + */ + private final int[] rootSize; + + /** + * The position of the given root-level bucket in the data array. Used to + * speed up calculating the hash of a key. + */ + private final int[] rootPos; + + /** + * The hash function level at the root of the tree. Typically 0, except if + * the hash function at that level didn't split the entries as expected + * (which can be due to a bad hash function, or due to an attack). + */ + private final int rootLevel; + + /** + * Create a hash object to convert keys to hashes. + * + * @param desc the data returned by the generate method + * @param hash the universal hash function + */ + public MinimalPerfectHash(byte[] desc, UniversalHash hash) { + this.hash = hash; + byte[] b = data = expand(desc); + seed = ((b[0] & 255) << 24) | + ((b[1] & 255) << 16) | + ((b[2] & 255) << 8) | + (b[3] & 255); + if (b[4] == SPLIT_MANY) { + rootLevel = b[b.length - 1] & 255; + int split = readVarInt(b, 5); + rootSize = new int[split]; + rootPos = new int[split]; + int pos = 5 + getVarIntLength(b, 5); + int sizeSum = 0; + for (int i = 0; i < split; i++) { + rootSize[i] = sizeSum; + rootPos[i] = pos; + int start = pos; + pos = getNextPos(pos); + sizeSum += getSizeSum(start, pos); + } + } else { + rootLevel = 0; + rootSize = null; + rootPos = null; + } + } + + /** + * Calculate the hash value for the given key. + * + * @param x the key + * @return the hash value + */ + public int get(K x) { + return get(4, x, true, rootLevel); + } + + /** + * Get the hash value for the given key, starting at a certain position and + * level. + * + * @param pos the start position + * @param x the key + * @param isRoot whether this is the root of the tree + * @param level the level + * @return the hash value + */ + private int get(int pos, K x, boolean isRoot, int level) { + int n = readVarInt(data, pos); + if (n < 2) { + return 0; + } else if (n > SPLIT_MANY) { + int size = getSize(n); + int offset = getOffset(n, size); + if (size >= SPEEDUP) { + int p = offset % (size + 1); + offset = offset / (size + 1); + int result = hash(x, hash, level, seed, offset, size + 1); + if (result >= p) { + result--; + } + return result; + } + return hash(x, hash, level, seed, offset, size); + } + pos++; + int split; + if (n == SPLIT_MANY) { + split = readVarInt(data, pos); + pos += getVarIntLength(data, pos); + } else { + split = n; + } + int h = hash(x, hash, level, seed, 0, split); + int s; + if (isRoot && rootPos != null) { + s = rootSize[h]; + pos = rootPos[h]; + } else { + int start = pos; + for (int i = 0; i < h; i++) { + pos = getNextPos(pos); + } + s = getSizeSum(start, pos); + } + return s + get(pos, x, false, level + 1); + } + + /** + * Get the position of the next sibling. + * + * @param pos the position of this branch + * @return the position of the next sibling + */ + private int getNextPos(int pos) { + int n = readVarInt(data, pos); + pos += getVarIntLength(data, pos); + if (n < 2 || n > SPLIT_MANY) { + return pos; + } + int split; + if (n == SPLIT_MANY) { + split = readVarInt(data, pos); + pos += getVarIntLength(data, pos); + } else { + split = n; + } + for (int i = 0; i < split; i++) { + pos = getNextPos(pos); + } + return pos; + } + + /** + * The sum of the sizes between the start and end position. + * + * @param start the start position + * @param end the end position (excluding) + * @return the sizes + */ + private int getSizeSum(int start, int end) { + int s = 0; + for (int pos = start; pos < end;) { + int n = readVarInt(data, pos); + pos += getVarIntLength(data, pos); + if (n < 2) { + s += n; + } else if (n > SPLIT_MANY) { + s += getSize(n); + } else if (n == SPLIT_MANY) { + pos += getVarIntLength(data, pos); + } + } + return s; + } + + private static void writeSizeOffset(ByteArrayOutputStream out, int size, + int offset) { + writeVarInt(out, SIZE_OFFSETS[size] + offset); + } + + private static int getOffset(int n, int size) { + return n - SIZE_OFFSETS[size]; + } + + private static int getSize(int n) { + for (int i = 0; i < SIZE_OFFSETS.length; i++) { + if (n < SIZE_OFFSETS[i]) { + return i - 1; + } + } + return 0; + } + + /** + * Generate the minimal perfect hash function data from the given set of + * integers. + * + * @param set the data + * @param hash the universal hash function + * @return the hash function description + */ + public static byte[] generate(Set set, UniversalHash hash) { + ArrayList list = new ArrayList<>(set); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int seed = RANDOM.nextInt(); + out.write(seed >>> 24); + out.write(seed >>> 16); + out.write(seed >>> 8); + out.write(seed); + generate(list, hash, 0, seed, out); + return compress(out.toByteArray()); + } + + /** + * Generate the perfect hash function data from the given set of integers. + * + * @param list the data, in the form of a list + * @param hash the universal hash function + * @param level the recursion level + * @param seed the random seed + * @param out the output stream + */ + static void generate(ArrayList list, UniversalHash hash, + int level, int seed, ByteArrayOutputStream out) { + int size = list.size(); + if (size <= 1) { + out.write(size); + return; + } + if (level > 32) { + throw new IllegalStateException("Too many recursions; " + + " incorrect universal hash function?"); + } + if (size <= MAX_SIZE) { + int maxOffset = MAX_OFFSETS[size]; + // get the hash codes - we could stop early + // if we detect that two keys have the same hash + int[] hashes = new int[size]; + for (int i = 0; i < size; i++) { + hashes[i] = hash.hashCode(list.get(i), level, seed); + } + // use the supplemental hash function to find a way + // to make the hash code unique within this group - + // there might be a much faster way than that, by + // checking which bits of the hash code matter most + int testSize = size; + if (size >= SPEEDUP) { + testSize++; + maxOffset /= testSize; + } + nextOffset: + for (int offset = 0; offset < maxOffset; offset++) { + int bits = 0; + for (int i = 0; i < size; i++) { + int x = hashes[i]; + int h = hash(x, level, offset, testSize); + if ((bits & (1 << h)) != 0) { + continue nextOffset; + } + bits |= 1 << h; + } + if (size >= SPEEDUP) { + int pos = Integer.numberOfTrailingZeros(~bits); + writeSizeOffset(out, size, offset * (size + 1) + pos); + } else { + writeSizeOffset(out, size, offset); + } + return; + } + } + int split; + if (size > 57 * DIVIDE) { + split = size / (36 * DIVIDE); + } else { + split = (size - 47) / DIVIDE; + } + split = Math.max(2, split); + boolean isRoot = level == 0; + ArrayList> lists; + do { + lists = new ArrayList<>(split); + for (int i = 0; i < split; i++) { + lists.add(new ArrayList(size / split)); + } + for (int i = 0; i < size; i++) { + K x = list.get(i); + ArrayList l = lists.get(hash(x, hash, level, seed, 0, split)); + l.add(x); + if (isRoot && split >= SPLIT_MANY && + l.size() > 36 * DIVIDE * 10) { + // a bad hash function or attack was detected + level++; + lists = null; + break; + } + } + } while (lists == null); + if (split >= SPLIT_MANY) { + out.write(SPLIT_MANY); + } + writeVarInt(out, split); + boolean multiThreaded = isRoot && list.size() > 1000; + list.clear(); + list.trimToSize(); + if (multiThreaded) { + generateMultiThreaded(lists, hash, level, seed, out); + } else { + for (ArrayList s2 : lists) { + generate(s2, hash, level + 1, seed, out); + } + } + if (isRoot && split >= SPLIT_MANY) { + out.write(level); + } + } + + private static void generateMultiThreaded( + final ArrayList> lists, + final UniversalHash hash, + final int level, + final int seed, + ByteArrayOutputStream out) { + final ArrayList outList = + new ArrayList<>(); + int processors = Runtime.getRuntime().availableProcessors(); + Thread[] threads = new Thread[processors]; + final AtomicInteger success = new AtomicInteger(); + final AtomicReference failure = new AtomicReference<>(); + for (int i = 0; i < processors; i++) { + threads[i] = new Thread() { + @Override + public void run() { + try { + while (true) { + ArrayList list; + ByteArrayOutputStream temp = + new ByteArrayOutputStream(); + synchronized (lists) { + if (lists.isEmpty()) { + break; + } + list = lists.remove(0); + outList.add(temp); + } + generate(list, hash, level + 1, seed, temp); + } + } catch (Exception e) { + failure.set(e); + return; + } + success.incrementAndGet(); + } + }; + } + for (Thread t : threads) { + t.start(); + } + try { + for (Thread t : threads) { + t.join(); + } + if (success.get() != threads.length) { + Exception e = failure.get(); + if (e != null) { + throw new RuntimeException(e); + } + throw new RuntimeException("Unknown failure in one thread"); + } + for (ByteArrayOutputStream temp : outList) { + out.write(temp.toByteArray()); + } + } catch (InterruptedException | IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Calculate the hash of a key. The result depends on the key, the recursion + * level, and the offset. + * + * @param o the key + * @param level the recursion level + * @param seed the random seed + * @param offset the index of the hash function + * @param size the size of the bucket + * @return the hash (a value between 0, including, and the size, excluding) + */ + private static int hash(K o, UniversalHash hash, int level, + int seed, int offset, int size) { + int x = hash.hashCode(o, level, seed); + x += level + offset * 32; + x = ((x >>> 16) ^ x) * 0x45d9f3b; + x = ((x >>> 16) ^ x) * 0x45d9f3b; + x = (x >>> 16) ^ x; + return (x & (-1 >>> 1)) % size; + } + + private static int hash(int x, int level, int offset, int size) { + x += level + offset * 32; + x = ((x >>> 16) ^ x) * 0x45d9f3b; + x = ((x >>> 16) ^ x) * 0x45d9f3b; + x = (x >>> 16) ^ x; + return (x & (-1 >>> 1)) % size; + } + + private static int writeVarInt(ByteArrayOutputStream out, int x) { + int len = 0; + while ((x & ~0x7f) != 0) { + out.write((byte) (0x80 | (x & 0x7f))); + x >>>= 7; + len++; + } + out.write((byte) x); + return ++len; + } + + private static int readVarInt(byte[] d, int pos) { + int x = d[pos++]; + if (x >= 0) { + return x; + } + x &= 0x7f; + for (int s = 7; s < 64; s += 7) { + int b = d[pos++]; + x |= (b & 0x7f) << s; + if (b >= 0) { + break; + } + } + return x; + } + + private static int getVarIntLength(byte[] d, int pos) { + int x = d[pos++]; + if (x >= 0) { + return 1; + } + int len = 2; + for (int s = 7; s < 64; s += 7) { + int b = d[pos++]; + if (b >= 0) { + break; + } + len++; + } + return len; + } + + /** + * Compress the hash description using a Huffman coding. + * + * @param d the data + * @return the compressed data + */ + private static byte[] compress(byte[] d) { + Deflater deflater = new Deflater(); + deflater.setStrategy(Deflater.HUFFMAN_ONLY); + deflater.setInput(d); + deflater.finish(); + ByteArrayOutputStream out2 = new ByteArrayOutputStream(d.length); + byte[] buffer = new byte[1024]; + while (!deflater.finished()) { + int count = deflater.deflate(buffer); + out2.write(buffer, 0, count); + } + deflater.end(); + return out2.toByteArray(); + } + + /** + * Decompress the hash description using a Huffman coding. + * + * @param d the data + * @return the decompressed data + */ + private static byte[] expand(byte[] d) { + Inflater inflater = new Inflater(); + inflater.setInput(d); + ByteArrayOutputStream out = new ByteArrayOutputStream(d.length); + byte[] buffer = new byte[1024]; + try { + while (!inflater.finished()) { + int count = inflater.inflate(buffer); + out.write(buffer, 0, count); + } + inflater.end(); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + return out.toByteArray(); + } + + /** + * An interface that can calculate multiple hash values for an object. The + * returned hash value of two distinct objects may be the same for a given + * hash function index, but as more hash functions indexes are called for + * those objects, the returned value must eventually be different. + *

    + * The returned value does not need to be uniformly distributed. + * + * @param the type + */ + public interface UniversalHash { + + /** + * Calculate the hash of the given object. + * + * @param o the object + * @param index the hash function index (index 0 is used first, so the + * method should be very fast with index 0; index 1 and so on + * are only called when really needed) + * @param seed the random seed (always the same for a hash table) + * @return the hash value + */ + int hashCode(T o, int index, int seed); + + } + + /** + * A sample hash implementation for long keys. + */ + public static class LongHash implements UniversalHash { + + @Override + public int hashCode(Long o, int index, int seed) { + if (index == 0) { + return o.hashCode(); + } else if (index < 8) { + long x = o; + x += index; + x = ((x >>> 32) ^ x) * 0x45d9f3b; + x = ((x >>> 32) ^ x) * 0x45d9f3b; + return (int) (x ^ (x >>> 32)); + } + // get the lower or higher 32 bit depending on the index + int shift = (index & 1) * 32; + return (int) (o >>> shift); + } + + } + + /** + * A sample hash implementation for integer keys. + */ + public static class StringHash implements UniversalHash { + + @Override + public int hashCode(String o, int index, int seed) { + if (index == 0) { + // use the default hash of a string, which might already be + // available + return o.hashCode(); + } else if (index < 8) { + // use a different hash function, which is fast but not + // necessarily universal, and not cryptographically secure + return getFastHash(o, index, seed); + } + // this method is supposed to be cryptographically secure; + // we could use SHA-256 for higher indexes + return getSipHash24(o, index, seed); + } + + /** + * A cryptographically weak hash function. It is supposed to be fast. + * + * @param o the string + * @param index the hash function index + * @param seed the seed + * @return the hash value + */ + public static int getFastHash(String o, int index, int seed) { + int x = (index * 0x9f3b) ^ seed; + int result = seed + o.length(); + for (int i = 0; i < o.length(); i++) { + x = 31 + x * 0x9f3b; + result ^= x * (1 + o.charAt(i)); + } + return result; + } + + /** + * A cryptographically relatively secure hash function. It is supposed + * to protected against hash-flooding denial-of-service attacks. + * + * @param o the string + * @param k0 key 0 + * @param k1 key 1 + * @return the hash value + */ + public static int getSipHash24(String o, long k0, long k1) { + byte[] b = o.getBytes(StandardCharsets.UTF_8); + return getSipHash24(b, 0, b.length, k0, k1); + } + + /** + * A cryptographically relatively secure hash function. It is supposed + * to protected against hash-flooding denial-of-service attacks. + * + * @param b the data + * @param start the start position + * @param end the end position plus one + * @param k0 key 0 + * @param k1 key 1 + * @return the hash value + */ + public static int getSipHash24(byte[] b, int start, int end, long k0, long k1) { + long v0 = k0 ^ 0x736f6d6570736575L; + long v1 = k1 ^ 0x646f72616e646f6dL; + long v2 = k0 ^ 0x6c7967656e657261L; + long v3 = k1 ^ 0x7465646279746573L; + int repeat; + for (int off = start; off <= end + 8; off += 8) { + long m; + if (off <= end) { + m = 0; + int i = 0; + for (; i < 8 && off + i < end; i++) { + m |= ((long) b[off + i] & 255) << (8 * i); + } + if (i < 8) { + m |= ((long) end - start) << 56; + } + v3 ^= m; + repeat = 2; + } else { + m = 0; + v2 ^= 0xff; + repeat = 4; + } + for (int i = 0; i < repeat; i++) { + v0 += v1; + v2 += v3; + v1 = Long.rotateLeft(v1, 13); + v3 = Long.rotateLeft(v3, 16); + v1 ^= v0; + v3 ^= v2; + v0 = Long.rotateLeft(v0, 32); + v2 += v1; + v0 += v3; + v1 = Long.rotateLeft(v1, 17); + v3 = Long.rotateLeft(v3, 21); + v1 ^= v2; + v3 ^= v0; + v2 = Long.rotateLeft(v2, 32); + } + v0 ^= m; + } + return (int) (v0 ^ v1 ^ v2 ^ v3); + } + + } + +} diff --git a/h2/src/tools/org/h2/dev/hash/PerfectHash.java b/h2/src/tools/org/h2/dev/hash/PerfectHash.java new file mode 100644 index 0000000..185c942 --- /dev/null +++ b/h2/src/tools/org/h2/dev/hash/PerfectHash.java @@ -0,0 +1,258 @@ +/* + * 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.dev.hash; + +import java.io.ByteArrayOutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +/** + * A perfect hash function tool. It needs about 1.4 bits per key, and the + * resulting hash table is about 79% full. The minimal perfect hash function + * needs about 2.3 bits per key. + *

    + * Generating the hash function takes about 1 second per million keys + * for both perfect hash and minimal perfect hash. + *

    + * The algorithm is recursive: sets that contain no or only one entry are not + * processed as no conflicts are possible. Sets that contain between 2 and 16 + * entries, up to 16 hash functions are tested to check if they can store the + * data without conflict. If no function was found, the same is tested on a + * larger bucket (except for the minimal perfect hash). If no hash function was + * found, and for larger buckets, the bucket is split into a number of smaller + * buckets (up to 32). + *

    + * At the end of the generation process, the data is compressed using a general + * purpose compression tool (Deflate / Huffman coding). The uncompressed data is + * around 1.52 bits per key (perfect hash) and 3.72 (minimal perfect hash). + *

    + * Please also note the MinimalPerfectHash class, which uses less space per key. + */ +public class PerfectHash { + + /** + * The maximum size of a bucket. + */ + private static final int MAX_SIZE = 16; + + /** + * The maximum number of hash functions to test. + */ + private static final int OFFSETS = 16; + + /** + * The maximum number of buckets to split the set into. + */ + private static final int MAX_SPLIT = 32; + + /** + * The description of the hash function. Used for calculating the hash of a + * key. + */ + private final byte[] data; + + /** + * The offset of the result of the hash function at the given offset within + * the data array. Used for calculating the hash of a key. + */ + private final int[] plus; + + /** + * The position of the next bucket in the data array (in case this bucket + * needs to be skipped). Used for calculating the hash of a key. + */ + private final int[] next; + + /** + * Create a hash object to convert keys to hashes. + * + * @param data the data returned by the generate method + */ + public PerfectHash(byte[] data) { + this.data = data = expand(data); + plus = new int[data.length]; + next = new int[data.length]; + for (int i = 0, p = 0; i < data.length; i++) { + plus[i] = p; + int n = data[i] & 255; + p += n < 2 ? n : n >= MAX_SPLIT ? (n / OFFSETS) : 0; + } + } + + /** + * Calculate the hash from the key. + * + * @param x the key + * @return the hash + */ + public int get(int x) { + return get(0, x, 0); + } + + private int get(int pos, int x, int level) { + int n = data[pos] & 255; + if (n < 2) { + return plus[pos]; + } else if (n >= MAX_SPLIT) { + return plus[pos] + hash(x, level, n % OFFSETS, n / OFFSETS); + } + pos++; + int h = hash(x, level, 0, n); + for (int i = 0; i < h; i++) { + pos = read(pos); + } + return get(pos, x, level + 1); + } + + private int read(int pos) { + int p = next[pos]; + if (p == 0) { + int n = data[pos] & 255; + if (n < 2 || n >= MAX_SPLIT) { + return pos + 1; + } + int start = pos++; + for (int i = 0; i < n; i++) { + pos = read(pos); + } + next[start] = p = pos; + } + return p; + } + + /** + * Generate the perfect hash function data from the given set of integers. + * + * @param list the set + * @param minimal whether the perfect hash function needs to be minimal + * @return the data + */ + public static byte[] generate(Set list, boolean minimal) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + generate(list, 0, minimal, out); + return compress(out.toByteArray()); + } + + private static void generate(Collection set, int level, + boolean minimal, ByteArrayOutputStream out) { + int size = set.size(); + if (size <= 1) { + out.write(size); + return; + } + if (size < MAX_SIZE) { + int max = minimal ? size : Math.min(MAX_SIZE - 1, size * 2); + for (int s = size; s <= max; s++) { + // Try a few hash functions ("offset" is basically the hash + // function index). We could try less hash functions, and + // instead use a larger size and remember the position of the + // hole (specially for the minimal perfect case), but that's + // more complicated. + nextOffset: + for (int offset = 0; offset < OFFSETS; offset++) { + int bits = 0; + for (int x : set) { + int h = hash(x, level, offset, s); + if ((bits & (1 << h)) != 0) { + continue nextOffset; + } + bits |= 1 << h; + } + out.write(s * OFFSETS + offset); + return; + } + } + } + // Split the set into multiple smaller sets. We could try to split more + // evenly by trying out multiple hash functions, but that's more + // complicated. + int split; + if (minimal) { + split = size > 150 ? size / 83 : (size + 3) / 4; + } else { + split = size > 265 ? size / 142 : (size + 5) / 7; + } + split = Math.min(MAX_SPLIT - 1, Math.max(2, split)); + out.write(split); + List> lists = new ArrayList<>(split); + for (int i = 0; i < split; i++) { + lists.add(new ArrayList(size / split)); + } + for (int x : set) { + lists.get(hash(x, level, 0, split)).add(x); + } + for (List s2 : lists) { + generate(s2, level + 1, minimal, out); + } + } + + /** + * Calculate the hash of a key. The result depends on the key, the recursion + * level, and the offset. + * + * @param x the key + * @param level the recursion level + * @param offset the index of the hash function + * @param size the size of the bucket + * @return the hash (a value between 0, including, and the size, excluding) + */ + private static int hash(int x, int level, int offset, int size) { + x += level * OFFSETS + offset; + x = ((x >>> 16) ^ x) * 0x45d9f3b; + x = ((x >>> 16) ^ x) * 0x45d9f3b; + x = (x >>> 16) ^ x; + return Math.abs(x % size); + } + + /** + * Compress the hash description using a Huffman coding. + * + * @param d the data + * @return the compressed data + */ + private static byte[] compress(byte[] d) { + Deflater deflater = new Deflater(); + deflater.setStrategy(Deflater.HUFFMAN_ONLY); + deflater.setInput(d); + deflater.finish(); + ByteArrayOutputStream out2 = new ByteArrayOutputStream(d.length); + byte[] buffer = new byte[1024]; + while (!deflater.finished()) { + int count = deflater.deflate(buffer); + out2.write(buffer, 0, count); + } + deflater.end(); + return out2.toByteArray(); + } + + /** + * Decompress the hash description using a Huffman coding. + * + * @param d the data + * @return the decompressed data + */ + private static byte[] expand(byte[] d) { + Inflater inflater = new Inflater(); + inflater.setInput(d); + ByteArrayOutputStream out = new ByteArrayOutputStream(d.length); + byte[] buffer = new byte[1024]; + try { + while (!inflater.finished()) { + int count = inflater.inflate(buffer); + out.write(buffer, 0, count); + } + inflater.end(); + } catch (Exception e) { + throw new IllegalArgumentException(e); + } + return out.toByteArray(); + } + +} diff --git a/h2/src/tools/org/h2/dev/hash/package.html b/h2/src/tools/org/h2/dev/hash/package.html new file mode 100644 index 0000000..f8d85f7 --- /dev/null +++ b/h2/src/tools/org/h2/dev/hash/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +A perfect hash function tool. + +

    \ No newline at end of file diff --git a/h2/src/tools/org/h2/dev/mail/SendMail.java.txt b/h2/src/tools/org/h2/dev/mail/SendMail.java.txt new file mode 100644 index 0000000..2601895 --- /dev/null +++ b/h2/src/tools/org/h2/dev/mail/SendMail.java.txt @@ -0,0 +1,45 @@ +/* + * 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.dev.mail; +import java.util.Properties; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.Message.RecipientType; +import javax.mail.internet.MimeMessage; + +/** + * Utility class to send a mail over a fixed gmail account. + */ +public class SendMail { + // http://repo2.maven.org/maven2/javax/mail/mail/1.4.1/mail-1.4.1.jar + // http://repo2.maven.org/maven2/javax/activation/activation/1.1/activation-1.1.jar + + public static void main(String[] args) throws Exception { + String to = "thomas.tom.mueller" + "@" + "gmail.com"; + sendMailOverGmail("", to, "Test", "Test Mail"); + } + + static void sendMailOverGmail(String password, String to, String subject, String body) throws Exception { + String username = "testing1212123" + "@" + "gmail.com"; + String host = "smtp.gmail.com"; + Properties prop = new Properties(); + prop.put("mail.smtps.auth", "true"); + Session session = Session.getDefaultInstance(prop); + session.setProtocolForAddress("rfc822", "smtps"); + session.setDebug(true); + MimeMessage msg = new MimeMessage(session); + msg.setRecipients(RecipientType.TO, to); + msg.setSubject(subject); + msg.setText(body); + Transport t = session.getTransport("smtps"); + try { + t.connect(host, username, password); + t.sendMessage(msg, msg.getAllRecipients()); + } finally { + t.close(); + } + } +} diff --git a/h2/src/tools/org/h2/dev/net/PgTcpRedirect.java b/h2/src/tools/org/h2/dev/net/PgTcpRedirect.java new file mode 100644 index 0000000..71ce3f9 --- /dev/null +++ b/h2/src/tools/org/h2/dev/net/PgTcpRedirect.java @@ -0,0 +1,559 @@ +/* + * 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.dev.net; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.net.Socket; + +/** + * This class helps debug the PostgreSQL network protocol. + * It listens on one port, and sends the exact same data to another port. + */ +public class PgTcpRedirect { + + private static final boolean DEBUG = false; + + /** + * This method is called when executing this application from the command + * line. + * + * @param args the command line parameters + */ + public static void main(String... args) throws Exception { + loop(args); + } + + private static void loop(String... args) throws Exception { + // MySQL protocol: + // http://www.redferni.uklinux.net/mysql/MySQL-Protocol.html + // PostgreSQL protocol: + // https://www.postgresql.org/docs/devel/protocol.html + // int portServer = 9083, portClient = 9084; + // int portServer = 3306, portClient = 3307; + // H2 PgServer + // int portServer = 5435, portClient = 5433; + // PostgreSQL + int portServer = 5432, portClient = 5433; + + for (int i = 0; i < args.length; i++) { + if ("-client".equals(args[i])) { + portClient = Integer.parseInt(args[++i]); + } else if ("-server".equals(args[i])) { + portServer = Integer.parseInt(args[++i]); + } + } + ServerSocket listener = new ServerSocket(portClient); + while (true) { + Socket client = listener.accept(); + Socket server = new Socket("localhost", portServer); + TcpRedirectThread c = new TcpRedirectThread(client, server, true); + TcpRedirectThread s = new TcpRedirectThread(server, client, false); + new Thread(c).start(); + new Thread(s).start(); + } + } + + /** + * This is the working thread of the TCP redirector. + */ + private static class TcpRedirectThread implements Runnable { + + private static final int STATE_INIT_CLIENT = 0, STATE_REGULAR = 1; + private final Socket read, write; + private int state; + private final boolean client; + + TcpRedirectThread(Socket read, Socket write, boolean client) { + this.read = read; + this.write = write; + this.client = client; + state = client ? STATE_INIT_CLIENT : STATE_REGULAR; + } + + String readStringNull(InputStream in) throws IOException { + StringBuilder buff = new StringBuilder(); + while (true) { + int x = in.read(); + if (x <= 0) { + break; + } + buff.append((char) x); + } + return buff.toString(); + } + + private static void println(String s) { + if (DEBUG) { + System.out.println(s); + } + } + + private boolean processClient(InputStream inStream, + OutputStream outStream) throws IOException { + DataInputStream dataIn = new DataInputStream(inStream); + ByteArrayOutputStream buff = new ByteArrayOutputStream(); + DataOutputStream dataOut = new DataOutputStream(buff); + if (state == STATE_INIT_CLIENT) { + state = STATE_REGULAR; + int len = dataIn.readInt(); + dataOut.writeInt(len); + len -= 4; + byte[] data = new byte[len]; + dataIn.readFully(data, 0, len); + dataOut.write(data); + dataIn = new DataInputStream(new ByteArrayInputStream(data, 0, len)); + int version = dataIn.readInt(); + if (version == 80877102) { + println("CancelRequest"); + println(" pid: " + dataIn.readInt()); + println(" key: " + dataIn.readInt()); + } else if (version == 80877103) { + println("SSLRequest"); + } else { + println("StartupMessage"); + println(" version " + version + " (" + (version >> 16) + + "." + (version & 0xff) + ")"); + while (true) { + String param = readStringNull(dataIn); + if (param.length() == 0) { + break; + } + String value = readStringNull(dataIn); + println(" param " + param + "=" + value); + } + } + } else { + int x = dataIn.read(); + if (x < 0) { + println("end"); + return false; + } + // System.out.println(" x=" + (char)x+" " +x); + dataOut.write(x); + int len = dataIn.readInt(); + dataOut.writeInt(len); + len -= 4; + byte[] data = new byte[len]; + dataIn.readFully(data, 0, len); + dataOut.write(data); + dataIn = new DataInputStream(new ByteArrayInputStream(data, 0, len)); + switch (x) { + case 'B': { + println("Bind"); + println(" destPortal: " + readStringNull(dataIn)); + println(" prepName: " + readStringNull(dataIn)); + int formatCodesCount = dataIn.readShort(); + for (int i = 0; i < formatCodesCount; i++) { + println(" formatCode[" + i + "]=" + dataIn.readShort()); + } + int paramCount = dataIn.readShort(); + for (int i = 0; i < paramCount; i++) { + int paramLen = dataIn.readInt(); + println(" length[" + i + "]=" + paramLen); + byte[] d2 = new byte[paramLen]; + dataIn.readFully(d2); + } + int resultCodeCount = dataIn.readShort(); + for (int i = 0; i < resultCodeCount; i++) { + println(" resultCodeCount[" + i + "]=" + dataIn.readShort()); + } + break; + } + case 'C': { + println("Close"); + println(" type: (S:prepared statement, P:portal): " + dataIn.read()); + break; + } + case 'd': { + println("CopyData"); + break; + } + case 'c': { + println("CopyDone"); + break; + } + case 'f': { + println("CopyFail"); + println(" message: " + readStringNull(dataIn)); + break; + } + case 'D': { + println("Describe"); + println(" type (S=prepared statement, P=portal): " + (char) dataIn.readByte()); + println(" name: " + readStringNull(dataIn)); + break; + } + case 'E': { + println("Execute"); + println(" name: " + readStringNull(dataIn)); + println(" maxRows: " + dataIn.readShort()); + break; + } + case 'H': { + println("Flush"); + break; + } + case 'F': { + println("FunctionCall"); + println(" objectId:" + dataIn.readInt()); + int columns = dataIn.readShort(); + for (int i = 0; i < columns; i++) { + println(" formatCode[" + i + "]: " + dataIn.readShort()); + } + int count = dataIn.readShort(); + for (int i = 0; i < count; i++) { + int l = dataIn.readInt(); + println(" len[" + i + "]: " + l); + if (l >= 0) { + for (int j = 0; j < l; j++) { + dataIn.readByte(); + } + } + } + println(" resultFormat: " + dataIn.readShort()); + break; + } + case 'P': { + println("Parse"); + println(" name:" + readStringNull(dataIn)); + println(" query:" + readStringNull(dataIn)); + int count = dataIn.readShort(); + for (int i = 0; i < count; i++) { + println(" [" + i + "]: " + dataIn.readInt()); + } + break; + } + case 'p': { + println("PasswordMessage"); + println(" password: " + readStringNull(dataIn)); + break; + } + case 'Q': { + println("Query"); + println(" sql : " + readStringNull(dataIn)); + break; + } + case 'S': { + println("Sync"); + break; + } + case 'X': { + println("Terminate"); + break; + } + default: + println("############## UNSUPPORTED: " + (char) x); + } + } + dataOut.flush(); + byte[] buffer = buff.toByteArray(); + printData(buffer, buffer.length); + try { + outStream.write(buffer, 0, buffer.length); + outStream.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + return true; + } + + private boolean processServer(InputStream inStream, + OutputStream outStream) throws IOException { + DataInputStream dataIn = new DataInputStream(inStream); + ByteArrayOutputStream buff = new ByteArrayOutputStream(); + DataOutputStream dataOut = new DataOutputStream(buff); + int x = dataIn.read(); + if (x < 0) { + println("end"); + return false; + } + // System.out.println(" x=" + (char)x+" " +x); + dataOut.write(x); + int len = dataIn.readInt(); + dataOut.writeInt(len); + len -= 4; + byte[] data = new byte[len]; + dataIn.readFully(data, 0, len); + dataOut.write(data); + dataIn = new DataInputStream(new ByteArrayInputStream(data, 0, len)); + switch (x) { + case 'R': { + println("Authentication"); + int value = dataIn.readInt(); + if (value == 0) { + println(" Ok"); + } else if (value == 2) { + println(" KerberosV5"); + } else if (value == 3) { + println(" CleartextPassword"); + } else if (value == 4) { + println(" CryptPassword"); + byte b1 = dataIn.readByte(); + byte b2 = dataIn.readByte(); + println(" salt1=" + b1 + " salt2=" + b2); + } else if (value == 5) { + println(" MD5Password"); + byte b1 = dataIn.readByte(); + byte b2 = dataIn.readByte(); + byte b3 = dataIn.readByte(); + byte b4 = dataIn.readByte(); + println(" salt1=" + b1 + " salt2=" + b2 + " 3=" + b3 + " 4=" + b4); + } else if (value == 6) { + println(" SCMCredential"); + } + break; + } + case 'K': { + println("BackendKeyData"); + println(" process ID " + dataIn.readInt()); + println(" key " + dataIn.readInt()); + break; + } + case '2': { + println("BindComplete"); + break; + } + case '3': { + println("CloseComplete"); + break; + } + case 'C': { + println("CommandComplete"); + println(" command tag: " + readStringNull(dataIn)); + break; + } + case 'd': { + println("CopyData"); + break; + } + case 'c': { + println("CopyDone"); + break; + } + case 'G': { + println("CopyInResponse"); + println(" format: " + dataIn.readByte()); + int columns = dataIn.readShort(); + for (int i = 0; i < columns; i++) { + println(" formatCode[" + i + "]: " + dataIn.readShort()); + } + break; + } + case 'H': { + println("CopyOutResponse"); + println(" format: " + dataIn.readByte()); + int columns = dataIn.readShort(); + for (int i = 0; i < columns; i++) { + println(" formatCode[" + i + "]: " + dataIn.readShort()); + } + break; + } + case 'D': { + println("DataRow"); + int columns = dataIn.readShort(); + println(" columns : " + columns); + for (int i = 0; i < columns; i++) { + int l = dataIn.readInt(); + if (l > 0) { + for (int j = 0; j < l; j++) { + dataIn.readByte(); + } + } + // println(" ["+i+"] len: " + l); + } + break; + } + case 'I': { + println("EmptyQueryResponse"); + break; + } + case 'E': { + println("ErrorResponse"); + while (true) { + int fieldType = dataIn.readByte(); + if (fieldType == 0) { + break; + } + String msg = readStringNull(dataIn); + // https://www.postgresql.org/docs/devel/protocol-error-fields.html + // S Severity + // C Code: the SQLSTATE code + // M Message + // D Detail + // H Hint + // P Position + // p Internal position + // q Internal query + // W Where + // F File + // L Line + // R Routine + println(" fieldType: " + fieldType + " msg: " + msg); + } + break; + } + case 'V': { + println("FunctionCallResponse"); + int resultLen = dataIn.readInt(); + println(" len: " + resultLen); + break; + } + case 'n': { + println("NoData"); + break; + } + case 'N': { + println("NoticeResponse"); + while (true) { + int fieldType = dataIn.readByte(); + if (fieldType == 0) { + break; + } + String msg = readStringNull(dataIn); + // https://www.postgresql.org/docs/devel/protocol-error-fields.html + // S Severity + // C Code: the SQLSTATE code + // M Message + // D Detail + // H Hint + // P Position + // p Internal position + // q Internal query + // W Where + // F File + // L Line + // R Routine + println(" fieldType: " + fieldType + " msg: " + msg); + } + break; + } + case 'A': { + println("NotificationResponse"); + println(" processID: " + dataIn.readInt()); + println(" condition: " + readStringNull(dataIn)); + println(" information: " + readStringNull(dataIn)); + break; + } + case 't': { + println("ParameterDescription"); + println(" processID: " + dataIn.readInt()); + int count = dataIn.readShort(); + for (int i = 0; i < count; i++) { + println(" [" + i + "] objectId: " + dataIn.readInt()); + } + break; + } + case 'S': { + println("ParameterStatus"); + println(" parameter " + readStringNull(dataIn) + " = " + + readStringNull(dataIn)); + break; + } + case '1': { + println("ParseComplete"); + break; + } + case 's': { + println("ParseComplete"); + break; + } + case 'Z': { + println("ReadyForQuery"); + println(" status (I:idle, T:transaction, E:failed): " + + (char) dataIn.readByte()); + break; + } + case 'T': { + println("RowDescription"); + int columns = dataIn.readShort(); + println(" columns : " + columns); + for (int i = 0; i < columns; i++) { + println(" [" + i + "]"); + println(" name:" + readStringNull(dataIn)); + println(" tableId:" + dataIn.readInt()); + println(" columnId:" + dataIn.readShort()); + println(" dataTypeId:" + dataIn.readInt()); + println(" dataTypeSize (pg_type.typlen):" + dataIn.readShort()); + println(" modifier (pg_attribute.atttypmod):" + dataIn.readInt()); + println(" format code:" + dataIn.readShort()); + } + break; + } + default: + println("############## UNSUPPORTED: " + (char) x); + } + dataOut.flush(); + byte[] buffer = buff.toByteArray(); + printData(buffer, buffer.length); + try { + outStream.write(buffer, 0, buffer.length); + outStream.flush(); + } catch (IOException e) { + e.printStackTrace(); + } + return true; + } + + @Override + public void run() { + try { + OutputStream out = write.getOutputStream(); + InputStream in = read.getInputStream(); + while (true) { + boolean more; + if (client) { + more = processClient(in, out); + } else { + more = processServer(in, out); + } + if (!more) { + break; + } + } + try { + read.close(); + } catch (IOException e) { + // ignore + } + try { + write.close(); + } catch (IOException e) { + // ignore + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + + /** + * Print the uninterpreted byte array. + * + * @param buffer the byte array + * @param len the length + */ + static synchronized void printData(byte[] buffer, int len) { + if (DEBUG) { + System.out.print(" "); + for (int i = 0; i < len; i++) { + int c = buffer[i] & 255; + if (c >= ' ' && c <= 127 && c != '[' & c != ']') { + System.out.print((char) c); + } else { + System.out.print("[" + Integer.toHexString(c) + "]"); + } + } + System.out.println(); + } + } +} diff --git a/h2/src/tools/org/h2/dev/net/package.html b/h2/src/tools/org/h2/dev/net/package.html new file mode 100644 index 0000000..4900db5 --- /dev/null +++ b/h2/src/tools/org/h2/dev/net/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +A tool to redirect and interpret PostgreSQL network protocol packets. + +

    \ No newline at end of file diff --git a/h2/src/tools/org/h2/dev/security/SecureKeyStoreBuilder.java b/h2/src/tools/org/h2/dev/security/SecureKeyStoreBuilder.java new file mode 100644 index 0000000..7deed48 --- /dev/null +++ b/h2/src/tools/org/h2/dev/security/SecureKeyStoreBuilder.java @@ -0,0 +1,106 @@ +/* + * 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.dev.security; + +import java.security.Key; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.util.Enumeration; + +import org.h2.security.CipherFactory; +import org.h2.util.StringUtils; + +/** + * Tool to generate source code for the SecureSocketFactory. First, create a + * keystore using: + *
    + * keytool -genkey -alias h2 -keyalg RSA -dname "cn=H2"
    + *     -validity 25000 -keypass h2pass -keystore h2.keystore
    + *     -storepass h2pass
    + * 
    + * Then run this application to generate the source code. Then replace the code + * in the function SecureSocketFactory.getKeyStore as specified + */ +public class SecureKeyStoreBuilder { + + private SecureKeyStoreBuilder() { + // utility class + } + + /** + * This method is called when executing this application from the command + * line. + * + * @param args the command line parameters + */ + public static void main(String... args) throws Exception { + String password = CipherFactory.KEYSTORE_PASSWORD; + KeyStore store = CipherFactory.getKeyStore(password); + printKeystore(store, password); + } + + private static void printKeystore(KeyStore store, String password) + throws KeyStoreException, NoSuchAlgorithmException, + UnrecoverableKeyException, CertificateEncodingException { + System.out.println("KeyStore store = KeyStore.getInstance(\"" + + store.getType() + "\");"); + System.out.println("store.load(null, password.toCharArray());"); + // System.out.println("keystore provider=" + + // store.getProvider().getName()); + Enumeration en = store.aliases(); + while (en.hasMoreElements()) { + String alias = en.nextElement(); + Key key = store.getKey(alias, password.toCharArray()); + System.out.println( + "KeyFactory keyFactory = KeyFactory.getInstance(\"" + + key.getAlgorithm() + "\");"); + System.out.println("store.load(null, password.toCharArray());"); + String pkFormat = key.getFormat(); + String encoded = StringUtils.convertBytesToHex(key.getEncoded()); + System.out.println( + pkFormat + "EncodedKeySpec keySpec = new " + + pkFormat + "EncodedKeySpec(getBytes(\"" + + encoded + "\"));"); + System.out.println( + "PrivateKey privateKey = keyFactory.generatePrivate(keySpec);"); + System.out.println("Certificate[] certs = {"); + for (Certificate cert : store.getCertificateChain(alias)) { + System.out.println( + " CertificateFactory.getInstance(\""+cert.getType()+"\")."); + String enc = StringUtils.convertBytesToHex(cert.getEncoded()); + System.out.println( + " generateCertificate(new ByteArrayInputStream(getBytes(\"" + + enc + "\"))),"); + // PublicKey pubKey = cert.getPublicKey(); + // System.out.println(" pubKey algorithm=" + + // pubKey.getAlgorithm()); + // System.out.println(" pubKey format=" + + // pubKey.getFormat()); + // System.out.println(" pubKey format="+ + // Utils.convertBytesToString(pubKey.getEncoded())); + } + System.out.println("};"); + System.out.println("store.setKeyEntry(\"" + alias + + "\", privateKey, password.toCharArray(), certs);"); + } + } + +// private void listCipherSuites(SSLServerSocketFactory f) { +// String[] def = f.getDefaultCipherSuites(); +// for (int i = 0; i < def.length; i++) { +// System.out.println("default = " + def[i]); +// } +// String[] sup = f.getSupportedCipherSuites(); +// for (int i = 0; i < sup.length; i++) { +// System.out.println("supported = " + sup[i]); +// } +// } + +} diff --git a/h2/src/tools/org/h2/dev/security/package.html b/h2/src/tools/org/h2/dev/security/package.html new file mode 100644 index 0000000..cb45245 --- /dev/null +++ b/h2/src/tools/org/h2/dev/security/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +Security tools. + +

    \ No newline at end of file diff --git a/h2/src/tools/org/h2/dev/sort/InPlaceStableMergeSort.java b/h2/src/tools/org/h2/dev/sort/InPlaceStableMergeSort.java new file mode 100644 index 0000000..a442391 --- /dev/null +++ b/h2/src/tools/org/h2/dev/sort/InPlaceStableMergeSort.java @@ -0,0 +1,322 @@ +/* + * 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.dev.sort; + +import java.util.Comparator; + +/** + * A stable merge sort implementation that uses at most O(log(n)) memory + * and O(n*log(n)*log(n)) time. + * + * @param the element type + */ +public class InPlaceStableMergeSort { + + /** + * The minimum size of the temporary array. It is used to speed up sorting + * small blocks. + */ + private static final int TEMP_SIZE = 1024; + + /** + * Blocks smaller than this number are sorted using binary insertion sort. + * This usually speeds up sorting. + */ + private static final int INSERTION_SORT_SIZE = 16; + + /** + * The data array to sort. + */ + private T[] data; + + /** + * The comparator. + */ + private Comparator comp; + + /** + * The temporary array. + */ + private T[] temp; + + /** + * Sort an array using the given comparator. + * + * @param data the data array to sort + * @param comp the comparator + */ + public static void sort(T[] data, Comparator comp) { + new InPlaceStableMergeSort().sortArray(data, comp); + } + + /** + * Sort an array using the given comparator. + * + * @param d the data array to sort + * @param c the comparator + */ + public void sortArray(T[] d, Comparator c) { + this.data = d; + this.comp = c; + int len = Math.max((int) (100 * Math.log(d.length)), TEMP_SIZE); + len = Math.min(d.length, len); + @SuppressWarnings("unchecked") + T[] t = (T[]) new Object[len]; + this.temp = t; + mergeSort(0, d.length - 1); + } + + /** + * Sort a block recursively using merge sort. + * + * @param from the index of the first entry to sort + * @param to the index of the last entry to sort + */ + void mergeSort(int from, int to) { + if (to - from < INSERTION_SORT_SIZE) { + binaryInsertionSort(from, to); + return; + } + int m = (from + to) >>> 1; + mergeSort(from, m); + mergeSort(m + 1, to); + merge(from, m + 1, to); + } + + /** + * Sort a block using the binary insertion sort algorithm. + * + * @param from the index of the first entry to sort + * @param to the index of the last entry to sort + */ + private void binaryInsertionSort(int from, int to) { + for (int i = from + 1; i <= to; i++) { + T x = data[i]; + int ins = binarySearch(x, from, i - 1); + for (int j = i - 1; j >= ins; j--) { + data[j + 1] = data[j]; + } + data[ins] = x; + } + } + + /** + * Find the index of the element that is larger than x. + * + * @param x the element to search + * @param from the index of the first entry + * @param to the index of the last entry + * @return the position + */ + private int binarySearch(T x, int from, int to) { + while (from <= to) { + int m = (from + to) >>> 1; + if (comp.compare(x, data[m]) >= 0) { + from = m + 1; + } else { + to = m - 1; + } + } + return from; + } + + /** + * Merge two arrays. + * + * @param from the start of the first range + * @param second start of the second range + * @param to the last element of the second range + */ + private void merge(int from, int second, int to) { + int len1 = second - from, len2 = to - second + 1; + if (len1 == 0 || len2 == 0) { + return; + } + if (len1 + len2 == 2) { + if (comp.compare(data[second], data[from]) < 0) { + swap(data, second, from); + } + return; + } + if (len1 <= temp.length) { + System.arraycopy(data, from, temp, 0, len1); + mergeSmall(data, from, temp, 0, len1 - 1, data, second, to); + return; + } else if (len2 <= temp.length) { + System.arraycopy(data, second, temp, 0, len2); + System.arraycopy(data, from, data, to - len1 + 1, len1); + mergeSmall(data, from, data, to - len1 + 1, to, temp, 0, len2 - 1); + return; + } + mergeBig(from, second, to); + } + + /** + * Merge two (large) arrays. This is done recursively by merging the + * beginning of both arrays, and then the end of both arrays. + * + * @param from the start of the first range + * @param second start of the second range + * @param to the last element of the second range + */ + private void mergeBig(int from, int second, int to) { + int len1 = second - from, len2 = to - second + 1; + int firstCut, secondCut, newSecond; + if (len1 > len2) { + firstCut = from + len1 / 2; + secondCut = findLower(data[firstCut], second, to); + int len = secondCut - second; + newSecond = firstCut + len; + } else { + int len = len2 / 2; + secondCut = second + len; + firstCut = findUpper(data[secondCut], from, second - 1); + newSecond = firstCut + len; + } + swapBlocks(firstCut, second, secondCut - 1); + merge(from, firstCut, newSecond - 1); + merge(newSecond, secondCut, to); + } + + /** + * Merge two (small) arrays using the temporary array. This is done to speed + * up merging. + * + * @param target the target array + * @param pos the position of the first element in the target array + * @param s1 the first source array + * @param from1 the index of the first element in the first source array + * @param to1 the index of the last element in the first source array + * @param s2 the second source array + * @param from2 the index of the first element in the second source array + * @param to2 the index of the last element in the second source array + */ + private void mergeSmall(T[] target, int pos, T[] s1, int from1, int to1, + T[] s2, int from2, int to2) { + T x1 = s1[from1], x2 = s2[from2]; + while (true) { + if (comp.compare(x1, x2) <= 0) { + target[pos++] = x1; + if (++from1 > to1) { + System.arraycopy(s2, from2, target, pos, to2 - from2 + 1); + break; + } + x1 = s1[from1]; + } else { + target[pos++] = x2; + if (++from2 > to2) { + System.arraycopy(s1, from1, target, pos, to1 - from1 + 1); + break; + } + x2 = s2[from2]; + } + } + } + + /** + * Find the largest element in the sorted array that is smaller than x. + * + * @param x the element to search + * @param from the index of the first entry + * @param to the index of the last entry + * @return the index of the resulting element + */ + private int findLower(T x, int from, int to) { + int len = to - from + 1, half; + while (len > 0) { + half = len / 2; + int m = from + half; + if (comp.compare(data[m], x) < 0) { + from = m + 1; + len = len - half - 1; + } else { + len = half; + } + } + return from; + } + + /** + * Find the smallest element in the sorted array that is larger than or + * equal to x. + * + * @param x the element to search + * @param from the index of the first entry + * @param to the index of the last entry + * @return the index of the resulting element + */ + private int findUpper(T x, int from, int to) { + int len = to - from + 1, half; + while (len > 0) { + half = len / 2; + int m = from + half; + if (comp.compare(data[m], x) <= 0) { + from = m + 1; + len = len - half - 1; + } else { + len = half; + } + } + return from; + } + + /** + * Swap the elements of two blocks in the data array. Both blocks are next + * to each other (the second block starts just after the first block ends). + * + * @param from the index of the first element in the first block + * @param second the index of the first element in the second block + * @param to the index of the last element in the second block + */ + private void swapBlocks(int from, int second, int to) { + int len1 = second - from, len2 = to - second + 1; + if (len1 == 0 || len2 == 0) { + return; + } + if (len1 < temp.length) { + System.arraycopy(data, from, temp, 0, len1); + System.arraycopy(data, second, data, from, len2); + System.arraycopy(temp, 0, data, from + len2, len1); + return; + } else if (len2 < temp.length) { + System.arraycopy(data, second, temp, 0, len2); + System.arraycopy(data, from, data, from + len2, len1); + System.arraycopy(temp, 0, data, from, len2); + return; + } + reverseBlock(from, second - 1); + reverseBlock(second, to); + reverseBlock(from, to); + } + + /** + * Reverse all elements in a block. + * + * @param from the index of the first element + * @param to the index of the last element + */ + private void reverseBlock(int from, int to) { + while (from < to) { + T old = data[from]; + data[from++] = data[to]; + data[to--] = old; + } + } + + /** + * Swap two elements in the array. + * + * @param d the array + * @param a the index of the first element + * @param b the index of the second element + */ + private void swap(T[] d, int a, int b) { + T t = d[a]; + d[a] = d[b]; + d[b] = t; + } + +} \ No newline at end of file diff --git a/h2/src/tools/org/h2/dev/sort/InPlaceStableQuicksort.java b/h2/src/tools/org/h2/dev/sort/InPlaceStableQuicksort.java new file mode 100644 index 0000000..dd0632e --- /dev/null +++ b/h2/src/tools/org/h2/dev/sort/InPlaceStableQuicksort.java @@ -0,0 +1,310 @@ +/* + * 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.dev.sort; + +import java.util.Comparator; + +/** + * A stable quicksort implementation that uses O(log(n)) memory. It normally + * runs in O(n*log(n)*log(n)), but at most in O(n^2). + * + * @param the element type + */ +public class InPlaceStableQuicksort { + + /** + * The minimum size of the temporary array. It is used to speed up sorting + * small blocks. + */ + private static final int TEMP_SIZE = 1024; + + /** + * Blocks smaller than this number are sorted using binary insertion sort. + * This usually speeds up sorting. + */ + private static final int INSERTION_SORT_SIZE = 16; + + /** + * The data array to sort. + */ + private T[] data; + + /** + * The comparator. + */ + private Comparator comp; + + /** + * The temporary array. + */ + private T[] temp; + + /** + * Sort an array using the given comparator. + * + * @param data the data array to sort + * @param comp the comparator + */ + public static void sort(T[] data, Comparator comp) { + new InPlaceStableQuicksort().sortArray(data, comp); + } + + /** + * Sort an array using the given comparator. + * + * @param d the data array to sort + * @param c the comparator + */ + public void sortArray(T[] d, Comparator c) { + this.data = d; + this.comp = c; + int len = Math.max((int) (100 * Math.log(d.length)), TEMP_SIZE); + len = Math.min(d.length, len); + @SuppressWarnings("unchecked") + T[] t = (T[]) new Object[len]; + this.temp = t; + quicksort(0, d.length - 1); + } + + /** + * Sort a block using the quicksort algorithm. + * + * @param from the index of the first entry to sort + * @param to the index of the last entry to sort + */ + private void quicksort(int from, int to) { + while (to > from) { + if (to - from < INSERTION_SORT_SIZE) { + binaryInsertionSort(from, to); + return; + } + T pivot = selectPivot(from, to); + int second = partition(pivot, from, to); + if (second > to) { + pivot = selectPivot(from, to); + pivot = data[to]; + second = partition(pivot, from, to); + if (second > to) { + second--; + } + } + quicksort(from, second - 1); + from = second; + } + } + + /** + * Sort a block using the binary insertion sort algorithm. + * + * @param from the index of the first entry to sort + * @param to the index of the last entry to sort + */ + private void binaryInsertionSort(int from, int to) { + for (int i = from + 1; i <= to; i++) { + T x = data[i]; + int ins = binarySearch(x, from, i - 1); + for (int j = i - 1; j >= ins; j--) { + data[j + 1] = data[j]; + } + data[ins] = x; + } + } + + /** + * Find the index of the element that is larger than x. + * + * @param x the element to search + * @param from the index of the first entry + * @param to the index of the last entry + * @return the position + */ + private int binarySearch(T x, int from, int to) { + while (from <= to) { + int m = (from + to) >>> 1; + if (comp.compare(x, data[m]) >= 0) { + from = m + 1; + } else { + to = m - 1; + } + } + return from; + } + + /** + * Move all elements that are bigger than the pivot to the end of the list, + * and return the partitioning index. The partitioning index is the start + * index of the range where all elements are larger than the pivot. If the + * partitioning index is larger than the 'to' index, then all elements are + * smaller or equal to the pivot. + * + * @param pivot the pivot + * @param from the index of the first element + * @param to the index of the last element + * @return the first element of the second partition + */ + private int partition(T pivot, int from, int to) { + if (to - from < temp.length) { + return partitionSmall(pivot, from, to); + } + int m = (from + to + 1) / 2; + int m1 = partition(pivot, from, m - 1); + int m2 = partition(pivot, m, to); + swapBlocks(m1, m, m2 - 1); + return m1 + m2 - m; + } + + /** + * Partition a small block using the temporary array. This will speed up + * partitioning. + * + * @param pivot the pivot + * @param from the index of the first element + * @param to the index of the last element + * @return the first element of the second partition + */ + private int partitionSmall(T pivot, int from, int to) { + int tempIndex = 0, dataIndex = from; + for (int i = from; i <= to; i++) { + T x = data[i]; + if (comp.compare(x, pivot) <= 0) { + if (tempIndex > 0) { + data[dataIndex] = x; + } + dataIndex++; + } else { + temp[tempIndex++] = x; + } + } + if (tempIndex > 0) { + System.arraycopy(temp, 0, data, dataIndex, tempIndex); + } + return dataIndex; + } + + /** + * Swap the elements of two blocks in the data array. Both blocks are next + * to each other (the second block starts just after the first block ends). + * + * @param from the index of the first element in the first block + * @param second the index of the first element in the second block + * @param to the index of the last element in the second block + */ + private void swapBlocks(int from, int second, int to) { + int len1 = second - from, len2 = to - second + 1; + if (len1 == 0 || len2 == 0) { + return; + } + if (len1 < temp.length) { + System.arraycopy(data, from, temp, 0, len1); + System.arraycopy(data, second, data, from, len2); + System.arraycopy(temp, 0, data, from + len2, len1); + return; + } else if (len2 < temp.length) { + System.arraycopy(data, second, temp, 0, len2); + System.arraycopy(data, from, data, from + len2, len1); + System.arraycopy(temp, 0, data, from, len2); + return; + } + reverseBlock(from, second - 1); + reverseBlock(second, to); + reverseBlock(from, to); + } + + /** + * Reverse all elements in a block. + * + * @param from the index of the first element + * @param to the index of the last element + */ + private void reverseBlock(int from, int to) { + while (from < to) { + T old = data[from]; + data[from++] = data[to]; + data[to--] = old; + } + } + + /** + * Select a pivot. To ensure a good pivot is select, the median element of a + * sample of the data is calculated. + * + * @param from the index of the first element + * @param to the index of the last element + * @return the pivot + */ + private T selectPivot(int from, int to) { + int count = (int) (6 * Math.log10(to - from)); + count = Math.min(count, temp.length); + int step = (to - from) / count; + for (int i = from, j = 0; i < to; i += step, j++) { + temp[j] = data[i]; + } + T pivot = select(temp, 0, count - 1, count / 2); + return pivot; + } + + /** + * Select the specified element. + * + * @param d the array + * @param from the index of the first element + * @param to the index of the last element + * @param k which element to return (1 means the lowest) + * @return the specified element + */ + private T select(T[] d, int from, int to, int k) { + while (true) { + int pivotIndex = (to + from) >>> 1; + int pivotNewIndex = selectPartition(d, from, to, pivotIndex); + int pivotDist = pivotNewIndex - from + 1; + if (pivotDist == k) { + return d[pivotNewIndex]; + } else if (k < pivotDist) { + to = pivotNewIndex - 1; + } else { + k = k - pivotDist; + from = pivotNewIndex + 1; + } + } + } + + /** + * Partition the elements to select an element. + * + * @param d the array + * @param from the index of the first element + * @param to the index of the last element + * @param pivotIndex the index of the pivot + * @return the new index + */ + private int selectPartition(T[] d, int from, int to, int pivotIndex) { + T pivotValue = d[pivotIndex]; + swap(d, pivotIndex, to); + int storeIndex = from; + for (int i = from; i <= to; i++) { + if (comp.compare(d[i], pivotValue) < 0) { + swap(d, storeIndex, i); + storeIndex++; + } + } + swap(d, to, storeIndex); + return storeIndex; + } + + /** + * Swap two elements in the array. + * + * @param d the array + * @param a the index of the first element + * @param b the index of the second element + */ + private void swap(T[] d, int a, int b) { + T t = d[a]; + d[a] = d[b]; + d[b] = t; + } + +} diff --git a/h2/src/tools/org/h2/dev/sort/package.html b/h2/src/tools/org/h2/dev/sort/package.html new file mode 100644 index 0000000..3632158 --- /dev/null +++ b/h2/src/tools/org/h2/dev/sort/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +Sorting utilities. + +

    \ No newline at end of file diff --git a/h2/src/tools/org/h2/dev/util/AnsCompression.java b/h2/src/tools/org/h2/dev/util/AnsCompression.java new file mode 100644 index 0000000..c27c8e3 --- /dev/null +++ b/h2/src/tools/org/h2/dev/util/AnsCompression.java @@ -0,0 +1,188 @@ +/* + * 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.dev.util; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +/** + * An ANS (Asymmetric Numeral Systems) compression tool. + * It uses the range variant. + */ +public class AnsCompression { + + private static final long TOP = 1L << 24; + private static final int SHIFT = 12; + private static final int MASK = (1 << SHIFT) - 1; + private static final long MAX = (TOP >> SHIFT) << 32; + + private AnsCompression() { + // a utility class + } + + /** + * Count the frequencies of codes in the data, and increment the target + * frequency table. + * + * @param freq the target frequency table + * @param data the data + */ + public static void countFrequencies(int[] freq, byte[] data) { + for (byte x : data) { + freq[x & 0xff]++; + } + } + + /** + * Scale the frequencies to a new total. Frequencies of 0 are kept as 0; + * larger frequencies result in at least 1. + * + * @param freq the (source and target) frequency table + * @param total the target total (sum of all frequencies) + */ + public static void scaleFrequencies(int[] freq, int total) { + int len = freq.length, sum = 0; + for (int x : freq) { + sum += x; + } + // the list of: (error << 8) + index + int[] errors = new int[len]; + int totalError = -total; + for (int i = 0; i < len; i++) { + int old = freq[i]; + if (old == 0) { + continue; + } + int ideal = (int) (old * total * 256L / sum); + // 1 too high so we can decrement if needed + int x = 1 + ideal / 256; + freq[i] = x; + totalError += x; + errors[i] = ((x * 256 - ideal) << 8) + i; + } + // we don't need to sort, we could just calculate + // which one is the nth element - but sorting is simpler + Arrays.sort(errors); + if (totalError < 0) { + // integer overflow + throw new IllegalArgumentException(); + } + while (totalError > 0) { + for (int i = 0; totalError > 0 && i < len; i++) { + int index = errors[i] & 0xff; + if (freq[index] > 1) { + freq[index]--; + totalError--; + } + } + } + } + + /** + * Generate the cumulative frequency table. + * + * @param freq the source frequency table + * @return the cumulative table, with one entry more + */ + static int[] generateCumulativeFrequencies(int[] freq) { + int len = freq.length; + int[] cumulativeFreq = new int[len + 1]; + for (int i = 0, x = 0; i < len; i++) { + x += freq[i]; + cumulativeFreq[i + 1] = x; + } + return cumulativeFreq; + } + + /** + * Generate the frequency-to-code table. + * + * @param cumulativeFreq the cumulative frequency table + * @return the result + */ + private static byte[] generateFrequencyToCode(int[] cumulativeFreq) { + byte[] freqToCode = new byte[1 << SHIFT]; + int x = 0; + byte s = -1; + for (int i : cumulativeFreq) { + while (x < i) { + freqToCode[x++] = s; + } + s++; + } + return freqToCode; + } + + /** + * Encode the data. + * + * @param freq the frequency table (will be scaled) + * @param data the source data (uncompressed) + * @return the compressed data + */ + public static byte[] encode(int[] freq, byte[] data) { + scaleFrequencies(freq, 1 << SHIFT); + int[] cumulativeFreq = generateCumulativeFrequencies(freq); + ByteBuffer buff = ByteBuffer.allocate(data.length * 2); + buff = encode(data, freq, cumulativeFreq, buff); + return Arrays.copyOfRange(buff.array(), + buff.arrayOffset() + buff.position(), buff.arrayOffset() + buff.limit()); + } + + private static ByteBuffer encode(byte[] data, int[] freq, + int[] cumulativeFreq, ByteBuffer buff) { + long state = TOP; + // encoding happens backwards + int b = buff.limit(); + for (int p = data.length - 1; p >= 0; p--) { + int x = data[p] & 0xff; + int f = freq[x]; + while (state >= MAX * f) { + b -= 4; + buff.putInt(b, (int) state); + state >>>= 32; + } + state = ((state / f) << SHIFT) + (state % f) + cumulativeFreq[x]; + } + b -= 8; + buff.putLong(b, state); + buff.position(b); + return buff.slice(); + } + + /** + * Decode the data. + * + * @param freq the frequency table (will be scaled) + * @param data the compressed data + * @param length the target length + * @return the uncompressed result + */ + public static byte[] decode(int[] freq, byte[] data, int length) { + scaleFrequencies(freq, 1 << SHIFT); + int[] cumulativeFreq = generateCumulativeFrequencies(freq); + byte[] freqToCode = generateFrequencyToCode(cumulativeFreq); + byte[] out = new byte[length]; + decode(data, freq, cumulativeFreq, freqToCode, out); + return out; + } + + private static void decode(byte[] data, int[] freq, int[] cumulativeFreq, + byte[] freqToCode, byte[] out) { + ByteBuffer buff = ByteBuffer.wrap(data); + long state = buff.getLong(); + for (int i = 0, size = out.length; i < size; i++) { + int x = (int) state & MASK; + int c = freqToCode[x] & 0xff; + out[i] = (byte) c; + state = (freq[c] * (state >> SHIFT)) + x - cumulativeFreq[c]; + while (state < TOP) { + state = (state << 32) | (buff.getInt() & 0xffffffffL); + } + } + } + +} diff --git a/h2/src/tools/org/h2/dev/util/ArrayUtils.java b/h2/src/tools/org/h2/dev/util/ArrayUtils.java new file mode 100644 index 0000000..657d7ea --- /dev/null +++ b/h2/src/tools/org/h2/dev/util/ArrayUtils.java @@ -0,0 +1,65 @@ +/* + * 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.dev.util; + +import java.util.Comparator; + +/** + * Array utility methods. + */ +public class ArrayUtils { + + /** + * Sort an array using binary insertion sort + * + * @param the type + * @param d the data + * @param left the index of the leftmost element + * @param right the index of the rightmost element + * @param comp the comparison class + */ + public static void binaryInsertionSort(T[] d, int left, int right, + Comparator comp) { + for (int i = left + 1; i <= right; i++) { + T t = d[i]; + int l = left; + for (int r = i; l < r;) { + int m = (l + r) >>> 1; + if (comp.compare(t, d[m]) >= 0) { + l = m + 1; + } else { + r = m; + } + } + for (int n = i - l; n > 0;) { + d[l + n--] = d[l + n]; + } + d[l] = t; + } + } + + /** + * Sort an array using insertion sort + * + * @param the type + * @param d the data + * @param left the index of the leftmost element + * @param right the index of the rightmost element + * @param comp the comparison class + */ + public static void insertionSort(T[] d, int left, int right, + Comparator comp) { + for (int i = left + 1, j; i <= right; i++) { + T t = d[i]; + for (j = i - 1; j >= left && comp.compare(d[j], t) > 0; j--) { + d[j + 1] = d[j]; + } + d[j + 1] = t; + } + } + + +} diff --git a/h2/src/tools/org/h2/dev/util/Base64.java b/h2/src/tools/org/h2/dev/util/Base64.java new file mode 100644 index 0000000..3606adf --- /dev/null +++ b/h2/src/tools/org/h2/dev/util/Base64.java @@ -0,0 +1,234 @@ +/* + * 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.dev.util; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/** + * This class converts binary to base64 and vice versa. + */ +public class Base64 { + + private static final byte[] CODE = new byte[64]; + private static final byte[] REV = new byte[256]; + + private Base64() { + // utility class + } + + static { + for (int i = 'A'; i <= 'Z'; i++) { + CODE[i - 'A'] = (byte) i; + CODE[i - 'A' + 26] = (byte) (i + 'a' - 'A'); + } + for (int i = 0; i < 10; i++) { + CODE[i + 2 * 26] = (byte) ('0' + i); + } + CODE[62] = (byte) '+'; + CODE[63] = (byte) '/'; + for (int i = 0; i < 255; i++) { + REV[i] = -1; + } + for (int i = 0; i < 64; i++) { + REV[CODE[i]] = (byte) i; + } + } + + private static void check(String a, String b) { + if (!a.equals(b)) { + throw new RuntimeException("mismatch: " + a + " <> " + b); + } + } + + /** + * Run the tests. + * + * @param args the command line parameters + */ + public static void main(String... args) { + check(new String(encode(new byte[] {})), ""); + check(new String(encode("A".getBytes())), "QQ=="); + check(new String(encode("AB".getBytes())), "QUI="); + check(new String(encode("ABC".getBytes())), "QUJD"); + check(new String(encode("ABCD".getBytes())), "QUJDRA=="); + check(new String(decode(new byte[] {})), ""); + check(new String(decode("QQ==".getBytes())), "A"); + check(new String(decode("QUI=".getBytes())), "AB"); + check(new String(decode("QUJD".getBytes())), "ABC"); + check(new String(decode("QUJDRA==".getBytes())), "ABCD"); + int len = 10000; + test(false, len); + test(true, len); + test(false, len); + test(true, len); + } + + private static void test(boolean fast, int len) { + Random random = new Random(10); + long time = System.nanoTime(); + byte[] bin = new byte[len]; + random.nextBytes(bin); + for (int i = 0; i < len; i++) { + byte[] dec; + if (fast) { + byte[] enc = encodeFast(bin); + dec = decodeFast(enc); + } else { + byte[] enc = encode(bin); + dec = decode(enc); + } + test(bin, dec); + } + time = System.nanoTime() - time; + System.out.println("fast=" + fast + " time=" + TimeUnit.NANOSECONDS.toMillis(time)); + } + + private static void test(byte[] in, byte[] out) { + if (in.length != out.length) { + throw new RuntimeException("Length error"); + } + for (int i = 0; i < in.length; i++) { + if (in[i] != out[i]) { + throw new RuntimeException("Error at " + i); + } + } + } + + private static byte[] encode(byte[] bin) { + byte[] code = CODE; + int size = bin.length; + int len = ((size + 2) / 3) * 4; + byte[] enc = new byte[len]; + int fast = size / 3 * 3, i = 0, j = 0; + for (; i < fast; i += 3, j += 4) { + int a = ((bin[i] & 255) << 16) + ((bin[i + 1] & 255) << 8) + (bin[i + 2] & 255); + enc[j] = code[a >> 18]; + enc[j + 1] = code[(a >> 12) & 63]; + enc[j + 2] = code[(a >> 6) & 63]; + enc[j + 3] = code[a & 63]; + } + if (i < size) { + int a = (bin[i++] & 255) << 16; + enc[j] = code[a >> 18]; + if (i < size) { + a += (bin[i] & 255) << 8; + enc[j + 2] = code[(a >> 6) & 63]; + } else { + enc[j + 2] = (byte) '='; + } + enc[j + 1] = code[(a >> 12) & 63]; + enc[j + 3] = (byte) '='; + } + return enc; + } + + private static byte[] encodeFast(byte[] bin) { + byte[] code = CODE; + int size = bin.length; + int len = ((size * 4) + 2) / 3; + byte[] enc = new byte[len]; + int fast = size / 3 * 3, i = 0, j = 0; + for (; i < fast; i += 3, j += 4) { + int a = ((bin[i] & 255) << 16) + ((bin[i + 1] & 255) << 8) + (bin[i + 2] & 255); + enc[j] = code[a >> 18]; + enc[j + 1] = code[(a >> 12) & 63]; + enc[j + 2] = code[(a >> 6) & 63]; + enc[j + 3] = code[a & 63]; + } + if (i < size) { + int a = (bin[i++] & 255) << 16; + enc[j] = code[a >> 18]; + if (i < size) { + a += (bin[i] & 255) << 8; + enc[j + 2] = code[(a >> 6) & 63]; + } + enc[j + 1] = code[(a >> 12) & 63]; + } + return enc; + } + + private static byte[] trim(byte[] enc) { + byte[] rev = REV; + int j = 0, size = enc.length; + if (size > 1 && enc[size - 2] == '=') { + size--; + } + if (size > 0 && enc[size - 1] == '=') { + size--; + } + for (int i = 0; i < size; i++) { + if (rev[enc[i] & 255] < 0) { + j++; + } + } + if (j == 0) { + return enc; + } + byte[] buff = new byte[size - j]; + for (int i = 0, k = 0; i < size; i++) { + int x = enc[i] & 255; + if (rev[x] >= 0) { + buff[k++] = (byte) x; + } + } + return buff; + } + + private static byte[] decode(byte[] enc) { + enc = trim(enc); + byte[] rev = REV; + int len = enc.length, size = (len * 3) / 4; + if (len > 0 && enc[len - 1] == '=') { + size--; + if (len > 1 && enc[len - 2] == '=') { + size--; + } + } + byte[] bin = new byte[size]; + int fast = size / 3 * 3, i = 0, j = 0; + for (; i < fast; i += 3, j += 4) { + int a = (rev[enc[j] & 255] << 18) + (rev[enc[j + 1] & 255] << 12) + + (rev[enc[j + 2] & 255] << 6) + rev[enc[j + 3] & 255]; + bin[i] = (byte) (a >> 16); + bin[i + 1] = (byte) (a >> 8); + bin[i + 2] = (byte) a; + } + if (i < size) { + int a = (rev[enc[j] & 255] << 10) + (rev[enc[j + 1] & 255] << 4); + bin[i++] = (byte) (a >> 8); + if (i < size) { + a += rev[enc[j + 2] & 255] >> 2; + bin[i] = (byte) a; + } + } + return bin; + } + + private static byte[] decodeFast(byte[] enc) { + byte[] rev = REV; + int len = enc.length, size = (len * 3) / 4; + byte[] bin = new byte[size]; + int fast = size / 3 * 3, i = 0, j = 0; + for (; i < fast; i += 3, j += 4) { + int a = (rev[enc[j] & 255] << 18) + (rev[enc[j + 1] & 255] << 12) + + (rev[enc[j + 2] & 255] << 6) + rev[enc[j + 3] & 255]; + bin[i] = (byte) (a >> 16); + bin[i + 1] = (byte) (a >> 8); + bin[i + 2] = (byte) a; + } + if (i < size) { + int a = (rev[enc[j] & 255] << 10) + (rev[enc[j + 1] & 255] << 4); + bin[i++] = (byte) (a >> 8); + if (i < size) { + a += rev[enc[j + 2] & 255] >> 2; + bin[i] = (byte) a; + } + } + return bin; + } + +} diff --git a/h2/src/tools/org/h2/dev/util/BinaryArithmeticStream.java b/h2/src/tools/org/h2/dev/util/BinaryArithmeticStream.java new file mode 100644 index 0000000..e0cacb2 --- /dev/null +++ b/h2/src/tools/org/h2/dev/util/BinaryArithmeticStream.java @@ -0,0 +1,269 @@ +/* + * 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.dev.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.PriorityQueue; + +/** + * A binary arithmetic stream. + */ +public class BinaryArithmeticStream { + + /** + * The maximum probability. + */ + public static final int MAX_PROBABILITY = (1 << 12) - 1; + + /** + * The low marker. + */ + protected int low; + + /** + * The high marker. + */ + protected int high = 0xffffffff; + + /** + * A binary arithmetic input stream. + */ + public static class In extends BinaryArithmeticStream { + + private final InputStream in; + private int data; + + public In(InputStream in) throws IOException { + this.in = in; + data = ((in.read() & 0xff) << 24) | + ((in.read() & 0xff) << 16) | + ((in.read() & 0xff) << 8) | + (in.read() & 0xff); + } + + /** + * Read a bit. + * + * @param probability the probability that the value is true + * @return the value + */ + public boolean readBit(int probability) throws IOException { + int split = low + probability * ((high - low) >>> 12); + boolean value; + // compare unsigned + if (data + Integer.MIN_VALUE > split + Integer.MIN_VALUE) { + low = split + 1; + value = false; + } else { + high = split; + value = true; + } + while (low >>> 24 == high >>> 24) { + data = (data << 8) | (in.read() & 0xff); + low <<= 8; + high = (high << 8) | 0xff; + } + return value; + } + + /** + * Read a value that is stored as a Golomb code. + * + * @param divisor the divisor + * @return the value + */ + public int readGolomb(int divisor) throws IOException { + int q = 0; + while (readBit(MAX_PROBABILITY / 2)) { + q++; + } + int bit = 31 - Integer.numberOfLeadingZeros(divisor - 1); + int r = 0; + if (bit >= 0) { + int cutOff = (2 << bit) - divisor; + for (; bit > 0; bit--) { + r = (r << 1) + (readBit(MAX_PROBABILITY / 2) ? 1 : 0); + } + if (r >= cutOff) { + r = (r << 1) + (readBit(MAX_PROBABILITY / 2) ? 1 : 0) - cutOff; + } + } + return q * divisor + r; + } + + } + + /** + * A binary arithmetic output stream. + */ + public static class Out extends BinaryArithmeticStream { + + private final OutputStream out; + + public Out(OutputStream out) { + this.out = out; + } + + /** + * Write a bit. + * + * @param value the value + * @param probability the probability that the value is true + */ + public void writeBit(boolean value, int probability) throws IOException { + int split = low + probability * ((high - low) >>> 12); + if (value) { + high = split; + } else { + low = split + 1; + } + while (low >>> 24 == high >>> 24) { + out.write(high >> 24); + low <<= 8; + high = (high << 8) | 0xff; + } + } + + /** + * Flush the stream. + */ + public void flush() throws IOException { + out.write(high >> 24); + out.write(high >> 16); + out.write(high >> 8); + out.write(high); + } + + /** + * Write the Golomb code of a value. + * + * @param divisor the divisor + * @param value the value + */ + public void writeGolomb(int divisor, int value) throws IOException { + int q = value / divisor; + for (int i = 0; i < q; i++) { + writeBit(true, MAX_PROBABILITY / 2); + } + writeBit(false, MAX_PROBABILITY / 2); + int r = value - q * divisor; + int bit = 31 - Integer.numberOfLeadingZeros(divisor - 1); + if (r < ((2 << bit) - divisor)) { + bit--; + } else { + r += (2 << bit) - divisor; + } + for (; bit >= 0; bit--) { + writeBit(((r >>> bit) & 1) == 1, MAX_PROBABILITY / 2); + } + } + + } + + /** + * A Huffman code table / tree. + */ + public static class Huffman { + + private final int[] codes; + private final Node tree; + + public Huffman(int[] frequencies) { + PriorityQueue queue = new PriorityQueue<>(); + for (int i = 0; i < frequencies.length; i++) { + int f = frequencies[i]; + if (f > 0) { + queue.offer(new Node(i, f)); + } + } + while (queue.size() > 1) { + queue.offer(new Node(queue.poll(), queue.poll())); + } + codes = new int[frequencies.length]; + tree = queue.poll(); + if (tree != null) { + tree.initCodes(codes, 1); + } + } + + /** + * Write a value. + * + * @param out the output stream + * @param value the value to write + */ + public void write(Out out, int value) throws IOException { + int code = codes[value]; + int bitCount = 30 - Integer.numberOfLeadingZeros(code); + Node n = tree; + for (int i = bitCount; i >= 0; i--) { + boolean goRight = ((code >> i) & 1) == 1; + int prob = (int) ((long) MAX_PROBABILITY * + n.right.frequency / n.frequency); + out.writeBit(goRight, prob); + n = goRight ? n.right : n.left; + } + } + + /** + * Read a value. + * + * @param in the input stream + * @return the value + */ + public int read(In in) throws IOException { + Node n = tree; + while (n.left != null) { + int prob = (int) ((long) MAX_PROBABILITY * + n.right.frequency / n.frequency); + boolean goRight = in.readBit(prob); + n = goRight ? n.right : n.left; + } + return n.value; + } + + } + + /** + * A Huffman code node. + */ + private static class Node implements Comparable { + + int value; + Node left; + Node right; + final int frequency; + + Node(int value, int frequency) { + this.frequency = frequency; + this.value = value; + } + + Node(Node left, Node right) { + this.left = left; + this.right = right; + this.frequency = left.frequency + right.frequency; + } + + @Override + public int compareTo(Node o) { + return frequency - o.frequency; + } + + void initCodes(int[] codes, int bits) { + if (left == null) { + codes[value] = bits; + } else { + left.initCodes(codes, bits << 1); + right.initCodes(codes, (bits << 1) + 1); + } + } + + } + +} diff --git a/h2/src/tools/org/h2/dev/util/BitStream.java b/h2/src/tools/org/h2/dev/util/BitStream.java new file mode 100644 index 0000000..7968a4a --- /dev/null +++ b/h2/src/tools/org/h2/dev/util/BitStream.java @@ -0,0 +1,298 @@ +/* + * 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.dev.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.PriorityQueue; + +/** + * A stream that supports Golomb and Huffman coding. + */ +public class BitStream { + + private BitStream() { + // a utility class + } + + /** + * A bit input stream. + */ + public static class In { + + private final InputStream in; + private int current = 0x10000; + + public In(InputStream in) { + this.in = in; + } + + /** + * Read a value that is stored as a Golomb code. + * + * @param divisor the divisor + * @return the value + */ + public int readGolomb(int divisor) { + int q = 0; + while (readBit() == 1) { + q++; + } + int bit = 31 - Integer.numberOfLeadingZeros(divisor - 1); + int r = 0; + if (bit >= 0) { + int cutOff = (2 << bit) - divisor; + for (; bit > 0; bit--) { + r = (r << 1) + readBit(); + } + if (r >= cutOff) { + r = (r << 1) + readBit() - cutOff; + } + } + return q * divisor + r; + } + + /** + * Read a bit. + * + * @return the bit (0 or 1) + */ + public int readBit() { + if (current >= 0x10000) { + try { + current = 0x100 | in.read(); + if (current < 0) { + return -1; + } + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + int bit = (current >>> 7) & 1; + current <<= 1; + return bit; + } + + /** + * Close the stream. This will also close the underlying stream. + */ + public void close() { + try { + in.close(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + } + + /** + * A bit output stream. + */ + public static class Out { + + private final OutputStream out; + private int current = 1; + + public Out(OutputStream out) { + this.out = out; + } + + /** + * Write the Golomb code of a value. + * + * @param divisor the divisor + * @param value the value + */ + public void writeGolomb(int divisor, int value) { + int q = value / divisor; + for (int i = 0; i < q; i++) { + writeBit(1); + } + writeBit(0); + int r = value - q * divisor; + int bit = 31 - Integer.numberOfLeadingZeros(divisor - 1); + if (r < ((2 << bit) - divisor)) { + bit--; + } else { + r += (2 << bit) - divisor; + } + for (; bit >= 0; bit--) { + writeBit((r >>> bit) & 1); + } + } + + /** + * Get the size of the Golomb code for this value. + * + * @param divisor the divisor + * @param value the value + * @return the number of bits + */ + public static int getGolombSize(int divisor, int value) { + int q = value / divisor; + int r = value - q * divisor; + int bit = 31 - Integer.numberOfLeadingZeros(divisor - 1); + if (r < ((2 << bit) - divisor)) { + bit--; + } + return bit + q + 2; + } + + /** + * Write a bit. + * + * @param bit the bit (0 or 1) + */ + public void writeBit(int bit) { + current = (current << 1) + bit; + if (current > 0xff) { + try { + out.write(current & 0xff); + } catch (IOException e) { + throw new IllegalStateException(e); + } + current = 1; + } + } + + /** + * Flush the stream. This will at write at most 7 '0' bits. + * This will also flush the underlying stream. + */ + public void flush() { + while (current > 1) { + writeBit(0); + } + try { + out.flush(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + /** + * Flush and close the stream. + * This will also close the underlying stream. + */ + public void close() { + flush(); + try { + out.close(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + + } + + } + + /** + * A Huffman code. + */ + public static class Huffman { + + private final int[] codes; + private final Node tree; + + public Huffman(int[] frequencies) { + PriorityQueue queue = new PriorityQueue<>(); + for (int i = 0; i < frequencies.length; i++) { + int f = frequencies[i]; + if (f > 0) { + queue.offer(new Node(i, f)); + } + } + while (queue.size() > 1) { + queue.offer(new Node(queue.poll(), queue.poll())); + } + codes = new int[frequencies.length]; + tree = queue.poll(); + if (tree != null) { + tree.initCodes(codes, 1); + } + } + + /** + * Write a value. + * + * @param out the output stream + * @param value the value to write + */ + public void write(BitStream.Out out, int value) { + int code = codes[value]; + int bitCount = 30 - Integer.numberOfLeadingZeros(code); + for (int i = bitCount; i >= 0; i--) { + out.writeBit((code >> i) & 1); + } + } + + /** + * Read a value. + * + * @param in the input stream + * @return the value + */ + public int read(BitStream.In in) { + Node n = tree; + while (n.left != null) { + n = in.readBit() == 1 ? n.right : n.left; + } + return n.value; + } + + /** + * Get the number of bits of the Huffman code for this value. + * + * @param value the value + * @return the number of bits + */ + public int getBitCount(int value) { + int code = codes[value]; + return 30 - Integer.numberOfLeadingZeros(code); + } + + } + + /** + * A Huffman code node. + */ + private static class Node implements Comparable { + + int value; + Node left; + Node right; + private final int frequency; + + Node(int value, int frequency) { + this.frequency = frequency; + this.value = value; + } + + Node(Node left, Node right) { + this.left = left; + this.right = right; + this.frequency = left.frequency + right.frequency; + } + + @Override + public int compareTo(Node o) { + return frequency - o.frequency; + } + + void initCodes(int[] codes, int bits) { + if (left == null) { + codes[value] = bits; + } else { + left.initCodes(codes, bits << 1); + right.initCodes(codes, (bits << 1) + 1); + } + } + + } + +} diff --git a/h2/src/tools/org/h2/dev/util/ConcurrentLinkedList.java b/h2/src/tools/org/h2/dev/util/ConcurrentLinkedList.java new file mode 100644 index 0000000..bf82210 --- /dev/null +++ b/h2/src/tools/org/h2/dev/util/ConcurrentLinkedList.java @@ -0,0 +1,145 @@ +/* + * 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.dev.util; + +import java.util.Iterator; + +/** + * A very simple linked list that supports concurrent access. + * Internally, it uses immutable objects. + * It uses recursion and is not meant for long lists. + * + * @param the key type + */ +public class ConcurrentLinkedList { + + /** + * The sentinel entry. + */ + static final Entry NULL = new Entry<>(null, null); + + /** + * The head entry. + */ + @SuppressWarnings("unchecked") + volatile Entry head = (Entry) NULL; + + /** + * Get the first element, or null if none. + * + * @return the first element + */ + public K peekFirst() { + Entry x = head; + return x.obj; + } + + /** + * Get the last element, or null if none. + * + * @return the last element + */ + public K peekLast() { + Entry x = head; + while (x != NULL && x.next != NULL) { + x = x.next; + } + return x.obj; + } + + /** + * Add an element at the end. + * + * @param obj the element + */ + public synchronized void add(K obj) { + head = Entry.append(head, obj); + } + + /** + * Remove the first element, if it matches. + * + * @param obj the element to remove + * @return true if the element matched and was removed + */ + public synchronized boolean removeFirst(K obj) { + if (head.obj != obj) { + return false; + } + head = head.next; + return true; + } + + /** + * Remove the last element, if it matches. + * + * @param obj the element to remove + * @return true if the element matched and was removed + */ + public synchronized boolean removeLast(K obj) { + if (peekLast() != obj) { + return false; + } + head = Entry.removeLast(head); + return true; + } + + /** + * Get an iterator over all entries. + * + * @return the iterator + */ + public Iterator iterator() { + return new Iterator() { + + Entry current = head; + + @Override + public boolean hasNext() { + return current != NULL; + } + + @Override + public K next() { + K x = current.obj; + current = current.next; + return x; + } + + }; + } + + /** + * An entry in the linked list. + */ + private static class Entry { + final K obj; + Entry next; + + Entry(K obj, Entry next) { + this.obj = obj; + this.next = next; + } + + @SuppressWarnings("unchecked") + static Entry append(Entry list, K obj) { + if (list == NULL) { + return new Entry<>(obj, (Entry) NULL); + } + return new Entry<>(list.obj, append(list.next, obj)); + } + + @SuppressWarnings("unchecked") + static Entry removeLast(Entry list) { + if (list == NULL || list.next == NULL) { + return (Entry) NULL; + } + return new Entry<>(list.obj, removeLast(list.next)); + } + + } + +} diff --git a/h2/src/tools/org/h2/dev/util/ConcurrentLinkedListWithTail.java b/h2/src/tools/org/h2/dev/util/ConcurrentLinkedListWithTail.java new file mode 100644 index 0000000..72a2ebd --- /dev/null +++ b/h2/src/tools/org/h2/dev/util/ConcurrentLinkedListWithTail.java @@ -0,0 +1,148 @@ +/* + * 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.dev.util; + +import java.util.Iterator; + +/** + * A very simple linked list that supports concurrent access. + * + * @param the key type + */ +public class ConcurrentLinkedListWithTail { + + /** + * The first entry (if any). + */ + volatile Entry head; + + /** + * The last entry (if any). + */ + private volatile Entry tail; + + /** + * Get the first element, or null if none. + * + * @return the first element + */ + public K peekFirst() { + Entry x = head; + return x == null ? null : x.obj; + } + + /** + * Get the last element, or null if none. + * + * @return the last element + */ + public K peekLast() { + Entry x = tail; + return x == null ? null : x.obj; + } + + /** + * Add an element at the end. + * + * @param obj the element + */ + public void add(K obj) { + Entry x = new Entry<>(obj); + Entry t = tail; + if (t != null) { + t.next = x; + } + tail = x; + if (head == null) { + head = x; + } + } + + /** + * Remove the first element, if it matches. + * + * @param obj the element to remove + * @return true if the element matched and was removed + */ + public boolean removeFirst(K obj) { + Entry x = head; + if (x == null || x.obj != obj) { + return false; + } + if (head == tail) { + tail = x.next; + } + head = x.next; + return true; + } + + /** + * Remove the last element, if it matches. + * + * @param obj the element to remove + * @return true if the element matched and was removed + */ + public boolean removeLast(K obj) { + Entry x = head; + if (x == null) { + return false; + } + Entry prev = null; + while (x.next != null) { + prev = x; + x = x.next; + } + if (x.obj != obj) { + return false; + } + if (prev != null) { + prev.next = null; + } + if (head == tail) { + head = prev; + } + tail = prev; + return true; + } + + /** + * Get an iterator over all entries. + * + * @return the iterator + */ + public Iterator iterator() { + return new Iterator() { + + Entry current = head; + + @Override + public boolean hasNext() { + return current != null; + } + + @Override + public K next() { + K x = current.obj; + current = current.next; + return x; + } + + }; + } + + /** + * An entry in the linked list. + */ + private static class Entry { + final K obj; + Entry next; + + Entry(K obj) { + this.obj = obj; + } + } + +} diff --git a/h2/src/tools/org/h2/dev/util/ConcurrentRing.java b/h2/src/tools/org/h2/dev/util/ConcurrentRing.java new file mode 100644 index 0000000..73a06ed --- /dev/null +++ b/h2/src/tools/org/h2/dev/util/ConcurrentRing.java @@ -0,0 +1,148 @@ +/* + * 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.dev.util; + +import java.util.Iterator; + +/** + * A ring buffer that supports concurrent access. + * + * @param the key type + */ +public class ConcurrentRing { + + /** + * The ring buffer. + */ + K[] buffer; + + /** + * The read position. + */ + volatile int readPos; + + /** + * The write position. + */ + volatile int writePos; + + @SuppressWarnings("unchecked") + public ConcurrentRing() { + buffer = (K[]) new Object[4]; + } + + /** + * Get the first element, or null if none. + * + * @return the first element + */ + public K peekFirst() { + return buffer[getIndex(readPos)]; + } + + /** + * Get the last element, or null if none. + * + * @return the last element + */ + public K peekLast() { + return buffer[getIndex(writePos - 1)]; + } + + /** + * Add an element at the end. + * + * @param obj the element + */ + public void add(K obj) { + buffer[getIndex(writePos)] = obj; + writePos++; + if (writePos - readPos >= buffer.length) { + // double the capacity + @SuppressWarnings("unchecked") + K[] b2 = (K[]) new Object[buffer.length * 2]; + for (int i = readPos; i < writePos; i++) { + K x = buffer[getIndex(i)]; + int i2 = i & b2.length - 1; + b2[i2] = x; + } + buffer = b2; + } + } + + /** + * Remove the first element, if it matches. + * + * @param obj the element to remove + * @return true if the element matched and was removed + */ + public boolean removeFirst(K obj) { + int p = readPos; + int idx = getIndex(p); + if (buffer[idx] != obj) { + return false; + } + buffer[idx] = null; + readPos = p + 1; + return true; + } + + /** + * Remove the last element, if it matches. + * + * @param obj the element to remove + * @return true if the element matched and was removed + */ + public boolean removeLast(K obj) { + int p = writePos; + int idx = getIndex(p - 1); + if (buffer[idx] != obj) { + return false; + } + buffer[idx] = null; + writePos = p - 1; + return true; + } + + /** + * Get the index in the array of the given position. + * + * @param pos the position + * @return the index + */ + int getIndex(int pos) { + return pos & (buffer.length - 1); + } + + /** + * Get an iterator over all entries. + * + * @return the iterator + */ + public Iterator iterator() { + return new Iterator() { + + int offset; + + @Override + public boolean hasNext() { + return readPos + offset < writePos; + } + + @Override + public K next() { + if (buffer[getIndex(readPos + offset)] == null) { + System.out.println("" + readPos); + System.out.println("" + getIndex(readPos + offset)); + System.out.println("null?"); + } + return buffer[getIndex(readPos + offset++)]; + } + + }; + } + +} diff --git a/h2/src/tools/org/h2/dev/util/FileContentHash.java b/h2/src/tools/org/h2/dev/util/FileContentHash.java new file mode 100644 index 0000000..f815c37 --- /dev/null +++ b/h2/src/tools/org/h2/dev/util/FileContentHash.java @@ -0,0 +1,171 @@ +/* + * 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.dev.util; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +import org.h2.store.fs.FileUtils; +import org.h2.util.SortedProperties; +import org.h2.util.StringUtils; + +/** + * A utility to calculate the content hash of files. It should help detect + * duplicate files and differences between directories. + */ +public class FileContentHash { + + // find empty directories: + // find . -type d -empty + // find . -name .hash.prop -delete + + private static final boolean WRITE_HASH_INDEX = true; + private static final String HASH_INDEX = ".hash.prop"; + private static final int MIN_SIZE = 0; + private final HashMap hashes = new HashMap<>(); + private long nextLog; + + /** + * Run the viewer. + * + * @param args the command line arguments + */ + public static void main(String... args) throws IOException { + new FileContentHash().runTool(args); + } + + private void runTool(String... args) throws IOException { + if (args.length == 0) { + System.out.println("Usage: java " + getClass().getName() + " "); + return; + } + for (int i = 0; i < args.length; i++) { + Info info = hash(args[i]); + System.out.println("size: " + info.size); + } + } + + private static MessageDigest createMessageDigest() { + try { + return MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + private Info hash(String path) throws IOException { + if (FileUtils.isDirectory(path)) { + long totalSize = 0; + SortedProperties propOld; + SortedProperties propNew = new SortedProperties(); + String hashFileName = path + "/" + HASH_INDEX; + if (FileUtils.exists(hashFileName)) { + propOld = SortedProperties.loadProperties(hashFileName); + } else { + propOld = new SortedProperties(); + } + List list = FileUtils.newDirectoryStream(path); + Collections.sort(list); + MessageDigest mdDir = createMessageDigest(); + for (String f : list) { + String name = FileUtils.getName(f); + if (name.equals(HASH_INDEX)) { + continue; + } + long length = FileUtils.size(f); + String entry = "name_" + name + + "-mod_" + FileUtils.lastModified(f) + + "-size_" + length; + String hash = propOld.getProperty(entry); + if (hash == null || FileUtils.isDirectory(f)) { + Info info = hash(f); + byte[] b = info.hash; + hash = StringUtils.convertBytesToHex(b); + totalSize += info.size; + entry = "name_" + name + + "-mod_" + FileUtils.lastModified(f) + + "-size_" + info.size; + } else { + totalSize += length; + checkCollision(f, length, StringUtils.convertHexToBytes(hash)); + } + propNew.put(entry, hash); + mdDir.update(entry.getBytes(StandardCharsets.UTF_8)); + mdDir.update(hash.getBytes(StandardCharsets.UTF_8)); + } + String oldFile = propOld.toString(); + String newFile = propNew.toString(); + if (!oldFile.equals(newFile)) { + if (WRITE_HASH_INDEX) { + propNew.store(path + "/" + HASH_INDEX); + } + } + Info info = new Info(); + info.hash = mdDir.digest(); + info.size = totalSize; + return info; + } + MessageDigest md = createMessageDigest(); + InputStream in = FileUtils.newInputStream(path); + long length = FileUtils.size(path); + byte[] buff = new byte[1024 * 1024]; + while (true) { + int len = in.read(buff); + if (len < 0) { + break; + } + md.update(buff, 0, len); + long t = System.nanoTime(); + if (nextLog == 0 || t > nextLog) { + System.out.println("Checking " + path); + nextLog = t + 5000 * 1000000L; + } + } + in.close(); + byte[] b = md.digest(); + checkCollision(path, length, b); + Info info = new Info(); + info.hash = b; + info.size = length; + return info; + } + + private void checkCollision(String path, long length, byte[] hash) { + if (length < MIN_SIZE) { + return; + } + String s = StringUtils.convertBytesToHex(hash); + String old = hashes.get(s); + if (old != null) { + System.out.println("Collision: " + old + "\n" + path + "\n"); + } else { + hashes.put(s, path); + } + } + + /** + * The info for a file. + */ + static class Info { + + /** + * The content hash. + */ + byte[] hash; + + /** + * The size in bytes. + */ + long size; + } + +} diff --git a/h2/src/tools/org/h2/dev/util/FileViewer.java b/h2/src/tools/org/h2/dev/util/FileViewer.java new file mode 100644 index 0000000..d92cd51 --- /dev/null +++ b/h2/src/tools/org/h2/dev/util/FileViewer.java @@ -0,0 +1,214 @@ +/* + * 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.dev.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; + +import org.h2.message.DbException; +import org.h2.util.Tool; + +/** + * A text file viewer that support very large files. + */ +public class FileViewer extends Tool { + + /** + * Run the viewer. + * + * @param args the command line arguments + */ + public static void main(String... args) throws SQLException { + new FileViewer().runTool(args); + } + + @Override + protected void showUsage() { + out.println("A text file viewer that support very large files."); + out.println("java "+getClass().getName() + "\n" + + " -file The name of the file to view\n" + + " [-find ] Find a string and display the next lines\n" + + " [-start ] Start at the given position\n" + + " [-head] Display the first lines\n" + + " [-tail] Display the last lines\n" + + " [-lines ] Display only x lines (default: 30)\n" + + " [-quiet] Do not print progress information"); + } + + @Override + public void runTool(String... args) throws SQLException { + String file = null; + String find = null; + boolean head = false, tail = false; + int lines = 30; + boolean quiet = false; + long start = 0; + for (int i = 0; args != null && i < args.length; i++) { + String arg = args[i]; + if (arg.equals("-file")) { + file = args[++i]; + } else if (arg.equals("-find")) { + find = args[++i]; + } else if (arg.equals("-start")) { + start = Long.decode(args[++i]); + } else if (arg.equals("-head")) { + head = true; + } else if (arg.equals("-tail")) { + tail = true; + } else if (arg.equals("-lines")) { + lines = Integer.decode(args[++i]); + } else if (arg.equals("-quiet")) { + quiet = true; + } else if (arg.equals("-help") || arg.equals("-?")) { + showUsage(); + return; + } else { + showUsageAndThrowUnsupportedOption(arg); + } + } + if (file == null) { + showUsage(); + return; + } + if (!head && !tail && find == null) { + head = true; + } + try { + process(file, find, head, tail, start, lines, quiet); + } catch (IOException e) { + throw DbException.toSQLException(e); + } + } + + private static void process(String fileName, String find, + boolean head, boolean tail, long start, int lines, + boolean quiet) throws IOException { + RandomAccessFile file = new RandomAccessFile(fileName, "r"); + long length = file.length(); + if (head) { + file.seek(start); + list(start, "Head", readLines(file, lines)); + } + if (find != null) { + file.seek(start); + long pos = find(file, find.getBytes(), quiet); + if (pos >= 0) { + file.seek(pos); + list(pos, "Found " + find, readLines(file, lines)); + } + } + if (tail) { + long pos = length - 100L * lines; + ArrayList list = null; + while (pos > 0) { + file.seek(pos); + list = readLines(file, Integer.MAX_VALUE); + if (list.size() > lines) { + break; + } + pos -= 100L * lines; + } + // remove the first (maybe partial) line + list.remove(0); + while (list.size() > lines) { + list.remove(0); + } + list(pos, "Tail", list); + } + } + + private static long find(RandomAccessFile file, byte[] find, boolean quiet) + throws IOException { + long pos = file.getFilePointer(); + long length = file.length(); + int bufferSize = 4 * 1024; + byte[] data = new byte[bufferSize * 2]; + long last = System.nanoTime(); + while (pos < length) { + System.arraycopy(data, bufferSize, data, 0, bufferSize); + if (pos + bufferSize > length) { + file.readFully(data, bufferSize, (int) (length - pos)); + return find(data, find, (int) (bufferSize + length - pos - find.length)); + } + if (!quiet) { + long now = System.nanoTime(); + if (now > last + TimeUnit.SECONDS.toNanos(5)) { + System.out.println((100 * pos / length) + "%"); + last = now; + } + } + file.readFully(data, bufferSize, bufferSize); + int f = find(data, find, bufferSize); + if (f >= 0) { + return f + pos - bufferSize; + } + pos += bufferSize; + } + return -1; + } + + private static int find(byte[] data, byte[] find, int max) { + outer: + for (int i = 0; i < max; i++) { + for (int j = 0; j < find.length; j++) { + if (data[i + j] != find[j]) { + continue outer; + } + } + return i; + } + return -1; + } + + private static void list(long pos, String header, ArrayList list) { + System.out.println("-----------------------------------------------"); + System.out.println("[" + pos + "]: " + header); + System.out.println("-----------------------------------------------"); + for (String l : list) { + System.out.println(l); + } + System.out.println("-----------------------------------------------"); + } + + private static ArrayList readLines(RandomAccessFile file, + int maxLines) throws IOException { + ArrayList lines = new ArrayList<>(); + ByteArrayOutputStream buff = new ByteArrayOutputStream(100); + boolean lastNewline = false; + while (maxLines > 0) { + int x = file.read(); + if (x < 0) { + break; + } + if (x == '\r' || x == '\n') { + if (!lastNewline) { + maxLines--; + lastNewline = true; + byte[] data = buff.toByteArray(); + String s = new String(data); + lines.add(s); + buff.reset(); + } + continue; + } + if (lastNewline) { + lastNewline = false; + } + buff.write(x); + } + byte[] data = buff.toByteArray(); + if (data.length > 0) { + String s = new String(data); + lines.add(s); + } + return lines; + } + +} diff --git a/h2/src/tools/org/h2/dev/util/ImmutableArray.java b/h2/src/tools/org/h2/dev/util/ImmutableArray.java new file mode 100644 index 0000000..2cdcfb2 --- /dev/null +++ b/h2/src/tools/org/h2/dev/util/ImmutableArray.java @@ -0,0 +1,172 @@ +/* + * 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.dev.util; + +import java.util.Arrays; +import java.util.Iterator; +import org.h2.mvstore.DataUtils; + +/** + * An immutable array. + * + * @param the type + */ +public final class ImmutableArray implements Iterable { + + private static final ImmutableArray EMPTY = new ImmutableArray<>( + new Object[0]); + + /** + * The array. + */ + private final K[] array; + + private ImmutableArray(K[] array) { + this.array = array; + } + + /** + * Get the entry at this index. + * + * @param index the index + * @return the entry + */ + public K get(int index) { + return array[index]; + } + + /** + * Get the length. + * + * @return the length + */ + public int length() { + return array.length; + } + + /** + * Set the entry at this index. + * + * @param index the index + * @param obj the object + * @return the new immutable array + */ + public ImmutableArray set(int index, K obj) { + K[] array = this.array.clone(); + array[index] = obj; + return new ImmutableArray<>(array); + } + + /** + * Insert an entry at this index. + * + * @param index the index + * @param obj the object + * @return the new immutable array + */ + public ImmutableArray insert(int index, K obj) { + int len = array.length + 1; + @SuppressWarnings("unchecked") + K[] array = (K[]) new Object[len]; + DataUtils.copyWithGap(this.array, array, this.array.length, index); + array[index] = obj; + return new ImmutableArray<>(array); + } + + /** + * Remove the entry at this index. + * + * @param index the index + * @return the new immutable array + */ + public ImmutableArray remove(int index) { + int len = array.length - 1; + @SuppressWarnings("unchecked") + K[] array = (K[]) new Object[len]; + DataUtils.copyExcept(this.array, array, this.array.length, index); + return new ImmutableArray<>(array); + } + + /** + * Get a sub-array. + * + * @param fromIndex the index of the first entry + * @param toIndex the end index, plus one + * @return the new immutable array + */ + public ImmutableArray subArray(int fromIndex, int toIndex) { + return new ImmutableArray<>(Arrays.copyOfRange(array, fromIndex, toIndex)); + } + + /** + * Create an immutable array. + * + * @param array the data + * @return the new immutable array + */ + @SafeVarargs + public static ImmutableArray create(K... array) { + return new ImmutableArray<>(array); + } + + /** + * Get the data. + * + * @return the data + */ + public K[] array() { + return array; + } + + @Override + public String toString() { + StringBuilder buff = new StringBuilder(); + for (K obj : this) { + buff.append(' ').append(obj); + } + return buff.toString(); + } + + /** + * Get an empty immutable array. + * + * @param the key type + * @return the array + */ + @SuppressWarnings("unchecked") + public static ImmutableArray empty() { + return (ImmutableArray) EMPTY; + } + + /** + * Get an iterator over all entries. + * + * @return the iterator + */ + @Override + public Iterator iterator() { + return new Iterator() { + + ImmutableArray a = ImmutableArray.this; + int index; + + @Override + public boolean hasNext() { + return index < a.length(); + } + + @Override + public K next() { + return a.get(index++); + } + + }; + } + +} + + + diff --git a/h2/src/tools/org/h2/dev/util/ImmutableArray2.java b/h2/src/tools/org/h2/dev/util/ImmutableArray2.java new file mode 100644 index 0000000..3e4130f --- /dev/null +++ b/h2/src/tools/org/h2/dev/util/ImmutableArray2.java @@ -0,0 +1,210 @@ +/* + * 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.dev.util; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicBoolean; +import org.h2.mvstore.DataUtils; + +/** + * An immutable array. + * + * @param the type + */ +public final class ImmutableArray2 implements Iterable { + + private static final ImmutableArray2 EMPTY = new ImmutableArray2<>( + new Object[0], 0); + + /** + * The array. + */ + private final K[] array; + private final int length; + private AtomicBoolean canExtend; + + private ImmutableArray2(K[] array, int len) { + this.array = array; + this.length = len; + } + + private ImmutableArray2(K[] array, int len, boolean canExtend) { + this.array = array; + this.length = len; + if (canExtend) { + this.canExtend = new AtomicBoolean(true); + } + } + + /** + * Get the entry at this index. + * + * @param index the index + * @return the entry + */ + public K get(int index) { + if (index >= length) { + throw new IndexOutOfBoundsException(); + } + return array[index]; + } + + /** + * Get the length. + * + * @return the length + */ + public int length() { + return length; + } + + /** + * Set the entry at this index. + * + * @param index the index + * @param obj the object + * @return the new immutable array + */ + public ImmutableArray2 set(int index, K obj) { + K[] a2 = Arrays.copyOf(array, length); + a2[index] = obj; + return new ImmutableArray2<>(a2, length); + } + + /** + * Insert an entry at this index. + * + * @param index the index + * @param obj the object + * @return the new immutable array + */ + public ImmutableArray2 insert(int index, K obj) { + int len = length + 1; + int newLen = len; + boolean extendable; + if (index == len - 1) { + AtomicBoolean x = canExtend; + if (x != null) { + // can set it to null early - we anyway + // reset the flag, so it is no longer useful + canExtend = null; + if (array.length > index && x.getAndSet(false)) { + array[index] = obj; + return new ImmutableArray2<>(array, len, true); + } + } + extendable = true; + newLen = len + 4; + } else { + extendable = false; + } + @SuppressWarnings("unchecked") + K[] a2 = (K[]) new Object[newLen]; + DataUtils.copyWithGap(array, a2, length, index); + a2[index] = obj; + return new ImmutableArray2<>(a2, len, extendable); + } + + /** + * Remove the entry at this index. + * + * @param index the index + * @return the new immutable array + */ + public ImmutableArray2 remove(int index) { + int len = length - 1; + if (index == len) { + return new ImmutableArray2<>(array, len); + } + @SuppressWarnings("unchecked") + K[] a2 = (K[]) new Object[len]; + DataUtils.copyExcept(array, a2, length, index); + return new ImmutableArray2<>(a2, len); + } + + /** + * Get a sub-array. + * + * @param fromIndex the index of the first entry + * @param toIndex the end index, plus one + * @return the new immutable array + */ + public ImmutableArray2 subArray(int fromIndex, int toIndex) { + int len = toIndex - fromIndex; + if (fromIndex == 0) { + return new ImmutableArray2<>(array, len); + } + return new ImmutableArray2<>(Arrays.copyOfRange(array, fromIndex, toIndex), len); + } + + /** + * Create an immutable array. + * + * @param array the data + * @return the new immutable array + */ + @SafeVarargs + public static ImmutableArray2 create(K... array) { + return new ImmutableArray2<>(array, array.length); + } + + /** + * Get the data. + * + * @return the data + */ + public K[] array() { + return array; + } + + @Override + public String toString() { + StringBuilder buff = new StringBuilder(); + for (K obj : this) { + buff.append(' ').append(obj); + } + return buff.toString(); + } + + /** + * Get an empty immutable array. + * + * @param the key type + * @return the array + */ + @SuppressWarnings("unchecked") + public static ImmutableArray2 empty() { + return (ImmutableArray2) EMPTY; + } + + /** + * Get an iterator over all entries. + * + * @return the iterator + */ + @Override + public Iterator iterator() { + return new Iterator() { + + ImmutableArray2 a = ImmutableArray2.this; + int index; + + @Override + public boolean hasNext() { + return index < a.length(); + } + + @Override + public K next() { + return a.get(index++); + } + + }; + } + +} + diff --git a/h2/src/tools/org/h2/dev/util/ImmutableArray3.java b/h2/src/tools/org/h2/dev/util/ImmutableArray3.java new file mode 100644 index 0000000..93cde7b --- /dev/null +++ b/h2/src/tools/org/h2/dev/util/ImmutableArray3.java @@ -0,0 +1,451 @@ +/* + * 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.dev.util; + +import java.util.Iterator; + +/** + * An immutable array. + * + * @param the type + */ +public abstract class ImmutableArray3 implements Iterable { + + private static final int MAX_LEVEL = 4; + + private static final ImmutableArray3 EMPTY = new Plain<>(new Object[0]); + + /** + * Get the length. + * + * @return the length + */ + public abstract int length(); + + /** + * Get the entry at this index. + * + * @param index the index + * @return the entry + */ + public abstract K get(int index); + + /** + * Set the entry at this index. + * + * @param index the index + * @param obj the object + * @return the new immutable array + */ + public abstract ImmutableArray3 set(int index, K obj); + + /** + * Insert an entry at this index. + * + * @param index the index + * @param obj the object + * @return the new immutable array + */ + public abstract ImmutableArray3 insert(int index, K obj); + + /** + * Remove the entry at this index. + * + * @param index the index + * @return the new immutable array + */ + public abstract ImmutableArray3 remove(int index); + + /** + * Get a sub-array. + * + * @param fromIndex the index of the first entry + * @param toIndex the end index, plus one + * @return the new immutable array + */ + public ImmutableArray3 subArray(int fromIndex, int toIndex) { + int len = toIndex - fromIndex; + @SuppressWarnings("unchecked") + K[] array = (K[]) new Object[len]; + for (int i = 0; i < len; i++) { + array[i] = get(fromIndex + i); + } + return new Plain<>(array); + } + + /** + * Create an immutable array. + * + * @param array the data + * @return the new immutable array + */ + @SafeVarargs + public static ImmutableArray3 create(K... array) { + return new Plain<>(array); + } + + /** + * Get the data. + * + * @return the data + */ + public K[] array() { + int len = length(); + @SuppressWarnings("unchecked") + K[] array = (K[]) new Object[len]; + for (int i = 0; i < len; i++) { + array[i] = get(i); + } + return array; + } + + /** + * Get the level of "abstraction". + * + * @return the level + */ + abstract int level(); + + @Override + public String toString() { + StringBuilder buff = new StringBuilder(); + for (K obj : this) { + buff.append(' ').append(obj); + } + return buff.toString(); + } + + /** + * Get an empty immutable array. + * + * @param the key type + * @return the array + */ + @SuppressWarnings("unchecked") + public static ImmutableArray3 empty() { + return (ImmutableArray3) EMPTY; + } + + /** + * Get an iterator over all entries. + * + * @return the iterator + */ + @Override + public Iterator iterator() { + return new Iterator() { + + ImmutableArray3 a = ImmutableArray3.this; + int index; + + @Override + public boolean hasNext() { + return index < a.length(); + } + + @Override + public K next() { + return a.get(index++); + } + + }; + } + + + /** + * An immutable array backed by an array. + * + * @param the type + */ + static class Plain extends ImmutableArray3 { + + /** + * The array. + */ + private final K[] array; + + public Plain(K[] array) { + this.array = array; + } + + @Override + public K get(int index) { + return array[index]; + } + + @Override + public int length() { + return array.length; + } + + @Override + public ImmutableArray3 set(int index, K obj) { + return new Set<>(this, index, obj); + } + + @Override + public ImmutableArray3 insert(int index, K obj) { + return new Insert<>(this, index, obj); + } + + @Override + public ImmutableArray3 remove(int index) { + return new Remove<>(this, index); + } + + /** + * Get a plain array with the given entry updated. + * + * @param the type + * @param base the base type + * @param index the index + * @param obj the object + * @return the immutable array + */ + static ImmutableArray3 set(ImmutableArray3 base, int index, K obj) { + int len = base.length(); + @SuppressWarnings("unchecked") + K[] array = (K[]) new Object[len]; + for (int i = 0; i < len; i++) { + array[i] = i == index ? obj : base.get(i); + } + return new Plain<>(array); + } + + /** + * Get a plain array with the given entry inserted. + * + * @param the type + * @param base the base type + * @param index the index + * @param obj the object + * @return the immutable array + */ + static ImmutableArray3 insert(ImmutableArray3 base, int index, K obj) { + int len = base.length() + 1; + @SuppressWarnings("unchecked") + K[] array = (K[]) new Object[len]; + for (int i = 0; i < len; i++) { + array[i] = i == index ? obj : i < index ? base.get(i) : base.get(i - 1); + } + return new Plain<>(array); + } + + /** + * Get a plain array with the given entry removed. + * + * @param the type + * @param base the base type + * @param index the index + * @return the immutable array + */ + static ImmutableArray3 remove(ImmutableArray3 base, int index) { + int len = base.length() - 1; + @SuppressWarnings("unchecked") + K[] array = (K[]) new Object[len]; + for (int i = 0; i < len; i++) { + array[i] = i < index ? base.get(i) : base.get(i + 1); + } + return new Plain<>(array); + } + + @Override + int level() { + return 0; + } + + } + + /** + * An immutable array backed by another immutable array, with one element + * changed. + * + * @param the type + */ + static class Set extends ImmutableArray3 { + + private final int index; + private final ImmutableArray3 base; + private final K obj; + + Set(ImmutableArray3 base, int index, K obj) { + this.base = base; + this.index = index; + this.obj = obj; + } + + @Override + public int length() { + return base.length(); + } + + @Override + public K get(int index) { + return this.index == index ? obj : base.get(index); + } + + @Override + public ImmutableArray3 set(int index, K obj) { + if (index == this.index) { + return new Set<>(base, index, obj); + } else if (level() < MAX_LEVEL) { + return new Set<>(this, index, obj); + } + return Plain.set(this, index, obj); + } + + @Override + public ImmutableArray3 insert(int index, K obj) { + if (level() < MAX_LEVEL) { + return new Insert<>(this, index, obj); + } + return Plain.insert(this, index, obj); + } + + @Override + public ImmutableArray3 remove(int index) { + if (level() < MAX_LEVEL) { + return new Remove<>(this, index); + } + return Plain.remove(this, index); + } + + @Override + int level() { + return base.level() + 1; + } + + } + + /** + * An immutable array backed by another immutable array, with one element + * added. + * + * @param the type + */ + static class Insert extends ImmutableArray3 { + + private final int index; + private final ImmutableArray3 base; + private final K obj; + + Insert(ImmutableArray3 base, int index, K obj) { + this.base = base; + this.index = index; + this.obj = obj; + } + + @Override + public ImmutableArray3 set(int index, K obj) { + if (level() < MAX_LEVEL) { + return new Set<>(this, index, obj); + } + return Plain.set(this, index, obj); + } + + @Override + public ImmutableArray3 insert(int index, K obj) { + if (level() < MAX_LEVEL) { + return new Insert<>(this, index, obj); + } + return Plain.insert(this, index, obj); + } + + @Override + public ImmutableArray3 remove(int index) { + if (index == this.index) { + return base; + } else if (level() < MAX_LEVEL) { + return new Remove<>(this, index); + } + return Plain.remove(this, index); + } + + @Override + public int length() { + return base.length() + 1; + } + + @Override + public K get(int index) { + if (index == this.index) { + return obj; + } else if (index < this.index) { + return base.get(index); + } + return base.get(index - 1); + } + + @Override + int level() { + return base.level() + 1; + } + + } + + /** + * An immutable array backed by another immutable array, with one element + * removed. + * + * @param the type + */ + static class Remove extends ImmutableArray3 { + + private final int index; + private final ImmutableArray3 base; + + Remove(ImmutableArray3 base, int index) { + this.base = base; + this.index = index; + } + + @Override + public ImmutableArray3 set(int index, K obj) { + if (level() < MAX_LEVEL) { + return new Set<>(this, index, obj); + } + return Plain.set(this, index, obj); + } + + @Override + public ImmutableArray3 insert(int index, K obj) { + if (index == this.index) { + return base.set(index, obj); + } else if (level() < MAX_LEVEL) { + return new Insert<>(this, index, obj); + } + return Plain.insert(this, index, obj); + } + + @Override + public ImmutableArray3 remove(int index) { + if (level() < MAX_LEVEL) { + return new Remove<>(this, index); + } + return Plain.remove(this, index); + } + + @Override + public int length() { + return base.length() - 1; + } + + @Override + public K get(int index) { + if (index < this.index) { + return base.get(index); + } + return base.get(index + 1); + } + + @Override + int level() { + return base.level() + 1; + } + + } + +} diff --git a/h2/src/tools/org/h2/dev/util/JavaProcessKiller.java b/h2/src/tools/org/h2/dev/util/JavaProcessKiller.java new file mode 100644 index 0000000..4a45487 --- /dev/null +++ b/h2/src/tools/org/h2/dev/util/JavaProcessKiller.java @@ -0,0 +1,126 @@ +/* + * 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.dev.util; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.Map.Entry; +import java.util.TreeMap; + +/** + * Allows to kill a certain Java process. + */ +public class JavaProcessKiller { + + /** + * Kill a certain Java process. The JDK (jps) needs to be in the path. + * + * @param args the Java process name as listed by jps -l. If not set the + * Java processes are listed + */ + + public static void main(String... args) { + new JavaProcessKiller().run(args); + } + + private void run(String... args) { + TreeMap map = getProcesses(); + System.out.println("Processes:"); + System.out.println(map); + if (args.length == 0) { + System.out.println("Kill a Java process"); + System.out.println("Usage: java " + getClass().getName() + " "); + return; + } + String processName = args[0]; + int killCount = 0; + for (Entry e : map.entrySet()) { + String name = e.getValue(); + if (name.equals(processName)) { + int pid = e.getKey(); + System.out.println("Killing pid " + pid + "..."); + // Windows + try { + exec("taskkill", "/pid", "" + pid, "/f"); + } catch (Exception e2) { + // ignore + } + // Unix + try { + exec("kill", "-9", "" + pid); + } catch (Exception e2) { + // ignore + } + System.out.println("done."); + killCount++; + } + } + if (killCount == 0) { + System.out.println("Process " + processName + " not found"); + } + map = getProcesses(); + System.out.println("Processes now:"); + System.out.println(map); + } + + private static TreeMap getProcesses() { + String processList = exec("jps", "-l"); + String[] processes = processList.split("\n"); + TreeMap map = new TreeMap<>(); + for (int i = 0; i < processes.length; i++) { + String p = processes[i].trim(); + int idx = p.indexOf(' '); + if (idx > 0) { + int pid = Integer.parseInt(p.substring(0, idx)); + String n = p.substring(idx + 1); + map.put(pid, n); + } + } + return map; + } + + private static String exec(String... args) { + ByteArrayOutputStream err = new ByteArrayOutputStream(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + Process p = Runtime.getRuntime().exec(args); + copyInThread(p.getInputStream(), out); + copyInThread(p.getErrorStream(), err); + p.waitFor(); + String e = new String(err.toByteArray(), StandardCharsets.UTF_8); + if (e.length() > 0) { + throw new RuntimeException(e); + } + String output = new String(out.toByteArray(), StandardCharsets.UTF_8); + return output; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void copyInThread(final InputStream in, final OutputStream out) { + new Thread("Stream copy") { + @Override + public void run() { + byte[] buffer = new byte[4096]; + try { + while (true) { + int len = in.read(buffer, 0, buffer.length); + if (len < 0) { + break; + } + out.write(buffer, 0, len); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }.start(); + } + +} diff --git a/h2/src/tools/org/h2/dev/util/Migrate.java b/h2/src/tools/org/h2/dev/util/Migrate.java new file mode 100644 index 0000000..b9e647a --- /dev/null +++ b/h2/src/tools/org/h2/dev/util/Migrate.java @@ -0,0 +1,249 @@ +/* + * 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.dev.util; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.RandomAccessFile; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.concurrent.TimeUnit; + +import org.h2.engine.Constants; +import org.h2.tools.RunScript; + +/** + * Migrate a H2 database version 1.1.x (page store not enabled) to 1.2.x (page + * store format). This will download the H2 jar file version 1.2.127 from + * maven.org if it doesn't exist, execute the Script tool (using Runtime.exec) + * to create a backup.sql script, rename the old database file to *.backup, + * created a new database (using the H2 jar file in the class path) using the + * Script tool, and then delete the backup.sql file. Most utility methods are + * copied from h2/src/tools/org/h2/build/BuildBase.java. + */ +public class Migrate { + + private static final String USER = "sa"; + private static final String PASSWORD = "sa"; + private static final File OLD_H2_FILE = new File("./h2-1.2.127.jar"); + private static final String DOWNLOAD_URL = + "https://repo1.maven.org/maven2/com/h2database/h2/1.2.127/h2-1.2.127.jar"; + private static final String CHECKSUM = + "056e784c7cf009483366ab9cd8d21d02fe47031a"; + private static final String TEMP_SCRIPT = "backup.sql"; + private final PrintStream sysOut = System.out; + private boolean quiet; + + /** + * Migrate databases. The user name and password are both "sa". + * + * @param args the path (default is the current directory) + * @throws Exception if conversion fails + */ + public static void main(String... args) throws Exception { + new Migrate().execute(new File(args.length == 1 ? args[0] : "."), true, + USER, PASSWORD, false); + } + + /** + * Migrate a database. + * + * @param file the database file (must end with .data.db) or directory + * @param recursive if the file parameter is in fact a directory (in which + * case the directory is scanned recursively) + * @param user the user name of the database + * @param password the password + * @param runQuiet to run in quiet mode + * @throws Exception if conversion fails + */ + public void execute(File file, boolean recursive, String user, + String password, boolean runQuiet) throws Exception { + String pathToJavaExe = getJavaExecutablePath(); + this.quiet = runQuiet; + if (file.isDirectory() && recursive) { + for (File f : file.listFiles()) { + execute(f, recursive, user, password, runQuiet); + } + return; + } + if (!file.getName().endsWith(Constants.SUFFIX_OLD_DATABASE_FILE)) { + return; + } + println("Migrating " + file.getName()); + if (!OLD_H2_FILE.exists()) { + download(OLD_H2_FILE.getAbsolutePath(), DOWNLOAD_URL, CHECKSUM); + } + String url = "jdbc:h2:" + file.getAbsolutePath(); + url = url.substring(0, url.length() - Constants.SUFFIX_OLD_DATABASE_FILE.length()); + exec(new String[] { + pathToJavaExe, + "-Xmx128m", + "-cp", OLD_H2_FILE.getAbsolutePath(), + "org.h2.tools.Script", + "-script", TEMP_SCRIPT, + "-url", url, + "-user", user, + "-password", password + }); + file.renameTo(new File(file.getAbsoluteFile() + ".backup")); + RunScript.execute(url, user, password, TEMP_SCRIPT, StandardCharsets.UTF_8, true); + new File(TEMP_SCRIPT).delete(); + } + + private static String getJavaExecutablePath() { + String pathToJava; + if (File.separator.equals("\\")) { + pathToJava = System.getProperty("java.home") + File.separator + + "bin" + File.separator + "java.exe"; + } else { + pathToJava = System.getProperty("java.home") + File.separator + + "bin" + File.separator + "java"; + } + if (!new File(pathToJava).exists()) { + // Fallback to old behaviour + pathToJava = "java"; + } + return pathToJava; + } + + private void download(String target, String fileURL, String sha1Checksum) { + File targetFile = new File(target); + if (targetFile.exists()) { + return; + } + mkdirs(targetFile.getAbsoluteFile().getParentFile()); + ByteArrayOutputStream buff = new ByteArrayOutputStream(); + try { + println("Downloading " + fileURL); + URL url = new URL(fileURL); + InputStream in = new BufferedInputStream(url.openStream()); + long last = System.nanoTime(); + int len = 0; + while (true) { + long now = System.nanoTime(); + if (now > last + TimeUnit.SECONDS.toNanos(1)) { + println("Downloaded " + len + " bytes"); + last = now; + } + int x = in.read(); + len++; + if (x < 0) { + break; + } + buff.write(x); + } + in.close(); + } catch (IOException e) { + throw new RuntimeException("Error downloading", e); + } + byte[] data = buff.toByteArray(); + String got = getSHA1(data); + if (sha1Checksum == null) { + println("SHA1 checksum: " + got); + } else { + if (!got.equals(sha1Checksum)) { + throw new RuntimeException("SHA1 checksum mismatch; got: " + got); + } + } + writeFile(targetFile, data); + } + + private static void mkdirs(File f) { + if (!f.exists()) { + if (!f.mkdirs()) { + throw new RuntimeException("Can not create directory " + f.getAbsolutePath()); + } + } + } + + private void println(String s) { + if (!quiet) { + sysOut.println(s); + } + } + + private void print(String s) { + if (!quiet) { + sysOut.print(s); + } + } + + private static String getSHA1(byte[] data) { + MessageDigest md; + try { + md = MessageDigest.getInstance("SHA-1"); + return convertBytesToString(md.digest(data)); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + private static String convertBytesToString(byte[] value) { + StringBuilder buff = new StringBuilder(value.length * 2); + for (byte c : value) { + int x = c & 0xff; + buff.append(Integer.toString(x >> 4, 16)). + append(Integer.toString(x & 0xf, 16)); + } + return buff.toString(); + } + + private static void writeFile(File file, byte[] data) { + try { + RandomAccessFile ra = new RandomAccessFile(file, "rw"); + ra.write(data); + ra.setLength(data.length); + ra.close(); + } catch (IOException e) { + throw new RuntimeException("Error writing to file " + file, e); + } + } + + private int exec(String[] command) { + try { + for (String c : command) { + print(c + " "); + } + println(""); + Process p = Runtime.getRuntime().exec(command); + copyInThread(p.getInputStream(), quiet ? null : sysOut); + copyInThread(p.getErrorStream(), quiet ? null : sysOut); + p.waitFor(); + return p.exitValue(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void copyInThread(final InputStream in, final OutputStream out) { + new Thread() { + @Override + public void run() { + try { + while (true) { + int x = in.read(); + if (x < 0) { + return; + } + if (out != null) { + out.write(x); + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }.start(); + } + +} diff --git a/h2/src/tools/org/h2/dev/util/ReaderInputStream.java b/h2/src/tools/org/h2/dev/util/ReaderInputStream.java new file mode 100644 index 0000000..1bb9c6a --- /dev/null +++ b/h2/src/tools/org/h2/dev/util/ReaderInputStream.java @@ -0,0 +1,67 @@ +/* + * 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.dev.util; + +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.nio.charset.StandardCharsets; + +import org.h2.engine.Constants; + +/** + * The reader input stream wraps a reader and convert the character to the UTF-8 + * format. + */ +public class ReaderInputStream extends InputStream { + + private final Reader reader; + private final char[] chars; + private final ByteArrayOutputStream out; + private final Writer writer; + private int pos; + private int remaining; + private byte[] buffer; + + public ReaderInputStream(Reader reader) { + chars = new char[Constants.IO_BUFFER_SIZE]; + this.reader = reader; + out = new ByteArrayOutputStream(Constants.IO_BUFFER_SIZE); + writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)); + } + + private void fillBuffer() throws IOException { + if (remaining == 0) { + pos = 0; + remaining = reader.read(chars, 0, Constants.IO_BUFFER_SIZE); + if (remaining < 0) { + return; + } + writer.write(chars, 0, remaining); + writer.flush(); + buffer = out.toByteArray(); + remaining = buffer.length; + out.reset(); + } + } + + @Override + public int read() throws IOException { + if (remaining == 0) { + fillBuffer(); + } + if (remaining < 0) { + return -1; + } + remaining--; + return buffer[pos++] & 0xff; + } + +} diff --git a/h2/src/tools/org/h2/dev/util/RemovePasswords.java b/h2/src/tools/org/h2/dev/util/RemovePasswords.java new file mode 100644 index 0000000..9b91592 --- /dev/null +++ b/h2/src/tools/org/h2/dev/util/RemovePasswords.java @@ -0,0 +1,85 @@ +/* + * 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.dev.util; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel.MapMode; +import java.nio.charset.StandardCharsets; + +import org.h2.engine.Constants; +import org.h2.security.SHA256; +import org.h2.store.fs.FileUtils; +import org.h2.util.MathUtils; +import org.h2.util.StringUtils; +import org.h2.util.Utils; + +/** + * A tool that removes passwords from an unencrypted database. + */ +public class RemovePasswords { + + /** + * Run the tool. + * + * @param args the command line arguments + */ + public static void main(String... args) throws Exception { + execute(args[0]); + } + + private static void execute(String fileName) throws IOException { + fileName = FileUtils.toRealPath(fileName); + RandomAccessFile f = new RandomAccessFile(fileName, "rw"); + long length = f.length(); + MappedByteBuffer buff = f.getChannel() + .map(MapMode.READ_WRITE, 0, length); + byte[] data = new byte[200]; + for (int i = 0; i < length - 200; i++) { + if (buff.get(i) != 'C' || buff.get(i + 1) != 'R' || + buff.get(i + 7) != 'U' || buff.get(i + 8) != 'S') { + continue; + } + buff.position(i); + buff.get(data); + String s = new String(data, StandardCharsets.UTF_8); + if (!s.startsWith("CREATE USER ")) { + continue; + } + int saltIndex = Utils.indexOf(s.getBytes(), "SALT ".getBytes(), 0); + if (saltIndex < 0) { + continue; + } + String userName = s.substring("CREATE USER ".length(), + s.indexOf("SALT ") - 1); + if (userName.startsWith("IF NOT EXISTS ")) { + userName = userName.substring("IF NOT EXISTS ".length()); + } + if (userName.startsWith("\"")) { + // TODO doesn't work for all cases ("" inside + // user name) + userName = userName.substring(1, userName.length() - 1); + } + System.out.println("User: " + userName); + byte[] userPasswordHash = SHA256.getKeyPasswordHash(userName, + "".toCharArray()); + byte[] salt = MathUtils.secureRandomBytes(Constants.SALT_LEN); + byte[] passwordHash = SHA256 + .getHashWithSalt(userPasswordHash, salt); + StringBuilder b = new StringBuilder(); + b.append("SALT '").append(StringUtils.convertBytesToHex(salt)) + .append("' HASH '") + .append(StringUtils.convertBytesToHex(passwordHash)) + .append('\''); + byte[] replacement = b.toString().getBytes(); + buff.position(i + saltIndex); + buff.put(replacement, 0, replacement.length); + } + f.close(); + } + +} diff --git a/h2/src/tools/org/h2/dev/util/ThreadDumpCleaner.java b/h2/src/tools/org/h2/dev/util/ThreadDumpCleaner.java new file mode 100644 index 0000000..0405a90 --- /dev/null +++ b/h2/src/tools/org/h2/dev/util/ThreadDumpCleaner.java @@ -0,0 +1,144 @@ +/* + * 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.dev.util; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.PrintWriter; +import java.io.Reader; +import java.util.ArrayList; +import java.util.regex.Pattern; + +/** + * A tool that removes uninteresting lines from stack traces. + */ +public class ThreadDumpCleaner { + + private static final String[] PATTERN = { + "\"Concurrent Mark-Sweep GC Thread\".*\n", + + "\"Exception Catcher Thread\".*\n", + + "JNI global references:.*\n\n", + + "\".*?\".*?\n java.lang.Thread.State:.*\n\n", + + "\".*?\".*\n\n", + + "\\$\\$YJP\\$\\$", + + "\"(Attach|Service|VM|GC|DestroyJavaVM|Signal|AWT|AppKit|C2 |Low Mem|" + + "process reaper|YJPAgent-).*?\"(?s).*?\n\n", + + " Locked ownable synchronizers:(?s).*?\n\n", + + "\".*?\".*?\n java.lang.Thread.State: (TIMED_)?WAITING(?s).*?\n\n", + + "\".*?\".*?\n java.lang.Thread.State:.*\n\t" + + "at sun.nio.ch.KQueueArrayWrapper.kevent0(?s).*?\n\n", + + "\".*?\".*?\n java.lang.Thread.State:.*\n\t" + + "at java.io.FileInputStream.readBytes(?s).*?\n\n", + + "\".*?\".*?\n java.lang.Thread.State:.*\n\t" + + "at sun.nio.ch.ServerSocketChannelImpl.accept(?s).*?\n\n", + + "\".*?\".*?\n java.lang.Thread.State:.*\n\t" + + "at java.net.DualStackPlainSocketImpl.accept0(?s).*\n\n", + + "\".*?\".*?\n java.lang.Thread.State:.*\n\t" + + "at sun.nio.ch.EPollArrayWrapper.epollWait(?s).*?\n\n", + + "\".*?\".*?\n java.lang.Thread.State:.*\n\t" + + "at java.lang.Object.wait(?s).*?\n\n", + + "\".*?\".*?\n java.lang.Thread.State:.*\n\t" + + "at java.net.PlainSocketImpl.socketAccept(?s).*?\n\n", + + "\".*?\".*?\n java.lang.Thread.State:.*\n\t" + + "at java.net.SocketInputStream.socketRead0(?s).*?\n\n", + + "\".*?\".*?\n java.lang.Thread.State:.*\n\t" + + "at sun.nio.ch.WindowsSelectorImpl\\$SubSelector.poll0(?s).*?\n\n", + + }; + + private final ArrayList patterns = new ArrayList<>(); + + { + for (String s : PATTERN) { + patterns.add(Pattern.compile(s)); + } + } + + /** + * Run the tool. + * + * @param args the command line arguments + */ + public static void main(String... args) throws IOException { + String inFile = null, outFile = null; + for (int i = 0; i < args.length; i++) { + if (args[i].equals("-in")) { + inFile = args[++i]; + } else if (args[i].equals("-out")) { + outFile = args[++i]; + } + } + if (args.length == 0) { + outFile = "-"; + } + if (outFile == null) { + outFile = inFile + ".clean.txt"; + } + PrintWriter writer; + if ("-".equals(outFile)) { + writer = new PrintWriter(System.out); + } else { + writer = new PrintWriter(new BufferedWriter(new FileWriter(outFile))); + } + Reader r; + if (inFile != null) { + r = new FileReader(inFile); + } else { + r = new InputStreamReader(System.in); + } + new ThreadDumpCleaner().run( + new LineNumberReader(new BufferedReader(r)), + writer); + writer.close(); + r.close(); + } + + private void run(LineNumberReader reader, PrintWriter writer) throws IOException { + StringBuilder buff = new StringBuilder(); + while (true) { + String line = reader.readLine(); + if (line == null) { + break; + } + buff.append(line).append('\n'); + if (line.trim().length() == 0) { + writer.print(filter(buff.toString())); + buff = new StringBuilder(); + } + } + writer.println(filter(buff.toString())); + } + + private String filter(String s) { + for (Pattern p : patterns) { + s = p.matcher(s).replaceAll(""); + } + return s; + } + +} diff --git a/h2/src/tools/org/h2/dev/util/ThreadDumpFilter.java b/h2/src/tools/org/h2/dev/util/ThreadDumpFilter.java new file mode 100644 index 0000000..acac8b9 --- /dev/null +++ b/h2/src/tools/org/h2/dev/util/ThreadDumpFilter.java @@ -0,0 +1,42 @@ +/* + * 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.dev.util; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.LineNumberReader; +import java.io.PrintWriter; + +/** + * Filter full thread dumps from a log file. + */ +public class ThreadDumpFilter { + + /** + * Usage: java ThreadDumpFilter <log.txt >threadDump.txt + * + * @param a the file name + */ + public static void main(String... a) throws Exception { + String fileName = a[0]; + LineNumberReader in = new LineNumberReader( + new BufferedReader(new FileReader(fileName))); + PrintWriter writer = new PrintWriter(new BufferedWriter( + new FileWriter(fileName + ".filtered.txt"))); + for (String s; (s = in.readLine()) != null;) { + if (s.startsWith("Full thread")) { + do { + writer.println(s); + s = in.readLine(); + } while(s != null && (s.length() == 0 || " \t\"".indexOf(s.charAt(0)) >= 0)); + } + } + writer.close(); + in.close(); + } +} diff --git a/h2/src/tools/org/h2/dev/util/ThreadDumpInliner.java b/h2/src/tools/org/h2/dev/util/ThreadDumpInliner.java new file mode 100644 index 0000000..0ab1755 --- /dev/null +++ b/h2/src/tools/org/h2/dev/util/ThreadDumpInliner.java @@ -0,0 +1,55 @@ +/* + * 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.dev.util; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.LineNumberReader; +import java.io.PrintWriter; + +/** + * Convert a list of thread dumps into one line per thread. + */ +public class ThreadDumpInliner { + + /** + * Usage: java ThreadDumpInliner threadDump.txt + * + * @param a the file name + */ + public static void main(String... a) throws Exception { + String fileName = a[0]; + LineNumberReader in = new LineNumberReader( + new BufferedReader(new FileReader(fileName))); + PrintWriter writer = new PrintWriter(new BufferedWriter( + new FileWriter(fileName + ".lines.txt"))); + + StringBuilder buff = new StringBuilder(); + for (String s; (s = in.readLine()) != null;) { + if (s.trim().length() == 0) { + continue; + } + if (s.startsWith(" ") || s.startsWith("\t")) { + buff.append('\t').append(s.trim()); + } else { + printNonEmpty(writer, buff.toString()); + buff = new StringBuilder(s); + } + } + printNonEmpty(writer, buff.toString()); + in.close(); + writer.close(); + } + + private static void printNonEmpty(PrintWriter writer, String s) { + s = s.trim(); + if (!s.isEmpty()) { + writer.println(s); + } + } +} diff --git a/h2/src/tools/org/h2/dev/util/package.html b/h2/src/tools/org/h2/dev/util/package.html new file mode 100644 index 0000000..39f23a4 --- /dev/null +++ b/h2/src/tools/org/h2/dev/util/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +Utility classes that are currently not used in the database engine. + +

    \ No newline at end of file diff --git a/h2/src/tools/org/h2/jcr/Railroads.java b/h2/src/tools/org/h2/jcr/Railroads.java new file mode 100644 index 0000000..21d167b --- /dev/null +++ b/h2/src/tools/org/h2/jcr/Railroads.java @@ -0,0 +1,126 @@ +/* + * 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.jcr; + +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import org.h2.bnf.Bnf; +import org.h2.build.BuildBase; +import org.h2.build.doc.BnfRailroad; +import org.h2.build.doc.BnfSyntax; +import org.h2.build.doc.RailroadImages; +import org.h2.server.web.PageParser; +import org.h2.tools.Csv; +import org.h2.util.StringUtils; + +/** + * JCR 2.0 / SQL-2 railroad generator. + */ +public class Railroads { + + private Bnf bnf; + private final HashMap session = new HashMap<>(); + + /** + * This method is called when executing this application from the command + * line. + * + * @param args the command line parameters + * @throws Exception on failure + */ + public static void main(String... args) throws Exception { + new Railroads().process(); + } + + private void process() throws Exception { + RailroadImages.main(); + bnf = Bnf.getInstance(getReader()); + Csv csv = new Csv(); + csv.setLineCommentCharacter('#'); + ResultSet rs = csv.read(getReader(), null); + map("grammar", rs, true); + processHtml("jcr-sql2.html"); + } + + private void processHtml(String fileName) throws Exception { + String source = "src/tools/org/h2/jcr/"; + String target = "docs/html/"; + byte[] s = BuildBase.readFile(Paths.get(source + "stylesheet.css")); + BuildBase.writeFile(Paths.get(target + "stylesheet.css"), s); + Path inFile = Paths.get(source + fileName); + Path outFile = Paths.get(target + fileName); + Files.createDirectories(outFile.getParent()); + byte[] bytes = Files.readAllBytes(inFile) ; + if (fileName.endsWith(".html")) { + String page = new String(bytes); + page = PageParser.parse(page, session); + bytes = page.getBytes(); + } + Files.write(outFile, bytes); + } + + private static Reader getReader() { + return new InputStreamReader(Railroads.class.getResourceAsStream("help.csv")); + } + + private void map(String key, ResultSet rs, boolean railroads) throws Exception { + ArrayList> list; + list = new ArrayList<>(); + while (rs.next()) { + HashMap map = new HashMap<>(); + ResultSetMetaData meta = rs.getMetaData(); + for (int i = 0; i < meta.getColumnCount(); i++) { + String k = StringUtils.toLowerEnglish(meta.getColumnLabel(i + 1)); + String value = rs.getString(i + 1); + value = value.trim(); + map.put(k, PageParser.escapeHtml(value)); + } + String topic = rs.getString("TOPIC"); + String syntax = rs.getString("SYNTAX").trim(); + if (railroads) { + BnfRailroad r = new BnfRailroad(); + String railroad = r.getHtml(bnf, syntax); + map.put("railroad", railroad); + } + BnfSyntax visitor = new BnfSyntax(); + String syntaxHtml = visitor.getHtml(bnf, syntax); + map.put("syntax", syntaxHtml); + // remove newlines in the regular text + String text = map.get("text"); + if (text != null) { + // text is enclosed in

    ..

    so this works. + text = StringUtils.replaceAll(text, "

    ", "

    "); + text = StringUtils.replaceAll(text, "
    ", " "); + map.put("text", text); + } + + String link = topic.toLowerCase(); + link = link.replace(' ', '_'); + // link = StringUtils.replaceAll(link, "_", ""); + link = link.replace('@', '_'); + map.put("link", StringUtils.urlEncode(link)); + list.add(map); + } + session.put(key, list); + int div = 3; + int part = (list.size() + div - 1) / div; + for (int i = 0, start = 0; i < div; i++, start += part) { + List> listThird = list.subList(start, + Math.min(start + part, list.size())); + session.put(key + "-" + i, listThird); + } + rs.close(); + } + +} diff --git a/h2/src/tools/org/h2/jcr/help.csv b/h2/src/tools/org/h2/jcr/help.csv new file mode 100644 index 0000000..2040b35 --- /dev/null +++ b/h2/src/tools/org/h2/jcr/help.csv @@ -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) + +"SECTION","TOPIC","SYNTAX","TEXT" +"Grammar","Query"," +SELECT { * | { column [ , ... ] } } FROM { selector [ join ... ] } +[ WHERE constraint ] [ ORDER BY { ordering [ , ... ] } ] +"," +" + +"Grammar","Column"," +{ [ selectorName . ] propertyName [ AS columnName ] } | { selectorName . * } +"," +" + +"Grammar","Selector"," +nodeTypeName [ AS selectorName ] +"," +" + +"Grammar","Join"," +{ INNER | { LEFT | RIGHT } OUTER } JOIN rightSelector ON +{ selectorName . propertyName = joinSelectorName . joinPropertyName } + | { ISSAMENODE( selectorName , joinSelectorName [ , selectorPathName ] ) } + | { ISCHILDNODE( childSelectorName , parentSelectorName ) } + | { ISDESCENDANTNODE( descendantSelectorName , ancestorSelectorName ) } +"," +" + +"Grammar","Constraint"," +andCondition [ { OR andCondition } [...] ] +"," +" + +"Grammar","And Condition"," +condition [ { AND condition } [...] ] +"," +" + +"Grammar","Condition"," +comparison | NOT constraint | ( constraint ) + | [ selectorName . ] propertyName IS [ NOT ] NULL + | CONTAINS( { { [ selectorName . ] propertyName } | { selectorName . * } } , fulltextSearchExpression ) + | { ISSAMENODE | ISCHILDNODE | ISDESCENDANTNODE } ( [ selectorName , ] PathName ) +"," +" + +"Grammar","Comparison"," +dynamicOperand { = | <> | < | <= | > | >= | LIKE } staticOperand +"," +" + +"Grammar","Fulltext Search Expression"," +' anythingExceptSingleQuote ' | $ bindVariableName +"," +" + +"Grammar","Static Operand"," +literal + | $ bindVariableName + | CAST ( literal AS { STRING | BINARY | DATE | LONG | DOUBLE | DECIMAL | BOOLEAN | NAME | PATH | REFERENCE | WEAKREFERENCE | URI } ) +"," +" + +"Grammar","Literal"," +' anythingExceptSingleQuote ' + | "" anythingExceptDoubleQuote "" + | numberLiteral +"," +" + +"Grammar","Number Literal"," +[ + | - ] { { number [ . number ] } | { . number } } [ E [ + | - ] expNumber [...] ] ] +"," +" + +"Grammar","Number"," +0-9 [...] +"," +" + +"Grammar","Dynamic Operand"," +[ selectorName . ] propertyName + | LENGTH( [ selectorName . ] propertyName ) + | { NAME | LOCALNAME | SCORE } ( [ selectorName ] ) + | { LOWER | UPPER } ( dynamicOperand ) +"," +" + +"Grammar","Ordering"," +simpleName [ ASC | DESC ] +"," +" + +"Grammar","Name"," +simpleName | '[' quotedName ']' +"," +" \ No newline at end of file diff --git a/h2/src/tools/org/h2/jcr/jcr-sql2.html b/h2/src/tools/org/h2/jcr/jcr-sql2.html new file mode 100644 index 0000000..4cf12dc --- /dev/null +++ b/h2/src/tools/org/h2/jcr/jcr-sql2.html @@ -0,0 +1,72 @@ + + + + +JCR 2.0 SQL-2 Grammar + + + +

    JCR 2.0 SQL-2 Grammar

    + + + + + + +
    + + ${item.topic}
    +
    +
    + + ${item.topic}
    +
    +
    + + ${item.topic}
    +
    +
    + + +
    +

    +These railroad diagrams are based on the +JCR 2.0 specification. +

    +The diagrams are created with a small Java program +and this BNF. The program uses the BNF parser / converter +of the H2 database engine. +

    +Please send feedback to the Jackrabbit User List. +

    + + +
    +

    ${item.topic}

    + +${item.railroad} + + +

    ${item.text}

    + +
    + + diff --git a/h2/src/tools/org/h2/jcr/package.html b/h2/src/tools/org/h2/jcr/package.html new file mode 100644 index 0000000..225645d --- /dev/null +++ b/h2/src/tools/org/h2/jcr/package.html @@ -0,0 +1,14 @@ + + + + +Javadoc package documentation +

    + +Utility classes related to the JCR API. + +

    \ No newline at end of file diff --git a/h2/src/tools/org/h2/jcr/stylesheet.css b/h2/src/tools/org/h2/jcr/stylesheet.css new file mode 100644 index 0000000..47ea40c --- /dev/null +++ b/h2/src/tools/org/h2/jcr/stylesheet.css @@ -0,0 +1,340 @@ +/* + * 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 + */ + +td, input, select, textarea, body, code, pre, td, th { + font: 9pt/130% Tahoma, Arial, Helvetica, sans-serif; + font-weight: normal; +} + +h1, h2, h3, h4, h5 { + font: 9pt Tahoma, Arial, Helvetica, sans-serif; + font-weight: bold; +} + +td, input, select, textarea, body, code, pre { + font-size: 9pt; +} + +pre { + background-color: #ece9d8; + border: 1px solid rgb(172, 168, 153); + padding: 4px; +} + +code { + background-color: #ece9d8; + padding: 0px 2px; +} + +img { + border: 0px; +} + +body { + margin: 20px; + max-width: 800px; +} + +h1 { + background-color: #0000bb; + padding: 2px 4px 2px 4px; + color: #fff; + font-size: 15pt; + line-height: normal; +} + +h2 { + font-size: 13pt; + margin-top: 1.5em; +} + +h3 { + font-size: 11pt; + margin-top: 1.5em; +} + +h4 { + font-size: 9pt; + margin-top: 1.5em; +} + +hr { + color: #CCC; + background-color: #CCC; + height: 1px; + border: 0px solid blue; +} + +table { + background-color: #ffffff; + border-collapse: collapse; + border: 1px solid #aca899; +} + +th { + text-align: left; + background-color: #ece9d8; + border: 1px solid #aca899; + padding: 2px; +} + +td { + background-color: #ffffff; + text-align: left; + vertical-align: top; + border: 1px solid #aca899; + padding: 2px; +} + +form { +} + +ul, ol { + list-style-position: outside; + padding-left: 20px; +} + +li { + margin-top: 2px; +} + +a { + text-decoration: none; + color: #0000ff; +} + +a:hover { + text-decoration: underline; +} + +em.u { + text-decoration: underline; + font-style: normal; +} + +.menu { + margin: 10px 10px 10px 10px; +} + +table.search { + width: 100%; + border: 0px; +} + +tr.search { + border: 0px; +} + +td.search { + border: 0px; + padding: 2px 0px 2px 10px; +} + +td.searchKeyword { + border: 0px; + padding: 0px 0px 0px 2px; +} + +td.searchKeyword a { + text-decoration: none; + color: #000000; +} + +td.searchKeyword a:hover { + text-decoration: underline; +} + +td.searchLink { + border: 0px; + padding: 0px 0px 0px 32px; + text-indent: -16px; +} + +td.searchLink a { + text-decoration: none; + color: #0000ff; +} + +td.searchLink a:hover { + text-decoration: underline; +} + +table.nav { + border: 0px; +} + +tr.nav { + border: 0px; +} + +td.nav { + border: 0px; +} + +table.content { + width: 100%; + height: 100%; + border: 0px; +} + +tr.content { + border:0px; +} + +td.content { + border:0px; +} + +.contentDiv { + margin:10px; +} + +.content { + margin: 10px 10px 10px 0px; +} + +.screenshot { + border: 1px outset #800; + padding: 10px; + margin: 10px 0px; +} + +.compareFeature { +} + +.compareY { + color: #050; +} + +.compareN { + color: #800; +} + +table.index { + width: 100%; + border: 0px; + padding: 0px; + margin: 0px; + border: 0px none; + border-collapse: collapse; +} + +/* width: 570px; + width: 190px; + */ + +td.index { + width: 33%; + border: 0px; + padding: 0px; + margin: 0px; + border: 0px none; + border-collapse: collapse; + vertical-align: top; +} + +.railroad { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; +} + +.c { + padding: 1px 3px; + margin: 0px 0px; + border: 2px solid; + -moz-border-radius: 0.4em; + -webkit-border-radius: 0.4em; + -khtml-border-radius: 0.4em; + border-radius: 0.4em; + background-color: #fff; +} + +.ts { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; + width: 16px; + height: 24px; + background-image: url(images/div-ts.png); + background-size: 16px 512px; +} + +.ls { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; + width: 16px; + height: 24px; + background-image: url(images/div-ls.png); + background-size: 16px 512px; +} + +.ks { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; + width: 16px; + height: 24px; + background-image: url(images/div-ks.png); + background-size: 16px 512px; +} + +.te { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; + width: 16px; + height: 24px; + background-image: url(images/div-te.png); + background-size: 16px 512px; +} + +.le { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; + width: 16px; + height: 24px; + background-image: url(images/div-le.png); + background-size: 16px 512px; +} + +.ke { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; + width: 16px; + height: 24px; + background-image: url(images/div-ke.png); + background-size: 16px 512px; +} + +.d { + border: 0px; + padding: 0px; + margin: 0px; + border-collapse: collapse; + vertical-align: top; + min-width: 16px; + height: 24px; + background-image: url(images/div-d.png); + background-size: 1024px 512px; +} diff --git a/src/main/java/portfopol/refactoring/basic/myData.java b/src/main/java/portfopol/refactoring/basic/MyData.java similarity index 77% rename from src/main/java/portfopol/refactoring/basic/myData.java rename to src/main/java/portfopol/refactoring/basic/MyData.java index 06a9657..7c82f70 100644 --- a/src/main/java/portfopol/refactoring/basic/myData.java +++ b/src/main/java/portfopol/refactoring/basic/MyData.java @@ -3,7 +3,8 @@ import lombok.Data; @Data -public class myData { +public class MyData { + private Long userId; private String data; private int year; private int month; diff --git a/src/main/java/portfopol/refactoring/basic/myUser.java b/src/main/java/portfopol/refactoring/basic/MyUser.java similarity index 87% rename from src/main/java/portfopol/refactoring/basic/myUser.java rename to src/main/java/portfopol/refactoring/basic/MyUser.java index 5be24e4..1b7070b 100644 --- a/src/main/java/portfopol/refactoring/basic/myUser.java +++ b/src/main/java/portfopol/refactoring/basic/MyUser.java @@ -4,7 +4,7 @@ import lombok.Setter; @Getter@Setter -public class myUser { +public class MyUser { private Long userId; private String userName; diff --git a/src/main/java/portfopol/refactoring/basic/userRepository/UserRepository.java b/src/main/java/portfopol/refactoring/basic/userRepository/UserRepository.java new file mode 100644 index 0000000..8a163df --- /dev/null +++ b/src/main/java/portfopol/refactoring/basic/userRepository/UserRepository.java @@ -0,0 +1,15 @@ +package portfopol.refactoring.basic.userRepository; + +import portfopol.refactoring.basic.MyUser; + +import java.util.List; +import java.util.Optional; + +public interface UserRepository { + + MyUser save(MyUser myUser); + Optional findById(Long id); + Optional findByName(String name); + List findAll(); + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8b13789..62ec24e 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,3 @@ - +spring.datasource.url=jdbc:h2:tcp://localhost/~/codes/portpofol/test +spring.datasource.driver-class-name=org.h2.Driver +spring.datasource.username=sa diff --git a/test.mv.db b/test.mv.db new file mode 100644 index 0000000000000000000000000000000000000000..4e645bf1c15e52e4be1bd4f98fa8b1c338323ed0 GIT binary patch literal 24576 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

    tnDo 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + Application + + + + + Application + + + + + + + + + + + + + + H2 Database + + H2 Server + + + + Application + + + + + + + + + + H2 Database + + + + + + + + + + + + + H2 Database + + H2 Server + Embedded + Remote + Mixed Mode + + + Application + + + diff --git a/h2/src/docsrc/images/console-2.png b/h2/src/docsrc/images/console-2.png new file mode 100644 index 0000000000000000000000000000000000000000..a71364401b16e14ad5bf1104ac66c93d65e04905 GIT binary patch literal 68604 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 literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/images/console.png b/h2/src/docsrc/images/console.png new file mode 100644 index 0000000000000000000000000000000000000000..39a7ef53db1371059b836bf23c01088d9fef53ef GIT binary patch literal 29569 zcmXtf1z4L+(>1gdmj-utr^U6n71!d$-QC?SxYOY7?(P&X#WlFQ^QX`If4LG!?%dhk z*`1l4J#!{PQC<=S5g!o>3JOJ9N=z9F>H`kseLXxZ)j4R0I{ z`HWyM^}`8r|Kh(Fv2ugYESPQ&Bf3NlmRdz)yiVxz`Q_1983norN+uO*ZJ!9v-|er zsolegv=?Z%H9Es^) z_p{P@uCu1RbM~`p;R8tx`+^ZLsK2MN0;t54@-Xix7yJLafE#SxB9|uRlD69y9D$t{ zy7KR;VNfs-Y0f^A^=F^haInQYAMwea9M{i0pMLL9xinUwCF>eFnE}4-zXqiQziY2{ z8PMbWYaFHd->(r>TEoA7&$s#4%XSvSx>_u-zTvqN4Y}%ulZnyfpJsV z@kPVhfx?M9$PfSBC)3j)PTeP5)`*kn4%4T~%nPM_O2+;Z(p-Lc<7*^!x0H0CPEp&5 zQMz;5SO}!WScJ5ulfT~z?%4qM8|HWt(*3pHIlqJZg310T(B-$5CaU$r{s3aUX@;+u z9;_mq%TY|6$w>bMFb}Xm!}yrLxFppkmVn}7Mnca*aTo+ZHF>%#vV^oB5!}PRO#R*2 zT1*yGgoq?ICSrj22V+Xj8o2}}a()$16A&Y|Wcc66gClTz8;WfJbadehclL9^fxuW@ zmKfs0X$k^RxCDJUUXkO^ytN+oRqF2zXksF?c}v)%cTxZ=jgS=bJaG|`L4C>ubKKS< zU&o*SH@VC8OsI>Mpml8N414(SnO80N1rMuJ4ZFD~{U}XyAZ)%Eh1xvw9Ft+5Xs`7- z_4fhdt@ud$H#D)>;cOjWFs2vg_@YOZpL=G`!$q`-rrDEz=H*Z)5qkYqHXiB1SpG^S9+ z&mWVjvGIiG$`O%9478bBDQ=$ET6F9znWGXNWc{}y=*ZXUUuTGaiZO*JNgdFkZxQ3f zwo|6;lH*3O)~Wu{FEuSmJ-TX~NA7$;1@q7Z8W$uv0+{~4y46Z@TZYg^hD!PWx$K<{ zN$g(d>~vy76lmT)zDooMqnnqv0qUUL&GZCyjOHw}D_iHF|`7;MbUV1Q*Xv zcFQ{pqT)csC>C#u_C<$Kf(}Oq0YdEqAkvU8i}wqq#RuS z{MGU$L43wj&}e+!7M zvd-uMP-@{N=<^;ez!+yu2K(s9TP>nDBY}UKq(;P9XF(cpOl~sdtCasO8zO~T?v!e! zMj82fuT!lEt0ADu(BOs!K9Cwu(GD%l-+V#$GYpJyk7@m3PC0S(fab_C@H1@rLYYTa zhk#B3$PHBnJ+Dx6Ai|WC}7t{Wso{k zbc?j%3@6jV!oq1^UWk#q9L1XNXL$;F4kJRQ=%zWNMgfxY{lefG&P+rbzNBMP^uj*) zAJf`r8{N4v1YC7?8-l2W+?zBQlCgxB7ztv%hPBg;RvJQE|3*+#6Auy%n=RL<4p&9& zJoS|QHQA)-G3Jt*3R18oHt=TBz{Jh>$LDjez2w zY2mCg;v23-X74OzzD<}cV?z=mlekDH%!jws#ZEnzl_r~=;#^PCm)8w(osoG zE!i)3MU#p8Ep?GHmU8wdwP6AR#n^#i`a_;cXusUBl%ql*u~GuO zBWCQ@B*h*OiThU4j1<_@=5nmrU=GmtzWkJ&oD5jxds8uf_Aok{N#A;7~U zx3GXb1$CIA!}_E8w+3zR4eC8=wbuOoNi{DX8QnWKZzEgQ$ZeaysU*e~Q3r&=vY40< zbK2RO-X6iGu-I)*li3ZpOpcBH+wQ*S@TUVPBn;}8L=xaRK+`LBs-L7`duvqH)v4E% zL#({>*(QK}><6`6jv!=<5(#1}lAjd-xpqzjXT#5@E=XaFaL+<`Hhfx!fJoJmV?MVt zvW`_~g!g1Mp?!?7f2WTNGW_0H2>aO>z8Z{&g}=4WVOGYjX;+M7TF zP?ugoht98pf&L~N;;y&0^vCN=`*xIEbjbYg+{`KLM}Pfo(Bls+j1bacTffBK97c}s zN%;|7lr=$~nVhX@3)dK9G-&4e=fFYe4$k*a3BYJqo2K^xGGz^Y`o>emTCA%txV6(1 zSj2H*kJtOxzxmE(HSwoupg~e1l+XPuG?5>Y zll3@ltxadhx!&KJQ$@p~1;WI|#j~7#^JMwGJ+|mnLFJKRAVv*t>uV%L8wfEu0%Qi` z7aLQKT=HvFRaGe`$?@2FJY_3)M8Osf#MIPVc+)RGJ*~yf)s4k(4UkcE?9>DrA`ZoA%rb>r5*Xk?F6d{H z6@DQ_1Q4OwWJ7VQM3H6*TKL@{;XNYMyfT>|CpgyOam_wnr^Up7J;83<=>lV6Vd*B> zu}WMjBmd#vgP(KVcV)b?nK-ZY$McVD)}J4m*IWq>4-b9Oe>ShI#3M6diOO-$!-x`{ zAb|8QjB#z8lz>t8C~1J9ipI$U7R~jU`4zEUcvH-v&MmyhLn55A6HN*o!+++B6}?MA zh$XKFYqg?f2lO01TW{{DUZQalDf;ibAQy#yU9Uh3U3dFZSF&5P{+G_PFmN9lUBQD(4MTYqcVA!g|myBBo?~ce2F)dNXI! zb;@R`3LS;>p0T>hs4Qv|4#yMG%}`uK9ficq-Q%|L37soyun%JI6DFy;q5WGI81U)F z<^a@6cVq#sh!lb3RA5tcu(zXtYQFE~AdUybx=TxE&>SPgm#CFLj)g9jYp@Xe+$e>j z5EMx>@46^q)@oJ06~26i58T{f-mlb49apIu{&3{#b1)4`1KZ^J7;SXy3Z1KiCeAoEKK?ka|CVr3y_znCaz3)tSPm_-!+d`I=nYLN_O{xT z;Oy^9^BH0O?#Fqv$F*sW$6h+;aQE{WOY7CxTzD2HWxc3^oTFiF_zRc?n_3}f#ADwl z>gx4jPC>}zUwnbjF}9I%;@V#HRNd9BrPxI0LHEAfgk|v@+3pCKkRyvES5s4?u07bK zWnkz(PTo7yc!pb$kmv0BJkqyd=X>Sbco~B=1Hb2*=&;4HukyF~^QgI)lDev7rW7VB zW~yZZQl$ZJB_3`v%n#dR%Y2fFO*n`pQi@pVG7RN`KmkAMO}8%HUp$P>ifc_L-)Fo1 z^vTaJFlI)F^=-nd-kvO`c#0!mpQlVG(nz@7YkvF1nNH(={r2{{#gq&c{AeLdk^uBk~M!s<}Rgbgi z$ZR7A*b0H)dzYKky%w|r>r zvnhp_#kYf*{EoY2t5ugpU1(+i#L||1@~o{sm!yT%WrI%4b&{hDeEA>!8Z906l8|uO z&COSjjLA7_e)f9`wJLUl>4Jc}Ty#{W@gVQd^EOPxmLd4P;h!jjbSM!|pR=~ryiB_C zXlLi=3;6E+Cr$hJuOAl|Y;>eK=L057#t;^oDAbrBHoDap;(gHz2mL5;J70)Q?9E?# zpd3yjEltUTu&SZy9^Smp8)sGhI^l=LY5lindoYUUdg3ee>DCN()Ti7joXw;7(?Z8m zYb(oVyh+&~t47Sm=B2n=5uX?OlbFO9Wa#o1vEK~{gHzHk;m)?G$jU@Mb zUfK9C8$+}b_M_exfjc}_bD)n2aQ=G9TYPb^?<7Zb-o=*`C$zrTjYHyn0lhO6W95X_ zAF@1IRH#S$b3S%Ru9dY+s|2NvG!kCK>kBP4c|hITs4bu};^(d_IwQW;=s@xiSDGh) z4x+wCR*83MYRs|X19yUS$w-xtaJzSqoj$48LncI2E~in4#M3TwjQvVkwpS62Z>`j6Vr)kMt&-^%B{91hhYfPK^$aK`Q`D6y^-Y^N+AwU%PRx!4Y@%UvAO zHDyHO-Hld@um$bA2{XoK_cRXK-W4O#9hgZ2=%oz}^tRnT{C=;;=(8&5~WE2zVRLF6B`V{^uuYPB3C=4m9c3Xv z3iO}aM`Cr=q7;q8Wk^P>d}PThchjC_eBW@Da(Sl4IsH`X7?OOe*e0G-AW|;Juu}d7 zUg7jvy&Pwz$wFY8IggEak@^`u`7;b47thII zLewM51l+fO{i$PS^f^hf&gqAsy5Q$;-)gefS9BXB|L}4AqMp!-3_-4JmTg_5Pev;ABugbD zfdq))FPggGLiJZDYfc7EZ5l1fh=F4pfrSxieqA!)N%M5Q8F9N?>eG1)DZI@{$debD zWgr;?u9i$F@102XYp|!ZY>6YtbXY52Tx`ZU${D6)G-Txzl@x2twcz&G2y~VfvJMes zHja2+o}w~r1W4uDsc|9(Y8_&d?zPmJA!lQ~YMkP^(o(H&+Rfn$mV?K)PchvoUSd48 zDRtjF!+kwU3&|OzJM=@nk|osIfeIbB^eG8x2`Sgv*&A zZcPzi53*5cfq14upagR*VB=h9_rEC7t^(0z;tiR%7e;A} z{1_u-$?wo?oivP-*P1*rR&fV)GvpX zWSuI?9?fMetK-dM`(&@kg!iuBLHx%Ng`S(FI&%Xi5)-6n^sC{b`Cd0I=!_Y99IXLS zl4pNj(#(N2%w-0i2#JuH<=jy0)_8>Ar{abv%Wd2V-^13aJ|YhsH?T-~!cn4HAEZ-c z+VeK8Rz;bCI_Q*`8qc({b$d0A=hzVM3NL0}u_a0zB9zK|Ex9zZX{@|`Y#`XGb*((2 zep$noQu9ak_4L!>V4w8GhjS=5D9(gu*j#H3DR!5bhPPBGJe%{##d2w8V-8W-OSJ@z z#st7AEdUSUk-iN3>mRR`#8vXkisT4Y|5?|Fyo2|wdacrU5@Zi_^1{_(D)5o`V)=?* z0W(lhf|pbvfRa8sft+_t%n{UK^`%u&R4yes_y?UC1K+cOjrVSB2fKoU-O8Hhc)+19AYEiL z5%OpQjD<2$QSJiM#Z)`GiZ%)<(NT?yzRru@?skApD4nzq7WH$v8I%DEd7piL?>S*> z!{{jp^qpL~vq#joQNUB_H`57(+VHhhz9)!Bq0TfOvkt}Zg^X-$VjL=mv)eEpkJZEyjDU4P+hPXqIrQTFvqRZ#D0|ID#&vXS``dG!$0Hlk&C?C4e1piLB zEOtuz`f@RMTnJ~$pFV@EzaF_TxuvEUf|}=52BJx~63yvAhuDCRR8APf)R56xgePwp zDn4N(Ut&A1DAD6u8kOFW;a@7iRXf@1H?}c(iIeEf5IA@M7T89Osh7YQCXX|OOw*Pv z#(fnKL=)YlPzyvosZ|#8MW1Ph#FYhaJ3P8lpWe0n+3 zXuh0NuyWsy5SlBLsEU6mMVU!inBU!7?DmaY)YtD7$H^X8ZSAIED3_e} z0*sb?X7VasaNRYbWHFIeVh*e|%7v&QDzvc4#lP4OTD{)B6Ar;#lsl;^!2tFzZ3D4{ z$>WhXl_f}l896mGkf`*BuQqKmlKC-`hMzViVIHB~Yz0jWoT$7!zP_&qhucPHTB?4| zWZn2SsVC;(cz#h+zkk5W!K#q7)GM^Om-Z3x)#!$j~z#Z zL+mmSdnPfe<_Ntw)_uzXvZTBmT+WT7j=uc)AtZo{nZTx z%7~dKA`ip!={`9%x!(Bq7pPDL{Go<0=J>JCV#GZ6wVWO{udW2+Z?7ne0v;$T=iQ`v zPc%<2F}uHtbxM3TJWp|!(V;vtyXU#faAtOe91KbRRCQ(+=f1@Xy&fWkqVNUCGPlNi zop&ZiU<}X6&^AbgV(nbwU#^qLZlimz>`hX=oVnRuZ@gELv9QG8=x0qZHAfHIp5y;% z)L0>9YA+C{6>VJ!jDV7eX9bHW^v09W;|W6S60#rLy7s<=YN3TEh27OPMh|q2S^|;W z=EuYxH-=0&zA4W47b_cgxPP1-f8I5Ejy{_lsSn#3v#)*X7OHjAMS!wRA@}{TH}liyp^Q=GPSju0#!s*?8GwG@alMDbCRv zBe3)CeR(ALYC2(l)_K3E;{h(@CS(pQ$GePOl%nPVlvLXpuXlhuo{n(5@7FkIXRTlE znu8uD?c|G`_E%G`Q+}R!Noo0%>Ae$7gu>S6ZqZ_YNhs1e5JV1ytRlQ31CD1^yupxK zr~dEp5mBrk*|(4rigzd32i}$4+}&7qTru(CLs58A%&~70!qUu)W(X>DWP|)MZISRi0b_;Qpg=MNtH92EtsoW$%di9lc^(XS283ykB7gi zwOmj)oq7vybxKv#UJ?5VirRQG zjg9Lv8V*Hncy2wMd^oVSIb>O%z*nFRvq;-^9rt!*vHwe*d0r+&k6=+5C6EqTW(l8M zg*iGt!yK5M-TVGB6*7^*k^Fe=XLxrqIFa*8&C+?ptJ`k+)n@ro$90MQx8U=4ohtTd z0E$PZ^B@L@Q>?DKb!ie!$l7o;frC-ACOC#b)}EX3d-ifVf!)cZ$3EDwzBN2ll@?^n zaK-6iG4DErE*;Z49jL%A`7e$oXjwjh{o2(_oM9l?@6Lc@6YltZMBLK3U@X?@(n&U` z0QNu8F_<28c zIvV~+S+m_KE2p+!P)jT+v)tQ>)41-1+qgPwC%G6RaBAI-ES<=_=LI2z{BeCu%`*7z zad`cxS#OFVoANTUJ;?BQ*6za2*;cE$`LL7C|LV-VsJj@`(cxUcGJe#GoA=BZppKmJ zB?h^ZyHPQ*A!Lpjy-cU1*-GsX^1%u2^mZJG*RGQNXh#KscqT}LU4l8`6dggWi?+!% zEBmV6zM#3w!6Tz%p190Lnr3^3XFn)&EX}Fk56a%f(P2S|y zz`m64m?QhtxW5cQ0D)_rY;V`t;q`Xk{`D3|S^s>eeo7sfn~SCjRh5z(qpl&vZ7IRH z!*O%Z#m-)1vtr9>xBfeYsr_$@1H*=IduYBS0o1|b)DXwwU+&|I-&r1}u#b-xQi$T- z>);@O7)1exX4eeqZ4%P|qSqCh8mO;yovu3KltVmMykW&U?r~(z;a`4TN2V5kHC%Rh zdw28c48xgvN$s}P+SjSG6sp$a58GQdt>9!>?C_vk3w1$xek%Baa7mXGWiX`MI=@mh zvGZ4i0Wdo8eZOw}x5$yZcu&|3SS_8B79kX^H1Qr9o1VVx@#*+{WPF^0iz{_?RR@w4 z3th0JiFc>n;fMytWMt$grE1vkjsPGKB9C`V*6E5H1nEbG$SUICKsR=-e|uyC?zrBp zsAtS_NlAWtHE|2W$QMqZ`8H)b_Iwjs1Y4i2b;iXblrLpD5%Q;>0zS z$LB9_kv7^}Wx#bjBmKBY?ecpAe%=LQkr}vUhY^{8db+iq;G$ukDdExCQ1n}L zhg68?Xe78(K>+;kcmQAWk+%_Kinhe1+Wn7^<5SFL6I`?#gtBEht)>~5Bfu2>Pm-~!j=NG=kbLuAajxsOt5>b2`QuQO82UHsA2_v|Vs&+dko+Mo zKX*_^2lFnNXJov-@r7}B9}>z>$w-9aQ1x28QsIEkQUP@!E|TfmLY;8aZ)TGIGp0CW zqy)k~J_7Y-Q+t)o>wTy4S!~meQIwfdVu*!~TQ=#3dAEM29?VV5t6MbKP;v`$9d56I z{@zIPC%u*BzIYM>32v_<$(6-ABgaQ3v?ikC^0tt~u>RXsDkR(xUv-)i*xlP3`rax< zG)T_Q>oR7z+JfP4+Q8>sssA;h&tMm~vH|otmm7;>S_L$Joebj@5XJEwoR{6RV_T5t zO#+3NzT|wCkh_a79;OJL4MH|8G1`T*h5(cN?=RO$si|<2!|;7-%=}6%;t2{Oss;L& z-M^HL8&=85ai7R55~5)kgmfsEsXo|@ihr}k7o$*yfbqj5eiI2)vMrB$nXPY6$I`O0 zQS?)H5%yI^b-BgnpL1Jc%twt&j6RNtGvY=AQYu!fL#I&%b?#p4cuv;II)O3#mmehJ z()Tv*7xMhOZW_1LEm3RUl?3&}xm-q1DM zgIRJSp7;|GZaWtZXFX*pApVD9O7NLcyU9ADsSMEX{>|*jtzb?FIjcz9L+_K>&8DBX z+w*&>85I$0r(KV0XPtm_Qi_rE_Pj07d>)E|vc0P-2a=2;naCB&g(LBfr&B39*vn5g zV{Kp6!;F6T<&*72%N43cgw#wVR^~Xc%7o6e+;0x3UK}3-&&IbsEz3ycuwp?8UtB&) zD$M@1$ArqA8t(xmLS49LJQ!}(KXlkyyV9iUKRavgmqyEDiz&b1hlq4!=t7Vz$bOHg z@B3&n4M&Fl5kH#L3S1c5D%FHPGcz8C4LUlXQ(_hY>?^Y9fNK2Sea~9=iRA@uRei5B zH(1>-Hfyd~0d&g6a>sxHW8h#~yLjL2d8tC1z=RW<`QP-n+wHaTms_SskL+AcsFUTV zN+9EKr-vU8Nr^&* zjk^Lfh5QBhv_7uD5^?ct5{{!a~P$V(c-cV4Xu)w zo}(jpI?rR7@9$S@F5)v826LA;ha?mQ_3mG@xt-}?GieP#1R7M+(8c<5Ps+=z+&*Oy zC)MgN56bq|HlD5-RV94H^Urmwq%77JapW3$`2-rnzQ0tX%O23w%w1-EjU4 z8Y~i#k%3R__u{bbu}?(oc|bN4N0NI&@1i*1iCe`fLWh0iBe77Pq|BV>67>*#cR6|s zEjnd9$W;xUV71#alUn0;9|x7?xQz1B^5O!Kq{iymQ6Aq4 z6zaX6x!?oiz)|m`;Klc^TZ`oAVsj@)M@R1OulGN?ygfvAwJGe9wQQDx;M@?SgV1n@ z0^kSotz9O>xzD-`R`M)%+TG6gG`gR#5~=031K@~%m6Xt0XIhghaW`QWr9ln|Xpx?N zHLxcb%aS#Q8v(^X8<#)BkWhK~8c)TZUwCFh9|*aB!IF&p(QQfW<;qy>yn&s8AJ=rk zME+&dJ#{Ns1h9xGCw;u&vW@^cl7FVjC&#W1d`> zbgE$-QK}eyQuRLe3BwO>IR;wptbg z!NA$EkoBMf4j5STualJxl|HmMs2XOTcY;8l!E$cZwh*Rc9Oz9)6Q#dq$RFutG$F#d zaD@GMea(E^c|ytA73%Ic;d@=Vw|>tMSn0M^k)NFT;vL8fRdAPF<0z;s)RLYZrjjSLXUbaiQoD-+9}49KbyNi~?+w@>b|f zw{1BfREckfpW=r5vv`~y9(LkdcVhX{cwH<57ov=RLsHu_QmtuGcdVFd65YLtOBx-B z!WXr!A?)mpQ#}#kc`;odX{4PdBgw70a(9G^&VNb2U5`&3ooxtS6)>bbzxCUU)vV|~ zcn1hq<^3%?OG>f+_GW0Zf1-D~w_FsaC>_@ZLzlmI!34Ug3=om z4gj!=NzK(?pPGqjoVTrnQ=;rt;C)nSQ(~`&l1zONxVtG=is^yMW& zn2kkLg8f#ZFfGcUq@rSXMv?^FdB48X^?bGi5lz80nY@o8j$hKj0fF>lSZMuwuAuJg zbJx2s0mOSFkU|)kg8Ck2t*}>jl!r3i5v7l(;?Bvb* zaH6d*A_o0RV@X0$gR?_JqboP@pTT`4yB|)C(3$Xj;1sqqS>9kb-6C#?*b#C`6H?M; z?*jpZ^lJwjscV%i*-lRnQ`KJ8Gn$pIFumhVC?rm^!I^ZlyuH<8Cx2h))WBZcF0;DO zi(<;mVnF%N`HRUI=0Ea-qm$Fhyfi^6=o=&$P2#ZPgtlMQO@)h(67U*J`zom%=p$I2t3$}G zIN4xf*ED*3T>bOz#AYI!4~X{_QLCv5O*^AZN*J8?I7KxwI!uP^>He0;U81Me#Pw*B6?AKQ(Y z%QDSW=Q>DuccM8qGlu?N0&KXk9+GcsUen|VSxqUo;Dxpn?gWw~!OrOAM(k#P}o3v#q}oiyTck%^(tpztYrM zjTb{1yD*c|xW#wB`M}DV4%YcSsN55~OJq9nfg9Qn=L^G0QuxIOw}{xeh{1|GuuzU| zxB2(}bDvcgDQVoGVo)`3R=6);g7AVFxuD{f61<=?FrC8b-1{tm_IlFAWUhb z$SY8FC^54pQ1iFIDD#f9{_;o{%9*biDO-M|V0S5KzRIpA-KzNB=hMxTi(5B78GBSx zmt)#2SM0RvwuCvl{jGgG2XBxd8s@Y|1*HIzEij=N&GF$?2H%bIr-SAQZkEK zl+L@yqw1~*&#^^+6ZPH8PazJgBa8L@A)s0DHC;NgdtP{SZ^Y}mL`iiX1e_{-8p4>I zoSaDi8m1ul7@TR_7C|N&I?&j009d*m8I|52XR$LLOIjZxp=>`a0wLqG!zl$vMw-gV z63{Vz$L>|me~l%T{->2K;!s7p7__x?$LsayFs1^&Vs72hy%fb?-FKZkawIIv?#bW< zztmlgj7;2;%;g{K_H>8=+m=PSb$>vfZ63w+NovU-IT;ujFx211k{QxDnG(2?)Jyo_ z8fVFQ#2aE%C)Uu(RhezGiJIOwJjuB~ZWMkUE@E@G)$ecz0G1pVh{uGI*8ei1B5D3| z)fH$wKB9ZC%K+vg!~ANUk(-Qjfts(ZmNd7m^fIc9J9Wd^!9OB>o{)#K?y}^PECtt)XMfK7{K8XznLV?t6 zcWv}OYCoN_Y_zO8enl5=i6i9c!%*Pw?FsmZp{!R?W53mGz1l)_@j(ybLhDW2erM06 zeK8>&tIpa37aBEdU%JDzxi~>|5`SReHU-xlsO*7P>Iq7gPj3cL>a@ZS4lURn5+SH- z<1ejg1ucl^hDE3NPv5zBCf#?{*0LRe3b!CE7cyqyJQ?@PZEOsRTh4PA3U;aNOpeJw zrvjq51L1R&YDeLiTuo=5uTh}uXM@_lSgSjw)|${V7EA7jKLE$-QB-@`zwJwhkBY=q z>!_%2-H5TwK&fG%LfJ@GmPCIFwNUK=J9E&M?9_nn_a|p`h@Jrr( zixnr?=C+@s?Z04ola5mC;@mOALRYL4F0$#y{+%#0ynZYb{bCbAc0PJ9fqkK&D`JUP z`*Jz3+e6KJrp(bfH#sPtO2B!|FHscShYv;HT}JktF#**q_Q&FI_pPm7bz`E+*KtN? zFU63j?vwN=)-PK%3T+lTYAZwDbecloyU>yWuu66ThochwPEAlfha^AJ&6T-aPVfRQ z&fS`Kj+(Eu8-Q%Vl2%P23EfH3E{@H5gKaJ`Y>02jC;&Fni+M5rU+=+~n7iA~%i6Qy z^NUpapuNkHAos#6sTY()7Gg(61cj{0m~ zVcxecw_&&1P<>yu7%59$Xm`$=;r!ZF*#Wx+)(0=Z9o_b5|*nq3kacGUr!u8hZU| z@ta7(*N&Y0Tvw8WydO5>5`_FIgzl$=U1*O{#)o$etvaIAD=z07&R zXGLnQ*(RCAtyI#fL7se-975|UR>_DH9Y2K>m5gyHYirBO$sPRuqK?aLZ_uvHBKY=T zA@31|mQfn}nOcJi_LsNjzFJR>Szi-*S;ThtHyfNr)9iW7bf3E z;`cKCaQSElG!{D9tx?m#DTkfbH@ZIM8G7|H@Hlr-XP0Rln`<3|Jlf&J%ym{KK*eRW z-i-`*9~dV2w8r?4hH|JDdVCrh8LiMty$c=5(8L=tAGl^7XPw*Lnc63*YX3%9<^b(0 z;}dka2~lBD3B%_O2f&Vxeh_kML1E#i7h}g$$JUpX-5m%6LB2TA<9hG%ZpBX0n^^*w znmQcAwLY@h8`OGOl)0^^S9dYxSG9fCE;M+IMawv0+#Be`^SEpM>$R<}PmLw%eb}%e z+;cr@UWHk;UMYi{6SQ#B=6u*UN|XB?ayaL6JuzPtw2Dtcit62psKq^7y50JY`lcM2 z&;6pwAvhzMs$XO@SvOnLksdirIh*2<{Ba+bpo5C&$K$!y)v%0l{#Ev$9^rjlZKPJk zHY&01v1!-vlFT8yRd&0pS#XhaTP_(ebFY#3E=L*q+nhR=9zXZ+-_m!jw(exRg@kKG zYN&;4t%yRWXf=)2nz$wdhaaCa#Vv6-XCAAlXNP%uVc`|uExt|?bU44-6!n(lFb#0# zi#H(G=~blXuvZES>e;vIKvE;g|6YGjT_N<_9X-=8(|-@PnpW0#Cbcp7YUb(v`<{;oo(vr@P(mpBRn39i}-*|m{;4%Sa zij!o4g=@NH8O7CHj}8DUDMXNc=Da z)85WpF4dv+BN{QU&~DydrG$4F%-DN#N}B=!;HallkAA}?GW!PaF6AnjvdAPS)_cT* zJbUaJ|MMF8GmXa2Q|sRKjYzX%e@&JIwuOkWiy}m{5QD|;&fiXK9#_Wyg4cg2@YH_4 znR{K<9Zmta*PV<|Efg(NrP0%kIJ8BP9K&w+y8zFOS`?qiQn@1ew-P*_g@3`S4~jSk z08gZmFRZ^;nkIYTVypjOc?Tr4L@4`lve_GzwuxH2;)Pf6y7GHwIP>BRprt=rxruL; zKgU>Hz|T!~evMp4Vx9LhCv1Q7RAUITnJe56){dgv<2xW;4hfv>ZMgA^lno*9oWpZU3YT zk^%^Z$+r>UTN^Oc zLDDd)M`{e3$%9Czi44y7GN@E2E2wio{gE z(z{*fN; z9Em|w6zIXcZe+_ALnvkKz&_h%r$HX)ES09eVXvLWBsq>fZv<?Rde<@FJ`_@;ZjtRX9_nSNuW^V(T|%AN06VX2UdVrU)h+srQu@5?jQ z()Ga2YEhR*Y+s!#0w_!r3Q@v^aI*2|Yvsi~uYO{eSh)w48{?^Yu9KlHD1a=jsWtxPg*A|T^~G~ zCc|gOAtg`{Z0BDI)cv|wTsEKEP>XAA0BPj#Tg`*f>r0<7qF&T`r`K1jKa3E#&Ta&C zSc@qFt*=y@K39?!mxBCAP@@ysB{${_4TsGoYT!eHHf8Lt2(XbB(R~%gef2i!F4( z-2|_Eh5_Dg2>fnpWcg@N``5m$ZCX)VM%0E*UIX=$Pcc-@*^1;+mwW~)SG^O9^_}xk z?3u~20*jJ|J4GUMI?^|vQ(-?mt8SmHv3>$5V01-a3!LC!-rDWqT-e6znOlWkXGW{j zzA~iVBPhdyA`7nmmUOw1p8;HXGhPlrUU z7-F0FHs5P=$;k7?+quVkVNdlt3ckn3*SPj|eYnB>4<)6m(psL(*>%rbx9t7~M$c_r zF9Uf~o3eOcAL%-BcvDz9a>n|P|8_pwc%ro>tp=*lo_KNc{dq@Hx^R5xvGlyj8F&-W zf4FwzQgbSBW@;4}vUh(+{6^03C@|GCw>lDgn~)Kovy^>Zr5Cyej2PA(?$RZh(bNy! z47#dYu-4}y@F&Z+Gn#J+g9fU}k`%bF@S~o-$JQmBkDl`geiJC+?+BRq-D%(EShGy% zkQ>-3CqxL^rivur8IIeZS5W{%%6SZX13$H_4F%TrxkLrW7{%z|ic9jFNkv>YX;psD z&}MMWrZ%d=KDbQO!)n$feoy@Vm%m&a^QeYa8LoVxZ1f2`MSRNE!|kXW`$;j z>t}Ho;Yj+8)=L-5IBch}B^JYGGno0p^O|y=%Nv>xR}3rnF@8Q52yWzt{d)&}JwUF@ zo$YogZp|wWo;8Ic$y~YjXO~H-m9V7M zi(-sT&SJe$YfA`jDpWDIVGpKTx4T%e6nu6t;z7*)mQLFkVlsYjh^<>(Tx#`)!)8zy zE!z8hCA@X%wx4!B@IE9?J;8~8WU=j(cTi_G%ND=E~#gBSiUT1U7FYeEz7(|0u3HbRUh5?XBd0QYNrDTM!*`5%C zIo=R8<7hnONd5Z%xB!}syUg@s>PqT$Ngurg^UXJJ{KI}*x-^z*Lu}>UQ%jkx%@1m^mdA;^k z1Dp9gll6~V%)EpAJZmg0lLS7hd=&sq&Ks8_(y)@s+LM6)w#~V@UQp@}|s`9nWtW-x=e( z^QZWutC*9OM}4R^oNQ^msjoF!r}O`>q_YZ(qv^IV!QF#9BtU`$cL>2<0|W*L?(XjH z?hxGFZSdeWxVyVM`TINPq93@KnXc~Ysx9wYt!kZbc!1zrP_c`jwa4txJ+v}(!jUGB zp>tq508~nvsF4MU%pYpq<`ObFodm0H)s)%HdkuCUc=N|OY`J-*c7G81#BZi4?YtGS z13`(7+uldPr(K-?R471MK!hCEq5!iM^Q7m-=JE}ychdJGgFrVY>ZwVEe8sOmkBe~_PuVPYPMpt~m`Z{+osHoDzwZGh&wGRiNQSAZR7_K8 zn1Ss-2NBFY;gI|KIel_PRD0Z8RH>lJU7|a~yZAKs5!MOHbfwMd$hjjY_H^y)1C01O za`*ch`OA#s1nHhomB;kb>Bb$U|EAG}Gkpzf^TIEUv#g_KFNQsYu6);wTfe;1j_F11 zR-gq_vNrgxeKGCx$QYSP(Q3RZm&B09^&zJ^Z236r5q4;T53A{2t8DV#r{jp}3h zbEe&T`38%IAD}VkobzB{`cCY)EY&^OjqQ)y!pR~AJ$W$3Hn|IAT*V#_=Zlulx?OUx z$H%LuYg(1dnmuh!FORzkX#97xRPVRN;8Mjg@S`~h(Ci|}xPWNjbx|>2(T3Y~fCl8xLtaQ08Jz40mfQ9HU z={=1*g5e!PCpklWfS+|HG;v~Pc+mE5@I0zIHo=8zqQRPd7H{W9DnaVwZQKamAy?E} z`@V|KGo!>PSKaN~jMU_D*m$zM*Z5>;UJTaGKS&;kZh~PdS_ z*wG-xQx`2e=;BdCLF2|VjfHVv@TVO=Lz!grguYzl{BUZ_FdB7c@wn##TJ9GYqmnspTOwJ?v_I!>dVfUtg6=IbM=3Q> zqfv27Vhb0b@JcRPn7^_@nNPLQ*&*Be@Oou+z01i-OGdeA1>aQs@(<^ABtvJy*}sg~ z7iJ4{Q}MPUgyB#r2>o?2F=lKZ!=N9z1f;3tl1>|xr+e$ zc}lEti)CrK2HLm}{DtyxkiHbc38w8E`e}kzj8!VUepZ32VF&W~WPlAJw>8w{3S;13 zWI~in1py{pk)0?=+ZnE;MOX}8`JX4dOYb#hV|a}a{}a8Mulycp`2LHq^E$Eb@p)rf zJuHmL>P*o6S2`vpCJq2r>j1QJ_0%8y=>vOvnLxHZw%t8rX*h&j)`(miugtK*yM72d zM$fl1hYQuA5Qu#+dS~$Raxb6UqcRvL3R~V?d zIUyw-@6_ph=E5qbZ#fO@v8*>*z5u)#qM3l-~is759 z%y&LoAAS?veDd9cY6%(Aw%u%!Se^F=+M1CQCr%40_q(EI3}0R^!!rZhxCGpM!C#xxc*YOo#hy`n#(cfrK8 zrx!g3Xjn0Uj-D1tf4zIYM71P%Q=g-{TVMU4NyV zFlJ=jg<0X%^HzA<M1$ytIAAegSoQ8itfR@@6I*XS?PZP^N*d0Oa7zUsr^!xW3d z5PFRUB%Pag&n1;se*ilRRO0!ZvNB#yt7Ty;D*+oYPfTWJ)$7}ZLVQAkT(kAstHYw> zS-MEEe46invYoSgMfC;106~C|w}ESKZ_aWZB&)4WU2nqtSx6{hki4rvuG9OWeZbJ9 zSsF70gA369%g`}_VN+0uh&o!vfID}ppg>9;D>Djy^35=< z5y)kYi$R-LoKOT z$Ac;MG|se#4k06e1kEt9dG{JTe^*3v2`DmCZ`@NA=uylVXq_-z-vVClDwt;WW1>lfO;e@5PV%OL zVd$3uU&N?-&Cnne-pT}Mxq$#hKIxI>r`qp#(3+}PF2B=;!SjRI4SZQc;IFLKtG~S; z^$?)ios*zrb8-L=iW>q}Lki^`hdQ&F$w)FUO2M3%LYWkp%PA3zsZI|{u2l+v|M>S7 z0AOEeL*)1;Ywf;Sd_wx0-JS&)F|9y#iP~UrJp@4hwA((K$}sIubY>E=!PmPv(5vgd zu@G~qDXPs6<%uD*+(OPHNi+K1-%OM>LWMQ}2ZjDf!U16L%VSYoyh}AbgZF0;t)E9-etqsk?|RNHczD)qxPrxt(9;?i;vZx=CflPqdBX;G{tSMAg+I+n3oLfrXA-B z*|R&hDF2jenT^Ft!lW0i+NxL- z_0cn2(ym>*P0!3s9>hfJ^E7I~pmmQU^v~0`uFl@4+sUc1c3L|vEuQ!FamS{{a>+bA z0z+9%?Wo$v$*FVS4hDV~>YJx4SCImF_ur^^);bGy#;L3hete6C`RiU31PyQpppVX= z`)U#OR#KHbVK#*M_17=F<6}|p#}C)n{F(OQ-Ki|lF6(K-i?#8**K00O?*`;P9f?B! zT2X6ie0)vdvLBk721Rauo4S}}1>Ubx~&Ou>f;EY>}!RrUgl zoJO~fu1;vxtMf2Y-XS5oO_CnSnawhGxZsbh~zQx^xl z@Sq{wL37PvasRjwf}SK^#i;#^GgJI8I$z?XMzfN{WfiJOHVv3sGPdmkfQwB}aSqc# z_}BA%Fc_R{Fy&^;< z-rr78Fo%vFAF8=rb&w}>{gXB#T~g{-4ZgmbQx^o5yEK>eqmkKC`TMh*=a{8-_0PC? z-9K^!34K3Ss~@GH+kjd$a}}uxYUi|?zV7u(2Ema(Zu8$YgzbvR{`QoB#6kl*ITC|f z4H?4x`{pUeY9M>~?w3m@8>t7J#lI9wd`ifvV~>b(pH|}of_2NScn9~UZV^fuCjHgy z^++Mgc)!iElHn}KhaSJx$aaMtLXu;P(jPxsdqu{Y=9dMltpk)MF^M!<7_85~7ZcBy zGfpU0wwada<6*{?jh(}3UMTR}{YE4)7rgCM;|WsB+H+Y@n~s9e8~oi}lm-Ldd_~&$ z;k1{pF{sL{WliTyR#l_JwadqAvs)hO^=Xpt(p{{s{>*aO5nI5c_x;TY_oEd$w;JYV zFfj?pK5XdaqWxF<{D``4*y$=gWQqcql6U{H@jU^gmdi9O59&HcCoU0^Ou4duWJa0o z={}-1O6TGyU`35!A(s||Xk1xoafbb}uaFlO%bHILBgBuR`W0o4;x|4#d;1r?^stB^ zmo< z$?0LsvKIgPatVSF6=<({L- zvOX?OPYe?lu>Yx&6GXvpbK8mfOy=6%JI*CTp14{!@b_{H;>OM{dDn8;D=YZ;ofin0 z?yoNUzL_bLk;TOPERVjPRoS>c5XzY-kct}sm0-zs0(r|=)$m!qHeh{lE!QkL_`QyA zEs%2QfF9bPoqd*id_4FQjOf6Sz{_niWl^3e2@#Vh^13HMI(b|o{MGjez5=$(NO3~m zgQJ$VPoXN_VU7*3E?*$U0w`!Ih3s+|!ZZI$M^2IX=RTk8>S}Q;V={R`la39Q&j4j& zO`||wPTXHHuom;D2IJ{oA67t22sRydT=+V!bv`RW_|Yb#$@+l%z))<&(3os-ZZ2aj z6gNO)508x%0Jv+6;B(}u<&i-G5E0rNO) zNwSFU7yDZe5#9u;U#JER2y;DLoQn_MH5MMnS02jBJlu`lou3oL*R4~JueKv-ZFizP zKAak{h^Zs9&;3|QiI&p(47BH{EcRrib#SnsTR{dZLT{$MWq;3(Acm_0*G!&BLCs{JEC#!_w&I5DkBo5r=z& z??uJ0&+|XOAIz&vz6x!n{fn#%E2%Omx`a_@%;RKmIB?c`d-hJ{flZS|X=~@3URjAk zVLly{b>Lyp?HceZ@w6wt1hIP?}fr@HqJ1u7Q_`KlI za+o(Ws(sP*&yxRo2-ENidd~;tNjZg+LH`ey z2HEFQe7!ipB|+Wtr>g#jFlM3^N)WU^#rIsr?*4w*b=Ms<&%1J6#ItiA13yLb5 zLPDx~_t9P5zR64;93X0DT3%V1o}Tt2WHU}|y5(}%AA|7RSlHM|czEzFFE2}X zS#n0q&#N^wHRTl+7M>Q5S2Xdc5w{}x|GD)zDonX-toEvO)IG;YkP%Ms-)8yaO7Q)| z5Carz6$XqvR1YQ8g&em37$~uNq6Gf4?)Nc(u|ba(=- zP{=S3^!|c1Pg7RC_GmSmUc#vG-Ns_6#b1(zpEt`L*Meu!wrmIb_oY@`zHACsZjXeA z7ee?ziHf+e?rBIu&>sv2vt}F}9)8!-(lYzH6u(r=S@gN0VXURN;*C~Qly56y~t%X2BDk3($eot}mUB!DPM`}@LYk(Z0L21bLB zDFOlbS6a{OZ%AP03~qZR*$}aGXeM3!V5JxN!vZ@onR>qWYvS0&;A+aa+YVXVXOMur zyt})Hhs;-Y-(ep_uluEDyCox(20CR|>SdGJ51YUO)yH>{sUwm-Y&bPHXGD5l69=~v zC~wa8%1aJMeXkeyjqT)9ai;e{9?!SV!UYw{VX?Q^DNmPD3?`w{IO~O*f)=ZzVMA5| z5|V!4X&r>T@i~*+)8I$(`%o_dXRW?;1yrl2*4L}#&kJuDBZ&qMeoJbFmxv68sWAEf zz!Ij+&88HytntcKA^I0prZ%hGsyG*cJ0}u<%_U`K_gz!z$K;1@NTJN=(7=oWd6l?y z8Ro$)@NY^&#QM69-B2GVD)dk|9L9kmTg8QJHgHi6K^CprXj|F852 zP28^BF$!(E;iGuXJ^y`Ow96T<|JD|NTlOoncxZEj&(>2@qM#Oj-Ah?_0u|3y92R$GD=u0#7QV?(p1to7*Q8@3Qq41b>BAc2xBV!c2 z?d)A$Nw_vTaJn7`1`8L>W4HJJx`6AEY{0+X?`D9Ri|rN*cA>(8rze>jHo={Pmdvw{ zi;pORVtz2MZmmL5wvV&y*XE=VOi4h`_qx`x*{$jhA zbg)^JF@ci72Rlv6&WX1z^HS}7hwFWv@Q(rIve9!jKD_#hMR59drOt zN~pAX@BA$W%1J+NA0%EtPIps7Npj6WeIXkI_~1($Bcu8C^=QB#rc9R-o&p;MhpC6- zv}h?Nxa})GiSV~48J14r2%VE9CHA~JC5Gp?5?rJopZ50Ls-q5@vF2+PjZRq4i!AG`<=O? zlT*@AP=SuA1GBIFHkR{aKmM=^6GShr$aj28>MsKi#9VTyioJMbfc;0(sJQ(YQ?Yg! z+rd>}r%uLcv*VHSf391ufQ^yNg3!yUmPY3@zUyJGPtWxpc?EYTeUbhKxcTx(VZ%?r zLx&j!AQoBh7&Iubk<7S7iOy}85FVV;mj`~Lg#%-KeHzf0W=j8%Zm;JsL}JIYT}Jr{ ze+C9@G*ng3<1l=YR_Q4JA|2?*DJ zd$&IVcZSGs6SL4rvFS=!YQ2#jZ}vz$?E@nRl)M-X$KGGzk0a72EYe!UVbN>?zB=SI zW~=Oyy3ddl+n2NEovB=rLJM*6zkpB@W3%-DeMds*nMSkxt-AFi<&y~IfIe39S`*sF zzxV2*$Ha*V`3kM(s@rLXD3BsFrQG&x_UHr0%H>_gPZg#yu+^*L4@{^u$BBW45M$@2 zrIGlZnF(6xQu{cNYCZ@S(CQMVGrBA{ZT--_OA*{C@|Uzr|3+U0c$KWWpH#NIoVQ;U z3%!+|zLL7|N1d(c4r2H`N`dSKo^+NtyfFW!I+EYbsvumhUA!|ppL;rtw1UBc72551 zt)Qw?X2^JQ0-Du&kX#Sc>weSI^V<@w4Bzfp%Ua?xIh`oHf>z!->Wx38)*a8hk?b0qZy4Z zaQt2wCylNjV!%Q63m*W=KRn)5V}u^0F#Pfd%CCPaE72+|D@`pdL^v}TiHU}d>#6Kw z1q=32%B;nd+%2j=Ety`qilQuUuMZ6fqlcEbo z&EV9I;jkF#x=jPkHq`;7gB@^fb%EGlqW@;e60cmXvbQ$~MaIzoE1urIw(VZ8%MjC- z<2;bYSJgJ$M*8lz+!%h^QhehRWtPLc=@}CJLd4kkh$G|})+EGwE4;4p;Tx}`MSRcI z%*mcVS47GRlhY6Xc`Sk7&TI?-8Hf>%02(0PG2(xJ?ApmE*N&d_dC##O=Saydk6_Z4 z`;I`tn)mZ`dKxN*{~C~?dgE&~gUit|a-8OWX#l7Z&=eC0QdNZO6K5}&qe-Xw6epcg z`S=F_$axR)G(;-%eh4pe)g2J^CgH(4ZFsY>#v6r}xx9~Fa)>zXs{#JR#KRvv3I zLl>yT)bQxKircgm4{RBpdtY^XzE*emS86r?*C0y0?x4+H4EXP0_IYCRJXSdYa1-*u zkLnd7oOS`%bHbQV3W9FXT1_Ma0T}28t}TCbR@T;{j7F37fZmdk2pOohhMoXi{sYR3 zdqUjOOKE2NPHJ~IaAKBP!Nqp?CoywkW<8#6VwkxSBy*xwk_GMu&r7slyZ3j;QcdEo z#ap1xZ)VK6$VO%y2jkv=VDqGOk1`-207#u#03XDFK{AgDOR=z?FdT!HhxN1nj#M@i z&Z_ovgO;nm7dQYcT#auUtEIEVH%n zUC&uAK91@GU5C57P{MGD{CW|@Y!j9T)vT!$e;I^ghMgnM>Q6z}ISACmLwb6IJ1%=s z07C-rfaVQvE!mmr{EIC>zx!Vn6f-eFs8PQU4YD6L2e}^&v^;Ke9G(lJM6p}izj9xW zv2{;meQ-V}8MyFvfgX%$U#U%FGtsxT#XQJ#h;F+UQT_@Np-jJUbecNkk?KM2L(?b6 zi=!YcUAo3c3x|U6;Lniv)`VWTTWuJrGVPPj-}g-vQ5VR^RuJoKR03^;>)V~(=@MIb7N10|fdpVsg+PZ3)m^0s zm4E}>*Nl)L6#TsE>dDqjRbqI%I8?SWizwhWZs!f;_XbLzya(iX8ZKeMc z&%uH9b2<#JI%KsouK$IKin<9n?)K0MULgT)5#_E|l99c)P*B**|Ggt%0Ke8|0}R<{ zkwNr_0(59S=W8C9^*L+W$QLIW0e*gB1OytPetvKS1gSCa@2$X}2n=sqHA@F>s=G@Umm^P9!Je-QL~l0W#cFE*l-f+)Y6REx6vW zU{R_TuO-RA7-eIMQaq+l`DHk~R$=k?A7tYraODupAo1dymhx4}n z_VLLR>=vcghC^d3_(g@InYmAj z90bkF%L^1gSy9o^w*kjo*S#e5%LAc*gf9;lgNuu)>bwBC9}bwTZUc5O6xkPtrXoyS zFs-YoKrlg(a`K&NA-Jl_dDq@*Xs>R_5IQn4Qs2-JbucDI-;gFIE{;E%eA2+skc65V zhk(?n97Ax(xSkuZHmv>K0}Uu`$w^5`RT96F`%gn4`r7UzQX!(;mN>gdYA6x6?;qDl zp;tA=W$tfp|J2tzjA$O=jt794xPqcqG+Y;1%!kdy{pL{$>~wtI?pgqcM;$*<&9B&Kvt% zl|-%N&eBa?U`q;Fod>alsLpO2uf2}E8sxLt4dn(m+}^g_eh-DPTS|sVqw@0c+4aET z{{~clsi~>manur*fT<7~&|vNE2|%!WeYBGqJ|rL}1}-1>y4Qn7!OK@M2?+#uclW88 znE+r_%>=wcRZc~fD_Y(bRZ%nQa>sY3gSig{8EwYN=saIdwUhH84#1W956lK+TEWxP zN>}L=h_fx8!MdOc4yU%OZVB)ndR==vF|hO%-mSbEur#q)ke4zu;Z+G!jlZ_85@xs{ z`9tC0;27xv+m|pVMaFn|U1M0WBHf=tBYW|#uu%>Sn(%N4YOJLQ3L9D%J3{C^YvZts zropxdO~K!c;PyXgQHv8Zw~BZElRY(SgEzw!KUfBD_Q(5HTODfc_oQKkeF3Ho7d7l~6G-Jj#h1e`skD+qaS!kEaI#W4*n<-v+$3G3P#nfg}SV#^g-#lxx&+gy(D= zcU=P((pB!tw{miBH-%E4i`D7Zk_02czQi+wmx5g|AsAkVb53Pz#^pF zq`x`dAnpq??Z@TRyhUSG&eP({)ra-|Jma^ffe zlwp~`-kK`l!I{jcrB;TMpP&DqR+WXHAGA!y=ZrcSP1<+0GiZ==kT@*seaxRSN|`d6 z3k2p3Iiq`1)68JY1^h&uDgjd8t0T!Xw#85q!gP54pqy;~97wIf|vUMg4F3bwGF$;vmBjxL) zKKC?3m@~%n>g#bF4kog;M*}EQAxG?_Ghtz2brUAF8V$*4pZu7JzYRhaJyJ&bKF6a* zeV5#dCzTo`!pgf4p0v2U%m10$kp!Vo@J-4L6TtZSW$C9Wv2n=aVqm{J`ha?L)dmxTBQ$X;sAj=QYxt%neP zzIyUD2|E24u?T}o_d32An-2jQvJ%drTi!Ud=3- zSv;8hEQ(WKpuXA^yYQ)9{XD1{GhjlPvPCX+=cGO%}+KIloKzSSlah(CH>Gwjnvn{ zL*sEpIR+N(TjYgvLRXDKDc212YBu(dVY{QmPuG@dvO;86=PSs$1<|j8qx>t;FCCh= zTerVU1$}t9Ns}!aV^)dpNkUytn;i-|Gs(V~NlWZ4aA|h>^H>{UXnGvv$PkBmj>7R` zyA-rb{)rgrgwKNWpjWL1ob;0lF1G)ic&3@8$Uvjw7{Bx1PTP??Gyyj;fFXEmlNjR&>$9lO| zwI8u)md8OSMD$h-RX4fV&w}weDf{{aaDAsQbRW}g4(`56uf-CXM`@&b)?g{Mae)0+ zu+bI}LmMV@3|_8Wwk|Y~^wpV6#r_O_j^x#&pDAUT%Fe95*$WS{;HEheXs6+^&X|^( z&?vKH@~8BY&`p4D9O*q9YfjnskK$fA4Oa)1O)RORluqX@Jj?E)u4ojb21bDviO|NY zv^0yDlnU?VtU%_nH1;4V>OV_@w8MHoQHTHCc%|!lR>A#bH1*gNVJc`dg-|`YtFLVu zRXLFzs!?TQ*Tzc13l%@f66gMzoTGRm#5u2v_*Sy)6S0WAFsGG(C}B|!J`K~cP>rAy z;M3A6h+c{TEiTsd&$?kuaBH0rKZDDH%KgQ>e2A5DUsS2~`Nl;36kJz)s6 z#1Ix=dgsZ>8`~r8zTK>O4H>Yw#p|a}!q>luuD(6d2Fci&_J@E2MkUm?MJ)0_l1be( zn(+{joe^tV9T{m#cqtB0m$WNFR}&{VioM7%&_OE5PK%*-{z;U*-tw!p{Gl(4P%eM> zdxIk(q@wcQg1eP+y0R*IrG(^}LPEtS)-C1zhF~!nk;04p<5pq-n!-jUN|76nm4s<@ z_|u^>goLD2;P)d6Lhk_k8ma zxXo%EC>7fGsj>N4Gy}s|kzax`3SU|3aCt3Jg^+t~lT$5`X_Z~aSji|1FA6{{f*RK3-E z;la+?D{ZAJnW|H_KW8>xFy)Ah?>Y2)CSOXhs#*%!CC%(R4L+4Tb>%_4bd+S_ul8j2 z$r8eD^S`Qgew$e67j90cl$Ok@7Xpcw5OSHE5)H??JM5?WuOwiw#UPW@T4pPd(kWW+ zWQr1}z7!DNGC{^>VyOfdi9WVX5+i5G$%s%99bHn6CxLS!xG#ANNoB$V!UE!99T?Tw z4%?-|17m>KD;*|N$-Ye?Vhho7yf%5Q-!CBsGLGFdWUFOQLOcex_{z#aQ0}>H?ns|u zN_Gk-mNREEZAjP-0UXm3@ni^;6qA60KfU?#>i#av*qOv@s7pH*#+JpyyoW&g{%4S> zJ$lryXliSvWpS2gIN1ueS3KG8SaM0T<2CbykVvX#P9V&4>LGHJWk~CUJc(>|D9r*a zsEHwq^=hr`g;Y#<6IcVzEo(PAC~|DgHc&{W)rH z89sfUa5*dlT+KG0StI(bfSsUUd!BRql!|QD8BGdaasH33$AQ5jxPYqh>d^M-b7iq) z9|u(hy^H2rO(88Xe|8+{?U+6WELV$?v}9$tvQup-pIAEODR4jb7QU4gjUw%W$px*`w>^fI40*c*|ON5OZt`iSTGKh-W|6%#6z$UEc<`SkZZH_ zHv0+|NYDmLb23pjKDg@DlUMd!QciWN=8au!U744Nr@iza)k#RRx)KOA%~>6hc}FR{ zRVRW>4?t0!qxj3`97p>PX}0d2F`B~FmGEU%ASh=f!dOCPrw(@XbIZP1qy@Wh*WU(r zuZ#PV91zMi+JPf!6%Et-yt{Z%CoR1;1l6OyP~bb6uK8$VNTti6#pJCHqhOABefl1G zQCxi0-o?O2WMZtTWq5jlBus0qmtA7{$){Kphf^6*%PZ4`cE~TeStI%{7ziUC#;@*X z8~cu*Xl3Jgx0ic<3Uhp}{>iIz|NSki8dj@%)Of*Dh(T|@SGgZSYcf9gX}QU9qF6$U zQMD{sjL_TmuHTIN0fwFbOYiCluzQ|>_6UkH3ty%DHHDRM_WXN4XQbUUjUC9UnJeZI zxP&kFqztrY)_etP_K0V4^omq}Se;Ockg*@R8woM?v#Fj^&x)Op%A!}l0OMU+b&PQ= z^QAG;j=yG>tBmCA9IO8Q$(4tVde(8qi%)1*NIS-1m$xsY)W@yAyt1vS?DbMqbQHy5AZLik$1#|G99EaUwF5UYdF1HeQ6!)q zxeIclWxm&$gfCN=>$C$;7|F{Q9VyBvK{Vpd`Qoj_Y|6b-MnMPd)F)iME#=Rk$!jI@ z-u{CGux2h3`84!tk_Z*j=gbWY1J7F>@=G^z`d1~ifM%&Oeoz~8SpU*|euAMsjx|MP zrkw!WC+fKXg&nG_2&#bgi8&O#;|%6$+a8J5DNpglgVyUcvB(08-ZBQI>(*@82B384 zck;v}HJ%)%w;|`SO1~|!dbyu+lOJaJd<8hal!-$D%B72>64Pkp2XeX`aVcmvgiGYzir1w zMw2?e8{9hB?r6pxhS-2xh_%8h<1<{|Ku(2ArCpR+)hM^E+}TUmgOo|Mq@W86LUgM>afxH)u<+^ z+=hTyga8$(Cdjz z_d7z{btHyI&8r&14RloVpF{e;;&ZawFRL7ya+(k?h>jbJpieGj!E+G5B>oTFF#N + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + H2Database + WebBrowser + + + H2ConsoleServer + + + diff --git a/h2/src/docsrc/images/db-16.png b/h2/src/docsrc/images/db-16.png new file mode 100644 index 0000000000000000000000000000000000000000..d5340e557f3a96d7ea27a03919ea1ec14fe1805f GIT binary patch literal 547 zcmV+;0^I$HP)QI%`NR*Ji@Q&v>hxeSLNGTKkLju5YoLd!l zI-TEuKl1${u6siQf8pEN-Wb%Pxu}8Jq#kRkos!supG`}q1Zy81z!_d&bu)s9}ve0Npe$24q!IB%V-qlc9}o60a26_V!Qp4-QDLbFZXFQz_RXB zDqR=OW(B(460ZB6R_j$3VA~Ja+S)_PJ@)tCqN*AYr>+79rnySH{eoJpp1*}k#bje+ l8`!@2H%cjyQr`NVe*qp(lEtU7gVF#1002ovPDHLkV1oQX=CJ?( literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/images/db-22.png b/h2/src/docsrc/images/db-22.png new file mode 100644 index 0000000000000000000000000000000000000000..95ba9c7be15bae06f2557ecb45b6d3d3dcc9f785 GIT binary patch literal 1239 zcmV;|1StE7P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L007ef007eg)Y|U(00007bV*G`2iXb~ z3M2*KJFn3I00dV_L_t(I%YBs1ixg)Nho5@;YkFpPW;groI=I;!gs?`ktI2{0S%R#I zxhD7EKafK_1o7a>OORaRITsJPhj@}i6a>vKK?9mqS%R(+_uKjCo}TWva+sdk)r1th zH2rqhQ@<*zO02aCAR-Egs-mi7Sw@y+SZe`Q5hK=S>6){F|U58@P!I%!%bzNN7#TWyC3n(C2mf<*t zG_`v3<{epGy~nLvKl9?nV?B6Kn3^)WwN+zfr7a?FapcGcOizETGc)hXp+m!fOR*Sg z5Cj-wR8-ZfDk8A9wk}t$e5%+J5a@bHM1N+oeSYK809f1zBCSzP=Icn%EUI9*UPSK4`sM5sonHW(Zn z=iIqX&Yr!>%*+%)P{8+nTykqPYSinSz%(!jyh3#skq%HbLZq1kn}{TcBtV^3Yl24O zJ}+Oc6NY`1%Vm0sv&NDo%KIGM_FZkzyY8^p^!2$v5qloh&)8~PgH+nWTcMg z9R&h5H`CXKN>%s3fb0cnA(EkLC=@~_C&vUf0Vh9!KpRyM>9lHRVr`GJAN*R3j#I}0 zvVE~le*WLgzEWss2+4<00YL;r$oEN(7c{?r6Vr8m<3{&aMS8XW_xJtp@?X`&bR+Cj z@P>Pjou{FBJ*AgPcX1hGw7cQlZWwD-R5QRxH&70g*R$L&Qv?*(b+J|nf*%;Ni}IDh^p zsns?S8AhZ8cp?JUT2U1d>2!sN1BhI^_B$6Z{=k_trwPMQN~IE>=ZRQr)mlpwMHrj)U*_X{GXx zOimu+=+Uu5izG?3 z-EK>_?;OWb*LB7BeF}vFK@dS->P)F8938;hv8RxAw11`u)Ih zocM19ZQK4uQ;fT0Syn3XdcA(*O#CUp_dB@m5zXdDqUbB8*&z%s@H`b&U7=E`QmsBg z*9{~|8mlo8aC-WMlan`;N?C0C4ze5r3LpdD7!E&ie7wcp-a{RQjob$=fF45ltgiN0SQxUu|1LQoj@z_af^vBUMd{xFfX`siMTj}h&yOZP^F%-p zd?%lG@w@=Tctj?1k03ZB49~c{Y!d_@5aI`h@tFDfL2>|qszwwFbAUppe{K_7y&MZ}6q zl-fSDg+M_tujVn}tCb4ASRY#~NPLqBKIBDx@CpW6LnKxzQbn4!QL9;-%w}`hxlbSB zOm;V$tNq~PoEhfK|9{T+opb)55ClQMG8P2Es$Ok^Agt=;Ca(tU`#zrM;rsrQemRNo z$D$}!xFksx&(%bh5C}m-FxPc)T^Gl3a2zM<8-{^l80flACX=ZEk!2Z4l8D7($g&)@ z;d@aOBhqp$aL@B_90%LBDHICaxpRkGw{9^yI*M)2(A+H2(UG9FRb*nK$k^CCFJ4GU zQkL!8x3g!@9(M2EO*WfFRaN5gIPrKKMNv=`B_aYKggHuQ$=v)fFXbmm$PTjGBD7G zAhfItY`IntY)+lZuyf}nIy*aw#bOn?5GA4S`?#*l)29!qum2uUHoWxJZ7L;WS#wNI zPNHcVx~`K8lfSYU+J_|0;z-f%^J-N~IExoUE)Ees7^Yvmcv+}q#tiVQrYrL%?$;9dp-VJ!= z@Kj5)3IZX!jc>!%$CXR6kVFLmQ4|RR8O!oEKw^1wT@TZ=P*oL0QHaH2i>ncWhXf8E z{)+zo->N`u6|8z!e%B8_{*0L?Tg1q8wb7WsV&?#+fs}aplUB6~NbSH*Y@WZ#Uh4bFgQ5KwQJXyn0U#71D~>Q-)FS8 zs^oGmO-(j+bp@uU{~(|LlYIUquU}7a_wE!oZb%41Gdp(dVArl)D#%&dyRQl~7fcL?S^VkwDjV z+S=OKx^*k5R0>_!NvG4))zy*BW+PQx9)@M>0@E~^pPy%Tb~f@*(O8yMp=yF4h_X5q zo$+{_WHO1SX=s{8DwT@rg7O@Q2vm~@D>3!s_}kJcz>G_aDdAO87d^dN2S0002ovPDHLk FV1ibdi248k literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/images/db-64-t.png b/h2/src/docsrc/images/db-64-t.png new file mode 100644 index 0000000000000000000000000000000000000000..3680773d11e9b7a843abd42abc59be6c5c7dfc81 GIT binary patch literal 11867 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/h2/src/docsrc/images/download-2.png b/h2/src/docsrc/images/download-2.png new file mode 100644 index 0000000000000000000000000000000000000000..00e506a1d550e577d7b9591c9311d932847259a3 GIT binary patch literal 2673 zcmV-%3Xb)OP)EXxA|)ObwVy?1+dDWULq;RkJPvs{-=g zm35!&-aV&(+`G%N4_?|#&&-*#&)ohtab7n!$n4JbVi5+2V6qNF4DA`!y#jsSOK>){WsoB)ZZ#Gryerr&_MSauA8#%zE4vC{ z2!Ksen+8*REMNuzw{hdf>D!98RB)lPH|q3z`9Ax7KM3~-8W_@dGL!%uqrkG6KPPFi z%Y?&+)Y}RR3u^!jNNPKSDIE~l4SVFskyn5GlOLTj(yoLvgJqn}Wf~1c00aaJ3>j!L z^QPxw)vRBUvvqalxw&tB0-z6oIT*AbummgLxN&2AN$JkUZr82E2Ezf?<#PE!#_}o1 zRK0~6VX0tcaw*D+&D*MRxDWRYS8%nv+HV4&K+?1{teg86Qt`#{{TnuHI1Hdyf);$C zM*&NID^E&F3cq#d`akX5;P+1rZsS}oS3o6K&-gvQ7qKXyOZhfR2@c)$I=MSzw{qfJ6_wCzvT2k67L3A6Nze-*ly^SoGO4js$_F6nHT#eL}FJeFiD2 zc|rYJ)7ON?N5`41+qdk@&dyE+paQ@HV7zj`2(Z-TYPEXKjD&>3v--Wf$7g;0Q5sqB z69xtV7F<9(ZuhvLJ0{cHI~05N?)f!vES1eUv+cyRu@08G}FG})-jDwbvF>m zj<+ahTFcqRix=hO<>k!>5CtEVy%m<%*4Dlr6BE0t*;pYDxWavmPZ`$7j`#Dfp?hfQ zYsU5+TR#F22OtbyDl=XqH$OY;!w1%8I^^sb*Epu2FhvkMQlfFMl?&|^OjhQ+jO66x zWB?Hm_bm*(q-Fr{s%TZN&U!^YR$_1)pVAJ|%bvF#Ur{=rw7@((M2m`wmcnQ8c@(a$ zuFm4+AyE&lHzWg)V(v(Hzc%8-lZ76cbU;zO;J@u{V&7c4Xn_Q*@F)y%zrd!ar?2R? zX`K$iCXtQ&Mg9h*$xOBpjC_BT0RC%5+xu^%rKP_j0f$1|fXO_-3WXx+sjHg{4(Y%6 zrqY5h;?a}nGj%{27QrPZCdNt%j{=tSA?!@EplAGGgZSB$T$m!Dolzo4i_d5s0HqX} znVInbLLf#8nOp*9cmlN2_G z84cQ_7#4;-zc09)Fx!kIB_-trj6?~UJH9b2!z!F)*iTUvpNNd;HItV!KucDoBU$C2 z&ilx+i9RR>)vk!)`QGS2d^?|R@X1w}bfCDwV(~9bwe}hwS z__<+ZcnqV{=}g|AcLNif)zPhFqx}LpaS8a>qkVX6zW3Z=T|~f%TYJ$yaMN$U9L7gN z&T=xXR%^h>X&VegG-9{edz4I!5GbJA1uMSNl|papd~UE56kp%{9RKM&A8=u-6mcNL zMKv0Y9^wg`0HAJQp{uLwPGVRp2^P>z!+O-TmB8fCKNsK&_bbuZb|P@}p%k-U$b#-+ zyO9uL0Wb{dtmg)Hm6nz^C^;1yra2%SUPa>{%+U>>odWA(@}@hcMtkOdS}7Z zgoWfn!zC>M6U17EYZP#OeZ9W7zvuSL5kD9+R(3wUgRk56!$q7^g1y&w6(_Fm^-KNU z0)=wnm>r+VR8>{o#K>vd0}P1%VQ+0|ubrz}?vznBXh5eFxHoVab-II-&UPJ!d#G&w zt2o(Ushrp(wiqaYDL@oSGy;c`dS zgkk$l9jIvD0jt9_X6olz1y;`efPVJr!A6MZx*>=~7D6c_%u zCUndcN(peT{qMNZeP&$Pr*=Imt`);z=^J-{vMlu-^!E;!N=i!3K|DR1FcKw3oj3#l z*8KeZN~^`*lasiCjAxuu;FRVQv>@T}@!CkCT!5Ea*V;HiBO0C4esB^ukl3_e+D4CSVwFlR3f zlf_xyUYlPo2!aMc2YeQrn=@#s3P3o3_``<}Z+Lh8&wh8gvy!>=;F!$M4?W=GYMvPC>;gq`)0IdWN zmz|yc+U`BOf3sxC;-xM9&A9mB7}NEn#SapJ7L_!Ha^c8MUV#;JKcM}+1E!LlyUwW9 z>hl0vAuh71m0KlXPeB{vAy5S%J})nC{`MVPKg!CSmth_@Qcd4wR@;9S15Z0JWE+4W zI6aVId?X@6)oKkI zjmAI-F+aI&?FI1&9x9C?((A1BSr^2#It+0GW5aiG?KKAY|L?`( fIPl-u)$M-)9sU13PWFu000000NkvXXu0mjf(_r!G literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/images/download.png b/h2/src/docsrc/images/download.png new file mode 100644 index 0000000000000000000000000000000000000000..51748dc3b268dfb65da58d17bc228100951b4028 GIT binary patch literal 745 zcmVk30Y=RzL{xwI`_WQV$ft5#c0u8T)2nt`*F`X_Z}o7 zB63~jx~}Ug*Hr*SL;$dEUw?#E0m+O^r%hItT=p<6?dpsnVB}>}Rv8Y}b%w9zNu;`@X8gz4-P6~{oY54erdOtX%wQT~fb3s( zZTM(XrJZe=(b#xoe}iRNYsNpXv#~T%B#O@sTq@zkvnu{c&Um=6G+`P>LZ$3P!sD~1 z-o@*9xVhp4=R6Mr0D78lO?{nx{bA&oS?%S9#e}Kp26Evy_l81q@p<#Jw>uIw46)vo z`$t}#iFLi{otTLq)~f_^fm{c6z`HMLFMk}Fd)D&AG>sffF2Zm4AD+73@gkb|?u6JL zMpz_vt^AZjgKv8q?-T_Jv)$bc0RWYSmA9LET6-_})4?*Md^d*fJs-GvJX%$JMAP!< z^MCf5lIm;qofjWmv6Sy}-;K8Nw))aAV=S*2HyJry@4KH)4xB7+IeVy;bH35Y!T&zB bh=_gzuQ*?CP6}Ym00000NkvXXu0mjfA`(kJ literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/images/download.svg b/h2/src/docsrc/images/download.svg new file mode 100644 index 0000000..4f8bd82 --- /dev/null +++ b/h2/src/docsrc/images/download.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/h2/src/docsrc/images/favicon.ico b/h2/src/docsrc/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..fd5e73a416cf2cde3a3e5d9e530e53c3e74742af GIT binary patch literal 4286 zcmcgwU2IfE6rO@~@65JbQG-8;l#uv`L}Pe|jiq;PA@~3(_zMJ+4SVlh2(gXPM7ty! zjnVo9{y_?8p{C38cGsYeUfyHtl;?MUd$g#Fi@@;{1KEQYmbQLrnNd6Mp zjwF8;-*3-_0-5z^^Q*AISrz)uN3eSZ^cnDX6uVnjmwH&MxfXsLhg{8tBA>ry+A!zE zZk7q9coX)A;&6U*`&~9x+BeRBD}4R!-}@J5z&nvf z6f@gaDBB)|?oXj73bt&vZ}`0iYcGOs#=+;83Z7Uy+gg(E>&)Iw*p1wadiHid#oDp2 zFJ}1Q@!t{ao_cGDAIF={+!uk?3pQr&A>TC>yTub)W{S$B z(>nGT`JzPeHu+fpvSs7CDL3bV=k!NX>@j(f$BS4$ZQ8)u>@#&}9-q6%FWYx*F8E2V zD4g>E?O$VH&wEY$*dH&`rR6lj&x6SES4UdFJLpIv$ zv8>kD<2>*c_I;{mr?dS|*s^ldd4c5E`*y_bnASDNdEq0TmaVg}cQY@gI7)J=tM zZ|ds#0^?K9sNv(jpN{b*9}YIOKB6(raiQ=7pKpP!8#C}F|2)ATvwMm5AqD@>xEE1B zvIzBm#Izrx=77Eg@B9pYat49#>`L&*HJ&}D;MdMC&E_0eBICGG=ORp&*GpX4WG z;|W!@%pUPBe$CK>zPMoP(D#11^bW!zOjAzxm-?{DYsMVdhXN_yux(R6Mt%-Op2*v> zh5E*s??kSBPw`K)nU~^T#O4z2LuZk2hk2&f%X}@Pqc9Y-GLGp|$bb4n2Ohed{%Pr2ByW`40T18r@efW?J7Isg?k@ zH(o3GyZWtX>NGmv+m(3$|Nr|0wnmX7NAb>nuHa`LGJ6)ysfSPix<7>5i$6$@QwGq*Qsa`Wxq<1>gVx literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/images/h2-16.png b/h2/src/docsrc/images/h2-16.png new file mode 100644 index 0000000000000000000000000000000000000000..747340ac7d966551c9c987e3cc3c2b36bcc0273f GIT binary patch literal 675 zcmV;U0$lxxP)YCf6lDVw{UCFSU`Py#`cUH%Tzm#`Xii%kGJ{9YVw=3F5Dw#6? z&X475I}rn{O^$vR%?~&~(+L2n?C5O>g5a<>G5pkRj~_#22FQ7nLy@A$jm4)yS`n^# z6GH<4PFwtDNy!jzS@!xP zKW8s0)C9<(4RKdjXeZYe)l4QI$tWA9D*DX?VxBvh+VeZm53aRDAG+$_1ge1PY;)~Q z$X}RE+jUJH;kxDzf#@$p98sCeFV2n^m3Ce8dv9v!ffK2}E=V7+V1?J)a-(-vH=c51 zx9#q+`w9DZy_X|P+q~Nnm9BWBzy)ABWX#!>SNGCT`k5NLZD+5YKy@j*JN>x0XMl=W z{Iwz{3iJ~~9sp%-t3Ux&v7Y+8a0*2Pd{m#P$PI%YD9=@7{x|=@)!{eLzsdjr002ov JPDHLkV1lTQAXNYW literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/images/h2-24.png b/h2/src/docsrc/images/h2-24.png new file mode 100644 index 0000000000000000000000000000000000000000..9f682d6861ec6c94be48c08a9797daa5a0df7d62 GIT binary patch literal 995 zcmV<9104K`P)VpxHKAC`#WX@cA z_VU4*aVFOZ@q1g}+WY&~TAaQ25w?LBOZFlRf%E~W;sS_J1wdsU>=!|P=8vs3WK-e6 z(I?dE1rhZHxmJA0E%534LCd1kZ+iYpxliF$6)A}uW`diu_jfe#sie zfz;7xJauJPqMN{rrKkOPYN#F?_;==387(bUlY2D?mMd>q81ase^Z-Nv)Y@?ebFMxX zFP?q{wAPK3g51=BW|3%L;-EO@Zaoe|JzTjx^Ai9I*{ai@JRepgG};F1KS)7PW_4DW_LO zuh%1ivnZ|#Tu_8-mX z04BIOdtb3Dek^fl*W$`V@Z4i(SjbkrJ4+W8>XZ0ORmY)r*%=qarn>-~Xb(yBaLGA7Nf$?|sEetdYp;$8)wuTei(V@{QCP5#=_ z?Z%CXV~8_G$UDWt)ZO)YfM_iJq=}yi`R#aQA94qqWrjLHt(d0fG$_12e4A%m; znrl8M(ZR$EV$E0Eby&m4n;O*N9UtkDit~~}KZ2?Kwp_N3S#qq+>?kjM^ADLIPZYa; R?3Dlj002ovPDHLkV1oY)(5wIe literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/images/h2-32.png b/h2/src/docsrc/images/h2-32.png new file mode 100644 index 0000000000000000000000000000000000000000..c7af904cf1ea5216687b613691c8ea53fa854c9c GIT binary patch literal 1362 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 Y0hFY<=$WofCjbBd07*qoM6N<$f*;= literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/images/h2-logo-2.png b/h2/src/docsrc/images/h2-logo-2.png new file mode 100644 index 0000000000000000000000000000000000000000..218fe975bd78f1f3c37538a824ac90fc278ded2c GIT binary patch literal 6617 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 O0Gg-=$ZA#F@c#jbc{l_B literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/images/h2-logo.png b/h2/src/docsrc/images/h2-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..fb65afe0b56cdf5d7f1301a192bb94c55bf6dcef GIT binary patch literal 12780 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 + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/h2/src/docsrc/images/h2_v2_3_7.svg b/h2/src/docsrc/images/h2_v2_3_7.svg new file mode 100644 index 0000000..c2dc03d --- /dev/null +++ b/h2/src/docsrc/images/h2_v2_3_7.svg @@ -0,0 +1,61 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/h2/src/docsrc/images/paypal-donate.png b/h2/src/docsrc/images/paypal-donate.png new file mode 100644 index 0000000000000000000000000000000000000000..793ef52e5b7b70f070af41df7ab2edcccb9f15a3 GIT binary patch literal 6172 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 Oh}J88wK|n|pZ*802rtzD literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/images/screenshot.png b/h2/src/docsrc/images/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..f9e388fc3937b5bd76d0ed167ce6aea8928885ab GIT binary patch literal 113875 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

    tnDo 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 literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/html/images/console.png b/h2/src/docsrc/html/images/console.png new file mode 100644 index 0000000000000000000000000000000000000000..39a7ef53db1371059b836bf23c01088d9fef53ef GIT binary patch literal 29569 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 literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/html/images/db-64-t.png b/h2/src/docsrc/html/images/db-64-t.png new file mode 100644 index 0000000000000000000000000000000000000000..3680773d11e9b7a843abd42abc59be6c5c7dfc81 GIT binary patch literal 11867 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 literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/html/images/download.png b/h2/src/docsrc/html/images/download.png new file mode 100644 index 0000000000000000000000000000000000000000..51748dc3b268dfb65da58d17bc228100951b4028 GIT binary patch literal 745 zcmVk30Y=RzL{xwI`_WQV$ft5#c0u8T)2nt`*F`X_Z}o7 zB63~jx~}Ug*Hr*SL;$dEUw?#E0m+O^r%hItT=p<6?dpsnVB}>}Rv8Y}b%w9zNu;`@X8gz4-P6~{oY54erdOtX%wQT~fb3s( zZTM(XrJZe=(b#xoe}iRNYsNpXv#~T%B#O@sTq@zkvnu{c&Um=6G+`P>LZ$3P!sD~1 z-o@*9xVhp4=R6Mr0D78lO?{nx{bA&oS?%S9#e}Kp26Evy_l81q@p<#Jw>uIw46)vo z`$t}#iFLi{otTLq)~f_^fm{c6z`HMLFMk}Fd)D&AG>sffF2Zm4AD+73@gkb|?u6JL zMpz_vt^AZjgKv8q?-T_Jv)$bc0RWYSmA9LET6-_})4?*Md^d*fJs-GvJX%$JMAP!< z^MCf5lIm;qofjWmv6Sy}-;K8Nw))aAV=S*2HyJry@4KH)4xB7+IeVy;bH35Y!T&zB bh=_gzuQ*?CP6}Ym00000NkvXXu0mjfA`(kJ literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/html/images/h2-logo-2.png b/h2/src/docsrc/html/images/h2-logo-2.png new file mode 100644 index 0000000000000000000000000000000000000000..d8025aa52debb541dc1b03013d43221f742e5651 GIT binary patch literal 4810 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 literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/html/images/h2-logo.png b/h2/src/docsrc/html/images/h2-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..76402f086fee57b12e864ddc65c6fbbb2a7c80ec GIT binary patch literal 3361 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 literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/html/images/language_en.gif b/h2/src/docsrc/html/images/language_en.gif new file mode 100644 index 0000000000000000000000000000000000000000..a48f4adb57f885b0300807e814192fe8f5bc3c1a GIT binary patch literal 268 zcmZ?wbhEHb6ldUPI3mn&_wL;dLPGmRMCNQdtL?i`!+q+6CHu})RT)L@y4%{yAfLk^ zo3iEXTLzWdJL2N2XP2KjbLKw~%$x}%7>EFhKUo+V7=#&gKx#pDGO%_&_|lV-IWI{` z<-meH4X5_C-05q1;vsn1L{N5pqrf*YW!J*%%u>^B+fR}K9EN{_e$7Kd8bU6cxGWNDxzx0*(3xSaRc2)AT4WbBLTTk%TNmnQ*4W(4 zx~j~W7qxYlt+}o|gNhRZ1v#HS4k8GM9OM-K2|ZWu&Aa#6-b;-QRn=@V5Ww0Ab8~YB zgJEZ9$L)4oESB~4b(hPf(P*?jKbBO$C%nv)LT2sPK1nVE|?TAAlV|4j`3E{TLRJ zN@FII55wXzSp>kMPz31oRe)%Dxq?FZOCrVid_&TW$CEcTnaO0w^0I4s+N@B70St1v z7Qkw?Mj{c3M4|(*EiK6awzjtX$(6P?buuS406cVh0>hLfQk>7104S55?d?X9$hW#0 z_j=dX)?%?(JRaZN-2CzB1tfNX|A87#O#$eH(%sVTC$J~c-PwWmz3zY0doYbgKoC5h zJM{ho9wDFvBIn64Z{(|Bm~(3{ihL0i<=0+#%o`=*O7^2%!3ev(;YNMsDbCN5g1hOz z$BP>4^6pX3w4FOtPx{Q4M=Iv`-LS}Pn z7Mg}j;A$q|nX!N9#KoEWcNo146mk*vAmiyYQh251)v+-&E0>myf5;%RuR|J*^y`3O zM*Oan$r3TCIg>qjK#FM-V-vXS!~LIM8tX128I^=)=AdjK9Y;AyEM$zPECG|tYWc_* mKx=C#ZwtyEO!egy7uAP6Dc!A*oniX3@A>x*?dcwXv;P1f;Yx@A literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/html/images/mail-support.png b/h2/src/docsrc/html/images/mail-support.png new file mode 100644 index 0000000000000000000000000000000000000000..8ff5349d40ce67ba353012320333600106b4740e GIT binary patch literal 756 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 Oh}J88wK|n|pZ*802rtzD literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/html/images/performance.png b/h2/src/docsrc/html/images/performance.png new file mode 100644 index 0000000000000000000000000000000000000000..368fbf77e67162428e3cfaa4de6bc20d4dca9799 GIT binary patch literal 2343 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%0DL&s>Vx5#0C1gBphT={>^zbT&G% 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 literal 0 HcmV?d00001 diff --git a/h2/src/docsrc/html/images/quickstart-4.png b/h2/src/docsrc/html/images/quickstart-4.png new file mode 100644 index 0000000000000000000000000000000000000000..08a02b5bedbd12d70ceb0e4eae7a6bed25e44a55 GIT binary patch literal 30387 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

    tnDo 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 literal 0 HcmV?d00001 diff --git a/h2/docs/html/images/console.png b/h2/docs/html/images/console.png new file mode 100644 index 0000000000000000000000000000000000000000..39a7ef53db1371059b836bf23c01088d9fef53ef GIT binary patch literal 29569 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 literal 0 HcmV?d00001 diff --git a/h2/docs/html/images/db-64-t.png b/h2/docs/html/images/db-64-t.png new file mode 100644 index 0000000000000000000000000000000000000000..3680773d11e9b7a843abd42abc59be6c5c7dfc81 GIT binary patch literal 11867 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 X=-0cfRr_4nL0bP0l+XkKBh+zd literal 0 HcmV?d00001 diff --git a/h2/docs/html/images/div-ks.png b/h2/docs/html/images/div-ks.png new file mode 100644 index 0000000000000000000000000000000000000000..70b1a8f5ac0e1ca7842f1449972a5847d2531d25 GIT binary patch literal 759 zcmeAS@N?(olHy`uVBq!ia0vp^3JeS^3><7gR>REbiwq1*`#fD7Ln>~)y~3S!L_vVz zg3Q^)mB#&P6KCB`5Z=7dS~K;2!-W2c(*xJMK?U zVSB-wQD#z~^3>=B&xLfOpx&hV-1o}?Dbh3Cuat}GK|yYX|~AIXFW<=j(K^4VLqB>_D&3Px54G}LqcU`qdX UmsQN5`5VaVp00i_>zopr0B*`{o&W#< literal 0 HcmV?d00001 diff --git a/h2/docs/html/images/div-le.png b/h2/docs/html/images/div-le.png new file mode 100644 index 0000000000000000000000000000000000000000..7f89f95729478d15c37adcb0eecb554da4300117 GIT binary patch literal 387 zcmeAS@N?(olHy`uVBq!ia0vp^3JeS^3><7gR>REbiwq2mUY;(FAr-gYUJK-EHV|NS z=zn&xXtRv2>e4RW*As(+8?%KOUr%3iU1wt02p#U<*& z{C}VAJ~($}yS#<}!rMY6%jD{B&25P-Iye91^uH-43qRMbZEDWgeT^09sR08G^~KC5 W-0p=+T2?rN9O3Ee=d#Wzp$PybmUdGB literal 0 HcmV?d00001 diff --git a/h2/docs/html/images/div-ls.png b/h2/docs/html/images/div-ls.png new file mode 100644 index 0000000000000000000000000000000000000000..cda5042f99779d1f92e2975e084244cf1c80e70e GIT binary patch literal 396 zcmeAS@N?(olHy`uVBq!ia0vp^3JeS^3><7gR>REbiwq2m!JaOTAr-gYUTe%cWFW$N zp(e^+w|<)cX`bwDvs7mr#fw_9b6sk>cW1gt@Gj@Irq^RNmu`!Gdu!WTxhWz~KRpfK ze}%cmZr85)O3r)y8s>ZsZ}|4}$BVVhcQy&me*2~O2GiV9seRKcBY$QYxAi`>ik2ooLiGqp(M}XYq`dia#E=PK7O=A#+xK?H}t46-vhfzu0cfcx;$|*6>J<%--He zWySYxq0-65zh|4Ywnl6AxAo-rNffa-4M=<7gR>REbiwq1*t2|vCLn>~)y_U<@tiZz% z(0}aW4oUaqgS9Doq0==s>8$6h?~q)-b(-&F0jG|Of!Sx3!zxdobof-@mlq>#lj_9-)KkPUw)tS zv32fLJBvNJON`(5ZwmW$Ud>wdF@wM~yNc&4ufP52`_g;;>))lPlBcyVeP8-x(J|?U zJD<;c->A8}`M>3XYUa9<`kgEVKYz0Wy)X(!ObGmlKES(d#e=39t&!V6p7wP0b6Mw< G&;$U4`)&#V literal 0 HcmV?d00001 diff --git a/h2/docs/html/images/div-ts.png b/h2/docs/html/images/div-ts.png new file mode 100644 index 0000000000000000000000000000000000000000..0b178e84f43b3078d47efb36b3d896961d8a9d3e GIT binary patch literal 727 zcmeAS@N?(olHy`uVBq!ia0vp^3JeS^3><7gR>REbiwq1*^E_P~Ln>~)y~@aSSb>N2 zg524~mB#(g3Jh+jY`wce^cXjXt5>DU{$06VnoGmN*P5=6;oY$18gtRP(x+9&5GmmI_n2b*ZDulfxPYM>gTe~DWM4fP~&2# literal 0 HcmV?d00001 diff --git a/h2/docs/html/images/download-2.png b/h2/docs/html/images/download-2.png new file mode 100644 index 0000000000000000000000000000000000000000..00e506a1d550e577d7b9591c9311d932847259a3 GIT binary patch literal 2673 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 literal 0 HcmV?d00001 diff --git a/h2/docs/html/images/download.png b/h2/docs/html/images/download.png new file mode 100644 index 0000000000000000000000000000000000000000..51748dc3b268dfb65da58d17bc228100951b4028 GIT binary patch literal 745 zcmVk30Y=RzL{xwI`_WQV$ft5#c0u8T)2nt`*F`X_Z}o7 zB63~jx~}Ug*Hr*SL;$dEUw?#E0m+O^r%hItT=p<6?dpsnVB}>}Rv8Y}b%w9zNu;`@X8gz4-P6~{oY54erdOtX%wQT~fb3s( zZTM(XrJZe=(b#xoe}iRNYsNpXv#~T%B#O@sTq@zkvnu{c&Um=6G+`P>LZ$3P!sD~1 z-o@*9xVhp4=R6Mr0D78lO?{nx{bA&oS?%S9#e}Kp26Evy_l81q@p<#Jw>uIw46)vo z`$t}#iFLi{otTLq)~f_^fm{c6z`HMLFMk}Fd)D&AG>sffF2Zm4AD+73@gkb|?u6JL zMpz_vt^AZjgKv8q?-T_Jv)$bc0RWYSmA9LET6-_})4?*Md^d*fJs-GvJX%$JMAP!< z^MCf5lIm;qofjWmv6Sy}-;K8Nw))aAV=S*2HyJry@4KH)4xB7+IeVy;bH35Y!T&zB bh=_gzuQ*?CP6}Ym00000NkvXXu0mjfA`(kJ literal 0 HcmV?d00001 diff --git a/h2/docs/html/images/h2-logo-2.png b/h2/docs/html/images/h2-logo-2.png new file mode 100644 index 0000000000000000000000000000000000000000..d8025aa52debb541dc1b03013d43221f742e5651 GIT binary patch literal 4810 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 literal 0 HcmV?d00001 diff --git a/h2/docs/html/images/h2-logo.png b/h2/docs/html/images/h2-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..76402f086fee57b12e864ddc65c6fbbb2a7c80ec GIT binary patch literal 3361 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 literal 0 HcmV?d00001 diff --git a/h2/docs/html/images/language_en.gif b/h2/docs/html/images/language_en.gif new file mode 100644 index 0000000000000000000000000000000000000000..a48f4adb57f885b0300807e814192fe8f5bc3c1a GIT binary patch literal 268 zcmZ?wbhEHb6ldUPI3mn&_wL;dLPGmRMCNQdtL?i`!+q+6CHu})RT)L@y4%{yAfLk^ zo3iEXTLzWdJL2N2XP2KjbLKw~%$x}%7>EFhKUo+V7=#&gKx#pDGO%_&_|lV-IWI{` z<-meH4X5_C-05q1;vsn1L{N5pqrf*YW!J*%%u>^B+fR}K9EN{_e$7Kd8bU6cxGWNDxzx0*(3xSaRc2)AT4WbBLTTk%TNmnQ*4W(4 zx~j~W7qxYlt+}o|gNhRZ1v#HS4k8GM9OM-K2|ZWu&Aa#6-b;-QRn=@V5Ww0Ab8~YB zgJEZ9$L)4oESB~4b(hPf(P*?jKbBO$C%nv)LT2sPK1nVE|?TAAlV|4j`3E{TLRJ zN@FII55wXzSp>kMPz31oRe)%Dxq?FZOCrVid_&TW$CEcTnaO0w^0I4s+N@B70St1v z7Qkw?Mj{c3M4|(*EiK6awzjtX$(6P?buuS406cVh0>hLfQk>7104S55?d?X9$hW#0 z_j=dX)?%?(JRaZN-2CzB1tfNX|A87#O#$eH(%sVTC$J~c-PwWmz3zY0doYbgKoC5h zJM{ho9wDFvBIn64Z{(|Bm~(3{ihL0i<=0+#%o`=*O7^2%!3ev(;YNMsDbCN5g1hOz z$BP>4^6pX3w4FOtPx{Q4M=Iv`-LS}Pn z7Mg}j;A$q|nX!N9#KoEWcNo146mk*vAmiyYQh251)v+-&E0>myf5;%RuR|J*^y`3O zM*Oan$r3TCIg>qjK#FM-V-vXS!~LIM8tX128I^=)=AdjK9Y;AyEM$zPECG|tYWc_* mKx=C#ZwtyEO!egy7uAP6Dc!A*oniX3@A>x*?dcwXv;P1f;Yx@A literal 0 HcmV?d00001 diff --git a/h2/docs/html/images/mail-support.png b/h2/docs/html/images/mail-support.png new file mode 100644 index 0000000000000000000000000000000000000000..8ff5349d40ce67ba353012320333600106b4740e GIT binary patch literal 756 zcmV7VbEfu|^h5Uj@Y8MU<nAfJc3$_QWA=2mpW^p>rOU0^XOM z0mv8{9?~oVLfxF6x31*QPjDW>DLvV9|U#Qww=Ly z2Oq5p5PBW~i}U_0w5xV5hmiE&7%G?C5DnT8Kp~!kFV(HM35Y)`_TNS-C#j9ud1$gH zTap88zba}@4u>p$!=l;fLA=@&cB-PjS4JNCon%Hh2)Aoe1_ z%t)K+W=JWg53bY>5IK*O0(~%EuM~IdsvYmkvNCS!K}(WTg=!E~J}ZsS#o}lcT5ko9 zfCWl`tz$8f8$q>-pFm^WW#mnOMA#PGyjjXN5!G}ca@I_FG{;?r?W%BPv+E(c^47N$ zNrMQgis788Ey~pa2)zagOW)cw)v9fo?xN8T>Mr9Os4uF+0{%4Bm&qt|{~7&w?cWz< m=IghLmiabv{;NCbuh4H~-)WLS7fb;F0000h?ygdVsG(u?#C(tGa#l1m8? z0)!%j5Q>@rLMYNpD9=0d{(|@6?ac1Z?Ck9BoH?^I=NE0LuR(j8?e?{6*J!mg)r|hk zbN_W3>OW)jW8tl9*O-Y~YRV@5^ILhhOc*sThXQu$(W@s;Se`CfAiK0Jz1_Ve|H4nv z59|i618zLH8}*M`CxA)$<-K0dYbz$Ny8$;0Cf-l*xWwgHF&uK)`RaAcDkg6t+IMD< zRS~RZS#|lZ%UHvA)_QjO*XzzqO^B@WE z{Uq0Fp5GiSojul6<6al*>l|JJIuf9di=XJgbhL%gfN=_7m9+86L8h*sIkh}Kkj?AUe@+1S(zzlfwl>>u0i z?W17#zwtuXs{g*|PvEO&(+v$fo^F>%W0y@;3^TqNtO40MeJdPAX*NcTaPQXI|173g zQFjITUcUi#u5@jv*Ws?v(J>8cERVYXEg$}&Ci8u4jJ;`dd&MH9xUToIZE966)KK-F z+ein0quisyovq3{Qi57z`R76O)zg!MjeZ(R0j_TfYuX^A9>bzxT^e%~Y zh;B!xe+bJh%X$aBQjC!wF#l$vStTPjhL$e;C$$EoRTtt;@hlT9K(@JoUu=N?&UqVLN%?+w?8p53sObhM32VU96FZFkDb3Pm?wkrU};wRd} zSZ2ZD#Zbm}*H9Cwxmuz#(9B}Yp@xA#FF%l3Q+p+!IqB%lnzM=N$pW&+R)9U{jrOKL zZ;_7BHNIPkaB7MWzO2exez}U;P6Cf#jD;wPxVrAom%fq6T(UPyC~2X{DB-yghBZqK z3FkiUs$IugreRW#tlk?t8~24fdU7!74(cU}4jZMQ)FSY~ma}iXeA`6DG+r@2ST3Qj z)7nKn%&94n=~^5rL^FY(A>_1okchYVZJ5!_yZC~EITLFoWtHL@;o--A*mUg!)TP4D zNB2wLH+W80_9h4$LfTzSWpEp%`4q98DmI+>UU2*#MMP+Hn zf?s0(Vy?=yq5lWaw4OUer8v0JK*z%$Z%5z=+YT}ATlvLS(xKaZ@v5vOZzhY|elzvj z)Q(~(CL^cC<74x6G)`W{fim9Koayy8Uvg-3u2?lJ{IEJ^jcw$@|FO~Jv-3kFe&S7r*R;l&x8dSU}CS`@^c&i_7UG;aYwe;O% z856R$7-nW3GEc;+?Mw$64) zsZ{DkjgK#ZE8j51s!DV#GByYKoznT(i#!+nV~!I4o57Of9DRCSfHYotcglz7Q-)QU z7CoP%%aV3TuG-eg0DuATAcM5;d*C358)!NG5lvR`1jp>r4`3F01bJZ229+)iXRr{U z@mHiwp>c}sx=^Zc4Z$P4!~ORHjn<-RWyqTVmCnF*4Ze%txgsK=6#E#+vecc6)S-(s zbt6A@%XCq(`G!rbW_2$n(UpC2kWoDiA|yF- zgAJ0{K1LFEEEAo)=cfInUCH8^1z=*?Y?4OajQ0y+h?#Xn<8w=D!M1cF$#HP^WUV^G z@Tthk3;f#m#6Cu1nMh0#|A5(41}f<%BA1Z!Zt6JH?YK-wf6_vrxBG>kerZa?MM#94 zHahqR!x4|D`jHG3z_Q5v*k?u@EtdZv!`VNaD;fT&=4~;rRF@QKeht(Ou2GYi1WIqIh`Tqyl@p9gx`=*zZyu=lzK z=V5=Xo?yb@P+`{OF9*uwd|Ah#_k)ru=|0av6*if35~iH2mQ#3b@v8gRMf0=L(Czq;Q<Ym?v9k=-5nI`?Q zd9S_AsgYYMH=?si?Df9?vOA1TFdEz-q#KN6XiFBtV)^hoL``2u$*cz{>XvIF?VHd_ zSY>w)T+urDC_CZR`kChwWv^TpK|XQGJpQw|W$E7nH;b@@w9w9I^en<#ZN8^l$RWlJ_V;k1gh4mYmD*CmD$Y zNf9ogDRX_S02`6Y|ML-R=mP^aCwnjNT6QoQX=tU=QTJBMx#CjEC?wu_2GqVa__ zU=KUSFx>luD>;u-IoZgzA|T^~EY(Vun1@oLn}T}?!gzqr5TV!-CCQoX*8E>oaoT|? z?BvKVITjkN)q}d3?kHUG`7qmS zWz>`B^X0Y-=U^Ca=P$@o3!ht%einc7&67x-%zd6Lc`}#sq6` zilwFeCz);u)pvb(I06@*IiO^`;V9*eDsxH}jC|^lIDJgE(wh27Q{B?6;Bl)H3du){ z8Er6=4YrP6SFLei+}xjMlZDnSu9RtJ?(#_yh%MK#6A9fa?ltT$-61S4&@D6zzRZ5- zIG-!}UV$D0&!m!_5K>~SCt6968lM>StIk7b?oMyZ*8P3}^S$pZCvM@Z5Y0{eyqqUx zGG@4NWtZlDx0SFg9c_`mMJTf`V5#LreU2ubBWTc`;_l?1^M27m=4Xnbg+lXw_7+0U z3Npsx2^&l);VYmkwV|2kMY5w_Su2tlw^C%slnAwi7<--yKC~Xz(*o#x4IF+xa_{P8 z=CED)SxV{&h;NGT=9j9OW_04s_eD6(nTA9W08mzEKI%7D{d+zDI8;>^#&HBPxjY$s zxs{5golhpwmc&S4+vUFZ$Vc!^_^4v2%2pB&w({ z4ZNGw^5aoua3XU``k-C@Jd=A=N^ag0A?y*(O#e$uQW`yv5cX$G0{J3lv5EdO_2fywB5_ef4*NV|_ie+e0r$Tr1OO-u}hZK{OI1#aSdxOoI%vLFli1w%>i0 z7FXgittJs@ZXYnr+E85m;!+ zv&v#2wi6WF>-?uvCHcKG4SwC+eAR2BmY2P;8CYHG%hiaFeZZZq)@nMmw0yAt=)7CM z1JO!oyI!XG_htL8%&T!`WKc&X&(w$94Zp5+TVNvCgkRS60bm*asQl&W@{QkF1XBd7Zy5ig^$69U7A&2x> zcK&Tj`ibX#o`dsHj0j5e3qP5u@Mc2ztON6x`VUgR;Tug9$fk9)KcI1#&}^cRP(6s# z>8PXa*Nmg8V!wC%N^a}bhC`%O=6VmoZY-i$!t%1Hh8oj>LNKPge?i zX+8VMa^hfzo(ZJbcmJ^}9?%Ttk9ABmlvFeAWi&#%hTG;eE4*kJwiIz!3XY4vIhaJQ z1~Uv=z=t&yjO_>aS=?mRb(enJ8927Cj2#|}zo2zMjzD>mJiVEmX1rH?WZ|&PlK3<< z=j|P#vuOuJHDuX*ykX&o1fxXis4-H+RTR&fAS|$c=ARx}?Ozs^ubrV-e417zyq766 z3n`NC@b?1Q#tc@8mH1+=p@7U(=0`LAYo)}1?i}_?SbRMhj+l$Tc zpN?JkaC5KHcW7vu;QSesLdUmvJ%o41Oc?shG#&t0XWDLB#mNy@&Oc`FI5)!$Pi}kW zkP0&oAXgumo_44?v^h4Hd%?!q{FErNs5WzU+Mt^$Ib^UHyDO?_w{IX$KVj9gZuN!J zq^D>(_D;kUo=0!eyk@MKpq^aBv72+jFFm!gv})#atg~r3>D?y$`kbew=-+4&x=`9M z&{fDLw>wXgP)vV113vK=XW55+#X^hr=9KD-9%ZOJ<;(SeeoZgZdzUf+%a4~t^d40m zY4q%)PxQ}s6JvTkJO#WI;6ds@n(>^E)9zg@g1zU4UypLinO1XSX5GVctvRlTL&+@Z zQHSM-Jz*ozrhd8u)%}7FwK|kDw>|MsQ5)&C?pd|CC+;29L+csTx2F@54|6KG0oH9p&zvbCy5$u z0fR!g{{0qMV2z7`F1JRt2-cOKTL;tIzCcY z0&+U!`01`wb3FO3$?o6M6Y=7X!x|mYYHK^%LIv(Z!*@4@bO7)eGTtBmRzEc!sr)9-fV%{WNshz4qX)08yyI4_wUZ6nQ;g zRnv5Ihbu~q^;q6zN+)k47|K%VEV_87E>HC>lDVWgjqJ)3*raHN#ZhNvZbIf6nmntB z`8f!I+1UNnzO=!OM!DrM5Sh|wr4y=n2p{3Ih58B1rBOx{d?#zore^S zGGIN*8#_^4Cr^NzLiL94B%f_cNLZ6u6SKU72E3D-CXKDrQ?-t^1u8L@V{NQBn}Re) zQz9_;=Am)!=fjmM;uVh&CPBkV4(alzC~v&7FKMpuK>np(#sX=2!T&P*`=oIY`%x#Y zpJ-Q&Le_a4YGvi@yTaV-Mg=kwQDY?gFfvVRHvkXeiW{Oq>sW=YC{7Fbh)`s;Y&(0V zu_dDkzcNbDYm<>4PA@DxoE)z6cXM0hum1@VFQt2F2ZL!}8vpr=EJL^L5mn1f;4?!& z!JuGRUf3;wSL-$b{f)UJv(aL74mq7`*IH85JAd|q0m`@)x5cZ~k(ve59Z8Moc_P3- zJf)V+T2i&WT|ap*VsA|q)3uwTqK*lK>4>KC%qBa*`vT27c!#I3oy-HiD7 z&E<6RM*TrX8n&AA)P!01ctCAWVK<>mSi%Uaqq71EcFd8-`N zylGe0maFqrL{zDD=&KJe7rr3)(4%cbx_*qalqWiHAA7Mm-+Hm@x<7xRl-M2n#jc;U z?NND5fxI#&FBU|Xz_V?;zOVpqw+8>g3$G78KX*wbI}$wqITT{GR-c;~?#;TvgU^u1 z`K&%O_&}nVgNR#$hqg|d$QGqi&+mjF#8Oj9?ly4PFn=q9CAkgLmI$jyo!4x)pTbUk zkET0Ml=9V8Yr+t-^u584FTh5X z7fJ_^0^44uK)DCDLpZ4{k$qNR zyng-<4Q=fnXU@#dy%rgvX8o=*wR)Goe1C;pAH^}wU|HwB5Gj(}jGGIH6`|7M>i_ zlEgE54_QgOciw^hMGGC2{k?9BaiIHuzG1PM~iHO7=y^}6`Q zef}yU45fXut-mqC|3?!|wh}xy{s5?B`_(T;QE4NyAAkL!%2tB9x3|^EFW;dmY6^EV zh3b;A5%RclnfRY$O3%_xunIL(q~GNCUh+pGH*+YAmCN!e-*zbXKf%-AFEyWgcPz3YODf$RB6O@QxgneBdL5h|C?+qW@fU!NoNrn*UQi5x)@>brH{)&*nE1?@u zrp92W01upXaDh867MKYYXr?TG`1|Yl^YmpQEC@mP>;K)Z|L+$vo`ljFyEy3^nezO1 Oh}J88wK|n|pZ*802rtzD literal 0 HcmV?d00001 diff --git a/h2/docs/html/images/performance.png b/h2/docs/html/images/performance.png new file mode 100644 index 0000000000000000000000000000000000000000..368fbf77e67162428e3cfaa4de6bc20d4dca9799 GIT binary patch literal 2343 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%0DL&s>Vx5#0C1gBphT={>^zbT&G% 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 literal 0 HcmV?d00001 diff --git a/h2/docs/html/images/quickstart-4.png b/h2/docs/html/images/quickstart-4.png new file mode 100644 index 0000000000000000000000000000000000000000..08a02b5bedbd12d70ceb0e4eae7a6bed25e44a55 GIT binary patch literal 30387 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

    + +

    H2 Database Engine Cheat Sheet

    +
    + +

    Using H2

    +
    + +

    Documentation

    +

    +Reference: +SQL grammar, +functions, +data types, +tools, +API +
    +Features: +fulltext search, +encryption, +read-only +(zip/jar), +CSV, +auto-reconnect, +triggers, +user functions +

    + +

    Database URLs

    +

    +Embedded
    +jdbc:h2:~/test 'test' in the user home directory
    +jdbc:h2:/data/test 'test' in the directory /data
    +jdbc:h2:./test in the current(!) working directory
    +

    +In-Memory
    +jdbc:h2:mem:test multiple connections in one process, +database is removed when all connections are closed
    +jdbc:h2:mem:test;DB_CLOSE_DELAY=-1 multiple connections in one process, +database in not removed when all connections are closed +(may create a memory leak)
    +jdbc:h2:mem: unnamed private; one connection
    +

    +Server Mode
    +jdbc:h2:tcp://localhost/~/test user home dir
    +jdbc:h2:tcp://localhost//data/test or jdbc:h2:tcp://localhost/D:/data/test absolute dir
    +Server start:java -cp *.jar org.h2.tools.Server +

    +Settings
    +jdbc:h2:..;MODE=MySQL;DATABASE_TO_LOWER=TRUE +compatibility (or HSQLDB,...)
    +jdbc:h2:..;TRACE_LEVEL_FILE=3 log to *.trace.db
    +

    + +
    +
    + +

    Using the JDBC API

    +
    +Connection conn = DriverManager.
    +    getConnection("jdbc:h2:~/test");
    +conn.close();
    +
    + +

    Connection Pool

    +
    +import org.h2.jdbcx.JdbcConnectionPool;
    +JdbcConnectionPool cp = JdbcConnectionPool.
    +    create("jdbc:h2:~/test", "sa", "sa");
    +Connection conn = cp.getConnection();
    +conn.close(); cp.dispose();
    +
    + +

    Maven 2

    +
    +<dependency>
    +    <groupId>com.h2database</groupId>
    +    <artifactId>h2</artifactId>
    +    <version>2.1.212</version>
    +</dependency>
    +
    + +

    Hibernate

    +

    +hibernate.cfg.xml (or use the HSQLDialect): +

    +
    +<property name="dialect">
    +    org.hibernate.dialect.H2Dialect
    +</property>
    +
    + +

    TopLink and Glassfish

    +

    +Datasource class: org.h2.jdbcx.JdbcDataSource
    +oracle.toplink.essentials.platform.
    +database.H2Platform +

    + +
    + +